1 /* 2 * Driver for BCM6358 memory-mapped LEDs, based on leds-syscon.c 3 * 4 * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com> 5 * 6 * This program is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License as published by the 8 * Free Software Foundation; either version 2 of the License, or (at your 9 * option) any later version. 10 */ 11 #include <linux/delay.h> 12 #include <linux/io.h> 13 #include <linux/leds.h> 14 #include <linux/module.h> 15 #include <linux/of.h> 16 #include <linux/platform_device.h> 17 #include <linux/spinlock.h> 18 19 #define BCM6358_REG_MODE 0x0 20 #define BCM6358_REG_CTRL 0x4 21 22 #define BCM6358_SLED_CLKDIV_MASK 3 23 #define BCM6358_SLED_CLKDIV_1 0 24 #define BCM6358_SLED_CLKDIV_2 1 25 #define BCM6358_SLED_CLKDIV_4 2 26 #define BCM6358_SLED_CLKDIV_8 3 27 28 #define BCM6358_SLED_POLARITY BIT(2) 29 #define BCM6358_SLED_BUSY BIT(3) 30 31 #define BCM6358_SLED_MAX_COUNT 32 32 #define BCM6358_SLED_WAIT 100 33 34 /** 35 * struct bcm6358_led - state container for bcm6358 based LEDs 36 * @cdev: LED class device for this LED 37 * @mem: memory resource 38 * @lock: memory lock 39 * @pin: LED pin number 40 * @active_low: LED is active low 41 */ 42 struct bcm6358_led { 43 struct led_classdev cdev; 44 void __iomem *mem; 45 spinlock_t *lock; 46 unsigned long pin; 47 bool active_low; 48 }; 49 50 static void bcm6358_led_write(void __iomem *reg, unsigned long data) 51 { 52 iowrite32be(data, reg); 53 } 54 55 static unsigned long bcm6358_led_read(void __iomem *reg) 56 { 57 return ioread32be(reg); 58 } 59 60 static unsigned long bcm6358_led_busy(void __iomem *mem) 61 { 62 unsigned long val; 63 64 while ((val = bcm6358_led_read(mem + BCM6358_REG_CTRL)) & 65 BCM6358_SLED_BUSY) 66 udelay(BCM6358_SLED_WAIT); 67 68 return val; 69 } 70 71 static void bcm6358_led_mode(struct bcm6358_led *led, unsigned long value) 72 { 73 unsigned long val; 74 75 bcm6358_led_busy(led->mem); 76 77 val = bcm6358_led_read(led->mem + BCM6358_REG_MODE); 78 if ((led->active_low && value == LED_OFF) || 79 (!led->active_low && value != LED_OFF)) 80 val |= BIT(led->pin); 81 else 82 val &= ~(BIT(led->pin)); 83 bcm6358_led_write(led->mem + BCM6358_REG_MODE, val); 84 } 85 86 static void bcm6358_led_set(struct led_classdev *led_cdev, 87 enum led_brightness value) 88 { 89 struct bcm6358_led *led = 90 container_of(led_cdev, struct bcm6358_led, cdev); 91 unsigned long flags; 92 93 spin_lock_irqsave(led->lock, flags); 94 bcm6358_led_mode(led, value); 95 spin_unlock_irqrestore(led->lock, flags); 96 } 97 98 static int bcm6358_led(struct device *dev, struct device_node *nc, u32 reg, 99 void __iomem *mem, spinlock_t *lock) 100 { 101 struct bcm6358_led *led; 102 unsigned long flags; 103 const char *state; 104 int rc; 105 106 led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); 107 if (!led) 108 return -ENOMEM; 109 110 led->pin = reg; 111 led->mem = mem; 112 led->lock = lock; 113 114 if (of_property_read_bool(nc, "active-low")) 115 led->active_low = true; 116 117 led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name; 118 led->cdev.default_trigger = of_get_property(nc, 119 "linux,default-trigger", 120 NULL); 121 122 spin_lock_irqsave(lock, flags); 123 if (!of_property_read_string(nc, "default-state", &state)) { 124 if (!strcmp(state, "on")) { 125 led->cdev.brightness = LED_FULL; 126 } else if (!strcmp(state, "keep")) { 127 unsigned long val; 128 129 bcm6358_led_busy(led->mem); 130 131 val = bcm6358_led_read(led->mem + BCM6358_REG_MODE); 132 val &= BIT(led->pin); 133 if ((led->active_low && !val) || 134 (!led->active_low && val)) 135 led->cdev.brightness = LED_FULL; 136 else 137 led->cdev.brightness = LED_OFF; 138 } else { 139 led->cdev.brightness = LED_OFF; 140 } 141 } else { 142 led->cdev.brightness = LED_OFF; 143 } 144 bcm6358_led_mode(led, led->cdev.brightness); 145 spin_unlock_irqrestore(lock, flags); 146 147 led->cdev.brightness_set = bcm6358_led_set; 148 149 rc = led_classdev_register(dev, &led->cdev); 150 if (rc < 0) 151 return rc; 152 153 dev_dbg(dev, "registered LED %s\n", led->cdev.name); 154 155 return 0; 156 } 157 158 static int bcm6358_leds_probe(struct platform_device *pdev) 159 { 160 struct device *dev = &pdev->dev; 161 struct device_node *np = pdev->dev.of_node; 162 struct device_node *child; 163 struct resource *mem_r; 164 void __iomem *mem; 165 spinlock_t *lock; /* memory lock */ 166 unsigned long val; 167 u32 clk_div; 168 169 mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 170 if (!mem_r) 171 return -EINVAL; 172 173 mem = devm_ioremap_resource(dev, mem_r); 174 if (IS_ERR(mem)) 175 return PTR_ERR(mem); 176 177 lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL); 178 if (!lock) 179 return -ENOMEM; 180 181 spin_lock_init(lock); 182 183 val = bcm6358_led_busy(mem); 184 val &= ~(BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK); 185 if (of_property_read_bool(np, "brcm,clk-dat-low")) 186 val |= BCM6358_SLED_POLARITY; 187 of_property_read_u32(np, "brcm,clk-div", &clk_div); 188 switch (clk_div) { 189 case 8: 190 val |= BCM6358_SLED_CLKDIV_8; 191 break; 192 case 4: 193 val |= BCM6358_SLED_CLKDIV_4; 194 break; 195 case 2: 196 val |= BCM6358_SLED_CLKDIV_2; 197 break; 198 default: 199 val |= BCM6358_SLED_CLKDIV_1; 200 break; 201 } 202 bcm6358_led_write(mem + BCM6358_REG_CTRL, val); 203 204 for_each_available_child_of_node(np, child) { 205 int rc; 206 u32 reg; 207 208 if (of_property_read_u32(child, "reg", ®)) 209 continue; 210 211 if (reg >= BCM6358_SLED_MAX_COUNT) { 212 dev_err(dev, "invalid LED (%u >= %d)\n", reg, 213 BCM6358_SLED_MAX_COUNT); 214 continue; 215 } 216 217 rc = bcm6358_led(dev, child, reg, mem, lock); 218 if (rc < 0) 219 return rc; 220 } 221 222 return 0; 223 } 224 225 static const struct of_device_id bcm6358_leds_of_match[] = { 226 { .compatible = "brcm,bcm6358-leds", }, 227 { }, 228 }; 229 230 static struct platform_driver bcm6358_leds_driver = { 231 .probe = bcm6358_leds_probe, 232 .driver = { 233 .name = "leds-bcm6358", 234 .of_match_table = bcm6358_leds_of_match, 235 }, 236 }; 237 238 module_platform_driver(bcm6358_leds_driver); 239 240 MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>"); 241 MODULE_DESCRIPTION("LED driver for BCM6358 controllers"); 242 MODULE_LICENSE("GPL v2"); 243 MODULE_ALIAS("platform:leds-bcm6358"); 244