brintos

brintos / linux-shallow public Read only

0
0
Text · 6.6 KiB · f181566 Raw
272 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * NCP5623 Multi-LED Driver4 *5 * Author: Abdel Alkuor <alkuor@gmail.com>6 * Datasheet: https://www.onsemi.com/pdf/datasheet/ncp5623-d.pdf7 */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		0x515#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		0x1f25#define NCP5623_MAX_DIM_TIME_MS		24026#define NCP5623_DIM_STEP_MS		827 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		else108			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 some126	 * unknown reason, the time factor it takes to change to the new127	 * value is the longest time possible. Otherwise, the time factor128	 * is simply the brightness difference.129	 *130	 * For example:131	 * current_brightness = 20 and new_brightness = 21 then the time it132	 * takes to set the new brightness increments to the maximum possible133	 * brightness from 20 then from 0 to 21.134	 * time_factor = max_brightness - 20 + 21135	 */136	if (abs(brightness_diff) == 1)137		ncp->delay = NCP5623_MAX_BRIGHTNESS + brightness_diff;138	else139		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	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			goto release_led_node;188 189		ret = fwnode_property_read_u32(led_node, "reg", &reg);190		if (ret)191			goto release_led_node;192 193		subled_info[ncp->mc_dev.num_colors].channel = reg;194		subled_info[ncp->mc_dev.num_colors++].color_index = color_index;195	}196 197	init_data.fwnode = mc_node;198 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";205 206	mutex_init(&ncp->lock);207	i2c_set_clientdata(client, ncp);208 209	ret = led_classdev_multicolor_register_ext(dev, &ncp->mc_dev, &init_data);210	if (ret)211		goto destroy_lock;212 213	return 0;214 215destroy_lock:216	mutex_destroy(&ncp->lock);217 218release_mc_node:219	fwnode_handle_put(mc_node);220 221	return ret;222 223release_led_node:224	fwnode_handle_put(led_node);225	goto release_mc_node;226}227 228static 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 241static 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 251static const struct of_device_id ncp5623_id[] = {252	{ .compatible = "onnn,ncp5623" },253	{ }254};255MODULE_DEVICE_TABLE(of, ncp5623_id);256 257static 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 267module_i2c_driver(ncp5623_i2c_driver);268 269MODULE_AUTHOR("Abdel Alkuor <alkuor@gmail.com>");270MODULE_DESCRIPTION("NCP5623 Multi-LED driver");271MODULE_LICENSE("GPL");272