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", ®); 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", ®); 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