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/i2c.h>
17 #include <zephyr/drivers/led.h>
18 #include <zephyr/device.h>
19 #include <zephyr/pm/device.h>
20 #include <zephyr/kernel.h>
21 
22 #include <zephyr/logging/log.h>
23 LOG_MODULE_REGISTER(lp5569, CONFIG_LED_LOG_LEVEL);
24 
25 #define LP5569_NUM_LEDS			9
26 
27 /* General Registers */
28 #define LP5569_CONFIG			0x00
29 #define LP5569_CHIP_EN			BIT(6)
30 
31 #define LP5569_MISC			0x2F
32 #define LP5569_POWERSAVE_EN		BIT(5)
33 
34 /* PWM base Register for controlling the duty-cycle */
35 #define LP5569_LED0_PWM			0x16
36 
37 struct lp5569_config {
38 	struct i2c_dt_spec bus;
39 };
40 
lp5569_led_set_brightness(const struct device * dev,uint32_t led,uint8_t brightness)41 static int lp5569_led_set_brightness(const struct device *dev, uint32_t led,
42 				     uint8_t brightness)
43 {
44 	const struct lp5569_config *config = dev->config;
45 	uint8_t val;
46 	int ret;
47 
48 	if (led >= LP5569_NUM_LEDS || brightness > 100) {
49 		return -EINVAL;
50 	}
51 
52 	/* Map 0-100 % to 0-255 pwm register value */
53 	val = brightness * 255 / 100;
54 
55 	ret = i2c_reg_write_byte_dt(&config->bus, LP5569_LED0_PWM + led, val);
56 	if (ret < 0) {
57 		LOG_ERR("LED reg update failed");
58 		return ret;
59 	}
60 
61 	return 0;
62 }
63 
lp5569_led_on(const struct device * dev,uint32_t led)64 static inline int lp5569_led_on(const struct device *dev, uint32_t led)
65 {
66 	/* Set LED brightness to 100 % */
67 	return lp5569_led_set_brightness(dev, led, 100);
68 }
69 
lp5569_led_off(const struct device * dev,uint32_t led)70 static inline int lp5569_led_off(const struct device *dev, uint32_t led)
71 {
72 	/* Set LED brightness to 0 % */
73 	return lp5569_led_set_brightness(dev, led, 0);
74 }
75 
lp5569_enable(const struct device * dev)76 static int lp5569_enable(const struct device *dev)
77 {
78 	const struct lp5569_config *config = dev->config;
79 	int ret;
80 
81 	if (!i2c_is_ready_dt(&config->bus)) {
82 		LOG_ERR("I2C device not ready");
83 		return -ENODEV;
84 	}
85 
86 	ret = i2c_reg_write_byte_dt(&config->bus, LP5569_CONFIG,
87 				    LP5569_CHIP_EN);
88 	if (ret < 0) {
89 		LOG_ERR("Enable LP5569 failed");
90 		return ret;
91 	}
92 
93 	ret = i2c_reg_write_byte_dt(&config->bus, LP5569_MISC,
94 				    LP5569_POWERSAVE_EN);
95 	if (ret < 0) {
96 		LOG_ERR("LED reg update failed");
97 		return ret;
98 	}
99 
100 	return 0;
101 }
102 
lp5569_init(const struct device * dev)103 static int lp5569_init(const struct device *dev)
104 {
105 	/* If the device is behind a power domain, it will start in
106 	 * PM_DEVICE_STATE_OFF.
107 	 */
108 	if (pm_device_on_power_domain(dev)) {
109 		pm_device_init_off(dev);
110 		LOG_INF("Init %s as PM_DEVICE_STATE_OFF", dev->name);
111 		return 0;
112 	}
113 
114 	return lp5569_enable(dev);
115 }
116 
117 #ifdef CONFIG_PM_DEVICE
lp5569_pm_action(const struct device * dev,enum pm_device_action action)118 static int lp5569_pm_action(const struct device *dev,
119 			    enum pm_device_action action)
120 {
121 	const struct lp5569_config *config = dev->config;
122 	int ret;
123 
124 	switch (action) {
125 	case PM_DEVICE_ACTION_TURN_ON:
126 	case PM_DEVICE_ACTION_RESUME:
127 		ret = lp5569_enable(dev);
128 		if (ret < 0) {
129 			LOG_ERR("Enable LP5569 failed");
130 			return ret;
131 		}
132 		break;
133 	case PM_DEVICE_ACTION_TURN_OFF:
134 	case PM_DEVICE_ACTION_SUSPEND:
135 		ret = i2c_reg_update_byte_dt(&config->bus, LP5569_CONFIG,
136 					   LP5569_CHIP_EN, 0);
137 		if (ret < 0) {
138 			LOG_ERR("Disable LP5569 failed");
139 			return ret;
140 		}
141 		break;
142 	default:
143 		return -ENOTSUP;
144 	}
145 
146 	return 0;
147 }
148 #endif /* CONFIG_PM_DEVICE */
149 
150 static const struct led_driver_api lp5569_led_api = {
151 	.set_brightness = lp5569_led_set_brightness,
152 	.on = lp5569_led_on,
153 	.off = lp5569_led_off,
154 };
155 
156 #define LP5569_DEFINE(id)						\
157 	static const struct lp5569_config lp5569_config_##id = {	\
158 		.bus = I2C_DT_SPEC_INST_GET(id),			\
159 	};								\
160 									\
161 	PM_DEVICE_DT_INST_DEFINE(id, lp5569_pm_action);			\
162 									\
163 	DEVICE_DT_INST_DEFINE(id, &lp5569_init,				\
164 			      PM_DEVICE_DT_INST_GET(id),		\
165 			      NULL,					\
166 			      &lp5569_config_##id, POST_KERNEL,		\
167 			      CONFIG_LED_INIT_PRIORITY,			\
168 			      &lp5569_led_api);
169 
170 DT_INST_FOREACH_STATUS_OKAY(LP5569_DEFINE)
171