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: Baolin Wang <baolin.wang@linaro.org>
Acked-by: Pavel Machek <pavel@ucw.c>
Signed-off-by: Jacek Anaszewski <jacek.anaszewski@gmail.com>
This commit is contained in:
Baolin Wang 2018-10-11 12:07:15 +08:00 committed by Jacek Anaszewski
parent 5fd752b6b3
commit 8dbac65f5c
2 changed files with 143 additions and 0 deletions

View file

@ -0,0 +1,22 @@
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".

View file

@ -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");