Blame view
drivers/leds/leds-gpio.c
7.59 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 |
* * 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. * */ |
4cc72346f led: gpio: Sort i... |
13 |
#include <linux/err.h> |
16db7f909 drivers/leds/leds... |
14 |
#include <linux/gpio.h> |
5c51277a9 leds: leds-gpio: ... |
15 |
#include <linux/gpio/consumer.h> |
4cc72346f led: gpio: Sort i... |
16 |
#include <linux/kernel.h> |
22e03f3b5 leds: Add generic... |
17 |
#include <linux/leds.h> |
4cc72346f led: gpio: Sort i... |
18 |
#include <linux/module.h> |
4cc72346f led: gpio: Sort i... |
19 |
#include <linux/platform_device.h> |
a43f2cbbb leds: leds-gpio: ... |
20 |
#include <linux/property.h> |
5a0e3ad6a include cleanup: ... |
21 |
#include <linux/slab.h> |
00852279a leds: Teach leds-... |
22 |
#include <linux/workqueue.h> |
22e03f3b5 leds: Add generic... |
23 24 |
struct gpio_led_data { struct led_classdev cdev; |
5c51277a9 leds: leds-gpio: ... |
25 |
struct gpio_desc *gpiod; |
00852279a leds: Teach leds-... |
26 27 28 |
struct work_struct work; u8 new_level; u8 can_sleep; |
2146325df leds: leds-gpio: ... |
29 |
u8 blinking; |
c673a2b40 leds: leds-gpio: ... |
30 |
int (*platform_gpio_blink_set)(struct gpio_desc *desc, int state, |
ca3259b36 leds: enable supp... |
31 |
unsigned long *delay_on, unsigned long *delay_off); |
22e03f3b5 leds: Add generic... |
32 |
}; |
00852279a leds: Teach leds-... |
33 34 |
static void gpio_led_work(struct work_struct *work) { |
a4c84e6aa leds: gpio: clean... |
35 |
struct gpio_led_data *led_dat = |
00852279a leds: Teach leds-... |
36 |
container_of(work, struct gpio_led_data, work); |
2146325df leds: leds-gpio: ... |
37 |
if (led_dat->blinking) { |
c673a2b40 leds: leds-gpio: ... |
38 39 |
led_dat->platform_gpio_blink_set(led_dat->gpiod, led_dat->new_level, NULL, NULL); |
2146325df leds: leds-gpio: ... |
40 41 |
led_dat->blinking = 0; } else |
5c51277a9 leds: leds-gpio: ... |
42 |
gpiod_set_value_cansleep(led_dat->gpiod, led_dat->new_level); |
00852279a leds: Teach leds-... |
43 |
} |
22e03f3b5 leds: Add generic... |
44 45 46 47 48 49 50 51 52 53 54 55 |
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; |
306dd85c1 leds: Remove inco... |
56 57 58 59 |
/* 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-... |
60 |
if (led_dat->can_sleep) { |
306dd85c1 leds: Remove inco... |
61 62 |
led_dat->new_level = level; schedule_work(&led_dat->work); |
2146325df leds: leds-gpio: ... |
63 64 |
} else { if (led_dat->blinking) { |
c673a2b40 leds: leds-gpio: ... |
65 66 |
led_dat->platform_gpio_blink_set(led_dat->gpiod, level, NULL, NULL); |
2146325df leds: leds-gpio: ... |
67 68 |
led_dat->blinking = 0; } else |
5c51277a9 leds: leds-gpio: ... |
69 |
gpiod_set_value(led_dat->gpiod, level); |
2146325df leds: leds-gpio: ... |
70 |
} |
22e03f3b5 leds: Add generic... |
71 |
} |
ca3259b36 leds: enable supp... |
72 73 74 75 76 |
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: ... |
77 |
led_dat->blinking = 1; |
c673a2b40 leds: leds-gpio: ... |
78 |
return led_dat->platform_gpio_blink_set(led_dat->gpiod, GPIO_LED_BLINK, |
2146325df leds: leds-gpio: ... |
79 |
delay_on, delay_off); |
ca3259b36 leds: enable supp... |
80 |
} |
98ea1ea20 leds: remove use ... |
81 |
static int create_gpio_led(const struct gpio_led *template, |
a7d878af9 leds: Add openfir... |
82 |
struct gpio_led_data *led_dat, struct device *parent, |
c673a2b40 leds: leds-gpio: ... |
83 84 |
int (*blink_set)(struct gpio_desc *, int, unsigned long *, unsigned long *)) |
a7d878af9 leds: Add openfir... |
85 |
{ |
ed88bae69 leds: Add options... |
86 |
int ret, state; |
a7d878af9 leds: Add openfir... |
87 |
|
ec98a4975 leds: leds-gpio: ... |
88 89 |
led_dat->gpiod = template->gpiod; if (!led_dat->gpiod) { |
c673a2b40 leds: leds-gpio: ... |
90 91 92 93 94 |
/* * This is the legacy code path for platform code that * still uses GPIO numbers. Ultimately we would like to get * rid of this block completely. */ |
5c51277a9 leds: leds-gpio: ... |
95 |
unsigned long flags = 0; |
0b4634fce leds-gpio: fix po... |
96 |
|
5c51277a9 leds: leds-gpio: ... |
97 98 99 100 101 102 103 |
/* skip leds that aren't available */ if (!gpio_is_valid(template->gpio)) { dev_info(parent, "Skipping unavailable LED gpio %d (%s) ", template->gpio, template->name); return 0; } |
d379ee8ac leds: just ignore... |
104 |
|
5c51277a9 leds: leds-gpio: ... |
105 106 107 108 109 110 111 112 113 114 115 116 |
if (template->active_low) flags |= GPIOF_ACTIVE_LOW; ret = devm_gpio_request_one(parent, template->gpio, flags, template->name); if (ret < 0) return ret; led_dat->gpiod = gpio_to_desc(template->gpio); if (IS_ERR(led_dat->gpiod)) return PTR_ERR(led_dat->gpiod); } |
803d19d57 leds: leds-gpio: ... |
117 |
|
a7d878af9 leds: Add openfir... |
118 119 |
led_dat->cdev.name = template->name; led_dat->cdev.default_trigger = template->default_trigger; |
ec98a4975 leds: leds-gpio: ... |
120 |
led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod); |
2146325df leds: leds-gpio: ... |
121 |
led_dat->blinking = 0; |
a7d878af9 leds: Add openfir... |
122 123 124 125 126 |
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... |
127 |
if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) |
5c51277a9 leds: leds-gpio: ... |
128 |
state = !!gpiod_get_value_cansleep(led_dat->gpiod); |
ed88bae69 leds: Add options... |
129 130 131 |
else state = (template->default_state == LEDS_GPIO_DEFSTATE_ON); led_dat->cdev.brightness = state ? LED_FULL : LED_OFF; |
defb512d2 leds: Add suspend... |
132 133 |
if (!template->retain_state_suspended) led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; |
a7d878af9 leds: Add openfir... |
134 |
|
5c51277a9 leds: leds-gpio: ... |
135 |
ret = gpiod_direction_output(led_dat->gpiod, state); |
a7d878af9 leds: Add openfir... |
136 |
if (ret < 0) |
a99d76f9e leds: leds-gpio: ... |
137 |
return ret; |
a7d878af9 leds: Add openfir... |
138 |
INIT_WORK(&led_dat->work, gpio_led_work); |
5c51277a9 leds: leds-gpio: ... |
139 |
return led_classdev_register(parent, &led_dat->cdev); |
a7d878af9 leds: Add openfir... |
140 141 142 143 144 145 |
} static void delete_gpio_led(struct gpio_led_data *led) { led_classdev_unregister(&led->cdev); cancel_work_sync(&led->work); |
a7d878af9 leds: Add openfir... |
146 |
} |
a314c5c00 leds/leds-gpio: m... |
147 148 149 150 |
struct gpio_leds_priv { int num_leds; struct gpio_led_data leds[]; }; |
22e03f3b5 leds: Add generic... |
151 |
|
a314c5c00 leds/leds-gpio: m... |
152 |
static inline int sizeof_gpio_leds_priv(int num_leds) |
22e03f3b5 leds: Add generic... |
153 |
{ |
a314c5c00 leds/leds-gpio: m... |
154 155 |
return sizeof(struct gpio_leds_priv) + (sizeof(struct gpio_led_data) * num_leds); |
22e03f3b5 leds: Add generic... |
156 |
} |
a43f2cbbb leds: leds-gpio: ... |
157 |
static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) |
a7d878af9 leds: Add openfir... |
158 |
{ |
a43f2cbbb leds: leds-gpio: ... |
159 160 |
struct device *dev = &pdev->dev; struct fwnode_handle *child; |
a314c5c00 leds/leds-gpio: m... |
161 |
struct gpio_leds_priv *priv; |
127aedc8e leds: leds-gpio: ... |
162 |
int count, ret; |
29470ea8d leds: leds-gpio: ... |
163 |
struct device_node *np; |
a7d878af9 leds: Add openfir... |
164 |
|
a43f2cbbb leds: leds-gpio: ... |
165 |
count = device_get_child_node_count(dev); |
a7d878af9 leds: Add openfir... |
166 |
if (!count) |
04553e925 leds: leds-gpio: ... |
167 |
return ERR_PTR(-ENODEV); |
a43f2cbbb leds: leds-gpio: ... |
168 |
priv = devm_kzalloc(dev, sizeof_gpio_leds_priv(count), GFP_KERNEL); |
a314c5c00 leds/leds-gpio: m... |
169 |
if (!priv) |
04553e925 leds: leds-gpio: ... |
170 |
return ERR_PTR(-ENOMEM); |
a7d878af9 leds: Add openfir... |
171 |
|
a43f2cbbb leds: leds-gpio: ... |
172 |
device_for_each_child_node(dev, child) { |
0493a4ff1 leds-gpio: fix de... |
173 |
struct gpio_led led = {}; |
a43f2cbbb leds: leds-gpio: ... |
174 |
const char *state = NULL; |
1feb57a24 gpio: add paramet... |
175 |
led.gpiod = devm_get_gpiod_from_child(dev, NULL, child); |
a43f2cbbb leds: leds-gpio: ... |
176 177 |
if (IS_ERR(led.gpiod)) { fwnode_handle_put(child); |
c6e71f813 leds: leds-gpio: ... |
178 |
ret = PTR_ERR(led.gpiod); |
a43f2cbbb leds: leds-gpio: ... |
179 180 |
goto err; } |
29470ea8d leds: leds-gpio: ... |
181 182 183 184 185 186 187 188 189 190 |
np = of_node(child); if (fwnode_property_present(child, "label")) { fwnode_property_read_string(child, "label", &led.name); } else { if (IS_ENABLED(CONFIG_OF) && !led.name && np) led.name = np->name; if (!led.name) return ERR_PTR(-EINVAL); } |
a43f2cbbb leds: leds-gpio: ... |
191 192 |
fwnode_property_read_string(child, "linux,default-trigger", &led.default_trigger); |
d735d25e6 leds: leds-gpio: ... |
193 |
if (!fwnode_property_read_string(child, "default-state", |
a43f2cbbb leds: leds-gpio: ... |
194 |
&state)) { |
ed88bae69 leds: Add options... |
195 196 |
if (!strcmp(state, "keep")) led.default_state = LEDS_GPIO_DEFSTATE_KEEP; |
a314c5c00 leds/leds-gpio: m... |
197 |
else if (!strcmp(state, "on")) |
ed88bae69 leds: Add options... |
198 199 200 201 |
led.default_state = LEDS_GPIO_DEFSTATE_ON; else led.default_state = LEDS_GPIO_DEFSTATE_OFF; } |
a7d878af9 leds: Add openfir... |
202 |
|
a43f2cbbb leds: leds-gpio: ... |
203 |
if (fwnode_property_present(child, "retain-state-suspended")) |
4270a78d2 leds: leds-gpio: ... |
204 |
led.retain_state_suspended = 1; |
a314c5c00 leds/leds-gpio: m... |
205 |
ret = create_gpio_led(&led, &priv->leds[priv->num_leds++], |
a43f2cbbb leds: leds-gpio: ... |
206 |
dev, NULL); |
a7d878af9 leds: Add openfir... |
207 |
if (ret < 0) { |
a43f2cbbb leds: leds-gpio: ... |
208 |
fwnode_handle_put(child); |
a7d878af9 leds: Add openfir... |
209 210 211 |
goto err; } } |
a314c5c00 leds/leds-gpio: m... |
212 |
return priv; |
a7d878af9 leds: Add openfir... |
213 214 |
err: |
a314c5c00 leds/leds-gpio: m... |
215 216 |
for (count = priv->num_leds - 2; count >= 0; count--) delete_gpio_led(&priv->leds[count]); |
c6e71f813 leds: leds-gpio: ... |
217 |
return ERR_PTR(ret); |
a314c5c00 leds/leds-gpio: m... |
218 |
} |
a7d878af9 leds: Add openfir... |
219 |
|
a314c5c00 leds/leds-gpio: m... |
220 221 222 223 |
static const struct of_device_id of_gpio_leds_match[] = { { .compatible = "gpio-leds", }, {}, }; |
472b854bb leds-gpio: of: in... |
224 225 |
MODULE_DEVICE_TABLE(of, of_gpio_leds_match); |
a7d878af9 leds: Add openfir... |
226 |
|
98ea1ea20 leds: remove use ... |
227 |
static int gpio_led_probe(struct platform_device *pdev) |
a314c5c00 leds/leds-gpio: m... |
228 |
{ |
87aae1ea8 leds: use dev_get... |
229 |
struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev); |
a314c5c00 leds/leds-gpio: m... |
230 231 232 233 |
struct gpio_leds_priv *priv; int i, ret = 0; if (pdata && pdata->num_leds) { |
198b86113 leds: Use devm_kz... |
234 235 236 |
priv = devm_kzalloc(&pdev->dev, sizeof_gpio_leds_priv(pdata->num_leds), GFP_KERNEL); |
a314c5c00 leds/leds-gpio: m... |
237 238 239 240 241 242 243 244 245 246 247 248 |
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]); |
a314c5c00 leds/leds-gpio: m... |
249 250 251 252 |
return ret; } } } else { |
a43f2cbbb leds: leds-gpio: ... |
253 |
priv = gpio_leds_create(pdev); |
04553e925 leds: leds-gpio: ... |
254 255 |
if (IS_ERR(priv)) return PTR_ERR(priv); |
a314c5c00 leds/leds-gpio: m... |
256 257 258 259 260 |
} platform_set_drvdata(pdev, priv); return 0; |
a7d878af9 leds: Add openfir... |
261 |
} |
678e8a6be leds: remove use ... |
262 |
static int gpio_led_remove(struct platform_device *pdev) |
a7d878af9 leds: Add openfir... |
263 |
{ |
59c4dce13 leds: leds-gpio: ... |
264 |
struct gpio_leds_priv *priv = platform_get_drvdata(pdev); |
a7d878af9 leds: Add openfir... |
265 |
int i; |
a314c5c00 leds/leds-gpio: m... |
266 267 |
for (i = 0; i < priv->num_leds; i++) delete_gpio_led(&priv->leds[i]); |
a7d878af9 leds: Add openfir... |
268 |
|
a7d878af9 leds: Add openfir... |
269 270 |
return 0; } |
a314c5c00 leds/leds-gpio: m... |
271 272 |
static struct platform_driver gpio_led_driver = { .probe = gpio_led_probe, |
df07cf812 leds: remove use ... |
273 |
.remove = gpio_led_remove, |
a314c5c00 leds/leds-gpio: m... |
274 275 |
.driver = { .name = "leds-gpio", |
a43f2cbbb leds: leds-gpio: ... |
276 |
.of_match_table = of_gpio_leds_match, |
a7d878af9 leds: Add openfir... |
277 |
}, |
a7d878af9 leds: Add openfir... |
278 |
}; |
a314c5c00 leds/leds-gpio: m... |
279 |
|
892a8843f leds: convert led... |
280 |
module_platform_driver(gpio_led_driver); |
a7d878af9 leds: Add openfir... |
281 282 |
MODULE_AUTHOR("Raphael Assenat <raph@8d.com>, Trent Piepho <tpiepho@freescale.com>"); |
22e03f3b5 leds: Add generic... |
283 284 |
MODULE_DESCRIPTION("GPIO LED driver"); MODULE_LICENSE("GPL"); |
892a8843f leds: convert led... |
285 |
MODULE_ALIAS("platform:leds-gpio"); |