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