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

leds: add Qualcomm PM8941 WLED driver

This adds support for the WLED ('White' LED) block on Qualcomm's
PM8941 PMICs.

(cooloney@gmail.com: remove unneeded semicolon)

Signed-off-by: Courtney Cavin <courtney.cavin@sonymobile.com>
Signed-off-by: Bjorn Andersson <bjorn.andersson@sonymobile.com>
Signed-off-by: Bryan Wu <cooloney@gmail.com>

authored by

Courtney Cavin and committed by
Bryan Wu
93c64f1e cbbd896f

+444
+8
drivers/leds/Kconfig
··· 526 526 This option enabled support for the LEDs on the ARM Versatile 527 527 and RealView boards. Say Y to enabled these. 528 528 529 + config LEDS_PM8941_WLED 530 + tristate "LED support for the Qualcomm PM8941 WLED block" 531 + depends on LEDS_CLASS 532 + select REGMAP 533 + help 534 + This option enables support for the 'White' LED block 535 + on Qualcomm PM8941 PMICs. 536 + 529 537 comment "LED Triggers" 530 538 source "drivers/leds/trigger/Kconfig" 531 539
+1
drivers/leds/Makefile
··· 58 58 obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o 59 59 obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o 60 60 obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o 61 + obj-$(CONFIG_LEDS_PM8941_WLED) += leds-pm8941-wled.o 61 62 62 63 # LED SPI Drivers 63 64 obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
+435
drivers/leds/leds-pm8941-wled.c
··· 1 + /* Copyright (c) 2015, Sony Mobile Communications, AB. 2 + * 3 + * This program is free software; you can redistribute it and/or modify 4 + * it under the terms of the GNU General Public License version 2 and 5 + * only version 2 as published by the Free Software Foundation. 6 + * 7 + * This program is distributed in the hope that it will be useful, 8 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 9 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 + * GNU General Public License for more details. 11 + */ 12 + 13 + #include <linux/kernel.h> 14 + #include <linux/leds.h> 15 + #include <linux/module.h> 16 + #include <linux/of.h> 17 + #include <linux/of_device.h> 18 + #include <linux/regmap.h> 19 + 20 + #define PM8941_WLED_REG_VAL_BASE 0x40 21 + #define PM8941_WLED_REG_VAL_MAX 0xFFF 22 + 23 + #define PM8941_WLED_REG_MOD_EN 0x46 24 + #define PM8941_WLED_REG_MOD_EN_BIT BIT(7) 25 + #define PM8941_WLED_REG_MOD_EN_MASK BIT(7) 26 + 27 + #define PM8941_WLED_REG_SYNC 0x47 28 + #define PM8941_WLED_REG_SYNC_MASK 0x07 29 + #define PM8941_WLED_REG_SYNC_LED1 BIT(0) 30 + #define PM8941_WLED_REG_SYNC_LED2 BIT(1) 31 + #define PM8941_WLED_REG_SYNC_LED3 BIT(2) 32 + #define PM8941_WLED_REG_SYNC_ALL 0x07 33 + #define PM8941_WLED_REG_SYNC_CLEAR 0x00 34 + 35 + #define PM8941_WLED_REG_FREQ 0x4c 36 + #define PM8941_WLED_REG_FREQ_MASK 0x0f 37 + 38 + #define PM8941_WLED_REG_OVP 0x4d 39 + #define PM8941_WLED_REG_OVP_MASK 0x03 40 + 41 + #define PM8941_WLED_REG_BOOST 0x4e 42 + #define PM8941_WLED_REG_BOOST_MASK 0x07 43 + 44 + #define PM8941_WLED_REG_SINK 0x4f 45 + #define PM8941_WLED_REG_SINK_MASK 0xe0 46 + #define PM8941_WLED_REG_SINK_SHFT 0x05 47 + 48 + /* Per-'string' registers below */ 49 + #define PM8941_WLED_REG_STR_OFFSET 0x10 50 + 51 + #define PM8941_WLED_REG_STR_MOD_EN_BASE 0x60 52 + #define PM8941_WLED_REG_STR_MOD_MASK BIT(7) 53 + #define PM8941_WLED_REG_STR_MOD_EN BIT(7) 54 + 55 + #define PM8941_WLED_REG_STR_SCALE_BASE 0x62 56 + #define PM8941_WLED_REG_STR_SCALE_MASK 0x1f 57 + 58 + #define PM8941_WLED_REG_STR_MOD_SRC_BASE 0x63 59 + #define PM8941_WLED_REG_STR_MOD_SRC_MASK 0x01 60 + #define PM8941_WLED_REG_STR_MOD_SRC_INT 0x00 61 + #define PM8941_WLED_REG_STR_MOD_SRC_EXT 0x01 62 + 63 + #define PM8941_WLED_REG_STR_CABC_BASE 0x66 64 + #define PM8941_WLED_REG_STR_CABC_MASK BIT(7) 65 + #define PM8941_WLED_REG_STR_CABC_EN BIT(7) 66 + 67 + struct pm8941_wled_config { 68 + u32 i_boost_limit; 69 + u32 ovp; 70 + u32 switch_freq; 71 + u32 num_strings; 72 + u32 i_limit; 73 + bool cs_out_en; 74 + bool ext_gen; 75 + bool cabc_en; 76 + }; 77 + 78 + struct pm8941_wled { 79 + struct regmap *regmap; 80 + u16 addr; 81 + 82 + struct led_classdev cdev; 83 + 84 + struct pm8941_wled_config cfg; 85 + }; 86 + 87 + static int pm8941_wled_set(struct led_classdev *cdev, 88 + enum led_brightness value) 89 + { 90 + struct pm8941_wled *wled; 91 + u8 ctrl = 0; 92 + u16 val; 93 + int rc; 94 + int i; 95 + 96 + wled = container_of(cdev, struct pm8941_wled, cdev); 97 + 98 + if (value != 0) 99 + ctrl = PM8941_WLED_REG_MOD_EN_BIT; 100 + 101 + val = value * PM8941_WLED_REG_VAL_MAX / LED_FULL; 102 + 103 + rc = regmap_update_bits(wled->regmap, 104 + wled->addr + PM8941_WLED_REG_MOD_EN, 105 + PM8941_WLED_REG_MOD_EN_MASK, ctrl); 106 + if (rc) 107 + return rc; 108 + 109 + for (i = 0; i < wled->cfg.num_strings; ++i) { 110 + u8 v[2] = { val & 0xff, (val >> 8) & 0xf }; 111 + 112 + rc = regmap_bulk_write(wled->regmap, 113 + wled->addr + PM8941_WLED_REG_VAL_BASE + 2 * i, 114 + v, 2); 115 + if (rc) 116 + return rc; 117 + } 118 + 119 + rc = regmap_update_bits(wled->regmap, 120 + wled->addr + PM8941_WLED_REG_SYNC, 121 + PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_ALL); 122 + if (rc) 123 + return rc; 124 + 125 + rc = regmap_update_bits(wled->regmap, 126 + wled->addr + PM8941_WLED_REG_SYNC, 127 + PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_CLEAR); 128 + return rc; 129 + } 130 + 131 + static void pm8941_wled_set_brightness(struct led_classdev *cdev, 132 + enum led_brightness value) 133 + { 134 + if (pm8941_wled_set(cdev, value)) { 135 + dev_err(cdev->dev, "Unable to set brightness\n"); 136 + return; 137 + } 138 + cdev->brightness = value; 139 + } 140 + 141 + static int pm8941_wled_setup(struct pm8941_wled *wled) 142 + { 143 + int rc; 144 + int i; 145 + 146 + rc = regmap_update_bits(wled->regmap, 147 + wled->addr + PM8941_WLED_REG_OVP, 148 + PM8941_WLED_REG_OVP_MASK, wled->cfg.ovp); 149 + if (rc) 150 + return rc; 151 + 152 + rc = regmap_update_bits(wled->regmap, 153 + wled->addr + PM8941_WLED_REG_BOOST, 154 + PM8941_WLED_REG_BOOST_MASK, wled->cfg.i_boost_limit); 155 + if (rc) 156 + return rc; 157 + 158 + rc = regmap_update_bits(wled->regmap, 159 + wled->addr + PM8941_WLED_REG_FREQ, 160 + PM8941_WLED_REG_FREQ_MASK, wled->cfg.switch_freq); 161 + if (rc) 162 + return rc; 163 + 164 + if (wled->cfg.cs_out_en) { 165 + u8 all = (BIT(wled->cfg.num_strings) - 1) 166 + << PM8941_WLED_REG_SINK_SHFT; 167 + 168 + rc = regmap_update_bits(wled->regmap, 169 + wled->addr + PM8941_WLED_REG_SINK, 170 + PM8941_WLED_REG_SINK_MASK, all); 171 + if (rc) 172 + return rc; 173 + } 174 + 175 + for (i = 0; i < wled->cfg.num_strings; ++i) { 176 + u16 addr = wled->addr + PM8941_WLED_REG_STR_OFFSET * i; 177 + 178 + rc = regmap_update_bits(wled->regmap, 179 + addr + PM8941_WLED_REG_STR_MOD_EN_BASE, 180 + PM8941_WLED_REG_STR_MOD_MASK, 181 + PM8941_WLED_REG_STR_MOD_EN); 182 + if (rc) 183 + return rc; 184 + 185 + if (wled->cfg.ext_gen) { 186 + rc = regmap_update_bits(wled->regmap, 187 + addr + PM8941_WLED_REG_STR_MOD_SRC_BASE, 188 + PM8941_WLED_REG_STR_MOD_SRC_MASK, 189 + PM8941_WLED_REG_STR_MOD_SRC_EXT); 190 + if (rc) 191 + return rc; 192 + } 193 + 194 + rc = regmap_update_bits(wled->regmap, 195 + addr + PM8941_WLED_REG_STR_SCALE_BASE, 196 + PM8941_WLED_REG_STR_SCALE_MASK, 197 + wled->cfg.i_limit); 198 + if (rc) 199 + return rc; 200 + 201 + rc = regmap_update_bits(wled->regmap, 202 + addr + PM8941_WLED_REG_STR_CABC_BASE, 203 + PM8941_WLED_REG_STR_CABC_MASK, 204 + wled->cfg.cabc_en ? 205 + PM8941_WLED_REG_STR_CABC_EN : 0); 206 + if (rc) 207 + return rc; 208 + } 209 + 210 + return 0; 211 + } 212 + 213 + static const struct pm8941_wled_config pm8941_wled_config_defaults = { 214 + .i_boost_limit = 3, 215 + .i_limit = 20, 216 + .ovp = 2, 217 + .switch_freq = 5, 218 + .num_strings = 0, 219 + .cs_out_en = false, 220 + .ext_gen = false, 221 + .cabc_en = false, 222 + }; 223 + 224 + struct pm8941_wled_var_cfg { 225 + const u32 *values; 226 + u32 (*fn)(u32); 227 + int size; 228 + }; 229 + 230 + static const u32 pm8941_wled_i_boost_limit_values[] = { 231 + 105, 385, 525, 805, 980, 1260, 1400, 1680, 232 + }; 233 + 234 + static const struct pm8941_wled_var_cfg pm8941_wled_i_boost_limit_cfg = { 235 + .values = pm8941_wled_i_boost_limit_values, 236 + .size = ARRAY_SIZE(pm8941_wled_i_boost_limit_values), 237 + }; 238 + 239 + static const u32 pm8941_wled_ovp_values[] = { 240 + 35, 32, 29, 27, 241 + }; 242 + 243 + static const struct pm8941_wled_var_cfg pm8941_wled_ovp_cfg = { 244 + .values = pm8941_wled_ovp_values, 245 + .size = ARRAY_SIZE(pm8941_wled_ovp_values), 246 + }; 247 + 248 + static u32 pm8941_wled_num_strings_values_fn(u32 idx) 249 + { 250 + return idx + 1; 251 + } 252 + 253 + static const struct pm8941_wled_var_cfg pm8941_wled_num_strings_cfg = { 254 + .fn = pm8941_wled_num_strings_values_fn, 255 + .size = 3, 256 + }; 257 + 258 + static u32 pm8941_wled_switch_freq_values_fn(u32 idx) 259 + { 260 + return 19200 / (2 * (1 + idx)); 261 + } 262 + 263 + static const struct pm8941_wled_var_cfg pm8941_wled_switch_freq_cfg = { 264 + .fn = pm8941_wled_switch_freq_values_fn, 265 + .size = 16, 266 + }; 267 + 268 + static const struct pm8941_wled_var_cfg pm8941_wled_i_limit_cfg = { 269 + .size = 26, 270 + }; 271 + 272 + static u32 pm8941_wled_values(const struct pm8941_wled_var_cfg *cfg, u32 idx) 273 + { 274 + if (idx >= cfg->size) 275 + return UINT_MAX; 276 + if (cfg->fn) 277 + return cfg->fn(idx); 278 + if (cfg->values) 279 + return cfg->values[idx]; 280 + return idx; 281 + } 282 + 283 + static int pm8941_wled_configure(struct pm8941_wled *wled, struct device *dev) 284 + { 285 + struct pm8941_wled_config *cfg = &wled->cfg; 286 + u32 val; 287 + int rc; 288 + u32 c; 289 + int i; 290 + int j; 291 + 292 + const struct { 293 + const char *name; 294 + u32 *val_ptr; 295 + const struct pm8941_wled_var_cfg *cfg; 296 + } u32_opts[] = { 297 + { 298 + "qcom,current-boost-limit", 299 + &cfg->i_boost_limit, 300 + .cfg = &pm8941_wled_i_boost_limit_cfg, 301 + }, 302 + { 303 + "qcom,current-limit", 304 + &cfg->i_limit, 305 + .cfg = &pm8941_wled_i_limit_cfg, 306 + }, 307 + { 308 + "qcom,ovp", 309 + &cfg->ovp, 310 + .cfg = &pm8941_wled_ovp_cfg, 311 + }, 312 + { 313 + "qcom,switching-freq", 314 + &cfg->switch_freq, 315 + .cfg = &pm8941_wled_switch_freq_cfg, 316 + }, 317 + { 318 + "qcom,num-strings", 319 + &cfg->num_strings, 320 + .cfg = &pm8941_wled_num_strings_cfg, 321 + }, 322 + }; 323 + const struct { 324 + const char *name; 325 + bool *val_ptr; 326 + } bool_opts[] = { 327 + { "qcom,cs-out", &cfg->cs_out_en, }, 328 + { "qcom,ext-gen", &cfg->ext_gen, }, 329 + { "qcom,cabc", &cfg->cabc_en, }, 330 + }; 331 + 332 + rc = of_property_read_u32(dev->of_node, "reg", &val); 333 + if (rc || val > 0xffff) { 334 + dev_err(dev, "invalid IO resources\n"); 335 + return rc ? rc : -EINVAL; 336 + } 337 + wled->addr = val; 338 + 339 + rc = of_property_read_string(dev->of_node, "label", &wled->cdev.name); 340 + if (rc) 341 + wled->cdev.name = dev->of_node->name; 342 + 343 + wled->cdev.default_trigger = of_get_property(dev->of_node, 344 + "linux,default-trigger", NULL); 345 + 346 + *cfg = pm8941_wled_config_defaults; 347 + for (i = 0; i < ARRAY_SIZE(u32_opts); ++i) { 348 + rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val); 349 + if (rc == -EINVAL) { 350 + continue; 351 + } else if (rc) { 352 + dev_err(dev, "error reading '%s'\n", u32_opts[i].name); 353 + return rc; 354 + } 355 + 356 + c = UINT_MAX; 357 + for (j = 0; c != val; j++) { 358 + c = pm8941_wled_values(u32_opts[i].cfg, j); 359 + if (c == UINT_MAX) { 360 + dev_err(dev, "invalid value for '%s'\n", 361 + u32_opts[i].name); 362 + return -EINVAL; 363 + } 364 + } 365 + 366 + dev_dbg(dev, "'%s' = %u\n", u32_opts[i].name, c); 367 + *u32_opts[i].val_ptr = j; 368 + } 369 + 370 + for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) { 371 + if (of_property_read_bool(dev->of_node, bool_opts[i].name)) 372 + *bool_opts[i].val_ptr = true; 373 + } 374 + 375 + cfg->num_strings = cfg->num_strings + 1; 376 + 377 + return 0; 378 + } 379 + 380 + static int pm8941_wled_probe(struct platform_device *pdev) 381 + { 382 + struct pm8941_wled *wled; 383 + struct regmap *regmap; 384 + int rc; 385 + 386 + regmap = dev_get_regmap(pdev->dev.parent, NULL); 387 + if (!regmap) { 388 + dev_err(&pdev->dev, "Unable to get regmap\n"); 389 + return -EINVAL; 390 + } 391 + 392 + wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL); 393 + if (!wled) 394 + return -ENOMEM; 395 + 396 + wled->regmap = regmap; 397 + 398 + rc = pm8941_wled_configure(wled, &pdev->dev); 399 + if (rc) 400 + return rc; 401 + 402 + rc = pm8941_wled_setup(wled); 403 + if (rc) 404 + return rc; 405 + 406 + wled->cdev.brightness_set = pm8941_wled_set_brightness; 407 + 408 + rc = devm_led_classdev_register(&pdev->dev, &wled->cdev); 409 + if (rc) 410 + return rc; 411 + 412 + platform_set_drvdata(pdev, wled); 413 + 414 + return 0; 415 + }; 416 + 417 + static const struct of_device_id pm8941_wled_match_table[] = { 418 + { .compatible = "qcom,pm8941-wled" }, 419 + {} 420 + }; 421 + MODULE_DEVICE_TABLE(of, pm8941_wled_match_table); 422 + 423 + static struct platform_driver pm8941_wled_driver = { 424 + .probe = pm8941_wled_probe, 425 + .driver = { 426 + .name = "pm8941-wled", 427 + .of_match_table = pm8941_wled_match_table, 428 + }, 429 + }; 430 + 431 + module_platform_driver(pm8941_wled_driver); 432 + 433 + MODULE_DESCRIPTION("pm8941 wled driver"); 434 + MODULE_LICENSE("GPL v2"); 435 + MODULE_ALIAS("platform:pm8941-wled");