diff options
Diffstat (limited to 'drivers/ide/tc86c001.c')
-rw-r--r-- | drivers/ide/tc86c001.c | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/drivers/ide/tc86c001.c b/drivers/ide/tc86c001.c new file mode 100644 index 000000000000..93e2cce4b296 --- /dev/null +++ b/drivers/ide/tc86c001.c | |||
@@ -0,0 +1,270 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2002 Toshiba Corporation | ||
3 | * Copyright (C) 2005-2006 MontaVista Software, Inc. <source@mvista.com> | ||
4 | * | ||
5 | * This file is licensed under the terms of the GNU General Public | ||
6 | * License version 2. This program is licensed "as is" without any | ||
7 | * warranty of any kind, whether express or implied. | ||
8 | */ | ||
9 | |||
10 | #include <linux/types.h> | ||
11 | #include <linux/pci.h> | ||
12 | #include <linux/ide.h> | ||
13 | |||
14 | #define DRV_NAME "tc86c001" | ||
15 | |||
16 | static void tc86c001_set_mode(ide_drive_t *drive, const u8 speed) | ||
17 | { | ||
18 | ide_hwif_t *hwif = HWIF(drive); | ||
19 | unsigned long scr_port = hwif->config_data + (drive->dn ? 0x02 : 0x00); | ||
20 | u16 mode, scr = inw(scr_port); | ||
21 | |||
22 | switch (speed) { | ||
23 | case XFER_UDMA_4: mode = 0x00c0; break; | ||
24 | case XFER_UDMA_3: mode = 0x00b0; break; | ||
25 | case XFER_UDMA_2: mode = 0x00a0; break; | ||
26 | case XFER_UDMA_1: mode = 0x0090; break; | ||
27 | case XFER_UDMA_0: mode = 0x0080; break; | ||
28 | case XFER_MW_DMA_2: mode = 0x0070; break; | ||
29 | case XFER_MW_DMA_1: mode = 0x0060; break; | ||
30 | case XFER_MW_DMA_0: mode = 0x0050; break; | ||
31 | case XFER_PIO_4: mode = 0x0400; break; | ||
32 | case XFER_PIO_3: mode = 0x0300; break; | ||
33 | case XFER_PIO_2: mode = 0x0200; break; | ||
34 | case XFER_PIO_1: mode = 0x0100; break; | ||
35 | case XFER_PIO_0: | ||
36 | default: mode = 0x0000; break; | ||
37 | } | ||
38 | |||
39 | scr &= (speed < XFER_MW_DMA_0) ? 0xf8ff : 0xff0f; | ||
40 | scr |= mode; | ||
41 | outw(scr, scr_port); | ||
42 | } | ||
43 | |||
44 | static void tc86c001_set_pio_mode(ide_drive_t *drive, const u8 pio) | ||
45 | { | ||
46 | tc86c001_set_mode(drive, XFER_PIO_0 + pio); | ||
47 | } | ||
48 | |||
49 | /* | ||
50 | * HACKITY HACK | ||
51 | * | ||
52 | * This is a workaround for the limitation 5 of the TC86C001 IDE controller: | ||
53 | * if a DMA transfer terminates prematurely, the controller leaves the device's | ||
54 | * interrupt request (INTRQ) pending and does not generate a PCI interrupt (or | ||
55 | * set the interrupt bit in the DMA status register), thus no PCI interrupt | ||
56 | * will occur until a DMA transfer has been successfully completed. | ||
57 | * | ||
58 | * We work around this by initiating dummy, zero-length DMA transfer on | ||
59 | * a DMA timeout expiration. I found no better way to do this with the current | ||
60 | * IDE core than to temporarily replace a higher level driver's timer expiry | ||
61 | * handler with our own backing up to that handler in case our recovery fails. | ||
62 | */ | ||
63 | static int tc86c001_timer_expiry(ide_drive_t *drive) | ||
64 | { | ||
65 | ide_hwif_t *hwif = HWIF(drive); | ||
66 | ide_expiry_t *expiry = ide_get_hwifdata(hwif); | ||
67 | ide_hwgroup_t *hwgroup = HWGROUP(drive); | ||
68 | u8 dma_stat = inb(hwif->dma_base + ATA_DMA_STATUS); | ||
69 | |||
70 | /* Restore a higher level driver's expiry handler first. */ | ||
71 | hwgroup->expiry = expiry; | ||
72 | |||
73 | if ((dma_stat & 5) == 1) { /* DMA active and no interrupt */ | ||
74 | unsigned long sc_base = hwif->config_data; | ||
75 | unsigned long twcr_port = sc_base + (drive->dn ? 0x06 : 0x04); | ||
76 | u8 dma_cmd = inb(hwif->dma_base + ATA_DMA_CMD); | ||
77 | |||
78 | printk(KERN_WARNING "%s: DMA interrupt possibly stuck, " | ||
79 | "attempting recovery...\n", drive->name); | ||
80 | |||
81 | /* Stop DMA */ | ||
82 | outb(dma_cmd & ~0x01, hwif->dma_base + ATA_DMA_CMD); | ||
83 | |||
84 | /* Setup the dummy DMA transfer */ | ||
85 | outw(0, sc_base + 0x0a); /* Sector Count */ | ||
86 | outw(0, twcr_port); /* Transfer Word Count 1 or 2 */ | ||
87 | |||
88 | /* Start the dummy DMA transfer */ | ||
89 | |||
90 | /* clear R_OR_WCTR for write */ | ||
91 | outb(0x00, hwif->dma_base + ATA_DMA_CMD); | ||
92 | /* set START_STOPBM */ | ||
93 | outb(0x01, hwif->dma_base + ATA_DMA_CMD); | ||
94 | |||
95 | /* | ||
96 | * If an interrupt was pending, it should come thru shortly. | ||
97 | * If not, a higher level driver's expiry handler should | ||
98 | * eventually cause some kind of recovery from the DMA stall. | ||
99 | */ | ||
100 | return WAIT_MIN_SLEEP; | ||
101 | } | ||
102 | |||
103 | /* Chain to the restored expiry handler if DMA wasn't active. */ | ||
104 | if (likely(expiry != NULL)) | ||
105 | return expiry(drive); | ||
106 | |||
107 | /* If there was no handler, "emulate" that for ide_timer_expiry()... */ | ||
108 | return -1; | ||
109 | } | ||
110 | |||
111 | static void tc86c001_dma_start(ide_drive_t *drive) | ||
112 | { | ||
113 | ide_hwif_t *hwif = HWIF(drive); | ||
114 | ide_hwgroup_t *hwgroup = HWGROUP(drive); | ||
115 | unsigned long sc_base = hwif->config_data; | ||
116 | unsigned long twcr_port = sc_base + (drive->dn ? 0x06 : 0x04); | ||
117 | unsigned long nsectors = hwgroup->rq->nr_sectors; | ||
118 | |||
119 | /* | ||
120 | * We have to manually load the sector count and size into | ||
121 | * the appropriate system control registers for DMA to work | ||
122 | * with LBA48 and ATAPI devices... | ||
123 | */ | ||
124 | outw(nsectors, sc_base + 0x0a); /* Sector Count */ | ||
125 | outw(SECTOR_SIZE / 2, twcr_port); /* Transfer Word Count 1/2 */ | ||
126 | |||
127 | /* Install our timeout expiry hook, saving the current handler... */ | ||
128 | ide_set_hwifdata(hwif, hwgroup->expiry); | ||
129 | hwgroup->expiry = &tc86c001_timer_expiry; | ||
130 | |||
131 | ide_dma_start(drive); | ||
132 | } | ||
133 | |||
134 | static u8 tc86c001_cable_detect(ide_hwif_t *hwif) | ||
135 | { | ||
136 | struct pci_dev *dev = to_pci_dev(hwif->dev); | ||
137 | unsigned long sc_base = pci_resource_start(dev, 5); | ||
138 | u16 scr1 = inw(sc_base + 0x00); | ||
139 | |||
140 | /* | ||
141 | * System Control 1 Register bit 13 (PDIAGN): | ||
142 | * 0=80-pin cable, 1=40-pin cable | ||
143 | */ | ||
144 | return (scr1 & 0x2000) ? ATA_CBL_PATA40 : ATA_CBL_PATA80; | ||
145 | } | ||
146 | |||
147 | static void __devinit init_hwif_tc86c001(ide_hwif_t *hwif) | ||
148 | { | ||
149 | struct pci_dev *dev = to_pci_dev(hwif->dev); | ||
150 | unsigned long sc_base = pci_resource_start(dev, 5); | ||
151 | u16 scr1 = inw(sc_base + 0x00); | ||
152 | |||
153 | /* System Control 1 Register bit 15 (Soft Reset) set */ | ||
154 | outw(scr1 | 0x8000, sc_base + 0x00); | ||
155 | |||
156 | /* System Control 1 Register bit 14 (FIFO Reset) set */ | ||
157 | outw(scr1 | 0x4000, sc_base + 0x00); | ||
158 | |||
159 | /* System Control 1 Register: reset clear */ | ||
160 | outw(scr1 & ~0xc000, sc_base + 0x00); | ||
161 | |||
162 | /* Store the system control register base for convenience... */ | ||
163 | hwif->config_data = sc_base; | ||
164 | |||
165 | if (!hwif->dma_base) | ||
166 | return; | ||
167 | |||
168 | /* | ||
169 | * Sector Count Control Register bits 0 and 1 set: | ||
170 | * software sets Sector Count Register for master and slave device | ||
171 | */ | ||
172 | outw(0x0003, sc_base + 0x0c); | ||
173 | |||
174 | /* Sector Count Register limit */ | ||
175 | hwif->rqsize = 0xffff; | ||
176 | } | ||
177 | |||
178 | static const struct ide_port_ops tc86c001_port_ops = { | ||
179 | .set_pio_mode = tc86c001_set_pio_mode, | ||
180 | .set_dma_mode = tc86c001_set_mode, | ||
181 | .cable_detect = tc86c001_cable_detect, | ||
182 | }; | ||
183 | |||
184 | static const struct ide_dma_ops tc86c001_dma_ops = { | ||
185 | .dma_host_set = ide_dma_host_set, | ||
186 | .dma_setup = ide_dma_setup, | ||
187 | .dma_exec_cmd = ide_dma_exec_cmd, | ||
188 | .dma_start = tc86c001_dma_start, | ||
189 | .dma_end = ide_dma_end, | ||
190 | .dma_test_irq = ide_dma_test_irq, | ||
191 | .dma_lost_irq = ide_dma_lost_irq, | ||
192 | .dma_timeout = ide_dma_timeout, | ||
193 | }; | ||
194 | |||
195 | static const struct ide_port_info tc86c001_chipset __devinitdata = { | ||
196 | .name = DRV_NAME, | ||
197 | .init_hwif = init_hwif_tc86c001, | ||
198 | .port_ops = &tc86c001_port_ops, | ||
199 | .dma_ops = &tc86c001_dma_ops, | ||
200 | .host_flags = IDE_HFLAG_SINGLE | IDE_HFLAG_OFF_BOARD, | ||
201 | .pio_mask = ATA_PIO4, | ||
202 | .mwdma_mask = ATA_MWDMA2, | ||
203 | .udma_mask = ATA_UDMA4, | ||
204 | }; | ||
205 | |||
206 | static int __devinit tc86c001_init_one(struct pci_dev *dev, | ||
207 | const struct pci_device_id *id) | ||
208 | { | ||
209 | int rc; | ||
210 | |||
211 | rc = pci_enable_device(dev); | ||
212 | if (rc) | ||
213 | goto out; | ||
214 | |||
215 | rc = pci_request_region(dev, 5, DRV_NAME); | ||
216 | if (rc) { | ||
217 | printk(KERN_ERR DRV_NAME ": system control regs already in use"); | ||
218 | goto out_disable; | ||
219 | } | ||
220 | |||
221 | rc = ide_pci_init_one(dev, &tc86c001_chipset, NULL); | ||
222 | if (rc) | ||
223 | goto out_release; | ||
224 | |||
225 | goto out; | ||
226 | |||
227 | out_release: | ||
228 | pci_release_region(dev, 5); | ||
229 | out_disable: | ||
230 | pci_disable_device(dev); | ||
231 | out: | ||
232 | return rc; | ||
233 | } | ||
234 | |||
235 | static void __devexit tc86c001_remove(struct pci_dev *dev) | ||
236 | { | ||
237 | ide_pci_remove(dev); | ||
238 | pci_release_region(dev, 5); | ||
239 | pci_disable_device(dev); | ||
240 | } | ||
241 | |||
242 | static const struct pci_device_id tc86c001_pci_tbl[] = { | ||
243 | { PCI_VDEVICE(TOSHIBA_2, PCI_DEVICE_ID_TOSHIBA_TC86C001_IDE), 0 }, | ||
244 | { 0, } | ||
245 | }; | ||
246 | MODULE_DEVICE_TABLE(pci, tc86c001_pci_tbl); | ||
247 | |||
248 | static struct pci_driver tc86c001_pci_driver = { | ||
249 | .name = "TC86C001", | ||
250 | .id_table = tc86c001_pci_tbl, | ||
251 | .probe = tc86c001_init_one, | ||
252 | .remove = __devexit_p(tc86c001_remove), | ||
253 | }; | ||
254 | |||
255 | static int __init tc86c001_ide_init(void) | ||
256 | { | ||
257 | return ide_pci_register_driver(&tc86c001_pci_driver); | ||
258 | } | ||
259 | |||
260 | static void __exit tc86c001_ide_exit(void) | ||
261 | { | ||
262 | pci_unregister_driver(&tc86c001_pci_driver); | ||
263 | } | ||
264 | |||
265 | module_init(tc86c001_ide_init); | ||
266 | module_exit(tc86c001_ide_exit); | ||
267 | |||
268 | MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>"); | ||
269 | MODULE_DESCRIPTION("PCI driver module for TC86C001 IDE"); | ||
270 | MODULE_LICENSE("GPL"); | ||