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; 108 int ret = 0; 109 110 brightness = backlight_get_brightness(bl); 111 112 if (data->type == AS3711_BL_SU1) { 113 ret = as3711_set_brightness_v(as3711, brightness, 114 AS3711_STEPUP_CONTROL_1); 115 } else { 116 const struct as3711_bl_pdata *pdata = supply->pdata; 117 118 switch (pdata->su2_feedback) { 119 case AS3711_SU2_VOLTAGE: 120 ret = as3711_set_brightness_v(as3711, brightness, 121 AS3711_STEPUP_CONTROL_2); 122 break; 123 case AS3711_SU2_CURR_AUTO: 124 ret = as3711_set_brightness_auto_i(data, brightness / 4); 125 if (ret < 0) 126 return ret; 127 if (brightness) { 128 ret = as3711_bl_su2_reset(supply); 129 if (ret < 0) 130 return ret; 131 udelay(500); 132 ret = as3711_set_brightness_auto_i(data, brightness); 133 } else { 134 ret = regmap_update_bits(as3711->regmap, 135 AS3711_STEPUP_CONTROL_2, 1, 0); 136 } 137 break; 138 /* Manual one current feedback pin below */ 139 case AS3711_SU2_CURR1: 140 ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE, 141 brightness); 142 break; 143 case AS3711_SU2_CURR2: 144 ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE, 145 brightness); 146 break; 147 case AS3711_SU2_CURR3: 148 ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE, 149 brightness); 150 break; 151 default: 152 ret = -EINVAL; 153 } 154 } 155 if (!ret) 156 data->brightness = brightness; 157 158 return ret; 159 } 160 161 static int as3711_bl_get_brightness(struct backlight_device *bl) 162 { 163 struct as3711_bl_data *data = bl_get_data(bl); 164 165 return data->brightness; 166 } 167 168 static const struct backlight_ops as3711_bl_ops = { 169 .update_status = as3711_bl_update_status, 170 .get_brightness = as3711_bl_get_brightness, 171 }; 172 173 static int as3711_bl_init_su2(struct as3711_bl_supply *supply) 174 { 175 struct as3711 *as3711 = supply->as3711; 176 const struct as3711_bl_pdata *pdata = supply->pdata; 177 u8 ctl = 0; 178 int ret; 179 180 dev_dbg(as3711->dev, "%s(): use %u\n", __func__, pdata->su2_feedback); 181 182 /* Turn SU2 off */ 183 ret = regmap_write(as3711->regmap, AS3711_STEPUP_CONTROL_2, 0); 184 if (ret < 0) 185 return ret; 186 187 switch (pdata->su2_feedback) { 188 case AS3711_SU2_VOLTAGE: 189 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 0); 190 break; 191 case AS3711_SU2_CURR1: 192 ctl = 1; 193 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 1); 194 break; 195 case AS3711_SU2_CURR2: 196 ctl = 4; 197 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 2); 198 break; 199 case AS3711_SU2_CURR3: 200 ctl = 0x10; 201 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 3); 202 break; 203 case AS3711_SU2_CURR_AUTO: 204 if (pdata->su2_auto_curr1) 205 ctl = 2; 206 if (pdata->su2_auto_curr2) 207 ctl |= 8; 208 if (pdata->su2_auto_curr3) 209 ctl |= 0x20; 210 ret = 0; 211 break; 212 default: 213 return -EINVAL; 214 } 215 216 if (!ret) 217 ret = regmap_write(as3711->regmap, AS3711_CURR_CONTROL, ctl); 218 219 return ret; 220 } 221 222 static int as3711_bl_register(struct platform_device *pdev, 223 unsigned int max_brightness, struct as3711_bl_data *su) 224 { 225 struct backlight_properties props = {.type = BACKLIGHT_RAW,}; 226 struct backlight_device *bl; 227 228 /* max tuning I = 31uA for voltage- and 38250uA for current-feedback */ 229 props.max_brightness = max_brightness; 230 231 bl = devm_backlight_device_register(&pdev->dev, 232 su->type == AS3711_BL_SU1 ? 233 "as3711-su1" : "as3711-su2", 234 &pdev->dev, su, 235 &as3711_bl_ops, &props); 236 if (IS_ERR(bl)) { 237 dev_err(&pdev->dev, "failed to register backlight\n"); 238 return PTR_ERR(bl); 239 } 240 241 bl->props.brightness = props.max_brightness; 242 243 backlight_update_status(bl); 244 245 su->bl = bl; 246 247 return 0; 248 } 249 250 static int as3711_backlight_parse_dt(struct device *dev) 251 { 252 struct as3711_bl_pdata *pdata = dev_get_platdata(dev); 253 struct device_node *bl, *fb; 254 int ret; 255 256 bl = of_get_child_by_name(dev->parent->of_node, "backlight"); 257 if (!bl) { 258 dev_dbg(dev, "backlight node not found\n"); 259 return -ENODEV; 260 } 261 262 fb = of_parse_phandle(bl, "su1-dev", 0); 263 if (fb) { 264 of_node_put(fb); 265 266 pdata->su1_fb = true; 267 268 ret = of_property_read_u32(bl, "su1-max-uA", &pdata->su1_max_uA); 269 if (pdata->su1_max_uA <= 0) 270 ret = -EINVAL; 271 if (ret < 0) 272 goto err_put_bl; 273 } 274 275 fb = of_parse_phandle(bl, "su2-dev", 0); 276 if (fb) { 277 int count = 0; 278 279 of_node_put(fb); 280 281 pdata->su2_fb = true; 282 283 ret = of_property_read_u32(bl, "su2-max-uA", &pdata->su2_max_uA); 284 if (pdata->su2_max_uA <= 0) 285 ret = -EINVAL; 286 if (ret < 0) 287 goto err_put_bl; 288 289 if (of_find_property(bl, "su2-feedback-voltage", NULL)) { 290 pdata->su2_feedback = AS3711_SU2_VOLTAGE; 291 count++; 292 } 293 if (of_find_property(bl, "su2-feedback-curr1", NULL)) { 294 pdata->su2_feedback = AS3711_SU2_CURR1; 295 count++; 296 } 297 if (of_find_property(bl, "su2-feedback-curr2", NULL)) { 298 pdata->su2_feedback = AS3711_SU2_CURR2; 299 count++; 300 } 301 if (of_find_property(bl, "su2-feedback-curr3", NULL)) { 302 pdata->su2_feedback = AS3711_SU2_CURR3; 303 count++; 304 } 305 if (of_find_property(bl, "su2-feedback-curr-auto", NULL)) { 306 pdata->su2_feedback = AS3711_SU2_CURR_AUTO; 307 count++; 308 } 309 if (count != 1) { 310 ret = -EINVAL; 311 goto err_put_bl; 312 } 313 314 count = 0; 315 if (of_find_property(bl, "su2-fbprot-lx-sd4", NULL)) { 316 pdata->su2_fbprot = AS3711_SU2_LX_SD4; 317 count++; 318 } 319 if (of_find_property(bl, "su2-fbprot-gpio2", NULL)) { 320 pdata->su2_fbprot = AS3711_SU2_GPIO2; 321 count++; 322 } 323 if (of_find_property(bl, "su2-fbprot-gpio3", NULL)) { 324 pdata->su2_fbprot = AS3711_SU2_GPIO3; 325 count++; 326 } 327 if (of_find_property(bl, "su2-fbprot-gpio4", NULL)) { 328 pdata->su2_fbprot = AS3711_SU2_GPIO4; 329 count++; 330 } 331 if (count != 1) { 332 ret = -EINVAL; 333 goto err_put_bl; 334 } 335 336 count = 0; 337 if (of_find_property(bl, "su2-auto-curr1", NULL)) { 338 pdata->su2_auto_curr1 = true; 339 count++; 340 } 341 if (of_find_property(bl, "su2-auto-curr2", NULL)) { 342 pdata->su2_auto_curr2 = true; 343 count++; 344 } 345 if (of_find_property(bl, "su2-auto-curr3", NULL)) { 346 pdata->su2_auto_curr3 = true; 347 count++; 348 } 349 350 /* 351 * At least one su2-auto-curr* must be specified iff 352 * AS3711_SU2_CURR_AUTO is used 353 */ 354 if (!count ^ (pdata->su2_feedback != AS3711_SU2_CURR_AUTO)) { 355 ret = -EINVAL; 356 goto err_put_bl; 357 } 358 } 359 360 of_node_put(bl); 361 362 return 0; 363 364 err_put_bl: 365 of_node_put(bl); 366 367 return ret; 368 } 369 370 static int as3711_backlight_probe(struct platform_device *pdev) 371 { 372 struct as3711_bl_pdata *pdata = dev_get_platdata(&pdev->dev); 373 struct as3711 *as3711 = dev_get_drvdata(pdev->dev.parent); 374 struct as3711_bl_supply *supply; 375 struct as3711_bl_data *su; 376 unsigned int max_brightness; 377 int ret; 378 379 if (!pdata) { 380 dev_err(&pdev->dev, "No platform data, exiting...\n"); 381 return -ENODEV; 382 } 383 384 if (pdev->dev.parent->of_node) { 385 ret = as3711_backlight_parse_dt(&pdev->dev); 386 if (ret < 0) { 387 dev_err(&pdev->dev, "DT parsing failed: %d\n", ret); 388 return ret; 389 } 390 } 391 392 if (!pdata->su1_fb && !pdata->su2_fb) { 393 dev_err(&pdev->dev, "No framebuffer specified\n"); 394 return -EINVAL; 395 } 396 397 /* 398 * Due to possible hardware damage I chose to block all modes, 399 * unsupported on my hardware. Anyone, wishing to use any of those modes 400 * will have to first review the code, then activate and test it. 401 */ 402 if (pdata->su1_fb || 403 pdata->su2_fbprot != AS3711_SU2_GPIO4 || 404 pdata->su2_feedback != AS3711_SU2_CURR_AUTO) { 405 dev_warn(&pdev->dev, 406 "Attention! An untested mode has been chosen!\n" 407 "Please, review the code, enable, test, and report success:-)\n"); 408 return -EINVAL; 409 } 410 411 supply = devm_kzalloc(&pdev->dev, sizeof(*supply), GFP_KERNEL); 412 if (!supply) 413 return -ENOMEM; 414 415 supply->as3711 = as3711; 416 supply->pdata = pdata; 417 418 if (pdata->su1_fb) { 419 su = &supply->su1; 420 su->type = AS3711_BL_SU1; 421 422 max_brightness = min(pdata->su1_max_uA, 31); 423 ret = as3711_bl_register(pdev, max_brightness, su); 424 if (ret < 0) 425 return ret; 426 } 427 428 if (pdata->su2_fb) { 429 su = &supply->su2; 430 su->type = AS3711_BL_SU2; 431 432 switch (pdata->su2_fbprot) { 433 case AS3711_SU2_GPIO2: 434 case AS3711_SU2_GPIO3: 435 case AS3711_SU2_GPIO4: 436 case AS3711_SU2_LX_SD4: 437 break; 438 default: 439 return -EINVAL; 440 } 441 442 switch (pdata->su2_feedback) { 443 case AS3711_SU2_VOLTAGE: 444 max_brightness = min(pdata->su2_max_uA, 31); 445 break; 446 case AS3711_SU2_CURR1: 447 case AS3711_SU2_CURR2: 448 case AS3711_SU2_CURR3: 449 case AS3711_SU2_CURR_AUTO: 450 max_brightness = min(pdata->su2_max_uA / 150, 255); 451 break; 452 default: 453 return -EINVAL; 454 } 455 456 ret = as3711_bl_init_su2(supply); 457 if (ret < 0) 458 return ret; 459 460 ret = as3711_bl_register(pdev, max_brightness, su); 461 if (ret < 0) 462 return ret; 463 } 464 465 platform_set_drvdata(pdev, supply); 466 467 return 0; 468 } 469 470 static struct platform_driver as3711_backlight_driver = { 471 .driver = { 472 .name = "as3711-backlight", 473 }, 474 .probe = as3711_backlight_probe, 475 }; 476 477 module_platform_driver(as3711_backlight_driver); 478 479 MODULE_DESCRIPTION("Backlight Driver for AS3711 PMICs"); 480 MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de"); 481 MODULE_LICENSE("GPL v2"); 482 MODULE_ALIAS("platform:as3711-backlight"); 483