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