From: Juha Yrjola Date: Mon, 24 Jul 2006 13:46:26 +0000 (+0300) Subject: LED: Add driver for LEDs connected to OMAP PWM X-Git-Tag: v2.6.17-omap2~24 X-Git-Url: http://pilppa.com/gitweb/?a=commitdiff_plain;h=021a852f987ba517c2adeca9fe75c772df60cace;p=linux-2.6-omap-h63xx.git LED: Add driver for LEDs connected to OMAP PWM The PWM block on the TI OMAP CPU can drive some external GPIOs. LEDs can be connected to these GPIOs. Signed-off-by: Juha Yrjola --- diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 626506234b7..03865e7a3c0 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -63,6 +63,13 @@ config LEDS_S3C24XX This option enables support for LEDs connected to GPIO lines on Samsung S3C24XX series CPUs, such as the S3C2410 and S3C2440. +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. + comment "LED Triggers" config LEDS_TRIGGERS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 40f042633bf..fd7313191fc 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_LEDS_SPITZ) += leds-spitz.o obj-$(CONFIG_LEDS_IXP4XX) += leds-ixp4xx-gpio.o obj-$(CONFIG_LEDS_TOSA) += leds-tosa.o obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o +obj-$(CONFIG_LEDS_OMAP_PWM) += leds-omap-pwm.o # LED Triggers obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o diff --git a/drivers/leds/leds-omap-pwm.c b/drivers/leds/leds-omap-pwm.c new file mode 100644 index 00000000000..32f8cf4702d --- /dev/null +++ b/drivers/leds/leds-omap-pwm.c @@ -0,0 +1,330 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct omap_pwm_led { + struct led_classdev cdev; + 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; +}; + +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 void omap_pwm_led_set_blink(struct omap_pwm_led *led) +{ + 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, -cmp_reg); + 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_set_source(led->intensity_timer, OMAP_TIMER_SRC_SYS_CLK); + + /* 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_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); + 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); + + if (value != LED_OFF) { + omap_pwm_led_power_on(led); + omap_pwm_led_set_pwm_cycle(led, value); + } else { + omap_pwm_led_power_off(led); + } +} + +static ssize_t omap_pwm_led_on_period_show(struct class_device *cdev, char *buf) +{ + struct led_classdev *led_cdev = class_get_devdata(cdev); + 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 class_device *cdev, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = class_get_devdata(cdev); + 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 class_device *cdev, char *buf) +{ + struct led_classdev *led_cdev = class_get_devdata(cdev); + 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 class_device *cdev, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = class_get_devdata(cdev); + 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 CLASS_DEVICE_ATTR(on_period, 0644, omap_pwm_led_on_period_show, + omap_pwm_led_on_period_store); +static CLASS_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; + + 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; + } + + 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_blink; + } + + class_device_create_file(led->cdev.class_dev, + &class_device_attr_on_period); + class_device_create_file(led->cdev.class_dev, + &class_device_attr_off_period); + } + + return 0; + +error_blink: + 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); + + class_device_remove_file(led->cdev.class_dev, + &class_device_attr_on_period); + class_device_remove_file(led->cdev.class_dev, + &class_device_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/include/asm-arm/arch-omap/board.h b/include/asm-arm/arch-omap/board.h index 47e762131f5..c3f42542d90 100644 --- a/include/asm-arm/arch-omap/board.h +++ b/include/asm-arm/arch-omap/board.h @@ -109,6 +109,13 @@ struct omap_fbmem_config { u32 size; }; +struct omap_pwm_led_platform_data { + const char *name; + int intensity_timer; + int blink_timer; + void (*set_power)(struct omap_pwm_led_platform_data *self, int on_off); +}; + /* Cover: * high -> closed * low -> open