Re: [PATCH 3/6] hwmon: (pc87427) Add support for manual fan speed control

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, 2010-08-11 at 11:10 -0400, Jean Delvare wrote:
> Add initial support for PWM outputs of the PC87427 Super-I/O chip.
> Only mode change and manual fan speed control are supported. Automatic
> mode configuration isn't supported, and won't be until at least one
> board is known, which makes uses of the PWM outputs.
> 
> Signed-off-by: Jean Delvare <khali@xxxxxxxxxxxx>

Acked-by: Guenter Roeck <guenter.roeck@xxxxxxxxxxxx>

Minor question inline.

> ---
>  Documentation/hwmon/pc87427 |   13 +-
>  drivers/hwmon/Kconfig       |    3
>  drivers/hwmon/pc87427.c     |  271 ++++++++++++++++++++++++++++++++++++++++++-
>  3 files changed, 282 insertions(+), 5 deletions(-)
> 
> --- linux-2.6.36-rc0.orig/Documentation/hwmon/pc87427   2010-08-02 00:11:14.000000000 +0200
> +++ linux-2.6.36-rc0/Documentation/hwmon/pc87427        2010-08-11 16:27:12.000000000 +0200
> @@ -20,8 +20,8 @@ The National Semiconductor Super I/O chi
>  monitoring capabilities. It can monitor up to 18 voltages, 8 fans and
>  6 temperature sensors. Only the fans are supported at the moment.
> 
> -This chip also has fan controlling features, which are not yet supported
> -by this driver either.
> +This chip also has fan controlling features (up to 4 PWM outputs),
> +which are partly supported by this driver.
> 
>  The driver assumes that no more than one chip is present, which seems
>  reasonable.
> @@ -36,3 +36,12 @@ signal. Speeds down to 83 RPM can be mea
>  An alarm is triggered if the rotation speed drops below a programmable
>  limit. Another alarm is triggered if the speed is too low to be measured
>  (including stalled or missing fan).
> +
> +
> +Fan Speed Control
> +-----------------
> +
> +Fan speed can be controlled by PWM outputs. There are 4 possible modes:
> +always off, always on, manual and automatic. The latter isn't supported
> +by the driver: you can only return to that mode if it was the original
> +setting, and the configuration interface is missing.
> --- linux-2.6.36-rc0.orig/drivers/hwmon/Kconfig 2010-08-11 15:42:11.000000000 +0200
> +++ linux-2.6.36-rc0/drivers/hwmon/Kconfig      2010-08-11 16:27:12.000000000 +0200
> @@ -711,7 +711,8 @@ config SENSORS_PC87427
>           functions of the National Semiconductor PC87427 Super-I/O chip.
>           The chip has two distinct logical devices, one for fan speed
>           monitoring and control, and one for voltage and temperature
> -         monitoring. Only fan speed monitoring is supported right now.
> +         monitoring. Only fan speed monitoring and control is supported
> +         right now.
> 
>           This driver can also be built as a module.  If so, the module
>           will be called pc87427.
> --- linux-2.6.36-rc0.orig/drivers/hwmon/pc87427.c       2010-08-11 16:25:40.000000000 +0200
> +++ linux-2.6.36-rc0/drivers/hwmon/pc87427.c    2010-08-11 16:27:12.000000000 +0200
> @@ -15,10 +15,10 @@
>   *  Supports the following chips:
>   *
>   *  Chip        #vin    #fan    #pwm    #temp   devid
> - *  PC87427     -       8       -       -       0xF2
> + *  PC87427     -       8       4       -       0xF2
>   *
>   *  This driver assumes that no more than one chip is present.
> - *  Only fan inputs are supported so far, although the chip can do much more.
> + *  Only fans are supported so far, although the chip can do much more.
>   */
> 
>  #include <linux/module.h>
> @@ -57,10 +57,16 @@ struct pc87427_data {
>         u16 fan[8];                     /* register values */
>         u16 fan_min[8];                 /* register values */
>         u8 fan_status[8];               /* register values */
> +
> +       u8 pwm_enabled;                 /* bit vector */
> +       u8 pwm_auto_ok;                 /* bit vector */
> +       u8 pwm_enable[4];               /* register values */
> +       u8 pwm[4];                      /* register values */
>  };
> 
>  struct pc87427_sio_data {
>         u8 has_fanin;
> +       u8 has_fanout;
>  };
> 
>  /*
> @@ -72,7 +78,9 @@ struct pc87427_sio_data {
>  #define SIOREG_CF2     0x22    /* Configuration 2 */
>  #define SIOREG_CF3     0x23    /* Configuration 3 */
>  #define SIOREG_CF4     0x24    /* Configuration 4 */
> +#define SIOREG_CF5     0x25    /* Configuration 5 */
>  #define SIOREG_CFB     0x2B    /* Configuration B */
> +#define SIOREG_CFC     0x2C    /* Configuration C */
>  #define SIOREG_CFD     0x2D    /* Configuration D */
>  #define SIOREG_ACT     0x30    /* Device activation */
>  #define SIOREG_MAP     0x50    /* I/O or memory mapping */
> @@ -188,6 +196,61 @@ static inline u16 fan_to_reg(unsigned lo
>  }
> 
>  /*
> + * PWM registers and conversions
> + */
> +
> +#define PC87427_REG_PWM_ENABLE         0x10
> +#define PC87427_REG_PWM_DUTY           0x12
> +
> +#define PWM_ENABLE_MODE_MASK           (7 << 4)
> +#define PWM_ENABLE_CTLEN               (1 << 0)
> +
> +#define PWM_MODE_MANUAL                        (0 << 4)
> +#define PWM_MODE_AUTO                  (1 << 4)
> +#define PWM_MODE_OFF                   (2 << 4)
> +#define PWM_MODE_ON                    (7 << 4)

Just wondering ... ON is 0x01110000, not 0x01000000 ?
No problem with it, only that the OFF overlaps the ON bit which seems a
bit odd.

> +
> +/* Dedicated function to read all registers related to a given PWM output.
> +   This saves us quite a few locks and bank selections.
> +   Must be called with data->lock held.
> +   nr is from 0 to 3 */
> +static void pc87427_readall_pwm(struct pc87427_data *data, u8 nr)
> +{
> +       int iobase = data->address[LD_FAN];
> +
> +       outb(BANK_FC(nr), iobase + PC87427_REG_BANK);
> +       data->pwm_enable[nr] = inb(iobase + PC87427_REG_PWM_ENABLE);
> +       data->pwm[nr] = inb(iobase + PC87427_REG_PWM_DUTY);
> +}
> +
> +static inline int pwm_enable_from_reg(u8 reg)
> +{
> +       switch (reg & PWM_ENABLE_MODE_MASK) {
> +       case PWM_MODE_ON:
> +               return 0;
> +       case PWM_MODE_MANUAL:
> +       case PWM_MODE_OFF:
> +               return 1;
> +       case PWM_MODE_AUTO:
> +               return 2;
> +       default:
> +               return -EPROTO;
> +       }
> +}
> +
> +static inline u8 pwm_enable_to_reg(unsigned long val, u8 pwmval)
> +{
> +       switch (val) {
> +       default:
> +               return PWM_MODE_ON;
> +       case 1:
> +               return pwmval ? PWM_MODE_MANUAL : PWM_MODE_OFF;
> +       case 2:
> +               return PWM_MODE_AUTO;
> +       }
> +}
> +
> +/*
>   * Data interface
>   */
> 
> @@ -207,6 +270,14 @@ static struct pc87427_data *pc87427_upda
>                         continue;
>                 pc87427_readall_fan(data, i);
>         }
> +
> +       /* PWM outputs */
> +       for (i = 0; i < 4; i++) {
> +               if (!(data->pwm_enabled & (1 << i)))
> +                       continue;
> +               pc87427_readall_pwm(data, i);
> +       }
> +
>         data->last_updated = jiffies;
> 
>  done:
> @@ -384,6 +455,145 @@ static const struct attribute_group pc87
>         { .attrs = pc87427_attributes_fan[7] },
>  };
> 
> +/* Must be called with data->lock held and pc87427_readall_pwm() freshly
> +   called */
> +static void update_pwm_enable(struct pc87427_data *data, int nr, u8 mode)
> +{
> +       int iobase = data->address[LD_FAN];
> +       data->pwm_enable[nr] &= ~PWM_ENABLE_MODE_MASK;
> +       data->pwm_enable[nr] |= mode;
> +       outb(data->pwm_enable[nr], iobase + PC87427_REG_PWM_ENABLE);
> +}
> +
> +static ssize_t show_pwm_enable(struct device *dev, struct device_attribute
> +                              *devattr, char *buf)
> +{
> +       struct pc87427_data *data = pc87427_update_device(dev);
> +       int nr = to_sensor_dev_attr(devattr)->index;
> +       int pwm_enable;
> +
> +       pwm_enable = pwm_enable_from_reg(data->pwm_enable[nr]);
> +       if (pwm_enable < 0)
> +               return pwm_enable;
> +       return sprintf(buf, "%d\n", pwm_enable);
> +}
> +
> +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute
> +                             *devattr, const char *buf, size_t count)
> +{
> +       struct pc87427_data *data = dev_get_drvdata(dev);
> +       int nr = to_sensor_dev_attr(devattr)->index;
> +       unsigned long val;
> +
> +       if (strict_strtoul(buf, 10, &val) < 0 || val > 2)
> +               return -EINVAL;
> +       /* Can't go to automatic mode if it isn't configured */
> +       if (val == 2 && !(data->pwm_auto_ok & (1 << nr)))
> +               return -EINVAL;
> +
> +       mutex_lock(&data->lock);
> +       pc87427_readall_pwm(data, nr);
> +       update_pwm_enable(data, nr, pwm_enable_to_reg(val, data->pwm[nr]));
> +       mutex_unlock(&data->lock);
> +
> +       return count;
> +}
> +
> +static ssize_t show_pwm(struct device *dev, struct device_attribute
> +                       *devattr, char *buf)
> +{
> +       struct pc87427_data *data = pc87427_update_device(dev);
> +       int nr = to_sensor_dev_attr(devattr)->index;
> +
> +       return sprintf(buf, "%d\n", (int)data->pwm[nr]);
> +}
> +
> +static ssize_t set_pwm(struct device *dev, struct device_attribute
> +                      *devattr, const char *buf, size_t count)
> +{
> +       struct pc87427_data *data = dev_get_drvdata(dev);
> +       int nr = to_sensor_dev_attr(devattr)->index;
> +       unsigned long val;
> +       int iobase = data->address[LD_FAN];
> +       u8 mode;
> +
> +       if (strict_strtoul(buf, 10, &val) < 0 || val > 0xff)
> +               return -EINVAL;
> +
> +       mutex_lock(&data->lock);
> +       pc87427_readall_pwm(data, nr);
> +       mode = data->pwm_enable[nr] & PWM_ENABLE_MODE_MASK;
> +       if (mode != PWM_MODE_MANUAL && mode != PWM_MODE_OFF) {
> +               dev_notice(dev, "Can't set PWM%d duty cycle while not in "
> +                          "manual mode\n", nr + 1);
> +               mutex_unlock(&data->lock);
> +               return -EPERM;
> +       }
> +
> +       /* We may have to change the mode */
> +       if (mode == PWM_MODE_MANUAL && val == 0) {
> +               /* Transition from Manual to Off */
> +               update_pwm_enable(data, nr, PWM_MODE_OFF);
> +               mode = PWM_MODE_OFF;
> +               dev_dbg(dev, "Switching PWM%d from %s to %s\n", nr + 1,
> +                       "manual", "off");
> +       } else if (mode == PWM_MODE_OFF && val != 0) {
> +               /* Transition from Off to Manual */
> +               update_pwm_enable(data, nr, PWM_MODE_MANUAL);
> +               mode = PWM_MODE_MANUAL;
> +               dev_dbg(dev, "Switching PWM%d from %s to %s\n", nr + 1,
> +                       "off", "manual");
> +       }
> +
> +       data->pwm[nr] = val;
> +       if (mode == PWM_MODE_MANUAL)
> +               outb(val, iobase + PC87427_REG_PWM_DUTY);
> +       mutex_unlock(&data->lock);
> +
> +       return count;
> +}
> +
> +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO,
> +                         show_pwm_enable, set_pwm_enable, 0);
> +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO,
> +                         show_pwm_enable, set_pwm_enable, 1);
> +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO,
> +                         show_pwm_enable, set_pwm_enable, 2);
> +static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO,
> +                         show_pwm_enable, set_pwm_enable, 3);
> +
> +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0);
> +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1);
> +static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 2);
> +static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 3);
> +
> +static struct attribute *pc87427_attributes_pwm[4][3] = {
> +       {
> +               &sensor_dev_attr_pwm1_enable.dev_attr.attr,
> +               &sensor_dev_attr_pwm1.dev_attr.attr,
> +               NULL
> +       }, {
> +               &sensor_dev_attr_pwm2_enable.dev_attr.attr,
> +               &sensor_dev_attr_pwm2.dev_attr.attr,
> +               NULL
> +       }, {
> +               &sensor_dev_attr_pwm3_enable.dev_attr.attr,
> +               &sensor_dev_attr_pwm3.dev_attr.attr,
> +               NULL
> +       }, {
> +               &sensor_dev_attr_pwm4_enable.dev_attr.attr,
> +               &sensor_dev_attr_pwm4.dev_attr.attr,
> +               NULL
> +       }
> +};
> +
> +static const struct attribute_group pc87427_group_pwm[4] = {
> +       { .attrs = pc87427_attributes_pwm[0] },
> +       { .attrs = pc87427_attributes_pwm[1] },
> +       { .attrs = pc87427_attributes_pwm[2] },
> +       { .attrs = pc87427_attributes_pwm[3] },
> +};
> +
>  static ssize_t show_name(struct device *dev, struct device_attribute
>                          *devattr, char *buf)
>  {
> @@ -431,6 +641,25 @@ static void __devinit pc87427_init_devic
>                 }
>                 data->fan_enabled = sio_data->has_fanin;
>         }
> +
> +       /* Check which PWM outputs are enabled */
> +       for (i = 0; i < 4; i++) {
> +               if (!(sio_data->has_fanout & (1 << i))) /* Not wired */
> +                       continue;
> +               reg = pc87427_read8_bank(data, LD_FAN, BANK_FC(i),
> +                                        PC87427_REG_PWM_ENABLE);
> +               if (reg & PWM_ENABLE_CTLEN)
> +                       data->pwm_enabled |= (1 << i);
> +
> +               /* We don't expose an interface to reconfigure the automatic
> +                  fan control mode, so only allow to return to this mode if
> +                  it was originally set. */
> +               if ((reg & PWM_ENABLE_MODE_MASK) == PWM_MODE_AUTO) {
> +                       dev_dbg(dev, "PWM%d is in automatic control mode\n",
> +                               i + 1);
> +                       data->pwm_auto_ok |= (1 << i);
> +               }
> +       }
>  }
> 
>  static int __devinit pc87427_probe(struct platform_device *pdev)
> @@ -474,6 +703,14 @@ static int __devinit pc87427_probe(struc
>                 if (err)
>                         goto exit_remove_files;
>         }
> +       for (i = 0; i < 4; i++) {
> +               if (!(data->pwm_enabled & (1 << i)))
> +                       continue;
> +               err = sysfs_create_group(&pdev->dev.kobj,
> +                                        &pc87427_group_pwm[i]);
> +               if (err)
> +                       goto exit_remove_files;
> +       }
> 
>         data->hwmon_dev = hwmon_device_register(&pdev->dev);
>         if (IS_ERR(data->hwmon_dev)) {
> @@ -490,6 +727,11 @@ exit_remove_files:
>                         continue;
>                 sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]);
>         }
> +       for (i = 0; i < 4; i++) {
> +               if (!(data->pwm_enabled & (1 << i)))
> +                       continue;
> +               sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_pwm[i]);
> +       }
>  exit_release_region:
>         release_region(res->start, resource_size(res));
>  exit_kfree:
> @@ -512,6 +754,11 @@ static int __devexit pc87427_remove(stru
>                         continue;
>                 sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]);
>         }
> +       for (i = 0; i < 4; i++) {
> +               if (!(data->pwm_enabled & (1 << i)))
> +                       continue;
> +               sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_pwm[i]);
> +       }
>         platform_set_drvdata(pdev, NULL);
>         kfree(data);
> 
> @@ -648,6 +895,26 @@ static int __init pc87427_find(int sioad
>         if ((cfg & (1 << 3)) && !(cfg_b & (1 << 5)))
>                 sio_data->has_fanin |= (1 << 6);        /* FANIN6 */
> 
> +       /* Check which fan outputs are wired */
> +       sio_data->has_fanout = (1 << 0);                /* FANOUT0 */
> +       if (cfg_b & (1 << 0))
> +               sio_data->has_fanout |= (1 << 3);       /* FANOUT3 */
> +
> +       cfg = superio_inb(sioaddr, SIOREG_CFC);
> +       if (!(cfg & (1 << 4))) {
> +               if (cfg_b & (1 << 1))
> +                       sio_data->has_fanout |= (1 << 1); /* FANOUT1 */
> +               if (cfg_b & (1 << 2))
> +                       sio_data->has_fanout |= (1 << 2); /* FANOUT2 */
> +       }
> +
> +       /* FANOUT1 and FANOUT2 can each be routed to 2 different pins */
> +       cfg = superio_inb(sioaddr, SIOREG_CF5);
> +       if (cfg & (1 << 6))
> +               sio_data->has_fanout |= (1 << 1);       /* FANOUT1 */
> +       if (cfg & (1 << 5))
> +               sio_data->has_fanout |= (1 << 2);       /* FANOUT2 */
> +
>  exit:
>         superio_exit(sioaddr);
>         return err;
> 
> --
> Jean Delvare
> 
> _______________________________________________
> lm-sensors mailing list
> lm-sensors@xxxxxxxxxxxxxx
> http://lists.lm-sensors.org/mailman/listinfo/lm-sensors



_______________________________________________
lm-sensors mailing list
lm-sensors@xxxxxxxxxxxxxx
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors


[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux