diff options
Diffstat (limited to 'drivers/hwmon/pwm-fan.c')
| -rw-r--r-- | drivers/hwmon/pwm-fan.c | 116 | 
1 files changed, 108 insertions, 8 deletions
diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index 167221c7628a..eead8afe6447 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -18,6 +18,7 @@  #include <linux/hwmon.h>  #include <linux/hwmon-sysfs.h> +#include <linux/interrupt.h>  #include <linux/module.h>  #include <linux/mutex.h>  #include <linux/of.h> @@ -26,6 +27,7 @@  #include <linux/regulator/consumer.h>  #include <linux/sysfs.h>  #include <linux/thermal.h> +#include <linux/timer.h>  #define MAX_PWM 255 @@ -33,6 +35,14 @@ struct pwm_fan_ctx {  	struct mutex lock;  	struct pwm_device *pwm;  	struct regulator *reg_en; + +	int irq; +	atomic_t pulses; +	unsigned int rpm; +	u8 pulses_per_revolution; +	ktime_t sample_start; +	struct timer_list rpm_timer; +  	unsigned int pwm_value;  	unsigned int pwm_fan_state;  	unsigned int pwm_fan_max_state; @@ -40,6 +50,32 @@ struct pwm_fan_ctx {  	struct thermal_cooling_device *cdev;  }; +/* This handler assumes self resetting edge triggered interrupt. */ +static irqreturn_t pulse_handler(int irq, void *dev_id) +{ +	struct pwm_fan_ctx *ctx = dev_id; + +	atomic_inc(&ctx->pulses); + +	return IRQ_HANDLED; +} + +static void sample_timer(struct timer_list *t) +{ +	struct pwm_fan_ctx *ctx = from_timer(ctx, t, rpm_timer); +	int pulses; +	u64 tmp; + +	pulses = atomic_read(&ctx->pulses); +	atomic_sub(pulses, &ctx->pulses); +	tmp = (u64)pulses * ktime_ms_delta(ktime_get(), ctx->sample_start) * 60; +	do_div(tmp, ctx->pulses_per_revolution * 1000); +	ctx->rpm = tmp; + +	ctx->sample_start = ktime_get(); +	mod_timer(&ctx->rpm_timer, jiffies + HZ); +} +  static int  __set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm)  {  	unsigned long period; @@ -100,15 +136,45 @@ static ssize_t pwm_show(struct device *dev, struct device_attribute *attr,  	return sprintf(buf, "%u\n", ctx->pwm_value);  } +static ssize_t rpm_show(struct device *dev, +			struct device_attribute *attr, char *buf) +{ +	struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); + +	return sprintf(buf, "%u\n", ctx->rpm); +}  static SENSOR_DEVICE_ATTR_RW(pwm1, pwm, 0); +static SENSOR_DEVICE_ATTR_RO(fan1_input, rpm, 0);  static struct attribute *pwm_fan_attrs[] = {  	&sensor_dev_attr_pwm1.dev_attr.attr, +	&sensor_dev_attr_fan1_input.dev_attr.attr,  	NULL,  }; -ATTRIBUTE_GROUPS(pwm_fan); +static umode_t pwm_fan_attrs_visible(struct kobject *kobj, struct attribute *a, +				     int n) +{ +	struct device *dev = container_of(kobj, struct device, kobj); +	struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); + +	/* Hide fan_input in case no interrupt is available  */ +	if (n == 1 && ctx->irq <= 0) +		return 0; + +	return a->mode; +} + +static const struct attribute_group pwm_fan_group = { +	.attrs = pwm_fan_attrs, +	.is_visible = pwm_fan_attrs_visible, +}; + +static const struct attribute_group *pwm_fan_groups[] = { +	&pwm_fan_group, +	NULL, +};  /* thermal cooling device callbacks */  static int pwm_fan_get_max_state(struct thermal_cooling_device *cdev, @@ -214,6 +280,7 @@ static int pwm_fan_probe(struct platform_device *pdev)  	struct device *hwmon;  	int ret;  	struct pwm_state state = { }; +	u32 ppr = 2;  	ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);  	if (!ctx) @@ -233,6 +300,10 @@ static int pwm_fan_probe(struct platform_device *pdev)  	platform_set_drvdata(pdev, ctx); +	ctx->irq = platform_get_irq(pdev, 0); +	if (ctx->irq == -EPROBE_DEFER) +		return ctx->irq; +  	ctx->reg_en = devm_regulator_get_optional(&pdev->dev, "fan");  	if (IS_ERR(ctx->reg_en)) {  		if (PTR_ERR(ctx->reg_en) != -ENODEV) @@ -257,21 +328,44 @@ static int pwm_fan_probe(struct platform_device *pdev)  	ret = pwm_apply_state(ctx->pwm, &state);  	if (ret) { -		dev_err(&pdev->dev, "Failed to configure PWM\n"); +		dev_err(&pdev->dev, "Failed to configure PWM: %d\n", ret);  		goto err_reg_disable;  	} +	timer_setup(&ctx->rpm_timer, sample_timer, 0); + +	of_property_read_u32(pdev->dev.of_node, "pulses-per-revolution", &ppr); +	ctx->pulses_per_revolution = ppr; +	if (!ctx->pulses_per_revolution) { +		dev_err(&pdev->dev, "pulses-per-revolution can't be zero.\n"); +		ret = -EINVAL; +		goto err_pwm_disable; +	} + +	if (ctx->irq > 0) { +		ret = devm_request_irq(&pdev->dev, ctx->irq, pulse_handler, 0, +				       pdev->name, ctx); +		if (ret) { +			dev_err(&pdev->dev, +				"Failed to request interrupt: %d\n", ret); +			goto err_pwm_disable; +		} +		ctx->sample_start = ktime_get(); +		mod_timer(&ctx->rpm_timer, jiffies + HZ); +	} +  	hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, "pwmfan",  						       ctx, pwm_fan_groups);  	if (IS_ERR(hwmon)) { -		dev_err(&pdev->dev, "Failed to register hwmon device\n");  		ret = PTR_ERR(hwmon); -		goto err_pwm_disable; +		dev_err(&pdev->dev, +			"Failed to register hwmon device: %d\n", ret); +		goto err_del_timer;  	}  	ret = pwm_fan_of_get_cooling_data(&pdev->dev, ctx);  	if (ret) -		return ret; +		goto err_del_timer;  	ctx->pwm_fan_state = ctx->pwm_fan_max_state;  	if (IS_ENABLED(CONFIG_THERMAL)) { @@ -279,10 +373,11 @@ static int pwm_fan_probe(struct platform_device *pdev)  							  "pwm-fan", ctx,  							  &pwm_fan_cooling_ops);  		if (IS_ERR(cdev)) { -			dev_err(&pdev->dev, -				"Failed to register pwm-fan as cooling device");  			ret = PTR_ERR(cdev); -			goto err_pwm_disable; +			dev_err(&pdev->dev, +				"Failed to register pwm-fan as cooling device: %d\n", +				ret); +			goto err_del_timer;  		}  		ctx->cdev = cdev;  		thermal_cdev_update(cdev); @@ -290,6 +385,9 @@ static int pwm_fan_probe(struct platform_device *pdev)  	return 0; +err_del_timer: +	del_timer_sync(&ctx->rpm_timer); +  err_pwm_disable:  	state.enabled = false;  	pwm_apply_state(ctx->pwm, &state); @@ -306,6 +404,8 @@ static int pwm_fan_remove(struct platform_device *pdev)  	struct pwm_fan_ctx *ctx = platform_get_drvdata(pdev);  	thermal_cooling_device_unregister(ctx->cdev); +	del_timer_sync(&ctx->rpm_timer); +  	if (ctx->pwm_value)  		pwm_disable(ctx->pwm);  | 
