xref: /linux/drivers/leds/leds-max77705.c (revision 1260ed77798502de9c98020040d2995008de10cc)
1*aebb5fc9SDzmitry Sankouski // SPDX-License-Identifier: GPL-2.0
2*aebb5fc9SDzmitry Sankouski /*
3*aebb5fc9SDzmitry Sankouski  * Based on leds-max77650 driver
4*aebb5fc9SDzmitry Sankouski  *
5*aebb5fc9SDzmitry Sankouski  * LED driver for MAXIM 77705 PMIC.
6*aebb5fc9SDzmitry Sankouski  * Copyright (C) 2025 Dzmitry Sankouski <dsankouski@gmail.org>
7*aebb5fc9SDzmitry Sankouski  */
8*aebb5fc9SDzmitry Sankouski 
9*aebb5fc9SDzmitry Sankouski #include <linux/i2c.h>
10*aebb5fc9SDzmitry Sankouski #include <linux/led-class-multicolor.h>
11*aebb5fc9SDzmitry Sankouski #include <linux/leds.h>
12*aebb5fc9SDzmitry Sankouski #include <linux/mfd/max77705-private.h>
13*aebb5fc9SDzmitry Sankouski #include <linux/module.h>
14*aebb5fc9SDzmitry Sankouski #include <linux/platform_device.h>
15*aebb5fc9SDzmitry Sankouski #include <linux/regmap.h>
16*aebb5fc9SDzmitry Sankouski 
17*aebb5fc9SDzmitry Sankouski #define MAX77705_LED_NUM_LEDS			4
18*aebb5fc9SDzmitry Sankouski #define MAX77705_LED_EN_MASK			GENMASK(1, 0)
19*aebb5fc9SDzmitry Sankouski #define MAX77705_LED_MAX_BRIGHTNESS		0xff
20*aebb5fc9SDzmitry Sankouski #define MAX77705_LED_EN_SHIFT(reg)		(reg * MAX77705_RGBLED_EN_WIDTH)
21*aebb5fc9SDzmitry Sankouski #define MAX77705_LED_REG_BRIGHTNESS(reg)	(reg + MAX77705_RGBLED_REG_LED0BRT)
22*aebb5fc9SDzmitry Sankouski 
23*aebb5fc9SDzmitry Sankouski struct max77705_led {
24*aebb5fc9SDzmitry Sankouski 	struct led_classdev cdev;
25*aebb5fc9SDzmitry Sankouski 	struct led_classdev_mc mcdev;
26*aebb5fc9SDzmitry Sankouski 	struct regmap *regmap;
27*aebb5fc9SDzmitry Sankouski 
28*aebb5fc9SDzmitry Sankouski 	struct mc_subled *subled_info;
29*aebb5fc9SDzmitry Sankouski };
30*aebb5fc9SDzmitry Sankouski 
31*aebb5fc9SDzmitry Sankouski static const struct regmap_config max77705_leds_regmap_config = {
32*aebb5fc9SDzmitry Sankouski 	.reg_base = MAX77705_RGBLED_REG_BASE,
33*aebb5fc9SDzmitry Sankouski 	.reg_bits = 8,
34*aebb5fc9SDzmitry Sankouski 	.val_bits = 8,
35*aebb5fc9SDzmitry Sankouski 	.max_register = MAX77705_LED_REG_END,
36*aebb5fc9SDzmitry Sankouski };
37*aebb5fc9SDzmitry Sankouski 
38*aebb5fc9SDzmitry Sankouski static int max77705_rgb_blink(struct led_classdev *cdev,
39*aebb5fc9SDzmitry Sankouski 				unsigned long *delay_on,
40*aebb5fc9SDzmitry Sankouski 				unsigned long *delay_off)
41*aebb5fc9SDzmitry Sankouski {
42*aebb5fc9SDzmitry Sankouski 	struct max77705_led *led = container_of(cdev, struct max77705_led, cdev);
43*aebb5fc9SDzmitry Sankouski 	int value, on_value, off_value;
44*aebb5fc9SDzmitry Sankouski 
45*aebb5fc9SDzmitry Sankouski 	if (*delay_on < MAX77705_RGB_DELAY_100_STEP)
46*aebb5fc9SDzmitry Sankouski 		on_value = 0;
47*aebb5fc9SDzmitry Sankouski 	else if (*delay_on < MAX77705_RGB_DELAY_100_STEP_LIM)
48*aebb5fc9SDzmitry Sankouski 		on_value = *delay_on / MAX77705_RGB_DELAY_100_STEP - 1;
49*aebb5fc9SDzmitry Sankouski 	else if (*delay_on < MAX77705_RGB_DELAY_250_STEP_LIM)
50*aebb5fc9SDzmitry Sankouski 		on_value = (*delay_on - MAX77705_RGB_DELAY_100_STEP_LIM) /
51*aebb5fc9SDzmitry Sankouski 				MAX77705_RGB_DELAY_250_STEP +
52*aebb5fc9SDzmitry Sankouski 				MAX77705_RGB_DELAY_100_STEP_COUNT;
53*aebb5fc9SDzmitry Sankouski 	else
54*aebb5fc9SDzmitry Sankouski 		on_value = 15;
55*aebb5fc9SDzmitry Sankouski 
56*aebb5fc9SDzmitry Sankouski 	on_value <<= 4;
57*aebb5fc9SDzmitry Sankouski 
58*aebb5fc9SDzmitry Sankouski 	if (*delay_off < 1)
59*aebb5fc9SDzmitry Sankouski 		off_value = 0;
60*aebb5fc9SDzmitry Sankouski 	else if (*delay_off < MAX77705_RGB_DELAY_500_STEP)
61*aebb5fc9SDzmitry Sankouski 		off_value = 1;
62*aebb5fc9SDzmitry Sankouski 	else if (*delay_off < MAX77705_RGB_DELAY_500_STEP_LIM)
63*aebb5fc9SDzmitry Sankouski 		off_value = *delay_off / MAX77705_RGB_DELAY_500_STEP;
64*aebb5fc9SDzmitry Sankouski 	else if (*delay_off < MAX77705_RGB_DELAY_1000_STEP_LIM)
65*aebb5fc9SDzmitry Sankouski 		off_value = (*delay_off - MAX77705_RGB_DELAY_1000_STEP_LIM) /
66*aebb5fc9SDzmitry Sankouski 				MAX77705_RGB_DELAY_1000_STEP +
67*aebb5fc9SDzmitry Sankouski 				MAX77705_RGB_DELAY_500_STEP_COUNT;
68*aebb5fc9SDzmitry Sankouski 	else if (*delay_off < MAX77705_RGB_DELAY_2000_STEP_LIM)
69*aebb5fc9SDzmitry Sankouski 		off_value = (*delay_off - MAX77705_RGB_DELAY_2000_STEP_LIM) /
70*aebb5fc9SDzmitry Sankouski 				MAX77705_RGB_DELAY_2000_STEP +
71*aebb5fc9SDzmitry Sankouski 				MAX77705_RGB_DELAY_1000_STEP_COUNT;
72*aebb5fc9SDzmitry Sankouski 	else
73*aebb5fc9SDzmitry Sankouski 		off_value = 15;
74*aebb5fc9SDzmitry Sankouski 
75*aebb5fc9SDzmitry Sankouski 	value = on_value | off_value;
76*aebb5fc9SDzmitry Sankouski 	return regmap_write(led->regmap, MAX77705_RGBLED_REG_LEDBLNK, value);
77*aebb5fc9SDzmitry Sankouski }
78*aebb5fc9SDzmitry Sankouski 
79*aebb5fc9SDzmitry Sankouski static int max77705_led_brightness_set(struct regmap *regmap, struct mc_subled *subled,
80*aebb5fc9SDzmitry Sankouski 				int num_colors)
81*aebb5fc9SDzmitry Sankouski {
82*aebb5fc9SDzmitry Sankouski 	int ret;
83*aebb5fc9SDzmitry Sankouski 
84*aebb5fc9SDzmitry Sankouski 	for (int i = 0; i < num_colors; i++) {
85*aebb5fc9SDzmitry Sankouski 		unsigned int channel, brightness;
86*aebb5fc9SDzmitry Sankouski 
87*aebb5fc9SDzmitry Sankouski 		channel = subled[i].channel;
88*aebb5fc9SDzmitry Sankouski 		brightness = subled[i].brightness;
89*aebb5fc9SDzmitry Sankouski 
90*aebb5fc9SDzmitry Sankouski 		if (brightness == LED_OFF) {
91*aebb5fc9SDzmitry Sankouski 			/* Flash OFF */
92*aebb5fc9SDzmitry Sankouski 			ret = regmap_update_bits(regmap,
93*aebb5fc9SDzmitry Sankouski 					MAX77705_RGBLED_REG_LEDEN,
94*aebb5fc9SDzmitry Sankouski 					MAX77705_LED_EN_MASK << MAX77705_LED_EN_SHIFT(channel), 0);
95*aebb5fc9SDzmitry Sankouski 		} else {
96*aebb5fc9SDzmitry Sankouski 			/* Set current */
97*aebb5fc9SDzmitry Sankouski 			ret = regmap_write(regmap, MAX77705_LED_REG_BRIGHTNESS(channel),
98*aebb5fc9SDzmitry Sankouski 					brightness);
99*aebb5fc9SDzmitry Sankouski 			if (ret < 0)
100*aebb5fc9SDzmitry Sankouski 				return ret;
101*aebb5fc9SDzmitry Sankouski 
102*aebb5fc9SDzmitry Sankouski 			ret = regmap_update_bits(regmap,
103*aebb5fc9SDzmitry Sankouski 					MAX77705_RGBLED_REG_LEDEN,
104*aebb5fc9SDzmitry Sankouski 					LED_ON << MAX77705_LED_EN_SHIFT(channel),
105*aebb5fc9SDzmitry Sankouski 					MAX77705_LED_EN_MASK << MAX77705_LED_EN_SHIFT(channel));
106*aebb5fc9SDzmitry Sankouski 		}
107*aebb5fc9SDzmitry Sankouski 	}
108*aebb5fc9SDzmitry Sankouski 
109*aebb5fc9SDzmitry Sankouski 	return ret;
110*aebb5fc9SDzmitry Sankouski }
111*aebb5fc9SDzmitry Sankouski 
112*aebb5fc9SDzmitry Sankouski static int max77705_led_brightness_set_single(struct led_classdev *cdev,
113*aebb5fc9SDzmitry Sankouski 					enum led_brightness brightness)
114*aebb5fc9SDzmitry Sankouski {
115*aebb5fc9SDzmitry Sankouski 	struct max77705_led *led = container_of(cdev, struct max77705_led, cdev);
116*aebb5fc9SDzmitry Sankouski 
117*aebb5fc9SDzmitry Sankouski 	led->subled_info->brightness = brightness;
118*aebb5fc9SDzmitry Sankouski 
119*aebb5fc9SDzmitry Sankouski 	return max77705_led_brightness_set(led->regmap, led->subled_info, 1);
120*aebb5fc9SDzmitry Sankouski }
121*aebb5fc9SDzmitry Sankouski 
122*aebb5fc9SDzmitry Sankouski static int max77705_led_brightness_set_multi(struct led_classdev *cdev,
123*aebb5fc9SDzmitry Sankouski 					enum led_brightness brightness)
124*aebb5fc9SDzmitry Sankouski {
125*aebb5fc9SDzmitry Sankouski 	struct led_classdev_mc *mcdev = lcdev_to_mccdev(cdev);
126*aebb5fc9SDzmitry Sankouski 	struct max77705_led *led = container_of(mcdev, struct max77705_led, mcdev);
127*aebb5fc9SDzmitry Sankouski 
128*aebb5fc9SDzmitry Sankouski 	led_mc_calc_color_components(mcdev, brightness);
129*aebb5fc9SDzmitry Sankouski 
130*aebb5fc9SDzmitry Sankouski 	return max77705_led_brightness_set(led->regmap, led->mcdev.subled_info, mcdev->num_colors);
131*aebb5fc9SDzmitry Sankouski }
132*aebb5fc9SDzmitry Sankouski 
133*aebb5fc9SDzmitry Sankouski static int max77705_parse_subled(struct device *dev, struct fwnode_handle *np,
134*aebb5fc9SDzmitry Sankouski 				struct mc_subled *info)
135*aebb5fc9SDzmitry Sankouski {
136*aebb5fc9SDzmitry Sankouski 	u32 color = LED_COLOR_ID_GREEN;
137*aebb5fc9SDzmitry Sankouski 	u32 reg;
138*aebb5fc9SDzmitry Sankouski 	int ret;
139*aebb5fc9SDzmitry Sankouski 
140*aebb5fc9SDzmitry Sankouski 	ret = fwnode_property_read_u32(np, "reg", &reg);
141*aebb5fc9SDzmitry Sankouski 	if (ret || !reg || reg >= MAX77705_LED_NUM_LEDS)
142*aebb5fc9SDzmitry Sankouski 		return dev_err_probe(dev, -EINVAL, "invalid \"reg\" of %pOFn\n", np);
143*aebb5fc9SDzmitry Sankouski 
144*aebb5fc9SDzmitry Sankouski 	info->channel = reg;
145*aebb5fc9SDzmitry Sankouski 
146*aebb5fc9SDzmitry Sankouski 	ret = fwnode_property_read_u32(np, "color", &color);
147*aebb5fc9SDzmitry Sankouski 	if (ret < 0 && ret != -EINVAL)
148*aebb5fc9SDzmitry Sankouski 		return dev_err_probe(dev, ret,
149*aebb5fc9SDzmitry Sankouski 				     "failed to parse \"color\" of %pOF\n", np);
150*aebb5fc9SDzmitry Sankouski 
151*aebb5fc9SDzmitry Sankouski 	info->color_index = color;
152*aebb5fc9SDzmitry Sankouski 
153*aebb5fc9SDzmitry Sankouski 	return 0;
154*aebb5fc9SDzmitry Sankouski }
155*aebb5fc9SDzmitry Sankouski 
156*aebb5fc9SDzmitry Sankouski static int max77705_add_led(struct device *dev, struct regmap *regmap, struct fwnode_handle *np)
157*aebb5fc9SDzmitry Sankouski {
158*aebb5fc9SDzmitry Sankouski 	int ret, i = 0;
159*aebb5fc9SDzmitry Sankouski 	unsigned int color, reg;
160*aebb5fc9SDzmitry Sankouski 	struct max77705_led *led;
161*aebb5fc9SDzmitry Sankouski 	struct led_classdev *cdev;
162*aebb5fc9SDzmitry Sankouski 	struct mc_subled *info;
163*aebb5fc9SDzmitry Sankouski 	struct fwnode_handle *child;
164*aebb5fc9SDzmitry Sankouski 	struct led_init_data init_data = {};
165*aebb5fc9SDzmitry Sankouski 
166*aebb5fc9SDzmitry Sankouski 	led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
167*aebb5fc9SDzmitry Sankouski 	if (!led)
168*aebb5fc9SDzmitry Sankouski 		return -ENOMEM;
169*aebb5fc9SDzmitry Sankouski 
170*aebb5fc9SDzmitry Sankouski 	ret = fwnode_property_read_u32(np, "color", &color);
171*aebb5fc9SDzmitry Sankouski 	if (ret < 0 && ret != -EINVAL)
172*aebb5fc9SDzmitry Sankouski 		return dev_err_probe(dev, ret,
173*aebb5fc9SDzmitry Sankouski 			      "failed to parse \"color\" of %pOF\n", np);
174*aebb5fc9SDzmitry Sankouski 
175*aebb5fc9SDzmitry Sankouski 	led->regmap = regmap;
176*aebb5fc9SDzmitry Sankouski 	init_data.fwnode = np;
177*aebb5fc9SDzmitry Sankouski 
178*aebb5fc9SDzmitry Sankouski 	if (color == LED_COLOR_ID_RGB) {
179*aebb5fc9SDzmitry Sankouski 		int num_channels = of_get_available_child_count(to_of_node(np));
180*aebb5fc9SDzmitry Sankouski 
181*aebb5fc9SDzmitry Sankouski 		ret = fwnode_property_read_u32(np, "reg", &reg);
182*aebb5fc9SDzmitry Sankouski 		if (ret || reg >= MAX77705_LED_NUM_LEDS)
183*aebb5fc9SDzmitry Sankouski 			ret = -EINVAL;
184*aebb5fc9SDzmitry Sankouski 
185*aebb5fc9SDzmitry Sankouski 		info = devm_kcalloc(dev, num_channels, sizeof(*info), GFP_KERNEL);
186*aebb5fc9SDzmitry Sankouski 		if (!info)
187*aebb5fc9SDzmitry Sankouski 			return -ENOMEM;
188*aebb5fc9SDzmitry Sankouski 
189*aebb5fc9SDzmitry Sankouski 		cdev = &led->mcdev.led_cdev;
190*aebb5fc9SDzmitry Sankouski 		cdev->max_brightness = MAX77705_LED_MAX_BRIGHTNESS;
191*aebb5fc9SDzmitry Sankouski 		cdev->brightness_set_blocking = max77705_led_brightness_set_multi;
192*aebb5fc9SDzmitry Sankouski 		cdev->blink_set = max77705_rgb_blink;
193*aebb5fc9SDzmitry Sankouski 
194*aebb5fc9SDzmitry Sankouski 		fwnode_for_each_available_child_node(np, child) {
195*aebb5fc9SDzmitry Sankouski 			ret = max77705_parse_subled(dev, child, &info[i]);
196*aebb5fc9SDzmitry Sankouski 			if (ret < 0)
197*aebb5fc9SDzmitry Sankouski 				return ret;
198*aebb5fc9SDzmitry Sankouski 
199*aebb5fc9SDzmitry Sankouski 			info[i].intensity = 0;
200*aebb5fc9SDzmitry Sankouski 			i++;
201*aebb5fc9SDzmitry Sankouski 		}
202*aebb5fc9SDzmitry Sankouski 
203*aebb5fc9SDzmitry Sankouski 		led->mcdev.subled_info = info;
204*aebb5fc9SDzmitry Sankouski 		led->mcdev.num_colors = num_channels;
205*aebb5fc9SDzmitry Sankouski 		led->cdev = *cdev;
206*aebb5fc9SDzmitry Sankouski 
207*aebb5fc9SDzmitry Sankouski 		ret = devm_led_classdev_multicolor_register_ext(dev, &led->mcdev, &init_data);
208*aebb5fc9SDzmitry Sankouski 		if (ret)
209*aebb5fc9SDzmitry Sankouski 			return ret;
210*aebb5fc9SDzmitry Sankouski 
211*aebb5fc9SDzmitry Sankouski 		ret = max77705_led_brightness_set_multi(&led->cdev, LED_OFF);
212*aebb5fc9SDzmitry Sankouski 		if (ret)
213*aebb5fc9SDzmitry Sankouski 			return ret;
214*aebb5fc9SDzmitry Sankouski 	} else {
215*aebb5fc9SDzmitry Sankouski 		info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
216*aebb5fc9SDzmitry Sankouski 		if (!info)
217*aebb5fc9SDzmitry Sankouski 			return -ENOMEM;
218*aebb5fc9SDzmitry Sankouski 
219*aebb5fc9SDzmitry Sankouski 		max77705_parse_subled(dev, np, info);
220*aebb5fc9SDzmitry Sankouski 
221*aebb5fc9SDzmitry Sankouski 		led->subled_info = info;
222*aebb5fc9SDzmitry Sankouski 		led->cdev.brightness_set_blocking = max77705_led_brightness_set_single;
223*aebb5fc9SDzmitry Sankouski 		led->cdev.blink_set = max77705_rgb_blink;
224*aebb5fc9SDzmitry Sankouski 		led->cdev.max_brightness = MAX77705_LED_MAX_BRIGHTNESS;
225*aebb5fc9SDzmitry Sankouski 
226*aebb5fc9SDzmitry Sankouski 		ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
227*aebb5fc9SDzmitry Sankouski 		if (ret)
228*aebb5fc9SDzmitry Sankouski 			return ret;
229*aebb5fc9SDzmitry Sankouski 
230*aebb5fc9SDzmitry Sankouski 		ret = max77705_led_brightness_set_single(&led->cdev, LED_OFF);
231*aebb5fc9SDzmitry Sankouski 		if (ret)
232*aebb5fc9SDzmitry Sankouski 			return ret;
233*aebb5fc9SDzmitry Sankouski 	}
234*aebb5fc9SDzmitry Sankouski 
235*aebb5fc9SDzmitry Sankouski 	return 0;
236*aebb5fc9SDzmitry Sankouski }
237*aebb5fc9SDzmitry Sankouski 
238*aebb5fc9SDzmitry Sankouski static int max77705_led_probe(struct platform_device *pdev)
239*aebb5fc9SDzmitry Sankouski {
240*aebb5fc9SDzmitry Sankouski 	struct device *dev = &pdev->dev;
241*aebb5fc9SDzmitry Sankouski 	struct i2c_client *i2c = to_i2c_client(pdev->dev.parent);
242*aebb5fc9SDzmitry Sankouski 	struct regmap *regmap;
243*aebb5fc9SDzmitry Sankouski 	int ret;
244*aebb5fc9SDzmitry Sankouski 
245*aebb5fc9SDzmitry Sankouski 	regmap = devm_regmap_init_i2c(i2c, &max77705_leds_regmap_config);
246*aebb5fc9SDzmitry Sankouski 	if (IS_ERR(regmap))
247*aebb5fc9SDzmitry Sankouski 		return dev_err_probe(dev, PTR_ERR(regmap), "Failed to register LEDs regmap\n");
248*aebb5fc9SDzmitry Sankouski 
249*aebb5fc9SDzmitry Sankouski 	device_for_each_child_node_scoped(dev, child) {
250*aebb5fc9SDzmitry Sankouski 		ret = max77705_add_led(dev, regmap, child);
251*aebb5fc9SDzmitry Sankouski 		if (ret)
252*aebb5fc9SDzmitry Sankouski 			return ret;
253*aebb5fc9SDzmitry Sankouski 	}
254*aebb5fc9SDzmitry Sankouski 
255*aebb5fc9SDzmitry Sankouski 	return 0;
256*aebb5fc9SDzmitry Sankouski }
257*aebb5fc9SDzmitry Sankouski 
258*aebb5fc9SDzmitry Sankouski static const struct of_device_id max77705_led_of_match[] = {
259*aebb5fc9SDzmitry Sankouski 	{ .compatible = "maxim,max77705-rgb" },
260*aebb5fc9SDzmitry Sankouski 	{ }
261*aebb5fc9SDzmitry Sankouski };
262*aebb5fc9SDzmitry Sankouski MODULE_DEVICE_TABLE(of, max77705_led_of_match);
263*aebb5fc9SDzmitry Sankouski 
264*aebb5fc9SDzmitry Sankouski static struct platform_driver max77705_led_driver = {
265*aebb5fc9SDzmitry Sankouski 	.driver = {
266*aebb5fc9SDzmitry Sankouski 		.name = "max77705-led",
267*aebb5fc9SDzmitry Sankouski 		.of_match_table = max77705_led_of_match,
268*aebb5fc9SDzmitry Sankouski 	},
269*aebb5fc9SDzmitry Sankouski 	.probe = max77705_led_probe,
270*aebb5fc9SDzmitry Sankouski };
271*aebb5fc9SDzmitry Sankouski module_platform_driver(max77705_led_driver);
272*aebb5fc9SDzmitry Sankouski 
273*aebb5fc9SDzmitry Sankouski MODULE_DESCRIPTION("Maxim MAX77705 LED driver");
274*aebb5fc9SDzmitry Sankouski MODULE_AUTHOR("Dzmitry Sankouski <dsankouski@gmail.com>");
275*aebb5fc9SDzmitry Sankouski MODULE_LICENSE("GPL");
276