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

leds: lp8864: Add support for Texas Instruments LP8864, LP8864S, LP8866 LED-backlights

Add driver for TI LP8864, LP8864S, LP8866 4/6 channel LED-backlight drivers
with I2C interface.

Link: https://www.ti.com/lit/gpn/lp8864-q1
Link: https://www.ti.com/lit/gpn/lp8864s-q1
Link: https://www.ti.com/lit/gpn/lp8866-q1
Link: https://www.ti.com/lit/gpn/lp8866s-q1
Signed-off-by: Alexander Sverdlin <alexander.sverdlin@siemens.com>
Link: https://lore.kernel.org/r/20241218210829.73191-3-alexander.sverdlin@siemens.com
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Alexander Sverdlin and committed by
Lee Jones
e14d8792 efd435a8

+316
+7
MAINTAINERS
··· 23240 23240 F: Documentation/devicetree/bindings/iio/dac/ti,dac7612.yaml 23241 23241 F: drivers/iio/dac/ti-dac7612.c 23242 23242 23243 + TEXAS INSTRUMENTS' LB8864 LED BACKLIGHT DRIVER 23244 + M: Alexander Sverdlin <alexander.sverdlin@siemens.com> 23245 + L: linux-leds@vger.kernel.org 23246 + S: Maintained 23247 + F: Documentation/devicetree/bindings/leds/backlight/ti,lp8864.yaml 23248 + F: drivers/leds/leds-lp8864.c 23249 + 23243 23250 TEXAS INSTRUMENTS' SYSTEM CONTROL INTERFACE (TISCI) PROTOCOL DRIVER 23244 23251 M: Nishanth Menon <nm@ti.com> 23245 23252 M: Tero Kristo <kristo@kernel.org>
+12
drivers/leds/Kconfig
··· 513 513 on the LP8860 4 channel LED driver using the I2C communication 514 514 bus. 515 515 516 + config LEDS_LP8864 517 + tristate "LED support for the TI LP8864/LP8866 4/6 channel LED drivers" 518 + depends on LEDS_CLASS && I2C && OF 519 + select REGMAP_I2C 520 + help 521 + If you say yes here you get support for the TI LP8864-Q1, 522 + LP8864S-Q1, LP8866-Q1, LP8866S-Q1 4/6 channel LED backlight 523 + drivers with I2C interface. 524 + 525 + To compile this driver as a module, choose M here: the 526 + module will be called leds-lp8864. 527 + 516 528 config LEDS_CLEVO_MAIL 517 529 tristate "Mail LED on Clevo notebook" 518 530 depends on LEDS_CLASS && BROKEN
+1
drivers/leds/Makefile
··· 57 57 obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o 58 58 obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o 59 59 obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o 60 + obj-$(CONFIG_LEDS_LP8864) += leds-lp8864.o 60 61 obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o 61 62 obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o 62 63 obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o
+296
drivers/leds/leds-lp8864.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * TI LP8864/LP8866 4/6 Channel LED Driver 4 + * 5 + * Copyright (C) 2024 Siemens AG 6 + * 7 + * Based on LP8860 driver by Dan Murphy <dmurphy@ti.com> 8 + */ 9 + 10 + #include <linux/gpio/consumer.h> 11 + #include <linux/i2c.h> 12 + #include <linux/init.h> 13 + #include <linux/leds.h> 14 + #include <linux/module.h> 15 + #include <linux/mutex.h> 16 + #include <linux/of.h> 17 + #include <linux/regmap.h> 18 + #include <linux/regulator/consumer.h> 19 + #include <linux/slab.h> 20 + 21 + #define LP8864_BRT_CONTROL 0x00 22 + #define LP8864_USER_CONFIG1 0x04 23 + #define LP8864_BRT_MODE_MASK GENMASK(9, 8) 24 + #define LP8864_BRT_MODE_REG BIT(9) /* Brightness control by DISPLAY_BRT reg */ 25 + #define LP8864_SUPPLY_STATUS 0x0e 26 + #define LP8864_BOOST_STATUS 0x10 27 + #define LP8864_LED_STATUS 0x12 28 + #define LP8864_LED_STATUS_WR_MASK GENMASK(14, 9) /* Writeable bits in the LED_STATUS reg */ 29 + 30 + /* Textual meaning for status bits, starting from bit 1 */ 31 + static const char *const lp8864_supply_status_msg[] = { 32 + "Vin under-voltage fault", 33 + "Vin over-voltage fault", 34 + "Vdd under-voltage fault", 35 + "Vin over-current fault", 36 + "Missing charge pump fault", 37 + "Charge pump fault", 38 + "Missing boost sync fault", 39 + "CRC error fault ", 40 + }; 41 + 42 + /* Textual meaning for status bits, starting from bit 1 */ 43 + static const char *const lp8864_boost_status_msg[] = { 44 + "Boost OVP low fault", 45 + "Boost OVP high fault", 46 + "Boost over-current fault", 47 + "Missing boost FSET resistor fault", 48 + "Missing MODE SEL resistor fault", 49 + "Missing LED resistor fault", 50 + "ISET resistor short to ground fault", 51 + "Thermal shutdown fault", 52 + }; 53 + 54 + /* Textual meaning for every register bit */ 55 + static const char *const lp8864_led_status_msg[] = { 56 + "LED 1 fault", 57 + "LED 2 fault", 58 + "LED 3 fault", 59 + "LED 4 fault", 60 + "LED 5 fault", 61 + "LED 6 fault", 62 + "LED open fault", 63 + "LED internal short fault", 64 + "LED short to GND fault", 65 + NULL, NULL, NULL, 66 + "Invalid string configuration fault", 67 + NULL, 68 + "I2C time out fault", 69 + }; 70 + 71 + /** 72 + * struct lp8864_led 73 + * @client: Pointer to the I2C client 74 + * @led_dev: led class device pointer 75 + * @regmap: Devices register map 76 + * @led_status_mask: Helps to report LED fault only once 77 + */ 78 + struct lp8864_led { 79 + struct i2c_client *client; 80 + struct led_classdev led_dev; 81 + struct regmap *regmap; 82 + u16 led_status_mask; 83 + }; 84 + 85 + static int lp8864_fault_check(struct lp8864_led *led) 86 + { 87 + int ret, i; 88 + unsigned int val; 89 + 90 + ret = regmap_read(led->regmap, LP8864_SUPPLY_STATUS, &val); 91 + if (ret) 92 + goto err; 93 + 94 + /* Odd bits are status bits, even bits are clear bits */ 95 + for (i = 0; i < ARRAY_SIZE(lp8864_supply_status_msg); i++) 96 + if (val & BIT(i * 2 + 1)) 97 + dev_warn(&led->client->dev, "%s\n", lp8864_supply_status_msg[i]); 98 + 99 + /* 100 + * Clear bits have an index preceding the corresponding Status bits; 101 + * both have to be written "1" simultaneously to clear the corresponding 102 + * Status bit. 103 + */ 104 + if (val) 105 + ret = regmap_write(led->regmap, LP8864_SUPPLY_STATUS, val >> 1 | val); 106 + if (ret) 107 + goto err; 108 + 109 + ret = regmap_read(led->regmap, LP8864_BOOST_STATUS, &val); 110 + if (ret) 111 + goto err; 112 + 113 + /* Odd bits are status bits, even bits are clear bits */ 114 + for (i = 0; i < ARRAY_SIZE(lp8864_boost_status_msg); i++) 115 + if (val & BIT(i * 2 + 1)) 116 + dev_warn(&led->client->dev, "%s\n", lp8864_boost_status_msg[i]); 117 + 118 + if (val) 119 + ret = regmap_write(led->regmap, LP8864_BOOST_STATUS, val >> 1 | val); 120 + if (ret) 121 + goto err; 122 + 123 + ret = regmap_read(led->regmap, LP8864_LED_STATUS, &val); 124 + if (ret) 125 + goto err; 126 + 127 + /* 128 + * Clear already reported faults that maintain their value until device 129 + * power-down 130 + */ 131 + val &= ~led->led_status_mask; 132 + 133 + for (i = 0; i < ARRAY_SIZE(lp8864_led_status_msg); i++) 134 + if (lp8864_led_status_msg[i] && val & BIT(i)) 135 + dev_warn(&led->client->dev, "%s\n", lp8864_led_status_msg[i]); 136 + 137 + /* 138 + * Mark those which maintain their value until device power-down as 139 + * "already reported" 140 + */ 141 + led->led_status_mask |= val & ~LP8864_LED_STATUS_WR_MASK; 142 + 143 + /* 144 + * Only bits 14, 12, 10 have to be cleared here, but others are RO, 145 + * we don't care what we write to them. 146 + */ 147 + if (val & LP8864_LED_STATUS_WR_MASK) 148 + ret = regmap_write(led->regmap, LP8864_LED_STATUS, val >> 1 | val); 149 + if (ret) 150 + goto err; 151 + 152 + return 0; 153 + 154 + err: 155 + dev_err(&led->client->dev, "Failed to read/clear faults (%pe)\n", ERR_PTR(ret)); 156 + 157 + return ret; 158 + } 159 + 160 + static int lp8864_brightness_set(struct led_classdev *led_cdev, 161 + enum led_brightness brt_val) 162 + { 163 + struct lp8864_led *led = container_of(led_cdev, struct lp8864_led, led_dev); 164 + /* Scale 0..LED_FULL into 16-bit HW brightness */ 165 + unsigned int val = brt_val * 0xffff / LED_FULL; 166 + int ret; 167 + 168 + ret = lp8864_fault_check(led); 169 + if (ret) 170 + return ret; 171 + 172 + ret = regmap_write(led->regmap, LP8864_BRT_CONTROL, val); 173 + if (ret) 174 + dev_err(&led->client->dev, "Failed to write brightness value\n"); 175 + 176 + return ret; 177 + } 178 + 179 + static enum led_brightness lp8864_brightness_get(struct led_classdev *led_cdev) 180 + { 181 + struct lp8864_led *led = container_of(led_cdev, struct lp8864_led, led_dev); 182 + unsigned int val; 183 + int ret; 184 + 185 + ret = regmap_read(led->regmap, LP8864_BRT_CONTROL, &val); 186 + if (ret) { 187 + dev_err(&led->client->dev, "Failed to read brightness value\n"); 188 + return ret; 189 + } 190 + 191 + /* Scale 16-bit HW brightness into 0..LED_FULL */ 192 + return val * LED_FULL / 0xffff; 193 + } 194 + 195 + static const struct regmap_config lp8864_regmap_config = { 196 + .reg_bits = 8, 197 + .val_bits = 16, 198 + .val_format_endian = REGMAP_ENDIAN_LITTLE, 199 + }; 200 + 201 + static void lp8864_disable_gpio(void *data) 202 + { 203 + struct gpio_desc *gpio = data; 204 + 205 + gpiod_set_value(gpio, 0); 206 + } 207 + 208 + static int lp8864_probe(struct i2c_client *client) 209 + { 210 + int ret; 211 + struct lp8864_led *led; 212 + struct device_node *np = dev_of_node(&client->dev); 213 + struct device_node *child_node; 214 + struct led_init_data init_data = {}; 215 + struct gpio_desc *enable_gpio; 216 + 217 + led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL); 218 + if (!led) 219 + return -ENOMEM; 220 + 221 + child_node = of_get_next_available_child(np, NULL); 222 + if (!child_node) { 223 + dev_err(&client->dev, "No LED function defined\n"); 224 + return -EINVAL; 225 + } 226 + 227 + ret = devm_regulator_get_enable_optional(&client->dev, "vled"); 228 + if (ret && ret != -ENODEV) 229 + return dev_err_probe(&client->dev, ret, "Failed to enable vled regulator\n"); 230 + 231 + enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_HIGH); 232 + if (IS_ERR(enable_gpio)) 233 + return dev_err_probe(&client->dev, PTR_ERR(enable_gpio), 234 + "Failed to get enable GPIO\n"); 235 + 236 + ret = devm_add_action_or_reset(&client->dev, lp8864_disable_gpio, enable_gpio); 237 + if (ret) 238 + return ret; 239 + 240 + led->client = client; 241 + led->led_dev.brightness_set_blocking = lp8864_brightness_set; 242 + led->led_dev.brightness_get = lp8864_brightness_get; 243 + 244 + led->regmap = devm_regmap_init_i2c(client, &lp8864_regmap_config); 245 + if (IS_ERR(led->regmap)) 246 + return dev_err_probe(&client->dev, PTR_ERR(led->regmap), 247 + "Failed to allocate regmap\n"); 248 + 249 + /* Control brightness by DISPLAY_BRT register */ 250 + ret = regmap_update_bits(led->regmap, LP8864_USER_CONFIG1, LP8864_BRT_MODE_MASK, 251 + LP8864_BRT_MODE_REG); 252 + if (ret) { 253 + dev_err(&led->client->dev, "Failed to set brightness control mode\n"); 254 + return ret; 255 + } 256 + 257 + ret = lp8864_fault_check(led); 258 + if (ret) 259 + return ret; 260 + 261 + init_data.fwnode = of_fwnode_handle(child_node); 262 + init_data.devicename = "lp8864"; 263 + init_data.default_label = ":display_cluster"; 264 + 265 + ret = devm_led_classdev_register_ext(&client->dev, &led->led_dev, &init_data); 266 + if (ret) 267 + dev_err(&client->dev, "Failed to register LED device (%pe)\n", ERR_PTR(ret)); 268 + 269 + return ret; 270 + } 271 + 272 + static const struct i2c_device_id lp8864_id[] = { 273 + { "lp8864" }, 274 + {} 275 + }; 276 + MODULE_DEVICE_TABLE(i2c, lp8864_id); 277 + 278 + static const struct of_device_id of_lp8864_leds_match[] = { 279 + { .compatible = "ti,lp8864" }, 280 + {} 281 + }; 282 + MODULE_DEVICE_TABLE(of, of_lp8864_leds_match); 283 + 284 + static struct i2c_driver lp8864_driver = { 285 + .driver = { 286 + .name = "lp8864", 287 + .of_match_table = of_lp8864_leds_match, 288 + }, 289 + .probe = lp8864_probe, 290 + .id_table = lp8864_id, 291 + }; 292 + module_i2c_driver(lp8864_driver); 293 + 294 + MODULE_DESCRIPTION("Texas Instruments LP8864/LP8866 LED driver"); 295 + MODULE_AUTHOR("Alexander Sverdlin <alexander.sverdlin@siemens.com>"); 296 + MODULE_LICENSE("GPL");