diff options
Diffstat (limited to 'drivers/memstick/host')
-rw-r--r-- | drivers/memstick/host/Kconfig | 10 | ||||
-rw-r--r-- | drivers/memstick/host/Makefile | 6 | ||||
-rw-r--r-- | drivers/memstick/host/jmb38x_ms.c | 945 |
3 files changed, 958 insertions, 3 deletions
diff --git a/drivers/memstick/host/Kconfig b/drivers/memstick/host/Kconfig index c002fcc3c879..4ce5c8dffb68 100644 --- a/drivers/memstick/host/Kconfig +++ b/drivers/memstick/host/Kconfig | |||
@@ -20,3 +20,13 @@ config MEMSTICK_TIFM_MS | |||
20 | To compile this driver as a module, choose M here: the | 20 | To compile this driver as a module, choose M here: the |
21 | module will be called tifm_ms. | 21 | module will be called tifm_ms. |
22 | 22 | ||
23 | config MEMSTICK_JMICRON_38X | ||
24 | tristate "JMicron JMB38X MemoryStick interface support (EXPERIMENTAL)" | ||
25 | depends on EXPERIMENTAL && PCI | ||
26 | |||
27 | help | ||
28 | Say Y here if you want to be able to access MemoryStick cards with | ||
29 | the JMicron(R) JMB38X MemoryStick card reader. | ||
30 | |||
31 | To compile this driver as a module, choose M here: the | ||
32 | module will be called jmb38x_ms. | ||
diff --git a/drivers/memstick/host/Makefile b/drivers/memstick/host/Makefile index ee666380efa1..12530e4311d3 100644 --- a/drivers/memstick/host/Makefile +++ b/drivers/memstick/host/Makefile | |||
@@ -3,8 +3,8 @@ | |||
3 | # | 3 | # |
4 | 4 | ||
5 | ifeq ($(CONFIG_MEMSTICK_DEBUG),y) | 5 | ifeq ($(CONFIG_MEMSTICK_DEBUG),y) |
6 | EXTRA_CFLAGS += -DDEBUG | 6 | EXTRA_CFLAGS += -DDEBUG |
7 | endif | 7 | endif |
8 | 8 | ||
9 | obj-$(CONFIG_MEMSTICK_TIFM_MS) += tifm_ms.o | 9 | obj-$(CONFIG_MEMSTICK_TIFM_MS) += tifm_ms.o |
10 | 10 | obj-$(CONFIG_MEMSTICK_JMICRON_38X) += jmb38x_ms.o | |
diff --git a/drivers/memstick/host/jmb38x_ms.c b/drivers/memstick/host/jmb38x_ms.c new file mode 100644 index 000000000000..03fe8783b1ee --- /dev/null +++ b/drivers/memstick/host/jmb38x_ms.c | |||
@@ -0,0 +1,945 @@ | |||
1 | /* | ||
2 | * jmb38x_ms.c - JMicron jmb38x MemoryStick card reader | ||
3 | * | ||
4 | * Copyright (C) 2008 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/spinlock.h> | ||
13 | #include <linux/interrupt.h> | ||
14 | #include <linux/pci.h> | ||
15 | #include <linux/delay.h> | ||
16 | #include <linux/highmem.h> | ||
17 | #include <linux/memstick.h> | ||
18 | |||
19 | #define DRIVER_NAME "jmb38x_ms" | ||
20 | |||
21 | static int no_dma; | ||
22 | module_param(no_dma, bool, 0644); | ||
23 | |||
24 | enum { | ||
25 | DMA_ADDRESS = 0x00, | ||
26 | BLOCK = 0x04, | ||
27 | DMA_CONTROL = 0x08, | ||
28 | TPC_P0 = 0x0c, | ||
29 | TPC_P1 = 0x10, | ||
30 | TPC = 0x14, | ||
31 | HOST_CONTROL = 0x18, | ||
32 | DATA = 0x1c, | ||
33 | STATUS = 0x20, | ||
34 | INT_STATUS = 0x24, | ||
35 | INT_STATUS_ENABLE = 0x28, | ||
36 | INT_SIGNAL_ENABLE = 0x2c, | ||
37 | TIMER = 0x30, | ||
38 | TIMER_CONTROL = 0x34, | ||
39 | PAD_OUTPUT_ENABLE = 0x38, | ||
40 | PAD_PU_PD = 0x3c, | ||
41 | CLOCK_DELAY = 0x40, | ||
42 | ADMA_ADDRESS = 0x44, | ||
43 | CLOCK_CONTROL = 0x48, | ||
44 | LED_CONTROL = 0x4c, | ||
45 | VERSION = 0x50 | ||
46 | }; | ||
47 | |||
48 | struct jmb38x_ms_host { | ||
49 | struct jmb38x_ms *chip; | ||
50 | void __iomem *addr; | ||
51 | spinlock_t lock; | ||
52 | int id; | ||
53 | char host_id[DEVICE_ID_SIZE]; | ||
54 | int irq; | ||
55 | unsigned int block_pos; | ||
56 | unsigned long timeout_jiffies; | ||
57 | struct timer_list timer; | ||
58 | struct memstick_request *req; | ||
59 | unsigned char eject:1, | ||
60 | use_dma:1; | ||
61 | unsigned char cmd_flags; | ||
62 | unsigned char io_pos; | ||
63 | unsigned int io_word[2]; | ||
64 | }; | ||
65 | |||
66 | struct jmb38x_ms { | ||
67 | struct pci_dev *pdev; | ||
68 | int host_cnt; | ||
69 | struct memstick_host *hosts[]; | ||
70 | }; | ||
71 | |||
72 | #define BLOCK_COUNT_MASK 0xffff0000 | ||
73 | #define BLOCK_SIZE_MASK 0x00000fff | ||
74 | |||
75 | #define DMA_CONTROL_ENABLE 0x00000001 | ||
76 | |||
77 | #define TPC_DATA_SEL 0x00008000 | ||
78 | #define TPC_DIR 0x00004000 | ||
79 | #define TPC_WAIT_INT 0x00002000 | ||
80 | #define TPC_GET_INT 0x00000800 | ||
81 | #define TPC_CODE_SZ_MASK 0x00000700 | ||
82 | #define TPC_DATA_SZ_MASK 0x00000007 | ||
83 | |||
84 | #define HOST_CONTROL_RESET_REQ 0x00008000 | ||
85 | #define HOST_CONTROL_REI 0x00004000 | ||
86 | #define HOST_CONTROL_LED 0x00000400 | ||
87 | #define HOST_CONTROL_FAST_CLK 0x00000200 | ||
88 | #define HOST_CONTROL_RESET 0x00000100 | ||
89 | #define HOST_CONTROL_POWER_EN 0x00000080 | ||
90 | #define HOST_CONTROL_CLOCK_EN 0x00000040 | ||
91 | #define HOST_CONTROL_IF_SHIFT 4 | ||
92 | |||
93 | #define HOST_CONTROL_IF_SERIAL 0x0 | ||
94 | #define HOST_CONTROL_IF_PAR4 0x1 | ||
95 | #define HOST_CONTROL_IF_PAR8 0x3 | ||
96 | |||
97 | #define STATUS_HAS_MEDIA 0x00000400 | ||
98 | #define STATUS_FIFO_EMPTY 0x00000200 | ||
99 | #define STATUS_FIFO_FULL 0x00000100 | ||
100 | |||
101 | #define INT_STATUS_TPC_ERR 0x00080000 | ||
102 | #define INT_STATUS_CRC_ERR 0x00040000 | ||
103 | #define INT_STATUS_TIMER_TO 0x00020000 | ||
104 | #define INT_STATUS_HSK_TO 0x00010000 | ||
105 | #define INT_STATUS_ANY_ERR 0x00008000 | ||
106 | #define INT_STATUS_FIFO_WRDY 0x00000080 | ||
107 | #define INT_STATUS_FIFO_RRDY 0x00000040 | ||
108 | #define INT_STATUS_MEDIA_OUT 0x00000010 | ||
109 | #define INT_STATUS_MEDIA_IN 0x00000008 | ||
110 | #define INT_STATUS_DMA_BOUNDARY 0x00000004 | ||
111 | #define INT_STATUS_EOTRAN 0x00000002 | ||
112 | #define INT_STATUS_EOTPC 0x00000001 | ||
113 | |||
114 | #define INT_STATUS_ALL 0x000f801f | ||
115 | |||
116 | #define PAD_OUTPUT_ENABLE_MS 0x0F3F | ||
117 | |||
118 | #define PAD_PU_PD_OFF 0x7FFF0000 | ||
119 | #define PAD_PU_PD_ON_MS_SOCK0 0x5f8f0000 | ||
120 | #define PAD_PU_PD_ON_MS_SOCK1 0x0f0f0000 | ||
121 | |||
122 | enum { | ||
123 | CMD_READY = 0x01, | ||
124 | FIFO_READY = 0x02, | ||
125 | REG_DATA = 0x04, | ||
126 | AUTO_GET_INT = 0x08 | ||
127 | }; | ||
128 | |||
129 | static unsigned int jmb38x_ms_read_data(struct jmb38x_ms_host *host, | ||
130 | unsigned char *buf, unsigned int length) | ||
131 | { | ||
132 | unsigned int off = 0; | ||
133 | |||
134 | while (host->io_pos && length) { | ||
135 | buf[off++] = host->io_word[0] & 0xff; | ||
136 | host->io_word[0] >>= 8; | ||
137 | length--; | ||
138 | host->io_pos--; | ||
139 | } | ||
140 | |||
141 | if (!length) | ||
142 | return off; | ||
143 | |||
144 | while (!(STATUS_FIFO_EMPTY & readl(host->addr + STATUS))) { | ||
145 | if (length < 4) | ||
146 | break; | ||
147 | *(unsigned int *)(buf + off) = __raw_readl(host->addr + DATA); | ||
148 | length -= 4; | ||
149 | off += 4; | ||
150 | } | ||
151 | |||
152 | if (length | ||
153 | && !(STATUS_FIFO_EMPTY & readl(host->addr + STATUS))) { | ||
154 | host->io_word[0] = readl(host->addr + DATA); | ||
155 | for (host->io_pos = 4; host->io_pos; --host->io_pos) { | ||
156 | buf[off++] = host->io_word[0] & 0xff; | ||
157 | host->io_word[0] >>= 8; | ||
158 | length--; | ||
159 | if (!length) | ||
160 | break; | ||
161 | } | ||
162 | } | ||
163 | |||
164 | return off; | ||
165 | } | ||
166 | |||
167 | static unsigned int jmb38x_ms_read_reg_data(struct jmb38x_ms_host *host, | ||
168 | unsigned char *buf, | ||
169 | unsigned int length) | ||
170 | { | ||
171 | unsigned int off = 0; | ||
172 | |||
173 | while (host->io_pos > 4 && length) { | ||
174 | buf[off++] = host->io_word[0] & 0xff; | ||
175 | host->io_word[0] >>= 8; | ||
176 | length--; | ||
177 | host->io_pos--; | ||
178 | } | ||
179 | |||
180 | if (!length) | ||
181 | return off; | ||
182 | |||
183 | while (host->io_pos && length) { | ||
184 | buf[off++] = host->io_word[1] & 0xff; | ||
185 | host->io_word[1] >>= 8; | ||
186 | length--; | ||
187 | host->io_pos--; | ||
188 | } | ||
189 | |||
190 | return off; | ||
191 | } | ||
192 | |||
193 | static unsigned int jmb38x_ms_write_data(struct jmb38x_ms_host *host, | ||
194 | unsigned char *buf, | ||
195 | unsigned int length) | ||
196 | { | ||
197 | unsigned int off = 0; | ||
198 | |||
199 | if (host->io_pos) { | ||
200 | while (host->io_pos < 4 && length) { | ||
201 | host->io_word[0] |= buf[off++] << (host->io_pos * 8); | ||
202 | host->io_pos++; | ||
203 | length--; | ||
204 | } | ||
205 | } | ||
206 | |||
207 | if (host->io_pos == 4 | ||
208 | && !(STATUS_FIFO_FULL & readl(host->addr + STATUS))) { | ||
209 | writel(host->io_word[0], host->addr + DATA); | ||
210 | host->io_pos = 0; | ||
211 | host->io_word[0] = 0; | ||
212 | } else if (host->io_pos) { | ||
213 | return off; | ||
214 | } | ||
215 | |||
216 | if (!length) | ||
217 | return off; | ||
218 | |||
219 | while (!(STATUS_FIFO_FULL & readl(host->addr + STATUS))) { | ||
220 | if (length < 4) | ||
221 | break; | ||
222 | |||
223 | __raw_writel(*(unsigned int *)(buf + off), | ||
224 | host->addr + DATA); | ||
225 | length -= 4; | ||
226 | off += 4; | ||
227 | } | ||
228 | |||
229 | switch (length) { | ||
230 | case 3: | ||
231 | host->io_word[0] |= buf[off + 2] << 16; | ||
232 | host->io_pos++; | ||
233 | case 2: | ||
234 | host->io_word[0] |= buf[off + 1] << 8; | ||
235 | host->io_pos++; | ||
236 | case 1: | ||
237 | host->io_word[0] |= buf[off]; | ||
238 | host->io_pos++; | ||
239 | } | ||
240 | |||
241 | off += host->io_pos; | ||
242 | |||
243 | return off; | ||
244 | } | ||
245 | |||
246 | static unsigned int jmb38x_ms_write_reg_data(struct jmb38x_ms_host *host, | ||
247 | unsigned char *buf, | ||
248 | unsigned int length) | ||
249 | { | ||
250 | unsigned int off = 0; | ||
251 | |||
252 | while (host->io_pos < 4 && length) { | ||
253 | host->io_word[0] &= ~(0xff << (host->io_pos * 8)); | ||
254 | host->io_word[0] |= buf[off++] << (host->io_pos * 8); | ||
255 | host->io_pos++; | ||
256 | length--; | ||
257 | } | ||
258 | |||
259 | if (!length) | ||
260 | return off; | ||
261 | |||
262 | while (host->io_pos < 8 && length) { | ||
263 | host->io_word[1] &= ~(0xff << (host->io_pos * 8)); | ||
264 | host->io_word[1] |= buf[off++] << (host->io_pos * 8); | ||
265 | host->io_pos++; | ||
266 | length--; | ||
267 | } | ||
268 | |||
269 | return off; | ||
270 | } | ||
271 | |||
272 | static int jmb38x_ms_transfer_data(struct jmb38x_ms_host *host) | ||
273 | { | ||
274 | unsigned int length; | ||
275 | unsigned int off; | ||
276 | unsigned int t_size, p_off, p_cnt; | ||
277 | unsigned char *buf; | ||
278 | struct page *pg; | ||
279 | unsigned long flags = 0; | ||
280 | |||
281 | if (host->req->long_data) { | ||
282 | length = host->req->sg.length - host->block_pos; | ||
283 | off = host->req->sg.offset + host->block_pos; | ||
284 | } else { | ||
285 | length = host->req->data_len - host->block_pos; | ||
286 | off = 0; | ||
287 | } | ||
288 | |||
289 | while (length) { | ||
290 | if (host->req->long_data) { | ||
291 | pg = nth_page(sg_page(&host->req->sg), | ||
292 | off >> PAGE_SHIFT); | ||
293 | p_off = offset_in_page(off); | ||
294 | p_cnt = PAGE_SIZE - p_off; | ||
295 | p_cnt = min(p_cnt, length); | ||
296 | |||
297 | local_irq_save(flags); | ||
298 | buf = kmap_atomic(pg, KM_BIO_SRC_IRQ) + p_off; | ||
299 | } else { | ||
300 | buf = host->req->data + host->block_pos; | ||
301 | p_cnt = host->req->data_len - host->block_pos; | ||
302 | } | ||
303 | |||
304 | if (host->req->data_dir == WRITE) | ||
305 | t_size = !(host->cmd_flags & REG_DATA) | ||
306 | ? jmb38x_ms_write_data(host, buf, p_cnt) | ||
307 | : jmb38x_ms_write_reg_data(host, buf, p_cnt); | ||
308 | else | ||
309 | t_size = !(host->cmd_flags & REG_DATA) | ||
310 | ? jmb38x_ms_read_data(host, buf, p_cnt) | ||
311 | : jmb38x_ms_read_reg_data(host, buf, p_cnt); | ||
312 | |||
313 | if (host->req->long_data) { | ||
314 | kunmap_atomic(buf - p_off, KM_BIO_SRC_IRQ); | ||
315 | local_irq_restore(flags); | ||
316 | } | ||
317 | |||
318 | if (!t_size) | ||
319 | break; | ||
320 | host->block_pos += t_size; | ||
321 | length -= t_size; | ||
322 | off += t_size; | ||
323 | } | ||
324 | |||
325 | if (!length && host->req->data_dir == WRITE) { | ||
326 | if (host->cmd_flags & REG_DATA) { | ||
327 | writel(host->io_word[0], host->addr + TPC_P0); | ||
328 | writel(host->io_word[1], host->addr + TPC_P1); | ||
329 | } else if (host->io_pos) { | ||
330 | writel(host->io_word[0], host->addr + DATA); | ||
331 | } | ||
332 | } | ||
333 | |||
334 | return length; | ||
335 | } | ||
336 | |||
337 | static int jmb38x_ms_issue_cmd(struct memstick_host *msh) | ||
338 | { | ||
339 | struct jmb38x_ms_host *host = memstick_priv(msh); | ||
340 | unsigned char *data; | ||
341 | unsigned int data_len, cmd, t_val; | ||
342 | |||
343 | if (!(STATUS_HAS_MEDIA & readl(host->addr + STATUS))) { | ||
344 | dev_dbg(msh->cdev.dev, "no media status\n"); | ||
345 | host->req->error = -ETIME; | ||
346 | return host->req->error; | ||
347 | } | ||
348 | |||
349 | dev_dbg(msh->cdev.dev, "control %08x\n", | ||
350 | readl(host->addr + HOST_CONTROL)); | ||
351 | dev_dbg(msh->cdev.dev, "status %08x\n", readl(host->addr + INT_STATUS)); | ||
352 | dev_dbg(msh->cdev.dev, "hstatus %08x\n", readl(host->addr + STATUS)); | ||
353 | |||
354 | host->cmd_flags = 0; | ||
355 | host->block_pos = 0; | ||
356 | host->io_pos = 0; | ||
357 | host->io_word[0] = 0; | ||
358 | host->io_word[1] = 0; | ||
359 | |||
360 | cmd = host->req->tpc << 16; | ||
361 | cmd |= TPC_DATA_SEL; | ||
362 | |||
363 | if (host->req->data_dir == READ) | ||
364 | cmd |= TPC_DIR; | ||
365 | if (host->req->need_card_int) | ||
366 | cmd |= TPC_WAIT_INT; | ||
367 | if (host->req->get_int_reg) | ||
368 | cmd |= TPC_GET_INT; | ||
369 | |||
370 | data = host->req->data; | ||
371 | |||
372 | host->use_dma = !no_dma; | ||
373 | |||
374 | if (host->req->long_data) { | ||
375 | data_len = host->req->sg.length; | ||
376 | } else { | ||
377 | data_len = host->req->data_len; | ||
378 | host->use_dma = 0; | ||
379 | } | ||
380 | |||
381 | if (data_len <= 8) { | ||
382 | cmd &= ~(TPC_DATA_SEL | 0xf); | ||
383 | host->cmd_flags |= REG_DATA; | ||
384 | cmd |= data_len & 0xf; | ||
385 | host->use_dma = 0; | ||
386 | } | ||
387 | |||
388 | if (host->use_dma) { | ||
389 | if (1 != pci_map_sg(host->chip->pdev, &host->req->sg, 1, | ||
390 | host->req->data_dir == READ | ||
391 | ? PCI_DMA_FROMDEVICE | ||
392 | : PCI_DMA_TODEVICE)) { | ||
393 | host->req->error = -ENOMEM; | ||
394 | return host->req->error; | ||
395 | } | ||
396 | data_len = sg_dma_len(&host->req->sg); | ||
397 | writel(sg_dma_address(&host->req->sg), | ||
398 | host->addr + DMA_ADDRESS); | ||
399 | writel(((1 << 16) & BLOCK_COUNT_MASK) | ||
400 | | (data_len & BLOCK_SIZE_MASK), | ||
401 | host->addr + BLOCK); | ||
402 | writel(DMA_CONTROL_ENABLE, host->addr + DMA_CONTROL); | ||
403 | } else if (!(host->cmd_flags & REG_DATA)) { | ||
404 | writel(((1 << 16) & BLOCK_COUNT_MASK) | ||
405 | | (data_len & BLOCK_SIZE_MASK), | ||
406 | host->addr + BLOCK); | ||
407 | t_val = readl(host->addr + INT_STATUS_ENABLE); | ||
408 | t_val |= host->req->data_dir == READ | ||
409 | ? INT_STATUS_FIFO_RRDY | ||
410 | : INT_STATUS_FIFO_WRDY; | ||
411 | |||
412 | writel(t_val, host->addr + INT_STATUS_ENABLE); | ||
413 | writel(t_val, host->addr + INT_SIGNAL_ENABLE); | ||
414 | } else { | ||
415 | cmd &= ~(TPC_DATA_SEL | 0xf); | ||
416 | host->cmd_flags |= REG_DATA; | ||
417 | cmd |= data_len & 0xf; | ||
418 | |||
419 | if (host->req->data_dir == WRITE) { | ||
420 | jmb38x_ms_transfer_data(host); | ||
421 | writel(host->io_word[0], host->addr + TPC_P0); | ||
422 | writel(host->io_word[1], host->addr + TPC_P1); | ||
423 | } | ||
424 | } | ||
425 | |||
426 | mod_timer(&host->timer, jiffies + host->timeout_jiffies); | ||
427 | writel(HOST_CONTROL_LED | readl(host->addr + HOST_CONTROL), | ||
428 | host->addr + HOST_CONTROL); | ||
429 | host->req->error = 0; | ||
430 | |||
431 | writel(cmd, host->addr + TPC); | ||
432 | dev_dbg(msh->cdev.dev, "executing TPC %08x, len %x\n", cmd, data_len); | ||
433 | |||
434 | return 0; | ||
435 | } | ||
436 | |||
437 | static void jmb38x_ms_complete_cmd(struct memstick_host *msh, int last) | ||
438 | { | ||
439 | struct jmb38x_ms_host *host = memstick_priv(msh); | ||
440 | unsigned int t_val = 0; | ||
441 | int rc; | ||
442 | |||
443 | del_timer(&host->timer); | ||
444 | |||
445 | dev_dbg(msh->cdev.dev, "c control %08x\n", | ||
446 | readl(host->addr + HOST_CONTROL)); | ||
447 | dev_dbg(msh->cdev.dev, "c status %08x\n", | ||
448 | readl(host->addr + INT_STATUS)); | ||
449 | dev_dbg(msh->cdev.dev, "c hstatus %08x\n", readl(host->addr + STATUS)); | ||
450 | |||
451 | if (host->req->get_int_reg) { | ||
452 | t_val = readl(host->addr + TPC_P0); | ||
453 | host->req->int_reg = (t_val & 0xff); | ||
454 | } | ||
455 | |||
456 | if (host->use_dma) { | ||
457 | writel(0, host->addr + DMA_CONTROL); | ||
458 | pci_unmap_sg(host->chip->pdev, &host->req->sg, 1, | ||
459 | host->req->data_dir == READ | ||
460 | ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE); | ||
461 | } else { | ||
462 | t_val = readl(host->addr + INT_STATUS_ENABLE); | ||
463 | if (host->req->data_dir == READ) | ||
464 | t_val &= ~INT_STATUS_FIFO_RRDY; | ||
465 | else | ||
466 | t_val &= ~INT_STATUS_FIFO_WRDY; | ||
467 | |||
468 | writel(t_val, host->addr + INT_STATUS_ENABLE); | ||
469 | writel(t_val, host->addr + INT_SIGNAL_ENABLE); | ||
470 | } | ||
471 | |||
472 | writel((~HOST_CONTROL_LED) & readl(host->addr + HOST_CONTROL), | ||
473 | host->addr + HOST_CONTROL); | ||
474 | |||
475 | if (!last) { | ||
476 | do { | ||
477 | rc = memstick_next_req(msh, &host->req); | ||
478 | } while (!rc && jmb38x_ms_issue_cmd(msh)); | ||
479 | } else { | ||
480 | do { | ||
481 | rc = memstick_next_req(msh, &host->req); | ||
482 | if (!rc) | ||
483 | host->req->error = -ETIME; | ||
484 | } while (!rc); | ||
485 | } | ||
486 | } | ||
487 | |||
488 | static irqreturn_t jmb38x_ms_isr(int irq, void *dev_id) | ||
489 | { | ||
490 | struct memstick_host *msh = dev_id; | ||
491 | struct jmb38x_ms_host *host = memstick_priv(msh); | ||
492 | unsigned int irq_status; | ||
493 | |||
494 | spin_lock(&host->lock); | ||
495 | irq_status = readl(host->addr + INT_STATUS); | ||
496 | dev_dbg(&host->chip->pdev->dev, "irq_status = %08x\n", irq_status); | ||
497 | if (irq_status == 0 || irq_status == (~0)) { | ||
498 | spin_unlock(&host->lock); | ||
499 | return IRQ_NONE; | ||
500 | } | ||
501 | |||
502 | if (host->req) { | ||
503 | if (irq_status & INT_STATUS_ANY_ERR) { | ||
504 | if (irq_status & INT_STATUS_CRC_ERR) | ||
505 | host->req->error = -EILSEQ; | ||
506 | else | ||
507 | host->req->error = -ETIME; | ||
508 | } else { | ||
509 | if (host->use_dma) { | ||
510 | if (irq_status & INT_STATUS_EOTRAN) | ||
511 | host->cmd_flags |= FIFO_READY; | ||
512 | } else { | ||
513 | if (irq_status & (INT_STATUS_FIFO_RRDY | ||
514 | | INT_STATUS_FIFO_WRDY)) | ||
515 | jmb38x_ms_transfer_data(host); | ||
516 | |||
517 | if (irq_status & INT_STATUS_EOTRAN) { | ||
518 | jmb38x_ms_transfer_data(host); | ||
519 | host->cmd_flags |= FIFO_READY; | ||
520 | } | ||
521 | } | ||
522 | |||
523 | if (irq_status & INT_STATUS_EOTPC) { | ||
524 | host->cmd_flags |= CMD_READY; | ||
525 | if (host->cmd_flags & REG_DATA) { | ||
526 | if (host->req->data_dir == READ) { | ||
527 | host->io_word[0] | ||
528 | = readl(host->addr | ||
529 | + TPC_P0); | ||
530 | host->io_word[1] | ||
531 | = readl(host->addr | ||
532 | + TPC_P1); | ||
533 | host->io_pos = 8; | ||
534 | |||
535 | jmb38x_ms_transfer_data(host); | ||
536 | } | ||
537 | host->cmd_flags |= FIFO_READY; | ||
538 | } | ||
539 | } | ||
540 | } | ||
541 | } | ||
542 | |||
543 | if (irq_status & (INT_STATUS_MEDIA_IN | INT_STATUS_MEDIA_OUT)) { | ||
544 | dev_dbg(&host->chip->pdev->dev, "media changed\n"); | ||
545 | memstick_detect_change(msh); | ||
546 | } | ||
547 | |||
548 | writel(irq_status, host->addr + INT_STATUS); | ||
549 | |||
550 | if (host->req | ||
551 | && (((host->cmd_flags & CMD_READY) | ||
552 | && (host->cmd_flags & FIFO_READY)) | ||
553 | || host->req->error)) | ||
554 | jmb38x_ms_complete_cmd(msh, 0); | ||
555 | |||
556 | spin_unlock(&host->lock); | ||
557 | return IRQ_HANDLED; | ||
558 | } | ||
559 | |||
560 | static void jmb38x_ms_abort(unsigned long data) | ||
561 | { | ||
562 | struct memstick_host *msh = (struct memstick_host *)data; | ||
563 | struct jmb38x_ms_host *host = memstick_priv(msh); | ||
564 | unsigned long flags; | ||
565 | |||
566 | dev_dbg(&host->chip->pdev->dev, "abort\n"); | ||
567 | spin_lock_irqsave(&host->lock, flags); | ||
568 | if (host->req) { | ||
569 | host->req->error = -ETIME; | ||
570 | jmb38x_ms_complete_cmd(msh, 0); | ||
571 | } | ||
572 | spin_unlock_irqrestore(&host->lock, flags); | ||
573 | } | ||
574 | |||
575 | static void jmb38x_ms_request(struct memstick_host *msh) | ||
576 | { | ||
577 | struct jmb38x_ms_host *host = memstick_priv(msh); | ||
578 | unsigned long flags; | ||
579 | int rc; | ||
580 | |||
581 | spin_lock_irqsave(&host->lock, flags); | ||
582 | if (host->req) { | ||
583 | spin_unlock_irqrestore(&host->lock, flags); | ||
584 | BUG(); | ||
585 | return; | ||
586 | } | ||
587 | |||
588 | do { | ||
589 | rc = memstick_next_req(msh, &host->req); | ||
590 | } while (!rc && jmb38x_ms_issue_cmd(msh)); | ||
591 | spin_unlock_irqrestore(&host->lock, flags); | ||
592 | } | ||
593 | |||
594 | static void jmb38x_ms_reset(struct jmb38x_ms_host *host) | ||
595 | { | ||
596 | unsigned int host_ctl = readl(host->addr + HOST_CONTROL); | ||
597 | |||
598 | writel(host_ctl | HOST_CONTROL_RESET_REQ | HOST_CONTROL_RESET, | ||
599 | host->addr + HOST_CONTROL); | ||
600 | |||
601 | while (HOST_CONTROL_RESET_REQ | ||
602 | & (host_ctl = readl(host->addr + HOST_CONTROL))) { | ||
603 | ndelay(100); | ||
604 | dev_dbg(&host->chip->pdev->dev, "reset\n"); | ||
605 | } | ||
606 | |||
607 | writel(INT_STATUS_ALL, host->addr + INT_STATUS_ENABLE); | ||
608 | writel(INT_STATUS_ALL, host->addr + INT_SIGNAL_ENABLE); | ||
609 | |||
610 | dev_dbg(&host->chip->pdev->dev, "reset\n"); | ||
611 | } | ||
612 | |||
613 | static void jmb38x_ms_set_param(struct memstick_host *msh, | ||
614 | enum memstick_param param, | ||
615 | int value) | ||
616 | { | ||
617 | struct jmb38x_ms_host *host = memstick_priv(msh); | ||
618 | unsigned int host_ctl; | ||
619 | unsigned long flags; | ||
620 | |||
621 | spin_lock_irqsave(&host->lock, flags); | ||
622 | |||
623 | switch (param) { | ||
624 | case MEMSTICK_POWER: | ||
625 | if (value == MEMSTICK_POWER_ON) { | ||
626 | jmb38x_ms_reset(host); | ||
627 | |||
628 | writel(host->id ? PAD_PU_PD_ON_MS_SOCK1 | ||
629 | : PAD_PU_PD_ON_MS_SOCK0, | ||
630 | host->addr + PAD_PU_PD); | ||
631 | |||
632 | writel(PAD_OUTPUT_ENABLE_MS, | ||
633 | host->addr + PAD_OUTPUT_ENABLE); | ||
634 | |||
635 | host_ctl = readl(host->addr + HOST_CONTROL); | ||
636 | host_ctl |= 7; | ||
637 | writel(host_ctl | (HOST_CONTROL_POWER_EN | ||
638 | | HOST_CONTROL_CLOCK_EN), | ||
639 | host->addr + HOST_CONTROL); | ||
640 | |||
641 | dev_dbg(&host->chip->pdev->dev, "power on\n"); | ||
642 | } else if (value == MEMSTICK_POWER_OFF) { | ||
643 | writel(readl(host->addr + HOST_CONTROL) | ||
644 | & ~(HOST_CONTROL_POWER_EN | ||
645 | | HOST_CONTROL_CLOCK_EN), | ||
646 | host->addr + HOST_CONTROL); | ||
647 | writel(0, host->addr + PAD_OUTPUT_ENABLE); | ||
648 | writel(PAD_PU_PD_OFF, host->addr + PAD_PU_PD); | ||
649 | dev_dbg(&host->chip->pdev->dev, "power off\n"); | ||
650 | } | ||
651 | break; | ||
652 | case MEMSTICK_INTERFACE: | ||
653 | /* jmb38x_ms_reset(host); */ | ||
654 | |||
655 | host_ctl = readl(host->addr + HOST_CONTROL); | ||
656 | host_ctl &= ~(3 << HOST_CONTROL_IF_SHIFT); | ||
657 | /* host_ctl |= 7; */ | ||
658 | |||
659 | if (value == MEMSTICK_SERIAL) { | ||
660 | host_ctl &= ~HOST_CONTROL_FAST_CLK; | ||
661 | host_ctl |= HOST_CONTROL_IF_SERIAL | ||
662 | << HOST_CONTROL_IF_SHIFT; | ||
663 | host_ctl |= HOST_CONTROL_REI; | ||
664 | writel(0, host->addr + CLOCK_DELAY); | ||
665 | } else if (value == MEMSTICK_PAR4) { | ||
666 | host_ctl |= HOST_CONTROL_FAST_CLK; | ||
667 | host_ctl |= HOST_CONTROL_IF_PAR4 | ||
668 | << HOST_CONTROL_IF_SHIFT; | ||
669 | host_ctl &= ~HOST_CONTROL_REI; | ||
670 | writel(4, host->addr + CLOCK_DELAY); | ||
671 | } else if (value == MEMSTICK_PAR8) { | ||
672 | host_ctl |= HOST_CONTROL_FAST_CLK; | ||
673 | host_ctl |= HOST_CONTROL_IF_PAR8 | ||
674 | << HOST_CONTROL_IF_SHIFT; | ||
675 | host_ctl &= ~HOST_CONTROL_REI; | ||
676 | writel(4, host->addr + CLOCK_DELAY); | ||
677 | } | ||
678 | writel(host_ctl, host->addr + HOST_CONTROL); | ||
679 | break; | ||
680 | }; | ||
681 | |||
682 | spin_unlock_irqrestore(&host->lock, flags); | ||
683 | } | ||
684 | |||
685 | #ifdef CONFIG_PM | ||
686 | |||
687 | static int jmb38x_ms_suspend(struct pci_dev *dev, pm_message_t state) | ||
688 | { | ||
689 | struct jmb38x_ms *jm = pci_get_drvdata(dev); | ||
690 | int cnt; | ||
691 | |||
692 | for (cnt = 0; cnt < jm->host_cnt; ++cnt) { | ||
693 | if (!jm->hosts[cnt]) | ||
694 | break; | ||
695 | memstick_suspend_host(jm->hosts[cnt]); | ||
696 | } | ||
697 | |||
698 | pci_save_state(dev); | ||
699 | pci_enable_wake(dev, pci_choose_state(dev, state), 0); | ||
700 | pci_disable_device(dev); | ||
701 | pci_set_power_state(dev, pci_choose_state(dev, state)); | ||
702 | return 0; | ||
703 | } | ||
704 | |||
705 | static int jmb38x_ms_resume(struct pci_dev *dev) | ||
706 | { | ||
707 | struct jmb38x_ms *jm = pci_get_drvdata(dev); | ||
708 | int rc; | ||
709 | |||
710 | pci_set_power_state(dev, PCI_D0); | ||
711 | pci_restore_state(dev); | ||
712 | rc = pci_enable_device(dev); | ||
713 | if (rc) | ||
714 | return rc; | ||
715 | pci_set_master(dev); | ||
716 | |||
717 | pci_read_config_dword(dev, 0xac, &rc); | ||
718 | pci_write_config_dword(dev, 0xac, rc | 0x00470000); | ||
719 | |||
720 | for (rc = 0; rc < jm->host_cnt; ++rc) { | ||
721 | if (!jm->hosts[rc]) | ||
722 | break; | ||
723 | memstick_resume_host(jm->hosts[rc]); | ||
724 | memstick_detect_change(jm->hosts[rc]); | ||
725 | } | ||
726 | |||
727 | return 0; | ||
728 | } | ||
729 | |||
730 | #else | ||
731 | |||
732 | #define jmb38x_ms_suspend NULL | ||
733 | #define jmb38x_ms_resume NULL | ||
734 | |||
735 | #endif /* CONFIG_PM */ | ||
736 | |||
737 | static int jmb38x_ms_count_slots(struct pci_dev *pdev) | ||
738 | { | ||
739 | int cnt, rc = 0; | ||
740 | |||
741 | for (cnt = 0; cnt < PCI_ROM_RESOURCE; ++cnt) { | ||
742 | if (!(IORESOURCE_MEM & pci_resource_flags(pdev, cnt))) | ||
743 | break; | ||
744 | |||
745 | if (256 != pci_resource_len(pdev, cnt)) | ||
746 | break; | ||
747 | |||
748 | ++rc; | ||
749 | } | ||
750 | return rc; | ||
751 | } | ||
752 | |||
753 | static struct memstick_host *jmb38x_ms_alloc_host(struct jmb38x_ms *jm, int cnt) | ||
754 | { | ||
755 | struct memstick_host *msh; | ||
756 | struct jmb38x_ms_host *host; | ||
757 | |||
758 | msh = memstick_alloc_host(sizeof(struct jmb38x_ms_host), | ||
759 | &jm->pdev->dev); | ||
760 | if (!msh) | ||
761 | return NULL; | ||
762 | |||
763 | host = memstick_priv(msh); | ||
764 | host->chip = jm; | ||
765 | host->addr = ioremap(pci_resource_start(jm->pdev, cnt), | ||
766 | pci_resource_len(jm->pdev, cnt)); | ||
767 | if (!host->addr) | ||
768 | goto err_out_free; | ||
769 | |||
770 | spin_lock_init(&host->lock); | ||
771 | host->id = cnt; | ||
772 | snprintf(host->host_id, DEVICE_ID_SIZE, DRIVER_NAME ":slot%d", | ||
773 | host->id); | ||
774 | host->irq = jm->pdev->irq; | ||
775 | host->timeout_jiffies = msecs_to_jiffies(4000); | ||
776 | msh->request = jmb38x_ms_request; | ||
777 | msh->set_param = jmb38x_ms_set_param; | ||
778 | /* | ||
779 | msh->caps = MEMSTICK_CAP_AUTO_GET_INT | MEMSTICK_CAP_PAR4 | ||
780 | | MEMSTICK_CAP_PAR8; | ||
781 | */ | ||
782 | msh->caps = MEMSTICK_CAP_PAR4 | MEMSTICK_CAP_PAR8; | ||
783 | |||
784 | setup_timer(&host->timer, jmb38x_ms_abort, (unsigned long)msh); | ||
785 | |||
786 | if (!request_irq(host->irq, jmb38x_ms_isr, IRQF_SHARED, host->host_id, | ||
787 | msh)) | ||
788 | return msh; | ||
789 | |||
790 | iounmap(host->addr); | ||
791 | err_out_free: | ||
792 | kfree(msh); | ||
793 | return NULL; | ||
794 | } | ||
795 | |||
796 | static void jmb38x_ms_free_host(struct memstick_host *msh) | ||
797 | { | ||
798 | struct jmb38x_ms_host *host = memstick_priv(msh); | ||
799 | |||
800 | free_irq(host->irq, msh); | ||
801 | iounmap(host->addr); | ||
802 | memstick_free_host(msh); | ||
803 | } | ||
804 | |||
805 | static int jmb38x_ms_probe(struct pci_dev *pdev, | ||
806 | const struct pci_device_id *dev_id) | ||
807 | { | ||
808 | struct jmb38x_ms *jm; | ||
809 | int pci_dev_busy = 0; | ||
810 | int rc, cnt; | ||
811 | |||
812 | rc = pci_set_dma_mask(pdev, DMA_32BIT_MASK); | ||
813 | if (rc) | ||
814 | return rc; | ||
815 | |||
816 | rc = pci_enable_device(pdev); | ||
817 | if (rc) | ||
818 | return rc; | ||
819 | |||
820 | pci_set_master(pdev); | ||
821 | |||
822 | rc = pci_request_regions(pdev, DRIVER_NAME); | ||
823 | if (rc) { | ||
824 | pci_dev_busy = 1; | ||
825 | goto err_out; | ||
826 | } | ||
827 | |||
828 | pci_read_config_dword(pdev, 0xac, &rc); | ||
829 | pci_write_config_dword(pdev, 0xac, rc | 0x00470000); | ||
830 | |||
831 | cnt = jmb38x_ms_count_slots(pdev); | ||
832 | if (!cnt) { | ||
833 | rc = -ENODEV; | ||
834 | pci_dev_busy = 1; | ||
835 | goto err_out; | ||
836 | } | ||
837 | |||
838 | jm = kzalloc(sizeof(struct jmb38x_ms) | ||
839 | + cnt * sizeof(struct memstick_host *), GFP_KERNEL); | ||
840 | if (!jm) { | ||
841 | rc = -ENOMEM; | ||
842 | goto err_out_int; | ||
843 | } | ||
844 | |||
845 | jm->pdev = pdev; | ||
846 | jm->host_cnt = cnt; | ||
847 | pci_set_drvdata(pdev, jm); | ||
848 | |||
849 | for (cnt = 0; cnt < jm->host_cnt; ++cnt) { | ||
850 | jm->hosts[cnt] = jmb38x_ms_alloc_host(jm, cnt); | ||
851 | if (!jm->hosts[cnt]) | ||
852 | break; | ||
853 | |||
854 | rc = memstick_add_host(jm->hosts[cnt]); | ||
855 | |||
856 | if (rc) { | ||
857 | jmb38x_ms_free_host(jm->hosts[cnt]); | ||
858 | jm->hosts[cnt] = NULL; | ||
859 | break; | ||
860 | } | ||
861 | } | ||
862 | |||
863 | if (cnt) | ||
864 | return 0; | ||
865 | |||
866 | rc = -ENODEV; | ||
867 | |||
868 | pci_set_drvdata(pdev, NULL); | ||
869 | kfree(jm); | ||
870 | err_out_int: | ||
871 | pci_release_regions(pdev); | ||
872 | err_out: | ||
873 | if (!pci_dev_busy) | ||
874 | pci_disable_device(pdev); | ||
875 | return rc; | ||
876 | } | ||
877 | |||
878 | static void jmb38x_ms_remove(struct pci_dev *dev) | ||
879 | { | ||
880 | struct jmb38x_ms *jm = pci_get_drvdata(dev); | ||
881 | struct jmb38x_ms_host *host; | ||
882 | int cnt; | ||
883 | unsigned long flags; | ||
884 | |||
885 | for (cnt = 0; cnt < jm->host_cnt; ++cnt) { | ||
886 | if (!jm->hosts[cnt]) | ||
887 | break; | ||
888 | |||
889 | host = memstick_priv(jm->hosts[cnt]); | ||
890 | |||
891 | writel(0, host->addr + INT_SIGNAL_ENABLE); | ||
892 | writel(0, host->addr + INT_STATUS_ENABLE); | ||
893 | mmiowb(); | ||
894 | dev_dbg(&jm->pdev->dev, "interrupts off\n"); | ||
895 | spin_lock_irqsave(&host->lock, flags); | ||
896 | if (host->req) { | ||
897 | host->req->error = -ETIME; | ||
898 | jmb38x_ms_complete_cmd(jm->hosts[cnt], 1); | ||
899 | } | ||
900 | spin_unlock_irqrestore(&host->lock, flags); | ||
901 | |||
902 | memstick_remove_host(jm->hosts[cnt]); | ||
903 | dev_dbg(&jm->pdev->dev, "host removed\n"); | ||
904 | |||
905 | jmb38x_ms_free_host(jm->hosts[cnt]); | ||
906 | } | ||
907 | |||
908 | pci_set_drvdata(dev, NULL); | ||
909 | pci_release_regions(dev); | ||
910 | pci_disable_device(dev); | ||
911 | kfree(jm); | ||
912 | } | ||
913 | |||
914 | static struct pci_device_id jmb38x_ms_id_tbl [] = { | ||
915 | { PCI_VENDOR_ID_JMICRON, PCI_DEVICE_ID_JMICRON_JMB38X_MS, PCI_ANY_ID, | ||
916 | PCI_ANY_ID, 0, 0, 0 }, | ||
917 | { } | ||
918 | }; | ||
919 | |||
920 | static struct pci_driver jmb38x_ms_driver = { | ||
921 | .name = DRIVER_NAME, | ||
922 | .id_table = jmb38x_ms_id_tbl, | ||
923 | .probe = jmb38x_ms_probe, | ||
924 | .remove = jmb38x_ms_remove, | ||
925 | .suspend = jmb38x_ms_suspend, | ||
926 | .resume = jmb38x_ms_resume | ||
927 | }; | ||
928 | |||
929 | static int __init jmb38x_ms_init(void) | ||
930 | { | ||
931 | return pci_register_driver(&jmb38x_ms_driver); | ||
932 | } | ||
933 | |||
934 | static void __exit jmb38x_ms_exit(void) | ||
935 | { | ||
936 | pci_unregister_driver(&jmb38x_ms_driver); | ||
937 | } | ||
938 | |||
939 | MODULE_AUTHOR("Alex Dubov"); | ||
940 | MODULE_DESCRIPTION("JMicron jmb38x MemoryStick driver"); | ||
941 | MODULE_LICENSE("GPL"); | ||
942 | MODULE_DEVICE_TABLE(pci, jmb38x_ms_id_tbl); | ||
943 | |||
944 | module_init(jmb38x_ms_init); | ||
945 | module_exit(jmb38x_ms_exit); | ||