diff options
author | David Brownell <dbrownell@users.sourceforge.net> | 2005-09-09 16:03:28 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2005-09-09 16:57:48 -0400 |
commit | f74e48a51c38f54fa26eb86a7a42f592156eccc2 (patch) | |
tree | c1b514520463170a51d2dc243664827425d7bc34 /drivers/pcmcia/omap_cf.c | |
parent | 71ed90d89eff51a1137cbef727f11b8f7d5b20f1 (diff) |
[PATCH] pcmcia: OMAP CF controller
This adds a socket driver for the OMAP CF controller; it's currently in use on
OSK boards.
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Dominik Brodowski <linux@dominikbrodowski.net>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'drivers/pcmcia/omap_cf.c')
-rw-r--r-- | drivers/pcmcia/omap_cf.c | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/drivers/pcmcia/omap_cf.c b/drivers/pcmcia/omap_cf.c new file mode 100644 index 000000000000..08d1c9288264 --- /dev/null +++ b/drivers/pcmcia/omap_cf.c | |||
@@ -0,0 +1,373 @@ | |||
1 | /* | ||
2 | * omap_cf.c -- OMAP 16xx CompactFlash controller driver | ||
3 | * | ||
4 | * Copyright (c) 2005 David Brownell | ||
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 as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/sched.h> | ||
15 | #include <linux/device.h> | ||
16 | #include <linux/errno.h> | ||
17 | #include <linux/init.h> | ||
18 | #include <linux/delay.h> | ||
19 | #include <linux/interrupt.h> | ||
20 | |||
21 | #include <pcmcia/ss.h> | ||
22 | |||
23 | #include <asm/hardware.h> | ||
24 | #include <asm/io.h> | ||
25 | #include <asm/mach-types.h> | ||
26 | #include <asm/sizes.h> | ||
27 | |||
28 | #include <asm/arch/mux.h> | ||
29 | #include <asm/arch/tc.h> | ||
30 | |||
31 | |||
32 | /* NOTE: don't expect this to support many I/O cards. The 16xx chips have | ||
33 | * hard-wired timings to support Compact Flash memory cards; they won't work | ||
34 | * with various other devices (like WLAN adapters) without some external | ||
35 | * logic to help out. | ||
36 | * | ||
37 | * NOTE: CF controller docs disagree with address space docs as to where | ||
38 | * CF_BASE really lives; this is a doc erratum. | ||
39 | */ | ||
40 | #define CF_BASE 0xfffe2800 | ||
41 | |||
42 | /* status; read after IRQ */ | ||
43 | #define CF_STATUS_REG __REG16(CF_BASE + 0x00) | ||
44 | # define CF_STATUS_BAD_READ (1 << 2) | ||
45 | # define CF_STATUS_BAD_WRITE (1 << 1) | ||
46 | # define CF_STATUS_CARD_DETECT (1 << 0) | ||
47 | |||
48 | /* which chipselect (CS0..CS3) is used for CF (active low) */ | ||
49 | #define CF_CFG_REG __REG16(CF_BASE + 0x02) | ||
50 | |||
51 | /* card reset */ | ||
52 | #define CF_CONTROL_REG __REG16(CF_BASE + 0x04) | ||
53 | # define CF_CONTROL_RESET (1 << 0) | ||
54 | |||
55 | #define omap_cf_present() (!(CF_STATUS_REG & CF_STATUS_CARD_DETECT)) | ||
56 | |||
57 | /*--------------------------------------------------------------------------*/ | ||
58 | |||
59 | static const char driver_name[] = "omap_cf"; | ||
60 | |||
61 | struct omap_cf_socket { | ||
62 | struct pcmcia_socket socket; | ||
63 | |||
64 | struct timer_list timer; | ||
65 | unsigned present:1; | ||
66 | unsigned active:1; | ||
67 | |||
68 | struct platform_device *pdev; | ||
69 | unsigned long phys_cf; | ||
70 | u_int irq; | ||
71 | }; | ||
72 | |||
73 | #define POLL_INTERVAL (2 * HZ) | ||
74 | |||
75 | #define SZ_2K (2 * SZ_1K) | ||
76 | |||
77 | /*--------------------------------------------------------------------------*/ | ||
78 | |||
79 | static int omap_cf_ss_init(struct pcmcia_socket *s) | ||
80 | { | ||
81 | return 0; | ||
82 | } | ||
83 | |||
84 | /* the timer is primarily to kick this socket's pccardd */ | ||
85 | static void omap_cf_timer(unsigned long _cf) | ||
86 | { | ||
87 | struct omap_cf_socket *cf = (void *) _cf; | ||
88 | unsigned present = omap_cf_present(); | ||
89 | |||
90 | if (present != cf->present) { | ||
91 | cf->present = present; | ||
92 | pr_debug("%s: card %s\n", driver_name, | ||
93 | present ? "present" : "gone"); | ||
94 | pcmcia_parse_events(&cf->socket, SS_DETECT); | ||
95 | } | ||
96 | |||
97 | if (cf->active) | ||
98 | mod_timer(&cf->timer, jiffies + POLL_INTERVAL); | ||
99 | } | ||
100 | |||
101 | /* This irq handler prevents "irqNNN: nobody cared" messages as drivers | ||
102 | * claim the card's IRQ. It may also detect some card insertions, but | ||
103 | * not removals; it can't always eliminate timer irqs. | ||
104 | */ | ||
105 | static irqreturn_t omap_cf_irq(int irq, void *_cf, struct pt_regs *r) | ||
106 | { | ||
107 | omap_cf_timer((unsigned long)_cf); | ||
108 | return IRQ_HANDLED; | ||
109 | } | ||
110 | |||
111 | static int omap_cf_get_status(struct pcmcia_socket *s, u_int *sp) | ||
112 | { | ||
113 | if (!sp) | ||
114 | return -EINVAL; | ||
115 | |||
116 | /* FIXME power management should probably be board-specific: | ||
117 | * - 3VCARD vs XVCARD (OSK only handles 3VCARD) | ||
118 | * - POWERON (switched on/off by set_socket) | ||
119 | */ | ||
120 | if (omap_cf_present()) { | ||
121 | struct omap_cf_socket *cf; | ||
122 | |||
123 | *sp = SS_READY | SS_DETECT | SS_POWERON | SS_3VCARD; | ||
124 | cf = container_of(s, struct omap_cf_socket, socket); | ||
125 | s->irq.AssignedIRQ = cf->irq; | ||
126 | } else | ||
127 | *sp = 0; | ||
128 | return 0; | ||
129 | } | ||
130 | |||
131 | static int | ||
132 | omap_cf_set_socket(struct pcmcia_socket *sock, struct socket_state_t *s) | ||
133 | { | ||
134 | u16 control; | ||
135 | |||
136 | /* FIXME some non-OSK boards will support power switching */ | ||
137 | switch (s->Vcc) { | ||
138 | case 0: | ||
139 | case 33: | ||
140 | break; | ||
141 | default: | ||
142 | return -EINVAL; | ||
143 | } | ||
144 | |||
145 | control = CF_CONTROL_REG; | ||
146 | if (s->flags & SS_RESET) | ||
147 | CF_CONTROL_REG = CF_CONTROL_RESET; | ||
148 | else | ||
149 | CF_CONTROL_REG = 0; | ||
150 | |||
151 | pr_debug("%s: Vcc %d, io_irq %d, flags %04x csc %04x\n", | ||
152 | driver_name, s->Vcc, s->io_irq, s->flags, s->csc_mask); | ||
153 | |||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | static int omap_cf_ss_suspend(struct pcmcia_socket *s) | ||
158 | { | ||
159 | pr_debug("%s: %s\n", driver_name, __FUNCTION__); | ||
160 | return omap_cf_set_socket(s, &dead_socket); | ||
161 | } | ||
162 | |||
163 | /* regions are 2K each: mem, attrib, io (and reserved-for-ide) */ | ||
164 | |||
165 | static int | ||
166 | omap_cf_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io) | ||
167 | { | ||
168 | struct omap_cf_socket *cf; | ||
169 | |||
170 | cf = container_of(s, struct omap_cf_socket, socket); | ||
171 | io->flags &= MAP_ACTIVE|MAP_ATTRIB|MAP_16BIT; | ||
172 | io->start = cf->phys_cf + SZ_4K; | ||
173 | io->stop = io->start + SZ_2K - 1; | ||
174 | return 0; | ||
175 | } | ||
176 | |||
177 | static int | ||
178 | omap_cf_set_mem_map(struct pcmcia_socket *s, struct pccard_mem_map *map) | ||
179 | { | ||
180 | struct omap_cf_socket *cf; | ||
181 | |||
182 | if (map->card_start) | ||
183 | return -EINVAL; | ||
184 | cf = container_of(s, struct omap_cf_socket, socket); | ||
185 | map->static_start = cf->phys_cf; | ||
186 | map->flags &= MAP_ACTIVE|MAP_ATTRIB|MAP_16BIT; | ||
187 | if (map->flags & MAP_ATTRIB) | ||
188 | map->static_start += SZ_2K; | ||
189 | return 0; | ||
190 | } | ||
191 | |||
192 | static struct pccard_operations omap_cf_ops = { | ||
193 | .init = omap_cf_ss_init, | ||
194 | .suspend = omap_cf_ss_suspend, | ||
195 | .get_status = omap_cf_get_status, | ||
196 | .set_socket = omap_cf_set_socket, | ||
197 | .set_io_map = omap_cf_set_io_map, | ||
198 | .set_mem_map = omap_cf_set_mem_map, | ||
199 | }; | ||
200 | |||
201 | /*--------------------------------------------------------------------------*/ | ||
202 | |||
203 | /* | ||
204 | * NOTE: right now the only board-specific platform_data is | ||
205 | * "what chipselect is used". Boards could want more. | ||
206 | */ | ||
207 | |||
208 | static int __init omap_cf_probe(struct device *dev) | ||
209 | { | ||
210 | unsigned seg; | ||
211 | struct omap_cf_socket *cf; | ||
212 | struct platform_device *pdev = to_platform_device(dev); | ||
213 | int irq; | ||
214 | int status; | ||
215 | |||
216 | seg = (int) dev->platform_data; | ||
217 | if (seg == 0 || seg > 3) | ||
218 | return -ENODEV; | ||
219 | |||
220 | /* either CFLASH.IREQ (INT_1610_CF) or some GPIO */ | ||
221 | irq = platform_get_irq(pdev, 0); | ||
222 | if (!irq) | ||
223 | return -EINVAL; | ||
224 | |||
225 | cf = kcalloc(1, sizeof *cf, GFP_KERNEL); | ||
226 | if (!cf) | ||
227 | return -ENOMEM; | ||
228 | init_timer(&cf->timer); | ||
229 | cf->timer.function = omap_cf_timer; | ||
230 | cf->timer.data = (unsigned long) cf; | ||
231 | |||
232 | cf->pdev = pdev; | ||
233 | dev_set_drvdata(dev, cf); | ||
234 | |||
235 | /* this primarily just shuts up irq handling noise */ | ||
236 | status = request_irq(irq, omap_cf_irq, SA_SHIRQ, | ||
237 | driver_name, cf); | ||
238 | if (status < 0) | ||
239 | goto fail0; | ||
240 | cf->irq = irq; | ||
241 | cf->socket.pci_irq = irq; | ||
242 | |||
243 | switch (seg) { | ||
244 | /* NOTE: CS0 could be configured too ... */ | ||
245 | case 1: | ||
246 | cf->phys_cf = OMAP_CS1_PHYS; | ||
247 | break; | ||
248 | case 2: | ||
249 | cf->phys_cf = OMAP_CS2_PHYS; | ||
250 | break; | ||
251 | case 3: | ||
252 | cf->phys_cf = omap_cs3_phys(); | ||
253 | break; | ||
254 | default: | ||
255 | goto fail1; | ||
256 | } | ||
257 | |||
258 | /* pcmcia layer only remaps "real" memory */ | ||
259 | cf->socket.io_offset = (unsigned long) | ||
260 | ioremap(cf->phys_cf + SZ_4K, SZ_2K); | ||
261 | if (!cf->socket.io_offset) | ||
262 | goto fail1; | ||
263 | |||
264 | if (!request_mem_region(cf->phys_cf, SZ_8K, driver_name)) | ||
265 | goto fail1; | ||
266 | |||
267 | /* NOTE: CF conflicts with MMC1 */ | ||
268 | omap_cfg_reg(W11_1610_CF_CD1); | ||
269 | omap_cfg_reg(P11_1610_CF_CD2); | ||
270 | omap_cfg_reg(R11_1610_CF_IOIS16); | ||
271 | omap_cfg_reg(V10_1610_CF_IREQ); | ||
272 | omap_cfg_reg(W10_1610_CF_RESET); | ||
273 | |||
274 | CF_CFG_REG = ~(1 << seg); | ||
275 | |||
276 | pr_info("%s: cs%d on irq %d\n", driver_name, seg, irq); | ||
277 | |||
278 | /* NOTE: better EMIFS setup might support more cards; but the | ||
279 | * TRM only shows how to affect regular flash signals, not their | ||
280 | * CF/PCMCIA variants... | ||
281 | */ | ||
282 | pr_debug("%s: cs%d, previous ccs %08x acs %08x\n", driver_name, | ||
283 | seg, EMIFS_CCS(seg), EMIFS_ACS(seg)); | ||
284 | EMIFS_CCS(seg) = 0x0004a1b3; /* synch mode 4 etc */ | ||
285 | EMIFS_ACS(seg) = 0x00000000; /* OE hold/setup */ | ||
286 | |||
287 | /* CF uses armxor_ck, which is "always" available */ | ||
288 | |||
289 | pr_debug("%s: sts %04x cfg %04x control %04x %s\n", driver_name, | ||
290 | CF_STATUS_REG, CF_CFG_REG, CF_CONTROL_REG, | ||
291 | omap_cf_present() ? "present" : "(not present)"); | ||
292 | |||
293 | cf->socket.owner = THIS_MODULE; | ||
294 | cf->socket.dev.dev = dev; | ||
295 | cf->socket.ops = &omap_cf_ops; | ||
296 | cf->socket.resource_ops = &pccard_static_ops; | ||
297 | cf->socket.features = SS_CAP_PCCARD | SS_CAP_STATIC_MAP | ||
298 | | SS_CAP_MEM_ALIGN; | ||
299 | cf->socket.map_size = SZ_2K; | ||
300 | |||
301 | status = pcmcia_register_socket(&cf->socket); | ||
302 | if (status < 0) | ||
303 | goto fail2; | ||
304 | |||
305 | cf->active = 1; | ||
306 | mod_timer(&cf->timer, jiffies + POLL_INTERVAL); | ||
307 | return 0; | ||
308 | |||
309 | fail2: | ||
310 | iounmap((void __iomem *) cf->socket.io_offset); | ||
311 | release_mem_region(cf->phys_cf, SZ_8K); | ||
312 | fail1: | ||
313 | free_irq(irq, cf); | ||
314 | fail0: | ||
315 | kfree(cf); | ||
316 | return status; | ||
317 | } | ||
318 | |||
319 | static int __devexit omap_cf_remove(struct device *dev) | ||
320 | { | ||
321 | struct omap_cf_socket *cf = dev_get_drvdata(dev); | ||
322 | |||
323 | cf->active = 0; | ||
324 | pcmcia_unregister_socket(&cf->socket); | ||
325 | del_timer_sync(&cf->timer); | ||
326 | iounmap((void __iomem *) cf->socket.io_offset); | ||
327 | release_mem_region(cf->phys_cf, SZ_8K); | ||
328 | free_irq(cf->irq, cf); | ||
329 | kfree(cf); | ||
330 | return 0; | ||
331 | } | ||
332 | |||
333 | static int omap_cf_suspend(struct device *dev, pm_message_t mesg, u32 level) | ||
334 | { | ||
335 | if (level != SUSPEND_SAVE_STATE) | ||
336 | return 0; | ||
337 | return pcmcia_socket_dev_suspend(dev, mesg); | ||
338 | } | ||
339 | |||
340 | static int omap_cf_resume(struct device *dev, u32 level) | ||
341 | { | ||
342 | if (level != RESUME_RESTORE_STATE) | ||
343 | return 0; | ||
344 | return pcmcia_socket_dev_resume(dev); | ||
345 | } | ||
346 | |||
347 | static struct device_driver omap_cf_driver = { | ||
348 | .name = (char *) driver_name, | ||
349 | .bus = &platform_bus_type, | ||
350 | .probe = omap_cf_probe, | ||
351 | .remove = __devexit_p(omap_cf_remove), | ||
352 | .suspend = omap_cf_suspend, | ||
353 | .resume = omap_cf_resume, | ||
354 | }; | ||
355 | |||
356 | static int __init omap_cf_init(void) | ||
357 | { | ||
358 | if (cpu_is_omap16xx()) | ||
359 | driver_register(&omap_cf_driver); | ||
360 | return 0; | ||
361 | } | ||
362 | |||
363 | static void __exit omap_cf_exit(void) | ||
364 | { | ||
365 | if (cpu_is_omap16xx()) | ||
366 | driver_unregister(&omap_cf_driver); | ||
367 | } | ||
368 | |||
369 | module_init(omap_cf_init); | ||
370 | module_exit(omap_cf_exit); | ||
371 | |||
372 | MODULE_DESCRIPTION("OMAP CF Driver"); | ||
373 | MODULE_LICENSE("GPL"); | ||