diff options
Diffstat (limited to 'drivers/pcmcia/bfin_cf_pcmcia.c')
-rw-r--r-- | drivers/pcmcia/bfin_cf_pcmcia.c | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/drivers/pcmcia/bfin_cf_pcmcia.c b/drivers/pcmcia/bfin_cf_pcmcia.c new file mode 100644 index 000000000000..bb7338863fb9 --- /dev/null +++ b/drivers/pcmcia/bfin_cf_pcmcia.c | |||
@@ -0,0 +1,339 @@ | |||
1 | /* | ||
2 | * file: drivers/pcmcia/bfin_cf.c | ||
3 | * | ||
4 | * based on: drivers/pcmcia/omap_cf.c | ||
5 | * omap_cf.c -- OMAP 16xx CompactFlash controller driver | ||
6 | * | ||
7 | * Copyright (c) 2005 David Brownell | ||
8 | * Copyright (c) 2006-2008 Michael Hennerich Analog Devices Inc. | ||
9 | * | ||
10 | * bugs: enter bugs at http://blackfin.uclinux.org/ | ||
11 | * | ||
12 | * this program is free software; you can redistribute it and/or modify | ||
13 | * it under the terms of the gnu general public license as published by | ||
14 | * the free software foundation; either version 2, or (at your option) | ||
15 | * any later version. | ||
16 | * | ||
17 | * this program is distributed in the hope that it will be useful, | ||
18 | * but without any warranty; without even the implied warranty of | ||
19 | * merchantability or fitness for a particular purpose. see the | ||
20 | * gnu general public license for more details. | ||
21 | * | ||
22 | * you should have received a copy of the gnu general public license | ||
23 | * along with this program; see the file copying. | ||
24 | * if not, write to the free software foundation, | ||
25 | * 59 temple place - suite 330, boston, ma 02111-1307, usa. | ||
26 | */ | ||
27 | |||
28 | #include <linux/module.h> | ||
29 | #include <linux/kernel.h> | ||
30 | #include <linux/sched.h> | ||
31 | #include <linux/platform_device.h> | ||
32 | #include <linux/errno.h> | ||
33 | #include <linux/init.h> | ||
34 | #include <linux/delay.h> | ||
35 | #include <linux/interrupt.h> | ||
36 | #include <linux/irq.h> | ||
37 | #include <linux/io.h> | ||
38 | |||
39 | #include <pcmcia/ss.h> | ||
40 | #include <pcmcia/cisreg.h> | ||
41 | #include <asm/gpio.h> | ||
42 | |||
43 | #define SZ_1K 0x00000400 | ||
44 | #define SZ_8K 0x00002000 | ||
45 | #define SZ_2K (2 * SZ_1K) | ||
46 | |||
47 | #define POLL_INTERVAL (2 * HZ) | ||
48 | |||
49 | #define CF_ATASEL_ENA 0x20311802 /* Inverts RESET */ | ||
50 | #define CF_ATASEL_DIS 0x20311800 | ||
51 | |||
52 | #define bfin_cf_present(pfx) (gpio_get_value(pfx)) | ||
53 | |||
54 | /*--------------------------------------------------------------------------*/ | ||
55 | |||
56 | static const char driver_name[] = "bfin_cf_pcmcia"; | ||
57 | |||
58 | struct bfin_cf_socket { | ||
59 | struct pcmcia_socket socket; | ||
60 | |||
61 | struct timer_list timer; | ||
62 | unsigned present:1; | ||
63 | unsigned active:1; | ||
64 | |||
65 | struct platform_device *pdev; | ||
66 | unsigned long phys_cf_io; | ||
67 | unsigned long phys_cf_attr; | ||
68 | u_int irq; | ||
69 | u_short cd_pfx; | ||
70 | }; | ||
71 | |||
72 | /*--------------------------------------------------------------------------*/ | ||
73 | static int bfin_cf_reset(void) | ||
74 | { | ||
75 | outw(0, CF_ATASEL_ENA); | ||
76 | mdelay(200); | ||
77 | outw(0, CF_ATASEL_DIS); | ||
78 | |||
79 | return 0; | ||
80 | } | ||
81 | |||
82 | static int bfin_cf_ss_init(struct pcmcia_socket *s) | ||
83 | { | ||
84 | return 0; | ||
85 | } | ||
86 | |||
87 | /* the timer is primarily to kick this socket's pccardd */ | ||
88 | static void bfin_cf_timer(unsigned long _cf) | ||
89 | { | ||
90 | struct bfin_cf_socket *cf = (void *)_cf; | ||
91 | unsigned short present = bfin_cf_present(cf->cd_pfx); | ||
92 | |||
93 | if (present != cf->present) { | ||
94 | cf->present = present; | ||
95 | dev_dbg(&cf->pdev->dev, ": card %s\n", | ||
96 | present ? "present" : "gone"); | ||
97 | pcmcia_parse_events(&cf->socket, SS_DETECT); | ||
98 | } | ||
99 | |||
100 | if (cf->active) | ||
101 | mod_timer(&cf->timer, jiffies + POLL_INTERVAL); | ||
102 | } | ||
103 | |||
104 | static int bfin_cf_get_status(struct pcmcia_socket *s, u_int *sp) | ||
105 | { | ||
106 | struct bfin_cf_socket *cf; | ||
107 | |||
108 | if (!sp) | ||
109 | return -EINVAL; | ||
110 | |||
111 | cf = container_of(s, struct bfin_cf_socket, socket); | ||
112 | |||
113 | if (bfin_cf_present(cf->cd_pfx)) { | ||
114 | *sp = SS_READY | SS_DETECT | SS_POWERON | SS_3VCARD; | ||
115 | s->irq.AssignedIRQ = 0; | ||
116 | s->pci_irq = cf->irq; | ||
117 | |||
118 | } else | ||
119 | *sp = 0; | ||
120 | return 0; | ||
121 | } | ||
122 | |||
123 | static int | ||
124 | bfin_cf_set_socket(struct pcmcia_socket *sock, struct socket_state_t *s) | ||
125 | { | ||
126 | |||
127 | struct bfin_cf_socket *cf; | ||
128 | cf = container_of(sock, struct bfin_cf_socket, socket); | ||
129 | |||
130 | switch (s->Vcc) { | ||
131 | case 0: | ||
132 | case 33: | ||
133 | break; | ||
134 | case 50: | ||
135 | break; | ||
136 | default: | ||
137 | return -EINVAL; | ||
138 | } | ||
139 | |||
140 | if (s->flags & SS_RESET) { | ||
141 | disable_irq(cf->irq); | ||
142 | bfin_cf_reset(); | ||
143 | enable_irq(cf->irq); | ||
144 | } | ||
145 | |||
146 | dev_dbg(&cf->pdev->dev, ": Vcc %d, io_irq %d, flags %04x csc %04x\n", | ||
147 | s->Vcc, s->io_irq, s->flags, s->csc_mask); | ||
148 | |||
149 | return 0; | ||
150 | } | ||
151 | |||
152 | static int bfin_cf_ss_suspend(struct pcmcia_socket *s) | ||
153 | { | ||
154 | return bfin_cf_set_socket(s, &dead_socket); | ||
155 | } | ||
156 | |||
157 | /* regions are 2K each: mem, attrib, io (and reserved-for-ide) */ | ||
158 | |||
159 | static int bfin_cf_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io) | ||
160 | { | ||
161 | struct bfin_cf_socket *cf; | ||
162 | |||
163 | cf = container_of(s, struct bfin_cf_socket, socket); | ||
164 | io->flags &= MAP_ACTIVE | MAP_ATTRIB | MAP_16BIT; | ||
165 | io->start = cf->phys_cf_io; | ||
166 | io->stop = io->start + SZ_2K - 1; | ||
167 | return 0; | ||
168 | } | ||
169 | |||
170 | static int | ||
171 | bfin_cf_set_mem_map(struct pcmcia_socket *s, struct pccard_mem_map *map) | ||
172 | { | ||
173 | struct bfin_cf_socket *cf; | ||
174 | |||
175 | if (map->card_start) | ||
176 | return -EINVAL; | ||
177 | cf = container_of(s, struct bfin_cf_socket, socket); | ||
178 | map->static_start = cf->phys_cf_io; | ||
179 | map->flags &= MAP_ACTIVE | MAP_ATTRIB | MAP_16BIT; | ||
180 | if (map->flags & MAP_ATTRIB) | ||
181 | map->static_start = cf->phys_cf_attr; | ||
182 | |||
183 | return 0; | ||
184 | } | ||
185 | |||
186 | static struct pccard_operations bfin_cf_ops = { | ||
187 | .init = bfin_cf_ss_init, | ||
188 | .suspend = bfin_cf_ss_suspend, | ||
189 | .get_status = bfin_cf_get_status, | ||
190 | .set_socket = bfin_cf_set_socket, | ||
191 | .set_io_map = bfin_cf_set_io_map, | ||
192 | .set_mem_map = bfin_cf_set_mem_map, | ||
193 | }; | ||
194 | |||
195 | /*--------------------------------------------------------------------------*/ | ||
196 | |||
197 | static int __devinit bfin_cf_probe(struct platform_device *pdev) | ||
198 | { | ||
199 | struct bfin_cf_socket *cf; | ||
200 | struct resource *io_mem, *attr_mem; | ||
201 | int irq; | ||
202 | unsigned short cd_pfx; | ||
203 | int status = 0; | ||
204 | |||
205 | dev_info(&pdev->dev, "Blackfin CompactFlash/PCMCIA Socket Driver\n"); | ||
206 | |||
207 | irq = platform_get_irq(pdev, 0); | ||
208 | if (!irq) | ||
209 | return -EINVAL; | ||
210 | |||
211 | cd_pfx = platform_get_irq(pdev, 1); /*Card Detect GPIO PIN */ | ||
212 | |||
213 | if (gpio_request(cd_pfx, "pcmcia: CD")) { | ||
214 | dev_err(&pdev->dev, | ||
215 | "Failed ro request Card Detect GPIO_%d\n", | ||
216 | cd_pfx); | ||
217 | return -EBUSY; | ||
218 | } | ||
219 | gpio_direction_input(cd_pfx); | ||
220 | |||
221 | cf = kzalloc(sizeof *cf, GFP_KERNEL); | ||
222 | if (!cf) { | ||
223 | gpio_free(cd_pfx); | ||
224 | return -ENOMEM; | ||
225 | } | ||
226 | |||
227 | cf->cd_pfx = cd_pfx; | ||
228 | |||
229 | setup_timer(&cf->timer, bfin_cf_timer, (unsigned long)cf); | ||
230 | |||
231 | cf->pdev = pdev; | ||
232 | platform_set_drvdata(pdev, cf); | ||
233 | |||
234 | cf->irq = irq; | ||
235 | cf->socket.pci_irq = irq; | ||
236 | |||
237 | set_irq_type(irq, IRQF_TRIGGER_LOW); | ||
238 | |||
239 | io_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
240 | attr_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); | ||
241 | |||
242 | if (!io_mem || !attr_mem) | ||
243 | goto fail0; | ||
244 | |||
245 | cf->phys_cf_io = io_mem->start; | ||
246 | cf->phys_cf_attr = attr_mem->start; | ||
247 | |||
248 | /* pcmcia layer only remaps "real" memory */ | ||
249 | cf->socket.io_offset = (unsigned long) | ||
250 | ioremap(cf->phys_cf_io, SZ_2K); | ||
251 | |||
252 | if (!cf->socket.io_offset) | ||
253 | goto fail0; | ||
254 | |||
255 | dev_err(&pdev->dev, ": on irq %d\n", irq); | ||
256 | |||
257 | dev_dbg(&pdev->dev, ": %s\n", | ||
258 | bfin_cf_present(cf->cd_pfx) ? "present" : "(not present)"); | ||
259 | |||
260 | cf->socket.owner = THIS_MODULE; | ||
261 | cf->socket.dev.parent = &pdev->dev; | ||
262 | cf->socket.ops = &bfin_cf_ops; | ||
263 | cf->socket.resource_ops = &pccard_static_ops; | ||
264 | cf->socket.features = SS_CAP_PCCARD | SS_CAP_STATIC_MAP | ||
265 | | SS_CAP_MEM_ALIGN; | ||
266 | cf->socket.map_size = SZ_2K; | ||
267 | |||
268 | status = pcmcia_register_socket(&cf->socket); | ||
269 | if (status < 0) | ||
270 | goto fail2; | ||
271 | |||
272 | cf->active = 1; | ||
273 | mod_timer(&cf->timer, jiffies + POLL_INTERVAL); | ||
274 | return 0; | ||
275 | |||
276 | fail2: | ||
277 | iounmap((void __iomem *)cf->socket.io_offset); | ||
278 | release_mem_region(cf->phys_cf_io, SZ_8K); | ||
279 | |||
280 | fail0: | ||
281 | gpio_free(cf->cd_pfx); | ||
282 | kfree(cf); | ||
283 | platform_set_drvdata(pdev, NULL); | ||
284 | |||
285 | return status; | ||
286 | } | ||
287 | |||
288 | static int __devexit bfin_cf_remove(struct platform_device *pdev) | ||
289 | { | ||
290 | struct bfin_cf_socket *cf = platform_get_drvdata(pdev); | ||
291 | |||
292 | gpio_free(cf->cd_pfx); | ||
293 | cf->active = 0; | ||
294 | pcmcia_unregister_socket(&cf->socket); | ||
295 | del_timer_sync(&cf->timer); | ||
296 | iounmap((void __iomem *)cf->socket.io_offset); | ||
297 | release_mem_region(cf->phys_cf_io, SZ_8K); | ||
298 | platform_set_drvdata(pdev, NULL); | ||
299 | kfree(cf); | ||
300 | return 0; | ||
301 | } | ||
302 | |||
303 | static int bfin_cf_suspend(struct platform_device *pdev, pm_message_t mesg) | ||
304 | { | ||
305 | return pcmcia_socket_dev_suspend(&pdev->dev, mesg); | ||
306 | } | ||
307 | |||
308 | static int bfin_cf_resume(struct platform_device *pdev) | ||
309 | { | ||
310 | return pcmcia_socket_dev_resume(&pdev->dev); | ||
311 | } | ||
312 | |||
313 | static struct platform_driver bfin_cf_driver = { | ||
314 | .driver = { | ||
315 | .name = (char *)driver_name, | ||
316 | .owner = THIS_MODULE, | ||
317 | }, | ||
318 | .probe = bfin_cf_probe, | ||
319 | .remove = __devexit_p(bfin_cf_remove), | ||
320 | .suspend = bfin_cf_suspend, | ||
321 | .resume = bfin_cf_resume, | ||
322 | }; | ||
323 | |||
324 | static int __init bfin_cf_init(void) | ||
325 | { | ||
326 | return platform_driver_register(&bfin_cf_driver); | ||
327 | } | ||
328 | |||
329 | static void __exit bfin_cf_exit(void) | ||
330 | { | ||
331 | platform_driver_unregister(&bfin_cf_driver); | ||
332 | } | ||
333 | |||
334 | module_init(bfin_cf_init); | ||
335 | module_exit(bfin_cf_exit); | ||
336 | |||
337 | MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>") | ||
338 | MODULE_DESCRIPTION("BFIN CF/PCMCIA Driver"); | ||
339 | MODULE_LICENSE("GPL"); | ||