diff options
Diffstat (limited to 'drivers/misc/tifm_7xx1.c')
-rw-r--r-- | drivers/misc/tifm_7xx1.c | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/drivers/misc/tifm_7xx1.c b/drivers/misc/tifm_7xx1.c new file mode 100644 index 000000000000..a7ed30446185 --- /dev/null +++ b/drivers/misc/tifm_7xx1.c | |||
@@ -0,0 +1,437 @@ | |||
1 | /* | ||
2 | * tifm_7xx1.c - TI FlashMedia driver | ||
3 | * | ||
4 | * Copyright (C) 2006 Alex Dubov <oakad@yahoo.com> | ||
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 | |||
12 | #include <linux/tifm.h> | ||
13 | #include <linux/dma-mapping.h> | ||
14 | |||
15 | #define DRIVER_NAME "tifm_7xx1" | ||
16 | #define DRIVER_VERSION "0.6" | ||
17 | |||
18 | static void tifm_7xx1_eject(struct tifm_adapter *fm, struct tifm_dev *sock) | ||
19 | { | ||
20 | int cnt; | ||
21 | unsigned long flags; | ||
22 | |||
23 | spin_lock_irqsave(&fm->lock, flags); | ||
24 | if (!fm->inhibit_new_cards) { | ||
25 | for (cnt = 0; cnt < fm->max_sockets; cnt++) { | ||
26 | if (fm->sockets[cnt] == sock) { | ||
27 | fm->remove_mask |= (1 << cnt); | ||
28 | queue_work(fm->wq, &fm->media_remover); | ||
29 | break; | ||
30 | } | ||
31 | } | ||
32 | } | ||
33 | spin_unlock_irqrestore(&fm->lock, flags); | ||
34 | } | ||
35 | |||
36 | static void tifm_7xx1_remove_media(void *adapter) | ||
37 | { | ||
38 | struct tifm_adapter *fm = adapter; | ||
39 | unsigned long flags; | ||
40 | int cnt; | ||
41 | struct tifm_dev *sock; | ||
42 | |||
43 | if (!class_device_get(&fm->cdev)) | ||
44 | return; | ||
45 | spin_lock_irqsave(&fm->lock, flags); | ||
46 | for (cnt = 0; cnt < fm->max_sockets; cnt++) { | ||
47 | if (fm->sockets[cnt] && (fm->remove_mask & (1 << cnt))) { | ||
48 | printk(KERN_INFO DRIVER_NAME | ||
49 | ": demand removing card from socket %d\n", cnt); | ||
50 | sock = fm->sockets[cnt]; | ||
51 | fm->sockets[cnt] = 0; | ||
52 | fm->remove_mask &= ~(1 << cnt); | ||
53 | |||
54 | writel(0x0e00, sock->addr + SOCK_CONTROL); | ||
55 | |||
56 | writel((TIFM_IRQ_FIFOMASK | TIFM_IRQ_CARDMASK) << cnt, | ||
57 | fm->addr + FM_CLEAR_INTERRUPT_ENABLE); | ||
58 | writel((TIFM_IRQ_FIFOMASK | TIFM_IRQ_CARDMASK) << cnt, | ||
59 | fm->addr + FM_SET_INTERRUPT_ENABLE); | ||
60 | |||
61 | spin_unlock_irqrestore(&fm->lock, flags); | ||
62 | device_unregister(&sock->dev); | ||
63 | spin_lock_irqsave(&fm->lock, flags); | ||
64 | } | ||
65 | } | ||
66 | spin_unlock_irqrestore(&fm->lock, flags); | ||
67 | class_device_put(&fm->cdev); | ||
68 | } | ||
69 | |||
70 | static irqreturn_t tifm_7xx1_isr(int irq, void *dev_id, struct pt_regs *regs) | ||
71 | { | ||
72 | struct tifm_adapter *fm = dev_id; | ||
73 | unsigned int irq_status; | ||
74 | unsigned int sock_irq_status, cnt; | ||
75 | |||
76 | spin_lock(&fm->lock); | ||
77 | irq_status = readl(fm->addr + FM_INTERRUPT_STATUS); | ||
78 | if (irq_status == 0 || irq_status == (~0)) { | ||
79 | spin_unlock(&fm->lock); | ||
80 | return IRQ_NONE; | ||
81 | } | ||
82 | |||
83 | if (irq_status & TIFM_IRQ_ENABLE) { | ||
84 | writel(TIFM_IRQ_ENABLE, fm->addr + FM_CLEAR_INTERRUPT_ENABLE); | ||
85 | |||
86 | for (cnt = 0; cnt < fm->max_sockets; cnt++) { | ||
87 | sock_irq_status = (irq_status >> cnt) & | ||
88 | (TIFM_IRQ_FIFOMASK | TIFM_IRQ_CARDMASK); | ||
89 | |||
90 | if (fm->sockets[cnt]) { | ||
91 | if (sock_irq_status && | ||
92 | fm->sockets[cnt]->signal_irq) | ||
93 | sock_irq_status = fm->sockets[cnt]-> | ||
94 | signal_irq(fm->sockets[cnt], | ||
95 | sock_irq_status); | ||
96 | |||
97 | if (irq_status & (1 << cnt)) | ||
98 | fm->remove_mask |= 1 << cnt; | ||
99 | } else { | ||
100 | if (irq_status & (1 << cnt)) | ||
101 | fm->insert_mask |= 1 << cnt; | ||
102 | } | ||
103 | } | ||
104 | } | ||
105 | writel(irq_status, fm->addr + FM_INTERRUPT_STATUS); | ||
106 | |||
107 | if (!fm->inhibit_new_cards) { | ||
108 | if (!fm->remove_mask && !fm->insert_mask) { | ||
109 | writel(TIFM_IRQ_ENABLE, | ||
110 | fm->addr + FM_SET_INTERRUPT_ENABLE); | ||
111 | } else { | ||
112 | queue_work(fm->wq, &fm->media_remover); | ||
113 | queue_work(fm->wq, &fm->media_inserter); | ||
114 | } | ||
115 | } | ||
116 | |||
117 | spin_unlock(&fm->lock); | ||
118 | return IRQ_HANDLED; | ||
119 | } | ||
120 | |||
121 | static tifm_media_id tifm_7xx1_toggle_sock_power(char *sock_addr, int is_x2) | ||
122 | { | ||
123 | unsigned int s_state; | ||
124 | int cnt; | ||
125 | |||
126 | writel(0x0e00, sock_addr + SOCK_CONTROL); | ||
127 | |||
128 | for (cnt = 0; cnt < 100; cnt++) { | ||
129 | if (!(TIFM_SOCK_STATE_POWERED & | ||
130 | readl(sock_addr + SOCK_PRESENT_STATE))) | ||
131 | break; | ||
132 | msleep(10); | ||
133 | } | ||
134 | |||
135 | s_state = readl(sock_addr + SOCK_PRESENT_STATE); | ||
136 | if (!(TIFM_SOCK_STATE_OCCUPIED & s_state)) | ||
137 | return FM_NULL; | ||
138 | |||
139 | if (is_x2) { | ||
140 | writel((s_state & 7) | 0x0c00, sock_addr + SOCK_CONTROL); | ||
141 | } else { | ||
142 | // SmartMedia cards need extra 40 msec | ||
143 | if (((readl(sock_addr + SOCK_PRESENT_STATE) >> 4) & 7) == 1) | ||
144 | msleep(40); | ||
145 | writel(readl(sock_addr + SOCK_CONTROL) | TIFM_CTRL_LED, | ||
146 | sock_addr + SOCK_CONTROL); | ||
147 | msleep(10); | ||
148 | writel((s_state & 0x7) | 0x0c00 | TIFM_CTRL_LED, | ||
149 | sock_addr + SOCK_CONTROL); | ||
150 | } | ||
151 | |||
152 | for (cnt = 0; cnt < 100; cnt++) { | ||
153 | if ((TIFM_SOCK_STATE_POWERED & | ||
154 | readl(sock_addr + SOCK_PRESENT_STATE))) | ||
155 | break; | ||
156 | msleep(10); | ||
157 | } | ||
158 | |||
159 | if (!is_x2) | ||
160 | writel(readl(sock_addr + SOCK_CONTROL) & (~TIFM_CTRL_LED), | ||
161 | sock_addr + SOCK_CONTROL); | ||
162 | |||
163 | return (readl(sock_addr + SOCK_PRESENT_STATE) >> 4) & 7; | ||
164 | } | ||
165 | |||
166 | inline static char *tifm_7xx1_sock_addr(char *base_addr, unsigned int sock_num) | ||
167 | { | ||
168 | return base_addr + ((sock_num + 1) << 10); | ||
169 | } | ||
170 | |||
171 | static void tifm_7xx1_insert_media(void *adapter) | ||
172 | { | ||
173 | struct tifm_adapter *fm = adapter; | ||
174 | unsigned long flags; | ||
175 | tifm_media_id media_id; | ||
176 | char *card_name = "xx"; | ||
177 | int cnt, ok_to_register; | ||
178 | unsigned int insert_mask; | ||
179 | struct tifm_dev *new_sock = 0; | ||
180 | |||
181 | if (!class_device_get(&fm->cdev)) | ||
182 | return; | ||
183 | spin_lock_irqsave(&fm->lock, flags); | ||
184 | insert_mask = fm->insert_mask; | ||
185 | fm->insert_mask = 0; | ||
186 | if (fm->inhibit_new_cards) { | ||
187 | spin_unlock_irqrestore(&fm->lock, flags); | ||
188 | class_device_put(&fm->cdev); | ||
189 | return; | ||
190 | } | ||
191 | spin_unlock_irqrestore(&fm->lock, flags); | ||
192 | |||
193 | for (cnt = 0; cnt < fm->max_sockets; cnt++) { | ||
194 | if (!(insert_mask & (1 << cnt))) | ||
195 | continue; | ||
196 | |||
197 | media_id = tifm_7xx1_toggle_sock_power(tifm_7xx1_sock_addr(fm->addr, cnt), | ||
198 | fm->max_sockets == 2); | ||
199 | if (media_id) { | ||
200 | ok_to_register = 0; | ||
201 | new_sock = tifm_alloc_device(fm, cnt); | ||
202 | if (new_sock) { | ||
203 | new_sock->addr = tifm_7xx1_sock_addr(fm->addr, | ||
204 | cnt); | ||
205 | new_sock->media_id = media_id; | ||
206 | switch (media_id) { | ||
207 | case 1: | ||
208 | card_name = "xd"; | ||
209 | break; | ||
210 | case 2: | ||
211 | card_name = "ms"; | ||
212 | break; | ||
213 | case 3: | ||
214 | card_name = "sd"; | ||
215 | break; | ||
216 | default: | ||
217 | break; | ||
218 | } | ||
219 | snprintf(new_sock->dev.bus_id, BUS_ID_SIZE, | ||
220 | "tifm_%s%u:%u", card_name, fm->id, cnt); | ||
221 | printk(KERN_INFO DRIVER_NAME | ||
222 | ": %s card detected in socket %d\n", | ||
223 | card_name, cnt); | ||
224 | spin_lock_irqsave(&fm->lock, flags); | ||
225 | if (!fm->sockets[cnt]) { | ||
226 | fm->sockets[cnt] = new_sock; | ||
227 | ok_to_register = 1; | ||
228 | } | ||
229 | spin_unlock_irqrestore(&fm->lock, flags); | ||
230 | if (!ok_to_register || | ||
231 | device_register(&new_sock->dev)) { | ||
232 | spin_lock_irqsave(&fm->lock, flags); | ||
233 | fm->sockets[cnt] = 0; | ||
234 | spin_unlock_irqrestore(&fm->lock, | ||
235 | flags); | ||
236 | tifm_free_device(&new_sock->dev); | ||
237 | } | ||
238 | } | ||
239 | } | ||
240 | writel((TIFM_IRQ_FIFOMASK | TIFM_IRQ_CARDMASK) << cnt, | ||
241 | fm->addr + FM_CLEAR_INTERRUPT_ENABLE); | ||
242 | writel((TIFM_IRQ_FIFOMASK | TIFM_IRQ_CARDMASK) << cnt, | ||
243 | fm->addr + FM_SET_INTERRUPT_ENABLE); | ||
244 | } | ||
245 | |||
246 | writel(TIFM_IRQ_ENABLE, fm->addr + FM_SET_INTERRUPT_ENABLE); | ||
247 | class_device_put(&fm->cdev); | ||
248 | } | ||
249 | |||
250 | static int tifm_7xx1_suspend(struct pci_dev *dev, pm_message_t state) | ||
251 | { | ||
252 | struct tifm_adapter *fm = pci_get_drvdata(dev); | ||
253 | unsigned long flags; | ||
254 | |||
255 | spin_lock_irqsave(&fm->lock, flags); | ||
256 | fm->inhibit_new_cards = 1; | ||
257 | fm->remove_mask = 0xf; | ||
258 | fm->insert_mask = 0; | ||
259 | writel(TIFM_IRQ_ENABLE, fm->addr + FM_CLEAR_INTERRUPT_ENABLE); | ||
260 | spin_unlock_irqrestore(&fm->lock, flags); | ||
261 | flush_workqueue(fm->wq); | ||
262 | |||
263 | tifm_7xx1_remove_media(fm); | ||
264 | |||
265 | pci_set_power_state(dev, PCI_D3hot); | ||
266 | pci_disable_device(dev); | ||
267 | pci_save_state(dev); | ||
268 | return 0; | ||
269 | } | ||
270 | |||
271 | static int tifm_7xx1_resume(struct pci_dev *dev) | ||
272 | { | ||
273 | struct tifm_adapter *fm = pci_get_drvdata(dev); | ||
274 | unsigned long flags; | ||
275 | |||
276 | pci_restore_state(dev); | ||
277 | pci_enable_device(dev); | ||
278 | pci_set_power_state(dev, PCI_D0); | ||
279 | pci_set_master(dev); | ||
280 | |||
281 | spin_lock_irqsave(&fm->lock, flags); | ||
282 | fm->inhibit_new_cards = 0; | ||
283 | writel(TIFM_IRQ_SETALL, fm->addr + FM_INTERRUPT_STATUS); | ||
284 | writel(TIFM_IRQ_SETALL, fm->addr + FM_CLEAR_INTERRUPT_ENABLE); | ||
285 | writel(TIFM_IRQ_ENABLE | TIFM_IRQ_SETALLSOCK, | ||
286 | fm->addr + FM_SET_INTERRUPT_ENABLE); | ||
287 | fm->insert_mask = 0xf; | ||
288 | spin_unlock_irqrestore(&fm->lock, flags); | ||
289 | return 0; | ||
290 | } | ||
291 | |||
292 | static int tifm_7xx1_probe(struct pci_dev *dev, | ||
293 | const struct pci_device_id *dev_id) | ||
294 | { | ||
295 | struct tifm_adapter *fm; | ||
296 | int pci_dev_busy = 0; | ||
297 | int rc; | ||
298 | |||
299 | rc = pci_set_dma_mask(dev, DMA_32BIT_MASK); | ||
300 | if (rc) | ||
301 | return rc; | ||
302 | |||
303 | rc = pci_enable_device(dev); | ||
304 | if (rc) | ||
305 | return rc; | ||
306 | |||
307 | pci_set_master(dev); | ||
308 | |||
309 | rc = pci_request_regions(dev, DRIVER_NAME); | ||
310 | if (rc) { | ||
311 | pci_dev_busy = 1; | ||
312 | goto err_out; | ||
313 | } | ||
314 | |||
315 | pci_intx(dev, 1); | ||
316 | |||
317 | fm = tifm_alloc_adapter(); | ||
318 | if (!fm) { | ||
319 | rc = -ENOMEM; | ||
320 | goto err_out_int; | ||
321 | } | ||
322 | |||
323 | fm->dev = &dev->dev; | ||
324 | fm->max_sockets = (dev->device == 0x803B) ? 2 : 4; | ||
325 | fm->sockets = kzalloc(sizeof(struct tifm_dev*) * fm->max_sockets, | ||
326 | GFP_KERNEL); | ||
327 | if (!fm->sockets) | ||
328 | goto err_out_free; | ||
329 | |||
330 | INIT_WORK(&fm->media_inserter, tifm_7xx1_insert_media, fm); | ||
331 | INIT_WORK(&fm->media_remover, tifm_7xx1_remove_media, fm); | ||
332 | fm->eject = tifm_7xx1_eject; | ||
333 | pci_set_drvdata(dev, fm); | ||
334 | |||
335 | fm->addr = ioremap(pci_resource_start(dev, 0), | ||
336 | pci_resource_len(dev, 0)); | ||
337 | if (!fm->addr) | ||
338 | goto err_out_free; | ||
339 | |||
340 | rc = request_irq(dev->irq, tifm_7xx1_isr, SA_SHIRQ, DRIVER_NAME, fm); | ||
341 | if (rc) | ||
342 | goto err_out_unmap; | ||
343 | |||
344 | rc = tifm_add_adapter(fm); | ||
345 | if (rc) | ||
346 | goto err_out_irq; | ||
347 | |||
348 | writel(TIFM_IRQ_SETALL, fm->addr + FM_CLEAR_INTERRUPT_ENABLE); | ||
349 | writel(TIFM_IRQ_ENABLE | TIFM_IRQ_SETALLSOCK, | ||
350 | fm->addr + FM_SET_INTERRUPT_ENABLE); | ||
351 | |||
352 | fm->insert_mask = 0xf; | ||
353 | |||
354 | return 0; | ||
355 | |||
356 | err_out_irq: | ||
357 | free_irq(dev->irq, fm); | ||
358 | err_out_unmap: | ||
359 | iounmap(fm->addr); | ||
360 | err_out_free: | ||
361 | pci_set_drvdata(dev, NULL); | ||
362 | tifm_free_adapter(fm); | ||
363 | err_out_int: | ||
364 | pci_intx(dev, 0); | ||
365 | pci_release_regions(dev); | ||
366 | err_out: | ||
367 | if (!pci_dev_busy) | ||
368 | pci_disable_device(dev); | ||
369 | return rc; | ||
370 | } | ||
371 | |||
372 | static void tifm_7xx1_remove(struct pci_dev *dev) | ||
373 | { | ||
374 | struct tifm_adapter *fm = pci_get_drvdata(dev); | ||
375 | unsigned long flags; | ||
376 | |||
377 | spin_lock_irqsave(&fm->lock, flags); | ||
378 | fm->inhibit_new_cards = 1; | ||
379 | fm->remove_mask = 0xf; | ||
380 | fm->insert_mask = 0; | ||
381 | writel(TIFM_IRQ_ENABLE, fm->addr + FM_CLEAR_INTERRUPT_ENABLE); | ||
382 | spin_unlock_irqrestore(&fm->lock, flags); | ||
383 | |||
384 | flush_workqueue(fm->wq); | ||
385 | |||
386 | tifm_7xx1_remove_media(fm); | ||
387 | |||
388 | writel(TIFM_IRQ_SETALL, fm->addr + FM_CLEAR_INTERRUPT_ENABLE); | ||
389 | free_irq(dev->irq, fm); | ||
390 | |||
391 | tifm_remove_adapter(fm); | ||
392 | |||
393 | pci_set_drvdata(dev, 0); | ||
394 | |||
395 | iounmap(fm->addr); | ||
396 | pci_intx(dev, 0); | ||
397 | pci_release_regions(dev); | ||
398 | |||
399 | pci_disable_device(dev); | ||
400 | tifm_free_adapter(fm); | ||
401 | } | ||
402 | |||
403 | static struct pci_device_id tifm_7xx1_pci_tbl [] = { | ||
404 | { PCI_VENDOR_ID_TI, 0x8033, PCI_ANY_ID, PCI_ANY_ID, 0, 0, | ||
405 | 0 }, /* xx21 - the one I have */ | ||
406 | { PCI_VENDOR_ID_TI, 0x803B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, | ||
407 | 0 }, /* xx12 - should be also supported */ | ||
408 | { } | ||
409 | }; | ||
410 | |||
411 | static struct pci_driver tifm_7xx1_driver = { | ||
412 | .name = DRIVER_NAME, | ||
413 | .id_table = tifm_7xx1_pci_tbl, | ||
414 | .probe = tifm_7xx1_probe, | ||
415 | .remove = tifm_7xx1_remove, | ||
416 | .suspend = tifm_7xx1_suspend, | ||
417 | .resume = tifm_7xx1_resume, | ||
418 | }; | ||
419 | |||
420 | static int __init tifm_7xx1_init(void) | ||
421 | { | ||
422 | return pci_register_driver(&tifm_7xx1_driver); | ||
423 | } | ||
424 | |||
425 | static void __exit tifm_7xx1_exit(void) | ||
426 | { | ||
427 | pci_unregister_driver(&tifm_7xx1_driver); | ||
428 | } | ||
429 | |||
430 | MODULE_AUTHOR("Alex Dubov"); | ||
431 | MODULE_DESCRIPTION("TI FlashMedia host driver"); | ||
432 | MODULE_LICENSE("GPL"); | ||
433 | MODULE_DEVICE_TABLE(pci, tifm_7xx1_pci_tbl); | ||
434 | MODULE_VERSION(DRIVER_VERSION); | ||
435 | |||
436 | module_init(tifm_7xx1_init); | ||
437 | module_exit(tifm_7xx1_exit); | ||