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 = backlight_device_register(su->type == AS3711_BL_SU1 ? 244 "as3711-su1" : "as3711-su2", 245 &pdev->dev, su, 246 &as3711_bl_ops, &props); 247 if (IS_ERR(bl)) { 248 dev_err(&pdev->dev, "failed to register backlight\n"); 249 return PTR_ERR(bl); 250 } 251 252 bl->props.brightness = props.max_brightness; 253 254 backlight_update_status(bl); 255 256 su->bl = bl; 257 258 return 0; 259 } 260 261 static int as3711_backlight_parse_dt(struct device *dev) 262 { 263 struct as3711_bl_pdata *pdata = dev_get_platdata(dev); 264 struct device_node *bl = 265 of_find_node_by_name(dev->parent->of_node, "backlight"), *fb; 266 int ret; 267 268 if (!bl) { 269 dev_dbg(dev, "backlight node not found\n"); 270 return -ENODEV; 271 } 272 273 fb = of_parse_phandle(bl, "su1-dev", 0); 274 if (fb) { 275 pdata->su1_fb = fb->full_name; 276 277 ret = of_property_read_u32(bl, "su1-max-uA", &pdata->su1_max_uA); 278 if (pdata->su1_max_uA <= 0) 279 ret = -EINVAL; 280 if (ret < 0) 281 return ret; 282 } 283 284 fb = of_parse_phandle(bl, "su2-dev", 0); 285 if (fb) { 286 int count = 0; 287 288 pdata->su2_fb = fb->full_name; 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 return ret; 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 return -EINVAL; 318 319 count = 0; 320 if (of_find_property(bl, "su2-fbprot-lx-sd4", NULL)) { 321 pdata->su2_fbprot = AS3711_SU2_LX_SD4; 322 count++; 323 } 324 if (of_find_property(bl, "su2-fbprot-gpio2", NULL)) { 325 pdata->su2_fbprot = AS3711_SU2_GPIO2; 326 count++; 327 } 328 if (of_find_property(bl, "su2-fbprot-gpio3", NULL)) { 329 pdata->su2_fbprot = AS3711_SU2_GPIO3; 330 count++; 331 } 332 if (of_find_property(bl, "su2-fbprot-gpio4", NULL)) { 333 pdata->su2_fbprot = AS3711_SU2_GPIO4; 334 count++; 335 } 336 if (count != 1) 337 return -EINVAL; 338 339 count = 0; 340 if (of_find_property(bl, "su2-auto-curr1", NULL)) { 341 pdata->su2_auto_curr1 = true; 342 count++; 343 } 344 if (of_find_property(bl, "su2-auto-curr2", NULL)) { 345 pdata->su2_auto_curr2 = true; 346 count++; 347 } 348 if (of_find_property(bl, "su2-auto-curr3", NULL)) { 349 pdata->su2_auto_curr3 = true; 350 count++; 351 } 352 353 /* 354 * At least one su2-auto-curr* must be specified iff 355 * AS3711_SU2_CURR_AUTO is used 356 */ 357 if (!count ^ (pdata->su2_feedback != AS3711_SU2_CURR_AUTO)) 358 return -EINVAL; 359 } 360 361 return 0; 362 } 363 364 static int as3711_backlight_probe(struct platform_device *pdev) 365 { 366 struct as3711_bl_pdata *pdata = dev_get_platdata(&pdev->dev); 367 struct as3711 *as3711 = dev_get_drvdata(pdev->dev.parent); 368 struct as3711_bl_supply *supply; 369 struct as3711_bl_data *su; 370 unsigned int max_brightness; 371 int ret; 372 373 if (!pdata) { 374 dev_err(&pdev->dev, "No platform data, exiting...\n"); 375 return -ENODEV; 376 } 377 378 if (pdev->dev.parent->of_node) { 379 ret = as3711_backlight_parse_dt(&pdev->dev); 380 if (ret < 0) { 381 dev_err(&pdev->dev, "DT parsing failed: %d\n", ret); 382 return ret; 383 } 384 } 385 386 if (!pdata->su1_fb && !pdata->su2_fb) { 387 dev_err(&pdev->dev, "No framebuffer specified\n"); 388 return -EINVAL; 389 } 390 391 /* 392 * Due to possible hardware damage I chose to block all modes, 393 * unsupported on my hardware. Anyone, wishing to use any of those modes 394 * will have to first review the code, then activate and test it. 395 */ 396 if (pdata->su1_fb || 397 pdata->su2_fbprot != AS3711_SU2_GPIO4 || 398 pdata->su2_feedback != AS3711_SU2_CURR_AUTO) { 399 dev_warn(&pdev->dev, 400 "Attention! An untested mode has been chosen!\n" 401 "Please, review the code, enable, test, and report success:-)\n"); 402 return -EINVAL; 403 } 404 405 supply = devm_kzalloc(&pdev->dev, sizeof(*supply), GFP_KERNEL); 406 if (!supply) 407 return -ENOMEM; 408 409 supply->as3711 = as3711; 410 supply->pdata = pdata; 411 412 if (pdata->su1_fb) { 413 su = &supply->su1; 414 su->fb_name = pdata->su1_fb; 415 su->type = AS3711_BL_SU1; 416 417 max_brightness = min(pdata->su1_max_uA, 31); 418 ret = as3711_bl_register(pdev, max_brightness, su); 419 if (ret < 0) 420 return ret; 421 } 422 423 if (pdata->su2_fb) { 424 su = &supply->su2; 425 su->fb_name = pdata->su2_fb; 426 su->type = AS3711_BL_SU2; 427 428 switch (pdata->su2_fbprot) { 429 case AS3711_SU2_GPIO2: 430 case AS3711_SU2_GPIO3: 431 case AS3711_SU2_GPIO4: 432 case AS3711_SU2_LX_SD4: 433 break; 434 default: 435 ret = -EINVAL; 436 goto esu2; 437 } 438 439 switch (pdata->su2_feedback) { 440 case AS3711_SU2_VOLTAGE: 441 max_brightness = min(pdata->su2_max_uA, 31); 442 break; 443 case AS3711_SU2_CURR1: 444 case AS3711_SU2_CURR2: 445 case AS3711_SU2_CURR3: 446 case AS3711_SU2_CURR_AUTO: 447 max_brightness = min(pdata->su2_max_uA / 150, 255); 448 break; 449 default: 450 ret = -EINVAL; 451 goto esu2; 452 } 453 454 ret = as3711_bl_init_su2(supply); 455 if (ret < 0) 456 return ret; 457 458 ret = as3711_bl_register(pdev, max_brightness, su); 459 if (ret < 0) 460 goto esu2; 461 } 462 463 platform_set_drvdata(pdev, supply); 464 465 return 0; 466 467 esu2: 468 backlight_device_unregister(supply->su1.bl); 469 return ret; 470 } 471 472 static int as3711_backlight_remove(struct platform_device *pdev) 473 { 474 struct as3711_bl_supply *supply = platform_get_drvdata(pdev); 475 476 backlight_device_unregister(supply->su1.bl); 477 backlight_device_unregister(supply->su2.bl); 478 479 return 0; 480 } 481 482 static struct platform_driver as3711_backlight_driver = { 483 .driver = { 484 .name = "as3711-backlight", 485 .owner = THIS_MODULE, 486 }, 487 .probe = as3711_backlight_probe, 488 .remove = as3711_backlight_remove, 489 }; 490 491 module_platform_driver(as3711_backlight_driver); 492 493 MODULE_DESCRIPTION("Backlight Driver for AS3711 PMICs"); 494 MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de"); 495 MODULE_LICENSE("GPL"); 496 MODULE_ALIAS("platform:as3711-backlight"); 497