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 24enum as3711_bl_type { 25 AS3711_BL_SU1, 26 AS3711_BL_SU2, 27}; 28 29struct 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 38struct 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 45static 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 56static 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 78static 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 89static 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 */ 107static 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 173static 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 180static const struct backlight_ops as3711_bl_ops = { 181 .update_status = as3711_bl_update_status, 182 .get_brightness = as3711_bl_get_brightness, 183}; 184 185static 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 234static 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 262static int as3711_backlight_parse_dt(struct device *dev) 263{ 264 struct as3711_bl_pdata *pdata = dev_get_platdata(dev); 265 struct device_node *bl = 266 of_find_node_by_name(dev->parent->of_node, "backlight"), *fb; 267 int ret; 268 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 return ret; 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 return ret; 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 return -EINVAL; 319 320 count = 0; 321 if (of_find_property(bl, "su2-fbprot-lx-sd4", NULL)) { 322 pdata->su2_fbprot = AS3711_SU2_LX_SD4; 323 count++; 324 } 325 if (of_find_property(bl, "su2-fbprot-gpio2", NULL)) { 326 pdata->su2_fbprot = AS3711_SU2_GPIO2; 327 count++; 328 } 329 if (of_find_property(bl, "su2-fbprot-gpio3", NULL)) { 330 pdata->su2_fbprot = AS3711_SU2_GPIO3; 331 count++; 332 } 333 if (of_find_property(bl, "su2-fbprot-gpio4", NULL)) { 334 pdata->su2_fbprot = AS3711_SU2_GPIO4; 335 count++; 336 } 337 if (count != 1) 338 return -EINVAL; 339 340 count = 0; 341 if (of_find_property(bl, "su2-auto-curr1", NULL)) { 342 pdata->su2_auto_curr1 = true; 343 count++; 344 } 345 if (of_find_property(bl, "su2-auto-curr2", NULL)) { 346 pdata->su2_auto_curr2 = true; 347 count++; 348 } 349 if (of_find_property(bl, "su2-auto-curr3", NULL)) { 350 pdata->su2_auto_curr3 = true; 351 count++; 352 } 353 354 /* 355 * At least one su2-auto-curr* must be specified iff 356 * AS3711_SU2_CURR_AUTO is used 357 */ 358 if (!count ^ (pdata->su2_feedback != AS3711_SU2_CURR_AUTO)) 359 return -EINVAL; 360 } 361 362 return 0; 363} 364 365static int as3711_backlight_probe(struct platform_device *pdev) 366{ 367 struct as3711_bl_pdata *pdata = dev_get_platdata(&pdev->dev); 368 struct as3711 *as3711 = dev_get_drvdata(pdev->dev.parent); 369 struct as3711_bl_supply *supply; 370 struct as3711_bl_data *su; 371 unsigned int max_brightness; 372 int ret; 373 374 if (!pdata) { 375 dev_err(&pdev->dev, "No platform data, exiting...\n"); 376 return -ENODEV; 377 } 378 379 if (pdev->dev.parent->of_node) { 380 ret = as3711_backlight_parse_dt(&pdev->dev); 381 if (ret < 0) { 382 dev_err(&pdev->dev, "DT parsing failed: %d\n", ret); 383 return ret; 384 } 385 } 386 387 if (!pdata->su1_fb && !pdata->su2_fb) { 388 dev_err(&pdev->dev, "No framebuffer specified\n"); 389 return -EINVAL; 390 } 391 392 /* 393 * Due to possible hardware damage I chose to block all modes, 394 * unsupported on my hardware. Anyone, wishing to use any of those modes 395 * will have to first review the code, then activate and test it. 396 */ 397 if (pdata->su1_fb || 398 pdata->su2_fbprot != AS3711_SU2_GPIO4 || 399 pdata->su2_feedback != AS3711_SU2_CURR_AUTO) { 400 dev_warn(&pdev->dev, 401 "Attention! An untested mode has been chosen!\n" 402 "Please, review the code, enable, test, and report success:-)\n"); 403 return -EINVAL; 404 } 405 406 supply = devm_kzalloc(&pdev->dev, sizeof(*supply), GFP_KERNEL); 407 if (!supply) 408 return -ENOMEM; 409 410 supply->as3711 = as3711; 411 supply->pdata = pdata; 412 413 if (pdata->su1_fb) { 414 su = &supply->su1; 415 su->fb_name = pdata->su1_fb; 416 su->type = AS3711_BL_SU1; 417 418 max_brightness = min(pdata->su1_max_uA, 31); 419 ret = as3711_bl_register(pdev, max_brightness, su); 420 if (ret < 0) 421 return ret; 422 } 423 424 if (pdata->su2_fb) { 425 su = &supply->su2; 426 su->fb_name = pdata->su2_fb; 427 su->type = AS3711_BL_SU2; 428 429 switch (pdata->su2_fbprot) { 430 case AS3711_SU2_GPIO2: 431 case AS3711_SU2_GPIO3: 432 case AS3711_SU2_GPIO4: 433 case AS3711_SU2_LX_SD4: 434 break; 435 default: 436 return -EINVAL; 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 return -EINVAL; 451 } 452 453 ret = as3711_bl_init_su2(supply); 454 if (ret < 0) 455 return ret; 456 457 ret = as3711_bl_register(pdev, max_brightness, su); 458 if (ret < 0) 459 return ret; 460 } 461 462 platform_set_drvdata(pdev, supply); 463 464 return 0; 465} 466 467static struct platform_driver as3711_backlight_driver = { 468 .driver = { 469 .name = "as3711-backlight", 470 }, 471 .probe = as3711_backlight_probe, 472}; 473 474module_platform_driver(as3711_backlight_driver); 475 476MODULE_DESCRIPTION("Backlight Driver for AS3711 PMICs"); 477MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de"); 478MODULE_LICENSE("GPL"); 479MODULE_ALIAS("platform:as3711-backlight"); 480