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