Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Baolin Wang and committed by
Jacek Anaszewski
8dbac65f 5fd752b6

+143
+22
Documentation/ABI/testing/sysfs-class-led-driver-sc27xx
··· 1 + What: /sys/class/leds/<led>/hw_pattern 2 + Date: September 2018 3 + KernelVersion: 4.20 4 + Description: 5 + Specify a hardware pattern for the SC27XX LED. For the SC27XX 6 + LED controller, it only supports 4 stages to make a single 7 + hardware pattern, which is used to configure the rise time, 8 + high time, fall time and low time for the breathing mode. 9 + 10 + For the breathing mode, the SC27XX LED only expects one brightness 11 + for the high stage. To be compatible with the hardware pattern 12 + format, we should set brightness as 0 for rise stage, fall 13 + stage and low stage. 14 + 15 + Min stage duration: 125 ms 16 + Max stage duration: 31875 ms 17 + 18 + Since the stage duration step is 125 ms, the duration should be 19 + a multiplier of 125, like 125ms, 250ms, 375ms, 500ms ... 31875ms. 20 + 21 + Thus the format of the hardware pattern values should be: 22 + "0 rise_duration brightness high_duration 0 fall_duration 0 low_duration".
+121
drivers/leds/leds-sc27xx-bltc.c
··· 32 32 #define SC27XX_DUTY_MASK GENMASK(15, 0) 33 33 #define SC27XX_MOD_MASK GENMASK(7, 0) 34 34 35 + #define SC27XX_CURVE_SHIFT 8 36 + #define SC27XX_CURVE_L_MASK GENMASK(7, 0) 37 + #define SC27XX_CURVE_H_MASK GENMASK(15, 8) 38 + 35 39 #define SC27XX_LEDS_OFFSET 0x10 36 40 #define SC27XX_LEDS_MAX 3 41 + #define SC27XX_LEDS_PATTERN_CNT 4 42 + /* Stage duration step, in milliseconds */ 43 + #define SC27XX_LEDS_STEP 125 44 + /* Minimum and maximum duration, in milliseconds */ 45 + #define SC27XX_DELTA_T_MIN SC27XX_LEDS_STEP 46 + #define SC27XX_DELTA_T_MAX (SC27XX_LEDS_STEP * 255) 37 47 38 48 struct sc27xx_led { 39 49 char name[LED_MAX_NAME_SIZE]; ··· 132 122 return err; 133 123 } 134 124 125 + static void sc27xx_led_clamp_align_delta_t(u32 *delta_t) 126 + { 127 + u32 v, offset, t = *delta_t; 128 + 129 + v = t + SC27XX_LEDS_STEP / 2; 130 + v = clamp_t(u32, v, SC27XX_DELTA_T_MIN, SC27XX_DELTA_T_MAX); 131 + offset = v - SC27XX_DELTA_T_MIN; 132 + offset = SC27XX_LEDS_STEP * (offset / SC27XX_LEDS_STEP); 133 + 134 + *delta_t = SC27XX_DELTA_T_MIN + offset; 135 + } 136 + 137 + static int sc27xx_led_pattern_clear(struct led_classdev *ldev) 138 + { 139 + struct sc27xx_led *leds = to_sc27xx_led(ldev); 140 + struct regmap *regmap = leds->priv->regmap; 141 + u32 base = sc27xx_led_get_offset(leds); 142 + u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL; 143 + u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line; 144 + int err; 145 + 146 + mutex_lock(&leds->priv->lock); 147 + 148 + /* Reset the rise, high, fall and low time to zero. */ 149 + regmap_write(regmap, base + SC27XX_LEDS_CURVE0, 0); 150 + regmap_write(regmap, base + SC27XX_LEDS_CURVE1, 0); 151 + 152 + err = regmap_update_bits(regmap, ctrl_base, 153 + (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, 0); 154 + 155 + ldev->brightness = LED_OFF; 156 + 157 + mutex_unlock(&leds->priv->lock); 158 + 159 + return err; 160 + } 161 + 162 + static int sc27xx_led_pattern_set(struct led_classdev *ldev, 163 + struct led_pattern *pattern, 164 + u32 len, int repeat) 165 + { 166 + struct sc27xx_led *leds = to_sc27xx_led(ldev); 167 + u32 base = sc27xx_led_get_offset(leds); 168 + u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL; 169 + u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line; 170 + struct regmap *regmap = leds->priv->regmap; 171 + int err; 172 + 173 + /* 174 + * Must contain 4 tuples to configure the rise time, high time, fall 175 + * time and low time to enable the breathing mode. 176 + */ 177 + if (len != SC27XX_LEDS_PATTERN_CNT) 178 + return -EINVAL; 179 + 180 + mutex_lock(&leds->priv->lock); 181 + 182 + sc27xx_led_clamp_align_delta_t(&pattern[0].delta_t); 183 + err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE0, 184 + SC27XX_CURVE_L_MASK, 185 + pattern[0].delta_t / SC27XX_LEDS_STEP); 186 + if (err) 187 + goto out; 188 + 189 + sc27xx_led_clamp_align_delta_t(&pattern[1].delta_t); 190 + err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE1, 191 + SC27XX_CURVE_L_MASK, 192 + pattern[1].delta_t / SC27XX_LEDS_STEP); 193 + if (err) 194 + goto out; 195 + 196 + sc27xx_led_clamp_align_delta_t(&pattern[2].delta_t); 197 + err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE0, 198 + SC27XX_CURVE_H_MASK, 199 + (pattern[2].delta_t / SC27XX_LEDS_STEP) << 200 + SC27XX_CURVE_SHIFT); 201 + if (err) 202 + goto out; 203 + 204 + sc27xx_led_clamp_align_delta_t(&pattern[3].delta_t); 205 + err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE1, 206 + SC27XX_CURVE_H_MASK, 207 + (pattern[3].delta_t / SC27XX_LEDS_STEP) << 208 + SC27XX_CURVE_SHIFT); 209 + if (err) 210 + goto out; 211 + 212 + err = regmap_update_bits(regmap, base + SC27XX_LEDS_DUTY, 213 + SC27XX_DUTY_MASK, 214 + (pattern[1].brightness << SC27XX_DUTY_SHIFT) | 215 + SC27XX_MOD_MASK); 216 + if (err) 217 + goto out; 218 + 219 + /* Enable the LED breathing mode */ 220 + err = regmap_update_bits(regmap, ctrl_base, 221 + SC27XX_LED_RUN << ctrl_shift, 222 + SC27XX_LED_RUN << ctrl_shift); 223 + if (!err) 224 + ldev->brightness = pattern[1].brightness; 225 + 226 + out: 227 + mutex_unlock(&leds->priv->lock); 228 + 229 + return err; 230 + } 231 + 135 232 static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv) 136 233 { 137 234 int i, err; ··· 257 140 led->priv = priv; 258 141 led->ldev.name = led->name; 259 142 led->ldev.brightness_set_blocking = sc27xx_led_set; 143 + led->ldev.pattern_set = sc27xx_led_pattern_set; 144 + led->ldev.pattern_clear = sc27xx_led_pattern_clear; 145 + led->ldev.default_trigger = "pattern"; 260 146 261 147 err = devm_led_classdev_register(dev, &led->ldev); 262 148 if (err) ··· 361 241 362 242 MODULE_DESCRIPTION("Spreadtrum SC27xx breathing light controller driver"); 363 243 MODULE_AUTHOR("Xiaotong Lu <xiaotong.lu@spreadtrum.com>"); 244 + MODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org>"); 364 245 MODULE_LICENSE("GPL v2");