From: Felipe Balbi <felipe.balbi@xxxxxxxxx> Signed-off-by: Felipe Balbi <felipe.balbi@xxxxxxxxx> --- drivers/leds/Kconfig | 21 +++ drivers/leds/Makefile | 2 + drivers/leds/leds-omap-pwm.c | 376 ++++++++++++++++++++++++++++++++++++++++++ drivers/leds/leds-omap.c | 135 +++++++++++++++ 4 files changed, 534 insertions(+), 0 deletions(-) create mode 100644 drivers/leds/leds-omap-pwm.c create mode 100644 drivers/leds/leds-omap.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 9556262..6a2f441 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -77,6 +77,27 @@ config LEDS_WRAP help This option enables support for the PCEngines WRAP programmable LEDs. +config LEDS_OMAP_DEBUG + boolean "LED Support for OMAP debug board LEDs" + depends on LEDS_CLASS=y && ARCH_OMAP + help + Enables support for the LEDs on the debug board used with OMAP + reference boards like H2/H3/H4 and Perseus2. Up to six of these + may be claimed by the original ARM debug LED API. + +config LEDS_OMAP + tristate "LED Support for OMAP GPIO LEDs" + depends on LEDS_CLASS && ARCH_OMAP + help + This option enables support for the LEDs on OMAP processors. + +config LEDS_OMAP_PWM + tristate "LED Support for OMAP PWM-controlled LEDs" + depends on LEDS_CLASS && ARCH_OMAP && OMAP_DM_TIMER + help + This options enables support for LEDs connected to GPIO lines + controlled by a PWM timer on OMAP CPUs. + config LEDS_H1940 tristate "LED Support for iPAQ H1940 device" depends on LEDS_CLASS && ARCH_H1940 diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index ff7982b..fd6316e 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -13,6 +13,8 @@ obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o obj-$(CONFIG_LEDS_AMS_DELTA) += leds-ams-delta.o obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o +obj-$(CONFIG_LEDS_OMAP) += leds-omap.o +obj-$(CONFIG_LEDS_OMAP_PWM) += leds-omap-pwm.o obj-$(CONFIG_LEDS_H1940) += leds-h1940.o obj-$(CONFIG_LEDS_COBALT_QUBE) += leds-cobalt-qube.o obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o diff --git a/drivers/leds/leds-omap-pwm.c b/drivers/leds/leds-omap-pwm.c new file mode 100644 index 0000000..57eb383 --- /dev/null +++ b/drivers/leds/leds-omap-pwm.c @@ -0,0 +1,376 @@ +/* drivers/leds/leds-omap_pwm.c + * + * Driver to blink LEDs using OMAP PWM timers + * + * Copyright (C) 2006 Nokia Corporation + * Author: Timo Teras + * + * 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 <linux/kernel.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/ctype.h> +#include <linux/sched.h> +#include <asm/delay.h> +#include <mach/board.h> +#include <mach/dmtimer.h> + +struct omap_pwm_led { + struct led_classdev cdev; + struct work_struct work; + struct omap_pwm_led_platform_data *pdata; + struct omap_dm_timer *intensity_timer; + struct omap_dm_timer *blink_timer; + int powered; + unsigned int on_period, off_period; + enum led_brightness brightness; +}; + +static inline struct omap_pwm_led *pdev_to_omap_pwm_led(struct platform_device *pdev) +{ + return platform_get_drvdata(pdev); +} + +static inline struct omap_pwm_led *cdev_to_omap_pwm_led(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct omap_pwm_led, cdev); +} + +static inline struct omap_pwm_led *work_to_omap_pwm_led(struct work_struct *work) +{ + return container_of(work, struct omap_pwm_led, work); +} + +static void omap_pwm_led_set_blink(struct omap_pwm_led *led) +{ + if (!led->powered) + return; + + if (led->on_period != 0 && led->off_period != 0) { + unsigned long load_reg, cmp_reg; + + load_reg = 32768 * (led->on_period + led->off_period) / 1000; + cmp_reg = 32768 * led->on_period / 1000; + + omap_dm_timer_stop(led->blink_timer); + omap_dm_timer_set_load(led->blink_timer, 1, -load_reg); + omap_dm_timer_set_match(led->blink_timer, 1, -cmp_reg); + omap_dm_timer_set_pwm(led->blink_timer, 1, 1, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + omap_dm_timer_write_counter(led->blink_timer, -2); + omap_dm_timer_start(led->blink_timer); + } else { + omap_dm_timer_set_pwm(led->blink_timer, 1, 1, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + omap_dm_timer_stop(led->blink_timer); + } +} + +static void omap_pwm_led_power_on(struct omap_pwm_led *led) +{ + if (led->powered) + return; + led->powered = 1; + + /* Select clock */ + omap_dm_timer_enable(led->intensity_timer); + omap_dm_timer_set_source(led->intensity_timer, OMAP_TIMER_SRC_32_KHZ); + + /* Turn voltage on */ + if (led->pdata->set_power != NULL) + led->pdata->set_power(led->pdata, 1); + + /* Enable PWM timers */ + if (led->blink_timer != NULL) { + omap_dm_timer_enable(led->blink_timer); + omap_dm_timer_set_source(led->blink_timer, + OMAP_TIMER_SRC_32_KHZ); + omap_pwm_led_set_blink(led); + } + + omap_dm_timer_set_load(led->intensity_timer, 1, 0xffffff00); +} + +static void omap_pwm_led_power_off(struct omap_pwm_led *led) +{ + if (!led->powered) + return; + led->powered = 0; + + /* Everything off */ + omap_dm_timer_stop(led->intensity_timer); + omap_dm_timer_disable(led->intensity_timer); + + if (led->blink_timer != NULL) { + omap_dm_timer_stop(led->blink_timer); + omap_dm_timer_disable(led->blink_timer); + } + + if (led->pdata->set_power != NULL) + led->pdata->set_power(led->pdata, 0); +} + +static void omap_pwm_led_set_pwm_cycle(struct omap_pwm_led *led, int cycle) +{ + int n; + + if (cycle == 0) + n = 0xff; + else n = cycle - 1; + + if (cycle == LED_FULL) { + omap_dm_timer_set_pwm(led->intensity_timer, 1, 1, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + omap_dm_timer_stop(led->intensity_timer); + } else { + omap_dm_timer_set_pwm(led->intensity_timer, 0, 1, + OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE); + omap_dm_timer_set_match(led->intensity_timer, 1, + (0xffffff00) | cycle); + omap_dm_timer_start(led->intensity_timer); + } +} + +static void omap_pwm_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + + led->brightness = value; + schedule_work(&led->work); +} + +static void omap_pwm_led_work(struct work_struct *work) +{ + struct omap_pwm_led *led = work_to_omap_pwm_led(work); + + if (led->brightness != LED_OFF) { + omap_pwm_led_power_on(led); + omap_pwm_led_set_pwm_cycle(led, led->brightness); + } else { + omap_pwm_led_power_off(led); + } +} + +static ssize_t omap_pwm_led_on_period_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + + return sprintf(buf, "%u\n", led->on_period) + 1; +} + +static ssize_t omap_pwm_led_on_period_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + int ret = -EINVAL; + unsigned long val; + char *after; + size_t count; + + val = simple_strtoul(buf, &after, 10); + count = after - buf; + if (*after && isspace(*after)) + count++; + + if (count == size) { + led->on_period = val; + omap_pwm_led_set_blink(led); + ret = count; + } + + return ret; +} + +static ssize_t omap_pwm_led_off_period_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + + return sprintf(buf, "%u\n", led->off_period) + 1; +} + +static ssize_t omap_pwm_led_off_period_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev); + int ret = -EINVAL; + unsigned long val; + char *after; + size_t count; + + val = simple_strtoul(buf, &after, 10); + count = after - buf; + if (*after && isspace(*after)) + count++; + + if (count == size) { + led->off_period = val; + omap_pwm_led_set_blink(led); + ret = count; + } + + return ret; +} + +static DEVICE_ATTR(on_period, 0644, omap_pwm_led_on_period_show, + omap_pwm_led_on_period_store); +static DEVICE_ATTR(off_period, 0644, omap_pwm_led_off_period_show, + omap_pwm_led_off_period_store); + +static int omap_pwm_led_probe(struct platform_device *pdev) +{ + struct omap_pwm_led_platform_data *pdata = pdev->dev.platform_data; + struct omap_pwm_led *led; + int ret; + + led = kzalloc(sizeof(struct omap_pwm_led), GFP_KERNEL); + if (led == NULL) { + dev_err(&pdev->dev, "No memory for device\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, led); + led->cdev.brightness_set = omap_pwm_led_set; + led->cdev.default_trigger = NULL; + led->cdev.name = pdata->name; + led->pdata = pdata; + led->brightness = LED_OFF; + INIT_WORK(&led->work, omap_pwm_led_work); + + dev_info(&pdev->dev, "OMAP PWM LED (%s) at GP timer %d/%d\n", + pdata->name, pdata->intensity_timer, pdata->blink_timer); + + /* register our new led device */ + ret = led_classdev_register(&pdev->dev, &led->cdev); + if (ret < 0) { + dev_err(&pdev->dev, "led_classdev_register failed\n"); + goto error_classdev; + } + + /* get related dm timers */ + led->intensity_timer = omap_dm_timer_request_specific(pdata->intensity_timer); + if (led->intensity_timer == NULL) { + dev_err(&pdev->dev, "failed to request intensity pwm timer\n"); + ret = -ENODEV; + goto error_intensity; + } + omap_dm_timer_disable(led->intensity_timer); + + if (pdata->blink_timer != 0) { + led->blink_timer = omap_dm_timer_request_specific(pdata->blink_timer); + if (led->blink_timer == NULL) { + dev_err(&pdev->dev, "failed to request blinking pwm timer\n"); + ret = -ENODEV; + goto error_blink1; + } + omap_dm_timer_disable(led->blink_timer); + + ret = device_create_file(led->cdev.dev, + &dev_attr_on_period); + if(ret) + goto error_blink2; + + ret = device_create_file(led->cdev.dev, + &dev_attr_off_period); + if(ret) + goto error_blink3; + + } + + return 0; + +error_blink3: + device_remove_file(led->cdev.dev, + &dev_attr_on_period); +error_blink2: + dev_err(&pdev->dev, "failed to create device file(s)\n"); +error_blink1: + omap_dm_timer_free(led->intensity_timer); +error_intensity: + led_classdev_unregister(&led->cdev); +error_classdev: + kfree(led); + return ret; +} + +static int omap_pwm_led_remove(struct platform_device *pdev) +{ + struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev); + + device_remove_file(led->cdev.dev, + &dev_attr_on_period); + device_remove_file(led->cdev.dev, + &dev_attr_off_period); + led_classdev_unregister(&led->cdev); + + omap_pwm_led_set(&led->cdev, LED_OFF); + if (led->blink_timer != NULL) + omap_dm_timer_free(led->blink_timer); + omap_dm_timer_free(led->intensity_timer); + kfree(led); + + return 0; +} + +#ifdef CONFIG_PM +static int omap_pwm_led_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev); + + led_classdev_suspend(&led->cdev); + return 0; +} + +static int omap_pwm_led_resume(struct platform_device *pdev) +{ + struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev); + + led_classdev_resume(&led->cdev); + return 0; +} +#else +#define omap_pwm_led_suspend NULL +#define omap_pwm_led_resume NULL +#endif + +static struct platform_driver omap_pwm_led_driver = { + .probe = omap_pwm_led_probe, + .remove = omap_pwm_led_remove, + .suspend = omap_pwm_led_suspend, + .resume = omap_pwm_led_resume, + .driver = { + .name = "omap_pwm_led", + .owner = THIS_MODULE, + }, +}; + +static int __init omap_pwm_led_init(void) +{ + return platform_driver_register(&omap_pwm_led_driver); +} + +static void __exit omap_pwm_led_exit(void) +{ + platform_driver_unregister(&omap_pwm_led_driver); +} + +module_init(omap_pwm_led_init); +module_exit(omap_pwm_led_exit); + +MODULE_AUTHOR("Timo Teras"); +MODULE_DESCRIPTION("OMAP PWM LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-omap.c b/drivers/leds/leds-omap.c new file mode 100644 index 0000000..5c14c4b --- /dev/null +++ b/drivers/leds/leds-omap.c @@ -0,0 +1,135 @@ +/* drivers/leds/leds-omap.c + * + * (C) 2006 Samsung Electronics + * Kyungmin Park <kyungmin.park@xxxxxxxxxxx> + * + * OMAP - LEDs GPIO driver + * + * 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 <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> + +#include <mach/gpio.h> +#include <mach/hardware.h> +#include <mach/led.h> + +/* our context */ + +static void omap_set_led_gpio(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct omap_led_config *led_dev; + + led_dev = container_of(led_cdev, struct omap_led_config, cdev); + + if (value) + omap_set_gpio_dataout(led_dev->gpio, 1); + else + omap_set_gpio_dataout(led_dev->gpio, 0); +} + +static void omap_configure_led_gpio(int gpio) +{ + if (omap_request_gpio(gpio) < 0) { + printk(KERN_ERR "Failed to request GPIO%d for LEDs\n", gpio); + return; + } + omap_set_gpio_direction(gpio, 0); /* OUT */ +} + +static int omap_led_probe(struct platform_device *dev) +{ + struct omap_led_platform_data *pdata = dev->dev.platform_data; + struct omap_led_config *leds = pdata->leds; + int i, ret = 0; + + for (i = 0; ret >= 0 && i < pdata->nr_leds; i++) { + omap_configure_led_gpio(leds[i].gpio); + if (!leds[i].cdev.brightness_set) + leds[i].cdev.brightness_set = omap_set_led_gpio; + + ret = led_classdev_register(&dev->dev, &leds[i].cdev); + } + + if (ret < 0 && i > 1) { + for (i = i - 2; i >= 0; i--) + led_classdev_unregister(&leds[i].cdev); + } + + return ret; +} + +static int omap_led_remove(struct platform_device *dev) +{ + struct omap_led_platform_data *pdata = dev->dev.platform_data; + struct omap_led_config *leds = pdata->leds; + int i; + + for (i = 0; i < pdata->nr_leds; i++) + led_classdev_unregister(&leds[i].cdev); + + return 0; +} + +#ifdef CONFIG_PM +static int omap_led_suspend(struct platform_device *dev, pm_message_t state) +{ + struct omap_led_platform_data *pdata = dev->dev.platform_data; + struct omap_led_config *leds = pdata->leds; + int i; + + for (i = 0; i < pdata->nr_leds; i++) + led_classdev_suspend(&leds[i].cdev); + + return 0; +} + +static int omap_led_resume(struct platform_device *dev) +{ + struct omap_led_platform_data *pdata = dev->dev.platform_data; + struct omap_led_config *leds = pdata->leds; + int i; + + for (i = 0; i < pdata->nr_leds; i++) + led_classdev_resume(&leds[i].cdev); + + return 0; +} +#else +#define omap_led_suspend NULL +#define omap_led_resume NULL +#endif + +static struct platform_driver omap_led_driver = { + .probe = omap_led_probe, + .remove = omap_led_remove, + .suspend = omap_led_suspend, + .resume = omap_led_resume, + .driver = { + .name = "omap-led", + .owner = THIS_MODULE, + }, +}; + +static int __init omap_led_init(void) +{ + return platform_driver_register(&omap_led_driver); +} + +static void __exit omap_led_exit(void) +{ + platform_driver_unregister(&omap_led_driver); +} + +module_init(omap_led_init); +module_exit(omap_led_exit); + +MODULE_AUTHOR("Kyungmin Park<kyungmin.park@xxxxxxxxxxx>"); +MODULE_DESCRIPTION("OMAP LED driver"); +MODULE_LICENSE("GPL"); -- 1.6.0.1.141.g445ca -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html