root/drivers/video/backlight/as3711_bl.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. to_supply
  2. as3711_set_brightness_auto_i
  3. as3711_set_brightness_v
  4. as3711_bl_su2_reset
  5. as3711_bl_update_status
  6. as3711_bl_get_brightness
  7. as3711_bl_init_su2
  8. as3711_bl_register
  9. as3711_backlight_parse_dt
  10. as3711_backlight_probe

   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 = bl->props.brightness;
 108         int ret = 0;
 109 
 110         dev_dbg(&bl->dev, "%s(): brightness %u, pwr %x, blank %x, state %x\n",
 111                 __func__, bl->props.brightness, bl->props.power,
 112                 bl->props.fb_blank, bl->props.state);
 113 
 114         if (bl->props.power != FB_BLANK_UNBLANK ||
 115             bl->props.fb_blank != FB_BLANK_UNBLANK ||
 116             bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
 117                 brightness = 0;
 118 
 119         if (data->type == AS3711_BL_SU1) {
 120                 ret = as3711_set_brightness_v(as3711, brightness,
 121                                               AS3711_STEPUP_CONTROL_1);
 122         } else {
 123                 const struct as3711_bl_pdata *pdata = supply->pdata;
 124 
 125                 switch (pdata->su2_feedback) {
 126                 case AS3711_SU2_VOLTAGE:
 127                         ret = as3711_set_brightness_v(as3711, brightness,
 128                                                       AS3711_STEPUP_CONTROL_2);
 129                         break;
 130                 case AS3711_SU2_CURR_AUTO:
 131                         ret = as3711_set_brightness_auto_i(data, brightness / 4);
 132                         if (ret < 0)
 133                                 return ret;
 134                         if (brightness) {
 135                                 ret = as3711_bl_su2_reset(supply);
 136                                 if (ret < 0)
 137                                         return ret;
 138                                 udelay(500);
 139                                 ret = as3711_set_brightness_auto_i(data, brightness);
 140                         } else {
 141                                 ret = regmap_update_bits(as3711->regmap,
 142                                                 AS3711_STEPUP_CONTROL_2, 1, 0);
 143                         }
 144                         break;
 145                 /* Manual one current feedback pin below */
 146                 case AS3711_SU2_CURR1:
 147                         ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE,
 148                                            brightness);
 149                         break;
 150                 case AS3711_SU2_CURR2:
 151                         ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE,
 152                                            brightness);
 153                         break;
 154                 case AS3711_SU2_CURR3:
 155                         ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE,
 156                                            brightness);
 157                         break;
 158                 default:
 159                         ret = -EINVAL;
 160                 }
 161         }
 162         if (!ret)
 163                 data->brightness = brightness;
 164 
 165         return ret;
 166 }
 167 
 168 static int as3711_bl_get_brightness(struct backlight_device *bl)
 169 {
 170         struct as3711_bl_data *data = bl_get_data(bl);
 171 
 172         return data->brightness;
 173 }
 174 
 175 static const struct backlight_ops as3711_bl_ops = {
 176         .update_status  = as3711_bl_update_status,
 177         .get_brightness = as3711_bl_get_brightness,
 178 };
 179 
 180 static int as3711_bl_init_su2(struct as3711_bl_supply *supply)
 181 {
 182         struct as3711 *as3711 = supply->as3711;
 183         const struct as3711_bl_pdata *pdata = supply->pdata;
 184         u8 ctl = 0;
 185         int ret;
 186 
 187         dev_dbg(as3711->dev, "%s(): use %u\n", __func__, pdata->su2_feedback);
 188 
 189         /* Turn SU2 off */
 190         ret = regmap_write(as3711->regmap, AS3711_STEPUP_CONTROL_2, 0);
 191         if (ret < 0)
 192                 return ret;
 193 
 194         switch (pdata->su2_feedback) {
 195         case AS3711_SU2_VOLTAGE:
 196                 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 0);
 197                 break;
 198         case AS3711_SU2_CURR1:
 199                 ctl = 1;
 200                 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 1);
 201                 break;
 202         case AS3711_SU2_CURR2:
 203                 ctl = 4;
 204                 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 2);
 205                 break;
 206         case AS3711_SU2_CURR3:
 207                 ctl = 0x10;
 208                 ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 3);
 209                 break;
 210         case AS3711_SU2_CURR_AUTO:
 211                 if (pdata->su2_auto_curr1)
 212                         ctl = 2;
 213                 if (pdata->su2_auto_curr2)
 214                         ctl |= 8;
 215                 if (pdata->su2_auto_curr3)
 216                         ctl |= 0x20;
 217                 ret = 0;
 218                 break;
 219         default:
 220                 return -EINVAL;
 221         }
 222 
 223         if (!ret)
 224                 ret = regmap_write(as3711->regmap, AS3711_CURR_CONTROL, ctl);
 225 
 226         return ret;
 227 }
 228 
 229 static int as3711_bl_register(struct platform_device *pdev,
 230                               unsigned int max_brightness, struct as3711_bl_data *su)
 231 {
 232         struct backlight_properties props = {.type = BACKLIGHT_RAW,};
 233         struct backlight_device *bl;
 234 
 235         /* max tuning I = 31uA for voltage- and 38250uA for current-feedback */
 236         props.max_brightness = max_brightness;
 237 
 238         bl = devm_backlight_device_register(&pdev->dev,
 239                                        su->type == AS3711_BL_SU1 ?
 240                                        "as3711-su1" : "as3711-su2",
 241                                        &pdev->dev, su,
 242                                        &as3711_bl_ops, &props);
 243         if (IS_ERR(bl)) {
 244                 dev_err(&pdev->dev, "failed to register backlight\n");
 245                 return PTR_ERR(bl);
 246         }
 247 
 248         bl->props.brightness = props.max_brightness;
 249 
 250         backlight_update_status(bl);
 251 
 252         su->bl = bl;
 253 
 254         return 0;
 255 }
 256 
 257 static int as3711_backlight_parse_dt(struct device *dev)
 258 {
 259         struct as3711_bl_pdata *pdata = dev_get_platdata(dev);
 260         struct device_node *bl, *fb;
 261         int ret;
 262 
 263         bl = of_get_child_by_name(dev->parent->of_node, "backlight");
 264         if (!bl) {
 265                 dev_dbg(dev, "backlight node not found\n");
 266                 return -ENODEV;
 267         }
 268 
 269         fb = of_parse_phandle(bl, "su1-dev", 0);
 270         if (fb) {
 271                 of_node_put(fb);
 272 
 273                 pdata->su1_fb = true;
 274 
 275                 ret = of_property_read_u32(bl, "su1-max-uA", &pdata->su1_max_uA);
 276                 if (pdata->su1_max_uA <= 0)
 277                         ret = -EINVAL;
 278                 if (ret < 0)
 279                         goto err_put_bl;
 280         }
 281 
 282         fb = of_parse_phandle(bl, "su2-dev", 0);
 283         if (fb) {
 284                 int count = 0;
 285 
 286                 of_node_put(fb);
 287 
 288                 pdata->su2_fb = true;
 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                         goto err_put_bl;
 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                         ret = -EINVAL;
 318                         goto err_put_bl;
 319                 }
 320 
 321                 count = 0;
 322                 if (of_find_property(bl, "su2-fbprot-lx-sd4", NULL)) {
 323                         pdata->su2_fbprot = AS3711_SU2_LX_SD4;
 324                         count++;
 325                 }
 326                 if (of_find_property(bl, "su2-fbprot-gpio2", NULL)) {
 327                         pdata->su2_fbprot = AS3711_SU2_GPIO2;
 328                         count++;
 329                 }
 330                 if (of_find_property(bl, "su2-fbprot-gpio3", NULL)) {
 331                         pdata->su2_fbprot = AS3711_SU2_GPIO3;
 332                         count++;
 333                 }
 334                 if (of_find_property(bl, "su2-fbprot-gpio4", NULL)) {
 335                         pdata->su2_fbprot = AS3711_SU2_GPIO4;
 336                         count++;
 337                 }
 338                 if (count != 1) {
 339                         ret = -EINVAL;
 340                         goto err_put_bl;
 341                 }
 342 
 343                 count = 0;
 344                 if (of_find_property(bl, "su2-auto-curr1", NULL)) {
 345                         pdata->su2_auto_curr1 = true;
 346                         count++;
 347                 }
 348                 if (of_find_property(bl, "su2-auto-curr2", NULL)) {
 349                         pdata->su2_auto_curr2 = true;
 350                         count++;
 351                 }
 352                 if (of_find_property(bl, "su2-auto-curr3", NULL)) {
 353                         pdata->su2_auto_curr3 = true;
 354                         count++;
 355                 }
 356 
 357                 /*
 358                  * At least one su2-auto-curr* must be specified iff
 359                  * AS3711_SU2_CURR_AUTO is used
 360                  */
 361                 if (!count ^ (pdata->su2_feedback != AS3711_SU2_CURR_AUTO)) {
 362                         ret = -EINVAL;
 363                         goto err_put_bl;
 364                 }
 365         }
 366 
 367         of_node_put(bl);
 368 
 369         return 0;
 370 
 371 err_put_bl:
 372         of_node_put(bl);
 373 
 374         return ret;
 375 }
 376 
 377 static int as3711_backlight_probe(struct platform_device *pdev)
 378 {
 379         struct as3711_bl_pdata *pdata = dev_get_platdata(&pdev->dev);
 380         struct as3711 *as3711 = dev_get_drvdata(pdev->dev.parent);
 381         struct as3711_bl_supply *supply;
 382         struct as3711_bl_data *su;
 383         unsigned int max_brightness;
 384         int ret;
 385 
 386         if (!pdata) {
 387                 dev_err(&pdev->dev, "No platform data, exiting...\n");
 388                 return -ENODEV;
 389         }
 390 
 391         if (pdev->dev.parent->of_node) {
 392                 ret = as3711_backlight_parse_dt(&pdev->dev);
 393                 if (ret < 0) {
 394                         dev_err(&pdev->dev, "DT parsing failed: %d\n", ret);
 395                         return ret;
 396                 }
 397         }
 398 
 399         if (!pdata->su1_fb && !pdata->su2_fb) {
 400                 dev_err(&pdev->dev, "No framebuffer specified\n");
 401                 return -EINVAL;
 402         }
 403 
 404         /*
 405          * Due to possible hardware damage I chose to block all modes,
 406          * unsupported on my hardware. Anyone, wishing to use any of those modes
 407          * will have to first review the code, then activate and test it.
 408          */
 409         if (pdata->su1_fb ||
 410             pdata->su2_fbprot != AS3711_SU2_GPIO4 ||
 411             pdata->su2_feedback != AS3711_SU2_CURR_AUTO) {
 412                 dev_warn(&pdev->dev,
 413                          "Attention! An untested mode has been chosen!\n"
 414                          "Please, review the code, enable, test, and report success:-)\n");
 415                 return -EINVAL;
 416         }
 417 
 418         supply = devm_kzalloc(&pdev->dev, sizeof(*supply), GFP_KERNEL);
 419         if (!supply)
 420                 return -ENOMEM;
 421 
 422         supply->as3711 = as3711;
 423         supply->pdata = pdata;
 424 
 425         if (pdata->su1_fb) {
 426                 su = &supply->su1;
 427                 su->type = AS3711_BL_SU1;
 428 
 429                 max_brightness = min(pdata->su1_max_uA, 31);
 430                 ret = as3711_bl_register(pdev, max_brightness, su);
 431                 if (ret < 0)
 432                         return ret;
 433         }
 434 
 435         if (pdata->su2_fb) {
 436                 su = &supply->su2;
 437                 su->type = AS3711_BL_SU2;
 438 
 439                 switch (pdata->su2_fbprot) {
 440                 case AS3711_SU2_GPIO2:
 441                 case AS3711_SU2_GPIO3:
 442                 case AS3711_SU2_GPIO4:
 443                 case AS3711_SU2_LX_SD4:
 444                         break;
 445                 default:
 446                         return -EINVAL;
 447                 }
 448 
 449                 switch (pdata->su2_feedback) {
 450                 case AS3711_SU2_VOLTAGE:
 451                         max_brightness = min(pdata->su2_max_uA, 31);
 452                         break;
 453                 case AS3711_SU2_CURR1:
 454                 case AS3711_SU2_CURR2:
 455                 case AS3711_SU2_CURR3:
 456                 case AS3711_SU2_CURR_AUTO:
 457                         max_brightness = min(pdata->su2_max_uA / 150, 255);
 458                         break;
 459                 default:
 460                         return -EINVAL;
 461                 }
 462 
 463                 ret = as3711_bl_init_su2(supply);
 464                 if (ret < 0)
 465                         return ret;
 466 
 467                 ret = as3711_bl_register(pdev, max_brightness, su);
 468                 if (ret < 0)
 469                         return ret;
 470         }
 471 
 472         platform_set_drvdata(pdev, supply);
 473 
 474         return 0;
 475 }
 476 
 477 static struct platform_driver as3711_backlight_driver = {
 478         .driver         = {
 479                 .name   = "as3711-backlight",
 480         },
 481         .probe          = as3711_backlight_probe,
 482 };
 483 
 484 module_platform_driver(as3711_backlight_driver);
 485 
 486 MODULE_DESCRIPTION("Backlight Driver for AS3711 PMICs");
 487 MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de");
 488 MODULE_LICENSE("GPL v2");
 489 MODULE_ALIAS("platform:as3711-backlight");

/* [<][>][^][v][top][bottom][index][help] */