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