mbox series

[0/3] leds: add driver for SPI driven WorldSemi WS2812B RGB LEDs

Message ID 20221129140955.137361-1-gch981213@gmail.com
Headers show
Series leds: add driver for SPI driven WorldSemi WS2812B RGB LEDs | expand

Message

Chuanhong Guo Nov. 29, 2022, 2:09 p.m. UTC
This patch adds support for driving a chain of WS2812B LED chips
using SPI bus.

WorldSemi WS2812B is a individually addressable LED chip that
can be chained together and controlled individually using a
single wire. The chip recognize a long pulse as a bit of 1 and
a short pulse as a bit of 0. Host sends a continuous stream
of 24-bits color values, each LED chip takes the first 3 byte
it receives as its color value and passes the leftover bytes to
the next LED on the chain.

This driver simulates this protocol using SPI bus by sending
a long pulse as 3'b110 and a short pulse as 3'b100. The SPI
frequency needs to be 2.105MHz~2.85MHz for the timing to be
correct and the controller needs to transfer all the bytes
continuously.

Chuanhong Guo (3):
  dt-bindings: vendor-prefixes: add an entry for WorldSemi
  dt-bindings: leds: add dt schema for worldsemi,ws2812b-spi
  leds: add driver for SPI driven WorldSemi WS2812B RGB LEDs

 .../bindings/leds/worldsemi,ws2812b-spi.yaml  | 131 +++++++++++
 .../devicetree/bindings/vendor-prefixes.yaml  |   2 +
 drivers/leds/rgb/Kconfig                      |  11 +
 drivers/leds/rgb/Makefile                     |   1 +
 drivers/leds/rgb/leds-ws2812b-spi.c           | 222 ++++++++++++++++++
 5 files changed, 367 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/leds/worldsemi,ws2812b-spi.yaml
 create mode 100644 drivers/leds/rgb/leds-ws2812b-spi.c

Comments

Sebastian Reichel Nov. 30, 2022, 12:14 a.m. UTC | #1
Hi,

On Tue, Nov 29, 2022 at 05:54:52PM +0100, Krzysztof Kozlowski wrote:
> On 29/11/2022 15:09, Chuanhong Guo wrote:
> > This patch adds dt binding schema for WorldSemi WS2812B driven using SPI
> > bus.

Nice, I have a hobby project for something similar (SK6812, which is
basically WS2812 with an extra white channel). I will switch to this
work and extend it once it lands :)

[...]

> > +      default-intensity:
> > +        description: |
> > +          An array of 3 integer specifying the default intensity of each color
> > +          components in this LED. <255 255 255> if unspecified.
> > +        $ref: /schemas/types.yaml#/definitions/uint32-array
> > +        minItems: 3
> 
> Drop minItems.... but:
> 
> > +        maxItems: 3
> > +        items:
> > +          minimum: 0
> > +          maximum: 255
> 
> default: 255

I would argue, that the default should be 0 (off) instead of 255
(full power).

> What controls the intensity? Don't you have PWM there?

WS2812 is a RGB led, which contains a small Microcontroller. The µC
takes 24 byte intensity data from a serial input and then passes on
any following bits to the next LED. SPI clk and chip-select are
ignored (chip-select support can be trivially added though).

You can find them everywhere nowadays, since they are quite cheap
(a few cents per LED) and need only one MOSI pin to control hundreds
of LEDs.

[...]

-- Sebastian
Krzysztof Kozlowski Nov. 30, 2022, 8:33 a.m. UTC | #2
On 30/11/2022 01:14, Sebastian Reichel wrote:
>>> +      default-intensity:
>>> +        description: |
>>> +          An array of 3 integer specifying the default intensity of each color
>>> +          components in this LED. <255 255 255> if unspecified.
>>> +        $ref: /schemas/types.yaml#/definitions/uint32-array
>>> +        minItems: 3
>>
>> Drop minItems.... but:
>>
>>> +        maxItems: 3
>>> +        items:
>>> +          minimum: 0
>>> +          maximum: 255
>>
>> default: 255
> 
> I would argue, that the default should be 0 (off) instead of 255
> (full power).

Yes.

> 
>> What controls the intensity? Don't you have PWM there?
> 
> WS2812 is a RGB led, which contains a small Microcontroller. The µC
> takes 24 byte intensity data from a serial input and then passes on
> any following bits to the next LED. SPI clk and chip-select are
> ignored (chip-select support can be trivially added though).
> 
> You can find them everywhere nowadays, since they are quite cheap
> (a few cents per LED) and need only one MOSI pin to control hundreds
> of LEDs.

OK. This should be anyway existing property, so default-brightness.

Best regards,
Krzysztof
Chuanhong Guo Nov. 30, 2022, 8:36 a.m. UTC | #3
Hi!

On Wed, Nov 30, 2022 at 12:54 AM Krzysztof Kozlowski
<krzysztof.kozlowski@linaro.org> wrote:
> > +description: |
> > +  WorldSemi WS2812B is a individually addressable LED chip that can be chained
> > +  together and controlled individually using a single wire.
> > +  This driver simulates the protocol used by this LED chip with SPI bus.
>
> Drop references to Linux driver, unless important for the binding.

I think the SPI part is important. (I'll explain it below.) What about:

This binding describes a chain of WS2812B LEDs connected to the SPI MOSI pin.

instead?

> > +  Typical setups includes connecting the data pin of the LED chain to MOSI as
> > +  the only device or using CS and MOSI with a tri-state voltage-level shifter
> > +  for the data pin.
> > +  The SPI frequency needs to be 2.105MHz~2.85MHz for the timing to be correct
> > +  and the controller needs to send all the bytes continuously.
> > +
> > +allOf:
> > +  - $ref: /schemas/spi/spi-peripheral-props.yaml#
> > +
> > +properties:
> > +  compatible:
> > +    const: worldsemi,ws2812b-spi
>
> Drop "-spi". Compatibles are not supposed to include bus information.
> The same for file name.

WS2812B isn't a SPI chip. It's controlled with only a single wire and
can be driven
using anything that can produce a long and a short pulse meeting its timing
requirement.
This driver uses a SPI bus to send the pulses, but it can also be
controlled with
I2S and the PIO pins on a Raspberry Pi Pico.
This spi suffix is to distinguish it from other possible
implementations if someone
else submits a support with a different peripheral.

>
> > +
> > +  reg:
> > +    description: The chip-select line on the SPI bus
>
> Drop description, it's obvious.

OK.

>
> > +    maxItems: 1
> > +
> > +  spi-max-frequency:
> > +    $ref: /schemas/types.yaml#/definitions/uint32
> > +    description:
> > +      Maximum SPI clocking speed of the device in Hz.
>
> No need for ref and description. It comes from spi-peripheral-props.

OK.

>
> > +    minimum: 2105000
> > +    maximum: 2850000
> > +
> > +  "#address-cells":
> > +    const: 1
> > +
> > +  "#size-cells":
> > +    const: 0
> > +
> > +patternProperties:
> > +  "^multi-led(@[0-9a-f])?$":
>
> Why unit address is optional?

It isn't. I copy-pasted it from led-class-multicolor.yaml and
didn't check the exact regex.
I'll fix it in the next version.

>
> > +    type: object
> > +    $ref: leds-class-multicolor.yaml#
>
>     unevaluatedProperties: false

OK.

> > +
> > +    properties:
> > +      color-index:
> > +        description: |
> > +          A 3-item array specifying color of each components in this LED. It
> > +          should be one of the LED_COLOR_ID_* prefixed definitions from the
> > +          header include/dt-bindings/leds/common.h. Defaults to
> > +          <LED_COLOR_ID_GREEN LED_COLOR_ID_RED LED_COLOR_ID_BLUE>
> > +          if unspecified.
> > +        $ref: /schemas/types.yaml#/definitions/uint32-array
> > +        minItems: 3
>
> Drop minItems.... but see comment below:
>
> > +        maxItems: 3
>
> Why this is different than other multi-color LEDs? I would expect here
> children with common.yaml.

WS2812B is a single LED package with 3 diodes and a microcontroller.
Each LED package has 3 colors. The original chip comes with GRB
color while there are some clones with RGB arrangement instead.
The LED chain can be really long so I'd like to simplify the binding
by using a single property to override the only variable, color, here.

>
> > +
> > +      default-intensity:
> > +        description: |
> > +          An array of 3 integer specifying the default intensity of each color
> > +          components in this LED. <255 255 255> if unspecified.
> > +        $ref: /schemas/types.yaml#/definitions/uint32-array
> > +        minItems: 3
>
> Drop minItems.... but:
>
> > +        maxItems: 3
> > +        items:
> > +          minimum: 0
> > +          maximum: 255
>
> default: 255
>
> What controls the intensity? Don't you have PWM there?

The LED takes 3-byte brightness value of each color. This property is used to
specify the default multi_intensity field for the multi-color LED. The final
brightness value is calculated with led_mc_calc_color_components like this:

mcled_cdev->subled_info[i].brightness = brightness *
mcled_cdev->subled_info[i].intensity / led_cdev->max_brightness;

The LED chip takes exactly 8 bits for the brightness (max_brightness = 255
which can't be changed.), so according to the formula above the maximum
intensity should be 255.

>
> > +
> > +      reg:
> > +        description: |
> > +          Which LED this node represents. The reg of the first LED on the chain
> > +          is 0.
>
> maxItems: 1

OK.

>
> > +
> > +    required:
> > +      - reg
> > +      - color
> > +      - function
> > +
> > +required:
> > +  - compatible
> > +
> > +additionalProperties: false
> > +
> > +examples:
> > +  - |
> > +    #include <dt-bindings/leds/common.h>
> > +    spi {
> > +        #address-cells = <1>;
> > +        #size-cells = <0>;
> > +
> > +        ws2812b@0 {
>
> Node names should be generic.
> https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#generic-names-recommendation

OK. I'll use leds instead.

>
> > +            #address-cells = <1>;
> > +            #size-cells = <0>;
> > +            compatible = "worldsemi,ws2812b-spi";
> > +            reg = <0>;
>
> compatible is first property, reg is second.

Got it.

--
Regards,
Chuanhong Guo
Chuanhong Guo Nov. 30, 2022, 8:38 a.m. UTC | #4
Hi!

On Wed, Nov 30, 2022 at 4:33 PM Krzysztof Kozlowski
<krzysztof.kozlowski@linaro.org> wrote:
> >> What controls the intensity? Don't you have PWM there?
> >
> > WS2812 is a RGB led, which contains a small Microcontroller. The µC
> > takes 24 byte intensity data from a serial input and then passes on
> > any following bits to the next LED. SPI clk and chip-select are
> > ignored (chip-select support can be trivially added though).
> >
> > You can find them everywhere nowadays, since they are quite cheap
> > (a few cents per LED) and need only one MOSI pin to control hundreds
> > of LEDs.
>
> OK. This should be anyway existing property, so default-brightness.

default-intensity is a different property. Intensity controls only the
color for a multicolor led. The final brightness is calculated with
this intensity value with the single brightness value of the current
LED. (See my previous mail.)
Krzysztof Kozlowski Nov. 30, 2022, 9:08 a.m. UTC | #5
On 30/11/2022 09:36, Chuanhong Guo wrote:
> Hi!
> 
> On Wed, Nov 30, 2022 at 12:54 AM Krzysztof Kozlowski
> <krzysztof.kozlowski@linaro.org> wrote:
>>> +description: |
>>> +  WorldSemi WS2812B is a individually addressable LED chip that can be chained
>>> +  together and controlled individually using a single wire.
>>> +  This driver simulates the protocol used by this LED chip with SPI bus.
>>
>> Drop references to Linux driver, unless important for the binding.
> 
> I think the SPI part is important. (I'll explain it below.) What about:
> 
> This binding describes a chain of WS2812B LEDs connected to the SPI MOSI pin.
> 
> instead?

OK

> 
>>> +  Typical setups includes connecting the data pin of the LED chain to MOSI as
>>> +  the only device or using CS and MOSI with a tri-state voltage-level shifter
>>> +  for the data pin.
>>> +  The SPI frequency needs to be 2.105MHz~2.85MHz for the timing to be correct
>>> +  and the controller needs to send all the bytes continuously.
>>> +
>>> +allOf:
>>> +  - $ref: /schemas/spi/spi-peripheral-props.yaml#
>>> +
>>> +properties:
>>> +  compatible:
>>> +    const: worldsemi,ws2812b-spi
>>
>> Drop "-spi". Compatibles are not supposed to include bus information.
>> The same for file name.
> 
> WS2812B isn't a SPI chip. It's controlled with only a single wire and
> can be driven
> using anything that can produce a long and a short pulse meeting its timing
> requirement.
> This driver uses a SPI bus to send the pulses, but it can also be
> controlled with
> I2S and the PIO pins on a Raspberry Pi Pico.
> This spi suffix is to distinguish it from other possible
> implementations if someone
> else submits a support with a different peripheral.

And that's exactly what I said - the compatibles should not include bus
information. The bus information comes from... the bus!

> 
>>
>>> +
>>> +  reg:
>>> +    description: The chip-select line on the SPI bus
>>
>> Drop description, it's obvious.
> 
> OK.
> 
>>
>>> +    maxItems: 1
>>> +
>>> +  spi-max-frequency:
>>> +    $ref: /schemas/types.yaml#/definitions/uint32
>>> +    description:
>>> +      Maximum SPI clocking speed of the device in Hz.
>>
>> No need for ref and description. It comes from spi-peripheral-props.
> 
> OK.
> 
>>
>>> +    minimum: 2105000
>>> +    maximum: 2850000
>>> +
>>> +  "#address-cells":
>>> +    const: 1
>>> +
>>> +  "#size-cells":
>>> +    const: 0
>>> +
>>> +patternProperties:
>>> +  "^multi-led(@[0-9a-f])?$":
>>
>> Why unit address is optional?
> 
> It isn't. I copy-pasted it from led-class-multicolor.yaml and
> didn't check the exact regex.
> I'll fix it in the next version.

Make it required and matching your case.

> 
>>
>>> +    type: object
>>> +    $ref: leds-class-multicolor.yaml#
>>
>>     unevaluatedProperties: false
> 
> OK.
> 
>>> +
>>> +    properties:
>>> +      color-index:
>>> +        description: |
>>> +          A 3-item array specifying color of each components in this LED. It
>>> +          should be one of the LED_COLOR_ID_* prefixed definitions from the
>>> +          header include/dt-bindings/leds/common.h. Defaults to
>>> +          <LED_COLOR_ID_GREEN LED_COLOR_ID_RED LED_COLOR_ID_BLUE>
>>> +          if unspecified.
>>> +        $ref: /schemas/types.yaml#/definitions/uint32-array
>>> +        minItems: 3
>>
>> Drop minItems.... but see comment below:
>>
>>> +        maxItems: 3
>>
>> Why this is different than other multi-color LEDs? I would expect here
>> children with common.yaml.
> 
> WS2812B is a single LED package with 3 diodes and a microcontroller.
> Each LED package has 3 colors. The original chip comes with GRB
> color while there are some clones with RGB arrangement instead.
> The LED chain can be really long so I'd like to simplify the binding
> by using a single property to override the only variable, color, here.

OK, that makes sense.

> 
>>
>>> +
>>> +      default-intensity:
>>> +        description: |
>>> +          An array of 3 integer specifying the default intensity of each color
>>> +          components in this LED. <255 255 255> if unspecified.
>>> +        $ref: /schemas/types.yaml#/definitions/uint32-array
>>> +        minItems: 3
>>
>> Drop minItems.... but:
>>
>>> +        maxItems: 3
>>> +        items:
>>> +          minimum: 0
>>> +          maximum: 255
>>
>> default: 255
>>
>> What controls the intensity? Don't you have PWM there?
> 
> The LED takes 3-byte brightness value of each color. This property is used to
> specify the default multi_intensity field for the multi-color LED. The final
> brightness value is calculated with led_mc_calc_color_components like this:
> 
> mcled_cdev->subled_info[i].brightness = brightness *
> mcled_cdev->subled_info[i].intensity / led_cdev->max_brightness;
> 
> The LED chip takes exactly 8 bits for the brightness (max_brightness = 255
> which can't be changed.), so according to the formula above the maximum
> intensity should be 255.

So this is brightness of each color... why insisting on calling it
differently?

Best regards,
Krzysztof
Chuanhong Guo Nov. 30, 2022, 9:25 a.m. UTC | #6
Hi!

On Wed, Nov 30, 2022 at 5:08 PM Krzysztof Kozlowski
<krzysztof.kozlowski@linaro.org> wrote:
> And that's exactly what I said - the compatibles should not include bus
> information. The bus information comes from... the bus!

Oh. I thought there will be a conflict if there is a SPI driver and
, say, an I2C driver with the same compatible string.

> [...]
> >>
> >> Why unit address is optional?
> >
> > It isn't. I copy-pasted it from led-class-multicolor.yaml and
> > didn't check the exact regex.
> > I'll fix it in the next version.
>
> Make it required and matching your case.

Got it.

> [...]
> >>> +      default-intensity:
> >>> +        description: |
> >>> +          An array of 3 integer specifying the default intensity of each color
> >>> +          components in this LED. <255 255 255> if unspecified.
> >>> +        $ref: /schemas/types.yaml#/definitions/uint32-array
> >>> +        minItems: 3
> [...]
> So this is brightness of each color...

I don't think so.
See the kernel doc for multicolor LED:
https://docs.kernel.org/leds/leds-class-multicolor.html
This property sets the sysfs file multi_intensity while the
actual LED brightness is controlled with another sysfs
file called 'brightness'.
Setting multi_intensity alone doesn't change the LED
brightness at all.
Krzysztof Kozlowski Nov. 30, 2022, 11:59 a.m. UTC | #7
On 30/11/2022 10:25, Chuanhong Guo wrote:
> Hi!
> 
> On Wed, Nov 30, 2022 at 5:08 PM Krzysztof Kozlowski
> <krzysztof.kozlowski@linaro.org> wrote:
>> And that's exactly what I said - the compatibles should not include bus
>> information. The bus information comes from... the bus!
> 
> Oh. I thought there will be a conflict if there is a SPI driver and
> , say, an I2C driver with the same compatible string.

We already have such. For example: adi,adxl312

> 
>> [...]
>>>>
>>>> Why unit address is optional?
>>>
>>> It isn't. I copy-pasted it from led-class-multicolor.yaml and
>>> didn't check the exact regex.
>>> I'll fix it in the next version.
>>
>> Make it required and matching your case.
> 
> Got it.
> 
>> [...]
>>>>> +      default-intensity:
>>>>> +        description: |
>>>>> +          An array of 3 integer specifying the default intensity of each color
>>>>> +          components in this LED. <255 255 255> if unspecified.
>>>>> +        $ref: /schemas/types.yaml#/definitions/uint32-array
>>>>> +        minItems: 3
>> [...]
>> So this is brightness of each color...
> 
> I don't think so.
> See the kernel doc for multicolor LED:
> https://docs.kernel.org/leds/leds-class-multicolor.html
> This property sets the sysfs file multi_intensity while the
> actual LED brightness is controlled with another sysfs
> file called 'brightness'.
> Setting multi_intensity alone doesn't change the LED
> brightness at all.

If you had brightness, that would be correct. But you do not have
brightness, right? Therefore the final brightness is always:

subled[i].brightness = 255 * subled[i].intensity / max_brightness (also
255);

Or your bindings are incomplete...

Best regards,
Krzysztof