Blame view
drivers/leds/leds-bcm6328.c
12.7 KB
2874c5fd2 treewide: Replace... |
1 |
// SPDX-License-Identifier: GPL-2.0-or-later |
fd7b025a2 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 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 leds: add BCM6328... |
27 |
|
e190f57df 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 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 leds-bcm6328: add... |
45 |
#define BCM6328_INIT_MASK (BCM6328_SERIAL_LED_EN | \ |
41251e246 leds: bcm6328: co... |
46 |
BCM6328_SERIAL_LED_MUX | \ |
9f82c778c leds-bcm6328: add... |
47 48 49 |
BCM6328_SERIAL_LED_CLK_NPOL | \ BCM6328_SERIAL_LED_DATA_PPOL | \ BCM6328_SERIAL_LED_SHIFT_DIR) |
fd7b025a2 leds: add BCM6328... |
50 51 |
#define BCM6328_LED_MODE_MASK 3 |
b964c5ba6 leds: bcm6328: Sw... |
52 |
#define BCM6328_LED_MODE_ON 0 |
e190f57df leds-bcm6328: sup... |
53 54 |
#define BCM6328_LED_MODE_BLINK1 1 #define BCM6328_LED_MODE_BLINK2 2 |
b964c5ba6 leds: bcm6328: Sw... |
55 |
#define BCM6328_LED_MODE_OFF 3 |
fd7b025a2 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 leds: bcm6328: ad... |
80 |
#ifdef CONFIG_CPU_BIG_ENDIAN |
fd7b025a2 leds: add BCM6328... |
81 |
iowrite32be(data, reg); |
a06cd4b76 leds: bcm6328: ad... |
82 83 84 |
#else writel(data, reg); #endif |
fd7b025a2 leds: add BCM6328... |
85 86 87 88 |
} static unsigned long bcm6328_led_read(void __iomem *reg) { |
a06cd4b76 leds: bcm6328: ad... |
89 |
#ifdef CONFIG_CPU_BIG_ENDIAN |
fd7b025a2 leds: add BCM6328... |
90 |
return ioread32be(reg); |
a06cd4b76 leds: bcm6328: ad... |
91 92 93 |
#else return readl(reg); #endif |
fd7b025a2 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 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 leds: add BCM6328... |
141 142 |
if ((led->active_low && value == LED_OFF) || (!led->active_low && value != LED_OFF)) |
fd7b025a2 leds: add BCM6328... |
143 |
bcm6328_led_mode(led, BCM6328_LED_MODE_ON); |
b964c5ba6 leds: bcm6328: Sw... |
144 145 |
else bcm6328_led_mode(led, BCM6328_LED_MODE_OFF); |
e190f57df leds-bcm6328: sup... |
146 |
|
fd7b025a2 leds: add BCM6328... |
147 148 |
spin_unlock_irqrestore(led->lock, flags); } |
1b85a5a5e leds: bcm6328: im... |
149 150 151 |
static unsigned long bcm6328_blink_delay(unsigned long delay) { unsigned long bcm6328_delay; |
e190f57df leds-bcm6328: sup... |
152 153 |
bcm6328_delay = delay + BCM6328_LED_BLINK_MS / 2; bcm6328_delay = bcm6328_delay / BCM6328_LED_BLINK_MS; |
1b85a5a5e leds: bcm6328: im... |
154 155 156 157 158 |
if (bcm6328_delay == 0) bcm6328_delay = 1; return bcm6328_delay; } |
fd7b025a2 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 leds: bcm6328: si... |
165 |
int rc; |
fd7b025a2 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 leds: bcm6328: im... |
171 172 |
delay = bcm6328_blink_delay(*delay_on); if (delay != bcm6328_blink_delay(*delay_off)) { |
fd7b025a2 leds: add BCM6328... |
173 174 175 176 177 |
dev_dbg(led_cdev->dev, "fallback to soft blinking (delay_on != delay_off) "); return -EINVAL; } |
e190f57df leds-bcm6328: sup... |
178 |
if (delay > BCM6328_LED_BLINK_MASK) { |
fd7b025a2 leds: add BCM6328... |
179 180 181 |
dev_dbg(led_cdev->dev, "fallback to soft blinking (delay > %ums) ", |
e190f57df leds-bcm6328: sup... |
182 |
BCM6328_LED_BLINK_MASK * BCM6328_LED_BLINK_MS); |
fd7b025a2 leds: add BCM6328... |
183 184 185 186 |
return -EINVAL; } spin_lock_irqsave(led->lock, flags); |
e190f57df 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 leds: add BCM6328... |
197 |
unsigned long val; |
e190f57df 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 leds: add BCM6328... |
203 |
|
e190f57df 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 leds: add BCM6328... |
208 |
val = bcm6328_led_read(led->mem + BCM6328_REG_INIT); |
e190f57df leds-bcm6328: sup... |
209 210 |
val &= ~BCM6328_LED_BLINK1_MASK; val |= (delay << BCM6328_LED_BLINK1_SHIFT); |
fd7b025a2 leds: add BCM6328... |
211 |
bcm6328_led_write(led->mem + BCM6328_REG_INIT, val); |
e190f57df 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 leds: bcm6328: si... |
238 |
rc = 0; |
fd7b025a2 leds: add BCM6328... |
239 |
} else { |
fd7b025a2 leds: add BCM6328... |
240 241 242 |
dev_dbg(led_cdev->dev, "fallback to soft blinking (delay already set) "); |
143b77ce0 leds: bcm6328: si... |
243 |
rc = -EINVAL; |
fd7b025a2 leds: add BCM6328... |
244 |
} |
143b77ce0 leds: bcm6328: si... |
245 |
spin_unlock_irqrestore(led->lock, flags); |
fd7b025a2 leds: add BCM6328... |
246 |
|
143b77ce0 leds: bcm6328: si... |
247 |
return rc; |
fd7b025a2 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 leds: bcm6328: fi... |
288 |
val |= (BIT(reg % 4) << (((sel % 4) * 4) + 16)); |
fd7b025a2 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 leds: bcm6328: fi... |
316 |
val |= (BIT(reg % 4) << ((sel % 4) * 4)); |
fd7b025a2 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 leds: bcm6328, bc... |
328 |
struct led_init_data init_data = {}; |
fd7b025a2 leds: add BCM6328... |
329 |
struct bcm6328_led *led; |
fd7b025a2 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 leds: add BCM6328... |
345 |
if (!of_property_read_string(nc, "default-state", &state)) { |
fd7b025a2 leds: add BCM6328... |
346 347 |
if (!strcmp(state, "on")) { led->cdev.brightness = LED_FULL; |
fd7b025a2 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 leds-bcm6328: sim... |
357 358 |
val = bcm6328_led_read(mode) >> BCM6328_LED_SHIFT(shift % 16); |
fd7b025a2 leds: add BCM6328... |
359 |
val &= BCM6328_LED_MODE_MASK; |
b964c5ba6 leds: bcm6328: Sw... |
360 361 |
if ((led->active_low && val == BCM6328_LED_MODE_OFF) || (!led->active_low && val == BCM6328_LED_MODE_ON)) |
fd7b025a2 leds: add BCM6328... |
362 |
led->cdev.brightness = LED_FULL; |
d8fe1606d leds-bcm6328: sim... |
363 |
else |
fd7b025a2 leds: add BCM6328... |
364 |
led->cdev.brightness = LED_OFF; |
fd7b025a2 leds: add BCM6328... |
365 366 |
} else { led->cdev.brightness = LED_OFF; |
fd7b025a2 leds: add BCM6328... |
367 |
} |
d8fe1606d leds-bcm6328: sim... |
368 369 |
} else { led->cdev.brightness = LED_OFF; |
fd7b025a2 leds: add BCM6328... |
370 |
} |
d8fe1606d leds-bcm6328: sim... |
371 |
|
9d3c0663d leds: bcm6328: Re... |
372 |
bcm6328_led_set(&led->cdev, led->cdev.brightness); |
fd7b025a2 leds: add BCM6328... |
373 374 |
led->cdev.brightness_set = bcm6328_led_set; led->cdev.blink_set = bcm6328_blink_set; |
e4e912a34 leds: bcm6328, bc... |
375 |
init_data.fwnode = of_fwnode_handle(nc); |
fd7b025a2 leds: add BCM6328... |
376 |
|
e4e912a34 leds: bcm6328, bc... |
377 |
rc = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); |
fd7b025a2 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 leds: various: us... |
390 |
struct device_node *np = dev_of_node(&pdev->dev); |
fd7b025a2 leds: add BCM6328... |
391 |
struct device_node *child; |
fd7b025a2 leds: add BCM6328... |
392 |
void __iomem *mem; |
41251e246 leds: bcm6328: co... |
393 |
spinlock_t *lock; /* memory lock */ |
fd7b025a2 leds: add BCM6328... |
394 |
unsigned long val, *blink_leds, *blink_delay; |
be9f18eef leds: bcm6328: Us... |
395 |
mem = devm_platform_ioremap_resource(pdev, 0); |
fd7b025a2 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 leds-bcm6328: sup... |
402 403 |
blink_leds = devm_kcalloc(dev, BCM6328_LED_BLINK_DELAYS, sizeof(*blink_leds), GFP_KERNEL); |
fd7b025a2 leds: add BCM6328... |
404 405 |
if (!blink_leds) return -ENOMEM; |
e190f57df leds-bcm6328: sup... |
406 407 |
blink_delay = devm_kcalloc(dev, BCM6328_LED_BLINK_DELAYS, sizeof(*blink_delay), GFP_KERNEL); |
fd7b025a2 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 leds-bcm6328: add... |
418 |
val &= ~(BCM6328_INIT_MASK); |
fd7b025a2 leds: add BCM6328... |
419 420 |
if (of_property_read_bool(np, "brcm,serial-leds")) val |= BCM6328_SERIAL_LED_EN; |
9f82c778c 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 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", ®)) continue; if (reg >= BCM6328_LED_MAX_COUNT) { |
79653fbb3 leds-bcm6328: pri... |
439 440 |
dev_err(dev, "invalid LED (%u >= %d) ", reg, |
fd7b025a2 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 leds: bcm6328: ad... |
450 451 |
if (rc < 0) { of_node_put(child); |
fd7b025a2 leds: add BCM6328... |
452 |
return rc; |
6cc762441 leds: bcm6328: ad... |
453 |
} |
fd7b025a2 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 leds: bcm6328: Fi... |
463 |
MODULE_DEVICE_TABLE(of, bcm6328_leds_of_match); |
fd7b025a2 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"); |