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