From b08796735a04cf1aec71351f22c87add4ae53e3b Mon Sep 17 00:00:00 2001 From: Jan Carlo Roleda Date: Wed, 3 Dec 2025 07:03:08 +0800 Subject: [PATCH 1/3] dt-bindings: leds: LTC3208: Document LTC3208 Multidisplay LED Driver Add Documentation for LTC3208 Multidisplay LED Driver. Signed-off-by: Jan Carlo Roleda --- .../bindings/leds/leds-ltc3208.yaml | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 Documentation/devicetree/bindings/leds/leds-ltc3208.yaml diff --git a/Documentation/devicetree/bindings/leds/leds-ltc3208.yaml b/Documentation/devicetree/bindings/leds/leds-ltc3208.yaml new file mode 100644 index 00000000000000..050ab19149abd1 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/leds-ltc3208.yaml @@ -0,0 +1,165 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (c) 2025 Analog Devices, Inc. +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/leds/leds-ltc3208.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: LTC3208 Multidisplay LED Controller from Linear Technologies (Now Analog Devices). + +maintainers: + - Jan Carlo Roleda + +description: + The LTC3208 is a multidisplay LED controller that can support up to 1A to all + connected LEDs. + + The datasheet for this device can be found in + https://www.analog.com/en/products/ltc3208.html + + +properties: + compatible: + const: adi,ltc3208 + + reg: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + adi,disable-camhl-pin: + type: boolean + description: Configures whether the external CAMHL pin is disabled. + if disabled then the output pins associated with CAM will always select + the CAM register's high half-byte brightness. + + adi,select-rgb-sub-en: + type: boolean + description: + Selects whether the ENRGBS pin controls the SUB channel's output pins if set, + or RGB channel's output pins if unset. + + adi,force-cpo-level: + $ref: /schemas/types.yaml#/definitions/string + description: Forces the Charge Pump Output to a specified multiplier + enum: + - "1" # none; device CPO multiplier acts on dropout signals + - "1.5" + - "2" + default: + - "1" + + adi,disable-rgb-aux4-dropout: + type: boolean + description: Configures the RGB and AUX4 dropout signals to be disabled. + + adi,aux1-channel: + $ref: /schemas/types.yaml#/definitions/string + description: + LED Channel that the AUX1 output pin mirrors its brightness level from. + enum: [aux, main, sub, cam] + default: aux + + adi,aux2-channel: + $ref: /schemas/types.yaml#/definitions/string + description: + LED Channel that the AUX2 output pin mirrors its brightness level from. + enum: [aux, main, sub, cam] + default: aux + + adi,aux3-channel: + $ref: /schemas/types.yaml#/definitions/string + description: + LED Channel that the AUX3 output pin mirrors its brightness level from. + enum: [aux, main, sub, cam] + default: aux + + adi,aux4-channel: + $ref: /schemas/types.yaml#/definitions/string + description: + LED Channel that the AUX4 output pin mirrors its brightness level from. + enum: [aux, main, sub, cam] + default: aux + +patternProperties: + "^led@[0-7]$": + type: object + unevaluatedProperties: false + properties: + reg: + description: + LED Channel Number. each channel maps to a specific channel group used + to configure the brightness level of the output pins corresponding to + the channel. + enum: + - 0 # Main Channel (8-bit brightness) + - 1 # Sub Channel (8-bit brightness) + - 2 # AUX Channel (4-bit brightness) + - 3 # Camera Channel, Low-side byte (4-bit brightness) + - 4 # Camera Channel, High-side byte (4-bit brightness) + - 5 # Red Channel (4-bit brightness) + - 6 # Blue Channel (4-bit brightness) + - 7 # Green Channel (4-bit brightness) + required: + - reg + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + led-controller@1b { + compatible = "adi,ltc3208"; + reg = <0x1b>; + #address-cells = <1>; + #size-cells = <0>; + adi,disable-camhl-pin; + adi,select-rgb-sub-en; + adi,force-cpo-level = "1"; + adi,disable-rgb-aux4-dropout; + + led@0 { + reg = <0>; + }; + + led@1 { + reg = <1>; + }; + + led@2 { + reg = <2>; + }; + + led@3 { + reg = <3>; + }; + + led@4 { + reg = <4>; + }; + + led@5 { + reg = <5>; + }; + + led@6 { + reg = <6>; + }; + + led@7 { + reg = <7>; + }; + }; + }; From 475670c4dbdc7cb9fe6c185fb4f0d9dc391c50b0 Mon Sep 17 00:00:00 2001 From: Jan Carlo Roleda Date: Wed, 3 Dec 2025 07:46:26 +0800 Subject: [PATCH 2/3] leds: ltc3208: add driver Kernel driver implementation for LTC3208 Multidisplay LED Driver Signed-off-by: Jan Carlo Roleda --- drivers/leds/Kconfig | 11 ++ drivers/leds/Makefile | 1 + drivers/leds/leds-ltc3208.c | 350 ++++++++++++++++++++++++++++++++++++ 3 files changed, 362 insertions(+) create mode 100644 drivers/leds/leds-ltc3208.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index b784bb74a8378a..2ef0fd2ba4e714 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -959,6 +959,17 @@ config LEDS_ACER_A500 This option enables support for the Power Button LED of Acer Iconia Tab A500. +config LEDS_LTC3208 + tristate "LED Driver for LTC3208" + depends on LEDS_CLASS && I2C + select REGMAP_I2C + help + Say Y to enable the LTC3208 LED driver. + This supports the LED device LTC3208. + + To compile this driver as a module, choose M here: the module will + be called ltc3208. + source "drivers/leds/blink/Kconfig" comment "Flash and Torch LED drivers" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 18afbb5a23ee5a..cfb7e429f3884a 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o +obj-$(CONFIG_LEDS_LTC3208) += leds-ltc3208.o obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o diff --git a/drivers/leds/leds-ltc3208.c b/drivers/leds/leds-ltc3208.c new file mode 100644 index 00000000000000..b83a90989cc9e0 --- /dev/null +++ b/drivers/leds/leds-ltc3208.c @@ -0,0 +1,350 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * LED driver for Analog Devices LTC3208 Multi-Display Driver + * + * Copyright 2025 Analog Devices Inc. + * + * Author: Jan Carlo Roleda + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LTC3208_SET_HIGH_BYTE_DATA(x) FIELD_PREP(GENMASK(7, 4), (x)) + +/* Registers */ +#define LTC3208_REG_A_GNRD 0x1 /* Green (High half-byte) and Red (Low half-byte) current DAC*/ +#define LTC3208_REG_B_AXBL 0x2 /* AUX (High half-byte) and Blue (Low half-byte) current DAC*/ +#define LTC3208_REG_C_MAIN 0x3 /* Main current DAC */ +#define LTC3208_REG_D_SUB 0x4 /* Sub current DAC */ +#define LTC3208_REG_E_AUX 0x5 /* AUX DAC Select */ +#define LTC3208_REG_F_CAM 0x6 /* CAM (High half-byte and Low half-byte) current DAC*/ +#define LTC3208_REG_G_OPT 0x7 /* Device Options */ + +/* Device Options register */ +#define LTC3208_OPT_CPO_MASK GENMASK(7, 6) +#define LTC3208_OPT_DIS_RGBDROP BIT(3) +#define LTC3208_OPT_DIS_CAMHILO BIT(2) +#define LTC3208_OPT_EN_RGBS BIT(1) + +/* Auxiliary DAC select masks */ +#define LTC3208_AUX1_MASK GENMASK(1, 0) +#define LTC3208_AUX2_MASK GENMASK(3, 2) +#define LTC3208_AUX3_MASK GENMASK(5, 4) +#define LTC3208_AUX4_MASK GENMASK(7, 6) + +#define LTC3208_MAX_BRIGHTNESS_4BIT 0xF +#define LTC3208_MAX_BRIGHTNESS_8BIT 0xFF + +#define LTC3208_NUM_LED_GRPS 8 +#define LTC3208_NUM_AUX_LEDS 4 + +#define LTC3208_NUM_AUX_OPT 4 +#define LTC3208_MAX_CPO_OPT 2 + +enum ltc3208_aux_channel { + LTC3208_AUX_CHAN_AUX = 0, + LTC3208_AUX_CHAN_MAIN, + LTC3208_AUX_CHAN_SUB, + LTC3208_AUX_CHAN_CAM +}; + +enum ltc3208_channel { + LTC3208_CHAN_MAIN = 0, + LTC3208_CHAN_SUB, + LTC3208_CHAN_AUX, + LTC3208_CHAN_CAML, + LTC3208_CHAN_CAMH, + LTC3208_CHAN_RED, + LTC3208_CHAN_BLUE, + LTC3208_CHAN_GREEN +}; + +enum ltc3208_cpo_mode { + LTC3208_CPO_AUTO = 0, + LTC3208_CPO_1P5X, + LTC3208_CPO_2X +}; + +static const char * const ltc3208_channel_labels[] = { + "main", "sub", "aux", "cam_lo", "cam_hi", "red", "blue", "green" +}; + +static const char * const ltc3208_dt_aux_channels[] = { + "adi,aux1-channel", "adi,aux2-channel", + "adi,aux3-channel", "adi,aux4-channel" +}; + +static const char * const ltc3208_aux_opt[] = { + "aux", "main", "sub", "cam" +}; + +struct ltc3208_led { + struct led_classdev cdev; + struct i2c_client *client; + enum ltc3208_channel channel; +}; + +/* + * as device is write-only the device configs must be stored in the driver to + * allow atomic operations in registers that control multiple channels/options + */ +struct ltc3208_chip_data { + struct ltc3208_led *leds; +}; + +struct ltc3208_dev { + struct i2c_client *client; + struct regmap *map; + struct ltc3208_chip_data chip_data; +}; + +static const struct regmap_config ltc3208_regmap_cfg = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int ltc3208_led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct ltc3208_led *led = container_of(led_cdev, struct ltc3208_led, cdev); + struct i2c_client *client = led->client; + struct ltc3208_dev *dev = i2c_get_clientdata(client); + struct regmap *map = dev->map; + int ret; + u32 reg = 0; + u8 current_level = brightness; + + /* + * For registers with 4-bit splits (CAM, AUX/BLUE, GREEN/RED), the other + * half of the byte will be retrieved from the stored DAC value before + * updating the register. + */ + switch (led->channel) { + case LTC3208_CHAN_MAIN: + reg = LTC3208_REG_C_MAIN; + break; + case LTC3208_CHAN_SUB: + reg = LTC3208_REG_D_SUB; + break; + + case LTC3208_CHAN_AUX: + /* combine both low and high halves of byte */ + current_level = LTC3208_SET_HIGH_BYTE_DATA(current_level); + current_level |= dev->chip_data.leds[LTC3208_CHAN_BLUE].cdev.brightness; + reg = LTC3208_REG_B_AXBL; + break; + + case LTC3208_CHAN_BLUE: + /* apply high bits stored in other led */ + current_level |= LTC3208_SET_HIGH_BYTE_DATA( + dev->chip_data.leds[LTC3208_CHAN_AUX].cdev.brightness); + reg = LTC3208_REG_B_AXBL; + break; + + case LTC3208_CHAN_CAMH: + current_level = LTC3208_SET_HIGH_BYTE_DATA(current_level); + current_level |= dev->chip_data.leds[LTC3208_CHAN_CAML].cdev.brightness; + reg = LTC3208_REG_F_CAM; + break; + + case LTC3208_CHAN_CAML: + current_level |= LTC3208_SET_HIGH_BYTE_DATA( + dev->chip_data.leds[LTC3208_CHAN_CAMH].cdev.brightness); + reg = LTC3208_REG_F_CAM; + break; + + case LTC3208_CHAN_GREEN: + current_level = LTC3208_SET_HIGH_BYTE_DATA(current_level); + current_level |= dev->chip_data.leds[LTC3208_CHAN_RED].cdev.brightness; + reg = LTC3208_REG_A_GNRD; + break; + case LTC3208_CHAN_RED: + current_level |= LTC3208_SET_HIGH_BYTE_DATA( + dev->chip_data.leds[LTC3208_CHAN_GREEN].cdev.brightness); + reg = LTC3208_REG_A_GNRD; + break; + } + + ret = regmap_write(map, reg, current_level); + if (ret) { + dev_err(&client->dev, + "Error Writing brightness to register %u\n", reg); + return ret; + } + + return 0; +} + +static int ltc3208_update_options(struct ltc3208_dev *dev, + bool is_sub, bool is_cam_hi, bool is_rgb_drop, + enum ltc3208_cpo_mode cpo_mode) +{ + struct regmap *map = dev->map; + u8 val = 0; + + val |= FIELD_PREP(LTC3208_OPT_EN_RGBS, is_sub); + val |= FIELD_PREP(LTC3208_OPT_DIS_CAMHILO, is_cam_hi); + val |= FIELD_PREP(LTC3208_OPT_DIS_RGBDROP, is_rgb_drop); + val |= FIELD_PREP(LTC3208_OPT_CPO_MASK, cpo_mode); + + return regmap_write(map, LTC3208_REG_G_OPT, val); +} + +static int ltc3208_update_aux_dac(struct ltc3208_dev *dev, + enum ltc3208_aux_channel aux_1, enum ltc3208_aux_channel aux_2, + enum ltc3208_aux_channel aux_3, enum ltc3208_aux_channel aux_4) +{ + struct regmap *map = dev->map; + u8 val = 0; + + val = FIELD_PREP(LTC3208_AUX1_MASK, aux_1) | + FIELD_PREP(LTC3208_AUX2_MASK, aux_2) | + FIELD_PREP(LTC3208_AUX3_MASK, aux_3) | + FIELD_PREP(LTC3208_AUX4_MASK, aux_4); + + return regmap_write(map, LTC3208_REG_E_AUX, val); +} + +static int ltc3208_probe(struct i2c_client *client) +{ + struct ltc3208_dev *data; + struct regmap *map; + struct ltc3208_led *leds; + enum ltc3208_aux_channel aux_channels[LTC3208_NUM_AUX_LEDS]; + enum ltc3208_cpo_mode cpo_mode; + int ret, i; + u32 val; + bool en_rgbs, dis_camhl, dropdis_rgb_aux4; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return dev_err_probe(&client->dev, -EIO, + "SMBUS Byte Data not Supported\n"); + + map = devm_regmap_init_i2c(client, <c3208_regmap_cfg); + if (IS_ERR(map)) + return dev_err_probe(&client->dev, PTR_ERR(map), + "Failed to initialize regmap\n"); + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return dev_err_probe(&client->dev, -ENOMEM, + "No memory for device data\n"); + + leds = devm_kcalloc(&client->dev, LTC3208_NUM_LED_GRPS, + sizeof(struct ltc3208_led), GFP_KERNEL); + + data->client = client; + data->map = map; + + /* initialize options from devicetree */ + dis_camhl = device_property_read_bool(&client->dev, + "adi,disable-camhl-pin"); + en_rgbs = device_property_read_bool(&client->dev, + "adi,select-rgb-sub-en"); + dropdis_rgb_aux4 = device_property_read_bool(&client->dev, + "adi,disable-rgb-aux4-dropout"); + + cpo_mode = LTC3208_CPO_AUTO; + if (!device_property_read_u32(&client->dev, + "adi,force-cpo-level", &val)) { + if (val > LTC3208_MAX_CPO_OPT) + return dev_err_probe(&client->dev, -EINVAL, + "invalid adi,force-cpo-level value: %u\n", val); + + cpo_mode = val; + } + + ret = ltc3208_update_options(data, en_rgbs, dis_camhl, + dropdis_rgb_aux4, cpo_mode); + if (ret) + return dev_err_probe(&client->dev, ret, + "error writing to options register\n"); + + /* initialize aux channel configurations from devicetree */ + for (i = 0; i < LTC3208_NUM_AUX_LEDS; i++) { + ret = device_property_match_property_string(&client->dev, + ltc3208_dt_aux_channels[i], ltc3208_aux_opt, + LTC3208_NUM_AUX_OPT); + if (ret == -ENXIO) /* use default value if unspecified on devicetree */ + aux_channels[i] = LTC3208_AUX_CHAN_AUX; + else if (ret >= 0) + aux_channels[i] = ret; + else + return dev_err_probe(&client->dev, ret, + "Invalid channel name.\n"); + } + + ret = ltc3208_update_aux_dac(data, aux_channels[0], aux_channels[1], + aux_channels[2], aux_channels[3]); + if (ret) + return dev_err_probe(&client->dev, ret, + "error writing to aux %u channel register.\n", i); + + i2c_set_clientdata(client, data); + + device_for_each_child_node_scoped(&client->dev, child) { + struct ltc3208_led *led; + struct led_init_data init_data = {}; + + ret = fwnode_property_read_u32(child, "reg", &val); + if (ret || val >= LTC3208_NUM_LED_GRPS) + return dev_err_probe(&client->dev, -EINVAL, + "Invalid reg property for LED\n"); + + led = &leds[val]; + led->client = client; + led->channel = val; + led->cdev.brightness_set_blocking = ltc3208_led_set_brightness; + led->cdev.max_brightness = LTC3208_MAX_BRIGHTNESS_4BIT; + if (val == LTC3208_CHAN_MAIN || val == LTC3208_CHAN_SUB) + led->cdev.max_brightness = LTC3208_MAX_BRIGHTNESS_8BIT; + + init_data.fwnode = child; + + ret = devm_led_classdev_register_ext(&client->dev, &led->cdev, + &init_data); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to register LED %u\n", val); + } + + data->chip_data.leds = leds; + + return 0; +} + +static const struct of_device_id ltc3208_match_table[] = { + {.compatible = "adi,ltc3208"}, + { } +}; +MODULE_DEVICE_TABLE(of, ltc3208_match_table); + +static const struct i2c_device_id ltc3208_idtable[] = { + { "ltc3208" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc3208_idtable); + +static struct i2c_driver ltc3208_driver = { + .driver = { + .name = "ltc3208", + .of_match_table = ltc3208_match_table, + }, + .id_table = ltc3208_idtable, + .probe = ltc3208_probe, +}; +module_i2c_driver(ltc3208_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Jan Carlo Roleda "); +MODULE_DESCRIPTION("LTC3208 LED Driver"); From 6e8165c901bf109984d074b782352054c820674b Mon Sep 17 00:00:00 2001 From: jcroleda Date: Tue, 4 Nov 2025 14:36:47 +0800 Subject: [PATCH 3/3] leds: Kconfig.adi: imply LTC3208 Add entry to Kconfig for LTC3208 driver Signed-off-by: Jan Carlo Roleda --- Kconfig.adi | 1 + 1 file changed, 1 insertion(+) diff --git a/Kconfig.adi b/Kconfig.adi index b55ae80169496b..98edad56e2c219 100644 --- a/Kconfig.adi +++ b/Kconfig.adi @@ -71,6 +71,7 @@ config KERNEL_ALL_ADI_DRIVERS imply PMIC_ADP5520 imply PWM_AXI_PWMGEN imply LEDS_ADP5520 + imply LEDS_LTC3208 imply BACKLIGHT_ADP5520 imply BACKLIGHT_ADP8860 imply BACKLIGHT_ADP8870