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