[PATCH 4/5] hwmon: DNS323 rev C1 fan support

Benjamin Herrenschmidt benh at kernel.crashing.org
Sat May 22 07:00:43 EDT 2010


On Sat, 2010-05-22 at 20:54 +1000, Benjamin Herrenschmidt wrote:
> The hardware only supports 3 settings: off, slow and fast.
> 
> In order to have a chance to work with existing fan control systems,
> we emulate a PWM device with the following mapping:
> 
>    0.. 15  off		0 RPM input
>   16..127  slow	      100 RPM input
                        ^^^

Little typo in the changeset comment, I actually fake 400 RPM, which
sounds more realistic from a gut feeling of how the fan is going but
I haven't actually measured.

Cheers,
Ben.

>  128..255  fast      2000 RPM input
> 
> This provides something more/less working with fancontrol, though
> it does have a tendency to work by doing short bursts of "slow"
> speed every half a minute as it settles around my min temp. Not
> a big deal a specialized script could probably do better, or even
> tweaks to fancontrol config. At leats it should be safe.
> 
> Signed-off-by: Benjamin Herrenschmidt <benh at kernel.crashing.org>
> CC: lm-sensors at lm-sensors.org
> ---
>  drivers/hwmon/Kconfig       |   12 ++
>  drivers/hwmon/Makefile      |    1 +
>  drivers/hwmon/dns323c-fan.c |  271 +++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 284 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/hwmon/dns323c-fan.c
> 
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 9be8e17..5f55735 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1087,6 +1087,18 @@ config SENSORS_MC13783_ADC
>          help
>            Support for the A/D converter on MC13783 PMIC.
>  
> +config SENSORS_DNS323C_FAN
> +       tristate "D-Link DNS323 rev C1 Fan"
> +       depends on MACH_DNS323
> +       help
> +         Support for the GPIO based fan control on the D-Link DNS323
> +	 HW revision C1. This exposes a pseudo pwm device with the
> +	 following values supported:
> +
> +	 	    0..15	: Fan off
> +		   16..127	: Fan on low speed
> +		  128..255	: Fan on high speed
> +	 
>  if ACPI
>  
>  comment "ACPI drivers"
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 4aa1a3d..15bcdef 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -100,6 +100,7 @@ obj-$(CONFIG_SENSORS_W83L785TS)	+= w83l785ts.o
>  obj-$(CONFIG_SENSORS_W83L786NG)	+= w83l786ng.o
>  obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
>  obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
> +obj-$(CONFIG_SENSORS_DNS323C_FAN)+= dns323c-fan.o
>  
>  ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y)
>  EXTRA_CFLAGS += -DDEBUG
> diff --git a/drivers/hwmon/dns323c-fan.c b/drivers/hwmon/dns323c-fan.c
> new file mode 100644
> index 0000000..4ae18d0
> --- /dev/null
> +++ b/drivers/hwmon/dns323c-fan.c
> @@ -0,0 +1,271 @@
> +/*
> + *  dns323c_fan - Driver for the D-LINK DNS-323 rev C1 fan control
> + *
> + *  Copyright 2010 Benjamin Herrenschmidt
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; either version 2 of the License, or
> + *  (at your option) any later version.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/gpio.h>
> +#include <linux/hwmon.h>
> +
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/gfp.h>
> +#include <linux/slab.h>
> +#include <linux/err.h>
> +#include <linux/sysfs.h>
> +#include <linux/platform_device.h>
> +
> +enum fan_speed {
> +	FAN_OFF,
> +	FAN_LOW	,
> +	FAN_FAST,
> +	FAN_FAST_LOCK,
> +};
> +
> +#define DNS323C_GPIO_FAN_BIT1		18
> +#define DNS323C_GPIO_FAN_BIT0		19
> +
> +struct dns323c_fan {
> +	struct device	*hwmon;
> +	struct mutex	lock;
> +	enum fan_speed	speed;
> +};
> +
> +static void __set_fan_speed(struct dns323c_fan *fan, enum fan_speed speed)
> +{
> +	if (speed == fan->speed)
> +		return;
> +
> +	switch(speed) {
> +	case FAN_OFF:
> +		gpio_set_value(DNS323C_GPIO_FAN_BIT1, 0);
> +		gpio_set_value(DNS323C_GPIO_FAN_BIT0, 0);
> +		break;
> +	case FAN_LOW:
> +		gpio_set_value(DNS323C_GPIO_FAN_BIT1, 0);
> +		gpio_set_value(DNS323C_GPIO_FAN_BIT0, 1);
> +		break;
> +	default:
> +		gpio_set_value(DNS323C_GPIO_FAN_BIT0, 0);
> +		gpio_set_value(DNS323C_GPIO_FAN_BIT1, 1);
> +	};
> +	fan->speed = speed;
> +}
> +
> +static ssize_t show_name(struct device *dev, struct device_attribute *da,
> +			   char *buf)
> +{
> +	return sprintf(buf, "dns323c-fan\n");
> +}
> +
> +static ssize_t show_pwm(struct device *dev, struct device_attribute *da,
> +			char *buf)
> +{
> +	struct dns323c_fan *fan = dev_get_drvdata(dev);
> +	int pseudo_pwm;
> +
> +	switch(fan->speed) {
> +	case FAN_OFF:
> +		pseudo_pwm = 0;
> +		break;
> +	case FAN_LOW:
> +		pseudo_pwm = 63;
> +		break;
> +	default:
> +		pseudo_pwm = 255;
> +	}
> +	return sprintf(buf, "%d\n", pseudo_pwm);
> +}
> +
> +static ssize_t set_pwm(struct device *dev, struct device_attribute *da,
> +		       const char *buf, size_t count)
> +{
> +	struct dns323c_fan *fan = dev_get_drvdata(dev);
> +	enum fan_speed speed;
> +	unsigned long val;
> +
> +	if (strict_strtoul(buf, 10, &val))
> +		return -EINVAL;
> +	if (fan->speed == FAN_FAST_LOCK)
> +		return count;
> +
> +	mutex_lock(&fan->lock);
> +	if (val < 16)
> +		speed = FAN_OFF;
> +	else if (val < 128)
> +		speed = FAN_LOW;
> +	else
> +		speed = FAN_FAST;
> +	__set_fan_speed(fan, speed);
> +	mutex_unlock(&fan->lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_pwm_en(struct device *dev, struct device_attribute *da,
> +			   char *buf)
> +{
> +	struct dns323c_fan *fan = dev_get_drvdata(dev);
> +
> +	if (fan->speed == FAN_FAST_LOCK)
> +		return sprintf(buf, "0\n");
> +	else
> +		return sprintf(buf, "1\n");
> +}
> +
> +static ssize_t set_pwm_en(struct device *dev, struct device_attribute *da,
> +			  const char *buf, size_t count)
> +{
> +	struct dns323c_fan *fan = dev_get_drvdata(dev);
> +	enum fan_speed speed;
> +	unsigned long val;
> +
> +	if (strict_strtoul(buf, 10, &val))
> +		return -EINVAL;
> +	if (val != 0 && val != 1)
> +		return -EINVAL;
> +
> +	mutex_lock(&fan->lock);
> +	if (val == 0 && fan->speed != FAN_FAST_LOCK)
> +		speed = FAN_FAST_LOCK;
> +	else if (val != 0 && fan->speed == FAN_FAST_LOCK)
> +		speed = FAN_FAST;
> +	else
> +		speed = fan->speed;
> +	__set_fan_speed(fan, speed);
> +	mutex_unlock(&fan->lock);
> +
> +	return count;
> +}
> +
> +static ssize_t show_fake_rpm(struct device *dev, struct device_attribute *da,
> +			     char *buf)
> +{
> +	struct dns323c_fan *fan = dev_get_drvdata(dev);
> +	int pseudo_rpm;
> +
> +	switch(fan->speed) {
> +	case FAN_OFF:
> +		pseudo_rpm = 0;
> +		break;
> +	case FAN_LOW:
> +		pseudo_rpm = 400;
> +		break;
> +	default:
> +		pseudo_rpm = 2000;
> +	}
> +	return sprintf(buf, "%d\n", pseudo_rpm);
> +}
> +
> +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
> +static DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm);
> +static DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, show_pwm_en, set_pwm_en);
> +static DEVICE_ATTR(fan1_input, S_IRUGO, show_fake_rpm, NULL);
> +
> +static int dns323c_fan_probe(struct platform_device *pdev)
> +{
> +	struct dns323c_fan *fan = NULL;
> +	int ret = -ENXIO;
> +
> +	/* Get the GPIOs */
> +	if (gpio_request(DNS323C_GPIO_FAN_BIT0, "FAN0") != 0) {
> +		pr_err("dns323c_fan: Failed to request fan GPIO 0 !\n");
> +		return -ENXIO;
> +	}
> +	if (gpio_request(DNS323C_GPIO_FAN_BIT1, "FAN1") != 0) {
> +		pr_err("dns323c_fan: Failed to request fan GPIO 1 !\n");
> +		goto err_gpio;
> +	}
> +
> +	/* Set directions to output and medium speed. We write bit 1 first
> +	 * since it contains 0 to avoid having a transitory 11 state which
> +	 * isn't supported
> +	 */
> +	gpio_direction_output(DNS323C_GPIO_FAN_BIT1, 0);
> +	gpio_direction_output(DNS323C_GPIO_FAN_BIT0, 1);
> +
> +	/* Grab some memory for our state */
> +	fan = kzalloc(sizeof(struct dns323c_fan), GFP_KERNEL);
> +	if (!fan) {
> +		ret = -ENOMEM;
> +		goto err_alloc;
> +	}
> +	fan->speed = FAN_LOW;
> +	mutex_init(&fan->lock);
> +	platform_set_drvdata(pdev, fan);
> +
> +	ret = device_create_file(&pdev->dev, &dev_attr_name);
> +	ret |= device_create_file(&pdev->dev, &dev_attr_pwm1);
> +	ret |= device_create_file(&pdev->dev, &dev_attr_pwm1_enable);
> +	ret |= device_create_file(&pdev->dev, &dev_attr_fan1_input);
> +	if (ret)
> +		goto err_file;
> +
> +	fan->hwmon = hwmon_device_register(&pdev->dev);
> +	if (IS_ERR(fan->hwmon)) {
> +		ret = PTR_ERR(fan->hwmon);
> +		goto err_dev;
> +	}
> +	return 0;
> +
> + err_dev:
> +	device_remove_file(&pdev->dev, &dev_attr_name);
> +	device_remove_file(&pdev->dev, &dev_attr_pwm1);
> +	device_remove_file(&pdev->dev, &dev_attr_pwm1_enable);
> +	device_remove_file(&pdev->dev, &dev_attr_fan1_input);
> + err_file:
> +	kfree(fan);
> + err_alloc:
> +	gpio_free(DNS323C_GPIO_FAN_BIT1);
> + err_gpio:
> +	gpio_free(DNS323C_GPIO_FAN_BIT0);
> +	return ret;
> +}
> +
> +static int __devexit dns323c_fan_remove(struct platform_device *pdev)
> +{
> +	struct dns323c_fan *fan = platform_get_drvdata(pdev);
> +
> +	hwmon_device_unregister(fan->hwmon);
> +	device_remove_file(&pdev->dev, &dev_attr_name);
> +	device_remove_file(&pdev->dev, &dev_attr_pwm1);
> +	device_remove_file(&pdev->dev, &dev_attr_pwm1_enable);
> +	device_remove_file(&pdev->dev, &dev_attr_fan1_input);
> +	kfree(fan);
> +	gpio_free(DNS323C_GPIO_FAN_BIT1);
> +	gpio_free(DNS323C_GPIO_FAN_BIT0);
> +	return 0;
> +}
> +
> +static struct platform_driver dns323c_fan_driver = {
> +	.probe = dns323c_fan_probe,
> +	.remove = __devexit_p(dns323c_fan_remove),
> +	.driver = {
> +		.name = "dns323c-fan",
> +		.owner = THIS_MODULE,
> +	},
> +};
> +
> +static int __init dns323c_fan_init(void)
> +{
> +	return platform_driver_register(&dns323c_fan_driver);
> +}
> +
> +static void __exit dns323c_fan_exit(void)
> +{
> +	platform_driver_unregister(&dns323c_fan_driver);
> +}
> +
> +MODULE_AUTHOR("Benjamin Herrenschmidt <benh at kernel.crashing.org>");
> +MODULE_DESCRIPTION("DNS323 RevC1 Fan control");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:dns323c-fan");
> +
> +module_init(dns323c_fan_init);
> +module_exit(dns323c_fan_exit);
> 
> 
> 
> _______________________________________________
> linux-arm-kernel mailing list
> linux-arm-kernel at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel





More information about the linux-arm-kernel mailing list