diff options
Diffstat (limited to 'drivers/leds/leds-bcm6358.c')
-rw-r--r-- | drivers/leds/leds-bcm6358.c | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/drivers/leds/leds-bcm6358.c b/drivers/leds/leds-bcm6358.c new file mode 100644 index 0000000000..86e51d44a5 --- /dev/null +++ b/drivers/leds/leds-bcm6358.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for BCM6358 memory-mapped LEDs, based on leds-syscon.c + * + * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com> + */ +#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) +{ +#ifdef CONFIG_CPU_BIG_ENDIAN + iowrite32be(data, reg); +#else + writel(data, reg); +#endif +} + +static unsigned long bcm6358_led_read(void __iomem *reg) +{ +#ifdef CONFIG_CPU_BIG_ENDIAN + return ioread32be(reg); +#else + return readl(reg); +#endif +} + +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_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, val; + + spin_lock_irqsave(led->lock, flags); + 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); + 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 led_init_data init_data = {}; + struct bcm6358_led *led; + enum led_default_state state; + unsigned long val; + 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; + + init_data.fwnode = of_fwnode_handle(nc); + + state = led_init_default_state_get(init_data.fwnode); + switch (state) { + case LEDS_DEFSTATE_ON: + led->cdev.brightness = LED_FULL; + break; + case LEDS_DEFSTATE_KEEP: + 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; + break; + default: + led->cdev.brightness = LED_OFF; + } + + bcm6358_led_set(&led->cdev, led->cdev.brightness); + + led->cdev.brightness_set = bcm6358_led_set; + + rc = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); + 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 = dev_of_node(&pdev->dev); + struct device_node *child; + void __iomem *mem; + spinlock_t *lock; /* memory lock */ + unsigned long val; + u32 clk_div; + + mem = devm_platform_ioremap_resource(pdev, 0); + 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) { + of_node_put(child); + return rc; + } + } + + return 0; +} + +static const struct of_device_id bcm6358_leds_of_match[] = { + { .compatible = "brcm,bcm6358-leds", }, + { }, +}; +MODULE_DEVICE_TABLE(of, bcm6358_leds_of_match); + +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"); |