linux/drivers/leds/leds-mt6323.c
<<
>>
Prefs
   1/*
   2 * LED driver for Mediatek MT6323 PMIC
   3 *
   4 * Copyright (C) 2017 Sean Wang <sean.wang@mediatek.com>
   5 *
   6 * This program is free software; you can redistribute it and/or
   7 * modify it under the terms of the GNU General Public License as
   8 * published by the Free Software Foundation; either version 2 of
   9 * the License, or (at your option) any later version.
  10 *
  11 * This program is distributed in the hope that it will be useful,
  12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14 * GNU General Public License for more details.
  15 */
  16#include <linux/kernel.h>
  17#include <linux/leds.h>
  18#include <linux/mfd/mt6323/registers.h>
  19#include <linux/mfd/mt6397/core.h>
  20#include <linux/module.h>
  21#include <linux/of.h>
  22#include <linux/platform_device.h>
  23#include <linux/regmap.h>
  24
  25/*
  26 * Register field for MT6323_TOP_CKPDN0 to enable
  27 * 32K clock common for LED device.
  28 */
  29#define MT6323_RG_DRV_32K_CK_PDN        BIT(11)
  30#define MT6323_RG_DRV_32K_CK_PDN_MASK   BIT(11)
  31
  32/*
  33 * Register field for MT6323_TOP_CKPDN2 to enable
  34 * individual clock for LED device.
  35 */
  36#define MT6323_RG_ISINK_CK_PDN(i)       BIT(i)
  37#define MT6323_RG_ISINK_CK_PDN_MASK(i)  BIT(i)
  38
  39/*
  40 * Register field for MT6323_TOP_CKCON1 to select
  41 * clock source.
  42 */
  43#define MT6323_RG_ISINK_CK_SEL_MASK(i)  (BIT(10) << (i))
  44
  45/*
  46 * Register for MT6323_ISINK_CON0 to setup the
  47 * duty cycle of the blink.
  48 */
  49#define MT6323_ISINK_CON0(i)            (MT6323_ISINK0_CON0 + 0x8 * (i))
  50#define MT6323_ISINK_DIM_DUTY_MASK      (0x1f << 8)
  51#define MT6323_ISINK_DIM_DUTY(i)        (((i) << 8) & \
  52                                        MT6323_ISINK_DIM_DUTY_MASK)
  53
  54/* Register to setup the period of the blink. */
  55#define MT6323_ISINK_CON1(i)            (MT6323_ISINK0_CON1 + 0x8 * (i))
  56#define MT6323_ISINK_DIM_FSEL_MASK      (0xffff)
  57#define MT6323_ISINK_DIM_FSEL(i)        ((i) & MT6323_ISINK_DIM_FSEL_MASK)
  58
  59/* Register to control the brightness. */
  60#define MT6323_ISINK_CON2(i)            (MT6323_ISINK0_CON2 + 0x8 * (i))
  61#define MT6323_ISINK_CH_STEP_SHIFT      12
  62#define MT6323_ISINK_CH_STEP_MASK       (0x7 << 12)
  63#define MT6323_ISINK_CH_STEP(i)         (((i) << 12) & \
  64                                        MT6323_ISINK_CH_STEP_MASK)
  65#define MT6323_ISINK_SFSTR0_TC_MASK     (0x3 << 1)
  66#define MT6323_ISINK_SFSTR0_TC(i)       (((i) << 1) & \
  67                                        MT6323_ISINK_SFSTR0_TC_MASK)
  68#define MT6323_ISINK_SFSTR0_EN_MASK     BIT(0)
  69#define MT6323_ISINK_SFSTR0_EN          BIT(0)
  70
  71/* Register to LED channel enablement. */
  72#define MT6323_ISINK_CH_EN_MASK(i)      BIT(i)
  73#define MT6323_ISINK_CH_EN(i)           BIT(i)
  74
  75#define MT6323_MAX_PERIOD               10000
  76#define MT6323_MAX_LEDS                 4
  77#define MT6323_MAX_BRIGHTNESS           6
  78#define MT6323_UNIT_DUTY                3125
  79#define MT6323_CAL_HW_DUTY(o, p)        DIV_ROUND_CLOSEST((o) * 100000ul,\
  80                                        (p) * MT6323_UNIT_DUTY)
  81
  82struct mt6323_leds;
  83
  84/**
  85 * struct mt6323_led - state container for the LED device
  86 * @id:                 the identifier in MT6323 LED device
  87 * @parent:             the pointer to MT6323 LED controller
  88 * @cdev:               LED class device for this LED device
  89 * @current_brightness: current state of the LED device
  90 */
  91struct mt6323_led {
  92        int                     id;
  93        struct mt6323_leds      *parent;
  94        struct led_classdev     cdev;
  95        enum led_brightness     current_brightness;
  96};
  97
  98/**
  99 * struct mt6323_leds - state container for holding LED controller
 100 *                      of the driver
 101 * @dev:                the device pointer
 102 * @hw:                 the underlying hardware providing shared
 103 *                      bus for the register operations
 104 * @lock:               the lock among process context
 105 * @led:                the array that contains the state of individual
 106 *                      LED device
 107 */
 108struct mt6323_leds {
 109        struct device           *dev;
 110        struct mt6397_chip      *hw;
 111        /* protect among process context */
 112        struct mutex            lock;
 113        struct mt6323_led       *led[MT6323_MAX_LEDS];
 114};
 115
 116static int mt6323_led_hw_brightness(struct led_classdev *cdev,
 117                                    enum led_brightness brightness)
 118{
 119        struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
 120        struct mt6323_leds *leds = led->parent;
 121        struct regmap *regmap = leds->hw->regmap;
 122        u32 con2_mask = 0, con2_val = 0;
 123        int ret;
 124
 125        /*
 126         * Setup current output for the corresponding
 127         * brightness level.
 128         */
 129        con2_mask |= MT6323_ISINK_CH_STEP_MASK |
 130                     MT6323_ISINK_SFSTR0_TC_MASK |
 131                     MT6323_ISINK_SFSTR0_EN_MASK;
 132        con2_val |=  MT6323_ISINK_CH_STEP(brightness - 1) |
 133                     MT6323_ISINK_SFSTR0_TC(2) |
 134                     MT6323_ISINK_SFSTR0_EN;
 135
 136        ret = regmap_update_bits(regmap, MT6323_ISINK_CON2(led->id),
 137                                 con2_mask, con2_val);
 138        return ret;
 139}
 140
 141static int mt6323_led_hw_off(struct led_classdev *cdev)
 142{
 143        struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
 144        struct mt6323_leds *leds = led->parent;
 145        struct regmap *regmap = leds->hw->regmap;
 146        unsigned int status;
 147        int ret;
 148
 149        status = MT6323_ISINK_CH_EN(led->id);
 150        ret = regmap_update_bits(regmap, MT6323_ISINK_EN_CTRL,
 151                                 MT6323_ISINK_CH_EN_MASK(led->id), ~status);
 152        if (ret < 0)
 153                return ret;
 154
 155        usleep_range(100, 300);
 156        ret = regmap_update_bits(regmap, MT6323_TOP_CKPDN2,
 157                                 MT6323_RG_ISINK_CK_PDN_MASK(led->id),
 158                                 MT6323_RG_ISINK_CK_PDN(led->id));
 159        if (ret < 0)
 160                return ret;
 161
 162        return 0;
 163}
 164
 165static enum led_brightness
 166mt6323_get_led_hw_brightness(struct led_classdev *cdev)
 167{
 168        struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
 169        struct mt6323_leds *leds = led->parent;
 170        struct regmap *regmap = leds->hw->regmap;
 171        unsigned int status;
 172        int ret;
 173
 174        ret = regmap_read(regmap, MT6323_TOP_CKPDN2, &status);
 175        if (ret < 0)
 176                return ret;
 177
 178        if (status & MT6323_RG_ISINK_CK_PDN_MASK(led->id))
 179                return 0;
 180
 181        ret = regmap_read(regmap, MT6323_ISINK_EN_CTRL, &status);
 182        if (ret < 0)
 183                return ret;
 184
 185        if (!(status & MT6323_ISINK_CH_EN(led->id)))
 186                return 0;
 187
 188        ret = regmap_read(regmap, MT6323_ISINK_CON2(led->id), &status);
 189        if (ret < 0)
 190                return ret;
 191
 192        return  ((status & MT6323_ISINK_CH_STEP_MASK)
 193                  >> MT6323_ISINK_CH_STEP_SHIFT) + 1;
 194}
 195
 196static int mt6323_led_hw_on(struct led_classdev *cdev,
 197                            enum led_brightness brightness)
 198{
 199        struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
 200        struct mt6323_leds *leds = led->parent;
 201        struct regmap *regmap = leds->hw->regmap;
 202        unsigned int status;
 203        int ret;
 204
 205        /*
 206         * Setup required clock source, enable the corresponding
 207         * clock and channel and let work with continuous blink as
 208         * the default.
 209         */
 210        ret = regmap_update_bits(regmap, MT6323_TOP_CKCON1,
 211                                 MT6323_RG_ISINK_CK_SEL_MASK(led->id), 0);
 212        if (ret < 0)
 213                return ret;
 214
 215        status = MT6323_RG_ISINK_CK_PDN(led->id);
 216        ret = regmap_update_bits(regmap, MT6323_TOP_CKPDN2,
 217                                 MT6323_RG_ISINK_CK_PDN_MASK(led->id),
 218                                 ~status);
 219        if (ret < 0)
 220                return ret;
 221
 222        usleep_range(100, 300);
 223
 224        ret = regmap_update_bits(regmap, MT6323_ISINK_EN_CTRL,
 225                                 MT6323_ISINK_CH_EN_MASK(led->id),
 226                                 MT6323_ISINK_CH_EN(led->id));
 227        if (ret < 0)
 228                return ret;
 229
 230        ret = mt6323_led_hw_brightness(cdev, brightness);
 231        if (ret < 0)
 232                return ret;
 233
 234        ret = regmap_update_bits(regmap, MT6323_ISINK_CON0(led->id),
 235                                 MT6323_ISINK_DIM_DUTY_MASK,
 236                                 MT6323_ISINK_DIM_DUTY(31));
 237        if (ret < 0)
 238                return ret;
 239
 240        ret = regmap_update_bits(regmap, MT6323_ISINK_CON1(led->id),
 241                                 MT6323_ISINK_DIM_FSEL_MASK,
 242                                 MT6323_ISINK_DIM_FSEL(1000));
 243        if (ret < 0)
 244                return ret;
 245
 246        return 0;
 247}
 248
 249static int mt6323_led_set_blink(struct led_classdev *cdev,
 250                                unsigned long *delay_on,
 251                                unsigned long *delay_off)
 252{
 253        struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
 254        struct mt6323_leds *leds = led->parent;
 255        struct regmap *regmap = leds->hw->regmap;
 256        unsigned long period;
 257        u8 duty_hw;
 258        int ret;
 259
 260        /*
 261         * Units are in ms, if over the hardware able
 262         * to support, fallback into software blink
 263         */
 264        period = *delay_on + *delay_off;
 265
 266        if (period > MT6323_MAX_PERIOD)
 267                return -EINVAL;
 268
 269        /*
 270         * LED subsystem requires a default user
 271         * friendly blink pattern for the LED so using
 272         * 1Hz duty cycle 50% here if without specific
 273         * value delay_on and delay off being assigned.
 274         */
 275        if (!*delay_on && !*delay_off) {
 276                *delay_on = 500;
 277                *delay_off = 500;
 278        }
 279
 280        /*
 281         * Calculate duty_hw based on the percentage of period during
 282         * which the led is ON.
 283         */
 284        duty_hw = MT6323_CAL_HW_DUTY(*delay_on, period);
 285
 286        /* hardware doesn't support zero duty cycle. */
 287        if (!duty_hw)
 288                return -EINVAL;
 289
 290        mutex_lock(&leds->lock);
 291        /*
 292         * Set max_brightness as the software blink behavior
 293         * when no blink brightness.
 294         */
 295        if (!led->current_brightness) {
 296                ret = mt6323_led_hw_on(cdev, cdev->max_brightness);
 297                if (ret < 0)
 298                        goto out;
 299                led->current_brightness = cdev->max_brightness;
 300        }
 301
 302        ret = regmap_update_bits(regmap, MT6323_ISINK_CON0(led->id),
 303                                 MT6323_ISINK_DIM_DUTY_MASK,
 304                                 MT6323_ISINK_DIM_DUTY(duty_hw - 1));
 305        if (ret < 0)
 306                goto out;
 307
 308        ret = regmap_update_bits(regmap, MT6323_ISINK_CON1(led->id),
 309                                 MT6323_ISINK_DIM_FSEL_MASK,
 310                                 MT6323_ISINK_DIM_FSEL(period - 1));
 311out:
 312        mutex_unlock(&leds->lock);
 313
 314        return ret;
 315}
 316
 317static int mt6323_led_set_brightness(struct led_classdev *cdev,
 318                                     enum led_brightness brightness)
 319{
 320        struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
 321        struct mt6323_leds *leds = led->parent;
 322        int ret;
 323
 324        mutex_lock(&leds->lock);
 325
 326        if (!led->current_brightness && brightness) {
 327                ret = mt6323_led_hw_on(cdev, brightness);
 328                if (ret < 0)
 329                        goto out;
 330        } else if (brightness) {
 331                ret = mt6323_led_hw_brightness(cdev, brightness);
 332                if (ret < 0)
 333                        goto out;
 334        } else {
 335                ret = mt6323_led_hw_off(cdev);
 336                if (ret < 0)
 337                        goto out;
 338        }
 339
 340        led->current_brightness = brightness;
 341out:
 342        mutex_unlock(&leds->lock);
 343
 344        return ret;
 345}
 346
 347static int mt6323_led_set_dt_default(struct led_classdev *cdev,
 348                                     struct device_node *np)
 349{
 350        struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
 351        const char *state;
 352        int ret = 0;
 353
 354        led->cdev.name = of_get_property(np, "label", NULL) ? : np->name;
 355        led->cdev.default_trigger = of_get_property(np,
 356                                                    "linux,default-trigger",
 357                                                    NULL);
 358
 359        state = of_get_property(np, "default-state", NULL);
 360        if (state) {
 361                if (!strcmp(state, "keep")) {
 362                        ret = mt6323_get_led_hw_brightness(cdev);
 363                        if (ret < 0)
 364                                return ret;
 365                        led->current_brightness = ret;
 366                        ret = 0;
 367                } else if (!strcmp(state, "on")) {
 368                        ret =
 369                        mt6323_led_set_brightness(cdev, cdev->max_brightness);
 370                } else  {
 371                        ret = mt6323_led_set_brightness(cdev, LED_OFF);
 372                }
 373        }
 374
 375        return ret;
 376}
 377
 378static int mt6323_led_probe(struct platform_device *pdev)
 379{
 380        struct device *dev = &pdev->dev;
 381        struct device_node *np = pdev->dev.of_node;
 382        struct device_node *child;
 383        struct mt6397_chip *hw = dev_get_drvdata(pdev->dev.parent);
 384        struct mt6323_leds *leds;
 385        struct mt6323_led *led;
 386        int ret;
 387        unsigned int status;
 388        u32 reg;
 389
 390        leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);
 391        if (!leds)
 392                return -ENOMEM;
 393
 394        platform_set_drvdata(pdev, leds);
 395        leds->dev = dev;
 396
 397        /*
 398         * leds->hw points to the underlying bus for the register
 399         * controlled.
 400         */
 401        leds->hw = hw;
 402        mutex_init(&leds->lock);
 403
 404        status = MT6323_RG_DRV_32K_CK_PDN;
 405        ret = regmap_update_bits(leds->hw->regmap, MT6323_TOP_CKPDN0,
 406                                 MT6323_RG_DRV_32K_CK_PDN_MASK, ~status);
 407        if (ret < 0) {
 408                dev_err(leds->dev,
 409                        "Failed to update MT6323_TOP_CKPDN0 Register\n");
 410                return ret;
 411        }
 412
 413        for_each_available_child_of_node(np, child) {
 414                ret = of_property_read_u32(child, "reg", &reg);
 415                if (ret) {
 416                        dev_err(dev, "Failed to read led 'reg' property\n");
 417                        goto put_child_node;
 418                }
 419
 420                if (reg >= MT6323_MAX_LEDS || leds->led[reg]) {
 421                        dev_err(dev, "Invalid led reg %u\n", reg);
 422                        ret = -EINVAL;
 423                        goto put_child_node;
 424                }
 425
 426                led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
 427                if (!led) {
 428                        ret = -ENOMEM;
 429                        goto put_child_node;
 430                }
 431
 432                leds->led[reg] = led;
 433                leds->led[reg]->id = reg;
 434                leds->led[reg]->cdev.max_brightness = MT6323_MAX_BRIGHTNESS;
 435                leds->led[reg]->cdev.brightness_set_blocking =
 436                                        mt6323_led_set_brightness;
 437                leds->led[reg]->cdev.blink_set = mt6323_led_set_blink;
 438                leds->led[reg]->cdev.brightness_get =
 439                                        mt6323_get_led_hw_brightness;
 440                leds->led[reg]->parent = leds;
 441
 442                ret = mt6323_led_set_dt_default(&leds->led[reg]->cdev, child);
 443                if (ret < 0) {
 444                        dev_err(leds->dev,
 445                                "Failed to LED set default from devicetree\n");
 446                        goto put_child_node;
 447                }
 448
 449                ret = devm_led_classdev_register(dev, &leds->led[reg]->cdev);
 450                if (ret) {
 451                        dev_err(&pdev->dev, "Failed to register LED: %d\n",
 452                                ret);
 453                        goto put_child_node;
 454                }
 455                leds->led[reg]->cdev.dev->of_node = child;
 456        }
 457
 458        return 0;
 459
 460put_child_node:
 461        of_node_put(child);
 462        return ret;
 463}
 464
 465static int mt6323_led_remove(struct platform_device *pdev)
 466{
 467        struct mt6323_leds *leds = platform_get_drvdata(pdev);
 468        int i;
 469
 470        /* Turn the LEDs off on driver removal. */
 471        for (i = 0 ; leds->led[i] ; i++)
 472                mt6323_led_hw_off(&leds->led[i]->cdev);
 473
 474        regmap_update_bits(leds->hw->regmap, MT6323_TOP_CKPDN0,
 475                           MT6323_RG_DRV_32K_CK_PDN_MASK,
 476                           MT6323_RG_DRV_32K_CK_PDN);
 477
 478        mutex_destroy(&leds->lock);
 479
 480        return 0;
 481}
 482
 483static const struct of_device_id mt6323_led_dt_match[] = {
 484        { .compatible = "mediatek,mt6323-led" },
 485        {},
 486};
 487MODULE_DEVICE_TABLE(of, mt6323_led_dt_match);
 488
 489static struct platform_driver mt6323_led_driver = {
 490        .probe          = mt6323_led_probe,
 491        .remove         = mt6323_led_remove,
 492        .driver         = {
 493                .name   = "mt6323-led",
 494                .of_match_table = mt6323_led_dt_match,
 495        },
 496};
 497
 498module_platform_driver(mt6323_led_driver);
 499
 500MODULE_DESCRIPTION("LED driver for Mediatek MT6323 PMIC");
 501MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>");
 502MODULE_LICENSE("GPL");
 503