* [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
* 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
* [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
* 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-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 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@vger.kernel.org, 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: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@vger.kernel.org, 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@vger.kernel.org, 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 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 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 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@vger.kernel.org, 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: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@vger.kernel.org, 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 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@vger.kernel.org, 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
* [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", ®); + 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", ®); > + 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", ®); > > + 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 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", ®); >>> + 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 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", ®); > + 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-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", ®); > > + 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", ®); >>> + 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", ®); > >>> + 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 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", ®); >>>>> + 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 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", ®); > >>>>> + 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-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 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-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", ®); >>>>>>> + 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-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", ®); > >>>>>>> + 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: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", ®); >>>>>>>>> + 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", ®); > >>>>>>>>> + 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", ®); >>>>>>>>>>> + 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 a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).