1 /*
2  * Copyright (c) 2018 Linaro Ltd.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT ti_lp3943
8 
9 /**
10  * @file
11  * @brief LP3943 LED driver
12  *
13  * Limitations:
14  * - Blink period and brightness value are controlled by two sets of PSCx/PWMx
15  *   registers. This driver partitions the available LEDs into two groups as
16  *   0 to 7 and 8 to 15 and assigns PSC0/PWM0 to LEDs from 0 to 7 and PSC1/PWM1
17  *   to LEDs from 8 to 15. So, it is not possible to set unique blink period
18  *   and brightness value for LEDs in a group, changing either of these
19  *   values for a LED will affect other LEDs also.
20  */
21 
22 #include <zephyr/drivers/i2c.h>
23 #include <zephyr/drivers/led.h>
24 #include <zephyr/sys/util.h>
25 #include <zephyr/kernel.h>
26 
27 #define LOG_LEVEL CONFIG_LED_LOG_LEVEL
28 #include <zephyr/logging/log.h>
29 LOG_MODULE_REGISTER(lp3943);
30 
31 #include "led_context.h"
32 
33 /* LP3943 Registers */
34 #define LP3943_INPUT_1 0x00
35 #define LP3943_INPUT_2 0x01
36 #define LP3943_PSC0 0x02
37 #define LP3943_PWM0 0x03
38 #define LP3943_PSC1 0x04
39 #define LP3943_PWM1 0x05
40 #define LP3943_LS0 0x06
41 #define LP3943_LS1 0x07
42 #define LP3943_LS2 0x08
43 #define LP3943_LS3 0x09
44 
45 #define LP3943_MASK 0x03
46 
47 enum lp3943_modes {
48 	LP3943_OFF,
49 	LP3943_ON,
50 	LP3943_DIM0,
51 	LP3943_DIM1,
52 };
53 
54 struct lp3943_config {
55 	struct i2c_dt_spec bus;
56 };
57 
58 struct lp3943_data {
59 	struct led_data dev_data;
60 };
61 
lp3943_get_led_reg(uint32_t * led,uint8_t * reg)62 static int lp3943_get_led_reg(uint32_t *led, uint8_t *reg)
63 {
64 	switch (*led) {
65 	case 0:
66 	case 1:
67 	case 2:
68 		__fallthrough;
69 	case 3:
70 		*reg = LP3943_LS0;
71 		break;
72 	case 4:
73 	case 5:
74 	case 6:
75 		__fallthrough;
76 	case 7:
77 		*reg = LP3943_LS1;
78 		*led -= 4U;
79 		break;
80 	case 8:
81 	case 9:
82 	case 10:
83 		__fallthrough;
84 	case 11:
85 		*reg = LP3943_LS2;
86 		*led -= 8U;
87 		break;
88 	case 12:
89 	case 13:
90 	case 14:
91 		__fallthrough;
92 	case 15:
93 		*reg = LP3943_LS3;
94 		*led -= 12U;
95 		break;
96 	default:
97 		LOG_ERR("Invalid LED specified");
98 		return -EINVAL;
99 	}
100 
101 	return 0;
102 }
103 
lp3943_set_dim_states(const struct lp3943_config * config,uint32_t led,uint8_t mode)104 static int lp3943_set_dim_states(const struct lp3943_config *config,
105 				 uint32_t led, uint8_t mode)
106 {
107 	int ret;
108 	uint8_t reg;
109 
110 	ret = lp3943_get_led_reg(&led, &reg);
111 	if (ret) {
112 		return ret;
113 	}
114 
115 	/* Set DIMx states for the LEDs */
116 	if (i2c_reg_update_byte_dt(&config->bus, reg, LP3943_MASK << (led << 1),
117 				   mode << (led << 1))) {
118 		LOG_ERR("LED reg update failed");
119 		return -EIO;
120 	}
121 
122 	return 0;
123 }
124 
lp3943_led_blink(const struct device * dev,uint32_t led,uint32_t delay_on,uint32_t delay_off)125 static int lp3943_led_blink(const struct device *dev, uint32_t led,
126 			    uint32_t delay_on, uint32_t delay_off)
127 {
128 	const struct lp3943_config *config = dev->config;
129 	struct lp3943_data *data = dev->data;
130 	struct led_data *dev_data = &data->dev_data;
131 	int ret;
132 	uint16_t period;
133 	uint8_t reg, val, mode;
134 
135 	period = delay_on + delay_off;
136 
137 	if (period < dev_data->min_period || period > dev_data->max_period) {
138 		return -EINVAL;
139 	}
140 
141 	/* Use DIM0 for LEDs 0 to 7 and DIM1 for LEDs 8 to 15 */
142 	if (led < 8) {
143 		mode = LP3943_DIM0;
144 	} else {
145 		mode = LP3943_DIM1;
146 	}
147 
148 	if (mode == LP3943_DIM0) {
149 		reg = LP3943_PSC0;
150 	} else {
151 		reg = LP3943_PSC1;
152 	}
153 
154 	val = (period * 255U) / dev_data->max_period;
155 	if (i2c_reg_write_byte_dt(&config->bus, reg, val)) {
156 		LOG_ERR("LED write failed");
157 		return -EIO;
158 	}
159 
160 	ret = lp3943_set_dim_states(config, led, mode);
161 	if (ret) {
162 		return ret;
163 	}
164 
165 	return 0;
166 }
167 
lp3943_led_set_brightness(const struct device * dev,uint32_t led,uint8_t value)168 static int lp3943_led_set_brightness(const struct device *dev, uint32_t led,
169 				     uint8_t value)
170 {
171 	const struct lp3943_config *config = dev->config;
172 	struct lp3943_data *data = dev->data;
173 	struct led_data *dev_data = &data->dev_data;
174 	int ret;
175 	uint8_t reg, val, mode;
176 
177 	if (value < dev_data->min_brightness ||
178 			value > dev_data->max_brightness) {
179 		return -EINVAL;
180 	}
181 
182 	/* Use DIM0 for LEDs 0 to 7 and DIM1 for LEDs 8 to 15 */
183 	if (led < 8) {
184 		mode = LP3943_DIM0;
185 	} else {
186 		mode = LP3943_DIM1;
187 	}
188 
189 	if (mode == LP3943_DIM0) {
190 		reg = LP3943_PWM0;
191 	} else {
192 		reg = LP3943_PWM1;
193 	}
194 
195 	val = (value * 255U) / dev_data->max_brightness;
196 	if (i2c_reg_write_byte_dt(&config->bus, reg, val)) {
197 		LOG_ERR("LED write failed");
198 		return -EIO;
199 	}
200 
201 	ret = lp3943_set_dim_states(config, led, mode);
202 	if (ret) {
203 		return ret;
204 	}
205 
206 	return 0;
207 }
208 
lp3943_led_on(const struct device * dev,uint32_t led)209 static inline int lp3943_led_on(const struct device *dev, uint32_t led)
210 {
211 	const struct lp3943_config *config = dev->config;
212 	int ret;
213 	uint8_t reg, mode;
214 
215 	ret = lp3943_get_led_reg(&led, &reg);
216 	if (ret) {
217 		return ret;
218 	}
219 
220 	/* Set LED state to ON */
221 	mode = LP3943_ON;
222 	if (i2c_reg_update_byte_dt(&config->bus, reg, LP3943_MASK << (led << 1),
223 				   mode << (led << 1))) {
224 		LOG_ERR("LED reg update failed");
225 		return -EIO;
226 	}
227 
228 	return 0;
229 }
230 
lp3943_led_off(const struct device * dev,uint32_t led)231 static inline int lp3943_led_off(const struct device *dev, uint32_t led)
232 {
233 	const struct lp3943_config *config = dev->config;
234 	int ret;
235 	uint8_t reg;
236 
237 	ret = lp3943_get_led_reg(&led, &reg);
238 	if (ret) {
239 		return ret;
240 	}
241 
242 	/* Set LED state to OFF */
243 	if (i2c_reg_update_byte_dt(&config->bus, reg, LP3943_MASK << (led << 1),
244 				   0)) {
245 		LOG_ERR("LED reg update failed");
246 		return -EIO;
247 	}
248 
249 	return 0;
250 }
251 
lp3943_led_init(const struct device * dev)252 static int lp3943_led_init(const struct device *dev)
253 {
254 	const struct lp3943_config *config = dev->config;
255 	struct lp3943_data *data = dev->data;
256 	struct led_data *dev_data = &data->dev_data;
257 
258 	if (!device_is_ready(config->bus.bus)) {
259 		LOG_ERR("I2C device not ready");
260 		return -ENODEV;
261 	}
262 
263 	/* Hardware specific limits */
264 	dev_data->min_period = 0U;
265 	dev_data->max_period = 1600U;
266 	dev_data->min_brightness = 0U;
267 	dev_data->max_brightness = 100U;
268 
269 	return 0;
270 }
271 
272 static struct lp3943_data lp3943_led_data;
273 
274 static const struct lp3943_config lp3943_led_config = {
275 	.bus = I2C_DT_SPEC_INST_GET(0),
276 };
277 
278 static DEVICE_API(led, lp3943_led_api) = {
279 	.blink = lp3943_led_blink,
280 	.set_brightness = lp3943_led_set_brightness,
281 	.on = lp3943_led_on,
282 	.off = lp3943_led_off,
283 };
284 
285 DEVICE_DT_INST_DEFINE(0, &lp3943_led_init, NULL, &lp3943_led_data,
286 		      &lp3943_led_config, POST_KERNEL, CONFIG_LED_INIT_PRIORITY,
287 		      &lp3943_led_api);
288