Commit | Line | Data |
---|---|---|
60fdd931 AD |
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> | |
d3597ea2 | 15 | #include <linux/dma-mapping.h> |
60fdd931 AD |
16 | #include <linux/delay.h> |
17 | #include <linux/highmem.h> | |
18 | #include <linux/memstick.h> | |
5a0e3ad6 | 19 | #include <linux/slab.h> |
c47e7893 | 20 | #include <linux/module.h> |
60fdd931 AD |
21 | |
22 | #define DRIVER_NAME "jmb38x_ms" | |
23 | ||
90ab5ee9 | 24 | static bool no_dma; |
60fdd931 AD |
25 | module_param(no_dma, bool, 0644); |
26 | ||
27 | enum { | |
28 | DMA_ADDRESS = 0x00, | |
29 | BLOCK = 0x04, | |
30 | DMA_CONTROL = 0x08, | |
31 | TPC_P0 = 0x0c, | |
32 | TPC_P1 = 0x10, | |
33 | TPC = 0x14, | |
34 | HOST_CONTROL = 0x18, | |
35 | DATA = 0x1c, | |
36 | STATUS = 0x20, | |
37 | INT_STATUS = 0x24, | |
38 | INT_STATUS_ENABLE = 0x28, | |
39 | INT_SIGNAL_ENABLE = 0x2c, | |
40 | TIMER = 0x30, | |
41 | TIMER_CONTROL = 0x34, | |
42 | PAD_OUTPUT_ENABLE = 0x38, | |
43 | PAD_PU_PD = 0x3c, | |
44 | CLOCK_DELAY = 0x40, | |
45 | ADMA_ADDRESS = 0x44, | |
46 | CLOCK_CONTROL = 0x48, | |
47 | LED_CONTROL = 0x4c, | |
48 | VERSION = 0x50 | |
49 | }; | |
50 | ||
51 | struct jmb38x_ms_host { | |
52 | struct jmb38x_ms *chip; | |
53 | void __iomem *addr; | |
54 | spinlock_t lock; | |
f1d82698 | 55 | struct tasklet_struct notify; |
60fdd931 | 56 | int id; |
b98cb4b7 | 57 | char host_id[32]; |
60fdd931 AD |
58 | int irq; |
59 | unsigned int block_pos; | |
60 | unsigned long timeout_jiffies; | |
61 | struct timer_list timer; | |
6243d38f | 62 | struct memstick_host *msh; |
60fdd931 | 63 | struct memstick_request *req; |
60fdd931 AD |
64 | unsigned char cmd_flags; |
65 | unsigned char io_pos; | |
23c5947a | 66 | unsigned char ifmode; |
60fdd931 AD |
67 | unsigned int io_word[2]; |
68 | }; | |
69 | ||
70 | struct jmb38x_ms { | |
71 | struct pci_dev *pdev; | |
72 | int host_cnt; | |
73 | struct memstick_host *hosts[]; | |
74 | }; | |
75 | ||
76 | #define BLOCK_COUNT_MASK 0xffff0000 | |
77 | #define BLOCK_SIZE_MASK 0x00000fff | |
78 | ||
79 | #define DMA_CONTROL_ENABLE 0x00000001 | |
80 | ||
81 | #define TPC_DATA_SEL 0x00008000 | |
82 | #define TPC_DIR 0x00004000 | |
83 | #define TPC_WAIT_INT 0x00002000 | |
84 | #define TPC_GET_INT 0x00000800 | |
85 | #define TPC_CODE_SZ_MASK 0x00000700 | |
86 | #define TPC_DATA_SZ_MASK 0x00000007 | |
87 | ||
8e82f8c3 AD |
88 | #define HOST_CONTROL_TDELAY_EN 0x00040000 |
89 | #define HOST_CONTROL_HW_OC_P 0x00010000 | |
60fdd931 AD |
90 | #define HOST_CONTROL_RESET_REQ 0x00008000 |
91 | #define HOST_CONTROL_REI 0x00004000 | |
92 | #define HOST_CONTROL_LED 0x00000400 | |
93 | #define HOST_CONTROL_FAST_CLK 0x00000200 | |
94 | #define HOST_CONTROL_RESET 0x00000100 | |
95 | #define HOST_CONTROL_POWER_EN 0x00000080 | |
96 | #define HOST_CONTROL_CLOCK_EN 0x00000040 | |
8e82f8c3 | 97 | #define HOST_CONTROL_REO 0x00000008 |
60fdd931 AD |
98 | #define HOST_CONTROL_IF_SHIFT 4 |
99 | ||
100 | #define HOST_CONTROL_IF_SERIAL 0x0 | |
101 | #define HOST_CONTROL_IF_PAR4 0x1 | |
102 | #define HOST_CONTROL_IF_PAR8 0x3 | |
103 | ||
ead70773 AD |
104 | #define STATUS_BUSY 0x00080000 |
105 | #define STATUS_MS_DAT7 0x00040000 | |
106 | #define STATUS_MS_DAT6 0x00020000 | |
107 | #define STATUS_MS_DAT5 0x00010000 | |
108 | #define STATUS_MS_DAT4 0x00008000 | |
109 | #define STATUS_MS_DAT3 0x00004000 | |
110 | #define STATUS_MS_DAT2 0x00002000 | |
111 | #define STATUS_MS_DAT1 0x00001000 | |
112 | #define STATUS_MS_DAT0 0x00000800 | |
60fdd931 AD |
113 | #define STATUS_HAS_MEDIA 0x00000400 |
114 | #define STATUS_FIFO_EMPTY 0x00000200 | |
115 | #define STATUS_FIFO_FULL 0x00000100 | |
ead70773 AD |
116 | #define STATUS_MS_CED 0x00000080 |
117 | #define STATUS_MS_ERR 0x00000040 | |
118 | #define STATUS_MS_BRQ 0x00000020 | |
119 | #define STATUS_MS_CNK 0x00000001 | |
60fdd931 AD |
120 | |
121 | #define INT_STATUS_TPC_ERR 0x00080000 | |
122 | #define INT_STATUS_CRC_ERR 0x00040000 | |
123 | #define INT_STATUS_TIMER_TO 0x00020000 | |
124 | #define INT_STATUS_HSK_TO 0x00010000 | |
125 | #define INT_STATUS_ANY_ERR 0x00008000 | |
126 | #define INT_STATUS_FIFO_WRDY 0x00000080 | |
127 | #define INT_STATUS_FIFO_RRDY 0x00000040 | |
128 | #define INT_STATUS_MEDIA_OUT 0x00000010 | |
129 | #define INT_STATUS_MEDIA_IN 0x00000008 | |
130 | #define INT_STATUS_DMA_BOUNDARY 0x00000004 | |
131 | #define INT_STATUS_EOTRAN 0x00000002 | |
132 | #define INT_STATUS_EOTPC 0x00000001 | |
133 | ||
134 | #define INT_STATUS_ALL 0x000f801f | |
135 | ||
136 | #define PAD_OUTPUT_ENABLE_MS 0x0F3F | |
137 | ||
138 | #define PAD_PU_PD_OFF 0x7FFF0000 | |
139 | #define PAD_PU_PD_ON_MS_SOCK0 0x5f8f0000 | |
140 | #define PAD_PU_PD_ON_MS_SOCK1 0x0f0f0000 | |
141 | ||
23c5947a | 142 | #define CLOCK_CONTROL_BY_MMIO 0x00000008 |
cf821e8f | 143 | #define CLOCK_CONTROL_40MHZ 0x00000001 |
23c5947a TI |
144 | #define CLOCK_CONTROL_50MHZ 0x00000002 |
145 | #define CLOCK_CONTROL_60MHZ 0x00000010 | |
146 | #define CLOCK_CONTROL_62_5MHZ 0x00000004 | |
cf821e8f AD |
147 | #define CLOCK_CONTROL_OFF 0x00000000 |
148 | ||
8e82f8c3 | 149 | #define PCI_CTL_CLOCK_DLY_ADDR 0x000000b0 |
8e82f8c3 | 150 | |
60fdd931 AD |
151 | enum { |
152 | CMD_READY = 0x01, | |
153 | FIFO_READY = 0x02, | |
154 | REG_DATA = 0x04, | |
ead70773 | 155 | DMA_DATA = 0x08 |
60fdd931 AD |
156 | }; |
157 | ||
158 | static unsigned int jmb38x_ms_read_data(struct jmb38x_ms_host *host, | |
159 | unsigned char *buf, unsigned int length) | |
160 | { | |
161 | unsigned int off = 0; | |
162 | ||
163 | while (host->io_pos && length) { | |
164 | buf[off++] = host->io_word[0] & 0xff; | |
165 | host->io_word[0] >>= 8; | |
166 | length--; | |
167 | host->io_pos--; | |
168 | } | |
169 | ||
170 | if (!length) | |
171 | return off; | |
172 | ||
173 | while (!(STATUS_FIFO_EMPTY & readl(host->addr + STATUS))) { | |
174 | if (length < 4) | |
175 | break; | |
176 | *(unsigned int *)(buf + off) = __raw_readl(host->addr + DATA); | |
177 | length -= 4; | |
178 | off += 4; | |
179 | } | |
180 | ||
181 | if (length | |
182 | && !(STATUS_FIFO_EMPTY & readl(host->addr + STATUS))) { | |
183 | host->io_word[0] = readl(host->addr + DATA); | |
184 | for (host->io_pos = 4; host->io_pos; --host->io_pos) { | |
185 | buf[off++] = host->io_word[0] & 0xff; | |
186 | host->io_word[0] >>= 8; | |
187 | length--; | |
188 | if (!length) | |
189 | break; | |
190 | } | |
191 | } | |
192 | ||
193 | return off; | |
194 | } | |
195 | ||
196 | static unsigned int jmb38x_ms_read_reg_data(struct jmb38x_ms_host *host, | |
197 | unsigned char *buf, | |
198 | unsigned int length) | |
199 | { | |
200 | unsigned int off = 0; | |
201 | ||
202 | while (host->io_pos > 4 && length) { | |
203 | buf[off++] = host->io_word[0] & 0xff; | |
204 | host->io_word[0] >>= 8; | |
205 | length--; | |
206 | host->io_pos--; | |
207 | } | |
208 | ||
209 | if (!length) | |
210 | return off; | |
211 | ||
212 | while (host->io_pos && length) { | |
213 | buf[off++] = host->io_word[1] & 0xff; | |
214 | host->io_word[1] >>= 8; | |
215 | length--; | |
216 | host->io_pos--; | |
217 | } | |
218 | ||
219 | return off; | |
220 | } | |
221 | ||
222 | static unsigned int jmb38x_ms_write_data(struct jmb38x_ms_host *host, | |
223 | unsigned char *buf, | |
224 | unsigned int length) | |
225 | { | |
226 | unsigned int off = 0; | |
227 | ||
228 | if (host->io_pos) { | |
229 | while (host->io_pos < 4 && length) { | |
230 | host->io_word[0] |= buf[off++] << (host->io_pos * 8); | |
231 | host->io_pos++; | |
232 | length--; | |
233 | } | |
234 | } | |
235 | ||
236 | if (host->io_pos == 4 | |
237 | && !(STATUS_FIFO_FULL & readl(host->addr + STATUS))) { | |
238 | writel(host->io_word[0], host->addr + DATA); | |
239 | host->io_pos = 0; | |
240 | host->io_word[0] = 0; | |
241 | } else if (host->io_pos) { | |
242 | return off; | |
243 | } | |
244 | ||
245 | if (!length) | |
246 | return off; | |
247 | ||
248 | while (!(STATUS_FIFO_FULL & readl(host->addr + STATUS))) { | |
249 | if (length < 4) | |
250 | break; | |
251 | ||
252 | __raw_writel(*(unsigned int *)(buf + off), | |
253 | host->addr + DATA); | |
254 | length -= 4; | |
255 | off += 4; | |
256 | } | |
257 | ||
258 | switch (length) { | |
259 | case 3: | |
260 | host->io_word[0] |= buf[off + 2] << 16; | |
261 | host->io_pos++; | |
262 | case 2: | |
263 | host->io_word[0] |= buf[off + 1] << 8; | |
264 | host->io_pos++; | |
265 | case 1: | |
266 | host->io_word[0] |= buf[off]; | |
267 | host->io_pos++; | |
268 | } | |
269 | ||
270 | off += host->io_pos; | |
271 | ||
272 | return off; | |
273 | } | |
274 | ||
275 | static unsigned int jmb38x_ms_write_reg_data(struct jmb38x_ms_host *host, | |
276 | unsigned char *buf, | |
277 | unsigned int length) | |
278 | { | |
279 | unsigned int off = 0; | |
280 | ||
281 | while (host->io_pos < 4 && length) { | |
282 | host->io_word[0] &= ~(0xff << (host->io_pos * 8)); | |
283 | host->io_word[0] |= buf[off++] << (host->io_pos * 8); | |
284 | host->io_pos++; | |
285 | length--; | |
286 | } | |
287 | ||
288 | if (!length) | |
289 | return off; | |
290 | ||
291 | while (host->io_pos < 8 && length) { | |
292 | host->io_word[1] &= ~(0xff << (host->io_pos * 8)); | |
293 | host->io_word[1] |= buf[off++] << (host->io_pos * 8); | |
294 | host->io_pos++; | |
295 | length--; | |
296 | } | |
297 | ||
298 | return off; | |
299 | } | |
300 | ||
301 | static int jmb38x_ms_transfer_data(struct jmb38x_ms_host *host) | |
302 | { | |
303 | unsigned int length; | |
304 | unsigned int off; | |
8195096b | 305 | unsigned int t_size, p_cnt; |
60fdd931 AD |
306 | unsigned char *buf; |
307 | struct page *pg; | |
308 | unsigned long flags = 0; | |
309 | ||
310 | if (host->req->long_data) { | |
311 | length = host->req->sg.length - host->block_pos; | |
312 | off = host->req->sg.offset + host->block_pos; | |
313 | } else { | |
314 | length = host->req->data_len - host->block_pos; | |
315 | off = 0; | |
316 | } | |
317 | ||
318 | while (length) { | |
8195096b AM |
319 | unsigned int uninitialized_var(p_off); |
320 | ||
60fdd931 AD |
321 | if (host->req->long_data) { |
322 | pg = nth_page(sg_page(&host->req->sg), | |
323 | off >> PAGE_SHIFT); | |
324 | p_off = offset_in_page(off); | |
325 | p_cnt = PAGE_SIZE - p_off; | |
326 | p_cnt = min(p_cnt, length); | |
327 | ||
328 | local_irq_save(flags); | |
eb3f0620 | 329 | buf = kmap_atomic(pg) + p_off; |
60fdd931 AD |
330 | } else { |
331 | buf = host->req->data + host->block_pos; | |
332 | p_cnt = host->req->data_len - host->block_pos; | |
333 | } | |
334 | ||
335 | if (host->req->data_dir == WRITE) | |
336 | t_size = !(host->cmd_flags & REG_DATA) | |
337 | ? jmb38x_ms_write_data(host, buf, p_cnt) | |
338 | : jmb38x_ms_write_reg_data(host, buf, p_cnt); | |
339 | else | |
340 | t_size = !(host->cmd_flags & REG_DATA) | |
341 | ? jmb38x_ms_read_data(host, buf, p_cnt) | |
342 | : jmb38x_ms_read_reg_data(host, buf, p_cnt); | |
343 | ||
344 | if (host->req->long_data) { | |
eb3f0620 | 345 | kunmap_atomic(buf - p_off); |
60fdd931 AD |
346 | local_irq_restore(flags); |
347 | } | |
348 | ||
349 | if (!t_size) | |
350 | break; | |
351 | host->block_pos += t_size; | |
352 | length -= t_size; | |
353 | off += t_size; | |
354 | } | |
355 | ||
356 | if (!length && host->req->data_dir == WRITE) { | |
357 | if (host->cmd_flags & REG_DATA) { | |
358 | writel(host->io_word[0], host->addr + TPC_P0); | |
359 | writel(host->io_word[1], host->addr + TPC_P1); | |
360 | } else if (host->io_pos) { | |
361 | writel(host->io_word[0], host->addr + DATA); | |
362 | } | |
363 | } | |
364 | ||
365 | return length; | |
366 | } | |
367 | ||
368 | static int jmb38x_ms_issue_cmd(struct memstick_host *msh) | |
369 | { | |
370 | struct jmb38x_ms_host *host = memstick_priv(msh); | |
371 | unsigned char *data; | |
372 | unsigned int data_len, cmd, t_val; | |
373 | ||
374 | if (!(STATUS_HAS_MEDIA & readl(host->addr + STATUS))) { | |
c4c66cf1 | 375 | dev_dbg(&msh->dev, "no media status\n"); |
60fdd931 AD |
376 | host->req->error = -ETIME; |
377 | return host->req->error; | |
378 | } | |
379 | ||
8e82f8c3 | 380 | dev_dbg(&msh->dev, "control %08x\n", readl(host->addr + HOST_CONTROL)); |
c4c66cf1 GKH |
381 | dev_dbg(&msh->dev, "status %08x\n", readl(host->addr + INT_STATUS)); |
382 | dev_dbg(&msh->dev, "hstatus %08x\n", readl(host->addr + STATUS)); | |
60fdd931 AD |
383 | |
384 | host->cmd_flags = 0; | |
385 | host->block_pos = 0; | |
386 | host->io_pos = 0; | |
387 | host->io_word[0] = 0; | |
388 | host->io_word[1] = 0; | |
389 | ||
390 | cmd = host->req->tpc << 16; | |
391 | cmd |= TPC_DATA_SEL; | |
392 | ||
393 | if (host->req->data_dir == READ) | |
394 | cmd |= TPC_DIR; | |
23c5947a TI |
395 | |
396 | if (host->req->need_card_int) { | |
397 | if (host->ifmode == MEMSTICK_SERIAL) | |
398 | cmd |= TPC_GET_INT; | |
399 | else | |
400 | cmd |= TPC_WAIT_INT; | |
401 | } | |
60fdd931 AD |
402 | |
403 | data = host->req->data; | |
404 | ||
ead70773 AD |
405 | if (!no_dma) |
406 | host->cmd_flags |= DMA_DATA; | |
60fdd931 AD |
407 | |
408 | if (host->req->long_data) { | |
409 | data_len = host->req->sg.length; | |
410 | } else { | |
411 | data_len = host->req->data_len; | |
ead70773 | 412 | host->cmd_flags &= ~DMA_DATA; |
60fdd931 AD |
413 | } |
414 | ||
415 | if (data_len <= 8) { | |
416 | cmd &= ~(TPC_DATA_SEL | 0xf); | |
417 | host->cmd_flags |= REG_DATA; | |
418 | cmd |= data_len & 0xf; | |
ead70773 | 419 | host->cmd_flags &= ~DMA_DATA; |
60fdd931 AD |
420 | } |
421 | ||
ead70773 | 422 | if (host->cmd_flags & DMA_DATA) { |
43abdbce | 423 | if (1 != dma_map_sg(&host->chip->pdev->dev, &host->req->sg, 1, |
60fdd931 | 424 | host->req->data_dir == READ |
43abdbce QL |
425 | ? DMA_FROM_DEVICE |
426 | : DMA_TO_DEVICE)) { | |
60fdd931 AD |
427 | host->req->error = -ENOMEM; |
428 | return host->req->error; | |
429 | } | |
430 | data_len = sg_dma_len(&host->req->sg); | |
431 | writel(sg_dma_address(&host->req->sg), | |
432 | host->addr + DMA_ADDRESS); | |
433 | writel(((1 << 16) & BLOCK_COUNT_MASK) | |
434 | | (data_len & BLOCK_SIZE_MASK), | |
435 | host->addr + BLOCK); | |
436 | writel(DMA_CONTROL_ENABLE, host->addr + DMA_CONTROL); | |
437 | } else if (!(host->cmd_flags & REG_DATA)) { | |
438 | writel(((1 << 16) & BLOCK_COUNT_MASK) | |
439 | | (data_len & BLOCK_SIZE_MASK), | |
440 | host->addr + BLOCK); | |
441 | t_val = readl(host->addr + INT_STATUS_ENABLE); | |
442 | t_val |= host->req->data_dir == READ | |
443 | ? INT_STATUS_FIFO_RRDY | |
444 | : INT_STATUS_FIFO_WRDY; | |
445 | ||
446 | writel(t_val, host->addr + INT_STATUS_ENABLE); | |
447 | writel(t_val, host->addr + INT_SIGNAL_ENABLE); | |
448 | } else { | |
449 | cmd &= ~(TPC_DATA_SEL | 0xf); | |
450 | host->cmd_flags |= REG_DATA; | |
451 | cmd |= data_len & 0xf; | |
452 | ||
453 | if (host->req->data_dir == WRITE) { | |
454 | jmb38x_ms_transfer_data(host); | |
455 | writel(host->io_word[0], host->addr + TPC_P0); | |
456 | writel(host->io_word[1], host->addr + TPC_P1); | |
457 | } | |
458 | } | |
459 | ||
460 | mod_timer(&host->timer, jiffies + host->timeout_jiffies); | |
461 | writel(HOST_CONTROL_LED | readl(host->addr + HOST_CONTROL), | |
462 | host->addr + HOST_CONTROL); | |
463 | host->req->error = 0; | |
464 | ||
465 | writel(cmd, host->addr + TPC); | |
c4c66cf1 | 466 | dev_dbg(&msh->dev, "executing TPC %08x, len %x\n", cmd, data_len); |
60fdd931 AD |
467 | |
468 | return 0; | |
469 | } | |
470 | ||
471 | static void jmb38x_ms_complete_cmd(struct memstick_host *msh, int last) | |
472 | { | |
473 | struct jmb38x_ms_host *host = memstick_priv(msh); | |
474 | unsigned int t_val = 0; | |
475 | int rc; | |
476 | ||
477 | del_timer(&host->timer); | |
478 | ||
c4c66cf1 | 479 | dev_dbg(&msh->dev, "c control %08x\n", |
60fdd931 | 480 | readl(host->addr + HOST_CONTROL)); |
c4c66cf1 | 481 | dev_dbg(&msh->dev, "c status %08x\n", |
60fdd931 | 482 | readl(host->addr + INT_STATUS)); |
c4c66cf1 | 483 | dev_dbg(&msh->dev, "c hstatus %08x\n", readl(host->addr + STATUS)); |
60fdd931 | 484 | |
ead70773 AD |
485 | host->req->int_reg = readl(host->addr + STATUS) & 0xff; |
486 | ||
487 | writel(0, host->addr + BLOCK); | |
488 | writel(0, host->addr + DMA_CONTROL); | |
60fdd931 | 489 | |
ead70773 | 490 | if (host->cmd_flags & DMA_DATA) { |
43abdbce | 491 | dma_unmap_sg(&host->chip->pdev->dev, &host->req->sg, 1, |
60fdd931 | 492 | host->req->data_dir == READ |
43abdbce | 493 | ? DMA_FROM_DEVICE : DMA_TO_DEVICE); |
60fdd931 AD |
494 | } else { |
495 | t_val = readl(host->addr + INT_STATUS_ENABLE); | |
496 | if (host->req->data_dir == READ) | |
497 | t_val &= ~INT_STATUS_FIFO_RRDY; | |
498 | else | |
499 | t_val &= ~INT_STATUS_FIFO_WRDY; | |
500 | ||
501 | writel(t_val, host->addr + INT_STATUS_ENABLE); | |
502 | writel(t_val, host->addr + INT_SIGNAL_ENABLE); | |
503 | } | |
504 | ||
505 | writel((~HOST_CONTROL_LED) & readl(host->addr + HOST_CONTROL), | |
506 | host->addr + HOST_CONTROL); | |
507 | ||
508 | if (!last) { | |
509 | do { | |
510 | rc = memstick_next_req(msh, &host->req); | |
511 | } while (!rc && jmb38x_ms_issue_cmd(msh)); | |
512 | } else { | |
513 | do { | |
514 | rc = memstick_next_req(msh, &host->req); | |
515 | if (!rc) | |
516 | host->req->error = -ETIME; | |
517 | } while (!rc); | |
518 | } | |
519 | } | |
520 | ||
521 | static irqreturn_t jmb38x_ms_isr(int irq, void *dev_id) | |
522 | { | |
523 | struct memstick_host *msh = dev_id; | |
524 | struct jmb38x_ms_host *host = memstick_priv(msh); | |
525 | unsigned int irq_status; | |
526 | ||
527 | spin_lock(&host->lock); | |
528 | irq_status = readl(host->addr + INT_STATUS); | |
529 | dev_dbg(&host->chip->pdev->dev, "irq_status = %08x\n", irq_status); | |
530 | if (irq_status == 0 || irq_status == (~0)) { | |
531 | spin_unlock(&host->lock); | |
532 | return IRQ_NONE; | |
533 | } | |
534 | ||
535 | if (host->req) { | |
536 | if (irq_status & INT_STATUS_ANY_ERR) { | |
537 | if (irq_status & INT_STATUS_CRC_ERR) | |
538 | host->req->error = -EILSEQ; | |
23c5947a TI |
539 | else if (irq_status & INT_STATUS_TPC_ERR) { |
540 | dev_dbg(&host->chip->pdev->dev, "TPC_ERR\n"); | |
541 | jmb38x_ms_complete_cmd(msh, 0); | |
542 | } else | |
60fdd931 AD |
543 | host->req->error = -ETIME; |
544 | } else { | |
ead70773 | 545 | if (host->cmd_flags & DMA_DATA) { |
60fdd931 AD |
546 | if (irq_status & INT_STATUS_EOTRAN) |
547 | host->cmd_flags |= FIFO_READY; | |
548 | } else { | |
549 | if (irq_status & (INT_STATUS_FIFO_RRDY | |
550 | | INT_STATUS_FIFO_WRDY)) | |
551 | jmb38x_ms_transfer_data(host); | |
552 | ||
553 | if (irq_status & INT_STATUS_EOTRAN) { | |
554 | jmb38x_ms_transfer_data(host); | |
555 | host->cmd_flags |= FIFO_READY; | |
556 | } | |
557 | } | |
558 | ||
559 | if (irq_status & INT_STATUS_EOTPC) { | |
560 | host->cmd_flags |= CMD_READY; | |
561 | if (host->cmd_flags & REG_DATA) { | |
562 | if (host->req->data_dir == READ) { | |
563 | host->io_word[0] | |
564 | = readl(host->addr | |
565 | + TPC_P0); | |
566 | host->io_word[1] | |
567 | = readl(host->addr | |
568 | + TPC_P1); | |
569 | host->io_pos = 8; | |
570 | ||
571 | jmb38x_ms_transfer_data(host); | |
572 | } | |
573 | host->cmd_flags |= FIFO_READY; | |
574 | } | |
575 | } | |
576 | } | |
577 | } | |
578 | ||
579 | if (irq_status & (INT_STATUS_MEDIA_IN | INT_STATUS_MEDIA_OUT)) { | |
580 | dev_dbg(&host->chip->pdev->dev, "media changed\n"); | |
581 | memstick_detect_change(msh); | |
582 | } | |
583 | ||
584 | writel(irq_status, host->addr + INT_STATUS); | |
585 | ||
586 | if (host->req | |
587 | && (((host->cmd_flags & CMD_READY) | |
588 | && (host->cmd_flags & FIFO_READY)) | |
589 | || host->req->error)) | |
590 | jmb38x_ms_complete_cmd(msh, 0); | |
591 | ||
592 | spin_unlock(&host->lock); | |
593 | return IRQ_HANDLED; | |
594 | } | |
595 | ||
6243d38f | 596 | static void jmb38x_ms_abort(struct timer_list *t) |
60fdd931 | 597 | { |
6243d38f KC |
598 | struct jmb38x_ms_host *host = from_timer(host, t, timer); |
599 | struct memstick_host *msh = host->msh; | |
60fdd931 AD |
600 | unsigned long flags; |
601 | ||
602 | dev_dbg(&host->chip->pdev->dev, "abort\n"); | |
603 | spin_lock_irqsave(&host->lock, flags); | |
604 | if (host->req) { | |
605 | host->req->error = -ETIME; | |
606 | jmb38x_ms_complete_cmd(msh, 0); | |
607 | } | |
608 | spin_unlock_irqrestore(&host->lock, flags); | |
609 | } | |
610 | ||
f1d82698 | 611 | static void jmb38x_ms_req_tasklet(unsigned long data) |
60fdd931 | 612 | { |
f1d82698 | 613 | struct memstick_host *msh = (struct memstick_host *)data; |
60fdd931 AD |
614 | struct jmb38x_ms_host *host = memstick_priv(msh); |
615 | unsigned long flags; | |
616 | int rc; | |
617 | ||
618 | spin_lock_irqsave(&host->lock, flags); | |
f1d82698 AD |
619 | if (!host->req) { |
620 | do { | |
621 | rc = memstick_next_req(msh, &host->req); | |
622 | dev_dbg(&host->chip->pdev->dev, "tasklet req %d\n", rc); | |
623 | } while (!rc && jmb38x_ms_issue_cmd(msh)); | |
60fdd931 | 624 | } |
60fdd931 AD |
625 | spin_unlock_irqrestore(&host->lock, flags); |
626 | } | |
627 | ||
f1d82698 AD |
628 | static void jmb38x_ms_dummy_submit(struct memstick_host *msh) |
629 | { | |
630 | return; | |
631 | } | |
632 | ||
633 | static void jmb38x_ms_submit_req(struct memstick_host *msh) | |
634 | { | |
635 | struct jmb38x_ms_host *host = memstick_priv(msh); | |
636 | ||
637 | tasklet_schedule(&host->notify); | |
638 | } | |
639 | ||
b7789998 | 640 | static int jmb38x_ms_reset(struct jmb38x_ms_host *host) |
60fdd931 | 641 | { |
b7789998 | 642 | int cnt; |
60fdd931 | 643 | |
b7789998 AD |
644 | writel(HOST_CONTROL_RESET_REQ | HOST_CONTROL_CLOCK_EN |
645 | | readl(host->addr + HOST_CONTROL), | |
646 | host->addr + HOST_CONTROL); | |
647 | mmiowb(); | |
648 | ||
649 | for (cnt = 0; cnt < 20; ++cnt) { | |
650 | if (!(HOST_CONTROL_RESET_REQ | |
651 | & readl(host->addr + HOST_CONTROL))) | |
652 | goto reset_next; | |
60fdd931 | 653 | |
cf821e8f | 654 | ndelay(20); |
60fdd931 | 655 | } |
b7789998 | 656 | dev_dbg(&host->chip->pdev->dev, "reset_req timeout\n"); |
60fdd931 | 657 | |
b7789998 AD |
658 | reset_next: |
659 | writel(HOST_CONTROL_RESET | HOST_CONTROL_CLOCK_EN | |
660 | | readl(host->addr + HOST_CONTROL), | |
661 | host->addr + HOST_CONTROL); | |
662 | mmiowb(); | |
663 | ||
664 | for (cnt = 0; cnt < 20; ++cnt) { | |
665 | if (!(HOST_CONTROL_RESET | |
666 | & readl(host->addr + HOST_CONTROL))) | |
667 | goto reset_ok; | |
668 | ||
669 | ndelay(20); | |
670 | } | |
671 | dev_dbg(&host->chip->pdev->dev, "reset timeout\n"); | |
672 | return -EIO; | |
673 | ||
674 | reset_ok: | |
cf821e8f | 675 | mmiowb(); |
60fdd931 | 676 | writel(INT_STATUS_ALL, host->addr + INT_SIGNAL_ENABLE); |
cf821e8f | 677 | writel(INT_STATUS_ALL, host->addr + INT_STATUS_ENABLE); |
b7789998 | 678 | return 0; |
60fdd931 AD |
679 | } |
680 | ||
b7789998 AD |
681 | static int jmb38x_ms_set_param(struct memstick_host *msh, |
682 | enum memstick_param param, | |
683 | int value) | |
60fdd931 AD |
684 | { |
685 | struct jmb38x_ms_host *host = memstick_priv(msh); | |
cf821e8f | 686 | unsigned int host_ctl = readl(host->addr + HOST_CONTROL); |
23c5947a | 687 | unsigned int clock_ctl = CLOCK_CONTROL_BY_MMIO, clock_delay = 0; |
b7789998 | 688 | int rc = 0; |
60fdd931 AD |
689 | |
690 | switch (param) { | |
691 | case MEMSTICK_POWER: | |
692 | if (value == MEMSTICK_POWER_ON) { | |
b7789998 AD |
693 | rc = jmb38x_ms_reset(host); |
694 | if (rc) | |
695 | return rc; | |
696 | ||
697 | host_ctl = 7; | |
698 | host_ctl |= HOST_CONTROL_POWER_EN | |
23c5947a | 699 | | HOST_CONTROL_CLOCK_EN; |
b7789998 | 700 | writel(host_ctl, host->addr + HOST_CONTROL); |
60fdd931 AD |
701 | |
702 | writel(host->id ? PAD_PU_PD_ON_MS_SOCK1 | |
cf821e8f | 703 | : PAD_PU_PD_ON_MS_SOCK0, |
60fdd931 AD |
704 | host->addr + PAD_PU_PD); |
705 | ||
706 | writel(PAD_OUTPUT_ENABLE_MS, | |
707 | host->addr + PAD_OUTPUT_ENABLE); | |
708 | ||
b7789998 | 709 | msleep(10); |
60fdd931 AD |
710 | dev_dbg(&host->chip->pdev->dev, "power on\n"); |
711 | } else if (value == MEMSTICK_POWER_OFF) { | |
cf821e8f AD |
712 | host_ctl &= ~(HOST_CONTROL_POWER_EN |
713 | | HOST_CONTROL_CLOCK_EN); | |
714 | writel(host_ctl, host->addr + HOST_CONTROL); | |
60fdd931 AD |
715 | writel(0, host->addr + PAD_OUTPUT_ENABLE); |
716 | writel(PAD_PU_PD_OFF, host->addr + PAD_PU_PD); | |
717 | dev_dbg(&host->chip->pdev->dev, "power off\n"); | |
b7789998 AD |
718 | } else |
719 | return -EINVAL; | |
60fdd931 AD |
720 | break; |
721 | case MEMSTICK_INTERFACE: | |
23c5947a TI |
722 | dev_dbg(&host->chip->pdev->dev, |
723 | "Set Host Interface Mode to %d\n", value); | |
724 | host_ctl &= ~(HOST_CONTROL_FAST_CLK | HOST_CONTROL_REI | | |
725 | HOST_CONTROL_REO); | |
726 | host_ctl |= HOST_CONTROL_TDELAY_EN | HOST_CONTROL_HW_OC_P; | |
60fdd931 | 727 | host_ctl &= ~(3 << HOST_CONTROL_IF_SHIFT); |
60fdd931 AD |
728 | |
729 | if (value == MEMSTICK_SERIAL) { | |
60fdd931 AD |
730 | host_ctl |= HOST_CONTROL_IF_SERIAL |
731 | << HOST_CONTROL_IF_SHIFT; | |
732 | host_ctl |= HOST_CONTROL_REI; | |
23c5947a TI |
733 | clock_ctl |= CLOCK_CONTROL_40MHZ; |
734 | clock_delay = 0; | |
60fdd931 | 735 | } else if (value == MEMSTICK_PAR4) { |
23c5947a | 736 | host_ctl |= HOST_CONTROL_FAST_CLK; |
60fdd931 AD |
737 | host_ctl |= HOST_CONTROL_IF_PAR4 |
738 | << HOST_CONTROL_IF_SHIFT; | |
23c5947a TI |
739 | host_ctl |= HOST_CONTROL_REO; |
740 | clock_ctl |= CLOCK_CONTROL_40MHZ; | |
741 | clock_delay = 4; | |
60fdd931 AD |
742 | } else if (value == MEMSTICK_PAR8) { |
743 | host_ctl |= HOST_CONTROL_FAST_CLK; | |
744 | host_ctl |= HOST_CONTROL_IF_PAR8 | |
745 | << HOST_CONTROL_IF_SHIFT; | |
23c5947a TI |
746 | clock_ctl |= CLOCK_CONTROL_50MHZ; |
747 | clock_delay = 0; | |
b7789998 AD |
748 | } else |
749 | return -EINVAL; | |
8e82f8c3 | 750 | |
60fdd931 | 751 | writel(host_ctl, host->addr + HOST_CONTROL); |
23c5947a | 752 | writel(CLOCK_CONTROL_OFF, host->addr + CLOCK_CONTROL); |
cf821e8f | 753 | writel(clock_ctl, host->addr + CLOCK_CONTROL); |
23c5947a TI |
754 | pci_write_config_byte(host->chip->pdev, |
755 | PCI_CTL_CLOCK_DLY_ADDR + 1, | |
756 | clock_delay); | |
757 | host->ifmode = value; | |
60fdd931 AD |
758 | break; |
759 | }; | |
b7789998 | 760 | return 0; |
60fdd931 AD |
761 | } |
762 | ||
68860b96 TI |
763 | #define PCI_PMOS0_CONTROL 0xae |
764 | #define PMOS0_ENABLE 0x01 | |
765 | #define PMOS0_OVERCURRENT_LEVEL_2_4V 0x06 | |
766 | #define PMOS0_EN_OVERCURRENT_DEBOUNCE 0x40 | |
767 | #define PMOS0_SW_LED_POLARITY_ENABLE 0x80 | |
768 | #define PMOS0_ACTIVE_BITS (PMOS0_ENABLE | PMOS0_EN_OVERCURRENT_DEBOUNCE | \ | |
769 | PMOS0_OVERCURRENT_LEVEL_2_4V) | |
8930c8aa TI |
770 | #define PCI_PMOS1_CONTROL 0xbd |
771 | #define PMOS1_ACTIVE_BITS 0x4a | |
68860b96 TI |
772 | #define PCI_CLOCK_CTL 0xb9 |
773 | ||
774 | static int jmb38x_ms_pmos(struct pci_dev *pdev, int flag) | |
775 | { | |
776 | unsigned char val; | |
777 | ||
778 | pci_read_config_byte(pdev, PCI_PMOS0_CONTROL, &val); | |
779 | if (flag) | |
780 | val |= PMOS0_ACTIVE_BITS; | |
781 | else | |
782 | val &= ~PMOS0_ACTIVE_BITS; | |
783 | pci_write_config_byte(pdev, PCI_PMOS0_CONTROL, val); | |
784 | dev_dbg(&pdev->dev, "JMB38x: set PMOS0 val 0x%x\n", val); | |
785 | ||
8930c8aa TI |
786 | if (pci_resource_flags(pdev, 1)) { |
787 | pci_read_config_byte(pdev, PCI_PMOS1_CONTROL, &val); | |
788 | if (flag) | |
789 | val |= PMOS1_ACTIVE_BITS; | |
790 | else | |
791 | val &= ~PMOS1_ACTIVE_BITS; | |
792 | pci_write_config_byte(pdev, PCI_PMOS1_CONTROL, val); | |
793 | dev_dbg(&pdev->dev, "JMB38x: set PMOS1 val 0x%x\n", val); | |
794 | } | |
795 | ||
68860b96 TI |
796 | pci_read_config_byte(pdev, PCI_CLOCK_CTL, &val); |
797 | pci_write_config_byte(pdev, PCI_CLOCK_CTL, val & ~0x0f); | |
798 | pci_write_config_byte(pdev, PCI_CLOCK_CTL, val | 0x01); | |
799 | dev_dbg(&pdev->dev, "Clock Control by PCI config is disabled!\n"); | |
800 | ||
801 | return 0; | |
802 | } | |
803 | ||
60fdd931 AD |
804 | #ifdef CONFIG_PM |
805 | ||
806 | static int jmb38x_ms_suspend(struct pci_dev *dev, pm_message_t state) | |
807 | { | |
808 | struct jmb38x_ms *jm = pci_get_drvdata(dev); | |
809 | int cnt; | |
810 | ||
811 | for (cnt = 0; cnt < jm->host_cnt; ++cnt) { | |
812 | if (!jm->hosts[cnt]) | |
813 | break; | |
814 | memstick_suspend_host(jm->hosts[cnt]); | |
815 | } | |
816 | ||
817 | pci_save_state(dev); | |
818 | pci_enable_wake(dev, pci_choose_state(dev, state), 0); | |
819 | pci_disable_device(dev); | |
820 | pci_set_power_state(dev, pci_choose_state(dev, state)); | |
821 | return 0; | |
822 | } | |
823 | ||
824 | static int jmb38x_ms_resume(struct pci_dev *dev) | |
825 | { | |
826 | struct jmb38x_ms *jm = pci_get_drvdata(dev); | |
827 | int rc; | |
828 | ||
829 | pci_set_power_state(dev, PCI_D0); | |
830 | pci_restore_state(dev); | |
831 | rc = pci_enable_device(dev); | |
832 | if (rc) | |
833 | return rc; | |
834 | pci_set_master(dev); | |
835 | ||
68860b96 | 836 | jmb38x_ms_pmos(dev, 1); |
60fdd931 AD |
837 | |
838 | for (rc = 0; rc < jm->host_cnt; ++rc) { | |
839 | if (!jm->hosts[rc]) | |
840 | break; | |
841 | memstick_resume_host(jm->hosts[rc]); | |
842 | memstick_detect_change(jm->hosts[rc]); | |
843 | } | |
844 | ||
845 | return 0; | |
846 | } | |
847 | ||
848 | #else | |
849 | ||
850 | #define jmb38x_ms_suspend NULL | |
851 | #define jmb38x_ms_resume NULL | |
852 | ||
853 | #endif /* CONFIG_PM */ | |
854 | ||
855 | static int jmb38x_ms_count_slots(struct pci_dev *pdev) | |
856 | { | |
857 | int cnt, rc = 0; | |
858 | ||
859 | for (cnt = 0; cnt < PCI_ROM_RESOURCE; ++cnt) { | |
860 | if (!(IORESOURCE_MEM & pci_resource_flags(pdev, cnt))) | |
861 | break; | |
862 | ||
863 | if (256 != pci_resource_len(pdev, cnt)) | |
864 | break; | |
865 | ||
866 | ++rc; | |
867 | } | |
868 | return rc; | |
869 | } | |
870 | ||
871 | static struct memstick_host *jmb38x_ms_alloc_host(struct jmb38x_ms *jm, int cnt) | |
872 | { | |
873 | struct memstick_host *msh; | |
874 | struct jmb38x_ms_host *host; | |
875 | ||
876 | msh = memstick_alloc_host(sizeof(struct jmb38x_ms_host), | |
877 | &jm->pdev->dev); | |
878 | if (!msh) | |
879 | return NULL; | |
880 | ||
881 | host = memstick_priv(msh); | |
6243d38f | 882 | host->msh = msh; |
60fdd931 AD |
883 | host->chip = jm; |
884 | host->addr = ioremap(pci_resource_start(jm->pdev, cnt), | |
885 | pci_resource_len(jm->pdev, cnt)); | |
886 | if (!host->addr) | |
887 | goto err_out_free; | |
888 | ||
889 | spin_lock_init(&host->lock); | |
890 | host->id = cnt; | |
b98cb4b7 | 891 | snprintf(host->host_id, sizeof(host->host_id), DRIVER_NAME ":slot%d", |
60fdd931 AD |
892 | host->id); |
893 | host->irq = jm->pdev->irq; | |
ead70773 | 894 | host->timeout_jiffies = msecs_to_jiffies(1000); |
f1d82698 AD |
895 | |
896 | tasklet_init(&host->notify, jmb38x_ms_req_tasklet, (unsigned long)msh); | |
897 | msh->request = jmb38x_ms_submit_req; | |
60fdd931 | 898 | msh->set_param = jmb38x_ms_set_param; |
ead70773 | 899 | |
60fdd931 AD |
900 | msh->caps = MEMSTICK_CAP_PAR4 | MEMSTICK_CAP_PAR8; |
901 | ||
6243d38f | 902 | timer_setup(&host->timer, jmb38x_ms_abort, 0); |
60fdd931 AD |
903 | |
904 | if (!request_irq(host->irq, jmb38x_ms_isr, IRQF_SHARED, host->host_id, | |
905 | msh)) | |
906 | return msh; | |
907 | ||
908 | iounmap(host->addr); | |
909 | err_out_free: | |
910 | kfree(msh); | |
911 | return NULL; | |
912 | } | |
913 | ||
914 | static void jmb38x_ms_free_host(struct memstick_host *msh) | |
915 | { | |
916 | struct jmb38x_ms_host *host = memstick_priv(msh); | |
917 | ||
918 | free_irq(host->irq, msh); | |
919 | iounmap(host->addr); | |
920 | memstick_free_host(msh); | |
921 | } | |
922 | ||
923 | static int jmb38x_ms_probe(struct pci_dev *pdev, | |
924 | const struct pci_device_id *dev_id) | |
925 | { | |
926 | struct jmb38x_ms *jm; | |
927 | int pci_dev_busy = 0; | |
928 | int rc, cnt; | |
929 | ||
43abdbce | 930 | rc = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); |
60fdd931 AD |
931 | if (rc) |
932 | return rc; | |
933 | ||
934 | rc = pci_enable_device(pdev); | |
935 | if (rc) | |
936 | return rc; | |
937 | ||
938 | pci_set_master(pdev); | |
939 | ||
940 | rc = pci_request_regions(pdev, DRIVER_NAME); | |
941 | if (rc) { | |
942 | pci_dev_busy = 1; | |
943 | goto err_out; | |
944 | } | |
945 | ||
68860b96 | 946 | jmb38x_ms_pmos(pdev, 1); |
60fdd931 AD |
947 | |
948 | cnt = jmb38x_ms_count_slots(pdev); | |
949 | if (!cnt) { | |
950 | rc = -ENODEV; | |
951 | pci_dev_busy = 1; | |
952 | goto err_out; | |
953 | } | |
954 | ||
955 | jm = kzalloc(sizeof(struct jmb38x_ms) | |
956 | + cnt * sizeof(struct memstick_host *), GFP_KERNEL); | |
957 | if (!jm) { | |
958 | rc = -ENOMEM; | |
959 | goto err_out_int; | |
960 | } | |
961 | ||
962 | jm->pdev = pdev; | |
963 | jm->host_cnt = cnt; | |
964 | pci_set_drvdata(pdev, jm); | |
965 | ||
966 | for (cnt = 0; cnt < jm->host_cnt; ++cnt) { | |
967 | jm->hosts[cnt] = jmb38x_ms_alloc_host(jm, cnt); | |
968 | if (!jm->hosts[cnt]) | |
969 | break; | |
970 | ||
971 | rc = memstick_add_host(jm->hosts[cnt]); | |
972 | ||
973 | if (rc) { | |
974 | jmb38x_ms_free_host(jm->hosts[cnt]); | |
975 | jm->hosts[cnt] = NULL; | |
976 | break; | |
977 | } | |
978 | } | |
979 | ||
980 | if (cnt) | |
981 | return 0; | |
982 | ||
983 | rc = -ENODEV; | |
984 | ||
985 | pci_set_drvdata(pdev, NULL); | |
986 | kfree(jm); | |
987 | err_out_int: | |
988 | pci_release_regions(pdev); | |
989 | err_out: | |
990 | if (!pci_dev_busy) | |
991 | pci_disable_device(pdev); | |
992 | return rc; | |
993 | } | |
994 | ||
995 | static void jmb38x_ms_remove(struct pci_dev *dev) | |
996 | { | |
997 | struct jmb38x_ms *jm = pci_get_drvdata(dev); | |
998 | struct jmb38x_ms_host *host; | |
999 | int cnt; | |
1000 | unsigned long flags; | |
1001 | ||
1002 | for (cnt = 0; cnt < jm->host_cnt; ++cnt) { | |
1003 | if (!jm->hosts[cnt]) | |
1004 | break; | |
1005 | ||
1006 | host = memstick_priv(jm->hosts[cnt]); | |
1007 | ||
f1d82698 AD |
1008 | jm->hosts[cnt]->request = jmb38x_ms_dummy_submit; |
1009 | tasklet_kill(&host->notify); | |
60fdd931 AD |
1010 | writel(0, host->addr + INT_SIGNAL_ENABLE); |
1011 | writel(0, host->addr + INT_STATUS_ENABLE); | |
1012 | mmiowb(); | |
1013 | dev_dbg(&jm->pdev->dev, "interrupts off\n"); | |
1014 | spin_lock_irqsave(&host->lock, flags); | |
1015 | if (host->req) { | |
1016 | host->req->error = -ETIME; | |
1017 | jmb38x_ms_complete_cmd(jm->hosts[cnt], 1); | |
1018 | } | |
1019 | spin_unlock_irqrestore(&host->lock, flags); | |
1020 | ||
1021 | memstick_remove_host(jm->hosts[cnt]); | |
1022 | dev_dbg(&jm->pdev->dev, "host removed\n"); | |
1023 | ||
1024 | jmb38x_ms_free_host(jm->hosts[cnt]); | |
1025 | } | |
1026 | ||
68860b96 TI |
1027 | jmb38x_ms_pmos(dev, 0); |
1028 | ||
60fdd931 AD |
1029 | pci_set_drvdata(dev, NULL); |
1030 | pci_release_regions(dev); | |
1031 | pci_disable_device(dev); | |
1032 | kfree(jm); | |
1033 | } | |
1034 | ||
1035 | static struct pci_device_id jmb38x_ms_id_tbl [] = { | |
8930c8aa TI |
1036 | { PCI_VDEVICE(JMICRON, PCI_DEVICE_ID_JMICRON_JMB38X_MS) }, |
1037 | { PCI_VDEVICE(JMICRON, PCI_DEVICE_ID_JMICRON_JMB385_MS) }, | |
1038 | { PCI_VDEVICE(JMICRON, PCI_DEVICE_ID_JMICRON_JMB390_MS) }, | |
60fdd931 AD |
1039 | { } |
1040 | }; | |
1041 | ||
1042 | static struct pci_driver jmb38x_ms_driver = { | |
1043 | .name = DRIVER_NAME, | |
1044 | .id_table = jmb38x_ms_id_tbl, | |
1045 | .probe = jmb38x_ms_probe, | |
1046 | .remove = jmb38x_ms_remove, | |
1047 | .suspend = jmb38x_ms_suspend, | |
1048 | .resume = jmb38x_ms_resume | |
1049 | }; | |
1050 | ||
10560490 | 1051 | module_pci_driver(jmb38x_ms_driver); |
60fdd931 AD |
1052 | |
1053 | MODULE_AUTHOR("Alex Dubov"); | |
1054 | MODULE_DESCRIPTION("JMicron jmb38x MemoryStick driver"); | |
1055 | MODULE_LICENSE("GPL"); | |
1056 | MODULE_DEVICE_TABLE(pci, jmb38x_ms_id_tbl); |