// SPDX-License-Identifier: GPL-2.0 /* * LED driver for Analog Devices LTC3208 Multi-Display Driver * * Copyright 2026 Analog Devices Inc. * * Author: Jan Carlo Roleda * */ #include #include #include #include #include #include #include #include #include /* Registers */ #define LTC3208_REG_A_GRNRED_CURRENT 0x1 /* Green and Red current DAC */ #define LTC3208_REG_B_AUXBLU_CURRENT 0x2 /* AUX and Blue current DAC */ #define LTC3208_REG_C_MAIN_CURRENT 0x3 /* Main current DAC */ #define LTC3208_REG_D_SUB_CURRENT 0x4 /* Sub current DAC */ #define LTC3208_REG_E_AUX_SELECT 0x5 /* AUX DAC Select */ #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_REG_F_CAM_CURRENT 0x6 /* CAM (High and Low) current DAC */ #define LTC3208_REG_G_OPT 0x7 /* Device Options */ #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) #define LTC3208_MAX_BRIGHTNESS_4BIT 0xF #define LTC3208_MAX_BRIGHTNESS_8BIT 0xFF #define LTC3208_NUM_AUX_LEDS 4 /* Number of configurable aux channels */ #define LTC3208_NUM_REGS 7 /* Number of available registers to access */ 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, LTC3208_CHAN_N_COUNT, }; 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 regmap_field *rfield; }; static const struct reg_default ltc3208_reg_defaults[] = { { LTC3208_REG_A_GRNRED_CURRENT, 0 }, { LTC3208_REG_B_AUXBLU_CURRENT, 0 }, { LTC3208_REG_C_MAIN_CURRENT, 0 }, { LTC3208_REG_D_SUB_CURRENT, 0 }, { LTC3208_REG_E_AUX_SELECT, 0 }, { LTC3208_REG_F_CAM_CURRENT, 0 }, { LTC3208_REG_G_OPT, 0 } }; static const struct regmap_config ltc3208_regmap_cfg = { .reg_bits = 8, .val_bits = 8, .max_register = LTC3208_REG_G_OPT, .cache_type = REGCACHE_FLAT_S, .reg_defaults = ltc3208_reg_defaults, .num_reg_defaults = LTC3208_NUM_REGS, }; static const struct reg_field ltc3208_led_reg_field[LTC3208_CHAN_N_COUNT] = { [LTC3208_CHAN_MAIN] = REG_FIELD(LTC3208_REG_C_MAIN_CURRENT, 0, 7), [LTC3208_CHAN_SUB] = REG_FIELD(LTC3208_REG_D_SUB_CURRENT, 0, 7), [LTC3208_CHAN_BLUE] = REG_FIELD(LTC3208_REG_B_AUXBLU_CURRENT, 0, 3), [LTC3208_CHAN_AUX] = REG_FIELD(LTC3208_REG_B_AUXBLU_CURRENT, 4, 7), [LTC3208_CHAN_CAML] = REG_FIELD(LTC3208_REG_F_CAM_CURRENT, 0, 3), [LTC3208_CHAN_CAMH] = REG_FIELD(LTC3208_REG_F_CAM_CURRENT, 4, 7), [LTC3208_CHAN_RED] = REG_FIELD(LTC3208_REG_A_GRNRED_CURRENT, 0, 3), [LTC3208_CHAN_GREEN] = REG_FIELD(LTC3208_REG_A_GRNRED_CURRENT, 4, 7), }; 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); u8 current_level = brightness; return regmap_field_write(led->rfield, current_level); } static int ltc3208_probe(struct i2c_client *client) { enum ltc3208_aux_channel aux_channels[LTC3208_NUM_AUX_LEDS]; DECLARE_BITMAP(registered_chans, LTC3208_CHAN_N_COUNT) = { }; struct regmap *regmap; bool disable_rgb_aux4_dropout_signal; bool disable_camhl_pin; bool set_sub_control_pin; int ret; u8 dev_options; regmap = devm_regmap_init_i2c(client, <c3208_regmap_cfg); if (IS_ERR(regmap)) return dev_err_probe(&client->dev, PTR_ERR(regmap), "Failed to initialize regmap"); regcache_mark_dirty(regmap); ret = regcache_sync(regmap); if (ret) return dev_err_probe(&client->dev, ret, "Failed to sync register cache"); disable_camhl_pin = device_property_read_bool(&client->dev, "adi,disable-camhl-pin"); set_sub_control_pin = device_property_read_bool(&client->dev, "adi,cfg-enrgbs-pin"); disable_rgb_aux4_dropout_signal = device_property_read_bool(&client->dev, "adi,disable-rgb-aux4-dropout"); dev_options = FIELD_PREP(LTC3208_OPT_EN_RGBS, set_sub_control_pin) | FIELD_PREP(LTC3208_OPT_DIS_CAMHILO, disable_camhl_pin) | FIELD_PREP(LTC3208_OPT_CPO_MASK, 0) | FIELD_PREP(LTC3208_OPT_DIS_RGBDROP, disable_rgb_aux4_dropout_signal); ret = regmap_write(regmap, LTC3208_REG_G_OPT, dev_options); if (ret) return dev_err_probe(&client->dev, ret, "Failed to set device options register"); /* Initialize AUX channel configurations */ for (int i = 0; i < LTC3208_NUM_AUX_LEDS; i++) { /* Default value (AUX) if property is not present in DT */ if (!device_property_present(&client->dev, ltc3208_dt_aux_channels[i])) { aux_channels[i] = LTC3208_AUX_CHAN_AUX; continue; } ret = device_property_match_property_string(&client->dev, ltc3208_dt_aux_channels[i], ltc3208_aux_opt, LTC3208_NUM_AUX_LEDS); if (ret < 0) return dev_err_probe(&client->dev, ret, "Error reading AUX Channel %d", i); aux_channels[i] = ret; } dev_options = FIELD_PREP(LTC3208_AUX1_MASK, aux_channels[0]) | FIELD_PREP(LTC3208_AUX2_MASK, aux_channels[1]) | FIELD_PREP(LTC3208_AUX3_MASK, aux_channels[2]) | FIELD_PREP(LTC3208_AUX4_MASK, aux_channels[3]); ret = regmap_write(regmap, LTC3208_REG_E_AUX_SELECT, dev_options); if (ret) return dev_err_probe(&client->dev, ret, "Error writing to aux channel register"); device_for_each_child_node_scoped(&client->dev, child) { struct ltc3208_led *led; struct led_init_data init_data = { }; u32 chan; ret = fwnode_property_read_u32(child, "reg", &chan); if (ret) return dev_err_probe(&client->dev, ret, "Failed to get register value of LED"); if (chan >= LTC3208_CHAN_N_COUNT) return dev_err_probe(&client->dev, -EINVAL, "%u is an invalid LED ID", chan); if (test_and_set_bit(chan, registered_chans)) return dev_err_probe(&client->dev, -EINVAL, "%u is already registered", chan); led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL); if (!led) return -ENOMEM; led->rfield = devm_regmap_field_alloc(&client->dev, regmap, ltc3208_led_reg_field[chan]); if (IS_ERR(led->rfield)) return dev_err_probe(&client->dev, PTR_ERR(led->rfield), "Cannot allocate regmap field"); led->cdev.brightness_set_blocking = ltc3208_led_set_brightness; led->cdev.max_brightness = LTC3208_MAX_BRIGHTNESS_4BIT; if (chan == LTC3208_CHAN_MAIN || chan == 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", chan); } return 0; } static const struct of_device_id ltc3208_match_table[] = { { .compatible = "adi,ltc3208" }, { } }; MODULE_DEVICE_TABLE(of, ltc3208_match_table); static struct i2c_driver ltc3208_driver = { .driver = { .name = "ltc3208", .of_match_table = ltc3208_match_table, }, .probe = ltc3208_probe, }; module_i2c_driver(ltc3208_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jan Carlo Roleda "); MODULE_DESCRIPTION("LTC3208 LED Driver");