brintos

brintos / linux-shallow public Read only

0
0
Text · 8.8 KiB · cca98c6 Raw
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", &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