Commit | Line | Data |
---|---|---|
994c84ea DB |
1 | /* |
2 | * linux/arch/arm/plat-omap/debug-leds.c | |
3 | * | |
4 | * Copyright 2003 by Texas Instruments Incorporated | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2 as | |
8 | * published by the Free Software Foundation. | |
9 | */ | |
2f8163ba | 10 | #include <linux/gpio.h> |
994c84ea DB |
11 | #include <linux/init.h> |
12 | #include <linux/platform_device.h> | |
13 | #include <linux/leds.h> | |
fced80c7 | 14 | #include <linux/io.h> |
4b25408f | 15 | #include <linux/platform_data/gpio-omap.h> |
994c84ea | 16 | |
a09e64fb | 17 | #include <mach/hardware.h> |
994c84ea | 18 | #include <asm/leds.h> |
994c84ea DB |
19 | #include <asm/mach-types.h> |
20 | ||
ce491cf8 | 21 | #include <plat/fpga.h> |
994c84ea DB |
22 | |
23 | ||
24 | /* Many OMAP development platforms reuse the same "debug board"; these | |
25 | * platforms include H2, H3, H4, and Perseus2. There are 16 LEDs on the | |
26 | * debug board (all green), accessed through FPGA registers. | |
27 | * | |
28 | * The "surfer" expansion board and H2 sample board also have two-color | |
29 | * green+red LEDs (in parallel), used here for timer and idle indicators | |
30 | * in preference to the ones on the debug board, for a "Disco LED" effect. | |
31 | * | |
32 | * This driver exports either the original ARM LED API, the new generic | |
33 | * one, or both. | |
34 | */ | |
35 | ||
36 | static spinlock_t lock; | |
37 | static struct h2p2_dbg_fpga __iomem *fpga; | |
38 | static u16 led_state, hw_led_state; | |
39 | ||
40 | ||
3c8ed2a9 | 41 | #ifdef CONFIG_OMAP_DEBUG_LEDS |
994c84ea DB |
42 | #define new_led_api() 1 |
43 | #else | |
44 | #define new_led_api() 0 | |
45 | #endif | |
46 | ||
47 | ||
48 | /*-------------------------------------------------------------------------*/ | |
49 | ||
50 | /* original ARM debug LED API: | |
51 | * - timer and idle leds (some boards use non-FPGA leds here); | |
52 | * - up to 4 generic leds, easily accessed in-kernel (any context) | |
53 | */ | |
54 | ||
55 | #define GPIO_LED_RED 3 | |
56 | #define GPIO_LED_GREEN OMAP_MPUIO(4) | |
57 | ||
58 | #define LED_STATE_ENABLED 0x01 | |
59 | #define LED_STATE_CLAIMED 0x02 | |
60 | #define LED_TIMER_ON 0x04 | |
61 | ||
62 | #define GPIO_IDLE GPIO_LED_GREEN | |
63 | #define GPIO_TIMER GPIO_LED_RED | |
64 | ||
65 | static void h2p2_dbg_leds_event(led_event_t evt) | |
66 | { | |
67 | unsigned long flags; | |
68 | ||
69 | spin_lock_irqsave(&lock, flags); | |
70 | ||
71 | if (!(led_state & LED_STATE_ENABLED) && evt != led_start) | |
72 | goto done; | |
73 | ||
74 | switch (evt) { | |
75 | case led_start: | |
76 | if (fpga) | |
77 | led_state |= LED_STATE_ENABLED; | |
78 | break; | |
79 | ||
80 | case led_stop: | |
81 | case led_halted: | |
82 | /* all leds off during suspend or shutdown */ | |
83 | ||
84 | if (!(machine_is_omap_perseus2() || machine_is_omap_h4())) { | |
0b84b5ca DB |
85 | gpio_set_value(GPIO_TIMER, 0); |
86 | gpio_set_value(GPIO_IDLE, 0); | |
994c84ea DB |
87 | } |
88 | ||
89 | __raw_writew(~0, &fpga->leds); | |
90 | led_state &= ~LED_STATE_ENABLED; | |
91 | goto done; | |
92 | ||
93 | case led_claim: | |
94 | led_state |= LED_STATE_CLAIMED; | |
95 | hw_led_state = 0; | |
96 | break; | |
97 | ||
98 | case led_release: | |
99 | led_state &= ~LED_STATE_CLAIMED; | |
100 | break; | |
101 | ||
102 | #ifdef CONFIG_LEDS_TIMER | |
103 | case led_timer: | |
104 | led_state ^= LED_TIMER_ON; | |
105 | ||
106 | if (machine_is_omap_perseus2() || machine_is_omap_h4()) | |
107 | hw_led_state ^= H2P2_DBG_FPGA_P2_LED_TIMER; | |
108 | else { | |
0b84b5ca | 109 | gpio_set_value(GPIO_TIMER, |
994c84ea DB |
110 | led_state & LED_TIMER_ON); |
111 | goto done; | |
112 | } | |
113 | ||
114 | break; | |
115 | #endif | |
116 | ||
117 | #ifdef CONFIG_LEDS_CPU | |
118 | /* LED lit iff busy */ | |
119 | case led_idle_start: | |
120 | if (machine_is_omap_perseus2() || machine_is_omap_h4()) | |
121 | hw_led_state &= ~H2P2_DBG_FPGA_P2_LED_IDLE; | |
122 | else { | |
0b84b5ca | 123 | gpio_set_value(GPIO_IDLE, 1); |
994c84ea DB |
124 | goto done; |
125 | } | |
126 | ||
127 | break; | |
128 | ||
129 | case led_idle_end: | |
130 | if (machine_is_omap_perseus2() || machine_is_omap_h4()) | |
131 | hw_led_state |= H2P2_DBG_FPGA_P2_LED_IDLE; | |
132 | else { | |
0b84b5ca | 133 | gpio_set_value(GPIO_IDLE, 0); |
994c84ea DB |
134 | goto done; |
135 | } | |
136 | ||
137 | break; | |
138 | #endif | |
139 | ||
140 | case led_green_on: | |
141 | hw_led_state |= H2P2_DBG_FPGA_LED_GREEN; | |
142 | break; | |
143 | case led_green_off: | |
144 | hw_led_state &= ~H2P2_DBG_FPGA_LED_GREEN; | |
145 | break; | |
146 | ||
147 | case led_amber_on: | |
148 | hw_led_state |= H2P2_DBG_FPGA_LED_AMBER; | |
149 | break; | |
150 | case led_amber_off: | |
151 | hw_led_state &= ~H2P2_DBG_FPGA_LED_AMBER; | |
152 | break; | |
153 | ||
154 | case led_red_on: | |
155 | hw_led_state |= H2P2_DBG_FPGA_LED_RED; | |
156 | break; | |
157 | case led_red_off: | |
158 | hw_led_state &= ~H2P2_DBG_FPGA_LED_RED; | |
159 | break; | |
160 | ||
161 | case led_blue_on: | |
162 | hw_led_state |= H2P2_DBG_FPGA_LED_BLUE; | |
163 | break; | |
164 | case led_blue_off: | |
165 | hw_led_state &= ~H2P2_DBG_FPGA_LED_BLUE; | |
166 | break; | |
167 | ||
168 | default: | |
169 | break; | |
170 | } | |
171 | ||
172 | ||
173 | /* | |
174 | * Actually burn the LEDs | |
175 | */ | |
176 | if (led_state & LED_STATE_ENABLED) | |
177 | __raw_writew(~hw_led_state, &fpga->leds); | |
178 | ||
179 | done: | |
180 | spin_unlock_irqrestore(&lock, flags); | |
181 | } | |
182 | ||
183 | /*-------------------------------------------------------------------------*/ | |
184 | ||
185 | /* "new" LED API | |
186 | * - with syfs access and generic triggering | |
187 | * - not readily accessible to in-kernel drivers | |
188 | */ | |
189 | ||
190 | struct dbg_led { | |
191 | struct led_classdev cdev; | |
192 | u16 mask; | |
193 | }; | |
194 | ||
195 | static struct dbg_led dbg_leds[] = { | |
196 | /* REVISIT at least H2 uses different timer & cpu leds... */ | |
197 | #ifndef CONFIG_LEDS_TIMER | |
e0b50d3c DB |
198 | { .mask = 1 << 0, .cdev.name = "d4:green", |
199 | .cdev.default_trigger = "heartbeat", }, | |
994c84ea DB |
200 | #endif |
201 | #ifndef CONFIG_LEDS_CPU | |
202 | { .mask = 1 << 1, .cdev.name = "d5:green", }, /* !idle */ | |
203 | #endif | |
204 | { .mask = 1 << 2, .cdev.name = "d6:green", }, | |
205 | { .mask = 1 << 3, .cdev.name = "d7:green", }, | |
206 | ||
207 | { .mask = 1 << 4, .cdev.name = "d8:green", }, | |
208 | { .mask = 1 << 5, .cdev.name = "d9:green", }, | |
209 | { .mask = 1 << 6, .cdev.name = "d10:green", }, | |
210 | { .mask = 1 << 7, .cdev.name = "d11:green", }, | |
211 | ||
212 | { .mask = 1 << 8, .cdev.name = "d12:green", }, | |
213 | { .mask = 1 << 9, .cdev.name = "d13:green", }, | |
214 | { .mask = 1 << 10, .cdev.name = "d14:green", }, | |
215 | { .mask = 1 << 11, .cdev.name = "d15:green", }, | |
216 | ||
217 | #ifndef CONFIG_LEDS | |
218 | { .mask = 1 << 12, .cdev.name = "d16:green", }, | |
219 | { .mask = 1 << 13, .cdev.name = "d17:green", }, | |
220 | { .mask = 1 << 14, .cdev.name = "d18:green", }, | |
221 | { .mask = 1 << 15, .cdev.name = "d19:green", }, | |
222 | #endif | |
223 | }; | |
224 | ||
225 | static void | |
226 | fpga_led_set(struct led_classdev *cdev, enum led_brightness value) | |
227 | { | |
228 | struct dbg_led *led = container_of(cdev, struct dbg_led, cdev); | |
229 | unsigned long flags; | |
230 | ||
231 | spin_lock_irqsave(&lock, flags); | |
232 | if (value == LED_OFF) | |
233 | hw_led_state &= ~led->mask; | |
234 | else | |
235 | hw_led_state |= led->mask; | |
236 | __raw_writew(~hw_led_state, &fpga->leds); | |
237 | spin_unlock_irqrestore(&lock, flags); | |
238 | } | |
239 | ||
240 | static void __init newled_init(struct device *dev) | |
241 | { | |
242 | unsigned i; | |
243 | struct dbg_led *led; | |
244 | int status; | |
245 | ||
246 | for (i = 0, led = dbg_leds; i < ARRAY_SIZE(dbg_leds); i++, led++) { | |
247 | led->cdev.brightness_set = fpga_led_set; | |
248 | status = led_classdev_register(dev, &led->cdev); | |
249 | if (status < 0) | |
250 | break; | |
251 | } | |
252 | return; | |
253 | } | |
254 | ||
255 | ||
256 | /*-------------------------------------------------------------------------*/ | |
257 | ||
258 | static int /* __init */ fpga_probe(struct platform_device *pdev) | |
259 | { | |
260 | struct resource *iomem; | |
261 | ||
262 | spin_lock_init(&lock); | |
263 | ||
264 | iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
265 | if (!iomem) | |
266 | return -ENODEV; | |
267 | ||
268 | fpga = ioremap(iomem->start, H2P2_DBG_FPGA_SIZE); | |
269 | __raw_writew(~0, &fpga->leds); | |
270 | ||
e0b50d3c DB |
271 | #ifdef CONFIG_LEDS |
272 | leds_event = h2p2_dbg_leds_event; | |
273 | leds_event(led_start); | |
274 | #endif | |
994c84ea DB |
275 | |
276 | if (new_led_api()) { | |
277 | newled_init(&pdev->dev); | |
278 | } | |
279 | ||
280 | return 0; | |
281 | } | |
282 | ||
79ee031f | 283 | static int fpga_suspend_noirq(struct device *dev) |
994c84ea DB |
284 | { |
285 | __raw_writew(~0, &fpga->leds); | |
286 | return 0; | |
287 | } | |
288 | ||
79ee031f | 289 | static int fpga_resume_noirq(struct device *dev) |
994c84ea DB |
290 | { |
291 | __raw_writew(~hw_led_state, &fpga->leds); | |
292 | return 0; | |
293 | } | |
294 | ||
47145210 | 295 | static const struct dev_pm_ops fpga_dev_pm_ops = { |
79ee031f MD |
296 | .suspend_noirq = fpga_suspend_noirq, |
297 | .resume_noirq = fpga_resume_noirq, | |
298 | }; | |
994c84ea DB |
299 | |
300 | static struct platform_driver led_driver = { | |
301 | .driver.name = "omap_dbg_led", | |
79ee031f | 302 | .driver.pm = &fpga_dev_pm_ops, |
994c84ea | 303 | .probe = fpga_probe, |
994c84ea DB |
304 | }; |
305 | ||
306 | static int __init fpga_init(void) | |
307 | { | |
308 | if (machine_is_omap_h4() | |
309 | || machine_is_omap_h3() | |
310 | || machine_is_omap_h2() | |
311 | || machine_is_omap_perseus2() | |
312 | ) | |
313 | return platform_driver_register(&led_driver); | |
314 | return 0; | |
315 | } | |
316 | fs_initcall(fpga_init); |