diff options
-rw-r--r-- | Documentation/ABI/testing/sysfs-class-led-driver-sc27xx | 22 | ||||
-rw-r--r-- | drivers/leds/leds-sc27xx-bltc.c | 121 |
2 files changed, 143 insertions, 0 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-led-driver-sc27xx b/Documentation/ABI/testing/sysfs-class-led-driver-sc27xx new file mode 100644 index 000000000000..45b1e605d355 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-led-driver-sc27xx | |||
@@ -0,0 +1,22 @@ | |||
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". | ||
diff --git a/drivers/leds/leds-sc27xx-bltc.c b/drivers/leds/leds-sc27xx-bltc.c index 9d9b7aab843f..fecf27fb1cdc 100644 --- a/drivers/leds/leds-sc27xx-bltc.c +++ b/drivers/leds/leds-sc27xx-bltc.c | |||
@@ -32,8 +32,18 @@ | |||
32 | #define SC27XX_DUTY_MASK GENMASK(15, 0) | 32 | #define SC27XX_DUTY_MASK GENMASK(15, 0) |
33 | #define SC27XX_MOD_MASK GENMASK(7, 0) | 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 | #define SC27XX_LEDS_OFFSET 0x10 | 39 | #define SC27XX_LEDS_OFFSET 0x10 |
36 | #define SC27XX_LEDS_MAX 3 | 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 | struct sc27xx_led { | 48 | struct sc27xx_led { |
39 | char name[LED_MAX_NAME_SIZE]; | 49 | char name[LED_MAX_NAME_SIZE]; |
@@ -122,6 +132,113 @@ static int sc27xx_led_set(struct led_classdev *ldev, enum led_brightness value) | |||
122 | return err; | 132 | return err; |
123 | } | 133 | } |
124 | 134 | ||
135 | static void sc27xx_led_clamp_align_delta_t(u32 *delta_t) | ||
136 | { | ||
137 | u32 v, offset, t = *delta_t; | ||
138 | |||
139 | v = t + SC27XX_LEDS_STEP / 2; | ||
140 | v = clamp_t(u32, v, SC27XX_DELTA_T_MIN, SC27XX_DELTA_T_MAX); | ||
141 | offset = v - SC27XX_DELTA_T_MIN; | ||
142 | offset = SC27XX_LEDS_STEP * (offset / SC27XX_LEDS_STEP); | ||
143 | |||
144 | *delta_t = SC27XX_DELTA_T_MIN + offset; | ||
145 | } | ||
146 | |||
147 | static int sc27xx_led_pattern_clear(struct led_classdev *ldev) | ||
148 | { | ||
149 | struct sc27xx_led *leds = to_sc27xx_led(ldev); | ||
150 | struct regmap *regmap = leds->priv->regmap; | ||
151 | u32 base = sc27xx_led_get_offset(leds); | ||
152 | u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL; | ||
153 | u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line; | ||
154 | int err; | ||
155 | |||
156 | mutex_lock(&leds->priv->lock); | ||
157 | |||
158 | /* Reset the rise, high, fall and low time to zero. */ | ||
159 | regmap_write(regmap, base + SC27XX_LEDS_CURVE0, 0); | ||
160 | regmap_write(regmap, base + SC27XX_LEDS_CURVE1, 0); | ||
161 | |||
162 | err = regmap_update_bits(regmap, ctrl_base, | ||
163 | (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, 0); | ||
164 | |||
165 | ldev->brightness = LED_OFF; | ||
166 | |||
167 | mutex_unlock(&leds->priv->lock); | ||
168 | |||
169 | return err; | ||
170 | } | ||
171 | |||
172 | static int sc27xx_led_pattern_set(struct led_classdev *ldev, | ||
173 | struct led_pattern *pattern, | ||
174 | u32 len, int repeat) | ||
175 | { | ||
176 | struct sc27xx_led *leds = to_sc27xx_led(ldev); | ||
177 | u32 base = sc27xx_led_get_offset(leds); | ||
178 | u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL; | ||
179 | u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line; | ||
180 | struct regmap *regmap = leds->priv->regmap; | ||
181 | int err; | ||
182 | |||
183 | /* | ||
184 | * Must contain 4 tuples to configure the rise time, high time, fall | ||
185 | * time and low time to enable the breathing mode. | ||
186 | */ | ||
187 | if (len != SC27XX_LEDS_PATTERN_CNT) | ||
188 | return -EINVAL; | ||
189 | |||
190 | mutex_lock(&leds->priv->lock); | ||
191 | |||
192 | sc27xx_led_clamp_align_delta_t(&pattern[0].delta_t); | ||
193 | err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE0, | ||
194 | SC27XX_CURVE_L_MASK, | ||
195 | pattern[0].delta_t / SC27XX_LEDS_STEP); | ||
196 | if (err) | ||
197 | goto out; | ||
198 | |||
199 | sc27xx_led_clamp_align_delta_t(&pattern[1].delta_t); | ||
200 | err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE1, | ||
201 | SC27XX_CURVE_L_MASK, | ||
202 | pattern[1].delta_t / SC27XX_LEDS_STEP); | ||
203 | if (err) | ||
204 | goto out; | ||
205 | |||
206 | sc27xx_led_clamp_align_delta_t(&pattern[2].delta_t); | ||
207 | err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE0, | ||
208 | SC27XX_CURVE_H_MASK, | ||
209 | (pattern[2].delta_t / SC27XX_LEDS_STEP) << | ||
210 | SC27XX_CURVE_SHIFT); | ||
211 | if (err) | ||
212 | goto out; | ||
213 | |||
214 | sc27xx_led_clamp_align_delta_t(&pattern[3].delta_t); | ||
215 | err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE1, | ||
216 | SC27XX_CURVE_H_MASK, | ||
217 | (pattern[3].delta_t / SC27XX_LEDS_STEP) << | ||
218 | SC27XX_CURVE_SHIFT); | ||
219 | if (err) | ||
220 | goto out; | ||
221 | |||
222 | err = regmap_update_bits(regmap, base + SC27XX_LEDS_DUTY, | ||
223 | SC27XX_DUTY_MASK, | ||
224 | (pattern[1].brightness << SC27XX_DUTY_SHIFT) | | ||
225 | SC27XX_MOD_MASK); | ||
226 | if (err) | ||
227 | goto out; | ||
228 | |||
229 | /* Enable the LED breathing mode */ | ||
230 | err = regmap_update_bits(regmap, ctrl_base, | ||
231 | SC27XX_LED_RUN << ctrl_shift, | ||
232 | SC27XX_LED_RUN << ctrl_shift); | ||
233 | if (!err) | ||
234 | ldev->brightness = pattern[1].brightness; | ||
235 | |||
236 | out: | ||
237 | mutex_unlock(&leds->priv->lock); | ||
238 | |||
239 | return err; | ||
240 | } | ||
241 | |||
125 | static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv) | 242 | static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv) |
126 | { | 243 | { |
127 | int i, err; | 244 | int i, err; |
@@ -140,6 +257,9 @@ static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv) | |||
140 | led->priv = priv; | 257 | led->priv = priv; |
141 | led->ldev.name = led->name; | 258 | led->ldev.name = led->name; |
142 | led->ldev.brightness_set_blocking = sc27xx_led_set; | 259 | led->ldev.brightness_set_blocking = sc27xx_led_set; |
260 | led->ldev.pattern_set = sc27xx_led_pattern_set; | ||
261 | led->ldev.pattern_clear = sc27xx_led_pattern_clear; | ||
262 | led->ldev.default_trigger = "pattern"; | ||
143 | 263 | ||
144 | err = devm_led_classdev_register(dev, &led->ldev); | 264 | err = devm_led_classdev_register(dev, &led->ldev); |
145 | if (err) | 265 | if (err) |
@@ -241,4 +361,5 @@ module_platform_driver(sc27xx_led_driver); | |||
241 | 361 | ||
242 | MODULE_DESCRIPTION("Spreadtrum SC27xx breathing light controller driver"); | 362 | MODULE_DESCRIPTION("Spreadtrum SC27xx breathing light controller driver"); |
243 | MODULE_AUTHOR("Xiaotong Lu <xiaotong.lu@spreadtrum.com>"); | 363 | MODULE_AUTHOR("Xiaotong Lu <xiaotong.lu@spreadtrum.com>"); |
364 | MODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org>"); | ||
244 | MODULE_LICENSE("GPL v2"); | 365 | MODULE_LICENSE("GPL v2"); |