2013-06-26 07:42:31

by Sourav Poddar

[permalink] [raw]
Subject: [PATCH 0/3] spi/mtd generic framework,ti qspi controller and spansion driver

This patch series add support for the generic spi based flash
framework(spinand_mtd), which can be used used by any spi based flash device to
attach itself to mtd framework.

The first patch of this series includes both the generic framework and the
the micron device(spinand_lld) making use of the framework.
I picked the first patch as a standalone patch. Can split the generic and
the lld part based on community suggestions.

The second patch is the ti qspi controller driver.
The third patch is the spansion s25fl256s driver, making use of the the
generic spinand_mtd frameowrk.

Test info:
Tested the generic framework(spinand_mtd.c) along with patch(2&3) on my dra7xx board
for write/erase/read using nand utils.

Compile tested(spinand_lld.c).

Mona Anonuevo (1):
drivers: mtd: spinand: Add generic spinand frameowrk and micron
driver.

Sourav Poddar (2):
drivers: spi: Add qspi flash controller
drivers: mtd: spinand: Add qspi spansion flash controller

drivers/mtd/Kconfig | 2 +
drivers/mtd/Makefile | 2 +
drivers/mtd/spinand/Kconfig | 31 ++
drivers/mtd/spinand/Makefile | 10 +
drivers/mtd/spinand/spinand_lld.c | 776 +++++++++++++++++++++++++++++++++++
drivers/mtd/spinand/spinand_mtd.c | 690 +++++++++++++++++++++++++++++++
drivers/mtd/spinand/ti-qspi-flash.c | 373 +++++++++++++++++
drivers/spi/Kconfig | 6 +
drivers/spi/Makefile | 1 +
drivers/spi/ti-qspi.c | 352 ++++++++++++++++
include/linux/mtd/spinand.h | 155 +++++++
11 files changed, 2398 insertions(+), 0 deletions(-)
create mode 100644 drivers/mtd/spinand/Kconfig
create mode 100644 drivers/mtd/spinand/Makefile
create mode 100644 drivers/mtd/spinand/spinand_lld.c
create mode 100644 drivers/mtd/spinand/spinand_mtd.c
create mode 100644 drivers/mtd/spinand/ti-qspi-flash.c
create mode 100644 drivers/spi/ti-qspi.c
create mode 100644 include/linux/mtd/spinand.h


2013-06-26 07:42:41

by Sourav Poddar

[permalink] [raw]
Subject: [PATCH 3/3] drivers: mtd: spinand: Add qspi spansion flash controller

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 <[email protected]>
---
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, [email protected]
+ *
+ * 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

2013-06-26 07:42:57

by Sourav Poddar

[permalink] [raw]
Subject: [PATCH 1/3] drivers: mtd: spinand: Add generic spinand frameowrk and micron driver.

From: Mona Anonuevo <[email protected]>

This patch adds support for a generic spinand framework(spinand_mtd.c).
This frameowrk can be used for other spi based flash devices also. The idea
is to have a common model under drivers/mtd, as also present for other no spi
devices(there is a generic framework and device part simply attaches itself to it.)

The generic frework will be used later by me for a SPI based spansion S25FL256 device.
The patch also contains a micron driver attaching itself to generic framework.

Signed-off-by: Mona Anonuevo <[email protected]>
Signed-off-by: Tuan Nguyen <[email protected]>
Signed-off-by: Sourav Poddar <[email protected]>
----
[I picked this as a standalone patch, can split it into generic and device part
based on community feedback.]

drivers/mtd/Kconfig | 2 +
drivers/mtd/Makefile | 2 +
drivers/mtd/spinand/Kconfig | 24 ++
drivers/mtd/spinand/Makefile | 10 +
drivers/mtd/spinand/spinand_lld.c | 776 +++++++++++++++++++++++++++++++++++++
drivers/mtd/spinand/spinand_mtd.c | 690 +++++++++++++++++++++++++++++++++
include/linux/mtd/spinand.h | 155 ++++++++
7 files changed, 1659 insertions(+), 0 deletions(-)
create mode 100644 drivers/mtd/spinand/Kconfig
create mode 100644 drivers/mtd/spinand/Makefile
create mode 100644 drivers/mtd/spinand/spinand_lld.c
create mode 100644 drivers/mtd/spinand/spinand_mtd.c
create mode 100644 include/linux/mtd/spinand.h

diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index 5fab4e6..c9e6c60 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -318,6 +318,8 @@ source "drivers/mtd/nand/Kconfig"

source "drivers/mtd/onenand/Kconfig"

+source "drivers/mtd/spinand/Kconfig"
+
source "drivers/mtd/lpddr/Kconfig"

source "drivers/mtd/ubi/Kconfig"
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index 4cfb31e..cce68db 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -32,4 +32,6 @@ inftl-objs := inftlcore.o inftlmount.o

obj-y += chips/ lpddr/ maps/ devices/ nand/ onenand/ tests/

+obj-y += spinand/
+
obj-$(CONFIG_MTD_UBI) += ubi/
diff --git a/drivers/mtd/spinand/Kconfig b/drivers/mtd/spinand/Kconfig
new file mode 100644
index 0000000..38c739f
--- /dev/null
+++ b/drivers/mtd/spinand/Kconfig
@@ -0,0 +1,24 @@
+#
+# linux/drivers/mtd/spinand/Kconfig
+#
+
+menuconfig MTD_SPINAND
+ tristate "SPINAND Device Support"
+ depends on MTD
+ help
+ This enables support for accessing Micron SPI NAND flash
+ devices.
+
+if MTD_SPINAND
+
+config MTD_SPINAND_ONDIEECC
+ bool "Use SPINAND internal ECC"
+ help
+ Internel ECC
+
+config MTD_SPINAND_SWECC
+ bool "Use software ECC"
+ depends on MTD_NAND
+ help
+ software ECC
+endif
diff --git a/drivers/mtd/spinand/Makefile b/drivers/mtd/spinand/Makefile
new file mode 100644
index 0000000..355e726
--- /dev/null
+++ b/drivers/mtd/spinand/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for the SPI NAND MTD
+#
+
+# Core functionality.
+obj-$(CONFIG_MTD_SPINAND) += spinand.o
+
+spinand-objs := spinand_mtd.o spinand_lld.o
+
+
diff --git a/drivers/mtd/spinand/spinand_lld.c b/drivers/mtd/spinand/spinand_lld.c
new file mode 100644
index 0000000..9f53737
--- /dev/null
+++ b/drivers/mtd/spinand/spinand_lld.c
@@ -0,0 +1,776 @@
+/*
+spinand_lld.c
+
+Copyright (c) 2009-2010 Micron Technology, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+*/
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/math64.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/spinand.h>
+
+#include <linux/spi/spi.h>
+#include <linux/spi/flash.h>
+
+#define mu_spi_nand_driver_version "Beagle-MTD_01.00_Linux2.6.33_20100507"
+#define SPI_NAND_MICRON_DRIVER_KEY 0x1233567
+
+/****************************************************************************/
+
+/**
+ OOB area specification layout: Total 32 available free bytes.
+*/
+static struct nand_ecclayout spinand_oob_64 = {
+ .eccbytes = 24,
+ .eccpos = {
+ 1, 2, 3, 4, 5, 6,
+ 17, 18, 19, 20, 21, 22,
+ 33, 34, 35, 36, 37, 38,
+ 49, 50, 51, 52, 53, 54, },
+ .oobavail = 32,
+ .oobfree = {
+ {.offset = 8,
+ .length = 8},
+ {.offset = 24,
+ .length = 8},
+ {.offset = 40,
+ .length = 8},
+ {.offset = 56,
+ .length = 8}, }
+};
+/**
+ * spinand_cmd - to process a command to send to the SPI Nand
+ *
+ * Description:
+ * Set up the command buffer to send to the SPI controller.
+ * The command buffer has to initized to 0
+ */
+int spinand_cmd(struct spi_device *spi, struct spinand_cmd *cmd)
+{
+ int ret;
+ struct spi_message message;
+ struct spi_transfer x[4];
+ u8 dummy = 0xff;
+
+ spi_message_init(&message);
+ memset(x, 0, sizeof(x));
+
+ x[0].len = 1;
+ x[0].tx_buf = &cmd->cmd;
+ spi_message_add_tail(&x[0], &message);
+
+ if (cmd->n_addr) {
+ x[1].len = cmd->n_addr;
+ x[1].tx_buf = cmd->addr;
+ spi_message_add_tail(&x[1], &message);
+ }
+
+ if (cmd->n_dummy) {
+ x[2].len = cmd->n_dummy;
+ x[2].tx_buf = &dummy;
+ spi_message_add_tail(&x[2], &message);
+ }
+
+ if (cmd->n_tx) {
+ x[3].len = cmd->n_tx;
+ x[3].tx_buf = cmd->tx_buf;
+ spi_message_add_tail(&x[3], &message);
+ }
+
+ if (cmd->n_rx) {
+ x[3].len = cmd->n_rx;
+ x[3].rx_buf = cmd->rx_buf;
+ spi_message_add_tail(&x[3], &message);
+ }
+
+ ret = spi_sync(spi, &message);
+
+ return ret;
+}
+
+/**
+ * spinand_reset- send reset command "0xff" to the Nand device
+ *
+ * Description:
+ * Reset the SPI Nand with the reset command 0xff
+*/
+static int spinand_reset(struct spi_device *spi_nand)
+{
+ struct spinand_cmd cmd = {0};
+
+ cmd.cmd = CMD_RESET;
+
+ return spinand_cmd(spi_nand, &cmd);
+}
+
+/**
+ * spinand_read_id- Read SPI Nand ID
+ *
+ * Description:
+ * Read ID: read two ID bytes from the SPI Nand device
+*/
+static int spinand_read_id(struct spi_device *spi_nand, u8 *id)
+{
+ struct spinand_cmd cmd = {0};
+ ssize_t retval;
+
+ cmd.cmd = CMD_READ_ID;
+ cmd.n_dummy = 1;
+ cmd.n_rx = 2;
+ cmd.rx_buf = id;
+
+ retval = spinand_cmd(spi_nand, &cmd);
+
+ if (retval != 0) {
+ dev_err(&spi_nand->dev, "error %d reading id\n",
+ (int) retval);
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * spinand_lock_block- send write register 0x1f command to the Nand device
+ *
+ * Description:
+ * After power up, all the Nand blocks are locked. This function allows
+ * one to unlock the blocks, and so it can be wriiten or erased.
+*/
+static int spinand_lock_block(struct spi_device *spi_nand,
+ struct spinand_info *info, u8 lock)
+{
+ struct spinand_cmd cmd = {0};
+ ssize_t retval;
+
+ cmd.cmd = CMD_WRITE_REG;
+ cmd.n_addr = 1;
+ cmd.addr[0] = REG_BLOCK_LOCK;
+ cmd.n_tx = 1;
+ cmd.tx_buf = &lock;
+
+ retval = spinand_cmd(spi_nand, &cmd);
+
+ if (retval != 0) {
+ dev_err(&spi_nand->dev, "error %d lock block\n",
+ (int) retval);
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * spinand_read_status- send command 0xf to the SPI Nand status register
+ *
+ * Description:
+ * After read, write, or erase, the Nand device is expected to
+ set the busy status.
+ * This function is to allow reading the status of the command:
+ read, write, and erase.
+ * Once the status turns to be ready, the other status bits also
+ are valid status bits.
+*/
+static int spinand_read_status(struct spi_device *spi_nand,
+ struct spinand_info *info, u8 *status)
+{
+ struct spinand_cmd cmd = {0};
+ ssize_t retval;
+
+ cmd.cmd = CMD_READ_REG;
+ cmd.n_addr = 1;
+ cmd.addr[0] = REG_STATUS;
+ cmd.n_rx = 1;
+ cmd.rx_buf = status;
+
+ retval = spinand_cmd(spi_nand, &cmd);
+
+ if (retval != 0) {
+ dev_err(&spi_nand->dev, "error %d reading status register\n",
+ (int) retval);
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * spinand_get_otp- send command 0xf to read the SPI Nand OTP register
+ *
+ * Description:
+ * There is one bit( bit 0x10 ) to set or to clear the internal ECC.
+ * Enable chip internal ECC, set the bit to 1
+ * Disable chip internal ECC, clear the bit to 0
+ */
+static int spinand_get_otp(struct spi_device *spi_nand,
+ struct spinand_info *info, u8 *otp)
+{
+ struct spinand_cmd cmd = {0};
+ ssize_t retval;
+
+ cmd.cmd = CMD_READ_REG;
+ cmd.n_addr = 1;
+ cmd.addr[0] = REG_OTP;
+ cmd.n_rx = 1;
+ cmd.rx_buf = otp;
+
+ retval = spinand_cmd(spi_nand, &cmd);
+
+ if (retval != 0) {
+ dev_err(&spi_nand->dev, "error %d get otp\n",
+ (int) retval);
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * spinand_set_otp- send command 0x1f to write the SPI Nand OTP register
+ *
+ * Description:
+ * There is one bit( bit 0x10 ) to set or to clear the internal ECC.
+ * Enable chip internal ECC, set the bit to 1
+ * Disable chip internal ECC, clear the bit to 0
+*/
+static int spinand_set_otp(struct spi_device *spi_nand,
+ struct spinand_info *info, u8 *otp)
+{
+ struct spinand_cmd cmd = {0};
+ ssize_t retval;
+
+ cmd.cmd = CMD_WRITE_REG;
+ cmd.n_addr = 1;
+ cmd.addr[0] = REG_OTP;
+ cmd.n_tx = 1;
+ cmd.tx_buf = otp;
+
+ retval = spinand_cmd(spi_nand, &cmd);
+
+ if (retval != 0) {
+ dev_err(&spi_nand->dev, "error %d set otp\n",
+ (int) retval);
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * sspinand_enable_ecc- send command 0x1f to write the SPI Nand OTP register
+ *
+ * Description:
+ * There is one bit( bit 0x10 ) to set or to clear the internal ECC.
+ * Enable chip internal ECC, set the bit to 1
+ * Disable chip internal ECC, clear the bit to 0
+*/
+#ifdef CONFIG_MTD_SPINAND_ONDIEECC
+static int spinand_enable_ecc(struct spi_device *spi_nand,
+ struct spinand_info *info)
+{
+ ssize_t retval;
+ u8 otp = 0;
+
+ retval = spinand_get_otp(spi_nand, info, &otp);
+
+ if ((otp & OTP_ECC_MASK) == OTP_ECC_MASK) {
+ return 0;
+ } else {
+ otp |= OTP_ECC_MASK;
+ retval = spinand_set_otp(spi_nand, info, &otp);
+ retval = spinand_get_otp(spi_nand, info, &otp);
+ return retval;
+ }
+}
+#else
+static int spinand_disable_ecc(struct spi_device *spi_nand,
+ struct spinand_info *info)
+{
+ ssize_t retval;
+ u8 otp = 0;
+
+ retval = spinand_get_otp(spi_nand, info, &otp);
+
+ if ((otp & OTP_ECC_MASK) == OTP_ECC_MASK) {
+ otp &= ~OTP_ECC_MASK;
+ retval = spinand_set_otp(spi_nand, info, &otp);
+ retval = spinand_get_otp(spi_nand, info, &otp);
+ return retval;
+ } else {
+ return 0;
+ }
+}
+#endif
+
+/**
+ * sspinand_write_enable- send command 0x06 to enable write or erase the Nand cells
+ *
+ * Description:
+ * Before write and erase the Nand cells, the write enable has to be set.
+ * After the write or erase, the write enable bit is automatically
+ cleared( status register bit 2 )
+ * Set the bit 2 of the status register has the same effect
+*/
+static int spinand_write_enable(struct spi_device *spi_nand,
+ struct spinand_info *info)
+{
+ struct spinand_cmd cmd = {0};
+
+ cmd.cmd = CMD_WR_ENABLE;
+
+ return spinand_cmd(spi_nand, &cmd);
+}
+
+static int spinand_read_page_to_cache(struct spi_device *spi_nand,
+ struct spinand_info *info, u16 page_id)
+{
+ struct spinand_cmd cmd = {0};
+ u16 row;
+
+ row = page_id;
+
+ cmd.cmd = CMD_READ;
+ cmd.n_addr = 3;
+ cmd.addr[1] = (u8)((row & 0xff00) >> 8);
+ cmd.addr[2] = (u8)(row & 0x00ff);
+
+ return spinand_cmd(spi_nand, &cmd);
+}
+
+/**
+ * spinand_read_from_cache- send command 0x03 to read out the data from the
+ cache register( 2112 bytes max )
+ *
+ * Description:
+ * The read can specify 1 to 2112 bytes of data read at the
+ coresponded locations.
+ * No tRd delay.
+*/
+static int spinand_read_from_cache(struct spi_device *spi_nand,
+ struct spinand_info *info, u16 byte_id, u16 len, u8 *rbuf)
+{
+ struct spinand_cmd cmd = {0};
+ u16 column;
+
+ column = byte_id;
+
+ cmd.cmd = CMD_READ_RDM;
+ cmd.n_addr = 2;
+ cmd.addr[0] = (u8)((column&0xff00)>>8);
+ cmd.addr[1] = (u8)(column&0x00ff);
+ cmd.n_dummy = 1;
+ cmd.n_rx = len;
+ cmd.rx_buf = rbuf;
+
+ return spinand_cmd(spi_nand, &cmd);
+}
+
+/**
+ * spinand_read_page-to read a page with:
+ * @page_id: the physical page number
+ * @offset: the location from 0 to 2111
+ * @len: number of bytes to read
+ * @rbuf: read buffer to hold @len bytes
+ *
+ * Description:
+ * The read icludes two commands to the Nand: 0x13 and 0x03 commands
+ * Poll to read status to wait for tRD time.
+ */
+static int spinand_read_page(struct spi_device *spi_nand,
+ struct spinand_info *info, u16 page_id, u16 offset,
+ u16 len, u8 *rbuf)
+{
+ ssize_t retval;
+ u8 status = 0;
+
+ retval = spinand_read_page_to_cache(spi_nand, info, page_id);
+
+ while (1) {
+ retval = spinand_read_status(spi_nand, info, &status);
+ if (retval < 0) {
+ dev_err(&spi_nand->dev, "error %d reading status register\n",
+ (int) retval);
+ return retval;
+ }
+
+ if ((status & STATUS_OIP_MASK) == STATUS_READY) {
+ if ((status & STATUS_ECC_MASK) == STATUS_ECC_ERROR) {
+ dev_err(&spi_nand->dev,
+ "ecc error, page=%d\n", page_id);
+ }
+ break;
+ }
+ }
+
+ retval = spinand_read_from_cache(spi_nand, info, offset, len, rbuf);
+ return 0;
+}
+
+/**
+ * spinand_program_data_to_cache--to write a page to cache with:
+ * @byte_id: the location to write to the cache
+ * @len: number of bytes to write
+ * @rbuf: read buffer to hold @len bytes
+ *
+ * Description:
+ * The write command used here is 0x84--indicating that the cache
+ is not cleared first.
+ * Since it is writing the data to cache, there is no tPROG time.
+ */
+static int spinand_program_data_to_cache(struct spi_device *spi_nand,
+ struct spinand_info *info, u16 byte_id, u16 len, u8 *wbuf)
+{
+ struct spinand_cmd cmd = {0};
+ u16 column;
+
+ column = byte_id;
+
+ cmd.cmd = CMD_PROG_PAGE_CLRCACHE;
+ cmd.n_addr = 2;
+ cmd.addr[0] = (u8)((column & 0xff00) >> 8);
+ cmd.addr[1] = (u8)(column & 0x00ff);
+ cmd.n_tx = len;
+ cmd.tx_buf = wbuf;
+
+ return spinand_cmd(spi_nand, &cmd);
+}
+
+/**
+ * spinand_program_execute--to write a page from cache to the Nand array with:
+ * @page_id: the physical page location to write the page.
+ *
+ * Description:
+ * The write command used here is 0x10--indicating the cache is
+ writing to the Nand array.
+ * Need to wait for tPROG time to finish the transaction.
+ */
+static int spinand_program_execute(struct spi_device *spi_nand,
+ struct spinand_info *info, u16 page_id)
+{
+ struct spinand_cmd cmd = {0};
+ u16 row;
+
+ row = page_id;
+
+ cmd.cmd = CMD_PROG_PAGE_EXC;
+ cmd.n_addr = 3;
+ cmd.addr[1] = (u8)((row & 0xff00) >> 8);
+ cmd.addr[2] = (u8)(row & 0x00ff);
+
+ return spinand_cmd(spi_nand, &cmd);
+}
+
+/**
+ * spinand_program_page--to write a page with:
+ * @page_id: the physical page location to write the page.
+ * @offset: the location from the cache starting from 0 to 2111
+ * @len: the number of bytes to write
+ * @wbuf: the buffer to hold the number of bytes
+ *
+ * Description:
+ * The commands used here are 0x06, 0x84, and 0x10--indicating that
+ the write enable is first
+ * sent, the write cache command, and the write execute command
+ * Poll to wait for the tPROG time to finish the transaction.
+ */
+static int spinand_program_page(struct spi_device *spi_nand,
+ struct spinand_info *info, u16 page_id, u16 offset,
+ u16 len, u8 *wbuf)
+{
+ ssize_t retval;
+ u8 status = 0;
+
+ retval = spinand_write_enable(spi_nand, info);
+
+ retval = spinand_program_data_to_cache(spi_nand, info, offset,
+ len, wbuf);
+
+ retval = spinand_program_execute(spi_nand, info, page_id);
+
+ while (1) {
+ retval = spinand_read_status(spi_nand, info, &status);
+ if (retval < 0) {
+ dev_err(&spi_nand->dev,
+ "error %d reading status register\n",
+ (int) retval);
+ return retval;
+ }
+
+ if ((status & STATUS_OIP_MASK) == STATUS_READY) {
+ if ((status & STATUS_P_FAIL_MASK) == STATUS_P_FAIL) {
+ dev_err(&spi_nand->dev,
+ "program error, page=%d\n", page_id);
+ return -1;
+ }
+ } else {
+ break;
+ }
+ }
+ return 0;
+}
+
+/**
+ * spinand_erase_block_erase--to erase a page with:
+ * @block_id: the physical block location to erase.
+ *
+ * Description:
+ * The command used here is 0xd8--indicating an erase
+command to erase one block--64 pages
+ * Need to wait for tERS.
+ */
+static int spinand_erase_block_erase(struct spi_device *spi_nand,
+ struct spinand_info *info, u16 block_id)
+{
+ struct spinand_cmd cmd = {0};
+ u16 row;
+
+ row = block_id << 6;
+ cmd.cmd = CMD_ERASE_BLK;
+ cmd.n_addr = 3;
+ cmd.addr[1] = (u8)((row & 0xff00) >> 8);
+ cmd.addr[2] = (u8)(row & 0x00ff);
+
+ return spinand_cmd(spi_nand, &cmd);
+}
+
+/**
+ * spinand_erase_block--to erase a page with:
+ * @block_id: the physical block location to erase.
+ *
+ * Description:
+ * The commands used here are 0x06 and 0xd8--indicating an erase
+ command to erase one block--64 pages
+ * It will first to enable the write enable bit ( 0x06 command ),
+ and then send the 0xd8 erase command
+ * Poll to wait for the tERS time to complete the tranaction.
+ */
+static int spinand_erase_block(struct spi_device *spi_nand,
+ struct spinand_info *info, u16 block_id)
+{
+ ssize_t retval;
+ u8 status = 0;
+
+ retval = spinand_write_enable(spi_nand, info);
+
+ retval = spinand_erase_block_erase(spi_nand, info, block_id);
+
+ while (1) {
+ retval = spinand_read_status(spi_nand, info, &status);
+ if (retval < 0) {
+ dev_err(&spi_nand->dev,
+ "error %d reading status register\n",
+ (int) retval);
+ return retval;
+ }
+
+ if ((status & STATUS_OIP_MASK) == STATUS_READY) {
+ if ((status & STATUS_E_FAIL_MASK) == STATUS_E_FAIL) {
+ dev_err(&spi_nand->dev,
+ "erase error, block=%d\n", block_id);
+ return -1;
+ } else {
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * spinand_get_info: get NAND info, from read id or const value
+ * Description:
+ * To set up the device parameters.
+ */
+static int spinand_get_info(struct spi_device *spi_nand,
+ struct spinand_info *info, u8 *id)
+{
+ if (id[0] == 0x2C && (id[1] == 0x11 ||
+ id[1] == 0x12 || id[1] == 0x13)) {
+ info->mid = id[0];
+ info->did = id[1];
+ info->name = "MT29F1G01ZAC";
+ info->nand_size = (1024 * 64 * 2112);
+ info->usable_size = (1024 * 64 * 2048);
+ info->block_size = (2112*64);
+ info->block_main_size = (2048*64);
+ info->block_num_per_chip = 1024;
+ info->page_size = 2112;
+ info->page_main_size = 2048;
+ info->page_spare_size = 64;
+ info->page_num_per_block = 64;
+
+ info->block_shift = 17;
+ info->block_mask = 0x1ffff;
+
+ info->page_shift = 11;
+ info->page_mask = 0x7ff;
+
+ info->ecclayout = &spinand_oob_64;
+ }
+ return 0;
+}
+
+/**
+ * spinand_probe - [spinand Interface]
+ * @spi_nand: registered device driver.
+ *
+ * Description:
+ * To set up the device driver parameters to make the device available.
+*/
+static int spinand_probe(struct spi_device *spi_nand)
+{
+ ssize_t retval;
+ struct mtd_info *mtd;
+ struct spinand_chip *chip;
+ struct spinand_info *info;
+ struct flash_platform_data *data;
+ struct mtd_part_parser_data ppdata;
+ u8 id[2] = {0};
+
+ retval = spinand_reset(spi_nand);
+ retval = spinand_reset(spi_nand);
+ retval = spinand_read_id(spi_nand, (u8 *)&id);
+ if (id[0] == 0 && id[1] == 0) {
+ pr_info(KERN_INFO "SPINAND: read id error! 0x%02x, 0x%02x!\n",
+ id[0], id[1]);
+ return 0;
+ }
+
+ data = spi_nand->dev.platform_data;
+ info = kzalloc(sizeof(struct spinand_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ retval = spinand_get_info(spi_nand, info, (u8 *)&id);
+ pr_info(KERN_INFO "SPINAND: 0x%02x, 0x%02x, %s\n",
+ id[0], id[1], info->name);
+ pr_info(KERN_INFO "%s\n", mu_spi_nand_driver_version);
+ retval = spinand_lock_block(spi_nand, info, BL_ALL_UNLOCKED);
+
+#ifdef CONFIG_MTD_SPINAND_ONDIEECC
+ retval = spinand_enable_ecc(spi_nand, info);
+#else
+ retval = spinand_disable_ecc(spi_nand, info);
+#endif
+
+ ppdata.of_node = spi_nand->dev.of_node;
+
+ chip = kzalloc(sizeof(struct spinand_chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->spi_nand = spi_nand;
+ chip->info = info;
+ chip->reset = spinand_reset;
+ 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_nand->dev, mtd);
+
+ mtd->priv = chip;
+
+ retval = spinand_mtd(mtd);
+
+ return mtd_device_parse_register(mtd, NULL, &ppdata,
+ data ? data->parts : NULL,
+ data ? data->nr_parts : 0);
+}
+
+/**
+ * __devexit spinand_remove--Remove the device driver
+ * @spi: the spi device.
+ *
+ * Description:
+ * To remove the device driver parameters and free up allocated memories.
+ */
+static int spinand_remove(struct spi_device *spi)
+{
+ struct mtd_info *mtd;
+ struct spinand_chip *chip;
+
+ pr_debug("%s: remove\n", dev_name(&spi->dev));
+
+ 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;
+}
+
+/**
+ * Device name structure description
+*/
+static struct spi_driver spinand_driver = {
+ .driver = {
+ .name = "spi_nand",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+
+ .probe = spinand_probe,
+ .remove = spinand_remove,
+};
+
+/**
+ * Device driver registration
+*/
+static int __init spinand_init(void)
+{
+ return spi_register_driver(&spinand_driver);
+}
+
+/**
+ * unregister Device driver.
+*/
+static void __exit spinand_exit(void)
+{
+ spi_unregister_driver(&spinand_driver);
+}
+
+module_init(spinand_init);
+module_exit(spinand_exit);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Henry Pan<[email protected]>");
+MODULE_DESCRIPTION("SPI NAND driver code");
diff --git a/drivers/mtd/spinand/spinand_mtd.c b/drivers/mtd/spinand/spinand_mtd.c
new file mode 100644
index 0000000..8bfff86
--- /dev/null
+++ b/drivers/mtd/spinand/spinand_mtd.c
@@ -0,0 +1,690 @@
+/*
+spinand_mtd.c
+
+Copyright (c) 2009-2010 Micron Technology, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/spinand.h>
+#include <linux/mtd/nand_ecc.h>
+
+/**
+ * spinand_get_device - [GENERIC] Get chip for selected access
+ * @param mtd MTD device structure
+ * @param new_state the state which is requested
+ *
+ * Get the device and lock it for exclusive access
+ */
+#define mu_spi_nand_driver_version "Beagle-MTD_01.00_Linux2.6.33_20100507"
+
+static int spinand_get_device(struct mtd_info *mtd, int new_state)
+{
+ struct spinand_chip *this = mtd->priv;
+ DECLARE_WAITQUEUE(wait, current);
+
+ /*
+ * Grab the lock and see if the device is available
+ */
+ while (1) {
+ spin_lock(&this->chip_lock);
+ if (this->state == FL_READY) {
+ this->state = new_state;
+ spin_unlock(&this->chip_lock);
+ break;
+ }
+ if (new_state == FL_PM_SUSPENDED) {
+ spin_unlock(&this->chip_lock);
+ return (this->state == FL_PM_SUSPENDED) ? 0 : -EAGAIN;
+ }
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&this->wq, &wait);
+ spin_unlock(&this->chip_lock);
+ schedule();
+ remove_wait_queue(&this->wq, &wait);
+ }
+ return 0;
+}
+
+/**
+ * spinand_release_device - [GENERIC] release chip
+ * @param mtd MTD device structure
+ *
+ * Deselect, release chip lock and wake up anyone waiting on the device
+ */
+static void spinand_release_device(struct mtd_info *mtd)
+{
+ struct spinand_chip *this = mtd->priv;
+
+ /* Release the chip */
+ spin_lock(&this->chip_lock);
+ this->state = FL_READY;
+ wake_up(&this->wq);
+ spin_unlock(&this->chip_lock);
+}
+
+#ifdef CONFIG_MTD_SPINAND_SWECC
+static void spinand_calculate_ecc(struct mtd_info *mtd)
+{
+ int i;
+ int eccsize = 512;
+ int eccbytes = 3;
+ int eccsteps = 4;
+ int ecctotal = 12;
+ struct spinand_chip *chip = mtd->priv;
+ struct spinand_info *info = chip->info;
+ unsigned char *p = chip->buf;
+
+ for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
+ __nand_calculate_ecc(p, eccsize, &chip->ecc_calc[i]);
+
+ for (i = 0; i < ecctotal; i++)
+ chip->buf[info->page_main_size +
+ info->ecclayout->eccpos[i]] = chip->ecc_calc[i];
+}
+
+static int spinand_correct_data(struct mtd_info *mtd)
+{
+ int i;
+ int eccsize = 512;
+ int eccbytes = 3;
+ int eccsteps = 4;
+ int ecctotal = 12;
+ struct spinand_chip *chip = mtd->priv;
+ struct spinand_info *info = chip->info;
+ unsigned char *p = chip->buf;
+ int errcode = 0;
+
+ for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
+ __nand_calculate_ecc(p, eccsize, &chip->ecc_calc[i]);
+
+ for (i = 0; i < ecctotal; i++)
+ chip->ecc_code[i] = chip->buf[info->page_main_size +
+ info->ecclayout->eccpos[i]];
+
+ for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
+ int stat;
+
+ stat = __nand_correct_data(p, &chip->ecc_code[i],
+ &chip->ecc_calc[i], eccsize);
+ if (stat < 0)
+ errcode = -1;
+ else if (stat == 1)
+ errcode = 1;
+ }
+ return errcode;
+}
+#endif
+
+static int spinand_read_ops(struct mtd_info *mtd, loff_t from,
+ struct mtd_oob_ops *ops)
+{
+ struct spinand_chip *chip = mtd->priv;
+ struct spi_device *spi_nand = chip->spi_nand;
+ struct spinand_info *info = chip->info;
+ int page_id, page_offset, page_num, oob_num;
+
+ int count;
+ int main_ok, main_left, main_offset;
+ int oob_ok, oob_left;
+
+ signed int retval;
+ signed int errcode = 0;
+
+ if (!chip->buf)
+ return -1;
+
+ page_id = from >> info->page_shift;
+
+ /* for main data */
+ page_offset = from & info->page_mask;
+ page_num = (page_offset + ops->len +
+ info->page_main_size - 1) / info->page_main_size;
+
+ /* for oob */
+ if (info->ecclayout->oobavail)
+ oob_num = (ops->ooblen +
+ info->ecclayout->oobavail - 1) / info->ecclayout->oobavail;
+ else
+ oob_num = 0;
+
+ count = 0;
+
+ main_left = ops->len;
+ main_ok = 0;
+ main_offset = page_offset;
+
+ oob_left = ops->ooblen;
+ oob_ok = 0;
+
+ while (1) {
+ if (count < page_num || count < oob_num) {
+ memset(chip->buf, 0, info->page_size);
+ retval = chip->read_page(spi_nand, info,
+ page_id + count, 0, info->page_size,
+ chip->buf);
+ if (retval != 0) {
+ errcode = -1;
+ pr_info(KERN_INFO
+ "spinand_read_ops: fail, page=%d!\n",
+ page_id);
+ return errcode;
+ }
+ } else {
+ break;
+ }
+ if (count < page_num && ops->datbuf) {
+ int size;
+
+#ifdef CONFIG_MTD_SPINAND_SWECC
+ retval = spinand_correct_data(mtd);
+ if (retval == -1)
+ pr_info(KERN_INFO
+ "SWECC uncorrectable error! page=%x\n",
+ page_id+count);
+ else if (retval == 1)
+ pr_info(KERN_INFO
+ "SWECC 1 bit error, corrected! page=%x\n",
+ page_id+count);
+#endif
+
+ if ((main_offset + main_left) < info->page_main_size)
+ size = main_left;
+ else
+ size = info->page_main_size - main_offset;
+
+ memcpy(ops->datbuf + main_ok, chip->buf, size);
+
+ main_ok += size;
+ main_left -= size;
+ main_offset = 0;
+ ops->retlen = main_ok;
+ }
+
+ if (count < oob_num && ops->oobbuf && chip->oobbuf) {
+ int size;
+ int offset, len, temp;
+
+ /* repack spare to oob */
+ memset(chip->oobbuf, 0, info->ecclayout->oobavail);
+
+ temp = 0;
+ offset = info->ecclayout->oobfree[0].offset;
+ len = info->ecclayout->oobfree[0].length;
+ memcpy(chip->oobbuf + temp,
+ chip->buf + info->page_main_size + offset, len);
+
+ temp += len;
+ offset = info->ecclayout->oobfree[1].offset;
+ len = info->ecclayout->oobfree[1].length;
+ memcpy(chip->oobbuf + temp,
+ chip->buf + info->page_main_size + offset, len);
+
+ temp += len;
+ offset = info->ecclayout->oobfree[2].offset;
+ len = info->ecclayout->oobfree[2].length;
+ memcpy(chip->oobbuf + temp,
+ chip->buf + info->page_main_size + offset, len);
+
+ temp += len;
+ offset = info->ecclayout->oobfree[3].offset;
+ len = info->ecclayout->oobfree[3].length;
+ memcpy(chip->oobbuf + temp,
+ chip->buf + info->page_main_size + offset, len);
+
+ /* copy oobbuf to ops oobbuf */
+ if (oob_left < info->ecclayout->oobavail)
+ size = oob_left;
+ else
+ size = info->ecclayout->oobavail;
+
+ memcpy(ops->oobbuf + oob_ok, chip->oobbuf, size);
+
+ oob_ok += size;
+ oob_left -= size;
+
+ ops->oobretlen = oob_ok;
+ }
+ count++;
+ }
+ return errcode;
+}
+
+static int spinand_write_ops(struct mtd_info *mtd, loff_t to,
+ struct mtd_oob_ops *ops)
+{
+ struct spinand_chip *chip = mtd->priv;
+ struct spi_device *spi_nand = chip->spi_nand;
+ struct spinand_info *info = chip->info;
+ int page_id, page_offset, page_num, oob_num;
+
+ int count;
+
+ int main_ok, main_left, main_offset;
+ int oob_ok, oob_left;
+
+ signed int retval;
+ signed int errcode = 0;
+
+ if (!chip->buf)
+ return -1;
+
+ page_id = to >> info->page_shift;
+
+ /* for main data */
+ page_offset = to & info->page_mask;
+ page_num = (page_offset + ops->len +
+ info->page_main_size - 1) / info->page_main_size;
+
+ /* for oob */
+ if (info->ecclayout->oobavail)
+ oob_num = (ops->ooblen +
+ info->ecclayout->oobavail - 1) / info->ecclayout->oobavail;
+ else
+ oob_num = 0;
+
+ count = 0;
+
+ main_left = ops->len;
+ main_ok = 0;
+ main_offset = page_offset;
+
+ oob_left = ops->ooblen;
+ oob_ok = 0;
+
+ while (1) {
+ if (count < page_num || count < oob_num)
+ memset(chip->buf, 0xFF, info->page_size);
+ else
+ break;
+
+ if (count < page_num && ops->datbuf) {
+ int size;
+
+ if ((main_offset + main_left) < info->page_main_size)
+ size = main_left;
+ else
+ size = info->page_main_size - main_offset;
+
+ memcpy(chip->buf, ops->datbuf + main_ok, size);
+
+ main_ok += size;
+ main_left -= size;
+ main_offset = 0;
+
+#ifdef CONFIG_MTD_SPINAND_SWECC
+ spinand_calculate_ecc(mtd);
+#endif
+ }
+
+ if (count < oob_num && ops->oobbuf && chip->oobbuf) {
+ int size;
+ int offset, len, temp;
+
+ memset(chip->oobbuf, 0xFF, info->ecclayout->oobavail);
+
+ if (oob_left < info->ecclayout->oobavail)
+ size = oob_left;
+ else
+ size = info->ecclayout->oobavail;
+
+ memcpy(chip->oobbuf, ops->oobbuf + oob_ok, size);
+
+ oob_ok += size;
+ oob_left -= size;
+
+ /* repack oob to spare */
+ temp = 0;
+ offset = info->ecclayout->oobfree[0].offset;
+ len = info->ecclayout->oobfree[0].length;
+ memcpy(chip->buf + info->page_main_size + offset,
+ chip->oobbuf + temp, len);
+
+ temp += len;
+ offset = info->ecclayout->oobfree[1].offset;
+ len = info->ecclayout->oobfree[1].length;
+ memcpy(chip->buf + info->page_main_size + offset,
+ chip->oobbuf + temp, len);
+
+ temp += len;
+ offset = info->ecclayout->oobfree[2].offset;
+ len = info->ecclayout->oobfree[2].length;
+ memcpy(chip->buf + info->page_main_size + offset,
+ chip->oobbuf + temp, len);
+
+ temp += len;
+ offset = info->ecclayout->oobfree[3].offset;
+ len = info->ecclayout->oobfree[3].length;
+ memcpy(chip->buf + info->page_main_size + offset,
+ chip->oobbuf + temp, len);
+ }
+
+ if (count < page_num || count < oob_num) {
+ retval = chip->program_page(spi_nand, info,
+ page_id + count, 0, info->page_size, chip->buf);
+ if (retval != 0) {
+ errcode = -1;
+ pr_err(KERN_INFO "spinand_write_ops: fail, page=%d!\n", page_id);
+
+ return errcode;
+ }
+ }
+
+ if (count < page_num && ops->datbuf)
+ ops->retlen = main_ok;
+
+ if (count < oob_num && ops->oobbuf && chip->oobbuf)
+ ops->oobretlen = oob_ok;
+
+ count++;
+ }
+ return errcode;
+}
+
+static int spinand_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct mtd_oob_ops ops = {0};
+ int ret;
+
+ /* Do not allow reads past end of device */
+ if ((from + len) > mtd->size)
+ return -EINVAL;
+
+ if (!len)
+ return 0;
+
+ spinand_get_device(mtd, FL_READING);
+
+ ops.len = len;
+ ops.datbuf = buf;
+
+ ret = spinand_read_ops(mtd, from, &ops);
+
+ *retlen = ops.retlen;
+
+ spinand_release_device(mtd);
+
+ return ret;
+}
+
+static int spinand_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct mtd_oob_ops ops = {0};
+ int ret;
+
+ /* Do not allow reads past end of device */
+ if ((to + len) > mtd->size)
+ return -EINVAL;
+ if (!len)
+ return 0;
+
+ spinand_get_device(mtd, FL_WRITING);
+
+ ops.len = len;
+ ops.datbuf = (uint8_t *)buf;
+
+ ret = spinand_write_ops(mtd, to, &ops);
+
+ *retlen = ops.retlen;
+
+ spinand_release_device(mtd);
+
+ return ret;
+}
+
+static int spinand_read_oob(struct mtd_info *mtd, loff_t from,
+ struct mtd_oob_ops *ops)
+{
+ int ret;
+
+ spinand_get_device(mtd, FL_READING);
+
+ ret = spinand_read_ops(mtd, from, ops);
+
+ spinand_release_device(mtd);
+ return ret;
+}
+
+static int spinand_write_oob(struct mtd_info *mtd, loff_t to,
+ struct mtd_oob_ops *ops)
+{
+ int ret;
+
+ spinand_get_device(mtd, FL_WRITING);
+
+ ret = spinand_write_ops(mtd, to, ops);
+
+ spinand_release_device(mtd);
+ return ret;
+}
+
+/**
+ * spinand_erase - [MTD Interface] erase block(s)
+ * @param mtd MTD device structure
+ * @param instr erase instruction
+ *
+ * Erase one ore more blocks
+ */
+static int spinand_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct spinand_chip *chip = mtd->priv;
+ struct spi_device *spi_nand = chip->spi_nand;
+ struct spinand_info *info = chip->info;
+ u16 block_id, block_num, count;
+ signed int retval = 0;
+ signed int errcode = 0;
+
+ pr_info("spinand_erase: start = 0x%012llx, len = %llu\n",
+ (unsigned long long)instr->addr, (unsigned long long)instr->len);
+
+ /* check address align on block boundary */
+ if (instr->addr & (info->block_main_size - 1)) {
+ pr_err("spinand_erase: Unaligned address\n");
+ return -EINVAL;
+ }
+
+ if (instr->len & (info->block_main_size - 1)) {
+ pr_err("spinand_erase: ""Length not block aligned\n");
+ return -EINVAL;
+ }
+
+ /* Do not allow erase past end of device */
+ if ((instr->len + instr->addr) > info->usable_size) {
+ pr_err("spinand_erase: ""Erase past end of device\n");
+ return -EINVAL;
+ }
+
+ instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
+
+ /* Grab the lock and see if the device is available */
+ spinand_get_device(mtd, FL_ERASING);
+
+ block_id = instr->addr >> info->block_shift;
+ block_num = instr->len >> info->block_shift;
+ count = 0;
+
+ while (count < block_num) {
+ retval = chip->erase_block(spi_nand, info, block_id + count);
+
+ if (retval != 0) {
+ retval = chip->erase_block(spi_nand, info,
+ block_id + count);
+ if (retval != 0) {
+ pr_info(KERN_INFO "spinand_erase: fail, block=%d!\n",
+ block_id + count);
+ errcode = -1;
+ }
+ }
+ count++;
+ }
+
+ if (errcode == 0)
+ instr->state = MTD_ERASE_DONE;
+
+ /* Deselect and wake up anyone waiting on the device */
+ spinand_release_device(mtd);
+
+ /* Do call back function */
+ if (instr->callback)
+ instr->callback(instr);
+
+ return errcode;
+}
+
+/**
+ * spinand_sync - [MTD Interface] sync
+ * @param mtd MTD device structure
+ *
+ * Sync is actually a wait for chip ready function
+ */
+static void spinand_sync(struct mtd_info *mtd)
+{
+ pr_debug("spinand_sync: called\n");
+
+ /* Grab the lock and see if the device is available */
+ spinand_get_device(mtd, FL_SYNCING);
+
+ /* Release it and go back */
+ spinand_release_device(mtd);
+}
+
+static int spinand_block_isbad(struct mtd_info *mtd, loff_t ofs)
+{
+ struct spinand_chip *chip = mtd->priv;
+ struct spi_device *spi_nand = chip->spi_nand;
+ struct spinand_info *info = chip->info;
+ u16 block_id;
+ u8 is_bad = 0x00;
+ u8 ret = 0;
+
+ spinand_get_device(mtd, FL_READING);
+
+ block_id = ofs >> info->block_shift;
+
+ chip->read_page(spi_nand, info, block_id*info->page_num_per_block,
+ info->page_main_size, 1, &is_bad);
+
+ if (is_bad != 0xFF)
+ ret = 1;
+
+ spinand_release_device(mtd);
+
+ return ret;
+}
+
+/**
+ * spinand_block_markbad - [MTD Interface] Mark bad block
+ * @param mtd MTD device structure
+ * @param ofs Bad block number
+ */
+static int spinand_block_markbad(struct mtd_info *mtd, loff_t ofs)
+{
+ struct spinand_chip *chip = mtd->priv;
+ struct spi_device *spi_nand = chip->spi_nand;
+ struct spinand_info *info = chip->info;
+ u16 block_id;
+ u8 is_bad = 0x00;
+ u8 ret = 0;
+
+ spinand_get_device(mtd, FL_WRITING);
+
+ block_id = ofs >> info->block_shift;
+
+ chip->program_page(spi_nand, info, block_id*info->page_num_per_block,
+ info->page_main_size, 1, &is_bad);
+
+ spinand_release_device(mtd);
+
+ return ret;
+}
+
+
+/**
+ * spinand_suspend - [MTD Interface] Suspend the spinand flash
+ * @param mtd MTD device structure
+ */
+static int spinand_suspend(struct mtd_info *mtd)
+{
+ return spinand_get_device(mtd, FL_PM_SUSPENDED);
+}
+
+/**
+ * spinand_resume - [MTD Interface] Resume the spinand flash
+ * @param mtd MTD device structure
+ */
+static void spinand_resume(struct mtd_info *mtd)
+{
+ struct spinand_chip *this = mtd->priv;
+
+ if (this->state == FL_PM_SUSPENDED)
+ spinand_release_device(mtd);
+ else
+ pr_err(KERN_ERR "resume() called for the chip which is not" "in suspended state\n");
+}
+
+/**
+ * spinand_mtd - add MTD device with parameters
+ * @param mtd MTD device structure
+ *
+ * Add MTD device with parameters.
+ */
+int spinand_mtd(struct mtd_info *mtd)
+{
+ struct spinand_chip *chip = mtd->priv;
+ struct spinand_info *info = chip->info;
+
+ chip->state = FL_READY;
+ init_waitqueue_head(&chip->wq);
+ spin_lock_init(&chip->chip_lock);
+
+ mtd->name = info->name;
+ mtd->size = info->usable_size;
+ mtd->erasesize = info->block_main_size;
+ mtd->writesize = info->page_main_size;
+ mtd->oobsize = info->page_spare_size;
+ mtd->owner = THIS_MODULE;
+ mtd->type = MTD_NANDFLASH;
+ mtd->flags = MTD_CAP_NANDFLASH;
+
+ mtd->ecclayout = info->ecclayout;
+
+ mtd->_erase = spinand_erase;
+ mtd->_point = NULL;
+ mtd->_unpoint = NULL;
+ mtd->_read = spinand_read;
+ mtd->_write = spinand_write;
+ mtd->_read_oob = spinand_read_oob;
+ mtd->_write_oob = spinand_write_oob;
+ mtd->_sync = spinand_sync;
+ mtd->_lock = NULL;
+ mtd->_unlock = NULL;
+ mtd->_suspend = spinand_suspend;
+ mtd->_resume = spinand_resume;
+ mtd->_block_isbad = spinand_block_isbad;
+ mtd->_block_markbad = spinand_block_markbad;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spinand_mtd);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Henry Pan<[email protected]>");
diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
new file mode 100644
index 0000000..3b8802a
--- /dev/null
+++ b/include/linux/mtd/spinand.h
@@ -0,0 +1,155 @@
+/*
+ * linux/include/linux/mtd/spinand.h
+ * Copyright (c) 2009-2010 Micron Technology, Inc.
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+/bin/bash: 4: command not found
+ *
+ * based on nand.h
+ */
+#ifndef __LINUX_MTD_SPI_NAND_H
+#define __LINUX_MTD_SPI_NAND_H
+
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include <linux/mtd/mtd.h>
+
+/* cmd */
+#define CMD_READ 0x13
+#define CMD_READ_RDM 0x03
+#define CMD_PROG_PAGE_CLRCACHE 0x02
+#define CMD_PROG_PAGE 0x84
+#define CMD_PROG_PAGE_EXC 0x10
+#define CMD_ERASE_BLK 0xd8
+#define CMD_WR_ENABLE 0x06
+#define CMD_WR_DISABLE 0x04
+#define CMD_READ_ID 0x9f
+#define CMD_RESET 0xff
+#define CMD_READ_REG 0x0f
+#define CMD_WRITE_REG 0x1f
+
+/* feature/ status reg */
+#define REG_BLOCK_LOCK 0xa0
+#define REG_OTP 0xb0
+#define REG_STATUS 0xc0/* timing */
+
+/* status */
+#define STATUS_OIP_MASK 0x01
+#define STATUS_READY (0 << 0)
+#define STATUS_BUSY (1 << 0)
+
+#define STATUS_E_FAIL_MASK 0x04
+#define STATUS_E_FAIL (1 << 2)
+
+#define STATUS_P_FAIL_MASK 0x08
+#define STATUS_P_FAIL (1 << 3)
+
+#define STATUS_ECC_MASK 0x30
+#define STATUS_ECC_1BIT_CORRECTED (1 << 4)
+#define STATUS_ECC_ERROR (2 << 4)
+#define STATUS_ECC_RESERVED (3 << 4)
+
+
+/*ECC enable defines*/
+#define OTP_ECC_MASK 0x10
+#define OTP_ECC_OFF 0
+#define OTP_ECC_ON 1
+
+#define ECC_DISABLED
+#define ECC_IN_NAND
+#define ECC_SOFT
+
+/* block lock */
+#define BL_ALL_LOCKED 0x38
+#define BL_1_2_LOCKED 0x30
+#define BL_1_4_LOCKED 0x28
+#define BL_1_8_LOCKED 0x20
+#define BL_1_16_LOCKED 0x18
+#define BL_1_32_LOCKED 0x10
+#define BL_1_64_LOCKED 0x08
+#define BL_ALL_UNLOCKED 0
+
+struct spinand_info {
+ u8 mid;
+ u8 did;
+ char *name;
+ u64 nand_size;
+ u64 usable_size;
+
+ u32 block_size;
+ u32 block_main_size;
+ /*u32 block_spare_size; */
+ u16 block_num_per_chip;
+ u16 page_size;
+ u16 page_main_size;
+ u16 page_spare_size;
+ u16 page_num_per_block;
+ u8 block_shift;
+ u32 block_mask;
+ u8 page_shift;
+ u16 page_mask;
+
+ struct nand_ecclayout *ecclayout;
+};
+
+typedef enum {
+ FL_READY,
+ FL_READING,
+ FL_WRITING,
+ FL_ERASING,
+ FL_SYNCING,
+ FL_LOCKING,
+ FL_RESETING,
+ FL_OTPING,
+ FL_PM_SUSPENDED,
+} spinand_state_t;
+
+struct spinand_chip { /* used for multi chip */
+ spinlock_t chip_lock;
+ wait_queue_head_t wq;
+ spinand_state_t state;
+ struct spi_device *spi_nand;
+ struct spinand_info *info;
+ /*struct mtd_info *mtd; */
+
+ int (*reset) (struct spi_device *spi_nand);
+ int (*read_id) (struct spi_device *spi_nand, u8 *id);
+ int (*read_page) (struct spi_device *spi_nand,
+ struct spinand_info *info, u16 page_id, u16 offset,
+ u16 len, u8 *rbuf);
+ int (*program_page) (struct spi_device *spi_nand,
+ struct spinand_info *info, u16 page_id, u16 offset,
+ u16 len, u8 *wbuf);
+ int (*erase_block) (struct spi_device *spi_nand,
+ struct spinand_info *info, u16 block_id);
+
+ u8 *buf;
+ u8 *oobbuf; /* temp buffer */
+
+#ifdef CONFIG_MTD_SPINAND_SWECC
+ u8 ecc_calc[12];
+ u8 ecc_code[12];
+#endif
+};
+
+struct spinand_cmd {
+ u8 cmd;
+ unsigned n_addr;
+ u8 addr[3];
+ unsigned n_dummy;
+ unsigned n_tx;
+ u8 *tx_buf;
+ unsigned n_rx;
+ u8 *rx_buf;
+};
+
+extern int spinand_mtd(struct mtd_info *mtd);
+extern void spinand_mtd_release(struct mtd_info *mtd);
+
+#endif
--
1.7.1

2013-06-26 07:42:56

by Sourav Poddar

[permalink] [raw]
Subject: [PATCH 2/3] drivers: spi: Add qspi flash controller

The patch add basic support for the quad spi controller.

QSPI is a kind of spi module that allows single,
dual and quad read access to external spi devices. The module
has a memory mapped interface which provide direct interface
for accessing data form external spi devices.

The patch will configure controller clocks, device control
register and for defining low level transfer apis which
will be used by the spi framework to transfer data to
the slave spi device(flash in this case).

Signed-off-by: Sourav Poddar <[email protected]>
---
drivers/spi/Kconfig | 6 +
drivers/spi/Makefile | 1 +
drivers/spi/ti-qspi.c | 352 +++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 359 insertions(+), 0 deletions(-)
create mode 100644 drivers/spi/ti-qspi.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 92a9345..29a363b 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -285,6 +285,12 @@ config SPI_OMAP24XX
SPI master controller for OMAP24XX and later Multichannel SPI
(McSPI) modules.

+config QSPI_DRA7xxx
+ tristate "DRA7xxx QSPI controller support"
+ depends on ARCH_OMAP2PLUS
+ help
+ QSPI master controller for DRA7xxx used for flash devices.
+
config SPI_OMAP_100K
tristate "OMAP SPI 100K"
depends on ARCH_OMAP850 || ARCH_OMAP730
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 33f9c09..ea14eff 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -46,6 +46,7 @@ obj-$(CONFIG_SPI_OCTEON) += spi-octeon.o
obj-$(CONFIG_SPI_OMAP_UWIRE) += spi-omap-uwire.o
obj-$(CONFIG_SPI_OMAP_100K) += spi-omap-100k.o
obj-$(CONFIG_SPI_OMAP24XX) += spi-omap2-mcspi.o
+obj-$(CONFIG_QSPI_DRA7xxx) += ti-qspi.o
obj-$(CONFIG_SPI_ORION) += spi-orion.o
obj-$(CONFIG_SPI_PL022) += spi-pl022.o
obj-$(CONFIG_SPI_PPC4xx) += spi-ppc4xx.o
diff --git a/drivers/spi/ti-qspi.c b/drivers/spi/ti-qspi.c
new file mode 100644
index 0000000..b33646a
--- /dev/null
+++ b/drivers/spi/ti-qspi.c
@@ -0,0 +1,352 @@
+/*
+ * TI QSPI driver
+ *
+ * Copyright (C) 2013, Texas Instruments, Incorporated
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR /PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/omap-dma.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/pinctrl/consumer.h>
+
+#include <linux/spi/spi.h>
+
+struct dra7xxx_qspi {
+ struct spi_master *master;
+ void __iomem *base;
+ int device_type;
+ struct device *dev;
+ u32 spi_max_frequency;
+ u32 cmd;
+ u32 dc;
+};
+
+#define QSPI_PID (0x0)
+#define QSPI_SYSCONFIG (0x10)
+#define QSPI_INTR_STATUS_RAW_SET (0x20)
+#define QSPI_INTR_STATUS_ENABLED_CLEAR (0x24)
+#define QSPI_INTR_ENABLE_SET_REG (0x28)
+#define QSPI_INTR_ENABLE_CLEAR_REG (0x2c)
+#define QSPI_SPI_CLOCK_CNTRL_REG (0x40)
+#define QSPI_SPI_DC_REG (0x44)
+#define QSPI_SPI_CMD_REG (0x48)
+#define QSPI_SPI_STATUS_REG (0x4c)
+#define QSPI_SPI_DATA_REG (0x50)
+#define QSPI_SPI_SETUP0_REG (0x54)
+#define QSPI_SPI_SWITCH_REG (0x64)
+#define QSPI_SPI_SETUP1_REG (0x58)
+#define QSPI_SPI_SETUP2_REG (0x5c)
+#define QSPI_SPI_SETUP3_REG (0x60)
+#define QSPI_SPI_DATA_REG_1 (0x68)
+#define QSPI_SPI_DATA_REG_2 (0x6c)
+#define QSPI_SPI_DATA_REG_3 (0x70)
+
+#define QSPI_TIMEOUT 2000000
+
+#define QSPI_FCLK 192000000
+
+/* Clock Control */
+#define QSPI_CLK_EN (1 << 31)
+#define QSPI_CLK_DIV_MAX 0xffff
+
+/* Command */
+#define QSPI_EN_CS(n) (n << 28)
+#define QSPI_WLEN(n) ((n-1) << 19)
+#define QSPI_3_PIN (1 << 18)
+#define QSPI_RD_SNGL (1 << 16)
+#define QSPI_WR_SNGL (2 << 16)
+#define QSPI_RD_QUAD (7 << 16)
+#define QSPI_INVAL (4 << 16)
+
+/* Device Control */
+#define QSPI_DD(m, n) (m << (3 + n*8))
+#define QSPI_CKPHA(n) (1 << (2 + n*8))
+#define QSPI_CSPOL(n) (1 << (1 + n*8))
+#define QSPI_CKPOL(n) (1 << (n*8))
+
+/* Status */
+#define QSPI_WC (1 << 1)
+#define QSPI_BUSY (1 << 0)
+#define QSPI_WC_BUSY (QSPI_WC | QSPI_BUSY)
+#define QSPI_XFER_DONE QSPI_WC
+
+#define XFER_END 0x01
+
+#define SPI_AUTOSUSPEND_TIMEOUT 2000
+
+static inline unsigned long dra7xxx_readl(struct dra7xxx_qspi *qspi,
+ unsigned long reg)
+{
+ return readl(qspi->base + reg);
+}
+
+static inline void dra7xxx_writel(struct dra7xxx_qspi *qspi,
+ unsigned long val, unsigned long reg)
+{
+ writel(val, qspi->base + reg);
+}
+
+static int dra7xxx_qspi_setup(struct spi_device *spi)
+{
+ struct dra7xxx_qspi *qspi =
+ spi_master_get_devdata(spi->master);
+
+ int clk_div;
+
+ if (!qspi->spi_max_frequency)
+ clk_div = 0;
+ else
+ clk_div = (QSPI_FCLK / qspi->spi_max_frequency) - 1;
+
+ pr_debug("%s: hz: %d, clock divider %d\n", __func__,
+ qspi->spi_max_frequency, clk_div);
+
+ pm_runtime_get_sync(qspi->dev);
+
+ /* disable SCLK */
+ dra7xxx_writel(qspi, dra7xxx_readl(qspi, QSPI_SPI_CLOCK_CNTRL_REG)
+ & ~QSPI_CLK_EN, QSPI_SPI_CLOCK_CNTRL_REG);
+
+ if (clk_div < 0) {
+ pr_debug("%s: clock divider < 0, using /1 divider\n", __func__);
+ clk_div = 0;
+ }
+
+ if (clk_div > QSPI_CLK_DIV_MAX) {
+ pr_debug("%s: clock divider >%d , using /%d divider\n",
+ __func__, QSPI_CLK_DIV_MAX, QSPI_CLK_DIV_MAX + 1);
+ clk_div = QSPI_CLK_DIV_MAX;
+ }
+
+ /* enable SCLK */
+ dra7xxx_writel(qspi, QSPI_CLK_EN | clk_div, QSPI_SPI_CLOCK_CNTRL_REG);
+
+ pm_runtime_mark_last_busy(qspi->dev);
+ pm_runtime_put_autosuspend(qspi->dev);
+
+ pr_debug("%s: spi_clock_cntrl %ld\n", __func__,
+ dra7xxx_readl(qspi, QSPI_SPI_CLOCK_CNTRL_REG));
+
+ return 0;
+}
+
+static int dra7xxx_qspi_prepare_xfer(struct spi_master *master)
+{
+ return 0;
+}
+
+static int dra7xxx_qspi_unprepare_xfer(struct spi_master *master)
+{
+ return 0;
+}
+
+static int qspi_transfer_msg(struct dra7xxx_qspi *qspi, unsigned count,
+ const u8 *txbuf, u8 *rxbuf, bool flags)
+{
+ uint status;
+ int timeout;
+
+ pm_runtime_get_sync(qspi->dev);
+
+ while (count--) {
+ if (txbuf) {
+ pr_debug("tx cmd %08x dc %08x data %02x\n",
+ qspi->cmd | QSPI_WR_SNGL, qspi->dc, *txbuf);
+ dra7xxx_writel(qspi, *txbuf++, QSPI_SPI_DATA_REG);
+ dra7xxx_writel(qspi, qspi->dc, QSPI_SPI_DC_REG);
+ dra7xxx_writel(qspi, qspi->cmd | QSPI_WR_SNGL,
+ QSPI_SPI_CMD_REG);
+ status = dra7xxx_readl(qspi, QSPI_SPI_STATUS_REG);
+ timeout = QSPI_TIMEOUT;
+ while ((status & QSPI_WC_BUSY) != QSPI_XFER_DONE) {
+ if (--timeout < 0) {
+ pr_debug("QSPI tx timed out\n");
+ return -1;
+ }
+ status = dra7xxx_readl(qspi, QSPI_SPI_STATUS_REG);
+ }
+ pr_debug("tx done, status %08x\n", status);
+ }
+ if (rxbuf) {
+ pr_debug("rx cmd %08x dc %08x\n",
+ qspi->cmd | QSPI_RD_SNGL, qspi->dc);
+ dra7xxx_writel(qspi, qspi->dc, QSPI_SPI_DC_REG);
+ dra7xxx_writel(qspi, qspi->cmd | QSPI_RD_SNGL,
+ QSPI_SPI_CMD_REG);
+ status = dra7xxx_readl(qspi, QSPI_SPI_STATUS_REG);
+ timeout = QSPI_TIMEOUT;
+ while ((status & QSPI_WC_BUSY) != QSPI_XFER_DONE) {
+ if (--timeout < 0) {
+ pr_debug("QSPI rx timed out\n");
+ return -1;
+ }
+ status = dra7xxx_readl(qspi, QSPI_SPI_STATUS_REG);
+ }
+ *rxbuf++ = dra7xxx_readl(qspi, QSPI_SPI_DATA_REG);
+ pr_debug("rx done, status %08x, read %02x\n",
+ status, *(rxbuf-1));
+ }
+ }
+
+ if (flags & XFER_END)
+ dra7xxx_writel(qspi, qspi->cmd | QSPI_INVAL, QSPI_SPI_CMD_REG);
+
+ pm_runtime_mark_last_busy(qspi->dev);
+ pm_runtime_put_autosuspend(qspi->dev);
+
+ return 0;
+}
+
+static int dra7xxx_qspi_start_transfer_one(struct spi_master *master,
+ struct spi_message *m)
+{
+ struct dra7xxx_qspi *qspi = spi_master_get_devdata(master);
+ struct spi_device *spi = m->spi;
+ struct spi_transfer *t;
+ int status = 0;
+ int flags = 0;
+
+ /* setup command reg */
+ qspi->cmd = 0;
+ qspi->cmd |= QSPI_WLEN(8);
+ qspi->cmd |= QSPI_EN_CS(0);
+ qspi->cmd |= 0xfff;
+
+ /* setup device control reg */
+ qspi->dc = 0;
+
+ if (spi->mode & SPI_CPHA)
+ qspi->dc |= QSPI_CKPHA(0);
+ if (spi->mode & SPI_CPOL)
+ qspi->dc |= QSPI_CKPOL(0);
+ if (spi->mode & SPI_CS_HIGH)
+ qspi->dc |= QSPI_CSPOL(0);
+
+ list_for_each_entry(t, &m->transfers, transfer_list) {
+ if (list_is_last(&t->transfer_list, &m->transfers))
+ flags = XFER_END;
+
+ qspi_transfer_msg(qspi, t->len, t->tx_buf,
+ t->rx_buf, flags);
+
+ m->actual_length += t->len;
+ }
+ m->status = status;
+ spi_finalize_current_message(master);
+
+ return status;
+}
+
+static int dra7xxx_qspi_probe(struct platform_device *pdev)
+{
+ struct dra7xxx_qspi *qspi;
+ struct spi_master *master;
+ struct resource *r;
+ struct device_node *np = pdev->dev.of_node;
+ u32 max_freq;
+ int ret;
+
+ master = spi_alloc_master(&pdev->dev, sizeof(*qspi));
+ if (!master)
+ return -ENOMEM;
+
+ master->mode_bits = SPI_CPOL | SPI_CPHA;
+
+ master->num_chipselect = 1;
+ master->bus_num = -1;
+ master->setup = dra7xxx_qspi_setup;
+ master->prepare_transfer_hardware = dra7xxx_qspi_prepare_xfer;
+ master->transfer_one_message = dra7xxx_qspi_start_transfer_one;
+ master->unprepare_transfer_hardware = dra7xxx_qspi_unprepare_xfer;
+ master->dev.of_node = pdev->dev.of_node;
+
+ dev_set_drvdata(&pdev->dev, master);
+
+ qspi = spi_master_get_devdata(master);
+ qspi->master = master;
+ qspi->dev = &pdev->dev;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (r == NULL) {
+ ret = -ENODEV;
+ goto free_master;
+ }
+
+ qspi->base = devm_request_and_ioremap(&pdev->dev, r);
+ if (!qspi->base) {
+ dev_dbg(&pdev->dev, "can't ioremap MCSPI\n");
+ ret = -ENOMEM;
+ goto free_master;
+ }
+
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, SPI_AUTOSUSPEND_TIMEOUT);
+ pm_runtime_enable(&pdev->dev);
+
+ if (!of_property_read_u32(np, "spi-max-frequency", &max_freq))
+ qspi->spi_max_frequency = max_freq;
+
+ ret = spi_register_master(master);
+ if (ret)
+ goto free_master;
+
+ return ret;
+
+free_master:
+ spi_master_put(master);
+ return ret;
+}
+
+static int dra7xxx_qspi_remove(struct platform_device *pdev)
+{
+ struct dra7xxx_qspi *qspi = platform_get_drvdata(pdev);
+
+ spi_unregister_master(qspi->master);
+
+ return 0;
+}
+
+static const struct of_device_id dra7xxx_qspi_match[] = {
+ {.compatible = "ti,dra7xxx-qspi" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, dra7xxx_qspi_match);
+
+static struct platform_driver dra7xxx_qspi_driver = {
+ .probe = dra7xxx_qspi_probe,
+ .remove = dra7xxx_qspi_remove,
+ .driver = {
+ .name = "ti,dra7xxx-qspi",
+ .owner = THIS_MODULE,
+ .of_match_table = dra7xxx_qspi_match,
+ }
+};
+
+module_platform_driver(dra7xxx_qspi_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("TI QSPI controller driver");
--
1.7.1

2013-06-26 14:16:40

by Florian Fainelli

[permalink] [raw]
Subject: Re: [PATCH 1/3] drivers: mtd: spinand: Add generic spinand frameowrk and micron driver.

Hello,

2013/6/26 Sourav Poddar <[email protected]>:
> From: Mona Anonuevo <[email protected]>
>
> This patch adds support for a generic spinand framework(spinand_mtd.c).
> This frameowrk can be used for other spi based flash devices also. The idea
> is to have a common model under drivers/mtd, as also present for other no spi
> devices(there is a generic framework and device part simply attaches itself to it.)
>
> The generic frework will be used later by me for a SPI based spansion S25FL256 device.
> The patch also contains a micron driver attaching itself to generic framework.

Some general comments below, I do not have any SPI NAND devices, just
reading through the code.

> +
> +config MTD_SPINAND_ONDIEECC
> + bool "Use SPINAND internal ECC"
> + help
> + Internel ECC
> +
> +config MTD_SPINAND_SWECC
> + bool "Use software ECC"
> + depends on MTD_NAND
> + help
> + software ECC

Cannot both of these be somehow detected by the identification bytes?
Or maybe explicitely specified in an identification table?

> +#define mu_spi_nand_driver_version "Beagle-MTD_01.00_Linux2.6.33_20100507"

You probably want to remove this.

> +#define SPI_NAND_MICRON_DRIVER_KEY 0x1233567

Ditto.

> +
> +/****************************************************************************/
> +
> +/**
> + OOB area specification layout: Total 32 available free bytes.
> +*/
> +static struct nand_ecclayout spinand_oob_64 = {
> + .eccbytes = 24,
> + .eccpos = {
> + 1, 2, 3, 4, 5, 6,
> + 17, 18, 19, 20, 21, 22,
> + 33, 34, 35, 36, 37, 38,
> + 49, 50, 51, 52, 53, 54, },
> + .oobavail = 32,
> + .oobfree = {
> + {.offset = 8,
> + .length = 8},
> + {.offset = 24,
> + .length = 8},
> + {.offset = 40,
> + .length = 8},
> + {.offset = 56,
> + .length = 8}, }
> +};

This should probably be per-device, or at best supplied by platform data?

> +/**
> + * spinand_cmd - to process a command to send to the SPI Nand
> + *
> + * Description:
> + * Set up the command buffer to send to the SPI controller.
> + * The command buffer has to initized to 0
> + */
> +int spinand_cmd(struct spi_device *spi, struct spinand_cmd *cmd)
> +{
> + int ret;
> + struct spi_message message;
> + struct spi_transfer x[4];
> + u8 dummy = 0xff;
> +
> + spi_message_init(&message);
> + memset(x, 0, sizeof(x));
> +
> + x[0].len = 1;
> + x[0].tx_buf = &cmd->cmd;
> + spi_message_add_tail(&x[0], &message);
> +
> + if (cmd->n_addr) {
> + x[1].len = cmd->n_addr;
> + x[1].tx_buf = cmd->addr;
> + spi_message_add_tail(&x[1], &message);
> + }
> +
> + if (cmd->n_dummy) {
> + x[2].len = cmd->n_dummy;
> + x[2].tx_buf = &dummy;
> + spi_message_add_tail(&x[2], &message);
> + }
> +
> + if (cmd->n_tx) {
> + x[3].len = cmd->n_tx;
> + x[3].tx_buf = cmd->tx_buf;
> + spi_message_add_tail(&x[3], &message);
> + }
> +
> + if (cmd->n_rx) {
> + x[3].len = cmd->n_rx;
> + x[3].rx_buf = cmd->rx_buf;
> + spi_message_add_tail(&x[3], &message);
> + }
> +
> + ret = spi_sync(spi, &message);

If any kind of locking is implicitely done by the SPI layer, you might
want to add a comment to specify it.

[snip]

> + retval = spinand_cmd(spi_nand, &cmd);
> +
> + if (retval != 0) {
> + dev_err(&spi_nand->dev, "error %d reading id\n",
> + (int) retval);
> + return retval;
> + }

Just:

if (retval)
dev_err(&spi_nand->dev, "...

return retval

[snip]

> + retval = spinand_cmd(spi_nand, &cmd);
> +
> + if (retval != 0) {
> + dev_err(&spi_nand->dev, "error %d lock block\n",
> + (int) retval);
> + return retval;
> + }

Same here

[snip]

> + if (retval != 0) {
> + dev_err(&spi_nand->dev, "error %d reading status register\n",
> + (int) retval);
> + return retval;
> + }

And here

[snip]

> + if (retval != 0) {
> + dev_err(&spi_nand->dev, "error %d get otp\n",
> + (int) retval);
> + return retval;
> + }

And here

[snip]

> + if (retval != 0) {
> + dev_err(&spi_nand->dev, "error %d set otp\n",
> + (int) retval);
> + return retval;

And here

[snip]

> +*/
> +#ifdef CONFIG_MTD_SPINAND_ONDIEECC

Same comment as above, you could probably do not make this enclosed
within an ifdef, but always compile it and test for a device flag for
instance.

> +static int spinand_enable_ecc(struct spi_device *spi_nand,
> + struct spinand_info *info)
> +{
> + ssize_t retval;
> + u8 otp = 0;
> +
> + retval = spinand_get_otp(spi_nand, info, &otp);
> +
> + if ((otp & OTP_ECC_MASK) == OTP_ECC_MASK) {
> + return 0;
> + } else {
> + otp |= OTP_ECC_MASK;
> + retval = spinand_set_otp(spi_nand, info, &otp);
> + retval = spinand_get_otp(spi_nand, info, &otp);
> + return retval;
> + }

You probably do not need the if() else() because the if branch returns
immediately.

[snip]

> + if ((otp & OTP_ECC_MASK) == OTP_ECC_MASK) {
> + otp &= ~OTP_ECC_MASK;
> + retval = spinand_set_otp(spi_nand, info, &otp);
> + retval = spinand_get_otp(spi_nand, info, &otp);
> + return retval;
> + } else {
> + return 0;

Same here

[snip]

> +static int spinand_read_page(struct spi_device *spi_nand,
> + struct spinand_info *info, u16 page_id, u16 offset,
> + u16 len, u8 *rbuf)
> +{
> + ssize_t retval;
> + u8 status = 0;
> +
> + retval = spinand_read_page_to_cache(spi_nand, info, page_id);

Either you check the value and return or you do not.

> +
> + while (1) {
> + retval = spinand_read_status(spi_nand, info, &status);
> + if (retval < 0) {
> + dev_err(&spi_nand->dev, "error %d reading status register\n",
> + (int) retval);
> + return retval;
> + }
> +
> + if ((status & STATUS_OIP_MASK) == STATUS_READY) {
> + if ((status & STATUS_ECC_MASK) == STATUS_ECC_ERROR) {
> + dev_err(&spi_nand->dev,
> + "ecc error, page=%d\n", page_id);
> + }
> + break;
> + }

Should not we somehow call cpu_relax() or wait for some delay here
before issuing multiple READ_STATUS commands?

[snip]

> + retval = spinand_write_enable(spi_nand, info);
> +
> + retval = spinand_program_data_to_cache(spi_nand, info, offset,
> + len, wbuf);
> +
> + retval = spinand_program_execute(spi_nand, info, page_id);

Same here either check return value and return or do not check the
return value at all.

[snip]

> +static int spinand_get_info(struct spi_device *spi_nand,
> + struct spinand_info *info, u8 *id)
> +{
> + if (id[0] == 0x2C && (id[1] == 0x11 ||
> + id[1] == 0x12 || id[1] == 0x13)) {
> + info->mid = id[0];
> + info->did = id[1];
> + info->name = "MT29F1G01ZAC";
> + info->nand_size = (1024 * 64 * 2112);
> + info->usable_size = (1024 * 64 * 2048);
> + info->block_size = (2112*64);
> + info->block_main_size = (2048*64);
> + info->block_num_per_chip = 1024;
> + info->page_size = 2112;
> + info->page_main_size = 2048;
> + info->page_spare_size = 64;
> + info->page_num_per_block = 64;
> +
> + info->block_shift = 17;
> + info->block_mask = 0x1ffff;
> +
> + info->page_shift = 11;
> + info->page_mask = 0x7ff;
> +
> + info->ecclayout = &spinand_oob_64;
> + }

Even if there is just one device supported by this driver, you
definitively want to use an identification table so that people can
easily add new chips without much pain.

> + return 0;
> +}
> +
> +/**
> + * spinand_probe - [spinand Interface]
> + * @spi_nand: registered device driver.
> + *
> + * Description:
> + * To set up the device driver parameters to make the device available.
> +*/
> +static int spinand_probe(struct spi_device *spi_nand)
> +{
> + ssize_t retval;
> + struct mtd_info *mtd;
> + struct spinand_chip *chip;
> + struct spinand_info *info;
> + struct flash_platform_data *data;
> + struct mtd_part_parser_data ppdata;
> + u8 id[2] = {0};
> +
> + retval = spinand_reset(spi_nand);
> + retval = spinand_reset(spi_nand);
> + retval = spinand_read_id(spi_nand, (u8 *)&id);
> + if (id[0] == 0 && id[1] == 0) {
> + pr_info(KERN_INFO "SPINAND: read id error! 0x%02x, 0x%02x!\n",
> + id[0], id[1]);
> + return 0;
> + }
> +
> + data = spi_nand->dev.platform_data;
> + info = kzalloc(sizeof(struct spinand_info), GFP_KERNEL);
> + if (!info)
> + return -ENOMEM;

You can use devm_kzalloc() to get your chunks of memory freed upon
driver removal.

> +
> + retval = spinand_get_info(spi_nand, info, (u8 *)&id);
> + pr_info(KERN_INFO "SPINAND: 0x%02x, 0x%02x, %s\n",
> + id[0], id[1], info->name);
> + pr_info(KERN_INFO "%s\n", mu_spi_nand_driver_version);
> + retval = spinand_lock_block(spi_nand, info, BL_ALL_UNLOCKED);
> +
> +#ifdef CONFIG_MTD_SPINAND_ONDIEECC
> + retval = spinand_enable_ecc(spi_nand, info);
> +#else
> + retval = spinand_disable_ecc(spi_nand, info);
> +#endif
> +
> + ppdata.of_node = spi_nand->dev.of_node;

You could probably go along and define Device Tree bindings for this
driver at the same time, such that they are directly usable once the
driver is merged.

> diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
> new file mode 100644
> index 0000000..3b8802a
> --- /dev/null
> +++ b/include/linux/mtd/spinand.h
> @@ -0,0 +1,155 @@
> +/*
> + * linux/include/linux/mtd/spinand.h
> + * Copyright (c) 2009-2010 Micron Technology, Inc.
> + * This software is licensed under the terms of the GNU General Public
> + * License version 2, as published by the Free Software Foundation, and
> + * may be copied, distributed, and modified under those terms.
> +
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> +/bin/bash: 4: command not found

?

> + *
> + * based on nand.h
> + */
> +#ifndef __LINUX_MTD_SPI_NAND_H
> +#define __LINUX_MTD_SPI_NAND_H
> +
> +#include <linux/wait.h>
> +#include <linux/spinlock.h>
> +#include <linux/mtd/mtd.h>
> +
> +/* cmd */
> +#define CMD_READ 0x13
> +#define CMD_READ_RDM 0x03
> +#define CMD_PROG_PAGE_CLRCACHE 0x02
> +#define CMD_PROG_PAGE 0x84
> +#define CMD_PROG_PAGE_EXC 0x10
> +#define CMD_ERASE_BLK 0xd8
> +#define CMD_WR_ENABLE 0x06
> +#define CMD_WR_DISABLE 0x04
> +#define CMD_READ_ID 0x9f
> +#define CMD_RESET 0xff
> +#define CMD_READ_REG 0x0f
> +#define CMD_WRITE_REG 0x1f

Please prefix all of them with SPI_NAND_CMD just to be consistent with
what is defined in include/linux/mtd/nand.h?

> +
> +/* feature/ status reg */
> +#define REG_BLOCK_LOCK 0xa0
> +#define REG_OTP 0xb0
> +#define REG_STATUS 0xc0/* timing */
> +
> +/* status */
> +#define STATUS_OIP_MASK 0x01
> +#define STATUS_READY (0 << 0)
> +#define STATUS_BUSY (1 << 0)
> +
> +#define STATUS_E_FAIL_MASK 0x04
> +#define STATUS_E_FAIL (1 << 2)
> +
> +#define STATUS_P_FAIL_MASK 0x08
> +#define STATUS_P_FAIL (1 << 3)
> +
> +#define STATUS_ECC_MASK 0x30
> +#define STATUS_ECC_1BIT_CORRECTED (1 << 4)
> +#define STATUS_ECC_ERROR (2 << 4)
> +#define STATUS_ECC_RESERVED (3 << 4)
> +
> +
> +/*ECC enable defines*/
> +#define OTP_ECC_MASK 0x10
> +#define OTP_ECC_OFF 0
> +#define OTP_ECC_ON 1
> +
> +#define ECC_DISABLED
> +#define ECC_IN_NAND
> +#define ECC_SOFT
> +
> +/* block lock */
> +#define BL_ALL_LOCKED 0x38
> +#define BL_1_2_LOCKED 0x30
> +#define BL_1_4_LOCKED 0x28
> +#define BL_1_8_LOCKED 0x20
> +#define BL_1_16_LOCKED 0x18
> +#define BL_1_32_LOCKED 0x10
> +#define BL_1_64_LOCKED 0x08
> +#define BL_ALL_UNLOCKED 0
> +
> +struct spinand_info {
> + u8 mid;
> + u8 did;
> + char *name;
> + u64 nand_size;
> + u64 usable_size;
> +
> + u32 block_size;
> + u32 block_main_size;
> + /*u32 block_spare_size; */
> + u16 block_num_per_chip;
> + u16 page_size;
> + u16 page_main_size;
> + u16 page_spare_size;
> + u16 page_num_per_block;
> + u8 block_shift;
> + u32 block_mask;
> + u8 page_shift;
> + u16 page_mask;
> +
> + struct nand_ecclayout *ecclayout;
> +};
> +
> +typedef enum {
> + FL_READY,
> + FL_READING,
> + FL_WRITING,
> + FL_ERASING,
> + FL_SYNCING,
> + FL_LOCKING,
> + FL_RESETING,
> + FL_OTPING,
> + FL_PM_SUSPENDED,
> +} spinand_state_t;

Maybe flstate_t from include/linux/mtd/flashchip.h should be made
move/made nore generic such that you can use these defines too?
--
Florian

2013-06-26 15:14:43

by Kamlakant Patel

[permalink] [raw]
Subject: Re: [PATCH 1/3] drivers: mtd: spinand: Add generic spinand frameowrk and micron driver.

On Wed, Jun 26, 2013 at 01:11:10PM +0530, Sourav Poddar wrote:
> From: Mona Anonuevo <[email protected]>
>
> This patch adds support for a generic spinand framework(spinand_mtd.c).
> This frameowrk can be used for other spi based flash devices also. The idea
> is to have a common model under drivers/mtd, as also present for other no spi
> devices(there is a generic framework and device part simply attaches itself to it.)
>
> The generic frework will be used later by me for a SPI based spansion S25FL256 device.
> The patch also contains a micron driver attaching itself to generic framework.
>
> Signed-off-by: Mona Anonuevo <[email protected]>
> Signed-off-by: Tuan Nguyen <[email protected]>
> Signed-off-by: Sourav Poddar <[email protected]>
> ----
> [I picked this as a standalone patch, can split it into generic and device part
> based on community feedback.]
>
> drivers/mtd/Kconfig | 2 +
> drivers/mtd/Makefile | 2 +
> drivers/mtd/spinand/Kconfig | 24 ++
> drivers/mtd/spinand/Makefile | 10 +
> drivers/mtd/spinand/spinand_lld.c | 776 +++++++++++++++++++++++++++++++++++++
> drivers/mtd/spinand/spinand_mtd.c | 690 +++++++++++++++++++++++++++++++++
> include/linux/mtd/spinand.h | 155 ++++++++
> 7 files changed, 1659 insertions(+), 0 deletions(-)
> create mode 100644 drivers/mtd/spinand/Kconfig
> create mode 100644 drivers/mtd/spinand/Makefile
> create mode 100644 drivers/mtd/spinand/spinand_lld.c
> create mode 100644 drivers/mtd/spinand/spinand_mtd.c
> create mode 100644 include/linux/mtd/spinand.h
>

I am working on Micron SPINAND(Micron MT29F1G01ZACH). I tried this patch, but it's not working.
It is throwing following error message while mounting:
[ 260.232000] jffs2: cannot read OOB for EB at 00000000, requested 8 bytes, read 0 bytes, error -22
mount: mounting /dev/mtdblock5 on /mnt/ failed: Input/output error

I am working on it to fix into the driver, will send an updated patch with the fix.

Thanks,
Kamlakant Patel

2013-06-27 04:52:28

by Sourav Poddar

[permalink] [raw]
Subject: Re: [PATCH 1/3] drivers: mtd: spinand: Add generic spinand frameowrk and micron driver.

Hi Kamlakant,
On Wednesday 26 June 2013 08:52 PM, Kamlakant Patel wrote:
> On Wed, Jun 26, 2013 at 01:11:10PM +0530, Sourav Poddar wrote:
>> From: Mona Anonuevo<[email protected]>
>>
>> This patch adds support for a generic spinand framework(spinand_mtd.c).
>> This frameowrk can be used for other spi based flash devices also. The idea
>> is to have a common model under drivers/mtd, as also present for other no spi
>> devices(there is a generic framework and device part simply attaches itself to it.)
>>
>> The generic frework will be used later by me for a SPI based spansion S25FL256 device.
>> The patch also contains a micron driver attaching itself to generic framework.
>>
>> Signed-off-by: Mona Anonuevo<[email protected]>
>> Signed-off-by: Tuan Nguyen<[email protected]>
>> Signed-off-by: Sourav Poddar<[email protected]>
>> ----
>> [I picked this as a standalone patch, can split it into generic and device part
>> based on community feedback.]
>>
>> drivers/mtd/Kconfig | 2 +
>> drivers/mtd/Makefile | 2 +
>> drivers/mtd/spinand/Kconfig | 24 ++
>> drivers/mtd/spinand/Makefile | 10 +
>> drivers/mtd/spinand/spinand_lld.c | 776 +++++++++++++++++++++++++++++++++++++
>> drivers/mtd/spinand/spinand_mtd.c | 690 +++++++++++++++++++++++++++++++++
>> include/linux/mtd/spinand.h | 155 ++++++++
>> 7 files changed, 1659 insertions(+), 0 deletions(-)
>> create mode 100644 drivers/mtd/spinand/Kconfig
>> create mode 100644 drivers/mtd/spinand/Makefile
>> create mode 100644 drivers/mtd/spinand/spinand_lld.c
>> create mode 100644 drivers/mtd/spinand/spinand_mtd.c
>> create mode 100644 include/linux/mtd/spinand.h
>>
> I am working on Micron SPINAND(Micron MT29F1G01ZACH). I tried this patch, but it's not working.
> It is throwing following error message while mounting:
> [ 260.232000] jffs2: cannot read OOB for EB at 00000000, requested 8 bytes, read 0 bytes, error -22
> mount: mounting /dev/mtdblock5 on /mnt/ failed: Input/output error
>
> I am working on it to fix into the driver, will send an updated patch with the fix.
>
Thanks for replying on this.
Since, this patch is already posted, I think it will be better if you post
the delta fix on top of this patch.
> Thanks,
> Kamlakant Patel
>

2013-07-01 05:18:23

by Sourav Poddar

[permalink] [raw]
Subject: Re: [PATCH 0/3] spi/mtd generic framework,ti qspi controller and spansion driver

+ Artem
On Wednesday 26 June 2013 01:11 PM, Sourav Poddar wrote:
> This patch series add support for the generic spi based flash
> framework(spinand_mtd), which can be used used by any spi based flash device to
> attach itself to mtd framework.
>
> The first patch of this series includes both the generic framework and the
> the micron device(spinand_lld) making use of the framework.
> I picked the first patch as a standalone patch. Can split the generic and
> the lld part based on community suggestions.
>
> The second patch is the ti qspi controller driver.
> The third patch is the spansion s25fl256s driver, making use of the the
> generic spinand_mtd frameowrk.
>
> Test info:
> Tested the generic framework(spinand_mtd.c) along with patch(2&3) on my dra7xx board
> for write/erase/read using nand utils.
>
> Compile tested(spinand_lld.c).
>
> Mona Anonuevo (1):
> drivers: mtd: spinand: Add generic spinand frameowrk and micron
> driver.
>
> Sourav Poddar (2):
> drivers: spi: Add qspi flash controller
> drivers: mtd: spinand: Add qspi spansion flash controller
>
> drivers/mtd/Kconfig | 2 +
> drivers/mtd/Makefile | 2 +
> drivers/mtd/spinand/Kconfig | 31 ++
> drivers/mtd/spinand/Makefile | 10 +
> drivers/mtd/spinand/spinand_lld.c | 776 +++++++++++++++++++++++++++++++++++
> drivers/mtd/spinand/spinand_mtd.c | 690 +++++++++++++++++++++++++++++++
> drivers/mtd/spinand/ti-qspi-flash.c | 373 +++++++++++++++++
> drivers/spi/Kconfig | 6 +
> drivers/spi/Makefile | 1 +
> drivers/spi/ti-qspi.c | 352 ++++++++++++++++
> include/linux/mtd/spinand.h | 155 +++++++
> 11 files changed, 2398 insertions(+), 0 deletions(-)
> create mode 100644 drivers/mtd/spinand/Kconfig
> create mode 100644 drivers/mtd/spinand/Makefile
> create mode 100644 drivers/mtd/spinand/spinand_lld.c
> create mode 100644 drivers/mtd/spinand/spinand_mtd.c
> create mode 100644 drivers/mtd/spinand/ti-qspi-flash.c
> create mode 100644 drivers/spi/ti-qspi.c
> create mode 100644 include/linux/mtd/spinand.h
>

2013-07-01 05:19:11

by Sourav Poddar

[permalink] [raw]
Subject: Re: [PATCH 1/3] drivers: mtd: spinand: Add generic spinand frameowrk and micron driver.

+ Artem
On Wednesday 26 June 2013 01:11 PM, Sourav Poddar wrote:
> From: Mona Anonuevo<[email protected]>
>
> This patch adds support for a generic spinand framework(spinand_mtd.c).
> This frameowrk can be used for other spi based flash devices also. The idea
> is to have a common model under drivers/mtd, as also present for other no spi
> devices(there is a generic framework and device part simply attaches itself to it.)
>
> The generic frework will be used later by me for a SPI based spansion S25FL256 device.
> The patch also contains a micron driver attaching itself to generic framework.
>
> Signed-off-by: Mona Anonuevo<[email protected]>
> Signed-off-by: Tuan Nguyen<[email protected]>
> Signed-off-by: Sourav Poddar<[email protected]>
> ----
> [I picked this as a standalone patch, can split it into generic and device part
> based on community feedback.]
>
> drivers/mtd/Kconfig | 2 +
> drivers/mtd/Makefile | 2 +
> drivers/mtd/spinand/Kconfig | 24 ++
> drivers/mtd/spinand/Makefile | 10 +
> drivers/mtd/spinand/spinand_lld.c | 776 +++++++++++++++++++++++++++++++++++++
> drivers/mtd/spinand/spinand_mtd.c | 690 +++++++++++++++++++++++++++++++++
> include/linux/mtd/spinand.h | 155 ++++++++
> 7 files changed, 1659 insertions(+), 0 deletions(-)
> create mode 100644 drivers/mtd/spinand/Kconfig
> create mode 100644 drivers/mtd/spinand/Makefile
> create mode 100644 drivers/mtd/spinand/spinand_lld.c
> create mode 100644 drivers/mtd/spinand/spinand_mtd.c
> create mode 100644 include/linux/mtd/spinand.h
>
> diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
> index 5fab4e6..c9e6c60 100644
> --- a/drivers/mtd/Kconfig
> +++ b/drivers/mtd/Kconfig
> @@ -318,6 +318,8 @@ source "drivers/mtd/nand/Kconfig"
>
> source "drivers/mtd/onenand/Kconfig"
>
> +source "drivers/mtd/spinand/Kconfig"
> +
> source "drivers/mtd/lpddr/Kconfig"
>
> source "drivers/mtd/ubi/Kconfig"
> diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
> index 4cfb31e..cce68db 100644
> --- a/drivers/mtd/Makefile
> +++ b/drivers/mtd/Makefile
> @@ -32,4 +32,6 @@ inftl-objs := inftlcore.o inftlmount.o
>
> obj-y += chips/ lpddr/ maps/ devices/ nand/ onenand/ tests/
>
> +obj-y += spinand/
> +
> obj-$(CONFIG_MTD_UBI) += ubi/
> diff --git a/drivers/mtd/spinand/Kconfig b/drivers/mtd/spinand/Kconfig
> new file mode 100644
> index 0000000..38c739f
> --- /dev/null
> +++ b/drivers/mtd/spinand/Kconfig
> @@ -0,0 +1,24 @@
> +#
> +# linux/drivers/mtd/spinand/Kconfig
> +#
> +
> +menuconfig MTD_SPINAND
> + tristate "SPINAND Device Support"
> + depends on MTD
> + help
> + This enables support for accessing Micron SPI NAND flash
> + devices.
> +
> +if MTD_SPINAND
> +
> +config MTD_SPINAND_ONDIEECC
> + bool "Use SPINAND internal ECC"
> + help
> + Internel ECC
> +
> +config MTD_SPINAND_SWECC
> + bool "Use software ECC"
> + depends on MTD_NAND
> + help
> + software ECC
> +endif
> diff --git a/drivers/mtd/spinand/Makefile b/drivers/mtd/spinand/Makefile
> new file mode 100644
> index 0000000..355e726
> --- /dev/null
> +++ b/drivers/mtd/spinand/Makefile
> @@ -0,0 +1,10 @@
> +#
> +# Makefile for the SPI NAND MTD
> +#
> +
> +# Core functionality.
> +obj-$(CONFIG_MTD_SPINAND) += spinand.o
> +
> +spinand-objs := spinand_mtd.o spinand_lld.o
> +
> +
> diff --git a/drivers/mtd/spinand/spinand_lld.c b/drivers/mtd/spinand/spinand_lld.c
> new file mode 100644
> index 0000000..9f53737
> --- /dev/null
> +++ b/drivers/mtd/spinand/spinand_lld.c
> @@ -0,0 +1,776 @@
> +/*
> +spinand_lld.c
> +
> +Copyright (c) 2009-2010 Micron Technology, Inc.
> +
> +This program is free software; you can redistribute it and/or
> +modify it under the terms of the GNU General Public License
> +as published by the Free Software Foundation; either version 2
> +of the License, or (at your option) any later version.
> +
> +This program is distributed in the hope that it will be useful,
> +but WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> +GNU General Public License for more details.
> +
> +*/
> +
> +#include<linux/init.h>
> +#include<linux/module.h>
> +#include<linux/device.h>
> +#include<linux/interrupt.h>
> +#include<linux/mutex.h>
> +#include<linux/math64.h>
> +
> +#include<linux/mtd/mtd.h>
> +#include<linux/mtd/partitions.h>
> +#include<linux/mtd/spinand.h>
> +
> +#include<linux/spi/spi.h>
> +#include<linux/spi/flash.h>
> +
> +#define mu_spi_nand_driver_version "Beagle-MTD_01.00_Linux2.6.33_20100507"
> +#define SPI_NAND_MICRON_DRIVER_KEY 0x1233567
> +
> +/****************************************************************************/
> +
> +/**
> + OOB area specification layout: Total 32 available free bytes.
> +*/
> +static struct nand_ecclayout spinand_oob_64 = {
> + .eccbytes = 24,
> + .eccpos = {
> + 1, 2, 3, 4, 5, 6,
> + 17, 18, 19, 20, 21, 22,
> + 33, 34, 35, 36, 37, 38,
> + 49, 50, 51, 52, 53, 54, },
> + .oobavail = 32,
> + .oobfree = {
> + {.offset = 8,
> + .length = 8},
> + {.offset = 24,
> + .length = 8},
> + {.offset = 40,
> + .length = 8},
> + {.offset = 56,
> + .length = 8}, }
> +};
> +/**
> + * spinand_cmd - to process a command to send to the SPI Nand
> + *
> + * Description:
> + * Set up the command buffer to send to the SPI controller.
> + * The command buffer has to initized to 0
> + */
> +int spinand_cmd(struct spi_device *spi, struct spinand_cmd *cmd)
> +{
> + int ret;
> + struct spi_message message;
> + struct spi_transfer x[4];
> + u8 dummy = 0xff;
> +
> + spi_message_init(&message);
> + memset(x, 0, sizeof(x));
> +
> + x[0].len = 1;
> + x[0].tx_buf =&cmd->cmd;
> + spi_message_add_tail(&x[0],&message);
> +
> + if (cmd->n_addr) {
> + x[1].len = cmd->n_addr;
> + x[1].tx_buf = cmd->addr;
> + spi_message_add_tail(&x[1],&message);
> + }
> +
> + if (cmd->n_dummy) {
> + x[2].len = cmd->n_dummy;
> + x[2].tx_buf =&dummy;
> + spi_message_add_tail(&x[2],&message);
> + }
> +
> + if (cmd->n_tx) {
> + x[3].len = cmd->n_tx;
> + x[3].tx_buf = cmd->tx_buf;
> + spi_message_add_tail(&x[3],&message);
> + }
> +
> + if (cmd->n_rx) {
> + x[3].len = cmd->n_rx;
> + x[3].rx_buf = cmd->rx_buf;
> + spi_message_add_tail(&x[3],&message);
> + }
> +
> + ret = spi_sync(spi,&message);
> +
> + return ret;
> +}
> +
> +/**
> + * spinand_reset- send reset command "0xff" to the Nand device
> + *
> + * Description:
> + * Reset the SPI Nand with the reset command 0xff
> +*/
> +static int spinand_reset(struct spi_device *spi_nand)
> +{
> + struct spinand_cmd cmd = {0};
> +
> + cmd.cmd = CMD_RESET;
> +
> + return spinand_cmd(spi_nand,&cmd);
> +}
> +
> +/**
> + * spinand_read_id- Read SPI Nand ID
> + *
> + * Description:
> + * Read ID: read two ID bytes from the SPI Nand device
> +*/
> +static int spinand_read_id(struct spi_device *spi_nand, u8 *id)
> +{
> + struct spinand_cmd cmd = {0};
> + ssize_t retval;
> +
> + cmd.cmd = CMD_READ_ID;
> + cmd.n_dummy = 1;
> + cmd.n_rx = 2;
> + cmd.rx_buf = id;
> +
> + retval = spinand_cmd(spi_nand,&cmd);
> +
> + if (retval != 0) {
> + dev_err(&spi_nand->dev, "error %d reading id\n",
> + (int) retval);
> + return retval;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * spinand_lock_block- send write register 0x1f command to the Nand device
> + *
> + * Description:
> + * After power up, all the Nand blocks are locked. This function allows
> + * one to unlock the blocks, and so it can be wriiten or erased.
> +*/
> +static int spinand_lock_block(struct spi_device *spi_nand,
> + struct spinand_info *info, u8 lock)
> +{
> + struct spinand_cmd cmd = {0};
> + ssize_t retval;
> +
> + cmd.cmd = CMD_WRITE_REG;
> + cmd.n_addr = 1;
> + cmd.addr[0] = REG_BLOCK_LOCK;
> + cmd.n_tx = 1;
> + cmd.tx_buf =&lock;
> +
> + retval = spinand_cmd(spi_nand,&cmd);
> +
> + if (retval != 0) {
> + dev_err(&spi_nand->dev, "error %d lock block\n",
> + (int) retval);
> + return retval;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * spinand_read_status- send command 0xf to the SPI Nand status register
> + *
> + * Description:
> + * After read, write, or erase, the Nand device is expected to
> + set the busy status.
> + * This function is to allow reading the status of the command:
> + read, write, and erase.
> + * Once the status turns to be ready, the other status bits also
> + are valid status bits.
> +*/
> +static int spinand_read_status(struct spi_device *spi_nand,
> + struct spinand_info *info, u8 *status)
> +{
> + struct spinand_cmd cmd = {0};
> + ssize_t retval;
> +
> + cmd.cmd = CMD_READ_REG;
> + cmd.n_addr = 1;
> + cmd.addr[0] = REG_STATUS;
> + cmd.n_rx = 1;
> + cmd.rx_buf = status;
> +
> + retval = spinand_cmd(spi_nand,&cmd);
> +
> + if (retval != 0) {
> + dev_err(&spi_nand->dev, "error %d reading status register\n",
> + (int) retval);
> + return retval;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * spinand_get_otp- send command 0xf to read the SPI Nand OTP register
> + *
> + * Description:
> + * There is one bit( bit 0x10 ) to set or to clear the internal ECC.
> + * Enable chip internal ECC, set the bit to 1
> + * Disable chip internal ECC, clear the bit to 0
> + */
> +static int spinand_get_otp(struct spi_device *spi_nand,
> + struct spinand_info *info, u8 *otp)
> +{
> + struct spinand_cmd cmd = {0};
> + ssize_t retval;
> +
> + cmd.cmd = CMD_READ_REG;
> + cmd.n_addr = 1;
> + cmd.addr[0] = REG_OTP;
> + cmd.n_rx = 1;
> + cmd.rx_buf = otp;
> +
> + retval = spinand_cmd(spi_nand,&cmd);
> +
> + if (retval != 0) {
> + dev_err(&spi_nand->dev, "error %d get otp\n",
> + (int) retval);
> + return retval;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * spinand_set_otp- send command 0x1f to write the SPI Nand OTP register
> + *
> + * Description:
> + * There is one bit( bit 0x10 ) to set or to clear the internal ECC.
> + * Enable chip internal ECC, set the bit to 1
> + * Disable chip internal ECC, clear the bit to 0
> +*/
> +static int spinand_set_otp(struct spi_device *spi_nand,
> + struct spinand_info *info, u8 *otp)
> +{
> + struct spinand_cmd cmd = {0};
> + ssize_t retval;
> +
> + cmd.cmd = CMD_WRITE_REG;
> + cmd.n_addr = 1;
> + cmd.addr[0] = REG_OTP;
> + cmd.n_tx = 1;
> + cmd.tx_buf = otp;
> +
> + retval = spinand_cmd(spi_nand,&cmd);
> +
> + if (retval != 0) {
> + dev_err(&spi_nand->dev, "error %d set otp\n",
> + (int) retval);
> + return retval;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * sspinand_enable_ecc- send command 0x1f to write the SPI Nand OTP register
> + *
> + * Description:
> + * There is one bit( bit 0x10 ) to set or to clear the internal ECC.
> + * Enable chip internal ECC, set the bit to 1
> + * Disable chip internal ECC, clear the bit to 0
> +*/
> +#ifdef CONFIG_MTD_SPINAND_ONDIEECC
> +static int spinand_enable_ecc(struct spi_device *spi_nand,
> + struct spinand_info *info)
> +{
> + ssize_t retval;
> + u8 otp = 0;
> +
> + retval = spinand_get_otp(spi_nand, info,&otp);
> +
> + if ((otp& OTP_ECC_MASK) == OTP_ECC_MASK) {
> + return 0;
> + } else {
> + otp |= OTP_ECC_MASK;
> + retval = spinand_set_otp(spi_nand, info,&otp);
> + retval = spinand_get_otp(spi_nand, info,&otp);
> + return retval;
> + }
> +}
> +#else
> +static int spinand_disable_ecc(struct spi_device *spi_nand,
> + struct spinand_info *info)
> +{
> + ssize_t retval;
> + u8 otp = 0;
> +
> + retval = spinand_get_otp(spi_nand, info,&otp);
> +
> + if ((otp& OTP_ECC_MASK) == OTP_ECC_MASK) {
> + otp&= ~OTP_ECC_MASK;
> + retval = spinand_set_otp(spi_nand, info,&otp);
> + retval = spinand_get_otp(spi_nand, info,&otp);
> + return retval;
> + } else {
> + return 0;
> + }
> +}
> +#endif
> +
> +/**
> + * sspinand_write_enable- send command 0x06 to enable write or erase the Nand cells
> + *
> + * Description:
> + * Before write and erase the Nand cells, the write enable has to be set.
> + * After the write or erase, the write enable bit is automatically
> + cleared( status register bit 2 )
> + * Set the bit 2 of the status register has the same effect
> +*/
> +static int spinand_write_enable(struct spi_device *spi_nand,
> + struct spinand_info *info)
> +{
> + struct spinand_cmd cmd = {0};
> +
> + cmd.cmd = CMD_WR_ENABLE;
> +
> + return spinand_cmd(spi_nand,&cmd);
> +}
> +
> +static int spinand_read_page_to_cache(struct spi_device *spi_nand,
> + struct spinand_info *info, u16 page_id)
> +{
> + struct spinand_cmd cmd = {0};
> + u16 row;
> +
> + row = page_id;
> +
> + cmd.cmd = CMD_READ;
> + cmd.n_addr = 3;
> + cmd.addr[1] = (u8)((row& 0xff00)>> 8);
> + cmd.addr[2] = (u8)(row& 0x00ff);
> +
> + return spinand_cmd(spi_nand,&cmd);
> +}
> +
> +/**
> + * spinand_read_from_cache- send command 0x03 to read out the data from the
> + cache register( 2112 bytes max )
> + *
> + * Description:
> + * The read can specify 1 to 2112 bytes of data read at the
> + coresponded locations.
> + * No tRd delay.
> +*/
> +static int spinand_read_from_cache(struct spi_device *spi_nand,
> + struct spinand_info *info, u16 byte_id, u16 len, u8 *rbuf)
> +{
> + struct spinand_cmd cmd = {0};
> + u16 column;
> +
> + column = byte_id;
> +
> + cmd.cmd = CMD_READ_RDM;
> + cmd.n_addr = 2;
> + cmd.addr[0] = (u8)((column&0xff00)>>8);
> + cmd.addr[1] = (u8)(column&0x00ff);
> + cmd.n_dummy = 1;
> + cmd.n_rx = len;
> + cmd.rx_buf = rbuf;
> +
> + return spinand_cmd(spi_nand,&cmd);
> +}
> +
> +/**
> + * spinand_read_page-to read a page with:
> + * @page_id: the physical page number
> + * @offset: the location from 0 to 2111
> + * @len: number of bytes to read
> + * @rbuf: read buffer to hold @len bytes
> + *
> + * Description:
> + * The read icludes two commands to the Nand: 0x13 and 0x03 commands
> + * Poll to read status to wait for tRD time.
> + */
> +static int spinand_read_page(struct spi_device *spi_nand,
> + struct spinand_info *info, u16 page_id, u16 offset,
> + u16 len, u8 *rbuf)
> +{
> + ssize_t retval;
> + u8 status = 0;
> +
> + retval = spinand_read_page_to_cache(spi_nand, info, page_id);
> +
> + while (1) {
> + retval = spinand_read_status(spi_nand, info,&status);
> + if (retval< 0) {
> + dev_err(&spi_nand->dev, "error %d reading status register\n",
> + (int) retval);
> + return retval;
> + }
> +
> + if ((status& STATUS_OIP_MASK) == STATUS_READY) {
> + if ((status& STATUS_ECC_MASK) == STATUS_ECC_ERROR) {
> + dev_err(&spi_nand->dev,
> + "ecc error, page=%d\n", page_id);
> + }
> + break;
> + }
> + }
> +
> + retval = spinand_read_from_cache(spi_nand, info, offset, len, rbuf);
> + return 0;
> +}
> +
> +/**
> + * spinand_program_data_to_cache--to write a page to cache with:
> + * @byte_id: the location to write to the cache
> + * @len: number of bytes to write
> + * @rbuf: read buffer to hold @len bytes
> + *
> + * Description:
> + * The write command used here is 0x84--indicating that the cache
> + is not cleared first.
> + * Since it is writing the data to cache, there is no tPROG time.
> + */
> +static int spinand_program_data_to_cache(struct spi_device *spi_nand,
> + struct spinand_info *info, u16 byte_id, u16 len, u8 *wbuf)
> +{
> + struct spinand_cmd cmd = {0};
> + u16 column;
> +
> + column = byte_id;
> +
> + cmd.cmd = CMD_PROG_PAGE_CLRCACHE;
> + cmd.n_addr = 2;
> + cmd.addr[0] = (u8)((column& 0xff00)>> 8);
> + cmd.addr[1] = (u8)(column& 0x00ff);
> + cmd.n_tx = len;
> + cmd.tx_buf = wbuf;
> +
> + return spinand_cmd(spi_nand,&cmd);
> +}
> +
> +/**
> + * spinand_program_execute--to write a page from cache to the Nand array with:
> + * @page_id: the physical page location to write the page.
> + *
> + * Description:
> + * The write command used here is 0x10--indicating the cache is
> + writing to the Nand array.
> + * Need to wait for tPROG time to finish the transaction.
> + */
> +static int spinand_program_execute(struct spi_device *spi_nand,
> + struct spinand_info *info, u16 page_id)
> +{
> + struct spinand_cmd cmd = {0};
> + u16 row;
> +
> + row = page_id;
> +
> + cmd.cmd = CMD_PROG_PAGE_EXC;
> + cmd.n_addr = 3;
> + cmd.addr[1] = (u8)((row& 0xff00)>> 8);
> + cmd.addr[2] = (u8)(row& 0x00ff);
> +
> + return spinand_cmd(spi_nand,&cmd);
> +}
> +
> +/**
> + * spinand_program_page--to write a page with:
> + * @page_id: the physical page location to write the page.
> + * @offset: the location from the cache starting from 0 to 2111
> + * @len: the number of bytes to write
> + * @wbuf: the buffer to hold the number of bytes
> + *
> + * Description:
> + * The commands used here are 0x06, 0x84, and 0x10--indicating that
> + the write enable is first
> + * sent, the write cache command, and the write execute command
> + * Poll to wait for the tPROG time to finish the transaction.
> + */
> +static int spinand_program_page(struct spi_device *spi_nand,
> + struct spinand_info *info, u16 page_id, u16 offset,
> + u16 len, u8 *wbuf)
> +{
> + ssize_t retval;
> + u8 status = 0;
> +
> + retval = spinand_write_enable(spi_nand, info);
> +
> + retval = spinand_program_data_to_cache(spi_nand, info, offset,
> + len, wbuf);
> +
> + retval = spinand_program_execute(spi_nand, info, page_id);
> +
> + while (1) {
> + retval = spinand_read_status(spi_nand, info,&status);
> + if (retval< 0) {
> + dev_err(&spi_nand->dev,
> + "error %d reading status register\n",
> + (int) retval);
> + return retval;
> + }
> +
> + if ((status& STATUS_OIP_MASK) == STATUS_READY) {
> + if ((status& STATUS_P_FAIL_MASK) == STATUS_P_FAIL) {
> + dev_err(&spi_nand->dev,
> + "program error, page=%d\n", page_id);
> + return -1;
> + }
> + } else {
> + break;
> + }
> + }
> + return 0;
> +}
> +
> +/**
> + * spinand_erase_block_erase--to erase a page with:
> + * @block_id: the physical block location to erase.
> + *
> + * Description:
> + * The command used here is 0xd8--indicating an erase
> +command to erase one block--64 pages
> + * Need to wait for tERS.
> + */
> +static int spinand_erase_block_erase(struct spi_device *spi_nand,
> + struct spinand_info *info, u16 block_id)
> +{
> + struct spinand_cmd cmd = {0};
> + u16 row;
> +
> + row = block_id<< 6;
> + cmd.cmd = CMD_ERASE_BLK;
> + cmd.n_addr = 3;
> + cmd.addr[1] = (u8)((row& 0xff00)>> 8);
> + cmd.addr[2] = (u8)(row& 0x00ff);
> +
> + return spinand_cmd(spi_nand,&cmd);
> +}
> +
> +/**
> + * spinand_erase_block--to erase a page with:
> + * @block_id: the physical block location to erase.
> + *
> + * Description:
> + * The commands used here are 0x06 and 0xd8--indicating an erase
> + command to erase one block--64 pages
> + * It will first to enable the write enable bit ( 0x06 command ),
> + and then send the 0xd8 erase command
> + * Poll to wait for the tERS time to complete the tranaction.
> + */
> +static int spinand_erase_block(struct spi_device *spi_nand,
> + struct spinand_info *info, u16 block_id)
> +{
> + ssize_t retval;
> + u8 status = 0;
> +
> + retval = spinand_write_enable(spi_nand, info);
> +
> + retval = spinand_erase_block_erase(spi_nand, info, block_id);
> +
> + while (1) {
> + retval = spinand_read_status(spi_nand, info,&status);
> + if (retval< 0) {
> + dev_err(&spi_nand->dev,
> + "error %d reading status register\n",
> + (int) retval);
> + return retval;
> + }
> +
> + if ((status& STATUS_OIP_MASK) == STATUS_READY) {
> + if ((status& STATUS_E_FAIL_MASK) == STATUS_E_FAIL) {
> + dev_err(&spi_nand->dev,
> + "erase error, block=%d\n", block_id);
> + return -1;
> + } else {
> + break;
> + }
> + }
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * spinand_get_info: get NAND info, from read id or const value
> + * Description:
> + * To set up the device parameters.
> + */
> +static int spinand_get_info(struct spi_device *spi_nand,
> + struct spinand_info *info, u8 *id)
> +{
> + if (id[0] == 0x2C&& (id[1] == 0x11 ||
> + id[1] == 0x12 || id[1] == 0x13)) {
> + info->mid = id[0];
> + info->did = id[1];
> + info->name = "MT29F1G01ZAC";
> + info->nand_size = (1024 * 64 * 2112);
> + info->usable_size = (1024 * 64 * 2048);
> + info->block_size = (2112*64);
> + info->block_main_size = (2048*64);
> + info->block_num_per_chip = 1024;
> + info->page_size = 2112;
> + info->page_main_size = 2048;
> + info->page_spare_size = 64;
> + info->page_num_per_block = 64;
> +
> + info->block_shift = 17;
> + info->block_mask = 0x1ffff;
> +
> + info->page_shift = 11;
> + info->page_mask = 0x7ff;
> +
> + info->ecclayout =&spinand_oob_64;
> + }
> + return 0;
> +}
> +
> +/**
> + * spinand_probe - [spinand Interface]
> + * @spi_nand: registered device driver.
> + *
> + * Description:
> + * To set up the device driver parameters to make the device available.
> +*/
> +static int spinand_probe(struct spi_device *spi_nand)
> +{
> + ssize_t retval;
> + struct mtd_info *mtd;
> + struct spinand_chip *chip;
> + struct spinand_info *info;
> + struct flash_platform_data *data;
> + struct mtd_part_parser_data ppdata;
> + u8 id[2] = {0};
> +
> + retval = spinand_reset(spi_nand);
> + retval = spinand_reset(spi_nand);
> + retval = spinand_read_id(spi_nand, (u8 *)&id);
> + if (id[0] == 0&& id[1] == 0) {
> + pr_info(KERN_INFO "SPINAND: read id error! 0x%02x, 0x%02x!\n",
> + id[0], id[1]);
> + return 0;
> + }
> +
> + data = spi_nand->dev.platform_data;
> + info = kzalloc(sizeof(struct spinand_info), GFP_KERNEL);
> + if (!info)
> + return -ENOMEM;
> +
> + retval = spinand_get_info(spi_nand, info, (u8 *)&id);
> + pr_info(KERN_INFO "SPINAND: 0x%02x, 0x%02x, %s\n",
> + id[0], id[1], info->name);
> + pr_info(KERN_INFO "%s\n", mu_spi_nand_driver_version);
> + retval = spinand_lock_block(spi_nand, info, BL_ALL_UNLOCKED);
> +
> +#ifdef CONFIG_MTD_SPINAND_ONDIEECC
> + retval = spinand_enable_ecc(spi_nand, info);
> +#else
> + retval = spinand_disable_ecc(spi_nand, info);
> +#endif
> +
> + ppdata.of_node = spi_nand->dev.of_node;
> +
> + chip = kzalloc(sizeof(struct spinand_chip), GFP_KERNEL);
> + if (!chip)
> + return -ENOMEM;
> +
> + chip->spi_nand = spi_nand;
> + chip->info = info;
> + chip->reset = spinand_reset;
> + 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_nand->dev, mtd);
> +
> + mtd->priv = chip;
> +
> + retval = spinand_mtd(mtd);
> +
> + return mtd_device_parse_register(mtd, NULL,&ppdata,
> + data ? data->parts : NULL,
> + data ? data->nr_parts : 0);
> +}
> +
> +/**
> + * __devexit spinand_remove--Remove the device driver
> + * @spi: the spi device.
> + *
> + * Description:
> + * To remove the device driver parameters and free up allocated memories.
> + */
> +static int spinand_remove(struct spi_device *spi)
> +{
> + struct mtd_info *mtd;
> + struct spinand_chip *chip;
> +
> + pr_debug("%s: remove\n", dev_name(&spi->dev));
> +
> + 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;
> +}
> +
> +/**
> + * Device name structure description
> +*/
> +static struct spi_driver spinand_driver = {
> + .driver = {
> + .name = "spi_nand",
> + .bus =&spi_bus_type,
> + .owner = THIS_MODULE,
> + },
> +
> + .probe = spinand_probe,
> + .remove = spinand_remove,
> +};
> +
> +/**
> + * Device driver registration
> +*/
> +static int __init spinand_init(void)
> +{
> + return spi_register_driver(&spinand_driver);
> +}
> +
> +/**
> + * unregister Device driver.
> +*/
> +static void __exit spinand_exit(void)
> +{
> + spi_unregister_driver(&spinand_driver);
> +}
> +
> +module_init(spinand_init);
> +module_exit(spinand_exit);
> +
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Henry Pan<[email protected]>");
> +MODULE_DESCRIPTION("SPI NAND driver code");
> diff --git a/drivers/mtd/spinand/spinand_mtd.c b/drivers/mtd/spinand/spinand_mtd.c
> new file mode 100644
> index 0000000..8bfff86
> --- /dev/null
> +++ b/drivers/mtd/spinand/spinand_mtd.c
> @@ -0,0 +1,690 @@
> +/*
> +spinand_mtd.c
> +
> +Copyright (c) 2009-2010 Micron Technology, Inc.
> +
> +This program is free software; you can redistribute it and/or
> +modify it under the terms of the GNU General Public License
> +as published by the Free Software Foundation; either version 2
> +of the License, or (at your option) any later version.
> +
> +This program is distributed in the hope that it will be useful,
> +but WITHOUT ANY WARRANTY; without even the implied warranty of
> +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> +GNU General Public License for more details.
> +*/
> +
> +#include<linux/kernel.h>
> +#include<linux/module.h>
> +#include<linux/init.h>
> +#include<linux/sched.h>
> +#include<linux/delay.h>
> +#include<linux/interrupt.h>
> +#include<linux/jiffies.h>
> +#include<linux/mtd/mtd.h>
> +#include<linux/mtd/partitions.h>
> +#include<linux/mtd/spinand.h>
> +#include<linux/mtd/nand_ecc.h>
> +
> +/**
> + * spinand_get_device - [GENERIC] Get chip for selected access
> + * @param mtd MTD device structure
> + * @param new_state the state which is requested
> + *
> + * Get the device and lock it for exclusive access
> + */
> +#define mu_spi_nand_driver_version "Beagle-MTD_01.00_Linux2.6.33_20100507"
> +
> +static int spinand_get_device(struct mtd_info *mtd, int new_state)
> +{
> + struct spinand_chip *this = mtd->priv;
> + DECLARE_WAITQUEUE(wait, current);
> +
> + /*
> + * Grab the lock and see if the device is available
> + */
> + while (1) {
> + spin_lock(&this->chip_lock);
> + if (this->state == FL_READY) {
> + this->state = new_state;
> + spin_unlock(&this->chip_lock);
> + break;
> + }
> + if (new_state == FL_PM_SUSPENDED) {
> + spin_unlock(&this->chip_lock);
> + return (this->state == FL_PM_SUSPENDED) ? 0 : -EAGAIN;
> + }
> + set_current_state(TASK_UNINTERRUPTIBLE);
> + add_wait_queue(&this->wq,&wait);
> + spin_unlock(&this->chip_lock);
> + schedule();
> + remove_wait_queue(&this->wq,&wait);
> + }
> + return 0;
> +}
> +
> +/**
> + * spinand_release_device - [GENERIC] release chip
> + * @param mtd MTD device structure
> + *
> + * Deselect, release chip lock and wake up anyone waiting on the device
> + */
> +static void spinand_release_device(struct mtd_info *mtd)
> +{
> + struct spinand_chip *this = mtd->priv;
> +
> + /* Release the chip */
> + spin_lock(&this->chip_lock);
> + this->state = FL_READY;
> + wake_up(&this->wq);
> + spin_unlock(&this->chip_lock);
> +}
> +
> +#ifdef CONFIG_MTD_SPINAND_SWECC
> +static void spinand_calculate_ecc(struct mtd_info *mtd)
> +{
> + int i;
> + int eccsize = 512;
> + int eccbytes = 3;
> + int eccsteps = 4;
> + int ecctotal = 12;
> + struct spinand_chip *chip = mtd->priv;
> + struct spinand_info *info = chip->info;
> + unsigned char *p = chip->buf;
> +
> + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
> + __nand_calculate_ecc(p, eccsize,&chip->ecc_calc[i]);
> +
> + for (i = 0; i< ecctotal; i++)
> + chip->buf[info->page_main_size +
> + info->ecclayout->eccpos[i]] = chip->ecc_calc[i];
> +}
> +
> +static int spinand_correct_data(struct mtd_info *mtd)
> +{
> + int i;
> + int eccsize = 512;
> + int eccbytes = 3;
> + int eccsteps = 4;
> + int ecctotal = 12;
> + struct spinand_chip *chip = mtd->priv;
> + struct spinand_info *info = chip->info;
> + unsigned char *p = chip->buf;
> + int errcode = 0;
> +
> + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize)
> + __nand_calculate_ecc(p, eccsize,&chip->ecc_calc[i]);
> +
> + for (i = 0; i< ecctotal; i++)
> + chip->ecc_code[i] = chip->buf[info->page_main_size +
> + info->ecclayout->eccpos[i]];
> +
> + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
> + int stat;
> +
> + stat = __nand_correct_data(p,&chip->ecc_code[i],
> + &chip->ecc_calc[i], eccsize);
> + if (stat< 0)
> + errcode = -1;
> + else if (stat == 1)
> + errcode = 1;
> + }
> + return errcode;
> +}
> +#endif
> +
> +static int spinand_read_ops(struct mtd_info *mtd, loff_t from,
> + struct mtd_oob_ops *ops)
> +{
> + struct spinand_chip *chip = mtd->priv;
> + struct spi_device *spi_nand = chip->spi_nand;
> + struct spinand_info *info = chip->info;
> + int page_id, page_offset, page_num, oob_num;
> +
> + int count;
> + int main_ok, main_left, main_offset;
> + int oob_ok, oob_left;
> +
> + signed int retval;
> + signed int errcode = 0;
> +
> + if (!chip->buf)
> + return -1;
> +
> + page_id = from>> info->page_shift;
> +
> + /* for main data */
> + page_offset = from& info->page_mask;
> + page_num = (page_offset + ops->len +
> + info->page_main_size - 1) / info->page_main_size;
> +
> + /* for oob */
> + if (info->ecclayout->oobavail)
> + oob_num = (ops->ooblen +
> + info->ecclayout->oobavail - 1) / info->ecclayout->oobavail;
> + else
> + oob_num = 0;
> +
> + count = 0;
> +
> + main_left = ops->len;
> + main_ok = 0;
> + main_offset = page_offset;
> +
> + oob_left = ops->ooblen;
> + oob_ok = 0;
> +
> + while (1) {
> + if (count< page_num || count< oob_num) {
> + memset(chip->buf, 0, info->page_size);
> + retval = chip->read_page(spi_nand, info,
> + page_id + count, 0, info->page_size,
> + chip->buf);
> + if (retval != 0) {
> + errcode = -1;
> + pr_info(KERN_INFO
> + "spinand_read_ops: fail, page=%d!\n",
> + page_id);
> + return errcode;
> + }
> + } else {
> + break;
> + }
> + if (count< page_num&& ops->datbuf) {
> + int size;
> +
> +#ifdef CONFIG_MTD_SPINAND_SWECC
> + retval = spinand_correct_data(mtd);
> + if (retval == -1)
> + pr_info(KERN_INFO
> + "SWECC uncorrectable error! page=%x\n",
> + page_id+count);
> + else if (retval == 1)
> + pr_info(KERN_INFO
> + "SWECC 1 bit error, corrected! page=%x\n",
> + page_id+count);
> +#endif
> +
> + if ((main_offset + main_left)< info->page_main_size)
> + size = main_left;
> + else
> + size = info->page_main_size - main_offset;
> +
> + memcpy(ops->datbuf + main_ok, chip->buf, size);
> +
> + main_ok += size;
> + main_left -= size;
> + main_offset = 0;
> + ops->retlen = main_ok;
> + }
> +
> + if (count< oob_num&& ops->oobbuf&& chip->oobbuf) {
> + int size;
> + int offset, len, temp;
> +
> + /* repack spare to oob */
> + memset(chip->oobbuf, 0, info->ecclayout->oobavail);
> +
> + temp = 0;
> + offset = info->ecclayout->oobfree[0].offset;
> + len = info->ecclayout->oobfree[0].length;
> + memcpy(chip->oobbuf + temp,
> + chip->buf + info->page_main_size + offset, len);
> +
> + temp += len;
> + offset = info->ecclayout->oobfree[1].offset;
> + len = info->ecclayout->oobfree[1].length;
> + memcpy(chip->oobbuf + temp,
> + chip->buf + info->page_main_size + offset, len);
> +
> + temp += len;
> + offset = info->ecclayout->oobfree[2].offset;
> + len = info->ecclayout->oobfree[2].length;
> + memcpy(chip->oobbuf + temp,
> + chip->buf + info->page_main_size + offset, len);
> +
> + temp += len;
> + offset = info->ecclayout->oobfree[3].offset;
> + len = info->ecclayout->oobfree[3].length;
> + memcpy(chip->oobbuf + temp,
> + chip->buf + info->page_main_size + offset, len);
> +
> + /* copy oobbuf to ops oobbuf */
> + if (oob_left< info->ecclayout->oobavail)
> + size = oob_left;
> + else
> + size = info->ecclayout->oobavail;
> +
> + memcpy(ops->oobbuf + oob_ok, chip->oobbuf, size);
> +
> + oob_ok += size;
> + oob_left -= size;
> +
> + ops->oobretlen = oob_ok;
> + }
> + count++;
> + }
> + return errcode;
> +}
> +
> +static int spinand_write_ops(struct mtd_info *mtd, loff_t to,
> + struct mtd_oob_ops *ops)
> +{
> + struct spinand_chip *chip = mtd->priv;
> + struct spi_device *spi_nand = chip->spi_nand;
> + struct spinand_info *info = chip->info;
> + int page_id, page_offset, page_num, oob_num;
> +
> + int count;
> +
> + int main_ok, main_left, main_offset;
> + int oob_ok, oob_left;
> +
> + signed int retval;
> + signed int errcode = 0;
> +
> + if (!chip->buf)
> + return -1;
> +
> + page_id = to>> info->page_shift;
> +
> + /* for main data */
> + page_offset = to& info->page_mask;
> + page_num = (page_offset + ops->len +
> + info->page_main_size - 1) / info->page_main_size;
> +
> + /* for oob */
> + if (info->ecclayout->oobavail)
> + oob_num = (ops->ooblen +
> + info->ecclayout->oobavail - 1) / info->ecclayout->oobavail;
> + else
> + oob_num = 0;
> +
> + count = 0;
> +
> + main_left = ops->len;
> + main_ok = 0;
> + main_offset = page_offset;
> +
> + oob_left = ops->ooblen;
> + oob_ok = 0;
> +
> + while (1) {
> + if (count< page_num || count< oob_num)
> + memset(chip->buf, 0xFF, info->page_size);
> + else
> + break;
> +
> + if (count< page_num&& ops->datbuf) {
> + int size;
> +
> + if ((main_offset + main_left)< info->page_main_size)
> + size = main_left;
> + else
> + size = info->page_main_size - main_offset;
> +
> + memcpy(chip->buf, ops->datbuf + main_ok, size);
> +
> + main_ok += size;
> + main_left -= size;
> + main_offset = 0;
> +
> +#ifdef CONFIG_MTD_SPINAND_SWECC
> + spinand_calculate_ecc(mtd);
> +#endif
> + }
> +
> + if (count< oob_num&& ops->oobbuf&& chip->oobbuf) {
> + int size;
> + int offset, len, temp;
> +
> + memset(chip->oobbuf, 0xFF, info->ecclayout->oobavail);
> +
> + if (oob_left< info->ecclayout->oobavail)
> + size = oob_left;
> + else
> + size = info->ecclayout->oobavail;
> +
> + memcpy(chip->oobbuf, ops->oobbuf + oob_ok, size);
> +
> + oob_ok += size;
> + oob_left -= size;
> +
> + /* repack oob to spare */
> + temp = 0;
> + offset = info->ecclayout->oobfree[0].offset;
> + len = info->ecclayout->oobfree[0].length;
> + memcpy(chip->buf + info->page_main_size + offset,
> + chip->oobbuf + temp, len);
> +
> + temp += len;
> + offset = info->ecclayout->oobfree[1].offset;
> + len = info->ecclayout->oobfree[1].length;
> + memcpy(chip->buf + info->page_main_size + offset,
> + chip->oobbuf + temp, len);
> +
> + temp += len;
> + offset = info->ecclayout->oobfree[2].offset;
> + len = info->ecclayout->oobfree[2].length;
> + memcpy(chip->buf + info->page_main_size + offset,
> + chip->oobbuf + temp, len);
> +
> + temp += len;
> + offset = info->ecclayout->oobfree[3].offset;
> + len = info->ecclayout->oobfree[3].length;
> + memcpy(chip->buf + info->page_main_size + offset,
> + chip->oobbuf + temp, len);
> + }
> +
> + if (count< page_num || count< oob_num) {
> + retval = chip->program_page(spi_nand, info,
> + page_id + count, 0, info->page_size, chip->buf);
> + if (retval != 0) {
> + errcode = -1;
> + pr_err(KERN_INFO "spinand_write_ops: fail, page=%d!\n", page_id);
> +
> + return errcode;
> + }
> + }
> +
> + if (count< page_num&& ops->datbuf)
> + ops->retlen = main_ok;
> +
> + if (count< oob_num&& ops->oobbuf&& chip->oobbuf)
> + ops->oobretlen = oob_ok;
> +
> + count++;
> + }
> + return errcode;
> +}
> +
> +static int spinand_read(struct mtd_info *mtd, loff_t from, size_t len,
> + size_t *retlen, u_char *buf)
> +{
> + struct mtd_oob_ops ops = {0};
> + int ret;
> +
> + /* Do not allow reads past end of device */
> + if ((from + len)> mtd->size)
> + return -EINVAL;
> +
> + if (!len)
> + return 0;
> +
> + spinand_get_device(mtd, FL_READING);
> +
> + ops.len = len;
> + ops.datbuf = buf;
> +
> + ret = spinand_read_ops(mtd, from,&ops);
> +
> + *retlen = ops.retlen;
> +
> + spinand_release_device(mtd);
> +
> + return ret;
> +}
> +
> +static int spinand_write(struct mtd_info *mtd, loff_t to, size_t len,
> + size_t *retlen, const u_char *buf)
> +{
> + struct mtd_oob_ops ops = {0};
> + int ret;
> +
> + /* Do not allow reads past end of device */
> + if ((to + len)> mtd->size)
> + return -EINVAL;
> + if (!len)
> + return 0;
> +
> + spinand_get_device(mtd, FL_WRITING);
> +
> + ops.len = len;
> + ops.datbuf = (uint8_t *)buf;
> +
> + ret = spinand_write_ops(mtd, to,&ops);
> +
> + *retlen = ops.retlen;
> +
> + spinand_release_device(mtd);
> +
> + return ret;
> +}
> +
> +static int spinand_read_oob(struct mtd_info *mtd, loff_t from,
> + struct mtd_oob_ops *ops)
> +{
> + int ret;
> +
> + spinand_get_device(mtd, FL_READING);
> +
> + ret = spinand_read_ops(mtd, from, ops);
> +
> + spinand_release_device(mtd);
> + return ret;
> +}
> +
> +static int spinand_write_oob(struct mtd_info *mtd, loff_t to,
> + struct mtd_oob_ops *ops)
> +{
> + int ret;
> +
> + spinand_get_device(mtd, FL_WRITING);
> +
> + ret = spinand_write_ops(mtd, to, ops);
> +
> + spinand_release_device(mtd);
> + return ret;
> +}
> +
> +/**
> + * spinand_erase - [MTD Interface] erase block(s)
> + * @param mtd MTD device structure
> + * @param instr erase instruction
> + *
> + * Erase one ore more blocks
> + */
> +static int spinand_erase(struct mtd_info *mtd, struct erase_info *instr)
> +{
> + struct spinand_chip *chip = mtd->priv;
> + struct spi_device *spi_nand = chip->spi_nand;
> + struct spinand_info *info = chip->info;
> + u16 block_id, block_num, count;
> + signed int retval = 0;
> + signed int errcode = 0;
> +
> + pr_info("spinand_erase: start = 0x%012llx, len = %llu\n",
> + (unsigned long long)instr->addr, (unsigned long long)instr->len);
> +
> + /* check address align on block boundary */
> + if (instr->addr& (info->block_main_size - 1)) {
> + pr_err("spinand_erase: Unaligned address\n");
> + return -EINVAL;
> + }
> +
> + if (instr->len& (info->block_main_size - 1)) {
> + pr_err("spinand_erase: ""Length not block aligned\n");
> + return -EINVAL;
> + }
> +
> + /* Do not allow erase past end of device */
> + if ((instr->len + instr->addr)> info->usable_size) {
> + pr_err("spinand_erase: ""Erase past end of device\n");
> + return -EINVAL;
> + }
> +
> + instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
> +
> + /* Grab the lock and see if the device is available */
> + spinand_get_device(mtd, FL_ERASING);
> +
> + block_id = instr->addr>> info->block_shift;
> + block_num = instr->len>> info->block_shift;
> + count = 0;
> +
> + while (count< block_num) {
> + retval = chip->erase_block(spi_nand, info, block_id + count);
> +
> + if (retval != 0) {
> + retval = chip->erase_block(spi_nand, info,
> + block_id + count);
> + if (retval != 0) {
> + pr_info(KERN_INFO "spinand_erase: fail, block=%d!\n",
> + block_id + count);
> + errcode = -1;
> + }
> + }
> + count++;
> + }
> +
> + if (errcode == 0)
> + instr->state = MTD_ERASE_DONE;
> +
> + /* Deselect and wake up anyone waiting on the device */
> + spinand_release_device(mtd);
> +
> + /* Do call back function */
> + if (instr->callback)
> + instr->callback(instr);
> +
> + return errcode;
> +}
> +
> +/**
> + * spinand_sync - [MTD Interface] sync
> + * @param mtd MTD device structure
> + *
> + * Sync is actually a wait for chip ready function
> + */
> +static void spinand_sync(struct mtd_info *mtd)
> +{
> + pr_debug("spinand_sync: called\n");
> +
> + /* Grab the lock and see if the device is available */
> + spinand_get_device(mtd, FL_SYNCING);
> +
> + /* Release it and go back */
> + spinand_release_device(mtd);
> +}
> +
> +static int spinand_block_isbad(struct mtd_info *mtd, loff_t ofs)
> +{
> + struct spinand_chip *chip = mtd->priv;
> + struct spi_device *spi_nand = chip->spi_nand;
> + struct spinand_info *info = chip->info;
> + u16 block_id;
> + u8 is_bad = 0x00;
> + u8 ret = 0;
> +
> + spinand_get_device(mtd, FL_READING);
> +
> + block_id = ofs>> info->block_shift;
> +
> + chip->read_page(spi_nand, info, block_id*info->page_num_per_block,
> + info->page_main_size, 1,&is_bad);
> +
> + if (is_bad != 0xFF)
> + ret = 1;
> +
> + spinand_release_device(mtd);
> +
> + return ret;
> +}
> +
> +/**
> + * spinand_block_markbad - [MTD Interface] Mark bad block
> + * @param mtd MTD device structure
> + * @param ofs Bad block number
> + */
> +static int spinand_block_markbad(struct mtd_info *mtd, loff_t ofs)
> +{
> + struct spinand_chip *chip = mtd->priv;
> + struct spi_device *spi_nand = chip->spi_nand;
> + struct spinand_info *info = chip->info;
> + u16 block_id;
> + u8 is_bad = 0x00;
> + u8 ret = 0;
> +
> + spinand_get_device(mtd, FL_WRITING);
> +
> + block_id = ofs>> info->block_shift;
> +
> + chip->program_page(spi_nand, info, block_id*info->page_num_per_block,
> + info->page_main_size, 1,&is_bad);
> +
> + spinand_release_device(mtd);
> +
> + return ret;
> +}
> +
> +
> +/**
> + * spinand_suspend - [MTD Interface] Suspend the spinand flash
> + * @param mtd MTD device structure
> + */
> +static int spinand_suspend(struct mtd_info *mtd)
> +{
> + return spinand_get_device(mtd, FL_PM_SUSPENDED);
> +}
> +
> +/**
> + * spinand_resume - [MTD Interface] Resume the spinand flash
> + * @param mtd MTD device structure
> + */
> +static void spinand_resume(struct mtd_info *mtd)
> +{
> + struct spinand_chip *this = mtd->priv;
> +
> + if (this->state == FL_PM_SUSPENDED)
> + spinand_release_device(mtd);
> + else
> + pr_err(KERN_ERR "resume() called for the chip which is not" "in suspended state\n");
> +}
> +
> +/**
> + * spinand_mtd - add MTD device with parameters
> + * @param mtd MTD device structure
> + *
> + * Add MTD device with parameters.
> + */
> +int spinand_mtd(struct mtd_info *mtd)
> +{
> + struct spinand_chip *chip = mtd->priv;
> + struct spinand_info *info = chip->info;
> +
> + chip->state = FL_READY;
> + init_waitqueue_head(&chip->wq);
> + spin_lock_init(&chip->chip_lock);
> +
> + mtd->name = info->name;
> + mtd->size = info->usable_size;
> + mtd->erasesize = info->block_main_size;
> + mtd->writesize = info->page_main_size;
> + mtd->oobsize = info->page_spare_size;
> + mtd->owner = THIS_MODULE;
> + mtd->type = MTD_NANDFLASH;
> + mtd->flags = MTD_CAP_NANDFLASH;
> +
> + mtd->ecclayout = info->ecclayout;
> +
> + mtd->_erase = spinand_erase;
> + mtd->_point = NULL;
> + mtd->_unpoint = NULL;
> + mtd->_read = spinand_read;
> + mtd->_write = spinand_write;
> + mtd->_read_oob = spinand_read_oob;
> + mtd->_write_oob = spinand_write_oob;
> + mtd->_sync = spinand_sync;
> + mtd->_lock = NULL;
> + mtd->_unlock = NULL;
> + mtd->_suspend = spinand_suspend;
> + mtd->_resume = spinand_resume;
> + mtd->_block_isbad = spinand_block_isbad;
> + mtd->_block_markbad = spinand_block_markbad;
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(spinand_mtd);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Henry Pan<[email protected]>");
> diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
> new file mode 100644
> index 0000000..3b8802a
> --- /dev/null
> +++ b/include/linux/mtd/spinand.h
> @@ -0,0 +1,155 @@
> +/*
> + * linux/include/linux/mtd/spinand.h
> + * Copyright (c) 2009-2010 Micron Technology, Inc.
> + * This software is licensed under the terms of the GNU General Public
> + * License version 2, as published by the Free Software Foundation, and
> + * may be copied, distributed, and modified under those terms.
> +
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> +/bin/bash: 4: command not found
> + *
> + * based on nand.h
> + */
> +#ifndef __LINUX_MTD_SPI_NAND_H
> +#define __LINUX_MTD_SPI_NAND_H
> +
> +#include<linux/wait.h>
> +#include<linux/spinlock.h>
> +#include<linux/mtd/mtd.h>
> +
> +/* cmd */
> +#define CMD_READ 0x13
> +#define CMD_READ_RDM 0x03
> +#define CMD_PROG_PAGE_CLRCACHE 0x02
> +#define CMD_PROG_PAGE 0x84
> +#define CMD_PROG_PAGE_EXC 0x10
> +#define CMD_ERASE_BLK 0xd8
> +#define CMD_WR_ENABLE 0x06
> +#define CMD_WR_DISABLE 0x04
> +#define CMD_READ_ID 0x9f
> +#define CMD_RESET 0xff
> +#define CMD_READ_REG 0x0f
> +#define CMD_WRITE_REG 0x1f
> +
> +/* feature/ status reg */
> +#define REG_BLOCK_LOCK 0xa0
> +#define REG_OTP 0xb0
> +#define REG_STATUS 0xc0/* timing */
> +
> +/* status */
> +#define STATUS_OIP_MASK 0x01
> +#define STATUS_READY (0<< 0)
> +#define STATUS_BUSY (1<< 0)
> +
> +#define STATUS_E_FAIL_MASK 0x04
> +#define STATUS_E_FAIL (1<< 2)
> +
> +#define STATUS_P_FAIL_MASK 0x08
> +#define STATUS_P_FAIL (1<< 3)
> +
> +#define STATUS_ECC_MASK 0x30
> +#define STATUS_ECC_1BIT_CORRECTED (1<< 4)
> +#define STATUS_ECC_ERROR (2<< 4)
> +#define STATUS_ECC_RESERVED (3<< 4)
> +
> +
> +/*ECC enable defines*/
> +#define OTP_ECC_MASK 0x10
> +#define OTP_ECC_OFF 0
> +#define OTP_ECC_ON 1
> +
> +#define ECC_DISABLED
> +#define ECC_IN_NAND
> +#define ECC_SOFT
> +
> +/* block lock */
> +#define BL_ALL_LOCKED 0x38
> +#define BL_1_2_LOCKED 0x30
> +#define BL_1_4_LOCKED 0x28
> +#define BL_1_8_LOCKED 0x20
> +#define BL_1_16_LOCKED 0x18
> +#define BL_1_32_LOCKED 0x10
> +#define BL_1_64_LOCKED 0x08
> +#define BL_ALL_UNLOCKED 0
> +
> +struct spinand_info {
> + u8 mid;
> + u8 did;
> + char *name;
> + u64 nand_size;
> + u64 usable_size;
> +
> + u32 block_size;
> + u32 block_main_size;
> + /*u32 block_spare_size; */
> + u16 block_num_per_chip;
> + u16 page_size;
> + u16 page_main_size;
> + u16 page_spare_size;
> + u16 page_num_per_block;
> + u8 block_shift;
> + u32 block_mask;
> + u8 page_shift;
> + u16 page_mask;
> +
> + struct nand_ecclayout *ecclayout;
> +};
> +
> +typedef enum {
> + FL_READY,
> + FL_READING,
> + FL_WRITING,
> + FL_ERASING,
> + FL_SYNCING,
> + FL_LOCKING,
> + FL_RESETING,
> + FL_OTPING,
> + FL_PM_SUSPENDED,
> +} spinand_state_t;
> +
> +struct spinand_chip { /* used for multi chip */
> + spinlock_t chip_lock;
> + wait_queue_head_t wq;
> + spinand_state_t state;
> + struct spi_device *spi_nand;
> + struct spinand_info *info;
> + /*struct mtd_info *mtd; */
> +
> + int (*reset) (struct spi_device *spi_nand);
> + int (*read_id) (struct spi_device *spi_nand, u8 *id);
> + int (*read_page) (struct spi_device *spi_nand,
> + struct spinand_info *info, u16 page_id, u16 offset,
> + u16 len, u8 *rbuf);
> + int (*program_page) (struct spi_device *spi_nand,
> + struct spinand_info *info, u16 page_id, u16 offset,
> + u16 len, u8 *wbuf);
> + int (*erase_block) (struct spi_device *spi_nand,
> + struct spinand_info *info, u16 block_id);
> +
> + u8 *buf;
> + u8 *oobbuf; /* temp buffer */
> +
> +#ifdef CONFIG_MTD_SPINAND_SWECC
> + u8 ecc_calc[12];
> + u8 ecc_code[12];
> +#endif
> +};
> +
> +struct spinand_cmd {
> + u8 cmd;
> + unsigned n_addr;
> + u8 addr[3];
> + unsigned n_dummy;
> + unsigned n_tx;
> + u8 *tx_buf;
> + unsigned n_rx;
> + u8 *rx_buf;
> +};
> +
> +extern int spinand_mtd(struct mtd_info *mtd);
> +extern void spinand_mtd_release(struct mtd_info *mtd);
> +
> +#endif

2013-07-01 05:19:56

by Sourav Poddar

[permalink] [raw]
Subject: Re: [PATCH 3/3] drivers: mtd: spinand: Add qspi spansion flash controller

+ Artem
On Wednesday 26 June 2013 01:11 PM, Sourav Poddar wrote:
> 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<[email protected]>
> ---
> 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, [email protected]
> + *
> + * 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");

2013-07-01 05:20:09

by Sourav Poddar

[permalink] [raw]
Subject: Re: [PATCH 2/3] drivers: spi: Add qspi flash controller

+ Artem
On Wednesday 26 June 2013 01:11 PM, Sourav Poddar wrote:
> The patch add basic support for the quad spi controller.
>
> QSPI is a kind of spi module that allows single,
> dual and quad read access to external spi devices. The module
> has a memory mapped interface which provide direct interface
> for accessing data form external spi devices.
>
> The patch will configure controller clocks, device control
> register and for defining low level transfer apis which
> will be used by the spi framework to transfer data to
> the slave spi device(flash in this case).
>
> Signed-off-by: Sourav Poddar<[email protected]>
> ---
> drivers/spi/Kconfig | 6 +
> drivers/spi/Makefile | 1 +
> drivers/spi/ti-qspi.c | 352 +++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 359 insertions(+), 0 deletions(-)
> create mode 100644 drivers/spi/ti-qspi.c
>
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 92a9345..29a363b 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -285,6 +285,12 @@ config SPI_OMAP24XX
> SPI master controller for OMAP24XX and later Multichannel SPI
> (McSPI) modules.
>
> +config QSPI_DRA7xxx
> + tristate "DRA7xxx QSPI controller support"
> + depends on ARCH_OMAP2PLUS
> + help
> + QSPI master controller for DRA7xxx used for flash devices.
> +
> config SPI_OMAP_100K
> tristate "OMAP SPI 100K"
> depends on ARCH_OMAP850 || ARCH_OMAP730
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index 33f9c09..ea14eff 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -46,6 +46,7 @@ obj-$(CONFIG_SPI_OCTEON) += spi-octeon.o
> obj-$(CONFIG_SPI_OMAP_UWIRE) += spi-omap-uwire.o
> obj-$(CONFIG_SPI_OMAP_100K) += spi-omap-100k.o
> obj-$(CONFIG_SPI_OMAP24XX) += spi-omap2-mcspi.o
> +obj-$(CONFIG_QSPI_DRA7xxx) += ti-qspi.o
> obj-$(CONFIG_SPI_ORION) += spi-orion.o
> obj-$(CONFIG_SPI_PL022) += spi-pl022.o
> obj-$(CONFIG_SPI_PPC4xx) += spi-ppc4xx.o
> diff --git a/drivers/spi/ti-qspi.c b/drivers/spi/ti-qspi.c
> new file mode 100644
> index 0000000..b33646a
> --- /dev/null
> +++ b/drivers/spi/ti-qspi.c
> @@ -0,0 +1,352 @@
> +/*
> + * TI QSPI driver
> + *
> + * Copyright (C) 2013, Texas Instruments, Incorporated
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation; either version 2 of
> + * the License, or (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR /PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#include<linux/kernel.h>
> +#include<linux/init.h>
> +#include<linux/interrupt.h>
> +#include<linux/module.h>
> +#include<linux/device.h>
> +#include<linux/delay.h>
> +#include<linux/dma-mapping.h>
> +#include<linux/dmaengine.h>
> +#include<linux/omap-dma.h>
> +#include<linux/platform_device.h>
> +#include<linux/err.h>
> +#include<linux/clk.h>
> +#include<linux/io.h>
> +#include<linux/slab.h>
> +#include<linux/pm_runtime.h>
> +#include<linux/of.h>
> +#include<linux/of_device.h>
> +#include<linux/pinctrl/consumer.h>
> +
> +#include<linux/spi/spi.h>
> +
> +struct dra7xxx_qspi {
> + struct spi_master *master;
> + void __iomem *base;
> + int device_type;
> + struct device *dev;
> + u32 spi_max_frequency;
> + u32 cmd;
> + u32 dc;
> +};
> +
> +#define QSPI_PID (0x0)
> +#define QSPI_SYSCONFIG (0x10)
> +#define QSPI_INTR_STATUS_RAW_SET (0x20)
> +#define QSPI_INTR_STATUS_ENABLED_CLEAR (0x24)
> +#define QSPI_INTR_ENABLE_SET_REG (0x28)
> +#define QSPI_INTR_ENABLE_CLEAR_REG (0x2c)
> +#define QSPI_SPI_CLOCK_CNTRL_REG (0x40)
> +#define QSPI_SPI_DC_REG (0x44)
> +#define QSPI_SPI_CMD_REG (0x48)
> +#define QSPI_SPI_STATUS_REG (0x4c)
> +#define QSPI_SPI_DATA_REG (0x50)
> +#define QSPI_SPI_SETUP0_REG (0x54)
> +#define QSPI_SPI_SWITCH_REG (0x64)
> +#define QSPI_SPI_SETUP1_REG (0x58)
> +#define QSPI_SPI_SETUP2_REG (0x5c)
> +#define QSPI_SPI_SETUP3_REG (0x60)
> +#define QSPI_SPI_DATA_REG_1 (0x68)
> +#define QSPI_SPI_DATA_REG_2 (0x6c)
> +#define QSPI_SPI_DATA_REG_3 (0x70)
> +
> +#define QSPI_TIMEOUT 2000000
> +
> +#define QSPI_FCLK 192000000
> +
> +/* Clock Control */
> +#define QSPI_CLK_EN (1<< 31)
> +#define QSPI_CLK_DIV_MAX 0xffff
> +
> +/* Command */
> +#define QSPI_EN_CS(n) (n<< 28)
> +#define QSPI_WLEN(n) ((n-1)<< 19)
> +#define QSPI_3_PIN (1<< 18)
> +#define QSPI_RD_SNGL (1<< 16)
> +#define QSPI_WR_SNGL (2<< 16)
> +#define QSPI_RD_QUAD (7<< 16)
> +#define QSPI_INVAL (4<< 16)
> +
> +/* Device Control */
> +#define QSPI_DD(m, n) (m<< (3 + n*8))
> +#define QSPI_CKPHA(n) (1<< (2 + n*8))
> +#define QSPI_CSPOL(n) (1<< (1 + n*8))
> +#define QSPI_CKPOL(n) (1<< (n*8))
> +
> +/* Status */
> +#define QSPI_WC (1<< 1)
> +#define QSPI_BUSY (1<< 0)
> +#define QSPI_WC_BUSY (QSPI_WC | QSPI_BUSY)
> +#define QSPI_XFER_DONE QSPI_WC
> +
> +#define XFER_END 0x01
> +
> +#define SPI_AUTOSUSPEND_TIMEOUT 2000
> +
> +static inline unsigned long dra7xxx_readl(struct dra7xxx_qspi *qspi,
> + unsigned long reg)
> +{
> + return readl(qspi->base + reg);
> +}
> +
> +static inline void dra7xxx_writel(struct dra7xxx_qspi *qspi,
> + unsigned long val, unsigned long reg)
> +{
> + writel(val, qspi->base + reg);
> +}
> +
> +static int dra7xxx_qspi_setup(struct spi_device *spi)
> +{
> + struct dra7xxx_qspi *qspi =
> + spi_master_get_devdata(spi->master);
> +
> + int clk_div;
> +
> + if (!qspi->spi_max_frequency)
> + clk_div = 0;
> + else
> + clk_div = (QSPI_FCLK / qspi->spi_max_frequency) - 1;
> +
> + pr_debug("%s: hz: %d, clock divider %d\n", __func__,
> + qspi->spi_max_frequency, clk_div);
> +
> + pm_runtime_get_sync(qspi->dev);
> +
> + /* disable SCLK */
> + dra7xxx_writel(qspi, dra7xxx_readl(qspi, QSPI_SPI_CLOCK_CNTRL_REG)
> + & ~QSPI_CLK_EN, QSPI_SPI_CLOCK_CNTRL_REG);
> +
> + if (clk_div< 0) {
> + pr_debug("%s: clock divider< 0, using /1 divider\n", __func__);
> + clk_div = 0;
> + }
> +
> + if (clk_div> QSPI_CLK_DIV_MAX) {
> + pr_debug("%s: clock divider>%d , using /%d divider\n",
> + __func__, QSPI_CLK_DIV_MAX, QSPI_CLK_DIV_MAX + 1);
> + clk_div = QSPI_CLK_DIV_MAX;
> + }
> +
> + /* enable SCLK */
> + dra7xxx_writel(qspi, QSPI_CLK_EN | clk_div, QSPI_SPI_CLOCK_CNTRL_REG);
> +
> + pm_runtime_mark_last_busy(qspi->dev);
> + pm_runtime_put_autosuspend(qspi->dev);
> +
> + pr_debug("%s: spi_clock_cntrl %ld\n", __func__,
> + dra7xxx_readl(qspi, QSPI_SPI_CLOCK_CNTRL_REG));
> +
> + return 0;
> +}
> +
> +static int dra7xxx_qspi_prepare_xfer(struct spi_master *master)
> +{
> + return 0;
> +}
> +
> +static int dra7xxx_qspi_unprepare_xfer(struct spi_master *master)
> +{
> + return 0;
> +}
> +
> +static int qspi_transfer_msg(struct dra7xxx_qspi *qspi, unsigned count,
> + const u8 *txbuf, u8 *rxbuf, bool flags)
> +{
> + uint status;
> + int timeout;
> +
> + pm_runtime_get_sync(qspi->dev);
> +
> + while (count--) {
> + if (txbuf) {
> + pr_debug("tx cmd %08x dc %08x data %02x\n",
> + qspi->cmd | QSPI_WR_SNGL, qspi->dc, *txbuf);
> + dra7xxx_writel(qspi, *txbuf++, QSPI_SPI_DATA_REG);
> + dra7xxx_writel(qspi, qspi->dc, QSPI_SPI_DC_REG);
> + dra7xxx_writel(qspi, qspi->cmd | QSPI_WR_SNGL,
> + QSPI_SPI_CMD_REG);
> + status = dra7xxx_readl(qspi, QSPI_SPI_STATUS_REG);
> + timeout = QSPI_TIMEOUT;
> + while ((status& QSPI_WC_BUSY) != QSPI_XFER_DONE) {
> + if (--timeout< 0) {
> + pr_debug("QSPI tx timed out\n");
> + return -1;
> + }
> + status = dra7xxx_readl(qspi, QSPI_SPI_STATUS_REG);
> + }
> + pr_debug("tx done, status %08x\n", status);
> + }
> + if (rxbuf) {
> + pr_debug("rx cmd %08x dc %08x\n",
> + qspi->cmd | QSPI_RD_SNGL, qspi->dc);
> + dra7xxx_writel(qspi, qspi->dc, QSPI_SPI_DC_REG);
> + dra7xxx_writel(qspi, qspi->cmd | QSPI_RD_SNGL,
> + QSPI_SPI_CMD_REG);
> + status = dra7xxx_readl(qspi, QSPI_SPI_STATUS_REG);
> + timeout = QSPI_TIMEOUT;
> + while ((status& QSPI_WC_BUSY) != QSPI_XFER_DONE) {
> + if (--timeout< 0) {
> + pr_debug("QSPI rx timed out\n");
> + return -1;
> + }
> + status = dra7xxx_readl(qspi, QSPI_SPI_STATUS_REG);
> + }
> + *rxbuf++ = dra7xxx_readl(qspi, QSPI_SPI_DATA_REG);
> + pr_debug("rx done, status %08x, read %02x\n",
> + status, *(rxbuf-1));
> + }
> + }
> +
> + if (flags& XFER_END)
> + dra7xxx_writel(qspi, qspi->cmd | QSPI_INVAL, QSPI_SPI_CMD_REG);
> +
> + pm_runtime_mark_last_busy(qspi->dev);
> + pm_runtime_put_autosuspend(qspi->dev);
> +
> + return 0;
> +}
> +
> +static int dra7xxx_qspi_start_transfer_one(struct spi_master *master,
> + struct spi_message *m)
> +{
> + struct dra7xxx_qspi *qspi = spi_master_get_devdata(master);
> + struct spi_device *spi = m->spi;
> + struct spi_transfer *t;
> + int status = 0;
> + int flags = 0;
> +
> + /* setup command reg */
> + qspi->cmd = 0;
> + qspi->cmd |= QSPI_WLEN(8);
> + qspi->cmd |= QSPI_EN_CS(0);
> + qspi->cmd |= 0xfff;
> +
> + /* setup device control reg */
> + qspi->dc = 0;
> +
> + if (spi->mode& SPI_CPHA)
> + qspi->dc |= QSPI_CKPHA(0);
> + if (spi->mode& SPI_CPOL)
> + qspi->dc |= QSPI_CKPOL(0);
> + if (spi->mode& SPI_CS_HIGH)
> + qspi->dc |= QSPI_CSPOL(0);
> +
> + list_for_each_entry(t,&m->transfers, transfer_list) {
> + if (list_is_last(&t->transfer_list,&m->transfers))
> + flags = XFER_END;
> +
> + qspi_transfer_msg(qspi, t->len, t->tx_buf,
> + t->rx_buf, flags);
> +
> + m->actual_length += t->len;
> + }
> + m->status = status;
> + spi_finalize_current_message(master);
> +
> + return status;
> +}
> +
> +static int dra7xxx_qspi_probe(struct platform_device *pdev)
> +{
> + struct dra7xxx_qspi *qspi;
> + struct spi_master *master;
> + struct resource *r;
> + struct device_node *np = pdev->dev.of_node;
> + u32 max_freq;
> + int ret;
> +
> + master = spi_alloc_master(&pdev->dev, sizeof(*qspi));
> + if (!master)
> + return -ENOMEM;
> +
> + master->mode_bits = SPI_CPOL | SPI_CPHA;
> +
> + master->num_chipselect = 1;
> + master->bus_num = -1;
> + master->setup = dra7xxx_qspi_setup;
> + master->prepare_transfer_hardware = dra7xxx_qspi_prepare_xfer;
> + master->transfer_one_message = dra7xxx_qspi_start_transfer_one;
> + master->unprepare_transfer_hardware = dra7xxx_qspi_unprepare_xfer;
> + master->dev.of_node = pdev->dev.of_node;
> +
> + dev_set_drvdata(&pdev->dev, master);
> +
> + qspi = spi_master_get_devdata(master);
> + qspi->master = master;
> + qspi->dev =&pdev->dev;
> +
> + r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (r == NULL) {
> + ret = -ENODEV;
> + goto free_master;
> + }
> +
> + qspi->base = devm_request_and_ioremap(&pdev->dev, r);
> + if (!qspi->base) {
> + dev_dbg(&pdev->dev, "can't ioremap MCSPI\n");
> + ret = -ENOMEM;
> + goto free_master;
> + }
> +
> + pm_runtime_use_autosuspend(&pdev->dev);
> + pm_runtime_set_autosuspend_delay(&pdev->dev, SPI_AUTOSUSPEND_TIMEOUT);
> + pm_runtime_enable(&pdev->dev);
> +
> + if (!of_property_read_u32(np, "spi-max-frequency",&max_freq))
> + qspi->spi_max_frequency = max_freq;
> +
> + ret = spi_register_master(master);
> + if (ret)
> + goto free_master;
> +
> + return ret;
> +
> +free_master:
> + spi_master_put(master);
> + return ret;
> +}
> +
> +static int dra7xxx_qspi_remove(struct platform_device *pdev)
> +{
> + struct dra7xxx_qspi *qspi = platform_get_drvdata(pdev);
> +
> + spi_unregister_master(qspi->master);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id dra7xxx_qspi_match[] = {
> + {.compatible = "ti,dra7xxx-qspi" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, dra7xxx_qspi_match);
> +
> +static struct platform_driver dra7xxx_qspi_driver = {
> + .probe = dra7xxx_qspi_probe,
> + .remove = dra7xxx_qspi_remove,
> + .driver = {
> + .name = "ti,dra7xxx-qspi",
> + .owner = THIS_MODULE,
> + .of_match_table = dra7xxx_qspi_match,
> + }
> +};
> +
> +module_platform_driver(dra7xxx_qspi_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("TI QSPI controller driver");

2013-07-01 10:56:42

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 2/3] drivers: spi: Add qspi flash controller

On Wed, Jun 26, 2013 at 01:11:11PM +0530, Sourav Poddar wrote:

> +static int dra7xxx_qspi_prepare_xfer(struct spi_master *master)
> +{
> + return 0;
> +}
> +
> +static int dra7xxx_qspi_unprepare_xfer(struct spi_master *master)
> +{
> + return 0;
> +}

Remove empty functions, though...

> + if (flags & XFER_END)
> + dra7xxx_writel(qspi, qspi->cmd | QSPI_INVAL, QSPI_SPI_CMD_REG);
> +
> + pm_runtime_mark_last_busy(qspi->dev);
> + pm_runtime_put_autosuspend(qspi->dev);

...there's no point in doing this per-message, it should be in the
prepare and unprepare functions.

> + master = spi_alloc_master(&pdev->dev, sizeof(*qspi));
> + if (!master)
> + return -ENOMEM;
> +
> + master->mode_bits = SPI_CPOL | SPI_CPHA;
> +
> + master->num_chipselect = 1;
> + master->bus_num = -1;
> + master->setup = dra7xxx_qspi_setup;
> + master->prepare_transfer_hardware = dra7xxx_qspi_prepare_xfer;
> + master->transfer_one_message = dra7xxx_qspi_start_transfer_one;
> + master->unprepare_transfer_hardware = dra7xxx_qspi_unprepare_xfer;
> + master->dev.of_node = pdev->dev.of_node;

There should be some bits per word restrictions in here I think - it
looks like only 8 bits per word is supported.

> + qspi->base = devm_request_and_ioremap(&pdev->dev, r);
> + if (!qspi->base) {
> + dev_dbg(&pdev->dev, "can't ioremap MCSPI\n");
> + ret = -ENOMEM;
> + goto free_master;
> + }

Use devm_ioremap_resource().

> + if (!of_property_read_u32(np, "spi-max-frequency", &max_freq))
> + qspi->spi_max_frequency = max_freq;

You have OF bindings, there should be a binding document and an OF ID
table.


Attachments:
(No filename) (1.55 kB)
signature.asc (836.00 B)
Digital signature
Download all attachments

2013-07-01 11:16:42

by Sourav Poddar

[permalink] [raw]
Subject: Re: [PATCH 2/3] drivers: spi: Add qspi flash controller

Hi Mark,

Thanks for the review.
Comments in lined.
On Monday 01 July 2013 04:26 PM, Mark Brown wrote:
> On Wed, Jun 26, 2013 at 01:11:11PM +0530, Sourav Poddar wrote:
>
>> +static int dra7xxx_qspi_prepare_xfer(struct spi_master *master)
>> +{
>> + return 0;
>> +}
>> +
>> +static int dra7xxx_qspi_unprepare_xfer(struct spi_master *master)
>> +{
>> + return 0;
>> +}
> Remove empty functions, though...
>
>> + if (flags& XFER_END)
>> + dra7xxx_writel(qspi, qspi->cmd | QSPI_INVAL, QSPI_SPI_CMD_REG);
>> +
>> + pm_runtime_mark_last_busy(qspi->dev);
>> + pm_runtime_put_autosuspend(qspi->dev);
> ...there's no point in doing this per-message, it should be in the
> prepare and unprepare functions.
>
Ok, will do runtime PM part in prepare/unprepare in my next version.
>> + master = spi_alloc_master(&pdev->dev, sizeof(*qspi));
>> + if (!master)
>> + return -ENOMEM;
>> +
>> + master->mode_bits = SPI_CPOL | SPI_CPHA;
>> +
>> + master->num_chipselect = 1;
>> + master->bus_num = -1;
>> + master->setup = dra7xxx_qspi_setup;
>> + master->prepare_transfer_hardware = dra7xxx_qspi_prepare_xfer;
>> + master->transfer_one_message = dra7xxx_qspi_start_transfer_one;
>> + master->unprepare_transfer_hardware = dra7xxx_qspi_unprepare_xfer;
>> + master->dev.of_node = pdev->dev.of_node;
> There should be some bits per word restrictions in here I think - it
> looks like only 8 bits per word is supported.
>
Yes, currently its only 8 bits per word support.
Yes, bits_per_word_mask can be filled to support upto 32 bits word
transition. Will add in v2.
>> + qspi->base = devm_request_and_ioremap(&pdev->dev, r);
>> + if (!qspi->base) {
>> + dev_dbg(&pdev->dev, "can't ioremap MCSPI\n");
>> + ret = -ENOMEM;
>> + goto free_master;
>> + }
> Use devm_ioremap_resource().
>
Ok. Will replace.
>> + if (!of_property_read_u32(np, "spi-max-frequency",&max_freq))
>> + qspi->spi_max_frequency = max_freq;
> You have OF bindings, there should be a binding document and an OF ID
> table.
Yes, will add binding documentation in my next version.

Though, I have a "of_device_id" [1] populated in my patch.

May be, I need to re-arrange it above probe function. ?

[1]:
+
+static const struct of_device_id dra7xxx_qspi_match[] = {
+ {.compatible = "ti,dra7xxx-qspi" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, dra7xxx_qspi_match);
+


Thanks,
Sourav