|  | // 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); | 
|  | 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_scoped(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", }, | 
|  | { }, | 
|  | }; | 
|  | 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"); |