Skip to content

Commit 7b7e50f

Browse files
bobisnooblag-linaro
authored andcommitted
leds: Add NCP5623 multi-led driver
NCP5623 is DC-DC multi-LEDs driver which has three PWMs that can be programmed up to 32 steps giving 32768 colors hue. NCP5623 driver supports gradual dimming upward/downward with programmable delays. Also, the driver supports driving a single LED or multi-LED like RGB. Signed-off-by: Abdel Alkuor <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Lee Jones <[email protected]>
1 parent 2c7c70f commit 7b7e50f

File tree

3 files changed

+283
-0
lines changed

3 files changed

+283
-0
lines changed

drivers/leds/rgb/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@ config LEDS_KTD202X
2727
To compile this driver as a module, choose M here: the module
2828
will be called leds-ktd202x.
2929

30+
config LEDS_NCP5623
31+
tristate "LED support for NCP5623"
32+
depends on I2C
33+
depends on OF
34+
help
35+
This option enables support for ON semiconductor NCP5623
36+
Triple Output I2C Controlled RGB LED Driver.
37+
38+
To compile this driver as a module, choose M here: the module
39+
will be called leds-ncp5623.
40+
3041
config LEDS_PWM_MULTICOLOR
3142
tristate "PWM driven multi-color LED Support"
3243
depends on PWM

drivers/leds/rgb/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) += leds-group-multicolor.o
44
obj-$(CONFIG_LEDS_KTD202X) += leds-ktd202x.o
5+
obj-$(CONFIG_LEDS_NCP5623) += leds-ncp5623.o
56
obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o
67
obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o
78
obj-$(CONFIG_LEDS_MT6370_RGB) += leds-mt6370-rgb.o

drivers/leds/rgb/leds-ncp5623.c

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* NCP5623 Multi-LED Driver
4+
*
5+
* Author: Abdel Alkuor <[email protected]>
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 240 /* ms */
26+
#define NCP5623_DIM_STEP 8 /* ms */
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 ||
96+
(pattern[0].delta_t % NCP5623_DIM_STEP) != 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);
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 <[email protected]>");
270+
MODULE_DESCRIPTION("NCP5623 Multi-LED driver");
271+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)