/* * Copyright (c) 2024 Javad Rahimipetroudi * * 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 #include #include #include #include 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 DEVICE_API(led_strip, 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)