diff options
author | Alex Dubov <oakad@yahoo.com> | 2006-10-04 05:15:37 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-10-04 10:55:14 -0400 |
commit | 4020f2d7f0b0e68b92bec9a3e1f4a54a7a9dc672 (patch) | |
tree | afbe35984c945c5f81b2f29f3c54a144abeca3f0 /drivers/misc | |
parent | 856fe98f168e5b80b053979769af2514aab96d6b (diff) |
[PATCH] mmc: driver for TI FlashMedia card reader - source
Driver for TI Flash Media card reader. At present, only MMC/SD cards are
supported.
[akpm@osdl.org: cleanups, build fixes]
Signed-off-by: Alex Dubov <oakad@yahoo.com>
Cc: Daniel Qarras <dqarras@yahoo.com>
Acked-by: Pierre Ossman <drzeus@drzeus.cx>
Cc: Russell King <rmk@arm.linux.org.uk>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'drivers/misc')
-rw-r--r-- | drivers/misc/tifm_7xx1.c | 437 | ||||
-rw-r--r-- | drivers/misc/tifm_core.c | 272 |
2 files changed, 709 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); | ||
diff --git a/drivers/misc/tifm_core.c b/drivers/misc/tifm_core.c new file mode 100644 index 000000000000..cca5f8522469 --- /dev/null +++ b/drivers/misc/tifm_core.c | |||
@@ -0,0 +1,272 @@ | |||
1 | /* | ||
2 | * tifm_core.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/init.h> | ||
14 | #include <linux/idr.h> | ||
15 | |||
16 | #define DRIVER_NAME "tifm_core" | ||
17 | #define DRIVER_VERSION "0.6" | ||
18 | |||
19 | static DEFINE_IDR(tifm_adapter_idr); | ||
20 | static DEFINE_SPINLOCK(tifm_adapter_lock); | ||
21 | |||
22 | static tifm_media_id *tifm_device_match(tifm_media_id *ids, | ||
23 | struct tifm_dev *dev) | ||
24 | { | ||
25 | while (*ids) { | ||
26 | if (dev->media_id == *ids) | ||
27 | return ids; | ||
28 | ids++; | ||
29 | } | ||
30 | return NULL; | ||
31 | } | ||
32 | |||
33 | static int tifm_match(struct device *dev, struct device_driver *drv) | ||
34 | { | ||
35 | struct tifm_dev *fm_dev = container_of(dev, struct tifm_dev, dev); | ||
36 | struct tifm_driver *fm_drv; | ||
37 | |||
38 | fm_drv = container_of(drv, struct tifm_driver, driver); | ||
39 | if (!fm_drv->id_table) | ||
40 | return -EINVAL; | ||
41 | if (tifm_device_match(fm_drv->id_table, fm_dev)) | ||
42 | return 1; | ||
43 | return -ENODEV; | ||
44 | } | ||
45 | |||
46 | static int tifm_uevent(struct device *dev, char **envp, int num_envp, | ||
47 | char *buffer, int buffer_size) | ||
48 | { | ||
49 | struct tifm_dev *fm_dev; | ||
50 | int i = 0; | ||
51 | int length = 0; | ||
52 | const char *card_type_name[] = {"INV", "SM", "MS", "SD"}; | ||
53 | |||
54 | if (!dev || !(fm_dev = container_of(dev, struct tifm_dev, dev))) | ||
55 | return -ENODEV; | ||
56 | if (add_uevent_var(envp, num_envp, &i, buffer, buffer_size, &length, | ||
57 | "TIFM_CARD_TYPE=%s", card_type_name[fm_dev->media_id])) | ||
58 | return -ENOMEM; | ||
59 | |||
60 | return 0; | ||
61 | } | ||
62 | |||
63 | static struct bus_type tifm_bus_type = { | ||
64 | .name = "tifm", | ||
65 | .match = tifm_match, | ||
66 | .uevent = tifm_uevent, | ||
67 | }; | ||
68 | |||
69 | static void tifm_free(struct class_device *cdev) | ||
70 | { | ||
71 | struct tifm_adapter *fm = container_of(cdev, struct tifm_adapter, cdev); | ||
72 | |||
73 | kfree(fm->sockets); | ||
74 | if (fm->wq) | ||
75 | destroy_workqueue(fm->wq); | ||
76 | kfree(fm); | ||
77 | } | ||
78 | |||
79 | static struct class tifm_adapter_class = { | ||
80 | .name = "tifm_adapter", | ||
81 | .release = tifm_free | ||
82 | }; | ||
83 | |||
84 | struct tifm_adapter *tifm_alloc_adapter(void) | ||
85 | { | ||
86 | struct tifm_adapter *fm; | ||
87 | |||
88 | fm = kzalloc(sizeof(struct tifm_adapter), GFP_KERNEL); | ||
89 | if (fm) { | ||
90 | fm->cdev.class = &tifm_adapter_class; | ||
91 | spin_lock_init(&fm->lock); | ||
92 | class_device_initialize(&fm->cdev); | ||
93 | } | ||
94 | return fm; | ||
95 | } | ||
96 | EXPORT_SYMBOL(tifm_alloc_adapter); | ||
97 | |||
98 | void tifm_free_adapter(struct tifm_adapter *fm) | ||
99 | { | ||
100 | class_device_put(&fm->cdev); | ||
101 | } | ||
102 | EXPORT_SYMBOL(tifm_free_adapter); | ||
103 | |||
104 | int tifm_add_adapter(struct tifm_adapter *fm) | ||
105 | { | ||
106 | int rc; | ||
107 | |||
108 | if (!idr_pre_get(&tifm_adapter_idr, GFP_KERNEL)) | ||
109 | return -ENOMEM; | ||
110 | |||
111 | spin_lock(&tifm_adapter_lock); | ||
112 | rc = idr_get_new(&tifm_adapter_idr, fm, &fm->id); | ||
113 | spin_unlock(&tifm_adapter_lock); | ||
114 | if (!rc) { | ||
115 | snprintf(fm->cdev.class_id, BUS_ID_SIZE, "tifm%u", fm->id); | ||
116 | strncpy(fm->wq_name, fm->cdev.class_id, KOBJ_NAME_LEN); | ||
117 | |||
118 | fm->wq = create_singlethread_workqueue(fm->wq_name); | ||
119 | if (fm->wq) | ||
120 | return class_device_add(&fm->cdev); | ||
121 | |||
122 | spin_lock(&tifm_adapter_lock); | ||
123 | idr_remove(&tifm_adapter_idr, fm->id); | ||
124 | spin_unlock(&tifm_adapter_lock); | ||
125 | rc = -ENOMEM; | ||
126 | } | ||
127 | return rc; | ||
128 | } | ||
129 | EXPORT_SYMBOL(tifm_add_adapter); | ||
130 | |||
131 | void tifm_remove_adapter(struct tifm_adapter *fm) | ||
132 | { | ||
133 | class_device_del(&fm->cdev); | ||
134 | |||
135 | spin_lock(&tifm_adapter_lock); | ||
136 | idr_remove(&tifm_adapter_idr, fm->id); | ||
137 | spin_unlock(&tifm_adapter_lock); | ||
138 | } | ||
139 | EXPORT_SYMBOL(tifm_remove_adapter); | ||
140 | |||
141 | void tifm_free_device(struct device *dev) | ||
142 | { | ||
143 | struct tifm_dev *fm_dev = container_of(dev, struct tifm_dev, dev); | ||
144 | if (fm_dev->wq) | ||
145 | destroy_workqueue(fm_dev->wq); | ||
146 | kfree(fm_dev); | ||
147 | } | ||
148 | EXPORT_SYMBOL(tifm_free_device); | ||
149 | |||
150 | struct tifm_dev *tifm_alloc_device(struct tifm_adapter *fm, unsigned int id) | ||
151 | { | ||
152 | struct tifm_dev *dev = kzalloc(sizeof(struct tifm_dev), GFP_KERNEL); | ||
153 | |||
154 | if (dev) { | ||
155 | spin_lock_init(&dev->lock); | ||
156 | snprintf(dev->wq_name, KOBJ_NAME_LEN, "tifm%u:%u", fm->id, id); | ||
157 | dev->wq = create_singlethread_workqueue(dev->wq_name); | ||
158 | if (!dev->wq) { | ||
159 | kfree(dev); | ||
160 | return 0; | ||
161 | } | ||
162 | dev->dev.parent = fm->dev; | ||
163 | dev->dev.bus = &tifm_bus_type; | ||
164 | dev->dev.release = tifm_free_device; | ||
165 | } | ||
166 | return dev; | ||
167 | } | ||
168 | EXPORT_SYMBOL(tifm_alloc_device); | ||
169 | |||
170 | void tifm_eject(struct tifm_dev *sock) | ||
171 | { | ||
172 | struct tifm_adapter *fm = dev_get_drvdata(sock->dev.parent); | ||
173 | fm->eject(fm, sock); | ||
174 | } | ||
175 | EXPORT_SYMBOL(tifm_eject); | ||
176 | |||
177 | int tifm_map_sg(struct tifm_dev *sock, struct scatterlist *sg, int nents, | ||
178 | int direction) | ||
179 | { | ||
180 | return pci_map_sg(to_pci_dev(sock->dev.parent), sg, nents, direction); | ||
181 | } | ||
182 | EXPORT_SYMBOL(tifm_map_sg); | ||
183 | |||
184 | void tifm_unmap_sg(struct tifm_dev *sock, struct scatterlist *sg, int nents, | ||
185 | int direction) | ||
186 | { | ||
187 | pci_unmap_sg(to_pci_dev(sock->dev.parent), sg, nents, direction); | ||
188 | } | ||
189 | EXPORT_SYMBOL(tifm_unmap_sg); | ||
190 | |||
191 | static int tifm_device_probe(struct device *dev) | ||
192 | { | ||
193 | struct tifm_driver *drv; | ||
194 | struct tifm_dev *fm_dev; | ||
195 | int rc = 0; | ||
196 | const tifm_media_id *id; | ||
197 | |||
198 | drv = container_of(dev->driver, struct tifm_driver, driver); | ||
199 | fm_dev = container_of(dev, struct tifm_dev, dev); | ||
200 | get_device(dev); | ||
201 | if (!fm_dev->drv && drv->probe && drv->id_table) { | ||
202 | rc = -ENODEV; | ||
203 | id = tifm_device_match(drv->id_table, fm_dev); | ||
204 | if (id) | ||
205 | rc = drv->probe(fm_dev); | ||
206 | if (rc >= 0) { | ||
207 | rc = 0; | ||
208 | fm_dev->drv = drv; | ||
209 | } | ||
210 | } | ||
211 | if (rc) | ||
212 | put_device(dev); | ||
213 | return rc; | ||
214 | } | ||
215 | |||
216 | static int tifm_device_remove(struct device *dev) | ||
217 | { | ||
218 | struct tifm_dev *fm_dev = container_of(dev, struct tifm_dev, dev); | ||
219 | struct tifm_driver *drv = fm_dev->drv; | ||
220 | |||
221 | if (drv) { | ||
222 | if (drv->remove) drv->remove(fm_dev); | ||
223 | fm_dev->drv = 0; | ||
224 | } | ||
225 | |||
226 | put_device(dev); | ||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | int tifm_register_driver(struct tifm_driver *drv) | ||
231 | { | ||
232 | drv->driver.bus = &tifm_bus_type; | ||
233 | drv->driver.probe = tifm_device_probe; | ||
234 | drv->driver.remove = tifm_device_remove; | ||
235 | |||
236 | return driver_register(&drv->driver); | ||
237 | } | ||
238 | EXPORT_SYMBOL(tifm_register_driver); | ||
239 | |||
240 | void tifm_unregister_driver(struct tifm_driver *drv) | ||
241 | { | ||
242 | driver_unregister(&drv->driver); | ||
243 | } | ||
244 | EXPORT_SYMBOL(tifm_unregister_driver); | ||
245 | |||
246 | static int __init tifm_init(void) | ||
247 | { | ||
248 | int rc = bus_register(&tifm_bus_type); | ||
249 | |||
250 | if (!rc) { | ||
251 | rc = class_register(&tifm_adapter_class); | ||
252 | if (rc) | ||
253 | bus_unregister(&tifm_bus_type); | ||
254 | } | ||
255 | |||
256 | return rc; | ||
257 | } | ||
258 | |||
259 | static void __exit tifm_exit(void) | ||
260 | { | ||
261 | class_unregister(&tifm_adapter_class); | ||
262 | bus_unregister(&tifm_bus_type); | ||
263 | } | ||
264 | |||
265 | subsys_initcall(tifm_init); | ||
266 | module_exit(tifm_exit); | ||
267 | |||
268 | MODULE_LICENSE("GPL"); | ||
269 | MODULE_AUTHOR("Alex Dubov"); | ||
270 | MODULE_DESCRIPTION("TI FlashMedia core driver"); | ||
271 | MODULE_LICENSE("GPL"); | ||
272 | MODULE_VERSION(DRIVER_VERSION); | ||