1 /*
2  * Copyright (c) 2023 NXP Semiconductors
3  * Copyright (c) 2023 Cognipilot Foundation
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #define DT_DRV_COMPAT onnn_ncp5623
9 
10 /**
11  * @file
12  * @brief NCP5623 LED driver
13  *
14  * The NCP5623 is a 3-channel LED driver that communicates over I2C.
15  */
16 
17 #include <zephyr/device.h>
18 #include <zephyr/drivers/i2c.h>
19 #include <zephyr/drivers/led.h>
20 #include <zephyr/kernel.h>
21 #include <zephyr/logging/log.h>
22 
23 LOG_MODULE_REGISTER(ncp5623, CONFIG_LED_LOG_LEVEL);
24 
25 #define NCP5623_LED_CURRENT 0x20
26 #define NCP5623_LED_PWM0    0x40
27 #define NCP5623_LED_PWM1    0x60
28 #define NCP5623_LED_PWM2    0x80
29 
30 #define NCP5623_CHANNEL_COUNT 3
31 
32 /* Brightness limits */
33 #define NCP5623_MIN_BRIGHTNESS 0
34 #define NCP5623_MAX_BRIGHTNESS 0x1f
35 
36 static const uint8_t led_channels[] = {NCP5623_LED_PWM0, NCP5623_LED_PWM1, NCP5623_LED_PWM2};
37 
38 struct ncp5623_config {
39 	struct i2c_dt_spec bus;
40 	uint8_t num_leds;
41 	const struct led_info *leds_info;
42 };
43 
ncp5623_led_to_info(const struct ncp5623_config * config,uint32_t led)44 static const struct led_info *ncp5623_led_to_info(const struct ncp5623_config *config, uint32_t led)
45 {
46 	if (led < config->num_leds) {
47 		return &config->leds_info[led];
48 	}
49 
50 	return NULL;
51 }
52 
ncp5623_get_info(const struct device * dev,uint32_t led,const struct led_info ** info)53 static int ncp5623_get_info(const struct device *dev, uint32_t led, const struct led_info **info)
54 {
55 	const struct ncp5623_config *config = dev->config;
56 	const struct led_info *led_info = ncp5623_led_to_info(config, led);
57 
58 	if (!led_info) {
59 		return -EINVAL;
60 	}
61 
62 	*info = led_info;
63 
64 	return 0;
65 }
66 
ncp5623_set_color(const struct device * dev,uint32_t led,uint8_t num_colors,const uint8_t * color)67 static int ncp5623_set_color(const struct device *dev, uint32_t led, uint8_t num_colors,
68 			     const uint8_t *color)
69 {
70 	const struct ncp5623_config *config = dev->config;
71 	const struct led_info *led_info = ncp5623_led_to_info(config, led);
72 	uint8_t buf[6] = {0x70, NCP5623_LED_PWM0, 0x70, NCP5623_LED_PWM1, 0x70, NCP5623_LED_PWM2};
73 
74 	if (!led_info) {
75 		return -ENODEV;
76 	}
77 
78 	if (led_info->num_colors != 3) {
79 		return -ENOTSUP;
80 	}
81 	if (num_colors != 3) {
82 		return -EINVAL;
83 	}
84 
85 	buf[1] = buf[1] | color[0] / 8;
86 	buf[3] = buf[3] | color[1] / 8;
87 	buf[5] = buf[5] | color[2] / 8;
88 
89 	return i2c_burst_write_dt(&config->bus, NCP5623_LED_CURRENT | NCP5623_MAX_BRIGHTNESS, buf,
90 				  sizeof(buf));
91 }
92 
ncp5623_set_brightness(const struct device * dev,uint32_t led,uint8_t value)93 static int ncp5623_set_brightness(const struct device *dev, uint32_t led, uint8_t value)
94 {
95 	const struct ncp5623_config *config = dev->config;
96 	const struct led_info *led_info = ncp5623_led_to_info(config, led);
97 	int ret = 0;
98 
99 	if (!led_info) {
100 		return -ENODEV;
101 	}
102 
103 	if (value > 100) {
104 		return -EINVAL;
105 	}
106 
107 	if (led_info->num_colors != 1) {
108 		return -ENOTSUP;
109 	}
110 
111 	/* Rescale 0..100 to 0..31 */
112 	value = value * NCP5623_MAX_BRIGHTNESS / 100;
113 
114 	ret = i2c_reg_write_byte_dt(&config->bus, led_channels[led] | value, 0x70);
115 
116 	if (ret < 0) {
117 		LOG_ERR("%s: LED write failed", dev->name);
118 	}
119 
120 	return ret;
121 }
122 
ncp5623_led_on(const struct device * dev,uint32_t led)123 static inline int ncp5623_led_on(const struct device *dev, uint32_t led)
124 {
125 	return ncp5623_set_brightness(dev, led, 100);
126 }
127 
ncp5623_led_off(const struct device * dev,uint32_t led)128 static inline int ncp5623_led_off(const struct device *dev, uint32_t led)
129 {
130 	return ncp5623_set_brightness(dev, led, 0);
131 }
132 
ncp5623_led_init(const struct device * dev)133 static int ncp5623_led_init(const struct device *dev)
134 {
135 	const struct ncp5623_config *config = dev->config;
136 	const struct led_info *led_info = NULL;
137 	int i;
138 	uint8_t buf[6] = {0x70, NCP5623_LED_PWM0, 0x70, NCP5623_LED_PWM1, 0x70, NCP5623_LED_PWM2};
139 
140 	if (!i2c_is_ready_dt(&config->bus)) {
141 		LOG_ERR("%s: I2C device not ready", dev->name);
142 		return -ENODEV;
143 	}
144 
145 	if (config->num_leds == 1) { /* one three-channel (RGB) LED */
146 		led_info = ncp5623_led_to_info(config, 0);
147 
148 		if (!led_info) {
149 			return -ENODEV;
150 		}
151 
152 		if (led_info->num_colors != NCP5623_CHANNEL_COUNT) {
153 			LOG_ERR("%s: invalid number of colors %d (must be %d with a single LED)",
154 				dev->name, led_info->num_colors, NCP5623_CHANNEL_COUNT);
155 			return -EINVAL;
156 		}
157 	} else if (config->num_leds <= 3) { /* three single-channel LEDs */
158 		for (i = 0; i < config->num_leds; i++) {
159 			led_info = ncp5623_led_to_info(config, i);
160 
161 			if (!led_info) {
162 				return -ENODEV;
163 			}
164 
165 			if (led_info->num_colors > 1) {
166 				LOG_ERR("%s: invalid number of colors %d (must be 1 when defining "
167 					"multiple leds)",
168 					dev->name, led_info->num_colors);
169 				return -EINVAL;
170 			}
171 		}
172 	} else {
173 		LOG_ERR("%s: invalid number of leds %d (max %d)", dev->name, config->num_leds,
174 			NCP5623_CHANNEL_COUNT);
175 		return -EINVAL;
176 	}
177 
178 	if (i2c_burst_write_dt(&config->bus, NCP5623_LED_CURRENT | NCP5623_MAX_BRIGHTNESS, buf,
179 			       6)) {
180 		LOG_ERR("%s: LED write failed", dev->name);
181 		return -EIO;
182 	}
183 
184 	return 0;
185 }
186 
187 static DEVICE_API(led, ncp5623_led_api) = {
188 	.set_brightness = ncp5623_set_brightness,
189 	.on = ncp5623_led_on,
190 	.off = ncp5623_led_off,
191 	.get_info = ncp5623_get_info,
192 	.set_color = ncp5623_set_color,
193 };
194 
195 #define COLOR_MAPPING(led_node_id)                                                                 \
196 	static const uint8_t color_mapping_##led_node_id[] = DT_PROP(led_node_id, color_mapping);
197 
198 #define LED_INFO(led_node_id)                                                                      \
199 	{                                                                                          \
200 		.label = DT_PROP(led_node_id, label),                                              \
201 		.index = DT_PROP(led_node_id, index),                                              \
202 		.num_colors = DT_PROP_LEN(led_node_id, color_mapping),                             \
203 		.color_mapping = color_mapping_##led_node_id,                                      \
204 	},
205 
206 #define NCP5623_DEFINE(id)                                                                         \
207                                                                                                    \
208 	DT_INST_FOREACH_CHILD(id, COLOR_MAPPING)                                                   \
209                                                                                                    \
210 	static const struct led_info ncp5623_leds_##id[] = {DT_INST_FOREACH_CHILD(id, LED_INFO)};  \
211                                                                                                    \
212 	static const struct ncp5623_config ncp5623_config_##id = {                                 \
213 		.bus = I2C_DT_SPEC_INST_GET(id),                                                   \
214 		.num_leds = ARRAY_SIZE(ncp5623_leds_##id),                                         \
215 		.leds_info = ncp5623_leds_##id,                                                    \
216 	};                                                                                         \
217 	DEVICE_DT_INST_DEFINE(id, &ncp5623_led_init, NULL, NULL, &ncp5623_config_##id,             \
218 			      POST_KERNEL, CONFIG_LED_INIT_PRIORITY, &ncp5623_led_api);
219 
220 DT_INST_FOREACH_STATUS_OKAY(NCP5623_DEFINE)
221