Blame view
drivers/leds/leds-gpio.c
7.42 KB
22e03f3b5 leds: Add generic... |
1 2 3 4 5 |
/* * LEDs driver for GPIOs * * Copyright (C) 2007 8D Technologies inc. * Raphael Assenat <raph@8d.com> |
a7d878af9 leds: Add openfir... |
6 |
* Copyright (C) 2008 Freescale Semiconductor, Inc. |
22e03f3b5 leds: Add generic... |
7 8 9 10 11 12 13 14 15 16 |
* * 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> |
a314c5c00 leds/leds-gpio: m... |
17 18 |
#include <linux/of_platform.h> #include <linux/of_gpio.h> |
5a0e3ad6a include cleanup: ... |
19 |
#include <linux/slab.h> |
00852279a leds: Teach leds-... |
20 |
#include <linux/workqueue.h> |
54f4dedb5 drivers/leds: Add... |
21 |
#include <linux/module.h> |
00852279a leds: Teach leds-... |
22 |
|
22e03f3b5 leds: Add generic... |
23 24 25 26 27 |
#include <asm/gpio.h> struct gpio_led_data { struct led_classdev cdev; unsigned gpio; |
00852279a leds: Teach leds-... |
28 29 30 |
struct work_struct work; u8 new_level; u8 can_sleep; |
22e03f3b5 leds: Add generic... |
31 |
u8 active_low; |
2146325df leds: leds-gpio: ... |
32 33 |
u8 blinking; int (*platform_gpio_blink_set)(unsigned gpio, int state, |
ca3259b36 leds: enable supp... |
34 |
unsigned long *delay_on, unsigned long *delay_off); |
22e03f3b5 leds: Add generic... |
35 |
}; |
00852279a leds: Teach leds-... |
36 37 38 39 |
static void gpio_led_work(struct work_struct *work) { struct gpio_led_data *led_dat = container_of(work, struct gpio_led_data, work); |
2146325df leds: leds-gpio: ... |
40 41 42 43 44 45 46 |
if (led_dat->blinking) { led_dat->platform_gpio_blink_set(led_dat->gpio, led_dat->new_level, NULL, NULL); led_dat->blinking = 0; } else gpio_set_value_cansleep(led_dat->gpio, led_dat->new_level); |
00852279a leds: Teach leds-... |
47 |
} |
22e03f3b5 leds: Add generic... |
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
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; |
306dd85c1 leds: Remove inco... |
63 64 65 66 |
/* Setting GPIOs with I2C/etc requires a task context, and we don't * seem to have a reliable way to know if we're already in one; so * let's just assume the worst. */ |
00852279a leds: Teach leds-... |
67 |
if (led_dat->can_sleep) { |
306dd85c1 leds: Remove inco... |
68 69 |
led_dat->new_level = level; schedule_work(&led_dat->work); |
2146325df leds: leds-gpio: ... |
70 71 72 73 74 75 76 77 |
} else { if (led_dat->blinking) { led_dat->platform_gpio_blink_set(led_dat->gpio, level, NULL, NULL); led_dat->blinking = 0; } else gpio_set_value(led_dat->gpio, level); } |
22e03f3b5 leds: Add generic... |
78 |
} |
ca3259b36 leds: enable supp... |
79 80 81 82 83 |
static int gpio_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { struct gpio_led_data *led_dat = container_of(led_cdev, struct gpio_led_data, cdev); |
2146325df leds: leds-gpio: ... |
84 85 86 |
led_dat->blinking = 1; return led_dat->platform_gpio_blink_set(led_dat->gpio, GPIO_LED_BLINK, delay_on, delay_off); |
ca3259b36 leds: enable supp... |
87 |
} |
a7d878af9 leds: Add openfir... |
88 89 |
static int __devinit create_gpio_led(const struct gpio_led *template, struct gpio_led_data *led_dat, struct device *parent, |
2146325df leds: leds-gpio: ... |
90 |
int (*blink_set)(unsigned, int, unsigned long *, unsigned long *)) |
a7d878af9 leds: Add openfir... |
91 |
{ |
ed88bae69 leds: Add options... |
92 |
int ret, state; |
a7d878af9 leds: Add openfir... |
93 |
|
0b4634fce leds-gpio: fix po... |
94 |
led_dat->gpio = -1; |
d379ee8ac leds: just ignore... |
95 96 |
/* skip leds that aren't available */ if (!gpio_is_valid(template->gpio)) { |
2fea09222 leds: gpio-leds: ... |
97 98 |
printk(KERN_INFO "Skipping unavailable LED gpio %d (%s) ", |
d379ee8ac leds: just ignore... |
99 |
template->gpio, template->name); |
ac15e9509 leds: just ignore... |
100 |
return 0; |
d379ee8ac leds: just ignore... |
101 |
} |
a7d878af9 leds: Add openfir... |
102 103 104 105 106 107 108 109 110 |
ret = gpio_request(template->gpio, template->name); if (ret < 0) return ret; led_dat->cdev.name = template->name; led_dat->cdev.default_trigger = template->default_trigger; led_dat->gpio = template->gpio; led_dat->can_sleep = gpio_cansleep(template->gpio); led_dat->active_low = template->active_low; |
2146325df leds: leds-gpio: ... |
111 |
led_dat->blinking = 0; |
a7d878af9 leds: Add openfir... |
112 113 114 115 116 |
if (blink_set) { led_dat->platform_gpio_blink_set = blink_set; led_dat->cdev.blink_set = gpio_blink_set; } led_dat->cdev.brightness_set = gpio_led_set; |
ed88bae69 leds: Add options... |
117 |
if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) |
dabc69c24 drivers/leds/leds... |
118 |
state = !!gpio_get_value_cansleep(led_dat->gpio) ^ led_dat->active_low; |
ed88bae69 leds: Add options... |
119 120 121 |
else state = (template->default_state == LEDS_GPIO_DEFSTATE_ON); led_dat->cdev.brightness = state ? LED_FULL : LED_OFF; |
defb512d2 leds: Add suspend... |
122 123 |
if (!template->retain_state_suspended) led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; |
a7d878af9 leds: Add openfir... |
124 |
|
ed88bae69 leds: Add options... |
125 |
ret = gpio_direction_output(led_dat->gpio, led_dat->active_low ^ state); |
a7d878af9 leds: Add openfir... |
126 127 |
if (ret < 0) goto err; |
2146325df leds: leds-gpio: ... |
128 |
|
a7d878af9 leds: Add openfir... |
129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
INIT_WORK(&led_dat->work, gpio_led_work); ret = led_classdev_register(parent, &led_dat->cdev); if (ret < 0) goto err; return 0; err: gpio_free(led_dat->gpio); return ret; } static void delete_gpio_led(struct gpio_led_data *led) { |
d379ee8ac leds: just ignore... |
143 144 |
if (!gpio_is_valid(led->gpio)) return; |
a7d878af9 leds: Add openfir... |
145 146 147 148 |
led_classdev_unregister(&led->cdev); cancel_work_sync(&led->work); gpio_free(led->gpio); } |
a314c5c00 leds/leds-gpio: m... |
149 150 151 152 |
struct gpio_leds_priv { int num_leds; struct gpio_led_data leds[]; }; |
22e03f3b5 leds: Add generic... |
153 |
|
a314c5c00 leds/leds-gpio: m... |
154 |
static inline int sizeof_gpio_leds_priv(int num_leds) |
22e03f3b5 leds: Add generic... |
155 |
{ |
a314c5c00 leds/leds-gpio: m... |
156 157 |
return sizeof(struct gpio_leds_priv) + (sizeof(struct gpio_led_data) * num_leds); |
22e03f3b5 leds: Add generic... |
158 |
} |
a7d878af9 leds: Add openfir... |
159 |
/* Code to create from OpenFirmware platform devices */ |
2bcc7ed5b leds: remove conf... |
160 |
#ifdef CONFIG_OF_GPIO |
a314c5c00 leds/leds-gpio: m... |
161 |
static struct gpio_leds_priv * __devinit gpio_leds_create_of(struct platform_device *pdev) |
a7d878af9 leds: Add openfir... |
162 |
{ |
a314c5c00 leds/leds-gpio: m... |
163 164 |
struct device_node *np = pdev->dev.of_node, *child; struct gpio_leds_priv *priv; |
a7d878af9 leds: Add openfir... |
165 |
int count = 0, ret; |
a314c5c00 leds/leds-gpio: m... |
166 |
/* count LEDs in this device, so we know how much to allocate */ |
a7d878af9 leds: Add openfir... |
167 168 169 |
for_each_child_of_node(np, child) count++; if (!count) |
a314c5c00 leds/leds-gpio: m... |
170 |
return NULL; |
a7d878af9 leds: Add openfir... |
171 |
|
a314c5c00 leds/leds-gpio: m... |
172 173 174 |
priv = kzalloc(sizeof_gpio_leds_priv(count), GFP_KERNEL); if (!priv) return NULL; |
a7d878af9 leds: Add openfir... |
175 |
|
a7d878af9 leds: Add openfir... |
176 |
for_each_child_of_node(np, child) { |
0493a4ff1 leds-gpio: fix de... |
177 |
struct gpio_led led = {}; |
a7d878af9 leds: Add openfir... |
178 |
enum of_gpio_flags flags; |
ed88bae69 leds: Add options... |
179 |
const char *state; |
a7d878af9 leds: Add openfir... |
180 181 182 183 184 185 |
led.gpio = of_get_gpio_flags(child, 0, &flags); led.active_low = flags & OF_GPIO_ACTIVE_LOW; led.name = of_get_property(child, "label", NULL) ? : child->name; led.default_trigger = of_get_property(child, "linux,default-trigger", NULL); |
ed88bae69 leds: Add options... |
186 187 188 189 |
state = of_get_property(child, "default-state", NULL); if (state) { if (!strcmp(state, "keep")) led.default_state = LEDS_GPIO_DEFSTATE_KEEP; |
a314c5c00 leds/leds-gpio: m... |
190 |
else if (!strcmp(state, "on")) |
ed88bae69 leds: Add options... |
191 192 193 194 |
led.default_state = LEDS_GPIO_DEFSTATE_ON; else led.default_state = LEDS_GPIO_DEFSTATE_OFF; } |
a7d878af9 leds: Add openfir... |
195 |
|
a314c5c00 leds/leds-gpio: m... |
196 197 |
ret = create_gpio_led(&led, &priv->leds[priv->num_leds++], &pdev->dev, NULL); |
a7d878af9 leds: Add openfir... |
198 199 200 201 202 |
if (ret < 0) { of_node_put(child); goto err; } } |
a314c5c00 leds/leds-gpio: m... |
203 |
return priv; |
a7d878af9 leds: Add openfir... |
204 205 |
err: |
a314c5c00 leds/leds-gpio: m... |
206 207 208 209 210 |
for (count = priv->num_leds - 2; count >= 0; count--) delete_gpio_led(&priv->leds[count]); kfree(priv); return NULL; } |
a7d878af9 leds: Add openfir... |
211 |
|
a314c5c00 leds/leds-gpio: m... |
212 213 214 215 |
static const struct of_device_id of_gpio_leds_match[] = { { .compatible = "gpio-leds", }, {}, }; |
2bcc7ed5b leds: remove conf... |
216 |
#else /* CONFIG_OF_GPIO */ |
a314c5c00 leds/leds-gpio: m... |
217 218 219 220 221 |
static struct gpio_leds_priv * __devinit gpio_leds_create_of(struct platform_device *pdev) { return NULL; } #define of_gpio_leds_match NULL |
2bcc7ed5b leds: remove conf... |
222 |
#endif /* CONFIG_OF_GPIO */ |
a7d878af9 leds: Add openfir... |
223 |
|
a314c5c00 leds/leds-gpio: m... |
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
static int __devinit gpio_led_probe(struct platform_device *pdev) { struct gpio_led_platform_data *pdata = pdev->dev.platform_data; struct gpio_leds_priv *priv; int i, ret = 0; if (pdata && pdata->num_leds) { priv = kzalloc(sizeof_gpio_leds_priv(pdata->num_leds), GFP_KERNEL); if (!priv) return -ENOMEM; priv->num_leds = pdata->num_leds; for (i = 0; i < priv->num_leds; i++) { ret = create_gpio_led(&pdata->leds[i], &priv->leds[i], &pdev->dev, pdata->gpio_blink_set); if (ret < 0) { /* On failure: unwind the led creations */ for (i = i - 1; i >= 0; i--) delete_gpio_led(&priv->leds[i]); kfree(priv); return ret; } } } else { priv = gpio_leds_create_of(pdev); if (!priv) return -ENODEV; } platform_set_drvdata(pdev, priv); return 0; |
a7d878af9 leds: Add openfir... |
259 |
} |
a314c5c00 leds/leds-gpio: m... |
260 |
static int __devexit gpio_led_remove(struct platform_device *pdev) |
a7d878af9 leds: Add openfir... |
261 |
{ |
a314c5c00 leds/leds-gpio: m... |
262 |
struct gpio_leds_priv *priv = dev_get_drvdata(&pdev->dev); |
a7d878af9 leds: Add openfir... |
263 |
int i; |
a314c5c00 leds/leds-gpio: m... |
264 265 |
for (i = 0; i < priv->num_leds; i++) delete_gpio_led(&priv->leds[i]); |
a7d878af9 leds: Add openfir... |
266 |
|
a314c5c00 leds/leds-gpio: m... |
267 268 |
dev_set_drvdata(&pdev->dev, NULL); kfree(priv); |
a7d878af9 leds: Add openfir... |
269 270 271 |
return 0; } |
a314c5c00 leds/leds-gpio: m... |
272 273 274 275 276 277 |
static struct platform_driver gpio_led_driver = { .probe = gpio_led_probe, .remove = __devexit_p(gpio_led_remove), .driver = { .name = "leds-gpio", .owner = THIS_MODULE, |
4018294b5 of: Remove duplic... |
278 |
.of_match_table = of_gpio_leds_match, |
a7d878af9 leds: Add openfir... |
279 |
}, |
a7d878af9 leds: Add openfir... |
280 |
}; |
a314c5c00 leds/leds-gpio: m... |
281 |
|
892a8843f leds: convert led... |
282 |
module_platform_driver(gpio_led_driver); |
a7d878af9 leds: Add openfir... |
283 284 |
MODULE_AUTHOR("Raphael Assenat <raph@8d.com>, Trent Piepho <tpiepho@freescale.com>"); |
22e03f3b5 leds: Add generic... |
285 286 |
MODULE_DESCRIPTION("GPIO LED driver"); MODULE_LICENSE("GPL"); |
892a8843f leds: convert led... |
287 |
MODULE_ALIAS("platform:leds-gpio"); |