diff --git a/drivers/mtd/spi/Makefile b/drivers/mtd/spi/Makefile
index 90f8392..ce60e1b 100644
--- a/drivers/mtd/spi/Makefile
+++ b/drivers/mtd/spi/Makefile
@@ -29,6 +29,9 @@ ifdef CONFIG_SPL_BUILD
 COBJS-$(CONFIG_SPL_SPI_LOAD)	+= spi_spl_load.o
 endif
 
+ifeq ($(CONFIG_FTSPI020),y)
+COBJS-$(CONFIG_FTSPI020) += ftspi020.o
+else
 COBJS-$(CONFIG_SPI_FLASH)	+= spi_flash.o
 COBJS-$(CONFIG_SPI_FLASH_ATMEL)	+= atmel.o
 COBJS-$(CONFIG_SPI_FLASH_EON)	+= eon.o
@@ -39,6 +42,7 @@ COBJS-$(CONFIG_SPI_FLASH_STMICRO)	+= stmicro.o
 COBJS-$(CONFIG_SPI_FLASH_WINBOND)	+= winbond.o
 COBJS-$(CONFIG_SPI_FRAM_RAMTRON)	+= ramtron.o
 COBJS-$(CONFIG_SPI_M95XXX) += eeprom_m95xxx.o
+endif
 
 COBJS	:= $(COBJS-y)
 SRCS	:= $(COBJS:.o=.c)
diff --git a/drivers/mtd/spi/ftspi020.c b/drivers/mtd/spi/ftspi020.c
new file mode 100644
index 0000000..3d8a62a
--- /dev/null
+++ b/drivers/mtd/spi/ftspi020.c
@@ -0,0 +1,691 @@
+/*
+ * Faraday SPI Flash Controller
+ *
+ * (C) Copyright 2010 Faraday Technology
+ * Dante Su <dantesu@faraday-tech.com>
+ *
+ * This file is released under the terms of GPL v2 and any later version.
+ * See the file COPYING in the root directory of the source tree for details.
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <malloc.h>
+#include <spi.h>
+#include <spi_flash.h>
+
+#include "ftspi020.h"
+
+#define CFG_SUPP_FASTRD         1
+#define CFG_SUPP_FASTRD_DUAL    1 /* Support fast read dual(2x) */
+#define CFG_SUPP_FASTRD_QUAD    0 /* Support fast read quad(4x) */
+#define CFG_SHOW_PROGRESS       1 /* print a '.' at the end of each action */
+
+/* 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 (low freq.) */
+#define OPCODE_NORM_READ4       0x13 /* Read (low freq., 4 bytes addr) */
+#define OPCODE_FAST_READ        0x0b /* Read (high freq.) */
+#define OPCODE_FAST_READ4       0x0c /* Read (high freq., 4 bytes addr) */
+#define OPCODE_FAST_READ_DUAL   0x3b /* Read (high freq.) */
+#define OPCODE_FAST_READ4_DUAL  0x3c /* Read (high freq., 4 bytes addr) */
+#define OPCODE_FAST_READ_QUAD   0x6b /* Read (high freq.) */
+#define OPCODE_FAST_READ4_QUAD  0x6c /* Read (high freq. 4 bytes addr) */
+#define OPCODE_PP               0x02 /* Page program */
+#define OPCODE_PP4              0x12 /* Page program (4 bytes addr) */
+#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 */
+#define OPCODE_SE4              0xdc /* Sector erase (4 bytes addr) */
+#define OPCODE_RDID             0x9f /* Read JEDEC ID */
+
+/* Status Register bits. */
+#define SR_WIP                  BIT_MASK(0) /* Write in progress */
+#define SR_WEL                  BIT_MASK(1) /* Write enable latch */
+
+struct ftspi020_chip;
+
+struct spi_flash_param {
+	const char *name;
+	uint32_t    id;
+	uint32_t    ext_id;
+	uint32_t    sz_sector;
+	uint32_t    nr_sector;
+
+	uint32_t    flags;
+#define SECT_4K            BIT_MASK(0) /* OPCODE_BE_4K works uniformly */
+#if CFG_SUPP_FASTRD_DUAL
+# define FASTRD_DUAL       BIT_MASK(8)
+#else
+# define FASTRD_DUAL       0
+#endif
+#if CFG_SUPP_FASTRD_QUAD
+# define FASTRD_QUAD       BIT_MASK(9)
+#else
+# define FASTRD_QUAD       0
+#endif
+};
+
+/* spi_flash needs to be first so upper layers can free() it */
+struct spi_flash_info {
+	struct spi_flash flash;
+	struct ftspi020_chip *chip;
+	const struct spi_flash_param *param;
+
+	unsigned fastrd_dual:1;
+	unsigned fastrd_quad:1;
+};
+
+struct ftspi020_chip {
+	void *iobase;
+	uint32_t cs;
+	struct spi_slave *spi;
+	struct spi_flash_info *sfi;
+};
+
+/* Register access macros */
+#define SPI_READ(r)			le32_to_cpu(readl(r))
+#define SPI_WRITE(v, r)		writel(cpu_to_le32(v), r)
+#define SPI_SETBITS(m, r)	setbits_le32(r, m)
+#define SPI_CLRBITS(m, r)	clrbits_le32(r, m)
+
+static inline struct ftspi020_chip *
+flash_to_chip(struct spi_flash *flash)
+{
+	struct spi_flash_info *fl = (struct spi_flash_info *)flash;
+	return fl->chip;
+}
+
+static inline const struct spi_flash_param *
+flash_to_param(struct spi_flash *flash)
+{
+	struct spi_flash_info *fl = (struct spi_flash_info *)flash;
+	return fl->param;
+}
+
+static const struct spi_flash_param sf_list[] = {
+
+	/* Atmel -- some are (confusingly) marketed as "DataFlash" */
+	{ "at25fs010",  0x1f6601, 0, 32 * 1024,   4 },
+	{ "at25fs040",  0x1f6604, 0, 64 * 1024,   8 },
+
+	{ "at25df041a", 0x1f4401, 0, 64 * 1024,   8 },
+	{ "at25df321a", 0x1f4701, 0, 64 * 1024,  64 },
+	{ "at25df641",  0x1f4800, 0, 64 * 1024, 128 },
+
+	{ "at26f004",   0x1f0400, 0, 64 * 1024,  8 },
+	{ "at26df081a", 0x1f4501, 0, 64 * 1024, 16 },
+	{ "at26df161a", 0x1f4601, 0, 64 * 1024, 32 },
+	{ "at26df321",  0x1f4700, 0, 64 * 1024, 64 },
+
+	/* EON -- en25xxx */
+	{ "en25f32",   0x1c3116, 0, 64 * 1024,  64 },
+	{ "en25p32",   0x1c2016, 0, 64 * 1024,  64 },
+	{ "en25q32b",  0x1c3016, 0, 64 * 1024,  64 },
+	{ "en25p64",   0x1c2017, 0, 64 * 1024, 128 },
+	{ "en25qh256", 0x1c7019, 0, 64 * 1024, 512 },
+
+	/* GD -- GD25xxx */
+	{ "gd25q16",   0xc84015, 0, 64 * 1024,  32 },
+	{ "gd25q32",   0xc84016, 0, 64 * 1024,  64 },
+	{ "gd25q64",   0xc84017, 0, 64 * 1024, 128 },
+
+	/* Intel/Numonyx -- xxxs33b */
+	{ "160s33b",  0x898911, 0, 64 * 1024,  32 },
+	{ "320s33b",  0x898912, 0, 64 * 1024,  64 },
+	{ "640s33b",  0x898913, 0, 64 * 1024, 128 },
+
+	/* Macronix */
+	{ "mx25l4005a",  0xc22013, 0, 64 * 1024,   8 },
+	{ "mx25l8005",   0xc22014, 0, 64 * 1024,  16 },
+	{ "mx25l1606e",  0xc22015, 0, 64 * 1024,  32 },
+	{ "mx25l3205d",  0xc22016, 0, 64 * 1024,  64 },
+	{ "mx25l6405d",  0xc22017, 0, 64 * 1024, 128 },
+	{ "mx25l12805d", 0xc22018, 0, 64 * 1024, 256 },
+	{ "mx25l12855e", 0xc22618, 0, 64 * 1024, 256 },
+	{ "mx25l25635e", 0xc22019, 0, 64 * 1024, 512 },
+	{ "mx25l25655e", 0xc22619, 0, 64 * 1024, 512 },
+
+	/* Spansion -- single (large) sector size only, at least
+	 * for the chips listed here (without boot sectors).
+	 */
+	{ "s25sl004a",  0x010212,      0,  64 * 1024,   8 },
+	{ "s25sl008a",  0x010213,      0,  64 * 1024,  16 },
+	{ "s25sl016a",  0x010214,      0,  64 * 1024,  32 },
+	{ "s25sl032a",  0x010215,      0,  64 * 1024,  64 },
+	{ "s25sl032p",  0x010215, 0x4d00,  64 * 1024,  64 },
+	{ "s25sl064a",  0x010216,      0,  64 * 1024, 128 },
+	{ "s25fl128s0", 0x010218, 0x4d00, 256 * 1024,  64,
+		FASTRD_DUAL | FASTRD_QUAD },
+	{ "s25fl128s1", 0x010218, 0x4d01,  64 * 1024, 256,
+		FASTRD_DUAL | FASTRD_QUAD },
+	{ "s25fl256s0", 0x010219, 0x4d00, 256 * 1024, 128,
+		FASTRD_DUAL | FASTRD_QUAD },
+	{ "s25fl256s1", 0x010219, 0x4d01,  64 * 1024, 512,
+		FASTRD_DUAL | FASTRD_QUAD },
+	{ "s25fl512s",  0x010220, 0x4d00, 256 * 1024, 256,
+		FASTRD_DUAL | FASTRD_QUAD },
+	{ "s70fl01gs",  0x010221, 0x4d00, 256 * 1024, 256 },
+	{ "s25sl12800", 0x012018, 0x0300, 256 * 1024,  64 },
+	{ "s25sl12801", 0x012018, 0x0301,  64 * 1024, 256 },
+	{ "s25fl129p0", 0x012018, 0x4d00, 256 * 1024,  64,
+		FASTRD_DUAL | FASTRD_QUAD },
+	{ "s25fl129p1", 0x012018, 0x4d01,  64 * 1024, 256,
+		FASTRD_DUAL | FASTRD_QUAD },
+	{ "s25fl016k",  0xef4015,      0,  64 * 1024,  32 },
+	{ "s25fl064k",  0xef4017,      0,  64 * 1024, 128 },
+
+	/* SST -- large erase sizes are "overlays", "sectors" are 4K */
+	{ "sst25vf040b", 0xbf258d, 0, 64 * 1024,  8 },
+	{ "sst25vf080b", 0xbf258e, 0, 64 * 1024, 16 },
+	{ "sst25vf016b", 0xbf2541, 0, 64 * 1024, 32 },
+	{ "sst25vf032b", 0xbf254a, 0, 64 * 1024, 64 },
+	{ "sst25vf064c", 0xbf254b, 0, 64 * 1024, 128,
+		FASTRD_DUAL },
+	{ "sst25wf512",  0xbf2501, 0, 64 * 1024,  1 },
+	{ "sst25wf010",  0xbf2502, 0, 64 * 1024,  2 },
+	{ "sst25wf020",  0xbf2503, 0, 64 * 1024,  4 },
+	{ "sst25wf040",  0xbf2504, 0, 64 * 1024,  8 },
+
+	/* Micron -- newer production may have feature updates */
+	{ "m25p05",  0x202010,  0,  32 * 1024,   2 },
+	{ "m25p10",  0x202011,  0,  32 * 1024,   4 },
+	{ "m25p20",  0x202012,  0,  64 * 1024,   4 },
+	{ "m25p40",  0x202013,  0,  64 * 1024,   8 },
+	{ "m25p80",  0x202014,  0,  64 * 1024,  16 },
+	{ "m25p16",  0x202015,  0,  64 * 1024,  32 },
+	{ "m25p32",  0x202016,  0,  64 * 1024,  64 },
+	{ "m25p64",  0x202017,  0,  64 * 1024, 128 },
+	{ "m25p128", 0x202018,  0, 256 * 1024,  64 },
+
+	{ "m45pe10", 0x204011,  0, 64 * 1024,    2 },
+	{ "m45pe80", 0x204014,  0, 64 * 1024,   16 },
+	{ "m45pe16", 0x204015,  0, 64 * 1024,   32 },
+
+	{ "m25pe80", 0x208014,  0, 64 * 1024, 16 },
+	{ "m25pe16", 0x208015,  0, 64 * 1024, 32 },
+
+	{ "m25px32",    0x207116,  0, 64 * 1024, 64 },
+	{ "m25px32-s0", 0x207316,  0, 64 * 1024, 64 },
+	{ "m25px32-s1", 0x206316,  0, 64 * 1024, 64 },
+	{ "m25px64",    0x207117,  0, 64 * 1024, 128 },
+
+	{ "n25q032a13e", 0x20ba16, 0, 64 * 1024,  64, },
+	{ "n25q064a13e", 0x20ba17, 0, 64 * 1024, 128, },
+	{ "n25q128a13e", 0x20ba18, 0, 64 * 1024, 256, },
+	{ "n25q256a13e", 0x20ba19, 0, 64 * 1024, 512, },
+	{ "n25qax3g",    0x20ba20, 0, 64 * 1024, 1024, },
+	{ "n25q00aa13g", 0x20ba21, 0, 64 * 1024, 2048, },
+
+	/* Winbond */
+	{ "w25x10", 0xef3011, 0, 64 * 1024, 2, },
+	{ "w25x20", 0xef3012, 0, 64 * 1024, 4, },
+	{ "w25x40", 0xef3013, 0, 64 * 1024, 8, },
+	{ "w25p80", 0xef2014, 0, 64 * 1024, 16, },
+	{ "w25x80", 0xef3014, 0, 64 * 1024, 16, },
+	{ "w25p16", 0xef2015, 0, 64 * 1024, 32, },
+	{ "w25x16", 0xef3015, 0, 64 * 1024, 32, },
+	{ "w25x32", 0xef3016, 0, 64 * 1024, 64, },
+	{ "w25q32", 0xef4016, 0, 64 * 1024, 64, },
+	{ "w25x64", 0xef3017, 0, 64 * 1024, 128, },
+	{ "w25q64", 0xef4017, 0, 64 * 1024, 128, },
+	{ "w25q128", 0xef4018, 0, 64 * 1024, 256,
+		FASTRD_DUAL | FASTRD_QUAD },
+
+	/* generic */
+	{ "unknown",       0, 0, 64 * 1024, 256, },
+};
+
+static int ftspi020_rdid(struct ftspi020_chip *chip, void *buf)
+{
+	struct ftspi020_regs *regs = chip->iobase;
+	uint32_t id32[2];
+	uint8_t *id = buf;
+	int i;
+
+	/* clear isr */
+	SPI_WRITE(ISR_CMD, &regs->isr);
+
+	/* issue command */
+	SPI_WRITE(0, &regs->cmd[0]);
+	SPI_WRITE(CMD1_ILEN(1), &regs->cmd[1]);
+	SPI_WRITE(sizeof(id32), &regs->cmd[2]);
+	SPI_WRITE(CMD3_OPC(OPCODE_RDID) | CMD3_CS(chip->cs) | CMD3_CMDIRQ,
+		&regs->cmd[3]);
+
+	for (i = 0; i < ARRAY_SIZE(id32); ++i) {
+		/* wait until rx ready */
+		while (!(SPI_READ(&regs->sr) & SR_RFR))
+			;
+		id32[i] = SPI_READ(&regs->dr);
+	}
+
+	/* wait until command finish */
+	while (!(SPI_READ(&regs->isr) & ISR_CMD))
+		;
+	/* clear isr */
+	SPI_WRITE(ISR_CMD, &regs->isr);
+
+	memcpy(id, id32, 5);
+
+	return 0;
+}
+
+static int ftspi020_rdsr(struct ftspi020_chip *chip)
+{
+	struct ftspi020_regs *regs = chip->iobase;
+	int st;
+
+	/* clear isr */
+	SPI_WRITE(ISR_CMD, &regs->isr);
+
+	/* issue command */
+	SPI_WRITE(0, &regs->cmd[0]);
+	SPI_WRITE(CMD1_ILEN(1), &regs->cmd[1]);
+	SPI_WRITE(1, &regs->cmd[2]);
+	SPI_WRITE(CMD3_OPC(OPCODE_RDSR) | CMD3_CS(chip->cs)
+		| CMD3_CMDIRQ, &regs->cmd[3]);
+
+	/* wait until rx ready */
+	while (!(SPI_READ(&regs->sr) & SR_RFR))
+		;
+	st = SPI_READ(&regs->dr);
+
+	/* wait until command finish */
+	while (!(SPI_READ(&regs->isr) & ISR_CMD))
+		;
+	/* clear isr */
+	SPI_WRITE(ISR_CMD, &regs->isr);
+
+	return st & 0xff;
+}
+
+/*
+ * Write status register
+ * Returns negative if error occurred.
+ */
+static int ftspi020_wrsr(struct ftspi020_chip *chip, uint32_t val, uint8_t len)
+{
+	struct ftspi020_regs *regs = chip->iobase;
+
+	/* clear isr */
+	SPI_WRITE(ISR_CMD, &regs->isr);
+
+	/* issue command */
+	SPI_WRITE(0, &regs->cmd[0]);
+	SPI_WRITE(CMD1_ILEN(1), &regs->cmd[1]);
+	SPI_WRITE(len, &regs->cmd[2]);
+	SPI_WRITE(CMD3_OPC(OPCODE_WRSR) | CMD3_CS(chip->cs)
+		| CMD3_WRITE | CMD3_CMDIRQ, &regs->cmd[3]);
+
+	/* wait until tx ready */
+	while (!(SPI_READ(&regs->sr) & SR_TFR))
+		;
+	SPI_WRITE(val, &regs->dr);
+
+	/* wait until command finish */
+	while (!(SPI_READ(&regs->isr) & ISR_CMD))
+		;
+
+	/* wait until device ready */
+	while (ftspi020_rdsr(chip) & SR_WEL)
+		;
+	/* clear isr */
+	SPI_WRITE(ISR_CMD, &regs->isr);
+
+	return 0;
+}
+
+static int ftspi020_read(struct spi_flash *flash,
+	u32 off, size_t len, void *buf)
+{
+	struct ftspi020_chip *chip = flash_to_chip(flash);
+	struct ftspi020_regs *regs = chip->iobase;
+	struct spi_flash_info *fl = (struct spi_flash_info *)flash;
+	uint32_t i, v;
+
+	/* wait until device ready */
+	while (ftspi020_rdsr(chip) & SR_WIP)
+		;
+
+	/* issue command (Rd) */
+	SPI_WRITE(ISR_CMD, &regs->isr);
+	SPI_WRITE(off, &regs->cmd[0]);
+	SPI_WRITE(len, &regs->cmd[2]);
+
+	if (off < 0x1000000) {
+#if CFG_SUPP_FASTRD
+		SPI_WRITE(CMD1_ILEN(1) | CMD1_DCYC(8) | CMD1_ALEN(3),
+			&regs->cmd[1]);
+		if (fl->fastrd_quad) {
+			SPI_WRITE(CMD3_OPC(OPCODE_FAST_READ_QUAD)
+				| CMD3_CS(chip->cs) | CMD3_QUAD | CMD3_CMDIRQ,
+				&regs->cmd[3]);
+		} else if (fl->fastrd_dual) {
+			SPI_WRITE(CMD3_OPC(OPCODE_FAST_READ_DUAL)
+				| CMD3_CS(chip->cs) | CMD3_DUAL | CMD3_CMDIRQ,
+				&regs->cmd[3]);
+		} else {
+			SPI_WRITE(CMD3_OPC(OPCODE_FAST_READ) | CMD3_CS(chip->cs)
+				| CMD3_CMDIRQ, &regs->cmd[3]);
+		}
+#else
+		SPI_WRITE(CMD1_ILEN(1) | CMD1_ALEN(3), &regs->cmd[1]);
+		SPI_WRITE(CMD3_OPC(OPCODE_NORM_READ) | CMD3_CS(chip->cs)
+			| CMD3_CMDIRQ, &regs->cmd[3]);
+#endif
+	} else {
+#if CFG_SUPP_FASTRD
+		SPI_WRITE(CMD1_ILEN(1) | CMD1_DCYC(8) | CMD1_ALEN(4),
+			&regs->cmd[1]);
+		if (fl->fastrd_quad) {
+			SPI_WRITE(CMD3_OPC(OPCODE_FAST_READ4_QUAD)
+				| CMD3_CS(chip->cs) | CMD3_QUAD | CMD3_CMDIRQ,
+				&regs->cmd[3]);
+		} else if (fl->fastrd_dual) {
+			SPI_WRITE(CMD3_OPC(OPCODE_FAST_READ4_DUAL)
+				| CMD3_CS(chip->cs) | CMD3_DUAL | CMD3_CMDIRQ,
+				&regs->cmd[3]);
+		} else {
+			SPI_WRITE(CMD3_OPC(OPCODE_FAST_READ4)
+				| CMD3_CS(chip->cs) | CMD3_CMDIRQ,
+				&regs->cmd[3]);
+		}
+#else
+		SPI_WRITE(CMD1_ILEN(1) | CMD1_ALEN(4), &regs->cmd[1]);
+		SPI_WRITE(CMD3_OPC(OPCODE_NORM_READ4) | CMD3_CS(chip->cs)
+			| CMD3_CMDIRQ, &regs->cmd[3]);
+#endif
+	}
+
+	/* data phase */
+	for (i = 0; i < (len & 0xFFFFFFFC); ) {
+		/* wait until rx ready */
+		while (!(SPI_READ(&regs->sr) & SR_RFR))
+			;
+
+		*((uint32_t *)buf) = SPI_READ(&regs->dr);
+
+		buf = (void *)((uint32_t)buf + 4);
+		i += 4;
+	}
+
+	if (len & 0x03) {
+		/* wait until rx ready */
+		while (!(SPI_READ(&regs->sr) & SR_RFR))
+			;
+
+		v = SPI_READ(&regs->dr);
+
+		for (i = 0; i < (len & 0x03); ++i)
+			((uint8_t *)buf)[i] = ((uint8_t *)&v)[i];
+	}
+
+	/* wait until command finish */
+	while (!(SPI_READ(&regs->isr) & ISR_CMD))
+		;
+	/* clear isr */
+	SPI_WRITE(ISR_CMD, &regs->isr);
+
+	return 0;
+}
+
+static int ftspi020_wren(struct ftspi020_chip *chip)
+{
+	struct ftspi020_regs *regs = chip->iobase;
+
+	/* clear isr */
+	SPI_WRITE(ISR_CMD, &regs->isr);
+
+	/* issue command (WE) */
+	SPI_WRITE(0, &regs->cmd[0]);
+	SPI_WRITE(CMD1_ILEN(1), &regs->cmd[1]);
+	SPI_WRITE(0, &regs->cmd[2]);
+	SPI_WRITE(CMD3_OPC(OPCODE_WREN) | CMD3_CS(chip->cs)
+		| CMD3_WRITE | CMD3_CMDIRQ, &regs->cmd[3]);
+
+	/* wait until command finish */
+	while (!(SPI_READ(&regs->isr) & ISR_CMD))
+		;
+	/* clear isr */
+	SPI_WRITE(ISR_CMD, &regs->isr);
+
+	return 0;
+}
+
+static int ftspi020_write(struct spi_flash *flash,
+	u32 off, size_t len, const void *buf)
+{
+	int i, wl;
+	struct ftspi020_chip *chip = flash_to_chip(flash);
+	struct ftspi020_regs *regs = chip->iobase;
+
+	/* page write */
+	while (len > 0) {
+		wl = min(flash->page_size, len);
+
+#if CFG_SHOW_PROGRESS
+		/* output a '.' on each 64KB boundary */
+		if (!(off & 0x0000ffff))
+			puts(".");
+#endif
+		/* wait until device ready */
+		while (ftspi020_rdsr(chip) & SR_WIP)
+			;
+
+		/* write enable */
+		while (!(ftspi020_rdsr(chip) & SR_WEL))
+			ftspi020_wren(chip);
+
+		/* issue command (PP) */
+		SPI_WRITE(off, &regs->cmd[0]);
+		SPI_WRITE(wl, &regs->cmd[2]);
+		if (off < 0x1000000) {
+			SPI_WRITE(CMD1_ILEN(1) | CMD1_ALEN(3), &regs->cmd[1]);
+			SPI_WRITE(CMD3_OPC(OPCODE_PP) | CMD3_CS(chip->cs)
+				| CMD3_WRITE | CMD3_CMDIRQ, &regs->cmd[3]);
+		} else {
+			SPI_WRITE(CMD1_ILEN(1) | CMD1_ALEN(4), &regs->cmd[1]);
+			SPI_WRITE(CMD3_OPC(OPCODE_PP4) | CMD3_CS(chip->cs)
+				| CMD3_WRITE | CMD3_CMDIRQ, &regs->cmd[3]);
+		}
+
+		/* data phase */
+		for (i = 0; i < wl; i += 4) {
+			/* wait until tx ready */
+			while (!(SPI_READ(&regs->sr) & SR_TFR))
+				;
+			SPI_WRITE(*(uint32_t *)buf, &regs->dr);
+			buf = (void *)(((uint32_t)buf) + 4);
+		}
+		off += wl;
+		len -= wl;
+
+		/* wait until command finish */
+		while (!(SPI_READ(&regs->isr) & ISR_CMD))
+			;
+		/* clear isr */
+		SPI_WRITE(ISR_CMD, &regs->isr);
+	}
+
+	return 0;
+}
+
+static int ftspi020_erase(struct spi_flash *flash, u32 offset, size_t len)
+{
+	u32 addr = 0;
+	struct ftspi020_chip *chip = flash_to_chip(flash);
+	struct ftspi020_regs *regs = chip->iobase;
+	const struct spi_flash_param *param = flash_to_param(flash);
+
+	for (addr = offset & ~(param->sz_sector - 1);
+		addr < offset + len; addr += param->sz_sector) {
+
+		/* wait until device ready */
+		while (ftspi020_rdsr(chip) & SR_WIP)
+			;
+
+		/* write enable */
+		while (!(ftspi020_rdsr(chip) & SR_WEL))
+			ftspi020_wren(chip);
+
+		/* issue command (SE) */
+		SPI_WRITE(addr, &regs->cmd[0]);
+		SPI_WRITE(0x00, &regs->cmd[2]);
+		if (addr < 0x1000000) {
+			SPI_WRITE(CMD1_ILEN(1) | CMD1_ALEN(3), &regs->cmd[1]);
+			SPI_WRITE(CMD3_OPC(OPCODE_SE) | CMD3_CS(chip->cs)
+				| CMD3_WRITE | CMD3_CMDIRQ, &regs->cmd[3]);
+		} else {
+			SPI_WRITE(CMD1_ILEN(1) | CMD1_ALEN(4), &regs->cmd[1]);
+			SPI_WRITE(CMD3_OPC(OPCODE_SE4) | CMD3_CS(chip->cs)
+				| CMD3_WRITE | CMD3_CMDIRQ, &regs->cmd[3]);
+		}
+
+		/* wait until command finish */
+		while (!(SPI_READ(&regs->isr) & ISR_CMD))
+			;
+		/* clear isr */
+		SPI_WRITE(ISR_CMD, &regs->isr);
+
+#if CFG_SHOW_PROGRESS
+		puts(".");
+#endif
+	}
+
+	return 0;
+}
+
+static struct ftspi020_chip chip_list[] = {
+	{ .iobase = (void *)CONFIG_FTSPI020_BASE, },
+#ifdef CONFIG_FTSPI020_BASE1
+	{ .iobase = (void *)CONFIG_FTSPI020_BASE1, },
+#endif
+#ifdef CONFIG_FTSPI020_BASE2
+	{ .iobase = (void *)CONFIG_FTSPI020_BASE2, },
+#endif
+#ifdef CONFIG_FTSPI020_BASE3
+	{ .iobase = (void *)CONFIG_FTSPI020_BASE3, },
+#endif
+};
+
+struct spi_flash *
+spi_flash_probe(uint bus, uint cs, uint max_hz, uint spi_mode)
+{
+	struct ftspi020_chip *chip;
+	struct ftspi020_regs *regs;
+	u32 i, id, ext_id, div = 8;
+	u8  idcode[5];
+
+	if (bus > ARRAY_SIZE(chip_list) || cs > 3)
+		return NULL;
+
+	chip = &chip_list[bus];
+	regs = chip->iobase;
+	chip->cs = cs;
+
+	chip->spi = malloc(sizeof(struct spi_slave));
+	if (!chip->spi)
+		return NULL;
+	chip->spi->bus = bus;
+	chip->spi->cs  = cs;
+
+	chip->sfi = malloc(sizeof(struct spi_flash_info));
+	if (!chip->sfi)
+		return NULL;
+	chip->sfi->chip        = chip;
+	chip->sfi->flash.spi   = chip->spi;
+	chip->sfi->flash.read  = ftspi020_read;
+	chip->sfi->flash.write = ftspi020_write;
+	chip->sfi->flash.erase = ftspi020_erase;
+
+	/* reset */
+	SPI_WRITE(CR_ABORT, &regs->cr);
+	while (SPI_READ(&regs->cr) & CR_ABORT)
+		;
+
+	/* clock speed */
+	if (max_hz > 0) {
+		ulong clk = clk_get_rate("SPI");
+		for (div = 2; div < 8; div += 2) {
+			if (clk / div <= max_hz)
+				break;
+		}
+	}
+
+	/* mode + clock */
+	switch (spi_mode) {
+	case SPI_MODE_0:
+		SPI_WRITE((div >> 1) - 1, &regs->cr);
+		break;
+	case SPI_MODE_3:
+		SPI_WRITE(CR_CLK_MODE_3 | ((div >> 1) - 1), &regs->cr);
+		break;
+	default:
+		printf("ftspi020: MODE%d is not supported.\n", spi_mode);
+		free(chip->spi);
+		free(chip->sfi);
+		return NULL;
+	}
+
+	/* AC timing: worst trace delay and cs delay */
+	SPI_WRITE(0xff, &regs->atr);
+
+	debug("ftspi020: div=%d\n", div);
+
+	ftspi020_rdid(chip, idcode);
+
+	id     = (idcode[0] << 16) | (idcode[1] << 8) | (idcode[2]);
+	ext_id = (idcode[3] <<  8) | (idcode[4]);
+
+	printf("ftspi020: id=%06x.%04x\n", id, ext_id);
+
+	for (i = 0; i < ARRAY_SIZE(sf_list) - 1; ++i) {
+		if (id == sf_list[i].id && (!sf_list[i].ext_id
+			|| sf_list[i].ext_id == ext_id))
+			break;
+	}
+
+	/*
+	 * Atmel, SST and Intel/Numonyx serial flash tend to power
+	 * up with the software protection bits set
+	 */
+	ftspi020_wren(chip);
+	ftspi020_wrsr(chip, 0, 1);
+
+	chip->sfi->param = sf_list + i;
+	chip->sfi->flash.name = sf_list[i].name;
+	chip->sfi->flash.size = sf_list[i].sz_sector * sf_list[i].nr_sector;
+	chip->sfi->flash.page_size = 256;
+	chip->sfi->flash.sector_size = sf_list[i].sz_sector;
+	chip->sfi->flash.memory_map = NULL;
+
+	printf("ftspi020: %s (%u MB)\n",
+		chip->sfi->flash.name, chip->sfi->flash.size >> 20);
+
+	if (chip->sfi->param->flags & FASTRD_QUAD) {
+		printf("ftspi020: use fast read quad(4x)\n");
+		ftspi020_wren(chip);
+		ftspi020_wrsr(chip, 0x0200, 2);
+		chip->sfi->fastrd_quad = 1;
+	} else if (chip->sfi->param->flags & FASTRD_DUAL) {
+		printf("ftspi020: use fast read dual(2x)\n");
+		chip->sfi->fastrd_dual = 1;
+	}
+
+	return &chip->sfi->flash;
+}
+
+void spi_flash_free(struct spi_flash *flash)
+{
+	struct ftspi020_chip *chip;
+
+	if (flash) {
+		chip = flash_to_chip(flash);
+		free(chip->spi);
+		free(chip->sfi);
+	}
+}
diff --git a/drivers/mtd/spi/ftspi020.h b/drivers/mtd/spi/ftspi020.h
new file mode 100644
index 0000000..6138e9b
--- /dev/null
+++ b/drivers/mtd/spi/ftspi020.h
@@ -0,0 +1,109 @@
+/*
+ * Faraday SPI Flash Controller
+ *
+ * (C) Copyright 2010 Faraday Technology
+ * Dante Su <dantesu@faraday-tech.com>
+ *
+ * This file is released under the terms of GPL v2 and any later version.
+ * See the file COPYING in the root directory of the source tree for details.
+ */
+
+#ifndef _FTSPI020_H
+#define _FTSPI020_H
+
+/*
+ * FTSPI020 Registers
+ */
+struct ftspi020_regs {
+	/* 0x000 ~ 0x0fc */
+	uint32_t	cmd[4];	/* Command Register */
+	uint32_t	cr;		/* Control Register */
+	uint32_t	atr;	/* AC Timing Register */
+	uint32_t	sr;		/* Status Register */
+	uint32_t	rsvd0[1];
+	uint32_t	ier;	/* Interrupt Enable Register */
+	uint32_t	isr;	/* Interrupt Status Register */
+	uint32_t	rdsr;	/* Read Status Register */
+	uint32_t	rsvd1[9];
+	uint32_t	revr;	/* Revision Register */
+	uint32_t	fear;	/* Feature Register */
+	uint32_t	rsvd2[42];
+
+	/* 0x100 ~ 0x1fc */
+	uint32_t	dr;		/* Data Register */
+};
+
+/*
+ * Control Register offset 0x10
+ */
+#define CR_READY_LOC_MASK   ~(0x7 << 16)
+#define CR_READY_LOC(x)     (((x) & 0x7) << 16)
+#define CR_ABORT            BIT_MASK(8)
+#define CR_CLK_MODE_0       0
+#define CR_CLK_MODE_3       BIT_MASK(4)
+#define CR_CLK_DIVIDER_MASK ~(3 << 0)
+#define CR_CLK_DIVIDER_2    (0 << 0)
+#define CR_CLK_DIVIDER_4    (1 << 0)
+#define CR_CLK_DIVIDER_6    (2 << 0)
+#define CR_CLK_DIVIDER_8    (3 << 0)
+
+/*
+ * Status Register offset 0x18
+ */
+#define SR_RFR              BIT_MASK(1) /* RX FIFO Ready */
+#define SR_TFR              BIT_MASK(0) /* TX FIFO Ready */
+
+/*
+ * Interrupt Control Register
+ */
+#define ICR_RFTH(x)         (((x) & 0x3) << 12) /* RX FIFO Threshold */
+#define ICR_TFTH(x)         (((x) & 0x3) << 8)  /* TX FIFO Threshold */
+#define ICR_DMA             BIT_MASK(0) /* DMA HW Handshake Enable */
+
+/*
+ * Interrupt Status Register
+ */
+#define ISR_CMD             BIT_MASK(0) /* Command Complete/Finish  */
+
+/*
+ * Feature Register
+ */
+#define FEAR_CLK_MODE(reg)       (((reg) >> 25) & 0x1)
+#define FEAR_DTR_MODE(reg)       (((reg) >> 24) & 0x1)
+#define FEAR_CMDQ_DEPTH(reg)     (((reg) >> 16) & 0xff)
+#define FEAR_RXFIFO_DEPTH(reg)   (((reg) >>  8) & 0xff)
+#define FEAR_TXFIFO_DEPTH(reg)   (((reg) >>  0) & 0xff)
+
+/*
+ * CMD1 Register offset 4: Command Queue Second Word
+ */
+#define CMD1_CREAD               BIT_MASK(28)
+#define CMD1_ILEN(x)             (((x) & 0x03) << 24)
+#define CMD1_DCYC(x)             (((x) & 0xff) << 16)
+#define CMD1_ALEN(x)             ((x) & 0x07)
+
+/*
+ * CMD3 Register offset 0xc: Command Queue Fourth Word
+ */
+#define CMD3_OPC(x)              (((x) & 0xff) << 24)
+#define CMD3_OPC_CREAD(x)        (((x) & 0xff) << 16)
+#define CMD3_CS(x)               (((x) & 0x3) << 8)
+#define CMD3_SERIAL              (0 << 5)
+#define CMD3_DUAL                (1 << 5)
+#define CMD3_QUAD                (2 << 5)
+#define CMD3_DUAL_IO             (3 << 5)
+#define CMD3_QUAD_IO             (4 << 5)
+
+#define CMD3_DTR                 BIT_MASK(4)
+
+#define CMD3_RDST_SW             BIT_MASK(3)
+#define CMD3_RDST_HW             0
+
+#define CMD3_RDST                BIT_MASK(2)
+
+#define CMD3_WRITE               BIT_MASK(1)
+#define CMD3_READ                0
+
+#define CMD3_CMDIRQ              BIT_MASK(0)
+
+#endif
