* [PATCH v2] hwmon: add generic GPIO fan driver @ 2010-10-20 16:37 Simon Guinot 2010-10-20 16:37 ` Simon Guinot 0 siblings, 1 reply; 16+ messages in thread From: Simon Guinot @ 2010-10-20 16:37 UTC (permalink / raw) To: linux-arm-kernel Hi Guenter, Here is an updated version for the gpio-fan driver. v2 changes : - add support for sysfs attributes fan1_target, fan1_min and fan1_max - simplify the pwm interface - add power management support - fix interrupt handling (allow to share the fan alarm irq) - fix bugs mostly addressed during code review... Documentation is still missing. Thanks, Simon ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2] hwmon: add generic GPIO fan driver 2010-10-20 16:37 [PATCH v2] hwmon: add generic GPIO fan driver Simon Guinot @ 2010-10-20 16:37 ` Simon Guinot 2010-10-20 16:59 ` Guenter Roeck 2010-10-21 6:41 ` Guenter Roeck 0 siblings, 2 replies; 16+ messages in thread From: Simon Guinot @ 2010-10-20 16:37 UTC (permalink / raw) To: linux-arm-kernel This patch adds hwmon support for the GPIO connected fan. Platform specific informations as GPIO pinout and speed conversion array (rpm from/to GPIO value) are passed to the driver via platform_data. Signed-off-by: Simon Guinot <sguinot@lacie.com> --- drivers/hwmon/Kconfig | 9 + drivers/hwmon/Makefile | 1 + drivers/hwmon/gpio-fan.c | 557 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/gpio-fan.h | 36 +++ 4 files changed, 603 insertions(+), 0 deletions(-) create mode 100644 drivers/hwmon/gpio-fan.c create mode 100644 include/linux/gpio-fan.h diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 4d4d09b..af318a1 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -368,6 +368,15 @@ config SENSORS_FSCHMD This driver can also be built as a module. If so, the module will be called fschmd. +config SENSORS_GPIO_FAN + tristate "GPIO fan" + depends on GENERIC_GPIO + help + If you say yes here you get support for the GPIO connected fan. + + This driver can also be built as a module. If so, the module + will be called gpio-fan. + config SENSORS_G760A tristate "GMT G760A" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index e3c2484..5df7e4a 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_SENSORS_F71805F) += f71805f.o obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o obj-$(CONFIG_SENSORS_F75375S) += f75375s.o obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o +obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o obj-$(CONFIG_SENSORS_G760A) += g760a.o obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c new file mode 100644 index 0000000..e3131a6 --- /dev/null +++ b/drivers/hwmon/gpio-fan.c @@ -0,0 +1,557 @@ +/* + * gpio-fan.c - Hwmon driver for GPIO connected fan. + * + * Copyright (C) 2010 LaCie + * + * Author: Simon Guinot <sguinot@lacie.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/hwmon.h> +#include <linux/gpio.h> +#include <linux/gpio-fan.h> + +struct gpio_fan_data { + struct platform_device *pdev; + struct device *hwmon_dev; + struct mutex lock; /* lock GPIOs operations. */ + int num_ctrl; + unsigned *ctrl; + int num_speed; + struct gpio_fan_speed *speed; + int speed_index; +#ifdef CONFIG_PM + int resume_speed; +#endif + int pwm_enable; + struct gpio_fan_alarm *alarm; + struct work_struct alarm_work; +}; + +/* + * Alarm GPIO. + */ + +static void fan_alarm_notify(struct work_struct *ws) +{ + struct gpio_fan_data *fan_data = + container_of(ws, struct gpio_fan_data, alarm_work); + + sysfs_notify(&fan_data->pdev->dev.kobj, NULL, "fan1_alarm"); + kobject_uevent(&fan_data->pdev->dev.kobj, KOBJ_CHANGE); +} + +static irqreturn_t fan_alarm_irq_handler(int irq, void *dev_id) +{ + struct gpio_fan_data *fan_data = dev_id; + + schedule_work(&fan_data->alarm_work); + + return IRQ_NONE; +} + +static ssize_t show_fan_alarm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + struct gpio_fan_alarm *alarm = fan_data->alarm; + int value = gpio_get_value(alarm->gpio); + + if (alarm->active_low) + value = !value; + + return sprintf(buf, "%d\n", value); +} + +static DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL); + +static int fan_alarm_init(struct gpio_fan_data *fan_data, + struct gpio_fan_alarm *alarm) +{ + int err; + int alarm_irq; + struct platform_device *pdev = fan_data->pdev; + + fan_data->alarm = alarm; + + err = gpio_request(alarm->gpio, "GPIO fan alarm"); + if (err) + return err; + + err = gpio_direction_input(alarm->gpio); + if (err) + goto err_free_gpio; + + err = device_create_file(&pdev->dev, &dev_attr_fan1_alarm); + if (err) + goto err_free_gpio; + + alarm_irq = gpio_to_irq(alarm->gpio); + if (alarm_irq < 0) + return 0; + + INIT_WORK(&fan_data->alarm_work, fan_alarm_notify); + set_irq_type(alarm_irq, IRQ_TYPE_EDGE_BOTH); + err = request_irq(alarm_irq, fan_alarm_irq_handler, IRQF_SHARED, + "GPIO fan alarm", fan_data); + if (err) + goto err_free_sysfs; + + return 0; + +err_free_sysfs: + device_remove_file(&pdev->dev, &dev_attr_fan1_alarm); +err_free_gpio: + gpio_free(alarm->gpio); + + return err; +} + +static void fan_alarm_free(struct gpio_fan_data *fan_data) +{ + struct platform_device *pdev = fan_data->pdev; + int alarm_irq = gpio_to_irq(fan_data->alarm->gpio); + + if (alarm_irq >= 0) + free_irq(alarm_irq, fan_data); + device_remove_file(&pdev->dev, &dev_attr_fan1_alarm); + gpio_free(fan_data->alarm->gpio); +} + +/* + * Control GPIOs. + */ + +static void __set_fan_ctrl(struct gpio_fan_data *fan_data, int ctrl_val) +{ + int i; + + for (i = 0; i < fan_data->num_ctrl; i++) + gpio_set_value(fan_data->ctrl[i], (ctrl_val >> i) & 1); +} + +static int __get_fan_ctrl(struct gpio_fan_data *fan_data) +{ + int i; + int ctrl_val = 0; + + for (i = 0; i < fan_data->num_ctrl; i++) { + int value; + + value = gpio_get_value(fan_data->ctrl[i]); + ctrl_val |= (value << i); + } + return ctrl_val; +} + +static void set_fan_speed(struct gpio_fan_data *fan_data, int speed_index) +{ + if (fan_data->speed_index == speed_index) + return; + + __set_fan_ctrl(fan_data, fan_data->speed[speed_index].ctrl_val); + fan_data->speed_index = speed_index; +} + +static int get_fan_speed(struct gpio_fan_data *fan_data) +{ + int ctrl_val = __get_fan_ctrl(fan_data); + int i; + + for (i = 0; i < fan_data->num_speed; i++) + if (fan_data->speed[i].ctrl_val == ctrl_val) + return i; + + dev_warn(&fan_data->pdev->dev, + "missing speed array entry for GPIO value %d\n", ctrl_val); + + return -EINVAL; +} + +static int rpm_to_speed(struct gpio_fan_data *fan_data, int rpm) +{ + struct gpio_fan_speed *speed = fan_data->speed; + int speed_index = fan_data->num_speed - 1; /* Maximum speed */ + int i; + + for (i = 0; i < fan_data->num_speed; i++) + if (speed[i].rpm >= rpm) { + speed_index = i; + break; + } + return speed_index; +} + +static ssize_t show_pwm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + u8 pwm = DIV_ROUND_CLOSEST(fan_data->speed_index * 255, + fan_data->num_speed - 1); + + return sprintf(buf, "%d\n", pwm); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + unsigned long pwm; + int speed_index; + int ret = count; + + if (strict_strtoul(buf, 10, &pwm) || pwm > 255) + return -EINVAL; + + mutex_lock(&fan_data->lock); + + if (fan_data->pwm_enable != 1) { + ret = -EPERM; + goto exit_unlock; + } + + speed_index = DIV_ROUND_UP(pwm * (fan_data->num_speed - 1), 255); + set_fan_speed(fan_data, speed_index); + +exit_unlock: + mutex_unlock(&fan_data->lock); + + return ret; +} + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", fan_data->pwm_enable); +} + +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 10, &val) || val > 1) + return -EINVAL; + + if (fan_data->pwm_enable == val) + return count; + + mutex_lock(&fan_data->lock); + + fan_data->pwm_enable = val; + + /* Disable manual control mode: set fan at full speed. */ + if (val == 0) + set_fan_speed(fan_data, fan_data->num_speed - 1); + + mutex_unlock(&fan_data->lock); + + return count; +} + +static ssize_t show_pwm_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "0\n"); +} + +static ssize_t show_rpm_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", fan_data->speed[0].rpm); +} + +static ssize_t show_rpm_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", + fan_data->speed[fan_data->num_speed - 1].rpm); +} + +static ssize_t show_rpm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", fan_data->speed[fan_data->speed_index].rpm); +} + +static ssize_t set_rpm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + unsigned long rpm; + int ret = count; + + if (strict_strtoul(buf, 10, &rpm)) + return -EINVAL; + + mutex_lock(&fan_data->lock); + + if (fan_data->pwm_enable != 1) { + ret = -EPERM; + goto exit_unlock; + } + + set_fan_speed(fan_data, rpm_to_speed(fan_data, rpm)); + +exit_unlock: + mutex_unlock(&fan_data->lock); + + return ret; +} + +static DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm); +static DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable); +static DEVICE_ATTR(pwm1_mode, S_IRUGO, show_pwm_mode, NULL); +static DEVICE_ATTR(fan1_min, S_IRUGO, show_rpm_min, NULL); +static DEVICE_ATTR(fan1_max, S_IRUGO, show_rpm_max, NULL); +static DEVICE_ATTR(fan1_input, S_IRUGO, show_rpm, NULL); +static DEVICE_ATTR(fan1_target, S_IRUGO | S_IWUSR, show_rpm, set_rpm); + +static struct attribute *gpio_fan_ctrl_attributes[] = { + &dev_attr_pwm1.attr, + &dev_attr_pwm1_enable.attr, + &dev_attr_pwm1_mode.attr, + &dev_attr_fan1_input.attr, + &dev_attr_fan1_target.attr, + &dev_attr_fan1_min.attr, + &dev_attr_fan1_max.attr, + NULL +}; + +static const struct attribute_group gpio_fan_ctrl_group = { + .attrs = gpio_fan_ctrl_attributes, +}; + +static int fan_ctrl_init(struct gpio_fan_data *fan_data, + struct gpio_fan_platform_data *pdata) +{ + struct platform_device *pdev = fan_data->pdev; + int num_ctrl = pdata->num_ctrl; + unsigned *ctrl = pdata->ctrl; + int i, err; + + for (i = 0; i < num_ctrl; i++) { + err = gpio_request(ctrl[i], "GPIO fan control"); + if (err) + goto err_free_gpio; + + err = gpio_direction_output(ctrl[i], gpio_get_value(ctrl[i])); + if (err) { + gpio_free(ctrl[i]); + goto err_free_gpio; + } + } + + err = sysfs_create_group(&pdev->dev.kobj, &gpio_fan_ctrl_group); + if (err) + goto err_free_gpio; + + fan_data->num_ctrl = num_ctrl; + fan_data->ctrl = ctrl; + fan_data->num_speed = pdata->num_speed; + fan_data->speed = pdata->speed; + fan_data->pwm_enable = 1; /* Enable manual fan speed control. */ + fan_data->speed_index = get_fan_speed(fan_data); + if (fan_data->speed_index < 0) { + /* Set fan speed to a known value. */ + set_fan_speed(fan_data, fan_data->num_speed / 2); + dev_info(&pdev->dev, "set fan speed to %d rpm\n", + fan_data->speed[fan_data->speed_index].rpm); + } + + return 0; + +err_free_gpio: + for (i = i - 1; i >= 0; i--) + gpio_free(ctrl[i]); + + return err; +} + +static void fan_ctrl_free(struct gpio_fan_data *fan_data) +{ + struct platform_device *pdev = fan_data->pdev; + int i; + + sysfs_remove_group(&pdev->dev.kobj, &gpio_fan_ctrl_group); + for (i = 0; i < fan_data->num_ctrl; i++) + gpio_free(fan_data->ctrl[i]); +} + +/* + * Platform driver. + */ + +static ssize_t show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "gpio-fan\n"); +} + +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +static int __devinit gpio_fan_probe(struct platform_device *pdev) +{ + int err = 0; + struct gpio_fan_data *fan_data; + struct gpio_fan_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return -EINVAL; + + fan_data = kzalloc(sizeof(struct gpio_fan_data), GFP_KERNEL); + if (!fan_data) + return -ENOMEM; + + fan_data->pdev = pdev; + platform_set_drvdata(pdev, fan_data); + mutex_init(&fan_data->lock); + + /* Configure alarm GPIO if available. */ + if (pdata->alarm) { + err = fan_alarm_init(fan_data, pdata->alarm); + if (err) + goto err_free_data; + } + + /* Configure control GPIOs if available. */ + if (pdata->ctrl && pdata->num_ctrl) { + if (!pdata->num_speed || !pdata->speed) { + err = -EINVAL; + goto err_free_alarm; + } + err = fan_ctrl_init(fan_data, pdata); + if (err) + goto err_free_alarm; + } + + err = device_create_file(&pdev->dev, &dev_attr_name); + if (err) + goto err_free_ctrl; + + /* Make this driver part of hwmon class. */ + fan_data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(fan_data->hwmon_dev)) { + err = PTR_ERR(fan_data->hwmon_dev); + goto err_remove_name; + } + + dev_info(&pdev->dev, "GPIO fan initialized\n"); + + return 0; + +err_remove_name: + device_remove_file(&pdev->dev, &dev_attr_name); +err_free_ctrl: + if (fan_data->ctrl) + fan_ctrl_free(fan_data); +err_free_alarm: + if (fan_data->alarm) + fan_alarm_free(fan_data); +err_free_data: + platform_set_drvdata(pdev, NULL); + kfree(fan_data); + + return err; +} + +static int __devexit gpio_fan_remove(struct platform_device *pdev) +{ + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); + + hwmon_device_unregister(fan_data->hwmon_dev); + device_remove_file(&pdev->dev, &dev_attr_name); + if (fan_data->alarm) + fan_alarm_free(fan_data); + if (fan_data->ctrl) + fan_ctrl_free(fan_data); + kfree(fan_data); + + return 0; +} + +#ifdef CONFIG_PM +static int gpio_fan_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); + + if (fan_data->ctrl) { + fan_data->resume_speed = fan_data->speed_index; + set_fan_speed(fan_data, 0); + } + + return 0; +} + +static int gpio_fan_resume(struct platform_device *pdev) +{ + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); + + if (fan_data->ctrl) + set_fan_speed(fan_data, fan_data->resume_speed); + + return 0; +} +#else +#define gpio_fan_suspend NULL +#define gpio_fan_resume NULL +#endif + +static struct platform_driver gpio_fan_driver = { + .probe = gpio_fan_probe, + .remove = __devexit_p(gpio_fan_remove), + .suspend = gpio_fan_suspend, + .resume = gpio_fan_resume, + .driver = { + .name = "gpio-fan", + }, +}; + +static int __init gpio_fan_init(void) +{ + return platform_driver_register(&gpio_fan_driver); +} + +static void __exit gpio_fan_exit(void) +{ + platform_driver_unregister(&gpio_fan_driver); +} + +module_init(gpio_fan_init); +module_exit(gpio_fan_exit); + +MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); +MODULE_DESCRIPTION("GPIO FAN driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio-fan"); diff --git a/include/linux/gpio-fan.h b/include/linux/gpio-fan.h new file mode 100644 index 0000000..0966591 --- /dev/null +++ b/include/linux/gpio-fan.h @@ -0,0 +1,36 @@ +/* + * include/linux/gpio-fan.h + * + * Platform data structure for GPIO fan driver + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef __LINUX_GPIO_FAN_H +#define __LINUX_GPIO_FAN_H + +struct gpio_fan_alarm { + unsigned gpio; + unsigned active_low; +}; + +struct gpio_fan_speed { + int rpm; + int ctrl_val; +}; + +struct gpio_fan_platform_data { + int num_ctrl; + unsigned *ctrl; /* fan control GPIOs. */ + struct gpio_fan_alarm *alarm; /* fan alarm GPIO. */ + /* + * Speed conversion array: rpm from/to GPIO bit field. + * This array _must_ be sorted in ascending rpm order. + */ + int num_speed; + struct gpio_fan_speed *speed; +}; + +#endif /* __LINUX_GPIO_FAN_H */ -- 1.6.3.1 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2] hwmon: add generic GPIO fan driver 2010-10-20 16:37 ` Simon Guinot @ 2010-10-20 16:59 ` Guenter Roeck 2010-10-20 19:10 ` Simon Guinot 2010-10-20 19:18 ` Russell King - ARM Linux 2010-10-21 6:41 ` Guenter Roeck 1 sibling, 2 replies; 16+ messages in thread From: Guenter Roeck @ 2010-10-20 16:59 UTC (permalink / raw) To: linux-arm-kernel On Wed, Oct 20, 2010 at 12:37:26PM -0400, Simon Guinot wrote: > This patch adds hwmon support for the GPIO connected fan. > > Platform specific informations as GPIO pinout and speed conversion array > (rpm from/to GPIO value) are passed to the driver via platform_data. > > Signed-off-by: Simon Guinot <sguinot@lacie.com> MicroSomething makes it really easy to be annoyed with them (folks, are you reading this ?). Mailer screwed up the patch again. We are making some noise to get this fixed, but that will take a while - the affected mail servers are hiding somewhere in Europe. Unfortunately, none of the hwmon and linux-arm archives I am aware of makes patches available on the web (like patchwork does), so I can not get it from there either. Can you resend it to me as attachment, or give me a pointer where I can pick it up ? Thanks, Guenter ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2] hwmon: add generic GPIO fan driver 2010-10-20 16:59 ` Guenter Roeck @ 2010-10-20 19:10 ` Simon Guinot 2010-10-20 20:42 ` Guenter Roeck 2010-10-20 19:18 ` Russell King - ARM Linux 1 sibling, 1 reply; 16+ messages in thread From: Simon Guinot @ 2010-10-20 19:10 UTC (permalink / raw) To: linux-arm-kernel On Wed, Oct 20, 2010 at 09:59:33AM -0700, Guenter Roeck wrote: > On Wed, Oct 20, 2010 at 12:37:26PM -0400, Simon Guinot wrote: > > This patch adds hwmon support for the GPIO connected fan. > > > > Platform specific informations as GPIO pinout and speed conversion array > > (rpm from/to GPIO value) are passed to the driver via platform_data. > > > > Signed-off-by: Simon Guinot <sguinot@lacie.com> > > MicroSomething makes it really easy to be annoyed with them (folks, are you reading this ?). > Mailer screwed up the patch again. We are making some noise to get this fixed, > but that will take a while - the affected mail servers are hiding somewhere in Europe. > > Unfortunately, none of the hwmon and linux-arm archives I am aware of makes > patches available on the web (like patchwork does), so I can not get it from there > either. Can you resend it to me as attachment, or give me a pointer where I can > pick it up ? You could use: ftp://lacie-nas.org/kernel/patches/0001-hwmon-add-generic-GPIO-fan-driver.patch and in a unknown and variable delay: git://lacie-nas.org/lacie-orion.git (gpio-fan) Simon -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 198 bytes Desc: Digital signature URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20101020/8e7fefc9/attachment.sig> ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2] hwmon: add generic GPIO fan driver 2010-10-20 19:10 ` Simon Guinot @ 2010-10-20 20:42 ` Guenter Roeck 0 siblings, 0 replies; 16+ messages in thread From: Guenter Roeck @ 2010-10-20 20:42 UTC (permalink / raw) To: linux-arm-kernel On Wed, Oct 20, 2010 at 03:10:05PM -0400, Simon Guinot wrote: > On Wed, Oct 20, 2010 at 09:59:33AM -0700, Guenter Roeck wrote: > > On Wed, Oct 20, 2010 at 12:37:26PM -0400, Simon Guinot wrote: > > > This patch adds hwmon support for the GPIO connected fan. > > > > > > Platform specific informations as GPIO pinout and speed conversion array > > > (rpm from/to GPIO value) are passed to the driver via platform_data. > > > > > > Signed-off-by: Simon Guinot <sguinot@lacie.com> > > > > MicroSomething makes it really easy to be annoyed with them (folks, are you reading this ?). > > Mailer screwed up the patch again. We are making some noise to get this fixed, > > but that will take a while - the affected mail servers are hiding somewhere in Europe. > > > > Unfortunately, none of the hwmon and linux-arm archives I am aware of makes > > patches available on the web (like patchwork does), so I can not get it from there > > either. Can you resend it to me as attachment, or give me a pointer where I can > > pick it up ? > > You could use: > > ftp://lacie-nas.org/kernel/patches/0001-hwmon-add-generic-GPIO-fan-driver.patch > Got it, thanks. Guenter ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2] hwmon: add generic GPIO fan driver 2010-10-20 16:59 ` Guenter Roeck 2010-10-20 19:10 ` Simon Guinot @ 2010-10-20 19:18 ` Russell King - ARM Linux 2010-10-20 20:06 ` Guenter Roeck 1 sibling, 1 reply; 16+ messages in thread From: Russell King - ARM Linux @ 2010-10-20 19:18 UTC (permalink / raw) To: linux-arm-kernel On Wed, Oct 20, 2010 at 09:59:33AM -0700, Guenter Roeck wrote: > On Wed, Oct 20, 2010 at 12:37:26PM -0400, Simon Guinot wrote: > > This patch adds hwmon support for the GPIO connected fan. > > > > Platform specific informations as GPIO pinout and speed conversion array > > (rpm from/to GPIO value) are passed to the driver via platform_data. > > > > Signed-off-by: Simon Guinot <sguinot@lacie.com> > > MicroSomething makes it really easy to be annoyed with them (folks, are you reading this ?). > Mailer screwed up the patch again. We are making some noise to get this fixed, > but that will take a while - the affected mail servers are hiding somewhere in Europe. > > Unfortunately, none of the hwmon and linux-arm archives I am aware of makes > patches available on the web (like patchwork does), so I can not get it from there > either. Can you resend it to me as attachment, or give me a pointer where I can > pick it up ? http://lists.arm.linux.org.uk/lurker/message/20101020.163726.f0929cc0.en.html -> 'Message as email' and you get an email-formatted chunk of text. Lurker really is very cool in this regard, it beats the crappy pipermail that's built into mailman in many ways. More people should use it. ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2] hwmon: add generic GPIO fan driver 2010-10-20 19:18 ` Russell King - ARM Linux @ 2010-10-20 20:06 ` Guenter Roeck 0 siblings, 0 replies; 16+ messages in thread From: Guenter Roeck @ 2010-10-20 20:06 UTC (permalink / raw) To: linux-arm-kernel On Wed, Oct 20, 2010 at 03:18:03PM -0400, Russell King - ARM Linux wrote: > On Wed, Oct 20, 2010 at 09:59:33AM -0700, Guenter Roeck wrote: > > On Wed, Oct 20, 2010 at 12:37:26PM -0400, Simon Guinot wrote: > > > This patch adds hwmon support for the GPIO connected fan. > > > > > > Platform specific informations as GPIO pinout and speed conversion array > > > (rpm from/to GPIO value) are passed to the driver via platform_data. > > > > > > Signed-off-by: Simon Guinot <sguinot@lacie.com> > > > > MicroSomething makes it really easy to be annoyed with them (folks, are you reading this ?). > > Mailer screwed up the patch again. We are making some noise to get this fixed, > > but that will take a while - the affected mail servers are hiding somewhere in Europe. > > > > Unfortunately, none of the hwmon and linux-arm archives I am aware of makes > > patches available on the web (like patchwork does), so I can not get it from there > > either. Can you resend it to me as attachment, or give me a pointer where I can > > pick it up ? > > http://lists.arm.linux.org.uk/lurker/message/20101020.163726.f0929cc0.en.html > > -> 'Message as email' > > and you get an email-formatted chunk of text. Lurker really is very cool > in this regard, it beats the crappy pipermail that's built into mailman > in many ways. More people should use it. Excellent. Thanks! Guenter ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2] hwmon: add generic GPIO fan driver 2010-10-20 16:37 ` Simon Guinot 2010-10-20 16:59 ` Guenter Roeck @ 2010-10-21 6:41 ` Guenter Roeck 2010-10-21 14:06 ` Simon Guinot 1 sibling, 1 reply; 16+ messages in thread From: Guenter Roeck @ 2010-10-21 6:41 UTC (permalink / raw) To: linux-arm-kernel Hi Simon, looking much better. Bunch of additional comments inline. On Wed, Oct 20, 2010 at 12:37:26PM -0400, Simon Guinot wrote: > This patch adds hwmon support for the GPIO connected fan. > > Platform specific informations as GPIO pinout and speed conversion array > (rpm from/to GPIO value) are passed to the driver via platform_data. > > Signed-off-by: Simon Guinot <sguinot@lacie.com> > --- > drivers/hwmon/Kconfig | 9 + > drivers/hwmon/Makefile | 1 + > drivers/hwmon/gpio-fan.c | 557 ++++++++++++++++++++++++++++++++++++++++++++++ > include/linux/gpio-fan.h | 36 +++ > 4 files changed, 603 insertions(+), 0 deletions(-) > create mode 100644 drivers/hwmon/gpio-fan.c > create mode 100644 include/linux/gpio-fan.h > > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig > index 4d4d09b..af318a1 100644 > --- a/drivers/hwmon/Kconfig > +++ b/drivers/hwmon/Kconfig > @@ -368,6 +368,15 @@ config SENSORS_FSCHMD > This driver can also be built as a module. If so, the module > will be called fschmd. > > +config SENSORS_GPIO_FAN > + tristate "GPIO fan" > + depends on GENERIC_GPIO > + help > + If you say yes here you get support for the GPIO connected fan. > + "for GPIO connected fans.". Can be more than one. > + This driver can also be built as a module. If so, the module > + will be called gpio-fan. > + > config SENSORS_G760A > tristate "GMT G760A" > depends on I2C > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile > index e3c2484..5df7e4a 100644 > --- a/drivers/hwmon/Makefile > +++ b/drivers/hwmon/Makefile > @@ -48,6 +48,7 @@ obj-$(CONFIG_SENSORS_F71805F) += f71805f.o > obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o > obj-$(CONFIG_SENSORS_F75375S) += f75375s.o > obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o > +obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o "gp" is alphabetically after "g7". Please move. > obj-$(CONFIG_SENSORS_G760A) += g760a.o > obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o > obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o > diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c > new file mode 100644 > index 0000000..e3131a6 > --- /dev/null > +++ b/drivers/hwmon/gpio-fan.c > @@ -0,0 +1,557 @@ > +/* > + * gpio-fan.c - Hwmon driver for GPIO connected fan. > + * > + * Copyright (C) 2010 LaCie > + * > + * Author: Simon Guinot <sguinot@lacie.com> > + * > + * 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. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + */ > + > +#include <linux/module.h> > +#include <linux/init.h> > +#include <linux/slab.h> > +#include <linux/interrupt.h> > +#include <linux/irq.h> > +#include <linux/platform_device.h> > +#include <linux/err.h> > +#include <linux/mutex.h> > +#include <linux/hwmon.h> > +#include <linux/gpio.h> > +#include <linux/gpio-fan.h> > + > +struct gpio_fan_data { > + struct platform_device *pdev; > + struct device *hwmon_dev; > + struct mutex lock; /* lock GPIOs operations. */ > + int num_ctrl; > + unsigned *ctrl; > + int num_speed; > + struct gpio_fan_speed *speed; > + int speed_index; > +#ifdef CONFIG_PM > + int resume_speed; > +#endif > + int pwm_enable; I would prefer a bool here. After all, that is what it is. > + struct gpio_fan_alarm *alarm; > + struct work_struct alarm_work; > +}; > + > +/* > + * Alarm GPIO. > + */ > + > +static void fan_alarm_notify(struct work_struct *ws) > +{ > + struct gpio_fan_data *fan_data = > + container_of(ws, struct gpio_fan_data, alarm_work); > + > + sysfs_notify(&fan_data->pdev->dev.kobj, NULL, "fan1_alarm"); > + kobject_uevent(&fan_data->pdev->dev.kobj, KOBJ_CHANGE); > +} > + > +static irqreturn_t fan_alarm_irq_handler(int irq, void *dev_id) > +{ > + struct gpio_fan_data *fan_data = dev_id; > + > + schedule_work(&fan_data->alarm_work); > + > + return IRQ_NONE; > +} > + > +static ssize_t show_fan_alarm(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > + struct gpio_fan_alarm *alarm = fan_data->alarm; > + int value = gpio_get_value(alarm->gpio); > + > + if (alarm->active_low) > + value = !value; > + > + return sprintf(buf, "%d\n", value); > +} > + > +static DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL); > + > +static int fan_alarm_init(struct gpio_fan_data *fan_data, > + struct gpio_fan_alarm *alarm) > +{ > + int err; > + int alarm_irq; > + struct platform_device *pdev = fan_data->pdev; > + > + fan_data->alarm = alarm; > + > + err = gpio_request(alarm->gpio, "GPIO fan alarm"); > + if (err) > + return err; > + > + err = gpio_direction_input(alarm->gpio); > + if (err) > + goto err_free_gpio; > + > + err = device_create_file(&pdev->dev, &dev_attr_fan1_alarm); > + if (err) > + goto err_free_gpio; > + > + alarm_irq = gpio_to_irq(alarm->gpio); > + if (alarm_irq < 0) > + return 0; > + This confused me for a bit, so it may confuse others. Please add a note indicating that the above is not an error condition. > + INIT_WORK(&fan_data->alarm_work, fan_alarm_notify); > + set_irq_type(alarm_irq, IRQ_TYPE_EDGE_BOTH); > + err = request_irq(alarm_irq, fan_alarm_irq_handler, IRQF_SHARED, > + "GPIO fan alarm", fan_data); > + if (err) > + goto err_free_sysfs; > + > + return 0; > + > +err_free_sysfs: > + device_remove_file(&pdev->dev, &dev_attr_fan1_alarm); > +err_free_gpio: > + gpio_free(alarm->gpio); > + > + return err; > +} > + > +static void fan_alarm_free(struct gpio_fan_data *fan_data) > +{ > + struct platform_device *pdev = fan_data->pdev; > + int alarm_irq = gpio_to_irq(fan_data->alarm->gpio); > + > + if (alarm_irq >= 0) > + free_irq(alarm_irq, fan_data); > + device_remove_file(&pdev->dev, &dev_attr_fan1_alarm); > + gpio_free(fan_data->alarm->gpio); > +} > + > +/* > + * Control GPIOs. > + */ > + Please add a note indicating that the function must be called with the lock held except during initialization. Such as "Must be called with data->update_lock held, except during initialization". > +static void __set_fan_ctrl(struct gpio_fan_data *fan_data, int ctrl_val) > +{ > + int i; > + > + for (i = 0; i < fan_data->num_ctrl; i++) > + gpio_set_value(fan_data->ctrl[i], (ctrl_val >> i) & 1); > +} > + > +static int __get_fan_ctrl(struct gpio_fan_data *fan_data) > +{ > + int i; > + int ctrl_val = 0; > + > + for (i = 0; i < fan_data->num_ctrl; i++) { > + int value; > + > + value = gpio_get_value(fan_data->ctrl[i]); > + ctrl_val |= (value << i); > + } > + return ctrl_val; > +} > + "Must be called with data->update_lock held, except during initialization". > +static void set_fan_speed(struct gpio_fan_data *fan_data, int speed_index) > +{ > + if (fan_data->speed_index == speed_index) > + return; > + > + __set_fan_ctrl(fan_data, fan_data->speed[speed_index].ctrl_val); > + fan_data->speed_index = speed_index; > +} > + > +static int get_fan_speed(struct gpio_fan_data *fan_data) get_fan_speed_index() might be a better name. The function doesn't really return the speed, but an index to the speed table. > +{ > + int ctrl_val = __get_fan_ctrl(fan_data); > + int i; > + > + for (i = 0; i < fan_data->num_speed; i++) > + if (fan_data->speed[i].ctrl_val == ctrl_val) > + return i; > + > + dev_warn(&fan_data->pdev->dev, > + "missing speed array entry for GPIO value %d\n", ctrl_val); > + 0x%x might be better here. > + return -EINVAL; > +} > + > +static int rpm_to_speed(struct gpio_fan_data *fan_data, int rpm) rpm_to_speed_index would avoid confusion. > +{ > + struct gpio_fan_speed *speed = fan_data->speed; > + int speed_index = fan_data->num_speed - 1; /* Maximum speed */ You don't really need speed_index here. > + int i; > + > + for (i = 0; i < fan_data->num_speed; i++) > + if (speed[i].rpm >= rpm) { Would it make more sense to return the closest rpm instead ? For example, if one sets a value of 1900, and the closest value in the table is 1800, it might make more sense to set the speed to 1800 and not to, say, 3000, if that is the next value. No strong opinion, though. One might as well argue that it is safer to run at the higher speed. > + speed_index = i; > + break; return i; > + } > + return speed_index; return fan_data->num_speed - 1; > +} > + > +static ssize_t show_pwm(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > + u8 pwm = DIV_ROUND_CLOSEST(fan_data->speed_index * 255, > + fan_data->num_speed - 1); > + > + return sprintf(buf, "%d\n", pwm); > +} > + > +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > + unsigned long pwm; > + int speed_index; > + int ret = count; > + > + if (strict_strtoul(buf, 10, &pwm) || pwm > 255) > + return -EINVAL; > + > + mutex_lock(&fan_data->lock); > + > + if (fan_data->pwm_enable != 1) { if (!fan_data->pwm_enable) { > + ret = -EPERM; > + goto exit_unlock; > + } > + > + speed_index = DIV_ROUND_UP(pwm * (fan_data->num_speed - 1), 255); The combination of DIV_ROUND_UP() and DIV_ROUND_CLOSEST() causes inconsistency. Assume num_speed = 8, pwm is set to 128. set: 128 * (8 - 1) / 255 = 3.513 ==> 4 get: 4 * 255 / (8 - 1) = 145.7 ==> 146 set: 146 * (8 - 1) / 255 = 4.007 ==> 5 get: 5 * 255 / (8 - 1) = 182.142 ==> 182 set: 182 * (8 - 1) / 255 = 4.996 ==> 5 Unless there is a really good reason to use DIV_ROUND_UP(), you might want to use DIV_ROUND_CLOSEST() instead. > + set_fan_speed(fan_data, speed_index); > + > +exit_unlock: > + mutex_unlock(&fan_data->lock); > + > + return ret; > +} > + > +static ssize_t show_pwm_enable(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > + > + return sprintf(buf, "%d\n", fan_data->pwm_enable); > +} > + > +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > + unsigned long val; > + > + if (strict_strtoul(buf, 10, &val) || val > 1) > + return -EINVAL; > + > + if (fan_data->pwm_enable == val) > + return count; > + > + mutex_lock(&fan_data->lock); > + > + fan_data->pwm_enable = val; > + > + /* Disable manual control mode: set fan at full speed. */ > + if (val == 0) > + set_fan_speed(fan_data, fan_data->num_speed - 1); > + > + mutex_unlock(&fan_data->lock); > + > + return count; > +} > + > +static ssize_t show_pwm_mode(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + return sprintf(buf, "0\n"); > +} > + > +static ssize_t show_rpm_min(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > + > + return sprintf(buf, "%d\n", fan_data->speed[0].rpm); > +} > + > +static ssize_t show_rpm_max(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > + > + return sprintf(buf, "%d\n", > + fan_data->speed[fan_data->num_speed - 1].rpm); > +} > + > +static ssize_t show_rpm(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > + > + return sprintf(buf, "%d\n", fan_data->speed[fan_data->speed_index].rpm); > +} > + > +static ssize_t set_rpm(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > + unsigned long rpm; > + int ret = count; > + > + if (strict_strtoul(buf, 10, &rpm)) > + return -EINVAL; > + > + mutex_lock(&fan_data->lock); > + > + if (fan_data->pwm_enable != 1) { if (!fan_data->pwm_enable) { > + ret = -EPERM; > + goto exit_unlock; > + } > + > + set_fan_speed(fan_data, rpm_to_speed(fan_data, rpm)); > + > +exit_unlock: > + mutex_unlock(&fan_data->lock); > + > + return ret; > +} > + > +static DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm); > +static DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, > + show_pwm_enable, set_pwm_enable); > +static DEVICE_ATTR(pwm1_mode, S_IRUGO, show_pwm_mode, NULL); > +static DEVICE_ATTR(fan1_min, S_IRUGO, show_rpm_min, NULL); > +static DEVICE_ATTR(fan1_max, S_IRUGO, show_rpm_max, NULL); > +static DEVICE_ATTR(fan1_input, S_IRUGO, show_rpm, NULL); > +static DEVICE_ATTR(fan1_target, S_IRUGO | S_IWUSR, show_rpm, set_rpm); > + > +static struct attribute *gpio_fan_ctrl_attributes[] = { > + &dev_attr_pwm1.attr, > + &dev_attr_pwm1_enable.attr, > + &dev_attr_pwm1_mode.attr, > + &dev_attr_fan1_input.attr, > + &dev_attr_fan1_target.attr, > + &dev_attr_fan1_min.attr, > + &dev_attr_fan1_max.attr, > + NULL > +}; > + > +static const struct attribute_group gpio_fan_ctrl_group = { > + .attrs = gpio_fan_ctrl_attributes, > +}; > + > +static int fan_ctrl_init(struct gpio_fan_data *fan_data, > + struct gpio_fan_platform_data *pdata) > +{ > + struct platform_device *pdev = fan_data->pdev; > + int num_ctrl = pdata->num_ctrl; > + unsigned *ctrl = pdata->ctrl; > + int i, err; > + > + for (i = 0; i < num_ctrl; i++) { > + err = gpio_request(ctrl[i], "GPIO fan control"); > + if (err) > + goto err_free_gpio; > + > + err = gpio_direction_output(ctrl[i], gpio_get_value(ctrl[i])); > + if (err) { > + gpio_free(ctrl[i]); > + goto err_free_gpio; > + } > + } > + > + err = sysfs_create_group(&pdev->dev.kobj, &gpio_fan_ctrl_group); > + if (err) > + goto err_free_gpio; > + > + fan_data->num_ctrl = num_ctrl; > + fan_data->ctrl = ctrl; > + fan_data->num_speed = pdata->num_speed; > + fan_data->speed = pdata->speed; > + fan_data->pwm_enable = 1; /* Enable manual fan speed control. */ fan_data->pwm_enable = true; > + fan_data->speed_index = get_fan_speed(fan_data); > + if (fan_data->speed_index < 0) { > + /* Set fan speed to a known value. */ > + set_fan_speed(fan_data, fan_data->num_speed / 2); > + dev_info(&pdev->dev, "set fan speed to %d rpm\n", > + fan_data->speed[fan_data->speed_index].rpm); I think a safer approach would be to return -ENODEV in this case. Better not to try any fan control at all if the GPIO bits report an unsupported value. It indicates that something is wrong. > + } > + > + return 0; > + > +err_free_gpio: > + for (i = i - 1; i >= 0; i--) > + gpio_free(ctrl[i]); > + > + return err; > +} > + > +static void fan_ctrl_free(struct gpio_fan_data *fan_data) > +{ > + struct platform_device *pdev = fan_data->pdev; > + int i; > + > + sysfs_remove_group(&pdev->dev.kobj, &gpio_fan_ctrl_group); > + for (i = 0; i < fan_data->num_ctrl; i++) > + gpio_free(fan_data->ctrl[i]); > +} > + > +/* > + * Platform driver. > + */ > + > +static ssize_t show_name(struct device *dev, > + struct device_attribute *attr, char *buf) > +{ > + return sprintf(buf, "gpio-fan\n"); > +} > + > +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); > + > +static int __devinit gpio_fan_probe(struct platform_device *pdev) > +{ > + int err = 0; > + struct gpio_fan_data *fan_data; > + struct gpio_fan_platform_data *pdata = pdev->dev.platform_data; > + > + if (!pdata) > + return -EINVAL; > + > + fan_data = kzalloc(sizeof(struct gpio_fan_data), GFP_KERNEL); > + if (!fan_data) > + return -ENOMEM; > + > + fan_data->pdev = pdev; > + platform_set_drvdata(pdev, fan_data); > + mutex_init(&fan_data->lock); > + > + /* Configure alarm GPIO if available. */ > + if (pdata->alarm) { > + err = fan_alarm_init(fan_data, pdata->alarm); > + if (err) > + goto err_free_data; > + } > + > + /* Configure control GPIOs if available. */ > + if (pdata->ctrl && pdata->num_ctrl) { You might want to check for pdata->num_ctrl > 0 here. It could be negative, which would be bad. > + if (!pdata->num_speed || !pdata->speed) { num_speed must be > 1. Otherwise there is a risk of division by zero if it is == 1. > + err = -EINVAL; > + goto err_free_alarm; > + } > + err = fan_ctrl_init(fan_data, pdata); > + if (err) > + goto err_free_alarm; > + } > + > + err = device_create_file(&pdev->dev, &dev_attr_name); > + if (err) > + goto err_free_ctrl; > + > + /* Make this driver part of hwmon class. */ > + fan_data->hwmon_dev = hwmon_device_register(&pdev->dev); > + if (IS_ERR(fan_data->hwmon_dev)) { > + err = PTR_ERR(fan_data->hwmon_dev); > + goto err_remove_name; > + } > + > + dev_info(&pdev->dev, "GPIO fan initialized\n"); > + > + return 0; > + > +err_remove_name: > + device_remove_file(&pdev->dev, &dev_attr_name); > +err_free_ctrl: > + if (fan_data->ctrl) > + fan_ctrl_free(fan_data); > +err_free_alarm: > + if (fan_data->alarm) > + fan_alarm_free(fan_data); > +err_free_data: > + platform_set_drvdata(pdev, NULL); > + kfree(fan_data); > + > + return err; > +} > + > +static int __devexit gpio_fan_remove(struct platform_device *pdev) > +{ > + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); > + > + hwmon_device_unregister(fan_data->hwmon_dev); > + device_remove_file(&pdev->dev, &dev_attr_name); > + if (fan_data->alarm) > + fan_alarm_free(fan_data); > + if (fan_data->ctrl) > + fan_ctrl_free(fan_data); > + kfree(fan_data); > + > + return 0; > +} > + > +#ifdef CONFIG_PM > +static int gpio_fan_suspend(struct platform_device *pdev, pm_message_t state) > +{ > + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); > + > + if (fan_data->ctrl) { > + fan_data->resume_speed = fan_data->speed_index; > + set_fan_speed(fan_data, 0); > + } > + > + return 0; > +} > + > +static int gpio_fan_resume(struct platform_device *pdev) > +{ > + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); > + > + if (fan_data->ctrl) > + set_fan_speed(fan_data, fan_data->resume_speed); > + > + return 0; > +} > +#else > +#define gpio_fan_suspend NULL > +#define gpio_fan_resume NULL > +#endif > + > +static struct platform_driver gpio_fan_driver = { > + .probe = gpio_fan_probe, > + .remove = __devexit_p(gpio_fan_remove), > + .suspend = gpio_fan_suspend, > + .resume = gpio_fan_resume, > + .driver = { > + .name = "gpio-fan", > + }, > +}; > + > +static int __init gpio_fan_init(void) > +{ > + return platform_driver_register(&gpio_fan_driver); > +} > + > +static void __exit gpio_fan_exit(void) > +{ > + platform_driver_unregister(&gpio_fan_driver); > +} > + > +module_init(gpio_fan_init); > +module_exit(gpio_fan_exit); > + > +MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); > +MODULE_DESCRIPTION("GPIO FAN driver"); > +MODULE_LICENSE("GPL"); > +MODULE_ALIAS("platform:gpio-fan"); > diff --git a/include/linux/gpio-fan.h b/include/linux/gpio-fan.h > new file mode 100644 > index 0000000..0966591 > --- /dev/null > +++ b/include/linux/gpio-fan.h > @@ -0,0 +1,36 @@ > +/* > + * include/linux/gpio-fan.h > + * > + * Platform data structure for GPIO fan driver > + * > + * This file is licensed under the terms of the GNU General Public > + * License version 2. This program is licensed "as is" without any > + * warranty of any kind, whether express or implied. > + */ > + > +#ifndef __LINUX_GPIO_FAN_H > +#define __LINUX_GPIO_FAN_H > + > +struct gpio_fan_alarm { > + unsigned gpio; > + unsigned active_low; > +}; > + > +struct gpio_fan_speed { > + int rpm; > + int ctrl_val; > +}; > + > +struct gpio_fan_platform_data { > + int num_ctrl; > + unsigned *ctrl; /* fan control GPIOs. */ > + struct gpio_fan_alarm *alarm; /* fan alarm GPIO. */ > + /* > + * Speed conversion array: rpm from/to GPIO bit field. > + * This array _must_ be sorted in ascending rpm order. > + */ > + int num_speed; > + struct gpio_fan_speed *speed; > +}; > + > +#endif /* __LINUX_GPIO_FAN_H */ > -- > 1.6.3.1 > ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2] hwmon: add generic GPIO fan driver 2010-10-21 6:41 ` Guenter Roeck @ 2010-10-21 14:06 ` Simon Guinot 2010-10-21 14:43 ` Guenter Roeck 0 siblings, 1 reply; 16+ messages in thread From: Simon Guinot @ 2010-10-21 14:06 UTC (permalink / raw) To: linux-arm-kernel Hi Guenter, Thanks for your comments. On Wed, Oct 20, 2010 at 11:41:45PM -0700, Guenter Roeck wrote: > Hi Simon, > > looking much better. Bunch of additional comments inline. > > On Wed, Oct 20, 2010 at 12:37:26PM -0400, Simon Guinot wrote: > > This patch adds hwmon support for the GPIO connected fan. > > > > Platform specific informations as GPIO pinout and speed conversion array > > (rpm from/to GPIO value) are passed to the driver via platform_data. > > > > Signed-off-by: Simon Guinot <sguinot@lacie.com> > > --- > > drivers/hwmon/Kconfig | 9 + > > drivers/hwmon/Makefile | 1 + > > drivers/hwmon/gpio-fan.c | 557 ++++++++++++++++++++++++++++++++++++++++++++++ > > include/linux/gpio-fan.h | 36 +++ > > 4 files changed, 603 insertions(+), 0 deletions(-) > > create mode 100644 drivers/hwmon/gpio-fan.c > > create mode 100644 include/linux/gpio-fan.h > > > > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig > > index 4d4d09b..af318a1 100644 > > --- a/drivers/hwmon/Kconfig > > +++ b/drivers/hwmon/Kconfig > > @@ -368,6 +368,15 @@ config SENSORS_FSCHMD > > This driver can also be built as a module. If so, the module > > will be called fschmd. > > > > +config SENSORS_GPIO_FAN > > + tristate "GPIO fan" > > + depends on GENERIC_GPIO > > + help > > + If you say yes here you get support for the GPIO connected fan. > > + > > "for GPIO connected fans.". Can be more than one. My bad English... > > > + This driver can also be built as a module. If so, the module > > + will be called gpio-fan. > > + > > config SENSORS_G760A > > tristate "GMT G760A" > > depends on I2C > > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile > > index e3c2484..5df7e4a 100644 > > --- a/drivers/hwmon/Makefile > > +++ b/drivers/hwmon/Makefile > > @@ -48,6 +48,7 @@ obj-$(CONFIG_SENSORS_F71805F) += f71805f.o > > obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o > > obj-$(CONFIG_SENSORS_F75375S) += f75375s.o > > obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o > > +obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o > > "gp" is alphabetically after "g7". Please move. It was the original place... Ok, I have been confused with the Kconfig "sequence" order: put SENSORS_GPIO_FAN before SENSORS_G760A seems wrong to me. Why we don't have to respect the alphabetical order for Kconfig ? > > > obj-$(CONFIG_SENSORS_G760A) += g760a.o > > obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o > > obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o > > diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c > > new file mode 100644 > > index 0000000..e3131a6 > > --- /dev/null > > +++ b/drivers/hwmon/gpio-fan.c > > @@ -0,0 +1,557 @@ > > +/* > > + * gpio-fan.c - Hwmon driver for GPIO connected fan. > > + * > > + * Copyright (C) 2010 LaCie > > + * > > + * Author: Simon Guinot <sguinot@lacie.com> > > + * > > + * 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. > > + * > > + * This program is distributed in the hope that it will be useful, > > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > > + * GNU General Public License for more details. > > + * > > + * You should have received a copy of the GNU General Public License > > + * along with this program; if not, write to the Free Software > > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > > + */ > > + > > +#include <linux/module.h> > > +#include <linux/init.h> > > +#include <linux/slab.h> > > +#include <linux/interrupt.h> > > +#include <linux/irq.h> > > +#include <linux/platform_device.h> > > +#include <linux/err.h> > > +#include <linux/mutex.h> > > +#include <linux/hwmon.h> > > +#include <linux/gpio.h> > > +#include <linux/gpio-fan.h> > > + > > +struct gpio_fan_data { > > + struct platform_device *pdev; > > + struct device *hwmon_dev; > > + struct mutex lock; /* lock GPIOs operations. */ > > + int num_ctrl; > > + unsigned *ctrl; > > + int num_speed; > > + struct gpio_fan_speed *speed; > > + int speed_index; > > +#ifdef CONFIG_PM > > + int resume_speed; > > +#endif > > + int pwm_enable; > > I would prefer a bool here. After all, that is what it is. As automatic fan speed control is not supported, it make sense. > > > + struct gpio_fan_alarm *alarm; > > + struct work_struct alarm_work; > > +}; > > + > > +/* > > + * Alarm GPIO. > > + */ > > + > > +static void fan_alarm_notify(struct work_struct *ws) > > +{ > > + struct gpio_fan_data *fan_data = > > + container_of(ws, struct gpio_fan_data, alarm_work); > > + > > + sysfs_notify(&fan_data->pdev->dev.kobj, NULL, "fan1_alarm"); > > + kobject_uevent(&fan_data->pdev->dev.kobj, KOBJ_CHANGE); > > +} > > + > > +static irqreturn_t fan_alarm_irq_handler(int irq, void *dev_id) > > +{ > > + struct gpio_fan_data *fan_data = dev_id; > > + > > + schedule_work(&fan_data->alarm_work); > > + > > + return IRQ_NONE; > > +} > > + > > +static ssize_t show_fan_alarm(struct device *dev, > > + struct device_attribute *attr, char *buf) > > +{ > > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > > + struct gpio_fan_alarm *alarm = fan_data->alarm; > > + int value = gpio_get_value(alarm->gpio); > > + > > + if (alarm->active_low) > > + value = !value; > > + > > + return sprintf(buf, "%d\n", value); > > +} > > + > > +static DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL); > > + > > +static int fan_alarm_init(struct gpio_fan_data *fan_data, > > + struct gpio_fan_alarm *alarm) > > +{ > > + int err; > > + int alarm_irq; > > + struct platform_device *pdev = fan_data->pdev; > > + > > + fan_data->alarm = alarm; > > + > > + err = gpio_request(alarm->gpio, "GPIO fan alarm"); > > + if (err) > > + return err; > > + > > + err = gpio_direction_input(alarm->gpio); > > + if (err) > > + goto err_free_gpio; > > + > > + err = device_create_file(&pdev->dev, &dev_attr_fan1_alarm); > > + if (err) > > + goto err_free_gpio; > > + > > + alarm_irq = gpio_to_irq(alarm->gpio); > > + if (alarm_irq < 0) > > + return 0; > > + > This confused me for a bit, so it may confuse others. > Please add a note indicating that the above is not an error condition. Ok. > > > + INIT_WORK(&fan_data->alarm_work, fan_alarm_notify); > > + set_irq_type(alarm_irq, IRQ_TYPE_EDGE_BOTH); > > + err = request_irq(alarm_irq, fan_alarm_irq_handler, IRQF_SHARED, > > + "GPIO fan alarm", fan_data); > > + if (err) > > + goto err_free_sysfs; > > + > > + return 0; > > + > > +err_free_sysfs: > > + device_remove_file(&pdev->dev, &dev_attr_fan1_alarm); > > +err_free_gpio: > > + gpio_free(alarm->gpio); > > + > > + return err; > > +} > > + > > +static void fan_alarm_free(struct gpio_fan_data *fan_data) > > +{ > > + struct platform_device *pdev = fan_data->pdev; > > + int alarm_irq = gpio_to_irq(fan_data->alarm->gpio); > > + > > + if (alarm_irq >= 0) > > + free_irq(alarm_irq, fan_data); > > + device_remove_file(&pdev->dev, &dev_attr_fan1_alarm); > > + gpio_free(fan_data->alarm->gpio); > > +} > > + > > +/* > > + * Control GPIOs. > > + */ > > + > Please add a note indicating that the function must be called with the lock held > except during initialization. > Such as "Must be called with data->update_lock held, except during initialization". Ok. > > > +static void __set_fan_ctrl(struct gpio_fan_data *fan_data, int ctrl_val) > > +{ > > + int i; > > + > > + for (i = 0; i < fan_data->num_ctrl; i++) > > + gpio_set_value(fan_data->ctrl[i], (ctrl_val >> i) & 1); > > +} > > + > > +static int __get_fan_ctrl(struct gpio_fan_data *fan_data) > > +{ > > + int i; > > + int ctrl_val = 0; > > + > > + for (i = 0; i < fan_data->num_ctrl; i++) { > > + int value; > > + > > + value = gpio_get_value(fan_data->ctrl[i]); > > + ctrl_val |= (value << i); > > + } > > + return ctrl_val; > > +} > > + > > "Must be called with data->update_lock held, except during initialization". Ok. > > > +static void set_fan_speed(struct gpio_fan_data *fan_data, int speed_index) > > +{ > > + if (fan_data->speed_index == speed_index) > > + return; > > + > > + __set_fan_ctrl(fan_data, fan_data->speed[speed_index].ctrl_val); > > + fan_data->speed_index = speed_index; > > +} > > + > > +static int get_fan_speed(struct gpio_fan_data *fan_data) > > get_fan_speed_index() might be a better name. The function doesn't > really return the speed, but an index to the speed table. I am not sure how useful is the information provided by the '_index' suffix. Add '_index' here force to add '_index' everywhere and make long function names... Anyway, I will change this. > > > +{ > > + int ctrl_val = __get_fan_ctrl(fan_data); > > + int i; > > + > > + for (i = 0; i < fan_data->num_speed; i++) > > + if (fan_data->speed[i].ctrl_val == ctrl_val) > > + return i; > > + > > + dev_warn(&fan_data->pdev->dev, > > + "missing speed array entry for GPIO value %d\n", ctrl_val); > > + > 0x%x might be better here. Yes. > > > + return -EINVAL; > > +} > > + > > +static int rpm_to_speed(struct gpio_fan_data *fan_data, int rpm) > > rpm_to_speed_index would avoid confusion. > > > +{ > > + struct gpio_fan_speed *speed = fan_data->speed; > > + int speed_index = fan_data->num_speed - 1; /* Maximum speed */ > > You don't really need speed_index here. > > > + int i; > > + > > + for (i = 0; i < fan_data->num_speed; i++) > > + if (speed[i].rpm >= rpm) { > > Would it make more sense to return the closest rpm instead ? > > For example, if one sets a value of 1900, and the closest value > in the table is 1800, it might make more sense to set the speed > to 1800 and not to, say, 3000, if that is the next value. On the other hand, you can set rpm to 500 and the closest value could be 0 and the fan stop (or don't start). That's a disturbing experience. If you agree, I prefer to set the upper speed. > > No strong opinion, though. One might as well argue that it is safer > to run at the higher speed. > Some fans have problems with the low speeds and needs a little boost to start up. > > + speed_index = i; > > + break; > > return i; > > > + } > > + return speed_index; > > return fan_data->num_speed - 1; Yes. > > > +} > > + > > +static ssize_t show_pwm(struct device *dev, > > + struct device_attribute *attr, char *buf) > > +{ > > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > > + u8 pwm = DIV_ROUND_CLOSEST(fan_data->speed_index * 255, > > + fan_data->num_speed - 1); > > + > > + return sprintf(buf, "%d\n", pwm); > > +} > > + > > +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, > > + const char *buf, size_t count) > > +{ > > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > > + unsigned long pwm; > > + int speed_index; > > + int ret = count; > > + > > + if (strict_strtoul(buf, 10, &pwm) || pwm > 255) > > + return -EINVAL; > > + > > + mutex_lock(&fan_data->lock); > > + > > + if (fan_data->pwm_enable != 1) { > > if (!fan_data->pwm_enable) { > > > + ret = -EPERM; > > + goto exit_unlock; > > + } > > + > > + speed_index = DIV_ROUND_UP(pwm * (fan_data->num_speed - 1), 255); > > The combination of DIV_ROUND_UP() and DIV_ROUND_CLOSEST() causes inconsistency. > > Assume num_speed = 8, pwm is set to 128. > > set: 128 * (8 - 1) / 255 = 3.513 ==> 4 > get: 4 * 255 / (8 - 1) = 145.7 ==> 146 > set: 146 * (8 - 1) / 255 = 4.007 ==> 5 > get: 5 * 255 / (8 - 1) = 182.142 ==> 182 > set: 182 * (8 - 1) / 255 = 4.996 ==> 5 > > Unless there is a really good reason to use DIV_ROUND_UP(), you might > want to use DIV_ROUND_CLOSEST() instead. This choice is coherent with the rpm interface one and the reason is the same: start the fan even with a low value. In your example, 36 is first speed threshold. > > > + set_fan_speed(fan_data, speed_index); > > + > > +exit_unlock: > > + mutex_unlock(&fan_data->lock); > > + > > + return ret; > > +} > > + > > +static ssize_t show_pwm_enable(struct device *dev, > > + struct device_attribute *attr, char *buf) > > +{ > > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > > + > > + return sprintf(buf, "%d\n", fan_data->pwm_enable); > > +} > > + > > +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr, > > + const char *buf, size_t count) > > +{ > > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > > + unsigned long val; > > + > > + if (strict_strtoul(buf, 10, &val) || val > 1) > > + return -EINVAL; > > + > > + if (fan_data->pwm_enable == val) > > + return count; > > + > > + mutex_lock(&fan_data->lock); > > + > > + fan_data->pwm_enable = val; > > + > > + /* Disable manual control mode: set fan at full speed. */ > > + if (val == 0) > > + set_fan_speed(fan_data, fan_data->num_speed - 1); > > + > > + mutex_unlock(&fan_data->lock); > > + > > + return count; > > +} > > + > > +static ssize_t show_pwm_mode(struct device *dev, > > + struct device_attribute *attr, char *buf) > > +{ > > + return sprintf(buf, "0\n"); > > +} > > + > > +static ssize_t show_rpm_min(struct device *dev, > > + struct device_attribute *attr, char *buf) > > +{ > > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > > + > > + return sprintf(buf, "%d\n", fan_data->speed[0].rpm); > > +} > > + > > +static ssize_t show_rpm_max(struct device *dev, > > + struct device_attribute *attr, char *buf) > > +{ > > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > > + > > + return sprintf(buf, "%d\n", > > + fan_data->speed[fan_data->num_speed - 1].rpm); > > +} > > + > > +static ssize_t show_rpm(struct device *dev, > > + struct device_attribute *attr, char *buf) > > +{ > > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > > + > > + return sprintf(buf, "%d\n", fan_data->speed[fan_data->speed_index].rpm); > > +} > > + > > +static ssize_t set_rpm(struct device *dev, struct device_attribute *attr, > > + const char *buf, size_t count) > > +{ > > + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); > > + unsigned long rpm; > > + int ret = count; > > + > > + if (strict_strtoul(buf, 10, &rpm)) > > + return -EINVAL; > > + > > + mutex_lock(&fan_data->lock); > > + > > + if (fan_data->pwm_enable != 1) { > > if (!fan_data->pwm_enable) { Ok. > > > + ret = -EPERM; > > + goto exit_unlock; > > + } > > + > > + set_fan_speed(fan_data, rpm_to_speed(fan_data, rpm)); > > + > > +exit_unlock: > > + mutex_unlock(&fan_data->lock); > > + > > + return ret; > > +} > > + > > +static DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm); > > +static DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, > > + show_pwm_enable, set_pwm_enable); > > +static DEVICE_ATTR(pwm1_mode, S_IRUGO, show_pwm_mode, NULL); > > +static DEVICE_ATTR(fan1_min, S_IRUGO, show_rpm_min, NULL); > > +static DEVICE_ATTR(fan1_max, S_IRUGO, show_rpm_max, NULL); > > +static DEVICE_ATTR(fan1_input, S_IRUGO, show_rpm, NULL); > > +static DEVICE_ATTR(fan1_target, S_IRUGO | S_IWUSR, show_rpm, set_rpm); > > + > > +static struct attribute *gpio_fan_ctrl_attributes[] = { > > + &dev_attr_pwm1.attr, > > + &dev_attr_pwm1_enable.attr, > > + &dev_attr_pwm1_mode.attr, > > + &dev_attr_fan1_input.attr, > > + &dev_attr_fan1_target.attr, > > + &dev_attr_fan1_min.attr, > > + &dev_attr_fan1_max.attr, > > + NULL > > +}; > > + > > +static const struct attribute_group gpio_fan_ctrl_group = { > > + .attrs = gpio_fan_ctrl_attributes, > > +}; > > + > > +static int fan_ctrl_init(struct gpio_fan_data *fan_data, > > + struct gpio_fan_platform_data *pdata) > > +{ > > + struct platform_device *pdev = fan_data->pdev; > > + int num_ctrl = pdata->num_ctrl; > > + unsigned *ctrl = pdata->ctrl; > > + int i, err; > > + > > + for (i = 0; i < num_ctrl; i++) { > > + err = gpio_request(ctrl[i], "GPIO fan control"); > > + if (err) > > + goto err_free_gpio; > > + > > + err = gpio_direction_output(ctrl[i], gpio_get_value(ctrl[i])); > > + if (err) { > > + gpio_free(ctrl[i]); > > + goto err_free_gpio; > > + } > > + } > > + > > + err = sysfs_create_group(&pdev->dev.kobj, &gpio_fan_ctrl_group); > > + if (err) > > + goto err_free_gpio; > > + > > + fan_data->num_ctrl = num_ctrl; > > + fan_data->ctrl = ctrl; > > + fan_data->num_speed = pdata->num_speed; > > + fan_data->speed = pdata->speed; > > + fan_data->pwm_enable = 1; /* Enable manual fan speed control. */ > > fan_data->pwm_enable = true; Ok. > > > + fan_data->speed_index = get_fan_speed(fan_data); > > + if (fan_data->speed_index < 0) { > > + /* Set fan speed to a known value. */ > > + set_fan_speed(fan_data, fan_data->num_speed / 2); > > + dev_info(&pdev->dev, "set fan speed to %d rpm\n", > > + fan_data->speed[fan_data->speed_index].rpm); > > I think a safer approach would be to return -ENODEV in this case. > Better not to try any fan control at all if the GPIO bits report > an unsupported value. It indicates that something is wrong. Ok. > > > + } > > + > > + return 0; > > + > > +err_free_gpio: > > + for (i = i - 1; i >= 0; i--) > > + gpio_free(ctrl[i]); > > + > > + return err; > > +} > > + > > +static void fan_ctrl_free(struct gpio_fan_data *fan_data) > > +{ > > + struct platform_device *pdev = fan_data->pdev; > > + int i; > > + > > + sysfs_remove_group(&pdev->dev.kobj, &gpio_fan_ctrl_group); > > + for (i = 0; i < fan_data->num_ctrl; i++) > > + gpio_free(fan_data->ctrl[i]); > > +} > > + > > +/* > > + * Platform driver. > > + */ > > + > > +static ssize_t show_name(struct device *dev, > > + struct device_attribute *attr, char *buf) > > +{ > > + return sprintf(buf, "gpio-fan\n"); > > +} > > + > > +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); > > + > > +static int __devinit gpio_fan_probe(struct platform_device *pdev) > > +{ > > + int err = 0; > > + struct gpio_fan_data *fan_data; > > + struct gpio_fan_platform_data *pdata = pdev->dev.platform_data; > > + > > + if (!pdata) > > + return -EINVAL; > > + > > + fan_data = kzalloc(sizeof(struct gpio_fan_data), GFP_KERNEL); > > + if (!fan_data) > > + return -ENOMEM; > > + > > + fan_data->pdev = pdev; > > + platform_set_drvdata(pdev, fan_data); > > + mutex_init(&fan_data->lock); > > + > > + /* Configure alarm GPIO if available. */ > > + if (pdata->alarm) { > > + err = fan_alarm_init(fan_data, pdata->alarm); > > + if (err) > > + goto err_free_data; > > + } > > + > > + /* Configure control GPIOs if available. */ > > + if (pdata->ctrl && pdata->num_ctrl) { > > You might want to check for pdata->num_ctrl > 0 here. > It could be negative, which would be bad. Yes. > > > + if (!pdata->num_speed || !pdata->speed) { > > num_speed must be > 1. Otherwise there is a risk of division by zero if it is == 1. Yes. > > > + err = -EINVAL; > > + goto err_free_alarm; > > + } > > + err = fan_ctrl_init(fan_data, pdata); > > + if (err) > > + goto err_free_alarm; > > + } > > + > > + err = device_create_file(&pdev->dev, &dev_attr_name); > > + if (err) > > + goto err_free_ctrl; > > + > > + /* Make this driver part of hwmon class. */ > > + fan_data->hwmon_dev = hwmon_device_register(&pdev->dev); > > + if (IS_ERR(fan_data->hwmon_dev)) { > > + err = PTR_ERR(fan_data->hwmon_dev); > > + goto err_remove_name; > > + } > > + > > + dev_info(&pdev->dev, "GPIO fan initialized\n"); > > + > > + return 0; > > + > > +err_remove_name: > > + device_remove_file(&pdev->dev, &dev_attr_name); > > +err_free_ctrl: > > + if (fan_data->ctrl) > > + fan_ctrl_free(fan_data); > > +err_free_alarm: > > + if (fan_data->alarm) > > + fan_alarm_free(fan_data); > > +err_free_data: > > + platform_set_drvdata(pdev, NULL); > > + kfree(fan_data); > > + > > + return err; > > +} > > + > > +static int __devexit gpio_fan_remove(struct platform_device *pdev) > > +{ > > + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); > > + > > + hwmon_device_unregister(fan_data->hwmon_dev); > > + device_remove_file(&pdev->dev, &dev_attr_name); > > + if (fan_data->alarm) > > + fan_alarm_free(fan_data); > > + if (fan_data->ctrl) > > + fan_ctrl_free(fan_data); > > + kfree(fan_data); > > + > > + return 0; > > +} > > + > > +#ifdef CONFIG_PM > > +static int gpio_fan_suspend(struct platform_device *pdev, pm_message_t state) > > +{ > > + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); > > + > > + if (fan_data->ctrl) { > > + fan_data->resume_speed = fan_data->speed_index; > > + set_fan_speed(fan_data, 0); > > + } > > + > > + return 0; > > +} > > + > > +static int gpio_fan_resume(struct platform_device *pdev) > > +{ > > + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); > > + > > + if (fan_data->ctrl) > > + set_fan_speed(fan_data, fan_data->resume_speed); > > + > > + return 0; > > +} > > +#else > > +#define gpio_fan_suspend NULL > > +#define gpio_fan_resume NULL > > +#endif > > + > > +static struct platform_driver gpio_fan_driver = { > > + .probe = gpio_fan_probe, > > + .remove = __devexit_p(gpio_fan_remove), > > + .suspend = gpio_fan_suspend, > > + .resume = gpio_fan_resume, > > + .driver = { > > + .name = "gpio-fan", > > + }, > > +}; > > + > > +static int __init gpio_fan_init(void) > > +{ > > + return platform_driver_register(&gpio_fan_driver); > > +} > > + > > +static void __exit gpio_fan_exit(void) > > +{ > > + platform_driver_unregister(&gpio_fan_driver); > > +} > > + > > +module_init(gpio_fan_init); > > +module_exit(gpio_fan_exit); > > + > > +MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); > > +MODULE_DESCRIPTION("GPIO FAN driver"); > > +MODULE_LICENSE("GPL"); > > +MODULE_ALIAS("platform:gpio-fan"); > > diff --git a/include/linux/gpio-fan.h b/include/linux/gpio-fan.h > > new file mode 100644 > > index 0000000..0966591 > > --- /dev/null > > +++ b/include/linux/gpio-fan.h > > @@ -0,0 +1,36 @@ > > +/* > > + * include/linux/gpio-fan.h > > + * > > + * Platform data structure for GPIO fan driver > > + * > > + * This file is licensed under the terms of the GNU General Public > > + * License version 2. This program is licensed "as is" without any > > + * warranty of any kind, whether express or implied. > > + */ > > + > > +#ifndef __LINUX_GPIO_FAN_H > > +#define __LINUX_GPIO_FAN_H > > + > > +struct gpio_fan_alarm { > > + unsigned gpio; > > + unsigned active_low; > > +}; > > + > > +struct gpio_fan_speed { > > + int rpm; > > + int ctrl_val; > > +}; > > + > > +struct gpio_fan_platform_data { > > + int num_ctrl; > > + unsigned *ctrl; /* fan control GPIOs. */ > > + struct gpio_fan_alarm *alarm; /* fan alarm GPIO. */ > > + /* > > + * Speed conversion array: rpm from/to GPIO bit field. > > + * This array _must_ be sorted in ascending rpm order. > > + */ > > + int num_speed; > > + struct gpio_fan_speed *speed; > > +}; > > + > > +#endif /* __LINUX_GPIO_FAN_H */ > > -- > > 1.6.3.1 > > > > _______________________________________________ > linux-arm-kernel mailing list > linux-arm-kernel at lists.infradead.org > http://lists.infradead.org/mailman/listinfo/linux-arm-kernel -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 198 bytes Desc: Digital signature URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20101021/cbb58326/attachment-0001.sig> ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2] hwmon: add generic GPIO fan driver 2010-10-21 14:06 ` Simon Guinot @ 2010-10-21 14:43 ` Guenter Roeck 2010-10-21 21:59 ` [lm-sensors] " Simon Guinot 0 siblings, 1 reply; 16+ messages in thread From: Guenter Roeck @ 2010-10-21 14:43 UTC (permalink / raw) To: linux-arm-kernel Hi Simon, On Thu, Oct 21, 2010 at 10:06:27AM -0400, Simon Guinot wrote: [ ... ] > > > > > > +config SENSORS_GPIO_FAN > > > + tristate "GPIO fan" > > > + depends on GENERIC_GPIO > > > + help > > > + If you say yes here you get support for the GPIO connected fan. > > > + > > > > "for GPIO connected fans.". Can be more than one. > > My bad English... > > > > > > + This driver can also be built as a module. If so, the module > > > + will be called gpio-fan. > > > + > > > config SENSORS_G760A > > > tristate "GMT G760A" > > > depends on I2C > > > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile > > > index e3c2484..5df7e4a 100644 > > > --- a/drivers/hwmon/Makefile > > > +++ b/drivers/hwmon/Makefile > > > @@ -48,6 +48,7 @@ obj-$(CONFIG_SENSORS_F71805F) += f71805f.o > > > obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o > > > obj-$(CONFIG_SENSORS_F75375S) += f75375s.o > > > obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o > > > +obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o > > > > "gp" is alphabetically after "g7". Please move. > > It was the original place... > > Ok, I have been confused with the Kconfig "sequence" order: put > SENSORS_GPIO_FAN before SENSORS_G760A seems wrong to me. > Which is why I asked you to move it in Makefile. > Why we don't have to respect the alphabetical order for Kconfig ? > Oh, we should. I just didn't notice that it is out of order in Kconfig as well. So please move the definition there as well. [ ... ] > > > > Would it make more sense to return the closest rpm instead ? > > > > For example, if one sets a value of 1900, and the closest value > > in the table is 1800, it might make more sense to set the speed > > to 1800 and not to, say, 3000, if that is the next value. > > On the other hand, you can set rpm to 500 and the closest value could be > 0 and the fan stop (or don't start). That's a disturbing experience. > If you agree, I prefer to set the upper speed. > Ok. > > > > No strong opinion, though. One might as well argue that it is safer > > to run at the higher speed. > > > > Some fans have problems with the low speeds and needs a little boost to > start up. > Ok. Not sure if rounding up really helps there, though. > > > > The combination of DIV_ROUND_UP() and DIV_ROUND_CLOSEST() causes inconsistency. > > > > Assume num_speed = 8, pwm is set to 128. > > > > set: 128 * (8 - 1) / 255 = 3.513 ==> 4 > > get: 4 * 255 / (8 - 1) = 145.7 ==> 146 > > set: 146 * (8 - 1) / 255 = 4.007 ==> 5 > > get: 5 * 255 / (8 - 1) = 182.142 ==> 182 > > set: 182 * (8 - 1) / 255 = 4.996 ==> 5 > > > > Unless there is a really good reason to use DIV_ROUND_UP(), you might > > want to use DIV_ROUND_CLOSEST() instead. > > This choice is coherent with the rpm interface one and the reason is the > same: start the fan even with a low value. In your example, 36 is first > speed threshold. > Yes, but here it causes an inconsistency between setting and reporting. I don't expect the speed to change if I set the same value that was read. Exactly this happens if one writes 146 in my example. That is much worse than a potential startup problem, or the observation that pwm values below X don't start the fan. Besides, one can set min and max values for pwm in fancontrol to avoid the startup problem. Thanks, Guenter ^ permalink raw reply [flat|nested] 16+ messages in thread
* [lm-sensors] [PATCH v2] hwmon: add generic GPIO fan driver 2010-10-21 14:43 ` Guenter Roeck @ 2010-10-21 21:59 ` Simon Guinot 2010-10-21 22:16 ` Guenter Roeck 0 siblings, 1 reply; 16+ messages in thread From: Simon Guinot @ 2010-10-21 21:59 UTC (permalink / raw) To: linux-arm-kernel Hi Guenter, On Thu, Oct 21, 2010 at 07:43:46AM -0700, Guenter Roeck wrote: > > > > > > The combination of DIV_ROUND_UP() and DIV_ROUND_CLOSEST() causes inconsistency. > > > > > > Assume num_speed = 8, pwm is set to 128. > > > > > > set: 128 * (8 - 1) / 255 = 3.513 ==> 4 > > > get: 4 * 255 / (8 - 1) = 145.7 ==> 146 > > > set: 146 * (8 - 1) / 255 = 4.007 ==> 5 > > > get: 5 * 255 / (8 - 1) = 182.142 ==> 182 > > > set: 182 * (8 - 1) / 255 = 4.996 ==> 5 > > > > > > Unless there is a really good reason to use DIV_ROUND_UP(), you might > > > want to use DIV_ROUND_CLOSEST() instead. > > > > This choice is coherent with the rpm interface one and the reason is the > > same: start the fan even with a low value. In your example, 36 is first > > speed threshold. > > > Yes, but here it causes an inconsistency between setting and reporting. > I don't expect the speed to change if I set the same value that was read. > Exactly this happens if one writes 146 in my example. That is much worse > than a potential startup problem, or the observation that pwm values below X > don't start the fan. Mmm. Convert a speed index into a low round pwm value (and not use DIV_ROUND_CLOSEST() at all) fix the inconsistency too. If you agree, I would prefer this option. Thanks, Simon -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 198 bytes Desc: Digital signature URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20101021/6d57e75b/attachment.sig> ^ permalink raw reply [flat|nested] 16+ messages in thread
* [lm-sensors] [PATCH v2] hwmon: add generic GPIO fan driver 2010-10-21 21:59 ` [lm-sensors] " Simon Guinot @ 2010-10-21 22:16 ` Guenter Roeck 2010-10-21 22:35 ` Simon Guinot 0 siblings, 1 reply; 16+ messages in thread From: Guenter Roeck @ 2010-10-21 22:16 UTC (permalink / raw) To: linux-arm-kernel Hi Simon, On Thu, 2010-10-21 at 17:59 -0400, Simon Guinot wrote: > Hi Guenter, > > On Thu, Oct 21, 2010 at 07:43:46AM -0700, Guenter Roeck wrote: > > > > > > > > The combination of DIV_ROUND_UP() and DIV_ROUND_CLOSEST() causes inconsistency. > > > > > > > > Assume num_speed = 8, pwm is set to 128. > > > > > > > > set: 128 * (8 - 1) / 255 = 3.513 ==> 4 > > > > get: 4 * 255 / (8 - 1) = 145.7 ==> 146 > > > > set: 146 * (8 - 1) / 255 = 4.007 ==> 5 > > > > get: 5 * 255 / (8 - 1) = 182.142 ==> 182 > > > > set: 182 * (8 - 1) / 255 = 4.996 ==> 5 > > > > > > > > Unless there is a really good reason to use DIV_ROUND_UP(), you might > > > > want to use DIV_ROUND_CLOSEST() instead. > > > > > > This choice is coherent with the rpm interface one and the reason is the > > > same: start the fan even with a low value. In your example, 36 is first > > > speed threshold. > > > > > Yes, but here it causes an inconsistency between setting and reporting. > > I don't expect the speed to change if I set the same value that was read. > > Exactly this happens if one writes 146 in my example. That is much worse > > than a potential startup problem, or the observation that pwm values below X > > don't start the fan. > > Mmm. Convert a speed index into a low round pwm value (and not use > DIV_ROUND_CLOSEST() at all) fix the inconsistency too. If you agree, > I would prefer this option. Ok if it works. I am mostly concerned about inconsistencies. Not sure I understand what you mean with "low round pwm value", though. Guenter ^ permalink raw reply [flat|nested] 16+ messages in thread
* [lm-sensors] [PATCH v2] hwmon: add generic GPIO fan driver 2010-10-21 22:16 ` Guenter Roeck @ 2010-10-21 22:35 ` Simon Guinot 2010-10-21 22:44 ` [PATCH v3] " Simon Guinot 0 siblings, 1 reply; 16+ messages in thread From: Simon Guinot @ 2010-10-21 22:35 UTC (permalink / raw) To: linux-arm-kernel On Thu, Oct 21, 2010 at 03:16:13PM -0700, Guenter Roeck wrote: > Hi Simon, > > On Thu, 2010-10-21 at 17:59 -0400, Simon Guinot wrote: > > Hi Guenter, > > > > On Thu, Oct 21, 2010 at 07:43:46AM -0700, Guenter Roeck wrote: > > > > > > > > > > The combination of DIV_ROUND_UP() and DIV_ROUND_CLOSEST() causes inconsistency. > > > > > > > > > > Assume num_speed = 8, pwm is set to 128. > > > > > > > > > > set: 128 * (8 - 1) / 255 = 3.513 ==> 4 > > > > > get: 4 * 255 / (8 - 1) = 145.7 ==> 146 > > > > > set: 146 * (8 - 1) / 255 = 4.007 ==> 5 > > > > > get: 5 * 255 / (8 - 1) = 182.142 ==> 182 > > > > > set: 182 * (8 - 1) / 255 = 4.996 ==> 5 > > > > > > > > > > Unless there is a really good reason to use DIV_ROUND_UP(), you might > > > > > want to use DIV_ROUND_CLOSEST() instead. > > > > > > > > This choice is coherent with the rpm interface one and the reason is the > > > > same: start the fan even with a low value. In your example, 36 is first > > > > speed threshold. > > > > > > > Yes, but here it causes an inconsistency between setting and reporting. > > > I don't expect the speed to change if I set the same value that was read. > > > Exactly this happens if one writes 146 in my example. That is much worse > > > than a potential startup problem, or the observation that pwm values below X > > > don't start the fan. > > > > Mmm. Convert a speed index into a low round pwm value (and not use > > DIV_ROUND_CLOSEST() at all) fix the inconsistency too. If you agree, > > I would prefer this option. > > Ok if it works. I am mostly concerned about inconsistencies. > > Not sure I understand what you mean with "low round pwm value", though. I mean not using DIV_ROUND_CLOSEST() in the show_pwm() function: u8 pwm = fan_data->speed_index * 255 / (fan_data->num_speed - 1); I will send an updated version of the patch. This should address your last comments. Thanks, Simon -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 198 bytes Desc: Digital signature URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20101021/2857ca41/attachment.sig> ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3] hwmon: add generic GPIO fan driver 2010-10-21 22:35 ` Simon Guinot @ 2010-10-21 22:44 ` Simon Guinot 2010-10-22 1:51 ` Guenter Roeck 0 siblings, 1 reply; 16+ messages in thread From: Simon Guinot @ 2010-10-21 22:44 UTC (permalink / raw) To: linux-arm-kernel From: Simon Guinot <sguinot@lacie.com> This patch adds hwmon support for the GPIO connected fan. Platform specific informations as GPIO pinout and speed conversion array (rpm from/to GPIO value) are passed to the driver via platform_data. Signed-off-by: Simon Guinot <sguinot@lacie.com> --- drivers/hwmon/Kconfig | 9 + drivers/hwmon/Makefile | 1 + drivers/hwmon/gpio-fan.c | 558 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/gpio-fan.h | 36 +++ 4 files changed, 604 insertions(+), 0 deletions(-) create mode 100644 drivers/hwmon/gpio-fan.c create mode 100644 include/linux/gpio-fan.h diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 4d4d09b..10ce9da 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -399,6 +399,15 @@ config SENSORS_GL520SM This driver can also be built as a module. If so, the module will be called gl520sm. +config SENSORS_GPIO_FAN + tristate "GPIO fan" + depends on GENERIC_GPIO + help + If you say yes here you get support for the GPIO connected fans. + + This driver can also be built as a module. If so, the module + will be called gpio-fan. + config SENSORS_CORETEMP tristate "Intel Core/Core2/Atom temperature sensor" depends on X86 && PCI && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index e3c2484..a793e28 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o obj-$(CONFIG_SENSORS_G760A) += g760a.o obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o +obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o obj-$(CONFIG_SENSORS_ULTRA45) += ultra45_env.o obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o diff --git a/drivers/hwmon/gpio-fan.c b/drivers/hwmon/gpio-fan.c new file mode 100644 index 0000000..0e4d575 --- /dev/null +++ b/drivers/hwmon/gpio-fan.c @@ -0,0 +1,558 @@ +/* + * gpio-fan.c - Hwmon driver for GPIO connected fan. + * + * Copyright (C) 2010 LaCie + * + * Author: Simon Guinot <sguinot@lacie.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/hwmon.h> +#include <linux/gpio.h> +#include <linux/gpio-fan.h> + +struct gpio_fan_data { + struct platform_device *pdev; + struct device *hwmon_dev; + struct mutex lock; /* lock GPIOs operations. */ + int num_ctrl; + unsigned *ctrl; + int num_speed; + struct gpio_fan_speed *speed; + int speed_index; +#ifdef CONFIG_PM + int resume_speed; +#endif + bool pwm_enable; + struct gpio_fan_alarm *alarm; + struct work_struct alarm_work; +}; + +/* + * Alarm GPIO. + */ + +static void fan_alarm_notify(struct work_struct *ws) +{ + struct gpio_fan_data *fan_data = + container_of(ws, struct gpio_fan_data, alarm_work); + + sysfs_notify(&fan_data->pdev->dev.kobj, NULL, "fan1_alarm"); + kobject_uevent(&fan_data->pdev->dev.kobj, KOBJ_CHANGE); +} + +static irqreturn_t fan_alarm_irq_handler(int irq, void *dev_id) +{ + struct gpio_fan_data *fan_data = dev_id; + + schedule_work(&fan_data->alarm_work); + + return IRQ_NONE; +} + +static ssize_t show_fan_alarm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + struct gpio_fan_alarm *alarm = fan_data->alarm; + int value = gpio_get_value(alarm->gpio); + + if (alarm->active_low) + value = !value; + + return sprintf(buf, "%d\n", value); +} + +static DEVICE_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL); + +static int fan_alarm_init(struct gpio_fan_data *fan_data, + struct gpio_fan_alarm *alarm) +{ + int err; + int alarm_irq; + struct platform_device *pdev = fan_data->pdev; + + fan_data->alarm = alarm; + + err = gpio_request(alarm->gpio, "GPIO fan alarm"); + if (err) + return err; + + err = gpio_direction_input(alarm->gpio); + if (err) + goto err_free_gpio; + + err = device_create_file(&pdev->dev, &dev_attr_fan1_alarm); + if (err) + goto err_free_gpio; + + /* + * If the alarm GPIO don't support interrupts, just leave + * without initializing the fail notification support. + */ + alarm_irq = gpio_to_irq(alarm->gpio); + if (alarm_irq < 0) + return 0; + + INIT_WORK(&fan_data->alarm_work, fan_alarm_notify); + set_irq_type(alarm_irq, IRQ_TYPE_EDGE_BOTH); + err = request_irq(alarm_irq, fan_alarm_irq_handler, IRQF_SHARED, + "GPIO fan alarm", fan_data); + if (err) + goto err_free_sysfs; + + return 0; + +err_free_sysfs: + device_remove_file(&pdev->dev, &dev_attr_fan1_alarm); +err_free_gpio: + gpio_free(alarm->gpio); + + return err; +} + +static void fan_alarm_free(struct gpio_fan_data *fan_data) +{ + struct platform_device *pdev = fan_data->pdev; + int alarm_irq = gpio_to_irq(fan_data->alarm->gpio); + + if (alarm_irq >= 0) + free_irq(alarm_irq, fan_data); + device_remove_file(&pdev->dev, &dev_attr_fan1_alarm); + gpio_free(fan_data->alarm->gpio); +} + +/* + * Control GPIOs. + */ + +/* Must be called with fan_data->lock held, except during initialization. */ +static void __set_fan_ctrl(struct gpio_fan_data *fan_data, int ctrl_val) +{ + int i; + + for (i = 0; i < fan_data->num_ctrl; i++) + gpio_set_value(fan_data->ctrl[i], (ctrl_val >> i) & 1); +} + +static int __get_fan_ctrl(struct gpio_fan_data *fan_data) +{ + int i; + int ctrl_val = 0; + + for (i = 0; i < fan_data->num_ctrl; i++) { + int value; + + value = gpio_get_value(fan_data->ctrl[i]); + ctrl_val |= (value << i); + } + return ctrl_val; +} + +/* Must be called with fan_data->lock held, except during initialization. */ +static void set_fan_speed(struct gpio_fan_data *fan_data, int speed_index) +{ + if (fan_data->speed_index == speed_index) + return; + + __set_fan_ctrl(fan_data, fan_data->speed[speed_index].ctrl_val); + fan_data->speed_index = speed_index; +} + +static int get_fan_speed_index(struct gpio_fan_data *fan_data) +{ + int ctrl_val = __get_fan_ctrl(fan_data); + int i; + + for (i = 0; i < fan_data->num_speed; i++) + if (fan_data->speed[i].ctrl_val == ctrl_val) + return i; + + dev_warn(&fan_data->pdev->dev, + "missing speed array entry for GPIO value 0x%x\n", ctrl_val); + + return -EINVAL; +} + +static int rpm_to_speed_index(struct gpio_fan_data *fan_data, int rpm) +{ + struct gpio_fan_speed *speed = fan_data->speed; + int i; + + for (i = 0; i < fan_data->num_speed; i++) + if (speed[i].rpm >= rpm) + return i; + + return fan_data->num_speed - 1; +} + +static ssize_t show_pwm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + u8 pwm = fan_data->speed_index * 255 / (fan_data->num_speed - 1); + + return sprintf(buf, "%d\n", pwm); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + unsigned long pwm; + int speed_index; + int ret = count; + + if (strict_strtoul(buf, 10, &pwm) || pwm > 255) + return -EINVAL; + + mutex_lock(&fan_data->lock); + + if (!fan_data->pwm_enable) { + ret = -EPERM; + goto exit_unlock; + } + + speed_index = DIV_ROUND_UP(pwm * (fan_data->num_speed - 1), 255); + set_fan_speed(fan_data, speed_index); + +exit_unlock: + mutex_unlock(&fan_data->lock); + + return ret; +} + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", fan_data->pwm_enable); +} + +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + unsigned long val; + + if (strict_strtoul(buf, 10, &val) || val > 1) + return -EINVAL; + + if (fan_data->pwm_enable == val) + return count; + + mutex_lock(&fan_data->lock); + + fan_data->pwm_enable = val; + + /* Disable manual control mode: set fan at full speed. */ + if (val == 0) + set_fan_speed(fan_data, fan_data->num_speed - 1); + + mutex_unlock(&fan_data->lock); + + return count; +} + +static ssize_t show_pwm_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "0\n"); +} + +static ssize_t show_rpm_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", fan_data->speed[0].rpm); +} + +static ssize_t show_rpm_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", + fan_data->speed[fan_data->num_speed - 1].rpm); +} + +static ssize_t show_rpm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", fan_data->speed[fan_data->speed_index].rpm); +} + +static ssize_t set_rpm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct gpio_fan_data *fan_data = dev_get_drvdata(dev); + unsigned long rpm; + int ret = count; + + if (strict_strtoul(buf, 10, &rpm)) + return -EINVAL; + + mutex_lock(&fan_data->lock); + + if (!fan_data->pwm_enable) { + ret = -EPERM; + goto exit_unlock; + } + + set_fan_speed(fan_data, rpm_to_speed_index(fan_data, rpm)); + +exit_unlock: + mutex_unlock(&fan_data->lock); + + return ret; +} + +static DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm); +static DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, + show_pwm_enable, set_pwm_enable); +static DEVICE_ATTR(pwm1_mode, S_IRUGO, show_pwm_mode, NULL); +static DEVICE_ATTR(fan1_min, S_IRUGO, show_rpm_min, NULL); +static DEVICE_ATTR(fan1_max, S_IRUGO, show_rpm_max, NULL); +static DEVICE_ATTR(fan1_input, S_IRUGO, show_rpm, NULL); +static DEVICE_ATTR(fan1_target, S_IRUGO | S_IWUSR, show_rpm, set_rpm); + +static struct attribute *gpio_fan_ctrl_attributes[] = { + &dev_attr_pwm1.attr, + &dev_attr_pwm1_enable.attr, + &dev_attr_pwm1_mode.attr, + &dev_attr_fan1_input.attr, + &dev_attr_fan1_target.attr, + &dev_attr_fan1_min.attr, + &dev_attr_fan1_max.attr, + NULL +}; + +static const struct attribute_group gpio_fan_ctrl_group = { + .attrs = gpio_fan_ctrl_attributes, +}; + +static int fan_ctrl_init(struct gpio_fan_data *fan_data, + struct gpio_fan_platform_data *pdata) +{ + struct platform_device *pdev = fan_data->pdev; + int num_ctrl = pdata->num_ctrl; + unsigned *ctrl = pdata->ctrl; + int i, err; + + for (i = 0; i < num_ctrl; i++) { + err = gpio_request(ctrl[i], "GPIO fan control"); + if (err) + goto err_free_gpio; + + err = gpio_direction_output(ctrl[i], gpio_get_value(ctrl[i])); + if (err) { + gpio_free(ctrl[i]); + goto err_free_gpio; + } + } + + err = sysfs_create_group(&pdev->dev.kobj, &gpio_fan_ctrl_group); + if (err) + goto err_free_gpio; + + fan_data->num_ctrl = num_ctrl; + fan_data->ctrl = ctrl; + fan_data->num_speed = pdata->num_speed; + fan_data->speed = pdata->speed; + fan_data->pwm_enable = true; /* Enable manual fan speed control. */ + fan_data->speed_index = get_fan_speed_index(fan_data); + if (fan_data->speed_index < 0) { + err = -ENODEV; + goto err_free_gpio; + } + + return 0; + +err_free_gpio: + for (i = i - 1; i >= 0; i--) + gpio_free(ctrl[i]); + + return err; +} + +static void fan_ctrl_free(struct gpio_fan_data *fan_data) +{ + struct platform_device *pdev = fan_data->pdev; + int i; + + sysfs_remove_group(&pdev->dev.kobj, &gpio_fan_ctrl_group); + for (i = 0; i < fan_data->num_ctrl; i++) + gpio_free(fan_data->ctrl[i]); +} + +/* + * Platform driver. + */ + +static ssize_t show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "gpio-fan\n"); +} + +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +static int __devinit gpio_fan_probe(struct platform_device *pdev) +{ + int err = 0; + struct gpio_fan_data *fan_data; + struct gpio_fan_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return -EINVAL; + + fan_data = kzalloc(sizeof(struct gpio_fan_data), GFP_KERNEL); + if (!fan_data) + return -ENOMEM; + + fan_data->pdev = pdev; + platform_set_drvdata(pdev, fan_data); + mutex_init(&fan_data->lock); + + /* Configure alarm GPIO if available. */ + if (pdata->alarm) { + err = fan_alarm_init(fan_data, pdata->alarm); + if (err) + goto err_free_data; + } + + /* Configure control GPIOs if available. */ + if (pdata->ctrl && pdata->num_ctrl > 0) { + if (!pdata->speed || pdata->num_speed <= 1) { + err = -EINVAL; + goto err_free_alarm; + } + err = fan_ctrl_init(fan_data, pdata); + if (err) + goto err_free_alarm; + } + + err = device_create_file(&pdev->dev, &dev_attr_name); + if (err) + goto err_free_ctrl; + + /* Make this driver part of hwmon class. */ + fan_data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(fan_data->hwmon_dev)) { + err = PTR_ERR(fan_data->hwmon_dev); + goto err_remove_name; + } + + dev_info(&pdev->dev, "GPIO fan initialized\n"); + + return 0; + +err_remove_name: + device_remove_file(&pdev->dev, &dev_attr_name); +err_free_ctrl: + if (fan_data->ctrl) + fan_ctrl_free(fan_data); +err_free_alarm: + if (fan_data->alarm) + fan_alarm_free(fan_data); +err_free_data: + platform_set_drvdata(pdev, NULL); + kfree(fan_data); + + return err; +} + +static int __devexit gpio_fan_remove(struct platform_device *pdev) +{ + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); + + hwmon_device_unregister(fan_data->hwmon_dev); + device_remove_file(&pdev->dev, &dev_attr_name); + if (fan_data->alarm) + fan_alarm_free(fan_data); + if (fan_data->ctrl) + fan_ctrl_free(fan_data); + kfree(fan_data); + + return 0; +} + +#ifdef CONFIG_PM +static int gpio_fan_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); + + if (fan_data->ctrl) { + fan_data->resume_speed = fan_data->speed_index; + set_fan_speed(fan_data, 0); + } + + return 0; +} + +static int gpio_fan_resume(struct platform_device *pdev) +{ + struct gpio_fan_data *fan_data = platform_get_drvdata(pdev); + + if (fan_data->ctrl) + set_fan_speed(fan_data, fan_data->resume_speed); + + return 0; +} +#else +#define gpio_fan_suspend NULL +#define gpio_fan_resume NULL +#endif + +static struct platform_driver gpio_fan_driver = { + .probe = gpio_fan_probe, + .remove = __devexit_p(gpio_fan_remove), + .suspend = gpio_fan_suspend, + .resume = gpio_fan_resume, + .driver = { + .name = "gpio-fan", + }, +}; + +static int __init gpio_fan_init(void) +{ + return platform_driver_register(&gpio_fan_driver); +} + +static void __exit gpio_fan_exit(void) +{ + platform_driver_unregister(&gpio_fan_driver); +} + +module_init(gpio_fan_init); +module_exit(gpio_fan_exit); + +MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); +MODULE_DESCRIPTION("GPIO FAN driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio-fan"); diff --git a/include/linux/gpio-fan.h b/include/linux/gpio-fan.h new file mode 100644 index 0000000..0966591 --- /dev/null +++ b/include/linux/gpio-fan.h @@ -0,0 +1,36 @@ +/* + * include/linux/gpio-fan.h + * + * Platform data structure for GPIO fan driver + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#ifndef __LINUX_GPIO_FAN_H +#define __LINUX_GPIO_FAN_H + +struct gpio_fan_alarm { + unsigned gpio; + unsigned active_low; +}; + +struct gpio_fan_speed { + int rpm; + int ctrl_val; +}; + +struct gpio_fan_platform_data { + int num_ctrl; + unsigned *ctrl; /* fan control GPIOs. */ + struct gpio_fan_alarm *alarm; /* fan alarm GPIO. */ + /* + * Speed conversion array: rpm from/to GPIO bit field. + * This array _must_ be sorted in ascending rpm order. + */ + int num_speed; + struct gpio_fan_speed *speed; +}; + +#endif /* __LINUX_GPIO_FAN_H */ -- 1.6.3.1 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v3] hwmon: add generic GPIO fan driver 2010-10-21 22:44 ` [PATCH v3] " Simon Guinot @ 2010-10-22 1:51 ` Guenter Roeck 2010-10-22 12:25 ` Simon Guinot 0 siblings, 1 reply; 16+ messages in thread From: Guenter Roeck @ 2010-10-22 1:51 UTC (permalink / raw) To: linux-arm-kernel Hi Simon, On Thu, 2010-10-21 at 18:44 -0400, Simon Guinot wrote: > From: Simon Guinot <sguinot@lacie.com> > > This patch adds hwmon support for the GPIO connected fan. > I rephrased this to ".. for fans connected to GPIO pins." > Platform specific informations as GPIO pinout and speed conversion array > (rpm from/to GPIO value) are passed to the driver via platform_data. > > Signed-off-by: Simon Guinot <sguinot@lacie.com> [ ... ] > +static int __devinit gpio_fan_probe(struct platform_device *pdev) > +{ > + int err = 0; and this variable initialization is not needed. Otherwise looks good. Applied with the above changed to my -next tree. I'll let it rest there for a couple of days. Unless some unforeseen problems show up, it might just make it into .37. Thanks, Guenter ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3] hwmon: add generic GPIO fan driver 2010-10-22 1:51 ` Guenter Roeck @ 2010-10-22 12:25 ` Simon Guinot 0 siblings, 0 replies; 16+ messages in thread From: Simon Guinot @ 2010-10-22 12:25 UTC (permalink / raw) To: linux-arm-kernel Hi Guenter, On Thu, Oct 21, 2010 at 06:51:41PM -0700, Guenter Roeck wrote: > Hi Simon, > > On Thu, 2010-10-21 at 18:44 -0400, Simon Guinot wrote: > > From: Simon Guinot <sguinot@lacie.com> > > > > This patch adds hwmon support for the GPIO connected fan. > > > I rephrased this to ".. for fans connected to GPIO pins." > > > Platform specific informations as GPIO pinout and speed conversion array > > (rpm from/to GPIO value) are passed to the driver via platform_data. > > > > Signed-off-by: Simon Guinot <sguinot@lacie.com> > > [ ... ] > > +static int __devinit gpio_fan_probe(struct platform_device *pdev) > > +{ > > + int err = 0; > > and this variable initialization is not needed. > > Otherwise looks good. Applied with the above changed to my -next tree. > > I'll let it rest there for a couple of days. Unless some unforeseen > problems show up, it might just make it into .37. Thanks for your help and your patience. Simon -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 198 bytes Desc: Digital signature URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20101022/df0cc95b/attachment.sig> ^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2010-10-22 12:25 UTC | newest] Thread overview: 16+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2010-10-20 16:37 [PATCH v2] hwmon: add generic GPIO fan driver Simon Guinot 2010-10-20 16:37 ` Simon Guinot 2010-10-20 16:59 ` Guenter Roeck 2010-10-20 19:10 ` Simon Guinot 2010-10-20 20:42 ` Guenter Roeck 2010-10-20 19:18 ` Russell King - ARM Linux 2010-10-20 20:06 ` Guenter Roeck 2010-10-21 6:41 ` Guenter Roeck 2010-10-21 14:06 ` Simon Guinot 2010-10-21 14:43 ` Guenter Roeck 2010-10-21 21:59 ` [lm-sensors] " Simon Guinot 2010-10-21 22:16 ` Guenter Roeck 2010-10-21 22:35 ` Simon Guinot 2010-10-21 22:44 ` [PATCH v3] " Simon Guinot 2010-10-22 1:51 ` Guenter Roeck 2010-10-22 12:25 ` Simon Guinot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).