356 lines · c
1// SPDX-License-Identifier: GPL-2.02// 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 10/* PMIC global control register definition */11#define SC27XX_MODULE_EN0 0xc0812#define SC27XX_CLK_EN0 0xc1813#define SC27XX_RGB_CTRL 0xebc14 15#define SC27XX_BLTC_EN BIT(9)16#define SC27XX_RTC_EN BIT(7)17#define SC27XX_RGB_PD BIT(0)18 19/* Breathing light controller register definition */20#define SC27XX_LEDS_CTRL 0x0021#define SC27XX_LEDS_PRESCALE 0x0422#define SC27XX_LEDS_DUTY 0x0823#define SC27XX_LEDS_CURVE0 0x0c24#define SC27XX_LEDS_CURVE1 0x1025 26#define SC27XX_CTRL_SHIFT 427#define SC27XX_LED_RUN BIT(0)28#define SC27XX_LED_TYPE BIT(1)29 30#define SC27XX_DUTY_SHIFT 831#define SC27XX_DUTY_MASK GENMASK(15, 0)32#define SC27XX_MOD_MASK GENMASK(7, 0)33 34#define SC27XX_CURVE_SHIFT 835#define SC27XX_CURVE_L_MASK GENMASK(7, 0)36#define SC27XX_CURVE_H_MASK GENMASK(15, 8)37 38#define SC27XX_LEDS_OFFSET 0x1039#define SC27XX_LEDS_MAX 340#define SC27XX_LEDS_PATTERN_CNT 441/* Stage duration step, in milliseconds */42#define SC27XX_LEDS_STEP 12543/* Minimum and maximum duration, in milliseconds */44#define SC27XX_DELTA_T_MIN SC27XX_LEDS_STEP45#define SC27XX_DELTA_T_MAX (SC27XX_LEDS_STEP * 255)46 47struct sc27xx_led {48 struct fwnode_handle *fwnode;49 struct led_classdev ldev;50 struct sc27xx_led_priv *priv;51 u8 line;52 bool active;53};54 55struct sc27xx_led_priv {56 struct sc27xx_led leds[SC27XX_LEDS_MAX];57 struct regmap *regmap;58 struct mutex lock;59 u32 base;60};61 62#define to_sc27xx_led(ldev) \63 container_of(ldev, struct sc27xx_led, ldev)64 65static int sc27xx_led_init(struct regmap *regmap)66{67 int err;68 69 err = regmap_update_bits(regmap, SC27XX_MODULE_EN0, SC27XX_BLTC_EN,70 SC27XX_BLTC_EN);71 if (err)72 return err;73 74 err = regmap_update_bits(regmap, SC27XX_CLK_EN0, SC27XX_RTC_EN,75 SC27XX_RTC_EN);76 if (err)77 return err;78 79 return regmap_update_bits(regmap, SC27XX_RGB_CTRL, SC27XX_RGB_PD, 0);80}81 82static u32 sc27xx_led_get_offset(struct sc27xx_led *leds)83{84 return leds->priv->base + SC27XX_LEDS_OFFSET * leds->line;85}86 87static int sc27xx_led_enable(struct sc27xx_led *leds, enum led_brightness value)88{89 u32 base = sc27xx_led_get_offset(leds);90 u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL;91 u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line;92 struct regmap *regmap = leds->priv->regmap;93 int err;94 95 err = regmap_update_bits(regmap, base + SC27XX_LEDS_DUTY,96 SC27XX_DUTY_MASK,97 (value << SC27XX_DUTY_SHIFT) |98 SC27XX_MOD_MASK);99 if (err)100 return err;101 102 return regmap_update_bits(regmap, ctrl_base,103 (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift,104 (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift);105}106 107static int sc27xx_led_disable(struct sc27xx_led *leds)108{109 struct regmap *regmap = leds->priv->regmap;110 u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL;111 u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line;112 113 return regmap_update_bits(regmap, ctrl_base,114 (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, 0);115}116 117static int sc27xx_led_set(struct led_classdev *ldev, enum led_brightness value)118{119 struct sc27xx_led *leds = to_sc27xx_led(ldev);120 int err;121 122 mutex_lock(&leds->priv->lock);123 124 if (value == LED_OFF)125 err = sc27xx_led_disable(leds);126 else127 err = sc27xx_led_enable(leds, value);128 129 mutex_unlock(&leds->priv->lock);130 131 return err;132}133 134static void sc27xx_led_clamp_align_delta_t(u32 *delta_t)135{136 u32 v, offset, t = *delta_t;137 138 v = t + SC27XX_LEDS_STEP / 2;139 v = clamp_t(u32, v, SC27XX_DELTA_T_MIN, SC27XX_DELTA_T_MAX);140 offset = v - SC27XX_DELTA_T_MIN;141 offset = SC27XX_LEDS_STEP * (offset / SC27XX_LEDS_STEP);142 143 *delta_t = SC27XX_DELTA_T_MIN + offset;144}145 146static int sc27xx_led_pattern_clear(struct led_classdev *ldev)147{148 struct sc27xx_led *leds = to_sc27xx_led(ldev);149 struct regmap *regmap = leds->priv->regmap;150 u32 base = sc27xx_led_get_offset(leds);151 u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL;152 u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line;153 int err;154 155 mutex_lock(&leds->priv->lock);156 157 /* Reset the rise, high, fall and low time to zero. */158 regmap_write(regmap, base + SC27XX_LEDS_CURVE0, 0);159 regmap_write(regmap, base + SC27XX_LEDS_CURVE1, 0);160 161 err = regmap_update_bits(regmap, ctrl_base,162 (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, 0);163 164 ldev->brightness = LED_OFF;165 166 mutex_unlock(&leds->priv->lock);167 168 return err;169}170 171static int sc27xx_led_pattern_set(struct led_classdev *ldev,172 struct led_pattern *pattern,173 u32 len, int repeat)174{175 struct sc27xx_led *leds = to_sc27xx_led(ldev);176 u32 base = sc27xx_led_get_offset(leds);177 u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL;178 u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line;179 struct regmap *regmap = leds->priv->regmap;180 int err;181 182 /*183 * Must contain 4 tuples to configure the rise time, high time, fall184 * time and low time to enable the breathing mode.185 */186 if (len != SC27XX_LEDS_PATTERN_CNT)187 return -EINVAL;188 189 mutex_lock(&leds->priv->lock);190 191 sc27xx_led_clamp_align_delta_t(&pattern[0].delta_t);192 err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE0,193 SC27XX_CURVE_L_MASK,194 pattern[0].delta_t / SC27XX_LEDS_STEP);195 if (err)196 goto out;197 198 sc27xx_led_clamp_align_delta_t(&pattern[1].delta_t);199 err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE1,200 SC27XX_CURVE_L_MASK,201 pattern[1].delta_t / SC27XX_LEDS_STEP);202 if (err)203 goto out;204 205 sc27xx_led_clamp_align_delta_t(&pattern[2].delta_t);206 err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE0,207 SC27XX_CURVE_H_MASK,208 (pattern[2].delta_t / SC27XX_LEDS_STEP) <<209 SC27XX_CURVE_SHIFT);210 if (err)211 goto out;212 213 sc27xx_led_clamp_align_delta_t(&pattern[3].delta_t);214 err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE1,215 SC27XX_CURVE_H_MASK,216 (pattern[3].delta_t / SC27XX_LEDS_STEP) <<217 SC27XX_CURVE_SHIFT);218 if (err)219 goto out;220 221 err = regmap_update_bits(regmap, base + SC27XX_LEDS_DUTY,222 SC27XX_DUTY_MASK,223 (pattern[1].brightness << SC27XX_DUTY_SHIFT) |224 SC27XX_MOD_MASK);225 if (err)226 goto out;227 228 /* Enable the LED breathing mode */229 err = regmap_update_bits(regmap, ctrl_base,230 SC27XX_LED_RUN << ctrl_shift,231 SC27XX_LED_RUN << ctrl_shift);232 if (!err)233 ldev->brightness = pattern[1].brightness;234 235out:236 mutex_unlock(&leds->priv->lock);237 238 return err;239}240 241static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv)242{243 int i, err;244 245 err = sc27xx_led_init(priv->regmap);246 if (err)247 return err;248 249 for (i = 0; i < SC27XX_LEDS_MAX; i++) {250 struct sc27xx_led *led = &priv->leds[i];251 struct led_init_data init_data = {};252 253 if (!led->active)254 continue;255 256 led->line = i;257 led->priv = priv;258 led->ldev.brightness_set_blocking = sc27xx_led_set;259 led->ldev.pattern_set = sc27xx_led_pattern_set;260 led->ldev.pattern_clear = sc27xx_led_pattern_clear;261 led->ldev.default_trigger = "pattern";262 263 init_data.fwnode = led->fwnode;264 init_data.devicename = "sc27xx";265 init_data.default_label = ":";266 267 err = devm_led_classdev_register_ext(dev, &led->ldev,268 &init_data);269 if (err)270 return err;271 }272 273 return 0;274}275 276static int sc27xx_led_probe(struct platform_device *pdev)277{278 struct device *dev = &pdev->dev;279 struct device_node *np = dev_of_node(dev);280 struct sc27xx_led_priv *priv;281 u32 base, count, reg;282 int err;283 284 count = of_get_available_child_count(np);285 if (!count || count > SC27XX_LEDS_MAX)286 return -EINVAL;287 288 err = of_property_read_u32(np, "reg", &base);289 if (err) {290 dev_err(dev, "fail to get reg of property\n");291 return err;292 }293 294 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);295 if (!priv)296 return -ENOMEM;297 298 platform_set_drvdata(pdev, priv);299 priv->base = base;300 priv->regmap = dev_get_regmap(dev->parent, NULL);301 if (!priv->regmap) {302 err = -ENODEV;303 dev_err(dev, "failed to get regmap: %d\n", err);304 return err;305 }306 307 for_each_available_child_of_node_scoped(np, child) {308 err = of_property_read_u32(child, "reg", ®);309 if (err)310 return err;311 312 if (reg >= SC27XX_LEDS_MAX || priv->leds[reg].active)313 return -EINVAL;314 315 priv->leds[reg].fwnode = of_fwnode_handle(child);316 priv->leds[reg].active = true;317 }318 319 mutex_init(&priv->lock);320 321 err = sc27xx_led_register(dev, priv);322 if (err)323 mutex_destroy(&priv->lock);324 325 return err;326}327 328static void sc27xx_led_remove(struct platform_device *pdev)329{330 struct sc27xx_led_priv *priv = platform_get_drvdata(pdev);331 332 mutex_destroy(&priv->lock);333}334 335static const struct of_device_id sc27xx_led_of_match[] = {336 { .compatible = "sprd,sc2731-bltc", },337 { }338};339MODULE_DEVICE_TABLE(of, sc27xx_led_of_match);340 341static struct platform_driver sc27xx_led_driver = {342 .driver = {343 .name = "sprd-bltc",344 .of_match_table = sc27xx_led_of_match,345 },346 .probe = sc27xx_led_probe,347 .remove_new = sc27xx_led_remove,348};349 350module_platform_driver(sc27xx_led_driver);351 352MODULE_DESCRIPTION("Spreadtrum SC27xx breathing light controller driver");353MODULE_AUTHOR("Xiaotong Lu <xiaotong.lu@spreadtrum.com>");354MODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org>");355MODULE_LICENSE("GPL v2");356