summaryrefslogtreecommitdiffstats
path: root/drivers/hwmon/pwm-fan.c
diff options
context:
space:
mode:
authorDave Airlie <airlied@redhat.com>2015-04-20 03:32:26 +0200
committerDave Airlie <airlied@redhat.com>2015-04-20 05:05:20 +0200
commit2c33ce009ca2389dbf0535d0672214d09738e35e (patch)
tree6186a6458c3c160385d794a23eaf07c786a9e61b /drivers/hwmon/pwm-fan.c
parentmedia-bus: Fixup RGB444_1X12, RGB565_1X16, and YUV8_1X24 media bus format (diff)
parentMerge branch 'turbostat' of git://git.kernel.org/pub/scm/linux/kernel/git/len... (diff)
downloadlinux-2c33ce009ca2389dbf0535d0672214d09738e35e.tar.xz
linux-2c33ce009ca2389dbf0535d0672214d09738e35e.zip
Merge Linus master into drm-next
The merge is clean, but the arm build fails afterwards, due to API changes in the regulator tree. I've included the patch into the merge to fix the build. Signed-off-by: Dave Airlie <airlied@redhat.com>
Diffstat (limited to 'drivers/hwmon/pwm-fan.c')
-rw-r--r--drivers/hwmon/pwm-fan.c184
1 files changed, 163 insertions, 21 deletions
diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c
index 1991d9032c38..2d9a712699ff 100644
--- a/drivers/hwmon/pwm-fan.c
+++ b/drivers/hwmon/pwm-fan.c
@@ -24,55 +24,78 @@
#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", },
{},
};