linux/drivers/leds/leds-sc27xx-bltc.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2// Copyright (C) 2018 Spreadtrum Communications Inc.
   3
   4#include <linux/leds.h>
   5#include <linux/module.h>
   6#include <linux/of.h>
   7#include <linux/platform_device.h>
   8#include <linux/regmap.h>
   9#include <uapi/linux/uleds.h>
  10
  11/* PMIC global control register definition */
  12#define SC27XX_MODULE_EN0       0xc08
  13#define SC27XX_CLK_EN0          0xc18
  14#define SC27XX_RGB_CTRL         0xebc
  15
  16#define SC27XX_BLTC_EN          BIT(9)
  17#define SC27XX_RTC_EN           BIT(7)
  18#define SC27XX_RGB_PD           BIT(0)
  19
  20/* Breathing light controller register definition */
  21#define SC27XX_LEDS_CTRL        0x00
  22#define SC27XX_LEDS_PRESCALE    0x04
  23#define SC27XX_LEDS_DUTY        0x08
  24#define SC27XX_LEDS_CURVE0      0x0c
  25#define SC27XX_LEDS_CURVE1      0x10
  26
  27#define SC27XX_CTRL_SHIFT       4
  28#define SC27XX_LED_RUN          BIT(0)
  29#define SC27XX_LED_TYPE         BIT(1)
  30
  31#define SC27XX_DUTY_SHIFT       8
  32#define SC27XX_DUTY_MASK        GENMASK(15, 0)
  33#define SC27XX_MOD_MASK         GENMASK(7, 0)
  34
  35#define SC27XX_LEDS_OFFSET      0x10
  36#define SC27XX_LEDS_MAX         3
  37
  38struct sc27xx_led {
  39        char name[LED_MAX_NAME_SIZE];
  40        struct led_classdev ldev;
  41        struct sc27xx_led_priv *priv;
  42        u8 line;
  43        bool active;
  44};
  45
  46struct sc27xx_led_priv {
  47        struct sc27xx_led leds[SC27XX_LEDS_MAX];
  48        struct regmap *regmap;
  49        struct mutex lock;
  50        u32 base;
  51};
  52
  53#define to_sc27xx_led(ldev) \
  54        container_of(ldev, struct sc27xx_led, ldev)
  55
  56static int sc27xx_led_init(struct regmap *regmap)
  57{
  58        int err;
  59
  60        err = regmap_update_bits(regmap, SC27XX_MODULE_EN0, SC27XX_BLTC_EN,
  61                                 SC27XX_BLTC_EN);
  62        if (err)
  63                return err;
  64
  65        err = regmap_update_bits(regmap, SC27XX_CLK_EN0, SC27XX_RTC_EN,
  66                                 SC27XX_RTC_EN);
  67        if (err)
  68                return err;
  69
  70        return regmap_update_bits(regmap, SC27XX_RGB_CTRL, SC27XX_RGB_PD, 0);
  71}
  72
  73static u32 sc27xx_led_get_offset(struct sc27xx_led *leds)
  74{
  75        return leds->priv->base + SC27XX_LEDS_OFFSET * leds->line;
  76}
  77
  78static int sc27xx_led_enable(struct sc27xx_led *leds, enum led_brightness value)
  79{
  80        u32 base = sc27xx_led_get_offset(leds);
  81        u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL;
  82        u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line;
  83        struct regmap *regmap = leds->priv->regmap;
  84        int err;
  85
  86        err = regmap_update_bits(regmap, base + SC27XX_LEDS_DUTY,
  87                                 SC27XX_DUTY_MASK,
  88                                 (value << SC27XX_DUTY_SHIFT) |
  89                                 SC27XX_MOD_MASK);
  90        if (err)
  91                return err;
  92
  93        return regmap_update_bits(regmap, ctrl_base,
  94                        (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift,
  95                        (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift);
  96}
  97
  98static int sc27xx_led_disable(struct sc27xx_led *leds)
  99{
 100        struct regmap *regmap = leds->priv->regmap;
 101        u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL;
 102        u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line;
 103
 104        return regmap_update_bits(regmap, ctrl_base,
 105                        (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, 0);
 106}
 107
 108static int sc27xx_led_set(struct led_classdev *ldev, enum led_brightness value)
 109{
 110        struct sc27xx_led *leds = to_sc27xx_led(ldev);
 111        int err;
 112
 113        mutex_lock(&leds->priv->lock);
 114
 115        if (value == LED_OFF)
 116                err = sc27xx_led_disable(leds);
 117        else
 118                err = sc27xx_led_enable(leds, value);
 119
 120        mutex_unlock(&leds->priv->lock);
 121
 122        return err;
 123}
 124
 125static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv)
 126{
 127        int i, err;
 128
 129        err = sc27xx_led_init(priv->regmap);
 130        if (err)
 131                return err;
 132
 133        for (i = 0; i < SC27XX_LEDS_MAX; i++) {
 134                struct sc27xx_led *led = &priv->leds[i];
 135
 136                if (!led->active)
 137                        continue;
 138
 139                led->line = i;
 140                led->priv = priv;
 141                led->ldev.name = led->name;
 142                led->ldev.brightness_set_blocking = sc27xx_led_set;
 143
 144                err = devm_led_classdev_register(dev, &led->ldev);
 145                if (err)
 146                        return err;
 147        }
 148
 149        return 0;
 150}
 151
 152static int sc27xx_led_probe(struct platform_device *pdev)
 153{
 154        struct device *dev = &pdev->dev;
 155        struct device_node *np = dev->of_node, *child;
 156        struct sc27xx_led_priv *priv;
 157        const char *str;
 158        u32 base, count, reg;
 159        int err;
 160
 161        count = of_get_child_count(np);
 162        if (!count || count > SC27XX_LEDS_MAX)
 163                return -EINVAL;
 164
 165        err = of_property_read_u32(np, "reg", &base);
 166        if (err) {
 167                dev_err(dev, "fail to get reg of property\n");
 168                return err;
 169        }
 170
 171        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 172        if (!priv)
 173                return -ENOMEM;
 174
 175        platform_set_drvdata(pdev, priv);
 176        mutex_init(&priv->lock);
 177        priv->base = base;
 178        priv->regmap = dev_get_regmap(dev->parent, NULL);
 179        if (!priv->regmap) {
 180                err = -ENODEV;
 181                dev_err(dev, "failed to get regmap: %d\n", err);
 182                return err;
 183        }
 184
 185        for_each_child_of_node(np, child) {
 186                err = of_property_read_u32(child, "reg", &reg);
 187                if (err) {
 188                        of_node_put(child);
 189                        mutex_destroy(&priv->lock);
 190                        return err;
 191                }
 192
 193                if (reg >= SC27XX_LEDS_MAX || priv->leds[reg].active) {
 194                        of_node_put(child);
 195                        mutex_destroy(&priv->lock);
 196                        return -EINVAL;
 197                }
 198
 199                priv->leds[reg].active = true;
 200
 201                err = of_property_read_string(child, "label", &str);
 202                if (err)
 203                        snprintf(priv->leds[reg].name, LED_MAX_NAME_SIZE,
 204                                 "sc27xx::");
 205                else
 206                        snprintf(priv->leds[reg].name, LED_MAX_NAME_SIZE,
 207                                 "sc27xx:%s", str);
 208        }
 209
 210        err = sc27xx_led_register(dev, priv);
 211        if (err)
 212                mutex_destroy(&priv->lock);
 213
 214        return err;
 215}
 216
 217static int sc27xx_led_remove(struct platform_device *pdev)
 218{
 219        struct sc27xx_led_priv *priv = platform_get_drvdata(pdev);
 220
 221        mutex_destroy(&priv->lock);
 222        return 0;
 223}
 224
 225static const struct of_device_id sc27xx_led_of_match[] = {
 226        { .compatible = "sprd,sc2731-bltc", },
 227        { }
 228};
 229MODULE_DEVICE_TABLE(of, sc27xx_led_of_match);
 230
 231static struct platform_driver sc27xx_led_driver = {
 232        .driver = {
 233                .name = "sprd-bltc",
 234                .of_match_table = sc27xx_led_of_match,
 235        },
 236        .probe = sc27xx_led_probe,
 237        .remove = sc27xx_led_remove,
 238};
 239
 240module_platform_driver(sc27xx_led_driver);
 241
 242MODULE_DESCRIPTION("Spreadtrum SC27xx breathing light controller driver");
 243MODULE_AUTHOR("Xiaotong Lu <xiaotong.lu@spreadtrum.com>");
 244MODULE_LICENSE("GPL v2");
 245