File Index
Files and directories
Sign in
FILE

leds-ncp5623.c

File preview with syntax highlighting.

linux-6.19.14/drivers/leds/rgb/leds-ncp5623.c C
  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
 28struct 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
 37static 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
 42static 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
 78static 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
148static int ncp5623_pattern_clear(struct led_classdev *led_cdev)
149{
150	return 0;
151}
152
153static 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	struct ncp5623 *ncp;
159	struct mc_subled *subled_info;
160	unsigned int num_subleds;
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	num_subleds = fwnode_get_child_node_count(mc_node);
176
177	subled_info = devm_kcalloc(dev, num_subleds, sizeof(*subled_info), GFP_KERNEL);
178	if (!subled_info) {
179		ret = -ENOMEM;
180		goto release_mc_node;
181	}
182
183	fwnode_for_each_child_node(mc_node, led_node) {
184		ret = fwnode_property_read_u32(led_node, "color", &color_index);
185		if (ret)
186			goto release_led_node;
187
188		ret = fwnode_property_read_u32(led_node, "reg", &reg);
189		if (ret)
190			goto release_led_node;
191
192		subled_info[ncp->mc_dev.num_colors].channel = reg;
193		subled_info[ncp->mc_dev.num_colors++].color_index = color_index;
194	}
195
196	init_data.fwnode = mc_node;
197
198	ncp->mc_dev.led_cdev.max_brightness = NCP5623_MAX_BRIGHTNESS;
199	ncp->mc_dev.subled_info = subled_info;
200	ncp->mc_dev.led_cdev.brightness_set_blocking = ncp5623_brightness_set;
201	ncp->mc_dev.led_cdev.pattern_set = ncp5623_pattern_set;
202	ncp->mc_dev.led_cdev.pattern_clear = ncp5623_pattern_clear;
203	ncp->mc_dev.led_cdev.default_trigger = "pattern";
204
205	mutex_init(&ncp->lock);
206	i2c_set_clientdata(client, ncp);
207
208	ret = led_classdev_multicolor_register_ext(dev, &ncp->mc_dev, &init_data);
209	if (ret)
210		goto destroy_lock;
211
212	return 0;
213
214destroy_lock:
215	mutex_destroy(&ncp->lock);
216
217release_mc_node:
218	fwnode_handle_put(mc_node);
219
220	return ret;
221
222release_led_node:
223	fwnode_handle_put(led_node);
224	goto release_mc_node;
225}
226
227static void ncp5623_remove(struct i2c_client *client)
228{
229	struct ncp5623 *ncp = i2c_get_clientdata(client);
230
231	mutex_lock(&ncp->lock);
232	ncp->delay = 0;
233	mutex_unlock(&ncp->lock);
234
235	ncp5623_write(client, NCP5623_DIMMING_TIME_REG, 0);
236	led_classdev_multicolor_unregister(&ncp->mc_dev);
237	mutex_destroy(&ncp->lock);
238}
239
240static void ncp5623_shutdown(struct i2c_client *client)
241{
242	struct ncp5623 *ncp = i2c_get_clientdata(client);
243
244	if (!(ncp->mc_dev.led_cdev.flags & LED_RETAIN_AT_SHUTDOWN))
245		ncp5623_write(client, NCP5623_SHUTDOWN_REG, 0);
246
247	mutex_destroy(&ncp->lock);
248}
249
250static const struct of_device_id ncp5623_id[] = {
251	{ .compatible = "onnn,ncp5623" },
252	{ }
253};
254MODULE_DEVICE_TABLE(of, ncp5623_id);
255
256static struct i2c_driver ncp5623_i2c_driver = {
257	.driver	= {
258		.name = "ncp5623",
259		.of_match_table = ncp5623_id,
260	},
261	.probe = ncp5623_probe,
262	.remove = ncp5623_remove,
263	.shutdown = ncp5623_shutdown,
264};
265
266module_i2c_driver(ncp5623_i2c_driver);
267
268MODULE_AUTHOR("Abdel Alkuor <alkuor@gmail.com>");
269MODULE_DESCRIPTION("NCP5623 Multi-LED driver");
270MODULE_LICENSE("GPL");
~/ Read-only file preview.