aboutsummaryrefslogtreecommitdiff
path: root/drivers/mtd/chips
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/mtd/chips
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'drivers/mtd/chips')
-rw-r--r--drivers/mtd/chips/Kconfig286
-rw-r--r--drivers/mtd/chips/Makefile26
-rw-r--r--drivers/mtd/chips/amd_flash.c1415
-rw-r--r--drivers/mtd/chips/cfi_cmdset_0001.c2160
-rw-r--r--drivers/mtd/chips/cfi_cmdset_0002.c1515
-rw-r--r--drivers/mtd/chips/cfi_cmdset_0020.c1418
-rw-r--r--drivers/mtd/chips/cfi_probe.c445
-rw-r--r--drivers/mtd/chips/cfi_util.c196
-rw-r--r--drivers/mtd/chips/chipreg.c111
-rw-r--r--drivers/mtd/chips/fwh_lock.h107
-rw-r--r--drivers/mtd/chips/gen_probe.c255
-rw-r--r--drivers/mtd/chips/jedec.c934
-rw-r--r--drivers/mtd/chips/jedec_probe.c2127
-rw-r--r--drivers/mtd/chips/map_absent.c117
-rw-r--r--drivers/mtd/chips/map_ram.c143
-rw-r--r--drivers/mtd/chips/map_rom.c94
-rw-r--r--drivers/mtd/chips/sharp.c596
17 files changed, 11945 insertions, 0 deletions
diff --git a/drivers/mtd/chips/Kconfig b/drivers/mtd/chips/Kconfig
new file mode 100644
index 00000000000..d682dbc8157
--- /dev/null
+++ b/drivers/mtd/chips/Kconfig
@@ -0,0 +1,286 @@
+# drivers/mtd/chips/Kconfig
+# $Id: Kconfig,v 1.13 2004/12/01 15:49:10 nico Exp $
+
+menu "RAM/ROM/Flash chip drivers"
+ depends on MTD!=n
+
+config MTD_CFI
+ tristate "Detect flash chips by Common Flash Interface (CFI) probe"
+ depends on MTD
+ select MTD_GEN_PROBE
+ help
+ The Common Flash Interface specification was developed by Intel,
+ AMD and other flash manufactures that provides a universal method
+ for probing the capabilities of flash devices. If you wish to
+ support any device that is CFI-compliant, you need to enable this
+ option. Visit <http://www.amd.com/products/nvd/overview/cfi.html>
+ for more information on CFI.
+
+config MTD_JEDECPROBE
+ tristate "Detect non-CFI AMD/JEDEC-compatible flash chips"
+ depends on MTD
+ select MTD_GEN_PROBE
+ help
+ This option enables JEDEC-style probing of flash chips which are not
+ compatible with the Common Flash Interface, but will use the common
+ CFI-targetted flash drivers for any chips which are identified which
+ are in fact compatible in all but the probe method. This actually
+ covers most AMD/Fujitsu-compatible chips, and will shortly cover also
+ non-CFI Intel chips (that code is in MTD CVS and should shortly be sent
+ for inclusion in Linus' tree)
+
+config MTD_GEN_PROBE
+ tristate
+
+config MTD_CFI_ADV_OPTIONS
+ bool "Flash chip driver advanced configuration options"
+ depends on MTD_GEN_PROBE
+ help
+ If you need to specify a specific endianness for access to flash
+ chips, or if you wish to reduce the size of the kernel by including
+ support for only specific arrangements of flash chips, say 'Y'. This
+ option does not directly affect the code, but will enable other
+ configuration options which allow you to do so.
+
+ If unsure, say 'N'.
+
+choice
+ prompt "Flash cmd/query data swapping"
+ depends on MTD_CFI_ADV_OPTIONS
+ default MTD_CFI_NOSWAP
+
+config MTD_CFI_NOSWAP
+ bool "NO"
+ ---help---
+ This option defines the way in which the CPU attempts to arrange
+ data bits when writing the 'magic' commands to the chips. Saying
+ 'NO', which is the default when CONFIG_MTD_CFI_ADV_OPTIONS isn't
+ enabled, means that the CPU will not do any swapping; the chips
+ are expected to be wired to the CPU in 'host-endian' form.
+ Specific arrangements are possible with the BIG_ENDIAN_BYTE and
+ LITTLE_ENDIAN_BYTE, if the bytes are reversed.
+
+ If you have a LART, on which the data (and address) lines were
+ connected in a fashion which ensured that the nets were as short
+ as possible, resulting in a bit-shuffling which seems utterly
+ random to the untrained eye, you need the LART_ENDIAN_BYTE option.
+
+ Yes, there really exists something sicker than PDP-endian :)
+
+config MTD_CFI_BE_BYTE_SWAP
+ bool "BIG_ENDIAN_BYTE"
+
+config MTD_CFI_LE_BYTE_SWAP
+ bool "LITTLE_ENDIAN_BYTE"
+
+endchoice
+
+config MTD_CFI_GEOMETRY
+ bool "Specific CFI Flash geometry selection"
+ depends on MTD_CFI_ADV_OPTIONS
+ help
+ This option does not affect the code directly, but will enable
+ some other configuration options which would allow you to reduce
+ the size of the kernel by including support for only certain
+ arrangements of CFI chips. If unsure, say 'N' and all options
+ which are supported by the current code will be enabled.
+
+config MTD_MAP_BANK_WIDTH_1
+ bool "Support 8-bit buswidth" if MTD_CFI_GEOMETRY
+ default y
+ help
+ If you wish to support CFI devices on a physical bus which is
+ 8 bits wide, say 'Y'.
+
+config MTD_MAP_BANK_WIDTH_2
+ bool "Support 16-bit buswidth" if MTD_CFI_GEOMETRY
+ default y
+ help
+ If you wish to support CFI devices on a physical bus which is
+ 16 bits wide, say 'Y'.
+
+config MTD_MAP_BANK_WIDTH_4
+ bool "Support 32-bit buswidth" if MTD_CFI_GEOMETRY
+ default y
+ help
+ If you wish to support CFI devices on a physical bus which is
+ 32 bits wide, say 'Y'.
+
+config MTD_MAP_BANK_WIDTH_8
+ bool "Support 64-bit buswidth" if MTD_CFI_GEOMETRY
+ default n
+ help
+ If you wish to support CFI devices on a physical bus which is
+ 64 bits wide, say 'Y'.
+
+config MTD_MAP_BANK_WIDTH_16
+ bool "Support 128-bit buswidth" if MTD_CFI_GEOMETRY
+ default n
+ help
+ If you wish to support CFI devices on a physical bus which is
+ 128 bits wide, say 'Y'.
+
+config MTD_MAP_BANK_WIDTH_32
+ bool "Support 256-bit buswidth" if MTD_CFI_GEOMETRY
+ default n
+ help
+ If you wish to support CFI devices on a physical bus which is
+ 256 bits wide, say 'Y'.
+
+config MTD_CFI_I1
+ bool "Support 1-chip flash interleave" if MTD_CFI_GEOMETRY
+ default y
+ help
+ If your flash chips are not interleaved - i.e. you only have one
+ flash chip addressed by each bus cycle, then say 'Y'.
+
+config MTD_CFI_I2
+ bool "Support 2-chip flash interleave" if MTD_CFI_GEOMETRY
+ default y
+ help
+ If your flash chips are interleaved in pairs - i.e. you have two
+ flash chips addressed by each bus cycle, then say 'Y'.
+
+config MTD_CFI_I4
+ bool "Support 4-chip flash interleave" if MTD_CFI_GEOMETRY
+ default n
+ help
+ If your flash chips are interleaved in fours - i.e. you have four
+ flash chips addressed by each bus cycle, then say 'Y'.
+
+config MTD_CFI_I8
+ bool "Support 8-chip flash interleave" if MTD_CFI_GEOMETRY
+ default n
+ help
+ If your flash chips are interleaved in eights - i.e. you have eight
+ flash chips addressed by each bus cycle, then say 'Y'.
+
+config MTD_CFI_INTELEXT
+ tristate "Support for Intel/Sharp flash chips"
+ depends on MTD_GEN_PROBE
+ select MTD_CFI_UTIL
+ help
+ The Common Flash Interface defines a number of different command
+ sets which a CFI-compliant chip may claim to implement. This code
+ provides support for one of those command sets, used on Intel
+ StrataFlash and other parts.
+
+config MTD_CFI_AMDSTD
+ tristate "Support for AMD/Fujitsu flash chips"
+ depends on MTD_GEN_PROBE
+ select MTD_CFI_UTIL
+ help
+ The Common Flash Interface defines a number of different command
+ sets which a CFI-compliant chip may claim to implement. This code
+ provides support for one of those command sets, used on chips
+ including the AMD Am29LV320.
+
+config MTD_CFI_AMDSTD_RETRY
+ int "Retry failed commands (erase/program)"
+ depends on MTD_CFI_AMDSTD
+ default "0"
+ help
+ Some chips, when attached to a shared bus, don't properly filter
+ bus traffic that is destined to other devices. This broken
+ behavior causes erase and program sequences to be aborted when
+ the sequences are mixed with traffic for other devices.
+
+ SST49LF040 (and related) chips are know to be broken.
+
+config MTD_CFI_AMDSTD_RETRY_MAX
+ int "Max retries of failed commands (erase/program)"
+ depends on MTD_CFI_AMDSTD_RETRY
+ default "0"
+ help
+ If you have an SST49LF040 (or related chip) then this value should
+ be set to at least 1. This can also be adjusted at driver load
+ time with the retry_cmd_max module parameter.
+
+config MTD_CFI_STAA
+ tristate "Support for ST (Advanced Architecture) flash chips"
+ depends on MTD_GEN_PROBE
+ select MTD_CFI_UTIL
+ help
+ The Common Flash Interface defines a number of different command
+ sets which a CFI-compliant chip may claim to implement. This code
+ provides support for one of those command sets.
+
+config MTD_CFI_UTIL
+ tristate
+
+config MTD_RAM
+ tristate "Support for RAM chips in bus mapping"
+ depends on MTD
+ help
+ This option enables basic support for RAM chips accessed through
+ a bus mapping driver.
+
+config MTD_ROM
+ tristate "Support for ROM chips in bus mapping"
+ depends on MTD
+ help
+ This option enables basic support for ROM chips accessed through
+ a bus mapping driver.
+
+config MTD_ABSENT
+ tristate "Support for absent chips in bus mapping"
+ depends on MTD
+ help
+ This option enables support for a dummy probing driver used to
+ allocated placeholder MTD devices on systems that have socketed
+ or removable media. Use of this driver as a fallback chip probe
+ preserves the expected registration order of MTD device nodes on
+ the system regardless of media presence. Device nodes created
+ with this driver will return -ENODEV upon access.
+
+config MTD_OBSOLETE_CHIPS
+ depends on MTD && BROKEN
+ bool "Older (theoretically obsoleted now) drivers for non-CFI chips"
+ help
+ This option does not enable any code directly, but will allow you to
+ select some other chip drivers which are now considered obsolete,
+ because the generic CONFIG_JEDECPROBE code above should now detect
+ the chips which are supported by these drivers, and allow the generic
+ CFI-compatible drivers to drive the chips. Say 'N' here unless you have
+ already tried the CONFIG_JEDECPROBE method and reported its failure
+ to the MTD mailing list at <linux-mtd@lists.infradead.org>
+
+config MTD_AMDSTD
+ tristate "AMD compatible flash chip support (non-CFI)"
+ depends on MTD && MTD_OBSOLETE_CHIPS
+ help
+ This option enables support for flash chips using AMD-compatible
+ commands, including some which are not CFI-compatible and hence
+ cannot be used with the CONFIG_MTD_CFI_AMDSTD option.
+
+ It also works on AMD compatible chips that do conform to CFI.
+
+config MTD_SHARP
+ tristate "pre-CFI Sharp chip support"
+ depends on MTD && MTD_OBSOLETE_CHIPS
+ help
+ This option enables support for flash chips using Sharp-compatible
+ commands, including some which are not CFI-compatible and hence
+ cannot be used with the CONFIG_MTD_CFI_INTELxxx options.
+
+config MTD_JEDEC
+ tristate "JEDEC device support"
+ depends on MTD && MTD_OBSOLETE_CHIPS
+ help
+ Enable older older JEDEC flash interface devices for self
+ programming flash. It is commonly used in older AMD chips. It is
+ only called JEDEC because the JEDEC association
+ <http://www.jedec.org/> distributes the identification codes for the
+ chips.
+
+config MTD_XIP
+ bool "XIP aware MTD support"
+ depends on !SMP && MTD_CFI_INTELEXT && EXPERIMENTAL
+ default y if XIP_KERNEL
+ help
+ This allows MTD support to work with flash memory which is also
+ used for XIP purposes. If you're not sure what this is all about
+ then say N.
+
+endmenu
+
diff --git a/drivers/mtd/chips/Makefile b/drivers/mtd/chips/Makefile
new file mode 100644
index 00000000000..6830489828c
--- /dev/null
+++ b/drivers/mtd/chips/Makefile
@@ -0,0 +1,26 @@
+#
+# linux/drivers/chips/Makefile
+#
+# $Id: Makefile.common,v 1.4 2004/07/12 16:07:30 dwmw2 Exp $
+
+# *** BIG UGLY NOTE ***
+#
+# The removal of get_module_symbol() and replacement with
+# inter_module_register() et al has introduced a link order dependency
+# here where previously there was none. We now have to ensure that
+# the CFI command set drivers are linked before gen_probe.o
+
+obj-$(CONFIG_MTD) += chipreg.o
+obj-$(CONFIG_MTD_AMDSTD) += amd_flash.o
+obj-$(CONFIG_MTD_CFI) += cfi_probe.o
+obj-$(CONFIG_MTD_CFI_UTIL) += cfi_util.o
+obj-$(CONFIG_MTD_CFI_STAA) += cfi_cmdset_0020.o
+obj-$(CONFIG_MTD_CFI_AMDSTD) += cfi_cmdset_0002.o
+obj-$(CONFIG_MTD_CFI_INTELEXT) += cfi_cmdset_0001.o
+obj-$(CONFIG_MTD_GEN_PROBE) += gen_probe.o
+obj-$(CONFIG_MTD_JEDEC) += jedec.o
+obj-$(CONFIG_MTD_JEDECPROBE) += jedec_probe.o
+obj-$(CONFIG_MTD_RAM) += map_ram.o
+obj-$(CONFIG_MTD_ROM) += map_rom.o
+obj-$(CONFIG_MTD_SHARP) += sharp.o
+obj-$(CONFIG_MTD_ABSENT) += map_absent.o
diff --git a/drivers/mtd/chips/amd_flash.c b/drivers/mtd/chips/amd_flash.c
new file mode 100644
index 00000000000..41e2e3e3160
--- /dev/null
+++ b/drivers/mtd/chips/amd_flash.c
@@ -0,0 +1,1415 @@
+/*
+ * MTD map driver for AMD compatible flash chips (non-CFI)
+ *
+ * Author: Jonas Holmberg <jonas.holmberg@axis.com>
+ *
+ * $Id: amd_flash.c,v 1.26 2004/11/20 12:49:04 dwmw2 Exp $
+ *
+ * Copyright (c) 2001 Axis Communications AB
+ *
+ * This file is under GPL.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/flashchip.h>
+
+/* There's no limit. It exists only to avoid realloc. */
+#define MAX_AMD_CHIPS 8
+
+#define DEVICE_TYPE_X8 (8 / 8)
+#define DEVICE_TYPE_X16 (16 / 8)
+#define DEVICE_TYPE_X32 (32 / 8)
+
+/* Addresses */
+#define ADDR_MANUFACTURER 0x0000
+#define ADDR_DEVICE_ID 0x0001
+#define ADDR_SECTOR_LOCK 0x0002
+#define ADDR_HANDSHAKE 0x0003
+#define ADDR_UNLOCK_1 0x0555
+#define ADDR_UNLOCK_2 0x02AA
+
+/* Commands */
+#define CMD_UNLOCK_DATA_1 0x00AA
+#define CMD_UNLOCK_DATA_2 0x0055
+#define CMD_MANUFACTURER_UNLOCK_DATA 0x0090
+#define CMD_UNLOCK_BYPASS_MODE 0x0020
+#define CMD_PROGRAM_UNLOCK_DATA 0x00A0
+#define CMD_RESET_DATA 0x00F0
+#define CMD_SECTOR_ERASE_UNLOCK_DATA 0x0080
+#define CMD_SECTOR_ERASE_UNLOCK_DATA_2 0x0030
+
+#define CMD_UNLOCK_SECTOR 0x0060
+
+/* Manufacturers */
+#define MANUFACTURER_AMD 0x0001
+#define MANUFACTURER_ATMEL 0x001F
+#define MANUFACTURER_FUJITSU 0x0004
+#define MANUFACTURER_ST 0x0020
+#define MANUFACTURER_SST 0x00BF
+#define MANUFACTURER_TOSHIBA 0x0098
+
+/* AMD */
+#define AM29F800BB 0x2258
+#define AM29F800BT 0x22D6
+#define AM29LV800BB 0x225B
+#define AM29LV800BT 0x22DA
+#define AM29LV160DT 0x22C4
+#define AM29LV160DB 0x2249
+#define AM29BDS323D 0x22D1
+#define AM29BDS643D 0x227E
+
+/* Atmel */
+#define AT49xV16x 0x00C0
+#define AT49xV16xT 0x00C2
+
+/* Fujitsu */
+#define MBM29LV160TE 0x22C4
+#define MBM29LV160BE 0x2249
+#define MBM29LV800BB 0x225B
+
+/* ST - www.st.com */
+#define M29W800T 0x00D7
+#define M29W160DT 0x22C4
+#define M29W160DB 0x2249
+
+/* SST */
+#define SST39LF800 0x2781
+#define SST39LF160 0x2782
+
+/* Toshiba */
+#define TC58FVT160 0x00C2
+#define TC58FVB160 0x0043
+
+#define D6_MASK 0x40
+
+struct amd_flash_private {
+ int device_type;
+ int interleave;
+ int numchips;
+ unsigned long chipshift;
+// const char *im_name;
+ struct flchip chips[0];
+};
+
+struct amd_flash_info {
+ const __u16 mfr_id;
+ const __u16 dev_id;
+ const char *name;
+ const u_long size;
+ const int numeraseregions;
+ const struct mtd_erase_region_info regions[4];
+};
+
+
+
+static int amd_flash_read(struct mtd_info *, loff_t, size_t, size_t *,
+ u_char *);
+static int amd_flash_write(struct mtd_info *, loff_t, size_t, size_t *,
+ const u_char *);
+static int amd_flash_erase(struct mtd_info *, struct erase_info *);
+static void amd_flash_sync(struct mtd_info *);
+static int amd_flash_suspend(struct mtd_info *);
+static void amd_flash_resume(struct mtd_info *);
+static void amd_flash_destroy(struct mtd_info *);
+static struct mtd_info *amd_flash_probe(struct map_info *map);
+
+
+static struct mtd_chip_driver amd_flash_chipdrv = {
+ .probe = amd_flash_probe,
+ .destroy = amd_flash_destroy,
+ .name = "amd_flash",
+ .module = THIS_MODULE
+};
+
+
+
+static const char im_name[] = "amd_flash";
+
+
+
+static inline __u32 wide_read(struct map_info *map, __u32 addr)
+{
+ if (map->buswidth == 1) {
+ return map_read8(map, addr);
+ } else if (map->buswidth == 2) {
+ return map_read16(map, addr);
+ } else if (map->buswidth == 4) {
+ return map_read32(map, addr);
+ }
+
+ return 0;
+}
+
+static inline void wide_write(struct map_info *map, __u32 val, __u32 addr)
+{
+ if (map->buswidth == 1) {
+ map_write8(map, val, addr);
+ } else if (map->buswidth == 2) {
+ map_write16(map, val, addr);
+ } else if (map->buswidth == 4) {
+ map_write32(map, val, addr);
+ }
+}
+
+static inline __u32 make_cmd(struct map_info *map, __u32 cmd)
+{
+ const struct amd_flash_private *private = map->fldrv_priv;
+ if ((private->interleave == 2) &&
+ (private->device_type == DEVICE_TYPE_X16)) {
+ cmd |= (cmd << 16);
+ }
+
+ return cmd;
+}
+
+static inline void send_unlock(struct map_info *map, unsigned long base)
+{
+ wide_write(map, (CMD_UNLOCK_DATA_1 << 16) | CMD_UNLOCK_DATA_1,
+ base + (map->buswidth * ADDR_UNLOCK_1));
+ wide_write(map, (CMD_UNLOCK_DATA_2 << 16) | CMD_UNLOCK_DATA_2,
+ base + (map->buswidth * ADDR_UNLOCK_2));
+}
+
+static inline void send_cmd(struct map_info *map, unsigned long base, __u32 cmd)
+{
+ send_unlock(map, base);
+ wide_write(map, make_cmd(map, cmd),
+ base + (map->buswidth * ADDR_UNLOCK_1));
+}
+
+static inline void send_cmd_to_addr(struct map_info *map, unsigned long base,
+ __u32 cmd, unsigned long addr)
+{
+ send_unlock(map, base);
+ wide_write(map, make_cmd(map, cmd), addr);
+}
+
+static inline int flash_is_busy(struct map_info *map, unsigned long addr,
+ int interleave)
+{
+
+ if ((interleave == 2) && (map->buswidth == 4)) {
+ __u32 read1, read2;
+
+ read1 = wide_read(map, addr);
+ read2 = wide_read(map, addr);
+
+ return (((read1 >> 16) & D6_MASK) !=
+ ((read2 >> 16) & D6_MASK)) ||
+ (((read1 & 0xffff) & D6_MASK) !=
+ ((read2 & 0xffff) & D6_MASK));
+ }
+
+ return ((wide_read(map, addr) & D6_MASK) !=
+ (wide_read(map, addr) & D6_MASK));
+}
+
+static inline void unlock_sector(struct map_info *map, unsigned long sect_addr,
+ int unlock)
+{
+ /* Sector lock address. A6 = 1 for unlock, A6 = 0 for lock */
+ int SLA = unlock ?
+ (sect_addr | (0x40 * map->buswidth)) :
+ (sect_addr & ~(0x40 * map->buswidth)) ;
+
+ __u32 cmd = make_cmd(map, CMD_UNLOCK_SECTOR);
+
+ wide_write(map, make_cmd(map, CMD_RESET_DATA), 0);
+ wide_write(map, cmd, SLA); /* 1st cycle: write cmd to any address */
+ wide_write(map, cmd, SLA); /* 2nd cycle: write cmd to any address */
+ wide_write(map, cmd, SLA); /* 3rd cycle: write cmd to SLA */
+}
+
+static inline int is_sector_locked(struct map_info *map,
+ unsigned long sect_addr)
+{
+ int status;
+
+ wide_write(map, CMD_RESET_DATA, 0);
+ send_cmd(map, sect_addr, CMD_MANUFACTURER_UNLOCK_DATA);
+
+ /* status is 0x0000 for unlocked and 0x0001 for locked */
+ status = wide_read(map, sect_addr + (map->buswidth * ADDR_SECTOR_LOCK));
+ wide_write(map, CMD_RESET_DATA, 0);
+ return status;
+}
+
+static int amd_flash_do_unlock(struct mtd_info *mtd, loff_t ofs, size_t len,
+ int is_unlock)
+{
+ struct map_info *map;
+ struct mtd_erase_region_info *merip;
+ int eraseoffset, erasesize, eraseblocks;
+ int i;
+ int retval = 0;
+ int lock_status;
+
+ map = mtd->priv;
+
+ /* Pass the whole chip through sector by sector and check for each
+ sector if the sector and the given interval overlap */
+ for(i = 0; i < mtd->numeraseregions; i++) {
+ merip = &mtd->eraseregions[i];
+
+ eraseoffset = merip->offset;
+ erasesize = merip->erasesize;
+ eraseblocks = merip->numblocks;
+
+ if (ofs > eraseoffset + erasesize)
+ continue;
+
+ while (eraseblocks > 0) {
+ if (ofs < eraseoffset + erasesize && ofs + len > eraseoffset) {
+ unlock_sector(map, eraseoffset, is_unlock);
+
+ lock_status = is_sector_locked(map, eraseoffset);
+
+ if (is_unlock && lock_status) {
+ printk("Cannot unlock sector at address %x length %xx\n",
+ eraseoffset, merip->erasesize);
+ retval = -1;
+ } else if (!is_unlock && !lock_status) {
+ printk("Cannot lock sector at address %x length %x\n",
+ eraseoffset, merip->erasesize);
+ retval = -1;
+ }
+ }
+ eraseoffset += erasesize;
+ eraseblocks --;
+ }
+ }
+ return retval;
+}
+
+static int amd_flash_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
+{
+ return amd_flash_do_unlock(mtd, ofs, len, 1);
+}
+
+static int amd_flash_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
+{
+ return amd_flash_do_unlock(mtd, ofs, len, 0);
+}
+
+
+/*
+ * Reads JEDEC manufacturer ID and device ID and returns the index of the first
+ * matching table entry (-1 if not found or alias for already found chip).
+ */
+static int probe_new_chip(struct mtd_info *mtd, __u32 base,
+ struct flchip *chips,
+ struct amd_flash_private *private,
+ const struct amd_flash_info *table, int table_size)
+{
+ __u32 mfr_id;
+ __u32 dev_id;
+ struct map_info *map = mtd->priv;
+ struct amd_flash_private temp;
+ int i;
+
+ temp.device_type = DEVICE_TYPE_X16; // Assume X16 (FIXME)
+ temp.interleave = 2;
+ map->fldrv_priv = &temp;
+
+ /* Enter autoselect mode. */
+ send_cmd(map, base, CMD_RESET_DATA);
+ send_cmd(map, base, CMD_MANUFACTURER_UNLOCK_DATA);
+
+ mfr_id = wide_read(map, base + (map->buswidth * ADDR_MANUFACTURER));
+ dev_id = wide_read(map, base + (map->buswidth * ADDR_DEVICE_ID));
+
+ if ((map->buswidth == 4) && ((mfr_id >> 16) == (mfr_id & 0xffff)) &&
+ ((dev_id >> 16) == (dev_id & 0xffff))) {
+ mfr_id &= 0xffff;
+ dev_id &= 0xffff;
+ } else {
+ temp.interleave = 1;
+ }
+
+ for (i = 0; i < table_size; i++) {
+ if ((mfr_id == table[i].mfr_id) &&
+ (dev_id == table[i].dev_id)) {
+ if (chips) {
+ int j;
+
+ /* Is this an alias for an already found chip?
+ * In that case that chip should be in
+ * autoselect mode now.
+ */
+ for (j = 0; j < private->numchips; j++) {
+ __u32 mfr_id_other;
+ __u32 dev_id_other;
+
+ mfr_id_other =
+ wide_read(map, chips[j].start +
+ (map->buswidth *
+ ADDR_MANUFACTURER
+ ));
+ dev_id_other =
+ wide_read(map, chips[j].start +
+ (map->buswidth *
+ ADDR_DEVICE_ID));
+ if (temp.interleave == 2) {
+ mfr_id_other &= 0xffff;
+ dev_id_other &= 0xffff;
+ }
+ if ((mfr_id_other == mfr_id) &&
+ (dev_id_other == dev_id)) {
+
+ /* Exit autoselect mode. */
+ send_cmd(map, base,
+ CMD_RESET_DATA);
+
+ return -1;
+ }
+ }
+
+ if (private->numchips == MAX_AMD_CHIPS) {
+ printk(KERN_WARNING
+ "%s: Too many flash chips "
+ "detected. Increase "
+ "MAX_AMD_CHIPS from %d.\n",
+ map->name, MAX_AMD_CHIPS);
+
+ return -1;
+ }
+
+ chips[private->numchips].start = base;
+ chips[private->numchips].state = FL_READY;
+ chips[private->numchips].mutex =
+ &chips[private->numchips]._spinlock;
+ private->numchips++;
+ }
+
+ printk("%s: Found %d x %ldMiB %s at 0x%x\n", map->name,
+ temp.interleave, (table[i].size)/(1024*1024),
+ table[i].name, base);
+
+ mtd->size += table[i].size * temp.interleave;
+ mtd->numeraseregions += table[i].numeraseregions;
+
+ break;
+ }
+ }
+
+ /* Exit autoselect mode. */
+ send_cmd(map, base, CMD_RESET_DATA);
+
+ if (i == table_size) {
+ printk(KERN_DEBUG "%s: unknown flash device at 0x%x, "
+ "mfr id 0x%x, dev id 0x%x\n", map->name,
+ base, mfr_id, dev_id);
+ map->fldrv_priv = NULL;
+
+ return -1;
+ }
+
+ private->device_type = temp.device_type;
+ private->interleave = temp.interleave;
+
+ return i;
+}
+
+
+
+static struct mtd_info *amd_flash_probe(struct map_info *map)
+{
+ static const struct amd_flash_info table[] = {
+ {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29LV160DT,
+ .name = "AMD AM29LV160DT",
+ .size = 0x00200000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 31 },
+ { .offset = 0x1F0000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x1F8000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x1FC000, .erasesize = 0x04000, .numblocks = 1 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29LV160DB,
+ .name = "AMD AM29LV160DB",
+ .size = 0x00200000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 },
+ { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 31 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_TOSHIBA,
+ .dev_id = TC58FVT160,
+ .name = "Toshiba TC58FVT160",
+ .size = 0x00200000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 31 },
+ { .offset = 0x1F0000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x1F8000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x1FC000, .erasesize = 0x04000, .numblocks = 1 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_FUJITSU,
+ .dev_id = MBM29LV160TE,
+ .name = "Fujitsu MBM29LV160TE",
+ .size = 0x00200000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 31 },
+ { .offset = 0x1F0000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x1F8000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x1FC000, .erasesize = 0x04000, .numblocks = 1 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_TOSHIBA,
+ .dev_id = TC58FVB160,
+ .name = "Toshiba TC58FVB160",
+ .size = 0x00200000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 },
+ { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 31 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_FUJITSU,
+ .dev_id = MBM29LV160BE,
+ .name = "Fujitsu MBM29LV160BE",
+ .size = 0x00200000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 },
+ { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 31 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29LV800BB,
+ .name = "AMD AM29LV800BB",
+ .size = 0x00100000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 },
+ { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 15 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29F800BB,
+ .name = "AMD AM29F800BB",
+ .size = 0x00100000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 },
+ { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 15 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29LV800BT,
+ .name = "AMD AM29LV800BT",
+ .size = 0x00100000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 15 },
+ { .offset = 0x0F0000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x0F8000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x0FC000, .erasesize = 0x04000, .numblocks = 1 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29F800BT,
+ .name = "AMD AM29F800BT",
+ .size = 0x00100000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 15 },
+ { .offset = 0x0F0000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x0F8000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x0FC000, .erasesize = 0x04000, .numblocks = 1 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29LV800BB,
+ .name = "AMD AM29LV800BB",
+ .size = 0x00100000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 15 },
+ { .offset = 0x0F0000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x0F8000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x0FC000, .erasesize = 0x04000, .numblocks = 1 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_FUJITSU,
+ .dev_id = MBM29LV800BB,
+ .name = "Fujitsu MBM29LV800BB",
+ .size = 0x00100000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 },
+ { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 15 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ST,
+ .dev_id = M29W800T,
+ .name = "ST M29W800T",
+ .size = 0x00100000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 15 },
+ { .offset = 0x0F0000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x0F8000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x0FC000, .erasesize = 0x04000, .numblocks = 1 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ST,
+ .dev_id = M29W160DT,
+ .name = "ST M29W160DT",
+ .size = 0x00200000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 31 },
+ { .offset = 0x1F0000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x1F8000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x1FC000, .erasesize = 0x04000, .numblocks = 1 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ST,
+ .dev_id = M29W160DB,
+ .name = "ST M29W160DB",
+ .size = 0x00200000,
+ .numeraseregions = 4,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x04000, .numblocks = 1 },
+ { .offset = 0x004000, .erasesize = 0x02000, .numblocks = 2 },
+ { .offset = 0x008000, .erasesize = 0x08000, .numblocks = 1 },
+ { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 31 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29BDS323D,
+ .name = "AMD AM29BDS323D",
+ .size = 0x00400000,
+ .numeraseregions = 3,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 48 },
+ { .offset = 0x300000, .erasesize = 0x10000, .numblocks = 15 },
+ { .offset = 0x3f0000, .erasesize = 0x02000, .numblocks = 8 },
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29BDS643D,
+ .name = "AMD AM29BDS643D",
+ .size = 0x00800000,
+ .numeraseregions = 3,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 96 },
+ { .offset = 0x600000, .erasesize = 0x10000, .numblocks = 31 },
+ { .offset = 0x7f0000, .erasesize = 0x02000, .numblocks = 8 },
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ATMEL,
+ .dev_id = AT49xV16x,
+ .name = "Atmel AT49xV16x",
+ .size = 0x00200000,
+ .numeraseregions = 2,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x02000, .numblocks = 8 },
+ { .offset = 0x010000, .erasesize = 0x10000, .numblocks = 31 }
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ATMEL,
+ .dev_id = AT49xV16xT,
+ .name = "Atmel AT49xV16xT",
+ .size = 0x00200000,
+ .numeraseregions = 2,
+ .regions = {
+ { .offset = 0x000000, .erasesize = 0x10000, .numblocks = 31 },
+ { .offset = 0x1F0000, .erasesize = 0x02000, .numblocks = 8 }
+ }
+ }
+ };
+
+ struct mtd_info *mtd;
+ struct flchip chips[MAX_AMD_CHIPS];
+ int table_pos[MAX_AMD_CHIPS];
+ struct amd_flash_private temp;
+ struct amd_flash_private *private;
+ u_long size;
+ unsigned long base;
+ int i;
+ int reg_idx;
+ int offset;
+
+ mtd = (struct mtd_info*)kmalloc(sizeof(*mtd), GFP_KERNEL);
+ if (!mtd) {
+ printk(KERN_WARNING
+ "%s: kmalloc failed for info structure\n", map->name);
+ return NULL;
+ }
+ memset(mtd, 0, sizeof(*mtd));
+ mtd->priv = map;
+
+ memset(&temp, 0, sizeof(temp));
+
+ printk("%s: Probing for AMD compatible flash...\n", map->name);
+
+ if ((table_pos[0] = probe_new_chip(mtd, 0, NULL, &temp, table,
+ sizeof(table)/sizeof(table[0])))
+ == -1) {
+ printk(KERN_WARNING
+ "%s: Found no AMD compatible device at location zero\n",
+ map->name);
+ kfree(mtd);
+
+ return NULL;
+ }
+
+ chips[0].start = 0;
+ chips[0].state = FL_READY;
+ chips[0].mutex = &chips[0]._spinlock;
+ temp.numchips = 1;
+ for (size = mtd->size; size > 1; size >>= 1) {
+ temp.chipshift++;
+ }
+ switch (temp.interleave) {
+ case 2:
+ temp.chipshift += 1;
+ break;
+ case 4:
+ temp.chipshift += 2;
+ break;
+ }
+
+ /* Find out if there are any more chips in the map. */
+ for (base = (1 << temp.chipshift);
+ base < map->size;
+ base += (1 << temp.chipshift)) {
+ int numchips = temp.numchips;
+ table_pos[numchips] = probe_new_chip(mtd, base, chips,
+ &temp, table, sizeof(table)/sizeof(table[0]));
+ }
+
+ mtd->eraseregions = kmalloc(sizeof(struct mtd_erase_region_info) *
+ mtd->numeraseregions, GFP_KERNEL);
+ if (!mtd->eraseregions) {
+ printk(KERN_WARNING "%s: Failed to allocate "
+ "memory for MTD erase region info\n", map->name);
+ kfree(mtd);
+ map->fldrv_priv = NULL;
+ return NULL;
+ }
+
+ reg_idx = 0;
+ offset = 0;
+ for (i = 0; i < temp.numchips; i++) {
+ int dev_size;
+ int j;
+
+ dev_size = 0;
+ for (j = 0; j < table[table_pos[i]].numeraseregions; j++) {
+ mtd->eraseregions[reg_idx].offset = offset +
+ (table[table_pos[i]].regions[j].offset *
+ temp.interleave);
+ mtd->eraseregions[reg_idx].erasesize =
+ table[table_pos[i]].regions[j].erasesize *
+ temp.interleave;
+ mtd->eraseregions[reg_idx].numblocks =
+ table[table_pos[i]].regions[j].numblocks;
+ if (mtd->erasesize <
+ mtd->eraseregions[reg_idx].erasesize) {
+ mtd->erasesize =
+ mtd->eraseregions[reg_idx].erasesize;
+ }
+ dev_size += mtd->eraseregions[reg_idx].erasesize *
+ mtd->eraseregions[reg_idx].numblocks;
+ reg_idx++;
+ }
+ offset += dev_size;
+ }
+ mtd->type = MTD_NORFLASH;
+ mtd->flags = MTD_CAP_NORFLASH;
+ mtd->name = map->name;
+ mtd->erase = amd_flash_erase;
+ mtd->read = amd_flash_read;
+ mtd->write = amd_flash_write;
+ mtd->sync = amd_flash_sync;
+ mtd->suspend = amd_flash_suspend;
+ mtd->resume = amd_flash_resume;
+ mtd->lock = amd_flash_lock;
+ mtd->unlock = amd_flash_unlock;
+
+ private = kmalloc(sizeof(*private) + (sizeof(struct flchip) *
+ temp.numchips), GFP_KERNEL);
+ if (!private) {
+ printk(KERN_WARNING
+ "%s: kmalloc failed for private structure\n", map->name);
+ kfree(mtd);
+ map->fldrv_priv = NULL;
+ return NULL;
+ }
+ memcpy(private, &temp, sizeof(temp));
+ memcpy(private->chips, chips,
+ sizeof(struct flchip) * private->numchips);
+ for (i = 0; i < private->numchips; i++) {
+ init_waitqueue_head(&private->chips[i].wq);
+ spin_lock_init(&private->chips[i]._spinlock);
+ }
+
+ map->fldrv_priv = private;
+
+ map->fldrv = &amd_flash_chipdrv;
+
+ __module_get(THIS_MODULE);
+ return mtd;
+}
+
+
+
+static inline int read_one_chip(struct map_info *map, struct flchip *chip,
+ loff_t adr, size_t len, u_char *buf)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long timeo = jiffies + HZ;
+
+retry:
+ spin_lock_bh(chip->mutex);
+
+ if (chip->state != FL_READY){
+ printk(KERN_INFO "%s: waiting for chip to read, state = %d\n",
+ map->name, chip->state);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+
+ spin_unlock_bh(chip->mutex);
+
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+
+ if(signal_pending(current)) {
+ return -EINTR;
+ }
+
+ timeo = jiffies + HZ;
+
+ goto retry;
+ }
+
+ adr += chip->start;
+
+ chip->state = FL_READY;
+
+ map_copy_from(map, buf, adr, len);
+
+ wake_up(&chip->wq);
+ spin_unlock_bh(chip->mutex);
+
+ return 0;
+}
+
+
+
+static int amd_flash_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct amd_flash_private *private = map->fldrv_priv;
+ unsigned long ofs;
+ int chipnum;
+ int ret = 0;
+
+ if ((from + len) > mtd->size) {
+ printk(KERN_WARNING "%s: read request past end of device "
+ "(0x%lx)\n", map->name, (unsigned long)from + len);
+
+ return -EINVAL;
+ }
+
+ /* Offset within the first chip that the first read should start. */
+ chipnum = (from >> private->chipshift);
+ ofs = from - (chipnum << private->chipshift);
+
+ *retlen = 0;
+
+ while (len) {
+ unsigned long this_len;
+
+ if (chipnum >= private->numchips) {
+ break;
+ }
+
+ if ((len + ofs - 1) >> private->chipshift) {
+ this_len = (1 << private->chipshift) - ofs;
+ } else {
+ this_len = len;
+ }
+
+ ret = read_one_chip(map, &private->chips[chipnum], ofs,
+ this_len, buf);
+ if (ret) {
+ break;
+ }
+
+ *retlen += this_len;
+ len -= this_len;
+ buf += this_len;
+
+ ofs = 0;
+ chipnum++;
+ }
+
+ return ret;
+}
+
+
+
+static int write_one_word(struct map_info *map, struct flchip *chip,
+ unsigned long adr, __u32 datum)
+{
+ unsigned long timeo = jiffies + HZ;
+ struct amd_flash_private *private = map->fldrv_priv;
+ DECLARE_WAITQUEUE(wait, current);
+ int ret = 0;
+ int times_left;
+
+retry:
+ spin_lock_bh(chip->mutex);
+
+ if (chip->state != FL_READY){
+ printk("%s: waiting for chip to write, state = %d\n",
+ map->name, chip->state);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+
+ spin_unlock_bh(chip->mutex);
+
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ printk(KERN_INFO "%s: woke up to write\n", map->name);
+ if(signal_pending(current))
+ return -EINTR;
+
+ timeo = jiffies + HZ;
+
+ goto retry;
+ }
+
+ chip->state = FL_WRITING;
+
+ adr += chip->start;
+ ENABLE_VPP(map);
+ send_cmd(map, chip->start, CMD_PROGRAM_UNLOCK_DATA);
+ wide_write(map, datum, adr);
+
+ times_left = 500000;
+ while (times_left-- && flash_is_busy(map, adr, private->interleave)) {
+ if (need_resched()) {
+ spin_unlock_bh(chip->mutex);
+ schedule();
+ spin_lock_bh(chip->mutex);
+ }
+ }
+
+ if (!times_left) {
+ printk(KERN_WARNING "%s: write to 0x%lx timed out!\n",
+ map->name, adr);
+ ret = -EIO;
+ } else {
+ __u32 verify;
+ if ((verify = wide_read(map, adr)) != datum) {
+ printk(KERN_WARNING "%s: write to 0x%lx failed. "
+ "datum = %x, verify = %x\n",
+ map->name, adr, datum, verify);
+ ret = -EIO;
+ }
+ }
+
+ DISABLE_VPP(map);
+ chip->state = FL_READY;
+ wake_up(&chip->wq);
+ spin_unlock_bh(chip->mutex);
+
+ return ret;
+}
+
+
+
+static int amd_flash_write(struct mtd_info *mtd, loff_t to , size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct amd_flash_private *private = map->fldrv_priv;
+ int ret = 0;
+ int chipnum;
+ unsigned long ofs;
+ unsigned long chipstart;
+
+ *retlen = 0;
+ if (!len) {
+ return 0;
+ }
+
+ chipnum = to >> private->chipshift;
+ ofs = to - (chipnum << private->chipshift);
+ chipstart = private->chips[chipnum].start;
+
+ /* If it's not bus-aligned, do the first byte write. */
+ if (ofs & (map->buswidth - 1)) {
+ unsigned long bus_ofs = ofs & ~(map->buswidth - 1);
+ int i = ofs - bus_ofs;
+ int n = 0;
+ u_char tmp_buf[4];
+ __u32 datum;
+
+ map_copy_from(map, tmp_buf,
+ bus_ofs + private->chips[chipnum].start,
+ map->buswidth);
+ while (len && i < map->buswidth)
+ tmp_buf[i++] = buf[n++], len--;
+
+ if (map->buswidth == 2) {
+ datum = *(__u16*)tmp_buf;
+ } else if (map->buswidth == 4) {
+ datum = *(__u32*)tmp_buf;
+ } else {
+ return -EINVAL; /* should never happen, but be safe */
+ }
+
+ ret = write_one_word(map, &private->chips[chipnum], bus_ofs,
+ datum);
+ if (ret) {
+ return ret;
+ }
+
+ ofs += n;
+ buf += n;
+ (*retlen) += n;
+
+ if (ofs >> private->chipshift) {
+ chipnum++;
+ ofs = 0;
+ if (chipnum == private->numchips) {
+ return 0;
+ }
+ }
+ }
+
+ /* We are now aligned, write as much as possible. */
+ while(len >= map->buswidth) {
+ __u32 datum;
+
+ if (map->buswidth == 1) {
+ datum = *(__u8*)buf;
+ } else if (map->buswidth == 2) {
+ datum = *(__u16*)buf;
+ } else if (map->buswidth == 4) {
+ datum = *(__u32*)buf;
+ } else {
+ return -EINVAL;
+ }
+
+ ret = write_one_word(map, &private->chips[chipnum], ofs, datum);
+
+ if (ret) {
+ return ret;
+ }
+
+ ofs += map->buswidth;
+ buf += map->buswidth;
+ (*retlen) += map->buswidth;
+ len -= map->buswidth;
+
+ if (ofs >> private->chipshift) {
+ chipnum++;
+ ofs = 0;
+ if (chipnum == private->numchips) {
+ return 0;
+ }
+ chipstart = private->chips[chipnum].start;
+ }
+ }
+
+ if (len & (map->buswidth - 1)) {
+ int i = 0, n = 0;
+ u_char tmp_buf[2];
+ __u32 datum;
+
+ map_copy_from(map, tmp_buf,
+ ofs + private->chips[chipnum].start,
+ map->buswidth);
+ while (len--) {
+ tmp_buf[i++] = buf[n++];
+ }
+
+ if (map->buswidth == 2) {
+ datum = *(__u16*)tmp_buf;
+ } else if (map->buswidth == 4) {
+ datum = *(__u32*)tmp_buf;
+ } else {
+ return -EINVAL; /* should never happen, but be safe */
+ }
+
+ ret = write_one_word(map, &private->chips[chipnum], ofs, datum);
+
+ if (ret) {
+ return ret;
+ }
+
+ (*retlen) += n;
+ }
+
+ return 0;
+}
+
+
+
+static inline int erase_one_block(struct map_info *map, struct flchip *chip,
+ unsigned long adr, u_long size)
+{
+ unsigned long timeo = jiffies + HZ;
+ struct amd_flash_private *private = map->fldrv_priv;
+ DECLARE_WAITQUEUE(wait, current);
+
+retry:
+ spin_lock_bh(chip->mutex);
+
+ if (chip->state != FL_READY){
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+
+ spin_unlock_bh(chip->mutex);
+
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+
+ if (signal_pending(current)) {
+ return -EINTR;
+ }
+
+ timeo = jiffies + HZ;
+
+ goto retry;
+ }
+
+ chip->state = FL_ERASING;
+
+ adr += chip->start;
+ ENABLE_VPP(map);
+ send_cmd(map, chip->start, CMD_SECTOR_ERASE_UNLOCK_DATA);
+ send_cmd_to_addr(map, chip->start, CMD_SECTOR_ERASE_UNLOCK_DATA_2, adr);
+
+ timeo = jiffies + (HZ * 20);
+
+ spin_unlock_bh(chip->mutex);
+ msleep(1000);
+ spin_lock_bh(chip->mutex);
+
+ while (flash_is_busy(map, adr, private->interleave)) {
+
+ if (chip->state != FL_ERASING) {
+ /* Someone's suspended the erase. Sleep */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+
+ spin_unlock_bh(chip->mutex);
+ printk(KERN_INFO "%s: erase suspended. Sleeping\n",
+ map->name);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+
+ if (signal_pending(current)) {
+ return -EINTR;
+ }
+
+ timeo = jiffies + (HZ*2); /* FIXME */
+ spin_lock_bh(chip->mutex);
+ continue;
+ }
+
+ /* OK Still waiting */
+ if (time_after(jiffies, timeo)) {
+ chip->state = FL_READY;
+ spin_unlock_bh(chip->mutex);
+ printk(KERN_WARNING "%s: waiting for erase to complete "
+ "timed out.\n", map->name);
+ DISABLE_VPP(map);
+
+ return -EIO;
+ }
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ spin_unlock_bh(chip->mutex);
+
+ if (need_resched())
+ schedule();
+ else
+ udelay(1);
+
+ spin_lock_bh(chip->mutex);
+ }
+
+ /* Verify every single word */
+ {
+ int address;
+ int error = 0;
+ __u8 verify;
+
+ for (address = adr; address < (adr + size); address++) {
+ if ((verify = map_read8(map, address)) != 0xFF) {
+ error = 1;
+ break;
+ }
+ }
+ if (error) {
+ chip->state = FL_READY;
+ spin_unlock_bh(chip->mutex);
+ printk(KERN_WARNING
+ "%s: verify error at 0x%x, size %ld.\n",
+ map->name, address, size);
+ DISABLE_VPP(map);
+
+ return -EIO;
+ }
+ }
+
+ DISABLE_VPP(map);
+ chip->state = FL_READY;
+ wake_up(&chip->wq);
+ spin_unlock_bh(chip->mutex);
+
+ return 0;
+}
+
+
+
+static int amd_flash_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct map_info *map = mtd->priv;
+ struct amd_flash_private *private = map->fldrv_priv;
+ unsigned long adr, len;
+ int chipnum;
+ int ret = 0;
+ int i;
+ int first;
+ struct mtd_erase_region_info *regions = mtd->eraseregions;
+
+ if (instr->addr > mtd->size) {
+ return -EINVAL;
+ }
+
+ if ((instr->len + instr->addr) > mtd->size) {
+ return -EINVAL;
+ }
+
+ /* Check that both start and end of the requested erase are
+ * aligned with the erasesize at the appropriate addresses.
+ */
+
+ i = 0;
+
+ /* Skip all erase regions which are ended before the start of
+ the requested erase. Actually, to save on the calculations,
+ we skip to the first erase region which starts after the
+ start of the requested erase, and then go back one.
+ */
+
+ while ((i < mtd->numeraseregions) &&
+ (instr->addr >= regions[i].offset)) {
+ i++;
+ }
+ i--;
+
+ /* OK, now i is pointing at the erase region in which this
+ * erase request starts. Check the start of the requested
+ * erase range is aligned with the erase size which is in
+ * effect here.
+ */
+
+ if (instr->addr & (regions[i].erasesize-1)) {
+ return -EINVAL;
+ }
+
+ /* Remember the erase region we start on. */
+
+ first = i;
+
+ /* Next, check that the end of the requested erase is aligned
+ * with the erase region at that address.
+ */
+
+ while ((i < mtd->numeraseregions) &&
+ ((instr->addr + instr->len) >= regions[i].offset)) {
+ i++;
+ }
+
+ /* As before, drop back one to point at the region in which
+ * the address actually falls.
+ */
+
+ i--;
+
+ if ((instr->addr + instr->len) & (regions[i].erasesize-1)) {
+ return -EINVAL;
+ }
+
+ chipnum = instr->addr >> private->chipshift;
+ adr = instr->addr - (chipnum << private->chipshift);
+ len = instr->len;
+
+ i = first;
+
+ while (len) {
+ ret = erase_one_block(map, &private->chips[chipnum], adr,
+ regions[i].erasesize);
+
+ if (ret) {
+ return ret;
+ }
+
+ adr += regions[i].erasesize;
+ len -= regions[i].erasesize;
+
+ if ((adr % (1 << private->chipshift)) ==
+ ((regions[i].offset + (regions[i].erasesize *
+ regions[i].numblocks))
+ % (1 << private->chipshift))) {
+ i++;
+ }
+
+ if (adr >> private->chipshift) {
+ adr = 0;
+ chipnum++;
+ if (chipnum >= private->numchips) {
+ break;
+ }
+ }
+ }
+
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+
+ return 0;
+}
+
+
+
+static void amd_flash_sync(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct amd_flash_private *private = map->fldrv_priv;
+ int i;
+ struct flchip *chip;
+ int ret = 0;
+ DECLARE_WAITQUEUE(wait, current);
+
+ for (i = 0; !ret && (i < private->numchips); i++) {
+ chip = &private->chips[i];
+
+ retry:
+ spin_lock_bh(chip->mutex);
+
+ switch(chip->state) {
+ case FL_READY:
+ case FL_STATUS:
+ case FL_CFI_QUERY:
+ case FL_JEDEC_QUERY:
+ chip->oldstate = chip->state;
+ chip->state = FL_SYNCING;
+ /* No need to wake_up() on this state change -
+ * as the whole point is that nobody can do anything
+ * with the chip now anyway.
+ */
+ case FL_SYNCING:
+ spin_unlock_bh(chip->mutex);
+ break;
+
+ default:
+ /* Not an idle state */
+ add_wait_queue(&chip->wq, &wait);
+
+ spin_unlock_bh(chip->mutex);
+
+ schedule();
+
+ remove_wait_queue(&chip->wq, &wait);
+
+ goto retry;
+ }
+ }
+
+ /* Unlock the chips again */
+ for (i--; i >= 0; i--) {
+ chip = &private->chips[i];
+
+ spin_lock_bh(chip->mutex);
+
+ if (chip->state == FL_SYNCING) {
+ chip->state = chip->oldstate;
+ wake_up(&chip->wq);
+ }
+ spin_unlock_bh(chip->mutex);
+ }
+}
+
+
+
+static int amd_flash_suspend(struct mtd_info *mtd)
+{
+printk("amd_flash_suspend(): not implemented!\n");
+ return -EINVAL;
+}
+
+
+
+static void amd_flash_resume(struct mtd_info *mtd)
+{
+printk("amd_flash_resume(): not implemented!\n");
+}
+
+
+
+static void amd_flash_destroy(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct amd_flash_private *private = map->fldrv_priv;
+ kfree(private);
+}
+
+int __init amd_flash_init(void)
+{
+ register_mtd_chip_driver(&amd_flash_chipdrv);
+ return 0;
+}
+
+void __exit amd_flash_exit(void)
+{
+ unregister_mtd_chip_driver(&amd_flash_chipdrv);
+}
+
+module_init(amd_flash_init);
+module_exit(amd_flash_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jonas Holmberg <jonas.holmberg@axis.com>");
+MODULE_DESCRIPTION("Old MTD chip driver for AMD flash chips");
diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c
new file mode 100644
index 00000000000..c268bcd7172
--- /dev/null
+++ b/drivers/mtd/chips/cfi_cmdset_0001.c
@@ -0,0 +1,2160 @@
+/*
+ * Common Flash Interface support:
+ * Intel Extended Vendor Command Set (ID 0x0001)
+ *
+ * (C) 2000 Red Hat. GPL'd
+ *
+ * $Id: cfi_cmdset_0001.c,v 1.164 2004/11/16 18:29:00 dwmw2 Exp $
+ *
+ *
+ * 10/10/2000 Nicolas Pitre <nico@cam.org>
+ * - completely revamped method functions so they are aware and
+ * independent of the flash geometry (buswidth, interleave, etc.)
+ * - scalability vs code size is completely set at compile-time
+ * (see include/linux/mtd/cfi.h for selection)
+ * - optimized write buffer method
+ * 02/05/2002 Christopher Hoover <ch@hpl.hp.com>/<ch@murgatroid.com>
+ * - reworked lock/unlock/erase support for var size flash
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <asm/byteorder.h>
+
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/mtd/xip.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/compatmac.h>
+#include <linux/mtd/cfi.h>
+
+/* #define CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE */
+/* #define CMDSET0001_DISABLE_WRITE_SUSPEND */
+
+// debugging, turns off buffer write mode if set to 1
+#define FORCE_WORD_WRITE 0
+
+#define MANUFACTURER_INTEL 0x0089
+#define I82802AB 0x00ad
+#define I82802AC 0x00ac
+#define MANUFACTURER_ST 0x0020
+#define M50LPW080 0x002F
+
+static int cfi_intelext_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
+//static int cfi_intelext_read_user_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
+//static int cfi_intelext_read_fact_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
+static int cfi_intelext_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
+static int cfi_intelext_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
+static int cfi_intelext_erase_varsize(struct mtd_info *, struct erase_info *);
+static void cfi_intelext_sync (struct mtd_info *);
+static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, size_t len);
+static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, size_t len);
+static int cfi_intelext_suspend (struct mtd_info *);
+static void cfi_intelext_resume (struct mtd_info *);
+
+static void cfi_intelext_destroy(struct mtd_info *);
+
+struct mtd_info *cfi_cmdset_0001(struct map_info *, int);
+
+static struct mtd_info *cfi_intelext_setup (struct mtd_info *);
+static int cfi_intelext_partition_fixup(struct mtd_info *, struct cfi_private **);
+
+static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char **mtdbuf);
+static void cfi_intelext_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from,
+ size_t len);
+
+static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode);
+static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr);
+#include "fwh_lock.h"
+
+
+
+/*
+ * *********** SETUP AND PROBE BITS ***********
+ */
+
+static struct mtd_chip_driver cfi_intelext_chipdrv = {
+ .probe = NULL, /* Not usable directly */
+ .destroy = cfi_intelext_destroy,
+ .name = "cfi_cmdset_0001",
+ .module = THIS_MODULE
+};
+
+/* #define DEBUG_LOCK_BITS */
+/* #define DEBUG_CFI_FEATURES */
+
+#ifdef DEBUG_CFI_FEATURES
+static void cfi_tell_features(struct cfi_pri_intelext *extp)
+{
+ int i;
+ printk(" Feature/Command Support: %4.4X\n", extp->FeatureSupport);
+ printk(" - Chip Erase: %s\n", extp->FeatureSupport&1?"supported":"unsupported");
+ printk(" - Suspend Erase: %s\n", extp->FeatureSupport&2?"supported":"unsupported");
+ printk(" - Suspend Program: %s\n", extp->FeatureSupport&4?"supported":"unsupported");
+ printk(" - Legacy Lock/Unlock: %s\n", extp->FeatureSupport&8?"supported":"unsupported");
+ printk(" - Queued Erase: %s\n", extp->FeatureSupport&16?"supported":"unsupported");
+ printk(" - Instant block lock: %s\n", extp->FeatureSupport&32?"supported":"unsupported");
+ printk(" - Protection Bits: %s\n", extp->FeatureSupport&64?"supported":"unsupported");
+ printk(" - Page-mode read: %s\n", extp->FeatureSupport&128?"supported":"unsupported");
+ printk(" - Synchronous read: %s\n", extp->FeatureSupport&256?"supported":"unsupported");
+ printk(" - Simultaneous operations: %s\n", extp->FeatureSupport&512?"supported":"unsupported");
+ for (i=10; i<32; i++) {
+ if (extp->FeatureSupport & (1<<i))
+ printk(" - Unknown Bit %X: supported\n", i);
+ }
+
+ printk(" Supported functions after Suspend: %2.2X\n", extp->SuspendCmdSupport);
+ printk(" - Program after Erase Suspend: %s\n", extp->SuspendCmdSupport&1?"supported":"unsupported");
+ for (i=1; i<8; i++) {
+ if (extp->SuspendCmdSupport & (1<<i))
+ printk(" - Unknown Bit %X: supported\n", i);
+ }
+
+ printk(" Block Status Register Mask: %4.4X\n", extp->BlkStatusRegMask);
+ printk(" - Lock Bit Active: %s\n", extp->BlkStatusRegMask&1?"yes":"no");
+ printk(" - Valid Bit Active: %s\n", extp->BlkStatusRegMask&2?"yes":"no");
+ for (i=2; i<16; i++) {
+ if (extp->BlkStatusRegMask & (1<<i))
+ printk(" - Unknown Bit %X Active: yes\n",i);
+ }
+
+ printk(" Vcc Logic Supply Optimum Program/Erase Voltage: %d.%d V\n",
+ extp->VccOptimal >> 4, extp->VccOptimal & 0xf);
+ if (extp->VppOptimal)
+ printk(" Vpp Programming Supply Optimum Program/Erase Voltage: %d.%d V\n",
+ extp->VppOptimal >> 4, extp->VppOptimal & 0xf);
+}
+#endif
+
+#ifdef CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE
+/* Some Intel Strata Flash prior to FPO revision C has bugs in this area */
+static void fixup_intel_strataflash(struct mtd_info *mtd, void* param)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ struct cfi_pri_amdstd *extp = cfi->cmdset_priv;
+
+ printk(KERN_WARNING "cfi_cmdset_0001: Suspend "
+ "erase on write disabled.\n");
+ extp->SuspendCmdSupport &= ~1;
+}
+#endif
+
+#ifdef CMDSET0001_DISABLE_WRITE_SUSPEND
+static void fixup_no_write_suspend(struct mtd_info *mtd, void* param)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ struct cfi_pri_intelext *cfip = cfi->cmdset_priv;
+
+ if (cfip && (cfip->FeatureSupport&4)) {
+ cfip->FeatureSupport &= ~4;
+ printk(KERN_WARNING "cfi_cmdset_0001: write suspend disabled\n");
+ }
+}
+#endif
+
+static void fixup_st_m28w320ct(struct mtd_info *mtd, void* param)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+
+ cfi->cfiq->BufWriteTimeoutTyp = 0; /* Not supported */
+ cfi->cfiq->BufWriteTimeoutMax = 0; /* Not supported */
+}
+
+static void fixup_st_m28w320cb(struct mtd_info *mtd, void* param)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+
+ /* Note this is done after the region info is endian swapped */
+ cfi->cfiq->EraseRegionInfo[1] =
+ (cfi->cfiq->EraseRegionInfo[1] & 0xffff0000) | 0x3e;
+};
+
+static void fixup_use_point(struct mtd_info *mtd, void *param)
+{
+ struct map_info *map = mtd->priv;
+ if (!mtd->point && map_is_linear(map)) {
+ mtd->point = cfi_intelext_point;
+ mtd->unpoint = cfi_intelext_unpoint;
+ }
+}
+
+static void fixup_use_write_buffers(struct mtd_info *mtd, void *param)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ if (cfi->cfiq->BufWriteTimeoutTyp) {
+ printk(KERN_INFO "Using buffer write method\n" );
+ mtd->write = cfi_intelext_write_buffers;
+ }
+}
+
+static struct cfi_fixup cfi_fixup_table[] = {
+#ifdef CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE
+ { CFI_MFR_ANY, CFI_ID_ANY, fixup_intel_strataflash, NULL },
+#endif
+#ifdef CMDSET0001_DISABLE_WRITE_SUSPEND
+ { CFI_MFR_ANY, CFI_ID_ANY, fixup_no_write_suspend, NULL },
+#endif
+#if !FORCE_WORD_WRITE
+ { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers, NULL },
+#endif
+ { CFI_MFR_ST, 0x00ba, /* M28W320CT */ fixup_st_m28w320ct, NULL },
+ { CFI_MFR_ST, 0x00bb, /* M28W320CB */ fixup_st_m28w320cb, NULL },
+ { 0, 0, NULL, NULL }
+};
+
+static struct cfi_fixup jedec_fixup_table[] = {
+ { MANUFACTURER_INTEL, I82802AB, fixup_use_fwh_lock, NULL, },
+ { MANUFACTURER_INTEL, I82802AC, fixup_use_fwh_lock, NULL, },
+ { MANUFACTURER_ST, M50LPW080, fixup_use_fwh_lock, NULL, },
+ { 0, 0, NULL, NULL }
+};
+static struct cfi_fixup fixup_table[] = {
+ /* The CFI vendor ids and the JEDEC vendor IDs appear
+ * to be common. It is like the devices id's are as
+ * well. This table is to pick all cases where
+ * we know that is the case.
+ */
+ { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_point, NULL },
+ { 0, 0, NULL, NULL }
+};
+
+static inline struct cfi_pri_intelext *
+read_pri_intelext(struct map_info *map, __u16 adr)
+{
+ struct cfi_pri_intelext *extp;
+ unsigned int extp_size = sizeof(*extp);
+
+ again:
+ extp = (struct cfi_pri_intelext *)cfi_read_pri(map, adr, extp_size, "Intel/Sharp");
+ if (!extp)
+ return NULL;
+
+ /* Do some byteswapping if necessary */
+ extp->FeatureSupport = le32_to_cpu(extp->FeatureSupport);
+ extp->BlkStatusRegMask = le16_to_cpu(extp->BlkStatusRegMask);
+ extp->ProtRegAddr = le16_to_cpu(extp->ProtRegAddr);
+
+ if (extp->MajorVersion == '1' && extp->MinorVersion == '3') {
+ unsigned int extra_size = 0;
+ int nb_parts, i;
+
+ /* Protection Register info */
+ extra_size += (extp->NumProtectionFields - 1) * (4 + 6);
+
+ /* Burst Read info */
+ extra_size += 6;
+
+ /* Number of hardware-partitions */
+ extra_size += 1;
+ if (extp_size < sizeof(*extp) + extra_size)
+ goto need_more;
+ nb_parts = extp->extra[extra_size - 1];
+
+ for (i = 0; i < nb_parts; i++) {
+ struct cfi_intelext_regioninfo *rinfo;
+ rinfo = (struct cfi_intelext_regioninfo *)&extp->extra[extra_size];
+ extra_size += sizeof(*rinfo);
+ if (extp_size < sizeof(*extp) + extra_size)
+ goto need_more;
+ rinfo->NumIdentPartitions=le16_to_cpu(rinfo->NumIdentPartitions);
+ extra_size += (rinfo->NumBlockTypes - 1)
+ * sizeof(struct cfi_intelext_blockinfo);
+ }
+
+ if (extp_size < sizeof(*extp) + extra_size) {
+ need_more:
+ extp_size = sizeof(*extp) + extra_size;
+ kfree(extp);
+ if (extp_size > 4096) {
+ printk(KERN_ERR
+ "%s: cfi_pri_intelext is too fat\n",
+ __FUNCTION__);
+ return NULL;
+ }
+ goto again;
+ }
+ }
+
+ return extp;
+}
+
+/* This routine is made available to other mtd code via
+ * inter_module_register. It must only be accessed through
+ * inter_module_get which will bump the use count of this module. The
+ * addresses passed back in cfi are valid as long as the use count of
+ * this module is non-zero, i.e. between inter_module_get and
+ * inter_module_put. Keith Owens <kaos@ocs.com.au> 29 Oct 2000.
+ */
+struct mtd_info *cfi_cmdset_0001(struct map_info *map, int primary)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ struct mtd_info *mtd;
+ int i;
+
+ mtd = kmalloc(sizeof(*mtd), GFP_KERNEL);
+ if (!mtd) {
+ printk(KERN_ERR "Failed to allocate memory for MTD device\n");
+ return NULL;
+ }
+ memset(mtd, 0, sizeof(*mtd));
+ mtd->priv = map;
+ mtd->type = MTD_NORFLASH;
+
+ /* Fill in the default mtd operations */
+ mtd->erase = cfi_intelext_erase_varsize;
+ mtd->read = cfi_intelext_read;
+ mtd->write = cfi_intelext_write_words;
+ mtd->sync = cfi_intelext_sync;
+ mtd->lock = cfi_intelext_lock;
+ mtd->unlock = cfi_intelext_unlock;
+ mtd->suspend = cfi_intelext_suspend;
+ mtd->resume = cfi_intelext_resume;
+ mtd->flags = MTD_CAP_NORFLASH;
+ mtd->name = map->name;
+
+ if (cfi->cfi_mode == CFI_MODE_CFI) {
+ /*
+ * It's a real CFI chip, not one for which the probe
+ * routine faked a CFI structure. So we read the feature
+ * table from it.
+ */
+ __u16 adr = primary?cfi->cfiq->P_ADR:cfi->cfiq->A_ADR;
+ struct cfi_pri_intelext *extp;
+
+ extp = read_pri_intelext(map, adr);
+ if (!extp) {
+ kfree(mtd);
+ return NULL;
+ }
+
+ /* Install our own private info structure */
+ cfi->cmdset_priv = extp;
+
+ cfi_fixup(mtd, cfi_fixup_table);
+
+#ifdef DEBUG_CFI_FEATURES
+ /* Tell the user about it in lots of lovely detail */
+ cfi_tell_features(extp);
+#endif
+
+ if(extp->SuspendCmdSupport & 1) {
+ printk(KERN_NOTICE "cfi_cmdset_0001: Erase suspend on write enabled\n");
+ }
+ }
+ else if (cfi->cfi_mode == CFI_MODE_JEDEC) {
+ /* Apply jedec specific fixups */
+ cfi_fixup(mtd, jedec_fixup_table);
+ }
+ /* Apply generic fixups */
+ cfi_fixup(mtd, fixup_table);
+
+ for (i=0; i< cfi->numchips; i++) {
+ cfi->chips[i].word_write_time = 1<<cfi->cfiq->WordWriteTimeoutTyp;
+ cfi->chips[i].buffer_write_time = 1<<cfi->cfiq->BufWriteTimeoutTyp;
+ cfi->chips[i].erase_time = 1<<cfi->cfiq->BlockEraseTimeoutTyp;
+ cfi->chips[i].ref_point_counter = 0;
+ }
+
+ map->fldrv = &cfi_intelext_chipdrv;
+
+ return cfi_intelext_setup(mtd);
+}
+
+static struct mtd_info *cfi_intelext_setup(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long offset = 0;
+ int i,j;
+ unsigned long devsize = (1<<cfi->cfiq->DevSize) * cfi->interleave;
+
+ //printk(KERN_DEBUG "number of CFI chips: %d\n", cfi->numchips);
+
+ mtd->size = devsize * cfi->numchips;
+
+ mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips;
+ mtd->eraseregions = kmalloc(sizeof(struct mtd_erase_region_info)
+ * mtd->numeraseregions, GFP_KERNEL);
+ if (!mtd->eraseregions) {
+ printk(KERN_ERR "Failed to allocate memory for MTD erase region info\n");
+ goto setup_err;
+ }
+
+ for (i=0; i<cfi->cfiq->NumEraseRegions; i++) {
+ unsigned long ernum, ersize;
+ ersize = ((cfi->cfiq->EraseRegionInfo[i] >> 8) & ~0xff) * cfi->interleave;
+ ernum = (cfi->cfiq->EraseRegionInfo[i] & 0xffff) + 1;
+
+ if (mtd->erasesize < ersize) {
+ mtd->erasesize = ersize;
+ }
+ for (j=0; j<cfi->numchips; j++) {
+ mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].offset = (j*devsize)+offset;
+ mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].erasesize = ersize;
+ mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].numblocks = ernum;
+ }
+ offset += (ersize * ernum);
+ }
+
+ if (offset != devsize) {
+ /* Argh */
+ printk(KERN_WARNING "Sum of regions (%lx) != total size of set of interleaved chips (%lx)\n", offset, devsize);
+ goto setup_err;
+ }
+
+ for (i=0; i<mtd->numeraseregions;i++){
+ printk(KERN_DEBUG "%d: offset=0x%x,size=0x%x,blocks=%d\n",
+ i,mtd->eraseregions[i].offset,
+ mtd->eraseregions[i].erasesize,
+ mtd->eraseregions[i].numblocks);
+ }
+
+#if 0
+ mtd->read_user_prot_reg = cfi_intelext_read_user_prot_reg;
+ mtd->read_fact_prot_reg = cfi_intelext_read_fact_prot_reg;
+#endif
+
+ /* This function has the potential to distort the reality
+ a bit and therefore should be called last. */
+ if (cfi_intelext_partition_fixup(mtd, &cfi) != 0)
+ goto setup_err;
+
+ __module_get(THIS_MODULE);
+ return mtd;
+
+ setup_err:
+ if(mtd) {
+ if(mtd->eraseregions)
+ kfree(mtd->eraseregions);
+ kfree(mtd);
+ }
+ kfree(cfi->cmdset_priv);
+ return NULL;
+}
+
+static int cfi_intelext_partition_fixup(struct mtd_info *mtd,
+ struct cfi_private **pcfi)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = *pcfi;
+ struct cfi_pri_intelext *extp = cfi->cmdset_priv;
+
+ /*
+ * Probing of multi-partition flash ships.
+ *
+ * To support multiple partitions when available, we simply arrange
+ * for each of them to have their own flchip structure even if they
+ * are on the same physical chip. This means completely recreating
+ * a new cfi_private structure right here which is a blatent code
+ * layering violation, but this is still the least intrusive
+ * arrangement at this point. This can be rearranged in the future
+ * if someone feels motivated enough. --nico
+ */
+ if (extp && extp->MajorVersion == '1' && extp->MinorVersion == '3'
+ && extp->FeatureSupport & (1 << 9)) {
+ struct cfi_private *newcfi;
+ struct flchip *chip;
+ struct flchip_shared *shared;
+ int offs, numregions, numparts, partshift, numvirtchips, i, j;
+
+ /* Protection Register info */
+ offs = (extp->NumProtectionFields - 1) * (4 + 6);
+
+ /* Burst Read info */
+ offs += 6;
+
+ /* Number of partition regions */
+ numregions = extp->extra[offs];
+ offs += 1;
+
+ /* Number of hardware partitions */
+ numparts = 0;
+ for (i = 0; i < numregions; i++) {
+ struct cfi_intelext_regioninfo *rinfo;
+ rinfo = (struct cfi_intelext_regioninfo *)&extp->extra[offs];
+ numparts += rinfo->NumIdentPartitions;
+ offs += sizeof(*rinfo)
+ + (rinfo->NumBlockTypes - 1) *
+ sizeof(struct cfi_intelext_blockinfo);
+ }
+
+ /*
+ * All functions below currently rely on all chips having
+ * the same geometry so we'll just assume that all hardware
+ * partitions are of the same size too.
+ */
+ partshift = cfi->chipshift - __ffs(numparts);
+
+ if ((1 << partshift) < mtd->erasesize) {
+ printk( KERN_ERR
+ "%s: bad number of hw partitions (%d)\n",
+ __FUNCTION__, numparts);
+ return -EINVAL;
+ }
+
+ numvirtchips = cfi->numchips * numparts;
+ newcfi = kmalloc(sizeof(struct cfi_private) + numvirtchips * sizeof(struct flchip), GFP_KERNEL);
+ if (!newcfi)
+ return -ENOMEM;
+ shared = kmalloc(sizeof(struct flchip_shared) * cfi->numchips, GFP_KERNEL);
+ if (!shared) {
+ kfree(newcfi);
+ return -ENOMEM;
+ }
+ memcpy(newcfi, cfi, sizeof(struct cfi_private));
+ newcfi->numchips = numvirtchips;
+ newcfi->chipshift = partshift;
+
+ chip = &newcfi->chips[0];
+ for (i = 0; i < cfi->numchips; i++) {
+ shared[i].writing = shared[i].erasing = NULL;
+ spin_lock_init(&shared[i].lock);
+ for (j = 0; j < numparts; j++) {
+ *chip = cfi->chips[i];
+ chip->start += j << partshift;
+ chip->priv = &shared[i];
+ /* those should be reset too since
+ they create memory references. */
+ init_waitqueue_head(&chip->wq);
+ spin_lock_init(&chip->_spinlock);
+ chip->mutex = &chip->_spinlock;
+ chip++;
+ }
+ }
+
+ printk(KERN_DEBUG "%s: %d set(s) of %d interleaved chips "
+ "--> %d partitions of %d KiB\n",
+ map->name, cfi->numchips, cfi->interleave,
+ newcfi->numchips, 1<<(newcfi->chipshift-10));
+
+ map->fldrv_priv = newcfi;
+ *pcfi = newcfi;
+ kfree(cfi);
+ }
+
+ return 0;
+}
+
+/*
+ * *********** CHIP ACCESS FUNCTIONS ***********
+ */
+
+static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct cfi_private *cfi = map->fldrv_priv;
+ map_word status, status_OK = CMD(0x80), status_PWS = CMD(0x01);
+ unsigned long timeo;
+ struct cfi_pri_intelext *cfip = cfi->cmdset_priv;
+
+ resettime:
+ timeo = jiffies + HZ;
+ retry:
+ if (chip->priv && (mode == FL_WRITING || mode == FL_ERASING)) {
+ /*
+ * OK. We have possibility for contension on the write/erase
+ * operations which are global to the real chip and not per
+ * partition. So let's fight it over in the partition which
+ * currently has authority on the operation.
+ *
+ * The rules are as follows:
+ *
+ * - any write operation must own shared->writing.
+ *
+ * - any erase operation must own _both_ shared->writing and
+ * shared->erasing.
+ *
+ * - contension arbitration is handled in the owner's context.
+ *
+ * The 'shared' struct can be read when its lock is taken.
+ * However any writes to it can only be made when the current
+ * owner's lock is also held.
+ */
+ struct flchip_shared *shared = chip->priv;
+ struct flchip *contender;
+ spin_lock(&shared->lock);
+ contender = shared->writing;
+ if (contender && contender != chip) {
+ /*
+ * The engine to perform desired operation on this
+ * partition is already in use by someone else.
+ * Let's fight over it in the context of the chip
+ * currently using it. If it is possible to suspend,
+ * that other partition will do just that, otherwise
+ * it'll happily send us to sleep. In any case, when
+ * get_chip returns success we're clear to go ahead.
+ */
+ int ret = spin_trylock(contender->mutex);
+ spin_unlock(&shared->lock);
+ if (!ret)
+ goto retry;
+ spin_unlock(chip->mutex);
+ ret = get_chip(map, contender, contender->start, mode);
+ spin_lock(chip->mutex);
+ if (ret) {
+ spin_unlock(contender->mutex);
+ return ret;
+ }
+ timeo = jiffies + HZ;
+ spin_lock(&shared->lock);
+ }
+
+ /* We now own it */
+ shared->writing = chip;
+ if (mode == FL_ERASING)
+ shared->erasing = chip;
+ if (contender && contender != chip)
+ spin_unlock(contender->mutex);
+ spin_unlock(&shared->lock);
+ }
+
+ switch (chip->state) {
+
+ case FL_STATUS:
+ for (;;) {
+ status = map_read(map, adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ /* At this point we're fine with write operations
+ in other partitions as they don't conflict. */
+ if (chip->priv && map_word_andequal(map, status, status_PWS, status_PWS))
+ break;
+
+ if (time_after(jiffies, timeo)) {
+ printk(KERN_ERR "Waiting for chip to be ready timed out. Status %lx\n",
+ status.x[0]);
+ return -EIO;
+ }
+ spin_unlock(chip->mutex);
+ cfi_udelay(1);
+ spin_lock(chip->mutex);
+ /* Someone else might have been playing with it. */
+ goto retry;
+ }
+
+ case FL_READY:
+ case FL_CFI_QUERY:
+ case FL_JEDEC_QUERY:
+ return 0;
+
+ case FL_ERASING:
+ if (!cfip ||
+ !(cfip->FeatureSupport & 2) ||
+ !(mode == FL_READY || mode == FL_POINT ||
+ (mode == FL_WRITING && (cfip->SuspendCmdSupport & 1))))
+ goto sleep;
+
+
+ /* Erase suspend */
+ map_write(map, CMD(0xB0), adr);
+
+ /* If the flash has finished erasing, then 'erase suspend'
+ * appears to make some (28F320) flash devices switch to
+ * 'read' mode. Make sure that we switch to 'read status'
+ * mode so we get the right data. --rmk
+ */
+ map_write(map, CMD(0x70), adr);
+ chip->oldstate = FL_ERASING;
+ chip->state = FL_ERASE_SUSPENDING;
+ chip->erase_suspended = 1;
+ for (;;) {
+ status = map_read(map, adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ if (time_after(jiffies, timeo)) {
+ /* Urgh. Resume and pretend we weren't here. */
+ map_write(map, CMD(0xd0), adr);
+ /* Make sure we're in 'read status' mode if it had finished */
+ map_write(map, CMD(0x70), adr);
+ chip->state = FL_ERASING;
+ chip->oldstate = FL_READY;
+ printk(KERN_ERR "Chip not ready after erase "
+ "suspended: status = 0x%lx\n", status.x[0]);
+ return -EIO;
+ }
+
+ spin_unlock(chip->mutex);
+ cfi_udelay(1);
+ spin_lock(chip->mutex);
+ /* Nobody will touch it while it's in state FL_ERASE_SUSPENDING.
+ So we can just loop here. */
+ }
+ chip->state = FL_STATUS;
+ return 0;
+
+ case FL_XIP_WHILE_ERASING:
+ if (mode != FL_READY && mode != FL_POINT &&
+ (mode != FL_WRITING || !cfip || !(cfip->SuspendCmdSupport&1)))
+ goto sleep;
+ chip->oldstate = chip->state;
+ chip->state = FL_READY;
+ return 0;
+
+ case FL_POINT:
+ /* Only if there's no operation suspended... */
+ if (mode == FL_READY && chip->oldstate == FL_READY)
+ return 0;
+
+ default:
+ sleep:
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ spin_unlock(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ spin_lock(chip->mutex);
+ goto resettime;
+ }
+}
+
+static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+
+ if (chip->priv) {
+ struct flchip_shared *shared = chip->priv;
+ spin_lock(&shared->lock);
+ if (shared->writing == chip && chip->oldstate == FL_READY) {
+ /* We own the ability to write, but we're done */
+ shared->writing = shared->erasing;
+ if (shared->writing && shared->writing != chip) {
+ /* give back ownership to who we loaned it from */
+ struct flchip *loaner = shared->writing;
+ spin_lock(loaner->mutex);
+ spin_unlock(&shared->lock);
+ spin_unlock(chip->mutex);
+ put_chip(map, loaner, loaner->start);
+ spin_lock(chip->mutex);
+ spin_unlock(loaner->mutex);
+ wake_up(&chip->wq);
+ return;
+ }
+ shared->erasing = NULL;
+ shared->writing = NULL;
+ } else if (shared->erasing == chip && shared->writing != chip) {
+ /*
+ * We own the ability to erase without the ability
+ * to write, which means the erase was suspended
+ * and some other partition is currently writing.
+ * Don't let the switch below mess things up since
+ * we don't have ownership to resume anything.
+ */
+ spin_unlock(&shared->lock);
+ wake_up(&chip->wq);
+ return;
+ }
+ spin_unlock(&shared->lock);
+ }
+
+ switch(chip->oldstate) {
+ case FL_ERASING:
+ chip->state = chip->oldstate;
+ /* What if one interleaved chip has finished and the
+ other hasn't? The old code would leave the finished
+ one in READY mode. That's bad, and caused -EROFS
+ errors to be returned from do_erase_oneblock because
+ that's the only bit it checked for at the time.
+ As the state machine appears to explicitly allow
+ sending the 0x70 (Read Status) command to an erasing
+ chip and expecting it to be ignored, that's what we
+ do. */
+ map_write(map, CMD(0xd0), adr);
+ map_write(map, CMD(0x70), adr);
+ chip->oldstate = FL_READY;
+ chip->state = FL_ERASING;
+ break;
+
+ case FL_XIP_WHILE_ERASING:
+ chip->state = chip->oldstate;
+ chip->oldstate = FL_READY;
+ break;
+
+ case FL_READY:
+ case FL_STATUS:
+ case FL_JEDEC_QUERY:
+ /* We should really make set_vpp() count, rather than doing this */
+ DISABLE_VPP(map);
+ break;
+ default:
+ printk(KERN_ERR "put_chip() called with oldstate %d!!\n", chip->oldstate);
+ }
+ wake_up(&chip->wq);
+}
+
+#ifdef CONFIG_MTD_XIP
+
+/*
+ * No interrupt what so ever can be serviced while the flash isn't in array
+ * mode. This is ensured by the xip_disable() and xip_enable() functions
+ * enclosing any code path where the flash is known not to be in array mode.
+ * And within a XIP disabled code path, only functions marked with __xipram
+ * may be called and nothing else (it's a good thing to inspect generated
+ * assembly to make sure inline functions were actually inlined and that gcc
+ * didn't emit calls to its own support functions). Also configuring MTD CFI
+ * support to a single buswidth and a single interleave is also recommended.
+ * Note that not only IRQs are disabled but the preemption count is also
+ * increased to prevent other locking primitives (namely spin_unlock) from
+ * decrementing the preempt count to zero and scheduling the CPU away while
+ * not in array mode.
+ */
+
+static void xip_disable(struct map_info *map, struct flchip *chip,
+ unsigned long adr)
+{
+ /* TODO: chips with no XIP use should ignore and return */
+ (void) map_read(map, adr); /* ensure mmu mapping is up to date */
+ preempt_disable();
+ local_irq_disable();
+}
+
+static void __xipram xip_enable(struct map_info *map, struct flchip *chip,
+ unsigned long adr)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ if (chip->state != FL_POINT && chip->state != FL_READY) {
+ map_write(map, CMD(0xff), adr);
+ chip->state = FL_READY;
+ }
+ (void) map_read(map, adr);
+ asm volatile (".rep 8; nop; .endr"); /* fill instruction prefetch */
+ local_irq_enable();
+ preempt_enable();
+}
+
+/*
+ * When a delay is required for the flash operation to complete, the
+ * xip_udelay() function is polling for both the given timeout and pending
+ * (but still masked) hardware interrupts. Whenever there is an interrupt
+ * pending then the flash erase or write operation is suspended, array mode
+ * restored and interrupts unmasked. Task scheduling might also happen at that
+ * point. The CPU eventually returns from the interrupt or the call to
+ * schedule() and the suspended flash operation is resumed for the remaining
+ * of the delay period.
+ *
+ * Warning: this function _will_ fool interrupt latency tracing tools.
+ */
+
+static void __xipram xip_udelay(struct map_info *map, struct flchip *chip,
+ unsigned long adr, int usec)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ struct cfi_pri_intelext *cfip = cfi->cmdset_priv;
+ map_word status, OK = CMD(0x80);
+ unsigned long suspended, start = xip_currtime();
+ flstate_t oldstate, newstate;
+
+ do {
+ cpu_relax();
+ if (xip_irqpending() && cfip &&
+ ((chip->state == FL_ERASING && (cfip->FeatureSupport&2)) ||
+ (chip->state == FL_WRITING && (cfip->FeatureSupport&4))) &&
+ (cfi_interleave_is_1(cfi) || chip->oldstate == FL_READY)) {
+ /*
+ * Let's suspend the erase or write operation when
+ * supported. Note that we currently don't try to
+ * suspend interleaved chips if there is already
+ * another operation suspended (imagine what happens
+ * when one chip was already done with the current
+ * operation while another chip suspended it, then
+ * we resume the whole thing at once). Yes, it
+ * can happen!
+ */
+ map_write(map, CMD(0xb0), adr);
+ map_write(map, CMD(0x70), adr);
+ usec -= xip_elapsed_since(start);
+ suspended = xip_currtime();
+ do {
+ if (xip_elapsed_since(suspended) > 100000) {
+ /*
+ * The chip doesn't want to suspend
+ * after waiting for 100 msecs.
+ * This is a critical error but there
+ * is not much we can do here.
+ */
+ return;
+ }
+ status = map_read(map, adr);
+ } while (!map_word_andequal(map, status, OK, OK));
+
+ /* Suspend succeeded */
+ oldstate = chip->state;
+ if (oldstate == FL_ERASING) {
+ if (!map_word_bitsset(map, status, CMD(0x40)))
+ break;
+ newstate = FL_XIP_WHILE_ERASING;
+ chip->erase_suspended = 1;
+ } else {
+ if (!map_word_bitsset(map, status, CMD(0x04)))
+ break;
+ newstate = FL_XIP_WHILE_WRITING;
+ chip->write_suspended = 1;
+ }
+ chip->state = newstate;
+ map_write(map, CMD(0xff), adr);
+ (void) map_read(map, adr);
+ asm volatile (".rep 8; nop; .endr");
+ local_irq_enable();
+ preempt_enable();
+ asm volatile (".rep 8; nop; .endr");
+ cond_resched();
+
+ /*
+ * We're back. However someone else might have
+ * decided to go write to the chip if we are in
+ * a suspended erase state. If so let's wait
+ * until it's done.
+ */
+ preempt_disable();
+ while (chip->state != newstate) {
+ DECLARE_WAITQUEUE(wait, current);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ preempt_enable();
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ preempt_disable();
+ }
+ /* Disallow XIP again */
+ local_irq_disable();
+
+ /* Resume the write or erase operation */
+ map_write(map, CMD(0xd0), adr);
+ map_write(map, CMD(0x70), adr);
+ chip->state = oldstate;
+ start = xip_currtime();
+ } else if (usec >= 1000000/HZ) {
+ /*
+ * Try to save on CPU power when waiting delay
+ * is at least a system timer tick period.
+ * No need to be extremely accurate here.
+ */
+ xip_cpu_idle();
+ }
+ status = map_read(map, adr);
+ } while (!map_word_andequal(map, status, OK, OK)
+ && xip_elapsed_since(start) < usec);
+}
+
+#define UDELAY(map, chip, adr, usec) xip_udelay(map, chip, adr, usec)
+
+/*
+ * The INVALIDATE_CACHED_RANGE() macro is normally used in parallel while
+ * the flash is actively programming or erasing since we have to poll for
+ * the operation to complete anyway. We can't do that in a generic way with
+ * a XIP setup so do it before the actual flash operation in this case.
+ */
+#undef INVALIDATE_CACHED_RANGE
+#define INVALIDATE_CACHED_RANGE(x...)
+#define XIP_INVAL_CACHED_RANGE(map, from, size) \
+ do { if(map->inval_cache) map->inval_cache(map, from, size); } while(0)
+
+/*
+ * Extra notes:
+ *
+ * Activating this XIP support changes the way the code works a bit. For
+ * example the code to suspend the current process when concurrent access
+ * happens is never executed because xip_udelay() will always return with the
+ * same chip state as it was entered with. This is why there is no care for
+ * the presence of add_wait_queue() or schedule() calls from within a couple
+ * xip_disable()'d areas of code, like in do_erase_oneblock for example.
+ * The queueing and scheduling are always happening within xip_udelay().
+ *
+ * Similarly, get_chip() and put_chip() just happen to always be executed
+ * with chip->state set to FL_READY (or FL_XIP_WHILE_*) where flash state
+ * is in array mode, therefore never executing many cases therein and not
+ * causing any problem with XIP.
+ */
+
+#else
+
+#define xip_disable(map, chip, adr)
+#define xip_enable(map, chip, adr)
+
+#define UDELAY(map, chip, adr, usec) cfi_udelay(usec)
+
+#define XIP_INVAL_CACHED_RANGE(x...)
+
+#endif
+
+static int do_point_onechip (struct map_info *map, struct flchip *chip, loff_t adr, size_t len)
+{
+ unsigned long cmd_addr;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int ret = 0;
+
+ adr += chip->start;
+
+ /* Ensure cmd read/writes are aligned. */
+ cmd_addr = adr & ~(map_bankwidth(map)-1);
+
+ spin_lock(chip->mutex);
+
+ ret = get_chip(map, chip, cmd_addr, FL_POINT);
+
+ if (!ret) {
+ if (chip->state != FL_POINT && chip->state != FL_READY)
+ map_write(map, CMD(0xff), cmd_addr);
+
+ chip->state = FL_POINT;
+ chip->ref_point_counter++;
+ }
+ spin_unlock(chip->mutex);
+
+ return ret;
+}
+
+static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long ofs;
+ int chipnum;
+ int ret = 0;
+
+ if (!map->virt || (from + len > mtd->size))
+ return -EINVAL;
+
+ *mtdbuf = (void *)map->virt + from;
+ *retlen = 0;
+
+ /* Now lock the chip(s) to POINT state */
+
+ /* ofs: offset within the first chip that the first read should start */
+ chipnum = (from >> cfi->chipshift);
+ ofs = from - (chipnum << cfi->chipshift);
+
+ while (len) {
+ unsigned long thislen;
+
+ if (chipnum >= cfi->numchips)
+ break;
+
+ if ((len + ofs -1) >> cfi->chipshift)
+ thislen = (1<<cfi->chipshift) - ofs;
+ else
+ thislen = len;
+
+ ret = do_point_onechip(map, &cfi->chips[chipnum], ofs, thislen);
+ if (ret)
+ break;
+
+ *retlen += thislen;
+ len -= thislen;
+
+ ofs = 0;
+ chipnum++;
+ }
+ return 0;
+}
+
+static void cfi_intelext_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from, size_t len)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long ofs;
+ int chipnum;
+
+ /* Now unlock the chip(s) POINT state */
+
+ /* ofs: offset within the first chip that the first read should start */
+ chipnum = (from >> cfi->chipshift);
+ ofs = from - (chipnum << cfi->chipshift);
+
+ while (len) {
+ unsigned long thislen;
+ struct flchip *chip;
+
+ chip = &cfi->chips[chipnum];
+ if (chipnum >= cfi->numchips)
+ break;
+
+ if ((len + ofs -1) >> cfi->chipshift)
+ thislen = (1<<cfi->chipshift) - ofs;
+ else
+ thislen = len;
+
+ spin_lock(chip->mutex);
+ if (chip->state == FL_POINT) {
+ chip->ref_point_counter--;
+ if(chip->ref_point_counter == 0)
+ chip->state = FL_READY;
+ } else
+ printk(KERN_ERR "Warning: unpoint called on non pointed region\n"); /* Should this give an error? */
+
+ put_chip(map, chip, chip->start);
+ spin_unlock(chip->mutex);
+
+ len -= thislen;
+ ofs = 0;
+ chipnum++;
+ }
+}
+
+static inline int do_read_onechip(struct map_info *map, struct flchip *chip, loff_t adr, size_t len, u_char *buf)
+{
+ unsigned long cmd_addr;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int ret;
+
+ adr += chip->start;
+
+ /* Ensure cmd read/writes are aligned. */
+ cmd_addr = adr & ~(map_bankwidth(map)-1);
+
+ spin_lock(chip->mutex);
+ ret = get_chip(map, chip, cmd_addr, FL_READY);
+ if (ret) {
+ spin_unlock(chip->mutex);
+ return ret;
+ }
+
+ if (chip->state != FL_POINT && chip->state != FL_READY) {
+ map_write(map, CMD(0xff), cmd_addr);
+
+ chip->state = FL_READY;
+ }
+
+ map_copy_from(map, buf, adr, len);
+
+ put_chip(map, chip, cmd_addr);
+
+ spin_unlock(chip->mutex);
+ return 0;
+}
+
+static int cfi_intelext_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long ofs;
+ int chipnum;
+ int ret = 0;
+
+ /* ofs: offset within the first chip that the first read should start */
+ chipnum = (from >> cfi->chipshift);
+ ofs = from - (chipnum << cfi->chipshift);
+
+ *retlen = 0;
+
+ while (len) {
+ unsigned long thislen;
+
+ if (chipnum >= cfi->numchips)
+ break;
+
+ if ((len + ofs -1) >> cfi->chipshift)
+ thislen = (1<<cfi->chipshift) - ofs;
+ else
+ thislen = len;
+
+ ret = do_read_onechip(map, &cfi->chips[chipnum], ofs, thislen, buf);
+ if (ret)
+ break;
+
+ *retlen += thislen;
+ len -= thislen;
+ buf += thislen;
+
+ ofs = 0;
+ chipnum++;
+ }
+ return ret;
+}
+
+#if 0
+static int __xipram cfi_intelext_read_prot_reg (struct mtd_info *mtd,
+ loff_t from, size_t len,
+ size_t *retlen,
+ u_char *buf,
+ int base_offst, int reg_sz)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ struct cfi_pri_intelext *extp = cfi->cmdset_priv;
+ struct flchip *chip;
+ int ofs_factor = cfi->interleave * cfi->device_type;
+ int count = len;
+ int chip_num, offst;
+ int ret;
+
+ chip_num = ((unsigned int)from/reg_sz);
+ offst = from - (reg_sz*chip_num)+base_offst;
+
+ while (count) {
+ /* Calculate which chip & protection register offset we need */
+
+ if (chip_num >= cfi->numchips)
+ goto out;
+
+ chip = &cfi->chips[chip_num];
+
+ spin_lock(chip->mutex);
+ ret = get_chip(map, chip, chip->start, FL_JEDEC_QUERY);
+ if (ret) {
+ spin_unlock(chip->mutex);
+ return (len-count)?:ret;
+ }
+
+ xip_disable(map, chip, chip->start);
+
+ if (chip->state != FL_JEDEC_QUERY) {
+ map_write(map, CMD(0x90), chip->start);
+ chip->state = FL_JEDEC_QUERY;
+ }
+
+ while (count && ((offst-base_offst) < reg_sz)) {
+ *buf = map_read8(map,(chip->start+((extp->ProtRegAddr+1)*ofs_factor)+offst));
+ buf++;
+ offst++;
+ count--;
+ }
+
+ xip_enable(map, chip, chip->start);
+ put_chip(map, chip, chip->start);
+ spin_unlock(chip->mutex);
+
+ /* Move on to the next chip */
+ chip_num++;
+ offst = base_offst;
+ }
+
+ out:
+ return len-count;
+}
+
+static int cfi_intelext_read_user_prot_reg (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ struct cfi_pri_intelext *extp=cfi->cmdset_priv;
+ int base_offst,reg_sz;
+
+ /* Check that we actually have some protection registers */
+ if(!extp || !(extp->FeatureSupport&64)){
+ printk(KERN_WARNING "%s: This flash device has no protection data to read!\n",map->name);
+ return 0;
+ }
+
+ base_offst=(1<<extp->FactProtRegSize);
+ reg_sz=(1<<extp->UserProtRegSize);
+
+ return cfi_intelext_read_prot_reg(mtd, from, len, retlen, buf, base_offst, reg_sz);
+}
+
+static int cfi_intelext_read_fact_prot_reg (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ struct cfi_pri_intelext *extp=cfi->cmdset_priv;
+ int base_offst,reg_sz;
+
+ /* Check that we actually have some protection registers */
+ if(!extp || !(extp->FeatureSupport&64)){
+ printk(KERN_WARNING "%s: This flash device has no protection data to read!\n",map->name);
+ return 0;
+ }
+
+ base_offst=0;
+ reg_sz=(1<<extp->FactProtRegSize);
+
+ return cfi_intelext_read_prot_reg(mtd, from, len, retlen, buf, base_offst, reg_sz);
+}
+#endif
+
+static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
+ unsigned long adr, map_word datum)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ map_word status, status_OK;
+ unsigned long timeo;
+ int z, ret=0;
+
+ adr += chip->start;
+
+ /* Let's determine this according to the interleave only once */
+ status_OK = CMD(0x80);
+
+ spin_lock(chip->mutex);
+ ret = get_chip(map, chip, adr, FL_WRITING);
+ if (ret) {
+ spin_unlock(chip->mutex);
+ return ret;
+ }
+
+ XIP_INVAL_CACHED_RANGE(map, adr, map_bankwidth(map));
+ ENABLE_VPP(map);
+ xip_disable(map, chip, adr);
+ map_write(map, CMD(0x40), adr);
+ map_write(map, datum, adr);
+ chip->state = FL_WRITING;
+
+ spin_unlock(chip->mutex);
+ INVALIDATE_CACHED_RANGE(map, adr, map_bankwidth(map));
+ UDELAY(map, chip, adr, chip->word_write_time);
+ spin_lock(chip->mutex);
+
+ timeo = jiffies + (HZ/2);
+ z = 0;
+ for (;;) {
+ if (chip->state != FL_WRITING) {
+ /* Someone's suspended the write. Sleep */
+ DECLARE_WAITQUEUE(wait, current);
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ spin_unlock(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ timeo = jiffies + (HZ / 2); /* FIXME */
+ spin_lock(chip->mutex);
+ continue;
+ }
+
+ status = map_read(map, adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ /* OK Still waiting */
+ if (time_after(jiffies, timeo)) {
+ chip->state = FL_STATUS;
+ xip_enable(map, chip, adr);
+ printk(KERN_ERR "waiting for chip to be ready timed out in word write\n");
+ ret = -EIO;
+ goto out;
+ }
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ spin_unlock(chip->mutex);
+ z++;
+ UDELAY(map, chip, adr, 1);
+ spin_lock(chip->mutex);
+ }
+ if (!z) {
+ chip->word_write_time--;
+ if (!chip->word_write_time)
+ chip->word_write_time++;
+ }
+ if (z > 1)
+ chip->word_write_time++;
+
+ /* Done and happy. */
+ chip->state = FL_STATUS;
+
+ /* check for lock bit */
+ if (map_word_bitsset(map, status, CMD(0x02))) {
+ /* clear status */
+ map_write(map, CMD(0x50), adr);
+ /* put back into read status register mode */
+ map_write(map, CMD(0x70), adr);
+ ret = -EROFS;
+ }
+
+ xip_enable(map, chip, adr);
+ out: put_chip(map, chip, adr);
+ spin_unlock(chip->mutex);
+
+ return ret;
+}
+
+
+static int cfi_intelext_write_words (struct mtd_info *mtd, loff_t to , size_t len, size_t *retlen, const u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int ret = 0;
+ int chipnum;
+ unsigned long ofs;
+
+ *retlen = 0;
+ if (!len)
+ return 0;
+
+ chipnum = to >> cfi->chipshift;
+ ofs = to - (chipnum << cfi->chipshift);
+
+ /* If it's not bus-aligned, do the first byte write */
+ if (ofs & (map_bankwidth(map)-1)) {
+ unsigned long bus_ofs = ofs & ~(map_bankwidth(map)-1);
+ int gap = ofs - bus_ofs;
+ int n;
+ map_word datum;
+
+ n = min_t(int, len, map_bankwidth(map)-gap);
+ datum = map_word_ff(map);
+ datum = map_word_load_partial(map, datum, buf, gap, n);
+
+ ret = do_write_oneword(map, &cfi->chips[chipnum],
+ bus_ofs, datum);
+ if (ret)
+ return ret;
+
+ len -= n;
+ ofs += n;
+ buf += n;
+ (*retlen) += n;
+
+ if (ofs >> cfi->chipshift) {
+ chipnum ++;
+ ofs = 0;
+ if (chipnum == cfi->numchips)
+ return 0;
+ }
+ }
+
+ while(len >= map_bankwidth(map)) {
+ map_word datum = map_word_load(map, buf);
+
+ ret = do_write_oneword(map, &cfi->chips[chipnum],
+ ofs, datum);
+ if (ret)
+ return ret;
+
+ ofs += map_bankwidth(map);
+ buf += map_bankwidth(map);
+ (*retlen) += map_bankwidth(map);
+ len -= map_bankwidth(map);
+
+ if (ofs >> cfi->chipshift) {
+ chipnum ++;
+ ofs = 0;
+ if (chipnum == cfi->numchips)
+ return 0;
+ }
+ }
+
+ if (len & (map_bankwidth(map)-1)) {
+ map_word datum;
+
+ datum = map_word_ff(map);
+ datum = map_word_load_partial(map, datum, buf, 0, len);
+
+ ret = do_write_oneword(map, &cfi->chips[chipnum],
+ ofs, datum);
+ if (ret)
+ return ret;
+
+ (*retlen) += len;
+ }
+
+ return 0;
+}
+
+
+static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip,
+ unsigned long adr, const u_char *buf, int len)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ map_word status, status_OK;
+ unsigned long cmd_adr, timeo;
+ int wbufsize, z, ret=0, bytes, words;
+
+ wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize;
+ adr += chip->start;
+ cmd_adr = adr & ~(wbufsize-1);
+
+ /* Let's determine this according to the interleave only once */
+ status_OK = CMD(0x80);
+
+ spin_lock(chip->mutex);
+ ret = get_chip(map, chip, cmd_adr, FL_WRITING);
+ if (ret) {
+ spin_unlock(chip->mutex);
+ return ret;
+ }
+
+ XIP_INVAL_CACHED_RANGE(map, adr, len);
+ ENABLE_VPP(map);
+ xip_disable(map, chip, cmd_adr);
+
+ /* §4.8 of the 28FxxxJ3A datasheet says "Any time SR.4 and/or SR.5 is set
+ [...], the device will not accept any more Write to Buffer commands".
+ So we must check here and reset those bits if they're set. Otherwise
+ we're just pissing in the wind */
+ if (chip->state != FL_STATUS)
+ map_write(map, CMD(0x70), cmd_adr);
+ status = map_read(map, cmd_adr);
+ if (map_word_bitsset(map, status, CMD(0x30))) {
+ xip_enable(map, chip, cmd_adr);
+ printk(KERN_WARNING "SR.4 or SR.5 bits set in buffer write (status %lx). Clearing.\n", status.x[0]);
+ xip_disable(map, chip, cmd_adr);
+ map_write(map, CMD(0x50), cmd_adr);
+ map_write(map, CMD(0x70), cmd_adr);
+ }
+
+ chip->state = FL_WRITING_TO_BUFFER;
+
+ z = 0;
+ for (;;) {
+ map_write(map, CMD(0xe8), cmd_adr);
+
+ status = map_read(map, cmd_adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ spin_unlock(chip->mutex);
+ UDELAY(map, chip, cmd_adr, 1);
+ spin_lock(chip->mutex);
+
+ if (++z > 20) {
+ /* Argh. Not ready for write to buffer */
+ map_word Xstatus;
+ map_write(map, CMD(0x70), cmd_adr);
+ chip->state = FL_STATUS;
+ Xstatus = map_read(map, cmd_adr);
+ /* Odd. Clear status bits */
+ map_write(map, CMD(0x50), cmd_adr);
+ map_write(map, CMD(0x70), cmd_adr);
+ xip_enable(map, chip, cmd_adr);
+ printk(KERN_ERR "Chip not ready for buffer write. status = %lx, Xstatus = %lx\n",
+ status.x[0], Xstatus.x[0]);
+ ret = -EIO;
+ goto out;
+ }
+ }
+
+ /* Write length of data to come */
+ bytes = len & (map_bankwidth(map)-1);
+ words = len / map_bankwidth(map);
+ map_write(map, CMD(words - !bytes), cmd_adr );
+
+ /* Write data */
+ z = 0;
+ while(z < words * map_bankwidth(map)) {
+ map_word datum = map_word_load(map, buf);
+ map_write(map, datum, adr+z);
+
+ z += map_bankwidth(map);
+ buf += map_bankwidth(map);
+ }
+
+ if (bytes) {
+ map_word datum;
+
+ datum = map_word_ff(map);
+ datum = map_word_load_partial(map, datum, buf, 0, bytes);
+ map_write(map, datum, adr+z);
+ }
+
+ /* GO GO GO */
+ map_write(map, CMD(0xd0), cmd_adr);
+ chip->state = FL_WRITING;
+
+ spin_unlock(chip->mutex);
+ INVALIDATE_CACHED_RANGE(map, adr, len);
+ UDELAY(map, chip, cmd_adr, chip->buffer_write_time);
+ spin_lock(chip->mutex);
+
+ timeo = jiffies + (HZ/2);
+ z = 0;
+ for (;;) {
+ if (chip->state != FL_WRITING) {
+ /* Someone's suspended the write. Sleep */
+ DECLARE_WAITQUEUE(wait, current);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ spin_unlock(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ timeo = jiffies + (HZ / 2); /* FIXME */
+ spin_lock(chip->mutex);
+ continue;
+ }
+
+ status = map_read(map, cmd_adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ /* OK Still waiting */
+ if (time_after(jiffies, timeo)) {
+ chip->state = FL_STATUS;
+ xip_enable(map, chip, cmd_adr);
+ printk(KERN_ERR "waiting for chip to be ready timed out in bufwrite\n");
+ ret = -EIO;
+ goto out;
+ }
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ spin_unlock(chip->mutex);
+ UDELAY(map, chip, cmd_adr, 1);
+ z++;
+ spin_lock(chip->mutex);
+ }
+ if (!z) {
+ chip->buffer_write_time--;
+ if (!chip->buffer_write_time)
+ chip->buffer_write_time++;
+ }
+ if (z > 1)
+ chip->buffer_write_time++;
+
+ /* Done and happy. */
+ chip->state = FL_STATUS;
+
+ /* check for lock bit */
+ if (map_word_bitsset(map, status, CMD(0x02))) {
+ /* clear status */
+ map_write(map, CMD(0x50), cmd_adr);
+ /* put back into read status register mode */
+ map_write(map, CMD(0x70), adr);
+ ret = -EROFS;
+ }
+
+ xip_enable(map, chip, cmd_adr);
+ out: put_chip(map, chip, cmd_adr);
+ spin_unlock(chip->mutex);
+ return ret;
+}
+
+static int cfi_intelext_write_buffers (struct mtd_info *mtd, loff_t to,
+ size_t len, size_t *retlen, const u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize;
+ int ret = 0;
+ int chipnum;
+ unsigned long ofs;
+
+ *retlen = 0;
+ if (!len)
+ return 0;
+
+ chipnum = to >> cfi->chipshift;
+ ofs = to - (chipnum << cfi->chipshift);
+
+ /* If it's not bus-aligned, do the first word write */
+ if (ofs & (map_bankwidth(map)-1)) {
+ size_t local_len = (-ofs)&(map_bankwidth(map)-1);
+ if (local_len > len)
+ local_len = len;
+ ret = cfi_intelext_write_words(mtd, to, local_len,
+ retlen, buf);
+ if (ret)
+ return ret;
+ ofs += local_len;
+ buf += local_len;
+ len -= local_len;
+
+ if (ofs >> cfi->chipshift) {
+ chipnum ++;
+ ofs = 0;
+ if (chipnum == cfi->numchips)
+ return 0;
+ }
+ }
+
+ while(len) {
+ /* We must not cross write block boundaries */
+ int size = wbufsize - (ofs & (wbufsize-1));
+
+ if (size > len)
+ size = len;
+ ret = do_write_buffer(map, &cfi->chips[chipnum],
+ ofs, buf, size);
+ if (ret)
+ return ret;
+
+ ofs += size;
+ buf += size;
+ (*retlen) += size;
+ len -= size;
+
+ if (ofs >> cfi->chipshift) {
+ chipnum ++;
+ ofs = 0;
+ if (chipnum == cfi->numchips)
+ return 0;
+ }
+ }
+ return 0;
+}
+
+static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip,
+ unsigned long adr, int len, void *thunk)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ map_word status, status_OK;
+ unsigned long timeo;
+ int retries = 3;
+ DECLARE_WAITQUEUE(wait, current);
+ int ret = 0;
+
+ adr += chip->start;
+
+ /* Let's determine this according to the interleave only once */
+ status_OK = CMD(0x80);
+
+ retry:
+ spin_lock(chip->mutex);
+ ret = get_chip(map, chip, adr, FL_ERASING);
+ if (ret) {
+ spin_unlock(chip->mutex);
+ return ret;
+ }
+
+ XIP_INVAL_CACHED_RANGE(map, adr, len);
+ ENABLE_VPP(map);
+ xip_disable(map, chip, adr);
+
+ /* Clear the status register first */
+ map_write(map, CMD(0x50), adr);
+
+ /* Now erase */
+ map_write(map, CMD(0x20), adr);
+ map_write(map, CMD(0xD0), adr);
+ chip->state = FL_ERASING;
+ chip->erase_suspended = 0;
+
+ spin_unlock(chip->mutex);
+ INVALIDATE_CACHED_RANGE(map, adr, len);
+ UDELAY(map, chip, adr, chip->erase_time*1000/2);
+ spin_lock(chip->mutex);
+
+ /* FIXME. Use a timer to check this, and return immediately. */
+ /* Once the state machine's known to be working I'll do that */
+
+ timeo = jiffies + (HZ*20);
+ for (;;) {
+ if (chip->state != FL_ERASING) {
+ /* Someone's suspended the erase. Sleep */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ spin_unlock(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ spin_lock(chip->mutex);
+ continue;
+ }
+ if (chip->erase_suspended) {
+ /* This erase was suspended and resumed.
+ Adjust the timeout */
+ timeo = jiffies + (HZ*20); /* FIXME */
+ chip->erase_suspended = 0;
+ }
+
+ status = map_read(map, adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ /* OK Still waiting */
+ if (time_after(jiffies, timeo)) {
+ map_word Xstatus;
+ map_write(map, CMD(0x70), adr);
+ chip->state = FL_STATUS;
+ Xstatus = map_read(map, adr);
+ /* Clear status bits */
+ map_write(map, CMD(0x50), adr);
+ map_write(map, CMD(0x70), adr);
+ xip_enable(map, chip, adr);
+ printk(KERN_ERR "waiting for erase at %08lx to complete timed out. status = %lx, Xstatus = %lx.\n",
+ adr, status.x[0], Xstatus.x[0]);
+ ret = -EIO;
+ goto out;
+ }
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ spin_unlock(chip->mutex);
+ UDELAY(map, chip, adr, 1000000/HZ);
+ spin_lock(chip->mutex);
+ }
+
+ /* We've broken this before. It doesn't hurt to be safe */
+ map_write(map, CMD(0x70), adr);
+ chip->state = FL_STATUS;
+ status = map_read(map, adr);
+
+ /* check for lock bit */
+ if (map_word_bitsset(map, status, CMD(0x3a))) {
+ unsigned char chipstatus;
+
+ /* Reset the error bits */
+ map_write(map, CMD(0x50), adr);
+ map_write(map, CMD(0x70), adr);
+ xip_enable(map, chip, adr);
+
+ chipstatus = status.x[0];
+ if (!map_word_equal(map, status, CMD(chipstatus))) {
+ int i, w;
+ for (w=0; w<map_words(map); w++) {
+ for (i = 0; i<cfi_interleave(cfi); i++) {
+ chipstatus |= status.x[w] >> (cfi->device_type * 8);
+ }
+ }
+ printk(KERN_WARNING "Status is not identical for all chips: 0x%lx. Merging to give 0x%02x\n",
+ status.x[0], chipstatus);
+ }
+
+ if ((chipstatus & 0x30) == 0x30) {
+ printk(KERN_NOTICE "Chip reports improper command sequence: status 0x%x\n", chipstatus);
+ ret = -EIO;
+ } else if (chipstatus & 0x02) {
+ /* Protection bit set */
+ ret = -EROFS;
+ } else if (chipstatus & 0x8) {
+ /* Voltage */
+ printk(KERN_WARNING "Chip reports voltage low on erase: status 0x%x\n", chipstatus);
+ ret = -EIO;
+ } else if (chipstatus & 0x20) {
+ if (retries--) {
+ printk(KERN_DEBUG "Chip erase failed at 0x%08lx: status 0x%x. Retrying...\n", adr, chipstatus);
+ timeo = jiffies + HZ;
+ put_chip(map, chip, adr);
+ spin_unlock(chip->mutex);
+ goto retry;
+ }
+ printk(KERN_DEBUG "Chip erase failed at 0x%08lx: status 0x%x\n", adr, chipstatus);
+ ret = -EIO;
+ }
+ } else {
+ xip_enable(map, chip, adr);
+ ret = 0;
+ }
+
+ out: put_chip(map, chip, adr);
+ spin_unlock(chip->mutex);
+ return ret;
+}
+
+int cfi_intelext_erase_varsize(struct mtd_info *mtd, struct erase_info *instr)
+{
+ unsigned long ofs, len;
+ int ret;
+
+ ofs = instr->addr;
+ len = instr->len;
+
+ ret = cfi_varsize_frob(mtd, do_erase_oneblock, ofs, len, NULL);
+ if (ret)
+ return ret;
+
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+
+ return 0;
+}
+
+static void cfi_intelext_sync (struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int i;
+ struct flchip *chip;
+ int ret = 0;
+
+ for (i=0; !ret && i<cfi->numchips; i++) {
+ chip = &cfi->chips[i];
+
+ spin_lock(chip->mutex);
+ ret = get_chip(map, chip, chip->start, FL_SYNCING);
+
+ if (!ret) {
+ chip->oldstate = chip->state;
+ chip->state = FL_SYNCING;
+ /* No need to wake_up() on this state change -
+ * as the whole point is that nobody can do anything
+ * with the chip now anyway.
+ */
+ }
+ spin_unlock(chip->mutex);
+ }
+
+ /* Unlock the chips again */
+
+ for (i--; i >=0; i--) {
+ chip = &cfi->chips[i];
+
+ spin_lock(chip->mutex);
+
+ if (chip->state == FL_SYNCING) {
+ chip->state = chip->oldstate;
+ wake_up(&chip->wq);
+ }
+ spin_unlock(chip->mutex);
+ }
+}
+
+#ifdef DEBUG_LOCK_BITS
+static int __xipram do_printlockstatus_oneblock(struct map_info *map,
+ struct flchip *chip,
+ unsigned long adr,
+ int len, void *thunk)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ int status, ofs_factor = cfi->interleave * cfi->device_type;
+
+ xip_disable(map, chip, adr+(2*ofs_factor));
+ cfi_send_gen_cmd(0x90, 0x55, 0, map, cfi, cfi->device_type, NULL);
+ chip->state = FL_JEDEC_QUERY;
+ status = cfi_read_query(map, adr+(2*ofs_factor));
+ xip_enable(map, chip, 0);
+ printk(KERN_DEBUG "block status register for 0x%08lx is %x\n",
+ adr, status);
+ return 0;
+}
+#endif
+
+#define DO_XXLOCK_ONEBLOCK_LOCK ((void *) 1)
+#define DO_XXLOCK_ONEBLOCK_UNLOCK ((void *) 2)
+
+static int __xipram do_xxlock_oneblock(struct map_info *map, struct flchip *chip,
+ unsigned long adr, int len, void *thunk)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ map_word status, status_OK;
+ unsigned long timeo = jiffies + HZ;
+ int ret;
+
+ adr += chip->start;
+
+ /* Let's determine this according to the interleave only once */
+ status_OK = CMD(0x80);
+
+ spin_lock(chip->mutex);
+ ret = get_chip(map, chip, adr, FL_LOCKING);
+ if (ret) {
+ spin_unlock(chip->mutex);
+ return ret;
+ }
+
+ ENABLE_VPP(map);
+ xip_disable(map, chip, adr);
+
+ map_write(map, CMD(0x60), adr);
+ if (thunk == DO_XXLOCK_ONEBLOCK_LOCK) {
+ map_write(map, CMD(0x01), adr);
+ chip->state = FL_LOCKING;
+ } else if (thunk == DO_XXLOCK_ONEBLOCK_UNLOCK) {
+ map_write(map, CMD(0xD0), adr);
+ chip->state = FL_UNLOCKING;
+ } else
+ BUG();
+
+ spin_unlock(chip->mutex);
+ UDELAY(map, chip, adr, 1000000/HZ);
+ spin_lock(chip->mutex);
+
+ /* FIXME. Use a timer to check this, and return immediately. */
+ /* Once the state machine's known to be working I'll do that */
+
+ timeo = jiffies + (HZ*20);
+ for (;;) {
+
+ status = map_read(map, adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ /* OK Still waiting */
+ if (time_after(jiffies, timeo)) {
+ map_word Xstatus;
+ map_write(map, CMD(0x70), adr);
+ chip->state = FL_STATUS;
+ Xstatus = map_read(map, adr);
+ xip_enable(map, chip, adr);
+ printk(KERN_ERR "waiting for unlock to complete timed out. status = %lx, Xstatus = %lx.\n",
+ status.x[0], Xstatus.x[0]);
+ put_chip(map, chip, adr);
+ spin_unlock(chip->mutex);
+ return -EIO;
+ }
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ spin_unlock(chip->mutex);
+ UDELAY(map, chip, adr, 1);
+ spin_lock(chip->mutex);
+ }
+
+ /* Done and happy. */
+ chip->state = FL_STATUS;
+ xip_enable(map, chip, adr);
+ put_chip(map, chip, adr);
+ spin_unlock(chip->mutex);
+ return 0;
+}
+
+static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
+{
+ int ret;
+
+#ifdef DEBUG_LOCK_BITS
+ printk(KERN_DEBUG "%s: lock status before, ofs=0x%08llx, len=0x%08X\n",
+ __FUNCTION__, ofs, len);
+ cfi_varsize_frob(mtd, do_printlockstatus_oneblock,
+ ofs, len, 0);
+#endif
+
+ ret = cfi_varsize_frob(mtd, do_xxlock_oneblock,
+ ofs, len, DO_XXLOCK_ONEBLOCK_LOCK);
+
+#ifdef DEBUG_LOCK_BITS
+ printk(KERN_DEBUG "%s: lock status after, ret=%d\n",
+ __FUNCTION__, ret);
+ cfi_varsize_frob(mtd, do_printlockstatus_oneblock,
+ ofs, len, 0);
+#endif
+
+ return ret;
+}
+
+static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
+{
+ int ret;
+
+#ifdef DEBUG_LOCK_BITS
+ printk(KERN_DEBUG "%s: lock status before, ofs=0x%08llx, len=0x%08X\n",
+ __FUNCTION__, ofs, len);
+ cfi_varsize_frob(mtd, do_printlockstatus_oneblock,
+ ofs, len, 0);
+#endif
+
+ ret = cfi_varsize_frob(mtd, do_xxlock_oneblock,
+ ofs, len, DO_XXLOCK_ONEBLOCK_UNLOCK);
+
+#ifdef DEBUG_LOCK_BITS
+ printk(KERN_DEBUG "%s: lock status after, ret=%d\n",
+ __FUNCTION__, ret);
+ cfi_varsize_frob(mtd, do_printlockstatus_oneblock,
+ ofs, len, 0);
+#endif
+
+ return ret;
+}
+
+static int cfi_intelext_suspend(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int i;
+ struct flchip *chip;
+ int ret = 0;
+
+ for (i=0; !ret && i<cfi->numchips; i++) {
+ chip = &cfi->chips[i];
+
+ spin_lock(chip->mutex);
+
+ switch (chip->state) {
+ case FL_READY:
+ case FL_STATUS:
+ case FL_CFI_QUERY:
+ case FL_JEDEC_QUERY:
+ if (chip->oldstate == FL_READY) {
+ chip->oldstate = chip->state;
+ chip->state = FL_PM_SUSPENDED;
+ /* No need to wake_up() on this state change -
+ * as the whole point is that nobody can do anything
+ * with the chip now anyway.
+ */
+ } else {
+ /* There seems to be an operation pending. We must wait for it. */
+ printk(KERN_NOTICE "Flash device refused suspend due to pending operation (oldstate %d)\n", chip->oldstate);
+ ret = -EAGAIN;
+ }
+ break;
+ default:
+ /* Should we actually wait? Once upon a time these routines weren't
+ allowed to. Or should we return -EAGAIN, because the upper layers
+ ought to have already shut down anything which was using the device
+ anyway? The latter for now. */
+ printk(KERN_NOTICE "Flash device refused suspend due to active operation (state %d)\n", chip->oldstate);
+ ret = -EAGAIN;
+ case FL_PM_SUSPENDED:
+ break;
+ }
+ spin_unlock(chip->mutex);
+ }
+
+ /* Unlock the chips again */
+
+ if (ret) {
+ for (i--; i >=0; i--) {
+ chip = &cfi->chips[i];
+
+ spin_lock(chip->mutex);
+
+ if (chip->state == FL_PM_SUSPENDED) {
+ /* No need to force it into a known state here,
+ because we're returning failure, and it didn't
+ get power cycled */
+ chip->state = chip->oldstate;
+ chip->oldstate = FL_READY;
+ wake_up(&chip->wq);
+ }
+ spin_unlock(chip->mutex);
+ }
+ }
+
+ return ret;
+}
+
+static void cfi_intelext_resume(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int i;
+ struct flchip *chip;
+
+ for (i=0; i<cfi->numchips; i++) {
+
+ chip = &cfi->chips[i];
+
+ spin_lock(chip->mutex);
+
+ /* Go to known state. Chip may have been power cycled */
+ if (chip->state == FL_PM_SUSPENDED) {
+ map_write(map, CMD(0xFF), cfi->chips[i].start);
+ chip->oldstate = chip->state = FL_READY;
+ wake_up(&chip->wq);
+ }
+
+ spin_unlock(chip->mutex);
+ }
+}
+
+static void cfi_intelext_destroy(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ kfree(cfi->cmdset_priv);
+ kfree(cfi->cfiq);
+ kfree(cfi->chips[0].priv);
+ kfree(cfi);
+ kfree(mtd->eraseregions);
+}
+
+static char im_name_1[]="cfi_cmdset_0001";
+static char im_name_3[]="cfi_cmdset_0003";
+
+static int __init cfi_intelext_init(void)
+{
+ inter_module_register(im_name_1, THIS_MODULE, &cfi_cmdset_0001);
+ inter_module_register(im_name_3, THIS_MODULE, &cfi_cmdset_0001);
+ return 0;
+}
+
+static void __exit cfi_intelext_exit(void)
+{
+ inter_module_unregister(im_name_1);
+ inter_module_unregister(im_name_3);
+}
+
+module_init(cfi_intelext_init);
+module_exit(cfi_intelext_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org> et al.");
+MODULE_DESCRIPTION("MTD chip driver for Intel/Sharp flash chips");
diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c
new file mode 100644
index 00000000000..fca8ff6f7e1
--- /dev/null
+++ b/drivers/mtd/chips/cfi_cmdset_0002.c
@@ -0,0 +1,1515 @@
+/*
+ * Common Flash Interface support:
+ * AMD & Fujitsu Standard Vendor Command Set (ID 0x0002)
+ *
+ * Copyright (C) 2000 Crossnet Co. <info@crossnet.co.jp>
+ * Copyright (C) 2004 Arcom Control Systems Ltd <linux@arcom.com>
+ *
+ * 2_by_8 routines added by Simon Munton
+ *
+ * 4_by_16 work by Carolyn J. Smith
+ *
+ * Occasionally maintained by Thayne Harbaugh tharbaugh at lnxi dot com
+ *
+ * This code is GPL
+ *
+ * $Id: cfi_cmdset_0002.c,v 1.114 2004/12/11 15:43:53 dedekind Exp $
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <asm/byteorder.h>
+
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/mtd/compatmac.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/cfi.h>
+
+#define AMD_BOOTLOC_BUG
+#define FORCE_WORD_WRITE 0
+
+#define MAX_WORD_RETRIES 3
+
+#define MANUFACTURER_AMD 0x0001
+#define MANUFACTURER_SST 0x00BF
+#define SST49LF004B 0x0060
+
+static int cfi_amdstd_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
+static int cfi_amdstd_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
+static int cfi_amdstd_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
+static int cfi_amdstd_erase_chip(struct mtd_info *, struct erase_info *);
+static int cfi_amdstd_erase_varsize(struct mtd_info *, struct erase_info *);
+static void cfi_amdstd_sync (struct mtd_info *);
+static int cfi_amdstd_suspend (struct mtd_info *);
+static void cfi_amdstd_resume (struct mtd_info *);
+static int cfi_amdstd_secsi_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
+
+static void cfi_amdstd_destroy(struct mtd_info *);
+
+struct mtd_info *cfi_cmdset_0002(struct map_info *, int);
+static struct mtd_info *cfi_amdstd_setup (struct mtd_info *);
+
+static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode);
+static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr);
+#include "fwh_lock.h"
+
+static struct mtd_chip_driver cfi_amdstd_chipdrv = {
+ .probe = NULL, /* Not usable directly */
+ .destroy = cfi_amdstd_destroy,
+ .name = "cfi_cmdset_0002",
+ .module = THIS_MODULE
+};
+
+
+/* #define DEBUG_CFI_FEATURES */
+
+
+#ifdef DEBUG_CFI_FEATURES
+static void cfi_tell_features(struct cfi_pri_amdstd *extp)
+{
+ const char* erase_suspend[3] = {
+ "Not supported", "Read only", "Read/write"
+ };
+ const char* top_bottom[6] = {
+ "No WP", "8x8KiB sectors at top & bottom, no WP",
+ "Bottom boot", "Top boot",
+ "Uniform, Bottom WP", "Uniform, Top WP"
+ };
+
+ printk(" Silicon revision: %d\n", extp->SiliconRevision >> 1);
+ printk(" Address sensitive unlock: %s\n",
+ (extp->SiliconRevision & 1) ? "Not required" : "Required");
+
+ if (extp->EraseSuspend < ARRAY_SIZE(erase_suspend))
+ printk(" Erase Suspend: %s\n", erase_suspend[extp->EraseSuspend]);
+ else
+ printk(" Erase Suspend: Unknown value %d\n", extp->EraseSuspend);
+
+ if (extp->BlkProt == 0)
+ printk(" Block protection: Not supported\n");
+ else
+ printk(" Block protection: %d sectors per group\n", extp->BlkProt);
+
+
+ printk(" Temporary block unprotect: %s\n",
+ extp->TmpBlkUnprotect ? "Supported" : "Not supported");
+ printk(" Block protect/unprotect scheme: %d\n", extp->BlkProtUnprot);
+ printk(" Number of simultaneous operations: %d\n", extp->SimultaneousOps);
+ printk(" Burst mode: %s\n",
+ extp->BurstMode ? "Supported" : "Not supported");
+ if (extp->PageMode == 0)
+ printk(" Page mode: Not supported\n");
+ else
+ printk(" Page mode: %d word page\n", extp->PageMode << 2);
+
+ printk(" Vpp Supply Minimum Program/Erase Voltage: %d.%d V\n",
+ extp->VppMin >> 4, extp->VppMin & 0xf);
+ printk(" Vpp Supply Maximum Program/Erase Voltage: %d.%d V\n",
+ extp->VppMax >> 4, extp->VppMax & 0xf);
+
+ if (extp->TopBottom < ARRAY_SIZE(top_bottom))
+ printk(" Top/Bottom Boot Block: %s\n", top_bottom[extp->TopBottom]);
+ else
+ printk(" Top/Bottom Boot Block: Unknown value %d\n", extp->TopBottom);
+}
+#endif
+
+#ifdef AMD_BOOTLOC_BUG
+/* Wheee. Bring me the head of someone at AMD. */
+static void fixup_amd_bootblock(struct mtd_info *mtd, void* param)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ struct cfi_pri_amdstd *extp = cfi->cmdset_priv;
+ __u8 major = extp->MajorVersion;
+ __u8 minor = extp->MinorVersion;
+
+ if (((major << 8) | minor) < 0x3131) {
+ /* CFI version 1.0 => don't trust bootloc */
+ if (cfi->id & 0x80) {
+ printk(KERN_WARNING "%s: JEDEC Device ID is 0x%02X. Assuming broken CFI table.\n", map->name, cfi->id);
+ extp->TopBottom = 3; /* top boot */
+ } else {
+ extp->TopBottom = 2; /* bottom boot */
+ }
+ }
+}
+#endif
+
+static void fixup_use_write_buffers(struct mtd_info *mtd, void *param)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ if (cfi->cfiq->BufWriteTimeoutTyp) {
+ DEBUG(MTD_DEBUG_LEVEL1, "Using buffer write method\n" );
+ mtd->write = cfi_amdstd_write_buffers;
+ }
+}
+
+static void fixup_use_secsi(struct mtd_info *mtd, void *param)
+{
+ /* Setup for chips with a secsi area */
+ mtd->read_user_prot_reg = cfi_amdstd_secsi_read;
+ mtd->read_fact_prot_reg = cfi_amdstd_secsi_read;
+}
+
+static void fixup_use_erase_chip(struct mtd_info *mtd, void *param)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ if ((cfi->cfiq->NumEraseRegions == 1) &&
+ ((cfi->cfiq->EraseRegionInfo[0] & 0xffff) == 0)) {
+ mtd->erase = cfi_amdstd_erase_chip;
+ }
+
+}
+
+static struct cfi_fixup cfi_fixup_table[] = {
+#ifdef AMD_BOOTLOC_BUG
+ { CFI_MFR_AMD, CFI_ID_ANY, fixup_amd_bootblock, NULL },
+#endif
+ { CFI_MFR_AMD, 0x0050, fixup_use_secsi, NULL, },
+ { CFI_MFR_AMD, 0x0053, fixup_use_secsi, NULL, },
+ { CFI_MFR_AMD, 0x0055, fixup_use_secsi, NULL, },
+ { CFI_MFR_AMD, 0x0056, fixup_use_secsi, NULL, },
+ { CFI_MFR_AMD, 0x005C, fixup_use_secsi, NULL, },
+ { CFI_MFR_AMD, 0x005F, fixup_use_secsi, NULL, },
+#if !FORCE_WORD_WRITE
+ { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers, NULL, },
+#endif
+ { 0, 0, NULL, NULL }
+};
+static struct cfi_fixup jedec_fixup_table[] = {
+ { MANUFACTURER_SST, SST49LF004B, fixup_use_fwh_lock, NULL, },
+ { 0, 0, NULL, NULL }
+};
+
+static struct cfi_fixup fixup_table[] = {
+ /* The CFI vendor ids and the JEDEC vendor IDs appear
+ * to be common. It is like the devices id's are as
+ * well. This table is to pick all cases where
+ * we know that is the case.
+ */
+ { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_erase_chip, NULL },
+ { 0, 0, NULL, NULL }
+};
+
+
+struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ struct mtd_info *mtd;
+ int i;
+
+ mtd = kmalloc(sizeof(*mtd), GFP_KERNEL);
+ if (!mtd) {
+ printk(KERN_WARNING "Failed to allocate memory for MTD device\n");
+ return NULL;
+ }
+ memset(mtd, 0, sizeof(*mtd));
+ mtd->priv = map;
+ mtd->type = MTD_NORFLASH;
+
+ /* Fill in the default mtd operations */
+ mtd->erase = cfi_amdstd_erase_varsize;
+ mtd->write = cfi_amdstd_write_words;
+ mtd->read = cfi_amdstd_read;
+ mtd->sync = cfi_amdstd_sync;
+ mtd->suspend = cfi_amdstd_suspend;
+ mtd->resume = cfi_amdstd_resume;
+ mtd->flags = MTD_CAP_NORFLASH;
+ mtd->name = map->name;
+
+ if (cfi->cfi_mode==CFI_MODE_CFI){
+ unsigned char bootloc;
+ /*
+ * It's a real CFI chip, not one for which the probe
+ * routine faked a CFI structure. So we read the feature
+ * table from it.
+ */
+ __u16 adr = primary?cfi->cfiq->P_ADR:cfi->cfiq->A_ADR;
+ struct cfi_pri_amdstd *extp;
+
+ extp = (struct cfi_pri_amdstd*)cfi_read_pri(map, adr, sizeof(*extp), "Amd/Fujitsu");
+ if (!extp) {
+ kfree(mtd);
+ return NULL;
+ }
+
+ /* Install our own private info structure */
+ cfi->cmdset_priv = extp;
+
+ /* Apply cfi device specific fixups */
+ cfi_fixup(mtd, cfi_fixup_table);
+
+#ifdef DEBUG_CFI_FEATURES
+ /* Tell the user about it in lots of lovely detail */
+ cfi_tell_features(extp);
+#endif
+
+ bootloc = extp->TopBottom;
+ if ((bootloc != 2) && (bootloc != 3)) {
+ printk(KERN_WARNING "%s: CFI does not contain boot "
+ "bank location. Assuming top.\n", map->name);
+ bootloc = 2;
+ }
+
+ if (bootloc == 3 && cfi->cfiq->NumEraseRegions > 1) {
+ printk(KERN_WARNING "%s: Swapping erase regions for broken CFI table.\n", map->name);
+
+ for (i=0; i<cfi->cfiq->NumEraseRegions / 2; i++) {
+ int j = (cfi->cfiq->NumEraseRegions-1)-i;
+ __u32 swap;
+
+ swap = cfi->cfiq->EraseRegionInfo[i];
+ cfi->cfiq->EraseRegionInfo[i] = cfi->cfiq->EraseRegionInfo[j];
+ cfi->cfiq->EraseRegionInfo[j] = swap;
+ }
+ }
+ /* Set the default CFI lock/unlock addresses */
+ cfi->addr_unlock1 = 0x555;
+ cfi->addr_unlock2 = 0x2aa;
+ /* Modify the unlock address if we are in compatibility mode */
+ if ( /* x16 in x8 mode */
+ ((cfi->device_type == CFI_DEVICETYPE_X8) &&
+ (cfi->cfiq->InterfaceDesc == 2)) ||
+ /* x32 in x16 mode */
+ ((cfi->device_type == CFI_DEVICETYPE_X16) &&
+ (cfi->cfiq->InterfaceDesc == 4)))
+ {
+ cfi->addr_unlock1 = 0xaaa;
+ cfi->addr_unlock2 = 0x555;
+ }
+
+ } /* CFI mode */
+ else if (cfi->cfi_mode == CFI_MODE_JEDEC) {
+ /* Apply jedec specific fixups */
+ cfi_fixup(mtd, jedec_fixup_table);
+ }
+ /* Apply generic fixups */
+ cfi_fixup(mtd, fixup_table);
+
+ for (i=0; i< cfi->numchips; i++) {
+ cfi->chips[i].word_write_time = 1<<cfi->cfiq->WordWriteTimeoutTyp;
+ cfi->chips[i].buffer_write_time = 1<<cfi->cfiq->BufWriteTimeoutTyp;
+ cfi->chips[i].erase_time = 1<<cfi->cfiq->BlockEraseTimeoutTyp;
+ }
+
+ map->fldrv = &cfi_amdstd_chipdrv;
+
+ return cfi_amdstd_setup(mtd);
+}
+
+
+static struct mtd_info *cfi_amdstd_setup(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long devsize = (1<<cfi->cfiq->DevSize) * cfi->interleave;
+ unsigned long offset = 0;
+ int i,j;
+
+ printk(KERN_NOTICE "number of %s chips: %d\n",
+ (cfi->cfi_mode == CFI_MODE_CFI)?"CFI":"JEDEC",cfi->numchips);
+ /* Select the correct geometry setup */
+ mtd->size = devsize * cfi->numchips;
+
+ mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips;
+ mtd->eraseregions = kmalloc(sizeof(struct mtd_erase_region_info)
+ * mtd->numeraseregions, GFP_KERNEL);
+ if (!mtd->eraseregions) {
+ printk(KERN_WARNING "Failed to allocate memory for MTD erase region info\n");
+ goto setup_err;
+ }
+
+ for (i=0; i<cfi->cfiq->NumEraseRegions; i++) {
+ unsigned long ernum, ersize;
+ ersize = ((cfi->cfiq->EraseRegionInfo[i] >> 8) & ~0xff) * cfi->interleave;
+ ernum = (cfi->cfiq->EraseRegionInfo[i] & 0xffff) + 1;
+
+ if (mtd->erasesize < ersize) {
+ mtd->erasesize = ersize;
+ }
+ for (j=0; j<cfi->numchips; j++) {
+ mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].offset = (j*devsize)+offset;
+ mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].erasesize = ersize;
+ mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].numblocks = ernum;
+ }
+ offset += (ersize * ernum);
+ }
+ if (offset != devsize) {
+ /* Argh */
+ printk(KERN_WARNING "Sum of regions (%lx) != total size of set of interleaved chips (%lx)\n", offset, devsize);
+ goto setup_err;
+ }
+#if 0
+ // debug
+ for (i=0; i<mtd->numeraseregions;i++){
+ printk("%d: offset=0x%x,size=0x%x,blocks=%d\n",
+ i,mtd->eraseregions[i].offset,
+ mtd->eraseregions[i].erasesize,
+ mtd->eraseregions[i].numblocks);
+ }
+#endif
+
+ /* FIXME: erase-suspend-program is broken. See
+ http://lists.infradead.org/pipermail/linux-mtd/2003-December/009001.html */
+ printk(KERN_NOTICE "cfi_cmdset_0002: Disabling erase-suspend-program due to code brokenness.\n");
+
+ __module_get(THIS_MODULE);
+ return mtd;
+
+ setup_err:
+ if(mtd) {
+ if(mtd->eraseregions)
+ kfree(mtd->eraseregions);
+ kfree(mtd);
+ }
+ kfree(cfi->cmdset_priv);
+ kfree(cfi->cfiq);
+ return NULL;
+}
+
+/*
+ * Return true if the chip is ready.
+ *
+ * Ready is one of: read mode, query mode, erase-suspend-read mode (in any
+ * non-suspended sector) and is indicated by no toggle bits toggling.
+ *
+ * Note that anything more complicated than checking if no bits are toggling
+ * (including checking DQ5 for an error status) is tricky to get working
+ * correctly and is therefore not done (particulary with interleaved chips
+ * as each chip must be checked independantly of the others).
+ */
+static int chip_ready(struct map_info *map, unsigned long addr)
+{
+ map_word d, t;
+
+ d = map_read(map, addr);
+ t = map_read(map, addr);
+
+ return map_word_equal(map, d, t);
+}
+
+static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long timeo;
+ struct cfi_pri_amdstd *cfip = (struct cfi_pri_amdstd *)cfi->cmdset_priv;
+
+ resettime:
+ timeo = jiffies + HZ;
+ retry:
+ switch (chip->state) {
+
+ case FL_STATUS:
+ for (;;) {
+ if (chip_ready(map, adr))
+ break;
+
+ if (time_after(jiffies, timeo)) {
+ printk(KERN_ERR "Waiting for chip to be ready timed out.\n");
+ cfi_spin_unlock(chip->mutex);
+ return -EIO;
+ }
+ cfi_spin_unlock(chip->mutex);
+ cfi_udelay(1);
+ cfi_spin_lock(chip->mutex);
+ /* Someone else might have been playing with it. */
+ goto retry;
+ }
+
+ case FL_READY:
+ case FL_CFI_QUERY:
+ case FL_JEDEC_QUERY:
+ return 0;
+
+ case FL_ERASING:
+ if (mode == FL_WRITING) /* FIXME: Erase-suspend-program appears broken. */
+ goto sleep;
+
+ if (!(mode == FL_READY || mode == FL_POINT
+ || !cfip
+ || (mode == FL_WRITING && (cfip->EraseSuspend & 0x2))
+ || (mode == FL_WRITING && (cfip->EraseSuspend & 0x1))))
+ goto sleep;
+
+ /* We could check to see if we're trying to access the sector
+ * that is currently being erased. However, no user will try
+ * anything like that so we just wait for the timeout. */
+
+ /* Erase suspend */
+ /* It's harmless to issue the Erase-Suspend and Erase-Resume
+ * commands when the erase algorithm isn't in progress. */
+ map_write(map, CMD(0xB0), chip->in_progress_block_addr);
+ chip->oldstate = FL_ERASING;
+ chip->state = FL_ERASE_SUSPENDING;
+ chip->erase_suspended = 1;
+ for (;;) {
+ if (chip_ready(map, adr))
+ break;
+
+ if (time_after(jiffies, timeo)) {
+ /* Should have suspended the erase by now.
+ * Send an Erase-Resume command as either
+ * there was an error (so leave the erase
+ * routine to recover from it) or we trying to
+ * use the erase-in-progress sector. */
+ map_write(map, CMD(0x30), chip->in_progress_block_addr);
+ chip->state = FL_ERASING;
+ chip->oldstate = FL_READY;
+ printk(KERN_ERR "MTD %s(): chip not ready after erase suspend\n", __func__);
+ return -EIO;
+ }
+
+ cfi_spin_unlock(chip->mutex);
+ cfi_udelay(1);
+ cfi_spin_lock(chip->mutex);
+ /* Nobody will touch it while it's in state FL_ERASE_SUSPENDING.
+ So we can just loop here. */
+ }
+ chip->state = FL_READY;
+ return 0;
+
+ case FL_POINT:
+ /* Only if there's no operation suspended... */
+ if (mode == FL_READY && chip->oldstate == FL_READY)
+ return 0;
+
+ default:
+ sleep:
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ cfi_spin_unlock(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ cfi_spin_lock(chip->mutex);
+ goto resettime;
+ }
+}
+
+
+static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+
+ switch(chip->oldstate) {
+ case FL_ERASING:
+ chip->state = chip->oldstate;
+ map_write(map, CMD(0x30), chip->in_progress_block_addr);
+ chip->oldstate = FL_READY;
+ chip->state = FL_ERASING;
+ break;
+
+ case FL_READY:
+ case FL_STATUS:
+ /* We should really make set_vpp() count, rather than doing this */
+ DISABLE_VPP(map);
+ break;
+ default:
+ printk(KERN_ERR "MTD: put_chip() called with oldstate %d!!\n", chip->oldstate);
+ }
+ wake_up(&chip->wq);
+}
+
+
+static inline int do_read_onechip(struct map_info *map, struct flchip *chip, loff_t adr, size_t len, u_char *buf)
+{
+ unsigned long cmd_addr;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int ret;
+
+ adr += chip->start;
+
+ /* Ensure cmd read/writes are aligned. */
+ cmd_addr = adr & ~(map_bankwidth(map)-1);
+
+ cfi_spin_lock(chip->mutex);
+ ret = get_chip(map, chip, cmd_addr, FL_READY);
+ if (ret) {
+ cfi_spin_unlock(chip->mutex);
+ return ret;
+ }
+
+ if (chip->state != FL_POINT && chip->state != FL_READY) {
+ map_write(map, CMD(0xf0), cmd_addr);
+ chip->state = FL_READY;
+ }
+
+ map_copy_from(map, buf, adr, len);
+
+ put_chip(map, chip, cmd_addr);
+
+ cfi_spin_unlock(chip->mutex);
+ return 0;
+}
+
+
+static int cfi_amdstd_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long ofs;
+ int chipnum;
+ int ret = 0;
+
+ /* ofs: offset within the first chip that the first read should start */
+
+ chipnum = (from >> cfi->chipshift);
+ ofs = from - (chipnum << cfi->chipshift);
+
+
+ *retlen = 0;
+
+ while (len) {
+ unsigned long thislen;
+
+ if (chipnum >= cfi->numchips)
+ break;
+
+ if ((len + ofs -1) >> cfi->chipshift)
+ thislen = (1<<cfi->chipshift) - ofs;
+ else
+ thislen = len;
+
+ ret = do_read_onechip(map, &cfi->chips[chipnum], ofs, thislen, buf);
+ if (ret)
+ break;
+
+ *retlen += thislen;
+ len -= thislen;
+ buf += thislen;
+
+ ofs = 0;
+ chipnum++;
+ }
+ return ret;
+}
+
+
+static inline int do_read_secsi_onechip(struct map_info *map, struct flchip *chip, loff_t adr, size_t len, u_char *buf)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long timeo = jiffies + HZ;
+ struct cfi_private *cfi = map->fldrv_priv;
+
+ retry:
+ cfi_spin_lock(chip->mutex);
+
+ if (chip->state != FL_READY){
+#if 0
+ printk(KERN_DEBUG "Waiting for chip to read, status = %d\n", chip->state);
+#endif
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+
+ cfi_spin_unlock(chip->mutex);
+
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+#if 0
+ if(signal_pending(current))
+ return -EINTR;
+#endif
+ timeo = jiffies + HZ;
+
+ goto retry;
+ }
+
+ adr += chip->start;
+
+ chip->state = FL_READY;
+
+ cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x88, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+
+ map_copy_from(map, buf, adr, len);
+
+ cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x90, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x00, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+
+ wake_up(&chip->wq);
+ cfi_spin_unlock(chip->mutex);
+
+ return 0;
+}
+
+static int cfi_amdstd_secsi_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long ofs;
+ int chipnum;
+ int ret = 0;
+
+
+ /* ofs: offset within the first chip that the first read should start */
+
+ /* 8 secsi bytes per chip */
+ chipnum=from>>3;
+ ofs=from & 7;
+
+
+ *retlen = 0;
+
+ while (len) {
+ unsigned long thislen;
+
+ if (chipnum >= cfi->numchips)
+ break;
+
+ if ((len + ofs -1) >> 3)
+ thislen = (1<<3) - ofs;
+ else
+ thislen = len;
+
+ ret = do_read_secsi_onechip(map, &cfi->chips[chipnum], ofs, thislen, buf);
+ if (ret)
+ break;
+
+ *retlen += thislen;
+ len -= thislen;
+ buf += thislen;
+
+ ofs = 0;
+ chipnum++;
+ }
+ return ret;
+}
+
+
+static int do_write_oneword(struct map_info *map, struct flchip *chip, unsigned long adr, map_word datum)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long timeo = jiffies + HZ;
+ /*
+ * We use a 1ms + 1 jiffies generic timeout for writes (most devices
+ * have a max write time of a few hundreds usec). However, we should
+ * use the maximum timeout value given by the chip at probe time
+ * instead. Unfortunately, struct flchip does have a field for
+ * maximum timeout, only for typical which can be far too short
+ * depending of the conditions. The ' + 1' is to avoid having a
+ * timeout of 0 jiffies if HZ is smaller than 1000.
+ */
+ unsigned long uWriteTimeout = ( HZ / 1000 ) + 1;
+ int ret = 0;
+ map_word oldd;
+ int retry_cnt = 0;
+
+ adr += chip->start;
+
+ cfi_spin_lock(chip->mutex);
+ ret = get_chip(map, chip, adr, FL_WRITING);
+ if (ret) {
+ cfi_spin_unlock(chip->mutex);
+ return ret;
+ }
+
+ DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): WRITE 0x%.8lx(0x%.8lx)\n",
+ __func__, adr, datum.x[0] );
+
+ /*
+ * Check for a NOP for the case when the datum to write is already
+ * present - it saves time and works around buggy chips that corrupt
+ * data at other locations when 0xff is written to a location that
+ * already contains 0xff.
+ */
+ oldd = map_read(map, adr);
+ if (map_word_equal(map, oldd, datum)) {
+ DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): NOP\n",
+ __func__);
+ goto op_done;
+ }
+
+ ENABLE_VPP(map);
+ retry:
+ cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0xA0, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+ map_write(map, datum, adr);
+ chip->state = FL_WRITING;
+
+ cfi_spin_unlock(chip->mutex);
+ cfi_udelay(chip->word_write_time);
+ cfi_spin_lock(chip->mutex);
+
+ /* See comment above for timeout value. */
+ timeo = jiffies + uWriteTimeout;
+ for (;;) {
+ if (chip->state != FL_WRITING) {
+ /* Someone's suspended the write. Sleep */
+ DECLARE_WAITQUEUE(wait, current);
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ cfi_spin_unlock(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ timeo = jiffies + (HZ / 2); /* FIXME */
+ cfi_spin_lock(chip->mutex);
+ continue;
+ }
+
+ if (chip_ready(map, adr))
+ goto op_done;
+
+ if (time_after(jiffies, timeo))
+ break;
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ cfi_spin_unlock(chip->mutex);
+ cfi_udelay(1);
+ cfi_spin_lock(chip->mutex);
+ }
+
+ printk(KERN_WARNING "MTD %s(): software timeout\n", __func__);
+
+ /* reset on all failures. */
+ map_write( map, CMD(0xF0), chip->start );
+ /* FIXME - should have reset delay before continuing */
+ if (++retry_cnt <= MAX_WORD_RETRIES)
+ goto retry;
+
+ ret = -EIO;
+ op_done:
+ chip->state = FL_READY;
+ put_chip(map, chip, adr);
+ cfi_spin_unlock(chip->mutex);
+
+ return ret;
+}
+
+
+static int cfi_amdstd_write_words(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int ret = 0;
+ int chipnum;
+ unsigned long ofs, chipstart;
+ DECLARE_WAITQUEUE(wait, current);
+
+ *retlen = 0;
+ if (!len)
+ return 0;
+
+ chipnum = to >> cfi->chipshift;
+ ofs = to - (chipnum << cfi->chipshift);
+ chipstart = cfi->chips[chipnum].start;
+
+ /* If it's not bus-aligned, do the first byte write */
+ if (ofs & (map_bankwidth(map)-1)) {
+ unsigned long bus_ofs = ofs & ~(map_bankwidth(map)-1);
+ int i = ofs - bus_ofs;
+ int n = 0;
+ map_word tmp_buf;
+
+ retry:
+ cfi_spin_lock(cfi->chips[chipnum].mutex);
+
+ if (cfi->chips[chipnum].state != FL_READY) {
+#if 0
+ printk(KERN_DEBUG "Waiting for chip to write, status = %d\n", cfi->chips[chipnum].state);
+#endif
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&cfi->chips[chipnum].wq, &wait);
+
+ cfi_spin_unlock(cfi->chips[chipnum].mutex);
+
+ schedule();
+ remove_wait_queue(&cfi->chips[chipnum].wq, &wait);
+#if 0
+ if(signal_pending(current))
+ return -EINTR;
+#endif
+ goto retry;
+ }
+
+ /* Load 'tmp_buf' with old contents of flash */
+ tmp_buf = map_read(map, bus_ofs+chipstart);
+
+ cfi_spin_unlock(cfi->chips[chipnum].mutex);
+
+ /* Number of bytes to copy from buffer */
+ n = min_t(int, len, map_bankwidth(map)-i);
+
+ tmp_buf = map_word_load_partial(map, tmp_buf, buf, i, n);
+
+ ret = do_write_oneword(map, &cfi->chips[chipnum],
+ bus_ofs, tmp_buf);
+ if (ret)
+ return ret;
+
+ ofs += n;
+ buf += n;
+ (*retlen) += n;
+ len -= n;
+
+ if (ofs >> cfi->chipshift) {
+ chipnum ++;
+ ofs = 0;
+ if (chipnum == cfi->numchips)
+ return 0;
+ }
+ }
+
+ /* We are now aligned, write as much as possible */
+ while(len >= map_bankwidth(map)) {
+ map_word datum;
+
+ datum = map_word_load(map, buf);
+
+ ret = do_write_oneword(map, &cfi->chips[chipnum],
+ ofs, datum);
+ if (ret)
+ return ret;
+
+ ofs += map_bankwidth(map);
+ buf += map_bankwidth(map);
+ (*retlen) += map_bankwidth(map);
+ len -= map_bankwidth(map);
+
+ if (ofs >> cfi->chipshift) {
+ chipnum ++;
+ ofs = 0;
+ if (chipnum == cfi->numchips)
+ return 0;
+ chipstart = cfi->chips[chipnum].start;
+ }
+ }
+
+ /* Write the trailing bytes if any */
+ if (len & (map_bankwidth(map)-1)) {
+ map_word tmp_buf;
+
+ retry1:
+ cfi_spin_lock(cfi->chips[chipnum].mutex);
+
+ if (cfi->chips[chipnum].state != FL_READY) {
+#if 0
+ printk(KERN_DEBUG "Waiting for chip to write, status = %d\n", cfi->chips[chipnum].state);
+#endif
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&cfi->chips[chipnum].wq, &wait);
+
+ cfi_spin_unlock(cfi->chips[chipnum].mutex);
+
+ schedule();
+ remove_wait_queue(&cfi->chips[chipnum].wq, &wait);
+#if 0
+ if(signal_pending(current))
+ return -EINTR;
+#endif
+ goto retry1;
+ }
+
+ tmp_buf = map_read(map, ofs + chipstart);
+
+ cfi_spin_unlock(cfi->chips[chipnum].mutex);
+
+ tmp_buf = map_word_load_partial(map, tmp_buf, buf, 0, len);
+
+ ret = do_write_oneword(map, &cfi->chips[chipnum],
+ ofs, tmp_buf);
+ if (ret)
+ return ret;
+
+ (*retlen) += len;
+ }
+
+ return 0;
+}
+
+
+/*
+ * FIXME: interleaved mode not tested, and probably not supported!
+ */
+static inline int do_write_buffer(struct map_info *map, struct flchip *chip,
+ unsigned long adr, const u_char *buf, int len)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long timeo = jiffies + HZ;
+ /* see comments in do_write_oneword() regarding uWriteTimeo. */
+ unsigned long uWriteTimeout = ( HZ / 1000 ) + 1;
+ int ret = -EIO;
+ unsigned long cmd_adr;
+ int z, words;
+ map_word datum;
+
+ adr += chip->start;
+ cmd_adr = adr;
+
+ cfi_spin_lock(chip->mutex);
+ ret = get_chip(map, chip, adr, FL_WRITING);
+ if (ret) {
+ cfi_spin_unlock(chip->mutex);
+ return ret;
+ }
+
+ datum = map_word_load(map, buf);
+
+ DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): WRITE 0x%.8lx(0x%.8lx)\n",
+ __func__, adr, datum.x[0] );
+
+ ENABLE_VPP(map);
+ cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL);
+ //cfi_send_gen_cmd(0xA0, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+
+ /* Write Buffer Load */
+ map_write(map, CMD(0x25), cmd_adr);
+
+ chip->state = FL_WRITING_TO_BUFFER;
+
+ /* Write length of data to come */
+ words = len / map_bankwidth(map);
+ map_write(map, CMD(words - 1), cmd_adr);
+ /* Write data */
+ z = 0;
+ while(z < words * map_bankwidth(map)) {
+ datum = map_word_load(map, buf);
+ map_write(map, datum, adr + z);
+
+ z += map_bankwidth(map);
+ buf += map_bankwidth(map);
+ }
+ z -= map_bankwidth(map);
+
+ adr += z;
+
+ /* Write Buffer Program Confirm: GO GO GO */
+ map_write(map, CMD(0x29), cmd_adr);
+ chip->state = FL_WRITING;
+
+ cfi_spin_unlock(chip->mutex);
+ cfi_udelay(chip->buffer_write_time);
+ cfi_spin_lock(chip->mutex);
+
+ timeo = jiffies + uWriteTimeout;
+
+ for (;;) {
+ if (chip->state != FL_WRITING) {
+ /* Someone's suspended the write. Sleep */
+ DECLARE_WAITQUEUE(wait, current);
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ cfi_spin_unlock(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ timeo = jiffies + (HZ / 2); /* FIXME */
+ cfi_spin_lock(chip->mutex);
+ continue;
+ }
+
+ if (chip_ready(map, adr))
+ goto op_done;
+
+ if( time_after(jiffies, timeo))
+ break;
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ cfi_spin_unlock(chip->mutex);
+ cfi_udelay(1);
+ cfi_spin_lock(chip->mutex);
+ }
+
+ printk(KERN_WARNING "MTD %s(): software timeout\n",
+ __func__ );
+
+ /* reset on all failures. */
+ map_write( map, CMD(0xF0), chip->start );
+ /* FIXME - should have reset delay before continuing */
+
+ ret = -EIO;
+ op_done:
+ chip->state = FL_READY;
+ put_chip(map, chip, adr);
+ cfi_spin_unlock(chip->mutex);
+
+ return ret;
+}
+
+
+static int cfi_amdstd_write_buffers(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize;
+ int ret = 0;
+ int chipnum;
+ unsigned long ofs;
+
+ *retlen = 0;
+ if (!len)
+ return 0;
+
+ chipnum = to >> cfi->chipshift;
+ ofs = to - (chipnum << cfi->chipshift);
+
+ /* If it's not bus-aligned, do the first word write */
+ if (ofs & (map_bankwidth(map)-1)) {
+ size_t local_len = (-ofs)&(map_bankwidth(map)-1);
+ if (local_len > len)
+ local_len = len;
+ ret = cfi_amdstd_write_words(mtd, ofs + (chipnum<<cfi->chipshift),
+ local_len, retlen, buf);
+ if (ret)
+ return ret;
+ ofs += local_len;
+ buf += local_len;
+ len -= local_len;
+
+ if (ofs >> cfi->chipshift) {
+ chipnum ++;
+ ofs = 0;
+ if (chipnum == cfi->numchips)
+ return 0;
+ }
+ }
+
+ /* Write buffer is worth it only if more than one word to write... */
+ while (len >= map_bankwidth(map) * 2) {
+ /* We must not cross write block boundaries */
+ int size = wbufsize - (ofs & (wbufsize-1));
+
+ if (size > len)
+ size = len;
+ if (size % map_bankwidth(map))
+ size -= size % map_bankwidth(map);
+
+ ret = do_write_buffer(map, &cfi->chips[chipnum],
+ ofs, buf, size);
+ if (ret)
+ return ret;
+
+ ofs += size;
+ buf += size;
+ (*retlen) += size;
+ len -= size;
+
+ if (ofs >> cfi->chipshift) {
+ chipnum ++;
+ ofs = 0;
+ if (chipnum == cfi->numchips)
+ return 0;
+ }
+ }
+
+ if (len) {
+ size_t retlen_dregs = 0;
+
+ ret = cfi_amdstd_write_words(mtd, ofs + (chipnum<<cfi->chipshift),
+ len, &retlen_dregs, buf);
+
+ *retlen += retlen_dregs;
+ return ret;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Handle devices with one erase region, that only implement
+ * the chip erase command.
+ */
+static inline int do_erase_chip(struct map_info *map, struct flchip *chip)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long timeo = jiffies + HZ;
+ unsigned long int adr;
+ DECLARE_WAITQUEUE(wait, current);
+ int ret = 0;
+
+ adr = cfi->addr_unlock1;
+
+ cfi_spin_lock(chip->mutex);
+ ret = get_chip(map, chip, adr, FL_WRITING);
+ if (ret) {
+ cfi_spin_unlock(chip->mutex);
+ return ret;
+ }
+
+ DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): ERASE 0x%.8lx\n",
+ __func__, chip->start );
+
+ ENABLE_VPP(map);
+ cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x80, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x10, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+
+ chip->state = FL_ERASING;
+ chip->erase_suspended = 0;
+ chip->in_progress_block_addr = adr;
+
+ cfi_spin_unlock(chip->mutex);
+ msleep(chip->erase_time/2);
+ cfi_spin_lock(chip->mutex);
+
+ timeo = jiffies + (HZ*20);
+
+ for (;;) {
+ if (chip->state != FL_ERASING) {
+ /* Someone's suspended the erase. Sleep */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ cfi_spin_unlock(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ cfi_spin_lock(chip->mutex);
+ continue;
+ }
+ if (chip->erase_suspended) {
+ /* This erase was suspended and resumed.
+ Adjust the timeout */
+ timeo = jiffies + (HZ*20); /* FIXME */
+ chip->erase_suspended = 0;
+ }
+
+ if (chip_ready(map, adr))
+ goto op_done;
+
+ if (time_after(jiffies, timeo))
+ break;
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ cfi_spin_unlock(chip->mutex);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ cfi_spin_lock(chip->mutex);
+ }
+
+ printk(KERN_WARNING "MTD %s(): software timeout\n",
+ __func__ );
+
+ /* reset on all failures. */
+ map_write( map, CMD(0xF0), chip->start );
+ /* FIXME - should have reset delay before continuing */
+
+ ret = -EIO;
+ op_done:
+ chip->state = FL_READY;
+ put_chip(map, chip, adr);
+ cfi_spin_unlock(chip->mutex);
+
+ return ret;
+}
+
+
+static inline int do_erase_oneblock(struct map_info *map, struct flchip *chip, unsigned long adr, int len, void *thunk)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long timeo = jiffies + HZ;
+ DECLARE_WAITQUEUE(wait, current);
+ int ret = 0;
+
+ adr += chip->start;
+
+ cfi_spin_lock(chip->mutex);
+ ret = get_chip(map, chip, adr, FL_ERASING);
+ if (ret) {
+ cfi_spin_unlock(chip->mutex);
+ return ret;
+ }
+
+ DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): ERASE 0x%.8lx\n",
+ __func__, adr );
+
+ ENABLE_VPP(map);
+ cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x80, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL);
+ map_write(map, CMD(0x30), adr);
+
+ chip->state = FL_ERASING;
+ chip->erase_suspended = 0;
+ chip->in_progress_block_addr = adr;
+
+ cfi_spin_unlock(chip->mutex);
+ msleep(chip->erase_time/2);
+ cfi_spin_lock(chip->mutex);
+
+ timeo = jiffies + (HZ*20);
+
+ for (;;) {
+ if (chip->state != FL_ERASING) {
+ /* Someone's suspended the erase. Sleep */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ cfi_spin_unlock(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ cfi_spin_lock(chip->mutex);
+ continue;
+ }
+ if (chip->erase_suspended) {
+ /* This erase was suspended and resumed.
+ Adjust the timeout */
+ timeo = jiffies + (HZ*20); /* FIXME */
+ chip->erase_suspended = 0;
+ }
+
+ if (chip_ready(map, adr))
+ goto op_done;
+
+ if (time_after(jiffies, timeo))
+ break;
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ cfi_spin_unlock(chip->mutex);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(1);
+ cfi_spin_lock(chip->mutex);
+ }
+
+ printk(KERN_WARNING "MTD %s(): software timeout\n",
+ __func__ );
+
+ /* reset on all failures. */
+ map_write( map, CMD(0xF0), chip->start );
+ /* FIXME - should have reset delay before continuing */
+
+ ret = -EIO;
+ op_done:
+ chip->state = FL_READY;
+ put_chip(map, chip, adr);
+ cfi_spin_unlock(chip->mutex);
+ return ret;
+}
+
+
+int cfi_amdstd_erase_varsize(struct mtd_info *mtd, struct erase_info *instr)
+{
+ unsigned long ofs, len;
+ int ret;
+
+ ofs = instr->addr;
+ len = instr->len;
+
+ ret = cfi_varsize_frob(mtd, do_erase_oneblock, ofs, len, NULL);
+ if (ret)
+ return ret;
+
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+
+ return 0;
+}
+
+
+static int cfi_amdstd_erase_chip(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int ret = 0;
+
+ if (instr->addr != 0)
+ return -EINVAL;
+
+ if (instr->len != mtd->size)
+ return -EINVAL;
+
+ ret = do_erase_chip(map, &cfi->chips[0]);
+ if (ret)
+ return ret;
+
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+
+ return 0;
+}
+
+
+static void cfi_amdstd_sync (struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int i;
+ struct flchip *chip;
+ int ret = 0;
+ DECLARE_WAITQUEUE(wait, current);
+
+ for (i=0; !ret && i<cfi->numchips; i++) {
+ chip = &cfi->chips[i];
+
+ retry:
+ cfi_spin_lock(chip->mutex);
+
+ switch(chip->state) {
+ case FL_READY:
+ case FL_STATUS:
+ case FL_CFI_QUERY:
+ case FL_JEDEC_QUERY:
+ chip->oldstate = chip->state;
+ chip->state = FL_SYNCING;
+ /* No need to wake_up() on this state change -
+ * as the whole point is that nobody can do anything
+ * with the chip now anyway.
+ */
+ case FL_SYNCING:
+ cfi_spin_unlock(chip->mutex);
+ break;
+
+ default:
+ /* Not an idle state */
+ add_wait_queue(&chip->wq, &wait);
+
+ cfi_spin_unlock(chip->mutex);
+
+ schedule();
+
+ remove_wait_queue(&chip->wq, &wait);
+
+ goto retry;
+ }
+ }
+
+ /* Unlock the chips again */
+
+ for (i--; i >=0; i--) {
+ chip = &cfi->chips[i];
+
+ cfi_spin_lock(chip->mutex);
+
+ if (chip->state == FL_SYNCING) {
+ chip->state = chip->oldstate;
+ wake_up(&chip->wq);
+ }
+ cfi_spin_unlock(chip->mutex);
+ }
+}
+
+
+static int cfi_amdstd_suspend(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int i;
+ struct flchip *chip;
+ int ret = 0;
+
+ for (i=0; !ret && i<cfi->numchips; i++) {
+ chip = &cfi->chips[i];
+
+ cfi_spin_lock(chip->mutex);
+
+ switch(chip->state) {
+ case FL_READY:
+ case FL_STATUS:
+ case FL_CFI_QUERY:
+ case FL_JEDEC_QUERY:
+ chip->oldstate = chip->state;
+ chip->state = FL_PM_SUSPENDED;
+ /* No need to wake_up() on this state change -
+ * as the whole point is that nobody can do anything
+ * with the chip now anyway.
+ */
+ case FL_PM_SUSPENDED:
+ break;
+
+ default:
+ ret = -EAGAIN;
+ break;
+ }
+ cfi_spin_unlock(chip->mutex);
+ }
+
+ /* Unlock the chips again */
+
+ if (ret) {
+ for (i--; i >=0; i--) {
+ chip = &cfi->chips[i];
+
+ cfi_spin_lock(chip->mutex);
+
+ if (chip->state == FL_PM_SUSPENDED) {
+ chip->state = chip->oldstate;
+ wake_up(&chip->wq);
+ }
+ cfi_spin_unlock(chip->mutex);
+ }
+ }
+
+ return ret;
+}
+
+
+static void cfi_amdstd_resume(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int i;
+ struct flchip *chip;
+
+ for (i=0; i<cfi->numchips; i++) {
+
+ chip = &cfi->chips[i];
+
+ cfi_spin_lock(chip->mutex);
+
+ if (chip->state == FL_PM_SUSPENDED) {
+ chip->state = FL_READY;
+ map_write(map, CMD(0xF0), chip->start);
+ wake_up(&chip->wq);
+ }
+ else
+ printk(KERN_ERR "Argh. Chip not in PM_SUSPENDED state upon resume()\n");
+
+ cfi_spin_unlock(chip->mutex);
+ }
+}
+
+static void cfi_amdstd_destroy(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ kfree(cfi->cmdset_priv);
+ kfree(cfi->cfiq);
+ kfree(cfi);
+ kfree(mtd->eraseregions);
+}
+
+static char im_name[]="cfi_cmdset_0002";
+
+
+static int __init cfi_amdstd_init(void)
+{
+ inter_module_register(im_name, THIS_MODULE, &cfi_cmdset_0002);
+ return 0;
+}
+
+
+static void __exit cfi_amdstd_exit(void)
+{
+ inter_module_unregister(im_name);
+}
+
+
+module_init(cfi_amdstd_init);
+module_exit(cfi_amdstd_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Crossnet Co. <info@crossnet.co.jp> et al.");
+MODULE_DESCRIPTION("MTD chip driver for AMD/Fujitsu flash chips");
diff --git a/drivers/mtd/chips/cfi_cmdset_0020.c b/drivers/mtd/chips/cfi_cmdset_0020.c
new file mode 100644
index 00000000000..8c24e18db3b
--- /dev/null
+++ b/drivers/mtd/chips/cfi_cmdset_0020.c
@@ -0,0 +1,1418 @@
+/*
+ * Common Flash Interface support:
+ * ST Advanced Architecture Command Set (ID 0x0020)
+ *
+ * (C) 2000 Red Hat. GPL'd
+ *
+ * $Id: cfi_cmdset_0020.c,v 1.17 2004/11/20 12:49:04 dwmw2 Exp $
+ *
+ * 10/10/2000 Nicolas Pitre <nico@cam.org>
+ * - completely revamped method functions so they are aware and
+ * independent of the flash geometry (buswidth, interleave, etc.)
+ * - scalability vs code size is completely set at compile-time
+ * (see include/linux/mtd/cfi.h for selection)
+ * - optimized write buffer method
+ * 06/21/2002 Joern Engel <joern@wh.fh-wedel.de> and others
+ * - modified Intel Command Set 0x0001 to support ST Advanced Architecture
+ * (command set 0x0020)
+ * - added a writev function
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <asm/byteorder.h>
+
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/cfi.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/compatmac.h>
+
+
+static int cfi_staa_read(struct mtd_info *, loff_t, size_t, size_t *, u_char *);
+static int cfi_staa_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
+static int cfi_staa_writev(struct mtd_info *mtd, const struct kvec *vecs,
+ unsigned long count, loff_t to, size_t *retlen);
+static int cfi_staa_erase_varsize(struct mtd_info *, struct erase_info *);
+static void cfi_staa_sync (struct mtd_info *);
+static int cfi_staa_lock(struct mtd_info *mtd, loff_t ofs, size_t len);
+static int cfi_staa_unlock(struct mtd_info *mtd, loff_t ofs, size_t len);
+static int cfi_staa_suspend (struct mtd_info *);
+static void cfi_staa_resume (struct mtd_info *);
+
+static void cfi_staa_destroy(struct mtd_info *);
+
+struct mtd_info *cfi_cmdset_0020(struct map_info *, int);
+
+static struct mtd_info *cfi_staa_setup (struct map_info *);
+
+static struct mtd_chip_driver cfi_staa_chipdrv = {
+ .probe = NULL, /* Not usable directly */
+ .destroy = cfi_staa_destroy,
+ .name = "cfi_cmdset_0020",
+ .module = THIS_MODULE
+};
+
+/* #define DEBUG_LOCK_BITS */
+//#define DEBUG_CFI_FEATURES
+
+#ifdef DEBUG_CFI_FEATURES
+static void cfi_tell_features(struct cfi_pri_intelext *extp)
+{
+ int i;
+ printk(" Feature/Command Support: %4.4X\n", extp->FeatureSupport);
+ printk(" - Chip Erase: %s\n", extp->FeatureSupport&1?"supported":"unsupported");
+ printk(" - Suspend Erase: %s\n", extp->FeatureSupport&2?"supported":"unsupported");
+ printk(" - Suspend Program: %s\n", extp->FeatureSupport&4?"supported":"unsupported");
+ printk(" - Legacy Lock/Unlock: %s\n", extp->FeatureSupport&8?"supported":"unsupported");
+ printk(" - Queued Erase: %s\n", extp->FeatureSupport&16?"supported":"unsupported");
+ printk(" - Instant block lock: %s\n", extp->FeatureSupport&32?"supported":"unsupported");
+ printk(" - Protection Bits: %s\n", extp->FeatureSupport&64?"supported":"unsupported");
+ printk(" - Page-mode read: %s\n", extp->FeatureSupport&128?"supported":"unsupported");
+ printk(" - Synchronous read: %s\n", extp->FeatureSupport&256?"supported":"unsupported");
+ for (i=9; i<32; i++) {
+ if (extp->FeatureSupport & (1<<i))
+ printk(" - Unknown Bit %X: supported\n", i);
+ }
+
+ printk(" Supported functions after Suspend: %2.2X\n", extp->SuspendCmdSupport);
+ printk(" - Program after Erase Suspend: %s\n", extp->SuspendCmdSupport&1?"supported":"unsupported");
+ for (i=1; i<8; i++) {
+ if (extp->SuspendCmdSupport & (1<<i))
+ printk(" - Unknown Bit %X: supported\n", i);
+ }
+
+ printk(" Block Status Register Mask: %4.4X\n", extp->BlkStatusRegMask);
+ printk(" - Lock Bit Active: %s\n", extp->BlkStatusRegMask&1?"yes":"no");
+ printk(" - Valid Bit Active: %s\n", extp->BlkStatusRegMask&2?"yes":"no");
+ for (i=2; i<16; i++) {
+ if (extp->BlkStatusRegMask & (1<<i))
+ printk(" - Unknown Bit %X Active: yes\n",i);
+ }
+
+ printk(" Vcc Logic Supply Optimum Program/Erase Voltage: %d.%d V\n",
+ extp->VccOptimal >> 8, extp->VccOptimal & 0xf);
+ if (extp->VppOptimal)
+ printk(" Vpp Programming Supply Optimum Program/Erase Voltage: %d.%d V\n",
+ extp->VppOptimal >> 8, extp->VppOptimal & 0xf);
+}
+#endif
+
+/* This routine is made available to other mtd code via
+ * inter_module_register. It must only be accessed through
+ * inter_module_get which will bump the use count of this module. The
+ * addresses passed back in cfi are valid as long as the use count of
+ * this module is non-zero, i.e. between inter_module_get and
+ * inter_module_put. Keith Owens <kaos@ocs.com.au> 29 Oct 2000.
+ */
+struct mtd_info *cfi_cmdset_0020(struct map_info *map, int primary)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ int i;
+
+ if (cfi->cfi_mode) {
+ /*
+ * It's a real CFI chip, not one for which the probe
+ * routine faked a CFI structure. So we read the feature
+ * table from it.
+ */
+ __u16 adr = primary?cfi->cfiq->P_ADR:cfi->cfiq->A_ADR;
+ struct cfi_pri_intelext *extp;
+
+ extp = (struct cfi_pri_intelext*)cfi_read_pri(map, adr, sizeof(*extp), "ST Microelectronics");
+ if (!extp)
+ return NULL;
+
+ /* Do some byteswapping if necessary */
+ extp->FeatureSupport = cfi32_to_cpu(extp->FeatureSupport);
+ extp->BlkStatusRegMask = cfi32_to_cpu(extp->BlkStatusRegMask);
+
+#ifdef DEBUG_CFI_FEATURES
+ /* Tell the user about it in lots of lovely detail */
+ cfi_tell_features(extp);
+#endif
+
+ /* Install our own private info structure */
+ cfi->cmdset_priv = extp;
+ }
+
+ for (i=0; i< cfi->numchips; i++) {
+ cfi->chips[i].word_write_time = 128;
+ cfi->chips[i].buffer_write_time = 128;
+ cfi->chips[i].erase_time = 1024;
+ }
+
+ return cfi_staa_setup(map);
+}
+
+static struct mtd_info *cfi_staa_setup(struct map_info *map)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ struct mtd_info *mtd;
+ unsigned long offset = 0;
+ int i,j;
+ unsigned long devsize = (1<<cfi->cfiq->DevSize) * cfi->interleave;
+
+ mtd = kmalloc(sizeof(*mtd), GFP_KERNEL);
+ //printk(KERN_DEBUG "number of CFI chips: %d\n", cfi->numchips);
+
+ if (!mtd) {
+ printk(KERN_ERR "Failed to allocate memory for MTD device\n");
+ kfree(cfi->cmdset_priv);
+ return NULL;
+ }
+
+ memset(mtd, 0, sizeof(*mtd));
+ mtd->priv = map;
+ mtd->type = MTD_NORFLASH;
+ mtd->size = devsize * cfi->numchips;
+
+ mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips;
+ mtd->eraseregions = kmalloc(sizeof(struct mtd_erase_region_info)
+ * mtd->numeraseregions, GFP_KERNEL);
+ if (!mtd->eraseregions) {
+ printk(KERN_ERR "Failed to allocate memory for MTD erase region info\n");
+ kfree(cfi->cmdset_priv);
+ kfree(mtd);
+ return NULL;
+ }
+
+ for (i=0; i<cfi->cfiq->NumEraseRegions; i++) {
+ unsigned long ernum, ersize;
+ ersize = ((cfi->cfiq->EraseRegionInfo[i] >> 8) & ~0xff) * cfi->interleave;
+ ernum = (cfi->cfiq->EraseRegionInfo[i] & 0xffff) + 1;
+
+ if (mtd->erasesize < ersize) {
+ mtd->erasesize = ersize;
+ }
+ for (j=0; j<cfi->numchips; j++) {
+ mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].offset = (j*devsize)+offset;
+ mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].erasesize = ersize;
+ mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].numblocks = ernum;
+ }
+ offset += (ersize * ernum);
+ }
+
+ if (offset != devsize) {
+ /* Argh */
+ printk(KERN_WARNING "Sum of regions (%lx) != total size of set of interleaved chips (%lx)\n", offset, devsize);
+ kfree(mtd->eraseregions);
+ kfree(cfi->cmdset_priv);
+ kfree(mtd);
+ return NULL;
+ }
+
+ for (i=0; i<mtd->numeraseregions;i++){
+ printk(KERN_DEBUG "%d: offset=0x%x,size=0x%x,blocks=%d\n",
+ i,mtd->eraseregions[i].offset,
+ mtd->eraseregions[i].erasesize,
+ mtd->eraseregions[i].numblocks);
+ }
+
+ /* Also select the correct geometry setup too */
+ mtd->erase = cfi_staa_erase_varsize;
+ mtd->read = cfi_staa_read;
+ mtd->write = cfi_staa_write_buffers;
+ mtd->writev = cfi_staa_writev;
+ mtd->sync = cfi_staa_sync;
+ mtd->lock = cfi_staa_lock;
+ mtd->unlock = cfi_staa_unlock;
+ mtd->suspend = cfi_staa_suspend;
+ mtd->resume = cfi_staa_resume;
+ mtd->flags = MTD_CAP_NORFLASH;
+ mtd->flags |= MTD_ECC; /* FIXME: Not all STMicro flashes have this */
+ mtd->eccsize = 8; /* FIXME: Should be 0 for STMicro flashes w/out ECC */
+ map->fldrv = &cfi_staa_chipdrv;
+ __module_get(THIS_MODULE);
+ mtd->name = map->name;
+ return mtd;
+}
+
+
+static inline int do_read_onechip(struct map_info *map, struct flchip *chip, loff_t adr, size_t len, u_char *buf)
+{
+ map_word status, status_OK;
+ unsigned long timeo;
+ DECLARE_WAITQUEUE(wait, current);
+ int suspended = 0;
+ unsigned long cmd_addr;
+ struct cfi_private *cfi = map->fldrv_priv;
+
+ adr += chip->start;
+
+ /* Ensure cmd read/writes are aligned. */
+ cmd_addr = adr & ~(map_bankwidth(map)-1);
+
+ /* Let's determine this according to the interleave only once */
+ status_OK = CMD(0x80);
+
+ timeo = jiffies + HZ;
+ retry:
+ spin_lock_bh(chip->mutex);
+
+ /* Check that the chip's ready to talk to us.
+ * If it's in FL_ERASING state, suspend it and make it talk now.
+ */
+ switch (chip->state) {
+ case FL_ERASING:
+ if (!(((struct cfi_pri_intelext *)cfi->cmdset_priv)->FeatureSupport & 2))
+ goto sleep; /* We don't support erase suspend */
+
+ map_write (map, CMD(0xb0), cmd_addr);
+ /* If the flash has finished erasing, then 'erase suspend'
+ * appears to make some (28F320) flash devices switch to
+ * 'read' mode. Make sure that we switch to 'read status'
+ * mode so we get the right data. --rmk
+ */
+ map_write(map, CMD(0x70), cmd_addr);
+ chip->oldstate = FL_ERASING;
+ chip->state = FL_ERASE_SUSPENDING;
+ // printk("Erase suspending at 0x%lx\n", cmd_addr);
+ for (;;) {
+ status = map_read(map, cmd_addr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ if (time_after(jiffies, timeo)) {
+ /* Urgh */
+ map_write(map, CMD(0xd0), cmd_addr);
+ /* make sure we're in 'read status' mode */
+ map_write(map, CMD(0x70), cmd_addr);
+ chip->state = FL_ERASING;
+ spin_unlock_bh(chip->mutex);
+ printk(KERN_ERR "Chip not ready after erase "
+ "suspended: status = 0x%lx\n", status.x[0]);
+ return -EIO;
+ }
+
+ spin_unlock_bh(chip->mutex);
+ cfi_udelay(1);
+ spin_lock_bh(chip->mutex);
+ }
+
+ suspended = 1;
+ map_write(map, CMD(0xff), cmd_addr);
+ chip->state = FL_READY;
+ break;
+
+#if 0
+ case FL_WRITING:
+ /* Not quite yet */
+#endif
+
+ case FL_READY:
+ break;
+
+ case FL_CFI_QUERY:
+ case FL_JEDEC_QUERY:
+ map_write(map, CMD(0x70), cmd_addr);
+ chip->state = FL_STATUS;
+
+ case FL_STATUS:
+ status = map_read(map, cmd_addr);
+ if (map_word_andequal(map, status, status_OK, status_OK)) {
+ map_write(map, CMD(0xff), cmd_addr);
+ chip->state = FL_READY;
+ break;
+ }
+
+ /* Urgh. Chip not yet ready to talk to us. */
+ if (time_after(jiffies, timeo)) {
+ spin_unlock_bh(chip->mutex);
+ printk(KERN_ERR "waiting for chip to be ready timed out in read. WSM status = %lx\n", status.x[0]);
+ return -EIO;
+ }
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ spin_unlock_bh(chip->mutex);
+ cfi_udelay(1);
+ goto retry;
+
+ default:
+ sleep:
+ /* Stick ourselves on a wait queue to be woken when
+ someone changes the status */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ spin_unlock_bh(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ timeo = jiffies + HZ;
+ goto retry;
+ }
+
+ map_copy_from(map, buf, adr, len);
+
+ if (suspended) {
+ chip->state = chip->oldstate;
+ /* What if one interleaved chip has finished and the
+ other hasn't? The old code would leave the finished
+ one in READY mode. That's bad, and caused -EROFS
+ errors to be returned from do_erase_oneblock because
+ that's the only bit it checked for at the time.
+ As the state machine appears to explicitly allow
+ sending the 0x70 (Read Status) command to an erasing
+ chip and expecting it to be ignored, that's what we
+ do. */
+ map_write(map, CMD(0xd0), cmd_addr);
+ map_write(map, CMD(0x70), cmd_addr);
+ }
+
+ wake_up(&chip->wq);
+ spin_unlock_bh(chip->mutex);
+ return 0;
+}
+
+static int cfi_staa_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long ofs;
+ int chipnum;
+ int ret = 0;
+
+ /* ofs: offset within the first chip that the first read should start */
+ chipnum = (from >> cfi->chipshift);
+ ofs = from - (chipnum << cfi->chipshift);
+
+ *retlen = 0;
+
+ while (len) {
+ unsigned long thislen;
+
+ if (chipnum >= cfi->numchips)
+ break;
+
+ if ((len + ofs -1) >> cfi->chipshift)
+ thislen = (1<<cfi->chipshift) - ofs;
+ else
+ thislen = len;
+
+ ret = do_read_onechip(map, &cfi->chips[chipnum], ofs, thislen, buf);
+ if (ret)
+ break;
+
+ *retlen += thislen;
+ len -= thislen;
+ buf += thislen;
+
+ ofs = 0;
+ chipnum++;
+ }
+ return ret;
+}
+
+static inline int do_write_buffer(struct map_info *map, struct flchip *chip,
+ unsigned long adr, const u_char *buf, int len)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ map_word status, status_OK;
+ unsigned long cmd_adr, timeo;
+ DECLARE_WAITQUEUE(wait, current);
+ int wbufsize, z;
+
+ /* M58LW064A requires bus alignment for buffer wriets -- saw */
+ if (adr & (map_bankwidth(map)-1))
+ return -EINVAL;
+
+ wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize;
+ adr += chip->start;
+ cmd_adr = adr & ~(wbufsize-1);
+
+ /* Let's determine this according to the interleave only once */
+ status_OK = CMD(0x80);
+
+ timeo = jiffies + HZ;
+ retry:
+
+#ifdef DEBUG_CFI_FEATURES
+ printk("%s: chip->state[%d]\n", __FUNCTION__, chip->state);
+#endif
+ spin_lock_bh(chip->mutex);
+
+ /* Check that the chip's ready to talk to us.
+ * Later, we can actually think about interrupting it
+ * if it's in FL_ERASING state.
+ * Not just yet, though.
+ */
+ switch (chip->state) {
+ case FL_READY:
+ break;
+
+ case FL_CFI_QUERY:
+ case FL_JEDEC_QUERY:
+ map_write(map, CMD(0x70), cmd_adr);
+ chip->state = FL_STATUS;
+#ifdef DEBUG_CFI_FEATURES
+ printk("%s: 1 status[%x]\n", __FUNCTION__, map_read(map, cmd_adr));
+#endif
+
+ case FL_STATUS:
+ status = map_read(map, cmd_adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+ /* Urgh. Chip not yet ready to talk to us. */
+ if (time_after(jiffies, timeo)) {
+ spin_unlock_bh(chip->mutex);
+ printk(KERN_ERR "waiting for chip to be ready timed out in buffer write Xstatus = %lx, status = %lx\n",
+ status.x[0], map_read(map, cmd_adr).x[0]);
+ return -EIO;
+ }
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ spin_unlock_bh(chip->mutex);
+ cfi_udelay(1);
+ goto retry;
+
+ default:
+ /* Stick ourselves on a wait queue to be woken when
+ someone changes the status */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ spin_unlock_bh(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ timeo = jiffies + HZ;
+ goto retry;
+ }
+
+ ENABLE_VPP(map);
+ map_write(map, CMD(0xe8), cmd_adr);
+ chip->state = FL_WRITING_TO_BUFFER;
+
+ z = 0;
+ for (;;) {
+ status = map_read(map, cmd_adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ spin_unlock_bh(chip->mutex);
+ cfi_udelay(1);
+ spin_lock_bh(chip->mutex);
+
+ if (++z > 100) {
+ /* Argh. Not ready for write to buffer */
+ DISABLE_VPP(map);
+ map_write(map, CMD(0x70), cmd_adr);
+ chip->state = FL_STATUS;
+ spin_unlock_bh(chip->mutex);
+ printk(KERN_ERR "Chip not ready for buffer write. Xstatus = %lx\n", status.x[0]);
+ return -EIO;
+ }
+ }
+
+ /* Write length of data to come */
+ map_write(map, CMD(len/map_bankwidth(map)-1), cmd_adr );
+
+ /* Write data */
+ for (z = 0; z < len;
+ z += map_bankwidth(map), buf += map_bankwidth(map)) {
+ map_word d;
+ d = map_word_load(map, buf);
+ map_write(map, d, adr+z);
+ }
+ /* GO GO GO */
+ map_write(map, CMD(0xd0), cmd_adr);
+ chip->state = FL_WRITING;
+
+ spin_unlock_bh(chip->mutex);
+ cfi_udelay(chip->buffer_write_time);
+ spin_lock_bh(chip->mutex);
+
+ timeo = jiffies + (HZ/2);
+ z = 0;
+ for (;;) {
+ if (chip->state != FL_WRITING) {
+ /* Someone's suspended the write. Sleep */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ spin_unlock_bh(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ timeo = jiffies + (HZ / 2); /* FIXME */
+ spin_lock_bh(chip->mutex);
+ continue;
+ }
+
+ status = map_read(map, cmd_adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ /* OK Still waiting */
+ if (time_after(jiffies, timeo)) {
+ /* clear status */
+ map_write(map, CMD(0x50), cmd_adr);
+ /* put back into read status register mode */
+ map_write(map, CMD(0x70), adr);
+ chip->state = FL_STATUS;
+ DISABLE_VPP(map);
+ spin_unlock_bh(chip->mutex);
+ printk(KERN_ERR "waiting for chip to be ready timed out in bufwrite\n");
+ return -EIO;
+ }
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ spin_unlock_bh(chip->mutex);
+ cfi_udelay(1);
+ z++;
+ spin_lock_bh(chip->mutex);
+ }
+ if (!z) {
+ chip->buffer_write_time--;
+ if (!chip->buffer_write_time)
+ chip->buffer_write_time++;
+ }
+ if (z > 1)
+ chip->buffer_write_time++;
+
+ /* Done and happy. */
+ DISABLE_VPP(map);
+ chip->state = FL_STATUS;
+
+ /* check for errors: 'lock bit', 'VPP', 'dead cell'/'unerased cell' or 'incorrect cmd' -- saw */
+ if (map_word_bitsset(map, status, CMD(0x3a))) {
+#ifdef DEBUG_CFI_FEATURES
+ printk("%s: 2 status[%lx]\n", __FUNCTION__, status.x[0]);
+#endif
+ /* clear status */
+ map_write(map, CMD(0x50), cmd_adr);
+ /* put back into read status register mode */
+ map_write(map, CMD(0x70), adr);
+ wake_up(&chip->wq);
+ spin_unlock_bh(chip->mutex);
+ return map_word_bitsset(map, status, CMD(0x02)) ? -EROFS : -EIO;
+ }
+ wake_up(&chip->wq);
+ spin_unlock_bh(chip->mutex);
+
+ return 0;
+}
+
+static int cfi_staa_write_buffers (struct mtd_info *mtd, loff_t to,
+ size_t len, size_t *retlen, const u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize;
+ int ret = 0;
+ int chipnum;
+ unsigned long ofs;
+
+ *retlen = 0;
+ if (!len)
+ return 0;
+
+ chipnum = to >> cfi->chipshift;
+ ofs = to - (chipnum << cfi->chipshift);
+
+#ifdef DEBUG_CFI_FEATURES
+ printk("%s: map_bankwidth(map)[%x]\n", __FUNCTION__, map_bankwidth(map));
+ printk("%s: chipnum[%x] wbufsize[%x]\n", __FUNCTION__, chipnum, wbufsize);
+ printk("%s: ofs[%x] len[%x]\n", __FUNCTION__, ofs, len);
+#endif
+
+ /* Write buffer is worth it only if more than one word to write... */
+ while (len > 0) {
+ /* We must not cross write block boundaries */
+ int size = wbufsize - (ofs & (wbufsize-1));
+
+ if (size > len)
+ size = len;
+
+ ret = do_write_buffer(map, &cfi->chips[chipnum],
+ ofs, buf, size);
+ if (ret)
+ return ret;
+
+ ofs += size;
+ buf += size;
+ (*retlen) += size;
+ len -= size;
+
+ if (ofs >> cfi->chipshift) {
+ chipnum ++;
+ ofs = 0;
+ if (chipnum == cfi->numchips)
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Writev for ECC-Flashes is a little more complicated. We need to maintain
+ * a small buffer for this.
+ * XXX: If the buffer size is not a multiple of 2, this will break
+ */
+#define ECCBUF_SIZE (mtd->eccsize)
+#define ECCBUF_DIV(x) ((x) & ~(ECCBUF_SIZE - 1))
+#define ECCBUF_MOD(x) ((x) & (ECCBUF_SIZE - 1))
+static int
+cfi_staa_writev(struct mtd_info *mtd, const struct kvec *vecs,
+ unsigned long count, loff_t to, size_t *retlen)
+{
+ unsigned long i;
+ size_t totlen = 0, thislen;
+ int ret = 0;
+ size_t buflen = 0;
+ static char *buffer;
+
+ if (!ECCBUF_SIZE) {
+ /* We should fall back to a general writev implementation.
+ * Until that is written, just break.
+ */
+ return -EIO;
+ }
+ buffer = kmalloc(ECCBUF_SIZE, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ for (i=0; i<count; i++) {
+ size_t elem_len = vecs[i].iov_len;
+ void *elem_base = vecs[i].iov_base;
+ if (!elem_len) /* FIXME: Might be unnecessary. Check that */
+ continue;
+ if (buflen) { /* cut off head */
+ if (buflen + elem_len < ECCBUF_SIZE) { /* just accumulate */
+ memcpy(buffer+buflen, elem_base, elem_len);
+ buflen += elem_len;
+ continue;
+ }
+ memcpy(buffer+buflen, elem_base, ECCBUF_SIZE-buflen);
+ ret = mtd->write(mtd, to, ECCBUF_SIZE, &thislen, buffer);
+ totlen += thislen;
+ if (ret || thislen != ECCBUF_SIZE)
+ goto write_error;
+ elem_len -= thislen-buflen;
+ elem_base += thislen-buflen;
+ to += ECCBUF_SIZE;
+ }
+ if (ECCBUF_DIV(elem_len)) { /* write clean aligned data */
+ ret = mtd->write(mtd, to, ECCBUF_DIV(elem_len), &thislen, elem_base);
+ totlen += thislen;
+ if (ret || thislen != ECCBUF_DIV(elem_len))
+ goto write_error;
+ to += thislen;
+ }
+ buflen = ECCBUF_MOD(elem_len); /* cut off tail */
+ if (buflen) {
+ memset(buffer, 0xff, ECCBUF_SIZE);
+ memcpy(buffer, elem_base + thislen, buflen);
+ }
+ }
+ if (buflen) { /* flush last page, even if not full */
+ /* This is sometimes intended behaviour, really */
+ ret = mtd->write(mtd, to, buflen, &thislen, buffer);
+ totlen += thislen;
+ if (ret || thislen != ECCBUF_SIZE)
+ goto write_error;
+ }
+write_error:
+ if (retlen)
+ *retlen = totlen;
+ return ret;
+}
+
+
+static inline int do_erase_oneblock(struct map_info *map, struct flchip *chip, unsigned long adr)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ map_word status, status_OK;
+ unsigned long timeo;
+ int retries = 3;
+ DECLARE_WAITQUEUE(wait, current);
+ int ret = 0;
+
+ adr += chip->start;
+
+ /* Let's determine this according to the interleave only once */
+ status_OK = CMD(0x80);
+
+ timeo = jiffies + HZ;
+retry:
+ spin_lock_bh(chip->mutex);
+
+ /* Check that the chip's ready to talk to us. */
+ switch (chip->state) {
+ case FL_CFI_QUERY:
+ case FL_JEDEC_QUERY:
+ case FL_READY:
+ map_write(map, CMD(0x70), adr);
+ chip->state = FL_STATUS;
+
+ case FL_STATUS:
+ status = map_read(map, adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ /* Urgh. Chip not yet ready to talk to us. */
+ if (time_after(jiffies, timeo)) {
+ spin_unlock_bh(chip->mutex);
+ printk(KERN_ERR "waiting for chip to be ready timed out in erase\n");
+ return -EIO;
+ }
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ spin_unlock_bh(chip->mutex);
+ cfi_udelay(1);
+ goto retry;
+
+ default:
+ /* Stick ourselves on a wait queue to be woken when
+ someone changes the status */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ spin_unlock_bh(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ timeo = jiffies + HZ;
+ goto retry;
+ }
+
+ ENABLE_VPP(map);
+ /* Clear the status register first */
+ map_write(map, CMD(0x50), adr);
+
+ /* Now erase */
+ map_write(map, CMD(0x20), adr);
+ map_write(map, CMD(0xD0), adr);
+ chip->state = FL_ERASING;
+
+ spin_unlock_bh(chip->mutex);
+ msleep(1000);
+ spin_lock_bh(chip->mutex);
+
+ /* FIXME. Use a timer to check this, and return immediately. */
+ /* Once the state machine's known to be working I'll do that */
+
+ timeo = jiffies + (HZ*20);
+ for (;;) {
+ if (chip->state != FL_ERASING) {
+ /* Someone's suspended the erase. Sleep */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ spin_unlock_bh(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ timeo = jiffies + (HZ*20); /* FIXME */
+ spin_lock_bh(chip->mutex);
+ continue;
+ }
+
+ status = map_read(map, adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ /* OK Still waiting */
+ if (time_after(jiffies, timeo)) {
+ map_write(map, CMD(0x70), adr);
+ chip->state = FL_STATUS;
+ printk(KERN_ERR "waiting for erase to complete timed out. Xstatus = %lx, status = %lx.\n", status.x[0], map_read(map, adr).x[0]);
+ DISABLE_VPP(map);
+ spin_unlock_bh(chip->mutex);
+ return -EIO;
+ }
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ spin_unlock_bh(chip->mutex);
+ cfi_udelay(1);
+ spin_lock_bh(chip->mutex);
+ }
+
+ DISABLE_VPP(map);
+ ret = 0;
+
+ /* We've broken this before. It doesn't hurt to be safe */
+ map_write(map, CMD(0x70), adr);
+ chip->state = FL_STATUS;
+ status = map_read(map, adr);
+
+ /* check for lock bit */
+ if (map_word_bitsset(map, status, CMD(0x3a))) {
+ unsigned char chipstatus = status.x[0];
+ if (!map_word_equal(map, status, CMD(chipstatus))) {
+ int i, w;
+ for (w=0; w<map_words(map); w++) {
+ for (i = 0; i<cfi_interleave(cfi); i++) {
+ chipstatus |= status.x[w] >> (cfi->device_type * 8);
+ }
+ }
+ printk(KERN_WARNING "Status is not identical for all chips: 0x%lx. Merging to give 0x%02x\n",
+ status.x[0], chipstatus);
+ }
+ /* Reset the error bits */
+ map_write(map, CMD(0x50), adr);
+ map_write(map, CMD(0x70), adr);
+
+ if ((chipstatus & 0x30) == 0x30) {
+ printk(KERN_NOTICE "Chip reports improper command sequence: status 0x%x\n", chipstatus);
+ ret = -EIO;
+ } else if (chipstatus & 0x02) {
+ /* Protection bit set */
+ ret = -EROFS;
+ } else if (chipstatus & 0x8) {
+ /* Voltage */
+ printk(KERN_WARNING "Chip reports voltage low on erase: status 0x%x\n", chipstatus);
+ ret = -EIO;
+ } else if (chipstatus & 0x20) {
+ if (retries--) {
+ printk(KERN_DEBUG "Chip erase failed at 0x%08lx: status 0x%x. Retrying...\n", adr, chipstatus);
+ timeo = jiffies + HZ;
+ chip->state = FL_STATUS;
+ spin_unlock_bh(chip->mutex);
+ goto retry;
+ }
+ printk(KERN_DEBUG "Chip erase failed at 0x%08lx: status 0x%x\n", adr, chipstatus);
+ ret = -EIO;
+ }
+ }
+
+ wake_up(&chip->wq);
+ spin_unlock_bh(chip->mutex);
+ return ret;
+}
+
+int cfi_staa_erase_varsize(struct mtd_info *mtd, struct erase_info *instr)
+{ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long adr, len;
+ int chipnum, ret = 0;
+ int i, first;
+ struct mtd_erase_region_info *regions = mtd->eraseregions;
+
+ if (instr->addr > mtd->size)
+ return -EINVAL;
+
+ if ((instr->len + instr->addr) > mtd->size)
+ return -EINVAL;
+
+ /* Check that both start and end of the requested erase are
+ * aligned with the erasesize at the appropriate addresses.
+ */
+
+ i = 0;
+
+ /* Skip all erase regions which are ended before the start of
+ the requested erase. Actually, to save on the calculations,
+ we skip to the first erase region which starts after the
+ start of the requested erase, and then go back one.
+ */
+
+ while (i < mtd->numeraseregions && instr->addr >= regions[i].offset)
+ i++;
+ i--;
+
+ /* OK, now i is pointing at the erase region in which this
+ erase request starts. Check the start of the requested
+ erase range is aligned with the erase size which is in
+ effect here.
+ */
+
+ if (instr->addr & (regions[i].erasesize-1))
+ return -EINVAL;
+
+ /* Remember the erase region we start on */
+ first = i;
+
+ /* Next, check that the end of the requested erase is aligned
+ * with the erase region at that address.
+ */
+
+ while (i<mtd->numeraseregions && (instr->addr + instr->len) >= regions[i].offset)
+ i++;
+
+ /* As before, drop back one to point at the region in which
+ the address actually falls
+ */
+ i--;
+
+ if ((instr->addr + instr->len) & (regions[i].erasesize-1))
+ return -EINVAL;
+
+ chipnum = instr->addr >> cfi->chipshift;
+ adr = instr->addr - (chipnum << cfi->chipshift);
+ len = instr->len;
+
+ i=first;
+
+ while(len) {
+ ret = do_erase_oneblock(map, &cfi->chips[chipnum], adr);
+
+ if (ret)
+ return ret;
+
+ adr += regions[i].erasesize;
+ len -= regions[i].erasesize;
+
+ if (adr % (1<< cfi->chipshift) == ((regions[i].offset + (regions[i].erasesize * regions[i].numblocks)) %( 1<< cfi->chipshift)))
+ i++;
+
+ if (adr >> cfi->chipshift) {
+ adr = 0;
+ chipnum++;
+
+ if (chipnum >= cfi->numchips)
+ break;
+ }
+ }
+
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+
+ return 0;
+}
+
+static void cfi_staa_sync (struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int i;
+ struct flchip *chip;
+ int ret = 0;
+ DECLARE_WAITQUEUE(wait, current);
+
+ for (i=0; !ret && i<cfi->numchips; i++) {
+ chip = &cfi->chips[i];
+
+ retry:
+ spin_lock_bh(chip->mutex);
+
+ switch(chip->state) {
+ case FL_READY:
+ case FL_STATUS:
+ case FL_CFI_QUERY:
+ case FL_JEDEC_QUERY:
+ chip->oldstate = chip->state;
+ chip->state = FL_SYNCING;
+ /* No need to wake_up() on this state change -
+ * as the whole point is that nobody can do anything
+ * with the chip now anyway.
+ */
+ case FL_SYNCING:
+ spin_unlock_bh(chip->mutex);
+ break;
+
+ default:
+ /* Not an idle state */
+ add_wait_queue(&chip->wq, &wait);
+
+ spin_unlock_bh(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+
+ goto retry;
+ }
+ }
+
+ /* Unlock the chips again */
+
+ for (i--; i >=0; i--) {
+ chip = &cfi->chips[i];
+
+ spin_lock_bh(chip->mutex);
+
+ if (chip->state == FL_SYNCING) {
+ chip->state = chip->oldstate;
+ wake_up(&chip->wq);
+ }
+ spin_unlock_bh(chip->mutex);
+ }
+}
+
+static inline int do_lock_oneblock(struct map_info *map, struct flchip *chip, unsigned long adr)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ map_word status, status_OK;
+ unsigned long timeo = jiffies + HZ;
+ DECLARE_WAITQUEUE(wait, current);
+
+ adr += chip->start;
+
+ /* Let's determine this according to the interleave only once */
+ status_OK = CMD(0x80);
+
+ timeo = jiffies + HZ;
+retry:
+ spin_lock_bh(chip->mutex);
+
+ /* Check that the chip's ready to talk to us. */
+ switch (chip->state) {
+ case FL_CFI_QUERY:
+ case FL_JEDEC_QUERY:
+ case FL_READY:
+ map_write(map, CMD(0x70), adr);
+ chip->state = FL_STATUS;
+
+ case FL_STATUS:
+ status = map_read(map, adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ /* Urgh. Chip not yet ready to talk to us. */
+ if (time_after(jiffies, timeo)) {
+ spin_unlock_bh(chip->mutex);
+ printk(KERN_ERR "waiting for chip to be ready timed out in lock\n");
+ return -EIO;
+ }
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ spin_unlock_bh(chip->mutex);
+ cfi_udelay(1);
+ goto retry;
+
+ default:
+ /* Stick ourselves on a wait queue to be woken when
+ someone changes the status */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ spin_unlock_bh(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ timeo = jiffies + HZ;
+ goto retry;
+ }
+
+ ENABLE_VPP(map);
+ map_write(map, CMD(0x60), adr);
+ map_write(map, CMD(0x01), adr);
+ chip->state = FL_LOCKING;
+
+ spin_unlock_bh(chip->mutex);
+ msleep(1000);
+ spin_lock_bh(chip->mutex);
+
+ /* FIXME. Use a timer to check this, and return immediately. */
+ /* Once the state machine's known to be working I'll do that */
+
+ timeo = jiffies + (HZ*2);
+ for (;;) {
+
+ status = map_read(map, adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ /* OK Still waiting */
+ if (time_after(jiffies, timeo)) {
+ map_write(map, CMD(0x70), adr);
+ chip->state = FL_STATUS;
+ printk(KERN_ERR "waiting for lock to complete timed out. Xstatus = %lx, status = %lx.\n", status.x[0], map_read(map, adr).x[0]);
+ DISABLE_VPP(map);
+ spin_unlock_bh(chip->mutex);
+ return -EIO;
+ }
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ spin_unlock_bh(chip->mutex);
+ cfi_udelay(1);
+ spin_lock_bh(chip->mutex);
+ }
+
+ /* Done and happy. */
+ chip->state = FL_STATUS;
+ DISABLE_VPP(map);
+ wake_up(&chip->wq);
+ spin_unlock_bh(chip->mutex);
+ return 0;
+}
+static int cfi_staa_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long adr;
+ int chipnum, ret = 0;
+#ifdef DEBUG_LOCK_BITS
+ int ofs_factor = cfi->interleave * cfi->device_type;
+#endif
+
+ if (ofs & (mtd->erasesize - 1))
+ return -EINVAL;
+
+ if (len & (mtd->erasesize -1))
+ return -EINVAL;
+
+ if ((len + ofs) > mtd->size)
+ return -EINVAL;
+
+ chipnum = ofs >> cfi->chipshift;
+ adr = ofs - (chipnum << cfi->chipshift);
+
+ while(len) {
+
+#ifdef DEBUG_LOCK_BITS
+ cfi_send_gen_cmd(0x90, 0x55, 0, map, cfi, cfi->device_type, NULL);
+ printk("before lock: block status register is %x\n",cfi_read_query(map, adr+(2*ofs_factor)));
+ cfi_send_gen_cmd(0xff, 0x55, 0, map, cfi, cfi->device_type, NULL);
+#endif
+
+ ret = do_lock_oneblock(map, &cfi->chips[chipnum], adr);
+
+#ifdef DEBUG_LOCK_BITS
+ cfi_send_gen_cmd(0x90, 0x55, 0, map, cfi, cfi->device_type, NULL);
+ printk("after lock: block status register is %x\n",cfi_read_query(map, adr+(2*ofs_factor)));
+ cfi_send_gen_cmd(0xff, 0x55, 0, map, cfi, cfi->device_type, NULL);
+#endif
+
+ if (ret)
+ return ret;
+
+ adr += mtd->erasesize;
+ len -= mtd->erasesize;
+
+ if (adr >> cfi->chipshift) {
+ adr = 0;
+ chipnum++;
+
+ if (chipnum >= cfi->numchips)
+ break;
+ }
+ }
+ return 0;
+}
+static inline int do_unlock_oneblock(struct map_info *map, struct flchip *chip, unsigned long adr)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ map_word status, status_OK;
+ unsigned long timeo = jiffies + HZ;
+ DECLARE_WAITQUEUE(wait, current);
+
+ adr += chip->start;
+
+ /* Let's determine this according to the interleave only once */
+ status_OK = CMD(0x80);
+
+ timeo = jiffies + HZ;
+retry:
+ spin_lock_bh(chip->mutex);
+
+ /* Check that the chip's ready to talk to us. */
+ switch (chip->state) {
+ case FL_CFI_QUERY:
+ case FL_JEDEC_QUERY:
+ case FL_READY:
+ map_write(map, CMD(0x70), adr);
+ chip->state = FL_STATUS;
+
+ case FL_STATUS:
+ status = map_read(map, adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ /* Urgh. Chip not yet ready to talk to us. */
+ if (time_after(jiffies, timeo)) {
+ spin_unlock_bh(chip->mutex);
+ printk(KERN_ERR "waiting for chip to be ready timed out in unlock\n");
+ return -EIO;
+ }
+
+ /* Latency issues. Drop the lock, wait a while and retry */
+ spin_unlock_bh(chip->mutex);
+ cfi_udelay(1);
+ goto retry;
+
+ default:
+ /* Stick ourselves on a wait queue to be woken when
+ someone changes the status */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+ spin_unlock_bh(chip->mutex);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+ timeo = jiffies + HZ;
+ goto retry;
+ }
+
+ ENABLE_VPP(map);
+ map_write(map, CMD(0x60), adr);
+ map_write(map, CMD(0xD0), adr);
+ chip->state = FL_UNLOCKING;
+
+ spin_unlock_bh(chip->mutex);
+ msleep(1000);
+ spin_lock_bh(chip->mutex);
+
+ /* FIXME. Use a timer to check this, and return immediately. */
+ /* Once the state machine's known to be working I'll do that */
+
+ timeo = jiffies + (HZ*2);
+ for (;;) {
+
+ status = map_read(map, adr);
+ if (map_word_andequal(map, status, status_OK, status_OK))
+ break;
+
+ /* OK Still waiting */
+ if (time_after(jiffies, timeo)) {
+ map_write(map, CMD(0x70), adr);
+ chip->state = FL_STATUS;
+ printk(KERN_ERR "waiting for unlock to complete timed out. Xstatus = %lx, status = %lx.\n", status.x[0], map_read(map, adr).x[0]);
+ DISABLE_VPP(map);
+ spin_unlock_bh(chip->mutex);
+ return -EIO;
+ }
+
+ /* Latency issues. Drop the unlock, wait a while and retry */
+ spin_unlock_bh(chip->mutex);
+ cfi_udelay(1);
+ spin_lock_bh(chip->mutex);
+ }
+
+ /* Done and happy. */
+ chip->state = FL_STATUS;
+ DISABLE_VPP(map);
+ wake_up(&chip->wq);
+ spin_unlock_bh(chip->mutex);
+ return 0;
+}
+static int cfi_staa_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long adr;
+ int chipnum, ret = 0;
+#ifdef DEBUG_LOCK_BITS
+ int ofs_factor = cfi->interleave * cfi->device_type;
+#endif
+
+ chipnum = ofs >> cfi->chipshift;
+ adr = ofs - (chipnum << cfi->chipshift);
+
+#ifdef DEBUG_LOCK_BITS
+ {
+ unsigned long temp_adr = adr;
+ unsigned long temp_len = len;
+
+ cfi_send_gen_cmd(0x90, 0x55, 0, map, cfi, cfi->device_type, NULL);
+ while (temp_len) {
+ printk("before unlock %x: block status register is %x\n",temp_adr,cfi_read_query(map, temp_adr+(2*ofs_factor)));
+ temp_adr += mtd->erasesize;
+ temp_len -= mtd->erasesize;
+ }
+ cfi_send_gen_cmd(0xff, 0x55, 0, map, cfi, cfi->device_type, NULL);
+ }
+#endif
+
+ ret = do_unlock_oneblock(map, &cfi->chips[chipnum], adr);
+
+#ifdef DEBUG_LOCK_BITS
+ cfi_send_gen_cmd(0x90, 0x55, 0, map, cfi, cfi->device_type, NULL);
+ printk("after unlock: block status register is %x\n",cfi_read_query(map, adr+(2*ofs_factor)));
+ cfi_send_gen_cmd(0xff, 0x55, 0, map, cfi, cfi->device_type, NULL);
+#endif
+
+ return ret;
+}
+
+static int cfi_staa_suspend(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int i;
+ struct flchip *chip;
+ int ret = 0;
+
+ for (i=0; !ret && i<cfi->numchips; i++) {
+ chip = &cfi->chips[i];
+
+ spin_lock_bh(chip->mutex);
+
+ switch(chip->state) {
+ case FL_READY:
+ case FL_STATUS:
+ case FL_CFI_QUERY:
+ case FL_JEDEC_QUERY:
+ chip->oldstate = chip->state;
+ chip->state = FL_PM_SUSPENDED;
+ /* No need to wake_up() on this state change -
+ * as the whole point is that nobody can do anything
+ * with the chip now anyway.
+ */
+ case FL_PM_SUSPENDED:
+ break;
+
+ default:
+ ret = -EAGAIN;
+ break;
+ }
+ spin_unlock_bh(chip->mutex);
+ }
+
+ /* Unlock the chips again */
+
+ if (ret) {
+ for (i--; i >=0; i--) {
+ chip = &cfi->chips[i];
+
+ spin_lock_bh(chip->mutex);
+
+ if (chip->state == FL_PM_SUSPENDED) {
+ /* No need to force it into a known state here,
+ because we're returning failure, and it didn't
+ get power cycled */
+ chip->state = chip->oldstate;
+ wake_up(&chip->wq);
+ }
+ spin_unlock_bh(chip->mutex);
+ }
+ }
+
+ return ret;
+}
+
+static void cfi_staa_resume(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ int i;
+ struct flchip *chip;
+
+ for (i=0; i<cfi->numchips; i++) {
+
+ chip = &cfi->chips[i];
+
+ spin_lock_bh(chip->mutex);
+
+ /* Go to known state. Chip may have been power cycled */
+ if (chip->state == FL_PM_SUSPENDED) {
+ map_write(map, CMD(0xFF), 0);
+ chip->state = FL_READY;
+ wake_up(&chip->wq);
+ }
+
+ spin_unlock_bh(chip->mutex);
+ }
+}
+
+static void cfi_staa_destroy(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ kfree(cfi->cmdset_priv);
+ kfree(cfi);
+}
+
+static char im_name[]="cfi_cmdset_0020";
+
+static int __init cfi_staa_init(void)
+{
+ inter_module_register(im_name, THIS_MODULE, &cfi_cmdset_0020);
+ return 0;
+}
+
+static void __exit cfi_staa_exit(void)
+{
+ inter_module_unregister(im_name);
+}
+
+module_init(cfi_staa_init);
+module_exit(cfi_staa_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/chips/cfi_probe.c b/drivers/mtd/chips/cfi_probe.c
new file mode 100644
index 00000000000..cf750038ce6
--- /dev/null
+++ b/drivers/mtd/chips/cfi_probe.c
@@ -0,0 +1,445 @@
+/*
+ Common Flash Interface probe code.
+ (C) 2000 Red Hat. GPL'd.
+ $Id: cfi_probe.c,v 1.83 2004/11/16 18:19:02 nico Exp $
+*/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <asm/byteorder.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+
+#include <linux/mtd/xip.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/cfi.h>
+#include <linux/mtd/gen_probe.h>
+
+//#define DEBUG_CFI
+
+#ifdef DEBUG_CFI
+static void print_cfi_ident(struct cfi_ident *);
+#endif
+
+static int cfi_probe_chip(struct map_info *map, __u32 base,
+ unsigned long *chip_map, struct cfi_private *cfi);
+static int cfi_chip_setup(struct map_info *map, struct cfi_private *cfi);
+
+struct mtd_info *cfi_probe(struct map_info *map);
+
+#ifdef CONFIG_MTD_XIP
+
+/* only needed for short periods, so this is rather simple */
+#define xip_disable() local_irq_disable()
+
+#define xip_allowed(base, map) \
+do { \
+ (void) map_read(map, base); \
+ asm volatile (".rep 8; nop; .endr"); \
+ local_irq_enable(); \
+} while (0)
+
+#define xip_enable(base, map, cfi) \
+do { \
+ cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); \
+ cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); \
+ xip_allowed(base, map); \
+} while (0)
+
+#define xip_disable_qry(base, map, cfi) \
+do { \
+ xip_disable(); \
+ cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); \
+ cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); \
+ cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); \
+} while (0)
+
+#else
+
+#define xip_disable() do { } while (0)
+#define xip_allowed(base, map) do { } while (0)
+#define xip_enable(base, map, cfi) do { } while (0)
+#define xip_disable_qry(base, map, cfi) do { } while (0)
+
+#endif
+
+/* check for QRY.
+ in: interleave,type,mode
+ ret: table index, <0 for error
+ */
+static int __xipram qry_present(struct map_info *map, __u32 base,
+ struct cfi_private *cfi)
+{
+ int osf = cfi->interleave * cfi->device_type; // scale factor
+ map_word val[3];
+ map_word qry[3];
+
+ qry[0] = cfi_build_cmd('Q', map, cfi);
+ qry[1] = cfi_build_cmd('R', map, cfi);
+ qry[2] = cfi_build_cmd('Y', map, cfi);
+
+ val[0] = map_read(map, base + osf*0x10);
+ val[1] = map_read(map, base + osf*0x11);
+ val[2] = map_read(map, base + osf*0x12);
+
+ if (!map_word_equal(map, qry[0], val[0]))
+ return 0;
+
+ if (!map_word_equal(map, qry[1], val[1]))
+ return 0;
+
+ if (!map_word_equal(map, qry[2], val[2]))
+ return 0;
+
+ return 1; // "QRY" found
+}
+
+static int __xipram cfi_probe_chip(struct map_info *map, __u32 base,
+ unsigned long *chip_map, struct cfi_private *cfi)
+{
+ int i;
+
+ if ((base + 0) >= map->size) {
+ printk(KERN_NOTICE
+ "Probe at base[0x00](0x%08lx) past the end of the map(0x%08lx)\n",
+ (unsigned long)base, map->size -1);
+ return 0;
+ }
+ if ((base + 0xff) >= map->size) {
+ printk(KERN_NOTICE
+ "Probe at base[0x55](0x%08lx) past the end of the map(0x%08lx)\n",
+ (unsigned long)base + 0x55, map->size -1);
+ return 0;
+ }
+
+ xip_disable();
+ cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL);
+
+ if (!qry_present(map,base,cfi)) {
+ xip_enable(base, map, cfi);
+ return 0;
+ }
+
+ if (!cfi->numchips) {
+ /* This is the first time we're called. Set up the CFI
+ stuff accordingly and return */
+ return cfi_chip_setup(map, cfi);
+ }
+
+ /* Check each previous chip to see if it's an alias */
+ for (i=0; i < (base >> cfi->chipshift); i++) {
+ unsigned long start;
+ if(!test_bit(i, chip_map)) {
+ /* Skip location; no valid chip at this address */
+ continue;
+ }
+ start = i << cfi->chipshift;
+ /* This chip should be in read mode if it's one
+ we've already touched. */
+ if (qry_present(map, start, cfi)) {
+ /* Eep. This chip also had the QRY marker.
+ * Is it an alias for the new one? */
+ cfi_send_gen_cmd(0xF0, 0, start, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0xFF, 0, start, map, cfi, cfi->device_type, NULL);
+
+ /* If the QRY marker goes away, it's an alias */
+ if (!qry_present(map, start, cfi)) {
+ xip_allowed(base, map);
+ printk(KERN_DEBUG "%s: Found an alias at 0x%x for the chip at 0x%lx\n",
+ map->name, base, start);
+ return 0;
+ }
+ /* Yes, it's actually got QRY for data. Most
+ * unfortunate. Stick the new chip in read mode
+ * too and if it's the same, assume it's an alias. */
+ /* FIXME: Use other modes to do a proper check */
+ cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0xFF, 0, start, map, cfi, cfi->device_type, NULL);
+
+ if (qry_present(map, base, cfi)) {
+ xip_allowed(base, map);
+ printk(KERN_DEBUG "%s: Found an alias at 0x%x for the chip at 0x%lx\n",
+ map->name, base, start);
+ return 0;
+ }
+ }
+ }
+
+ /* OK, if we got to here, then none of the previous chips appear to
+ be aliases for the current one. */
+ set_bit((base >> cfi->chipshift), chip_map); /* Update chip map */
+ cfi->numchips++;
+
+ /* Put it back into Read Mode */
+ cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL);
+ xip_allowed(base, map);
+
+ printk(KERN_INFO "%s: Found %d x%d devices at 0x%x in %d-bit bank\n",
+ map->name, cfi->interleave, cfi->device_type*8, base,
+ map->bankwidth*8);
+
+ return 1;
+}
+
+static int __xipram cfi_chip_setup(struct map_info *map,
+ struct cfi_private *cfi)
+{
+ int ofs_factor = cfi->interleave*cfi->device_type;
+ __u32 base = 0;
+ int num_erase_regions = cfi_read_query(map, base + (0x10 + 28)*ofs_factor);
+ int i;
+
+ xip_enable(base, map, cfi);
+#ifdef DEBUG_CFI
+ printk("Number of erase regions: %d\n", num_erase_regions);
+#endif
+ if (!num_erase_regions)
+ return 0;
+
+ cfi->cfiq = kmalloc(sizeof(struct cfi_ident) + num_erase_regions * 4, GFP_KERNEL);
+ if (!cfi->cfiq) {
+ printk(KERN_WARNING "%s: kmalloc failed for CFI ident structure\n", map->name);
+ return 0;
+ }
+
+ memset(cfi->cfiq,0,sizeof(struct cfi_ident));
+
+ cfi->cfi_mode = CFI_MODE_CFI;
+
+ /* Read the CFI info structure */
+ xip_disable_qry(base, map, cfi);
+ for (i=0; i<(sizeof(struct cfi_ident) + num_erase_regions * 4); i++)
+ ((unsigned char *)cfi->cfiq)[i] = cfi_read_query(map,base + (0x10 + i)*ofs_factor);
+
+ /* Note we put the device back into Read Mode BEFORE going into Auto
+ * Select Mode, as some devices support nesting of modes, others
+ * don't. This way should always work.
+ * On cmdset 0001 the writes of 0xaa and 0x55 are not needed, and
+ * so should be treated as nops or illegal (and so put the device
+ * back into Read Mode, which is a nop in this case).
+ */
+ cfi_send_gen_cmd(0xf0, 0, base, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0xaa, 0x555, base, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x55, 0x2aa, base, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x90, 0x555, base, map, cfi, cfi->device_type, NULL);
+ cfi->mfr = cfi_read_query(map, base);
+ cfi->id = cfi_read_query(map, base + ofs_factor);
+
+ /* Put it back into Read Mode */
+ cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL);
+ /* ... even if it's an Intel chip */
+ cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL);
+ xip_allowed(base, map);
+
+ /* Do any necessary byteswapping */
+ cfi->cfiq->P_ID = le16_to_cpu(cfi->cfiq->P_ID);
+
+ cfi->cfiq->P_ADR = le16_to_cpu(cfi->cfiq->P_ADR);
+ cfi->cfiq->A_ID = le16_to_cpu(cfi->cfiq->A_ID);
+ cfi->cfiq->A_ADR = le16_to_cpu(cfi->cfiq->A_ADR);
+ cfi->cfiq->InterfaceDesc = le16_to_cpu(cfi->cfiq->InterfaceDesc);
+ cfi->cfiq->MaxBufWriteSize = le16_to_cpu(cfi->cfiq->MaxBufWriteSize);
+
+#ifdef DEBUG_CFI
+ /* Dump the information therein */
+ print_cfi_ident(cfi->cfiq);
+#endif
+
+ for (i=0; i<cfi->cfiq->NumEraseRegions; i++) {
+ cfi->cfiq->EraseRegionInfo[i] = le32_to_cpu(cfi->cfiq->EraseRegionInfo[i]);
+
+#ifdef DEBUG_CFI
+ printk(" Erase Region #%d: BlockSize 0x%4.4X bytes, %d blocks\n",
+ i, (cfi->cfiq->EraseRegionInfo[i] >> 8) & ~0xff,
+ (cfi->cfiq->EraseRegionInfo[i] & 0xffff) + 1);
+#endif
+ }
+
+ printk(KERN_INFO "%s: Found %d x%d devices at 0x%x in %d-bit bank\n",
+ map->name, cfi->interleave, cfi->device_type*8, base,
+ map->bankwidth*8);
+
+ return 1;
+}
+
+#ifdef DEBUG_CFI
+static char *vendorname(__u16 vendor)
+{
+ switch (vendor) {
+ case P_ID_NONE:
+ return "None";
+
+ case P_ID_INTEL_EXT:
+ return "Intel/Sharp Extended";
+
+ case P_ID_AMD_STD:
+ return "AMD/Fujitsu Standard";
+
+ case P_ID_INTEL_STD:
+ return "Intel/Sharp Standard";
+
+ case P_ID_AMD_EXT:
+ return "AMD/Fujitsu Extended";
+
+ case P_ID_WINBOND:
+ return "Winbond Standard";
+
+ case P_ID_ST_ADV:
+ return "ST Advanced";
+
+ case P_ID_MITSUBISHI_STD:
+ return "Mitsubishi Standard";
+
+ case P_ID_MITSUBISHI_EXT:
+ return "Mitsubishi Extended";
+
+ case P_ID_SST_PAGE:
+ return "SST Page Write";
+
+ case P_ID_INTEL_PERFORMANCE:
+ return "Intel Performance Code";
+
+ case P_ID_INTEL_DATA:
+ return "Intel Data";
+
+ case P_ID_RESERVED:
+ return "Not Allowed / Reserved for Future Use";
+
+ default:
+ return "Unknown";
+ }
+}
+
+
+static void print_cfi_ident(struct cfi_ident *cfip)
+{
+#if 0
+ if (cfip->qry[0] != 'Q' || cfip->qry[1] != 'R' || cfip->qry[2] != 'Y') {
+ printk("Invalid CFI ident structure.\n");
+ return;
+ }
+#endif
+ printk("Primary Vendor Command Set: %4.4X (%s)\n", cfip->P_ID, vendorname(cfip->P_ID));
+ if (cfip->P_ADR)
+ printk("Primary Algorithm Table at %4.4X\n", cfip->P_ADR);
+ else
+ printk("No Primary Algorithm Table\n");
+
+ printk("Alternative Vendor Command Set: %4.4X (%s)\n", cfip->A_ID, vendorname(cfip->A_ID));
+ if (cfip->A_ADR)
+ printk("Alternate Algorithm Table at %4.4X\n", cfip->A_ADR);
+ else
+ printk("No Alternate Algorithm Table\n");
+
+
+ printk("Vcc Minimum: %2d.%d V\n", cfip->VccMin >> 4, cfip->VccMin & 0xf);
+ printk("Vcc Maximum: %2d.%d V\n", cfip->VccMax >> 4, cfip->VccMax & 0xf);
+ if (cfip->VppMin) {
+ printk("Vpp Minimum: %2d.%d V\n", cfip->VppMin >> 4, cfip->VppMin & 0xf);
+ printk("Vpp Maximum: %2d.%d V\n", cfip->VppMax >> 4, cfip->VppMax & 0xf);
+ }
+ else
+ printk("No Vpp line\n");
+
+ printk("Typical byte/word write timeout: %d µs\n", 1<<cfip->WordWriteTimeoutTyp);
+ printk("Maximum byte/word write timeout: %d µs\n", (1<<cfip->WordWriteTimeoutMax) * (1<<cfip->WordWriteTimeoutTyp));
+
+ if (cfip->BufWriteTimeoutTyp || cfip->BufWriteTimeoutMax) {
+ printk("Typical full buffer write timeout: %d µs\n", 1<<cfip->BufWriteTimeoutTyp);
+ printk("Maximum full buffer write timeout: %d µs\n", (1<<cfip->BufWriteTimeoutMax) * (1<<cfip->BufWriteTimeoutTyp));
+ }
+ else
+ printk("Full buffer write not supported\n");
+
+ printk("Typical block erase timeout: %d ms\n", 1<<cfip->BlockEraseTimeoutTyp);
+ printk("Maximum block erase timeout: %d ms\n", (1<<cfip->BlockEraseTimeoutMax) * (1<<cfip->BlockEraseTimeoutTyp));
+ if (cfip->ChipEraseTimeoutTyp || cfip->ChipEraseTimeoutMax) {
+ printk("Typical chip erase timeout: %d ms\n", 1<<cfip->ChipEraseTimeoutTyp);
+ printk("Maximum chip erase timeout: %d ms\n", (1<<cfip->ChipEraseTimeoutMax) * (1<<cfip->ChipEraseTimeoutTyp));
+ }
+ else
+ printk("Chip erase not supported\n");
+
+ printk("Device size: 0x%X bytes (%d MiB)\n", 1 << cfip->DevSize, 1<< (cfip->DevSize - 20));
+ printk("Flash Device Interface description: 0x%4.4X\n", cfip->InterfaceDesc);
+ switch(cfip->InterfaceDesc) {
+ case 0:
+ printk(" - x8-only asynchronous interface\n");
+ break;
+
+ case 1:
+ printk(" - x16-only asynchronous interface\n");
+ break;
+
+ case 2:
+ printk(" - supports x8 and x16 via BYTE# with asynchronous interface\n");
+ break;
+
+ case 3:
+ printk(" - x32-only asynchronous interface\n");
+ break;
+
+ case 4:
+ printk(" - supports x16 and x32 via Word# with asynchronous interface\n");
+ break;
+
+ case 65535:
+ printk(" - Not Allowed / Reserved\n");
+ break;
+
+ default:
+ printk(" - Unknown\n");
+ break;
+ }
+
+ printk("Max. bytes in buffer write: 0x%x\n", 1<< cfip->MaxBufWriteSize);
+ printk("Number of Erase Block Regions: %d\n", cfip->NumEraseRegions);
+
+}
+#endif /* DEBUG_CFI */
+
+static struct chip_probe cfi_chip_probe = {
+ .name = "CFI",
+ .probe_chip = cfi_probe_chip
+};
+
+struct mtd_info *cfi_probe(struct map_info *map)
+{
+ /*
+ * Just use the generic probe stuff to call our CFI-specific
+ * chip_probe routine in all the possible permutations, etc.
+ */
+ return mtd_do_chip_probe(map, &cfi_chip_probe);
+}
+
+static struct mtd_chip_driver cfi_chipdrv = {
+ .probe = cfi_probe,
+ .name = "cfi_probe",
+ .module = THIS_MODULE
+};
+
+int __init cfi_probe_init(void)
+{
+ register_mtd_chip_driver(&cfi_chipdrv);
+ return 0;
+}
+
+static void __exit cfi_probe_exit(void)
+{
+ unregister_mtd_chip_driver(&cfi_chipdrv);
+}
+
+module_init(cfi_probe_init);
+module_exit(cfi_probe_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org> et al.");
+MODULE_DESCRIPTION("Probe code for CFI-compliant flash chips");
diff --git a/drivers/mtd/chips/cfi_util.c b/drivers/mtd/chips/cfi_util.c
new file mode 100644
index 00000000000..2b2ede2bfcc
--- /dev/null
+++ b/drivers/mtd/chips/cfi_util.c
@@ -0,0 +1,196 @@
+/*
+ * Common Flash Interface support:
+ * Generic utility functions not dependant on command set
+ *
+ * Copyright (C) 2002 Red Hat
+ * Copyright (C) 2003 STMicroelectronics Limited
+ *
+ * This code is covered by the GPL.
+ *
+ * $Id: cfi_util.c,v 1.8 2004/12/14 19:55:56 nico Exp $
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <asm/io.h>
+#include <asm/byteorder.h>
+
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/mtd/xip.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/cfi.h>
+#include <linux/mtd/compatmac.h>
+
+struct cfi_extquery *
+__xipram cfi_read_pri(struct map_info *map, __u16 adr, __u16 size, const char* name)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ __u32 base = 0; // cfi->chips[0].start;
+ int ofs_factor = cfi->interleave * cfi->device_type;
+ int i;
+ struct cfi_extquery *extp = NULL;
+
+ printk(" %s Extended Query Table at 0x%4.4X\n", name, adr);
+ if (!adr)
+ goto out;
+
+ extp = kmalloc(size, GFP_KERNEL);
+ if (!extp) {
+ printk(KERN_ERR "Failed to allocate memory\n");
+ goto out;
+ }
+
+#ifdef CONFIG_MTD_XIP
+ local_irq_disable();
+#endif
+
+ /* Switch it into Query Mode */
+ cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL);
+
+ /* Read in the Extended Query Table */
+ for (i=0; i<size; i++) {
+ ((unsigned char *)extp)[i] =
+ cfi_read_query(map, base+((adr+i)*ofs_factor));
+ }
+
+ /* Make sure it returns to read mode */
+ cfi_send_gen_cmd(0xf0, 0, base, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0xff, 0, base, map, cfi, cfi->device_type, NULL);
+
+#ifdef CONFIG_MTD_XIP
+ (void) map_read(map, base);
+ asm volatile (".rep 8; nop; .endr");
+ local_irq_enable();
+#endif
+
+ if (extp->MajorVersion != '1' ||
+ (extp->MinorVersion < '0' || extp->MinorVersion > '3')) {
+ printk(KERN_WARNING " Unknown %s Extended Query "
+ "version %c.%c.\n", name, extp->MajorVersion,
+ extp->MinorVersion);
+ kfree(extp);
+ extp = NULL;
+ }
+
+ out: return extp;
+}
+
+EXPORT_SYMBOL(cfi_read_pri);
+
+void cfi_fixup(struct mtd_info *mtd, struct cfi_fixup *fixups)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ struct cfi_fixup *f;
+
+ for (f=fixups; f->fixup; f++) {
+ if (((f->mfr == CFI_MFR_ANY) || (f->mfr == cfi->mfr)) &&
+ ((f->id == CFI_ID_ANY) || (f->id == cfi->id))) {
+ f->fixup(mtd, f->param);
+ }
+ }
+}
+
+EXPORT_SYMBOL(cfi_fixup);
+
+int cfi_varsize_frob(struct mtd_info *mtd, varsize_frob_t frob,
+ loff_t ofs, size_t len, void *thunk)
+{
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+ unsigned long adr;
+ int chipnum, ret = 0;
+ int i, first;
+ struct mtd_erase_region_info *regions = mtd->eraseregions;
+
+ if (ofs > mtd->size)
+ return -EINVAL;
+
+ if ((len + ofs) > mtd->size)
+ return -EINVAL;
+
+ /* Check that both start and end of the requested erase are
+ * aligned with the erasesize at the appropriate addresses.
+ */
+
+ i = 0;
+
+ /* Skip all erase regions which are ended before the start of
+ the requested erase. Actually, to save on the calculations,
+ we skip to the first erase region which starts after the
+ start of the requested erase, and then go back one.
+ */
+
+ while (i < mtd->numeraseregions && ofs >= regions[i].offset)
+ i++;
+ i--;
+
+ /* OK, now i is pointing at the erase region in which this
+ erase request starts. Check the start of the requested
+ erase range is aligned with the erase size which is in
+ effect here.
+ */
+
+ if (ofs & (regions[i].erasesize-1))
+ return -EINVAL;
+
+ /* Remember the erase region we start on */
+ first = i;
+
+ /* Next, check that the end of the requested erase is aligned
+ * with the erase region at that address.
+ */
+
+ while (i<mtd->numeraseregions && (ofs + len) >= regions[i].offset)
+ i++;
+
+ /* As before, drop back one to point at the region in which
+ the address actually falls
+ */
+ i--;
+
+ if ((ofs + len) & (regions[i].erasesize-1))
+ return -EINVAL;
+
+ chipnum = ofs >> cfi->chipshift;
+ adr = ofs - (chipnum << cfi->chipshift);
+
+ i=first;
+
+ while(len) {
+ int size = regions[i].erasesize;
+
+ ret = (*frob)(map, &cfi->chips[chipnum], adr, size, thunk);
+
+ if (ret)
+ return ret;
+
+ adr += size;
+ ofs += size;
+ len -= size;
+
+ if (ofs == regions[i].offset + size * regions[i].numblocks)
+ i++;
+
+ if (adr >> cfi->chipshift) {
+ adr = 0;
+ chipnum++;
+
+ if (chipnum >= cfi->numchips)
+ break;
+ }
+ }
+
+ return 0;
+}
+
+EXPORT_SYMBOL(cfi_varsize_frob);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/chips/chipreg.c b/drivers/mtd/chips/chipreg.c
new file mode 100644
index 00000000000..d7d739a108a
--- /dev/null
+++ b/drivers/mtd/chips/chipreg.c
@@ -0,0 +1,111 @@
+/*
+ * $Id: chipreg.c,v 1.17 2004/11/16 18:29:00 dwmw2 Exp $
+ *
+ * Registration for chip drivers
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kmod.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/compatmac.h>
+
+static DEFINE_SPINLOCK(chip_drvs_lock);
+static LIST_HEAD(chip_drvs_list);
+
+void register_mtd_chip_driver(struct mtd_chip_driver *drv)
+{
+ spin_lock(&chip_drvs_lock);
+ list_add(&drv->list, &chip_drvs_list);
+ spin_unlock(&chip_drvs_lock);
+}
+
+void unregister_mtd_chip_driver(struct mtd_chip_driver *drv)
+{
+ spin_lock(&chip_drvs_lock);
+ list_del(&drv->list);
+ spin_unlock(&chip_drvs_lock);
+}
+
+static struct mtd_chip_driver *get_mtd_chip_driver (const char *name)
+{
+ struct list_head *pos;
+ struct mtd_chip_driver *ret = NULL, *this;
+
+ spin_lock(&chip_drvs_lock);
+
+ list_for_each(pos, &chip_drvs_list) {
+ this = list_entry(pos, typeof(*this), list);
+
+ if (!strcmp(this->name, name)) {
+ ret = this;
+ break;
+ }
+ }
+ if (ret && !try_module_get(ret->module))
+ ret = NULL;
+
+ spin_unlock(&chip_drvs_lock);
+
+ return ret;
+}
+
+ /* Hide all the horrid details, like some silly person taking
+ get_module_symbol() away from us, from the caller. */
+
+struct mtd_info *do_map_probe(const char *name, struct map_info *map)
+{
+ struct mtd_chip_driver *drv;
+ struct mtd_info *ret;
+
+ drv = get_mtd_chip_driver(name);
+
+ if (!drv && !request_module("%s", name))
+ drv = get_mtd_chip_driver(name);
+
+ if (!drv)
+ return NULL;
+
+ ret = drv->probe(map);
+
+ /* We decrease the use count here. It may have been a
+ probe-only module, which is no longer required from this
+ point, having given us a handle on (and increased the use
+ count of) the actual driver code.
+ */
+ module_put(drv->module);
+
+ if (ret)
+ return ret;
+
+ return NULL;
+}
+/*
+ * Destroy an MTD device which was created for a map device.
+ * Make sure the MTD device is already unregistered before calling this
+ */
+void map_destroy(struct mtd_info *mtd)
+{
+ struct map_info *map = mtd->priv;
+
+ if (map->fldrv->destroy)
+ map->fldrv->destroy(mtd);
+
+ module_put(map->fldrv->module);
+
+ kfree(mtd);
+}
+
+EXPORT_SYMBOL(register_mtd_chip_driver);
+EXPORT_SYMBOL(unregister_mtd_chip_driver);
+EXPORT_SYMBOL(do_map_probe);
+EXPORT_SYMBOL(map_destroy);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("Core routines for registering and invoking MTD chip drivers");
diff --git a/drivers/mtd/chips/fwh_lock.h b/drivers/mtd/chips/fwh_lock.h
new file mode 100644
index 00000000000..fbf44708a86
--- /dev/null
+++ b/drivers/mtd/chips/fwh_lock.h
@@ -0,0 +1,107 @@
+#ifndef FWH_LOCK_H
+#define FWH_LOCK_H
+
+
+enum fwh_lock_state {
+ FWH_UNLOCKED = 0,
+ FWH_DENY_WRITE = 1,
+ FWH_IMMUTABLE = 2,
+ FWH_DENY_READ = 4,
+};
+
+struct fwh_xxlock_thunk {
+ enum fwh_lock_state val;
+ flstate_t state;
+};
+
+
+#define FWH_XXLOCK_ONEBLOCK_LOCK ((struct fwh_xxlock_thunk){ FWH_DENY_WRITE, FL_LOCKING})
+#define FWH_XXLOCK_ONEBLOCK_UNLOCK ((struct fwh_xxlock_thunk){ FWH_UNLOCKED, FL_UNLOCKING})
+
+/*
+ * This locking/unlock is specific to firmware hub parts. Only one
+ * is known that supports the Intel command set. Firmware
+ * hub parts cannot be interleaved as they are on the LPC bus
+ * so this code has not been tested with interleaved chips,
+ * and will likely fail in that context.
+ */
+static int fwh_xxlock_oneblock(struct map_info *map, struct flchip *chip,
+ unsigned long adr, int len, void *thunk)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ struct fwh_xxlock_thunk *xxlt = (struct fwh_xxlock_thunk *)thunk;
+ int ret;
+
+ /* Refuse the operation if the we cannot look behind the chip */
+ if (chip->start < 0x400000) {
+ DEBUG( MTD_DEBUG_LEVEL3,
+ "MTD %s(): chip->start: %lx wanted >= 0x400000\n",
+ __func__, chip->start );
+ return -EIO;
+ }
+ /*
+ * lock block registers:
+ * - on 64k boundariesand
+ * - bit 1 set high
+ * - block lock registers are 4MiB lower - overflow subtract (danger)
+ *
+ * The address manipulation is first done on the logical address
+ * which is 0 at the start of the chip, and then the offset of
+ * the individual chip is addted to it. Any other order a weird
+ * map offset could cause problems.
+ */
+ adr = (adr & ~0xffffUL) | 0x2;
+ adr += chip->start - 0x400000;
+
+ /*
+ * This is easy because these are writes to registers and not writes
+ * to flash memory - that means that we don't have to check status
+ * and timeout.
+ */
+ cfi_spin_lock(chip->mutex);
+ ret = get_chip(map, chip, adr, FL_LOCKING);
+ if (ret) {
+ cfi_spin_unlock(chip->mutex);
+ return ret;
+ }
+
+ chip->state = xxlt->state;
+ map_write(map, CMD(xxlt->val), adr);
+
+ /* Done and happy. */
+ chip->state = FL_READY;
+ put_chip(map, chip, adr);
+ cfi_spin_unlock(chip->mutex);
+ return 0;
+}
+
+
+static int fwh_lock_varsize(struct mtd_info *mtd, loff_t ofs, size_t len)
+{
+ int ret;
+
+ ret = cfi_varsize_frob(mtd, fwh_xxlock_oneblock, ofs, len,
+ (void *)&FWH_XXLOCK_ONEBLOCK_LOCK);
+
+ return ret;
+}
+
+
+static int fwh_unlock_varsize(struct mtd_info *mtd, loff_t ofs, size_t len)
+{
+ int ret;
+
+ ret = cfi_varsize_frob(mtd, fwh_xxlock_oneblock, ofs, len,
+ (void *)&FWH_XXLOCK_ONEBLOCK_UNLOCK);
+
+ return ret;
+}
+
+static void fixup_use_fwh_lock(struct mtd_info *mtd, void *param)
+{
+ printk(KERN_NOTICE "using fwh lock/unlock method\n");
+ /* Setup for the chips with the fwh lock method */
+ mtd->lock = fwh_lock_varsize;
+ mtd->unlock = fwh_unlock_varsize;
+}
+#endif /* FWH_LOCK_H */
diff --git a/drivers/mtd/chips/gen_probe.c b/drivers/mtd/chips/gen_probe.c
new file mode 100644
index 00000000000..fc982c4671f
--- /dev/null
+++ b/drivers/mtd/chips/gen_probe.c
@@ -0,0 +1,255 @@
+/*
+ * Routines common to all CFI-type probes.
+ * (C) 2001-2003 Red Hat, Inc.
+ * GPL'd
+ * $Id: gen_probe.c,v 1.21 2004/08/14 15:14:05 dwmw2 Exp $
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/cfi.h>
+#include <linux/mtd/gen_probe.h>
+
+static struct mtd_info *check_cmd_set(struct map_info *, int);
+static struct cfi_private *genprobe_ident_chips(struct map_info *map,
+ struct chip_probe *cp);
+static int genprobe_new_chip(struct map_info *map, struct chip_probe *cp,
+ struct cfi_private *cfi);
+
+struct mtd_info *mtd_do_chip_probe(struct map_info *map, struct chip_probe *cp)
+{
+ struct mtd_info *mtd = NULL;
+ struct cfi_private *cfi;
+
+ /* First probe the map to see if we have CFI stuff there. */
+ cfi = genprobe_ident_chips(map, cp);
+
+ if (!cfi)
+ return NULL;
+
+ map->fldrv_priv = cfi;
+ /* OK we liked it. Now find a driver for the command set it talks */
+
+ mtd = check_cmd_set(map, 1); /* First the primary cmdset */
+ if (!mtd)
+ mtd = check_cmd_set(map, 0); /* Then the secondary */
+
+ if (mtd)
+ return mtd;
+
+ printk(KERN_WARNING"gen_probe: No supported Vendor Command Set found\n");
+
+ kfree(cfi->cfiq);
+ kfree(cfi);
+ map->fldrv_priv = NULL;
+ return NULL;
+}
+EXPORT_SYMBOL(mtd_do_chip_probe);
+
+
+static struct cfi_private *genprobe_ident_chips(struct map_info *map, struct chip_probe *cp)
+{
+ struct cfi_private cfi;
+ struct cfi_private *retcfi;
+ unsigned long *chip_map;
+ int i, j, mapsize;
+ int max_chips;
+
+ memset(&cfi, 0, sizeof(cfi));
+
+ /* Call the probetype-specific code with all permutations of
+ interleave and device type, etc. */
+ if (!genprobe_new_chip(map, cp, &cfi)) {
+ /* The probe didn't like it */
+ printk(KERN_DEBUG "%s: Found no %s device at location zero\n",
+ cp->name, map->name);
+ return NULL;
+ }
+
+#if 0 /* Let the CFI probe routine do this sanity check. The Intel and AMD
+ probe routines won't ever return a broken CFI structure anyway,
+ because they make them up themselves.
+ */
+ if (cfi.cfiq->NumEraseRegions == 0) {
+ printk(KERN_WARNING "Number of erase regions is zero\n");
+ kfree(cfi.cfiq);
+ return NULL;
+ }
+#endif
+ cfi.chipshift = cfi.cfiq->DevSize;
+
+ if (cfi_interleave_is_1(&cfi)) {
+ ;
+ } else if (cfi_interleave_is_2(&cfi)) {
+ cfi.chipshift++;
+ } else if (cfi_interleave_is_4((&cfi))) {
+ cfi.chipshift += 2;
+ } else if (cfi_interleave_is_8(&cfi)) {
+ cfi.chipshift += 3;
+ } else {
+ BUG();
+ }
+
+ cfi.numchips = 1;
+
+ /*
+ * Allocate memory for bitmap of valid chips.
+ * Align bitmap storage size to full byte.
+ */
+ max_chips = map->size >> cfi.chipshift;
+ mapsize = (max_chips / 8) + ((max_chips % 8) ? 1 : 0);
+ chip_map = kmalloc(mapsize, GFP_KERNEL);
+ if (!chip_map) {
+ printk(KERN_WARNING "%s: kmalloc failed for CFI chip map\n", map->name);
+ kfree(cfi.cfiq);
+ return NULL;
+ }
+ memset (chip_map, 0, mapsize);
+
+ set_bit(0, chip_map); /* Mark first chip valid */
+
+ /*
+ * Now probe for other chips, checking sensibly for aliases while
+ * we're at it. The new_chip probe above should have let the first
+ * chip in read mode.
+ */
+
+ for (i = 1; i < max_chips; i++) {
+ cp->probe_chip(map, i << cfi.chipshift, chip_map, &cfi);
+ }
+
+ /*
+ * Now allocate the space for the structures we need to return to
+ * our caller, and copy the appropriate data into them.
+ */
+
+ retcfi = kmalloc(sizeof(struct cfi_private) + cfi.numchips * sizeof(struct flchip), GFP_KERNEL);
+
+ if (!retcfi) {
+ printk(KERN_WARNING "%s: kmalloc failed for CFI private structure\n", map->name);
+ kfree(cfi.cfiq);
+ kfree(chip_map);
+ return NULL;
+ }
+
+ memcpy(retcfi, &cfi, sizeof(cfi));
+ memset(&retcfi->chips[0], 0, sizeof(struct flchip) * cfi.numchips);
+
+ for (i = 0, j = 0; (j < cfi.numchips) && (i < max_chips); i++) {
+ if(test_bit(i, chip_map)) {
+ struct flchip *pchip = &retcfi->chips[j++];
+
+ pchip->start = (i << cfi.chipshift);
+ pchip->state = FL_READY;
+ init_waitqueue_head(&pchip->wq);
+ spin_lock_init(&pchip->_spinlock);
+ pchip->mutex = &pchip->_spinlock;
+ }
+ }
+
+ kfree(chip_map);
+ return retcfi;
+}
+
+
+static int genprobe_new_chip(struct map_info *map, struct chip_probe *cp,
+ struct cfi_private *cfi)
+{
+ int min_chips = (map_bankwidth(map)/4?:1); /* At most 4-bytes wide. */
+ int max_chips = map_bankwidth(map); /* And minimum 1 */
+ int nr_chips, type;
+
+ for (nr_chips = min_chips; nr_chips <= max_chips; nr_chips <<= 1) {
+
+ if (!cfi_interleave_supported(nr_chips))
+ continue;
+
+ cfi->interleave = nr_chips;
+
+ /* Minimum device size. Don't look for one 8-bit device
+ in a 16-bit bus, etc. */
+ type = map_bankwidth(map) / nr_chips;
+
+ for (; type <= CFI_DEVICETYPE_X32; type<<=1) {
+ cfi->device_type = type;
+
+ if (cp->probe_chip(map, 0, NULL, cfi))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+typedef struct mtd_info *cfi_cmdset_fn_t(struct map_info *, int);
+
+extern cfi_cmdset_fn_t cfi_cmdset_0001;
+extern cfi_cmdset_fn_t cfi_cmdset_0002;
+extern cfi_cmdset_fn_t cfi_cmdset_0020;
+
+static inline struct mtd_info *cfi_cmdset_unknown(struct map_info *map,
+ int primary)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ __u16 type = primary?cfi->cfiq->P_ID:cfi->cfiq->A_ID;
+#if defined(CONFIG_MODULES) && defined(HAVE_INTER_MODULE)
+ char probename[32];
+ cfi_cmdset_fn_t *probe_function;
+
+ sprintf(probename, "cfi_cmdset_%4.4X", type);
+
+ probe_function = inter_module_get_request(probename, probename);
+
+ if (probe_function) {
+ struct mtd_info *mtd;
+
+ mtd = (*probe_function)(map, primary);
+ /* If it was happy, it'll have increased its own use count */
+ inter_module_put(probename);
+ return mtd;
+ }
+#endif
+ printk(KERN_NOTICE "Support for command set %04X not present\n",
+ type);
+
+ return NULL;
+}
+
+static struct mtd_info *check_cmd_set(struct map_info *map, int primary)
+{
+ struct cfi_private *cfi = map->fldrv_priv;
+ __u16 type = primary?cfi->cfiq->P_ID:cfi->cfiq->A_ID;
+
+ if (type == P_ID_NONE || type == P_ID_RESERVED)
+ return NULL;
+
+ switch(type){
+ /* Urgh. Ifdefs. The version with weak symbols was
+ * _much_ nicer. Shame it didn't seem to work on
+ * anything but x86, really.
+ * But we can't rely in inter_module_get() because
+ * that'd mean we depend on link order.
+ */
+#ifdef CONFIG_MTD_CFI_INTELEXT
+ case 0x0001:
+ case 0x0003:
+ return cfi_cmdset_0001(map, primary);
+#endif
+#ifdef CONFIG_MTD_CFI_AMDSTD
+ case 0x0002:
+ return cfi_cmdset_0002(map, primary);
+#endif
+#ifdef CONFIG_MTD_CFI_STAA
+ case 0x0020:
+ return cfi_cmdset_0020(map, primary);
+#endif
+ }
+
+ return cfi_cmdset_unknown(map, primary);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("Helper routines for flash chip probe code");
diff --git a/drivers/mtd/chips/jedec.c b/drivers/mtd/chips/jedec.c
new file mode 100644
index 00000000000..62d235a9a4e
--- /dev/null
+++ b/drivers/mtd/chips/jedec.c
@@ -0,0 +1,934 @@
+
+/* JEDEC Flash Interface.
+ * This is an older type of interface for self programming flash. It is
+ * commonly use in older AMD chips and is obsolete compared with CFI.
+ * It is called JEDEC because the JEDEC association distributes the ID codes
+ * for the chips.
+ *
+ * See the AMD flash databook for information on how to operate the interface.
+ *
+ * This code does not support anything wider than 8 bit flash chips, I am
+ * not going to guess how to send commands to them, plus I expect they will
+ * all speak CFI..
+ *
+ * $Id: jedec.c,v 1.22 2005/01/05 18:05:11 dwmw2 Exp $
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mtd/jedec.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/compatmac.h>
+
+static struct mtd_info *jedec_probe(struct map_info *);
+static int jedec_probe8(struct map_info *map,unsigned long base,
+ struct jedec_private *priv);
+static int jedec_probe16(struct map_info *map,unsigned long base,
+ struct jedec_private *priv);
+static int jedec_probe32(struct map_info *map,unsigned long base,
+ struct jedec_private *priv);
+static void jedec_flash_chip_scan(struct jedec_private *priv,unsigned long start,
+ unsigned long len);
+static int flash_erase(struct mtd_info *mtd, struct erase_info *instr);
+static int flash_write(struct mtd_info *mtd, loff_t start, size_t len,
+ size_t *retlen, const u_char *buf);
+
+static unsigned long my_bank_size;
+
+/* Listing of parts and sizes. We need this table to learn the sector
+ size of the chip and the total length */
+static const struct JEDECTable JEDEC_table[] = {
+ {
+ .jedec = 0x013D,
+ .name = "AMD Am29F017D",
+ .size = 2*1024*1024,
+ .sectorsize = 64*1024,
+ .capabilities = MTD_CAP_NORFLASH
+ },
+ {
+ .jedec = 0x01AD,
+ .name = "AMD Am29F016",
+ .size = 2*1024*1024,
+ .sectorsize = 64*1024,
+ .capabilities = MTD_CAP_NORFLASH
+ },
+ {
+ .jedec = 0x01D5,
+ .name = "AMD Am29F080",
+ .size = 1*1024*1024,
+ .sectorsize = 64*1024,
+ .capabilities = MTD_CAP_NORFLASH
+ },
+ {
+ .jedec = 0x01A4,
+ .name = "AMD Am29F040",
+ .size = 512*1024,
+ .sectorsize = 64*1024,
+ .capabilities = MTD_CAP_NORFLASH
+ },
+ {
+ .jedec = 0x20E3,
+ .name = "AMD Am29W040B",
+ .size = 512*1024,
+ .sectorsize = 64*1024,
+ .capabilities = MTD_CAP_NORFLASH
+ },
+ {
+ .jedec = 0xC2AD,
+ .name = "Macronix MX29F016",
+ .size = 2*1024*1024,
+ .sectorsize = 64*1024,
+ .capabilities = MTD_CAP_NORFLASH
+ },
+ { .jedec = 0x0 }
+};
+
+static const struct JEDECTable *jedec_idtoinf(__u8 mfr,__u8 id);
+static void jedec_sync(struct mtd_info *mtd) {};
+static int jedec_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf);
+static int jedec_read_banked(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf);
+
+static struct mtd_info *jedec_probe(struct map_info *map);
+
+
+
+static struct mtd_chip_driver jedec_chipdrv = {
+ .probe = jedec_probe,
+ .name = "jedec",
+ .module = THIS_MODULE
+};
+
+/* Probe entry point */
+
+static struct mtd_info *jedec_probe(struct map_info *map)
+{
+ struct mtd_info *MTD;
+ struct jedec_private *priv;
+ unsigned long Base;
+ unsigned long SectorSize;
+ unsigned count;
+ unsigned I,Uniq;
+ char Part[200];
+ memset(&priv,0,sizeof(priv));
+
+ MTD = kmalloc(sizeof(struct mtd_info) + sizeof(struct jedec_private), GFP_KERNEL);
+ if (!MTD)
+ return NULL;
+
+ memset(MTD, 0, sizeof(struct mtd_info) + sizeof(struct jedec_private));
+ priv = (struct jedec_private *)&MTD[1];
+
+ my_bank_size = map->size;
+
+ if (map->size/my_bank_size > MAX_JEDEC_CHIPS)
+ {
+ printk("mtd: Increase MAX_JEDEC_CHIPS, too many banks.\n");
+ kfree(MTD);
+ return NULL;
+ }
+
+ for (Base = 0; Base < map->size; Base += my_bank_size)
+ {
+ // Perhaps zero could designate all tests?
+ if (map->buswidth == 0)
+ map->buswidth = 1;
+
+ if (map->buswidth == 1){
+ if (jedec_probe8(map,Base,priv) == 0) {
+ printk("did recognize jedec chip\n");
+ kfree(MTD);
+ return NULL;
+ }
+ }
+ if (map->buswidth == 2)
+ jedec_probe16(map,Base,priv);
+ if (map->buswidth == 4)
+ jedec_probe32(map,Base,priv);
+ }
+
+ // Get the biggest sector size
+ SectorSize = 0;
+ for (I = 0; priv->chips[I].jedec != 0 && I < MAX_JEDEC_CHIPS; I++)
+ {
+ // printk("priv->chips[%d].jedec is %x\n",I,priv->chips[I].jedec);
+ // printk("priv->chips[%d].sectorsize is %lx\n",I,priv->chips[I].sectorsize);
+ if (priv->chips[I].sectorsize > SectorSize)
+ SectorSize = priv->chips[I].sectorsize;
+ }
+
+ // Quickly ensure that the other sector sizes are factors of the largest
+ for (I = 0; priv->chips[I].jedec != 0 && I < MAX_JEDEC_CHIPS; I++)
+ {
+ if ((SectorSize/priv->chips[I].sectorsize)*priv->chips[I].sectorsize != SectorSize)
+ {
+ printk("mtd: Failed. Device has incompatible mixed sector sizes\n");
+ kfree(MTD);
+ return NULL;
+ }
+ }
+
+ /* Generate a part name that includes the number of different chips and
+ other configuration information */
+ count = 1;
+ strlcpy(Part,map->name,sizeof(Part)-10);
+ strcat(Part," ");
+ Uniq = 0;
+ for (I = 0; priv->chips[I].jedec != 0 && I < MAX_JEDEC_CHIPS; I++)
+ {
+ const struct JEDECTable *JEDEC;
+
+ if (priv->chips[I+1].jedec == priv->chips[I].jedec)
+ {
+ count++;
+ continue;
+ }
+
+ // Locate the chip in the jedec table
+ JEDEC = jedec_idtoinf(priv->chips[I].jedec >> 8,priv->chips[I].jedec);
+ if (JEDEC == 0)
+ {
+ printk("mtd: Internal Error, JEDEC not set\n");
+ kfree(MTD);
+ return NULL;
+ }
+
+ if (Uniq != 0)
+ strcat(Part,",");
+ Uniq++;
+
+ if (count != 1)
+ sprintf(Part+strlen(Part),"%x*[%s]",count,JEDEC->name);
+ else
+ sprintf(Part+strlen(Part),"%s",JEDEC->name);
+ if (strlen(Part) > sizeof(Part)*2/3)
+ break;
+ count = 1;
+ }
+
+ /* Determine if the chips are organized in a linear fashion, or if there
+ are empty banks. Note, the last bank does not count here, only the
+ first banks are important. Holes on non-bank boundaries can not exist
+ due to the way the detection algorithm works. */
+ if (priv->size < my_bank_size)
+ my_bank_size = priv->size;
+ priv->is_banked = 0;
+ //printk("priv->size is %x, my_bank_size is %x\n",priv->size,my_bank_size);
+ //printk("priv->bank_fill[0] is %x\n",priv->bank_fill[0]);
+ if (!priv->size) {
+ printk("priv->size is zero\n");
+ kfree(MTD);
+ return NULL;
+ }
+ if (priv->size/my_bank_size) {
+ if (priv->size/my_bank_size == 1) {
+ priv->size = my_bank_size;
+ }
+ else {
+ for (I = 0; I != priv->size/my_bank_size - 1; I++)
+ {
+ if (priv->bank_fill[I] != my_bank_size)
+ priv->is_banked = 1;
+
+ /* This even could be eliminated, but new de-optimized read/write
+ functions have to be written */
+ printk("priv->bank_fill[%d] is %lx, priv->bank_fill[0] is %lx\n",I,priv->bank_fill[I],priv->bank_fill[0]);
+ if (priv->bank_fill[I] != priv->bank_fill[0])
+ {
+ printk("mtd: Failed. Cannot handle unsymmetric banking\n");
+ kfree(MTD);
+ return NULL;
+ }
+ }
+ }
+ }
+ if (priv->is_banked == 1)
+ strcat(Part,", banked");
+
+ // printk("Part: '%s'\n",Part);
+
+ memset(MTD,0,sizeof(*MTD));
+ // strlcpy(MTD->name,Part,sizeof(MTD->name));
+ MTD->name = map->name;
+ MTD->type = MTD_NORFLASH;
+ MTD->flags = MTD_CAP_NORFLASH;
+ MTD->erasesize = SectorSize*(map->buswidth);
+ // printk("MTD->erasesize is %x\n",(unsigned int)MTD->erasesize);
+ MTD->size = priv->size;
+ // printk("MTD->size is %x\n",(unsigned int)MTD->size);
+ //MTD->module = THIS_MODULE; // ? Maybe this should be the low level module?
+ MTD->erase = flash_erase;
+ if (priv->is_banked == 1)
+ MTD->read = jedec_read_banked;
+ else
+ MTD->read = jedec_read;
+ MTD->write = flash_write;
+ MTD->sync = jedec_sync;
+ MTD->priv = map;
+ map->fldrv_priv = priv;
+ map->fldrv = &jedec_chipdrv;
+ __module_get(THIS_MODULE);
+ return MTD;
+}
+
+/* Helper for the JEDEC function, JEDEC numbers all have odd parity */
+static int checkparity(u_char C)
+{
+ u_char parity = 0;
+ while (C != 0)
+ {
+ parity ^= C & 1;
+ C >>= 1;
+ }
+
+ return parity == 1;
+}
+
+
+/* Take an array of JEDEC numbers that represent interleved flash chips
+ and process them. Check to make sure they are good JEDEC numbers, look
+ them up and then add them to the chip list */
+static int handle_jedecs(struct map_info *map,__u8 *Mfg,__u8 *Id,unsigned Count,
+ unsigned long base,struct jedec_private *priv)
+{
+ unsigned I,J;
+ unsigned long Size;
+ unsigned long SectorSize;
+ const struct JEDECTable *JEDEC;
+
+ // Test #2 JEDEC numbers exhibit odd parity
+ for (I = 0; I != Count; I++)
+ {
+ if (checkparity(Mfg[I]) == 0 || checkparity(Id[I]) == 0)
+ return 0;
+ }
+
+ // Finally, just make sure all the chip sizes are the same
+ JEDEC = jedec_idtoinf(Mfg[0],Id[0]);
+
+ if (JEDEC == 0)
+ {
+ printk("mtd: Found JEDEC flash chip, but do not have a table entry for %x:%x\n",Mfg[0],Mfg[1]);
+ return 0;
+ }
+
+ Size = JEDEC->size;
+ SectorSize = JEDEC->sectorsize;
+ for (I = 0; I != Count; I++)
+ {
+ JEDEC = jedec_idtoinf(Mfg[0],Id[0]);
+ if (JEDEC == 0)
+ {
+ printk("mtd: Found JEDEC flash chip, but do not have a table entry for %x:%x\n",Mfg[0],Mfg[1]);
+ return 0;
+ }
+
+ if (Size != JEDEC->size || SectorSize != JEDEC->sectorsize)
+ {
+ printk("mtd: Failed. Interleved flash does not have matching characteristics\n");
+ return 0;
+ }
+ }
+
+ // Load the Chips
+ for (I = 0; I != MAX_JEDEC_CHIPS; I++)
+ {
+ if (priv->chips[I].jedec == 0)
+ break;
+ }
+
+ if (I + Count > MAX_JEDEC_CHIPS)
+ {
+ printk("mtd: Device has too many chips. Increase MAX_JEDEC_CHIPS\n");
+ return 0;
+ }
+
+ // Add them to the table
+ for (J = 0; J != Count; J++)
+ {
+ unsigned long Bank;
+
+ JEDEC = jedec_idtoinf(Mfg[J],Id[J]);
+ priv->chips[I].jedec = (Mfg[J] << 8) | Id[J];
+ priv->chips[I].size = JEDEC->size;
+ priv->chips[I].sectorsize = JEDEC->sectorsize;
+ priv->chips[I].base = base + J;
+ priv->chips[I].datashift = J*8;
+ priv->chips[I].capabilities = JEDEC->capabilities;
+ priv->chips[I].offset = priv->size + J;
+
+ // log2 n :|
+ priv->chips[I].addrshift = 0;
+ for (Bank = Count; Bank != 1; Bank >>= 1, priv->chips[I].addrshift++);
+
+ // Determine how filled this bank is.
+ Bank = base & (~(my_bank_size-1));
+ if (priv->bank_fill[Bank/my_bank_size] < base +
+ (JEDEC->size << priv->chips[I].addrshift) - Bank)
+ priv->bank_fill[Bank/my_bank_size] = base + (JEDEC->size << priv->chips[I].addrshift) - Bank;
+ I++;
+ }
+
+ priv->size += priv->chips[I-1].size*Count;
+
+ return priv->chips[I-1].size;
+}
+
+/* Lookup the chip information from the JEDEC ID table. */
+static const struct JEDECTable *jedec_idtoinf(__u8 mfr,__u8 id)
+{
+ __u16 Id = (mfr << 8) | id;
+ unsigned long I = 0;
+ for (I = 0; JEDEC_table[I].jedec != 0; I++)
+ if (JEDEC_table[I].jedec == Id)
+ return JEDEC_table + I;
+ return NULL;
+}
+
+// Look for flash using an 8 bit bus interface
+static int jedec_probe8(struct map_info *map,unsigned long base,
+ struct jedec_private *priv)
+{
+ #define flread(x) map_read8(map,base+x)
+ #define flwrite(v,x) map_write8(map,v,base+x)
+
+ const unsigned long AutoSel1 = 0xAA;
+ const unsigned long AutoSel2 = 0x55;
+ const unsigned long AutoSel3 = 0x90;
+ const unsigned long Reset = 0xF0;
+ __u32 OldVal;
+ __u8 Mfg[1];
+ __u8 Id[1];
+ unsigned I;
+ unsigned long Size;
+
+ // Wait for any write/erase operation to settle
+ OldVal = flread(base);
+ for (I = 0; OldVal != flread(base) && I < 10000; I++)
+ OldVal = flread(base);
+
+ // Reset the chip
+ flwrite(Reset,0x555);
+
+ // Send the sequence
+ flwrite(AutoSel1,0x555);
+ flwrite(AutoSel2,0x2AA);
+ flwrite(AutoSel3,0x555);
+
+ // Get the JEDEC numbers
+ Mfg[0] = flread(0);
+ Id[0] = flread(1);
+ // printk("Mfg is %x, Id is %x\n",Mfg[0],Id[0]);
+
+ Size = handle_jedecs(map,Mfg,Id,1,base,priv);
+ // printk("handle_jedecs Size is %x\n",(unsigned int)Size);
+ if (Size == 0)
+ {
+ flwrite(Reset,0x555);
+ return 0;
+ }
+
+
+ // Reset.
+ flwrite(Reset,0x555);
+
+ return 1;
+
+ #undef flread
+ #undef flwrite
+}
+
+// Look for flash using a 16 bit bus interface (ie 2 8-bit chips)
+static int jedec_probe16(struct map_info *map,unsigned long base,
+ struct jedec_private *priv)
+{
+ return 0;
+}
+
+// Look for flash using a 32 bit bus interface (ie 4 8-bit chips)
+static int jedec_probe32(struct map_info *map,unsigned long base,
+ struct jedec_private *priv)
+{
+ #define flread(x) map_read32(map,base+((x)<<2))
+ #define flwrite(v,x) map_write32(map,v,base+((x)<<2))
+
+ const unsigned long AutoSel1 = 0xAAAAAAAA;
+ const unsigned long AutoSel2 = 0x55555555;
+ const unsigned long AutoSel3 = 0x90909090;
+ const unsigned long Reset = 0xF0F0F0F0;
+ __u32 OldVal;
+ __u8 Mfg[4];
+ __u8 Id[4];
+ unsigned I;
+ unsigned long Size;
+
+ // Wait for any write/erase operation to settle
+ OldVal = flread(base);
+ for (I = 0; OldVal != flread(base) && I < 10000; I++)
+ OldVal = flread(base);
+
+ // Reset the chip
+ flwrite(Reset,0x555);
+
+ // Send the sequence
+ flwrite(AutoSel1,0x555);
+ flwrite(AutoSel2,0x2AA);
+ flwrite(AutoSel3,0x555);
+
+ // Test #1, JEDEC numbers are readable from 0x??00/0x??01
+ if (flread(0) != flread(0x100) ||
+ flread(1) != flread(0x101))
+ {
+ flwrite(Reset,0x555);
+ return 0;
+ }
+
+ // Split up the JEDEC numbers
+ OldVal = flread(0);
+ for (I = 0; I != 4; I++)
+ Mfg[I] = (OldVal >> (I*8));
+ OldVal = flread(1);
+ for (I = 0; I != 4; I++)
+ Id[I] = (OldVal >> (I*8));
+
+ Size = handle_jedecs(map,Mfg,Id,4,base,priv);
+ if (Size == 0)
+ {
+ flwrite(Reset,0x555);
+ return 0;
+ }
+
+ /* Check if there is address wrap around within a single bank, if this
+ returns JEDEC numbers then we assume that it is wrap around. Notice
+ we call this routine with the JEDEC return still enabled, if two or
+ more flashes have a truncated address space the probe test will still
+ work */
+ if (base + (Size<<2)+0x555 < map->size &&
+ base + (Size<<2)+0x555 < (base & (~(my_bank_size-1))) + my_bank_size)
+ {
+ if (flread(base+Size) != flread(base+Size + 0x100) ||
+ flread(base+Size + 1) != flread(base+Size + 0x101))
+ {
+ jedec_probe32(map,base+Size,priv);
+ }
+ }
+
+ // Reset.
+ flwrite(0xF0F0F0F0,0x555);
+
+ return 1;
+
+ #undef flread
+ #undef flwrite
+}
+
+/* Linear read. */
+static int jedec_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+
+ map_copy_from(map, buf, from, len);
+ *retlen = len;
+ return 0;
+}
+
+/* Banked read. Take special care to jump past the holes in the bank
+ mapping. This version assumes symetry in the holes.. */
+static int jedec_read_banked(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct jedec_private *priv = map->fldrv_priv;
+
+ *retlen = 0;
+ while (len > 0)
+ {
+ // Determine what bank and offset into that bank the first byte is
+ unsigned long bank = from & (~(priv->bank_fill[0]-1));
+ unsigned long offset = from & (priv->bank_fill[0]-1);
+ unsigned long get = len;
+ if (priv->bank_fill[0] - offset < len)
+ get = priv->bank_fill[0] - offset;
+
+ bank /= priv->bank_fill[0];
+ map_copy_from(map,buf + *retlen,bank*my_bank_size + offset,get);
+
+ len -= get;
+ *retlen += get;
+ from += get;
+ }
+ return 0;
+}
+
+/* Pass the flags value that the flash return before it re-entered read
+ mode. */
+static void jedec_flash_failed(unsigned char code)
+{
+ /* Bit 5 being high indicates that there was an internal device
+ failure, erasure time limits exceeded or something */
+ if ((code & (1 << 5)) != 0)
+ {
+ printk("mtd: Internal Flash failure\n");
+ return;
+ }
+ printk("mtd: Programming didn't take\n");
+}
+
+/* This uses the erasure function described in the AMD Flash Handbook,
+ it will work for flashes with a fixed sector size only. Flashes with
+ a selection of sector sizes (ie the AMD Am29F800B) will need a different
+ routine. This routine tries to parallize erasing multiple chips/sectors
+ where possible */
+static int flash_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ // Does IO to the currently selected chip
+ #define flread(x) map_read8(map,chip->base+((x)<<chip->addrshift))
+ #define flwrite(v,x) map_write8(map,v,chip->base+((x)<<chip->addrshift))
+
+ unsigned long Time = 0;
+ unsigned long NoTime = 0;
+ unsigned long start = instr->addr, len = instr->len;
+ unsigned int I;
+ struct map_info *map = mtd->priv;
+ struct jedec_private *priv = map->fldrv_priv;
+
+ // Verify the arguments..
+ if (start + len > mtd->size ||
+ (start % mtd->erasesize) != 0 ||
+ (len % mtd->erasesize) != 0 ||
+ (len/mtd->erasesize) == 0)
+ return -EINVAL;
+
+ jedec_flash_chip_scan(priv,start,len);
+
+ // Start the erase sequence on each chip
+ for (I = 0; priv->chips[I].jedec != 0 && I < MAX_JEDEC_CHIPS; I++)
+ {
+ unsigned long off;
+ struct jedec_flash_chip *chip = priv->chips + I;
+
+ if (chip->length == 0)
+ continue;
+
+ if (chip->start + chip->length > chip->size)
+ {
+ printk("DIE\n");
+ return -EIO;
+ }
+
+ flwrite(0xF0,chip->start + 0x555);
+ flwrite(0xAA,chip->start + 0x555);
+ flwrite(0x55,chip->start + 0x2AA);
+ flwrite(0x80,chip->start + 0x555);
+ flwrite(0xAA,chip->start + 0x555);
+ flwrite(0x55,chip->start + 0x2AA);
+
+ /* Once we start selecting the erase sectors the delay between each
+ command must not exceed 50us or it will immediately start erasing
+ and ignore the other sectors */
+ for (off = 0; off < len; off += chip->sectorsize)
+ {
+ // Check to make sure we didn't timeout
+ flwrite(0x30,chip->start + off);
+ if (off == 0)
+ continue;
+ if ((flread(chip->start + off) & (1 << 3)) != 0)
+ {
+ printk("mtd: Ack! We timed out the erase timer!\n");
+ return -EIO;
+ }
+ }
+ }
+
+ /* We could split this into a timer routine and return early, performing
+ background erasure.. Maybe later if the need warrents */
+
+ /* Poll the flash for erasure completion, specs say this can take as long
+ as 480 seconds to do all the sectors (for a 2 meg flash).
+ Erasure time is dependent on chip age, temp and wear.. */
+
+ /* This being a generic routine assumes a 32 bit bus. It does read32s
+ and bundles interleved chips into the same grouping. This will work
+ for all bus widths */
+ Time = 0;
+ NoTime = 0;
+ for (I = 0; priv->chips[I].jedec != 0 && I < MAX_JEDEC_CHIPS; I++)
+ {
+ struct jedec_flash_chip *chip = priv->chips + I;
+ unsigned long off = 0;
+ unsigned todo[4] = {0,0,0,0};
+ unsigned todo_left = 0;
+ unsigned J;
+
+ if (chip->length == 0)
+ continue;
+
+ /* Find all chips in this data line, realistically this is all
+ or nothing up to the interleve count */
+ for (J = 0; priv->chips[J].jedec != 0 && J < MAX_JEDEC_CHIPS; J++)
+ {
+ if ((priv->chips[J].base & (~((1<<chip->addrshift)-1))) ==
+ (chip->base & (~((1<<chip->addrshift)-1))))
+ {
+ todo_left++;
+ todo[priv->chips[J].base & ((1<<chip->addrshift)-1)] = 1;
+ }
+ }
+
+ /* printk("todo: %x %x %x %x\n",(short)todo[0],(short)todo[1],
+ (short)todo[2],(short)todo[3]);
+ */
+ while (1)
+ {
+ __u32 Last[4];
+ unsigned long Count = 0;
+
+ /* During erase bit 7 is held low and bit 6 toggles, we watch this,
+ should it stop toggling or go high then the erase is completed,
+ or this is not really flash ;> */
+ switch (map->buswidth) {
+ case 1:
+ Last[0] = map_read8(map,(chip->base >> chip->addrshift) + chip->start + off);
+ Last[1] = map_read8(map,(chip->base >> chip->addrshift) + chip->start + off);
+ Last[2] = map_read8(map,(chip->base >> chip->addrshift) + chip->start + off);
+ break;
+ case 2:
+ Last[0] = map_read16(map,(chip->base >> chip->addrshift) + chip->start + off);
+ Last[1] = map_read16(map,(chip->base >> chip->addrshift) + chip->start + off);
+ Last[2] = map_read16(map,(chip->base >> chip->addrshift) + chip->start + off);
+ break;
+ case 3:
+ Last[0] = map_read32(map,(chip->base >> chip->addrshift) + chip->start + off);
+ Last[1] = map_read32(map,(chip->base >> chip->addrshift) + chip->start + off);
+ Last[2] = map_read32(map,(chip->base >> chip->addrshift) + chip->start + off);
+ break;
+ }
+ Count = 3;
+ while (todo_left != 0)
+ {
+ for (J = 0; J != 4; J++)
+ {
+ __u8 Byte1 = (Last[(Count-1)%4] >> (J*8)) & 0xFF;
+ __u8 Byte2 = (Last[(Count-2)%4] >> (J*8)) & 0xFF;
+ __u8 Byte3 = (Last[(Count-3)%4] >> (J*8)) & 0xFF;
+ if (todo[J] == 0)
+ continue;
+
+ if ((Byte1 & (1 << 7)) == 0 && Byte1 != Byte2)
+ {
+// printk("Check %x %x %x\n",(short)J,(short)Byte1,(short)Byte2);
+ continue;
+ }
+
+ if (Byte1 == Byte2)
+ {
+ jedec_flash_failed(Byte3);
+ return -EIO;
+ }
+
+ todo[J] = 0;
+ todo_left--;
+ }
+
+/* if (NoTime == 0)
+ Time += HZ/10 - schedule_timeout(HZ/10);*/
+ NoTime = 0;
+
+ switch (map->buswidth) {
+ case 1:
+ Last[Count % 4] = map_read8(map,(chip->base >> chip->addrshift) + chip->start + off);
+ break;
+ case 2:
+ Last[Count % 4] = map_read16(map,(chip->base >> chip->addrshift) + chip->start + off);
+ break;
+ case 4:
+ Last[Count % 4] = map_read32(map,(chip->base >> chip->addrshift) + chip->start + off);
+ break;
+ }
+ Count++;
+
+/* // Count time, max of 15s per sector (according to AMD)
+ if (Time > 15*len/mtd->erasesize*HZ)
+ {
+ printk("mtd: Flash Erase Timed out\n");
+ return -EIO;
+ } */
+ }
+
+ // Skip to the next chip if we used chip erase
+ if (chip->length == chip->size)
+ off = chip->size;
+ else
+ off += chip->sectorsize;
+
+ if (off >= chip->length)
+ break;
+ NoTime = 1;
+ }
+
+ for (J = 0; priv->chips[J].jedec != 0 && J < MAX_JEDEC_CHIPS; J++)
+ {
+ if ((priv->chips[J].base & (~((1<<chip->addrshift)-1))) ==
+ (chip->base & (~((1<<chip->addrshift)-1))))
+ priv->chips[J].length = 0;
+ }
+ }
+
+ //printk("done\n");
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+ return 0;
+
+ #undef flread
+ #undef flwrite
+}
+
+/* This is the simple flash writing function. It writes to every byte, in
+ sequence. It takes care of how to properly address the flash if
+ the flash is interleved. It can only be used if all the chips in the
+ array are identical!*/
+static int flash_write(struct mtd_info *mtd, loff_t start, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ /* Does IO to the currently selected chip. It takes the bank addressing
+ base (which is divisible by the chip size) adds the necessary lower bits
+ of addrshift (interleave index) and then adds the control register index. */
+ #define flread(x) map_read8(map,base+(off&((1<<chip->addrshift)-1))+((x)<<chip->addrshift))
+ #define flwrite(v,x) map_write8(map,v,base+(off&((1<<chip->addrshift)-1))+((x)<<chip->addrshift))
+
+ struct map_info *map = mtd->priv;
+ struct jedec_private *priv = map->fldrv_priv;
+ unsigned long base;
+ unsigned long off;
+ size_t save_len = len;
+
+ if (start + len > mtd->size)
+ return -EIO;
+
+ //printk("Here");
+
+ //printk("flash_write: start is %x, len is %x\n",start,(unsigned long)len);
+ while (len != 0)
+ {
+ struct jedec_flash_chip *chip = priv->chips;
+ unsigned long bank;
+ unsigned long boffset;
+
+ // Compute the base of the flash.
+ off = ((unsigned long)start) % (chip->size << chip->addrshift);
+ base = start - off;
+
+ // Perform banked addressing translation.
+ bank = base & (~(priv->bank_fill[0]-1));
+ boffset = base & (priv->bank_fill[0]-1);
+ bank = (bank/priv->bank_fill[0])*my_bank_size;
+ base = bank + boffset;
+
+ // printk("Flasing %X %X %X\n",base,chip->size,len);
+ // printk("off is %x, compare with %x\n",off,chip->size << chip->addrshift);
+
+ // Loop over this page
+ for (; off != (chip->size << chip->addrshift) && len != 0; start++, len--, off++,buf++)
+ {
+ unsigned char oldbyte = map_read8(map,base+off);
+ unsigned char Last[4];
+ unsigned long Count = 0;
+
+ if (oldbyte == *buf) {
+ // printk("oldbyte and *buf is %x,len is %x\n",oldbyte,len);
+ continue;
+ }
+ if (((~oldbyte) & *buf) != 0)
+ printk("mtd: warn: Trying to set a 0 to a 1\n");
+
+ // Write
+ flwrite(0xAA,0x555);
+ flwrite(0x55,0x2AA);
+ flwrite(0xA0,0x555);
+ map_write8(map,*buf,base + off);
+ Last[0] = map_read8(map,base + off);
+ Last[1] = map_read8(map,base + off);
+ Last[2] = map_read8(map,base + off);
+
+ /* Wait for the flash to finish the operation. We store the last 4
+ status bytes that have been retrieved so we can determine why
+ it failed. The toggle bits keep toggling when there is a
+ failure */
+ for (Count = 3; Last[(Count - 1) % 4] != Last[(Count - 2) % 4] &&
+ Count < 10000; Count++)
+ Last[Count % 4] = map_read8(map,base + off);
+ if (Last[(Count - 1) % 4] != *buf)
+ {
+ jedec_flash_failed(Last[(Count - 3) % 4]);
+ return -EIO;
+ }
+ }
+ }
+ *retlen = save_len;
+ return 0;
+}
+
+/* This is used to enhance the speed of the erase routine,
+ when things are being done to multiple chips it is possible to
+ parallize the operations, particularly full memory erases of multi
+ chip memories benifit */
+static void jedec_flash_chip_scan(struct jedec_private *priv,unsigned long start,
+ unsigned long len)
+{
+ unsigned int I;
+
+ // Zero the records
+ for (I = 0; priv->chips[I].jedec != 0 && I < MAX_JEDEC_CHIPS; I++)
+ priv->chips[I].start = priv->chips[I].length = 0;
+
+ // Intersect the region with each chip
+ for (I = 0; priv->chips[I].jedec != 0 && I < MAX_JEDEC_CHIPS; I++)
+ {
+ struct jedec_flash_chip *chip = priv->chips + I;
+ unsigned long ByteStart;
+ unsigned long ChipEndByte = chip->offset + (chip->size << chip->addrshift);
+
+ // End is before this chip or the start is after it
+ if (start+len < chip->offset ||
+ ChipEndByte - (1 << chip->addrshift) < start)
+ continue;
+
+ if (start < chip->offset)
+ {
+ ByteStart = chip->offset;
+ chip->start = 0;
+ }
+ else
+ {
+ chip->start = (start - chip->offset + (1 << chip->addrshift)-1) >> chip->addrshift;
+ ByteStart = start;
+ }
+
+ if (start + len >= ChipEndByte)
+ chip->length = (ChipEndByte - ByteStart) >> chip->addrshift;
+ else
+ chip->length = (start + len - ByteStart + (1 << chip->addrshift)-1) >> chip->addrshift;
+ }
+}
+
+int __init jedec_init(void)
+{
+ register_mtd_chip_driver(&jedec_chipdrv);
+ return 0;
+}
+
+static void __exit jedec_exit(void)
+{
+ unregister_mtd_chip_driver(&jedec_chipdrv);
+}
+
+module_init(jedec_init);
+module_exit(jedec_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jason Gunthorpe <jgg@deltatee.com> et al.");
+MODULE_DESCRIPTION("Old MTD chip driver for JEDEC-compliant flash chips");
diff --git a/drivers/mtd/chips/jedec_probe.c b/drivers/mtd/chips/jedec_probe.c
new file mode 100644
index 00000000000..30325a25ab9
--- /dev/null
+++ b/drivers/mtd/chips/jedec_probe.c
@@ -0,0 +1,2127 @@
+/*
+ Common Flash Interface probe code.
+ (C) 2000 Red Hat. GPL'd.
+ $Id: jedec_probe.c,v 1.61 2004/11/19 20:52:16 thayne Exp $
+ See JEDEC (http://www.jedec.org/) standard JESD21C (section 3.5)
+ for the standard this probe goes back to.
+
+ Occasionally maintained by Thayne Harbaugh tharbaugh at lnxi dot com
+*/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <asm/io.h>
+#include <asm/byteorder.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/cfi.h>
+#include <linux/mtd/gen_probe.h>
+
+/* Manufacturers */
+#define MANUFACTURER_AMD 0x0001
+#define MANUFACTURER_ATMEL 0x001f
+#define MANUFACTURER_FUJITSU 0x0004
+#define MANUFACTURER_HYUNDAI 0x00AD
+#define MANUFACTURER_INTEL 0x0089
+#define MANUFACTURER_MACRONIX 0x00C2
+#define MANUFACTURER_NEC 0x0010
+#define MANUFACTURER_PMC 0x009D
+#define MANUFACTURER_SST 0x00BF
+#define MANUFACTURER_ST 0x0020
+#define MANUFACTURER_TOSHIBA 0x0098
+#define MANUFACTURER_WINBOND 0x00da
+
+
+/* AMD */
+#define AM29DL800BB 0x22C8
+#define AM29DL800BT 0x224A
+
+#define AM29F800BB 0x2258
+#define AM29F800BT 0x22D6
+#define AM29LV400BB 0x22BA
+#define AM29LV400BT 0x22B9
+#define AM29LV800BB 0x225B
+#define AM29LV800BT 0x22DA
+#define AM29LV160DT 0x22C4
+#define AM29LV160DB 0x2249
+#define AM29F017D 0x003D
+#define AM29F016D 0x00AD
+#define AM29F080 0x00D5
+#define AM29F040 0x00A4
+#define AM29LV040B 0x004F
+#define AM29F032B 0x0041
+#define AM29F002T 0x00B0
+
+/* Atmel */
+#define AT49BV512 0x0003
+#define AT29LV512 0x003d
+#define AT49BV16X 0x00C0
+#define AT49BV16XT 0x00C2
+#define AT49BV32X 0x00C8
+#define AT49BV32XT 0x00C9
+
+/* Fujitsu */
+#define MBM29F040C 0x00A4
+#define MBM29LV650UE 0x22D7
+#define MBM29LV320TE 0x22F6
+#define MBM29LV320BE 0x22F9
+#define MBM29LV160TE 0x22C4
+#define MBM29LV160BE 0x2249
+#define MBM29LV800BA 0x225B
+#define MBM29LV800TA 0x22DA
+#define MBM29LV400TC 0x22B9
+#define MBM29LV400BC 0x22BA
+
+/* Hyundai */
+#define HY29F002T 0x00B0
+
+/* Intel */
+#define I28F004B3T 0x00d4
+#define I28F004B3B 0x00d5
+#define I28F400B3T 0x8894
+#define I28F400B3B 0x8895
+#define I28F008S5 0x00a6
+#define I28F016S5 0x00a0
+#define I28F008SA 0x00a2
+#define I28F008B3T 0x00d2
+#define I28F008B3B 0x00d3
+#define I28F800B3T 0x8892
+#define I28F800B3B 0x8893
+#define I28F016S3 0x00aa
+#define I28F016B3T 0x00d0
+#define I28F016B3B 0x00d1
+#define I28F160B3T 0x8890
+#define I28F160B3B 0x8891
+#define I28F320B3T 0x8896
+#define I28F320B3B 0x8897
+#define I28F640B3T 0x8898
+#define I28F640B3B 0x8899
+#define I82802AB 0x00ad
+#define I82802AC 0x00ac
+
+/* Macronix */
+#define MX29LV040C 0x004F
+#define MX29LV160T 0x22C4
+#define MX29LV160B 0x2249
+#define MX29F016 0x00AD
+#define MX29F002T 0x00B0
+#define MX29F004T 0x0045
+#define MX29F004B 0x0046
+
+/* NEC */
+#define UPD29F064115 0x221C
+
+/* PMC */
+#define PM49FL002 0x006D
+#define PM49FL004 0x006E
+#define PM49FL008 0x006A
+
+/* ST - www.st.com */
+#define M29W800DT 0x00D7
+#define M29W800DB 0x005B
+#define M29W160DT 0x22C4
+#define M29W160DB 0x2249
+#define M29W040B 0x00E3
+#define M50FW040 0x002C
+#define M50FW080 0x002D
+#define M50FW016 0x002E
+#define M50LPW080 0x002F
+
+/* SST */
+#define SST29EE020 0x0010
+#define SST29LE020 0x0012
+#define SST29EE512 0x005d
+#define SST29LE512 0x003d
+#define SST39LF800 0x2781
+#define SST39LF160 0x2782
+#define SST39LF512 0x00D4
+#define SST39LF010 0x00D5
+#define SST39LF020 0x00D6
+#define SST39LF040 0x00D7
+#define SST39SF010A 0x00B5
+#define SST39SF020A 0x00B6
+#define SST49LF004B 0x0060
+#define SST49LF008A 0x005a
+#define SST49LF030A 0x001C
+#define SST49LF040A 0x0051
+#define SST49LF080A 0x005B
+
+/* Toshiba */
+#define TC58FVT160 0x00C2
+#define TC58FVB160 0x0043
+#define TC58FVT321 0x009A
+#define TC58FVB321 0x009C
+#define TC58FVT641 0x0093
+#define TC58FVB641 0x0095
+
+/* Winbond */
+#define W49V002A 0x00b0
+
+
+/*
+ * Unlock address sets for AMD command sets.
+ * Intel command sets use the MTD_UADDR_UNNECESSARY.
+ * Each identifier, except MTD_UADDR_UNNECESSARY, and
+ * MTD_UADDR_NO_SUPPORT must be defined below in unlock_addrs[].
+ * MTD_UADDR_NOT_SUPPORTED must be 0 so that structure
+ * initialization need not require initializing all of the
+ * unlock addresses for all bit widths.
+ */
+enum uaddr {
+ MTD_UADDR_NOT_SUPPORTED = 0, /* data width not supported */
+ MTD_UADDR_0x0555_0x02AA,
+ MTD_UADDR_0x0555_0x0AAA,
+ MTD_UADDR_0x5555_0x2AAA,
+ MTD_UADDR_0x0AAA_0x0555,
+ MTD_UADDR_DONT_CARE, /* Requires an arbitrary address */
+ MTD_UADDR_UNNECESSARY, /* Does not require any address */
+};
+
+
+struct unlock_addr {
+ u32 addr1;
+ u32 addr2;
+};
+
+
+/*
+ * I don't like the fact that the first entry in unlock_addrs[]
+ * exists, but is for MTD_UADDR_NOT_SUPPORTED - and, therefore,
+ * should not be used. The problem is that structures with
+ * initializers have extra fields initialized to 0. It is _very_
+ * desireable to have the unlock address entries for unsupported
+ * data widths automatically initialized - that means that
+ * MTD_UADDR_NOT_SUPPORTED must be 0 and the first entry here
+ * must go unused.
+ */
+static const struct unlock_addr unlock_addrs[] = {
+ [MTD_UADDR_NOT_SUPPORTED] = {
+ .addr1 = 0xffff,
+ .addr2 = 0xffff
+ },
+
+ [MTD_UADDR_0x0555_0x02AA] = {
+ .addr1 = 0x0555,
+ .addr2 = 0x02aa
+ },
+
+ [MTD_UADDR_0x0555_0x0AAA] = {
+ .addr1 = 0x0555,
+ .addr2 = 0x0aaa
+ },
+
+ [MTD_UADDR_0x5555_0x2AAA] = {
+ .addr1 = 0x5555,
+ .addr2 = 0x2aaa
+ },
+
+ [MTD_UADDR_0x0AAA_0x0555] = {
+ .addr1 = 0x0AAA,
+ .addr2 = 0x0555
+ },
+
+ [MTD_UADDR_DONT_CARE] = {
+ .addr1 = 0x0000, /* Doesn't matter which address */
+ .addr2 = 0x0000 /* is used - must be last entry */
+ },
+
+ [MTD_UADDR_UNNECESSARY] = {
+ .addr1 = 0x0000,
+ .addr2 = 0x0000
+ }
+};
+
+
+struct amd_flash_info {
+ const __u16 mfr_id;
+ const __u16 dev_id;
+ const char *name;
+ const int DevSize;
+ const int NumEraseRegions;
+ const int CmdSet;
+ const __u8 uaddr[4]; /* unlock addrs for 8, 16, 32, 64 */
+ const ulong regions[6];
+};
+
+#define ERASEINFO(size,blocks) (size<<8)|(blocks-1)
+
+#define SIZE_64KiB 16
+#define SIZE_128KiB 17
+#define SIZE_256KiB 18
+#define SIZE_512KiB 19
+#define SIZE_1MiB 20
+#define SIZE_2MiB 21
+#define SIZE_4MiB 22
+#define SIZE_8MiB 23
+
+
+/*
+ * Please keep this list ordered by manufacturer!
+ * Fortunately, the list isn't searched often and so a
+ * slow, linear search isn't so bad.
+ */
+static const struct amd_flash_info jedec_table[] = {
+ {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29F032B,
+ .name = "AMD AM29F032B",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
+ },
+ .DevSize = SIZE_4MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,64)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29LV160DT,
+ .name = "AMD AM29LV160DT",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,31),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29LV160DB,
+ .name = "AMD AM29LV160DB",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x10000,31)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29LV400BB,
+ .name = "AMD AM29LV400BB",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x10000,7)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29LV400BT,
+ .name = "AMD AM29LV400BT",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,7),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29LV800BB,
+ .name = "AMD AM29LV800BB",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x10000,15),
+ }
+ }, {
+/* add DL */
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29DL800BB,
+ .name = "AMD AM29DL800BB",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 6,
+ .regions = {
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,4),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x10000,14)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29DL800BT,
+ .name = "AMD AM29DL800BT",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 6,
+ .regions = {
+ ERASEINFO(0x10000,14),
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,4),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x04000,1)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29F800BB,
+ .name = "AMD AM29F800BB",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x10000,15),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29LV800BT,
+ .name = "AMD AM29LV800BT",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,15),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29F800BT,
+ .name = "AMD AM29F800BT",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,15),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29F017D,
+ .name = "AMD AM29F017D",
+ .uaddr = {
+ [0] = MTD_UADDR_DONT_CARE /* x8 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,32),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29F016D,
+ .name = "AMD AM29F016D",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,32),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29F080,
+ .name = "AMD AM29F080",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,16),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29F040,
+ .name = "AMD AM29F040",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29LV040B,
+ .name = "AMD AM29LV040B",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_AMD,
+ .dev_id = AM29F002T,
+ .name = "AMD AM29F002T",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
+ },
+ .DevSize = SIZE_256KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,3),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ATMEL,
+ .dev_id = AT49BV512,
+ .name = "Atmel AT49BV512",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_64KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,1)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ATMEL,
+ .dev_id = AT29LV512,
+ .name = "Atmel AT29LV512",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_64KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x80,256),
+ ERASEINFO(0x80,256)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ATMEL,
+ .dev_id = AT49BV16X,
+ .name = "Atmel AT49BV16X",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x0AAA, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x0AAA /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x02000,8),
+ ERASEINFO(0x10000,31)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ATMEL,
+ .dev_id = AT49BV16XT,
+ .name = "Atmel AT49BV16XT",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x0AAA, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x0AAA /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x10000,31),
+ ERASEINFO(0x02000,8)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ATMEL,
+ .dev_id = AT49BV32X,
+ .name = "Atmel AT49BV32X",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x0AAA, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x0AAA /* x16 */
+ },
+ .DevSize = SIZE_4MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x02000,8),
+ ERASEINFO(0x10000,63)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ATMEL,
+ .dev_id = AT49BV32XT,
+ .name = "Atmel AT49BV32XT",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x0AAA, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x0AAA /* x16 */
+ },
+ .DevSize = SIZE_4MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x10000,63),
+ ERASEINFO(0x02000,8)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_FUJITSU,
+ .dev_id = MBM29F040C,
+ .name = "Fujitsu MBM29F040C",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,8)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_FUJITSU,
+ .dev_id = MBM29LV650UE,
+ .name = "Fujitsu MBM29LV650UE",
+ .uaddr = {
+ [0] = MTD_UADDR_DONT_CARE /* x16 */
+ },
+ .DevSize = SIZE_8MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,128)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_FUJITSU,
+ .dev_id = MBM29LV320TE,
+ .name = "Fujitsu MBM29LV320TE",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_4MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x10000,63),
+ ERASEINFO(0x02000,8)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_FUJITSU,
+ .dev_id = MBM29LV320BE,
+ .name = "Fujitsu MBM29LV320BE",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_4MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x02000,8),
+ ERASEINFO(0x10000,63)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_FUJITSU,
+ .dev_id = MBM29LV160TE,
+ .name = "Fujitsu MBM29LV160TE",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,31),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_FUJITSU,
+ .dev_id = MBM29LV160BE,
+ .name = "Fujitsu MBM29LV160BE",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x10000,31)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_FUJITSU,
+ .dev_id = MBM29LV800BA,
+ .name = "Fujitsu MBM29LV800BA",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x10000,15)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_FUJITSU,
+ .dev_id = MBM29LV800TA,
+ .name = "Fujitsu MBM29LV800TA",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,15),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_FUJITSU,
+ .dev_id = MBM29LV400BC,
+ .name = "Fujitsu MBM29LV400BC",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x10000,7)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_FUJITSU,
+ .dev_id = MBM29LV400TC,
+ .name = "Fujitsu MBM29LV400TC",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,7),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_HYUNDAI,
+ .dev_id = HY29F002T,
+ .name = "Hyundai HY29F002T",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
+ },
+ .DevSize = SIZE_256KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,3),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F004B3B,
+ .name = "Intel 28F004B3B",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x02000, 8),
+ ERASEINFO(0x10000, 7),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F004B3T,
+ .name = "Intel 28F004B3T",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x10000, 7),
+ ERASEINFO(0x02000, 8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F400B3B,
+ .name = "Intel 28F400B3B",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ [1] = MTD_UADDR_UNNECESSARY, /* x16 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x02000, 8),
+ ERASEINFO(0x10000, 7),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F400B3T,
+ .name = "Intel 28F400B3T",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ [1] = MTD_UADDR_UNNECESSARY, /* x16 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x10000, 7),
+ ERASEINFO(0x02000, 8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F008B3B,
+ .name = "Intel 28F008B3B",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x02000, 8),
+ ERASEINFO(0x10000, 15),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F008B3T,
+ .name = "Intel 28F008B3T",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x10000, 15),
+ ERASEINFO(0x02000, 8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F008S5,
+ .name = "Intel 28F008S5",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_INTEL_EXT,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,16),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F016S5,
+ .name = "Intel 28F016S5",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_INTEL_EXT,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,32),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F008SA,
+ .name = "Intel 28F008SA",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000, 16),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F800B3B,
+ .name = "Intel 28F800B3B",
+ .uaddr = {
+ [1] = MTD_UADDR_UNNECESSARY, /* x16 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x02000, 8),
+ ERASEINFO(0x10000, 15),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F800B3T,
+ .name = "Intel 28F800B3T",
+ .uaddr = {
+ [1] = MTD_UADDR_UNNECESSARY, /* x16 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x10000, 15),
+ ERASEINFO(0x02000, 8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F016B3B,
+ .name = "Intel 28F016B3B",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x02000, 8),
+ ERASEINFO(0x10000, 31),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F016S3,
+ .name = "Intel I28F016S3",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000, 32),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F016B3T,
+ .name = "Intel 28F016B3T",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x10000, 31),
+ ERASEINFO(0x02000, 8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F160B3B,
+ .name = "Intel 28F160B3B",
+ .uaddr = {
+ [1] = MTD_UADDR_UNNECESSARY, /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x02000, 8),
+ ERASEINFO(0x10000, 31),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F160B3T,
+ .name = "Intel 28F160B3T",
+ .uaddr = {
+ [1] = MTD_UADDR_UNNECESSARY, /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x10000, 31),
+ ERASEINFO(0x02000, 8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F320B3B,
+ .name = "Intel 28F320B3B",
+ .uaddr = {
+ [1] = MTD_UADDR_UNNECESSARY, /* x16 */
+ },
+ .DevSize = SIZE_4MiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x02000, 8),
+ ERASEINFO(0x10000, 63),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F320B3T,
+ .name = "Intel 28F320B3T",
+ .uaddr = {
+ [1] = MTD_UADDR_UNNECESSARY, /* x16 */
+ },
+ .DevSize = SIZE_4MiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x10000, 63),
+ ERASEINFO(0x02000, 8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F640B3B,
+ .name = "Intel 28F640B3B",
+ .uaddr = {
+ [1] = MTD_UADDR_UNNECESSARY, /* x16 */
+ },
+ .DevSize = SIZE_8MiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x02000, 8),
+ ERASEINFO(0x10000, 127),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I28F640B3T,
+ .name = "Intel 28F640B3T",
+ .uaddr = {
+ [1] = MTD_UADDR_UNNECESSARY, /* x16 */
+ },
+ .DevSize = SIZE_8MiB,
+ .CmdSet = P_ID_INTEL_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x10000, 127),
+ ERASEINFO(0x02000, 8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I82802AB,
+ .name = "Intel 82802AB",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_INTEL_EXT,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_INTEL,
+ .dev_id = I82802AC,
+ .name = "Intel 82802AC",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_INTEL_EXT,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,16),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_MACRONIX,
+ .dev_id = MX29LV040C,
+ .name = "Macronix MX29LV040C",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA, /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_MACRONIX,
+ .dev_id = MX29LV160T,
+ .name = "MXIC MX29LV160T",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,31),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_NEC,
+ .dev_id = UPD29F064115,
+ .name = "NEC uPD29F064115",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_8MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 3,
+ .regions = {
+ ERASEINFO(0x2000,8),
+ ERASEINFO(0x10000,126),
+ ERASEINFO(0x2000,8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_MACRONIX,
+ .dev_id = MX29LV160B,
+ .name = "MXIC MX29LV160B",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x10000,31)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_MACRONIX,
+ .dev_id = MX29F016,
+ .name = "Macronix MX29F016",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,32),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_MACRONIX,
+ .dev_id = MX29F004T,
+ .name = "Macronix MX29F004T",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,7),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_MACRONIX,
+ .dev_id = MX29F004B,
+ .name = "Macronix MX29F004B",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x10000,7),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_MACRONIX,
+ .dev_id = MX29F002T,
+ .name = "Macronix MX29F002T",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
+ },
+ .DevSize = SIZE_256KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,3),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_PMC,
+ .dev_id = PM49FL002,
+ .name = "PMC Pm49FL002",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_256KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO( 0x01000, 64 )
+ }
+ }, {
+ .mfr_id = MANUFACTURER_PMC,
+ .dev_id = PM49FL004,
+ .name = "PMC Pm49FL004",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO( 0x01000, 128 )
+ }
+ }, {
+ .mfr_id = MANUFACTURER_PMC,
+ .dev_id = PM49FL008,
+ .name = "PMC Pm49FL008",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO( 0x01000, 256 )
+ }
+ }, {
+ .mfr_id = MANUFACTURER_SST,
+ .dev_id = SST39LF512,
+ .name = "SST 39LF512",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_64KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x01000,16),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_SST,
+ .dev_id = SST39LF010,
+ .name = "SST 39LF010",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_128KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x01000,32),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_SST,
+ .dev_id = SST29EE020,
+ .name = "SST 29EE020",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_256KiB,
+ .CmdSet = P_ID_SST_PAGE,
+ .NumEraseRegions= 1,
+ .regions = {ERASEINFO(0x01000,64),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_SST,
+ .dev_id = SST29LE020,
+ .name = "SST 29LE020",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_256KiB,
+ .CmdSet = P_ID_SST_PAGE,
+ .NumEraseRegions= 1,
+ .regions = {ERASEINFO(0x01000,64),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_SST,
+ .dev_id = SST39LF020,
+ .name = "SST 39LF020",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_256KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x01000,64),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_SST,
+ .dev_id = SST39LF040,
+ .name = "SST 39LF040",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x01000,128),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_SST,
+ .dev_id = SST39SF010A,
+ .name = "SST 39SF010A",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_128KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x01000,32),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_SST,
+ .dev_id = SST39SF020A,
+ .name = "SST 39SF020A",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_256KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x01000,64),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_SST,
+ .dev_id = SST49LF004B,
+ .name = "SST 49LF004B",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x01000,128),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_SST,
+ .dev_id = SST49LF008A,
+ .name = "SST 49LF008A",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x01000,256),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_SST,
+ .dev_id = SST49LF030A,
+ .name = "SST 49LF030A",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x01000,96),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_SST,
+ .dev_id = SST49LF040A,
+ .name = "SST 49LF040A",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x01000,128),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_SST,
+ .dev_id = SST49LF080A,
+ .name = "SST 49LF080A",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x01000,256),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_SST, /* should be CFI */
+ .dev_id = SST39LF160,
+ .name = "SST 39LF160",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA, /* x8 */
+ [1] = MTD_UADDR_0x5555_0x2AAA /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x1000,256),
+ ERASEINFO(0x1000,256)
+ }
+
+ }, {
+ .mfr_id = MANUFACTURER_ST, /* FIXME - CFI device? */
+ .dev_id = M29W800DT,
+ .name = "ST M29W800DT",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA, /* x8 */
+ [1] = MTD_UADDR_0x5555_0x2AAA /* x16 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,15),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ST, /* FIXME - CFI device? */
+ .dev_id = M29W800DB,
+ .name = "ST M29W800DB",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA, /* x8 */
+ [1] = MTD_UADDR_0x5555_0x2AAA /* x16 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x10000,15)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ST, /* FIXME - CFI device? */
+ .dev_id = M29W160DT,
+ .name = "ST M29W160DT",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,31),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ST, /* FIXME - CFI device? */
+ .dev_id = M29W160DB,
+ .name = "ST M29W160DB",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x10000,31)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ST,
+ .dev_id = M29W040B,
+ .name = "ST M29W040B",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0555_0x02AA /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ST,
+ .dev_id = M50FW040,
+ .name = "ST M50FW040",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_512KiB,
+ .CmdSet = P_ID_INTEL_EXT,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,8),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ST,
+ .dev_id = M50FW080,
+ .name = "ST M50FW080",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_INTEL_EXT,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,16),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ST,
+ .dev_id = M50FW016,
+ .name = "ST M50FW016",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_INTEL_EXT,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,32),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_ST,
+ .dev_id = M50LPW080,
+ .name = "ST M50LPW080",
+ .uaddr = {
+ [0] = MTD_UADDR_UNNECESSARY, /* x8 */
+ },
+ .DevSize = SIZE_1MiB,
+ .CmdSet = P_ID_INTEL_EXT,
+ .NumEraseRegions= 1,
+ .regions = {
+ ERASEINFO(0x10000,16),
+ }
+ }, {
+ .mfr_id = MANUFACTURER_TOSHIBA,
+ .dev_id = TC58FVT160,
+ .name = "Toshiba TC58FVT160",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000,31),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x04000,1)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_TOSHIBA,
+ .dev_id = TC58FVB160,
+ .name = "Toshiba TC58FVB160",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA /* x16 */
+ },
+ .DevSize = SIZE_2MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x04000,1),
+ ERASEINFO(0x02000,2),
+ ERASEINFO(0x08000,1),
+ ERASEINFO(0x10000,31)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_TOSHIBA,
+ .dev_id = TC58FVB321,
+ .name = "Toshiba TC58FVB321",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA /* x16 */
+ },
+ .DevSize = SIZE_4MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x02000,8),
+ ERASEINFO(0x10000,63)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_TOSHIBA,
+ .dev_id = TC58FVT321,
+ .name = "Toshiba TC58FVT321",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA /* x16 */
+ },
+ .DevSize = SIZE_4MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x10000,63),
+ ERASEINFO(0x02000,8)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_TOSHIBA,
+ .dev_id = TC58FVB641,
+ .name = "Toshiba TC58FVB641",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_8MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x02000,8),
+ ERASEINFO(0x10000,127)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_TOSHIBA,
+ .dev_id = TC58FVT641,
+ .name = "Toshiba TC58FVT641",
+ .uaddr = {
+ [0] = MTD_UADDR_0x0AAA_0x0555, /* x8 */
+ [1] = MTD_UADDR_0x0555_0x02AA, /* x16 */
+ },
+ .DevSize = SIZE_8MiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 2,
+ .regions = {
+ ERASEINFO(0x10000,127),
+ ERASEINFO(0x02000,8)
+ }
+ }, {
+ .mfr_id = MANUFACTURER_WINBOND,
+ .dev_id = W49V002A,
+ .name = "Winbond W49V002A",
+ .uaddr = {
+ [0] = MTD_UADDR_0x5555_0x2AAA /* x8 */
+ },
+ .DevSize = SIZE_256KiB,
+ .CmdSet = P_ID_AMD_STD,
+ .NumEraseRegions= 4,
+ .regions = {
+ ERASEINFO(0x10000, 3),
+ ERASEINFO(0x08000, 1),
+ ERASEINFO(0x02000, 2),
+ ERASEINFO(0x04000, 1),
+ }
+ }
+};
+
+
+static int cfi_jedec_setup(struct cfi_private *p_cfi, int index);
+
+static int jedec_probe_chip(struct map_info *map, __u32 base,
+ unsigned long *chip_map, struct cfi_private *cfi);
+
+static struct mtd_info *jedec_probe(struct map_info *map);
+
+static inline u32 jedec_read_mfr(struct map_info *map, __u32 base,
+ struct cfi_private *cfi)
+{
+ map_word result;
+ unsigned long mask;
+ u32 ofs = cfi_build_cmd_addr(0, cfi_interleave(cfi), cfi->device_type);
+ mask = (1 << (cfi->device_type * 8)) -1;
+ result = map_read(map, base + ofs);
+ return result.x[0] & mask;
+}
+
+static inline u32 jedec_read_id(struct map_info *map, __u32 base,
+ struct cfi_private *cfi)
+{
+ map_word result;
+ unsigned long mask;
+ u32 ofs = cfi_build_cmd_addr(1, cfi_interleave(cfi), cfi->device_type);
+ mask = (1 << (cfi->device_type * 8)) -1;
+ result = map_read(map, base + ofs);
+ return result.x[0] & mask;
+}
+
+static inline void jedec_reset(u32 base, struct map_info *map,
+ struct cfi_private *cfi)
+{
+ /* Reset */
+
+ /* after checking the datasheets for SST, MACRONIX and ATMEL
+ * (oh and incidentaly the jedec spec - 3.5.3.3) the reset
+ * sequence is *supposed* to be 0xaa at 0x5555, 0x55 at
+ * 0x2aaa, 0xF0 at 0x5555 this will not affect the AMD chips
+ * as they will ignore the writes and dont care what address
+ * the F0 is written to */
+ if(cfi->addr_unlock1) {
+ DEBUG( MTD_DEBUG_LEVEL3,
+ "reset unlock called %x %x \n",
+ cfi->addr_unlock1,cfi->addr_unlock2);
+ cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, cfi->device_type, NULL);
+ }
+
+ cfi_send_gen_cmd(0xF0, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL);
+ /* Some misdesigned intel chips do not respond for 0xF0 for a reset,
+ * so ensure we're in read mode. Send both the Intel and the AMD command
+ * for this. Intel uses 0xff for this, AMD uses 0xff for NOP, so
+ * this should be safe.
+ */
+ cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL);
+ /* FIXME - should have reset delay before continuing */
+}
+
+
+static inline __u8 finfo_uaddr(const struct amd_flash_info *finfo, int device_type)
+{
+ int uaddr_idx;
+ __u8 uaddr = MTD_UADDR_NOT_SUPPORTED;
+
+ switch ( device_type ) {
+ case CFI_DEVICETYPE_X8: uaddr_idx = 0; break;
+ case CFI_DEVICETYPE_X16: uaddr_idx = 1; break;
+ case CFI_DEVICETYPE_X32: uaddr_idx = 2; break;
+ default:
+ printk(KERN_NOTICE "MTD: %s(): unknown device_type %d\n",
+ __func__, device_type);
+ goto uaddr_done;
+ }
+
+ uaddr = finfo->uaddr[uaddr_idx];
+
+ if (uaddr != MTD_UADDR_NOT_SUPPORTED ) {
+ /* ASSERT("The unlock addresses for non-8-bit mode
+ are bollocks. We don't really need an array."); */
+ uaddr = finfo->uaddr[0];
+ }
+
+ uaddr_done:
+ return uaddr;
+}
+
+
+static int cfi_jedec_setup(struct cfi_private *p_cfi, int index)
+{
+ int i,num_erase_regions;
+ __u8 uaddr;
+
+ printk("Found: %s\n",jedec_table[index].name);
+
+ num_erase_regions = jedec_table[index].NumEraseRegions;
+
+ p_cfi->cfiq = kmalloc(sizeof(struct cfi_ident) + num_erase_regions * 4, GFP_KERNEL);
+ if (!p_cfi->cfiq) {
+ //xx printk(KERN_WARNING "%s: kmalloc failed for CFI ident structure\n", map->name);
+ return 0;
+ }
+
+ memset(p_cfi->cfiq,0,sizeof(struct cfi_ident));
+
+ p_cfi->cfiq->P_ID = jedec_table[index].CmdSet;
+ p_cfi->cfiq->NumEraseRegions = jedec_table[index].NumEraseRegions;
+ p_cfi->cfiq->DevSize = jedec_table[index].DevSize;
+ p_cfi->cfi_mode = CFI_MODE_JEDEC;
+
+ for (i=0; i<num_erase_regions; i++){
+ p_cfi->cfiq->EraseRegionInfo[i] = jedec_table[index].regions[i];
+ }
+ p_cfi->cmdset_priv = NULL;
+
+ /* This may be redundant for some cases, but it doesn't hurt */
+ p_cfi->mfr = jedec_table[index].mfr_id;
+ p_cfi->id = jedec_table[index].dev_id;
+
+ uaddr = finfo_uaddr(&jedec_table[index], p_cfi->device_type);
+ if ( uaddr == MTD_UADDR_NOT_SUPPORTED ) {
+ kfree( p_cfi->cfiq );
+ return 0;
+ }
+
+ p_cfi->addr_unlock1 = unlock_addrs[uaddr].addr1;
+ p_cfi->addr_unlock2 = unlock_addrs[uaddr].addr2;
+
+ return 1; /* ok */
+}
+
+
+/*
+ * There is a BIG problem properly ID'ing the JEDEC devic and guaranteeing
+ * the mapped address, unlock addresses, and proper chip ID. This function
+ * attempts to minimize errors. It is doubtfull that this probe will ever
+ * be perfect - consequently there should be some module parameters that
+ * could be manually specified to force the chip info.
+ */
+static inline int jedec_match( __u32 base,
+ struct map_info *map,
+ struct cfi_private *cfi,
+ const struct amd_flash_info *finfo )
+{
+ int rc = 0; /* failure until all tests pass */
+ u32 mfr, id;
+ __u8 uaddr;
+
+ /*
+ * The IDs must match. For X16 and X32 devices operating in
+ * a lower width ( X8 or X16 ), the device ID's are usually just
+ * the lower byte(s) of the larger device ID for wider mode. If
+ * a part is found that doesn't fit this assumption (device id for
+ * smaller width mode is completely unrealated to full-width mode)
+ * then the jedec_table[] will have to be augmented with the IDs
+ * for different widths.
+ */
+ switch (cfi->device_type) {
+ case CFI_DEVICETYPE_X8:
+ mfr = (__u8)finfo->mfr_id;
+ id = (__u8)finfo->dev_id;
+ break;
+ case CFI_DEVICETYPE_X16:
+ mfr = (__u16)finfo->mfr_id;
+ id = (__u16)finfo->dev_id;
+ break;
+ case CFI_DEVICETYPE_X32:
+ mfr = (__u16)finfo->mfr_id;
+ id = (__u32)finfo->dev_id;
+ break;
+ default:
+ printk(KERN_WARNING
+ "MTD %s(): Unsupported device type %d\n",
+ __func__, cfi->device_type);
+ goto match_done;
+ }
+ if ( cfi->mfr != mfr || cfi->id != id ) {
+ goto match_done;
+ }
+
+ /* the part size must fit in the memory window */
+ DEBUG( MTD_DEBUG_LEVEL3,
+ "MTD %s(): Check fit 0x%.8x + 0x%.8x = 0x%.8x\n",
+ __func__, base, 1 << finfo->DevSize, base + (1 << finfo->DevSize) );
+ if ( base + cfi_interleave(cfi) * ( 1 << finfo->DevSize ) > map->size ) {
+ DEBUG( MTD_DEBUG_LEVEL3,
+ "MTD %s(): 0x%.4x 0x%.4x %dKiB doesn't fit\n",
+ __func__, finfo->mfr_id, finfo->dev_id,
+ 1 << finfo->DevSize );
+ goto match_done;
+ }
+
+ uaddr = finfo_uaddr(finfo, cfi->device_type);
+ if ( uaddr == MTD_UADDR_NOT_SUPPORTED ) {
+ goto match_done;
+ }
+
+ DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): check unlock addrs 0x%.4x 0x%.4x\n",
+ __func__, cfi->addr_unlock1, cfi->addr_unlock2 );
+ if ( MTD_UADDR_UNNECESSARY != uaddr && MTD_UADDR_DONT_CARE != uaddr
+ && ( unlock_addrs[uaddr].addr1 != cfi->addr_unlock1 ||
+ unlock_addrs[uaddr].addr2 != cfi->addr_unlock2 ) ) {
+ DEBUG( MTD_DEBUG_LEVEL3,
+ "MTD %s(): 0x%.4x 0x%.4x did not match\n",
+ __func__,
+ unlock_addrs[uaddr].addr1,
+ unlock_addrs[uaddr].addr2);
+ goto match_done;
+ }
+
+ /*
+ * Make sure the ID's dissappear when the device is taken out of
+ * ID mode. The only time this should fail when it should succeed
+ * is when the ID's are written as data to the same
+ * addresses. For this rare and unfortunate case the chip
+ * cannot be probed correctly.
+ * FIXME - write a driver that takes all of the chip info as
+ * module parameters, doesn't probe but forces a load.
+ */
+ DEBUG( MTD_DEBUG_LEVEL3,
+ "MTD %s(): check ID's disappear when not in ID mode\n",
+ __func__ );
+ jedec_reset( base, map, cfi );
+ mfr = jedec_read_mfr( map, base, cfi );
+ id = jedec_read_id( map, base, cfi );
+ if ( mfr == cfi->mfr && id == cfi->id ) {
+ DEBUG( MTD_DEBUG_LEVEL3,
+ "MTD %s(): ID 0x%.2x:0x%.2x did not change after reset:\n"
+ "You might need to manually specify JEDEC parameters.\n",
+ __func__, cfi->mfr, cfi->id );
+ goto match_done;
+ }
+
+ /* all tests passed - mark as success */
+ rc = 1;
+
+ /*
+ * Put the device back in ID mode - only need to do this if we
+ * were truly frobbing a real device.
+ */
+ DEBUG( MTD_DEBUG_LEVEL3, "MTD %s(): return to ID mode\n", __func__ );
+ if(cfi->addr_unlock1) {
+ cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, cfi->device_type, NULL);
+ }
+ cfi_send_gen_cmd(0x90, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL);
+ /* FIXME - should have a delay before continuing */
+
+ match_done:
+ return rc;
+}
+
+
+static int jedec_probe_chip(struct map_info *map, __u32 base,
+ unsigned long *chip_map, struct cfi_private *cfi)
+{
+ int i;
+ enum uaddr uaddr_idx = MTD_UADDR_NOT_SUPPORTED;
+ u32 probe_offset1, probe_offset2;
+
+ retry:
+ if (!cfi->numchips) {
+ uaddr_idx++;
+
+ if (MTD_UADDR_UNNECESSARY == uaddr_idx)
+ return 0;
+
+ cfi->addr_unlock1 = unlock_addrs[uaddr_idx].addr1;
+ cfi->addr_unlock2 = unlock_addrs[uaddr_idx].addr2;
+ }
+
+ /* Make certain we aren't probing past the end of map */
+ if (base >= map->size) {
+ printk(KERN_NOTICE
+ "Probe at base(0x%08x) past the end of the map(0x%08lx)\n",
+ base, map->size -1);
+ return 0;
+
+ }
+ /* Ensure the unlock addresses we try stay inside the map */
+ probe_offset1 = cfi_build_cmd_addr(
+ cfi->addr_unlock1,
+ cfi_interleave(cfi),
+ cfi->device_type);
+ probe_offset2 = cfi_build_cmd_addr(
+ cfi->addr_unlock1,
+ cfi_interleave(cfi),
+ cfi->device_type);
+ if ( ((base + probe_offset1 + map_bankwidth(map)) >= map->size) ||
+ ((base + probe_offset2 + map_bankwidth(map)) >= map->size))
+ {
+ goto retry;
+ }
+
+ /* Reset */
+ jedec_reset(base, map, cfi);
+
+ /* Autoselect Mode */
+ if(cfi->addr_unlock1) {
+ cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL);
+ cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, cfi->device_type, NULL);
+ }
+ cfi_send_gen_cmd(0x90, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL);
+ /* FIXME - should have a delay before continuing */
+
+ if (!cfi->numchips) {
+ /* This is the first time we're called. Set up the CFI
+ stuff accordingly and return */
+
+ cfi->mfr = jedec_read_mfr(map, base, cfi);
+ cfi->id = jedec_read_id(map, base, cfi);
+ DEBUG(MTD_DEBUG_LEVEL3,
+ "Search for id:(%02x %02x) interleave(%d) type(%d)\n",
+ cfi->mfr, cfi->id, cfi_interleave(cfi), cfi->device_type);
+ for (i=0; i<sizeof(jedec_table)/sizeof(jedec_table[0]); i++) {
+ if ( jedec_match( base, map, cfi, &jedec_table[i] ) ) {
+ DEBUG( MTD_DEBUG_LEVEL3,
+ "MTD %s(): matched device 0x%x,0x%x unlock_addrs: 0x%.4x 0x%.4x\n",
+ __func__, cfi->mfr, cfi->id,
+ cfi->addr_unlock1, cfi->addr_unlock2 );
+ if (!cfi_jedec_setup(cfi, i))
+ return 0;
+ goto ok_out;
+ }
+ }
+ goto retry;
+ } else {
+ __u16 mfr;
+ __u16 id;
+
+ /* Make sure it is a chip of the same manufacturer and id */
+ mfr = jedec_read_mfr(map, base, cfi);
+ id = jedec_read_id(map, base, cfi);
+
+ if ((mfr != cfi->mfr) || (id != cfi->id)) {
+ printk(KERN_DEBUG "%s: Found different chip or no chip at all (mfr 0x%x, id 0x%x) at 0x%x\n",
+ map->name, mfr, id, base);
+ jedec_reset(base, map, cfi);
+ return 0;
+ }
+ }
+
+ /* Check each previous chip locations to see if it's an alias */
+ for (i=0; i < (base >> cfi->chipshift); i++) {
+ unsigned long start;
+ if(!test_bit(i, chip_map)) {
+ continue; /* Skip location; no valid chip at this address */
+ }
+ start = i << cfi->chipshift;
+ if (jedec_read_mfr(map, start, cfi) == cfi->mfr &&
+ jedec_read_id(map, start, cfi) == cfi->id) {
+ /* Eep. This chip also looks like it's in autoselect mode.
+ Is it an alias for the new one? */
+ jedec_reset(start, map, cfi);
+
+ /* If the device IDs go away, it's an alias */
+ if (jedec_read_mfr(map, base, cfi) != cfi->mfr ||
+ jedec_read_id(map, base, cfi) != cfi->id) {
+ printk(KERN_DEBUG "%s: Found an alias at 0x%x for the chip at 0x%lx\n",
+ map->name, base, start);
+ return 0;
+ }
+
+ /* Yes, it's actually got the device IDs as data. Most
+ * unfortunate. Stick the new chip in read mode
+ * too and if it's the same, assume it's an alias. */
+ /* FIXME: Use other modes to do a proper check */
+ jedec_reset(base, map, cfi);
+ if (jedec_read_mfr(map, base, cfi) == cfi->mfr &&
+ jedec_read_id(map, base, cfi) == cfi->id) {
+ printk(KERN_DEBUG "%s: Found an alias at 0x%x for the chip at 0x%lx\n",
+ map->name, base, start);
+ return 0;
+ }
+ }
+ }
+
+ /* OK, if we got to here, then none of the previous chips appear to
+ be aliases for the current one. */
+ set_bit((base >> cfi->chipshift), chip_map); /* Update chip map */
+ cfi->numchips++;
+
+ok_out:
+ /* Put it back into Read Mode */
+ jedec_reset(base, map, cfi);
+
+ printk(KERN_INFO "%s: Found %d x%d devices at 0x%x in %d-bit bank\n",
+ map->name, cfi_interleave(cfi), cfi->device_type*8, base,
+ map->bankwidth*8);
+
+ return 1;
+}
+
+static struct chip_probe jedec_chip_probe = {
+ .name = "JEDEC",
+ .probe_chip = jedec_probe_chip
+};
+
+static struct mtd_info *jedec_probe(struct map_info *map)
+{
+ /*
+ * Just use the generic probe stuff to call our CFI-specific
+ * chip_probe routine in all the possible permutations, etc.
+ */
+ return mtd_do_chip_probe(map, &jedec_chip_probe);
+}
+
+static struct mtd_chip_driver jedec_chipdrv = {
+ .probe = jedec_probe,
+ .name = "jedec_probe",
+ .module = THIS_MODULE
+};
+
+static int __init jedec_probe_init(void)
+{
+ register_mtd_chip_driver(&jedec_chipdrv);
+ return 0;
+}
+
+static void __exit jedec_probe_exit(void)
+{
+ unregister_mtd_chip_driver(&jedec_chipdrv);
+}
+
+module_init(jedec_probe_init);
+module_exit(jedec_probe_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Erwin Authried <eauth@softsys.co.at> et al.");
+MODULE_DESCRIPTION("Probe code for JEDEC-compliant flash chips");
diff --git a/drivers/mtd/chips/map_absent.c b/drivers/mtd/chips/map_absent.c
new file mode 100644
index 00000000000..c6c83833cc3
--- /dev/null
+++ b/drivers/mtd/chips/map_absent.c
@@ -0,0 +1,117 @@
+/*
+ * Common code to handle absent "placeholder" devices
+ * Copyright 2001 Resilience Corporation <ebrower@resilience.com>
+ * $Id: map_absent.c,v 1.5 2004/11/16 18:29:00 dwmw2 Exp $
+ *
+ * This map driver is used to allocate "placeholder" MTD
+ * devices on systems that have socketed/removable media.
+ * Use of this driver as a fallback preserves the expected
+ * registration of MTD device nodes regardless of probe outcome.
+ * A usage example is as follows:
+ *
+ * my_dev[i] = do_map_probe("cfi", &my_map[i]);
+ * if(NULL == my_dev[i]) {
+ * my_dev[i] = do_map_probe("map_absent", &my_map[i]);
+ * }
+ *
+ * Any device 'probed' with this driver will return -ENODEV
+ * upon open.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/compatmac.h>
+
+static int map_absent_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
+static int map_absent_write (struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
+static int map_absent_erase (struct mtd_info *, struct erase_info *);
+static void map_absent_sync (struct mtd_info *);
+static struct mtd_info *map_absent_probe(struct map_info *map);
+static void map_absent_destroy (struct mtd_info *);
+
+
+static struct mtd_chip_driver map_absent_chipdrv = {
+ .probe = map_absent_probe,
+ .destroy = map_absent_destroy,
+ .name = "map_absent",
+ .module = THIS_MODULE
+};
+
+static struct mtd_info *map_absent_probe(struct map_info *map)
+{
+ struct mtd_info *mtd;
+
+ mtd = kmalloc(sizeof(*mtd), GFP_KERNEL);
+ if (!mtd) {
+ return NULL;
+ }
+
+ memset(mtd, 0, sizeof(*mtd));
+
+ map->fldrv = &map_absent_chipdrv;
+ mtd->priv = map;
+ mtd->name = map->name;
+ mtd->type = MTD_ABSENT;
+ mtd->size = map->size;
+ mtd->erase = map_absent_erase;
+ mtd->read = map_absent_read;
+ mtd->write = map_absent_write;
+ mtd->sync = map_absent_sync;
+ mtd->flags = 0;
+ mtd->erasesize = PAGE_SIZE;
+
+ __module_get(THIS_MODULE);
+ return mtd;
+}
+
+
+static int map_absent_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
+{
+ *retlen = 0;
+ return -ENODEV;
+}
+
+static int map_absent_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf)
+{
+ *retlen = 0;
+ return -ENODEV;
+}
+
+static int map_absent_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ return -ENODEV;
+}
+
+static void map_absent_sync(struct mtd_info *mtd)
+{
+ /* nop */
+}
+
+static void map_absent_destroy(struct mtd_info *mtd)
+{
+ /* nop */
+}
+
+static int __init map_absent_init(void)
+{
+ register_mtd_chip_driver(&map_absent_chipdrv);
+ return 0;
+}
+
+static void __exit map_absent_exit(void)
+{
+ unregister_mtd_chip_driver(&map_absent_chipdrv);
+}
+
+module_init(map_absent_init);
+module_exit(map_absent_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Resilience Corporation - Eric Brower <ebrower@resilience.com>");
+MODULE_DESCRIPTION("Placeholder MTD chip driver for 'absent' chips");
diff --git a/drivers/mtd/chips/map_ram.c b/drivers/mtd/chips/map_ram.c
new file mode 100644
index 00000000000..bd2e876a814
--- /dev/null
+++ b/drivers/mtd/chips/map_ram.c
@@ -0,0 +1,143 @@
+/*
+ * Common code to handle map devices which are simple RAM
+ * (C) 2000 Red Hat. GPL'd.
+ * $Id: map_ram.c,v 1.22 2005/01/05 18:05:12 dwmw2 Exp $
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <asm/io.h>
+#include <asm/byteorder.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/compatmac.h>
+
+
+static int mapram_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
+static int mapram_write (struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
+static int mapram_erase (struct mtd_info *, struct erase_info *);
+static void mapram_nop (struct mtd_info *);
+static struct mtd_info *map_ram_probe(struct map_info *map);
+
+
+static struct mtd_chip_driver mapram_chipdrv = {
+ .probe = map_ram_probe,
+ .name = "map_ram",
+ .module = THIS_MODULE
+};
+
+static struct mtd_info *map_ram_probe(struct map_info *map)
+{
+ struct mtd_info *mtd;
+
+ /* Check the first byte is RAM */
+#if 0
+ map_write8(map, 0x55, 0);
+ if (map_read8(map, 0) != 0x55)
+ return NULL;
+
+ map_write8(map, 0xAA, 0);
+ if (map_read8(map, 0) != 0xAA)
+ return NULL;
+
+ /* Check the last byte is RAM */
+ map_write8(map, 0x55, map->size-1);
+ if (map_read8(map, map->size-1) != 0x55)
+ return NULL;
+
+ map_write8(map, 0xAA, map->size-1);
+ if (map_read8(map, map->size-1) != 0xAA)
+ return NULL;
+#endif
+ /* OK. It seems to be RAM. */
+
+ mtd = kmalloc(sizeof(*mtd), GFP_KERNEL);
+ if (!mtd)
+ return NULL;
+
+ memset(mtd, 0, sizeof(*mtd));
+
+ map->fldrv = &mapram_chipdrv;
+ mtd->priv = map;
+ mtd->name = map->name;
+ mtd->type = MTD_RAM;
+ mtd->size = map->size;
+ mtd->erase = mapram_erase;
+ mtd->read = mapram_read;
+ mtd->write = mapram_write;
+ mtd->sync = mapram_nop;
+ mtd->flags = MTD_CAP_RAM | MTD_VOLATILE;
+
+ mtd->erasesize = PAGE_SIZE;
+ while(mtd->size & (mtd->erasesize - 1))
+ mtd->erasesize >>= 1;
+
+ __module_get(THIS_MODULE);
+ return mtd;
+}
+
+
+static int mapram_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+
+ map_copy_from(map, buf, from, len);
+ *retlen = len;
+ return 0;
+}
+
+static int mapram_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+
+ map_copy_to(map, to, buf, len);
+ *retlen = len;
+ return 0;
+}
+
+static int mapram_erase (struct mtd_info *mtd, struct erase_info *instr)
+{
+ /* Yeah, it's inefficient. Who cares? It's faster than a _real_
+ flash erase. */
+ struct map_info *map = mtd->priv;
+ map_word allff;
+ unsigned long i;
+
+ allff = map_word_ff(map);
+
+ for (i=0; i<instr->len; i += map_bankwidth(map))
+ map_write(map, allff, instr->addr + i);
+
+ instr->state = MTD_ERASE_DONE;
+
+ mtd_erase_callback(instr);
+
+ return 0;
+}
+
+static void mapram_nop(struct mtd_info *mtd)
+{
+ /* Nothing to see here */
+}
+
+static int __init map_ram_init(void)
+{
+ register_mtd_chip_driver(&mapram_chipdrv);
+ return 0;
+}
+
+static void __exit map_ram_exit(void)
+{
+ unregister_mtd_chip_driver(&mapram_chipdrv);
+}
+
+module_init(map_ram_init);
+module_exit(map_ram_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("MTD chip driver for RAM chips");
diff --git a/drivers/mtd/chips/map_rom.c b/drivers/mtd/chips/map_rom.c
new file mode 100644
index 00000000000..624c12c232c
--- /dev/null
+++ b/drivers/mtd/chips/map_rom.c
@@ -0,0 +1,94 @@
+/*
+ * Common code to handle map devices which are simple ROM
+ * (C) 2000 Red Hat. GPL'd.
+ * $Id: map_rom.c,v 1.23 2005/01/05 18:05:12 dwmw2 Exp $
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <asm/io.h>
+#include <asm/byteorder.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/compatmac.h>
+
+static int maprom_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
+static int maprom_write (struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
+static void maprom_nop (struct mtd_info *);
+static struct mtd_info *map_rom_probe(struct map_info *map);
+
+static struct mtd_chip_driver maprom_chipdrv = {
+ .probe = map_rom_probe,
+ .name = "map_rom",
+ .module = THIS_MODULE
+};
+
+static struct mtd_info *map_rom_probe(struct map_info *map)
+{
+ struct mtd_info *mtd;
+
+ mtd = kmalloc(sizeof(*mtd), GFP_KERNEL);
+ if (!mtd)
+ return NULL;
+
+ memset(mtd, 0, sizeof(*mtd));
+
+ map->fldrv = &maprom_chipdrv;
+ mtd->priv = map;
+ mtd->name = map->name;
+ mtd->type = MTD_ROM;
+ mtd->size = map->size;
+ mtd->read = maprom_read;
+ mtd->write = maprom_write;
+ mtd->sync = maprom_nop;
+ mtd->flags = MTD_CAP_ROM;
+ mtd->erasesize = 131072;
+ while(mtd->size & (mtd->erasesize - 1))
+ mtd->erasesize >>= 1;
+
+ __module_get(THIS_MODULE);
+ return mtd;
+}
+
+
+static int maprom_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+
+ map_copy_from(map, buf, from, len);
+ *retlen = len;
+ return 0;
+}
+
+static void maprom_nop(struct mtd_info *mtd)
+{
+ /* Nothing to see here */
+}
+
+static int maprom_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf)
+{
+ printk(KERN_NOTICE "maprom_write called\n");
+ return -EIO;
+}
+
+static int __init map_rom_init(void)
+{
+ register_mtd_chip_driver(&maprom_chipdrv);
+ return 0;
+}
+
+static void __exit map_rom_exit(void)
+{
+ unregister_mtd_chip_driver(&maprom_chipdrv);
+}
+
+module_init(map_rom_init);
+module_exit(map_rom_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("MTD chip driver for ROM chips");
diff --git a/drivers/mtd/chips/sharp.c b/drivers/mtd/chips/sharp.c
new file mode 100644
index 00000000000..c3cf0f63bc9
--- /dev/null
+++ b/drivers/mtd/chips/sharp.c
@@ -0,0 +1,596 @@
+/*
+ * MTD chip driver for pre-CFI Sharp flash chips
+ *
+ * Copyright 2000,2001 David A. Schleef <ds@schleef.org>
+ * 2000,2001 Lineo, Inc.
+ *
+ * $Id: sharp.c,v 1.14 2004/08/09 13:19:43 dwmw2 Exp $
+ *
+ * Devices supported:
+ * LH28F016SCT Symmetrical block flash memory, 2Mx8
+ * LH28F008SCT Symmetrical block flash memory, 1Mx8
+ *
+ * Documentation:
+ * http://www.sharpmeg.com/datasheets/memic/flashcmp/
+ * http://www.sharpmeg.com/datasheets/memic/flashcmp/01symf/16m/016sctl9.pdf
+ * 016sctl9.pdf
+ *
+ * Limitations:
+ * This driver only supports 4x1 arrangement of chips.
+ * Not tested on anything but PowerPC.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/cfi.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+
+#define CMD_RESET 0xffffffff
+#define CMD_READ_ID 0x90909090
+#define CMD_READ_STATUS 0x70707070
+#define CMD_CLEAR_STATUS 0x50505050
+#define CMD_BLOCK_ERASE_1 0x20202020
+#define CMD_BLOCK_ERASE_2 0xd0d0d0d0
+#define CMD_BYTE_WRITE 0x40404040
+#define CMD_SUSPEND 0xb0b0b0b0
+#define CMD_RESUME 0xd0d0d0d0
+#define CMD_SET_BLOCK_LOCK_1 0x60606060
+#define CMD_SET_BLOCK_LOCK_2 0x01010101
+#define CMD_SET_MASTER_LOCK_1 0x60606060
+#define CMD_SET_MASTER_LOCK_2 0xf1f1f1f1
+#define CMD_CLEAR_BLOCK_LOCKS_1 0x60606060
+#define CMD_CLEAR_BLOCK_LOCKS_2 0xd0d0d0d0
+
+#define SR_READY 0x80808080 // 1 = ready
+#define SR_ERASE_SUSPEND 0x40404040 // 1 = block erase suspended
+#define SR_ERROR_ERASE 0x20202020 // 1 = error in block erase or clear lock bits
+#define SR_ERROR_WRITE 0x10101010 // 1 = error in byte write or set lock bit
+#define SR_VPP 0x08080808 // 1 = Vpp is low
+#define SR_WRITE_SUSPEND 0x04040404 // 1 = byte write suspended
+#define SR_PROTECT 0x02020202 // 1 = lock bit set
+#define SR_RESERVED 0x01010101
+
+#define SR_ERRORS (SR_ERROR_ERASE|SR_ERROR_WRITE|SR_VPP|SR_PROTECT)
+
+/* Configuration options */
+
+#undef AUTOUNLOCK /* automatically unlocks blocks before erasing */
+
+struct mtd_info *sharp_probe(struct map_info *);
+
+static int sharp_probe_map(struct map_info *map,struct mtd_info *mtd);
+
+static int sharp_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf);
+static int sharp_write(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, const u_char *buf);
+static int sharp_erase(struct mtd_info *mtd, struct erase_info *instr);
+static void sharp_sync(struct mtd_info *mtd);
+static int sharp_suspend(struct mtd_info *mtd);
+static void sharp_resume(struct mtd_info *mtd);
+static void sharp_destroy(struct mtd_info *mtd);
+
+static int sharp_write_oneword(struct map_info *map, struct flchip *chip,
+ unsigned long adr, __u32 datum);
+static int sharp_erase_oneblock(struct map_info *map, struct flchip *chip,
+ unsigned long adr);
+#ifdef AUTOUNLOCK
+static void sharp_unlock_oneblock(struct map_info *map, struct flchip *chip,
+ unsigned long adr);
+#endif
+
+
+struct sharp_info{
+ struct flchip *chip;
+ int bogus;
+ int chipshift;
+ int numchips;
+ struct flchip chips[1];
+};
+
+struct mtd_info *sharp_probe(struct map_info *map);
+static void sharp_destroy(struct mtd_info *mtd);
+
+static struct mtd_chip_driver sharp_chipdrv = {
+ .probe = sharp_probe,
+ .destroy = sharp_destroy,
+ .name = "sharp",
+ .module = THIS_MODULE
+};
+
+
+struct mtd_info *sharp_probe(struct map_info *map)
+{
+ struct mtd_info *mtd = NULL;
+ struct sharp_info *sharp = NULL;
+ int width;
+
+ mtd = kmalloc(sizeof(*mtd), GFP_KERNEL);
+ if(!mtd)
+ return NULL;
+
+ sharp = kmalloc(sizeof(*sharp), GFP_KERNEL);
+ if(!sharp) {
+ kfree(mtd);
+ return NULL;
+ }
+
+ memset(mtd, 0, sizeof(*mtd));
+
+ width = sharp_probe_map(map,mtd);
+ if(!width){
+ kfree(mtd);
+ kfree(sharp);
+ return NULL;
+ }
+
+ mtd->priv = map;
+ mtd->type = MTD_NORFLASH;
+ mtd->erase = sharp_erase;
+ mtd->read = sharp_read;
+ mtd->write = sharp_write;
+ mtd->sync = sharp_sync;
+ mtd->suspend = sharp_suspend;
+ mtd->resume = sharp_resume;
+ mtd->flags = MTD_CAP_NORFLASH;
+ mtd->name = map->name;
+
+ memset(sharp, 0, sizeof(*sharp));
+ sharp->chipshift = 23;
+ sharp->numchips = 1;
+ sharp->chips[0].start = 0;
+ sharp->chips[0].state = FL_READY;
+ sharp->chips[0].mutex = &sharp->chips[0]._spinlock;
+ sharp->chips[0].word_write_time = 0;
+ init_waitqueue_head(&sharp->chips[0].wq);
+ spin_lock_init(&sharp->chips[0]._spinlock);
+
+ map->fldrv = &sharp_chipdrv;
+ map->fldrv_priv = sharp;
+
+ __module_get(THIS_MODULE);
+ return mtd;
+}
+
+static int sharp_probe_map(struct map_info *map,struct mtd_info *mtd)
+{
+ unsigned long tmp;
+ unsigned long base = 0;
+ u32 read0, read4;
+ int width = 4;
+
+ tmp = map_read32(map, base+0);
+
+ map_write32(map, CMD_READ_ID, base+0);
+
+ read0=map_read32(map, base+0);
+ read4=map_read32(map, base+4);
+ if(read0 == 0x89898989){
+ printk("Looks like sharp flash\n");
+ switch(read4){
+ case 0xaaaaaaaa:
+ case 0xa0a0a0a0:
+ /* aa - LH28F016SCT-L95 2Mx8, 32 64k blocks*/
+ /* a0 - LH28F016SCT-Z4 2Mx8, 32 64k blocks*/
+ mtd->erasesize = 0x10000 * width;
+ mtd->size = 0x200000 * width;
+ return width;
+ case 0xa6a6a6a6:
+ /* a6 - LH28F008SCT-L12 1Mx8, 16 64k blocks*/
+ /* a6 - LH28F008SCR-L85 1Mx8, 16 64k blocks*/
+ mtd->erasesize = 0x10000 * width;
+ mtd->size = 0x100000 * width;
+ return width;
+#if 0
+ case 0x00000000: /* unknown */
+ /* XX - LH28F004SCT 512kx8, 8 64k blocks*/
+ mtd->erasesize = 0x10000 * width;
+ mtd->size = 0x80000 * width;
+ return width;
+#endif
+ default:
+ printk("Sort-of looks like sharp flash, 0x%08x 0x%08x\n",
+ read0,read4);
+ }
+ }else if((map_read32(map, base+0) == CMD_READ_ID)){
+ /* RAM, probably */
+ printk("Looks like RAM\n");
+ map_write32(map, tmp, base+0);
+ }else{
+ printk("Doesn't look like sharp flash, 0x%08x 0x%08x\n",
+ read0,read4);
+ }
+
+ return 0;
+}
+
+/* This function returns with the chip->mutex lock held. */
+static int sharp_wait(struct map_info *map, struct flchip *chip)
+{
+ __u16 status;
+ unsigned long timeo = jiffies + HZ;
+ DECLARE_WAITQUEUE(wait, current);
+ int adr = 0;
+
+retry:
+ spin_lock_bh(chip->mutex);
+
+ switch(chip->state){
+ case FL_READY:
+ map_write32(map,CMD_READ_STATUS,adr);
+ chip->state = FL_STATUS;
+ case FL_STATUS:
+ status = map_read32(map,adr);
+//printk("status=%08x\n",status);
+
+ udelay(100);
+ if((status & SR_READY)!=SR_READY){
+//printk(".status=%08x\n",status);
+ udelay(100);
+ }
+ break;
+ default:
+ printk("Waiting for chip\n");
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+
+ spin_unlock_bh(chip->mutex);
+
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+
+ if(signal_pending(current))
+ return -EINTR;
+
+ timeo = jiffies + HZ;
+
+ goto retry;
+ }
+
+ map_write32(map,CMD_RESET, adr);
+
+ chip->state = FL_READY;
+
+ return 0;
+}
+
+static void sharp_release(struct flchip *chip)
+{
+ wake_up(&chip->wq);
+ spin_unlock_bh(chip->mutex);
+}
+
+static int sharp_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct sharp_info *sharp = map->fldrv_priv;
+ int chipnum;
+ int ret = 0;
+ int ofs = 0;
+
+ chipnum = (from >> sharp->chipshift);
+ ofs = from & ((1 << sharp->chipshift)-1);
+
+ *retlen = 0;
+
+ while(len){
+ unsigned long thislen;
+
+ if(chipnum>=sharp->numchips)
+ break;
+
+ thislen = len;
+ if(ofs+thislen >= (1<<sharp->chipshift))
+ thislen = (1<<sharp->chipshift) - ofs;
+
+ ret = sharp_wait(map,&sharp->chips[chipnum]);
+ if(ret<0)
+ break;
+
+ map_copy_from(map,buf,ofs,thislen);
+
+ sharp_release(&sharp->chips[chipnum]);
+
+ *retlen += thislen;
+ len -= thislen;
+ buf += thislen;
+
+ ofs = 0;
+ chipnum++;
+ }
+ return ret;
+}
+
+static int sharp_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ struct sharp_info *sharp = map->fldrv_priv;
+ int ret = 0;
+ int i,j;
+ int chipnum;
+ unsigned long ofs;
+ union { u32 l; unsigned char uc[4]; } tbuf;
+
+ *retlen = 0;
+
+ while(len){
+ tbuf.l = 0xffffffff;
+ chipnum = to >> sharp->chipshift;
+ ofs = to & ((1<<sharp->chipshift)-1);
+
+ j=0;
+ for(i=ofs&3;i<4 && len;i++){
+ tbuf.uc[i] = *buf;
+ buf++;
+ to++;
+ len--;
+ j++;
+ }
+ sharp_write_oneword(map, &sharp->chips[chipnum], ofs&~3, tbuf.l);
+ if(ret<0)
+ return ret;
+ (*retlen)+=j;
+ }
+
+ return 0;
+}
+
+static int sharp_write_oneword(struct map_info *map, struct flchip *chip,
+ unsigned long adr, __u32 datum)
+{
+ int ret;
+ int timeo;
+ int try;
+ int i;
+ int status = 0;
+
+ ret = sharp_wait(map,chip);
+
+ for(try=0;try<10;try++){
+ map_write32(map,CMD_BYTE_WRITE,adr);
+ /* cpu_to_le32 -> hack to fix the writel be->le conversion */
+ map_write32(map,cpu_to_le32(datum),adr);
+
+ chip->state = FL_WRITING;
+
+ timeo = jiffies + (HZ/2);
+
+ map_write32(map,CMD_READ_STATUS,adr);
+ for(i=0;i<100;i++){
+ status = map_read32(map,adr);
+ if((status & SR_READY)==SR_READY)
+ break;
+ }
+ if(i==100){
+ printk("sharp: timed out writing\n");
+ }
+
+ if(!(status&SR_ERRORS))
+ break;
+
+ printk("sharp: error writing byte at addr=%08lx status=%08x\n",adr,status);
+
+ map_write32(map,CMD_CLEAR_STATUS,adr);
+ }
+ map_write32(map,CMD_RESET,adr);
+ chip->state = FL_READY;
+
+ wake_up(&chip->wq);
+ spin_unlock_bh(chip->mutex);
+
+ return 0;
+}
+
+static int sharp_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct map_info *map = mtd->priv;
+ struct sharp_info *sharp = map->fldrv_priv;
+ unsigned long adr,len;
+ int chipnum, ret=0;
+
+//printk("sharp_erase()\n");
+ if(instr->addr & (mtd->erasesize - 1))
+ return -EINVAL;
+ if(instr->len & (mtd->erasesize - 1))
+ return -EINVAL;
+ if(instr->len + instr->addr > mtd->size)
+ return -EINVAL;
+
+ chipnum = instr->addr >> sharp->chipshift;
+ adr = instr->addr & ((1<<sharp->chipshift)-1);
+ len = instr->len;
+
+ while(len){
+ ret = sharp_erase_oneblock(map, &sharp->chips[chipnum], adr);
+ if(ret)return ret;
+
+ adr += mtd->erasesize;
+ len -= mtd->erasesize;
+ if(adr >> sharp->chipshift){
+ adr = 0;
+ chipnum++;
+ if(chipnum>=sharp->numchips)
+ break;
+ }
+ }
+
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+
+ return 0;
+}
+
+static int sharp_do_wait_for_ready(struct map_info *map, struct flchip *chip,
+ unsigned long adr)
+{
+ int ret;
+ unsigned long timeo;
+ int status;
+ DECLARE_WAITQUEUE(wait, current);
+
+ map_write32(map,CMD_READ_STATUS,adr);
+ status = map_read32(map,adr);
+
+ timeo = jiffies + HZ;
+
+ while(time_before(jiffies, timeo)){
+ map_write32(map,CMD_READ_STATUS,adr);
+ status = map_read32(map,adr);
+ if((status & SR_READY)==SR_READY){
+ ret = 0;
+ goto out;
+ }
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&chip->wq, &wait);
+
+ //spin_unlock_bh(chip->mutex);
+
+ schedule_timeout(1);
+ schedule();
+ remove_wait_queue(&chip->wq, &wait);
+
+ //spin_lock_bh(chip->mutex);
+
+ if (signal_pending(current)){
+ ret = -EINTR;
+ goto out;
+ }
+
+ }
+ ret = -ETIME;
+out:
+ return ret;
+}
+
+static int sharp_erase_oneblock(struct map_info *map, struct flchip *chip,
+ unsigned long adr)
+{
+ int ret;
+ //int timeo;
+ int status;
+ //int i;
+
+//printk("sharp_erase_oneblock()\n");
+
+#ifdef AUTOUNLOCK
+ /* This seems like a good place to do an unlock */
+ sharp_unlock_oneblock(map,chip,adr);
+#endif
+
+ map_write32(map,CMD_BLOCK_ERASE_1,adr);
+ map_write32(map,CMD_BLOCK_ERASE_2,adr);
+
+ chip->state = FL_ERASING;
+
+ ret = sharp_do_wait_for_ready(map,chip,adr);
+ if(ret<0)return ret;
+
+ map_write32(map,CMD_READ_STATUS,adr);
+ status = map_read32(map,adr);
+
+ if(!(status&SR_ERRORS)){
+ map_write32(map,CMD_RESET,adr);
+ chip->state = FL_READY;
+ //spin_unlock_bh(chip->mutex);
+ return 0;
+ }
+
+ printk("sharp: error erasing block at addr=%08lx status=%08x\n",adr,status);
+ map_write32(map,CMD_CLEAR_STATUS,adr);
+
+ //spin_unlock_bh(chip->mutex);
+
+ return -EIO;
+}
+
+#ifdef AUTOUNLOCK
+static void sharp_unlock_oneblock(struct map_info *map, struct flchip *chip,
+ unsigned long adr)
+{
+ int i;
+ int status;
+
+ map_write32(map,CMD_CLEAR_BLOCK_LOCKS_1,adr);
+ map_write32(map,CMD_CLEAR_BLOCK_LOCKS_2,adr);
+
+ udelay(100);
+
+ status = map_read32(map,adr);
+ printk("status=%08x\n",status);
+
+ for(i=0;i<1000;i++){
+ //map_write32(map,CMD_READ_STATUS,adr);
+ status = map_read32(map,adr);
+ if((status & SR_READY)==SR_READY)
+ break;
+ udelay(100);
+ }
+ if(i==1000){
+ printk("sharp: timed out unlocking block\n");
+ }
+
+ if(!(status&SR_ERRORS)){
+ map_write32(map,CMD_RESET,adr);
+ chip->state = FL_READY;
+ return;
+ }
+
+ printk("sharp: error unlocking block at addr=%08lx status=%08x\n",adr,status);
+ map_write32(map,CMD_CLEAR_STATUS,adr);
+}
+#endif
+
+static void sharp_sync(struct mtd_info *mtd)
+{
+ //printk("sharp_sync()\n");
+}
+
+static int sharp_suspend(struct mtd_info *mtd)
+{
+ printk("sharp_suspend()\n");
+ return -EINVAL;
+}
+
+static void sharp_resume(struct mtd_info *mtd)
+{
+ printk("sharp_resume()\n");
+
+}
+
+static void sharp_destroy(struct mtd_info *mtd)
+{
+ printk("sharp_destroy()\n");
+
+}
+
+int __init sharp_probe_init(void)
+{
+ printk("MTD Sharp chip driver <ds@lineo.com>\n");
+
+ register_mtd_chip_driver(&sharp_chipdrv);
+
+ return 0;
+}
+
+static void __exit sharp_probe_exit(void)
+{
+ unregister_mtd_chip_driver(&sharp_chipdrv);
+}
+
+module_init(sharp_probe_init);
+module_exit(sharp_probe_exit);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Schleef <ds@schleef.org>");
+MODULE_DESCRIPTION("Old MTD chip driver for pre-CFI Sharp flash chips");