linux/drivers/video/backlight/pm8941-wled.c
<<
>>
Prefs
   1/* Copyright (c) 2015, Sony Mobile Communications, AB.
   2 *
   3 * This program is free software; you can redistribute it and/or modify
   4 * it under the terms of the GNU General Public License version 2 and
   5 * only version 2 as published by the Free Software Foundation.
   6 *
   7 * This program is distributed in the hope that it will be useful,
   8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
   9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  10 * GNU General Public License for more details.
  11 */
  12
  13#include <linux/kernel.h>
  14#include <linux/backlight.h>
  15#include <linux/module.h>
  16#include <linux/of.h>
  17#include <linux/of_device.h>
  18#include <linux/regmap.h>
  19
  20/* From DT binding */
  21#define PM8941_WLED_DEFAULT_BRIGHTNESS          2048
  22
  23#define PM8941_WLED_REG_VAL_BASE                0x40
  24#define  PM8941_WLED_REG_VAL_MAX                0xFFF
  25
  26#define PM8941_WLED_REG_MOD_EN                  0x46
  27#define  PM8941_WLED_REG_MOD_EN_BIT             BIT(7)
  28#define  PM8941_WLED_REG_MOD_EN_MASK            BIT(7)
  29
  30#define PM8941_WLED_REG_SYNC                    0x47
  31#define  PM8941_WLED_REG_SYNC_MASK              0x07
  32#define  PM8941_WLED_REG_SYNC_LED1              BIT(0)
  33#define  PM8941_WLED_REG_SYNC_LED2              BIT(1)
  34#define  PM8941_WLED_REG_SYNC_LED3              BIT(2)
  35#define  PM8941_WLED_REG_SYNC_ALL               0x07
  36#define  PM8941_WLED_REG_SYNC_CLEAR             0x00
  37
  38#define PM8941_WLED_REG_FREQ                    0x4c
  39#define  PM8941_WLED_REG_FREQ_MASK              0x0f
  40
  41#define PM8941_WLED_REG_OVP                     0x4d
  42#define  PM8941_WLED_REG_OVP_MASK               0x03
  43
  44#define PM8941_WLED_REG_BOOST                   0x4e
  45#define  PM8941_WLED_REG_BOOST_MASK             0x07
  46
  47#define PM8941_WLED_REG_SINK                    0x4f
  48#define  PM8941_WLED_REG_SINK_MASK              0xe0
  49#define  PM8941_WLED_REG_SINK_SHFT              0x05
  50
  51/* Per-'string' registers below */
  52#define PM8941_WLED_REG_STR_OFFSET              0x10
  53
  54#define PM8941_WLED_REG_STR_MOD_EN_BASE         0x60
  55#define  PM8941_WLED_REG_STR_MOD_MASK           BIT(7)
  56#define  PM8941_WLED_REG_STR_MOD_EN             BIT(7)
  57
  58#define PM8941_WLED_REG_STR_SCALE_BASE          0x62
  59#define  PM8941_WLED_REG_STR_SCALE_MASK         0x1f
  60
  61#define PM8941_WLED_REG_STR_MOD_SRC_BASE        0x63
  62#define  PM8941_WLED_REG_STR_MOD_SRC_MASK       0x01
  63#define  PM8941_WLED_REG_STR_MOD_SRC_INT        0x00
  64#define  PM8941_WLED_REG_STR_MOD_SRC_EXT        0x01
  65
  66#define PM8941_WLED_REG_STR_CABC_BASE           0x66
  67#define  PM8941_WLED_REG_STR_CABC_MASK          BIT(7)
  68#define  PM8941_WLED_REG_STR_CABC_EN            BIT(7)
  69
  70struct pm8941_wled_config {
  71        u32 i_boost_limit;
  72        u32 ovp;
  73        u32 switch_freq;
  74        u32 num_strings;
  75        u32 i_limit;
  76        bool cs_out_en;
  77        bool ext_gen;
  78        bool cabc_en;
  79};
  80
  81struct pm8941_wled {
  82        const char *name;
  83        struct regmap *regmap;
  84        u16 addr;
  85
  86        struct pm8941_wled_config cfg;
  87};
  88
  89static int pm8941_wled_update_status(struct backlight_device *bl)
  90{
  91        struct pm8941_wled *wled = bl_get_data(bl);
  92        u16 val = bl->props.brightness;
  93        u8 ctrl = 0;
  94        int rc;
  95        int i;
  96
  97        if (bl->props.power != FB_BLANK_UNBLANK ||
  98            bl->props.fb_blank != FB_BLANK_UNBLANK ||
  99            bl->props.state & BL_CORE_FBBLANK)
 100                val = 0;
 101
 102        if (val != 0)
 103                ctrl = PM8941_WLED_REG_MOD_EN_BIT;
 104
 105        rc = regmap_update_bits(wled->regmap,
 106                        wled->addr + PM8941_WLED_REG_MOD_EN,
 107                        PM8941_WLED_REG_MOD_EN_MASK, ctrl);
 108        if (rc)
 109                return rc;
 110
 111        for (i = 0; i < wled->cfg.num_strings; ++i) {
 112                u8 v[2] = { val & 0xff, (val >> 8) & 0xf };
 113
 114                rc = regmap_bulk_write(wled->regmap,
 115                                wled->addr + PM8941_WLED_REG_VAL_BASE + 2 * i,
 116                                v, 2);
 117                if (rc)
 118                        return rc;
 119        }
 120
 121        rc = regmap_update_bits(wled->regmap,
 122                        wled->addr + PM8941_WLED_REG_SYNC,
 123                        PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_ALL);
 124        if (rc)
 125                return rc;
 126
 127        rc = regmap_update_bits(wled->regmap,
 128                        wled->addr + PM8941_WLED_REG_SYNC,
 129                        PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_CLEAR);
 130        return rc;
 131}
 132
 133static int pm8941_wled_setup(struct pm8941_wled *wled)
 134{
 135        int rc;
 136        int i;
 137
 138        rc = regmap_update_bits(wled->regmap,
 139                        wled->addr + PM8941_WLED_REG_OVP,
 140                        PM8941_WLED_REG_OVP_MASK, wled->cfg.ovp);
 141        if (rc)
 142                return rc;
 143
 144        rc = regmap_update_bits(wled->regmap,
 145                        wled->addr + PM8941_WLED_REG_BOOST,
 146                        PM8941_WLED_REG_BOOST_MASK, wled->cfg.i_boost_limit);
 147        if (rc)
 148                return rc;
 149
 150        rc = regmap_update_bits(wled->regmap,
 151                        wled->addr + PM8941_WLED_REG_FREQ,
 152                        PM8941_WLED_REG_FREQ_MASK, wled->cfg.switch_freq);
 153        if (rc)
 154                return rc;
 155
 156        if (wled->cfg.cs_out_en) {
 157                u8 all = (BIT(wled->cfg.num_strings) - 1)
 158                                << PM8941_WLED_REG_SINK_SHFT;
 159
 160                rc = regmap_update_bits(wled->regmap,
 161                                wled->addr + PM8941_WLED_REG_SINK,
 162                                PM8941_WLED_REG_SINK_MASK, all);
 163                if (rc)
 164                        return rc;
 165        }
 166
 167        for (i = 0; i < wled->cfg.num_strings; ++i) {
 168                u16 addr = wled->addr + PM8941_WLED_REG_STR_OFFSET * i;
 169
 170                rc = regmap_update_bits(wled->regmap,
 171                                addr + PM8941_WLED_REG_STR_MOD_EN_BASE,
 172                                PM8941_WLED_REG_STR_MOD_MASK,
 173                                PM8941_WLED_REG_STR_MOD_EN);
 174                if (rc)
 175                        return rc;
 176
 177                if (wled->cfg.ext_gen) {
 178                        rc = regmap_update_bits(wled->regmap,
 179                                        addr + PM8941_WLED_REG_STR_MOD_SRC_BASE,
 180                                        PM8941_WLED_REG_STR_MOD_SRC_MASK,
 181                                        PM8941_WLED_REG_STR_MOD_SRC_EXT);
 182                        if (rc)
 183                                return rc;
 184                }
 185
 186                rc = regmap_update_bits(wled->regmap,
 187                                addr + PM8941_WLED_REG_STR_SCALE_BASE,
 188                                PM8941_WLED_REG_STR_SCALE_MASK,
 189                                wled->cfg.i_limit);
 190                if (rc)
 191                        return rc;
 192
 193                rc = regmap_update_bits(wled->regmap,
 194                                addr + PM8941_WLED_REG_STR_CABC_BASE,
 195                                PM8941_WLED_REG_STR_CABC_MASK,
 196                                wled->cfg.cabc_en ?
 197                                        PM8941_WLED_REG_STR_CABC_EN : 0);
 198                if (rc)
 199                        return rc;
 200        }
 201
 202        return 0;
 203}
 204
 205static const struct pm8941_wled_config pm8941_wled_config_defaults = {
 206        .i_boost_limit = 3,
 207        .i_limit = 20,
 208        .ovp = 2,
 209        .switch_freq = 5,
 210        .num_strings = 0,
 211        .cs_out_en = false,
 212        .ext_gen = false,
 213        .cabc_en = false,
 214};
 215
 216struct pm8941_wled_var_cfg {
 217        const u32 *values;
 218        u32 (*fn)(u32);
 219        int size;
 220};
 221
 222static const u32 pm8941_wled_i_boost_limit_values[] = {
 223        105, 385, 525, 805, 980, 1260, 1400, 1680,
 224};
 225
 226static const struct pm8941_wled_var_cfg pm8941_wled_i_boost_limit_cfg = {
 227        .values = pm8941_wled_i_boost_limit_values,
 228        .size = ARRAY_SIZE(pm8941_wled_i_boost_limit_values),
 229};
 230
 231static const u32 pm8941_wled_ovp_values[] = {
 232        35, 32, 29, 27,
 233};
 234
 235static const struct pm8941_wled_var_cfg pm8941_wled_ovp_cfg = {
 236        .values = pm8941_wled_ovp_values,
 237        .size = ARRAY_SIZE(pm8941_wled_ovp_values),
 238};
 239
 240static u32 pm8941_wled_num_strings_values_fn(u32 idx)
 241{
 242        return idx + 1;
 243}
 244
 245static const struct pm8941_wled_var_cfg pm8941_wled_num_strings_cfg = {
 246        .fn = pm8941_wled_num_strings_values_fn,
 247        .size = 3,
 248};
 249
 250static u32 pm8941_wled_switch_freq_values_fn(u32 idx)
 251{
 252        return 19200 / (2 * (1 + idx));
 253}
 254
 255static const struct pm8941_wled_var_cfg pm8941_wled_switch_freq_cfg = {
 256        .fn = pm8941_wled_switch_freq_values_fn,
 257        .size = 16,
 258};
 259
 260static const struct pm8941_wled_var_cfg pm8941_wled_i_limit_cfg = {
 261        .size = 26,
 262};
 263
 264static u32 pm8941_wled_values(const struct pm8941_wled_var_cfg *cfg, u32 idx)
 265{
 266        if (idx >= cfg->size)
 267                return UINT_MAX;
 268        if (cfg->fn)
 269                return cfg->fn(idx);
 270        if (cfg->values)
 271                return cfg->values[idx];
 272        return idx;
 273}
 274
 275static int pm8941_wled_configure(struct pm8941_wled *wled, struct device *dev)
 276{
 277        struct pm8941_wled_config *cfg = &wled->cfg;
 278        u32 val;
 279        int rc;
 280        u32 c;
 281        int i;
 282        int j;
 283
 284        const struct {
 285                const char *name;
 286                u32 *val_ptr;
 287                const struct pm8941_wled_var_cfg *cfg;
 288        } u32_opts[] = {
 289                {
 290                        "qcom,current-boost-limit",
 291                        &cfg->i_boost_limit,
 292                        .cfg = &pm8941_wled_i_boost_limit_cfg,
 293                },
 294                {
 295                        "qcom,current-limit",
 296                        &cfg->i_limit,
 297                        .cfg = &pm8941_wled_i_limit_cfg,
 298                },
 299                {
 300                        "qcom,ovp",
 301                        &cfg->ovp,
 302                        .cfg = &pm8941_wled_ovp_cfg,
 303                },
 304                {
 305                        "qcom,switching-freq",
 306                        &cfg->switch_freq,
 307                        .cfg = &pm8941_wled_switch_freq_cfg,
 308                },
 309                {
 310                        "qcom,num-strings",
 311                        &cfg->num_strings,
 312                        .cfg = &pm8941_wled_num_strings_cfg,
 313                },
 314        };
 315        const struct {
 316                const char *name;
 317                bool *val_ptr;
 318        } bool_opts[] = {
 319                { "qcom,cs-out", &cfg->cs_out_en, },
 320                { "qcom,ext-gen", &cfg->ext_gen, },
 321                { "qcom,cabc", &cfg->cabc_en, },
 322        };
 323
 324        rc = of_property_read_u32(dev->of_node, "reg", &val);
 325        if (rc || val > 0xffff) {
 326                dev_err(dev, "invalid IO resources\n");
 327                return rc ? rc : -EINVAL;
 328        }
 329        wled->addr = val;
 330
 331        rc = of_property_read_string(dev->of_node, "label", &wled->name);
 332        if (rc)
 333                wled->name = dev->of_node->name;
 334
 335        *cfg = pm8941_wled_config_defaults;
 336        for (i = 0; i < ARRAY_SIZE(u32_opts); ++i) {
 337                rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val);
 338                if (rc == -EINVAL) {
 339                        continue;
 340                } else if (rc) {
 341                        dev_err(dev, "error reading '%s'\n", u32_opts[i].name);
 342                        return rc;
 343                }
 344
 345                c = UINT_MAX;
 346                for (j = 0; c != val; j++) {
 347                        c = pm8941_wled_values(u32_opts[i].cfg, j);
 348                        if (c == UINT_MAX) {
 349                                dev_err(dev, "invalid value for '%s'\n",
 350                                        u32_opts[i].name);
 351                                return -EINVAL;
 352                        }
 353                }
 354
 355                dev_dbg(dev, "'%s' = %u\n", u32_opts[i].name, c);
 356                *u32_opts[i].val_ptr = j;
 357        }
 358
 359        for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) {
 360                if (of_property_read_bool(dev->of_node, bool_opts[i].name))
 361                        *bool_opts[i].val_ptr = true;
 362        }
 363
 364        cfg->num_strings = cfg->num_strings + 1;
 365
 366        return 0;
 367}
 368
 369static const struct backlight_ops pm8941_wled_ops = {
 370        .update_status = pm8941_wled_update_status,
 371};
 372
 373static int pm8941_wled_probe(struct platform_device *pdev)
 374{
 375        struct backlight_properties props;
 376        struct backlight_device *bl;
 377        struct pm8941_wled *wled;
 378        struct regmap *regmap;
 379        u32 val;
 380        int rc;
 381
 382        regmap = dev_get_regmap(pdev->dev.parent, NULL);
 383        if (!regmap) {
 384                dev_err(&pdev->dev, "Unable to get regmap\n");
 385                return -EINVAL;
 386        }
 387
 388        wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL);
 389        if (!wled)
 390                return -ENOMEM;
 391
 392        wled->regmap = regmap;
 393
 394        rc = pm8941_wled_configure(wled, &pdev->dev);
 395        if (rc)
 396                return rc;
 397
 398        rc = pm8941_wled_setup(wled);
 399        if (rc)
 400                return rc;
 401
 402        val = PM8941_WLED_DEFAULT_BRIGHTNESS;
 403        of_property_read_u32(pdev->dev.of_node, "default-brightness", &val);
 404
 405        memset(&props, 0, sizeof(struct backlight_properties));
 406        props.type = BACKLIGHT_RAW;
 407        props.brightness = val;
 408        props.max_brightness = PM8941_WLED_REG_VAL_MAX;
 409        bl = devm_backlight_device_register(&pdev->dev, wled->name,
 410                                            &pdev->dev, wled,
 411                                            &pm8941_wled_ops, &props);
 412        return PTR_ERR_OR_ZERO(bl);
 413};
 414
 415static const struct of_device_id pm8941_wled_match_table[] = {
 416        { .compatible = "qcom,pm8941-wled" },
 417        {}
 418};
 419MODULE_DEVICE_TABLE(of, pm8941_wled_match_table);
 420
 421static struct platform_driver pm8941_wled_driver = {
 422        .probe = pm8941_wled_probe,
 423        .driver = {
 424                .name = "pm8941-wled",
 425                .of_match_table = pm8941_wled_match_table,
 426        },
 427};
 428
 429module_platform_driver(pm8941_wled_driver);
 430
 431MODULE_DESCRIPTION("pm8941 wled driver");
 432MODULE_LICENSE("GPL v2");
 433