linux/drivers/video/backlight/as3711_bl.c
<<
>>
Prefs
   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
  21enum as3711_bl_type {
  22        AS3711_BL_SU1,
  23        AS3711_BL_SU2,
  24};
  25
  26struct as3711_bl_data {
  27        bool powered;
  28        enum as3711_bl_type type;
  29        int brightness;
  30        struct backlight_device *bl;
  31};
  32
  33struct 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
  40static 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
  51static 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
  73static 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
  84static 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 */
 102static 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
 168static 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
 175static const struct backlight_ops as3711_bl_ops = {
 176        .update_status  = as3711_bl_update_status,
 177        .get_brightness = as3711_bl_get_brightness,
 178};
 179
 180static 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
 229static 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
 257static 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
 371err_put_bl:
 372        of_node_put(bl);
 373
 374        return ret;
 375}
 376
 377static 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
 477static struct platform_driver as3711_backlight_driver = {
 478        .driver         = {
 479                .name   = "as3711-backlight",
 480        },
 481        .probe          = as3711_backlight_probe,
 482};
 483
 484module_platform_driver(as3711_backlight_driver);
 485
 486MODULE_DESCRIPTION("Backlight Driver for AS3711 PMICs");
 487MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de");
 488MODULE_LICENSE("GPL v2");
 489MODULE_ALIAS("platform:as3711-backlight");
 490