mail archive of the barebox mailing list
 help / color / mirror / Atom feed
* [PATCH 0/2] add support for pca955x i2c led drivers
@ 2018-09-05  6:20 Oleg.Karfich
  2018-09-05  6:20 ` [PATCH 1/2] led: add pca955x led support Oleg.Karfich
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Oleg.Karfich @ 2018-09-05  6:20 UTC (permalink / raw)
  To: barebox; +Cc: Oleg.Karfich

Hi,

this series adds support for the pca955x i2c-bus led drivers. The driver is
ported from the linux kernel and is reduced for locking and gpio support (could
not be tested by us, so we leave it out). Documentation is already there [1].

We tested this driver on our board with the led driver in [2].

[1] dts/Bindings/leds/leds-pca955x.txt
[2] https://www.nxp.com/docs/en/data-sheet/PCA9552.pdf

Oleg Karfich (2):
  led: add pca955x led support
  led: use max led value in case of led trigger

 drivers/led/Kconfig       |   9 +
 drivers/led/Makefile      |   1 +
 drivers/led/core.c        |   4 +-
 drivers/led/led-pca955x.c | 421 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 434 insertions(+), 1 deletion(-)
 create mode 100644 drivers/led/led-pca955x.c

-- 
2.7.4

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH 1/2] led: add pca955x led support
  2018-09-05  6:20 [PATCH 0/2] add support for pca955x i2c led drivers Oleg.Karfich
@ 2018-09-05  6:20 ` Oleg.Karfich
  2018-09-05  6:20 ` [PATCH 2/2] led: use max led value in case of led trigger Oleg.Karfich
  2018-09-07  6:21 ` [PATCH 0/2] add support for pca955x i2c led drivers Sascha Hauer
  2 siblings, 0 replies; 4+ messages in thread
From: Oleg.Karfich @ 2018-09-05  6:20 UTC (permalink / raw)
  To: barebox; +Cc: Oleg.Karfich

Signed-off-by: Oleg Karfich <oleg.karfich@wago.com>
---
 drivers/led/Kconfig       |   9 +
 drivers/led/Makefile      |   1 +
 drivers/led/led-pca955x.c | 421 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 431 insertions(+)
 create mode 100644 drivers/led/led-pca955x.c

diff --git a/drivers/led/Kconfig b/drivers/led/Kconfig
index 50f0d8f..2a5920a 100644
--- a/drivers/led/Kconfig
+++ b/drivers/led/Kconfig
@@ -31,4 +31,13 @@ config LED_TRIGGERS
 	  This allows to assign certain triggers like heartbeat or network
 	  activity to LEDs.
 
+config LED_PCA955X
+	bool "LED Support for PCA955x I2C chips"
+	depends on I2C
+	help
+	  This option enables support for LEDs connected to PCA955x
+	  LED driver chips accessed via the I2C bus.  Supported
+	  devices include PCA9550, PCA9551, PCA9552, and PCA9553.
+
+
 endif
diff --git a/drivers/led/Makefile b/drivers/led/Makefile
index 619bbcf..862e173 100644
--- a/drivers/led/Makefile
+++ b/drivers/led/Makefile
@@ -2,3 +2,4 @@ obj-$(CONFIG_LED) += core.o
 obj-$(CONFIG_LED_GPIO) += led-gpio.o
 obj-$(CONFIG_LED_PWM) += led-pwm.o
 obj-$(CONFIG_LED_TRIGGERS) += led-triggers.o
+obj-$(CONFIG_LED_TRIGGERS) += led-pca955x.o
diff --git a/drivers/led/led-pca955x.c b/drivers/led/led-pca955x.c
new file mode 100644
index 0000000..9c4f796
--- /dev/null
+++ b/drivers/led/led-pca955x.c
@@ -0,0 +1,421 @@
+/*
+ * Copyright 2007-2008 Extreme Engineering Solutions, Inc.
+ * Author: Nate Case <ncase@xes-inc.com>
+ *
+ * Copyright (C) 2018 WAGO Kontakttechnik GmbH & Co. KG <http://global.wago.com>
+ * Author: Oleg Karfich <oleg.karfich@wago.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * This code was ported from linux-4.18 kernel driver.
+ * Orginal code with it's copyright info can be found in
+ * drivers/leds/leds-pca955x.c
+ *
+ * LED driver for various PCA955x I2C LED drivers
+ *
+ * Supported devices:
+ *
+ *	Device		Description		7-bit slave address
+ *	------		-----------		-------------------
+ *	PCA9550		2-bit driver		0x60 .. 0x61
+ *	PCA9551		8-bit driver		0x60 .. 0x67
+ *	PCA9552		16-bit driver		0x60 .. 0x67
+ *	PCA9553/01	4-bit driver		0x62
+ *	PCA9553/02	4-bit driver		0x63
+ *
+ * Philips PCA955x LED driver chips follow a register map as shown below:
+ *
+ *	Control Register		Description
+ *	----------------		-----------
+ *	0x0				Input register 0
+ *					..
+ *	NUM_INPUT_REGS - 1		Last Input register X
+ *
+ *	NUM_INPUT_REGS			Frequency prescaler 0
+ *	NUM_INPUT_REGS + 1		PWM register 0
+ *	NUM_INPUT_REGS + 2		Frequency prescaler 1
+ *	NUM_INPUT_REGS + 3		PWM register 1
+ *
+ *	NUM_INPUT_REGS + 4		LED selector 0
+ *	NUM_INPUT_REGS + 4
+ *	    + NUM_LED_REGS - 1		Last LED selector
+ *
+ *  where NUM_INPUT_REGS and NUM_LED_REGS vary depending on how many
+ *  bits the chip supports.
+ *
+  */
+
+#include <common.h>
+#include <init.h>
+#include <led.h>
+#include <malloc.h>
+#include <i2c/i2c.h>
+#include <of_device.h>
+
+/* LED select registers determine the source that drives LED outputs */
+#define PCA955X_LS_LED_ON	0x0	/* Output LOW */
+#define PCA955X_LS_LED_OFF	0x1	/* Output HI-Z */
+#define PCA955X_LS_BLINK0	0x2	/* Blink at PWM0 rate */
+#define PCA955X_LS_BLINK1	0x3	/* Blink at PWM1 rate */
+
+enum led_brightness {
+	LED_OFF		= 0,
+	LED_HALF	= 127,
+	LED_FULL	= 255,
+};
+
+enum pca955x_type {
+	pca9550,
+	pca9551,
+	pca9552,
+	pca9553,
+};
+
+struct pca955x_chipdef {
+	int	bits;
+	u8	slv_addr;	/* 7-bit slave address mask */
+	int	slv_addr_shift;	/* Number of bits to ignore */
+};
+
+static struct pca955x_chipdef pca955x_chipdefs[] = {
+	[pca9550] = {
+		.bits		= 2,
+		.slv_addr	= /* 110000x */ 0x60,
+		.slv_addr_shift	= 1,
+	},
+	[pca9551] = {
+		.bits		= 8,
+		.slv_addr	= /* 1100xxx */ 0x60,
+		.slv_addr_shift	= 3,
+	},
+	[pca9552] = {
+		.bits		= 16,
+		.slv_addr	= /* 1100xxx */ 0x60,
+		.slv_addr_shift	= 3,
+	},
+	[pca9553] = {
+		.bits		= 4,
+		.slv_addr	= /* 110001x */ 0x62,
+		.slv_addr_shift	= 1,
+	},
+};
+
+static const struct platform_device_id led_pca955x_id[] = {
+	{ "pca9550", pca9550 },
+	{ "pca9551", pca9551 },
+	{ "pca9552", pca9552 },
+	{ "pca9553", pca9553 },
+	{ }
+};
+
+struct pca955x {
+	struct pca955x_led *leds;
+	struct pca955x_chipdef	*chipdef;
+	struct i2c_client	*client;
+};
+
+struct pca955x_led {
+	struct pca955x	*pca955x;
+	struct led	led_cdev;
+	int		led_num;	/* 0 .. 15 potentially */
+	char		name[32];
+};
+
+struct pca955x_platform_data {
+	struct pca955x_led	*leds;
+	int			num_leds;
+};
+
+/* 8 bits per input register */
+static inline int pca95xx_num_input_regs(int bits)
+{
+	return (bits + 7) / 8;
+}
+
+/*
+ * Return an LED selector register value based on an existing one, with
+ * the appropriate 2-bit state value set for the given LED number (0-3).
+ */
+static inline u8 pca955x_ledsel(u8 oldval, int led_num, int state)
+{
+	return (oldval & (~(0x3 << (led_num << 1)))) |
+		((state & 0x3) << (led_num << 1));
+}
+
+/*
+ * Write to frequency prescaler register, used to program the
+ * period of the PWM output.  period = (PSCx + 1) / 38
+ */
+static int pca955x_write_psc(struct i2c_client *client, int n, u8 val)
+{
+	struct pca955x *pca955x = i2c_get_clientdata(client);
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(client,
+		pca95xx_num_input_regs(pca955x->chipdef->bits) + 2*n,
+		val);
+	if (ret < 0)
+		dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n",
+			__func__, n, val, ret);
+	return ret;
+}
+
+/*
+ * Write to PWM register, which determines the duty cycle of the
+ * output.  LED is OFF when the count is less than the value of this
+ * register, and ON when it is greater.  If PWMx == 0, LED is always OFF.
+ *
+ * Duty cycle is (256 - PWMx) / 256
+ */
+static int pca955x_write_pwm(struct i2c_client *client, int n, u8 val)
+{
+	struct pca955x *pca955x = i2c_get_clientdata(client);
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(client,
+		pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + 2*n,
+		val);
+	if (ret < 0)
+		dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n",
+			__func__, n, val, ret);
+	return ret;
+}
+
+/*
+ * Write to LED selector register, which determines the source that
+ * drives the LED output.
+ */
+static int pca955x_write_ls(struct i2c_client *client, int n, u8 val)
+{
+	struct pca955x *pca955x = i2c_get_clientdata(client);
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(client,
+		pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n,
+		val);
+	if (ret < 0)
+		dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n",
+			__func__, n, val, ret);
+	return ret;
+}
+
+/*
+ * Read the LED selector register, which determines the source that
+ * drives the LED output.
+ */
+static int pca955x_read_ls(struct i2c_client *client, int n, u8 *val)
+{
+	struct pca955x *pca955x = i2c_get_clientdata(client);
+	int ret;
+
+	ret = i2c_smbus_read_byte_data(client,
+		pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n);
+	if (ret < 0) {
+		dev_err(&client->dev, "%s: reg 0x%x, err %d\n",
+			__func__, n, ret);
+		return ret;
+	}
+	*val = (u8)ret;
+	return 0;
+}
+
+static void pca955x_led_set(struct led *led_cdev, unsigned int value)
+{
+	struct pca955x_led *pca955x_led;
+	struct pca955x *pca955x;
+	u8 ls;
+	int chip_ls;	/* which LSx to use (0-3 potentially) */
+	int ls_led;	/* which set of bits within LSx to use (0-3) */
+	int ret;
+
+	pca955x_led = container_of(led_cdev, struct pca955x_led, led_cdev);
+	pca955x = pca955x_led->pca955x;
+
+	chip_ls = pca955x_led->led_num / 4;
+	ls_led = pca955x_led->led_num % 4;
+
+	ret = pca955x_read_ls(pca955x->client, chip_ls, &ls);
+	if (ret)
+		return;
+
+	switch (value) {
+	case LED_FULL:
+		ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_ON);
+		break;
+	case LED_OFF:
+		ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_OFF);
+		break;
+	case LED_HALF:
+		ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK0);
+		break;
+	default:
+		/*
+		 * Use PWM1 for all other values.  This has the unwanted
+		 * side effect of making all LEDs on the chip share the
+		 * same brightness level if set to a value other than
+		 * OFF, HALF, or FULL.  But, this is probably better than
+		 * just turning off for all other values.
+		 */
+		ret = pca955x_write_pwm(pca955x->client, 1, 255 - value);
+		if (ret)
+			return;
+		ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK1);
+		break;
+	}
+
+	pca955x_write_ls(pca955x->client, chip_ls, ls);
+}
+
+static struct pca955x_platform_data *
+led_pca955x_pdata_of_init(struct device_node *np, struct pca955x *pca955x)
+{
+	struct device_node *child;
+	struct pca955x_chipdef *chip = pca955x->chipdef;
+	struct pca955x_platform_data *pdata;
+	int count, err;
+
+	count = of_get_child_count(np);
+	if (!count || count > chip->bits)
+		return ERR_PTR(-ENODEV);
+
+	pdata = xzalloc(sizeof(*pdata));
+	if (!pdata)
+		return ERR_PTR(-ENOMEM);
+
+	pdata->leds = xzalloc(chip->bits * sizeof(struct pca955x_led));
+	if (!pdata->leds)
+		return ERR_PTR(-ENOMEM);
+
+	for_each_child_of_node(np, child) {
+		struct pca955x_led *pca955x_led;
+		const char *name;
+		u32 reg;
+		int res;
+
+		res = of_property_read_u32(child, "reg", &reg);
+		if ((res != 0) || (reg >= chip->bits))
+			continue;
+
+		pca955x_led = &pdata->leds[reg];
+		pca955x_led->led_num = reg;
+		pca955x_led->pca955x = pca955x;
+
+		if (of_property_read_string(child, "label", &name))
+			name = child->name;
+
+		snprintf(pca955x_led->name, sizeof(pca955x_led->name),
+								"%s", name);
+
+		pca955x_led->led_cdev.name = pca955x_led->name;
+		pca955x_led->led_cdev.set = pca955x_led_set;
+		pca955x_led->led_cdev.num = pca955x_led->led_num;
+		pca955x_led->led_cdev.max_value = 255;
+
+		err = led_register(&pca955x_led->led_cdev);
+		if (err)
+			return ERR_PTR(err);
+
+		/* Turn off LED */
+		pca955x_led_set(&pca955x_led->led_cdev, LED_OFF);
+
+		led_of_parse_trigger(&pca955x_led->led_cdev, child);
+	}
+
+	pdata->num_leds = count;
+
+	return pdata;
+}
+
+static const struct of_device_id of_pca955x_match[] = {
+	{ .compatible = "nxp,pca9550", .data = (void *)pca9550 },
+	{ .compatible = "nxp,pca9551", .data = (void *)pca9551 },
+	{ .compatible = "nxp,pca9552", .data = (void *)pca9552 },
+	{ .compatible = "nxp,pca9553", .data = (void *)pca9553 },
+	{},
+};
+
+static int led_pca955x_probe(struct device_d *dev)
+{
+	struct pca955x *pca955x;
+	struct pca955x_led *pca955x_led;
+	struct pca955x_chipdef *chip;
+	struct i2c_client *client;
+	int err;
+	struct pca955x_platform_data *pdata;
+
+	chip = &pca955x_chipdefs[dev->id_entry->driver_data];
+	client = to_i2c_client(dev);
+
+	/* Make sure the slave address / chip type combo given is possible */
+	if ((client->addr & ~((1 << chip->slv_addr_shift) - 1)) !=
+	    chip->slv_addr) {
+		dev_err(dev, "invalid slave address %02x\n", client->addr);
+		return -ENODEV;
+	}
+
+	dev_info(dev, "leds-pca955x: Using %s %d-bit LED driver at "
+			"slave address 0x%02x\n",
+			client->dev.name, chip->bits, client->addr);
+
+	pca955x = xzalloc(sizeof(*pca955x));
+	if (!pca955x)
+		return -ENOMEM;
+
+	pca955x->leds = xzalloc(chip->bits * sizeof(*pca955x_led));
+	if (!pca955x->leds)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, pca955x);
+
+	pca955x->client = client;
+	pca955x->chipdef = chip;
+
+	pdata =	led_pca955x_pdata_of_init(dev->device_node, pca955x);
+	if (IS_ERR(pdata))
+		return PTR_ERR(pdata);
+
+	if (pdata->num_leds != chip->bits)
+		dev_warn(dev, "board info claims %d LEDs on a %d-bit chip\n",
+			pdata->num_leds, chip->bits);
+
+	/* PWM0 is used for half brightness or 50% duty cycle */
+	err = pca955x_write_pwm(client, 0, 255 - LED_HALF);
+	if (err)
+		return err;
+
+	/* PWM1 is used for variable brightness, default to OFF */
+	err = pca955x_write_pwm(client, 1, 0);
+	if (err)
+		return err;
+
+	/* Set to fast frequency so we do not see flashing */
+	err = pca955x_write_psc(client, 0, 0);
+	if (err)
+		return err;
+	err = pca955x_write_psc(client, 1, 0);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static struct driver_d led_pca955x_driver = {
+	.name	= "led-pca955x",
+	.probe	= led_pca955x_probe,
+	.id_table = led_pca955x_id,
+	.of_compatible = DRV_OF_COMPAT(of_pca955x_match),
+};
+
+static int __init led_pca955x_init(void)
+{
+	return i2c_driver_register(&led_pca955x_driver);
+}
+device_initcall(led_pca955x_init);
-- 
2.7.4

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH 2/2] led: use max led value in case of led trigger
  2018-09-05  6:20 [PATCH 0/2] add support for pca955x i2c led drivers Oleg.Karfich
  2018-09-05  6:20 ` [PATCH 1/2] led: add pca955x led support Oleg.Karfich
@ 2018-09-05  6:20 ` Oleg.Karfich
  2018-09-07  6:21 ` [PATCH 0/2] add support for pca955x i2c led drivers Sascha Hauer
  2 siblings, 0 replies; 4+ messages in thread
From: Oleg.Karfich @ 2018-09-05  6:20 UTC (permalink / raw)
  To: barebox; +Cc: Oleg.Karfich

The led_poller function blink_func uses for flashing and blinking only the
values 1/0 for setting the leds. In case of an e.g. gpio led this is true. But
in case of pwm driven leds, where someone could dimm the leds, the value of 1
dimms the led. Use the max value for blinking and flashing of a led when enabling.

Signed-off-by: Oleg Karfich <oleg.karfich@wago.com>
---
 drivers/led/core.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/led/core.c b/drivers/led/core.c
index 6f66de0..a388e6b 100644
--- a/drivers/led/core.c
+++ b/drivers/led/core.c
@@ -127,7 +127,7 @@ static void led_blink_func(struct poller_struct *poller)
 	struct led *led;
 
 	list_for_each_entry(led, &leds, list) {
-		bool on;
+		int on;
 
 		if (!led->blink && !led->flash)
 			continue;
@@ -137,6 +137,8 @@ static void led_blink_func(struct poller_struct *poller)
 		}
 
 		on = !(led->blink_next_state % 2);
+		if (on)
+			on = led->max_value;
 
 		led->blink_next_event = get_time_ns() +
 			(led->blink_states[led->blink_next_state] * MSECOND);
-- 
2.7.4

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH 0/2] add support for pca955x i2c led drivers
  2018-09-05  6:20 [PATCH 0/2] add support for pca955x i2c led drivers Oleg.Karfich
  2018-09-05  6:20 ` [PATCH 1/2] led: add pca955x led support Oleg.Karfich
  2018-09-05  6:20 ` [PATCH 2/2] led: use max led value in case of led trigger Oleg.Karfich
@ 2018-09-07  6:21 ` Sascha Hauer
  2 siblings, 0 replies; 4+ messages in thread
From: Sascha Hauer @ 2018-09-07  6:21 UTC (permalink / raw)
  To: Oleg.Karfich; +Cc: barebox

On Wed, Sep 05, 2018 at 06:20:38AM +0000, Oleg.Karfich@wago.com wrote:
> Hi,
> 
> this series adds support for the pca955x i2c-bus led drivers. The driver is
> ported from the linux kernel and is reduced for locking and gpio support (could
> not be tested by us, so we leave it out). Documentation is already there [1].
> 
> We tested this driver on our board with the led driver in [2].
> 
> [1] dts/Bindings/leds/leds-pca955x.txt
> [2] https://www.nxp.com/docs/en/data-sheet/PCA9552.pdf
> 
> Oleg Karfich (2):
>   led: add pca955x led support
>   led: use max led value in case of led trigger

Applied, thanks

Sascha

> 
>  drivers/led/Kconfig       |   9 +
>  drivers/led/Makefile      |   1 +
>  drivers/led/core.c        |   4 +-
>  drivers/led/led-pca955x.c | 421 ++++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 434 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/led/led-pca955x.c
> 
> -- 
> 2.7.4
> 
> _______________________________________________
> barebox mailing list
> barebox@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/barebox
> 

-- 
Pengutronix e.K.                           |                             |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

_______________________________________________
barebox mailing list
barebox@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/barebox

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2018-09-07  6:22 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-09-05  6:20 [PATCH 0/2] add support for pca955x i2c led drivers Oleg.Karfich
2018-09-05  6:20 ` [PATCH 1/2] led: add pca955x led support Oleg.Karfich
2018-09-05  6:20 ` [PATCH 2/2] led: use max led value in case of led trigger Oleg.Karfich
2018-09-07  6:21 ` [PATCH 0/2] add support for pca955x i2c led drivers Sascha Hauer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox