diff options
author | Cédric Le Goater <clg@kaod.org> | 2017-08-08 09:42:39 -0400 |
---|---|---|
committer | Jacek Anaszewski <jacek.anaszewski@gmail.com> | 2017-08-14 16:22:37 -0400 |
commit | 561099a1a2e992a482a8318c0c9c5af26222e5cd (patch) | |
tree | 1fab407e2140c2efdb5e005a4302d576d6613d33 /drivers/leds | |
parent | 91940bb4ca7b7f1b5426cc14bdbd0c7f8347683f (diff) |
leds: pca955x: add GPIO support
The PCA955x family of chips are I2C LED blinkers whose pins not used
to control LEDs can be used as general purpose I/Os (GPIOs).
The following adds such a support by defining different operation
modes for the pins (See bindings documentation for more details). The
pca955x driver is then extended with a gpio_chip when some of pins are
operating as GPIOs. The default operating mode is to behave as a LED.
The GPIO support is conditioned by CONFIG_LEDS_PCA955X_GPIO.
Signed-off-by: Cédric Le Goater <clg@kaod.org>
Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Jacek Anaszewski <jacek.anaszewski@gmail.com>
Diffstat (limited to 'drivers/leds')
-rw-r--r-- | drivers/leds/Kconfig | 11 | ||||
-rw-r--r-- | drivers/leds/leds-pca955x.c | 137 |
2 files changed, 131 insertions, 17 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 594b24d410c3..3013cd35c65e 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig | |||
@@ -377,6 +377,17 @@ config LEDS_PCA955X | |||
377 | LED driver chips accessed via the I2C bus. Supported | 377 | LED driver chips accessed via the I2C bus. Supported |
378 | devices include PCA9550, PCA9551, PCA9552, and PCA9553. | 378 | devices include PCA9550, PCA9551, PCA9552, and PCA9553. |
379 | 379 | ||
380 | config LEDS_PCA955X_GPIO | ||
381 | bool "Enable GPIO support for PCA955X" | ||
382 | depends on LEDS_PCA955X | ||
383 | depends on GPIOLIB | ||
384 | help | ||
385 | Allow unused pins on PCA955X to be used as gpio. | ||
386 | |||
387 | To use a pin as gpio the pin type should be set to | ||
388 | PCA955X_TYPE_GPIO in the device tree. | ||
389 | |||
390 | |||
380 | config LEDS_PCA963X | 391 | config LEDS_PCA963X |
381 | tristate "LED support for PCA963x I2C chip" | 392 | tristate "LED support for PCA963x I2C chip" |
382 | depends on LEDS_CLASS | 393 | depends on LEDS_CLASS |
diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index 5a5d3765cfd3..f062d1e7640f 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c | |||
@@ -53,6 +53,8 @@ | |||
53 | #include <linux/slab.h> | 53 | #include <linux/slab.h> |
54 | #include <linux/string.h> | 54 | #include <linux/string.h> |
55 | 55 | ||
56 | #include <dt-bindings/leds/leds-pca955x.h> | ||
57 | |||
56 | /* LED select registers determine the source that drives LED outputs */ | 58 | /* LED select registers determine the source that drives LED outputs */ |
57 | #define PCA955X_LS_LED_ON 0x0 /* Output LOW */ | 59 | #define PCA955X_LS_LED_ON 0x0 /* Output LOW */ |
58 | #define PCA955X_LS_LED_OFF 0x1 /* Output HI-Z */ | 60 | #define PCA955X_LS_LED_OFF 0x1 /* Output HI-Z */ |
@@ -118,6 +120,9 @@ struct pca955x { | |||
118 | struct pca955x_led *leds; | 120 | struct pca955x_led *leds; |
119 | struct pca955x_chipdef *chipdef; | 121 | struct pca955x_chipdef *chipdef; |
120 | struct i2c_client *client; | 122 | struct i2c_client *client; |
123 | #ifdef CONFIG_LEDS_PCA955X_GPIO | ||
124 | struct gpio_chip gpio; | ||
125 | #endif | ||
121 | }; | 126 | }; |
122 | 127 | ||
123 | struct pca955x_led { | 128 | struct pca955x_led { |
@@ -125,6 +130,7 @@ struct pca955x_led { | |||
125 | struct led_classdev led_cdev; | 130 | struct led_classdev led_cdev; |
126 | int led_num; /* 0 .. 15 potentially */ | 131 | int led_num; /* 0 .. 15 potentially */ |
127 | char name[32]; | 132 | char name[32]; |
133 | u32 type; | ||
128 | const char *default_trigger; | 134 | const char *default_trigger; |
129 | }; | 135 | }; |
130 | 136 | ||
@@ -259,6 +265,65 @@ static int pca955x_led_set(struct led_classdev *led_cdev, | |||
259 | return 0; | 265 | return 0; |
260 | } | 266 | } |
261 | 267 | ||
268 | #ifdef CONFIG_LEDS_PCA955X_GPIO | ||
269 | /* | ||
270 | * Read the INPUT register, which contains the state of LEDs. | ||
271 | */ | ||
272 | static u8 pca955x_read_input(struct i2c_client *client, int n) | ||
273 | { | ||
274 | return (u8)i2c_smbus_read_byte_data(client, n); | ||
275 | } | ||
276 | |||
277 | static int pca955x_gpio_request_pin(struct gpio_chip *gc, unsigned int offset) | ||
278 | { | ||
279 | struct pca955x *pca955x = gpiochip_get_data(gc); | ||
280 | struct pca955x_led *led = &pca955x->leds[offset]; | ||
281 | |||
282 | if (led->type == PCA955X_TYPE_GPIO) | ||
283 | return 0; | ||
284 | |||
285 | return -EBUSY; | ||
286 | } | ||
287 | |||
288 | static void pca955x_gpio_set_value(struct gpio_chip *gc, unsigned int offset, | ||
289 | int val) | ||
290 | { | ||
291 | struct pca955x *pca955x = gpiochip_get_data(gc); | ||
292 | struct pca955x_led *led = &pca955x->leds[offset]; | ||
293 | |||
294 | if (val) | ||
295 | pca955x_led_set(&led->led_cdev, LED_FULL); | ||
296 | else | ||
297 | pca955x_led_set(&led->led_cdev, LED_OFF); | ||
298 | } | ||
299 | |||
300 | static int pca955x_gpio_get_value(struct gpio_chip *gc, unsigned int offset) | ||
301 | { | ||
302 | struct pca955x *pca955x = gpiochip_get_data(gc); | ||
303 | struct pca955x_led *led = &pca955x->leds[offset]; | ||
304 | u8 reg = pca955x_read_input(pca955x->client, led->led_num / 8); | ||
305 | |||
306 | return !!(reg & (1 << (led->led_num % 8))); | ||
307 | } | ||
308 | |||
309 | static int pca955x_gpio_direction_input(struct gpio_chip *gc, | ||
310 | unsigned int offset) | ||
311 | { | ||
312 | /* To use as input ensure pin is not driven */ | ||
313 | pca955x_gpio_set_value(gc, offset, 0); | ||
314 | |||
315 | return 0; | ||
316 | } | ||
317 | |||
318 | static int pca955x_gpio_direction_output(struct gpio_chip *gc, | ||
319 | unsigned int offset, int val) | ||
320 | { | ||
321 | pca955x_gpio_set_value(gc, offset, val); | ||
322 | |||
323 | return 0; | ||
324 | } | ||
325 | #endif /* CONFIG_LEDS_PCA955X_GPIO */ | ||
326 | |||
262 | #if IS_ENABLED(CONFIG_OF) | 327 | #if IS_ENABLED(CONFIG_OF) |
263 | static struct pca955x_platform_data * | 328 | static struct pca955x_platform_data * |
264 | pca955x_pdata_of_init(struct i2c_client *client, struct pca955x_chipdef *chip) | 329 | pca955x_pdata_of_init(struct i2c_client *client, struct pca955x_chipdef *chip) |
@@ -297,6 +362,8 @@ pca955x_pdata_of_init(struct i2c_client *client, struct pca955x_chipdef *chip) | |||
297 | snprintf(pdata->leds[reg].name, sizeof(pdata->leds[reg].name), | 362 | snprintf(pdata->leds[reg].name, sizeof(pdata->leds[reg].name), |
298 | "%s", name); | 363 | "%s", name); |
299 | 364 | ||
365 | pdata->leds[reg].type = PCA955X_TYPE_LED; | ||
366 | of_property_read_u32(child, "type", &pdata->leds[reg].type); | ||
300 | of_property_read_string(child, "linux,default-trigger", | 367 | of_property_read_string(child, "linux,default-trigger", |
301 | &pdata->leds[reg].default_trigger); | 368 | &pdata->leds[reg].default_trigger); |
302 | } | 369 | } |
@@ -332,6 +399,7 @@ static int pca955x_probe(struct i2c_client *client, | |||
332 | struct i2c_adapter *adapter; | 399 | struct i2c_adapter *adapter; |
333 | int i, err; | 400 | int i, err; |
334 | struct pca955x_platform_data *pdata; | 401 | struct pca955x_platform_data *pdata; |
402 | int ngpios = 0; | ||
335 | 403 | ||
336 | if (id) { | 404 | if (id) { |
337 | chip = &pca955x_chipdefs[id->driver_data]; | 405 | chip = &pca955x_chipdefs[id->driver_data]; |
@@ -392,9 +460,19 @@ static int pca955x_probe(struct i2c_client *client, | |||
392 | pca955x_led = &pca955x->leds[i]; | 460 | pca955x_led = &pca955x->leds[i]; |
393 | pca955x_led->led_num = i; | 461 | pca955x_led->led_num = i; |
394 | pca955x_led->pca955x = pca955x; | 462 | pca955x_led->pca955x = pca955x; |
395 | 463 | pca955x_led->type = pdata->leds[i].type; | |
396 | /* Platform data can specify LED names and default triggers */ | 464 | |
397 | if (pdata) { | 465 | switch (pca955x_led->type) { |
466 | case PCA955X_TYPE_NONE: | ||
467 | break; | ||
468 | case PCA955X_TYPE_GPIO: | ||
469 | ngpios++; | ||
470 | break; | ||
471 | case PCA955X_TYPE_LED: | ||
472 | /* | ||
473 | * Platform data can specify LED names and | ||
474 | * default triggers | ||
475 | */ | ||
398 | if (pdata->leds[i].name) | 476 | if (pdata->leds[i].name) |
399 | snprintf(pca955x_led->name, | 477 | snprintf(pca955x_led->name, |
400 | sizeof(pca955x_led->name), "pca955x:%s", | 478 | sizeof(pca955x_led->name), "pca955x:%s", |
@@ -402,23 +480,20 @@ static int pca955x_probe(struct i2c_client *client, | |||
402 | if (pdata->leds[i].default_trigger) | 480 | if (pdata->leds[i].default_trigger) |
403 | pca955x_led->led_cdev.default_trigger = | 481 | pca955x_led->led_cdev.default_trigger = |
404 | pdata->leds[i].default_trigger; | 482 | pdata->leds[i].default_trigger; |
405 | } else { | ||
406 | snprintf(pca955x_led->name, sizeof(pca955x_led->name), | ||
407 | "pca955x:%d", i); | ||
408 | } | ||
409 | 483 | ||
410 | pca955x_led->led_cdev.name = pca955x_led->name; | 484 | pca955x_led->led_cdev.name = pca955x_led->name; |
411 | pca955x_led->led_cdev.brightness_set_blocking = pca955x_led_set; | 485 | pca955x_led->led_cdev.brightness_set_blocking = |
486 | pca955x_led_set; | ||
412 | 487 | ||
413 | err = devm_led_classdev_register(&client->dev, | 488 | err = devm_led_classdev_register(&client->dev, |
414 | &pca955x_led->led_cdev); | 489 | &pca955x_led->led_cdev); |
415 | if (err) | 490 | if (err) |
416 | return err; | 491 | return err; |
417 | } | ||
418 | 492 | ||
419 | /* Turn off LEDs */ | 493 | /* Turn off LED */ |
420 | for (i = 0; i < pca95xx_num_led_regs(chip->bits); i++) | 494 | pca955x_led_set(&pca955x_led->led_cdev, LED_OFF); |
421 | pca955x_write_ls(client, i, 0x55); | 495 | } |
496 | } | ||
422 | 497 | ||
423 | /* PWM0 is used for half brightness or 50% duty cycle */ | 498 | /* PWM0 is used for half brightness or 50% duty cycle */ |
424 | pca955x_write_pwm(client, 0, 255-LED_HALF); | 499 | pca955x_write_pwm(client, 0, 255-LED_HALF); |
@@ -430,6 +505,34 @@ static int pca955x_probe(struct i2c_client *client, | |||
430 | pca955x_write_psc(client, 0, 0); | 505 | pca955x_write_psc(client, 0, 0); |
431 | pca955x_write_psc(client, 1, 0); | 506 | pca955x_write_psc(client, 1, 0); |
432 | 507 | ||
508 | #ifdef CONFIG_LEDS_PCA955X_GPIO | ||
509 | if (ngpios) { | ||
510 | pca955x->gpio.label = "gpio-pca955x"; | ||
511 | pca955x->gpio.direction_input = pca955x_gpio_direction_input; | ||
512 | pca955x->gpio.direction_output = pca955x_gpio_direction_output; | ||
513 | pca955x->gpio.set = pca955x_gpio_set_value; | ||
514 | pca955x->gpio.get = pca955x_gpio_get_value; | ||
515 | pca955x->gpio.request = pca955x_gpio_request_pin; | ||
516 | pca955x->gpio.can_sleep = 1; | ||
517 | pca955x->gpio.base = -1; | ||
518 | pca955x->gpio.ngpio = ngpios; | ||
519 | pca955x->gpio.parent = &client->dev; | ||
520 | pca955x->gpio.owner = THIS_MODULE; | ||
521 | |||
522 | err = devm_gpiochip_add_data(&client->dev, &pca955x->gpio, | ||
523 | pca955x); | ||
524 | if (err) { | ||
525 | /* Use data->gpio.dev as a flag for freeing gpiochip */ | ||
526 | pca955x->gpio.parent = NULL; | ||
527 | dev_warn(&client->dev, "could not add gpiochip\n"); | ||
528 | return err; | ||
529 | } | ||
530 | dev_info(&client->dev, "gpios %i...%i\n", | ||
531 | pca955x->gpio.base, pca955x->gpio.base + | ||
532 | pca955x->gpio.ngpio - 1); | ||
533 | } | ||
534 | #endif | ||
535 | |||
433 | return 0; | 536 | return 0; |
434 | } | 537 | } |
435 | 538 | ||