diff options
-rw-r--r-- | drivers/leds/Kconfig | 7 | ||||
-rw-r--r-- | drivers/leds/Makefile | 1 | ||||
-rw-r--r-- | drivers/leds/leds-atmel-pwm.c | 157 |
3 files changed, 165 insertions, 0 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 851a3b01781e..859814f62cb0 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig | |||
@@ -18,6 +18,13 @@ config LEDS_CLASS | |||
18 | 18 | ||
19 | comment "LED drivers" | 19 | comment "LED drivers" |
20 | 20 | ||
21 | config LEDS_ATMEL_PWM | ||
22 | tristate "LED Support using Atmel PWM outputs" | ||
23 | depends on LEDS_CLASS && ATMEL_PWM | ||
24 | help | ||
25 | This option enables support for LEDs driven using outputs | ||
26 | of the dedicated PWM controller found on newer Atmel SOCs. | ||
27 | |||
21 | config LEDS_CORGI | 28 | config LEDS_CORGI |
22 | tristate "LED Support for the Sharp SL-C7x0 series" | 29 | tristate "LED Support for the Sharp SL-C7x0 series" |
23 | depends on LEDS_CLASS && PXA_SHARP_C7xx | 30 | depends on LEDS_CLASS && PXA_SHARP_C7xx |
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index bc6afc8dcb27..84ced3b1a13d 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile | |||
@@ -5,6 +5,7 @@ obj-$(CONFIG_LEDS_CLASS) += led-class.o | |||
5 | obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o | 5 | obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o |
6 | 6 | ||
7 | # LED Platform Drivers | 7 | # LED Platform Drivers |
8 | obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o | ||
8 | obj-$(CONFIG_LEDS_CORGI) += leds-corgi.o | 9 | obj-$(CONFIG_LEDS_CORGI) += leds-corgi.o |
9 | obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o | 10 | obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o |
10 | obj-$(CONFIG_LEDS_SPITZ) += leds-spitz.o | 11 | obj-$(CONFIG_LEDS_SPITZ) += leds-spitz.o |
diff --git a/drivers/leds/leds-atmel-pwm.c b/drivers/leds/leds-atmel-pwm.c new file mode 100644 index 000000000000..af61f55571fe --- /dev/null +++ b/drivers/leds/leds-atmel-pwm.c | |||
@@ -0,0 +1,157 @@ | |||
1 | #include <linux/kernel.h> | ||
2 | #include <linux/platform_device.h> | ||
3 | #include <linux/leds.h> | ||
4 | #include <linux/io.h> | ||
5 | #include <linux/atmel_pwm.h> | ||
6 | |||
7 | |||
8 | struct pwmled { | ||
9 | struct led_classdev cdev; | ||
10 | struct pwm_channel pwmc; | ||
11 | struct gpio_led *desc; | ||
12 | u32 mult; | ||
13 | u8 active_low; | ||
14 | }; | ||
15 | |||
16 | |||
17 | /* | ||
18 | * For simplicity, we use "brightness" as if it were a linear function | ||
19 | * of PWM duty cycle. However, a logarithmic function of duty cycle is | ||
20 | * probably a better match for perceived brightness: two is half as bright | ||
21 | * as four, four is half as bright as eight, etc | ||
22 | */ | ||
23 | static void pwmled_brightness(struct led_classdev *cdev, enum led_brightness b) | ||
24 | { | ||
25 | struct pwmled *led; | ||
26 | |||
27 | /* update the duty cycle for the *next* period */ | ||
28 | led = container_of(cdev, struct pwmled, cdev); | ||
29 | pwm_channel_writel(&led->pwmc, PWM_CUPD, led->mult * (unsigned) b); | ||
30 | } | ||
31 | |||
32 | /* | ||
33 | * NOTE: we reuse the platform_data structure of GPIO leds, | ||
34 | * but repurpose its "gpio" number as a PWM channel number. | ||
35 | */ | ||
36 | static int __init pwmled_probe(struct platform_device *pdev) | ||
37 | { | ||
38 | const struct gpio_led_platform_data *pdata; | ||
39 | struct pwmled *leds; | ||
40 | unsigned i; | ||
41 | int status; | ||
42 | |||
43 | pdata = pdev->dev.platform_data; | ||
44 | if (!pdata || pdata->num_leds < 1) | ||
45 | return -ENODEV; | ||
46 | |||
47 | leds = kcalloc(pdata->num_leds, sizeof(*leds), GFP_KERNEL); | ||
48 | if (!leds) | ||
49 | return -ENOMEM; | ||
50 | |||
51 | for (i = 0; i < pdata->num_leds; i++) { | ||
52 | struct pwmled *led = leds + i; | ||
53 | const struct gpio_led *dat = pdata->leds + i; | ||
54 | u32 tmp; | ||
55 | |||
56 | led->cdev.name = dat->name; | ||
57 | led->cdev.brightness = LED_OFF; | ||
58 | led->cdev.brightness_set = pwmled_brightness; | ||
59 | led->cdev.default_trigger = dat->default_trigger; | ||
60 | |||
61 | led->active_low = dat->active_low; | ||
62 | |||
63 | status = pwm_channel_alloc(dat->gpio, &led->pwmc); | ||
64 | if (status < 0) | ||
65 | goto err; | ||
66 | |||
67 | /* | ||
68 | * Prescale clock by 2^x, so PWM counts in low MHz. | ||
69 | * Start each cycle with the LED active, so increasing | ||
70 | * the duty cycle gives us more time on (== brighter). | ||
71 | */ | ||
72 | tmp = 5; | ||
73 | if (!led->active_low) | ||
74 | tmp |= PWM_CPR_CPOL; | ||
75 | pwm_channel_writel(&led->pwmc, PWM_CMR, tmp); | ||
76 | |||
77 | /* | ||
78 | * Pick a period so PWM cycles at 100+ Hz; and a multiplier | ||
79 | * for scaling duty cycle: brightness * mult. | ||
80 | */ | ||
81 | tmp = (led->pwmc.mck / (1 << 5)) / 100; | ||
82 | tmp /= 255; | ||
83 | led->mult = tmp; | ||
84 | pwm_channel_writel(&led->pwmc, PWM_CDTY, | ||
85 | led->cdev.brightness * 255); | ||
86 | pwm_channel_writel(&led->pwmc, PWM_CPRD, | ||
87 | LED_FULL * tmp); | ||
88 | |||
89 | pwm_channel_enable(&led->pwmc); | ||
90 | |||
91 | /* Hand it over to the LED framework */ | ||
92 | status = led_classdev_register(&pdev->dev, &led->cdev); | ||
93 | if (status < 0) { | ||
94 | pwm_channel_free(&led->pwmc); | ||
95 | goto err; | ||
96 | } | ||
97 | } | ||
98 | |||
99 | platform_set_drvdata(pdev, leds); | ||
100 | return 0; | ||
101 | |||
102 | err: | ||
103 | if (i > 0) { | ||
104 | for (i = i - 1; i >= 0; i--) { | ||
105 | led_classdev_unregister(&leds[i].cdev); | ||
106 | pwm_channel_free(&leds[i].pwmc); | ||
107 | } | ||
108 | } | ||
109 | kfree(leds); | ||
110 | |||
111 | return status; | ||
112 | } | ||
113 | |||
114 | static int __exit pwmled_remove(struct platform_device *pdev) | ||
115 | { | ||
116 | const struct gpio_led_platform_data *pdata; | ||
117 | struct pwmled *leds; | ||
118 | unsigned i; | ||
119 | |||
120 | pdata = pdev->dev.platform_data; | ||
121 | leds = platform_get_drvdata(pdev); | ||
122 | |||
123 | for (i = 0; i < pdata->num_leds; i++) { | ||
124 | struct pwmled *led = leds + i; | ||
125 | |||
126 | led_classdev_unregister(&led->cdev); | ||
127 | pwm_channel_free(&led->pwmc); | ||
128 | } | ||
129 | |||
130 | kfree(leds); | ||
131 | platform_set_drvdata(pdev, NULL); | ||
132 | return 0; | ||
133 | } | ||
134 | |||
135 | static struct platform_driver pwmled_driver = { | ||
136 | .driver = { | ||
137 | .name = "leds-atmel-pwm", | ||
138 | .owner = THIS_MODULE, | ||
139 | }, | ||
140 | /* REVISIT add suspend() and resume() methods */ | ||
141 | .remove = __exit_p(pwmled_remove), | ||
142 | }; | ||
143 | |||
144 | static int __init modinit(void) | ||
145 | { | ||
146 | return platform_driver_probe(&pwmled_driver, pwmled_probe); | ||
147 | } | ||
148 | module_init(modinit); | ||
149 | |||
150 | static void __exit modexit(void) | ||
151 | { | ||
152 | platform_driver_unregister(&pwmled_driver); | ||
153 | } | ||
154 | module_exit(modexit); | ||
155 | |||
156 | MODULE_DESCRIPTION("Driver for LEDs with PWM-controlled brightness"); | ||
157 | MODULE_LICENSE("GPL"); | ||