| /* | |
| * 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) | |
| { | |
| #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 bcm6358_led *led; | |
| 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); | |
| 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; | |
| 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_set(&led->cdev, led->cdev.brightness); | |
| 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) { | |
| 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"); |