zephyr/drivers/led_strip/apa102.c
Roman Vaughan ac6fec45e7 drivers: led_strip: Add support for external SPI CS on APA102 LED strips
Some boards may include an APA102 LED on an existing SPI bus. With
additional circuitry, chip select is supported where the APA102
lacks a built in CS pin.

This patch allows CS to be defined in the devicetree (through .dts
files) and utilised in the apa102.c driver if it's available. Preserving
old behaviour if no chip select is defined.

Signed-off-by: Roman Vaughan <nzsmartie@gmail.com>
2020-07-02 08:22:31 -04:00

147 lines
3.5 KiB
C

/*
* Copyright (c) 2018 Google LLC.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT apa_apa102
#include <errno.h>
#include <drivers/led_strip.h>
#include <drivers/spi.h>
#include <drivers/gpio.h>
#include <sys/util.h>
/*
* Devicetree helper macros which gets the 'flags' cell from a 'cs_gpios'
* property on the spi bus, or returns 0 if the property has no 'flags' cell.
*
* Hopefully these helpers will be removed once #25827 is resolved.
*/
#define HAS_FLAGS(spi_dev, spi_reg) \
DT_PHA_HAS_CELL_AT_IDX(spi_dev, cs_gpios, spi_reg, flags)
#define INST_SPI_DEV_CS_GPIOS_HAS_FLAGS(node) \
HAS_FLAGS(DT_BUS(node), DT_REG_ADDR(node))
#define FLAGS_OR_ZERO(inst) \
COND_CODE_1( \
INST_SPI_DEV_CS_GPIOS_HAS_FLAGS(DT_DRV_INST(inst)), \
(DT_INST_SPI_DEV_CS_GPIOS_FLAGS(inst)), \
(0x0))
struct apa102_data {
struct device *spi;
struct spi_config cfg;
#if DT_INST_SPI_DEV_HAS_CS_GPIOS(0)
struct spi_cs_control cs_ctl;
#endif /* DT_INST_SPI_DEV_HAS_CS_GPIOS(0) */
};
static int apa102_update(struct device *dev, void *buf, size_t size)
{
struct apa102_data *data = dev->driver_data;
static const uint8_t zeros[] = {0, 0, 0, 0};
static const uint8_t ones[] = {0xFF, 0xFF, 0xFF, 0xFF};
const struct spi_buf tx_bufs[] = {
{
/* Start frame: at least 32 zeros */
.buf = (uint8_t *)zeros,
.len = sizeof(zeros),
},
{
/* LED data itself */
.buf = buf,
.len = size,
},
{
/* End frame: at least 32 ones to clock the
* remaining bits to the LEDs at the end of
* the strip.
*/
.buf = (uint8_t *)ones,
.len = sizeof(ones),
},
};
const struct spi_buf_set tx = {
.buffers = tx_bufs,
.count = ARRAY_SIZE(tx_bufs)
};
return spi_write(data->spi, &data->cfg, &tx);
}
static int apa102_update_rgb(struct device *dev, struct led_rgb *pixels,
size_t count)
{
uint8_t *p = (uint8_t *)pixels;
size_t i;
/* SOF (3 bits) followed by the 0 to 31 global dimming level */
uint8_t prefix = 0xE0 | 31;
/* Rewrite to the on-wire format */
for (i = 0; i < count; i++) {
uint8_t r = pixels[i].r;
uint8_t g = pixels[i].g;
uint8_t b = pixels[i].b;
*p++ = prefix;
*p++ = b;
*p++ = g;
*p++ = r;
}
BUILD_ASSERT(sizeof(struct led_rgb) == 4);
return apa102_update(dev, pixels, sizeof(struct led_rgb) * count);
}
static int apa102_update_channels(struct device *dev, uint8_t *channels,
size_t num_channels)
{
/* Not implemented */
return -EINVAL;
}
static int apa102_init(struct device *dev)
{
struct apa102_data *data = dev->driver_data;
data->spi = device_get_binding(DT_INST_BUS_LABEL(0));
if (!data->spi) {
return -ENODEV;
}
data->cfg.slave = DT_INST_REG_ADDR(0);
data->cfg.frequency = DT_INST_PROP(0, spi_max_frequency);
data->cfg.operation =
SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8);
#if DT_INST_SPI_DEV_HAS_CS_GPIOS(0)
data->cs_ctl.gpio_dev =
device_get_binding(DT_INST_SPI_DEV_CS_GPIOS_LABEL(0));
if (!data->cs_ctl.gpio_dev) {
return -ENODEV;
}
data->cs_ctl.gpio_pin = DT_INST_SPI_DEV_CS_GPIOS_PIN(0);
data->cs_ctl.delay = 0;
data->cfg.cs = &data->cs_ctl;
gpio_pin_configure(data->cs_ctl.gpio_dev, data->cs_ctl.gpio_pin,
GPIO_OUTPUT_INACTIVE | FLAGS_OR_ZERO(0));
#endif /* DT_INST_SPI_DEV_HAS_CS_GPIOS(0) */
return 0;
}
static struct apa102_data apa102_data_0;
static const struct led_strip_driver_api apa102_api = {
.update_rgb = apa102_update_rgb,
.update_channels = apa102_update_channels,
};
DEVICE_AND_API_INIT(apa102_0, DT_INST_LABEL(0), apa102_init,
&apa102_data_0, NULL, POST_KERNEL,
CONFIG_LED_STRIP_INIT_PRIORITY, &apa102_api);