GIT 5667a134f02eeabed787b0c95d92e1db72fbd979 git://git.o-hand.com/linux-rpurdie-leds#for-mm commit Author: Richard Purdie Date: Fri Jun 1 12:38:37 2007 +0100 leds: Add colour attribute Add colour attribute since only exposing the information through the device name is frowned upon. Signed-off-by: Richard Purdie commit f2618a75c95a01c8aea0133f388cc77dc59fedb3 Author: Richard Purdie Date: Fri May 11 00:12:01 2007 +0100 leds: Add warning printks in error paths Add warning printks if led_trigger_register_simple() fails. Signed-off-by: Richard Purdie commit c04e07c952c8a8741faf61728b12f0bf1fdbc3ef Author: Richard Purdie Date: Thu May 10 23:46:30 2007 +0100 leds: Fix trigger unregister_simple if register_simple fails Fix led_trigger_unregister_simple to handle the case where led_trigger_register_simple fails, avoiding a NULL pointer dereference. Signed-off-by: Richard Purdie commit 63e106831cede7c3ef451f7921c9504da555f138 Author: Jan Engelhardt Date: Thu May 10 10:44:11 2007 +0100 leds: Use menuconfig objects II - LED Change Kconfig objects from "menu, config" into "menuconfig" so that the user can disable the whole feature without having to enter the menu first. Signed-off-by: Jan Engelhardt Signed-off-by: Andrew Morton Signed-off-by: Richard Purdie commit 1e2b1a1f8b41a4f9218d359a8a032b3ae6cbf19b Author: David Brownell Date: Thu May 10 10:51:41 2007 +0100 leds: Teach leds-gpio to handle timer-unsafe GPIOs Teach the new leds-gpio driver that some GPIOs can't be accessed from timer callbacks ... which is how all today's standard triggers use them. Signed-off-by: David Brownell Signed-off-by: Richard Purdie commit f7be428f8afd09e39fa11d9203d0ef56278f0fd0 Author: Raphael Assenat Date: Tue Feb 27 19:49:53 2007 +0000 leds: Add generic GPIO LED driver This patch adds support for GPIO connected leds via the new GPIO framework. Information about leds (gpio, polarity, name, default trigger) is passed to the driver via platform_data. Signed-off-by: Raphael Assenat Signed-off-by: Richard Purdie drivers/leds/Kconfig | 22 +++-- drivers/leds/Makefile | 1 drivers/leds/led-class.c | 37 ++++++++ drivers/leds/led-triggers.c | 14 ++- drivers/leds/leds-gpio.c | 199 +++++++++++++++++++++++++++++++++++++++++++ include/linux/leds.h | 14 +++ 6 files changed, 274 insertions(+), 13 deletions(-) diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 87d2046..4468cb3 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -1,9 +1,6 @@ - -menu "LED devices" - depends on HAS_IOMEM - -config NEW_LEDS +menuconfig NEW_LEDS bool "LED Support" + depends on HAS_IOMEM help Say Y to enable Linux LED support. This allows control of supported LEDs from both userspace and optionally, by kernel events (triggers). @@ -11,9 +8,10 @@ config NEW_LEDS This is not related to standard keyboard LEDs which are controlled via the input system. +if NEW_LEDS + config LEDS_CLASS tristate "LED Class Support" - depends on NEW_LEDS help This option enables the led sysfs class in /sys/class/leds. You'll need this to do anything useful with LEDs. If unsure, say N. @@ -95,11 +93,18 @@ config LEDS_COBALT help This option enables support for the front LED on Cobalt Server +config LEDS_GPIO + tristate "LED Support for GPIO connected LEDs" + depends on LEDS_CLASS && GENERIC_GPIO + help + This option enables support for the LEDs connected to GPIO + outputs. To be useful the particular board must have LEDs + and they must be connected to the GPIO lines. + comment "LED Triggers" config LEDS_TRIGGERS bool "LED Trigger support" - depends on NEW_LEDS help This option enables trigger support for the leds class. These triggers allow kernel events to drive the LEDs and can @@ -128,5 +133,4 @@ config LEDS_TRIGGER_HEARTBEAT load average. If unsure, say Y. -endmenu - +endif # NEW_LEDS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index aa2c18e..f8995c9 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_LEDS_NET48XX) += leds-net4 obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o obj-$(CONFIG_LEDS_H1940) += leds-h1940.o obj-$(CONFIG_LEDS_COBALT) += leds-cobalt.o +obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o # LED Triggers obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 3c17112..5faf8fc 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -56,8 +56,33 @@ static ssize_t led_brightness_store(stru return ret; } +static ssize_t led_colour_show(struct class_device *dev, char *buf) +{ + struct led_classdev *led_cdev = class_get_devdata(dev); + char *colour, *str = led_cdev->name; + ssize_t len = 0; + + /* skip first field */ + while (*str != ':' && *str != '\0') + str++; + + if (*str == ':') { + str++; + colour = str; + while (*str != ':' && *str != '\0') { + str++; + len++; + } + memcpy(buf, colour, len); + } + + buf[len] = '\n'; + return len + 1; +} + static CLASS_DEVICE_ATTR(brightness, 0644, led_brightness_show, led_brightness_store); +static CLASS_DEVICE_ATTR(colour, 0644, led_colour_show, NULL); #ifdef CONFIG_LEDS_TRIGGERS static CLASS_DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store); #endif @@ -106,6 +131,11 @@ int led_classdev_register(struct device if (rc) goto err_out; + rc = class_device_create_file(led_cdev->class_dev, + &class_device_attr_colour); + if (rc) + goto err_out_colour; + /* add to the list of leds */ write_lock(&leds_list_lock); list_add_tail(&led_cdev->node, &leds_list); @@ -130,9 +160,12 @@ #endif #ifdef CONFIG_LEDS_TRIGGERS err_out_led_list: class_device_remove_file(led_cdev->class_dev, + &class_device_attr_colour); +#endif +err_out_colour: + class_device_remove_file(led_cdev->class_dev, &class_device_attr_brightness); list_del(&led_cdev->node); -#endif err_out: class_device_unregister(led_cdev->class_dev); return rc; @@ -149,6 +182,8 @@ void led_classdev_unregister(struct led_ { class_device_remove_file(led_cdev->class_dev, &class_device_attr_brightness); + class_device_remove_file(led_cdev->class_dev, + &class_device_attr_colour); #ifdef CONFIG_LEDS_TRIGGERS class_device_remove_file(led_cdev->class_dev, &class_device_attr_trigger); diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index 454fb09..b2438a0 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -183,13 +183,20 @@ int led_trigger_register(struct led_trig void led_trigger_register_simple(const char *name, struct led_trigger **tp) { struct led_trigger *trigger; + int err; trigger = kzalloc(sizeof(struct led_trigger), GFP_KERNEL); if (trigger) { trigger->name = name; - led_trigger_register(trigger); - } + err = led_trigger_register(trigger); + if (err < 0) + printk(KERN_WARNING "LED trigger %s failed to register" + " (%d)\n", name, err); + } else + printk(KERN_WARNING "LED trigger %s failed to register" + " (no memory)\n", name); + *tp = trigger; } @@ -215,7 +222,8 @@ void led_trigger_unregister(struct led_t void led_trigger_unregister_simple(struct led_trigger *trigger) { - led_trigger_unregister(trigger); + if (trigger) + led_trigger_unregister(trigger); kfree(trigger); } diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c new file mode 100644 index 0000000..47d90db --- /dev/null +++ b/drivers/leds/leds-gpio.c @@ -0,0 +1,199 @@ +/* + * LEDs driver for GPIOs + * + * Copyright (C) 2007 8D Technologies inc. + * Raphael Assenat + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include + +#include + +struct gpio_led_data { + struct led_classdev cdev; + unsigned gpio; + struct work_struct work; + u8 new_level; + u8 can_sleep; + u8 active_low; +}; + +static void gpio_led_work(struct work_struct *work) +{ + struct gpio_led_data *led_dat = + container_of(work, struct gpio_led_data, work); + + gpio_set_value_cansleep(led_dat->gpio, led_dat->new_level); +} + +static void gpio_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct gpio_led_data *led_dat = + container_of(led_cdev, struct gpio_led_data, cdev); + int level; + + if (value == LED_OFF) + level = 0; + else + level = 1; + + if (led_dat->active_low) + level = !level; + + /* setting GPIOs with I2C/etc requires a preemptible task context */ + if (led_dat->can_sleep) { + if (preempt_count()) { + led_dat->new_level = level; + schedule_work(&led_dat->work); + } else + gpio_set_value_cansleep(led_dat->gpio, level); + } else + gpio_set_value(led_dat->gpio, level); +} + +static int __init gpio_led_probe(struct platform_device *pdev) +{ + struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct gpio_led *cur_led; + struct gpio_led_data *leds_data, *led_dat; + int i, ret = 0; + + if (!pdata) + return -EBUSY; + + leds_data = kzalloc(sizeof(struct gpio_led_data) * pdata->num_leds, + GFP_KERNEL); + if (!leds_data) + return -ENOMEM; + + for (i = 0; i < pdata->num_leds; i++) { + cur_led = &pdata->leds[i]; + led_dat = &leds_data[i]; + + led_dat->cdev.name = cur_led->name; + led_dat->cdev.default_trigger = cur_led->default_trigger; + led_dat->gpio = cur_led->gpio; + led_dat->can_sleep = gpio_cansleep(cur_led->gpio); + led_dat->active_low = cur_led->active_low; + led_dat->cdev.brightness_set = gpio_led_set; + led_dat->cdev.brightness = cur_led->active_low ? LED_FULL : LED_OFF; + + ret = gpio_request(led_dat->gpio, led_dat->cdev.name); + if (ret < 0) + goto err; + + gpio_direction_output(led_dat->gpio, led_dat->active_low); + + ret = led_classdev_register(&pdev->dev, &led_dat->cdev); + if (ret < 0) { + gpio_free(led_dat->gpio); + goto err; + } + + INIT_WORK(&led_dat->work, gpio_led_work); + } + + platform_set_drvdata(pdev, leds_data); + + return 0; + +err: + if (i > 0) { + for (i = i - 1; i >= 0; i--) { + led_classdev_unregister(&leds_data[i].cdev); + gpio_free(leds_data[i].gpio); + } + } + + flush_scheduled_work(); + kfree(leds_data); + + return ret; +} + +static int __exit gpio_led_remove(struct platform_device *pdev) +{ + int i; + struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct gpio_led_data *leds_data; + + leds_data = platform_get_drvdata(pdev); + + for (i = 0; i < pdata->num_leds; i++) { + led_classdev_unregister(&leds_data[i].cdev); + gpio_free(leds_data[i].gpio); + } + + kfree(leds_data); + + return 0; +} + +#ifdef CONFIG_PM +static int gpio_led_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct gpio_led_data *leds_data; + int i; + + leds_data = platform_get_drvdata(pdev); + + for (i = 0; i < pdata->num_leds; i++) + led_classdev_suspend(&leds_data[i].cdev); + + return 0; +} + +static int gpio_led_resume(struct platform_device *pdev) +{ + struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct gpio_led_data *leds_data; + int i; + + leds_data = platform_get_drvdata(pdev); + + for (i = 0; i < pdata->num_leds; i++) + led_classdev_resume(&leds_data[i].cdev); + + return 0; +} +#else +#define gpio_led_suspend NULL +#define gpio_led_resume NULL +#endif + +static struct platform_driver gpio_led_driver = { + .remove = __exit_p(gpio_led_remove), + .suspend = gpio_led_suspend, + .resume = gpio_led_resume, + .driver = { + .name = "leds-gpio", + .owner = THIS_MODULE, + }, +}; + +static int __init gpio_led_init(void) +{ + return platform_driver_probe(&gpio_led_driver, gpio_led_probe); +} + +static void __exit gpio_led_exit(void) +{ + platform_driver_unregister(&gpio_led_driver); +} + +module_init(gpio_led_init); +module_exit(gpio_led_exit); + +MODULE_AUTHOR("Raphael Assenat "); +MODULE_DESCRIPTION("GPIO LED driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/leds.h b/include/linux/leds.h index 88afcef..059abfe 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -110,4 +110,18 @@ #else #define ledtrig_ide_activity() do {} while(0) #endif +/* For the leds-gpio driver */ +struct gpio_led { + const char *name; + char *default_trigger; + unsigned gpio; + u8 active_low; +}; + +struct gpio_led_platform_data { + int num_leds; + struct gpio_led *leds; +}; + + #endif /* __LINUX_LEDS_H_INCLUDED */