/* * Copyright (c) 2017 Linaro Limited * Copyright (c) 2019, Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT worldsemi_ws2812_spi #include #include #define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL #include LOG_MODULE_REGISTER(ws2812_spi); #include #include #include #include #include /* spi-one-frame and spi-zero-frame in DT are for 8-bit frames. */ #define SPI_FRAME_BITS 8 /* * Delay time to make sure the strip has latched a signal. * * Despite datasheet claims, a 6 microsecond delay is enough to reset * the strip. Delay for 8 usec just to be safe. */ #define RESET_DELAY_USEC 8 /* * SPI master configuration: * * - mode 0 (the default), 8 bit, MSB first (arbitrary), one-line SPI * - no shenanigans (don't hold CS, don't hold the device lock, this * isn't an EEPROM) */ #define SPI_OPER (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | \ SPI_WORD_SET(SPI_FRAME_BITS) | SPI_LINES_SINGLE) #define BYTES_PER_PX(has_white) ((has_white) ? 32 : 24) struct ws2812_spi_data { struct device *spi; }; struct ws2812_spi_cfg { struct spi_config spi_cfg; uint8_t *px_buf; size_t px_buf_size; uint8_t one_frame; uint8_t zero_frame; bool has_white; }; static struct ws2812_spi_data *dev_data(struct device *dev) { return dev->driver_data; } static const struct ws2812_spi_cfg *dev_cfg(struct device *dev) { return dev->config_info; } /* * Serialize an 8-bit color channel value into an equivalent sequence * of SPI frames, MSbit first, where a one bit becomes SPI frame * one_frame, and zero bit becomes zero_frame. */ static inline void ws2812_spi_ser(uint8_t buf[8], uint8_t color, const uint8_t one_frame, const uint8_t zero_frame) { int i; for (i = 0; i < 8; i++) { buf[i] = color & BIT(7 - i) ? one_frame : zero_frame; } } /* * Returns true if and only if cfg->px_buf is big enough to convert * num_pixels RGB color values into SPI frames. */ static inline bool num_pixels_ok(const struct ws2812_spi_cfg *cfg, size_t num_pixels) { size_t nbytes; bool overflow; overflow = size_mul_overflow(num_pixels, BYTES_PER_PX(cfg->has_white), &nbytes); return !overflow && (nbytes <= cfg->px_buf_size); } /* * Latch current color values on strip and reset its state machines. */ static inline void ws2812_reset_delay(void) { /* * TODO: swap out with k_usleep() once that can be trusted to * work reliably. */ k_busy_wait(RESET_DELAY_USEC); } static int ws2812_strip_update_rgb(struct device *dev, struct led_rgb *pixels, size_t num_pixels) { const struct ws2812_spi_cfg *cfg = dev_cfg(dev); const uint8_t one = cfg->one_frame, zero = cfg->zero_frame; struct spi_buf buf = { .buf = cfg->px_buf, .len = cfg->px_buf_size, }; const struct spi_buf_set tx = { .buffers = &buf, .count = 1 }; uint8_t *px_buf = cfg->px_buf; size_t i; int rc; if (!num_pixels_ok(cfg, num_pixels)) { return -ENOMEM; } /* * Convert pixel data into SPI frames. Each frame has pixel * data in GRB on-wire format, with zeroed out white channel data * if applicable. */ for (i = 0; i < num_pixels; i++) { ws2812_spi_ser(px_buf, pixels[i].g, one, zero); ws2812_spi_ser(px_buf + 8, pixels[i].r, one, zero); ws2812_spi_ser(px_buf + 16, pixels[i].b, one, zero); if (cfg->has_white) { ws2812_spi_ser(px_buf + 24, 0, one, zero); px_buf += 32; } else { px_buf += 24; } } /* * Display the pixel data. */ rc = spi_write(dev_data(dev)->spi, &cfg->spi_cfg, &tx); ws2812_reset_delay(); return rc; } static int ws2812_strip_update_channels(struct device *dev, uint8_t *channels, size_t num_channels) { LOG_ERR("update_channels not implemented"); return -ENOTSUP; } static const struct led_strip_driver_api ws2812_spi_api = { .update_rgb = ws2812_strip_update_rgb, .update_channels = ws2812_strip_update_channels, }; #define WS2812_SPI_LABEL(idx) \ (DT_INST_LABEL(idx)) #define WS2812_SPI_NUM_PIXELS(idx) \ (DT_INST_PROP(idx, chain_length)) #define WS2812_SPI_HAS_WHITE(idx) \ (DT_INST_PROP(idx, has_white_channel) == 1) #define WS2812_SPI_BUS(idx) \ (DT_INST_BUS_LABEL(idx)) #define WS2812_SPI_SLAVE(idx) \ (DT_INST_REG_ADDR(idx)) #define WS2812_SPI_FREQ(idx) \ (DT_INST_PROP(idx, spi_max_frequency)) #define WS2812_SPI_ONE_FRAME(idx) \ (DT_INST_PROP(idx, spi_one_frame)) #define WS2812_SPI_ZERO_FRAME(idx)\ (DT_INST_PROP(idx, spi_zero_frame)) #define WS2812_SPI_BUFSZ(idx) \ (BYTES_PER_PX(WS2812_SPI_HAS_WHITE(idx)) * WS2812_SPI_NUM_PIXELS(idx)) #define WS2812_SPI_DEVICE(idx) \ \ static struct ws2812_spi_data ws2812_spi_##idx##_data; \ \ static uint8_t ws2812_spi_##idx##_px_buf[WS2812_SPI_BUFSZ(idx)]; \ \ static const struct ws2812_spi_cfg ws2812_spi_##idx##_cfg = { \ .spi_cfg = { \ .frequency = WS2812_SPI_FREQ(idx), \ .operation = SPI_OPER, \ .slave = WS2812_SPI_SLAVE(idx), \ .cs = NULL, \ }, \ .px_buf = ws2812_spi_##idx##_px_buf, \ .px_buf_size = WS2812_SPI_BUFSZ(idx), \ .one_frame = WS2812_SPI_ONE_FRAME(idx), \ .zero_frame = WS2812_SPI_ZERO_FRAME(idx), \ .has_white = WS2812_SPI_HAS_WHITE(idx), \ }; \ \ static int ws2812_spi_##idx##_init(struct device *dev) \ { \ struct ws2812_spi_data *data = dev_data(dev); \ \ data->spi = device_get_binding(WS2812_SPI_BUS(idx)); \ if (!data->spi) { \ LOG_ERR("SPI device %s not found", \ WS2812_SPI_BUS(idx)); \ return -ENODEV; \ } \ \ return 0; \ } \ \ DEVICE_AND_API_INIT(ws2812_spi_##idx, \ WS2812_SPI_LABEL(idx), \ ws2812_spi_##idx##_init, \ &ws2812_spi_##idx##_data, \ &ws2812_spi_##idx##_cfg, \ POST_KERNEL, \ CONFIG_LED_STRIP_INIT_PRIORITY, \ &ws2812_spi_api); DT_INST_FOREACH_STATUS_OKAY(WS2812_SPI_DEVICE)