Commit 021a852f authored by Juha Yrjola's avatar Juha Yrjola

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: default avatarJuha Yrjola <juha.yrjola@solidboot.com>
parent 02ac8959
...@@ -63,6 +63,13 @@ config LEDS_S3C24XX ...@@ -63,6 +63,13 @@ config LEDS_S3C24XX
This option enables support for LEDs connected to GPIO lines This option enables support for LEDs connected to GPIO lines
on Samsung S3C24XX series CPUs, such as the S3C2410 and S3C2440. 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" comment "LED Triggers"
config LEDS_TRIGGERS config LEDS_TRIGGERS
......
...@@ -11,6 +11,7 @@ obj-$(CONFIG_LEDS_SPITZ) += leds-spitz.o ...@@ -11,6 +11,7 @@ obj-$(CONFIG_LEDS_SPITZ) += leds-spitz.o
obj-$(CONFIG_LEDS_IXP4XX) += leds-ixp4xx-gpio.o obj-$(CONFIG_LEDS_IXP4XX) += leds-ixp4xx-gpio.o
obj-$(CONFIG_LEDS_TOSA) += leds-tosa.o obj-$(CONFIG_LEDS_TOSA) += leds-tosa.o
obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o
obj-$(CONFIG_LEDS_OMAP_PWM) += leds-omap-pwm.o
# LED Triggers # LED Triggers
obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o
......
/* 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/config.h>
#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 <asm/delay.h>
#include <asm/arch/board.h>
#include <asm/arch/dmtimer.h>
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");
...@@ -109,6 +109,13 @@ struct omap_fbmem_config { ...@@ -109,6 +109,13 @@ struct omap_fbmem_config {
u32 size; 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: /* Cover:
* high -> closed * high -> closed
* low -> open * low -> open
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment