/*
* pata_ns87415.c - NS87415 (non PARISC) PATA
*
* (C) 2005 Red Hat <alan@lxorguk.ukuu.org.uk>
*
* This is a fairly generic MWDMA controller. It has some limitations
* as it requires timing reloads on PIO/DMA transitions but it is otherwise
* fairly well designed.
*
* This driver assumes the firmware has left the chip in a valid ST506
* compliant state, either legacy IRQ 14/15 or native INTA shared. You
* may need to add platform code if your system fails to do this.
*
* The same cell appears in the 87560 controller used by some PARISC
* systems. This has its own special mountain of errata.
*
* TODO:
* Test PARISC SuperIO
* Get someone to test on SPARC
* Implement lazy pio/dma switching for better performance
* 8bit shared timing.
* See if we need to kill the FIFO for ATAPI
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <scsi/scsi_host.h>
#include <linux/libata.h>
#include <linux/ata.h>
#define DRV_NAME "pata_ns87415"
#define DRV_VERSION "0.0.1"
/**
* ns87415_set_mode - Initialize host controller mode timings
* @ap: Port whose timings we are configuring
* @adev: Device whose timings we are configuring
* @mode: Mode to set
*
* Program the mode registers for this controller, channel and
* device. Because the chip is quite an old design we have to do this
* for PIO/DMA switches.
*
* LOCKING:
* None (inherited from caller).
*/
static void ns87415_set_mode(struct ata_port *ap, struct ata_device *adev, u8 mode)
{
struct pci_dev *dev = to_pci_dev(ap->host->dev);
int unit = 2 * ap->port_no + adev->devno;
int timing = 0x44 + 2 * unit;
unsigned long T = 1000000000 / 33333; /* PCI clocks */
struct ata_timing t;
u16 clocking;
u8 iordy;
u8 status;
/* Timing register format is 17 - low nybble read timing with
the high nybble being 16 - x for recovery time in PCI clocks */
ata_timing_compute(adev, adev->pio_mode, &t, T, 0);
clocking = 17 - clamp_val(t.active, 2, 17);
clocking |= (16 - clamp_val(t.recover, 1, 16)) << 4;
/* Use the same timing for read and write bytes */
clocking |= (clocking << 8);
pci_write_config_word(dev, timing, clocking);
/* Set the IORDY enable versus DMA enable on or off properly */
pci_read_config_byte(dev, 0x42, &iordy);
iordy &= ~(1 << (4 + unit));
if (mode >= XFER_MW_DMA_0 || !ata_pio_need_iordy(adev))
iordy |= (1 << (4 + unit));
/* Paranoia: We shouldn't ever get here with busy write buffers
but if so wait */
pci_read_config_byte(dev, 0x43, &status);
while (status & 0x03) {
udelay(1);
pci_read_config_byte(dev, 0x43, &status);
}
/* Flip the IORDY/DMA bits now we are sure the write buffers are
clear */
pci_write_config_byte(dev, 0x42, iordy);
/* TODO: Set byte 54 command timing to the best 8bit
mode shared by all four devices */
}
/**
* ns87415_set_piomode - Initialize host controller PATA PIO timings
* @ap: Port whose timings we are configuring
* @adev: Device to program
*
* Set PIO mode for device, in host controller PCI config space.
*
* LOCKING:
* None (inherited from caller).
*/
static void ns87415_set_piomode(struct ata_port *ap, struct ata_device *adev)
{
ns87415_set_mode(ap, adev, adev->pio_mode);
}
/**
* ns87415_bmdma_setup - Set up DMA
* @qc: Command block
*
* Set up for bus masterng DMA. We have to do this ourselves
* rather than use the helper due to a chip erratum
*/
static void ns87415_bmdma_setup(struct ata_queued_cmd *qc)
{
struct ata_port *ap = qc->ap;
unsigned int rw = (qc->tf.flags & ATA_TFLAG_WRITE);
u8 dmactl;
/* load PRD table addr. */
mb(); /* make sure PRD table writes are visible to controller */
iowrite32(ap->prd_dma, ap->ioaddr.bmdma_addr + ATA_DMA_TABLE_OFS);
/* specify data direction, triple-check start bit is clear */
dmactl = ioread8(ap->ioaddr.bmdma_addr + ATA_DMA_CMD);
dmactl &= ~(ATA_DMA_WR | ATA_DMA_START);
/* Due to an erratum we need to write these bits to the wrong
place - which does save us an I/O bizarrely */
dmactl |= ATA_DMA_INTR | ATA_DMA_ERR;
if (!rw)
dmactl |= ATA_DMA_WR;
iowrite8(dmactl, ap->ioaddr.bmdma_addr + ATA_DMA_CMD);
/* issue r/w command */
ap->ops->sff_exec_command(ap, &qc->tf);
}
/**
* ns87415_bmdma_start - Begin DMA transfer
* @qc: Command block
*