linux/drivers/leds/leds-bcm6328.c
<<
>>
Prefs
   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 */
  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#ifdef CONFIG_CPU_BIG_ENDIAN
  80        iowrite32be(data, reg);
  81#else
  82        writel(data, reg);
  83#endif
  84}
  85
  86static 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 */
 101static 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
 109static 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
 126static 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
 143static 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
 155static 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
 208static 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
 280static 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
 348static 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", &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
 426static const struct of_device_id bcm6328_leds_of_match[] = {
 427        { .compatible = "brcm,bcm6328-leds", },
 428        { },
 429};
 430MODULE_DEVICE_TABLE(of, bcm6328_leds_of_match);
 431
 432static 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
 440module_platform_driver(bcm6328_leds_driver);
 441
 442MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
 443MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
 444MODULE_DESCRIPTION("LED driver for BCM6328 controllers");
 445MODULE_LICENSE("GPL v2");
 446MODULE_ALIAS("platform:leds-bcm6328");
 447