253 lines
9.6 KiB
C
253 lines
9.6 KiB
C
/*
|
|
* Copyright (c) 2023 TOKITA Hiroshi
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/drivers/gpio.h>
|
|
#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;
|
|
const uint8_t gpio_pin;
|
|
uint8_t num_colors;
|
|
size_t length;
|
|
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->gpio_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->gpio_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 size_t ws2812_led_strip_length(const struct device *dev)
|
|
{
|
|
const struct ws2812_led_strip_config *config = dev->config;
|
|
|
|
return config->length;
|
|
}
|
|
|
|
static const struct led_strip_driver_api ws2812_led_strip_api = {
|
|
.update_rgb = ws2812_led_strip_update_rgb,
|
|
.length = ws2812_led_strip_length,
|
|
};
|
|
|
|
/*
|
|
* 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))), \
|
|
.gpio_pin = DT_GPIO_PIN_BY_IDX(node, gpios, 0), \
|
|
.num_colors = DT_PROP_LEN(node, color_mapping), \
|
|
.length = DT_PROP(node, chain_length), \
|
|
.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)
|