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.
135 lines
3.3 KiB
135 lines
3.3 KiB
/* |
|
* Copyright (c) 2018 Google LLC. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT apa_apa102 |
|
|
|
#include <errno.h> |
|
#include <zephyr/drivers/led_strip.h> |
|
#include <zephyr/drivers/spi.h> |
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/sys/util.h> |
|
|
|
struct apa102_config { |
|
struct spi_dt_spec bus; |
|
size_t length; |
|
uint8_t *const end_frame; |
|
const size_t end_frame_size; |
|
}; |
|
|
|
static int apa102_update(const struct device *dev, void *buf, size_t size) |
|
{ |
|
const struct apa102_config *config = dev->config; |
|
static const uint8_t zeros[] = { 0, 0, 0, 0 }; |
|
|
|
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 *)config->end_frame, |
|
.len = config->end_frame_size, |
|
}, |
|
}; |
|
const struct spi_buf_set tx = { |
|
.buffers = tx_bufs, |
|
.count = ARRAY_SIZE(tx_bufs) |
|
}; |
|
|
|
return spi_write_dt(&config->bus, &tx); |
|
} |
|
|
|
static int apa102_update_rgb(const 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 size_t apa102_length(const struct device *dev) |
|
{ |
|
const struct apa102_config *config = dev->config; |
|
|
|
return config->length; |
|
} |
|
|
|
static int apa102_init(const struct device *dev) |
|
{ |
|
const struct apa102_config *config = dev->config; |
|
|
|
if (!spi_is_ready_dt(&config->bus)) { |
|
return -ENODEV; |
|
} |
|
|
|
memset(config->end_frame, 0xFF, config->end_frame_size); |
|
|
|
return 0; |
|
} |
|
|
|
static DEVICE_API(led_strip, apa102_api) = { |
|
.update_rgb = apa102_update_rgb, |
|
.length = apa102_length, |
|
}; |
|
|
|
/* |
|
* The "End frame" is statically allocated, as a sequence of 0xFF bytes |
|
* The only function of the “End frame” is to supply more clock pulses |
|
* to the string until the data has permeated to the last LED. The |
|
* number of clock pulses required is exactly half the total number |
|
* of LEDs in the string. See below `end_frame`. |
|
*/ |
|
#define APA102_DEVICE(idx) \ |
|
static uint8_t apa102_end_frame_##idx \ |
|
[(DT_INST_PROP(idx, chain_length) / \ |
|
sizeof(struct led_rgb) / 2) + 1]; \ |
|
static const struct apa102_config apa102_##idx##_config = { \ |
|
.bus = SPI_DT_SPEC_INST_GET( \ |
|
idx, \ |
|
SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), \ |
|
0), \ |
|
.length = DT_INST_PROP(idx, chain_length), \ |
|
.end_frame = apa102_end_frame_##idx, \ |
|
.end_frame_size = (DT_INST_PROP(idx, chain_length) / \ |
|
sizeof(struct led_rgb) / 2) + 1, \ |
|
}; \ |
|
\ |
|
DEVICE_DT_INST_DEFINE(idx, \ |
|
apa102_init, \ |
|
NULL, \ |
|
NULL, \ |
|
&apa102_##idx##_config, \ |
|
POST_KERNEL, \ |
|
CONFIG_LED_STRIP_INIT_PRIORITY, \ |
|
&apa102_api); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(APA102_DEVICE)
|
|
|