This patch includes the latest changes to the wbsd driver.

Changelog:

* Proper usage of kunmap.
* Comment about hw bugs.
* Waits for data transfers to finish properly.
* Added module version info.
* FIFO bug fix for small reads.
* Optimised FIFO loop.
* DMA demand mode.
* IRQ race condition when sending commands fixed.

/Pierre
Index: linux/drivers/mmc/wbsd.c
===================================================================
--- linux/drivers/mmc/wbsd.c	(revision 105)
+++ linux/drivers/mmc/wbsd.c	(working copy)
@@ -1,11 +1,24 @@
 /*
- *  linux/drivers/mmc/wbsd.c
+ *  linux/drivers/mmc/wbsd.c - Winbond W83L51xD SD/MMC driver
  *
- *  Copyright (C) 2004 Pierre Ossman, All Rights Reserved.
+ *  Copyright (C) 2004-2005 Pierre Ossman, All Rights Reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
+ *
+ *
+ * Warning!
+ *
+ * Changes to the FIFO system should be done with extreme care since
+ * the hardware is full of bugs related to the FIFO. Known issues are:
+ *
+ * - FIFO size field in FSR is always zero.
+ *
+ * - FIFO interrupts tend not to work as they should. Interrupts are
+ *   triggered only for full/empty events, not for threshold values.
+ *
+ * - On APIC systems the FIFO empty interrupt is sometimes lost.
  */
 
 #include <linux/config.h>
@@ -27,7 +40,7 @@
 #include "wbsd.h"
 
 #define DRIVER_NAME "wbsd"
-#define DRIVER_VERSION "1.0"
+#define DRIVER_VERSION "1.1"
 
 #ifdef CONFIG_MMC_DEBUG
 #define DBG(x...) \
@@ -239,13 +252,14 @@
 
 static inline char* wbsd_kmap_sg(struct wbsd_host* host)
 {
-	return kmap_atomic(host->cur_sg->page, KM_BIO_SRC_IRQ) +
+	host->mapped_sg = kmap_atomic(host->cur_sg->page, KM_BIO_SRC_IRQ) +
 		host->cur_sg->offset;
+	return host->mapped_sg;
 }
 
 static inline void wbsd_kunmap_sg(struct wbsd_host* host)
 {
-	kunmap_atomic(host->cur_sg->page, KM_BIO_SRC_IRQ);
+	kunmap_atomic(host->mapped_sg, KM_BIO_SRC_IRQ);
 }
 
 static inline void wbsd_sg_to_dma(struct wbsd_host* host, struct mmc_data* data)
@@ -272,7 +286,7 @@
 			memcpy(dmabuf, sgbuf, size);
 		else
 			memcpy(dmabuf, sgbuf, sg[i].length);
-		kunmap_atomic(sg[i].page, KM_BIO_SRC_IRQ);
+		kunmap_atomic(sgbuf, KM_BIO_SRC_IRQ);
 		dmabuf += sg[i].length;
 		
 		if (size < sg[i].length)
@@ -318,7 +332,7 @@
 			memcpy(sgbuf, dmabuf, size);
 		else
 			memcpy(sgbuf, dmabuf, sg[i].length);
-		kunmap_atomic(sg[i].page, KM_BIO_SRC_IRQ);
+		kunmap_atomic(sgbuf, KM_BIO_SRC_IRQ);
 		dmabuf += sg[i].length;
 		
 		if (size < sg[i].length)
@@ -400,16 +414,16 @@
 static void wbsd_send_command(struct wbsd_host* host, struct mmc_command* cmd)
 {
 	int i;
-	u8 status, eir, isr;
+	u8 status, isr;
 	
 	DBGF("Sending cmd (%x)\n", cmd->opcode);
 
 	/*
-	 * Disable interrupts as the interrupt routine
-	 * will destroy the contents of ISR.
+	 * Clear accumulated ISR. The interrupt routine
+	 * will fill this one with events that occur during
+	 * transfer.
 	 */
-	eir = inb(host->base + WBSD_EIR);
-	outb(0, host->base + WBSD_EIR);
+	host->isr = 0;
 	
 	/*
 	 * Send the command (CRC calculated by host).
@@ -435,7 +449,7 @@
 		/*
 		 * Read back status.
 		 */
-		isr = inb(host->base + WBSD_ISR);
+		isr = host->isr;
 		
 		/* Card removed? */
 		if (isr & WBSD_INT_CARD)
@@ -456,17 +470,6 @@
 		}
 	}
 
-	/*
-	 * Restore interrupt mask to previous value.
-	 */
-	outb(eir, host->base + WBSD_EIR);
-	
-	/*
-	 * Call the interrupt routine to jump start
-	 * interrupts.
-	 */
-	wbsd_irq(0, host, NULL);
-
 	DBGF("Sent cmd (%x), res %d\n", cmd->opcode, cmd->error);
 }
 
@@ -478,6 +481,7 @@
 {
 	struct mmc_data* data = host->mrq->cmd->data;
 	char* buffer;
+	int i, fsr, fifo;
 	
 	/*
 	 * Handle excessive data.
@@ -491,60 +495,83 @@
 	 * Drain the fifo. This has a tendency to loop longer
 	 * than the FIFO length (usually one block).
 	 */
-	while (!(inb(host->base + WBSD_FSR) & WBSD_FIFO_EMPTY))
+	while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_EMPTY))
 	{
-		*buffer = inb(host->base + WBSD_DFR);
-		buffer++;
-		host->offset++;
-		host->remain--;
-
-		data->bytes_xfered++;
-		
 		/*
-		 * Transfer done?
-		 */
-		if (data->bytes_xfered == host->size)
-		{
-			wbsd_kunmap_sg(host);				
-			return;
-		}
+		 * The size field in the FSR is broken so we have to
+		 * do some guessing.
+		 */		
+		if (fsr & WBSD_FIFO_FULL)
+			fifo = 16;
+		else if (fsr & WBSD_FIFO_FUTHRE)
+			fifo = 8;
+		else
+			fifo = 1;
 		
-		/*
-		 * End of scatter list entry?
-		 */
-		if (host->remain == 0)
+		for (i = 0;i < fifo;i++)
 		{
-			wbsd_kunmap_sg(host);
+			*buffer = inb(host->base + WBSD_DFR);
+			buffer++;
+			host->offset++;
+			host->remain--;
+
+			data->bytes_xfered++;
 			
 			/*
-			 * Get next entry. Check if last.
+			 * Transfer done?
 			 */
-			if (!wbsd_next_sg(host))
+			if (data->bytes_xfered == host->size)
 			{
+				wbsd_kunmap_sg(host);				
+				return;
+			}
+			
+			/*
+			 * End of scatter list entry?
+			 */
+			if (host->remain == 0)
+			{
+				wbsd_kunmap_sg(host);
+				
 				/*
-				 * We should never reach this point.
-				 * It means that we're trying to
-				 * transfer more blocks than can fit
-				 * into the scatter list.
+				 * Get next entry. Check if last.
 				 */
-				BUG_ON(1);
+				if (!wbsd_next_sg(host))
+				{
+					/*
+					 * We should never reach this point.
+					 * It means that we're trying to
+					 * transfer more blocks than can fit
+					 * into the scatter list.
+					 */
+					BUG_ON(1);
+					
+					host->size = data->bytes_xfered;
+					
+					return;
+				}
 				
-				host->size = data->bytes_xfered;
-				
-				return;
+				buffer = wbsd_kmap_sg(host);
 			}
-			
-			buffer = wbsd_kmap_sg(host);
 		}
 	}
 	
 	wbsd_kunmap_sg(host);
+
+	/*
+	 * This is a very dirty hack to solve a
+	 * hardware problem. The chip doesn't trigger
+	 * FIFO threshold interrupts properly.
+	 */
+	if ((host->size - data->bytes_xfered) < 16)
+		tasklet_schedule(&host->fifo_tasklet);
 }
 
 static void wbsd_fill_fifo(struct wbsd_host* host)
 {
 	struct mmc_data* data = host->mrq->cmd->data;
 	char* buffer;
+	int i, fsr, fifo;
 	
 	/*
 	 * Check that we aren't being called after the
@@ -559,50 +586,64 @@
 	 * Fill the fifo. This has a tendency to loop longer
 	 * than the FIFO length (usually one block).
 	 */
-	while (!(inb(host->base + WBSD_FSR) & WBSD_FIFO_FULL))
+	while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_FULL))
 	{
-		outb(*buffer, host->base + WBSD_DFR);
-		buffer++;
-		host->offset++;
-		host->remain--;
-		
-		data->bytes_xfered++;
-		
 		/*
-		 * Transfer done?
-		 */
-		if (data->bytes_xfered == host->size)
-		{
-			wbsd_kunmap_sg(host);
-			return;
-		}
+		 * The size field in the FSR is broken so we have to
+		 * do some guessing.
+		 */		
+		if (fsr & WBSD_FIFO_EMPTY)
+			fifo = 0;
+		else if (fsr & WBSD_FIFO_EMTHRE)
+			fifo = 8;
+		else
+			fifo = 15;
 
-		/*
-		 * End of scatter list entry?
-		 */
-		if (host->remain == 0)
+		for (i = 16;i > fifo;i--)
 		{
-			wbsd_kunmap_sg(host);
+			outb(*buffer, host->base + WBSD_DFR);
+			buffer++;
+			host->offset++;
+			host->remain--;
 			
+			data->bytes_xfered++;
+			
 			/*
-			 * Get next entry. Check if last.
+			 * Transfer done?
 			 */
-			if (!wbsd_next_sg(host))
+			if (data->bytes_xfered == host->size)
 			{
+				wbsd_kunmap_sg(host);
+				return;
+			}
+
+			/*
+			 * End of scatter list entry?
+			 */
+			if (host->remain == 0)
+			{
+				wbsd_kunmap_sg(host);
+				
 				/*
-				 * We should never reach this point.
-				 * It means that we're trying to
-				 * transfer more blocks than can fit
-				 * into the scatter list.
+				 * Get next entry. Check if last.
 				 */
-				BUG_ON(1);
+				if (!wbsd_next_sg(host))
+				{
+					/*
+					 * We should never reach this point.
+					 * It means that we're trying to
+					 * transfer more blocks than can fit
+					 * into the scatter list.
+					 */
+					BUG_ON(1);
+					
+					host->size = data->bytes_xfered;
+					
+					return;
+				}
 				
-				host->size = data->bytes_xfered;
-				
-				return;
+				buffer = wbsd_kmap_sg(host);
 			}
-			
-			buffer = wbsd_kmap_sg(host);
 		}
 	}
 	
@@ -689,9 +730,9 @@
 		disable_dma(host->dma);
 		clear_dma_ff(host->dma);
 		if (data->flags & MMC_DATA_READ)
-			set_dma_mode(host->dma, DMA_MODE_READ);
+			set_dma_mode(host->dma, DMA_MODE_READ & ~0x40);
 		else
-			set_dma_mode(host->dma, DMA_MODE_WRITE);
+			set_dma_mode(host->dma, DMA_MODE_WRITE & ~0x40);
 		set_dma_addr(host->dma, host->dma_addr);
 		set_dma_count(host->dma, host->size);
 
@@ -701,8 +742,7 @@
 		/*
 		 * Enable DMA on the host.
 		 */
-		wbsd_write_index(host, WBSD_IDX_DMA,
-			WBSD_DMA_SINGLE | WBSD_DMA_ENABLE);
+		wbsd_write_index(host, WBSD_IDX_DMA, WBSD_DMA_ENABLE);
 	}
 	else
 	{
@@ -746,6 +786,7 @@
 {
 	unsigned long dmaflags;
 	int count;
+	u8 status;
 	
 	WARN_ON(host->mrq == NULL);
 
@@ -754,6 +795,15 @@
 	 */
 	if (data->stop)
 		wbsd_send_command(host, data->stop);
+
+	/*
+	 * Wait for the controller to leave data
+	 * transfer state.
+	 */
+	do
+	{
+		status = wbsd_read_index(host, WBSD_IDX_STATUS);
+	} while (status & (WBSD_BLOCK_READ | WBSD_BLOCK_WRITE));
 	
 	/*
 	 * DMA transfer?
@@ -851,7 +901,13 @@
 	 * transfered.
 	 */	
 	if (cmd->data && (cmd->error == MMC_ERR_NONE))
-	{		
+	{
+		/*
+		 * Dirty fix for hardware bug.
+		 */
+		if (host->dma == -1)
+			tasklet_schedule(&host->fifo_tasklet);
+
 		spin_unlock_bh(&host->lock);
 
 		return;
@@ -1021,7 +1077,6 @@
 	
 	spin_lock(&host->lock);
 	
-	WARN_ON(!host->mrq);
 	if (!host->mrq)
 		goto end;
 	
@@ -1046,7 +1101,6 @@
 	
 	spin_lock(&host->lock);
 	
-	WARN_ON(!host->mrq);
 	if (!host->mrq)
 		goto end;
 	
@@ -1126,6 +1180,8 @@
 	 */
 	if (isr == 0xff || isr == 0x00)
 		return IRQ_NONE;
+	
+	host->isr |= isr;
 
 	/*
 	 * Schedule tasklets as needed.
@@ -1133,7 +1189,7 @@
 	if (isr & WBSD_INT_CARD)
 		tasklet_schedule(&host->card_tasklet);
 	if (isr & WBSD_INT_FIFO_THRE)
-		tasklet_hi_schedule(&host->fifo_tasklet);
+		tasklet_schedule(&host->fifo_tasklet);
 	if (isr & WBSD_INT_CRC)
 		tasklet_hi_schedule(&host->crc_tasklet);
 	if (isr & WBSD_INT_TIMEOUT)
@@ -1392,8 +1448,8 @@
 	 * Maximum number of segments. Worst case is one sector per segment
 	 * so this will be 64kB/512.
 	 */
-	mmc->max_hw_segs = NR_SG;
-	mmc->max_phys_segs = NR_SG;
+	mmc->max_hw_segs = 128;
+	mmc->max_phys_segs = 128;
 	
 	/*
 	 * Maximum number of sectors in one transfer. Also limited by 64kB
@@ -1590,6 +1646,7 @@
 
 MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION("Winbond W83L51xD SD/MMC card interface driver");
+MODULE_VERSION(DRIVER_VERSION);
 
 MODULE_PARM_DESC(io, "I/O base to allocate. Must be 8 byte aligned. (default 0x248)");
 MODULE_PARM_DESC(irq, "IRQ to allocate. (default 6)");
Index: linux/drivers/mmc/wbsd.h
===================================================================
--- linux/drivers/mmc/wbsd.h	(revision 105)
+++ linux/drivers/mmc/wbsd.h	(working copy)
@@ -1,7 +1,7 @@
 /*
- *  linux/drivers/mmc/wbsd.h
+ *  linux/drivers/mmc/wbsd.h - Winbond W83L51xD SD/MMC driver
  *
- *  Copyright (C) 2004 Pierre Ossman, All Rights Reserved.
+ *  Copyright (C) 2004-2005 Pierre Ossman, All Rights Reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
@@ -119,6 +119,8 @@
 #define WBSD_FIFOEN_FULL	0x10
 #define WBSD_FIFO_THREMASK	0x0F
 
+#define WBSD_BLOCK_READ		0x80
+#define WBSD_BLOCK_WRITE	0x40
 #define WBSD_BUSY		0x20
 #define WBSD_CARDTRAFFIC	0x04
 #define WBSD_SENDCMD		0x02
@@ -132,9 +134,6 @@
 #define WBSD_CRC_FAIL		0x0B /* S101E (01011) */
 
 
-/* 64kB / 512 */
-#define NR_SG			128
-
 struct wbsd_host
 {
 	struct mmc_host*	mmc;		/* MMC structure */
@@ -143,9 +142,11 @@
 
 	struct mmc_request*	mrq;		/* Current request */
 	
-	struct scatterlist	sg[NR_SG];	/* SG list */
+	u8			isr;		/* Accumulated ISR */
+	
 	struct scatterlist*	cur_sg;		/* Current SG entry */
 	unsigned int		num_sg;		/* Number of entries left */
+	void*			mapped_sg;	/* vaddr of mapped sg */
 	
 	unsigned int		offset;		/* Offset into current entry */
 	unsigned int		remain;		/* Data left in curren entry */

Reply via email to