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