linux/drivers/leds/leds-bcm6358.c
<<
>>
Prefs
   1/*
   2 * Driver for BCM6358 memory-mapped LEDs, based on leds-syscon.c
   3 *
   4 * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>
   5 *
   6 * This program is free software; you can redistribute  it and/or modify it
   7 * under  the terms of  the GNU General  Public License as published by the
   8 * Free Software Foundation;  either version 2 of the  License, or (at your
   9 * option) any later version.
  10 */
  11#include <linux/delay.h>
  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 BCM6358_REG_MODE                0x0
  20#define BCM6358_REG_CTRL                0x4
  21
  22#define BCM6358_SLED_CLKDIV_MASK        3
  23#define BCM6358_SLED_CLKDIV_1           0
  24#define BCM6358_SLED_CLKDIV_2           1
  25#define BCM6358_SLED_CLKDIV_4           2
  26#define BCM6358_SLED_CLKDIV_8           3
  27
  28#define BCM6358_SLED_POLARITY           BIT(2)
  29#define BCM6358_SLED_BUSY               BIT(3)
  30
  31#define BCM6358_SLED_MAX_COUNT          32
  32#define BCM6358_SLED_WAIT               100
  33
  34/**
  35 * struct bcm6358_led - state container for bcm6358 based LEDs
  36 * @cdev: LED class device for this LED
  37 * @mem: memory resource
  38 * @lock: memory lock
  39 * @pin: LED pin number
  40 * @active_low: LED is active low
  41 */
  42struct bcm6358_led {
  43        struct led_classdev cdev;
  44        void __iomem *mem;
  45        spinlock_t *lock;
  46        unsigned long pin;
  47        bool active_low;
  48};
  49
  50static void bcm6358_led_write(void __iomem *reg, unsigned long data)
  51{
  52#ifdef CONFIG_CPU_BIG_ENDIAN
  53        iowrite32be(data, reg);
  54#else
  55        writel(data, reg);
  56#endif
  57}
  58
  59static unsigned long bcm6358_led_read(void __iomem *reg)
  60{
  61#ifdef CONFIG_CPU_BIG_ENDIAN
  62        return ioread32be(reg);
  63#else
  64        return readl(reg);
  65#endif
  66}
  67
  68static unsigned long bcm6358_led_busy(void __iomem *mem)
  69{
  70        unsigned long val;
  71
  72        while ((val = bcm6358_led_read(mem + BCM6358_REG_CTRL)) &
  73                BCM6358_SLED_BUSY)
  74                udelay(BCM6358_SLED_WAIT);
  75
  76        return val;
  77}
  78
  79static void bcm6358_led_set(struct led_classdev *led_cdev,
  80                            enum led_brightness value)
  81{
  82        struct bcm6358_led *led =
  83                container_of(led_cdev, struct bcm6358_led, cdev);
  84        unsigned long flags, val;
  85
  86        spin_lock_irqsave(led->lock, flags);
  87        bcm6358_led_busy(led->mem);
  88        val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);
  89        if ((led->active_low && value == LED_OFF) ||
  90            (!led->active_low && value != LED_OFF))
  91                val |= BIT(led->pin);
  92        else
  93                val &= ~(BIT(led->pin));
  94        bcm6358_led_write(led->mem + BCM6358_REG_MODE, val);
  95        spin_unlock_irqrestore(led->lock, flags);
  96}
  97
  98static int bcm6358_led(struct device *dev, struct device_node *nc, u32 reg,
  99                       void __iomem *mem, spinlock_t *lock)
 100{
 101        struct bcm6358_led *led;
 102        const char *state;
 103        int rc;
 104
 105        led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
 106        if (!led)
 107                return -ENOMEM;
 108
 109        led->pin = reg;
 110        led->mem = mem;
 111        led->lock = lock;
 112
 113        if (of_property_read_bool(nc, "active-low"))
 114                led->active_low = true;
 115
 116        led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name;
 117        led->cdev.default_trigger = of_get_property(nc,
 118                                                    "linux,default-trigger",
 119                                                    NULL);
 120
 121        if (!of_property_read_string(nc, "default-state", &state)) {
 122                if (!strcmp(state, "on")) {
 123                        led->cdev.brightness = LED_FULL;
 124                } else if (!strcmp(state, "keep")) {
 125                        unsigned long val;
 126                        val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);
 127                        val &= BIT(led->pin);
 128                        if ((led->active_low && !val) ||
 129                            (!led->active_low && val))
 130                                led->cdev.brightness = LED_FULL;
 131                        else
 132                                led->cdev.brightness = LED_OFF;
 133                } else {
 134                        led->cdev.brightness = LED_OFF;
 135                }
 136        } else {
 137                led->cdev.brightness = LED_OFF;
 138        }
 139
 140        bcm6358_led_set(&led->cdev, led->cdev.brightness);
 141
 142        led->cdev.brightness_set = bcm6358_led_set;
 143
 144        rc = led_classdev_register(dev, &led->cdev);
 145        if (rc < 0)
 146                return rc;
 147
 148        dev_dbg(dev, "registered LED %s\n", led->cdev.name);
 149
 150        return 0;
 151}
 152
 153static int bcm6358_leds_probe(struct platform_device *pdev)
 154{
 155        struct device *dev = &pdev->dev;
 156        struct device_node *np = pdev->dev.of_node;
 157        struct device_node *child;
 158        struct resource *mem_r;
 159        void __iomem *mem;
 160        spinlock_t *lock; /* memory lock */
 161        unsigned long val;
 162        u32 clk_div;
 163
 164        mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 165        if (!mem_r)
 166                return -EINVAL;
 167
 168        mem = devm_ioremap_resource(dev, mem_r);
 169        if (IS_ERR(mem))
 170                return PTR_ERR(mem);
 171
 172        lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
 173        if (!lock)
 174                return -ENOMEM;
 175
 176        spin_lock_init(lock);
 177
 178        val = bcm6358_led_busy(mem);
 179        val &= ~(BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK);
 180        if (of_property_read_bool(np, "brcm,clk-dat-low"))
 181                val |= BCM6358_SLED_POLARITY;
 182        of_property_read_u32(np, "brcm,clk-div", &clk_div);
 183        switch (clk_div) {
 184        case 8:
 185                val |= BCM6358_SLED_CLKDIV_8;
 186                break;
 187        case 4:
 188                val |= BCM6358_SLED_CLKDIV_4;
 189                break;
 190        case 2:
 191                val |= BCM6358_SLED_CLKDIV_2;
 192                break;
 193        default:
 194                val |= BCM6358_SLED_CLKDIV_1;
 195                break;
 196        }
 197        bcm6358_led_write(mem + BCM6358_REG_CTRL, val);
 198
 199        for_each_available_child_of_node(np, child) {
 200                int rc;
 201                u32 reg;
 202
 203                if (of_property_read_u32(child, "reg", &reg))
 204                        continue;
 205
 206                if (reg >= BCM6358_SLED_MAX_COUNT) {
 207                        dev_err(dev, "invalid LED (%u >= %d)\n", reg,
 208                                BCM6358_SLED_MAX_COUNT);
 209                        continue;
 210                }
 211
 212                rc = bcm6358_led(dev, child, reg, mem, lock);
 213                if (rc < 0) {
 214                        of_node_put(child);
 215                        return rc;
 216                }
 217        }
 218
 219        return 0;
 220}
 221
 222static const struct of_device_id bcm6358_leds_of_match[] = {
 223        { .compatible = "brcm,bcm6358-leds", },
 224        { },
 225};
 226MODULE_DEVICE_TABLE(of, bcm6358_leds_of_match);
 227
 228static struct platform_driver bcm6358_leds_driver = {
 229        .probe = bcm6358_leds_probe,
 230        .driver = {
 231                .name = "leds-bcm6358",
 232                .of_match_table = bcm6358_leds_of_match,
 233        },
 234};
 235
 236module_platform_driver(bcm6358_leds_driver);
 237
 238MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
 239MODULE_DESCRIPTION("LED driver for BCM6358 controllers");
 240MODULE_LICENSE("GPL v2");
 241MODULE_ALIAS("platform:leds-bcm6358");
 242