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