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 | { | |
4ba113b6 | 52 | #ifdef CONFIG_CPU_BIG_ENDIAN |
589fca16 | 53 | iowrite32be(data, reg); |
4ba113b6 ÁFR |
54 | #else |
55 | writel(data, reg); | |
56 | #endif | |
589fca16 ÁFR |
57 | } |
58 | ||
59 | static unsigned long bcm6358_led_read(void __iomem *reg) | |
60 | { | |
4ba113b6 | 61 | #ifdef CONFIG_CPU_BIG_ENDIAN |
589fca16 | 62 | return ioread32be(reg); |
4ba113b6 ÁFR |
63 | #else |
64 | return readl(reg); | |
65 | #endif | |
589fca16 ÁFR |
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 | ||
6e636a0a ÁFR |
79 | static void bcm6358_led_set(struct led_classdev *led_cdev, |
80 | enum led_brightness value) | |
589fca16 | 81 | { |
6e636a0a ÁFR |
82 | struct bcm6358_led *led = |
83 | container_of(led_cdev, struct bcm6358_led, cdev); | |
84 | unsigned long flags, val; | |
589fca16 | 85 | |
6e636a0a | 86 | spin_lock_irqsave(led->lock, flags); |
589fca16 | 87 | bcm6358_led_busy(led->mem); |
589fca16 ÁFR |
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); | |
589fca16 ÁFR |
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; | |
589fca16 ÁFR |
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 | ||
589fca16 ÁFR |
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; | |
589fca16 ÁFR |
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 | } | |
589fca16 | 139 | |
42273caa ÁFR |
140 | bcm6358_led_set(&led->cdev, led->cdev.brightness); |
141 | ||
589fca16 ÁFR |
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); | |
4b6ba5e2 JL |
213 | if (rc < 0) { |
214 | of_node_put(child); | |
589fca16 | 215 | return rc; |
4b6ba5e2 | 216 | } |
589fca16 ÁFR |
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 | }; | |
01736f07 | 226 | MODULE_DEVICE_TABLE(of, bcm6358_leds_of_match); |
589fca16 ÁFR |
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"); |