Merge branch 'akpm' (patches from Andrew)
[cascardo/linux.git] / drivers / hwmon / pwm-fan.c
index 1991d90..2d9a712 100644 (file)
 #include <linux/platform_device.h>
 #include <linux/pwm.h>
 #include <linux/sysfs.h>
+#include <linux/thermal.h>
 
 #define MAX_PWM 255
 
 struct pwm_fan_ctx {
        struct mutex lock;
        struct pwm_device *pwm;
-       unsigned char pwm_value;
+       unsigned int pwm_value;
+       unsigned int pwm_fan_state;
+       unsigned int pwm_fan_max_state;
+       unsigned int *pwm_fan_cooling_levels;
+       struct thermal_cooling_device *cdev;
 };
 
-static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
-                      const char *buf, size_t count)
+static int  __set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm)
 {
-       struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
-       unsigned long pwm, duty;
-       ssize_t ret;
-
-       if (kstrtoul(buf, 10, &pwm) || pwm > MAX_PWM)
-               return -EINVAL;
+       unsigned long duty;
+       int ret = 0;
 
        mutex_lock(&ctx->lock);
-
        if (ctx->pwm_value == pwm)
-               goto exit_set_pwm_no_change;
-
-       if (pwm == 0) {
-               pwm_disable(ctx->pwm);
-               goto exit_set_pwm;
-       }
+               goto exit_set_pwm_err;
 
        duty = DIV_ROUND_UP(pwm * (ctx->pwm->period - 1), MAX_PWM);
        ret = pwm_config(ctx->pwm, duty, ctx->pwm->period);
        if (ret)
                goto exit_set_pwm_err;
 
+       if (pwm == 0)
+               pwm_disable(ctx->pwm);
+
        if (ctx->pwm_value == 0) {
                ret = pwm_enable(ctx->pwm);
                if (ret)
                        goto exit_set_pwm_err;
        }
 
-exit_set_pwm:
        ctx->pwm_value = pwm;
-exit_set_pwm_no_change:
-       ret = count;
 exit_set_pwm_err:
        mutex_unlock(&ctx->lock);
        return ret;
 }
 
+static void pwm_fan_update_state(struct pwm_fan_ctx *ctx, unsigned long pwm)
+{
+       int i;
+
+       for (i = 0; i < ctx->pwm_fan_max_state; ++i)
+               if (pwm < ctx->pwm_fan_cooling_levels[i + 1])
+                       break;
+
+       ctx->pwm_fan_state = i;
+}
+
+static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
+                      const char *buf, size_t count)
+{
+       struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
+       unsigned long pwm;
+       int ret;
+
+       if (kstrtoul(buf, 10, &pwm) || pwm > MAX_PWM)
+               return -EINVAL;
+
+       ret = __set_pwm(ctx, pwm);
+       if (ret)
+               return ret;
+
+       pwm_fan_update_state(ctx, pwm);
+       return count;
+}
+
 static ssize_t show_pwm(struct device *dev,
                        struct device_attribute *attr, char *buf)
 {
@@ -91,10 +114,108 @@ static struct attribute *pwm_fan_attrs[] = {
 
 ATTRIBUTE_GROUPS(pwm_fan);
 
+/* thermal cooling device callbacks */
+static int pwm_fan_get_max_state(struct thermal_cooling_device *cdev,
+                                unsigned long *state)
+{
+       struct pwm_fan_ctx *ctx = cdev->devdata;
+
+       if (!ctx)
+               return -EINVAL;
+
+       *state = ctx->pwm_fan_max_state;
+
+       return 0;
+}
+
+static int pwm_fan_get_cur_state(struct thermal_cooling_device *cdev,
+                                unsigned long *state)
+{
+       struct pwm_fan_ctx *ctx = cdev->devdata;
+
+       if (!ctx)
+               return -EINVAL;
+
+       *state = ctx->pwm_fan_state;
+
+       return 0;
+}
+
+static int
+pwm_fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
+{
+       struct pwm_fan_ctx *ctx = cdev->devdata;
+       int ret;
+
+       if (!ctx || (state > ctx->pwm_fan_max_state))
+               return -EINVAL;
+
+       if (state == ctx->pwm_fan_state)
+               return 0;
+
+       ret = __set_pwm(ctx, ctx->pwm_fan_cooling_levels[state]);
+       if (ret) {
+               dev_err(&cdev->device, "Cannot set pwm!\n");
+               return ret;
+       }
+
+       ctx->pwm_fan_state = state;
+
+       return ret;
+}
+
+static const struct thermal_cooling_device_ops pwm_fan_cooling_ops = {
+       .get_max_state = pwm_fan_get_max_state,
+       .get_cur_state = pwm_fan_get_cur_state,
+       .set_cur_state = pwm_fan_set_cur_state,
+};
+
+static int pwm_fan_of_get_cooling_data(struct device *dev,
+                                      struct pwm_fan_ctx *ctx)
+{
+       struct device_node *np = dev->of_node;
+       int num, i, ret;
+
+       if (!of_find_property(np, "cooling-levels", NULL))
+               return 0;
+
+       ret = of_property_count_u32_elems(np, "cooling-levels");
+       if (ret <= 0) {
+               dev_err(dev, "Wrong data!\n");
+               return ret ? : -EINVAL;
+       }
+
+       num = ret;
+       ctx->pwm_fan_cooling_levels = devm_kzalloc(dev, num * sizeof(u32),
+                                                  GFP_KERNEL);
+       if (!ctx->pwm_fan_cooling_levels)
+               return -ENOMEM;
+
+       ret = of_property_read_u32_array(np, "cooling-levels",
+                                        ctx->pwm_fan_cooling_levels, num);
+       if (ret) {
+               dev_err(dev, "Property 'cooling-levels' cannot be read!\n");
+               return ret;
+       }
+
+       for (i = 0; i < num; i++) {
+               if (ctx->pwm_fan_cooling_levels[i] > MAX_PWM) {
+                       dev_err(dev, "PWM fan state[%d]:%d > %d\n", i,
+                               ctx->pwm_fan_cooling_levels[i], MAX_PWM);
+                       return -EINVAL;
+               }
+       }
+
+       ctx->pwm_fan_max_state = num - 1;
+
+       return 0;
+}
+
 static int pwm_fan_probe(struct platform_device *pdev)
 {
-       struct device *hwmon;
+       struct thermal_cooling_device *cdev;
        struct pwm_fan_ctx *ctx;
+       struct device *hwmon;
        int duty_cycle;
        int ret;
 
@@ -136,6 +257,26 @@ static int pwm_fan_probe(struct platform_device *pdev)
                pwm_disable(ctx->pwm);
                return PTR_ERR(hwmon);
        }
+
+       ret = pwm_fan_of_get_cooling_data(&pdev->dev, ctx);
+       if (ret)
+               return ret;
+
+       ctx->pwm_fan_state = ctx->pwm_fan_max_state;
+       if (IS_ENABLED(CONFIG_THERMAL)) {
+               cdev = thermal_of_cooling_device_register(pdev->dev.of_node,
+                                                         "pwm-fan", ctx,
+                                                         &pwm_fan_cooling_ops);
+               if (IS_ERR(cdev)) {
+                       dev_err(&pdev->dev,
+                               "Failed to register pwm-fan as cooling device");
+                       pwm_disable(ctx->pwm);
+                       return PTR_ERR(cdev);
+               }
+               ctx->cdev = cdev;
+               thermal_cdev_update(cdev);
+       }
+
        return 0;
 }
 
@@ -143,6 +284,7 @@ static int pwm_fan_remove(struct platform_device *pdev)
 {
        struct pwm_fan_ctx *ctx = platform_get_drvdata(pdev);
 
+       thermal_cooling_device_unregister(ctx->cdev);
        if (ctx->pwm_value)
                pwm_disable(ctx->pwm);
        return 0;
@@ -177,7 +319,7 @@ static int pwm_fan_resume(struct device *dev)
 
 static SIMPLE_DEV_PM_OPS(pwm_fan_pm, pwm_fan_suspend, pwm_fan_resume);
 
-static struct of_device_id of_pwm_fan_match[] = {
+static const struct of_device_id of_pwm_fan_match[] = {
        { .compatible = "pwm-fan", },
        {},
 };