diff options
author | Álvaro Fernández Rojas <noltari@gmail.com> | 2015-05-21 19:11:10 +0200 |
---|---|---|
committer | Bryan Wu <cooloney@gmail.com> | 2015-05-25 22:26:48 +0200 |
commit | 589fca16c14adec7ebeb601e22850826e18b8f8d (patch) | |
tree | de16c30eec41c38ca775a82c50ce2e76132e6d59 /drivers/leds/leds-bcm6358.c | |
parent | leds: add DT binding for BCM6358 LED controller (diff) | |
download | linux-589fca16c14adec7ebeb601e22850826e18b8f8d.tar.xz linux-589fca16c14adec7ebeb601e22850826e18b8f8d.zip |
leds: add BCM6358 LED driver
This adds support for the LED controller on Broadcom's BCM6358.
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
Acked-by: Jacek Anaszewski <j.anaszewski@samsung.com>
Signed-off-by: Bryan Wu <cooloney@gmail.com>
Diffstat (limited to 'drivers/leds/leds-bcm6358.c')
-rw-r--r-- | drivers/leds/leds-bcm6358.c | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/drivers/leds/leds-bcm6358.c b/drivers/leds/leds-bcm6358.c new file mode 100644 index 000000000000..21f96930b3be --- /dev/null +++ b/drivers/leds/leds-bcm6358.c @@ -0,0 +1,243 @@ +/* + * Driver for BCM6358 memory-mapped LEDs, based on leds-syscon.c + * + * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#define BCM6358_REG_MODE 0x0 +#define BCM6358_REG_CTRL 0x4 + +#define BCM6358_SLED_CLKDIV_MASK 3 +#define BCM6358_SLED_CLKDIV_1 0 +#define BCM6358_SLED_CLKDIV_2 1 +#define BCM6358_SLED_CLKDIV_4 2 +#define BCM6358_SLED_CLKDIV_8 3 + +#define BCM6358_SLED_POLARITY BIT(2) +#define BCM6358_SLED_BUSY BIT(3) + +#define BCM6358_SLED_MAX_COUNT 32 +#define BCM6358_SLED_WAIT 100 + +/** + * struct bcm6358_led - state container for bcm6358 based LEDs + * @cdev: LED class device for this LED + * @mem: memory resource + * @lock: memory lock + * @pin: LED pin number + * @active_low: LED is active low + */ +struct bcm6358_led { + struct led_classdev cdev; + void __iomem *mem; + spinlock_t *lock; + unsigned long pin; + bool active_low; +}; + +static void bcm6358_led_write(void __iomem *reg, unsigned long data) +{ + iowrite32be(data, reg); +} + +static unsigned long bcm6358_led_read(void __iomem *reg) +{ + return ioread32be(reg); +} + +static unsigned long bcm6358_led_busy(void __iomem *mem) +{ + unsigned long val; + + while ((val = bcm6358_led_read(mem + BCM6358_REG_CTRL)) & + BCM6358_SLED_BUSY) + udelay(BCM6358_SLED_WAIT); + + return val; +} + +static void bcm6358_led_mode(struct bcm6358_led *led, unsigned long value) +{ + unsigned long val; + + bcm6358_led_busy(led->mem); + + val = bcm6358_led_read(led->mem + BCM6358_REG_MODE); + if ((led->active_low && value == LED_OFF) || + (!led->active_low && value != LED_OFF)) + val |= BIT(led->pin); + else + val &= ~(BIT(led->pin)); + bcm6358_led_write(led->mem + BCM6358_REG_MODE, val); +} + +static void bcm6358_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct bcm6358_led *led = + container_of(led_cdev, struct bcm6358_led, cdev); + unsigned long flags; + + spin_lock_irqsave(led->lock, flags); + bcm6358_led_mode(led, value); + spin_unlock_irqrestore(led->lock, flags); +} + +static int bcm6358_led(struct device *dev, struct device_node *nc, u32 reg, + void __iomem *mem, spinlock_t *lock) +{ + struct bcm6358_led *led; + unsigned long flags; + const char *state; + int rc; + + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->pin = reg; + led->mem = mem; + led->lock = lock; + + if (of_property_read_bool(nc, "active-low")) + led->active_low = true; + + led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name; + led->cdev.default_trigger = of_get_property(nc, + "linux,default-trigger", + NULL); + + spin_lock_irqsave(lock, flags); + if (!of_property_read_string(nc, "default-state", &state)) { + if (!strcmp(state, "on")) { + led->cdev.brightness = LED_FULL; + } else if (!strcmp(state, "keep")) { + unsigned long val; + + bcm6358_led_busy(led->mem); + + val = bcm6358_led_read(led->mem + BCM6358_REG_MODE); + val &= BIT(led->pin); + if ((led->active_low && !val) || + (!led->active_low && val)) + led->cdev.brightness = LED_FULL; + else + led->cdev.brightness = LED_OFF; + } else { + led->cdev.brightness = LED_OFF; + } + } else { + led->cdev.brightness = LED_OFF; + } + bcm6358_led_mode(led, led->cdev.brightness); + spin_unlock_irqrestore(lock, flags); + + led->cdev.brightness_set = bcm6358_led_set; + + rc = led_classdev_register(dev, &led->cdev); + if (rc < 0) + return rc; + + dev_dbg(dev, "registered LED %s\n", led->cdev.name); + + return 0; +} + +static int bcm6358_leds_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + struct device_node *child; + struct resource *mem_r; + void __iomem *mem; + spinlock_t *lock; /* memory lock */ + unsigned long val; + u32 clk_div; + + mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_r) + return -EINVAL; + + mem = devm_ioremap_resource(dev, mem_r); + if (IS_ERR(mem)) + return PTR_ERR(mem); + + lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL); + if (!lock) + return -ENOMEM; + + spin_lock_init(lock); + + val = bcm6358_led_busy(mem); + val &= ~(BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK); + if (of_property_read_bool(np, "brcm,clk-dat-low")) + val |= BCM6358_SLED_POLARITY; + of_property_read_u32(np, "brcm,clk-div", &clk_div); + switch (clk_div) { + case 8: + val |= BCM6358_SLED_CLKDIV_8; + break; + case 4: + val |= BCM6358_SLED_CLKDIV_4; + break; + case 2: + val |= BCM6358_SLED_CLKDIV_2; + break; + default: + val |= BCM6358_SLED_CLKDIV_1; + break; + } + bcm6358_led_write(mem + BCM6358_REG_CTRL, val); + + for_each_available_child_of_node(np, child) { + int rc; + u32 reg; + + if (of_property_read_u32(child, "reg", ®)) + continue; + + if (reg >= BCM6358_SLED_MAX_COUNT) { + dev_err(dev, "invalid LED (%u >= %d)\n", reg, + BCM6358_SLED_MAX_COUNT); + continue; + } + + rc = bcm6358_led(dev, child, reg, mem, lock); + if (rc < 0) + return rc; + } + + return 0; +} + +static const struct of_device_id bcm6358_leds_of_match[] = { + { .compatible = "brcm,bcm6358-leds", }, + { }, +}; + +static struct platform_driver bcm6358_leds_driver = { + .probe = bcm6358_leds_probe, + .driver = { + .name = "leds-bcm6358", + .of_match_table = bcm6358_leds_of_match, + }, +}; + +module_platform_driver(bcm6358_leds_driver); + +MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>"); +MODULE_DESCRIPTION("LED driver for BCM6358 controllers"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:leds-bcm6358"); |