Blame view

drivers/leds/leds-gpio.c 7.42 KB
22e03f3b5   Raphael Assenat   leds: Add generic...
1
2
3
4
5
  /*
   * LEDs driver for GPIOs
   *
   * Copyright (C) 2007 8D Technologies inc.
   * Raphael Assenat <raph@8d.com>
a7d878af9   Trent Piepho   leds: Add openfir...
6
   * Copyright (C) 2008 Freescale Semiconductor, Inc.
22e03f3b5   Raphael Assenat   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   Grant Likely   leds/leds-gpio: m...
17
18
  #include <linux/of_platform.h>
  #include <linux/of_gpio.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
19
  #include <linux/slab.h>
00852279a   David Brownell   leds: Teach leds-...
20
  #include <linux/workqueue.h>
54f4dedb5   Paul Gortmaker   drivers/leds: Add...
21
  #include <linux/module.h>
00852279a   David Brownell   leds: Teach leds-...
22

22e03f3b5   Raphael Assenat   leds: Add generic...
23
24
25
26
27
  #include <asm/gpio.h>
  
  struct gpio_led_data {
  	struct led_classdev cdev;
  	unsigned gpio;
00852279a   David Brownell   leds: Teach leds-...
28
29
30
  	struct work_struct work;
  	u8 new_level;
  	u8 can_sleep;
22e03f3b5   Raphael Assenat   leds: Add generic...
31
  	u8 active_low;
2146325df   Benjamin Herrenschmidt   leds: leds-gpio: ...
32
33
  	u8 blinking;
  	int (*platform_gpio_blink_set)(unsigned gpio, int state,
ca3259b36   Herbert Valerio Riedel   leds: enable supp...
34
  			unsigned long *delay_on, unsigned long *delay_off);
22e03f3b5   Raphael Assenat   leds: Add generic...
35
  };
00852279a   David Brownell   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   Benjamin Herrenschmidt   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   David Brownell   leds: Teach leds-...
47
  }
22e03f3b5   Raphael Assenat   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   David Brownell   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   David Brownell   leds: Teach leds-...
67
  	if (led_dat->can_sleep) {
306dd85c1   David Brownell   leds: Remove inco...
68
69
  		led_dat->new_level = level;
  		schedule_work(&led_dat->work);
2146325df   Benjamin Herrenschmidt   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   Raphael Assenat   leds: Add generic...
78
  }
ca3259b36   Herbert Valerio Riedel   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   Benjamin Herrenschmidt   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   Herbert Valerio Riedel   leds: enable supp...
87
  }
a7d878af9   Trent Piepho   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   Benjamin Herrenschmidt   leds: leds-gpio: ...
90
  	int (*blink_set)(unsigned, int, unsigned long *, unsigned long *))
a7d878af9   Trent Piepho   leds: Add openfir...
91
  {
ed88bae69   Trent Piepho   leds: Add options...
92
  	int ret, state;
a7d878af9   Trent Piepho   leds: Add openfir...
93

0b4634fce   Dmitry Eremin-Solenikov   leds-gpio: fix po...
94
  	led_dat->gpio = -1;
d379ee8ac   David Brownell   leds: just ignore...
95
96
  	/* skip leds that aren't available */
  	if (!gpio_is_valid(template->gpio)) {
2fea09222   Michal Simek   leds: gpio-leds: ...
97
98
  		printk(KERN_INFO "Skipping unavailable LED gpio %d (%s)
  ",
d379ee8ac   David Brownell   leds: just ignore...
99
  				template->gpio, template->name);
ac15e9509   David Brownell   leds: just ignore...
100
  		return 0;
d379ee8ac   David Brownell   leds: just ignore...
101
  	}
a7d878af9   Trent Piepho   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   Benjamin Herrenschmidt   leds: leds-gpio: ...
111
  	led_dat->blinking = 0;
a7d878af9   Trent Piepho   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   Trent Piepho   leds: Add options...
117
  	if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP)
dabc69c24   David Daney   drivers/leds/leds...
118
  		state = !!gpio_get_value_cansleep(led_dat->gpio) ^ led_dat->active_low;
ed88bae69   Trent Piepho   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   Richard Purdie   leds: Add suspend...
122
123
  	if (!template->retain_state_suspended)
  		led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
a7d878af9   Trent Piepho   leds: Add openfir...
124

ed88bae69   Trent Piepho   leds: Add options...
125
  	ret = gpio_direction_output(led_dat->gpio, led_dat->active_low ^ state);
a7d878af9   Trent Piepho   leds: Add openfir...
126
127
  	if (ret < 0)
  		goto err;
2146325df   Benjamin Herrenschmidt   leds: leds-gpio: ...
128
  		
a7d878af9   Trent Piepho   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   David Brownell   leds: just ignore...
143
144
  	if (!gpio_is_valid(led->gpio))
  		return;
a7d878af9   Trent Piepho   leds: Add openfir...
145
146
147
148
  	led_classdev_unregister(&led->cdev);
  	cancel_work_sync(&led->work);
  	gpio_free(led->gpio);
  }
a314c5c00   Grant Likely   leds/leds-gpio: m...
149
150
151
152
  struct gpio_leds_priv {
  	int num_leds;
  	struct gpio_led_data leds[];
  };
22e03f3b5   Raphael Assenat   leds: Add generic...
153

a314c5c00   Grant Likely   leds/leds-gpio: m...
154
  static inline int sizeof_gpio_leds_priv(int num_leds)
22e03f3b5   Raphael Assenat   leds: Add generic...
155
  {
a314c5c00   Grant Likely   leds/leds-gpio: m...
156
157
  	return sizeof(struct gpio_leds_priv) +
  		(sizeof(struct gpio_led_data) * num_leds);
22e03f3b5   Raphael Assenat   leds: Add generic...
158
  }
a7d878af9   Trent Piepho   leds: Add openfir...
159
  /* Code to create from OpenFirmware platform devices */
2bcc7ed5b   Shawn Guo   leds: remove conf...
160
  #ifdef CONFIG_OF_GPIO
a314c5c00   Grant Likely   leds/leds-gpio: m...
161
  static struct gpio_leds_priv * __devinit gpio_leds_create_of(struct platform_device *pdev)
a7d878af9   Trent Piepho   leds: Add openfir...
162
  {
a314c5c00   Grant Likely   leds/leds-gpio: m...
163
164
  	struct device_node *np = pdev->dev.of_node, *child;
  	struct gpio_leds_priv *priv;
a7d878af9   Trent Piepho   leds: Add openfir...
165
  	int count = 0, ret;
a314c5c00   Grant Likely   leds/leds-gpio: m...
166
  	/* count LEDs in this device, so we know how much to allocate */
a7d878af9   Trent Piepho   leds: Add openfir...
167
168
169
  	for_each_child_of_node(np, child)
  		count++;
  	if (!count)
a314c5c00   Grant Likely   leds/leds-gpio: m...
170
  		return NULL;
a7d878af9   Trent Piepho   leds: Add openfir...
171

a314c5c00   Grant Likely   leds/leds-gpio: m...
172
173
174
  	priv = kzalloc(sizeof_gpio_leds_priv(count), GFP_KERNEL);
  	if (!priv)
  		return NULL;
a7d878af9   Trent Piepho   leds: Add openfir...
175

a7d878af9   Trent Piepho   leds: Add openfir...
176
  	for_each_child_of_node(np, child) {
0493a4ff1   Anton Vorontsov   leds-gpio: fix de...
177
  		struct gpio_led led = {};
a7d878af9   Trent Piepho   leds: Add openfir...
178
  		enum of_gpio_flags flags;
ed88bae69   Trent Piepho   leds: Add options...
179
  		const char *state;
a7d878af9   Trent Piepho   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   Trent Piepho   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   Grant Likely   leds/leds-gpio: m...
190
  			else if (!strcmp(state, "on"))
ed88bae69   Trent Piepho   leds: Add options...
191
192
193
194
  				led.default_state = LEDS_GPIO_DEFSTATE_ON;
  			else
  				led.default_state = LEDS_GPIO_DEFSTATE_OFF;
  		}
a7d878af9   Trent Piepho   leds: Add openfir...
195

a314c5c00   Grant Likely   leds/leds-gpio: m...
196
197
  		ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],
  				      &pdev->dev, NULL);
a7d878af9   Trent Piepho   leds: Add openfir...
198
199
200
201
202
  		if (ret < 0) {
  			of_node_put(child);
  			goto err;
  		}
  	}
a314c5c00   Grant Likely   leds/leds-gpio: m...
203
  	return priv;
a7d878af9   Trent Piepho   leds: Add openfir...
204
205
  
  err:
a314c5c00   Grant Likely   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   Trent Piepho   leds: Add openfir...
211

a314c5c00   Grant Likely   leds/leds-gpio: m...
212
213
214
215
  static const struct of_device_id of_gpio_leds_match[] = {
  	{ .compatible = "gpio-leds", },
  	{},
  };
2bcc7ed5b   Shawn Guo   leds: remove conf...
216
  #else /* CONFIG_OF_GPIO */
a314c5c00   Grant Likely   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   Shawn Guo   leds: remove conf...
222
  #endif /* CONFIG_OF_GPIO */
a7d878af9   Trent Piepho   leds: Add openfir...
223

a314c5c00   Grant Likely   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   Trent Piepho   leds: Add openfir...
259
  }
a314c5c00   Grant Likely   leds/leds-gpio: m...
260
  static int __devexit gpio_led_remove(struct platform_device *pdev)
a7d878af9   Trent Piepho   leds: Add openfir...
261
  {
a314c5c00   Grant Likely   leds/leds-gpio: m...
262
  	struct gpio_leds_priv *priv = dev_get_drvdata(&pdev->dev);
a7d878af9   Trent Piepho   leds: Add openfir...
263
  	int i;
a314c5c00   Grant Likely   leds/leds-gpio: m...
264
265
  	for (i = 0; i < priv->num_leds; i++)
  		delete_gpio_led(&priv->leds[i]);
a7d878af9   Trent Piepho   leds: Add openfir...
266

a314c5c00   Grant Likely   leds/leds-gpio: m...
267
268
  	dev_set_drvdata(&pdev->dev, NULL);
  	kfree(priv);
a7d878af9   Trent Piepho   leds: Add openfir...
269
270
271
  
  	return 0;
  }
a314c5c00   Grant Likely   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   Grant Likely   of: Remove duplic...
278
  		.of_match_table = of_gpio_leds_match,
a7d878af9   Trent Piepho   leds: Add openfir...
279
  	},
a7d878af9   Trent Piepho   leds: Add openfir...
280
  };
a314c5c00   Grant Likely   leds/leds-gpio: m...
281

892a8843f   Axel Lin   leds: convert led...
282
  module_platform_driver(gpio_led_driver);
a7d878af9   Trent Piepho   leds: Add openfir...
283
284
  
  MODULE_AUTHOR("Raphael Assenat <raph@8d.com>, Trent Piepho <tpiepho@freescale.com>");
22e03f3b5   Raphael Assenat   leds: Add generic...
285
286
  MODULE_DESCRIPTION("GPIO LED driver");
  MODULE_LICENSE("GPL");
892a8843f   Axel Lin   leds: convert led...
287
  MODULE_ALIAS("platform:leds-gpio");