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