mirror of
https://github.com/torvalds/linux.git
synced 2026-07-28 01:55:51 +02:00
hwmon: (lm63) expose PWM frequency and LUT hysteresis as writable
The driver caches the PWM frequency register and the CONFIG_FAN slow-clock select bit, but never lets userspace pick a different output frequency. Add a pwm1_freq sysfs attribute that selects the closest SCS + PFR combination for the requested value in Hz, gated by manual mode like set_pwm1(). PFR is clamped to 31 so that 2*PFR fits in the chip's 6-bit PWM register (matching the existing scaling assumption in show_pwm1). The hardware LUT hysteresis register is shared by all LUT entries, so the per-point pwm1_auto_pointN_temp_hyst attributes can't be made RW without N-to-1 cross-attribute side effects. Following the max31760 precedent, expose a single chip-wide pwm1_auto_point_temp_hyst attribute holding the hysteresis amount in millidegrees; the per-point attributes stay RO and continue to show the resulting absolute trip-down temperature for each entry. This was tested on a Linksys LGS328MPC switch hardware where the fan would not spin with the default PWM Frequency, which is why this change is required. Signed-off-by: Jan-Henrik Bruhn <kernel@jhbruhn.de> Link: https://lore.kernel.org/r/20260523133617.3439102-1-kernel@jhbruhn.de Signed-off-by: Guenter Roeck <linux@roeck-us.net>
This commit is contained in:
parent
22c1992642
commit
964acc3456
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user