/*
* Driver for Western Digital WD7193, WD7197 and WD7296 SCSI cards
* Copyright 2013 Ondrej Zary
*
* Original driver by
* Aaron Dewell <dewell@woods.net>
* Gaerti <Juergen.Gaertner@mbox.si.uni-hannover.de>
*
* HW documentation available in book:
*
* SPIDER Command Protocol
* by Chandru M. Sippy
* SCSI Storage Products (MCP)
* Western Digital Corporation
* 09-15-95
*
* http://web.archive.org/web/20070717175254/http://sun1.rrzn.uni-hannover.de/gaertner.juergen/wd719x/Linux/Docu/Spider/
*/
/*
* Driver workflow:
* 1. SCSI command is transformed to SCB (Spider Control Block) by the
* queuecommand function.
* 2. The address of the SCB is stored in a list to be able to access it, if
* something goes wrong.
* 3. The address of the SCB is written to the Controller, which loads the SCB
* via BM-DMA and processes it.
* 4. After it has finished, it generates an interrupt, and sets registers.
*
* flaws:
* - abort/reset functions
*
* ToDo:
* - tagged queueing
*/
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/firmware.h>
#include <linux/eeprom_93cx6.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_host.h>
#include "wd719x.h"
/* low-level register access */
static inline u8 wd719x_readb(struct wd719x *wd, u8 reg)
{
return ioread8(wd->base + reg);
}
static inline u32 wd719x_readl(struct wd719x *wd, u8 reg)
{
return ioread32(wd->base + reg);
}
static inline void wd719x_writeb(struct wd719x *wd, u8 reg, u8 val)
{
iowrite8(val, wd->base + reg);
}
static inline void wd719x_writew(struct wd719x *wd, u8 reg, u16 val)
{
iowrite16(val, wd->base + reg);
}
static inline void wd719x_writel(struct wd719x *wd, u8 reg, u32 val)
{
iowrite32(val, wd->base + reg);
}
/* wait until the command register is ready */
static inline int wd719x_wait_ready(struct wd719x *wd)
{
int i = 0;
do {
if (wd719x_readb(wd, WD719X_AMR_COMMAND) == WD719X_CMD_READY)
return 0;
udelay(1);
} while (i++ < WD719X_WAIT_FOR_CMD_READY);
dev_err(&wd->pdev->dev, "command register is not ready: 0x%02x\n",
wd719x_readb(wd, WD719X_AMR_COMMAND));
return -ETIMEDOUT;
}
/* poll interrupt status register until command finishes */
static inline int wd719x_wait_done(struct wd719x *wd, int timeout)
{
u8 status;
while (timeout > 0) {
status = wd719x_readb(wd, WD719X_AMR_INT_STATUS);
if (status)
break;
timeout--;
udelay(1);
}
if (timeout <= 0) {
dev_err(&wd->pdev->dev, "direct command timed out\n");
return -ETIMEDOUT;
}
if (status != WD719X_INT_NOERRORS) {
dev_err(&wd->pdev->dev, "direct command failed, status 0x%02x, SUE 0x%02x\n",
status, wd719x_readb(wd, WD719X_AMR_SCB_ERROR));
return -EIO;
}
return 0;
}
static int wd719x_direct_cmd(struct wd719x *wd, u8 opcode, u8 dev, u8 lun,
u8 tag, dma_addr_t data, int timeout)
{
int ret = 0;
/* clear interrupt status register (allow command register to clear) */
wd719x_writeb(wd, WD719X_AMR_INT_STATUS, WD719X_INT_NONE);
/* Wait for the Command register to become free */
if (wd719x_wait_ready(wd))
return -ETIMEDOUT;
/* make sure we get NO interrupts */
dev |= WD719X_DISABLE_INT;
wd719x_writeb(wd, WD719X_AMR_CMD_PARAM, dev);
wd719x_writeb(wd, WD719X_AMR_CMD_PARAM_2, lun);
wd719x_writeb(wd, WD719X_AMR_CMD_PARAM_3, tag);
if (data)
wd719x_writel(wd, WD719X_AMR_SCB_IN, data);
/* clear interrupt status register again */
wd719x_writeb(wd, WD719X_AMR_INT_STATUS, WD719X_INT_NONE);
/* Now, write the command */
wd719x_writeb(wd, WD719X_AMR_COMMAND, opcode);
if (timeout) /* wait for the command to complete */
ret = wd719x_wait_done(wd, timeout);
/* clear interrupt status register (clean up) */
if (opcode != WD719X_CMD_READ_FIRMVER)
wd719x_writeb(wd, WD719X_AMR_INT_STATUS, WD719X_INT_NONE);
return ret;
}
static void wd719x_destroy(struct wd719x *wd)
{
struct wd719x_scb *scb;
/* stop the RISC */
if (wd719x_direct_cmd(wd, WD719X_CMD_SLEEP, 0, 0, 0, 0,
WD719X_WAIT_FOR_RISC))
dev_warn(&wd->pdev->dev, "RISC sleep command failed\n");
/* disable RISC */
wd719x_writeb(wd, WD719X_PCI_MODE_SELECT, 0);
/* free all SCBs */