#define LM96163_REG_REMOTE_TEMP_U_LSB 0x32
#define LM96163_REG_CONFIG_ENHANCED 0x45
+#define LM63_PWM_BASE_FAST_HZ 180000
+#define LM63_PWM_BASE_SLOW_HZ 700
+
#define LM63_MAX_CONVRATE 9
#define LM63_MAX_CONVRATE_HZ 32
return count;
}
+static ssize_t pwm1_freq_show(struct device *dev,
+ struct device_attribute *dummy, char *buf)
+{
+ struct lm63_data *data = lm63_update_device(dev);
+ unsigned int base, freq;
+
+ mutex_lock(&data->update_lock);
+ base = (data->config_fan & 0x08) ?
+ LM63_PWM_BASE_SLOW_HZ : LM63_PWM_BASE_FAST_HZ;
+ freq = data->pwm1_freq;
+ mutex_unlock(&data->update_lock);
+
+ return sprintf(buf, "%u\n", base / freq);
+}
+
+/*
+ * Pick the closest CONFIG_FAN.SCS + PFR for the requested frequency.
+ * PWM_FREQ writes need the LUT unlocked, same as set_pwm1(). LUT PWM
+ * bytes are register-relative; rewrite them after a frequency change
+ * if duty cycles must be preserved.
+ */
+static ssize_t pwm1_freq_store(struct device *dev,
+ struct device_attribute *dummy,
+ const char *buf, size_t count)
+{
+ struct lm63_data *data = dev_get_drvdata(dev);
+ struct i2c_client *client = data->client;
+ unsigned long val, pfr_fast, pfr_slow, err_fast, err_slow, pfr;
+ bool slow_clock;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+ if (val == 0)
+ return -EINVAL;
+
+ pfr_fast = clamp_val(DIV_ROUND_CLOSEST((unsigned long)LM63_PWM_BASE_FAST_HZ, val),
+ 1UL, 31UL);
+ pfr_slow = clamp_val(DIV_ROUND_CLOSEST((unsigned long)LM63_PWM_BASE_SLOW_HZ, val),
+ 1UL, 31UL);
+ err_fast = abs_diff(LM63_PWM_BASE_FAST_HZ / pfr_fast, val);
+ err_slow = abs_diff(LM63_PWM_BASE_SLOW_HZ / pfr_slow, val);
+
+ if (err_slow < err_fast) {
+ slow_clock = true;
+ pfr = pfr_slow;
+ } else {
+ slow_clock = false;
+ pfr = pfr_fast;
+ }
+
+ mutex_lock(&data->update_lock);
+ ret = i2c_smbus_read_byte_data(client, LM63_REG_CONFIG_FAN);
+ if (ret < 0) {
+ mutex_unlock(&data->update_lock);
+ return ret;
+ }
+ data->config_fan = ret;
+
+ if (!(data->config_fan & 0x20)) { /* register is read-only */
+ mutex_unlock(&data->update_lock);
+ return -EPERM;
+ }
+
+ if (data->kind == lm96163) {
+ ret = i2c_smbus_read_byte_data(client, LM96163_REG_CONFIG_ENHANCED);
+ if (ret < 0) {
+ mutex_unlock(&data->update_lock);
+ return ret;
+ }
+ data->pwm_highres = !slow_clock && pfr == 8 && (ret & 0x10);
+ }
+
+ if (slow_clock)
+ data->config_fan |= 0x08;
+ else
+ data->config_fan &= ~0x08;
+ i2c_smbus_write_byte_data(client, LM63_REG_CONFIG_FAN, data->config_fan);
+ i2c_smbus_write_byte_data(client, LM63_REG_PWM_FREQ, pfr);
+ data->pwm1_freq = pfr;
+ mutex_unlock(&data->update_lock);
+ return count;
+}
+
/*
* There are 8bit registers for both local(temp1) and remote(temp2) sensor.
* For remote sensor registers temp2_offset has to be considered,
return sprintf(buf, "%d\n", temp);
}
+/*
+ * The LM63 has a single hysteresis register shared by all LUT entries.
+ * Expose it as a chip-wide hysteresis amount in millidegrees; the
+ * per-point pwm1_auto_pointN_temp_hyst attributes remain read-only and
+ * show the resulting absolute trip-down temperature for each entry.
+ */
+static ssize_t pwm1_auto_point_temp_hyst_show(struct device *dev,
+ struct device_attribute *dummy,
+ char *buf)
+{
+ struct lm63_data *data = lm63_update_device(dev);
+ u8 hyst;
+
+ mutex_lock(&data->update_lock);
+ hyst = data->lut_temp_hyst;
+ mutex_unlock(&data->update_lock);
+
+ return sprintf(buf, "%d\n", TEMP8_FROM_REG(hyst));
+}
+
+static ssize_t pwm1_auto_point_temp_hyst_store(struct device *dev,
+ struct device_attribute *dummy,
+ const char *buf, size_t count)
+{
+ struct lm63_data *data = dev_get_drvdata(dev);
+ struct i2c_client *client = data->client;
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 10, &val);
+ if (err)
+ return err;
+
+ mutex_lock(&data->update_lock);
+ data->lut_temp_hyst = HYST_TO_REG(val);
+ i2c_smbus_write_byte_data(client, LM63_REG_LUT_TEMP_HYST,
+ data->lut_temp_hyst);
+ mutex_unlock(&data->update_lock);
+ return count;
+}
+
/*
* And now the other way around, user-space provides an absolute
* hysteresis value and we have to store a relative one
static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm1, set_pwm1, 0);
static DEVICE_ATTR_RW(pwm1_enable);
+static DEVICE_ATTR_RW(pwm1_freq);
+static DEVICE_ATTR_RW(pwm1_auto_point_temp_hyst);
static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IWUSR | S_IRUGO,
show_pwm1, set_pwm1, 1);
static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp, S_IWUSR | S_IRUGO,
static struct attribute *lm63_attributes[] = {
&sensor_dev_attr_pwm1.dev_attr.attr,
&dev_attr_pwm1_enable.attr,
+ &dev_attr_pwm1_freq.attr,
+ &dev_attr_pwm1_auto_point_temp_hyst.attr,
&sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr,
&sensor_dev_attr_pwm1_auto_point1_temp_hyst.dev_attr.attr,