diff --git a/drivers/hwmon/lm63.c b/drivers/hwmon/lm63.c index a0d77a7386a9..e2a429e579ac 100644 --- a/drivers/hwmon/lm63.c +++ b/drivers/hwmon/lm63.c @@ -92,6 +92,9 @@ static const unsigned short normal_i2c[] = { 0x18, 0x4c, 0x4e, I2C_CLIENT_END }; #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 @@ -455,6 +458,91 @@ static ssize_t pwm1_enable_store(struct device *dev, 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, @@ -629,6 +717,47 @@ static ssize_t show_lut_temp_hyst(struct device *dev, 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 @@ -764,6 +893,8 @@ static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan, 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, @@ -869,6 +1000,8 @@ static DEVICE_ATTR_RW(update_interval); 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,