Commit 8dbac65f authored by Baolin Wang's avatar Baolin Wang Committed by Jacek Anaszewski
Browse files

leds: sc27xx: Add pattern_set/clear interfaces for LED controller



This patch implements the 'pattern_set'and 'pattern_clear'
interfaces to support SC27XX LED breathing mode.

Signed-off-by: default avatarBaolin Wang <baolin.wang@linaro.org>
Acked-by: default avatarPavel Machek <pavel@ucw.c>
Signed-off-by: default avatarJacek Anaszewski <jacek.anaszewski@gmail.com>
parent 5fd752b6
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
What:		/sys/class/leds/<led>/hw_pattern
Date:		September 2018
KernelVersion:	4.20
Description:
		Specify a hardware pattern for the SC27XX LED. For the SC27XX
		LED controller, it only supports 4 stages to make a single
		hardware pattern, which is used to configure the rise time,
		high time, fall time and low time for the breathing mode.

		For the breathing mode, the SC27XX LED only expects one brightness
		for the high stage. To be compatible with the hardware pattern
		format, we should set brightness as 0 for rise stage, fall
		stage and low stage.

		Min stage duration: 125 ms
		Max stage duration: 31875 ms

		Since the stage duration step is 125 ms, the duration should be
		a multiplier of 125, like 125ms, 250ms, 375ms, 500ms ... 31875ms.

		Thus the format of the hardware pattern values should be:
		"0 rise_duration brightness high_duration 0 fall_duration 0 low_duration".
+121 −0
Original line number Diff line number Diff line
@@ -32,8 +32,18 @@
#define SC27XX_DUTY_MASK	GENMASK(15, 0)
#define SC27XX_MOD_MASK		GENMASK(7, 0)

#define SC27XX_CURVE_SHIFT	8
#define SC27XX_CURVE_L_MASK	GENMASK(7, 0)
#define SC27XX_CURVE_H_MASK	GENMASK(15, 8)

#define SC27XX_LEDS_OFFSET	0x10
#define SC27XX_LEDS_MAX		3
#define SC27XX_LEDS_PATTERN_CNT	4
/* Stage duration step, in milliseconds */
#define SC27XX_LEDS_STEP	125
/* Minimum and maximum duration, in milliseconds */
#define SC27XX_DELTA_T_MIN	SC27XX_LEDS_STEP
#define SC27XX_DELTA_T_MAX	(SC27XX_LEDS_STEP * 255)

struct sc27xx_led {
	char name[LED_MAX_NAME_SIZE];
@@ -122,6 +132,113 @@ static int sc27xx_led_set(struct led_classdev *ldev, enum led_brightness value)
	return err;
}

static void sc27xx_led_clamp_align_delta_t(u32 *delta_t)
{
	u32 v, offset, t = *delta_t;

	v = t + SC27XX_LEDS_STEP / 2;
	v = clamp_t(u32, v, SC27XX_DELTA_T_MIN, SC27XX_DELTA_T_MAX);
	offset = v - SC27XX_DELTA_T_MIN;
	offset = SC27XX_LEDS_STEP * (offset / SC27XX_LEDS_STEP);

	*delta_t = SC27XX_DELTA_T_MIN + offset;
}

static int sc27xx_led_pattern_clear(struct led_classdev *ldev)
{
	struct sc27xx_led *leds = to_sc27xx_led(ldev);
	struct regmap *regmap = leds->priv->regmap;
	u32 base = sc27xx_led_get_offset(leds);
	u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL;
	u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line;
	int err;

	mutex_lock(&leds->priv->lock);

	/* Reset the rise, high, fall and low time to zero. */
	regmap_write(regmap, base + SC27XX_LEDS_CURVE0, 0);
	regmap_write(regmap, base + SC27XX_LEDS_CURVE1, 0);

	err = regmap_update_bits(regmap, ctrl_base,
			(SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, 0);

	ldev->brightness = LED_OFF;

	mutex_unlock(&leds->priv->lock);

	return err;
}

static int sc27xx_led_pattern_set(struct led_classdev *ldev,
				  struct led_pattern *pattern,
				  u32 len, int repeat)
{
	struct sc27xx_led *leds = to_sc27xx_led(ldev);
	u32 base = sc27xx_led_get_offset(leds);
	u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL;
	u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line;
	struct regmap *regmap = leds->priv->regmap;
	int err;

	/*
	 * Must contain 4 tuples to configure the rise time, high time, fall
	 * time and low time to enable the breathing mode.
	 */
	if (len != SC27XX_LEDS_PATTERN_CNT)
		return -EINVAL;

	mutex_lock(&leds->priv->lock);

	sc27xx_led_clamp_align_delta_t(&pattern[0].delta_t);
	err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE0,
				 SC27XX_CURVE_L_MASK,
				 pattern[0].delta_t / SC27XX_LEDS_STEP);
	if (err)
		goto out;

	sc27xx_led_clamp_align_delta_t(&pattern[1].delta_t);
	err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE1,
				 SC27XX_CURVE_L_MASK,
				 pattern[1].delta_t / SC27XX_LEDS_STEP);
	if (err)
		goto out;

	sc27xx_led_clamp_align_delta_t(&pattern[2].delta_t);
	err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE0,
				 SC27XX_CURVE_H_MASK,
				 (pattern[2].delta_t / SC27XX_LEDS_STEP) <<
				 SC27XX_CURVE_SHIFT);
	if (err)
		goto out;

	sc27xx_led_clamp_align_delta_t(&pattern[3].delta_t);
	err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE1,
				 SC27XX_CURVE_H_MASK,
				 (pattern[3].delta_t / SC27XX_LEDS_STEP) <<
				 SC27XX_CURVE_SHIFT);
	if (err)
		goto out;

	err = regmap_update_bits(regmap, base + SC27XX_LEDS_DUTY,
				 SC27XX_DUTY_MASK,
				 (pattern[1].brightness << SC27XX_DUTY_SHIFT) |
				 SC27XX_MOD_MASK);
	if (err)
		goto out;

	/* Enable the LED breathing mode */
	err = regmap_update_bits(regmap, ctrl_base,
				 SC27XX_LED_RUN << ctrl_shift,
				 SC27XX_LED_RUN << ctrl_shift);
	if (!err)
		ldev->brightness = pattern[1].brightness;

out:
	mutex_unlock(&leds->priv->lock);

	return err;
}

static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv)
{
	int i, err;
@@ -140,6 +257,9 @@ static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv)
		led->priv = priv;
		led->ldev.name = led->name;
		led->ldev.brightness_set_blocking = sc27xx_led_set;
		led->ldev.pattern_set = sc27xx_led_pattern_set;
		led->ldev.pattern_clear = sc27xx_led_pattern_clear;
		led->ldev.default_trigger = "pattern";

		err = devm_led_classdev_register(dev, &led->ldev);
		if (err)
@@ -241,4 +361,5 @@ module_platform_driver(sc27xx_led_driver);

MODULE_DESCRIPTION("Spreadtrum SC27xx breathing light controller driver");
MODULE_AUTHOR("Xiaotong Lu <xiaotong.lu@spreadtrum.com>");
MODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org>");
MODULE_LICENSE("GPL v2");