diff options
Diffstat (limited to 'drivers/ata/pata_cmd64x.c')
-rw-r--r-- | drivers/ata/pata_cmd64x.c | 125 |
1 files changed, 110 insertions, 15 deletions
diff --git a/drivers/ata/pata_cmd64x.c b/drivers/ata/pata_cmd64x.c index f98dffedf4bc..dadfc358ba1c 100644 --- a/drivers/ata/pata_cmd64x.c +++ b/drivers/ata/pata_cmd64x.c @@ -31,7 +31,7 @@ #include <linux/libata.h> #define DRV_NAME "pata_cmd64x" -#define DRV_VERSION "0.2.5" +#define DRV_VERSION "0.3.1" /* * CMD64x specific registers definition. @@ -254,17 +254,109 @@ static void cmd648_bmdma_stop(struct ata_queued_cmd *qc) } /** - * cmd646r1_dma_stop - DMA stop callback + * cmd64x_bmdma_stop - DMA stop callback * @qc: Command in progress * - * Stub for now while investigating the r1 quirk in the old driver. + * Track the completion of live DMA commands and clear the + * host->private_data DMA tracking flag as we do. */ -static void cmd646r1_bmdma_stop(struct ata_queued_cmd *qc) +static void cmd64x_bmdma_stop(struct ata_queued_cmd *qc) { + struct ata_port *ap = qc->ap; ata_bmdma_stop(qc); + WARN_ON(ap->host->private_data != ap); + ap->host->private_data = NULL; } +/** + * cmd64x_qc_defer - Defer logic for chip limits + * @qc: queued command + * + * Decide whether we can issue the command. Called under the host lock. + */ + +static int cmd64x_qc_defer(struct ata_queued_cmd *qc) +{ + struct ata_host *host = qc->ap->host; + struct ata_port *alt = host->ports[1 ^ qc->ap->port_no]; + int rc; + int dma = 0; + + /* Apply the ATA rules first */ + rc = ata_std_qc_defer(qc); + if (rc) + return rc; + + if (qc->tf.protocol == ATAPI_PROT_DMA || + qc->tf.protocol == ATA_PROT_DMA) + dma = 1; + + /* If the other port is not live then issue the command */ + if (alt == NULL || !alt->qc_active) { + if (dma) + host->private_data = qc->ap; + return 0; + } + /* If there is a live DMA command then wait */ + if (host->private_data != NULL) + return ATA_DEFER_PORT; + if (dma) + /* Cannot overlap our DMA command */ + return ATA_DEFER_PORT; + return 0; +} + +/** + * cmd64x_interrupt - ATA host interrupt handler + * @irq: irq line (unused) + * @dev_instance: pointer to our ata_host information structure + * + * Our interrupt handler for PCI IDE devices. Calls + * ata_sff_host_intr() for each port that is flagging an IRQ. We cannot + * use the defaults as we need to avoid touching status/altstatus during + * a DMA. + * + * LOCKING: + * Obtains host lock during operation. + * + * RETURNS: + * IRQ_NONE or IRQ_HANDLED. + */ +irqreturn_t cmd64x_interrupt(int irq, void *dev_instance) +{ + struct ata_host *host = dev_instance; + struct pci_dev *pdev = to_pci_dev(host->dev); + unsigned int i; + unsigned int handled = 0; + unsigned long flags; + static const u8 irq_reg[2] = { CFR, ARTTIM23 }; + static const u8 irq_mask[2] = { 1 << 2, 1 << 4 }; + + /* TODO: make _irqsave conditional on x86 PCI IDE legacy mode */ + spin_lock_irqsave(&host->lock, flags); + + for (i = 0; i < host->n_ports; i++) { + struct ata_port *ap; + u8 reg; + + pci_read_config_byte(pdev, irq_reg[i], ®); + ap = host->ports[i]; + if (ap && (reg & irq_mask[i]) && + !(ap->flags & ATA_FLAG_DISABLED)) { + struct ata_queued_cmd *qc; + + qc = ata_qc_from_tag(ap, ap->link.active_tag); + if (qc && (!(qc->tf.flags & ATA_TFLAG_POLLING)) && + (qc->flags & ATA_QCFLAG_ACTIVE)) + handled |= ata_sff_host_intr(ap, qc); + } + } + + spin_unlock_irqrestore(&host->lock, flags); + + return IRQ_RETVAL(handled); +} static struct scsi_host_template cmd64x_sht = { ATA_BMDMA_SHT(DRV_NAME), }; @@ -273,6 +365,8 @@ static const struct ata_port_operations cmd64x_base_ops = { .inherits = &ata_bmdma_port_ops, .set_piomode = cmd64x_set_piomode, .set_dmamode = cmd64x_set_dmamode, + .bmdma_stop = cmd64x_bmdma_stop, + .qc_defer = cmd64x_qc_defer, }; static struct ata_port_operations cmd64x_port_ops = { @@ -282,7 +376,6 @@ static struct ata_port_operations cmd64x_port_ops = { static struct ata_port_operations cmd646r1_port_ops = { .inherits = &cmd64x_base_ops, - .bmdma_stop = cmd646r1_bmdma_stop, .cable_detect = ata_cable_40wire, }; @@ -290,12 +383,11 @@ static struct ata_port_operations cmd648_port_ops = { .inherits = &cmd64x_base_ops, .bmdma_stop = cmd648_bmdma_stop, .cable_detect = cmd648_cable_detect, + .qc_defer = ata_std_qc_defer }; static int cmd64x_init_one(struct pci_dev *pdev, const struct pci_device_id *id) { - u32 class_rev; - static const struct ata_port_info cmd_info[6] = { { /* CMD 643 - no UDMA */ .flags = ATA_FLAG_SLAVE_POSS, @@ -340,40 +432,43 @@ static int cmd64x_init_one(struct pci_dev *pdev, const struct pci_device_id *id) const struct ata_port_info *ppi[] = { &cmd_info[id->driver_data], NULL }; u8 mrdmode; int rc; + struct ata_host *host; rc = pcim_enable_device(pdev); if (rc) return rc; - pci_read_config_dword(pdev, PCI_CLASS_REVISION, &class_rev); - class_rev &= 0xFF; - if (id->driver_data == 0) /* 643 */ ata_pci_bmdma_clear_simplex(pdev); if (pdev->device == PCI_DEVICE_ID_CMD_646) { /* Does UDMA work ? */ - if (class_rev > 4) + if (pdev->revision > 4) ppi[0] = &cmd_info[2]; /* Early rev with other problems ? */ - else if (class_rev == 1) + else if (pdev->revision == 1) ppi[0] = &cmd_info[3]; } + pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 64); pci_read_config_byte(pdev, MRDMODE, &mrdmode); mrdmode &= ~ 0x30; /* IRQ set up */ mrdmode |= 0x02; /* Memory read line enable */ pci_write_config_byte(pdev, MRDMODE, mrdmode); - /* Force PIO 0 here.. */ - /* PPC specific fixup copied from old driver */ #ifdef CONFIG_PPC pci_write_config_byte(pdev, UDIDETCR0, 0xF0); #endif + rc = ata_pci_sff_prepare_host(pdev, ppi, &host); + if (rc) + return rc; + /* We use this pointer to track the AP which has DMA running */ + host->private_data = NULL; - return ata_pci_sff_init_one(pdev, ppi, &cmd64x_sht, NULL); + pci_set_master(pdev); + return ata_pci_sff_activate_host(host, cmd64x_interrupt, &cmd64x_sht); } #ifdef CONFIG_PM |