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