xref: /linux/drivers/leds/rgb/leds-ncp5623.c (revision 1e525507)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * NCP5623 Multi-LED Driver
4  *
5  * Author: Abdel Alkuor <alkuor@gmail.com>
6  * Datasheet: https://www.onsemi.com/pdf/datasheet/ncp5623-d.pdf
7  */
8 
9 #include <linux/i2c.h>
10 #include <linux/module.h>
11 
12 #include <linux/led-class-multicolor.h>
13 
14 #define NCP5623_FUNCTION_OFFSET		0x5
15 #define NCP5623_REG(x)			((x) << NCP5623_FUNCTION_OFFSET)
16 
17 #define NCP5623_SHUTDOWN_REG		NCP5623_REG(0x0)
18 #define NCP5623_ILED_REG		NCP5623_REG(0x1)
19 #define NCP5623_PWM_REG(index)		NCP5623_REG(0x2 + (index))
20 #define NCP5623_UPWARD_STEP_REG		NCP5623_REG(0x5)
21 #define NCP5623_DOWNWARD_STEP_REG	NCP5623_REG(0x6)
22 #define NCP5623_DIMMING_TIME_REG	NCP5623_REG(0x7)
23 
24 #define NCP5623_MAX_BRIGHTNESS		0x1f
25 #define NCP5623_MAX_DIM_TIME_MS		240
26 #define NCP5623_DIM_STEP_MS		8
27 
28 struct ncp5623 {
29 	struct i2c_client *client;
30 	struct led_classdev_mc mc_dev;
31 	struct mutex lock;
32 
33 	int current_brightness;
34 	unsigned long delay;
35 };
36 
37 static int ncp5623_write(struct i2c_client *client, u8 reg, u8 data)
38 {
39 	return i2c_smbus_write_byte_data(client, reg | data, 0);
40 }
41 
42 static int ncp5623_brightness_set(struct led_classdev *cdev,
43 				  enum led_brightness brightness)
44 {
45 	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
46 	struct ncp5623 *ncp = container_of(mc_cdev, struct ncp5623, mc_dev);
47 	int ret;
48 
49 	guard(mutex)(&ncp->lock);
50 
51 	if (ncp->delay && time_is_after_jiffies(ncp->delay))
52 		return -EBUSY;
53 
54 	ncp->delay = 0;
55 
56 	for (int i = 0; i < mc_cdev->num_colors; i++) {
57 		ret = ncp5623_write(ncp->client,
58 				    NCP5623_PWM_REG(mc_cdev->subled_info[i].channel),
59 				    min(mc_cdev->subled_info[i].intensity,
60 					NCP5623_MAX_BRIGHTNESS));
61 		if (ret)
62 			return ret;
63 	}
64 
65 	ret = ncp5623_write(ncp->client, NCP5623_DIMMING_TIME_REG, 0);
66 	if (ret)
67 		return ret;
68 
69 	ret = ncp5623_write(ncp->client, NCP5623_ILED_REG, brightness);
70 	if (ret)
71 		return ret;
72 
73 	ncp->current_brightness = brightness;
74 
75 	return 0;
76 }
77 
78 static int ncp5623_pattern_set(struct led_classdev *cdev,
79 			       struct led_pattern *pattern,
80 			       u32 len, int repeat)
81 {
82 	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
83 	struct ncp5623 *ncp = container_of(mc_cdev, struct ncp5623, mc_dev);
84 	int brightness_diff;
85 	u8 reg;
86 	int ret;
87 
88 	guard(mutex)(&ncp->lock);
89 
90 	if (ncp->delay && time_is_after_jiffies(ncp->delay))
91 		return -EBUSY;
92 
93 	ncp->delay = 0;
94 
95 	if (pattern[0].delta_t > NCP5623_MAX_DIM_TIME_MS ||
96 	   (pattern[0].delta_t % NCP5623_DIM_STEP_MS) != 0)
97 		return -EINVAL;
98 
99 	brightness_diff = pattern[0].brightness - ncp->current_brightness;
100 
101 	if (brightness_diff == 0)
102 		return 0;
103 
104 	if (pattern[0].delta_t) {
105 		if (brightness_diff > 0)
106 			reg = NCP5623_UPWARD_STEP_REG;
107 		else
108 			reg = NCP5623_DOWNWARD_STEP_REG;
109 	} else {
110 		reg = NCP5623_ILED_REG;
111 	}
112 
113 	ret = ncp5623_write(ncp->client, reg,
114 			    min(pattern[0].brightness, NCP5623_MAX_BRIGHTNESS));
115 	if (ret)
116 		return ret;
117 
118 	ret = ncp5623_write(ncp->client,
119 			    NCP5623_DIMMING_TIME_REG,
120 			    pattern[0].delta_t / NCP5623_DIM_STEP_MS);
121 	if (ret)
122 		return ret;
123 
124 	/*
125 	 * During testing, when the brightness difference is 1, for some
126 	 * unknown reason, the time factor it takes to change to the new
127 	 * value is the longest time possible. Otherwise, the time factor
128 	 * is simply the brightness difference.
129 	 *
130 	 * For example:
131 	 * current_brightness = 20 and new_brightness = 21 then the time it
132 	 * takes to set the new brightness increments to the maximum possible
133 	 * brightness from 20 then from 0 to 21.
134 	 * time_factor = max_brightness - 20 + 21
135 	 */
136 	if (abs(brightness_diff) == 1)
137 		ncp->delay = NCP5623_MAX_BRIGHTNESS + brightness_diff;
138 	else
139 		ncp->delay = abs(brightness_diff);
140 
141 	ncp->delay = msecs_to_jiffies(ncp->delay * pattern[0].delta_t) + jiffies;
142 
143 	ncp->current_brightness = pattern[0].brightness;
144 
145 	return 0;
146 }
147 
148 static int ncp5623_pattern_clear(struct led_classdev *led_cdev)
149 {
150 	return 0;
151 }
152 
153 static int ncp5623_probe(struct i2c_client *client)
154 {
155 	struct device *dev = &client->dev;
156 	struct fwnode_handle *mc_node, *led_node;
157 	struct led_init_data init_data = { };
158 	int num_subleds = 0;
159 	struct ncp5623 *ncp;
160 	struct mc_subled *subled_info;
161 	u32 color_index;
162 	u32 reg;
163 	int ret;
164 
165 	ncp = devm_kzalloc(dev, sizeof(*ncp), GFP_KERNEL);
166 	if (!ncp)
167 		return -ENOMEM;
168 
169 	ncp->client = client;
170 
171 	mc_node = device_get_named_child_node(dev, "multi-led");
172 	if (!mc_node)
173 		return -EINVAL;
174 
175 	fwnode_for_each_child_node(mc_node, led_node)
176 		num_subleds++;
177 
178 	subled_info = devm_kcalloc(dev, num_subleds, sizeof(*subled_info), GFP_KERNEL);
179 	if (!subled_info) {
180 		ret = -ENOMEM;
181 		goto release_mc_node;
182 	}
183 
184 	fwnode_for_each_available_child_node(mc_node, led_node) {
185 		ret = fwnode_property_read_u32(led_node, "color", &color_index);
186 		if (ret) {
187 			fwnode_handle_put(led_node);
188 			goto release_mc_node;
189 		}
190 
191 		ret = fwnode_property_read_u32(led_node, "reg", &reg);
192 		if (ret) {
193 			fwnode_handle_put(led_node);
194 			goto release_mc_node;
195 		}
196 
197 		subled_info[ncp->mc_dev.num_colors].channel = reg;
198 		subled_info[ncp->mc_dev.num_colors++].color_index = color_index;
199 	}
200 
201 	init_data.fwnode = mc_node;
202 
203 	ncp->mc_dev.led_cdev.max_brightness = NCP5623_MAX_BRIGHTNESS;
204 	ncp->mc_dev.subled_info = subled_info;
205 	ncp->mc_dev.led_cdev.brightness_set_blocking = ncp5623_brightness_set;
206 	ncp->mc_dev.led_cdev.pattern_set = ncp5623_pattern_set;
207 	ncp->mc_dev.led_cdev.pattern_clear = ncp5623_pattern_clear;
208 	ncp->mc_dev.led_cdev.default_trigger = "pattern";
209 
210 	mutex_init(&ncp->lock);
211 	i2c_set_clientdata(client, ncp);
212 
213 	ret = led_classdev_multicolor_register_ext(dev, &ncp->mc_dev, &init_data);
214 	if (ret)
215 		goto destroy_lock;
216 
217 	return 0;
218 
219 destroy_lock:
220 	mutex_destroy(&ncp->lock);
221 
222 release_mc_node:
223 	fwnode_handle_put(mc_node);
224 
225 	return ret;
226 }
227 
228 static void ncp5623_remove(struct i2c_client *client)
229 {
230 	struct ncp5623 *ncp = i2c_get_clientdata(client);
231 
232 	mutex_lock(&ncp->lock);
233 	ncp->delay = 0;
234 	mutex_unlock(&ncp->lock);
235 
236 	ncp5623_write(client, NCP5623_DIMMING_TIME_REG, 0);
237 	led_classdev_multicolor_unregister(&ncp->mc_dev);
238 	mutex_destroy(&ncp->lock);
239 }
240 
241 static void ncp5623_shutdown(struct i2c_client *client)
242 {
243 	struct ncp5623 *ncp = i2c_get_clientdata(client);
244 
245 	if (!(ncp->mc_dev.led_cdev.flags & LED_RETAIN_AT_SHUTDOWN))
246 		ncp5623_write(client, NCP5623_SHUTDOWN_REG, 0);
247 
248 	mutex_destroy(&ncp->lock);
249 }
250 
251 static const struct of_device_id ncp5623_id[] = {
252 	{ .compatible = "onnn,ncp5623" },
253 	{ }
254 };
255 MODULE_DEVICE_TABLE(of, ncp5623_id);
256 
257 static struct i2c_driver ncp5623_i2c_driver = {
258 	.driver	= {
259 		.name = "ncp5623",
260 		.of_match_table = ncp5623_id,
261 	},
262 	.probe = ncp5623_probe,
263 	.remove = ncp5623_remove,
264 	.shutdown = ncp5623_shutdown,
265 };
266 
267 module_i2c_driver(ncp5623_i2c_driver);
268 
269 MODULE_AUTHOR("Abdel Alkuor <alkuor@gmail.com>");
270 MODULE_DESCRIPTION("NCP5623 Multi-LED driver");
271 MODULE_LICENSE("GPL");
272