diff mbox

mtd: sst25l: fix reads with broken spi-masters

Message ID 0D753D10438DA54287A00B027084269764CDF4146A@AUSP01VMBX24.collaborationhost.net (mailing list archive)
State Superseded, archived
Headers show

Commit Message

Hartley Sweeten Jan. 10, 2011, 9:21 p.m. UTC
None
diff mbox

Patch

diff --git a/drivers/mtd/devices/sst25l.c b/drivers/mtd/devices/sst25l.c
index c163e61..864bd20 100644
--- a/drivers/mtd/devices/sst25l.c
+++ b/drivers/mtd/devices/sst25l.c
@@ -54,6 +54,10 @@  struct sst25l_flash {
 	struct mtd_info		mtd;
 
 	int 			partitioned;
+
+	bool			use_fifo;
+	int			fifo_size;
+	unsigned char		*bounce_buf;
 };
 
 struct flash_info {
@@ -269,6 +273,79 @@  static int sst25l_read(struct mtd_info *mtd, loff_t from, size_t len,
 	return 0;
 }
 
+static int sst25l_read_use_fifo(struct mtd_info *mtd, loff_t from, size_t len,
+				size_t *retlen, unsigned char *buf)
+{
+	struct sst25l_flash *flash = to_sst25l_flash(mtd);
+	struct spi_message message;
+	struct spi_transfer transfer;
+	loff_t addr;
+	size_t count;
+	int xfer_size = flash->fifo_size - 4;
+	int ret, i;
+
+	/* Sanity checking */
+	if (len == 0)
+		return 0;
+
+	if (from + len > flash->mtd.size)
+		return -EINVAL;
+
+	if (retlen)
+		*retlen = 0;
+
+	mutex_lock(&flash->lock);
+
+	/* Wait for previous write/erase to complete */
+	ret = sst25l_wait_till_ready(flash);
+	if (ret) {
+		mutex_unlock(&flash->lock);
+		return ret;
+	}
+
+	for (addr = from, count = 0; addr < from + len; addr += xfer_size) {
+		spi_message_init(&message);
+		memset(&transfer, 0, sizeof(transfer));
+
+		/* Fill the bounce buffer with known data */
+		memset(flash->bounce_buf, 0xff, flash->fifo_size);
+
+		/*
+		 * Send the 4 byte Read command and read back as much
+		 * data as possible (fifo_size - 4) into the bounce buffer.
+		 */
+		flash->bounce_buf[0] = SST25L_CMD_READ;
+		flash->bounce_buf[1] = addr >> 16;
+		flash->bounce_buf[2] = addr >> 8;
+		flash->bounce_buf[3] = addr;
+
+		transfer.tx_buf = flash->bounce_buf;
+		transfer.rx_buf = flash->bounce_buf;
+		transfer.len = flash->fifo_size;
+		spi_message_add_tail(&transfer, &message);
+		ret = spi_sync(flash->spi, &message);
+		if (ret < 0) {
+			mutex_unlock(&flash->lock);
+			return ret;
+		}
+
+		/*
+		 * Copy the read data from the bounce buffer to the
+		 * passed buffer.  Don't overflow the passed buffer.
+		 */
+		for (i = 0; i < xfer_size; i++) {
+			if (count < len)
+				buf[count++] = flash->bounce_buf[4+i];
+		}
+	}
+
+	if (retlen)
+		*retlen += count;
+
+	mutex_unlock(&flash->lock);
+	return 0;
+}
+
 static int sst25l_write(struct mtd_info *mtd, loff_t to, size_t len,
 			size_t *retlen, const unsigned char *buf)
 {
@@ -400,13 +477,25 @@  static int __devinit sst25l_probe(struct spi_device *spi)
 	else
 		flash->mtd.name = dev_name(&spi->dev);
 
+	flash->use_fifo		= data->use_fifo;
+	flash->fifo_size	= data->fifo_size;
+
 	flash->mtd.type		= MTD_NORFLASH;
 	flash->mtd.flags	= MTD_CAP_NORFLASH;
 	flash->mtd.erasesize	= flash_info->erase_size;
 	flash->mtd.writesize	= flash_info->page_size;
 	flash->mtd.size		= flash_info->page_size * flash_info->nr_pages;
 	flash->mtd.erase	= sst25l_erase;
-	flash->mtd.read		= sst25l_read;
+	if (flash->use_fifo) {
+		flash->bounce_buf = kzalloc(flash->fifo_size, GFP_KERNEL);
+		if (!flash->bounce_buf) {
+			ret = -ENOMEM;
+			goto err;
+		}
+		flash->mtd.read = sst25l_read_use_fifo;
+	} else {
+		flash->mtd.read	= sst25l_read;
+	}
 	flash->mtd.write 	= sst25l_write;
 
 	dev_info(&spi->dev, "%s (%lld KiB)\n", flash_info->name,
@@ -461,12 +550,17 @@  static int __devinit sst25l_probe(struct spi_device *spi)
 
 	ret = add_mtd_device(&flash->mtd);
 	if (ret == 1) {
-		kfree(flash);
-		dev_set_drvdata(&spi->dev, NULL);
-		return -ENODEV;
+		ret = -ENODEV;
+		goto err;
 	}
 
 	return 0;
+
+err:
+	kfree(flash->bounce_buf);
+	kfree(flash);
+	dev_set_drvdata(&spi->dev, NULL);
+	return ret;
 }
 
 static int __exit sst25l_remove(struct spi_device *spi)
@@ -478,8 +572,10 @@  static int __exit sst25l_remove(struct spi_device *spi)
 		ret = del_mtd_partitions(&flash->mtd);
 	else
 		ret = del_mtd_device(&flash->mtd);
-	if (ret == 0)
+	if (ret == 0) {
+		kfree(flash->bounce_buf);
 		kfree(flash);
+	}
 	return ret;
 }
 
diff --git a/include/linux/spi/flash.h b/include/linux/spi/flash.h
index 3f22932..8524221 100644
--- a/include/linux/spi/flash.h
+++ b/include/linux/spi/flash.h
@@ -10,6 +10,10 @@  struct mtd_partition;
  * @nr_parts: number of mtd_partitions for static partitoning
  * @type: optional flash device type (e.g. m25p80 vs m25p64), for use
  *	with chips that can't be queried for JEDEC or other IDs
+ * @use_fifo: optional flag to read/write data using the SPI master's
+ *	fifo limitations (for broken SPI masters that deassert the
+ *	chip select during multi-part transfers)
+ * @fifo_size: optional fifo size used with broken SPI masters
  *
  * Board init code (in arch/.../mach-xxx/board-yyy.c files) can
  * provide information about SPI flash parts (such as DataFlash) to
@@ -25,6 +29,9 @@  struct flash_platform_data {
 
 	char		*type;
 
+	bool		use_fifo;
+	int		fifo_size;
+
 	/* we'll likely add more ... use JEDEC IDs, etc */
 };