Expose fanX_target, pwmX and pwmX_enable hwmon sysfs attributes.

Fans in a PMBus device are driven by the configuration of two registers:
FAN_CONFIG_x_y and FAN_COMMAND_x: FAN_CONFIG_x_y dictates how the fan
and the tacho operate (if installed), while FAN_COMMAND_x sets the
desired fan rate. The unit of FAN_COMMAND_x is dependent on the
operational fan mode, RPM or PWM percent duty, as determined by the
corresponding FAN_CONFIG_x_y.

The mapping of fanX_target, pwmX and pwmX_enable onto FAN_CONFIG_x_y and
FAN_COMMAND_x is implemented with the addition of virtual registers and
generic implementations in the core:

1. PMBUS_VIRT_FAN_TARGET_x
2. PMBUS_VIRT_PWM_x
3. PMBUS_VIRT_PWM_ENABLE_x

The virtual registers facilitate the necessary side-effects of each
access. Examples of the read case, assuming m = 1, b = 0, R = 0:

             Read     |              With              || Gives
         -------------------------------------------------------
           Attribute  | FAN_CONFIG_x_y | FAN_COMMAND_y || Value
         ----------------------------------------------++-------
          fanX_target | ~PB_FAN_z_RPM  | 0x0001        || 1
          pwm1        | ~PB_FAN_z_RPM  | 0x0064        || 255
          pwmX_enable | ~PB_FAN_z_RPM  | 0x0001        || 1
          fanX_target |  PB_FAN_z_RPM  | 0x0001        || 1
          pwm1        |  PB_FAN_z_RPM  | 0x0064        || 0
          pwmX_enable |  PB_FAN_z_RPM  | 0x0001        || 1

And the write case:

             Write    | With  ||               Sets
         -------------+-------++----------------+---------------
           Attribute  | Value || FAN_CONFIG_x_y | FAN_COMMAND_x
         -------------+-------++----------------+---------------
          fanX_target | 1     ||  PB_FAN_z_RPM  | 0x0001
          pwmX        | 255   || ~PB_FAN_z_RPM  | 0x0064
          pwmX_enable | 1     || ~PB_FAN_z_RPM  | 0x0064

Also, the DIRECT mode scaling of some controllers is different between
RPM and PWM percent duty control modes, so PSC_PWM is introduced to
capture the necessary coefficients.

Signed-off-by: Andrew Jeffery <and...@aj.id.au>
---
 drivers/hwmon/pmbus/pmbus.h      |  29 +++++
 drivers/hwmon/pmbus/pmbus_core.c | 224 ++++++++++++++++++++++++++++++++++++---
 2 files changed, 238 insertions(+), 15 deletions(-)

diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h
index 4efa2bd4f6d8..cdf3e288e626 100644
--- a/drivers/hwmon/pmbus/pmbus.h
+++ b/drivers/hwmon/pmbus/pmbus.h
@@ -190,6 +190,28 @@ enum pmbus_regs {
        PMBUS_VIRT_VMON_UV_FAULT_LIMIT,
        PMBUS_VIRT_VMON_OV_FAULT_LIMIT,
        PMBUS_VIRT_STATUS_VMON,
+
+       /*
+        * RPM and PWM Fan control
+        *
+        * Drivers wanting to expose PWM control must define the behaviour of
+        * PMBUS_VIRT_PWM_ENABLE_[1-4] in the {read,write}_word_data callback.
+        *
+        * pmbus core provides default implementations for
+        * PMBUS_VIRT_FAN_TARGET_[1-4] and PMBUS_VIRT_PWM_[1-4].
+        */
+       PMBUS_VIRT_FAN_TARGET_1,
+       PMBUS_VIRT_FAN_TARGET_2,
+       PMBUS_VIRT_FAN_TARGET_3,
+       PMBUS_VIRT_FAN_TARGET_4,
+       PMBUS_VIRT_PWM_1,
+       PMBUS_VIRT_PWM_2,
+       PMBUS_VIRT_PWM_3,
+       PMBUS_VIRT_PWM_4,
+       PMBUS_VIRT_PWM_ENABLE_1,
+       PMBUS_VIRT_PWM_ENABLE_2,
+       PMBUS_VIRT_PWM_ENABLE_3,
+       PMBUS_VIRT_PWM_ENABLE_4,
 };
 
 /*
@@ -223,6 +245,8 @@ enum pmbus_regs {
 #define PB_FAN_1_RPM                   BIT(6)
 #define PB_FAN_1_INSTALLED             BIT(7)
 
+enum pmbus_fan_mode { percent = 0, rpm };
+
 /*
  * STATUS_BYTE, STATUS_WORD (lower)
  */
@@ -313,6 +337,7 @@ enum pmbus_sensor_classes {
        PSC_POWER,
        PSC_TEMPERATURE,
        PSC_FAN,
+       PSC_PWM,
        PSC_NUM_CLASSES         /* Number of power sensor classes */
 };
 
@@ -339,6 +364,8 @@ enum pmbus_sensor_classes {
 #define PMBUS_HAVE_STATUS_FAN34        BIT(17)
 #define PMBUS_HAVE_VMON                BIT(18)
 #define PMBUS_HAVE_STATUS_VMON BIT(19)
+#define PMBUS_HAVE_PWM12       BIT(20)
+#define PMBUS_HAVE_PWM34       BIT(21)
 
 enum pmbus_data_format { linear = 0, direct, vid };
 enum vrm_version { vr11 = 0, vr12, vr13 };
@@ -413,6 +440,8 @@ int pmbus_write_byte_data(struct i2c_client *client, int 
page, u8 reg,
                          u8 value);
 int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg,
                           u8 mask, u8 value);
+int pmbus_update_fan(struct i2c_client *client, int page, int id,
+                    u8 config, u8 mask, u16 command);
 void pmbus_clear_faults(struct i2c_client *client);
 bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg);
 bool pmbus_check_word_register(struct i2c_client *client, int page, int reg);
diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c
index 302f0aef59de..55838b69e99a 100644
--- a/drivers/hwmon/pmbus/pmbus_core.c
+++ b/drivers/hwmon/pmbus/pmbus_core.c
@@ -64,6 +64,7 @@ struct pmbus_sensor {
        u16 reg;                /* register */
        enum pmbus_sensor_classes class;        /* sensor class */
        bool update;            /* runtime sensor update needed */
+       bool convert;           /* Whether or not to apply linear/vid/direct */
        int data;               /* Sensor data.
                                   Negative if there was a read error */
 };
@@ -128,6 +129,27 @@ struct pmbus_debugfs_entry {
        u8 reg;
 };
 
+static const int pmbus_fan_rpm_mask[] = {
+       PB_FAN_1_RPM,
+       PB_FAN_2_RPM,
+       PB_FAN_1_RPM,
+       PB_FAN_2_RPM,
+};
+
+static const int pmbus_fan_config_registers[] = {
+       PMBUS_FAN_CONFIG_12,
+       PMBUS_FAN_CONFIG_12,
+       PMBUS_FAN_CONFIG_34,
+       PMBUS_FAN_CONFIG_34
+};
+
+static const int pmbus_fan_command_registers[] = {
+       PMBUS_FAN_COMMAND_1,
+       PMBUS_FAN_COMMAND_2,
+       PMBUS_FAN_COMMAND_3,
+       PMBUS_FAN_COMMAND_4,
+};
+
 void pmbus_clear_cache(struct i2c_client *client)
 {
        struct pmbus_data *data = i2c_get_clientdata(client);
@@ -198,6 +220,31 @@ int pmbus_write_word_data(struct i2c_client *client, u8 
page, u8 reg, u16 word)
 }
 EXPORT_SYMBOL_GPL(pmbus_write_word_data);
 
+int pmbus_update_fan(struct i2c_client *client, int page, int id,
+                              u8 config, u8 mask, u16 command)
+{
+       int from, rv;
+       u8 to;
+
+       from = pmbus_read_byte_data(client, page,
+                                   pmbus_fan_config_registers[id]);
+       if (from < 0)
+               return from;
+
+       to = (from & ~mask) | (config & mask);
+
+       if (to != from) {
+               rv = pmbus_write_byte_data(client, page,
+                                          pmbus_fan_config_registers[id], to);
+               if (rv < 0)
+                       return rv;
+       }
+
+       return pmbus_write_word_data(client, page,
+                                    pmbus_fan_command_registers[id], command);
+}
+EXPORT_SYMBOL_GPL(pmbus_update_fan);
+
 /*
  * _pmbus_write_word_data() is similar to pmbus_write_word_data(), but checks 
if
  * a device specific mapping function exists and calls it if necessary.
@@ -214,8 +261,40 @@ static int _pmbus_write_word_data(struct i2c_client 
*client, int page, int reg,
                if (status != -ENODATA)
                        return status;
        }
-       if (reg >= PMBUS_VIRT_BASE)
-               return -ENXIO;
+       if (reg >= PMBUS_VIRT_BASE) {
+               int id, bit;
+
+               switch (reg) {
+               case PMBUS_VIRT_FAN_TARGET_1:
+               case PMBUS_VIRT_FAN_TARGET_2:
+               case PMBUS_VIRT_FAN_TARGET_3:
+               case PMBUS_VIRT_FAN_TARGET_4:
+                       id = reg - PMBUS_VIRT_FAN_TARGET_1;
+                       bit = pmbus_fan_rpm_mask[id];
+                       status = pmbus_update_fan(client, page, id, bit, bit,
+                                                 word);
+                       break;
+               case PMBUS_VIRT_PWM_1:
+               case PMBUS_VIRT_PWM_2:
+               case PMBUS_VIRT_PWM_3:
+               case PMBUS_VIRT_PWM_4:
+               {
+                       u32 command = word;
+
+                       id = reg - PMBUS_VIRT_PWM_1;
+                       bit = pmbus_fan_rpm_mask[id];
+                       command *= 100;
+                       command /= 255;
+                       status = pmbus_update_fan(client, page, id, 0, bit,
+                                                 command);
+                       break;
+               }
+               default:
+                       status = -ENXIO;
+                       break;
+               }
+               return status;
+       }
        return pmbus_write_word_data(client, page, reg, word);
 }
 
@@ -231,6 +310,9 @@ int pmbus_read_word_data(struct i2c_client *client, u8 
page, u8 reg)
 }
 EXPORT_SYMBOL_GPL(pmbus_read_word_data);
 
+static int pmbus_get_fan_command(struct i2c_client *client, int page, int id,
+                                enum pmbus_fan_mode mode);
+
 /*
  * _pmbus_read_word_data() is similar to pmbus_read_word_data(), but checks if
  * a device specific mapping function exists and calls it if necessary.
@@ -246,8 +328,42 @@ static int _pmbus_read_word_data(struct i2c_client 
*client, int page, int reg)
                if (status != -ENODATA)
                        return status;
        }
-       if (reg >= PMBUS_VIRT_BASE)
-               return -ENXIO;
+       if (reg >= PMBUS_VIRT_BASE) {
+               int id;
+
+               switch (reg) {
+               case PMBUS_VIRT_FAN_TARGET_1:
+               case PMBUS_VIRT_FAN_TARGET_2:
+               case PMBUS_VIRT_FAN_TARGET_3:
+               case PMBUS_VIRT_FAN_TARGET_4:
+                       id = reg - PMBUS_VIRT_FAN_TARGET_1;
+                       status = pmbus_get_fan_command(client, page, id, rpm);
+                       break;
+               case PMBUS_VIRT_PWM_1:
+               case PMBUS_VIRT_PWM_2:
+               case PMBUS_VIRT_PWM_3:
+               case PMBUS_VIRT_PWM_4:
+               {
+                       int rv;
+
+                       id = reg - PMBUS_VIRT_PWM_1;
+                       rv = pmbus_get_fan_command(client, page, id, percent);
+                       if (rv < 0)
+                               return rv;
+
+                       rv *= 255;
+                       rv /= 100;
+
+                       status = rv;
+                       break;
+               }
+               default:
+                       status = -ENXIO;
+                       break;
+               }
+
+               return status;
+       }
        return pmbus_read_word_data(client, page, reg);
 }
 
@@ -314,6 +430,28 @@ static int _pmbus_read_byte_data(struct i2c_client 
*client, int page, int reg)
        return pmbus_read_byte_data(client, page, reg);
 }
 
+static int pmbus_get_fan_command(struct i2c_client *client, int page, int id,
+                                enum pmbus_fan_mode mode)
+{
+       int config;
+
+       config = _pmbus_read_byte_data(client, page,
+                                      pmbus_fan_config_registers[id]);
+       if (config < 0)
+               return config;
+
+       /*
+        * We can't meaningfully translate between PWM and RPM, so if the
+        * attribute mode (fan[1-*]_target is RPM, pwm[1-*] and pwm[1-*]_enable
+        * are PWM) doesn't match the hardware mode, then report 0 instead.
+        */
+       if ((mode == rpm) != (!!(config & pmbus_fan_rpm_mask[id])))
+               return 0;
+
+       return _pmbus_read_word_data(client, page,
+                                    pmbus_fan_command_registers[id]);
+}
+
 static void pmbus_clear_fault_page(struct i2c_client *client, int page)
 {
        _pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS);
@@ -515,7 +653,7 @@ static long pmbus_reg2data_direct(struct pmbus_data *data,
        /* X = 1/m * (Y * 10^-R - b) */
        R = -R;
        /* scale result to milli-units for everything but fans */
-       if (sensor->class != PSC_FAN) {
+       if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) {
                R += 3;
                b *= 1000;
        }
@@ -569,6 +707,9 @@ static long pmbus_reg2data(struct pmbus_data *data, struct 
pmbus_sensor *sensor)
 {
        long val;
 
+       if (!sensor->convert)
+               return sensor->data;
+
        switch (data->info->format[sensor->class]) {
        case direct:
                val = pmbus_reg2data_direct(data, sensor);
@@ -672,7 +813,7 @@ static u16 pmbus_data2reg_direct(struct pmbus_data *data,
        }
 
        /* Calculate Y = (m * X + b) * 10^R */
-       if (sensor->class != PSC_FAN) {
+       if (!(sensor->class == PSC_FAN || sensor->class == PSC_PWM)) {
                R -= 3;         /* Adjust R and b for data in milli-units */
                b *= 1000;
        }
@@ -703,6 +844,9 @@ static u16 pmbus_data2reg(struct pmbus_data *data,
 {
        u16 regval;
 
+       if (!sensor->convert)
+               return val;
+
        switch (data->info->format[sensor->class]) {
        case direct:
                regval = pmbus_data2reg_direct(data, sensor, val);
@@ -925,12 +1069,18 @@ static struct pmbus_sensor *pmbus_add_sensor(struct 
pmbus_data *data,
                return NULL;
        a = &sensor->attribute;
 
-       snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s",
-                name, seq, type);
+       if (type)
+               snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s",
+                        name, seq, type);
+       else
+               snprintf(sensor->name, sizeof(sensor->name), "%s%d",
+                        name, seq);
+
        sensor->page = page;
        sensor->reg = reg;
        sensor->class = class;
        sensor->update = update;
+       sensor->convert = true;
        pmbus_dev_attr_init(a, sensor->name,
                            readonly ? S_IRUGO : S_IRUGO | S_IWUSR,
                            pmbus_show_sensor, pmbus_set_sensor);
@@ -1592,13 +1742,6 @@ static const int pmbus_fan_registers[] = {
        PMBUS_READ_FAN_SPEED_4
 };
 
-static const int pmbus_fan_config_registers[] = {
-       PMBUS_FAN_CONFIG_12,
-       PMBUS_FAN_CONFIG_12,
-       PMBUS_FAN_CONFIG_34,
-       PMBUS_FAN_CONFIG_34
-};
-
 static const int pmbus_fan_status_registers[] = {
        PMBUS_STATUS_FAN_12,
        PMBUS_STATUS_FAN_12,
@@ -1621,6 +1764,48 @@ static const u32 pmbus_fan_status_flags[] = {
 };
 
 /* Fans */
+static int pmbus_add_fan_ctrl(struct i2c_client *client,
+               struct pmbus_data *data, int index, int page, int id,
+               u8 config)
+{
+       struct pmbus_sensor *sensor;
+       int rv;
+
+       rv = _pmbus_read_word_data(client, page,
+                                  pmbus_fan_command_registers[id]);
+       if (rv < 0)
+               return rv;
+
+       sensor = pmbus_add_sensor(data, "fan", "target", index, page,
+                                 PMBUS_VIRT_FAN_TARGET_1 + id, PSC_FAN,
+                                 true, false);
+
+       if (!sensor)
+               return -ENOMEM;
+
+       if (!((data->info->func[page] & PMBUS_HAVE_PWM12) ||
+                       (data->info->func[page] & PMBUS_HAVE_PWM34)))
+               return 0;
+
+       sensor = pmbus_add_sensor(data, "pwm", NULL, index, page,
+                                 PMBUS_VIRT_PWM_1 + id, PSC_PWM,
+                                 true, false);
+
+       if (!sensor)
+               return -ENOMEM;
+
+       sensor = pmbus_add_sensor(data, "pwm", "enable", index, page,
+                                 PMBUS_VIRT_PWM_ENABLE_1 + id, PSC_PWM,
+                                 true, false);
+
+       if (!sensor)
+               return -ENOMEM;
+
+       sensor->convert = false;
+
+       return 0;
+}
+
 static int pmbus_add_fan_attributes(struct i2c_client *client,
                                    struct pmbus_data *data)
 {
@@ -1658,6 +1843,15 @@ static int pmbus_add_fan_attributes(struct i2c_client 
*client,
                                             PSC_FAN, true, true) == NULL)
                                return -ENOMEM;
 
+                       /* Fan control */
+                       if (pmbus_check_word_register(client, page,
+                                       pmbus_fan_command_registers[f])) {
+                               ret = pmbus_add_fan_ctrl(client, data, index,
+                                                        page, f, regval);
+                               if (ret < 0)
+                                       return ret;
+                       }
+
                        /*
                         * Each fan status register covers multiple fans,
                         * so we have to do some magic.
-- 
2.11.0

--
To unsubscribe from this list: send the line "unsubscribe linux-doc" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to