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

leds: pca995x: Add support for PCA995X chips

The PCA995x chips are I2C controlled LED drivers. Each chip has
up to 16 outputs, each one with an individual 8-bit resolution
PWM for brightness control.

Signed-off-by: Isai Gaspar <isaiezequiel.gaspar@nxp.com>
Signed-off-by: Marek Vasut <marex@denx.de> # Basically rewrite the driver
Link: https://lore.kernel.org/r/20230713163516.21644-2-marex@denx.de
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Isai Gaspar and committed by
Lee Jones
ee4e80b2 a4789089

+214
+9
drivers/leds/Kconfig
··· 521 521 LED driver chip accessed via the I2C bus. Supported 522 522 devices include PCA9633 and PCA9634 523 523 524 + config LEDS_PCA995X 525 + tristate "LED Support for PCA995x I2C chips" 526 + depends on LEDS_CLASS 527 + depends on I2C 528 + help 529 + This option enables support for LEDs connected to PCA995x 530 + LED driver chips accessed via the I2C bus. Supported 531 + devices include PCA9955BTW, PCA9952TW and PCA9955TW. 532 + 524 533 config LEDS_WM831X_STATUS 525 534 tristate "LED support for status LEDs on WM831x PMICs" 526 535 depends on LEDS_CLASS
+1
drivers/leds/Makefile
··· 72 72 obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o 73 73 obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o 74 74 obj-$(CONFIG_LEDS_PCA963X) += leds-pca963x.o 75 + obj-$(CONFIG_LEDS_PCA995X) += leds-pca995x.o 75 76 obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o 76 77 obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o 77 78 obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
+204
drivers/leds/leds-pca995x.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * LED driver for PCA995x I2C LED drivers 4 + * 5 + * Copyright 2011 bct electronic GmbH 6 + * Copyright 2013 Qtechnology/AS 7 + * Copyright 2022 NXP 8 + * Copyright 2023 Marek Vasut 9 + */ 10 + 11 + #include <linux/i2c.h> 12 + #include <linux/leds.h> 13 + #include <linux/module.h> 14 + #include <linux/mod_devicetable.h> 15 + #include <linux/property.h> 16 + #include <linux/regmap.h> 17 + 18 + /* Register definition */ 19 + #define PCA995X_MODE1 0x00 20 + #define PCA995X_MODE2 0x01 21 + #define PCA995X_LEDOUT0 0x02 22 + #define PCA9955B_PWM0 0x08 23 + #define PCA9952_PWM0 0x0A 24 + #define PCA9952_IREFALL 0x43 25 + #define PCA9955B_IREFALL 0x45 26 + 27 + /* Auto-increment disabled. Normal mode */ 28 + #define PCA995X_MODE1_CFG 0x00 29 + 30 + /* LED select registers determine the source that drives LED outputs */ 31 + #define PCA995X_LED_OFF 0x0 32 + #define PCA995X_LED_ON 0x1 33 + #define PCA995X_LED_PWM_MODE 0x2 34 + #define PCA995X_LDRX_MASK 0x3 35 + #define PCA995X_LDRX_BITS 2 36 + 37 + #define PCA995X_MAX_OUTPUTS 16 38 + #define PCA995X_OUTPUTS_PER_REG 4 39 + 40 + #define PCA995X_IREFALL_FULL_CFG 0xFF 41 + #define PCA995X_IREFALL_HALF_CFG (PCA995X_IREFALL_FULL_CFG / 2) 42 + 43 + #define PCA995X_TYPE_NON_B 0 44 + #define PCA995X_TYPE_B 1 45 + 46 + #define ldev_to_led(c) container_of(c, struct pca995x_led, ldev) 47 + 48 + struct pca995x_led { 49 + unsigned int led_no; 50 + struct led_classdev ldev; 51 + struct pca995x_chip *chip; 52 + }; 53 + 54 + struct pca995x_chip { 55 + struct regmap *regmap; 56 + struct pca995x_led leds[PCA995X_MAX_OUTPUTS]; 57 + int btype; 58 + }; 59 + 60 + static int pca995x_brightness_set(struct led_classdev *led_cdev, 61 + enum led_brightness brightness) 62 + { 63 + struct pca995x_led *led = ldev_to_led(led_cdev); 64 + struct pca995x_chip *chip = led->chip; 65 + u8 ledout_addr, pwmout_addr; 66 + int shift, ret; 67 + 68 + pwmout_addr = (chip->btype ? PCA9955B_PWM0 : PCA9952_PWM0) + led->led_no; 69 + ledout_addr = PCA995X_LEDOUT0 + (led->led_no / PCA995X_OUTPUTS_PER_REG); 70 + shift = PCA995X_LDRX_BITS * (led->led_no % PCA995X_OUTPUTS_PER_REG); 71 + 72 + switch (brightness) { 73 + case LED_FULL: 74 + return regmap_update_bits(chip->regmap, ledout_addr, 75 + PCA995X_LDRX_MASK << shift, 76 + PCA995X_LED_ON << shift); 77 + case LED_OFF: 78 + return regmap_update_bits(chip->regmap, ledout_addr, 79 + PCA995X_LDRX_MASK << shift, 0); 80 + default: 81 + /* Adjust brightness as per user input by changing individual PWM */ 82 + ret = regmap_write(chip->regmap, pwmout_addr, brightness); 83 + if (ret) 84 + return ret; 85 + 86 + /* 87 + * Change LDRx configuration to individual brightness via PWM. 88 + * LED will stop blinking if it's doing so. 89 + */ 90 + return regmap_update_bits(chip->regmap, ledout_addr, 91 + PCA995X_LDRX_MASK << shift, 92 + PCA995X_LED_PWM_MODE << shift); 93 + } 94 + } 95 + 96 + static const struct regmap_config pca995x_regmap = { 97 + .reg_bits = 8, 98 + .val_bits = 8, 99 + .max_register = 0x49, 100 + }; 101 + 102 + static int pca995x_probe(struct i2c_client *client) 103 + { 104 + struct fwnode_handle *led_fwnodes[PCA995X_MAX_OUTPUTS] = { 0 }; 105 + struct fwnode_handle *np, *child; 106 + struct device *dev = &client->dev; 107 + struct pca995x_chip *chip; 108 + struct pca995x_led *led; 109 + int i, btype, reg, ret; 110 + 111 + btype = (unsigned long)device_get_match_data(&client->dev); 112 + 113 + np = dev_fwnode(dev); 114 + if (!np) 115 + return -ENODEV; 116 + 117 + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); 118 + if (!chip) 119 + return -ENOMEM; 120 + 121 + chip->btype = btype; 122 + chip->regmap = devm_regmap_init_i2c(client, &pca995x_regmap); 123 + if (IS_ERR(chip->regmap)) 124 + return PTR_ERR(chip->regmap); 125 + 126 + i2c_set_clientdata(client, chip); 127 + 128 + fwnode_for_each_available_child_node(np, child) { 129 + ret = fwnode_property_read_u32(child, "reg", &reg); 130 + if (ret) { 131 + fwnode_handle_put(child); 132 + return ret; 133 + } 134 + 135 + if (reg < 0 || reg >= PCA995X_MAX_OUTPUTS || led_fwnodes[reg]) { 136 + fwnode_handle_put(child); 137 + return -EINVAL; 138 + } 139 + 140 + led = &chip->leds[reg]; 141 + led_fwnodes[reg] = child; 142 + led->chip = chip; 143 + led->led_no = reg; 144 + led->ldev.brightness_set_blocking = pca995x_brightness_set; 145 + led->ldev.max_brightness = 255; 146 + } 147 + 148 + for (i = 0; i < PCA995X_MAX_OUTPUTS; i++) { 149 + struct led_init_data init_data = {}; 150 + 151 + if (!led_fwnodes[i]) 152 + continue; 153 + 154 + init_data.fwnode = led_fwnodes[i]; 155 + 156 + ret = devm_led_classdev_register_ext(dev, 157 + &chip->leds[i].ldev, 158 + &init_data); 159 + if (ret < 0) { 160 + fwnode_handle_put(child); 161 + return dev_err_probe(dev, ret, 162 + "Could not register LED %s\n", 163 + chip->leds[i].ldev.name); 164 + } 165 + } 166 + 167 + /* Disable LED all-call address and set normal mode */ 168 + ret = regmap_write(chip->regmap, PCA995X_MODE1, PCA995X_MODE1_CFG); 169 + if (ret) 170 + return ret; 171 + 172 + /* IREF Output current value for all LEDn outputs */ 173 + return regmap_write(chip->regmap, 174 + btype ? PCA9955B_IREFALL : PCA9952_IREFALL, 175 + PCA995X_IREFALL_HALF_CFG); 176 + } 177 + 178 + static const struct i2c_device_id pca995x_id[] = { 179 + { "pca9952", .driver_data = (kernel_ulong_t)PCA995X_TYPE_NON_B }, 180 + { "pca9955b", .driver_data = (kernel_ulong_t)PCA995X_TYPE_B }, 181 + {} 182 + }; 183 + MODULE_DEVICE_TABLE(i2c, pca995x_id); 184 + 185 + static const struct of_device_id pca995x_of_match[] = { 186 + { .compatible = "nxp,pca9952", .data = (void *)PCA995X_TYPE_NON_B }, 187 + { .compatible = "nxp,pca9955b", .data = (void *)PCA995X_TYPE_B }, 188 + {}, 189 + }; 190 + MODULE_DEVICE_TABLE(i2c, pca995x_of_match); 191 + 192 + static struct i2c_driver pca995x_driver = { 193 + .driver = { 194 + .name = "leds-pca995x", 195 + .of_match_table = pca995x_of_match, 196 + }, 197 + .probe = pca995x_probe, 198 + .id_table = pca995x_id, 199 + }; 200 + module_i2c_driver(pca995x_driver); 201 + 202 + MODULE_AUTHOR("Isai Gaspar <isaiezequiel.gaspar@nxp.com>"); 203 + MODULE_DESCRIPTION("PCA995x LED driver"); 204 + MODULE_LICENSE("GPL");