1 /*
2  * Copyright (c) 2024 Javad Rahimipetroudi <javad.rahimipetroudi@mind.be>
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT ti_tlc59731
8 
9 /**
10  * @file
11  * @brief LED driver for the TLC59731 LED driver.
12  *
13  * TLC59731 is a 3-Channel, 8-Bit, PWM LED Driver
14  * With Single-Wire Interface (EasySet)
15  *
16  * The EasySet protocol is based on short pulses and the time between
17  * them. At least one pulse must be sent every T_CYCLE, which can be
18  * between 1.67us and 50us. We want to go as fast as possible, but
19  * delays under 1us don't work very well, so we settle on 5us for the
20  * cycle time.
21  * A pulse must be high for at least 14ns. In practice, turning a GPIO on
22  * and immediately off again already takes longer than that, so no delay
23  * is needed there.
24  * A zero is represented by no additional pulses within a cycle.
25  * A one is represented by an additional pulse between 275ns and 2.5us
26  * (half a cycle) after the first one. We need at least some delay to get to
27  * 275ns, but because of the limited granularity of k_busy_wait we use a
28  * full 1us. After the pulse, we wait an additional T_CYCLE_1 to complete
29  * the cycle. This time can be slightly shorter because the second pulse
30  * already closes the cycle.
31  * Finally we need to keep the line low for T_H0 to complete the address
32  * for a single chip, and T_H1 to complete the write for all chips.
33  */
34 
35 #include <zephyr/drivers/led_strip.h>
36 #include <zephyr/drivers/gpio.h>
37 #include <zephyr/device.h>
38 #include <zephyr/kernel.h>
39 
40 #include <zephyr/logging/log.h>
41 LOG_MODULE_REGISTER(tlc59731, CONFIG_LED_STRIP_LOG_LEVEL);
42 
43 /* Pulse timing */
44 #define TLC59731_DELAY     0x01 /* us */
45 #define TLC59731_T_CYCLE_0 0x04 /* us */
46 #define TLC59731_T_CYCLE_1 0x01 /* us */
47 #define TLC59731_T_H0      (4 * TLC59731_T_CYCLE_0)
48 #define TLC59731_T_H1      (8 * TLC59731_T_CYCLE_0)
49 /* Threshould levels */
50 #define TLC59731_HIGH      0x01
51 #define TLC59731_LOW       0x00
52 
53 /* Write command */
54 #define TLC59731_WR 0x3A
55 
56 struct tlc59731_cfg {
57 	struct gpio_dt_spec sdi_gpio;
58 	size_t length;
59 };
60 
rgb_pulse(const struct gpio_dt_spec * led_dev)61 static inline int rgb_pulse(const struct gpio_dt_spec *led_dev)
62 {
63 	int fret = 0;
64 
65 	fret = gpio_pin_set_dt(led_dev, TLC59731_HIGH);
66 	if (fret != 0) {
67 		return fret;
68 	}
69 
70 	fret = gpio_pin_set_dt(led_dev, TLC59731_LOW);
71 	if (fret != 0) {
72 		return fret;
73 	}
74 
75 	return fret;
76 }
77 
rgb_write_bit(const struct gpio_dt_spec * led_dev,uint8_t data)78 static int rgb_write_bit(const struct gpio_dt_spec *led_dev, uint8_t data)
79 {
80 	rgb_pulse(led_dev);
81 
82 	k_busy_wait(TLC59731_DELAY);
83 
84 	if (data) {
85 		rgb_pulse(led_dev);
86 		k_busy_wait(TLC59731_T_CYCLE_1);
87 	} else {
88 		k_busy_wait(TLC59731_T_CYCLE_0);
89 	}
90 
91 	return 0;
92 }
93 
rgb_write_data(const struct gpio_dt_spec * led_dev,uint8_t data)94 static int rgb_write_data(const struct gpio_dt_spec *led_dev, uint8_t data)
95 {
96 	int8_t idx = 7;
97 
98 	while (idx >= 0) {
99 		rgb_write_bit(led_dev, data & BIT((idx--)));
100 	}
101 
102 	return 0;
103 }
104 
tlc59731_led_set_color(const struct device * dev,struct led_rgb * pixel)105 static int tlc59731_led_set_color(const struct device *dev, struct led_rgb *pixel)
106 {
107 
108 	const struct tlc59731_cfg *tlc_conf = dev->config;
109 	const struct gpio_dt_spec *led_gpio = &tlc_conf->sdi_gpio;
110 
111 	rgb_write_data(led_gpio, TLC59731_WR);
112 	rgb_write_data(led_gpio, pixel->r);
113 	rgb_write_data(led_gpio, pixel->g);
114 	rgb_write_data(led_gpio, pixel->b);
115 
116 	return 0;
117 }
118 
tlc59731_gpio_update_rgb(const struct device * dev,struct led_rgb * pixels,size_t num_pixels)119 static int tlc59731_gpio_update_rgb(const struct device *dev, struct led_rgb *pixels,
120 				    size_t num_pixels)
121 {
122 	size_t i;
123 	int err = 0;
124 
125 	for (i = 0; i < num_pixels; i++) {
126 		err = tlc59731_led_set_color(dev, &pixels[i]);
127 		if (err) {
128 			break;
129 		}
130 	}
131 
132 	return err;
133 }
134 
tlc59731_length(const struct device * dev)135 static size_t tlc59731_length(const struct device *dev)
136 {
137 	const struct tlc59731_cfg *config = dev->config;
138 
139 	return config->length;
140 }
141 
142 static DEVICE_API(led_strip, tlc59731_gpio_api) = {
143 	.update_rgb = tlc59731_gpio_update_rgb,
144 	.length = tlc59731_length,
145 };
146 
tlc59731_gpio_init(const struct device * dev)147 static int tlc59731_gpio_init(const struct device *dev)
148 {
149 	const struct tlc59731_cfg *tlc_conf = dev->config;
150 	const struct gpio_dt_spec *led = &tlc_conf->sdi_gpio;
151 	int err = 0;
152 
153 	if (!device_is_ready(led->port)) {
154 		LOG_ERR("%s: no LEDs found (DT child nodes missing)", dev->name);
155 		err = -ENODEV;
156 		goto scape;
157 	}
158 
159 	err = gpio_pin_configure_dt(led, GPIO_OUTPUT_ACTIVE);
160 	if (err < 0) {
161 		LOG_ERR("%s: Unable to setup SDI port", dev->name);
162 		err = -EIO;
163 		goto scape;
164 	}
165 
166 	err = gpio_pin_set_dt(led, TLC59731_LOW);
167 	if (err < 0) {
168 		LOG_ERR("%s: Unable to set the SDI-GPIO)", dev->name);
169 		err = -EIO;
170 		goto scape;
171 	}
172 
173 	gpio_pin_set_dt(led, TLC59731_HIGH);
174 	gpio_pin_set_dt(led, TLC59731_LOW);
175 
176 	k_busy_wait((TLC59731_DELAY + TLC59731_T_CYCLE_0));
177 scape:
178 	return err;
179 }
180 
181 #define TLC59731_DEVICE(i)                                                                         \
182 	static struct tlc59731_cfg tlc59731_cfg_##i = {                                            \
183 		.sdi_gpio = GPIO_DT_SPEC_INST_GET(i, gpios),                                       \
184 		.length = DT_INST_PROP(i, chain_length),                                           \
185 	};                                                                                         \
186                                                                                                    \
187 	DEVICE_DT_INST_DEFINE(i, tlc59731_gpio_init, NULL, NULL, &tlc59731_cfg_##i, POST_KERNEL,   \
188 			      CONFIG_LED_STRIP_INIT_PRIORITY, &tlc59731_gpio_api);
189 DT_INST_FOREACH_STATUS_OKAY(TLC59731_DEVICE)
190