1*2874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2589fca16SÁlvaro Fernández Rojas /* 3589fca16SÁlvaro Fernández Rojas * Driver for BCM6358 memory-mapped LEDs, based on leds-syscon.c 4589fca16SÁlvaro Fernández Rojas * 5589fca16SÁlvaro Fernández Rojas * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com> 6589fca16SÁlvaro Fernández Rojas */ 7589fca16SÁlvaro Fernández Rojas #include <linux/delay.h> 8589fca16SÁlvaro Fernández Rojas #include <linux/io.h> 9589fca16SÁlvaro Fernández Rojas #include <linux/leds.h> 10589fca16SÁlvaro Fernández Rojas #include <linux/module.h> 11589fca16SÁlvaro Fernández Rojas #include <linux/of.h> 12589fca16SÁlvaro Fernández Rojas #include <linux/platform_device.h> 13589fca16SÁlvaro Fernández Rojas #include <linux/spinlock.h> 14589fca16SÁlvaro Fernández Rojas 15589fca16SÁlvaro Fernández Rojas #define BCM6358_REG_MODE 0x0 16589fca16SÁlvaro Fernández Rojas #define BCM6358_REG_CTRL 0x4 17589fca16SÁlvaro Fernández Rojas 18589fca16SÁlvaro Fernández Rojas #define BCM6358_SLED_CLKDIV_MASK 3 19589fca16SÁlvaro Fernández Rojas #define BCM6358_SLED_CLKDIV_1 0 20589fca16SÁlvaro Fernández Rojas #define BCM6358_SLED_CLKDIV_2 1 21589fca16SÁlvaro Fernández Rojas #define BCM6358_SLED_CLKDIV_4 2 22589fca16SÁlvaro Fernández Rojas #define BCM6358_SLED_CLKDIV_8 3 23589fca16SÁlvaro Fernández Rojas 24589fca16SÁlvaro Fernández Rojas #define BCM6358_SLED_POLARITY BIT(2) 25589fca16SÁlvaro Fernández Rojas #define BCM6358_SLED_BUSY BIT(3) 26589fca16SÁlvaro Fernández Rojas 27589fca16SÁlvaro Fernández Rojas #define BCM6358_SLED_MAX_COUNT 32 28589fca16SÁlvaro Fernández Rojas #define BCM6358_SLED_WAIT 100 29589fca16SÁlvaro Fernández Rojas 30589fca16SÁlvaro Fernández Rojas /** 31589fca16SÁlvaro Fernández Rojas * struct bcm6358_led - state container for bcm6358 based LEDs 32589fca16SÁlvaro Fernández Rojas * @cdev: LED class device for this LED 33589fca16SÁlvaro Fernández Rojas * @mem: memory resource 34589fca16SÁlvaro Fernández Rojas * @lock: memory lock 35589fca16SÁlvaro Fernández Rojas * @pin: LED pin number 36589fca16SÁlvaro Fernández Rojas * @active_low: LED is active low 37589fca16SÁlvaro Fernández Rojas */ 38589fca16SÁlvaro Fernández Rojas struct bcm6358_led { 39589fca16SÁlvaro Fernández Rojas struct led_classdev cdev; 40589fca16SÁlvaro Fernández Rojas void __iomem *mem; 41589fca16SÁlvaro Fernández Rojas spinlock_t *lock; 42589fca16SÁlvaro Fernández Rojas unsigned long pin; 43589fca16SÁlvaro Fernández Rojas bool active_low; 44589fca16SÁlvaro Fernández Rojas }; 45589fca16SÁlvaro Fernández Rojas 46589fca16SÁlvaro Fernández Rojas static void bcm6358_led_write(void __iomem *reg, unsigned long data) 47589fca16SÁlvaro Fernández Rojas { 484ba113b6SÁlvaro Fernández Rojas #ifdef CONFIG_CPU_BIG_ENDIAN 49589fca16SÁlvaro Fernández Rojas iowrite32be(data, reg); 504ba113b6SÁlvaro Fernández Rojas #else 514ba113b6SÁlvaro Fernández Rojas writel(data, reg); 524ba113b6SÁlvaro Fernández Rojas #endif 53589fca16SÁlvaro Fernández Rojas } 54589fca16SÁlvaro Fernández Rojas 55589fca16SÁlvaro Fernández Rojas static unsigned long bcm6358_led_read(void __iomem *reg) 56589fca16SÁlvaro Fernández Rojas { 574ba113b6SÁlvaro Fernández Rojas #ifdef CONFIG_CPU_BIG_ENDIAN 58589fca16SÁlvaro Fernández Rojas return ioread32be(reg); 594ba113b6SÁlvaro Fernández Rojas #else 604ba113b6SÁlvaro Fernández Rojas return readl(reg); 614ba113b6SÁlvaro Fernández Rojas #endif 62589fca16SÁlvaro Fernández Rojas } 63589fca16SÁlvaro Fernández Rojas 64589fca16SÁlvaro Fernández Rojas static unsigned long bcm6358_led_busy(void __iomem *mem) 65589fca16SÁlvaro Fernández Rojas { 66589fca16SÁlvaro Fernández Rojas unsigned long val; 67589fca16SÁlvaro Fernández Rojas 68589fca16SÁlvaro Fernández Rojas while ((val = bcm6358_led_read(mem + BCM6358_REG_CTRL)) & 69589fca16SÁlvaro Fernández Rojas BCM6358_SLED_BUSY) 70589fca16SÁlvaro Fernández Rojas udelay(BCM6358_SLED_WAIT); 71589fca16SÁlvaro Fernández Rojas 72589fca16SÁlvaro Fernández Rojas return val; 73589fca16SÁlvaro Fernández Rojas } 74589fca16SÁlvaro Fernández Rojas 756e636a0aSÁlvaro Fernández Rojas static void bcm6358_led_set(struct led_classdev *led_cdev, 766e636a0aSÁlvaro Fernández Rojas enum led_brightness value) 77589fca16SÁlvaro Fernández Rojas { 786e636a0aSÁlvaro Fernández Rojas struct bcm6358_led *led = 796e636a0aSÁlvaro Fernández Rojas container_of(led_cdev, struct bcm6358_led, cdev); 806e636a0aSÁlvaro Fernández Rojas unsigned long flags, val; 81589fca16SÁlvaro Fernández Rojas 826e636a0aSÁlvaro Fernández Rojas spin_lock_irqsave(led->lock, flags); 83589fca16SÁlvaro Fernández Rojas bcm6358_led_busy(led->mem); 84589fca16SÁlvaro Fernández Rojas val = bcm6358_led_read(led->mem + BCM6358_REG_MODE); 85589fca16SÁlvaro Fernández Rojas if ((led->active_low && value == LED_OFF) || 86589fca16SÁlvaro Fernández Rojas (!led->active_low && value != LED_OFF)) 87589fca16SÁlvaro Fernández Rojas val |= BIT(led->pin); 88589fca16SÁlvaro Fernández Rojas else 89589fca16SÁlvaro Fernández Rojas val &= ~(BIT(led->pin)); 90589fca16SÁlvaro Fernández Rojas bcm6358_led_write(led->mem + BCM6358_REG_MODE, val); 91589fca16SÁlvaro Fernández Rojas spin_unlock_irqrestore(led->lock, flags); 92589fca16SÁlvaro Fernández Rojas } 93589fca16SÁlvaro Fernández Rojas 94589fca16SÁlvaro Fernández Rojas static int bcm6358_led(struct device *dev, struct device_node *nc, u32 reg, 95589fca16SÁlvaro Fernández Rojas void __iomem *mem, spinlock_t *lock) 96589fca16SÁlvaro Fernández Rojas { 97589fca16SÁlvaro Fernández Rojas struct bcm6358_led *led; 98589fca16SÁlvaro Fernández Rojas const char *state; 99589fca16SÁlvaro Fernández Rojas int rc; 100589fca16SÁlvaro Fernández Rojas 101589fca16SÁlvaro Fernández Rojas led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); 102589fca16SÁlvaro Fernández Rojas if (!led) 103589fca16SÁlvaro Fernández Rojas return -ENOMEM; 104589fca16SÁlvaro Fernández Rojas 105589fca16SÁlvaro Fernández Rojas led->pin = reg; 106589fca16SÁlvaro Fernández Rojas led->mem = mem; 107589fca16SÁlvaro Fernández Rojas led->lock = lock; 108589fca16SÁlvaro Fernández Rojas 109589fca16SÁlvaro Fernández Rojas if (of_property_read_bool(nc, "active-low")) 110589fca16SÁlvaro Fernández Rojas led->active_low = true; 111589fca16SÁlvaro Fernández Rojas 112589fca16SÁlvaro Fernández Rojas led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name; 113589fca16SÁlvaro Fernández Rojas led->cdev.default_trigger = of_get_property(nc, 114589fca16SÁlvaro Fernández Rojas "linux,default-trigger", 115589fca16SÁlvaro Fernández Rojas NULL); 116589fca16SÁlvaro Fernández Rojas 117589fca16SÁlvaro Fernández Rojas if (!of_property_read_string(nc, "default-state", &state)) { 118589fca16SÁlvaro Fernández Rojas if (!strcmp(state, "on")) { 119589fca16SÁlvaro Fernández Rojas led->cdev.brightness = LED_FULL; 120589fca16SÁlvaro Fernández Rojas } else if (!strcmp(state, "keep")) { 121589fca16SÁlvaro Fernández Rojas unsigned long val; 122589fca16SÁlvaro Fernández Rojas val = bcm6358_led_read(led->mem + BCM6358_REG_MODE); 123589fca16SÁlvaro Fernández Rojas val &= BIT(led->pin); 124589fca16SÁlvaro Fernández Rojas if ((led->active_low && !val) || 125589fca16SÁlvaro Fernández Rojas (!led->active_low && val)) 126589fca16SÁlvaro Fernández Rojas led->cdev.brightness = LED_FULL; 127589fca16SÁlvaro Fernández Rojas else 128589fca16SÁlvaro Fernández Rojas led->cdev.brightness = LED_OFF; 129589fca16SÁlvaro Fernández Rojas } else { 130589fca16SÁlvaro Fernández Rojas led->cdev.brightness = LED_OFF; 131589fca16SÁlvaro Fernández Rojas } 132589fca16SÁlvaro Fernández Rojas } else { 133589fca16SÁlvaro Fernández Rojas led->cdev.brightness = LED_OFF; 134589fca16SÁlvaro Fernández Rojas } 135589fca16SÁlvaro Fernández Rojas 13642273caaSÁlvaro Fernández Rojas bcm6358_led_set(&led->cdev, led->cdev.brightness); 13742273caaSÁlvaro Fernández Rojas 138589fca16SÁlvaro Fernández Rojas led->cdev.brightness_set = bcm6358_led_set; 139589fca16SÁlvaro Fernández Rojas 140589fca16SÁlvaro Fernández Rojas rc = led_classdev_register(dev, &led->cdev); 141589fca16SÁlvaro Fernández Rojas if (rc < 0) 142589fca16SÁlvaro Fernández Rojas return rc; 143589fca16SÁlvaro Fernández Rojas 144589fca16SÁlvaro Fernández Rojas dev_dbg(dev, "registered LED %s\n", led->cdev.name); 145589fca16SÁlvaro Fernández Rojas 146589fca16SÁlvaro Fernández Rojas return 0; 147589fca16SÁlvaro Fernández Rojas } 148589fca16SÁlvaro Fernández Rojas 149589fca16SÁlvaro Fernández Rojas static int bcm6358_leds_probe(struct platform_device *pdev) 150589fca16SÁlvaro Fernández Rojas { 151589fca16SÁlvaro Fernández Rojas struct device *dev = &pdev->dev; 152589fca16SÁlvaro Fernández Rojas struct device_node *np = pdev->dev.of_node; 153589fca16SÁlvaro Fernández Rojas struct device_node *child; 154589fca16SÁlvaro Fernández Rojas struct resource *mem_r; 155589fca16SÁlvaro Fernández Rojas void __iomem *mem; 156589fca16SÁlvaro Fernández Rojas spinlock_t *lock; /* memory lock */ 157589fca16SÁlvaro Fernández Rojas unsigned long val; 158589fca16SÁlvaro Fernández Rojas u32 clk_div; 159589fca16SÁlvaro Fernández Rojas 160589fca16SÁlvaro Fernández Rojas mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 161589fca16SÁlvaro Fernández Rojas if (!mem_r) 162589fca16SÁlvaro Fernández Rojas return -EINVAL; 163589fca16SÁlvaro Fernández Rojas 164589fca16SÁlvaro Fernández Rojas mem = devm_ioremap_resource(dev, mem_r); 165589fca16SÁlvaro Fernández Rojas if (IS_ERR(mem)) 166589fca16SÁlvaro Fernández Rojas return PTR_ERR(mem); 167589fca16SÁlvaro Fernández Rojas 168589fca16SÁlvaro Fernández Rojas lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL); 169589fca16SÁlvaro Fernández Rojas if (!lock) 170589fca16SÁlvaro Fernández Rojas return -ENOMEM; 171589fca16SÁlvaro Fernández Rojas 172589fca16SÁlvaro Fernández Rojas spin_lock_init(lock); 173589fca16SÁlvaro Fernández Rojas 174589fca16SÁlvaro Fernández Rojas val = bcm6358_led_busy(mem); 175589fca16SÁlvaro Fernández Rojas val &= ~(BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK); 176589fca16SÁlvaro Fernández Rojas if (of_property_read_bool(np, "brcm,clk-dat-low")) 177589fca16SÁlvaro Fernández Rojas val |= BCM6358_SLED_POLARITY; 178589fca16SÁlvaro Fernández Rojas of_property_read_u32(np, "brcm,clk-div", &clk_div); 179589fca16SÁlvaro Fernández Rojas switch (clk_div) { 180589fca16SÁlvaro Fernández Rojas case 8: 181589fca16SÁlvaro Fernández Rojas val |= BCM6358_SLED_CLKDIV_8; 182589fca16SÁlvaro Fernández Rojas break; 183589fca16SÁlvaro Fernández Rojas case 4: 184589fca16SÁlvaro Fernández Rojas val |= BCM6358_SLED_CLKDIV_4; 185589fca16SÁlvaro Fernández Rojas break; 186589fca16SÁlvaro Fernández Rojas case 2: 187589fca16SÁlvaro Fernández Rojas val |= BCM6358_SLED_CLKDIV_2; 188589fca16SÁlvaro Fernández Rojas break; 189589fca16SÁlvaro Fernández Rojas default: 190589fca16SÁlvaro Fernández Rojas val |= BCM6358_SLED_CLKDIV_1; 191589fca16SÁlvaro Fernández Rojas break; 192589fca16SÁlvaro Fernández Rojas } 193589fca16SÁlvaro Fernández Rojas bcm6358_led_write(mem + BCM6358_REG_CTRL, val); 194589fca16SÁlvaro Fernández Rojas 195589fca16SÁlvaro Fernández Rojas for_each_available_child_of_node(np, child) { 196589fca16SÁlvaro Fernández Rojas int rc; 197589fca16SÁlvaro Fernández Rojas u32 reg; 198589fca16SÁlvaro Fernández Rojas 199589fca16SÁlvaro Fernández Rojas if (of_property_read_u32(child, "reg", ®)) 200589fca16SÁlvaro Fernández Rojas continue; 201589fca16SÁlvaro Fernández Rojas 202589fca16SÁlvaro Fernández Rojas if (reg >= BCM6358_SLED_MAX_COUNT) { 203589fca16SÁlvaro Fernández Rojas dev_err(dev, "invalid LED (%u >= %d)\n", reg, 204589fca16SÁlvaro Fernández Rojas BCM6358_SLED_MAX_COUNT); 205589fca16SÁlvaro Fernández Rojas continue; 206589fca16SÁlvaro Fernández Rojas } 207589fca16SÁlvaro Fernández Rojas 208589fca16SÁlvaro Fernández Rojas rc = bcm6358_led(dev, child, reg, mem, lock); 2094b6ba5e2SJulia Lawall if (rc < 0) { 2104b6ba5e2SJulia Lawall of_node_put(child); 211589fca16SÁlvaro Fernández Rojas return rc; 212589fca16SÁlvaro Fernández Rojas } 2134b6ba5e2SJulia Lawall } 214589fca16SÁlvaro Fernández Rojas 215589fca16SÁlvaro Fernández Rojas return 0; 216589fca16SÁlvaro Fernández Rojas } 217589fca16SÁlvaro Fernández Rojas 218589fca16SÁlvaro Fernández Rojas static const struct of_device_id bcm6358_leds_of_match[] = { 219589fca16SÁlvaro Fernández Rojas { .compatible = "brcm,bcm6358-leds", }, 220589fca16SÁlvaro Fernández Rojas { }, 221589fca16SÁlvaro Fernández Rojas }; 22201736f07SLuis de Bethencourt MODULE_DEVICE_TABLE(of, bcm6358_leds_of_match); 223589fca16SÁlvaro Fernández Rojas 224589fca16SÁlvaro Fernández Rojas static struct platform_driver bcm6358_leds_driver = { 225589fca16SÁlvaro Fernández Rojas .probe = bcm6358_leds_probe, 226589fca16SÁlvaro Fernández Rojas .driver = { 227589fca16SÁlvaro Fernández Rojas .name = "leds-bcm6358", 228589fca16SÁlvaro Fernández Rojas .of_match_table = bcm6358_leds_of_match, 229589fca16SÁlvaro Fernández Rojas }, 230589fca16SÁlvaro Fernández Rojas }; 231589fca16SÁlvaro Fernández Rojas 232589fca16SÁlvaro Fernández Rojas module_platform_driver(bcm6358_leds_driver); 233589fca16SÁlvaro Fernández Rojas 234589fca16SÁlvaro Fernández Rojas MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>"); 235589fca16SÁlvaro Fernández Rojas MODULE_DESCRIPTION("LED driver for BCM6358 controllers"); 236589fca16SÁlvaro Fernández Rojas MODULE_LICENSE("GPL v2"); 237589fca16SÁlvaro Fernández Rojas MODULE_ALIAS("platform:leds-bcm6358"); 238