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