All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH RFC 0/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
@ 2016-02-23 18:17 David Rivshin (Allworx)
  2016-02-23 18:17 ` [PATCH RFC 1/3] DT: Add vendor prefix for Integrated Silicon Solutions Inc David Rivshin (Allworx)
                   ` (2 more replies)
  0 siblings, 3 replies; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-02-23 18:17 UTC (permalink / raw)
  To: linux-leds, devicetree
  Cc: Richard Purdie, Jacek Anaszewski, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Stefan Wahren

From: David Rivshin <drivshin@allworx.com>

This series adds support for the ISSI IS31FL32xx family of I2C LED
drivers. I'm posting this first as an RFC as there are a couple of
items I'd especially like feedback on:

In the binding, I choose to have 'reg' be 1-based in order to follow
the hardware documentation. It seems 0-based is the normal choice.

In the binding, I used the 'reg' property for the LED channel, as
it seemed ePAPR required that name. I also considered naming the
property 'chan', and not pretending that it represented a bus
address at all.

In the binding, max-brightness is an optional property. In other
bindings it seems to be either unsupported, or required.

If there is a problem with the devicetree for an LED subnode, I
ignore that one node and continue on with the rest. I could see
an argument for just bailing on the whole probe if any subnode is
bad.

I left the LED enable bit always enabled, and just let the PWM be
set to 0 for "off" naturally.

I did not see a need to use regmap, as there was never a need for
read/modify/write. In the case of the 3218/3216 This was facilitated
by the above choice.

I did not see for a mutex to protect any of the data, as it was
read-only after probing, and I2C core already has a mutex to protect
the device.

I implemented support for all 4 devices in a single driver mainly
because I really need is31fl3236 support, but I had access to an
is31fl3216 eval board early, and could get a lot of milage out of it.
The other two parts were similar enough to one of those that it was
trivial include them as well (although untested).
    - The 3236 and 3235 are nearly identical (differing just in
      number of channels and some register addresses).
    - The 3218 is like the 3236/3235, except it packs the enable
      bits into 3 registers, doesn't support per-channel
      max-current divisor, and lacks a global control register.
    - The 3216 has the most differences. It has some additional
      register differences, and goes on to supports a number
      of (relatively) complex extra features:
        - using some channels as GPIOs
        - HW animation of LEDs
        - HW modulation of LEDs according to an audio input
I could certainly see an argument for having the 3236/3235 driver be
separate from the other devices. I'm not sure where the desired
balance between duplicate code (mostly in probing) vs simpler drivers.
For reference, I think about 70 lines (15%) are unique to the 3216,
and another 20 (5%) unique to the 3218.

I should also mention that I just noticed the is31fl3218 and
is31fl3216 appear to be the same devices as the SI-EN SN3218 and
SN3216. ISSI acquired SI-EN in 2011, and seems to have just rebranded
those devices. Either this driver or the recently-added SN3218 driver
should work for both the ISSI and SN "3218" parts.
Obviously we don't need both implementations, though I have no
preference for which one to use. For instance, I'd have no argument
with just adding a compatible entry for is31fl3218 to the sn3218
driver (since that's trivial), remove the 3216/3218 support from
this driver, and call it a day. I suspect that anyone actually using
the 3216 in product would need some of the advanced functions that
I did not implement anyways.

David Rivshin (3):
  DT: Add vendor prefix for Integrated Silicon Solutions Inc.
  DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers
  leds: Add driver for the ISSI IS31FL32xx family of LED drivers

 .../devicetree/bindings/leds/leds-is31fl32xx.txt   |  51 +++
 .../devicetree/bindings/vendor-prefixes.txt        |   1 +
 drivers/leds/Kconfig                               |   9 +
 drivers/leds/Makefile                              |   1 +
 drivers/leds/leds-is31fl32xx.c                     | 442 +++++++++++++++++++++
 5 files changed, 504 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
 create mode 100644 drivers/leds/leds-is31fl32xx.c

-- 
2.5.0

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

* [PATCH RFC 1/3] DT: Add vendor prefix for Integrated Silicon Solutions Inc.
  2016-02-23 18:17 [PATCH RFC 0/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers David Rivshin (Allworx)
@ 2016-02-23 18:17 ` David Rivshin (Allworx)
  2016-02-23 23:36   ` Rob Herring
  2016-02-23 18:17 ` [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers David Rivshin (Allworx)
  2016-02-23 18:17 ` [PATCH RFC 3/3] leds: Add driver " David Rivshin (Allworx)
  2 siblings, 1 reply; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-02-23 18:17 UTC (permalink / raw)
  To: linux-leds, devicetree
  Cc: Richard Purdie, Jacek Anaszewski, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Stefan Wahren

From: David Rivshin <drivshin@allworx.com>

ISSI is the stock ticker Integrated Silicon Solutions Inc.
Company website: http://www.issi.com

Signed-off-by: David Rivshin <drivshin@allworx.com>
---
 Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
index 52f22f9..dd72e05 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.txt
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -120,6 +120,7 @@ intercontrol	Inter Control Group
 invensense	InvenSense Inc.
 isee	ISEE 2007 S.L.
 isil	Intersil
+issi	Integrated Silicon Solutions Inc.
 jedec	JEDEC Solid State Technology Association
 karo	Ka-Ro electronics GmbH
 keymile	Keymile GmbH
-- 
2.5.0

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

* [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers
  2016-02-23 18:17 [PATCH RFC 0/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers David Rivshin (Allworx)
  2016-02-23 18:17 ` [PATCH RFC 1/3] DT: Add vendor prefix for Integrated Silicon Solutions Inc David Rivshin (Allworx)
@ 2016-02-23 18:17 ` David Rivshin (Allworx)
  2016-02-24  1:15   ` Rob Herring
  2016-02-24 16:04   ` Jacek Anaszewski
  2016-02-23 18:17 ` [PATCH RFC 3/3] leds: Add driver " David Rivshin (Allworx)
  2 siblings, 2 replies; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-02-23 18:17 UTC (permalink / raw)
  To: linux-leds, devicetree
  Cc: Richard Purdie, Jacek Anaszewski, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Stefan Wahren

From: David Rivshin <drivshin@allworx.com>

This adds a binding description for the is31fl3236/35/18/16 I2C LED
drivers.

Signed-off-by: David Rivshin <drivshin@allworx.com>
---
 .../devicetree/bindings/leds/leds-is31fl32xx.txt   | 51 ++++++++++++++++++++++
 1 file changed, 51 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt

diff --git a/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt b/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
new file mode 100644
index 0000000..0a05a1d
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
@@ -0,0 +1,51 @@
+Binding for ISSI IS31FL32xx LED Drivers
+
+The IS31FL32xx family of LED drivers are I2C devices with multiple
+constant-current channels, each with independent 256-level PWM control.
+Each LED is represented as a sub-node of the device.
+
+Required properties:
+- compatible: one of
+	issi,is31fl3236
+	issi,is31fl3235
+	issi,is31fl3218
+	issi,is31fl3216
+- reg: I2C slave address
+- address-cells : must be 1
+- size-cells : must be 0
+
+LED sub-node properties:
+- reg : LED channel number (1..N)
+- max-brightness : (optional) Maximum brightness possible for the LED.
+  Default is 255.
+- label :  (optional)
+  see Documentation/devicetree/bindings/leds/common.txt
+- linux,default-trigger :  (optional)
+  see Documentation/devicetree/bindings/leds/common.txt
+
+
+Example:
+
+leds: is31fl3236@3c {
+	compatible = "issi,is31fl3236";
+	reg = <0x3c>;
+
+	led@1 {
+		reg = <1>;
+		label = "EB:blue:usr0";
+		max-brightness = <128>;
+	};
+	led@2 {
+		reg = <2>;
+		label = "EB:blue:usr1";
+	};
+	...
+	led@36 {
+		reg = <36>;
+		label = "EB:blue:usr35";
+		max-brightness = <255>;
+	};
+};
+
+For more product information please see the link below:
+http://www.issi.com/US/product-analog-fxled-driver.shtml
\ No newline at end of file
-- 
2.5.0

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

* [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-23 18:17 [PATCH RFC 0/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers David Rivshin (Allworx)
  2016-02-23 18:17 ` [PATCH RFC 1/3] DT: Add vendor prefix for Integrated Silicon Solutions Inc David Rivshin (Allworx)
  2016-02-23 18:17 ` [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers David Rivshin (Allworx)
@ 2016-02-23 18:17 ` David Rivshin (Allworx)
  2016-02-23 18:45   ` Mark Rutland
  2016-02-24 16:04   ` Jacek Anaszewski
  2 siblings, 2 replies; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-02-23 18:17 UTC (permalink / raw)
  To: linux-leds, devicetree
  Cc: Richard Purdie, Jacek Anaszewski, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Stefan Wahren

From: David Rivshin <drivshin@allworx.com>

The IS31FL32xx family of LED drivers are I2C devices with multiple
constant-current channels, each with independent 256-level PWM control.

HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml

This has been tested on the IS31FL3236 and IS31FL3216 on an ARM
(TI am335x) platform.

The programming paradigm of these devices is similar in the following
ways:
 - All registers are 8 bit
 - All LED control registers are write-only
 - Each LED channel has a PWM register (0-255)
 - PWM register writes are shadowed until an Update register is poked
 - All have a concept of Software Shutdown, which disables output

However, there are some differences in devices:
 - 3236/3235 have a separate Control register for each LED,
   (3218/3216 pack the enable bits into fewer registers)
 - 3236/3235 have a per-channel current divisor setting
 - 3236/3235 have a Global Control register that can turn off all LEDs
 - 3216 is unique in a number of ways
    - OUT9-OUT16 can be configured as GPIOs instead of LED controls
    - LEDs can be programmed with an 8-frame animation, with
      programmable delay between frames
    - LEDs can be modulated by an input audio signal
    - Max output current can be adjusted from 1/4 to 2x globally
    - Has a Configuration register instead of a Shutdown register

This driver currently only supports the base PWM control function
of these devices. The following features of these devices are not
implemented, although it should be possible to add them in the future:
 - All devices are capable of going into a lower-power "software
   shutdown" mode.
 - The is31fl3236 and is31fl3235 can reduce the max output current
   per-channel with a divisor of 1, 2, 3, or 4.
 - The is31fl3216 can use some LED channels as GPIOs instead.
 - The is31fl3216 can animate LEDs in hardware.
 - The is31fl3216 can modulate LEDs according to an audio input.
 - The is31fl3216 can reduce/increase max output current globally.

Signed-off-by: David Rivshin <drivshin@allworx.com>
---
 drivers/leds/Kconfig           |   9 +
 drivers/leds/Makefile          |   1 +
 drivers/leds/leds-is31fl32xx.c | 442 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 452 insertions(+)
 create mode 100644 drivers/leds/leds-is31fl32xx.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 1034696..8f6c46f 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -580,6 +580,15 @@ config LEDS_SN3218
 	  This driver can also be built as a module. If so the module
 	  will be called leds-sn3218.
 
+config LEDS_IS31FL32XX
+	tristate "Driver for ISSI IS31FL32XX I2C LED driver chip family"
+	depends on LEDS_CLASS && I2C && OF
+	help
+	  Say Y here to include support for the ISSI 31FL32XX LED driver family.
+	  They are I2C devices with multiple constant-current channels, each
+	  with independent 256-level PWM control. This will only work with
+	  device tree enabled devices.
+
 comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
 
 config LEDS_BLINKM
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 89c9b6f..3fdf313 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -67,6 +67,7 @@ obj-$(CONFIG_LEDS_KTD2692)		+= leds-ktd2692.o
 obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
 obj-$(CONFIG_LEDS_SEAD3)		+= leds-sead3.o
 obj-$(CONFIG_LEDS_SN3218)		+= leds-sn3218.o
+obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
 
 # LED SPI Drivers
 obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
new file mode 100644
index 0000000..8dea518
--- /dev/null
+++ b/drivers/leds/leds-is31fl32xx.c
@@ -0,0 +1,442 @@
+/*
+ * linux/drivers/leds-is31fl32xx.c
+ *
+ * Driver for ISSI IS31FL32xx family of I2C LED controllers
+ *
+ * Copyright 2015 Allworx Corp.
+ *
+ *
+ * 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.
+ *
+ * HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+
+#ifdef DEBUG
+ #undef dev_dbg
+ #define dev_dbg dev_info
+#endif
+
+/* Used to indicate a device has no such register */
+#define IS31FL32XX_REG_NONE 0xFF
+
+#define IS31FL3216_CONFIG_REG 0x00
+#define IS31FL3216_LIGHTING_EFFECT_REG 0x03
+#define IS31FL3216_CHANNEL_CONFIG_REG 0x04
+
+struct is31fl32xx_priv;
+struct is31fl32xx_led_data {
+	struct led_classdev cdev;
+	u8 channel; /* 1-based, max priv->cdef->channels */
+	struct is31fl32xx_priv *priv;
+};
+
+struct is31fl32xx_priv {
+	const struct is31fl32xx_chipdef *cdef;
+	struct i2c_client *client;
+	unsigned int num_leds;
+	struct is31fl32xx_led_data leds[0];
+};
+
+/**
+ * struct is31fl32xx_chipdef - chip-specific attributes
+ * @channels            : Number of LED channels
+ * @shutdown_reg        : address of Shutdown register (optional)
+ * @pwm_update_reg      : address of PWM Update register
+ * @global_control_reg	: address of Global Control register (optional)
+ * @reset_reg           : address of Reset register (optional)
+ * @pwm_register_base	: address of first PWM register
+ * @pwm_registers_reversed: : true if PWM registers count down instead of up
+ * @led_control_register_base : address of first LED control register (optional)
+ * @enable_bits_per_led_control_register: number of LEDs enable bits in each
+ * @reset_func:         : pointer to reset function
+ *
+ * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE
+ * indicates that this chip has no such register.
+ *
+ * If non-NULL, @reset_func will be called during probing to set all
+ * necessary registers to a known initialization state. This is needed
+ * for chips that do not have a @reset_reg.
+ *
+ * @enable_bits_per_led_control_register must be >=1 if
+ * @led_control_register_base != %IS31FL32XX_REG_NONE.
+ */
+struct is31fl32xx_chipdef {
+	u8	channels;
+	u8	shutdown_reg;
+	u8	pwm_update_reg;
+	u8	global_control_reg;
+	u8	reset_reg;
+	u8	pwm_register_base;
+	bool	pwm_registers_reversed;
+	u8	led_control_register_base;
+	u8	enable_bits_per_led_control_register;
+	int (*reset_func)(struct is31fl32xx_priv *priv);
+};
+
+static const struct is31fl32xx_chipdef is31fl3236_cdef = {
+	.channels				= 36,
+	.shutdown_reg				= 0x00,
+	.pwm_update_reg				= 0x25,
+	.global_control_reg			= 0x4a,
+	.reset_reg				= 0x4f,
+	.pwm_register_base			= 0x01,
+	.led_control_register_base		= 0x26,
+	.enable_bits_per_led_control_register	= 1,
+};
+
+static const struct is31fl32xx_chipdef is31fl3235_cdef = {
+	.channels				= 28,
+	.shutdown_reg				= 0x00,
+	.pwm_update_reg				= 0x25,
+	.global_control_reg			= 0x4a,
+	.reset_reg				= 0x4f,
+	.pwm_register_base			= 0x05,
+	.led_control_register_base		= 0x2a,
+	.enable_bits_per_led_control_register	= 1,
+};
+
+static const struct is31fl32xx_chipdef is31fl3218_cdef = {
+	.channels				= 18,
+	.shutdown_reg				= 0x00,
+	.pwm_update_reg				= 0x16,
+	.global_control_reg			= IS31FL32XX_REG_NONE,
+	.reset_reg				= 0x17,
+	.pwm_register_base			= 0x01,
+	.led_control_register_base		= 0x13,
+	.enable_bits_per_led_control_register	= 6,
+};
+
+static int is31fl3216_reset(struct is31fl32xx_priv *priv);
+static const struct is31fl32xx_chipdef is31fl3216_cdef = {
+	.channels				= 16,
+	.shutdown_reg				= IS31FL32XX_REG_NONE,
+	.pwm_update_reg				= 0xB0,
+	.global_control_reg			= IS31FL32XX_REG_NONE,
+	.reset_reg				= IS31FL32XX_REG_NONE,
+	.pwm_register_base			= 0x10,
+	.pwm_registers_reversed			= true,
+	.led_control_register_base		= 0x01,
+	.enable_bits_per_led_control_register	= 8,
+	.reset_func				= is31fl3216_reset,
+};
+
+static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)
+{
+	int ret;
+
+	dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
+
+	ret =  i2c_smbus_write_byte_data(priv->client, reg, val);
+	if (ret) {
+		dev_err(&priv->client->dev,
+			"register write to 0x%02X failed (error %d)",
+			reg, ret);
+	}
+	return ret;
+}
+
+/*
+ * Custom reset function for IS31FL3216 because it does not have a RESET
+ * register the way that the other IS31FL32xx chips do. We don't bother
+ * writing the GPIO and animation registers, because the registers we
+ * do write ensure those will have no effect.
+ */
+static int is31fl3216_reset(struct is31fl32xx_priv *priv)
+{
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < priv->cdef->channels; i++) {
+		ret = is31fl32xx_write(priv, priv->cdef->pwm_register_base+i,
+				       0x00);
+		if (ret)
+			return ret;
+	}
+	ret = is31fl32xx_write(priv, priv->cdef->pwm_update_reg, 0);
+	if (ret)
+		return ret;
+	ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, 0x00);
+	if (ret)
+		return ret;
+	ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, 0x00);
+	if (ret)
+		return ret;
+	ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, 0x00);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+
+static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,
+				     enum led_brightness brightness)
+{
+	const struct is31fl32xx_led_data *led_data =
+		container_of(led_cdev, struct is31fl32xx_led_data, cdev);
+	const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef;
+	u8 pwm_register_offset;
+	int ret;
+
+	dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);
+
+	/* NOTE: led_data->channel is 1-based */
+	if (cdef->pwm_registers_reversed)
+		pwm_register_offset = cdef->channels - led_data->channel;
+	else
+		pwm_register_offset = led_data->channel - 1;
+
+	ret = is31fl32xx_write(led_data->priv,
+			       cdef->pwm_register_base + pwm_register_offset,
+			       brightness);
+	if (ret)
+		return ret;
+
+	return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);
+
+}
+
+static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
+{
+	const struct is31fl32xx_chipdef *cdef = priv->cdef;
+	int ret;
+
+	if (cdef->reset_reg != IS31FL32XX_REG_NONE) {
+		ret = is31fl32xx_write(priv, cdef->reset_reg, 0);
+		if (ret)
+			return ret;
+	}
+	if (cdef->reset_func) {
+		ret = cdef->reset_func(priv);
+		if (ret)
+			return ret;
+	}
+	if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) {
+		u8 value =
+		    GENMASK(cdef->enable_bits_per_led_control_register-1, 0);
+		u8 num_regs = cdef->channels /
+				cdef->enable_bits_per_led_control_register;
+		int i;
+
+		for (i = 0; i < num_regs; i++) {
+			ret = is31fl32xx_write(priv,
+					       cdef->led_control_register_base+i,
+					       value);
+			if (ret)
+				return ret;
+		}
+	}
+	if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) {
+		ret = is31fl32xx_write(priv, cdef->shutdown_reg, BIT(0));
+		if (ret)
+			return ret;
+	}
+	if (cdef->global_control_reg != IS31FL32XX_REG_NONE) {
+		ret = is31fl32xx_write(priv, cdef->global_control_reg, 0x00);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static inline size_t sizeof_is31fl32xx_priv(int num_leds)
+{
+	return sizeof(struct is31fl32xx_priv) +
+		      (sizeof(struct is31fl32xx_led_data) * num_leds);
+}
+
+static int is31fl32xx_parse_child_dt(const struct device *dev,
+				     const struct device_node *child,
+				     struct is31fl32xx_led_data *led_data)
+{
+	struct led_classdev *cdev = &led_data->cdev;
+	int ret = 0;
+	u32 reg;
+
+	cdev->name = of_get_property(child, "label", NULL) ? : child->name;
+
+	ret = of_property_read_u32(child, "reg", &reg);
+	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
+		dev_err(dev,
+			"Child node %s does not have a valid reg property\n",
+			child->name);
+		return -EINVAL;
+	}
+	led_data->channel = reg;
+
+	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
+						NULL);
+	cdev->brightness = LED_OFF;
+	ret = of_property_read_u32(child, "max-brightness",
+				   &cdev->max_brightness);
+	if (ret == -EINVAL) {
+		cdev->max_brightness = 255;
+	} else if (ret) {
+		dev_dbg(dev,
+			"Child node %s has an invalid max-brightness property\n",
+			child->name);
+		return -EINVAL;
+	}
+
+	cdev->brightness_set_blocking = is31fl32xx_brightness_set;
+	return 0;
+}
+
+static struct is31fl32xx_led_data *is31fl32xx_find_led_data(
+					struct is31fl32xx_priv *priv,
+					u8 channel)
+{
+	size_t i;
+
+	for (i = 0; i < priv->num_leds; i++) {
+		if (priv->leds[i].channel == channel)
+			return &priv->leds[i];
+	}
+	return NULL;
+}
+
+static int is31fl32xx_parse_dt(struct device *dev,
+			       struct is31fl32xx_priv *priv)
+{
+	struct device_node *child;
+
+	for_each_child_of_node(dev->of_node, child) {
+		struct is31fl32xx_led_data *led_data =
+			&priv->leds[priv->num_leds];
+		int ret = 0;
+		const struct is31fl32xx_led_data *other_led_data;
+
+		led_data->priv = priv;
+
+		ret = is31fl32xx_parse_child_dt(dev, child, led_data);
+		if (ret)
+			continue;
+
+		/* Detect if channel is already in use by another child */
+		other_led_data = is31fl32xx_find_led_data(priv,
+							  led_data->channel);
+		if (other_led_data) {
+			dev_err(dev,
+				"%s ignored: channel %d already used by %s",
+				led_data->cdev.name,
+				led_data->channel,
+				other_led_data->cdev.name);
+			continue;
+		}
+
+		ret = devm_led_classdev_register(dev, &led_data->cdev);
+		if (ret == 0) {
+			priv->num_leds++;
+		} else {
+			dev_err(dev, "failed to register PWM led for %s: %d\n",
+				led_data->cdev.name, ret);
+		}
+	}
+
+	return 0;
+}
+
+static const struct of_device_id of_is31fl31xx_match[] = {
+	{ .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },
+	{ .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },
+	{ .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, },
+	{ .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, of_is31fl31xx_match);
+
+static int is31fl32xx_probe(struct i2c_client *client,
+				  const struct i2c_device_id *id)
+{
+	const struct is31fl32xx_chipdef *cdef;
+	const struct of_device_id *of_dev_id;
+	struct device *dev = &client->dev;
+	struct is31fl32xx_priv *priv;
+	int count;
+	int ret = 0;
+
+	of_dev_id = of_match_device(of_is31fl31xx_match, dev);
+	if (!of_dev_id)
+		return -EINVAL;
+
+	cdef = of_dev_id->data;
+
+	count = of_get_child_count(dev->of_node);
+	if (!count)
+		return -EINVAL;
+
+	priv = devm_kzalloc(dev, sizeof_is31fl32xx_priv(count),
+			    GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->client = client;
+	priv->cdef = cdef;
+	i2c_set_clientdata(client, priv);
+
+	ret = is31fl32xx_init_regs(priv);
+	if (ret)
+		return ret;
+
+	ret = is31fl32xx_parse_dt(dev, priv);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int is31fl32xx_remove(struct i2c_client *client)
+{
+	struct is31fl32xx_priv *priv = i2c_get_clientdata(client);
+
+	/* If there is a reset reg, then it does everything we need */
+	if (priv->cdef->reset_reg != IS31FL32XX_REG_NONE)
+		return is31fl32xx_write(priv, priv->cdef->reset_reg, 0);
+
+	/* If there is a reset func, then it does everything we need */
+	if (priv->cdef->reset_func)
+		return priv->cdef->reset_func(priv);
+
+	/* If we can't reset, then try just using software-shutdown mode */
+	if (priv->cdef->shutdown_reg != IS31FL32XX_REG_NONE)
+		return is31fl32xx_write(priv, priv->cdef->shutdown_reg, 0x00);
+
+	return 0;
+}
+
+/*
+ * i2c-core requires that id_table be non-NULL, even though
+ * it is not used for DeviceTree based instantiation.
+ */
+static const struct i2c_device_id is31fl31xx_id[] = {
+	{},
+};
+
+MODULE_DEVICE_TABLE(i2c, is31fl31xx_id);
+
+static struct i2c_driver is31fl32xx_driver = {
+	.driver = {
+		.name	= "is31fl32xx",
+		.of_match_table = of_is31fl31xx_match,
+	},
+	.probe		= is31fl32xx_probe,
+	.remove		= is31fl32xx_remove,
+	.id_table	= is31fl31xx_id,
+};
+
+module_i2c_driver(is31fl32xx_driver);
+
+MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>");
+MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver");
+MODULE_LICENSE("GPL v2");
-- 
2.5.0

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-23 18:17 ` [PATCH RFC 3/3] leds: Add driver " David Rivshin (Allworx)
@ 2016-02-23 18:45   ` Mark Rutland
  2016-02-23 22:38     ` David Rivshin (Allworx)
  2016-02-24 16:04   ` Jacek Anaszewski
  1 sibling, 1 reply; 33+ messages in thread
From: Mark Rutland @ 2016-02-23 18:45 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: linux-leds, devicetree, Richard Purdie, Jacek Anaszewski,
	Rob Herring, Pawel Moll, Ian Campbell, Kumar Gala, Stefan Wahren

On Tue, Feb 23, 2016 at 01:17:25PM -0500, David Rivshin (Allworx) wrote:
> From: David Rivshin <drivshin@allworx.com>
> +static int is31fl32xx_parse_child_dt(const struct device *dev,
> +				     const struct device_node *child,
> +				     struct is31fl32xx_led_data *led_data)
> +{
> +	struct led_classdev *cdev = &led_data->cdev;
> +	int ret = 0;
> +	u32 reg;
> +
> +	cdev->name = of_get_property(child, "label", NULL) ? : child->name;

We really shouldn't be spreading of_get_property into more drivers. It's
generally not what you want, and doesn't do appropriate sanity checking.

Use of_property_read_string, though you may need to copy the result.

> +
> +	ret = of_property_read_u32(child, "reg", &reg);
> +	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
> +		dev_err(dev,
> +			"Child node %s does not have a valid reg property\n",
> +			child->name);
> +		return -EINVAL;
> +	}
> +	led_data->channel = reg;
> +
> +	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
> +						NULL);

Likewise.

Thanks,
Mark. 

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-23 18:45   ` Mark Rutland
@ 2016-02-23 22:38     ` David Rivshin (Allworx)
  2016-02-24 16:08       ` Jacek Anaszewski
  0 siblings, 1 reply; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-02-23 22:38 UTC (permalink / raw)
  To: Mark Rutland
  Cc: linux-leds, devicetree, Richard Purdie, Jacek Anaszewski,
	Rob Herring, Pawel Moll, Ian Campbell, Kumar Gala, Stefan Wahren

On Tue, 23 Feb 2016 18:45:17 +0000
Mark Rutland <mark.rutland@arm.com> wrote:

> On Tue, Feb 23, 2016 at 01:17:25PM -0500, David Rivshin (Allworx) wrote:
> > From: David Rivshin <drivshin@allworx.com>
> > +static int is31fl32xx_parse_child_dt(const struct device *dev,
> > +				     const struct device_node *child,
> > +				     struct is31fl32xx_led_data *led_data)
> > +{
> > +	struct led_classdev *cdev = &led_data->cdev;
> > +	int ret = 0;
> > +	u32 reg;
> > +
> > +	cdev->name = of_get_property(child, "label", NULL) ? : child->name;  
> 
> We really shouldn't be spreading of_get_property into more drivers. It's
> generally not what you want, and doesn't do appropriate sanity checking.

Sorry, I copied the basic DT parsing from some other led driver (leds-pwm, 
I think), and did not check if there was a better way.

> Use of_property_read_string, though you may need to copy the result.

Will do. 

As far as copying the result, this is an area I'm fuzzy on. If I 
understand correctly the device_nodes are reference counted, and 
of_property_read_string() returns a pointer to the data inside the
device_node. So it seems I'd either need to hold onto a reference, 
or make a copy of the string.

devm_kstrdup() seems like it would be an easy way to go, although
maybe not the most efficient.

Using of_node_get() would seem to be more efficient, but then what 
would be the right way to release that reference on remove()? 
 - Does that already happen in some infrastructure when using DT 
   probing? 
 - Is there a devm_*() helper function that should be used? 
 - Or would it be best to just keep a pointer to the device_node so
   that of_node_put() can be called on remove()?

Also, I have yet to find an example of a driver which either copies 
the string or does an of_node_get() on the node. In fact just by a 
count of files, it seems very few have any hope of doing either:

$ find drivers/ -name '*.c' -exec grep of_property_read_string -l {} + \
| wc -l
197
$ find drivers/ -name '*.c' -exec grep of_property_read_string -l {} + \
| xargs grep -l of_node_get | wc -l
15
$ find drivers/ -name '*.c' -exec grep of_property_read_string -l {} + \
| xargs grep -l strdup | wc -l
11

So I'm very much hoping that there's some infrastructure automatically 
handling the appropriate reference counting, so that the nodes stay
around (at least) as long as the driver is instantiated...

> > +
> > +	ret = of_property_read_u32(child, "reg", &reg);
> > +	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
> > +		dev_err(dev,
> > +			"Child node %s does not have a valid reg property\n",
> > +			child->name);
> > +		return -EINVAL;
> > +	}
> > +	led_data->channel = reg;
> > +
> > +	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
> > +						NULL);  
> 
> Likewise.

Will take care of this same as above.

Thanks!

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

* Re: [PATCH RFC 1/3] DT: Add vendor prefix for Integrated Silicon Solutions Inc.
  2016-02-23 18:17 ` [PATCH RFC 1/3] DT: Add vendor prefix for Integrated Silicon Solutions Inc David Rivshin (Allworx)
@ 2016-02-23 23:36   ` Rob Herring
  0 siblings, 0 replies; 33+ messages in thread
From: Rob Herring @ 2016-02-23 23:36 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: linux-leds, devicetree, Richard Purdie, Jacek Anaszewski,
	Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Stefan Wahren

On Tue, Feb 23, 2016 at 01:17:23PM -0500, David Rivshin (Allworx) wrote:
> From: David Rivshin <drivshin@allworx.com>
> 
> ISSI is the stock ticker Integrated Silicon Solutions Inc.
> Company website: http://www.issi.com
> 
> Signed-off-by: David Rivshin <drivshin@allworx.com>
> ---
>  Documentation/devicetree/bindings/vendor-prefixes.txt | 1 +
>  1 file changed, 1 insertion(+)

Acked-by: Rob Herring <robh@kernel.org>

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

* Re: [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers
  2016-02-23 18:17 ` [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers David Rivshin (Allworx)
@ 2016-02-24  1:15   ` Rob Herring
  2016-02-24 18:57     ` David Rivshin (Allworx)
  2016-02-24 16:04   ` Jacek Anaszewski
  1 sibling, 1 reply; 33+ messages in thread
From: Rob Herring @ 2016-02-24  1:15 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: linux-leds, devicetree, Richard Purdie, Jacek Anaszewski,
	Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Stefan Wahren

On Tue, Feb 23, 2016 at 01:17:24PM -0500, David Rivshin (Allworx) wrote:
> From: David Rivshin <drivshin@allworx.com>
> 
> This adds a binding description for the is31fl3236/35/18/16 I2C LED
> drivers.
> 
> Signed-off-by: David Rivshin <drivshin@allworx.com>
> ---
>  .../devicetree/bindings/leds/leds-is31fl32xx.txt   | 51 ++++++++++++++++++++++
>  1 file changed, 51 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt

Acked-by: Rob Herring <robh@kernel.org>

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

* Re: [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers
  2016-02-23 18:17 ` [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers David Rivshin (Allworx)
  2016-02-24  1:15   ` Rob Herring
@ 2016-02-24 16:04   ` Jacek Anaszewski
  2016-02-24 19:34     ` David Rivshin (Allworx)
  1 sibling, 1 reply; 33+ messages in thread
From: Jacek Anaszewski @ 2016-02-24 16:04 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: linux-leds, devicetree, Richard Purdie, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Stefan Wahren

Hi David,

Thanks for the patch.

On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:
> From: David Rivshin <drivshin@allworx.com>
>
> This adds a binding description for the is31fl3236/35/18/16 I2C LED
> drivers.
>
> Signed-off-by: David Rivshin <drivshin@allworx.com>
> ---
>   .../devicetree/bindings/leds/leds-is31fl32xx.txt   | 51 ++++++++++++++++++++++
>   1 file changed, 51 insertions(+)
>   create mode 100644 Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
>
> diff --git a/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt b/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
> new file mode 100644
> index 0000000..0a05a1d
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
> @@ -0,0 +1,51 @@
> +Binding for ISSI IS31FL32xx LED Drivers
> +
> +The IS31FL32xx family of LED drivers are I2C devices with multiple
> +constant-current channels, each with independent 256-level PWM control.
> +Each LED is represented as a sub-node of the device.
> +
> +Required properties:
> +- compatible: one of
> +	issi,is31fl3236
> +	issi,is31fl3235
> +	issi,is31fl3218
> +	issi,is31fl3216
> +- reg: I2C slave address
> +- address-cells : must be 1
> +- size-cells : must be 0
> +
> +LED sub-node properties:
> +- reg : LED channel number (1..N)
> +- max-brightness : (optional) Maximum brightness possible for the LED.

Please use led-max-microamp instead. You can refer to
Documentation/devicetree/bindings/leds/common.txt for detailed
description.

> +  Default is 255.
> +- label :  (optional)
> +  see Documentation/devicetree/bindings/leds/common.txt
> +- linux,default-trigger :  (optional)
> +  see Documentation/devicetree/bindings/leds/common.txt
> +
> +
> +Example:
> +
> +leds: is31fl3236@3c {
> +	compatible = "issi,is31fl3236";
> +	reg = <0x3c>;

You're missing address-cells and size-cells in this example.

> +
> +	led@1 {
> +		reg = <1>;
> +		label = "EB:blue:usr0";
> +		max-brightness = <128>;
> +	};
> +	led@2 {
> +		reg = <2>;
> +		label = "EB:blue:usr1";
> +	};
> +	...
> +	led@36 {
> +		reg = <36>;
> +		label = "EB:blue:usr35";
> +		max-brightness = <255>;
> +	};
> +};
> +
> +For more product information please see the link below:
> +http://www.issi.com/US/product-analog-fxled-driver.shtml
> \ No newline at end of file
>


-- 
Best regards,
Jacek Anaszewski

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-23 18:17 ` [PATCH RFC 3/3] leds: Add driver " David Rivshin (Allworx)
  2016-02-23 18:45   ` Mark Rutland
@ 2016-02-24 16:04   ` Jacek Anaszewski
  2016-02-25  2:24     ` David Rivshin (Allworx)
  1 sibling, 1 reply; 33+ messages in thread
From: Jacek Anaszewski @ 2016-02-24 16:04 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: linux-leds, devicetree, Richard Purdie, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Stefan Wahren

Hi David,

Thanks for the patch. Very nice driver. I have few comments
below.

On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:
> From: David Rivshin <drivshin@allworx.com>
>
> The IS31FL32xx family of LED drivers are I2C devices with multiple
> constant-current channels, each with independent 256-level PWM control.
>
> HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
>
> This has been tested on the IS31FL3236 and IS31FL3216 on an ARM
> (TI am335x) platform.
>
> The programming paradigm of these devices is similar in the following
> ways:
>   - All registers are 8 bit
>   - All LED control registers are write-only
>   - Each LED channel has a PWM register (0-255)
>   - PWM register writes are shadowed until an Update register is poked
>   - All have a concept of Software Shutdown, which disables output
>
> However, there are some differences in devices:
>   - 3236/3235 have a separate Control register for each LED,
>     (3218/3216 pack the enable bits into fewer registers)
>   - 3236/3235 have a per-channel current divisor setting
>   - 3236/3235 have a Global Control register that can turn off all LEDs
>   - 3216 is unique in a number of ways
>      - OUT9-OUT16 can be configured as GPIOs instead of LED controls
>      - LEDs can be programmed with an 8-frame animation, with
>        programmable delay between frames
>      - LEDs can be modulated by an input audio signal
>      - Max output current can be adjusted from 1/4 to 2x globally
>      - Has a Configuration register instead of a Shutdown register
>
> This driver currently only supports the base PWM control function
> of these devices. The following features of these devices are not
> implemented, although it should be possible to add them in the future:
>   - All devices are capable of going into a lower-power "software
>     shutdown" mode.
>   - The is31fl3236 and is31fl3235 can reduce the max output current
>     per-channel with a divisor of 1, 2, 3, or 4.
>   - The is31fl3216 can use some LED channels as GPIOs instead.
>   - The is31fl3216 can animate LEDs in hardware.
>   - The is31fl3216 can modulate LEDs according to an audio input.
>   - The is31fl3216 can reduce/increase max output current globally.
>
> Signed-off-by: David Rivshin <drivshin@allworx.com>
> ---
>   drivers/leds/Kconfig           |   9 +
>   drivers/leds/Makefile          |   1 +
>   drivers/leds/leds-is31fl32xx.c | 442 +++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 452 insertions(+)
>   create mode 100644 drivers/leds/leds-is31fl32xx.c
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index 1034696..8f6c46f 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -580,6 +580,15 @@ config LEDS_SN3218
>   	  This driver can also be built as a module. If so the module
>   	  will be called leds-sn3218.
>
> +config LEDS_IS31FL32XX
> +	tristate "Driver for ISSI IS31FL32XX I2C LED driver chip family"

2 x "[Dd]river".

How about:

"LED Support for ISSI IS31FL32XX I2C LED chip family" ?

> +	depends on LEDS_CLASS && I2C && OF
> +	help
> +	  Say Y here to include support for the ISSI 31FL32XX LED driver family.

s/driver/chip/

> +	  They are I2C devices with multiple constant-current channels, each
> +	  with independent 256-level PWM control. This will only work with
> +	  device tree enabled devices.

We can skip the last sentence I think.

> +
>   comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
>
>   config LEDS_BLINKM
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index 89c9b6f..3fdf313 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -67,6 +67,7 @@ obj-$(CONFIG_LEDS_KTD2692)		+= leds-ktd2692.o
>   obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
>   obj-$(CONFIG_LEDS_SEAD3)		+= leds-sead3.o
>   obj-$(CONFIG_LEDS_SN3218)		+= leds-sn3218.o
> +obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
>
>   # LED SPI Drivers
>   obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
> diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
> new file mode 100644
> index 0000000..8dea518
> --- /dev/null
> +++ b/drivers/leds/leds-is31fl32xx.c
> @@ -0,0 +1,442 @@
> +/*
> + * linux/drivers/leds-is31fl32xx.c
> + *
> + * Driver for ISSI IS31FL32xx family of I2C LED controllers
> + *
> + * Copyright 2015 Allworx Corp.
> + *
> + *
> + * 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.
> + *
> + * HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
> + */
> +
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/kernel.h>
> +#include <linux/leds.h>
> +#include <linux/module.h>
> +#include <linux/of_platform.h>
> +
> +#ifdef DEBUG
> + #undef dev_dbg
> + #define dev_dbg dev_info
> +#endif

What's the benefit of the above?

> +/* Used to indicate a device has no such register */
> +#define IS31FL32XX_REG_NONE 0xFF
> +
> +#define IS31FL3216_CONFIG_REG 0x00
> +#define IS31FL3216_LIGHTING_EFFECT_REG 0x03
> +#define IS31FL3216_CHANNEL_CONFIG_REG 0x04
> +
> +struct is31fl32xx_priv;
> +struct is31fl32xx_led_data {
> +	struct led_classdev cdev;
> +	u8 channel; /* 1-based, max priv->cdef->channels */
> +	struct is31fl32xx_priv *priv;
> +};
> +
> +struct is31fl32xx_priv {
> +	const struct is31fl32xx_chipdef *cdef;
> +	struct i2c_client *client;
> +	unsigned int num_leds;
> +	struct is31fl32xx_led_data leds[0];

Is there any specific reason for not having *leds here instead?

> +};
> +
> +/**
> + * struct is31fl32xx_chipdef - chip-specific attributes
> + * @channels            : Number of LED channels
> + * @shutdown_reg        : address of Shutdown register (optional)
> + * @pwm_update_reg      : address of PWM Update register
> + * @global_control_reg	: address of Global Control register (optional)
> + * @reset_reg           : address of Reset register (optional)
> + * @pwm_register_base	: address of first PWM register
> + * @pwm_registers_reversed: : true if PWM registers count down instead of up
> + * @led_control_register_base : address of first LED control register (optional)
> + * @enable_bits_per_led_control_register: number of LEDs enable bits in each
> + * @reset_func:         : pointer to reset function
> + *
> + * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE
> + * indicates that this chip has no such register.
> + *
> + * If non-NULL, @reset_func will be called during probing to set all
> + * necessary registers to a known initialization state. This is needed
> + * for chips that do not have a @reset_reg.
> + *
> + * @enable_bits_per_led_control_register must be >=1 if
> + * @led_control_register_base != %IS31FL32XX_REG_NONE.
> + */
> +struct is31fl32xx_chipdef {
> +	u8	channels;
> +	u8	shutdown_reg;
> +	u8	pwm_update_reg;
> +	u8	global_control_reg;
> +	u8	reset_reg;
> +	u8	pwm_register_base;
> +	bool	pwm_registers_reversed;
> +	u8	led_control_register_base;
> +	u8	enable_bits_per_led_control_register;
> +	int (*reset_func)(struct is31fl32xx_priv *priv);
> +};
> +
> +static const struct is31fl32xx_chipdef is31fl3236_cdef = {
> +	.channels				= 36,
> +	.shutdown_reg				= 0x00,
> +	.pwm_update_reg				= 0x25,
> +	.global_control_reg			= 0x4a,
> +	.reset_reg				= 0x4f,
> +	.pwm_register_base			= 0x01,
> +	.led_control_register_base		= 0x26,
> +	.enable_bits_per_led_control_register	= 1,
> +};
> +
> +static const struct is31fl32xx_chipdef is31fl3235_cdef = {
> +	.channels				= 28,
> +	.shutdown_reg				= 0x00,
> +	.pwm_update_reg				= 0x25,
> +	.global_control_reg			= 0x4a,
> +	.reset_reg				= 0x4f,
> +	.pwm_register_base			= 0x05,
> +	.led_control_register_base		= 0x2a,
> +	.enable_bits_per_led_control_register	= 1,
> +};
> +
> +static const struct is31fl32xx_chipdef is31fl3218_cdef = {
> +	.channels				= 18,
> +	.shutdown_reg				= 0x00,
> +	.pwm_update_reg				= 0x16,
> +	.global_control_reg			= IS31FL32XX_REG_NONE,
> +	.reset_reg				= 0x17,
> +	.pwm_register_base			= 0x01,
> +	.led_control_register_base		= 0x13,
> +	.enable_bits_per_led_control_register	= 6,
> +};
> +
> +static int is31fl3216_reset(struct is31fl32xx_priv *priv);
> +static const struct is31fl32xx_chipdef is31fl3216_cdef = {
> +	.channels				= 16,
> +	.shutdown_reg				= IS31FL32XX_REG_NONE,
> +	.pwm_update_reg				= 0xB0,
> +	.global_control_reg			= IS31FL32XX_REG_NONE,
> +	.reset_reg				= IS31FL32XX_REG_NONE,
> +	.pwm_register_base			= 0x10,
> +	.pwm_registers_reversed			= true,
> +	.led_control_register_base		= 0x01,
> +	.enable_bits_per_led_control_register	= 8,
> +	.reset_func				= is31fl3216_reset,
> +};
> +
> +static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)
> +{
> +	int ret;
> +
> +	dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
> +
> +	ret =  i2c_smbus_write_byte_data(priv->client, reg, val);
> +	if (ret) {
> +		dev_err(&priv->client->dev,
> +			"register write to 0x%02X failed (error %d)",
> +			reg, ret);
> +	}
> +	return ret;
> +}
> +
> +/*
> + * Custom reset function for IS31FL3216 because it does not have a RESET
> + * register the way that the other IS31FL32xx chips do. We don't bother
> + * writing the GPIO and animation registers, because the registers we
> + * do write ensure those will have no effect.
> + */
> +static int is31fl3216_reset(struct is31fl32xx_priv *priv)
> +{
> +	unsigned int i;
> +	int ret;
> +
> +	for (i = 0; i < priv->cdef->channels; i++) {
> +		ret = is31fl32xx_write(priv, priv->cdef->pwm_register_base+i,
> +				       0x00);
> +		if (ret)
> +			return ret;
> +	}
> +	ret = is31fl32xx_write(priv, priv->cdef->pwm_update_reg, 0);
> +	if (ret)
> +		return ret;
> +	ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, 0x00);
> +	if (ret)
> +		return ret;
> +	ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, 0x00);
> +	if (ret)
> +		return ret;
> +	ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, 0x00);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +
> +static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,
> +				     enum led_brightness brightness)
> +{
> +	const struct is31fl32xx_led_data *led_data =
> +		container_of(led_cdev, struct is31fl32xx_led_data, cdev);
> +	const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef;
> +	u8 pwm_register_offset;
> +	int ret;
> +
> +	dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);
> +
> +	/* NOTE: led_data->channel is 1-based */
> +	if (cdef->pwm_registers_reversed)
> +		pwm_register_offset = cdef->channels - led_data->channel;
> +	else
> +		pwm_register_offset = led_data->channel - 1;
> +
> +	ret = is31fl32xx_write(led_data->priv,
> +			       cdef->pwm_register_base + pwm_register_offset,
> +			       brightness);
> +	if (ret)
> +		return ret;

I infer that nothing wrong happens in case current process is preempted
here by the call originating from the other sub-LED?

> +	return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);
> +
> +}
> +
> +static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
> +{
> +	const struct is31fl32xx_chipdef *cdef = priv->cdef;
> +	int ret;
> +
> +	if (cdef->reset_reg != IS31FL32XX_REG_NONE) {
> +		ret = is31fl32xx_write(priv, cdef->reset_reg, 0);
> +		if (ret)
> +			return ret;
> +	}
> +	if (cdef->reset_func) {
> +		ret = cdef->reset_func(priv);
> +		if (ret)
> +			return ret;
> +	}
> +	if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) {
> +		u8 value =
> +		    GENMASK(cdef->enable_bits_per_led_control_register-1, 0);
> +		u8 num_regs = cdef->channels /
> +				cdef->enable_bits_per_led_control_register;
> +		int i;
> +
> +		for (i = 0; i < num_regs; i++) {
> +			ret = is31fl32xx_write(priv,
> +					       cdef->led_control_register_base+i,
> +					       value);
> +			if (ret)
> +				return ret;
> +		}
> +	}
> +	if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) {
> +		ret = is31fl32xx_write(priv, cdef->shutdown_reg, BIT(0));
> +		if (ret)
> +			return ret;
> +	}
> +	if (cdef->global_control_reg != IS31FL32XX_REG_NONE) {
> +		ret = is31fl32xx_write(priv, cdef->global_control_reg, 0x00);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static inline size_t sizeof_is31fl32xx_priv(int num_leds)
> +{
> +	return sizeof(struct is31fl32xx_priv) +
> +		      (sizeof(struct is31fl32xx_led_data) * num_leds);
> +}
> +
> +static int is31fl32xx_parse_child_dt(const struct device *dev,
> +				     const struct device_node *child,
> +				     struct is31fl32xx_led_data *led_data)
> +{
> +	struct led_classdev *cdev = &led_data->cdev;
> +	int ret = 0;
> +	u32 reg;
> +
> +	cdev->name = of_get_property(child, "label", NULL) ? : child->name;
> +
> +	ret = of_property_read_u32(child, "reg", &reg);
> +	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
> +		dev_err(dev,
> +			"Child node %s does not have a valid reg property\n",
> +			child->name);
> +		return -EINVAL;
> +	}
> +	led_data->channel = reg;
> +
> +	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
> +						NULL);
> +	cdev->brightness = LED_OFF;

devm_kzalloc secures that.

> +	ret = of_property_read_u32(child, "max-brightness",
> +				   &cdev->max_brightness);
> +	if (ret == -EINVAL) {
> +		cdev->max_brightness = 255;

s/255/LED_FULL/

> +	} else if (ret) {
> +		dev_dbg(dev,
> +			"Child node %s has an invalid max-brightness property\n",
> +			child->name);
> +		return -EINVAL;
> +	}
> +
> +	cdev->brightness_set_blocking = is31fl32xx_brightness_set;

Please add empty line here.

> +	return 0;
> +}
> +
> +static struct is31fl32xx_led_data *is31fl32xx_find_led_data(
> +					struct is31fl32xx_priv *priv,
> +					u8 channel)
> +{
> +	size_t i;
> +
> +	for (i = 0; i < priv->num_leds; i++) {
> +		if (priv->leds[i].channel == channel)
> +			return &priv->leds[i];
> +	}

Ditto.

> +	return NULL;
> +}
> +
> +static int is31fl32xx_parse_dt(struct device *dev,
> +			       struct is31fl32xx_priv *priv)
> +{
> +	struct device_node *child;
> +
> +	for_each_child_of_node(dev->of_node, child) {
> +		struct is31fl32xx_led_data *led_data =
> +			&priv->leds[priv->num_leds];
> +		int ret = 0;
> +		const struct is31fl32xx_led_data *other_led_data;
> +
> +		led_data->priv = priv;
> +
> +		ret = is31fl32xx_parse_child_dt(dev, child, led_data);
> +		if (ret)
> +			continue;

I prefer failing in such cases,

> +
> +		/* Detect if channel is already in use by another child */
> +		other_led_data = is31fl32xx_find_led_data(priv,
> +							  led_data->channel);
> +		if (other_led_data) {
> +			dev_err(dev,
> +				"%s ignored: channel %d already used by %s",
> +				led_data->cdev.name,
> +				led_data->channel,
> +				other_led_data->cdev.name);
> +			continue;

Ditto.

> +		}
> +
> +		ret = devm_led_classdev_register(dev, &led_data->cdev);
> +		if (ret == 0) {
> +			priv->num_leds++;
> +		} else {
> +			dev_err(dev, "failed to register PWM led for %s: %d\n",
> +				led_data->cdev.name, ret);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id of_is31fl31xx_match[] = {
> +	{ .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },
> +	{ .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },
> +	{ .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, },
> +	{ .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, },
> +	{},
> +};
> +
> +MODULE_DEVICE_TABLE(of, of_is31fl31xx_match);
> +
> +static int is31fl32xx_probe(struct i2c_client *client,
> +				  const struct i2c_device_id *id)
> +{
> +	const struct is31fl32xx_chipdef *cdef;
> +	const struct of_device_id *of_dev_id;
> +	struct device *dev = &client->dev;
> +	struct is31fl32xx_priv *priv;
> +	int count;
> +	int ret = 0;
> +
> +	of_dev_id = of_match_device(of_is31fl31xx_match, dev);
> +	if (!of_dev_id)
> +		return -EINVAL;
> +
> +	cdef = of_dev_id->data;
> +
> +	count = of_get_child_count(dev->of_node);
> +	if (!count)
> +		return -EINVAL;
> +
> +	priv = devm_kzalloc(dev, sizeof_is31fl32xx_priv(count),
> +			    GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->client = client;
> +	priv->cdef = cdef;
> +	i2c_set_clientdata(client, priv);
> +
> +	ret = is31fl32xx_init_regs(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = is31fl32xx_parse_dt(dev, priv);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int is31fl32xx_remove(struct i2c_client *client)
> +{
> +	struct is31fl32xx_priv *priv = i2c_get_clientdata(client);
> +
> +	/* If there is a reset reg, then it does everything we need */
> +	if (priv->cdef->reset_reg != IS31FL32XX_REG_NONE)
> +		return is31fl32xx_write(priv, priv->cdef->reset_reg, 0);
> +
> +	/* If there is a reset func, then it does everything we need */
> +	if (priv->cdef->reset_func)
> +		return priv->cdef->reset_func(priv);
> +
> +	/* If we can't reset, then try just using software-shutdown mode */
> +	if (priv->cdef->shutdown_reg != IS31FL32XX_REG_NONE)
> +		return is31fl32xx_write(priv, priv->cdef->shutdown_reg, 0x00);
> +
> +	return 0;
> +}
> +
> +/*
> + * i2c-core requires that id_table be non-NULL, even though
> + * it is not used for DeviceTree based instantiation.
> + */
> +static const struct i2c_device_id is31fl31xx_id[] = {
> +	{},
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, is31fl31xx_id);
> +
> +static struct i2c_driver is31fl32xx_driver = {
> +	.driver = {
> +		.name	= "is31fl32xx",
> +		.of_match_table = of_is31fl31xx_match,
> +	},
> +	.probe		= is31fl32xx_probe,
> +	.remove		= is31fl32xx_remove,
> +	.id_table	= is31fl31xx_id,
> +};
> +
> +module_i2c_driver(is31fl32xx_driver);
> +
> +MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>");
> +MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver");
> +MODULE_LICENSE("GPL v2");
>


-- 
Best regards,
Jacek Anaszewski

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-23 22:38     ` David Rivshin (Allworx)
@ 2016-02-24 16:08       ` Jacek Anaszewski
  0 siblings, 0 replies; 33+ messages in thread
From: Jacek Anaszewski @ 2016-02-24 16:08 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: Mark Rutland, linux-leds, devicetree, Richard Purdie,
	Rob Herring, Pawel Moll, Ian Campbell, Kumar Gala, Stefan Wahren

On 02/23/2016 11:38 PM, David Rivshin (Allworx) wrote:
> On Tue, 23 Feb 2016 18:45:17 +0000
> Mark Rutland <mark.rutland@arm.com> wrote:
>
>> On Tue, Feb 23, 2016 at 01:17:25PM -0500, David Rivshin (Allworx) wrote:
>>> From: David Rivshin <drivshin@allworx.com>
>>> +static int is31fl32xx_parse_child_dt(const struct device *dev,
>>> +				     const struct device_node *child,
>>> +				     struct is31fl32xx_led_data *led_data)
>>> +{
>>> +	struct led_classdev *cdev = &led_data->cdev;
>>> +	int ret = 0;
>>> +	u32 reg;
>>> +
>>> +	cdev->name = of_get_property(child, "label", NULL) ? : child->name;
>>
>> We really shouldn't be spreading of_get_property into more drivers. It's
>> generally not what you want, and doesn't do appropriate sanity checking.
>
> Sorry, I copied the basic DT parsing from some other led driver (leds-pwm,
> I think), and did not check if there was a better way.
>
>> Use of_property_read_string, though you may need to copy the result.
>
> Will do.
>
> As far as copying the result, this is an area I'm fuzzy on. If I
> understand correctly the device_nodes are reference counted, and
> of_property_read_string() returns a pointer to the data inside the
> device_node. So it seems I'd either need to hold onto a reference,
> or make a copy of the string.
>
> devm_kstrdup() seems like it would be an easy way to go, although
> maybe not the most efficient.
>
> Using of_node_get() would seem to be more efficient, but then what
> would be the right way to release that reference on remove()?
>   - Does that already happen in some infrastructure when using DT
>     probing?
>   - Is there a devm_*() helper function that should be used?
>   - Or would it be best to just keep a pointer to the device_node so
>     that of_node_put() can be called on remove()?
>
> Also, I have yet to find an example of a driver which either copies
> the string or does an of_node_get() on the node. In fact just by a
> count of files, it seems very few have any hope of doing either:
>
> $ find drivers/ -name '*.c' -exec grep of_property_read_string -l {} + \
> | wc -l
> 197
> $ find drivers/ -name '*.c' -exec grep of_property_read_string -l {} + \
> | xargs grep -l of_node_get | wc -l
> 15
> $ find drivers/ -name '*.c' -exec grep of_property_read_string -l {} + \
> | xargs grep -l strdup | wc -l
> 11
>
> So I'm very much hoping that there's some infrastructure automatically
> handling the appropriate reference counting, so that the nodes stay
> around (at least) as long as the driver is instantiated...
>
>>> +
>>> +	ret = of_property_read_u32(child, "reg", &reg);
>>> +	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
>>> +		dev_err(dev,
>>> +			"Child node %s does not have a valid reg property\n",
>>> +			child->name);
>>> +		return -EINVAL;
>>> +	}
>>> +	led_data->channel = reg;
>>> +
>>> +	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
>>> +						NULL);
>>
>> Likewise.
>
> Will take care of this same as above.

DT nodes refcounting is enabled only when CONFIG_OF_DYNAMIC is defined,
and does matter in case Device Tree overlays are used. DT people -
please correct me if I'm wrong.

If this use case is not valid for this driver, then we can rely
on the pointer obtained with of_property_read_string,
I've been doing empirical tests and child node refcount is greater
than 0 throughout whole driver lifespan.

-- 
Best regards,
Jacek Anaszewski

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

* Re: [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers
  2016-02-24  1:15   ` Rob Herring
@ 2016-02-24 18:57     ` David Rivshin (Allworx)
  2016-02-24 19:45       ` Rob Herring
  0 siblings, 1 reply; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-02-24 18:57 UTC (permalink / raw)
  To: Rob Herring
  Cc: linux-leds, devicetree, Richard Purdie, Jacek Anaszewski,
	Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Stefan Wahren

On Tue, 23 Feb 2016 19:15:08 -0600
Rob Herring <robh@kernel.org> wrote:

> On Tue, Feb 23, 2016 at 01:17:24PM -0500, David Rivshin (Allworx) wrote:
> > From: David Rivshin <drivshin@allworx.com>
> > 
> > This adds a binding description for the is31fl3236/35/18/16 I2C LED
> > drivers.
> > 
> > Signed-off-by: David Rivshin <drivshin@allworx.com>
> > ---
> >  .../devicetree/bindings/leds/leds-is31fl32xx.txt   | 51 ++++++++++++++++++++++
> >  1 file changed, 51 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt  
> 
> Acked-by: Rob Herring <robh@kernel.org>

Thanks, Rob. I just want to double check whether you noticed some
binding-related questions I had in the coverletter [1]. In hindsight
I should probably have included them in the patch comments as well so 
they'd show up in patchwork. For convenience, I'll repeat them here:

I choose to have 'reg' be 1-based in order to follow the hardware 
documentation. It seems 0-based is the normal choice, although HW
docs also usually use the 0-based numbering. Perhaps being consistent 
with other bindings is more important than being consistent with the 
datasheet's numbering?

I used the 'reg' property for the LED channel, as it seemed ePAPR 
required that name. I also considered naming the property 'chan', 
and not pretending that it represented a bus address at all (and
then removing the @n from the subnode names). That would solve the
'reg' question above as a side-effect, but would be inconstant with
other LED bindings (tca6507, pca963x, tlc59108, etc).

Note that the recently-added (and only in for-next) SN3218 driver 
uses a 0-based 'reg' property, and the SN3218 has the same HW doc
numbering as the IS31FL32xx family (indeed the IS31FL3218 appears 
to be a rebranded SN3218). So perhaps that sets the precedent if 
there was not one before?

Any guidance you could offer would be appreciated (especially since
this is my first from-scratch binding, and I'd rather not form any
bad habits).

[1] http://www.spinics.net/lists/linux-leds/msg05564.html
    http://thread.gmane.org/gmane.linux.leds/4530

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

* Re: [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers
  2016-02-24 16:04   ` Jacek Anaszewski
@ 2016-02-24 19:34     ` David Rivshin (Allworx)
  2016-02-24 19:49       ` Rob Herring
  0 siblings, 1 reply; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-02-24 19:34 UTC (permalink / raw)
  To: Jacek Anaszewski
  Cc: linux-leds, devicetree, Richard Purdie, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Stefan Wahren

On Wed, 24 Feb 2016 17:04:49 +0100
Jacek Anaszewski <j.anaszewski@samsung.com> wrote:

> Hi David,
> 
> Thanks for the patch.
> 
> On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:
> > From: David Rivshin <drivshin@allworx.com>
> >
> > This adds a binding description for the is31fl3236/35/18/16 I2C LED
> > drivers.
> >
> > Signed-off-by: David Rivshin <drivshin@allworx.com>
> > ---
> >   .../devicetree/bindings/leds/leds-is31fl32xx.txt   | 51 ++++++++++++++++++++++
> >   1 file changed, 51 insertions(+)
> >   create mode 100644 Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
> >
> > diff --git a/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt b/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
> > new file mode 100644
> > index 0000000..0a05a1d
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
> > @@ -0,0 +1,51 @@
> > +Binding for ISSI IS31FL32xx LED Drivers
> > +
> > +The IS31FL32xx family of LED drivers are I2C devices with multiple
> > +constant-current channels, each with independent 256-level PWM control.
> > +Each LED is represented as a sub-node of the device.
> > +
> > +Required properties:
> > +- compatible: one of
> > +	issi,is31fl3236
> > +	issi,is31fl3235
> > +	issi,is31fl3218
> > +	issi,is31fl3216
> > +- reg: I2C slave address
> > +- address-cells : must be 1
> > +- size-cells : must be 0
> > +
> > +LED sub-node properties:
> > +- reg : LED channel number (1..N)
> > +- max-brightness : (optional) Maximum brightness possible for the LED.  
> 
> Please use led-max-microamp instead. You can refer to
> Documentation/devicetree/bindings/leds/common.txt for detailed
> description.

FYI, I think I got that property from leds-pwm, since these use PWMs 
internally, although I don't actually use it myself. 

I can see how led-max-microamp could be a useful property, however 
I'm uncertain about computing cdev->max_brightness from it as these 
devices look to control their max current via an external resistor. 
I suppose either the resistor value, or the resultant max-current 
would need to be given in the devicetree, and then that used along
with led-max-microamp to compute cdev->max_brightness. Although I'd 
want it to be optional as I suspect most users don't need any 
restriction. Also, I don't think I can properly test the result, 
which always makes me nervous. 

Would it be acceptable to simply remove the max-brightness property
without adding led-max-microamp? It can always be added if a user
turns out to need it in the future. Or do you feel strongly that
led-max-microamp should be in the binding from the start?

> 
> > +  Default is 255.
> > +- label :  (optional)
> > +  see Documentation/devicetree/bindings/leds/common.txt
> > +- linux,default-trigger :  (optional)
> > +  see Documentation/devicetree/bindings/leds/common.txt
> > +
> > +
> > +Example:
> > +
> > +leds: is31fl3236@3c {
> > +	compatible = "issi,is31fl3236";
> > +	reg = <0x3c>;  
> 
> You're missing address-cells and size-cells in this example.

Indeed, I missed that when I renamed the child node property
from 'chan' to 'reg'.

> 
> > +
> > +	led@1 {
> > +		reg = <1>;
> > +		label = "EB:blue:usr0";
> > +		max-brightness = <128>;
> > +	};
> > +	led@2 {
> > +		reg = <2>;
> > +		label = "EB:blue:usr1";
> > +	};
> > +	...
> > +	led@36 {
> > +		reg = <36>;
> > +		label = "EB:blue:usr35";
> > +		max-brightness = <255>;
> > +	};
> > +};
> > +
> > +For more product information please see the link below:
> > +http://www.issi.com/US/product-analog-fxled-driver.shtml
> > \ No newline at end of file
> >  
> 
> 

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

* Re: [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers
  2016-02-24 18:57     ` David Rivshin (Allworx)
@ 2016-02-24 19:45       ` Rob Herring
  2016-02-24 23:16         ` David Rivshin (Allworx)
  0 siblings, 1 reply; 33+ messages in thread
From: Rob Herring @ 2016-02-24 19:45 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: Linux LED Subsystem, devicetree, Richard Purdie,
	Jacek Anaszewski, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, Stefan Wahren

On Wed, Feb 24, 2016 at 12:57 PM, David Rivshin (Allworx)
<drivshin.allworx@gmail.com> wrote:
> On Tue, 23 Feb 2016 19:15:08 -0600
> Rob Herring <robh@kernel.org> wrote:
>
>> On Tue, Feb 23, 2016 at 01:17:24PM -0500, David Rivshin (Allworx) wrote:
>> > From: David Rivshin <drivshin@allworx.com>
>> >
>> > This adds a binding description for the is31fl3236/35/18/16 I2C LED
>> > drivers.
>> >
>> > Signed-off-by: David Rivshin <drivshin@allworx.com>
>> > ---
>> >  .../devicetree/bindings/leds/leds-is31fl32xx.txt   | 51 ++++++++++++++++++++++
>> >  1 file changed, 51 insertions(+)
>> >  create mode 100644 Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
>>
>> Acked-by: Rob Herring <robh@kernel.org>
>
> Thanks, Rob. I just want to double check whether you noticed some
> binding-related questions I had in the coverletter [1]. In hindsight
> I should probably have included them in the patch comments as well so
> they'd show up in patchwork. For convenience, I'll repeat them here:

I had not.

> I choose to have 'reg' be 1-based in order to follow the hardware
> documentation. It seems 0-based is the normal choice, although HW
> docs also usually use the 0-based numbering. Perhaps being consistent
> with other bindings is more important than being consistent with the
> datasheet's numbering?

Usually reg is some type of address, so you should follow whatever is
more natural for accessing the h/w.

Ideally, it is not just an index for s/w convenience.

> I used the 'reg' property for the LED channel, as it seemed ePAPR
> required that name. I also considered naming the property 'chan',
> and not pretending that it represented a bus address at all (and
> then removing the @n from the subnode names). That would solve the
> 'reg' question above as a side-effect, but would be inconstant with
> other LED bindings (tca6507, pca963x, tlc59108, etc).

I would expect the reg value is either the register (base) address for
each channel or a bit offset in a register if they are all in the same
register. Or it could be how the pins are labeled on the package if
neither of those work.

> Note that the recently-added (and only in for-next) SN3218 driver
> uses a 0-based 'reg' property, and the SN3218 has the same HW doc
> numbering as the IS31FL32xx family (indeed the IS31FL3218 appears
> to be a rebranded SN3218). So perhaps that sets the precedent if
> there was not one before?

If they are the same, then you should use the SN3218 binding. If it
has problems, then it is not too late to fix it. You may want to add
another compatible string if they are not truly the exact same die.

Rob

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

* Re: [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers
  2016-02-24 19:34     ` David Rivshin (Allworx)
@ 2016-02-24 19:49       ` Rob Herring
  2016-02-26  0:30         ` David Rivshin (Allworx)
  0 siblings, 1 reply; 33+ messages in thread
From: Rob Herring @ 2016-02-24 19:49 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: Jacek Anaszewski, Linux LED Subsystem, devicetree,
	Richard Purdie, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, Stefan Wahren

On Wed, Feb 24, 2016 at 1:34 PM, David Rivshin (Allworx)
<drivshin.allworx@gmail.com> wrote:
> On Wed, 24 Feb 2016 17:04:49 +0100
> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>
>> Hi David,
>>
>> Thanks for the patch.
>>
>> On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:
>> > From: David Rivshin <drivshin@allworx.com>
>> >
>> > This adds a binding description for the is31fl3236/35/18/16 I2C LED
>> > drivers.
>> >
>> > Signed-off-by: David Rivshin <drivshin@allworx.com>
>> > ---
>> >   .../devicetree/bindings/leds/leds-is31fl32xx.txt   | 51 ++++++++++++++++++++++
>> >   1 file changed, 51 insertions(+)
>> >   create mode 100644 Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
>> >
>> > diff --git a/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt b/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
>> > new file mode 100644
>> > index 0000000..0a05a1d
>> > --- /dev/null
>> > +++ b/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
>> > @@ -0,0 +1,51 @@
>> > +Binding for ISSI IS31FL32xx LED Drivers
>> > +
>> > +The IS31FL32xx family of LED drivers are I2C devices with multiple
>> > +constant-current channels, each with independent 256-level PWM control.
>> > +Each LED is represented as a sub-node of the device.
>> > +
>> > +Required properties:
>> > +- compatible: one of
>> > +   issi,is31fl3236
>> > +   issi,is31fl3235
>> > +   issi,is31fl3218
>> > +   issi,is31fl3216
>> > +- reg: I2C slave address
>> > +- address-cells : must be 1
>> > +- size-cells : must be 0
>> > +
>> > +LED sub-node properties:
>> > +- reg : LED channel number (1..N)
>> > +- max-brightness : (optional) Maximum brightness possible for the LED.
>>
>> Please use led-max-microamp instead. You can refer to
>> Documentation/devicetree/bindings/leds/common.txt for detailed
>> description.
>
> FYI, I think I got that property from leds-pwm, since these use PWMs
> internally, although I don't actually use it myself.
>
> I can see how led-max-microamp could be a useful property, however
> I'm uncertain about computing cdev->max_brightness from it as these
> devices look to control their max current via an external resistor.
> I suppose either the resistor value, or the resultant max-current
> would need to be given in the devicetree, and then that used along
> with led-max-microamp to compute cdev->max_brightness. Although I'd
> want it to be optional as I suspect most users don't need any
> restriction. Also, I don't think I can properly test the result,
> which always makes me nervous.
>
> Would it be acceptable to simply remove the max-brightness property
> without adding led-max-microamp? It can always be added if a user
> turns out to need it in the future. Or do you feel strongly that
> led-max-microamp should be in the binding from the start?

You should put in DT whatever makes sense for the controls the h/w
has. If you only have a PWM, then only max-brightness makes sense and
led-max-microamp does not.

Rob

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

* Re: [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers
  2016-02-24 19:45       ` Rob Herring
@ 2016-02-24 23:16         ` David Rivshin (Allworx)
  2016-02-25  1:17           ` Rob Herring
  0 siblings, 1 reply; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-02-24 23:16 UTC (permalink / raw)
  To: Rob Herring, Stefan Wahren
  Cc: Linux LED Subsystem, devicetree, Richard Purdie,
	Jacek Anaszewski, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala

On Wed, 24 Feb 2016 13:45:14 -0600
Rob Herring <robh@kernel.org> wrote:

> On Wed, Feb 24, 2016 at 12:57 PM, David Rivshin (Allworx)
> <drivshin.allworx@gmail.com> wrote:
> > On Tue, 23 Feb 2016 19:15:08 -0600
> > Rob Herring <robh@kernel.org> wrote:
> >  
> >> On Tue, Feb 23, 2016 at 01:17:24PM -0500, David Rivshin (Allworx) wrote:  
> >> > From: David Rivshin <drivshin@allworx.com>
> >> >
> >> > This adds a binding description for the is31fl3236/35/18/16 I2C LED
> >> > drivers.
> >> >
> >> > Signed-off-by: David Rivshin <drivshin@allworx.com>
> >> > ---
> >> >  .../devicetree/bindings/leds/leds-is31fl32xx.txt   | 51 ++++++++++++++++++++++
> >> >  1 file changed, 51 insertions(+)
> >> >  create mode 100644 Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt  
> >>
> >> Acked-by: Rob Herring <robh@kernel.org>  
> >
> > Thanks, Rob. I just want to double check whether you noticed some
> > binding-related questions I had in the coverletter [1]. In hindsight
> > I should probably have included them in the patch comments as well so
> > they'd show up in patchwork. For convenience, I'll repeat them here:  
> 
> I had not.
> 
> > I choose to have 'reg' be 1-based in order to follow the hardware
> > documentation. It seems 0-based is the normal choice, although HW
> > docs also usually use the 0-based numbering. Perhaps being consistent
> > with other bindings is more important than being consistent with the
> > datasheet's numbering?  
> 
> Usually reg is some type of address, so you should follow whatever is
> more natural for accessing the h/w.

In this case I don't think there is a natural "address" in the HW. 
There are a number of (non-contiguous) registers (or parts thereof) 
which together control a "channel". They are not so much an "single-
LED controller" block which is replicated N times, but an monolithic 
"N-channel LED controller".

That is also why originally I had used a 'chan' property instead. But, 
I then saw that ePAPR required a 'reg' property to use the @n labeling, 
and it seemed all bindings for similar devices used a 'reg' property.

> Ideally, it is not just an index for s/w convenience.

Thanks, that is useful clarification. 

> > I used the 'reg' property for the LED channel, as it seemed ePAPR
> > required that name. I also considered naming the property 'chan',
> > and not pretending that it represented a bus address at all (and
> > then removing the @n from the subnode names). That would solve the
> > 'reg' question above as a side-effect, but would be inconstant with
> > other LED bindings (tca6507, pca963x, tlc59108, etc).  
> 
> I would expect the reg value is either the register (base) address for
> each channel or a bit offset in a register if they are all in the same
> register. Or it could be how the pins are labeled on the package if
> neither of those work.

I don't think that 'reg' being a register base address or bit offset
would work in this case, for the reasons mentioned above.
The only really obvious numbering is that the datasheets name them 
OUT1 through OUTn, and I'd expect schematics to follow that naming. 
So that seemed like the natural way to refer to them in the devicetree, 
and let the driver compute the various register addresses.

Just for the sake of illustration here are some of the important 
registers for these devices:
 3236:
   - 0x01-0x24: per channel PWM duty cycle registers (low-to-high)
   - 0x26-0x49: per channel enable bit and current divisor
 3235:
   - 0x01-0x20: per channel PWM duty cycle registers (low-to-high)
   - 0x2A-0x45: per channel enable bit and current divisor
 3218:
   - 0x01-0x12: per channel PWM duty cycle registers (low-to-high)
   - 0x13h: OUT1-6 packed enable bits, LSB to MSB
   - 0x14h: OUT7-12 packed enable bits, LSB to MSB
   - 0x15h: OUT13-18 packed enable bits, LSB to MSB
 3216:
   - 0x01: OUT9-16 packed enable bits, LSB to MSB
   - 0x02: OUT1-8 packed enable bits, LSB to MSB
   - 0x04: OUT9-16 GPIO (vs LED) bits
   - 0x10-0x1F: per channel PWM duty cycle registers (high-to-low)
   - 0x20-0xAF: 8 sets of enable and PWM registers for animation

> > Note that the recently-added (and only in for-next) SN3218 driver
> > uses a 0-based 'reg' property, and the SN3218 has the same HW doc
> > numbering as the IS31FL32xx family (indeed the IS31FL3218 appears
> > to be a rebranded SN3218). So perhaps that sets the precedent if
> > there was not one before?  
> 
> If they are the same, then you should use the SN3218 binding. If it
> has problems, then it is not too late to fix it. 

Given the above, do you think that the SN3218 binding should be changed
so that 'reg' is 1-based instead of 0-based?

> You may want to add
> another compatible string if they are not truly the exact same die.

Are you implying that if the IS31FL3218 and SN3218 are the same die
(which they may well be), that it would *not* be appropriate to have 
two "compatible" strings for them (in the same binding)? 
I was assuming that having two compatible strings would be preferable 
regardless, as they are sold under two different part numbers and 
companies. The only reason I noticed they were the same was because I 
was looking for an example of another device that numbered channels 
starting with '1' and was surprised how similar the datasheet was.

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

* Re: [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers
  2016-02-24 23:16         ` David Rivshin (Allworx)
@ 2016-02-25  1:17           ` Rob Herring
  0 siblings, 0 replies; 33+ messages in thread
From: Rob Herring @ 2016-02-25  1:17 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: Stefan Wahren, Linux LED Subsystem, devicetree, Richard Purdie,
	Jacek Anaszewski, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala

On Wed, Feb 24, 2016 at 5:16 PM, David Rivshin (Allworx) <drivshin.allworx@gmail.com> wrote:
> On Wed, 24 Feb 2016 13:45:14 -0600
> Rob Herring <robh@kernel.org> wrote:
>
>> On Wed, Feb 24, 2016 at 12:57 PM, David Rivshin (Allworx)
>> <drivshin.allworx@gmail.com> wrote:
>> > On Tue, 23 Feb 2016 19:15:08 -0600
>> > Rob Herring <robh@kernel.org> wrote:
>> >
>> >> On Tue, Feb 23, 2016 at 01:17:24PM -0500, David Rivshin (Allworx) wrote:
>> >> > From: David Rivshin <drivshin@allworx.com>
>> >> >
>> >> > This adds a binding description for the is31fl3236/35/18/16 I2C LED
>> >> > drivers.
>> >> >
>> >> > Signed-off-by: David Rivshin <drivshin@allworx.com>
>> >> > ---
>> >> >  .../devicetree/bindings/leds/leds-is31fl32xx.txt   | 51 ++++++++++++++++++++++
>> >> >  1 file changed, 51 insertions(+)
>> >> >  create mode 100644 Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
>> >>
>> >> Acked-by: Rob Herring <robh@kernel.org>
>> >
>> > Thanks, Rob. I just want to double check whether you noticed some
>> > binding-related questions I had in the coverletter [1]. In hindsight
>> > I should probably have included them in the patch comments as well so
>> > they'd show up in patchwork. For convenience, I'll repeat them here:
>>
>> I had not.
>>
>> > I choose to have 'reg' be 1-based in order to follow the hardware
>> > documentation. It seems 0-based is the normal choice, although HW
>> > docs also usually use the 0-based numbering. Perhaps being consistent
>> > with other bindings is more important than being consistent with the
>> > datasheet's numbering?
>>
>> Usually reg is some type of address, so you should follow whatever is
>> more natural for accessing the h/w.
>
> In this case I don't think there is a natural "address" in the HW.
> There are a number of (non-contiguous) registers (or parts thereof)
> which together control a "channel". They are not so much an "single-
> LED controller" block which is replicated N times, but an monolithic
> "N-channel LED controller".
>
> That is also why originally I had used a 'chan' property instead. But,
> I then saw that ePAPR required a 'reg' property to use the @n labeling,
> and it seemed all bindings for similar devices used a 'reg' property.
>
>> Ideally, it is not just an index for s/w convenience.
>
> Thanks, that is useful clarification.
>
>> > I used the 'reg' property for the LED channel, as it seemed ePAPR
>> > required that name. I also considered naming the property 'chan',
>> > and not pretending that it represented a bus address at all (and
>> > then removing the @n from the subnode names). That would solve the
>> > 'reg' question above as a side-effect, but would be inconstant with
>> > other LED bindings (tca6507, pca963x, tlc59108, etc).
>>
>> I would expect the reg value is either the register (base) address for
>> each channel or a bit offset in a register if they are all in the same
>> register. Or it could be how the pins are labeled on the package if
>> neither of those work.
>
> I don't think that 'reg' being a register base address or bit offset
> would work in this case, for the reasons mentioned above.
> The only really obvious numbering is that the datasheets name them
> OUT1 through OUTn, and I'd expect schematics to follow that naming.
> So that seemed like the natural way to refer to them in the devicetree,
> and let the driver compute the various register addresses.
>
> Just for the sake of illustration here are some of the important
> registers for these devices:
>  3236:
>    - 0x01-0x24: per channel PWM duty cycle registers (low-to-high)
>    - 0x26-0x49: per channel enable bit and current divisor
>  3235:
>    - 0x01-0x20: per channel PWM duty cycle registers (low-to-high)
>    - 0x2A-0x45: per channel enable bit and current divisor
>  3218:
>    - 0x01-0x12: per channel PWM duty cycle registers (low-to-high)
>    - 0x13h: OUT1-6 packed enable bits, LSB to MSB
>    - 0x14h: OUT7-12 packed enable bits, LSB to MSB
>    - 0x15h: OUT13-18 packed enable bits, LSB to MSB
>  3216:
>    - 0x01: OUT9-16 packed enable bits, LSB to MSB
>    - 0x02: OUT1-8 packed enable bits, LSB to MSB
>    - 0x04: OUT9-16 GPIO (vs LED) bits
>    - 0x10-0x1F: per channel PWM duty cycle registers (high-to-low)
>    - 0x20-0xAF: 8 sets of enable and PWM registers for animation
>
>> > Note that the recently-added (and only in for-next) SN3218 driver
>> > uses a 0-based 'reg' property, and the SN3218 has the same HW doc
>> > numbering as the IS31FL32xx family (indeed the IS31FL3218 appears
>> > to be a rebranded SN3218). So perhaps that sets the precedent if
>> > there was not one before?
>>
>> If they are the same, then you should use the SN3218 binding. If it
>> has problems, then it is not too late to fix it.
>
> Given the above, do you think that the SN3218 binding should be changed
> so that 'reg' is 1-based instead of 0-based?
>
>> You may want to add
>> another compatible string if they are not truly the exact same die.
>
> Are you implying that if the IS31FL3218 and SN3218 are the same die
> (which they may well be), that it would *not* be appropriate to have
> two "compatible" strings for them (in the same binding)?
> I was assuming that having two compatible strings would be preferable
> regardless, as they are sold under two different part numbers and
> companies. The only reason I noticed they were the same was because I
> was looking for an example of another device that numbered channels
> starting with '1' and was surprised how similar the datasheet was.
>

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-24 16:04   ` Jacek Anaszewski
@ 2016-02-25  2:24     ` David Rivshin (Allworx)
  2016-02-25 10:55       ` Jacek Anaszewski
  0 siblings, 1 reply; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-02-25  2:24 UTC (permalink / raw)
  To: Jacek Anaszewski
  Cc: linux-leds, devicetree, Richard Purdie, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Stefan Wahren

On Wed, 24 Feb 2016 17:04:58 +0100
Jacek Anaszewski <j.anaszewski@samsung.com> wrote:

> Hi David,
> 
> Thanks for the patch. Very nice driver. I have few comments
> below.

Thanks Jacek, I have responded the comments inline. I also wanted to 
double check whether you noticed some questions I had in the cover 
letter [1]. As I mentioned in another email to Rob, in hindsight I'm 
guessing I should have included them in the patch comments as well (or 
instead of).

Your review comments here effectively answered some of the questions, but 
the big one I'm still unsure of is whether it actually makes sense to 
have all 4 of these devices supported by a single driver. I won't 
clutter this email with a duplicate of the details (it's somewhat long), 
but if you could check the cover letter and give some guidance, I would 
appreciate it.

[1] http://www.spinics.net/lists/linux-leds/msg05564.html
    http://thread.gmane.org/gmane.linux.leds/4530

> 
> On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:
> > From: David Rivshin <drivshin@allworx.com>
> >
> > The IS31FL32xx family of LED drivers are I2C devices with multiple
> > constant-current channels, each with independent 256-level PWM control.
> >
> > HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
> >
> > This has been tested on the IS31FL3236 and IS31FL3216 on an ARM
> > (TI am335x) platform.
> >
> > The programming paradigm of these devices is similar in the following
> > ways:
> >   - All registers are 8 bit
> >   - All LED control registers are write-only
> >   - Each LED channel has a PWM register (0-255)
> >   - PWM register writes are shadowed until an Update register is poked
> >   - All have a concept of Software Shutdown, which disables output
> >
> > However, there are some differences in devices:
> >   - 3236/3235 have a separate Control register for each LED,
> >     (3218/3216 pack the enable bits into fewer registers)
> >   - 3236/3235 have a per-channel current divisor setting
> >   - 3236/3235 have a Global Control register that can turn off all LEDs
> >   - 3216 is unique in a number of ways
> >      - OUT9-OUT16 can be configured as GPIOs instead of LED controls
> >      - LEDs can be programmed with an 8-frame animation, with
> >        programmable delay between frames
> >      - LEDs can be modulated by an input audio signal
> >      - Max output current can be adjusted from 1/4 to 2x globally
> >      - Has a Configuration register instead of a Shutdown register
> >
> > This driver currently only supports the base PWM control function
> > of these devices. The following features of these devices are not
> > implemented, although it should be possible to add them in the future:
> >   - All devices are capable of going into a lower-power "software
> >     shutdown" mode.
> >   - The is31fl3236 and is31fl3235 can reduce the max output current
> >     per-channel with a divisor of 1, 2, 3, or 4.
> >   - The is31fl3216 can use some LED channels as GPIOs instead.
> >   - The is31fl3216 can animate LEDs in hardware.
> >   - The is31fl3216 can modulate LEDs according to an audio input.
> >   - The is31fl3216 can reduce/increase max output current globally.
> >
> > Signed-off-by: David Rivshin <drivshin@allworx.com>
> > ---
> >   drivers/leds/Kconfig           |   9 +
> >   drivers/leds/Makefile          |   1 +
> >   drivers/leds/leds-is31fl32xx.c | 442 +++++++++++++++++++++++++++++++++++++++++
> >   3 files changed, 452 insertions(+)
> >   create mode 100644 drivers/leds/leds-is31fl32xx.c
> >
> > diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> > index 1034696..8f6c46f 100644
> > --- a/drivers/leds/Kconfig
> > +++ b/drivers/leds/Kconfig
> > @@ -580,6 +580,15 @@ config LEDS_SN3218
> >   	  This driver can also be built as a module. If so the module
> >   	  will be called leds-sn3218.
> >
> > +config LEDS_IS31FL32XX
> > +	tristate "Driver for ISSI IS31FL32XX I2C LED driver chip family"  
> 
> 2 x "[Dd]river".
> 
> How about:
> 
> "LED Support for ISSI IS31FL32XX I2C LED chip family" ?

Yes, I found that awkward as well. HW folks (and the datasheets) seem
always refer to devices of this type as "LED Driver"s (which can lead
to some interesting confusions). Taking a cue from the LP5521/23/62
entries, how about:
 "LED Support for the ISSI IS31FL32XX I2C LED driver chip family" ?
Perhaps that's the best of both worlds?

> > +	depends on LEDS_CLASS && I2C && OF
> > +	help
> > +	  Say Y here to include support for the ISSI 31FL32XX LED driver family.  
> 
> s/driver/chip/
> 
> > +	  They are I2C devices with multiple constant-current channels, each
> > +	  with independent 256-level PWM control. This will only work with
> > +	  device tree enabled devices.  
> 
> We can skip the last sentence I think.

OK. FYI, I think I got that verbiage from LEDS_SYSCON.

> > +
> >   comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
> >
> >   config LEDS_BLINKM
> > diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> > index 89c9b6f..3fdf313 100644
> > --- a/drivers/leds/Makefile
> > +++ b/drivers/leds/Makefile
> > @@ -67,6 +67,7 @@ obj-$(CONFIG_LEDS_KTD2692)		+= leds-ktd2692.o
> >   obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
> >   obj-$(CONFIG_LEDS_SEAD3)		+= leds-sead3.o
> >   obj-$(CONFIG_LEDS_SN3218)		+= leds-sn3218.o
> > +obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
> >
> >   # LED SPI Drivers
> >   obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
> > diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
> > new file mode 100644
> > index 0000000..8dea518
> > --- /dev/null
> > +++ b/drivers/leds/leds-is31fl32xx.c
> > @@ -0,0 +1,442 @@
> > +/*
> > + * linux/drivers/leds-is31fl32xx.c
> > + *
> > + * Driver for ISSI IS31FL32xx family of I2C LED controllers
> > + *
> > + * Copyright 2015 Allworx Corp.
> > + *
> > + *
> > + * 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.
> > + *
> > + * HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
> > + */
> > +
> > +#include <linux/err.h>
> > +#include <linux/i2c.h>
> > +#include <linux/kernel.h>
> > +#include <linux/leds.h>
> > +#include <linux/module.h>
> > +#include <linux/of_platform.h>
> > +
> > +#ifdef DEBUG
> > + #undef dev_dbg
> > + #define dev_dbg dev_info
> > +#endif  
> 
> What's the benefit of the above?

It gave me a way to easily see debug output from the driver while it 
was parsing the DT (especially if the driver was built-in). Early on 
there were other things within that #ifdef as well.
Regardless, passing ddebug_query on the kernel commandline is a more 
appropriate way of accomplishing that; I'll remove for the next version.

> > +/* Used to indicate a device has no such register */
> > +#define IS31FL32XX_REG_NONE 0xFF
> > +
> > +#define IS31FL3216_CONFIG_REG 0x00
> > +#define IS31FL3216_LIGHTING_EFFECT_REG 0x03
> > +#define IS31FL3216_CHANNEL_CONFIG_REG 0x04
> > +
> > +struct is31fl32xx_priv;
> > +struct is31fl32xx_led_data {
> > +	struct led_classdev cdev;
> > +	u8 channel; /* 1-based, max priv->cdef->channels */
> > +	struct is31fl32xx_priv *priv;
> > +};
> > +
> > +struct is31fl32xx_priv {
> > +	const struct is31fl32xx_chipdef *cdef;
> > +	struct i2c_client *client;
> > +	unsigned int num_leds;
> > +	struct is31fl32xx_led_data leds[0];  
> 
> Is there any specific reason for not having *leds here instead?

I followed a pattern from leds-pwm where it did a single allocation 
for both priv and priv->leds[]. See sizeof_is31fl32xx_priv(), and 
its use, below. I saw the benefit as one fewer small allocation, so 
slightly more kind to the allocator (and devres). If you'd prefer to 
do it as two allocations, I'll make the change.

> > +};
> > +
> > +/**
> > + * struct is31fl32xx_chipdef - chip-specific attributes
> > + * @channels            : Number of LED channels
> > + * @shutdown_reg        : address of Shutdown register (optional)
> > + * @pwm_update_reg      : address of PWM Update register
> > + * @global_control_reg	: address of Global Control register (optional)
> > + * @reset_reg           : address of Reset register (optional)
> > + * @pwm_register_base	: address of first PWM register
> > + * @pwm_registers_reversed: : true if PWM registers count down instead of up
> > + * @led_control_register_base : address of first LED control register (optional)
> > + * @enable_bits_per_led_control_register: number of LEDs enable bits in each
> > + * @reset_func:         : pointer to reset function
> > + *
> > + * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE
> > + * indicates that this chip has no such register.
> > + *
> > + * If non-NULL, @reset_func will be called during probing to set all
> > + * necessary registers to a known initialization state. This is needed
> > + * for chips that do not have a @reset_reg.
> > + *
> > + * @enable_bits_per_led_control_register must be >=1 if
> > + * @led_control_register_base != %IS31FL32XX_REG_NONE.
> > + */
> > +struct is31fl32xx_chipdef {
> > +	u8	channels;
> > +	u8	shutdown_reg;
> > +	u8	pwm_update_reg;
> > +	u8	global_control_reg;
> > +	u8	reset_reg;
> > +	u8	pwm_register_base;
> > +	bool	pwm_registers_reversed;
> > +	u8	led_control_register_base;
> > +	u8	enable_bits_per_led_control_register;
> > +	int (*reset_func)(struct is31fl32xx_priv *priv);
> > +};
> > +
> > +static const struct is31fl32xx_chipdef is31fl3236_cdef = {
> > +	.channels				= 36,
> > +	.shutdown_reg				= 0x00,
> > +	.pwm_update_reg				= 0x25,
> > +	.global_control_reg			= 0x4a,
> > +	.reset_reg				= 0x4f,
> > +	.pwm_register_base			= 0x01,
> > +	.led_control_register_base		= 0x26,
> > +	.enable_bits_per_led_control_register	= 1,
> > +};
> > +
> > +static const struct is31fl32xx_chipdef is31fl3235_cdef = {
> > +	.channels				= 28,
> > +	.shutdown_reg				= 0x00,
> > +	.pwm_update_reg				= 0x25,
> > +	.global_control_reg			= 0x4a,
> > +	.reset_reg				= 0x4f,
> > +	.pwm_register_base			= 0x05,
> > +	.led_control_register_base		= 0x2a,
> > +	.enable_bits_per_led_control_register	= 1,
> > +};
> > +
> > +static const struct is31fl32xx_chipdef is31fl3218_cdef = {
> > +	.channels				= 18,
> > +	.shutdown_reg				= 0x00,
> > +	.pwm_update_reg				= 0x16,
> > +	.global_control_reg			= IS31FL32XX_REG_NONE,
> > +	.reset_reg				= 0x17,
> > +	.pwm_register_base			= 0x01,
> > +	.led_control_register_base		= 0x13,
> > +	.enable_bits_per_led_control_register	= 6,
> > +};
> > +
> > +static int is31fl3216_reset(struct is31fl32xx_priv *priv);
> > +static const struct is31fl32xx_chipdef is31fl3216_cdef = {
> > +	.channels				= 16,
> > +	.shutdown_reg				= IS31FL32XX_REG_NONE,
> > +	.pwm_update_reg				= 0xB0,
> > +	.global_control_reg			= IS31FL32XX_REG_NONE,
> > +	.reset_reg				= IS31FL32XX_REG_NONE,
> > +	.pwm_register_base			= 0x10,
> > +	.pwm_registers_reversed			= true,
> > +	.led_control_register_base		= 0x01,
> > +	.enable_bits_per_led_control_register	= 8,
> > +	.reset_func				= is31fl3216_reset,
> > +};
> > +
> > +static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)
> > +{
> > +	int ret;
> > +
> > +	dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
> > +
> > +	ret =  i2c_smbus_write_byte_data(priv->client, reg, val);
> > +	if (ret) {
> > +		dev_err(&priv->client->dev,
> > +			"register write to 0x%02X failed (error %d)",
> > +			reg, ret);
> > +	}
> > +	return ret;
> > +}
> > +
> > +/*
> > + * Custom reset function for IS31FL3216 because it does not have a RESET
> > + * register the way that the other IS31FL32xx chips do. We don't bother
> > + * writing the GPIO and animation registers, because the registers we
> > + * do write ensure those will have no effect.
> > + */
> > +static int is31fl3216_reset(struct is31fl32xx_priv *priv)
> > +{
> > +	unsigned int i;
> > +	int ret;
> > +
> > +	for (i = 0; i < priv->cdef->channels; i++) {
> > +		ret = is31fl32xx_write(priv, priv->cdef->pwm_register_base+i,
> > +				       0x00);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +	ret = is31fl32xx_write(priv, priv->cdef->pwm_update_reg, 0);
> > +	if (ret)
> > +		return ret;
> > +	ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, 0x00);
> > +	if (ret)
> > +		return ret;
> > +	ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, 0x00);
> > +	if (ret)
> > +		return ret;
> > +	ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, 0x00);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return 0;
> > +}
> > +
> > +
> > +static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,
> > +				     enum led_brightness brightness)
> > +{
> > +	const struct is31fl32xx_led_data *led_data =
> > +		container_of(led_cdev, struct is31fl32xx_led_data, cdev);
> > +	const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef;
> > +	u8 pwm_register_offset;
> > +	int ret;
> > +
> > +	dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);
> > +
> > +	/* NOTE: led_data->channel is 1-based */
> > +	if (cdef->pwm_registers_reversed)
> > +		pwm_register_offset = cdef->channels - led_data->channel;
> > +	else
> > +		pwm_register_offset = led_data->channel - 1;
> > +
> > +	ret = is31fl32xx_write(led_data->priv,
> > +			       cdef->pwm_register_base + pwm_register_offset,
> > +			       brightness);
> > +	if (ret)
> > +		return ret;  
> 
> I infer that nothing wrong happens in case current process is preempted
> here by the call originating from the other sub-LED?

I do not believe so. All the driver-specific data used here is read-only
after probing. chipdefs are entirely const, and the only thing in priv
that's referenced is the chipdef pointer which logically could not change
post-probe. Actually nothing else in priv is modified post-probe either.

The I2C core code has a mutex on the bus, so two writes cannot happen at
once.

In all these devices there is a unique PWM duty-cycle register for each
LED channel (which is what is being written here), so no register writes
for one LED channel effect any others.

I believe the worst that could happen is that the device would see:
	PWM_REG_A write X
	PWM_REG_B write Y
	UPDATE_REG write 0
	UPDATE_REG write 0
instead of 
	PWM_REG_A write X
	UPDATE_REG write 0
	PWM_REG_B write Y
	UPDATE_REG write 0
but that makes no difference to the functionality. Poking the update 
register merely applies all PWM register writes up to that point (I'm 
assuming to allow atomically changing the state of multiple LEDs at 
once).

I should note here (as mentioned in cover letter), I made a choice to
always leave the per-LED "enable" bits on, and let the PWM just get set
to 0 naturally to turn an LED off. This differs from the existing SN3218 
driver, which used regmap_update_bits, and is then protected by a per-
regmap mutex.

> > +	return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);
> > +
> > +}
> > +
> > +static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
> > +{
> > +	const struct is31fl32xx_chipdef *cdef = priv->cdef;
> > +	int ret;
> > +
> > +	if (cdef->reset_reg != IS31FL32XX_REG_NONE) {
> > +		ret = is31fl32xx_write(priv, cdef->reset_reg, 0);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +	if (cdef->reset_func) {
> > +		ret = cdef->reset_func(priv);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +	if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) {
> > +		u8 value =
> > +		    GENMASK(cdef->enable_bits_per_led_control_register-1, 0);
> > +		u8 num_regs = cdef->channels /
> > +				cdef->enable_bits_per_led_control_register;
> > +		int i;
> > +
> > +		for (i = 0; i < num_regs; i++) {
> > +			ret = is31fl32xx_write(priv,
> > +					       cdef->led_control_register_base+i,
> > +					       value);
> > +			if (ret)
> > +				return ret;
> > +		}
> > +	}
> > +	if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) {
> > +		ret = is31fl32xx_write(priv, cdef->shutdown_reg, BIT(0));
> > +		if (ret)
> > +			return ret;
> > +	}
> > +	if (cdef->global_control_reg != IS31FL32XX_REG_NONE) {
> > +		ret = is31fl32xx_write(priv, cdef->global_control_reg, 0x00);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static inline size_t sizeof_is31fl32xx_priv(int num_leds)
> > +{
> > +	return sizeof(struct is31fl32xx_priv) +
> > +		      (sizeof(struct is31fl32xx_led_data) * num_leds);
> > +}
> > +
> > +static int is31fl32xx_parse_child_dt(const struct device *dev,
> > +				     const struct device_node *child,
> > +				     struct is31fl32xx_led_data *led_data)
> > +{
> > +	struct led_classdev *cdev = &led_data->cdev;
> > +	int ret = 0;
> > +	u32 reg;
> > +
> > +	cdev->name = of_get_property(child, "label", NULL) ? : child->name;
> > +
> > +	ret = of_property_read_u32(child, "reg", &reg);
> > +	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
> > +		dev_err(dev,
> > +			"Child node %s does not have a valid reg property\n",
> > +			child->name);
> > +		return -EINVAL;
> > +	}
> > +	led_data->channel = reg;
> > +
> > +	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
> > +						NULL);
> > +	cdev->brightness = LED_OFF;  
> 
> devm_kzalloc secures that.

OK, I will remove.

> > +	ret = of_property_read_u32(child, "max-brightness",
> > +				   &cdev->max_brightness);
> > +	if (ret == -EINVAL) {
> > +		cdev->max_brightness = 255;  
> 
> s/255/LED_FULL/

Noted, although (from the patch 2 discussion) max-brightness property is
removed/replaced, this would go away anyways.

> > +	} else if (ret) {
> > +		dev_dbg(dev,
> > +			"Child node %s has an invalid max-brightness property\n",
> > +			child->name);
> > +		return -EINVAL;
> > +	}
> > +
> > +	cdev->brightness_set_blocking = is31fl32xx_brightness_set;  
> 
> Please add empty line here.

Done.

> > +	return 0;
> > +}
> > +
> > +static struct is31fl32xx_led_data *is31fl32xx_find_led_data(
> > +					struct is31fl32xx_priv *priv,
> > +					u8 channel)
> > +{
> > +	size_t i;
> > +
> > +	for (i = 0; i < priv->num_leds; i++) {
> > +		if (priv->leds[i].channel == channel)
> > +			return &priv->leds[i];
> > +	}  
> 
> Ditto.

Done.

> > +	return NULL;
> > +}
> > +
> > +static int is31fl32xx_parse_dt(struct device *dev,
> > +			       struct is31fl32xx_priv *priv)
> > +{
> > +	struct device_node *child;
> > +
> > +	for_each_child_of_node(dev->of_node, child) {
> > +		struct is31fl32xx_led_data *led_data =
> > +			&priv->leds[priv->num_leds];
> > +		int ret = 0;
> > +		const struct is31fl32xx_led_data *other_led_data;
> > +
> > +		led_data->priv = priv;
> > +
> > +		ret = is31fl32xx_parse_child_dt(dev, child, led_data);
> > +		if (ret)
> > +			continue;  
> 
> I prefer failing in such cases,

OK, I will change to an 'goto err' which will have an 'of_node_put()'
and 'return ret'.

I will say, however, that while testing the error-detection in the
parsing logic, it was very convenient to construct a single devicetree
with a variety of errors. Then a single boot would test multiple
cases at once.

> > +
> > +		/* Detect if channel is already in use by another child */
> > +		other_led_data = is31fl32xx_find_led_data(priv,
> > +							  led_data->channel);
> > +		if (other_led_data) {
> > +			dev_err(dev,
> > +				"%s ignored: channel %d already used by %s",
> > +				led_data->cdev.name,
> > +				led_data->channel,
> > +				other_led_data->cdev.name);
> > +			continue;  
> 
> Ditto.

OK.

> > +		}
> > +
> > +		ret = devm_led_classdev_register(dev, &led_data->cdev);
> > +		if (ret == 0) {
> > +			priv->num_leds++;
> > +		} else {
> > +			dev_err(dev, "failed to register PWM led for %s: %d\n",
> > +				led_data->cdev.name, ret);

Should I also fail here, then? Right now it will continue trying to 
register future LED devices if a classdev_register fails for some 
reason, and will successfully load even if all of them fail.

> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct of_device_id of_is31fl31xx_match[] = {
> > +	{ .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },
> > +	{ .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },
> > +	{ .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, },
> > +	{ .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, },
> > +	{},
> > +};
> > +
> > +MODULE_DEVICE_TABLE(of, of_is31fl31xx_match);
> > +
> > +static int is31fl32xx_probe(struct i2c_client *client,
> > +				  const struct i2c_device_id *id)
> > +{
> > +	const struct is31fl32xx_chipdef *cdef;
> > +	const struct of_device_id *of_dev_id;
> > +	struct device *dev = &client->dev;
> > +	struct is31fl32xx_priv *priv;
> > +	int count;
> > +	int ret = 0;
> > +
> > +	of_dev_id = of_match_device(of_is31fl31xx_match, dev);
> > +	if (!of_dev_id)
> > +		return -EINVAL;
> > +
> > +	cdef = of_dev_id->data;
> > +
> > +	count = of_get_child_count(dev->of_node);
> > +	if (!count)
> > +		return -EINVAL;
> > +
> > +	priv = devm_kzalloc(dev, sizeof_is31fl32xx_priv(count),
> > +			    GFP_KERNEL);
> > +	if (!priv)
> > +		return -ENOMEM;
> > +
> > +	priv->client = client;
> > +	priv->cdef = cdef;
> > +	i2c_set_clientdata(client, priv);
> > +
> > +	ret = is31fl32xx_init_regs(priv);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = is31fl32xx_parse_dt(dev, priv);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return 0;
> > +}
> > +
> > +static int is31fl32xx_remove(struct i2c_client *client)
> > +{
> > +	struct is31fl32xx_priv *priv = i2c_get_clientdata(client);
> > +
> > +	/* If there is a reset reg, then it does everything we need */
> > +	if (priv->cdef->reset_reg != IS31FL32XX_REG_NONE)
> > +		return is31fl32xx_write(priv, priv->cdef->reset_reg, 0);
> > +
> > +	/* If there is a reset func, then it does everything we need */
> > +	if (priv->cdef->reset_func)
> > +		return priv->cdef->reset_func(priv);
> > +
> > +	/* If we can't reset, then try just using software-shutdown mode */
> > +	if (priv->cdef->shutdown_reg != IS31FL32XX_REG_NONE)
> > +		return is31fl32xx_write(priv, priv->cdef->shutdown_reg, 0x00);
> > +
> > +	return 0;
> > +}
> > +
> > +/*
> > + * i2c-core requires that id_table be non-NULL, even though
> > + * it is not used for DeviceTree based instantiation.
> > + */
> > +static const struct i2c_device_id is31fl31xx_id[] = {
> > +	{},
> > +};
> > +
> > +MODULE_DEVICE_TABLE(i2c, is31fl31xx_id);
> > +
> > +static struct i2c_driver is31fl32xx_driver = {
> > +	.driver = {
> > +		.name	= "is31fl32xx",
> > +		.of_match_table = of_is31fl31xx_match,
> > +	},
> > +	.probe		= is31fl32xx_probe,
> > +	.remove		= is31fl32xx_remove,
> > +	.id_table	= is31fl31xx_id,
> > +};
> > +
> > +module_i2c_driver(is31fl32xx_driver);
> > +
> > +MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>");
> > +MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver");
> > +MODULE_LICENSE("GPL v2");
> >  
 

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-25  2:24     ` David Rivshin (Allworx)
@ 2016-02-25 10:55       ` Jacek Anaszewski
  2016-02-25 19:12         ` David Rivshin (Allworx)
  0 siblings, 1 reply; 33+ messages in thread
From: Jacek Anaszewski @ 2016-02-25 10:55 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: linux-leds, devicetree, Richard Purdie, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Stefan Wahren

On 02/25/2016 03:24 AM, David Rivshin (Allworx) wrote:
> On Wed, 24 Feb 2016 17:04:58 +0100
> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>
>> Hi David,
>>
>> Thanks for the patch. Very nice driver. I have few comments
>> below.
>
> Thanks Jacek, I have responded the comments inline. I also wanted to
> double check whether you noticed some questions I had in the cover
> letter [1]. As I mentioned in another email to Rob, in hindsight I'm
> guessing I should have included them in the patch comments as well (or
> instead of).

I saw them. I assumed that the review itself will address those
questions.

> Your review comments here effectively answered some of the questions, but
> the big one I'm still unsure of is whether it actually makes sense to
> have all 4 of these devices supported by a single driver.

It's perfectly fine. Many drivers implement this pattern.

> I won't
> clutter this email with a duplicate of the details (it's somewhat long),
> but if you could check the cover letter and give some guidance, I would
> appreciate it.
>
> [1] http://www.spinics.net/lists/linux-leds/msg05564.html
>      http://thread.gmane.org/gmane.linux.leds/4530
>
>>
>> On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:
>>> From: David Rivshin <drivshin@allworx.com>
>>>
>>> The IS31FL32xx family of LED drivers are I2C devices with multiple
>>> constant-current channels, each with independent 256-level PWM control.
>>>
>>> HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
>>>
>>> This has been tested on the IS31FL3236 and IS31FL3216 on an ARM
>>> (TI am335x) platform.
>>>
>>> The programming paradigm of these devices is similar in the following
>>> ways:
>>>    - All registers are 8 bit
>>>    - All LED control registers are write-only
>>>    - Each LED channel has a PWM register (0-255)
>>>    - PWM register writes are shadowed until an Update register is poked
>>>    - All have a concept of Software Shutdown, which disables output
>>>
>>> However, there are some differences in devices:
>>>    - 3236/3235 have a separate Control register for each LED,
>>>      (3218/3216 pack the enable bits into fewer registers)
>>>    - 3236/3235 have a per-channel current divisor setting
>>>    - 3236/3235 have a Global Control register that can turn off all LEDs
>>>    - 3216 is unique in a number of ways
>>>       - OUT9-OUT16 can be configured as GPIOs instead of LED controls
>>>       - LEDs can be programmed with an 8-frame animation, with
>>>         programmable delay between frames
>>>       - LEDs can be modulated by an input audio signal
>>>       - Max output current can be adjusted from 1/4 to 2x globally
>>>       - Has a Configuration register instead of a Shutdown register
>>>
>>> This driver currently only supports the base PWM control function
>>> of these devices. The following features of these devices are not
>>> implemented, although it should be possible to add them in the future:
>>>    - All devices are capable of going into a lower-power "software
>>>      shutdown" mode.
>>>    - The is31fl3236 and is31fl3235 can reduce the max output current
>>>      per-channel with a divisor of 1, 2, 3, or 4.
>>>    - The is31fl3216 can use some LED channels as GPIOs instead.
>>>    - The is31fl3216 can animate LEDs in hardware.
>>>    - The is31fl3216 can modulate LEDs according to an audio input.
>>>    - The is31fl3216 can reduce/increase max output current globally.
>>>
>>> Signed-off-by: David Rivshin <drivshin@allworx.com>
>>> ---
>>>    drivers/leds/Kconfig           |   9 +
>>>    drivers/leds/Makefile          |   1 +
>>>    drivers/leds/leds-is31fl32xx.c | 442 +++++++++++++++++++++++++++++++++++++++++
>>>    3 files changed, 452 insertions(+)
>>>    create mode 100644 drivers/leds/leds-is31fl32xx.c
>>>
>>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>>> index 1034696..8f6c46f 100644
>>> --- a/drivers/leds/Kconfig
>>> +++ b/drivers/leds/Kconfig
>>> @@ -580,6 +580,15 @@ config LEDS_SN3218
>>>    	  This driver can also be built as a module. If so the module
>>>    	  will be called leds-sn3218.
>>>
>>> +config LEDS_IS31FL32XX
>>> +	tristate "Driver for ISSI IS31FL32XX I2C LED driver chip family"
>>
>> 2 x "[Dd]river".
>>
>> How about:
>>
>> "LED Support for ISSI IS31FL32XX I2C LED chip family" ?
>
> Yes, I found that awkward as well. HW folks (and the datasheets) seem
> always refer to devices of this type as "LED Driver"s (which can lead
> to some interesting confusions). Taking a cue from the LP5521/23/62
> entries, how about:
>   "LED Support for the ISSI IS31FL32XX I2C LED driver chip family" ?

"LED Support" means "LED class driver". Driver is a software support
for hardware chip. What discrepancy do you see in the description
I proposed?

> Perhaps that's the best of both worlds?
>
>>> +	depends on LEDS_CLASS && I2C && OF
>>> +	help
>>> +	  Say Y here to include support for the ISSI 31FL32XX LED driver family.
>>
>> s/driver/chip/
>>
>>> +	  They are I2C devices with multiple constant-current channels, each
>>> +	  with independent 256-level PWM control. This will only work with
>>> +	  device tree enabled devices.
>>
>> We can skip the last sentence I think.
>
> OK. FYI, I think I got that verbiage from LEDS_SYSCON.

Having "depends on OF" is self-explanatory here.

>>> +
>>>    comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
>>>
>>>    config LEDS_BLINKM
>>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
>>> index 89c9b6f..3fdf313 100644
>>> --- a/drivers/leds/Makefile
>>> +++ b/drivers/leds/Makefile
>>> @@ -67,6 +67,7 @@ obj-$(CONFIG_LEDS_KTD2692)		+= leds-ktd2692.o
>>>    obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
>>>    obj-$(CONFIG_LEDS_SEAD3)		+= leds-sead3.o
>>>    obj-$(CONFIG_LEDS_SN3218)		+= leds-sn3218.o
>>> +obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
>>>
>>>    # LED SPI Drivers
>>>    obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
>>> diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
>>> new file mode 100644
>>> index 0000000..8dea518
>>> --- /dev/null
>>> +++ b/drivers/leds/leds-is31fl32xx.c
>>> @@ -0,0 +1,442 @@
>>> +/*
>>> + * linux/drivers/leds-is31fl32xx.c
>>> + *
>>> + * Driver for ISSI IS31FL32xx family of I2C LED controllers
>>> + *
>>> + * Copyright 2015 Allworx Corp.
>>> + *
>>> + *
>>> + * 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.
>>> + *
>>> + * HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
>>> + */
>>> +
>>> +#include <linux/err.h>
>>> +#include <linux/i2c.h>
>>> +#include <linux/kernel.h>
>>> +#include <linux/leds.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of_platform.h>
>>> +
>>> +#ifdef DEBUG
>>> + #undef dev_dbg
>>> + #define dev_dbg dev_info
>>> +#endif
>>
>> What's the benefit of the above?
>
> It gave me a way to easily see debug output from the driver while it
> was parsing the DT (especially if the driver was built-in). Early on
> there were other things within that #ifdef as well.
> Regardless, passing ddebug_query on the kernel commandline is a more
> appropriate way of accomplishing that; I'll remove for the next version.

Thanks.

>>> +/* Used to indicate a device has no such register */
>>> +#define IS31FL32XX_REG_NONE 0xFF
>>> +
>>> +#define IS31FL3216_CONFIG_REG 0x00
>>> +#define IS31FL3216_LIGHTING_EFFECT_REG 0x03
>>> +#define IS31FL3216_CHANNEL_CONFIG_REG 0x04
>>> +
>>> +struct is31fl32xx_priv;
>>> +struct is31fl32xx_led_data {
>>> +	struct led_classdev cdev;
>>> +	u8 channel; /* 1-based, max priv->cdef->channels */
>>> +	struct is31fl32xx_priv *priv;
>>> +};
>>> +
>>> +struct is31fl32xx_priv {
>>> +	const struct is31fl32xx_chipdef *cdef;
>>> +	struct i2c_client *client;
>>> +	unsigned int num_leds;
>>> +	struct is31fl32xx_led_data leds[0];
>>
>> Is there any specific reason for not having *leds here instead?
>
> I followed a pattern from leds-pwm where it did a single allocation
> for both priv and priv->leds[]. See sizeof_is31fl32xx_priv(), and
> its use, below. I saw the benefit as one fewer small allocation, so
> slightly more kind to the allocator (and devres). If you'd prefer to
> do it as two allocations, I'll make the change.

OK, I had to look at this one more time. I like the idea.

>>> +};
>>> +
>>> +/**
>>> + * struct is31fl32xx_chipdef - chip-specific attributes
>>> + * @channels            : Number of LED channels
>>> + * @shutdown_reg        : address of Shutdown register (optional)
>>> + * @pwm_update_reg      : address of PWM Update register
>>> + * @global_control_reg	: address of Global Control register (optional)
>>> + * @reset_reg           : address of Reset register (optional)
>>> + * @pwm_register_base	: address of first PWM register
>>> + * @pwm_registers_reversed: : true if PWM registers count down instead of up
>>> + * @led_control_register_base : address of first LED control register (optional)
>>> + * @enable_bits_per_led_control_register: number of LEDs enable bits in each
>>> + * @reset_func:         : pointer to reset function
>>> + *
>>> + * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE
>>> + * indicates that this chip has no such register.
>>> + *
>>> + * If non-NULL, @reset_func will be called during probing to set all
>>> + * necessary registers to a known initialization state. This is needed
>>> + * for chips that do not have a @reset_reg.
>>> + *
>>> + * @enable_bits_per_led_control_register must be >=1 if
>>> + * @led_control_register_base != %IS31FL32XX_REG_NONE.
>>> + */
>>> +struct is31fl32xx_chipdef {
>>> +	u8	channels;
>>> +	u8	shutdown_reg;
>>> +	u8	pwm_update_reg;
>>> +	u8	global_control_reg;
>>> +	u8	reset_reg;
>>> +	u8	pwm_register_base;
>>> +	bool	pwm_registers_reversed;
>>> +	u8	led_control_register_base;
>>> +	u8	enable_bits_per_led_control_register;
>>> +	int (*reset_func)(struct is31fl32xx_priv *priv);
>>> +};
>>> +
>>> +static const struct is31fl32xx_chipdef is31fl3236_cdef = {
>>> +	.channels				= 36,
>>> +	.shutdown_reg				= 0x00,
>>> +	.pwm_update_reg				= 0x25,
>>> +	.global_control_reg			= 0x4a,
>>> +	.reset_reg				= 0x4f,
>>> +	.pwm_register_base			= 0x01,
>>> +	.led_control_register_base		= 0x26,
>>> +	.enable_bits_per_led_control_register	= 1,
>>> +};
>>> +
>>> +static const struct is31fl32xx_chipdef is31fl3235_cdef = {
>>> +	.channels				= 28,
>>> +	.shutdown_reg				= 0x00,
>>> +	.pwm_update_reg				= 0x25,
>>> +	.global_control_reg			= 0x4a,
>>> +	.reset_reg				= 0x4f,
>>> +	.pwm_register_base			= 0x05,
>>> +	.led_control_register_base		= 0x2a,
>>> +	.enable_bits_per_led_control_register	= 1,
>>> +};
>>> +
>>> +static const struct is31fl32xx_chipdef is31fl3218_cdef = {
>>> +	.channels				= 18,
>>> +	.shutdown_reg				= 0x00,
>>> +	.pwm_update_reg				= 0x16,
>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
>>> +	.reset_reg				= 0x17,
>>> +	.pwm_register_base			= 0x01,
>>> +	.led_control_register_base		= 0x13,
>>> +	.enable_bits_per_led_control_register	= 6,
>>> +};
>>> +
>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv);
>>> +static const struct is31fl32xx_chipdef is31fl3216_cdef = {
>>> +	.channels				= 16,
>>> +	.shutdown_reg				= IS31FL32XX_REG_NONE,
>>> +	.pwm_update_reg				= 0xB0,
>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
>>> +	.reset_reg				= IS31FL32XX_REG_NONE,
>>> +	.pwm_register_base			= 0x10,
>>> +	.pwm_registers_reversed			= true,
>>> +	.led_control_register_base		= 0x01,
>>> +	.enable_bits_per_led_control_register	= 8,
>>> +	.reset_func				= is31fl3216_reset,
>>> +};
>>> +
>>> +static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)
>>> +{
>>> +	int ret;
>>> +
>>> +	dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
>>> +
>>> +	ret =  i2c_smbus_write_byte_data(priv->client, reg, val);
>>> +	if (ret) {
>>> +		dev_err(&priv->client->dev,
>>> +			"register write to 0x%02X failed (error %d)",
>>> +			reg, ret);
>>> +	}
>>> +	return ret;
>>> +}
>>> +
>>> +/*
>>> + * Custom reset function for IS31FL3216 because it does not have a RESET
>>> + * register the way that the other IS31FL32xx chips do. We don't bother
>>> + * writing the GPIO and animation registers, because the registers we
>>> + * do write ensure those will have no effect.
>>> + */
>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv)
>>> +{
>>> +	unsigned int i;
>>> +	int ret;
>>> +
>>> +	for (i = 0; i < priv->cdef->channels; i++) {
>>> +		ret = is31fl32xx_write(priv, priv->cdef->pwm_register_base+i,
>>> +				       0x00);
>>> +		if (ret)
>>> +			return ret;
>>> +	}
>>> +	ret = is31fl32xx_write(priv, priv->cdef->pwm_update_reg, 0);
>>> +	if (ret)
>>> +		return ret;
>>> +	ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, 0x00);
>>> +	if (ret)
>>> +		return ret;
>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, 0x00);
>>> +	if (ret)
>>> +		return ret;
>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, 0x00);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +
>>> +static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,
>>> +				     enum led_brightness brightness)
>>> +{
>>> +	const struct is31fl32xx_led_data *led_data =
>>> +		container_of(led_cdev, struct is31fl32xx_led_data, cdev);
>>> +	const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef;
>>> +	u8 pwm_register_offset;
>>> +	int ret;
>>> +
>>> +	dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);
>>> +
>>> +	/* NOTE: led_data->channel is 1-based */
>>> +	if (cdef->pwm_registers_reversed)
>>> +		pwm_register_offset = cdef->channels - led_data->channel;
>>> +	else
>>> +		pwm_register_offset = led_data->channel - 1;
>>> +
>>> +	ret = is31fl32xx_write(led_data->priv,
>>> +			       cdef->pwm_register_base + pwm_register_offset,
>>> +			       brightness);
>>> +	if (ret)
>>> +		return ret;
>>
>> I infer that nothing wrong happens in case current process is preempted
>> here by the call originating from the other sub-LED?
>
> I do not believe so. All the driver-specific data used here is read-only
> after probing. chipdefs are entirely const, and the only thing in priv
> that's referenced is the chipdef pointer which logically could not change
> post-probe. Actually nothing else in priv is modified post-probe either.
>
> The I2C core code has a mutex on the bus, so two writes cannot happen at
> once.
>
> In all these devices there is a unique PWM duty-cycle register for each
> LED channel (which is what is being written here), so no register writes
> for one LED channel effect any others.
>
> I believe the worst that could happen is that the device would see:
> 	PWM_REG_A write X
> 	PWM_REG_B write Y
> 	UPDATE_REG write 0
> 	UPDATE_REG write 0
> instead of
> 	PWM_REG_A write X
> 	UPDATE_REG write 0
> 	PWM_REG_B write Y
> 	UPDATE_REG write 0
> but that makes no difference to the functionality. Poking the update
> register merely applies all PWM register writes up to that point (I'm
> assuming to allow atomically changing the state of multiple LEDs at
> once).

Thanks for this comprehensive explanation.

> I should note here (as mentioned in cover letter), I made a choice to
> always leave the per-LED "enable" bits on, and let the PWM just get set
> to 0 naturally to turn an LED off. This differs from the existing SN3218
> driver, which used regmap_update_bits, and is then protected by a per-
> regmap mutex.

ack.

>>> +	return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);
>>> +
>>> +}
>>> +
>>> +static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
>>> +{
>>> +	const struct is31fl32xx_chipdef *cdef = priv->cdef;
>>> +	int ret;
>>> +
>>> +	if (cdef->reset_reg != IS31FL32XX_REG_NONE) {
>>> +		ret = is31fl32xx_write(priv, cdef->reset_reg, 0);
>>> +		if (ret)
>>> +			return ret;
>>> +	}
>>> +	if (cdef->reset_func) {
>>> +		ret = cdef->reset_func(priv);
>>> +		if (ret)
>>> +			return ret;
>>> +	}
>>> +	if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) {
>>> +		u8 value =
>>> +		    GENMASK(cdef->enable_bits_per_led_control_register-1, 0);
>>> +		u8 num_regs = cdef->channels /
>>> +				cdef->enable_bits_per_led_control_register;
>>> +		int i;
>>> +
>>> +		for (i = 0; i < num_regs; i++) {
>>> +			ret = is31fl32xx_write(priv,
>>> +					       cdef->led_control_register_base+i,
>>> +					       value);
>>> +			if (ret)
>>> +				return ret;
>>> +		}
>>> +	}
>>> +	if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) {
>>> +		ret = is31fl32xx_write(priv, cdef->shutdown_reg, BIT(0));
>>> +		if (ret)
>>> +			return ret;
>>> +	}
>>> +	if (cdef->global_control_reg != IS31FL32XX_REG_NONE) {
>>> +		ret = is31fl32xx_write(priv, cdef->global_control_reg, 0x00);
>>> +		if (ret)
>>> +			return ret;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static inline size_t sizeof_is31fl32xx_priv(int num_leds)
>>> +{
>>> +	return sizeof(struct is31fl32xx_priv) +
>>> +		      (sizeof(struct is31fl32xx_led_data) * num_leds);
>>> +}
>>> +
>>> +static int is31fl32xx_parse_child_dt(const struct device *dev,
>>> +				     const struct device_node *child,
>>> +				     struct is31fl32xx_led_data *led_data)
>>> +{
>>> +	struct led_classdev *cdev = &led_data->cdev;
>>> +	int ret = 0;
>>> +	u32 reg;
>>> +
>>> +	cdev->name = of_get_property(child, "label", NULL) ? : child->name;
>>> +
>>> +	ret = of_property_read_u32(child, "reg", &reg);
>>> +	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
>>> +		dev_err(dev,
>>> +			"Child node %s does not have a valid reg property\n",
>>> +			child->name);
>>> +		return -EINVAL;
>>> +	}
>>> +	led_data->channel = reg;
>>> +
>>> +	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
>>> +						NULL);
>>> +	cdev->brightness = LED_OFF;
>>
>> devm_kzalloc secures that.
>
> OK, I will remove.
>
>>> +	ret = of_property_read_u32(child, "max-brightness",
>>> +				   &cdev->max_brightness);
>>> +	if (ret == -EINVAL) {
>>> +		cdev->max_brightness = 255;
>>
>> s/255/LED_FULL/
>
> Noted, although (from the patch 2 discussion) max-brightness property is
> removed/replaced, this would go away anyways.
>
>>> +	} else if (ret) {
>>> +		dev_dbg(dev,
>>> +			"Child node %s has an invalid max-brightness property\n",
>>> +			child->name);
>>> +		return -EINVAL;
>>> +	}
>>> +
>>> +	cdev->brightness_set_blocking = is31fl32xx_brightness_set;
>>
>> Please add empty line here.
>
> Done.
>
>>> +	return 0;
>>> +}
>>> +
>>> +static struct is31fl32xx_led_data *is31fl32xx_find_led_data(
>>> +					struct is31fl32xx_priv *priv,
>>> +					u8 channel)
>>> +{
>>> +	size_t i;
>>> +
>>> +	for (i = 0; i < priv->num_leds; i++) {
>>> +		if (priv->leds[i].channel == channel)
>>> +			return &priv->leds[i];
>>> +	}
>>
>> Ditto.
>
> Done.
>
>>> +	return NULL;
>>> +}
>>> +
>>> +static int is31fl32xx_parse_dt(struct device *dev,
>>> +			       struct is31fl32xx_priv *priv)
>>> +{
>>> +	struct device_node *child;
>>> +
>>> +	for_each_child_of_node(dev->of_node, child) {
>>> +		struct is31fl32xx_led_data *led_data =
>>> +			&priv->leds[priv->num_leds];
>>> +		int ret = 0;
>>> +		const struct is31fl32xx_led_data *other_led_data;
>>> +
>>> +		led_data->priv = priv;
>>> +
>>> +		ret = is31fl32xx_parse_child_dt(dev, child, led_data);
>>> +		if (ret)
>>> +			continue;
>>
>> I prefer failing in such cases,
>
> OK, I will change to an 'goto err' which will have an 'of_node_put()'
> and 'return ret'.
>
> I will say, however, that while testing the error-detection in the
> parsing logic, it was very convenient to construct a single devicetree
> with a variety of errors. Then a single boot would test multiple
> cases at once.

Good idea for testing, but in case some failure occurs during DT child
node parsing in the release environment you're left with unused
allocated memory.

>>> +
>>> +		/* Detect if channel is already in use by another child */
>>> +		other_led_data = is31fl32xx_find_led_data(priv,
>>> +							  led_data->channel);
>>> +		if (other_led_data) {
>>> +			dev_err(dev,
>>> +				"%s ignored: channel %d already used by %s",
>>> +				led_data->cdev.name,
>>> +				led_data->channel,
>>> +				other_led_data->cdev.name);
>>> +			continue;
>>
>> Ditto.
>
> OK.
>
>>> +		}
>>> +
>>> +		ret = devm_led_classdev_register(dev, &led_data->cdev);
>>> +		if (ret == 0) {
>>> +			priv->num_leds++;
>>> +		} else {
>>> +			dev_err(dev, "failed to register PWM led for %s: %d\n",
>>> +				led_data->cdev.name, ret);
>
> Should I also fail here, then? Right now it will continue trying to
> register future LED devices if a classdev_register fails for some
> reason, and will successfully load even if all of them fail.

Please fail here too. If we can't setup the sub-LED that is advertised
in a DT child node, then it means that something went wrong.
This is clear error case.

>>> +		}
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct of_device_id of_is31fl31xx_match[] = {
>>> +	{ .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },
>>> +	{ .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },
>>> +	{ .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, },
>>> +	{ .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, },
>>> +	{},
>>> +};
>>> +
>>> +MODULE_DEVICE_TABLE(of, of_is31fl31xx_match);
>>> +
>>> +static int is31fl32xx_probe(struct i2c_client *client,
>>> +				  const struct i2c_device_id *id)
>>> +{
>>> +	const struct is31fl32xx_chipdef *cdef;
>>> +	const struct of_device_id *of_dev_id;
>>> +	struct device *dev = &client->dev;
>>> +	struct is31fl32xx_priv *priv;
>>> +	int count;
>>> +	int ret = 0;
>>> +
>>> +	of_dev_id = of_match_device(of_is31fl31xx_match, dev);
>>> +	if (!of_dev_id)
>>> +		return -EINVAL;
>>> +
>>> +	cdef = of_dev_id->data;
>>> +
>>> +	count = of_get_child_count(dev->of_node);
>>> +	if (!count)
>>> +		return -EINVAL;
>>> +
>>> +	priv = devm_kzalloc(dev, sizeof_is31fl32xx_priv(count),
>>> +			    GFP_KERNEL);
>>> +	if (!priv)
>>> +		return -ENOMEM;
>>> +
>>> +	priv->client = client;
>>> +	priv->cdef = cdef;
>>> +	i2c_set_clientdata(client, priv);
>>> +
>>> +	ret = is31fl32xx_init_regs(priv);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	ret = is31fl32xx_parse_dt(dev, priv);
>>> +	if (ret)
>>> +		return ret;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int is31fl32xx_remove(struct i2c_client *client)
>>> +{
>>> +	struct is31fl32xx_priv *priv = i2c_get_clientdata(client);
>>> +
>>> +	/* If there is a reset reg, then it does everything we need */
>>> +	if (priv->cdef->reset_reg != IS31FL32XX_REG_NONE)
>>> +		return is31fl32xx_write(priv, priv->cdef->reset_reg, 0);
>>> +
>>> +	/* If there is a reset func, then it does everything we need */
>>> +	if (priv->cdef->reset_func)
>>> +		return priv->cdef->reset_func(priv);
>>> +
>>> +	/* If we can't reset, then try just using software-shutdown mode */
>>> +	if (priv->cdef->shutdown_reg != IS31FL32XX_REG_NONE)
>>> +		return is31fl32xx_write(priv, priv->cdef->shutdown_reg, 0x00);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +/*
>>> + * i2c-core requires that id_table be non-NULL, even though
>>> + * it is not used for DeviceTree based instantiation.
>>> + */
>>> +static const struct i2c_device_id is31fl31xx_id[] = {
>>> +	{},
>>> +};
>>> +
>>> +MODULE_DEVICE_TABLE(i2c, is31fl31xx_id);
>>> +
>>> +static struct i2c_driver is31fl32xx_driver = {
>>> +	.driver = {
>>> +		.name	= "is31fl32xx",
>>> +		.of_match_table = of_is31fl31xx_match,
>>> +	},
>>> +	.probe		= is31fl32xx_probe,
>>> +	.remove		= is31fl32xx_remove,
>>> +	.id_table	= is31fl31xx_id,
>>> +};
>>> +
>>> +module_i2c_driver(is31fl32xx_driver);
>>> +
>>> +MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>");
>>> +MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver");
>>> +MODULE_LICENSE("GPL v2");
>>>
>
>
>


-- 
Best regards,
Jacek Anaszewski

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-25 10:55       ` Jacek Anaszewski
@ 2016-02-25 19:12         ` David Rivshin (Allworx)
  2016-02-26  9:47           ` Jacek Anaszewski
  0 siblings, 1 reply; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-02-25 19:12 UTC (permalink / raw)
  To: Jacek Anaszewski, Stefan Wahren
  Cc: linux-leds, devicetree, Richard Purdie, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala

On Thu, 25 Feb 2016 11:55:58 +0100
Jacek Anaszewski <j.anaszewski@samsung.com> wrote:

> On 02/25/2016 03:24 AM, David Rivshin (Allworx) wrote:
> > On Wed, 24 Feb 2016 17:04:58 +0100
> > Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> >  
> >> Hi David,
> >>
> >> Thanks for the patch. Very nice driver. I have few comments
> >> below.  
> >
> > Thanks Jacek, I have responded the comments inline. I also wanted to
> > double check whether you noticed some questions I had in the cover
> > letter [1]. As I mentioned in another email to Rob, in hindsight I'm
> > guessing I should have included them in the patch comments as well (or
> > instead of).  
> 
> I saw them. I assumed that the review itself will address those
> questions.

Fair enough, thanks for the confirmation.

> > Your review comments here effectively answered some of the questions, but
> > the big one I'm still unsure of is whether it actually makes sense to
> > have all 4 of these devices supported by a single driver.  
> 
> It's perfectly fine. Many drivers implement this pattern.

OK, then I'll assume you think this driver is not yet too complicated
for it's own good. Out of curiosity, might that view change if the
3216 specific features were ever implemented, especially GPIO and HW 
animation support? Gut feel is that would make 3216 specific code
bigger than the rest of the code combined.

Bigger question is what should be done in terms of the overlap in device 
support between this driver and leds-sn3218? If you think I should leave 
the *3218 support in this driver, then I would propose:
  - remove leds-sn3218 and its separate binding doc
  - add the "si-en,sn3218" compatible string to this driver and binding doc
Note that while I expect this driver to work with the 3218 chips, I do
not have one to test against. If we go down this route I would definitely
want Stefan to test so that I don't accidentally break him.

Also I feel I should point out some differences between the 3218 support 
in this driver versus the leds-sn3218 driver, in case they have any 
impact:
  - (as previously mentioned) leds-sn3218 turns off an LEDs enable
    bit if the brightness is set to 0. This driver just sets the PWM
    to 0 and leaves the enable bits always on.
  - leds-sn3218 uses a regmap, I think mostly to deal with the enable
    bits, but it also has the benefit of showing up in debugfs. This
    could be seen as useful in and of itself by some users. On the other 
    hand regmap introduces another mutex on every write.
  - leds-sn3218 implements the shutdown callback. Actually, I think I 
    should add that to this driver in any event.
  - leds-sn3218 just puts the chip in software-shutdown mode on remove/
    shutdown. This driver uses the reset register to put the device in 
    poweron state, and software-shutdown is part of the poweron state. 
    Only difference would be if the next code to use the device does 
    not do it's own full initialization (which seems unlikely, or at 
    least unwise), but instead just clears software-shutdown.

 
> > I won't
> > clutter this email with a duplicate of the details (it's somewhat long),
> > but if you could check the cover letter and give some guidance, I would
> > appreciate it.
> >
> > [1] http://www.spinics.net/lists/linux-leds/msg05564.html
> >      http://thread.gmane.org/gmane.linux.leds/4530
> >  
> >>
> >> On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:  
> >>> From: David Rivshin <drivshin@allworx.com>
> >>>
> >>> The IS31FL32xx family of LED drivers are I2C devices with multiple
> >>> constant-current channels, each with independent 256-level PWM control.
> >>>
> >>> HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
> >>>
> >>> This has been tested on the IS31FL3236 and IS31FL3216 on an ARM
> >>> (TI am335x) platform.
> >>>
> >>> The programming paradigm of these devices is similar in the following
> >>> ways:
> >>>    - All registers are 8 bit
> >>>    - All LED control registers are write-only
> >>>    - Each LED channel has a PWM register (0-255)
> >>>    - PWM register writes are shadowed until an Update register is poked
> >>>    - All have a concept of Software Shutdown, which disables output
> >>>
> >>> However, there are some differences in devices:
> >>>    - 3236/3235 have a separate Control register for each LED,
> >>>      (3218/3216 pack the enable bits into fewer registers)
> >>>    - 3236/3235 have a per-channel current divisor setting
> >>>    - 3236/3235 have a Global Control register that can turn off all LEDs
> >>>    - 3216 is unique in a number of ways
> >>>       - OUT9-OUT16 can be configured as GPIOs instead of LED controls
> >>>       - LEDs can be programmed with an 8-frame animation, with
> >>>         programmable delay between frames
> >>>       - LEDs can be modulated by an input audio signal
> >>>       - Max output current can be adjusted from 1/4 to 2x globally
> >>>       - Has a Configuration register instead of a Shutdown register
> >>>
> >>> This driver currently only supports the base PWM control function
> >>> of these devices. The following features of these devices are not
> >>> implemented, although it should be possible to add them in the future:
> >>>    - All devices are capable of going into a lower-power "software
> >>>      shutdown" mode.
> >>>    - The is31fl3236 and is31fl3235 can reduce the max output current
> >>>      per-channel with a divisor of 1, 2, 3, or 4.
> >>>    - The is31fl3216 can use some LED channels as GPIOs instead.
> >>>    - The is31fl3216 can animate LEDs in hardware.
> >>>    - The is31fl3216 can modulate LEDs according to an audio input.
> >>>    - The is31fl3216 can reduce/increase max output current globally.
> >>>
> >>> Signed-off-by: David Rivshin <drivshin@allworx.com>
> >>> ---
> >>>    drivers/leds/Kconfig           |   9 +
> >>>    drivers/leds/Makefile          |   1 +
> >>>    drivers/leds/leds-is31fl32xx.c | 442 +++++++++++++++++++++++++++++++++++++++++
> >>>    3 files changed, 452 insertions(+)
> >>>    create mode 100644 drivers/leds/leds-is31fl32xx.c
> >>>
> >>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> >>> index 1034696..8f6c46f 100644
> >>> --- a/drivers/leds/Kconfig
> >>> +++ b/drivers/leds/Kconfig
> >>> @@ -580,6 +580,15 @@ config LEDS_SN3218
> >>>    	  This driver can also be built as a module. If so the module
> >>>    	  will be called leds-sn3218.
> >>>
> >>> +config LEDS_IS31FL32XX
> >>> +	tristate "Driver for ISSI IS31FL32XX I2C LED driver chip family"  
> >>
> >> 2 x "[Dd]river".
> >>
> >> How about:
> >>
> >> "LED Support for ISSI IS31FL32XX I2C LED chip family" ?  
> >
> > Yes, I found that awkward as well. HW folks (and the datasheets) seem
> > always refer to devices of this type as "LED Driver"s (which can lead
> > to some interesting confusions). Taking a cue from the LP5521/23/62
> > entries, how about:
> >   "LED Support for the ISSI IS31FL32XX I2C LED driver chip family" ?  
> 
> "LED Support" means "LED class driver". Driver is a software support
> for hardware chip. What discrepancy do you see in the description
> I proposed?

I think in this case "driver" also means "hardware device which drives
a physical LED". It seems that "LED driver" is the term universally used 
to describe this type of HW device in datasheets. So it seemed useful to 
use exactly that phrase in the description of what hardware this software 
supports. I could see someone interpreting the phrase "LED chip" as
referring to an actual LED device. 
I don't feel very strongly on this topic, but for the sake of discussion,
maybe "LED controller" would avoid any possible confusion in both 
directions?

> > Perhaps that's the best of both worlds?
> >  
> >>> +	depends on LEDS_CLASS && I2C && OF
> >>> +	help
> >>> +	  Say Y here to include support for the ISSI 31FL32XX LED driver family.  
> >>
> >> s/driver/chip/
> >>  
> >>> +	  They are I2C devices with multiple constant-current channels, each
> >>> +	  with independent 256-level PWM control. This will only work with
> >>> +	  device tree enabled devices.  
> >>
> >> We can skip the last sentence I think.  
> >
> > OK. FYI, I think I got that verbiage from LEDS_SYSCON.  
> 
> Having "depends on OF" is self-explanatory here.

Noted.

> >>> +
> >>>    comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
> >>>
> >>>    config LEDS_BLINKM
> >>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> >>> index 89c9b6f..3fdf313 100644
> >>> --- a/drivers/leds/Makefile
> >>> +++ b/drivers/leds/Makefile
> >>> @@ -67,6 +67,7 @@ obj-$(CONFIG_LEDS_KTD2692)		+= leds-ktd2692.o
> >>>    obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
> >>>    obj-$(CONFIG_LEDS_SEAD3)		+= leds-sead3.o
> >>>    obj-$(CONFIG_LEDS_SN3218)		+= leds-sn3218.o
> >>> +obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
> >>>
> >>>    # LED SPI Drivers
> >>>    obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
> >>> diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
> >>> new file mode 100644
> >>> index 0000000..8dea518
> >>> --- /dev/null
> >>> +++ b/drivers/leds/leds-is31fl32xx.c
> >>> @@ -0,0 +1,442 @@
> >>> +/*
> >>> + * linux/drivers/leds-is31fl32xx.c
> >>> + *
> >>> + * Driver for ISSI IS31FL32xx family of I2C LED controllers
> >>> + *
> >>> + * Copyright 2015 Allworx Corp.
> >>> + *
> >>> + *
> >>> + * 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.
> >>> + *
> >>> + * HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
> >>> + */
> >>> +
> >>> +#include <linux/err.h>
> >>> +#include <linux/i2c.h>
> >>> +#include <linux/kernel.h>
> >>> +#include <linux/leds.h>
> >>> +#include <linux/module.h>
> >>> +#include <linux/of_platform.h>
> >>> +
> >>> +#ifdef DEBUG
> >>> + #undef dev_dbg
> >>> + #define dev_dbg dev_info
> >>> +#endif  
> >>
> >> What's the benefit of the above?  
> >
> > It gave me a way to easily see debug output from the driver while it
> > was parsing the DT (especially if the driver was built-in). Early on
> > there were other things within that #ifdef as well.
> > Regardless, passing ddebug_query on the kernel commandline is a more
> > appropriate way of accomplishing that; I'll remove for the next version.  
> 
> Thanks.
> 
> >>> +/* Used to indicate a device has no such register */
> >>> +#define IS31FL32XX_REG_NONE 0xFF
> >>> +
> >>> +#define IS31FL3216_CONFIG_REG 0x00
> >>> +#define IS31FL3216_LIGHTING_EFFECT_REG 0x03
> >>> +#define IS31FL3216_CHANNEL_CONFIG_REG 0x04
> >>> +
> >>> +struct is31fl32xx_priv;
> >>> +struct is31fl32xx_led_data {
> >>> +	struct led_classdev cdev;
> >>> +	u8 channel; /* 1-based, max priv->cdef->channels */
> >>> +	struct is31fl32xx_priv *priv;
> >>> +};
> >>> +
> >>> +struct is31fl32xx_priv {
> >>> +	const struct is31fl32xx_chipdef *cdef;
> >>> +	struct i2c_client *client;
> >>> +	unsigned int num_leds;
> >>> +	struct is31fl32xx_led_data leds[0];  
> >>
> >> Is there any specific reason for not having *leds here instead?  
> >
> > I followed a pattern from leds-pwm where it did a single allocation
> > for both priv and priv->leds[]. See sizeof_is31fl32xx_priv(), and
> > its use, below. I saw the benefit as one fewer small allocation, so
> > slightly more kind to the allocator (and devres). If you'd prefer to
> > do it as two allocations, I'll make the change.  
> 
> OK, I had to look at this one more time. I like the idea.

OK, I'll keep it as-is.
 
> >>> +};
> >>> +
> >>> +/**
> >>> + * struct is31fl32xx_chipdef - chip-specific attributes
> >>> + * @channels            : Number of LED channels
> >>> + * @shutdown_reg        : address of Shutdown register (optional)
> >>> + * @pwm_update_reg      : address of PWM Update register
> >>> + * @global_control_reg	: address of Global Control register (optional)
> >>> + * @reset_reg           : address of Reset register (optional)
> >>> + * @pwm_register_base	: address of first PWM register
> >>> + * @pwm_registers_reversed: : true if PWM registers count down instead of up
> >>> + * @led_control_register_base : address of first LED control register (optional)
> >>> + * @enable_bits_per_led_control_register: number of LEDs enable bits in each
> >>> + * @reset_func:         : pointer to reset function
> >>> + *
> >>> + * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE
> >>> + * indicates that this chip has no such register.
> >>> + *
> >>> + * If non-NULL, @reset_func will be called during probing to set all
> >>> + * necessary registers to a known initialization state. This is needed
> >>> + * for chips that do not have a @reset_reg.
> >>> + *
> >>> + * @enable_bits_per_led_control_register must be >=1 if
> >>> + * @led_control_register_base != %IS31FL32XX_REG_NONE.
> >>> + */
> >>> +struct is31fl32xx_chipdef {
> >>> +	u8	channels;
> >>> +	u8	shutdown_reg;
> >>> +	u8	pwm_update_reg;
> >>> +	u8	global_control_reg;
> >>> +	u8	reset_reg;
> >>> +	u8	pwm_register_base;
> >>> +	bool	pwm_registers_reversed;
> >>> +	u8	led_control_register_base;
> >>> +	u8	enable_bits_per_led_control_register;
> >>> +	int (*reset_func)(struct is31fl32xx_priv *priv);
> >>> +};
> >>> +
> >>> +static const struct is31fl32xx_chipdef is31fl3236_cdef = {
> >>> +	.channels				= 36,
> >>> +	.shutdown_reg				= 0x00,
> >>> +	.pwm_update_reg				= 0x25,
> >>> +	.global_control_reg			= 0x4a,
> >>> +	.reset_reg				= 0x4f,
> >>> +	.pwm_register_base			= 0x01,
> >>> +	.led_control_register_base		= 0x26,
> >>> +	.enable_bits_per_led_control_register	= 1,
> >>> +};
> >>> +
> >>> +static const struct is31fl32xx_chipdef is31fl3235_cdef = {
> >>> +	.channels				= 28,
> >>> +	.shutdown_reg				= 0x00,
> >>> +	.pwm_update_reg				= 0x25,
> >>> +	.global_control_reg			= 0x4a,
> >>> +	.reset_reg				= 0x4f,
> >>> +	.pwm_register_base			= 0x05,
> >>> +	.led_control_register_base		= 0x2a,
> >>> +	.enable_bits_per_led_control_register	= 1,
> >>> +};
> >>> +
> >>> +static const struct is31fl32xx_chipdef is31fl3218_cdef = {
> >>> +	.channels				= 18,
> >>> +	.shutdown_reg				= 0x00,
> >>> +	.pwm_update_reg				= 0x16,
> >>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
> >>> +	.reset_reg				= 0x17,
> >>> +	.pwm_register_base			= 0x01,
> >>> +	.led_control_register_base		= 0x13,
> >>> +	.enable_bits_per_led_control_register	= 6,
> >>> +};
> >>> +
> >>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv);
> >>> +static const struct is31fl32xx_chipdef is31fl3216_cdef = {
> >>> +	.channels				= 16,
> >>> +	.shutdown_reg				= IS31FL32XX_REG_NONE,
> >>> +	.pwm_update_reg				= 0xB0,
> >>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
> >>> +	.reset_reg				= IS31FL32XX_REG_NONE,
> >>> +	.pwm_register_base			= 0x10,
> >>> +	.pwm_registers_reversed			= true,
> >>> +	.led_control_register_base		= 0x01,
> >>> +	.enable_bits_per_led_control_register	= 8,
> >>> +	.reset_func				= is31fl3216_reset,
> >>> +};
> >>> +
> >>> +static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)
> >>> +{
> >>> +	int ret;
> >>> +
> >>> +	dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
> >>> +
> >>> +	ret =  i2c_smbus_write_byte_data(priv->client, reg, val);
> >>> +	if (ret) {
> >>> +		dev_err(&priv->client->dev,
> >>> +			"register write to 0x%02X failed (error %d)",
> >>> +			reg, ret);
> >>> +	}
> >>> +	return ret;
> >>> +}
> >>> +
> >>> +/*
> >>> + * Custom reset function for IS31FL3216 because it does not have a RESET
> >>> + * register the way that the other IS31FL32xx chips do. We don't bother
> >>> + * writing the GPIO and animation registers, because the registers we
> >>> + * do write ensure those will have no effect.
> >>> + */
> >>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv)
> >>> +{
> >>> +	unsigned int i;
> >>> +	int ret;
> >>> +
> >>> +	for (i = 0; i < priv->cdef->channels; i++) {
> >>> +		ret = is31fl32xx_write(priv, priv->cdef->pwm_register_base+i,
> >>> +				       0x00);
> >>> +		if (ret)
> >>> +			return ret;
> >>> +	}
> >>> +	ret = is31fl32xx_write(priv, priv->cdef->pwm_update_reg, 0);
> >>> +	if (ret)
> >>> +		return ret;
> >>> +	ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, 0x00);
> >>> +	if (ret)
> >>> +		return ret;
> >>> +	ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, 0x00);
> >>> +	if (ret)
> >>> +		return ret;
> >>> +	ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, 0x00);
> >>> +	if (ret)
> >>> +		return ret;
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +
> >>> +static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,
> >>> +				     enum led_brightness brightness)
> >>> +{
> >>> +	const struct is31fl32xx_led_data *led_data =
> >>> +		container_of(led_cdev, struct is31fl32xx_led_data, cdev);
> >>> +	const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef;
> >>> +	u8 pwm_register_offset;
> >>> +	int ret;
> >>> +
> >>> +	dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);
> >>> +
> >>> +	/* NOTE: led_data->channel is 1-based */
> >>> +	if (cdef->pwm_registers_reversed)
> >>> +		pwm_register_offset = cdef->channels - led_data->channel;
> >>> +	else
> >>> +		pwm_register_offset = led_data->channel - 1;
> >>> +
> >>> +	ret = is31fl32xx_write(led_data->priv,
> >>> +			       cdef->pwm_register_base + pwm_register_offset,
> >>> +			       brightness);
> >>> +	if (ret)
> >>> +		return ret;  
> >>
> >> I infer that nothing wrong happens in case current process is preempted
> >> here by the call originating from the other sub-LED?  
> >
> > I do not believe so. All the driver-specific data used here is read-only
> > after probing. chipdefs are entirely const, and the only thing in priv
> > that's referenced is the chipdef pointer which logically could not change
> > post-probe. Actually nothing else in priv is modified post-probe either.
> >
> > The I2C core code has a mutex on the bus, so two writes cannot happen at
> > once.
> >
> > In all these devices there is a unique PWM duty-cycle register for each
> > LED channel (which is what is being written here), so no register writes
> > for one LED channel effect any others.
> >
> > I believe the worst that could happen is that the device would see:
> > 	PWM_REG_A write X
> > 	PWM_REG_B write Y
> > 	UPDATE_REG write 0
> > 	UPDATE_REG write 0
> > instead of
> > 	PWM_REG_A write X
> > 	UPDATE_REG write 0
> > 	PWM_REG_B write Y
> > 	UPDATE_REG write 0
> > but that makes no difference to the functionality. Poking the update
> > register merely applies all PWM register writes up to that point (I'm
> > assuming to allow atomically changing the state of multiple LEDs at
> > once).  
> 
> Thanks for this comprehensive explanation.

Should I put some part of this explanation in a comment somewhere? Seems
like the kind of thing someone else might wonder about in the future also.

> > I should note here (as mentioned in cover letter), I made a choice to
> > always leave the per-LED "enable" bits on, and let the PWM just get set
> > to 0 naturally to turn an LED off. This differs from the existing SN3218
> > driver, which used regmap_update_bits, and is then protected by a per-
> > regmap mutex.  
> 
> ack.
> 
> >>> +	return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);
> >>> +
> >>> +}
> >>> +
> >>> +static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
> >>> +{
> >>> +	const struct is31fl32xx_chipdef *cdef = priv->cdef;
> >>> +	int ret;
> >>> +
> >>> +	if (cdef->reset_reg != IS31FL32XX_REG_NONE) {
> >>> +		ret = is31fl32xx_write(priv, cdef->reset_reg, 0);
> >>> +		if (ret)
> >>> +			return ret;
> >>> +	}
> >>> +	if (cdef->reset_func) {
> >>> +		ret = cdef->reset_func(priv);
> >>> +		if (ret)
> >>> +			return ret;
> >>> +	}
> >>> +	if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) {
> >>> +		u8 value =
> >>> +		    GENMASK(cdef->enable_bits_per_led_control_register-1, 0);
> >>> +		u8 num_regs = cdef->channels /
> >>> +				cdef->enable_bits_per_led_control_register;
> >>> +		int i;
> >>> +
> >>> +		for (i = 0; i < num_regs; i++) {
> >>> +			ret = is31fl32xx_write(priv,
> >>> +					       cdef->led_control_register_base+i,
> >>> +					       value);
> >>> +			if (ret)
> >>> +				return ret;
> >>> +		}
> >>> +	}
> >>> +	if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) {
> >>> +		ret = is31fl32xx_write(priv, cdef->shutdown_reg, BIT(0));
> >>> +		if (ret)
> >>> +			return ret;
> >>> +	}
> >>> +	if (cdef->global_control_reg != IS31FL32XX_REG_NONE) {
> >>> +		ret = is31fl32xx_write(priv, cdef->global_control_reg, 0x00);
> >>> +		if (ret)
> >>> +			return ret;
> >>> +	}
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static inline size_t sizeof_is31fl32xx_priv(int num_leds)
> >>> +{
> >>> +	return sizeof(struct is31fl32xx_priv) +
> >>> +		      (sizeof(struct is31fl32xx_led_data) * num_leds);
> >>> +}
> >>> +
> >>> +static int is31fl32xx_parse_child_dt(const struct device *dev,
> >>> +				     const struct device_node *child,
> >>> +				     struct is31fl32xx_led_data *led_data)
> >>> +{
> >>> +	struct led_classdev *cdev = &led_data->cdev;
> >>> +	int ret = 0;
> >>> +	u32 reg;
> >>> +
> >>> +	cdev->name = of_get_property(child, "label", NULL) ? : child->name;
> >>> +
> >>> +	ret = of_property_read_u32(child, "reg", &reg);
> >>> +	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
> >>> +		dev_err(dev,
> >>> +			"Child node %s does not have a valid reg property\n",
> >>> +			child->name);
> >>> +		return -EINVAL;
> >>> +	}
> >>> +	led_data->channel = reg;
> >>> +
> >>> +	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
> >>> +						NULL);
> >>> +	cdev->brightness = LED_OFF;  
> >>
> >> devm_kzalloc secures that.  
> >
> > OK, I will remove.
> >  
> >>> +	ret = of_property_read_u32(child, "max-brightness",
> >>> +				   &cdev->max_brightness);
> >>> +	if (ret == -EINVAL) {
> >>> +		cdev->max_brightness = 255;  
> >>
> >> s/255/LED_FULL/  
> >
> > Noted, although (from the patch 2 discussion) max-brightness property is
> > removed/replaced, this would go away anyways.
> >  
> >>> +	} else if (ret) {
> >>> +		dev_dbg(dev,
> >>> +			"Child node %s has an invalid max-brightness property\n",
> >>> +			child->name);
> >>> +		return -EINVAL;
> >>> +	}
> >>> +
> >>> +	cdev->brightness_set_blocking = is31fl32xx_brightness_set;  
> >>
> >> Please add empty line here.  
> >
> > Done.
> >  
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static struct is31fl32xx_led_data *is31fl32xx_find_led_data(
> >>> +					struct is31fl32xx_priv *priv,
> >>> +					u8 channel)
> >>> +{
> >>> +	size_t i;
> >>> +
> >>> +	for (i = 0; i < priv->num_leds; i++) {
> >>> +		if (priv->leds[i].channel == channel)
> >>> +			return &priv->leds[i];
> >>> +	}  
> >>
> >> Ditto.  
> >
> > Done.
> >  
> >>> +	return NULL;
> >>> +}
> >>> +
> >>> +static int is31fl32xx_parse_dt(struct device *dev,
> >>> +			       struct is31fl32xx_priv *priv)
> >>> +{
> >>> +	struct device_node *child;
> >>> +
> >>> +	for_each_child_of_node(dev->of_node, child) {
> >>> +		struct is31fl32xx_led_data *led_data =
> >>> +			&priv->leds[priv->num_leds];
> >>> +		int ret = 0;
> >>> +		const struct is31fl32xx_led_data *other_led_data;
> >>> +
> >>> +		led_data->priv = priv;
> >>> +
> >>> +		ret = is31fl32xx_parse_child_dt(dev, child, led_data);
> >>> +		if (ret)
> >>> +			continue;  
> >>
> >> I prefer failing in such cases,  
> >
> > OK, I will change to an 'goto err' which will have an 'of_node_put()'
> > and 'return ret'.
> >
> > I will say, however, that while testing the error-detection in the
> > parsing logic, it was very convenient to construct a single devicetree
> > with a variety of errors. Then a single boot would test multiple
> > cases at once.  
> 
> Good idea for testing, but in case some failure occurs during DT child
> node parsing in the release environment you're left with unused
> allocated memory.

Agreed. BTW, I assume from this that it's common to say "if there's 
anything wrong in one part of your DT, there is no guarantee as to what 
parts will actually be used"? I say this because what we're saying is that 
if one LED node on this device is faulty, that all of them are ignored. 
Analogy might be to a whole I2C bus being ignored because one of the 
devices on it failed to probe. To the devicetree it's still a parent/child 
bus/address relationship, even though the driver implementation is 
very different.

> >>> +
> >>> +		/* Detect if channel is already in use by another child */
> >>> +		other_led_data = is31fl32xx_find_led_data(priv,
> >>> +							  led_data->channel);
> >>> +		if (other_led_data) {
> >>> +			dev_err(dev,
> >>> +				"%s ignored: channel %d already used by %s",
> >>> +				led_data->cdev.name,
> >>> +				led_data->channel,
> >>> +				other_led_data->cdev.name);
> >>> +			continue;  
> >>
> >> Ditto.  
> >
> > OK.
> >  
> >>> +		}
> >>> +
> >>> +		ret = devm_led_classdev_register(dev, &led_data->cdev);
> >>> +		if (ret == 0) {
> >>> +			priv->num_leds++;
> >>> +		} else {
> >>> +			dev_err(dev, "failed to register PWM led for %s: %d\n",
> >>> +				led_data->cdev.name, ret);  
> >
> > Should I also fail here, then? Right now it will continue trying to
> > register future LED devices if a classdev_register fails for some
> > reason, and will successfully load even if all of them fail.  
> 
> Please fail here too. If we can't setup the sub-LED that is advertised
> in a DT child node, then it means that something went wrong.
> This is clear error case.

Done.

> >>> +		}
> >>> +	}
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static const struct of_device_id of_is31fl31xx_match[] = {
> >>> +	{ .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },
> >>> +	{ .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },
> >>> +	{ .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, },
> >>> +	{ .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, },
> >>> +	{},
> >>> +};
> >>> +
> >>> +MODULE_DEVICE_TABLE(of, of_is31fl31xx_match);
> >>> +
> >>> +static int is31fl32xx_probe(struct i2c_client *client,
> >>> +				  const struct i2c_device_id *id)
> >>> +{
> >>> +	const struct is31fl32xx_chipdef *cdef;
> >>> +	const struct of_device_id *of_dev_id;
> >>> +	struct device *dev = &client->dev;
> >>> +	struct is31fl32xx_priv *priv;
> >>> +	int count;
> >>> +	int ret = 0;
> >>> +
> >>> +	of_dev_id = of_match_device(of_is31fl31xx_match, dev);
> >>> +	if (!of_dev_id)
> >>> +		return -EINVAL;
> >>> +
> >>> +	cdef = of_dev_id->data;
> >>> +
> >>> +	count = of_get_child_count(dev->of_node);
> >>> +	if (!count)
> >>> +		return -EINVAL;
> >>> +
> >>> +	priv = devm_kzalloc(dev, sizeof_is31fl32xx_priv(count),
> >>> +			    GFP_KERNEL);
> >>> +	if (!priv)
> >>> +		return -ENOMEM;
> >>> +
> >>> +	priv->client = client;
> >>> +	priv->cdef = cdef;
> >>> +	i2c_set_clientdata(client, priv);
> >>> +
> >>> +	ret = is31fl32xx_init_regs(priv);
> >>> +	if (ret)
> >>> +		return ret;
> >>> +
> >>> +	ret = is31fl32xx_parse_dt(dev, priv);
> >>> +	if (ret)
> >>> +		return ret;
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +static int is31fl32xx_remove(struct i2c_client *client)
> >>> +{
> >>> +	struct is31fl32xx_priv *priv = i2c_get_clientdata(client);
> >>> +
> >>> +	/* If there is a reset reg, then it does everything we need */
> >>> +	if (priv->cdef->reset_reg != IS31FL32XX_REG_NONE)
> >>> +		return is31fl32xx_write(priv, priv->cdef->reset_reg, 0);
> >>> +
> >>> +	/* If there is a reset func, then it does everything we need */
> >>> +	if (priv->cdef->reset_func)
> >>> +		return priv->cdef->reset_func(priv);
> >>> +
> >>> +	/* If we can't reset, then try just using software-shutdown mode */
> >>> +	if (priv->cdef->shutdown_reg != IS31FL32XX_REG_NONE)
> >>> +		return is31fl32xx_write(priv, priv->cdef->shutdown_reg, 0x00);
> >>> +
> >>> +	return 0;
> >>> +}
> >>> +
> >>> +/*
> >>> + * i2c-core requires that id_table be non-NULL, even though
> >>> + * it is not used for DeviceTree based instantiation.
> >>> + */
> >>> +static const struct i2c_device_id is31fl31xx_id[] = {
> >>> +	{},
> >>> +};
> >>> +
> >>> +MODULE_DEVICE_TABLE(i2c, is31fl31xx_id);
> >>> +
> >>> +static struct i2c_driver is31fl32xx_driver = {
> >>> +	.driver = {
> >>> +		.name	= "is31fl32xx",
> >>> +		.of_match_table = of_is31fl31xx_match,
> >>> +	},
> >>> +	.probe		= is31fl32xx_probe,
> >>> +	.remove		= is31fl32xx_remove,
> >>> +	.id_table	= is31fl31xx_id,
> >>> +};
> >>> +
> >>> +module_i2c_driver(is31fl32xx_driver);
> >>> +
> >>> +MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>");
> >>> +MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver");
> >>> +MODULE_LICENSE("GPL v2");
> >>>  

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

* Re: [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers
  2016-02-24 19:49       ` Rob Herring
@ 2016-02-26  0:30         ` David Rivshin (Allworx)
  2016-02-26  9:56           ` Jacek Anaszewski
  0 siblings, 1 reply; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-02-26  0:30 UTC (permalink / raw)
  To: Rob Herring
  Cc: Jacek Anaszewski, Linux LED Subsystem, devicetree,
	Richard Purdie, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, Stefan Wahren

On Wed, 24 Feb 2016 13:49:46 -0600
Rob Herring <robh+dt@kernel.org> wrote:

> On Wed, Feb 24, 2016 at 1:34 PM, David Rivshin (Allworx)
> <drivshin.allworx@gmail.com> wrote:
> > On Wed, 24 Feb 2016 17:04:49 +0100
> > Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> >  
> >> Hi David,
> >>
> >> Thanks for the patch.
> >>
> >> On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:  
> >> > From: David Rivshin <drivshin@allworx.com>
> >> >
> >> > This adds a binding description for the is31fl3236/35/18/16 I2C LED
> >> > drivers.
> >> >
> >> > Signed-off-by: David Rivshin <drivshin@allworx.com>
> >> > ---
> >> >   .../devicetree/bindings/leds/leds-is31fl32xx.txt   | 51 ++++++++++++++++++++++
> >> >   1 file changed, 51 insertions(+)
> >> >   create mode 100644 Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
> >> >
> >> > diff --git a/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt b/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
> >> > new file mode 100644
> >> > index 0000000..0a05a1d
> >> > --- /dev/null
> >> > +++ b/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
> >> > @@ -0,0 +1,51 @@
> >> > +Binding for ISSI IS31FL32xx LED Drivers
> >> > +
> >> > +The IS31FL32xx family of LED drivers are I2C devices with multiple
> >> > +constant-current channels, each with independent 256-level PWM control.
> >> > +Each LED is represented as a sub-node of the device.
> >> > +
> >> > +Required properties:
> >> > +- compatible: one of
> >> > +   issi,is31fl3236
> >> > +   issi,is31fl3235
> >> > +   issi,is31fl3218
> >> > +   issi,is31fl3216
> >> > +- reg: I2C slave address
> >> > +- address-cells : must be 1
> >> > +- size-cells : must be 0
> >> > +
> >> > +LED sub-node properties:
> >> > +- reg : LED channel number (1..N)
> >> > +- max-brightness : (optional) Maximum brightness possible for the LED.  
> >>
> >> Please use led-max-microamp instead. You can refer to
> >> Documentation/devicetree/bindings/leds/common.txt for detailed
> >> description.  
> >
> > FYI, I think I got that property from leds-pwm, since these use PWMs
> > internally, although I don't actually use it myself.
> >
> > I can see how led-max-microamp could be a useful property, however
> > I'm uncertain about computing cdev->max_brightness from it as these
> > devices look to control their max current via an external resistor.
> > I suppose either the resistor value, or the resultant max-current
> > would need to be given in the devicetree, and then that used along
> > with led-max-microamp to compute cdev->max_brightness. Although I'd
> > want it to be optional as I suspect most users don't need any
> > restriction. Also, I don't think I can properly test the result,
> > which always makes me nervous.
> >
> > Would it be acceptable to simply remove the max-brightness property
> > without adding led-max-microamp? It can always be added if a user
> > turns out to need it in the future. Or do you feel strongly that
> > led-max-microamp should be in the binding from the start?  
> 
> You should put in DT whatever makes sense for the controls the h/w
> has. If you only have a PWM, then only max-brightness makes sense and
> led-max-microamp does not.

There are multiple ways you can look at it, and I'm not sure which
is the overriding one. The brightness of the LED is determined by the 
current, which is in turn a combination of the following:
- The max per-LED current is set with an external resistor.
- Some devices have a scaling factor, which can be changed dynamically:
  - One of the devices has either a global scaling factor in a register,
    that can adjust max-current from 0.25x to 2x.
  - Two of the devices have a per-LED current divisor (1, 2, 3, or 4).
- The main control is a 0-255 "PWM duty cycle" register, which 
  effectively scales the current.
IOW, the final current is:
 <resistor-setting> * <scaling> * <PWM-duty-cycle>
Currently the binding and driver do not support setting either type of 
scaling (always unity) and only use the PWM duty cycle to dim the LED.

So if the purpose is to say "never allow this LED channel to go above 
X current", the leds-max-microamp probably makes sense.

If the purpose is to say "never allow the PWM duty cycle register
for this channel to go above value X", then max-brightness perhaps
makes sense. 

If the scaling is fixed by the DT (i.e won't be changed dynamically 
by the OS), then those two are basically equivalent in functionality. 
But if scaling can be changed dynamically, then they have different 
implications.

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-25 19:12         ` David Rivshin (Allworx)
@ 2016-02-26  9:47           ` Jacek Anaszewski
  2016-02-26 21:58             ` David Rivshin (Allworx)
  0 siblings, 1 reply; 33+ messages in thread
From: Jacek Anaszewski @ 2016-02-26  9:47 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: Stefan Wahren, linux-leds, devicetree, Richard Purdie,
	Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala

On 02/25/2016 08:12 PM, David Rivshin (Allworx) wrote:
> On Thu, 25 Feb 2016 11:55:58 +0100
> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>
>> On 02/25/2016 03:24 AM, David Rivshin (Allworx) wrote:
>>> On Wed, 24 Feb 2016 17:04:58 +0100
>>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>>>
>>>> Hi David,
>>>>
>>>> Thanks for the patch. Very nice driver. I have few comments
>>>> below.
>>>
>>> Thanks Jacek, I have responded the comments inline. I also wanted to
>>> double check whether you noticed some questions I had in the cover
>>> letter [1]. As I mentioned in another email to Rob, in hindsight I'm
>>> guessing I should have included them in the patch comments as well (or
>>> instead of).
>>
>> I saw them. I assumed that the review itself will address those
>> questions.
>
> Fair enough, thanks for the confirmation.
>
>>> Your review comments here effectively answered some of the questions, but
>>> the big one I'm still unsure of is whether it actually makes sense to
>>> have all 4 of these devices supported by a single driver.
>>
>> It's perfectly fine. Many drivers implement this pattern.
>
> OK, then I'll assume you think this driver is not yet too complicated
> for it's own good. Out of curiosity, might that view change if the
> 3216 specific features were ever implemented, especially GPIO and HW
> animation support? Gut feel is that would make 3216 specific code
> bigger than the rest of the code combined.

I don't think so.

> Bigger question is what should be dvi sone in terms of the overlap in device
> support between this driver and leds-sn3218? If you think I should leave
> the *3218 support in this driver, then I would propose:
>    - remove leds-sn3218 and its separate binding doc
>    - add the "si-en,sn3218" compatible string to this driver and binding doc
> Note that while I expect this driver to work with the 3218 chips, I do
> not have one to test against. If we go down this route I would definitely
> want Stefan to test so that I don't accidentally break him.

I'd prefer to have a single driver for the same hardware. Stefan, would
it be possible for you to test David's driver with the hardware you
have an access to?

> Also I feel I should point out some differences between the 3218 support
> in this driver versus the leds-sn3218 driver, in case they have any
> impact:
>    - (as previously mentioned) leds-sn3218 turns off an LEDs enable
>      bit if the brightness is set to 0. This driver just sets the PWM
>      to 0 and leaves the enable bits always on.

Setting brightness to 0 is an equivalent to turning the device in
a power down mode or at least in the state where the current consumption
is as low as possible. A hardware configuration that is most fitting
for this requirements should be chosen.

>    - leds-sn3218 uses a regmap, I think mostly to deal with the enable
>      bits, but it also has the benefit of showing up in debugfs. This
>      could be seen as useful in and of itself by some users. On the other
>      hand regmap introduces another mutex on every write.

I will not insist on using regmap if you don't refer to the current
state of hw registers in your driver.

>    - leds-sn3218 implements the shutdown callback. Actually, I think I
>      should add that to this driver in any event.

Do you see use cases or hardware configurations that need such
a callback? I didn't oppose in case of Stefan's driver, but if we are
at it, we can consult Stefan if he saw that use case?

I'd say that shutdown op is usually useful when CPU and LED
controller are powered from different sources, which can result in
a situation when CPU is turned off, but LED remains on.

>    - leds-sn3218 just puts the chip in software-shutdown mode on remove/
>      shutdown. This driver uses the reset register to put the device in
>      poweron state, and software-shutdown is part of the poweron state.
>      Only difference would be if the next code to use the device does
>      not do it's own full initialization (which seems unlikely, or at
>      least unwise), but instead just clears software-shutdown.

I believe that my above explanations address this question, i.e.
both brightness = 0 an remove/shutdown should set the device
in a power down mode.

>
>>> I won't
>>> clutter this email with a duplicate of the details (it's somewhat long),
>>> but if you could check the cover letter and give some guidance, I would
>>> appreciate it.
>>>
>>> [1] http://www.spinics.net/lists/linux-leds/msg05564.html
>>>       http://thread.gmane.org/gmane.linux.leds/4530
>>>
>>>>
>>>> On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:
>>>>> From: David Rivshin <drivshin@allworx.com>
>>>>>
>>>>> The IS31FL32xx family of LED drivers are I2C devices with multiple
>>>>> constant-current channels, each with independent 256-level PWM control.
>>>>>
>>>>> HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
>>>>>
>>>>> This has been tested on the IS31FL3236 and IS31FL3216 on an ARM
>>>>> (TI am335x) platform.
>>>>>
>>>>> The programming paradigm of these devices is similar in the following
>>>>> ways:
>>>>>     - All registers are 8 bit
>>>>>     - All LED control registers are write-only
>>>>>     - Each LED channel has a PWM register (0-255)
>>>>>     - PWM register writes are shadowed until an Update register is poked
>>>>>     - All have a concept of Software Shutdown, which disables output
>>>>>
>>>>> However, there are some differences in devices:
>>>>>     - 3236/3235 have a separate Control register for each LED,
>>>>>       (3218/3216 pack the enable bits into fewer registers)
>>>>>     - 3236/3235 have a per-channel current divisor setting
>>>>>     - 3236/3235 have a Global Control register that can turn off all LEDs
>>>>>     - 3216 is unique in a number of ways
>>>>>        - OUT9-OUT16 can be configured as GPIOs instead of LED controls
>>>>>        - LEDs can be programmed with an 8-frame animation, with
>>>>>          programmable delay between frames
>>>>>        - LEDs can be modulated by an input audio signal
>>>>>        - Max output current can be adjusted from 1/4 to 2x globally
>>>>>        - Has a Configuration register instead of a Shutdown register
>>>>>
>>>>> This driver currently only supports the base PWM control function
>>>>> of these devices. The following features of these devices are not
>>>>> implemented, although it should be possible to add them in the future:
>>>>>     - All devices are capable of going into a lower-power "software
>>>>>       shutdown" mode.
>>>>>     - The is31fl3236 and is31fl3235 can reduce the max output current
>>>>>       per-channel with a divisor of 1, 2, 3, or 4.
>>>>>     - The is31fl3216 can use some LED channels as GPIOs instead.
>>>>>     - The is31fl3216 can animate LEDs in hardware.
>>>>>     - The is31fl3216 can modulate LEDs according to an audio input.
>>>>>     - The is31fl3216 can reduce/increase max output current globally.
>>>>>
>>>>> Signed-off-by: David Rivshin <drivshin@allworx.com>
>>>>> ---
>>>>>     drivers/leds/Kconfig           |   9 +
>>>>>     drivers/leds/Makefile          |   1 +
>>>>>     drivers/leds/leds-is31fl32xx.c | 442 +++++++++++++++++++++++++++++++++++++++++
>>>>>     3 files changed, 452 insertions(+)
>>>>>     create mode 100644 drivers/leds/leds-is31fl32xx.c
>>>>>
>>>>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>>>>> index 1034696..8f6c46f 100644
>>>>> --- a/drivers/leds/Kconfig
>>>>> +++ b/drivers/leds/Kconfig
>>>>> @@ -580,6 +580,15 @@ config LEDS_SN3218
>>>>>     	  This driver can also be built as a module. If so the module
>>>>>     	  will be called leds-sn3218.
>>>>>
>>>>> +config LEDS_IS31FL32XX
>>>>> +	tristate "Driver for ISSI IS31FL32XX I2C LED driver chip family"
>>>>
>>>> 2 x "[Dd]river".
>>>>
>>>> How about:
>>>>
>>>> "LED Support for ISSI IS31FL32XX I2C LED chip family" ?
>>>
>>> Yes, I found that awkward as well. HW folks (and the datasheets) seem
>>> always refer to devices of this type as "LED Driver"s (which can lead
>>> to some interesting confusions). Taking a cue from the LP5521/23/62
>>> entries, how about:
>>>    "LED Support for the ISSI IS31FL32XX I2C LED driver chip family" ?
>>
>> "LED Support" means "LED class driver". Driver is a software support
>> for hardware chip. What discrepancy do you see in the description
>> I proposed?
>
> I think in this case "driver" also means "hardware device which drives
> a physical LED".

Let's not confuse these notions. From Linux perspective "driver" refers
to a piece of software used for controlling a hardware.

> It seems that "LED driver" is the term universally used
> to describe this type of HW device in datasheets.

There are also e.g. "LED controllers", "LED current regulators".
Let's stick to the convention predominantly used in the LED subsystem
kernel config menu.

> So it seemed useful to
> use exactly that phrase in the description of what hardware this software
> supports. I could see someone interpreting the phrase "LED chip" as
> referring to an actual LED device.
> I don't feel very strongly on this topic, but for the sake of discussion,
> maybe "LED controller" would avoid any possible confusion in both
> directions?

Right, so let's use the following:

"LED Support for ISSI IS31FL32XX I2C LED controller family"

I understand "LED Support" as "Linux LED subsystem support".

>>> Perhaps that's the best of both worlds?
>>>
>>>>> +	depends on LEDS_CLASS && I2C && OF
>>>>> +	help
>>>>> +	  Say Y here to include support for the ISSI 31FL32XX LED driver family.
>>>>
>>>> s/driver/chip/
>>>>
>>>>> +	  They are I2C devices with multiple constant-current channels, each
>>>>> +	  with independent 256-level PWM control. This will only work with
>>>>> +	  device tree enabled devices.
>>>>
>>>> We can skip the last sentence I think.
>>>
>>> OK. FYI, I think I got that verbiage from LEDS_SYSCON.
>>
>> Having "depends on OF" is self-explanatory here.
>
> Noted.
>
>>>>> +
>>>>>     comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
>>>>>
>>>>>     config LEDS_BLINKM
>>>>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
>>>>> index 89c9b6f..3fdf313 100644
>>>>> --- a/drivers/leds/Makefile
>>>>> +++ b/drivers/leds/Makefile
>>>>> @@ -67,6 +67,7 @@ obj-$(CONFIG_LEDS_KTD2692)		+= leds-ktd2692.o
>>>>>     obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
>>>>>     obj-$(CONFIG_LEDS_SEAD3)		+= leds-sead3.o
>>>>>     obj-$(CONFIG_LEDS_SN3218)		+= leds-sn3218.o
>>>>> +obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
>>>>>
>>>>>     # LED SPI Drivers
>>>>>     obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
>>>>> diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
>>>>> new file mode 100644
>>>>> index 0000000..8dea518
>>>>> --- /dev/null
>>>>> +++ b/drivers/leds/leds-is31fl32xx.c
>>>>> @@ -0,0 +1,442 @@
>>>>> +/*
>>>>> + * linux/drivers/leds-is31fl32xx.c
>>>>> + *
>>>>> + * Driver for ISSI IS31FL32xx family of I2C LED controllers
>>>>> + *
>>>>> + * Copyright 2015 Allworx Corp.
>>>>> + *
>>>>> + *
>>>>> + * 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.
>>>>> + *
>>>>> + * HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
>>>>> + */
>>>>> +
>>>>> +#include <linux/err.h>
>>>>> +#include <linux/i2c.h>
>>>>> +#include <linux/kernel.h>
>>>>> +#include <linux/leds.h>
>>>>> +#include <linux/module.h>
>>>>> +#include <linux/of_platform.h>
>>>>> +
>>>>> +#ifdef DEBUG
>>>>> + #undef dev_dbg
>>>>> + #define dev_dbg dev_info
>>>>> +#endif
>>>>
>>>> What's the benefit of the above?
>>>
>>> It gave me a way to easily see debug output from the driver while it
>>> was parsing the DT (especially if the driver was built-in). Early on
>>> there were other things within that #ifdef as well.
>>> Regardless, passing ddebug_query on the kernel commandline is a more
>>> appropriate way of accomplishing that; I'll remove for the next version.
>>
>> Thanks.
>>
>>>>> +/* Used to indicate a device has no such register */
>>>>> +#define IS31FL32XX_REG_NONE 0xFF
>>>>> +
>>>>> +#define IS31FL3216_CONFIG_REG 0x00
>>>>> +#define IS31FL3216_LIGHTING_EFFECT_REG 0x03
>>>>> +#define IS31FL3216_CHANNEL_CONFIG_REG 0x04
>>>>> +
>>>>> +struct is31fl32xx_priv;
>>>>> +struct is31fl32xx_led_data {
>>>>> +	struct led_classdev cdev;
>>>>> +	u8 channel; /* 1-based, max priv->cdef->channels */
>>>>> +	struct is31fl32xx_priv *priv;
>>>>> +};
>>>>> +
>>>>> +struct is31fl32xx_priv {
>>>>> +	const struct is31fl32xx_chipdef *cdef;
>>>>> +	struct i2c_client *client;
>>>>> +	unsigned int num_leds;
>>>>> +	struct is31fl32xx_led_data leds[0];
>>>>
>>>> Is there any specific reason for not having *leds here instead?
>>>
>>> I followed a pattern from leds-pwm where it did a single allocation
>>> for both priv and priv->leds[]. See sizeof_is31fl32xx_priv(), and
>>> its use, below. I saw the benefit as one fewer small allocation, so
>>> slightly more kind to the allocator (and devres). If you'd prefer to
>>> do it as two allocations, I'll make the change.
>>
>> OK, I had to look at this one more time. I like the idea.
>
> OK, I'll keep it as-is.
>
>>>>> +};
>>>>> +
>>>>> +/**
>>>>> + * struct is31fl32xx_chipdef - chip-specific attributes
>>>>> + * @channels            : Number of LED channels
>>>>> + * @shutdown_reg        : address of Shutdown register (optional)
>>>>> + * @pwm_update_reg      : address of PWM Update register
>>>>> + * @global_control_reg	: address of Global Control register (optional)
>>>>> + * @reset_reg           : address of Reset register (optional)
>>>>> + * @pwm_register_base	: address of first PWM register
>>>>> + * @pwm_registers_reversed: : true if PWM registers count down instead of up
>>>>> + * @led_control_register_base : address of first LED control register (optional)
>>>>> + * @enable_bits_per_led_control_register: number of LEDs enable bits in each
>>>>> + * @reset_func:         : pointer to reset function
>>>>> + *
>>>>> + * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE
>>>>> + * indicates that this chip has no such register.
>>>>> + *
>>>>> + * If non-NULL, @reset_func will be called during probing to set all
>>>>> + * necessary registers to a known initialization state. This is needed
>>>>> + * for chips that do not have a @reset_reg.
>>>>> + *
>>>>> + * @enable_bits_per_led_control_register must be >=1 if
>>>>> + * @led_control_register_base != %IS31FL32XX_REG_NONE.
>>>>> + */
>>>>> +struct is31fl32xx_chipdef {
>>>>> +	u8	channels;
>>>>> +	u8	shutdown_reg;
>>>>> +	u8	pwm_update_reg;
>>>>> +	u8	global_control_reg;
>>>>> +	u8	reset_reg;
>>>>> +	u8	pwm_register_base;
>>>>> +	bool	pwm_registers_reversed;
>>>>> +	u8	led_control_register_base;
>>>>> +	u8	enable_bits_per_led_control_register;
>>>>> +	int (*reset_func)(struct is31fl32xx_priv *priv);
>>>>> +};
>>>>> +
>>>>> +static const struct is31fl32xx_chipdef is31fl3236_cdef = {
>>>>> +	.channels				= 36,
>>>>> +	.shutdown_reg				= 0x00,
>>>>> +	.pwm_update_reg				= 0x25,
>>>>> +	.global_control_reg			= 0x4a,
>>>>> +	.reset_reg				= 0x4f,
>>>>> +	.pwm_register_base			= 0x01,
>>>>> +	.led_control_register_base		= 0x26,
>>>>> +	.enable_bits_per_led_control_register	= 1,
>>>>> +};
>>>>> +
>>>>> +static const struct is31fl32xx_chipdef is31fl3235_cdef = {
>>>>> +	.channels				= 28,
>>>>> +	.shutdown_reg				= 0x00,
>>>>> +	.pwm_update_reg				= 0x25,
>>>>> +	.global_control_reg			= 0x4a,
>>>>> +	.reset_reg				= 0x4f,
>>>>> +	.pwm_register_base			= 0x05,
>>>>> +	.led_control_register_base		= 0x2a,
>>>>> +	.enable_bits_per_led_control_register	= 1,
>>>>> +};
>>>>> +
>>>>> +static const struct is31fl32xx_chipdef is31fl3218_cdef = {
>>>>> +	.channels				= 18,
>>>>> +	.shutdown_reg				= 0x00,
>>>>> +	.pwm_update_reg				= 0x16,
>>>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
>>>>> +	.reset_reg				= 0x17,
>>>>> +	.pwm_register_base			= 0x01,
>>>>> +	.led_control_register_base		= 0x13,
>>>>> +	.enable_bits_per_led_control_register	= 6,
>>>>> +};
>>>>> +
>>>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv);
>>>>> +static const struct is31fl32xx_chipdef is31fl3216_cdef = {
>>>>> +	.channels				= 16,
>>>>> +	.shutdown_reg				= IS31FL32XX_REG_NONE,
>>>>> +	.pwm_update_reg				= 0xB0,
>>>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
>>>>> +	.reset_reg				= IS31FL32XX_REG_NONE,
>>>>> +	.pwm_register_base			= 0x10,
>>>>> +	.pwm_registers_reversed			= true,
>>>>> +	.led_control_register_base		= 0x01,
>>>>> +	.enable_bits_per_led_control_register	= 8,
>>>>> +	.reset_func				= is31fl3216_reset,
>>>>> +};
>>>>> +
>>>>> +static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)
>>>>> +{
>>>>> +	int ret;
>>>>> +
>>>>> +	dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
>>>>> +
>>>>> +	ret =  i2c_smbus_write_byte_data(priv->client, reg, val);
>>>>> +	if (ret) {
>>>>> +		dev_err(&priv->client->dev,
>>>>> +			"register write to 0x%02X failed (error %d)",
>>>>> +			reg, ret);
>>>>> +	}
>>>>> +	return ret;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * Custom reset function for IS31FL3216 because it does not have a RESET
>>>>> + * register the way that the other IS31FL32xx chips do. We don't bother
>>>>> + * writing the GPIO and animation registers, because the registers we
>>>>> + * do write ensure those will have no effect.
>>>>> + */
>>>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv)
>>>>> +{
>>>>> +	unsigned int i;
>>>>> +	int ret;
>>>>> +
>>>>> +	for (i = 0; i < priv->cdef->channels; i++) {
>>>>> +		ret = is31fl32xx_write(priv, priv->cdef->pwm_register_base+i,
>>>>> +				       0x00);
>>>>> +		if (ret)
>>>>> +			return ret;
>>>>> +	}
>>>>> +	ret = is31fl32xx_write(priv, priv->cdef->pwm_update_reg, 0);
>>>>> +	if (ret)
>>>>> +		return ret;
>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, 0x00);
>>>>> +	if (ret)
>>>>> +		return ret;
>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, 0x00);
>>>>> +	if (ret)
>>>>> +		return ret;
>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, 0x00);
>>>>> +	if (ret)
>>>>> +		return ret;
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +
>>>>> +static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,
>>>>> +				     enum led_brightness brightness)
>>>>> +{
>>>>> +	const struct is31fl32xx_led_data *led_data =
>>>>> +		container_of(led_cdev, struct is31fl32xx_led_data, cdev);
>>>>> +	const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef;
>>>>> +	u8 pwm_register_offset;
>>>>> +	int ret;
>>>>> +
>>>>> +	dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);
>>>>> +
>>>>> +	/* NOTE: led_data->channel is 1-based */
>>>>> +	if (cdef->pwm_registers_reversed)
>>>>> +		pwm_register_offset = cdef->channels - led_data->channel;
>>>>> +	else
>>>>> +		pwm_register_offset = led_data->channel - 1;
>>>>> +
>>>>> +	ret = is31fl32xx_write(led_data->priv,
>>>>> +			       cdef->pwm_register_base + pwm_register_offset,
>>>>> +			       brightness);
>>>>> +	if (ret)
>>>>> +		return ret;
>>>>
>>>> I infer that nothing wrong happens in case current process is preempted
>>>> here by the call originating from the other sub-LED?
>>>
>>> I do not believe so. All the driver-specific data used here is read-only
>>> after probing. chipdefs are entirely const, and the only thing in priv
>>> that's referenced is the chipdef pointer which logically could not change
>>> post-probe. Actually nothing else in priv is modified post-probe either.
>>>
>>> The I2C core code has a mutex on the bus, so two writes cannot happen at
>>> once.
>>>
>>> In all these devices there is a unique PWM duty-cycle register for each
>>> LED channel (which is what is being written here), so no register writes
>>> for one LED channel effect any others.
>>>
>>> I believe the worst that could happen is that the device would see:
>>> 	PWM_REG_A write X
>>> 	PWM_REG_B write Y
>>> 	UPDATE_REG write 0
>>> 	UPDATE_REG write 0
>>> instead of
>>> 	PWM_REG_A write X
>>> 	UPDATE_REG write 0
>>> 	PWM_REG_B write Y
>>> 	UPDATE_REG write 0
>>> but that makes no difference to the functionality. Poking the update
>>> register merely applies all PWM register writes up to that point (I'm
>>> assuming to allow atomically changing the state of multiple LEDs at
>>> once).
>>
>> Thanks for this comprehensive explanation.
>
> Should I put some part of this explanation in a comment somewhere? Seems
> like the kind of thing someone else might wonder about in the future also.

Good idea.

>>> I should note here (as mentioned in cover letter), I made a choice to
>>> always leave the per-LED "enable" bits on, and let the PWM just get set
>>> to 0 naturally to turn an LED off. This differs from the existing SN3218
>>> driver, which used regmap_update_bits, and is then protected by a per-
>>> regmap mutex.
>>
>> ack.
>>
>>>>> +	return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);
>>>>> +
>>>>> +}
>>>>> +
>>>>> +static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
>>>>> +{
>>>>> +	const struct is31fl32xx_chipdef *cdef = priv->cdef;
>>>>> +	int ret;
>>>>> +
>>>>> +	if (cdef->reset_reg != IS31FL32XX_REG_NONE) {
>>>>> +		ret = is31fl32xx_write(priv, cdef->reset_reg, 0);
>>>>> +		if (ret)
>>>>> +			return ret;
>>>>> +	}
>>>>> +	if (cdef->reset_func) {
>>>>> +		ret = cdef->reset_func(priv);
>>>>> +		if (ret)
>>>>> +			return ret;
>>>>> +	}
>>>>> +	if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) {
>>>>> +		u8 value =
>>>>> +		    GENMASK(cdef->enable_bits_per_led_control_register-1, 0);
>>>>> +		u8 num_regs = cdef->channels /
>>>>> +				cdef->enable_bits_per_led_control_register;
>>>>> +		int i;
>>>>> +
>>>>> +		for (i = 0; i < num_regs; i++) {
>>>>> +			ret = is31fl32xx_write(priv,
>>>>> +					       cdef->led_control_register_base+i,
>>>>> +					       value);
>>>>> +			if (ret)
>>>>> +				return ret;
>>>>> +		}
>>>>> +	}
>>>>> +	if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) {
>>>>> +		ret = is31fl32xx_write(priv, cdef->shutdown_reg, BIT(0));
>>>>> +		if (ret)
>>>>> +			return ret;
>>>>> +	}
>>>>> +	if (cdef->global_control_reg != IS31FL32XX_REG_NONE) {
>>>>> +		ret = is31fl32xx_write(priv, cdef->global_control_reg, 0x00);
>>>>> +		if (ret)
>>>>> +			return ret;
>>>>> +	}
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static inline size_t sizeof_is31fl32xx_priv(int num_leds)
>>>>> +{
>>>>> +	return sizeof(struct is31fl32xx_priv) +
>>>>> +		      (sizeof(struct is31fl32xx_led_data) * num_leds);
>>>>> +}
>>>>> +
>>>>> +static int is31fl32xx_parse_child_dt(const struct device *dev,
>>>>> +				     const struct device_node *child,
>>>>> +				     struct is31fl32xx_led_data *led_data)
>>>>> +{
>>>>> +	struct led_classdev *cdev = &led_data->cdev;
>>>>> +	int ret = 0;
>>>>> +	u32 reg;
>>>>> +
>>>>> +	cdev->name = of_get_property(child, "label", NULL) ? : child->name;
>>>>> +
>>>>> +	ret = of_property_read_u32(child, "reg", &reg);
>>>>> +	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
>>>>> +		dev_err(dev,
>>>>> +			"Child node %s does not have a valid reg property\n",
>>>>> +			child->name);
>>>>> +		return -EINVAL;
>>>>> +	}
>>>>> +	led_data->channel = reg;
>>>>> +
>>>>> +	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
>>>>> +						NULL);
>>>>> +	cdev->brightness = LED_OFF;
>>>>
>>>> devm_kzalloc secures that.
>>>
>>> OK, I will remove.
>>>
>>>>> +	ret = of_property_read_u32(child, "max-brightness",
>>>>> +				   &cdev->max_brightness);
>>>>> +	if (ret == -EINVAL) {
>>>>> +		cdev->max_brightness = 255;
>>>>
>>>> s/255/LED_FULL/
>>>
>>> Noted, although (from the patch 2 discussion) max-brightness property is
>>> removed/replaced, this would go away anyways.
>>>
>>>>> +	} else if (ret) {
>>>>> +		dev_dbg(dev,
>>>>> +			"Child node %s has an invalid max-brightness property\n",
>>>>> +			child->name);
>>>>> +		return -EINVAL;
>>>>> +	}
>>>>> +
>>>>> +	cdev->brightness_set_blocking = is31fl32xx_brightness_set;
>>>>
>>>> Please add empty line here.
>>>
>>> Done.
>>>
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static struct is31fl32xx_led_data *is31fl32xx_find_led_data(
>>>>> +					struct is31fl32xx_priv *priv,
>>>>> +					u8 channel)
>>>>> +{
>>>>> +	size_t i;
>>>>> +
>>>>> +	for (i = 0; i < priv->num_leds; i++) {
>>>>> +		if (priv->leds[i].channel == channel)
>>>>> +			return &priv->leds[i];
>>>>> +	}
>>>>
>>>> Ditto.
>>>
>>> Done.
>>>
>>>>> +	return NULL;
>>>>> +}
>>>>> +
>>>>> +static int is31fl32xx_parse_dt(struct device *dev,
>>>>> +			       struct is31fl32xx_priv *priv)
>>>>> +{
>>>>> +	struct device_node *child;
>>>>> +
>>>>> +	for_each_child_of_node(dev->of_node, child) {
>>>>> +		struct is31fl32xx_led_data *led_data =
>>>>> +			&priv->leds[priv->num_leds];
>>>>> +		int ret = 0;
>>>>> +		const struct is31fl32xx_led_data *other_led_data;
>>>>> +
>>>>> +		led_data->priv = priv;
>>>>> +
>>>>> +		ret = is31fl32xx_parse_child_dt(dev, child, led_data);
>>>>> +		if (ret)
>>>>> +			continue;
>>>>
>>>> I prefer failing in such cases,
>>>
>>> OK, I will change to an 'goto err' which will have an 'of_node_put()'
>>> and 'return ret'.
>>>
>>> I will say, however, that while testing the error-detection in the
>>> parsing logic, it was very convenient to construct a single devicetree
>>> with a variety of errors. Then a single boot would test multiple
>>> cases at once.
>>
>> Good idea for testing, but in case some failure occurs during DT child
>> node parsing in the release environment you're left with unused
>> allocated memory.
>
> Agreed. BTW, I assume from this that it's common to say "if there's
> anything wrong in one part of your DT, there is no guarantee as to what
> parts will actually be used"? I say this because what we're saying is that
> if one LED node on this device is faulty, that all of them are ignored.
> Analogy might be to a whole I2C bus being ignored because one of the
> devices on it failed to probe. To the devicetree it's still a parent/child
> bus/address relationship, even though the driver implementation is
> very different.

I2C example is too generic I think. I2C controller is not as tightly
coupled with I2C clients as LED controller with its current outputs.
Besides, I2C controller doesn't have an idea what devices will attach
to the bus it controls upon probing, contrarily to a LED controller.

>>>>> +
>>>>> +		/* Detect if channel is already in use by another child */
>>>>> +		other_led_data = is31fl32xx_find_led_data(priv,
>>>>> +							  led_data->channel);
>>>>> +		if (other_led_data) {
>>>>> +			dev_err(dev,
>>>>> +				"%s ignored: channel %d already used by %s",
>>>>> +				led_data->cdev.name,
>>>>> +				led_data->channel,
>>>>> +				other_led_data->cdev.name);
>>>>> +			continue;
>>>>
>>>> Ditto.
>>>
>>> OK.
>>>
>>>>> +		}
>>>>> +
>>>>> +		ret = devm_led_classdev_register(dev, &led_data->cdev);
>>>>> +		if (ret == 0) {
>>>>> +			priv->num_leds++;
>>>>> +		} else {
>>>>> +			dev_err(dev, "failed to register PWM led for %s: %d\n",
>>>>> +				led_data->cdev.name, ret);
>>>
>>> Should I also fail here, then? Right now it will continue trying to
>>> register future LED devices if a classdev_register fails for some
>>> reason, and will successfully load even if all of them fail.
>>
>> Please fail here too. If we can't setup the sub-LED that is advertised
>> in a DT child node, then it means that something went wrong.
>> This is clear error case.
>
> Done.
>
>>>>> +		}
>>>>> +	}
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static const struct of_device_id of_is31fl31xx_match[] = {
>>>>> +	{ .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },
>>>>> +	{ .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },
>>>>> +	{ .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, },
>>>>> +	{ .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, },
>>>>> +	{},
>>>>> +};
>>>>> +
>>>>> +MODULE_DEVICE_TABLE(of, of_is31fl31xx_match);
>>>>> +
>>>>> +static int is31fl32xx_probe(struct i2c_client *client,
>>>>> +				  const struct i2c_device_id *id)
>>>>> +{
>>>>> +	const struct is31fl32xx_chipdef *cdef;
>>>>> +	const struct of_device_id *of_dev_id;
>>>>> +	struct device *dev = &client->dev;
>>>>> +	struct is31fl32xx_priv *priv;
>>>>> +	int count;
>>>>> +	int ret = 0;
>>>>> +
>>>>> +	of_dev_id = of_match_device(of_is31fl31xx_match, dev);
>>>>> +	if (!of_dev_id)
>>>>> +		return -EINVAL;
>>>>> +
>>>>> +	cdef = of_dev_id->data;
>>>>> +
>>>>> +	count = of_get_child_count(dev->of_node);
>>>>> +	if (!count)
>>>>> +		return -EINVAL;
>>>>> +
>>>>> +	priv = devm_kzalloc(dev, sizeof_is31fl32xx_priv(count),
>>>>> +			    GFP_KERNEL);
>>>>> +	if (!priv)
>>>>> +		return -ENOMEM;
>>>>> +
>>>>> +	priv->client = client;
>>>>> +	priv->cdef = cdef;
>>>>> +	i2c_set_clientdata(client, priv);
>>>>> +
>>>>> +	ret = is31fl32xx_init_regs(priv);
>>>>> +	if (ret)
>>>>> +		return ret;
>>>>> +
>>>>> +	ret = is31fl32xx_parse_dt(dev, priv);
>>>>> +	if (ret)
>>>>> +		return ret;
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static int is31fl32xx_remove(struct i2c_client *client)
>>>>> +{
>>>>> +	struct is31fl32xx_priv *priv = i2c_get_clientdata(client);
>>>>> +
>>>>> +	/* If there is a reset reg, then it does everything we need */
>>>>> +	if (priv->cdef->reset_reg != IS31FL32XX_REG_NONE)
>>>>> +		return is31fl32xx_write(priv, priv->cdef->reset_reg, 0);
>>>>> +
>>>>> +	/* If there is a reset func, then it does everything we need */
>>>>> +	if (priv->cdef->reset_func)
>>>>> +		return priv->cdef->reset_func(priv);
>>>>> +
>>>>> +	/* If we can't reset, then try just using software-shutdown mode */
>>>>> +	if (priv->cdef->shutdown_reg != IS31FL32XX_REG_NONE)
>>>>> +		return is31fl32xx_write(priv, priv->cdef->shutdown_reg, 0x00);
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * i2c-core requires that id_table be non-NULL, even though
>>>>> + * it is not used for DeviceTree based instantiation.
>>>>> + */
>>>>> +static const struct i2c_device_id is31fl31xx_id[] = {
>>>>> +	{},
>>>>> +};
>>>>> +
>>>>> +MODULE_DEVICE_TABLE(i2c, is31fl31xx_id);
>>>>> +
>>>>> +static struct i2c_driver is31fl32xx_driver = {
>>>>> +	.driver = {
>>>>> +		.name	= "is31fl32xx",
>>>>> +		.of_match_table = of_is31fl31xx_match,
>>>>> +	},
>>>>> +	.probe		= is31fl32xx_probe,
>>>>> +	.remove		= is31fl32xx_remove,
>>>>> +	.id_table	= is31fl31xx_id,
>>>>> +};
>>>>> +
>>>>> +module_i2c_driver(is31fl32xx_driver);
>>>>> +
>>>>> +MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>");
>>>>> +MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver");
>>>>> +MODULE_LICENSE("GPL v2");
>>>>>
>
>
>


-- 
Best regards,
Jacek Anaszewski

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

* Re: [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers
  2016-02-26  0:30         ` David Rivshin (Allworx)
@ 2016-02-26  9:56           ` Jacek Anaszewski
  0 siblings, 0 replies; 33+ messages in thread
From: Jacek Anaszewski @ 2016-02-26  9:56 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: Rob Herring, Linux LED Subsystem, devicetree, Richard Purdie,
	Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Stefan Wahren

On 02/26/2016 01:30 AM, David Rivshin (Allworx) wrote:
> On Wed, 24 Feb 2016 13:49:46 -0600
> Rob Herring <robh+dt@kernel.org> wrote:
>
>> On Wed, Feb 24, 2016 at 1:34 PM, David Rivshin (Allworx)
>> <drivshin.allworx@gmail.com> wrote:
>>> On Wed, 24 Feb 2016 17:04:49 +0100
>>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>>>
>>>> Hi David,
>>>>
>>>> Thanks for the patch.
>>>>
>>>> On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:
>>>>> From: David Rivshin <drivshin@allworx.com>
>>>>>
>>>>> This adds a binding description for the is31fl3236/35/18/16 I2C LED
>>>>> drivers.
>>>>>
>>>>> Signed-off-by: David Rivshin <drivshin@allworx.com>
>>>>> ---
>>>>>    .../devicetree/bindings/leds/leds-is31fl32xx.txt   | 51 ++++++++++++++++++++++
>>>>>    1 file changed, 51 insertions(+)
>>>>>    create mode 100644 Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
>>>>>
>>>>> diff --git a/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt b/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
>>>>> new file mode 100644
>>>>> index 0000000..0a05a1d
>>>>> --- /dev/null
>>>>> +++ b/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
>>>>> @@ -0,0 +1,51 @@
>>>>> +Binding for ISSI IS31FL32xx LED Drivers
>>>>> +
>>>>> +The IS31FL32xx family of LED drivers are I2C devices with multiple
>>>>> +constant-current channels, each with independent 256-level PWM control.
>>>>> +Each LED is represented as a sub-node of the device.
>>>>> +
>>>>> +Required properties:
>>>>> +- compatible: one of
>>>>> +   issi,is31fl3236
>>>>> +   issi,is31fl3235
>>>>> +   issi,is31fl3218
>>>>> +   issi,is31fl3216
>>>>> +- reg: I2C slave address
>>>>> +- address-cells : must be 1
>>>>> +- size-cells : must be 0
>>>>> +
>>>>> +LED sub-node properties:
>>>>> +- reg : LED channel number (1..N)
>>>>> +- max-brightness : (optional) Maximum brightness possible for the LED.
>>>>
>>>> Please use led-max-microamp instead. You can refer to
>>>> Documentation/devicetree/bindings/leds/common.txt for detailed
>>>> description.
>>>
>>> FYI, I think I got that property from leds-pwm, since these use PWMs
>>> internally, although I don't actually use it myself.
>>>
>>> I can see how led-max-microamp could be a useful property, however
>>> I'm uncertain about computing cdev->max_brightness from it as these
>>> devices look to control their max current via an external resistor.
>>> I suppose either the resistor value, or the resultant max-current
>>> would need to be given in the devicetree, and then that used along
>>> with led-max-microamp to compute cdev->max_brightness. Although I'd
>>> want it to be optional as I suspect most users don't need any
>>> restriction. Also, I don't think I can properly test the result,
>>> which always makes me nervous.
>>>
>>> Would it be acceptable to simply remove the max-brightness property
>>> without adding led-max-microamp? It can always be added if a user
>>> turns out to need it in the future. Or do you feel strongly that
>>> led-max-microamp should be in the binding from the start?
>>
>> You should put in DT whatever makes sense for the controls the h/w
>> has. If you only have a PWM, then only max-brightness makes sense and
>> led-max-microamp does not.
>
> There are multiple ways you can look at it, and I'm not sure which
> is the overriding one. The brightness of the LED is determined by the
> current, which is in turn a combination of the following:
> - The max per-LED current is set with an external resistor.
> - Some devices have a scaling factor, which can be changed dynamically:
>    - One of the devices has either a global scaling factor in a register,
>      that can adjust max-current from 0.25x to 2x.
>    - Two of the devices have a per-LED current divisor (1, 2, 3, or 4).
> - The main control is a 0-255 "PWM duty cycle" register, which
>    effectively scales the current.
> IOW, the final current is:
>   <resistor-setting> * <scaling> * <PWM-duty-cycle>
> Currently the binding and driver do not support setting either type of
> scaling (always unity) and only use the PWM duty cycle to dim the LED.
>
> So if the purpose is to say "never allow this LED channel to go above
> X current", the leds-max-microamp probably makes sense.
>
> If the purpose is to say "never allow the PWM duty cycle register
> for this channel to go above value X", then max-brightness perhaps
> makes sense.
>
> If the scaling is fixed by the DT (i.e won't be changed dynamically
> by the OS), then those two are basically equivalent in functionality.
> But if scaling can be changed dynamically, then they have different
> implications.

Actually we introduced led-max-microamp property because we came
to conclusion that brightness level is not a suitable unit for
describing hardware.

As stated in Documentation/devicetree/bindings/leds/common.txt,
the led-max-microamp's property purpose is to protect the LEDs
from damaging in case of excessive current is set. This is especially
vital in case of flash LED controllers where the current can reach ~1A.

IMO having max-brightness only for defining a LED class device
usage policy is not justified, since this can be accomplished
from user space.

-- 
Best regards,
Jacek Anaszewski

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-26  9:47           ` Jacek Anaszewski
@ 2016-02-26 21:58             ` David Rivshin (Allworx)
  2016-02-27 10:48               ` Stefan Wahren
  2016-02-29  9:47               ` Jacek Anaszewski
  0 siblings, 2 replies; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-02-26 21:58 UTC (permalink / raw)
  To: Jacek Anaszewski, Stefan Wahren
  Cc: linux-leds, devicetree, Richard Purdie, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala

On Fri, 26 Feb 2016 10:47:46 +0100
Jacek Anaszewski <j.anaszewski@samsung.com> wrote:

> On 02/25/2016 08:12 PM, David Rivshin (Allworx) wrote:
> > On Thu, 25 Feb 2016 11:55:58 +0100
> > Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> >  
> >> On 02/25/2016 03:24 AM, David Rivshin (Allworx) wrote:  
> >>> On Wed, 24 Feb 2016 17:04:58 +0100
> >>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> >>>  
> >>>> Hi David,
> >>>>
> >>>> Thanks for the patch. Very nice driver. I have few comments
> >>>> below.  
> >>>
> >>> Thanks Jacek, I have responded the comments inline. I also wanted to
> >>> double check whether you noticed some questions I had in the cover
> >>> letter [1]. As I mentioned in another email to Rob, in hindsight I'm
> >>> guessing I should have included them in the patch comments as well (or
> >>> instead of).  
> >>
> >> I saw them. I assumed that the review itself will address those
> >> questions.  
> >
> > Fair enough, thanks for the confirmation.
> >  
> >>> Your review comments here effectively answered some of the questions, but
> >>> the big one I'm still unsure of is whether it actually makes sense to
> >>> have all 4 of these devices supported by a single driver.  
> >>
> >> It's perfectly fine. Many drivers implement this pattern.  
> >
> > OK, then I'll assume you think this driver is not yet too complicated
> > for it's own good. Out of curiosity, might that view change if the
> > 3216 specific features were ever implemented, especially GPIO and HW
> > animation support? Gut feel is that would make 3216 specific code
> > bigger than the rest of the code combined.  
> 
> I don't think so.

Thanks, that helps calibrate my intuition for the future.

> > Bigger question is what should be done in terms of the overlap in device
> > support between this driver and leds-sn3218? If you think I should leave
> > the *3218 support in this driver, then I would propose:
> >    - remove leds-sn3218 and its separate binding doc
> >    - add the "si-en,sn3218" compatible string to this driver and binding doc
> > Note that while I expect this driver to work with the 3218 chips, I do
> > not have one to test against. If we go down this route I would definitely
> > want Stefan to test so that I don't accidentally break him.  
> 
> I'd prefer to have a single driver for the same hardware. Stefan, would
> it be possible for you to test David's driver with the hardware you
> have an access to?

Stefan, one thing to note: the existing sn3218 driver/binding uses 0-based 
'reg' values, and this driver/binding uses 1-based 'reg' values. So your
devicetree(s) would need to be updated for that (as well as the compatible
string).

I didn't see a final answer from Rob as to which way is most appropriate 
for these devices yet, so I don't know which way this will end up in the
final patch.

> > Also I feel I should point out some differences between the 3218 support
> > in this driver versus the leds-sn3218 driver, in case they have any
> > impact:
> >    - (as previously mentioned) leds-sn3218 turns off an LEDs enable
> >      bit if the brightness is set to 0. This driver just sets the PWM
> >      to 0 and leaves the enable bits always on.  
> 
> Setting brightness to 0 is an equivalent to turning the device in
> a power down mode or at least in the state where the current consumption
> is as low as possible. A hardware configuration that is most fitting
> for this requirements should be chosen.

As far as I can tell from the datasheets, setting the PWM duty cycle for
a given channel to 0 should have the same net effect as setting the enable
bit of that channel to 0. I assume the purpose of the enable bits is to
make it easier to turn an LED on/off without adjusting the PWM duty cycle,
but just using always the PWM duty cycle register conveniently maps to
the leds API.

> >    - leds-sn3218 uses a regmap, I think mostly to deal with the enable
> >      bits, but it also has the benefit of showing up in debugfs. This
> >      could be seen as useful in and of itself by some users. On the other
> >      hand regmap introduces another mutex on every write.  
> 
> I will not insist on using regmap if you don't refer to the current
> state of hw registers in your driver.

Currently I have not had a need to refer to the current state of any HW
registers. I could imagine that might be needed in the future if extra
functionality is implemented, but it wasn't so far.

> >    - leds-sn3218 implements the shutdown callback. Actually, I think I
> >      should add that to this driver in any event.  
> 
> Do you see use cases or hardware configurations that need such
> a callback? I didn't oppose in case of Stefan's driver, but if we are
> at it, we can consult Stefan if he saw that use case?
> 
> I'd say that shutdown op is usually useful when CPU and LED
> controller are powered from different sources, which can result in
> a situation when CPU is turned off, but LED remains on.

That is exactly what happens today on my board: if the system is rebooted 
or shut down the LEDs all stay in whatever state they were last in. This 
could also be handled by userspace shutdown scripts easily enough, but I 
thought it surprising when the system reboots but LEDs stay on. On the 
other hand someone might have a good reason to want to leave an LED on 
through a reboot. 

There is also an inconsistency on what happens during remove() vs shutdown().
In led_classdev_unregister() brightness is set to LED_OFF, so that happens 
for all drivers on remove(). But only some drivers implement a shutdown() 
which also turns off LEDs, most do not. For instance, leds-gpio turns off 
all LEDs, but leds-pwm does not. 

Is the general policy that LEDs should be forced off by the driver when the 
kernel halts or reboots the CPU? Or left alone and let userspace deal with
it? 
And should this (in principle) be the same as what happens when a module
is unloaded (which currently always turns LEDs off)? 

> >    - leds-sn3218 just puts the chip in software-shutdown mode on remove/
> >      shutdown. This driver uses the reset register to put the device in
> >      poweron state, and software-shutdown is part of the poweron state.
> >      Only difference would be if the next code to use the device does
> >      not do it's own full initialization (which seems unlikely, or at
> >      least unwise), but instead just clears software-shutdown.  
> 
> I believe that my above explanations address this question, i.e.
> both brightness = 0 an remove/shutdown should set the device
> in a power down mode.

I think there is some confusion, there are 3 separate controls:
 - per-LED PWM duty cycle
 - per-LED enable bit
 - device-wide shutdown mode
Shutdown-mode results in all LEDs going dark (regardless of any other
register state), and I think implies that it also uses less power 
(compared to just turning them all off with either of the other controls). 
Registers do retain their value and the I2C interface continues to work. 
I suspect that all it does is turn off an internal oscillator that drives 
the PWM circuits, but the documentation is not clear.

The distinction I was making was that the leds-sn3218 driver *only* turned 
on the Shutdown-mode, while this driver reset all other registers in the 
device to their default values as well. Though in practice I don't expect
that to make a difference.

> >>> I won't
> >>> clutter this email with a duplicate of the details (it's somewhat long),
> >>> but if you could check the cover letter and give some guidance, I would
> >>> appreciate it.
> >>>
> >>> [1] http://www.spinics.net/lists/linux-leds/msg05564.html
> >>>       http://thread.gmane.org/gmane.linux.leds/4530
> >>>  
> >>>>
> >>>> On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:  
> >>>>> From: David Rivshin <drivshin@allworx.com>
> >>>>>
> >>>>> The IS31FL32xx family of LED drivers are I2C devices with multiple
> >>>>> constant-current channels, each with independent 256-level PWM control.
> >>>>>
> >>>>> HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
> >>>>>
> >>>>> This has been tested on the IS31FL3236 and IS31FL3216 on an ARM
> >>>>> (TI am335x) platform.
> >>>>>
> >>>>> The programming paradigm of these devices is similar in the following
> >>>>> ways:
> >>>>>     - All registers are 8 bit
> >>>>>     - All LED control registers are write-only
> >>>>>     - Each LED channel has a PWM register (0-255)
> >>>>>     - PWM register writes are shadowed until an Update register is poked
> >>>>>     - All have a concept of Software Shutdown, which disables output
> >>>>>
> >>>>> However, there are some differences in devices:
> >>>>>     - 3236/3235 have a separate Control register for each LED,
> >>>>>       (3218/3216 pack the enable bits into fewer registers)
> >>>>>     - 3236/3235 have a per-channel current divisor setting
> >>>>>     - 3236/3235 have a Global Control register that can turn off all LEDs
> >>>>>     - 3216 is unique in a number of ways
> >>>>>        - OUT9-OUT16 can be configured as GPIOs instead of LED controls
> >>>>>        - LEDs can be programmed with an 8-frame animation, with
> >>>>>          programmable delay between frames
> >>>>>        - LEDs can be modulated by an input audio signal
> >>>>>        - Max output current can be adjusted from 1/4 to 2x globally
> >>>>>        - Has a Configuration register instead of a Shutdown register
> >>>>>
> >>>>> This driver currently only supports the base PWM control function
> >>>>> of these devices. The following features of these devices are not
> >>>>> implemented, although it should be possible to add them in the future:
> >>>>>     - All devices are capable of going into a lower-power "software
> >>>>>       shutdown" mode.
> >>>>>     - The is31fl3236 and is31fl3235 can reduce the max output current
> >>>>>       per-channel with a divisor of 1, 2, 3, or 4.
> >>>>>     - The is31fl3216 can use some LED channels as GPIOs instead.
> >>>>>     - The is31fl3216 can animate LEDs in hardware.
> >>>>>     - The is31fl3216 can modulate LEDs according to an audio input.
> >>>>>     - The is31fl3216 can reduce/increase max output current globally.
> >>>>>
> >>>>> Signed-off-by: David Rivshin <drivshin@allworx.com>
> >>>>> ---
> >>>>>     drivers/leds/Kconfig           |   9 +
> >>>>>     drivers/leds/Makefile          |   1 +
> >>>>>     drivers/leds/leds-is31fl32xx.c | 442 +++++++++++++++++++++++++++++++++++++++++
> >>>>>     3 files changed, 452 insertions(+)
> >>>>>     create mode 100644 drivers/leds/leds-is31fl32xx.c
> >>>>>
> >>>>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> >>>>> index 1034696..8f6c46f 100644
> >>>>> --- a/drivers/leds/Kconfig
> >>>>> +++ b/drivers/leds/Kconfig
> >>>>> @@ -580,6 +580,15 @@ config LEDS_SN3218
> >>>>>     	  This driver can also be built as a module. If so the module
> >>>>>     	  will be called leds-sn3218.
> >>>>>
> >>>>> +config LEDS_IS31FL32XX
> >>>>> +	tristate "Driver for ISSI IS31FL32XX I2C LED driver chip family"  
> >>>>
> >>>> 2 x "[Dd]river".
> >>>>
> >>>> How about:
> >>>>
> >>>> "LED Support for ISSI IS31FL32XX I2C LED chip family" ?  
> >>>
> >>> Yes, I found that awkward as well. HW folks (and the datasheets) seem
> >>> always refer to devices of this type as "LED Driver"s (which can lead
> >>> to some interesting confusions). Taking a cue from the LP5521/23/62
> >>> entries, how about:
> >>>    "LED Support for the ISSI IS31FL32XX I2C LED driver chip family" ?  
> >>
> >> "LED Support" means "LED class driver". Driver is a software support
> >> for hardware chip. What discrepancy do you see in the description
> >> I proposed?  
> >
> > I think in this case "driver" also means "hardware device which drives
> > a physical LED".  
> 
> Let's not confuse these notions. From Linux perspective "driver" refers
> to a piece of software used for controlling a hardware.
> 
> > It seems that "LED driver" is the term universally used
> > to describe this type of HW device in datasheets.  
> 
> There are also e.g. "LED controllers", "LED current regulators".
> Let's stick to the convention predominantly used in the LED subsystem
> kernel config menu.
> 
> > So it seemed useful to
> > use exactly that phrase in the description of what hardware this software
> > supports. I could see someone interpreting the phrase "LED chip" as
> > referring to an actual LED device.
> > I don't feel very strongly on this topic, but for the sake of discussion,
> > maybe "LED controller" would avoid any possible confusion in both
> > directions?  
> 
> Right, so let's use the following:
> 
> "LED Support for ISSI IS31FL32XX I2C LED controller family"
> 
> I understand "LED Support" as "Linux LED subsystem support".

STGM. Done.

> >>> Perhaps that's the best of both worlds?
> >>>  
> >>>>> +	depends on LEDS_CLASS && I2C && OF
> >>>>> +	help
> >>>>> +	  Say Y here to include support for the ISSI 31FL32XX LED driver family.  
> >>>>
> >>>> s/driver/chip/
> >>>>  
> >>>>> +	  They are I2C devices with multiple constant-current channels, each
> >>>>> +	  with independent 256-level PWM control. This will only work with
> >>>>> +	  device tree enabled devices.  
> >>>>
> >>>> We can skip the last sentence I think.  
> >>>
> >>> OK. FYI, I think I got that verbiage from LEDS_SYSCON.  
> >>
> >> Having "depends on OF" is self-explanatory here.  
> >
> > Noted.
> >  
> >>>>> +
> >>>>>     comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
> >>>>>
> >>>>>     config LEDS_BLINKM
> >>>>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> >>>>> index 89c9b6f..3fdf313 100644
> >>>>> --- a/drivers/leds/Makefile
> >>>>> +++ b/drivers/leds/Makefile
> >>>>> @@ -67,6 +67,7 @@ obj-$(CONFIG_LEDS_KTD2692)		+= leds-ktd2692.o
> >>>>>     obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
> >>>>>     obj-$(CONFIG_LEDS_SEAD3)		+= leds-sead3.o
> >>>>>     obj-$(CONFIG_LEDS_SN3218)		+= leds-sn3218.o
> >>>>> +obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
> >>>>>
> >>>>>     # LED SPI Drivers
> >>>>>     obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
> >>>>> diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
> >>>>> new file mode 100644
> >>>>> index 0000000..8dea518
> >>>>> --- /dev/null
> >>>>> +++ b/drivers/leds/leds-is31fl32xx.c
> >>>>> @@ -0,0 +1,442 @@
> >>>>> +/*
> >>>>> + * linux/drivers/leds-is31fl32xx.c
> >>>>> + *
> >>>>> + * Driver for ISSI IS31FL32xx family of I2C LED controllers
> >>>>> + *
> >>>>> + * Copyright 2015 Allworx Corp.
> >>>>> + *
> >>>>> + *
> >>>>> + * 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.
> >>>>> + *
> >>>>> + * HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
> >>>>> + */
> >>>>> +
> >>>>> +#include <linux/err.h>
> >>>>> +#include <linux/i2c.h>
> >>>>> +#include <linux/kernel.h>
> >>>>> +#include <linux/leds.h>
> >>>>> +#include <linux/module.h>
> >>>>> +#include <linux/of_platform.h>
> >>>>> +
> >>>>> +#ifdef DEBUG
> >>>>> + #undef dev_dbg
> >>>>> + #define dev_dbg dev_info
> >>>>> +#endif  
> >>>>
> >>>> What's the benefit of the above?  
> >>>
> >>> It gave me a way to easily see debug output from the driver while it
> >>> was parsing the DT (especially if the driver was built-in). Early on
> >>> there were other things within that #ifdef as well.
> >>> Regardless, passing ddebug_query on the kernel commandline is a more
> >>> appropriate way of accomplishing that; I'll remove for the next version.  
> >>
> >> Thanks.
> >>  
> >>>>> +/* Used to indicate a device has no such register */
> >>>>> +#define IS31FL32XX_REG_NONE 0xFF
> >>>>> +
> >>>>> +#define IS31FL3216_CONFIG_REG 0x00
> >>>>> +#define IS31FL3216_LIGHTING_EFFECT_REG 0x03
> >>>>> +#define IS31FL3216_CHANNEL_CONFIG_REG 0x04
> >>>>> +
> >>>>> +struct is31fl32xx_priv;
> >>>>> +struct is31fl32xx_led_data {
> >>>>> +	struct led_classdev cdev;
> >>>>> +	u8 channel; /* 1-based, max priv->cdef->channels */
> >>>>> +	struct is31fl32xx_priv *priv;
> >>>>> +};
> >>>>> +
> >>>>> +struct is31fl32xx_priv {
> >>>>> +	const struct is31fl32xx_chipdef *cdef;
> >>>>> +	struct i2c_client *client;
> >>>>> +	unsigned int num_leds;
> >>>>> +	struct is31fl32xx_led_data leds[0];  
> >>>>
> >>>> Is there any specific reason for not having *leds here instead?  
> >>>
> >>> I followed a pattern from leds-pwm where it did a single allocation
> >>> for both priv and priv->leds[]. See sizeof_is31fl32xx_priv(), and
> >>> its use, below. I saw the benefit as one fewer small allocation, so
> >>> slightly more kind to the allocator (and devres). If you'd prefer to
> >>> do it as two allocations, I'll make the change.  
> >>
> >> OK, I had to look at this one more time. I like the idea.  
> >
> > OK, I'll keep it as-is.
> >  
> >>>>> +};
> >>>>> +
> >>>>> +/**
> >>>>> + * struct is31fl32xx_chipdef - chip-specific attributes
> >>>>> + * @channels            : Number of LED channels
> >>>>> + * @shutdown_reg        : address of Shutdown register (optional)
> >>>>> + * @pwm_update_reg      : address of PWM Update register
> >>>>> + * @global_control_reg	: address of Global Control register (optional)
> >>>>> + * @reset_reg           : address of Reset register (optional)
> >>>>> + * @pwm_register_base	: address of first PWM register
> >>>>> + * @pwm_registers_reversed: : true if PWM registers count down instead of up
> >>>>> + * @led_control_register_base : address of first LED control register (optional)
> >>>>> + * @enable_bits_per_led_control_register: number of LEDs enable bits in each
> >>>>> + * @reset_func:         : pointer to reset function
> >>>>> + *
> >>>>> + * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE
> >>>>> + * indicates that this chip has no such register.
> >>>>> + *
> >>>>> + * If non-NULL, @reset_func will be called during probing to set all
> >>>>> + * necessary registers to a known initialization state. This is needed
> >>>>> + * for chips that do not have a @reset_reg.
> >>>>> + *
> >>>>> + * @enable_bits_per_led_control_register must be >=1 if
> >>>>> + * @led_control_register_base != %IS31FL32XX_REG_NONE.
> >>>>> + */
> >>>>> +struct is31fl32xx_chipdef {
> >>>>> +	u8	channels;
> >>>>> +	u8	shutdown_reg;
> >>>>> +	u8	pwm_update_reg;
> >>>>> +	u8	global_control_reg;
> >>>>> +	u8	reset_reg;
> >>>>> +	u8	pwm_register_base;
> >>>>> +	bool	pwm_registers_reversed;
> >>>>> +	u8	led_control_register_base;
> >>>>> +	u8	enable_bits_per_led_control_register;
> >>>>> +	int (*reset_func)(struct is31fl32xx_priv *priv);
> >>>>> +};
> >>>>> +
> >>>>> +static const struct is31fl32xx_chipdef is31fl3236_cdef = {
> >>>>> +	.channels				= 36,
> >>>>> +	.shutdown_reg				= 0x00,
> >>>>> +	.pwm_update_reg				= 0x25,
> >>>>> +	.global_control_reg			= 0x4a,
> >>>>> +	.reset_reg				= 0x4f,
> >>>>> +	.pwm_register_base			= 0x01,
> >>>>> +	.led_control_register_base		= 0x26,
> >>>>> +	.enable_bits_per_led_control_register	= 1,
> >>>>> +};
> >>>>> +
> >>>>> +static const struct is31fl32xx_chipdef is31fl3235_cdef = {
> >>>>> +	.channels				= 28,
> >>>>> +	.shutdown_reg				= 0x00,
> >>>>> +	.pwm_update_reg				= 0x25,
> >>>>> +	.global_control_reg			= 0x4a,
> >>>>> +	.reset_reg				= 0x4f,
> >>>>> +	.pwm_register_base			= 0x05,
> >>>>> +	.led_control_register_base		= 0x2a,
> >>>>> +	.enable_bits_per_led_control_register	= 1,
> >>>>> +};
> >>>>> +
> >>>>> +static const struct is31fl32xx_chipdef is31fl3218_cdef = {
> >>>>> +	.channels				= 18,
> >>>>> +	.shutdown_reg				= 0x00,
> >>>>> +	.pwm_update_reg				= 0x16,
> >>>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
> >>>>> +	.reset_reg				= 0x17,
> >>>>> +	.pwm_register_base			= 0x01,
> >>>>> +	.led_control_register_base		= 0x13,
> >>>>> +	.enable_bits_per_led_control_register	= 6,
> >>>>> +};
> >>>>> +
> >>>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv);
> >>>>> +static const struct is31fl32xx_chipdef is31fl3216_cdef = {
> >>>>> +	.channels				= 16,
> >>>>> +	.shutdown_reg				= IS31FL32XX_REG_NONE,
> >>>>> +	.pwm_update_reg				= 0xB0,
> >>>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
> >>>>> +	.reset_reg				= IS31FL32XX_REG_NONE,
> >>>>> +	.pwm_register_base			= 0x10,
> >>>>> +	.pwm_registers_reversed			= true,
> >>>>> +	.led_control_register_base		= 0x01,
> >>>>> +	.enable_bits_per_led_control_register	= 8,
> >>>>> +	.reset_func				= is31fl3216_reset,
> >>>>> +};
> >>>>> +
> >>>>> +static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)
> >>>>> +{
> >>>>> +	int ret;
> >>>>> +
> >>>>> +	dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
> >>>>> +
> >>>>> +	ret =  i2c_smbus_write_byte_data(priv->client, reg, val);
> >>>>> +	if (ret) {
> >>>>> +		dev_err(&priv->client->dev,
> >>>>> +			"register write to 0x%02X failed (error %d)",
> >>>>> +			reg, ret);
> >>>>> +	}
> >>>>> +	return ret;
> >>>>> +}
> >>>>> +
> >>>>> +/*
> >>>>> + * Custom reset function for IS31FL3216 because it does not have a RESET
> >>>>> + * register the way that the other IS31FL32xx chips do. We don't bother
> >>>>> + * writing the GPIO and animation registers, because the registers we
> >>>>> + * do write ensure those will have no effect.
> >>>>> + */
> >>>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv)
> >>>>> +{
> >>>>> +	unsigned int i;
> >>>>> +	int ret;
> >>>>> +
> >>>>> +	for (i = 0; i < priv->cdef->channels; i++) {
> >>>>> +		ret = is31fl32xx_write(priv, priv->cdef->pwm_register_base+i,
> >>>>> +				       0x00);
> >>>>> +		if (ret)
> >>>>> +			return ret;
> >>>>> +	}
> >>>>> +	ret = is31fl32xx_write(priv, priv->cdef->pwm_update_reg, 0);
> >>>>> +	if (ret)
> >>>>> +		return ret;
> >>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, 0x00);
> >>>>> +	if (ret)
> >>>>> +		return ret;
> >>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, 0x00);
> >>>>> +	if (ret)
> >>>>> +		return ret;
> >>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, 0x00);
> >>>>> +	if (ret)
> >>>>> +		return ret;
> >>>>> +
> >>>>> +	return 0;
> >>>>> +}
> >>>>> +
> >>>>> +
> >>>>> +static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,
> >>>>> +				     enum led_brightness brightness)
> >>>>> +{
> >>>>> +	const struct is31fl32xx_led_data *led_data =
> >>>>> +		container_of(led_cdev, struct is31fl32xx_led_data, cdev);
> >>>>> +	const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef;
> >>>>> +	u8 pwm_register_offset;
> >>>>> +	int ret;
> >>>>> +
> >>>>> +	dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);
> >>>>> +
> >>>>> +	/* NOTE: led_data->channel is 1-based */
> >>>>> +	if (cdef->pwm_registers_reversed)
> >>>>> +		pwm_register_offset = cdef->channels - led_data->channel;
> >>>>> +	else
> >>>>> +		pwm_register_offset = led_data->channel - 1;
> >>>>> +
> >>>>> +	ret = is31fl32xx_write(led_data->priv,
> >>>>> +			       cdef->pwm_register_base + pwm_register_offset,
> >>>>> +			       brightness);
> >>>>> +	if (ret)
> >>>>> +		return ret;  
> >>>>
> >>>> I infer that nothing wrong happens in case current process is preempted
> >>>> here by the call originating from the other sub-LED?  
> >>>
> >>> I do not believe so. All the driver-specific data used here is read-only
> >>> after probing. chipdefs are entirely const, and the only thing in priv
> >>> that's referenced is the chipdef pointer which logically could not change
> >>> post-probe. Actually nothing else in priv is modified post-probe either.
> >>>
> >>> The I2C core code has a mutex on the bus, so two writes cannot happen at
> >>> once.
> >>>
> >>> In all these devices there is a unique PWM duty-cycle register for each
> >>> LED channel (which is what is being written here), so no register writes
> >>> for one LED channel effect any others.
> >>>
> >>> I believe the worst that could happen is that the device would see:
> >>> 	PWM_REG_A write X
> >>> 	PWM_REG_B write Y
> >>> 	UPDATE_REG write 0
> >>> 	UPDATE_REG write 0
> >>> instead of
> >>> 	PWM_REG_A write X
> >>> 	UPDATE_REG write 0
> >>> 	PWM_REG_B write Y
> >>> 	UPDATE_REG write 0
> >>> but that makes no difference to the functionality. Poking the update
> >>> register merely applies all PWM register writes up to that point (I'm
> >>> assuming to allow atomically changing the state of multiple LEDs at
> >>> once).  
> >>
> >> Thanks for this comprehensive explanation.  
> >
> > Should I put some part of this explanation in a comment somewhere? Seems
> > like the kind of thing someone else might wonder about in the future also.  
> 
> Good idea.

Done.

> >>> I should note here (as mentioned in cover letter), I made a choice to
> >>> always leave the per-LED "enable" bits on, and let the PWM just get set
> >>> to 0 naturally to turn an LED off. This differs from the existing SN3218
> >>> driver, which used regmap_update_bits, and is then protected by a per-
> >>> regmap mutex.  
> >>
> >> ack.
> >>  
> >>>>> +	return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);
> >>>>> +
> >>>>> +}
> >>>>> +
> >>>>> +static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
> >>>>> +{
> >>>>> +	const struct is31fl32xx_chipdef *cdef = priv->cdef;
> >>>>> +	int ret;
> >>>>> +
> >>>>> +	if (cdef->reset_reg != IS31FL32XX_REG_NONE) {
> >>>>> +		ret = is31fl32xx_write(priv, cdef->reset_reg, 0);
> >>>>> +		if (ret)
> >>>>> +			return ret;
> >>>>> +	}
> >>>>> +	if (cdef->reset_func) {
> >>>>> +		ret = cdef->reset_func(priv);
> >>>>> +		if (ret)
> >>>>> +			return ret;
> >>>>> +	}
> >>>>> +	if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) {
> >>>>> +		u8 value =
> >>>>> +		    GENMASK(cdef->enable_bits_per_led_control_register-1, 0);
> >>>>> +		u8 num_regs = cdef->channels /
> >>>>> +				cdef->enable_bits_per_led_control_register;
> >>>>> +		int i;
> >>>>> +
> >>>>> +		for (i = 0; i < num_regs; i++) {
> >>>>> +			ret = is31fl32xx_write(priv,
> >>>>> +					       cdef->led_control_register_base+i,
> >>>>> +					       value);
> >>>>> +			if (ret)
> >>>>> +				return ret;
> >>>>> +		}
> >>>>> +	}
> >>>>> +	if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) {
> >>>>> +		ret = is31fl32xx_write(priv, cdef->shutdown_reg, BIT(0));
> >>>>> +		if (ret)
> >>>>> +			return ret;
> >>>>> +	}
> >>>>> +	if (cdef->global_control_reg != IS31FL32XX_REG_NONE) {
> >>>>> +		ret = is31fl32xx_write(priv, cdef->global_control_reg, 0x00);
> >>>>> +		if (ret)
> >>>>> +			return ret;
> >>>>> +	}
> >>>>> +
> >>>>> +	return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static inline size_t sizeof_is31fl32xx_priv(int num_leds)
> >>>>> +{
> >>>>> +	return sizeof(struct is31fl32xx_priv) +
> >>>>> +		      (sizeof(struct is31fl32xx_led_data) * num_leds);
> >>>>> +}
> >>>>> +
> >>>>> +static int is31fl32xx_parse_child_dt(const struct device *dev,
> >>>>> +				     const struct device_node *child,
> >>>>> +				     struct is31fl32xx_led_data *led_data)
> >>>>> +{
> >>>>> +	struct led_classdev *cdev = &led_data->cdev;
> >>>>> +	int ret = 0;
> >>>>> +	u32 reg;
> >>>>> +
> >>>>> +	cdev->name = of_get_property(child, "label", NULL) ? : child->name;
> >>>>> +
> >>>>> +	ret = of_property_read_u32(child, "reg", &reg);
> >>>>> +	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
> >>>>> +		dev_err(dev,
> >>>>> +			"Child node %s does not have a valid reg property\n",
> >>>>> +			child->name);
> >>>>> +		return -EINVAL;
> >>>>> +	}
> >>>>> +	led_data->channel = reg;
> >>>>> +
> >>>>> +	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
> >>>>> +						NULL);
> >>>>> +	cdev->brightness = LED_OFF;  
> >>>>
> >>>> devm_kzalloc secures that.  
> >>>
> >>> OK, I will remove.
> >>>  
> >>>>> +	ret = of_property_read_u32(child, "max-brightness",
> >>>>> +				   &cdev->max_brightness);
> >>>>> +	if (ret == -EINVAL) {
> >>>>> +		cdev->max_brightness = 255;  
> >>>>
> >>>> s/255/LED_FULL/  
> >>>
> >>> Noted, although (from the patch 2 discussion) max-brightness property is
> >>> removed/replaced, this would go away anyways.
> >>>  
> >>>>> +	} else if (ret) {
> >>>>> +		dev_dbg(dev,
> >>>>> +			"Child node %s has an invalid max-brightness property\n",
> >>>>> +			child->name);
> >>>>> +		return -EINVAL;
> >>>>> +	}
> >>>>> +
> >>>>> +	cdev->brightness_set_blocking = is31fl32xx_brightness_set;  
> >>>>
> >>>> Please add empty line here.  
> >>>
> >>> Done.
> >>>  
> >>>>> +	return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static struct is31fl32xx_led_data *is31fl32xx_find_led_data(
> >>>>> +					struct is31fl32xx_priv *priv,
> >>>>> +					u8 channel)
> >>>>> +{
> >>>>> +	size_t i;
> >>>>> +
> >>>>> +	for (i = 0; i < priv->num_leds; i++) {
> >>>>> +		if (priv->leds[i].channel == channel)
> >>>>> +			return &priv->leds[i];
> >>>>> +	}  
> >>>>
> >>>> Ditto.  
> >>>
> >>> Done.
> >>>  
> >>>>> +	return NULL;
> >>>>> +}
> >>>>> +
> >>>>> +static int is31fl32xx_parse_dt(struct device *dev,
> >>>>> +			       struct is31fl32xx_priv *priv)
> >>>>> +{
> >>>>> +	struct device_node *child;
> >>>>> +
> >>>>> +	for_each_child_of_node(dev->of_node, child) {
> >>>>> +		struct is31fl32xx_led_data *led_data =
> >>>>> +			&priv->leds[priv->num_leds];
> >>>>> +		int ret = 0;
> >>>>> +		const struct is31fl32xx_led_data *other_led_data;
> >>>>> +
> >>>>> +		led_data->priv = priv;
> >>>>> +
> >>>>> +		ret = is31fl32xx_parse_child_dt(dev, child, led_data);
> >>>>> +		if (ret)
> >>>>> +			continue;  
> >>>>
> >>>> I prefer failing in such cases,  
> >>>
> >>> OK, I will change to an 'goto err' which will have an 'of_node_put()'
> >>> and 'return ret'.
> >>>
> >>> I will say, however, that while testing the error-detection in the
> >>> parsing logic, it was very convenient to construct a single devicetree
> >>> with a variety of errors. Then a single boot would test multiple
> >>> cases at once.  
> >>
> >> Good idea for testing, but in case some failure occurs during DT child
> >> node parsing in the release environment you're left with unused
> >> allocated memory.  
> >
> > Agreed. BTW, I assume from this that it's common to say "if there's
> > anything wrong in one part of your DT, there is no guarantee as to what
> > parts will actually be used"? I say this because what we're saying is that
> > if one LED node on this device is faulty, that all of them are ignored.
> > Analogy might be to a whole I2C bus being ignored because one of the
> > devices on it failed to probe. To the devicetree it's still a parent/child
> > bus/address relationship, even though the driver implementation is
> > very different.  
> 
> I2C example is too generic I think. I2C controller is not as tightly
> coupled with I2C clients as LED controller with its current outputs.
> Besides, I2C controller doesn't have an idea what devices will attach
> to the bus it controls upon probing, contrarily to a LED controller.

OK. I realize that in code there is a large distinction in these cases, 
but I wasn't sure if that would be reflected in how errors in parsing 
the devicetree should handled. Sounds like there is at least a de-facto
distinction between "a device and its children" and "a bus and its 
children".

> >>>>> +
> >>>>> +		/* Detect if channel is already in use by another child */
> >>>>> +		other_led_data = is31fl32xx_find_led_data(priv,
> >>>>> +							  led_data->channel);
> >>>>> +		if (other_led_data) {
> >>>>> +			dev_err(dev,
> >>>>> +				"%s ignored: channel %d already used by %s",
> >>>>> +				led_data->cdev.name,
> >>>>> +				led_data->channel,
> >>>>> +				other_led_data->cdev.name);
> >>>>> +			continue;  
> >>>>
> >>>> Ditto.  
> >>>
> >>> OK.
> >>>  
> >>>>> +		}
> >>>>> +
> >>>>> +		ret = devm_led_classdev_register(dev, &led_data->cdev);
> >>>>> +		if (ret == 0) {
> >>>>> +			priv->num_leds++;
> >>>>> +		} else {
> >>>>> +			dev_err(dev, "failed to register PWM led for %s: %d\n",
> >>>>> +				led_data->cdev.name, ret);  
> >>>
> >>> Should I also fail here, then? Right now it will continue trying to
> >>> register future LED devices if a classdev_register fails for some
> >>> reason, and will successfully load even if all of them fail.  
> >>
> >> Please fail here too. If we can't setup the sub-LED that is advertised
> >> in a DT child node, then it means that something went wrong.
> >> This is clear error case.  
> >
> > Done.
> >  
> >>>>> +		}
> >>>>> +	}
> >>>>> +
> >>>>> +	return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static const struct of_device_id of_is31fl31xx_match[] = {
> >>>>> +	{ .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },
> >>>>> +	{ .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },
> >>>>> +	{ .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, },
> >>>>> +	{ .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, },
> >>>>> +	{},
> >>>>> +};
> >>>>> +
> >>>>> +MODULE_DEVICE_TABLE(of, of_is31fl31xx_match);
> >>>>> +
> >>>>> +static int is31fl32xx_probe(struct i2c_client *client,
> >>>>> +				  const struct i2c_device_id *id)
> >>>>> +{
> >>>>> +	const struct is31fl32xx_chipdef *cdef;
> >>>>> +	const struct of_device_id *of_dev_id;
> >>>>> +	struct device *dev = &client->dev;
> >>>>> +	struct is31fl32xx_priv *priv;
> >>>>> +	int count;
> >>>>> +	int ret = 0;
> >>>>> +
> >>>>> +	of_dev_id = of_match_device(of_is31fl31xx_match, dev);
> >>>>> +	if (!of_dev_id)
> >>>>> +		return -EINVAL;
> >>>>> +
> >>>>> +	cdef = of_dev_id->data;
> >>>>> +
> >>>>> +	count = of_get_child_count(dev->of_node);
> >>>>> +	if (!count)
> >>>>> +		return -EINVAL;
> >>>>> +
> >>>>> +	priv = devm_kzalloc(dev, sizeof_is31fl32xx_priv(count),
> >>>>> +			    GFP_KERNEL);
> >>>>> +	if (!priv)
> >>>>> +		return -ENOMEM;
> >>>>> +
> >>>>> +	priv->client = client;
> >>>>> +	priv->cdef = cdef;
> >>>>> +	i2c_set_clientdata(client, priv);
> >>>>> +
> >>>>> +	ret = is31fl32xx_init_regs(priv);
> >>>>> +	if (ret)
> >>>>> +		return ret;
> >>>>> +
> >>>>> +	ret = is31fl32xx_parse_dt(dev, priv);
> >>>>> +	if (ret)
> >>>>> +		return ret;
> >>>>> +
> >>>>> +	return 0;
> >>>>> +}
> >>>>> +
> >>>>> +static int is31fl32xx_remove(struct i2c_client *client)
> >>>>> +{
> >>>>> +	struct is31fl32xx_priv *priv = i2c_get_clientdata(client);
> >>>>> +
> >>>>> +	/* If there is a reset reg, then it does everything we need */
> >>>>> +	if (priv->cdef->reset_reg != IS31FL32XX_REG_NONE)
> >>>>> +		return is31fl32xx_write(priv, priv->cdef->reset_reg, 0);
> >>>>> +
> >>>>> +	/* If there is a reset func, then it does everything we need */
> >>>>> +	if (priv->cdef->reset_func)
> >>>>> +		return priv->cdef->reset_func(priv);
> >>>>> +
> >>>>> +	/* If we can't reset, then try just using software-shutdown mode */
> >>>>> +	if (priv->cdef->shutdown_reg != IS31FL32XX_REG_NONE)
> >>>>> +		return is31fl32xx_write(priv, priv->cdef->shutdown_reg, 0x00);
> >>>>> +
> >>>>> +	return 0;
> >>>>> +}
> >>>>> +
> >>>>> +/*
> >>>>> + * i2c-core requires that id_table be non-NULL, even though
> >>>>> + * it is not used for DeviceTree based instantiation.
> >>>>> + */
> >>>>> +static const struct i2c_device_id is31fl31xx_id[] = {
> >>>>> +	{},
> >>>>> +};
> >>>>> +
> >>>>> +MODULE_DEVICE_TABLE(i2c, is31fl31xx_id);
> >>>>> +
> >>>>> +static struct i2c_driver is31fl32xx_driver = {
> >>>>> +	.driver = {
> >>>>> +		.name	= "is31fl32xx",
> >>>>> +		.of_match_table = of_is31fl31xx_match,
> >>>>> +	},
> >>>>> +	.probe		= is31fl32xx_probe,
> >>>>> +	.remove		= is31fl32xx_remove,
> >>>>> +	.id_table	= is31fl31xx_id,
> >>>>> +};
> >>>>> +
> >>>>> +module_i2c_driver(is31fl32xx_driver);
> >>>>> +
> >>>>> +MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>");
> >>>>> +MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver");
> >>>>> +MODULE_LICENSE("GPL v2");
> >>>>>  

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-26 21:58             ` David Rivshin (Allworx)
@ 2016-02-27 10:48               ` Stefan Wahren
  2016-02-29 18:02                 ` David Rivshin (Allworx)
  2016-02-29  9:47               ` Jacek Anaszewski
  1 sibling, 1 reply; 33+ messages in thread
From: Stefan Wahren @ 2016-02-27 10:48 UTC (permalink / raw)
  To: Jacek Anaszewski, David Rivshin (Allworx)
  Cc: Pawel Moll, Rob Herring, Ian Campbell, Kumar Gala, linux-leds,
	Richard Purdie, Mark Rutland, devicetree

Hi David,

> "David Rivshin (Allworx)" <drivshin.allworx@gmail.com> hat am 26. Februar 2016
> um 22:58 geschrieben:
>
>
> On Fri, 26 Feb 2016 10:47:46 +0100
> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>
> > On 02/25/2016 08:12 PM, David Rivshin (Allworx) wrote:
> > > On Thu, 25 Feb 2016 11:55:58 +0100
> > > Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> > >
> > >> On 02/25/2016 03:24 AM, David Rivshin (Allworx) wrote:
> > >>> On Wed, 24 Feb 2016 17:04:58 +0100
> > >>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> > >>>
> > >>>> Hi David,
> > >>>>
> > >>>> Thanks for the patch. Very nice driver. I have few comments
> > >>>> below.
> > >>>
> > >>> Thanks Jacek, I have responded the comments inline. I also wanted to
> > >>> double check whether you noticed some questions I had in the cover
> > >>> letter [1]. As I mentioned in another email to Rob, in hindsight I'm
> > >>> guessing I should have included them in the patch comments as well (or
> > >>> instead of).
> > >>
> > >> I saw them. I assumed that the review itself will address those
> > >> questions.
> > >
> > > Fair enough, thanks for the confirmation.
> > >
> > >>> Your review comments here effectively answered some of the questions,
> > >>> but
> > >>> the big one I'm still unsure of is whether it actually makes sense to
> > >>> have all 4 of these devices supported by a single driver.
> > >>
> > >> It's perfectly fine. Many drivers implement this pattern.
> > >
> > > OK, then I'll assume you think this driver is not yet too complicated
> > > for it's own good. Out of curiosity, might that view change if the
> > > 3216 specific features were ever implemented, especially GPIO and HW
> > > animation support? Gut feel is that would make 3216 specific code
> > > bigger than the rest of the code combined.
> >
> > I don't think so.
>
> Thanks, that helps calibrate my intuition for the future.
>
> > > Bigger question is what should be done in terms of the overlap in device
> > > support between this driver and leds-sn3218? If you think I should leave
> > > the *3218 support in this driver, then I would propose:
> > > - remove leds-sn3218 and its separate binding doc
> > > - add the "si-en,sn3218" compatible string to this driver and binding doc
> > > Note that while I expect this driver to work with the 3218 chips, I do
> > > not have one to test against. If we go down this route I would definitely
> > > want Stefan to test so that I don't accidentally break him.
> >
> > I'd prefer to have a single driver for the same hardware. Stefan, would
> > it be possible for you to test David's driver with the hardware you
> > have an access to?
>
> Stefan, one thing to note: the existing sn3218 driver/binding uses 0-based
> 'reg' values, and this driver/binding uses 1-based 'reg' values. So your
> devicetree(s) would need to be updated for that (as well as the compatible
> string).
>
> I didn't see a final answer from Rob as to which way is most appropriate
> for these devices yet, so I don't know which way this will end up in the
> final patch.

unfortunately i'm very busy. Yes, i will test it, but i can't promise when.

Should i apply this version or wait for the next?

Best regards

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-26 21:58             ` David Rivshin (Allworx)
  2016-02-27 10:48               ` Stefan Wahren
@ 2016-02-29  9:47               ` Jacek Anaszewski
  2016-02-29 18:26                 ` David Rivshin (Allworx)
  1 sibling, 1 reply; 33+ messages in thread
From: Jacek Anaszewski @ 2016-02-29  9:47 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: Stefan Wahren, linux-leds, devicetree, Richard Purdie,
	Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala

On 02/26/2016 10:58 PM, David Rivshin (Allworx) wrote:
> On Fri, 26 Feb 2016 10:47:46 +0100
> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>
>> On 02/25/2016 08:12 PM, David Rivshin (Allworx) wrote:
>>> On Thu, 25 Feb 2016 11:55:58 +0100
>>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>>>
>>>> On 02/25/2016 03:24 AM, David Rivshin (Allworx) wrote:
>>>>> On Wed, 24 Feb 2016 17:04:58 +0100
>>>>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>>>>>
>>>>>> Hi David,
>>>>>>
>>>>>> Thanks for the patch. Very nice driver. I have few comments
>>>>>> below.
>>>>>
>>>>> Thanks Jacek, I have responded the comments inline. I also wanted to
>>>>> double check whether you noticed some questions I had in the cover
>>>>> letter [1]. As I mentioned in another email to Rob, in hindsight I'm
>>>>> guessing I should have included them in the patch comments as well (or
>>>>> instead of).
>>>>
>>>> I saw them. I assumed that the review itself will address those
>>>> questions.
>>>
>>> Fair enough, thanks for the confirmation.
>>>
>>>>> Your review comments here effectively answered some of the questions, but
>>>>> the big one I'm still unsure of is whether it actually makes sense to
>>>>> have all 4 of these devices supported by a single driver.
>>>>
>>>> It's perfectly fine. Many drivers implement this pattern.
>>>
>>> OK, then I'll assume you think this driver is not yet too complicated
>>> for it's own good. Out of curiosity, might that view change if the
>>> 3216 specific features were ever implemented, especially GPIO and HW
>>> animation support? Gut feel is that would make 3216 specific code
>>> bigger than the rest of the code combined.
>>
>> I don't think so.
>
> Thanks, that helps calibrate my intuition for the future.
>
>>> Bigger question is what should be done in terms of the overlap in device
>>> support between this driver and leds-sn3218? If you think I should leave
>>> the *3218 support in this driver, then I would propose:
>>>     - remove leds-sn3218 and its separate binding doc
>>>     - add the "si-en,sn3218" compatible string to this driver and binding doc
>>> Note that while I expect this driver to work with the 3218 chips, I do
>>> not have one to test against. If we go down this route I would definitely
>>> want Stefan to test so that I don't accidentally break him.
>>
>> I'd prefer to have a single driver for the same hardware. Stefan, would
>> it be possible for you to test David's driver with the hardware you
>> have an access to?
>
> Stefan, one thing to note: the existing sn3218 driver/binding uses 0-based
> 'reg' values, and this driver/binding uses 1-based 'reg' values. So your
> devicetree(s) would need to be updated for that (as well as the compatible
> string).

Actually, If your driver can successfully handle Si-En SN3218 I'd prefer
to drop leds-sn3218 along with its bindings and add related compatible
to your bindings documentation.

> I didn't see a final answer from Rob as to which way is most appropriate
> for these devices yet, so I don't know which way this will end up in the
> final patch.
>
>>> Also I feel I should point out some differences between the 3218 support
>>> in this driver versus the leds-sn3218 driver, in case they have any
>>> impact:
>>>     - (as previously mentioned) leds-sn3218 turns off an LEDs enable
>>>       bit if the brightness is set to 0. This driver just sets the PWM
>>>       to 0 and leaves the enable bits always on.
>>
>> Setting brightness to 0 is an equivalent to turning the device in
>> a power down mode or at least in the state where the current consumption
>> is as low as possible. A hardware configuration that is most fitting
>> for this requirements should be chosen.
>
> As far as I can tell from the datasheets, setting the PWM duty cycle for
> a given channel to 0 should have the same net effect as setting the enable
> bit of that channel to 0. I assume the purpose of the enable bits is to
> make it easier to turn an LED on/off without adjusting the PWM duty cycle,
> but just using always the PWM duty cycle register conveniently maps to
> the leds API.

ack.

>>>     - leds-sn3218 uses a regmap, I think mostly to deal with the enable
>>>       bits, but it also has the benefit of showing up in debugfs. This
>>>       could be seen as useful in and of itself by some users. On the other
>>>       hand regmap introduces another mutex on every write.
>>
>> I will not insist on using regmap if you don't refer to the current
>> state of hw registers in your driver.
>
> Currently I have not had a need to refer to the current state of any HW
> registers. I could imagine that might be needed in the future if extra
> functionality is implemented, but it wasn't so far.

Regmap exposes nice debugfs interface, so this, and the fact that there
are uncovered hw features, can be thought of as a sufficient
argument in favour of using regmap even now. But it's up to you.

>>>     - leds-sn3218 implements the shutdown callback. Actually, I think I
>>>       should add that to this driver in any event.
>>
>> Do you see use cases or hardware configurations that need such
>> a callback? I didn't oppose in case of Stefan's driver, but if we are
>> at it, we can consult Stefan if he saw that use case?
>>
>> I'd say that shutdown op is usually useful when CPU and LED
>> controller are powered from different sources, which can result in
>> a situation when CPU is turned off, but LED remains on.
>
> That is exactly what happens today on my board: if the system is rebooted
> or shut down the LEDs all stay in whatever state they were last in. This
> could also be handled by userspace shutdown scripts easily enough, but I
> thought it surprising when the system reboots but LEDs stay on.

That's the shutdown callback is for.

> On the
> other hand someone might have a good reason to want to leave an LED on
> through a reboot.

If you have such a use case, then you can add DT property for this.
E.g. retain-state-on-shutdown.

> There is also an inconsistency on what happens during remove() vs shutdown().
> In led_classdev_unregister() brightness is set to LED_OFF, so that happens
> for all drivers on remove(). But only some drivers implement a shutdown()
> which also turns off LEDs, most do not. For instance, leds-gpio turns off
> all LEDs, but leds-pwm does not.
 >
> Is the general policy that LEDs should be forced off by the driver when the
> kernel halts or reboots the CPU? Or left alone and let userspace deal with
> it?

I think that this depends on use cases and hardware configurations
available on the market. People added the callback when they needed it.
There is no defined policy for this.

> And should this (in principle) be the same as what happens when a module
> is unloaded (which currently always turns LEDs off)?

Turning the LED off on removal is logically justified IMO.

>>>     - leds-sn3218 just puts the chip in software-shutdown mode on remove/
>>>       shutdown. This driver uses the reset register to put the device in
>>>       poweron state, and software-shutdown is part of the poweron state.
>>>       Only difference would be if the next code to use the device does
>>>       not do it's own full initialization (which seems unlikely, or at
>>>       least unwise), but instead just clears software-shutdown.
>>
>> I believe that my above explanations address this question, i.e.
>> both brightness = 0 an remove/shutdown should set the device
>> in a power down mode.
>
> I think there is some confusion, there are 3 separate controls:
>   - per-LED PWM duty cycle
>   - per-LED enable bit
>   - device-wide shutdown mode
> Shutdown-mode results in all LEDs going dark (regardless of any other
> register state), and I think implies that it also uses less power
> (compared to just turning them all off with either of the other controls).
> Registers do retain their value and the I2C interface continues to work.
> I suspect that all it does is turn off an internal oscillator that drives
> the PWM circuits, but the documentation is not clear.
>
> The distinction I was making was that the leds-sn3218 driver *only* turned
> on the Shutdown-mode, while this driver reset all other registers in the
> device to their default values as well. Though in practice I don't expect
> that to make a difference.

If documentation isn't clear about that, you can always measure current
consumption in both cases. Note, that this is not required, you can
follow your intuition.

>>>>> I won't
>>>>> clutter this email with a duplicate of the details (it's somewhat long),
>>>>> but if you could check the cover letter and give some guidance, I would
>>>>> appreciate it.
>>>>>
>>>>> [1] http://www.spinics.net/lists/linux-leds/msg05564.html
>>>>>        http://thread.gmane.org/gmane.linux.leds/4530
>>>>>
>>>>>>
>>>>>> On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:
>>>>>>> From: David Rivshin <drivshin@allworx.com>
>>>>>>>
>>>>>>> The IS31FL32xx family of LED drivers are I2C devices with multiple
>>>>>>> constant-current channels, each with independent 256-level PWM control.
>>>>>>>
>>>>>>> HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
>>>>>>>
>>>>>>> This has been tested on the IS31FL3236 and IS31FL3216 on an ARM
>>>>>>> (TI am335x) platform.
>>>>>>>
>>>>>>> The programming paradigm of these devices is similar in the following
>>>>>>> ways:
>>>>>>>      - All registers are 8 bit
>>>>>>>      - All LED control registers are write-only
>>>>>>>      - Each LED channel has a PWM register (0-255)
>>>>>>>      - PWM register writes are shadowed until an Update register is poked
>>>>>>>      - All have a concept of Software Shutdown, which disables output
>>>>>>>
>>>>>>> However, there are some differences in devices:
>>>>>>>      - 3236/3235 have a separate Control register for each LED,
>>>>>>>        (3218/3216 pack the enable bits into fewer registers)
>>>>>>>      - 3236/3235 have a per-channel current divisor setting
>>>>>>>      - 3236/3235 have a Global Control register that can turn off all LEDs
>>>>>>>      - 3216 is unique in a number of ways
>>>>>>>         - OUT9-OUT16 can be configured as GPIOs instead of LED controls
>>>>>>>         - LEDs can be programmed with an 8-frame animation, with
>>>>>>>           programmable delay between frames
>>>>>>>         - LEDs can be modulated by an input audio signal
>>>>>>>         - Max output current can be adjusted from 1/4 to 2x globally
>>>>>>>         - Has a Configuration register instead of a Shutdown register
>>>>>>>
>>>>>>> This driver currently only supports the base PWM control function
>>>>>>> of these devices. The following features of these devices are not
>>>>>>> implemented, although it should be possible to add them in the future:
>>>>>>>      - All devices are capable of going into a lower-power "software
>>>>>>>        shutdown" mode.
>>>>>>>      - The is31fl3236 and is31fl3235 can reduce the max output current
>>>>>>>        per-channel with a divisor of 1, 2, 3, or 4.
>>>>>>>      - The is31fl3216 can use some LED channels as GPIOs instead.
>>>>>>>      - The is31fl3216 can animate LEDs in hardware.
>>>>>>>      - The is31fl3216 can modulate LEDs according to an audio input.
>>>>>>>      - The is31fl3216 can reduce/increase max output current globally.
>>>>>>>
>>>>>>> Signed-off-by: David Rivshin <drivshin@allworx.com>
>>>>>>> ---
>>>>>>>      drivers/leds/Kconfig           |   9 +
>>>>>>>      drivers/leds/Makefile          |   1 +
>>>>>>>      drivers/leds/leds-is31fl32xx.c | 442 +++++++++++++++++++++++++++++++++++++++++
>>>>>>>      3 files changed, 452 insertions(+)
>>>>>>>      create mode 100644 drivers/leds/leds-is31fl32xx.c
>>>>>>>
>>>>>>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>>>>>>> index 1034696..8f6c46f 100644
>>>>>>> --- a/drivers/leds/Kconfig
>>>>>>> +++ b/drivers/leds/Kconfig
>>>>>>> @@ -580,6 +580,15 @@ config LEDS_SN3218
>>>>>>>      	  This driver can also be built as a module. If so the module
>>>>>>>      	  will be called leds-sn3218.
>>>>>>>
>>>>>>> +config LEDS_IS31FL32XX
>>>>>>> +	tristate "Driver for ISSI IS31FL32XX I2C LED driver chip family"
>>>>>>
>>>>>> 2 x "[Dd]river".
>>>>>>
>>>>>> How about:
>>>>>>
>>>>>> "LED Support for ISSI IS31FL32XX I2C LED chip family" ?
>>>>>
>>>>> Yes, I found that awkward as well. HW folks (and the datasheets) seem
>>>>> always refer to devices of this type as "LED Driver"s (which can lead
>>>>> to some interesting confusions). Taking a cue from the LP5521/23/62
>>>>> entries, how about:
>>>>>     "LED Support for the ISSI IS31FL32XX I2C LED driver chip family" ?
>>>>
>>>> "LED Support" means "LED class driver". Driver is a software support
>>>> for hardware chip. What discrepancy do you see in the description
>>>> I proposed?
>>>
>>> I think in this case "driver" also means "hardware device which drives
>>> a physical LED".
>>
>> Let's not confuse these notions. From Linux perspective "driver" refers
>> to a piece of software used for controlling a hardware.
>>
>>> It seems that "LED driver" is the term universally used
>>> to describe this type of HW device in datasheets.
>>
>> There are also e.g. "LED controllers", "LED current regulators".
>> Let's stick to the convention predominantly used in the LED subsystem
>> kernel config menu.
>>
>>> So it seemed useful to
>>> use exactly that phrase in the description of what hardware this software
>>> supports. I could see someone interpreting the phrase "LED chip" as
>>> referring to an actual LED device.
>>> I don't feel very strongly on this topic, but for the sake of discussion,
>>> maybe "LED controller" would avoid any possible confusion in both
>>> directions?
>>
>> Right, so let's use the following:
>>
>> "LED Support for ISSI IS31FL32XX I2C LED controller family"
>>
>> I understand "LED Support" as "Linux LED subsystem support".
>
> STGM. Done.
>
>>>>> Perhaps that's the best of both worlds?
>>>>>
>>>>>>> +	depends on LEDS_CLASS && I2C && OF
>>>>>>> +	help
>>>>>>> +	  Say Y here to include support for the ISSI 31FL32XX LED driver family.
>>>>>>
>>>>>> s/driver/chip/
>>>>>>
>>>>>>> +	  They are I2C devices with multiple constant-current channels, each
>>>>>>> +	  with independent 256-level PWM control. This will only work with
>>>>>>> +	  device tree enabled devices.
>>>>>>
>>>>>> We can skip the last sentence I think.
>>>>>
>>>>> OK. FYI, I think I got that verbiage from LEDS_SYSCON.
>>>>
>>>> Having "depends on OF" is self-explanatory here.
>>>
>>> Noted.
>>>
>>>>>>> +
>>>>>>>      comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
>>>>>>>
>>>>>>>      config LEDS_BLINKM
>>>>>>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
>>>>>>> index 89c9b6f..3fdf313 100644
>>>>>>> --- a/drivers/leds/Makefile
>>>>>>> +++ b/drivers/leds/Makefile
>>>>>>> @@ -67,6 +67,7 @@ obj-$(CONFIG_LEDS_KTD2692)		+= leds-ktd2692.o
>>>>>>>      obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
>>>>>>>      obj-$(CONFIG_LEDS_SEAD3)		+= leds-sead3.o
>>>>>>>      obj-$(CONFIG_LEDS_SN3218)		+= leds-sn3218.o
>>>>>>> +obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
>>>>>>>
>>>>>>>      # LED SPI Drivers
>>>>>>>      obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
>>>>>>> diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
>>>>>>> new file mode 100644
>>>>>>> index 0000000..8dea518
>>>>>>> --- /dev/null
>>>>>>> +++ b/drivers/leds/leds-is31fl32xx.c
>>>>>>> @@ -0,0 +1,442 @@
>>>>>>> +/*
>>>>>>> + * linux/drivers/leds-is31fl32xx.c
>>>>>>> + *
>>>>>>> + * Driver for ISSI IS31FL32xx family of I2C LED controllers
>>>>>>> + *
>>>>>>> + * Copyright 2015 Allworx Corp.
>>>>>>> + *
>>>>>>> + *
>>>>>>> + * 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.
>>>>>>> + *
>>>>>>> + * HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
>>>>>>> + */
>>>>>>> +
>>>>>>> +#include <linux/err.h>
>>>>>>> +#include <linux/i2c.h>
>>>>>>> +#include <linux/kernel.h>
>>>>>>> +#include <linux/leds.h>
>>>>>>> +#include <linux/module.h>
>>>>>>> +#include <linux/of_platform.h>
>>>>>>> +
>>>>>>> +#ifdef DEBUG
>>>>>>> + #undef dev_dbg
>>>>>>> + #define dev_dbg dev_info
>>>>>>> +#endif
>>>>>>
>>>>>> What's the benefit of the above?
>>>>>
>>>>> It gave me a way to easily see debug output from the driver while it
>>>>> was parsing the DT (especially if the driver was built-in). Early on
>>>>> there were other things within that #ifdef as well.
>>>>> Regardless, passing ddebug_query on the kernel commandline is a more
>>>>> appropriate way of accomplishing that; I'll remove for the next version.
>>>>
>>>> Thanks.
>>>>
>>>>>>> +/* Used to indicate a device has no such register */
>>>>>>> +#define IS31FL32XX_REG_NONE 0xFF
>>>>>>> +
>>>>>>> +#define IS31FL3216_CONFIG_REG 0x00
>>>>>>> +#define IS31FL3216_LIGHTING_EFFECT_REG 0x03
>>>>>>> +#define IS31FL3216_CHANNEL_CONFIG_REG 0x04
>>>>>>> +
>>>>>>> +struct is31fl32xx_priv;
>>>>>>> +struct is31fl32xx_led_data {
>>>>>>> +	struct led_classdev cdev;
>>>>>>> +	u8 channel; /* 1-based, max priv->cdef->channels */
>>>>>>> +	struct is31fl32xx_priv *priv;
>>>>>>> +};
>>>>>>> +
>>>>>>> +struct is31fl32xx_priv {
>>>>>>> +	const struct is31fl32xx_chipdef *cdef;
>>>>>>> +	struct i2c_client *client;
>>>>>>> +	unsigned int num_leds;
>>>>>>> +	struct is31fl32xx_led_data leds[0];
>>>>>>
>>>>>> Is there any specific reason for not having *leds here instead?
>>>>>
>>>>> I followed a pattern from leds-pwm where it did a single allocation
>>>>> for both priv and priv->leds[]. See sizeof_is31fl32xx_priv(), and
>>>>> its use, below. I saw the benefit as one fewer small allocation, so
>>>>> slightly more kind to the allocator (and devres). If you'd prefer to
>>>>> do it as two allocations, I'll make the change.
>>>>
>>>> OK, I had to look at this one more time. I like the idea.
>>>
>>> OK, I'll keep it as-is.
>>>
>>>>>>> +};
>>>>>>> +
>>>>>>> +/**
>>>>>>> + * struct is31fl32xx_chipdef - chip-specific attributes
>>>>>>> + * @channels            : Number of LED channels
>>>>>>> + * @shutdown_reg        : address of Shutdown register (optional)
>>>>>>> + * @pwm_update_reg      : address of PWM Update register
>>>>>>> + * @global_control_reg	: address of Global Control register (optional)
>>>>>>> + * @reset_reg           : address of Reset register (optional)
>>>>>>> + * @pwm_register_base	: address of first PWM register
>>>>>>> + * @pwm_registers_reversed: : true if PWM registers count down instead of up
>>>>>>> + * @led_control_register_base : address of first LED control register (optional)
>>>>>>> + * @enable_bits_per_led_control_register: number of LEDs enable bits in each
>>>>>>> + * @reset_func:         : pointer to reset function
>>>>>>> + *
>>>>>>> + * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE
>>>>>>> + * indicates that this chip has no such register.
>>>>>>> + *
>>>>>>> + * If non-NULL, @reset_func will be called during probing to set all
>>>>>>> + * necessary registers to a known initialization state. This is needed
>>>>>>> + * for chips that do not have a @reset_reg.
>>>>>>> + *
>>>>>>> + * @enable_bits_per_led_control_register must be >=1 if
>>>>>>> + * @led_control_register_base != %IS31FL32XX_REG_NONE.
>>>>>>> + */
>>>>>>> +struct is31fl32xx_chipdef {
>>>>>>> +	u8	channels;
>>>>>>> +	u8	shutdown_reg;
>>>>>>> +	u8	pwm_update_reg;
>>>>>>> +	u8	global_control_reg;
>>>>>>> +	u8	reset_reg;
>>>>>>> +	u8	pwm_register_base;
>>>>>>> +	bool	pwm_registers_reversed;
>>>>>>> +	u8	led_control_register_base;
>>>>>>> +	u8	enable_bits_per_led_control_register;
>>>>>>> +	int (*reset_func)(struct is31fl32xx_priv *priv);
>>>>>>> +};
>>>>>>> +
>>>>>>> +static const struct is31fl32xx_chipdef is31fl3236_cdef = {
>>>>>>> +	.channels				= 36,
>>>>>>> +	.shutdown_reg				= 0x00,
>>>>>>> +	.pwm_update_reg				= 0x25,
>>>>>>> +	.global_control_reg			= 0x4a,
>>>>>>> +	.reset_reg				= 0x4f,
>>>>>>> +	.pwm_register_base			= 0x01,
>>>>>>> +	.led_control_register_base		= 0x26,
>>>>>>> +	.enable_bits_per_led_control_register	= 1,
>>>>>>> +};
>>>>>>> +
>>>>>>> +static const struct is31fl32xx_chipdef is31fl3235_cdef = {
>>>>>>> +	.channels				= 28,
>>>>>>> +	.shutdown_reg				= 0x00,
>>>>>>> +	.pwm_update_reg				= 0x25,
>>>>>>> +	.global_control_reg			= 0x4a,
>>>>>>> +	.reset_reg				= 0x4f,
>>>>>>> +	.pwm_register_base			= 0x05,
>>>>>>> +	.led_control_register_base		= 0x2a,
>>>>>>> +	.enable_bits_per_led_control_register	= 1,
>>>>>>> +};
>>>>>>> +
>>>>>>> +static const struct is31fl32xx_chipdef is31fl3218_cdef = {
>>>>>>> +	.channels				= 18,
>>>>>>> +	.shutdown_reg				= 0x00,
>>>>>>> +	.pwm_update_reg				= 0x16,
>>>>>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
>>>>>>> +	.reset_reg				= 0x17,
>>>>>>> +	.pwm_register_base			= 0x01,
>>>>>>> +	.led_control_register_base		= 0x13,
>>>>>>> +	.enable_bits_per_led_control_register	= 6,
>>>>>>> +};
>>>>>>> +
>>>>>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv);
>>>>>>> +static const struct is31fl32xx_chipdef is31fl3216_cdef = {
>>>>>>> +	.channels				= 16,
>>>>>>> +	.shutdown_reg				= IS31FL32XX_REG_NONE,
>>>>>>> +	.pwm_update_reg				= 0xB0,
>>>>>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
>>>>>>> +	.reset_reg				= IS31FL32XX_REG_NONE,
>>>>>>> +	.pwm_register_base			= 0x10,
>>>>>>> +	.pwm_registers_reversed			= true,
>>>>>>> +	.led_control_register_base		= 0x01,
>>>>>>> +	.enable_bits_per_led_control_register	= 8,
>>>>>>> +	.reset_func				= is31fl3216_reset,
>>>>>>> +};
>>>>>>> +
>>>>>>> +static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)
>>>>>>> +{
>>>>>>> +	int ret;
>>>>>>> +
>>>>>>> +	dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
>>>>>>> +
>>>>>>> +	ret =  i2c_smbus_write_byte_data(priv->client, reg, val);
>>>>>>> +	if (ret) {
>>>>>>> +		dev_err(&priv->client->dev,
>>>>>>> +			"register write to 0x%02X failed (error %d)",
>>>>>>> +			reg, ret);
>>>>>>> +	}
>>>>>>> +	return ret;
>>>>>>> +}
>>>>>>> +
>>>>>>> +/*
>>>>>>> + * Custom reset function for IS31FL3216 because it does not have a RESET
>>>>>>> + * register the way that the other IS31FL32xx chips do. We don't bother
>>>>>>> + * writing the GPIO and animation registers, because the registers we
>>>>>>> + * do write ensure those will have no effect.
>>>>>>> + */
>>>>>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv)
>>>>>>> +{
>>>>>>> +	unsigned int i;
>>>>>>> +	int ret;
>>>>>>> +
>>>>>>> +	for (i = 0; i < priv->cdef->channels; i++) {
>>>>>>> +		ret = is31fl32xx_write(priv, priv->cdef->pwm_register_base+i,
>>>>>>> +				       0x00);
>>>>>>> +		if (ret)
>>>>>>> +			return ret;
>>>>>>> +	}
>>>>>>> +	ret = is31fl32xx_write(priv, priv->cdef->pwm_update_reg, 0);
>>>>>>> +	if (ret)
>>>>>>> +		return ret;
>>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, 0x00);
>>>>>>> +	if (ret)
>>>>>>> +		return ret;
>>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, 0x00);
>>>>>>> +	if (ret)
>>>>>>> +		return ret;
>>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, 0x00);
>>>>>>> +	if (ret)
>>>>>>> +		return ret;
>>>>>>> +
>>>>>>> +	return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> +
>>>>>>> +static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,
>>>>>>> +				     enum led_brightness brightness)
>>>>>>> +{
>>>>>>> +	const struct is31fl32xx_led_data *led_data =
>>>>>>> +		container_of(led_cdev, struct is31fl32xx_led_data, cdev);
>>>>>>> +	const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef;
>>>>>>> +	u8 pwm_register_offset;
>>>>>>> +	int ret;
>>>>>>> +
>>>>>>> +	dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);
>>>>>>> +
>>>>>>> +	/* NOTE: led_data->channel is 1-based */
>>>>>>> +	if (cdef->pwm_registers_reversed)
>>>>>>> +		pwm_register_offset = cdef->channels - led_data->channel;
>>>>>>> +	else
>>>>>>> +		pwm_register_offset = led_data->channel - 1;
>>>>>>> +
>>>>>>> +	ret = is31fl32xx_write(led_data->priv,
>>>>>>> +			       cdef->pwm_register_base + pwm_register_offset,
>>>>>>> +			       brightness);
>>>>>>> +	if (ret)
>>>>>>> +		return ret;
>>>>>>
>>>>>> I infer that nothing wrong happens in case current process is preempted
>>>>>> here by the call originating from the other sub-LED?
>>>>>
>>>>> I do not believe so. All the driver-specific data used here is read-only
>>>>> after probing. chipdefs are entirely const, and the only thing in priv
>>>>> that's referenced is the chipdef pointer which logically could not change
>>>>> post-probe. Actually nothing else in priv is modified post-probe either.
>>>>>
>>>>> The I2C core code has a mutex on the bus, so two writes cannot happen at
>>>>> once.
>>>>>
>>>>> In all these devices there is a unique PWM duty-cycle register for each
>>>>> LED channel (which is what is being written here), so no register writes
>>>>> for one LED channel effect any others.
>>>>>
>>>>> I believe the worst that could happen is that the device would see:
>>>>> 	PWM_REG_A write X
>>>>> 	PWM_REG_B write Y
>>>>> 	UPDATE_REG write 0
>>>>> 	UPDATE_REG write 0
>>>>> instead of
>>>>> 	PWM_REG_A write X
>>>>> 	UPDATE_REG write 0
>>>>> 	PWM_REG_B write Y
>>>>> 	UPDATE_REG write 0
>>>>> but that makes no difference to the functionality. Poking the update
>>>>> register merely applies all PWM register writes up to that point (I'm
>>>>> assuming to allow atomically changing the state of multiple LEDs at
>>>>> once).
>>>>
>>>> Thanks for this comprehensive explanation.
>>>
>>> Should I put some part of this explanation in a comment somewhere? Seems
>>> like the kind of thing someone else might wonder about in the future also.
>>
>> Good idea.
>
> Done.
>
>>>>> I should note here (as mentioned in cover letter), I made a choice to
>>>>> always leave the per-LED "enable" bits on, and let the PWM just get set
>>>>> to 0 naturally to turn an LED off. This differs from the existing SN3218
>>>>> driver, which used regmap_update_bits, and is then protected by a per-
>>>>> regmap mutex.
>>>>
>>>> ack.
>>>>
>>>>>>> +	return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);
>>>>>>> +
>>>>>>> +}
>>>>>>> +
>>>>>>> +static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
>>>>>>> +{
>>>>>>> +	const struct is31fl32xx_chipdef *cdef = priv->cdef;
>>>>>>> +	int ret;
>>>>>>> +
>>>>>>> +	if (cdef->reset_reg != IS31FL32XX_REG_NONE) {
>>>>>>> +		ret = is31fl32xx_write(priv, cdef->reset_reg, 0);
>>>>>>> +		if (ret)
>>>>>>> +			return ret;
>>>>>>> +	}
>>>>>>> +	if (cdef->reset_func) {
>>>>>>> +		ret = cdef->reset_func(priv);
>>>>>>> +		if (ret)
>>>>>>> +			return ret;
>>>>>>> +	}
>>>>>>> +	if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) {
>>>>>>> +		u8 value =
>>>>>>> +		    GENMASK(cdef->enable_bits_per_led_control_register-1, 0);
>>>>>>> +		u8 num_regs = cdef->channels /
>>>>>>> +				cdef->enable_bits_per_led_control_register;
>>>>>>> +		int i;
>>>>>>> +
>>>>>>> +		for (i = 0; i < num_regs; i++) {
>>>>>>> +			ret = is31fl32xx_write(priv,
>>>>>>> +					       cdef->led_control_register_base+i,
>>>>>>> +					       value);
>>>>>>> +			if (ret)
>>>>>>> +				return ret;
>>>>>>> +		}
>>>>>>> +	}
>>>>>>> +	if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) {
>>>>>>> +		ret = is31fl32xx_write(priv, cdef->shutdown_reg, BIT(0));
>>>>>>> +		if (ret)
>>>>>>> +			return ret;
>>>>>>> +	}
>>>>>>> +	if (cdef->global_control_reg != IS31FL32XX_REG_NONE) {
>>>>>>> +		ret = is31fl32xx_write(priv, cdef->global_control_reg, 0x00);
>>>>>>> +		if (ret)
>>>>>>> +			return ret;
>>>>>>> +	}
>>>>>>> +
>>>>>>> +	return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> +static inline size_t sizeof_is31fl32xx_priv(int num_leds)
>>>>>>> +{
>>>>>>> +	return sizeof(struct is31fl32xx_priv) +
>>>>>>> +		      (sizeof(struct is31fl32xx_led_data) * num_leds);
>>>>>>> +}
>>>>>>> +
>>>>>>> +static int is31fl32xx_parse_child_dt(const struct device *dev,
>>>>>>> +				     const struct device_node *child,
>>>>>>> +				     struct is31fl32xx_led_data *led_data)
>>>>>>> +{
>>>>>>> +	struct led_classdev *cdev = &led_data->cdev;
>>>>>>> +	int ret = 0;
>>>>>>> +	u32 reg;
>>>>>>> +
>>>>>>> +	cdev->name = of_get_property(child, "label", NULL) ? : child->name;
>>>>>>> +
>>>>>>> +	ret = of_property_read_u32(child, "reg", &reg);
>>>>>>> +	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
>>>>>>> +		dev_err(dev,
>>>>>>> +			"Child node %s does not have a valid reg property\n",
>>>>>>> +			child->name);
>>>>>>> +		return -EINVAL;
>>>>>>> +	}
>>>>>>> +	led_data->channel = reg;
>>>>>>> +
>>>>>>> +	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
>>>>>>> +						NULL);
>>>>>>> +	cdev->brightness = LED_OFF;
>>>>>>
>>>>>> devm_kzalloc secures that.
>>>>>
>>>>> OK, I will remove.
>>>>>
>>>>>>> +	ret = of_property_read_u32(child, "max-brightness",
>>>>>>> +				   &cdev->max_brightness);
>>>>>>> +	if (ret == -EINVAL) {
>>>>>>> +		cdev->max_brightness = 255;
>>>>>>
>>>>>> s/255/LED_FULL/
>>>>>
>>>>> Noted, although (from the patch 2 discussion) max-brightness property is
>>>>> removed/replaced, this would go away anyways.
>>>>>
>>>>>>> +	} else if (ret) {
>>>>>>> +		dev_dbg(dev,
>>>>>>> +			"Child node %s has an invalid max-brightness property\n",
>>>>>>> +			child->name);
>>>>>>> +		return -EINVAL;
>>>>>>> +	}
>>>>>>> +
>>>>>>> +	cdev->brightness_set_blocking = is31fl32xx_brightness_set;
>>>>>>
>>>>>> Please add empty line here.
>>>>>
>>>>> Done.
>>>>>
>>>>>>> +	return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> +static struct is31fl32xx_led_data *is31fl32xx_find_led_data(
>>>>>>> +					struct is31fl32xx_priv *priv,
>>>>>>> +					u8 channel)
>>>>>>> +{
>>>>>>> +	size_t i;
>>>>>>> +
>>>>>>> +	for (i = 0; i < priv->num_leds; i++) {
>>>>>>> +		if (priv->leds[i].channel == channel)
>>>>>>> +			return &priv->leds[i];
>>>>>>> +	}
>>>>>>
>>>>>> Ditto.
>>>>>
>>>>> Done.
>>>>>
>>>>>>> +	return NULL;
>>>>>>> +}
>>>>>>> +
>>>>>>> +static int is31fl32xx_parse_dt(struct device *dev,
>>>>>>> +			       struct is31fl32xx_priv *priv)
>>>>>>> +{
>>>>>>> +	struct device_node *child;
>>>>>>> +
>>>>>>> +	for_each_child_of_node(dev->of_node, child) {
>>>>>>> +		struct is31fl32xx_led_data *led_data =
>>>>>>> +			&priv->leds[priv->num_leds];
>>>>>>> +		int ret = 0;
>>>>>>> +		const struct is31fl32xx_led_data *other_led_data;
>>>>>>> +
>>>>>>> +		led_data->priv = priv;
>>>>>>> +
>>>>>>> +		ret = is31fl32xx_parse_child_dt(dev, child, led_data);
>>>>>>> +		if (ret)
>>>>>>> +			continue;
>>>>>>
>>>>>> I prefer failing in such cases,
>>>>>
>>>>> OK, I will change to an 'goto err' which will have an 'of_node_put()'
>>>>> and 'return ret'.
>>>>>
>>>>> I will say, however, that while testing the error-detection in the
>>>>> parsing logic, it was very convenient to construct a single devicetree
>>>>> with a variety of errors. Then a single boot would test multiple
>>>>> cases at once.
>>>>
>>>> Good idea for testing, but in case some failure occurs during DT child
>>>> node parsing in the release environment you're left with unused
>>>> allocated memory.
>>>
>>> Agreed. BTW, I assume from this that it's common to say "if there's
>>> anything wrong in one part of your DT, there is no guarantee as to what
>>> parts will actually be used"? I say this because what we're saying is that
>>> if one LED node on this device is faulty, that all of them are ignored.
>>> Analogy might be to a whole I2C bus being ignored because one of the
>>> devices on it failed to probe. To the devicetree it's still a parent/child
>>> bus/address relationship, even though the driver implementation is
>>> very different.
>>
>> I2C example is too generic I think. I2C controller is not as tightly
>> coupled with I2C clients as LED controller with its current outputs.
>> Besides, I2C controller doesn't have an idea what devices will attach
>> to the bus it controls upon probing, contrarily to a LED controller.
>
> OK. I realize that in code there is a large distinction in these cases,
> but I wasn't sure if that would be reflected in how errors in parsing
> the devicetree should handled. Sounds like there is at least a de-facto
> distinction between "a device and its children" and "a bus and its
> children".
>
>>>>>>> +
>>>>>>> +		/* Detect if channel is already in use by another child */
>>>>>>> +		other_led_data = is31fl32xx_find_led_data(priv,
>>>>>>> +							  led_data->channel);
>>>>>>> +		if (other_led_data) {
>>>>>>> +			dev_err(dev,
>>>>>>> +				"%s ignored: channel %d already used by %s",
>>>>>>> +				led_data->cdev.name,
>>>>>>> +				led_data->channel,
>>>>>>> +				other_led_data->cdev.name);
>>>>>>> +			continue;
>>>>>>
>>>>>> Ditto.
>>>>>
>>>>> OK.
>>>>>
>>>>>>> +		}
>>>>>>> +
>>>>>>> +		ret = devm_led_classdev_register(dev, &led_data->cdev);
>>>>>>> +		if (ret == 0) {
>>>>>>> +			priv->num_leds++;
>>>>>>> +		} else {
>>>>>>> +			dev_err(dev, "failed to register PWM led for %s: %d\n",
>>>>>>> +				led_data->cdev.name, ret);
>>>>>
>>>>> Should I also fail here, then? Right now it will continue trying to
>>>>> register future LED devices if a classdev_register fails for some
>>>>> reason, and will successfully load even if all of them fail.
>>>>
>>>> Please fail here too. If we can't setup the sub-LED that is advertised
>>>> in a DT child node, then it means that something went wrong.
>>>> This is clear error case.
>>>
>>> Done.
>>>
>>>>>>> +		}
>>>>>>> +	}
>>>>>>> +
>>>>>>> +	return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> +static const struct of_device_id of_is31fl31xx_match[] = {
>>>>>>> +	{ .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },
>>>>>>> +	{ .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },
>>>>>>> +	{ .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, },
>>>>>>> +	{ .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, },
>>>>>>> +	{},
>>>>>>> +};
>>>>>>> +
>>>>>>> +MODULE_DEVICE_TABLE(of, of_is31fl31xx_match);
>>>>>>> +
>>>>>>> +static int is31fl32xx_probe(struct i2c_client *client,
>>>>>>> +				  const struct i2c_device_id *id)
>>>>>>> +{
>>>>>>> +	const struct is31fl32xx_chipdef *cdef;
>>>>>>> +	const struct of_device_id *of_dev_id;
>>>>>>> +	struct device *dev = &client->dev;
>>>>>>> +	struct is31fl32xx_priv *priv;
>>>>>>> +	int count;
>>>>>>> +	int ret = 0;
>>>>>>> +
>>>>>>> +	of_dev_id = of_match_device(of_is31fl31xx_match, dev);
>>>>>>> +	if (!of_dev_id)
>>>>>>> +		return -EINVAL;
>>>>>>> +
>>>>>>> +	cdef = of_dev_id->data;
>>>>>>> +
>>>>>>> +	count = of_get_child_count(dev->of_node);
>>>>>>> +	if (!count)
>>>>>>> +		return -EINVAL;
>>>>>>> +
>>>>>>> +	priv = devm_kzalloc(dev, sizeof_is31fl32xx_priv(count),
>>>>>>> +			    GFP_KERNEL);
>>>>>>> +	if (!priv)
>>>>>>> +		return -ENOMEM;
>>>>>>> +
>>>>>>> +	priv->client = client;
>>>>>>> +	priv->cdef = cdef;
>>>>>>> +	i2c_set_clientdata(client, priv);
>>>>>>> +
>>>>>>> +	ret = is31fl32xx_init_regs(priv);
>>>>>>> +	if (ret)
>>>>>>> +		return ret;
>>>>>>> +
>>>>>>> +	ret = is31fl32xx_parse_dt(dev, priv);
>>>>>>> +	if (ret)
>>>>>>> +		return ret;
>>>>>>> +
>>>>>>> +	return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> +static int is31fl32xx_remove(struct i2c_client *client)
>>>>>>> +{
>>>>>>> +	struct is31fl32xx_priv *priv = i2c_get_clientdata(client);
>>>>>>> +
>>>>>>> +	/* If there is a reset reg, then it does everything we need */
>>>>>>> +	if (priv->cdef->reset_reg != IS31FL32XX_REG_NONE)
>>>>>>> +		return is31fl32xx_write(priv, priv->cdef->reset_reg, 0);
>>>>>>> +
>>>>>>> +	/* If there is a reset func, then it does everything we need */
>>>>>>> +	if (priv->cdef->reset_func)
>>>>>>> +		return priv->cdef->reset_func(priv);
>>>>>>> +
>>>>>>> +	/* If we can't reset, then try just using software-shutdown mode */
>>>>>>> +	if (priv->cdef->shutdown_reg != IS31FL32XX_REG_NONE)
>>>>>>> +		return is31fl32xx_write(priv, priv->cdef->shutdown_reg, 0x00);
>>>>>>> +
>>>>>>> +	return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> +/*
>>>>>>> + * i2c-core requires that id_table be non-NULL, even though
>>>>>>> + * it is not used for DeviceTree based instantiation.
>>>>>>> + */
>>>>>>> +static const struct i2c_device_id is31fl31xx_id[] = {
>>>>>>> +	{},
>>>>>>> +};
>>>>>>> +
>>>>>>> +MODULE_DEVICE_TABLE(i2c, is31fl31xx_id);
>>>>>>> +
>>>>>>> +static struct i2c_driver is31fl32xx_driver = {
>>>>>>> +	.driver = {
>>>>>>> +		.name	= "is31fl32xx",
>>>>>>> +		.of_match_table = of_is31fl31xx_match,
>>>>>>> +	},
>>>>>>> +	.probe		= is31fl32xx_probe,
>>>>>>> +	.remove		= is31fl32xx_remove,
>>>>>>> +	.id_table	= is31fl31xx_id,
>>>>>>> +};
>>>>>>> +
>>>>>>> +module_i2c_driver(is31fl32xx_driver);
>>>>>>> +
>>>>>>> +MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>");
>>>>>>> +MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver");
>>>>>>> +MODULE_LICENSE("GPL v2");
>>>>>>>
>
>
>


-- 
Best regards,
Jacek Anaszewski

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-27 10:48               ` Stefan Wahren
@ 2016-02-29 18:02                 ` David Rivshin (Allworx)
  2016-02-29 19:40                   ` Stefan Wahren
  0 siblings, 1 reply; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-02-29 18:02 UTC (permalink / raw)
  To: Stefan Wahren
  Cc: Jacek Anaszewski, Pawel Moll, Rob Herring, Ian Campbell,
	Kumar Gala, linux-leds, Richard Purdie, Mark Rutland, devicetree

On Sat, 27 Feb 2016 11:48:45 +0100 (CET)
Stefan Wahren <stefan.wahren@i2se.com> wrote:

> Hi David,
> 
> > "David Rivshin (Allworx)" <drivshin.allworx@gmail.com> hat am 26. Februar 2016
> > um 22:58 geschrieben:
> >
> >
> > On Fri, 26 Feb 2016 10:47:46 +0100
> > Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> >  
> > > On 02/25/2016 08:12 PM, David Rivshin (Allworx) wrote:  
> > > > On Thu, 25 Feb 2016 11:55:58 +0100
> > > > Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> > > >  
> > > >> On 02/25/2016 03:24 AM, David Rivshin (Allworx) wrote:  
> > > >>> On Wed, 24 Feb 2016 17:04:58 +0100
> > > >>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> > > >>>  
> > > >>>> Hi David,
> > > >>>>
> > > >>>> Thanks for the patch. Very nice driver. I have few comments
> > > >>>> below.  
> > > >>>
> > > >>> Thanks Jacek, I have responded the comments inline. I also wanted to
> > > >>> double check whether you noticed some questions I had in the cover
> > > >>> letter [1]. As I mentioned in another email to Rob, in hindsight I'm
> > > >>> guessing I should have included them in the patch comments as well (or
> > > >>> instead of).  
> > > >>
> > > >> I saw them. I assumed that the review itself will address those
> > > >> questions.  
> > > >
> > > > Fair enough, thanks for the confirmation.
> > > >  
> > > >>> Your review comments here effectively answered some of the questions,
> > > >>> but
> > > >>> the big one I'm still unsure of is whether it actually makes sense to
> > > >>> have all 4 of these devices supported by a single driver.  
> > > >>
> > > >> It's perfectly fine. Many drivers implement this pattern.  
> > > >
> > > > OK, then I'll assume you think this driver is not yet too complicated
> > > > for it's own good. Out of curiosity, might that view change if the
> > > > 3216 specific features were ever implemented, especially GPIO and HW
> > > > animation support? Gut feel is that would make 3216 specific code
> > > > bigger than the rest of the code combined.  
> > >
> > > I don't think so.  
> >
> > Thanks, that helps calibrate my intuition for the future.
> >  
> > > > Bigger question is what should be done in terms of the overlap in device
> > > > support between this driver and leds-sn3218? If you think I should leave
> > > > the *3218 support in this driver, then I would propose:
> > > > - remove leds-sn3218 and its separate binding doc
> > > > - add the "si-en,sn3218" compatible string to this driver and binding doc
> > > > Note that while I expect this driver to work with the 3218 chips, I do
> > > > not have one to test against. If we go down this route I would definitely
> > > > want Stefan to test so that I don't accidentally break him.  
> > >
> > > I'd prefer to have a single driver for the same hardware. Stefan, would
> > > it be possible for you to test David's driver with the hardware you
> > > have an access to?  
> >
> > Stefan, one thing to note: the existing sn3218 driver/binding uses 0-based
> > 'reg' values, and this driver/binding uses 1-based 'reg' values. So your
> > devicetree(s) would need to be updated for that (as well as the compatible
> > string).
> >
> > I didn't see a final answer from Rob as to which way is most appropriate
> > for these devices yet, so I don't know which way this will end up in the
> > final patch.  
> 
> unfortunately i'm very busy. Yes, i will test it, but i can't promise when.
> 
> Should i apply this version or wait for the next?

Thanks Stefan. I am hoping to have the next version ready in the next day or 
so. To better use your time, it's probably best to wait for that. 

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-29  9:47               ` Jacek Anaszewski
@ 2016-02-29 18:26                 ` David Rivshin (Allworx)
  2016-03-01  8:24                   ` Jacek Anaszewski
  0 siblings, 1 reply; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-02-29 18:26 UTC (permalink / raw)
  To: Jacek Anaszewski
  Cc: Stefan Wahren, linux-leds, devicetree, Richard Purdie,
	Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala

On Mon, 29 Feb 2016 10:47:44 +0100
Jacek Anaszewski <j.anaszewski@samsung.com> wrote:

> On 02/26/2016 10:58 PM, David Rivshin (Allworx) wrote:
> > On Fri, 26 Feb 2016 10:47:46 +0100
> > Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> >
> >> On 02/25/2016 08:12 PM, David Rivshin (Allworx) wrote:
> >>> On Thu, 25 Feb 2016 11:55:58 +0100
> >>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> >>>
> >>>> On 02/25/2016 03:24 AM, David Rivshin (Allworx) wrote:
> >>>>> On Wed, 24 Feb 2016 17:04:58 +0100
> >>>>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> >>>>>
> >>>>>> Hi David,
> >>>>>>
> >>>>>> Thanks for the patch. Very nice driver. I have few comments
> >>>>>> below.
> >>>>>
> >>>>> Thanks Jacek, I have responded the comments inline. I also wanted to
> >>>>> double check whether you noticed some questions I had in the cover
> >>>>> letter [1]. As I mentioned in another email to Rob, in hindsight I'm
> >>>>> guessing I should have included them in the patch comments as well (or
> >>>>> instead of).
> >>>>
> >>>> I saw them. I assumed that the review itself will address those
> >>>> questions.
> >>>
> >>> Fair enough, thanks for the confirmation.
> >>>
> >>>>> Your review comments here effectively answered some of the questions, but
> >>>>> the big one I'm still unsure of is whether it actually makes sense to
> >>>>> have all 4 of these devices supported by a single driver.
> >>>>
> >>>> It's perfectly fine. Many drivers implement this pattern.
> >>>
> >>> OK, then I'll assume you think this driver is not yet too complicated
> >>> for it's own good. Out of curiosity, might that view change if the
> >>> 3216 specific features were ever implemented, especially GPIO and HW
> >>> animation support? Gut feel is that would make 3216 specific code
> >>> bigger than the rest of the code combined.
> >>
> >> I don't think so.
> >
> > Thanks, that helps calibrate my intuition for the future.
> >
> >>> Bigger question is what should be done in terms of the overlap in device
> >>> support between this driver and leds-sn3218? If you think I should leave
> >>> the *3218 support in this driver, then I would propose:
> >>>     - remove leds-sn3218 and its separate binding doc
> >>>     - add the "si-en,sn3218" compatible string to this driver and binding doc
> >>> Note that while I expect this driver to work with the 3218 chips, I do
> >>> not have one to test against. If we go down this route I would definitely
> >>> want Stefan to test so that I don't accidentally break him.
> >>
> >> I'd prefer to have a single driver for the same hardware. Stefan, would
> >> it be possible for you to test David's driver with the hardware you
> >> have an access to?
> >
> > Stefan, one thing to note: the existing sn3218 driver/binding uses 0-based
> > 'reg' values, and this driver/binding uses 1-based 'reg' values. So your
> > devicetree(s) would need to be updated for that (as well as the compatible
> > string).
> 
> Actually, If your driver can successfully handle Si-En SN3218 I'd prefer
> to drop leds-sn3218 along with its bindings and add related compatible
> to your bindings documentation.

Agreed. The changing of compatible string would only need to be done with
the current version of the series. 
In the next version I'll add a 4th patch (unless you'd prefer a separate 
patch not part of the series?) that removes leds-sn3218 and moves that
support into the is31fl32xx driver.

> > I didn't see a final answer from Rob as to which way is most appropriate
> > for these devices yet, so I don't know which way this will end up in the
> > final patch.
> >
> >>> Also I feel I should point out some differences between the 3218 support
> >>> in this driver versus the leds-sn3218 driver, in case they have any
> >>> impact:
> >>>     - (as previously mentioned) leds-sn3218 turns off an LEDs enable
> >>>       bit if the brightness is set to 0. This driver just sets the PWM
> >>>       to 0 and leaves the enable bits always on.
> >>
> >> Setting brightness to 0 is an equivalent to turning the device in
> >> a power down mode or at least in the state where the current consumption
> >> is as low as possible. A hardware configuration that is most fitting
> >> for this requirements should be chosen.
> >
> > As far as I can tell from the datasheets, setting the PWM duty cycle for
> > a given channel to 0 should have the same net effect as setting the enable
> > bit of that channel to 0. I assume the purpose of the enable bits is to
> > make it easier to turn an LED on/off without adjusting the PWM duty cycle,
> > but just using always the PWM duty cycle register conveniently maps to
> > the leds API.
> 
> ack.
> 
> >>>     - leds-sn3218 uses a regmap, I think mostly to deal with the enable
> >>>       bits, but it also has the benefit of showing up in debugfs. This
> >>>       could be seen as useful in and of itself by some users. On the other
> >>>       hand regmap introduces another mutex on every write.
> >>
> >> I will not insist on using regmap if you don't refer to the current
> >> state of hw registers in your driver.
> >
> > Currently I have not had a need to refer to the current state of any HW
> > registers. I could imagine that might be needed in the future if extra
> > functionality is implemented, but it wasn't so far.
> 
> Regmap exposes nice debugfs interface, so this, and the fact that there
> are uncovered hw features, can be thought of as a sufficient
> argument in favour of using regmap even now. But it's up to you.

If it's OK with you, I think I'll leave it without regmap for now. I 
don't really relish the thought of having 4 large blocks of reg_default 
(especially the 3216 has a large register set for animation), and I
haven't yet worked out how/if I could dynamically generate them from 
the chipdefs in a reasonable way.

> >>>     - leds-sn3218 implements the shutdown callback. Actually, I think I
> >>>       should add that to this driver in any event.
> >>
> >> Do you see use cases or hardware configurations that need such
> >> a callback? I didn't oppose in case of Stefan's driver, but if we are
> >> at it, we can consult Stefan if he saw that use case?
> >>
> >> I'd say that shutdown op is usually useful when CPU and LED
> >> controller are powered from different sources, which can result in
> >> a situation when CPU is turned off, but LED remains on.
> >
> > That is exactly what happens today on my board: if the system is rebooted
> > or shut down the LEDs all stay in whatever state they were last in. This
> > could also be handled by userspace shutdown scripts easily enough, but I
> > thought it surprising when the system reboots but LEDs stay on.
> 
> That's the shutdown callback is for.

I'll add a shutdown callback that will do the same as the remove callback,
and ensure all LEDs go dark.
Is there anything verboten with having one just call the other, or just
registering the same function for both callbacks?

> > On the
> > other hand someone might have a good reason to want to leave an LED on
> > through a reboot.
> 
> If you have such a use case, then you can add DT property for this.
> E.g. retain-state-on-shutdown.

I don't have such a use case right now, but noted for the future.

> > There is also an inconsistency on what happens during remove() vs shutdown().
> > In led_classdev_unregister() brightness is set to LED_OFF, so that happens
> > for all drivers on remove(). But only some drivers implement a shutdown()
> > which also turns off LEDs, most do not. For instance, leds-gpio turns off
> > all LEDs, but leds-pwm does not.
>  >
> > Is the general policy that LEDs should be forced off by the driver when the
> > kernel halts or reboots the CPU? Or left alone and let userspace deal with
> > it?
> 
> I think that this depends on use cases and hardware configurations
> available on the market. People added the callback when they needed it.
> There is no defined policy for this.
> 
> > And should this (in principle) be the same as what happens when a module
> > is unloaded (which currently always turns LEDs off)?
> 
> Turning the LED off on removal is logically justified IMO.

Agreed. I just found the inconsistency is some/most drivers between remove 
and shutdown unexpected. Given that not all LED drivers turn off on shutdown,
(and my use case desires them to turn off, including a leds-pwm instance), 
I'll just write 0 to /sys/class/leds/*/brightness unconditionally in a 
userspace shutdown script.

> >>>     - leds-sn3218 just puts the chip in software-shutdown mode on remove/
> >>>       shutdown. This driver uses the reset register to put the device in
> >>>       poweron state, and software-shutdown is part of the poweron state.
> >>>       Only difference would be if the next code to use the device does
> >>>       not do it's own full initialization (which seems unlikely, or at
> >>>       least unwise), but instead just clears software-shutdown.
> >>
> >> I believe that my above explanations address this question, i.e.
> >> both brightness = 0 an remove/shutdown should set the device
> >> in a power down mode.
> >
> > I think there is some confusion, there are 3 separate controls:
> >   - per-LED PWM duty cycle
> >   - per-LED enable bit
> >   - device-wide shutdown mode
> > Shutdown-mode results in all LEDs going dark (regardless of any other
> > register state), and I think implies that it also uses less power
> > (compared to just turning them all off with either of the other controls).
> > Registers do retain their value and the I2C interface continues to work.
> > I suspect that all it does is turn off an internal oscillator that drives
> > the PWM circuits, but the documentation is not clear.
> >
> > The distinction I was making was that the leds-sn3218 driver *only* turned
> > on the Shutdown-mode, while this driver reset all other registers in the
> > device to their default values as well. Though in practice I don't expect
> > that to make a difference.
> 
> If documentation isn't clear about that, you can always measure current
> consumption in both cases. Note, that this is not required, you can
> follow your intuition.

I may try to do that out of curiosity. 

> >>>>> I won't
> >>>>> clutter this email with a duplicate of the details (it's somewhat long),
> >>>>> but if you could check the cover letter and give some guidance, I would
> >>>>> appreciate it.
> >>>>>
> >>>>> [1] http://www.spinics.net/lists/linux-leds/msg05564.html
> >>>>>        http://thread.gmane.org/gmane.linux.leds/4530
> >>>>>
> >>>>>>
> >>>>>> On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:
> >>>>>>> From: David Rivshin <drivshin@allworx.com>
> >>>>>>>
> >>>>>>> The IS31FL32xx family of LED drivers are I2C devices with multiple
> >>>>>>> constant-current channels, each with independent 256-level PWM control.
> >>>>>>>
> >>>>>>> HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
> >>>>>>>
> >>>>>>> This has been tested on the IS31FL3236 and IS31FL3216 on an ARM
> >>>>>>> (TI am335x) platform.
> >>>>>>>
> >>>>>>> The programming paradigm of these devices is similar in the following
> >>>>>>> ways:
> >>>>>>>      - All registers are 8 bit
> >>>>>>>      - All LED control registers are write-only
> >>>>>>>      - Each LED channel has a PWM register (0-255)
> >>>>>>>      - PWM register writes are shadowed until an Update register is poked
> >>>>>>>      - All have a concept of Software Shutdown, which disables output
> >>>>>>>
> >>>>>>> However, there are some differences in devices:
> >>>>>>>      - 3236/3235 have a separate Control register for each LED,
> >>>>>>>        (3218/3216 pack the enable bits into fewer registers)
> >>>>>>>      - 3236/3235 have a per-channel current divisor setting
> >>>>>>>      - 3236/3235 have a Global Control register that can turn off all LEDs
> >>>>>>>      - 3216 is unique in a number of ways
> >>>>>>>         - OUT9-OUT16 can be configured as GPIOs instead of LED controls
> >>>>>>>         - LEDs can be programmed with an 8-frame animation, with
> >>>>>>>           programmable delay between frames
> >>>>>>>         - LEDs can be modulated by an input audio signal
> >>>>>>>         - Max output current can be adjusted from 1/4 to 2x globally
> >>>>>>>         - Has a Configuration register instead of a Shutdown register
> >>>>>>>
> >>>>>>> This driver currently only supports the base PWM control function
> >>>>>>> of these devices. The following features of these devices are not
> >>>>>>> implemented, although it should be possible to add them in the future:
> >>>>>>>      - All devices are capable of going into a lower-power "software
> >>>>>>>        shutdown" mode.
> >>>>>>>      - The is31fl3236 and is31fl3235 can reduce the max output current
> >>>>>>>        per-channel with a divisor of 1, 2, 3, or 4.
> >>>>>>>      - The is31fl3216 can use some LED channels as GPIOs instead.
> >>>>>>>      - The is31fl3216 can animate LEDs in hardware.
> >>>>>>>      - The is31fl3216 can modulate LEDs according to an audio input.
> >>>>>>>      - The is31fl3216 can reduce/increase max output current globally.
> >>>>>>>
> >>>>>>> Signed-off-by: David Rivshin <drivshin@allworx.com>
> >>>>>>> ---
> >>>>>>>      drivers/leds/Kconfig           |   9 +
> >>>>>>>      drivers/leds/Makefile          |   1 +
> >>>>>>>      drivers/leds/leds-is31fl32xx.c | 442 +++++++++++++++++++++++++++++++++++++++++
> >>>>>>>      3 files changed, 452 insertions(+)
> >>>>>>>      create mode 100644 drivers/leds/leds-is31fl32xx.c
> >>>>>>>
> >>>>>>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> >>>>>>> index 1034696..8f6c46f 100644
> >>>>>>> --- a/drivers/leds/Kconfig
> >>>>>>> +++ b/drivers/leds/Kconfig
> >>>>>>> @@ -580,6 +580,15 @@ config LEDS_SN3218
> >>>>>>>      	  This driver can also be built as a module. If so the module
> >>>>>>>      	  will be called leds-sn3218.
> >>>>>>>
> >>>>>>> +config LEDS_IS31FL32XX
> >>>>>>> +	tristate "Driver for ISSI IS31FL32XX I2C LED driver chip family"
> >>>>>>
> >>>>>> 2 x "[Dd]river".
> >>>>>>
> >>>>>> How about:
> >>>>>>
> >>>>>> "LED Support for ISSI IS31FL32XX I2C LED chip family" ?
> >>>>>
> >>>>> Yes, I found that awkward as well. HW folks (and the datasheets) seem
> >>>>> always refer to devices of this type as "LED Driver"s (which can lead
> >>>>> to some interesting confusions). Taking a cue from the LP5521/23/62
> >>>>> entries, how about:
> >>>>>     "LED Support for the ISSI IS31FL32XX I2C LED driver chip family" ?
> >>>>
> >>>> "LED Support" means "LED class driver". Driver is a software support
> >>>> for hardware chip. What discrepancy do you see in the description
> >>>> I proposed?
> >>>
> >>> I think in this case "driver" also means "hardware device which drives
> >>> a physical LED".
> >>
> >> Let's not confuse these notions. From Linux perspective "driver" refers
> >> to a piece of software used for controlling a hardware.
> >>
> >>> It seems that "LED driver" is the term universally used
> >>> to describe this type of HW device in datasheets.
> >>
> >> There are also e.g. "LED controllers", "LED current regulators".
> >> Let's stick to the convention predominantly used in the LED subsystem
> >> kernel config menu.
> >>
> >>> So it seemed useful to
> >>> use exactly that phrase in the description of what hardware this software
> >>> supports. I could see someone interpreting the phrase "LED chip" as
> >>> referring to an actual LED device.
> >>> I don't feel very strongly on this topic, but for the sake of discussion,
> >>> maybe "LED controller" would avoid any possible confusion in both
> >>> directions?
> >>
> >> Right, so let's use the following:
> >>
> >> "LED Support for ISSI IS31FL32XX I2C LED controller family"
> >>
> >> I understand "LED Support" as "Linux LED subsystem support".
> >
> > STGM. Done.
> >
> >>>>> Perhaps that's the best of both worlds?
> >>>>>
> >>>>>>> +	depends on LEDS_CLASS && I2C && OF
> >>>>>>> +	help
> >>>>>>> +	  Say Y here to include support for the ISSI 31FL32XX LED driver family.
> >>>>>>
> >>>>>> s/driver/chip/
> >>>>>>
> >>>>>>> +	  They are I2C devices with multiple constant-current channels, each
> >>>>>>> +	  with independent 256-level PWM control. This will only work with
> >>>>>>> +	  device tree enabled devices.
> >>>>>>
> >>>>>> We can skip the last sentence I think.
> >>>>>
> >>>>> OK. FYI, I think I got that verbiage from LEDS_SYSCON.
> >>>>
> >>>> Having "depends on OF" is self-explanatory here.
> >>>
> >>> Noted.
> >>>
> >>>>>>> +
> >>>>>>>      comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
> >>>>>>>
> >>>>>>>      config LEDS_BLINKM
> >>>>>>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> >>>>>>> index 89c9b6f..3fdf313 100644
> >>>>>>> --- a/drivers/leds/Makefile
> >>>>>>> +++ b/drivers/leds/Makefile
> >>>>>>> @@ -67,6 +67,7 @@ obj-$(CONFIG_LEDS_KTD2692)		+= leds-ktd2692.o
> >>>>>>>      obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
> >>>>>>>      obj-$(CONFIG_LEDS_SEAD3)		+= leds-sead3.o
> >>>>>>>      obj-$(CONFIG_LEDS_SN3218)		+= leds-sn3218.o
> >>>>>>> +obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
> >>>>>>>
> >>>>>>>      # LED SPI Drivers
> >>>>>>>      obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
> >>>>>>> diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
> >>>>>>> new file mode 100644
> >>>>>>> index 0000000..8dea518
> >>>>>>> --- /dev/null
> >>>>>>> +++ b/drivers/leds/leds-is31fl32xx.c
> >>>>>>> @@ -0,0 +1,442 @@
> >>>>>>> +/*
> >>>>>>> + * linux/drivers/leds-is31fl32xx.c
> >>>>>>> + *
> >>>>>>> + * Driver for ISSI IS31FL32xx family of I2C LED controllers
> >>>>>>> + *
> >>>>>>> + * Copyright 2015 Allworx Corp.
> >>>>>>> + *
> >>>>>>> + *
> >>>>>>> + * 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.
> >>>>>>> + *
> >>>>>>> + * HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
> >>>>>>> + */
> >>>>>>> +
> >>>>>>> +#include <linux/err.h>
> >>>>>>> +#include <linux/i2c.h>
> >>>>>>> +#include <linux/kernel.h>
> >>>>>>> +#include <linux/leds.h>
> >>>>>>> +#include <linux/module.h>
> >>>>>>> +#include <linux/of_platform.h>
> >>>>>>> +
> >>>>>>> +#ifdef DEBUG
> >>>>>>> + #undef dev_dbg
> >>>>>>> + #define dev_dbg dev_info
> >>>>>>> +#endif
> >>>>>>
> >>>>>> What's the benefit of the above?
> >>>>>
> >>>>> It gave me a way to easily see debug output from the driver while it
> >>>>> was parsing the DT (especially if the driver was built-in). Early on
> >>>>> there were other things within that #ifdef as well.
> >>>>> Regardless, passing ddebug_query on the kernel commandline is a more
> >>>>> appropriate way of accomplishing that; I'll remove for the next version.
> >>>>
> >>>> Thanks.
> >>>>
> >>>>>>> +/* Used to indicate a device has no such register */
> >>>>>>> +#define IS31FL32XX_REG_NONE 0xFF
> >>>>>>> +
> >>>>>>> +#define IS31FL3216_CONFIG_REG 0x00
> >>>>>>> +#define IS31FL3216_LIGHTING_EFFECT_REG 0x03
> >>>>>>> +#define IS31FL3216_CHANNEL_CONFIG_REG 0x04
> >>>>>>> +
> >>>>>>> +struct is31fl32xx_priv;
> >>>>>>> +struct is31fl32xx_led_data {
> >>>>>>> +	struct led_classdev cdev;
> >>>>>>> +	u8 channel; /* 1-based, max priv->cdef->channels */
> >>>>>>> +	struct is31fl32xx_priv *priv;
> >>>>>>> +};
> >>>>>>> +
> >>>>>>> +struct is31fl32xx_priv {
> >>>>>>> +	const struct is31fl32xx_chipdef *cdef;
> >>>>>>> +	struct i2c_client *client;
> >>>>>>> +	unsigned int num_leds;
> >>>>>>> +	struct is31fl32xx_led_data leds[0];
> >>>>>>
> >>>>>> Is there any specific reason for not having *leds here instead?
> >>>>>
> >>>>> I followed a pattern from leds-pwm where it did a single allocation
> >>>>> for both priv and priv->leds[]. See sizeof_is31fl32xx_priv(), and
> >>>>> its use, below. I saw the benefit as one fewer small allocation, so
> >>>>> slightly more kind to the allocator (and devres). If you'd prefer to
> >>>>> do it as two allocations, I'll make the change.
> >>>>
> >>>> OK, I had to look at this one more time. I like the idea.
> >>>
> >>> OK, I'll keep it as-is.
> >>>
> >>>>>>> +};
> >>>>>>> +
> >>>>>>> +/**
> >>>>>>> + * struct is31fl32xx_chipdef - chip-specific attributes
> >>>>>>> + * @channels            : Number of LED channels
> >>>>>>> + * @shutdown_reg        : address of Shutdown register (optional)
> >>>>>>> + * @pwm_update_reg      : address of PWM Update register
> >>>>>>> + * @global_control_reg	: address of Global Control register (optional)
> >>>>>>> + * @reset_reg           : address of Reset register (optional)
> >>>>>>> + * @pwm_register_base	: address of first PWM register
> >>>>>>> + * @pwm_registers_reversed: : true if PWM registers count down instead of up
> >>>>>>> + * @led_control_register_base : address of first LED control register (optional)
> >>>>>>> + * @enable_bits_per_led_control_register: number of LEDs enable bits in each
> >>>>>>> + * @reset_func:         : pointer to reset function
> >>>>>>> + *
> >>>>>>> + * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE
> >>>>>>> + * indicates that this chip has no such register.
> >>>>>>> + *
> >>>>>>> + * If non-NULL, @reset_func will be called during probing to set all
> >>>>>>> + * necessary registers to a known initialization state. This is needed
> >>>>>>> + * for chips that do not have a @reset_reg.
> >>>>>>> + *
> >>>>>>> + * @enable_bits_per_led_control_register must be >=1 if
> >>>>>>> + * @led_control_register_base != %IS31FL32XX_REG_NONE.
> >>>>>>> + */
> >>>>>>> +struct is31fl32xx_chipdef {
> >>>>>>> +	u8	channels;
> >>>>>>> +	u8	shutdown_reg;
> >>>>>>> +	u8	pwm_update_reg;
> >>>>>>> +	u8	global_control_reg;
> >>>>>>> +	u8	reset_reg;
> >>>>>>> +	u8	pwm_register_base;
> >>>>>>> +	bool	pwm_registers_reversed;
> >>>>>>> +	u8	led_control_register_base;
> >>>>>>> +	u8	enable_bits_per_led_control_register;
> >>>>>>> +	int (*reset_func)(struct is31fl32xx_priv *priv);
> >>>>>>> +};
> >>>>>>> +
> >>>>>>> +static const struct is31fl32xx_chipdef is31fl3236_cdef = {
> >>>>>>> +	.channels				= 36,
> >>>>>>> +	.shutdown_reg				= 0x00,
> >>>>>>> +	.pwm_update_reg				= 0x25,
> >>>>>>> +	.global_control_reg			= 0x4a,
> >>>>>>> +	.reset_reg				= 0x4f,
> >>>>>>> +	.pwm_register_base			= 0x01,
> >>>>>>> +	.led_control_register_base		= 0x26,
> >>>>>>> +	.enable_bits_per_led_control_register	= 1,
> >>>>>>> +};
> >>>>>>> +
> >>>>>>> +static const struct is31fl32xx_chipdef is31fl3235_cdef = {
> >>>>>>> +	.channels				= 28,
> >>>>>>> +	.shutdown_reg				= 0x00,
> >>>>>>> +	.pwm_update_reg				= 0x25,
> >>>>>>> +	.global_control_reg			= 0x4a,
> >>>>>>> +	.reset_reg				= 0x4f,
> >>>>>>> +	.pwm_register_base			= 0x05,
> >>>>>>> +	.led_control_register_base		= 0x2a,
> >>>>>>> +	.enable_bits_per_led_control_register	= 1,
> >>>>>>> +};
> >>>>>>> +
> >>>>>>> +static const struct is31fl32xx_chipdef is31fl3218_cdef = {
> >>>>>>> +	.channels				= 18,
> >>>>>>> +	.shutdown_reg				= 0x00,
> >>>>>>> +	.pwm_update_reg				= 0x16,
> >>>>>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
> >>>>>>> +	.reset_reg				= 0x17,
> >>>>>>> +	.pwm_register_base			= 0x01,
> >>>>>>> +	.led_control_register_base		= 0x13,
> >>>>>>> +	.enable_bits_per_led_control_register	= 6,
> >>>>>>> +};
> >>>>>>> +
> >>>>>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv);
> >>>>>>> +static const struct is31fl32xx_chipdef is31fl3216_cdef = {
> >>>>>>> +	.channels				= 16,
> >>>>>>> +	.shutdown_reg				= IS31FL32XX_REG_NONE,
> >>>>>>> +	.pwm_update_reg				= 0xB0,
> >>>>>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
> >>>>>>> +	.reset_reg				= IS31FL32XX_REG_NONE,
> >>>>>>> +	.pwm_register_base			= 0x10,
> >>>>>>> +	.pwm_registers_reversed			= true,
> >>>>>>> +	.led_control_register_base		= 0x01,
> >>>>>>> +	.enable_bits_per_led_control_register	= 8,
> >>>>>>> +	.reset_func				= is31fl3216_reset,
> >>>>>>> +};
> >>>>>>> +
> >>>>>>> +static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)
> >>>>>>> +{
> >>>>>>> +	int ret;
> >>>>>>> +
> >>>>>>> +	dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
> >>>>>>> +
> >>>>>>> +	ret =  i2c_smbus_write_byte_data(priv->client, reg, val);
> >>>>>>> +	if (ret) {
> >>>>>>> +		dev_err(&priv->client->dev,
> >>>>>>> +			"register write to 0x%02X failed (error %d)",
> >>>>>>> +			reg, ret);
> >>>>>>> +	}
> >>>>>>> +	return ret;
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> +/*
> >>>>>>> + * Custom reset function for IS31FL3216 because it does not have a RESET
> >>>>>>> + * register the way that the other IS31FL32xx chips do. We don't bother
> >>>>>>> + * writing the GPIO and animation registers, because the registers we
> >>>>>>> + * do write ensure those will have no effect.
> >>>>>>> + */
> >>>>>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv)
> >>>>>>> +{
> >>>>>>> +	unsigned int i;
> >>>>>>> +	int ret;
> >>>>>>> +
> >>>>>>> +	for (i = 0; i < priv->cdef->channels; i++) {
> >>>>>>> +		ret = is31fl32xx_write(priv, priv->cdef->pwm_register_base+i,
> >>>>>>> +				       0x00);
> >>>>>>> +		if (ret)
> >>>>>>> +			return ret;
> >>>>>>> +	}
> >>>>>>> +	ret = is31fl32xx_write(priv, priv->cdef->pwm_update_reg, 0);
> >>>>>>> +	if (ret)
> >>>>>>> +		return ret;
> >>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, 0x00);
> >>>>>>> +	if (ret)
> >>>>>>> +		return ret;
> >>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, 0x00);
> >>>>>>> +	if (ret)
> >>>>>>> +		return ret;
> >>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, 0x00);
> >>>>>>> +	if (ret)
> >>>>>>> +		return ret;
> >>>>>>> +
> >>>>>>> +	return 0;
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> +
> >>>>>>> +static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,
> >>>>>>> +				     enum led_brightness brightness)
> >>>>>>> +{
> >>>>>>> +	const struct is31fl32xx_led_data *led_data =
> >>>>>>> +		container_of(led_cdev, struct is31fl32xx_led_data, cdev);
> >>>>>>> +	const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef;
> >>>>>>> +	u8 pwm_register_offset;
> >>>>>>> +	int ret;
> >>>>>>> +
> >>>>>>> +	dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);
> >>>>>>> +
> >>>>>>> +	/* NOTE: led_data->channel is 1-based */
> >>>>>>> +	if (cdef->pwm_registers_reversed)
> >>>>>>> +		pwm_register_offset = cdef->channels - led_data->channel;
> >>>>>>> +	else
> >>>>>>> +		pwm_register_offset = led_data->channel - 1;
> >>>>>>> +
> >>>>>>> +	ret = is31fl32xx_write(led_data->priv,
> >>>>>>> +			       cdef->pwm_register_base + pwm_register_offset,
> >>>>>>> +			       brightness);
> >>>>>>> +	if (ret)
> >>>>>>> +		return ret;
> >>>>>>
> >>>>>> I infer that nothing wrong happens in case current process is preempted
> >>>>>> here by the call originating from the other sub-LED?
> >>>>>
> >>>>> I do not believe so. All the driver-specific data used here is read-only
> >>>>> after probing. chipdefs are entirely const, and the only thing in priv
> >>>>> that's referenced is the chipdef pointer which logically could not change
> >>>>> post-probe. Actually nothing else in priv is modified post-probe either.
> >>>>>
> >>>>> The I2C core code has a mutex on the bus, so two writes cannot happen at
> >>>>> once.
> >>>>>
> >>>>> In all these devices there is a unique PWM duty-cycle register for each
> >>>>> LED channel (which is what is being written here), so no register writes
> >>>>> for one LED channel effect any others.
> >>>>>
> >>>>> I believe the worst that could happen is that the device would see:
> >>>>> 	PWM_REG_A write X
> >>>>> 	PWM_REG_B write Y
> >>>>> 	UPDATE_REG write 0
> >>>>> 	UPDATE_REG write 0
> >>>>> instead of
> >>>>> 	PWM_REG_A write X
> >>>>> 	UPDATE_REG write 0
> >>>>> 	PWM_REG_B write Y
> >>>>> 	UPDATE_REG write 0
> >>>>> but that makes no difference to the functionality. Poking the update
> >>>>> register merely applies all PWM register writes up to that point (I'm
> >>>>> assuming to allow atomically changing the state of multiple LEDs at
> >>>>> once).
> >>>>
> >>>> Thanks for this comprehensive explanation.
> >>>
> >>> Should I put some part of this explanation in a comment somewhere? Seems
> >>> like the kind of thing someone else might wonder about in the future also.
> >>
> >> Good idea.
> >
> > Done.
> >
> >>>>> I should note here (as mentioned in cover letter), I made a choice to
> >>>>> always leave the per-LED "enable" bits on, and let the PWM just get set
> >>>>> to 0 naturally to turn an LED off. This differs from the existing SN3218
> >>>>> driver, which used regmap_update_bits, and is then protected by a per-
> >>>>> regmap mutex.
> >>>>
> >>>> ack.
> >>>>
> >>>>>>> +	return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);
> >>>>>>> +
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> +static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
> >>>>>>> +{
> >>>>>>> +	const struct is31fl32xx_chipdef *cdef = priv->cdef;
> >>>>>>> +	int ret;
> >>>>>>> +
> >>>>>>> +	if (cdef->reset_reg != IS31FL32XX_REG_NONE) {
> >>>>>>> +		ret = is31fl32xx_write(priv, cdef->reset_reg, 0);
> >>>>>>> +		if (ret)
> >>>>>>> +			return ret;
> >>>>>>> +	}
> >>>>>>> +	if (cdef->reset_func) {
> >>>>>>> +		ret = cdef->reset_func(priv);
> >>>>>>> +		if (ret)
> >>>>>>> +			return ret;
> >>>>>>> +	}
> >>>>>>> +	if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) {
> >>>>>>> +		u8 value =
> >>>>>>> +		    GENMASK(cdef->enable_bits_per_led_control_register-1, 0);
> >>>>>>> +		u8 num_regs = cdef->channels /
> >>>>>>> +				cdef->enable_bits_per_led_control_register;
> >>>>>>> +		int i;
> >>>>>>> +
> >>>>>>> +		for (i = 0; i < num_regs; i++) {
> >>>>>>> +			ret = is31fl32xx_write(priv,
> >>>>>>> +					       cdef->led_control_register_base+i,
> >>>>>>> +					       value);
> >>>>>>> +			if (ret)
> >>>>>>> +				return ret;
> >>>>>>> +		}
> >>>>>>> +	}
> >>>>>>> +	if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) {
> >>>>>>> +		ret = is31fl32xx_write(priv, cdef->shutdown_reg, BIT(0));
> >>>>>>> +		if (ret)
> >>>>>>> +			return ret;
> >>>>>>> +	}
> >>>>>>> +	if (cdef->global_control_reg != IS31FL32XX_REG_NONE) {
> >>>>>>> +		ret = is31fl32xx_write(priv, cdef->global_control_reg, 0x00);
> >>>>>>> +		if (ret)
> >>>>>>> +			return ret;
> >>>>>>> +	}
> >>>>>>> +
> >>>>>>> +	return 0;
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> +static inline size_t sizeof_is31fl32xx_priv(int num_leds)
> >>>>>>> +{
> >>>>>>> +	return sizeof(struct is31fl32xx_priv) +
> >>>>>>> +		      (sizeof(struct is31fl32xx_led_data) * num_leds);
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> +static int is31fl32xx_parse_child_dt(const struct device *dev,
> >>>>>>> +				     const struct device_node *child,
> >>>>>>> +				     struct is31fl32xx_led_data *led_data)
> >>>>>>> +{
> >>>>>>> +	struct led_classdev *cdev = &led_data->cdev;
> >>>>>>> +	int ret = 0;
> >>>>>>> +	u32 reg;
> >>>>>>> +
> >>>>>>> +	cdev->name = of_get_property(child, "label", NULL) ? : child->name;
> >>>>>>> +
> >>>>>>> +	ret = of_property_read_u32(child, "reg", &reg);
> >>>>>>> +	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
> >>>>>>> +		dev_err(dev,
> >>>>>>> +			"Child node %s does not have a valid reg property\n",
> >>>>>>> +			child->name);
> >>>>>>> +		return -EINVAL;
> >>>>>>> +	}
> >>>>>>> +	led_data->channel = reg;
> >>>>>>> +
> >>>>>>> +	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
> >>>>>>> +						NULL);
> >>>>>>> +	cdev->brightness = LED_OFF;
> >>>>>>
> >>>>>> devm_kzalloc secures that.
> >>>>>
> >>>>> OK, I will remove.
> >>>>>
> >>>>>>> +	ret = of_property_read_u32(child, "max-brightness",
> >>>>>>> +				   &cdev->max_brightness);
> >>>>>>> +	if (ret == -EINVAL) {
> >>>>>>> +		cdev->max_brightness = 255;
> >>>>>>
> >>>>>> s/255/LED_FULL/
> >>>>>
> >>>>> Noted, although (from the patch 2 discussion) max-brightness property is
> >>>>> removed/replaced, this would go away anyways.
> >>>>>
> >>>>>>> +	} else if (ret) {
> >>>>>>> +		dev_dbg(dev,
> >>>>>>> +			"Child node %s has an invalid max-brightness property\n",
> >>>>>>> +			child->name);
> >>>>>>> +		return -EINVAL;
> >>>>>>> +	}
> >>>>>>> +
> >>>>>>> +	cdev->brightness_set_blocking = is31fl32xx_brightness_set;
> >>>>>>
> >>>>>> Please add empty line here.
> >>>>>
> >>>>> Done.
> >>>>>
> >>>>>>> +	return 0;
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> +static struct is31fl32xx_led_data *is31fl32xx_find_led_data(
> >>>>>>> +					struct is31fl32xx_priv *priv,
> >>>>>>> +					u8 channel)
> >>>>>>> +{
> >>>>>>> +	size_t i;
> >>>>>>> +
> >>>>>>> +	for (i = 0; i < priv->num_leds; i++) {
> >>>>>>> +		if (priv->leds[i].channel == channel)
> >>>>>>> +			return &priv->leds[i];
> >>>>>>> +	}
> >>>>>>
> >>>>>> Ditto.
> >>>>>
> >>>>> Done.
> >>>>>
> >>>>>>> +	return NULL;
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> +static int is31fl32xx_parse_dt(struct device *dev,
> >>>>>>> +			       struct is31fl32xx_priv *priv)
> >>>>>>> +{
> >>>>>>> +	struct device_node *child;
> >>>>>>> +
> >>>>>>> +	for_each_child_of_node(dev->of_node, child) {
> >>>>>>> +		struct is31fl32xx_led_data *led_data =
> >>>>>>> +			&priv->leds[priv->num_leds];
> >>>>>>> +		int ret = 0;
> >>>>>>> +		const struct is31fl32xx_led_data *other_led_data;
> >>>>>>> +
> >>>>>>> +		led_data->priv = priv;
> >>>>>>> +
> >>>>>>> +		ret = is31fl32xx_parse_child_dt(dev, child, led_data);
> >>>>>>> +		if (ret)
> >>>>>>> +			continue;
> >>>>>>
> >>>>>> I prefer failing in such cases,
> >>>>>
> >>>>> OK, I will change to an 'goto err' which will have an 'of_node_put()'
> >>>>> and 'return ret'.
> >>>>>
> >>>>> I will say, however, that while testing the error-detection in the
> >>>>> parsing logic, it was very convenient to construct a single devicetree
> >>>>> with a variety of errors. Then a single boot would test multiple
> >>>>> cases at once.
> >>>>
> >>>> Good idea for testing, but in case some failure occurs during DT child
> >>>> node parsing in the release environment you're left with unused
> >>>> allocated memory.
> >>>
> >>> Agreed. BTW, I assume from this that it's common to say "if there's
> >>> anything wrong in one part of your DT, there is no guarantee as to what
> >>> parts will actually be used"? I say this because what we're saying is that
> >>> if one LED node on this device is faulty, that all of them are ignored.
> >>> Analogy might be to a whole I2C bus being ignored because one of the
> >>> devices on it failed to probe. To the devicetree it's still a parent/child
> >>> bus/address relationship, even though the driver implementation is
> >>> very different.
> >>
> >> I2C example is too generic I think. I2C controller is not as tightly
> >> coupled with I2C clients as LED controller with its current outputs.
> >> Besides, I2C controller doesn't have an idea what devices will attach
> >> to the bus it controls upon probing, contrarily to a LED controller.
> >
> > OK. I realize that in code there is a large distinction in these cases,
> > but I wasn't sure if that would be reflected in how errors in parsing
> > the devicetree should handled. Sounds like there is at least a de-facto
> > distinction between "a device and its children" and "a bus and its
> > children".
> >
> >>>>>>> +
> >>>>>>> +		/* Detect if channel is already in use by another child */
> >>>>>>> +		other_led_data = is31fl32xx_find_led_data(priv,
> >>>>>>> +							  led_data->channel);
> >>>>>>> +		if (other_led_data) {
> >>>>>>> +			dev_err(dev,
> >>>>>>> +				"%s ignored: channel %d already used by %s",
> >>>>>>> +				led_data->cdev.name,
> >>>>>>> +				led_data->channel,
> >>>>>>> +				other_led_data->cdev.name);
> >>>>>>> +			continue;
> >>>>>>
> >>>>>> Ditto.
> >>>>>
> >>>>> OK.
> >>>>>
> >>>>>>> +		}
> >>>>>>> +
> >>>>>>> +		ret = devm_led_classdev_register(dev, &led_data->cdev);
> >>>>>>> +		if (ret == 0) {
> >>>>>>> +			priv->num_leds++;
> >>>>>>> +		} else {
> >>>>>>> +			dev_err(dev, "failed to register PWM led for %s: %d\n",
> >>>>>>> +				led_data->cdev.name, ret);
> >>>>>
> >>>>> Should I also fail here, then? Right now it will continue trying to
> >>>>> register future LED devices if a classdev_register fails for some
> >>>>> reason, and will successfully load even if all of them fail.
> >>>>
> >>>> Please fail here too. If we can't setup the sub-LED that is advertised
> >>>> in a DT child node, then it means that something went wrong.
> >>>> This is clear error case.
> >>>
> >>> Done.
> >>>
> >>>>>>> +		}
> >>>>>>> +	}
> >>>>>>> +
> >>>>>>> +	return 0;
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> +static const struct of_device_id of_is31fl31xx_match[] = {
> >>>>>>> +	{ .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },
> >>>>>>> +	{ .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },
> >>>>>>> +	{ .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, },
> >>>>>>> +	{ .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, },
> >>>>>>> +	{},
> >>>>>>> +};
> >>>>>>> +
> >>>>>>> +MODULE_DEVICE_TABLE(of, of_is31fl31xx_match);
> >>>>>>> +
> >>>>>>> +static int is31fl32xx_probe(struct i2c_client *client,
> >>>>>>> +				  const struct i2c_device_id *id)
> >>>>>>> +{
> >>>>>>> +	const struct is31fl32xx_chipdef *cdef;
> >>>>>>> +	const struct of_device_id *of_dev_id;
> >>>>>>> +	struct device *dev = &client->dev;
> >>>>>>> +	struct is31fl32xx_priv *priv;
> >>>>>>> +	int count;
> >>>>>>> +	int ret = 0;
> >>>>>>> +
> >>>>>>> +	of_dev_id = of_match_device(of_is31fl31xx_match, dev);
> >>>>>>> +	if (!of_dev_id)
> >>>>>>> +		return -EINVAL;
> >>>>>>> +
> >>>>>>> +	cdef = of_dev_id->data;
> >>>>>>> +
> >>>>>>> +	count = of_get_child_count(dev->of_node);
> >>>>>>> +	if (!count)
> >>>>>>> +		return -EINVAL;
> >>>>>>> +
> >>>>>>> +	priv = devm_kzalloc(dev, sizeof_is31fl32xx_priv(count),
> >>>>>>> +			    GFP_KERNEL);
> >>>>>>> +	if (!priv)
> >>>>>>> +		return -ENOMEM;
> >>>>>>> +
> >>>>>>> +	priv->client = client;
> >>>>>>> +	priv->cdef = cdef;
> >>>>>>> +	i2c_set_clientdata(client, priv);
> >>>>>>> +
> >>>>>>> +	ret = is31fl32xx_init_regs(priv);
> >>>>>>> +	if (ret)
> >>>>>>> +		return ret;
> >>>>>>> +
> >>>>>>> +	ret = is31fl32xx_parse_dt(dev, priv);
> >>>>>>> +	if (ret)
> >>>>>>> +		return ret;
> >>>>>>> +
> >>>>>>> +	return 0;
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> +static int is31fl32xx_remove(struct i2c_client *client)
> >>>>>>> +{
> >>>>>>> +	struct is31fl32xx_priv *priv = i2c_get_clientdata(client);
> >>>>>>> +
> >>>>>>> +	/* If there is a reset reg, then it does everything we need */
> >>>>>>> +	if (priv->cdef->reset_reg != IS31FL32XX_REG_NONE)
> >>>>>>> +		return is31fl32xx_write(priv, priv->cdef->reset_reg, 0);
> >>>>>>> +
> >>>>>>> +	/* If there is a reset func, then it does everything we need */
> >>>>>>> +	if (priv->cdef->reset_func)
> >>>>>>> +		return priv->cdef->reset_func(priv);
> >>>>>>> +
> >>>>>>> +	/* If we can't reset, then try just using software-shutdown mode */
> >>>>>>> +	if (priv->cdef->shutdown_reg != IS31FL32XX_REG_NONE)
> >>>>>>> +		return is31fl32xx_write(priv, priv->cdef->shutdown_reg, 0x00);
> >>>>>>> +
> >>>>>>> +	return 0;
> >>>>>>> +}
> >>>>>>> +
> >>>>>>> +/*
> >>>>>>> + * i2c-core requires that id_table be non-NULL, even though
> >>>>>>> + * it is not used for DeviceTree based instantiation.
> >>>>>>> + */
> >>>>>>> +static const struct i2c_device_id is31fl31xx_id[] = {
> >>>>>>> +	{},
> >>>>>>> +};
> >>>>>>> +
> >>>>>>> +MODULE_DEVICE_TABLE(i2c, is31fl31xx_id);
> >>>>>>> +
> >>>>>>> +static struct i2c_driver is31fl32xx_driver = {
> >>>>>>> +	.driver = {
> >>>>>>> +		.name	= "is31fl32xx",
> >>>>>>> +		.of_match_table = of_is31fl31xx_match,
> >>>>>>> +	},
> >>>>>>> +	.probe		= is31fl32xx_probe,
> >>>>>>> +	.remove		= is31fl32xx_remove,
> >>>>>>> +	.id_table	= is31fl31xx_id,
> >>>>>>> +};
> >>>>>>> +
> >>>>>>> +module_i2c_driver(is31fl32xx_driver);
> >>>>>>> +
> >>>>>>> +MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>");
> >>>>>>> +MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver");
> >>>>>>> +MODULE_LICENSE("GPL v2");
> >>>>>>>

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-29 18:02                 ` David Rivshin (Allworx)
@ 2016-02-29 19:40                   ` Stefan Wahren
  2016-03-01  1:32                     ` David Rivshin (Allworx)
  0 siblings, 1 reply; 33+ messages in thread
From: Stefan Wahren @ 2016-02-29 19:40 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: Pawel Moll, Rob Herring, Ian Campbell, Kumar Gala, linux-leds,
	Jacek Anaszewski, Richard Purdie, Mark Rutland, devicetree

Hi David,

> "David Rivshin (Allworx)" <drivshin.allworx@gmail.com> hat am 29. Februar 2016
> um 19:02 geschrieben:
>
>
> On Sat, 27 Feb 2016 11:48:45 +0100 (CET)
> Stefan Wahren <stefan.wahren@i2se.com> wrote:
>
> > Hi David,
> >
> > > "David Rivshin (Allworx)" <drivshin.allworx@gmail.com> hat am 26. Februar
> > > 2016
> > > um 22:58 geschrieben:
> > >
> > >
> > > On Fri, 26 Feb 2016 10:47:46 +0100
> > > Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> > >
> > > > On 02/25/2016 08:12 PM, David Rivshin (Allworx) wrote:
> > > > > On Thu, 25 Feb 2016 11:55:58 +0100
> > > > > Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> > > > >
> > > > >> On 02/25/2016 03:24 AM, David Rivshin (Allworx) wrote:
> > > > >>> On Wed, 24 Feb 2016 17:04:58 +0100
> > > > >>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> > > > >>>
> > > > >>>> Hi David,
> > > > >>>>
> > > > >>>> Thanks for the patch. Very nice driver. I have few comments
> > > > >>>> below.
> > > > >>>
> > > > >>> Thanks Jacek, I have responded the comments inline. I also wanted to
> > > > >>> double check whether you noticed some questions I had in the cover
> > > > >>> letter [1]. As I mentioned in another email to Rob, in hindsight I'm
> > > > >>> guessing I should have included them in the patch comments as well
> > > > >>> (or
> > > > >>> instead of).
> > > > >>
> > > > >> I saw them. I assumed that the review itself will address those
> > > > >> questions.
> > > > >
> > > > > Fair enough, thanks for the confirmation.
> > > > >
> > > > >>> Your review comments here effectively answered some of the
> > > > >>> questions,
> > > > >>> but
> > > > >>> the big one I'm still unsure of is whether it actually makes sense
> > > > >>> to
> > > > >>> have all 4 of these devices supported by a single driver.
> > > > >>
> > > > >> It's perfectly fine. Many drivers implement this pattern.
> > > > >
> > > > > OK, then I'll assume you think this driver is not yet too complicated
> > > > > for it's own good. Out of curiosity, might that view change if the
> > > > > 3216 specific features were ever implemented, especially GPIO and HW
> > > > > animation support? Gut feel is that would make 3216 specific code
> > > > > bigger than the rest of the code combined.
> > > >
> > > > I don't think so.
> > >
> > > Thanks, that helps calibrate my intuition for the future.
> > >
> > > > > Bigger question is what should be done in terms of the overlap in
> > > > > device
> > > > > support between this driver and leds-sn3218? If you think I should
> > > > > leave
> > > > > the *3218 support in this driver, then I would propose:
> > > > > - remove leds-sn3218 and its separate binding doc
> > > > > - add the "si-en,sn3218" compatible string to this driver and binding
> > > > > doc
> > > > > Note that while I expect this driver to work with the 3218 chips, I do
> > > > > not have one to test against. If we go down this route I would
> > > > > definitely
> > > > > want Stefan to test so that I don't accidentally break him.
> > > >
> > > > I'd prefer to have a single driver for the same hardware. Stefan, would
> > > > it be possible for you to test David's driver with the hardware you
> > > > have an access to?
> > >
> > > Stefan, one thing to note: the existing sn3218 driver/binding uses 0-based
> > > 'reg' values, and this driver/binding uses 1-based 'reg' values. So your
> > > devicetree(s) would need to be updated for that (as well as the compatible
> > > string).
> > >
> > > I didn't see a final answer from Rob as to which way is most appropriate
> > > for these devices yet, so I don't know which way this will end up in the
> > > final patch.
> >
> > unfortunately i'm very busy. Yes, i will test it, but i can't promise when.
> >
> > Should i apply this version or wait for the next?
>
> Thanks Stefan. I am hoping to have the next version ready in the next day or
> so. To better use your time, it's probably best to wait for that.

i'm okay with replacing leds-sn3218. The pins of the SN3218 in the datasheet [1]
are 1-based.
Sorry, my fault.

[1] - http://www.si-en.com/uploadpdf/s2011517171720.pdf

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-29 19:40                   ` Stefan Wahren
@ 2016-03-01  1:32                     ` David Rivshin (Allworx)
  0 siblings, 0 replies; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-03-01  1:32 UTC (permalink / raw)
  To: Stefan Wahren
  Cc: Pawel Moll, Rob Herring, Ian Campbell, Kumar Gala, linux-leds,
	Jacek Anaszewski, Richard Purdie, Mark Rutland, devicetree

On Mon, 29 Feb 2016 20:40:49 +0100 (CET)
Stefan Wahren <stefan.wahren@i2se.com> wrote:

> Hi David,
> 
> > "David Rivshin (Allworx)" <drivshin.allworx@gmail.com> hat am 29. Februar 2016
> > um 19:02 geschrieben:
> >
> >
> > On Sat, 27 Feb 2016 11:48:45 +0100 (CET)
> > Stefan Wahren <stefan.wahren@i2se.com> wrote:
> >  
> > > Hi David,
> > >  
> > > > "David Rivshin (Allworx)" <drivshin.allworx@gmail.com> hat am 26. Februar
> > > > 2016
> > > > um 22:58 geschrieben:
> > > >
> > > >
> > > > On Fri, 26 Feb 2016 10:47:46 +0100
> > > > Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> > > >  
> > > > > On 02/25/2016 08:12 PM, David Rivshin (Allworx) wrote:  
> > > > > > On Thu, 25 Feb 2016 11:55:58 +0100
> > > > > > Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> > > > > >  
> > > > > >> On 02/25/2016 03:24 AM, David Rivshin (Allworx) wrote:  
> > > > > >>> On Wed, 24 Feb 2016 17:04:58 +0100
> > > > > >>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> > > > > >>>  
> > > > > >>>> Hi David,
> > > > > >>>>
> > > > > >>>> Thanks for the patch. Very nice driver. I have few comments
> > > > > >>>> below.  
> > > > > >>>
> > > > > >>> Thanks Jacek, I have responded the comments inline. I also wanted to
> > > > > >>> double check whether you noticed some questions I had in the cover
> > > > > >>> letter [1]. As I mentioned in another email to Rob, in hindsight I'm
> > > > > >>> guessing I should have included them in the patch comments as well
> > > > > >>> (or
> > > > > >>> instead of).  
> > > > > >>
> > > > > >> I saw them. I assumed that the review itself will address those
> > > > > >> questions.  
> > > > > >
> > > > > > Fair enough, thanks for the confirmation.
> > > > > >  
> > > > > >>> Your review comments here effectively answered some of the
> > > > > >>> questions,
> > > > > >>> but
> > > > > >>> the big one I'm still unsure of is whether it actually makes sense
> > > > > >>> to
> > > > > >>> have all 4 of these devices supported by a single driver.  
> > > > > >>
> > > > > >> It's perfectly fine. Many drivers implement this pattern.  
> > > > > >
> > > > > > OK, then I'll assume you think this driver is not yet too complicated
> > > > > > for it's own good. Out of curiosity, might that view change if the
> > > > > > 3216 specific features were ever implemented, especially GPIO and HW
> > > > > > animation support? Gut feel is that would make 3216 specific code
> > > > > > bigger than the rest of the code combined.  
> > > > >
> > > > > I don't think so.  
> > > >
> > > > Thanks, that helps calibrate my intuition for the future.
> > > >  
> > > > > > Bigger question is what should be done in terms of the overlap in
> > > > > > device
> > > > > > support between this driver and leds-sn3218? If you think I should
> > > > > > leave
> > > > > > the *3218 support in this driver, then I would propose:
> > > > > > - remove leds-sn3218 and its separate binding doc
> > > > > > - add the "si-en,sn3218" compatible string to this driver and binding
> > > > > > doc
> > > > > > Note that while I expect this driver to work with the 3218 chips, I do
> > > > > > not have one to test against. If we go down this route I would
> > > > > > definitely
> > > > > > want Stefan to test so that I don't accidentally break him.  
> > > > >
> > > > > I'd prefer to have a single driver for the same hardware. Stefan, would
> > > > > it be possible for you to test David's driver with the hardware you
> > > > > have an access to?  
> > > >
> > > > Stefan, one thing to note: the existing sn3218 driver/binding uses 0-based
> > > > 'reg' values, and this driver/binding uses 1-based 'reg' values. So your
> > > > devicetree(s) would need to be updated for that (as well as the compatible
> > > > string).
> > > >
> > > > I didn't see a final answer from Rob as to which way is most appropriate
> > > > for these devices yet, so I don't know which way this will end up in the
> > > > final patch.  
> > >
> > > unfortunately i'm very busy. Yes, i will test it, but i can't promise when.
> > >
> > > Should i apply this version or wait for the next?  
> >
> > Thanks Stefan. I am hoping to have the next version ready in the next day or
> > so. To better use your time, it's probably best to wait for that.  
> 
> i'm okay with replacing leds-sn3218. The pins of the SN3218 in the datasheet [1]
> are 1-based.
> Sorry, my fault.

No worries, I was quite unsure whether to do the 'reg' devicetree property
as 1-based or 0-based myself. I went with 1-based originally mostly because
it made writing devicetrees an easier match with the schematics I have.

FYI, I don't quite have the next version ready to post yet, but I hope to do
it tomorrow (assuming I can steal enough time to retest).

> 
> [1] - http://www.si-en.com/uploadpdf/s2011517171720.pdf

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-02-29 18:26                 ` David Rivshin (Allworx)
@ 2016-03-01  8:24                   ` Jacek Anaszewski
  2016-03-01 18:45                     ` David Rivshin (Allworx)
  0 siblings, 1 reply; 33+ messages in thread
From: Jacek Anaszewski @ 2016-03-01  8:24 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: Stefan Wahren, linux-leds, devicetree, Richard Purdie,
	Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala

On 02/29/2016 07:26 PM, David Rivshin (Allworx) wrote:
> On Mon, 29 Feb 2016 10:47:44 +0100
> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>
>> On 02/26/2016 10:58 PM, David Rivshin (Allworx) wrote:
>>> On Fri, 26 Feb 2016 10:47:46 +0100
>>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>>>
>>>> On 02/25/2016 08:12 PM, David Rivshin (Allworx) wrote:
>>>>> On Thu, 25 Feb 2016 11:55:58 +0100
>>>>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>>>>>
>>>>>> On 02/25/2016 03:24 AM, David Rivshin (Allworx) wrote:
>>>>>>> On Wed, 24 Feb 2016 17:04:58 +0100
>>>>>>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>>>>>>>
>>>>>>>> Hi David,
>>>>>>>>
>>>>>>>> Thanks for the patch. Very nice driver. I have few comments
>>>>>>>> below.
>>>>>>>
>>>>>>> Thanks Jacek, I have responded the comments inline. I also wanted to
>>>>>>> double check whether you noticed some questions I had in the cover
>>>>>>> letter [1]. As I mentioned in another email to Rob, in hindsight I'm
>>>>>>> guessing I should have included them in the patch comments as well (or
>>>>>>> instead of).
>>>>>>
>>>>>> I saw them. I assumed that the review itself will address those
>>>>>> questions.
>>>>>
>>>>> Fair enough, thanks for the confirmation.
>>>>>
>>>>>>> Your review comments here effectively answered some of the questions, but
>>>>>>> the big one I'm still unsure of is whether it actually makes sense to
>>>>>>> have all 4 of these devices supported by a single driver.
>>>>>>
>>>>>> It's perfectly fine. Many drivers implement this pattern.
>>>>>
>>>>> OK, then I'll assume you think this driver is not yet too complicated
>>>>> for it's own good. Out of curiosity, might that view change if the
>>>>> 3216 specific features were ever implemented, especially GPIO and HW
>>>>> animation support? Gut feel is that would make 3216 specific code
>>>>> bigger than the rest of the code combined.
>>>>
>>>> I don't think so.
>>>
>>> Thanks, that helps calibrate my intuition for the future.
>>>
>>>>> Bigger question is what should be done in terms of the overlap in device
>>>>> support between this driver and leds-sn3218? If you think I should leave
>>>>> the *3218 support in this driver, then I would propose:
>>>>>      - remove leds-sn3218 and its separate binding doc
>>>>>      - add the "si-en,sn3218" compatible string to this driver and binding doc
>>>>> Note that while I expect this driver to work with the 3218 chips, I do
>>>>> not have one to test against. If we go down this route I would definitely
>>>>> want Stefan to test so that I don't accidentally break him.
>>>>
>>>> I'd prefer to have a single driver for the same hardware. Stefan, would
>>>> it be possible for you to test David's driver with the hardware you
>>>> have an access to?
>>>
>>> Stefan, one thing to note: the existing sn3218 driver/binding uses 0-based
>>> 'reg' values, and this driver/binding uses 1-based 'reg' values. So your
>>> devicetree(s) would need to be updated for that (as well as the compatible
>>> string).
>>
>> Actually, If your driver can successfully handle Si-En SN3218 I'd prefer
>> to drop leds-sn3218 along with its bindings and add related compatible
>> to your bindings documentation.
>
> Agreed. The changing of compatible string would only need to be done with
> the current version of the series.
> In the next version I'll add a 4th patch (unless you'd prefer a separate
> patch not part of the series?) that removes leds-sn3218 and moves that
> support into the is31fl32xx driver.

Please add it as 4th patch to this set.

>>> I didn't see a final answer from Rob as to which way is most appropriate
>>> for these devices yet, so I don't know which way this will end up in the
>>> final patch.
>>>
>>>>> Also I feel I should point out some differences between the 3218 support
>>>>> in this driver versus the leds-sn3218 driver, in case they have any
>>>>> impact:
>>>>>      - (as previously mentioned) leds-sn3218 turns off an LEDs enable
>>>>>        bit if the brightness is set to 0. This driver just sets the PWM
>>>>>        to 0 and leaves the enable bits always on.
>>>>
>>>> Setting brightness to 0 is an equivalent to turning the device in
>>>> a power down mode or at least in the state where the current consumption
>>>> is as low as possible. A hardware configuration that is most fitting
>>>> for this requirements should be chosen.
>>>
>>> As far as I can tell from the datasheets, setting the PWM duty cycle for
>>> a given channel to 0 should have the same net effect as setting the enable
>>> bit of that channel to 0. I assume the purpose of the enable bits is to
>>> make it easier to turn an LED on/off without adjusting the PWM duty cycle,
>>> but just using always the PWM duty cycle register conveniently maps to
>>> the leds API.
>>
>> ack.
>>
>>>>>      - leds-sn3218 uses a regmap, I think mostly to deal with the enable
>>>>>        bits, but it also has the benefit of showing up in debugfs. This
>>>>>        could be seen as useful in and of itself by some users. On the other
>>>>>        hand regmap introduces another mutex on every write.
>>>>
>>>> I will not insist on using regmap if you don't refer to the current
>>>> state of hw registers in your driver.
>>>
>>> Currently I have not had a need to refer to the current state of any HW
>>> registers. I could imagine that might be needed in the future if extra
>>> functionality is implemented, but it wasn't so far.
>>
>> Regmap exposes nice debugfs interface, so this, and the fact that there
>> are uncovered hw features, can be thought of as a sufficient
>> argument in favour of using regmap even now. But it's up to you.
>
> If it's OK with you, I think I'll leave it without regmap for now. I
> don't really relish the thought of having 4 large blocks of reg_default
> (especially the 3216 has a large register set for animation), and I
> haven't yet worked out how/if I could dynamically generate them from
> the chipdefs in a reasonable way.

I'm OK with it.

>>>>>      - leds-sn3218 implements the shutdown callback. Actually, I think I
>>>>>        should add that to this driver in any event.
>>>>
>>>> Do you see use cases or hardware configurations that need such
>>>> a callback? I didn't oppose in case of Stefan's driver, but if we are
>>>> at it, we can consult Stefan if he saw that use case?
>>>>
>>>> I'd say that shutdown op is usually useful when CPU and LED
>>>> controller are powered from different sources, which can result in
>>>> a situation when CPU is turned off, but LED remains on.
>>>
>>> That is exactly what happens today on my board: if the system is rebooted
>>> or shut down the LEDs all stay in whatever state they were last in. This
>>> could also be handled by userspace shutdown scripts easily enough, but I
>>> thought it surprising when the system reboots but LEDs stay on.
>>
>> That's the shutdown callback is for.
>
> I'll add a shutdown callback that will do the same as the remove callback,
> and ensure all LEDs go dark.
> Is there anything verboten with having one just call the other, or just
> registering the same function for both callbacks?

Please enclose current content of your remove callback in a new
function, and call this function from both remove and shutdown.

>>> On the
>>> other hand someone might have a good reason to want to leave an LED on
>>> through a reboot.
>>
>> If you have such a use case, then you can add DT property for this.
>> E.g. retain-state-on-shutdown.
>
> I don't have such a use case right now, but noted for the future.
>
>>> There is also an inconsistency on what happens during remove() vs shutdown().
>>> In led_classdev_unregister() brightness is set to LED_OFF, so that happens
>>> for all drivers on remove(). But only some drivers implement a shutdown()
>>> which also turns off LEDs, most do not. For instance, leds-gpio turns off
>>> all LEDs, but leds-pwm does not.
>>   >
>>> Is the general policy that LEDs should be forced off by the driver when the
>>> kernel halts or reboots the CPU? Or left alone and let userspace deal with
>>> it?
>>
>> I think that this depends on use cases and hardware configurations
>> available on the market. People added the callback when they needed it.
>> There is no defined policy for this.
>>
>>> And should this (in principle) be the same as what happens when a module
>>> is unloaded (which currently always turns LEDs off)?
>>
>> Turning the LED off on removal is logically justified IMO.
>
> Agreed. I just found the inconsistency is some/most drivers between remove
> and shutdown unexpected. Given that not all LED drivers turn off on shutdown,
> (and my use case desires them to turn off, including a leds-pwm instance),
> I'll just write 0 to /sys/class/leds/*/brightness unconditionally in a
> userspace shutdown script.

Doesn't it stand in contradiction with your above statement?:

"I'll add a shutdown callback that will do the same as the remove callback,"

I'm a bit lost - does your current is31fl32xx_remove() turn all
the sub-LEDs off?

>>>>>      - leds-sn3218 just puts the chip in software-shutdown mode on remove/
>>>>>        shutdown. This driver uses the reset register to put the device in
>>>>>        poweron state, and software-shutdown is part of the poweron state.
>>>>>        Only difference would be if the next code to use the device does
>>>>>        not do it's own full initialization (which seems unlikely, or at
>>>>>        least unwise), but instead just clears software-shutdown.
>>>>
>>>> I believe that my above explanations address this question, i.e.
>>>> both brightness = 0 an remove/shutdown should set the device
>>>> in a power down mode.
>>>
>>> I think there is some confusion, there are 3 separate controls:
>>>    - per-LED PWM duty cycle
>>>    - per-LED enable bit
>>>    - device-wide shutdown mode
>>> Shutdown-mode results in all LEDs going dark (regardless of any other
>>> register state), and I think implies that it also uses less power
>>> (compared to just turning them all off with either of the other controls).
>>> Registers do retain their value and the I2C interface continues to work.
>>> I suspect that all it does is turn off an internal oscillator that drives
>>> the PWM circuits, but the documentation is not clear.
>>>
>>> The distinction I was making was that the leds-sn3218 driver *only* turned
>>> on the Shutdown-mode, while this driver reset all other registers in the
>>> device to their default values as well. Though in practice I don't expect
>>> that to make a difference.
>>
>> If documentation isn't clear about that, you can always measure current
>> consumption in both cases. Note, that this is not required, you can
>> follow your intuition.
>
> I may try to do that out of curiosity.
>
>>>>>>> I won't
>>>>>>> clutter this email with a duplicate of the details (it's somewhat long),
>>>>>>> but if you could check the cover letter and give some guidance, I would
>>>>>>> appreciate it.
>>>>>>>
>>>>>>> [1] http://www.spinics.net/lists/linux-leds/msg05564.html
>>>>>>>         http://thread.gmane.org/gmane.linux.leds/4530
>>>>>>>
>>>>>>>>
>>>>>>>> On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:
>>>>>>>>> From: David Rivshin <drivshin@allworx.com>
>>>>>>>>>
>>>>>>>>> The IS31FL32xx family of LED drivers are I2C devices with multiple
>>>>>>>>> constant-current channels, each with independent 256-level PWM control.
>>>>>>>>>
>>>>>>>>> HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
>>>>>>>>>
>>>>>>>>> This has been tested on the IS31FL3236 and IS31FL3216 on an ARM
>>>>>>>>> (TI am335x) platform.
>>>>>>>>>
>>>>>>>>> The programming paradigm of these devices is similar in the following
>>>>>>>>> ways:
>>>>>>>>>       - All registers are 8 bit
>>>>>>>>>       - All LED control registers are write-only
>>>>>>>>>       - Each LED channel has a PWM register (0-255)
>>>>>>>>>       - PWM register writes are shadowed until an Update register is poked
>>>>>>>>>       - All have a concept of Software Shutdown, which disables output
>>>>>>>>>
>>>>>>>>> However, there are some differences in devices:
>>>>>>>>>       - 3236/3235 have a separate Control register for each LED,
>>>>>>>>>         (3218/3216 pack the enable bits into fewer registers)
>>>>>>>>>       - 3236/3235 have a per-channel current divisor setting
>>>>>>>>>       - 3236/3235 have a Global Control register that can turn off all LEDs
>>>>>>>>>       - 3216 is unique in a number of ways
>>>>>>>>>          - OUT9-OUT16 can be configured as GPIOs instead of LED controls
>>>>>>>>>          - LEDs can be programmed with an 8-frame animation, with
>>>>>>>>>            programmable delay between frames
>>>>>>>>>          - LEDs can be modulated by an input audio signal
>>>>>>>>>          - Max output current can be adjusted from 1/4 to 2x globally
>>>>>>>>>          - Has a Configuration register instead of a Shutdown register
>>>>>>>>>
>>>>>>>>> This driver currently only supports the base PWM control function
>>>>>>>>> of these devices. The following features of these devices are not
>>>>>>>>> implemented, although it should be possible to add them in the future:
>>>>>>>>>       - All devices are capable of going into a lower-power "software
>>>>>>>>>         shutdown" mode.
>>>>>>>>>       - The is31fl3236 and is31fl3235 can reduce the max output current
>>>>>>>>>         per-channel with a divisor of 1, 2, 3, or 4.
>>>>>>>>>       - The is31fl3216 can use some LED channels as GPIOs instead.
>>>>>>>>>       - The is31fl3216 can animate LEDs in hardware.
>>>>>>>>>       - The is31fl3216 can modulate LEDs according to an audio input.
>>>>>>>>>       - The is31fl3216 can reduce/increase max output current globally.
>>>>>>>>>
>>>>>>>>> Signed-off-by: David Rivshin <drivshin@allworx.com>
>>>>>>>>> ---
>>>>>>>>>       drivers/leds/Kconfig           |   9 +
>>>>>>>>>       drivers/leds/Makefile          |   1 +
>>>>>>>>>       drivers/leds/leds-is31fl32xx.c | 442 +++++++++++++++++++++++++++++++++++++++++
>>>>>>>>>       3 files changed, 452 insertions(+)
>>>>>>>>>       create mode 100644 drivers/leds/leds-is31fl32xx.c
>>>>>>>>>
>>>>>>>>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>>>>>>>>> index 1034696..8f6c46f 100644
>>>>>>>>> --- a/drivers/leds/Kconfig
>>>>>>>>> +++ b/drivers/leds/Kconfig
>>>>>>>>> @@ -580,6 +580,15 @@ config LEDS_SN3218
>>>>>>>>>       	  This driver can also be built as a module. If so the module
>>>>>>>>>       	  will be called leds-sn3218.
>>>>>>>>>
>>>>>>>>> +config LEDS_IS31FL32XX
>>>>>>>>> +	tristate "Driver for ISSI IS31FL32XX I2C LED driver chip family"
>>>>>>>>
>>>>>>>> 2 x "[Dd]river".
>>>>>>>>
>>>>>>>> How about:
>>>>>>>>
>>>>>>>> "LED Support for ISSI IS31FL32XX I2C LED chip family" ?
>>>>>>>
>>>>>>> Yes, I found that awkward as well. HW folks (and the datasheets) seem
>>>>>>> always refer to devices of this type as "LED Driver"s (which can lead
>>>>>>> to some interesting confusions). Taking a cue from the LP5521/23/62
>>>>>>> entries, how about:
>>>>>>>      "LED Support for the ISSI IS31FL32XX I2C LED driver chip family" ?
>>>>>>
>>>>>> "LED Support" means "LED class driver". Driver is a software support
>>>>>> for hardware chip. What discrepancy do you see in the description
>>>>>> I proposed?
>>>>>
>>>>> I think in this case "driver" also means "hardware device which drives
>>>>> a physical LED".
>>>>
>>>> Let's not confuse these notions. From Linux perspective "driver" refers
>>>> to a piece of software used for controlling a hardware.
>>>>
>>>>> It seems that "LED driver" is the term universally used
>>>>> to describe this type of HW device in datasheets.
>>>>
>>>> There are also e.g. "LED controllers", "LED current regulators".
>>>> Let's stick to the convention predominantly used in the LED subsystem
>>>> kernel config menu.
>>>>
>>>>> So it seemed useful to
>>>>> use exactly that phrase in the description of what hardware this software
>>>>> supports. I could see someone interpreting the phrase "LED chip" as
>>>>> referring to an actual LED device.
>>>>> I don't feel very strongly on this topic, but for the sake of discussion,
>>>>> maybe "LED controller" would avoid any possible confusion in both
>>>>> directions?
>>>>
>>>> Right, so let's use the following:
>>>>
>>>> "LED Support for ISSI IS31FL32XX I2C LED controller family"
>>>>
>>>> I understand "LED Support" as "Linux LED subsystem support".
>>>
>>> STGM. Done.
>>>
>>>>>>> Perhaps that's the best of both worlds?
>>>>>>>
>>>>>>>>> +	depends on LEDS_CLASS && I2C && OF
>>>>>>>>> +	help
>>>>>>>>> +	  Say Y here to include support for the ISSI 31FL32XX LED driver family.
>>>>>>>>
>>>>>>>> s/driver/chip/
>>>>>>>>
>>>>>>>>> +	  They are I2C devices with multiple constant-current channels, each
>>>>>>>>> +	  with independent 256-level PWM control. This will only work with
>>>>>>>>> +	  device tree enabled devices.
>>>>>>>>
>>>>>>>> We can skip the last sentence I think.
>>>>>>>
>>>>>>> OK. FYI, I think I got that verbiage from LEDS_SYSCON.
>>>>>>
>>>>>> Having "depends on OF" is self-explanatory here.
>>>>>
>>>>> Noted.
>>>>>
>>>>>>>>> +
>>>>>>>>>       comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
>>>>>>>>>
>>>>>>>>>       config LEDS_BLINKM
>>>>>>>>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
>>>>>>>>> index 89c9b6f..3fdf313 100644
>>>>>>>>> --- a/drivers/leds/Makefile
>>>>>>>>> +++ b/drivers/leds/Makefile
>>>>>>>>> @@ -67,6 +67,7 @@ obj-$(CONFIG_LEDS_KTD2692)		+= leds-ktd2692.o
>>>>>>>>>       obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
>>>>>>>>>       obj-$(CONFIG_LEDS_SEAD3)		+= leds-sead3.o
>>>>>>>>>       obj-$(CONFIG_LEDS_SN3218)		+= leds-sn3218.o
>>>>>>>>> +obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
>>>>>>>>>
>>>>>>>>>       # LED SPI Drivers
>>>>>>>>>       obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
>>>>>>>>> diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
>>>>>>>>> new file mode 100644
>>>>>>>>> index 0000000..8dea518
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/drivers/leds/leds-is31fl32xx.c
>>>>>>>>> @@ -0,0 +1,442 @@
>>>>>>>>> +/*
>>>>>>>>> + * linux/drivers/leds-is31fl32xx.c
>>>>>>>>> + *
>>>>>>>>> + * Driver for ISSI IS31FL32xx family of I2C LED controllers
>>>>>>>>> + *
>>>>>>>>> + * Copyright 2015 Allworx Corp.
>>>>>>>>> + *
>>>>>>>>> + *
>>>>>>>>> + * 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.
>>>>>>>>> + *
>>>>>>>>> + * HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
>>>>>>>>> + */
>>>>>>>>> +
>>>>>>>>> +#include <linux/err.h>
>>>>>>>>> +#include <linux/i2c.h>
>>>>>>>>> +#include <linux/kernel.h>
>>>>>>>>> +#include <linux/leds.h>
>>>>>>>>> +#include <linux/module.h>
>>>>>>>>> +#include <linux/of_platform.h>
>>>>>>>>> +
>>>>>>>>> +#ifdef DEBUG
>>>>>>>>> + #undef dev_dbg
>>>>>>>>> + #define dev_dbg dev_info
>>>>>>>>> +#endif
>>>>>>>>
>>>>>>>> What's the benefit of the above?
>>>>>>>
>>>>>>> It gave me a way to easily see debug output from the driver while it
>>>>>>> was parsing the DT (especially if the driver was built-in). Early on
>>>>>>> there were other things within that #ifdef as well.
>>>>>>> Regardless, passing ddebug_query on the kernel commandline is a more
>>>>>>> appropriate way of accomplishing that; I'll remove for the next version.
>>>>>>
>>>>>> Thanks.
>>>>>>
>>>>>>>>> +/* Used to indicate a device has no such register */
>>>>>>>>> +#define IS31FL32XX_REG_NONE 0xFF
>>>>>>>>> +
>>>>>>>>> +#define IS31FL3216_CONFIG_REG 0x00
>>>>>>>>> +#define IS31FL3216_LIGHTING_EFFECT_REG 0x03
>>>>>>>>> +#define IS31FL3216_CHANNEL_CONFIG_REG 0x04
>>>>>>>>> +
>>>>>>>>> +struct is31fl32xx_priv;
>>>>>>>>> +struct is31fl32xx_led_data {
>>>>>>>>> +	struct led_classdev cdev;
>>>>>>>>> +	u8 channel; /* 1-based, max priv->cdef->channels */
>>>>>>>>> +	struct is31fl32xx_priv *priv;
>>>>>>>>> +};
>>>>>>>>> +
>>>>>>>>> +struct is31fl32xx_priv {
>>>>>>>>> +	const struct is31fl32xx_chipdef *cdef;
>>>>>>>>> +	struct i2c_client *client;
>>>>>>>>> +	unsigned int num_leds;
>>>>>>>>> +	struct is31fl32xx_led_data leds[0];
>>>>>>>>
>>>>>>>> Is there any specific reason for not having *leds here instead?
>>>>>>>
>>>>>>> I followed a pattern from leds-pwm where it did a single allocation
>>>>>>> for both priv and priv->leds[]. See sizeof_is31fl32xx_priv(), and
>>>>>>> its use, below. I saw the benefit as one fewer small allocation, so
>>>>>>> slightly more kind to the allocator (and devres). If you'd prefer to
>>>>>>> do it as two allocations, I'll make the change.
>>>>>>
>>>>>> OK, I had to look at this one more time. I like the idea.
>>>>>
>>>>> OK, I'll keep it as-is.
>>>>>
>>>>>>>>> +};
>>>>>>>>> +
>>>>>>>>> +/**
>>>>>>>>> + * struct is31fl32xx_chipdef - chip-specific attributes
>>>>>>>>> + * @channels            : Number of LED channels
>>>>>>>>> + * @shutdown_reg        : address of Shutdown register (optional)
>>>>>>>>> + * @pwm_update_reg      : address of PWM Update register
>>>>>>>>> + * @global_control_reg	: address of Global Control register (optional)
>>>>>>>>> + * @reset_reg           : address of Reset register (optional)
>>>>>>>>> + * @pwm_register_base	: address of first PWM register
>>>>>>>>> + * @pwm_registers_reversed: : true if PWM registers count down instead of up
>>>>>>>>> + * @led_control_register_base : address of first LED control register (optional)
>>>>>>>>> + * @enable_bits_per_led_control_register: number of LEDs enable bits in each
>>>>>>>>> + * @reset_func:         : pointer to reset function
>>>>>>>>> + *
>>>>>>>>> + * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE
>>>>>>>>> + * indicates that this chip has no such register.
>>>>>>>>> + *
>>>>>>>>> + * If non-NULL, @reset_func will be called during probing to set all
>>>>>>>>> + * necessary registers to a known initialization state. This is needed
>>>>>>>>> + * for chips that do not have a @reset_reg.
>>>>>>>>> + *
>>>>>>>>> + * @enable_bits_per_led_control_register must be >=1 if
>>>>>>>>> + * @led_control_register_base != %IS31FL32XX_REG_NONE.
>>>>>>>>> + */
>>>>>>>>> +struct is31fl32xx_chipdef {
>>>>>>>>> +	u8	channels;
>>>>>>>>> +	u8	shutdown_reg;
>>>>>>>>> +	u8	pwm_update_reg;
>>>>>>>>> +	u8	global_control_reg;
>>>>>>>>> +	u8	reset_reg;
>>>>>>>>> +	u8	pwm_register_base;
>>>>>>>>> +	bool	pwm_registers_reversed;
>>>>>>>>> +	u8	led_control_register_base;
>>>>>>>>> +	u8	enable_bits_per_led_control_register;
>>>>>>>>> +	int (*reset_func)(struct is31fl32xx_priv *priv);
>>>>>>>>> +};
>>>>>>>>> +
>>>>>>>>> +static const struct is31fl32xx_chipdef is31fl3236_cdef = {
>>>>>>>>> +	.channels				= 36,
>>>>>>>>> +	.shutdown_reg				= 0x00,
>>>>>>>>> +	.pwm_update_reg				= 0x25,
>>>>>>>>> +	.global_control_reg			= 0x4a,
>>>>>>>>> +	.reset_reg				= 0x4f,
>>>>>>>>> +	.pwm_register_base			= 0x01,
>>>>>>>>> +	.led_control_register_base		= 0x26,
>>>>>>>>> +	.enable_bits_per_led_control_register	= 1,
>>>>>>>>> +};
>>>>>>>>> +
>>>>>>>>> +static const struct is31fl32xx_chipdef is31fl3235_cdef = {
>>>>>>>>> +	.channels				= 28,
>>>>>>>>> +	.shutdown_reg				= 0x00,
>>>>>>>>> +	.pwm_update_reg				= 0x25,
>>>>>>>>> +	.global_control_reg			= 0x4a,
>>>>>>>>> +	.reset_reg				= 0x4f,
>>>>>>>>> +	.pwm_register_base			= 0x05,
>>>>>>>>> +	.led_control_register_base		= 0x2a,
>>>>>>>>> +	.enable_bits_per_led_control_register	= 1,
>>>>>>>>> +};
>>>>>>>>> +
>>>>>>>>> +static const struct is31fl32xx_chipdef is31fl3218_cdef = {
>>>>>>>>> +	.channels				= 18,
>>>>>>>>> +	.shutdown_reg				= 0x00,
>>>>>>>>> +	.pwm_update_reg				= 0x16,
>>>>>>>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
>>>>>>>>> +	.reset_reg				= 0x17,
>>>>>>>>> +	.pwm_register_base			= 0x01,
>>>>>>>>> +	.led_control_register_base		= 0x13,
>>>>>>>>> +	.enable_bits_per_led_control_register	= 6,
>>>>>>>>> +};
>>>>>>>>> +
>>>>>>>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv);
>>>>>>>>> +static const struct is31fl32xx_chipdef is31fl3216_cdef = {
>>>>>>>>> +	.channels				= 16,
>>>>>>>>> +	.shutdown_reg				= IS31FL32XX_REG_NONE,
>>>>>>>>> +	.pwm_update_reg				= 0xB0,
>>>>>>>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
>>>>>>>>> +	.reset_reg				= IS31FL32XX_REG_NONE,
>>>>>>>>> +	.pwm_register_base			= 0x10,
>>>>>>>>> +	.pwm_registers_reversed			= true,
>>>>>>>>> +	.led_control_register_base		= 0x01,
>>>>>>>>> +	.enable_bits_per_led_control_register	= 8,
>>>>>>>>> +	.reset_func				= is31fl3216_reset,
>>>>>>>>> +};
>>>>>>>>> +
>>>>>>>>> +static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)
>>>>>>>>> +{
>>>>>>>>> +	int ret;
>>>>>>>>> +
>>>>>>>>> +	dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
>>>>>>>>> +
>>>>>>>>> +	ret =  i2c_smbus_write_byte_data(priv->client, reg, val);
>>>>>>>>> +	if (ret) {
>>>>>>>>> +		dev_err(&priv->client->dev,
>>>>>>>>> +			"register write to 0x%02X failed (error %d)",
>>>>>>>>> +			reg, ret);
>>>>>>>>> +	}
>>>>>>>>> +	return ret;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +/*
>>>>>>>>> + * Custom reset function for IS31FL3216 because it does not have a RESET
>>>>>>>>> + * register the way that the other IS31FL32xx chips do. We don't bother
>>>>>>>>> + * writing the GPIO and animation registers, because the registers we
>>>>>>>>> + * do write ensure those will have no effect.
>>>>>>>>> + */
>>>>>>>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv)
>>>>>>>>> +{
>>>>>>>>> +	unsigned int i;
>>>>>>>>> +	int ret;
>>>>>>>>> +
>>>>>>>>> +	for (i = 0; i < priv->cdef->channels; i++) {
>>>>>>>>> +		ret = is31fl32xx_write(priv, priv->cdef->pwm_register_base+i,
>>>>>>>>> +				       0x00);
>>>>>>>>> +		if (ret)
>>>>>>>>> +			return ret;
>>>>>>>>> +	}
>>>>>>>>> +	ret = is31fl32xx_write(priv, priv->cdef->pwm_update_reg, 0);
>>>>>>>>> +	if (ret)
>>>>>>>>> +		return ret;
>>>>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, 0x00);
>>>>>>>>> +	if (ret)
>>>>>>>>> +		return ret;
>>>>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, 0x00);
>>>>>>>>> +	if (ret)
>>>>>>>>> +		return ret;
>>>>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, 0x00);
>>>>>>>>> +	if (ret)
>>>>>>>>> +		return ret;
>>>>>>>>> +
>>>>>>>>> +	return 0;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,
>>>>>>>>> +				     enum led_brightness brightness)
>>>>>>>>> +{
>>>>>>>>> +	const struct is31fl32xx_led_data *led_data =
>>>>>>>>> +		container_of(led_cdev, struct is31fl32xx_led_data, cdev);
>>>>>>>>> +	const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef;
>>>>>>>>> +	u8 pwm_register_offset;
>>>>>>>>> +	int ret;
>>>>>>>>> +
>>>>>>>>> +	dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);
>>>>>>>>> +
>>>>>>>>> +	/* NOTE: led_data->channel is 1-based */
>>>>>>>>> +	if (cdef->pwm_registers_reversed)
>>>>>>>>> +		pwm_register_offset = cdef->channels - led_data->channel;
>>>>>>>>> +	else
>>>>>>>>> +		pwm_register_offset = led_data->channel - 1;
>>>>>>>>> +
>>>>>>>>> +	ret = is31fl32xx_write(led_data->priv,
>>>>>>>>> +			       cdef->pwm_register_base + pwm_register_offset,
>>>>>>>>> +			       brightness);
>>>>>>>>> +	if (ret)
>>>>>>>>> +		return ret;
>>>>>>>>
>>>>>>>> I infer that nothing wrong happens in case current process is preempted
>>>>>>>> here by the call originating from the other sub-LED?
>>>>>>>
>>>>>>> I do not believe so. All the driver-specific data used here is read-only
>>>>>>> after probing. chipdefs are entirely const, and the only thing in priv
>>>>>>> that's referenced is the chipdef pointer which logically could not change
>>>>>>> post-probe. Actually nothing else in priv is modified post-probe either.
>>>>>>>
>>>>>>> The I2C core code has a mutex on the bus, so two writes cannot happen at
>>>>>>> once.
>>>>>>>
>>>>>>> In all these devices there is a unique PWM duty-cycle register for each
>>>>>>> LED channel (which is what is being written here), so no register writes
>>>>>>> for one LED channel effect any others.
>>>>>>>
>>>>>>> I believe the worst that could happen is that the device would see:
>>>>>>> 	PWM_REG_A write X
>>>>>>> 	PWM_REG_B write Y
>>>>>>> 	UPDATE_REG write 0
>>>>>>> 	UPDATE_REG write 0
>>>>>>> instead of
>>>>>>> 	PWM_REG_A write X
>>>>>>> 	UPDATE_REG write 0
>>>>>>> 	PWM_REG_B write Y
>>>>>>> 	UPDATE_REG write 0
>>>>>>> but that makes no difference to the functionality. Poking the update
>>>>>>> register merely applies all PWM register writes up to that point (I'm
>>>>>>> assuming to allow atomically changing the state of multiple LEDs at
>>>>>>> once).
>>>>>>
>>>>>> Thanks for this comprehensive explanation.
>>>>>
>>>>> Should I put some part of this explanation in a comment somewhere? Seems
>>>>> like the kind of thing someone else might wonder about in the future also.
>>>>
>>>> Good idea.
>>>
>>> Done.
>>>
>>>>>>> I should note here (as mentioned in cover letter), I made a choice to
>>>>>>> always leave the per-LED "enable" bits on, and let the PWM just get set
>>>>>>> to 0 naturally to turn an LED off. This differs from the existing SN3218
>>>>>>> driver, which used regmap_update_bits, and is then protected by a per-
>>>>>>> regmap mutex.
>>>>>>
>>>>>> ack.
>>>>>>
>>>>>>>>> +	return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);
>>>>>>>>> +
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
>>>>>>>>> +{
>>>>>>>>> +	const struct is31fl32xx_chipdef *cdef = priv->cdef;
>>>>>>>>> +	int ret;
>>>>>>>>> +
>>>>>>>>> +	if (cdef->reset_reg != IS31FL32XX_REG_NONE) {
>>>>>>>>> +		ret = is31fl32xx_write(priv, cdef->reset_reg, 0);
>>>>>>>>> +		if (ret)
>>>>>>>>> +			return ret;
>>>>>>>>> +	}
>>>>>>>>> +	if (cdef->reset_func) {
>>>>>>>>> +		ret = cdef->reset_func(priv);
>>>>>>>>> +		if (ret)
>>>>>>>>> +			return ret;
>>>>>>>>> +	}
>>>>>>>>> +	if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) {
>>>>>>>>> +		u8 value =
>>>>>>>>> +		    GENMASK(cdef->enable_bits_per_led_control_register-1, 0);
>>>>>>>>> +		u8 num_regs = cdef->channels /
>>>>>>>>> +				cdef->enable_bits_per_led_control_register;
>>>>>>>>> +		int i;
>>>>>>>>> +
>>>>>>>>> +		for (i = 0; i < num_regs; i++) {
>>>>>>>>> +			ret = is31fl32xx_write(priv,
>>>>>>>>> +					       cdef->led_control_register_base+i,
>>>>>>>>> +					       value);
>>>>>>>>> +			if (ret)
>>>>>>>>> +				return ret;
>>>>>>>>> +		}
>>>>>>>>> +	}
>>>>>>>>> +	if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) {
>>>>>>>>> +		ret = is31fl32xx_write(priv, cdef->shutdown_reg, BIT(0));
>>>>>>>>> +		if (ret)
>>>>>>>>> +			return ret;
>>>>>>>>> +	}
>>>>>>>>> +	if (cdef->global_control_reg != IS31FL32XX_REG_NONE) {
>>>>>>>>> +		ret = is31fl32xx_write(priv, cdef->global_control_reg, 0x00);
>>>>>>>>> +		if (ret)
>>>>>>>>> +			return ret;
>>>>>>>>> +	}
>>>>>>>>> +
>>>>>>>>> +	return 0;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +static inline size_t sizeof_is31fl32xx_priv(int num_leds)
>>>>>>>>> +{
>>>>>>>>> +	return sizeof(struct is31fl32xx_priv) +
>>>>>>>>> +		      (sizeof(struct is31fl32xx_led_data) * num_leds);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +static int is31fl32xx_parse_child_dt(const struct device *dev,
>>>>>>>>> +				     const struct device_node *child,
>>>>>>>>> +				     struct is31fl32xx_led_data *led_data)
>>>>>>>>> +{
>>>>>>>>> +	struct led_classdev *cdev = &led_data->cdev;
>>>>>>>>> +	int ret = 0;
>>>>>>>>> +	u32 reg;
>>>>>>>>> +
>>>>>>>>> +	cdev->name = of_get_property(child, "label", NULL) ? : child->name;
>>>>>>>>> +
>>>>>>>>> +	ret = of_property_read_u32(child, "reg", &reg);
>>>>>>>>> +	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
>>>>>>>>> +		dev_err(dev,
>>>>>>>>> +			"Child node %s does not have a valid reg property\n",
>>>>>>>>> +			child->name);
>>>>>>>>> +		return -EINVAL;
>>>>>>>>> +	}
>>>>>>>>> +	led_data->channel = reg;
>>>>>>>>> +
>>>>>>>>> +	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
>>>>>>>>> +						NULL);
>>>>>>>>> +	cdev->brightness = LED_OFF;
>>>>>>>>
>>>>>>>> devm_kzalloc secures that.
>>>>>>>
>>>>>>> OK, I will remove.
>>>>>>>
>>>>>>>>> +	ret = of_property_read_u32(child, "max-brightness",
>>>>>>>>> +				   &cdev->max_brightness);
>>>>>>>>> +	if (ret == -EINVAL) {
>>>>>>>>> +		cdev->max_brightness = 255;
>>>>>>>>
>>>>>>>> s/255/LED_FULL/
>>>>>>>
>>>>>>> Noted, although (from the patch 2 discussion) max-brightness property is
>>>>>>> removed/replaced, this would go away anyways.
>>>>>>>
>>>>>>>>> +	} else if (ret) {
>>>>>>>>> +		dev_dbg(dev,
>>>>>>>>> +			"Child node %s has an invalid max-brightness property\n",
>>>>>>>>> +			child->name);
>>>>>>>>> +		return -EINVAL;
>>>>>>>>> +	}
>>>>>>>>> +
>>>>>>>>> +	cdev->brightness_set_blocking = is31fl32xx_brightness_set;
>>>>>>>>
>>>>>>>> Please add empty line here.
>>>>>>>
>>>>>>> Done.
>>>>>>>
>>>>>>>>> +	return 0;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +static struct is31fl32xx_led_data *is31fl32xx_find_led_data(
>>>>>>>>> +					struct is31fl32xx_priv *priv,
>>>>>>>>> +					u8 channel)
>>>>>>>>> +{
>>>>>>>>> +	size_t i;
>>>>>>>>> +
>>>>>>>>> +	for (i = 0; i < priv->num_leds; i++) {
>>>>>>>>> +		if (priv->leds[i].channel == channel)
>>>>>>>>> +			return &priv->leds[i];
>>>>>>>>> +	}
>>>>>>>>
>>>>>>>> Ditto.
>>>>>>>
>>>>>>> Done.
>>>>>>>
>>>>>>>>> +	return NULL;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +static int is31fl32xx_parse_dt(struct device *dev,
>>>>>>>>> +			       struct is31fl32xx_priv *priv)
>>>>>>>>> +{
>>>>>>>>> +	struct device_node *child;
>>>>>>>>> +
>>>>>>>>> +	for_each_child_of_node(dev->of_node, child) {
>>>>>>>>> +		struct is31fl32xx_led_data *led_data =
>>>>>>>>> +			&priv->leds[priv->num_leds];
>>>>>>>>> +		int ret = 0;
>>>>>>>>> +		const struct is31fl32xx_led_data *other_led_data;
>>>>>>>>> +
>>>>>>>>> +		led_data->priv = priv;
>>>>>>>>> +
>>>>>>>>> +		ret = is31fl32xx_parse_child_dt(dev, child, led_data);
>>>>>>>>> +		if (ret)
>>>>>>>>> +			continue;
>>>>>>>>
>>>>>>>> I prefer failing in such cases,
>>>>>>>
>>>>>>> OK, I will change to an 'goto err' which will have an 'of_node_put()'
>>>>>>> and 'return ret'.
>>>>>>>
>>>>>>> I will say, however, that while testing the error-detection in the
>>>>>>> parsing logic, it was very convenient to construct a single devicetree
>>>>>>> with a variety of errors. Then a single boot would test multiple
>>>>>>> cases at once.
>>>>>>
>>>>>> Good idea for testing, but in case some failure occurs during DT child
>>>>>> node parsing in the release environment you're left with unused
>>>>>> allocated memory.
>>>>>
>>>>> Agreed. BTW, I assume from this that it's common to say "if there's
>>>>> anything wrong in one part of your DT, there is no guarantee as to what
>>>>> parts will actually be used"? I say this because what we're saying is that
>>>>> if one LED node on this device is faulty, that all of them are ignored.
>>>>> Analogy might be to a whole I2C bus being ignored because one of the
>>>>> devices on it failed to probe. To the devicetree it's still a parent/child
>>>>> bus/address relationship, even though the driver implementation is
>>>>> very different.
>>>>
>>>> I2C example is too generic I think. I2C controller is not as tightly
>>>> coupled with I2C clients as LED controller with its current outputs.
>>>> Besides, I2C controller doesn't have an idea what devices will attach
>>>> to the bus it controls upon probing, contrarily to a LED controller.
>>>
>>> OK. I realize that in code there is a large distinction in these cases,
>>> but I wasn't sure if that would be reflected in how errors in parsing
>>> the devicetree should handled. Sounds like there is at least a de-facto
>>> distinction between "a device and its children" and "a bus and its
>>> children".
>>>
>>>>>>>>> +
>>>>>>>>> +		/* Detect if channel is already in use by another child */
>>>>>>>>> +		other_led_data = is31fl32xx_find_led_data(priv,
>>>>>>>>> +							  led_data->channel);
>>>>>>>>> +		if (other_led_data) {
>>>>>>>>> +			dev_err(dev,
>>>>>>>>> +				"%s ignored: channel %d already used by %s",
>>>>>>>>> +				led_data->cdev.name,
>>>>>>>>> +				led_data->channel,
>>>>>>>>> +				other_led_data->cdev.name);
>>>>>>>>> +			continue;
>>>>>>>>
>>>>>>>> Ditto.
>>>>>>>
>>>>>>> OK.
>>>>>>>
>>>>>>>>> +		}
>>>>>>>>> +
>>>>>>>>> +		ret = devm_led_classdev_register(dev, &led_data->cdev);
>>>>>>>>> +		if (ret == 0) {
>>>>>>>>> +			priv->num_leds++;
>>>>>>>>> +		} else {
>>>>>>>>> +			dev_err(dev, "failed to register PWM led for %s: %d\n",
>>>>>>>>> +				led_data->cdev.name, ret);
>>>>>>>
>>>>>>> Should I also fail here, then? Right now it will continue trying to
>>>>>>> register future LED devices if a classdev_register fails for some
>>>>>>> reason, and will successfully load even if all of them fail.
>>>>>>
>>>>>> Please fail here too. If we can't setup the sub-LED that is advertised
>>>>>> in a DT child node, then it means that something went wrong.
>>>>>> This is clear error case.
>>>>>
>>>>> Done.
>>>>>
>>>>>>>>> +		}
>>>>>>>>> +	}
>>>>>>>>> +
>>>>>>>>> +	return 0;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +static const struct of_device_id of_is31fl31xx_match[] = {
>>>>>>>>> +	{ .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },
>>>>>>>>> +	{ .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },
>>>>>>>>> +	{ .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, },
>>>>>>>>> +	{ .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, },
>>>>>>>>> +	{},
>>>>>>>>> +};
>>>>>>>>> +
>>>>>>>>> +MODULE_DEVICE_TABLE(of, of_is31fl31xx_match);
>>>>>>>>> +
>>>>>>>>> +static int is31fl32xx_probe(struct i2c_client *client,
>>>>>>>>> +				  const struct i2c_device_id *id)
>>>>>>>>> +{
>>>>>>>>> +	const struct is31fl32xx_chipdef *cdef;
>>>>>>>>> +	const struct of_device_id *of_dev_id;
>>>>>>>>> +	struct device *dev = &client->dev;
>>>>>>>>> +	struct is31fl32xx_priv *priv;
>>>>>>>>> +	int count;
>>>>>>>>> +	int ret = 0;
>>>>>>>>> +
>>>>>>>>> +	of_dev_id = of_match_device(of_is31fl31xx_match, dev);
>>>>>>>>> +	if (!of_dev_id)
>>>>>>>>> +		return -EINVAL;
>>>>>>>>> +
>>>>>>>>> +	cdef = of_dev_id->data;
>>>>>>>>> +
>>>>>>>>> +	count = of_get_child_count(dev->of_node);
>>>>>>>>> +	if (!count)
>>>>>>>>> +		return -EINVAL;
>>>>>>>>> +
>>>>>>>>> +	priv = devm_kzalloc(dev, sizeof_is31fl32xx_priv(count),
>>>>>>>>> +			    GFP_KERNEL);
>>>>>>>>> +	if (!priv)
>>>>>>>>> +		return -ENOMEM;
>>>>>>>>> +
>>>>>>>>> +	priv->client = client;
>>>>>>>>> +	priv->cdef = cdef;
>>>>>>>>> +	i2c_set_clientdata(client, priv);
>>>>>>>>> +
>>>>>>>>> +	ret = is31fl32xx_init_regs(priv);
>>>>>>>>> +	if (ret)
>>>>>>>>> +		return ret;
>>>>>>>>> +
>>>>>>>>> +	ret = is31fl32xx_parse_dt(dev, priv);
>>>>>>>>> +	if (ret)
>>>>>>>>> +		return ret;
>>>>>>>>> +
>>>>>>>>> +	return 0;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +static int is31fl32xx_remove(struct i2c_client *client)
>>>>>>>>> +{
>>>>>>>>> +	struct is31fl32xx_priv *priv = i2c_get_clientdata(client);
>>>>>>>>> +
>>>>>>>>> +	/* If there is a reset reg, then it does everything we need */
>>>>>>>>> +	if (priv->cdef->reset_reg != IS31FL32XX_REG_NONE)
>>>>>>>>> +		return is31fl32xx_write(priv, priv->cdef->reset_reg, 0);
>>>>>>>>> +
>>>>>>>>> +	/* If there is a reset func, then it does everything we need */
>>>>>>>>> +	if (priv->cdef->reset_func)
>>>>>>>>> +		return priv->cdef->reset_func(priv);
>>>>>>>>> +
>>>>>>>>> +	/* If we can't reset, then try just using software-shutdown mode */
>>>>>>>>> +	if (priv->cdef->shutdown_reg != IS31FL32XX_REG_NONE)
>>>>>>>>> +		return is31fl32xx_write(priv, priv->cdef->shutdown_reg, 0x00);
>>>>>>>>> +
>>>>>>>>> +	return 0;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +/*
>>>>>>>>> + * i2c-core requires that id_table be non-NULL, even though
>>>>>>>>> + * it is not used for DeviceTree based instantiation.
>>>>>>>>> + */
>>>>>>>>> +static const struct i2c_device_id is31fl31xx_id[] = {
>>>>>>>>> +	{},
>>>>>>>>> +};
>>>>>>>>> +
>>>>>>>>> +MODULE_DEVICE_TABLE(i2c, is31fl31xx_id);
>>>>>>>>> +
>>>>>>>>> +static struct i2c_driver is31fl32xx_driver = {
>>>>>>>>> +	.driver = {
>>>>>>>>> +		.name	= "is31fl32xx",
>>>>>>>>> +		.of_match_table = of_is31fl31xx_match,
>>>>>>>>> +	},
>>>>>>>>> +	.probe		= is31fl32xx_probe,
>>>>>>>>> +	.remove		= is31fl32xx_remove,
>>>>>>>>> +	.id_table	= is31fl31xx_id,
>>>>>>>>> +};
>>>>>>>>> +
>>>>>>>>> +module_i2c_driver(is31fl32xx_driver);
>>>>>>>>> +
>>>>>>>>> +MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>");
>>>>>>>>> +MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver");
>>>>>>>>> +MODULE_LICENSE("GPL v2");
>>>>>>>>>
> --
> To unsubscribe from this list: send the line "unsubscribe devicetree" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>
>


-- 
Best regards,
Jacek Anaszewski

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-03-01  8:24                   ` Jacek Anaszewski
@ 2016-03-01 18:45                     ` David Rivshin (Allworx)
  2016-03-02  8:15                       ` Jacek Anaszewski
  0 siblings, 1 reply; 33+ messages in thread
From: David Rivshin (Allworx) @ 2016-03-01 18:45 UTC (permalink / raw)
  To: Jacek Anaszewski
  Cc: Stefan Wahren, linux-leds, devicetree, Richard Purdie,
	Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala

On Tue, 01 Mar 2016 09:24:59 +0100
Jacek Anaszewski <j.anaszewski@samsung.com> wrote:

> On 02/29/2016 07:26 PM, David Rivshin (Allworx) wrote:
> > On Mon, 29 Feb 2016 10:47:44 +0100
> > Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> >  
> >> On 02/26/2016 10:58 PM, David Rivshin (Allworx) wrote:  
> >>> On Fri, 26 Feb 2016 10:47:46 +0100
> >>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> >>>  
> >>>> On 02/25/2016 08:12 PM, David Rivshin (Allworx) wrote:  
> >>>>> On Thu, 25 Feb 2016 11:55:58 +0100
> >>>>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> >>>>>  
> >>>>>> On 02/25/2016 03:24 AM, David Rivshin (Allworx) wrote:  
> >>>>>>> On Wed, 24 Feb 2016 17:04:58 +0100
> >>>>>>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
> >>>>>>>  
> >>>>>>>> Hi David,
> >>>>>>>>
> >>>>>>>> Thanks for the patch. Very nice driver. I have few comments
> >>>>>>>> below.  
> >>>>>>>
> >>>>>>> Thanks Jacek, I have responded the comments inline. I also wanted to
> >>>>>>> double check whether you noticed some questions I had in the cover
> >>>>>>> letter [1]. As I mentioned in another email to Rob, in hindsight I'm
> >>>>>>> guessing I should have included them in the patch comments as well (or
> >>>>>>> instead of).  
> >>>>>>
> >>>>>> I saw them. I assumed that the review itself will address those
> >>>>>> questions.  
> >>>>>
> >>>>> Fair enough, thanks for the confirmation.
> >>>>>  
> >>>>>>> Your review comments here effectively answered some of the questions, but
> >>>>>>> the big one I'm still unsure of is whether it actually makes sense to
> >>>>>>> have all 4 of these devices supported by a single driver.  
> >>>>>>
> >>>>>> It's perfectly fine. Many drivers implement this pattern.  
> >>>>>
> >>>>> OK, then I'll assume you think this driver is not yet too complicated
> >>>>> for it's own good. Out of curiosity, might that view change if the
> >>>>> 3216 specific features were ever implemented, especially GPIO and HW
> >>>>> animation support? Gut feel is that would make 3216 specific code
> >>>>> bigger than the rest of the code combined.  
> >>>>
> >>>> I don't think so.  
> >>>
> >>> Thanks, that helps calibrate my intuition for the future.
> >>>  
> >>>>> Bigger question is what should be done in terms of the overlap in device
> >>>>> support between this driver and leds-sn3218? If you think I should leave
> >>>>> the *3218 support in this driver, then I would propose:
> >>>>>      - remove leds-sn3218 and its separate binding doc
> >>>>>      - add the "si-en,sn3218" compatible string to this driver and binding doc
> >>>>> Note that while I expect this driver to work with the 3218 chips, I do
> >>>>> not have one to test against. If we go down this route I would definitely
> >>>>> want Stefan to test so that I don't accidentally break him.  
> >>>>
> >>>> I'd prefer to have a single driver for the same hardware. Stefan, would
> >>>> it be possible for you to test David's driver with the hardware you
> >>>> have an access to?  
> >>>
> >>> Stefan, one thing to note: the existing sn3218 driver/binding uses 0-based
> >>> 'reg' values, and this driver/binding uses 1-based 'reg' values. So your
> >>> devicetree(s) would need to be updated for that (as well as the compatible
> >>> string).  
> >>
> >> Actually, If your driver can successfully handle Si-En SN3218 I'd prefer
> >> to drop leds-sn3218 along with its bindings and add related compatible
> >> to your bindings documentation.  
> >
> > Agreed. The changing of compatible string would only need to be done with
> > the current version of the series.
> > In the next version I'll add a 4th patch (unless you'd prefer a separate
> > patch not part of the series?) that removes leds-sn3218 and moves that
> > support into the is31fl32xx driver.  
> 
> Please add it as 4th patch to this set.

OK.

> >>> I didn't see a final answer from Rob as to which way is most appropriate
> >>> for these devices yet, so I don't know which way this will end up in the
> >>> final patch.
> >>>  
> >>>>> Also I feel I should point out some differences between the 3218 support
> >>>>> in this driver versus the leds-sn3218 driver, in case they have any
> >>>>> impact:
> >>>>>      - (as previously mentioned) leds-sn3218 turns off an LEDs enable
> >>>>>        bit if the brightness is set to 0. This driver just sets the PWM
> >>>>>        to 0 and leaves the enable bits always on.  
> >>>>
> >>>> Setting brightness to 0 is an equivalent to turning the device in
> >>>> a power down mode or at least in the state where the current consumption
> >>>> is as low as possible. A hardware configuration that is most fitting
> >>>> for this requirements should be chosen.  
> >>>
> >>> As far as I can tell from the datasheets, setting the PWM duty cycle for
> >>> a given channel to 0 should have the same net effect as setting the enable
> >>> bit of that channel to 0. I assume the purpose of the enable bits is to
> >>> make it easier to turn an LED on/off without adjusting the PWM duty cycle,
> >>> but just using always the PWM duty cycle register conveniently maps to
> >>> the leds API.  
> >>
> >> ack.
> >>  
> >>>>>      - leds-sn3218 uses a regmap, I think mostly to deal with the enable
> >>>>>        bits, but it also has the benefit of showing up in debugfs. This
> >>>>>        could be seen as useful in and of itself by some users. On the other
> >>>>>        hand regmap introduces another mutex on every write.  
> >>>>
> >>>> I will not insist on using regmap if you don't refer to the current
> >>>> state of hw registers in your driver.  
> >>>
> >>> Currently I have not had a need to refer to the current state of any HW
> >>> registers. I could imagine that might be needed in the future if extra
> >>> functionality is implemented, but it wasn't so far.  
> >>
> >> Regmap exposes nice debugfs interface, so this, and the fact that there
> >> are uncovered hw features, can be thought of as a sufficient
> >> argument in favour of using regmap even now. But it's up to you.  
> >
> > If it's OK with you, I think I'll leave it without regmap for now. I
> > don't really relish the thought of having 4 large blocks of reg_default
> > (especially the 3216 has a large register set for animation), and I
> > haven't yet worked out how/if I could dynamically generate them from
> > the chipdefs in a reasonable way.  
> 
> I'm OK with it.
> 
> >>>>>      - leds-sn3218 implements the shutdown callback. Actually, I think I
> >>>>>        should add that to this driver in any event.  
> >>>>
> >>>> Do you see use cases or hardware configurations that need such
> >>>> a callback? I didn't oppose in case of Stefan's driver, but if we are
> >>>> at it, we can consult Stefan if he saw that use case?
> >>>>
> >>>> I'd say that shutdown op is usually useful when CPU and LED
> >>>> controller are powered from different sources, which can result in
> >>>> a situation when CPU is turned off, but LED remains on.  
> >>>
> >>> That is exactly what happens today on my board: if the system is rebooted
> >>> or shut down the LEDs all stay in whatever state they were last in. This
> >>> could also be handled by userspace shutdown scripts easily enough, but I
> >>> thought it surprising when the system reboots but LEDs stay on.  
> >>
> >> That's the shutdown callback is for.  
> >
> > I'll add a shutdown callback that will do the same as the remove callback,
> > and ensure all LEDs go dark.
> > Is there anything verboten with having one just call the other, or just
> > registering the same function for both callbacks?  
> 
> Please enclose current content of your remove callback in a new
> function, and call this function from both remove and shutdown.

OK.

> >>> On the
> >>> other hand someone might have a good reason to want to leave an LED on
> >>> through a reboot.  
> >>
> >> If you have such a use case, then you can add DT property for this.
> >> E.g. retain-state-on-shutdown.  
> >
> > I don't have such a use case right now, but noted for the future.
> >  
> >>> There is also an inconsistency on what happens during remove() vs shutdown().
> >>> In led_classdev_unregister() brightness is set to LED_OFF, so that happens
> >>> for all drivers on remove(). But only some drivers implement a shutdown()
> >>> which also turns off LEDs, most do not. For instance, leds-gpio turns off
> >>> all LEDs, but leds-pwm does not.
> >>   >
> >>> Is the general policy that LEDs should be forced off by the driver when the
> >>> kernel halts or reboots the CPU? Or left alone and let userspace deal with
> >>> it?  
> >>
> >> I think that this depends on use cases and hardware configurations
> >> available on the market. People added the callback when they needed it.
> >> There is no defined policy for this.
> >>  
> >>> And should this (in principle) be the same as what happens when a module
> >>> is unloaded (which currently always turns LEDs off)?  
> >>
> >> Turning the LED off on removal is logically justified IMO.  
> >
> > Agreed. I just found the inconsistency in some/most drivers between remove
> > and shutdown unexpected. Given that not all LED drivers turn off on shutdown,
> > (and my use case desires them to turn off, including a leds-pwm instance),
> > I'll just write 0 to /sys/class/leds/*/brightness unconditionally in a
> > userspace shutdown script.  
> 
> Doesn't it stand in contradiction with your above statement?:
> 
> "I'll add a shutdown callback that will do the same as the remove callback,"
> 
> I'm a bit lost - does your current is31fl32xx_remove() turn all
> the sub-LEDs off?

Yes, is31fl32xx_remove (and soon is31fl32xx_shutdown) will turn all the 
LED channels off. However, I also have a leds-pwm instance and that does 
not turn off LEDs on shutdown(), so during a reboot those LEDs currently 
left on. Also, it's always possible that future boards might use a 
different device (and therefore driver) to drive LEDs. Very few LED 
existing drivers have a shutdown callback, so I expect them to have the 
same behavior as leds-pwm.

For obvious reasons I'd rather not have userspace know what LEDs are 
controlled through what specific driver (and what drivers do/don't
turn off LEDs on their own). So if the kernel does not guarantee that 
all LEDs are turned off on a reboot, it's cleanest from a userspace 
perspective to just turn all LEDs off unconditionally via the sysfs 
interface. 

Unless I misunderstood your earlier statement: 
    "There is no defined policy for this."
? If you consider the current leds-pwm behavior a bug (as opposed to a 
choice), then I might submit a patch to add a shutdown callback for 
leds-pwm instead. Although I could imagine someone complaining about 
such a change being backwards-incompatible if they were somehow relying 
on current behavior. And there is still the vast majority of other LED 
drivers which I think likely have the same basic issue, though I can't 
test those to verify.

> >>>>>      - leds-sn3218 just puts the chip in software-shutdown mode on remove/
> >>>>>        shutdown. This driver uses the reset register to put the device in
> >>>>>        poweron state, and software-shutdown is part of the poweron state.
> >>>>>        Only difference would be if the next code to use the device does
> >>>>>        not do it's own full initialization (which seems unlikely, or at
> >>>>>        least unwise), but instead just clears software-shutdown.  
> >>>>
> >>>> I believe that my above explanations address this question, i.e.
> >>>> both brightness = 0 an remove/shutdown should set the device
> >>>> in a power down mode.  
> >>>
> >>> I think there is some confusion, there are 3 separate controls:
> >>>    - per-LED PWM duty cycle
> >>>    - per-LED enable bit
> >>>    - device-wide shutdown mode
> >>> Shutdown-mode results in all LEDs going dark (regardless of any other
> >>> register state), and I think implies that it also uses less power
> >>> (compared to just turning them all off with either of the other controls).
> >>> Registers do retain their value and the I2C interface continues to work.
> >>> I suspect that all it does is turn off an internal oscillator that drives
> >>> the PWM circuits, but the documentation is not clear.
> >>>
> >>> The distinction I was making was that the leds-sn3218 driver *only* turned
> >>> on the Shutdown-mode, while this driver reset all other registers in the
> >>> device to their default values as well. Though in practice I don't expect
> >>> that to make a difference.  
> >>
> >> If documentation isn't clear about that, you can always measure current
> >> consumption in both cases. Note, that this is not required, you can
> >> follow your intuition.  
> >
> > I may try to do that out of curiosity.
> >  
> >>>>>>> I won't
> >>>>>>> clutter this email with a duplicate of the details (it's somewhat long),
> >>>>>>> but if you could check the cover letter and give some guidance, I would
> >>>>>>> appreciate it.
> >>>>>>>
> >>>>>>> [1] http://www.spinics.net/lists/linux-leds/msg05564.html
> >>>>>>>         http://thread.gmane.org/gmane.linux.leds/4530
> >>>>>>>  
> >>>>>>>>
> >>>>>>>> On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:  
> >>>>>>>>> From: David Rivshin <drivshin@allworx.com>
> >>>>>>>>>
> >>>>>>>>> The IS31FL32xx family of LED drivers are I2C devices with multiple
> >>>>>>>>> constant-current channels, each with independent 256-level PWM control.
> >>>>>>>>>
> >>>>>>>>> HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
> >>>>>>>>>
> >>>>>>>>> This has been tested on the IS31FL3236 and IS31FL3216 on an ARM
> >>>>>>>>> (TI am335x) platform.
> >>>>>>>>>
> >>>>>>>>> The programming paradigm of these devices is similar in the following
> >>>>>>>>> ways:
> >>>>>>>>>       - All registers are 8 bit
> >>>>>>>>>       - All LED control registers are write-only
> >>>>>>>>>       - Each LED channel has a PWM register (0-255)
> >>>>>>>>>       - PWM register writes are shadowed until an Update register is poked
> >>>>>>>>>       - All have a concept of Software Shutdown, which disables output
> >>>>>>>>>
> >>>>>>>>> However, there are some differences in devices:
> >>>>>>>>>       - 3236/3235 have a separate Control register for each LED,
> >>>>>>>>>         (3218/3216 pack the enable bits into fewer registers)
> >>>>>>>>>       - 3236/3235 have a per-channel current divisor setting
> >>>>>>>>>       - 3236/3235 have a Global Control register that can turn off all LEDs
> >>>>>>>>>       - 3216 is unique in a number of ways
> >>>>>>>>>          - OUT9-OUT16 can be configured as GPIOs instead of LED controls
> >>>>>>>>>          - LEDs can be programmed with an 8-frame animation, with
> >>>>>>>>>            programmable delay between frames
> >>>>>>>>>          - LEDs can be modulated by an input audio signal
> >>>>>>>>>          - Max output current can be adjusted from 1/4 to 2x globally
> >>>>>>>>>          - Has a Configuration register instead of a Shutdown register
> >>>>>>>>>
> >>>>>>>>> This driver currently only supports the base PWM control function
> >>>>>>>>> of these devices. The following features of these devices are not
> >>>>>>>>> implemented, although it should be possible to add them in the future:
> >>>>>>>>>       - All devices are capable of going into a lower-power "software
> >>>>>>>>>         shutdown" mode.
> >>>>>>>>>       - The is31fl3236 and is31fl3235 can reduce the max output current
> >>>>>>>>>         per-channel with a divisor of 1, 2, 3, or 4.
> >>>>>>>>>       - The is31fl3216 can use some LED channels as GPIOs instead.
> >>>>>>>>>       - The is31fl3216 can animate LEDs in hardware.
> >>>>>>>>>       - The is31fl3216 can modulate LEDs according to an audio input.
> >>>>>>>>>       - The is31fl3216 can reduce/increase max output current globally.
> >>>>>>>>>
> >>>>>>>>> Signed-off-by: David Rivshin <drivshin@allworx.com>
> >>>>>>>>> ---
> >>>>>>>>>       drivers/leds/Kconfig           |   9 +
> >>>>>>>>>       drivers/leds/Makefile          |   1 +
> >>>>>>>>>       drivers/leds/leds-is31fl32xx.c | 442 +++++++++++++++++++++++++++++++++++++++++
> >>>>>>>>>       3 files changed, 452 insertions(+)
> >>>>>>>>>       create mode 100644 drivers/leds/leds-is31fl32xx.c
> >>>>>>>>>
> >>>>>>>>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> >>>>>>>>> index 1034696..8f6c46f 100644
> >>>>>>>>> --- a/drivers/leds/Kconfig
> >>>>>>>>> +++ b/drivers/leds/Kconfig
> >>>>>>>>> @@ -580,6 +580,15 @@ config LEDS_SN3218
> >>>>>>>>>       	  This driver can also be built as a module. If so the module
> >>>>>>>>>       	  will be called leds-sn3218.
> >>>>>>>>>
> >>>>>>>>> +config LEDS_IS31FL32XX
> >>>>>>>>> +	tristate "Driver for ISSI IS31FL32XX I2C LED driver chip family"  
> >>>>>>>>
> >>>>>>>> 2 x "[Dd]river".
> >>>>>>>>
> >>>>>>>> How about:
> >>>>>>>>
> >>>>>>>> "LED Support for ISSI IS31FL32XX I2C LED chip family" ?  
> >>>>>>>
> >>>>>>> Yes, I found that awkward as well. HW folks (and the datasheets) seem
> >>>>>>> always refer to devices of this type as "LED Driver"s (which can lead
> >>>>>>> to some interesting confusions). Taking a cue from the LP5521/23/62
> >>>>>>> entries, how about:
> >>>>>>>      "LED Support for the ISSI IS31FL32XX I2C LED driver chip family" ?  
> >>>>>>
> >>>>>> "LED Support" means "LED class driver". Driver is a software support
> >>>>>> for hardware chip. What discrepancy do you see in the description
> >>>>>> I proposed?  
> >>>>>
> >>>>> I think in this case "driver" also means "hardware device which drives
> >>>>> a physical LED".  
> >>>>
> >>>> Let's not confuse these notions. From Linux perspective "driver" refers
> >>>> to a piece of software used for controlling a hardware.
> >>>>  
> >>>>> It seems that "LED driver" is the term universally used
> >>>>> to describe this type of HW device in datasheets.  
> >>>>
> >>>> There are also e.g. "LED controllers", "LED current regulators".
> >>>> Let's stick to the convention predominantly used in the LED subsystem
> >>>> kernel config menu.
> >>>>  
> >>>>> So it seemed useful to
> >>>>> use exactly that phrase in the description of what hardware this software
> >>>>> supports. I could see someone interpreting the phrase "LED chip" as
> >>>>> referring to an actual LED device.
> >>>>> I don't feel very strongly on this topic, but for the sake of discussion,
> >>>>> maybe "LED controller" would avoid any possible confusion in both
> >>>>> directions?  
> >>>>
> >>>> Right, so let's use the following:
> >>>>
> >>>> "LED Support for ISSI IS31FL32XX I2C LED controller family"
> >>>>
> >>>> I understand "LED Support" as "Linux LED subsystem support".  
> >>>
> >>> STGM. Done.
> >>>  
> >>>>>>> Perhaps that's the best of both worlds?
> >>>>>>>  
> >>>>>>>>> +	depends on LEDS_CLASS && I2C && OF
> >>>>>>>>> +	help
> >>>>>>>>> +	  Say Y here to include support for the ISSI 31FL32XX LED driver family.  
> >>>>>>>>
> >>>>>>>> s/driver/chip/
> >>>>>>>>  
> >>>>>>>>> +	  They are I2C devices with multiple constant-current channels, each
> >>>>>>>>> +	  with independent 256-level PWM control. This will only work with
> >>>>>>>>> +	  device tree enabled devices.  
> >>>>>>>>
> >>>>>>>> We can skip the last sentence I think.  
> >>>>>>>
> >>>>>>> OK. FYI, I think I got that verbiage from LEDS_SYSCON.  
> >>>>>>
> >>>>>> Having "depends on OF" is self-explanatory here.  
> >>>>>
> >>>>> Noted.
> >>>>>  
> >>>>>>>>> +
> >>>>>>>>>       comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
> >>>>>>>>>
> >>>>>>>>>       config LEDS_BLINKM
> >>>>>>>>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> >>>>>>>>> index 89c9b6f..3fdf313 100644
> >>>>>>>>> --- a/drivers/leds/Makefile
> >>>>>>>>> +++ b/drivers/leds/Makefile
> >>>>>>>>> @@ -67,6 +67,7 @@ obj-$(CONFIG_LEDS_KTD2692)		+= leds-ktd2692.o
> >>>>>>>>>       obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
> >>>>>>>>>       obj-$(CONFIG_LEDS_SEAD3)		+= leds-sead3.o
> >>>>>>>>>       obj-$(CONFIG_LEDS_SN3218)		+= leds-sn3218.o
> >>>>>>>>> +obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
> >>>>>>>>>
> >>>>>>>>>       # LED SPI Drivers
> >>>>>>>>>       obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
> >>>>>>>>> diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
> >>>>>>>>> new file mode 100644
> >>>>>>>>> index 0000000..8dea518
> >>>>>>>>> --- /dev/null
> >>>>>>>>> +++ b/drivers/leds/leds-is31fl32xx.c
> >>>>>>>>> @@ -0,0 +1,442 @@
> >>>>>>>>> +/*
> >>>>>>>>> + * linux/drivers/leds-is31fl32xx.c
> >>>>>>>>> + *
> >>>>>>>>> + * Driver for ISSI IS31FL32xx family of I2C LED controllers
> >>>>>>>>> + *
> >>>>>>>>> + * Copyright 2015 Allworx Corp.
> >>>>>>>>> + *
> >>>>>>>>> + *
> >>>>>>>>> + * 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.
> >>>>>>>>> + *
> >>>>>>>>> + * HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
> >>>>>>>>> + */
> >>>>>>>>> +
> >>>>>>>>> +#include <linux/err.h>
> >>>>>>>>> +#include <linux/i2c.h>
> >>>>>>>>> +#include <linux/kernel.h>
> >>>>>>>>> +#include <linux/leds.h>
> >>>>>>>>> +#include <linux/module.h>
> >>>>>>>>> +#include <linux/of_platform.h>
> >>>>>>>>> +
> >>>>>>>>> +#ifdef DEBUG
> >>>>>>>>> + #undef dev_dbg
> >>>>>>>>> + #define dev_dbg dev_info
> >>>>>>>>> +#endif  
> >>>>>>>>
> >>>>>>>> What's the benefit of the above?  
> >>>>>>>
> >>>>>>> It gave me a way to easily see debug output from the driver while it
> >>>>>>> was parsing the DT (especially if the driver was built-in). Early on
> >>>>>>> there were other things within that #ifdef as well.
> >>>>>>> Regardless, passing ddebug_query on the kernel commandline is a more
> >>>>>>> appropriate way of accomplishing that; I'll remove for the next version.  
> >>>>>>
> >>>>>> Thanks.
> >>>>>>  
> >>>>>>>>> +/* Used to indicate a device has no such register */
> >>>>>>>>> +#define IS31FL32XX_REG_NONE 0xFF
> >>>>>>>>> +
> >>>>>>>>> +#define IS31FL3216_CONFIG_REG 0x00
> >>>>>>>>> +#define IS31FL3216_LIGHTING_EFFECT_REG 0x03
> >>>>>>>>> +#define IS31FL3216_CHANNEL_CONFIG_REG 0x04
> >>>>>>>>> +
> >>>>>>>>> +struct is31fl32xx_priv;
> >>>>>>>>> +struct is31fl32xx_led_data {
> >>>>>>>>> +	struct led_classdev cdev;
> >>>>>>>>> +	u8 channel; /* 1-based, max priv->cdef->channels */
> >>>>>>>>> +	struct is31fl32xx_priv *priv;
> >>>>>>>>> +};
> >>>>>>>>> +
> >>>>>>>>> +struct is31fl32xx_priv {
> >>>>>>>>> +	const struct is31fl32xx_chipdef *cdef;
> >>>>>>>>> +	struct i2c_client *client;
> >>>>>>>>> +	unsigned int num_leds;
> >>>>>>>>> +	struct is31fl32xx_led_data leds[0];  
> >>>>>>>>
> >>>>>>>> Is there any specific reason for not having *leds here instead?  
> >>>>>>>
> >>>>>>> I followed a pattern from leds-pwm where it did a single allocation
> >>>>>>> for both priv and priv->leds[]. See sizeof_is31fl32xx_priv(), and
> >>>>>>> its use, below. I saw the benefit as one fewer small allocation, so
> >>>>>>> slightly more kind to the allocator (and devres). If you'd prefer to
> >>>>>>> do it as two allocations, I'll make the change.  
> >>>>>>
> >>>>>> OK, I had to look at this one more time. I like the idea.  
> >>>>>
> >>>>> OK, I'll keep it as-is.
> >>>>>  
> >>>>>>>>> +};
> >>>>>>>>> +
> >>>>>>>>> +/**
> >>>>>>>>> + * struct is31fl32xx_chipdef - chip-specific attributes
> >>>>>>>>> + * @channels            : Number of LED channels
> >>>>>>>>> + * @shutdown_reg        : address of Shutdown register (optional)
> >>>>>>>>> + * @pwm_update_reg      : address of PWM Update register
> >>>>>>>>> + * @global_control_reg	: address of Global Control register (optional)
> >>>>>>>>> + * @reset_reg           : address of Reset register (optional)
> >>>>>>>>> + * @pwm_register_base	: address of first PWM register
> >>>>>>>>> + * @pwm_registers_reversed: : true if PWM registers count down instead of up
> >>>>>>>>> + * @led_control_register_base : address of first LED control register (optional)
> >>>>>>>>> + * @enable_bits_per_led_control_register: number of LEDs enable bits in each
> >>>>>>>>> + * @reset_func:         : pointer to reset function
> >>>>>>>>> + *
> >>>>>>>>> + * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE
> >>>>>>>>> + * indicates that this chip has no such register.
> >>>>>>>>> + *
> >>>>>>>>> + * If non-NULL, @reset_func will be called during probing to set all
> >>>>>>>>> + * necessary registers to a known initialization state. This is needed
> >>>>>>>>> + * for chips that do not have a @reset_reg.
> >>>>>>>>> + *
> >>>>>>>>> + * @enable_bits_per_led_control_register must be >=1 if
> >>>>>>>>> + * @led_control_register_base != %IS31FL32XX_REG_NONE.
> >>>>>>>>> + */
> >>>>>>>>> +struct is31fl32xx_chipdef {
> >>>>>>>>> +	u8	channels;
> >>>>>>>>> +	u8	shutdown_reg;
> >>>>>>>>> +	u8	pwm_update_reg;
> >>>>>>>>> +	u8	global_control_reg;
> >>>>>>>>> +	u8	reset_reg;
> >>>>>>>>> +	u8	pwm_register_base;
> >>>>>>>>> +	bool	pwm_registers_reversed;
> >>>>>>>>> +	u8	led_control_register_base;
> >>>>>>>>> +	u8	enable_bits_per_led_control_register;
> >>>>>>>>> +	int (*reset_func)(struct is31fl32xx_priv *priv);
> >>>>>>>>> +};
> >>>>>>>>> +
> >>>>>>>>> +static const struct is31fl32xx_chipdef is31fl3236_cdef = {
> >>>>>>>>> +	.channels				= 36,
> >>>>>>>>> +	.shutdown_reg				= 0x00,
> >>>>>>>>> +	.pwm_update_reg				= 0x25,
> >>>>>>>>> +	.global_control_reg			= 0x4a,
> >>>>>>>>> +	.reset_reg				= 0x4f,
> >>>>>>>>> +	.pwm_register_base			= 0x01,
> >>>>>>>>> +	.led_control_register_base		= 0x26,
> >>>>>>>>> +	.enable_bits_per_led_control_register	= 1,
> >>>>>>>>> +};
> >>>>>>>>> +
> >>>>>>>>> +static const struct is31fl32xx_chipdef is31fl3235_cdef = {
> >>>>>>>>> +	.channels				= 28,
> >>>>>>>>> +	.shutdown_reg				= 0x00,
> >>>>>>>>> +	.pwm_update_reg				= 0x25,
> >>>>>>>>> +	.global_control_reg			= 0x4a,
> >>>>>>>>> +	.reset_reg				= 0x4f,
> >>>>>>>>> +	.pwm_register_base			= 0x05,
> >>>>>>>>> +	.led_control_register_base		= 0x2a,
> >>>>>>>>> +	.enable_bits_per_led_control_register	= 1,
> >>>>>>>>> +};
> >>>>>>>>> +
> >>>>>>>>> +static const struct is31fl32xx_chipdef is31fl3218_cdef = {
> >>>>>>>>> +	.channels				= 18,
> >>>>>>>>> +	.shutdown_reg				= 0x00,
> >>>>>>>>> +	.pwm_update_reg				= 0x16,
> >>>>>>>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
> >>>>>>>>> +	.reset_reg				= 0x17,
> >>>>>>>>> +	.pwm_register_base			= 0x01,
> >>>>>>>>> +	.led_control_register_base		= 0x13,
> >>>>>>>>> +	.enable_bits_per_led_control_register	= 6,
> >>>>>>>>> +};
> >>>>>>>>> +
> >>>>>>>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv);
> >>>>>>>>> +static const struct is31fl32xx_chipdef is31fl3216_cdef = {
> >>>>>>>>> +	.channels				= 16,
> >>>>>>>>> +	.shutdown_reg				= IS31FL32XX_REG_NONE,
> >>>>>>>>> +	.pwm_update_reg				= 0xB0,
> >>>>>>>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
> >>>>>>>>> +	.reset_reg				= IS31FL32XX_REG_NONE,
> >>>>>>>>> +	.pwm_register_base			= 0x10,
> >>>>>>>>> +	.pwm_registers_reversed			= true,
> >>>>>>>>> +	.led_control_register_base		= 0x01,
> >>>>>>>>> +	.enable_bits_per_led_control_register	= 8,
> >>>>>>>>> +	.reset_func				= is31fl3216_reset,
> >>>>>>>>> +};
> >>>>>>>>> +
> >>>>>>>>> +static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)
> >>>>>>>>> +{
> >>>>>>>>> +	int ret;
> >>>>>>>>> +
> >>>>>>>>> +	dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
> >>>>>>>>> +
> >>>>>>>>> +	ret =  i2c_smbus_write_byte_data(priv->client, reg, val);
> >>>>>>>>> +	if (ret) {
> >>>>>>>>> +		dev_err(&priv->client->dev,
> >>>>>>>>> +			"register write to 0x%02X failed (error %d)",
> >>>>>>>>> +			reg, ret);
> >>>>>>>>> +	}
> >>>>>>>>> +	return ret;
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> +/*
> >>>>>>>>> + * Custom reset function for IS31FL3216 because it does not have a RESET
> >>>>>>>>> + * register the way that the other IS31FL32xx chips do. We don't bother
> >>>>>>>>> + * writing the GPIO and animation registers, because the registers we
> >>>>>>>>> + * do write ensure those will have no effect.
> >>>>>>>>> + */
> >>>>>>>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv)
> >>>>>>>>> +{
> >>>>>>>>> +	unsigned int i;
> >>>>>>>>> +	int ret;
> >>>>>>>>> +
> >>>>>>>>> +	for (i = 0; i < priv->cdef->channels; i++) {
> >>>>>>>>> +		ret = is31fl32xx_write(priv, priv->cdef->pwm_register_base+i,
> >>>>>>>>> +				       0x00);
> >>>>>>>>> +		if (ret)
> >>>>>>>>> +			return ret;
> >>>>>>>>> +	}
> >>>>>>>>> +	ret = is31fl32xx_write(priv, priv->cdef->pwm_update_reg, 0);
> >>>>>>>>> +	if (ret)
> >>>>>>>>> +		return ret;
> >>>>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, 0x00);
> >>>>>>>>> +	if (ret)
> >>>>>>>>> +		return ret;
> >>>>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, 0x00);
> >>>>>>>>> +	if (ret)
> >>>>>>>>> +		return ret;
> >>>>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, 0x00);
> >>>>>>>>> +	if (ret)
> >>>>>>>>> +		return ret;
> >>>>>>>>> +
> >>>>>>>>> +	return 0;
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> +
> >>>>>>>>> +static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,
> >>>>>>>>> +				     enum led_brightness brightness)
> >>>>>>>>> +{
> >>>>>>>>> +	const struct is31fl32xx_led_data *led_data =
> >>>>>>>>> +		container_of(led_cdev, struct is31fl32xx_led_data, cdev);
> >>>>>>>>> +	const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef;
> >>>>>>>>> +	u8 pwm_register_offset;
> >>>>>>>>> +	int ret;
> >>>>>>>>> +
> >>>>>>>>> +	dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);
> >>>>>>>>> +
> >>>>>>>>> +	/* NOTE: led_data->channel is 1-based */
> >>>>>>>>> +	if (cdef->pwm_registers_reversed)
> >>>>>>>>> +		pwm_register_offset = cdef->channels - led_data->channel;
> >>>>>>>>> +	else
> >>>>>>>>> +		pwm_register_offset = led_data->channel - 1;
> >>>>>>>>> +
> >>>>>>>>> +	ret = is31fl32xx_write(led_data->priv,
> >>>>>>>>> +			       cdef->pwm_register_base + pwm_register_offset,
> >>>>>>>>> +			       brightness);
> >>>>>>>>> +	if (ret)
> >>>>>>>>> +		return ret;  
> >>>>>>>>
> >>>>>>>> I infer that nothing wrong happens in case current process is preempted
> >>>>>>>> here by the call originating from the other sub-LED?  
> >>>>>>>
> >>>>>>> I do not believe so. All the driver-specific data used here is read-only
> >>>>>>> after probing. chipdefs are entirely const, and the only thing in priv
> >>>>>>> that's referenced is the chipdef pointer which logically could not change
> >>>>>>> post-probe. Actually nothing else in priv is modified post-probe either.
> >>>>>>>
> >>>>>>> The I2C core code has a mutex on the bus, so two writes cannot happen at
> >>>>>>> once.
> >>>>>>>
> >>>>>>> In all these devices there is a unique PWM duty-cycle register for each
> >>>>>>> LED channel (which is what is being written here), so no register writes
> >>>>>>> for one LED channel effect any others.
> >>>>>>>
> >>>>>>> I believe the worst that could happen is that the device would see:
> >>>>>>> 	PWM_REG_A write X
> >>>>>>> 	PWM_REG_B write Y
> >>>>>>> 	UPDATE_REG write 0
> >>>>>>> 	UPDATE_REG write 0
> >>>>>>> instead of
> >>>>>>> 	PWM_REG_A write X
> >>>>>>> 	UPDATE_REG write 0
> >>>>>>> 	PWM_REG_B write Y
> >>>>>>> 	UPDATE_REG write 0
> >>>>>>> but that makes no difference to the functionality. Poking the update
> >>>>>>> register merely applies all PWM register writes up to that point (I'm
> >>>>>>> assuming to allow atomically changing the state of multiple LEDs at
> >>>>>>> once).  
> >>>>>>
> >>>>>> Thanks for this comprehensive explanation.  
> >>>>>
> >>>>> Should I put some part of this explanation in a comment somewhere? Seems
> >>>>> like the kind of thing someone else might wonder about in the future also.  
> >>>>
> >>>> Good idea.  
> >>>
> >>> Done.
> >>>  
> >>>>>>> I should note here (as mentioned in cover letter), I made a choice to
> >>>>>>> always leave the per-LED "enable" bits on, and let the PWM just get set
> >>>>>>> to 0 naturally to turn an LED off. This differs from the existing SN3218
> >>>>>>> driver, which used regmap_update_bits, and is then protected by a per-
> >>>>>>> regmap mutex.  
> >>>>>>
> >>>>>> ack.
> >>>>>>  
> >>>>>>>>> +	return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);
> >>>>>>>>> +
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> +static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
> >>>>>>>>> +{
> >>>>>>>>> +	const struct is31fl32xx_chipdef *cdef = priv->cdef;
> >>>>>>>>> +	int ret;
> >>>>>>>>> +
> >>>>>>>>> +	if (cdef->reset_reg != IS31FL32XX_REG_NONE) {
> >>>>>>>>> +		ret = is31fl32xx_write(priv, cdef->reset_reg, 0);
> >>>>>>>>> +		if (ret)
> >>>>>>>>> +			return ret;
> >>>>>>>>> +	}
> >>>>>>>>> +	if (cdef->reset_func) {
> >>>>>>>>> +		ret = cdef->reset_func(priv);
> >>>>>>>>> +		if (ret)
> >>>>>>>>> +			return ret;
> >>>>>>>>> +	}
> >>>>>>>>> +	if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) {
> >>>>>>>>> +		u8 value =
> >>>>>>>>> +		    GENMASK(cdef->enable_bits_per_led_control_register-1, 0);
> >>>>>>>>> +		u8 num_regs = cdef->channels /
> >>>>>>>>> +				cdef->enable_bits_per_led_control_register;
> >>>>>>>>> +		int i;
> >>>>>>>>> +
> >>>>>>>>> +		for (i = 0; i < num_regs; i++) {
> >>>>>>>>> +			ret = is31fl32xx_write(priv,
> >>>>>>>>> +					       cdef->led_control_register_base+i,
> >>>>>>>>> +					       value);
> >>>>>>>>> +			if (ret)
> >>>>>>>>> +				return ret;
> >>>>>>>>> +		}
> >>>>>>>>> +	}
> >>>>>>>>> +	if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) {
> >>>>>>>>> +		ret = is31fl32xx_write(priv, cdef->shutdown_reg, BIT(0));
> >>>>>>>>> +		if (ret)
> >>>>>>>>> +			return ret;
> >>>>>>>>> +	}
> >>>>>>>>> +	if (cdef->global_control_reg != IS31FL32XX_REG_NONE) {
> >>>>>>>>> +		ret = is31fl32xx_write(priv, cdef->global_control_reg, 0x00);
> >>>>>>>>> +		if (ret)
> >>>>>>>>> +			return ret;
> >>>>>>>>> +	}
> >>>>>>>>> +
> >>>>>>>>> +	return 0;
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> +static inline size_t sizeof_is31fl32xx_priv(int num_leds)
> >>>>>>>>> +{
> >>>>>>>>> +	return sizeof(struct is31fl32xx_priv) +
> >>>>>>>>> +		      (sizeof(struct is31fl32xx_led_data) * num_leds);
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> +static int is31fl32xx_parse_child_dt(const struct device *dev,
> >>>>>>>>> +				     const struct device_node *child,
> >>>>>>>>> +				     struct is31fl32xx_led_data *led_data)
> >>>>>>>>> +{
> >>>>>>>>> +	struct led_classdev *cdev = &led_data->cdev;
> >>>>>>>>> +	int ret = 0;
> >>>>>>>>> +	u32 reg;
> >>>>>>>>> +
> >>>>>>>>> +	cdev->name = of_get_property(child, "label", NULL) ? : child->name;
> >>>>>>>>> +
> >>>>>>>>> +	ret = of_property_read_u32(child, "reg", &reg);
> >>>>>>>>> +	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
> >>>>>>>>> +		dev_err(dev,
> >>>>>>>>> +			"Child node %s does not have a valid reg property\n",
> >>>>>>>>> +			child->name);
> >>>>>>>>> +		return -EINVAL;
> >>>>>>>>> +	}
> >>>>>>>>> +	led_data->channel = reg;
> >>>>>>>>> +
> >>>>>>>>> +	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
> >>>>>>>>> +						NULL);
> >>>>>>>>> +	cdev->brightness = LED_OFF;  
> >>>>>>>>
> >>>>>>>> devm_kzalloc secures that.  
> >>>>>>>
> >>>>>>> OK, I will remove.
> >>>>>>>  
> >>>>>>>>> +	ret = of_property_read_u32(child, "max-brightness",
> >>>>>>>>> +				   &cdev->max_brightness);
> >>>>>>>>> +	if (ret == -EINVAL) {
> >>>>>>>>> +		cdev->max_brightness = 255;  
> >>>>>>>>
> >>>>>>>> s/255/LED_FULL/  
> >>>>>>>
> >>>>>>> Noted, although (from the patch 2 discussion) max-brightness property is
> >>>>>>> removed/replaced, this would go away anyways.
> >>>>>>>  
> >>>>>>>>> +	} else if (ret) {
> >>>>>>>>> +		dev_dbg(dev,
> >>>>>>>>> +			"Child node %s has an invalid max-brightness property\n",
> >>>>>>>>> +			child->name);
> >>>>>>>>> +		return -EINVAL;
> >>>>>>>>> +	}
> >>>>>>>>> +
> >>>>>>>>> +	cdev->brightness_set_blocking = is31fl32xx_brightness_set;  
> >>>>>>>>
> >>>>>>>> Please add empty line here.  
> >>>>>>>
> >>>>>>> Done.
> >>>>>>>  
> >>>>>>>>> +	return 0;
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> +static struct is31fl32xx_led_data *is31fl32xx_find_led_data(
> >>>>>>>>> +					struct is31fl32xx_priv *priv,
> >>>>>>>>> +					u8 channel)
> >>>>>>>>> +{
> >>>>>>>>> +	size_t i;
> >>>>>>>>> +
> >>>>>>>>> +	for (i = 0; i < priv->num_leds; i++) {
> >>>>>>>>> +		if (priv->leds[i].channel == channel)
> >>>>>>>>> +			return &priv->leds[i];
> >>>>>>>>> +	}  
> >>>>>>>>
> >>>>>>>> Ditto.  
> >>>>>>>
> >>>>>>> Done.
> >>>>>>>  
> >>>>>>>>> +	return NULL;
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> +static int is31fl32xx_parse_dt(struct device *dev,
> >>>>>>>>> +			       struct is31fl32xx_priv *priv)
> >>>>>>>>> +{
> >>>>>>>>> +	struct device_node *child;
> >>>>>>>>> +
> >>>>>>>>> +	for_each_child_of_node(dev->of_node, child) {
> >>>>>>>>> +		struct is31fl32xx_led_data *led_data =
> >>>>>>>>> +			&priv->leds[priv->num_leds];
> >>>>>>>>> +		int ret = 0;
> >>>>>>>>> +		const struct is31fl32xx_led_data *other_led_data;
> >>>>>>>>> +
> >>>>>>>>> +		led_data->priv = priv;
> >>>>>>>>> +
> >>>>>>>>> +		ret = is31fl32xx_parse_child_dt(dev, child, led_data);
> >>>>>>>>> +		if (ret)
> >>>>>>>>> +			continue;  
> >>>>>>>>
> >>>>>>>> I prefer failing in such cases,  
> >>>>>>>
> >>>>>>> OK, I will change to an 'goto err' which will have an 'of_node_put()'
> >>>>>>> and 'return ret'.
> >>>>>>>
> >>>>>>> I will say, however, that while testing the error-detection in the
> >>>>>>> parsing logic, it was very convenient to construct a single devicetree
> >>>>>>> with a variety of errors. Then a single boot would test multiple
> >>>>>>> cases at once.  
> >>>>>>
> >>>>>> Good idea for testing, but in case some failure occurs during DT child
> >>>>>> node parsing in the release environment you're left with unused
> >>>>>> allocated memory.  
> >>>>>
> >>>>> Agreed. BTW, I assume from this that it's common to say "if there's
> >>>>> anything wrong in one part of your DT, there is no guarantee as to what
> >>>>> parts will actually be used"? I say this because what we're saying is that
> >>>>> if one LED node on this device is faulty, that all of them are ignored.
> >>>>> Analogy might be to a whole I2C bus being ignored because one of the
> >>>>> devices on it failed to probe. To the devicetree it's still a parent/child
> >>>>> bus/address relationship, even though the driver implementation is
> >>>>> very different.  
> >>>>
> >>>> I2C example is too generic I think. I2C controller is not as tightly
> >>>> coupled with I2C clients as LED controller with its current outputs.
> >>>> Besides, I2C controller doesn't have an idea what devices will attach
> >>>> to the bus it controls upon probing, contrarily to a LED controller.  
> >>>
> >>> OK. I realize that in code there is a large distinction in these cases,
> >>> but I wasn't sure if that would be reflected in how errors in parsing
> >>> the devicetree should handled. Sounds like there is at least a de-facto
> >>> distinction between "a device and its children" and "a bus and its
> >>> children".
> >>>  
> >>>>>>>>> +
> >>>>>>>>> +		/* Detect if channel is already in use by another child */
> >>>>>>>>> +		other_led_data = is31fl32xx_find_led_data(priv,
> >>>>>>>>> +							  led_data->channel);
> >>>>>>>>> +		if (other_led_data) {
> >>>>>>>>> +			dev_err(dev,
> >>>>>>>>> +				"%s ignored: channel %d already used by %s",
> >>>>>>>>> +				led_data->cdev.name,
> >>>>>>>>> +				led_data->channel,
> >>>>>>>>> +				other_led_data->cdev.name);
> >>>>>>>>> +			continue;  
> >>>>>>>>
> >>>>>>>> Ditto.  
> >>>>>>>
> >>>>>>> OK.
> >>>>>>>  
> >>>>>>>>> +		}
> >>>>>>>>> +
> >>>>>>>>> +		ret = devm_led_classdev_register(dev, &led_data->cdev);
> >>>>>>>>> +		if (ret == 0) {
> >>>>>>>>> +			priv->num_leds++;
> >>>>>>>>> +		} else {
> >>>>>>>>> +			dev_err(dev, "failed to register PWM led for %s: %d\n",
> >>>>>>>>> +				led_data->cdev.name, ret);  
> >>>>>>>
> >>>>>>> Should I also fail here, then? Right now it will continue trying to
> >>>>>>> register future LED devices if a classdev_register fails for some
> >>>>>>> reason, and will successfully load even if all of them fail.  
> >>>>>>
> >>>>>> Please fail here too. If we can't setup the sub-LED that is advertised
> >>>>>> in a DT child node, then it means that something went wrong.
> >>>>>> This is clear error case.  
> >>>>>
> >>>>> Done.
> >>>>>  
> >>>>>>>>> +		}
> >>>>>>>>> +	}
> >>>>>>>>> +
> >>>>>>>>> +	return 0;
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> +static const struct of_device_id of_is31fl31xx_match[] = {
> >>>>>>>>> +	{ .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },
> >>>>>>>>> +	{ .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },
> >>>>>>>>> +	{ .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, },
> >>>>>>>>> +	{ .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, },
> >>>>>>>>> +	{},
> >>>>>>>>> +};
> >>>>>>>>> +
> >>>>>>>>> +MODULE_DEVICE_TABLE(of, of_is31fl31xx_match);
> >>>>>>>>> +
> >>>>>>>>> +static int is31fl32xx_probe(struct i2c_client *client,
> >>>>>>>>> +				  const struct i2c_device_id *id)
> >>>>>>>>> +{
> >>>>>>>>> +	const struct is31fl32xx_chipdef *cdef;
> >>>>>>>>> +	const struct of_device_id *of_dev_id;
> >>>>>>>>> +	struct device *dev = &client->dev;
> >>>>>>>>> +	struct is31fl32xx_priv *priv;
> >>>>>>>>> +	int count;
> >>>>>>>>> +	int ret = 0;
> >>>>>>>>> +
> >>>>>>>>> +	of_dev_id = of_match_device(of_is31fl31xx_match, dev);
> >>>>>>>>> +	if (!of_dev_id)
> >>>>>>>>> +		return -EINVAL;
> >>>>>>>>> +
> >>>>>>>>> +	cdef = of_dev_id->data;
> >>>>>>>>> +
> >>>>>>>>> +	count = of_get_child_count(dev->of_node);
> >>>>>>>>> +	if (!count)
> >>>>>>>>> +		return -EINVAL;
> >>>>>>>>> +
> >>>>>>>>> +	priv = devm_kzalloc(dev, sizeof_is31fl32xx_priv(count),
> >>>>>>>>> +			    GFP_KERNEL);
> >>>>>>>>> +	if (!priv)
> >>>>>>>>> +		return -ENOMEM;
> >>>>>>>>> +
> >>>>>>>>> +	priv->client = client;
> >>>>>>>>> +	priv->cdef = cdef;
> >>>>>>>>> +	i2c_set_clientdata(client, priv);
> >>>>>>>>> +
> >>>>>>>>> +	ret = is31fl32xx_init_regs(priv);
> >>>>>>>>> +	if (ret)
> >>>>>>>>> +		return ret;
> >>>>>>>>> +
> >>>>>>>>> +	ret = is31fl32xx_parse_dt(dev, priv);
> >>>>>>>>> +	if (ret)
> >>>>>>>>> +		return ret;
> >>>>>>>>> +
> >>>>>>>>> +	return 0;
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> +static int is31fl32xx_remove(struct i2c_client *client)
> >>>>>>>>> +{
> >>>>>>>>> +	struct is31fl32xx_priv *priv = i2c_get_clientdata(client);
> >>>>>>>>> +
> >>>>>>>>> +	/* If there is a reset reg, then it does everything we need */
> >>>>>>>>> +	if (priv->cdef->reset_reg != IS31FL32XX_REG_NONE)
> >>>>>>>>> +		return is31fl32xx_write(priv, priv->cdef->reset_reg, 0);
> >>>>>>>>> +
> >>>>>>>>> +	/* If there is a reset func, then it does everything we need */
> >>>>>>>>> +	if (priv->cdef->reset_func)
> >>>>>>>>> +		return priv->cdef->reset_func(priv);
> >>>>>>>>> +
> >>>>>>>>> +	/* If we can't reset, then try just using software-shutdown mode */
> >>>>>>>>> +	if (priv->cdef->shutdown_reg != IS31FL32XX_REG_NONE)
> >>>>>>>>> +		return is31fl32xx_write(priv, priv->cdef->shutdown_reg, 0x00);
> >>>>>>>>> +
> >>>>>>>>> +	return 0;
> >>>>>>>>> +}
> >>>>>>>>> +
> >>>>>>>>> +/*
> >>>>>>>>> + * i2c-core requires that id_table be non-NULL, even though
> >>>>>>>>> + * it is not used for DeviceTree based instantiation.
> >>>>>>>>> + */
> >>>>>>>>> +static const struct i2c_device_id is31fl31xx_id[] = {
> >>>>>>>>> +	{},
> >>>>>>>>> +};
> >>>>>>>>> +
> >>>>>>>>> +MODULE_DEVICE_TABLE(i2c, is31fl31xx_id);
> >>>>>>>>> +
> >>>>>>>>> +static struct i2c_driver is31fl32xx_driver = {
> >>>>>>>>> +	.driver = {
> >>>>>>>>> +		.name	= "is31fl32xx",
> >>>>>>>>> +		.of_match_table = of_is31fl31xx_match,
> >>>>>>>>> +	},
> >>>>>>>>> +	.probe		= is31fl32xx_probe,
> >>>>>>>>> +	.remove		= is31fl32xx_remove,
> >>>>>>>>> +	.id_table	= is31fl31xx_id,
> >>>>>>>>> +};
> >>>>>>>>> +
> >>>>>>>>> +module_i2c_driver(is31fl32xx_driver);
> >>>>>>>>> +
> >>>>>>>>> +MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>");
> >>>>>>>>> +MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver");
> >>>>>>>>> +MODULE_LICENSE("GPL v2");
> >>>>>>>>>  
> > --
> > To unsubscribe from this list: send the line "unsubscribe devicetree" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH RFC 3/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers
  2016-03-01 18:45                     ` David Rivshin (Allworx)
@ 2016-03-02  8:15                       ` Jacek Anaszewski
  0 siblings, 0 replies; 33+ messages in thread
From: Jacek Anaszewski @ 2016-03-02  8:15 UTC (permalink / raw)
  To: David Rivshin (Allworx)
  Cc: Stefan Wahren, linux-leds, devicetree, Richard Purdie,
	Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala

On 03/01/2016 07:45 PM, David Rivshin (Allworx) wrote:
> On Tue, 01 Mar 2016 09:24:59 +0100
> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>
>> On 02/29/2016 07:26 PM, David Rivshin (Allworx) wrote:
>>> On Mon, 29 Feb 2016 10:47:44 +0100
>>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>>>
>>>> On 02/26/2016 10:58 PM, David Rivshin (Allworx) wrote:
>>>>> On Fri, 26 Feb 2016 10:47:46 +0100
>>>>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>>>>>
>>>>>> On 02/25/2016 08:12 PM, David Rivshin (Allworx) wrote:
>>>>>>> On Thu, 25 Feb 2016 11:55:58 +0100
>>>>>>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>>>>>>>
>>>>>>>> On 02/25/2016 03:24 AM, David Rivshin (Allworx) wrote:
>>>>>>>>> On Wed, 24 Feb 2016 17:04:58 +0100
>>>>>>>>> Jacek Anaszewski <j.anaszewski@samsung.com> wrote:
>>>>>>>>>
>>>>>>>>>> Hi David,
>>>>>>>>>>
>>>>>>>>>> Thanks for the patch. Very nice driver. I have few comments
>>>>>>>>>> below.
>>>>>>>>>
>>>>>>>>> Thanks Jacek, I have responded the comments inline. I also wanted to
>>>>>>>>> double check whether you noticed some questions I had in the cover
>>>>>>>>> letter [1]. As I mentioned in another email to Rob, in hindsight I'm
>>>>>>>>> guessing I should have included them in the patch comments as well (or
>>>>>>>>> instead of).
>>>>>>>>
>>>>>>>> I saw them. I assumed that the review itself will address those
>>>>>>>> questions.
>>>>>>>
>>>>>>> Fair enough, thanks for the confirmation.
>>>>>>>
>>>>>>>>> Your review comments here effectively answered some of the questions, but
>>>>>>>>> the big one I'm still unsure of is whether it actually makes sense to
>>>>>>>>> have all 4 of these devices supported by a single driver.
>>>>>>>>
>>>>>>>> It's perfectly fine. Many drivers implement this pattern.
>>>>>>>
>>>>>>> OK, then I'll assume you think this driver is not yet too complicated
>>>>>>> for it's own good. Out of curiosity, might that view change if the
>>>>>>> 3216 specific features were ever implemented, especially GPIO and HW
>>>>>>> animation support? Gut feel is that would make 3216 specific code
>>>>>>> bigger than the rest of the code combined.
>>>>>>
>>>>>> I don't think so.
>>>>>
>>>>> Thanks, that helps calibrate my intuition for the future.
>>>>>
>>>>>>> Bigger question is what should be done in terms of the overlap in device
>>>>>>> support between this driver and leds-sn3218? If you think I should leave
>>>>>>> the *3218 support in this driver, then I would propose:
>>>>>>>       - remove leds-sn3218 and its separate binding doc
>>>>>>>       - add the "si-en,sn3218" compatible string to this driver and binding doc
>>>>>>> Note that while I expect this driver to work with the 3218 chips, I do
>>>>>>> not have one to test against. If we go down this route I would definitely
>>>>>>> want Stefan to test so that I don't accidentally break him.
>>>>>>
>>>>>> I'd prefer to have a single driver for the same hardware. Stefan, would
>>>>>> it be possible for you to test David's driver with the hardware you
>>>>>> have an access to?
>>>>>
>>>>> Stefan, one thing to note: the existing sn3218 driver/binding uses 0-based
>>>>> 'reg' values, and this driver/binding uses 1-based 'reg' values. So your
>>>>> devicetree(s) would need to be updated for that (as well as the compatible
>>>>> string).
>>>>
>>>> Actually, If your driver can successfully handle Si-En SN3218 I'd prefer
>>>> to drop leds-sn3218 along with its bindings and add related compatible
>>>> to your bindings documentation.
>>>
>>> Agreed. The changing of compatible string would only need to be done with
>>> the current version of the series.
>>> In the next version I'll add a 4th patch (unless you'd prefer a separate
>>> patch not part of the series?) that removes leds-sn3218 and moves that
>>> support into the is31fl32xx driver.
>>
>> Please add it as 4th patch to this set.
>
> OK.
>
>>>>> I didn't see a final answer from Rob as to which way is most appropriate
>>>>> for these devices yet, so I don't know which way this will end up in the
>>>>> final patch.
>>>>>
>>>>>>> Also I feel I should point out some differences between the 3218 support
>>>>>>> in this driver versus the leds-sn3218 driver, in case they have any
>>>>>>> impact:
>>>>>>>       - (as previously mentioned) leds-sn3218 turns off an LEDs enable
>>>>>>>         bit if the brightness is set to 0. This driver just sets the PWM
>>>>>>>         to 0 and leaves the enable bits always on.
>>>>>>
>>>>>> Setting brightness to 0 is an equivalent to turning the device in
>>>>>> a power down mode or at least in the state where the current consumption
>>>>>> is as low as possible. A hardware configuration that is most fitting
>>>>>> for this requirements should be chosen.
>>>>>
>>>>> As far as I can tell from the datasheets, setting the PWM duty cycle for
>>>>> a given channel to 0 should have the same net effect as setting the enable
>>>>> bit of that channel to 0. I assume the purpose of the enable bits is to
>>>>> make it easier to turn an LED on/off without adjusting the PWM duty cycle,
>>>>> but just using always the PWM duty cycle register conveniently maps to
>>>>> the leds API.
>>>>
>>>> ack.
>>>>
>>>>>>>       - leds-sn3218 uses a regmap, I think mostly to deal with the enable
>>>>>>>         bits, but it also has the benefit of showing up in debugfs. This
>>>>>>>         could be seen as useful in and of itself by some users. On the other
>>>>>>>         hand regmap introduces another mutex on every write.
>>>>>>
>>>>>> I will not insist on using regmap if you don't refer to the current
>>>>>> state of hw registers in your driver.
>>>>>
>>>>> Currently I have not had a need to refer to the current state of any HW
>>>>> registers. I could imagine that might be needed in the future if extra
>>>>> functionality is implemented, but it wasn't so far.
>>>>
>>>> Regmap exposes nice debugfs interface, so this, and the fact that there
>>>> are uncovered hw features, can be thought of as a sufficient
>>>> argument in favour of using regmap even now. But it's up to you.
>>>
>>> If it's OK with you, I think I'll leave it without regmap for now. I
>>> don't really relish the thought of having 4 large blocks of reg_default
>>> (especially the 3216 has a large register set for animation), and I
>>> haven't yet worked out how/if I could dynamically generate them from
>>> the chipdefs in a reasonable way.
>>
>> I'm OK with it.
>>
>>>>>>>       - leds-sn3218 implements the shutdown callback. Actually, I think I
>>>>>>>         should add that to this driver in any event.
>>>>>>
>>>>>> Do you see use cases or hardware configurations that need such
>>>>>> a callback? I didn't oppose in case of Stefan's driver, but if we are
>>>>>> at it, we can consult Stefan if he saw that use case?
>>>>>>
>>>>>> I'd say that shutdown op is usually useful when CPU and LED
>>>>>> controller are powered from different sources, which can result in
>>>>>> a situation when CPU is turned off, but LED remains on.
>>>>>
>>>>> That is exactly what happens today on my board: if the system is rebooted
>>>>> or shut down the LEDs all stay in whatever state they were last in. This
>>>>> could also be handled by userspace shutdown scripts easily enough, but I
>>>>> thought it surprising when the system reboots but LEDs stay on.
>>>>
>>>> That's the shutdown callback is for.
>>>
>>> I'll add a shutdown callback that will do the same as the remove callback,
>>> and ensure all LEDs go dark.
>>> Is there anything verboten with having one just call the other, or just
>>> registering the same function for both callbacks?
>>
>> Please enclose current content of your remove callback in a new
>> function, and call this function from both remove and shutdown.
>
> OK.
>
>>>>> On the
>>>>> other hand someone might have a good reason to want to leave an LED on
>>>>> through a reboot.
>>>>
>>>> If you have such a use case, then you can add DT property for this.
>>>> E.g. retain-state-on-shutdown.
>>>
>>> I don't have such a use case right now, but noted for the future.
>>>
>>>>> There is also an inconsistency on what happens during remove() vs shutdown().
>>>>> In led_classdev_unregister() brightness is set to LED_OFF, so that happens
>>>>> for all drivers on remove(). But only some drivers implement a shutdown()
>>>>> which also turns off LEDs, most do not. For instance, leds-gpio turns off
>>>>> all LEDs, but leds-pwm does not.
>>>>    >
>>>>> Is the general policy that LEDs should be forced off by the driver when the
>>>>> kernel halts or reboots the CPU? Or left alone and let userspace deal with
>>>>> it?
>>>>
>>>> I think that this depends on use cases and hardware configurations
>>>> available on the market. People added the callback when they needed it.
>>>> There is no defined policy for this.
>>>>
>>>>> And should this (in principle) be the same as what happens when a module
>>>>> is unloaded (which currently always turns LEDs off)?
>>>>
>>>> Turning the LED off on removal is logically justified IMO.
>>>
>>> Agreed. I just found the inconsistency in some/most drivers between remove
>>> and shutdown unexpected. Given that not all LED drivers turn off on shutdown,
>>> (and my use case desires them to turn off, including a leds-pwm instance),
>>> I'll just write 0 to /sys/class/leds/*/brightness unconditionally in a
>>> userspace shutdown script.
>>
>> Doesn't it stand in contradiction with your above statement?:
>>
>> "I'll add a shutdown callback that will do the same as the remove callback,"
>>
>> I'm a bit lost - does your current is31fl32xx_remove() turn all
>> the sub-LEDs off?
>
> Yes, is31fl32xx_remove (and soon is31fl32xx_shutdown) will turn all the
> LED channels off. However, I also have a leds-pwm instance and that does
> not turn off LEDs on shutdown(), so during a reboot those LEDs currently
> left on. Also, it's always possible that future boards might use a
> different device (and therefore driver) to drive LEDs. Very few LED
> existing drivers have a shutdown callback, so I expect them to have the
> same behavior as leds-pwm.
>
> For obvious reasons I'd rather not have userspace know what LEDs are
> controlled through what specific driver (and what drivers do/don't
> turn off LEDs on their own). So if the kernel does not guarantee that
> all LEDs are turned off on a reboot, it's cleanest from a userspace
> perspective to just turn all LEDs off unconditionally via the sysfs
> interface.
>
> Unless I misunderstood your earlier statement:
>      "There is no defined policy for this."
> ? If you consider the current leds-pwm behavior a bug (as opposed to a
> choice), then I might submit a patch to add a shutdown callback for
> leds-pwm instead. Although I could imagine someone complaining about
> such a change being backwards-incompatible if they were somehow relying
> on current behavior. And there is still the vast majority of other LED
> drivers which I think likely have the same basic issue, though I can't
> test those to verify.

Thanks for the explanation. Your analysis makes me leaning towards
considering shutdown callback not fitting for LED class devices.
It imposes the shutdown policy, which may be not fitting for all
hardware configurations.

Please don't implement shutdown callback.

>>>>>>>       - leds-sn3218 just puts the chip in software-shutdown mode on remove/
>>>>>>>         shutdown. This driver uses the reset register to put the device in
>>>>>>>         poweron state, and software-shutdown is part of the poweron state.
>>>>>>>         Only difference would be if the next code to use the device does
>>>>>>>         not do it's own full initialization (which seems unlikely, or at
>>>>>>>         least unwise), but instead just clears software-shutdown.
>>>>>>
>>>>>> I believe that my above explanations address this question, i.e.
>>>>>> both brightness = 0 an remove/shutdown should set the device
>>>>>> in a power down mode.
>>>>>
>>>>> I think there is some confusion, there are 3 separate controls:
>>>>>     - per-LED PWM duty cycle
>>>>>     - per-LED enable bit
>>>>>     - device-wide shutdown mode
>>>>> Shutdown-mode results in all LEDs going dark (regardless of any other
>>>>> register state), and I think implies that it also uses less power
>>>>> (compared to just turning them all off with either of the other controls).
>>>>> Registers do retain their value and the I2C interface continues to work.
>>>>> I suspect that all it does is turn off an internal oscillator that drives
>>>>> the PWM circuits, but the documentation is not clear.
>>>>>
>>>>> The distinction I was making was that the leds-sn3218 driver *only* turned
>>>>> on the Shutdown-mode, while this driver reset all other registers in the
>>>>> device to their default values as well. Though in practice I don't expect
>>>>> that to make a difference.
>>>>
>>>> If documentation isn't clear about that, you can always measure current
>>>> consumption in both cases. Note, that this is not required, you can
>>>> follow your intuition.
>>>
>>> I may try to do that out of curiosity.
>>>
>>>>>>>>> I won't
>>>>>>>>> clutter this email with a duplicate of the details (it's somewhat long),
>>>>>>>>> but if you could check the cover letter and give some guidance, I would
>>>>>>>>> appreciate it.
>>>>>>>>>
>>>>>>>>> [1] http://www.spinics.net/lists/linux-leds/msg05564.html
>>>>>>>>>          http://thread.gmane.org/gmane.linux.leds/4530
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> On 02/23/2016 07:17 PM, David Rivshin (Allworx) wrote:
>>>>>>>>>>> From: David Rivshin <drivshin@allworx.com>
>>>>>>>>>>>
>>>>>>>>>>> The IS31FL32xx family of LED drivers are I2C devices with multiple
>>>>>>>>>>> constant-current channels, each with independent 256-level PWM control.
>>>>>>>>>>>
>>>>>>>>>>> HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
>>>>>>>>>>>
>>>>>>>>>>> This has been tested on the IS31FL3236 and IS31FL3216 on an ARM
>>>>>>>>>>> (TI am335x) platform.
>>>>>>>>>>>
>>>>>>>>>>> The programming paradigm of these devices is similar in the following
>>>>>>>>>>> ways:
>>>>>>>>>>>        - All registers are 8 bit
>>>>>>>>>>>        - All LED control registers are write-only
>>>>>>>>>>>        - Each LED channel has a PWM register (0-255)
>>>>>>>>>>>        - PWM register writes are shadowed until an Update register is poked
>>>>>>>>>>>        - All have a concept of Software Shutdown, which disables output
>>>>>>>>>>>
>>>>>>>>>>> However, there are some differences in devices:
>>>>>>>>>>>        - 3236/3235 have a separate Control register for each LED,
>>>>>>>>>>>          (3218/3216 pack the enable bits into fewer registers)
>>>>>>>>>>>        - 3236/3235 have a per-channel current divisor setting
>>>>>>>>>>>        - 3236/3235 have a Global Control register that can turn off all LEDs
>>>>>>>>>>>        - 3216 is unique in a number of ways
>>>>>>>>>>>           - OUT9-OUT16 can be configured as GPIOs instead of LED controls
>>>>>>>>>>>           - LEDs can be programmed with an 8-frame animation, with
>>>>>>>>>>>             programmable delay between frames
>>>>>>>>>>>           - LEDs can be modulated by an input audio signal
>>>>>>>>>>>           - Max output current can be adjusted from 1/4 to 2x globally
>>>>>>>>>>>           - Has a Configuration register instead of a Shutdown register
>>>>>>>>>>>
>>>>>>>>>>> This driver currently only supports the base PWM control function
>>>>>>>>>>> of these devices. The following features of these devices are not
>>>>>>>>>>> implemented, although it should be possible to add them in the future:
>>>>>>>>>>>        - All devices are capable of going into a lower-power "software
>>>>>>>>>>>          shutdown" mode.
>>>>>>>>>>>        - The is31fl3236 and is31fl3235 can reduce the max output current
>>>>>>>>>>>          per-channel with a divisor of 1, 2, 3, or 4.
>>>>>>>>>>>        - The is31fl3216 can use some LED channels as GPIOs instead.
>>>>>>>>>>>        - The is31fl3216 can animate LEDs in hardware.
>>>>>>>>>>>        - The is31fl3216 can modulate LEDs according to an audio input.
>>>>>>>>>>>        - The is31fl3216 can reduce/increase max output current globally.
>>>>>>>>>>>
>>>>>>>>>>> Signed-off-by: David Rivshin <drivshin@allworx.com>
>>>>>>>>>>> ---
>>>>>>>>>>>        drivers/leds/Kconfig           |   9 +
>>>>>>>>>>>        drivers/leds/Makefile          |   1 +
>>>>>>>>>>>        drivers/leds/leds-is31fl32xx.c | 442 +++++++++++++++++++++++++++++++++++++++++
>>>>>>>>>>>        3 files changed, 452 insertions(+)
>>>>>>>>>>>        create mode 100644 drivers/leds/leds-is31fl32xx.c
>>>>>>>>>>>
>>>>>>>>>>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>>>>>>>>>>> index 1034696..8f6c46f 100644
>>>>>>>>>>> --- a/drivers/leds/Kconfig
>>>>>>>>>>> +++ b/drivers/leds/Kconfig
>>>>>>>>>>> @@ -580,6 +580,15 @@ config LEDS_SN3218
>>>>>>>>>>>        	  This driver can also be built as a module. If so the module
>>>>>>>>>>>        	  will be called leds-sn3218.
>>>>>>>>>>>
>>>>>>>>>>> +config LEDS_IS31FL32XX
>>>>>>>>>>> +	tristate "Driver for ISSI IS31FL32XX I2C LED driver chip family"
>>>>>>>>>>
>>>>>>>>>> 2 x "[Dd]river".
>>>>>>>>>>
>>>>>>>>>> How about:
>>>>>>>>>>
>>>>>>>>>> "LED Support for ISSI IS31FL32XX I2C LED chip family" ?
>>>>>>>>>
>>>>>>>>> Yes, I found that awkward as well. HW folks (and the datasheets) seem
>>>>>>>>> always refer to devices of this type as "LED Driver"s (which can lead
>>>>>>>>> to some interesting confusions). Taking a cue from the LP5521/23/62
>>>>>>>>> entries, how about:
>>>>>>>>>       "LED Support for the ISSI IS31FL32XX I2C LED driver chip family" ?
>>>>>>>>
>>>>>>>> "LED Support" means "LED class driver". Driver is a software support
>>>>>>>> for hardware chip. What discrepancy do you see in the description
>>>>>>>> I proposed?
>>>>>>>
>>>>>>> I think in this case "driver" also means "hardware device which drives
>>>>>>> a physical LED".
>>>>>>
>>>>>> Let's not confuse these notions. From Linux perspective "driver" refers
>>>>>> to a piece of software used for controlling a hardware.
>>>>>>
>>>>>>> It seems that "LED driver" is the term universally used
>>>>>>> to describe this type of HW device in datasheets.
>>>>>>
>>>>>> There are also e.g. "LED controllers", "LED current regulators".
>>>>>> Let's stick to the convention predominantly used in the LED subsystem
>>>>>> kernel config menu.
>>>>>>
>>>>>>> So it seemed useful to
>>>>>>> use exactly that phrase in the description of what hardware this software
>>>>>>> supports. I could see someone interpreting the phrase "LED chip" as
>>>>>>> referring to an actual LED device.
>>>>>>> I don't feel very strongly on this topic, but for the sake of discussion,
>>>>>>> maybe "LED controller" would avoid any possible confusion in both
>>>>>>> directions?
>>>>>>
>>>>>> Right, so let's use the following:
>>>>>>
>>>>>> "LED Support for ISSI IS31FL32XX I2C LED controller family"
>>>>>>
>>>>>> I understand "LED Support" as "Linux LED subsystem support".
>>>>>
>>>>> STGM. Done.
>>>>>
>>>>>>>>> Perhaps that's the best of both worlds?
>>>>>>>>>
>>>>>>>>>>> +	depends on LEDS_CLASS && I2C && OF
>>>>>>>>>>> +	help
>>>>>>>>>>> +	  Say Y here to include support for the ISSI 31FL32XX LED driver family.
>>>>>>>>>>
>>>>>>>>>> s/driver/chip/
>>>>>>>>>>
>>>>>>>>>>> +	  They are I2C devices with multiple constant-current channels, each
>>>>>>>>>>> +	  with independent 256-level PWM control. This will only work with
>>>>>>>>>>> +	  device tree enabled devices.
>>>>>>>>>>
>>>>>>>>>> We can skip the last sentence I think.
>>>>>>>>>
>>>>>>>>> OK. FYI, I think I got that verbiage from LEDS_SYSCON.
>>>>>>>>
>>>>>>>> Having "depends on OF" is self-explanatory here.
>>>>>>>
>>>>>>> Noted.
>>>>>>>
>>>>>>>>>>> +
>>>>>>>>>>>        comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
>>>>>>>>>>>
>>>>>>>>>>>        config LEDS_BLINKM
>>>>>>>>>>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
>>>>>>>>>>> index 89c9b6f..3fdf313 100644
>>>>>>>>>>> --- a/drivers/leds/Makefile
>>>>>>>>>>> +++ b/drivers/leds/Makefile
>>>>>>>>>>> @@ -67,6 +67,7 @@ obj-$(CONFIG_LEDS_KTD2692)		+= leds-ktd2692.o
>>>>>>>>>>>        obj-$(CONFIG_LEDS_POWERNV)		+= leds-powernv.o
>>>>>>>>>>>        obj-$(CONFIG_LEDS_SEAD3)		+= leds-sead3.o
>>>>>>>>>>>        obj-$(CONFIG_LEDS_SN3218)		+= leds-sn3218.o
>>>>>>>>>>> +obj-$(CONFIG_LEDS_IS31FL32XX)		+= leds-is31fl32xx.o
>>>>>>>>>>>
>>>>>>>>>>>        # LED SPI Drivers
>>>>>>>>>>>        obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
>>>>>>>>>>> diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 0000000..8dea518
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/drivers/leds/leds-is31fl32xx.c
>>>>>>>>>>> @@ -0,0 +1,442 @@
>>>>>>>>>>> +/*
>>>>>>>>>>> + * linux/drivers/leds-is31fl32xx.c
>>>>>>>>>>> + *
>>>>>>>>>>> + * Driver for ISSI IS31FL32xx family of I2C LED controllers
>>>>>>>>>>> + *
>>>>>>>>>>> + * Copyright 2015 Allworx Corp.
>>>>>>>>>>> + *
>>>>>>>>>>> + *
>>>>>>>>>>> + * 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.
>>>>>>>>>>> + *
>>>>>>>>>>> + * HW Docs: http://www.issi.com/US/product-analog-fxled-driver.shtml
>>>>>>>>>>> + */
>>>>>>>>>>> +
>>>>>>>>>>> +#include <linux/err.h>
>>>>>>>>>>> +#include <linux/i2c.h>
>>>>>>>>>>> +#include <linux/kernel.h>
>>>>>>>>>>> +#include <linux/leds.h>
>>>>>>>>>>> +#include <linux/module.h>
>>>>>>>>>>> +#include <linux/of_platform.h>
>>>>>>>>>>> +
>>>>>>>>>>> +#ifdef DEBUG
>>>>>>>>>>> + #undef dev_dbg
>>>>>>>>>>> + #define dev_dbg dev_info
>>>>>>>>>>> +#endif
>>>>>>>>>>
>>>>>>>>>> What's the benefit of the above?
>>>>>>>>>
>>>>>>>>> It gave me a way to easily see debug output from the driver while it
>>>>>>>>> was parsing the DT (especially if the driver was built-in). Early on
>>>>>>>>> there were other things within that #ifdef as well.
>>>>>>>>> Regardless, passing ddebug_query on the kernel commandline is a more
>>>>>>>>> appropriate way of accomplishing that; I'll remove for the next version.
>>>>>>>>
>>>>>>>> Thanks.
>>>>>>>>
>>>>>>>>>>> +/* Used to indicate a device has no such register */
>>>>>>>>>>> +#define IS31FL32XX_REG_NONE 0xFF
>>>>>>>>>>> +
>>>>>>>>>>> +#define IS31FL3216_CONFIG_REG 0x00
>>>>>>>>>>> +#define IS31FL3216_LIGHTING_EFFECT_REG 0x03
>>>>>>>>>>> +#define IS31FL3216_CHANNEL_CONFIG_REG 0x04
>>>>>>>>>>> +
>>>>>>>>>>> +struct is31fl32xx_priv;
>>>>>>>>>>> +struct is31fl32xx_led_data {
>>>>>>>>>>> +	struct led_classdev cdev;
>>>>>>>>>>> +	u8 channel; /* 1-based, max priv->cdef->channels */
>>>>>>>>>>> +	struct is31fl32xx_priv *priv;
>>>>>>>>>>> +};
>>>>>>>>>>> +
>>>>>>>>>>> +struct is31fl32xx_priv {
>>>>>>>>>>> +	const struct is31fl32xx_chipdef *cdef;
>>>>>>>>>>> +	struct i2c_client *client;
>>>>>>>>>>> +	unsigned int num_leds;
>>>>>>>>>>> +	struct is31fl32xx_led_data leds[0];
>>>>>>>>>>
>>>>>>>>>> Is there any specific reason for not having *leds here instead?
>>>>>>>>>
>>>>>>>>> I followed a pattern from leds-pwm where it did a single allocation
>>>>>>>>> for both priv and priv->leds[]. See sizeof_is31fl32xx_priv(), and
>>>>>>>>> its use, below. I saw the benefit as one fewer small allocation, so
>>>>>>>>> slightly more kind to the allocator (and devres). If you'd prefer to
>>>>>>>>> do it as two allocations, I'll make the change.
>>>>>>>>
>>>>>>>> OK, I had to look at this one more time. I like the idea.
>>>>>>>
>>>>>>> OK, I'll keep it as-is.
>>>>>>>
>>>>>>>>>>> +};
>>>>>>>>>>> +
>>>>>>>>>>> +/**
>>>>>>>>>>> + * struct is31fl32xx_chipdef - chip-specific attributes
>>>>>>>>>>> + * @channels            : Number of LED channels
>>>>>>>>>>> + * @shutdown_reg        : address of Shutdown register (optional)
>>>>>>>>>>> + * @pwm_update_reg      : address of PWM Update register
>>>>>>>>>>> + * @global_control_reg	: address of Global Control register (optional)
>>>>>>>>>>> + * @reset_reg           : address of Reset register (optional)
>>>>>>>>>>> + * @pwm_register_base	: address of first PWM register
>>>>>>>>>>> + * @pwm_registers_reversed: : true if PWM registers count down instead of up
>>>>>>>>>>> + * @led_control_register_base : address of first LED control register (optional)
>>>>>>>>>>> + * @enable_bits_per_led_control_register: number of LEDs enable bits in each
>>>>>>>>>>> + * @reset_func:         : pointer to reset function
>>>>>>>>>>> + *
>>>>>>>>>>> + * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE
>>>>>>>>>>> + * indicates that this chip has no such register.
>>>>>>>>>>> + *
>>>>>>>>>>> + * If non-NULL, @reset_func will be called during probing to set all
>>>>>>>>>>> + * necessary registers to a known initialization state. This is needed
>>>>>>>>>>> + * for chips that do not have a @reset_reg.
>>>>>>>>>>> + *
>>>>>>>>>>> + * @enable_bits_per_led_control_register must be >=1 if
>>>>>>>>>>> + * @led_control_register_base != %IS31FL32XX_REG_NONE.
>>>>>>>>>>> + */
>>>>>>>>>>> +struct is31fl32xx_chipdef {
>>>>>>>>>>> +	u8	channels;
>>>>>>>>>>> +	u8	shutdown_reg;
>>>>>>>>>>> +	u8	pwm_update_reg;
>>>>>>>>>>> +	u8	global_control_reg;
>>>>>>>>>>> +	u8	reset_reg;
>>>>>>>>>>> +	u8	pwm_register_base;
>>>>>>>>>>> +	bool	pwm_registers_reversed;
>>>>>>>>>>> +	u8	led_control_register_base;
>>>>>>>>>>> +	u8	enable_bits_per_led_control_register;
>>>>>>>>>>> +	int (*reset_func)(struct is31fl32xx_priv *priv);
>>>>>>>>>>> +};
>>>>>>>>>>> +
>>>>>>>>>>> +static const struct is31fl32xx_chipdef is31fl3236_cdef = {
>>>>>>>>>>> +	.channels				= 36,
>>>>>>>>>>> +	.shutdown_reg				= 0x00,
>>>>>>>>>>> +	.pwm_update_reg				= 0x25,
>>>>>>>>>>> +	.global_control_reg			= 0x4a,
>>>>>>>>>>> +	.reset_reg				= 0x4f,
>>>>>>>>>>> +	.pwm_register_base			= 0x01,
>>>>>>>>>>> +	.led_control_register_base		= 0x26,
>>>>>>>>>>> +	.enable_bits_per_led_control_register	= 1,
>>>>>>>>>>> +};
>>>>>>>>>>> +
>>>>>>>>>>> +static const struct is31fl32xx_chipdef is31fl3235_cdef = {
>>>>>>>>>>> +	.channels				= 28,
>>>>>>>>>>> +	.shutdown_reg				= 0x00,
>>>>>>>>>>> +	.pwm_update_reg				= 0x25,
>>>>>>>>>>> +	.global_control_reg			= 0x4a,
>>>>>>>>>>> +	.reset_reg				= 0x4f,
>>>>>>>>>>> +	.pwm_register_base			= 0x05,
>>>>>>>>>>> +	.led_control_register_base		= 0x2a,
>>>>>>>>>>> +	.enable_bits_per_led_control_register	= 1,
>>>>>>>>>>> +};
>>>>>>>>>>> +
>>>>>>>>>>> +static const struct is31fl32xx_chipdef is31fl3218_cdef = {
>>>>>>>>>>> +	.channels				= 18,
>>>>>>>>>>> +	.shutdown_reg				= 0x00,
>>>>>>>>>>> +	.pwm_update_reg				= 0x16,
>>>>>>>>>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
>>>>>>>>>>> +	.reset_reg				= 0x17,
>>>>>>>>>>> +	.pwm_register_base			= 0x01,
>>>>>>>>>>> +	.led_control_register_base		= 0x13,
>>>>>>>>>>> +	.enable_bits_per_led_control_register	= 6,
>>>>>>>>>>> +};
>>>>>>>>>>> +
>>>>>>>>>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv);
>>>>>>>>>>> +static const struct is31fl32xx_chipdef is31fl3216_cdef = {
>>>>>>>>>>> +	.channels				= 16,
>>>>>>>>>>> +	.shutdown_reg				= IS31FL32XX_REG_NONE,
>>>>>>>>>>> +	.pwm_update_reg				= 0xB0,
>>>>>>>>>>> +	.global_control_reg			= IS31FL32XX_REG_NONE,
>>>>>>>>>>> +	.reset_reg				= IS31FL32XX_REG_NONE,
>>>>>>>>>>> +	.pwm_register_base			= 0x10,
>>>>>>>>>>> +	.pwm_registers_reversed			= true,
>>>>>>>>>>> +	.led_control_register_base		= 0x01,
>>>>>>>>>>> +	.enable_bits_per_led_control_register	= 8,
>>>>>>>>>>> +	.reset_func				= is31fl3216_reset,
>>>>>>>>>>> +};
>>>>>>>>>>> +
>>>>>>>>>>> +static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)
>>>>>>>>>>> +{
>>>>>>>>>>> +	int ret;
>>>>>>>>>>> +
>>>>>>>>>>> +	dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
>>>>>>>>>>> +
>>>>>>>>>>> +	ret =  i2c_smbus_write_byte_data(priv->client, reg, val);
>>>>>>>>>>> +	if (ret) {
>>>>>>>>>>> +		dev_err(&priv->client->dev,
>>>>>>>>>>> +			"register write to 0x%02X failed (error %d)",
>>>>>>>>>>> +			reg, ret);
>>>>>>>>>>> +	}
>>>>>>>>>>> +	return ret;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +/*
>>>>>>>>>>> + * Custom reset function for IS31FL3216 because it does not have a RESET
>>>>>>>>>>> + * register the way that the other IS31FL32xx chips do. We don't bother
>>>>>>>>>>> + * writing the GPIO and animation registers, because the registers we
>>>>>>>>>>> + * do write ensure those will have no effect.
>>>>>>>>>>> + */
>>>>>>>>>>> +static int is31fl3216_reset(struct is31fl32xx_priv *priv)
>>>>>>>>>>> +{
>>>>>>>>>>> +	unsigned int i;
>>>>>>>>>>> +	int ret;
>>>>>>>>>>> +
>>>>>>>>>>> +	for (i = 0; i < priv->cdef->channels; i++) {
>>>>>>>>>>> +		ret = is31fl32xx_write(priv, priv->cdef->pwm_register_base+i,
>>>>>>>>>>> +				       0x00);
>>>>>>>>>>> +		if (ret)
>>>>>>>>>>> +			return ret;
>>>>>>>>>>> +	}
>>>>>>>>>>> +	ret = is31fl32xx_write(priv, priv->cdef->pwm_update_reg, 0);
>>>>>>>>>>> +	if (ret)
>>>>>>>>>>> +		return ret;
>>>>>>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, 0x00);
>>>>>>>>>>> +	if (ret)
>>>>>>>>>>> +		return ret;
>>>>>>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, 0x00);
>>>>>>>>>>> +	if (ret)
>>>>>>>>>>> +		return ret;
>>>>>>>>>>> +	ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, 0x00);
>>>>>>>>>>> +	if (ret)
>>>>>>>>>>> +		return ret;
>>>>>>>>>>> +
>>>>>>>>>>> +	return 0;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +
>>>>>>>>>>> +static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,
>>>>>>>>>>> +				     enum led_brightness brightness)
>>>>>>>>>>> +{
>>>>>>>>>>> +	const struct is31fl32xx_led_data *led_data =
>>>>>>>>>>> +		container_of(led_cdev, struct is31fl32xx_led_data, cdev);
>>>>>>>>>>> +	const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef;
>>>>>>>>>>> +	u8 pwm_register_offset;
>>>>>>>>>>> +	int ret;
>>>>>>>>>>> +
>>>>>>>>>>> +	dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);
>>>>>>>>>>> +
>>>>>>>>>>> +	/* NOTE: led_data->channel is 1-based */
>>>>>>>>>>> +	if (cdef->pwm_registers_reversed)
>>>>>>>>>>> +		pwm_register_offset = cdef->channels - led_data->channel;
>>>>>>>>>>> +	else
>>>>>>>>>>> +		pwm_register_offset = led_data->channel - 1;
>>>>>>>>>>> +
>>>>>>>>>>> +	ret = is31fl32xx_write(led_data->priv,
>>>>>>>>>>> +			       cdef->pwm_register_base + pwm_register_offset,
>>>>>>>>>>> +			       brightness);
>>>>>>>>>>> +	if (ret)
>>>>>>>>>>> +		return ret;
>>>>>>>>>>
>>>>>>>>>> I infer that nothing wrong happens in case current process is preempted
>>>>>>>>>> here by the call originating from the other sub-LED?
>>>>>>>>>
>>>>>>>>> I do not believe so. All the driver-specific data used here is read-only
>>>>>>>>> after probing. chipdefs are entirely const, and the only thing in priv
>>>>>>>>> that's referenced is the chipdef pointer which logically could not change
>>>>>>>>> post-probe. Actually nothing else in priv is modified post-probe either.
>>>>>>>>>
>>>>>>>>> The I2C core code has a mutex on the bus, so two writes cannot happen at
>>>>>>>>> once.
>>>>>>>>>
>>>>>>>>> In all these devices there is a unique PWM duty-cycle register for each
>>>>>>>>> LED channel (which is what is being written here), so no register writes
>>>>>>>>> for one LED channel effect any others.
>>>>>>>>>
>>>>>>>>> I believe the worst that could happen is that the device would see:
>>>>>>>>> 	PWM_REG_A write X
>>>>>>>>> 	PWM_REG_B write Y
>>>>>>>>> 	UPDATE_REG write 0
>>>>>>>>> 	UPDATE_REG write 0
>>>>>>>>> instead of
>>>>>>>>> 	PWM_REG_A write X
>>>>>>>>> 	UPDATE_REG write 0
>>>>>>>>> 	PWM_REG_B write Y
>>>>>>>>> 	UPDATE_REG write 0
>>>>>>>>> but that makes no difference to the functionality. Poking the update
>>>>>>>>> register merely applies all PWM register writes up to that point (I'm
>>>>>>>>> assuming to allow atomically changing the state of multiple LEDs at
>>>>>>>>> once).
>>>>>>>>
>>>>>>>> Thanks for this comprehensive explanation.
>>>>>>>
>>>>>>> Should I put some part of this explanation in a comment somewhere? Seems
>>>>>>> like the kind of thing someone else might wonder about in the future also.
>>>>>>
>>>>>> Good idea.
>>>>>
>>>>> Done.
>>>>>
>>>>>>>>> I should note here (as mentioned in cover letter), I made a choice to
>>>>>>>>> always leave the per-LED "enable" bits on, and let the PWM just get set
>>>>>>>>> to 0 naturally to turn an LED off. This differs from the existing SN3218
>>>>>>>>> driver, which used regmap_update_bits, and is then protected by a per-
>>>>>>>>> regmap mutex.
>>>>>>>>
>>>>>>>> ack.
>>>>>>>>
>>>>>>>>>>> +	return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);
>>>>>>>>>>> +
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
>>>>>>>>>>> +{
>>>>>>>>>>> +	const struct is31fl32xx_chipdef *cdef = priv->cdef;
>>>>>>>>>>> +	int ret;
>>>>>>>>>>> +
>>>>>>>>>>> +	if (cdef->reset_reg != IS31FL32XX_REG_NONE) {
>>>>>>>>>>> +		ret = is31fl32xx_write(priv, cdef->reset_reg, 0);
>>>>>>>>>>> +		if (ret)
>>>>>>>>>>> +			return ret;
>>>>>>>>>>> +	}
>>>>>>>>>>> +	if (cdef->reset_func) {
>>>>>>>>>>> +		ret = cdef->reset_func(priv);
>>>>>>>>>>> +		if (ret)
>>>>>>>>>>> +			return ret;
>>>>>>>>>>> +	}
>>>>>>>>>>> +	if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) {
>>>>>>>>>>> +		u8 value =
>>>>>>>>>>> +		    GENMASK(cdef->enable_bits_per_led_control_register-1, 0);
>>>>>>>>>>> +		u8 num_regs = cdef->channels /
>>>>>>>>>>> +				cdef->enable_bits_per_led_control_register;
>>>>>>>>>>> +		int i;
>>>>>>>>>>> +
>>>>>>>>>>> +		for (i = 0; i < num_regs; i++) {
>>>>>>>>>>> +			ret = is31fl32xx_write(priv,
>>>>>>>>>>> +					       cdef->led_control_register_base+i,
>>>>>>>>>>> +					       value);
>>>>>>>>>>> +			if (ret)
>>>>>>>>>>> +				return ret;
>>>>>>>>>>> +		}
>>>>>>>>>>> +	}
>>>>>>>>>>> +	if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) {
>>>>>>>>>>> +		ret = is31fl32xx_write(priv, cdef->shutdown_reg, BIT(0));
>>>>>>>>>>> +		if (ret)
>>>>>>>>>>> +			return ret;
>>>>>>>>>>> +	}
>>>>>>>>>>> +	if (cdef->global_control_reg != IS31FL32XX_REG_NONE) {
>>>>>>>>>>> +		ret = is31fl32xx_write(priv, cdef->global_control_reg, 0x00);
>>>>>>>>>>> +		if (ret)
>>>>>>>>>>> +			return ret;
>>>>>>>>>>> +	}
>>>>>>>>>>> +
>>>>>>>>>>> +	return 0;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +static inline size_t sizeof_is31fl32xx_priv(int num_leds)
>>>>>>>>>>> +{
>>>>>>>>>>> +	return sizeof(struct is31fl32xx_priv) +
>>>>>>>>>>> +		      (sizeof(struct is31fl32xx_led_data) * num_leds);
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +static int is31fl32xx_parse_child_dt(const struct device *dev,
>>>>>>>>>>> +				     const struct device_node *child,
>>>>>>>>>>> +				     struct is31fl32xx_led_data *led_data)
>>>>>>>>>>> +{
>>>>>>>>>>> +	struct led_classdev *cdev = &led_data->cdev;
>>>>>>>>>>> +	int ret = 0;
>>>>>>>>>>> +	u32 reg;
>>>>>>>>>>> +
>>>>>>>>>>> +	cdev->name = of_get_property(child, "label", NULL) ? : child->name;
>>>>>>>>>>> +
>>>>>>>>>>> +	ret = of_property_read_u32(child, "reg", &reg);
>>>>>>>>>>> +	if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
>>>>>>>>>>> +		dev_err(dev,
>>>>>>>>>>> +			"Child node %s does not have a valid reg property\n",
>>>>>>>>>>> +			child->name);
>>>>>>>>>>> +		return -EINVAL;
>>>>>>>>>>> +	}
>>>>>>>>>>> +	led_data->channel = reg;
>>>>>>>>>>> +
>>>>>>>>>>> +	cdev->default_trigger = of_get_property(child, "linux,default-trigger",
>>>>>>>>>>> +						NULL);
>>>>>>>>>>> +	cdev->brightness = LED_OFF;
>>>>>>>>>>
>>>>>>>>>> devm_kzalloc secures that.
>>>>>>>>>
>>>>>>>>> OK, I will remove.
>>>>>>>>>
>>>>>>>>>>> +	ret = of_property_read_u32(child, "max-brightness",
>>>>>>>>>>> +				   &cdev->max_brightness);
>>>>>>>>>>> +	if (ret == -EINVAL) {
>>>>>>>>>>> +		cdev->max_brightness = 255;
>>>>>>>>>>
>>>>>>>>>> s/255/LED_FULL/
>>>>>>>>>
>>>>>>>>> Noted, although (from the patch 2 discussion) max-brightness property is
>>>>>>>>> removed/replaced, this would go away anyways.
>>>>>>>>>
>>>>>>>>>>> +	} else if (ret) {
>>>>>>>>>>> +		dev_dbg(dev,
>>>>>>>>>>> +			"Child node %s has an invalid max-brightness property\n",
>>>>>>>>>>> +			child->name);
>>>>>>>>>>> +		return -EINVAL;
>>>>>>>>>>> +	}
>>>>>>>>>>> +
>>>>>>>>>>> +	cdev->brightness_set_blocking = is31fl32xx_brightness_set;
>>>>>>>>>>
>>>>>>>>>> Please add empty line here.
>>>>>>>>>
>>>>>>>>> Done.
>>>>>>>>>
>>>>>>>>>>> +	return 0;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +static struct is31fl32xx_led_data *is31fl32xx_find_led_data(
>>>>>>>>>>> +					struct is31fl32xx_priv *priv,
>>>>>>>>>>> +					u8 channel)
>>>>>>>>>>> +{
>>>>>>>>>>> +	size_t i;
>>>>>>>>>>> +
>>>>>>>>>>> +	for (i = 0; i < priv->num_leds; i++) {
>>>>>>>>>>> +		if (priv->leds[i].channel == channel)
>>>>>>>>>>> +			return &priv->leds[i];
>>>>>>>>>>> +	}
>>>>>>>>>>
>>>>>>>>>> Ditto.
>>>>>>>>>
>>>>>>>>> Done.
>>>>>>>>>
>>>>>>>>>>> +	return NULL;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +static int is31fl32xx_parse_dt(struct device *dev,
>>>>>>>>>>> +			       struct is31fl32xx_priv *priv)
>>>>>>>>>>> +{
>>>>>>>>>>> +	struct device_node *child;
>>>>>>>>>>> +
>>>>>>>>>>> +	for_each_child_of_node(dev->of_node, child) {
>>>>>>>>>>> +		struct is31fl32xx_led_data *led_data =
>>>>>>>>>>> +			&priv->leds[priv->num_leds];
>>>>>>>>>>> +		int ret = 0;
>>>>>>>>>>> +		const struct is31fl32xx_led_data *other_led_data;
>>>>>>>>>>> +
>>>>>>>>>>> +		led_data->priv = priv;
>>>>>>>>>>> +
>>>>>>>>>>> +		ret = is31fl32xx_parse_child_dt(dev, child, led_data);
>>>>>>>>>>> +		if (ret)
>>>>>>>>>>> +			continue;
>>>>>>>>>>
>>>>>>>>>> I prefer failing in such cases,
>>>>>>>>>
>>>>>>>>> OK, I will change to an 'goto err' which will have an 'of_node_put()'
>>>>>>>>> and 'return ret'.
>>>>>>>>>
>>>>>>>>> I will say, however, that while testing the error-detection in the
>>>>>>>>> parsing logic, it was very convenient to construct a single devicetree
>>>>>>>>> with a variety of errors. Then a single boot would test multiple
>>>>>>>>> cases at once.
>>>>>>>>
>>>>>>>> Good idea for testing, but in case some failure occurs during DT child
>>>>>>>> node parsing in the release environment you're left with unused
>>>>>>>> allocated memory.
>>>>>>>
>>>>>>> Agreed. BTW, I assume from this that it's common to say "if there's
>>>>>>> anything wrong in one part of your DT, there is no guarantee as to what
>>>>>>> parts will actually be used"? I say this because what we're saying is that
>>>>>>> if one LED node on this device is faulty, that all of them are ignored.
>>>>>>> Analogy might be to a whole I2C bus being ignored because one of the
>>>>>>> devices on it failed to probe. To the devicetree it's still a parent/child
>>>>>>> bus/address relationship, even though the driver implementation is
>>>>>>> very different.
>>>>>>
>>>>>> I2C example is too generic I think. I2C controller is not as tightly
>>>>>> coupled with I2C clients as LED controller with its current outputs.
>>>>>> Besides, I2C controller doesn't have an idea what devices will attach
>>>>>> to the bus it controls upon probing, contrarily to a LED controller.
>>>>>
>>>>> OK. I realize that in code there is a large distinction in these cases,
>>>>> but I wasn't sure if that would be reflected in how errors in parsing
>>>>> the devicetree should handled. Sounds like there is at least a de-facto
>>>>> distinction between "a device and its children" and "a bus and its
>>>>> children".
>>>>>
>>>>>>>>>>> +
>>>>>>>>>>> +		/* Detect if channel is already in use by another child */
>>>>>>>>>>> +		other_led_data = is31fl32xx_find_led_data(priv,
>>>>>>>>>>> +							  led_data->channel);
>>>>>>>>>>> +		if (other_led_data) {
>>>>>>>>>>> +			dev_err(dev,
>>>>>>>>>>> +				"%s ignored: channel %d already used by %s",
>>>>>>>>>>> +				led_data->cdev.name,
>>>>>>>>>>> +				led_data->channel,
>>>>>>>>>>> +				other_led_data->cdev.name);
>>>>>>>>>>> +			continue;
>>>>>>>>>>
>>>>>>>>>> Ditto.
>>>>>>>>>
>>>>>>>>> OK.
>>>>>>>>>
>>>>>>>>>>> +		}
>>>>>>>>>>> +
>>>>>>>>>>> +		ret = devm_led_classdev_register(dev, &led_data->cdev);
>>>>>>>>>>> +		if (ret == 0) {
>>>>>>>>>>> +			priv->num_leds++;
>>>>>>>>>>> +		} else {
>>>>>>>>>>> +			dev_err(dev, "failed to register PWM led for %s: %d\n",
>>>>>>>>>>> +				led_data->cdev.name, ret);
>>>>>>>>>
>>>>>>>>> Should I also fail here, then? Right now it will continue trying to
>>>>>>>>> register future LED devices if a classdev_register fails for some
>>>>>>>>> reason, and will successfully load even if all of them fail.
>>>>>>>>
>>>>>>>> Please fail here too. If we can't setup the sub-LED that is advertised
>>>>>>>> in a DT child node, then it means that something went wrong.
>>>>>>>> This is clear error case.
>>>>>>>
>>>>>>> Done.
>>>>>>>
>>>>>>>>>>> +		}
>>>>>>>>>>> +	}
>>>>>>>>>>> +
>>>>>>>>>>> +	return 0;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +static const struct of_device_id of_is31fl31xx_match[] = {
>>>>>>>>>>> +	{ .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },
>>>>>>>>>>> +	{ .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },
>>>>>>>>>>> +	{ .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, },
>>>>>>>>>>> +	{ .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, },
>>>>>>>>>>> +	{},
>>>>>>>>>>> +};
>>>>>>>>>>> +
>>>>>>>>>>> +MODULE_DEVICE_TABLE(of, of_is31fl31xx_match);
>>>>>>>>>>> +
>>>>>>>>>>> +static int is31fl32xx_probe(struct i2c_client *client,
>>>>>>>>>>> +				  const struct i2c_device_id *id)
>>>>>>>>>>> +{
>>>>>>>>>>> +	const struct is31fl32xx_chipdef *cdef;
>>>>>>>>>>> +	const struct of_device_id *of_dev_id;
>>>>>>>>>>> +	struct device *dev = &client->dev;
>>>>>>>>>>> +	struct is31fl32xx_priv *priv;
>>>>>>>>>>> +	int count;
>>>>>>>>>>> +	int ret = 0;
>>>>>>>>>>> +
>>>>>>>>>>> +	of_dev_id = of_match_device(of_is31fl31xx_match, dev);
>>>>>>>>>>> +	if (!of_dev_id)
>>>>>>>>>>> +		return -EINVAL;
>>>>>>>>>>> +
>>>>>>>>>>> +	cdef = of_dev_id->data;
>>>>>>>>>>> +
>>>>>>>>>>> +	count = of_get_child_count(dev->of_node);
>>>>>>>>>>> +	if (!count)
>>>>>>>>>>> +		return -EINVAL;
>>>>>>>>>>> +
>>>>>>>>>>> +	priv = devm_kzalloc(dev, sizeof_is31fl32xx_priv(count),
>>>>>>>>>>> +			    GFP_KERNEL);
>>>>>>>>>>> +	if (!priv)
>>>>>>>>>>> +		return -ENOMEM;
>>>>>>>>>>> +
>>>>>>>>>>> +	priv->client = client;
>>>>>>>>>>> +	priv->cdef = cdef;
>>>>>>>>>>> +	i2c_set_clientdata(client, priv);
>>>>>>>>>>> +
>>>>>>>>>>> +	ret = is31fl32xx_init_regs(priv);
>>>>>>>>>>> +	if (ret)
>>>>>>>>>>> +		return ret;
>>>>>>>>>>> +
>>>>>>>>>>> +	ret = is31fl32xx_parse_dt(dev, priv);
>>>>>>>>>>> +	if (ret)
>>>>>>>>>>> +		return ret;
>>>>>>>>>>> +
>>>>>>>>>>> +	return 0;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +static int is31fl32xx_remove(struct i2c_client *client)
>>>>>>>>>>> +{
>>>>>>>>>>> +	struct is31fl32xx_priv *priv = i2c_get_clientdata(client);
>>>>>>>>>>> +
>>>>>>>>>>> +	/* If there is a reset reg, then it does everything we need */
>>>>>>>>>>> +	if (priv->cdef->reset_reg != IS31FL32XX_REG_NONE)
>>>>>>>>>>> +		return is31fl32xx_write(priv, priv->cdef->reset_reg, 0);
>>>>>>>>>>> +
>>>>>>>>>>> +	/* If there is a reset func, then it does everything we need */
>>>>>>>>>>> +	if (priv->cdef->reset_func)
>>>>>>>>>>> +		return priv->cdef->reset_func(priv);
>>>>>>>>>>> +
>>>>>>>>>>> +	/* If we can't reset, then try just using software-shutdown mode */
>>>>>>>>>>> +	if (priv->cdef->shutdown_reg != IS31FL32XX_REG_NONE)
>>>>>>>>>>> +		return is31fl32xx_write(priv, priv->cdef->shutdown_reg, 0x00);
>>>>>>>>>>> +
>>>>>>>>>>> +	return 0;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +/*
>>>>>>>>>>> + * i2c-core requires that id_table be non-NULL, even though
>>>>>>>>>>> + * it is not used for DeviceTree based instantiation.
>>>>>>>>>>> + */
>>>>>>>>>>> +static const struct i2c_device_id is31fl31xx_id[] = {
>>>>>>>>>>> +	{},
>>>>>>>>>>> +};
>>>>>>>>>>> +
>>>>>>>>>>> +MODULE_DEVICE_TABLE(i2c, is31fl31xx_id);
>>>>>>>>>>> +
>>>>>>>>>>> +static struct i2c_driver is31fl32xx_driver = {
>>>>>>>>>>> +	.driver = {
>>>>>>>>>>> +		.name	= "is31fl32xx",
>>>>>>>>>>> +		.of_match_table = of_is31fl31xx_match,
>>>>>>>>>>> +	},
>>>>>>>>>>> +	.probe		= is31fl32xx_probe,
>>>>>>>>>>> +	.remove		= is31fl32xx_remove,
>>>>>>>>>>> +	.id_table	= is31fl31xx_id,
>>>>>>>>>>> +};
>>>>>>>>>>> +
>>>>>>>>>>> +module_i2c_driver(is31fl32xx_driver);
>>>>>>>>>>> +
>>>>>>>>>>> +MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>");
>>>>>>>>>>> +MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver");
>>>>>>>>>>> +MODULE_LICENSE("GPL v2");
>>>>>>>>>>>
>>> --
>>> To unsubscribe from this list: send the line "unsubscribe devicetree" in
>>> the body of a message to majordomo@vger.kernel.org
>>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>
>
>


-- 
Best regards,
Jacek Anaszewski

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

end of thread, other threads:[~2016-03-02  8:15 UTC | newest]

Thread overview: 33+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-02-23 18:17 [PATCH RFC 0/3] leds: Add driver for the ISSI IS31FL32xx family of LED drivers David Rivshin (Allworx)
2016-02-23 18:17 ` [PATCH RFC 1/3] DT: Add vendor prefix for Integrated Silicon Solutions Inc David Rivshin (Allworx)
2016-02-23 23:36   ` Rob Herring
2016-02-23 18:17 ` [PATCH RFC 2/3] DT: leds: Add binding for the ISSI IS31FL32xx family of LED drivers David Rivshin (Allworx)
2016-02-24  1:15   ` Rob Herring
2016-02-24 18:57     ` David Rivshin (Allworx)
2016-02-24 19:45       ` Rob Herring
2016-02-24 23:16         ` David Rivshin (Allworx)
2016-02-25  1:17           ` Rob Herring
2016-02-24 16:04   ` Jacek Anaszewski
2016-02-24 19:34     ` David Rivshin (Allworx)
2016-02-24 19:49       ` Rob Herring
2016-02-26  0:30         ` David Rivshin (Allworx)
2016-02-26  9:56           ` Jacek Anaszewski
2016-02-23 18:17 ` [PATCH RFC 3/3] leds: Add driver " David Rivshin (Allworx)
2016-02-23 18:45   ` Mark Rutland
2016-02-23 22:38     ` David Rivshin (Allworx)
2016-02-24 16:08       ` Jacek Anaszewski
2016-02-24 16:04   ` Jacek Anaszewski
2016-02-25  2:24     ` David Rivshin (Allworx)
2016-02-25 10:55       ` Jacek Anaszewski
2016-02-25 19:12         ` David Rivshin (Allworx)
2016-02-26  9:47           ` Jacek Anaszewski
2016-02-26 21:58             ` David Rivshin (Allworx)
2016-02-27 10:48               ` Stefan Wahren
2016-02-29 18:02                 ` David Rivshin (Allworx)
2016-02-29 19:40                   ` Stefan Wahren
2016-03-01  1:32                     ` David Rivshin (Allworx)
2016-02-29  9:47               ` Jacek Anaszewski
2016-02-29 18:26                 ` David Rivshin (Allworx)
2016-03-01  8:24                   ` Jacek Anaszewski
2016-03-01 18:45                     ` David Rivshin (Allworx)
2016-03-02  8:15                       ` Jacek Anaszewski

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.