Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

leds: add BCM6328 LED driver

This adds support for the LED controller on Broadcom's BCM6328.

Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
Signed-off-by: Jonas Gorski <jogo@openwrt.org>
Acked-by: Jacek Anaszewski <j.anaszewski@samsung.com>
Signed-off-by: Bryan Wu <cooloney@gmail.com>

authored by

Álvaro Fernández Rojas and committed by
Bryan Wu
fd7b025a 6e4b55e8

+422
+8
drivers/leds/Kconfig
··· 50 50 help 51 51 This option enables support for the LEDs on the AAT1290. 52 52 53 + config LEDS_BCM6328 54 + tristate "LED Support for Broadcom BCM6328" 55 + depends on LEDS_CLASS 56 + depends on OF 57 + help 58 + This option enables support for LEDs connected to the BCM6328 59 + LED HW controller accessed via MMIO registers. 60 + 53 61 config LEDS_LM3530 54 62 tristate "LCD Backlight driver for LM3530" 55 63 depends on LEDS_CLASS
+1
drivers/leds/Makefile
··· 8 8 # LED Platform Drivers 9 9 obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o 10 10 obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o 11 + obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o 11 12 obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o 12 13 obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o 13 14 obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
+413
drivers/leds/leds-bcm6328.c
··· 1 + /* 2 + * Driver for BCM6328 memory-mapped LEDs, based on leds-syscon.c 3 + * 4 + * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com> 5 + * Copyright 2015 Jonas Gorski <jogo@openwrt.org> 6 + * 7 + * This program is free software; you can redistribute it and/or modify it 8 + * under the terms of the GNU General Public License as published by the 9 + * Free Software Foundation; either version 2 of the License, or (at your 10 + * option) any later version. 11 + */ 12 + #include <linux/io.h> 13 + #include <linux/leds.h> 14 + #include <linux/module.h> 15 + #include <linux/of.h> 16 + #include <linux/platform_device.h> 17 + #include <linux/spinlock.h> 18 + 19 + #define BCM6328_REG_INIT 0x00 20 + #define BCM6328_REG_MODE_HI 0x04 21 + #define BCM6328_REG_MODE_LO 0x08 22 + #define BCM6328_REG_HWDIS 0x0c 23 + #define BCM6328_REG_STROBE 0x10 24 + #define BCM6328_REG_LNKACTSEL_HI 0x14 25 + #define BCM6328_REG_LNKACTSEL_LO 0x18 26 + #define BCM6328_REG_RBACK 0x1c 27 + #define BCM6328_REG_SERMUX 0x20 28 + 29 + #define BCM6328_LED_MAX_COUNT 24 30 + #define BCM6328_LED_DEF_DELAY 500 31 + #define BCM6328_LED_INTERVAL_MS 20 32 + 33 + #define BCM6328_LED_INTV_MASK 0x3f 34 + #define BCM6328_LED_FAST_INTV_SHIFT 6 35 + #define BCM6328_LED_FAST_INTV_MASK (BCM6328_LED_INTV_MASK << \ 36 + BCM6328_LED_FAST_INTV_SHIFT) 37 + #define BCM6328_SERIAL_LED_EN BIT(12) 38 + #define BCM6328_SERIAL_LED_MUX BIT(13) 39 + #define BCM6328_SERIAL_LED_CLK_NPOL BIT(14) 40 + #define BCM6328_SERIAL_LED_DATA_PPOL BIT(15) 41 + #define BCM6328_SERIAL_LED_SHIFT_DIR BIT(16) 42 + #define BCM6328_LED_SHIFT_TEST BIT(30) 43 + #define BCM6328_LED_TEST BIT(31) 44 + 45 + #define BCM6328_LED_MODE_MASK 3 46 + #define BCM6328_LED_MODE_OFF 0 47 + #define BCM6328_LED_MODE_FAST 1 48 + #define BCM6328_LED_MODE_BLINK 2 49 + #define BCM6328_LED_MODE_ON 3 50 + #define BCM6328_LED_SHIFT(X) ((X) << 1) 51 + 52 + /** 53 + * struct bcm6328_led - state container for bcm6328 based LEDs 54 + * @cdev: LED class device for this LED 55 + * @mem: memory resource 56 + * @lock: memory lock 57 + * @pin: LED pin number 58 + * @blink_leds: blinking LEDs 59 + * @blink_delay: blinking delay 60 + * @active_low: LED is active low 61 + */ 62 + struct bcm6328_led { 63 + struct led_classdev cdev; 64 + void __iomem *mem; 65 + spinlock_t *lock; 66 + unsigned long pin; 67 + unsigned long *blink_leds; 68 + unsigned long *blink_delay; 69 + bool active_low; 70 + }; 71 + 72 + static void bcm6328_led_write(void __iomem *reg, unsigned long data) 73 + { 74 + iowrite32be(data, reg); 75 + } 76 + 77 + static unsigned long bcm6328_led_read(void __iomem *reg) 78 + { 79 + return ioread32be(reg); 80 + } 81 + 82 + /** 83 + * LEDMode 64 bits / 24 LEDs 84 + * bits [31:0] -> LEDs 8-23 85 + * bits [47:32] -> LEDs 0-7 86 + * bits [63:48] -> unused 87 + */ 88 + static unsigned long bcm6328_pin2shift(unsigned long pin) 89 + { 90 + if (pin < 8) 91 + return pin + 16; /* LEDs 0-7 (bits 47:32) */ 92 + else 93 + return pin - 8; /* LEDs 8-23 (bits 31:0) */ 94 + } 95 + 96 + static void bcm6328_led_mode(struct bcm6328_led *led, unsigned long value) 97 + { 98 + void __iomem *mode; 99 + unsigned long val, shift; 100 + 101 + shift = bcm6328_pin2shift(led->pin); 102 + if (shift / 16) 103 + mode = led->mem + BCM6328_REG_MODE_HI; 104 + else 105 + mode = led->mem + BCM6328_REG_MODE_LO; 106 + 107 + val = bcm6328_led_read(mode); 108 + val &= ~(BCM6328_LED_MODE_MASK << BCM6328_LED_SHIFT(shift % 16)); 109 + val |= (value << BCM6328_LED_SHIFT(shift % 16)); 110 + bcm6328_led_write(mode, val); 111 + } 112 + 113 + static void bcm6328_led_set(struct led_classdev *led_cdev, 114 + enum led_brightness value) 115 + { 116 + struct bcm6328_led *led = 117 + container_of(led_cdev, struct bcm6328_led, cdev); 118 + unsigned long flags; 119 + 120 + spin_lock_irqsave(led->lock, flags); 121 + *(led->blink_leds) &= ~BIT(led->pin); 122 + if ((led->active_low && value == LED_OFF) || 123 + (!led->active_low && value != LED_OFF)) 124 + bcm6328_led_mode(led, BCM6328_LED_MODE_OFF); 125 + else 126 + bcm6328_led_mode(led, BCM6328_LED_MODE_ON); 127 + spin_unlock_irqrestore(led->lock, flags); 128 + } 129 + 130 + static int bcm6328_blink_set(struct led_classdev *led_cdev, 131 + unsigned long *delay_on, unsigned long *delay_off) 132 + { 133 + struct bcm6328_led *led = 134 + container_of(led_cdev, struct bcm6328_led, cdev); 135 + unsigned long delay, flags; 136 + 137 + if (!*delay_on) 138 + *delay_on = BCM6328_LED_DEF_DELAY; 139 + if (!*delay_off) 140 + *delay_off = BCM6328_LED_DEF_DELAY; 141 + 142 + if (*delay_on != *delay_off) { 143 + dev_dbg(led_cdev->dev, 144 + "fallback to soft blinking (delay_on != delay_off)\n"); 145 + return -EINVAL; 146 + } 147 + 148 + delay = *delay_on / BCM6328_LED_INTERVAL_MS; 149 + if (delay == 0) 150 + delay = 1; 151 + else if (delay > BCM6328_LED_INTV_MASK) { 152 + dev_dbg(led_cdev->dev, 153 + "fallback to soft blinking (delay > %ums)\n", 154 + BCM6328_LED_INTV_MASK * BCM6328_LED_INTERVAL_MS); 155 + return -EINVAL; 156 + } 157 + 158 + spin_lock_irqsave(led->lock, flags); 159 + if (*(led->blink_leds) == 0 || 160 + *(led->blink_leds) == BIT(led->pin) || 161 + *(led->blink_delay) == delay) { 162 + unsigned long val; 163 + 164 + *(led->blink_leds) |= BIT(led->pin); 165 + *(led->blink_delay) = delay; 166 + 167 + val = bcm6328_led_read(led->mem + BCM6328_REG_INIT); 168 + val &= ~BCM6328_LED_FAST_INTV_MASK; 169 + val |= (delay << BCM6328_LED_FAST_INTV_SHIFT); 170 + bcm6328_led_write(led->mem + BCM6328_REG_INIT, val); 171 + 172 + bcm6328_led_mode(led, BCM6328_LED_MODE_BLINK); 173 + 174 + spin_unlock_irqrestore(led->lock, flags); 175 + } else { 176 + spin_unlock_irqrestore(led->lock, flags); 177 + dev_dbg(led_cdev->dev, 178 + "fallback to soft blinking (delay already set)\n"); 179 + return -EINVAL; 180 + } 181 + 182 + return 0; 183 + } 184 + 185 + static int bcm6328_hwled(struct device *dev, struct device_node *nc, u32 reg, 186 + void __iomem *mem, spinlock_t *lock) 187 + { 188 + int i, cnt; 189 + unsigned long flags, val; 190 + 191 + spin_lock_irqsave(lock, flags); 192 + val = bcm6328_led_read(mem + BCM6328_REG_HWDIS); 193 + val &= ~BIT(reg); 194 + bcm6328_led_write(mem + BCM6328_REG_HWDIS, val); 195 + spin_unlock_irqrestore(lock, flags); 196 + 197 + /* Only LEDs 0-7 can be activity/link controlled */ 198 + if (reg >= 8) 199 + return 0; 200 + 201 + cnt = of_property_count_elems_of_size(nc, "brcm,link-signal-sources", 202 + sizeof(u32)); 203 + for (i = 0; i < cnt; i++) { 204 + u32 sel; 205 + void __iomem *addr; 206 + 207 + if (reg < 4) 208 + addr = mem + BCM6328_REG_LNKACTSEL_LO; 209 + else 210 + addr = mem + BCM6328_REG_LNKACTSEL_HI; 211 + 212 + of_property_read_u32_index(nc, "brcm,link-signal-sources", i, 213 + &sel); 214 + 215 + if (reg / 4 != sel / 4) { 216 + dev_warn(dev, "invalid link signal source\n"); 217 + continue; 218 + } 219 + 220 + spin_lock_irqsave(lock, flags); 221 + val = bcm6328_led_read(addr); 222 + val |= (BIT(reg) << (((sel % 4) * 4) + 16)); 223 + bcm6328_led_write(addr, val); 224 + spin_unlock_irqrestore(lock, flags); 225 + } 226 + 227 + cnt = of_property_count_elems_of_size(nc, 228 + "brcm,activity-signal-sources", 229 + sizeof(u32)); 230 + for (i = 0; i < cnt; i++) { 231 + u32 sel; 232 + void __iomem *addr; 233 + 234 + if (reg < 4) 235 + addr = mem + BCM6328_REG_LNKACTSEL_LO; 236 + else 237 + addr = mem + BCM6328_REG_LNKACTSEL_HI; 238 + 239 + of_property_read_u32_index(nc, "brcm,activity-signal-sources", 240 + i, &sel); 241 + 242 + if (reg / 4 != sel / 4) { 243 + dev_warn(dev, "invalid activity signal source\n"); 244 + continue; 245 + } 246 + 247 + spin_lock_irqsave(lock, flags); 248 + val = bcm6328_led_read(addr); 249 + val |= (BIT(reg) << ((sel % 4) * 4)); 250 + bcm6328_led_write(addr, val); 251 + spin_unlock_irqrestore(lock, flags); 252 + } 253 + 254 + return 0; 255 + } 256 + 257 + static int bcm6328_led(struct device *dev, struct device_node *nc, u32 reg, 258 + void __iomem *mem, spinlock_t *lock, 259 + unsigned long *blink_leds, unsigned long *blink_delay) 260 + { 261 + struct bcm6328_led *led; 262 + unsigned long flags; 263 + const char *state; 264 + int rc; 265 + 266 + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); 267 + if (!led) 268 + return -ENOMEM; 269 + 270 + led->pin = reg; 271 + led->mem = mem; 272 + led->lock = lock; 273 + led->blink_leds = blink_leds; 274 + led->blink_delay = blink_delay; 275 + 276 + if (of_property_read_bool(nc, "active-low")) 277 + led->active_low = true; 278 + 279 + led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name; 280 + led->cdev.default_trigger = of_get_property(nc, 281 + "linux,default-trigger", 282 + NULL); 283 + 284 + if (!of_property_read_string(nc, "default-state", &state)) { 285 + spin_lock_irqsave(lock, flags); 286 + if (!strcmp(state, "on")) { 287 + led->cdev.brightness = LED_FULL; 288 + bcm6328_led_mode(led, BCM6328_LED_MODE_ON); 289 + } else if (!strcmp(state, "keep")) { 290 + void __iomem *mode; 291 + unsigned long val, shift; 292 + 293 + shift = bcm6328_pin2shift(led->pin); 294 + if (shift / 16) 295 + mode = mem + BCM6328_REG_MODE_HI; 296 + else 297 + mode = mem + BCM6328_REG_MODE_LO; 298 + 299 + val = bcm6328_led_read(mode) >> (shift % 16); 300 + val &= BCM6328_LED_MODE_MASK; 301 + if (val == BCM6328_LED_MODE_ON) 302 + led->cdev.brightness = LED_FULL; 303 + else { 304 + led->cdev.brightness = LED_OFF; 305 + bcm6328_led_mode(led, BCM6328_LED_MODE_OFF); 306 + } 307 + } else { 308 + led->cdev.brightness = LED_OFF; 309 + bcm6328_led_mode(led, BCM6328_LED_MODE_OFF); 310 + } 311 + spin_unlock_irqrestore(lock, flags); 312 + } 313 + 314 + led->cdev.brightness_set = bcm6328_led_set; 315 + led->cdev.blink_set = bcm6328_blink_set; 316 + 317 + rc = led_classdev_register(dev, &led->cdev); 318 + if (rc < 0) 319 + return rc; 320 + 321 + dev_dbg(dev, "registered LED %s\n", led->cdev.name); 322 + 323 + return 0; 324 + } 325 + 326 + static int bcm6328_leds_probe(struct platform_device *pdev) 327 + { 328 + struct device *dev = &pdev->dev; 329 + struct device_node *np = pdev->dev.of_node; 330 + struct device_node *child; 331 + struct resource *mem_r; 332 + void __iomem *mem; 333 + spinlock_t *lock; 334 + unsigned long val, *blink_leds, *blink_delay; 335 + 336 + mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 337 + if (!mem_r) 338 + return -EINVAL; 339 + 340 + mem = devm_ioremap_resource(dev, mem_r); 341 + if (IS_ERR(mem)) 342 + return PTR_ERR(mem); 343 + 344 + lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL); 345 + if (!lock) 346 + return -ENOMEM; 347 + 348 + blink_leds = devm_kzalloc(dev, sizeof(*blink_leds), GFP_KERNEL); 349 + if (!blink_leds) 350 + return -ENOMEM; 351 + 352 + blink_delay = devm_kzalloc(dev, sizeof(*blink_delay), GFP_KERNEL); 353 + if (!blink_delay) 354 + return -ENOMEM; 355 + 356 + spin_lock_init(lock); 357 + 358 + bcm6328_led_write(mem + BCM6328_REG_HWDIS, ~0); 359 + bcm6328_led_write(mem + BCM6328_REG_LNKACTSEL_HI, 0); 360 + bcm6328_led_write(mem + BCM6328_REG_LNKACTSEL_LO, 0); 361 + 362 + val = bcm6328_led_read(mem + BCM6328_REG_INIT); 363 + val &= ~BCM6328_SERIAL_LED_EN; 364 + if (of_property_read_bool(np, "brcm,serial-leds")) 365 + val |= BCM6328_SERIAL_LED_EN; 366 + bcm6328_led_write(mem + BCM6328_REG_INIT, val); 367 + 368 + for_each_available_child_of_node(np, child) { 369 + int rc; 370 + u32 reg; 371 + 372 + if (of_property_read_u32(child, "reg", &reg)) 373 + continue; 374 + 375 + if (reg >= BCM6328_LED_MAX_COUNT) { 376 + dev_err(dev, "invalid LED (>= %d)\n", 377 + BCM6328_LED_MAX_COUNT); 378 + continue; 379 + } 380 + 381 + if (of_property_read_bool(child, "brcm,hardware-controlled")) 382 + rc = bcm6328_hwled(dev, child, reg, mem, lock); 383 + else 384 + rc = bcm6328_led(dev, child, reg, mem, lock, 385 + blink_leds, blink_delay); 386 + 387 + if (rc < 0) 388 + return rc; 389 + } 390 + 391 + return 0; 392 + } 393 + 394 + static const struct of_device_id bcm6328_leds_of_match[] = { 395 + { .compatible = "brcm,bcm6328-leds", }, 396 + { }, 397 + }; 398 + 399 + static struct platform_driver bcm6328_leds_driver = { 400 + .probe = bcm6328_leds_probe, 401 + .driver = { 402 + .name = "leds-bcm6328", 403 + .of_match_table = bcm6328_leds_of_match, 404 + }, 405 + }; 406 + 407 + module_platform_driver(bcm6328_leds_driver); 408 + 409 + MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>"); 410 + MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>"); 411 + MODULE_DESCRIPTION("LED driver for BCM6328 controllers"); 412 + MODULE_LICENSE("GPL v2"); 413 + MODULE_ALIAS("platform:leds-bcm6328");