You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
162 lines
3.6 KiB
162 lines
3.6 KiB
/* |
|
* Copyright (c) 2017 Linaro Limited |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <zephyr/drivers/led_strip.h> |
|
|
|
#include <errno.h> |
|
#include <string.h> |
|
|
|
#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 <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(lpd880x); |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/device.h> |
|
#include <zephyr/drivers/spi.h> |
|
#include <zephyr/sys/util.h> |
|
|
|
/* |
|
* 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 DEVICE_API(led_strip, 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);
|
|
|