Blame view

drivers/leds/leds-bcm6328.c 12.7 KB
2874c5fd2   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-or-later
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
2
3
4
5
6
  /*
   * Driver for BCM6328 memory-mapped LEDs, based on leds-syscon.c
   *
   * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>
   * Copyright 2015 Jonas Gorski <jogo@openwrt.org>
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
   */
  #include <linux/io.h>
  #include <linux/leds.h>
  #include <linux/module.h>
  #include <linux/of.h>
  #include <linux/platform_device.h>
  #include <linux/spinlock.h>
  
  #define BCM6328_REG_INIT		0x00
  #define BCM6328_REG_MODE_HI		0x04
  #define BCM6328_REG_MODE_LO		0x08
  #define BCM6328_REG_HWDIS		0x0c
  #define BCM6328_REG_STROBE		0x10
  #define BCM6328_REG_LNKACTSEL_HI	0x14
  #define BCM6328_REG_LNKACTSEL_LO	0x18
  #define BCM6328_REG_RBACK		0x1c
  #define BCM6328_REG_SERMUX		0x20
  
  #define BCM6328_LED_MAX_COUNT		24
  #define BCM6328_LED_DEF_DELAY		500
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
27

e190f57df   Álvaro Fernández Rojas   leds-bcm6328: sup...
28
29
30
31
32
33
34
35
36
37
  #define BCM6328_LED_BLINK_DELAYS	2
  #define BCM6328_LED_BLINK_MS		20
  
  #define BCM6328_LED_BLINK_MASK		0x3f
  #define BCM6328_LED_BLINK1_SHIFT	0
  #define BCM6328_LED_BLINK1_MASK		(BCM6328_LED_BLINK_MASK << \
  					 BCM6328_LED_BLINK1_SHIFT)
  #define BCM6328_LED_BLINK2_SHIFT	6
  #define BCM6328_LED_BLINK2_MASK		(BCM6328_LED_BLINK_MASK << \
  					 BCM6328_LED_BLINK2_SHIFT)
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
38
39
40
41
42
43
44
  #define BCM6328_SERIAL_LED_EN		BIT(12)
  #define BCM6328_SERIAL_LED_MUX		BIT(13)
  #define BCM6328_SERIAL_LED_CLK_NPOL	BIT(14)
  #define BCM6328_SERIAL_LED_DATA_PPOL	BIT(15)
  #define BCM6328_SERIAL_LED_SHIFT_DIR	BIT(16)
  #define BCM6328_LED_SHIFT_TEST		BIT(30)
  #define BCM6328_LED_TEST		BIT(31)
9f82c778c   Álvaro Fernández Rojas   leds-bcm6328: add...
45
  #define BCM6328_INIT_MASK		(BCM6328_SERIAL_LED_EN | \
41251e246   Álvaro Fernández Rojas   leds: bcm6328: co...
46
  					 BCM6328_SERIAL_LED_MUX | \
9f82c778c   Álvaro Fernández Rojas   leds-bcm6328: add...
47
48
49
  					 BCM6328_SERIAL_LED_CLK_NPOL | \
  					 BCM6328_SERIAL_LED_DATA_PPOL | \
  					 BCM6328_SERIAL_LED_SHIFT_DIR)
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
50
51
  
  #define BCM6328_LED_MODE_MASK		3
b964c5ba6   Simon Arlott   leds: bcm6328: Sw...
52
  #define BCM6328_LED_MODE_ON		0
e190f57df   Álvaro Fernández Rojas   leds-bcm6328: sup...
53
54
  #define BCM6328_LED_MODE_BLINK1		1
  #define BCM6328_LED_MODE_BLINK2		2
b964c5ba6   Simon Arlott   leds: bcm6328: Sw...
55
  #define BCM6328_LED_MODE_OFF		3
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
  #define BCM6328_LED_SHIFT(X)		((X) << 1)
  
  /**
   * struct bcm6328_led - state container for bcm6328 based LEDs
   * @cdev: LED class device for this LED
   * @mem: memory resource
   * @lock: memory lock
   * @pin: LED pin number
   * @blink_leds: blinking LEDs
   * @blink_delay: blinking delay
   * @active_low: LED is active low
   */
  struct bcm6328_led {
  	struct led_classdev cdev;
  	void __iomem *mem;
  	spinlock_t *lock;
  	unsigned long pin;
  	unsigned long *blink_leds;
  	unsigned long *blink_delay;
  	bool active_low;
  };
  
  static void bcm6328_led_write(void __iomem *reg, unsigned long data)
  {
a06cd4b76   Álvaro Fernández Rojas   leds: bcm6328: ad...
80
  #ifdef CONFIG_CPU_BIG_ENDIAN
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
81
  	iowrite32be(data, reg);
a06cd4b76   Álvaro Fernández Rojas   leds: bcm6328: ad...
82
83
84
  #else
  	writel(data, reg);
  #endif
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
85
86
87
88
  }
  
  static unsigned long bcm6328_led_read(void __iomem *reg)
  {
a06cd4b76   Álvaro Fernández Rojas   leds: bcm6328: ad...
89
  #ifdef CONFIG_CPU_BIG_ENDIAN
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
90
  	return ioread32be(reg);
a06cd4b76   Álvaro Fernández Rojas   leds: bcm6328: ad...
91
92
93
  #else
  	return readl(reg);
  #endif
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
  }
  
  /**
   * LEDMode 64 bits / 24 LEDs
   * bits [31:0] -> LEDs 8-23
   * bits [47:32] -> LEDs 0-7
   * bits [63:48] -> unused
   */
  static unsigned long bcm6328_pin2shift(unsigned long pin)
  {
  	if (pin < 8)
  		return pin + 16; /* LEDs 0-7 (bits 47:32) */
  	else
  		return pin - 8; /* LEDs 8-23 (bits 31:0) */
  }
  
  static void bcm6328_led_mode(struct bcm6328_led *led, unsigned long value)
  {
  	void __iomem *mode;
  	unsigned long val, shift;
  
  	shift = bcm6328_pin2shift(led->pin);
  	if (shift / 16)
  		mode = led->mem + BCM6328_REG_MODE_HI;
  	else
  		mode = led->mem + BCM6328_REG_MODE_LO;
  
  	val = bcm6328_led_read(mode);
  	val &= ~(BCM6328_LED_MODE_MASK << BCM6328_LED_SHIFT(shift % 16));
  	val |= (value << BCM6328_LED_SHIFT(shift % 16));
  	bcm6328_led_write(mode, val);
  }
  
  static void bcm6328_led_set(struct led_classdev *led_cdev,
  			    enum led_brightness value)
  {
  	struct bcm6328_led *led =
  		container_of(led_cdev, struct bcm6328_led, cdev);
  	unsigned long flags;
  
  	spin_lock_irqsave(led->lock, flags);
e190f57df   Álvaro Fernández Rojas   leds-bcm6328: sup...
135
136
137
138
139
140
  
  	/* Remove LED from cached HW blinking intervals */
  	led->blink_leds[0] &= ~BIT(led->pin);
  	led->blink_leds[1] &= ~BIT(led->pin);
  
  	/* Set LED on/off */
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
141
142
  	if ((led->active_low && value == LED_OFF) ||
  	    (!led->active_low && value != LED_OFF))
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
143
  		bcm6328_led_mode(led, BCM6328_LED_MODE_ON);
b964c5ba6   Simon Arlott   leds: bcm6328: Sw...
144
145
  	else
  		bcm6328_led_mode(led, BCM6328_LED_MODE_OFF);
e190f57df   Álvaro Fernández Rojas   leds-bcm6328: sup...
146

fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
147
148
  	spin_unlock_irqrestore(led->lock, flags);
  }
1b85a5a5e   Álvaro Fernández Rojas   leds: bcm6328: im...
149
150
151
  static unsigned long bcm6328_blink_delay(unsigned long delay)
  {
  	unsigned long bcm6328_delay;
e190f57df   Álvaro Fernández Rojas   leds-bcm6328: sup...
152
153
  	bcm6328_delay = delay + BCM6328_LED_BLINK_MS / 2;
  	bcm6328_delay = bcm6328_delay / BCM6328_LED_BLINK_MS;
1b85a5a5e   Álvaro Fernández Rojas   leds: bcm6328: im...
154
155
156
157
158
  	if (bcm6328_delay == 0)
  		bcm6328_delay = 1;
  
  	return bcm6328_delay;
  }
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
159
160
161
162
163
164
  static int bcm6328_blink_set(struct led_classdev *led_cdev,
  			     unsigned long *delay_on, unsigned long *delay_off)
  {
  	struct bcm6328_led *led =
  		container_of(led_cdev, struct bcm6328_led, cdev);
  	unsigned long delay, flags;
143b77ce0   Álvaro Fernández Rojas   leds: bcm6328: si...
165
  	int rc;
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
166
167
168
169
170
  
  	if (!*delay_on)
  		*delay_on = BCM6328_LED_DEF_DELAY;
  	if (!*delay_off)
  		*delay_off = BCM6328_LED_DEF_DELAY;
1b85a5a5e   Álvaro Fernández Rojas   leds: bcm6328: im...
171
172
  	delay = bcm6328_blink_delay(*delay_on);
  	if (delay != bcm6328_blink_delay(*delay_off)) {
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
173
174
175
176
177
  		dev_dbg(led_cdev->dev,
  			"fallback to soft blinking (delay_on != delay_off)
  ");
  		return -EINVAL;
  	}
e190f57df   Álvaro Fernández Rojas   leds-bcm6328: sup...
178
  	if (delay > BCM6328_LED_BLINK_MASK) {
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
179
180
181
  		dev_dbg(led_cdev->dev,
  			"fallback to soft blinking (delay > %ums)
  ",
e190f57df   Álvaro Fernández Rojas   leds-bcm6328: sup...
182
  			BCM6328_LED_BLINK_MASK * BCM6328_LED_BLINK_MS);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
183
184
185
186
  		return -EINVAL;
  	}
  
  	spin_lock_irqsave(led->lock, flags);
e190f57df   Álvaro Fernández Rojas   leds-bcm6328: sup...
187
188
189
190
191
192
193
194
195
196
  	/*
  	 * Check if any of the two configurable HW blinking intervals is
  	 * available:
  	 *   1. No LEDs assigned to the HW blinking interval.
  	 *   2. Only this LED is assigned to the HW blinking interval.
  	 *   3. LEDs with the same delay assigned.
  	 */
  	if (led->blink_leds[0] == 0 ||
  	    led->blink_leds[0] == BIT(led->pin) ||
  	    led->blink_delay[0] == delay) {
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
197
  		unsigned long val;
e190f57df   Álvaro Fernández Rojas   leds-bcm6328: sup...
198
199
200
201
202
  		/* Add LED to the first HW blinking interval cache */
  		led->blink_leds[0] |= BIT(led->pin);
  
  		/* Remove LED from the second HW blinking interval cache */
  		led->blink_leds[1] &= ~BIT(led->pin);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
203

e190f57df   Álvaro Fernández Rojas   leds-bcm6328: sup...
204
205
206
207
  		/* Cache first HW blinking interval delay */
  		led->blink_delay[0] = delay;
  
  		/* Update the delay for the first HW blinking interval */
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
208
  		val = bcm6328_led_read(led->mem + BCM6328_REG_INIT);
e190f57df   Álvaro Fernández Rojas   leds-bcm6328: sup...
209
210
  		val &= ~BCM6328_LED_BLINK1_MASK;
  		val |= (delay << BCM6328_LED_BLINK1_SHIFT);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
211
  		bcm6328_led_write(led->mem + BCM6328_REG_INIT, val);
e190f57df   Álvaro Fernández Rojas   leds-bcm6328: sup...
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
  		/* Set the LED to first HW blinking interval */
  		bcm6328_led_mode(led, BCM6328_LED_MODE_BLINK1);
  
  		rc = 0;
  	} else if (led->blink_leds[1] == 0 ||
  		   led->blink_leds[1] == BIT(led->pin) ||
  		   led->blink_delay[1] == delay) {
  		unsigned long val;
  
  		/* Remove LED from the first HW blinking interval */
  		led->blink_leds[0] &= ~BIT(led->pin);
  
  		/* Add LED to the second HW blinking interval */
  		led->blink_leds[1] |= BIT(led->pin);
  
  		/* Cache second HW blinking interval delay */
  		led->blink_delay[1] = delay;
  
  		/* Update the delay for the second HW blinking interval */
  		val = bcm6328_led_read(led->mem + BCM6328_REG_INIT);
  		val &= ~BCM6328_LED_BLINK2_MASK;
  		val |= (delay << BCM6328_LED_BLINK2_SHIFT);
  		bcm6328_led_write(led->mem + BCM6328_REG_INIT, val);
  
  		/* Set the LED to second HW blinking interval */
  		bcm6328_led_mode(led, BCM6328_LED_MODE_BLINK2);
143b77ce0   Álvaro Fernández Rojas   leds: bcm6328: si...
238
  		rc = 0;
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
239
  	} else {
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
240
241
242
  		dev_dbg(led_cdev->dev,
  			"fallback to soft blinking (delay already set)
  ");
143b77ce0   Álvaro Fernández Rojas   leds: bcm6328: si...
243
  		rc = -EINVAL;
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
244
  	}
143b77ce0   Álvaro Fernández Rojas   leds: bcm6328: si...
245
  	spin_unlock_irqrestore(led->lock, flags);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
246

143b77ce0   Álvaro Fernández Rojas   leds: bcm6328: si...
247
  	return rc;
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
  }
  
  static int bcm6328_hwled(struct device *dev, struct device_node *nc, u32 reg,
  			 void __iomem *mem, spinlock_t *lock)
  {
  	int i, cnt;
  	unsigned long flags, val;
  
  	spin_lock_irqsave(lock, flags);
  	val = bcm6328_led_read(mem + BCM6328_REG_HWDIS);
  	val &= ~BIT(reg);
  	bcm6328_led_write(mem + BCM6328_REG_HWDIS, val);
  	spin_unlock_irqrestore(lock, flags);
  
  	/* Only LEDs 0-7 can be activity/link controlled */
  	if (reg >= 8)
  		return 0;
  
  	cnt = of_property_count_elems_of_size(nc, "brcm,link-signal-sources",
  					      sizeof(u32));
  	for (i = 0; i < cnt; i++) {
  		u32 sel;
  		void __iomem *addr;
  
  		if (reg < 4)
  			addr = mem + BCM6328_REG_LNKACTSEL_LO;
  		else
  			addr = mem + BCM6328_REG_LNKACTSEL_HI;
  
  		of_property_read_u32_index(nc, "brcm,link-signal-sources", i,
  					   &sel);
  
  		if (reg / 4 != sel / 4) {
  			dev_warn(dev, "invalid link signal source
  ");
  			continue;
  		}
  
  		spin_lock_irqsave(lock, flags);
  		val = bcm6328_led_read(addr);
4f02b50ec   Jonas Gorski   leds: bcm6328: fi...
288
  		val |= (BIT(reg % 4) << (((sel % 4) * 4) + 16));
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
  		bcm6328_led_write(addr, val);
  		spin_unlock_irqrestore(lock, flags);
  	}
  
  	cnt = of_property_count_elems_of_size(nc,
  					      "brcm,activity-signal-sources",
  					      sizeof(u32));
  	for (i = 0; i < cnt; i++) {
  		u32 sel;
  		void __iomem *addr;
  
  		if (reg < 4)
  			addr = mem + BCM6328_REG_LNKACTSEL_LO;
  		else
  			addr = mem + BCM6328_REG_LNKACTSEL_HI;
  
  		of_property_read_u32_index(nc, "brcm,activity-signal-sources",
  					   i, &sel);
  
  		if (reg / 4 != sel / 4) {
  			dev_warn(dev, "invalid activity signal source
  ");
  			continue;
  		}
  
  		spin_lock_irqsave(lock, flags);
  		val = bcm6328_led_read(addr);
4f02b50ec   Jonas Gorski   leds: bcm6328: fi...
316
  		val |= (BIT(reg % 4) << ((sel % 4) * 4));
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
317
318
319
320
321
322
323
324
325
326
327
  		bcm6328_led_write(addr, val);
  		spin_unlock_irqrestore(lock, flags);
  	}
  
  	return 0;
  }
  
  static int bcm6328_led(struct device *dev, struct device_node *nc, u32 reg,
  		       void __iomem *mem, spinlock_t *lock,
  		       unsigned long *blink_leds, unsigned long *blink_delay)
  {
e4e912a34   Marek Behún   leds: bcm6328, bc...
328
  	struct led_init_data init_data = {};
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
329
  	struct bcm6328_led *led;
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
  	const char *state;
  	int rc;
  
  	led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
  	if (!led)
  		return -ENOMEM;
  
  	led->pin = reg;
  	led->mem = mem;
  	led->lock = lock;
  	led->blink_leds = blink_leds;
  	led->blink_delay = blink_delay;
  
  	if (of_property_read_bool(nc, "active-low"))
  		led->active_low = true;
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
345
  	if (!of_property_read_string(nc, "default-state", &state)) {
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
346
347
  		if (!strcmp(state, "on")) {
  			led->cdev.brightness = LED_FULL;
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
348
349
350
351
352
353
354
355
356
  		} else if (!strcmp(state, "keep")) {
  			void __iomem *mode;
  			unsigned long val, shift;
  
  			shift = bcm6328_pin2shift(led->pin);
  			if (shift / 16)
  				mode = mem + BCM6328_REG_MODE_HI;
  			else
  				mode = mem + BCM6328_REG_MODE_LO;
d8fe1606d   Álvaro Fernández Rojas   leds-bcm6328: sim...
357
358
  			val = bcm6328_led_read(mode) >>
  			      BCM6328_LED_SHIFT(shift % 16);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
359
  			val &= BCM6328_LED_MODE_MASK;
b964c5ba6   Simon Arlott   leds: bcm6328: Sw...
360
361
  			if ((led->active_low && val == BCM6328_LED_MODE_OFF) ||
  			    (!led->active_low && val == BCM6328_LED_MODE_ON))
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
362
  				led->cdev.brightness = LED_FULL;
d8fe1606d   Álvaro Fernández Rojas   leds-bcm6328: sim...
363
  			else
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
364
  				led->cdev.brightness = LED_OFF;
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
365
366
  		} else {
  			led->cdev.brightness = LED_OFF;
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
367
  		}
d8fe1606d   Álvaro Fernández Rojas   leds-bcm6328: sim...
368
369
  	} else {
  		led->cdev.brightness = LED_OFF;
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
370
  	}
d8fe1606d   Álvaro Fernández Rojas   leds-bcm6328: sim...
371

9d3c0663d   Simon Arlott   leds: bcm6328: Re...
372
  	bcm6328_led_set(&led->cdev, led->cdev.brightness);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
373
374
  	led->cdev.brightness_set = bcm6328_led_set;
  	led->cdev.blink_set = bcm6328_blink_set;
e4e912a34   Marek Behún   leds: bcm6328, bc...
375
  	init_data.fwnode = of_fwnode_handle(nc);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
376

e4e912a34   Marek Behún   leds: bcm6328, bc...
377
  	rc = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
378
379
380
381
382
383
384
385
386
387
388
389
  	if (rc < 0)
  		return rc;
  
  	dev_dbg(dev, "registered LED %s
  ", led->cdev.name);
  
  	return 0;
  }
  
  static int bcm6328_leds_probe(struct platform_device *pdev)
  {
  	struct device *dev = &pdev->dev;
8853c95e9   Marek Behún   leds: various: us...
390
  	struct device_node *np = dev_of_node(&pdev->dev);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
391
  	struct device_node *child;
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
392
  	void __iomem *mem;
41251e246   Álvaro Fernández Rojas   leds: bcm6328: co...
393
  	spinlock_t *lock; /* memory lock */
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
394
  	unsigned long val, *blink_leds, *blink_delay;
be9f18eef   Markus Elfring   leds: bcm6328: Us...
395
  	mem = devm_platform_ioremap_resource(pdev, 0);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
396
397
398
399
400
401
  	if (IS_ERR(mem))
  		return PTR_ERR(mem);
  
  	lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
  	if (!lock)
  		return -ENOMEM;
e190f57df   Álvaro Fernández Rojas   leds-bcm6328: sup...
402
403
  	blink_leds = devm_kcalloc(dev, BCM6328_LED_BLINK_DELAYS,
  				  sizeof(*blink_leds), GFP_KERNEL);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
404
405
  	if (!blink_leds)
  		return -ENOMEM;
e190f57df   Álvaro Fernández Rojas   leds-bcm6328: sup...
406
407
  	blink_delay = devm_kcalloc(dev, BCM6328_LED_BLINK_DELAYS,
  				   sizeof(*blink_delay), GFP_KERNEL);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
408
409
410
411
412
413
414
415
416
417
  	if (!blink_delay)
  		return -ENOMEM;
  
  	spin_lock_init(lock);
  
  	bcm6328_led_write(mem + BCM6328_REG_HWDIS, ~0);
  	bcm6328_led_write(mem + BCM6328_REG_LNKACTSEL_HI, 0);
  	bcm6328_led_write(mem + BCM6328_REG_LNKACTSEL_LO, 0);
  
  	val = bcm6328_led_read(mem + BCM6328_REG_INIT);
9f82c778c   Álvaro Fernández Rojas   leds-bcm6328: add...
418
  	val &= ~(BCM6328_INIT_MASK);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
419
420
  	if (of_property_read_bool(np, "brcm,serial-leds"))
  		val |= BCM6328_SERIAL_LED_EN;
9f82c778c   Álvaro Fernández Rojas   leds-bcm6328: add...
421
422
423
424
425
426
427
428
  	if (of_property_read_bool(np, "brcm,serial-mux"))
  		val |= BCM6328_SERIAL_LED_MUX;
  	if (of_property_read_bool(np, "brcm,serial-clk-low"))
  		val |= BCM6328_SERIAL_LED_CLK_NPOL;
  	if (!of_property_read_bool(np, "brcm,serial-dat-low"))
  		val |= BCM6328_SERIAL_LED_DATA_PPOL;
  	if (!of_property_read_bool(np, "brcm,serial-shift-inv"))
  		val |= BCM6328_SERIAL_LED_SHIFT_DIR;
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
429
430
431
432
433
434
435
436
437
438
  	bcm6328_led_write(mem + BCM6328_REG_INIT, val);
  
  	for_each_available_child_of_node(np, child) {
  		int rc;
  		u32 reg;
  
  		if (of_property_read_u32(child, "reg", &reg))
  			continue;
  
  		if (reg >= BCM6328_LED_MAX_COUNT) {
79653fbb3   Álvaro Fernández Rojas   leds-bcm6328: pri...
439
440
  			dev_err(dev, "invalid LED (%u >= %d)
  ", reg,
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
441
442
443
444
445
446
447
448
449
  				BCM6328_LED_MAX_COUNT);
  			continue;
  		}
  
  		if (of_property_read_bool(child, "brcm,hardware-controlled"))
  			rc = bcm6328_hwled(dev, child, reg, mem, lock);
  		else
  			rc = bcm6328_led(dev, child, reg, mem, lock,
  					 blink_leds, blink_delay);
6cc762441   Julia Lawall   leds: bcm6328: ad...
450
451
  		if (rc < 0) {
  			of_node_put(child);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
452
  			return rc;
6cc762441   Julia Lawall   leds: bcm6328: ad...
453
  		}
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
454
455
456
457
458
459
460
461
462
  	}
  
  	return 0;
  }
  
  static const struct of_device_id bcm6328_leds_of_match[] = {
  	{ .compatible = "brcm,bcm6328-leds", },
  	{ },
  };
6528ca19c   Luis de Bethencourt   leds: bcm6328: Fi...
463
  MODULE_DEVICE_TABLE(of, bcm6328_leds_of_match);
fd7b025a2   Álvaro Fernández Rojas   leds: add BCM6328...
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
  
  static struct platform_driver bcm6328_leds_driver = {
  	.probe = bcm6328_leds_probe,
  	.driver = {
  		.name = "leds-bcm6328",
  		.of_match_table = bcm6328_leds_of_match,
  	},
  };
  
  module_platform_driver(bcm6328_leds_driver);
  
  MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
  MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
  MODULE_DESCRIPTION("LED driver for BCM6328 controllers");
  MODULE_LICENSE("GPL v2");
  MODULE_ALIAS("platform:leds-bcm6328");