1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * AS3711 PMIC backlight driver, using DCDC Step Up Converters 4 * 5 * Copyright (C) 2012 Renesas Electronics Corporation 6 * Author: Guennadi Liakhovetski, <g.liakhovetski@gmx.de> 7 */ 8 9 #include <linux/backlight.h> 10 #include <linux/delay.h> 11 #include <linux/device.h> 12 #include <linux/err.h> 13 #include <linux/fb.h> 14 #include <linux/kernel.h> 15 #include <linux/mfd/as3711.h> 16 #include <linux/module.h> 17 #include <linux/platform_device.h> 18 #include <linux/regmap.h> 19 #include <linux/slab.h> 20 21 enum as3711_bl_type { 22 AS3711_BL_SU1, 23 AS3711_BL_SU2, 24 }; 25 26 struct as3711_bl_data { 27 bool powered; 28 enum as3711_bl_type type; 29 int brightness; 30 struct backlight_device *bl; 31 }; 32 33 struct as3711_bl_supply { 34 struct as3711_bl_data su1; 35 struct as3711_bl_data su2; 36 const struct as3711_bl_pdata *pdata; 37 struct as3711 *as3711; 38 }; 39 40 static struct as3711_bl_supply *to_supply(struct as3711_bl_data *su) 41 { 42 switch (su->type) { 43 case AS3711_BL_SU1: 44 return container_of(su, struct as3711_bl_supply, su1); 45 case AS3711_BL_SU2: 46 return container_of(su, struct as3711_bl_supply, su2); 47 } 48 return NULL; 49 } 50 51 static int as3711_set_brightness_auto_i(struct as3711_bl_data *data, 52 unsigned int brightness) 53 { 54 struct as3711_bl_supply *supply = to_supply(data); 55 struct as3711 *as3711 = supply->as3711; 56 const struct as3711_bl_pdata *pdata = supply->pdata; 57 int ret = 0; 58 59 /* Only all equal current values are supported */ 60 if (pdata->su2_auto_curr1) 61 ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE, 62 brightness); 63 if (!ret && pdata->su2_auto_curr2) 64 ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE, 65 brightness); 66 if (!ret && pdata->su2_auto_curr3) 67 ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE, 68 brightness); 69 70 return ret; 71 } 72 73 static int as3711_set_brightness_v(struct as3711 *as3711, 74 unsigned int brightness, 75 unsigned int reg) 76 { 77 if (brightness > 31) 78 return -EINVAL; 79 80 return regmap_update_bits(as3711->regmap, reg, 0xf0, 81 brightness << 4); 82 } 83 84 static int as3711_bl_su2_reset(struct as3711_bl_supply *supply) 85 { 86 struct as3711 *as3711 = supply->as3711; 87 int ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_5, 88 3, supply->pdata->su2_fbprot); 89 if (!ret) 90 ret = regmap_update_bits(as3711->regmap, 91 AS3711_STEPUP_CONTROL_2, 1, 0); 92 if (!ret) 93 ret = regmap_update_bits(as3711->regmap, 94 AS3711_STEPUP_CONTROL_2, 1, 1); 95 return ret; 96 } 97 98 /* 99 * Someone with less fragile or less expensive hardware could try to simplify 100 * the brightness adjustment procedure. 101 */ 102 static int as3711_bl_update_status(struct backlight_device *bl) 103 { 104 struct as3711_bl_data *data = bl_get_data(bl); 105 struct as3711_bl_supply *supply = to_supply(data); 106 struct as3711 *as3711 = supply->as3711; 107 int brightness = bl->props.brightness; 108 int ret = 0; 109 110 dev_dbg(&bl->dev, "%s(): brightness %u, pwr %x, blank %x, state %x\n", 111 __func__, bl->props.brightness, bl->props.power, 112 bl->props.fb_blank, bl->props.state); 113 114 if (bl->props.power != FB_BLANK_UNBLANK || 115 bl->props.fb_blank != FB_BLANK_UNBLANK || 116 bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) 117 brightness = 0; 118 119 if (data->type == AS3711_BL_SU1) { 120 ret = as3711_set_brightness_v(as3711, brightness, 121 AS3711_STEPUP_CONTROL_1); 122 } else { 123 const struct as3711_bl_pdata *pdata = supply->pdata; 124 125 switch (pdata->su2_feedback) { 126 case AS3711_SU2_VOLTAGE: 127 ret = as3711_set_brightness_v(as3711, brightness, 128 AS3711_STEPUP_CONTROL_2); 129 break; 130 case AS3711_SU2_CURR_AUTO: 131 ret = as3711_set_brightness_auto_i(data, brightness / 4); 132 if (ret < 0) 133 return ret; 134 if (brightness) { 135 ret = as3711_bl_su2_reset(supply); 136 if (ret < 0) 137 return ret; 138 udelay(500); 139 ret = as3711_set_brightness_auto_i(data, brightness); 140 } else { 141 ret = regmap_update_bits(as3711->regmap, 142 AS3711_STEPUP_CONTROL_2, 1, 0); 143 } 144 break; 145 /* Manual one current feedback pin below */ 146 case AS3711_SU2_CURR1: 147 ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE, 148 brightness); 149 break; 150 case AS3711_SU2_CURR2: 151 ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE, 152 brightness); 153 break; 154 case AS3711_SU2_CURR3: 155 ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE, 156 brightness); 157 break; 158 default: 159 ret = -EINVAL; 160 } 161 } 162 if (!ret) 163 data->brightness = brightness; 164 165 return ret; 166 } 167 168 static int as3711_bl_get_brightness(struct backlight_device *bl) 169 { 170 struct as3711_bl_data *data = bl_get_data(bl); 171 172 return data->brightness; 173 } 174 175 static const struct backlight_ops as3711_bl_ops = { 176 .update_status = as3711_bl_update_status, 177 .get_brightness = as3711_bl_get_brightness, 178 }; 179 180 static int as3711_bl_init_su2(struct as3711_bl_supply *supply) 181 { 182 struct as3711 *as3711 = supply->as3711; 183 const struct as3711_bl_pdata *pdata = supply->pdata; 184 u8 ctl = 0; 185 int ret; 186 187 dev_dbg(as3711->dev, "%s(): use %u\n", __func__, pdata->su2_feedback); 188 189 /* Turn SU2 off */ 190 ret = regmap_write(as3711->regmap, AS3711_STEPUP_CONTROL_2, 0); 191 if (ret < 0) 192 return ret; 193 194 switch (pdata->su2_feedback) { 195 case AS3711_SU2_VOLTAGE: 196 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 0); 197 break; 198 case AS3711_SU2_CURR1: 199 ctl = 1; 200 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 1); 201 break; 202 case AS3711_SU2_CURR2: 203 ctl = 4; 204 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 2); 205 break; 206 case AS3711_SU2_CURR3: 207 ctl = 0x10; 208 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 3); 209 break; 210 case AS3711_SU2_CURR_AUTO: 211 if (pdata->su2_auto_curr1) 212 ctl = 2; 213 if (pdata->su2_auto_curr2) 214 ctl |= 8; 215 if (pdata->su2_auto_curr3) 216 ctl |= 0x20; 217 ret = 0; 218 break; 219 default: 220 return -EINVAL; 221 } 222 223 if (!ret) 224 ret = regmap_write(as3711->regmap, AS3711_CURR_CONTROL, ctl); 225 226 return ret; 227 } 228 229 static int as3711_bl_register(struct platform_device *pdev, 230 unsigned int max_brightness, struct as3711_bl_data *su) 231 { 232 struct backlight_properties props = {.type = BACKLIGHT_RAW,}; 233 struct backlight_device *bl; 234 235 /* max tuning I = 31uA for voltage- and 38250uA for current-feedback */ 236 props.max_brightness = max_brightness; 237 238 bl = devm_backlight_device_register(&pdev->dev, 239 su->type == AS3711_BL_SU1 ? 240 "as3711-su1" : "as3711-su2", 241 &pdev->dev, su, 242 &as3711_bl_ops, &props); 243 if (IS_ERR(bl)) { 244 dev_err(&pdev->dev, "failed to register backlight\n"); 245 return PTR_ERR(bl); 246 } 247 248 bl->props.brightness = props.max_brightness; 249 250 backlight_update_status(bl); 251 252 su->bl = bl; 253 254 return 0; 255 } 256 257 static int as3711_backlight_parse_dt(struct device *dev) 258 { 259 struct as3711_bl_pdata *pdata = dev_get_platdata(dev); 260 struct device_node *bl, *fb; 261 int ret; 262 263 bl = of_get_child_by_name(dev->parent->of_node, "backlight"); 264 if (!bl) { 265 dev_dbg(dev, "backlight node not found\n"); 266 return -ENODEV; 267 } 268 269 fb = of_parse_phandle(bl, "su1-dev", 0); 270 if (fb) { 271 of_node_put(fb); 272 273 pdata->su1_fb = true; 274 275 ret = of_property_read_u32(bl, "su1-max-uA", &pdata->su1_max_uA); 276 if (pdata->su1_max_uA <= 0) 277 ret = -EINVAL; 278 if (ret < 0) 279 goto err_put_bl; 280 } 281 282 fb = of_parse_phandle(bl, "su2-dev", 0); 283 if (fb) { 284 int count = 0; 285 286 of_node_put(fb); 287 288 pdata->su2_fb = true; 289 290 ret = of_property_read_u32(bl, "su2-max-uA", &pdata->su2_max_uA); 291 if (pdata->su2_max_uA <= 0) 292 ret = -EINVAL; 293 if (ret < 0) 294 goto err_put_bl; 295 296 if (of_find_property(bl, "su2-feedback-voltage", NULL)) { 297 pdata->su2_feedback = AS3711_SU2_VOLTAGE; 298 count++; 299 } 300 if (of_find_property(bl, "su2-feedback-curr1", NULL)) { 301 pdata->su2_feedback = AS3711_SU2_CURR1; 302 count++; 303 } 304 if (of_find_property(bl, "su2-feedback-curr2", NULL)) { 305 pdata->su2_feedback = AS3711_SU2_CURR2; 306 count++; 307 } 308 if (of_find_property(bl, "su2-feedback-curr3", NULL)) { 309 pdata->su2_feedback = AS3711_SU2_CURR3; 310 count++; 311 } 312 if (of_find_property(bl, "su2-feedback-curr-auto", NULL)) { 313 pdata->su2_feedback = AS3711_SU2_CURR_AUTO; 314 count++; 315 } 316 if (count != 1) { 317 ret = -EINVAL; 318 goto err_put_bl; 319 } 320 321 count = 0; 322 if (of_find_property(bl, "su2-fbprot-lx-sd4", NULL)) { 323 pdata->su2_fbprot = AS3711_SU2_LX_SD4; 324 count++; 325 } 326 if (of_find_property(bl, "su2-fbprot-gpio2", NULL)) { 327 pdata->su2_fbprot = AS3711_SU2_GPIO2; 328 count++; 329 } 330 if (of_find_property(bl, "su2-fbprot-gpio3", NULL)) { 331 pdata->su2_fbprot = AS3711_SU2_GPIO3; 332 count++; 333 } 334 if (of_find_property(bl, "su2-fbprot-gpio4", NULL)) { 335 pdata->su2_fbprot = AS3711_SU2_GPIO4; 336 count++; 337 } 338 if (count != 1) { 339 ret = -EINVAL; 340 goto err_put_bl; 341 } 342 343 count = 0; 344 if (of_find_property(bl, "su2-auto-curr1", NULL)) { 345 pdata->su2_auto_curr1 = true; 346 count++; 347 } 348 if (of_find_property(bl, "su2-auto-curr2", NULL)) { 349 pdata->su2_auto_curr2 = true; 350 count++; 351 } 352 if (of_find_property(bl, "su2-auto-curr3", NULL)) { 353 pdata->su2_auto_curr3 = true; 354 count++; 355 } 356 357 /* 358 * At least one su2-auto-curr* must be specified iff 359 * AS3711_SU2_CURR_AUTO is used 360 */ 361 if (!count ^ (pdata->su2_feedback != AS3711_SU2_CURR_AUTO)) { 362 ret = -EINVAL; 363 goto err_put_bl; 364 } 365 } 366 367 of_node_put(bl); 368 369 return 0; 370 371 err_put_bl: 372 of_node_put(bl); 373 374 return ret; 375 } 376 377 static int as3711_backlight_probe(struct platform_device *pdev) 378 { 379 struct as3711_bl_pdata *pdata = dev_get_platdata(&pdev->dev); 380 struct as3711 *as3711 = dev_get_drvdata(pdev->dev.parent); 381 struct as3711_bl_supply *supply; 382 struct as3711_bl_data *su; 383 unsigned int max_brightness; 384 int ret; 385 386 if (!pdata) { 387 dev_err(&pdev->dev, "No platform data, exiting...\n"); 388 return -ENODEV; 389 } 390 391 if (pdev->dev.parent->of_node) { 392 ret = as3711_backlight_parse_dt(&pdev->dev); 393 if (ret < 0) { 394 dev_err(&pdev->dev, "DT parsing failed: %d\n", ret); 395 return ret; 396 } 397 } 398 399 if (!pdata->su1_fb && !pdata->su2_fb) { 400 dev_err(&pdev->dev, "No framebuffer specified\n"); 401 return -EINVAL; 402 } 403 404 /* 405 * Due to possible hardware damage I chose to block all modes, 406 * unsupported on my hardware. Anyone, wishing to use any of those modes 407 * will have to first review the code, then activate and test it. 408 */ 409 if (pdata->su1_fb || 410 pdata->su2_fbprot != AS3711_SU2_GPIO4 || 411 pdata->su2_feedback != AS3711_SU2_CURR_AUTO) { 412 dev_warn(&pdev->dev, 413 "Attention! An untested mode has been chosen!\n" 414 "Please, review the code, enable, test, and report success:-)\n"); 415 return -EINVAL; 416 } 417 418 supply = devm_kzalloc(&pdev->dev, sizeof(*supply), GFP_KERNEL); 419 if (!supply) 420 return -ENOMEM; 421 422 supply->as3711 = as3711; 423 supply->pdata = pdata; 424 425 if (pdata->su1_fb) { 426 su = &supply->su1; 427 su->type = AS3711_BL_SU1; 428 429 max_brightness = min(pdata->su1_max_uA, 31); 430 ret = as3711_bl_register(pdev, max_brightness, su); 431 if (ret < 0) 432 return ret; 433 } 434 435 if (pdata->su2_fb) { 436 su = &supply->su2; 437 su->type = AS3711_BL_SU2; 438 439 switch (pdata->su2_fbprot) { 440 case AS3711_SU2_GPIO2: 441 case AS3711_SU2_GPIO3: 442 case AS3711_SU2_GPIO4: 443 case AS3711_SU2_LX_SD4: 444 break; 445 default: 446 return -EINVAL; 447 } 448 449 switch (pdata->su2_feedback) { 450 case AS3711_SU2_VOLTAGE: 451 max_brightness = min(pdata->su2_max_uA, 31); 452 break; 453 case AS3711_SU2_CURR1: 454 case AS3711_SU2_CURR2: 455 case AS3711_SU2_CURR3: 456 case AS3711_SU2_CURR_AUTO: 457 max_brightness = min(pdata->su2_max_uA / 150, 255); 458 break; 459 default: 460 return -EINVAL; 461 } 462 463 ret = as3711_bl_init_su2(supply); 464 if (ret < 0) 465 return ret; 466 467 ret = as3711_bl_register(pdev, max_brightness, su); 468 if (ret < 0) 469 return ret; 470 } 471 472 platform_set_drvdata(pdev, supply); 473 474 return 0; 475 } 476 477 static struct platform_driver as3711_backlight_driver = { 478 .driver = { 479 .name = "as3711-backlight", 480 }, 481 .probe = as3711_backlight_probe, 482 }; 483 484 module_platform_driver(as3711_backlight_driver); 485 486 MODULE_DESCRIPTION("Backlight Driver for AS3711 PMICs"); 487 MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de"); 488 MODULE_LICENSE("GPL v2"); 489 MODULE_ALIAS("platform:as3711-backlight"); 490