/* * Copyright (c) 2017 Linaro Limited * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #if DT_NODE_HAS_STATUS_OKAY(DT_INST(0, greeled_lpd8806)) #define DT_DRV_COMPAT greeled_lpd8806 #else #define DT_DRV_COMPAT greeled_lpd8803 #endif #define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL #include LOG_MODULE_REGISTER(lpd880x); #include #include #include #include /* * LPD880X SPI master configuration: * * - mode 0 (the default), 8 bit, MSB first, one-line SPI * - no shenanigans (no CS hold, release device lock, not an EEPROM) */ #define LPD880X_SPI_OPERATION (SPI_OP_MODE_MASTER | \ SPI_TRANSFER_MSB | \ SPI_WORD_SET(8)) struct lpd880x_config { struct spi_dt_spec bus; size_t length; }; static int lpd880x_update(const struct device *dev, void *data, size_t size) { const struct lpd880x_config *config = dev->config; /* * Per the AdaFruit reverse engineering notes on the protocol, * a zero byte propagates through at most 32 LED driver ICs. * The LPD8803 is the worst case, at 3 output channels per IC. */ uint8_t reset_size = DIV_ROUND_UP(DIV_ROUND_UP(size, 3), 32); uint8_t reset_buf[reset_size]; uint8_t last = 0x00; const struct spi_buf bufs[3] = { { /* Prepares the strip to shift in new data values. */ .buf = reset_buf, .len = reset_size }, { /* Displays the serialized pixel data. */ .buf = data, .len = size }, { /* Ensures the last byte of pixel data is displayed. */ .buf = &last, .len = sizeof(last) } }; const struct spi_buf_set tx = { .buffers = bufs, .count = 3 }; size_t rc; (void)memset(reset_buf, 0x00, reset_size); rc = spi_write_dt(&config->bus, &tx); if (rc) { LOG_ERR("can't update strip: %zu", rc); } return rc; } static int lpd880x_strip_update_rgb(const struct device *dev, struct led_rgb *pixels, size_t num_pixels) { uint8_t *px = (uint8_t *)pixels; uint8_t r, g, b; size_t i; /* * Overwrite a prefix of the pixels array with its on-wire * representation, eliminating padding/scratch garbage, if any. */ for (i = 0; i < num_pixels; i++) { r = 0x80 | (pixels[i].r >> 1); g = 0x80 | (pixels[i].g >> 1); b = 0x80 | (pixels[i].b >> 1); /* * GRB is the ordering used by commonly available * LPD880x strips. */ *px++ = g; *px++ = r; *px++ = b; } return lpd880x_update(dev, pixels, 3 * num_pixels); } static int lpd880x_strip_update_channels(const struct device *dev, uint8_t *channels, size_t num_channels) { size_t i; for (i = 0; i < num_channels; i++) { channels[i] = 0x80 | (channels[i] >> 1); } return lpd880x_update(dev, channels, num_channels); } static size_t lpd880x_strip_length(const struct device *dev) { const struct lpd880x_config *config = dev->config; return config->length; } static int lpd880x_strip_init(const struct device *dev) { const struct lpd880x_config *config = dev->config; if (!spi_is_ready_dt(&config->bus)) { LOG_ERR("SPI device %s not ready", config->bus.bus->name); return -ENODEV; } return 0; } static const struct lpd880x_config lpd880x_config = { .bus = SPI_DT_SPEC_INST_GET(0, LPD880X_SPI_OPERATION, 0), .length = DT_INST_PROP(0, chain_length), }; static const struct led_strip_driver_api lpd880x_strip_api = { .update_rgb = lpd880x_strip_update_rgb, .update_channels = lpd880x_strip_update_channels, .length = lpd880x_strip_length, }; DEVICE_DT_INST_DEFINE(0, lpd880x_strip_init, NULL, NULL, &lpd880x_config, POST_KERNEL, CONFIG_LED_STRIP_INIT_PRIORITY, &lpd880x_strip_api);