hwmon: (adt7475) Add support for configuring initial PWM state
authorChris Packham <chris.packham@alliedtelesis.co.nz>
Mon, 22 Jul 2024 22:17:37 +0000 (10:17 +1200)
committerGuenter Roeck <linux@roeck-us.net>
Wed, 31 Jul 2024 17:43:52 +0000 (10:43 -0700)
By default the PWM duty cycle in hardware is 100%. On some systems this
can cause unwanted fan noise. Add the ability to specify the fan
connections and initial state of the PWMs via device properties.

Signed-off-by: Chris Packham <chris.packham@alliedtelesis.co.nz>
Link: https://lore.kernel.org/r/20240722221737.3407958-4-chris.packham@alliedtelesis.co.nz
[groeck: Cleaned up formatting]
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
drivers/hwmon/adt7475.c

index bc186c61a2c0a575118f1c97bf28f39881da5c31..05c85dc6b142d64c302650e63dfc8ef150782dc5 100644 (file)
@@ -21,6 +21,8 @@
 #include <linux/of.h>
 #include <linux/util_macros.h>
 
+#include <dt-bindings/pwm/pwm.h>
+
 /* Indexes for the sysfs hooks */
 
 #define INPUT          0
@@ -1662,6 +1664,130 @@ static int adt7475_set_pwm_polarity(struct i2c_client *client)
        return 0;
 }
 
+struct adt7475_pwm_config {
+       int index;
+       int freq;
+       int flags;
+       int duty;
+};
+
+static int _adt7475_pwm_properties_parse_args(u32 args[4], struct adt7475_pwm_config *cfg)
+{
+       int freq_hz;
+       int duty;
+
+       if (args[1] == 0)
+               return -EINVAL;
+
+       freq_hz = 1000000000UL / args[1];
+       if (args[3] >= args[1])
+               duty = 255;
+       else
+               duty = div_u64(255ULL * args[3], args[1]);
+
+       cfg->index = args[0];
+       cfg->freq = find_closest(freq_hz, pwmfreq_table, ARRAY_SIZE(pwmfreq_table));
+       cfg->flags = args[2];
+       cfg->duty = duty;
+
+       return 0;
+}
+
+static int adt7475_pwm_properties_parse_reference_args(struct fwnode_handle *fwnode,
+                                                      struct adt7475_pwm_config *cfg)
+{
+       int ret, i;
+       struct fwnode_reference_args rargs = {};
+       u32 args[4] = {};
+
+       ret = fwnode_property_get_reference_args(fwnode, "pwms", "#pwm-cells", 0, 0, &rargs);
+       if (ret)
+               return ret;
+
+       if (rargs.nargs != 4) {
+               fwnode_handle_put(rargs.fwnode);
+               return -EINVAL;
+       }
+
+       for (i = 0; i < 4; i++)
+               args[i] = rargs.args[i];
+
+       ret = _adt7475_pwm_properties_parse_args(args, cfg);
+
+       fwnode_handle_put(rargs.fwnode);
+
+       return ret;
+}
+
+static int adt7475_pwm_properties_parse_args(struct fwnode_handle *fwnode,
+                                            struct adt7475_pwm_config *cfg)
+{
+       int ret;
+       u32 args[4] = {};
+
+       ret = fwnode_property_read_u32_array(fwnode, "pwms", args, ARRAY_SIZE(args));
+       if (ret)
+               return ret;
+
+       return _adt7475_pwm_properties_parse_args(args, cfg);
+}
+
+static int adt7475_fan_pwm_config(struct i2c_client *client)
+{
+       struct adt7475_data *data = i2c_get_clientdata(client);
+       struct fwnode_handle *child;
+       struct adt7475_pwm_config cfg = {};
+       int ret;
+
+       device_for_each_child_node(&client->dev, child) {
+               if (!fwnode_property_present(child, "pwms"))
+                       continue;
+
+               if (is_of_node(child))
+                       ret = adt7475_pwm_properties_parse_reference_args(child, &cfg);
+               else
+                       ret = adt7475_pwm_properties_parse_args(child, &cfg);
+
+               if (cfg.index >= ADT7475_PWM_COUNT)
+                       return -EINVAL;
+
+               ret = adt7475_read(PWM_CONFIG_REG(cfg.index));
+               if (ret < 0)
+                       return ret;
+               data->pwm[CONTROL][cfg.index] = ret;
+               if (cfg.flags & PWM_POLARITY_INVERTED)
+                       data->pwm[CONTROL][cfg.index] |= BIT(4);
+               else
+                       data->pwm[CONTROL][cfg.index] &= ~BIT(4);
+
+               /* Force to manual mode so PWM values take effect */
+               data->pwm[CONTROL][cfg.index] &= ~0xE0;
+               data->pwm[CONTROL][cfg.index] |= 0x07 << 5;
+
+               ret = i2c_smbus_write_byte_data(client, PWM_CONFIG_REG(cfg.index),
+                                               data->pwm[CONTROL][cfg.index]);
+               if (ret)
+                       return ret;
+
+               data->pwm[INPUT][cfg.index] = cfg.duty;
+               ret = i2c_smbus_write_byte_data(client, PWM_REG(cfg.index),
+                                               data->pwm[INPUT][cfg.index]);
+               if (ret)
+                       return ret;
+
+               data->range[cfg.index] = adt7475_read(TEMP_TRANGE_REG(cfg.index));
+               data->range[cfg.index] &= ~0xf;
+               data->range[cfg.index] |= cfg.freq;
+
+               ret = i2c_smbus_write_byte_data(client, TEMP_TRANGE_REG(cfg.index),
+                                               data->range[cfg.index]);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
 static int adt7475_probe(struct i2c_client *client)
 {
        enum chips chip;
@@ -1774,6 +1900,10 @@ static int adt7475_probe(struct i2c_client *client)
        if (ret && ret != -EINVAL)
                dev_warn(&client->dev, "Error configuring pwm polarity\n");
 
+       ret = adt7475_fan_pwm_config(client);
+       if (ret)
+               dev_warn(&client->dev, "Error %d configuring fan/pwm\n", ret);
+
        /* Start monitoring */
        switch (chip) {
        case adt7475: