Merge tag 'fscrypt_for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso...
[sfrench/cifs-2.6.git] / drivers / leds / leds-bcm6358.c
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", &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");