[PATCH 3/3] drivers: mtd: spinand: Add qspi spansion flash controller
From: Sourav Poddar
Date: Wed Jun 26 2013 - 03:42:45 EST
The patch adds support for spansion s25fl256s spi flash controller.
Currently, the patch supports only SPI based transaction.
As, the qspi to which flash is attached supports memory mapped interface,
support will be added in future for memory mapped transactions also.
This driver gets attached to the generic spinand mtd framework proposed in the
first patch of the series.
Signed-off-by: Sourav Poddar <sourav.poddar@xxxxxx>
---
drivers/mtd/spinand/Kconfig | 7 +
drivers/mtd/spinand/Makefile | 2 +-
drivers/mtd/spinand/ti-qspi-flash.c | 373 +++++++++++++++++++++++++++++++++++
3 files changed, 381 insertions(+), 1 deletions(-)
create mode 100644 drivers/mtd/spinand/ti-qspi-flash.c
diff --git a/drivers/mtd/spinand/Kconfig b/drivers/mtd/spinand/Kconfig
index 38c739f..1342de3 100644
--- a/drivers/mtd/spinand/Kconfig
+++ b/drivers/mtd/spinand/Kconfig
@@ -16,6 +16,13 @@ config MTD_SPINAND_ONDIEECC
help
Internel ECC
+config MTD_S25FL256S
+ tristate "Support spansion memory mapped SPI Flash chips"
+ depends on SPI_MASTER
+ help
+ This enables access to spansion QSPI flash chips, which used
+ memory mapped interface used for program and data storage.
+
config MTD_SPINAND_SWECC
bool "Use software ECC"
depends on MTD_NAND
diff --git a/drivers/mtd/spinand/Makefile b/drivers/mtd/spinand/Makefile
index 355e726..8ad0dd5 100644
--- a/drivers/mtd/spinand/Makefile
+++ b/drivers/mtd/spinand/Makefile
@@ -5,6 +5,6 @@
# Core functionality.
obj-$(CONFIG_MTD_SPINAND) += spinand.o
-spinand-objs := spinand_mtd.o spinand_lld.o
+spinand-objs := spinand_mtd.o spinand_lld.o ti-qspi-flash.o
diff --git a/drivers/mtd/spinand/ti-qspi-flash.c b/drivers/mtd/spinand/ti-qspi-flash.c
new file mode 100644
index 0000000..dfa6235
--- /dev/null
+++ b/drivers/mtd/spinand/ti-qspi-flash.c
@@ -0,0 +1,373 @@
+/*
+ * MTD SPI driver for spansion s25fl256s (and similar) serial flash chips
+ *
+ * Author: Sourav Poddar, sourav.poddar@xxxxxx
+ *
+ * Copyright (c) 2013, Texas Instruments.
+ *
+ * This code 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.
+ *
+*/
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/math64.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/mod_devicetable.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/of_platform.h>
+
+#include <linux/spi/spi.h>
+#include <linux/mtd/spinand.h>
+
+#define CMD_OPCODE_RDSR 0x05 /* Read status register */
+#define CMD_OPCODE_FAST_READ 0x0b /* Fast Read */
+#define MAX_READY_WAIT_JIFFIES (40 * HZ) /* M25P16 specs 40s max chip erase */
+
+#define SR_WIP 1 /* Write in progress */
+#define SR_WEL 2 /* Write enable latch */
+
+static u16 addr_width;
+bool fast_read;
+
+static struct nand_ecclayout spinand_oob_0 = {
+ .eccbytes = 0,
+ .eccpos = {},
+ .oobavail = 0,
+ .oobfree = {
+ {.offset = 0,
+ .length = 0}, }
+};
+
+/*
+ * Read the status register, returning its value in the location
+ * Return the status register value.
+ * Returns negative if error occurred.
+*/
+static int read_sr(struct spi_device *spi_nand)
+{
+ ssize_t retval;
+ u8 val;
+ u8 code = CMD_OPCODE_RDSR;
+
+ retval = spi_write_then_read(spi_nand, &code, 1, &val, 1);
+
+ if (retval < 0) {
+ dev_info(&spi_nand->dev, "error %d reading SR\n",
+ (int) retval);
+ return retval;
+ }
+
+ return val;
+}
+
+/*
+ * Set write enable latch with Write Enable command.
+ * Returns negative if error occurred.
+*/
+static inline int write_enable(struct spi_device *spi_nand)
+{
+ u8 code = CMD_WR_ENABLE;
+
+ return spi_write_then_read(spi_nand, &code, 1, NULL, 0);
+}
+
+/*
+ * Send write disble instruction to the chip.
+*/
+static inline int write_disable(struct spi_device *spi_nand)
+{
+ u8 code = CMD_WR_DISABLE;
+
+ return spi_write_then_read(spi_nand, &code, 1, NULL, 0);
+}
+
+/*
+ * Service routine to read status register until ready, or timeout occurs.
+ * Returns non-zero if error.
+*/
+static int wait_till_ready(struct spi_device *spi_nand)
+{
+ unsigned long deadline;
+ int sr;
+
+ deadline = jiffies + MAX_READY_WAIT_JIFFIES;
+
+ do {
+ sr = read_sr(spi_nand);
+ if (sr < 0)
+ return -1;
+ else if (!(sr & SR_WIP))
+ break;
+
+ cond_resched();
+ } while (!time_after_eq(jiffies, deadline));
+
+ if ((sr & SR_WIP) == 0)
+ return 0;
+
+ return -1;
+}
+
+static inline int spinand_read_id(struct spi_device *spi_nand, u8 *id)
+{
+ u8 code = CMD_READ_ID;
+
+ return spi_write_then_read(spi_nand, &code, 1, id, sizeof(id));
+}
+
+static void s25fl_addr2cmd(struct spi_device *spi_nand,
+ unsigned int addr, u8 *cmd)
+{
+ /* opcode is in cmd[0] */
+ cmd[1] = addr >> (addr_width * 8 - 8);
+ cmd[2] = addr >> (addr_width * 8 - 16);
+ cmd[3] = addr >> (addr_width * 8 - 24);
+}
+
+static int s25fl_cmdsz(struct spi_device *spi_nand)
+{
+ return 1 + addr_width;
+}
+
+static int spinand_erase_block(struct spi_device *spi_nand,
+ struct spinand_info *info, u16 block_id)
+{
+ unsigned int offset;
+ u8 cmd[4];
+ uint8_t opcode;
+
+ offset = block_id * info->block_size;
+
+ /* Wait until finished previous write command. */
+ if (wait_till_ready(spi_nand))
+ return 1;
+
+ /* Send write enable, then erase commands. */
+ write_enable(spi_nand);
+
+ /* Set up command buffer. */
+ opcode = CMD_ERASE_BLK;
+ cmd[0] = opcode;
+ s25fl_addr2cmd(spi_nand, offset, cmd);
+
+ spi_write(spi_nand, cmd, s25fl_cmdsz(spi_nand));
+
+ return 0;
+}
+
+static int spinand_read_page(struct spi_device *spi_nand,
+ struct spinand_info *info, u16 page_id, u16 offset, u16 len, u8 *rbuf)
+{
+ struct spi_transfer t[2];
+ struct spi_message m;
+ uint8_t opcode;
+ u8 cmd[4];
+
+ spi_message_init(&m);
+ memset(t, 0, sizeof(t));
+
+ t[0].tx_buf = cmd;
+ t[0].len = s25fl_cmdsz(spi_nand);
+ spi_message_add_tail(&t[0], &m);
+
+ t[1].rx_buf = rbuf;
+ t[1].len = len;
+ spi_message_add_tail(&t[1], &m);
+
+ /* Wait till previous write/erase is done. */
+ if (wait_till_ready(spi_nand))
+ return 1;
+
+ /* Set up the write data buffer. */
+ opcode = fast_read ? CMD_OPCODE_FAST_READ : CMD_READ_RDM;
+ cmd[0] = opcode;
+
+ s25fl_addr2cmd(spi_nand, offset, cmd);
+
+ spi_sync(spi_nand, &m);
+
+ return 0;
+}
+
+static int spinand_program_page(struct spi_device *spi_nand,
+ struct spinand_info *info, u16 page_id, u16 offset, u16 len, u8 *wbuf)
+{
+ struct spi_transfer t[2];
+ struct spi_message m;
+ u8 cmd[4];
+
+ pr_debug("%s: %s to 0x%08x, len %zd\n", dev_name(&spi_nand->dev),
+ __func__, (u32)offset, len);
+
+ spi_message_init(&m);
+ memset(t, 0, sizeof(t));
+
+ t[0].tx_buf = cmd;
+ t[0].len = 4;
+ spi_message_add_tail(&t[0], &m);
+
+ t[1].tx_buf = wbuf;
+ t[0].len = len;
+ spi_message_add_tail(&t[1], &m);
+
+ write_enable(spi_nand);
+
+ /* Wait until finished previous write command. */
+ if (wait_till_ready(spi_nand))
+ return 1;
+
+ /* Set up the opcode in the write buffer. */
+ cmd[0] = CMD_PROG_PAGE_CLRCACHE;
+ s25fl_addr2cmd(spi_nand, offset, cmd);
+
+ spi_sync(spi_nand, &m);
+
+ return 0;
+}
+
+static int spinand_get_info(struct spi_device *spi_nand,
+ struct spinand_info *info, u8 *id)
+{
+ if (id[0] == 0x01 && id[1] == 0x02) {
+ info->mid = id[0];
+ info->did = id[1];
+ info->name = "S25FL256S";
+ info->nand_size = (1024 * 32 * 1024);
+ info->page_size = 256;
+ info->page_main_size = 256;
+ info->page_spare_size = info->page_size - info->page_main_size;
+ info->block_size = (1024 * 64);
+ info->page_num_per_block = info->block_size / info->page_size;
+ info->block_main_size = info->page_main_size *
+ info->page_num_per_block;
+ info->usable_size = (1024 * 30 * 1024);
+ info->block_num_per_chip = info->nand_size / info->block_size;
+ info->block_shift = 16;
+ info->block_mask = info->block_size - 1;
+ info->page_shift = 8;
+ info->page_mask = info->page_size - 1;
+ info->ecclayout = &spinand_oob_0;
+ }
+ return 0;
+}
+
+static int spinand_probe(struct spi_device *spi)
+{
+ ssize_t retval;
+ struct mtd_info *mtd;
+ struct spinand_chip *chip;
+ struct spinand_info *info;
+ struct mtd_part_parser_data ppdata;
+ struct device_node __maybe_unused *np = spi->dev.of_node;
+
+ u8 id[2] = {0};
+
+ retval = spinand_read_id(spi, (u8 *)&id);
+ if (id[0] == 0 && id[1] == 0) {
+ pr_err(KERN_ERR "SPINAND: read id error! 0x%02x, 0x%02x!\n",
+ id[0], id[1]);
+ return 0;
+ }
+
+ info = kzalloc(sizeof(struct spinand_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ if (np && of_property_read_bool(np, "s25fl,fast-read"))
+ fast_read = true;
+ if (np && of_property_read_bool(np, "s25fl,three-byte"))
+ addr_width = 3;
+ else
+ addr_width = 4;
+
+ ppdata.of_node = spi->dev.of_node;
+
+ retval = spinand_get_info(spi, info, (u8 *)&id);
+
+ chip = kzalloc(sizeof(struct spinand_chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->spi_nand = spi;
+ chip->info = info;
+ chip->read_id = spinand_read_id;
+ chip->read_page = spinand_read_page;
+ chip->program_page = spinand_program_page;
+ chip->erase_block = spinand_erase_block;
+ chip->buf = kzalloc(info->page_size, GFP_KERNEL);
+ if (!chip->buf)
+ return -ENOMEM;
+
+ chip->oobbuf = kzalloc(info->ecclayout->oobavail, GFP_KERNEL);
+ if (!chip->oobbuf)
+ return -ENOMEM;
+
+ mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
+ if (!mtd)
+ return -ENOMEM;
+
+ dev_set_drvdata(&spi->dev, mtd);
+
+ mtd->priv = chip;
+
+ retval = spinand_mtd(mtd);
+
+ return mtd_device_parse_register(mtd, NULL, &ppdata,
+ NULL, 1);
+}
+
+/*
+ *spinand_remove--Remove the device driver
+ * @spi: the spi device.
+*/
+static int spinand_remove(struct spi_device *spi)
+{
+ struct mtd_info *mtd;
+ struct spinand_chip *chip;
+
+ mtd = dev_get_drvdata(&spi->dev);
+
+ mtd_device_unregister(mtd);
+
+ chip = mtd->priv;
+
+ kfree(chip->info);
+ kfree(chip->buf);
+ kfree(chip->oobbuf);
+ kfree(chip);
+ kfree(mtd);
+
+ return 0;
+}
+
+static const struct of_device_id s25fl256s_dt_ids[] = {
+ { .compatible = "ti, s25fl256s"},
+ { /* sentinel */ },
+};
+
+static struct spi_driver spinand_driver = {
+ .driver = {
+ .name = "s25fl256s",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ .of_match_table = s25fl256s_dt_ids,
+ },
+ .probe = spinand_probe,
+ .remove = spinand_remove,
+};
+module_spi_driver(spinand_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sourav Poddar");
+MODULE_DESCRIPTION("MTD SPI driver for spansion flash chips");
--
1.7.1
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/