hwmon: (aspeed-pwm-tacho) cooling device support.
authorMykola Kostenok <c_mykolak@mellanox.com>
Thu, 3 Aug 2017 08:50:44 +0000 (11:50 +0300)
committerGuenter Roeck <linux@roeck-us.net>
Sun, 13 Aug 2017 15:24:38 +0000 (08:24 -0700)
Add support in aspeed-pwm-tacho driver for cooling device creation.
This cooling device could be bound to a thermal zone
for the thermal control. Device will appear in /sys/class/thermal
folder as cooling_deviceX. Then it could be bound to particular
thermal zones. Allow specification of the cooling levels
vector - PWM duty cycle values in a range from 0 to 255
which correspond to thermal cooling states.

Signed-off-by: Mykola Kostenok <c_mykolak@mellanox.com>
Reviewed-by: Joel Stanley <joel@jms.id.au>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
drivers/hwmon/aspeed-pwm-tacho.c

index ddfe66bdff8669033556351cafc3d39224b7fdfb..69b97d45e3cbb459ccdf73321dc4c3ef8ae23488 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/platform_device.h>
 #include <linux/sysfs.h>
 #include <linux/regmap.h>
+#include <linux/thermal.h>
 
 /* ASPEED PWM & FAN Tach Register Definition */
 #define ASPEED_PTCR_CTRL               0x00
 /* How long we sleep in us while waiting for an RPM result. */
 #define ASPEED_RPM_STATUS_SLEEP_USEC   500
 
+#define MAX_CDEV_NAME_LEN 16
+
+struct aspeed_cooling_device {
+       char name[16];
+       struct aspeed_pwm_tacho_data *priv;
+       struct thermal_cooling_device *tcdev;
+       int pwm_port;
+       u8 *cooling_levels;
+       u8 max_state;
+       u8 cur_state;
+};
+
 struct aspeed_pwm_tacho_data {
        struct regmap *regmap;
        unsigned long clk_freq;
@@ -180,6 +193,7 @@ struct aspeed_pwm_tacho_data {
        u8 pwm_port_type[8];
        u8 pwm_port_fan_ctrl[8];
        u8 fan_tach_ch_source[16];
+       struct aspeed_cooling_device *cdev[8];
        const struct attribute_group *groups[3];
 };
 
@@ -765,6 +779,94 @@ static void aspeed_create_fan_tach_channel(struct aspeed_pwm_tacho_data *priv,
        }
 }
 
+static int
+aspeed_pwm_cz_get_max_state(struct thermal_cooling_device *tcdev,
+                           unsigned long *state)
+{
+       struct aspeed_cooling_device *cdev = tcdev->devdata;
+
+       *state = cdev->max_state;
+
+       return 0;
+}
+
+static int
+aspeed_pwm_cz_get_cur_state(struct thermal_cooling_device *tcdev,
+                           unsigned long *state)
+{
+       struct aspeed_cooling_device *cdev = tcdev->devdata;
+
+       *state = cdev->cur_state;
+
+       return 0;
+}
+
+static int
+aspeed_pwm_cz_set_cur_state(struct thermal_cooling_device *tcdev,
+                           unsigned long state)
+{
+       struct aspeed_cooling_device *cdev = tcdev->devdata;
+
+       if (state > cdev->max_state)
+               return -EINVAL;
+
+       cdev->cur_state = state;
+       cdev->priv->pwm_port_fan_ctrl[cdev->pwm_port] =
+                                       cdev->cooling_levels[cdev->cur_state];
+       aspeed_set_pwm_port_fan_ctrl(cdev->priv, cdev->pwm_port,
+                                    cdev->cooling_levels[cdev->cur_state]);
+
+       return 0;
+}
+
+static const struct thermal_cooling_device_ops aspeed_pwm_cool_ops = {
+       .get_max_state = aspeed_pwm_cz_get_max_state,
+       .get_cur_state = aspeed_pwm_cz_get_cur_state,
+       .set_cur_state = aspeed_pwm_cz_set_cur_state,
+};
+
+static int aspeed_create_pwm_cooling(struct device *dev,
+                                    struct device_node *child,
+                                    struct aspeed_pwm_tacho_data *priv,
+                                    u32 pwm_port, u8 num_levels)
+{
+       int ret;
+       struct aspeed_cooling_device *cdev;
+
+       cdev = devm_kzalloc(dev, sizeof(*cdev), GFP_KERNEL);
+
+       if (!cdev)
+               return -ENOMEM;
+
+       cdev->cooling_levels = devm_kzalloc(dev, num_levels, GFP_KERNEL);
+       if (!cdev->cooling_levels)
+               return -ENOMEM;
+
+       cdev->max_state = num_levels - 1;
+       ret = of_property_read_u8_array(child, "cooling-levels",
+                                       cdev->cooling_levels,
+                                       num_levels);
+       if (ret) {
+               dev_err(dev, "Property 'cooling-levels' cannot be read.\n");
+               return ret;
+       }
+       snprintf(cdev->name, MAX_CDEV_NAME_LEN, "%s%d", child->name, pwm_port);
+
+       cdev->tcdev = thermal_of_cooling_device_register(child,
+                                                        cdev->name,
+                                                        cdev,
+                                                        &aspeed_pwm_cool_ops);
+       if (IS_ERR(cdev->tcdev))
+               return PTR_ERR(cdev->tcdev);
+
+       cdev->priv = priv;
+       cdev->pwm_port = pwm_port;
+
+       priv->cdev[pwm_port] = cdev;
+
+       return 0;
+}
+
 static int aspeed_create_fan(struct device *dev,
                             struct device_node *child,
                             struct aspeed_pwm_tacho_data *priv)
@@ -778,6 +880,15 @@ static int aspeed_create_fan(struct device *dev,
                return ret;
        aspeed_create_pwm_port(priv, (u8)pwm_port);
 
+       ret = of_property_count_u8_elems(child, "cooling-levels");
+
+       if (ret > 0) {
+               ret = aspeed_create_pwm_cooling(dev, child, priv, pwm_port,
+                                               ret);
+               if (ret)
+                       return ret;
+       }
+
        count = of_property_count_u8_elems(child, "aspeed,fan-tach-ch");
        if (count < 1)
                return -EINVAL;
@@ -834,9 +945,10 @@ static int aspeed_pwm_tacho_probe(struct platform_device *pdev)
 
        for_each_child_of_node(np, child) {
                ret = aspeed_create_fan(dev, child, priv);
-               of_node_put(child);
-               if (ret)
+               if (ret) {
+                       of_node_put(child);
                        return ret;
+               }
        }
 
        priv->groups[0] = &pwm_dev_group;