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