1 /*
2  * Copyright (c) 2023 Phytec Messtechnik GmbH.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT ti_lp5569
8 
9 /**
10  * @file
11  * @brief LP5569 LED controller
12  *
13  * The LP5569 is a 9-channel LED driver that communicates over I2C.
14  */
15 
16 #include <zephyr/drivers/gpio.h>
17 #include <zephyr/drivers/i2c.h>
18 #include <zephyr/drivers/led.h>
19 #include <zephyr/device.h>
20 #include <zephyr/pm/device.h>
21 #include <zephyr/kernel.h>
22 
23 #include <zephyr/logging/log.h>
24 LOG_MODULE_REGISTER(lp5569, CONFIG_LED_LOG_LEVEL);
25 
26 #define LP5569_NUM_LEDS 9
27 
28 /* General Registers */
29 #define LP5569_CONFIG  0x00
30 #define LP5569_CHIP_EN BIT(6)
31 
32 #define LP5569_MISC          0x2F
33 #define LP5569_POWERSAVE_EN  BIT(5)
34 #define LP5569_EN_AUTO_INCR  BIT(6)
35 #define LP5569_CP_MODE_SHIFT 3
36 
37 /* PWM base Register for controlling the duty-cycle */
38 #define LP5569_LED0_PWM 0x16
39 
40 struct lp5569_config {
41 	struct i2c_dt_spec bus;
42 	struct gpio_dt_spec enable_gpio;
43 	const uint8_t cp_mode;
44 };
45 
lp5569_led_set_brightness(const struct device * dev,uint32_t led,uint8_t brightness)46 static int lp5569_led_set_brightness(const struct device *dev, uint32_t led, uint8_t brightness)
47 {
48 	const struct lp5569_config *config = dev->config;
49 	uint8_t val;
50 	int ret;
51 
52 	if (led >= LP5569_NUM_LEDS || brightness > 100) {
53 		return -EINVAL;
54 	}
55 
56 	/* Map 0-100 % to 0-255 pwm register value */
57 	val = brightness * 255 / 100;
58 
59 	ret = i2c_reg_write_byte_dt(&config->bus, LP5569_LED0_PWM + led, val);
60 	if (ret < 0) {
61 		LOG_ERR("LED reg update failed");
62 		return ret;
63 	}
64 
65 	return 0;
66 }
67 
lp5569_led_on(const struct device * dev,uint32_t led)68 static inline int lp5569_led_on(const struct device *dev, uint32_t led)
69 {
70 	/* Set LED brightness to 100 % */
71 	return lp5569_led_set_brightness(dev, led, 100);
72 }
73 
lp5569_led_off(const struct device * dev,uint32_t led)74 static inline int lp5569_led_off(const struct device *dev, uint32_t led)
75 {
76 	/* Set LED brightness to 0 % */
77 	return lp5569_led_set_brightness(dev, led, 0);
78 }
79 
lp5569_write_channels(const struct device * dev,uint32_t start_channel,uint32_t num_channels,const uint8_t * buf)80 static int lp5569_write_channels(const struct device *dev, uint32_t start_channel,
81 				 uint32_t num_channels, const uint8_t *buf)
82 {
83 	const struct lp5569_config *config = dev->config;
84 	uint32_t i2c_len = num_channels + 1;
85 	uint8_t i2c_msg[LP5569_NUM_LEDS + 1];
86 
87 	if ((uint64_t)start_channel + num_channels > LP5569_NUM_LEDS) {
88 		return -EINVAL;
89 	}
90 
91 	i2c_msg[0] = LP5569_LED0_PWM + start_channel;
92 	memcpy(&i2c_msg[1], buf, num_channels);
93 
94 	return i2c_write_dt(&config->bus, i2c_msg, i2c_len);
95 }
96 
lp5569_enable(const struct device * dev)97 static int lp5569_enable(const struct device *dev)
98 {
99 	const struct lp5569_config *config = dev->config;
100 	int ret;
101 
102 	if (!i2c_is_ready_dt(&config->bus)) {
103 		LOG_ERR("I2C device not ready");
104 		return -ENODEV;
105 	}
106 
107 	/* flip the enable pin if specified */
108 	if (config->enable_gpio.port) {
109 		if (!gpio_is_ready_dt(&config->enable_gpio)) {
110 			LOG_ERR("Enable GPIO not ready");
111 			return -ENODEV;
112 		}
113 
114 		ret = gpio_pin_configure_dt(&config->enable_gpio, GPIO_OUTPUT_ACTIVE);
115 		if (ret < 0) {
116 			LOG_ERR("Failed to configure enable_gpio, err: %d", ret);
117 			return ret;
118 		}
119 
120 		/* datasheet 7.9: t_en max 3 ms for chip initialization */
121 		k_msleep(3);
122 	}
123 
124 	ret = i2c_reg_write_byte_dt(&config->bus, LP5569_CONFIG, LP5569_CHIP_EN);
125 	if (ret < 0) {
126 		LOG_ERR("Enable LP5569 failed");
127 		return ret;
128 	}
129 
130 	ret = i2c_reg_write_byte_dt(&config->bus, LP5569_MISC,
131 				    LP5569_POWERSAVE_EN | LP5569_EN_AUTO_INCR |
132 					    (config->cp_mode << LP5569_CP_MODE_SHIFT));
133 	if (ret < 0) {
134 		LOG_ERR("LED reg update failed");
135 		return ret;
136 	}
137 
138 	return 0;
139 }
140 
lp5569_init(const struct device * dev)141 static int lp5569_init(const struct device *dev)
142 {
143 	/* If the device is behind a power domain, it will start in
144 	 * PM_DEVICE_STATE_OFF.
145 	 */
146 	if (pm_device_on_power_domain(dev)) {
147 		pm_device_init_off(dev);
148 		LOG_INF("Init %s as PM_DEVICE_STATE_OFF", dev->name);
149 		return 0;
150 	}
151 
152 	return lp5569_enable(dev);
153 }
154 
155 #ifdef CONFIG_PM_DEVICE
lp5569_pm_action(const struct device * dev,enum pm_device_action action)156 static int lp5569_pm_action(const struct device *dev, enum pm_device_action action)
157 {
158 	const struct lp5569_config *config = dev->config;
159 	int ret;
160 
161 	switch (action) {
162 	case PM_DEVICE_ACTION_TURN_ON:
163 	case PM_DEVICE_ACTION_RESUME:
164 		ret = lp5569_enable(dev);
165 		if (ret < 0) {
166 			LOG_ERR("Enable LP5569 failed");
167 			return ret;
168 		}
169 		break;
170 	case PM_DEVICE_ACTION_TURN_OFF:
171 	case PM_DEVICE_ACTION_SUSPEND:
172 		ret = i2c_reg_update_byte_dt(&config->bus, LP5569_CONFIG, LP5569_CHIP_EN, 0);
173 		if (ret < 0) {
174 			LOG_ERR("Disable LP5569 failed");
175 			return ret;
176 		}
177 		break;
178 	default:
179 		return -ENOTSUP;
180 	}
181 
182 	return 0;
183 }
184 #endif /* CONFIG_PM_DEVICE */
185 
186 static DEVICE_API(led, lp5569_led_api) = {
187 	.set_brightness = lp5569_led_set_brightness,
188 	.on = lp5569_led_on,
189 	.off = lp5569_led_off,
190 	.write_channels = lp5569_write_channels,
191 };
192 
193 #define LP5569_DEFINE(id)                                                                          \
194 	static const struct lp5569_config lp5569_config_##id = {                                   \
195 		.bus = I2C_DT_SPEC_INST_GET(id),                                                   \
196 		.enable_gpio = GPIO_DT_SPEC_INST_GET_OR(id, enable_gpios, {0}),                    \
197 		.cp_mode = DT_ENUM_IDX(DT_DRV_INST(id), charge_pump_mode),                         \
198 	};                                                                                         \
199                                                                                                    \
200 	PM_DEVICE_DT_INST_DEFINE(id, lp5569_pm_action);                                            \
201                                                                                                    \
202 	DEVICE_DT_INST_DEFINE(id, &lp5569_init, PM_DEVICE_DT_INST_GET(id), NULL,                   \
203 			      &lp5569_config_##id, POST_KERNEL, CONFIG_LED_INIT_PRIORITY,          \
204 			      &lp5569_led_api);
205 
206 DT_INST_FOREACH_STATUS_OKAY(LP5569_DEFINE)
207