aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Shields <simon@lineageos.org>2018-09-09 06:38:25 -0400
committerJacek Anaszewski <jacek.anaszewski@gmail.com>2018-09-10 15:31:11 -0400
commit2d00f35c55e74fcb5626c9aa336c1ed697cd2ae9 (patch)
tree5b4e770455b5b4bb668c8f599a6b12de8719469c
parent86bc7b2d753ad361bfd358c89a8888bae04c8e5a (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/Kconfig10
-rw-r--r--drivers/leds/Makefile1
-rw-r--r--drivers/leds/leds-an30259a.c368
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
61config 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
61config LEDS_APU 71config 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
11obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o 11obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o
12obj-$(CONFIG_LEDS_APU) += leds-apu.o 12obj-$(CONFIG_LEDS_APU) += leds-apu.o
13obj-$(CONFIG_LEDS_AS3645A) += leds-as3645a.o 13obj-$(CONFIG_LEDS_AS3645A) += leds-as3645a.o
14obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o
14obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o 15obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
15obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o 16obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o
16obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o 17obj-$(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
61struct an30259a;
62
63struct 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
72struct 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
80static 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
120error:
121 mutex_unlock(&led->chip->mutex);
122
123 return ret;
124}
125
126static 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;
195error:
196 mutex_unlock(&led->chip->mutex);
197
198 return ret;
199}
200
201static 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
261static const struct regmap_config an30259a_regmap_config = {
262 .reg_bits = 8,
263 .val_bits = 8,
264 .max_register = AN30259A_REG_MAX,
265};
266
267static 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
295static 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
327exit:
328 mutex_destroy(&chip->mutex);
329 return err;
330}
331
332static 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
341static const struct of_device_id an30259a_match_table[] = {
342 { .compatible = "panasonic,an30259a", },
343 { /* sentinel */ },
344};
345
346MODULE_DEVICE_TABLE(of, an30259a_match_table);
347
348static const struct i2c_device_id an30259a_id[] = {
349 { "an30259a", 0 },
350 { /* sentinel */ },
351};
352MODULE_DEVICE_TABLE(i2c, an30259a_id);
353
354static 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
364module_i2c_driver(an30259a_driver);
365
366MODULE_AUTHOR("Simon Shields <simon@lineageos.org>");
367MODULE_DESCRIPTION("AN32059A LED driver");
368MODULE_LICENSE("GPL v2");