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_property_read_bool(bl, "su2-feedback-voltage")) { 290 pdata->su2_feedback = AS3711_SU2_VOLTAGE; 291 count++; 292 } 293 if (of_property_read_bool(bl, "su2-feedback-curr1")) { 294 pdata->su2_feedback = AS3711_SU2_CURR1; 295 count++; 296 } 297 if (of_property_read_bool(bl, "su2-feedback-curr2")) { 298 pdata->su2_feedback = AS3711_SU2_CURR2; 299 count++; 300 } 301 if (of_property_read_bool(bl, "su2-feedback-curr3")) { 302 pdata->su2_feedback = AS3711_SU2_CURR3; 303 count++; 304 } 305 if (of_property_read_bool(bl, "su2-feedback-curr-auto")) { 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_property_read_bool(bl, "su2-fbprot-lx-sd4")) { 316 pdata->su2_fbprot = AS3711_SU2_LX_SD4; 317 count++; 318 } 319 if (of_property_read_bool(bl, "su2-fbprot-gpio2")) { 320 pdata->su2_fbprot = AS3711_SU2_GPIO2; 321 count++; 322 } 323 if (of_property_read_bool(bl, "su2-fbprot-gpio3")) { 324 pdata->su2_fbprot = AS3711_SU2_GPIO3; 325 count++; 326 } 327 if (of_property_read_bool(bl, "su2-fbprot-gpio4")) { 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_property_read_bool(bl, "su2-auto-curr1")) { 338 pdata->su2_auto_curr1 = true; 339 count++; 340 } 341 if (of_property_read_bool(bl, "su2-auto-curr2")) { 342 pdata->su2_auto_curr2 = true; 343 count++; 344 } 345 if (of_property_read_bool(bl, "su2-auto-curr3")) { 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 return dev_err_probe(&pdev->dev, ret, "DT parsing failed\n"); 388 } 389 390 if (!pdata->su1_fb && !pdata->su2_fb) { 391 dev_err(&pdev->dev, "No framebuffer specified\n"); 392 return -EINVAL; 393 } 394 395 /* 396 * Due to possible hardware damage I chose to block all modes, 397 * unsupported on my hardware. Anyone, wishing to use any of those modes 398 * will have to first review the code, then activate and test it. 399 */ 400 if (pdata->su1_fb || 401 pdata->su2_fbprot != AS3711_SU2_GPIO4 || 402 pdata->su2_feedback != AS3711_SU2_CURR_AUTO) { 403 dev_warn(&pdev->dev, 404 "Attention! An untested mode has been chosen!\n" 405 "Please, review the code, enable, test, and report success:-)\n"); 406 return -EINVAL; 407 } 408 409 supply = devm_kzalloc(&pdev->dev, sizeof(*supply), GFP_KERNEL); 410 if (!supply) 411 return -ENOMEM; 412 413 supply->as3711 = as3711; 414 supply->pdata = pdata; 415 416 if (pdata->su1_fb) { 417 su = &supply->su1; 418 su->type = AS3711_BL_SU1; 419 420 max_brightness = min(pdata->su1_max_uA, 31); 421 ret = as3711_bl_register(pdev, max_brightness, su); 422 if (ret < 0) 423 return ret; 424 } 425 426 if (pdata->su2_fb) { 427 su = &supply->su2; 428 su->type = AS3711_BL_SU2; 429 430 switch (pdata->su2_fbprot) { 431 case AS3711_SU2_GPIO2: 432 case AS3711_SU2_GPIO3: 433 case AS3711_SU2_GPIO4: 434 case AS3711_SU2_LX_SD4: 435 break; 436 default: 437 return -EINVAL; 438 } 439 440 switch (pdata->su2_feedback) { 441 case AS3711_SU2_VOLTAGE: 442 max_brightness = min(pdata->su2_max_uA, 31); 443 break; 444 case AS3711_SU2_CURR1: 445 case AS3711_SU2_CURR2: 446 case AS3711_SU2_CURR3: 447 case AS3711_SU2_CURR_AUTO: 448 max_brightness = min(pdata->su2_max_uA / 150, 255); 449 break; 450 default: 451 return -EINVAL; 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 return ret; 461 } 462 463 platform_set_drvdata(pdev, supply); 464 465 return 0; 466 } 467 468 static struct platform_driver as3711_backlight_driver = { 469 .driver = { 470 .name = "as3711-backlight", 471 }, 472 .probe = as3711_backlight_probe, 473 }; 474 475 module_platform_driver(as3711_backlight_driver); 476 477 MODULE_DESCRIPTION("Backlight Driver for AS3711 PMICs"); 478 MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de"); 479 MODULE_LICENSE("GPL v2"); 480 MODULE_ALIAS("platform:as3711-backlight"); 481