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