1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Driver for ISSI IS31FL32xx family of I2C LED controllers |
4 | * |
5 | * Copyright 2015 Allworx Corp. |
6 | * |
7 | * Datasheets: |
8 | * http://www.issi.com/US/product-analog-fxled-driver.shtml |
9 | * http://www.si-en.com/product.asp?parentid=890 |
10 | */ |
11 | |
12 | #include <linux/device.h> |
13 | #include <linux/i2c.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/leds.h> |
16 | #include <linux/module.h> |
17 | #include <linux/of.h> |
18 | |
19 | /* Used to indicate a device has no such register */ |
20 | #define IS31FL32XX_REG_NONE 0xFF |
21 | |
22 | /* Software Shutdown bit in Shutdown Register */ |
23 | #define IS31FL32XX_SHUTDOWN_SSD_ENABLE 0 |
24 | #define IS31FL32XX_SHUTDOWN_SSD_DISABLE BIT(0) |
25 | |
26 | /* IS31FL3216 has a number of unique registers */ |
27 | #define IS31FL3216_CONFIG_REG 0x00 |
28 | #define IS31FL3216_LIGHTING_EFFECT_REG 0x03 |
29 | #define IS31FL3216_CHANNEL_CONFIG_REG 0x04 |
30 | |
31 | /* Software Shutdown bit in 3216 Config Register */ |
32 | #define IS31FL3216_CONFIG_SSD_ENABLE BIT(7) |
33 | #define IS31FL3216_CONFIG_SSD_DISABLE 0 |
34 | |
35 | struct is31fl32xx_priv; |
36 | struct is31fl32xx_led_data { |
37 | struct led_classdev cdev; |
38 | u8 channel; /* 1-based, max priv->cdef->channels */ |
39 | struct is31fl32xx_priv *priv; |
40 | }; |
41 | |
42 | struct is31fl32xx_priv { |
43 | const struct is31fl32xx_chipdef *cdef; |
44 | struct i2c_client *client; |
45 | unsigned int num_leds; |
46 | struct is31fl32xx_led_data leds[]; |
47 | }; |
48 | |
49 | /** |
50 | * struct is31fl32xx_chipdef - chip-specific attributes |
51 | * @channels : Number of LED channels |
52 | * @shutdown_reg : address of Shutdown register (optional) |
53 | * @pwm_update_reg : address of PWM Update register |
54 | * @global_control_reg : address of Global Control register (optional) |
55 | * @reset_reg : address of Reset register (optional) |
56 | * @pwm_register_base : address of first PWM register |
57 | * @pwm_registers_reversed: : true if PWM registers count down instead of up |
58 | * @led_control_register_base : address of first LED control register (optional) |
59 | * @enable_bits_per_led_control_register: number of LEDs enable bits in each |
60 | * @reset_func : pointer to reset function |
61 | * @sw_shutdown_func : pointer to software shutdown function |
62 | * |
63 | * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE |
64 | * indicates that this chip has no such register. |
65 | * |
66 | * If non-NULL, @reset_func will be called during probing to set all |
67 | * necessary registers to a known initialization state. This is needed |
68 | * for chips that do not have a @reset_reg. |
69 | * |
70 | * @enable_bits_per_led_control_register must be >=1 if |
71 | * @led_control_register_base != %IS31FL32XX_REG_NONE. |
72 | */ |
73 | struct is31fl32xx_chipdef { |
74 | u8 channels; |
75 | u8 shutdown_reg; |
76 | u8 pwm_update_reg; |
77 | u8 global_control_reg; |
78 | u8 reset_reg; |
79 | u8 pwm_register_base; |
80 | bool pwm_registers_reversed; |
81 | u8 led_control_register_base; |
82 | u8 enable_bits_per_led_control_register; |
83 | int (*reset_func)(struct is31fl32xx_priv *priv); |
84 | int (*sw_shutdown_func)(struct is31fl32xx_priv *priv, bool enable); |
85 | }; |
86 | |
87 | static const struct is31fl32xx_chipdef is31fl3236_cdef = { |
88 | .channels = 36, |
89 | .shutdown_reg = 0x00, |
90 | .pwm_update_reg = 0x25, |
91 | .global_control_reg = 0x4a, |
92 | .reset_reg = 0x4f, |
93 | .pwm_register_base = 0x01, |
94 | .led_control_register_base = 0x26, |
95 | .enable_bits_per_led_control_register = 1, |
96 | }; |
97 | |
98 | static const struct is31fl32xx_chipdef is31fl3235_cdef = { |
99 | .channels = 28, |
100 | .shutdown_reg = 0x00, |
101 | .pwm_update_reg = 0x25, |
102 | .global_control_reg = 0x4a, |
103 | .reset_reg = 0x4f, |
104 | .pwm_register_base = 0x05, |
105 | .led_control_register_base = 0x2a, |
106 | .enable_bits_per_led_control_register = 1, |
107 | }; |
108 | |
109 | static const struct is31fl32xx_chipdef is31fl3218_cdef = { |
110 | .channels = 18, |
111 | .shutdown_reg = 0x00, |
112 | .pwm_update_reg = 0x16, |
113 | .global_control_reg = IS31FL32XX_REG_NONE, |
114 | .reset_reg = 0x17, |
115 | .pwm_register_base = 0x01, |
116 | .led_control_register_base = 0x13, |
117 | .enable_bits_per_led_control_register = 6, |
118 | }; |
119 | |
120 | static int is31fl3216_reset(struct is31fl32xx_priv *priv); |
121 | static int is31fl3216_software_shutdown(struct is31fl32xx_priv *priv, |
122 | bool enable); |
123 | static const struct is31fl32xx_chipdef is31fl3216_cdef = { |
124 | .channels = 16, |
125 | .shutdown_reg = IS31FL32XX_REG_NONE, |
126 | .pwm_update_reg = 0xB0, |
127 | .global_control_reg = IS31FL32XX_REG_NONE, |
128 | .reset_reg = IS31FL32XX_REG_NONE, |
129 | .pwm_register_base = 0x10, |
130 | .pwm_registers_reversed = true, |
131 | .led_control_register_base = 0x01, |
132 | .enable_bits_per_led_control_register = 8, |
133 | .reset_func = is31fl3216_reset, |
134 | .sw_shutdown_func = is31fl3216_software_shutdown, |
135 | }; |
136 | |
137 | static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val) |
138 | { |
139 | int ret; |
140 | |
141 | dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X" , reg, val); |
142 | |
143 | ret = i2c_smbus_write_byte_data(client: priv->client, command: reg, value: val); |
144 | if (ret) { |
145 | dev_err(&priv->client->dev, |
146 | "register write to 0x%02X failed (error %d)" , |
147 | reg, ret); |
148 | } |
149 | return ret; |
150 | } |
151 | |
152 | /* |
153 | * Custom reset function for IS31FL3216 because it does not have a RESET |
154 | * register the way that the other IS31FL32xx chips do. We don't bother |
155 | * writing the GPIO and animation registers, because the registers we |
156 | * do write ensure those will have no effect. |
157 | */ |
158 | static int is31fl3216_reset(struct is31fl32xx_priv *priv) |
159 | { |
160 | unsigned int i; |
161 | int ret; |
162 | |
163 | ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, |
164 | IS31FL3216_CONFIG_SSD_ENABLE); |
165 | if (ret) |
166 | return ret; |
167 | for (i = 0; i < priv->cdef->channels; i++) { |
168 | ret = is31fl32xx_write(priv, reg: priv->cdef->pwm_register_base+i, |
169 | val: 0x00); |
170 | if (ret) |
171 | return ret; |
172 | } |
173 | ret = is31fl32xx_write(priv, reg: priv->cdef->pwm_update_reg, val: 0); |
174 | if (ret) |
175 | return ret; |
176 | ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, val: 0x00); |
177 | if (ret) |
178 | return ret; |
179 | ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, val: 0x00); |
180 | if (ret) |
181 | return ret; |
182 | |
183 | return 0; |
184 | } |
185 | |
186 | /* |
187 | * Custom Software-Shutdown function for IS31FL3216 because it does not have |
188 | * a SHUTDOWN register the way that the other IS31FL32xx chips do. |
189 | * We don't bother doing a read/modify/write on the CONFIG register because |
190 | * we only ever use a value of '0' for the other fields in that register. |
191 | */ |
192 | static int is31fl3216_software_shutdown(struct is31fl32xx_priv *priv, |
193 | bool enable) |
194 | { |
195 | u8 value = enable ? IS31FL3216_CONFIG_SSD_ENABLE : |
196 | IS31FL3216_CONFIG_SSD_DISABLE; |
197 | |
198 | return is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, val: value); |
199 | } |
200 | |
201 | /* |
202 | * NOTE: A mutex is not needed in this function because: |
203 | * - All referenced data is read-only after probe() |
204 | * - The I2C core has a mutex on to protect the bus |
205 | * - There are no read/modify/write operations |
206 | * - Intervening operations between the write of the PWM register |
207 | * and the Update register are harmless. |
208 | * |
209 | * Example: |
210 | * PWM_REG_1 write 16 |
211 | * UPDATE_REG write 0 |
212 | * PWM_REG_2 write 128 |
213 | * UPDATE_REG write 0 |
214 | * vs: |
215 | * PWM_REG_1 write 16 |
216 | * PWM_REG_2 write 128 |
217 | * UPDATE_REG write 0 |
218 | * UPDATE_REG write 0 |
219 | * are equivalent. Poking the Update register merely applies all PWM |
220 | * register writes up to that point. |
221 | */ |
222 | static int is31fl32xx_brightness_set(struct led_classdev *led_cdev, |
223 | enum led_brightness brightness) |
224 | { |
225 | const struct is31fl32xx_led_data *led_data = |
226 | container_of(led_cdev, struct is31fl32xx_led_data, cdev); |
227 | const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef; |
228 | u8 pwm_register_offset; |
229 | int ret; |
230 | |
231 | dev_dbg(led_cdev->dev, "%s: %d\n" , __func__, brightness); |
232 | |
233 | /* NOTE: led_data->channel is 1-based */ |
234 | if (cdef->pwm_registers_reversed) |
235 | pwm_register_offset = cdef->channels - led_data->channel; |
236 | else |
237 | pwm_register_offset = led_data->channel - 1; |
238 | |
239 | ret = is31fl32xx_write(priv: led_data->priv, |
240 | reg: cdef->pwm_register_base + pwm_register_offset, |
241 | val: brightness); |
242 | if (ret) |
243 | return ret; |
244 | |
245 | return is31fl32xx_write(priv: led_data->priv, reg: cdef->pwm_update_reg, val: 0); |
246 | } |
247 | |
248 | static int is31fl32xx_reset_regs(struct is31fl32xx_priv *priv) |
249 | { |
250 | const struct is31fl32xx_chipdef *cdef = priv->cdef; |
251 | int ret; |
252 | |
253 | if (cdef->reset_reg != IS31FL32XX_REG_NONE) { |
254 | ret = is31fl32xx_write(priv, reg: cdef->reset_reg, val: 0); |
255 | if (ret) |
256 | return ret; |
257 | } |
258 | |
259 | if (cdef->reset_func) |
260 | return cdef->reset_func(priv); |
261 | |
262 | return 0; |
263 | } |
264 | |
265 | static int is31fl32xx_software_shutdown(struct is31fl32xx_priv *priv, |
266 | bool enable) |
267 | { |
268 | const struct is31fl32xx_chipdef *cdef = priv->cdef; |
269 | int ret; |
270 | |
271 | if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) { |
272 | u8 value = enable ? IS31FL32XX_SHUTDOWN_SSD_ENABLE : |
273 | IS31FL32XX_SHUTDOWN_SSD_DISABLE; |
274 | ret = is31fl32xx_write(priv, reg: cdef->shutdown_reg, val: value); |
275 | if (ret) |
276 | return ret; |
277 | } |
278 | |
279 | if (cdef->sw_shutdown_func) |
280 | return cdef->sw_shutdown_func(priv, enable); |
281 | |
282 | return 0; |
283 | } |
284 | |
285 | static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv) |
286 | { |
287 | const struct is31fl32xx_chipdef *cdef = priv->cdef; |
288 | int ret; |
289 | |
290 | ret = is31fl32xx_reset_regs(priv); |
291 | if (ret) |
292 | return ret; |
293 | |
294 | /* |
295 | * Set enable bit for all channels. |
296 | * We will control state with PWM registers alone. |
297 | */ |
298 | if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) { |
299 | u8 value = |
300 | GENMASK(cdef->enable_bits_per_led_control_register-1, 0); |
301 | u8 num_regs = cdef->channels / |
302 | cdef->enable_bits_per_led_control_register; |
303 | int i; |
304 | |
305 | for (i = 0; i < num_regs; i++) { |
306 | ret = is31fl32xx_write(priv, |
307 | reg: cdef->led_control_register_base+i, |
308 | val: value); |
309 | if (ret) |
310 | return ret; |
311 | } |
312 | } |
313 | |
314 | ret = is31fl32xx_software_shutdown(priv, enable: false); |
315 | if (ret) |
316 | return ret; |
317 | |
318 | if (cdef->global_control_reg != IS31FL32XX_REG_NONE) { |
319 | ret = is31fl32xx_write(priv, reg: cdef->global_control_reg, val: 0x00); |
320 | if (ret) |
321 | return ret; |
322 | } |
323 | |
324 | return 0; |
325 | } |
326 | |
327 | static int is31fl32xx_parse_child_dt(const struct device *dev, |
328 | const struct device_node *child, |
329 | struct is31fl32xx_led_data *led_data) |
330 | { |
331 | struct led_classdev *cdev = &led_data->cdev; |
332 | int ret = 0; |
333 | u32 reg; |
334 | |
335 | ret = of_property_read_u32(np: child, propname: "reg" , out_value: ®); |
336 | if (ret || reg < 1 || reg > led_data->priv->cdef->channels) { |
337 | dev_err(dev, |
338 | "Child node %pOF does not have a valid reg property\n" , |
339 | child); |
340 | return -EINVAL; |
341 | } |
342 | led_data->channel = reg; |
343 | |
344 | cdev->brightness_set_blocking = is31fl32xx_brightness_set; |
345 | |
346 | return 0; |
347 | } |
348 | |
349 | static struct is31fl32xx_led_data *is31fl32xx_find_led_data( |
350 | struct is31fl32xx_priv *priv, |
351 | u8 channel) |
352 | { |
353 | size_t i; |
354 | |
355 | for (i = 0; i < priv->num_leds; i++) { |
356 | if (priv->leds[i].channel == channel) |
357 | return &priv->leds[i]; |
358 | } |
359 | |
360 | return NULL; |
361 | } |
362 | |
363 | static int is31fl32xx_parse_dt(struct device *dev, |
364 | struct is31fl32xx_priv *priv) |
365 | { |
366 | struct device_node *child; |
367 | int ret = 0; |
368 | |
369 | for_each_available_child_of_node(dev_of_node(dev), child) { |
370 | struct led_init_data init_data = {}; |
371 | struct is31fl32xx_led_data *led_data = |
372 | &priv->leds[priv->num_leds]; |
373 | const struct is31fl32xx_led_data *other_led_data; |
374 | |
375 | led_data->priv = priv; |
376 | |
377 | ret = is31fl32xx_parse_child_dt(dev, child, led_data); |
378 | if (ret) |
379 | goto err; |
380 | |
381 | /* Detect if channel is already in use by another child */ |
382 | other_led_data = is31fl32xx_find_led_data(priv, |
383 | channel: led_data->channel); |
384 | if (other_led_data) { |
385 | dev_err(dev, |
386 | "Node %pOF 'reg' conflicts with another LED\n" , |
387 | child); |
388 | ret = -EINVAL; |
389 | goto err; |
390 | } |
391 | |
392 | init_data.fwnode = of_fwnode_handle(child); |
393 | |
394 | ret = devm_led_classdev_register_ext(parent: dev, led_cdev: &led_data->cdev, |
395 | init_data: &init_data); |
396 | if (ret) { |
397 | dev_err(dev, "Failed to register LED for %pOF: %d\n" , |
398 | child, ret); |
399 | goto err; |
400 | } |
401 | |
402 | priv->num_leds++; |
403 | } |
404 | |
405 | return 0; |
406 | |
407 | err: |
408 | of_node_put(node: child); |
409 | return ret; |
410 | } |
411 | |
412 | static const struct of_device_id of_is31fl32xx_match[] = { |
413 | { .compatible = "issi,is31fl3236" , .data = &is31fl3236_cdef, }, |
414 | { .compatible = "issi,is31fl3235" , .data = &is31fl3235_cdef, }, |
415 | { .compatible = "issi,is31fl3218" , .data = &is31fl3218_cdef, }, |
416 | { .compatible = "si-en,sn3218" , .data = &is31fl3218_cdef, }, |
417 | { .compatible = "issi,is31fl3216" , .data = &is31fl3216_cdef, }, |
418 | { .compatible = "si-en,sn3216" , .data = &is31fl3216_cdef, }, |
419 | {}, |
420 | }; |
421 | |
422 | MODULE_DEVICE_TABLE(of, of_is31fl32xx_match); |
423 | |
424 | static int is31fl32xx_probe(struct i2c_client *client) |
425 | { |
426 | const struct is31fl32xx_chipdef *cdef; |
427 | struct device *dev = &client->dev; |
428 | struct is31fl32xx_priv *priv; |
429 | int count; |
430 | int ret = 0; |
431 | |
432 | cdef = device_get_match_data(dev); |
433 | |
434 | count = of_get_available_child_count(np: dev_of_node(dev)); |
435 | if (!count) |
436 | return -EINVAL; |
437 | |
438 | priv = devm_kzalloc(dev, struct_size(priv, leds, count), |
439 | GFP_KERNEL); |
440 | if (!priv) |
441 | return -ENOMEM; |
442 | |
443 | priv->client = client; |
444 | priv->cdef = cdef; |
445 | i2c_set_clientdata(client, data: priv); |
446 | |
447 | ret = is31fl32xx_init_regs(priv); |
448 | if (ret) |
449 | return ret; |
450 | |
451 | ret = is31fl32xx_parse_dt(dev, priv); |
452 | if (ret) |
453 | return ret; |
454 | |
455 | return 0; |
456 | } |
457 | |
458 | static void is31fl32xx_remove(struct i2c_client *client) |
459 | { |
460 | struct is31fl32xx_priv *priv = i2c_get_clientdata(client); |
461 | int ret; |
462 | |
463 | ret = is31fl32xx_reset_regs(priv); |
464 | if (ret) |
465 | dev_err(&client->dev, "Failed to reset registers on removal (%pe)\n" , |
466 | ERR_PTR(ret)); |
467 | } |
468 | |
469 | /* |
470 | * i2c-core (and modalias) requires that id_table be properly filled, |
471 | * even though it is not used for DeviceTree based instantiation. |
472 | */ |
473 | static const struct i2c_device_id is31fl32xx_id[] = { |
474 | { "is31fl3236" }, |
475 | { "is31fl3235" }, |
476 | { "is31fl3218" }, |
477 | { "sn3218" }, |
478 | { "is31fl3216" }, |
479 | { "sn3216" }, |
480 | {}, |
481 | }; |
482 | |
483 | MODULE_DEVICE_TABLE(i2c, is31fl32xx_id); |
484 | |
485 | static struct i2c_driver is31fl32xx_driver = { |
486 | .driver = { |
487 | .name = "is31fl32xx" , |
488 | .of_match_table = of_is31fl32xx_match, |
489 | }, |
490 | .probe = is31fl32xx_probe, |
491 | .remove = is31fl32xx_remove, |
492 | .id_table = is31fl32xx_id, |
493 | }; |
494 | |
495 | module_i2c_driver(is31fl32xx_driver); |
496 | |
497 | MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>" ); |
498 | MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver" ); |
499 | MODULE_LICENSE("GPL v2" ); |
500 | |