1 // SPDX-License-Identifier: GPL-2.0-only
3 * WorldSemi WS2812B individually-addressable LED driver using SPI
5 * Copyright 2022 Chuanhong Guo <gch981213@gmail.com>
7 * This driver simulates WS2812B protocol using SPI MOSI pin. A one pulse
8 * is transferred as 3'b110 and a zero pulse is 3'b100. For this driver to
9 * work properly, the SPI frequency should be 2.105MHz~2.85MHz and it needs
10 * to transfer all the bytes continuously.
13 #include <linux/led-class-multicolor.h>
14 #include <linux/leds.h>
15 #include <linux/module.h>
16 #include <linux/of_device.h>
17 #include <linux/property.h>
18 #include <linux/spi/spi.h>
19 #include <linux/mutex.h>
21 #define WS2812B_BYTES_PER_COLOR 3
22 #define WS2812B_NUM_COLORS 3
23 /* A continuous 0 for 50us+ as the 'reset' signal */
24 #define WS2812B_RESET_LEN 18
27 struct led_classdev_mc mc_cdev
;
28 struct mc_subled subled
[WS2812B_NUM_COLORS
];
33 struct led_classdev ldev
;
34 struct spi_device
*spi
;
39 struct ws2812b_led leds
[];
43 * ws2812b_set_byte - convert a byte of data to 3-byte SPI data for pulses
44 * @priv: pointer to the private data structure
45 * @offset: offset of the target byte in the data stream
46 * @val: 1-byte data to be set
48 * WS2812B receives a stream of bytes from DI, takes the first 3 byte as LED
49 * brightness and pases the rest to the next LED through the DO pin.
50 * This function assembles a single byte of data to the LED:
51 * A bit is represented with a pulse of specific length. A long pulse is a 1
52 * and a short pulse is a 0.
53 * SPI transfers data continuously, MSB first. We can send 3'b100 to create a
54 * 0 pulse and 3'b110 for a 1 pulse. In this way, a byte of data takes up 3
55 * bytes in a SPI transfer:
56 * 1x0 1x0 1x0 1x0 1x0 1x0 1x0 1x0
57 * Let's rearrange it in 8 bits:
58 * 1x01x01x 01x01x01 x01x01x0
59 * The higher 3 bits, middle 2 bits and lower 3 bits are represented with the
60 * 1st, 2nd and 3rd byte in the SPI transfer respectively.
61 * There are only 8 combinations for 3 bits and 4 for 2 bits, so we can create
62 * a lookup table for the 3 bytes.
63 * e.g. For 0x6b -> 2'b01101011:
64 * Bit 7-5: 3'b011 -> 10011011 -> 0x9b
65 * Bit 4-3: 2'b01 -> 01001101 -> 0x4d
66 * Bit 2-0: 3'b011 -> 00110110 -> 0x36
68 static void ws2812b_set_byte(struct ws2812b_priv
*priv
, size_t offset
, u8 val
)
70 /* The lookup table for Bit 7-5 4-3 2-0 */
71 const u8 h3b
[] = { 0x92, 0x93, 0x9a, 0x9b, 0xd2, 0xd3, 0xda, 0xdb };
72 const u8 m2b
[] = { 0x49, 0x4d, 0x69, 0x6d };
73 const u8 l3b
[] = { 0x24, 0x26, 0x34, 0x36, 0xa4, 0xa6, 0xb4, 0xb6 };
74 u8
*p
= priv
->data_buf
+ WS2812B_RESET_LEN
+ (offset
* WS2812B_BYTES_PER_COLOR
);
76 p
[0] = h3b
[val
>> 5]; /* Bit 7-5 */
77 p
[1] = m2b
[(val
>> 3) & 0x3]; /* Bit 4-3 */
78 p
[2] = l3b
[val
& 0x7]; /* Bit 2-0 */
81 static int ws2812b_set(struct led_classdev
*cdev
,
82 enum led_brightness brightness
)
84 struct led_classdev_mc
*mc_cdev
= lcdev_to_mccdev(cdev
);
85 struct ws2812b_led
*led
=
86 container_of(mc_cdev
, struct ws2812b_led
, mc_cdev
);
87 struct ws2812b_priv
*priv
= dev_get_drvdata(cdev
->dev
->parent
);
91 led_mc_calc_color_components(mc_cdev
, brightness
);
93 mutex_lock(&priv
->mutex
);
94 for (i
= 0; i
< WS2812B_NUM_COLORS
; i
++)
95 ws2812b_set_byte(priv
, led
->cascade
* WS2812B_NUM_COLORS
+ i
,
96 led
->subled
[i
].brightness
);
97 ret
= spi_write(priv
->spi
, priv
->data_buf
, priv
->data_len
);
98 mutex_unlock(&priv
->mutex
);
103 static int ws2812b_probe(struct spi_device
*spi
)
105 struct device
*dev
= &spi
->dev
;
107 struct ws2812b_priv
*priv
;
108 struct fwnode_handle
*led_node
;
109 int num_leds
, i
, cnt
, ret
;
111 num_leds
= device_get_child_node_count(dev
);
113 priv
= devm_kzalloc(dev
, struct_size(priv
, leds
, num_leds
), GFP_KERNEL
);
117 num_leds
* WS2812B_BYTES_PER_COLOR
* WS2812B_NUM_COLORS
+
119 priv
->data_buf
= kzalloc(priv
->data_len
, GFP_KERNEL
);
123 for (i
= 0; i
< num_leds
* WS2812B_NUM_COLORS
; i
++)
124 ws2812b_set_byte(priv
, i
, 0);
126 mutex_init(&priv
->mutex
);
127 priv
->num_leds
= num_leds
;
130 device_for_each_child_node(dev
, led_node
) {
131 struct led_init_data init_data
= {
134 /* WS2812B LEDs usually come with GRB color */
135 u32 color_idx
[WS2812B_NUM_COLORS
] = {
142 ret
= fwnode_property_read_u32(led_node
, "reg", &cascade
);
144 dev_err(dev
, "failed to obtain numerical LED index for %s",
145 fwnode_get_name(led_node
));
148 if (cascade
>= num_leds
) {
149 dev_err(dev
, "LED index of %s is larger than the number of LEDs.",
150 fwnode_get_name(led_node
));
155 cnt
= fwnode_property_count_u32(led_node
, "color-index");
156 if (cnt
> 0 && cnt
<= WS2812B_NUM_COLORS
)
157 fwnode_property_read_u32_array(led_node
, "color-index",
158 color_idx
, (size_t)cnt
);
160 priv
->leds
[cur_led
].mc_cdev
.subled_info
=
161 priv
->leds
[cur_led
].subled
;
162 priv
->leds
[cur_led
].mc_cdev
.num_colors
= WS2812B_NUM_COLORS
;
163 priv
->leds
[cur_led
].mc_cdev
.led_cdev
.max_brightness
= 255;
164 priv
->leds
[cur_led
].mc_cdev
.led_cdev
.brightness_set_blocking
= ws2812b_set
;
166 for (i
= 0; i
< WS2812B_NUM_COLORS
; i
++) {
167 priv
->leds
[cur_led
].subled
[i
].color_index
= color_idx
[i
];
168 priv
->leds
[cur_led
].subled
[i
].intensity
= 255;
171 priv
->leds
[cur_led
].cascade
= cascade
;
173 ret
= led_classdev_multicolor_register_ext(
174 dev
, &priv
->leds
[cur_led
].mc_cdev
, &init_data
);
176 dev_err(dev
, "registration of %s failed.",
177 fwnode_get_name(led_node
));
183 spi_set_drvdata(spi
, priv
);
187 for (; cur_led
>= 0; cur_led
--)
188 led_classdev_multicolor_unregister(&priv
->leds
[cur_led
].mc_cdev
);
189 mutex_destroy(&priv
->mutex
);
190 kfree(priv
->data_buf
);
194 static int ws2812b_remove(struct spi_device
*spi
)
196 struct ws2812b_priv
*priv
= spi_get_drvdata(spi
);
199 for (cur_led
= priv
->num_leds
- 1; cur_led
>= 0; cur_led
--)
200 led_classdev_multicolor_unregister(&priv
->leds
[cur_led
].mc_cdev
);
201 kfree(priv
->data_buf
);
202 mutex_destroy(&priv
->mutex
);
207 static const struct spi_device_id ws2812b_spi_ids
[] = {
211 MODULE_DEVICE_TABLE(spi
, ws2812b_spi_ids
);
213 static const struct of_device_id ws2812b_dt_ids
[] = {
214 { .compatible
= "worldsemi,ws2812b" },
217 MODULE_DEVICE_TABLE(of
, ws2812b_dt_ids
);
219 static struct spi_driver ws2812b_driver
= {
220 .probe
= ws2812b_probe
,
221 .remove
= ws2812b_remove
,
222 .id_table
= ws2812b_spi_ids
,
224 .name
= KBUILD_MODNAME
,
225 .of_match_table
= ws2812b_dt_ids
,
229 module_spi_driver(ws2812b_driver
);
231 MODULE_AUTHOR("Chuanhong Guo <gch981213@gmail.com>");
232 MODULE_DESCRIPTION("WS2812B LED driver using SPI");
233 MODULE_LICENSE("GPL");