diff options
author | Simon Shields <simon@lineageos.org> | 2018-09-09 06:38:25 -0400 |
---|---|---|
committer | Jacek Anaszewski <jacek.anaszewski@gmail.com> | 2018-09-10 15:31:11 -0400 |
commit | 2d00f35c55e74fcb5626c9aa336c1ed697cd2ae9 (patch) | |
tree | 5b4e770455b5b4bb668c8f599a6b12de8719469c | |
parent | 86bc7b2d753ad361bfd358c89a8888bae04c8e5a (diff) |
leds: add Panasonic AN30259A support
AN30259A is a 3-channel LED driver which uses I2C. It supports timed
operation via an internal PWM clock, and variable brightness. This
driver offers support for basic hardware-based blinking and brightness
control.
The datasheet is freely available:
https://www.alliedelec.com/m/d/a9d2b3ee87c2d1a535a41dd747b1c247.pdf
Signed-off-by: Simon Shields <simon@lineageos.org>
Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Jacek Anaszewski <jacek.anaszewski@gmail.com>
-rw-r--r-- | drivers/leds/Kconfig | 10 | ||||
-rw-r--r-- | drivers/leds/Makefile | 1 | ||||
-rw-r--r-- | drivers/leds/leds-an30259a.c | 368 |
3 files changed, 379 insertions, 0 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 44097a3e0fcc..a72f97fca57b 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig | |||
@@ -58,6 +58,16 @@ config LEDS_AAT1290 | |||
58 | help | 58 | help |
59 | This option enables support for the LEDs on the AAT1290. | 59 | This option enables support for the LEDs on the AAT1290. |
60 | 60 | ||
61 | config LEDS_AN30259A | ||
62 | tristate "LED support for Panasonic AN30259A" | ||
63 | depends on LEDS_CLASS && I2C && OF | ||
64 | help | ||
65 | This option enables support for the AN30259A 3-channel | ||
66 | LED driver. | ||
67 | |||
68 | To compile this driver as a module, choose M here: the module | ||
69 | will be called leds-an30259a. | ||
70 | |||
61 | config LEDS_APU | 71 | config LEDS_APU |
62 | tristate "Front panel LED support for PC Engines APU/APU2/APU3 boards" | 72 | tristate "Front panel LED support for PC Engines APU/APU2/APU3 boards" |
63 | depends on LEDS_CLASS | 73 | depends on LEDS_CLASS |
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 420b5d2cfa62..4c1b0054f379 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile | |||
@@ -11,6 +11,7 @@ obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o | |||
11 | obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o | 11 | obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o |
12 | obj-$(CONFIG_LEDS_APU) += leds-apu.o | 12 | obj-$(CONFIG_LEDS_APU) += leds-apu.o |
13 | obj-$(CONFIG_LEDS_AS3645A) += leds-as3645a.o | 13 | obj-$(CONFIG_LEDS_AS3645A) += leds-as3645a.o |
14 | obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o | ||
14 | obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o | 15 | obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o |
15 | obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o | 16 | obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o |
16 | obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o | 17 | obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o |
diff --git a/drivers/leds/leds-an30259a.c b/drivers/leds/leds-an30259a.c new file mode 100644 index 000000000000..1c1f0c8c56f4 --- /dev/null +++ b/drivers/leds/leds-an30259a.c | |||
@@ -0,0 +1,368 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0+ | ||
2 | // | ||
3 | // Driver for Panasonic AN30259A 3-channel LED driver | ||
4 | // | ||
5 | // Copyright (c) 2018 Simon Shields <simon@lineageos.org> | ||
6 | // | ||
7 | // Datasheet: | ||
8 | // https://www.alliedelec.com/m/d/a9d2b3ee87c2d1a535a41dd747b1c247.pdf | ||
9 | |||
10 | #include <linux/i2c.h> | ||
11 | #include <linux/leds.h> | ||
12 | #include <linux/module.h> | ||
13 | #include <linux/mutex.h> | ||
14 | #include <linux/of.h> | ||
15 | #include <linux/regmap.h> | ||
16 | #include <uapi/linux/uleds.h> | ||
17 | |||
18 | #define AN30259A_MAX_LEDS 3 | ||
19 | |||
20 | #define AN30259A_REG_SRESET 0x00 | ||
21 | #define AN30259A_LED_SRESET BIT(0) | ||
22 | |||
23 | /* LED power registers */ | ||
24 | #define AN30259A_REG_LED_ON 0x01 | ||
25 | #define AN30259A_LED_EN(x) BIT((x) - 1) | ||
26 | #define AN30259A_LED_SLOPE(x) BIT(((x) - 1) + 4) | ||
27 | |||
28 | #define AN30259A_REG_LEDCC(x) (0x03 + ((x) - 1)) | ||
29 | |||
30 | /* slope control registers */ | ||
31 | #define AN30259A_REG_SLOPE(x) (0x06 + ((x) - 1)) | ||
32 | #define AN30259A_LED_SLOPETIME1(x) (x) | ||
33 | #define AN30259A_LED_SLOPETIME2(x) ((x) << 4) | ||
34 | |||
35 | #define AN30259A_REG_LEDCNT1(x) (0x09 + (4 * ((x) - 1))) | ||
36 | #define AN30259A_LED_DUTYMAX(x) ((x) << 4) | ||
37 | #define AN30259A_LED_DUTYMID(x) (x) | ||
38 | |||
39 | #define AN30259A_REG_LEDCNT2(x) (0x0A + (4 * ((x) - 1))) | ||
40 | #define AN30259A_LED_DELAY(x) ((x) << 4) | ||
41 | #define AN30259A_LED_DUTYMIN(x) (x) | ||
42 | |||
43 | /* detention time control (length of each slope step) */ | ||
44 | #define AN30259A_REG_LEDCNT3(x) (0x0B + (4 * ((x) - 1))) | ||
45 | #define AN30259A_LED_DT1(x) (x) | ||
46 | #define AN30259A_LED_DT2(x) ((x) << 4) | ||
47 | |||
48 | #define AN30259A_REG_LEDCNT4(x) (0x0C + (4 * ((x) - 1))) | ||
49 | #define AN30259A_LED_DT3(x) (x) | ||
50 | #define AN30259A_LED_DT4(x) ((x) << 4) | ||
51 | |||
52 | #define AN30259A_REG_MAX 0x14 | ||
53 | |||
54 | #define AN30259A_BLINK_MAX_TIME 7500 /* ms */ | ||
55 | #define AN30259A_SLOPE_RESOLUTION 500 /* ms */ | ||
56 | |||
57 | #define STATE_OFF 0 | ||
58 | #define STATE_KEEP 1 | ||
59 | #define STATE_ON 2 | ||
60 | |||
61 | struct an30259a; | ||
62 | |||
63 | struct an30259a_led { | ||
64 | struct an30259a *chip; | ||
65 | struct led_classdev cdev; | ||
66 | u32 num; | ||
67 | u32 default_state; | ||
68 | bool sloping; | ||
69 | char label[LED_MAX_NAME_SIZE]; | ||
70 | }; | ||
71 | |||
72 | struct an30259a { | ||
73 | struct mutex mutex; /* held when writing to registers */ | ||
74 | struct i2c_client *client; | ||
75 | struct an30259a_led leds[AN30259A_MAX_LEDS]; | ||
76 | struct regmap *regmap; | ||
77 | int num_leds; | ||
78 | }; | ||
79 | |||
80 | static int an30259a_brightness_set(struct led_classdev *cdev, | ||
81 | enum led_brightness brightness) | ||
82 | { | ||
83 | struct an30259a_led *led; | ||
84 | int ret; | ||
85 | unsigned int led_on; | ||
86 | |||
87 | led = container_of(cdev, struct an30259a_led, cdev); | ||
88 | mutex_lock(&led->chip->mutex); | ||
89 | |||
90 | ret = regmap_read(led->chip->regmap, AN30259A_REG_LED_ON, &led_on); | ||
91 | if (ret) | ||
92 | goto error; | ||
93 | |||
94 | switch (brightness) { | ||
95 | case LED_OFF: | ||
96 | led_on &= ~AN30259A_LED_EN(led->num); | ||
97 | led_on &= ~AN30259A_LED_SLOPE(led->num); | ||
98 | led->sloping = false; | ||
99 | break; | ||
100 | default: | ||
101 | led_on |= AN30259A_LED_EN(led->num); | ||
102 | if (led->sloping) | ||
103 | led_on |= AN30259A_LED_SLOPE(led->num); | ||
104 | ret = regmap_write(led->chip->regmap, | ||
105 | AN30259A_REG_LEDCNT1(led->num), | ||
106 | AN30259A_LED_DUTYMAX(0xf) | | ||
107 | AN30259A_LED_DUTYMID(0xf)); | ||
108 | if (ret) | ||
109 | goto error; | ||
110 | break; | ||
111 | } | ||
112 | |||
113 | ret = regmap_write(led->chip->regmap, AN30259A_REG_LED_ON, led_on); | ||
114 | if (ret) | ||
115 | goto error; | ||
116 | |||
117 | ret = regmap_write(led->chip->regmap, AN30259A_REG_LEDCC(led->num), | ||
118 | brightness); | ||
119 | |||
120 | error: | ||
121 | mutex_unlock(&led->chip->mutex); | ||
122 | |||
123 | return ret; | ||
124 | } | ||
125 | |||
126 | static int an30259a_blink_set(struct led_classdev *cdev, | ||
127 | unsigned long *delay_off, unsigned long *delay_on) | ||
128 | { | ||
129 | struct an30259a_led *led; | ||
130 | int ret, num; | ||
131 | unsigned int led_on; | ||
132 | unsigned long off = *delay_off, on = *delay_on; | ||
133 | |||
134 | led = container_of(cdev, struct an30259a_led, cdev); | ||
135 | |||
136 | mutex_lock(&led->chip->mutex); | ||
137 | num = led->num; | ||
138 | |||
139 | /* slope time can only be a multiple of 500ms. */ | ||
140 | if (off % AN30259A_SLOPE_RESOLUTION || on % AN30259A_SLOPE_RESOLUTION) { | ||
141 | ret = -EINVAL; | ||
142 | goto error; | ||
143 | } | ||
144 | |||
145 | /* up to a maximum of 7500ms. */ | ||
146 | if (off > AN30259A_BLINK_MAX_TIME || on > AN30259A_BLINK_MAX_TIME) { | ||
147 | ret = -EINVAL; | ||
148 | goto error; | ||
149 | } | ||
150 | |||
151 | /* if no blink specified, default to 1 Hz. */ | ||
152 | if (!off && !on) { | ||
153 | *delay_off = off = 500; | ||
154 | *delay_on = on = 500; | ||
155 | } | ||
156 | |||
157 | /* convert into values the HW will understand. */ | ||
158 | off /= AN30259A_SLOPE_RESOLUTION; | ||
159 | on /= AN30259A_SLOPE_RESOLUTION; | ||
160 | |||
161 | /* duty min should be zero (=off), delay should be zero. */ | ||
162 | ret = regmap_write(led->chip->regmap, AN30259A_REG_LEDCNT2(num), | ||
163 | AN30259A_LED_DELAY(0) | AN30259A_LED_DUTYMIN(0)); | ||
164 | if (ret) | ||
165 | goto error; | ||
166 | |||
167 | /* reset detention time (no "breathing" effect). */ | ||
168 | ret = regmap_write(led->chip->regmap, AN30259A_REG_LEDCNT3(num), | ||
169 | AN30259A_LED_DT1(0) | AN30259A_LED_DT2(0)); | ||
170 | if (ret) | ||
171 | goto error; | ||
172 | ret = regmap_write(led->chip->regmap, AN30259A_REG_LEDCNT4(num), | ||
173 | AN30259A_LED_DT3(0) | AN30259A_LED_DT4(0)); | ||
174 | if (ret) | ||
175 | goto error; | ||
176 | |||
177 | /* slope time controls on/off cycle length. */ | ||
178 | ret = regmap_write(led->chip->regmap, AN30259A_REG_SLOPE(num), | ||
179 | AN30259A_LED_SLOPETIME1(off) | | ||
180 | AN30259A_LED_SLOPETIME2(on)); | ||
181 | if (ret) | ||
182 | goto error; | ||
183 | |||
184 | /* Finally, enable slope mode. */ | ||
185 | ret = regmap_read(led->chip->regmap, AN30259A_REG_LED_ON, &led_on); | ||
186 | if (ret) | ||
187 | goto error; | ||
188 | |||
189 | led_on |= AN30259A_LED_SLOPE(num) | AN30259A_LED_EN(led->num); | ||
190 | |||
191 | ret = regmap_write(led->chip->regmap, AN30259A_REG_LED_ON, led_on); | ||
192 | |||
193 | if (!ret) | ||
194 | led->sloping = true; | ||
195 | error: | ||
196 | mutex_unlock(&led->chip->mutex); | ||
197 | |||
198 | return ret; | ||
199 | } | ||
200 | |||
201 | static int an30259a_dt_init(struct i2c_client *client, | ||
202 | struct an30259a *chip) | ||
203 | { | ||
204 | struct device_node *np = client->dev.of_node, *child; | ||
205 | int count, ret; | ||
206 | int i = 0; | ||
207 | const char *str; | ||
208 | struct an30259a_led *led; | ||
209 | |||
210 | count = of_get_child_count(np); | ||
211 | if (!count || count > AN30259A_MAX_LEDS) | ||
212 | return -EINVAL; | ||
213 | |||
214 | for_each_available_child_of_node(np, child) { | ||
215 | u32 source; | ||
216 | |||
217 | ret = of_property_read_u32(child, "reg", &source); | ||
218 | if (ret != 0 || !source || source > AN30259A_MAX_LEDS) { | ||
219 | dev_err(&client->dev, "Couldn't read LED address: %d\n", | ||
220 | ret); | ||
221 | count--; | ||
222 | continue; | ||
223 | } | ||
224 | |||
225 | led = &chip->leds[i]; | ||
226 | |||
227 | led->num = source; | ||
228 | led->chip = chip; | ||
229 | |||
230 | if (of_property_read_string(child, "label", &str)) | ||
231 | snprintf(led->label, sizeof(led->label), "an30259a::"); | ||
232 | else | ||
233 | snprintf(led->label, sizeof(led->label), "an30259a:%s", | ||
234 | str); | ||
235 | |||
236 | led->cdev.name = led->label; | ||
237 | |||
238 | if (!of_property_read_string(child, "default-state", &str)) { | ||
239 | if (!strcmp(str, "on")) | ||
240 | led->default_state = STATE_ON; | ||
241 | else if (!strcmp(str, "keep")) | ||
242 | led->default_state = STATE_KEEP; | ||
243 | else | ||
244 | led->default_state = STATE_OFF; | ||
245 | } | ||
246 | |||
247 | of_property_read_string(child, "linux,default-trigger", | ||
248 | &led->cdev.default_trigger); | ||
249 | |||
250 | i++; | ||
251 | } | ||
252 | |||
253 | if (!count) | ||
254 | return -EINVAL; | ||
255 | |||
256 | chip->num_leds = i; | ||
257 | |||
258 | return 0; | ||
259 | } | ||
260 | |||
261 | static const struct regmap_config an30259a_regmap_config = { | ||
262 | .reg_bits = 8, | ||
263 | .val_bits = 8, | ||
264 | .max_register = AN30259A_REG_MAX, | ||
265 | }; | ||
266 | |||
267 | static void an30259a_init_default_state(struct an30259a_led *led) | ||
268 | { | ||
269 | struct an30259a *chip = led->chip; | ||
270 | int led_on, err; | ||
271 | |||
272 | switch (led->default_state) { | ||
273 | case STATE_ON: | ||
274 | led->cdev.brightness = LED_FULL; | ||
275 | break; | ||
276 | case STATE_KEEP: | ||
277 | err = regmap_read(chip->regmap, AN30259A_REG_LED_ON, &led_on); | ||
278 | if (err) | ||
279 | break; | ||
280 | |||
281 | if (!(led_on & AN30259A_LED_EN(led->num))) { | ||
282 | led->cdev.brightness = LED_OFF; | ||
283 | break; | ||
284 | } | ||
285 | regmap_read(chip->regmap, AN30259A_REG_LEDCC(led->num), | ||
286 | &led->cdev.brightness); | ||
287 | break; | ||
288 | default: | ||
289 | led->cdev.brightness = LED_OFF; | ||
290 | } | ||
291 | |||
292 | an30259a_brightness_set(&led->cdev, led->cdev.brightness); | ||
293 | } | ||
294 | |||
295 | static int an30259a_probe(struct i2c_client *client) | ||
296 | { | ||
297 | struct an30259a *chip; | ||
298 | int i, err; | ||
299 | |||
300 | chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); | ||
301 | if (!chip) | ||
302 | return -ENOMEM; | ||
303 | |||
304 | err = an30259a_dt_init(client, chip); | ||
305 | if (err < 0) | ||
306 | return err; | ||
307 | |||
308 | mutex_init(&chip->mutex); | ||
309 | chip->client = client; | ||
310 | i2c_set_clientdata(client, chip); | ||
311 | |||
312 | chip->regmap = devm_regmap_init_i2c(client, &an30259a_regmap_config); | ||
313 | |||
314 | for (i = 0; i < chip->num_leds; i++) { | ||
315 | an30259a_init_default_state(&chip->leds[i]); | ||
316 | chip->leds[i].cdev.brightness_set_blocking = | ||
317 | an30259a_brightness_set; | ||
318 | chip->leds[i].cdev.blink_set = an30259a_blink_set; | ||
319 | |||
320 | err = devm_led_classdev_register(&client->dev, | ||
321 | &chip->leds[i].cdev); | ||
322 | if (err < 0) | ||
323 | goto exit; | ||
324 | } | ||
325 | return 0; | ||
326 | |||
327 | exit: | ||
328 | mutex_destroy(&chip->mutex); | ||
329 | return err; | ||
330 | } | ||
331 | |||
332 | static int an30259a_remove(struct i2c_client *client) | ||
333 | { | ||
334 | struct an30259a *chip = i2c_get_clientdata(client); | ||
335 | |||
336 | mutex_destroy(&chip->mutex); | ||
337 | |||
338 | return 0; | ||
339 | } | ||
340 | |||
341 | static const struct of_device_id an30259a_match_table[] = { | ||
342 | { .compatible = "panasonic,an30259a", }, | ||
343 | { /* sentinel */ }, | ||
344 | }; | ||
345 | |||
346 | MODULE_DEVICE_TABLE(of, an30259a_match_table); | ||
347 | |||
348 | static const struct i2c_device_id an30259a_id[] = { | ||
349 | { "an30259a", 0 }, | ||
350 | { /* sentinel */ }, | ||
351 | }; | ||
352 | MODULE_DEVICE_TABLE(i2c, an30259a_id); | ||
353 | |||
354 | static struct i2c_driver an30259a_driver = { | ||
355 | .driver = { | ||
356 | .name = "leds-an32059a", | ||
357 | .of_match_table = of_match_ptr(an30259a_match_table), | ||
358 | }, | ||
359 | .probe_new = an30259a_probe, | ||
360 | .remove = an30259a_remove, | ||
361 | .id_table = an30259a_id, | ||
362 | }; | ||
363 | |||
364 | module_i2c_driver(an30259a_driver); | ||
365 | |||
366 | MODULE_AUTHOR("Simon Shields <simon@lineageos.org>"); | ||
367 | MODULE_DESCRIPTION("AN32059A LED driver"); | ||
368 | MODULE_LICENSE("GPL v2"); | ||