Bugzilla 771 - Fix pata_fsl libata driver DMA mode
authorQuinn Jensen <quinn.jensen@freescale.com>
Thu, 25 Oct 2007 03:24:53 +0000 (21:24 -0600)
committerQuinn Jensen <quinn.jensen@freescale.com>
Thu, 25 Oct 2007 03:24:53 +0000 (21:24 -0600)
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

arch/arm/mach-mx27/devices.c
arch/arm/mach-mx3/devices.c
drivers/ata/pata_fsl.c
include/linux/fsl_devices.h

index 202816f62d61909c43ecad55036735e0f355c5ae..b1ac7c5b24dafc682ea9a3fc038feb40f62bae83 100644 (file)
@@ -638,6 +638,9 @@ static int ata_get_clk_rate(void)
 }
 
 static struct fsl_ata_platform_data ata_data = {
+       .udma_mask        = 0x0F, /* the board handles up to UDMA3 */
+       .fifo_alarm       = MXC_IDE_DMA_WATERMARK / 2,
+       .max_sg           = MXC_IDE_DMA_BD_NR,
        .init             = ata_init,
        .exit             = ata_exit,
        .get_clk_rate     = ata_get_clk_rate,
index 77932196059104f00302eb3d04167119029730dc..18dee757dc21e43ee0279f0203b3d7d1aa9c4091 100644 (file)
@@ -918,6 +918,9 @@ static int ata_get_clk_rate(void)
 }
 
 static struct fsl_ata_platform_data ata_data = {
+       .udma_mask        = 0x0F, /* board can handle up to UDMA3 */
+       .fifo_alarm       = MXC_IDE_DMA_WATERMARK / 2,
+       .max_sg           = MXC_IDE_DMA_BD_NR,
        .init             = ata_init,
        .exit             = ata_exit,
        .get_clk_rate     = ata_get_clk_rate,
index 94e3d054be98e0dff5930efd2506ee6c93e2bfce..4cf87e38f0fbe72b48745f919e0e866b93c4da85 100644 (file)
 #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();
index bf68f2c42ca84b6664d4ff8d2df3cca34f44be8c..024063603e6c3adab7a6d12f026c7bbad29ea516 100644 (file)
@@ -142,6 +142,9 @@ struct fsl_spi_platform_data {
 };
 
 struct fsl_ata_platform_data {
+       int     udma_mask;      /* UDMA modes h/w can handle */
+       int     fifo_alarm;     /* value for fifo_alarm reg */
+       int     max_sg;         /* longest sglist h/w can handle */
        int     (*init)(struct platform_device *pdev);
        void    (*exit)(void);
        int     (*get_clk_rate)(void);