]>
Commit | Line | Data |
---|---|---|
589fca16 ÁFR |
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 | }; | |
01736f07 | 229 | MODULE_DEVICE_TABLE(of, bcm6358_leds_of_match); |
589fca16 ÁFR |
230 | |
231 | static struct platform_driver bcm6358_leds_driver = { | |
232 | .probe = bcm6358_leds_probe, | |
233 | .driver = { | |
234 | .name = "leds-bcm6358", | |
235 | .of_match_table = bcm6358_leds_of_match, | |
236 | }, | |
237 | }; | |
238 | ||
239 | module_platform_driver(bcm6358_leds_driver); | |
240 | ||
241 | MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>"); | |
242 | MODULE_DESCRIPTION("LED driver for BCM6358 controllers"); | |
243 | MODULE_LICENSE("GPL v2"); | |
244 | MODULE_ALIAS("platform:leds-bcm6358"); |