1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // 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 0xc08 |
12 | #define SC27XX_CLK_EN0 0xc18 |
13 | #define SC27XX_RGB_CTRL 0xebc |
14 | |
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 0x00 |
21 | #define SC27XX_LEDS_PRESCALE 0x04 |
22 | #define SC27XX_LEDS_DUTY 0x08 |
23 | #define SC27XX_LEDS_CURVE0 0x0c |
24 | #define SC27XX_LEDS_CURVE1 0x10 |
25 | |
26 | #define SC27XX_CTRL_SHIFT 4 |
27 | #define SC27XX_LED_RUN BIT(0) |
28 | #define SC27XX_LED_TYPE BIT(1) |
29 | |
30 | #define SC27XX_DUTY_SHIFT 8 |
31 | #define SC27XX_DUTY_MASK GENMASK(15, 0) |
32 | #define SC27XX_MOD_MASK GENMASK(7, 0) |
33 | |
34 | #define SC27XX_CURVE_SHIFT 8 |
35 | #define SC27XX_CURVE_L_MASK GENMASK(7, 0) |
36 | #define SC27XX_CURVE_H_MASK GENMASK(15, 8) |
37 | |
38 | #define SC27XX_LEDS_OFFSET 0x10 |
39 | #define SC27XX_LEDS_MAX 3 |
40 | #define SC27XX_LEDS_PATTERN_CNT 4 |
41 | /* Stage duration step, in milliseconds */ |
42 | #define SC27XX_LEDS_STEP 125 |
43 | /* Minimum and maximum duration, in milliseconds */ |
44 | #define SC27XX_DELTA_T_MIN SC27XX_LEDS_STEP |
45 | #define SC27XX_DELTA_T_MAX (SC27XX_LEDS_STEP * 255) |
46 | |
47 | struct 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 | |
55 | struct 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 | |
65 | static int sc27xx_led_init(struct regmap *regmap) |
66 | { |
67 | int err; |
68 | |
69 | err = regmap_update_bits(map: regmap, SC27XX_MODULE_EN0, SC27XX_BLTC_EN, |
70 | SC27XX_BLTC_EN); |
71 | if (err) |
72 | return err; |
73 | |
74 | err = regmap_update_bits(map: regmap, SC27XX_CLK_EN0, SC27XX_RTC_EN, |
75 | SC27XX_RTC_EN); |
76 | if (err) |
77 | return err; |
78 | |
79 | return regmap_update_bits(map: regmap, SC27XX_RGB_CTRL, SC27XX_RGB_PD, val: 0); |
80 | } |
81 | |
82 | static u32 sc27xx_led_get_offset(struct sc27xx_led *leds) |
83 | { |
84 | return leds->priv->base + SC27XX_LEDS_OFFSET * leds->line; |
85 | } |
86 | |
87 | static 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(map: regmap, reg: base + SC27XX_LEDS_DUTY, |
96 | SC27XX_DUTY_MASK, |
97 | val: (value << SC27XX_DUTY_SHIFT) | |
98 | SC27XX_MOD_MASK); |
99 | if (err) |
100 | return err; |
101 | |
102 | return regmap_update_bits(map: regmap, reg: ctrl_base, |
103 | mask: (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, |
104 | val: (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift); |
105 | } |
106 | |
107 | static 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(map: regmap, reg: ctrl_base, |
114 | mask: (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, val: 0); |
115 | } |
116 | |
117 | static 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 | else |
127 | err = sc27xx_led_enable(leds, value); |
128 | |
129 | mutex_unlock(lock: &leds->priv->lock); |
130 | |
131 | return err; |
132 | } |
133 | |
134 | static 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 | |
146 | static 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(map: regmap, reg: base + SC27XX_LEDS_CURVE0, val: 0); |
159 | regmap_write(map: regmap, reg: base + SC27XX_LEDS_CURVE1, val: 0); |
160 | |
161 | err = regmap_update_bits(map: regmap, reg: ctrl_base, |
162 | mask: (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, val: 0); |
163 | |
164 | ldev->brightness = LED_OFF; |
165 | |
166 | mutex_unlock(lock: &leds->priv->lock); |
167 | |
168 | return err; |
169 | } |
170 | |
171 | static 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, fall |
184 | * 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(delta_t: &pattern[0].delta_t); |
192 | err = regmap_update_bits(map: regmap, reg: base + SC27XX_LEDS_CURVE0, |
193 | SC27XX_CURVE_L_MASK, |
194 | val: pattern[0].delta_t / SC27XX_LEDS_STEP); |
195 | if (err) |
196 | goto out; |
197 | |
198 | sc27xx_led_clamp_align_delta_t(delta_t: &pattern[1].delta_t); |
199 | err = regmap_update_bits(map: regmap, reg: base + SC27XX_LEDS_CURVE1, |
200 | SC27XX_CURVE_L_MASK, |
201 | val: pattern[1].delta_t / SC27XX_LEDS_STEP); |
202 | if (err) |
203 | goto out; |
204 | |
205 | sc27xx_led_clamp_align_delta_t(delta_t: &pattern[2].delta_t); |
206 | err = regmap_update_bits(map: regmap, reg: base + SC27XX_LEDS_CURVE0, |
207 | SC27XX_CURVE_H_MASK, |
208 | val: (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(delta_t: &pattern[3].delta_t); |
214 | err = regmap_update_bits(map: regmap, reg: base + SC27XX_LEDS_CURVE1, |
215 | SC27XX_CURVE_H_MASK, |
216 | val: (pattern[3].delta_t / SC27XX_LEDS_STEP) << |
217 | SC27XX_CURVE_SHIFT); |
218 | if (err) |
219 | goto out; |
220 | |
221 | err = regmap_update_bits(map: regmap, reg: base + SC27XX_LEDS_DUTY, |
222 | SC27XX_DUTY_MASK, |
223 | val: (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(map: regmap, reg: ctrl_base, |
230 | SC27XX_LED_RUN << ctrl_shift, |
231 | SC27XX_LED_RUN << ctrl_shift); |
232 | if (!err) |
233 | ldev->brightness = pattern[1].brightness; |
234 | |
235 | out: |
236 | mutex_unlock(lock: &leds->priv->lock); |
237 | |
238 | return err; |
239 | } |
240 | |
241 | static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv) |
242 | { |
243 | int i, err; |
244 | |
245 | err = sc27xx_led_init(regmap: 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(parent: dev, led_cdev: &led->ldev, |
268 | init_data: &init_data); |
269 | if (err) |
270 | return err; |
271 | } |
272 | |
273 | return 0; |
274 | } |
275 | |
276 | static int sc27xx_led_probe(struct platform_device *pdev) |
277 | { |
278 | struct device *dev = &pdev->dev; |
279 | struct device_node *np = dev_of_node(dev), *child; |
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, propname: "reg" , out_value: &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, size: sizeof(*priv), GFP_KERNEL); |
295 | if (!priv) |
296 | return -ENOMEM; |
297 | |
298 | platform_set_drvdata(pdev, data: priv); |
299 | priv->base = base; |
300 | priv->regmap = dev_get_regmap(dev: 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(np, child) { |
308 | err = of_property_read_u32(np: child, propname: "reg" , out_value: ®); |
309 | if (err) { |
310 | of_node_put(node: child); |
311 | return err; |
312 | } |
313 | |
314 | if (reg >= SC27XX_LEDS_MAX || priv->leds[reg].active) { |
315 | of_node_put(node: child); |
316 | return -EINVAL; |
317 | } |
318 | |
319 | priv->leds[reg].fwnode = of_fwnode_handle(child); |
320 | priv->leds[reg].active = true; |
321 | } |
322 | |
323 | mutex_init(&priv->lock); |
324 | |
325 | err = sc27xx_led_register(dev, priv); |
326 | if (err) |
327 | mutex_destroy(lock: &priv->lock); |
328 | |
329 | return err; |
330 | } |
331 | |
332 | static void sc27xx_led_remove(struct platform_device *pdev) |
333 | { |
334 | struct sc27xx_led_priv *priv = platform_get_drvdata(pdev); |
335 | |
336 | mutex_destroy(lock: &priv->lock); |
337 | } |
338 | |
339 | static const struct of_device_id sc27xx_led_of_match[] = { |
340 | { .compatible = "sprd,sc2731-bltc" , }, |
341 | { } |
342 | }; |
343 | MODULE_DEVICE_TABLE(of, sc27xx_led_of_match); |
344 | |
345 | static struct platform_driver sc27xx_led_driver = { |
346 | .driver = { |
347 | .name = "sprd-bltc" , |
348 | .of_match_table = sc27xx_led_of_match, |
349 | }, |
350 | .probe = sc27xx_led_probe, |
351 | .remove_new = sc27xx_led_remove, |
352 | }; |
353 | |
354 | module_platform_driver(sc27xx_led_driver); |
355 | |
356 | MODULE_DESCRIPTION("Spreadtrum SC27xx breathing light controller driver" ); |
357 | MODULE_AUTHOR("Xiaotong Lu <xiaotong.lu@spreadtrum.com>" ); |
358 | MODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org>" ); |
359 | MODULE_LICENSE("GPL v2" ); |
360 | |