]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - drivers/leds/leds-bcm6358.c
Merge branch 'sched-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[karo-tx-linux.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         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", &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                         of_node_put(child);
220                         return rc;
221                 }
222         }
223
224         return 0;
225 }
226
227 static const struct of_device_id bcm6358_leds_of_match[] = {
228         { .compatible = "brcm,bcm6358-leds", },
229         { },
230 };
231 MODULE_DEVICE_TABLE(of, bcm6358_leds_of_match);
232
233 static struct platform_driver bcm6358_leds_driver = {
234         .probe = bcm6358_leds_probe,
235         .driver = {
236                 .name = "leds-bcm6358",
237                 .of_match_table = bcm6358_leds_of_match,
238         },
239 };
240
241 module_platform_driver(bcm6358_leds_driver);
242
243 MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
244 MODULE_DESCRIPTION("LED driver for BCM6358 controllers");
245 MODULE_LICENSE("GPL v2");
246 MODULE_ALIAS("platform:leds-bcm6358");