Commit 561099a1 authored by Cédric Le Goater's avatar Cédric Le Goater Committed by Jacek Anaszewski

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: default avatarCédric Le Goater <clg@kaod.org>
Acked-by: default avatarPavel Machek <pavel@ucw.cz>
Signed-off-by: default avatarJacek Anaszewski <jacek.anaszewski@gmail.com>
parent 91940bb4
...@@ -377,6 +377,17 @@ config LEDS_PCA955X ...@@ -377,6 +377,17 @@ config LEDS_PCA955X
LED driver chips accessed via the I2C bus. Supported LED driver chips accessed via the I2C bus. Supported
devices include PCA9550, PCA9551, PCA9552, and PCA9553. devices include PCA9550, PCA9551, PCA9552, and PCA9553.
config LEDS_PCA955X_GPIO
bool "Enable GPIO support for PCA955X"
depends on LEDS_PCA955X
depends on GPIOLIB
help
Allow unused pins on PCA955X to be used as gpio.
To use a pin as gpio the pin type should be set to
PCA955X_TYPE_GPIO in the device tree.
config LEDS_PCA963X config LEDS_PCA963X
tristate "LED support for PCA963x I2C chip" tristate "LED support for PCA963x I2C chip"
depends on LEDS_CLASS depends on LEDS_CLASS
......
...@@ -53,6 +53,8 @@ ...@@ -53,6 +53,8 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/string.h> #include <linux/string.h>
#include <dt-bindings/leds/leds-pca955x.h>
/* LED select registers determine the source that drives LED outputs */ /* LED select registers determine the source that drives LED outputs */
#define PCA955X_LS_LED_ON 0x0 /* Output LOW */ #define PCA955X_LS_LED_ON 0x0 /* Output LOW */
#define PCA955X_LS_LED_OFF 0x1 /* Output HI-Z */ #define PCA955X_LS_LED_OFF 0x1 /* Output HI-Z */
...@@ -118,6 +120,9 @@ struct pca955x { ...@@ -118,6 +120,9 @@ struct pca955x {
struct pca955x_led *leds; struct pca955x_led *leds;
struct pca955x_chipdef *chipdef; struct pca955x_chipdef *chipdef;
struct i2c_client *client; struct i2c_client *client;
#ifdef CONFIG_LEDS_PCA955X_GPIO
struct gpio_chip gpio;
#endif
}; };
struct pca955x_led { struct pca955x_led {
...@@ -125,6 +130,7 @@ struct pca955x_led { ...@@ -125,6 +130,7 @@ struct pca955x_led {
struct led_classdev led_cdev; struct led_classdev led_cdev;
int led_num; /* 0 .. 15 potentially */ int led_num; /* 0 .. 15 potentially */
char name[32]; char name[32];
u32 type;
const char *default_trigger; const char *default_trigger;
}; };
...@@ -259,6 +265,65 @@ static int pca955x_led_set(struct led_classdev *led_cdev, ...@@ -259,6 +265,65 @@ static int pca955x_led_set(struct led_classdev *led_cdev,
return 0; return 0;
} }
#ifdef CONFIG_LEDS_PCA955X_GPIO
/*
* Read the INPUT register, which contains the state of LEDs.
*/
static u8 pca955x_read_input(struct i2c_client *client, int n)
{
return (u8)i2c_smbus_read_byte_data(client, n);
}
static int pca955x_gpio_request_pin(struct gpio_chip *gc, unsigned int offset)
{
struct pca955x *pca955x = gpiochip_get_data(gc);
struct pca955x_led *led = &pca955x->leds[offset];
if (led->type == PCA955X_TYPE_GPIO)
return 0;
return -EBUSY;
}
static void pca955x_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
int val)
{
struct pca955x *pca955x = gpiochip_get_data(gc);
struct pca955x_led *led = &pca955x->leds[offset];
if (val)
pca955x_led_set(&led->led_cdev, LED_FULL);
else
pca955x_led_set(&led->led_cdev, LED_OFF);
}
static int pca955x_gpio_get_value(struct gpio_chip *gc, unsigned int offset)
{
struct pca955x *pca955x = gpiochip_get_data(gc);
struct pca955x_led *led = &pca955x->leds[offset];
u8 reg = pca955x_read_input(pca955x->client, led->led_num / 8);
return !!(reg & (1 << (led->led_num % 8)));
}
static int pca955x_gpio_direction_input(struct gpio_chip *gc,
unsigned int offset)
{
/* To use as input ensure pin is not driven */
pca955x_gpio_set_value(gc, offset, 0);
return 0;
}
static int pca955x_gpio_direction_output(struct gpio_chip *gc,
unsigned int offset, int val)
{
pca955x_gpio_set_value(gc, offset, val);
return 0;
}
#endif /* CONFIG_LEDS_PCA955X_GPIO */
#if IS_ENABLED(CONFIG_OF) #if IS_ENABLED(CONFIG_OF)
static struct pca955x_platform_data * static struct pca955x_platform_data *
pca955x_pdata_of_init(struct i2c_client *client, struct pca955x_chipdef *chip) 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,6 +362,8 @@ pca955x_pdata_of_init(struct i2c_client *client, struct pca955x_chipdef *chip)
snprintf(pdata->leds[reg].name, sizeof(pdata->leds[reg].name), snprintf(pdata->leds[reg].name, sizeof(pdata->leds[reg].name),
"%s", name); "%s", name);
pdata->leds[reg].type = PCA955X_TYPE_LED;
of_property_read_u32(child, "type", &pdata->leds[reg].type);
of_property_read_string(child, "linux,default-trigger", of_property_read_string(child, "linux,default-trigger",
&pdata->leds[reg].default_trigger); &pdata->leds[reg].default_trigger);
} }
...@@ -332,6 +399,7 @@ static int pca955x_probe(struct i2c_client *client, ...@@ -332,6 +399,7 @@ static int pca955x_probe(struct i2c_client *client,
struct i2c_adapter *adapter; struct i2c_adapter *adapter;
int i, err; int i, err;
struct pca955x_platform_data *pdata; struct pca955x_platform_data *pdata;
int ngpios = 0;
if (id) { if (id) {
chip = &pca955x_chipdefs[id->driver_data]; chip = &pca955x_chipdefs[id->driver_data];
...@@ -392,9 +460,19 @@ static int pca955x_probe(struct i2c_client *client, ...@@ -392,9 +460,19 @@ static int pca955x_probe(struct i2c_client *client,
pca955x_led = &pca955x->leds[i]; pca955x_led = &pca955x->leds[i];
pca955x_led->led_num = i; pca955x_led->led_num = i;
pca955x_led->pca955x = pca955x; pca955x_led->pca955x = pca955x;
pca955x_led->type = pdata->leds[i].type;
/* Platform data can specify LED names and default triggers */
if (pdata) { switch (pca955x_led->type) {
case PCA955X_TYPE_NONE:
break;
case PCA955X_TYPE_GPIO:
ngpios++;
break;
case PCA955X_TYPE_LED:
/*
* Platform data can specify LED names and
* default triggers
*/
if (pdata->leds[i].name) if (pdata->leds[i].name)
snprintf(pca955x_led->name, snprintf(pca955x_led->name,
sizeof(pca955x_led->name), "pca955x:%s", sizeof(pca955x_led->name), "pca955x:%s",
...@@ -402,23 +480,20 @@ static int pca955x_probe(struct i2c_client *client, ...@@ -402,23 +480,20 @@ static int pca955x_probe(struct i2c_client *client,
if (pdata->leds[i].default_trigger) if (pdata->leds[i].default_trigger)
pca955x_led->led_cdev.default_trigger = pca955x_led->led_cdev.default_trigger =
pdata->leds[i].default_trigger; pdata->leds[i].default_trigger;
} else {
snprintf(pca955x_led->name, sizeof(pca955x_led->name),
"pca955x:%d", i);
}
pca955x_led->led_cdev.name = pca955x_led->name; pca955x_led->led_cdev.name = pca955x_led->name;
pca955x_led->led_cdev.brightness_set_blocking = pca955x_led_set; pca955x_led->led_cdev.brightness_set_blocking =
pca955x_led_set;
err = devm_led_classdev_register(&client->dev, err = devm_led_classdev_register(&client->dev,
&pca955x_led->led_cdev); &pca955x_led->led_cdev);
if (err) if (err)
return err; return err;
}
/* Turn off LEDs */ /* Turn off LED */
for (i = 0; i < pca95xx_num_led_regs(chip->bits); i++) pca955x_led_set(&pca955x_led->led_cdev, LED_OFF);
pca955x_write_ls(client, i, 0x55); }
}
/* PWM0 is used for half brightness or 50% duty cycle */ /* PWM0 is used for half brightness or 50% duty cycle */
pca955x_write_pwm(client, 0, 255-LED_HALF); pca955x_write_pwm(client, 0, 255-LED_HALF);
...@@ -430,6 +505,34 @@ static int pca955x_probe(struct i2c_client *client, ...@@ -430,6 +505,34 @@ static int pca955x_probe(struct i2c_client *client,
pca955x_write_psc(client, 0, 0); pca955x_write_psc(client, 0, 0);
pca955x_write_psc(client, 1, 0); pca955x_write_psc(client, 1, 0);
#ifdef CONFIG_LEDS_PCA955X_GPIO
if (ngpios) {
pca955x->gpio.label = "gpio-pca955x";
pca955x->gpio.direction_input = pca955x_gpio_direction_input;
pca955x->gpio.direction_output = pca955x_gpio_direction_output;
pca955x->gpio.set = pca955x_gpio_set_value;
pca955x->gpio.get = pca955x_gpio_get_value;
pca955x->gpio.request = pca955x_gpio_request_pin;
pca955x->gpio.can_sleep = 1;
pca955x->gpio.base = -1;
pca955x->gpio.ngpio = ngpios;
pca955x->gpio.parent = &client->dev;
pca955x->gpio.owner = THIS_MODULE;
err = devm_gpiochip_add_data(&client->dev, &pca955x->gpio,
pca955x);
if (err) {
/* Use data->gpio.dev as a flag for freeing gpiochip */
pca955x->gpio.parent = NULL;
dev_warn(&client->dev, "could not add gpiochip\n");
return err;
}
dev_info(&client->dev, "gpios %i...%i\n",
pca955x->gpio.base, pca955x->gpio.base +
pca955x->gpio.ngpio - 1);
}
#endif
return 0; return 0;
} }
......
/*
* This header provides constants for pca955x LED bindings.
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*/
#ifndef _DT_BINDINGS_LEDS_PCA955X_H
#define _DT_BINDINGS_LEDS_PCA955X_H
#define PCA955X_TYPE_NONE 0
#define PCA955X_TYPE_LED 1
#define PCA955X_TYPE_GPIO 2
#endif /* _DT_BINDINGS_LEDS_PCA955X_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment