diff options
Diffstat (limited to 'drivers/hwmon/pwm-fan.c')
| -rw-r--r-- | drivers/hwmon/pwm-fan.c | 184 | 
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", },  	{},  }; |