Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions boards/arm/adafruit_kb2040/adafruit_kb2040-pinctrl.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,10 @@

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a description to the commit message: LED controller model, type of LED, pin number and driving information (PIO), etc

Copy link
Contributor

@simonguinot simonguinot May 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And please add a description as well to the commit adding the driver. It would be useful to describe a bit the PIO interface and how it can be used to output a signal compatible with WS2812 LED strip controllers.

Copy link
Member Author

@soburi soburi Aug 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a description to the commit message: LED controller model, type of LED, pin number and driving information (PIO), etc

Added short description to commit message

And please add a description as well to the commit adding the driver. It would be useful to describe a bit the PIO interface and how it can be used to output a signal compatible with WS2812 LED strip controllers.

I added information to commit message about setting that used to verify this patch.

clocks_default: clocks_default {
};

ws2812_pio0_default: ws2812_pio0_default {
ws2812 {
pinmux = <PIO0_P17>;
};
};
};
25 changes: 25 additions & 0 deletions boards/arm/adafruit_kb2040/adafruit_kb2040.dts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "adafruit_kb2040-pinctrl.dtsi"
#include "sparkfun_pro_micro_connector.dtsi"
#include <freq.h>
#include <zephyr/dt-bindings/led/led.h>

/ {
chosen {
Expand All @@ -24,6 +25,7 @@

aliases {
watchdog0 = &wdt0;
led-strip = &ws2812;
};
};

Expand Down Expand Up @@ -94,6 +96,29 @@
pinctrl-names = "default";
};

&pio0 {
status = "okay";

pio-ws2812 {
compatible = "worldsemi,ws2812-rpi_pico-pio";
status = "okay";
pinctrl-0 = <&ws2812_pio0_default>;
pinctrl-names = "default";
bit-waveform = <3>, <3>, <4>;

ws2812: ws2812 {
status = "okay";
output-pin = <17>;
chain-length = <1>;
color-mapping = <LED_COLOR_ID_GREEN
LED_COLOR_ID_RED
LED_COLOR_ID_BLUE>;
reset-delay = <280>;
frequency = <800000>;
};
};
};

zephyr_udc0: &usbd {
status = "okay";
};
Expand Down
1 change: 1 addition & 0 deletions drivers/led_strip/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ zephyr_library_sources_ifdef(CONFIG_LPD880X_STRIP lpd880x.c)
zephyr_library_sources_ifdef(CONFIG_WS2812_STRIP_GPIO ws2812_gpio.c)
zephyr_library_sources_ifdef(CONFIG_WS2812_STRIP_SPI ws2812_spi.c)
zephyr_library_sources_ifdef(CONFIG_WS2812_STRIP_I2S ws2812_i2s.c)
zephyr_library_sources_ifdef(CONFIG_WS2812_STRIP_RPI_PICO_PIO ws2812_rpi_pico_pio.c)
zephyr_library_sources_ifdef(CONFIG_TLC5971_STRIP tlc5971.c)
7 changes: 7 additions & 0 deletions drivers/led_strip/Kconfig.ws2812
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,11 @@ config WS2812_STRIP_GPIO
Note that this driver is not compatible with the Everlight B1414
controller.

config WS2812_STRIP_RPI_PICO_PIO
bool "Raspberry Pi Pico PIO"
depends on DT_HAS_WORLDSEMI_WS2812_RPI_PICO_PIO_ENABLED
select PICOSDK_USE_PIO
help
Use the PIO feature available on RaspberryPi Pico devices.

endchoice
249 changes: 249 additions & 0 deletions drivers/led_strip/ws2812_rpi_pico_pio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
/*
* Copyright (c) 2023 TOKITA Hiroshi
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/led_strip.h>
#include <zephyr/drivers/misc/pio_rpi_pico/pio_rpi_pico.h>
#include <zephyr/dt-bindings/led/led.h>
#include <zephyr/kernel.h>

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(ws2812_rpi_pico_pio, CONFIG_LED_STRIP_LOG_LEVEL);

#define DT_DRV_COMPAT worldsemi_ws2812_rpi_pico_pio

struct ws2812_led_strip_data {
uint32_t sm;
};

struct ws2812_led_strip_config {
const struct device *piodev;
uint32_t output_pin;
uint8_t num_colors;
uint32_t frequency;
const uint8_t *const color_mapping;
uint16_t reset_delay;
uint32_t cycles_per_bit;
};

struct ws2812_rpi_pico_pio_config {
const struct device *piodev;
const struct pinctrl_dev_config *const pcfg;
struct pio_program program;
};

static int ws2812_led_strip_sm_init(const struct device *dev)
{
const struct ws2812_led_strip_config *config = dev->config;
const float clkdiv =
sys_clock_hw_cycles_per_sec() / (config->cycles_per_bit * config->frequency);
pio_sm_config sm_config = pio_get_default_sm_config();
PIO pio;
int sm;

pio = pio_rpi_pico_get_pio(config->piodev);

sm = pio_claim_unused_sm(pio, false);
if (sm < 0) {
return -EINVAL;
}

sm_config_set_sideset(&sm_config, 1, false, false);
sm_config_set_sideset_pins(&sm_config, config->output_pin);
sm_config_set_out_shift(&sm_config, false, true, (config->num_colors == 4 ? 32 : 24));
sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
sm_config_set_clkdiv(&sm_config, clkdiv);
pio_sm_set_consecutive_pindirs(pio, sm, config->output_pin, 1, true);
pio_sm_init(pio, sm, -1, &sm_config);
pio_sm_set_enabled(pio, sm, true);

return sm;
}

/*
* Latch current color values on strip and reset its state machines.
*/
static inline void ws2812_led_strip_reset_delay(uint16_t delay)
{
k_usleep(delay);
}

static int ws2812_led_strip_update_rgb(const struct device *dev, struct led_rgb *pixels,
size_t num_pixels)
{
const struct ws2812_led_strip_config *config = dev->config;
struct ws2812_led_strip_data *data = dev->data;
PIO pio = pio_rpi_pico_get_pio(config->piodev);

for (size_t i = 0; i < num_pixels; i++) {
uint32_t color = 0;

for (size_t j = 0; j < config->num_colors; j++) {
switch (config->color_mapping[j]) {
/* White channel is not supported by LED strip API. */
case LED_COLOR_ID_WHITE:
color |= 0;
break;
case LED_COLOR_ID_RED:
color |= pixels[i].r << (8 * (2 - j));
break;
case LED_COLOR_ID_GREEN:
color |= pixels[i].g << (8 * (2 - j));
break;
case LED_COLOR_ID_BLUE:
color |= pixels[i].b << (8 * (2 - j));
break;
}
}

pio_sm_put_blocking(pio, data->sm, color << (config->num_colors == 4 ? 0 : 8));
}

ws2812_led_strip_reset_delay(config->reset_delay);

return 0;
}

static int ws2812_led_strip_update_channels(const struct device *dev, uint8_t *channels,
size_t num_channels)
{
LOG_DBG("update_channels not implemented");
return -ENOTSUP;
}

static const struct led_strip_driver_api ws2812_led_strip_api = {
.update_rgb = ws2812_led_strip_update_rgb,
.update_channels = ws2812_led_strip_update_channels,
};

/*
* Retrieve the channel to color mapping (e.g. RGB, BGR, GRB, ...) from the
* "color-mapping" DT property.
*/
static int ws2812_led_strip_init(const struct device *dev)
{
const struct ws2812_led_strip_config *config = dev->config;
struct ws2812_led_strip_data *data = dev->data;
int sm;

if (!device_is_ready(config->piodev)) {
LOG_ERR("%s: PIO device not ready", dev->name);
return -ENODEV;
}

for (uint32_t i = 0; i < config->num_colors; i++) {
switch (config->color_mapping[i]) {
case LED_COLOR_ID_WHITE:
case LED_COLOR_ID_RED:
case LED_COLOR_ID_GREEN:
case LED_COLOR_ID_BLUE:
break;
default:
LOG_ERR("%s: invalid channel to color mapping."
" Check the color-mapping DT property",
dev->name);
return -EINVAL;
}
}

sm = ws2812_led_strip_sm_init(dev);
if (sm < 0) {
return sm;
}

data->sm = sm;

return 0;
}

static int ws2812_rpi_pico_pio_init(const struct device *dev)
{
const struct ws2812_rpi_pico_pio_config *config = dev->config;
PIO pio;

if (!device_is_ready(config->piodev)) {
LOG_ERR("%s: PIO device not ready", dev->name);
return -ENODEV;
}

pio = pio_rpi_pico_get_pio(config->piodev);

pio_add_program(pio, &config->program);

return pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
}

#define CYCLES_PER_BIT(node) \
(DT_PROP_BY_IDX(node, bit_waveform, 0) + DT_PROP_BY_IDX(node, bit_waveform, 1) + \
DT_PROP_BY_IDX(node, bit_waveform, 2))

#define WS2812_CHILD_INIT(node) \
static const uint8_t ws2812_led_strip_##node##_color_mapping[] = \
DT_PROP(node, color_mapping); \
struct ws2812_led_strip_data ws2812_led_strip_##node##_data; \
\
static const struct ws2812_led_strip_config ws2812_led_strip_##node##_config = { \
.piodev = DEVICE_DT_GET(DT_PARENT(DT_PARENT(node))), \
.output_pin = DT_PROP(node, output_pin), \
.num_colors = DT_PROP_LEN(node, color_mapping), \
.color_mapping = ws2812_led_strip_##node##_color_mapping, \
.reset_delay = DT_PROP(node, reset_delay), \
.frequency = DT_PROP(node, frequency), \
.cycles_per_bit = CYCLES_PER_BIT(DT_PARENT(node)), \
}; \
\
DEVICE_DT_DEFINE(node, &ws2812_led_strip_init, NULL, &ws2812_led_strip_##node##_data, \
&ws2812_led_strip_##node##_config, POST_KERNEL, \
CONFIG_LED_STRIP_INIT_PRIORITY, &ws2812_led_strip_api);

#define SET_DELAY(op, inst, i) \
(op | (((DT_INST_PROP_BY_IDX(inst, bit_waveform, i) - 1) & 0xF) << 8))

/*
* This pio program runs [T0+T1+T2] cycles per 1 loop.
* The first `out` instruction outputs 0 by [T2] times to the sideset pin.
* These zeros are padding. Here is the start of actual data transmission.
* The second `jmp` instruction output 1 by [T0] times to the sideset pin.
* This `jmp` instruction jumps to line 3 if the value of register x is true.
* Otherwise, jump to line 4.
* The third `jmp` instruction outputs 1 by [T1] times to the sideset pin.
* After output, return to the first line.
* The fourth `jmp` instruction outputs 0 by [T1] times.
* After output, return to the first line and output 0 by [T2] times.
*
* In the case of configuration, T0=3, T1=3, T2 =4,
* the final output is 1110000000 in case register x is false.
* It represents code 0, defined in the datasheet.
* And outputs 1111110000 in case of x is true. It represents code 1.
*/
#define WS2812_RPI_PICO_PIO_INIT(inst) \
PINCTRL_DT_INST_DEFINE(inst); \
\
DT_INST_FOREACH_CHILD_STATUS_OKAY(inst, WS2812_CHILD_INIT); \
\
static const uint16_t rpi_pico_pio_ws2812_instructions_##inst[] = { \
SET_DELAY(0x6021, inst, 2), /* 0: out x, 1 side 0 [T2 - 1] */ \
SET_DELAY(0x1023, inst, 0), /* 1: jmp !x, 3 side 1 [T0 - 1] */ \
SET_DELAY(0x1000, inst, 1), /* 2: jmp 0 side 1 [T1 - 1] */ \
SET_DELAY(0x0000, inst, 1), /* 3: jmp 0 side 0 [T1 - 1] */ \
}; \
\
static const struct ws2812_rpi_pico_pio_config rpi_pico_pio_ws2812_##inst##_config = { \
.piodev = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
.program = \
{ \
.instructions = rpi_pico_pio_ws2812_instructions_##inst, \
.length = ARRAY_SIZE(rpi_pico_pio_ws2812_instructions_##inst), \
.origin = -1, \
}, \
}; \
\
DEVICE_DT_INST_DEFINE(inst, &ws2812_rpi_pico_pio_init, NULL, NULL, \
&rpi_pico_pio_ws2812_##inst##_config, POST_KERNEL, \
CONFIG_LED_STRIP_INIT_PRIORITY, NULL);

DT_INST_FOREACH_STATUS_OKAY(WS2812_RPI_PICO_PIO_INIT)
68 changes: 68 additions & 0 deletions dts/bindings/led_strip/worldsemi,ws2812-rpi_pico-pio.yaml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (non-blocking): The new compatibility string is somewhat misleading and could suggest that this feature could only be used for one board. The name "rpi_pico" refers to a board, namely the "Raspberry Pi Pico", which is based on the SoC called "RP2040". It is precisely this SoC that has this special PIO unit, not the board. A naming "worldsemi,ws2812-rp2040-pio" would be more accurate and comprehensible here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been a topic of discussion since the beginning of support for rpi pico, but at this point the official name of the SoC family name is unknown, and the discussion will be reconsidered once a successor model is released and the family name is confirmed. This PR also follows that policy.

https://discord.com/channels/720317445772017664/938474761405726800/1177014983423442964

This is the discussion at the time of the first commit:
#34835 (review)
The conclusion is that the name of a clear "SoC family" is unknown now.
There isn't clear consistency, including Pico-SDK.

The introduction of RP1 has made things even more confusing.
https://datasheets.raspberrypi.com/rp1/rp1-peripherals.pdf

I think it would be okay to reconsider when the family name becomes clearer as variations increase, as was the case in the original discussion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I understand. I didn't realize how confusing the current situation with the chip name is. I had been wondering about it for months…

Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright (c) 2023, TOKITA Hiroshi
# SPDX-License-Identifier: Apache-2.0

description: |
The pio node configured for ws2812.

compatible: "worldsemi,ws2812-rpi_pico-pio"

include: pinctrl-device.yaml

properties:
bit-waveform:
type: array
description: |
This property defines the waveform for sending 1-bit data.
The program uses the first three elements of the array.
The T0 is equal to T0H in the datasheet.
The T2 is equal to T1L in the datasheet.
The T1 is equal to (T1H-T0H) or (T0L-T1L) in the datasheet.

Code-0
+------+ +---
| | |
| T0 | T1+T2 |
| | |
---+ +-----------------+

Code-1
+---------------+ +---
| | |
| T0+T1 | T2 |
| | |
---+ +--------+


The frequency determines the wave period.
The T0~T2 means ratio in one period.

For example, T0=3, T1=3, T2=4 and the frequency is 800kHz case,
T0H is
(1 / 800kHz) * (3/10) = 375ns
T0L is
(1 / 800kHz) * ((4+3)/10) = 875ns

child-binding:
description: |
Worldsemi WS2812 or compatible LED strip driver based on RaspberryPi Pico's PIO
The LED strip node can put up to 4 instances under a single PIO node.

include: ws2812.yaml

properties:
output-pin:
type: int
required: true
description: |
Select the output pin.

Note: This driver does not configure the output pin.
You need to configure the pin with pinctrl that is in the parent node configuration
for use by PIO.

frequency:
type: int
description: |
Specify the number of times a waveform representing 1 bit is
transmitted per second. It is same meaning as bit-per-seconds.
WS2812 works with 800000. Set the value 400000 if use with WS2811.
1 change: 1 addition & 0 deletions samples/drivers/led_ws2812/boards/adafruit_kb2040.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONFIG_WS2812_STRIP_RPI_PICO_PIO=y