xref: /linux/drivers/leds/rgb/leds-lp5860-core.c (revision 49bda4826843be0ef97a162009a29ea3a63f3935)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (c) 2025 Pengutronix
4  *
5  * Author: Steffen Trumtrar <kernel@pengutronix.de>
6  */
7 
8 #include <linux/led-class-multicolor.h>
9 #include <linux/module.h>
10 #include <linux/mutex.h>
11 #include <linux/of_platform.h>
12 #include <linux/property.h>
13 #include <linux/regmap.h>
14 
15 #include "leds-lp5860.h"
16 
mcled_cdev_to_led(struct led_classdev_mc * mc_cdev)17 static struct lp5860_led *mcled_cdev_to_led(struct led_classdev_mc *mc_cdev)
18 {
19 	return container_of(mc_cdev, struct lp5860_led, mc_cdev);
20 }
21 
lp5860_set_dot_onoff(struct lp5860_led * led,unsigned int dot,bool enable)22 static int lp5860_set_dot_onoff(struct lp5860_led *led, unsigned int dot, bool enable)
23 {
24 	unsigned int offset = dot / LP5860_MAX_DOT_ONOFF_GROUP_NUM;
25 	unsigned int mask = BIT(dot % LP5860_MAX_DOT_ONOFF_GROUP_NUM);
26 
27 	if (dot > LP5860_MAX_LED)
28 		return -EINVAL;
29 
30 	return regmap_update_bits(led->chip->regmap,
31 				  LP5860_REG_DOT_ONOFF_START + offset, mask,
32 				  enable ? LP5860_DOT_ALL_ON : LP5860_DOT_ALL_OFF);
33 }
34 
lp5860_set_mc_brightness(struct led_classdev * cdev,enum led_brightness brightness)35 static int lp5860_set_mc_brightness(struct led_classdev *cdev,
36 				    enum led_brightness brightness)
37 {
38 	struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
39 	struct lp5860_led *led = mcled_cdev_to_led(mc_cdev);
40 
41 	led_mc_calc_color_components(mc_cdev, brightness);
42 
43 	guard(mutex)(&led->chip->lock);
44 	for (int i = 0; i < led->mc_cdev.num_colors; i++) {
45 		unsigned int channel = mc_cdev->subled_info[i].channel;
46 		unsigned int led_brightness = mc_cdev->subled_info[i].brightness;
47 		int ret;
48 
49 		ret = lp5860_set_dot_onoff(led, channel, !!led_brightness);
50 		if (ret)
51 			return ret;
52 
53 		ret = regmap_write(led->chip->regmap,
54 				   LP5860_REG_PWM_BRI_START + channel, led_brightness);
55 		if (ret)
56 			return ret;
57 	}
58 
59 	return 0;
60 }
61 
lp5860_chip_enable(struct lp5860 * lp,bool enable)62 static int lp5860_chip_enable(struct lp5860 *lp, bool enable)
63 {
64 	guard(mutex)(&lp->lock);
65 	return regmap_write(lp->regmap, LP5860_REG_CHIP_EN, enable);
66 }
67 
lp5860_led_init(struct lp5860_led * led,struct fwnode_handle * fwnode,unsigned int channel)68 static int lp5860_led_init(struct lp5860_led *led, struct fwnode_handle *fwnode,
69 			   unsigned int channel)
70 {
71 	enum led_default_state default_state;
72 	unsigned int brightness;
73 	int ret;
74 
75 	guard(mutex)(&led->chip->lock);
76 	ret = regmap_read(led->chip->regmap, LP5860_REG_PWM_BRI_START + channel, &brightness);
77 	if (ret)
78 		return ret;
79 
80 	default_state = led_init_default_state_get(fwnode);
81 
82 	switch (default_state) {
83 	case LEDS_DEFSTATE_ON:
84 		led->brightness = LP5860_MAX_BRIGHTNESS;
85 		break;
86 	case LEDS_DEFSTATE_KEEP:
87 		led->brightness = min(brightness, LP5860_MAX_BRIGHTNESS);
88 		break;
89 	default:
90 		led->brightness = 0;
91 		break;
92 	}
93 
94 	return 0;
95 }
96 
lp5860_iterate_subleds(struct lp5860_led * led,struct led_init_data * init_data)97 static int lp5860_iterate_subleds(struct lp5860_led *led, struct led_init_data *init_data)
98 {
99 	struct fwnode_handle *led_node = NULL;
100 	struct fwnode_handle *multi_led = init_data->fwnode;
101 	int subled = 0;
102 
103 	fwnode_for_each_child_node(multi_led, led_node) {
104 		u32 channel;
105 		u32 color_index;
106 		int ret;
107 
108 		ret = fwnode_property_read_u32(led_node, "color", &color_index);
109 		if (ret) {
110 			dev_err_probe(led->chip->dev, ret,
111 				      "%pfwP: Cannot read 'color' property. Skipping.\n", led_node);
112 			fwnode_handle_put(led_node);
113 			return ret;
114 		}
115 
116 		ret = fwnode_property_read_u32(led_node, "reg", &channel);
117 		if (ret < 0 || channel > LP5860_MAX_LED) {
118 			dev_err_probe(led->chip->dev, ret,
119 				      "%pfwP: 'reg' property is missing. Skipping.\n", led_node);
120 			fwnode_handle_put(led_node);
121 			return ret;
122 		}
123 
124 		led->mc_cdev.subled_info[subled].color_index = color_index;
125 		led->mc_cdev.subled_info[subled].channel = channel;
126 		ret = lp5860_led_init(led, init_data->fwnode, channel);
127 		if (ret) {
128 			dev_err_probe(led->chip->dev, ret,
129 				      "%pfwP: Failed to init LED\n", led_node);
130 			fwnode_handle_put(led_node);
131 			return ret;
132 		}
133 
134 		subled++;
135 	}
136 
137 	return 0;
138 }
139 
lp5860_init_dt(struct lp5860 * lp)140 static int lp5860_init_dt(struct lp5860 *lp)
141 {
142 	struct led_init_data init_data = {};
143 	struct led_classdev *led_cdev;
144 	struct mc_subled *mc_led_info;
145 	struct lp5860_led *led;
146 	int led_index = 0;
147 	int chan;
148 	int ret;
149 
150 	device_for_each_child_node_scoped(lp->dev, multi_led) {
151 		led = &lp->leds[led_index];
152 
153 		init_data.fwnode = multi_led;
154 
155 		/* Count the number of channels in this multi_led */
156 		chan = fwnode_get_child_node_count(multi_led);
157 		if (!chan || chan > LP5860_MAX_LED_CHANNELS)
158 			return -EINVAL;
159 
160 		led->mc_cdev.num_colors = chan;
161 
162 		mc_led_info = devm_kcalloc(lp->dev, chan, sizeof(*mc_led_info), GFP_KERNEL);
163 		if (!mc_led_info)
164 			return -ENOMEM;
165 
166 		led->chip = lp;
167 		led->mc_cdev.subled_info = mc_led_info;
168 		led_cdev = &led->mc_cdev.led_cdev;
169 		led_cdev->max_brightness = LP5860_MAX_BRIGHTNESS;
170 		led_cdev->brightness_set_blocking = lp5860_set_mc_brightness;
171 
172 		ret = lp5860_iterate_subleds(led, &init_data);
173 		if (ret)
174 			continue;
175 
176 		ret = lp5860_set_mc_brightness(&led->mc_cdev.led_cdev, led->brightness);
177 		if (ret)
178 			return dev_err_probe(lp->dev, ret, "%pfwP: Failed to set Multi-Color brightness\n",
179 					     multi_led);
180 
181 		ret = devm_led_classdev_multicolor_register_ext(lp->dev, &led->mc_cdev, &init_data);
182 		if (ret)
183 			return dev_err_probe(lp->dev, ret, "%pfwP: Failed to register Multi-Color LEDs\n",
184 					     multi_led);
185 		led_index++;
186 	}
187 
188 	return 0;
189 }
190 
lp5860_device_init(struct device * dev)191 int lp5860_device_init(struct device *dev)
192 {
193 	struct lp5860 *lp = dev_get_drvdata(dev);
194 	int ret;
195 
196 	ret = lp5860_chip_enable(lp, LP5860_CHIP_ENABLE);
197 	if (ret)
198 		return ret;
199 
200 	/*
201 	 * Set to 8-bit PWM data without VSYNC.
202 	 * Data is sent out for display instantly after received.
203 	 */
204 	mutex_lock(&lp->lock);
205 	ret = regmap_update_bits(lp->regmap, LP5860_REG_DEV_INITIAL, LP5860_MODE_MASK,
206 				 LP5860_MODE_1 << LP5860_MODE_SHIFT);
207 	mutex_unlock(&lp->lock);
208 	if (ret)
209 		goto err_disable;
210 
211 	ret = lp5860_init_dt(lp);
212 	if (ret)
213 		goto err_disable;
214 
215 	return 0;
216 
217 err_disable:
218 	lp5860_chip_enable(lp, LP5860_CHIP_DISABLE);
219 	return ret;
220 }
221 EXPORT_SYMBOL_GPL(lp5860_device_init);
222 
lp5860_device_remove(struct device * dev)223 void lp5860_device_remove(struct device *dev)
224 {
225 	struct lp5860 *lp = dev_get_drvdata(dev);
226 
227 	lp5860_chip_enable(lp, LP5860_CHIP_DISABLE);
228 }
229 EXPORT_SYMBOL_GPL(lp5860_device_remove);
230 
231 MODULE_AUTHOR("Steffen Trumtrar <kernel@pengutronix.de>");
232 MODULE_DESCRIPTION("TI LP5860 RGB LED core driver");
233 MODULE_LICENSE("GPL");
234