Commit 48a31030066315a74e6c11153b4382edbf133bb3
Committed by
Christoph Hellwig
1 parent
7ff28aee40
Exists in
ti-lsk-linux-4.1.y
and in
10 other branches
wd719x: Introduce Western Digital WD7193/7197/7296 PCI SCSI card driver
Introduce wd719x, a driver for Western Digital WD7193, WD7197 and WD7296 PCI SCSI controllers based on WD33C296A chip. Tested with WD7193 card. Signed-off-by: Ondrej Zary <linux@rainbow-software.org> Reviewed-by: Hannes Reinecke <hare@suse.de> Signed-off-by: Christoph Hellwig <hch@lst.de>
Showing 4 changed files with 1256 additions and 0 deletions Side-by-side Diff
drivers/scsi/Kconfig
... | ... | @@ -1453,6 +1453,14 @@ |
1453 | 1453 | To compile this driver as a module, choose M here: the |
1454 | 1454 | module will be called nsp32. |
1455 | 1455 | |
1456 | +config SCSI_WD719X | |
1457 | + tristate "Western Digital WD7193/7197/7296 support" | |
1458 | + depends on PCI && SCSI | |
1459 | + select EEPROM_93CX6 | |
1460 | + ---help--- | |
1461 | + This is a driver for Western Digital WD7193, WD7197 and WD7296 PCI | |
1462 | + SCSI controllers (based on WD33C296A chip). | |
1463 | + | |
1456 | 1464 | config SCSI_DEBUG |
1457 | 1465 | tristate "SCSI debugging host simulator" |
1458 | 1466 | depends on SCSI |
drivers/scsi/Makefile
drivers/scsi/wd719x.c
Changes suppressed. Click to show
1 | +/* | |
2 | + * Driver for Western Digital WD7193, WD7197 and WD7296 SCSI cards | |
3 | + * Copyright 2013 Ondrej Zary | |
4 | + * | |
5 | + * Original driver by | |
6 | + * Aaron Dewell <dewell@woods.net> | |
7 | + * Gaerti <Juergen.Gaertner@mbox.si.uni-hannover.de> | |
8 | + * | |
9 | + * HW documentation available in book: | |
10 | + * | |
11 | + * SPIDER Command Protocol | |
12 | + * by Chandru M. Sippy | |
13 | + * SCSI Storage Products (MCP) | |
14 | + * Western Digital Corporation | |
15 | + * 09-15-95 | |
16 | + * | |
17 | + * http://web.archive.org/web/20070717175254/http://sun1.rrzn.uni-hannover.de/gaertner.juergen/wd719x/Linux/Docu/Spider/ | |
18 | + */ | |
19 | + | |
20 | +/* | |
21 | + * Driver workflow: | |
22 | + * 1. SCSI command is transformed to SCB (Spider Control Block) by the | |
23 | + * queuecommand function. | |
24 | + * 2. The address of the SCB is stored in a list to be able to access it, if | |
25 | + * something goes wrong. | |
26 | + * 3. The address of the SCB is written to the Controller, which loads the SCB | |
27 | + * via BM-DMA and processes it. | |
28 | + * 4. After it has finished, it generates an interrupt, and sets registers. | |
29 | + * | |
30 | + * flaws: | |
31 | + * - abort/reset functions | |
32 | + * | |
33 | + * ToDo: | |
34 | + * - tagged queueing | |
35 | + */ | |
36 | + | |
37 | +#include <linux/interrupt.h> | |
38 | +#include <linux/module.h> | |
39 | +#include <linux/delay.h> | |
40 | +#include <linux/pci.h> | |
41 | +#include <linux/firmware.h> | |
42 | +#include <linux/eeprom_93cx6.h> | |
43 | +#include <scsi/scsi_cmnd.h> | |
44 | +#include <scsi/scsi_device.h> | |
45 | +#include <scsi/scsi_host.h> | |
46 | +#include "wd719x.h" | |
47 | + | |
48 | +/* low-level register access */ | |
49 | +static inline u8 wd719x_readb(struct wd719x *wd, u8 reg) | |
50 | +{ | |
51 | + return ioread8(wd->base + reg); | |
52 | +} | |
53 | + | |
54 | +static inline u32 wd719x_readl(struct wd719x *wd, u8 reg) | |
55 | +{ | |
56 | + return ioread32(wd->base + reg); | |
57 | +} | |
58 | + | |
59 | +static inline void wd719x_writeb(struct wd719x *wd, u8 reg, u8 val) | |
60 | +{ | |
61 | + iowrite8(val, wd->base + reg); | |
62 | +} | |
63 | + | |
64 | +static inline void wd719x_writew(struct wd719x *wd, u8 reg, u16 val) | |
65 | +{ | |
66 | + iowrite16(val, wd->base + reg); | |
67 | +} | |
68 | + | |
69 | +static inline void wd719x_writel(struct wd719x *wd, u8 reg, u32 val) | |
70 | +{ | |
71 | + iowrite32(val, wd->base + reg); | |
72 | +} | |
73 | + | |
74 | +/* wait until the command register is ready */ | |
75 | +static inline int wd719x_wait_ready(struct wd719x *wd) | |
76 | +{ | |
77 | + int i = 0; | |
78 | + | |
79 | + do { | |
80 | + if (wd719x_readb(wd, WD719X_AMR_COMMAND) == WD719X_CMD_READY) | |
81 | + return 0; | |
82 | + udelay(1); | |
83 | + } while (i++ < WD719X_WAIT_FOR_CMD_READY); | |
84 | + | |
85 | + dev_err(&wd->pdev->dev, "command register is not ready: 0x%02x\n", | |
86 | + wd719x_readb(wd, WD719X_AMR_COMMAND)); | |
87 | + | |
88 | + return -ETIMEDOUT; | |
89 | +} | |
90 | + | |
91 | +/* poll interrupt status register until command finishes */ | |
92 | +static inline int wd719x_wait_done(struct wd719x *wd, int timeout) | |
93 | +{ | |
94 | + u8 status; | |
95 | + | |
96 | + while (timeout > 0) { | |
97 | + status = wd719x_readb(wd, WD719X_AMR_INT_STATUS); | |
98 | + if (status) | |
99 | + break; | |
100 | + timeout--; | |
101 | + udelay(1); | |
102 | + } | |
103 | + | |
104 | + if (timeout <= 0) { | |
105 | + dev_err(&wd->pdev->dev, "direct command timed out\n"); | |
106 | + return -ETIMEDOUT; | |
107 | + } | |
108 | + | |
109 | + if (status != WD719X_INT_NOERRORS) { | |
110 | + dev_err(&wd->pdev->dev, "direct command failed, status 0x%02x, SUE 0x%02x\n", | |
111 | + status, wd719x_readb(wd, WD719X_AMR_SCB_ERROR)); | |
112 | + return -EIO; | |
113 | + } | |
114 | + | |
115 | + return 0; | |
116 | +} | |
117 | + | |
118 | +static int wd719x_direct_cmd(struct wd719x *wd, u8 opcode, u8 dev, u8 lun, | |
119 | + u8 tag, dma_addr_t data, int timeout) | |
120 | +{ | |
121 | + int ret = 0; | |
122 | + | |
123 | + /* clear interrupt status register (allow command register to clear) */ | |
124 | + wd719x_writeb(wd, WD719X_AMR_INT_STATUS, WD719X_INT_NONE); | |
125 | + | |
126 | + /* Wait for the Command register to become free */ | |
127 | + if (wd719x_wait_ready(wd)) | |
128 | + return -ETIMEDOUT; | |
129 | + | |
130 | + /* make sure we get NO interrupts */ | |
131 | + dev |= WD719X_DISABLE_INT; | |
132 | + wd719x_writeb(wd, WD719X_AMR_CMD_PARAM, dev); | |
133 | + wd719x_writeb(wd, WD719X_AMR_CMD_PARAM_2, lun); | |
134 | + wd719x_writeb(wd, WD719X_AMR_CMD_PARAM_3, tag); | |
135 | + if (data) | |
136 | + wd719x_writel(wd, WD719X_AMR_SCB_IN, data); | |
137 | + | |
138 | + /* clear interrupt status register again */ | |
139 | + wd719x_writeb(wd, WD719X_AMR_INT_STATUS, WD719X_INT_NONE); | |
140 | + | |
141 | + /* Now, write the command */ | |
142 | + wd719x_writeb(wd, WD719X_AMR_COMMAND, opcode); | |
143 | + | |
144 | + if (timeout) /* wait for the command to complete */ | |
145 | + ret = wd719x_wait_done(wd, timeout); | |
146 | + | |
147 | + /* clear interrupt status register (clean up) */ | |
148 | + if (opcode != WD719X_CMD_READ_FIRMVER) | |
149 | + wd719x_writeb(wd, WD719X_AMR_INT_STATUS, WD719X_INT_NONE); | |
150 | + | |
151 | + return ret; | |
152 | +} | |
153 | + | |
154 | +static void wd719x_destroy(struct wd719x *wd) | |
155 | +{ | |
156 | + struct wd719x_scb *scb; | |
157 | + | |
158 | + /* stop the RISC */ | |
159 | + if (wd719x_direct_cmd(wd, WD719X_CMD_SLEEP, 0, 0, 0, 0, | |
160 | + WD719X_WAIT_FOR_RISC)) | |
161 | + dev_warn(&wd->pdev->dev, "RISC sleep command failed\n"); | |
162 | + /* disable RISC */ | |
163 | + wd719x_writeb(wd, WD719X_PCI_MODE_SELECT, 0); | |
164 | + | |
165 | + /* free all SCBs */ | |
166 | + list_for_each_entry(scb, &wd->active_scbs, list) | |
167 | + pci_free_consistent(wd->pdev, sizeof(struct wd719x_scb), scb, | |
168 | + scb->phys); | |
169 | + list_for_each_entry(scb, &wd->free_scbs, list) | |
170 | + pci_free_consistent(wd->pdev, sizeof(struct wd719x_scb), scb, | |
171 | + scb->phys); | |
172 | + /* free internal buffers */ | |
173 | + pci_free_consistent(wd->pdev, wd->fw_size, wd->fw_virt, wd->fw_phys); | |
174 | + wd->fw_virt = NULL; | |
175 | + pci_free_consistent(wd->pdev, WD719X_HASH_TABLE_SIZE, wd->hash_virt, | |
176 | + wd->hash_phys); | |
177 | + wd->hash_virt = NULL; | |
178 | + pci_free_consistent(wd->pdev, sizeof(struct wd719x_host_param), | |
179 | + wd->params, wd->params_phys); | |
180 | + wd->params = NULL; | |
181 | + free_irq(wd->pdev->irq, wd); | |
182 | +} | |
183 | + | |
184 | +/* finish a SCSI command, mark SCB (if any) as free, unmap buffers */ | |
185 | +static void wd719x_finish_cmd(struct scsi_cmnd *cmd, int result) | |
186 | +{ | |
187 | + struct wd719x *wd = shost_priv(cmd->device->host); | |
188 | + struct wd719x_scb *scb = (struct wd719x_scb *) cmd->host_scribble; | |
189 | + | |
190 | + if (scb) { | |
191 | + list_move(&scb->list, &wd->free_scbs); | |
192 | + dma_unmap_single(&wd->pdev->dev, cmd->SCp.dma_handle, | |
193 | + SCSI_SENSE_BUFFERSIZE, DMA_FROM_DEVICE); | |
194 | + scsi_dma_unmap(cmd); | |
195 | + } | |
196 | + cmd->result = result << 16; | |
197 | + cmd->scsi_done(cmd); | |
198 | +} | |
199 | + | |
200 | +/* Build a SCB and send it to the card */ | |
201 | +static int wd719x_queuecommand(struct Scsi_Host *sh, struct scsi_cmnd *cmd) | |
202 | +{ | |
203 | + int i, count_sg; | |
204 | + unsigned long flags; | |
205 | + struct wd719x_scb *scb; | |
206 | + struct wd719x *wd = shost_priv(sh); | |
207 | + dma_addr_t phys; | |
208 | + | |
209 | + cmd->host_scribble = NULL; | |
210 | + | |
211 | + /* get a free SCB - either from existing ones or allocate a new one */ | |
212 | + spin_lock_irqsave(wd->sh->host_lock, flags); | |
213 | + scb = list_first_entry_or_null(&wd->free_scbs, struct wd719x_scb, list); | |
214 | + if (scb) { | |
215 | + list_del(&scb->list); | |
216 | + phys = scb->phys; | |
217 | + } else { | |
218 | + spin_unlock_irqrestore(wd->sh->host_lock, flags); | |
219 | + scb = pci_alloc_consistent(wd->pdev, sizeof(struct wd719x_scb), | |
220 | + &phys); | |
221 | + spin_lock_irqsave(wd->sh->host_lock, flags); | |
222 | + if (!scb) { | |
223 | + dev_err(&wd->pdev->dev, "unable to allocate SCB\n"); | |
224 | + wd719x_finish_cmd(cmd, DID_ERROR); | |
225 | + spin_unlock_irqrestore(wd->sh->host_lock, flags); | |
226 | + return 0; | |
227 | + } | |
228 | + } | |
229 | + memset(scb, 0, sizeof(struct wd719x_scb)); | |
230 | + list_add(&scb->list, &wd->active_scbs); | |
231 | + | |
232 | + scb->phys = phys; | |
233 | + scb->cmd = cmd; | |
234 | + cmd->host_scribble = (char *) scb; | |
235 | + | |
236 | + scb->CDB_tag = 0; /* Tagged queueing not supported yet */ | |
237 | + scb->devid = cmd->device->id; | |
238 | + scb->lun = cmd->device->lun; | |
239 | + | |
240 | + /* copy the command */ | |
241 | + memcpy(scb->CDB, cmd->cmnd, cmd->cmd_len); | |
242 | + | |
243 | + /* map sense buffer */ | |
244 | + scb->sense_buf_length = SCSI_SENSE_BUFFERSIZE; | |
245 | + cmd->SCp.dma_handle = dma_map_single(&wd->pdev->dev, cmd->sense_buffer, | |
246 | + SCSI_SENSE_BUFFERSIZE, DMA_FROM_DEVICE); | |
247 | + dma_cache_sync(&wd->pdev->dev, cmd->sense_buffer, | |
248 | + SCSI_SENSE_BUFFERSIZE, DMA_FROM_DEVICE); | |
249 | + scb->sense_buf = cpu_to_le32(cmd->SCp.dma_handle); | |
250 | + | |
251 | + /* request autosense */ | |
252 | + scb->SCB_options |= WD719X_SCB_FLAGS_AUTO_REQUEST_SENSE; | |
253 | + | |
254 | + /* check direction */ | |
255 | + if (cmd->sc_data_direction == DMA_TO_DEVICE) | |
256 | + scb->SCB_options |= WD719X_SCB_FLAGS_CHECK_DIRECTION | |
257 | + | WD719X_SCB_FLAGS_PCI_TO_SCSI; | |
258 | + else if (cmd->sc_data_direction == DMA_FROM_DEVICE) | |
259 | + scb->SCB_options |= WD719X_SCB_FLAGS_CHECK_DIRECTION; | |
260 | + | |
261 | + /* Scather/gather */ | |
262 | + count_sg = scsi_dma_map(cmd); | |
263 | + if (count_sg < 0) { | |
264 | + wd719x_finish_cmd(cmd, DID_ERROR); | |
265 | + spin_unlock_irqrestore(wd->sh->host_lock, flags); | |
266 | + return 0; | |
267 | + } | |
268 | + BUG_ON(count_sg > WD719X_SG); | |
269 | + | |
270 | + if (count_sg) { | |
271 | + struct scatterlist *sg; | |
272 | + | |
273 | + scb->data_length = cpu_to_le32(count_sg * | |
274 | + sizeof(struct wd719x_sglist)); | |
275 | + scb->data_p = cpu_to_le32(scb->phys + | |
276 | + offsetof(struct wd719x_scb, sg_list)); | |
277 | + | |
278 | + scsi_for_each_sg(cmd, sg, count_sg, i) { | |
279 | + scb->sg_list[i].ptr = cpu_to_le32(sg_dma_address(sg)); | |
280 | + scb->sg_list[i].length = cpu_to_le32(sg_dma_len(sg)); | |
281 | + } | |
282 | + scb->SCB_options |= WD719X_SCB_FLAGS_DO_SCATTER_GATHER; | |
283 | + } else { /* zero length */ | |
284 | + scb->data_length = 0; | |
285 | + scb->data_p = 0; | |
286 | + } | |
287 | + | |
288 | + /* check if the Command register is free */ | |
289 | + if (wd719x_readb(wd, WD719X_AMR_COMMAND) != WD719X_CMD_READY) { | |
290 | + spin_unlock_irqrestore(wd->sh->host_lock, flags); | |
291 | + return SCSI_MLQUEUE_HOST_BUSY; | |
292 | + } | |
293 | + | |
294 | + /* write pointer to the AMR */ | |
295 | + wd719x_writel(wd, WD719X_AMR_SCB_IN, scb->phys); | |
296 | + /* send SCB opcode */ | |
297 | + wd719x_writeb(wd, WD719X_AMR_COMMAND, WD719X_CMD_PROCESS_SCB); | |
298 | + | |
299 | + spin_unlock_irqrestore(wd->sh->host_lock, flags); | |
300 | + | |
301 | + return 0; | |
302 | +} | |
303 | + | |
304 | +static int wd719x_chip_init(struct wd719x *wd) | |
305 | +{ | |
306 | + int i, ret; | |
307 | + u32 risc_init[3]; | |
308 | + const struct firmware *fw_wcs, *fw_risc; | |
309 | + const char fwname_wcs[] = "wd719x-wcs.bin"; | |
310 | + const char fwname_risc[] = "wd719x-risc.bin"; | |
311 | + | |
312 | + memset(wd->hash_virt, 0, WD719X_HASH_TABLE_SIZE); | |
313 | + | |
314 | + /* WCS (sequencer) firmware */ | |
315 | + ret = request_firmware(&fw_wcs, fwname_wcs, &wd->pdev->dev); | |
316 | + if (ret) { | |
317 | + dev_err(&wd->pdev->dev, "Unable to load firmware %s: %d\n", | |
318 | + fwname_wcs, ret); | |
319 | + return ret; | |
320 | + } | |
321 | + /* RISC firmware */ | |
322 | + ret = request_firmware(&fw_risc, fwname_risc, &wd->pdev->dev); | |
323 | + if (ret) { | |
324 | + dev_err(&wd->pdev->dev, "Unable to load firmware %s: %d\n", | |
325 | + fwname_risc, ret); | |
326 | + release_firmware(fw_wcs); | |
327 | + return ret; | |
328 | + } | |
329 | + wd->fw_size = ALIGN(fw_wcs->size, 4) + fw_risc->size; | |
330 | + | |
331 | + if (!wd->fw_virt) | |
332 | + wd->fw_virt = pci_alloc_consistent(wd->pdev, wd->fw_size, | |
333 | + &wd->fw_phys); | |
334 | + if (!wd->fw_virt) { | |
335 | + ret = -ENOMEM; | |
336 | + goto wd719x_init_end; | |
337 | + } | |
338 | + | |
339 | + /* make a fresh copy of WCS and RISC code */ | |
340 | + memcpy(wd->fw_virt, fw_wcs->data, fw_wcs->size); | |
341 | + memcpy(wd->fw_virt + ALIGN(fw_wcs->size, 4), fw_risc->data, | |
342 | + fw_risc->size); | |
343 | + | |
344 | + /* Reset the Spider Chip and adapter itself */ | |
345 | + wd719x_writeb(wd, WD719X_PCI_PORT_RESET, WD719X_PCI_RESET); | |
346 | + udelay(WD719X_WAIT_FOR_RISC); | |
347 | + /* Clear PIO mode bits set by BIOS */ | |
348 | + wd719x_writeb(wd, WD719X_AMR_CMD_PARAM, 0); | |
349 | + /* ensure RISC is not running */ | |
350 | + wd719x_writeb(wd, WD719X_PCI_MODE_SELECT, 0); | |
351 | + /* ensure command port is ready */ | |
352 | + wd719x_writeb(wd, WD719X_AMR_COMMAND, 0); | |
353 | + if (wd719x_wait_ready(wd)) { | |
354 | + ret = -ETIMEDOUT; | |
355 | + goto wd719x_init_end; | |
356 | + } | |
357 | + | |
358 | + /* Transfer the first 2K words of RISC code to kick start the uP */ | |
359 | + risc_init[0] = wd->fw_phys; /* WCS FW */ | |
360 | + risc_init[1] = wd->fw_phys + ALIGN(fw_wcs->size, 4); /* RISC FW */ | |
361 | + risc_init[2] = wd->hash_phys; /* hash table */ | |
362 | + | |
363 | + /* clear DMA status */ | |
364 | + wd719x_writeb(wd, WD719X_PCI_CHANNEL2_3STATUS, 0); | |
365 | + | |
366 | + /* address to read firmware from */ | |
367 | + wd719x_writel(wd, WD719X_PCI_EXTERNAL_ADDR, risc_init[1]); | |
368 | + /* base address to write firmware to (on card) */ | |
369 | + wd719x_writew(wd, WD719X_PCI_INTERNAL_ADDR, WD719X_PRAM_BASE_ADDR); | |
370 | + /* size: first 2K words */ | |
371 | + wd719x_writew(wd, WD719X_PCI_DMA_TRANSFER_SIZE, 2048 * 2); | |
372 | + /* start DMA */ | |
373 | + wd719x_writeb(wd, WD719X_PCI_CHANNEL2_3CMD, WD719X_START_CHANNEL2_3DMA); | |
374 | + | |
375 | + /* wait for DMA to complete */ | |
376 | + i = WD719X_WAIT_FOR_RISC; | |
377 | + while (i-- > 0) { | |
378 | + u8 status = wd719x_readb(wd, WD719X_PCI_CHANNEL2_3STATUS); | |
379 | + if (status == WD719X_START_CHANNEL2_3DONE) | |
380 | + break; | |
381 | + if (status == WD719X_START_CHANNEL2_3ABORT) { | |
382 | + dev_warn(&wd->pdev->dev, "RISC bootstrap failed: DMA aborted\n"); | |
383 | + ret = -EIO; | |
384 | + goto wd719x_init_end; | |
385 | + } | |
386 | + udelay(1); | |
387 | + } | |
388 | + if (i < 1) { | |
389 | + dev_warn(&wd->pdev->dev, "RISC bootstrap failed: DMA timeout\n"); | |
390 | + ret = -ETIMEDOUT; | |
391 | + goto wd719x_init_end; | |
392 | + } | |
393 | + | |
394 | + /* firmware is loaded, now initialize and wake up the RISC */ | |
395 | + /* write RISC initialization long words to Spider */ | |
396 | + wd719x_writel(wd, WD719X_AMR_SCB_IN, risc_init[0]); | |
397 | + wd719x_writel(wd, WD719X_AMR_SCB_IN + 4, risc_init[1]); | |
398 | + wd719x_writel(wd, WD719X_AMR_SCB_IN + 8, risc_init[2]); | |
399 | + | |
400 | + /* disable interrupts during initialization of RISC */ | |
401 | + wd719x_writeb(wd, WD719X_AMR_CMD_PARAM, WD719X_DISABLE_INT); | |
402 | + | |
403 | + /* issue INITIALIZE RISC comand */ | |
404 | + wd719x_writeb(wd, WD719X_AMR_COMMAND, WD719X_CMD_INIT_RISC); | |
405 | + /* enable advanced mode (wake up RISC) */ | |
406 | + wd719x_writeb(wd, WD719X_PCI_MODE_SELECT, WD719X_ENABLE_ADVANCE_MODE); | |
407 | + udelay(WD719X_WAIT_FOR_RISC); | |
408 | + | |
409 | + ret = wd719x_wait_done(wd, WD719X_WAIT_FOR_RISC); | |
410 | + /* clear interrupt status register */ | |
411 | + wd719x_writeb(wd, WD719X_AMR_INT_STATUS, WD719X_INT_NONE); | |
412 | + if (ret) { | |
413 | + dev_warn(&wd->pdev->dev, "Unable to initialize RISC\n"); | |
414 | + goto wd719x_init_end; | |
415 | + } | |
416 | + /* RISC is up and running */ | |
417 | + | |
418 | + /* Read FW version from RISC */ | |
419 | + ret = wd719x_direct_cmd(wd, WD719X_CMD_READ_FIRMVER, 0, 0, 0, 0, | |
420 | + WD719X_WAIT_FOR_RISC); | |
421 | + if (ret) { | |
422 | + dev_warn(&wd->pdev->dev, "Unable to read firmware version\n"); | |
423 | + goto wd719x_init_end; | |
424 | + } | |
425 | + dev_info(&wd->pdev->dev, "RISC initialized with firmware version %.2x.%.2x\n", | |
426 | + wd719x_readb(wd, WD719X_AMR_SCB_OUT + 1), | |
427 | + wd719x_readb(wd, WD719X_AMR_SCB_OUT)); | |
428 | + | |
429 | + /* RESET SCSI bus */ | |
430 | + ret = wd719x_direct_cmd(wd, WD719X_CMD_BUSRESET, 0, 0, 0, 0, | |
431 | + WD719X_WAIT_FOR_SCSI_RESET); | |
432 | + if (ret) { | |
433 | + dev_warn(&wd->pdev->dev, "SCSI bus reset failed\n"); | |
434 | + goto wd719x_init_end; | |
435 | + } | |
436 | + | |
437 | + /* use HostParameter structure to set Spider's Host Parameter Block */ | |
438 | + ret = wd719x_direct_cmd(wd, WD719X_CMD_SET_PARAM, 0, | |
439 | + sizeof(struct wd719x_host_param), 0, | |
440 | + wd->params_phys, WD719X_WAIT_FOR_RISC); | |
441 | + if (ret) { | |
442 | + dev_warn(&wd->pdev->dev, "Failed to set HOST PARAMETERS\n"); | |
443 | + goto wd719x_init_end; | |
444 | + } | |
445 | + | |
446 | + /* initiate SCAM (does nothing if disabled in BIOS) */ | |
447 | + /* bug?: we should pass a mask of static IDs which we don't have */ | |
448 | + ret = wd719x_direct_cmd(wd, WD719X_CMD_INIT_SCAM, 0, 0, 0, 0, | |
449 | + WD719X_WAIT_FOR_SCSI_RESET); | |
450 | + if (ret) { | |
451 | + dev_warn(&wd->pdev->dev, "SCAM initialization failed\n"); | |
452 | + goto wd719x_init_end; | |
453 | + } | |
454 | + | |
455 | + /* clear AMR_BIOS_SHARE_INT register */ | |
456 | + wd719x_writeb(wd, WD719X_AMR_BIOS_SHARE_INT, 0); | |
457 | + | |
458 | +wd719x_init_end: | |
459 | + release_firmware(fw_wcs); | |
460 | + release_firmware(fw_risc); | |
461 | + | |
462 | + return ret; | |
463 | +} | |
464 | + | |
465 | +static int wd719x_abort(struct scsi_cmnd *cmd) | |
466 | +{ | |
467 | + int action, result; | |
468 | + unsigned long flags; | |
469 | + struct wd719x_scb *scb = (struct wd719x_scb *)cmd->host_scribble; | |
470 | + struct wd719x *wd = shost_priv(cmd->device->host); | |
471 | + | |
472 | + dev_info(&wd->pdev->dev, "abort command, tag: %x\n", cmd->tag); | |
473 | + | |
474 | + action = /*cmd->tag ? WD719X_CMD_ABORT_TAG : */WD719X_CMD_ABORT; | |
475 | + | |
476 | + spin_lock_irqsave(wd->sh->host_lock, flags); | |
477 | + result = wd719x_direct_cmd(wd, action, cmd->device->id, | |
478 | + cmd->device->lun, cmd->tag, scb->phys, 0); | |
479 | + spin_unlock_irqrestore(wd->sh->host_lock, flags); | |
480 | + if (result) | |
481 | + return FAILED; | |
482 | + | |
483 | + return SUCCESS; | |
484 | +} | |
485 | + | |
486 | +static int wd719x_reset(struct scsi_cmnd *cmd, u8 opcode, u8 device) | |
487 | +{ | |
488 | + int result; | |
489 | + unsigned long flags; | |
490 | + struct wd719x *wd = shost_priv(cmd->device->host); | |
491 | + | |
492 | + dev_info(&wd->pdev->dev, "%s reset requested\n", | |
493 | + (opcode == WD719X_CMD_BUSRESET) ? "bus" : "device"); | |
494 | + | |
495 | + spin_lock_irqsave(wd->sh->host_lock, flags); | |
496 | + result = wd719x_direct_cmd(wd, opcode, device, 0, 0, 0, | |
497 | + WD719X_WAIT_FOR_SCSI_RESET); | |
498 | + spin_unlock_irqrestore(wd->sh->host_lock, flags); | |
499 | + if (result) | |
500 | + return FAILED; | |
501 | + | |
502 | + return SUCCESS; | |
503 | +} | |
504 | + | |
505 | +static int wd719x_dev_reset(struct scsi_cmnd *cmd) | |
506 | +{ | |
507 | + return wd719x_reset(cmd, WD719X_CMD_RESET, cmd->device->id); | |
508 | +} | |
509 | + | |
510 | +static int wd719x_bus_reset(struct scsi_cmnd *cmd) | |
511 | +{ | |
512 | + return wd719x_reset(cmd, WD719X_CMD_BUSRESET, 0); | |
513 | +} | |
514 | + | |
515 | +static int wd719x_host_reset(struct scsi_cmnd *cmd) | |
516 | +{ | |
517 | + struct wd719x *wd = shost_priv(cmd->device->host); | |
518 | + struct wd719x_scb *scb, *tmp; | |
519 | + unsigned long flags; | |
520 | + int result; | |
521 | + | |
522 | + dev_info(&wd->pdev->dev, "host reset requested\n"); | |
523 | + spin_lock_irqsave(wd->sh->host_lock, flags); | |
524 | + /* Try to reinit the RISC */ | |
525 | + if (wd719x_chip_init(wd) == 0) | |
526 | + result = SUCCESS; | |
527 | + else | |
528 | + result = FAILED; | |
529 | + | |
530 | + /* flush all SCBs */ | |
531 | + list_for_each_entry_safe(scb, tmp, &wd->active_scbs, list) { | |
532 | + struct scsi_cmnd *tmp_cmd = scb->cmd; | |
533 | + wd719x_finish_cmd(tmp_cmd, result); | |
534 | + } | |
535 | + spin_unlock_irqrestore(wd->sh->host_lock, flags); | |
536 | + | |
537 | + return result; | |
538 | +} | |
539 | + | |
540 | +static int wd719x_biosparam(struct scsi_device *sdev, struct block_device *bdev, | |
541 | + sector_t capacity, int geom[]) | |
542 | +{ | |
543 | + if (capacity >= 0x200000) { | |
544 | + geom[0] = 255; /* heads */ | |
545 | + geom[1] = 63; /* sectors */ | |
546 | + } else { | |
547 | + geom[0] = 64; /* heads */ | |
548 | + geom[1] = 32; /* sectors */ | |
549 | + } | |
550 | + geom[2] = sector_div(capacity, geom[0] * geom[1]); /* cylinders */ | |
551 | + | |
552 | + return 0; | |
553 | +} | |
554 | + | |
555 | +/* process a SCB-completion interrupt */ | |
556 | +static inline void wd719x_interrupt_SCB(struct wd719x *wd, | |
557 | + union wd719x_regs regs, | |
558 | + struct wd719x_scb *scb) | |
559 | +{ | |
560 | + struct scsi_cmnd *cmd; | |
561 | + int result; | |
562 | + | |
563 | + /* now have to find result from card */ | |
564 | + switch (regs.bytes.SUE) { | |
565 | + case WD719X_SUE_NOERRORS: | |
566 | + result = DID_OK; | |
567 | + break; | |
568 | + case WD719X_SUE_REJECTED: | |
569 | + dev_err(&wd->pdev->dev, "command rejected\n"); | |
570 | + result = DID_ERROR; | |
571 | + break; | |
572 | + case WD719X_SUE_SCBQFULL: | |
573 | + dev_err(&wd->pdev->dev, "SCB queue is full\n"); | |
574 | + result = DID_ERROR; | |
575 | + break; | |
576 | + case WD719X_SUE_TERM: | |
577 | + dev_dbg(&wd->pdev->dev, "SCB terminated by direct command\n"); | |
578 | + result = DID_ABORT; /* or DID_RESET? */ | |
579 | + break; | |
580 | + case WD719X_SUE_CHAN1ABORT: | |
581 | + case WD719X_SUE_CHAN23ABORT: | |
582 | + result = DID_ABORT; | |
583 | + dev_err(&wd->pdev->dev, "DMA abort\n"); | |
584 | + break; | |
585 | + case WD719X_SUE_CHAN1PAR: | |
586 | + case WD719X_SUE_CHAN23PAR: | |
587 | + result = DID_PARITY; | |
588 | + dev_err(&wd->pdev->dev, "DMA parity error\n"); | |
589 | + break; | |
590 | + case WD719X_SUE_TIMEOUT: | |
591 | + result = DID_TIME_OUT; | |
592 | + dev_dbg(&wd->pdev->dev, "selection timeout\n"); | |
593 | + break; | |
594 | + case WD719X_SUE_RESET: | |
595 | + dev_dbg(&wd->pdev->dev, "bus reset occured\n"); | |
596 | + result = DID_RESET; | |
597 | + break; | |
598 | + case WD719X_SUE_BUSERROR: | |
599 | + dev_dbg(&wd->pdev->dev, "SCSI bus error\n"); | |
600 | + result = DID_ERROR; | |
601 | + break; | |
602 | + case WD719X_SUE_WRONGWAY: | |
603 | + dev_err(&wd->pdev->dev, "wrong data transfer direction\n"); | |
604 | + result = DID_ERROR; | |
605 | + break; | |
606 | + case WD719X_SUE_BADPHASE: | |
607 | + dev_err(&wd->pdev->dev, "invalid SCSI phase\n"); | |
608 | + result = DID_ERROR; | |
609 | + break; | |
610 | + case WD719X_SUE_TOOLONG: | |
611 | + dev_err(&wd->pdev->dev, "record too long\n"); | |
612 | + result = DID_ERROR; | |
613 | + break; | |
614 | + case WD719X_SUE_BUSFREE: | |
615 | + dev_err(&wd->pdev->dev, "unexpected bus free\n"); | |
616 | + result = DID_NO_CONNECT; /* or DID_ERROR ???*/ | |
617 | + break; | |
618 | + case WD719X_SUE_ARSDONE: | |
619 | + dev_dbg(&wd->pdev->dev, "auto request sense\n"); | |
620 | + if (regs.bytes.SCSI == 0) | |
621 | + result = DID_OK; | |
622 | + else | |
623 | + result = DID_PARITY; | |
624 | + break; | |
625 | + case WD719X_SUE_IGNORED: | |
626 | + dev_err(&wd->pdev->dev, "target id %d ignored command\n", | |
627 | + scb->cmd->device->id); | |
628 | + result = DID_NO_CONNECT; | |
629 | + break; | |
630 | + case WD719X_SUE_WRONGTAGS: | |
631 | + dev_err(&wd->pdev->dev, "reversed tags\n"); | |
632 | + result = DID_ERROR; | |
633 | + break; | |
634 | + case WD719X_SUE_BADTAGS: | |
635 | + dev_err(&wd->pdev->dev, "tag type not supported by target\n"); | |
636 | + result = DID_ERROR; | |
637 | + break; | |
638 | + case WD719X_SUE_NOSCAMID: | |
639 | + dev_err(&wd->pdev->dev, "no SCAM soft ID available\n"); | |
640 | + result = DID_ERROR; | |
641 | + break; | |
642 | + default: | |
643 | + dev_warn(&wd->pdev->dev, "unknown SUE error code: 0x%x\n", | |
644 | + regs.bytes.SUE); | |
645 | + result = DID_ERROR; | |
646 | + break; | |
647 | + } | |
648 | + cmd = scb->cmd; | |
649 | + | |
650 | + wd719x_finish_cmd(cmd, result); | |
651 | +} | |
652 | + | |
653 | +static irqreturn_t wd719x_interrupt(int irq, void *dev_id) | |
654 | +{ | |
655 | + struct wd719x *wd = dev_id; | |
656 | + union wd719x_regs regs; | |
657 | + unsigned long flags; | |
658 | + u32 SCB_out; | |
659 | + | |
660 | + spin_lock_irqsave(wd->sh->host_lock, flags); | |
661 | + /* read SCB pointer back from card */ | |
662 | + SCB_out = wd719x_readl(wd, WD719X_AMR_SCB_OUT); | |
663 | + /* read all status info at once */ | |
664 | + regs.all = cpu_to_le32(wd719x_readl(wd, WD719X_AMR_OP_CODE)); | |
665 | + | |
666 | + switch (regs.bytes.INT) { | |
667 | + case WD719X_INT_NONE: | |
668 | + spin_unlock_irqrestore(wd->sh->host_lock, flags); | |
669 | + return IRQ_NONE; | |
670 | + case WD719X_INT_LINKNOSTATUS: | |
671 | + dev_err(&wd->pdev->dev, "linked command completed with no status\n"); | |
672 | + break; | |
673 | + case WD719X_INT_BADINT: | |
674 | + dev_err(&wd->pdev->dev, "unsolicited interrupt\n"); | |
675 | + break; | |
676 | + case WD719X_INT_NOERRORS: | |
677 | + case WD719X_INT_LINKNOERRORS: | |
678 | + case WD719X_INT_ERRORSLOGGED: | |
679 | + case WD719X_INT_SPIDERFAILED: | |
680 | + /* was the cmd completed a direct or SCB command? */ | |
681 | + if (regs.bytes.OPC == WD719X_CMD_PROCESS_SCB) { | |
682 | + struct wd719x_scb *scb; | |
683 | + list_for_each_entry(scb, &wd->active_scbs, list) | |
684 | + if (SCB_out == scb->phys) | |
685 | + break; | |
686 | + if (SCB_out == scb->phys) | |
687 | + wd719x_interrupt_SCB(wd, regs, scb); | |
688 | + else | |
689 | + dev_err(&wd->pdev->dev, "card returned invalid SCB pointer\n"); | |
690 | + } else | |
691 | + dev_warn(&wd->pdev->dev, "direct command 0x%x completed\n", | |
692 | + regs.bytes.OPC); | |
693 | + break; | |
694 | + case WD719X_INT_PIOREADY: | |
695 | + dev_err(&wd->pdev->dev, "card indicates PIO data ready but we never use PIO\n"); | |
696 | + /* interrupt will not be cleared until all data is read */ | |
697 | + break; | |
698 | + default: | |
699 | + dev_err(&wd->pdev->dev, "unknown interrupt reason: %d\n", | |
700 | + regs.bytes.INT); | |
701 | + | |
702 | + } | |
703 | + /* clear interrupt so another can happen */ | |
704 | + wd719x_writeb(wd, WD719X_AMR_INT_STATUS, WD719X_INT_NONE); | |
705 | + spin_unlock_irqrestore(wd->sh->host_lock, flags); | |
706 | + | |
707 | + return IRQ_HANDLED; | |
708 | +} | |
709 | + | |
710 | +static void wd719x_eeprom_reg_read(struct eeprom_93cx6 *eeprom) | |
711 | +{ | |
712 | + struct wd719x *wd = eeprom->data; | |
713 | + u8 reg = wd719x_readb(wd, WD719X_PCI_GPIO_DATA); | |
714 | + | |
715 | + eeprom->reg_data_out = reg & WD719X_EE_DO; | |
716 | +} | |
717 | + | |
718 | +static void wd719x_eeprom_reg_write(struct eeprom_93cx6 *eeprom) | |
719 | +{ | |
720 | + struct wd719x *wd = eeprom->data; | |
721 | + u8 reg = 0; | |
722 | + | |
723 | + if (eeprom->reg_data_in) | |
724 | + reg |= WD719X_EE_DI; | |
725 | + if (eeprom->reg_data_clock) | |
726 | + reg |= WD719X_EE_CLK; | |
727 | + if (eeprom->reg_chip_select) | |
728 | + reg |= WD719X_EE_CS; | |
729 | + | |
730 | + wd719x_writeb(wd, WD719X_PCI_GPIO_DATA, reg); | |
731 | +} | |
732 | + | |
733 | +/* read config from EEPROM so it can be downloaded by the RISC on (re-)init */ | |
734 | +static void wd719x_read_eeprom(struct wd719x *wd) | |
735 | +{ | |
736 | + struct eeprom_93cx6 eeprom; | |
737 | + u8 gpio; | |
738 | + struct wd719x_eeprom_header header; | |
739 | + | |
740 | + eeprom.data = wd; | |
741 | + eeprom.register_read = wd719x_eeprom_reg_read; | |
742 | + eeprom.register_write = wd719x_eeprom_reg_write; | |
743 | + eeprom.width = PCI_EEPROM_WIDTH_93C46; | |
744 | + | |
745 | + /* set all outputs to low */ | |
746 | + wd719x_writeb(wd, WD719X_PCI_GPIO_DATA, 0); | |
747 | + /* configure GPIO pins */ | |
748 | + gpio = wd719x_readb(wd, WD719X_PCI_GPIO_CONTROL); | |
749 | + /* GPIO outputs */ | |
750 | + gpio &= (~(WD719X_EE_CLK | WD719X_EE_DI | WD719X_EE_CS)); | |
751 | + /* GPIO input */ | |
752 | + gpio |= WD719X_EE_DO; | |
753 | + wd719x_writeb(wd, WD719X_PCI_GPIO_CONTROL, gpio); | |
754 | + | |
755 | + /* read EEPROM header */ | |
756 | + eeprom_93cx6_multireadb(&eeprom, 0, (u8 *)&header, sizeof(header)); | |
757 | + | |
758 | + if (header.sig1 == 'W' && header.sig2 == 'D') | |
759 | + eeprom_93cx6_multireadb(&eeprom, header.cfg_offset, | |
760 | + (u8 *)wd->params, | |
761 | + sizeof(struct wd719x_host_param)); | |
762 | + else { /* default EEPROM values */ | |
763 | + dev_warn(&wd->pdev->dev, "EEPROM signature is invalid (0x%02x 0x%02x), using default values\n", | |
764 | + header.sig1, header.sig2); | |
765 | + wd->params->ch_1_th = 0x10; /* 16 DWs = 64 B */ | |
766 | + wd->params->scsi_conf = 0x4c; /* 48ma, spue, parity check */ | |
767 | + wd->params->own_scsi_id = 0x07; /* ID 7, SCAM disabled */ | |
768 | + wd->params->sel_timeout = 0x4d; /* 250 ms */ | |
769 | + wd->params->sleep_timer = 0x01; | |
770 | + wd->params->cdb_size = cpu_to_le16(0x5555); /* all 6 B */ | |
771 | + wd->params->scsi_pad = 0x1b; | |
772 | + if (wd->type == WD719X_TYPE_7193) /* narrow card - disable */ | |
773 | + wd->params->wide = cpu_to_le32(0x00000000); | |
774 | + else /* initiate & respond to WIDE messages */ | |
775 | + wd->params->wide = cpu_to_le32(0xffffffff); | |
776 | + wd->params->sync = cpu_to_le32(0xffffffff); | |
777 | + wd->params->soft_mask = 0x00; /* all disabled */ | |
778 | + wd->params->unsol_mask = 0x00; /* all disabled */ | |
779 | + } | |
780 | + /* disable TAGGED messages */ | |
781 | + wd->params->tag_en = cpu_to_le16(0x0000); | |
782 | +} | |
783 | + | |
784 | +/* Read card type from GPIO bits 1 and 3 */ | |
785 | +static enum wd719x_card_type wd719x_detect_type(struct wd719x *wd) | |
786 | +{ | |
787 | + u8 card = wd719x_readb(wd, WD719X_PCI_GPIO_CONTROL); | |
788 | + | |
789 | + card |= WD719X_GPIO_ID_BITS; | |
790 | + wd719x_writeb(wd, WD719X_PCI_GPIO_CONTROL, card); | |
791 | + card = wd719x_readb(wd, WD719X_PCI_GPIO_DATA) & WD719X_GPIO_ID_BITS; | |
792 | + switch (card) { | |
793 | + case 0x08: | |
794 | + return WD719X_TYPE_7193; | |
795 | + case 0x02: | |
796 | + return WD719X_TYPE_7197; | |
797 | + case 0x00: | |
798 | + return WD719X_TYPE_7296; | |
799 | + default: | |
800 | + dev_warn(&wd->pdev->dev, "unknown card type 0x%x\n", card); | |
801 | + return WD719X_TYPE_UNKNOWN; | |
802 | + } | |
803 | +} | |
804 | + | |
805 | +static int wd719x_board_found(struct Scsi_Host *sh) | |
806 | +{ | |
807 | + struct wd719x *wd = shost_priv(sh); | |
808 | + char *card_types[] = { "Unknown card", "WD7193", "WD7197", "WD7296" }; | |
809 | + int ret; | |
810 | + | |
811 | + INIT_LIST_HEAD(&wd->active_scbs); | |
812 | + INIT_LIST_HEAD(&wd->free_scbs); | |
813 | + | |
814 | + sh->base = pci_resource_start(wd->pdev, 0); | |
815 | + | |
816 | + wd->type = wd719x_detect_type(wd); | |
817 | + | |
818 | + wd->sh = sh; | |
819 | + sh->irq = wd->pdev->irq; | |
820 | + wd->fw_virt = NULL; | |
821 | + | |
822 | + /* memory area for host (EEPROM) parameters */ | |
823 | + wd->params = pci_alloc_consistent(wd->pdev, | |
824 | + sizeof(struct wd719x_host_param), | |
825 | + &wd->params_phys); | |
826 | + if (!wd->params) { | |
827 | + dev_warn(&wd->pdev->dev, "unable to allocate parameter buffer\n"); | |
828 | + return -ENOMEM; | |
829 | + } | |
830 | + | |
831 | + /* memory area for the RISC for hash table of outstanding requests */ | |
832 | + wd->hash_virt = pci_alloc_consistent(wd->pdev, WD719X_HASH_TABLE_SIZE, | |
833 | + &wd->hash_phys); | |
834 | + if (!wd->hash_virt) { | |
835 | + dev_warn(&wd->pdev->dev, "unable to allocate hash buffer\n"); | |
836 | + ret = -ENOMEM; | |
837 | + goto fail_free_params; | |
838 | + } | |
839 | + | |
840 | + ret = request_irq(wd->pdev->irq, wd719x_interrupt, IRQF_SHARED, | |
841 | + "wd719x", wd); | |
842 | + if (ret) { | |
843 | + dev_warn(&wd->pdev->dev, "unable to assign IRQ %d\n", | |
844 | + wd->pdev->irq); | |
845 | + goto fail_free_hash; | |
846 | + } | |
847 | + | |
848 | + /* read parameters from EEPROM */ | |
849 | + wd719x_read_eeprom(wd); | |
850 | + | |
851 | + ret = wd719x_chip_init(wd); | |
852 | + if (ret) | |
853 | + goto fail_free_irq; | |
854 | + | |
855 | + sh->this_id = wd->params->own_scsi_id & WD719X_EE_SCSI_ID_MASK; | |
856 | + | |
857 | + dev_info(&wd->pdev->dev, "%s at I/O 0x%lx, IRQ %u, SCSI ID %d\n", | |
858 | + card_types[wd->type], sh->base, sh->irq, sh->this_id); | |
859 | + | |
860 | + return 0; | |
861 | + | |
862 | +fail_free_irq: | |
863 | + free_irq(wd->pdev->irq, wd); | |
864 | +fail_free_hash: | |
865 | + pci_free_consistent(wd->pdev, WD719X_HASH_TABLE_SIZE, wd->hash_virt, | |
866 | + wd->hash_phys); | |
867 | +fail_free_params: | |
868 | + pci_free_consistent(wd->pdev, sizeof(struct wd719x_host_param), | |
869 | + wd->params, wd->params_phys); | |
870 | + | |
871 | + return ret; | |
872 | +} | |
873 | + | |
874 | +static struct scsi_host_template wd719x_template = { | |
875 | + .name = "Western Digital 719x", | |
876 | + .queuecommand = wd719x_queuecommand, | |
877 | + .eh_abort_handler = wd719x_abort, | |
878 | + .eh_device_reset_handler = wd719x_dev_reset, | |
879 | + .eh_bus_reset_handler = wd719x_bus_reset, | |
880 | + .eh_host_reset_handler = wd719x_host_reset, | |
881 | + .bios_param = wd719x_biosparam, | |
882 | + .proc_name = "wd719x", | |
883 | + .can_queue = 255, | |
884 | + .this_id = 7, | |
885 | + .sg_tablesize = WD719X_SG, | |
886 | + .cmd_per_lun = WD719X_CMD_PER_LUN, | |
887 | + .use_clustering = ENABLE_CLUSTERING, | |
888 | +}; | |
889 | + | |
890 | +static int wd719x_pci_probe(struct pci_dev *pdev, const struct pci_device_id *d) | |
891 | +{ | |
892 | + int err; | |
893 | + struct Scsi_Host *sh; | |
894 | + struct wd719x *wd; | |
895 | + | |
896 | + err = pci_enable_device(pdev); | |
897 | + if (err) | |
898 | + goto fail; | |
899 | + | |
900 | + if (pci_set_dma_mask(pdev, DMA_BIT_MASK(32))) { | |
901 | + dev_warn(&pdev->dev, "Unable to set 32-bit DMA mask\n"); | |
902 | + goto disable_device; | |
903 | + } | |
904 | + | |
905 | + err = pci_request_regions(pdev, "wd719x"); | |
906 | + if (err) | |
907 | + goto disable_device; | |
908 | + pci_set_master(pdev); | |
909 | + | |
910 | + err = -ENODEV; | |
911 | + if (pci_resource_len(pdev, 0) == 0) | |
912 | + goto release_region; | |
913 | + | |
914 | + err = -ENOMEM; | |
915 | + sh = scsi_host_alloc(&wd719x_template, sizeof(struct wd719x)); | |
916 | + if (!sh) | |
917 | + goto release_region; | |
918 | + | |
919 | + wd = shost_priv(sh); | |
920 | + wd->base = pci_iomap(pdev, 0, 0); | |
921 | + if (!wd->base) | |
922 | + goto free_host; | |
923 | + wd->pdev = pdev; | |
924 | + | |
925 | + err = wd719x_board_found(sh); | |
926 | + if (err) | |
927 | + goto unmap; | |
928 | + | |
929 | + err = scsi_add_host(sh, &wd->pdev->dev); | |
930 | + if (err) | |
931 | + goto destroy; | |
932 | + | |
933 | + scsi_scan_host(sh); | |
934 | + | |
935 | + pci_set_drvdata(pdev, sh); | |
936 | + return 0; | |
937 | + | |
938 | +destroy: | |
939 | + wd719x_destroy(wd); | |
940 | +unmap: | |
941 | + pci_iounmap(pdev, wd->base); | |
942 | +free_host: | |
943 | + scsi_host_put(sh); | |
944 | +release_region: | |
945 | + pci_release_regions(pdev); | |
946 | +disable_device: | |
947 | + pci_disable_device(pdev); | |
948 | +fail: | |
949 | + return err; | |
950 | +} | |
951 | + | |
952 | + | |
953 | +static void wd719x_pci_remove(struct pci_dev *pdev) | |
954 | +{ | |
955 | + struct Scsi_Host *sh = pci_get_drvdata(pdev); | |
956 | + struct wd719x *wd = shost_priv(sh); | |
957 | + | |
958 | + scsi_remove_host(sh); | |
959 | + wd719x_destroy(wd); | |
960 | + pci_iounmap(pdev, wd->base); | |
961 | + pci_release_regions(pdev); | |
962 | + pci_disable_device(pdev); | |
963 | + | |
964 | + scsi_host_put(sh); | |
965 | +} | |
966 | + | |
967 | +static DEFINE_PCI_DEVICE_TABLE(wd719x_pci_table) = { | |
968 | + { PCI_DEVICE(PCI_VENDOR_ID_WD, 0x3296) }, | |
969 | + {} | |
970 | +}; | |
971 | + | |
972 | +MODULE_DEVICE_TABLE(pci, wd719x_pci_table); | |
973 | + | |
974 | +static struct pci_driver wd719x_pci_driver = { | |
975 | + .name = "wd719x", | |
976 | + .id_table = wd719x_pci_table, | |
977 | + .probe = wd719x_pci_probe, | |
978 | + .remove = wd719x_pci_remove, | |
979 | +}; | |
980 | + | |
981 | +static int __init wd719x_init(void) | |
982 | +{ | |
983 | + return pci_register_driver(&wd719x_pci_driver); | |
984 | +} | |
985 | + | |
986 | +static void __exit wd719x_exit(void) | |
987 | +{ | |
988 | + pci_unregister_driver(&wd719x_pci_driver); | |
989 | +} | |
990 | + | |
991 | +module_init(wd719x_init); | |
992 | +module_exit(wd719x_exit); | |
993 | + | |
994 | +MODULE_DESCRIPTION("Western Digital WD7193/7197/7296 SCSI driver"); | |
995 | +MODULE_AUTHOR("Ondrej Zary, Aaron Dewell, Juergen Gaertner"); | |
996 | +MODULE_LICENSE("GPL"); | |
997 | +MODULE_FIRMWARE("wd719x-wcs.bin"); | |
998 | +MODULE_FIRMWARE("wd719x-risc.bin"); |
drivers/scsi/wd719x.h
1 | +#ifndef _WD719X_H_ | |
2 | +#define _WD719X_H_ | |
3 | + | |
4 | +#define WD719X_SG 255 /* Scatter/gather size */ | |
5 | +#define WD719X_CMD_PER_LUN 1 /* We should be able to do linked commands, but | |
6 | + * this is 1 for now to be safe. */ | |
7 | + | |
8 | +struct wd719x_sglist { | |
9 | + __le32 ptr; | |
10 | + __le32 length; | |
11 | +} __packed; | |
12 | + | |
13 | +enum wd719x_card_type { | |
14 | + WD719X_TYPE_UNKNOWN = 0, | |
15 | + WD719X_TYPE_7193, | |
16 | + WD719X_TYPE_7197, | |
17 | + WD719X_TYPE_7296, | |
18 | +}; | |
19 | + | |
20 | +union wd719x_regs { | |
21 | + __le32 all; /* All Status at once */ | |
22 | + struct { | |
23 | + u8 OPC; /* Opcode register */ | |
24 | + u8 SCSI; /* SCSI Errors */ | |
25 | + u8 SUE; /* Spider unique Errors */ | |
26 | + u8 INT; /* Interrupt Status */ | |
27 | + } bytes; | |
28 | +}; | |
29 | + | |
30 | +/* Spider Command Block (SCB) */ | |
31 | +struct wd719x_scb { | |
32 | + __le32 Int_SCB; /* 00-03 Internal SCB link pointer (must be cleared) */ | |
33 | + u8 SCB_opcode; /* 04 SCB Command opcode */ | |
34 | + u8 CDB_tag; /* 05 SCSI Tag byte for CDB queues (0 if untagged) */ | |
35 | + u8 lun; /* 06 SCSI LUN */ | |
36 | + u8 devid; /* 07 SCSI Device ID */ | |
37 | + u8 CDB[16]; /* 08-23 SCSI CDB (16 bytes as defined by ANSI spec. */ | |
38 | + __le32 data_p; /* 24-27 Data transfer address (or SG list address) */ | |
39 | + __le32 data_length; /* 28-31 Data transfer Length (or SG list length) */ | |
40 | + __le32 CDB_link; /* 32-35 SCSI CDB Link Ptr */ | |
41 | + __le32 sense_buf; /* 36-39 Auto request sense buffer address */ | |
42 | + u8 sense_buf_length;/* 40 Auto request sense transfer length */ | |
43 | + u8 reserved; /* 41 reserved */ | |
44 | + u8 SCB_options; /* 42 SCB-options */ | |
45 | + u8 SCB_tag_msg; /* 43 Tagged messages options */ | |
46 | + /* Not filled in by host */ | |
47 | + __le32 req_ptr; /* 44-47 Ptr to Host Request returned on interrupt */ | |
48 | + u8 host_opcode; /* 48 Host Command Opcode (same as AMR_00) */ | |
49 | + u8 scsi_stat; /* 49 SCSI Status returned */ | |
50 | + u8 ret_error; /* 50 SPIDER Unique Error Code returned (SUE) */ | |
51 | + u8 int_stat; /* 51 Message u8 / Interrupt Status byte returned */ | |
52 | + __le32 transferred; /* 52-55 Bytes Transferred */ | |
53 | + u8 last_trans[3]; /* 56-58 Bytes Transferred in last session */ | |
54 | + u8 length; /* 59 SCSI Messages Length (1-8) */ | |
55 | + u8 sync_offset; /* 60 Synchronous offset */ | |
56 | + u8 sync_rate; /* 61 Synchronous rate */ | |
57 | + u8 flags[2]; /* 62-63 SCB specific flags (local to each thread) */ | |
58 | + /* everything below is for driver use (not used by card) */ | |
59 | + dma_addr_t phys; /* bus address of the SCB */ | |
60 | + struct scsi_cmnd *cmd; /* a copy of the pointer we were passed */ | |
61 | + struct list_head list; | |
62 | + struct wd719x_sglist sg_list[WD719X_SG] __aligned(8); /* SG list */ | |
63 | +} __packed; | |
64 | + | |
65 | +struct wd719x { | |
66 | + struct Scsi_Host *sh; /* pointer to host structure */ | |
67 | + struct pci_dev *pdev; | |
68 | + void __iomem *base; | |
69 | + enum wd719x_card_type type; /* type of card */ | |
70 | + void *fw_virt; /* firmware buffer CPU address */ | |
71 | + dma_addr_t fw_phys; /* firmware buffer bus address */ | |
72 | + size_t fw_size; /* firmware buffer size */ | |
73 | + struct wd719x_host_param *params; /* host parameters (EEPROM) */ | |
74 | + dma_addr_t params_phys; /* host parameters bus address */ | |
75 | + void *hash_virt; /* hash table CPU address */ | |
76 | + dma_addr_t hash_phys; /* hash table bus address */ | |
77 | + struct list_head active_scbs; | |
78 | + struct list_head free_scbs; | |
79 | +}; | |
80 | + | |
81 | +/* timeout delays in microsecs */ | |
82 | +#define WD719X_WAIT_FOR_CMD_READY 500 | |
83 | +#define WD719X_WAIT_FOR_RISC 2000 | |
84 | +#define WD719X_WAIT_FOR_SCSI_RESET 3000000 | |
85 | + | |
86 | +/* All commands except 0x00 generate an interrupt */ | |
87 | +#define WD719X_CMD_READY 0x00 /* Command register ready (or noop) */ | |
88 | +#define WD719X_CMD_INIT_RISC 0x01 /* Initialize RISC */ | |
89 | +/* 0x02 is reserved */ | |
90 | +#define WD719X_CMD_BUSRESET 0x03 /* Assert SCSI bus reset */ | |
91 | +#define WD719X_CMD_READ_FIRMVER 0x04 /* Read the Firmware Revision */ | |
92 | +#define WD719X_CMD_ECHO_BYTES 0x05 /* Echo command bytes (DW) */ | |
93 | +/* 0x06 is reserved */ | |
94 | +/* 0x07 is reserved */ | |
95 | +#define WD719X_CMD_GET_PARAM 0x08 /* Get programmable parameters */ | |
96 | +#define WD719X_CMD_SET_PARAM 0x09 /* Set programmable parameters */ | |
97 | +#define WD719X_CMD_SLEEP 0x0a /* Put SPIDER to sleep */ | |
98 | +#define WD719X_CMD_READ_INIT 0x0b /* Read initialization parameters */ | |
99 | +#define WD719X_CMD_RESTORE_INIT 0x0c /* Restore initialization parameters */ | |
100 | +/* 0x0d is reserved */ | |
101 | +/* 0x0e is reserved */ | |
102 | +/* 0x0f is reserved */ | |
103 | +#define WD719X_CMD_ABORT_TAG 0x10 /* Send Abort tag message to target */ | |
104 | +#define WD719X_CMD_ABORT 0x11 /* Send Abort message to target */ | |
105 | +#define WD719X_CMD_RESET 0x12 /* Send Reset message to target */ | |
106 | +#define WD719X_CMD_INIT_SCAM 0x13 /* Initiate SCAM */ | |
107 | +#define WD719X_CMD_GET_SYNC 0x14 /* Get synchronous rates */ | |
108 | +#define WD719X_CMD_SET_SYNC 0x15 /* Set synchronous rates */ | |
109 | +#define WD719X_CMD_GET_WIDTH 0x16 /* Get SCSI bus width */ | |
110 | +#define WD719X_CMD_SET_WIDTH 0x17 /* Set SCSI bus width */ | |
111 | +#define WD719X_CMD_GET_TAGS 0x18 /* Get tag flags */ | |
112 | +#define WD719X_CMD_SET_TAGS 0x19 /* Set tag flags */ | |
113 | +#define WD719X_CMD_GET_PARAM2 0x1a /* Get programmable params (format 2) */ | |
114 | +#define WD719X_CMD_SET_PARAM2 0x1b /* Set programmable params (format 2) */ | |
115 | +/* Commands with request pointers (mailbox) */ | |
116 | +#define WD719X_CMD_PROCESS_SCB 0x80 /* Process SCSI Control Block (SCB) */ | |
117 | +/* No interrupt generated on acceptance of SCB pointer */ | |
118 | + | |
119 | +/* interrupt status defines */ | |
120 | +#define WD719X_INT_NONE 0x00 /* No interrupt pending */ | |
121 | +#define WD719X_INT_NOERRORS 0x01 /* Command completed with no errors */ | |
122 | +#define WD719X_INT_LINKNOERRORS 0x02 /* link cmd completed with no errors */ | |
123 | +#define WD719X_INT_LINKNOSTATUS 0x03 /* link cmd completed with no flag set */ | |
124 | +#define WD719X_INT_ERRORSLOGGED 0x04 /* cmd completed with errors logged */ | |
125 | +#define WD719X_INT_SPIDERFAILED 0x05 /* cmd failed without valid SCSI status */ | |
126 | +#define WD719X_INT_BADINT 0x80 /* unsolicited interrupt */ | |
127 | +#define WD719X_INT_PIOREADY 0xf0 /* data ready for PIO output */ | |
128 | + | |
129 | +/* Spider Unique Error Codes (SUE) */ | |
130 | +#define WD719X_SUE_NOERRORS 0x00 /* No errors detected by SPIDER */ | |
131 | +#define WD719X_SUE_REJECTED 0x01 /* Command Rejected (bad opcode/param) */ | |
132 | +#define WD719X_SUE_SCBQFULL 0x02 /* SCB queue full */ | |
133 | +/* 0x03 is reserved */ | |
134 | +#define WD719X_SUE_TERM 0x04 /* Host terminated SCB via primative cmd */ | |
135 | +#define WD719X_SUE_CHAN1PAR 0x05 /* PCI Channel 1 parity error occurred */ | |
136 | +#define WD719X_SUE_CHAN1ABORT 0x06 /* PCI Channel 1 system abort occurred */ | |
137 | +#define WD719X_SUE_CHAN23PAR 0x07 /* PCI Channel 2/3 parity error occurred */ | |
138 | +#define WD719X_SUE_CHAN23ABORT 0x08 /* PCI Channel 2/3 system abort occurred */ | |
139 | +#define WD719X_SUE_TIMEOUT 0x10 /* Selection/reselection timeout */ | |
140 | +#define WD719X_SUE_RESET 0x11 /* SCSI bus reset occurred */ | |
141 | +#define WD719X_SUE_BUSERROR 0x12 /* SCSI bus error */ | |
142 | +#define WD719X_SUE_WRONGWAY 0x13 /* Wrong data transfer dir set by target */ | |
143 | +#define WD719X_SUE_BADPHASE 0x14 /* SCSI phase illegal or unexpected */ | |
144 | +#define WD719X_SUE_TOOLONG 0x15 /* target requested too much data */ | |
145 | +#define WD719X_SUE_BUSFREE 0x16 /* Unexpected SCSI bus free */ | |
146 | +#define WD719X_SUE_ARSDONE 0x17 /* Auto request sense executed */ | |
147 | +#define WD719X_SUE_IGNORED 0x18 /* SCSI message was ignored by target */ | |
148 | +#define WD719X_SUE_WRONGTAGS 0x19 /* Tagged SCB & tags off (or vice versa) */ | |
149 | +#define WD719X_SUE_BADTAGS 0x1a /* Wrong tag message type for target */ | |
150 | +#define WD719X_SUE_NOSCAMID 0x1b /* No SCAM soft ID available */ | |
151 | + | |
152 | +/* code sizes */ | |
153 | +#define WD719X_HASH_TABLE_SIZE 4096 | |
154 | + | |
155 | +/* Advanced Mode Registers */ | |
156 | +/* Regs 0x00..0x1f are for Advanced Mode of the card (RISC is running). */ | |
157 | +#define WD719X_AMR_COMMAND 0x00 | |
158 | +#define WD719X_AMR_CMD_PARAM 0x01 | |
159 | +#define WD719X_AMR_CMD_PARAM_2 0x02 | |
160 | +#define WD719X_AMR_CMD_PARAM_3 0x03 | |
161 | +#define WD719X_AMR_SCB_IN 0x04 | |
162 | + | |
163 | +#define WD719X_AMR_BIOS_SHARE_INT 0x0f | |
164 | + | |
165 | +#define WD719X_AMR_SCB_OUT 0x18 | |
166 | +#define WD719X_AMR_OP_CODE 0x1c | |
167 | +#define WD719X_AMR_SCSI_STATUS 0x1d | |
168 | +#define WD719X_AMR_SCB_ERROR 0x1e | |
169 | +#define WD719X_AMR_INT_STATUS 0x1f | |
170 | + | |
171 | +#define WD719X_DISABLE_INT 0x80 | |
172 | + | |
173 | +/* SCB flags */ | |
174 | +#define WD719X_SCB_FLAGS_CHECK_DIRECTION 0x01 | |
175 | +#define WD719X_SCB_FLAGS_PCI_TO_SCSI 0x02 | |
176 | +#define WD719X_SCB_FLAGS_AUTO_REQUEST_SENSE 0x10 | |
177 | +#define WD719X_SCB_FLAGS_DO_SCATTER_GATHER 0x20 | |
178 | +#define WD719X_SCB_FLAGS_NO_DISCONNECT 0x40 | |
179 | + | |
180 | +/* PCI Registers used for reset, initial code download */ | |
181 | +/* Regs 0x20..0x3f are for Normal (DOS) mode (RISC is asleep). */ | |
182 | +#define WD719X_PCI_GPIO_CONTROL 0x3C | |
183 | +#define WD719X_PCI_GPIO_DATA 0x3D | |
184 | +#define WD719X_PCI_PORT_RESET 0x3E | |
185 | +#define WD719X_PCI_MODE_SELECT 0x3F | |
186 | + | |
187 | +#define WD719X_PCI_EXTERNAL_ADDR 0x60 | |
188 | +#define WD719X_PCI_INTERNAL_ADDR 0x64 | |
189 | +#define WD719X_PCI_DMA_TRANSFER_SIZE 0x66 | |
190 | +#define WD719X_PCI_CHANNEL2_3CMD 0x68 | |
191 | +#define WD719X_PCI_CHANNEL2_3STATUS 0x69 | |
192 | + | |
193 | +#define WD719X_GPIO_ID_BITS 0x0a | |
194 | +#define WD719X_PRAM_BASE_ADDR 0x00 | |
195 | + | |
196 | +/* codes written to or read from the card */ | |
197 | +#define WD719X_PCI_RESET 0x01 | |
198 | +#define WD719X_ENABLE_ADVANCE_MODE 0x01 | |
199 | + | |
200 | +#define WD719X_START_CHANNEL2_3DMA 0x17 | |
201 | +#define WD719X_START_CHANNEL2_3DONE 0x01 | |
202 | +#define WD719X_START_CHANNEL2_3ABORT 0x20 | |
203 | + | |
204 | +/* 33C296 GPIO bits for EEPROM pins */ | |
205 | +#define WD719X_EE_DI (1 << 1) | |
206 | +#define WD719X_EE_CS (1 << 2) | |
207 | +#define WD719X_EE_CLK (1 << 3) | |
208 | +#define WD719X_EE_DO (1 << 4) | |
209 | + | |
210 | +/* EEPROM contents */ | |
211 | +struct wd719x_eeprom_header { | |
212 | + u8 sig1; | |
213 | + u8 sig2; | |
214 | + u8 version; | |
215 | + u8 checksum; | |
216 | + u8 cfg_offset; | |
217 | + u8 cfg_size; | |
218 | + u8 setup_offset; | |
219 | + u8 setup_size; | |
220 | +} __packed; | |
221 | + | |
222 | +#define WD719X_EE_SIG1 0 | |
223 | +#define WD719X_EE_SIG2 1 | |
224 | +#define WD719X_EE_VERSION 2 | |
225 | +#define WD719X_EE_CHECKSUM 3 | |
226 | +#define WD719X_EE_CFG_OFFSET 4 | |
227 | +#define WD719X_EE_CFG_SIZE 5 | |
228 | +#define WD719X_EE_SETUP_OFFSET 6 | |
229 | +#define WD719X_EE_SETUP_SIZE 7 | |
230 | + | |
231 | +#define WD719X_EE_SCSI_ID_MASK 0xf | |
232 | + | |
233 | +/* SPIDER Host Parameters Block (=EEPROM configuration block) */ | |
234 | +struct wd719x_host_param { | |
235 | + u8 ch_1_th; /* FIFO threshold */ | |
236 | + u8 scsi_conf; /* SCSI configuration */ | |
237 | + u8 own_scsi_id; /* controller SCSI ID */ | |
238 | + u8 sel_timeout; /* selection timeout*/ | |
239 | + u8 sleep_timer; /* seep timer */ | |
240 | + __le16 cdb_size;/* CDB size groups */ | |
241 | + __le16 tag_en; /* Tag msg enables (ID 0-15) */ | |
242 | + u8 scsi_pad; /* SCSI pad control */ | |
243 | + __le32 wide; /* WIDE msg options (ID 0-15) */ | |
244 | + __le32 sync; /* SYNC msg options (ID 0-15) */ | |
245 | + u8 soft_mask; /* soft error mask */ | |
246 | + u8 unsol_mask; /* unsolicited error mask */ | |
247 | +} __packed; | |
248 | + | |
249 | +#endif /* _WD719X_H_ */ |