zephyr/drivers/led_strip/tlc59731.c

190 lines
5.1 KiB
C

/*
* Copyright (c) 2024 Javad Rahimipetroudi <javad.rahimipetroudi@mind.be>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT ti_tlc59731
/**
* @file
* @brief LED driver for the TLC59731 LED driver.
*
* TLC59731 is a 3-Channel, 8-Bit, PWM LED Driver
* With Single-Wire Interface (EasySet)
*
* The EasySet protocol is based on short pulses and the time between
* them. At least one pulse must be sent every T_CYCLE, which can be
* between 1.67us and 50us. We want to go as fast as possible, but
* delays under 1us don't work very well, so we settle on 5us for the
* cycle time.
* A pulse must be high for at least 14ns. In practice, turning a GPIO on
* and immediately off again already takes longer than that, so no delay
* is needed there.
* A zero is represented by no additional pulses within a cycle.
* A one is represented by an additional pulse between 275ns and 2.5us
* (half a cycle) after the first one. We need at least some delay to get to
* 275ns, but because of the limited granularity of k_busy_wait we use a
* full 1us. After the pulse, we wait an additional T_CYCLE_1 to complete
* the cycle. This time can be slightly shorter because the second pulse
* already closes the cycle.
* Finally we need to keep the line low for T_H0 to complete the address
* for a single chip, and T_H1 to complete the write for all chips.
*/
#include <zephyr/drivers/led_strip.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(tlc59731, CONFIG_LED_STRIP_LOG_LEVEL);
/* Pulse timing */
#define TLC59731_DELAY 0x01 /* us */
#define TLC59731_T_CYCLE_0 0x04 /* us */
#define TLC59731_T_CYCLE_1 0x01 /* us */
#define TLC59731_T_H0 (4 * TLC59731_T_CYCLE_0)
#define TLC59731_T_H1 (8 * TLC59731_T_CYCLE_0)
/* Threshould levels */
#define TLC59731_HIGH 0x01
#define TLC59731_LOW 0x00
/* Write command */
#define TLC59731_WR 0x3A
struct tlc59731_cfg {
struct gpio_dt_spec sdi_gpio;
size_t length;
};
static inline int rgb_pulse(const struct gpio_dt_spec *led_dev)
{
int fret = 0;
fret = gpio_pin_set_dt(led_dev, TLC59731_HIGH);
if (fret != 0) {
return fret;
}
fret = gpio_pin_set_dt(led_dev, TLC59731_LOW);
if (fret != 0) {
return fret;
}
return fret;
}
static int rgb_write_bit(const struct gpio_dt_spec *led_dev, uint8_t data)
{
rgb_pulse(led_dev);
k_busy_wait(TLC59731_DELAY);
if (data) {
rgb_pulse(led_dev);
k_busy_wait(TLC59731_T_CYCLE_1);
} else {
k_busy_wait(TLC59731_T_CYCLE_0);
}
return 0;
}
static int rgb_write_data(const struct gpio_dt_spec *led_dev, uint8_t data)
{
int8_t idx = 7;
while (idx >= 0) {
rgb_write_bit(led_dev, data & BIT((idx--)));
}
return 0;
}
static int tlc59731_led_set_color(const struct device *dev, struct led_rgb *pixel)
{
const struct tlc59731_cfg *tlc_conf = dev->config;
const struct gpio_dt_spec *led_gpio = &tlc_conf->sdi_gpio;
rgb_write_data(led_gpio, TLC59731_WR);
rgb_write_data(led_gpio, pixel->r);
rgb_write_data(led_gpio, pixel->g);
rgb_write_data(led_gpio, pixel->b);
return 0;
}
static int tlc59731_gpio_update_rgb(const struct device *dev, struct led_rgb *pixels,
size_t num_pixels)
{
size_t i;
int err = 0;
for (i = 0; i < num_pixels; i++) {
err = tlc59731_led_set_color(dev, &pixels[i]);
if (err) {
break;
}
}
return err;
}
static size_t tlc59731_length(const struct device *dev)
{
const struct tlc59731_cfg *config = dev->config;
return config->length;
}
static const struct led_strip_driver_api tlc59731_gpio_api = {
.update_rgb = tlc59731_gpio_update_rgb,
.length = tlc59731_length,
};
static int tlc59731_gpio_init(const struct device *dev)
{
const struct tlc59731_cfg *tlc_conf = dev->config;
const struct gpio_dt_spec *led = &tlc_conf->sdi_gpio;
int err = 0;
if (!device_is_ready(led->port)) {
LOG_ERR("%s: no LEDs found (DT child nodes missing)", dev->name);
err = -ENODEV;
goto scape;
}
err = gpio_pin_configure_dt(led, GPIO_OUTPUT_ACTIVE);
if (err < 0) {
LOG_ERR("%s: Unable to setup SDI port", dev->name);
err = -EIO;
goto scape;
}
err = gpio_pin_set_dt(led, TLC59731_LOW);
if (err < 0) {
LOG_ERR("%s: Unable to set the SDI-GPIO)", dev->name);
err = -EIO;
goto scape;
}
gpio_pin_set_dt(led, TLC59731_HIGH);
gpio_pin_set_dt(led, TLC59731_LOW);
k_busy_wait((TLC59731_DELAY + TLC59731_T_CYCLE_0));
scape:
return err;
}
#define TLC59731_DEVICE(i) \
static struct tlc59731_cfg tlc59731_cfg_##i = { \
.sdi_gpio = GPIO_DT_SPEC_INST_GET(i, gpios), \
.length = DT_INST_PROP(i, chain_length), \
}; \
\
DEVICE_DT_INST_DEFINE(i, tlc59731_gpio_init, NULL, NULL, &tlc59731_cfg_##i, POST_KERNEL, \
CONFIG_LED_STRIP_INIT_PRIORITY, &tlc59731_gpio_api);
DT_INST_FOREACH_STATUS_OKAY(TLC59731_DEVICE)