diff options
author | Quinn Jensen <quinn.jensen@freescale.com> | 2007-10-24 21:24:53 -0600 |
---|---|---|
committer | Quinn Jensen <quinn.jensen@freescale.com> | 2007-10-24 21:24:53 -0600 |
commit | d8749be38fee07bde24f77bedb122c9c2cdf8377 (patch) | |
tree | 1b20169bd60e59eff12fc0b6c28042e7d6ca35b3 /drivers | |
parent | b4d071c2e403c2279e9e1b7216e22cdbe12d43c7 (diff) |
Bugzilla 771 - Fix pata_fsl libata driver DMA mode
Patch for Bugzilla 771 - Fix pata_fsl libata driver DMA mode. Full
sglist DMA working on linux 2.6.22 for MX27/MX31. Not fast enough on
MX27, though. MX31 is as fast as the old mxc_ide driver. Writes are
unreliable on MX27 TO1. Beyond UDMA3 on MX27 wasn't 100% reliable.
http://www.bitshrine.org/gpp/linux-2.6.22-mx-Bugzilla-771-Fix-pata_fsl-libata-driver-DM.patch
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/ata/pata_fsl.c | 187 |
1 files changed, 183 insertions, 4 deletions
diff --git a/drivers/ata/pata_fsl.c b/drivers/ata/pata_fsl.c index 94e3d054be98..4cf87e38f0fb 100644 --- a/drivers/ata/pata_fsl.c +++ b/drivers/ata/pata_fsl.c @@ -24,15 +24,25 @@ #include <linux/libata.h> #include <linux/platform_device.h> #include <linux/fsl_devices.h> +#include <asm/dma.h> #define DRV_NAME "pata_fsl" #define DRV_VERSION "1.0" struct pata_fsl_priv { + int ultra; u8 *fsl_ata_regs; + int dma_rchan; + int dma_wchan; + int dma_done; + int dma_dir; }; enum { + /* various constants */ + + FSL_ATA_MAX_SG_LEN = 65534, + /* offsets to registers */ FSL_ATA_TIMING_REGS = 0x00, @@ -285,11 +295,166 @@ static void pata_fsl_set_piomode(struct ata_port *ap, struct ata_device *adev) set_ata_bus_timing(adev->pio_mode, to_platform_device(ap->dev)); } +static void pata_fsl_set_dmamode(struct ata_port *ap, struct ata_device *adev) +{ + struct pata_fsl_priv *priv = ap->host->private_data; + + priv->ultra = adev->dma_mode >= XFER_UDMA_0; + + set_ata_bus_timing(adev->dma_mode, to_platform_device(ap->dev)); +} + static int pata_fsl_port_start(struct ata_port *ap) { return 0; } +static void dma_callback(void *arg, int error_status, unsigned int count) +{ + struct ata_port *ap = arg; + struct pata_fsl_priv *priv = ap->host->private_data; + u8 *ata_regs = priv->fsl_ata_regs; + + priv->dma_done = 1; + /* + * DMA is finished, so unmask INTRQ from the drive to allow the + * normal ISR to fire. + */ + __raw_writel(FSL_ATA_INTR_ATA_INTRQ2, ata_regs + FSL_ATA_INT_EN); + mb(); +} + +static void pata_fsl_bmdma_setup(struct ata_queued_cmd *qc) +{ + int nr_sg = 0; + int chan; + int dma_mode = 0, dma_ultra; + u8 ata_control; + struct ata_port *ap = qc->ap; + struct pata_fsl_priv *priv = ap->host->private_data; + u8 *ata_regs = priv->fsl_ata_regs; + struct fsl_ata_platform_data *plat = ap->dev->platform_data; + struct scatterlist tmp[plat->max_sg], *tsg, *sg; + int err; + + priv->dma_dir = qc->dma_dir; + + /* + * Configure the on-chip ATA interface hardware. + */ + dma_ultra = priv->ultra ? + FSL_ATA_CTRL_DMA_ULTRA : 0; + + ata_control = FSL_ATA_CTRL_FIFO_RST_B | + FSL_ATA_CTRL_ATA_RST_B | + FSL_ATA_CTRL_DMA_PENDING | + dma_ultra; + + if (qc->dma_dir == DMA_TO_DEVICE) { + chan = priv->dma_wchan; + ata_control |= FSL_ATA_CTRL_FIFO_TX_EN | + FSL_ATA_CTRL_DMA_WRITE; + dma_mode = DMA_MODE_WRITE; + } else { + chan = priv->dma_rchan; + ata_control |= FSL_ATA_CTRL_FIFO_RCV_EN; + dma_mode = DMA_MODE_READ; + } + + __raw_writel(ata_control, ata_regs + FSL_ATA_CONTROL); + __raw_writel(plat->fifo_alarm, ata_regs + FSL_ATA_FIFO_ALARM); + __raw_writel(FSL_ATA_INTR_ATA_INTRQ1, ata_regs + FSL_ATA_INT_EN); + mb(); + + /* + * Set up the DMA completion callback. + */ + mxc_dma_callback_set(chan, dma_callback, (void *)ap); + + /* + * Copy the sg list to an array. + */ + tsg = tmp; + ata_for_each_sg(sg, qc) { + memcpy(tsg, sg, sizeof *sg); + tsg++; + nr_sg++; + } + + err = mxc_dma_sg_config(chan, tmp, nr_sg, 0, dma_mode); + if (err) { + printk(KERN_ERR "pata_fsl_bmdma_setup: error %d\n", err); + } +} + +static void pata_fsl_bmdma_start(struct ata_queued_cmd *qc) +{ + struct ata_port *ap = qc->ap; + struct pata_fsl_priv *priv = ap->host->private_data; + int chan; + int err; + + /* + * Start the channel. + */ + chan = qc->dma_dir == DMA_TO_DEVICE ? priv->dma_wchan : priv->dma_rchan; + + priv->dma_done = 0; + + err = mxc_dma_enable(chan); + if (err) { + printk(KERN_ERR "%s: : error %d\n", __func__, err); + } + + ap->ops->exec_command(ap, &qc->tf); +} + +static void pata_fsl_bmdma_stop(struct ata_queued_cmd *qc) +{ + struct ata_port *ap = qc->ap; + + /* do a dummy read as in ata_bmdma_stop */ + ata_altstatus(ap); +} + +static u8 pata_fsl_bmdma_status(struct ata_port *ap) +{ + struct pata_fsl_priv *priv = ap->host->private_data; + + return priv->dma_done ? ATA_DMA_INTR : 0; +} + +static void pata_fsl_dma_init(struct ata_port *ap) +{ + struct pata_fsl_priv *priv = ap->host->private_data; + + priv->dma_rchan = -1; + priv->dma_wchan = -1; + + priv->dma_rchan = mxc_dma_request(MXC_DMA_ATA_RX, "MXC ATA RX"); + if (priv->dma_rchan < 0) { + dev_printk(KERN_ERR, ap->dev, "couldn't get RX DMA channel\n"); + goto err_out; + } + + priv->dma_wchan = mxc_dma_request(MXC_DMA_ATA_TX, "MXC ATA TX"); + if (priv->dma_wchan < 0) { + dev_printk(KERN_ERR, ap->dev, "couldn't get TX DMA channel\n"); + goto err_out; + } + + dev_printk(KERN_ERR, ap->dev, "rchan=%d wchan=%d\n", priv->dma_rchan, + priv->dma_wchan); + return; + +err_out: + ap->mwdma_mask = 0; + ap->udma_mask = 0; + mxc_dma_free(priv->dma_rchan); + mxc_dma_free(priv->dma_wchan); + kfree(priv); +} + static u8 pata_fsl_irq_ack(struct ata_port *ap, unsigned int chk_drq) { unsigned int bits = chk_drq ? ATA_BUSY | ATA_DRQ : ATA_BUSY; @@ -317,6 +482,7 @@ static struct scsi_host_template pata_fsl_sht = { .emulated = ATA_SHT_EMULATED, .use_clustering = ATA_SHT_USE_CLUSTERING, .proc_name = DRV_NAME, + .dma_boundary = FSL_ATA_MAX_SG_LEN, .slave_configure = ata_scsi_slave_config, .slave_destroy = ata_scsi_slave_destroy, .bios_param = ata_std_bios_param, @@ -324,6 +490,7 @@ static struct scsi_host_template pata_fsl_sht = { static struct ata_port_operations pata_fsl_port_ops = { .set_piomode = pata_fsl_set_piomode, + .set_dmamode = pata_fsl_set_dmamode, .port_disable = ata_port_disable, .tf_load = ata_tf_load, @@ -338,6 +505,9 @@ static struct ata_port_operations pata_fsl_port_ops = { .post_internal_cmd = ata_bmdma_post_internal_cmd, .cable_detect = ata_cable_unknown, + .bmdma_setup = pata_fsl_bmdma_setup, + .bmdma_start = pata_fsl_bmdma_start, + .qc_prep = ata_noop_qc_prep, .qc_issue = ata_qc_issue_prot, @@ -348,6 +518,9 @@ static struct ata_port_operations pata_fsl_port_ops = { .irq_ack = pata_fsl_irq_ack, .port_start = pata_fsl_port_start, + + .bmdma_stop = pata_fsl_bmdma_stop, + .bmdma_status = pata_fsl_bmdma_status, }; static void fsl_setup_port(struct ata_ioports *ioaddr) @@ -400,7 +573,7 @@ static int __devinit pata_fsl_probe(struct platform_device *pdev) /* * Allocate private data */ - priv = kmalloc(sizeof (struct pata_fsl_priv), GFP_KERNEL); + priv = kzalloc(sizeof (struct pata_fsl_priv), GFP_KERNEL); if(priv == NULL) { /* free(host); */ return -ENOMEM; @@ -423,8 +596,9 @@ static int __devinit pata_fsl_probe(struct platform_device *pdev) ap->ioaddr.altstatus_addr = ap->ioaddr.ctl_addr; ap->ops = &pata_fsl_port_ops; ap->pio_mask = 0x7F; - ap->mwdma_mask = 0x00; - ap->udma_mask = 0x00; + ap->mwdma_mask = 0x7F; + ap->udma_mask = plat->udma_mask; + pata_fsl_sht.sg_tablesize = plat->max_sg; fsl_setup_port(&ap->ioaddr); @@ -446,8 +620,13 @@ static int __devinit pata_fsl_probe(struct platform_device *pdev) /* Set initial timing and mode */ set_ata_bus_timing(XFER_PIO_4, pdev); + /* get DMA ready */ + pata_fsl_dma_init(ap); + /* - * Enable hardware interrupts. + * Enable the ATA INTRQ interrupt from the bus, but + * only allow the CPU to see it (INTRQ2) at this point. + * INTRQ1, which goes to the DMA, will be enabled later. */ __raw_writel(FSL_ATA_INTR_ATA_INTRQ2, ata_regs + FSL_ATA_INT_EN); mb(); |