diff options
Diffstat (limited to 'drivers/hwmon')
-rw-r--r-- | drivers/hwmon/Kconfig | 3 | ||||
-rw-r--r-- | drivers/hwmon/pc87427.c | 271 |
2 files changed, 271 insertions, 3 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index d50e9fa8016f..ea3d8dff684b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig | |||
@@ -711,7 +711,8 @@ config SENSORS_PC87427 | |||
711 | functions of the National Semiconductor PC87427 Super-I/O chip. | 711 | functions of the National Semiconductor PC87427 Super-I/O chip. |
712 | The chip has two distinct logical devices, one for fan speed | 712 | The chip has two distinct logical devices, one for fan speed |
713 | monitoring and control, and one for voltage and temperature | 713 | monitoring and control, and one for voltage and temperature |
714 | monitoring. Only fan speed monitoring is supported right now. | 714 | monitoring. Only fan speed monitoring and control is supported |
715 | right now. | ||
715 | 716 | ||
716 | This driver can also be built as a module. If so, the module | 717 | This driver can also be built as a module. If so, the module |
717 | will be called pc87427. | 718 | will be called pc87427. |
diff --git a/drivers/hwmon/pc87427.c b/drivers/hwmon/pc87427.c index 0ac55ba6c6b8..869822785b4f 100644 --- a/drivers/hwmon/pc87427.c +++ b/drivers/hwmon/pc87427.c | |||
@@ -15,10 +15,10 @@ | |||
15 | * Supports the following chips: | 15 | * Supports the following chips: |
16 | * | 16 | * |
17 | * Chip #vin #fan #pwm #temp devid | 17 | * Chip #vin #fan #pwm #temp devid |
18 | * PC87427 - 8 - - 0xF2 | 18 | * PC87427 - 8 4 - 0xF2 |
19 | * | 19 | * |
20 | * This driver assumes that no more than one chip is present. | 20 | * This driver assumes that no more than one chip is present. |
21 | * Only fan inputs are supported so far, although the chip can do much more. | 21 | * Only fans are supported so far, although the chip can do much more. |
22 | */ | 22 | */ |
23 | 23 | ||
24 | #include <linux/module.h> | 24 | #include <linux/module.h> |
@@ -57,10 +57,16 @@ struct pc87427_data { | |||
57 | u16 fan[8]; /* register values */ | 57 | u16 fan[8]; /* register values */ |
58 | u16 fan_min[8]; /* register values */ | 58 | u16 fan_min[8]; /* register values */ |
59 | u8 fan_status[8]; /* register values */ | 59 | u8 fan_status[8]; /* register values */ |
60 | |||
61 | u8 pwm_enabled; /* bit vector */ | ||
62 | u8 pwm_auto_ok; /* bit vector */ | ||
63 | u8 pwm_enable[4]; /* register values */ | ||
64 | u8 pwm[4]; /* register values */ | ||
60 | }; | 65 | }; |
61 | 66 | ||
62 | struct pc87427_sio_data { | 67 | struct pc87427_sio_data { |
63 | u8 has_fanin; | 68 | u8 has_fanin; |
69 | u8 has_fanout; | ||
64 | }; | 70 | }; |
65 | 71 | ||
66 | /* | 72 | /* |
@@ -72,7 +78,9 @@ struct pc87427_sio_data { | |||
72 | #define SIOREG_CF2 0x22 /* Configuration 2 */ | 78 | #define SIOREG_CF2 0x22 /* Configuration 2 */ |
73 | #define SIOREG_CF3 0x23 /* Configuration 3 */ | 79 | #define SIOREG_CF3 0x23 /* Configuration 3 */ |
74 | #define SIOREG_CF4 0x24 /* Configuration 4 */ | 80 | #define SIOREG_CF4 0x24 /* Configuration 4 */ |
81 | #define SIOREG_CF5 0x25 /* Configuration 5 */ | ||
75 | #define SIOREG_CFB 0x2B /* Configuration B */ | 82 | #define SIOREG_CFB 0x2B /* Configuration B */ |
83 | #define SIOREG_CFC 0x2C /* Configuration C */ | ||
76 | #define SIOREG_CFD 0x2D /* Configuration D */ | 84 | #define SIOREG_CFD 0x2D /* Configuration D */ |
77 | #define SIOREG_ACT 0x30 /* Device activation */ | 85 | #define SIOREG_ACT 0x30 /* Device activation */ |
78 | #define SIOREG_MAP 0x50 /* I/O or memory mapping */ | 86 | #define SIOREG_MAP 0x50 /* I/O or memory mapping */ |
@@ -188,6 +196,61 @@ static inline u16 fan_to_reg(unsigned long val) | |||
188 | } | 196 | } |
189 | 197 | ||
190 | /* | 198 | /* |
199 | * PWM registers and conversions | ||
200 | */ | ||
201 | |||
202 | #define PC87427_REG_PWM_ENABLE 0x10 | ||
203 | #define PC87427_REG_PWM_DUTY 0x12 | ||
204 | |||
205 | #define PWM_ENABLE_MODE_MASK (7 << 4) | ||
206 | #define PWM_ENABLE_CTLEN (1 << 0) | ||
207 | |||
208 | #define PWM_MODE_MANUAL (0 << 4) | ||
209 | #define PWM_MODE_AUTO (1 << 4) | ||
210 | #define PWM_MODE_OFF (2 << 4) | ||
211 | #define PWM_MODE_ON (7 << 4) | ||
212 | |||
213 | /* Dedicated function to read all registers related to a given PWM output. | ||
214 | This saves us quite a few locks and bank selections. | ||
215 | Must be called with data->lock held. | ||
216 | nr is from 0 to 3 */ | ||
217 | static void pc87427_readall_pwm(struct pc87427_data *data, u8 nr) | ||
218 | { | ||
219 | int iobase = data->address[LD_FAN]; | ||
220 | |||
221 | outb(BANK_FC(nr), iobase + PC87427_REG_BANK); | ||
222 | data->pwm_enable[nr] = inb(iobase + PC87427_REG_PWM_ENABLE); | ||
223 | data->pwm[nr] = inb(iobase + PC87427_REG_PWM_DUTY); | ||
224 | } | ||
225 | |||
226 | static inline int pwm_enable_from_reg(u8 reg) | ||
227 | { | ||
228 | switch (reg & PWM_ENABLE_MODE_MASK) { | ||
229 | case PWM_MODE_ON: | ||
230 | return 0; | ||
231 | case PWM_MODE_MANUAL: | ||
232 | case PWM_MODE_OFF: | ||
233 | return 1; | ||
234 | case PWM_MODE_AUTO: | ||
235 | return 2; | ||
236 | default: | ||
237 | return -EPROTO; | ||
238 | } | ||
239 | } | ||
240 | |||
241 | static inline u8 pwm_enable_to_reg(unsigned long val, u8 pwmval) | ||
242 | { | ||
243 | switch (val) { | ||
244 | default: | ||
245 | return PWM_MODE_ON; | ||
246 | case 1: | ||
247 | return pwmval ? PWM_MODE_MANUAL : PWM_MODE_OFF; | ||
248 | case 2: | ||
249 | return PWM_MODE_AUTO; | ||
250 | } | ||
251 | } | ||
252 | |||
253 | /* | ||
191 | * Data interface | 254 | * Data interface |
192 | */ | 255 | */ |
193 | 256 | ||
@@ -207,6 +270,14 @@ static struct pc87427_data *pc87427_update_device(struct device *dev) | |||
207 | continue; | 270 | continue; |
208 | pc87427_readall_fan(data, i); | 271 | pc87427_readall_fan(data, i); |
209 | } | 272 | } |
273 | |||
274 | /* PWM outputs */ | ||
275 | for (i = 0; i < 4; i++) { | ||
276 | if (!(data->pwm_enabled & (1 << i))) | ||
277 | continue; | ||
278 | pc87427_readall_pwm(data, i); | ||
279 | } | ||
280 | |||
210 | data->last_updated = jiffies; | 281 | data->last_updated = jiffies; |
211 | 282 | ||
212 | done: | 283 | done: |
@@ -384,6 +455,145 @@ static const struct attribute_group pc87427_group_fan[8] = { | |||
384 | { .attrs = pc87427_attributes_fan[7] }, | 455 | { .attrs = pc87427_attributes_fan[7] }, |
385 | }; | 456 | }; |
386 | 457 | ||
458 | /* Must be called with data->lock held and pc87427_readall_pwm() freshly | ||
459 | called */ | ||
460 | static void update_pwm_enable(struct pc87427_data *data, int nr, u8 mode) | ||
461 | { | ||
462 | int iobase = data->address[LD_FAN]; | ||
463 | data->pwm_enable[nr] &= ~PWM_ENABLE_MODE_MASK; | ||
464 | data->pwm_enable[nr] |= mode; | ||
465 | outb(data->pwm_enable[nr], iobase + PC87427_REG_PWM_ENABLE); | ||
466 | } | ||
467 | |||
468 | static ssize_t show_pwm_enable(struct device *dev, struct device_attribute | ||
469 | *devattr, char *buf) | ||
470 | { | ||
471 | struct pc87427_data *data = pc87427_update_device(dev); | ||
472 | int nr = to_sensor_dev_attr(devattr)->index; | ||
473 | int pwm_enable; | ||
474 | |||
475 | pwm_enable = pwm_enable_from_reg(data->pwm_enable[nr]); | ||
476 | if (pwm_enable < 0) | ||
477 | return pwm_enable; | ||
478 | return sprintf(buf, "%d\n", pwm_enable); | ||
479 | } | ||
480 | |||
481 | static ssize_t set_pwm_enable(struct device *dev, struct device_attribute | ||
482 | *devattr, const char *buf, size_t count) | ||
483 | { | ||
484 | struct pc87427_data *data = dev_get_drvdata(dev); | ||
485 | int nr = to_sensor_dev_attr(devattr)->index; | ||
486 | unsigned long val; | ||
487 | |||
488 | if (strict_strtoul(buf, 10, &val) < 0 || val > 2) | ||
489 | return -EINVAL; | ||
490 | /* Can't go to automatic mode if it isn't configured */ | ||
491 | if (val == 2 && !(data->pwm_auto_ok & (1 << nr))) | ||
492 | return -EINVAL; | ||
493 | |||
494 | mutex_lock(&data->lock); | ||
495 | pc87427_readall_pwm(data, nr); | ||
496 | update_pwm_enable(data, nr, pwm_enable_to_reg(val, data->pwm[nr])); | ||
497 | mutex_unlock(&data->lock); | ||
498 | |||
499 | return count; | ||
500 | } | ||
501 | |||
502 | static ssize_t show_pwm(struct device *dev, struct device_attribute | ||
503 | *devattr, char *buf) | ||
504 | { | ||
505 | struct pc87427_data *data = pc87427_update_device(dev); | ||
506 | int nr = to_sensor_dev_attr(devattr)->index; | ||
507 | |||
508 | return sprintf(buf, "%d\n", (int)data->pwm[nr]); | ||
509 | } | ||
510 | |||
511 | static ssize_t set_pwm(struct device *dev, struct device_attribute | ||
512 | *devattr, const char *buf, size_t count) | ||
513 | { | ||
514 | struct pc87427_data *data = dev_get_drvdata(dev); | ||
515 | int nr = to_sensor_dev_attr(devattr)->index; | ||
516 | unsigned long val; | ||
517 | int iobase = data->address[LD_FAN]; | ||
518 | u8 mode; | ||
519 | |||
520 | if (strict_strtoul(buf, 10, &val) < 0 || val > 0xff) | ||
521 | return -EINVAL; | ||
522 | |||
523 | mutex_lock(&data->lock); | ||
524 | pc87427_readall_pwm(data, nr); | ||
525 | mode = data->pwm_enable[nr] & PWM_ENABLE_MODE_MASK; | ||
526 | if (mode != PWM_MODE_MANUAL && mode != PWM_MODE_OFF) { | ||
527 | dev_notice(dev, "Can't set PWM%d duty cycle while not in " | ||
528 | "manual mode\n", nr + 1); | ||
529 | mutex_unlock(&data->lock); | ||
530 | return -EPERM; | ||
531 | } | ||
532 | |||
533 | /* We may have to change the mode */ | ||
534 | if (mode == PWM_MODE_MANUAL && val == 0) { | ||
535 | /* Transition from Manual to Off */ | ||
536 | update_pwm_enable(data, nr, PWM_MODE_OFF); | ||
537 | mode = PWM_MODE_OFF; | ||
538 | dev_dbg(dev, "Switching PWM%d from %s to %s\n", nr + 1, | ||
539 | "manual", "off"); | ||
540 | } else if (mode == PWM_MODE_OFF && val != 0) { | ||
541 | /* Transition from Off to Manual */ | ||
542 | update_pwm_enable(data, nr, PWM_MODE_MANUAL); | ||
543 | mode = PWM_MODE_MANUAL; | ||
544 | dev_dbg(dev, "Switching PWM%d from %s to %s\n", nr + 1, | ||
545 | "off", "manual"); | ||
546 | } | ||
547 | |||
548 | data->pwm[nr] = val; | ||
549 | if (mode == PWM_MODE_MANUAL) | ||
550 | outb(val, iobase + PC87427_REG_PWM_DUTY); | ||
551 | mutex_unlock(&data->lock); | ||
552 | |||
553 | return count; | ||
554 | } | ||
555 | |||
556 | static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, | ||
557 | show_pwm_enable, set_pwm_enable, 0); | ||
558 | static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, | ||
559 | show_pwm_enable, set_pwm_enable, 1); | ||
560 | static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, | ||
561 | show_pwm_enable, set_pwm_enable, 2); | ||
562 | static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO, | ||
563 | show_pwm_enable, set_pwm_enable, 3); | ||
564 | |||
565 | static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0); | ||
566 | static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1); | ||
567 | static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 2); | ||
568 | static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 3); | ||
569 | |||
570 | static struct attribute *pc87427_attributes_pwm[4][3] = { | ||
571 | { | ||
572 | &sensor_dev_attr_pwm1_enable.dev_attr.attr, | ||
573 | &sensor_dev_attr_pwm1.dev_attr.attr, | ||
574 | NULL | ||
575 | }, { | ||
576 | &sensor_dev_attr_pwm2_enable.dev_attr.attr, | ||
577 | &sensor_dev_attr_pwm2.dev_attr.attr, | ||
578 | NULL | ||
579 | }, { | ||
580 | &sensor_dev_attr_pwm3_enable.dev_attr.attr, | ||
581 | &sensor_dev_attr_pwm3.dev_attr.attr, | ||
582 | NULL | ||
583 | }, { | ||
584 | &sensor_dev_attr_pwm4_enable.dev_attr.attr, | ||
585 | &sensor_dev_attr_pwm4.dev_attr.attr, | ||
586 | NULL | ||
587 | } | ||
588 | }; | ||
589 | |||
590 | static const struct attribute_group pc87427_group_pwm[4] = { | ||
591 | { .attrs = pc87427_attributes_pwm[0] }, | ||
592 | { .attrs = pc87427_attributes_pwm[1] }, | ||
593 | { .attrs = pc87427_attributes_pwm[2] }, | ||
594 | { .attrs = pc87427_attributes_pwm[3] }, | ||
595 | }; | ||
596 | |||
387 | static ssize_t show_name(struct device *dev, struct device_attribute | 597 | static ssize_t show_name(struct device *dev, struct device_attribute |
388 | *devattr, char *buf) | 598 | *devattr, char *buf) |
389 | { | 599 | { |
@@ -431,6 +641,25 @@ static void __devinit pc87427_init_device(struct device *dev) | |||
431 | } | 641 | } |
432 | data->fan_enabled = sio_data->has_fanin; | 642 | data->fan_enabled = sio_data->has_fanin; |
433 | } | 643 | } |
644 | |||
645 | /* Check which PWM outputs are enabled */ | ||
646 | for (i = 0; i < 4; i++) { | ||
647 | if (!(sio_data->has_fanout & (1 << i))) /* Not wired */ | ||
648 | continue; | ||
649 | reg = pc87427_read8_bank(data, LD_FAN, BANK_FC(i), | ||
650 | PC87427_REG_PWM_ENABLE); | ||
651 | if (reg & PWM_ENABLE_CTLEN) | ||
652 | data->pwm_enabled |= (1 << i); | ||
653 | |||
654 | /* We don't expose an interface to reconfigure the automatic | ||
655 | fan control mode, so only allow to return to this mode if | ||
656 | it was originally set. */ | ||
657 | if ((reg & PWM_ENABLE_MODE_MASK) == PWM_MODE_AUTO) { | ||
658 | dev_dbg(dev, "PWM%d is in automatic control mode\n", | ||
659 | i + 1); | ||
660 | data->pwm_auto_ok |= (1 << i); | ||
661 | } | ||
662 | } | ||
434 | } | 663 | } |
435 | 664 | ||
436 | static int __devinit pc87427_probe(struct platform_device *pdev) | 665 | static int __devinit pc87427_probe(struct platform_device *pdev) |
@@ -474,6 +703,14 @@ static int __devinit pc87427_probe(struct platform_device *pdev) | |||
474 | if (err) | 703 | if (err) |
475 | goto exit_remove_files; | 704 | goto exit_remove_files; |
476 | } | 705 | } |
706 | for (i = 0; i < 4; i++) { | ||
707 | if (!(data->pwm_enabled & (1 << i))) | ||
708 | continue; | ||
709 | err = sysfs_create_group(&pdev->dev.kobj, | ||
710 | &pc87427_group_pwm[i]); | ||
711 | if (err) | ||
712 | goto exit_remove_files; | ||
713 | } | ||
477 | 714 | ||
478 | data->hwmon_dev = hwmon_device_register(&pdev->dev); | 715 | data->hwmon_dev = hwmon_device_register(&pdev->dev); |
479 | if (IS_ERR(data->hwmon_dev)) { | 716 | if (IS_ERR(data->hwmon_dev)) { |
@@ -490,6 +727,11 @@ exit_remove_files: | |||
490 | continue; | 727 | continue; |
491 | sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]); | 728 | sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]); |
492 | } | 729 | } |
730 | for (i = 0; i < 4; i++) { | ||
731 | if (!(data->pwm_enabled & (1 << i))) | ||
732 | continue; | ||
733 | sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_pwm[i]); | ||
734 | } | ||
493 | exit_release_region: | 735 | exit_release_region: |
494 | release_region(res->start, resource_size(res)); | 736 | release_region(res->start, resource_size(res)); |
495 | exit_kfree: | 737 | exit_kfree: |
@@ -512,6 +754,11 @@ static int __devexit pc87427_remove(struct platform_device *pdev) | |||
512 | continue; | 754 | continue; |
513 | sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]); | 755 | sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]); |
514 | } | 756 | } |
757 | for (i = 0; i < 4; i++) { | ||
758 | if (!(data->pwm_enabled & (1 << i))) | ||
759 | continue; | ||
760 | sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_pwm[i]); | ||
761 | } | ||
515 | platform_set_drvdata(pdev, NULL); | 762 | platform_set_drvdata(pdev, NULL); |
516 | kfree(data); | 763 | kfree(data); |
517 | 764 | ||
@@ -648,6 +895,26 @@ static int __init pc87427_find(int sioaddr, unsigned short *address, | |||
648 | if ((cfg & (1 << 3)) && !(cfg_b & (1 << 5))) | 895 | if ((cfg & (1 << 3)) && !(cfg_b & (1 << 5))) |
649 | sio_data->has_fanin |= (1 << 6); /* FANIN6 */ | 896 | sio_data->has_fanin |= (1 << 6); /* FANIN6 */ |
650 | 897 | ||
898 | /* Check which fan outputs are wired */ | ||
899 | sio_data->has_fanout = (1 << 0); /* FANOUT0 */ | ||
900 | if (cfg_b & (1 << 0)) | ||
901 | sio_data->has_fanout |= (1 << 3); /* FANOUT3 */ | ||
902 | |||
903 | cfg = superio_inb(sioaddr, SIOREG_CFC); | ||
904 | if (!(cfg & (1 << 4))) { | ||
905 | if (cfg_b & (1 << 1)) | ||
906 | sio_data->has_fanout |= (1 << 1); /* FANOUT1 */ | ||
907 | if (cfg_b & (1 << 2)) | ||
908 | sio_data->has_fanout |= (1 << 2); /* FANOUT2 */ | ||
909 | } | ||
910 | |||
911 | /* FANOUT1 and FANOUT2 can each be routed to 2 different pins */ | ||
912 | cfg = superio_inb(sioaddr, SIOREG_CF5); | ||
913 | if (cfg & (1 << 6)) | ||
914 | sio_data->has_fanout |= (1 << 1); /* FANOUT1 */ | ||
915 | if (cfg & (1 << 5)) | ||
916 | sio_data->has_fanout |= (1 << 2); /* FANOUT2 */ | ||
917 | |||
651 | exit: | 918 | exit: |
652 | superio_exit(sioaddr); | 919 | superio_exit(sioaddr); |
653 | return err; | 920 | return err; |