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

backlight: Add new lm3509 backlight driver

This is a general driver for LM3509 backlight chip of TI.
LM3509 is High Efficiency Boost for White LEDs and/or OLED Displays with
Dual Current Sinks. This driver supports OLED/White LED select, brightness
control and sub/main control.
The datasheet can be found at http://www.ti.com/product/lm3509.

Signed-off-by: Patrick Gansterer <paroga@paroga.com>
Reviewed-by: Daniel Thompson <daniel.thompson@linaro.org>
Link: https://lore.kernel.org/r/20240526105136.721529-3-paroga@paroga.com
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Patrick Gansterer and committed by
Lee Jones
b72755f5 0aaee23d

+348
+7
drivers/video/backlight/Kconfig
··· 373 373 If you have a AnalogicTech AAT2870 say Y to enable the 374 374 backlight driver. 375 375 376 + config BACKLIGHT_LM3509 377 + tristate "Backlight Driver for LM3509" 378 + depends on I2C 379 + select REGMAP_I2C 380 + help 381 + This supports TI LM3509 Backlight Driver 382 + 376 383 config BACKLIGHT_LM3630A 377 384 tristate "Backlight Driver for LM3630A" 378 385 depends on I2C && PWM
+1
drivers/video/backlight/Makefile
··· 36 36 obj-$(CONFIG_BACKLIGHT_KTD253) += ktd253-backlight.o 37 37 obj-$(CONFIG_BACKLIGHT_KTD2801) += ktd2801-backlight.o 38 38 obj-$(CONFIG_BACKLIGHT_KTZ8866) += ktz8866.o 39 + obj-$(CONFIG_BACKLIGHT_LM3509) += lm3509_bl.o 39 40 obj-$(CONFIG_BACKLIGHT_LM3533) += lm3533_bl.o 40 41 obj-$(CONFIG_BACKLIGHT_LM3630A) += lm3630a_bl.o 41 42 obj-$(CONFIG_BACKLIGHT_LM3639) += lm3639_bl.o
+340
drivers/video/backlight/lm3509_bl.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + #include <linux/backlight.h> 3 + #include <linux/delay.h> 4 + #include <linux/gpio/consumer.h> 5 + #include <linux/i2c.h> 6 + #include <linux/module.h> 7 + #include <linux/regmap.h> 8 + 9 + #define LM3509_NAME "lm3509_bl" 10 + 11 + #define LM3509_SINK_MAIN 0 12 + #define LM3509_SINK_SUB 1 13 + #define LM3509_NUM_SINKS 2 14 + 15 + #define LM3509_DEF_BRIGHTNESS 0x12 16 + #define LM3509_MAX_BRIGHTNESS 0x1F 17 + 18 + #define REG_GP 0x10 19 + #define REG_BMAIN 0xA0 20 + #define REG_BSUB 0xB0 21 + #define REG_MAX 0xFF 22 + 23 + enum { 24 + REG_GP_ENM_BIT = 0, 25 + REG_GP_ENS_BIT, 26 + REG_GP_UNI_BIT, 27 + REG_GP_RMP0_BIT, 28 + REG_GP_RMP1_BIT, 29 + REG_GP_OLED_BIT, 30 + }; 31 + 32 + struct lm3509_bl { 33 + struct regmap *regmap; 34 + struct backlight_device *bl_main; 35 + struct backlight_device *bl_sub; 36 + struct gpio_desc *reset_gpio; 37 + }; 38 + 39 + struct lm3509_bl_led_data { 40 + const char *label; 41 + int led_sources; 42 + u32 brightness; 43 + u32 max_brightness; 44 + }; 45 + 46 + static void lm3509_reset(struct lm3509_bl *data) 47 + { 48 + if (data->reset_gpio) { 49 + gpiod_set_value(data->reset_gpio, 1); 50 + udelay(1); 51 + gpiod_set_value(data->reset_gpio, 0); 52 + udelay(10); 53 + } 54 + } 55 + 56 + static int lm3509_update_status(struct backlight_device *bl, 57 + unsigned int en_mask, unsigned int br_reg) 58 + { 59 + struct lm3509_bl *data = bl_get_data(bl); 60 + int ret; 61 + bool en; 62 + 63 + ret = regmap_write(data->regmap, br_reg, backlight_get_brightness(bl)); 64 + if (ret < 0) 65 + return ret; 66 + 67 + en = !backlight_is_blank(bl); 68 + return regmap_update_bits(data->regmap, REG_GP, en_mask, 69 + en ? en_mask : 0); 70 + } 71 + 72 + static int lm3509_main_update_status(struct backlight_device *bl) 73 + { 74 + return lm3509_update_status(bl, BIT(REG_GP_ENM_BIT), REG_BMAIN); 75 + } 76 + 77 + static const struct backlight_ops lm3509_main_ops = { 78 + .options = BL_CORE_SUSPENDRESUME, 79 + .update_status = lm3509_main_update_status, 80 + }; 81 + 82 + static int lm3509_sub_update_status(struct backlight_device *bl) 83 + { 84 + return lm3509_update_status(bl, BIT(REG_GP_ENS_BIT), REG_BSUB); 85 + } 86 + 87 + static const struct backlight_ops lm3509_sub_ops = { 88 + .options = BL_CORE_SUSPENDRESUME, 89 + .update_status = lm3509_sub_update_status, 90 + }; 91 + 92 + static struct backlight_device * 93 + lm3509_backlight_register(struct device *dev, const char *name_suffix, 94 + struct lm3509_bl *data, 95 + const struct backlight_ops *ops, 96 + const struct lm3509_bl_led_data *led_data) 97 + 98 + { 99 + struct backlight_device *bd; 100 + struct backlight_properties props; 101 + const char *label = led_data->label; 102 + char name[64]; 103 + 104 + memset(&props, 0, sizeof(props)); 105 + props.type = BACKLIGHT_RAW; 106 + props.brightness = led_data->brightness; 107 + props.max_brightness = led_data->max_brightness; 108 + props.scale = BACKLIGHT_SCALE_NON_LINEAR; 109 + 110 + if (!label) { 111 + snprintf(name, sizeof(name), "lm3509-%s-%s", dev_name(dev), 112 + name_suffix); 113 + label = name; 114 + } 115 + 116 + bd = devm_backlight_device_register(dev, label, dev, data, ops, &props); 117 + if (bd) 118 + backlight_update_status(bd); 119 + 120 + return bd; 121 + } 122 + 123 + static const struct regmap_config lm3509_regmap = { 124 + .reg_bits = 8, 125 + .val_bits = 8, 126 + .max_register = REG_MAX, 127 + }; 128 + 129 + static int lm3509_parse_led_sources(struct device_node *node, 130 + int default_led_sources) 131 + { 132 + u32 sources[LM3509_NUM_SINKS]; 133 + int ret, num_sources, i; 134 + 135 + num_sources = of_property_count_u32_elems(node, "led-sources"); 136 + if (num_sources < 0) 137 + return default_led_sources; 138 + else if (num_sources > ARRAY_SIZE(sources)) 139 + return -EINVAL; 140 + 141 + ret = of_property_read_u32_array(node, "led-sources", sources, 142 + num_sources); 143 + if (ret) 144 + return ret; 145 + 146 + for (i = 0; i < num_sources; i++) { 147 + if (sources[i] >= LM3509_NUM_SINKS) 148 + return -EINVAL; 149 + 150 + ret |= BIT(sources[i]); 151 + } 152 + 153 + return ret; 154 + } 155 + 156 + static int lm3509_parse_dt_node(struct device *dev, 157 + struct lm3509_bl_led_data *led_data) 158 + { 159 + struct device_node *child; 160 + int seen_led_sources = 0; 161 + 162 + for_each_child_of_node(dev->of_node, child) { 163 + struct lm3509_bl_led_data *ld; 164 + int ret; 165 + u32 reg; 166 + int valid_led_sources; 167 + 168 + ret = of_property_read_u32(child, "reg", &reg); 169 + if (ret < 0) 170 + return ret; 171 + if (reg >= LM3509_NUM_SINKS) 172 + return -EINVAL; 173 + ld = &led_data[reg]; 174 + 175 + ld->led_sources = lm3509_parse_led_sources(child, BIT(reg)); 176 + if (ld->led_sources < 0) 177 + return ld->led_sources; 178 + 179 + if (reg == 0) 180 + valid_led_sources = BIT(LM3509_SINK_MAIN) | 181 + BIT(LM3509_SINK_SUB); 182 + else 183 + valid_led_sources = BIT(LM3509_SINK_SUB); 184 + 185 + if (ld->led_sources != (ld->led_sources & valid_led_sources)) 186 + return -EINVAL; 187 + 188 + if (seen_led_sources & ld->led_sources) 189 + return -EINVAL; 190 + 191 + seen_led_sources |= ld->led_sources; 192 + 193 + ld->label = NULL; 194 + of_property_read_string(child, "label", &ld->label); 195 + 196 + ld->max_brightness = LM3509_MAX_BRIGHTNESS; 197 + of_property_read_u32(child, "max-brightness", 198 + &ld->max_brightness); 199 + ld->max_brightness = 200 + min_t(u32, ld->max_brightness, LM3509_MAX_BRIGHTNESS); 201 + 202 + ld->brightness = LM3509_DEF_BRIGHTNESS; 203 + of_property_read_u32(child, "default-brightness", 204 + &ld->brightness); 205 + ld->brightness = min_t(u32, ld->brightness, ld->max_brightness); 206 + } 207 + 208 + return 0; 209 + } 210 + 211 + static int lm3509_probe(struct i2c_client *client) 212 + { 213 + struct lm3509_bl *data; 214 + struct device *dev = &client->dev; 215 + int ret; 216 + bool oled_mode = false; 217 + unsigned int reg_gp_val = 0; 218 + struct lm3509_bl_led_data led_data[LM3509_NUM_SINKS]; 219 + u32 rate_of_change = 0; 220 + 221 + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { 222 + dev_err(dev, "i2c functionality check failed\n"); 223 + return -EOPNOTSUPP; 224 + } 225 + 226 + data = devm_kzalloc(dev, sizeof(struct lm3509_bl), GFP_KERNEL); 227 + if (!data) 228 + return -ENOMEM; 229 + 230 + data->regmap = devm_regmap_init_i2c(client, &lm3509_regmap); 231 + if (IS_ERR(data->regmap)) 232 + return PTR_ERR(data->regmap); 233 + i2c_set_clientdata(client, data); 234 + 235 + data->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); 236 + if (IS_ERR(data->reset_gpio)) 237 + return dev_err_probe(dev, PTR_ERR(data->reset_gpio), 238 + "Failed to get 'reset' gpio\n"); 239 + 240 + lm3509_reset(data); 241 + 242 + memset(led_data, 0, sizeof(led_data)); 243 + ret = lm3509_parse_dt_node(dev, led_data); 244 + if (ret) 245 + return ret; 246 + 247 + oled_mode = of_property_read_bool(dev->of_node, "ti,oled-mode"); 248 + 249 + if (!of_property_read_u32(dev->of_node, 250 + "ti,brightness-rate-of-change-us", 251 + &rate_of_change)) { 252 + switch (rate_of_change) { 253 + case 51: 254 + reg_gp_val = 0; 255 + break; 256 + case 13000: 257 + reg_gp_val = BIT(REG_GP_RMP1_BIT); 258 + break; 259 + case 26000: 260 + reg_gp_val = BIT(REG_GP_RMP0_BIT); 261 + break; 262 + case 52000: 263 + reg_gp_val = BIT(REG_GP_RMP0_BIT) | 264 + BIT(REG_GP_RMP1_BIT); 265 + break; 266 + default: 267 + dev_warn(dev, "invalid rate of change %u\n", 268 + rate_of_change); 269 + break; 270 + } 271 + } 272 + 273 + if (led_data[0].led_sources == 274 + (BIT(LM3509_SINK_MAIN) | BIT(LM3509_SINK_SUB))) 275 + reg_gp_val |= BIT(REG_GP_UNI_BIT); 276 + if (oled_mode) 277 + reg_gp_val |= BIT(REG_GP_OLED_BIT); 278 + 279 + ret = regmap_write(data->regmap, REG_GP, reg_gp_val); 280 + if (ret < 0) 281 + return dev_err_probe(dev, ret, "failed to write register\n"); 282 + 283 + if (led_data[0].led_sources) { 284 + data->bl_main = lm3509_backlight_register( 285 + dev, "main", data, &lm3509_main_ops, &led_data[0]); 286 + if (IS_ERR(data->bl_main)) { 287 + return dev_err_probe( 288 + dev, PTR_ERR(data->bl_main), 289 + "failed to register main backlight\n"); 290 + } 291 + } 292 + 293 + if (led_data[1].led_sources) { 294 + data->bl_sub = lm3509_backlight_register( 295 + dev, "sub", data, &lm3509_sub_ops, &led_data[1]); 296 + if (IS_ERR(data->bl_sub)) { 297 + return dev_err_probe( 298 + dev, PTR_ERR(data->bl_sub), 299 + "failed to register secondary backlight\n"); 300 + } 301 + } 302 + 303 + return 0; 304 + } 305 + 306 + static void lm3509_remove(struct i2c_client *client) 307 + { 308 + struct lm3509_bl *data = i2c_get_clientdata(client); 309 + 310 + regmap_write(data->regmap, REG_GP, 0x00); 311 + } 312 + 313 + static const struct i2c_device_id lm3509_id[] = { { LM3509_NAME, 0 }, {} }; 314 + 315 + MODULE_DEVICE_TABLE(i2c, lm3509_id); 316 + 317 + static const struct of_device_id lm3509_match_table[] = { 318 + { 319 + .compatible = "ti,lm3509", 320 + }, 321 + {}, 322 + }; 323 + 324 + MODULE_DEVICE_TABLE(of, lm3509_match_table); 325 + 326 + static struct i2c_driver lm3509_i2c_driver = { 327 + .driver = { 328 + .name = LM3509_NAME, 329 + .of_match_table = lm3509_match_table, 330 + }, 331 + .probe = lm3509_probe, 332 + .remove = lm3509_remove, 333 + .id_table = lm3509_id, 334 + }; 335 + 336 + module_i2c_driver(lm3509_i2c_driver); 337 + 338 + MODULE_DESCRIPTION("Texas Instruments Backlight driver for LM3509"); 339 + MODULE_AUTHOR("Patrick Gansterer <paroga@paroga.com>"); 340 + MODULE_LICENSE("GPL");