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_cansleep(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_nosleep(gpio_data->led,
58 					   gpio_data->desired_brightness);
59 		else
60 			led_set_brightness_nosleep(gpio_data->led, LED_FULL);
61 	} else {
62 		led_set_brightness_nosleep(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");
254