1 // SPDX-License-Identifier: GPL-2.0-only
3 * NCP5623 Multi-LED Driver
5 * Author: Abdel Alkuor <alkuor@gmail.com>
6 * Datasheet: https://www.onsemi.com/pdf/datasheet/ncp5623-d.pdf
10 #include <linux/module.h>
12 #include <linux/led-class-multicolor.h>
14 #define NCP5623_FUNCTION_OFFSET 0x5
15 #define NCP5623_REG(x) ((x) << NCP5623_FUNCTION_OFFSET)
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)
24 #define NCP5623_MAX_BRIGHTNESS 0x1f
25 #define NCP5623_MAX_DIM_TIME_MS 240
26 #define NCP5623_DIM_STEP_MS 8
29 struct i2c_client
*client
;
30 struct led_classdev_mc mc_dev
;
33 int current_brightness
;
37 static int ncp5623_write(struct i2c_client
*client
, u8 reg
, u8 data
)
39 return i2c_smbus_write_byte_data(client
, reg
| data
, 0);
42 static int ncp5623_brightness_set(struct led_classdev
*cdev
,
43 enum led_brightness brightness
)
45 struct led_classdev_mc
*mc_cdev
= lcdev_to_mccdev(cdev
);
46 struct ncp5623
*ncp
= container_of(mc_cdev
, struct ncp5623
, mc_dev
);
49 guard(mutex
)(&ncp
->lock
);
51 if (ncp
->delay
&& time_is_after_jiffies(ncp
->delay
))
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
));
65 ret
= ncp5623_write(ncp
->client
, NCP5623_DIMMING_TIME_REG
, 0);
69 ret
= ncp5623_write(ncp
->client
, NCP5623_ILED_REG
, brightness
);
73 ncp
->current_brightness
= brightness
;
78 static int ncp5623_pattern_set(struct led_classdev
*cdev
,
79 struct led_pattern
*pattern
,
82 struct led_classdev_mc
*mc_cdev
= lcdev_to_mccdev(cdev
);
83 struct ncp5623
*ncp
= container_of(mc_cdev
, struct ncp5623
, mc_dev
);
88 guard(mutex
)(&ncp
->lock
);
90 if (ncp
->delay
&& time_is_after_jiffies(ncp
->delay
))
95 if (pattern
[0].delta_t
> NCP5623_MAX_DIM_TIME_MS
||
96 (pattern
[0].delta_t
% NCP5623_DIM_STEP_MS
) != 0)
99 brightness_diff
= pattern
[0].brightness
- ncp
->current_brightness
;
101 if (brightness_diff
== 0)
104 if (pattern
[0].delta_t
) {
105 if (brightness_diff
> 0)
106 reg
= NCP5623_UPWARD_STEP_REG
;
108 reg
= NCP5623_DOWNWARD_STEP_REG
;
110 reg
= NCP5623_ILED_REG
;
113 ret
= ncp5623_write(ncp
->client
, reg
,
114 min(pattern
[0].brightness
, NCP5623_MAX_BRIGHTNESS
));
118 ret
= ncp5623_write(ncp
->client
,
119 NCP5623_DIMMING_TIME_REG
,
120 pattern
[0].delta_t
/ NCP5623_DIM_STEP_MS
);
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.
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
136 if (abs(brightness_diff
) == 1)
137 ncp
->delay
= NCP5623_MAX_BRIGHTNESS
+ brightness_diff
;
139 ncp
->delay
= abs(brightness_diff
);
141 ncp
->delay
= msecs_to_jiffies(ncp
->delay
* pattern
[0].delta_t
) + jiffies
;
143 ncp
->current_brightness
= pattern
[0].brightness
;
148 static int ncp5623_pattern_clear(struct led_classdev
*led_cdev
)
153 static int ncp5623_probe(struct i2c_client
*client
)
155 struct device
*dev
= &client
->dev
;
156 struct fwnode_handle
*mc_node
, *led_node
;
157 struct led_init_data init_data
= { };
160 struct mc_subled
*subled_info
;
165 ncp
= devm_kzalloc(dev
, sizeof(*ncp
), GFP_KERNEL
);
169 ncp
->client
= client
;
171 mc_node
= device_get_named_child_node(dev
, "multi-led");
175 fwnode_for_each_child_node(mc_node
, led_node
)
178 subled_info
= devm_kcalloc(dev
, num_subleds
, sizeof(*subled_info
), GFP_KERNEL
);
181 goto release_mc_node
;
184 fwnode_for_each_available_child_node(mc_node
, led_node
) {
185 ret
= fwnode_property_read_u32(led_node
, "color", &color_index
);
187 goto release_led_node
;
189 ret
= fwnode_property_read_u32(led_node
, "reg", ®
);
191 goto release_led_node
;
193 subled_info
[ncp
->mc_dev
.num_colors
].channel
= reg
;
194 subled_info
[ncp
->mc_dev
.num_colors
++].color_index
= color_index
;
197 init_data
.fwnode
= mc_node
;
199 ncp
->mc_dev
.led_cdev
.max_brightness
= NCP5623_MAX_BRIGHTNESS
;
200 ncp
->mc_dev
.subled_info
= subled_info
;
201 ncp
->mc_dev
.led_cdev
.brightness_set_blocking
= ncp5623_brightness_set
;
202 ncp
->mc_dev
.led_cdev
.pattern_set
= ncp5623_pattern_set
;
203 ncp
->mc_dev
.led_cdev
.pattern_clear
= ncp5623_pattern_clear
;
204 ncp
->mc_dev
.led_cdev
.default_trigger
= "pattern";
206 mutex_init(&ncp
->lock
);
207 i2c_set_clientdata(client
, ncp
);
209 ret
= led_classdev_multicolor_register_ext(dev
, &ncp
->mc_dev
, &init_data
);
216 mutex_destroy(&ncp
->lock
);
219 fwnode_handle_put(mc_node
);
224 fwnode_handle_put(led_node
);
225 goto release_mc_node
;
228 static void ncp5623_remove(struct i2c_client
*client
)
230 struct ncp5623
*ncp
= i2c_get_clientdata(client
);
232 mutex_lock(&ncp
->lock
);
234 mutex_unlock(&ncp
->lock
);
236 ncp5623_write(client
, NCP5623_DIMMING_TIME_REG
, 0);
237 led_classdev_multicolor_unregister(&ncp
->mc_dev
);
238 mutex_destroy(&ncp
->lock
);
241 static void ncp5623_shutdown(struct i2c_client
*client
)
243 struct ncp5623
*ncp
= i2c_get_clientdata(client
);
245 if (!(ncp
->mc_dev
.led_cdev
.flags
& LED_RETAIN_AT_SHUTDOWN
))
246 ncp5623_write(client
, NCP5623_SHUTDOWN_REG
, 0);
248 mutex_destroy(&ncp
->lock
);
251 static const struct of_device_id ncp5623_id
[] = {
252 { .compatible
= "onnn,ncp5623" },
255 MODULE_DEVICE_TABLE(of
, ncp5623_id
);
257 static struct i2c_driver ncp5623_i2c_driver
= {
260 .of_match_table
= ncp5623_id
,
262 .probe
= ncp5623_probe
,
263 .remove
= ncp5623_remove
,
264 .shutdown
= ncp5623_shutdown
,
267 module_i2c_driver(ncp5623_i2c_driver
);
269 MODULE_AUTHOR("Abdel Alkuor <alkuor@gmail.com>");
270 MODULE_DESCRIPTION("NCP5623 Multi-LED driver");
271 MODULE_LICENSE("GPL");