sf: new driver for EON devices
authorChong Huang <chuang@ucrobotics.com>
Tue, 30 Nov 2010 08:33:25 +0000 (03:33 -0500)
committerMike Frysinger <vapier@gentoo.org>
Fri, 17 Dec 2010 13:53:55 +0000 (08:53 -0500)
Signed-off-by: Chong Huang <chuang@ucrobotics.com>
Signed-off-by: Haitao Zhang <minipanda@linuxrobot.org>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
drivers/mtd/spi/Makefile
drivers/mtd/spi/eon.c [new file with mode: 0644]
drivers/mtd/spi/spi_flash.c
drivers/mtd/spi/spi_flash_internal.h

index 3d607c06d39f5075b62091aa6d8bc4d3dc937ac7..57112af66f2e13d85eeda79bd8da5e8566b0cb9e 100644 (file)
@@ -27,6 +27,7 @@ LIB   := $(obj)libspi_flash.o
 
 COBJS-$(CONFIG_SPI_FLASH)      += spi_flash.o
 COBJS-$(CONFIG_SPI_FLASH_ATMEL)        += atmel.o
+COBJS-$(CONFIG_SPI_FLASH_EON)  += eon.o
 COBJS-$(CONFIG_SPI_FLASH_MACRONIX)     += macronix.o
 COBJS-$(CONFIG_SPI_FLASH_SPANSION)     += spansion.o
 COBJS-$(CONFIG_SPI_FLASH_SST)  += sst.o
diff --git a/drivers/mtd/spi/eon.c b/drivers/mtd/spi/eon.c
new file mode 100644 (file)
index 0000000..02c3bb9
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * (C) Copyright 2010, ucRobotics Inc.
+ * Author: Chong Huang <chuang@ucrobotics.com>
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <spi_flash.h>
+
+#include "spi_flash_internal.h"
+
+/* EN25Q128-specific commands */
+#define CMD_EN25Q128_WREN      0x06    /* Write Enable */
+#define CMD_EN25Q128_WRDI      0x04    /* Write Disable */
+#define CMD_EN25Q128_RDSR      0x05    /* Read Status Register */
+#define CMD_EN25Q128_WRSR      0x01    /* Write Status Register */
+#define CMD_EN25Q128_READ      0x03    /* Read Data Bytes */
+#define CMD_EN25Q128_FAST_READ 0x0b    /* Read Data Bytes at Higher Speed */
+#define CMD_EN25Q128_PP                0x02    /* Page Program */
+#define CMD_EN25Q128_SE                0x20    /* Sector Erase */
+#define CMD_EN25Q128_BE                0xd8    /* Block Erase */
+#define CMD_EN25Q128_DP                0xb9    /* Deep Power-down */
+#define CMD_EN25Q128_RES       0xab    /* Release from DP, and Read Signature */
+
+#define EON_ID_EN25Q128                0x18
+
+#define EON_SR_WIP             (1 << 0)        /* Write-in-Progress */
+
+struct eon_spi_flash_params {
+       u8 idcode1;
+       u16 page_size;
+       u16 pages_per_sector;
+       u16 sectors_per_block;
+       u16 nr_sectors;
+       const char *name;
+};
+
+/* spi_flash needs to be first so upper layers can free() it */
+struct eon_spi_flash {
+       struct spi_flash flash;
+       const struct eon_spi_flash_params *params;
+};
+
+static inline struct eon_spi_flash *to_eon_spi_flash(struct spi_flash *flash)
+{
+       return container_of(flash, struct eon_spi_flash, flash);
+}
+
+static const struct eon_spi_flash_params eon_spi_flash_table[] = {
+       {
+               .idcode1 = EON_ID_EN25Q128,
+               .page_size = 256,
+               .pages_per_sector = 16,
+               .sectors_per_block = 16,
+               .nr_sectors = 4096,
+               .name = "EN25Q128",
+       },
+};
+
+static int eon_wait_ready(struct spi_flash *flash, unsigned long timeout)
+{
+       struct spi_slave *spi = flash->spi;
+       unsigned long timebase;
+       int ret;
+       u8 cmd = CMD_EN25Q128_RDSR;
+       u8 status;
+
+       ret = spi_xfer(spi, 8, &cmd, NULL, SPI_XFER_BEGIN);
+       if (ret) {
+               debug("SF: Failed to send command %02x: %d\n", cmd, ret);
+               return ret;
+       }
+
+       timebase = get_timer(0);
+       do {
+               ret = spi_xfer(spi, 8, NULL, &status, 0);
+               if (ret)
+                       return -1;
+
+               if ((status & EON_SR_WIP) == 0)
+                       break;
+
+       } while (get_timer(timebase) < timeout);
+
+       spi_xfer(spi, 0, NULL, NULL, SPI_XFER_END);
+
+       if ((status & EON_SR_WIP) == 0)
+               return 0;
+
+       /* Timed out */
+       return -1;
+}
+
+static int eon_read_fast(struct spi_flash *flash,
+                        u32 offset, size_t len, void *buf)
+{
+       struct eon_spi_flash *eon = to_eon_spi_flash(flash);
+       unsigned long page_addr;
+       unsigned long page_size;
+       u8 cmd[5];
+
+       page_size = eon->params->page_size;
+       page_addr = offset / page_size;
+
+       cmd[0] = CMD_READ_ARRAY_FAST;
+       cmd[1] = page_addr >> 8;
+       cmd[2] = page_addr;
+       cmd[3] = offset % page_size;
+       cmd[4] = 0x00;
+
+       return spi_flash_read_common(flash, cmd, sizeof(cmd), buf, len);
+}
+
+static int eon_write(struct spi_flash *flash,
+                    u32 offset, size_t len, const void *buf)
+{
+       struct eon_spi_flash *eon = to_eon_spi_flash(flash);
+       unsigned long page_addr;
+       unsigned long byte_addr;
+       unsigned long page_size;
+       size_t chunk_len;
+       size_t actual;
+       int ret;
+       u8 cmd[4];
+
+       page_size = eon->params->page_size;
+       page_addr = offset / page_size;
+       byte_addr = offset % page_size;
+
+       ret = spi_claim_bus(flash->spi);
+       if (ret) {
+               debug("SF: Unable to claim SPI bus\n");
+               return ret;
+       }
+
+       ret = 0;
+       for (actual = 0; actual < len; actual += chunk_len) {
+               chunk_len = min(len - actual, page_size - byte_addr);
+
+               cmd[0] = CMD_EN25Q128_PP;
+               cmd[1] = page_addr >> 8;
+               cmd[2] = page_addr;
+               cmd[3] = byte_addr;
+
+               debug
+                   ("PP: 0x%p => cmd = { 0x%02x 0x%02x%02x%02x } chunk_len = %d\n",
+                    buf + actual, cmd[0], cmd[1], cmd[2], cmd[3], chunk_len);
+
+               ret = spi_flash_cmd(flash->spi, CMD_EN25Q128_WREN, NULL, 0);
+               if (ret < 0) {
+                       debug("SF: Enabling Write failed\n");
+                       break;
+               }
+
+               ret = spi_flash_cmd_write(flash->spi, cmd, 4,
+                                         buf + actual, chunk_len);
+               if (ret < 0) {
+                       debug("SF: EON Page Program failed\n");
+                       break;
+               }
+
+               ret = eon_wait_ready(flash, SPI_FLASH_PROG_TIMEOUT);
+               if (ret < 0) {
+                       debug("SF: EON page programming timed out\n");
+                       break;
+               }
+
+               page_addr++;
+               byte_addr = 0;
+       }
+
+       debug("SF: EON: Successfully programmed %u bytes @ 0x%x\n",
+             len, offset);
+
+       spi_release_bus(flash->spi);
+       return ret;
+}
+
+int eon_erase(struct spi_flash *flash, u32 offset, size_t len)
+{
+       /* block erase */
+       struct eon_spi_flash *eon = to_eon_spi_flash(flash);
+       unsigned long block_size;
+       size_t actual;
+       int ret;
+       u8 cmd[4];
+
+
+       block_size = eon->params->page_size * eon->params->pages_per_sector
+              * eon->params->sectors_per_block;
+
+       if (offset % block_size || len % block_size) {
+               debug("SF: Erase offset/length not multiple of block size\n");
+               return -1;
+       }
+
+       len /= block_size;
+       cmd[0] = CMD_EN25Q128_BE;
+       cmd[2] = 0x00;
+       cmd[3] = 0x00;
+
+       ret = spi_claim_bus(flash->spi);
+       if (ret) {
+               debug("SF: Unable to claim SPI bus\n");
+               return ret;
+       }
+
+       ret = 0;
+       for (actual = 0; actual < len; actual++) {
+               cmd[1] = (offset / block_size) + actual;
+               ret = spi_flash_cmd(flash->spi, CMD_EN25Q128_WREN, NULL, 0);
+               if (ret < 0) {
+                       debug("SF: Enabling Write failed\n");
+                       break;
+               }
+
+               ret = spi_flash_cmd_write(flash->spi, cmd, 4, NULL, 0);
+               if (ret < 0) {
+                       debug("SF: EON page erase failed\n");
+                       break;
+               }
+
+               ret = eon_wait_ready(flash, SPI_FLASH_PAGE_ERASE_TIMEOUT);
+               if (ret < 0) {
+                       debug("SF: EON page erase timed out\n");
+                       break;
+               }
+       }
+
+       debug("SF: EON: Successfully erased %u bytes @ 0x%x\n",
+             len * block_size, offset);
+
+       spi_release_bus(flash->spi);
+       return ret;
+}
+
+struct spi_flash *spi_flash_probe_eon(struct spi_slave *spi, u8 *idcode)
+{
+       const struct eon_spi_flash_params *params;
+       struct eon_spi_flash *eon;
+       unsigned int i;
+
+       for (i = 0; i < ARRAY_SIZE(eon_spi_flash_table); ++i) {
+               params = &eon_spi_flash_table[i];
+               if (params->idcode1 == idcode[2])
+                       break;
+       }
+
+       if (i == ARRAY_SIZE(eon_spi_flash_table)) {
+               debug("SF: Unsupported EON ID %02x\n", idcode[1]);
+               return NULL;
+       }
+
+       eon = malloc(sizeof(*eon));
+       if (!eon) {
+               debug("SF: Failed to allocate memory\n");
+               return NULL;
+       }
+
+       eon->params = params;
+       eon->flash.spi = spi;
+       eon->flash.name = params->name;
+
+       eon->flash.write = eon_write;
+       eon->flash.erase = eon_erase;
+       eon->flash.read = eon_read_fast;
+       eon->flash.size = params->page_size * params->pages_per_sector
+           * params->nr_sectors;
+
+       debug("SF: Detected %s with page size %u, total %u bytes\n",
+             params->name, params->page_size, eon->flash.size);
+
+       return &eon->flash;
+}
index ab02ef3e04db5d09916d2a65aaaefc5aaa37a7d6..b61d2198cd1f6b674beab77b905b3b29dd265308 100644 (file)
@@ -131,6 +131,9 @@ static const struct {
 #ifdef CONFIG_SPI_FLASH_ATMEL
        { 0, 0x1f, spi_flash_probe_atmel, },
 #endif
+#ifdef CONFIG_SPI_FLASH_EON
+       { 0, 0x1c, spi_flash_probe_eon, },
+#endif
 #ifdef CONFIG_SPI_FLASH_MACRONIX
        { 0, 0xc2, spi_flash_probe_macronix, },
 #endif
index 9bc43dd21e17eba88c154f79b464f4b17d81579c..68dcffb97174bc3c17a96c74d8d2ec452ba7c315 100644 (file)
@@ -46,6 +46,7 @@ int spi_flash_read_common(struct spi_flash *flash, const u8 *cmd,
 /* Manufacturer-specific probe functions */
 struct spi_flash *spi_flash_probe_spansion(struct spi_slave *spi, u8 *idcode);
 struct spi_flash *spi_flash_probe_atmel(struct spi_slave *spi, u8 *idcode);
+struct spi_flash *spi_flash_probe_eon(struct spi_slave *spi, u8 *idcode);
 struct spi_flash *spi_flash_probe_macronix(struct spi_slave *spi, u8 *idcode);
 struct spi_flash *spi_flash_probe_sst(struct spi_slave *spi, u8 *idcode);
 struct spi_flash *spi_flash_probe_stmicro(struct spi_slave *spi, u8 *idcode);