summaryrefslogtreecommitdiffstats
path: root/drivers/mtd/devices/m25p80.c
diff options
context:
space:
mode:
authorJean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com>2012-10-31 13:04:02 +0100
committerSascha Hauer <s.hauer@pengutronix.de>2012-11-20 09:40:23 +0100
commit1308d908ea6f1c6267007b485f3d8373bef9cf7f (patch)
tree71e9f27470d294cb8befe9f4e3e9c1e1c50d92fb /drivers/mtd/devices/m25p80.c
parentcb85fb8214f932a946ca87bc98e904a09d4b73b4 (diff)
downloadbarebox-1308d908ea6f1c6267007b485f3d8373bef9cf7f.tar.gz
barebox-1308d908ea6f1c6267007b485f3d8373bef9cf7f.tar.xz
m25p80: re-import it againt mtd_add_host
so we now create the cdev via mtd This will also simplify sync with linux to avoid the m25p8000 or m25p00 the cdev is still named name m25p and the drivers m25p80 Signed-off-by: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/mtd/devices/m25p80.c')
-rw-r--r--drivers/mtd/devices/m25p80.c937
1 files changed, 937 insertions, 0 deletions
diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c
new file mode 100644
index 0000000000..b4ac0459e4
--- /dev/null
+++ b/drivers/mtd/devices/m25p80.c
@@ -0,0 +1,937 @@
+/*
+ * MTD SPI driver for ST M25Pxx (and similar) serial flash chips
+ *
+ * Author: Mike Lavender, mike@steroidmicros.com
+ * Copyright (c) 2005, Intec Automation Inc.
+ *
+ * Some parts are based on lart.c by Abraham Van Der Merwe
+ *
+ * Cleaned up and generalized based on mtd_dataflash.c
+ *
+ * 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 <common.h>
+#include <init.h>
+#include <driver.h>
+#include <spi/spi.h>
+#include <spi/flash.h>
+#include <xfuncs.h>
+#include <malloc.h>
+#include <errno.h>
+#include <linux/err.h>
+#include <clock.h>
+#include <linux/math64.h>
+#include <linux/mtd/cfi.h>
+#include <linux/mtd/mtd.h>
+
+/* Flash opcodes. */
+#define OPCODE_WREN 0x06 /* Write enable */
+#define OPCODE_RDSR 0x05 /* Read status register */
+#define OPCODE_WRSR 0x01 /* Write status register 1 byte */
+#define OPCODE_NORM_READ 0x03 /* Read data bytes (low frequency) */
+#define OPCODE_FAST_READ 0x0b /* Read data bytes (high frequency) */
+#define OPCODE_PP 0x02 /* Page program (up to 256 bytes) */
+#define OPCODE_BE_4K 0x20 /* Erase 4KiB block */
+#define OPCODE_BE_32K 0x52 /* Erase 32KiB block */
+#define OPCODE_CHIP_ERASE 0xc7 /* Erase whole flash chip */
+#define OPCODE_SE 0xd8 /* Sector erase (usually 64KiB) */
+#define OPCODE_RDID 0x9f /* Read JEDEC ID */
+
+/* Used for SST flashes only. */
+#define OPCODE_BP 0x02 /* Byte program */
+#define OPCODE_WRDI 0x04 /* Write disable */
+#define OPCODE_AAI_WP 0xad /* Auto address increment word program */
+
+/* Used for Macronix flashes only. */
+#define OPCODE_EN4B 0xb7 /* Enter 4-byte mode */
+#define OPCODE_EX4B 0xe9 /* Exit 4-byte mode */
+
+/* Used for Spansion flashes only. */
+#define OPCODE_BRWR 0x17 /* Bank register write */
+
+/* Status Register bits. */
+#define SR_WIP 1 /* Write in progress */
+#define SR_WEL 2 /* Write enable latch */
+/* meaning of other SR_* bits may differ between vendors */
+#define SR_BP0 4 /* Block protect 0 */
+#define SR_BP1 8 /* Block protect 1 */
+#define SR_BP2 0x10 /* Block protect 2 */
+#define SR_SRWD 0x80 /* SR write protect */
+
+/* Define max times to check status register before we give up. */
+#define MAX_READY_WAIT 40 /* M25P16 specs 40s max chip erase */
+#define MAX_CMD_SIZE 6
+
+#define JEDEC_MFR(_jedec_id) ((_jedec_id) >> 16)
+
+/****************************************************************************/
+
+#define SPI_NAME_SIZE 32
+
+struct spi_device_id {
+ char name[SPI_NAME_SIZE];
+ unsigned long driver_data;
+};
+
+struct m25p {
+ struct spi_device *spi;
+ struct mtd_info mtd;
+ u16 page_size;
+ unsigned sector_size;
+ u16 addr_width;
+ u8 erase_opcode;
+ u8 erase_opcode_4k;
+ u8 *command;
+};
+
+static inline struct m25p *mtd_to_m25p(struct mtd_info *mtd)
+{
+ return container_of(mtd, struct m25p, mtd);
+}
+
+/****************************************************************************/
+
+/*
+ * Internal helper functions
+ */
+
+/*
+ * 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 m25p *flash)
+{
+ ssize_t retval;
+ u8 code = OPCODE_RDSR;
+ u8 val;
+
+ retval = spi_write_then_read(flash->spi, &code, 1, &val, 1);
+
+ if (retval < 0) {
+ dev_err(&flash->spi->dev, "error %d reading SR\n",
+ (int) retval);
+ return retval;
+ }
+
+ return val;
+}
+
+/*
+ * Write status register 1 byte
+ * Returns negative if error occurred.
+ */
+static int write_sr(struct m25p *flash, u8 val)
+{
+ flash->command[0] = OPCODE_WRSR;
+ flash->command[1] = val;
+
+ return spi_write(flash->spi, flash->command, 2);
+}
+
+/*
+ * Set write enable latch with Write Enable command.
+ * Returns negative if error occurred.
+ */
+static inline int write_enable(struct m25p *flash)
+{
+ u8 code = OPCODE_WREN;
+
+ return spi_write_then_read(flash->spi, &code, 1, NULL, 0);
+}
+
+/*
+ * Send write disble instruction to the chip.
+ */
+static inline int write_disable(struct m25p *flash)
+{
+ u8 code = OPCODE_WRDI;
+
+ return spi_write_then_read(flash->spi, &code, 1, NULL, 0);
+}
+
+/*
+ * Enable/disable 4-byte addressing mode.
+ */
+static inline int set_4byte(struct m25p *flash, u32 jedec_id, int enable)
+{
+ switch (JEDEC_MFR(jedec_id)) {
+ case CFI_MFR_MACRONIX:
+ flash->command[0] = enable ? OPCODE_EN4B : OPCODE_EX4B;
+ return spi_write(flash->spi, flash->command, 1);
+ default:
+ /* Spansion style */
+ flash->command[0] = OPCODE_BRWR;
+ flash->command[1] = enable << 7;
+ return spi_write(flash->spi, flash->command, 2);
+ }
+}
+
+/*
+ * Service routine to read status register until ready, or timeout occurs.
+ * Returns non-zero if error.
+ */
+static int wait_till_ready(struct m25p *flash)
+{
+ int sr;
+ uint64_t timer_start;
+
+ timer_start = get_time_ns();
+
+ do {
+ if ((sr = read_sr(flash)) < 0)
+ break;
+ else if (!(sr & SR_WIP))
+ return 0;
+
+ } while (!(is_timeout(timer_start, MAX_READY_WAIT * SECOND)));
+
+ return -ETIMEDOUT;
+}
+
+/*
+ * Erase the whole flash memory
+ *
+ * Returns 0 if successful, non-zero otherwise.
+ */
+static int erase_chip(struct m25p *flash)
+{
+ dev_dbg(&flash->spi->dev, "%s %lldKiB\n",
+ __func__, (long long)(flash->mtd.size >> 10));
+
+ /* Wait until finished previous write command. */
+ if (wait_till_ready(flash))
+ return -ETIMEDOUT;
+
+ /* Send write enable, then erase commands. */
+ write_enable(flash);
+
+ /* Set up command buffer. */
+ flash->command[0] = OPCODE_CHIP_ERASE;
+
+ spi_write(flash->spi, flash->command, 1);
+
+ return 0;
+}
+
+static void m25p_addr2cmd(struct m25p *flash, unsigned int addr, u8 *cmd)
+{
+ /* opcode is in cmd[0] */
+ cmd[1] = addr >> (flash->addr_width * 8 - 8);
+ cmd[2] = addr >> (flash->addr_width * 8 - 16);
+ cmd[3] = addr >> (flash->addr_width * 8 - 24);
+ cmd[4] = addr >> (flash->addr_width * 8 - 32);
+}
+
+static int m25p_cmdsz(struct m25p *flash)
+{
+ return 1 + flash->addr_width;
+}
+
+/*
+ * Erase one sector of flash memory at offset ``offset'' which is any
+ * address within the sector which should be erased.
+ *
+ * Returns 0 if successful, non-zero otherwise.
+ */
+static int erase_sector(struct m25p *flash, u32 offset, u32 command)
+{
+ dev_dbg(&flash->spi->dev, "%s %dKiB at 0x%08x\n",
+ __func__, flash->mtd.erasesize / 1024, offset);
+
+ /* Wait until finished previous write command. */
+ if (wait_till_ready(flash))
+ return -ETIMEDOUT;
+
+ /* Send write enable, then erase commands. */
+ write_enable(flash);
+
+ /* Set up command buffer. */
+ flash->command[0] = command;
+ m25p_addr2cmd(flash, offset, flash->command);
+
+ spi_write(flash->spi, flash->command, m25p_cmdsz(flash));
+
+ return 0;
+}
+
+/****************************************************************************/
+
+/*
+ * MTD implementation
+ */
+
+/*
+ * Erase an address range on the flash chip. The address range may extend
+ * one or more erase sectors. Return an error is there is a problem erasing.
+ */
+static int m25p80_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct m25p *flash = mtd_to_m25p(mtd);
+ u32 addr, len;
+ uint32_t rem;
+
+ dev_dbg(&flash->spi->dev, "%s at 0x%llx, len %lld\n",
+ __func__, (long long)instr->addr,
+ (long long)instr->len);
+
+ div_u64_rem(instr->len, mtd->erasesize, &rem);
+ if (rem)
+ return -EINVAL;
+
+ addr = instr->addr;
+ len = instr->len;
+
+ /* whole-chip erase? */
+ if (len == flash->mtd.size) {
+ if (erase_chip(flash)) {
+ instr->state = MTD_ERASE_FAILED;
+ return -EIO;
+ }
+ return 0;
+ }
+
+ if (flash->erase_opcode_4k) {
+ while (len && (addr & (flash->sector_size - 1))) {
+ if (ctrlc())
+ return -EINTR;
+ if (erase_sector(flash, addr, flash->erase_opcode_4k))
+ return -EIO;
+ addr += mtd->erasesize;
+ len -= mtd->erasesize;
+ }
+
+ while (len >= flash->sector_size) {
+ if (ctrlc())
+ return -EINTR;
+ if (erase_sector(flash, addr, flash->erase_opcode))
+ return -EIO;
+ addr += flash->sector_size;
+ len -= flash->sector_size;
+ }
+
+ while (len) {
+ if (ctrlc())
+ return -EINTR;
+ if (erase_sector(flash, addr, flash->erase_opcode_4k))
+ return -EIO;
+ addr += mtd->erasesize;
+ len -= mtd->erasesize;
+ }
+ } else {
+ while (len) {
+ if (ctrlc())
+ return -EINTR;
+ if (erase_sector(flash, addr, flash->erase_opcode))
+ return -EIO;
+
+ if (len <= mtd->erasesize)
+ break;
+ addr += mtd->erasesize;
+ len -= mtd->erasesize;
+ }
+ }
+
+ if (wait_till_ready(flash))
+ return -ETIMEDOUT;
+
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+
+ return 0;
+}
+
+/*
+ * Read an address range from the flash chip. The address range
+ * may be any size provided it is within the physical boundaries.
+ */
+static int m25p80_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct m25p *flash = mtd_to_m25p(mtd);
+ struct spi_transfer t[2];
+ struct spi_message m;
+ int fast_read = 0;
+
+ if (flash->spi->max_speed_hz >= 25000000)
+ fast_read = 1;
+
+ spi_message_init(&m);
+ memset(t, 0, (sizeof t));
+
+ /* NOTE:
+ * OPCODE_FAST_READ (if available) is faster.
+ * Should add 1 byte DUMMY_BYTE.
+ */
+ t[0].tx_buf = flash->command;
+ t[0].len = m25p_cmdsz(flash) + fast_read;
+ spi_message_add_tail(&t[0], &m);
+
+ t[1].rx_buf = buf;
+ t[1].len = len;
+ spi_message_add_tail(&t[1], &m);
+
+ /* Wait till previous write/erase is done. */
+ if (wait_till_ready(flash))
+ return -ETIMEDOUT;
+
+ /* FIXME switch to OPCODE_FAST_READ. It's required for higher
+ * clocks; and at this writing, every chip this driver handles
+ * supports that opcode.
+ */
+
+ /* Set up the write data buffer. */
+ flash->command[0] = fast_read ? OPCODE_FAST_READ : OPCODE_NORM_READ;
+ m25p_addr2cmd(flash, from, flash->command);
+
+ spi_sync(flash->spi, &m);
+
+ *retlen = m.actual_length - m25p_cmdsz(flash) - fast_read;
+
+ return 0;
+}
+
+/*
+ * Write an address range to the flash chip. Data must be written in
+ * FLASH_PAGESIZE chunks. The address range may be any size provided
+ * it is within the physical boundaries.
+ */
+static int m25p80_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct m25p *flash = mtd_to_m25p(mtd);
+ u32 page_offset, page_size;
+ struct spi_transfer t[2];
+ struct spi_message m;
+
+ debug("m25p80_write %ld bytes at 0x%08lX\n", (unsigned long)count, offset);
+
+ spi_message_init(&m);
+ memset(t, 0, (sizeof t));
+
+ t[0].tx_buf = flash->command;
+ t[0].len = m25p_cmdsz(flash);
+ spi_message_add_tail(&t[0], &m);
+
+ t[1].tx_buf = buf;
+ spi_message_add_tail(&t[1], &m);
+
+ /* Wait until finished previous write command. */
+ if (wait_till_ready(flash))
+ return -ETIMEDOUT;
+
+ write_enable(flash);
+
+ /* Set up the opcode in the write buffer. */
+ flash->command[0] = OPCODE_PP;
+ m25p_addr2cmd(flash, to, flash->command);
+
+ page_offset = to & (flash->page_size - 1);
+
+ /* do all the bytes fit onto one page? */
+ if (page_offset + len <= flash->page_size) {
+ t[1].len = len;
+
+ spi_sync(flash->spi, &m);
+
+ *retlen = m.actual_length - m25p_cmdsz(flash);
+ } else {
+ u32 i;
+
+ /* the size of data remaining on the first page */
+ page_size = flash->page_size - page_offset;
+
+ t[1].len = page_size;
+ spi_sync(flash->spi, &m);
+
+ *retlen = m.actual_length - m25p_cmdsz(flash);
+
+ /* write everything in flash->page_size chunks */
+ for (i = page_size; i < len; i += page_size) {
+ page_size = len - i;
+ if (page_size > flash->page_size)
+ page_size = flash->page_size;
+
+ /* write the next page to flash */
+ m25p_addr2cmd(flash, to + i, flash->command);
+
+ t[1].tx_buf = buf + i;
+ t[1].len = page_size;
+
+ wait_till_ready(flash);
+
+ write_enable(flash);
+
+ spi_sync(flash->spi, &m);
+
+ *retlen += m.actual_length - m25p_cmdsz(flash);
+ }
+ }
+
+ return 0;
+}
+
+static int sst_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct m25p *flash = mtd_to_m25p(mtd);
+ struct spi_transfer t[2];
+ struct spi_message m;
+ size_t actual;
+ int cmd_sz, ret;
+
+ pr_debug("%s: %s to 0x%08x, len %zd\n", dev_name(&flash->spi->dev),
+ __func__, (u32)to, len);
+
+ spi_message_init(&m);
+ memset(t, 0, (sizeof t));
+
+ t[0].tx_buf = flash->command;
+ t[0].len = m25p_cmdsz(flash);
+ spi_message_add_tail(&t[0], &m);
+
+ t[1].tx_buf = buf;
+ spi_message_add_tail(&t[1], &m);
+
+ /* Wait until finished previous write command. */
+ ret = wait_till_ready(flash);
+ if (ret)
+ goto time_out;
+
+ write_enable(flash);
+
+ actual = to % 2;
+ /* Start write from odd address. */
+ if (actual) {
+ flash->command[0] = OPCODE_BP;
+ m25p_addr2cmd(flash, to, flash->command);
+
+ /* write one byte. */
+ t[1].len = 1;
+ spi_sync(flash->spi, &m);
+ ret = wait_till_ready(flash);
+ if (ret)
+ goto time_out;
+ *retlen += m.actual_length - m25p_cmdsz(flash);
+ }
+ to += actual;
+
+ flash->command[0] = OPCODE_AAI_WP;
+ m25p_addr2cmd(flash, to, flash->command);
+
+ /* Write out most of the data here. */
+ cmd_sz = m25p_cmdsz(flash);
+ for (; actual < len - 1; actual += 2) {
+ t[0].len = cmd_sz;
+ /* write two bytes. */
+ t[1].len = 2;
+ t[1].tx_buf = buf + actual;
+
+ spi_sync(flash->spi, &m);
+ ret = wait_till_ready(flash);
+ if (ret)
+ goto time_out;
+ *retlen += m.actual_length - cmd_sz;
+ cmd_sz = 1;
+ to += 2;
+ }
+ write_disable(flash);
+ ret = wait_till_ready(flash);
+ if (ret)
+ goto time_out;
+
+ /* Write out trailing byte if it exists. */
+ if (actual != len) {
+ write_enable(flash);
+ flash->command[0] = OPCODE_BP;
+ m25p_addr2cmd(flash, to, flash->command);
+ t[0].len = m25p_cmdsz(flash);
+ t[1].len = 1;
+ t[1].tx_buf = buf + actual;
+
+ spi_sync(flash->spi, &m);
+ ret = wait_till_ready(flash);
+ if (ret)
+ goto time_out;
+ *retlen += m.actual_length - m25p_cmdsz(flash);
+ write_disable(flash);
+ }
+
+time_out:
+ return ret;
+}
+
+/****************************************************************************/
+
+/*
+ * SPI device driver setup and teardown
+ */
+
+struct flash_info {
+ /* JEDEC id zero means "no ID" (most older chips); otherwise it has
+ * a high byte of zero plus three data bytes: the manufacturer id,
+ * then a two byte device id.
+ */
+ u32 jedec_id;
+ u16 ext_id;
+
+ /* The size listed here is what works with OPCODE_SE, which isn't
+ * necessarily called a "sector" by the vendor.
+ */
+ unsigned sector_size;
+ u16 n_sectors;
+
+ u16 page_size;
+ u16 addr_width;
+
+ u16 flags;
+#define SECT_4K 0x01 /* OPCODE_BE_4K works uniformly */
+#define M25P_NO_ERASE 0x02 /* No erase command needed */
+};
+
+#define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \
+ ((unsigned long)&(struct flash_info) { \
+ .jedec_id = (_jedec_id), \
+ .ext_id = (_ext_id), \
+ .sector_size = (_sector_size), \
+ .n_sectors = (_n_sectors), \
+ .page_size = 256, \
+ .flags = (_flags), \
+ })
+
+#define CAT25_INFO(_sector_size, _n_sectors, _page_size, _addr_width) \
+ ((unsigned long)&(struct flash_info) { \
+ .sector_size = (_sector_size), \
+ .n_sectors = (_n_sectors), \
+ .page_size = (_page_size), \
+ .addr_width = (_addr_width), \
+ .flags = M25P_NO_ERASE, \
+ })
+
+/* NOTE: double check command sets and memory organization when you add
+ * more flash chips. This current list focusses on newer chips, which
+ * have been converging on command sets which including JEDEC ID.
+ */
+static const struct spi_device_id m25p_ids[] = {
+ /* Atmel -- some are (confusingly) marketed as "DataFlash" */
+ { "at25fs010", INFO(0x1f6601, 0, 32 * 1024, 4, SECT_4K) },
+ { "at25fs040", INFO(0x1f6604, 0, 64 * 1024, 8, SECT_4K) },
+
+ { "at25df041a", INFO(0x1f4401, 0, 64 * 1024, 8, SECT_4K) },
+ { "at25df641", INFO(0x1f4800, 0, 64 * 1024, 128, SECT_4K) },
+
+ { "at26f004", INFO(0x1f0400, 0, 64 * 1024, 8, SECT_4K) },
+ { "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16, SECT_4K) },
+ { "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32, SECT_4K) },
+ { "at26df321", INFO(0x1f4700, 0, 64 * 1024, 64, SECT_4K) },
+
+ /* EON -- en25xxx */
+ { "en25f32", INFO(0x1c3116, 0, 64 * 1024, 64, SECT_4K) },
+ { "en25p32", INFO(0x1c2016, 0, 64 * 1024, 64, 0) },
+ { "en25p64", INFO(0x1c2017, 0, 64 * 1024, 128, 0) },
+
+ /* Intel/Numonyx -- xxxs33b */
+ { "160s33b", INFO(0x898911, 0, 64 * 1024, 32, 0) },
+ { "320s33b", INFO(0x898912, 0, 64 * 1024, 64, 0) },
+ { "640s33b", INFO(0x898913, 0, 64 * 1024, 128, 0) },
+
+ /* Macronix */
+ { "mx25l4005a", INFO(0xc22013, 0, 64 * 1024, 8, SECT_4K) },
+ { "mx25l8005", INFO(0xc22014, 0, 64 * 1024, 16, 0) },
+ { "mx25l3205d", INFO(0xc22016, 0, 64 * 1024, 64, 0) },
+ { "mx25l6405d", INFO(0xc22017, 0, 64 * 1024, 128, 0) },
+ { "mx25l12805d", INFO(0xc22018, 0, 64 * 1024, 256, 0) },
+ { "mx25l12855e", INFO(0xc22618, 0, 64 * 1024, 256, 0) },
+ { "mx25l25635e", INFO(0xc22019, 0, 64 * 1024, 512, 0) },
+ { "mx25l25655e", INFO(0xc22619, 0, 64 * 1024, 512, 0) },
+
+ /* Spansion -- single (large) sector size only, at least
+ * for the chips listed here (without boot sectors).
+ */
+ { "s25sl004a", INFO(0x010212, 0, 64 * 1024, 8, 0) },
+ { "s25sl008a", INFO(0x010213, 0, 64 * 1024, 16, 0) },
+ { "s25sl016a", INFO(0x010214, 0, 64 * 1024, 32, 0) },
+ { "s25sl032a", INFO(0x010215, 0, 64 * 1024, 64, 0) },
+ { "s25sl032p", INFO(0x010215, 0x4d00, 64 * 1024, 64, SECT_4K) },
+ { "s25sl064a", INFO(0x010216, 0, 64 * 1024, 128, 0) },
+ { "s25sl12800", INFO(0x012018, 0x0300, 256 * 1024, 64, 0) },
+ { "s25sl12801", INFO(0x012018, 0x0301, 64 * 1024, 256, 0) },
+ { "s25fl129p0", INFO(0x012018, 0x4d00, 256 * 1024, 64, 0) },
+ { "s25fl129p1", INFO(0x012018, 0x4d01, 64 * 1024, 256, 0) },
+ { "s25fl016k", INFO(0xef4015, 0, 64 * 1024, 32, SECT_4K) },
+ { "s25fl064k", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) },
+
+ /* SST -- large erase sizes are "overlays", "sectors" are 4K */
+ { "sst25vf040b", INFO(0xbf258d, 0, 64 * 1024, 8, SECT_4K) },
+ { "sst25vf080b", INFO(0xbf258e, 0, 64 * 1024, 16, SECT_4K) },
+ { "sst25vf016b", INFO(0xbf2541, 0, 64 * 1024, 32, SECT_4K) },
+ { "sst25vf032b", INFO(0xbf254a, 0, 64 * 1024, 64, SECT_4K) },
+ { "sst25wf512", INFO(0xbf2501, 0, 64 * 1024, 1, SECT_4K) },
+ { "sst25wf010", INFO(0xbf2502, 0, 64 * 1024, 2, SECT_4K) },
+ { "sst25wf020", INFO(0xbf2503, 0, 64 * 1024, 4, SECT_4K) },
+ { "sst25wf040", INFO(0xbf2504, 0, 64 * 1024, 8, SECT_4K) },
+
+ /* ST Microelectronics -- newer production may have feature updates */
+ { "m25p05", INFO(0x202010, 0, 32 * 1024, 2, 0) },
+ { "m25p10", INFO(0x202011, 0, 32 * 1024, 4, 0) },
+ { "m25p20", INFO(0x202012, 0, 64 * 1024, 4, 0) },
+ { "m25p40", INFO(0x202013, 0, 64 * 1024, 8, 0) },
+ { "m25p80", INFO(0x202014, 0, 64 * 1024, 16, 0) },
+ { "m25p16", INFO(0x202015, 0, 64 * 1024, 32, 0) },
+ { "m25p32", INFO(0x202016, 0, 64 * 1024, 64, 0) },
+ { "m25p64", INFO(0x202017, 0, 64 * 1024, 128, 0) },
+ { "m25p128", INFO(0x202018, 0, 256 * 1024, 64, 0) },
+
+ { "m25p05-nonjedec", INFO(0, 0, 32 * 1024, 2, 0) },
+ { "m25p10-nonjedec", INFO(0, 0, 32 * 1024, 4, 0) },
+ { "m25p20-nonjedec", INFO(0, 0, 64 * 1024, 4, 0) },
+ { "m25p40-nonjedec", INFO(0, 0, 64 * 1024, 8, 0) },
+ { "m25p80-nonjedec", INFO(0, 0, 64 * 1024, 16, 0) },
+ { "m25p16-nonjedec", INFO(0, 0, 64 * 1024, 32, 0) },
+ { "m25p32-nonjedec", INFO(0, 0, 64 * 1024, 64, 0) },
+ { "m25p64-nonjedec", INFO(0, 0, 64 * 1024, 128, 0) },
+ { "m25p128-nonjedec", INFO(0, 0, 256 * 1024, 64, 0) },
+
+ { "m45pe10", INFO(0x204011, 0, 64 * 1024, 2, 0) },
+ { "m45pe80", INFO(0x204014, 0, 64 * 1024, 16, 0) },
+ { "m45pe16", INFO(0x204015, 0, 64 * 1024, 32, 0) },
+
+ { "m25pe80", INFO(0x208014, 0, 64 * 1024, 16, 0) },
+ { "m25pe16", INFO(0x208015, 0, 64 * 1024, 32, SECT_4K) },
+
+ { "m25px64", INFO(0x207117, 0, 64 * 1024, 128, 0) },
+
+ /* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */
+ { "w25x10", INFO(0xef3011, 0, 64 * 1024, 2, SECT_4K) },
+ { "w25x20", INFO(0xef3012, 0, 64 * 1024, 4, SECT_4K) },
+ { "w25x40", INFO(0xef3013, 0, 64 * 1024, 8, SECT_4K) },
+ { "w25x80", INFO(0xef3014, 0, 64 * 1024, 16, SECT_4K) },
+ { "w25x16", INFO(0xef3015, 0, 64 * 1024, 32, SECT_4K) },
+ { "w25x32", INFO(0xef3016, 0, 64 * 1024, 64, SECT_4K) },
+ { "w25q32", INFO(0xef4016, 0, 64 * 1024, 64, SECT_4K) },
+ { "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, SECT_4K) },
+ { "w25q64", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) },
+
+ /* Catalyst / On Semiconductor -- non-JEDEC */
+ { "cat25c11", CAT25_INFO( 16, 8, 16, 1) },
+ { "cat25c03", CAT25_INFO( 32, 8, 16, 2) },
+ { "cat25c09", CAT25_INFO( 128, 8, 32, 2) },
+ { "cat25c17", CAT25_INFO( 256, 8, 32, 2) },
+ { "cat25128", CAT25_INFO(2048, 8, 64, 2) },
+
+ /* Micron */
+ { "n25q128", INFO(0x20ba18, 0, 64 * 1024, 256, 0) },
+ { },
+};
+
+static const struct spi_device_id *jedec_probe(struct spi_device *spi)
+{
+ int tmp;
+ u8 code = OPCODE_RDID;
+ u8 id[5];
+ u32 jedec;
+ u16 ext_jedec;
+ struct flash_info *info;
+
+ /* JEDEC also defines an optional "extended device information"
+ * string for after vendor-specific data, after the three bytes
+ * we use here. Supporting some chips might require using it.
+ */
+ tmp = spi_write_then_read(spi, &code, 1, id, 5);
+ if (tmp < 0) {
+ pr_debug("%s: error %d reading JEDEC ID\n",
+ dev_name(&spi->dev), tmp);
+ return ERR_PTR(tmp);
+ }
+ jedec = id[0];
+ jedec = jedec << 8;
+ jedec |= id[1];
+ jedec = jedec << 8;
+ jedec |= id[2];
+
+ ext_jedec = id[3] << 8 | id[4];
+
+ for (tmp = 0; tmp < ARRAY_SIZE(m25p_ids) - 1; tmp++) {
+ info = (void *)m25p_ids[tmp].driver_data;
+ if (info->jedec_id == jedec) {
+ if (info->ext_id != 0 && info->ext_id != ext_jedec)
+ continue;
+ return &m25p_ids[tmp];
+ }
+ }
+ dev_err(&spi->dev, "unrecognized JEDEC id %06x\n", jedec);
+ return ERR_PTR(-ENODEV);
+}
+
+
+/*
+ * board specific setup should have ensured the SPI clock used here
+ * matches what the READ command supports, at least until this driver
+ * understands FAST_READ (for clocks over 25 MHz).
+ */
+static int m25p_probe(struct device_d *dev)
+{
+ struct spi_device *spi = (struct spi_device *)dev->type_data;
+ const struct spi_device_id *id = NULL;
+ struct flash_platform_data *data;
+ struct m25p *flash;
+ struct flash_info *info = NULL;
+ unsigned i;
+ unsigned do_jdec_probe = 1;
+
+ /* Platform data helps sort out which chip type we have, as
+ * well as how this board partitions it. If we don't have
+ * a chip ID, try the JEDEC id commands; they'll work for most
+ * newer chips, even if we don't recognize the particular chip.
+ */
+ data = dev->platform_data;
+ if (data && data->type) {
+ const struct spi_device_id *plat_id;
+
+ for (i = 0; i < ARRAY_SIZE(m25p_ids) - 1; i++) {
+ plat_id = &m25p_ids[i];
+ if (strcmp(data->type, plat_id->name))
+ continue;
+ break;
+ }
+
+ if (i < ARRAY_SIZE(m25p_ids) - 1) {
+ id = plat_id;
+ info = (void *)id->driver_data;
+ /* If flash type is provided but the memory is not
+ * JEDEC compliant, don't try to probe the JEDEC id */
+ if (!info->jedec_id)
+ do_jdec_probe = 0;
+ } else
+ dev_warn(&spi->dev, "unrecognized id %s\n", data->type);
+ }
+
+ if (do_jdec_probe) {
+ const struct spi_device_id *jid;
+
+ jid = jedec_probe(spi);
+ if (IS_ERR(jid)) {
+ return PTR_ERR(jid);
+ } else if (jid != id) {
+ /*
+ * JEDEC knows better, so overwrite platform ID. We
+ * can't trust partitions any longer, but we'll let
+ * mtd apply them anyway, since some partitions may be
+ * marked read-only, and we don't want to lose that
+ * information, even if it's not 100% accurate.
+ */
+ if (id)
+ dev_warn(dev, "found %s, expected %s\n",
+ jid->name, id->name);
+
+ id = jid;
+ info = (void *)jid->driver_data;
+ }
+ }
+
+ flash = xzalloc(sizeof *flash);
+ flash->command = xmalloc(MAX_CMD_SIZE);
+
+ flash->spi = spi;
+ dev->priv = (void *)flash;
+ /*
+ * Atmel, SST and Intel/Numonyx serial flash tend to power
+ * up with the software protection bits set
+ */
+
+ if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ATMEL ||
+ JEDEC_MFR(info->jedec_id) == CFI_MFR_INTEL ||
+ JEDEC_MFR(info->jedec_id) == CFI_MFR_SST) {
+ write_enable(flash);
+ write_sr(flash, 0);
+ }
+
+ if (data && data->name)
+ flash->mtd.name = data->name;
+ else
+ flash->mtd.name = "m25p";
+
+ flash->mtd.type = MTD_NORFLASH;
+ flash->mtd.writesize = 1;
+ flash->mtd.flags = MTD_CAP_NORFLASH;
+ flash->mtd.size = info->sector_size * info->n_sectors;
+ flash->mtd.erase = m25p80_erase;
+ flash->mtd.read = m25p80_read;
+
+ /* sst flash chips use AAI word program */
+ if (IS_ENABLED(CONFIG_MTD_SST25L) && JEDEC_MFR(info->jedec_id) == CFI_MFR_SST)
+ flash->mtd.write = sst_write;
+ else
+ flash->mtd.write = m25p80_write;
+
+ /* prefer "small sector" erase if possible */
+ if (info->flags & SECT_4K) {
+ flash->erase_opcode_4k = OPCODE_BE_4K;
+ flash->erase_opcode = OPCODE_SE;
+ flash->mtd.erasesize = 4096;
+ } else {
+ flash->erase_opcode = OPCODE_SE;
+ flash->mtd.erasesize = info->sector_size;
+ }
+
+ if (info->flags & M25P_NO_ERASE)
+ flash->mtd.flags |= MTD_NO_ERASE;
+
+ flash->mtd.parent = &spi->dev;
+ flash->page_size = info->page_size;
+ flash->sector_size = info->sector_size;
+
+ if (info->addr_width)
+ flash->addr_width = info->addr_width;
+ else {
+ /* enable 4-byte addressing if the device exceeds 16MiB */
+ if (flash->mtd.size > 0x1000000) {
+ flash->addr_width = 4;
+ set_4byte(flash, info->jedec_id, 1);
+ } else
+ flash->addr_width = 3;
+ }
+
+ dev_info(dev, "%s (%lld Kbytes)\n", id->name,
+ (long long)flash->mtd.size >> 10);
+
+ pr_debug("mtd .name = %s, .size = 0x%llx (%lldMiB) "
+ ".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
+ flash->mtd.name,
+ (long long)flash->mtd.size, (long long)(flash->mtd.size >> 20),
+ flash->mtd.erasesize, flash->mtd.erasesize / 1024,
+ flash->mtd.numeraseregions);
+
+ if (flash->mtd.numeraseregions)
+ for (i = 0; i < flash->mtd.numeraseregions; i++)
+ pr_debug("mtd.eraseregions[%d] = { .offset = 0x%llx, "
+ ".erasesize = 0x%.8x (%uKiB), "
+ ".numblocks = %d }\n",
+ i, (long long)flash->mtd.eraseregions[i].offset,
+ flash->mtd.eraseregions[i].erasesize,
+ flash->mtd.eraseregions[i].erasesize / 1024,
+ flash->mtd.eraseregions[i].numblocks);
+
+
+
+ return add_mtd_device(&flash->mtd, flash->mtd.name);
+}
+
+static struct driver_d m25p80_driver = {
+ .name = "m25p80",
+ .probe = m25p_probe,
+};
+
+static int m25p80_init(void)
+{
+ return spi_register_driver(&m25p80_driver);
+}
+device_initcall(m25p80_init);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mike Lavender");
+MODULE_DESCRIPTION("MTD SPI driver for ST M25Pxx flash chips");