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