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 #ifdef CONFIG_CPU_BIG_ENDIAN 53 iowrite32be(data, reg); 54 #else 55 writel(data, reg); 56 #endif 57 } 58 59 static unsigned long bcm6358_led_read(void __iomem *reg) 60 { 61 #ifdef CONFIG_CPU_BIG_ENDIAN 62 return ioread32be(reg); 63 #else 64 return readl(reg); 65 #endif 66 } 67 68 static unsigned long bcm6358_led_busy(void __iomem *mem) 69 { 70 unsigned long val; 71 72 while ((val = bcm6358_led_read(mem + BCM6358_REG_CTRL)) & 73 BCM6358_SLED_BUSY) 74 udelay(BCM6358_SLED_WAIT); 75 76 return val; 77 } 78 79 static void bcm6358_led_set(struct led_classdev *led_cdev, 80 enum led_brightness value) 81 { 82 struct bcm6358_led *led = 83 container_of(led_cdev, struct bcm6358_led, cdev); 84 unsigned long flags, val; 85 86 spin_lock_irqsave(led->lock, flags); 87 bcm6358_led_busy(led->mem); 88 val = bcm6358_led_read(led->mem + BCM6358_REG_MODE); 89 if ((led->active_low && value == LED_OFF) || 90 (!led->active_low && value != LED_OFF)) 91 val |= BIT(led->pin); 92 else 93 val &= ~(BIT(led->pin)); 94 bcm6358_led_write(led->mem + BCM6358_REG_MODE, val); 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 const char *state; 103 int rc; 104 105 led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); 106 if (!led) 107 return -ENOMEM; 108 109 led->pin = reg; 110 led->mem = mem; 111 led->lock = lock; 112 113 if (of_property_read_bool(nc, "active-low")) 114 led->active_low = true; 115 116 led->cdev.name = of_get_property(nc, "label", NULL) ? : nc->name; 117 led->cdev.default_trigger = of_get_property(nc, 118 "linux,default-trigger", 119 NULL); 120 121 if (!of_property_read_string(nc, "default-state", &state)) { 122 if (!strcmp(state, "on")) { 123 led->cdev.brightness = LED_FULL; 124 } else if (!strcmp(state, "keep")) { 125 unsigned long val; 126 val = bcm6358_led_read(led->mem + BCM6358_REG_MODE); 127 val &= BIT(led->pin); 128 if ((led->active_low && !val) || 129 (!led->active_low && val)) 130 led->cdev.brightness = LED_FULL; 131 else 132 led->cdev.brightness = LED_OFF; 133 } else { 134 led->cdev.brightness = LED_OFF; 135 } 136 } else { 137 led->cdev.brightness = LED_OFF; 138 } 139 140 bcm6358_led_set(&led->cdev, led->cdev.brightness); 141 142 led->cdev.brightness_set = bcm6358_led_set; 143 144 rc = led_classdev_register(dev, &led->cdev); 145 if (rc < 0) 146 return rc; 147 148 dev_dbg(dev, "registered LED %s\n", led->cdev.name); 149 150 return 0; 151 } 152 153 static int bcm6358_leds_probe(struct platform_device *pdev) 154 { 155 struct device *dev = &pdev->dev; 156 struct device_node *np = pdev->dev.of_node; 157 struct device_node *child; 158 struct resource *mem_r; 159 void __iomem *mem; 160 spinlock_t *lock; /* memory lock */ 161 unsigned long val; 162 u32 clk_div; 163 164 mem_r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 165 if (!mem_r) 166 return -EINVAL; 167 168 mem = devm_ioremap_resource(dev, mem_r); 169 if (IS_ERR(mem)) 170 return PTR_ERR(mem); 171 172 lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL); 173 if (!lock) 174 return -ENOMEM; 175 176 spin_lock_init(lock); 177 178 val = bcm6358_led_busy(mem); 179 val &= ~(BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK); 180 if (of_property_read_bool(np, "brcm,clk-dat-low")) 181 val |= BCM6358_SLED_POLARITY; 182 of_property_read_u32(np, "brcm,clk-div", &clk_div); 183 switch (clk_div) { 184 case 8: 185 val |= BCM6358_SLED_CLKDIV_8; 186 break; 187 case 4: 188 val |= BCM6358_SLED_CLKDIV_4; 189 break; 190 case 2: 191 val |= BCM6358_SLED_CLKDIV_2; 192 break; 193 default: 194 val |= BCM6358_SLED_CLKDIV_1; 195 break; 196 } 197 bcm6358_led_write(mem + BCM6358_REG_CTRL, val); 198 199 for_each_available_child_of_node(np, child) { 200 int rc; 201 u32 reg; 202 203 if (of_property_read_u32(child, "reg", ®)) 204 continue; 205 206 if (reg >= BCM6358_SLED_MAX_COUNT) { 207 dev_err(dev, "invalid LED (%u >= %d)\n", reg, 208 BCM6358_SLED_MAX_COUNT); 209 continue; 210 } 211 212 rc = bcm6358_led(dev, child, reg, mem, lock); 213 if (rc < 0) { 214 of_node_put(child); 215 return rc; 216 } 217 } 218 219 return 0; 220 } 221 222 static const struct of_device_id bcm6358_leds_of_match[] = { 223 { .compatible = "brcm,bcm6358-leds", }, 224 { }, 225 }; 226 MODULE_DEVICE_TABLE(of, bcm6358_leds_of_match); 227 228 static struct platform_driver bcm6358_leds_driver = { 229 .probe = bcm6358_leds_probe, 230 .driver = { 231 .name = "leds-bcm6358", 232 .of_match_table = bcm6358_leds_of_match, 233 }, 234 }; 235 236 module_platform_driver(bcm6358_leds_driver); 237 238 MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>"); 239 MODULE_DESCRIPTION("LED driver for BCM6358 controllers"); 240 MODULE_LICENSE("GPL v2"); 241 MODULE_ALIAS("platform:leds-bcm6358"); 242