diff options
Diffstat (limited to 'drivers/leds/trigger/ledtrig-gpio.c')
-rw-r--r-- | drivers/leds/trigger/ledtrig-gpio.c | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/drivers/leds/trigger/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c new file mode 100644 index 000000000000..35812e3a37f2 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-gpio.c | |||
@@ -0,0 +1,253 @@ | |||
1 | /* | ||
2 | * ledtrig-gio.c - LED Trigger Based on GPIO events | ||
3 | * | ||
4 | * Copyright 2009 Felipe Balbi <me@felipebalbi.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | * | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/init.h> | ||
15 | #include <linux/gpio.h> | ||
16 | #include <linux/interrupt.h> | ||
17 | #include <linux/workqueue.h> | ||
18 | #include <linux/leds.h> | ||
19 | #include <linux/slab.h> | ||
20 | #include "../leds.h" | ||
21 | |||
22 | struct gpio_trig_data { | ||
23 | struct led_classdev *led; | ||
24 | struct work_struct work; | ||
25 | |||
26 | unsigned desired_brightness; /* desired brightness when led is on */ | ||
27 | unsigned inverted; /* true when gpio is inverted */ | ||
28 | unsigned gpio; /* gpio that triggers the leds */ | ||
29 | }; | ||
30 | |||
31 | static irqreturn_t gpio_trig_irq(int irq, void *_led) | ||
32 | { | ||
33 | struct led_classdev *led = _led; | ||
34 | struct gpio_trig_data *gpio_data = led->trigger_data; | ||
35 | |||
36 | /* just schedule_work since gpio_get_value can sleep */ | ||
37 | schedule_work(&gpio_data->work); | ||
38 | |||
39 | return IRQ_HANDLED; | ||
40 | }; | ||
41 | |||
42 | static void gpio_trig_work(struct work_struct *work) | ||
43 | { | ||
44 | struct gpio_trig_data *gpio_data = container_of(work, | ||
45 | struct gpio_trig_data, work); | ||
46 | int tmp; | ||
47 | |||
48 | if (!gpio_data->gpio) | ||
49 | return; | ||
50 | |||
51 | tmp = gpio_get_value(gpio_data->gpio); | ||
52 | if (gpio_data->inverted) | ||
53 | tmp = !tmp; | ||
54 | |||
55 | if (tmp) { | ||
56 | if (gpio_data->desired_brightness) | ||
57 | __led_set_brightness(gpio_data->led, | ||
58 | gpio_data->desired_brightness); | ||
59 | else | ||
60 | __led_set_brightness(gpio_data->led, LED_FULL); | ||
61 | } else { | ||
62 | __led_set_brightness(gpio_data->led, LED_OFF); | ||
63 | } | ||
64 | } | ||
65 | |||
66 | static ssize_t gpio_trig_brightness_show(struct device *dev, | ||
67 | struct device_attribute *attr, char *buf) | ||
68 | { | ||
69 | struct led_classdev *led = dev_get_drvdata(dev); | ||
70 | struct gpio_trig_data *gpio_data = led->trigger_data; | ||
71 | |||
72 | return sprintf(buf, "%u\n", gpio_data->desired_brightness); | ||
73 | } | ||
74 | |||
75 | static ssize_t gpio_trig_brightness_store(struct device *dev, | ||
76 | struct device_attribute *attr, const char *buf, size_t n) | ||
77 | { | ||
78 | struct led_classdev *led = dev_get_drvdata(dev); | ||
79 | struct gpio_trig_data *gpio_data = led->trigger_data; | ||
80 | unsigned desired_brightness; | ||
81 | int ret; | ||
82 | |||
83 | ret = sscanf(buf, "%u", &desired_brightness); | ||
84 | if (ret < 1 || desired_brightness > 255) { | ||
85 | dev_err(dev, "invalid value\n"); | ||
86 | return -EINVAL; | ||
87 | } | ||
88 | |||
89 | gpio_data->desired_brightness = desired_brightness; | ||
90 | |||
91 | return n; | ||
92 | } | ||
93 | static DEVICE_ATTR(desired_brightness, 0644, gpio_trig_brightness_show, | ||
94 | gpio_trig_brightness_store); | ||
95 | |||
96 | static ssize_t gpio_trig_inverted_show(struct device *dev, | ||
97 | struct device_attribute *attr, char *buf) | ||
98 | { | ||
99 | struct led_classdev *led = dev_get_drvdata(dev); | ||
100 | struct gpio_trig_data *gpio_data = led->trigger_data; | ||
101 | |||
102 | return sprintf(buf, "%u\n", gpio_data->inverted); | ||
103 | } | ||
104 | |||
105 | static ssize_t gpio_trig_inverted_store(struct device *dev, | ||
106 | struct device_attribute *attr, const char *buf, size_t n) | ||
107 | { | ||
108 | struct led_classdev *led = dev_get_drvdata(dev); | ||
109 | struct gpio_trig_data *gpio_data = led->trigger_data; | ||
110 | unsigned long inverted; | ||
111 | int ret; | ||
112 | |||
113 | ret = kstrtoul(buf, 10, &inverted); | ||
114 | if (ret < 0) | ||
115 | return ret; | ||
116 | |||
117 | if (inverted > 1) | ||
118 | return -EINVAL; | ||
119 | |||
120 | gpio_data->inverted = inverted; | ||
121 | |||
122 | /* After inverting, we need to update the LED. */ | ||
123 | schedule_work(&gpio_data->work); | ||
124 | |||
125 | return n; | ||
126 | } | ||
127 | static DEVICE_ATTR(inverted, 0644, gpio_trig_inverted_show, | ||
128 | gpio_trig_inverted_store); | ||
129 | |||
130 | static ssize_t gpio_trig_gpio_show(struct device *dev, | ||
131 | struct device_attribute *attr, char *buf) | ||
132 | { | ||
133 | struct led_classdev *led = dev_get_drvdata(dev); | ||
134 | struct gpio_trig_data *gpio_data = led->trigger_data; | ||
135 | |||
136 | return sprintf(buf, "%u\n", gpio_data->gpio); | ||
137 | } | ||
138 | |||
139 | static ssize_t gpio_trig_gpio_store(struct device *dev, | ||
140 | struct device_attribute *attr, const char *buf, size_t n) | ||
141 | { | ||
142 | struct led_classdev *led = dev_get_drvdata(dev); | ||
143 | struct gpio_trig_data *gpio_data = led->trigger_data; | ||
144 | unsigned gpio; | ||
145 | int ret; | ||
146 | |||
147 | ret = sscanf(buf, "%u", &gpio); | ||
148 | if (ret < 1) { | ||
149 | dev_err(dev, "couldn't read gpio number\n"); | ||
150 | flush_work(&gpio_data->work); | ||
151 | return -EINVAL; | ||
152 | } | ||
153 | |||
154 | if (gpio_data->gpio == gpio) | ||
155 | return n; | ||
156 | |||
157 | if (!gpio) { | ||
158 | if (gpio_data->gpio != 0) | ||
159 | free_irq(gpio_to_irq(gpio_data->gpio), led); | ||
160 | gpio_data->gpio = 0; | ||
161 | return n; | ||
162 | } | ||
163 | |||
164 | ret = request_irq(gpio_to_irq(gpio), gpio_trig_irq, | ||
165 | IRQF_SHARED | IRQF_TRIGGER_RISING | ||
166 | | IRQF_TRIGGER_FALLING, "ledtrig-gpio", led); | ||
167 | if (ret) { | ||
168 | dev_err(dev, "request_irq failed with error %d\n", ret); | ||
169 | } else { | ||
170 | if (gpio_data->gpio != 0) | ||
171 | free_irq(gpio_to_irq(gpio_data->gpio), led); | ||
172 | gpio_data->gpio = gpio; | ||
173 | } | ||
174 | |||
175 | return ret ? ret : n; | ||
176 | } | ||
177 | static DEVICE_ATTR(gpio, 0644, gpio_trig_gpio_show, gpio_trig_gpio_store); | ||
178 | |||
179 | static void gpio_trig_activate(struct led_classdev *led) | ||
180 | { | ||
181 | struct gpio_trig_data *gpio_data; | ||
182 | int ret; | ||
183 | |||
184 | gpio_data = kzalloc(sizeof(*gpio_data), GFP_KERNEL); | ||
185 | if (!gpio_data) | ||
186 | return; | ||
187 | |||
188 | ret = device_create_file(led->dev, &dev_attr_gpio); | ||
189 | if (ret) | ||
190 | goto err_gpio; | ||
191 | |||
192 | ret = device_create_file(led->dev, &dev_attr_inverted); | ||
193 | if (ret) | ||
194 | goto err_inverted; | ||
195 | |||
196 | ret = device_create_file(led->dev, &dev_attr_desired_brightness); | ||
197 | if (ret) | ||
198 | goto err_brightness; | ||
199 | |||
200 | gpio_data->led = led; | ||
201 | led->trigger_data = gpio_data; | ||
202 | INIT_WORK(&gpio_data->work, gpio_trig_work); | ||
203 | led->activated = true; | ||
204 | |||
205 | return; | ||
206 | |||
207 | err_brightness: | ||
208 | device_remove_file(led->dev, &dev_attr_inverted); | ||
209 | |||
210 | err_inverted: | ||
211 | device_remove_file(led->dev, &dev_attr_gpio); | ||
212 | |||
213 | err_gpio: | ||
214 | kfree(gpio_data); | ||
215 | } | ||
216 | |||
217 | static void gpio_trig_deactivate(struct led_classdev *led) | ||
218 | { | ||
219 | struct gpio_trig_data *gpio_data = led->trigger_data; | ||
220 | |||
221 | if (led->activated) { | ||
222 | device_remove_file(led->dev, &dev_attr_gpio); | ||
223 | device_remove_file(led->dev, &dev_attr_inverted); | ||
224 | device_remove_file(led->dev, &dev_attr_desired_brightness); | ||
225 | flush_work(&gpio_data->work); | ||
226 | if (gpio_data->gpio != 0) | ||
227 | free_irq(gpio_to_irq(gpio_data->gpio), led); | ||
228 | kfree(gpio_data); | ||
229 | led->activated = false; | ||
230 | } | ||
231 | } | ||
232 | |||
233 | static struct led_trigger gpio_led_trigger = { | ||
234 | .name = "gpio", | ||
235 | .activate = gpio_trig_activate, | ||
236 | .deactivate = gpio_trig_deactivate, | ||
237 | }; | ||
238 | |||
239 | static int __init gpio_trig_init(void) | ||
240 | { | ||
241 | return led_trigger_register(&gpio_led_trigger); | ||
242 | } | ||
243 | module_init(gpio_trig_init); | ||
244 | |||
245 | static void __exit gpio_trig_exit(void) | ||
246 | { | ||
247 | led_trigger_unregister(&gpio_led_trigger); | ||
248 | } | ||
249 | module_exit(gpio_trig_exit); | ||
250 | |||
251 | MODULE_AUTHOR("Felipe Balbi <me@felipebalbi.com>"); | ||
252 | MODULE_DESCRIPTION("GPIO LED trigger"); | ||
253 | MODULE_LICENSE("GPL"); | ||