summaryrefslogtreecommitdiff
path: root/drivers/mtd/spi
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd/spi')
-rw-r--r--drivers/mtd/spi/Makefile1
-rw-r--r--drivers/mtd/spi/imx_spi_nor_m25pxx.c548
2 files changed, 549 insertions, 0 deletions
diff --git a/drivers/mtd/spi/Makefile b/drivers/mtd/spi/Makefile
index 9d92bf5..cf4d5eb 100644
--- a/drivers/mtd/spi/Makefile
+++ b/drivers/mtd/spi/Makefile
@@ -35,6 +35,7 @@ COBJS-$(CONFIG_SPI_M95XXX) += eeprom_m95xxx.o
COBJS-$(CONFIG_SPI_FLASH_IMX) += imx_spi_nor.o
COBJS-$(CONFIG_SPI_FLASH_IMX_SST) += imx_spi_nor_sst.o
COBJS-$(CONFIG_SPI_FLASH_IMX_ATMEL) += imx_spi_nor_atmel.o
+COBJS-$(CONFIG_SPI_FLASH_IMX_M25PXX) += imx_spi_nor_m25pxx.o
COBJS := $(COBJS-y)
SRCS := $(COBJS:.o=.c)
diff --git a/drivers/mtd/spi/imx_spi_nor_m25pxx.c b/drivers/mtd/spi/imx_spi_nor_m25pxx.c
new file mode 100644
index 0000000..a87bab6
--- /dev/null
+++ b/drivers/mtd/spi/imx_spi_nor_m25pxx.c
@@ -0,0 +1,548 @@
+/*
+ * Copyright (C) 2011 Freescale Semiconductor, Inc.
+ *
+ * This code is based on drivers/mtd/devices/mxc_m25p80.c in Linux kernel.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <config.h>
+#include <common.h>
+#include <spi.h>
+#include <spi_flash.h>
+#include <asm/errno.h>
+#include <linux/types.h>
+#include <malloc.h>
+
+#include <imx_spi.h>
+#include <imx_spi_nor.h>
+
+static u8 g_tx_buf[256];
+static u8 g_rx_buf[256];
+
+#define WRITE_ENABLE(a) spi_nor_cmd_1byte(a, OPCODE_WREN)
+#define write_enable(a) WRITE_ENABLE(a)
+
+#define SPI_FIFOSIZE 24
+
+struct imx_spi_flash_params {
+ u32 idcode1;
+ u32 block_size;
+ u32 block_count;
+ u32 device_size;
+ u32 page_size;
+ const char *name;
+};
+
+struct imx_spi_flash {
+ const struct imx_spi_flash_params *params;
+ struct spi_flash flash;
+};
+
+static inline struct imx_spi_flash *
+to_imx_spi_flash(struct spi_flash *flash)
+{
+ return container_of(flash, struct imx_spi_flash, flash);
+}
+
+static const struct imx_spi_flash_params imx_spi_flash_table[] = {
+ {
+ .idcode1 = 0x20,
+ .block_size = SZ_64K,
+ .block_count = 64,
+ .device_size = SZ_64K * 64,
+ .page_size = 256,
+ .name = "M25P32 - 4MB",
+ },
+};
+
+static s32 spi_nor_cmd_1byte(struct spi_flash *flash, u8 cmd)
+{
+ g_tx_buf[0] = cmd;
+ g_tx_buf[1] = 0;
+ g_tx_buf[2] = 0;
+ g_tx_buf[3] = 0;
+
+ if (spi_xfer(flash->spi, (1 << 3), g_tx_buf, g_rx_buf,
+ SPI_XFER_BEGIN | SPI_XFER_END) != 0) {
+ printf("Error: %s(): %d\n", __func__, __LINE__);
+ return -1;
+ }
+ return 0;
+}
+
+static s32 spi_nor_flash_query(struct spi_flash *flash, void* data)
+{
+ u8 au8Tmp[4] = { 0 };
+ u8 *pData = (u8 *)data;
+
+ g_tx_buf[3] = JEDEC_ID;
+
+ if (spi_xfer(flash->spi, (4 << 3), g_tx_buf, au8Tmp,
+ SPI_XFER_BEGIN | SPI_XFER_END)) {
+ return -1;
+ }
+
+ printf("JEDEC ID: 0x%02x:0x%02x:0x%02x\n",
+ au8Tmp[2], au8Tmp[1], au8Tmp[0]);
+
+ pData[0] = au8Tmp[2];
+ pData[1] = au8Tmp[1];
+ pData[2] = au8Tmp[0];
+
+ return 0;
+}
+
+static s32 spi_nor_status(struct spi_flash *flash)
+{
+ g_tx_buf[1] = OPCODE_RDSR;
+
+ if (spi_xfer(flash->spi, 2 << 3, g_tx_buf, g_rx_buf,
+ SPI_XFER_BEGIN | SPI_XFER_END) != 0) {
+ printf("Error: %s(): %d\n", __func__, __LINE__);
+ return 0;
+ }
+ return g_rx_buf[0];
+}
+
+static int wait_till_ready(struct spi_flash *flash)
+{
+ int sr;
+ int i;
+ int times = 100;
+
+ do {
+ sr = spi_nor_status(flash);
+ if (sr < 0)
+ break;
+ else if (!(sr & SR_WIP))
+ return 0;
+
+ udelay(1000);
+
+ } while (times--);
+
+ return 1;
+}
+
+static int erase_chip(struct spi_flash *flash)
+{
+ /* Wait until finished previous write command. */
+ if (wait_till_ready(flash))
+ return 1;
+
+ /* Send write enable, then erase commands. */
+ WRITE_ENABLE(flash);
+
+ /* Set up command buffer. */
+ g_tx_buf[3] = OPCODE_CHIP_ERASE;
+
+ if (spi_xfer(flash->spi, (4 << 3), g_tx_buf, g_rx_buf,
+ SPI_XFER_BEGIN | SPI_XFER_END)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int erase_sector(struct spi_flash *flash, u32 offset)
+{
+ /* Wait until finished previous write command. */
+ if (wait_till_ready(flash))
+ return 1;
+
+ /* Send write enable, then erase commands. */
+ WRITE_ENABLE(flash);
+
+ /* Set up command buffer. */
+ g_tx_buf[3] = OPCODE_SE;
+ g_tx_buf[2] = offset >> 16;
+ g_tx_buf[1] = offset >> 8;
+ g_tx_buf[0] = offset;
+
+ if (spi_xfer(flash->spi, (4 << 3), g_tx_buf, g_rx_buf,
+ SPI_XFER_BEGIN | SPI_XFER_END)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int spi_nor_flash_read(struct spi_flash *flash, u32 from,
+ size_t len, void *buf)
+{
+ struct imx_spi_flash *imx_sf = to_imx_spi_flash(flash);
+ int rx_len = 0, count = 0, i = 0;
+ int addr, cmd_len, status;
+ u8 txer[SPI_FIFOSIZE] = { 0 };
+ u8 *s = txer;
+ u8 *d = buf;
+
+ debug("%s: %s 0x%08x, len %zd\n", __func__, "from", (u32)from, len);
+
+ /* sanity checks */
+ if (!len)
+ return 0;
+
+ if (from + len > imx_sf->params->device_size)
+ return -EINVAL;
+
+ /* Wait till previous write/erase is done. */
+ if (wait_till_ready(flash)) {
+ /* REVISIT status return?? */
+ return 1;
+ }
+
+ cmd_len = 4;
+
+ addr = from;
+
+ while (len > 0) {
+
+ rx_len = len > (SPI_FIFOSIZE - cmd_len) ?
+ SPI_FIFOSIZE - cmd_len : len;
+
+ /* Set up the write data buffer. */
+ txer[3] = OPCODE_NORM_READ;
+ txer[2] = addr >> 16;
+ txer[1] = addr >> 8;
+ txer[0] = addr;
+
+ if (spi_xfer(flash->spi, (roundup(rx_len, 4) + cmd_len) << 3,
+ txer, txer,
+ SPI_XFER_BEGIN | SPI_XFER_END) != 0) {
+ printf("Error: %s(%d): failed\n",
+ __FILE__, __LINE__);
+ return -1;
+ }
+
+ s = txer + cmd_len;
+
+ for (i = rx_len; i >= 0; i -= 4, s += 4) {
+ if (i < 4) {
+ if (i == 1) {
+ *d = s[3];
+ } else if (i == 2) {
+ *d++ = s[3];
+ *d++ = s[2];
+ } else if (i == 3) {
+ *d++ = s[3];
+ *d++ = s[2];
+ *d++ = s[1];
+ }
+
+ break;
+ }
+
+ *d++ = s[3];
+ *d++ = s[2];
+ *d++ = s[1];
+ *d++ = s[0];
+ }
+
+ /* updaate */
+ len -= rx_len;
+ addr += rx_len;
+ count += rx_len;
+
+ debug("%s: left:0x%x, from:0x%08x, to:0x%p, done: 0x%x\n",
+ __func__, len, (u32) addr, d, count);
+ }
+
+ return 0;
+}
+
+static int _fsl_spi_write(struct spi_flash *flash, const void *buf, int len, int addr)
+{
+ u8 txer[SPI_FIFOSIZE] = { 0 };
+ u8 *d = txer;
+ u8 *s = (u8 *) buf;
+ int delta = 0, l = 0, i = 0, count = 0, status;
+
+ count = len;
+ delta = count % 4;
+ if (delta)
+ count -= delta;
+
+ while (count) {
+ d = txer;
+ l = count > (SPI_FIFOSIZE - 4) ?
+ SPI_FIFOSIZE - 4 : count;
+
+ d[3] = OPCODE_PP;
+ d[2] = addr >> 16;
+ d[1] = addr >> 8;
+ d[0] = addr;
+
+ for (i = 0, d += 4; i < l / 4; i++, d += 4) {
+ d[3] = *s++;
+ d[2] = *s++;
+ d[1] = *s++;
+ d[0] = *s++;
+ }
+
+ debug("WRITEBUF: (%x) %x %x %x\n",
+ txer[3], txer[2], txer[1], txer[0]);
+
+ wait_till_ready(flash);
+
+ write_enable(flash);
+
+ if (spi_xfer(flash->spi, (l + 4) << 3,
+ txer, txer,
+ SPI_XFER_BEGIN | SPI_XFER_END) != 0) {
+ printf("Error: %s(%d): failed\n",
+ __FILE__, __LINE__);
+ return -1;
+ }
+
+ /* update */
+ count -= l;
+ addr += l;
+ }
+
+ if (delta) {
+ d = txer;
+ /* to keep uninterested bytes untouched */
+ for (i = 4; i < 8; i++)
+ d[i] = 0xff;
+
+ d[3] = OPCODE_PP;
+ d[2] = (addr >> 16) & 0xff;
+ d[1] = (addr >> 8) & 0xff;
+ d[0] = (addr) & 0xff;
+
+ switch (delta) {
+ case 1:
+ d[7] = *s++;
+ break;
+ case 2:
+ d[7] = *s++;
+ d[6] = *s++;
+ break;
+ case 3:
+ d[7] = *s++;
+ d[6] = *s++;
+ d[5] = *s++;
+ break;
+ default:
+ break;
+ }
+
+ debug("WRITEBUF: (%x) %x %x %x\n",
+ txer[3], txer[2], txer[1], txer[0]);
+
+ wait_till_ready(flash);
+
+ write_enable(flash);
+
+ if (spi_xfer(flash->spi, (4 + 4) << 3,
+ txer, txer,
+ SPI_XFER_BEGIN | SPI_XFER_END) != 0) {
+ printf("Error: %s(%d): failed\n",
+ __FILE__, __LINE__);
+ return -1;
+ }
+ }
+
+ return len;
+}
+
+static int spi_nor_flash_write(struct spi_flash *flash, u32 to,
+ size_t len, const void *buf)
+{
+ struct imx_spi_flash *imx_sf = to_imx_spi_flash(flash);
+ u32 page_offset, page_size;
+
+ /* sanity checks */
+ if (!len)
+ return 0;
+
+ if (to + len > imx_sf->params->device_size)
+ return -EINVAL;
+
+ /* Wait until finished previous write command. */
+ if (wait_till_ready(flash))
+ return 1;
+
+ write_enable(flash);
+
+ page_offset = to & (imx_sf->params->page_size - 1);
+
+ /* do all the bytes fit onto one page? */
+ if (page_offset + len <= imx_sf->params->page_size) {
+ _fsl_spi_write(flash, buf, len, to);
+
+ } else {
+ u32 i;
+
+ /* the size of data remaining on the first page */
+ page_size = imx_sf->params->page_size - page_offset;
+
+ _fsl_spi_write(flash, buf, page_size, to);
+
+ /* write everything in flash->page_size chunks */
+ for (i = page_size; i < len; i += page_size) {
+ page_size = len - i;
+ if (page_size > imx_sf->params->page_size)
+ page_size = imx_sf->params->page_size;
+
+ wait_till_ready(flash);
+
+ write_enable(flash);
+
+ _fsl_spi_write(flash, buf + i, page_size, to + i);
+ if (page_size % imx_sf->params->page_size == 0)
+ printf(".");
+ }
+ }
+
+ printf("SUCCESS\n\n");
+
+ return 0;
+}
+
+static int spi_nor_flash_erase(struct spi_flash *flash, u32 offset,
+ size_t len)
+{
+ struct imx_spi_flash *imx_sf = to_imx_spi_flash(flash);
+
+ /* whole-chip erase? */
+ if (len == imx_sf->params->device_size) {
+ if (erase_chip(flash))
+ return -EIO;
+
+ /* REVISIT in some cases we could speed up erasing large regions
+ * by using OPCODE_SE instead of OPCODE_BE_4K. We may have set up
+ * to use "small sector erase", but that's not always optimal.
+ */
+
+ /* "sector"-at-a-time erase */
+ } else {
+ len = roundup(len, imx_sf->params->block_size);
+ while (len) {
+ if (erase_sector(flash, offset))
+ return -EIO;
+
+ offset += imx_sf->params->block_size;
+ len -= imx_sf->params->block_size;
+ }
+ }
+
+
+ return 0;
+}
+
+struct spi_flash *spi_flash_probe(unsigned int bus, unsigned int cs, unsigned int max_hz, unsigned int spi_mode)
+{
+ struct spi_slave *spi = NULL;
+ const struct imx_spi_flash_params *params = NULL;
+ struct imx_spi_flash *imx_sf = NULL;
+ u8 idcode[4] = { 0 };
+ u32 i = 0;
+ s32 ret = 0;
+
+ if (CONFIG_SPI_FLASH_CS != cs) {
+ printf("Invalid cs for SPI NOR.\n");
+ return NULL;
+ }
+
+ spi = spi_setup_slave(bus, cs, max_hz, spi_mode);
+
+ if (!spi) {
+ debug("SF: Failed to set up slave\n");
+ return NULL;
+ }
+
+ ret = spi_claim_bus(spi);
+ if (ret) {
+ debug("SF: Failed to claim SPI bus: %d\n", ret);
+ goto err_claim_bus;
+ }
+
+ imx_sf = (struct imx_spi_flash *)malloc(sizeof(struct imx_spi_flash));
+
+ if (!imx_sf) {
+ debug("SF: Failed to allocate memory\n");
+ spi_free_slave(spi);
+ return NULL;
+ }
+
+ imx_sf->flash.spi = spi;
+
+ /* Read the ID codes */
+ ret = spi_nor_flash_query(&(imx_sf->flash), idcode);
+ if (ret)
+ goto err_read_id;
+
+ for (i = 0; i < ARRAY_SIZE(imx_spi_flash_table); ++i) {
+ params = &imx_spi_flash_table[i];
+ if (params->idcode1 == idcode[1])
+ break;
+ }
+
+ if (i == ARRAY_SIZE(imx_spi_flash_table)) {
+ debug("SF: Unsupported DataFlash ID %02x\n",
+ idcode[1]);
+
+ goto err_invalid_dev;
+ }
+
+ imx_sf->params = params;
+
+ imx_sf->flash.name = params->name;
+ imx_sf->flash.size = params->device_size;
+
+ imx_sf->flash.read = spi_nor_flash_read;
+ imx_sf->flash.write = spi_nor_flash_write;
+ imx_sf->flash.erase = spi_nor_flash_erase;
+
+ debug("SF: Detected %s with block size %lu, "
+ "block count %lu, total %u bytes\n",
+ params->name,
+ params->block_size,
+ params->block_count,
+ params->device_size);
+
+ return &(imx_sf->flash);
+
+err_read_id:
+ spi_release_bus(spi);
+err_invalid_dev:
+ if (imx_sf)
+ free(imx_sf);
+err_claim_bus:
+ if (spi)
+ spi_free_slave(spi);
+ return NULL;
+}
+
+void spi_flash_free(struct spi_flash *flash)
+{
+ struct imx_spi_flash *imx_sf = NULL;
+
+ if (!flash)
+ return;
+
+ imx_sf = to_imx_spi_flash(flash);
+
+ if (flash->spi) {
+ spi_free_slave(flash->spi);
+ flash->spi = NULL;
+ }
+
+ free(imx_sf);
+}