summaryrefslogtreecommitdiff
path: root/drivers/mtd/spi/imx_spi_nor_atmel.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd/spi/imx_spi_nor_atmel.c')
-rw-r--r--drivers/mtd/spi/imx_spi_nor_atmel.c531
1 files changed, 531 insertions, 0 deletions
diff --git a/drivers/mtd/spi/imx_spi_nor_atmel.c b/drivers/mtd/spi/imx_spi_nor_atmel.c
new file mode 100644
index 0000000..24d446a
--- /dev/null
+++ b/drivers/mtd/spi/imx_spi_nor_atmel.c
@@ -0,0 +1,531 @@
+/*
+ * (C) Copyright 2008-2009 Freescale Semiconductor, Inc.
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * 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 <asm/arch/imx_spi.h>
+#include <asm/arch/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, WREN)
+#define ENABLE_WRITE_STATUS(a) spi_nor_cmd_1byte(a, EWSR)
+
+struct imx_spi_flash_params {
+ u8 idcode1;
+ u32 block_size;
+ u32 block_count;
+ u32 device_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 = 0x27,
+ .block_size = SZ_64K,
+ .block_count = 64,
+ .device_size = SZ_64K * 64,
+ .name = "AT45DB321D - 4MB",
+ },
+};
+
+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] = STAT_READ;
+
+ 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];
+}
+
+/*!
+ * Erase a block_size data from block_addr offset in the flash
+ */
+static int spi_nor_erase_page(struct spi_flash *flash,
+ void *page_addr)
+{
+ u32 *cmd = (u32 *)g_tx_buf;
+ u32 addr = (u32)page_addr;
+
+ if ((addr & 512) != 0) {
+ printf("Error - page_addr is not "
+ "512 Bytes aligned: %p\n",
+ page_addr);
+ return -1;
+ }
+
+ /* now do the block erase */
+ if (spi_xfer(flash->spi, 4 << 3, g_tx_buf, g_rx_buf,
+ SPI_XFER_BEGIN | SPI_XFER_END) != 0) {
+ return -1;
+ }
+
+ while (spi_nor_status(flash) & RDSR_BUSY)
+ ;
+
+ return 0;
+}
+
+static int spi_nor_flash_read(struct spi_flash *flash, u32 offset,
+ size_t len, void *buf)
+{
+ struct imx_spi_flash *imx_sf = to_imx_spi_flash(flash);
+ u32 *cmd = (u32 *)g_tx_buf;
+ u32 max_rx_sz = (MAX_SPI_BYTES) - 4;
+ u8 *d_buf = (u8 *)buf;
+ u8 *s_buf;
+ s32 s32remain_size = len;
+ int i;
+
+ if (!(flash->spi))
+ return -1;
+
+ printf("Reading SPI NOR flash 0x%x [0x%x bytes] -> ram 0x%p\n",
+ offset, len, buf);
+ debug("%s(from flash=0x%08x to ram=%p len=0x%x)\n",
+ __func__,
+ offset, buf, len);
+
+ if (len == 0)
+ return 0;
+
+ *cmd = (READ << 24) | ((u32)offset & 0x00FFFFFF);
+
+ for (; s32remain_size > 0;
+ s32remain_size -= max_rx_sz, *cmd += max_rx_sz) {
+ debug("Addr:0x%p=>Offset:0x%08x, %d bytes transferred\n",
+ d_buf,
+ (*cmd & 0x00FFFFFF),
+ (len - s32remain_size));
+ debug("%d%% completed\n", ((len - s32remain_size) * 100 / len));
+
+ if (s32remain_size < max_rx_sz) {
+ debug("100%% completed\n");
+
+ if (spi_xfer(flash->spi, (s32remain_size + 4) << 3,
+ g_tx_buf, g_rx_buf,
+ SPI_XFER_BEGIN | SPI_XFER_END) != 0) {
+ printf("Error: %s(%d): failed\n",
+ __FILE__, __LINE__);
+ return -1;
+ }
+ /* throw away 4 bytes (5th received bytes is real) */
+ s_buf = g_rx_buf + 4;
+
+ /* now adjust the endianness */
+ for (i = s32remain_size; i >= 0; i -= 4, s_buf += 4) {
+ if (i < 4) {
+ if (i == 1) {
+ *d_buf = s_buf[0];
+ } else if (i == 2) {
+ *d_buf++ = s_buf[1];
+ *d_buf++ = s_buf[0];
+ } else if (i == 3) {
+ *d_buf++ = s_buf[2];
+ *d_buf++ = s_buf[1];
+ *d_buf++ = s_buf[0];
+ }
+ printf("SUCCESS\n\n");
+ return 0;
+ }
+ /* copy 4 bytes */
+ *d_buf++ = s_buf[3];
+ *d_buf++ = s_buf[2];
+ *d_buf++ = s_buf[1];
+ *d_buf++ = s_buf[0];
+ }
+ }
+
+ /* now grab max_rx_sz data (+4 is
+ *needed due to 4-throw away bytes */
+ if (spi_xfer(flash->spi, (max_rx_sz + 4) << 3,
+ g_tx_buf, g_rx_buf,
+ SPI_XFER_BEGIN | SPI_XFER_END) != 0) {
+ printf("Error: %s(%d): failed\n", __FILE__, __LINE__);
+ return -1;
+ }
+ /* throw away 4 bytes (5th received bytes is real) */
+ s_buf = g_rx_buf + 4;
+ /* now adjust the endianness */
+ for (i = 0; i < max_rx_sz; i += 4, s_buf += 4) {
+ *d_buf++ = s_buf[3];
+ *d_buf++ = s_buf[2];
+ *d_buf++ = s_buf[1];
+ *d_buf++ = s_buf[0];
+ }
+
+ if ((s32remain_size % imx_sf->params->block_size) == 0)
+ printf(".");
+ }
+ printf("SUCCESS\n\n");
+
+ return -1;
+}
+
+static int spi_nor_flash_write(struct spi_flash *flash, u32 offset,
+ size_t len, const void *buf)
+{
+ u32 d_addr = offset;
+ u8 *s_buf = (u8 *)buf;
+ unsigned int final_addr = 0;
+ int page_size = 528, trans_bytes = 0, buf_ptr = 0,
+ bytes_sent = 0, byte_sent_per_iter = 0;
+ int page_no = 0, buf_addr = 0, page_off = 0,
+ i = 0, j = 0, k = 0, fifo_size = 32;
+ int remain_len = 0;
+
+ if (!(flash->spi))
+ return -1;
+
+ if (len == 0)
+ return 0;
+
+ printf("Writing SPI NOR flash 0x%x [0x%x bytes] <- ram 0x%p\n",
+ offset, len, buf);
+ debug("%s(flash addr=0x%08x, ram=%p, len=0x%x)\n",
+ __func__, offset, buf, len);
+
+ /* Read the status register to get the Page size */
+ if (spi_nor_status(flash) & STAT_PG_SZ) {
+ page_size = 512;
+ } else {
+ puts("Unsupported Page Size of 528 bytes\n");
+ g_tx_buf[0] = CONFIG_REG4;
+ g_tx_buf[1] = CONFIG_REG3;
+ g_tx_buf[2] = CONFIG_REG2;
+ g_tx_buf[3] = CONFIG_REG1;
+
+ if (spi_xfer(flash->spi, 4 << 3, g_tx_buf, g_rx_buf,
+ SPI_XFER_BEGIN | SPI_XFER_END) != 0) {
+ printf("Error: %s(): %d", __func__, __LINE__);
+ return -1;
+ }
+
+ while (!(spi_nor_status(flash) & STAT_BUSY))
+ ;
+
+ puts("Reprogrammed the Page Size to 512 bytes\n");
+ puts("Please Power Cycle the board for the change to take effect\n");
+
+ return -1;
+ }
+
+ /* Due to the way CSPI operates send data less
+ that 4 bytes in a different manner */
+ remain_len = len % 4;
+ if (remain_len)
+ len -= remain_len;
+
+ while (1) {
+ page_no = d_addr / page_size;
+ /* Get the offset within the page
+ if address is not page-aligned */
+ page_off = (d_addr % page_size);
+ if (page_off) {
+ if (page_no == 0)
+ buf_addr = d_addr;
+ else
+ buf_addr = page_off;
+
+ trans_bytes = page_size - buf_addr;
+ } else {
+ buf_addr = 0;
+ trans_bytes = page_size;
+ }
+
+ if (len <= 0)
+ break;
+
+ if (trans_bytes > len)
+ trans_bytes = len;
+
+ bytes_sent = trans_bytes;
+ /* Write the data to the SPI-NOR Buffer first */
+ while (trans_bytes > 0) {
+ final_addr = (buf_addr & 0x3FF);
+ g_tx_buf[0] = final_addr;
+ g_tx_buf[1] = final_addr >> 8;
+ g_tx_buf[2] = final_addr >> 16;
+ g_tx_buf[3] = BUF1_WR; /*Opcode */
+
+ /* 4 bytes already used for Opcode & address bytes,
+ check to ensure we do not overflow the SPI TX buffer */
+ if (trans_bytes > (fifo_size - 4))
+ byte_sent_per_iter = fifo_size;
+ else
+ byte_sent_per_iter = trans_bytes + 4;
+
+ for (i = 4; i < byte_sent_per_iter; i += 4) {
+ g_tx_buf[i + 3] = s_buf[buf_ptr++];
+ g_tx_buf[i + 2] = s_buf[buf_ptr++];
+ g_tx_buf[i + 1] = s_buf[buf_ptr++];
+ g_tx_buf[i] = s_buf[buf_ptr++];
+ }
+
+ if (spi_xfer(flash->spi, byte_sent_per_iter << 3,
+ g_tx_buf, g_rx_buf,
+ SPI_XFER_BEGIN | SPI_XFER_END) != 0) {
+ printf("Error: %s(%d): failed\n",
+ __FILE__, __LINE__);
+ return -1;
+ }
+
+ while (!(spi_nor_status(flash) & STAT_BUSY))
+ ;
+
+ /* Deduct 4 bytes as it is used for Opcode & address bytes */
+ trans_bytes -= (byte_sent_per_iter - 4);
+ /* Update the destination buffer address */
+ buf_addr += (byte_sent_per_iter - 4);
+ }
+
+ /* Send the command to write data from the SPI-NOR Buffer to Flash memory */
+ final_addr = (page_size == 512) ? ((page_no & 0x1FFF) << 9) : \
+ ((page_no & 0x1FFF) << 10);
+
+ /* Specify the Page address in Flash where the data should be written to */
+ g_tx_buf[0] = final_addr;
+ g_tx_buf[1] = final_addr >> 8;
+ g_tx_buf[2] = final_addr >> 16;
+ g_tx_buf[3] = BUF1_TO_MEM; /*Opcode */
+ if (spi_xfer(flash->spi, 4 << 3, g_tx_buf, g_rx_buf,
+ SPI_XFER_BEGIN | SPI_XFER_END) != 0) {
+ printf("Error: %s(%d): failed\n", __FILE__, __LINE__);
+ return -1;
+ }
+
+ while (!(spi_nor_status(flash) & STAT_BUSY))
+ ;
+
+ d_addr += bytes_sent;
+ len -= bytes_sent;
+ if (d_addr % (page_size * 50) == 0)
+ puts(".");
+ }
+
+ if (remain_len) {
+ buf_ptr += remain_len;
+ /* Write the remaining data bytes first */
+ for (i = 0; i < remain_len; ++i)
+ g_tx_buf[i] = s_buf[buf_ptr--];
+
+ /* Write the address bytes next in the same word
+ as the data byte from the next byte */
+ for (j = i, k = 0; j < 4; j++, k++)
+ g_tx_buf[j] = final_addr >> (k * 8);
+
+ /* Write the remaining address bytes in the next word */
+ j = 0;
+ final_addr = (buf_addr & 0x3FF);
+
+ for (j = 0; k < 3; j++, k++)
+ g_tx_buf[j] = final_addr >> (k * 8);
+
+ /* Finally the Opcode to write the data to the buffer */
+ g_tx_buf[j] = BUF1_WR; /*Opcode */
+
+ if (spi_xfer(flash->spi, (remain_len + 4) << 3,
+ g_tx_buf, g_rx_buf,
+ SPI_XFER_BEGIN | SPI_XFER_END) != 0) {
+ printf("Error: %s(%d): failed\n", __FILE__, __LINE__);
+ return -1;
+ }
+
+ while (!(spi_nor_status(flash) & STAT_BUSY))
+ ;
+
+ if (page_size == 512)
+ final_addr = (page_no & 0x1FFF) << 9;
+ else
+ final_addr = (page_no & 0x1FFF) << 10;
+
+ g_tx_buf[0] = final_addr;
+ g_tx_buf[1] = final_addr >> 8;
+ g_tx_buf[2] = final_addr >> 16;
+ g_tx_buf[3] = BUF1_TO_MEM; /*Opcode */
+ if (spi_xfer(flash->spi, 4 << 3, g_tx_buf, g_rx_buf,
+ SPI_XFER_BEGIN | SPI_XFER_END) != 0) {
+ printf("Error: %s(%d): failed\n", __FILE__, __LINE__);
+ return -1;
+ }
+
+ while (!(spi_nor_status(flash) & STAT_BUSY))
+ ;
+ }
+
+ printf("SUCCESS\n\n");
+
+ return 0;
+}
+
+static int spi_nor_flash_erase(struct spi_flash *flash, u32 offset,
+ size_t len)
+{
+ printf("Erase is built in program.\n");
+
+ 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);
+}
+