1 /*
2 * Copyright (c) 2024 Arduino SA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT issi_is31fl3194
8
9 /**
10 * @file
11 * @brief IS31FL3194 LED driver
12 *
13 * The IS31FL3194 is a 3-channel LED driver that communicates over I2C.
14 */
15
16 #include <zephyr/device.h>
17 #include <zephyr/drivers/i2c.h>
18 #include <zephyr/drivers/led.h>
19 #include <zephyr/kernel.h>
20 #include <zephyr/logging/log.h>
21
22 #include <zephyr/dt-bindings/led/led.h>
23
24 LOG_MODULE_REGISTER(is31fl3194, CONFIG_LED_LOG_LEVEL);
25
26 #define IS31FL3194_PROD_ID_REG 0x00
27 #define IS31FL3194_CONF_REG 0x01
28 #define IS31FL3194_CURRENT_REG 0x03
29 #define IS31FL3194_OUT1_REG 0x10
30 #define IS31FL3194_OUT2_REG 0x21
31 #define IS31FL3194_OUT3_REG 0x32
32 #define IS31FL3194_UPDATE_REG 0x40
33
34 #define IS31FL3194_PROD_ID_VAL 0xce
35 #define IS31FL3194_CONF_ENABLE 0x01
36 #define IS31FL3194_UPDATE_VAL 0xc5
37
38 #define IS31FL3194_CHANNEL_COUNT 3
39
40 static const uint8_t led_channels[] = {
41 IS31FL3194_OUT1_REG,
42 IS31FL3194_OUT2_REG,
43 IS31FL3194_OUT3_REG
44 };
45
46 struct is31fl3194_config {
47 struct i2c_dt_spec bus;
48 uint8_t num_leds;
49 const struct led_info *led_infos;
50 const uint8_t *current_limits;
51 };
52
is31fl3194_led_to_info(const struct is31fl3194_config * config,uint32_t led)53 static const struct led_info *is31fl3194_led_to_info(const struct is31fl3194_config *config,
54 uint32_t led)
55 {
56 if (led < config->num_leds) {
57 return &config->led_infos[led];
58 }
59
60 return NULL;
61 }
62
is31fl3194_get_info(const struct device * dev,uint32_t led,const struct led_info ** info_out)63 static int is31fl3194_get_info(const struct device *dev,
64 uint32_t led,
65 const struct led_info **info_out)
66 {
67 const struct is31fl3194_config *config = dev->config;
68 const struct led_info *info = is31fl3194_led_to_info(config, led);
69
70 if (info == NULL) {
71 return -EINVAL;
72 }
73
74 *info_out = info;
75 return 0;
76 }
77
is31fl3194_set_color(const struct device * dev,uint32_t led,uint8_t num_colors,const uint8_t * color)78 static int is31fl3194_set_color(const struct device *dev, uint32_t led, uint8_t num_colors,
79 const uint8_t *color)
80 {
81 const struct is31fl3194_config *config = dev->config;
82 const struct led_info *info = is31fl3194_led_to_info(config, led);
83 int ret;
84
85 if (info == NULL) {
86 return -ENODEV;
87 }
88
89 if (info->num_colors != 3) {
90 return -ENOTSUP;
91 }
92
93 if (num_colors != 3) {
94 return -EINVAL;
95 }
96
97 for (int i = 0; i < 3; i++) {
98 uint8_t value;
99
100 switch (info->color_mapping[i]) {
101 case LED_COLOR_ID_RED:
102 value = color[0];
103 break;
104 case LED_COLOR_ID_GREEN:
105 value = color[1];
106 break;
107 case LED_COLOR_ID_BLUE:
108 value = color[2];
109 break;
110 default:
111 /* unreachable: mapping already tested in is31fl3194_check_config */
112 return -EINVAL;
113 }
114
115 ret = i2c_reg_write_byte_dt(&config->bus, led_channels[i], value);
116 if (ret != 0) {
117 break;
118 }
119 }
120
121 if (ret == 0) {
122 ret = i2c_reg_write_byte_dt(&config->bus,
123 IS31FL3194_UPDATE_REG,
124 IS31FL3194_UPDATE_VAL);
125 }
126
127 if (ret != 0) {
128 LOG_ERR("%s: LED write failed: %d", dev->name, ret);
129 }
130
131 return ret;
132 }
133
is31fl3194_set_brightness(const struct device * dev,uint32_t led,uint8_t value)134 static int is31fl3194_set_brightness(const struct device *dev, uint32_t led, uint8_t value)
135 {
136 const struct is31fl3194_config *config = dev->config;
137 const struct led_info *info = is31fl3194_led_to_info(config, led);
138 int ret = 0;
139
140 if (info == NULL) {
141 return -ENODEV;
142 }
143
144 if (info->num_colors != 1) {
145 return -ENOTSUP;
146 }
147
148 if (value > 100) {
149 return -EINVAL;
150 }
151
152 /* Rescale 0..100 to 0..255 */
153 value = value * 255 / 100;
154
155 ret = i2c_reg_write_byte_dt(&config->bus, led_channels[led], value);
156 if (ret == 0) {
157 ret = i2c_reg_write_byte_dt(&config->bus,
158 IS31FL3194_UPDATE_REG,
159 IS31FL3194_UPDATE_VAL);
160 }
161
162 if (ret != 0) {
163 LOG_ERR("%s: LED write failed", dev->name);
164 }
165
166 return ret;
167 }
168
is31fl3194_led_on(const struct device * dev,uint32_t led)169 static inline int is31fl3194_led_on(const struct device *dev, uint32_t led)
170 {
171 return is31fl3194_set_brightness(dev, led, 100);
172 }
173
is31fl3194_led_off(const struct device * dev,uint32_t led)174 static inline int is31fl3194_led_off(const struct device *dev, uint32_t led)
175 {
176 return is31fl3194_set_brightness(dev, led, 0);
177 }
178
179 /*
180 * Counts red, green, blue channels; returns true if color_id is valid
181 * and no more than one channel maps to the same color
182 */
is31fl3194_count_colors(const struct device * dev,uint8_t color_id,uint8_t * rgb_counts)183 static bool is31fl3194_count_colors(const struct device *dev,
184 uint8_t color_id, uint8_t *rgb_counts)
185 {
186 bool ret = false;
187
188 switch (color_id) {
189 case LED_COLOR_ID_RED:
190 ret = (++rgb_counts[0] == 1);
191 break;
192 case LED_COLOR_ID_GREEN:
193 ret = (++rgb_counts[1] == 1);
194 break;
195 case LED_COLOR_ID_BLUE:
196 ret = (++rgb_counts[2] == 1);
197 break;
198 }
199
200 if (!ret) {
201 LOG_ERR("%s: invalid color %d (duplicate or not RGB)",
202 dev->name, color_id);
203 }
204
205 return ret;
206 }
207
is31fl3194_check_config(const struct device * dev)208 static int is31fl3194_check_config(const struct device *dev)
209 {
210 const struct is31fl3194_config *config = dev->config;
211 const struct led_info *info;
212 uint8_t rgb_counts[3] = { 0 };
213 uint8_t i;
214
215 switch (config->num_leds) {
216 case 1:
217 /* check that it is a three-channel LED */
218 info = &config->led_infos[0];
219
220 if (info->num_colors != 3) {
221 LOG_ERR("%s: invalid number of colors %d "
222 "(must be 3 for RGB LED)",
223 dev->name, info->num_colors);
224 return -EINVAL;
225 }
226
227 for (i = 0; i < 3; i++) {
228 if (!is31fl3194_count_colors(dev, info->color_mapping[i], rgb_counts)) {
229 return -EINVAL;
230 }
231
232 }
233 break;
234 case 3:
235 /* check that each LED is single-color */
236 for (i = 0; i < 3; i++) {
237 info = &config->led_infos[i];
238
239 if (info->num_colors != 1) {
240 LOG_ERR("%s: invalid number of colors %d "
241 "(must be 1 when defining multiple LEDs)",
242 dev->name, info->num_colors);
243 return -EINVAL;
244 }
245
246 if (!is31fl3194_count_colors(dev, info->color_mapping[0], rgb_counts)) {
247 return -EINVAL;
248 }
249 }
250 break;
251 default:
252 LOG_ERR("%s: invalid number of LEDs %d (must be 1 or 3)",
253 dev->name, config->num_leds);
254 return -EINVAL;
255 }
256
257 return 0;
258 }
259
is31fl3194_init(const struct device * dev)260 static int is31fl3194_init(const struct device *dev)
261 {
262 const struct is31fl3194_config *config = dev->config;
263 const struct led_info *info = NULL;
264 int i, ret;
265 uint8_t prod_id, band;
266 uint8_t current_reg = 0;
267
268 ret = is31fl3194_check_config(dev);
269 if (ret != 0) {
270 return ret;
271 }
272
273 if (!i2c_is_ready_dt(&config->bus)) {
274 LOG_ERR("%s: I2C device not ready", dev->name);
275 return -ENODEV;
276 }
277
278 ret = i2c_reg_read_byte_dt(&config->bus, IS31FL3194_PROD_ID_REG, &prod_id);
279 if (ret != 0) {
280 LOG_ERR("%s: failed to read product ID", dev->name);
281 return ret;
282 }
283
284 if (prod_id != IS31FL3194_PROD_ID_VAL) {
285 LOG_ERR("%s: invalid product ID 0x%02x (expected 0x%02x)", dev->name, prod_id,
286 IS31FL3194_PROD_ID_VAL);
287 return -ENODEV;
288 }
289
290 /* calc current limit register value */
291 info = &config->led_infos[0];
292 if (info->num_colors == IS31FL3194_CHANNEL_COUNT) {
293 /* one RGB LED: set all channels to the same current limit */
294 band = (config->current_limits[0] / 10) - 1;
295 for (i = 0; i < IS31FL3194_CHANNEL_COUNT; i++) {
296 current_reg |= band << (2 * i);
297 }
298 } else {
299 /* single-channel LEDs: independent limits */
300 for (i = 0; i < config->num_leds; i++) {
301 band = (config->current_limits[i] / 10) - 1;
302 current_reg |= band << (2 * i);
303 }
304 }
305
306 ret = i2c_reg_write_byte_dt(&config->bus, IS31FL3194_CURRENT_REG, current_reg);
307 if (ret != 0) {
308 LOG_ERR("%s: failed to set current limit", dev->name);
309 return ret;
310 }
311
312 /* enable device */
313 return i2c_reg_write_byte_dt(&config->bus, IS31FL3194_CONF_REG, IS31FL3194_CONF_ENABLE);
314 }
315
316 static DEVICE_API(led, is31fl3194_led_api) = {
317 .set_brightness = is31fl3194_set_brightness,
318 .on = is31fl3194_led_on,
319 .off = is31fl3194_led_off,
320 .get_info = is31fl3194_get_info,
321 .set_color = is31fl3194_set_color,
322 };
323
324 #define COLOR_MAPPING(led_node_id) \
325 static const uint8_t color_mapping_##led_node_id[] = \
326 DT_PROP(led_node_id, color_mapping);
327
328 #define LED_INFO(led_node_id) \
329 { \
330 .label = DT_PROP(led_node_id, label), \
331 .num_colors = DT_PROP_LEN(led_node_id, color_mapping), \
332 .color_mapping = color_mapping_##led_node_id, \
333 },
334
335 #define LED_CURRENT(led_node_id) \
336 DT_PROP(led_node_id, current_limit),
337
338 #define IS31FL3194_DEFINE(id) \
339 \
340 DT_INST_FOREACH_CHILD(id, COLOR_MAPPING) \
341 \
342 static const struct led_info is31fl3194_leds_##id[] = \
343 { DT_INST_FOREACH_CHILD(id, LED_INFO) }; \
344 static const uint8_t is31fl3194_currents_##id[] = \
345 { DT_INST_FOREACH_CHILD(id, LED_CURRENT) }; \
346 BUILD_ASSERT(ARRAY_SIZE(is31fl3194_leds_##id) > 0, \
347 "No LEDs defined for " #id); \
348 \
349 static const struct is31fl3194_config is31fl3194_config_##id = { \
350 .bus = I2C_DT_SPEC_INST_GET(id), \
351 .num_leds = ARRAY_SIZE(is31fl3194_leds_##id), \
352 .led_infos = is31fl3194_leds_##id, \
353 .current_limits = is31fl3194_currents_##id, \
354 }; \
355 DEVICE_DT_INST_DEFINE(id, &is31fl3194_init, NULL, NULL, \
356 &is31fl3194_config_##id, POST_KERNEL, \
357 CONFIG_LED_INIT_PRIORITY, &is31fl3194_led_api);
358
359 DT_INST_FOREACH_STATUS_OKAY(IS31FL3194_DEFINE)
360