Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

leds: max77705: Add LEDs support

This adds basic support for LEDs for the max77705 PMIC.

Signed-off-by: Dzmitry Sankouski <dsankouski@gmail.com>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Link: https://lore.kernel.org/r/20250123-starqltechn_integration_upstream-v17-7-8b06685b6612@gmail.com
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Dzmitry Sankouski and committed by
Lee Jones
aebb5fc9 eb79f3a5

+302
+1
MAINTAINERS
··· 14316 14316 F: Documentation/devicetree/bindings/*/maxim,max77705*.yaml 14317 14317 F: Documentation/devicetree/bindings/*/maxim,max77843.yaml 14318 14318 F: Documentation/devicetree/bindings/clock/maxim,max77686.txt 14319 + F: drivers/leds/leds-max77705.c 14319 14320 F: drivers/*/*max77843.c 14320 14321 F: drivers/*/max14577*.c 14321 14322 F: drivers/*/max77686*.c
+8
drivers/leds/Kconfig
··· 778 778 help 779 779 LEDs driver for MAX77650 family of PMICs from Maxim Integrated. 780 780 781 + config LEDS_MAX77705 782 + tristate "LED support for Maxim MAX77705 PMIC" 783 + depends on MFD_MAX77705 784 + depends on LEDS_CLASS 785 + depends on LEDS_CLASS_MULTICOLOR 786 + help 787 + LED driver for MAX77705 PMIC from Maxim Integrated. 788 + 781 789 config LEDS_MAX8997 782 790 tristate "LED support for MAX8997 PMIC" 783 791 depends on LEDS_CLASS && MFD_MAX8997
+1
drivers/leds/Makefile
··· 61 61 obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o 62 62 obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o 63 63 obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o 64 + obj-$(CONFIG_LEDS_MAX77705) += leds-max77705.o 64 65 obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o 65 66 obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o 66 67 obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o
+275
drivers/leds/leds-max77705.c
··· 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 + 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 + 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 + 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 + 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 + 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", &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 + 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", &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 + 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");
+17
include/linux/mfd/max77705-private.h
··· 33 33 #define MAX77705_SYSTEM_IRQ_SYSOVLO_INT BIT(5) 34 34 #define MAX77705_SYSTEM_IRQ_TSHDN_INT BIT(6) 35 35 #define MAX77705_SYSTEM_IRQ_TM_INT BIT(7) 36 + /* MAX77705_RGBLED_REG_LEDEN register */ 37 + #define MAX77705_RGBLED_EN_WIDTH 2 38 + /* MAX77705_RGBLED_REG_LEDBLNK register */ 39 + #define MAX77705_RGB_DELAY_100_STEP_LIM 500 40 + #define MAX77705_RGB_DELAY_100_STEP_COUNT 4 41 + #define MAX77705_RGB_DELAY_100_STEP 100 42 + #define MAX77705_RGB_DELAY_250_STEP_LIM 3250 43 + #define MAX77705_RGB_DELAY_250_STEP 250 44 + #define MAX77705_RGB_DELAY_500_STEP 500 45 + #define MAX77705_RGB_DELAY_500_STEP_COUNT 10 46 + #define MAX77705_RGB_DELAY_500_STEP_LIM 5000 47 + #define MAX77705_RGB_DELAY_1000_STEP_LIM 8000 48 + #define MAX77705_RGB_DELAY_1000_STEP_COUNT 13 49 + #define MAX77705_RGB_DELAY_1000_STEP 1000 50 + #define MAX77705_RGB_DELAY_2000_STEP 2000 51 + #define MAX77705_RGB_DELAY_2000_STEP_COUNT 13 52 + #define MAX77705_RGB_DELAY_2000_STEP_LIM 12000 36 53 37 54 enum max77705_hw_rev { 38 55 MAX77705_PASS1 = 1,