162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for Western Digital WD7193, WD7197 and WD7296 SCSI cards 462306a36Sopenharmony_ci * Copyright 2013 Ondrej Zary 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Original driver by 762306a36Sopenharmony_ci * Aaron Dewell <dewell@woods.net> 862306a36Sopenharmony_ci * Gaerti <Juergen.Gaertner@mbox.si.uni-hannover.de> 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * HW documentation available in book: 1162306a36Sopenharmony_ci * 1262306a36Sopenharmony_ci * SPIDER Command Protocol 1362306a36Sopenharmony_ci * by Chandru M. Sippy 1462306a36Sopenharmony_ci * SCSI Storage Products (MCP) 1562306a36Sopenharmony_ci * Western Digital Corporation 1662306a36Sopenharmony_ci * 09-15-95 1762306a36Sopenharmony_ci * 1862306a36Sopenharmony_ci * http://web.archive.org/web/20070717175254/http://sun1.rrzn.uni-hannover.de/gaertner.juergen/wd719x/Linux/Docu/Spider/ 1962306a36Sopenharmony_ci */ 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/* 2262306a36Sopenharmony_ci * Driver workflow: 2362306a36Sopenharmony_ci * 1. SCSI command is transformed to SCB (Spider Control Block) by the 2462306a36Sopenharmony_ci * queuecommand function. 2562306a36Sopenharmony_ci * 2. The address of the SCB is stored in a list to be able to access it, if 2662306a36Sopenharmony_ci * something goes wrong. 2762306a36Sopenharmony_ci * 3. The address of the SCB is written to the Controller, which loads the SCB 2862306a36Sopenharmony_ci * via BM-DMA and processes it. 2962306a36Sopenharmony_ci * 4. After it has finished, it generates an interrupt, and sets registers. 3062306a36Sopenharmony_ci * 3162306a36Sopenharmony_ci * flaws: 3262306a36Sopenharmony_ci * - abort/reset functions 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * ToDo: 3562306a36Sopenharmony_ci * - tagged queueing 3662306a36Sopenharmony_ci */ 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#include <linux/interrupt.h> 3962306a36Sopenharmony_ci#include <linux/module.h> 4062306a36Sopenharmony_ci#include <linux/delay.h> 4162306a36Sopenharmony_ci#include <linux/pci.h> 4262306a36Sopenharmony_ci#include <linux/firmware.h> 4362306a36Sopenharmony_ci#include <linux/eeprom_93cx6.h> 4462306a36Sopenharmony_ci#include <scsi/scsi_cmnd.h> 4562306a36Sopenharmony_ci#include <scsi/scsi_device.h> 4662306a36Sopenharmony_ci#include <scsi/scsi_host.h> 4762306a36Sopenharmony_ci#include "wd719x.h" 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci/* low-level register access */ 5062306a36Sopenharmony_cistatic inline u8 wd719x_readb(struct wd719x *wd, u8 reg) 5162306a36Sopenharmony_ci{ 5262306a36Sopenharmony_ci return ioread8(wd->base + reg); 5362306a36Sopenharmony_ci} 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cistatic inline u32 wd719x_readl(struct wd719x *wd, u8 reg) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci return ioread32(wd->base + reg); 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic inline void wd719x_writeb(struct wd719x *wd, u8 reg, u8 val) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci iowrite8(val, wd->base + reg); 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_cistatic inline void wd719x_writew(struct wd719x *wd, u8 reg, u16 val) 6662306a36Sopenharmony_ci{ 6762306a36Sopenharmony_ci iowrite16(val, wd->base + reg); 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic inline void wd719x_writel(struct wd719x *wd, u8 reg, u32 val) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci iowrite32(val, wd->base + reg); 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci/* wait until the command register is ready */ 7662306a36Sopenharmony_cistatic inline int wd719x_wait_ready(struct wd719x *wd) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci int i = 0; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci do { 8162306a36Sopenharmony_ci if (wd719x_readb(wd, WD719X_AMR_COMMAND) == WD719X_CMD_READY) 8262306a36Sopenharmony_ci return 0; 8362306a36Sopenharmony_ci udelay(1); 8462306a36Sopenharmony_ci } while (i++ < WD719X_WAIT_FOR_CMD_READY); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "command register is not ready: 0x%02x\n", 8762306a36Sopenharmony_ci wd719x_readb(wd, WD719X_AMR_COMMAND)); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci return -ETIMEDOUT; 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci/* poll interrupt status register until command finishes */ 9362306a36Sopenharmony_cistatic inline int wd719x_wait_done(struct wd719x *wd, int timeout) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci u8 status; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci while (timeout > 0) { 9862306a36Sopenharmony_ci status = wd719x_readb(wd, WD719X_AMR_INT_STATUS); 9962306a36Sopenharmony_ci if (status) 10062306a36Sopenharmony_ci break; 10162306a36Sopenharmony_ci timeout--; 10262306a36Sopenharmony_ci udelay(1); 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci if (timeout <= 0) { 10662306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "direct command timed out\n"); 10762306a36Sopenharmony_ci return -ETIMEDOUT; 10862306a36Sopenharmony_ci } 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci if (status != WD719X_INT_NOERRORS) { 11162306a36Sopenharmony_ci u8 sue = wd719x_readb(wd, WD719X_AMR_SCB_ERROR); 11262306a36Sopenharmony_ci /* we get this after wd719x_dev_reset, it's not an error */ 11362306a36Sopenharmony_ci if (sue == WD719X_SUE_TERM) 11462306a36Sopenharmony_ci return 0; 11562306a36Sopenharmony_ci /* we get this after wd719x_bus_reset, it's not an error */ 11662306a36Sopenharmony_ci if (sue == WD719X_SUE_RESET) 11762306a36Sopenharmony_ci return 0; 11862306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "direct command failed, status 0x%02x, SUE 0x%02x\n", 11962306a36Sopenharmony_ci status, sue); 12062306a36Sopenharmony_ci return -EIO; 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci return 0; 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cistatic int wd719x_direct_cmd(struct wd719x *wd, u8 opcode, u8 dev, u8 lun, 12762306a36Sopenharmony_ci u8 tag, dma_addr_t data, int timeout) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci int ret = 0; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci /* clear interrupt status register (allow command register to clear) */ 13262306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_INT_STATUS, WD719X_INT_NONE); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci /* Wait for the Command register to become free */ 13562306a36Sopenharmony_ci if (wd719x_wait_ready(wd)) 13662306a36Sopenharmony_ci return -ETIMEDOUT; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci /* disable interrupts except for RESET/ABORT (it breaks them) */ 13962306a36Sopenharmony_ci if (opcode != WD719X_CMD_BUSRESET && opcode != WD719X_CMD_ABORT && 14062306a36Sopenharmony_ci opcode != WD719X_CMD_ABORT_TAG && opcode != WD719X_CMD_RESET) 14162306a36Sopenharmony_ci dev |= WD719X_DISABLE_INT; 14262306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_CMD_PARAM, dev); 14362306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_CMD_PARAM_2, lun); 14462306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_CMD_PARAM_3, tag); 14562306a36Sopenharmony_ci if (data) 14662306a36Sopenharmony_ci wd719x_writel(wd, WD719X_AMR_SCB_IN, data); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci /* clear interrupt status register again */ 14962306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_INT_STATUS, WD719X_INT_NONE); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci /* Now, write the command */ 15262306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_COMMAND, opcode); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci if (timeout) /* wait for the command to complete */ 15562306a36Sopenharmony_ci ret = wd719x_wait_done(wd, timeout); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci /* clear interrupt status register (clean up) */ 15862306a36Sopenharmony_ci if (opcode != WD719X_CMD_READ_FIRMVER) 15962306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_INT_STATUS, WD719X_INT_NONE); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci return ret; 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic void wd719x_destroy(struct wd719x *wd) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci /* stop the RISC */ 16762306a36Sopenharmony_ci if (wd719x_direct_cmd(wd, WD719X_CMD_SLEEP, 0, 0, 0, 0, 16862306a36Sopenharmony_ci WD719X_WAIT_FOR_RISC)) 16962306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "RISC sleep command failed\n"); 17062306a36Sopenharmony_ci /* disable RISC */ 17162306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_PCI_MODE_SELECT, 0); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci WARN_ON_ONCE(!list_empty(&wd->active_scbs)); 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* free internal buffers */ 17662306a36Sopenharmony_ci dma_free_coherent(&wd->pdev->dev, wd->fw_size, wd->fw_virt, 17762306a36Sopenharmony_ci wd->fw_phys); 17862306a36Sopenharmony_ci wd->fw_virt = NULL; 17962306a36Sopenharmony_ci dma_free_coherent(&wd->pdev->dev, WD719X_HASH_TABLE_SIZE, wd->hash_virt, 18062306a36Sopenharmony_ci wd->hash_phys); 18162306a36Sopenharmony_ci wd->hash_virt = NULL; 18262306a36Sopenharmony_ci dma_free_coherent(&wd->pdev->dev, sizeof(struct wd719x_host_param), 18362306a36Sopenharmony_ci wd->params, wd->params_phys); 18462306a36Sopenharmony_ci wd->params = NULL; 18562306a36Sopenharmony_ci free_irq(wd->pdev->irq, wd); 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci/* finish a SCSI command, unmap buffers */ 18962306a36Sopenharmony_cistatic void wd719x_finish_cmd(struct wd719x_scb *scb, int result) 19062306a36Sopenharmony_ci{ 19162306a36Sopenharmony_ci struct scsi_cmnd *cmd = scb->cmd; 19262306a36Sopenharmony_ci struct wd719x *wd = shost_priv(cmd->device->host); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci list_del(&scb->list); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci dma_unmap_single(&wd->pdev->dev, scb->phys, 19762306a36Sopenharmony_ci sizeof(struct wd719x_scb), DMA_BIDIRECTIONAL); 19862306a36Sopenharmony_ci scsi_dma_unmap(cmd); 19962306a36Sopenharmony_ci dma_unmap_single(&wd->pdev->dev, scb->dma_handle, 20062306a36Sopenharmony_ci SCSI_SENSE_BUFFERSIZE, DMA_FROM_DEVICE); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci cmd->result = result << 16; 20362306a36Sopenharmony_ci scsi_done(cmd); 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci/* Build a SCB and send it to the card */ 20762306a36Sopenharmony_cistatic int wd719x_queuecommand(struct Scsi_Host *sh, struct scsi_cmnd *cmd) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci int i, count_sg; 21062306a36Sopenharmony_ci unsigned long flags; 21162306a36Sopenharmony_ci struct wd719x_scb *scb = scsi_cmd_priv(cmd); 21262306a36Sopenharmony_ci struct wd719x *wd = shost_priv(sh); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci scb->cmd = cmd; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci scb->CDB_tag = 0; /* Tagged queueing not supported yet */ 21762306a36Sopenharmony_ci scb->devid = cmd->device->id; 21862306a36Sopenharmony_ci scb->lun = cmd->device->lun; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci /* copy the command */ 22162306a36Sopenharmony_ci memcpy(scb->CDB, cmd->cmnd, cmd->cmd_len); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci /* map SCB */ 22462306a36Sopenharmony_ci scb->phys = dma_map_single(&wd->pdev->dev, scb, sizeof(*scb), 22562306a36Sopenharmony_ci DMA_BIDIRECTIONAL); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci if (dma_mapping_error(&wd->pdev->dev, scb->phys)) 22862306a36Sopenharmony_ci goto out_error; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci /* map sense buffer */ 23162306a36Sopenharmony_ci scb->sense_buf_length = SCSI_SENSE_BUFFERSIZE; 23262306a36Sopenharmony_ci scb->dma_handle = dma_map_single(&wd->pdev->dev, cmd->sense_buffer, 23362306a36Sopenharmony_ci SCSI_SENSE_BUFFERSIZE, DMA_FROM_DEVICE); 23462306a36Sopenharmony_ci if (dma_mapping_error(&wd->pdev->dev, scb->dma_handle)) 23562306a36Sopenharmony_ci goto out_unmap_scb; 23662306a36Sopenharmony_ci scb->sense_buf = cpu_to_le32(scb->dma_handle); 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci /* request autosense */ 23962306a36Sopenharmony_ci scb->SCB_options |= WD719X_SCB_FLAGS_AUTO_REQUEST_SENSE; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci /* check direction */ 24262306a36Sopenharmony_ci if (cmd->sc_data_direction == DMA_TO_DEVICE) 24362306a36Sopenharmony_ci scb->SCB_options |= WD719X_SCB_FLAGS_CHECK_DIRECTION 24462306a36Sopenharmony_ci | WD719X_SCB_FLAGS_PCI_TO_SCSI; 24562306a36Sopenharmony_ci else if (cmd->sc_data_direction == DMA_FROM_DEVICE) 24662306a36Sopenharmony_ci scb->SCB_options |= WD719X_SCB_FLAGS_CHECK_DIRECTION; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci /* Scather/gather */ 24962306a36Sopenharmony_ci count_sg = scsi_dma_map(cmd); 25062306a36Sopenharmony_ci if (count_sg < 0) 25162306a36Sopenharmony_ci goto out_unmap_sense; 25262306a36Sopenharmony_ci BUG_ON(count_sg > WD719X_SG); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci if (count_sg) { 25562306a36Sopenharmony_ci struct scatterlist *sg; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci scb->data_length = cpu_to_le32(count_sg * 25862306a36Sopenharmony_ci sizeof(struct wd719x_sglist)); 25962306a36Sopenharmony_ci scb->data_p = cpu_to_le32(scb->phys + 26062306a36Sopenharmony_ci offsetof(struct wd719x_scb, sg_list)); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci scsi_for_each_sg(cmd, sg, count_sg, i) { 26362306a36Sopenharmony_ci scb->sg_list[i].ptr = cpu_to_le32(sg_dma_address(sg)); 26462306a36Sopenharmony_ci scb->sg_list[i].length = cpu_to_le32(sg_dma_len(sg)); 26562306a36Sopenharmony_ci } 26662306a36Sopenharmony_ci scb->SCB_options |= WD719X_SCB_FLAGS_DO_SCATTER_GATHER; 26762306a36Sopenharmony_ci } else { /* zero length */ 26862306a36Sopenharmony_ci scb->data_length = 0; 26962306a36Sopenharmony_ci scb->data_p = 0; 27062306a36Sopenharmony_ci } 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci spin_lock_irqsave(wd->sh->host_lock, flags); 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci /* check if the Command register is free */ 27562306a36Sopenharmony_ci if (wd719x_readb(wd, WD719X_AMR_COMMAND) != WD719X_CMD_READY) { 27662306a36Sopenharmony_ci spin_unlock_irqrestore(wd->sh->host_lock, flags); 27762306a36Sopenharmony_ci return SCSI_MLQUEUE_HOST_BUSY; 27862306a36Sopenharmony_ci } 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci list_add(&scb->list, &wd->active_scbs); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci /* write pointer to the AMR */ 28362306a36Sopenharmony_ci wd719x_writel(wd, WD719X_AMR_SCB_IN, scb->phys); 28462306a36Sopenharmony_ci /* send SCB opcode */ 28562306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_COMMAND, WD719X_CMD_PROCESS_SCB); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci spin_unlock_irqrestore(wd->sh->host_lock, flags); 28862306a36Sopenharmony_ci return 0; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ciout_unmap_sense: 29162306a36Sopenharmony_ci dma_unmap_single(&wd->pdev->dev, scb->dma_handle, 29262306a36Sopenharmony_ci SCSI_SENSE_BUFFERSIZE, DMA_FROM_DEVICE); 29362306a36Sopenharmony_ciout_unmap_scb: 29462306a36Sopenharmony_ci dma_unmap_single(&wd->pdev->dev, scb->phys, sizeof(*scb), 29562306a36Sopenharmony_ci DMA_BIDIRECTIONAL); 29662306a36Sopenharmony_ciout_error: 29762306a36Sopenharmony_ci cmd->result = DID_ERROR << 16; 29862306a36Sopenharmony_ci scsi_done(cmd); 29962306a36Sopenharmony_ci return 0; 30062306a36Sopenharmony_ci} 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_cistatic int wd719x_chip_init(struct wd719x *wd) 30362306a36Sopenharmony_ci{ 30462306a36Sopenharmony_ci int i, ret; 30562306a36Sopenharmony_ci u32 risc_init[3]; 30662306a36Sopenharmony_ci const struct firmware *fw_wcs, *fw_risc; 30762306a36Sopenharmony_ci const char fwname_wcs[] = "wd719x-wcs.bin"; 30862306a36Sopenharmony_ci const char fwname_risc[] = "wd719x-risc.bin"; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci memset(wd->hash_virt, 0, WD719X_HASH_TABLE_SIZE); 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci /* WCS (sequencer) firmware */ 31362306a36Sopenharmony_ci ret = request_firmware(&fw_wcs, fwname_wcs, &wd->pdev->dev); 31462306a36Sopenharmony_ci if (ret) { 31562306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "Unable to load firmware %s: %d\n", 31662306a36Sopenharmony_ci fwname_wcs, ret); 31762306a36Sopenharmony_ci return ret; 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci /* RISC firmware */ 32062306a36Sopenharmony_ci ret = request_firmware(&fw_risc, fwname_risc, &wd->pdev->dev); 32162306a36Sopenharmony_ci if (ret) { 32262306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "Unable to load firmware %s: %d\n", 32362306a36Sopenharmony_ci fwname_risc, ret); 32462306a36Sopenharmony_ci release_firmware(fw_wcs); 32562306a36Sopenharmony_ci return ret; 32662306a36Sopenharmony_ci } 32762306a36Sopenharmony_ci wd->fw_size = ALIGN(fw_wcs->size, 4) + fw_risc->size; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci if (!wd->fw_virt) 33062306a36Sopenharmony_ci wd->fw_virt = dma_alloc_coherent(&wd->pdev->dev, wd->fw_size, 33162306a36Sopenharmony_ci &wd->fw_phys, GFP_KERNEL); 33262306a36Sopenharmony_ci if (!wd->fw_virt) { 33362306a36Sopenharmony_ci ret = -ENOMEM; 33462306a36Sopenharmony_ci goto wd719x_init_end; 33562306a36Sopenharmony_ci } 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci /* make a fresh copy of WCS and RISC code */ 33862306a36Sopenharmony_ci memcpy(wd->fw_virt, fw_wcs->data, fw_wcs->size); 33962306a36Sopenharmony_ci memcpy(wd->fw_virt + ALIGN(fw_wcs->size, 4), fw_risc->data, 34062306a36Sopenharmony_ci fw_risc->size); 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci /* Reset the Spider Chip and adapter itself */ 34362306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_PCI_PORT_RESET, WD719X_PCI_RESET); 34462306a36Sopenharmony_ci udelay(WD719X_WAIT_FOR_RISC); 34562306a36Sopenharmony_ci /* Clear PIO mode bits set by BIOS */ 34662306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_CMD_PARAM, 0); 34762306a36Sopenharmony_ci /* ensure RISC is not running */ 34862306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_PCI_MODE_SELECT, 0); 34962306a36Sopenharmony_ci /* ensure command port is ready */ 35062306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_COMMAND, 0); 35162306a36Sopenharmony_ci if (wd719x_wait_ready(wd)) { 35262306a36Sopenharmony_ci ret = -ETIMEDOUT; 35362306a36Sopenharmony_ci goto wd719x_init_end; 35462306a36Sopenharmony_ci } 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci /* Transfer the first 2K words of RISC code to kick start the uP */ 35762306a36Sopenharmony_ci risc_init[0] = wd->fw_phys; /* WCS FW */ 35862306a36Sopenharmony_ci risc_init[1] = wd->fw_phys + ALIGN(fw_wcs->size, 4); /* RISC FW */ 35962306a36Sopenharmony_ci risc_init[2] = wd->hash_phys; /* hash table */ 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci /* clear DMA status */ 36262306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_PCI_CHANNEL2_3STATUS, 0); 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci /* address to read firmware from */ 36562306a36Sopenharmony_ci wd719x_writel(wd, WD719X_PCI_EXTERNAL_ADDR, risc_init[1]); 36662306a36Sopenharmony_ci /* base address to write firmware to (on card) */ 36762306a36Sopenharmony_ci wd719x_writew(wd, WD719X_PCI_INTERNAL_ADDR, WD719X_PRAM_BASE_ADDR); 36862306a36Sopenharmony_ci /* size: first 2K words */ 36962306a36Sopenharmony_ci wd719x_writew(wd, WD719X_PCI_DMA_TRANSFER_SIZE, 2048 * 2); 37062306a36Sopenharmony_ci /* start DMA */ 37162306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_PCI_CHANNEL2_3CMD, WD719X_START_CHANNEL2_3DMA); 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci /* wait for DMA to complete */ 37462306a36Sopenharmony_ci i = WD719X_WAIT_FOR_RISC; 37562306a36Sopenharmony_ci while (i-- > 0) { 37662306a36Sopenharmony_ci u8 status = wd719x_readb(wd, WD719X_PCI_CHANNEL2_3STATUS); 37762306a36Sopenharmony_ci if (status == WD719X_START_CHANNEL2_3DONE) 37862306a36Sopenharmony_ci break; 37962306a36Sopenharmony_ci if (status == WD719X_START_CHANNEL2_3ABORT) { 38062306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "RISC bootstrap failed: DMA aborted\n"); 38162306a36Sopenharmony_ci ret = -EIO; 38262306a36Sopenharmony_ci goto wd719x_init_end; 38362306a36Sopenharmony_ci } 38462306a36Sopenharmony_ci udelay(1); 38562306a36Sopenharmony_ci } 38662306a36Sopenharmony_ci if (i < 1) { 38762306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "RISC bootstrap failed: DMA timeout\n"); 38862306a36Sopenharmony_ci ret = -ETIMEDOUT; 38962306a36Sopenharmony_ci goto wd719x_init_end; 39062306a36Sopenharmony_ci } 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci /* firmware is loaded, now initialize and wake up the RISC */ 39362306a36Sopenharmony_ci /* write RISC initialization long words to Spider */ 39462306a36Sopenharmony_ci wd719x_writel(wd, WD719X_AMR_SCB_IN, risc_init[0]); 39562306a36Sopenharmony_ci wd719x_writel(wd, WD719X_AMR_SCB_IN + 4, risc_init[1]); 39662306a36Sopenharmony_ci wd719x_writel(wd, WD719X_AMR_SCB_IN + 8, risc_init[2]); 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci /* disable interrupts during initialization of RISC */ 39962306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_CMD_PARAM, WD719X_DISABLE_INT); 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci /* issue INITIALIZE RISC comand */ 40262306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_COMMAND, WD719X_CMD_INIT_RISC); 40362306a36Sopenharmony_ci /* enable advanced mode (wake up RISC) */ 40462306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_PCI_MODE_SELECT, WD719X_ENABLE_ADVANCE_MODE); 40562306a36Sopenharmony_ci udelay(WD719X_WAIT_FOR_RISC); 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci ret = wd719x_wait_done(wd, WD719X_WAIT_FOR_RISC); 40862306a36Sopenharmony_ci /* clear interrupt status register */ 40962306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_INT_STATUS, WD719X_INT_NONE); 41062306a36Sopenharmony_ci if (ret) { 41162306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "Unable to initialize RISC\n"); 41262306a36Sopenharmony_ci goto wd719x_init_end; 41362306a36Sopenharmony_ci } 41462306a36Sopenharmony_ci /* RISC is up and running */ 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci /* Read FW version from RISC */ 41762306a36Sopenharmony_ci ret = wd719x_direct_cmd(wd, WD719X_CMD_READ_FIRMVER, 0, 0, 0, 0, 41862306a36Sopenharmony_ci WD719X_WAIT_FOR_RISC); 41962306a36Sopenharmony_ci if (ret) { 42062306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "Unable to read firmware version\n"); 42162306a36Sopenharmony_ci goto wd719x_init_end; 42262306a36Sopenharmony_ci } 42362306a36Sopenharmony_ci dev_info(&wd->pdev->dev, "RISC initialized with firmware version %.2x.%.2x\n", 42462306a36Sopenharmony_ci wd719x_readb(wd, WD719X_AMR_SCB_OUT + 1), 42562306a36Sopenharmony_ci wd719x_readb(wd, WD719X_AMR_SCB_OUT)); 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci /* RESET SCSI bus */ 42862306a36Sopenharmony_ci ret = wd719x_direct_cmd(wd, WD719X_CMD_BUSRESET, 0, 0, 0, 0, 42962306a36Sopenharmony_ci WD719X_WAIT_FOR_SCSI_RESET); 43062306a36Sopenharmony_ci if (ret) { 43162306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "SCSI bus reset failed\n"); 43262306a36Sopenharmony_ci goto wd719x_init_end; 43362306a36Sopenharmony_ci } 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci /* use HostParameter structure to set Spider's Host Parameter Block */ 43662306a36Sopenharmony_ci ret = wd719x_direct_cmd(wd, WD719X_CMD_SET_PARAM, 0, 43762306a36Sopenharmony_ci sizeof(struct wd719x_host_param), 0, 43862306a36Sopenharmony_ci wd->params_phys, WD719X_WAIT_FOR_RISC); 43962306a36Sopenharmony_ci if (ret) { 44062306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "Failed to set HOST PARAMETERS\n"); 44162306a36Sopenharmony_ci goto wd719x_init_end; 44262306a36Sopenharmony_ci } 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci /* initiate SCAM (does nothing if disabled in BIOS) */ 44562306a36Sopenharmony_ci /* bug?: we should pass a mask of static IDs which we don't have */ 44662306a36Sopenharmony_ci ret = wd719x_direct_cmd(wd, WD719X_CMD_INIT_SCAM, 0, 0, 0, 0, 44762306a36Sopenharmony_ci WD719X_WAIT_FOR_SCSI_RESET); 44862306a36Sopenharmony_ci if (ret) { 44962306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "SCAM initialization failed\n"); 45062306a36Sopenharmony_ci goto wd719x_init_end; 45162306a36Sopenharmony_ci } 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci /* clear AMR_BIOS_SHARE_INT register */ 45462306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_BIOS_SHARE_INT, 0); 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ciwd719x_init_end: 45762306a36Sopenharmony_ci release_firmware(fw_wcs); 45862306a36Sopenharmony_ci release_firmware(fw_risc); 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci return ret; 46162306a36Sopenharmony_ci} 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_cistatic int wd719x_abort(struct scsi_cmnd *cmd) 46462306a36Sopenharmony_ci{ 46562306a36Sopenharmony_ci int action, result; 46662306a36Sopenharmony_ci unsigned long flags; 46762306a36Sopenharmony_ci struct wd719x_scb *scb = scsi_cmd_priv(cmd); 46862306a36Sopenharmony_ci struct wd719x *wd = shost_priv(cmd->device->host); 46962306a36Sopenharmony_ci struct device *dev = &wd->pdev->dev; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci dev_info(dev, "abort command, tag: %x\n", scsi_cmd_to_rq(cmd)->tag); 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci action = WD719X_CMD_ABORT; 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci spin_lock_irqsave(wd->sh->host_lock, flags); 47662306a36Sopenharmony_ci result = wd719x_direct_cmd(wd, action, cmd->device->id, 47762306a36Sopenharmony_ci cmd->device->lun, scsi_cmd_to_rq(cmd)->tag, 47862306a36Sopenharmony_ci scb->phys, 0); 47962306a36Sopenharmony_ci wd719x_finish_cmd(scb, DID_ABORT); 48062306a36Sopenharmony_ci spin_unlock_irqrestore(wd->sh->host_lock, flags); 48162306a36Sopenharmony_ci if (result) 48262306a36Sopenharmony_ci return FAILED; 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci return SUCCESS; 48562306a36Sopenharmony_ci} 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_cistatic int wd719x_reset(struct scsi_cmnd *cmd, u8 opcode, u8 device) 48862306a36Sopenharmony_ci{ 48962306a36Sopenharmony_ci int result; 49062306a36Sopenharmony_ci unsigned long flags; 49162306a36Sopenharmony_ci struct wd719x *wd = shost_priv(cmd->device->host); 49262306a36Sopenharmony_ci struct wd719x_scb *scb, *tmp; 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci dev_info(&wd->pdev->dev, "%s reset requested\n", 49562306a36Sopenharmony_ci (opcode == WD719X_CMD_BUSRESET) ? "bus" : "device"); 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci spin_lock_irqsave(wd->sh->host_lock, flags); 49862306a36Sopenharmony_ci result = wd719x_direct_cmd(wd, opcode, device, 0, 0, 0, 49962306a36Sopenharmony_ci WD719X_WAIT_FOR_SCSI_RESET); 50062306a36Sopenharmony_ci /* flush all SCBs (or all for a device if dev_reset) */ 50162306a36Sopenharmony_ci list_for_each_entry_safe(scb, tmp, &wd->active_scbs, list) { 50262306a36Sopenharmony_ci if (opcode == WD719X_CMD_BUSRESET || 50362306a36Sopenharmony_ci scb->cmd->device->id == device) 50462306a36Sopenharmony_ci wd719x_finish_cmd(scb, DID_RESET); 50562306a36Sopenharmony_ci } 50662306a36Sopenharmony_ci spin_unlock_irqrestore(wd->sh->host_lock, flags); 50762306a36Sopenharmony_ci if (result) 50862306a36Sopenharmony_ci return FAILED; 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci return SUCCESS; 51162306a36Sopenharmony_ci} 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_cistatic int wd719x_dev_reset(struct scsi_cmnd *cmd) 51462306a36Sopenharmony_ci{ 51562306a36Sopenharmony_ci return wd719x_reset(cmd, WD719X_CMD_RESET, cmd->device->id); 51662306a36Sopenharmony_ci} 51762306a36Sopenharmony_ci 51862306a36Sopenharmony_cistatic int wd719x_bus_reset(struct scsi_cmnd *cmd) 51962306a36Sopenharmony_ci{ 52062306a36Sopenharmony_ci return wd719x_reset(cmd, WD719X_CMD_BUSRESET, 0); 52162306a36Sopenharmony_ci} 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_cistatic int wd719x_host_reset(struct scsi_cmnd *cmd) 52462306a36Sopenharmony_ci{ 52562306a36Sopenharmony_ci struct wd719x *wd = shost_priv(cmd->device->host); 52662306a36Sopenharmony_ci struct wd719x_scb *scb, *tmp; 52762306a36Sopenharmony_ci unsigned long flags; 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci dev_info(&wd->pdev->dev, "host reset requested\n"); 53062306a36Sopenharmony_ci spin_lock_irqsave(wd->sh->host_lock, flags); 53162306a36Sopenharmony_ci /* stop the RISC */ 53262306a36Sopenharmony_ci if (wd719x_direct_cmd(wd, WD719X_CMD_SLEEP, 0, 0, 0, 0, 53362306a36Sopenharmony_ci WD719X_WAIT_FOR_RISC)) 53462306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "RISC sleep command failed\n"); 53562306a36Sopenharmony_ci /* disable RISC */ 53662306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_PCI_MODE_SELECT, 0); 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci /* flush all SCBs */ 53962306a36Sopenharmony_ci list_for_each_entry_safe(scb, tmp, &wd->active_scbs, list) 54062306a36Sopenharmony_ci wd719x_finish_cmd(scb, DID_RESET); 54162306a36Sopenharmony_ci spin_unlock_irqrestore(wd->sh->host_lock, flags); 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci /* Try to reinit the RISC */ 54462306a36Sopenharmony_ci return wd719x_chip_init(wd) == 0 ? SUCCESS : FAILED; 54562306a36Sopenharmony_ci} 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_cistatic int wd719x_biosparam(struct scsi_device *sdev, struct block_device *bdev, 54862306a36Sopenharmony_ci sector_t capacity, int geom[]) 54962306a36Sopenharmony_ci{ 55062306a36Sopenharmony_ci if (capacity >= 0x200000) { 55162306a36Sopenharmony_ci geom[0] = 255; /* heads */ 55262306a36Sopenharmony_ci geom[1] = 63; /* sectors */ 55362306a36Sopenharmony_ci } else { 55462306a36Sopenharmony_ci geom[0] = 64; /* heads */ 55562306a36Sopenharmony_ci geom[1] = 32; /* sectors */ 55662306a36Sopenharmony_ci } 55762306a36Sopenharmony_ci geom[2] = sector_div(capacity, geom[0] * geom[1]); /* cylinders */ 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci return 0; 56062306a36Sopenharmony_ci} 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci/* process a SCB-completion interrupt */ 56362306a36Sopenharmony_cistatic inline void wd719x_interrupt_SCB(struct wd719x *wd, 56462306a36Sopenharmony_ci union wd719x_regs regs, 56562306a36Sopenharmony_ci struct wd719x_scb *scb) 56662306a36Sopenharmony_ci{ 56762306a36Sopenharmony_ci int result; 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci /* now have to find result from card */ 57062306a36Sopenharmony_ci switch (regs.bytes.SUE) { 57162306a36Sopenharmony_ci case WD719X_SUE_NOERRORS: 57262306a36Sopenharmony_ci result = DID_OK; 57362306a36Sopenharmony_ci break; 57462306a36Sopenharmony_ci case WD719X_SUE_REJECTED: 57562306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "command rejected\n"); 57662306a36Sopenharmony_ci result = DID_ERROR; 57762306a36Sopenharmony_ci break; 57862306a36Sopenharmony_ci case WD719X_SUE_SCBQFULL: 57962306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "SCB queue is full\n"); 58062306a36Sopenharmony_ci result = DID_ERROR; 58162306a36Sopenharmony_ci break; 58262306a36Sopenharmony_ci case WD719X_SUE_TERM: 58362306a36Sopenharmony_ci dev_dbg(&wd->pdev->dev, "SCB terminated by direct command\n"); 58462306a36Sopenharmony_ci result = DID_ABORT; /* or DID_RESET? */ 58562306a36Sopenharmony_ci break; 58662306a36Sopenharmony_ci case WD719X_SUE_CHAN1ABORT: 58762306a36Sopenharmony_ci case WD719X_SUE_CHAN23ABORT: 58862306a36Sopenharmony_ci result = DID_ABORT; 58962306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "DMA abort\n"); 59062306a36Sopenharmony_ci break; 59162306a36Sopenharmony_ci case WD719X_SUE_CHAN1PAR: 59262306a36Sopenharmony_ci case WD719X_SUE_CHAN23PAR: 59362306a36Sopenharmony_ci result = DID_PARITY; 59462306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "DMA parity error\n"); 59562306a36Sopenharmony_ci break; 59662306a36Sopenharmony_ci case WD719X_SUE_TIMEOUT: 59762306a36Sopenharmony_ci result = DID_TIME_OUT; 59862306a36Sopenharmony_ci dev_dbg(&wd->pdev->dev, "selection timeout\n"); 59962306a36Sopenharmony_ci break; 60062306a36Sopenharmony_ci case WD719X_SUE_RESET: 60162306a36Sopenharmony_ci dev_dbg(&wd->pdev->dev, "bus reset occurred\n"); 60262306a36Sopenharmony_ci result = DID_RESET; 60362306a36Sopenharmony_ci break; 60462306a36Sopenharmony_ci case WD719X_SUE_BUSERROR: 60562306a36Sopenharmony_ci dev_dbg(&wd->pdev->dev, "SCSI bus error\n"); 60662306a36Sopenharmony_ci result = DID_ERROR; 60762306a36Sopenharmony_ci break; 60862306a36Sopenharmony_ci case WD719X_SUE_WRONGWAY: 60962306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "wrong data transfer direction\n"); 61062306a36Sopenharmony_ci result = DID_ERROR; 61162306a36Sopenharmony_ci break; 61262306a36Sopenharmony_ci case WD719X_SUE_BADPHASE: 61362306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "invalid SCSI phase\n"); 61462306a36Sopenharmony_ci result = DID_ERROR; 61562306a36Sopenharmony_ci break; 61662306a36Sopenharmony_ci case WD719X_SUE_TOOLONG: 61762306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "record too long\n"); 61862306a36Sopenharmony_ci result = DID_ERROR; 61962306a36Sopenharmony_ci break; 62062306a36Sopenharmony_ci case WD719X_SUE_BUSFREE: 62162306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "unexpected bus free\n"); 62262306a36Sopenharmony_ci result = DID_NO_CONNECT; /* or DID_ERROR ???*/ 62362306a36Sopenharmony_ci break; 62462306a36Sopenharmony_ci case WD719X_SUE_ARSDONE: 62562306a36Sopenharmony_ci dev_dbg(&wd->pdev->dev, "auto request sense\n"); 62662306a36Sopenharmony_ci if (regs.bytes.SCSI == 0) 62762306a36Sopenharmony_ci result = DID_OK; 62862306a36Sopenharmony_ci else 62962306a36Sopenharmony_ci result = DID_PARITY; 63062306a36Sopenharmony_ci break; 63162306a36Sopenharmony_ci case WD719X_SUE_IGNORED: 63262306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "target id %d ignored command\n", 63362306a36Sopenharmony_ci scb->cmd->device->id); 63462306a36Sopenharmony_ci result = DID_NO_CONNECT; 63562306a36Sopenharmony_ci break; 63662306a36Sopenharmony_ci case WD719X_SUE_WRONGTAGS: 63762306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "reversed tags\n"); 63862306a36Sopenharmony_ci result = DID_ERROR; 63962306a36Sopenharmony_ci break; 64062306a36Sopenharmony_ci case WD719X_SUE_BADTAGS: 64162306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "tag type not supported by target\n"); 64262306a36Sopenharmony_ci result = DID_ERROR; 64362306a36Sopenharmony_ci break; 64462306a36Sopenharmony_ci case WD719X_SUE_NOSCAMID: 64562306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "no SCAM soft ID available\n"); 64662306a36Sopenharmony_ci result = DID_ERROR; 64762306a36Sopenharmony_ci break; 64862306a36Sopenharmony_ci default: 64962306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "unknown SUE error code: 0x%x\n", 65062306a36Sopenharmony_ci regs.bytes.SUE); 65162306a36Sopenharmony_ci result = DID_ERROR; 65262306a36Sopenharmony_ci break; 65362306a36Sopenharmony_ci } 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci wd719x_finish_cmd(scb, result); 65662306a36Sopenharmony_ci} 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_cistatic irqreturn_t wd719x_interrupt(int irq, void *dev_id) 65962306a36Sopenharmony_ci{ 66062306a36Sopenharmony_ci struct wd719x *wd = dev_id; 66162306a36Sopenharmony_ci union wd719x_regs regs; 66262306a36Sopenharmony_ci unsigned long flags; 66362306a36Sopenharmony_ci u32 SCB_out; 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci spin_lock_irqsave(wd->sh->host_lock, flags); 66662306a36Sopenharmony_ci /* read SCB pointer back from card */ 66762306a36Sopenharmony_ci SCB_out = wd719x_readl(wd, WD719X_AMR_SCB_OUT); 66862306a36Sopenharmony_ci /* read all status info at once */ 66962306a36Sopenharmony_ci regs.all = cpu_to_le32(wd719x_readl(wd, WD719X_AMR_OP_CODE)); 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci switch (regs.bytes.INT) { 67262306a36Sopenharmony_ci case WD719X_INT_NONE: 67362306a36Sopenharmony_ci spin_unlock_irqrestore(wd->sh->host_lock, flags); 67462306a36Sopenharmony_ci return IRQ_NONE; 67562306a36Sopenharmony_ci case WD719X_INT_LINKNOSTATUS: 67662306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "linked command completed with no status\n"); 67762306a36Sopenharmony_ci break; 67862306a36Sopenharmony_ci case WD719X_INT_BADINT: 67962306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "unsolicited interrupt\n"); 68062306a36Sopenharmony_ci break; 68162306a36Sopenharmony_ci case WD719X_INT_NOERRORS: 68262306a36Sopenharmony_ci case WD719X_INT_LINKNOERRORS: 68362306a36Sopenharmony_ci case WD719X_INT_ERRORSLOGGED: 68462306a36Sopenharmony_ci case WD719X_INT_SPIDERFAILED: 68562306a36Sopenharmony_ci /* was the cmd completed a direct or SCB command? */ 68662306a36Sopenharmony_ci if (regs.bytes.OPC == WD719X_CMD_PROCESS_SCB) { 68762306a36Sopenharmony_ci struct wd719x_scb *scb; 68862306a36Sopenharmony_ci list_for_each_entry(scb, &wd->active_scbs, list) 68962306a36Sopenharmony_ci if (SCB_out == scb->phys) 69062306a36Sopenharmony_ci break; 69162306a36Sopenharmony_ci if (SCB_out == scb->phys) 69262306a36Sopenharmony_ci wd719x_interrupt_SCB(wd, regs, scb); 69362306a36Sopenharmony_ci else 69462306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "card returned invalid SCB pointer\n"); 69562306a36Sopenharmony_ci } else 69662306a36Sopenharmony_ci dev_dbg(&wd->pdev->dev, "direct command 0x%x completed\n", 69762306a36Sopenharmony_ci regs.bytes.OPC); 69862306a36Sopenharmony_ci break; 69962306a36Sopenharmony_ci case WD719X_INT_PIOREADY: 70062306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "card indicates PIO data ready but we never use PIO\n"); 70162306a36Sopenharmony_ci /* interrupt will not be cleared until all data is read */ 70262306a36Sopenharmony_ci break; 70362306a36Sopenharmony_ci default: 70462306a36Sopenharmony_ci dev_err(&wd->pdev->dev, "unknown interrupt reason: %d\n", 70562306a36Sopenharmony_ci regs.bytes.INT); 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_ci } 70862306a36Sopenharmony_ci /* clear interrupt so another can happen */ 70962306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_AMR_INT_STATUS, WD719X_INT_NONE); 71062306a36Sopenharmony_ci spin_unlock_irqrestore(wd->sh->host_lock, flags); 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci return IRQ_HANDLED; 71362306a36Sopenharmony_ci} 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_cistatic void wd719x_eeprom_reg_read(struct eeprom_93cx6 *eeprom) 71662306a36Sopenharmony_ci{ 71762306a36Sopenharmony_ci struct wd719x *wd = eeprom->data; 71862306a36Sopenharmony_ci u8 reg = wd719x_readb(wd, WD719X_PCI_GPIO_DATA); 71962306a36Sopenharmony_ci 72062306a36Sopenharmony_ci eeprom->reg_data_out = reg & WD719X_EE_DO; 72162306a36Sopenharmony_ci} 72262306a36Sopenharmony_ci 72362306a36Sopenharmony_cistatic void wd719x_eeprom_reg_write(struct eeprom_93cx6 *eeprom) 72462306a36Sopenharmony_ci{ 72562306a36Sopenharmony_ci struct wd719x *wd = eeprom->data; 72662306a36Sopenharmony_ci u8 reg = 0; 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci if (eeprom->reg_data_in) 72962306a36Sopenharmony_ci reg |= WD719X_EE_DI; 73062306a36Sopenharmony_ci if (eeprom->reg_data_clock) 73162306a36Sopenharmony_ci reg |= WD719X_EE_CLK; 73262306a36Sopenharmony_ci if (eeprom->reg_chip_select) 73362306a36Sopenharmony_ci reg |= WD719X_EE_CS; 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_PCI_GPIO_DATA, reg); 73662306a36Sopenharmony_ci} 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci/* read config from EEPROM so it can be downloaded by the RISC on (re-)init */ 73962306a36Sopenharmony_cistatic void wd719x_read_eeprom(struct wd719x *wd) 74062306a36Sopenharmony_ci{ 74162306a36Sopenharmony_ci struct eeprom_93cx6 eeprom; 74262306a36Sopenharmony_ci u8 gpio; 74362306a36Sopenharmony_ci struct wd719x_eeprom_header header; 74462306a36Sopenharmony_ci 74562306a36Sopenharmony_ci eeprom.data = wd; 74662306a36Sopenharmony_ci eeprom.register_read = wd719x_eeprom_reg_read; 74762306a36Sopenharmony_ci eeprom.register_write = wd719x_eeprom_reg_write; 74862306a36Sopenharmony_ci eeprom.width = PCI_EEPROM_WIDTH_93C46; 74962306a36Sopenharmony_ci 75062306a36Sopenharmony_ci /* set all outputs to low */ 75162306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_PCI_GPIO_DATA, 0); 75262306a36Sopenharmony_ci /* configure GPIO pins */ 75362306a36Sopenharmony_ci gpio = wd719x_readb(wd, WD719X_PCI_GPIO_CONTROL); 75462306a36Sopenharmony_ci /* GPIO outputs */ 75562306a36Sopenharmony_ci gpio &= (~(WD719X_EE_CLK | WD719X_EE_DI | WD719X_EE_CS)); 75662306a36Sopenharmony_ci /* GPIO input */ 75762306a36Sopenharmony_ci gpio |= WD719X_EE_DO; 75862306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_PCI_GPIO_CONTROL, gpio); 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_ci /* read EEPROM header */ 76162306a36Sopenharmony_ci eeprom_93cx6_multireadb(&eeprom, 0, (u8 *)&header, sizeof(header)); 76262306a36Sopenharmony_ci 76362306a36Sopenharmony_ci if (header.sig1 == 'W' && header.sig2 == 'D') 76462306a36Sopenharmony_ci eeprom_93cx6_multireadb(&eeprom, header.cfg_offset, 76562306a36Sopenharmony_ci (u8 *)wd->params, 76662306a36Sopenharmony_ci sizeof(struct wd719x_host_param)); 76762306a36Sopenharmony_ci else { /* default EEPROM values */ 76862306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "EEPROM signature is invalid (0x%02x 0x%02x), using default values\n", 76962306a36Sopenharmony_ci header.sig1, header.sig2); 77062306a36Sopenharmony_ci wd->params->ch_1_th = 0x10; /* 16 DWs = 64 B */ 77162306a36Sopenharmony_ci wd->params->scsi_conf = 0x4c; /* 48ma, spue, parity check */ 77262306a36Sopenharmony_ci wd->params->own_scsi_id = 0x07; /* ID 7, SCAM disabled */ 77362306a36Sopenharmony_ci wd->params->sel_timeout = 0x4d; /* 250 ms */ 77462306a36Sopenharmony_ci wd->params->sleep_timer = 0x01; 77562306a36Sopenharmony_ci wd->params->cdb_size = cpu_to_le16(0x5555); /* all 6 B */ 77662306a36Sopenharmony_ci wd->params->scsi_pad = 0x1b; 77762306a36Sopenharmony_ci if (wd->type == WD719X_TYPE_7193) /* narrow card - disable */ 77862306a36Sopenharmony_ci wd->params->wide = cpu_to_le32(0x00000000); 77962306a36Sopenharmony_ci else /* initiate & respond to WIDE messages */ 78062306a36Sopenharmony_ci wd->params->wide = cpu_to_le32(0xffffffff); 78162306a36Sopenharmony_ci wd->params->sync = cpu_to_le32(0xffffffff); 78262306a36Sopenharmony_ci wd->params->soft_mask = 0x00; /* all disabled */ 78362306a36Sopenharmony_ci wd->params->unsol_mask = 0x00; /* all disabled */ 78462306a36Sopenharmony_ci } 78562306a36Sopenharmony_ci /* disable TAGGED messages */ 78662306a36Sopenharmony_ci wd->params->tag_en = cpu_to_le16(0x0000); 78762306a36Sopenharmony_ci} 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_ci/* Read card type from GPIO bits 1 and 3 */ 79062306a36Sopenharmony_cistatic enum wd719x_card_type wd719x_detect_type(struct wd719x *wd) 79162306a36Sopenharmony_ci{ 79262306a36Sopenharmony_ci u8 card = wd719x_readb(wd, WD719X_PCI_GPIO_CONTROL); 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci card |= WD719X_GPIO_ID_BITS; 79562306a36Sopenharmony_ci wd719x_writeb(wd, WD719X_PCI_GPIO_CONTROL, card); 79662306a36Sopenharmony_ci card = wd719x_readb(wd, WD719X_PCI_GPIO_DATA) & WD719X_GPIO_ID_BITS; 79762306a36Sopenharmony_ci switch (card) { 79862306a36Sopenharmony_ci case 0x08: 79962306a36Sopenharmony_ci return WD719X_TYPE_7193; 80062306a36Sopenharmony_ci case 0x02: 80162306a36Sopenharmony_ci return WD719X_TYPE_7197; 80262306a36Sopenharmony_ci case 0x00: 80362306a36Sopenharmony_ci return WD719X_TYPE_7296; 80462306a36Sopenharmony_ci default: 80562306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "unknown card type 0x%x\n", card); 80662306a36Sopenharmony_ci return WD719X_TYPE_UNKNOWN; 80762306a36Sopenharmony_ci } 80862306a36Sopenharmony_ci} 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_cistatic int wd719x_board_found(struct Scsi_Host *sh) 81162306a36Sopenharmony_ci{ 81262306a36Sopenharmony_ci struct wd719x *wd = shost_priv(sh); 81362306a36Sopenharmony_ci static const char * const card_types[] = { 81462306a36Sopenharmony_ci "Unknown card", "WD7193", "WD7197", "WD7296" 81562306a36Sopenharmony_ci }; 81662306a36Sopenharmony_ci int ret; 81762306a36Sopenharmony_ci 81862306a36Sopenharmony_ci INIT_LIST_HEAD(&wd->active_scbs); 81962306a36Sopenharmony_ci 82062306a36Sopenharmony_ci sh->base = pci_resource_start(wd->pdev, 0); 82162306a36Sopenharmony_ci 82262306a36Sopenharmony_ci wd->type = wd719x_detect_type(wd); 82362306a36Sopenharmony_ci 82462306a36Sopenharmony_ci wd->sh = sh; 82562306a36Sopenharmony_ci sh->irq = wd->pdev->irq; 82662306a36Sopenharmony_ci wd->fw_virt = NULL; 82762306a36Sopenharmony_ci 82862306a36Sopenharmony_ci /* memory area for host (EEPROM) parameters */ 82962306a36Sopenharmony_ci wd->params = dma_alloc_coherent(&wd->pdev->dev, 83062306a36Sopenharmony_ci sizeof(struct wd719x_host_param), 83162306a36Sopenharmony_ci &wd->params_phys, GFP_KERNEL); 83262306a36Sopenharmony_ci if (!wd->params) { 83362306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "unable to allocate parameter buffer\n"); 83462306a36Sopenharmony_ci return -ENOMEM; 83562306a36Sopenharmony_ci } 83662306a36Sopenharmony_ci 83762306a36Sopenharmony_ci /* memory area for the RISC for hash table of outstanding requests */ 83862306a36Sopenharmony_ci wd->hash_virt = dma_alloc_coherent(&wd->pdev->dev, 83962306a36Sopenharmony_ci WD719X_HASH_TABLE_SIZE, 84062306a36Sopenharmony_ci &wd->hash_phys, GFP_KERNEL); 84162306a36Sopenharmony_ci if (!wd->hash_virt) { 84262306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "unable to allocate hash buffer\n"); 84362306a36Sopenharmony_ci ret = -ENOMEM; 84462306a36Sopenharmony_ci goto fail_free_params; 84562306a36Sopenharmony_ci } 84662306a36Sopenharmony_ci 84762306a36Sopenharmony_ci ret = request_irq(wd->pdev->irq, wd719x_interrupt, IRQF_SHARED, 84862306a36Sopenharmony_ci "wd719x", wd); 84962306a36Sopenharmony_ci if (ret) { 85062306a36Sopenharmony_ci dev_warn(&wd->pdev->dev, "unable to assign IRQ %d\n", 85162306a36Sopenharmony_ci wd->pdev->irq); 85262306a36Sopenharmony_ci goto fail_free_hash; 85362306a36Sopenharmony_ci } 85462306a36Sopenharmony_ci 85562306a36Sopenharmony_ci /* read parameters from EEPROM */ 85662306a36Sopenharmony_ci wd719x_read_eeprom(wd); 85762306a36Sopenharmony_ci 85862306a36Sopenharmony_ci ret = wd719x_chip_init(wd); 85962306a36Sopenharmony_ci if (ret) 86062306a36Sopenharmony_ci goto fail_free_irq; 86162306a36Sopenharmony_ci 86262306a36Sopenharmony_ci sh->this_id = wd->params->own_scsi_id & WD719X_EE_SCSI_ID_MASK; 86362306a36Sopenharmony_ci 86462306a36Sopenharmony_ci dev_info(&wd->pdev->dev, "%s at I/O 0x%lx, IRQ %u, SCSI ID %d\n", 86562306a36Sopenharmony_ci card_types[wd->type], sh->base, sh->irq, sh->this_id); 86662306a36Sopenharmony_ci 86762306a36Sopenharmony_ci return 0; 86862306a36Sopenharmony_ci 86962306a36Sopenharmony_cifail_free_irq: 87062306a36Sopenharmony_ci free_irq(wd->pdev->irq, wd); 87162306a36Sopenharmony_cifail_free_hash: 87262306a36Sopenharmony_ci dma_free_coherent(&wd->pdev->dev, WD719X_HASH_TABLE_SIZE, wd->hash_virt, 87362306a36Sopenharmony_ci wd->hash_phys); 87462306a36Sopenharmony_cifail_free_params: 87562306a36Sopenharmony_ci dma_free_coherent(&wd->pdev->dev, sizeof(struct wd719x_host_param), 87662306a36Sopenharmony_ci wd->params, wd->params_phys); 87762306a36Sopenharmony_ci 87862306a36Sopenharmony_ci return ret; 87962306a36Sopenharmony_ci} 88062306a36Sopenharmony_ci 88162306a36Sopenharmony_cistatic const struct scsi_host_template wd719x_template = { 88262306a36Sopenharmony_ci .module = THIS_MODULE, 88362306a36Sopenharmony_ci .name = "Western Digital 719x", 88462306a36Sopenharmony_ci .cmd_size = sizeof(struct wd719x_scb), 88562306a36Sopenharmony_ci .queuecommand = wd719x_queuecommand, 88662306a36Sopenharmony_ci .eh_abort_handler = wd719x_abort, 88762306a36Sopenharmony_ci .eh_device_reset_handler = wd719x_dev_reset, 88862306a36Sopenharmony_ci .eh_bus_reset_handler = wd719x_bus_reset, 88962306a36Sopenharmony_ci .eh_host_reset_handler = wd719x_host_reset, 89062306a36Sopenharmony_ci .bios_param = wd719x_biosparam, 89162306a36Sopenharmony_ci .proc_name = "wd719x", 89262306a36Sopenharmony_ci .can_queue = 255, 89362306a36Sopenharmony_ci .this_id = 7, 89462306a36Sopenharmony_ci .sg_tablesize = WD719X_SG, 89562306a36Sopenharmony_ci}; 89662306a36Sopenharmony_ci 89762306a36Sopenharmony_cistatic int wd719x_pci_probe(struct pci_dev *pdev, const struct pci_device_id *d) 89862306a36Sopenharmony_ci{ 89962306a36Sopenharmony_ci int err; 90062306a36Sopenharmony_ci struct Scsi_Host *sh; 90162306a36Sopenharmony_ci struct wd719x *wd; 90262306a36Sopenharmony_ci 90362306a36Sopenharmony_ci err = pci_enable_device(pdev); 90462306a36Sopenharmony_ci if (err) 90562306a36Sopenharmony_ci goto fail; 90662306a36Sopenharmony_ci 90762306a36Sopenharmony_ci err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); 90862306a36Sopenharmony_ci if (err) { 90962306a36Sopenharmony_ci dev_warn(&pdev->dev, "Unable to set 32-bit DMA mask\n"); 91062306a36Sopenharmony_ci goto disable_device; 91162306a36Sopenharmony_ci } 91262306a36Sopenharmony_ci 91362306a36Sopenharmony_ci err = pci_request_regions(pdev, "wd719x"); 91462306a36Sopenharmony_ci if (err) 91562306a36Sopenharmony_ci goto disable_device; 91662306a36Sopenharmony_ci pci_set_master(pdev); 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_ci err = -ENODEV; 91962306a36Sopenharmony_ci if (pci_resource_len(pdev, 0) == 0) 92062306a36Sopenharmony_ci goto release_region; 92162306a36Sopenharmony_ci 92262306a36Sopenharmony_ci err = -ENOMEM; 92362306a36Sopenharmony_ci sh = scsi_host_alloc(&wd719x_template, sizeof(struct wd719x)); 92462306a36Sopenharmony_ci if (!sh) 92562306a36Sopenharmony_ci goto release_region; 92662306a36Sopenharmony_ci 92762306a36Sopenharmony_ci wd = shost_priv(sh); 92862306a36Sopenharmony_ci wd->base = pci_iomap(pdev, 0, 0); 92962306a36Sopenharmony_ci if (!wd->base) 93062306a36Sopenharmony_ci goto free_host; 93162306a36Sopenharmony_ci wd->pdev = pdev; 93262306a36Sopenharmony_ci 93362306a36Sopenharmony_ci err = wd719x_board_found(sh); 93462306a36Sopenharmony_ci if (err) 93562306a36Sopenharmony_ci goto unmap; 93662306a36Sopenharmony_ci 93762306a36Sopenharmony_ci err = scsi_add_host(sh, &wd->pdev->dev); 93862306a36Sopenharmony_ci if (err) 93962306a36Sopenharmony_ci goto destroy; 94062306a36Sopenharmony_ci 94162306a36Sopenharmony_ci scsi_scan_host(sh); 94262306a36Sopenharmony_ci 94362306a36Sopenharmony_ci pci_set_drvdata(pdev, sh); 94462306a36Sopenharmony_ci return 0; 94562306a36Sopenharmony_ci 94662306a36Sopenharmony_cidestroy: 94762306a36Sopenharmony_ci wd719x_destroy(wd); 94862306a36Sopenharmony_ciunmap: 94962306a36Sopenharmony_ci pci_iounmap(pdev, wd->base); 95062306a36Sopenharmony_cifree_host: 95162306a36Sopenharmony_ci scsi_host_put(sh); 95262306a36Sopenharmony_cirelease_region: 95362306a36Sopenharmony_ci pci_release_regions(pdev); 95462306a36Sopenharmony_cidisable_device: 95562306a36Sopenharmony_ci pci_disable_device(pdev); 95662306a36Sopenharmony_cifail: 95762306a36Sopenharmony_ci return err; 95862306a36Sopenharmony_ci} 95962306a36Sopenharmony_ci 96062306a36Sopenharmony_ci 96162306a36Sopenharmony_cistatic void wd719x_pci_remove(struct pci_dev *pdev) 96262306a36Sopenharmony_ci{ 96362306a36Sopenharmony_ci struct Scsi_Host *sh = pci_get_drvdata(pdev); 96462306a36Sopenharmony_ci struct wd719x *wd = shost_priv(sh); 96562306a36Sopenharmony_ci 96662306a36Sopenharmony_ci scsi_remove_host(sh); 96762306a36Sopenharmony_ci wd719x_destroy(wd); 96862306a36Sopenharmony_ci pci_iounmap(pdev, wd->base); 96962306a36Sopenharmony_ci pci_release_regions(pdev); 97062306a36Sopenharmony_ci pci_disable_device(pdev); 97162306a36Sopenharmony_ci 97262306a36Sopenharmony_ci scsi_host_put(sh); 97362306a36Sopenharmony_ci} 97462306a36Sopenharmony_ci 97562306a36Sopenharmony_cistatic const struct pci_device_id wd719x_pci_table[] = { 97662306a36Sopenharmony_ci { PCI_DEVICE(PCI_VENDOR_ID_WD, 0x3296) }, 97762306a36Sopenharmony_ci {} 97862306a36Sopenharmony_ci}; 97962306a36Sopenharmony_ci 98062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, wd719x_pci_table); 98162306a36Sopenharmony_ci 98262306a36Sopenharmony_cistatic struct pci_driver wd719x_pci_driver = { 98362306a36Sopenharmony_ci .name = "wd719x", 98462306a36Sopenharmony_ci .id_table = wd719x_pci_table, 98562306a36Sopenharmony_ci .probe = wd719x_pci_probe, 98662306a36Sopenharmony_ci .remove = wd719x_pci_remove, 98762306a36Sopenharmony_ci}; 98862306a36Sopenharmony_ci 98962306a36Sopenharmony_cimodule_pci_driver(wd719x_pci_driver); 99062306a36Sopenharmony_ci 99162306a36Sopenharmony_ciMODULE_DESCRIPTION("Western Digital WD7193/7197/7296 SCSI driver"); 99262306a36Sopenharmony_ciMODULE_AUTHOR("Ondrej Zary, Aaron Dewell, Juergen Gaertner"); 99362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 99462306a36Sopenharmony_ciMODULE_FIRMWARE("wd719x-wcs.bin"); 99562306a36Sopenharmony_ciMODULE_FIRMWARE("wd719x-risc.bin"); 996