Commit d216f6809eb690b9a888c286cde68cda4d0c4cfa
Committed by
Jean Delvare
1 parent
d93ab78070
Exists in
master
and in
6 other branches
hwmon: (lm63) Expose automatic fan speed control lookup table
The LM63 and compatible devices have a lookup table to control the fan speed automatically. Expose it in sysfs. Values are cached for 5 seconds, independently of the other register values to avoid slowing down "sensors". We might make the table values writable in the future. Signed-off-by: Jean Delvare <khali@linux-fr.org> Tested-by: Guenter Roeck <guenter.roeck@ericsson.com> Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
Showing 2 changed files with 136 additions and 15 deletions Side-by-side Diff
Documentation/hwmon/lm63
... | ... | @@ -66,7 +66,8 @@ |
66 | 66 | |
67 | 67 | The lm63 driver will not update its values more frequently than configured with |
68 | 68 | the update_interval sysfs attribute; reading them more often will do no harm, |
69 | -but will return 'old' values. | |
69 | +but will return 'old' values. Values in the automatic fan control lookup table | |
70 | +(attributes pwm1_auto_*) have their own independent lifetime of 5 seconds. | |
70 | 71 | |
71 | 72 | The LM64 is effectively an LM63 with GPIO lines. The driver does not |
72 | 73 | support these GPIO lines at present. |
drivers/hwmon/lm63.c
... | ... | @@ -75,6 +75,9 @@ |
75 | 75 | |
76 | 76 | #define LM63_REG_PWM_VALUE 0x4C |
77 | 77 | #define LM63_REG_PWM_FREQ 0x4D |
78 | +#define LM63_REG_LUT_TEMP_HYST 0x4F | |
79 | +#define LM63_REG_LUT_TEMP(nr) (0x50 + 2 * (nr)) | |
80 | +#define LM63_REG_LUT_PWM(nr) (0x51 + 2 * (nr)) | |
78 | 81 | |
79 | 82 | #define LM63_REG_LOCAL_TEMP 0x00 |
80 | 83 | #define LM63_REG_LOCAL_HIGH 0x05 |
81 | 84 | |
... | ... | @@ -192,7 +195,9 @@ |
192 | 195 | struct device *hwmon_dev; |
193 | 196 | struct mutex update_lock; |
194 | 197 | char valid; /* zero until following fields are valid */ |
198 | + char lut_valid; /* zero until lut fields are valid */ | |
195 | 199 | unsigned long last_updated; /* in jiffies */ |
200 | + unsigned long lut_last_updated; /* in jiffies */ | |
196 | 201 | enum chips kind; |
197 | 202 | int temp2_offset; |
198 | 203 | |
199 | 204 | |
200 | 205 | |
201 | 206 | |
... | ... | @@ -204,18 +209,22 @@ |
204 | 209 | u16 fan[2]; /* 0: input |
205 | 210 | 1: low limit */ |
206 | 211 | u8 pwm1_freq; |
207 | - u8 pwm1_value; | |
208 | - s8 temp8[3]; /* 0: local input | |
212 | + u8 pwm1[9]; /* 0: current output | |
213 | + 1-8: lookup table */ | |
214 | + s8 temp8[11]; /* 0: local input | |
209 | 215 | 1: local high limit |
210 | - 2: remote critical limit */ | |
216 | + 2: remote critical limit | |
217 | + 3-10: lookup table */ | |
211 | 218 | s16 temp11[4]; /* 0: remote input |
212 | 219 | 1: remote low limit |
213 | 220 | 2: remote high limit |
214 | 221 | 3: remote offset */ |
215 | 222 | u16 temp11u; /* remote input (unsigned) */ |
216 | 223 | u8 temp2_crit_hyst; |
224 | + u8 lut_temp_hyst; | |
217 | 225 | u8 alarms; |
218 | 226 | bool pwm_highres; |
227 | + bool lut_temp_highres; | |
219 | 228 | bool remote_unsigned; /* true if unsigned remote upper limits */ |
220 | 229 | bool trutherm; |
221 | 230 | }; |
... | ... | @@ -227,6 +236,11 @@ |
227 | 236 | return TEMP8_FROM_REG(data->temp8[nr]); |
228 | 237 | } |
229 | 238 | |
239 | +static inline int lut_temp_from_reg(struct lm63_data *data, int nr) | |
240 | +{ | |
241 | + return data->temp8[nr] * (data->lut_temp_highres ? 500 : 1000); | |
242 | +} | |
243 | + | |
230 | 244 | /* |
231 | 245 | * Sysfs callback functions and files |
232 | 246 | */ |
233 | 247 | |
234 | 248 | |
235 | 249 | |
236 | 250 | |
... | ... | @@ -261,17 +275,19 @@ |
261 | 275 | return count; |
262 | 276 | } |
263 | 277 | |
264 | -static ssize_t show_pwm1(struct device *dev, struct device_attribute *dummy, | |
278 | +static ssize_t show_pwm1(struct device *dev, struct device_attribute *devattr, | |
265 | 279 | char *buf) |
266 | 280 | { |
281 | + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
267 | 282 | struct lm63_data *data = lm63_update_device(dev); |
283 | + int nr = attr->index; | |
268 | 284 | int pwm; |
269 | 285 | |
270 | 286 | if (data->pwm_highres) |
271 | - pwm = data->pwm1_value; | |
287 | + pwm = data->pwm1[nr]; | |
272 | 288 | else |
273 | - pwm = data->pwm1_value >= 2 * data->pwm1_freq ? | |
274 | - 255 : (data->pwm1_value * 255 + data->pwm1_freq) / | |
289 | + pwm = data->pwm1[nr] >= 2 * data->pwm1_freq ? | |
290 | + 255 : (data->pwm1[nr] * 255 + data->pwm1_freq) / | |
275 | 291 | (2 * data->pwm1_freq); |
276 | 292 | |
277 | 293 | return sprintf(buf, "%d\n", pwm); |
... | ... | @@ -294,9 +310,9 @@ |
294 | 310 | |
295 | 311 | val = SENSORS_LIMIT(val, 0, 255); |
296 | 312 | mutex_lock(&data->update_lock); |
297 | - data->pwm1_value = data->pwm_highres ? val : | |
298 | - (val * data->pwm1_freq * 2 + 127) / 255; | |
299 | - i2c_smbus_write_byte_data(client, LM63_REG_PWM_VALUE, data->pwm1_value); | |
313 | + data->pwm1[0] = data->pwm_highres ? val : | |
314 | + (val * data->pwm1_freq * 2 + 127) / 255; | |
315 | + i2c_smbus_write_byte_data(client, LM63_REG_PWM_VALUE, data->pwm1[0]); | |
300 | 316 | mutex_unlock(&data->update_lock); |
301 | 317 | return count; |
302 | 318 | } |
... | ... | @@ -333,6 +349,16 @@ |
333 | 349 | + data->temp2_offset); |
334 | 350 | } |
335 | 351 | |
352 | +static ssize_t show_lut_temp(struct device *dev, | |
353 | + struct device_attribute *devattr, | |
354 | + char *buf) | |
355 | +{ | |
356 | + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
357 | + struct lm63_data *data = lm63_update_device(dev); | |
358 | + return sprintf(buf, "%d\n", lut_temp_from_reg(data, attr->index) | |
359 | + + data->temp2_offset); | |
360 | +} | |
361 | + | |
336 | 362 | static ssize_t set_temp8(struct device *dev, struct device_attribute *devattr, |
337 | 363 | const char *buf, size_t count) |
338 | 364 | { |
... | ... | @@ -440,6 +466,17 @@ |
440 | 466 | - TEMP8_FROM_REG(data->temp2_crit_hyst)); |
441 | 467 | } |
442 | 468 | |
469 | +static ssize_t show_lut_temp_hyst(struct device *dev, | |
470 | + struct device_attribute *devattr, char *buf) | |
471 | +{ | |
472 | + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | |
473 | + struct lm63_data *data = lm63_update_device(dev); | |
474 | + | |
475 | + return sprintf(buf, "%d\n", lut_temp_from_reg(data, attr->index) | |
476 | + + data->temp2_offset | |
477 | + - TEMP8_FROM_REG(data->lut_temp_hyst)); | |
478 | +} | |
479 | + | |
443 | 480 | /* |
444 | 481 | * And now the other way around, user-space provides an absolute |
445 | 482 | * hysteresis value and we have to store a relative one |
446 | 483 | |
... | ... | @@ -574,8 +611,48 @@ |
574 | 611 | static SENSOR_DEVICE_ATTR(fan1_min, S_IWUSR | S_IRUGO, show_fan, |
575 | 612 | set_fan, 1); |
576 | 613 | |
577 | -static DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm1, set_pwm1); | |
614 | +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm1, set_pwm1, 0); | |
578 | 615 | static DEVICE_ATTR(pwm1_enable, S_IRUGO, show_pwm1_enable, NULL); |
616 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO, show_pwm1, NULL, 1); | |
617 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp, S_IRUGO, | |
618 | + show_lut_temp, NULL, 3); | |
619 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_temp_hyst, S_IRUGO, | |
620 | + show_lut_temp_hyst, NULL, 3); | |
621 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IRUGO, show_pwm1, NULL, 2); | |
622 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_temp, S_IRUGO, | |
623 | + show_lut_temp, NULL, 4); | |
624 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_temp_hyst, S_IRUGO, | |
625 | + show_lut_temp_hyst, NULL, 4); | |
626 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO, show_pwm1, NULL, 3); | |
627 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point3_temp, S_IRUGO, | |
628 | + show_lut_temp, NULL, 5); | |
629 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point3_temp_hyst, S_IRUGO, | |
630 | + show_lut_temp_hyst, NULL, 5); | |
631 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point4_pwm, S_IRUGO, show_pwm1, NULL, 4); | |
632 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point4_temp, S_IRUGO, | |
633 | + show_lut_temp, NULL, 6); | |
634 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point4_temp_hyst, S_IRUGO, | |
635 | + show_lut_temp_hyst, NULL, 6); | |
636 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point5_pwm, S_IRUGO, show_pwm1, NULL, 5); | |
637 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point5_temp, S_IRUGO, | |
638 | + show_lut_temp, NULL, 7); | |
639 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point5_temp_hyst, S_IRUGO, | |
640 | + show_lut_temp_hyst, NULL, 7); | |
641 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point6_pwm, S_IRUGO, show_pwm1, NULL, 6); | |
642 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point6_temp, S_IRUGO, | |
643 | + show_lut_temp, NULL, 8); | |
644 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point6_temp_hyst, S_IRUGO, | |
645 | + show_lut_temp_hyst, NULL, 8); | |
646 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point7_pwm, S_IRUGO, show_pwm1, NULL, 7); | |
647 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point7_temp, S_IRUGO, | |
648 | + show_lut_temp, NULL, 9); | |
649 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point7_temp_hyst, S_IRUGO, | |
650 | + show_lut_temp_hyst, NULL, 9); | |
651 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point8_pwm, S_IRUGO, show_pwm1, NULL, 8); | |
652 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point8_temp, S_IRUGO, | |
653 | + show_lut_temp, NULL, 10); | |
654 | +static SENSOR_DEVICE_ATTR(pwm1_auto_point8_temp_hyst, S_IRUGO, | |
655 | + show_lut_temp_hyst, NULL, 10); | |
579 | 656 | |
580 | 657 | static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_local_temp8, NULL, 0); |
581 | 658 | static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_local_temp8, |
582 | 659 | |
... | ... | @@ -609,8 +686,33 @@ |
609 | 686 | set_update_interval); |
610 | 687 | |
611 | 688 | static struct attribute *lm63_attributes[] = { |
612 | - &dev_attr_pwm1.attr, | |
689 | + &sensor_dev_attr_pwm1.dev_attr.attr, | |
613 | 690 | &dev_attr_pwm1_enable.attr, |
691 | + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, | |
692 | + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, | |
693 | + &sensor_dev_attr_pwm1_auto_point1_temp_hyst.dev_attr.attr, | |
694 | + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, | |
695 | + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, | |
696 | + &sensor_dev_attr_pwm1_auto_point2_temp_hyst.dev_attr.attr, | |
697 | + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, | |
698 | + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, | |
699 | + &sensor_dev_attr_pwm1_auto_point3_temp_hyst.dev_attr.attr, | |
700 | + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr, | |
701 | + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, | |
702 | + &sensor_dev_attr_pwm1_auto_point4_temp_hyst.dev_attr.attr, | |
703 | + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr, | |
704 | + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr, | |
705 | + &sensor_dev_attr_pwm1_auto_point5_temp_hyst.dev_attr.attr, | |
706 | + &sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr, | |
707 | + &sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr, | |
708 | + &sensor_dev_attr_pwm1_auto_point6_temp_hyst.dev_attr.attr, | |
709 | + &sensor_dev_attr_pwm1_auto_point7_pwm.dev_attr.attr, | |
710 | + &sensor_dev_attr_pwm1_auto_point7_temp.dev_attr.attr, | |
711 | + &sensor_dev_attr_pwm1_auto_point7_temp_hyst.dev_attr.attr, | |
712 | + &sensor_dev_attr_pwm1_auto_point8_pwm.dev_attr.attr, | |
713 | + &sensor_dev_attr_pwm1_auto_point8_temp.dev_attr.attr, | |
714 | + &sensor_dev_attr_pwm1_auto_point8_temp_hyst.dev_attr.attr, | |
715 | + | |
614 | 716 | &sensor_dev_attr_temp1_input.dev_attr.attr, |
615 | 717 | &sensor_dev_attr_temp2_input.dev_attr.attr, |
616 | 718 | &sensor_dev_attr_temp2_min.dev_attr.attr, |
... | ... | @@ -834,6 +936,8 @@ |
834 | 936 | u8 config_enhanced |
835 | 937 | = i2c_smbus_read_byte_data(client, |
836 | 938 | LM96163_REG_CONFIG_ENHANCED); |
939 | + if (config_enhanced & 0x20) | |
940 | + data->lut_temp_highres = true; | |
837 | 941 | if ((config_enhanced & 0x10) |
838 | 942 | && !(data->config_fan & 0x08) && data->pwm1_freq == 8) |
839 | 943 | data->pwm_highres = true; |
... | ... | @@ -872,6 +976,7 @@ |
872 | 976 | struct i2c_client *client = to_i2c_client(dev); |
873 | 977 | struct lm63_data *data = i2c_get_clientdata(client); |
874 | 978 | unsigned long next_update; |
979 | + int i; | |
875 | 980 | |
876 | 981 | mutex_lock(&data->update_lock); |
877 | 982 | |
... | ... | @@ -895,8 +1000,8 @@ |
895 | 1000 | LM63_REG_PWM_FREQ); |
896 | 1001 | if (data->pwm1_freq == 0) |
897 | 1002 | data->pwm1_freq = 1; |
898 | - data->pwm1_value = i2c_smbus_read_byte_data(client, | |
899 | - LM63_REG_PWM_VALUE); | |
1003 | + data->pwm1[0] = i2c_smbus_read_byte_data(client, | |
1004 | + LM63_REG_PWM_VALUE); | |
900 | 1005 | |
901 | 1006 | data->temp8[0] = i2c_smbus_read_byte_data(client, |
902 | 1007 | LM63_REG_LOCAL_TEMP); |
... | ... | @@ -937,6 +1042,21 @@ |
937 | 1042 | |
938 | 1043 | data->last_updated = jiffies; |
939 | 1044 | data->valid = 1; |
1045 | + } | |
1046 | + | |
1047 | + if (time_after(jiffies, data->lut_last_updated + 5 * HZ) || | |
1048 | + !data->lut_valid) { | |
1049 | + for (i = 0; i < 8; i++) { | |
1050 | + data->pwm1[1 + i] = i2c_smbus_read_byte_data(client, | |
1051 | + LM63_REG_LUT_PWM(i)); | |
1052 | + data->temp8[3 + i] = i2c_smbus_read_byte_data(client, | |
1053 | + LM63_REG_LUT_TEMP(i)); | |
1054 | + } | |
1055 | + data->lut_temp_hyst = i2c_smbus_read_byte_data(client, | |
1056 | + LM63_REG_LUT_TEMP_HYST); | |
1057 | + | |
1058 | + data->lut_last_updated = jiffies; | |
1059 | + data->lut_valid = 1; | |
940 | 1060 | } |
941 | 1061 | |
942 | 1062 | mutex_unlock(&data->update_lock); |