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 #define BCM6328_INIT_MASK (BCM6328_SERIAL_LED_EN | \ 45 BCM6328_SERIAL_LED_MUX | \ 46 BCM6328_SERIAL_LED_CLK_NPOL | \ 47 BCM6328_SERIAL_LED_DATA_PPOL | \ 48 BCM6328_SERIAL_LED_SHIFT_DIR) 49 50 #define BCM6328_LED_MODE_MASK 3 51 #define BCM6328_LED_MODE_ON 0 52 #define BCM6328_LED_MODE_FAST 1 53 #define BCM6328_LED_MODE_BLINK 2 54 #define BCM6328_LED_MODE_OFF 3 55 #define BCM6328_LED_SHIFT(X) ((X) << 1) 56 57 /** 58 * struct bcm6328_led - state container for bcm6328 based LEDs 59 * @cdev: LED class device for this LED 60 * @mem: memory resource 61 * @lock: memory lock 62 * @pin: LED pin number 63 * @blink_leds: blinking LEDs 64 * @blink_delay: blinking delay 65 * @active_low: LED is active low 66 */ 67 struct bcm6328_led { 68 struct led_classdev cdev; 69 void __iomem *mem; 70 spinlock_t *lock; 71 unsigned long pin; 72 unsigned long *blink_leds; 73 unsigned long *blink_delay; 74 bool active_low; 75 }; 76 77 static void bcm6328_led_write(void __iomem *reg, unsigned long data) 78 { 79 #ifdef CONFIG_CPU_BIG_ENDIAN 80 iowrite32be(data, reg); 81 #else 82 writel(data, reg); 83 #endif 84 } 85 86 static unsigned long bcm6328_led_read(void __iomem *reg) 87 { 88 #ifdef CONFIG_CPU_BIG_ENDIAN 89 return ioread32be(reg); 90 #else 91 return readl(reg); 92 #endif 93 } 94 95 /** 96 * LEDMode 64 bits / 24 LEDs 97 * bits [31:0] -> LEDs 8-23 98 * bits [47:32] -> LEDs 0-7 99 * bits [63:48] -> unused 100 */ 101 static unsigned long bcm6328_pin2shift(unsigned long pin) 102 { 103 if (pin < 8) 104 return pin + 16; /* LEDs 0-7 (bits 47:32) */ 105 else 106 return pin - 8; /* LEDs 8-23 (bits 31:0) */ 107 } 108 109 static void bcm6328_led_mode(struct bcm6328_led *led, unsigned long value) 110 { 111 void __iomem *mode; 112 unsigned long val, shift; 113 114 shift = bcm6328_pin2shift(led->pin); 115 if (shift / 16) 116 mode = led->mem + BCM6328_REG_MODE_HI; 117 else 118 mode = led->mem + BCM6328_REG_MODE_LO; 119 120 val = bcm6328_led_read(mode); 121 val &= ~(BCM6328_LED_MODE_MASK << BCM6328_LED_SHIFT(shift % 16)); 122 val |= (value << BCM6328_LED_SHIFT(shift % 16)); 123 bcm6328_led_write(mode, val); 124 } 125 126 static void bcm6328_led_set(struct led_classdev *led_cdev, 127 enum led_brightness value) 128 { 129 struct bcm6328_led *led = 130 container_of(led_cdev, struct bcm6328_led, cdev); 131 unsigned long flags; 132 133 spin_lock_irqsave(led->lock, flags); 134 *(led->blink_leds) &= ~BIT(led->pin); 135 if ((led->active_low && value == LED_OFF) || 136 (!led->active_low && value != LED_OFF)) 137 bcm6328_led_mode(led, BCM6328_LED_MODE_ON); 138 else 139 bcm6328_led_mode(led, BCM6328_LED_MODE_OFF); 140 spin_unlock_irqrestore(led->lock, flags); 141 } 142 143 static unsigned long bcm6328_blink_delay(unsigned long delay) 144 { 145 unsigned long bcm6328_delay; 146 147 bcm6328_delay = delay + BCM6328_LED_INTERVAL_MS / 2; 148 bcm6328_delay = bcm6328_delay / BCM6328_LED_INTERVAL_MS; 149 if (bcm6328_delay == 0) 150 bcm6328_delay = 1; 151 152 return bcm6328_delay; 153 } 154 155 static int bcm6328_blink_set(struct led_classdev *led_cdev, 156 unsigned long *delay_on, unsigned long *delay_off) 157 { 158 struct bcm6328_led *led = 159 container_of(led_cdev, struct bcm6328_led, cdev); 160 unsigned long delay, flags; 161 int rc; 162 163 if (!*delay_on) 164 *delay_on = BCM6328_LED_DEF_DELAY; 165 if (!*delay_off) 166 *delay_off = BCM6328_LED_DEF_DELAY; 167 168 delay = bcm6328_blink_delay(*delay_on); 169 if (delay != bcm6328_blink_delay(*delay_off)) { 170 dev_dbg(led_cdev->dev, 171 "fallback to soft blinking (delay_on != delay_off)\n"); 172 return -EINVAL; 173 } 174 175 if (delay > BCM6328_LED_INTV_MASK) { 176 dev_dbg(led_cdev->dev, 177 "fallback to soft blinking (delay > %ums)\n", 178 BCM6328_LED_INTV_MASK * BCM6328_LED_INTERVAL_MS); 179 return -EINVAL; 180 } 181 182 spin_lock_irqsave(led->lock, flags); 183 if (*(led->blink_leds) == 0 || 184 *(led->blink_leds) == BIT(led->pin) || 185 *(led->blink_delay) == delay) { 186 unsigned long val; 187 188 *(led->blink_leds) |= BIT(led->pin); 189 *(led->blink_delay) = delay; 190 191 val = bcm6328_led_read(led->mem + BCM6328_REG_INIT); 192 val &= ~BCM6328_LED_FAST_INTV_MASK; 193 val |= (delay << BCM6328_LED_FAST_INTV_SHIFT); 194 bcm6328_led_write(led->mem + BCM6328_REG_INIT, val); 195 196 bcm6328_led_mode(led, BCM6328_LED_MODE_BLINK); 197 rc = 0; 198 } else { 199 dev_dbg(led_cdev->dev, 200 "fallback to soft blinking (delay already set)\n"); 201 rc = -EINVAL; 202 } 203 spin_unlock_irqrestore(led->lock, flags); 204 205 return rc; 206 } 207 208 static int bcm6328_hwled(struct device *dev, struct device_node *nc, u32 reg, 209 void __iomem *mem, spinlock_t *lock) 210 { 211 int i, cnt; 212 unsigned long flags, val; 213 214 spin_lock_irqsave(lock, flags); 215 val = bcm6328_led_read(mem + BCM6328_REG_HWDIS); 216 val &= ~BIT(reg); 217 bcm6328_led_write(mem + BCM6328_REG_HWDIS, val); 218 spin_unlock_irqrestore(lock, flags); 219 220 /* Only LEDs 0-7 can be activity/link controlled */ 221 if (reg >= 8) 222 return 0; 223 224 cnt = of_property_count_elems_of_size(nc, "brcm,link-signal-sources", 225 sizeof(u32)); 226 for (i = 0; i < cnt; i++) { 227 u32 sel; 228 void __iomem *addr; 229 230 if (reg < 4) 231 addr = mem + BCM6328_REG_LNKACTSEL_LO; 232 else 233 addr = mem + BCM6328_REG_LNKACTSEL_HI; 234 235 of_property_read_u32_index(nc, "brcm,link-signal-sources", i, 236 &sel); 237 238 if (reg / 4 != sel / 4) { 239 dev_warn(dev, "invalid link signal source\n"); 240 continue; 241 } 242 243 spin_lock_irqsave(lock, flags); 244 val = bcm6328_led_read(addr); 245 val |= (BIT(reg % 4) << (((sel % 4) * 4) + 16)); 246 bcm6328_led_write(addr, val); 247 spin_unlock_irqrestore(lock, flags); 248 } 249 250 cnt = of_property_count_elems_of_size(nc, 251 "brcm,activity-signal-sources", 252 sizeof(u32)); 253 for (i = 0; i < cnt; i++) { 254 u32 sel; 255 void __iomem *addr; 256 257 if (reg < 4) 258 addr = mem + BCM6328_REG_LNKACTSEL_LO; 259 else 260 addr = mem + BCM6328_REG_LNKACTSEL_HI; 261 262 of_property_read_u32_index(nc, "brcm,activity-signal-sources", 263 i, &sel); 264 265 if (reg / 4 != sel / 4) { 266 dev_warn(dev, "invalid activity signal source\n"); 267 continue; 268 } 269 270 spin_lock_irqsave(lock, flags); 271 val = bcm6328_led_read(addr); 272 val |= (BIT(reg % 4) << ((sel % 4) * 4)); 273 bcm6328_led_write(addr, val); 274 spin_unlock_irqrestore(lock, flags); 275 } 276 277 return 0; 278 } 279 280 static int bcm6328_led(struct device *dev, struct device_node *nc, u32 reg, 281 void __iomem *mem, spinlock_t *lock, 282 unsigned long *blink_leds, unsigned long *blink_delay) 283 { 284 struct bcm6328_led *led; 285 const char *state; 286 int rc; 287 288 led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); 289 if (!led) 290 return -ENOMEM; 291 292 led->pin = reg; 293 led->mem = mem; 294 led->lock = lock; 295 led->blink_leds = blink_leds; 296 led->blink_delay = blink_delay; 297 298 if (of_property_read_bool(nc, "active-low")) 299 led->active_low = true; 300 301 led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name; 302 led->cdev.default_trigger = of_get_property(nc, 303 "linux,default-trigger", 304 NULL); 305 306 if (!of_property_read_string(nc, "default-state", &state)) { 307 if (!strcmp(state, "on")) { 308 led->cdev.brightness = LED_FULL; 309 } else if (!strcmp(state, "keep")) { 310 void __iomem *mode; 311 unsigned long val, shift; 312 313 shift = bcm6328_pin2shift(led->pin); 314 if (shift / 16) 315 mode = mem + BCM6328_REG_MODE_HI; 316 else 317 mode = mem + BCM6328_REG_MODE_LO; 318 319 val = bcm6328_led_read(mode) >> 320 BCM6328_LED_SHIFT(shift % 16); 321 val &= BCM6328_LED_MODE_MASK; 322 if ((led->active_low && val == BCM6328_LED_MODE_OFF) || 323 (!led->active_low && val == BCM6328_LED_MODE_ON)) 324 led->cdev.brightness = LED_FULL; 325 else 326 led->cdev.brightness = LED_OFF; 327 } else { 328 led->cdev.brightness = LED_OFF; 329 } 330 } else { 331 led->cdev.brightness = LED_OFF; 332 } 333 334 bcm6328_led_set(&led->cdev, led->cdev.brightness); 335 336 led->cdev.brightness_set = bcm6328_led_set; 337 led->cdev.blink_set = bcm6328_blink_set; 338 339 rc = led_classdev_register(dev, &led->cdev); 340 if (rc < 0) 341 return rc; 342 343 dev_dbg(dev, "registered LED %s\n", led->cdev.name); 344 345 return 0; 346 } 347 348 static int bcm6328_leds_probe(struct platform_device *pdev) 349 { 350 struct device *dev = &pdev->dev; 351 struct device_node *np = pdev->dev.of_node; 352 struct device_node *child; 353 struct resource *mem_r; 354 void __iomem *mem; 355 spinlock_t *lock; /* memory lock */ 356 unsigned long val, *blink_leds, *blink_delay; 357 358 mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 359 if (!mem_r) 360 return -EINVAL; 361 362 mem = devm_ioremap_resource(dev, mem_r); 363 if (IS_ERR(mem)) 364 return PTR_ERR(mem); 365 366 lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL); 367 if (!lock) 368 return -ENOMEM; 369 370 blink_leds = devm_kzalloc(dev, sizeof(*blink_leds), GFP_KERNEL); 371 if (!blink_leds) 372 return -ENOMEM; 373 374 blink_delay = devm_kzalloc(dev, sizeof(*blink_delay), GFP_KERNEL); 375 if (!blink_delay) 376 return -ENOMEM; 377 378 spin_lock_init(lock); 379 380 bcm6328_led_write(mem + BCM6328_REG_HWDIS, ~0); 381 bcm6328_led_write(mem + BCM6328_REG_LNKACTSEL_HI, 0); 382 bcm6328_led_write(mem + BCM6328_REG_LNKACTSEL_LO, 0); 383 384 val = bcm6328_led_read(mem + BCM6328_REG_INIT); 385 val &= ~(BCM6328_INIT_MASK); 386 if (of_property_read_bool(np, "brcm,serial-leds")) 387 val |= BCM6328_SERIAL_LED_EN; 388 if (of_property_read_bool(np, "brcm,serial-mux")) 389 val |= BCM6328_SERIAL_LED_MUX; 390 if (of_property_read_bool(np, "brcm,serial-clk-low")) 391 val |= BCM6328_SERIAL_LED_CLK_NPOL; 392 if (!of_property_read_bool(np, "brcm,serial-dat-low")) 393 val |= BCM6328_SERIAL_LED_DATA_PPOL; 394 if (!of_property_read_bool(np, "brcm,serial-shift-inv")) 395 val |= BCM6328_SERIAL_LED_SHIFT_DIR; 396 bcm6328_led_write(mem + BCM6328_REG_INIT, val); 397 398 for_each_available_child_of_node(np, child) { 399 int rc; 400 u32 reg; 401 402 if (of_property_read_u32(child, "reg", ®)) 403 continue; 404 405 if (reg >= BCM6328_LED_MAX_COUNT) { 406 dev_err(dev, "invalid LED (%u >= %d)\n", reg, 407 BCM6328_LED_MAX_COUNT); 408 continue; 409 } 410 411 if (of_property_read_bool(child, "brcm,hardware-controlled")) 412 rc = bcm6328_hwled(dev, child, reg, mem, lock); 413 else 414 rc = bcm6328_led(dev, child, reg, mem, lock, 415 blink_leds, blink_delay); 416 417 if (rc < 0) { 418 of_node_put(child); 419 return rc; 420 } 421 } 422 423 return 0; 424 } 425 426 static const struct of_device_id bcm6328_leds_of_match[] = { 427 { .compatible = "brcm,bcm6328-leds", }, 428 { }, 429 }; 430 MODULE_DEVICE_TABLE(of, bcm6328_leds_of_match); 431 432 static struct platform_driver bcm6328_leds_driver = { 433 .probe = bcm6328_leds_probe, 434 .driver = { 435 .name = "leds-bcm6328", 436 .of_match_table = bcm6328_leds_of_match, 437 }, 438 }; 439 440 module_platform_driver(bcm6328_leds_driver); 441 442 MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>"); 443 MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>"); 444 MODULE_DESCRIPTION("LED driver for BCM6328 controllers"); 445 MODULE_LICENSE("GPL v2"); 446 MODULE_ALIAS("platform:leds-bcm6328"); 447