summaryrefslogtreecommitdiff
path: root/drivers/mtd
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
downloadlwn-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz
lwn-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip
Linux-2.6.12-rc2v2.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')
-rw-r--r--drivers/mtd/Kconfig265
-rw-r--r--drivers/mtd/Makefile27
-rw-r--r--drivers/mtd/afs.c296
-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
-rw-r--r--drivers/mtd/cmdlinepart.c367
-rw-r--r--drivers/mtd/devices/Kconfig259
-rw-r--r--drivers/mtd/devices/Makefile25
-rw-r--r--drivers/mtd/devices/blkmtd.c823
-rw-r--r--drivers/mtd/devices/block2mtd.c495
-rw-r--r--drivers/mtd/devices/doc2000.c1309
-rw-r--r--drivers/mtd/devices/doc2001.c888
-rw-r--r--drivers/mtd/devices/doc2001plus.c1154
-rw-r--r--drivers/mtd/devices/docecc.c526
-rw-r--r--drivers/mtd/devices/docprobe.c355
-rw-r--r--drivers/mtd/devices/lart.c711
-rw-r--r--drivers/mtd/devices/ms02-nv.c326
-rw-r--r--drivers/mtd/devices/ms02-nv.h107
-rw-r--r--drivers/mtd/devices/mtdram.c235
-rw-r--r--drivers/mtd/devices/phram.c285
-rw-r--r--drivers/mtd/devices/pmc551.c843
-rw-r--r--drivers/mtd/devices/slram.c357
-rw-r--r--drivers/mtd/ftl.c1115
-rw-r--r--drivers/mtd/inftlcore.c912
-rw-r--r--drivers/mtd/inftlmount.c804
-rw-r--r--drivers/mtd/maps/Kconfig663
-rw-r--r--drivers/mtd/maps/Makefile73
-rw-r--r--drivers/mtd/maps/amd76xrom.c332
-rw-r--r--drivers/mtd/maps/arctic-mtd.c135
-rw-r--r--drivers/mtd/maps/autcpu12-nvram.c127
-rw-r--r--drivers/mtd/maps/bast-flash.c227
-rw-r--r--drivers/mtd/maps/beech-mtd.c112
-rw-r--r--drivers/mtd/maps/cdb89712.c268
-rw-r--r--drivers/mtd/maps/ceiva.c350
-rw-r--r--drivers/mtd/maps/cfi_flagadm.c139
-rw-r--r--drivers/mtd/maps/cstm_mips_ixx.c270
-rw-r--r--drivers/mtd/maps/db1550-flash.c187
-rw-r--r--drivers/mtd/maps/db1x00-flash.c226
-rw-r--r--drivers/mtd/maps/dbox2-flash.c126
-rw-r--r--drivers/mtd/maps/dc21285.c253
-rw-r--r--drivers/mtd/maps/dilnetpc.c495
-rw-r--r--drivers/mtd/maps/dmv182.c149
-rw-r--r--drivers/mtd/maps/ebony.c163
-rw-r--r--drivers/mtd/maps/edb7312.c147
-rw-r--r--drivers/mtd/maps/elan-104nc.c228
-rw-r--r--drivers/mtd/maps/epxa10db-flash.c176
-rw-r--r--drivers/mtd/maps/fortunet.c271
-rw-r--r--drivers/mtd/maps/h720x-flash.c144
-rw-r--r--drivers/mtd/maps/ichxrom.c383
-rw-r--r--drivers/mtd/maps/impa7.c161
-rw-r--r--drivers/mtd/maps/integrator-flash.c217
-rw-r--r--drivers/mtd/maps/ipaq-flash.c464
-rw-r--r--drivers/mtd/maps/iq80310.c119
-rw-r--r--drivers/mtd/maps/ixp2000.c280
-rw-r--r--drivers/mtd/maps/ixp4xx.c259
-rw-r--r--drivers/mtd/maps/l440gx.c157
-rw-r--r--drivers/mtd/maps/lasat.c102
-rw-r--r--drivers/mtd/maps/lubbock-flash.c168
-rw-r--r--drivers/mtd/maps/map_funcs.c44
-rw-r--r--drivers/mtd/maps/mbx860.c100
-rw-r--r--drivers/mtd/maps/mpc1211.c81
-rw-r--r--drivers/mtd/maps/netsc520.c140
-rw-r--r--drivers/mtd/maps/nettel.c496
-rw-r--r--drivers/mtd/maps/ocelot.c175
-rw-r--r--drivers/mtd/maps/ocotea.c154
-rw-r--r--drivers/mtd/maps/octagon-5066.c248
-rw-r--r--drivers/mtd/maps/omap-toto-flash.c137
-rw-r--r--drivers/mtd/maps/pb1550-flash.c203
-rw-r--r--drivers/mtd/maps/pb1xxx-flash.c178
-rw-r--r--drivers/mtd/maps/pci.c388
-rw-r--r--drivers/mtd/maps/pcmciamtd.c860
-rw-r--r--drivers/mtd/maps/physmap.c125
-rw-r--r--drivers/mtd/maps/pnc2000.c93
-rw-r--r--drivers/mtd/maps/redwood.c169
-rw-r--r--drivers/mtd/maps/rpxlite.c66
-rw-r--r--drivers/mtd/maps/sa1100-flash.c453
-rw-r--r--drivers/mtd/maps/sbc8240.c247
-rw-r--r--drivers/mtd/maps/sbc_gxx.c239
-rw-r--r--drivers/mtd/maps/sc520cdp.c304
-rw-r--r--drivers/mtd/maps/scb2_flash.c256
-rw-r--r--drivers/mtd/maps/scx200_docflash.c233
-rw-r--r--drivers/mtd/maps/sharpsl-flash.c101
-rw-r--r--drivers/mtd/maps/solutionengine.c137
-rw-r--r--drivers/mtd/maps/sun_uflash.c177
-rw-r--r--drivers/mtd/maps/tqm8xxl.c263
-rw-r--r--drivers/mtd/maps/ts5500_flash.c141
-rw-r--r--drivers/mtd/maps/tsunami_flash.c108
-rw-r--r--drivers/mtd/maps/uclinux.c127
-rw-r--r--drivers/mtd/maps/vmax301.c198
-rw-r--r--drivers/mtd/maps/walnut.c122
-rw-r--r--drivers/mtd/maps/wr_sbc82xx_flash.c181
-rw-r--r--drivers/mtd/mtd_blkdevs.c478
-rw-r--r--drivers/mtd/mtdblock.c394
-rw-r--r--drivers/mtd/mtdblock_ro.c87
-rw-r--r--drivers/mtd/mtdchar.c562
-rw-r--r--drivers/mtd/mtdconcat.c897
-rw-r--r--drivers/mtd/mtdcore.c419
-rw-r--r--drivers/mtd/mtdpart.c599
-rw-r--r--drivers/mtd/nand/Kconfig207
-rw-r--r--drivers/mtd/nand/Makefile24
-rw-r--r--drivers/mtd/nand/au1550nd.c477
-rw-r--r--drivers/mtd/nand/autcpu12.c225
-rw-r--r--drivers/mtd/nand/diskonchip.c1782
-rw-r--r--drivers/mtd/nand/edb7312.c218
-rw-r--r--drivers/mtd/nand/h1910.c208
-rw-r--r--drivers/mtd/nand/nand_base.c2563
-rw-r--r--drivers/mtd/nand/nand_bbt.c1056
-rw-r--r--drivers/mtd/nand/nand_ecc.c250
-rw-r--r--drivers/mtd/nand/nand_ids.c129
-rw-r--r--drivers/mtd/nand/nandsim.c1613
-rw-r--r--drivers/mtd/nand/ppchameleonevb.c420
-rw-r--r--drivers/mtd/nand/rtc_from4.c559
-rw-r--r--drivers/mtd/nand/s3c2410.c704
-rwxr-xr-xdrivers/mtd/nand/sharpsl.c260
-rw-r--r--drivers/mtd/nand/spia.c173
-rw-r--r--drivers/mtd/nand/toto.c205
-rw-r--r--drivers/mtd/nand/tx4925ndfmc.c416
-rw-r--r--drivers/mtd/nand/tx4938ndfmc.c406
-rw-r--r--drivers/mtd/nftlcore.c767
-rw-r--r--drivers/mtd/nftlmount.c770
-rw-r--r--drivers/mtd/redboot.c235
136 files changed, 56047 insertions, 0 deletions
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
new file mode 100644
index 000000000000..027054dea032
--- /dev/null
+++ b/drivers/mtd/Kconfig
@@ -0,0 +1,265 @@
+# $Id: Kconfig,v 1.7 2004/11/22 11:33:56 ijc Exp $
+
+menu "Memory Technology Devices (MTD)"
+
+config MTD
+ tristate "Memory Technology Device (MTD) support"
+ help
+ Memory Technology Devices are flash, RAM and similar chips, often
+ used for solid state file systems on embedded devices. This option
+ will provide the generic support for MTD drivers to register
+ themselves with the kernel and for potential users of MTD devices
+ to enumerate the devices which are present and obtain a handle on
+ them. It will also allow you to select individual drivers for
+ particular hardware and users of MTD devices. If unsure, say N.
+
+config MTD_DEBUG
+ bool "Debugging"
+ depends on MTD
+ help
+ This turns on low-level debugging for the entire MTD sub-system.
+ Normally, you should say 'N'.
+
+config MTD_DEBUG_VERBOSE
+ int "Debugging verbosity (0 = quiet, 3 = noisy)"
+ depends on MTD_DEBUG
+ default "0"
+ help
+ Determines the verbosity level of the MTD debugging messages.
+
+config MTD_CONCAT
+ tristate "MTD concatenating support"
+ depends on MTD
+ help
+ Support for concatenating several MTD devices into a single
+ (virtual) one. This allows you to have -for example- a JFFS(2)
+ file system spanning multiple physical flash chips. If unsure,
+ say 'Y'.
+
+config MTD_PARTITIONS
+ bool "MTD partitioning support"
+ depends on MTD
+ help
+ If you have a device which needs to divide its flash chip(s) up
+ into multiple 'partitions', each of which appears to the user as
+ a separate MTD device, you require this option to be enabled. If
+ unsure, say 'Y'.
+
+ Note, however, that you don't need this option for the DiskOnChip
+ devices. Partitioning on NFTL 'devices' is a different - that's the
+ 'normal' form of partitioning used on a block device.
+
+config MTD_REDBOOT_PARTS
+ tristate "RedBoot partition table parsing"
+ depends on MTD_PARTITIONS
+ ---help---
+ RedBoot is a ROM monitor and bootloader which deals with multiple
+ 'images' in flash devices by putting a table one of the erase
+ blocks on the device, similar to a partition table, which gives
+ the offsets, lengths and names of all the images stored in the
+ flash.
+
+ If you need code which can detect and parse this table, and register
+ MTD 'partitions' corresponding to each image in the table, enable
+ this option.
+
+ You will still need the parsing functions to be called by the driver
+ for your particular device. It won't happen automatically. The
+ SA1100 map driver (CONFIG_MTD_SA1100) has an option for this, for
+ example.
+
+config MTD_REDBOOT_DIRECTORY_BLOCK
+ int "Location of RedBoot partition table"
+ depends on MTD_REDBOOT_PARTS
+ default "-1"
+ ---help---
+ This option is the Linux counterpart to the
+ CYGNUM_REDBOOT_FIS_DIRECTORY_BLOCK RedBoot compile time
+ option.
+
+ The option specifies which Flash sectors holds the RedBoot
+ partition table. A zero or positive value gives an absolete
+ erase block number. A negative value specifies a number of
+ sectors before the end of the device.
+
+ For example "2" means block number 2, "-1" means the last
+ block and "-2" means the penultimate block.
+
+config MTD_REDBOOT_PARTS_UNALLOCATED
+ bool " Include unallocated flash regions"
+ depends on MTD_REDBOOT_PARTS
+ help
+ If you need to register each unallocated flash region as a MTD
+ 'partition', enable this option.
+
+config MTD_REDBOOT_PARTS_READONLY
+ bool " Force read-only for RedBoot system images"
+ depends on MTD_REDBOOT_PARTS
+ help
+ If you need to force read-only for 'RedBoot', 'RedBoot Config' and
+ 'FIS directory' images, enable this option.
+
+config MTD_CMDLINE_PARTS
+ bool "Command line partition table parsing"
+ depends on MTD_PARTITIONS = "y"
+ ---help---
+ Allow generic configuration of the MTD paritition tables via the kernel
+ command line. Multiple flash resources are supported for hardware where
+ different kinds of flash memory are available.
+
+ You will still need the parsing functions to be called by the driver
+ for your particular device. It won't happen automatically. The
+ SA1100 map driver (CONFIG_MTD_SA1100) has an option for this, for
+ example.
+
+ The format for the command line is as follows:
+
+ mtdparts=<mtddef>[;<mtddef]
+ <mtddef> := <mtd-id>:<partdef>[,<partdef>]
+ <partdef> := <size>[@offset][<name>][ro]
+ <mtd-id> := unique id used in mapping driver/device
+ <size> := standard linux memsize OR "-" to denote all
+ remaining space
+ <name> := (NAME)
+
+ Due to the way Linux handles the command line, no spaces are
+ allowed in the partition definition, including mtd id's and partition
+ names.
+
+ Examples:
+
+ 1 flash resource (mtd-id "sa1100"), with 1 single writable partition:
+ mtdparts=sa1100:-
+
+ Same flash, but 2 named partitions, the first one being read-only:
+ mtdparts=sa1100:256k(ARMboot)ro,-(root)
+
+ If unsure, say 'N'.
+
+config MTD_AFS_PARTS
+ tristate "ARM Firmware Suite partition parsing"
+ depends on ARM && MTD_PARTITIONS
+ ---help---
+ The ARM Firmware Suite allows the user to divide flash devices into
+ multiple 'images'. Each such image has a header containing its name
+ and offset/size etc.
+
+ If you need code which can detect and parse these tables, and
+ register MTD 'partitions' corresponding to each image detected,
+ enable this option.
+
+ You will still need the parsing functions to be called by the driver
+ for your particular device. It won't happen automatically. The
+ 'armflash' map driver (CONFIG_MTD_ARMFLASH) does this, for example.
+
+comment "User Modules And Translation Layers"
+ depends on MTD
+
+config MTD_CHAR
+ tristate "Direct char device access to MTD devices"
+ depends on MTD
+ help
+ This provides a character device for each MTD device present in
+ the system, allowing the user to read and write directly to the
+ memory chips, and also use ioctl() to obtain information about
+ the device, or to erase parts of it.
+
+config MTD_BLOCK
+ tristate "Caching block device access to MTD devices"
+ depends on MTD
+ ---help---
+ Although most flash chips have an erase size too large to be useful
+ as block devices, it is possible to use MTD devices which are based
+ on RAM chips in this manner. This block device is a user of MTD
+ devices performing that function.
+
+ At the moment, it is also required for the Journalling Flash File
+ System(s) to obtain a handle on the MTD device when it's mounted
+ (although JFFS and JFFS2 don't actually use any of the functionality
+ of the mtdblock device).
+
+ Later, it may be extended to perform read/erase/modify/write cycles
+ on flash chips to emulate a smaller block size. Needless to say,
+ this is very unsafe, but could be useful for file systems which are
+ almost never written to.
+
+ You do not need this option for use with the DiskOnChip devices. For
+ those, enable NFTL support (CONFIG_NFTL) instead.
+
+config MTD_BLOCK_RO
+ tristate "Readonly block device access to MTD devices"
+ depends on MTD_BLOCK!=y && MTD
+ help
+ This allows you to mount read-only file systems (such as cramfs)
+ from an MTD device, without the overhead (and danger) of the caching
+ driver.
+
+ You do not need this option for use with the DiskOnChip devices. For
+ those, enable NFTL support (CONFIG_NFTL) instead.
+
+config FTL
+ tristate "FTL (Flash Translation Layer) support"
+ depends on MTD
+ ---help---
+ This provides support for the original Flash Translation Layer which
+ is part of the PCMCIA specification. It uses a kind of pseudo-
+ file system on a flash device to emulate a block device with
+ 512-byte sectors, on top of which you put a 'normal' file system.
+
+ You may find that the algorithms used in this code are patented
+ unless you live in the Free World where software patents aren't
+ legal - in the USA you are only permitted to use this on PCMCIA
+ hardware, although under the terms of the GPL you're obviously
+ permitted to copy, modify and distribute the code as you wish. Just
+ not use it.
+
+config NFTL
+ tristate "NFTL (NAND Flash Translation Layer) support"
+ depends on MTD
+ ---help---
+ This provides support for the NAND Flash Translation Layer which is
+ used on M-Systems' DiskOnChip devices. It uses a kind of pseudo-
+ file system on a flash device to emulate a block device with
+ 512-byte sectors, on top of which you put a 'normal' file system.
+
+ You may find that the algorithms used in this code are patented
+ unless you live in the Free World where software patents aren't
+ legal - in the USA you are only permitted to use this on DiskOnChip
+ hardware, although under the terms of the GPL you're obviously
+ permitted to copy, modify and distribute the code as you wish. Just
+ not use it.
+
+config NFTL_RW
+ bool "Write support for NFTL"
+ depends on NFTL
+ help
+ Support for writing to the NAND Flash Translation Layer, as used
+ on the DiskOnChip.
+
+config INFTL
+ tristate "INFTL (Inverse NAND Flash Translation Layer) support"
+ depends on MTD
+ ---help---
+ This provides support for the Inverse NAND Flash Translation
+ Layer which is used on M-Systems' newer DiskOnChip devices. It
+ uses a kind of pseudo-file system on a flash device to emulate
+ a block device with 512-byte sectors, on top of which you put
+ a 'normal' file system.
+
+ You may find that the algorithms used in this code are patented
+ unless you live in the Free World where software patents aren't
+ legal - in the USA you are only permitted to use this on DiskOnChip
+ hardware, although under the terms of the GPL you're obviously
+ permitted to copy, modify and distribute the code as you wish. Just
+ not use it.
+
+source "drivers/mtd/chips/Kconfig"
+
+source "drivers/mtd/maps/Kconfig"
+
+source "drivers/mtd/devices/Kconfig"
+
+source "drivers/mtd/nand/Kconfig"
+
+endmenu
+
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
new file mode 100644
index 000000000000..e4ad588327f7
--- /dev/null
+++ b/drivers/mtd/Makefile
@@ -0,0 +1,27 @@
+#
+# Makefile for the memory technology device drivers.
+#
+# $Id: Makefile.common,v 1.5 2004/08/10 20:51:49 dwmw2 Exp $
+
+# Core functionality.
+mtd-y := mtdcore.o
+mtd-$(CONFIG_MTD_PARTITIONS) += mtdpart.o
+obj-$(CONFIG_MTD) += $(mtd-y)
+
+obj-$(CONFIG_MTD_CONCAT) += mtdconcat.o
+obj-$(CONFIG_MTD_REDBOOT_PARTS) += redboot.o
+obj-$(CONFIG_MTD_CMDLINE_PARTS) += cmdlinepart.o
+obj-$(CONFIG_MTD_AFS_PARTS) += afs.o
+
+# 'Users' - code which presents functionality to userspace.
+obj-$(CONFIG_MTD_CHAR) += mtdchar.o
+obj-$(CONFIG_MTD_BLOCK) += mtdblock.o mtd_blkdevs.o
+obj-$(CONFIG_MTD_BLOCK_RO) += mtdblock_ro.o mtd_blkdevs.o
+obj-$(CONFIG_FTL) += ftl.o mtd_blkdevs.o
+obj-$(CONFIG_NFTL) += nftl.o mtd_blkdevs.o
+obj-$(CONFIG_INFTL) += inftl.o mtd_blkdevs.o
+
+nftl-objs := nftlcore.o nftlmount.o
+inftl-objs := inftlcore.o inftlmount.o
+
+obj-y += chips/ maps/ devices/ nand/
diff --git a/drivers/mtd/afs.c b/drivers/mtd/afs.c
new file mode 100644
index 000000000000..801e6c7d0892
--- /dev/null
+++ b/drivers/mtd/afs.c
@@ -0,0 +1,296 @@
+/*======================================================================
+
+ drivers/mtd/afs.c: ARM Flash Layout/Partitioning
+
+ Copyright (C) 2000 ARM Limited
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ This is access code for flashes using ARM's flash partitioning
+ standards.
+
+ $Id: afs.c,v 1.13 2004/02/27 22:09:59 rmk Exp $
+
+======================================================================*/
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/init.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+struct footer_struct {
+ u32 image_info_base; /* Address of first word of ImageFooter */
+ u32 image_start; /* Start of area reserved by this footer */
+ u32 signature; /* 'Magic' number proves it's a footer */
+ u32 type; /* Area type: ARM Image, SIB, customer */
+ u32 checksum; /* Just this structure */
+};
+
+struct image_info_struct {
+ u32 bootFlags; /* Boot flags, compression etc. */
+ u32 imageNumber; /* Unique number, selects for boot etc. */
+ u32 loadAddress; /* Address program should be loaded to */
+ u32 length; /* Actual size of image */
+ u32 address; /* Image is executed from here */
+ char name[16]; /* Null terminated */
+ u32 headerBase; /* Flash Address of any stripped header */
+ u32 header_length; /* Length of header in memory */
+ u32 headerType; /* AIF, RLF, s-record etc. */
+ u32 checksum; /* Image checksum (inc. this struct) */
+};
+
+static u32 word_sum(void *words, int num)
+{
+ u32 *p = words;
+ u32 sum = 0;
+
+ while (num--)
+ sum += *p++;
+
+ return sum;
+}
+
+static int
+afs_read_footer(struct mtd_info *mtd, u_int *img_start, u_int *iis_start,
+ u_int off, u_int mask)
+{
+ struct footer_struct fs;
+ u_int ptr = off + mtd->erasesize - sizeof(fs);
+ size_t sz;
+ int ret;
+
+ ret = mtd->read(mtd, ptr, sizeof(fs), &sz, (u_char *) &fs);
+ if (ret >= 0 && sz != sizeof(fs))
+ ret = -EINVAL;
+
+ if (ret < 0) {
+ printk(KERN_ERR "AFS: mtd read failed at 0x%x: %d\n",
+ ptr, ret);
+ return ret;
+ }
+
+ ret = 1;
+
+ /*
+ * Does it contain the magic number?
+ */
+ if (fs.signature != 0xa0ffff9f)
+ ret = 0;
+
+ /*
+ * Check the checksum.
+ */
+ if (word_sum(&fs, sizeof(fs) / sizeof(u32)) != 0xffffffff)
+ ret = 0;
+
+ /*
+ * Don't touch the SIB.
+ */
+ if (fs.type == 2)
+ ret = 0;
+
+ *iis_start = fs.image_info_base & mask;
+ *img_start = fs.image_start & mask;
+
+ /*
+ * Check the image info base. This can not
+ * be located after the footer structure.
+ */
+ if (*iis_start >= ptr)
+ ret = 0;
+
+ /*
+ * Check the start of this image. The image
+ * data can not be located after this block.
+ */
+ if (*img_start > off)
+ ret = 0;
+
+ return ret;
+}
+
+static int
+afs_read_iis(struct mtd_info *mtd, struct image_info_struct *iis, u_int ptr)
+{
+ size_t sz;
+ int ret, i;
+
+ memset(iis, 0, sizeof(*iis));
+ ret = mtd->read(mtd, ptr, sizeof(*iis), &sz, (u_char *) iis);
+ if (ret < 0)
+ goto failed;
+
+ if (sz != sizeof(*iis)) {
+ ret = -EINVAL;
+ goto failed;
+ }
+
+ ret = 0;
+
+ /*
+ * Validate the name - it must be NUL terminated.
+ */
+ for (i = 0; i < sizeof(iis->name); i++)
+ if (iis->name[i] == '\0')
+ break;
+
+ if (i < sizeof(iis->name))
+ ret = 1;
+
+ return ret;
+
+ failed:
+ printk(KERN_ERR "AFS: mtd read failed at 0x%x: %d\n",
+ ptr, ret);
+ return ret;
+}
+
+static int parse_afs_partitions(struct mtd_info *mtd,
+ struct mtd_partition **pparts,
+ unsigned long origin)
+{
+ struct mtd_partition *parts;
+ u_int mask, off, idx, sz;
+ int ret = 0;
+ char *str;
+
+ /*
+ * This is the address mask; we use this to mask off out of
+ * range address bits.
+ */
+ mask = mtd->size - 1;
+
+ /*
+ * First, calculate the size of the array we need for the
+ * partition information. We include in this the size of
+ * the strings.
+ */
+ for (idx = off = sz = 0; off < mtd->size; off += mtd->erasesize) {
+ struct image_info_struct iis;
+ u_int iis_ptr, img_ptr;
+
+ ret = afs_read_footer(mtd, &img_ptr, &iis_ptr, off, mask);
+ if (ret < 0)
+ break;
+ if (ret == 0)
+ continue;
+
+ ret = afs_read_iis(mtd, &iis, iis_ptr);
+ if (ret < 0)
+ break;
+ if (ret == 0)
+ continue;
+
+ sz += sizeof(struct mtd_partition);
+ sz += strlen(iis.name) + 1;
+ idx += 1;
+ }
+
+ if (!sz)
+ return ret;
+
+ parts = kmalloc(sz, GFP_KERNEL);
+ if (!parts)
+ return -ENOMEM;
+
+ memset(parts, 0, sz);
+ str = (char *)(parts + idx);
+
+ /*
+ * Identify the partitions
+ */
+ for (idx = off = 0; off < mtd->size; off += mtd->erasesize) {
+ struct image_info_struct iis;
+ u_int iis_ptr, img_ptr, size;
+
+ /* Read the footer. */
+ ret = afs_read_footer(mtd, &img_ptr, &iis_ptr, off, mask);
+ if (ret < 0)
+ break;
+ if (ret == 0)
+ continue;
+
+ /* Read the image info block */
+ ret = afs_read_iis(mtd, &iis, iis_ptr);
+ if (ret < 0)
+ break;
+ if (ret == 0)
+ continue;
+
+ strcpy(str, iis.name);
+ size = mtd->erasesize + off - img_ptr;
+
+ /*
+ * In order to support JFFS2 partitions on this layout,
+ * we must lie to MTD about the real size of JFFS2
+ * partitions; this ensures that the AFS flash footer
+ * won't be erased by JFFS2. Please ensure that your
+ * JFFS2 partitions are given image numbers between
+ * 1000 and 2000 inclusive.
+ */
+ if (iis.imageNumber >= 1000 && iis.imageNumber < 2000)
+ size -= mtd->erasesize;
+
+ parts[idx].name = str;
+ parts[idx].size = size;
+ parts[idx].offset = img_ptr;
+ parts[idx].mask_flags = 0;
+
+ printk(" mtd%d: at 0x%08x, %5dKB, %8u, %s\n",
+ idx, img_ptr, parts[idx].size / 1024,
+ iis.imageNumber, str);
+
+ idx += 1;
+ str = str + strlen(iis.name) + 1;
+ }
+
+ if (!idx) {
+ kfree(parts);
+ parts = NULL;
+ }
+
+ *pparts = parts;
+ return idx ? idx : ret;
+}
+
+static struct mtd_part_parser afs_parser = {
+ .owner = THIS_MODULE,
+ .parse_fn = parse_afs_partitions,
+ .name = "afs",
+};
+
+static int __init afs_parser_init(void)
+{
+ return register_mtd_parser(&afs_parser);
+}
+
+static void __exit afs_parser_exit(void)
+{
+ deregister_mtd_parser(&afs_parser);
+}
+
+module_init(afs_parser_init);
+module_exit(afs_parser_exit);
+
+
+MODULE_AUTHOR("ARM Ltd");
+MODULE_DESCRIPTION("ARM Firmware Suite partition parser");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/chips/Kconfig b/drivers/mtd/chips/Kconfig
new file mode 100644
index 000000000000..d682dbc8157e
--- /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 000000000000..6830489828c6
--- /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 000000000000..41e2e3e31603
--- /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 000000000000..c268bcd71720
--- /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 000000000000..fca8ff6f7e14
--- /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 000000000000..8c24e18db3b4
--- /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 000000000000..cf750038ce6a
--- /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 000000000000..2b2ede2bfcca
--- /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 000000000000..d7d739a108ae
--- /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 000000000000..fbf44708a861
--- /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 000000000000..fc982c4671f0
--- /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 000000000000..62d235a9a4e2
--- /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 000000000000..30325a25ab95
--- /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 000000000000..c6c83833cc32
--- /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 000000000000..bd2e876a814b
--- /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 000000000000..624c12c232c8
--- /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 000000000000..c3cf0f63bc93
--- /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");
diff --git a/drivers/mtd/cmdlinepart.c b/drivers/mtd/cmdlinepart.c
new file mode 100644
index 000000000000..60ab4b89a2f9
--- /dev/null
+++ b/drivers/mtd/cmdlinepart.c
@@ -0,0 +1,367 @@
+/*
+ * $Id: cmdlinepart.c,v 1.17 2004/11/26 11:18:47 lavinen Exp $
+ *
+ * Read flash partition table from command line
+ *
+ * Copyright 2002 SYSGO Real-Time Solutions GmbH
+ *
+ * The format for the command line is as follows:
+ *
+ * mtdparts=<mtddef>[;<mtddef]
+ * <mtddef> := <mtd-id>:<partdef>[,<partdef>]
+ * <partdef> := <size>[@offset][<name>][ro]
+ * <mtd-id> := unique name used in mapping driver/device (mtd->name)
+ * <size> := standard linux memsize OR "-" to denote all remaining space
+ * <name> := '(' NAME ')'
+ *
+ * Examples:
+ *
+ * 1 NOR Flash, with 1 single writable partition:
+ * edb7312-nor:-
+ *
+ * 1 NOR Flash with 2 partitions, 1 NAND with one
+ * edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home)
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/bootmem.h>
+
+/* error message prefix */
+#define ERRP "mtd: "
+
+/* debug macro */
+#if 0
+#define dbg(x) do { printk("DEBUG-CMDLINE-PART: "); printk x; } while(0)
+#else
+#define dbg(x)
+#endif
+
+
+/* special size referring to all the remaining space in a partition */
+#define SIZE_REMAINING 0xffffffff
+
+struct cmdline_mtd_partition {
+ struct cmdline_mtd_partition *next;
+ char *mtd_id;
+ int num_parts;
+ struct mtd_partition *parts;
+};
+
+/* mtdpart_setup() parses into here */
+static struct cmdline_mtd_partition *partitions;
+
+/* the command line passed to mtdpart_setupd() */
+static char *cmdline;
+static int cmdline_parsed = 0;
+
+/*
+ * Parse one partition definition for an MTD. Since there can be many
+ * comma separated partition definitions, this function calls itself
+ * recursively until no more partition definitions are found. Nice side
+ * effect: the memory to keep the mtd_partition structs and the names
+ * is allocated upon the last definition being found. At that point the
+ * syntax has been verified ok.
+ */
+static struct mtd_partition * newpart(char *s,
+ char **retptr,
+ int *num_parts,
+ int this_part,
+ unsigned char **extra_mem_ptr,
+ int extra_mem_size)
+{
+ struct mtd_partition *parts;
+ unsigned long size;
+ unsigned long offset = 0;
+ char *name;
+ int name_len;
+ unsigned char *extra_mem;
+ char delim;
+ unsigned int mask_flags;
+
+ /* fetch the partition size */
+ if (*s == '-')
+ { /* assign all remaining space to this partition */
+ size = SIZE_REMAINING;
+ s++;
+ }
+ else
+ {
+ size = memparse(s, &s);
+ if (size < PAGE_SIZE)
+ {
+ printk(KERN_ERR ERRP "partition size too small (%lx)\n", size);
+ return NULL;
+ }
+ }
+
+ /* fetch partition name and flags */
+ mask_flags = 0; /* this is going to be a regular partition */
+ delim = 0;
+ /* check for offset */
+ if (*s == '@')
+ {
+ s++;
+ offset = memparse(s, &s);
+ }
+ /* now look for name */
+ if (*s == '(')
+ {
+ delim = ')';
+ }
+
+ if (delim)
+ {
+ char *p;
+
+ name = ++s;
+ if ((p = strchr(name, delim)) == 0)
+ {
+ printk(KERN_ERR ERRP "no closing %c found in partition name\n", delim);
+ return NULL;
+ }
+ name_len = p - name;
+ s = p + 1;
+ }
+ else
+ {
+ name = NULL;
+ name_len = 13; /* Partition_000 */
+ }
+
+ /* record name length for memory allocation later */
+ extra_mem_size += name_len + 1;
+
+ /* test for options */
+ if (strncmp(s, "ro", 2) == 0)
+ {
+ mask_flags |= MTD_WRITEABLE;
+ s += 2;
+ }
+
+ /* test if more partitions are following */
+ if (*s == ',')
+ {
+ if (size == SIZE_REMAINING)
+ {
+ printk(KERN_ERR ERRP "no partitions allowed after a fill-up partition\n");
+ return NULL;
+ }
+ /* more partitions follow, parse them */
+ if ((parts = newpart(s + 1, &s, num_parts,
+ this_part + 1, &extra_mem, extra_mem_size)) == 0)
+ return NULL;
+ }
+ else
+ { /* this is the last partition: allocate space for all */
+ int alloc_size;
+
+ *num_parts = this_part + 1;
+ alloc_size = *num_parts * sizeof(struct mtd_partition) +
+ extra_mem_size;
+ parts = kmalloc(alloc_size, GFP_KERNEL);
+ if (!parts)
+ {
+ printk(KERN_ERR ERRP "out of memory\n");
+ return NULL;
+ }
+ memset(parts, 0, alloc_size);
+ extra_mem = (unsigned char *)(parts + *num_parts);
+ }
+ /* enter this partition (offset will be calculated later if it is zero at this point) */
+ parts[this_part].size = size;
+ parts[this_part].offset = offset;
+ parts[this_part].mask_flags = mask_flags;
+ if (name)
+ {
+ strlcpy(extra_mem, name, name_len + 1);
+ }
+ else
+ {
+ sprintf(extra_mem, "Partition_%03d", this_part);
+ }
+ parts[this_part].name = extra_mem;
+ extra_mem += name_len + 1;
+
+ dbg(("partition %d: name <%s>, offset %x, size %x, mask flags %x\n",
+ this_part,
+ parts[this_part].name,
+ parts[this_part].offset,
+ parts[this_part].size,
+ parts[this_part].mask_flags));
+
+ /* return (updated) pointer to extra_mem memory */
+ if (extra_mem_ptr)
+ *extra_mem_ptr = extra_mem;
+
+ /* return (updated) pointer command line string */
+ *retptr = s;
+
+ /* return partition table */
+ return parts;
+}
+
+/*
+ * Parse the command line.
+ */
+static int mtdpart_setup_real(char *s)
+{
+ cmdline_parsed = 1;
+
+ for( ; s != NULL; )
+ {
+ struct cmdline_mtd_partition *this_mtd;
+ struct mtd_partition *parts;
+ int mtd_id_len;
+ int num_parts;
+ char *p, *mtd_id;
+
+ mtd_id = s;
+ /* fetch <mtd-id> */
+ if (!(p = strchr(s, ':')))
+ {
+ printk(KERN_ERR ERRP "no mtd-id\n");
+ return 0;
+ }
+ mtd_id_len = p - mtd_id;
+
+ dbg(("parsing <%s>\n", p+1));
+
+ /*
+ * parse one mtd. have it reserve memory for the
+ * struct cmdline_mtd_partition and the mtd-id string.
+ */
+ parts = newpart(p + 1, /* cmdline */
+ &s, /* out: updated cmdline ptr */
+ &num_parts, /* out: number of parts */
+ 0, /* first partition */
+ (unsigned char**)&this_mtd, /* out: extra mem */
+ mtd_id_len + 1 + sizeof(*this_mtd));
+ if(!parts)
+ {
+ /*
+ * An error occurred. We're either:
+ * a) out of memory, or
+ * b) in the middle of the partition spec
+ * Either way, this mtd is hosed and we're
+ * unlikely to succeed in parsing any more
+ */
+ return 0;
+ }
+
+ /* enter results */
+ this_mtd->parts = parts;
+ this_mtd->num_parts = num_parts;
+ this_mtd->mtd_id = (char*)(this_mtd + 1);
+ strlcpy(this_mtd->mtd_id, mtd_id, mtd_id_len + 1);
+
+ /* link into chain */
+ this_mtd->next = partitions;
+ partitions = this_mtd;
+
+ dbg(("mtdid=<%s> num_parts=<%d>\n",
+ this_mtd->mtd_id, this_mtd->num_parts));
+
+
+ /* EOS - we're done */
+ if (*s == 0)
+ break;
+
+ /* does another spec follow? */
+ if (*s != ';')
+ {
+ printk(KERN_ERR ERRP "bad character after partition (%c)\n", *s);
+ return 0;
+ }
+ s++;
+ }
+ return 1;
+}
+
+/*
+ * Main function to be called from the MTD mapping driver/device to
+ * obtain the partitioning information. At this point the command line
+ * arguments will actually be parsed and turned to struct mtd_partition
+ * information. It returns partitions for the requested mtd device, or
+ * the first one in the chain if a NULL mtd_id is passed in.
+ */
+static int parse_cmdline_partitions(struct mtd_info *master,
+ struct mtd_partition **pparts,
+ unsigned long origin)
+{
+ unsigned long offset;
+ int i;
+ struct cmdline_mtd_partition *part;
+ char *mtd_id = master->name;
+
+ if(!cmdline)
+ return -EINVAL;
+
+ /* parse command line */
+ if (!cmdline_parsed)
+ mtdpart_setup_real(cmdline);
+
+ for(part = partitions; part; part = part->next)
+ {
+ if ((!mtd_id) || (!strcmp(part->mtd_id, mtd_id)))
+ {
+ for(i = 0, offset = 0; i < part->num_parts; i++)
+ {
+ if (!part->parts[i].offset)
+ part->parts[i].offset = offset;
+ else
+ offset = part->parts[i].offset;
+ if (part->parts[i].size == SIZE_REMAINING)
+ part->parts[i].size = master->size - offset;
+ if (offset + part->parts[i].size > master->size)
+ {
+ printk(KERN_WARNING ERRP
+ "%s: partitioning exceeds flash size, truncating\n",
+ part->mtd_id);
+ part->parts[i].size = master->size - offset;
+ part->num_parts = i;
+ }
+ offset += part->parts[i].size;
+ }
+ *pparts = part->parts;
+ return part->num_parts;
+ }
+ }
+ return -EINVAL;
+}
+
+
+/*
+ * This is the handler for our kernel parameter, called from
+ * main.c::checksetup(). Note that we can not yet kmalloc() anything,
+ * so we only save the commandline for later processing.
+ *
+ * This function needs to be visible for bootloaders.
+ */
+int mtdpart_setup(char *s)
+{
+ cmdline = s;
+ return 1;
+}
+
+__setup("mtdparts=", mtdpart_setup);
+
+static struct mtd_part_parser cmdline_parser = {
+ .owner = THIS_MODULE,
+ .parse_fn = parse_cmdline_partitions,
+ .name = "cmdlinepart",
+};
+
+static int __init cmdline_parser_init(void)
+{
+ return register_mtd_parser(&cmdline_parser);
+}
+
+module_init(cmdline_parser_init);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Marius Groeger <mag@sysgo.de>");
+MODULE_DESCRIPTION("Command line configuration of MTD partitions");
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
new file mode 100644
index 000000000000..c4a56a4ac5e2
--- /dev/null
+++ b/drivers/mtd/devices/Kconfig
@@ -0,0 +1,259 @@
+# drivers/mtd/maps/Kconfig
+# $Id: Kconfig,v 1.15 2004/12/22 17:51:15 joern Exp $
+
+menu "Self-contained MTD device drivers"
+ depends on MTD!=n
+
+config MTD_PMC551
+ tristate "Ramix PMC551 PCI Mezzanine RAM card support"
+ depends on MTD && PCI
+ ---help---
+ This provides a MTD device driver for the Ramix PMC551 RAM PCI card
+ from Ramix Inc. <http://www.ramix.com/products/memory/pmc551.html>.
+ These devices come in memory configurations from 32M - 1G. If you
+ have one, you probably want to enable this.
+
+ If this driver is compiled as a module you get the ability to select
+ the size of the aperture window pointing into the devices memory.
+ What this means is that if you have a 1G card, normally the kernel
+ will use a 1G memory map as its view of the device. As a module,
+ you can select a 1M window into the memory and the driver will
+ "slide" the window around the PMC551's memory. This was
+ particularly useful on the 2.2 kernels on PPC architectures as there
+ was limited kernel space to deal with.
+
+config MTD_PMC551_BUGFIX
+ bool "PMC551 256M DRAM Bugfix"
+ depends on MTD_PMC551
+ help
+ Some of Ramix's PMC551 boards with 256M configurations have invalid
+ column and row mux values. This option will fix them, but will
+ break other memory configurations. If unsure say N.
+
+config MTD_PMC551_DEBUG
+ bool "PMC551 Debugging"
+ depends on MTD_PMC551
+ help
+ This option makes the PMC551 more verbose during its operation and
+ is only really useful if you are developing on this driver or
+ suspect a possible hardware or driver bug. If unsure say N.
+
+config MTD_MS02NV
+ tristate "DEC MS02-NV NVRAM module support"
+ depends on MTD && MACH_DECSTATION
+ help
+ This is an MTD driver for the DEC's MS02-NV (54-20948-01) battery
+ backed-up NVRAM module. The module was originally meant as an NFS
+ accelerator. Say Y here if you have a DECstation 5000/2x0 or a
+ DECsystem 5900 equipped with such a module.
+
+config MTD_SLRAM
+ tristate "Uncached system RAM"
+ depends on MTD
+ help
+ If your CPU cannot cache all of the physical memory in your machine,
+ you can still use it for storage or swap by using this driver to
+ present it to the system as a Memory Technology Device.
+
+config MTD_PHRAM
+ tristate "Physical system RAM"
+ depends on MTD
+ help
+ This is a re-implementation of the slram driver above.
+
+ Use this driver to access physical memory that the kernel proper
+ doesn't have access to, memory beyond the mem=xxx limit, nvram,
+ memory on the video card, etc...
+
+config MTD_LART
+ tristate "28F160xx flash driver for LART"
+ depends on SA1100_LART && MTD
+ help
+ This enables the flash driver for LART. Please note that you do
+ not need any mapping/chip driver for LART. This one does it all
+ for you, so go disable all of those if you enabled some of them (:
+
+config MTD_MTDRAM
+ tristate "Test driver using RAM"
+ depends on MTD
+ help
+ This enables a test MTD device driver which uses vmalloc() to
+ provide storage. You probably want to say 'N' unless you're
+ testing stuff.
+
+config MTDRAM_TOTAL_SIZE
+ int "MTDRAM device size in KiB"
+ depends on MTD_MTDRAM
+ default "4096"
+ help
+ This allows you to configure the total size of the MTD device
+ emulated by the MTDRAM driver. If the MTDRAM driver is built
+ as a module, it is also possible to specify this as a parameter when
+ loading the module.
+
+config MTDRAM_ERASE_SIZE
+ int "MTDRAM erase block size in KiB"
+ depends on MTD_MTDRAM
+ default "128"
+ help
+ This allows you to configure the size of the erase blocks in the
+ device emulated by the MTDRAM driver. If the MTDRAM driver is built
+ as a module, it is also possible to specify this as a parameter when
+ loading the module.
+
+#If not a module (I don't want to test it as a module)
+config MTDRAM_ABS_POS
+ hex "SRAM Hexadecimal Absolute position or 0"
+ depends on MTD_MTDRAM=y
+ default "0"
+ help
+ If you have system RAM accessible by the CPU but not used by Linux
+ in normal operation, you can give the physical address at which the
+ available RAM starts, and the MTDRAM driver will use it instead of
+ allocating space from Linux's available memory. Otherwise, leave
+ this set to zero. Most people will want to leave this as zero.
+
+config MTD_BLKMTD
+ tristate "MTD emulation using block device"
+ depends on MTD
+ help
+ This driver allows a block device to appear as an MTD. It would
+ generally be used in the following cases:
+
+ Using Compact Flash as an MTD, these usually present themselves to
+ the system as an ATA drive.
+ Testing MTD users (eg JFFS2) on large media and media that might
+ be removed during a write (using the floppy drive).
+
+config MTD_BLOCK2MTD
+ tristate "MTD using block device (rewrite)"
+ depends on MTD && EXPERIMENTAL
+ help
+ This driver is basically the same at MTD_BLKMTD above, but
+ experienced some interface changes plus serious speedups. In
+ the long term, it should replace MTD_BLKMTD. Right now, you
+ shouldn't entrust important data to it yet.
+
+comment "Disk-On-Chip Device Drivers"
+
+config MTD_DOC2000
+ tristate "M-Systems Disk-On-Chip 2000 and Millennium (DEPRECATED)"
+ depends on MTD
+ select MTD_DOCPROBE
+ select MTD_NAND_IDS
+ ---help---
+ This provides an MTD device driver for the M-Systems DiskOnChip
+ 2000 and Millennium devices. Originally designed for the DiskOnChip
+ 2000, it also now includes support for the DiskOnChip Millennium.
+ If you have problems with this driver and the DiskOnChip Millennium,
+ you may wish to try the alternative Millennium driver below. To use
+ the alternative driver, you will need to undefine DOC_SINGLE_DRIVER
+ in the <file:drivers/mtd/devices/docprobe.c> source code.
+
+ If you use this device, you probably also want to enable the NFTL
+ 'NAND Flash Translation Layer' option below, which is used to
+ emulate a block device by using a kind of file system on the flash
+ chips.
+
+ NOTE: This driver is deprecated and will probably be removed soon.
+ Please try the new DiskOnChip driver under "NAND Flash Device
+ Drivers".
+
+config MTD_DOC2001
+ tristate "M-Systems Disk-On-Chip Millennium-only alternative driver (DEPRECATED)"
+ depends on MTD
+ select MTD_DOCPROBE
+ select MTD_NAND_IDS
+ ---help---
+ This provides an alternative MTD device driver for the M-Systems
+ DiskOnChip Millennium devices. Use this if you have problems with
+ the combined DiskOnChip 2000 and Millennium driver above. To get
+ the DiskOnChip probe code to load and use this driver instead of
+ the other one, you will need to undefine DOC_SINGLE_DRIVER near
+ the beginning of <file:drivers/mtd/devices/docprobe.c>.
+
+ If you use this device, you probably also want to enable the NFTL
+ 'NAND Flash Translation Layer' option below, which is used to
+ emulate a block device by using a kind of file system on the flash
+ chips.
+
+ NOTE: This driver is deprecated and will probably be removed soon.
+ Please try the new DiskOnChip driver under "NAND Flash Device
+ Drivers".
+
+config MTD_DOC2001PLUS
+ tristate "M-Systems Disk-On-Chip Millennium Plus"
+ depends on MTD
+ select MTD_DOCPROBE
+ select MTD_NAND_IDS
+ ---help---
+ This provides an MTD device driver for the M-Systems DiskOnChip
+ Millennium Plus devices.
+
+ If you use this device, you probably also want to enable the INFTL
+ 'Inverse NAND Flash Translation Layer' option below, which is used
+ to emulate a block device by using a kind of file system on the
+ flash chips.
+
+ NOTE: This driver will soon be replaced by the new DiskOnChip driver
+ under "NAND Flash Device Drivers" (currently that driver does not
+ support all Millennium Plus devices).
+
+config MTD_DOCPROBE
+ tristate
+ select MTD_DOCECC
+
+config MTD_DOCECC
+ tristate
+
+config MTD_DOCPROBE_ADVANCED
+ bool "Advanced detection options for DiskOnChip"
+ depends on MTD_DOCPROBE
+ help
+ This option allows you to specify nonstandard address at which to
+ probe for a DiskOnChip, or to change the detection options. You
+ are unlikely to need any of this unless you are using LinuxBIOS.
+ Say 'N'.
+
+config MTD_DOCPROBE_ADDRESS
+ hex "Physical address of DiskOnChip" if MTD_DOCPROBE_ADVANCED
+ depends on MTD_DOCPROBE
+ default "0x0000" if MTD_DOCPROBE_ADVANCED
+ default "0" if !MTD_DOCPROBE_ADVANCED
+ ---help---
+ By default, the probe for DiskOnChip devices will look for a
+ DiskOnChip at every multiple of 0x2000 between 0xC8000 and 0xEE000.
+ This option allows you to specify a single address at which to probe
+ for the device, which is useful if you have other devices in that
+ range which get upset when they are probed.
+
+ (Note that on PowerPC, the normal probe will only check at
+ 0xE4000000.)
+
+ Normally, you should leave this set to zero, to allow the probe at
+ the normal addresses.
+
+config MTD_DOCPROBE_HIGH
+ bool "Probe high addresses"
+ depends on MTD_DOCPROBE_ADVANCED
+ help
+ By default, the probe for DiskOnChip devices will look for a
+ DiskOnChip at every multiple of 0x2000 between 0xC8000 and 0xEE000.
+ This option changes to make it probe between 0xFFFC8000 and
+ 0xFFFEE000. Unless you are using LinuxBIOS, this is unlikely to be
+ useful to you. Say 'N'.
+
+config MTD_DOCPROBE_55AA
+ bool "Probe for 0x55 0xAA BIOS Extension Signature"
+ depends on MTD_DOCPROBE_ADVANCED
+ help
+ Check for the 0x55 0xAA signature of a DiskOnChip, and do not
+ continue with probing if it is absent. The signature will always be
+ present for a DiskOnChip 2000 or a normal DiskOnChip Millennium.
+ Only if you have overwritten the first block of a DiskOnChip
+ Millennium will it be absent. Enable this option if you are using
+ LinuxBIOS or if you need to recover a DiskOnChip Millennium on which
+ you have managed to wipe the first block.
+
+endmenu
+
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
new file mode 100644
index 000000000000..e38db348057d
--- /dev/null
+++ b/drivers/mtd/devices/Makefile
@@ -0,0 +1,25 @@
+#
+# linux/drivers/devices/Makefile
+#
+# $Id: Makefile.common,v 1.7 2004/12/22 17:51:15 joern 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
+# doc200[01].o are linked before docprobe.o
+
+obj-$(CONFIG_MTD_DOC2000) += doc2000.o
+obj-$(CONFIG_MTD_DOC2001) += doc2001.o
+obj-$(CONFIG_MTD_DOC2001PLUS) += doc2001plus.o
+obj-$(CONFIG_MTD_DOCPROBE) += docprobe.o
+obj-$(CONFIG_MTD_DOCECC) += docecc.o
+obj-$(CONFIG_MTD_SLRAM) += slram.o
+obj-$(CONFIG_MTD_PHRAM) += phram.o
+obj-$(CONFIG_MTD_PMC551) += pmc551.o
+obj-$(CONFIG_MTD_MS02NV) += ms02-nv.o
+obj-$(CONFIG_MTD_MTDRAM) += mtdram.o
+obj-$(CONFIG_MTD_LART) += lart.o
+obj-$(CONFIG_MTD_BLKMTD) += blkmtd.o
+obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o
diff --git a/drivers/mtd/devices/blkmtd.c b/drivers/mtd/devices/blkmtd.c
new file mode 100644
index 000000000000..662e807801ed
--- /dev/null
+++ b/drivers/mtd/devices/blkmtd.c
@@ -0,0 +1,823 @@
+/*
+ * $Id: blkmtd.c,v 1.24 2004/11/16 18:29:01 dwmw2 Exp $
+ *
+ * blkmtd.c - use a block device as a fake MTD
+ *
+ * Author: Simon Evans <spse@secret.org.uk>
+ *
+ * Copyright (C) 2001,2002 Simon Evans
+ *
+ * Licence: GPL
+ *
+ * How it works:
+ * The driver uses raw/io to read/write the device and the page
+ * cache to cache access. Writes update the page cache with the
+ * new data and mark it dirty and add the page into a BIO which
+ * is then written out.
+ *
+ * It can be loaded Read-Only to prevent erases and writes to the
+ * medium.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/blkdev.h>
+#include <linux/bio.h>
+#include <linux/pagemap.h>
+#include <linux/list.h>
+#include <linux/init.h>
+#include <linux/mtd/mtd.h>
+
+
+#define err(format, arg...) printk(KERN_ERR "blkmtd: " format "\n" , ## arg)
+#define info(format, arg...) printk(KERN_INFO "blkmtd: " format "\n" , ## arg)
+#define warn(format, arg...) printk(KERN_WARNING "blkmtd: " format "\n" , ## arg)
+#define crit(format, arg...) printk(KERN_CRIT "blkmtd: " format "\n" , ## arg)
+
+
+/* Default erase size in K, always make it a multiple of PAGE_SIZE */
+#define CONFIG_MTD_BLKDEV_ERASESIZE (128 << 10) /* 128KiB */
+#define VERSION "$Revision: 1.24 $"
+
+/* Info for the block device */
+struct blkmtd_dev {
+ struct list_head list;
+ struct block_device *blkdev;
+ struct mtd_info mtd_info;
+ struct semaphore wrbuf_mutex;
+};
+
+
+/* Static info about the MTD, used in cleanup_module */
+static LIST_HEAD(blkmtd_device_list);
+
+
+static void blkmtd_sync(struct mtd_info *mtd);
+
+#define MAX_DEVICES 4
+
+/* Module parameters passed by insmod/modprobe */
+static char *device[MAX_DEVICES]; /* the block device to use */
+static int erasesz[MAX_DEVICES]; /* optional default erase size */
+static int ro[MAX_DEVICES]; /* optional read only flag */
+static int sync;
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Simon Evans <spse@secret.org.uk>");
+MODULE_DESCRIPTION("Emulate an MTD using a block device");
+module_param_array(device, charp, NULL, 0);
+MODULE_PARM_DESC(device, "block device to use");
+module_param_array(erasesz, int, NULL, 0);
+MODULE_PARM_DESC(erasesz, "optional erase size to use in KiB. eg 4=4KiB.");
+module_param_array(ro, bool, NULL, 0);
+MODULE_PARM_DESC(ro, "1=Read only, writes and erases cause errors");
+module_param(sync, bool, 0);
+MODULE_PARM_DESC(sync, "1=Synchronous writes");
+
+
+/* completion handler for BIO reads */
+static int bi_read_complete(struct bio *bio, unsigned int bytes_done, int error)
+{
+ if (bio->bi_size)
+ return 1;
+
+ complete((struct completion*)bio->bi_private);
+ return 0;
+}
+
+
+/* completion handler for BIO writes */
+static int bi_write_complete(struct bio *bio, unsigned int bytes_done, int error)
+{
+ const int uptodate = test_bit(BIO_UPTODATE, &bio->bi_flags);
+ struct bio_vec *bvec = bio->bi_io_vec + bio->bi_vcnt - 1;
+
+ if (bio->bi_size)
+ return 1;
+
+ if(!uptodate)
+ err("bi_write_complete: not uptodate\n");
+
+ do {
+ struct page *page = bvec->bv_page;
+ DEBUG(3, "Cleaning up page %ld\n", page->index);
+ if (--bvec >= bio->bi_io_vec)
+ prefetchw(&bvec->bv_page->flags);
+
+ if (uptodate) {
+ SetPageUptodate(page);
+ } else {
+ ClearPageUptodate(page);
+ SetPageError(page);
+ }
+ ClearPageDirty(page);
+ unlock_page(page);
+ page_cache_release(page);
+ } while (bvec >= bio->bi_io_vec);
+
+ complete((struct completion*)bio->bi_private);
+ return 0;
+}
+
+
+/* read one page from the block device */
+static int blkmtd_readpage(struct blkmtd_dev *dev, struct page *page)
+{
+ struct bio *bio;
+ struct completion event;
+ int err = -ENOMEM;
+
+ if(PageUptodate(page)) {
+ DEBUG(2, "blkmtd: readpage page %ld is already upto date\n", page->index);
+ unlock_page(page);
+ return 0;
+ }
+
+ ClearPageUptodate(page);
+ ClearPageError(page);
+
+ bio = bio_alloc(GFP_KERNEL, 1);
+ if(bio) {
+ init_completion(&event);
+ bio->bi_bdev = dev->blkdev;
+ bio->bi_sector = page->index << (PAGE_SHIFT-9);
+ bio->bi_private = &event;
+ bio->bi_end_io = bi_read_complete;
+ if(bio_add_page(bio, page, PAGE_SIZE, 0) == PAGE_SIZE) {
+ submit_bio(READ_SYNC, bio);
+ wait_for_completion(&event);
+ err = test_bit(BIO_UPTODATE, &bio->bi_flags) ? 0 : -EIO;
+ bio_put(bio);
+ }
+ }
+
+ if(err)
+ SetPageError(page);
+ else
+ SetPageUptodate(page);
+ flush_dcache_page(page);
+ unlock_page(page);
+ return err;
+}
+
+
+/* write out the current BIO and wait for it to finish */
+static int blkmtd_write_out(struct bio *bio)
+{
+ struct completion event;
+ int err;
+
+ if(!bio->bi_vcnt) {
+ bio_put(bio);
+ return 0;
+ }
+
+ init_completion(&event);
+ bio->bi_private = &event;
+ bio->bi_end_io = bi_write_complete;
+ submit_bio(WRITE_SYNC, bio);
+ wait_for_completion(&event);
+ DEBUG(3, "submit_bio completed, bi_vcnt = %d\n", bio->bi_vcnt);
+ err = test_bit(BIO_UPTODATE, &bio->bi_flags) ? 0 : -EIO;
+ bio_put(bio);
+ return err;
+}
+
+
+/**
+ * blkmtd_add_page - add a page to the current BIO
+ * @bio: bio to add to (NULL to alloc initial bio)
+ * @blkdev: block device
+ * @page: page to add
+ * @pagecnt: pages left to add
+ *
+ * Adds a page to the current bio, allocating it if necessary. If it cannot be
+ * added, the current bio is written out and a new one is allocated. Returns
+ * the new bio to add or NULL on error
+ */
+static struct bio *blkmtd_add_page(struct bio *bio, struct block_device *blkdev,
+ struct page *page, int pagecnt)
+{
+
+ retry:
+ if(!bio) {
+ bio = bio_alloc(GFP_KERNEL, pagecnt);
+ if(!bio)
+ return NULL;
+ bio->bi_sector = page->index << (PAGE_SHIFT-9);
+ bio->bi_bdev = blkdev;
+ }
+
+ if(bio_add_page(bio, page, PAGE_SIZE, 0) != PAGE_SIZE) {
+ blkmtd_write_out(bio);
+ bio = NULL;
+ goto retry;
+ }
+ return bio;
+}
+
+
+/**
+ * write_pages - write block of data to device via the page cache
+ * @dev: device to write to
+ * @buf: data source or NULL if erase (output is set to 0xff)
+ * @to: offset into output device
+ * @len: amount to data to write
+ * @retlen: amount of data written
+ *
+ * Grab pages from the page cache and fill them with the source data.
+ * Non page aligned start and end result in a readin of the page and
+ * part of the page being modified. Pages are added to the bio and then written
+ * out.
+ */
+static int write_pages(struct blkmtd_dev *dev, const u_char *buf, loff_t to,
+ size_t len, size_t *retlen)
+{
+ int pagenr, offset;
+ size_t start_len = 0, end_len;
+ int pagecnt = 0;
+ int err = 0;
+ struct bio *bio = NULL;
+ size_t thislen = 0;
+
+ pagenr = to >> PAGE_SHIFT;
+ offset = to & ~PAGE_MASK;
+
+ DEBUG(2, "blkmtd: write_pages: buf = %p to = %ld len = %zd pagenr = %d offset = %d\n",
+ buf, (long)to, len, pagenr, offset);
+
+ /* see if we have to do a partial write at the start */
+ if(offset) {
+ start_len = ((offset + len) > PAGE_SIZE) ? PAGE_SIZE - offset : len;
+ len -= start_len;
+ }
+
+ /* calculate the length of the other two regions */
+ end_len = len & ~PAGE_MASK;
+ len -= end_len;
+
+ if(start_len)
+ pagecnt++;
+
+ if(len)
+ pagecnt += len >> PAGE_SHIFT;
+
+ if(end_len)
+ pagecnt++;
+
+ down(&dev->wrbuf_mutex);
+
+ DEBUG(3, "blkmtd: write: start_len = %zd len = %zd end_len = %zd pagecnt = %d\n",
+ start_len, len, end_len, pagecnt);
+
+ if(start_len) {
+ /* do partial start region */
+ struct page *page;
+
+ DEBUG(3, "blkmtd: write: doing partial start, page = %d len = %zd offset = %d\n",
+ pagenr, start_len, offset);
+
+ BUG_ON(!buf);
+ page = read_cache_page(dev->blkdev->bd_inode->i_mapping, pagenr, (filler_t *)blkmtd_readpage, dev);
+ lock_page(page);
+ if(PageDirty(page)) {
+ err("to = %lld start_len = %zd len = %zd end_len = %zd pagenr = %d\n",
+ to, start_len, len, end_len, pagenr);
+ BUG();
+ }
+ memcpy(page_address(page)+offset, buf, start_len);
+ SetPageDirty(page);
+ SetPageUptodate(page);
+ buf += start_len;
+ thislen = start_len;
+ bio = blkmtd_add_page(bio, dev->blkdev, page, pagecnt);
+ if(!bio) {
+ err = -ENOMEM;
+ err("bio_add_page failed\n");
+ goto write_err;
+ }
+ pagecnt--;
+ pagenr++;
+ }
+
+ /* Now do the main loop to a page aligned, n page sized output */
+ if(len) {
+ int pagesc = len >> PAGE_SHIFT;
+ DEBUG(3, "blkmtd: write: whole pages start = %d, count = %d\n",
+ pagenr, pagesc);
+ while(pagesc) {
+ struct page *page;
+
+ /* see if page is in the page cache */
+ DEBUG(3, "blkmtd: write: grabbing page %d from page cache\n", pagenr);
+ page = grab_cache_page(dev->blkdev->bd_inode->i_mapping, pagenr);
+ if(PageDirty(page)) {
+ BUG();
+ }
+ if(!page) {
+ warn("write: cannot grab cache page %d", pagenr);
+ err = -ENOMEM;
+ goto write_err;
+ }
+ if(!buf) {
+ memset(page_address(page), 0xff, PAGE_SIZE);
+ } else {
+ memcpy(page_address(page), buf, PAGE_SIZE);
+ buf += PAGE_SIZE;
+ }
+ bio = blkmtd_add_page(bio, dev->blkdev, page, pagecnt);
+ if(!bio) {
+ err = -ENOMEM;
+ err("bio_add_page failed\n");
+ goto write_err;
+ }
+ pagenr++;
+ pagecnt--;
+ SetPageDirty(page);
+ SetPageUptodate(page);
+ pagesc--;
+ thislen += PAGE_SIZE;
+ }
+ }
+
+ if(end_len) {
+ /* do the third region */
+ struct page *page;
+ DEBUG(3, "blkmtd: write: doing partial end, page = %d len = %zd\n",
+ pagenr, end_len);
+ BUG_ON(!buf);
+ page = read_cache_page(dev->blkdev->bd_inode->i_mapping, pagenr, (filler_t *)blkmtd_readpage, dev);
+ lock_page(page);
+ if(PageDirty(page)) {
+ err("to = %lld start_len = %zd len = %zd end_len = %zd pagenr = %d\n",
+ to, start_len, len, end_len, pagenr);
+ BUG();
+ }
+ memcpy(page_address(page), buf, end_len);
+ SetPageDirty(page);
+ SetPageUptodate(page);
+ DEBUG(3, "blkmtd: write: writing out partial end\n");
+ thislen += end_len;
+ bio = blkmtd_add_page(bio, dev->blkdev, page, pagecnt);
+ if(!bio) {
+ err = -ENOMEM;
+ err("bio_add_page failed\n");
+ goto write_err;
+ }
+ pagenr++;
+ }
+
+ DEBUG(3, "blkmtd: write: got %d vectors to write\n", bio->bi_vcnt);
+ write_err:
+ if(bio)
+ blkmtd_write_out(bio);
+
+ DEBUG(2, "blkmtd: write: end, retlen = %zd, err = %d\n", *retlen, err);
+ up(&dev->wrbuf_mutex);
+
+ if(retlen)
+ *retlen = thislen;
+ return err;
+}
+
+
+/* erase a specified part of the device */
+static int blkmtd_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct blkmtd_dev *dev = mtd->priv;
+ struct mtd_erase_region_info *einfo = mtd->eraseregions;
+ int numregions = mtd->numeraseregions;
+ size_t from;
+ u_long len;
+ int err = -EIO;
+ size_t retlen;
+
+ instr->state = MTD_ERASING;
+ from = instr->addr;
+ len = instr->len;
+
+ /* check erase region has valid start and length */
+ DEBUG(2, "blkmtd: erase: dev = `%s' from = 0x%zx len = 0x%lx\n",
+ mtd->name+9, from, len);
+ while(numregions) {
+ DEBUG(3, "blkmtd: checking erase region = 0x%08X size = 0x%X num = 0x%x\n",
+ einfo->offset, einfo->erasesize, einfo->numblocks);
+ if(from >= einfo->offset
+ && from < einfo->offset + (einfo->erasesize * einfo->numblocks)) {
+ if(len == einfo->erasesize
+ && ( (from - einfo->offset) % einfo->erasesize == 0))
+ break;
+ }
+ numregions--;
+ einfo++;
+ }
+
+ if(!numregions) {
+ /* Not a valid erase block */
+ err("erase: invalid erase request 0x%lX @ 0x%08zX", len, from);
+ instr->state = MTD_ERASE_FAILED;
+ err = -EIO;
+ }
+
+ if(instr->state != MTD_ERASE_FAILED) {
+ /* do the erase */
+ DEBUG(3, "Doing erase from = %zd len = %ld\n", from, len);
+ err = write_pages(dev, NULL, from, len, &retlen);
+ if(err || retlen != len) {
+ err("erase failed err = %d", err);
+ instr->state = MTD_ERASE_FAILED;
+ } else {
+ instr->state = MTD_ERASE_DONE;
+ }
+ }
+
+ DEBUG(3, "blkmtd: erase: checking callback\n");
+ mtd_erase_callback(instr);
+ DEBUG(2, "blkmtd: erase: finished (err = %d)\n", err);
+ return err;
+}
+
+
+/* read a range of the data via the page cache */
+static int blkmtd_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct blkmtd_dev *dev = mtd->priv;
+ int err = 0;
+ int offset;
+ int pagenr, pages;
+ size_t thislen = 0;
+
+ DEBUG(2, "blkmtd: read: dev = `%s' from = %lld len = %zd buf = %p\n",
+ mtd->name+9, from, len, buf);
+
+ if(from > mtd->size)
+ return -EINVAL;
+ if(from + len > mtd->size)
+ len = mtd->size - from;
+
+ pagenr = from >> PAGE_SHIFT;
+ offset = from - (pagenr << PAGE_SHIFT);
+
+ pages = (offset+len+PAGE_SIZE-1) >> PAGE_SHIFT;
+ DEBUG(3, "blkmtd: read: pagenr = %d offset = %d, pages = %d\n",
+ pagenr, offset, pages);
+
+ while(pages) {
+ struct page *page;
+ int cpylen;
+
+ DEBUG(3, "blkmtd: read: looking for page: %d\n", pagenr);
+ page = read_cache_page(dev->blkdev->bd_inode->i_mapping, pagenr, (filler_t *)blkmtd_readpage, dev);
+ if(IS_ERR(page)) {
+ err = -EIO;
+ goto readerr;
+ }
+
+ cpylen = (PAGE_SIZE > len) ? len : PAGE_SIZE;
+ if(offset+cpylen > PAGE_SIZE)
+ cpylen = PAGE_SIZE-offset;
+
+ memcpy(buf + thislen, page_address(page) + offset, cpylen);
+ offset = 0;
+ len -= cpylen;
+ thislen += cpylen;
+ pagenr++;
+ pages--;
+ if(!PageDirty(page))
+ page_cache_release(page);
+ }
+
+ readerr:
+ if(retlen)
+ *retlen = thislen;
+ DEBUG(2, "blkmtd: end read: retlen = %zd, err = %d\n", thislen, err);
+ return err;
+}
+
+
+/* write data to the underlying device */
+static int blkmtd_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct blkmtd_dev *dev = mtd->priv;
+ int err;
+
+ if(!len)
+ return 0;
+
+ DEBUG(2, "blkmtd: write: dev = `%s' to = %lld len = %zd buf = %p\n",
+ mtd->name+9, to, len, buf);
+
+ if(to >= mtd->size) {
+ return -ENOSPC;
+ }
+
+ if(to + len > mtd->size) {
+ len = mtd->size - to;
+ }
+
+ err = write_pages(dev, buf, to, len, retlen);
+ if(err > 0)
+ err = 0;
+ DEBUG(2, "blkmtd: write: end, err = %d\n", err);
+ return err;
+}
+
+
+/* sync the device - wait until the write queue is empty */
+static void blkmtd_sync(struct mtd_info *mtd)
+{
+ /* Currently all writes are synchronous */
+}
+
+
+static void free_device(struct blkmtd_dev *dev)
+{
+ DEBUG(2, "blkmtd: free_device() dev = %p\n", dev);
+ if(dev) {
+ if(dev->mtd_info.eraseregions)
+ kfree(dev->mtd_info.eraseregions);
+ if(dev->mtd_info.name)
+ kfree(dev->mtd_info.name);
+
+ if(dev->blkdev) {
+ invalidate_inode_pages(dev->blkdev->bd_inode->i_mapping);
+ close_bdev_excl(dev->blkdev);
+ }
+ kfree(dev);
+ }
+}
+
+
+/* For a given size and initial erase size, calculate the number
+ * and size of each erase region. Goes round the loop twice,
+ * once to find out how many regions, then allocates space,
+ * then round the loop again to fill it in.
+ */
+static struct mtd_erase_region_info *calc_erase_regions(
+ size_t erase_size, size_t total_size, int *regions)
+{
+ struct mtd_erase_region_info *info = NULL;
+
+ DEBUG(2, "calc_erase_regions, es = %zd size = %zd regions = %d\n",
+ erase_size, total_size, *regions);
+ /* Make any user specified erasesize be a power of 2
+ and at least PAGE_SIZE */
+ if(erase_size) {
+ int es = erase_size;
+ erase_size = 1;
+ while(es != 1) {
+ es >>= 1;
+ erase_size <<= 1;
+ }
+ if(erase_size < PAGE_SIZE)
+ erase_size = PAGE_SIZE;
+ } else {
+ erase_size = CONFIG_MTD_BLKDEV_ERASESIZE;
+ }
+
+ *regions = 0;
+
+ do {
+ int tot_size = total_size;
+ int er_size = erase_size;
+ int count = 0, offset = 0, regcnt = 0;
+
+ while(tot_size) {
+ count = tot_size / er_size;
+ if(count) {
+ tot_size = tot_size % er_size;
+ if(info) {
+ DEBUG(2, "adding to erase info off=%d er=%d cnt=%d\n",
+ offset, er_size, count);
+ (info+regcnt)->offset = offset;
+ (info+regcnt)->erasesize = er_size;
+ (info+regcnt)->numblocks = count;
+ (*regions)++;
+ }
+ regcnt++;
+ offset += (count * er_size);
+ }
+ while(er_size > tot_size)
+ er_size >>= 1;
+ }
+ if(info == NULL) {
+ info = kmalloc(regcnt * sizeof(struct mtd_erase_region_info), GFP_KERNEL);
+ if(!info)
+ break;
+ }
+ } while(!(*regions));
+ DEBUG(2, "calc_erase_regions done, es = %zd size = %zd regions = %d\n",
+ erase_size, total_size, *regions);
+ return info;
+}
+
+
+extern dev_t __init name_to_dev_t(const char *line);
+
+static struct blkmtd_dev *add_device(char *devname, int readonly, int erase_size)
+{
+ struct block_device *bdev;
+ int mode;
+ struct blkmtd_dev *dev;
+
+ if(!devname)
+ return NULL;
+
+ /* Get a handle on the device */
+
+
+#ifdef MODULE
+ mode = (readonly) ? O_RDONLY : O_RDWR;
+ bdev = open_bdev_excl(devname, mode, NULL);
+#else
+ mode = (readonly) ? FMODE_READ : FMODE_WRITE;
+ bdev = open_by_devnum(name_to_dev_t(devname), mode);
+#endif
+ if(IS_ERR(bdev)) {
+ err("error: cannot open device %s", devname);
+ DEBUG(2, "blkmtd: opening bdev returned %ld\n", PTR_ERR(bdev));
+ return NULL;
+ }
+
+ DEBUG(1, "blkmtd: found a block device major = %d, minor = %d\n",
+ MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev));
+
+ if(MAJOR(bdev->bd_dev) == MTD_BLOCK_MAJOR) {
+ err("attempting to use an MTD device as a block device");
+ blkdev_put(bdev);
+ return NULL;
+ }
+
+ dev = kmalloc(sizeof(struct blkmtd_dev), GFP_KERNEL);
+ if(dev == NULL) {
+ blkdev_put(bdev);
+ return NULL;
+ }
+
+ memset(dev, 0, sizeof(struct blkmtd_dev));
+ dev->blkdev = bdev;
+ if(!readonly) {
+ init_MUTEX(&dev->wrbuf_mutex);
+ }
+
+ dev->mtd_info.size = dev->blkdev->bd_inode->i_size & PAGE_MASK;
+
+ /* Setup the MTD structure */
+ /* make the name contain the block device in */
+ dev->mtd_info.name = kmalloc(sizeof("blkmtd: ") + strlen(devname), GFP_KERNEL);
+ if(dev->mtd_info.name == NULL)
+ goto devinit_err;
+
+ sprintf(dev->mtd_info.name, "blkmtd: %s", devname);
+ dev->mtd_info.eraseregions = calc_erase_regions(erase_size, dev->mtd_info.size,
+ &dev->mtd_info.numeraseregions);
+ if(dev->mtd_info.eraseregions == NULL)
+ goto devinit_err;
+
+ dev->mtd_info.erasesize = dev->mtd_info.eraseregions->erasesize;
+ DEBUG(1, "blkmtd: init: found %d erase regions\n",
+ dev->mtd_info.numeraseregions);
+
+ if(readonly) {
+ dev->mtd_info.type = MTD_ROM;
+ dev->mtd_info.flags = MTD_CAP_ROM;
+ } else {
+ dev->mtd_info.type = MTD_RAM;
+ dev->mtd_info.flags = MTD_CAP_RAM;
+ dev->mtd_info.erase = blkmtd_erase;
+ dev->mtd_info.write = blkmtd_write;
+ dev->mtd_info.writev = default_mtd_writev;
+ dev->mtd_info.sync = blkmtd_sync;
+ }
+ dev->mtd_info.read = blkmtd_read;
+ dev->mtd_info.readv = default_mtd_readv;
+ dev->mtd_info.priv = dev;
+ dev->mtd_info.owner = THIS_MODULE;
+
+ list_add(&dev->list, &blkmtd_device_list);
+ if (add_mtd_device(&dev->mtd_info)) {
+ /* Device didnt get added, so free the entry */
+ list_del(&dev->list);
+ goto devinit_err;
+ } else {
+ info("mtd%d: [%s] erase_size = %dKiB %s",
+ dev->mtd_info.index, dev->mtd_info.name + strlen("blkmtd: "),
+ dev->mtd_info.erasesize >> 10,
+ readonly ? "(read-only)" : "");
+ }
+
+ return dev;
+
+ devinit_err:
+ free_device(dev);
+ return NULL;
+}
+
+
+/* Cleanup and exit - sync the device and kill of the kernel thread */
+static void __devexit cleanup_blkmtd(void)
+{
+ struct list_head *temp1, *temp2;
+
+ /* Remove the MTD devices */
+ list_for_each_safe(temp1, temp2, &blkmtd_device_list) {
+ struct blkmtd_dev *dev = list_entry(temp1, struct blkmtd_dev,
+ list);
+ blkmtd_sync(&dev->mtd_info);
+ del_mtd_device(&dev->mtd_info);
+ info("mtd%d: [%s] removed", dev->mtd_info.index,
+ dev->mtd_info.name + strlen("blkmtd: "));
+ list_del(&dev->list);
+ free_device(dev);
+ }
+}
+
+#ifndef MODULE
+
+/* Handle kernel boot params */
+
+
+static int __init param_blkmtd_device(char *str)
+{
+ int i;
+
+ for(i = 0; i < MAX_DEVICES; i++) {
+ device[i] = str;
+ DEBUG(2, "blkmtd: device setup: %d = %s\n", i, device[i]);
+ strsep(&str, ",");
+ }
+ return 1;
+}
+
+
+static int __init param_blkmtd_erasesz(char *str)
+{
+ int i;
+ for(i = 0; i < MAX_DEVICES; i++) {
+ char *val = strsep(&str, ",");
+ if(val)
+ erasesz[i] = simple_strtoul(val, NULL, 0);
+ DEBUG(2, "blkmtd: erasesz setup: %d = %d\n", i, erasesz[i]);
+ }
+
+ return 1;
+}
+
+
+static int __init param_blkmtd_ro(char *str)
+{
+ int i;
+ for(i = 0; i < MAX_DEVICES; i++) {
+ char *val = strsep(&str, ",");
+ if(val)
+ ro[i] = simple_strtoul(val, NULL, 0);
+ DEBUG(2, "blkmtd: ro setup: %d = %d\n", i, ro[i]);
+ }
+
+ return 1;
+}
+
+
+static int __init param_blkmtd_sync(char *str)
+{
+ if(str[0] == '1')
+ sync = 1;
+ return 1;
+}
+
+__setup("blkmtd_device=", param_blkmtd_device);
+__setup("blkmtd_erasesz=", param_blkmtd_erasesz);
+__setup("blkmtd_ro=", param_blkmtd_ro);
+__setup("blkmtd_sync=", param_blkmtd_sync);
+
+#endif
+
+
+/* Startup */
+static int __init init_blkmtd(void)
+{
+ int i;
+
+ info("version " VERSION);
+ /* Check args - device[0] is the bare minimum*/
+ if(!device[0]) {
+ err("error: missing `device' name\n");
+ return -EINVAL;
+ }
+
+ for(i = 0; i < MAX_DEVICES; i++)
+ add_device(device[i], ro[i], erasesz[i] << 10);
+
+ if(list_empty(&blkmtd_device_list))
+ return -EINVAL;
+
+ return 0;
+}
+
+module_init(init_blkmtd);
+module_exit(cleanup_blkmtd);
diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c
new file mode 100644
index 000000000000..cfe6ccf07972
--- /dev/null
+++ b/drivers/mtd/devices/block2mtd.c
@@ -0,0 +1,495 @@
+/*
+ * $Id: block2mtd.c,v 1.23 2005/01/05 17:05:46 dwmw2 Exp $
+ *
+ * block2mtd.c - create an mtd from a block device
+ *
+ * Copyright (C) 2001,2002 Simon Evans <spse@secret.org.uk>
+ * Copyright (C) 2004 Gareth Bult <Gareth@Encryptec.net>
+ * Copyright (C) 2004,2005 Jrn Engel <joern@wh.fh-wedel.de>
+ *
+ * Licence: GPL
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/blkdev.h>
+#include <linux/bio.h>
+#include <linux/pagemap.h>
+#include <linux/list.h>
+#include <linux/init.h>
+#include <linux/mtd/mtd.h>
+#include <linux/buffer_head.h>
+
+#define VERSION "$Revision: 1.23 $"
+
+
+#define ERROR(fmt, args...) printk(KERN_ERR "block2mtd: " fmt "\n" , ## args)
+#define INFO(fmt, args...) printk(KERN_INFO "block2mtd: " fmt "\n" , ## args)
+
+
+/* Info for the block device */
+struct block2mtd_dev {
+ struct list_head list;
+ struct block_device *blkdev;
+ struct mtd_info mtd;
+ struct semaphore write_mutex;
+};
+
+
+/* Static info about the MTD, used in cleanup_module */
+static LIST_HEAD(blkmtd_device_list);
+
+
+#define PAGE_READAHEAD 64
+void cache_readahead(struct address_space *mapping, int index)
+{
+ filler_t *filler = (filler_t*)mapping->a_ops->readpage;
+ int i, pagei;
+ unsigned ret = 0;
+ unsigned long end_index;
+ struct page *page;
+ LIST_HEAD(page_pool);
+ struct inode *inode = mapping->host;
+ loff_t isize = i_size_read(inode);
+
+ if (!isize) {
+ INFO("iSize=0 in cache_readahead\n");
+ return;
+ }
+
+ end_index = ((isize - 1) >> PAGE_CACHE_SHIFT);
+
+ read_lock_irq(&mapping->tree_lock);
+ for (i = 0; i < PAGE_READAHEAD; i++) {
+ pagei = index + i;
+ if (pagei > end_index) {
+ INFO("Overrun end of disk in cache readahead\n");
+ break;
+ }
+ page = radix_tree_lookup(&mapping->page_tree, pagei);
+ if (page && (!i))
+ break;
+ if (page)
+ continue;
+ read_unlock_irq(&mapping->tree_lock);
+ page = page_cache_alloc_cold(mapping);
+ read_lock_irq(&mapping->tree_lock);
+ if (!page)
+ break;
+ page->index = pagei;
+ list_add(&page->lru, &page_pool);
+ ret++;
+ }
+ read_unlock_irq(&mapping->tree_lock);
+ if (ret)
+ read_cache_pages(mapping, &page_pool, filler, NULL);
+}
+
+
+static struct page* page_readahead(struct address_space *mapping, int index)
+{
+ filler_t *filler = (filler_t*)mapping->a_ops->readpage;
+ //do_page_cache_readahead(mapping, index, XXX, 64);
+ cache_readahead(mapping, index);
+ return read_cache_page(mapping, index, filler, NULL);
+}
+
+
+/* erase a specified part of the device */
+static int _block2mtd_erase(struct block2mtd_dev *dev, loff_t to, size_t len)
+{
+ struct address_space *mapping = dev->blkdev->bd_inode->i_mapping;
+ struct page *page;
+ int index = to >> PAGE_SHIFT; // page index
+ int pages = len >> PAGE_SHIFT;
+ u_long *p;
+ u_long *max;
+
+ while (pages) {
+ page = page_readahead(mapping, index);
+ if (!page)
+ return -ENOMEM;
+ if (IS_ERR(page))
+ return PTR_ERR(page);
+
+ max = (u_long*)page_address(page) + PAGE_SIZE;
+ for (p=(u_long*)page_address(page); p<max; p++)
+ if (*p != -1UL) {
+ lock_page(page);
+ memset(page_address(page), 0xff, PAGE_SIZE);
+ set_page_dirty(page);
+ unlock_page(page);
+ break;
+ }
+
+ page_cache_release(page);
+ pages--;
+ index++;
+ }
+ return 0;
+}
+static int block2mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct block2mtd_dev *dev = mtd->priv;
+ size_t from = instr->addr;
+ size_t len = instr->len;
+ int err;
+
+ instr->state = MTD_ERASING;
+ down(&dev->write_mutex);
+ err = _block2mtd_erase(dev, from, len);
+ up(&dev->write_mutex);
+ if (err) {
+ ERROR("erase failed err = %d", err);
+ instr->state = MTD_ERASE_FAILED;
+ } else
+ instr->state = MTD_ERASE_DONE;
+
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+ return err;
+}
+
+
+static int block2mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct block2mtd_dev *dev = mtd->priv;
+ struct page *page;
+ int index = from >> PAGE_SHIFT;
+ int offset = from & (PAGE_SHIFT-1);
+ int cpylen;
+
+ if (from > mtd->size)
+ return -EINVAL;
+ if (from + len > mtd->size)
+ len = mtd->size - from;
+
+ if (retlen)
+ *retlen = 0;
+
+ while (len) {
+ if ((offset + len) > PAGE_SIZE)
+ cpylen = PAGE_SIZE - offset; // multiple pages
+ else
+ cpylen = len; // this page
+ len = len - cpylen;
+
+ // Get page
+ page = page_readahead(dev->blkdev->bd_inode->i_mapping, index);
+ if (!page)
+ return -ENOMEM;
+ if (IS_ERR(page))
+ return PTR_ERR(page);
+
+ memcpy(buf, page_address(page) + offset, cpylen);
+ page_cache_release(page);
+
+ if (retlen)
+ *retlen += cpylen;
+ buf += cpylen;
+ offset = 0;
+ index++;
+ }
+ return 0;
+}
+
+
+/* write data to the underlying device */
+static int _block2mtd_write(struct block2mtd_dev *dev, const u_char *buf,
+ loff_t to, size_t len, size_t *retlen)
+{
+ struct page *page;
+ struct address_space *mapping = dev->blkdev->bd_inode->i_mapping;
+ int index = to >> PAGE_SHIFT; // page index
+ int offset = to & ~PAGE_MASK; // page offset
+ int cpylen;
+
+ if (retlen)
+ *retlen = 0;
+ while (len) {
+ if ((offset+len) > PAGE_SIZE)
+ cpylen = PAGE_SIZE - offset; // multiple pages
+ else
+ cpylen = len; // this page
+ len = len - cpylen;
+
+ // Get page
+ page = page_readahead(mapping, index);
+ if (!page)
+ return -ENOMEM;
+ if (IS_ERR(page))
+ return PTR_ERR(page);
+
+ if (memcmp(page_address(page)+offset, buf, cpylen)) {
+ lock_page(page);
+ memcpy(page_address(page) + offset, buf, cpylen);
+ set_page_dirty(page);
+ unlock_page(page);
+ }
+ page_cache_release(page);
+
+ if (retlen)
+ *retlen += cpylen;
+
+ buf += cpylen;
+ offset = 0;
+ index++;
+ }
+ return 0;
+}
+static int block2mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct block2mtd_dev *dev = mtd->priv;
+ int err;
+
+ if (!len)
+ return 0;
+ if (to >= mtd->size)
+ return -ENOSPC;
+ if (to + len > mtd->size)
+ len = mtd->size - to;
+
+ down(&dev->write_mutex);
+ err = _block2mtd_write(dev, buf, to, len, retlen);
+ up(&dev->write_mutex);
+ if (err > 0)
+ err = 0;
+ return err;
+}
+
+
+/* sync the device - wait until the write queue is empty */
+static void block2mtd_sync(struct mtd_info *mtd)
+{
+ struct block2mtd_dev *dev = mtd->priv;
+ sync_blockdev(dev->blkdev);
+ return;
+}
+
+
+static void block2mtd_free_device(struct block2mtd_dev *dev)
+{
+ if (!dev)
+ return;
+
+ kfree(dev->mtd.name);
+
+ if (dev->blkdev) {
+ invalidate_inode_pages(dev->blkdev->bd_inode->i_mapping);
+ close_bdev_excl(dev->blkdev);
+ }
+
+ kfree(dev);
+}
+
+
+/* FIXME: ensure that mtd->size % erase_size == 0 */
+static struct block2mtd_dev *add_device(char *devname, int erase_size)
+{
+ struct block_device *bdev;
+ struct block2mtd_dev *dev;
+
+ if (!devname)
+ return NULL;
+
+ dev = kmalloc(sizeof(struct block2mtd_dev), GFP_KERNEL);
+ if (!dev)
+ return NULL;
+ memset(dev, 0, sizeof(*dev));
+
+ /* Get a handle on the device */
+ bdev = open_bdev_excl(devname, O_RDWR, NULL);
+ if (IS_ERR(bdev)) {
+ ERROR("error: cannot open device %s", devname);
+ goto devinit_err;
+ }
+ dev->blkdev = bdev;
+
+ if (MAJOR(bdev->bd_dev) == MTD_BLOCK_MAJOR) {
+ ERROR("attempting to use an MTD device as a block device");
+ goto devinit_err;
+ }
+
+ init_MUTEX(&dev->write_mutex);
+
+ /* Setup the MTD structure */
+ /* make the name contain the block device in */
+ dev->mtd.name = kmalloc(sizeof("block2mtd: ") + strlen(devname),
+ GFP_KERNEL);
+ if (!dev->mtd.name)
+ goto devinit_err;
+
+ sprintf(dev->mtd.name, "block2mtd: %s", devname);
+
+ dev->mtd.size = dev->blkdev->bd_inode->i_size & PAGE_MASK;
+ dev->mtd.erasesize = erase_size;
+ dev->mtd.type = MTD_RAM;
+ dev->mtd.flags = MTD_CAP_RAM;
+ dev->mtd.erase = block2mtd_erase;
+ dev->mtd.write = block2mtd_write;
+ dev->mtd.writev = default_mtd_writev;
+ dev->mtd.sync = block2mtd_sync;
+ dev->mtd.read = block2mtd_read;
+ dev->mtd.readv = default_mtd_readv;
+ dev->mtd.priv = dev;
+ dev->mtd.owner = THIS_MODULE;
+
+ if (add_mtd_device(&dev->mtd)) {
+ /* Device didnt get added, so free the entry */
+ goto devinit_err;
+ }
+ list_add(&dev->list, &blkmtd_device_list);
+ INFO("mtd%d: [%s] erase_size = %dKiB [%d]", dev->mtd.index,
+ dev->mtd.name + strlen("blkmtd: "),
+ dev->mtd.erasesize >> 10, dev->mtd.erasesize);
+ return dev;
+
+devinit_err:
+ block2mtd_free_device(dev);
+ return NULL;
+}
+
+
+static int ustrtoul(const char *cp, char **endp, unsigned int base)
+{
+ unsigned long result = simple_strtoul(cp, endp, base);
+ switch (**endp) {
+ case 'G' :
+ result *= 1024;
+ case 'M':
+ result *= 1024;
+ case 'k':
+ result *= 1024;
+ /* By dwmw2 editorial decree, "ki", "Mi" or "Gi" are to be used. */
+ if ((*endp)[1] == 'i')
+ (*endp) += 2;
+ }
+ return result;
+}
+
+
+static int parse_num32(u32 *num32, const char *token)
+{
+ char *endp;
+ unsigned long n;
+
+ n = ustrtoul(token, &endp, 0);
+ if (*endp)
+ return -EINVAL;
+
+ *num32 = n;
+ return 0;
+}
+
+
+static int parse_name(char **pname, const char *token, size_t limit)
+{
+ size_t len;
+ char *name;
+
+ len = strlen(token) + 1;
+ if (len > limit)
+ return -ENOSPC;
+
+ name = kmalloc(len, GFP_KERNEL);
+ if (!name)
+ return -ENOMEM;
+
+ strcpy(name, token);
+
+ *pname = name;
+ return 0;
+}
+
+
+static inline void kill_final_newline(char *str)
+{
+ char *newline = strrchr(str, '\n');
+ if (newline && !newline[1])
+ *newline = 0;
+}
+
+
+#define parse_err(fmt, args...) do { \
+ ERROR("block2mtd: " fmt "\n", ## args); \
+ return 0; \
+} while (0)
+
+static int block2mtd_setup(const char *val, struct kernel_param *kp)
+{
+ char buf[80+12], *str=buf; /* 80 for device, 12 for erase size */
+ char *token[2];
+ char *name;
+ u32 erase_size = PAGE_SIZE;
+ int i, ret;
+
+ if (strnlen(val, sizeof(buf)) >= sizeof(buf))
+ parse_err("parameter too long");
+
+ strcpy(str, val);
+ kill_final_newline(str);
+
+ for (i=0; i<2; i++)
+ token[i] = strsep(&str, ",");
+
+ if (str)
+ parse_err("too many arguments");
+
+ if (!token[0])
+ parse_err("no argument");
+
+ ret = parse_name(&name, token[0], 80);
+ if (ret == -ENOMEM)
+ parse_err("out of memory");
+ if (ret == -ENOSPC)
+ parse_err("name too long");
+ if (ret)
+ return 0;
+
+ if (token[1]) {
+ ret = parse_num32(&erase_size, token[1]);
+ if (ret)
+ parse_err("illegal erase size");
+ }
+
+ add_device(name, erase_size);
+
+ return 0;
+}
+
+
+module_param_call(block2mtd, block2mtd_setup, NULL, NULL, 0200);
+MODULE_PARM_DESC(block2mtd, "Device to use. \"block2mtd=<dev>[,<erasesize>]\"");
+
+static int __init block2mtd_init(void)
+{
+ INFO("version " VERSION);
+ return 0;
+}
+
+
+static void __devexit block2mtd_exit(void)
+{
+ struct list_head *pos, *next;
+
+ /* Remove the MTD devices */
+ list_for_each_safe(pos, next, &blkmtd_device_list) {
+ struct block2mtd_dev *dev = list_entry(pos, typeof(*dev), list);
+ block2mtd_sync(&dev->mtd);
+ del_mtd_device(&dev->mtd);
+ INFO("mtd%d: [%s] removed", dev->mtd.index,
+ dev->mtd.name + strlen("blkmtd: "));
+ list_del(&dev->list);
+ block2mtd_free_device(dev);
+ }
+}
+
+
+module_init(block2mtd_init);
+module_exit(block2mtd_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Simon Evans <spse@secret.org.uk> and others");
+MODULE_DESCRIPTION("Emulate an MTD using a block device");
diff --git a/drivers/mtd/devices/doc2000.c b/drivers/mtd/devices/doc2000.c
new file mode 100644
index 000000000000..5fc532895a24
--- /dev/null
+++ b/drivers/mtd/devices/doc2000.c
@@ -0,0 +1,1309 @@
+
+/*
+ * Linux driver for Disk-On-Chip 2000 and Millennium
+ * (c) 1999 Machine Vision Holdings, Inc.
+ * (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org>
+ *
+ * $Id: doc2000.c,v 1.66 2005/01/05 18:05:12 dwmw2 Exp $
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/doc2000.h>
+
+#define DOC_SUPPORT_2000
+#define DOC_SUPPORT_2000TSOP
+#define DOC_SUPPORT_MILLENNIUM
+
+#ifdef DOC_SUPPORT_2000
+#define DoC_is_2000(doc) (doc->ChipID == DOC_ChipID_Doc2k)
+#else
+#define DoC_is_2000(doc) (0)
+#endif
+
+#if defined(DOC_SUPPORT_2000TSOP) || defined(DOC_SUPPORT_MILLENNIUM)
+#define DoC_is_Millennium(doc) (doc->ChipID == DOC_ChipID_DocMil)
+#else
+#define DoC_is_Millennium(doc) (0)
+#endif
+
+/* #define ECC_DEBUG */
+
+/* I have no idea why some DoC chips can not use memcpy_from|to_io().
+ * This may be due to the different revisions of the ASIC controller built-in or
+ * simplily a QA/Bug issue. Who knows ?? If you have trouble, please uncomment
+ * this:
+ #undef USE_MEMCPY
+*/
+
+static int doc_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf);
+static int doc_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf);
+static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel);
+static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel);
+static int doc_writev_ecc(struct mtd_info *mtd, const struct kvec *vecs,
+ unsigned long count, loff_t to, size_t *retlen,
+ u_char *eccbuf, struct nand_oobinfo *oobsel);
+static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t *retlen, u_char *buf);
+static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t *retlen, const u_char *buf);
+static int doc_write_oob_nolock(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t *retlen, const u_char *buf);
+static int doc_erase (struct mtd_info *mtd, struct erase_info *instr);
+
+static struct mtd_info *doc2klist = NULL;
+
+/* Perform the required delay cycles by reading from the appropriate register */
+static void DoC_Delay(struct DiskOnChip *doc, unsigned short cycles)
+{
+ volatile char dummy;
+ int i;
+
+ for (i = 0; i < cycles; i++) {
+ if (DoC_is_Millennium(doc))
+ dummy = ReadDOC(doc->virtadr, NOP);
+ else
+ dummy = ReadDOC(doc->virtadr, DOCStatus);
+ }
+
+}
+
+/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */
+static int _DoC_WaitReady(struct DiskOnChip *doc)
+{
+ void __iomem *docptr = doc->virtadr;
+ unsigned long timeo = jiffies + (HZ * 10);
+
+ DEBUG(MTD_DEBUG_LEVEL3,
+ "_DoC_WaitReady called for out-of-line wait\n");
+
+ /* Out-of-line routine to wait for chip response */
+ while (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) {
+ /* issue 2 read from NOP register after reading from CDSNControl register
+ see Software Requirement 11.4 item 2. */
+ DoC_Delay(doc, 2);
+
+ if (time_after(jiffies, timeo)) {
+ DEBUG(MTD_DEBUG_LEVEL2, "_DoC_WaitReady timed out.\n");
+ return -EIO;
+ }
+ udelay(1);
+ cond_resched();
+ }
+
+ return 0;
+}
+
+static inline int DoC_WaitReady(struct DiskOnChip *doc)
+{
+ void __iomem *docptr = doc->virtadr;
+
+ /* This is inline, to optimise the common case, where it's ready instantly */
+ int ret = 0;
+
+ /* 4 read form NOP register should be issued in prior to the read from CDSNControl
+ see Software Requirement 11.4 item 2. */
+ DoC_Delay(doc, 4);
+
+ if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B))
+ /* Call the out-of-line routine to wait */
+ ret = _DoC_WaitReady(doc);
+
+ /* issue 2 read from NOP register after reading from CDSNControl register
+ see Software Requirement 11.4 item 2. */
+ DoC_Delay(doc, 2);
+
+ return ret;
+}
+
+/* DoC_Command: Send a flash command to the flash chip through the CDSN Slow IO register to
+ bypass the internal pipeline. Each of 4 delay cycles (read from the NOP register) is
+ required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */
+
+static inline int DoC_Command(struct DiskOnChip *doc, unsigned char command,
+ unsigned char xtraflags)
+{
+ void __iomem *docptr = doc->virtadr;
+
+ if (DoC_is_2000(doc))
+ xtraflags |= CDSN_CTRL_FLASH_IO;
+
+ /* Assert the CLE (Command Latch Enable) line to the flash chip */
+ WriteDOC(xtraflags | CDSN_CTRL_CLE | CDSN_CTRL_CE, docptr, CDSNControl);
+ DoC_Delay(doc, 4); /* Software requirement 11.4.3 for Millennium */
+
+ if (DoC_is_Millennium(doc))
+ WriteDOC(command, docptr, CDSNSlowIO);
+
+ /* Send the command */
+ WriteDOC_(command, docptr, doc->ioreg);
+ if (DoC_is_Millennium(doc))
+ WriteDOC(command, docptr, WritePipeTerm);
+
+ /* Lower the CLE line */
+ WriteDOC(xtraflags | CDSN_CTRL_CE, docptr, CDSNControl);
+ DoC_Delay(doc, 4); /* Software requirement 11.4.3 for Millennium */
+
+ /* Wait for the chip to respond - Software requirement 11.4.1 (extended for any command) */
+ return DoC_WaitReady(doc);
+}
+
+/* DoC_Address: Set the current address for the flash chip through the CDSN Slow IO register to
+ bypass the internal pipeline. Each of 4 delay cycles (read from the NOP register) is
+ required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */
+
+static int DoC_Address(struct DiskOnChip *doc, int numbytes, unsigned long ofs,
+ unsigned char xtraflags1, unsigned char xtraflags2)
+{
+ int i;
+ void __iomem *docptr = doc->virtadr;
+
+ if (DoC_is_2000(doc))
+ xtraflags1 |= CDSN_CTRL_FLASH_IO;
+
+ /* Assert the ALE (Address Latch Enable) line to the flash chip */
+ WriteDOC(xtraflags1 | CDSN_CTRL_ALE | CDSN_CTRL_CE, docptr, CDSNControl);
+
+ DoC_Delay(doc, 4); /* Software requirement 11.4.3 for Millennium */
+
+ /* Send the address */
+ /* Devices with 256-byte page are addressed as:
+ Column (bits 0-7), Page (bits 8-15, 16-23, 24-31)
+ * there is no device on the market with page256
+ and more than 24 bits.
+ Devices with 512-byte page are addressed as:
+ Column (bits 0-7), Page (bits 9-16, 17-24, 25-31)
+ * 25-31 is sent only if the chip support it.
+ * bit 8 changes the read command to be sent
+ (NAND_CMD_READ0 or NAND_CMD_READ1).
+ */
+
+ if (numbytes == ADDR_COLUMN || numbytes == ADDR_COLUMN_PAGE) {
+ if (DoC_is_Millennium(doc))
+ WriteDOC(ofs & 0xff, docptr, CDSNSlowIO);
+ WriteDOC_(ofs & 0xff, docptr, doc->ioreg);
+ }
+
+ if (doc->page256) {
+ ofs = ofs >> 8;
+ } else {
+ ofs = ofs >> 9;
+ }
+
+ if (numbytes == ADDR_PAGE || numbytes == ADDR_COLUMN_PAGE) {
+ for (i = 0; i < doc->pageadrlen; i++, ofs = ofs >> 8) {
+ if (DoC_is_Millennium(doc))
+ WriteDOC(ofs & 0xff, docptr, CDSNSlowIO);
+ WriteDOC_(ofs & 0xff, docptr, doc->ioreg);
+ }
+ }
+
+ if (DoC_is_Millennium(doc))
+ WriteDOC(ofs & 0xff, docptr, WritePipeTerm);
+
+ DoC_Delay(doc, 2); /* Needed for some slow flash chips. mf. */
+
+ /* FIXME: The SlowIO's for millennium could be replaced by
+ a single WritePipeTerm here. mf. */
+
+ /* Lower the ALE line */
+ WriteDOC(xtraflags1 | xtraflags2 | CDSN_CTRL_CE, docptr,
+ CDSNControl);
+
+ DoC_Delay(doc, 4); /* Software requirement 11.4.3 for Millennium */
+
+ /* Wait for the chip to respond - Software requirement 11.4.1 */
+ return DoC_WaitReady(doc);
+}
+
+/* Read a buffer from DoC, taking care of Millennium odditys */
+static void DoC_ReadBuf(struct DiskOnChip *doc, u_char * buf, int len)
+{
+ volatile int dummy;
+ int modulus = 0xffff;
+ void __iomem *docptr = doc->virtadr;
+ int i;
+
+ if (len <= 0)
+ return;
+
+ if (DoC_is_Millennium(doc)) {
+ /* Read the data via the internal pipeline through CDSN IO register,
+ see Pipelined Read Operations 11.3 */
+ dummy = ReadDOC(docptr, ReadPipeInit);
+
+ /* Millennium should use the LastDataRead register - Pipeline Reads */
+ len--;
+
+ /* This is needed for correctly ECC calculation */
+ modulus = 0xff;
+ }
+
+ for (i = 0; i < len; i++)
+ buf[i] = ReadDOC_(docptr, doc->ioreg + (i & modulus));
+
+ if (DoC_is_Millennium(doc)) {
+ buf[i] = ReadDOC(docptr, LastDataRead);
+ }
+}
+
+/* Write a buffer to DoC, taking care of Millennium odditys */
+static void DoC_WriteBuf(struct DiskOnChip *doc, const u_char * buf, int len)
+{
+ void __iomem *docptr = doc->virtadr;
+ int i;
+
+ if (len <= 0)
+ return;
+
+ for (i = 0; i < len; i++)
+ WriteDOC_(buf[i], docptr, doc->ioreg + i);
+
+ if (DoC_is_Millennium(doc)) {
+ WriteDOC(0x00, docptr, WritePipeTerm);
+ }
+}
+
+
+/* DoC_SelectChip: Select a given flash chip within the current floor */
+
+static inline int DoC_SelectChip(struct DiskOnChip *doc, int chip)
+{
+ void __iomem *docptr = doc->virtadr;
+
+ /* Software requirement 11.4.4 before writing DeviceSelect */
+ /* Deassert the CE line to eliminate glitches on the FCE# outputs */
+ WriteDOC(CDSN_CTRL_WP, docptr, CDSNControl);
+ DoC_Delay(doc, 4); /* Software requirement 11.4.3 for Millennium */
+
+ /* Select the individual flash chip requested */
+ WriteDOC(chip, docptr, CDSNDeviceSelect);
+ DoC_Delay(doc, 4);
+
+ /* Reassert the CE line */
+ WriteDOC(CDSN_CTRL_CE | CDSN_CTRL_FLASH_IO | CDSN_CTRL_WP, docptr,
+ CDSNControl);
+ DoC_Delay(doc, 4); /* Software requirement 11.4.3 for Millennium */
+
+ /* Wait for it to be ready */
+ return DoC_WaitReady(doc);
+}
+
+/* DoC_SelectFloor: Select a given floor (bank of flash chips) */
+
+static inline int DoC_SelectFloor(struct DiskOnChip *doc, int floor)
+{
+ void __iomem *docptr = doc->virtadr;
+
+ /* Select the floor (bank) of chips required */
+ WriteDOC(floor, docptr, FloorSelect);
+
+ /* Wait for the chip to be ready */
+ return DoC_WaitReady(doc);
+}
+
+/* DoC_IdentChip: Identify a given NAND chip given {floor,chip} */
+
+static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip)
+{
+ int mfr, id, i, j;
+ volatile char dummy;
+
+ /* Page in the required floor/chip */
+ DoC_SelectFloor(doc, floor);
+ DoC_SelectChip(doc, chip);
+
+ /* Reset the chip */
+ if (DoC_Command(doc, NAND_CMD_RESET, CDSN_CTRL_WP)) {
+ DEBUG(MTD_DEBUG_LEVEL2,
+ "DoC_Command (reset) for %d,%d returned true\n",
+ floor, chip);
+ return 0;
+ }
+
+
+ /* Read the NAND chip ID: 1. Send ReadID command */
+ if (DoC_Command(doc, NAND_CMD_READID, CDSN_CTRL_WP)) {
+ DEBUG(MTD_DEBUG_LEVEL2,
+ "DoC_Command (ReadID) for %d,%d returned true\n",
+ floor, chip);
+ return 0;
+ }
+
+ /* Read the NAND chip ID: 2. Send address byte zero */
+ DoC_Address(doc, ADDR_COLUMN, 0, CDSN_CTRL_WP, 0);
+
+ /* Read the manufacturer and device id codes from the device */
+
+ if (DoC_is_Millennium(doc)) {
+ DoC_Delay(doc, 2);
+ dummy = ReadDOC(doc->virtadr, ReadPipeInit);
+ mfr = ReadDOC(doc->virtadr, LastDataRead);
+
+ DoC_Delay(doc, 2);
+ dummy = ReadDOC(doc->virtadr, ReadPipeInit);
+ id = ReadDOC(doc->virtadr, LastDataRead);
+ } else {
+ /* CDSN Slow IO register see Software Req 11.4 item 5. */
+ dummy = ReadDOC(doc->virtadr, CDSNSlowIO);
+ DoC_Delay(doc, 2);
+ mfr = ReadDOC_(doc->virtadr, doc->ioreg);
+
+ /* CDSN Slow IO register see Software Req 11.4 item 5. */
+ dummy = ReadDOC(doc->virtadr, CDSNSlowIO);
+ DoC_Delay(doc, 2);
+ id = ReadDOC_(doc->virtadr, doc->ioreg);
+ }
+
+ /* No response - return failure */
+ if (mfr == 0xff || mfr == 0)
+ return 0;
+
+ /* Check it's the same as the first chip we identified.
+ * M-Systems say that any given DiskOnChip device should only
+ * contain _one_ type of flash part, although that's not a
+ * hardware restriction. */
+ if (doc->mfr) {
+ if (doc->mfr == mfr && doc->id == id)
+ return 1; /* This is another the same the first */
+ else
+ printk(KERN_WARNING
+ "Flash chip at floor %d, chip %d is different:\n",
+ floor, chip);
+ }
+
+ /* Print and store the manufacturer and ID codes. */
+ for (i = 0; nand_flash_ids[i].name != NULL; i++) {
+ if (id == nand_flash_ids[i].id) {
+ /* Try to identify manufacturer */
+ for (j = 0; nand_manuf_ids[j].id != 0x0; j++) {
+ if (nand_manuf_ids[j].id == mfr)
+ break;
+ }
+ printk(KERN_INFO
+ "Flash chip found: Manufacturer ID: %2.2X, "
+ "Chip ID: %2.2X (%s:%s)\n", mfr, id,
+ nand_manuf_ids[j].name, nand_flash_ids[i].name);
+ if (!doc->mfr) {
+ doc->mfr = mfr;
+ doc->id = id;
+ doc->chipshift =
+ ffs((nand_flash_ids[i].chipsize << 20)) - 1;
+ doc->page256 = (nand_flash_ids[i].pagesize == 256) ? 1 : 0;
+ doc->pageadrlen = doc->chipshift > 25 ? 3 : 2;
+ doc->erasesize =
+ nand_flash_ids[i].erasesize;
+ return 1;
+ }
+ return 0;
+ }
+ }
+
+
+ /* We haven't fully identified the chip. Print as much as we know. */
+ printk(KERN_WARNING "Unknown flash chip found: %2.2X %2.2X\n",
+ id, mfr);
+
+ printk(KERN_WARNING "Please report to dwmw2@infradead.org\n");
+ return 0;
+}
+
+/* DoC_ScanChips: Find all NAND chips present in a DiskOnChip, and identify them */
+
+static void DoC_ScanChips(struct DiskOnChip *this, int maxchips)
+{
+ int floor, chip;
+ int numchips[MAX_FLOORS];
+ int ret = 1;
+
+ this->numchips = 0;
+ this->mfr = 0;
+ this->id = 0;
+
+ /* For each floor, find the number of valid chips it contains */
+ for (floor = 0; floor < MAX_FLOORS; floor++) {
+ ret = 1;
+ numchips[floor] = 0;
+ for (chip = 0; chip < maxchips && ret != 0; chip++) {
+
+ ret = DoC_IdentChip(this, floor, chip);
+ if (ret) {
+ numchips[floor]++;
+ this->numchips++;
+ }
+ }
+ }
+
+ /* If there are none at all that we recognise, bail */
+ if (!this->numchips) {
+ printk(KERN_NOTICE "No flash chips recognised.\n");
+ return;
+ }
+
+ /* Allocate an array to hold the information for each chip */
+ this->chips = kmalloc(sizeof(struct Nand) * this->numchips, GFP_KERNEL);
+ if (!this->chips) {
+ printk(KERN_NOTICE "No memory for allocating chip info structures\n");
+ return;
+ }
+
+ ret = 0;
+
+ /* Fill out the chip array with {floor, chipno} for each
+ * detected chip in the device. */
+ for (floor = 0; floor < MAX_FLOORS; floor++) {
+ for (chip = 0; chip < numchips[floor]; chip++) {
+ this->chips[ret].floor = floor;
+ this->chips[ret].chip = chip;
+ this->chips[ret].curadr = 0;
+ this->chips[ret].curmode = 0x50;
+ ret++;
+ }
+ }
+
+ /* Calculate and print the total size of the device */
+ this->totlen = this->numchips * (1 << this->chipshift);
+
+ printk(KERN_INFO "%d flash chips found. Total DiskOnChip size: %ld MiB\n",
+ this->numchips, this->totlen >> 20);
+}
+
+static int DoC2k_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2)
+{
+ int tmp1, tmp2, retval;
+ if (doc1->physadr == doc2->physadr)
+ return 1;
+
+ /* Use the alias resolution register which was set aside for this
+ * purpose. If it's value is the same on both chips, they might
+ * be the same chip, and we write to one and check for a change in
+ * the other. It's unclear if this register is usuable in the
+ * DoC 2000 (it's in the Millennium docs), but it seems to work. */
+ tmp1 = ReadDOC(doc1->virtadr, AliasResolution);
+ tmp2 = ReadDOC(doc2->virtadr, AliasResolution);
+ if (tmp1 != tmp2)
+ return 0;
+
+ WriteDOC((tmp1 + 1) % 0xff, doc1->virtadr, AliasResolution);
+ tmp2 = ReadDOC(doc2->virtadr, AliasResolution);
+ if (tmp2 == (tmp1 + 1) % 0xff)
+ retval = 1;
+ else
+ retval = 0;
+
+ /* Restore register contents. May not be necessary, but do it just to
+ * be safe. */
+ WriteDOC(tmp1, doc1->virtadr, AliasResolution);
+
+ return retval;
+}
+
+static const char im_name[] = "DoC2k_init";
+
+/* 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 mtd 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.
+ */
+static void DoC2k_init(struct mtd_info *mtd)
+{
+ struct DiskOnChip *this = mtd->priv;
+ struct DiskOnChip *old = NULL;
+ int maxchips;
+
+ /* We must avoid being called twice for the same device. */
+
+ if (doc2klist)
+ old = doc2klist->priv;
+
+ while (old) {
+ if (DoC2k_is_alias(old, this)) {
+ printk(KERN_NOTICE
+ "Ignoring DiskOnChip 2000 at 0x%lX - already configured\n",
+ this->physadr);
+ iounmap(this->virtadr);
+ kfree(mtd);
+ return;
+ }
+ if (old->nextdoc)
+ old = old->nextdoc->priv;
+ else
+ old = NULL;
+ }
+
+
+ switch (this->ChipID) {
+ case DOC_ChipID_Doc2kTSOP:
+ mtd->name = "DiskOnChip 2000 TSOP";
+ this->ioreg = DoC_Mil_CDSN_IO;
+ /* Pretend it's a Millennium */
+ this->ChipID = DOC_ChipID_DocMil;
+ maxchips = MAX_CHIPS;
+ break;
+ case DOC_ChipID_Doc2k:
+ mtd->name = "DiskOnChip 2000";
+ this->ioreg = DoC_2k_CDSN_IO;
+ maxchips = MAX_CHIPS;
+ break;
+ case DOC_ChipID_DocMil:
+ mtd->name = "DiskOnChip Millennium";
+ this->ioreg = DoC_Mil_CDSN_IO;
+ maxchips = MAX_CHIPS_MIL;
+ break;
+ default:
+ printk("Unknown ChipID 0x%02x\n", this->ChipID);
+ kfree(mtd);
+ iounmap(this->virtadr);
+ return;
+ }
+
+ printk(KERN_NOTICE "%s found at address 0x%lX\n", mtd->name,
+ this->physadr);
+
+ mtd->type = MTD_NANDFLASH;
+ mtd->flags = MTD_CAP_NANDFLASH;
+ mtd->ecctype = MTD_ECC_RS_DiskOnChip;
+ mtd->size = 0;
+ mtd->erasesize = 0;
+ mtd->oobblock = 512;
+ mtd->oobsize = 16;
+ mtd->owner = THIS_MODULE;
+ mtd->erase = doc_erase;
+ mtd->point = NULL;
+ mtd->unpoint = NULL;
+ mtd->read = doc_read;
+ mtd->write = doc_write;
+ mtd->read_ecc = doc_read_ecc;
+ mtd->write_ecc = doc_write_ecc;
+ mtd->writev_ecc = doc_writev_ecc;
+ mtd->read_oob = doc_read_oob;
+ mtd->write_oob = doc_write_oob;
+ mtd->sync = NULL;
+
+ this->totlen = 0;
+ this->numchips = 0;
+
+ this->curfloor = -1;
+ this->curchip = -1;
+ init_MUTEX(&this->lock);
+
+ /* Ident all the chips present. */
+ DoC_ScanChips(this, maxchips);
+
+ if (!this->totlen) {
+ kfree(mtd);
+ iounmap(this->virtadr);
+ } else {
+ this->nextdoc = doc2klist;
+ doc2klist = mtd;
+ mtd->size = this->totlen;
+ mtd->erasesize = this->erasesize;
+ add_mtd_device(mtd);
+ return;
+ }
+}
+
+static int doc_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t * retlen, u_char * buf)
+{
+ /* Just a special case of doc_read_ecc */
+ return doc_read_ecc(mtd, from, len, retlen, buf, NULL, NULL);
+}
+
+static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t * retlen, u_char * buf, u_char * eccbuf, struct nand_oobinfo *oobsel)
+{
+ struct DiskOnChip *this = mtd->priv;
+ void __iomem *docptr = this->virtadr;
+ struct Nand *mychip;
+ unsigned char syndrome[6];
+ volatile char dummy;
+ int i, len256 = 0, ret=0;
+ size_t left = len;
+
+ /* Don't allow read past end of device */
+ if (from >= this->totlen)
+ return -EINVAL;
+
+ down(&this->lock);
+
+ *retlen = 0;
+ while (left) {
+ len = left;
+
+ /* Don't allow a single read to cross a 512-byte block boundary */
+ if (from + len > ((from | 0x1ff) + 1))
+ len = ((from | 0x1ff) + 1) - from;
+
+ /* The ECC will not be calculated correctly if less than 512 is read */
+ if (len != 0x200 && eccbuf)
+ printk(KERN_WARNING
+ "ECC needs a full sector read (adr: %lx size %lx)\n",
+ (long) from, (long) len);
+
+ /* printk("DoC_Read (adr: %lx size %lx)\n", (long) from, (long) len); */
+
+
+ /* Find the chip which is to be used and select it */
+ mychip = &this->chips[from >> (this->chipshift)];
+
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(this, mychip->floor);
+ DoC_SelectChip(this, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(this, mychip->chip);
+ }
+
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ DoC_Command(this,
+ (!this->page256
+ && (from & 0x100)) ? NAND_CMD_READ1 : NAND_CMD_READ0,
+ CDSN_CTRL_WP);
+ DoC_Address(this, ADDR_COLUMN_PAGE, from, CDSN_CTRL_WP,
+ CDSN_CTRL_ECC_IO);
+
+ if (eccbuf) {
+ /* Prime the ECC engine */
+ WriteDOC(DOC_ECC_RESET, docptr, ECCConf);
+ WriteDOC(DOC_ECC_EN, docptr, ECCConf);
+ } else {
+ /* disable the ECC engine */
+ WriteDOC(DOC_ECC_RESET, docptr, ECCConf);
+ WriteDOC(DOC_ECC_DIS, docptr, ECCConf);
+ }
+
+ /* treat crossing 256-byte sector for 2M x 8bits devices */
+ if (this->page256 && from + len > (from | 0xff) + 1) {
+ len256 = (from | 0xff) + 1 - from;
+ DoC_ReadBuf(this, buf, len256);
+
+ DoC_Command(this, NAND_CMD_READ0, CDSN_CTRL_WP);
+ DoC_Address(this, ADDR_COLUMN_PAGE, from + len256,
+ CDSN_CTRL_WP, CDSN_CTRL_ECC_IO);
+ }
+
+ DoC_ReadBuf(this, &buf[len256], len - len256);
+
+ /* Let the caller know we completed it */
+ *retlen += len;
+
+ if (eccbuf) {
+ /* Read the ECC data through the DiskOnChip ECC logic */
+ /* Note: this will work even with 2M x 8bit devices as */
+ /* they have 8 bytes of OOB per 256 page. mf. */
+ DoC_ReadBuf(this, eccbuf, 6);
+
+ /* Flush the pipeline */
+ if (DoC_is_Millennium(this)) {
+ dummy = ReadDOC(docptr, ECCConf);
+ dummy = ReadDOC(docptr, ECCConf);
+ i = ReadDOC(docptr, ECCConf);
+ } else {
+ dummy = ReadDOC(docptr, 2k_ECCStatus);
+ dummy = ReadDOC(docptr, 2k_ECCStatus);
+ i = ReadDOC(docptr, 2k_ECCStatus);
+ }
+
+ /* Check the ECC Status */
+ if (i & 0x80) {
+ int nb_errors;
+ /* There was an ECC error */
+#ifdef ECC_DEBUG
+ printk(KERN_ERR "DiskOnChip ECC Error: Read at %lx\n", (long)from);
+#endif
+ /* Read the ECC syndrom through the DiskOnChip ECC logic.
+ These syndrome will be all ZERO when there is no error */
+ for (i = 0; i < 6; i++) {
+ syndrome[i] =
+ ReadDOC(docptr, ECCSyndrome0 + i);
+ }
+ nb_errors = doc_decode_ecc(buf, syndrome);
+
+#ifdef ECC_DEBUG
+ printk(KERN_ERR "Errors corrected: %x\n", nb_errors);
+#endif
+ if (nb_errors < 0) {
+ /* We return error, but have actually done the read. Not that
+ this can be told to user-space, via sys_read(), but at least
+ MTD-aware stuff can know about it by checking *retlen */
+ ret = -EIO;
+ }
+ }
+
+#ifdef PSYCHO_DEBUG
+ printk(KERN_DEBUG "ECC DATA at %lxB: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
+ (long)from, eccbuf[0], eccbuf[1], eccbuf[2],
+ eccbuf[3], eccbuf[4], eccbuf[5]);
+#endif
+
+ /* disable the ECC engine */
+ WriteDOC(DOC_ECC_DIS, docptr , ECCConf);
+ }
+
+ /* according to 11.4.1, we need to wait for the busy line
+ * drop if we read to the end of the page. */
+ if(0 == ((from + len) & 0x1ff))
+ {
+ DoC_WaitReady(this);
+ }
+
+ from += len;
+ left -= len;
+ buf += len;
+ }
+
+ up(&this->lock);
+
+ return ret;
+}
+
+static int doc_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t * retlen, const u_char * buf)
+{
+ char eccbuf[6];
+ return doc_write_ecc(mtd, to, len, retlen, buf, eccbuf, NULL);
+}
+
+static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t * retlen, const u_char * buf,
+ u_char * eccbuf, struct nand_oobinfo *oobsel)
+{
+ struct DiskOnChip *this = mtd->priv;
+ int di; /* Yes, DI is a hangover from when I was disassembling the binary driver */
+ void __iomem *docptr = this->virtadr;
+ volatile char dummy;
+ int len256 = 0;
+ struct Nand *mychip;
+ size_t left = len;
+ int status;
+
+ /* Don't allow write past end of device */
+ if (to >= this->totlen)
+ return -EINVAL;
+
+ down(&this->lock);
+
+ *retlen = 0;
+ while (left) {
+ len = left;
+
+ /* Don't allow a single write to cross a 512-byte block boundary */
+ if (to + len > ((to | 0x1ff) + 1))
+ len = ((to | 0x1ff) + 1) - to;
+
+ /* The ECC will not be calculated correctly if less than 512 is written */
+/* DBB-
+ if (len != 0x200 && eccbuf)
+ printk(KERN_WARNING
+ "ECC needs a full sector write (adr: %lx size %lx)\n",
+ (long) to, (long) len);
+ -DBB */
+
+ /* printk("DoC_Write (adr: %lx size %lx)\n", (long) to, (long) len); */
+
+ /* Find the chip which is to be used and select it */
+ mychip = &this->chips[to >> (this->chipshift)];
+
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(this, mychip->floor);
+ DoC_SelectChip(this, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(this, mychip->chip);
+ }
+
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ /* Set device to main plane of flash */
+ DoC_Command(this, NAND_CMD_RESET, CDSN_CTRL_WP);
+ DoC_Command(this,
+ (!this->page256
+ && (to & 0x100)) ? NAND_CMD_READ1 : NAND_CMD_READ0,
+ CDSN_CTRL_WP);
+
+ DoC_Command(this, NAND_CMD_SEQIN, 0);
+ DoC_Address(this, ADDR_COLUMN_PAGE, to, 0, CDSN_CTRL_ECC_IO);
+
+ if (eccbuf) {
+ /* Prime the ECC engine */
+ WriteDOC(DOC_ECC_RESET, docptr, ECCConf);
+ WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf);
+ } else {
+ /* disable the ECC engine */
+ WriteDOC(DOC_ECC_RESET, docptr, ECCConf);
+ WriteDOC(DOC_ECC_DIS, docptr, ECCConf);
+ }
+
+ /* treat crossing 256-byte sector for 2M x 8bits devices */
+ if (this->page256 && to + len > (to | 0xff) + 1) {
+ len256 = (to | 0xff) + 1 - to;
+ DoC_WriteBuf(this, buf, len256);
+
+ DoC_Command(this, NAND_CMD_PAGEPROG, 0);
+
+ DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP);
+ /* There's an implicit DoC_WaitReady() in DoC_Command */
+
+ dummy = ReadDOC(docptr, CDSNSlowIO);
+ DoC_Delay(this, 2);
+
+ if (ReadDOC_(docptr, this->ioreg) & 1) {
+ printk(KERN_ERR "Error programming flash\n");
+ /* Error in programming */
+ *retlen = 0;
+ up(&this->lock);
+ return -EIO;
+ }
+
+ DoC_Command(this, NAND_CMD_SEQIN, 0);
+ DoC_Address(this, ADDR_COLUMN_PAGE, to + len256, 0,
+ CDSN_CTRL_ECC_IO);
+ }
+
+ DoC_WriteBuf(this, &buf[len256], len - len256);
+
+ if (eccbuf) {
+ WriteDOC(CDSN_CTRL_ECC_IO | CDSN_CTRL_CE, docptr,
+ CDSNControl);
+
+ if (DoC_is_Millennium(this)) {
+ WriteDOC(0, docptr, NOP);
+ WriteDOC(0, docptr, NOP);
+ WriteDOC(0, docptr, NOP);
+ } else {
+ WriteDOC_(0, docptr, this->ioreg);
+ WriteDOC_(0, docptr, this->ioreg);
+ WriteDOC_(0, docptr, this->ioreg);
+ }
+
+ WriteDOC(CDSN_CTRL_ECC_IO | CDSN_CTRL_FLASH_IO | CDSN_CTRL_CE, docptr,
+ CDSNControl);
+
+ /* Read the ECC data through the DiskOnChip ECC logic */
+ for (di = 0; di < 6; di++) {
+ eccbuf[di] = ReadDOC(docptr, ECCSyndrome0 + di);
+ }
+
+ /* Reset the ECC engine */
+ WriteDOC(DOC_ECC_DIS, docptr, ECCConf);
+
+#ifdef PSYCHO_DEBUG
+ printk
+ ("OOB data at %lx is %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
+ (long) to, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3],
+ eccbuf[4], eccbuf[5]);
+#endif
+ }
+
+ DoC_Command(this, NAND_CMD_PAGEPROG, 0);
+
+ DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP);
+ /* There's an implicit DoC_WaitReady() in DoC_Command */
+
+ if (DoC_is_Millennium(this)) {
+ ReadDOC(docptr, ReadPipeInit);
+ status = ReadDOC(docptr, LastDataRead);
+ } else {
+ dummy = ReadDOC(docptr, CDSNSlowIO);
+ DoC_Delay(this, 2);
+ status = ReadDOC_(docptr, this->ioreg);
+ }
+
+ if (status & 1) {
+ printk(KERN_ERR "Error programming flash\n");
+ /* Error in programming */
+ *retlen = 0;
+ up(&this->lock);
+ return -EIO;
+ }
+
+ /* Let the caller know we completed it */
+ *retlen += len;
+
+ if (eccbuf) {
+ unsigned char x[8];
+ size_t dummy;
+ int ret;
+
+ /* Write the ECC data to flash */
+ for (di=0; di<6; di++)
+ x[di] = eccbuf[di];
+
+ x[6]=0x55;
+ x[7]=0x55;
+
+ ret = doc_write_oob_nolock(mtd, to, 8, &dummy, x);
+ if (ret) {
+ up(&this->lock);
+ return ret;
+ }
+ }
+
+ to += len;
+ left -= len;
+ buf += len;
+ }
+
+ up(&this->lock);
+ return 0;
+}
+
+static int doc_writev_ecc(struct mtd_info *mtd, const struct kvec *vecs,
+ unsigned long count, loff_t to, size_t *retlen,
+ u_char *eccbuf, struct nand_oobinfo *oobsel)
+{
+ static char static_buf[512];
+ static DECLARE_MUTEX(writev_buf_sem);
+
+ size_t totretlen = 0;
+ size_t thisvecofs = 0;
+ int ret= 0;
+
+ down(&writev_buf_sem);
+
+ while(count) {
+ size_t thislen, thisretlen;
+ unsigned char *buf;
+
+ buf = vecs->iov_base + thisvecofs;
+ thislen = vecs->iov_len - thisvecofs;
+
+
+ if (thislen >= 512) {
+ thislen = thislen & ~(512-1);
+ thisvecofs += thislen;
+ } else {
+ /* Not enough to fill a page. Copy into buf */
+ memcpy(static_buf, buf, thislen);
+ buf = &static_buf[thislen];
+
+ while(count && thislen < 512) {
+ vecs++;
+ count--;
+ thisvecofs = min((512-thislen), vecs->iov_len);
+ memcpy(buf, vecs->iov_base, thisvecofs);
+ thislen += thisvecofs;
+ buf += thisvecofs;
+ }
+ buf = static_buf;
+ }
+ if (count && thisvecofs == vecs->iov_len) {
+ thisvecofs = 0;
+ vecs++;
+ count--;
+ }
+ ret = doc_write_ecc(mtd, to, thislen, &thisretlen, buf, eccbuf, oobsel);
+
+ totretlen += thisretlen;
+
+ if (ret || thisretlen != thislen)
+ break;
+
+ to += thislen;
+ }
+
+ up(&writev_buf_sem);
+ *retlen = totretlen;
+ return ret;
+}
+
+
+static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t * retlen, u_char * buf)
+{
+ struct DiskOnChip *this = mtd->priv;
+ int len256 = 0, ret;
+ struct Nand *mychip;
+
+ down(&this->lock);
+
+ mychip = &this->chips[ofs >> this->chipshift];
+
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(this, mychip->floor);
+ DoC_SelectChip(this, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(this, mychip->chip);
+ }
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ /* update address for 2M x 8bit devices. OOB starts on the second */
+ /* page to maintain compatibility with doc_read_ecc. */
+ if (this->page256) {
+ if (!(ofs & 0x8))
+ ofs += 0x100;
+ else
+ ofs -= 0x8;
+ }
+
+ DoC_Command(this, NAND_CMD_READOOB, CDSN_CTRL_WP);
+ DoC_Address(this, ADDR_COLUMN_PAGE, ofs, CDSN_CTRL_WP, 0);
+
+ /* treat crossing 8-byte OOB data for 2M x 8bit devices */
+ /* Note: datasheet says it should automaticaly wrap to the */
+ /* next OOB block, but it didn't work here. mf. */
+ if (this->page256 && ofs + len > (ofs | 0x7) + 1) {
+ len256 = (ofs | 0x7) + 1 - ofs;
+ DoC_ReadBuf(this, buf, len256);
+
+ DoC_Command(this, NAND_CMD_READOOB, CDSN_CTRL_WP);
+ DoC_Address(this, ADDR_COLUMN_PAGE, ofs & (~0x1ff),
+ CDSN_CTRL_WP, 0);
+ }
+
+ DoC_ReadBuf(this, &buf[len256], len - len256);
+
+ *retlen = len;
+ /* Reading the full OOB data drops us off of the end of the page,
+ * causing the flash device to go into busy mode, so we need
+ * to wait until ready 11.4.1 and Toshiba TC58256FT docs */
+
+ ret = DoC_WaitReady(this);
+
+ up(&this->lock);
+ return ret;
+
+}
+
+static int doc_write_oob_nolock(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t * retlen, const u_char * buf)
+{
+ struct DiskOnChip *this = mtd->priv;
+ int len256 = 0;
+ void __iomem *docptr = this->virtadr;
+ struct Nand *mychip = &this->chips[ofs >> this->chipshift];
+ volatile int dummy;
+ int status;
+
+ // printk("doc_write_oob(%lx, %d): %2.2X %2.2X %2.2X %2.2X ... %2.2X %2.2X .. %2.2X %2.2X\n",(long)ofs, len,
+ // buf[0], buf[1], buf[2], buf[3], buf[8], buf[9], buf[14],buf[15]);
+
+ /* Find the chip which is to be used and select it */
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(this, mychip->floor);
+ DoC_SelectChip(this, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(this, mychip->chip);
+ }
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ /* disable the ECC engine */
+ WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
+ WriteDOC (DOC_ECC_DIS, docptr, ECCConf);
+
+ /* Reset the chip, see Software Requirement 11.4 item 1. */
+ DoC_Command(this, NAND_CMD_RESET, CDSN_CTRL_WP);
+
+ /* issue the Read2 command to set the pointer to the Spare Data Area. */
+ DoC_Command(this, NAND_CMD_READOOB, CDSN_CTRL_WP);
+
+ /* update address for 2M x 8bit devices. OOB starts on the second */
+ /* page to maintain compatibility with doc_read_ecc. */
+ if (this->page256) {
+ if (!(ofs & 0x8))
+ ofs += 0x100;
+ else
+ ofs -= 0x8;
+ }
+
+ /* issue the Serial Data In command to initial the Page Program process */
+ DoC_Command(this, NAND_CMD_SEQIN, 0);
+ DoC_Address(this, ADDR_COLUMN_PAGE, ofs, 0, 0);
+
+ /* treat crossing 8-byte OOB data for 2M x 8bit devices */
+ /* Note: datasheet says it should automaticaly wrap to the */
+ /* next OOB block, but it didn't work here. mf. */
+ if (this->page256 && ofs + len > (ofs | 0x7) + 1) {
+ len256 = (ofs | 0x7) + 1 - ofs;
+ DoC_WriteBuf(this, buf, len256);
+
+ DoC_Command(this, NAND_CMD_PAGEPROG, 0);
+ DoC_Command(this, NAND_CMD_STATUS, 0);
+ /* DoC_WaitReady() is implicit in DoC_Command */
+
+ if (DoC_is_Millennium(this)) {
+ ReadDOC(docptr, ReadPipeInit);
+ status = ReadDOC(docptr, LastDataRead);
+ } else {
+ dummy = ReadDOC(docptr, CDSNSlowIO);
+ DoC_Delay(this, 2);
+ status = ReadDOC_(docptr, this->ioreg);
+ }
+
+ if (status & 1) {
+ printk(KERN_ERR "Error programming oob data\n");
+ /* There was an error */
+ *retlen = 0;
+ return -EIO;
+ }
+ DoC_Command(this, NAND_CMD_SEQIN, 0);
+ DoC_Address(this, ADDR_COLUMN_PAGE, ofs & (~0x1ff), 0, 0);
+ }
+
+ DoC_WriteBuf(this, &buf[len256], len - len256);
+
+ DoC_Command(this, NAND_CMD_PAGEPROG, 0);
+ DoC_Command(this, NAND_CMD_STATUS, 0);
+ /* DoC_WaitReady() is implicit in DoC_Command */
+
+ if (DoC_is_Millennium(this)) {
+ ReadDOC(docptr, ReadPipeInit);
+ status = ReadDOC(docptr, LastDataRead);
+ } else {
+ dummy = ReadDOC(docptr, CDSNSlowIO);
+ DoC_Delay(this, 2);
+ status = ReadDOC_(docptr, this->ioreg);
+ }
+
+ if (status & 1) {
+ printk(KERN_ERR "Error programming oob data\n");
+ /* There was an error */
+ *retlen = 0;
+ return -EIO;
+ }
+
+ *retlen = len;
+ return 0;
+
+}
+
+static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t * retlen, const u_char * buf)
+{
+ struct DiskOnChip *this = mtd->priv;
+ int ret;
+
+ down(&this->lock);
+ ret = doc_write_oob_nolock(mtd, ofs, len, retlen, buf);
+
+ up(&this->lock);
+ return ret;
+}
+
+static int doc_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct DiskOnChip *this = mtd->priv;
+ __u32 ofs = instr->addr;
+ __u32 len = instr->len;
+ volatile int dummy;
+ void __iomem *docptr = this->virtadr;
+ struct Nand *mychip;
+ int status;
+
+ down(&this->lock);
+
+ if (ofs & (mtd->erasesize-1) || len & (mtd->erasesize-1)) {
+ up(&this->lock);
+ return -EINVAL;
+ }
+
+ instr->state = MTD_ERASING;
+
+ /* FIXME: Do this in the background. Use timers or schedule_task() */
+ while(len) {
+ mychip = &this->chips[ofs >> this->chipshift];
+
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(this, mychip->floor);
+ DoC_SelectChip(this, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(this, mychip->chip);
+ }
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ DoC_Command(this, NAND_CMD_ERASE1, 0);
+ DoC_Address(this, ADDR_PAGE, ofs, 0, 0);
+ DoC_Command(this, NAND_CMD_ERASE2, 0);
+
+ DoC_Command(this, NAND_CMD_STATUS, CDSN_CTRL_WP);
+
+ if (DoC_is_Millennium(this)) {
+ ReadDOC(docptr, ReadPipeInit);
+ status = ReadDOC(docptr, LastDataRead);
+ } else {
+ dummy = ReadDOC(docptr, CDSNSlowIO);
+ DoC_Delay(this, 2);
+ status = ReadDOC_(docptr, this->ioreg);
+ }
+
+ if (status & 1) {
+ printk(KERN_ERR "Error erasing at 0x%x\n", ofs);
+ /* There was an error */
+ instr->state = MTD_ERASE_FAILED;
+ goto callback;
+ }
+ ofs += mtd->erasesize;
+ len -= mtd->erasesize;
+ }
+ instr->state = MTD_ERASE_DONE;
+
+ callback:
+ mtd_erase_callback(instr);
+
+ up(&this->lock);
+ return 0;
+}
+
+
+/****************************************************************************
+ *
+ * Module stuff
+ *
+ ****************************************************************************/
+
+static int __init init_doc2000(void)
+{
+ inter_module_register(im_name, THIS_MODULE, &DoC2k_init);
+ return 0;
+}
+
+static void __exit cleanup_doc2000(void)
+{
+ struct mtd_info *mtd;
+ struct DiskOnChip *this;
+
+ while ((mtd = doc2klist)) {
+ this = mtd->priv;
+ doc2klist = this->nextdoc;
+
+ del_mtd_device(mtd);
+
+ iounmap(this->virtadr);
+ kfree(this->chips);
+ kfree(mtd);
+ }
+ inter_module_unregister(im_name);
+}
+
+module_exit(cleanup_doc2000);
+module_init(init_doc2000);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org> et al.");
+MODULE_DESCRIPTION("MTD driver for DiskOnChip 2000 and Millennium");
+
diff --git a/drivers/mtd/devices/doc2001.c b/drivers/mtd/devices/doc2001.c
new file mode 100644
index 000000000000..1e704915ef08
--- /dev/null
+++ b/drivers/mtd/devices/doc2001.c
@@ -0,0 +1,888 @@
+
+/*
+ * Linux driver for Disk-On-Chip Millennium
+ * (c) 1999 Machine Vision Holdings, Inc.
+ * (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org>
+ *
+ * $Id: doc2001.c,v 1.48 2005/01/05 18:05:12 dwmw2 Exp $
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/doc2000.h>
+
+/* #define ECC_DEBUG */
+
+/* I have no idea why some DoC chips can not use memcop_form|to_io().
+ * This may be due to the different revisions of the ASIC controller built-in or
+ * simplily a QA/Bug issue. Who knows ?? If you have trouble, please uncomment
+ * this:*/
+#undef USE_MEMCPY
+
+static int doc_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf);
+static int doc_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf);
+static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf, u_char *eccbuf,
+ struct nand_oobinfo *oobsel);
+static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf, u_char *eccbuf,
+ struct nand_oobinfo *oobsel);
+static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t *retlen, u_char *buf);
+static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t *retlen, const u_char *buf);
+static int doc_erase (struct mtd_info *mtd, struct erase_info *instr);
+
+static struct mtd_info *docmillist = NULL;
+
+/* Perform the required delay cycles by reading from the NOP register */
+static void DoC_Delay(void __iomem * docptr, unsigned short cycles)
+{
+ volatile char dummy;
+ int i;
+
+ for (i = 0; i < cycles; i++)
+ dummy = ReadDOC(docptr, NOP);
+}
+
+/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */
+static int _DoC_WaitReady(void __iomem * docptr)
+{
+ unsigned short c = 0xffff;
+
+ DEBUG(MTD_DEBUG_LEVEL3,
+ "_DoC_WaitReady called for out-of-line wait\n");
+
+ /* Out-of-line routine to wait for chip response */
+ while (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B) && --c)
+ ;
+
+ if (c == 0)
+ DEBUG(MTD_DEBUG_LEVEL2, "_DoC_WaitReady timed out.\n");
+
+ return (c == 0);
+}
+
+static inline int DoC_WaitReady(void __iomem * docptr)
+{
+ /* This is inline, to optimise the common case, where it's ready instantly */
+ int ret = 0;
+
+ /* 4 read form NOP register should be issued in prior to the read from CDSNControl
+ see Software Requirement 11.4 item 2. */
+ DoC_Delay(docptr, 4);
+
+ if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B))
+ /* Call the out-of-line routine to wait */
+ ret = _DoC_WaitReady(docptr);
+
+ /* issue 2 read from NOP register after reading from CDSNControl register
+ see Software Requirement 11.4 item 2. */
+ DoC_Delay(docptr, 2);
+
+ return ret;
+}
+
+/* DoC_Command: Send a flash command to the flash chip through the CDSN IO register
+ with the internal pipeline. Each of 4 delay cycles (read from the NOP register) is
+ required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */
+
+static inline void DoC_Command(void __iomem * docptr, unsigned char command,
+ unsigned char xtraflags)
+{
+ /* Assert the CLE (Command Latch Enable) line to the flash chip */
+ WriteDOC(xtraflags | CDSN_CTRL_CLE | CDSN_CTRL_CE, docptr, CDSNControl);
+ DoC_Delay(docptr, 4);
+
+ /* Send the command */
+ WriteDOC(command, docptr, Mil_CDSN_IO);
+ WriteDOC(0x00, docptr, WritePipeTerm);
+
+ /* Lower the CLE line */
+ WriteDOC(xtraflags | CDSN_CTRL_CE, docptr, CDSNControl);
+ DoC_Delay(docptr, 4);
+}
+
+/* DoC_Address: Set the current address for the flash chip through the CDSN IO register
+ with the internal pipeline. Each of 4 delay cycles (read from the NOP register) is
+ required after writing to CDSN Control register, see Software Requirement 11.4 item 3. */
+
+static inline void DoC_Address(void __iomem * docptr, int numbytes, unsigned long ofs,
+ unsigned char xtraflags1, unsigned char xtraflags2)
+{
+ /* Assert the ALE (Address Latch Enable) line to the flash chip */
+ WriteDOC(xtraflags1 | CDSN_CTRL_ALE | CDSN_CTRL_CE, docptr, CDSNControl);
+ DoC_Delay(docptr, 4);
+
+ /* Send the address */
+ switch (numbytes)
+ {
+ case 1:
+ /* Send single byte, bits 0-7. */
+ WriteDOC(ofs & 0xff, docptr, Mil_CDSN_IO);
+ WriteDOC(0x00, docptr, WritePipeTerm);
+ break;
+ case 2:
+ /* Send bits 9-16 followed by 17-23 */
+ WriteDOC((ofs >> 9) & 0xff, docptr, Mil_CDSN_IO);
+ WriteDOC((ofs >> 17) & 0xff, docptr, Mil_CDSN_IO);
+ WriteDOC(0x00, docptr, WritePipeTerm);
+ break;
+ case 3:
+ /* Send 0-7, 9-16, then 17-23 */
+ WriteDOC(ofs & 0xff, docptr, Mil_CDSN_IO);
+ WriteDOC((ofs >> 9) & 0xff, docptr, Mil_CDSN_IO);
+ WriteDOC((ofs >> 17) & 0xff, docptr, Mil_CDSN_IO);
+ WriteDOC(0x00, docptr, WritePipeTerm);
+ break;
+ default:
+ return;
+ }
+
+ /* Lower the ALE line */
+ WriteDOC(xtraflags1 | xtraflags2 | CDSN_CTRL_CE, docptr, CDSNControl);
+ DoC_Delay(docptr, 4);
+}
+
+/* DoC_SelectChip: Select a given flash chip within the current floor */
+static int DoC_SelectChip(void __iomem * docptr, int chip)
+{
+ /* Select the individual flash chip requested */
+ WriteDOC(chip, docptr, CDSNDeviceSelect);
+ DoC_Delay(docptr, 4);
+
+ /* Wait for it to be ready */
+ return DoC_WaitReady(docptr);
+}
+
+/* DoC_SelectFloor: Select a given floor (bank of flash chips) */
+static int DoC_SelectFloor(void __iomem * docptr, int floor)
+{
+ /* Select the floor (bank) of chips required */
+ WriteDOC(floor, docptr, FloorSelect);
+
+ /* Wait for the chip to be ready */
+ return DoC_WaitReady(docptr);
+}
+
+/* DoC_IdentChip: Identify a given NAND chip given {floor,chip} */
+static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip)
+{
+ int mfr, id, i, j;
+ volatile char dummy;
+
+ /* Page in the required floor/chip
+ FIXME: is this supported by Millennium ?? */
+ DoC_SelectFloor(doc->virtadr, floor);
+ DoC_SelectChip(doc->virtadr, chip);
+
+ /* Reset the chip, see Software Requirement 11.4 item 1. */
+ DoC_Command(doc->virtadr, NAND_CMD_RESET, CDSN_CTRL_WP);
+ DoC_WaitReady(doc->virtadr);
+
+ /* Read the NAND chip ID: 1. Send ReadID command */
+ DoC_Command(doc->virtadr, NAND_CMD_READID, CDSN_CTRL_WP);
+
+ /* Read the NAND chip ID: 2. Send address byte zero */
+ DoC_Address(doc->virtadr, 1, 0x00, CDSN_CTRL_WP, 0x00);
+
+ /* Read the manufacturer and device id codes of the flash device through
+ CDSN IO register see Software Requirement 11.4 item 5.*/
+ dummy = ReadDOC(doc->virtadr, ReadPipeInit);
+ DoC_Delay(doc->virtadr, 2);
+ mfr = ReadDOC(doc->virtadr, Mil_CDSN_IO);
+
+ DoC_Delay(doc->virtadr, 2);
+ id = ReadDOC(doc->virtadr, Mil_CDSN_IO);
+ dummy = ReadDOC(doc->virtadr, LastDataRead);
+
+ /* No response - return failure */
+ if (mfr == 0xff || mfr == 0)
+ return 0;
+
+ /* FIXME: to deal with multi-flash on multi-Millennium case more carefully */
+ for (i = 0; nand_flash_ids[i].name != NULL; i++) {
+ if ( id == nand_flash_ids[i].id) {
+ /* Try to identify manufacturer */
+ for (j = 0; nand_manuf_ids[j].id != 0x0; j++) {
+ if (nand_manuf_ids[j].id == mfr)
+ break;
+ }
+ printk(KERN_INFO "Flash chip found: Manufacturer ID: %2.2X, "
+ "Chip ID: %2.2X (%s:%s)\n",
+ mfr, id, nand_manuf_ids[j].name, nand_flash_ids[i].name);
+ doc->mfr = mfr;
+ doc->id = id;
+ doc->chipshift = ffs((nand_flash_ids[i].chipsize << 20)) - 1;
+ break;
+ }
+ }
+
+ if (nand_flash_ids[i].name == NULL)
+ return 0;
+ else
+ return 1;
+}
+
+/* DoC_ScanChips: Find all NAND chips present in a DiskOnChip, and identify them */
+static void DoC_ScanChips(struct DiskOnChip *this)
+{
+ int floor, chip;
+ int numchips[MAX_FLOORS_MIL];
+ int ret;
+
+ this->numchips = 0;
+ this->mfr = 0;
+ this->id = 0;
+
+ /* For each floor, find the number of valid chips it contains */
+ for (floor = 0,ret = 1; floor < MAX_FLOORS_MIL; floor++) {
+ numchips[floor] = 0;
+ for (chip = 0; chip < MAX_CHIPS_MIL && ret != 0; chip++) {
+ ret = DoC_IdentChip(this, floor, chip);
+ if (ret) {
+ numchips[floor]++;
+ this->numchips++;
+ }
+ }
+ }
+ /* If there are none at all that we recognise, bail */
+ if (!this->numchips) {
+ printk("No flash chips recognised.\n");
+ return;
+ }
+
+ /* Allocate an array to hold the information for each chip */
+ this->chips = kmalloc(sizeof(struct Nand) * this->numchips, GFP_KERNEL);
+ if (!this->chips){
+ printk("No memory for allocating chip info structures\n");
+ return;
+ }
+
+ /* Fill out the chip array with {floor, chipno} for each
+ * detected chip in the device. */
+ for (floor = 0, ret = 0; floor < MAX_FLOORS_MIL; floor++) {
+ for (chip = 0 ; chip < numchips[floor] ; chip++) {
+ this->chips[ret].floor = floor;
+ this->chips[ret].chip = chip;
+ this->chips[ret].curadr = 0;
+ this->chips[ret].curmode = 0x50;
+ ret++;
+ }
+ }
+
+ /* Calculate and print the total size of the device */
+ this->totlen = this->numchips * (1 << this->chipshift);
+ printk(KERN_INFO "%d flash chips found. Total DiskOnChip size: %ld MiB\n",
+ this->numchips ,this->totlen >> 20);
+}
+
+static int DoCMil_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2)
+{
+ int tmp1, tmp2, retval;
+
+ if (doc1->physadr == doc2->physadr)
+ return 1;
+
+ /* Use the alias resolution register which was set aside for this
+ * purpose. If it's value is the same on both chips, they might
+ * be the same chip, and we write to one and check for a change in
+ * the other. It's unclear if this register is usuable in the
+ * DoC 2000 (it's in the Millenium docs), but it seems to work. */
+ tmp1 = ReadDOC(doc1->virtadr, AliasResolution);
+ tmp2 = ReadDOC(doc2->virtadr, AliasResolution);
+ if (tmp1 != tmp2)
+ return 0;
+
+ WriteDOC((tmp1+1) % 0xff, doc1->virtadr, AliasResolution);
+ tmp2 = ReadDOC(doc2->virtadr, AliasResolution);
+ if (tmp2 == (tmp1+1) % 0xff)
+ retval = 1;
+ else
+ retval = 0;
+
+ /* Restore register contents. May not be necessary, but do it just to
+ * be safe. */
+ WriteDOC(tmp1, doc1->virtadr, AliasResolution);
+
+ return retval;
+}
+
+static const char im_name[] = "DoCMil_init";
+
+/* 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 mtd 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.
+ */
+static void DoCMil_init(struct mtd_info *mtd)
+{
+ struct DiskOnChip *this = mtd->priv;
+ struct DiskOnChip *old = NULL;
+
+ /* We must avoid being called twice for the same device. */
+ if (docmillist)
+ old = docmillist->priv;
+
+ while (old) {
+ if (DoCMil_is_alias(this, old)) {
+ printk(KERN_NOTICE "Ignoring DiskOnChip Millennium at "
+ "0x%lX - already configured\n", this->physadr);
+ iounmap(this->virtadr);
+ kfree(mtd);
+ return;
+ }
+ if (old->nextdoc)
+ old = old->nextdoc->priv;
+ else
+ old = NULL;
+ }
+
+ mtd->name = "DiskOnChip Millennium";
+ printk(KERN_NOTICE "DiskOnChip Millennium found at address 0x%lX\n",
+ this->physadr);
+
+ mtd->type = MTD_NANDFLASH;
+ mtd->flags = MTD_CAP_NANDFLASH;
+ mtd->ecctype = MTD_ECC_RS_DiskOnChip;
+ mtd->size = 0;
+
+ /* FIXME: erase size is not always 8KiB */
+ mtd->erasesize = 0x2000;
+
+ mtd->oobblock = 512;
+ mtd->oobsize = 16;
+ mtd->owner = THIS_MODULE;
+ mtd->erase = doc_erase;
+ mtd->point = NULL;
+ mtd->unpoint = NULL;
+ mtd->read = doc_read;
+ mtd->write = doc_write;
+ mtd->read_ecc = doc_read_ecc;
+ mtd->write_ecc = doc_write_ecc;
+ mtd->read_oob = doc_read_oob;
+ mtd->write_oob = doc_write_oob;
+ mtd->sync = NULL;
+
+ this->totlen = 0;
+ this->numchips = 0;
+ this->curfloor = -1;
+ this->curchip = -1;
+
+ /* Ident all the chips present. */
+ DoC_ScanChips(this);
+
+ if (!this->totlen) {
+ kfree(mtd);
+ iounmap(this->virtadr);
+ } else {
+ this->nextdoc = docmillist;
+ docmillist = mtd;
+ mtd->size = this->totlen;
+ add_mtd_device(mtd);
+ return;
+ }
+}
+
+static int doc_read (struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ /* Just a special case of doc_read_ecc */
+ return doc_read_ecc(mtd, from, len, retlen, buf, NULL, NULL);
+}
+
+static int doc_read_ecc (struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf, u_char *eccbuf,
+ struct nand_oobinfo *oobsel)
+{
+ int i, ret;
+ volatile char dummy;
+ unsigned char syndrome[6];
+ struct DiskOnChip *this = mtd->priv;
+ void __iomem *docptr = this->virtadr;
+ struct Nand *mychip = &this->chips[from >> (this->chipshift)];
+
+ /* Don't allow read past end of device */
+ if (from >= this->totlen)
+ return -EINVAL;
+
+ /* Don't allow a single read to cross a 512-byte block boundary */
+ if (from + len > ((from | 0x1ff) + 1))
+ len = ((from | 0x1ff) + 1) - from;
+
+ /* Find the chip which is to be used and select it */
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(docptr, mychip->floor);
+ DoC_SelectChip(docptr, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(docptr, mychip->chip);
+ }
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ /* issue the Read0 or Read1 command depend on which half of the page
+ we are accessing. Polling the Flash Ready bit after issue 3 bytes
+ address in Sequence Read Mode, see Software Requirement 11.4 item 1.*/
+ DoC_Command(docptr, (from >> 8) & 1, CDSN_CTRL_WP);
+ DoC_Address(docptr, 3, from, CDSN_CTRL_WP, 0x00);
+ DoC_WaitReady(docptr);
+
+ if (eccbuf) {
+ /* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/
+ WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
+ WriteDOC (DOC_ECC_EN, docptr, ECCConf);
+ } else {
+ /* disable the ECC engine */
+ WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
+ WriteDOC (DOC_ECC_DIS, docptr, ECCConf);
+ }
+
+ /* Read the data via the internal pipeline through CDSN IO register,
+ see Pipelined Read Operations 11.3 */
+ dummy = ReadDOC(docptr, ReadPipeInit);
+#ifndef USE_MEMCPY
+ for (i = 0; i < len-1; i++) {
+ /* N.B. you have to increase the source address in this way or the
+ ECC logic will not work properly */
+ buf[i] = ReadDOC(docptr, Mil_CDSN_IO + (i & 0xff));
+ }
+#else
+ memcpy_fromio(buf, docptr + DoC_Mil_CDSN_IO, len - 1);
+#endif
+ buf[len - 1] = ReadDOC(docptr, LastDataRead);
+
+ /* Let the caller know we completed it */
+ *retlen = len;
+ ret = 0;
+
+ if (eccbuf) {
+ /* Read the ECC data from Spare Data Area,
+ see Reed-Solomon EDC/ECC 11.1 */
+ dummy = ReadDOC(docptr, ReadPipeInit);
+#ifndef USE_MEMCPY
+ for (i = 0; i < 5; i++) {
+ /* N.B. you have to increase the source address in this way or the
+ ECC logic will not work properly */
+ eccbuf[i] = ReadDOC(docptr, Mil_CDSN_IO + i);
+ }
+#else
+ memcpy_fromio(eccbuf, docptr + DoC_Mil_CDSN_IO, 5);
+#endif
+ eccbuf[5] = ReadDOC(docptr, LastDataRead);
+
+ /* Flush the pipeline */
+ dummy = ReadDOC(docptr, ECCConf);
+ dummy = ReadDOC(docptr, ECCConf);
+
+ /* Check the ECC Status */
+ if (ReadDOC(docptr, ECCConf) & 0x80) {
+ int nb_errors;
+ /* There was an ECC error */
+#ifdef ECC_DEBUG
+ printk("DiskOnChip ECC Error: Read at %lx\n", (long)from);
+#endif
+ /* Read the ECC syndrom through the DiskOnChip ECC logic.
+ These syndrome will be all ZERO when there is no error */
+ for (i = 0; i < 6; i++) {
+ syndrome[i] = ReadDOC(docptr, ECCSyndrome0 + i);
+ }
+ nb_errors = doc_decode_ecc(buf, syndrome);
+#ifdef ECC_DEBUG
+ printk("ECC Errors corrected: %x\n", nb_errors);
+#endif
+ if (nb_errors < 0) {
+ /* We return error, but have actually done the read. Not that
+ this can be told to user-space, via sys_read(), but at least
+ MTD-aware stuff can know about it by checking *retlen */
+ ret = -EIO;
+ }
+ }
+
+#ifdef PSYCHO_DEBUG
+ printk("ECC DATA at %lx: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
+ (long)from, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3],
+ eccbuf[4], eccbuf[5]);
+#endif
+
+ /* disable the ECC engine */
+ WriteDOC(DOC_ECC_DIS, docptr , ECCConf);
+ }
+
+ return ret;
+}
+
+static int doc_write (struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ char eccbuf[6];
+ return doc_write_ecc(mtd, to, len, retlen, buf, eccbuf, NULL);
+}
+
+static int doc_write_ecc (struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf, u_char *eccbuf,
+ struct nand_oobinfo *oobsel)
+{
+ int i,ret = 0;
+ volatile char dummy;
+ struct DiskOnChip *this = mtd->priv;
+ void __iomem *docptr = this->virtadr;
+ struct Nand *mychip = &this->chips[to >> (this->chipshift)];
+
+ /* Don't allow write past end of device */
+ if (to >= this->totlen)
+ return -EINVAL;
+
+#if 0
+ /* Don't allow a single write to cross a 512-byte block boundary */
+ if (to + len > ( (to | 0x1ff) + 1))
+ len = ((to | 0x1ff) + 1) - to;
+#else
+ /* Don't allow writes which aren't exactly one block */
+ if (to & 0x1ff || len != 0x200)
+ return -EINVAL;
+#endif
+
+ /* Find the chip which is to be used and select it */
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(docptr, mychip->floor);
+ DoC_SelectChip(docptr, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(docptr, mychip->chip);
+ }
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ /* Reset the chip, see Software Requirement 11.4 item 1. */
+ DoC_Command(docptr, NAND_CMD_RESET, 0x00);
+ DoC_WaitReady(docptr);
+ /* Set device to main plane of flash */
+ DoC_Command(docptr, NAND_CMD_READ0, 0x00);
+
+ /* issue the Serial Data In command to initial the Page Program process */
+ DoC_Command(docptr, NAND_CMD_SEQIN, 0x00);
+ DoC_Address(docptr, 3, to, 0x00, 0x00);
+ DoC_WaitReady(docptr);
+
+ if (eccbuf) {
+ /* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/
+ WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
+ WriteDOC (DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf);
+ } else {
+ /* disable the ECC engine */
+ WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
+ WriteDOC (DOC_ECC_DIS, docptr, ECCConf);
+ }
+
+ /* Write the data via the internal pipeline through CDSN IO register,
+ see Pipelined Write Operations 11.2 */
+#ifndef USE_MEMCPY
+ for (i = 0; i < len; i++) {
+ /* N.B. you have to increase the source address in this way or the
+ ECC logic will not work properly */
+ WriteDOC(buf[i], docptr, Mil_CDSN_IO + i);
+ }
+#else
+ memcpy_toio(docptr + DoC_Mil_CDSN_IO, buf, len);
+#endif
+ WriteDOC(0x00, docptr, WritePipeTerm);
+
+ if (eccbuf) {
+ /* Write ECC data to flash, the ECC info is generated by the DiskOnChip ECC logic
+ see Reed-Solomon EDC/ECC 11.1 */
+ WriteDOC(0, docptr, NOP);
+ WriteDOC(0, docptr, NOP);
+ WriteDOC(0, docptr, NOP);
+
+ /* Read the ECC data through the DiskOnChip ECC logic */
+ for (i = 0; i < 6; i++) {
+ eccbuf[i] = ReadDOC(docptr, ECCSyndrome0 + i);
+ }
+
+ /* ignore the ECC engine */
+ WriteDOC(DOC_ECC_DIS, docptr , ECCConf);
+
+#ifndef USE_MEMCPY
+ /* Write the ECC data to flash */
+ for (i = 0; i < 6; i++) {
+ /* N.B. you have to increase the source address in this way or the
+ ECC logic will not work properly */
+ WriteDOC(eccbuf[i], docptr, Mil_CDSN_IO + i);
+ }
+#else
+ memcpy_toio(docptr + DoC_Mil_CDSN_IO, eccbuf, 6);
+#endif
+
+ /* write the block status BLOCK_USED (0x5555) at the end of ECC data
+ FIXME: this is only a hack for programming the IPL area for LinuxBIOS
+ and should be replace with proper codes in user space utilities */
+ WriteDOC(0x55, docptr, Mil_CDSN_IO);
+ WriteDOC(0x55, docptr, Mil_CDSN_IO + 1);
+
+ WriteDOC(0x00, docptr, WritePipeTerm);
+
+#ifdef PSYCHO_DEBUG
+ printk("OOB data at %lx is %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
+ (long) to, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3],
+ eccbuf[4], eccbuf[5]);
+#endif
+ }
+
+ /* Commit the Page Program command and wait for ready
+ see Software Requirement 11.4 item 1.*/
+ DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00);
+ DoC_WaitReady(docptr);
+
+ /* Read the status of the flash device through CDSN IO register
+ see Software Requirement 11.4 item 5.*/
+ DoC_Command(docptr, NAND_CMD_STATUS, CDSN_CTRL_WP);
+ dummy = ReadDOC(docptr, ReadPipeInit);
+ DoC_Delay(docptr, 2);
+ if (ReadDOC(docptr, Mil_CDSN_IO) & 1) {
+ printk("Error programming flash\n");
+ /* Error in programming
+ FIXME: implement Bad Block Replacement (in nftl.c ??) */
+ *retlen = 0;
+ ret = -EIO;
+ }
+ dummy = ReadDOC(docptr, LastDataRead);
+
+ /* Let the caller know we completed it */
+ *retlen = len;
+
+ return ret;
+}
+
+static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t *retlen, u_char *buf)
+{
+#ifndef USE_MEMCPY
+ int i;
+#endif
+ volatile char dummy;
+ struct DiskOnChip *this = mtd->priv;
+ void __iomem *docptr = this->virtadr;
+ struct Nand *mychip = &this->chips[ofs >> this->chipshift];
+
+ /* Find the chip which is to be used and select it */
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(docptr, mychip->floor);
+ DoC_SelectChip(docptr, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(docptr, mychip->chip);
+ }
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ /* disable the ECC engine */
+ WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
+ WriteDOC (DOC_ECC_DIS, docptr, ECCConf);
+
+ /* issue the Read2 command to set the pointer to the Spare Data Area.
+ Polling the Flash Ready bit after issue 3 bytes address in
+ Sequence Read Mode, see Software Requirement 11.4 item 1.*/
+ DoC_Command(docptr, NAND_CMD_READOOB, CDSN_CTRL_WP);
+ DoC_Address(docptr, 3, ofs, CDSN_CTRL_WP, 0x00);
+ DoC_WaitReady(docptr);
+
+ /* Read the data out via the internal pipeline through CDSN IO register,
+ see Pipelined Read Operations 11.3 */
+ dummy = ReadDOC(docptr, ReadPipeInit);
+#ifndef USE_MEMCPY
+ for (i = 0; i < len-1; i++) {
+ /* N.B. you have to increase the source address in this way or the
+ ECC logic will not work properly */
+ buf[i] = ReadDOC(docptr, Mil_CDSN_IO + i);
+ }
+#else
+ memcpy_fromio(buf, docptr + DoC_Mil_CDSN_IO, len - 1);
+#endif
+ buf[len - 1] = ReadDOC(docptr, LastDataRead);
+
+ *retlen = len;
+
+ return 0;
+}
+
+static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+#ifndef USE_MEMCPY
+ int i;
+#endif
+ volatile char dummy;
+ int ret = 0;
+ struct DiskOnChip *this = mtd->priv;
+ void __iomem *docptr = this->virtadr;
+ struct Nand *mychip = &this->chips[ofs >> this->chipshift];
+
+ /* Find the chip which is to be used and select it */
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(docptr, mychip->floor);
+ DoC_SelectChip(docptr, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(docptr, mychip->chip);
+ }
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ /* disable the ECC engine */
+ WriteDOC (DOC_ECC_RESET, docptr, ECCConf);
+ WriteDOC (DOC_ECC_DIS, docptr, ECCConf);
+
+ /* Reset the chip, see Software Requirement 11.4 item 1. */
+ DoC_Command(docptr, NAND_CMD_RESET, CDSN_CTRL_WP);
+ DoC_WaitReady(docptr);
+ /* issue the Read2 command to set the pointer to the Spare Data Area. */
+ DoC_Command(docptr, NAND_CMD_READOOB, CDSN_CTRL_WP);
+
+ /* issue the Serial Data In command to initial the Page Program process */
+ DoC_Command(docptr, NAND_CMD_SEQIN, 0x00);
+ DoC_Address(docptr, 3, ofs, 0x00, 0x00);
+
+ /* Write the data via the internal pipeline through CDSN IO register,
+ see Pipelined Write Operations 11.2 */
+#ifndef USE_MEMCPY
+ for (i = 0; i < len; i++) {
+ /* N.B. you have to increase the source address in this way or the
+ ECC logic will not work properly */
+ WriteDOC(buf[i], docptr, Mil_CDSN_IO + i);
+ }
+#else
+ memcpy_toio(docptr + DoC_Mil_CDSN_IO, buf, len);
+#endif
+ WriteDOC(0x00, docptr, WritePipeTerm);
+
+ /* Commit the Page Program command and wait for ready
+ see Software Requirement 11.4 item 1.*/
+ DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00);
+ DoC_WaitReady(docptr);
+
+ /* Read the status of the flash device through CDSN IO register
+ see Software Requirement 11.4 item 5.*/
+ DoC_Command(docptr, NAND_CMD_STATUS, 0x00);
+ dummy = ReadDOC(docptr, ReadPipeInit);
+ DoC_Delay(docptr, 2);
+ if (ReadDOC(docptr, Mil_CDSN_IO) & 1) {
+ printk("Error programming oob data\n");
+ /* FIXME: implement Bad Block Replacement (in nftl.c ??) */
+ *retlen = 0;
+ ret = -EIO;
+ }
+ dummy = ReadDOC(docptr, LastDataRead);
+
+ *retlen = len;
+
+ return ret;
+}
+
+int doc_erase (struct mtd_info *mtd, struct erase_info *instr)
+{
+ volatile char dummy;
+ struct DiskOnChip *this = mtd->priv;
+ __u32 ofs = instr->addr;
+ __u32 len = instr->len;
+ void __iomem *docptr = this->virtadr;
+ struct Nand *mychip = &this->chips[ofs >> this->chipshift];
+
+ if (len != mtd->erasesize)
+ printk(KERN_WARNING "Erase not right size (%x != %x)n",
+ len, mtd->erasesize);
+
+ /* Find the chip which is to be used and select it */
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(docptr, mychip->floor);
+ DoC_SelectChip(docptr, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(docptr, mychip->chip);
+ }
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ instr->state = MTD_ERASE_PENDING;
+
+ /* issue the Erase Setup command */
+ DoC_Command(docptr, NAND_CMD_ERASE1, 0x00);
+ DoC_Address(docptr, 2, ofs, 0x00, 0x00);
+
+ /* Commit the Erase Start command and wait for ready
+ see Software Requirement 11.4 item 1.*/
+ DoC_Command(docptr, NAND_CMD_ERASE2, 0x00);
+ DoC_WaitReady(docptr);
+
+ instr->state = MTD_ERASING;
+
+ /* Read the status of the flash device through CDSN IO register
+ see Software Requirement 11.4 item 5.
+ FIXME: it seems that we are not wait long enough, some blocks are not
+ erased fully */
+ DoC_Command(docptr, NAND_CMD_STATUS, CDSN_CTRL_WP);
+ dummy = ReadDOC(docptr, ReadPipeInit);
+ DoC_Delay(docptr, 2);
+ if (ReadDOC(docptr, Mil_CDSN_IO) & 1) {
+ printk("Error Erasing at 0x%x\n", ofs);
+ /* There was an error
+ FIXME: implement Bad Block Replacement (in nftl.c ??) */
+ instr->state = MTD_ERASE_FAILED;
+ } else
+ instr->state = MTD_ERASE_DONE;
+ dummy = ReadDOC(docptr, LastDataRead);
+
+ mtd_erase_callback(instr);
+
+ return 0;
+}
+
+/****************************************************************************
+ *
+ * Module stuff
+ *
+ ****************************************************************************/
+
+static int __init init_doc2001(void)
+{
+ inter_module_register(im_name, THIS_MODULE, &DoCMil_init);
+ return 0;
+}
+
+static void __exit cleanup_doc2001(void)
+{
+ struct mtd_info *mtd;
+ struct DiskOnChip *this;
+
+ while ((mtd=docmillist)) {
+ this = mtd->priv;
+ docmillist = this->nextdoc;
+
+ del_mtd_device(mtd);
+
+ iounmap(this->virtadr);
+ kfree(this->chips);
+ kfree(mtd);
+ }
+ inter_module_unregister(im_name);
+}
+
+module_exit(cleanup_doc2001);
+module_init(init_doc2001);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org> et al.");
+MODULE_DESCRIPTION("Alternative driver for DiskOnChip Millennium");
diff --git a/drivers/mtd/devices/doc2001plus.c b/drivers/mtd/devices/doc2001plus.c
new file mode 100644
index 000000000000..ed47bafb2ce2
--- /dev/null
+++ b/drivers/mtd/devices/doc2001plus.c
@@ -0,0 +1,1154 @@
+/*
+ * Linux driver for Disk-On-Chip Millennium Plus
+ *
+ * (c) 2002-2003 Greg Ungerer <gerg@snapgear.com>
+ * (c) 2002-2003 SnapGear Inc
+ * (c) 1999 Machine Vision Holdings, Inc.
+ * (c) 1999, 2000 David Woodhouse <dwmw2@infradead.org>
+ *
+ * $Id: doc2001plus.c,v 1.13 2005/01/05 18:05:12 dwmw2 Exp $
+ *
+ * Released under GPL
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/bitops.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/doc2000.h>
+
+/* #define ECC_DEBUG */
+
+/* I have no idea why some DoC chips can not use memcop_form|to_io().
+ * This may be due to the different revisions of the ASIC controller built-in or
+ * simplily a QA/Bug issue. Who knows ?? If you have trouble, please uncomment
+ * this:*/
+#undef USE_MEMCPY
+
+static int doc_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf);
+static int doc_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf);
+static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf, u_char *eccbuf,
+ struct nand_oobinfo *oobsel);
+static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf, u_char *eccbuf,
+ struct nand_oobinfo *oobsel);
+static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t *retlen, u_char *buf);
+static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t *retlen, const u_char *buf);
+static int doc_erase (struct mtd_info *mtd, struct erase_info *instr);
+
+static struct mtd_info *docmilpluslist = NULL;
+
+
+/* Perform the required delay cycles by writing to the NOP register */
+static void DoC_Delay(void __iomem * docptr, int cycles)
+{
+ int i;
+
+ for (i = 0; (i < cycles); i++)
+ WriteDOC(0, docptr, Mplus_NOP);
+}
+
+#define CDSN_CTRL_FR_B_MASK (CDSN_CTRL_FR_B0 | CDSN_CTRL_FR_B1)
+
+/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */
+static int _DoC_WaitReady(void __iomem * docptr)
+{
+ unsigned int c = 0xffff;
+
+ DEBUG(MTD_DEBUG_LEVEL3,
+ "_DoC_WaitReady called for out-of-line wait\n");
+
+ /* Out-of-line routine to wait for chip response */
+ while (((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) && --c)
+ ;
+
+ if (c == 0)
+ DEBUG(MTD_DEBUG_LEVEL2, "_DoC_WaitReady timed out.\n");
+
+ return (c == 0);
+}
+
+static inline int DoC_WaitReady(void __iomem * docptr)
+{
+ /* This is inline, to optimise the common case, where it's ready instantly */
+ int ret = 0;
+
+ /* read form NOP register should be issued prior to the read from CDSNControl
+ see Software Requirement 11.4 item 2. */
+ DoC_Delay(docptr, 4);
+
+ if ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK)
+ /* Call the out-of-line routine to wait */
+ ret = _DoC_WaitReady(docptr);
+
+ return ret;
+}
+
+/* For some reason the Millennium Plus seems to occassionally put itself
+ * into reset mode. For me this happens randomly, with no pattern that I
+ * can detect. M-systems suggest always check this on any block level
+ * operation and setting to normal mode if in reset mode.
+ */
+static inline void DoC_CheckASIC(void __iomem * docptr)
+{
+ /* Make sure the DoC is in normal mode */
+ if ((ReadDOC(docptr, Mplus_DOCControl) & DOC_MODE_NORMAL) == 0) {
+ WriteDOC((DOC_MODE_NORMAL | DOC_MODE_MDWREN), docptr, Mplus_DOCControl);
+ WriteDOC(~(DOC_MODE_NORMAL | DOC_MODE_MDWREN), docptr, Mplus_CtrlConfirm);
+ }
+}
+
+/* DoC_Command: Send a flash command to the flash chip through the Flash
+ * command register. Need 2 Write Pipeline Terminates to complete send.
+ */
+static inline void DoC_Command(void __iomem * docptr, unsigned char command,
+ unsigned char xtraflags)
+{
+ WriteDOC(command, docptr, Mplus_FlashCmd);
+ WriteDOC(command, docptr, Mplus_WritePipeTerm);
+ WriteDOC(command, docptr, Mplus_WritePipeTerm);
+}
+
+/* DoC_Address: Set the current address for the flash chip through the Flash
+ * Address register. Need 2 Write Pipeline Terminates to complete send.
+ */
+static inline void DoC_Address(struct DiskOnChip *doc, int numbytes,
+ unsigned long ofs, unsigned char xtraflags1,
+ unsigned char xtraflags2)
+{
+ void __iomem * docptr = doc->virtadr;
+
+ /* Allow for possible Mill Plus internal flash interleaving */
+ ofs >>= doc->interleave;
+
+ switch (numbytes) {
+ case 1:
+ /* Send single byte, bits 0-7. */
+ WriteDOC(ofs & 0xff, docptr, Mplus_FlashAddress);
+ break;
+ case 2:
+ /* Send bits 9-16 followed by 17-23 */
+ WriteDOC((ofs >> 9) & 0xff, docptr, Mplus_FlashAddress);
+ WriteDOC((ofs >> 17) & 0xff, docptr, Mplus_FlashAddress);
+ break;
+ case 3:
+ /* Send 0-7, 9-16, then 17-23 */
+ WriteDOC(ofs & 0xff, docptr, Mplus_FlashAddress);
+ WriteDOC((ofs >> 9) & 0xff, docptr, Mplus_FlashAddress);
+ WriteDOC((ofs >> 17) & 0xff, docptr, Mplus_FlashAddress);
+ break;
+ default:
+ return;
+ }
+
+ WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
+ WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
+}
+
+/* DoC_SelectChip: Select a given flash chip within the current floor */
+static int DoC_SelectChip(void __iomem * docptr, int chip)
+{
+ /* No choice for flash chip on Millennium Plus */
+ return 0;
+}
+
+/* DoC_SelectFloor: Select a given floor (bank of flash chips) */
+static int DoC_SelectFloor(void __iomem * docptr, int floor)
+{
+ WriteDOC((floor & 0x3), docptr, Mplus_DeviceSelect);
+ return 0;
+}
+
+/*
+ * Translate the given offset into the appropriate command and offset.
+ * This does the mapping using the 16bit interleave layout defined by
+ * M-Systems, and looks like this for a sector pair:
+ * +-----------+-------+-------+-------+--------------+---------+-----------+
+ * | 0 --- 511 |512-517|518-519|520-521| 522 --- 1033 |1034-1039|1040 - 1055|
+ * +-----------+-------+-------+-------+--------------+---------+-----------+
+ * | Data 0 | ECC 0 |Flags0 |Flags1 | Data 1 |ECC 1 | OOB 1 + 2 |
+ * +-----------+-------+-------+-------+--------------+---------+-----------+
+ */
+/* FIXME: This lives in INFTL not here. Other users of flash devices
+ may not want it */
+static unsigned int DoC_GetDataOffset(struct mtd_info *mtd, loff_t *from)
+{
+ struct DiskOnChip *this = mtd->priv;
+
+ if (this->interleave) {
+ unsigned int ofs = *from & 0x3ff;
+ unsigned int cmd;
+
+ if (ofs < 512) {
+ cmd = NAND_CMD_READ0;
+ ofs &= 0x1ff;
+ } else if (ofs < 1014) {
+ cmd = NAND_CMD_READ1;
+ ofs = (ofs & 0x1ff) + 10;
+ } else {
+ cmd = NAND_CMD_READOOB;
+ ofs = ofs - 1014;
+ }
+
+ *from = (*from & ~0x3ff) | ofs;
+ return cmd;
+ } else {
+ /* No interleave */
+ if ((*from) & 0x100)
+ return NAND_CMD_READ1;
+ return NAND_CMD_READ0;
+ }
+}
+
+static unsigned int DoC_GetECCOffset(struct mtd_info *mtd, loff_t *from)
+{
+ unsigned int ofs, cmd;
+
+ if (*from & 0x200) {
+ cmd = NAND_CMD_READOOB;
+ ofs = 10 + (*from & 0xf);
+ } else {
+ cmd = NAND_CMD_READ1;
+ ofs = (*from & 0xf);
+ }
+
+ *from = (*from & ~0x3ff) | ofs;
+ return cmd;
+}
+
+static unsigned int DoC_GetFlagsOffset(struct mtd_info *mtd, loff_t *from)
+{
+ unsigned int ofs, cmd;
+
+ cmd = NAND_CMD_READ1;
+ ofs = (*from & 0x200) ? 8 : 6;
+ *from = (*from & ~0x3ff) | ofs;
+ return cmd;
+}
+
+static unsigned int DoC_GetHdrOffset(struct mtd_info *mtd, loff_t *from)
+{
+ unsigned int ofs, cmd;
+
+ cmd = NAND_CMD_READOOB;
+ ofs = (*from & 0x200) ? 24 : 16;
+ *from = (*from & ~0x3ff) | ofs;
+ return cmd;
+}
+
+static inline void MemReadDOC(void __iomem * docptr, unsigned char *buf, int len)
+{
+#ifndef USE_MEMCPY
+ int i;
+ for (i = 0; i < len; i++)
+ buf[i] = ReadDOC(docptr, Mil_CDSN_IO + i);
+#else
+ memcpy_fromio(buf, docptr + DoC_Mil_CDSN_IO, len);
+#endif
+}
+
+static inline void MemWriteDOC(void __iomem * docptr, unsigned char *buf, int len)
+{
+#ifndef USE_MEMCPY
+ int i;
+ for (i = 0; i < len; i++)
+ WriteDOC(buf[i], docptr, Mil_CDSN_IO + i);
+#else
+ memcpy_toio(docptr + DoC_Mil_CDSN_IO, buf, len);
+#endif
+}
+
+/* DoC_IdentChip: Identify a given NAND chip given {floor,chip} */
+static int DoC_IdentChip(struct DiskOnChip *doc, int floor, int chip)
+{
+ int mfr, id, i, j;
+ volatile char dummy;
+ void __iomem * docptr = doc->virtadr;
+
+ /* Page in the required floor/chip */
+ DoC_SelectFloor(docptr, floor);
+ DoC_SelectChip(docptr, chip);
+
+ /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
+ WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect);
+
+ /* Reset the chip, see Software Requirement 11.4 item 1. */
+ DoC_Command(docptr, NAND_CMD_RESET, 0);
+ DoC_WaitReady(docptr);
+
+ /* Read the NAND chip ID: 1. Send ReadID command */
+ DoC_Command(docptr, NAND_CMD_READID, 0);
+
+ /* Read the NAND chip ID: 2. Send address byte zero */
+ DoC_Address(doc, 1, 0x00, 0, 0x00);
+
+ WriteDOC(0, docptr, Mplus_FlashControl);
+ DoC_WaitReady(docptr);
+
+ /* Read the manufacturer and device id codes of the flash device through
+ CDSN IO register see Software Requirement 11.4 item 5.*/
+ dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+ dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+
+ mfr = ReadDOC(docptr, Mil_CDSN_IO);
+ if (doc->interleave)
+ dummy = ReadDOC(docptr, Mil_CDSN_IO); /* 2 way interleave */
+
+ id = ReadDOC(docptr, Mil_CDSN_IO);
+ if (doc->interleave)
+ dummy = ReadDOC(docptr, Mil_CDSN_IO); /* 2 way interleave */
+
+ dummy = ReadDOC(docptr, Mplus_LastDataRead);
+ dummy = ReadDOC(docptr, Mplus_LastDataRead);
+
+ /* Disable flash internally */
+ WriteDOC(0, docptr, Mplus_FlashSelect);
+
+ /* No response - return failure */
+ if (mfr == 0xff || mfr == 0)
+ return 0;
+
+ for (i = 0; nand_flash_ids[i].name != NULL; i++) {
+ if (id == nand_flash_ids[i].id) {
+ /* Try to identify manufacturer */
+ for (j = 0; nand_manuf_ids[j].id != 0x0; j++) {
+ if (nand_manuf_ids[j].id == mfr)
+ break;
+ }
+ printk(KERN_INFO "Flash chip found: Manufacturer ID: %2.2X, "
+ "Chip ID: %2.2X (%s:%s)\n", mfr, id,
+ nand_manuf_ids[j].name, nand_flash_ids[i].name);
+ doc->mfr = mfr;
+ doc->id = id;
+ doc->chipshift = ffs((nand_flash_ids[i].chipsize << 20)) - 1;
+ doc->erasesize = nand_flash_ids[i].erasesize << doc->interleave;
+ break;
+ }
+ }
+
+ if (nand_flash_ids[i].name == NULL)
+ return 0;
+ return 1;
+}
+
+/* DoC_ScanChips: Find all NAND chips present in a DiskOnChip, and identify them */
+static void DoC_ScanChips(struct DiskOnChip *this)
+{
+ int floor, chip;
+ int numchips[MAX_FLOORS_MPLUS];
+ int ret;
+
+ this->numchips = 0;
+ this->mfr = 0;
+ this->id = 0;
+
+ /* Work out the intended interleave setting */
+ this->interleave = 0;
+ if (this->ChipID == DOC_ChipID_DocMilPlus32)
+ this->interleave = 1;
+
+ /* Check the ASIC agrees */
+ if ( (this->interleave << 2) !=
+ (ReadDOC(this->virtadr, Mplus_Configuration) & 4)) {
+ u_char conf = ReadDOC(this->virtadr, Mplus_Configuration);
+ printk(KERN_NOTICE "Setting DiskOnChip Millennium Plus interleave to %s\n",
+ this->interleave?"on (16-bit)":"off (8-bit)");
+ conf ^= 4;
+ WriteDOC(conf, this->virtadr, Mplus_Configuration);
+ }
+
+ /* For each floor, find the number of valid chips it contains */
+ for (floor = 0,ret = 1; floor < MAX_FLOORS_MPLUS; floor++) {
+ numchips[floor] = 0;
+ for (chip = 0; chip < MAX_CHIPS_MPLUS && ret != 0; chip++) {
+ ret = DoC_IdentChip(this, floor, chip);
+ if (ret) {
+ numchips[floor]++;
+ this->numchips++;
+ }
+ }
+ }
+ /* If there are none at all that we recognise, bail */
+ if (!this->numchips) {
+ printk("No flash chips recognised.\n");
+ return;
+ }
+
+ /* Allocate an array to hold the information for each chip */
+ this->chips = kmalloc(sizeof(struct Nand) * this->numchips, GFP_KERNEL);
+ if (!this->chips){
+ printk("MTD: No memory for allocating chip info structures\n");
+ return;
+ }
+
+ /* Fill out the chip array with {floor, chipno} for each
+ * detected chip in the device. */
+ for (floor = 0, ret = 0; floor < MAX_FLOORS_MPLUS; floor++) {
+ for (chip = 0 ; chip < numchips[floor] ; chip++) {
+ this->chips[ret].floor = floor;
+ this->chips[ret].chip = chip;
+ this->chips[ret].curadr = 0;
+ this->chips[ret].curmode = 0x50;
+ ret++;
+ }
+ }
+
+ /* Calculate and print the total size of the device */
+ this->totlen = this->numchips * (1 << this->chipshift);
+ printk(KERN_INFO "%d flash chips found. Total DiskOnChip size: %ld MiB\n",
+ this->numchips ,this->totlen >> 20);
+}
+
+static int DoCMilPlus_is_alias(struct DiskOnChip *doc1, struct DiskOnChip *doc2)
+{
+ int tmp1, tmp2, retval;
+
+ if (doc1->physadr == doc2->physadr)
+ return 1;
+
+ /* Use the alias resolution register which was set aside for this
+ * purpose. If it's value is the same on both chips, they might
+ * be the same chip, and we write to one and check for a change in
+ * the other. It's unclear if this register is usuable in the
+ * DoC 2000 (it's in the Millennium docs), but it seems to work. */
+ tmp1 = ReadDOC(doc1->virtadr, Mplus_AliasResolution);
+ tmp2 = ReadDOC(doc2->virtadr, Mplus_AliasResolution);
+ if (tmp1 != tmp2)
+ return 0;
+
+ WriteDOC((tmp1+1) % 0xff, doc1->virtadr, Mplus_AliasResolution);
+ tmp2 = ReadDOC(doc2->virtadr, Mplus_AliasResolution);
+ if (tmp2 == (tmp1+1) % 0xff)
+ retval = 1;
+ else
+ retval = 0;
+
+ /* Restore register contents. May not be necessary, but do it just to
+ * be safe. */
+ WriteDOC(tmp1, doc1->virtadr, Mplus_AliasResolution);
+
+ return retval;
+}
+
+static const char im_name[] = "DoCMilPlus_init";
+
+/* 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 mtd 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.
+ */
+static void DoCMilPlus_init(struct mtd_info *mtd)
+{
+ struct DiskOnChip *this = mtd->priv;
+ struct DiskOnChip *old = NULL;
+
+ /* We must avoid being called twice for the same device. */
+ if (docmilpluslist)
+ old = docmilpluslist->priv;
+
+ while (old) {
+ if (DoCMilPlus_is_alias(this, old)) {
+ printk(KERN_NOTICE "Ignoring DiskOnChip Millennium "
+ "Plus at 0x%lX - already configured\n",
+ this->physadr);
+ iounmap(this->virtadr);
+ kfree(mtd);
+ return;
+ }
+ if (old->nextdoc)
+ old = old->nextdoc->priv;
+ else
+ old = NULL;
+ }
+
+ mtd->name = "DiskOnChip Millennium Plus";
+ printk(KERN_NOTICE "DiskOnChip Millennium Plus found at "
+ "address 0x%lX\n", this->physadr);
+
+ mtd->type = MTD_NANDFLASH;
+ mtd->flags = MTD_CAP_NANDFLASH;
+ mtd->ecctype = MTD_ECC_RS_DiskOnChip;
+ mtd->size = 0;
+
+ mtd->erasesize = 0;
+ mtd->oobblock = 512;
+ mtd->oobsize = 16;
+ mtd->owner = THIS_MODULE;
+ mtd->erase = doc_erase;
+ mtd->point = NULL;
+ mtd->unpoint = NULL;
+ mtd->read = doc_read;
+ mtd->write = doc_write;
+ mtd->read_ecc = doc_read_ecc;
+ mtd->write_ecc = doc_write_ecc;
+ mtd->read_oob = doc_read_oob;
+ mtd->write_oob = doc_write_oob;
+ mtd->sync = NULL;
+
+ this->totlen = 0;
+ this->numchips = 0;
+ this->curfloor = -1;
+ this->curchip = -1;
+
+ /* Ident all the chips present. */
+ DoC_ScanChips(this);
+
+ if (!this->totlen) {
+ kfree(mtd);
+ iounmap(this->virtadr);
+ } else {
+ this->nextdoc = docmilpluslist;
+ docmilpluslist = mtd;
+ mtd->size = this->totlen;
+ mtd->erasesize = this->erasesize;
+ add_mtd_device(mtd);
+ return;
+ }
+}
+
+#if 0
+static int doc_dumpblk(struct mtd_info *mtd, loff_t from)
+{
+ int i;
+ loff_t fofs;
+ struct DiskOnChip *this = mtd->priv;
+ void __iomem * docptr = this->virtadr;
+ struct Nand *mychip = &this->chips[from >> (this->chipshift)];
+ unsigned char *bp, buf[1056];
+ char c[32];
+
+ from &= ~0x3ff;
+
+ /* Don't allow read past end of device */
+ if (from >= this->totlen)
+ return -EINVAL;
+
+ DoC_CheckASIC(docptr);
+
+ /* Find the chip which is to be used and select it */
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(docptr, mychip->floor);
+ DoC_SelectChip(docptr, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(docptr, mychip->chip);
+ }
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
+ WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect);
+
+ /* Reset the chip, see Software Requirement 11.4 item 1. */
+ DoC_Command(docptr, NAND_CMD_RESET, 0);
+ DoC_WaitReady(docptr);
+
+ fofs = from;
+ DoC_Command(docptr, DoC_GetDataOffset(mtd, &fofs), 0);
+ DoC_Address(this, 3, fofs, 0, 0x00);
+ WriteDOC(0, docptr, Mplus_FlashControl);
+ DoC_WaitReady(docptr);
+
+ /* disable the ECC engine */
+ WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
+
+ ReadDOC(docptr, Mplus_ReadPipeInit);
+ ReadDOC(docptr, Mplus_ReadPipeInit);
+
+ /* Read the data via the internal pipeline through CDSN IO
+ register, see Pipelined Read Operations 11.3 */
+ MemReadDOC(docptr, buf, 1054);
+ buf[1054] = ReadDOC(docptr, Mplus_LastDataRead);
+ buf[1055] = ReadDOC(docptr, Mplus_LastDataRead);
+
+ memset(&c[0], 0, sizeof(c));
+ printk("DUMP OFFSET=%x:\n", (int)from);
+
+ for (i = 0, bp = &buf[0]; (i < 1056); i++) {
+ if ((i % 16) == 0)
+ printk("%08x: ", i);
+ printk(" %02x", *bp);
+ c[(i & 0xf)] = ((*bp >= 0x20) && (*bp <= 0x7f)) ? *bp : '.';
+ bp++;
+ if (((i + 1) % 16) == 0)
+ printk(" %s\n", c);
+ }
+ printk("\n");
+
+ /* Disable flash internally */
+ WriteDOC(0, docptr, Mplus_FlashSelect);
+
+ return 0;
+}
+#endif
+
+static int doc_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ /* Just a special case of doc_read_ecc */
+ return doc_read_ecc(mtd, from, len, retlen, buf, NULL, NULL);
+}
+
+static int doc_read_ecc(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf, u_char *eccbuf,
+ struct nand_oobinfo *oobsel)
+{
+ int ret, i;
+ volatile char dummy;
+ loff_t fofs;
+ unsigned char syndrome[6];
+ struct DiskOnChip *this = mtd->priv;
+ void __iomem * docptr = this->virtadr;
+ struct Nand *mychip = &this->chips[from >> (this->chipshift)];
+
+ /* Don't allow read past end of device */
+ if (from >= this->totlen)
+ return -EINVAL;
+
+ /* Don't allow a single read to cross a 512-byte block boundary */
+ if (from + len > ((from | 0x1ff) + 1))
+ len = ((from | 0x1ff) + 1) - from;
+
+ DoC_CheckASIC(docptr);
+
+ /* Find the chip which is to be used and select it */
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(docptr, mychip->floor);
+ DoC_SelectChip(docptr, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(docptr, mychip->chip);
+ }
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
+ WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect);
+
+ /* Reset the chip, see Software Requirement 11.4 item 1. */
+ DoC_Command(docptr, NAND_CMD_RESET, 0);
+ DoC_WaitReady(docptr);
+
+ fofs = from;
+ DoC_Command(docptr, DoC_GetDataOffset(mtd, &fofs), 0);
+ DoC_Address(this, 3, fofs, 0, 0x00);
+ WriteDOC(0, docptr, Mplus_FlashControl);
+ DoC_WaitReady(docptr);
+
+ if (eccbuf) {
+ /* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/
+ WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
+ WriteDOC(DOC_ECC_EN, docptr, Mplus_ECCConf);
+ } else {
+ /* disable the ECC engine */
+ WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
+ }
+
+ /* Let the caller know we completed it */
+ *retlen = len;
+ ret = 0;
+
+ ReadDOC(docptr, Mplus_ReadPipeInit);
+ ReadDOC(docptr, Mplus_ReadPipeInit);
+
+ if (eccbuf) {
+ /* Read the data via the internal pipeline through CDSN IO
+ register, see Pipelined Read Operations 11.3 */
+ MemReadDOC(docptr, buf, len);
+
+ /* Read the ECC data following raw data */
+ MemReadDOC(docptr, eccbuf, 4);
+ eccbuf[4] = ReadDOC(docptr, Mplus_LastDataRead);
+ eccbuf[5] = ReadDOC(docptr, Mplus_LastDataRead);
+
+ /* Flush the pipeline */
+ dummy = ReadDOC(docptr, Mplus_ECCConf);
+ dummy = ReadDOC(docptr, Mplus_ECCConf);
+
+ /* Check the ECC Status */
+ if (ReadDOC(docptr, Mplus_ECCConf) & 0x80) {
+ int nb_errors;
+ /* There was an ECC error */
+#ifdef ECC_DEBUG
+ printk("DiskOnChip ECC Error: Read at %lx\n", (long)from);
+#endif
+ /* Read the ECC syndrom through the DiskOnChip ECC logic.
+ These syndrome will be all ZERO when there is no error */
+ for (i = 0; i < 6; i++)
+ syndrome[i] = ReadDOC(docptr, Mplus_ECCSyndrome0 + i);
+
+ nb_errors = doc_decode_ecc(buf, syndrome);
+#ifdef ECC_DEBUG
+ printk("ECC Errors corrected: %x\n", nb_errors);
+#endif
+ if (nb_errors < 0) {
+ /* We return error, but have actually done the read. Not that
+ this can be told to user-space, via sys_read(), but at least
+ MTD-aware stuff can know about it by checking *retlen */
+#ifdef ECC_DEBUG
+ printk("%s(%d): Millennium Plus ECC error (from=0x%x:\n",
+ __FILE__, __LINE__, (int)from);
+ printk(" syndrome= %02x:%02x:%02x:%02x:%02x:"
+ "%02x\n",
+ syndrome[0], syndrome[1], syndrome[2],
+ syndrome[3], syndrome[4], syndrome[5]);
+ printk(" eccbuf= %02x:%02x:%02x:%02x:%02x:"
+ "%02x\n",
+ eccbuf[0], eccbuf[1], eccbuf[2],
+ eccbuf[3], eccbuf[4], eccbuf[5]);
+#endif
+ ret = -EIO;
+ }
+ }
+
+#ifdef PSYCHO_DEBUG
+ printk("ECC DATA at %lx: %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
+ (long)from, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3],
+ eccbuf[4], eccbuf[5]);
+#endif
+
+ /* disable the ECC engine */
+ WriteDOC(DOC_ECC_DIS, docptr , Mplus_ECCConf);
+ } else {
+ /* Read the data via the internal pipeline through CDSN IO
+ register, see Pipelined Read Operations 11.3 */
+ MemReadDOC(docptr, buf, len-2);
+ buf[len-2] = ReadDOC(docptr, Mplus_LastDataRead);
+ buf[len-1] = ReadDOC(docptr, Mplus_LastDataRead);
+ }
+
+ /* Disable flash internally */
+ WriteDOC(0, docptr, Mplus_FlashSelect);
+
+ return ret;
+}
+
+static int doc_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ char eccbuf[6];
+ return doc_write_ecc(mtd, to, len, retlen, buf, eccbuf, NULL);
+}
+
+static int doc_write_ecc(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf, u_char *eccbuf,
+ struct nand_oobinfo *oobsel)
+{
+ int i, before, ret = 0;
+ loff_t fto;
+ volatile char dummy;
+ struct DiskOnChip *this = mtd->priv;
+ void __iomem * docptr = this->virtadr;
+ struct Nand *mychip = &this->chips[to >> (this->chipshift)];
+
+ /* Don't allow write past end of device */
+ if (to >= this->totlen)
+ return -EINVAL;
+
+ /* Don't allow writes which aren't exactly one block (512 bytes) */
+ if ((to & 0x1ff) || (len != 0x200))
+ return -EINVAL;
+
+ /* Determine position of OOB flags, before or after data */
+ before = (this->interleave && (to & 0x200));
+
+ DoC_CheckASIC(docptr);
+
+ /* Find the chip which is to be used and select it */
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(docptr, mychip->floor);
+ DoC_SelectChip(docptr, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(docptr, mychip->chip);
+ }
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
+ WriteDOC(DOC_FLASH_CE, docptr, Mplus_FlashSelect);
+
+ /* Reset the chip, see Software Requirement 11.4 item 1. */
+ DoC_Command(docptr, NAND_CMD_RESET, 0);
+ DoC_WaitReady(docptr);
+
+ /* Set device to appropriate plane of flash */
+ fto = to;
+ WriteDOC(DoC_GetDataOffset(mtd, &fto), docptr, Mplus_FlashCmd);
+
+ /* On interleaved devices the flags for 2nd half 512 are before data */
+ if (eccbuf && before)
+ fto -= 2;
+
+ /* issue the Serial Data In command to initial the Page Program process */
+ DoC_Command(docptr, NAND_CMD_SEQIN, 0x00);
+ DoC_Address(this, 3, fto, 0x00, 0x00);
+
+ /* Disable the ECC engine */
+ WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
+
+ if (eccbuf) {
+ if (before) {
+ /* Write the block status BLOCK_USED (0x5555) */
+ WriteDOC(0x55, docptr, Mil_CDSN_IO);
+ WriteDOC(0x55, docptr, Mil_CDSN_IO);
+ }
+
+ /* init the ECC engine, see Reed-Solomon EDC/ECC 11.1 .*/
+ WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, Mplus_ECCConf);
+ }
+
+ MemWriteDOC(docptr, (unsigned char *) buf, len);
+
+ if (eccbuf) {
+ /* Write ECC data to flash, the ECC info is generated by
+ the DiskOnChip ECC logic see Reed-Solomon EDC/ECC 11.1 */
+ DoC_Delay(docptr, 3);
+
+ /* Read the ECC data through the DiskOnChip ECC logic */
+ for (i = 0; i < 6; i++)
+ eccbuf[i] = ReadDOC(docptr, Mplus_ECCSyndrome0 + i);
+
+ /* disable the ECC engine */
+ WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf);
+
+ /* Write the ECC data to flash */
+ MemWriteDOC(docptr, eccbuf, 6);
+
+ if (!before) {
+ /* Write the block status BLOCK_USED (0x5555) */
+ WriteDOC(0x55, docptr, Mil_CDSN_IO+6);
+ WriteDOC(0x55, docptr, Mil_CDSN_IO+7);
+ }
+
+#ifdef PSYCHO_DEBUG
+ printk("OOB data at %lx is %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n",
+ (long) to, eccbuf[0], eccbuf[1], eccbuf[2], eccbuf[3],
+ eccbuf[4], eccbuf[5]);
+#endif
+ }
+
+ WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
+ WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
+
+ /* Commit the Page Program command and wait for ready
+ see Software Requirement 11.4 item 1.*/
+ DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00);
+ DoC_WaitReady(docptr);
+
+ /* Read the status of the flash device through CDSN IO register
+ see Software Requirement 11.4 item 5.*/
+ DoC_Command(docptr, NAND_CMD_STATUS, 0);
+ dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+ dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+ DoC_Delay(docptr, 2);
+ if ((dummy = ReadDOC(docptr, Mplus_LastDataRead)) & 1) {
+ printk("MTD: Error 0x%x programming at 0x%x\n", dummy, (int)to);
+ /* Error in programming
+ FIXME: implement Bad Block Replacement (in nftl.c ??) */
+ *retlen = 0;
+ ret = -EIO;
+ }
+ dummy = ReadDOC(docptr, Mplus_LastDataRead);
+
+ /* Disable flash internally */
+ WriteDOC(0, docptr, Mplus_FlashSelect);
+
+ /* Let the caller know we completed it */
+ *retlen = len;
+
+ return ret;
+}
+
+static int doc_read_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ loff_t fofs, base;
+ struct DiskOnChip *this = mtd->priv;
+ void __iomem * docptr = this->virtadr;
+ struct Nand *mychip = &this->chips[ofs >> this->chipshift];
+ size_t i, size, got, want;
+
+ DoC_CheckASIC(docptr);
+
+ /* Find the chip which is to be used and select it */
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(docptr, mychip->floor);
+ DoC_SelectChip(docptr, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(docptr, mychip->chip);
+ }
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
+ WriteDOC((DOC_FLASH_CE | DOC_FLASH_WP), docptr, Mplus_FlashSelect);
+
+ /* disable the ECC engine */
+ WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
+ DoC_WaitReady(docptr);
+
+ /* Maximum of 16 bytes in the OOB region, so limit read to that */
+ if (len > 16)
+ len = 16;
+ got = 0;
+ want = len;
+
+ for (i = 0; ((i < 3) && (want > 0)); i++) {
+ /* Figure out which region we are accessing... */
+ fofs = ofs;
+ base = ofs & 0xf;
+ if (!this->interleave) {
+ DoC_Command(docptr, NAND_CMD_READOOB, 0);
+ size = 16 - base;
+ } else if (base < 6) {
+ DoC_Command(docptr, DoC_GetECCOffset(mtd, &fofs), 0);
+ size = 6 - base;
+ } else if (base < 8) {
+ DoC_Command(docptr, DoC_GetFlagsOffset(mtd, &fofs), 0);
+ size = 8 - base;
+ } else {
+ DoC_Command(docptr, DoC_GetHdrOffset(mtd, &fofs), 0);
+ size = 16 - base;
+ }
+ if (size > want)
+ size = want;
+
+ /* Issue read command */
+ DoC_Address(this, 3, fofs, 0, 0x00);
+ WriteDOC(0, docptr, Mplus_FlashControl);
+ DoC_WaitReady(docptr);
+
+ ReadDOC(docptr, Mplus_ReadPipeInit);
+ ReadDOC(docptr, Mplus_ReadPipeInit);
+ MemReadDOC(docptr, &buf[got], size - 2);
+ buf[got + size - 2] = ReadDOC(docptr, Mplus_LastDataRead);
+ buf[got + size - 1] = ReadDOC(docptr, Mplus_LastDataRead);
+
+ ofs += size;
+ got += size;
+ want -= size;
+ }
+
+ /* Disable flash internally */
+ WriteDOC(0, docptr, Mplus_FlashSelect);
+
+ *retlen = len;
+ return 0;
+}
+
+static int doc_write_oob(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ volatile char dummy;
+ loff_t fofs, base;
+ struct DiskOnChip *this = mtd->priv;
+ void __iomem * docptr = this->virtadr;
+ struct Nand *mychip = &this->chips[ofs >> this->chipshift];
+ size_t i, size, got, want;
+ int ret = 0;
+
+ DoC_CheckASIC(docptr);
+
+ /* Find the chip which is to be used and select it */
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(docptr, mychip->floor);
+ DoC_SelectChip(docptr, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(docptr, mychip->chip);
+ }
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
+ WriteDOC(DOC_FLASH_CE, docptr, Mplus_FlashSelect);
+
+
+ /* Maximum of 16 bytes in the OOB region, so limit write to that */
+ if (len > 16)
+ len = 16;
+ got = 0;
+ want = len;
+
+ for (i = 0; ((i < 3) && (want > 0)); i++) {
+ /* Reset the chip, see Software Requirement 11.4 item 1. */
+ DoC_Command(docptr, NAND_CMD_RESET, 0);
+ DoC_WaitReady(docptr);
+
+ /* Figure out which region we are accessing... */
+ fofs = ofs;
+ base = ofs & 0x0f;
+ if (!this->interleave) {
+ WriteDOC(NAND_CMD_READOOB, docptr, Mplus_FlashCmd);
+ size = 16 - base;
+ } else if (base < 6) {
+ WriteDOC(DoC_GetECCOffset(mtd, &fofs), docptr, Mplus_FlashCmd);
+ size = 6 - base;
+ } else if (base < 8) {
+ WriteDOC(DoC_GetFlagsOffset(mtd, &fofs), docptr, Mplus_FlashCmd);
+ size = 8 - base;
+ } else {
+ WriteDOC(DoC_GetHdrOffset(mtd, &fofs), docptr, Mplus_FlashCmd);
+ size = 16 - base;
+ }
+ if (size > want)
+ size = want;
+
+ /* Issue the Serial Data In command to initial the Page Program process */
+ DoC_Command(docptr, NAND_CMD_SEQIN, 0x00);
+ DoC_Address(this, 3, fofs, 0, 0x00);
+
+ /* Disable the ECC engine */
+ WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
+
+ /* Write the data via the internal pipeline through CDSN IO
+ register, see Pipelined Write Operations 11.2 */
+ MemWriteDOC(docptr, (unsigned char *) &buf[got], size);
+ WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
+ WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
+
+ /* Commit the Page Program command and wait for ready
+ see Software Requirement 11.4 item 1.*/
+ DoC_Command(docptr, NAND_CMD_PAGEPROG, 0x00);
+ DoC_WaitReady(docptr);
+
+ /* Read the status of the flash device through CDSN IO register
+ see Software Requirement 11.4 item 5.*/
+ DoC_Command(docptr, NAND_CMD_STATUS, 0x00);
+ dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+ dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+ DoC_Delay(docptr, 2);
+ if ((dummy = ReadDOC(docptr, Mplus_LastDataRead)) & 1) {
+ printk("MTD: Error 0x%x programming oob at 0x%x\n",
+ dummy, (int)ofs);
+ /* FIXME: implement Bad Block Replacement */
+ *retlen = 0;
+ ret = -EIO;
+ }
+ dummy = ReadDOC(docptr, Mplus_LastDataRead);
+
+ ofs += size;
+ got += size;
+ want -= size;
+ }
+
+ /* Disable flash internally */
+ WriteDOC(0, docptr, Mplus_FlashSelect);
+
+ *retlen = len;
+ return ret;
+}
+
+int doc_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ volatile char dummy;
+ struct DiskOnChip *this = mtd->priv;
+ __u32 ofs = instr->addr;
+ __u32 len = instr->len;
+ void __iomem * docptr = this->virtadr;
+ struct Nand *mychip = &this->chips[ofs >> this->chipshift];
+
+ DoC_CheckASIC(docptr);
+
+ if (len != mtd->erasesize)
+ printk(KERN_WARNING "MTD: Erase not right size (%x != %x)n",
+ len, mtd->erasesize);
+
+ /* Find the chip which is to be used and select it */
+ if (this->curfloor != mychip->floor) {
+ DoC_SelectFloor(docptr, mychip->floor);
+ DoC_SelectChip(docptr, mychip->chip);
+ } else if (this->curchip != mychip->chip) {
+ DoC_SelectChip(docptr, mychip->chip);
+ }
+ this->curfloor = mychip->floor;
+ this->curchip = mychip->chip;
+
+ instr->state = MTD_ERASE_PENDING;
+
+ /* Millennium Plus bus cycle sequence as per figure 2, section 2.4 */
+ WriteDOC(DOC_FLASH_CE, docptr, Mplus_FlashSelect);
+
+ DoC_Command(docptr, NAND_CMD_RESET, 0x00);
+ DoC_WaitReady(docptr);
+
+ DoC_Command(docptr, NAND_CMD_ERASE1, 0);
+ DoC_Address(this, 2, ofs, 0, 0x00);
+ DoC_Command(docptr, NAND_CMD_ERASE2, 0);
+ DoC_WaitReady(docptr);
+ instr->state = MTD_ERASING;
+
+ /* Read the status of the flash device through CDSN IO register
+ see Software Requirement 11.4 item 5. */
+ DoC_Command(docptr, NAND_CMD_STATUS, 0);
+ dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+ dummy = ReadDOC(docptr, Mplus_ReadPipeInit);
+ if ((dummy = ReadDOC(docptr, Mplus_LastDataRead)) & 1) {
+ printk("MTD: Error 0x%x erasing at 0x%x\n", dummy, ofs);
+ /* FIXME: implement Bad Block Replacement (in nftl.c ??) */
+ instr->state = MTD_ERASE_FAILED;
+ } else {
+ instr->state = MTD_ERASE_DONE;
+ }
+ dummy = ReadDOC(docptr, Mplus_LastDataRead);
+
+ /* Disable flash internally */
+ WriteDOC(0, docptr, Mplus_FlashSelect);
+
+ mtd_erase_callback(instr);
+
+ return 0;
+}
+
+/****************************************************************************
+ *
+ * Module stuff
+ *
+ ****************************************************************************/
+
+static int __init init_doc2001plus(void)
+{
+ inter_module_register(im_name, THIS_MODULE, &DoCMilPlus_init);
+ return 0;
+}
+
+static void __exit cleanup_doc2001plus(void)
+{
+ struct mtd_info *mtd;
+ struct DiskOnChip *this;
+
+ while ((mtd=docmilpluslist)) {
+ this = mtd->priv;
+ docmilpluslist = this->nextdoc;
+
+ del_mtd_device(mtd);
+
+ iounmap(this->virtadr);
+ kfree(this->chips);
+ kfree(mtd);
+ }
+ inter_module_unregister(im_name);
+}
+
+module_exit(cleanup_doc2001plus);
+module_init(init_doc2001plus);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Greg Ungerer <gerg@snapgear.com> et al.");
+MODULE_DESCRIPTION("Driver for DiskOnChip Millennium Plus");
diff --git a/drivers/mtd/devices/docecc.c b/drivers/mtd/devices/docecc.c
new file mode 100644
index 000000000000..933877ff4d88
--- /dev/null
+++ b/drivers/mtd/devices/docecc.c
@@ -0,0 +1,526 @@
+/*
+ * ECC algorithm for M-systems disk on chip. We use the excellent Reed
+ * Solmon code of Phil Karn (karn@ka9q.ampr.org) available under the
+ * GNU GPL License. The rest is simply to convert the disk on chip
+ * syndrom into a standard syndom.
+ *
+ * Author: Fabrice Bellard (fabrice.bellard@netgem.com)
+ * Copyright (C) 2000 Netgem S.A.
+ *
+ * $Id: docecc.c,v 1.5 2003/05/21 15:15:06 dwmw2 Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/types.h>
+
+#include <linux/mtd/compatmac.h> /* for min() in older kernels */
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/doc2000.h>
+
+/* need to undef it (from asm/termbits.h) */
+#undef B0
+
+#define MM 10 /* Symbol size in bits */
+#define KK (1023-4) /* Number of data symbols per block */
+#define B0 510 /* First root of generator polynomial, alpha form */
+#define PRIM 1 /* power of alpha used to generate roots of generator poly */
+#define NN ((1 << MM) - 1)
+
+typedef unsigned short dtype;
+
+/* 1+x^3+x^10 */
+static const int Pp[MM+1] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1 };
+
+/* This defines the type used to store an element of the Galois Field
+ * used by the code. Make sure this is something larger than a char if
+ * if anything larger than GF(256) is used.
+ *
+ * Note: unsigned char will work up to GF(256) but int seems to run
+ * faster on the Pentium.
+ */
+typedef int gf;
+
+/* No legal value in index form represents zero, so
+ * we need a special value for this purpose
+ */
+#define A0 (NN)
+
+/* Compute x % NN, where NN is 2**MM - 1,
+ * without a slow divide
+ */
+static inline gf
+modnn(int x)
+{
+ while (x >= NN) {
+ x -= NN;
+ x = (x >> MM) + (x & NN);
+ }
+ return x;
+}
+
+#define CLEAR(a,n) {\
+int ci;\
+for(ci=(n)-1;ci >=0;ci--)\
+(a)[ci] = 0;\
+}
+
+#define COPY(a,b,n) {\
+int ci;\
+for(ci=(n)-1;ci >=0;ci--)\
+(a)[ci] = (b)[ci];\
+}
+
+#define COPYDOWN(a,b,n) {\
+int ci;\
+for(ci=(n)-1;ci >=0;ci--)\
+(a)[ci] = (b)[ci];\
+}
+
+#define Ldec 1
+
+/* generate GF(2**m) from the irreducible polynomial p(X) in Pp[0]..Pp[m]
+ lookup tables: index->polynomial form alpha_to[] contains j=alpha**i;
+ polynomial form -> index form index_of[j=alpha**i] = i
+ alpha=2 is the primitive element of GF(2**m)
+ HARI's COMMENT: (4/13/94) alpha_to[] can be used as follows:
+ Let @ represent the primitive element commonly called "alpha" that
+ is the root of the primitive polynomial p(x). Then in GF(2^m), for any
+ 0 <= i <= 2^m-2,
+ @^i = a(0) + a(1) @ + a(2) @^2 + ... + a(m-1) @^(m-1)
+ where the binary vector (a(0),a(1),a(2),...,a(m-1)) is the representation
+ of the integer "alpha_to[i]" with a(0) being the LSB and a(m-1) the MSB. Thus for
+ example the polynomial representation of @^5 would be given by the binary
+ representation of the integer "alpha_to[5]".
+ Similarily, index_of[] can be used as follows:
+ As above, let @ represent the primitive element of GF(2^m) that is
+ the root of the primitive polynomial p(x). In order to find the power
+ of @ (alpha) that has the polynomial representation
+ a(0) + a(1) @ + a(2) @^2 + ... + a(m-1) @^(m-1)
+ we consider the integer "i" whose binary representation with a(0) being LSB
+ and a(m-1) MSB is (a(0),a(1),...,a(m-1)) and locate the entry
+ "index_of[i]". Now, @^index_of[i] is that element whose polynomial
+ representation is (a(0),a(1),a(2),...,a(m-1)).
+ NOTE:
+ The element alpha_to[2^m-1] = 0 always signifying that the
+ representation of "@^infinity" = 0 is (0,0,0,...,0).
+ Similarily, the element index_of[0] = A0 always signifying
+ that the power of alpha which has the polynomial representation
+ (0,0,...,0) is "infinity".
+
+*/
+
+static void
+generate_gf(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1])
+{
+ register int i, mask;
+
+ mask = 1;
+ Alpha_to[MM] = 0;
+ for (i = 0; i < MM; i++) {
+ Alpha_to[i] = mask;
+ Index_of[Alpha_to[i]] = i;
+ /* If Pp[i] == 1 then, term @^i occurs in poly-repr of @^MM */
+ if (Pp[i] != 0)
+ Alpha_to[MM] ^= mask; /* Bit-wise EXOR operation */
+ mask <<= 1; /* single left-shift */
+ }
+ Index_of[Alpha_to[MM]] = MM;
+ /*
+ * Have obtained poly-repr of @^MM. Poly-repr of @^(i+1) is given by
+ * poly-repr of @^i shifted left one-bit and accounting for any @^MM
+ * term that may occur when poly-repr of @^i is shifted.
+ */
+ mask >>= 1;
+ for (i = MM + 1; i < NN; i++) {
+ if (Alpha_to[i - 1] >= mask)
+ Alpha_to[i] = Alpha_to[MM] ^ ((Alpha_to[i - 1] ^ mask) << 1);
+ else
+ Alpha_to[i] = Alpha_to[i - 1] << 1;
+ Index_of[Alpha_to[i]] = i;
+ }
+ Index_of[0] = A0;
+ Alpha_to[NN] = 0;
+}
+
+/*
+ * Performs ERRORS+ERASURES decoding of RS codes. bb[] is the content
+ * of the feedback shift register after having processed the data and
+ * the ECC.
+ *
+ * Return number of symbols corrected, or -1 if codeword is illegal
+ * or uncorrectable. If eras_pos is non-null, the detected error locations
+ * are written back. NOTE! This array must be at least NN-KK elements long.
+ * The corrected data are written in eras_val[]. They must be xor with the data
+ * to retrieve the correct data : data[erase_pos[i]] ^= erase_val[i] .
+ *
+ * First "no_eras" erasures are declared by the calling program. Then, the
+ * maximum # of errors correctable is t_after_eras = floor((NN-KK-no_eras)/2).
+ * If the number of channel errors is not greater than "t_after_eras" the
+ * transmitted codeword will be recovered. Details of algorithm can be found
+ * in R. Blahut's "Theory ... of Error-Correcting Codes".
+
+ * Warning: the eras_pos[] array must not contain duplicate entries; decoder failure
+ * will result. The decoder *could* check for this condition, but it would involve
+ * extra time on every decoding operation.
+ * */
+static int
+eras_dec_rs(dtype Alpha_to[NN + 1], dtype Index_of[NN + 1],
+ gf bb[NN - KK + 1], gf eras_val[NN-KK], int eras_pos[NN-KK],
+ int no_eras)
+{
+ int deg_lambda, el, deg_omega;
+ int i, j, r,k;
+ gf u,q,tmp,num1,num2,den,discr_r;
+ gf lambda[NN-KK + 1], s[NN-KK + 1]; /* Err+Eras Locator poly
+ * and syndrome poly */
+ gf b[NN-KK + 1], t[NN-KK + 1], omega[NN-KK + 1];
+ gf root[NN-KK], reg[NN-KK + 1], loc[NN-KK];
+ int syn_error, count;
+
+ syn_error = 0;
+ for(i=0;i<NN-KK;i++)
+ syn_error |= bb[i];
+
+ if (!syn_error) {
+ /* if remainder is zero, data[] is a codeword and there are no
+ * errors to correct. So return data[] unmodified
+ */
+ count = 0;
+ goto finish;
+ }
+
+ for(i=1;i<=NN-KK;i++){
+ s[i] = bb[0];
+ }
+ for(j=1;j<NN-KK;j++){
+ if(bb[j] == 0)
+ continue;
+ tmp = Index_of[bb[j]];
+
+ for(i=1;i<=NN-KK;i++)
+ s[i] ^= Alpha_to[modnn(tmp + (B0+i-1)*PRIM*j)];
+ }
+
+ /* undo the feedback register implicit multiplication and convert
+ syndromes to index form */
+
+ for(i=1;i<=NN-KK;i++) {
+ tmp = Index_of[s[i]];
+ if (tmp != A0)
+ tmp = modnn(tmp + 2 * KK * (B0+i-1)*PRIM);
+ s[i] = tmp;
+ }
+
+ CLEAR(&lambda[1],NN-KK);
+ lambda[0] = 1;
+
+ if (no_eras > 0) {
+ /* Init lambda to be the erasure locator polynomial */
+ lambda[1] = Alpha_to[modnn(PRIM * eras_pos[0])];
+ for (i = 1; i < no_eras; i++) {
+ u = modnn(PRIM*eras_pos[i]);
+ for (j = i+1; j > 0; j--) {
+ tmp = Index_of[lambda[j - 1]];
+ if(tmp != A0)
+ lambda[j] ^= Alpha_to[modnn(u + tmp)];
+ }
+ }
+#if DEBUG >= 1
+ /* Test code that verifies the erasure locator polynomial just constructed
+ Needed only for decoder debugging. */
+
+ /* find roots of the erasure location polynomial */
+ for(i=1;i<=no_eras;i++)
+ reg[i] = Index_of[lambda[i]];
+ count = 0;
+ for (i = 1,k=NN-Ldec; i <= NN; i++,k = modnn(NN+k-Ldec)) {
+ q = 1;
+ for (j = 1; j <= no_eras; j++)
+ if (reg[j] != A0) {
+ reg[j] = modnn(reg[j] + j);
+ q ^= Alpha_to[reg[j]];
+ }
+ if (q != 0)
+ continue;
+ /* store root and error location number indices */
+ root[count] = i;
+ loc[count] = k;
+ count++;
+ }
+ if (count != no_eras) {
+ printf("\n lambda(x) is WRONG\n");
+ count = -1;
+ goto finish;
+ }
+#if DEBUG >= 2
+ printf("\n Erasure positions as determined by roots of Eras Loc Poly:\n");
+ for (i = 0; i < count; i++)
+ printf("%d ", loc[i]);
+ printf("\n");
+#endif
+#endif
+ }
+ for(i=0;i<NN-KK+1;i++)
+ b[i] = Index_of[lambda[i]];
+
+ /*
+ * Begin Berlekamp-Massey algorithm to determine error+erasure
+ * locator polynomial
+ */
+ r = no_eras;
+ el = no_eras;
+ while (++r <= NN-KK) { /* r is the step number */
+ /* Compute discrepancy at the r-th step in poly-form */
+ discr_r = 0;
+ for (i = 0; i < r; i++){
+ if ((lambda[i] != 0) && (s[r - i] != A0)) {
+ discr_r ^= Alpha_to[modnn(Index_of[lambda[i]] + s[r - i])];
+ }
+ }
+ discr_r = Index_of[discr_r]; /* Index form */
+ if (discr_r == A0) {
+ /* 2 lines below: B(x) <-- x*B(x) */
+ COPYDOWN(&b[1],b,NN-KK);
+ b[0] = A0;
+ } else {
+ /* 7 lines below: T(x) <-- lambda(x) - discr_r*x*b(x) */
+ t[0] = lambda[0];
+ for (i = 0 ; i < NN-KK; i++) {
+ if(b[i] != A0)
+ t[i+1] = lambda[i+1] ^ Alpha_to[modnn(discr_r + b[i])];
+ else
+ t[i+1] = lambda[i+1];
+ }
+ if (2 * el <= r + no_eras - 1) {
+ el = r + no_eras - el;
+ /*
+ * 2 lines below: B(x) <-- inv(discr_r) *
+ * lambda(x)
+ */
+ for (i = 0; i <= NN-KK; i++)
+ b[i] = (lambda[i] == 0) ? A0 : modnn(Index_of[lambda[i]] - discr_r + NN);
+ } else {
+ /* 2 lines below: B(x) <-- x*B(x) */
+ COPYDOWN(&b[1],b,NN-KK);
+ b[0] = A0;
+ }
+ COPY(lambda,t,NN-KK+1);
+ }
+ }
+
+ /* Convert lambda to index form and compute deg(lambda(x)) */
+ deg_lambda = 0;
+ for(i=0;i<NN-KK+1;i++){
+ lambda[i] = Index_of[lambda[i]];
+ if(lambda[i] != A0)
+ deg_lambda = i;
+ }
+ /*
+ * Find roots of the error+erasure locator polynomial by Chien
+ * Search
+ */
+ COPY(&reg[1],&lambda[1],NN-KK);
+ count = 0; /* Number of roots of lambda(x) */
+ for (i = 1,k=NN-Ldec; i <= NN; i++,k = modnn(NN+k-Ldec)) {
+ q = 1;
+ for (j = deg_lambda; j > 0; j--){
+ if (reg[j] != A0) {
+ reg[j] = modnn(reg[j] + j);
+ q ^= Alpha_to[reg[j]];
+ }
+ }
+ if (q != 0)
+ continue;
+ /* store root (index-form) and error location number */
+ root[count] = i;
+ loc[count] = k;
+ /* If we've already found max possible roots,
+ * abort the search to save time
+ */
+ if(++count == deg_lambda)
+ break;
+ }
+ if (deg_lambda != count) {
+ /*
+ * deg(lambda) unequal to number of roots => uncorrectable
+ * error detected
+ */
+ count = -1;
+ goto finish;
+ }
+ /*
+ * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo
+ * x**(NN-KK)). in index form. Also find deg(omega).
+ */
+ deg_omega = 0;
+ for (i = 0; i < NN-KK;i++){
+ tmp = 0;
+ j = (deg_lambda < i) ? deg_lambda : i;
+ for(;j >= 0; j--){
+ if ((s[i + 1 - j] != A0) && (lambda[j] != A0))
+ tmp ^= Alpha_to[modnn(s[i + 1 - j] + lambda[j])];
+ }
+ if(tmp != 0)
+ deg_omega = i;
+ omega[i] = Index_of[tmp];
+ }
+ omega[NN-KK] = A0;
+
+ /*
+ * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 =
+ * inv(X(l))**(B0-1) and den = lambda_pr(inv(X(l))) all in poly-form
+ */
+ for (j = count-1; j >=0; j--) {
+ num1 = 0;
+ for (i = deg_omega; i >= 0; i--) {
+ if (omega[i] != A0)
+ num1 ^= Alpha_to[modnn(omega[i] + i * root[j])];
+ }
+ num2 = Alpha_to[modnn(root[j] * (B0 - 1) + NN)];
+ den = 0;
+
+ /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */
+ for (i = min(deg_lambda,NN-KK-1) & ~1; i >= 0; i -=2) {
+ if(lambda[i+1] != A0)
+ den ^= Alpha_to[modnn(lambda[i+1] + i * root[j])];
+ }
+ if (den == 0) {
+#if DEBUG >= 1
+ printf("\n ERROR: denominator = 0\n");
+#endif
+ /* Convert to dual- basis */
+ count = -1;
+ goto finish;
+ }
+ /* Apply error to data */
+ if (num1 != 0) {
+ eras_val[j] = Alpha_to[modnn(Index_of[num1] + Index_of[num2] + NN - Index_of[den])];
+ } else {
+ eras_val[j] = 0;
+ }
+ }
+ finish:
+ for(i=0;i<count;i++)
+ eras_pos[i] = loc[i];
+ return count;
+}
+
+/***************************************************************************/
+/* The DOC specific code begins here */
+
+#define SECTOR_SIZE 512
+/* The sector bytes are packed into NB_DATA MM bits words */
+#define NB_DATA (((SECTOR_SIZE + 1) * 8 + 6) / MM)
+
+/*
+ * Correct the errors in 'sector[]' by using 'ecc1[]' which is the
+ * content of the feedback shift register applyied to the sector and
+ * the ECC. Return the number of errors corrected (and correct them in
+ * sector), or -1 if error
+ */
+int doc_decode_ecc(unsigned char sector[SECTOR_SIZE], unsigned char ecc1[6])
+{
+ int parity, i, nb_errors;
+ gf bb[NN - KK + 1];
+ gf error_val[NN-KK];
+ int error_pos[NN-KK], pos, bitpos, index, val;
+ dtype *Alpha_to, *Index_of;
+
+ /* init log and exp tables here to save memory. However, it is slower */
+ Alpha_to = kmalloc((NN + 1) * sizeof(dtype), GFP_KERNEL);
+ if (!Alpha_to)
+ return -1;
+
+ Index_of = kmalloc((NN + 1) * sizeof(dtype), GFP_KERNEL);
+ if (!Index_of) {
+ kfree(Alpha_to);
+ return -1;
+ }
+
+ generate_gf(Alpha_to, Index_of);
+
+ parity = ecc1[1];
+
+ bb[0] = (ecc1[4] & 0xff) | ((ecc1[5] & 0x03) << 8);
+ bb[1] = ((ecc1[5] & 0xfc) >> 2) | ((ecc1[2] & 0x0f) << 6);
+ bb[2] = ((ecc1[2] & 0xf0) >> 4) | ((ecc1[3] & 0x3f) << 4);
+ bb[3] = ((ecc1[3] & 0xc0) >> 6) | ((ecc1[0] & 0xff) << 2);
+
+ nb_errors = eras_dec_rs(Alpha_to, Index_of, bb,
+ error_val, error_pos, 0);
+ if (nb_errors <= 0)
+ goto the_end;
+
+ /* correct the errors */
+ for(i=0;i<nb_errors;i++) {
+ pos = error_pos[i];
+ if (pos >= NB_DATA && pos < KK) {
+ nb_errors = -1;
+ goto the_end;
+ }
+ if (pos < NB_DATA) {
+ /* extract bit position (MSB first) */
+ pos = 10 * (NB_DATA - 1 - pos) - 6;
+ /* now correct the following 10 bits. At most two bytes
+ can be modified since pos is even */
+ index = (pos >> 3) ^ 1;
+ bitpos = pos & 7;
+ if ((index >= 0 && index < SECTOR_SIZE) ||
+ index == (SECTOR_SIZE + 1)) {
+ val = error_val[i] >> (2 + bitpos);
+ parity ^= val;
+ if (index < SECTOR_SIZE)
+ sector[index] ^= val;
+ }
+ index = ((pos >> 3) + 1) ^ 1;
+ bitpos = (bitpos + 10) & 7;
+ if (bitpos == 0)
+ bitpos = 8;
+ if ((index >= 0 && index < SECTOR_SIZE) ||
+ index == (SECTOR_SIZE + 1)) {
+ val = error_val[i] << (8 - bitpos);
+ parity ^= val;
+ if (index < SECTOR_SIZE)
+ sector[index] ^= val;
+ }
+ }
+ }
+
+ /* use parity to test extra errors */
+ if ((parity & 0xff) != 0)
+ nb_errors = -1;
+
+ the_end:
+ kfree(Alpha_to);
+ kfree(Index_of);
+ return nb_errors;
+}
+
+EXPORT_SYMBOL_GPL(doc_decode_ecc);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Fabrice Bellard <fabrice.bellard@netgem.com>");
+MODULE_DESCRIPTION("ECC code for correcting errors detected by DiskOnChip 2000 and Millennium ECC hardware");
diff --git a/drivers/mtd/devices/docprobe.c b/drivers/mtd/devices/docprobe.c
new file mode 100644
index 000000000000..197d67045e1e
--- /dev/null
+++ b/drivers/mtd/devices/docprobe.c
@@ -0,0 +1,355 @@
+
+/* Linux driver for Disk-On-Chip devices */
+/* Probe routines common to all DoC devices */
+/* (C) 1999 Machine Vision Holdings, Inc. */
+/* (C) 1999-2003 David Woodhouse <dwmw2@infradead.org> */
+
+/* $Id: docprobe.c,v 1.44 2005/01/05 12:40:36 dwmw2 Exp $ */
+
+
+
+/* DOC_PASSIVE_PROBE:
+ In order to ensure that the BIOS checksum is correct at boot time, and
+ hence that the onboard BIOS extension gets executed, the DiskOnChip
+ goes into reset mode when it is read sequentially: all registers
+ return 0xff until the chip is woken up again by writing to the
+ DOCControl register.
+
+ Unfortunately, this means that the probe for the DiskOnChip is unsafe,
+ because one of the first things it does is write to where it thinks
+ the DOCControl register should be - which may well be shared memory
+ for another device. I've had machines which lock up when this is
+ attempted. Hence the possibility to do a passive probe, which will fail
+ to detect a chip in reset mode, but is at least guaranteed not to lock
+ the machine.
+
+ If you have this problem, uncomment the following line:
+#define DOC_PASSIVE_PROBE
+*/
+
+
+/* DOC_SINGLE_DRIVER:
+ Millennium driver has been merged into DOC2000 driver.
+
+ The old Millennium-only driver has been retained just in case there
+ are problems with the new code. If the combined driver doesn't work
+ for you, you can try the old one by undefining DOC_SINGLE_DRIVER
+ below and also enabling it in your configuration. If this fixes the
+ problems, please send a report to the MTD mailing list at
+ <linux-mtd@lists.infradead.org>.
+*/
+#define DOC_SINGLE_DRIVER
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/types.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/doc2000.h>
+#include <linux/mtd/compatmac.h>
+
+/* Where to look for the devices? */
+#ifndef CONFIG_MTD_DOCPROBE_ADDRESS
+#define CONFIG_MTD_DOCPROBE_ADDRESS 0
+#endif
+
+
+static unsigned long doc_config_location = CONFIG_MTD_DOCPROBE_ADDRESS;
+module_param(doc_config_location, ulong, 0);
+MODULE_PARM_DESC(doc_config_location, "Physical memory address at which to probe for DiskOnChip");
+
+static unsigned long __initdata doc_locations[] = {
+#if defined (__alpha__) || defined(__i386__) || defined(__x86_64__)
+#ifdef CONFIG_MTD_DOCPROBE_HIGH
+ 0xfffc8000, 0xfffca000, 0xfffcc000, 0xfffce000,
+ 0xfffd0000, 0xfffd2000, 0xfffd4000, 0xfffd6000,
+ 0xfffd8000, 0xfffda000, 0xfffdc000, 0xfffde000,
+ 0xfffe0000, 0xfffe2000, 0xfffe4000, 0xfffe6000,
+ 0xfffe8000, 0xfffea000, 0xfffec000, 0xfffee000,
+#else /* CONFIG_MTD_DOCPROBE_HIGH */
+ 0xc8000, 0xca000, 0xcc000, 0xce000,
+ 0xd0000, 0xd2000, 0xd4000, 0xd6000,
+ 0xd8000, 0xda000, 0xdc000, 0xde000,
+ 0xe0000, 0xe2000, 0xe4000, 0xe6000,
+ 0xe8000, 0xea000, 0xec000, 0xee000,
+#endif /* CONFIG_MTD_DOCPROBE_HIGH */
+#elif defined(__PPC__)
+ 0xe4000000,
+#elif defined(CONFIG_MOMENCO_OCELOT)
+ 0x2f000000,
+ 0xff000000,
+#elif defined(CONFIG_MOMENCO_OCELOT_G) || defined (CONFIG_MOMENCO_OCELOT_C)
+ 0xff000000,
+##else
+#warning Unknown architecture for DiskOnChip. No default probe locations defined
+#endif
+ 0xffffffff };
+
+/* doccheck: Probe a given memory window to see if there's a DiskOnChip present */
+
+static inline int __init doccheck(void __iomem *potential, unsigned long physadr)
+{
+ void __iomem *window=potential;
+ unsigned char tmp, tmpb, tmpc, ChipID;
+#ifndef DOC_PASSIVE_PROBE
+ unsigned char tmp2;
+#endif
+
+ /* Routine copied from the Linux DOC driver */
+
+#ifdef CONFIG_MTD_DOCPROBE_55AA
+ /* Check for 0x55 0xAA signature at beginning of window,
+ this is no longer true once we remove the IPL (for Millennium */
+ if (ReadDOC(window, Sig1) != 0x55 || ReadDOC(window, Sig2) != 0xaa)
+ return 0;
+#endif /* CONFIG_MTD_DOCPROBE_55AA */
+
+#ifndef DOC_PASSIVE_PROBE
+ /* It's not possible to cleanly detect the DiskOnChip - the
+ * bootup procedure will put the device into reset mode, and
+ * it's not possible to talk to it without actually writing
+ * to the DOCControl register. So we store the current contents
+ * of the DOCControl register's location, in case we later decide
+ * that it's not a DiskOnChip, and want to put it back how we
+ * found it.
+ */
+ tmp2 = ReadDOC(window, DOCControl);
+
+ /* Reset the DiskOnChip ASIC */
+ WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET,
+ window, DOCControl);
+ WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET,
+ window, DOCControl);
+
+ /* Enable the DiskOnChip ASIC */
+ WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL,
+ window, DOCControl);
+ WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL,
+ window, DOCControl);
+#endif /* !DOC_PASSIVE_PROBE */
+
+ /* We need to read the ChipID register four times. For some
+ newer DiskOnChip 2000 units, the first three reads will
+ return the DiskOnChip Millennium ident. Don't ask. */
+ ChipID = ReadDOC(window, ChipID);
+
+ switch (ChipID) {
+ case DOC_ChipID_Doc2k:
+ /* Check the TOGGLE bit in the ECC register */
+ tmp = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT;
+ tmpb = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT;
+ tmpc = ReadDOC(window, 2k_ECCStatus) & DOC_TOGGLE_BIT;
+ if (tmp != tmpb && tmp == tmpc)
+ return ChipID;
+ break;
+
+ case DOC_ChipID_DocMil:
+ /* Check for the new 2000 with Millennium ASIC */
+ ReadDOC(window, ChipID);
+ ReadDOC(window, ChipID);
+ if (ReadDOC(window, ChipID) != DOC_ChipID_DocMil)
+ ChipID = DOC_ChipID_Doc2kTSOP;
+
+ /* Check the TOGGLE bit in the ECC register */
+ tmp = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT;
+ tmpb = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT;
+ tmpc = ReadDOC(window, ECCConf) & DOC_TOGGLE_BIT;
+ if (tmp != tmpb && tmp == tmpc)
+ return ChipID;
+ break;
+
+ case DOC_ChipID_DocMilPlus16:
+ case DOC_ChipID_DocMilPlus32:
+ case 0:
+ /* Possible Millennium+, need to do more checks */
+#ifndef DOC_PASSIVE_PROBE
+ /* Possibly release from power down mode */
+ for (tmp = 0; (tmp < 4); tmp++)
+ ReadDOC(window, Mplus_Power);
+
+ /* Reset the DiskOnChip ASIC */
+ tmp = DOC_MODE_RESET | DOC_MODE_MDWREN | DOC_MODE_RST_LAT |
+ DOC_MODE_BDECT;
+ WriteDOC(tmp, window, Mplus_DOCControl);
+ WriteDOC(~tmp, window, Mplus_CtrlConfirm);
+
+ mdelay(1);
+ /* Enable the DiskOnChip ASIC */
+ tmp = DOC_MODE_NORMAL | DOC_MODE_MDWREN | DOC_MODE_RST_LAT |
+ DOC_MODE_BDECT;
+ WriteDOC(tmp, window, Mplus_DOCControl);
+ WriteDOC(~tmp, window, Mplus_CtrlConfirm);
+ mdelay(1);
+#endif /* !DOC_PASSIVE_PROBE */
+
+ ChipID = ReadDOC(window, ChipID);
+
+ switch (ChipID) {
+ case DOC_ChipID_DocMilPlus16:
+ case DOC_ChipID_DocMilPlus32:
+ /* Check the TOGGLE bit in the toggle register */
+ tmp = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT;
+ tmpb = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT;
+ tmpc = ReadDOC(window, Mplus_Toggle) & DOC_TOGGLE_BIT;
+ if (tmp != tmpb && tmp == tmpc)
+ return ChipID;
+ default:
+ break;
+ }
+ /* FALL TRHU */
+
+ default:
+
+#ifdef CONFIG_MTD_DOCPROBE_55AA
+ printk(KERN_DEBUG "Possible DiskOnChip with unknown ChipID %2.2X found at 0x%lx\n",
+ ChipID, physadr);
+#endif
+#ifndef DOC_PASSIVE_PROBE
+ /* Put back the contents of the DOCControl register, in case it's not
+ * actually a DiskOnChip.
+ */
+ WriteDOC(tmp2, window, DOCControl);
+#endif
+ return 0;
+ }
+
+ printk(KERN_WARNING "DiskOnChip failed TOGGLE test, dropping.\n");
+
+#ifndef DOC_PASSIVE_PROBE
+ /* Put back the contents of the DOCControl register: it's not a DiskOnChip */
+ WriteDOC(tmp2, window, DOCControl);
+#endif
+ return 0;
+}
+
+static int docfound;
+
+static void __init DoC_Probe(unsigned long physadr)
+{
+ void __iomem *docptr;
+ struct DiskOnChip *this;
+ struct mtd_info *mtd;
+ int ChipID;
+ char namebuf[15];
+ char *name = namebuf;
+ char *im_funcname = NULL;
+ char *im_modname = NULL;
+ void (*initroutine)(struct mtd_info *) = NULL;
+
+ docptr = ioremap(physadr, DOC_IOREMAP_LEN);
+
+ if (!docptr)
+ return;
+
+ if ((ChipID = doccheck(docptr, physadr))) {
+ if (ChipID == DOC_ChipID_Doc2kTSOP) {
+ /* Remove this at your own peril. The hardware driver works but nothing prevents you from erasing bad blocks */
+ printk(KERN_NOTICE "Refusing to drive DiskOnChip 2000 TSOP until Bad Block Table is correctly supported by INFTL\n");
+ iounmap(docptr);
+ return;
+ }
+ docfound = 1;
+ mtd = kmalloc(sizeof(struct DiskOnChip) + sizeof(struct mtd_info), GFP_KERNEL);
+
+ if (!mtd) {
+ printk(KERN_WARNING "Cannot allocate memory for data structures. Dropping.\n");
+ iounmap(docptr);
+ return;
+ }
+
+ this = (struct DiskOnChip *)(&mtd[1]);
+
+ memset((char *)mtd,0, sizeof(struct mtd_info));
+ memset((char *)this, 0, sizeof(struct DiskOnChip));
+
+ mtd->priv = this;
+ this->virtadr = docptr;
+ this->physadr = physadr;
+ this->ChipID = ChipID;
+ sprintf(namebuf, "with ChipID %2.2X", ChipID);
+
+ switch(ChipID) {
+ case DOC_ChipID_Doc2kTSOP:
+ name="2000 TSOP";
+ im_funcname = "DoC2k_init";
+ im_modname = "doc2000";
+ break;
+
+ case DOC_ChipID_Doc2k:
+ name="2000";
+ im_funcname = "DoC2k_init";
+ im_modname = "doc2000";
+ break;
+
+ case DOC_ChipID_DocMil:
+ name="Millennium";
+#ifdef DOC_SINGLE_DRIVER
+ im_funcname = "DoC2k_init";
+ im_modname = "doc2000";
+#else
+ im_funcname = "DoCMil_init";
+ im_modname = "doc2001";
+#endif /* DOC_SINGLE_DRIVER */
+ break;
+
+ case DOC_ChipID_DocMilPlus16:
+ case DOC_ChipID_DocMilPlus32:
+ name="MillenniumPlus";
+ im_funcname = "DoCMilPlus_init";
+ im_modname = "doc2001plus";
+ break;
+ }
+
+ if (im_funcname)
+ initroutine = inter_module_get_request(im_funcname, im_modname);
+
+ if (initroutine) {
+ (*initroutine)(mtd);
+ inter_module_put(im_funcname);
+ return;
+ }
+ printk(KERN_NOTICE "Cannot find driver for DiskOnChip %s at 0x%lX\n", name, physadr);
+ kfree(mtd);
+ }
+ iounmap(docptr);
+}
+
+
+/****************************************************************************
+ *
+ * Module stuff
+ *
+ ****************************************************************************/
+
+static int __init init_doc(void)
+{
+ int i;
+
+ if (doc_config_location) {
+ printk(KERN_INFO "Using configured DiskOnChip probe address 0x%lx\n", doc_config_location);
+ DoC_Probe(doc_config_location);
+ } else {
+ for (i=0; (doc_locations[i] != 0xffffffff); i++) {
+ DoC_Probe(doc_locations[i]);
+ }
+ }
+ /* No banner message any more. Print a message if no DiskOnChip
+ found, so the user knows we at least tried. */
+ if (!docfound)
+ printk(KERN_INFO "No recognised DiskOnChip devices found\n");
+ return -EAGAIN;
+}
+
+module_init(init_doc);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("Probe code for DiskOnChip 2000 and Millennium devices");
+
diff --git a/drivers/mtd/devices/lart.c b/drivers/mtd/devices/lart.c
new file mode 100644
index 000000000000..dfd335e4a2a8
--- /dev/null
+++ b/drivers/mtd/devices/lart.c
@@ -0,0 +1,711 @@
+
+/*
+ * MTD driver for the 28F160F3 Flash Memory (non-CFI) on LART.
+ *
+ * $Id: lart.c,v 1.7 2004/08/09 13:19:44 dwmw2 Exp $
+ *
+ * Author: Abraham vd Merwe <abraham@2d3d.co.za>
+ *
+ * Copyright (c) 2001, 2d3D, Inc.
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * References:
+ *
+ * [1] 3 Volt Fast Boot Block Flash Memory" Intel Datasheet
+ * - Order Number: 290644-005
+ * - January 2000
+ *
+ * [2] MTD internal API documentation
+ * - http://www.linux-mtd.infradead.org/tech/
+ *
+ * Limitations:
+ *
+ * Even though this driver is written for 3 Volt Fast Boot
+ * Block Flash Memory, it is rather specific to LART. With
+ * Minor modifications, notably the without data/address line
+ * mangling and different bus settings, etc. it should be
+ * trivial to adapt to other platforms.
+ *
+ * If somebody would sponsor me a different board, I'll
+ * adapt the driver (:
+ */
+
+/* debugging */
+//#define LART_DEBUG
+
+/* partition support */
+#define HAVE_PARTITIONS
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/mtd/mtd.h>
+#ifdef HAVE_PARTITIONS
+#include <linux/mtd/partitions.h>
+#endif
+
+#ifndef CONFIG_SA1100_LART
+#error This is for LART architecture only
+#endif
+
+static char module_name[] = "lart";
+
+/*
+ * These values is specific to 28Fxxxx3 flash memory.
+ * See section 2.3.1 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
+ */
+#define FLASH_BLOCKSIZE_PARAM (4096 * BUSWIDTH)
+#define FLASH_NUMBLOCKS_16m_PARAM 8
+#define FLASH_NUMBLOCKS_8m_PARAM 8
+
+/*
+ * These values is specific to 28Fxxxx3 flash memory.
+ * See section 2.3.2 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
+ */
+#define FLASH_BLOCKSIZE_MAIN (32768 * BUSWIDTH)
+#define FLASH_NUMBLOCKS_16m_MAIN 31
+#define FLASH_NUMBLOCKS_8m_MAIN 15
+
+/*
+ * These values are specific to LART
+ */
+
+/* general */
+#define BUSWIDTH 4 /* don't change this - a lot of the code _will_ break if you change this */
+#define FLASH_OFFSET 0xe8000000 /* see linux/arch/arm/mach-sa1100/lart.c */
+
+/* blob */
+#define NUM_BLOB_BLOCKS FLASH_NUMBLOCKS_16m_PARAM
+#define BLOB_START 0x00000000
+#define BLOB_LEN (NUM_BLOB_BLOCKS * FLASH_BLOCKSIZE_PARAM)
+
+/* kernel */
+#define NUM_KERNEL_BLOCKS 7
+#define KERNEL_START (BLOB_START + BLOB_LEN)
+#define KERNEL_LEN (NUM_KERNEL_BLOCKS * FLASH_BLOCKSIZE_MAIN)
+
+/* initial ramdisk */
+#define NUM_INITRD_BLOCKS 24
+#define INITRD_START (KERNEL_START + KERNEL_LEN)
+#define INITRD_LEN (NUM_INITRD_BLOCKS * FLASH_BLOCKSIZE_MAIN)
+
+/*
+ * See section 4.0 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
+ */
+#define READ_ARRAY 0x00FF00FF /* Read Array/Reset */
+#define READ_ID_CODES 0x00900090 /* Read Identifier Codes */
+#define ERASE_SETUP 0x00200020 /* Block Erase */
+#define ERASE_CONFIRM 0x00D000D0 /* Block Erase and Program Resume */
+#define PGM_SETUP 0x00400040 /* Program */
+#define STATUS_READ 0x00700070 /* Read Status Register */
+#define STATUS_CLEAR 0x00500050 /* Clear Status Register */
+#define STATUS_BUSY 0x00800080 /* Write State Machine Status (WSMS) */
+#define STATUS_ERASE_ERR 0x00200020 /* Erase Status (ES) */
+#define STATUS_PGM_ERR 0x00100010 /* Program Status (PS) */
+
+/*
+ * See section 4.2 in "3 Volt Fast Boot Block Flash Memory" Intel Datasheet
+ */
+#define FLASH_MANUFACTURER 0x00890089
+#define FLASH_DEVICE_8mbit_TOP 0x88f188f1
+#define FLASH_DEVICE_8mbit_BOTTOM 0x88f288f2
+#define FLASH_DEVICE_16mbit_TOP 0x88f388f3
+#define FLASH_DEVICE_16mbit_BOTTOM 0x88f488f4
+
+/***************************************************************************************************/
+
+/*
+ * The data line mapping on LART is as follows:
+ *
+ * U2 CPU | U3 CPU
+ * -------------------
+ * 0 20 | 0 12
+ * 1 22 | 1 14
+ * 2 19 | 2 11
+ * 3 17 | 3 9
+ * 4 24 | 4 0
+ * 5 26 | 5 2
+ * 6 31 | 6 7
+ * 7 29 | 7 5
+ * 8 21 | 8 13
+ * 9 23 | 9 15
+ * 10 18 | 10 10
+ * 11 16 | 11 8
+ * 12 25 | 12 1
+ * 13 27 | 13 3
+ * 14 30 | 14 6
+ * 15 28 | 15 4
+ */
+
+/* Mangle data (x) */
+#define DATA_TO_FLASH(x) \
+ ( \
+ (((x) & 0x08009000) >> 11) + \
+ (((x) & 0x00002000) >> 10) + \
+ (((x) & 0x04004000) >> 8) + \
+ (((x) & 0x00000010) >> 4) + \
+ (((x) & 0x91000820) >> 3) + \
+ (((x) & 0x22080080) >> 2) + \
+ ((x) & 0x40000400) + \
+ (((x) & 0x00040040) << 1) + \
+ (((x) & 0x00110000) << 4) + \
+ (((x) & 0x00220100) << 5) + \
+ (((x) & 0x00800208) << 6) + \
+ (((x) & 0x00400004) << 9) + \
+ (((x) & 0x00000001) << 12) + \
+ (((x) & 0x00000002) << 13) \
+ )
+
+/* Unmangle data (x) */
+#define FLASH_TO_DATA(x) \
+ ( \
+ (((x) & 0x00010012) << 11) + \
+ (((x) & 0x00000008) << 10) + \
+ (((x) & 0x00040040) << 8) + \
+ (((x) & 0x00000001) << 4) + \
+ (((x) & 0x12200104) << 3) + \
+ (((x) & 0x08820020) << 2) + \
+ ((x) & 0x40000400) + \
+ (((x) & 0x00080080) >> 1) + \
+ (((x) & 0x01100000) >> 4) + \
+ (((x) & 0x04402000) >> 5) + \
+ (((x) & 0x20008200) >> 6) + \
+ (((x) & 0x80000800) >> 9) + \
+ (((x) & 0x00001000) >> 12) + \
+ (((x) & 0x00004000) >> 13) \
+ )
+
+/*
+ * The address line mapping on LART is as follows:
+ *
+ * U3 CPU | U2 CPU
+ * -------------------
+ * 0 2 | 0 2
+ * 1 3 | 1 3
+ * 2 9 | 2 9
+ * 3 13 | 3 8
+ * 4 8 | 4 7
+ * 5 12 | 5 6
+ * 6 11 | 6 5
+ * 7 10 | 7 4
+ * 8 4 | 8 10
+ * 9 5 | 9 11
+ * 10 6 | 10 12
+ * 11 7 | 11 13
+ *
+ * BOOT BLOCK BOUNDARY
+ *
+ * 12 15 | 12 15
+ * 13 14 | 13 14
+ * 14 16 | 14 16
+ *
+ * MAIN BLOCK BOUNDARY
+ *
+ * 15 17 | 15 18
+ * 16 18 | 16 17
+ * 17 20 | 17 20
+ * 18 19 | 18 19
+ * 19 21 | 19 21
+ *
+ * As we can see from above, the addresses aren't mangled across
+ * block boundaries, so we don't need to worry about address
+ * translations except for sending/reading commands during
+ * initialization
+ */
+
+/* Mangle address (x) on chip U2 */
+#define ADDR_TO_FLASH_U2(x) \
+ ( \
+ (((x) & 0x00000f00) >> 4) + \
+ (((x) & 0x00042000) << 1) + \
+ (((x) & 0x0009c003) << 2) + \
+ (((x) & 0x00021080) << 3) + \
+ (((x) & 0x00000010) << 4) + \
+ (((x) & 0x00000040) << 5) + \
+ (((x) & 0x00000024) << 7) + \
+ (((x) & 0x00000008) << 10) \
+ )
+
+/* Unmangle address (x) on chip U2 */
+#define FLASH_U2_TO_ADDR(x) \
+ ( \
+ (((x) << 4) & 0x00000f00) + \
+ (((x) >> 1) & 0x00042000) + \
+ (((x) >> 2) & 0x0009c003) + \
+ (((x) >> 3) & 0x00021080) + \
+ (((x) >> 4) & 0x00000010) + \
+ (((x) >> 5) & 0x00000040) + \
+ (((x) >> 7) & 0x00000024) + \
+ (((x) >> 10) & 0x00000008) \
+ )
+
+/* Mangle address (x) on chip U3 */
+#define ADDR_TO_FLASH_U3(x) \
+ ( \
+ (((x) & 0x00000080) >> 3) + \
+ (((x) & 0x00000040) >> 1) + \
+ (((x) & 0x00052020) << 1) + \
+ (((x) & 0x00084f03) << 2) + \
+ (((x) & 0x00029010) << 3) + \
+ (((x) & 0x00000008) << 5) + \
+ (((x) & 0x00000004) << 7) \
+ )
+
+/* Unmangle address (x) on chip U3 */
+#define FLASH_U3_TO_ADDR(x) \
+ ( \
+ (((x) << 3) & 0x00000080) + \
+ (((x) << 1) & 0x00000040) + \
+ (((x) >> 1) & 0x00052020) + \
+ (((x) >> 2) & 0x00084f03) + \
+ (((x) >> 3) & 0x00029010) + \
+ (((x) >> 5) & 0x00000008) + \
+ (((x) >> 7) & 0x00000004) \
+ )
+
+/***************************************************************************************************/
+
+static __u8 read8 (__u32 offset)
+{
+ volatile __u8 *data = (__u8 *) (FLASH_OFFSET + offset);
+#ifdef LART_DEBUG
+ printk (KERN_DEBUG "%s(): 0x%.8x -> 0x%.2x\n",__FUNCTION__,offset,*data);
+#endif
+ return (*data);
+}
+
+static __u32 read32 (__u32 offset)
+{
+ volatile __u32 *data = (__u32 *) (FLASH_OFFSET + offset);
+#ifdef LART_DEBUG
+ printk (KERN_DEBUG "%s(): 0x%.8x -> 0x%.8x\n",__FUNCTION__,offset,*data);
+#endif
+ return (*data);
+}
+
+static void write32 (__u32 x,__u32 offset)
+{
+ volatile __u32 *data = (__u32 *) (FLASH_OFFSET + offset);
+ *data = x;
+#ifdef LART_DEBUG
+ printk (KERN_DEBUG "%s(): 0x%.8x <- 0x%.8x\n",__FUNCTION__,offset,*data);
+#endif
+}
+
+/***************************************************************************************************/
+
+/*
+ * Probe for 16mbit flash memory on a LART board without doing
+ * too much damage. Since we need to write 1 dword to memory,
+ * we're f**cked if this happens to be DRAM since we can't
+ * restore the memory (otherwise we might exit Read Array mode).
+ *
+ * Returns 1 if we found 16mbit flash memory on LART, 0 otherwise.
+ */
+static int flash_probe (void)
+{
+ __u32 manufacturer,devtype;
+
+ /* setup "Read Identifier Codes" mode */
+ write32 (DATA_TO_FLASH (READ_ID_CODES),0x00000000);
+
+ /* probe U2. U2/U3 returns the same data since the first 3
+ * address lines is mangled in the same way */
+ manufacturer = FLASH_TO_DATA (read32 (ADDR_TO_FLASH_U2 (0x00000000)));
+ devtype = FLASH_TO_DATA (read32 (ADDR_TO_FLASH_U2 (0x00000001)));
+
+ /* put the flash back into command mode */
+ write32 (DATA_TO_FLASH (READ_ARRAY),0x00000000);
+
+ return (manufacturer == FLASH_MANUFACTURER && (devtype == FLASH_DEVICE_16mbit_TOP || FLASH_DEVICE_16mbit_BOTTOM));
+}
+
+/*
+ * Erase one block of flash memory at offset ``offset'' which is any
+ * address within the block which should be erased.
+ *
+ * Returns 1 if successful, 0 otherwise.
+ */
+static inline int erase_block (__u32 offset)
+{
+ __u32 status;
+
+#ifdef LART_DEBUG
+ printk (KERN_DEBUG "%s(): 0x%.8x\n",__FUNCTION__,offset);
+#endif
+
+ /* erase and confirm */
+ write32 (DATA_TO_FLASH (ERASE_SETUP),offset);
+ write32 (DATA_TO_FLASH (ERASE_CONFIRM),offset);
+
+ /* wait for block erase to finish */
+ do
+ {
+ write32 (DATA_TO_FLASH (STATUS_READ),offset);
+ status = FLASH_TO_DATA (read32 (offset));
+ }
+ while ((~status & STATUS_BUSY) != 0);
+
+ /* put the flash back into command mode */
+ write32 (DATA_TO_FLASH (READ_ARRAY),offset);
+
+ /* was the erase successfull? */
+ if ((status & STATUS_ERASE_ERR))
+ {
+ printk (KERN_WARNING "%s: erase error at address 0x%.8x.\n",module_name,offset);
+ return (0);
+ }
+
+ return (1);
+}
+
+static int flash_erase (struct mtd_info *mtd,struct erase_info *instr)
+{
+ __u32 addr,len;
+ int i,first;
+
+#ifdef LART_DEBUG
+ printk (KERN_DEBUG "%s(addr = 0x%.8x, len = %d)\n",__FUNCTION__,instr->addr,instr->len);
+#endif
+
+ /* sanity checks */
+ if (instr->addr + instr->len > mtd->size) return (-EINVAL);
+
+ /*
+ * check that both start and end of the requested erase are
+ * aligned with the erasesize at the appropriate addresses.
+ *
+ * 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.
+ */
+ for (i = 0; i < mtd->numeraseregions && instr->addr >= mtd->eraseregions[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 & (mtd->eraseregions[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.
+ *
+ * as before, drop back one to point at the region in which
+ * the address actually falls
+ */
+ for (; i < mtd->numeraseregions && instr->addr + instr->len >= mtd->eraseregions[i].offset; i++) ;
+ i--;
+
+ /* is the end aligned on a block boundary? */
+ if ((instr->addr + instr->len) & (mtd->eraseregions[i].erasesize - 1)) return (-EINVAL);
+
+ addr = instr->addr;
+ len = instr->len;
+
+ i = first;
+
+ /* now erase those blocks */
+ while (len)
+ {
+ if (!erase_block (addr))
+ {
+ instr->state = MTD_ERASE_FAILED;
+ return (-EIO);
+ }
+
+ addr += mtd->eraseregions[i].erasesize;
+ len -= mtd->eraseregions[i].erasesize;
+
+ if (addr == mtd->eraseregions[i].offset + (mtd->eraseregions[i].erasesize * mtd->eraseregions[i].numblocks)) i++;
+ }
+
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+
+ return (0);
+}
+
+static int flash_read (struct mtd_info *mtd,loff_t from,size_t len,size_t *retlen,u_char *buf)
+{
+#ifdef LART_DEBUG
+ printk (KERN_DEBUG "%s(from = 0x%.8x, len = %d)\n",__FUNCTION__,(__u32) from,len);
+#endif
+
+ /* sanity checks */
+ if (!len) return (0);
+ if (from + len > mtd->size) return (-EINVAL);
+
+ /* we always read len bytes */
+ *retlen = len;
+
+ /* first, we read bytes until we reach a dword boundary */
+ if (from & (BUSWIDTH - 1))
+ {
+ int gap = BUSWIDTH - (from & (BUSWIDTH - 1));
+
+ while (len && gap--) *buf++ = read8 (from++), len--;
+ }
+
+ /* now we read dwords until we reach a non-dword boundary */
+ while (len >= BUSWIDTH)
+ {
+ *((__u32 *) buf) = read32 (from);
+
+ buf += BUSWIDTH;
+ from += BUSWIDTH;
+ len -= BUSWIDTH;
+ }
+
+ /* top up the last unaligned bytes */
+ if (len & (BUSWIDTH - 1))
+ while (len--) *buf++ = read8 (from++);
+
+ return (0);
+}
+
+/*
+ * Write one dword ``x'' to flash memory at offset ``offset''. ``offset''
+ * must be 32 bits, i.e. it must be on a dword boundary.
+ *
+ * Returns 1 if successful, 0 otherwise.
+ */
+static inline int write_dword (__u32 offset,__u32 x)
+{
+ __u32 status;
+
+#ifdef LART_DEBUG
+ printk (KERN_DEBUG "%s(): 0x%.8x <- 0x%.8x\n",__FUNCTION__,offset,x);
+#endif
+
+ /* setup writing */
+ write32 (DATA_TO_FLASH (PGM_SETUP),offset);
+
+ /* write the data */
+ write32 (x,offset);
+
+ /* wait for the write to finish */
+ do
+ {
+ write32 (DATA_TO_FLASH (STATUS_READ),offset);
+ status = FLASH_TO_DATA (read32 (offset));
+ }
+ while ((~status & STATUS_BUSY) != 0);
+
+ /* put the flash back into command mode */
+ write32 (DATA_TO_FLASH (READ_ARRAY),offset);
+
+ /* was the write successfull? */
+ if ((status & STATUS_PGM_ERR) || read32 (offset) != x)
+ {
+ printk (KERN_WARNING "%s: write error at address 0x%.8x.\n",module_name,offset);
+ return (0);
+ }
+
+ return (1);
+}
+
+static int flash_write (struct mtd_info *mtd,loff_t to,size_t len,size_t *retlen,const u_char *buf)
+{
+ __u8 tmp[4];
+ int i,n;
+
+#ifdef LART_DEBUG
+ printk (KERN_DEBUG "%s(to = 0x%.8x, len = %d)\n",__FUNCTION__,(__u32) to,len);
+#endif
+
+ *retlen = 0;
+
+ /* sanity checks */
+ if (!len) return (0);
+ if (to + len > mtd->size) return (-EINVAL);
+
+ /* first, we write a 0xFF.... padded byte until we reach a dword boundary */
+ if (to & (BUSWIDTH - 1))
+ {
+ __u32 aligned = to & ~(BUSWIDTH - 1);
+ int gap = to - aligned;
+
+ i = n = 0;
+
+ while (gap--) tmp[i++] = 0xFF;
+ while (len && i < BUSWIDTH) tmp[i++] = buf[n++], len--;
+ while (i < BUSWIDTH) tmp[i++] = 0xFF;
+
+ if (!write_dword (aligned,*((__u32 *) tmp))) return (-EIO);
+
+ to += n;
+ buf += n;
+ *retlen += n;
+ }
+
+ /* now we write dwords until we reach a non-dword boundary */
+ while (len >= BUSWIDTH)
+ {
+ if (!write_dword (to,*((__u32 *) buf))) return (-EIO);
+
+ to += BUSWIDTH;
+ buf += BUSWIDTH;
+ *retlen += BUSWIDTH;
+ len -= BUSWIDTH;
+ }
+
+ /* top up the last unaligned bytes, padded with 0xFF.... */
+ if (len & (BUSWIDTH - 1))
+ {
+ i = n = 0;
+
+ while (len--) tmp[i++] = buf[n++];
+ while (i < BUSWIDTH) tmp[i++] = 0xFF;
+
+ if (!write_dword (to,*((__u32 *) tmp))) return (-EIO);
+
+ *retlen += n;
+ }
+
+ return (0);
+}
+
+/***************************************************************************************************/
+
+#define NB_OF(x) (sizeof (x) / sizeof (x[0]))
+
+static struct mtd_info mtd;
+
+static struct mtd_erase_region_info erase_regions[] = {
+ /* parameter blocks */
+ {
+ .offset = 0x00000000,
+ .erasesize = FLASH_BLOCKSIZE_PARAM,
+ .numblocks = FLASH_NUMBLOCKS_16m_PARAM,
+ },
+ /* main blocks */
+ {
+ .offset = FLASH_BLOCKSIZE_PARAM * FLASH_NUMBLOCKS_16m_PARAM,
+ .erasesize = FLASH_BLOCKSIZE_MAIN,
+ .numblocks = FLASH_NUMBLOCKS_16m_MAIN,
+ }
+};
+
+#ifdef HAVE_PARTITIONS
+static struct mtd_partition lart_partitions[] = {
+ /* blob */
+ {
+ .name = "blob",
+ .offset = BLOB_START,
+ .size = BLOB_LEN,
+ },
+ /* kernel */
+ {
+ .name = "kernel",
+ .offset = KERNEL_START, /* MTDPART_OFS_APPEND */
+ .size = KERNEL_LEN,
+ },
+ /* initial ramdisk / file system */
+ {
+ .name = "file system",
+ .offset = INITRD_START, /* MTDPART_OFS_APPEND */
+ .size = INITRD_LEN, /* MTDPART_SIZ_FULL */
+ }
+};
+#endif
+
+int __init lart_flash_init (void)
+{
+ int result;
+ memset (&mtd,0,sizeof (mtd));
+ printk ("MTD driver for LART. Written by Abraham vd Merwe <abraham@2d3d.co.za>\n");
+ printk ("%s: Probing for 28F160x3 flash on LART...\n",module_name);
+ if (!flash_probe ())
+ {
+ printk (KERN_WARNING "%s: Found no LART compatible flash device\n",module_name);
+ return (-ENXIO);
+ }
+ printk ("%s: This looks like a LART board to me.\n",module_name);
+ mtd.name = module_name;
+ mtd.type = MTD_NORFLASH;
+ mtd.flags = MTD_CAP_NORFLASH;
+ mtd.size = FLASH_BLOCKSIZE_PARAM * FLASH_NUMBLOCKS_16m_PARAM + FLASH_BLOCKSIZE_MAIN * FLASH_NUMBLOCKS_16m_MAIN;
+ mtd.erasesize = FLASH_BLOCKSIZE_MAIN;
+ mtd.numeraseregions = NB_OF (erase_regions);
+ mtd.eraseregions = erase_regions;
+ mtd.erase = flash_erase;
+ mtd.read = flash_read;
+ mtd.write = flash_write;
+ mtd.owner = THIS_MODULE;
+
+#ifdef LART_DEBUG
+ printk (KERN_DEBUG
+ "mtd.name = %s\n"
+ "mtd.size = 0x%.8x (%uM)\n"
+ "mtd.erasesize = 0x%.8x (%uK)\n"
+ "mtd.numeraseregions = %d\n",
+ mtd.name,
+ mtd.size,mtd.size / (1024*1024),
+ mtd.erasesize,mtd.erasesize / 1024,
+ mtd.numeraseregions);
+
+ if (mtd.numeraseregions)
+ for (result = 0; result < mtd.numeraseregions; result++)
+ printk (KERN_DEBUG
+ "\n\n"
+ "mtd.eraseregions[%d].offset = 0x%.8x\n"
+ "mtd.eraseregions[%d].erasesize = 0x%.8x (%uK)\n"
+ "mtd.eraseregions[%d].numblocks = %d\n",
+ result,mtd.eraseregions[result].offset,
+ result,mtd.eraseregions[result].erasesize,mtd.eraseregions[result].erasesize / 1024,
+ result,mtd.eraseregions[result].numblocks);
+
+#ifdef HAVE_PARTITIONS
+ printk ("\npartitions = %d\n",NB_OF (lart_partitions));
+
+ for (result = 0; result < NB_OF (lart_partitions); result++)
+ printk (KERN_DEBUG
+ "\n\n"
+ "lart_partitions[%d].name = %s\n"
+ "lart_partitions[%d].offset = 0x%.8x\n"
+ "lart_partitions[%d].size = 0x%.8x (%uK)\n",
+ result,lart_partitions[result].name,
+ result,lart_partitions[result].offset,
+ result,lart_partitions[result].size,lart_partitions[result].size / 1024);
+#endif
+#endif
+
+#ifndef HAVE_PARTITIONS
+ result = add_mtd_device (&mtd);
+#else
+ result = add_mtd_partitions (&mtd,lart_partitions,NB_OF (lart_partitions));
+#endif
+
+ return (result);
+}
+
+void __exit lart_flash_exit (void)
+{
+#ifndef HAVE_PARTITIONS
+ del_mtd_device (&mtd);
+#else
+ del_mtd_partitions (&mtd);
+#endif
+}
+
+module_init (lart_flash_init);
+module_exit (lart_flash_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Abraham vd Merwe <abraham@2d3d.co.za>");
+MODULE_DESCRIPTION("MTD driver for Intel 28F160F3 on LART board");
+
+
diff --git a/drivers/mtd/devices/ms02-nv.c b/drivers/mtd/devices/ms02-nv.c
new file mode 100644
index 000000000000..380ff08d29e4
--- /dev/null
+++ b/drivers/mtd/devices/ms02-nv.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright (c) 2001 Maciej W. Rozycki
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * $Id: ms02-nv.c,v 1.8 2005/01/05 18:05:12 dwmw2 Exp $
+ */
+
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <asm/addrspace.h>
+#include <asm/bootinfo.h>
+#include <asm/dec/ioasic_addrs.h>
+#include <asm/dec/kn02.h>
+#include <asm/dec/kn03.h>
+#include <asm/io.h>
+#include <asm/paccess.h>
+
+#include "ms02-nv.h"
+
+
+static char version[] __initdata =
+ "ms02-nv.c: v.1.0.0 13 Aug 2001 Maciej W. Rozycki.\n";
+
+MODULE_AUTHOR("Maciej W. Rozycki <macro@linux-mips.org>");
+MODULE_DESCRIPTION("DEC MS02-NV NVRAM module driver");
+MODULE_LICENSE("GPL");
+
+
+/*
+ * Addresses we probe for an MS02-NV at. Modules may be located
+ * at any 8MiB boundary within a 0MiB up to 112MiB range or at any 32MiB
+ * boundary within a 0MiB up to 448MiB range. We don't support a module
+ * at 0MiB, though.
+ */
+static ulong ms02nv_addrs[] __initdata = {
+ 0x07000000, 0x06800000, 0x06000000, 0x05800000, 0x05000000,
+ 0x04800000, 0x04000000, 0x03800000, 0x03000000, 0x02800000,
+ 0x02000000, 0x01800000, 0x01000000, 0x00800000
+};
+
+static const char ms02nv_name[] = "DEC MS02-NV NVRAM";
+static const char ms02nv_res_diag_ram[] = "Diagnostic RAM";
+static const char ms02nv_res_user_ram[] = "General-purpose RAM";
+static const char ms02nv_res_csr[] = "Control and status register";
+
+static struct mtd_info *root_ms02nv_mtd;
+
+
+static int ms02nv_read(struct mtd_info *mtd, loff_t from,
+ size_t len, size_t *retlen, u_char *buf)
+{
+ struct ms02nv_private *mp = mtd->priv;
+
+ if (from + len > mtd->size)
+ return -EINVAL;
+
+ memcpy(buf, mp->uaddr + from, len);
+ *retlen = len;
+
+ return 0;
+}
+
+static int ms02nv_write(struct mtd_info *mtd, loff_t to,
+ size_t len, size_t *retlen, const u_char *buf)
+{
+ struct ms02nv_private *mp = mtd->priv;
+
+ if (to + len > mtd->size)
+ return -EINVAL;
+
+ memcpy(mp->uaddr + to, buf, len);
+ *retlen = len;
+
+ return 0;
+}
+
+
+static inline uint ms02nv_probe_one(ulong addr)
+{
+ ms02nv_uint *ms02nv_diagp;
+ ms02nv_uint *ms02nv_magicp;
+ uint ms02nv_diag;
+ uint ms02nv_magic;
+ size_t size;
+
+ int err;
+
+ /*
+ * The firmware writes MS02NV_ID at MS02NV_MAGIC and also
+ * a diagnostic status at MS02NV_DIAG.
+ */
+ ms02nv_diagp = (ms02nv_uint *)(KSEG1ADDR(addr + MS02NV_DIAG));
+ ms02nv_magicp = (ms02nv_uint *)(KSEG1ADDR(addr + MS02NV_MAGIC));
+ err = get_dbe(ms02nv_magic, ms02nv_magicp);
+ if (err)
+ return 0;
+ if (ms02nv_magic != MS02NV_ID)
+ return 0;
+
+ ms02nv_diag = *ms02nv_diagp;
+ size = (ms02nv_diag & MS02NV_DIAG_SIZE_MASK) << MS02NV_DIAG_SIZE_SHIFT;
+ if (size > MS02NV_CSR)
+ size = MS02NV_CSR;
+
+ return size;
+}
+
+static int __init ms02nv_init_one(ulong addr)
+{
+ struct mtd_info *mtd;
+ struct ms02nv_private *mp;
+ struct resource *mod_res;
+ struct resource *diag_res;
+ struct resource *user_res;
+ struct resource *csr_res;
+ ulong fixaddr;
+ size_t size, fixsize;
+
+ static int version_printed;
+
+ int ret = -ENODEV;
+
+ /* The module decodes 8MiB of address space. */
+ mod_res = kmalloc(sizeof(*mod_res), GFP_KERNEL);
+ if (!mod_res)
+ return -ENOMEM;
+
+ memset(mod_res, 0, sizeof(*mod_res));
+ mod_res->name = ms02nv_name;
+ mod_res->start = addr;
+ mod_res->end = addr + MS02NV_SLOT_SIZE - 1;
+ mod_res->flags = IORESOURCE_MEM | IORESOURCE_BUSY;
+ if (request_resource(&iomem_resource, mod_res) < 0)
+ goto err_out_mod_res;
+
+ size = ms02nv_probe_one(addr);
+ if (!size)
+ goto err_out_mod_res_rel;
+
+ if (!version_printed) {
+ printk(KERN_INFO "%s", version);
+ version_printed = 1;
+ }
+
+ ret = -ENOMEM;
+ mtd = kmalloc(sizeof(*mtd), GFP_KERNEL);
+ if (!mtd)
+ goto err_out_mod_res_rel;
+ memset(mtd, 0, sizeof(*mtd));
+ mp = kmalloc(sizeof(*mp), GFP_KERNEL);
+ if (!mp)
+ goto err_out_mtd;
+ memset(mp, 0, sizeof(*mp));
+
+ mtd->priv = mp;
+ mp->resource.module = mod_res;
+
+ /* Firmware's diagnostic NVRAM area. */
+ diag_res = kmalloc(sizeof(*diag_res), GFP_KERNEL);
+ if (!diag_res)
+ goto err_out_mp;
+
+ memset(diag_res, 0, sizeof(*diag_res));
+ diag_res->name = ms02nv_res_diag_ram;
+ diag_res->start = addr;
+ diag_res->end = addr + MS02NV_RAM - 1;
+ diag_res->flags = IORESOURCE_BUSY;
+ request_resource(mod_res, diag_res);
+
+ mp->resource.diag_ram = diag_res;
+
+ /* User-available general-purpose NVRAM area. */
+ user_res = kmalloc(sizeof(*user_res), GFP_KERNEL);
+ if (!user_res)
+ goto err_out_diag_res;
+
+ memset(user_res, 0, sizeof(*user_res));
+ user_res->name = ms02nv_res_user_ram;
+ user_res->start = addr + MS02NV_RAM;
+ user_res->end = addr + size - 1;
+ user_res->flags = IORESOURCE_BUSY;
+ request_resource(mod_res, user_res);
+
+ mp->resource.user_ram = user_res;
+
+ /* Control and status register. */
+ csr_res = kmalloc(sizeof(*csr_res), GFP_KERNEL);
+ if (!csr_res)
+ goto err_out_user_res;
+
+ memset(csr_res, 0, sizeof(*csr_res));
+ csr_res->name = ms02nv_res_csr;
+ csr_res->start = addr + MS02NV_CSR;
+ csr_res->end = addr + MS02NV_CSR + 3;
+ csr_res->flags = IORESOURCE_BUSY;
+ request_resource(mod_res, csr_res);
+
+ mp->resource.csr = csr_res;
+
+ mp->addr = phys_to_virt(addr);
+ mp->size = size;
+
+ /*
+ * Hide the firmware's diagnostic area. It may get destroyed
+ * upon a reboot. Take paging into account for mapping support.
+ */
+ fixaddr = (addr + MS02NV_RAM + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
+ fixsize = (size - (fixaddr - addr)) & ~(PAGE_SIZE - 1);
+ mp->uaddr = phys_to_virt(fixaddr);
+
+ mtd->type = MTD_RAM;
+ mtd->flags = MTD_CAP_RAM | MTD_XIP;
+ mtd->size = fixsize;
+ mtd->name = (char *)ms02nv_name;
+ mtd->owner = THIS_MODULE;
+ mtd->read = ms02nv_read;
+ mtd->write = ms02nv_write;
+
+ ret = -EIO;
+ if (add_mtd_device(mtd)) {
+ printk(KERN_ERR
+ "ms02-nv: Unable to register MTD device, aborting!\n");
+ goto err_out_csr_res;
+ }
+
+ printk(KERN_INFO "mtd%d: %s at 0x%08lx, size %uMiB.\n",
+ mtd->index, ms02nv_name, addr, size >> 20);
+
+ mp->next = root_ms02nv_mtd;
+ root_ms02nv_mtd = mtd;
+
+ return 0;
+
+
+err_out_csr_res:
+ release_resource(csr_res);
+ kfree(csr_res);
+err_out_user_res:
+ release_resource(user_res);
+ kfree(user_res);
+err_out_diag_res:
+ release_resource(diag_res);
+ kfree(diag_res);
+err_out_mp:
+ kfree(mp);
+err_out_mtd:
+ kfree(mtd);
+err_out_mod_res_rel:
+ release_resource(mod_res);
+err_out_mod_res:
+ kfree(mod_res);
+ return ret;
+}
+
+static void __exit ms02nv_remove_one(void)
+{
+ struct mtd_info *mtd = root_ms02nv_mtd;
+ struct ms02nv_private *mp = mtd->priv;
+
+ root_ms02nv_mtd = mp->next;
+
+ del_mtd_device(mtd);
+
+ release_resource(mp->resource.csr);
+ kfree(mp->resource.csr);
+ release_resource(mp->resource.user_ram);
+ kfree(mp->resource.user_ram);
+ release_resource(mp->resource.diag_ram);
+ kfree(mp->resource.diag_ram);
+ release_resource(mp->resource.module);
+ kfree(mp->resource.module);
+ kfree(mp);
+ kfree(mtd);
+}
+
+
+static int __init ms02nv_init(void)
+{
+ volatile u32 *csr;
+ uint stride = 0;
+ int count = 0;
+ int i;
+
+ switch (mips_machtype) {
+ case MACH_DS5000_200:
+ csr = (volatile u32 *)KN02_CSR_BASE;
+ if (*csr & KN02_CSR_BNK32M)
+ stride = 2;
+ break;
+ case MACH_DS5000_2X0:
+ case MACH_DS5900:
+ csr = (volatile u32 *)KN03_MCR_BASE;
+ if (*csr & KN03_MCR_BNK32M)
+ stride = 2;
+ break;
+ default:
+ return -ENODEV;
+ break;
+ }
+
+ for (i = 0; i < (sizeof(ms02nv_addrs) / sizeof(*ms02nv_addrs)); i++)
+ if (!ms02nv_init_one(ms02nv_addrs[i] << stride))
+ count++;
+
+ return (count > 0) ? 0 : -ENODEV;
+}
+
+static void __exit ms02nv_cleanup(void)
+{
+ while (root_ms02nv_mtd)
+ ms02nv_remove_one();
+}
+
+
+module_init(ms02nv_init);
+module_exit(ms02nv_cleanup);
diff --git a/drivers/mtd/devices/ms02-nv.h b/drivers/mtd/devices/ms02-nv.h
new file mode 100644
index 000000000000..8a6eef7cfee3
--- /dev/null
+++ b/drivers/mtd/devices/ms02-nv.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2001, 2003 Maciej W. Rozycki
+ *
+ * DEC MS02-NV (54-20948-01) battery backed-up NVRAM module for
+ * DECstation/DECsystem 5000/2x0 and DECsystem 5900 and 5900/260
+ * systems.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * $Id: ms02-nv.h,v 1.3 2003/08/19 09:25:36 dwmw2 Exp $
+ */
+
+#include <linux/ioport.h>
+#include <linux/mtd/mtd.h>
+
+/*
+ * Addresses are decoded as follows:
+ *
+ * 0x000000 - 0x3fffff SRAM
+ * 0x400000 - 0x7fffff CSR
+ *
+ * Within the SRAM area the following ranges are forced by the system
+ * firmware:
+ *
+ * 0x000000 - 0x0003ff diagnostic area, destroyed upon a reboot
+ * 0x000400 - ENDofRAM storage area, available to operating systems
+ *
+ * but we can't really use the available area right from 0x000400 as
+ * the first word is used by the firmware as a status flag passed
+ * from an operating system. If anything but the valid data magic
+ * ID value is found, the firmware considers the SRAM clean, i.e.
+ * containing no valid data, and disables the battery resulting in
+ * data being erased as soon as power is switched off. So the choice
+ * for the start address of the user-available is 0x001000 which is
+ * nicely page aligned. The area between 0x000404 and 0x000fff may
+ * be used by the driver for own needs.
+ *
+ * The diagnostic area defines two status words to be read by an
+ * operating system, a magic ID to distinguish a MS02-NV board from
+ * anything else and a status information providing results of tests
+ * as well as the size of SRAM available, which can be 1MiB or 2MiB
+ * (that's what the firmware handles; no idea if 2MiB modules ever
+ * existed).
+ *
+ * The firmware only handles the MS02-NV board if installed in the
+ * last (15th) slot, so for any other location the status information
+ * stored in the SRAM cannot be relied upon. But from the hardware
+ * point of view there is no problem using up to 14 such boards in a
+ * system -- only the 1st slot needs to be filled with a DRAM module.
+ * The MS02-NV board is ECC-protected, like other MS02 memory boards.
+ *
+ * The state of the battery as provided by the CSR is reflected on
+ * the two onboard LEDs. When facing the battery side of the board,
+ * with the LEDs at the top left and the battery at the bottom right
+ * (i.e. looking from the back side of the system box), their meaning
+ * is as follows (the system has to be powered on):
+ *
+ * left LED battery disable status: lit = enabled
+ * right LED battery condition status: lit = OK
+ */
+
+/* MS02-NV iomem register offsets. */
+#define MS02NV_CSR 0x400000 /* control & status register */
+
+/* MS02-NV CSR status bits. */
+#define MS02NV_CSR_BATT_OK 0x01 /* battery OK */
+#define MS02NV_CSR_BATT_OFF 0x02 /* battery disabled */
+
+
+/* MS02-NV memory offsets. */
+#define MS02NV_DIAG 0x0003f8 /* diagnostic status */
+#define MS02NV_MAGIC 0x0003fc /* MS02-NV magic ID */
+#define MS02NV_VALID 0x000400 /* valid data magic ID */
+#define MS02NV_RAM 0x001000 /* user-exposed RAM start */
+
+/* MS02-NV diagnostic status bits. */
+#define MS02NV_DIAG_TEST 0x01 /* SRAM test done (?) */
+#define MS02NV_DIAG_RO 0x02 /* SRAM r/o test done */
+#define MS02NV_DIAG_RW 0x04 /* SRAM r/w test done */
+#define MS02NV_DIAG_FAIL 0x08 /* SRAM test failed */
+#define MS02NV_DIAG_SIZE_MASK 0xf0 /* SRAM size mask */
+#define MS02NV_DIAG_SIZE_SHIFT 0x10 /* SRAM size shift (left) */
+
+/* MS02-NV general constants. */
+#define MS02NV_ID 0x03021966 /* MS02-NV magic ID value */
+#define MS02NV_VALID_ID 0xbd100248 /* valid data magic ID value */
+#define MS02NV_SLOT_SIZE 0x800000 /* size of the address space
+ decoded by the module */
+
+
+typedef volatile u32 ms02nv_uint;
+
+struct ms02nv_private {
+ struct mtd_info *next;
+ struct {
+ struct resource *module;
+ struct resource *diag_ram;
+ struct resource *user_ram;
+ struct resource *csr;
+ } resource;
+ u_char *addr;
+ size_t size;
+ u_char *uaddr;
+};
diff --git a/drivers/mtd/devices/mtdram.c b/drivers/mtd/devices/mtdram.c
new file mode 100644
index 000000000000..edac4156d69c
--- /dev/null
+++ b/drivers/mtd/devices/mtdram.c
@@ -0,0 +1,235 @@
+/*
+ * mtdram - a test mtd device
+ * $Id: mtdram.c,v 1.35 2005/01/05 18:05:12 dwmw2 Exp $
+ * Author: Alexander Larsson <alex@cendio.se>
+ *
+ * Copyright (c) 1999 Alexander Larsson <alex@cendio.se>
+ *
+ * This code is GPL
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <linux/mtd/compatmac.h>
+#include <linux/mtd/mtd.h>
+
+#ifndef CONFIG_MTDRAM_ABS_POS
+ #define CONFIG_MTDRAM_ABS_POS 0
+#endif
+
+#if CONFIG_MTDRAM_ABS_POS > 0
+ #include <asm/io.h>
+#endif
+
+#ifdef MODULE
+static unsigned long total_size = CONFIG_MTDRAM_TOTAL_SIZE;
+static unsigned long erase_size = CONFIG_MTDRAM_ERASE_SIZE;
+module_param(total_size,ulong,0);
+MODULE_PARM_DESC(total_size, "Total device size in KiB");
+module_param(erase_size,ulong,0);
+MODULE_PARM_DESC(erase_size, "Device erase block size in KiB");
+#define MTDRAM_TOTAL_SIZE (total_size * 1024)
+#define MTDRAM_ERASE_SIZE (erase_size * 1024)
+#else
+#define MTDRAM_TOTAL_SIZE (CONFIG_MTDRAM_TOTAL_SIZE * 1024)
+#define MTDRAM_ERASE_SIZE (CONFIG_MTDRAM_ERASE_SIZE * 1024)
+#endif
+
+
+// We could store these in the mtd structure, but we only support 1 device..
+static struct mtd_info *mtd_info;
+
+
+static int
+ram_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ DEBUG(MTD_DEBUG_LEVEL2, "ram_erase(pos:%ld, len:%ld)\n", (long)instr->addr, (long)instr->len);
+ if (instr->addr + instr->len > mtd->size) {
+ DEBUG(MTD_DEBUG_LEVEL1, "ram_erase() out of bounds (%ld > %ld)\n", (long)(instr->addr + instr->len), (long)mtd->size);
+ return -EINVAL;
+ }
+
+ memset((char *)mtd->priv + instr->addr, 0xff, instr->len);
+
+ instr->state = MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+
+ return 0;
+}
+
+static int ram_point (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf)
+{
+ if (from + len > mtd->size)
+ return -EINVAL;
+
+ *mtdbuf = mtd->priv + from;
+ *retlen = len;
+ return 0;
+}
+
+static void ram_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from,
+ size_t len)
+{
+ DEBUG(MTD_DEBUG_LEVEL2, "ram_unpoint\n");
+}
+
+static int ram_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ DEBUG(MTD_DEBUG_LEVEL2, "ram_read(pos:%ld, len:%ld)\n", (long)from, (long)len);
+ if (from + len > mtd->size) {
+ DEBUG(MTD_DEBUG_LEVEL1, "ram_read() out of bounds (%ld > %ld)\n", (long)(from + len), (long)mtd->size);
+ return -EINVAL;
+ }
+
+ memcpy(buf, mtd->priv + from, len);
+
+ *retlen=len;
+ return 0;
+}
+
+static int ram_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ DEBUG(MTD_DEBUG_LEVEL2, "ram_write(pos:%ld, len:%ld)\n", (long)to, (long)len);
+ if (to + len > mtd->size) {
+ DEBUG(MTD_DEBUG_LEVEL1, "ram_write() out of bounds (%ld > %ld)\n", (long)(to + len), (long)mtd->size);
+ return -EINVAL;
+ }
+
+ memcpy ((char *)mtd->priv + to, buf, len);
+
+ *retlen=len;
+ return 0;
+}
+
+static void __exit cleanup_mtdram(void)
+{
+ if (mtd_info) {
+ del_mtd_device(mtd_info);
+#if CONFIG_MTDRAM_TOTAL_SIZE > 0
+ if (mtd_info->priv)
+#if CONFIG_MTDRAM_ABS_POS > 0
+ iounmap(mtd_info->priv);
+#else
+ vfree(mtd_info->priv);
+#endif
+#endif
+ kfree(mtd_info);
+ }
+}
+
+int mtdram_init_device(struct mtd_info *mtd, void *mapped_address,
+ unsigned long size, char *name)
+{
+ memset(mtd, 0, sizeof(*mtd));
+
+ /* Setup the MTD structure */
+ mtd->name = name;
+ mtd->type = MTD_RAM;
+ mtd->flags = MTD_CAP_RAM;
+ mtd->size = size;
+ mtd->erasesize = MTDRAM_ERASE_SIZE;
+ mtd->priv = mapped_address;
+
+ mtd->owner = THIS_MODULE;
+ mtd->erase = ram_erase;
+ mtd->point = ram_point;
+ mtd->unpoint = ram_unpoint;
+ mtd->read = ram_read;
+ mtd->write = ram_write;
+
+ if (add_mtd_device(mtd)) {
+ return -EIO;
+ }
+
+ return 0;
+}
+
+#if CONFIG_MTDRAM_TOTAL_SIZE > 0
+#if CONFIG_MTDRAM_ABS_POS > 0
+static int __init init_mtdram(void)
+{
+ void *addr;
+ int err;
+ /* Allocate some memory */
+ mtd_info = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
+ if (!mtd_info)
+ return -ENOMEM;
+
+ addr = ioremap(CONFIG_MTDRAM_ABS_POS, MTDRAM_TOTAL_SIZE);
+ if (!addr) {
+ DEBUG(MTD_DEBUG_LEVEL1,
+ "Failed to ioremap) memory region of size %ld at ABS_POS:%ld\n",
+ (long)MTDRAM_TOTAL_SIZE, (long)CONFIG_MTDRAM_ABS_POS);
+ kfree(mtd_info);
+ mtd_info = NULL;
+ return -ENOMEM;
+ }
+ err = mtdram_init_device(mtd_info, addr,
+ MTDRAM_TOTAL_SIZE, "mtdram test device");
+ if (err)
+ {
+ iounmap(addr);
+ kfree(mtd_info);
+ mtd_info = NULL;
+ return err;
+ }
+ memset(mtd_info->priv, 0xff, MTDRAM_TOTAL_SIZE);
+ return err;
+}
+
+#else /* CONFIG_MTDRAM_ABS_POS > 0 */
+
+static int __init init_mtdram(void)
+{
+ void *addr;
+ int err;
+ /* Allocate some memory */
+ mtd_info = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
+ if (!mtd_info)
+ return -ENOMEM;
+
+ addr = vmalloc(MTDRAM_TOTAL_SIZE);
+ if (!addr) {
+ DEBUG(MTD_DEBUG_LEVEL1,
+ "Failed to vmalloc memory region of size %ld\n",
+ (long)MTDRAM_TOTAL_SIZE);
+ kfree(mtd_info);
+ mtd_info = NULL;
+ return -ENOMEM;
+ }
+ err = mtdram_init_device(mtd_info, addr,
+ MTDRAM_TOTAL_SIZE, "mtdram test device");
+ if (err)
+ {
+ vfree(addr);
+ kfree(mtd_info);
+ mtd_info = NULL;
+ return err;
+ }
+ memset(mtd_info->priv, 0xff, MTDRAM_TOTAL_SIZE);
+ return err;
+}
+#endif /* !(CONFIG_MTDRAM_ABS_POS > 0) */
+
+#else /* CONFIG_MTDRAM_TOTAL_SIZE > 0 */
+
+static int __init init_mtdram(void)
+{
+ return 0;
+}
+#endif /* !(CONFIG_MTDRAM_TOTAL_SIZE > 0) */
+
+module_init(init_mtdram);
+module_exit(cleanup_mtdram);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alexander Larsson <alexl@redhat.com>");
+MODULE_DESCRIPTION("Simulated MTD driver for testing");
+
diff --git a/drivers/mtd/devices/phram.c b/drivers/mtd/devices/phram.c
new file mode 100644
index 000000000000..5f8e164ddb71
--- /dev/null
+++ b/drivers/mtd/devices/phram.c
@@ -0,0 +1,285 @@
+/**
+ * $Id: phram.c,v 1.11 2005/01/05 18:05:13 dwmw2 Exp $
+ *
+ * Copyright (c) ???? Jochen Schuble <psionic@psionic.de>
+ * Copyright (c) 2003-2004 Jrn Engel <joern@wh.fh-wedel.de>
+ *
+ * Usage:
+ *
+ * one commend line parameter per device, each in the form:
+ * phram=<name>,<start>,<len>
+ * <name> may be up to 63 characters.
+ * <start> and <len> can be octal, decimal or hexadecimal. If followed
+ * by "ki", "Mi" or "Gi", the numbers will be interpreted as kilo, mega or
+ * gigabytes.
+ *
+ * Example:
+ * phram=swap,64Mi,128Mi phram=test,900Mi,1Mi
+ *
+ */
+
+#include <asm/io.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mtd/mtd.h>
+
+#define ERROR(fmt, args...) printk(KERN_ERR "phram: " fmt , ## args)
+
+struct phram_mtd_list {
+ struct mtd_info mtd;
+ struct list_head list;
+};
+
+static LIST_HEAD(phram_list);
+
+
+
+static int phram_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ u_char *start = mtd->priv;
+
+ if (instr->addr + instr->len > mtd->size)
+ return -EINVAL;
+
+ memset(start + instr->addr, 0xff, instr->len);
+
+ /* This'll catch a few races. Free the thing before returning :)
+ * I don't feel at all ashamed. This kind of thing is possible anyway
+ * with flash, but unlikely.
+ */
+
+ instr->state = MTD_ERASE_DONE;
+
+ mtd_erase_callback(instr);
+
+ return 0;
+}
+
+static int phram_point(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char **mtdbuf)
+{
+ u_char *start = mtd->priv;
+
+ if (from + len > mtd->size)
+ return -EINVAL;
+
+ *mtdbuf = start + from;
+ *retlen = len;
+ return 0;
+}
+
+static void phram_unpoint(struct mtd_info *mtd, u_char *addr, loff_t from, size_t len)
+{
+}
+
+static int phram_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ u_char *start = mtd->priv;
+
+ if (from + len > mtd->size)
+ return -EINVAL;
+
+ memcpy(buf, start + from, len);
+
+ *retlen = len;
+ return 0;
+}
+
+static int phram_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ u_char *start = mtd->priv;
+
+ if (to + len > mtd->size)
+ return -EINVAL;
+
+ memcpy(start + to, buf, len);
+
+ *retlen = len;
+ return 0;
+}
+
+
+
+static void unregister_devices(void)
+{
+ struct phram_mtd_list *this;
+
+ list_for_each_entry(this, &phram_list, list) {
+ del_mtd_device(&this->mtd);
+ iounmap(this->mtd.priv);
+ kfree(this);
+ }
+}
+
+static int register_device(char *name, unsigned long start, unsigned long len)
+{
+ struct phram_mtd_list *new;
+ int ret = -ENOMEM;
+
+ new = kmalloc(sizeof(*new), GFP_KERNEL);
+ if (!new)
+ goto out0;
+
+ memset(new, 0, sizeof(*new));
+
+ ret = -EIO;
+ new->mtd.priv = ioremap(start, len);
+ if (!new->mtd.priv) {
+ ERROR("ioremap failed\n");
+ goto out1;
+ }
+
+
+ new->mtd.name = name;
+ new->mtd.size = len;
+ new->mtd.flags = MTD_CAP_RAM | MTD_ERASEABLE | MTD_VOLATILE;
+ new->mtd.erase = phram_erase;
+ new->mtd.point = phram_point;
+ new->mtd.unpoint = phram_unpoint;
+ new->mtd.read = phram_read;
+ new->mtd.write = phram_write;
+ new->mtd.owner = THIS_MODULE;
+ new->mtd.type = MTD_RAM;
+ new->mtd.erasesize = 0;
+
+ ret = -EAGAIN;
+ if (add_mtd_device(&new->mtd)) {
+ ERROR("Failed to register new device\n");
+ goto out2;
+ }
+
+ list_add_tail(&new->list, &phram_list);
+ return 0;
+
+out2:
+ iounmap(new->mtd.priv);
+out1:
+ kfree(new);
+out0:
+ return ret;
+}
+
+static int ustrtoul(const char *cp, char **endp, unsigned int base)
+{
+ unsigned long result = simple_strtoul(cp, endp, base);
+
+ switch (**endp) {
+ case 'G':
+ result *= 1024;
+ case 'M':
+ result *= 1024;
+ case 'k':
+ result *= 1024;
+ /* By dwmw2 editorial decree, "ki", "Mi" or "Gi" are to be used. */
+ if ((*endp)[1] == 'i')
+ (*endp) += 2;
+ }
+ return result;
+}
+
+static int parse_num32(uint32_t *num32, const char *token)
+{
+ char *endp;
+ unsigned long n;
+
+ n = ustrtoul(token, &endp, 0);
+ if (*endp)
+ return -EINVAL;
+
+ *num32 = n;
+ return 0;
+}
+
+static int parse_name(char **pname, const char *token)
+{
+ size_t len;
+ char *name;
+
+ len = strlen(token) + 1;
+ if (len > 64)
+ return -ENOSPC;
+
+ name = kmalloc(len, GFP_KERNEL);
+ if (!name)
+ return -ENOMEM;
+
+ strcpy(name, token);
+
+ *pname = name;
+ return 0;
+}
+
+#define parse_err(fmt, args...) do { \
+ ERROR(fmt , ## args); \
+ return 0; \
+} while (0)
+
+static int phram_setup(const char *val, struct kernel_param *kp)
+{
+ char buf[64+12+12], *str = buf;
+ char *token[3];
+ char *name;
+ uint32_t start;
+ uint32_t len;
+ int i, ret;
+
+ if (strnlen(val, sizeof(buf)) >= sizeof(buf))
+ parse_err("parameter too long\n");
+
+ strcpy(str, val);
+
+ for (i=0; i<3; i++)
+ token[i] = strsep(&str, ",");
+
+ if (str)
+ parse_err("too many arguments\n");
+
+ if (!token[2])
+ parse_err("not enough arguments\n");
+
+ ret = parse_name(&name, token[0]);
+ if (ret == -ENOMEM)
+ parse_err("out of memory\n");
+ if (ret == -ENOSPC)
+ parse_err("name too long\n");
+ if (ret)
+ return 0;
+
+ ret = parse_num32(&start, token[1]);
+ if (ret)
+ parse_err("illegal start address\n");
+
+ ret = parse_num32(&len, token[2]);
+ if (ret)
+ parse_err("illegal device length\n");
+
+ register_device(name, start, len);
+
+ return 0;
+}
+
+module_param_call(phram, phram_setup, NULL, NULL, 000);
+MODULE_PARM_DESC(phram,"Memory region to map. \"map=<name>,<start>,<length>\"");
+
+
+static int __init init_phram(void)
+{
+ return 0;
+}
+
+static void __exit cleanup_phram(void)
+{
+ unregister_devices();
+}
+
+module_init(init_phram);
+module_exit(cleanup_phram);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jrn Engel <joern@wh.fh-wedel.de>");
+MODULE_DESCRIPTION("MTD driver for physical RAM");
diff --git a/drivers/mtd/devices/pmc551.c b/drivers/mtd/devices/pmc551.c
new file mode 100644
index 000000000000..5b3defadf884
--- /dev/null
+++ b/drivers/mtd/devices/pmc551.c
@@ -0,0 +1,843 @@
+/*
+ * $Id: pmc551.c,v 1.30 2005/01/05 18:05:13 dwmw2 Exp $
+ *
+ * PMC551 PCI Mezzanine Ram Device
+ *
+ * Author:
+ * Mark Ferrell <mferrell@mvista.com>
+ * Copyright 1999,2000 Nortel Networks
+ *
+ * License:
+ * As part of this driver was derived from the slram.c driver it
+ * falls under the same license, which is GNU General Public
+ * License v2
+ *
+ * Description:
+ * This driver is intended to support the PMC551 PCI Ram device
+ * from Ramix Inc. The PMC551 is a PMC Mezzanine module for
+ * cPCI embedded systems. The device contains a single SROM
+ * that initially programs the V370PDC chipset onboard the
+ * device, and various banks of DRAM/SDRAM onboard. This driver
+ * implements this PCI Ram device as an MTD (Memory Technology
+ * Device) so that it can be used to hold a file system, or for
+ * added swap space in embedded systems. Since the memory on
+ * this board isn't as fast as main memory we do not try to hook
+ * it into main memory as that would simply reduce performance
+ * on the system. Using it as a block device allows us to use
+ * it as high speed swap or for a high speed disk device of some
+ * sort. Which becomes very useful on diskless systems in the
+ * embedded market I might add.
+ *
+ * Notes:
+ * Due to what I assume is more buggy SROM, the 64M PMC551 I
+ * have available claims that all 4 of it's DRAM banks have 64M
+ * of ram configured (making a grand total of 256M onboard).
+ * This is slightly annoying since the BAR0 size reflects the
+ * aperture size, not the dram size, and the V370PDC supplies no
+ * other method for memory size discovery. This problem is
+ * mostly only relevant when compiled as a module, as the
+ * unloading of the module with an aperture size smaller then
+ * the ram will cause the driver to detect the onboard memory
+ * size to be equal to the aperture size when the module is
+ * reloaded. Soooo, to help, the module supports an msize
+ * option to allow the specification of the onboard memory, and
+ * an asize option, to allow the specification of the aperture
+ * size. The aperture must be equal to or less then the memory
+ * size, the driver will correct this if you screw it up. This
+ * problem is not relevant for compiled in drivers as compiled
+ * in drivers only init once.
+ *
+ * Credits:
+ * Saeed Karamooz <saeed@ramix.com> of Ramix INC. for the
+ * initial example code of how to initialize this device and for
+ * help with questions I had concerning operation of the device.
+ *
+ * Most of the MTD code for this driver was originally written
+ * for the slram.o module in the MTD drivers package which
+ * allows the mapping of system memory into an MTD device.
+ * Since the PMC551 memory module is accessed in the same
+ * fashion as system memory, the slram.c code became a very nice
+ * fit to the needs of this driver. All we added was PCI
+ * detection/initialization to the driver and automatically figure
+ * out the size via the PCI detection.o, later changes by Corey
+ * Minyard set up the card to utilize a 1M sliding apature.
+ *
+ * Corey Minyard <minyard@nortelnetworks.com>
+ * * Modified driver to utilize a sliding aperture instead of
+ * mapping all memory into kernel space which turned out to
+ * be very wasteful.
+ * * Located a bug in the SROM's initialization sequence that
+ * made the memory unusable, added a fix to code to touch up
+ * the DRAM some.
+ *
+ * Bugs/FIXME's:
+ * * MUST fix the init function to not spin on a register
+ * waiting for it to set .. this does not safely handle busted
+ * devices that never reset the register correctly which will
+ * cause the system to hang w/ a reboot being the only chance at
+ * recover. [sort of fixed, could be better]
+ * * Add I2C handling of the SROM so we can read the SROM's information
+ * about the aperture size. This should always accurately reflect the
+ * onboard memory size.
+ * * Comb the init routine. It's still a bit cludgy on a few things.
+ */
+
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/ptrace.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/major.h>
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <linux/pci.h>
+
+#ifndef CONFIG_PCI
+#error Enable PCI in your kernel config
+#endif
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/pmc551.h>
+#include <linux/mtd/compatmac.h>
+
+static struct mtd_info *pmc551list;
+
+static int pmc551_erase (struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct mypriv *priv = mtd->priv;
+ u32 soff_hi, soff_lo; /* start address offset hi/lo */
+ u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
+ unsigned long end;
+ u_char *ptr;
+ size_t retlen;
+
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk(KERN_DEBUG "pmc551_erase(pos:%ld, len:%ld)\n", (long)instr->addr, (long)instr->len);
+#endif
+
+ end = instr->addr + instr->len - 1;
+
+ /* Is it past the end? */
+ if ( end > mtd->size ) {
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk(KERN_DEBUG "pmc551_erase() out of bounds (%ld > %ld)\n", (long)end, (long)mtd->size);
+#endif
+ return -EINVAL;
+ }
+
+ eoff_hi = end & ~(priv->asize - 1);
+ soff_hi = instr->addr & ~(priv->asize - 1);
+ eoff_lo = end & (priv->asize - 1);
+ soff_lo = instr->addr & (priv->asize - 1);
+
+ pmc551_point (mtd, instr->addr, instr->len, &retlen, &ptr);
+
+ if ( soff_hi == eoff_hi || mtd->size == priv->asize) {
+ /* The whole thing fits within one access, so just one shot
+ will do it. */
+ memset(ptr, 0xff, instr->len);
+ } else {
+ /* We have to do multiple writes to get all the data
+ written. */
+ while (soff_hi != eoff_hi) {
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk( KERN_DEBUG "pmc551_erase() soff_hi: %ld, eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
+#endif
+ memset(ptr, 0xff, priv->asize);
+ if (soff_hi + priv->asize >= mtd->size) {
+ goto out;
+ }
+ soff_hi += priv->asize;
+ pmc551_point (mtd,(priv->base_map0|soff_hi),
+ priv->asize, &retlen, &ptr);
+ }
+ memset (ptr, 0xff, eoff_lo);
+ }
+
+out:
+ instr->state = MTD_ERASE_DONE;
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk(KERN_DEBUG "pmc551_erase() done\n");
+#endif
+
+ mtd_erase_callback(instr);
+ return 0;
+}
+
+
+static int pmc551_point (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf)
+{
+ struct mypriv *priv = mtd->priv;
+ u32 soff_hi;
+ u32 soff_lo;
+
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk(KERN_DEBUG "pmc551_point(%ld, %ld)\n", (long)from, (long)len);
+#endif
+
+ if (from + len > mtd->size) {
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk(KERN_DEBUG "pmc551_point() out of bounds (%ld > %ld)\n", (long)from+len, (long)mtd->size);
+#endif
+ return -EINVAL;
+ }
+
+ soff_hi = from & ~(priv->asize - 1);
+ soff_lo = from & (priv->asize - 1);
+
+ /* Cheap hack optimization */
+ if( priv->curr_map0 != from ) {
+ pci_write_config_dword ( priv->dev, PMC551_PCI_MEM_MAP0,
+ (priv->base_map0 | soff_hi) );
+ priv->curr_map0 = soff_hi;
+ }
+
+ *mtdbuf = priv->start + soff_lo;
+ *retlen = len;
+ return 0;
+}
+
+
+static void pmc551_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from, size_t len)
+{
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk(KERN_DEBUG "pmc551_unpoint()\n");
+#endif
+}
+
+
+static int pmc551_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
+{
+ struct mypriv *priv = mtd->priv;
+ u32 soff_hi, soff_lo; /* start address offset hi/lo */
+ u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
+ unsigned long end;
+ u_char *ptr;
+ u_char *copyto = buf;
+
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk(KERN_DEBUG "pmc551_read(pos:%ld, len:%ld) asize: %ld\n", (long)from, (long)len, (long)priv->asize);
+#endif
+
+ end = from + len - 1;
+
+ /* Is it past the end? */
+ if (end > mtd->size) {
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk(KERN_DEBUG "pmc551_read() out of bounds (%ld > %ld)\n", (long) end, (long)mtd->size);
+#endif
+ return -EINVAL;
+ }
+
+ soff_hi = from & ~(priv->asize - 1);
+ eoff_hi = end & ~(priv->asize - 1);
+ soff_lo = from & (priv->asize - 1);
+ eoff_lo = end & (priv->asize - 1);
+
+ pmc551_point (mtd, from, len, retlen, &ptr);
+
+ if (soff_hi == eoff_hi) {
+ /* The whole thing fits within one access, so just one shot
+ will do it. */
+ memcpy(copyto, ptr, len);
+ copyto += len;
+ } else {
+ /* We have to do multiple writes to get all the data
+ written. */
+ while (soff_hi != eoff_hi) {
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk( KERN_DEBUG "pmc551_read() soff_hi: %ld, eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
+#endif
+ memcpy(copyto, ptr, priv->asize);
+ copyto += priv->asize;
+ if (soff_hi + priv->asize >= mtd->size) {
+ goto out;
+ }
+ soff_hi += priv->asize;
+ pmc551_point (mtd, soff_hi, priv->asize, retlen, &ptr);
+ }
+ memcpy(copyto, ptr, eoff_lo);
+ copyto += eoff_lo;
+ }
+
+out:
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk(KERN_DEBUG "pmc551_read() done\n");
+#endif
+ *retlen = copyto - buf;
+ return 0;
+}
+
+static int pmc551_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf)
+{
+ struct mypriv *priv = mtd->priv;
+ u32 soff_hi, soff_lo; /* start address offset hi/lo */
+ u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
+ unsigned long end;
+ u_char *ptr;
+ const u_char *copyfrom = buf;
+
+
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk(KERN_DEBUG "pmc551_write(pos:%ld, len:%ld) asize:%ld\n", (long)to, (long)len, (long)priv->asize);
+#endif
+
+ end = to + len - 1;
+ /* Is it past the end? or did the u32 wrap? */
+ if (end > mtd->size ) {
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk(KERN_DEBUG "pmc551_write() out of bounds (end: %ld, size: %ld, to: %ld)\n", (long) end, (long)mtd->size, (long)to);
+#endif
+ return -EINVAL;
+ }
+
+ soff_hi = to & ~(priv->asize - 1);
+ eoff_hi = end & ~(priv->asize - 1);
+ soff_lo = to & (priv->asize - 1);
+ eoff_lo = end & (priv->asize - 1);
+
+ pmc551_point (mtd, to, len, retlen, &ptr);
+
+ if (soff_hi == eoff_hi) {
+ /* The whole thing fits within one access, so just one shot
+ will do it. */
+ memcpy(ptr, copyfrom, len);
+ copyfrom += len;
+ } else {
+ /* We have to do multiple writes to get all the data
+ written. */
+ while (soff_hi != eoff_hi) {
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk( KERN_DEBUG "pmc551_write() soff_hi: %ld, eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
+#endif
+ memcpy(ptr, copyfrom, priv->asize);
+ copyfrom += priv->asize;
+ if (soff_hi >= mtd->size) {
+ goto out;
+ }
+ soff_hi += priv->asize;
+ pmc551_point (mtd, soff_hi, priv->asize, retlen, &ptr);
+ }
+ memcpy(ptr, copyfrom, eoff_lo);
+ copyfrom += eoff_lo;
+ }
+
+out:
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk(KERN_DEBUG "pmc551_write() done\n");
+#endif
+ *retlen = copyfrom - buf;
+ return 0;
+}
+
+/*
+ * Fixup routines for the V370PDC
+ * PCI device ID 0x020011b0
+ *
+ * This function basicly kick starts the DRAM oboard the card and gets it
+ * ready to be used. Before this is done the device reads VERY erratic, so
+ * much that it can crash the Linux 2.2.x series kernels when a user cat's
+ * /proc/pci .. though that is mainly a kernel bug in handling the PCI DEVSEL
+ * register. FIXME: stop spinning on registers .. must implement a timeout
+ * mechanism
+ * returns the size of the memory region found.
+ */
+static u32 fixup_pmc551 (struct pci_dev *dev)
+{
+#ifdef CONFIG_MTD_PMC551_BUGFIX
+ u32 dram_data;
+#endif
+ u32 size, dcmd, cfg, dtmp;
+ u16 cmd, tmp, i;
+ u8 bcmd, counter;
+
+ /* Sanity Check */
+ if(!dev) {
+ return -ENODEV;
+ }
+
+ /*
+ * Attempt to reset the card
+ * FIXME: Stop Spinning registers
+ */
+ counter=0;
+ /* unlock registers */
+ pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, 0xA5 );
+ /* read in old data */
+ pci_read_config_byte(dev, PMC551_SYS_CTRL_REG, &bcmd );
+ /* bang the reset line up and down for a few */
+ for(i=0;i<10;i++) {
+ counter=0;
+ bcmd &= ~0x80;
+ while(counter++ < 100) {
+ pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
+ }
+ counter=0;
+ bcmd |= 0x80;
+ while(counter++ < 100) {
+ pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
+ }
+ }
+ bcmd |= (0x40|0x20);
+ pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
+
+ /*
+ * Take care and turn off the memory on the device while we
+ * tweak the configurations
+ */
+ pci_read_config_word(dev, PCI_COMMAND, &cmd);
+ tmp = cmd & ~(PCI_COMMAND_IO|PCI_COMMAND_MEMORY);
+ pci_write_config_word(dev, PCI_COMMAND, tmp);
+
+ /*
+ * Disable existing aperture before probing memory size
+ */
+ pci_read_config_dword(dev, PMC551_PCI_MEM_MAP0, &dcmd);
+ dtmp=(dcmd|PMC551_PCI_MEM_MAP_ENABLE|PMC551_PCI_MEM_MAP_REG_EN);
+ pci_write_config_dword(dev, PMC551_PCI_MEM_MAP0, dtmp);
+ /*
+ * Grab old BAR0 config so that we can figure out memory size
+ * This is another bit of kludge going on. The reason for the
+ * redundancy is I am hoping to retain the original configuration
+ * previously assigned to the card by the BIOS or some previous
+ * fixup routine in the kernel. So we read the old config into cfg,
+ * then write all 1's to the memory space, read back the result into
+ * "size", and then write back all the old config.
+ */
+ pci_read_config_dword( dev, PCI_BASE_ADDRESS_0, &cfg );
+#ifndef CONFIG_MTD_PMC551_BUGFIX
+ pci_write_config_dword( dev, PCI_BASE_ADDRESS_0, ~0 );
+ pci_read_config_dword( dev, PCI_BASE_ADDRESS_0, &size );
+ size = (size&PCI_BASE_ADDRESS_MEM_MASK);
+ size &= ~(size-1);
+ pci_write_config_dword( dev, PCI_BASE_ADDRESS_0, cfg );
+#else
+ /*
+ * Get the size of the memory by reading all the DRAM size values
+ * and adding them up.
+ *
+ * KLUDGE ALERT: the boards we are using have invalid column and
+ * row mux values. We fix them here, but this will break other
+ * memory configurations.
+ */
+ pci_read_config_dword(dev, PMC551_DRAM_BLK0, &dram_data);
+ size = PMC551_DRAM_BLK_GET_SIZE(dram_data);
+ dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
+ dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
+ pci_write_config_dword(dev, PMC551_DRAM_BLK0, dram_data);
+
+ pci_read_config_dword(dev, PMC551_DRAM_BLK1, &dram_data);
+ size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
+ dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
+ dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
+ pci_write_config_dword(dev, PMC551_DRAM_BLK1, dram_data);
+
+ pci_read_config_dword(dev, PMC551_DRAM_BLK2, &dram_data);
+ size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
+ dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
+ dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
+ pci_write_config_dword(dev, PMC551_DRAM_BLK2, dram_data);
+
+ pci_read_config_dword(dev, PMC551_DRAM_BLK3, &dram_data);
+ size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
+ dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
+ dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
+ pci_write_config_dword(dev, PMC551_DRAM_BLK3, dram_data);
+
+ /*
+ * Oops .. something went wrong
+ */
+ if( (size &= PCI_BASE_ADDRESS_MEM_MASK) == 0) {
+ return -ENODEV;
+ }
+#endif /* CONFIG_MTD_PMC551_BUGFIX */
+
+ if ((cfg&PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_MEMORY) {
+ return -ENODEV;
+ }
+
+ /*
+ * Precharge Dram
+ */
+ pci_write_config_word( dev, PMC551_SDRAM_MA, 0x0400 );
+ pci_write_config_word( dev, PMC551_SDRAM_CMD, 0x00bf );
+
+ /*
+ * Wait until command has gone through
+ * FIXME: register spinning issue
+ */
+ do { pci_read_config_word( dev, PMC551_SDRAM_CMD, &cmd );
+ if(counter++ > 100)break;
+ } while ( (PCI_COMMAND_IO) & cmd );
+
+ /*
+ * Turn on auto refresh
+ * The loop is taken directly from Ramix's example code. I assume that
+ * this must be held high for some duration of time, but I can find no
+ * documentation refrencing the reasons why.
+ */
+ for ( i = 1; i<=8 ; i++) {
+ pci_write_config_word (dev, PMC551_SDRAM_CMD, 0x0df);
+
+ /*
+ * Make certain command has gone through
+ * FIXME: register spinning issue
+ */
+ counter=0;
+ do { pci_read_config_word(dev, PMC551_SDRAM_CMD, &cmd);
+ if(counter++ > 100)break;
+ } while ( (PCI_COMMAND_IO) & cmd );
+ }
+
+ pci_write_config_word ( dev, PMC551_SDRAM_MA, 0x0020);
+ pci_write_config_word ( dev, PMC551_SDRAM_CMD, 0x0ff);
+
+ /*
+ * Wait until command completes
+ * FIXME: register spinning issue
+ */
+ counter=0;
+ do { pci_read_config_word ( dev, PMC551_SDRAM_CMD, &cmd);
+ if(counter++ > 100)break;
+ } while ( (PCI_COMMAND_IO) & cmd );
+
+ pci_read_config_dword ( dev, PMC551_DRAM_CFG, &dcmd);
+ dcmd |= 0x02000000;
+ pci_write_config_dword ( dev, PMC551_DRAM_CFG, dcmd);
+
+ /*
+ * Check to make certain fast back-to-back, if not
+ * then set it so
+ */
+ pci_read_config_word( dev, PCI_STATUS, &cmd);
+ if((cmd&PCI_COMMAND_FAST_BACK) == 0) {
+ cmd |= PCI_COMMAND_FAST_BACK;
+ pci_write_config_word( dev, PCI_STATUS, cmd);
+ }
+
+ /*
+ * Check to make certain the DEVSEL is set correctly, this device
+ * has a tendancy to assert DEVSEL and TRDY when a write is performed
+ * to the memory when memory is read-only
+ */
+ if((cmd&PCI_STATUS_DEVSEL_MASK) != 0x0) {
+ cmd &= ~PCI_STATUS_DEVSEL_MASK;
+ pci_write_config_word( dev, PCI_STATUS, cmd );
+ }
+ /*
+ * Set to be prefetchable and put everything back based on old cfg.
+ * it's possible that the reset of the V370PDC nuked the original
+ * setup
+ */
+ /*
+ cfg |= PCI_BASE_ADDRESS_MEM_PREFETCH;
+ pci_write_config_dword( dev, PCI_BASE_ADDRESS_0, cfg );
+ */
+
+ /*
+ * Turn PCI memory and I/O bus access back on
+ */
+ pci_write_config_word( dev, PCI_COMMAND,
+ PCI_COMMAND_MEMORY | PCI_COMMAND_IO );
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ /*
+ * Some screen fun
+ */
+ printk(KERN_DEBUG "pmc551: %d%c (0x%x) of %sprefetchable memory at 0x%lx\n",
+ (size<1024)?size:(size<1048576)?size>>10:size>>20,
+ (size<1024)?'B':(size<1048576)?'K':'M',
+ size, ((dcmd&(0x1<<3)) == 0)?"non-":"",
+ (dev->resource[0].start)&PCI_BASE_ADDRESS_MEM_MASK );
+
+ /*
+ * Check to see the state of the memory
+ */
+ pci_read_config_dword( dev, PMC551_DRAM_BLK0, &dcmd );
+ printk(KERN_DEBUG "pmc551: DRAM_BLK0 Flags: %s,%s\n"
+ "pmc551: DRAM_BLK0 Size: %d at %d\n"
+ "pmc551: DRAM_BLK0 Row MUX: %d, Col MUX: %d\n",
+ (((0x1<<1)&dcmd) == 0)?"RW":"RO",
+ (((0x1<<0)&dcmd) == 0)?"Off":"On",
+ PMC551_DRAM_BLK_GET_SIZE(dcmd),
+ ((dcmd>>20)&0x7FF), ((dcmd>>13)&0x7), ((dcmd>>9)&0xF) );
+
+ pci_read_config_dword( dev, PMC551_DRAM_BLK1, &dcmd );
+ printk(KERN_DEBUG "pmc551: DRAM_BLK1 Flags: %s,%s\n"
+ "pmc551: DRAM_BLK1 Size: %d at %d\n"
+ "pmc551: DRAM_BLK1 Row MUX: %d, Col MUX: %d\n",
+ (((0x1<<1)&dcmd) == 0)?"RW":"RO",
+ (((0x1<<0)&dcmd) == 0)?"Off":"On",
+ PMC551_DRAM_BLK_GET_SIZE(dcmd),
+ ((dcmd>>20)&0x7FF), ((dcmd>>13)&0x7), ((dcmd>>9)&0xF) );
+
+ pci_read_config_dword( dev, PMC551_DRAM_BLK2, &dcmd );
+ printk(KERN_DEBUG "pmc551: DRAM_BLK2 Flags: %s,%s\n"
+ "pmc551: DRAM_BLK2 Size: %d at %d\n"
+ "pmc551: DRAM_BLK2 Row MUX: %d, Col MUX: %d\n",
+ (((0x1<<1)&dcmd) == 0)?"RW":"RO",
+ (((0x1<<0)&dcmd) == 0)?"Off":"On",
+ PMC551_DRAM_BLK_GET_SIZE(dcmd),
+ ((dcmd>>20)&0x7FF), ((dcmd>>13)&0x7), ((dcmd>>9)&0xF) );
+
+ pci_read_config_dword( dev, PMC551_DRAM_BLK3, &dcmd );
+ printk(KERN_DEBUG "pmc551: DRAM_BLK3 Flags: %s,%s\n"
+ "pmc551: DRAM_BLK3 Size: %d at %d\n"
+ "pmc551: DRAM_BLK3 Row MUX: %d, Col MUX: %d\n",
+ (((0x1<<1)&dcmd) == 0)?"RW":"RO",
+ (((0x1<<0)&dcmd) == 0)?"Off":"On",
+ PMC551_DRAM_BLK_GET_SIZE(dcmd),
+ ((dcmd>>20)&0x7FF), ((dcmd>>13)&0x7), ((dcmd>>9)&0xF) );
+
+ pci_read_config_word( dev, PCI_COMMAND, &cmd );
+ printk( KERN_DEBUG "pmc551: Memory Access %s\n",
+ (((0x1<<1)&cmd) == 0)?"off":"on" );
+ printk( KERN_DEBUG "pmc551: I/O Access %s\n",
+ (((0x1<<0)&cmd) == 0)?"off":"on" );
+
+ pci_read_config_word( dev, PCI_STATUS, &cmd );
+ printk( KERN_DEBUG "pmc551: Devsel %s\n",
+ ((PCI_STATUS_DEVSEL_MASK&cmd)==0x000)?"Fast":
+ ((PCI_STATUS_DEVSEL_MASK&cmd)==0x200)?"Medium":
+ ((PCI_STATUS_DEVSEL_MASK&cmd)==0x400)?"Slow":"Invalid" );
+
+ printk( KERN_DEBUG "pmc551: %sFast Back-to-Back\n",
+ ((PCI_COMMAND_FAST_BACK&cmd) == 0)?"Not ":"" );
+
+ pci_read_config_byte(dev, PMC551_SYS_CTRL_REG, &bcmd );
+ printk( KERN_DEBUG "pmc551: EEPROM is under %s control\n"
+ "pmc551: System Control Register is %slocked to PCI access\n"
+ "pmc551: System Control Register is %slocked to EEPROM access\n",
+ (bcmd&0x1)?"software":"hardware",
+ (bcmd&0x20)?"":"un", (bcmd&0x40)?"":"un");
+#endif
+ return size;
+}
+
+/*
+ * Kernel version specific module stuffages
+ */
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mark Ferrell <mferrell@mvista.com>");
+MODULE_DESCRIPTION(PMC551_VERSION);
+
+/*
+ * Stuff these outside the ifdef so as to not bust compiled in driver support
+ */
+static int msize=0;
+#if defined(CONFIG_MTD_PMC551_APERTURE_SIZE)
+static int asize=CONFIG_MTD_PMC551_APERTURE_SIZE
+#else
+static int asize=0;
+#endif
+
+module_param(msize, int, 0);
+MODULE_PARM_DESC(msize, "memory size in Megabytes [1 - 1024]");
+module_param(asize, int, 0);
+MODULE_PARM_DESC(asize, "aperture size, must be <= memsize [1-1024]");
+
+/*
+ * PMC551 Card Initialization
+ */
+static int __init init_pmc551(void)
+{
+ struct pci_dev *PCI_Device = NULL;
+ struct mypriv *priv;
+ int count, found=0;
+ struct mtd_info *mtd;
+ u32 length = 0;
+
+ if(msize) {
+ msize = (1 << (ffs(msize) - 1))<<20;
+ if (msize > (1<<30)) {
+ printk(KERN_NOTICE "pmc551: Invalid memory size [%d]\n", msize);
+ return -EINVAL;
+ }
+ }
+
+ if(asize) {
+ asize = (1 << (ffs(asize) - 1))<<20;
+ if (asize > (1<<30) ) {
+ printk(KERN_NOTICE "pmc551: Invalid aperture size [%d]\n", asize);
+ return -EINVAL;
+ }
+ }
+
+ printk(KERN_INFO PMC551_VERSION);
+
+ /*
+ * PCU-bus chipset probe.
+ */
+ for( count = 0; count < MAX_MTD_DEVICES; count++ ) {
+
+ if ((PCI_Device = pci_find_device(PCI_VENDOR_ID_V3_SEMI,
+ PCI_DEVICE_ID_V3_SEMI_V370PDC,
+ PCI_Device ) ) == NULL) {
+ break;
+ }
+
+ printk(KERN_NOTICE "pmc551: Found PCI V370PDC at 0x%lX\n",
+ PCI_Device->resource[0].start);
+
+ /*
+ * The PMC551 device acts VERY weird if you don't init it
+ * first. i.e. it will not correctly report devsel. If for
+ * some reason the sdram is in a wrote-protected state the
+ * device will DEVSEL when it is written to causing problems
+ * with the oldproc.c driver in
+ * some kernels (2.2.*)
+ */
+ if((length = fixup_pmc551(PCI_Device)) <= 0) {
+ printk(KERN_NOTICE "pmc551: Cannot init SDRAM\n");
+ break;
+ }
+
+ /*
+ * This is needed until the driver is capable of reading the
+ * onboard I2C SROM to discover the "real" memory size.
+ */
+ if(msize) {
+ length = msize;
+ printk(KERN_NOTICE "pmc551: Using specified memory size 0x%x\n", length);
+ } else {
+ msize = length;
+ }
+
+ mtd = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
+ if (!mtd) {
+ printk(KERN_NOTICE "pmc551: Cannot allocate new MTD device.\n");
+ break;
+ }
+
+ memset(mtd, 0, sizeof(struct mtd_info));
+
+ priv = kmalloc (sizeof(struct mypriv), GFP_KERNEL);
+ if (!priv) {
+ printk(KERN_NOTICE "pmc551: Cannot allocate new MTD device.\n");
+ kfree(mtd);
+ break;
+ }
+ memset(priv, 0, sizeof(*priv));
+ mtd->priv = priv;
+ priv->dev = PCI_Device;
+
+ if(asize > length) {
+ printk(KERN_NOTICE "pmc551: reducing aperture size to fit %dM\n",length>>20);
+ priv->asize = asize = length;
+ } else if (asize == 0 || asize == length) {
+ printk(KERN_NOTICE "pmc551: Using existing aperture size %dM\n", length>>20);
+ priv->asize = asize = length;
+ } else {
+ printk(KERN_NOTICE "pmc551: Using specified aperture size %dM\n", asize>>20);
+ priv->asize = asize;
+ }
+ priv->start = ioremap(((PCI_Device->resource[0].start)
+ & PCI_BASE_ADDRESS_MEM_MASK),
+ priv->asize);
+
+ if (!priv->start) {
+ printk(KERN_NOTICE "pmc551: Unable to map IO space\n");
+ kfree(mtd->priv);
+ kfree(mtd);
+ break;
+ }
+
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk( KERN_DEBUG "pmc551: setting aperture to %d\n",
+ ffs(priv->asize>>20)-1);
+#endif
+
+ priv->base_map0 = ( PMC551_PCI_MEM_MAP_REG_EN
+ | PMC551_PCI_MEM_MAP_ENABLE
+ | (ffs(priv->asize>>20)-1)<<4 );
+ priv->curr_map0 = priv->base_map0;
+ pci_write_config_dword ( priv->dev, PMC551_PCI_MEM_MAP0,
+ priv->curr_map0 );
+
+#ifdef CONFIG_MTD_PMC551_DEBUG
+ printk( KERN_DEBUG "pmc551: aperture set to %d\n",
+ (priv->base_map0 & 0xF0)>>4 );
+#endif
+
+ mtd->size = msize;
+ mtd->flags = MTD_CAP_RAM;
+ mtd->erase = pmc551_erase;
+ mtd->read = pmc551_read;
+ mtd->write = pmc551_write;
+ mtd->point = pmc551_point;
+ mtd->unpoint = pmc551_unpoint;
+ mtd->type = MTD_RAM;
+ mtd->name = "PMC551 RAM board";
+ mtd->erasesize = 0x10000;
+ mtd->owner = THIS_MODULE;
+
+ if (add_mtd_device(mtd)) {
+ printk(KERN_NOTICE "pmc551: Failed to register new device\n");
+ iounmap(priv->start);
+ kfree(mtd->priv);
+ kfree(mtd);
+ break;
+ }
+ printk(KERN_NOTICE "Registered pmc551 memory device.\n");
+ printk(KERN_NOTICE "Mapped %dM of memory from 0x%p to 0x%p\n",
+ priv->asize>>20,
+ priv->start,
+ priv->start + priv->asize);
+ printk(KERN_NOTICE "Total memory is %d%c\n",
+ (length<1024)?length:
+ (length<1048576)?length>>10:length>>20,
+ (length<1024)?'B':(length<1048576)?'K':'M');
+ priv->nextpmc551 = pmc551list;
+ pmc551list = mtd;
+ found++;
+ }
+
+ if( !pmc551list ) {
+ printk(KERN_NOTICE "pmc551: not detected\n");
+ return -ENODEV;
+ } else {
+ printk(KERN_NOTICE "pmc551: %d pmc551 devices loaded\n", found);
+ return 0;
+ }
+}
+
+/*
+ * PMC551 Card Cleanup
+ */
+static void __exit cleanup_pmc551(void)
+{
+ int found=0;
+ struct mtd_info *mtd;
+ struct mypriv *priv;
+
+ while((mtd=pmc551list)) {
+ priv = mtd->priv;
+ pmc551list = priv->nextpmc551;
+
+ if(priv->start) {
+ printk (KERN_DEBUG "pmc551: unmapping %dM starting at 0x%p\n",
+ priv->asize>>20, priv->start);
+ iounmap (priv->start);
+ }
+
+ kfree (mtd->priv);
+ del_mtd_device (mtd);
+ kfree (mtd);
+ found++;
+ }
+
+ printk(KERN_NOTICE "pmc551: %d pmc551 devices unloaded\n", found);
+}
+
+module_init(init_pmc551);
+module_exit(cleanup_pmc551);
diff --git a/drivers/mtd/devices/slram.c b/drivers/mtd/devices/slram.c
new file mode 100644
index 000000000000..5ab15e643be7
--- /dev/null
+++ b/drivers/mtd/devices/slram.c
@@ -0,0 +1,357 @@
+/*======================================================================
+
+ $Id: slram.c,v 1.33 2005/01/05 18:05:13 dwmw2 Exp $
+
+ This driver provides a method to access memory not used by the kernel
+ itself (i.e. if the kernel commandline mem=xxx is used). To actually
+ use slram at least mtdblock or mtdchar is required (for block or
+ character device access).
+
+ Usage:
+
+ if compiled as loadable module:
+ modprobe slram map=<name>,<start>,<end/offset>
+ if statically linked into the kernel use the following kernel cmd.line
+ slram=<name>,<start>,<end/offset>
+
+ <name>: name of the device that will be listed in /proc/mtd
+ <start>: start of the memory region, decimal or hex (0xabcdef)
+ <end/offset>: end of the memory region. It's possible to use +0x1234
+ to specify the offset instead of the absolute address
+
+ NOTE:
+ With slram it's only possible to map a contigous memory region. Therfore
+ if there's a device mapped somewhere in the region specified slram will
+ fail to load (see kernel log if modprobe fails).
+
+ -
+
+ Jochen Schaeuble <psionic@psionic.de>
+
+======================================================================*/
+
+
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ptrace.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/major.h>
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <asm/system.h>
+
+#include <linux/mtd/mtd.h>
+
+#define SLRAM_MAX_DEVICES_PARAMS 6 /* 3 parameters / device */
+
+#define T(fmt, args...) printk(KERN_DEBUG fmt, ## args)
+#define E(fmt, args...) printk(KERN_NOTICE fmt, ## args)
+
+typedef struct slram_priv {
+ u_char *start;
+ u_char *end;
+} slram_priv_t;
+
+typedef struct slram_mtd_list {
+ struct mtd_info *mtdinfo;
+ struct slram_mtd_list *next;
+} slram_mtd_list_t;
+
+#ifdef MODULE
+static char *map[SLRAM_MAX_DEVICES_PARAMS];
+
+module_param_array(map, charp, NULL, 0);
+MODULE_PARM_DESC(map, "List of memory regions to map. \"map=<name>, <start>, <length / end>\"");
+#else
+static char *map;
+#endif
+
+static slram_mtd_list_t *slram_mtdlist = NULL;
+
+static int slram_erase(struct mtd_info *, struct erase_info *);
+static int slram_point(struct mtd_info *, loff_t, size_t, size_t *, u_char **);
+static void slram_unpoint(struct mtd_info *, u_char *, loff_t, size_t);
+static int slram_read(struct mtd_info *, loff_t, size_t, size_t *, u_char *);
+static int slram_write(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
+
+static int slram_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ slram_priv_t *priv = mtd->priv;
+
+ if (instr->addr + instr->len > mtd->size) {
+ return(-EINVAL);
+ }
+
+ memset(priv->start + instr->addr, 0xff, instr->len);
+
+ /* This'll catch a few races. Free the thing before returning :)
+ * I don't feel at all ashamed. This kind of thing is possible anyway
+ * with flash, but unlikely.
+ */
+
+ instr->state = MTD_ERASE_DONE;
+
+ mtd_erase_callback(instr);
+
+ return(0);
+}
+
+static int slram_point(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char **mtdbuf)
+{
+ slram_priv_t *priv = mtd->priv;
+
+ *mtdbuf = priv->start + from;
+ *retlen = len;
+ return(0);
+}
+
+static void slram_unpoint(struct mtd_info *mtd, u_char *addr, loff_t from, size_t len)
+{
+}
+
+static int slram_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ slram_priv_t *priv = mtd->priv;
+
+ memcpy(buf, priv->start + from, len);
+
+ *retlen = len;
+ return(0);
+}
+
+static int slram_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ slram_priv_t *priv = mtd->priv;
+
+ memcpy(priv->start + to, buf, len);
+
+ *retlen = len;
+ return(0);
+}
+
+/*====================================================================*/
+
+static int register_device(char *name, unsigned long start, unsigned long length)
+{
+ slram_mtd_list_t **curmtd;
+
+ curmtd = &slram_mtdlist;
+ while (*curmtd) {
+ curmtd = &(*curmtd)->next;
+ }
+
+ *curmtd = kmalloc(sizeof(slram_mtd_list_t), GFP_KERNEL);
+ if (!(*curmtd)) {
+ E("slram: Cannot allocate new MTD device.\n");
+ return(-ENOMEM);
+ }
+ (*curmtd)->mtdinfo = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
+ (*curmtd)->next = NULL;
+
+ if ((*curmtd)->mtdinfo) {
+ memset((char *)(*curmtd)->mtdinfo, 0, sizeof(struct mtd_info));
+ (*curmtd)->mtdinfo->priv =
+ kmalloc(sizeof(slram_priv_t), GFP_KERNEL);
+
+ if (!(*curmtd)->mtdinfo->priv) {
+ kfree((*curmtd)->mtdinfo);
+ (*curmtd)->mtdinfo = NULL;
+ } else {
+ memset((*curmtd)->mtdinfo->priv,0,sizeof(slram_priv_t));
+ }
+ }
+
+ if (!(*curmtd)->mtdinfo) {
+ E("slram: Cannot allocate new MTD device.\n");
+ return(-ENOMEM);
+ }
+
+ if (!(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start =
+ ioremap(start, length))) {
+ E("slram: ioremap failed\n");
+ return -EIO;
+ }
+ ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end =
+ ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start + length;
+
+
+ (*curmtd)->mtdinfo->name = name;
+ (*curmtd)->mtdinfo->size = length;
+ (*curmtd)->mtdinfo->flags = MTD_CLEAR_BITS | MTD_SET_BITS |
+ MTD_WRITEB_WRITEABLE | MTD_VOLATILE;
+ (*curmtd)->mtdinfo->erase = slram_erase;
+ (*curmtd)->mtdinfo->point = slram_point;
+ (*curmtd)->mtdinfo->unpoint = slram_unpoint;
+ (*curmtd)->mtdinfo->read = slram_read;
+ (*curmtd)->mtdinfo->write = slram_write;
+ (*curmtd)->mtdinfo->owner = THIS_MODULE;
+ (*curmtd)->mtdinfo->type = MTD_RAM;
+ (*curmtd)->mtdinfo->erasesize = 0x0;
+
+ if (add_mtd_device((*curmtd)->mtdinfo)) {
+ E("slram: Failed to register new device\n");
+ iounmap(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start);
+ kfree((*curmtd)->mtdinfo->priv);
+ kfree((*curmtd)->mtdinfo);
+ return(-EAGAIN);
+ }
+ T("slram: Registered device %s from %luKiB to %luKiB\n", name,
+ (start / 1024), ((start + length) / 1024));
+ T("slram: Mapped from 0x%p to 0x%p\n",
+ ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start,
+ ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end);
+ return(0);
+}
+
+static void unregister_devices(void)
+{
+ slram_mtd_list_t *nextitem;
+
+ while (slram_mtdlist) {
+ nextitem = slram_mtdlist->next;
+ del_mtd_device(slram_mtdlist->mtdinfo);
+ iounmap(((slram_priv_t *)slram_mtdlist->mtdinfo->priv)->start);
+ kfree(slram_mtdlist->mtdinfo->priv);
+ kfree(slram_mtdlist->mtdinfo);
+ kfree(slram_mtdlist);
+ slram_mtdlist = nextitem;
+ }
+}
+
+static unsigned long handle_unit(unsigned long value, char *unit)
+{
+ if ((*unit == 'M') || (*unit == 'm')) {
+ return(value * 1024 * 1024);
+ } else if ((*unit == 'K') || (*unit == 'k')) {
+ return(value * 1024);
+ }
+ return(value);
+}
+
+static int parse_cmdline(char *devname, char *szstart, char *szlength)
+{
+ char *buffer;
+ unsigned long devstart;
+ unsigned long devlength;
+
+ if ((!devname) || (!szstart) || (!szlength)) {
+ unregister_devices();
+ return(-EINVAL);
+ }
+
+ devstart = simple_strtoul(szstart, &buffer, 0);
+ devstart = handle_unit(devstart, buffer);
+
+ if (*(szlength) != '+') {
+ devlength = simple_strtoul(szlength, &buffer, 0);
+ devlength = handle_unit(devlength, buffer) - devstart;
+ } else {
+ devlength = simple_strtoul(szlength + 1, &buffer, 0);
+ devlength = handle_unit(devlength, buffer);
+ }
+ T("slram: devname=%s, devstart=0x%lx, devlength=0x%lx\n",
+ devname, devstart, devlength);
+ if ((devstart < 0) || (devlength < 0)) {
+ E("slram: Illegal start / length parameter.\n");
+ return(-EINVAL);
+ }
+
+ if ((devstart = register_device(devname, devstart, devlength))){
+ unregister_devices();
+ return((int)devstart);
+ }
+ return(0);
+}
+
+#ifndef MODULE
+
+static int __init mtd_slram_setup(char *str)
+{
+ map = str;
+ return(1);
+}
+
+__setup("slram=", mtd_slram_setup);
+
+#endif
+
+static int init_slram(void)
+{
+ char *devname;
+ int i;
+
+#ifndef MODULE
+ char *devstart;
+ char *devlength;
+
+ i = 0;
+
+ if (!map) {
+ E("slram: not enough parameters.\n");
+ return(-EINVAL);
+ }
+ while (map) {
+ devname = devstart = devlength = NULL;
+
+ if (!(devname = strsep(&map, ","))) {
+ E("slram: No devicename specified.\n");
+ break;
+ }
+ T("slram: devname = %s\n", devname);
+ if ((!map) || (!(devstart = strsep(&map, ",")))) {
+ E("slram: No devicestart specified.\n");
+ }
+ T("slram: devstart = %s\n", devstart);
+ if ((!map) || (!(devlength = strsep(&map, ",")))) {
+ E("slram: No devicelength / -end specified.\n");
+ }
+ T("slram: devlength = %s\n", devlength);
+ if (parse_cmdline(devname, devstart, devlength) != 0) {
+ return(-EINVAL);
+ }
+ }
+#else
+ int count;
+
+ for (count = 0; (map[count]) && (count < SLRAM_MAX_DEVICES_PARAMS);
+ count++) {
+ }
+
+ if ((count % 3 != 0) || (count == 0)) {
+ E("slram: not enough parameters.\n");
+ return(-EINVAL);
+ }
+ for (i = 0; i < (count / 3); i++) {
+ devname = map[i * 3];
+
+ if (parse_cmdline(devname, map[i * 3 + 1], map[i * 3 + 2])!=0) {
+ return(-EINVAL);
+ }
+
+ }
+#endif /* !MODULE */
+
+ return(0);
+}
+
+static void __exit cleanup_slram(void)
+{
+ unregister_devices();
+}
+
+module_init(init_slram);
+module_exit(cleanup_slram);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jochen Schaeuble <psionic@psionic.de>");
+MODULE_DESCRIPTION("MTD driver for uncached system RAM");
diff --git a/drivers/mtd/ftl.c b/drivers/mtd/ftl.c
new file mode 100644
index 000000000000..18cc8846e733
--- /dev/null
+++ b/drivers/mtd/ftl.c
@@ -0,0 +1,1115 @@
+/* This version ported to the Linux-MTD system by dwmw2@infradead.org
+ * $Id: ftl.c,v 1.54 2004/11/16 18:33:15 dwmw2 Exp $
+ *
+ * Fixes: Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ * - fixes some leaks on failure in build_maps and ftl_notify_add, cleanups
+ *
+ * Based on:
+ */
+/*======================================================================
+
+ A Flash Translation Layer memory card driver
+
+ This driver implements a disk-like block device driver with an
+ apparent block size of 512 bytes for flash memory cards.
+
+ ftl_cs.c 1.62 2000/02/01 00:59:04
+
+ The contents of this file are subject to the Mozilla Public
+ License Version 1.1 (the "License"); you may not use this file
+ except in compliance with the License. You may obtain a copy of
+ the License at http://www.mozilla.org/MPL/
+
+ Software distributed under the License is distributed on an "AS
+ IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ implied. See the License for the specific language governing
+ rights and limitations under the License.
+
+ The initial developer of the original code is David A. Hinds
+ <dahinds@users.sourceforge.net>. Portions created by David A. Hinds
+ are Copyright (C) 1999 David A. Hinds. All Rights Reserved.
+
+ Alternatively, the contents of this file may be used under the
+ terms of the GNU General Public License version 2 (the "GPL"), in
+ which case the provisions of the GPL are applicable instead of the
+ above. If you wish to allow the use of your version of this file
+ only under the terms of the GPL and not to allow others to use
+ your version of this file under the MPL, indicate your decision
+ by deleting the provisions above and replace them with the notice
+ and other provisions required by the GPL. If you do not delete
+ the provisions above, a recipient may use your version of this
+ file under either the MPL or the GPL.
+
+ LEGAL NOTE: The FTL format is patented by M-Systems. They have
+ granted a license for its use with PCMCIA devices:
+
+ "M-Systems grants a royalty-free, non-exclusive license under
+ any presently existing M-Systems intellectual property rights
+ necessary for the design and development of FTL-compatible
+ drivers, file systems and utilities using the data formats with
+ PCMCIA PC Cards as described in the PCMCIA Flash Translation
+ Layer (FTL) Specification."
+
+ Use of the FTL format for non-PCMCIA applications may be an
+ infringement of these patents. For additional information,
+ contact M-Systems (http://www.m-sys.com) directly.
+
+======================================================================*/
+#include <linux/mtd/blktrans.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+/*#define PSYCHO_DEBUG */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ptrace.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/major.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/hdreg.h>
+#include <linux/vmalloc.h>
+#include <linux/blkpg.h>
+#include <asm/uaccess.h>
+
+#include <linux/mtd/ftl.h>
+
+/*====================================================================*/
+
+/* Parameters that can be set with 'insmod' */
+static int shuffle_freq = 50;
+module_param(shuffle_freq, int, 0);
+
+/*====================================================================*/
+
+/* Major device # for FTL device */
+#ifndef FTL_MAJOR
+#define FTL_MAJOR 44
+#endif
+
+
+/*====================================================================*/
+
+/* Maximum number of separate memory devices we'll allow */
+#define MAX_DEV 4
+
+/* Maximum number of regions per device */
+#define MAX_REGION 4
+
+/* Maximum number of partitions in an FTL region */
+#define PART_BITS 4
+
+/* Maximum number of outstanding erase requests per socket */
+#define MAX_ERASE 8
+
+/* Sector size -- shouldn't need to change */
+#define SECTOR_SIZE 512
+
+
+/* Each memory region corresponds to a minor device */
+typedef struct partition_t {
+ struct mtd_blktrans_dev mbd;
+ u_int32_t state;
+ u_int32_t *VirtualBlockMap;
+ u_int32_t *VirtualPageMap;
+ u_int32_t FreeTotal;
+ struct eun_info_t {
+ u_int32_t Offset;
+ u_int32_t EraseCount;
+ u_int32_t Free;
+ u_int32_t Deleted;
+ } *EUNInfo;
+ struct xfer_info_t {
+ u_int32_t Offset;
+ u_int32_t EraseCount;
+ u_int16_t state;
+ } *XferInfo;
+ u_int16_t bam_index;
+ u_int32_t *bam_cache;
+ u_int16_t DataUnits;
+ u_int32_t BlocksPerUnit;
+ erase_unit_header_t header;
+#if 0
+ region_info_t region;
+ memory_handle_t handle;
+#endif
+} partition_t;
+
+void ftl_freepart(partition_t *part);
+
+/* Partition state flags */
+#define FTL_FORMATTED 0x01
+
+/* Transfer unit states */
+#define XFER_UNKNOWN 0x00
+#define XFER_ERASING 0x01
+#define XFER_ERASED 0x02
+#define XFER_PREPARED 0x03
+#define XFER_FAILED 0x04
+
+/*====================================================================*/
+
+
+static void ftl_erase_callback(struct erase_info *done);
+
+
+/*======================================================================
+
+ Scan_header() checks to see if a memory region contains an FTL
+ partition. build_maps() reads all the erase unit headers, builds
+ the erase unit map, and then builds the virtual page map.
+
+======================================================================*/
+
+static int scan_header(partition_t *part)
+{
+ erase_unit_header_t header;
+ loff_t offset, max_offset;
+ size_t ret;
+ int err;
+ part->header.FormattedSize = 0;
+ max_offset = (0x100000<part->mbd.mtd->size)?0x100000:part->mbd.mtd->size;
+ /* Search first megabyte for a valid FTL header */
+ for (offset = 0;
+ (offset + sizeof(header)) < max_offset;
+ offset += part->mbd.mtd->erasesize ? : 0x2000) {
+
+ err = part->mbd.mtd->read(part->mbd.mtd, offset, sizeof(header), &ret,
+ (unsigned char *)&header);
+
+ if (err)
+ return err;
+
+ if (strcmp(header.DataOrgTuple+3, "FTL100") == 0) break;
+ }
+
+ if (offset == max_offset) {
+ printk(KERN_NOTICE "ftl_cs: FTL header not found.\n");
+ return -ENOENT;
+ }
+ if (header.BlockSize != 9 ||
+ (header.EraseUnitSize < 10) || (header.EraseUnitSize > 31) ||
+ (header.NumTransferUnits >= le16_to_cpu(header.NumEraseUnits))) {
+ printk(KERN_NOTICE "ftl_cs: FTL header corrupt!\n");
+ return -1;
+ }
+ if ((1 << header.EraseUnitSize) != part->mbd.mtd->erasesize) {
+ printk(KERN_NOTICE "ftl: FTL EraseUnitSize %x != MTD erasesize %x\n",
+ 1 << header.EraseUnitSize,part->mbd.mtd->erasesize);
+ return -1;
+ }
+ part->header = header;
+ return 0;
+}
+
+static int build_maps(partition_t *part)
+{
+ erase_unit_header_t header;
+ u_int16_t xvalid, xtrans, i;
+ u_int blocks, j;
+ int hdr_ok, ret = -1;
+ ssize_t retval;
+ loff_t offset;
+
+ /* Set up erase unit maps */
+ part->DataUnits = le16_to_cpu(part->header.NumEraseUnits) -
+ part->header.NumTransferUnits;
+ part->EUNInfo = kmalloc(part->DataUnits * sizeof(struct eun_info_t),
+ GFP_KERNEL);
+ if (!part->EUNInfo)
+ goto out;
+ for (i = 0; i < part->DataUnits; i++)
+ part->EUNInfo[i].Offset = 0xffffffff;
+ part->XferInfo =
+ kmalloc(part->header.NumTransferUnits * sizeof(struct xfer_info_t),
+ GFP_KERNEL);
+ if (!part->XferInfo)
+ goto out_EUNInfo;
+
+ xvalid = xtrans = 0;
+ for (i = 0; i < le16_to_cpu(part->header.NumEraseUnits); i++) {
+ offset = ((i + le16_to_cpu(part->header.FirstPhysicalEUN))
+ << part->header.EraseUnitSize);
+ ret = part->mbd.mtd->read(part->mbd.mtd, offset, sizeof(header), &retval,
+ (unsigned char *)&header);
+
+ if (ret)
+ goto out_XferInfo;
+
+ ret = -1;
+ /* Is this a transfer partition? */
+ hdr_ok = (strcmp(header.DataOrgTuple+3, "FTL100") == 0);
+ if (hdr_ok && (le16_to_cpu(header.LogicalEUN) < part->DataUnits) &&
+ (part->EUNInfo[le16_to_cpu(header.LogicalEUN)].Offset == 0xffffffff)) {
+ part->EUNInfo[le16_to_cpu(header.LogicalEUN)].Offset = offset;
+ part->EUNInfo[le16_to_cpu(header.LogicalEUN)].EraseCount =
+ le32_to_cpu(header.EraseCount);
+ xvalid++;
+ } else {
+ if (xtrans == part->header.NumTransferUnits) {
+ printk(KERN_NOTICE "ftl_cs: format error: too many "
+ "transfer units!\n");
+ goto out_XferInfo;
+ }
+ if (hdr_ok && (le16_to_cpu(header.LogicalEUN) == 0xffff)) {
+ part->XferInfo[xtrans].state = XFER_PREPARED;
+ part->XferInfo[xtrans].EraseCount = le32_to_cpu(header.EraseCount);
+ } else {
+ part->XferInfo[xtrans].state = XFER_UNKNOWN;
+ /* Pick anything reasonable for the erase count */
+ part->XferInfo[xtrans].EraseCount =
+ le32_to_cpu(part->header.EraseCount);
+ }
+ part->XferInfo[xtrans].Offset = offset;
+ xtrans++;
+ }
+ }
+ /* Check for format trouble */
+ header = part->header;
+ if ((xtrans != header.NumTransferUnits) ||
+ (xvalid+xtrans != le16_to_cpu(header.NumEraseUnits))) {
+ printk(KERN_NOTICE "ftl_cs: format error: erase units "
+ "don't add up!\n");
+ goto out_XferInfo;
+ }
+
+ /* Set up virtual page map */
+ blocks = le32_to_cpu(header.FormattedSize) >> header.BlockSize;
+ part->VirtualBlockMap = vmalloc(blocks * sizeof(u_int32_t));
+ if (!part->VirtualBlockMap)
+ goto out_XferInfo;
+
+ memset(part->VirtualBlockMap, 0xff, blocks * sizeof(u_int32_t));
+ part->BlocksPerUnit = (1 << header.EraseUnitSize) >> header.BlockSize;
+
+ part->bam_cache = kmalloc(part->BlocksPerUnit * sizeof(u_int32_t),
+ GFP_KERNEL);
+ if (!part->bam_cache)
+ goto out_VirtualBlockMap;
+
+ part->bam_index = 0xffff;
+ part->FreeTotal = 0;
+
+ for (i = 0; i < part->DataUnits; i++) {
+ part->EUNInfo[i].Free = 0;
+ part->EUNInfo[i].Deleted = 0;
+ offset = part->EUNInfo[i].Offset + le32_to_cpu(header.BAMOffset);
+
+ ret = part->mbd.mtd->read(part->mbd.mtd, offset,
+ part->BlocksPerUnit * sizeof(u_int32_t), &retval,
+ (unsigned char *)part->bam_cache);
+
+ if (ret)
+ goto out_bam_cache;
+
+ for (j = 0; j < part->BlocksPerUnit; j++) {
+ if (BLOCK_FREE(le32_to_cpu(part->bam_cache[j]))) {
+ part->EUNInfo[i].Free++;
+ part->FreeTotal++;
+ } else if ((BLOCK_TYPE(le32_to_cpu(part->bam_cache[j])) == BLOCK_DATA) &&
+ (BLOCK_NUMBER(le32_to_cpu(part->bam_cache[j])) < blocks))
+ part->VirtualBlockMap[BLOCK_NUMBER(le32_to_cpu(part->bam_cache[j]))] =
+ (i << header.EraseUnitSize) + (j << header.BlockSize);
+ else if (BLOCK_DELETED(le32_to_cpu(part->bam_cache[j])))
+ part->EUNInfo[i].Deleted++;
+ }
+ }
+
+ ret = 0;
+ goto out;
+
+out_bam_cache:
+ kfree(part->bam_cache);
+out_VirtualBlockMap:
+ vfree(part->VirtualBlockMap);
+out_XferInfo:
+ kfree(part->XferInfo);
+out_EUNInfo:
+ kfree(part->EUNInfo);
+out:
+ return ret;
+} /* build_maps */
+
+/*======================================================================
+
+ Erase_xfer() schedules an asynchronous erase operation for a
+ transfer unit.
+
+======================================================================*/
+
+static int erase_xfer(partition_t *part,
+ u_int16_t xfernum)
+{
+ int ret;
+ struct xfer_info_t *xfer;
+ struct erase_info *erase;
+
+ xfer = &part->XferInfo[xfernum];
+ DEBUG(1, "ftl_cs: erasing xfer unit at 0x%x\n", xfer->Offset);
+ xfer->state = XFER_ERASING;
+
+ /* Is there a free erase slot? Always in MTD. */
+
+
+ erase=kmalloc(sizeof(struct erase_info), GFP_KERNEL);
+ if (!erase)
+ return -ENOMEM;
+
+ erase->callback = ftl_erase_callback;
+ erase->addr = xfer->Offset;
+ erase->len = 1 << part->header.EraseUnitSize;
+ erase->priv = (u_long)part;
+
+ ret = part->mbd.mtd->erase(part->mbd.mtd, erase);
+
+ if (!ret)
+ xfer->EraseCount++;
+ else
+ kfree(erase);
+
+ return ret;
+} /* erase_xfer */
+
+/*======================================================================
+
+ Prepare_xfer() takes a freshly erased transfer unit and gives
+ it an appropriate header.
+
+======================================================================*/
+
+static void ftl_erase_callback(struct erase_info *erase)
+{
+ partition_t *part;
+ struct xfer_info_t *xfer;
+ int i;
+
+ /* Look up the transfer unit */
+ part = (partition_t *)(erase->priv);
+
+ for (i = 0; i < part->header.NumTransferUnits; i++)
+ if (part->XferInfo[i].Offset == erase->addr) break;
+
+ if (i == part->header.NumTransferUnits) {
+ printk(KERN_NOTICE "ftl_cs: internal error: "
+ "erase lookup failed!\n");
+ return;
+ }
+
+ xfer = &part->XferInfo[i];
+ if (erase->state == MTD_ERASE_DONE)
+ xfer->state = XFER_ERASED;
+ else {
+ xfer->state = XFER_FAILED;
+ printk(KERN_NOTICE "ftl_cs: erase failed: state = %d\n",
+ erase->state);
+ }
+
+ kfree(erase);
+
+} /* ftl_erase_callback */
+
+static int prepare_xfer(partition_t *part, int i)
+{
+ erase_unit_header_t header;
+ struct xfer_info_t *xfer;
+ int nbam, ret;
+ u_int32_t ctl;
+ ssize_t retlen;
+ loff_t offset;
+
+ xfer = &part->XferInfo[i];
+ xfer->state = XFER_FAILED;
+
+ DEBUG(1, "ftl_cs: preparing xfer unit at 0x%x\n", xfer->Offset);
+
+ /* Write the transfer unit header */
+ header = part->header;
+ header.LogicalEUN = cpu_to_le16(0xffff);
+ header.EraseCount = cpu_to_le32(xfer->EraseCount);
+
+ ret = part->mbd.mtd->write(part->mbd.mtd, xfer->Offset, sizeof(header),
+ &retlen, (u_char *)&header);
+
+ if (ret) {
+ return ret;
+ }
+
+ /* Write the BAM stub */
+ nbam = (part->BlocksPerUnit * sizeof(u_int32_t) +
+ le32_to_cpu(part->header.BAMOffset) + SECTOR_SIZE - 1) / SECTOR_SIZE;
+
+ offset = xfer->Offset + le32_to_cpu(part->header.BAMOffset);
+ ctl = cpu_to_le32(BLOCK_CONTROL);
+
+ for (i = 0; i < nbam; i++, offset += sizeof(u_int32_t)) {
+
+ ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(u_int32_t),
+ &retlen, (u_char *)&ctl);
+
+ if (ret)
+ return ret;
+ }
+ xfer->state = XFER_PREPARED;
+ return 0;
+
+} /* prepare_xfer */
+
+/*======================================================================
+
+ Copy_erase_unit() takes a full erase block and a transfer unit,
+ copies everything to the transfer unit, then swaps the block
+ pointers.
+
+ All data blocks are copied to the corresponding blocks in the
+ target unit, so the virtual block map does not need to be
+ updated.
+
+======================================================================*/
+
+static int copy_erase_unit(partition_t *part, u_int16_t srcunit,
+ u_int16_t xferunit)
+{
+ u_char buf[SECTOR_SIZE];
+ struct eun_info_t *eun;
+ struct xfer_info_t *xfer;
+ u_int32_t src, dest, free, i;
+ u_int16_t unit;
+ int ret;
+ ssize_t retlen;
+ loff_t offset;
+ u_int16_t srcunitswap = cpu_to_le16(srcunit);
+
+ eun = &part->EUNInfo[srcunit];
+ xfer = &part->XferInfo[xferunit];
+ DEBUG(2, "ftl_cs: copying block 0x%x to 0x%x\n",
+ eun->Offset, xfer->Offset);
+
+
+ /* Read current BAM */
+ if (part->bam_index != srcunit) {
+
+ offset = eun->Offset + le32_to_cpu(part->header.BAMOffset);
+
+ ret = part->mbd.mtd->read(part->mbd.mtd, offset,
+ part->BlocksPerUnit * sizeof(u_int32_t),
+ &retlen, (u_char *) (part->bam_cache));
+
+ /* mark the cache bad, in case we get an error later */
+ part->bam_index = 0xffff;
+
+ if (ret) {
+ printk( KERN_WARNING "ftl: Failed to read BAM cache in copy_erase_unit()!\n");
+ return ret;
+ }
+ }
+
+ /* Write the LogicalEUN for the transfer unit */
+ xfer->state = XFER_UNKNOWN;
+ offset = xfer->Offset + 20; /* Bad! */
+ unit = cpu_to_le16(0x7fff);
+
+ ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(u_int16_t),
+ &retlen, (u_char *) &unit);
+
+ if (ret) {
+ printk( KERN_WARNING "ftl: Failed to write back to BAM cache in copy_erase_unit()!\n");
+ return ret;
+ }
+
+ /* Copy all data blocks from source unit to transfer unit */
+ src = eun->Offset; dest = xfer->Offset;
+
+ free = 0;
+ ret = 0;
+ for (i = 0; i < part->BlocksPerUnit; i++) {
+ switch (BLOCK_TYPE(le32_to_cpu(part->bam_cache[i]))) {
+ case BLOCK_CONTROL:
+ /* This gets updated later */
+ break;
+ case BLOCK_DATA:
+ case BLOCK_REPLACEMENT:
+ ret = part->mbd.mtd->read(part->mbd.mtd, src, SECTOR_SIZE,
+ &retlen, (u_char *) buf);
+ if (ret) {
+ printk(KERN_WARNING "ftl: Error reading old xfer unit in copy_erase_unit\n");
+ return ret;
+ }
+
+
+ ret = part->mbd.mtd->write(part->mbd.mtd, dest, SECTOR_SIZE,
+ &retlen, (u_char *) buf);
+ if (ret) {
+ printk(KERN_WARNING "ftl: Error writing new xfer unit in copy_erase_unit\n");
+ return ret;
+ }
+
+ break;
+ default:
+ /* All other blocks must be free */
+ part->bam_cache[i] = cpu_to_le32(0xffffffff);
+ free++;
+ break;
+ }
+ src += SECTOR_SIZE;
+ dest += SECTOR_SIZE;
+ }
+
+ /* Write the BAM to the transfer unit */
+ ret = part->mbd.mtd->write(part->mbd.mtd, xfer->Offset + le32_to_cpu(part->header.BAMOffset),
+ part->BlocksPerUnit * sizeof(int32_t), &retlen,
+ (u_char *)part->bam_cache);
+ if (ret) {
+ printk( KERN_WARNING "ftl: Error writing BAM in copy_erase_unit\n");
+ return ret;
+ }
+
+
+ /* All clear? Then update the LogicalEUN again */
+ ret = part->mbd.mtd->write(part->mbd.mtd, xfer->Offset + 20, sizeof(u_int16_t),
+ &retlen, (u_char *)&srcunitswap);
+
+ if (ret) {
+ printk(KERN_WARNING "ftl: Error writing new LogicalEUN in copy_erase_unit\n");
+ return ret;
+ }
+
+
+ /* Update the maps and usage stats*/
+ i = xfer->EraseCount;
+ xfer->EraseCount = eun->EraseCount;
+ eun->EraseCount = i;
+ i = xfer->Offset;
+ xfer->Offset = eun->Offset;
+ eun->Offset = i;
+ part->FreeTotal -= eun->Free;
+ part->FreeTotal += free;
+ eun->Free = free;
+ eun->Deleted = 0;
+
+ /* Now, the cache should be valid for the new block */
+ part->bam_index = srcunit;
+
+ return 0;
+} /* copy_erase_unit */
+
+/*======================================================================
+
+ reclaim_block() picks a full erase unit and a transfer unit and
+ then calls copy_erase_unit() to copy one to the other. Then, it
+ schedules an erase on the expired block.
+
+ What's a good way to decide which transfer unit and which erase
+ unit to use? Beats me. My way is to always pick the transfer
+ unit with the fewest erases, and usually pick the data unit with
+ the most deleted blocks. But with a small probability, pick the
+ oldest data unit instead. This means that we generally postpone
+ the next reclaimation as long as possible, but shuffle static
+ stuff around a bit for wear leveling.
+
+======================================================================*/
+
+static int reclaim_block(partition_t *part)
+{
+ u_int16_t i, eun, xfer;
+ u_int32_t best;
+ int queued, ret;
+
+ DEBUG(0, "ftl_cs: reclaiming space...\n");
+ DEBUG(3, "NumTransferUnits == %x\n", part->header.NumTransferUnits);
+ /* Pick the least erased transfer unit */
+ best = 0xffffffff; xfer = 0xffff;
+ do {
+ queued = 0;
+ for (i = 0; i < part->header.NumTransferUnits; i++) {
+ int n=0;
+ if (part->XferInfo[i].state == XFER_UNKNOWN) {
+ DEBUG(3,"XferInfo[%d].state == XFER_UNKNOWN\n",i);
+ n=1;
+ erase_xfer(part, i);
+ }
+ if (part->XferInfo[i].state == XFER_ERASING) {
+ DEBUG(3,"XferInfo[%d].state == XFER_ERASING\n",i);
+ n=1;
+ queued = 1;
+ }
+ else if (part->XferInfo[i].state == XFER_ERASED) {
+ DEBUG(3,"XferInfo[%d].state == XFER_ERASED\n",i);
+ n=1;
+ prepare_xfer(part, i);
+ }
+ if (part->XferInfo[i].state == XFER_PREPARED) {
+ DEBUG(3,"XferInfo[%d].state == XFER_PREPARED\n",i);
+ n=1;
+ if (part->XferInfo[i].EraseCount <= best) {
+ best = part->XferInfo[i].EraseCount;
+ xfer = i;
+ }
+ }
+ if (!n)
+ DEBUG(3,"XferInfo[%d].state == %x\n",i, part->XferInfo[i].state);
+
+ }
+ if (xfer == 0xffff) {
+ if (queued) {
+ DEBUG(1, "ftl_cs: waiting for transfer "
+ "unit to be prepared...\n");
+ if (part->mbd.mtd->sync)
+ part->mbd.mtd->sync(part->mbd.mtd);
+ } else {
+ static int ne = 0;
+ if (++ne < 5)
+ printk(KERN_NOTICE "ftl_cs: reclaim failed: no "
+ "suitable transfer units!\n");
+ else
+ DEBUG(1, "ftl_cs: reclaim failed: no "
+ "suitable transfer units!\n");
+
+ return -EIO;
+ }
+ }
+ } while (xfer == 0xffff);
+
+ eun = 0;
+ if ((jiffies % shuffle_freq) == 0) {
+ DEBUG(1, "ftl_cs: recycling freshest block...\n");
+ best = 0xffffffff;
+ for (i = 0; i < part->DataUnits; i++)
+ if (part->EUNInfo[i].EraseCount <= best) {
+ best = part->EUNInfo[i].EraseCount;
+ eun = i;
+ }
+ } else {
+ best = 0;
+ for (i = 0; i < part->DataUnits; i++)
+ if (part->EUNInfo[i].Deleted >= best) {
+ best = part->EUNInfo[i].Deleted;
+ eun = i;
+ }
+ if (best == 0) {
+ static int ne = 0;
+ if (++ne < 5)
+ printk(KERN_NOTICE "ftl_cs: reclaim failed: "
+ "no free blocks!\n");
+ else
+ DEBUG(1,"ftl_cs: reclaim failed: "
+ "no free blocks!\n");
+
+ return -EIO;
+ }
+ }
+ ret = copy_erase_unit(part, eun, xfer);
+ if (!ret)
+ erase_xfer(part, xfer);
+ else
+ printk(KERN_NOTICE "ftl_cs: copy_erase_unit failed!\n");
+ return ret;
+} /* reclaim_block */
+
+/*======================================================================
+
+ Find_free() searches for a free block. If necessary, it updates
+ the BAM cache for the erase unit containing the free block. It
+ returns the block index -- the erase unit is just the currently
+ cached unit. If there are no free blocks, it returns 0 -- this
+ is never a valid data block because it contains the header.
+
+======================================================================*/
+
+#ifdef PSYCHO_DEBUG
+static void dump_lists(partition_t *part)
+{
+ int i;
+ printk(KERN_DEBUG "ftl_cs: Free total = %d\n", part->FreeTotal);
+ for (i = 0; i < part->DataUnits; i++)
+ printk(KERN_DEBUG "ftl_cs: unit %d: %d phys, %d free, "
+ "%d deleted\n", i,
+ part->EUNInfo[i].Offset >> part->header.EraseUnitSize,
+ part->EUNInfo[i].Free, part->EUNInfo[i].Deleted);
+}
+#endif
+
+static u_int32_t find_free(partition_t *part)
+{
+ u_int16_t stop, eun;
+ u_int32_t blk;
+ size_t retlen;
+ int ret;
+
+ /* Find an erase unit with some free space */
+ stop = (part->bam_index == 0xffff) ? 0 : part->bam_index;
+ eun = stop;
+ do {
+ if (part->EUNInfo[eun].Free != 0) break;
+ /* Wrap around at end of table */
+ if (++eun == part->DataUnits) eun = 0;
+ } while (eun != stop);
+
+ if (part->EUNInfo[eun].Free == 0)
+ return 0;
+
+ /* Is this unit's BAM cached? */
+ if (eun != part->bam_index) {
+ /* Invalidate cache */
+ part->bam_index = 0xffff;
+
+ ret = part->mbd.mtd->read(part->mbd.mtd,
+ part->EUNInfo[eun].Offset + le32_to_cpu(part->header.BAMOffset),
+ part->BlocksPerUnit * sizeof(u_int32_t),
+ &retlen, (u_char *) (part->bam_cache));
+
+ if (ret) {
+ printk(KERN_WARNING"ftl: Error reading BAM in find_free\n");
+ return 0;
+ }
+ part->bam_index = eun;
+ }
+
+ /* Find a free block */
+ for (blk = 0; blk < part->BlocksPerUnit; blk++)
+ if (BLOCK_FREE(le32_to_cpu(part->bam_cache[blk]))) break;
+ if (blk == part->BlocksPerUnit) {
+#ifdef PSYCHO_DEBUG
+ static int ne = 0;
+ if (++ne == 1)
+ dump_lists(part);
+#endif
+ printk(KERN_NOTICE "ftl_cs: bad free list!\n");
+ return 0;
+ }
+ DEBUG(2, "ftl_cs: found free block at %d in %d\n", blk, eun);
+ return blk;
+
+} /* find_free */
+
+
+/*======================================================================
+
+ Read a series of sectors from an FTL partition.
+
+======================================================================*/
+
+static int ftl_read(partition_t *part, caddr_t buffer,
+ u_long sector, u_long nblocks)
+{
+ u_int32_t log_addr, bsize;
+ u_long i;
+ int ret;
+ size_t offset, retlen;
+
+ DEBUG(2, "ftl_cs: ftl_read(0x%p, 0x%lx, %ld)\n",
+ part, sector, nblocks);
+ if (!(part->state & FTL_FORMATTED)) {
+ printk(KERN_NOTICE "ftl_cs: bad partition\n");
+ return -EIO;
+ }
+ bsize = 1 << part->header.EraseUnitSize;
+
+ for (i = 0; i < nblocks; i++) {
+ if (((sector+i) * SECTOR_SIZE) >= le32_to_cpu(part->header.FormattedSize)) {
+ printk(KERN_NOTICE "ftl_cs: bad read offset\n");
+ return -EIO;
+ }
+ log_addr = part->VirtualBlockMap[sector+i];
+ if (log_addr == 0xffffffff)
+ memset(buffer, 0, SECTOR_SIZE);
+ else {
+ offset = (part->EUNInfo[log_addr / bsize].Offset
+ + (log_addr % bsize));
+ ret = part->mbd.mtd->read(part->mbd.mtd, offset, SECTOR_SIZE,
+ &retlen, (u_char *) buffer);
+
+ if (ret) {
+ printk(KERN_WARNING "Error reading MTD device in ftl_read()\n");
+ return ret;
+ }
+ }
+ buffer += SECTOR_SIZE;
+ }
+ return 0;
+} /* ftl_read */
+
+/*======================================================================
+
+ Write a series of sectors to an FTL partition
+
+======================================================================*/
+
+static int set_bam_entry(partition_t *part, u_int32_t log_addr,
+ u_int32_t virt_addr)
+{
+ u_int32_t bsize, blk, le_virt_addr;
+#ifdef PSYCHO_DEBUG
+ u_int32_t old_addr;
+#endif
+ u_int16_t eun;
+ int ret;
+ size_t retlen, offset;
+
+ DEBUG(2, "ftl_cs: set_bam_entry(0x%p, 0x%x, 0x%x)\n",
+ part, log_addr, virt_addr);
+ bsize = 1 << part->header.EraseUnitSize;
+ eun = log_addr / bsize;
+ blk = (log_addr % bsize) / SECTOR_SIZE;
+ offset = (part->EUNInfo[eun].Offset + blk * sizeof(u_int32_t) +
+ le32_to_cpu(part->header.BAMOffset));
+
+#ifdef PSYCHO_DEBUG
+ ret = part->mbd.mtd->read(part->mbd.mtd, offset, sizeof(u_int32_t),
+ &retlen, (u_char *)&old_addr);
+ if (ret) {
+ printk(KERN_WARNING"ftl: Error reading old_addr in set_bam_entry: %d\n",ret);
+ return ret;
+ }
+ old_addr = le32_to_cpu(old_addr);
+
+ if (((virt_addr == 0xfffffffe) && !BLOCK_FREE(old_addr)) ||
+ ((virt_addr == 0) && (BLOCK_TYPE(old_addr) != BLOCK_DATA)) ||
+ (!BLOCK_DELETED(virt_addr) && (old_addr != 0xfffffffe))) {
+ static int ne = 0;
+ if (++ne < 5) {
+ printk(KERN_NOTICE "ftl_cs: set_bam_entry() inconsistency!\n");
+ printk(KERN_NOTICE "ftl_cs: log_addr = 0x%x, old = 0x%x"
+ ", new = 0x%x\n", log_addr, old_addr, virt_addr);
+ }
+ return -EIO;
+ }
+#endif
+ le_virt_addr = cpu_to_le32(virt_addr);
+ if (part->bam_index == eun) {
+#ifdef PSYCHO_DEBUG
+ if (le32_to_cpu(part->bam_cache[blk]) != old_addr) {
+ static int ne = 0;
+ if (++ne < 5) {
+ printk(KERN_NOTICE "ftl_cs: set_bam_entry() "
+ "inconsistency!\n");
+ printk(KERN_NOTICE "ftl_cs: log_addr = 0x%x, cache"
+ " = 0x%x\n",
+ le32_to_cpu(part->bam_cache[blk]), old_addr);
+ }
+ return -EIO;
+ }
+#endif
+ part->bam_cache[blk] = le_virt_addr;
+ }
+ ret = part->mbd.mtd->write(part->mbd.mtd, offset, sizeof(u_int32_t),
+ &retlen, (u_char *)&le_virt_addr);
+
+ if (ret) {
+ printk(KERN_NOTICE "ftl_cs: set_bam_entry() failed!\n");
+ printk(KERN_NOTICE "ftl_cs: log_addr = 0x%x, new = 0x%x\n",
+ log_addr, virt_addr);
+ }
+ return ret;
+} /* set_bam_entry */
+
+static int ftl_write(partition_t *part, caddr_t buffer,
+ u_long sector, u_long nblocks)
+{
+ u_int32_t bsize, log_addr, virt_addr, old_addr, blk;
+ u_long i;
+ int ret;
+ size_t retlen, offset;
+
+ DEBUG(2, "ftl_cs: ftl_write(0x%p, %ld, %ld)\n",
+ part, sector, nblocks);
+ if (!(part->state & FTL_FORMATTED)) {
+ printk(KERN_NOTICE "ftl_cs: bad partition\n");
+ return -EIO;
+ }
+ /* See if we need to reclaim space, before we start */
+ while (part->FreeTotal < nblocks) {
+ ret = reclaim_block(part);
+ if (ret)
+ return ret;
+ }
+
+ bsize = 1 << part->header.EraseUnitSize;
+
+ virt_addr = sector * SECTOR_SIZE | BLOCK_DATA;
+ for (i = 0; i < nblocks; i++) {
+ if (virt_addr >= le32_to_cpu(part->header.FormattedSize)) {
+ printk(KERN_NOTICE "ftl_cs: bad write offset\n");
+ return -EIO;
+ }
+
+ /* Grab a free block */
+ blk = find_free(part);
+ if (blk == 0) {
+ static int ne = 0;
+ if (++ne < 5)
+ printk(KERN_NOTICE "ftl_cs: internal error: "
+ "no free blocks!\n");
+ return -ENOSPC;
+ }
+
+ /* Tag the BAM entry, and write the new block */
+ log_addr = part->bam_index * bsize + blk * SECTOR_SIZE;
+ part->EUNInfo[part->bam_index].Free--;
+ part->FreeTotal--;
+ if (set_bam_entry(part, log_addr, 0xfffffffe))
+ return -EIO;
+ part->EUNInfo[part->bam_index].Deleted++;
+ offset = (part->EUNInfo[part->bam_index].Offset +
+ blk * SECTOR_SIZE);
+ ret = part->mbd.mtd->write(part->mbd.mtd, offset, SECTOR_SIZE, &retlen,
+ buffer);
+
+ if (ret) {
+ printk(KERN_NOTICE "ftl_cs: block write failed!\n");
+ printk(KERN_NOTICE "ftl_cs: log_addr = 0x%x, virt_addr"
+ " = 0x%x, Offset = 0x%zx\n", log_addr, virt_addr,
+ offset);
+ return -EIO;
+ }
+
+ /* Only delete the old entry when the new entry is ready */
+ old_addr = part->VirtualBlockMap[sector+i];
+ if (old_addr != 0xffffffff) {
+ part->VirtualBlockMap[sector+i] = 0xffffffff;
+ part->EUNInfo[old_addr/bsize].Deleted++;
+ if (set_bam_entry(part, old_addr, 0))
+ return -EIO;
+ }
+
+ /* Finally, set up the new pointers */
+ if (set_bam_entry(part, log_addr, virt_addr))
+ return -EIO;
+ part->VirtualBlockMap[sector+i] = log_addr;
+ part->EUNInfo[part->bam_index].Deleted--;
+
+ buffer += SECTOR_SIZE;
+ virt_addr += SECTOR_SIZE;
+ }
+ return 0;
+} /* ftl_write */
+
+static int ftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
+{
+ partition_t *part = (void *)dev;
+ u_long sect;
+
+ /* Sort of arbitrary: round size down to 4KiB boundary */
+ sect = le32_to_cpu(part->header.FormattedSize)/SECTOR_SIZE;
+
+ geo->heads = 1;
+ geo->sectors = 8;
+ geo->cylinders = sect >> 3;
+
+ return 0;
+}
+
+static int ftl_readsect(struct mtd_blktrans_dev *dev,
+ unsigned long block, char *buf)
+{
+ return ftl_read((void *)dev, buf, block, 1);
+}
+
+static int ftl_writesect(struct mtd_blktrans_dev *dev,
+ unsigned long block, char *buf)
+{
+ return ftl_write((void *)dev, buf, block, 1);
+}
+
+/*====================================================================*/
+
+void ftl_freepart(partition_t *part)
+{
+ if (part->VirtualBlockMap) {
+ vfree(part->VirtualBlockMap);
+ part->VirtualBlockMap = NULL;
+ }
+ if (part->VirtualPageMap) {
+ kfree(part->VirtualPageMap);
+ part->VirtualPageMap = NULL;
+ }
+ if (part->EUNInfo) {
+ kfree(part->EUNInfo);
+ part->EUNInfo = NULL;
+ }
+ if (part->XferInfo) {
+ kfree(part->XferInfo);
+ part->XferInfo = NULL;
+ }
+ if (part->bam_cache) {
+ kfree(part->bam_cache);
+ part->bam_cache = NULL;
+ }
+
+} /* ftl_freepart */
+
+static void ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
+{
+ partition_t *partition;
+
+ partition = kmalloc(sizeof(partition_t), GFP_KERNEL);
+
+ if (!partition) {
+ printk(KERN_WARNING "No memory to scan for FTL on %s\n",
+ mtd->name);
+ return;
+ }
+
+ memset(partition, 0, sizeof(partition_t));
+
+ partition->mbd.mtd = mtd;
+
+ if ((scan_header(partition) == 0) &&
+ (build_maps(partition) == 0)) {
+
+ partition->state = FTL_FORMATTED;
+#ifdef PCMCIA_DEBUG
+ printk(KERN_INFO "ftl_cs: opening %d KiB FTL partition\n",
+ le32_to_cpu(partition->header.FormattedSize) >> 10);
+#endif
+ partition->mbd.size = le32_to_cpu(partition->header.FormattedSize) >> 9;
+ partition->mbd.blksize = SECTOR_SIZE;
+ partition->mbd.tr = tr;
+ partition->mbd.devnum = -1;
+ if (!add_mtd_blktrans_dev((void *)partition))
+ return;
+ }
+
+ ftl_freepart(partition);
+ kfree(partition);
+}
+
+static void ftl_remove_dev(struct mtd_blktrans_dev *dev)
+{
+ del_mtd_blktrans_dev(dev);
+ ftl_freepart((partition_t *)dev);
+ kfree(dev);
+}
+
+struct mtd_blktrans_ops ftl_tr = {
+ .name = "ftl",
+ .major = FTL_MAJOR,
+ .part_bits = PART_BITS,
+ .readsect = ftl_readsect,
+ .writesect = ftl_writesect,
+ .getgeo = ftl_getgeo,
+ .add_mtd = ftl_add_mtd,
+ .remove_dev = ftl_remove_dev,
+ .owner = THIS_MODULE,
+};
+
+int init_ftl(void)
+{
+ DEBUG(0, "$Id: ftl.c,v 1.54 2004/11/16 18:33:15 dwmw2 Exp $\n");
+
+ return register_mtd_blktrans(&ftl_tr);
+}
+
+static void __exit cleanup_ftl(void)
+{
+ deregister_mtd_blktrans(&ftl_tr);
+}
+
+module_init(init_ftl);
+module_exit(cleanup_ftl);
+
+
+MODULE_LICENSE("Dual MPL/GPL");
+MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>");
+MODULE_DESCRIPTION("Support code for Flash Translation Layer, used on PCMCIA devices");
diff --git a/drivers/mtd/inftlcore.c b/drivers/mtd/inftlcore.c
new file mode 100644
index 000000000000..39eb53f6551f
--- /dev/null
+++ b/drivers/mtd/inftlcore.c
@@ -0,0 +1,912 @@
+/*
+ * inftlcore.c -- Linux driver for Inverse Flash Translation Layer (INFTL)
+ *
+ * (C) Copyright 2002, Greg Ungerer (gerg@snapgear.com)
+ *
+ * Based heavily on the nftlcore.c code which is:
+ * (c) 1999 Machine Vision Holdings, Inc.
+ * Author: David Woodhouse <dwmw2@infradead.org>
+ *
+ * $Id: inftlcore.c,v 1.18 2004/11/16 18:28:59 dwmw2 Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/kmod.h>
+#include <linux/hdreg.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nftl.h>
+#include <linux/mtd/inftl.h>
+#include <asm/uaccess.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+
+/*
+ * Maximum number of loops while examining next block, to have a
+ * chance to detect consistency problems (they should never happen
+ * because of the checks done in the mounting.
+ */
+#define MAX_LOOPS 10000
+
+extern void INFTL_dumptables(struct INFTLrecord *inftl);
+extern void INFTL_dumpVUchains(struct INFTLrecord *inftl);
+
+static void inftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
+{
+ struct INFTLrecord *inftl;
+ unsigned long temp;
+
+ if (mtd->type != MTD_NANDFLASH)
+ return;
+ /* OK, this is moderately ugly. But probably safe. Alternatives? */
+ if (memcmp(mtd->name, "DiskOnChip", 10))
+ return;
+
+ if (!mtd->block_isbad) {
+ printk(KERN_ERR
+"INFTL no longer supports the old DiskOnChip drivers loaded via docprobe.\n"
+"Please use the new diskonchip driver under the NAND subsystem.\n");
+ return;
+ }
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: add_mtd for %s\n", mtd->name);
+
+ inftl = kmalloc(sizeof(*inftl), GFP_KERNEL);
+
+ if (!inftl) {
+ printk(KERN_WARNING "INFTL: Out of memory for data structures\n");
+ return;
+ }
+ memset(inftl, 0, sizeof(*inftl));
+
+ inftl->mbd.mtd = mtd;
+ inftl->mbd.devnum = -1;
+ inftl->mbd.blksize = 512;
+ inftl->mbd.tr = tr;
+ memcpy(&inftl->oobinfo, &mtd->oobinfo, sizeof(struct nand_oobinfo));
+ inftl->oobinfo.useecc = MTD_NANDECC_PLACEONLY;
+
+ if (INFTL_mount(inftl) < 0) {
+ printk(KERN_WARNING "INFTL: could not mount device\n");
+ kfree(inftl);
+ return;
+ }
+
+ /* OK, it's a new one. Set up all the data structures. */
+
+ /* Calculate geometry */
+ inftl->cylinders = 1024;
+ inftl->heads = 16;
+
+ temp = inftl->cylinders * inftl->heads;
+ inftl->sectors = inftl->mbd.size / temp;
+ if (inftl->mbd.size % temp) {
+ inftl->sectors++;
+ temp = inftl->cylinders * inftl->sectors;
+ inftl->heads = inftl->mbd.size / temp;
+
+ if (inftl->mbd.size % temp) {
+ inftl->heads++;
+ temp = inftl->heads * inftl->sectors;
+ inftl->cylinders = inftl->mbd.size / temp;
+ }
+ }
+
+ if (inftl->mbd.size != inftl->heads * inftl->cylinders * inftl->sectors) {
+ /*
+ Oh no we don't have
+ mbd.size == heads * cylinders * sectors
+ */
+ printk(KERN_WARNING "INFTL: cannot calculate a geometry to "
+ "match size of 0x%lx.\n", inftl->mbd.size);
+ printk(KERN_WARNING "INFTL: using C:%d H:%d S:%d "
+ "(== 0x%lx sects)\n",
+ inftl->cylinders, inftl->heads , inftl->sectors,
+ (long)inftl->cylinders * (long)inftl->heads *
+ (long)inftl->sectors );
+ }
+
+ if (add_mtd_blktrans_dev(&inftl->mbd)) {
+ if (inftl->PUtable)
+ kfree(inftl->PUtable);
+ if (inftl->VUtable)
+ kfree(inftl->VUtable);
+ kfree(inftl);
+ return;
+ }
+#ifdef PSYCHO_DEBUG
+ printk(KERN_INFO "INFTL: Found new nftl%c\n", nftl->mbd.devnum + 'a');
+#endif
+ return;
+}
+
+static void inftl_remove_dev(struct mtd_blktrans_dev *dev)
+{
+ struct INFTLrecord *inftl = (void *)dev;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: remove_dev (i=%d)\n", dev->devnum);
+
+ del_mtd_blktrans_dev(dev);
+
+ if (inftl->PUtable)
+ kfree(inftl->PUtable);
+ if (inftl->VUtable)
+ kfree(inftl->VUtable);
+ kfree(inftl);
+}
+
+/*
+ * Actual INFTL access routines.
+ */
+
+/*
+ * INFTL_findfreeblock: Find a free Erase Unit on the INFTL partition.
+ * This function is used when the give Virtual Unit Chain.
+ */
+static u16 INFTL_findfreeblock(struct INFTLrecord *inftl, int desperate)
+{
+ u16 pot = inftl->LastFreeEUN;
+ int silly = inftl->nb_blocks;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_findfreeblock(inftl=%p,"
+ "desperate=%d)\n", inftl, desperate);
+
+ /*
+ * Normally, we force a fold to happen before we run out of free
+ * blocks completely.
+ */
+ if (!desperate && inftl->numfreeEUNs < 2) {
+ DEBUG(MTD_DEBUG_LEVEL1, "INFTL: there are too few free "
+ "EUNs (%d)\n", inftl->numfreeEUNs);
+ return 0xffff;
+ }
+
+ /* Scan for a free block */
+ do {
+ if (inftl->PUtable[pot] == BLOCK_FREE) {
+ inftl->LastFreeEUN = pot;
+ return pot;
+ }
+
+ if (++pot > inftl->lastEUN)
+ pot = 0;
+
+ if (!silly--) {
+ printk(KERN_WARNING "INFTL: no free blocks found! "
+ "EUN range = %d - %d\n", 0, inftl->LastFreeEUN);
+ return BLOCK_NIL;
+ }
+ } while (pot != inftl->LastFreeEUN);
+
+ return BLOCK_NIL;
+}
+
+static u16 INFTL_foldchain(struct INFTLrecord *inftl, unsigned thisVUC, unsigned pendingblock)
+{
+ u16 BlockMap[MAX_SECTORS_PER_UNIT];
+ unsigned char BlockDeleted[MAX_SECTORS_PER_UNIT];
+ unsigned int thisEUN, prevEUN, status;
+ int block, silly;
+ unsigned int targetEUN;
+ struct inftl_oob oob;
+ size_t retlen;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_foldchain(inftl=%p,thisVUC=%d,"
+ "pending=%d)\n", inftl, thisVUC, pendingblock);
+
+ memset(BlockMap, 0xff, sizeof(BlockMap));
+ memset(BlockDeleted, 0, sizeof(BlockDeleted));
+
+ thisEUN = targetEUN = inftl->VUtable[thisVUC];
+
+ if (thisEUN == BLOCK_NIL) {
+ printk(KERN_WARNING "INFTL: trying to fold non-existent "
+ "Virtual Unit Chain %d!\n", thisVUC);
+ return BLOCK_NIL;
+ }
+
+ /*
+ * Scan to find the Erase Unit which holds the actual data for each
+ * 512-byte block within the Chain.
+ */
+ silly = MAX_LOOPS;
+ while (thisEUN < inftl->nb_blocks) {
+ for (block = 0; block < inftl->EraseSize/SECTORSIZE; block ++) {
+ if ((BlockMap[block] != 0xffff) || BlockDeleted[block])
+ continue;
+
+ if (MTD_READOOB(inftl->mbd.mtd, (thisEUN * inftl->EraseSize)
+ + (block * SECTORSIZE), 16 , &retlen,
+ (char *)&oob) < 0)
+ status = SECTOR_IGNORE;
+ else
+ status = oob.b.Status | oob.b.Status1;
+
+ switch(status) {
+ case SECTOR_FREE:
+ case SECTOR_IGNORE:
+ break;
+ case SECTOR_USED:
+ BlockMap[block] = thisEUN;
+ continue;
+ case SECTOR_DELETED:
+ BlockDeleted[block] = 1;
+ continue;
+ default:
+ printk(KERN_WARNING "INFTL: unknown status "
+ "for block %d in EUN %d: %x\n",
+ block, thisEUN, status);
+ break;
+ }
+ }
+
+ if (!silly--) {
+ printk(KERN_WARNING "INFTL: infinite loop in Virtual "
+ "Unit Chain 0x%x\n", thisVUC);
+ return BLOCK_NIL;
+ }
+
+ thisEUN = inftl->PUtable[thisEUN];
+ }
+
+ /*
+ * OK. We now know the location of every block in the Virtual Unit
+ * Chain, and the Erase Unit into which we are supposed to be copying.
+ * Go for it.
+ */
+ DEBUG(MTD_DEBUG_LEVEL1, "INFTL: folding chain %d into unit %d\n",
+ thisVUC, targetEUN);
+
+ for (block = 0; block < inftl->EraseSize/SECTORSIZE ; block++) {
+ unsigned char movebuf[SECTORSIZE];
+ int ret;
+
+ /*
+ * If it's in the target EUN already, or if it's pending write,
+ * do nothing.
+ */
+ if (BlockMap[block] == targetEUN || (pendingblock ==
+ (thisVUC * (inftl->EraseSize / SECTORSIZE) + block))) {
+ continue;
+ }
+
+ /*
+ * Copy only in non free block (free blocks can only
+ * happen in case of media errors or deleted blocks).
+ */
+ if (BlockMap[block] == BLOCK_NIL)
+ continue;
+
+ ret = MTD_READ(inftl->mbd.mtd, (inftl->EraseSize *
+ BlockMap[block]) + (block * SECTORSIZE), SECTORSIZE,
+ &retlen, movebuf);
+ if (ret < 0) {
+ ret = MTD_READ(inftl->mbd.mtd, (inftl->EraseSize *
+ BlockMap[block]) + (block * SECTORSIZE),
+ SECTORSIZE, &retlen, movebuf);
+ if (ret != -EIO)
+ DEBUG(MTD_DEBUG_LEVEL1, "INFTL: error went "
+ "away on retry?\n");
+ }
+ memset(&oob, 0xff, sizeof(struct inftl_oob));
+ oob.b.Status = oob.b.Status1 = SECTOR_USED;
+ MTD_WRITEECC(inftl->mbd.mtd, (inftl->EraseSize * targetEUN) +
+ (block * SECTORSIZE), SECTORSIZE, &retlen,
+ movebuf, (char *)&oob, &inftl->oobinfo);
+ }
+
+ /*
+ * Newest unit in chain now contains data from _all_ older units.
+ * So go through and erase each unit in chain, oldest first. (This
+ * is important, by doing oldest first if we crash/reboot then it
+ * it is relatively simple to clean up the mess).
+ */
+ DEBUG(MTD_DEBUG_LEVEL1, "INFTL: want to erase virtual chain %d\n",
+ thisVUC);
+
+ for (;;) {
+ /* Find oldest unit in chain. */
+ thisEUN = inftl->VUtable[thisVUC];
+ prevEUN = BLOCK_NIL;
+ while (inftl->PUtable[thisEUN] != BLOCK_NIL) {
+ prevEUN = thisEUN;
+ thisEUN = inftl->PUtable[thisEUN];
+ }
+
+ /* Check if we are all done */
+ if (thisEUN == targetEUN)
+ break;
+
+ if (INFTL_formatblock(inftl, thisEUN) < 0) {
+ /*
+ * Could not erase : mark block as reserved.
+ */
+ inftl->PUtable[thisEUN] = BLOCK_RESERVED;
+ } else {
+ /* Correctly erased : mark it as free */
+ inftl->PUtable[thisEUN] = BLOCK_FREE;
+ inftl->PUtable[prevEUN] = BLOCK_NIL;
+ inftl->numfreeEUNs++;
+ }
+ }
+
+ return targetEUN;
+}
+
+static u16 INFTL_makefreeblock(struct INFTLrecord *inftl, unsigned pendingblock)
+{
+ /*
+ * This is the part that needs some cleverness applied.
+ * For now, I'm doing the minimum applicable to actually
+ * get the thing to work.
+ * Wear-levelling and other clever stuff needs to be implemented
+ * and we also need to do some assessment of the results when
+ * the system loses power half-way through the routine.
+ */
+ u16 LongestChain = 0;
+ u16 ChainLength = 0, thislen;
+ u16 chain, EUN;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_makefreeblock(inftl=%p,"
+ "pending=%d)\n", inftl, pendingblock);
+
+ for (chain = 0; chain < inftl->nb_blocks; chain++) {
+ EUN = inftl->VUtable[chain];
+ thislen = 0;
+
+ while (EUN <= inftl->lastEUN) {
+ thislen++;
+ EUN = inftl->PUtable[EUN];
+ if (thislen > 0xff00) {
+ printk(KERN_WARNING "INFTL: endless loop in "
+ "Virtual Chain %d: Unit %x\n",
+ chain, EUN);
+ /*
+ * Actually, don't return failure.
+ * Just ignore this chain and get on with it.
+ */
+ thislen = 0;
+ break;
+ }
+ }
+
+ if (thislen > ChainLength) {
+ ChainLength = thislen;
+ LongestChain = chain;
+ }
+ }
+
+ if (ChainLength < 2) {
+ printk(KERN_WARNING "INFTL: no Virtual Unit Chains available "
+ "for folding. Failing request\n");
+ return BLOCK_NIL;
+ }
+
+ return INFTL_foldchain(inftl, LongestChain, pendingblock);
+}
+
+static int nrbits(unsigned int val, int bitcount)
+{
+ int i, total = 0;
+
+ for (i = 0; (i < bitcount); i++)
+ total += (((0x1 << i) & val) ? 1 : 0);
+ return total;
+}
+
+/*
+ * INFTL_findwriteunit: Return the unit number into which we can write
+ * for this block. Make it available if it isn't already.
+ */
+static inline u16 INFTL_findwriteunit(struct INFTLrecord *inftl, unsigned block)
+{
+ unsigned int thisVUC = block / (inftl->EraseSize / SECTORSIZE);
+ unsigned int thisEUN, writeEUN, prev_block, status;
+ unsigned long blockofs = (block * SECTORSIZE) & (inftl->EraseSize -1);
+ struct inftl_oob oob;
+ struct inftl_bci bci;
+ unsigned char anac, nacs, parity;
+ size_t retlen;
+ int silly, silly2 = 3;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_findwriteunit(inftl=%p,"
+ "block=%d)\n", inftl, block);
+
+ do {
+ /*
+ * Scan the media to find a unit in the VUC which has
+ * a free space for the block in question.
+ */
+ writeEUN = BLOCK_NIL;
+ thisEUN = inftl->VUtable[thisVUC];
+ silly = MAX_LOOPS;
+
+ while (thisEUN <= inftl->lastEUN) {
+ MTD_READOOB(inftl->mbd.mtd, (thisEUN * inftl->EraseSize) +
+ blockofs, 8, &retlen, (char *)&bci);
+
+ status = bci.Status | bci.Status1;
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: status of block %d in "
+ "EUN %d is %x\n", block , writeEUN, status);
+
+ switch(status) {
+ case SECTOR_FREE:
+ writeEUN = thisEUN;
+ break;
+ case SECTOR_DELETED:
+ case SECTOR_USED:
+ /* Can't go any further */
+ goto hitused;
+ case SECTOR_IGNORE:
+ break;
+ default:
+ /*
+ * Invalid block. Don't use it any more.
+ * Must implement.
+ */
+ break;
+ }
+
+ if (!silly--) {
+ printk(KERN_WARNING "INFTL: infinite loop in "
+ "Virtual Unit Chain 0x%x\n", thisVUC);
+ return 0xffff;
+ }
+
+ /* Skip to next block in chain */
+ thisEUN = inftl->PUtable[thisEUN];
+ }
+
+hitused:
+ if (writeEUN != BLOCK_NIL)
+ return writeEUN;
+
+
+ /*
+ * OK. We didn't find one in the existing chain, or there
+ * is no existing chain. Allocate a new one.
+ */
+ writeEUN = INFTL_findfreeblock(inftl, 0);
+
+ if (writeEUN == BLOCK_NIL) {
+ /*
+ * That didn't work - there were no free blocks just
+ * waiting to be picked up. We're going to have to fold
+ * a chain to make room.
+ */
+ thisEUN = INFTL_makefreeblock(inftl, 0xffff);
+
+ /*
+ * Hopefully we free something, lets try again.
+ * This time we are desperate...
+ */
+ DEBUG(MTD_DEBUG_LEVEL1, "INFTL: using desperate==1 "
+ "to find free EUN to accommodate write to "
+ "VUC %d\n", thisVUC);
+ writeEUN = INFTL_findfreeblock(inftl, 1);
+ if (writeEUN == BLOCK_NIL) {
+ /*
+ * Ouch. This should never happen - we should
+ * always be able to make some room somehow.
+ * If we get here, we've allocated more storage
+ * space than actual media, or our makefreeblock
+ * routine is missing something.
+ */
+ printk(KERN_WARNING "INFTL: cannot make free "
+ "space.\n");
+#ifdef DEBUG
+ INFTL_dumptables(inftl);
+ INFTL_dumpVUchains(inftl);
+#endif
+ return BLOCK_NIL;
+ }
+ }
+
+ /*
+ * Insert new block into virtual chain. Firstly update the
+ * block headers in flash...
+ */
+ anac = 0;
+ nacs = 0;
+ thisEUN = inftl->VUtable[thisVUC];
+ if (thisEUN != BLOCK_NIL) {
+ MTD_READOOB(inftl->mbd.mtd, thisEUN * inftl->EraseSize
+ + 8, 8, &retlen, (char *)&oob.u);
+ anac = oob.u.a.ANAC + 1;
+ nacs = oob.u.a.NACs + 1;
+ }
+
+ prev_block = inftl->VUtable[thisVUC];
+ if (prev_block < inftl->nb_blocks)
+ prev_block -= inftl->firstEUN;
+
+ parity = (nrbits(thisVUC, 16) & 0x1) ? 0x1 : 0;
+ parity |= (nrbits(prev_block, 16) & 0x1) ? 0x2 : 0;
+ parity |= (nrbits(anac, 8) & 0x1) ? 0x4 : 0;
+ parity |= (nrbits(nacs, 8) & 0x1) ? 0x8 : 0;
+
+ oob.u.a.virtualUnitNo = cpu_to_le16(thisVUC);
+ oob.u.a.prevUnitNo = cpu_to_le16(prev_block);
+ oob.u.a.ANAC = anac;
+ oob.u.a.NACs = nacs;
+ oob.u.a.parityPerField = parity;
+ oob.u.a.discarded = 0xaa;
+
+ MTD_WRITEOOB(inftl->mbd.mtd, writeEUN * inftl->EraseSize + 8, 8,
+ &retlen, (char *)&oob.u);
+
+ /* Also back up header... */
+ oob.u.b.virtualUnitNo = cpu_to_le16(thisVUC);
+ oob.u.b.prevUnitNo = cpu_to_le16(prev_block);
+ oob.u.b.ANAC = anac;
+ oob.u.b.NACs = nacs;
+ oob.u.b.parityPerField = parity;
+ oob.u.b.discarded = 0xaa;
+
+ MTD_WRITEOOB(inftl->mbd.mtd, writeEUN * inftl->EraseSize +
+ SECTORSIZE * 4 + 8, 8, &retlen, (char *)&oob.u);
+
+ inftl->PUtable[writeEUN] = inftl->VUtable[thisVUC];
+ inftl->VUtable[thisVUC] = writeEUN;
+
+ inftl->numfreeEUNs--;
+ return writeEUN;
+
+ } while (silly2--);
+
+ printk(KERN_WARNING "INFTL: error folding to make room for Virtual "
+ "Unit Chain 0x%x\n", thisVUC);
+ return 0xffff;
+}
+
+/*
+ * Given a Virtual Unit Chain, see if it can be deleted, and if so do it.
+ */
+static void INFTL_trydeletechain(struct INFTLrecord *inftl, unsigned thisVUC)
+{
+ unsigned char BlockUsed[MAX_SECTORS_PER_UNIT];
+ unsigned char BlockDeleted[MAX_SECTORS_PER_UNIT];
+ unsigned int thisEUN, status;
+ int block, silly;
+ struct inftl_bci bci;
+ size_t retlen;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_trydeletechain(inftl=%p,"
+ "thisVUC=%d)\n", inftl, thisVUC);
+
+ memset(BlockUsed, 0, sizeof(BlockUsed));
+ memset(BlockDeleted, 0, sizeof(BlockDeleted));
+
+ thisEUN = inftl->VUtable[thisVUC];
+ if (thisEUN == BLOCK_NIL) {
+ printk(KERN_WARNING "INFTL: trying to delete non-existent "
+ "Virtual Unit Chain %d!\n", thisVUC);
+ return;
+ }
+
+ /*
+ * Scan through the Erase Units to determine whether any data is in
+ * each of the 512-byte blocks within the Chain.
+ */
+ silly = MAX_LOOPS;
+ while (thisEUN < inftl->nb_blocks) {
+ for (block = 0; block < inftl->EraseSize/SECTORSIZE; block++) {
+ if (BlockUsed[block] || BlockDeleted[block])
+ continue;
+
+ if (MTD_READOOB(inftl->mbd.mtd, (thisEUN * inftl->EraseSize)
+ + (block * SECTORSIZE), 8 , &retlen,
+ (char *)&bci) < 0)
+ status = SECTOR_IGNORE;
+ else
+ status = bci.Status | bci.Status1;
+
+ switch(status) {
+ case SECTOR_FREE:
+ case SECTOR_IGNORE:
+ break;
+ case SECTOR_USED:
+ BlockUsed[block] = 1;
+ continue;
+ case SECTOR_DELETED:
+ BlockDeleted[block] = 1;
+ continue;
+ default:
+ printk(KERN_WARNING "INFTL: unknown status "
+ "for block %d in EUN %d: 0x%x\n",
+ block, thisEUN, status);
+ }
+ }
+
+ if (!silly--) {
+ printk(KERN_WARNING "INFTL: infinite loop in Virtual "
+ "Unit Chain 0x%x\n", thisVUC);
+ return;
+ }
+
+ thisEUN = inftl->PUtable[thisEUN];
+ }
+
+ for (block = 0; block < inftl->EraseSize/SECTORSIZE; block++)
+ if (BlockUsed[block])
+ return;
+
+ /*
+ * For each block in the chain free it and make it available
+ * for future use. Erase from the oldest unit first.
+ */
+ DEBUG(MTD_DEBUG_LEVEL1, "INFTL: deleting empty VUC %d\n", thisVUC);
+
+ for (;;) {
+ u16 *prevEUN = &inftl->VUtable[thisVUC];
+ thisEUN = *prevEUN;
+
+ /* If the chain is all gone already, we're done */
+ if (thisEUN == BLOCK_NIL) {
+ DEBUG(MTD_DEBUG_LEVEL2, "INFTL: Empty VUC %d for deletion was already absent\n", thisEUN);
+ return;
+ }
+
+ /* Find oldest unit in chain. */
+ while (inftl->PUtable[thisEUN] != BLOCK_NIL) {
+ BUG_ON(thisEUN >= inftl->nb_blocks);
+
+ prevEUN = &inftl->PUtable[thisEUN];
+ thisEUN = *prevEUN;
+ }
+
+ DEBUG(MTD_DEBUG_LEVEL3, "Deleting EUN %d from VUC %d\n",
+ thisEUN, thisVUC);
+
+ if (INFTL_formatblock(inftl, thisEUN) < 0) {
+ /*
+ * Could not erase : mark block as reserved.
+ */
+ inftl->PUtable[thisEUN] = BLOCK_RESERVED;
+ } else {
+ /* Correctly erased : mark it as free */
+ inftl->PUtable[thisEUN] = BLOCK_FREE;
+ inftl->numfreeEUNs++;
+ }
+
+ /* Now sort out whatever was pointing to it... */
+ *prevEUN = BLOCK_NIL;
+
+ /* Ideally we'd actually be responsive to new
+ requests while we're doing this -- if there's
+ free space why should others be made to wait? */
+ cond_resched();
+ }
+
+ inftl->VUtable[thisVUC] = BLOCK_NIL;
+}
+
+static int INFTL_deleteblock(struct INFTLrecord *inftl, unsigned block)
+{
+ unsigned int thisEUN = inftl->VUtable[block / (inftl->EraseSize / SECTORSIZE)];
+ unsigned long blockofs = (block * SECTORSIZE) & (inftl->EraseSize - 1);
+ unsigned int status;
+ int silly = MAX_LOOPS;
+ size_t retlen;
+ struct inftl_bci bci;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_deleteblock(inftl=%p,"
+ "block=%d)\n", inftl, block);
+
+ while (thisEUN < inftl->nb_blocks) {
+ if (MTD_READOOB(inftl->mbd.mtd, (thisEUN * inftl->EraseSize) +
+ blockofs, 8, &retlen, (char *)&bci) < 0)
+ status = SECTOR_IGNORE;
+ else
+ status = bci.Status | bci.Status1;
+
+ switch (status) {
+ case SECTOR_FREE:
+ case SECTOR_IGNORE:
+ break;
+ case SECTOR_DELETED:
+ thisEUN = BLOCK_NIL;
+ goto foundit;
+ case SECTOR_USED:
+ goto foundit;
+ default:
+ printk(KERN_WARNING "INFTL: unknown status for "
+ "block %d in EUN %d: 0x%x\n",
+ block, thisEUN, status);
+ break;
+ }
+
+ if (!silly--) {
+ printk(KERN_WARNING "INFTL: infinite loop in Virtual "
+ "Unit Chain 0x%x\n",
+ block / (inftl->EraseSize / SECTORSIZE));
+ return 1;
+ }
+ thisEUN = inftl->PUtable[thisEUN];
+ }
+
+foundit:
+ if (thisEUN != BLOCK_NIL) {
+ loff_t ptr = (thisEUN * inftl->EraseSize) + blockofs;
+
+ if (MTD_READOOB(inftl->mbd.mtd, ptr, 8, &retlen, (char *)&bci) < 0)
+ return -EIO;
+ bci.Status = bci.Status1 = SECTOR_DELETED;
+ if (MTD_WRITEOOB(inftl->mbd.mtd, ptr, 8, &retlen, (char *)&bci) < 0)
+ return -EIO;
+ INFTL_trydeletechain(inftl, block / (inftl->EraseSize / SECTORSIZE));
+ }
+ return 0;
+}
+
+static int inftl_writeblock(struct mtd_blktrans_dev *mbd, unsigned long block,
+ char *buffer)
+{
+ struct INFTLrecord *inftl = (void *)mbd;
+ unsigned int writeEUN;
+ unsigned long blockofs = (block * SECTORSIZE) & (inftl->EraseSize - 1);
+ size_t retlen;
+ struct inftl_oob oob;
+ char *p, *pend;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: inftl_writeblock(inftl=%p,block=%ld,"
+ "buffer=%p)\n", inftl, block, buffer);
+
+ /* Is block all zero? */
+ pend = buffer + SECTORSIZE;
+ for (p = buffer; p < pend && !*p; p++)
+ ;
+
+ if (p < pend) {
+ writeEUN = INFTL_findwriteunit(inftl, block);
+
+ if (writeEUN == BLOCK_NIL) {
+ printk(KERN_WARNING "inftl_writeblock(): cannot find "
+ "block to write to\n");
+ /*
+ * If we _still_ haven't got a block to use,
+ * we're screwed.
+ */
+ return 1;
+ }
+
+ memset(&oob, 0xff, sizeof(struct inftl_oob));
+ oob.b.Status = oob.b.Status1 = SECTOR_USED;
+ MTD_WRITEECC(inftl->mbd.mtd, (writeEUN * inftl->EraseSize) +
+ blockofs, SECTORSIZE, &retlen, (char *)buffer,
+ (char *)&oob, &inftl->oobinfo);
+ /*
+ * need to write SECTOR_USED flags since they are not written
+ * in mtd_writeecc
+ */
+ } else {
+ INFTL_deleteblock(inftl, block);
+ }
+
+ return 0;
+}
+
+static int inftl_readblock(struct mtd_blktrans_dev *mbd, unsigned long block,
+ char *buffer)
+{
+ struct INFTLrecord *inftl = (void *)mbd;
+ unsigned int thisEUN = inftl->VUtable[block / (inftl->EraseSize / SECTORSIZE)];
+ unsigned long blockofs = (block * SECTORSIZE) & (inftl->EraseSize - 1);
+ unsigned int status;
+ int silly = MAX_LOOPS;
+ struct inftl_bci bci;
+ size_t retlen;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: inftl_readblock(inftl=%p,block=%ld,"
+ "buffer=%p)\n", inftl, block, buffer);
+
+ while (thisEUN < inftl->nb_blocks) {
+ if (MTD_READOOB(inftl->mbd.mtd, (thisEUN * inftl->EraseSize) +
+ blockofs, 8, &retlen, (char *)&bci) < 0)
+ status = SECTOR_IGNORE;
+ else
+ status = bci.Status | bci.Status1;
+
+ switch (status) {
+ case SECTOR_DELETED:
+ thisEUN = BLOCK_NIL;
+ goto foundit;
+ case SECTOR_USED:
+ goto foundit;
+ case SECTOR_FREE:
+ case SECTOR_IGNORE:
+ break;
+ default:
+ printk(KERN_WARNING "INFTL: unknown status for "
+ "block %ld in EUN %d: 0x%04x\n",
+ block, thisEUN, status);
+ break;
+ }
+
+ if (!silly--) {
+ printk(KERN_WARNING "INFTL: infinite loop in "
+ "Virtual Unit Chain 0x%lx\n",
+ block / (inftl->EraseSize / SECTORSIZE));
+ return 1;
+ }
+
+ thisEUN = inftl->PUtable[thisEUN];
+ }
+
+foundit:
+ if (thisEUN == BLOCK_NIL) {
+ /* The requested block is not on the media, return all 0x00 */
+ memset(buffer, 0, SECTORSIZE);
+ } else {
+ size_t retlen;
+ loff_t ptr = (thisEUN * inftl->EraseSize) + blockofs;
+ if (MTD_READ(inftl->mbd.mtd, ptr, SECTORSIZE, &retlen,
+ buffer))
+ return -EIO;
+ }
+ return 0;
+}
+
+static int inftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
+{
+ struct INFTLrecord *inftl = (void *)dev;
+
+ geo->heads = inftl->heads;
+ geo->sectors = inftl->sectors;
+ geo->cylinders = inftl->cylinders;
+
+ return 0;
+}
+
+static struct mtd_blktrans_ops inftl_tr = {
+ .name = "inftl",
+ .major = INFTL_MAJOR,
+ .part_bits = INFTL_PARTN_BITS,
+ .getgeo = inftl_getgeo,
+ .readsect = inftl_readblock,
+ .writesect = inftl_writeblock,
+ .add_mtd = inftl_add_mtd,
+ .remove_dev = inftl_remove_dev,
+ .owner = THIS_MODULE,
+};
+
+extern char inftlmountrev[];
+
+static int __init init_inftl(void)
+{
+ printk(KERN_INFO "INFTL: inftlcore.c $Revision: 1.18 $, "
+ "inftlmount.c %s\n", inftlmountrev);
+
+ return register_mtd_blktrans(&inftl_tr);
+}
+
+static void __exit cleanup_inftl(void)
+{
+ deregister_mtd_blktrans(&inftl_tr);
+}
+
+module_init(init_inftl);
+module_exit(cleanup_inftl);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Greg Ungerer <gerg@snapgear.com>, David Woodhouse <dwmw2@infradead.org>, Fabrice Bellard <fabrice.bellard@netgem.com> et al.");
+MODULE_DESCRIPTION("Support code for Inverse Flash Translation Layer, used on M-Systems DiskOnChip 2000, Millennium and Millennium Plus");
diff --git a/drivers/mtd/inftlmount.c b/drivers/mtd/inftlmount.c
new file mode 100644
index 000000000000..b5dda47395a7
--- /dev/null
+++ b/drivers/mtd/inftlmount.c
@@ -0,0 +1,804 @@
+/*
+ * inftlmount.c -- INFTL mount code with extensive checks.
+ *
+ * Author: Greg Ungerer (gerg@snapgear.com)
+ * (C) Copyright 2002-2003, Greg Ungerer (gerg@snapgear.com)
+ *
+ * Based heavily on the nftlmount.c code which is:
+ * Author: Fabrice Bellard (fabrice.bellard@netgem.com)
+ * Copyright (C) 2000 Netgem S.A.
+ *
+ * $Id: inftlmount.c,v 1.16 2004/11/22 13:50:53 kalev Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nftl.h>
+#include <linux/mtd/inftl.h>
+#include <linux/mtd/compatmac.h>
+
+char inftlmountrev[]="$Revision: 1.16 $";
+
+/*
+ * find_boot_record: Find the INFTL Media Header and its Spare copy which
+ * contains the various device information of the INFTL partition and
+ * Bad Unit Table. Update the PUtable[] table according to the Bad
+ * Unit Table. PUtable[] is used for management of Erase Unit in
+ * other routines in inftlcore.c and inftlmount.c.
+ */
+static int find_boot_record(struct INFTLrecord *inftl)
+{
+ struct inftl_unittail h1;
+ //struct inftl_oob oob;
+ unsigned int i, block;
+ u8 buf[SECTORSIZE];
+ struct INFTLMediaHeader *mh = &inftl->MediaHdr;
+ struct INFTLPartition *ip;
+ size_t retlen;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: find_boot_record(inftl=%p)\n", inftl);
+
+ /*
+ * Assume logical EraseSize == physical erasesize for starting the
+ * scan. We'll sort it out later if we find a MediaHeader which says
+ * otherwise.
+ */
+ inftl->EraseSize = inftl->mbd.mtd->erasesize;
+ inftl->nb_blocks = inftl->mbd.mtd->size / inftl->EraseSize;
+
+ inftl->MediaUnit = BLOCK_NIL;
+
+ /* Search for a valid boot record */
+ for (block = 0; block < inftl->nb_blocks; block++) {
+ int ret;
+
+ /*
+ * Check for BNAND header first. Then whinge if it's found
+ * but later checks fail.
+ */
+ ret = MTD_READ(inftl->mbd.mtd, block * inftl->EraseSize,
+ SECTORSIZE, &retlen, buf);
+ /* We ignore ret in case the ECC of the MediaHeader is invalid
+ (which is apparently acceptable) */
+ if (retlen != SECTORSIZE) {
+ static int warncount = 5;
+
+ if (warncount) {
+ printk(KERN_WARNING "INFTL: block read at 0x%x "
+ "of mtd%d failed: %d\n",
+ block * inftl->EraseSize,
+ inftl->mbd.mtd->index, ret);
+ if (!--warncount)
+ printk(KERN_WARNING "INFTL: further "
+ "failures for this block will "
+ "not be printed\n");
+ }
+ continue;
+ }
+
+ if (retlen < 6 || memcmp(buf, "BNAND", 6)) {
+ /* BNAND\0 not found. Continue */
+ continue;
+ }
+
+ /* To be safer with BIOS, also use erase mark as discriminant */
+ if ((ret = MTD_READOOB(inftl->mbd.mtd, block * inftl->EraseSize +
+ SECTORSIZE + 8, 8, &retlen, (char *)&h1) < 0)) {
+ printk(KERN_WARNING "INFTL: ANAND header found at "
+ "0x%x in mtd%d, but OOB data read failed "
+ "(err %d)\n", block * inftl->EraseSize,
+ inftl->mbd.mtd->index, ret);
+ continue;
+ }
+
+
+ /*
+ * This is the first we've seen.
+ * Copy the media header structure into place.
+ */
+ memcpy(mh, buf, sizeof(struct INFTLMediaHeader));
+
+ /* Read the spare media header at offset 4096 */
+ MTD_READ(inftl->mbd.mtd, block * inftl->EraseSize + 4096,
+ SECTORSIZE, &retlen, buf);
+ if (retlen != SECTORSIZE) {
+ printk(KERN_WARNING "INFTL: Unable to read spare "
+ "Media Header\n");
+ return -1;
+ }
+ /* Check if this one is the same as the first one we found. */
+ if (memcmp(mh, buf, sizeof(struct INFTLMediaHeader))) {
+ printk(KERN_WARNING "INFTL: Primary and spare Media "
+ "Headers disagree.\n");
+ return -1;
+ }
+
+ mh->NoOfBootImageBlocks = le32_to_cpu(mh->NoOfBootImageBlocks);
+ mh->NoOfBinaryPartitions = le32_to_cpu(mh->NoOfBinaryPartitions);
+ mh->NoOfBDTLPartitions = le32_to_cpu(mh->NoOfBDTLPartitions);
+ mh->BlockMultiplierBits = le32_to_cpu(mh->BlockMultiplierBits);
+ mh->FormatFlags = le32_to_cpu(mh->FormatFlags);
+ mh->PercentUsed = le32_to_cpu(mh->PercentUsed);
+
+#ifdef CONFIG_MTD_DEBUG_VERBOSE
+ if (CONFIG_MTD_DEBUG_VERBOSE >= 2) {
+ printk("INFTL: Media Header ->\n"
+ " bootRecordID = %s\n"
+ " NoOfBootImageBlocks = %d\n"
+ " NoOfBinaryPartitions = %d\n"
+ " NoOfBDTLPartitions = %d\n"
+ " BlockMultiplerBits = %d\n"
+ " FormatFlgs = %d\n"
+ " OsakVersion = 0x%x\n"
+ " PercentUsed = %d\n",
+ mh->bootRecordID, mh->NoOfBootImageBlocks,
+ mh->NoOfBinaryPartitions,
+ mh->NoOfBDTLPartitions,
+ mh->BlockMultiplierBits, mh->FormatFlags,
+ mh->OsakVersion, mh->PercentUsed);
+ }
+#endif
+
+ if (mh->NoOfBDTLPartitions == 0) {
+ printk(KERN_WARNING "INFTL: Media Header sanity check "
+ "failed: NoOfBDTLPartitions (%d) == 0, "
+ "must be at least 1\n", mh->NoOfBDTLPartitions);
+ return -1;
+ }
+
+ if ((mh->NoOfBDTLPartitions + mh->NoOfBinaryPartitions) > 4) {
+ printk(KERN_WARNING "INFTL: Media Header sanity check "
+ "failed: Total Partitions (%d) > 4, "
+ "BDTL=%d Binary=%d\n", mh->NoOfBDTLPartitions +
+ mh->NoOfBinaryPartitions,
+ mh->NoOfBDTLPartitions,
+ mh->NoOfBinaryPartitions);
+ return -1;
+ }
+
+ if (mh->BlockMultiplierBits > 1) {
+ printk(KERN_WARNING "INFTL: sorry, we don't support "
+ "UnitSizeFactor 0x%02x\n",
+ mh->BlockMultiplierBits);
+ return -1;
+ } else if (mh->BlockMultiplierBits == 1) {
+ printk(KERN_WARNING "INFTL: support for INFTL with "
+ "UnitSizeFactor 0x%02x is experimental\n",
+ mh->BlockMultiplierBits);
+ inftl->EraseSize = inftl->mbd.mtd->erasesize <<
+ mh->BlockMultiplierBits;
+ inftl->nb_blocks = inftl->mbd.mtd->size / inftl->EraseSize;
+ block >>= mh->BlockMultiplierBits;
+ }
+
+ /* Scan the partitions */
+ for (i = 0; (i < 4); i++) {
+ ip = &mh->Partitions[i];
+ ip->virtualUnits = le32_to_cpu(ip->virtualUnits);
+ ip->firstUnit = le32_to_cpu(ip->firstUnit);
+ ip->lastUnit = le32_to_cpu(ip->lastUnit);
+ ip->flags = le32_to_cpu(ip->flags);
+ ip->spareUnits = le32_to_cpu(ip->spareUnits);
+ ip->Reserved0 = le32_to_cpu(ip->Reserved0);
+
+#ifdef CONFIG_MTD_DEBUG_VERBOSE
+ if (CONFIG_MTD_DEBUG_VERBOSE >= 2) {
+ printk(" PARTITION[%d] ->\n"
+ " virtualUnits = %d\n"
+ " firstUnit = %d\n"
+ " lastUnit = %d\n"
+ " flags = 0x%x\n"
+ " spareUnits = %d\n",
+ i, ip->virtualUnits, ip->firstUnit,
+ ip->lastUnit, ip->flags,
+ ip->spareUnits);
+ }
+#endif
+
+ if (ip->Reserved0 != ip->firstUnit) {
+ struct erase_info *instr = &inftl->instr;
+
+ instr->mtd = inftl->mbd.mtd;
+
+ /*
+ * Most likely this is using the
+ * undocumented qiuck mount feature.
+ * We don't support that, we will need
+ * to erase the hidden block for full
+ * compatibility.
+ */
+ instr->addr = ip->Reserved0 * inftl->EraseSize;
+ instr->len = inftl->EraseSize;
+ MTD_ERASE(inftl->mbd.mtd, instr);
+ }
+ if ((ip->lastUnit - ip->firstUnit + 1) < ip->virtualUnits) {
+ printk(KERN_WARNING "INFTL: Media Header "
+ "Partition %d sanity check failed\n"
+ " firstUnit %d : lastUnit %d > "
+ "virtualUnits %d\n", i, ip->lastUnit,
+ ip->firstUnit, ip->Reserved0);
+ return -1;
+ }
+ if (ip->Reserved1 != 0) {
+ printk(KERN_WARNING "INFTL: Media Header "
+ "Partition %d sanity check failed: "
+ "Reserved1 %d != 0\n",
+ i, ip->Reserved1);
+ return -1;
+ }
+
+ if (ip->flags & INFTL_BDTL)
+ break;
+ }
+
+ if (i >= 4) {
+ printk(KERN_WARNING "INFTL: Media Header Partition "
+ "sanity check failed:\n No partition "
+ "marked as Disk Partition\n");
+ return -1;
+ }
+
+ inftl->nb_boot_blocks = ip->firstUnit;
+ inftl->numvunits = ip->virtualUnits;
+ if (inftl->numvunits > (inftl->nb_blocks -
+ inftl->nb_boot_blocks - 2)) {
+ printk(KERN_WARNING "INFTL: Media Header sanity check "
+ "failed:\n numvunits (%d) > nb_blocks "
+ "(%d) - nb_boot_blocks(%d) - 2\n",
+ inftl->numvunits, inftl->nb_blocks,
+ inftl->nb_boot_blocks);
+ return -1;
+ }
+
+ inftl->mbd.size = inftl->numvunits *
+ (inftl->EraseSize / SECTORSIZE);
+
+ /*
+ * Block count is set to last used EUN (we won't need to keep
+ * any meta-data past that point).
+ */
+ inftl->firstEUN = ip->firstUnit;
+ inftl->lastEUN = ip->lastUnit;
+ inftl->nb_blocks = ip->lastUnit + 1;
+
+ /* Memory alloc */
+ inftl->PUtable = kmalloc(inftl->nb_blocks * sizeof(u16), GFP_KERNEL);
+ if (!inftl->PUtable) {
+ printk(KERN_WARNING "INFTL: allocation of PUtable "
+ "failed (%zd bytes)\n",
+ inftl->nb_blocks * sizeof(u16));
+ return -ENOMEM;
+ }
+
+ inftl->VUtable = kmalloc(inftl->nb_blocks * sizeof(u16), GFP_KERNEL);
+ if (!inftl->VUtable) {
+ kfree(inftl->PUtable);
+ printk(KERN_WARNING "INFTL: allocation of VUtable "
+ "failed (%zd bytes)\n",
+ inftl->nb_blocks * sizeof(u16));
+ return -ENOMEM;
+ }
+
+ /* Mark the blocks before INFTL MediaHeader as reserved */
+ for (i = 0; i < inftl->nb_boot_blocks; i++)
+ inftl->PUtable[i] = BLOCK_RESERVED;
+ /* Mark all remaining blocks as potentially containing data */
+ for (; i < inftl->nb_blocks; i++)
+ inftl->PUtable[i] = BLOCK_NOTEXPLORED;
+
+ /* Mark this boot record (NFTL MediaHeader) block as reserved */
+ inftl->PUtable[block] = BLOCK_RESERVED;
+
+ /* Read Bad Erase Unit Table and modify PUtable[] accordingly */
+ for (i = 0; i < inftl->nb_blocks; i++) {
+ int physblock;
+ /* If any of the physical eraseblocks are bad, don't
+ use the unit. */
+ for (physblock = 0; physblock < inftl->EraseSize; physblock += inftl->mbd.mtd->erasesize) {
+ if (inftl->mbd.mtd->block_isbad(inftl->mbd.mtd, i * inftl->EraseSize + physblock))
+ inftl->PUtable[i] = BLOCK_RESERVED;
+ }
+ }
+
+ inftl->MediaUnit = block;
+ return 0;
+ }
+
+ /* Not found. */
+ return -1;
+}
+
+static int memcmpb(void *a, int c, int n)
+{
+ int i;
+ for (i = 0; i < n; i++) {
+ if (c != ((unsigned char *)a)[i])
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * check_free_sector: check if a free sector is actually FREE,
+ * i.e. All 0xff in data and oob area.
+ */
+static int check_free_sectors(struct INFTLrecord *inftl, unsigned int address,
+ int len, int check_oob)
+{
+ u8 buf[SECTORSIZE + inftl->mbd.mtd->oobsize];
+ size_t retlen;
+ int i;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: check_free_sectors(inftl=%p,"
+ "address=0x%x,len=%d,check_oob=%d)\n", inftl,
+ address, len, check_oob);
+
+ for (i = 0; i < len; i += SECTORSIZE) {
+ if (MTD_READECC(inftl->mbd.mtd, address, SECTORSIZE, &retlen, buf, &buf[SECTORSIZE], &inftl->oobinfo) < 0)
+ return -1;
+ if (memcmpb(buf, 0xff, SECTORSIZE) != 0)
+ return -1;
+
+ if (check_oob) {
+ if (memcmpb(buf + SECTORSIZE, 0xff, inftl->mbd.mtd->oobsize) != 0)
+ return -1;
+ }
+ address += SECTORSIZE;
+ }
+
+ return 0;
+}
+
+/*
+ * INFTL_format: format a Erase Unit by erasing ALL Erase Zones in the Erase
+ * Unit and Update INFTL metadata. Each erase operation is
+ * checked with check_free_sectors.
+ *
+ * Return: 0 when succeed, -1 on error.
+ *
+ * ToDo: 1. Is it neceressary to check_free_sector after erasing ??
+ */
+int INFTL_formatblock(struct INFTLrecord *inftl, int block)
+{
+ size_t retlen;
+ struct inftl_unittail uci;
+ struct erase_info *instr = &inftl->instr;
+ int physblock;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_formatblock(inftl=%p,"
+ "block=%d)\n", inftl, block);
+
+ memset(instr, 0, sizeof(struct erase_info));
+
+ /* FIXME: Shouldn't we be setting the 'discarded' flag to zero
+ _first_? */
+
+ /* Use async erase interface, test return code */
+ instr->mtd = inftl->mbd.mtd;
+ instr->addr = block * inftl->EraseSize;
+ instr->len = inftl->mbd.mtd->erasesize;
+ /* Erase one physical eraseblock at a time, even though the NAND api
+ allows us to group them. This way we if we have a failure, we can
+ mark only the failed block in the bbt. */
+ for (physblock = 0; physblock < inftl->EraseSize; physblock += instr->len, instr->addr += instr->len) {
+ MTD_ERASE(inftl->mbd.mtd, instr);
+
+ if (instr->state == MTD_ERASE_FAILED) {
+ printk(KERN_WARNING "INFTL: error while formatting block %d\n",
+ block);
+ goto fail;
+ }
+
+ /*
+ * Check the "freeness" of Erase Unit before updating metadata.
+ * FixMe: is this check really necessary? Since we have check the
+ * return code after the erase operation.
+ */
+ if (check_free_sectors(inftl, instr->addr, instr->len, 1) != 0)
+ goto fail;
+ }
+
+ uci.EraseMark = cpu_to_le16(ERASE_MARK);
+ uci.EraseMark1 = cpu_to_le16(ERASE_MARK);
+ uci.Reserved[0] = 0;
+ uci.Reserved[1] = 0;
+ uci.Reserved[2] = 0;
+ uci.Reserved[3] = 0;
+ instr->addr = block * inftl->EraseSize + SECTORSIZE * 2;
+ if (MTD_WRITEOOB(inftl->mbd.mtd, instr->addr +
+ 8, 8, &retlen, (char *)&uci) < 0)
+ goto fail;
+ return 0;
+fail:
+ /* could not format, update the bad block table (caller is responsible
+ for setting the PUtable to BLOCK_RESERVED on failure) */
+ inftl->mbd.mtd->block_markbad(inftl->mbd.mtd, instr->addr);
+ return -1;
+}
+
+/*
+ * format_chain: Format an invalid Virtual Unit chain. It frees all the Erase
+ * Units in a Virtual Unit Chain, i.e. all the units are disconnected.
+ *
+ * Since the chain is invalid then we will have to erase it from its
+ * head (normally for INFTL we go from the oldest). But if it has a
+ * loop then there is no oldest...
+ */
+static void format_chain(struct INFTLrecord *inftl, unsigned int first_block)
+{
+ unsigned int block = first_block, block1;
+
+ printk(KERN_WARNING "INFTL: formatting chain at block %d\n",
+ first_block);
+
+ for (;;) {
+ block1 = inftl->PUtable[block];
+
+ printk(KERN_WARNING "INFTL: formatting block %d\n", block);
+ if (INFTL_formatblock(inftl, block) < 0) {
+ /*
+ * Cannot format !!!! Mark it as Bad Unit,
+ */
+ inftl->PUtable[block] = BLOCK_RESERVED;
+ } else {
+ inftl->PUtable[block] = BLOCK_FREE;
+ }
+
+ /* Goto next block on the chain */
+ block = block1;
+
+ if (block == BLOCK_NIL || block >= inftl->lastEUN)
+ break;
+ }
+}
+
+void INFTL_dumptables(struct INFTLrecord *s)
+{
+ int i;
+
+ printk("-------------------------------------------"
+ "----------------------------------\n");
+
+ printk("VUtable[%d] ->", s->nb_blocks);
+ for (i = 0; i < s->nb_blocks; i++) {
+ if ((i % 8) == 0)
+ printk("\n%04x: ", i);
+ printk("%04x ", s->VUtable[i]);
+ }
+
+ printk("\n-------------------------------------------"
+ "----------------------------------\n");
+
+ printk("PUtable[%d-%d=%d] ->", s->firstEUN, s->lastEUN, s->nb_blocks);
+ for (i = 0; i <= s->lastEUN; i++) {
+ if ((i % 8) == 0)
+ printk("\n%04x: ", i);
+ printk("%04x ", s->PUtable[i]);
+ }
+
+ printk("\n-------------------------------------------"
+ "----------------------------------\n");
+
+ printk("INFTL ->\n"
+ " EraseSize = %d\n"
+ " h/s/c = %d/%d/%d\n"
+ " numvunits = %d\n"
+ " firstEUN = %d\n"
+ " lastEUN = %d\n"
+ " numfreeEUNs = %d\n"
+ " LastFreeEUN = %d\n"
+ " nb_blocks = %d\n"
+ " nb_boot_blocks = %d",
+ s->EraseSize, s->heads, s->sectors, s->cylinders,
+ s->numvunits, s->firstEUN, s->lastEUN, s->numfreeEUNs,
+ s->LastFreeEUN, s->nb_blocks, s->nb_boot_blocks);
+
+ printk("\n-------------------------------------------"
+ "----------------------------------\n");
+}
+
+void INFTL_dumpVUchains(struct INFTLrecord *s)
+{
+ int logical, block, i;
+
+ printk("-------------------------------------------"
+ "----------------------------------\n");
+
+ printk("INFTL Virtual Unit Chains:\n");
+ for (logical = 0; logical < s->nb_blocks; logical++) {
+ block = s->VUtable[logical];
+ if (block > s->nb_blocks)
+ continue;
+ printk(" LOGICAL %d --> %d ", logical, block);
+ for (i = 0; i < s->nb_blocks; i++) {
+ if (s->PUtable[block] == BLOCK_NIL)
+ break;
+ block = s->PUtable[block];
+ printk("%d ", block);
+ }
+ printk("\n");
+ }
+
+ printk("-------------------------------------------"
+ "----------------------------------\n");
+}
+
+int INFTL_mount(struct INFTLrecord *s)
+{
+ unsigned int block, first_block, prev_block, last_block;
+ unsigned int first_logical_block, logical_block, erase_mark;
+ int chain_length, do_format_chain;
+ struct inftl_unithead1 h0;
+ struct inftl_unittail h1;
+ size_t retlen;
+ int i;
+ u8 *ANACtable, ANAC;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: INFTL_mount(inftl=%p)\n", s);
+
+ /* Search for INFTL MediaHeader and Spare INFTL Media Header */
+ if (find_boot_record(s) < 0) {
+ printk(KERN_WARNING "INFTL: could not find valid boot record?\n");
+ return -1;
+ }
+
+ /* Init the logical to physical table */
+ for (i = 0; i < s->nb_blocks; i++)
+ s->VUtable[i] = BLOCK_NIL;
+
+ logical_block = block = BLOCK_NIL;
+
+ /* Temporary buffer to store ANAC numbers. */
+ ANACtable = kmalloc(s->nb_blocks * sizeof(u8), GFP_KERNEL);
+ memset(ANACtable, 0, s->nb_blocks);
+
+ /*
+ * First pass is to explore each physical unit, and construct the
+ * virtual chains that exist (newest physical unit goes into VUtable).
+ * Any block that is in any way invalid will be left in the
+ * NOTEXPLORED state. Then at the end we will try to format it and
+ * mark it as free.
+ */
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: pass 1, explore each unit\n");
+ for (first_block = s->firstEUN; first_block <= s->lastEUN; first_block++) {
+ if (s->PUtable[first_block] != BLOCK_NOTEXPLORED)
+ continue;
+
+ do_format_chain = 0;
+ first_logical_block = BLOCK_NIL;
+ last_block = BLOCK_NIL;
+ block = first_block;
+
+ for (chain_length = 0; ; chain_length++) {
+
+ if ((chain_length == 0) &&
+ (s->PUtable[block] != BLOCK_NOTEXPLORED)) {
+ /* Nothing to do here, onto next block */
+ break;
+ }
+
+ if (MTD_READOOB(s->mbd.mtd, block * s->EraseSize + 8,
+ 8, &retlen, (char *)&h0) < 0 ||
+ MTD_READOOB(s->mbd.mtd, block * s->EraseSize +
+ 2 * SECTORSIZE + 8, 8, &retlen, (char *)&h1) < 0) {
+ /* Should never happen? */
+ do_format_chain++;
+ break;
+ }
+
+ logical_block = le16_to_cpu(h0.virtualUnitNo);
+ prev_block = le16_to_cpu(h0.prevUnitNo);
+ erase_mark = le16_to_cpu((h1.EraseMark | h1.EraseMark1));
+ ANACtable[block] = h0.ANAC;
+
+ /* Previous block is relative to start of Partition */
+ if (prev_block < s->nb_blocks)
+ prev_block += s->firstEUN;
+
+ /* Already explored partial chain? */
+ if (s->PUtable[block] != BLOCK_NOTEXPLORED) {
+ /* Check if chain for this logical */
+ if (logical_block == first_logical_block) {
+ if (last_block != BLOCK_NIL)
+ s->PUtable[last_block] = block;
+ }
+ break;
+ }
+
+ /* Check for invalid block */
+ if (erase_mark != ERASE_MARK) {
+ printk(KERN_WARNING "INFTL: corrupt block %d "
+ "in chain %d, chain length %d, erase "
+ "mark 0x%x?\n", block, first_block,
+ chain_length, erase_mark);
+ /*
+ * Assume end of chain, probably incomplete
+ * fold/erase...
+ */
+ if (chain_length == 0)
+ do_format_chain++;
+ break;
+ }
+
+ /* Check for it being free already then... */
+ if ((logical_block == BLOCK_FREE) ||
+ (logical_block == BLOCK_NIL)) {
+ s->PUtable[block] = BLOCK_FREE;
+ break;
+ }
+
+ /* Sanity checks on block numbers */
+ if ((logical_block >= s->nb_blocks) ||
+ ((prev_block >= s->nb_blocks) &&
+ (prev_block != BLOCK_NIL))) {
+ if (chain_length > 0) {
+ printk(KERN_WARNING "INFTL: corrupt "
+ "block %d in chain %d?\n",
+ block, first_block);
+ do_format_chain++;
+ }
+ break;
+ }
+
+ if (first_logical_block == BLOCK_NIL) {
+ first_logical_block = logical_block;
+ } else {
+ if (first_logical_block != logical_block) {
+ /* Normal for folded chain... */
+ break;
+ }
+ }
+
+ /*
+ * Current block is valid, so if we followed a virtual
+ * chain to get here then we can set the previous
+ * block pointer in our PUtable now. Then move onto
+ * the previous block in the chain.
+ */
+ s->PUtable[block] = BLOCK_NIL;
+ if (last_block != BLOCK_NIL)
+ s->PUtable[last_block] = block;
+ last_block = block;
+ block = prev_block;
+
+ /* Check for end of chain */
+ if (block == BLOCK_NIL)
+ break;
+
+ /* Validate next block before following it... */
+ if (block > s->lastEUN) {
+ printk(KERN_WARNING "INFTL: invalid previous "
+ "block %d in chain %d?\n", block,
+ first_block);
+ do_format_chain++;
+ break;
+ }
+ }
+
+ if (do_format_chain) {
+ format_chain(s, first_block);
+ continue;
+ }
+
+ /*
+ * Looks like a valid chain then. It may not really be the
+ * newest block in the chain, but it is the newest we have
+ * found so far. We might update it in later iterations of
+ * this loop if we find something newer.
+ */
+ s->VUtable[first_logical_block] = first_block;
+ logical_block = BLOCK_NIL;
+ }
+
+#ifdef CONFIG_MTD_DEBUG_VERBOSE
+ if (CONFIG_MTD_DEBUG_VERBOSE >= 2)
+ INFTL_dumptables(s);
+#endif
+
+ /*
+ * Second pass, check for infinite loops in chains. These are
+ * possible because we don't update the previous pointers when
+ * we fold chains. No big deal, just fix them up in PUtable.
+ */
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: pass 2, validate virtual chains\n");
+ for (logical_block = 0; logical_block < s->numvunits; logical_block++) {
+ block = s->VUtable[logical_block];
+ last_block = BLOCK_NIL;
+
+ /* Check for free/reserved/nil */
+ if (block >= BLOCK_RESERVED)
+ continue;
+
+ ANAC = ANACtable[block];
+ for (i = 0; i < s->numvunits; i++) {
+ if (s->PUtable[block] == BLOCK_NIL)
+ break;
+ if (s->PUtable[block] > s->lastEUN) {
+ printk(KERN_WARNING "INFTL: invalid prev %d, "
+ "in virtual chain %d\n",
+ s->PUtable[block], logical_block);
+ s->PUtable[block] = BLOCK_NIL;
+
+ }
+ if (ANACtable[block] != ANAC) {
+ /*
+ * Chain must point back to itself. This is ok,
+ * but we will need adjust the tables with this
+ * newest block and oldest block.
+ */
+ s->VUtable[logical_block] = block;
+ s->PUtable[last_block] = BLOCK_NIL;
+ break;
+ }
+
+ ANAC--;
+ last_block = block;
+ block = s->PUtable[block];
+ }
+
+ if (i >= s->nb_blocks) {
+ /*
+ * Uhoo, infinite chain with valid ANACS!
+ * Format whole chain...
+ */
+ format_chain(s, first_block);
+ }
+ }
+
+#ifdef CONFIG_MTD_DEBUG_VERBOSE
+ if (CONFIG_MTD_DEBUG_VERBOSE >= 2)
+ INFTL_dumptables(s);
+ if (CONFIG_MTD_DEBUG_VERBOSE >= 2)
+ INFTL_dumpVUchains(s);
+#endif
+
+ /*
+ * Third pass, format unreferenced blocks and init free block count.
+ */
+ s->numfreeEUNs = 0;
+ s->LastFreeEUN = BLOCK_NIL;
+
+ DEBUG(MTD_DEBUG_LEVEL3, "INFTL: pass 3, format unused blocks\n");
+ for (block = s->firstEUN; block <= s->lastEUN; block++) {
+ if (s->PUtable[block] == BLOCK_NOTEXPLORED) {
+ printk("INFTL: unreferenced block %d, formatting it\n",
+ block);
+ if (INFTL_formatblock(s, block) < 0)
+ s->PUtable[block] = BLOCK_RESERVED;
+ else
+ s->PUtable[block] = BLOCK_FREE;
+ }
+ if (s->PUtable[block] == BLOCK_FREE) {
+ s->numfreeEUNs++;
+ if (s->LastFreeEUN == BLOCK_NIL)
+ s->LastFreeEUN = block;
+ }
+ }
+
+ kfree(ANACtable);
+ return 0;
+}
diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig
new file mode 100644
index 000000000000..8480057eadb4
--- /dev/null
+++ b/drivers/mtd/maps/Kconfig
@@ -0,0 +1,663 @@
+# drivers/mtd/maps/Kconfig
+# $Id: Kconfig,v 1.42 2005/01/05 16:59:50 dwmw2 Exp $
+
+menu "Mapping drivers for chip access"
+ depends on MTD!=n
+
+config MTD_COMPLEX_MAPPINGS
+ bool "Support non-linear mappings of flash chips"
+ depends on MTD
+ help
+ This causes the chip drivers to allow for complicated
+ paged mappings of flash chips.
+
+config MTD_PHYSMAP
+ tristate "CFI Flash device in physical memory map"
+ depends on MTD_CFI
+ help
+ This provides a 'mapping' driver which allows the CFI probe and
+ command set driver code to communicate with flash chips which
+ are mapped physically into the CPU's memory. You will need to
+ configure the physical address and size of the flash chips on
+ your particular board as well as the bus width, either statically
+ with config options or at run-time.
+
+config MTD_PHYSMAP_START
+ hex "Physical start address of flash mapping"
+ depends on MTD_PHYSMAP
+ default "0x8000000"
+ help
+ This is the physical memory location at which the flash chips
+ are mapped on your particular target board. Refer to the
+ memory map which should hopefully be in the documentation for
+ your board.
+ Ignore this option if you use run-time physmap configuration
+ (i.e., run-time calling physmap_configure()).
+
+config MTD_PHYSMAP_LEN
+ hex "Physical length of flash mapping"
+ depends on MTD_PHYSMAP
+ default "0x4000000"
+ help
+ This is the total length of the mapping of the flash chips on
+ your particular board. If there is space, or aliases, in the
+ physical memory map between the chips, this could be larger
+ than the total amount of flash present. Refer to the memory
+ map which should hopefully be in the documentation for your
+ board.
+ Ignore this option if you use run-time physmap configuration
+ (i.e., run-time calling physmap_configure()).
+
+config MTD_PHYSMAP_BANKWIDTH
+ int "Bank width in octets"
+ depends on MTD_PHYSMAP
+ default "2"
+ help
+ This is the total width of the data bus of the flash devices
+ in octets. For example, if you have a data bus width of 32
+ bits, you would set the bus width octect value to 4. This is
+ used internally by the CFI drivers.
+ Ignore this option if you use run-time physmap configuration
+ (i.e., run-time calling physmap_configure()).
+
+config MTD_SUN_UFLASH
+ tristate "Sun Microsystems userflash support"
+ depends on (SPARC32 || SPARC64) && MTD_CFI
+ help
+ This provides a 'mapping' driver which supports the way in
+ which user-programmable flash chips are connected on various
+ Sun Microsystems boardsets. This driver will require CFI support
+ in the kernel, so if you did not enable CFI previously, do that now.
+
+config MTD_PNC2000
+ tristate "CFI Flash device mapped on Photron PNC-2000"
+ depends on X86 && MTD_CFI && MTD_PARTITIONS
+ help
+ PNC-2000 is the name of Network Camera product from PHOTRON
+ Ltd. in Japan. It uses CFI-compliant flash.
+
+config MTD_SC520CDP
+ tristate "CFI Flash device mapped on AMD SC520 CDP"
+ depends on X86 && MTD_CFI
+ help
+ The SC520 CDP board has two banks of CFI-compliant chips and one
+ Dual-in-line JEDEC chip. This 'mapping' driver supports that
+ arrangement, implementing three MTD devices.
+
+config MTD_NETSC520
+ tristate "CFI Flash device mapped on AMD NetSc520"
+ depends on X86 && MTD_CFI && MTD_PARTITIONS
+ help
+ This enables access routines for the flash chips on the AMD NetSc520
+ demonstration board. If you have one of these boards and would like
+ to use the flash chips on it, say 'Y'.
+
+config MTD_TS5500
+ tristate "JEDEC Flash device mapped on Technologic Systems TS-5500"
+ depends on X86 && MTD_JEDECPROBE && MTD_PARTITIONS
+ help
+ This provides a driver for the on-board flash of the Technologic
+ System's TS-5500 board. The flash is split into 3 partitions
+ which are accessed as separate MTD devices.
+
+ mtd0 and mtd2 are the two BIOS drives. Unfortunately the BIOS
+ uses a proprietary flash translation layer from General Software,
+ which is not supported (the drives cannot be mounted). You can
+ create your own file system (jffs for example), but the BIOS
+ won't be able to boot from it.
+
+ mtd1 allows you to reprogram your BIOS. BE VERY CAREFUL.
+
+ Note that jumper 3 ("Write Enable Drive A") must be set
+ otherwise detection won't succeeed.
+
+config MTD_SBC_GXX
+ tristate "CFI Flash device mapped on Arcom SBC-GXx boards"
+ depends on X86 && MTD_CFI_INTELEXT && MTD_PARTITIONS && MTD_COMPLEX_MAPPINGS
+ help
+ This provides a driver for the on-board flash of Arcom Control
+ Systems' SBC-GXn family of boards, formerly known as SBC-MediaGX.
+ By default the flash is split into 3 partitions which are accessed
+ as separate MTD devices. This board utilizes Intel StrataFlash.
+ More info at
+ <http://www.arcomcontrols.com/products/icp/pc104/processors/SBC_GX1.htm>.
+
+config MTD_ELAN_104NC
+ tristate "CFI Flash device mapped on Arcom ELAN-104NC"
+ depends on X86 && MTD_CFI_INTELEXT && MTD_PARTITIONS && MTD_COMPLEX_MAPPINGS
+ help
+ This provides a driver for the on-board flash of the Arcom Control
+ System's ELAN-104NC development board. By default the flash
+ is split into 3 partitions which are accessed as separate MTD
+ devices. This board utilizes Intel StrataFlash. More info at
+ <http://www.arcomcontrols.com/products/icp/pc104/processors/ELAN104NC.htm>.
+
+config MTD_LUBBOCK
+ tristate "CFI Flash device mapped on Intel Lubbock XScale eval board"
+ depends on ARCH_LUBBOCK && MTD_CFI_INTELEXT && MTD_PARTITIONS
+ help
+ This provides a driver for the on-board flash of the Intel
+ 'Lubbock' XScale evaluation board.
+
+config MTD_OCTAGON
+ tristate "JEDEC Flash device mapped on Octagon 5066 SBC"
+ depends on X86 && MTD_JEDEC && MTD_COMPLEX_MAPPINGS
+ help
+ This provides a 'mapping' driver which supports the way in which
+ the flash chips are connected in the Octagon-5066 Single Board
+ Computer. More information on the board is available at
+ <http://www.octagonsystems.com/CPUpages/5066.html>.
+
+config MTD_VMAX
+ tristate "JEDEC Flash device mapped on Tempustech VMAX SBC301"
+ depends on X86 && MTD_JEDEC && MTD_COMPLEX_MAPPINGS
+ help
+ This provides a 'mapping' driver which supports the way in which
+ the flash chips are connected in the Tempustech VMAX SBC301 Single
+ Board Computer. More information on the board is available at
+ <http://www.tempustech.com/>.
+
+config MTD_SCx200_DOCFLASH
+ tristate "Flash device mapped with DOCCS on NatSemi SCx200"
+ depends on SCx200 && MTD_CFI && MTD_PARTITIONS
+ help
+ Enable support for a flash chip mapped using the DOCCS signal on a
+ National Semiconductor SCx200 processor.
+
+ If you don't know what to do here, say N.
+
+ If compiled as a module, it will be called scx200_docflash.
+
+config MTD_AMD76XROM
+ tristate "BIOS flash chip on AMD76x southbridge"
+ depends on X86 && MTD_JEDECPROBE
+ help
+ Support for treating the BIOS flash chip on AMD76x motherboards
+ as an MTD device - with this you can reprogram your BIOS.
+
+ BE VERY CAREFUL.
+
+config MTD_ICHXROM
+ tristate "BIOS flash chip on Intel Controller Hub 2/3/4/5"
+ depends on X86 && MTD_JEDECPROBE
+ help
+ Support for treating the BIOS flash chip on ICHX motherboards
+ as an MTD device - with this you can reprogram your BIOS.
+
+ BE VERY CAREFUL.
+
+config MTD_SCB2_FLASH
+ tristate "BIOS flash chip on Intel SCB2 boards"
+ depends on X86 && MTD_JEDECPROBE
+ help
+ Support for treating the BIOS flash chip on Intel SCB2 boards
+ as an MTD device - with this you can reprogram your BIOS.
+
+ BE VERY CAREFUL.
+
+config MTD_TSUNAMI
+ tristate "Flash chips on Tsunami TIG bus"
+ depends on ALPHA_TSUNAMI && MTD_COMPLEX_MAPPINGS
+ help
+ Support for the flash chip on Tsunami TIG bus.
+
+config MTD_LASAT
+ tristate "Flash chips on LASAT board"
+ depends on LASAT
+ help
+ Support for the flash chips on the Lasat 100 and 200 boards.
+
+config MTD_NETtel
+ tristate "CFI flash device on SnapGear/SecureEdge"
+ depends on X86 && MTD_PARTITIONS && MTD_JEDECPROBE
+ help
+ Support for flash chips on NETtel/SecureEdge/SnapGear boards.
+
+config MTD_PB1XXX
+ tristate "Flash devices on Alchemy PB1xxx boards"
+ depends on MIPS && ( MIPS_PB1000 || MIPS_PB1100 || MIPS_PB1500 )
+ help
+ Flash memory access on Alchemy Pb1000/Pb1100/Pb1500 boards
+
+config MTD_PB1XXX_BOOT
+ bool "PB1x00 boot flash device"
+ depends on MTD_PB1XXX && ( MIPS_PB1100 || MIPS_PB1500 )
+ help
+ Use the first of the two 32MiB flash banks on Pb1100/Pb1500 board.
+ You can say 'Y' to both this and 'MTD_PB1XXX_USER' below, to use
+ both banks.
+
+config MTD_PB1XXX_USER
+ bool "PB1x00 user flash device"
+ depends on MTD_PB1XXX && ( MIPS_PB1100 || MIPS_PB1500 )
+ default y if MTD_PB1XX_BOOT = n
+ help
+ Use the second of the two 32MiB flash banks on Pb1100/Pb1500 board.
+ You can say 'Y' to both this and 'MTD_PB1XXX_BOOT' above, to use
+ both banks.
+
+config MTD_PB1550
+ tristate "Flash devices on Alchemy PB1550 board"
+ depends on MIPS && MIPS_PB1550
+ help
+ Flash memory access on Alchemy Pb1550 board
+
+config MTD_PB1550_BOOT
+ bool "PB1550 boot flash device"
+ depends on MTD_PB1550
+ help
+ Use the first of the two 64MiB flash banks on Pb1550 board.
+ You can say 'Y' to both this and 'MTD_PB1550_USER' below, to use
+ both banks.
+
+config MTD_PB1550_USER
+ bool "PB1550 user flash device"
+ depends on MTD_PB1550
+ default y if MTD_PB1550_BOOT = n
+ help
+ Use the second of the two 64MiB flash banks on Pb1550 board.
+ You can say 'Y' to both this and 'MTD_PB1550_BOOT' above, to use
+ both banks.
+
+config MTD_DB1550
+ tristate "Flash devices on Alchemy DB1550 board"
+ depends on MIPS && MIPS_DB1550
+ help
+ Flash memory access on Alchemy Db1550 board
+
+config MTD_DB1550_BOOT
+ bool "DB1550 boot flash device"
+ depends on MTD_DB1550
+ help
+ Use the first of the two 64MiB flash banks on Db1550 board.
+ You can say 'Y' to both this and 'MTD_DB1550_USER' below, to use
+ both banks.
+
+config MTD_DB1550_USER
+ bool "DB1550 user flash device"
+ depends on MTD_DB1550
+ default y if MTD_DB1550_BOOT = n
+ help
+ Use the second of the two 64MiB flash banks on Db1550 board.
+ You can say 'Y' to both this and 'MTD_DB1550_BOOT' above, to use
+ both banks.
+
+config MTD_DILNETPC
+ tristate "CFI Flash device mapped on DIL/Net PC"
+ depends on X86 && MTD_CONCAT && MTD_PARTITIONS && MTD_CFI_INTELEXT
+ help
+ MTD map driver for SSV DIL/Net PC Boards "DNP" and "ADNP".
+ For details, see <http://www.ssv-embedded.de/ssv/pc104/p169.htm>
+ and <http://www.ssv-embedded.de/ssv/pc104/p170.htm>
+
+config MTD_DILNETPC_BOOTSIZE
+ hex "Size of DIL/Net PC flash boot partition"
+ depends on MTD_DILNETPC
+ default "0x80000"
+ help
+ The amount of space taken up by the kernel or Etherboot
+ on the DIL/Net PC flash chips.
+
+config MTD_L440GX
+ tristate "BIOS flash chip on Intel L440GX boards"
+ depends on X86 && MTD_JEDECPROBE
+ help
+ Support for treating the BIOS flash chip on Intel L440GX motherboards
+ as an MTD device - with this you can reprogram your BIOS.
+
+ BE VERY CAREFUL.
+
+config MTD_SBC8240
+ tristate "Flash device on SBC8240"
+ depends on PPC32 && MTD_JEDECPROBE && 6xx && 8260
+ help
+ Flash access on the SBC8240 board from Wind River. See
+ <http://www.windriver.com/products/sbc8240/>
+
+config MTD_TQM8XXL
+ tristate "CFI Flash device mapped on TQM8XXL"
+ depends on MTD_CFI && PPC32 && 8xx && TQM8xxL
+ help
+ The TQM8xxL PowerPC board has up to two banks of CFI-compliant
+ chips, currently uses AMD one. This 'mapping' driver supports
+ that arrangement, allowing the CFI probe and command set driver
+ code to communicate with the chips on the TQM8xxL board. More at
+ <http://www.denx.de/embedded-ppc-en.html>.
+
+config MTD_RPXLITE
+ tristate "CFI Flash device mapped on RPX Lite or CLLF"
+ depends on MTD_CFI && PPC32 && 8xx && (RPXCLASSIC || RPXLITE)
+ help
+ The RPXLite PowerPC board has CFI-compliant chips mapped in
+ a strange sparse mapping. This 'mapping' driver supports that
+ arrangement, allowing the CFI probe and command set driver code
+ to communicate with the chips on the RPXLite board. More at
+ <http://www.embeddedplanet.com/>.
+
+config MTD_MBX860
+ tristate "System flash on MBX860 board"
+ depends on MTD_CFI && PPC32 && 8xx && MBX
+ help
+ This enables access routines for the flash chips on the Motorola
+ MBX860 board. If you have one of these boards and would like
+ to use the flash chips on it, say 'Y'.
+
+config MTD_DBOX2
+ tristate "CFI Flash device mapped on D-Box2"
+ depends on PPC32 && 8xx && DBOX2 && MTD_CFI_INTELSTD && MTD_CFI_INTELEXT && MTD_CFI_AMDSTD
+ help
+ This enables access routines for the flash chips on the Nokia/Sagem
+ D-Box 2 board. If you have one of these boards and would like to use
+ the flash chips on it, say 'Y'.
+
+config MTD_CFI_FLAGADM
+ tristate "CFI Flash device mapping on FlagaDM"
+ depends on PPC32 && 8xx && MTD_CFI
+ help
+ Mapping for the Flaga digital module. If you don't have one, ignore
+ this setting.
+
+config MTD_BEECH
+ tristate "CFI Flash device mapped on IBM 405LP Beech"
+ depends on MTD_CFI && PPC32 && 40x && BEECH
+ help
+ This enables access routines for the flash chips on the IBM
+ 405LP Beech board. If you have one of these boards and would like
+ to use the flash chips on it, say 'Y'.
+
+config MTD_ARCTIC
+ tristate "CFI Flash device mapped on IBM 405LP Arctic"
+ depends on MTD_CFI && PPC32 && 40x && ARCTIC2
+ help
+ This enables access routines for the flash chips on the IBM 405LP
+ Arctic board. If you have one of these boards and would like to
+ use the flash chips on it, say 'Y'.
+
+config MTD_WALNUT
+ tristate "Flash device mapped on IBM 405GP Walnut"
+ depends on MTD_JEDECPROBE && PPC32 && 40x && WALNUT
+ help
+ This enables access routines for the flash chips on the IBM 405GP
+ Walnut board. If you have one of these boards and would like to
+ use the flash chips on it, say 'Y'.
+
+config MTD_EBONY
+ tristate "Flash devices mapped on IBM 440GP Ebony"
+ depends on MTD_JEDECPROBE && PPC32 && 44x && EBONY
+ help
+ This enables access routines for the flash chips on the IBM 440GP
+ Ebony board. If you have one of these boards and would like to
+ use the flash chips on it, say 'Y'.
+
+config MTD_OCOTEA
+ tristate "Flash devices mapped on IBM 440GX Ocotea"
+ depends on MTD_CFI && PPC32 && 44x && OCOTEA
+ help
+ This enables access routines for the flash chips on the IBM 440GX
+ Ocotea board. If you have one of these boards and would like to
+ use the flash chips on it, say 'Y'.
+
+config MTD_REDWOOD
+ tristate "CFI Flash devices mapped on IBM Redwood"
+ depends on MTD_CFI && PPC32 && 4xx && 40x && ( REDWOOD_4 || REDWOOD_5 || REDWOOD_6 )
+ help
+ This enables access routines for the flash chips on the IBM
+ Redwood board. If you have one of these boards and would like to
+ use the flash chips on it, say 'Y'.
+
+config MTD_CSTM_MIPS_IXX
+ tristate "Flash chip mapping on ITE QED-4N-S01B, Globespan IVR or custom board"
+ depends on MIPS && MTD_CFI && MTD_JEDECPROBE && MTD_PARTITIONS
+ help
+ This provides a mapping driver for the Integrated Technology
+ Express, Inc (ITE) QED-4N-S01B eval board and the Globespan IVR
+ Reference Board. It provides the necessary addressing, length,
+ buswidth, vpp code and addition setup of the flash device for
+ these boards. In addition, this mapping driver can be used for
+ other boards via setting of the CONFIG_MTD_CSTM_MIPS_IXX_START/
+ LEN/BUSWIDTH parameters. This mapping will provide one mtd device
+ using one partition. The start address can be offset from the
+ beginning of flash and the len can be less than the total flash
+ device size to allow a window into the flash. Both CFI and JEDEC
+ probes are called.
+
+config MTD_CSTM_MIPS_IXX_START
+ hex "Physical start address of flash mapping"
+ depends on MTD_CSTM_MIPS_IXX
+ default "0x8000000"
+ help
+ This is the physical memory location that the MTD driver will
+ use for the flash chips on your particular target board.
+ Refer to the memory map which should hopefully be in the
+ documentation for your board.
+
+config MTD_CSTM_MIPS_IXX_LEN
+ hex "Physical length of flash mapping"
+ depends on MTD_CSTM_MIPS_IXX
+ default "0x4000000"
+ help
+ This is the total length that the MTD driver will use for the
+ flash chips on your particular board. Refer to the memory
+ map which should hopefully be in the documentation for your
+ board.
+
+config MTD_CSTM_MIPS_IXX_BUSWIDTH
+ int "Bus width in octets"
+ depends on MTD_CSTM_MIPS_IXX
+ default "2"
+ help
+ This is the total bus width of the mapping of the flash chips
+ on your particular board.
+
+config MTD_OCELOT
+ tristate "Momenco Ocelot boot flash device"
+ depends on MIPS && MOMENCO_OCELOT
+ help
+ This enables access routines for the boot flash device and for the
+ NVRAM on the Momenco Ocelot board. If you have one of these boards
+ and would like access to either of these, say 'Y'.
+
+config MTD_SOLUTIONENGINE
+ tristate "CFI Flash device mapped on Hitachi SolutionEngine"
+ depends on SUPERH && MTD_CFI && MTD_REDBOOT_PARTS
+ help
+ This enables access to the flash chips on the Hitachi SolutionEngine and
+ similar boards. Say 'Y' if you are building a kernel for such a board.
+
+config MTD_ARM_INTEGRATOR
+ tristate "CFI Flash device mapped on ARM Integrator/P720T"
+ depends on ARM && MTD_CFI
+
+config MTD_CDB89712
+ tristate "Cirrus CDB89712 evaluation board mappings"
+ depends on ARM && MTD_CFI && ARCH_CDB89712
+ help
+ This enables access to the flash or ROM chips on the CDB89712 board.
+ If you have such a board, say 'Y'.
+
+config MTD_SA1100
+ tristate "CFI Flash device mapped on StrongARM SA11x0"
+ depends on ARM && MTD_CFI && ARCH_SA1100 && MTD_PARTITIONS
+ help
+ This enables access to the flash chips on most platforms based on
+ the SA1100 and SA1110, including the Assabet and the Compaq iPAQ.
+ If you have such a board, say 'Y'.
+
+config MTD_IPAQ
+ tristate "CFI Flash device mapped on Compaq/HP iPAQ"
+ depends on ARM && IPAQ_HANDHELD && MTD_CFI
+ help
+ This provides a driver for the on-board flash of the iPAQ.
+
+config MTD_DC21285
+ tristate "CFI Flash device mapped on DC21285 Footbridge"
+ depends on ARM && MTD_CFI && ARCH_FOOTBRIDGE && MTD_COMPLEX_MAPPINGS
+ help
+ This provides a driver for the flash accessed using Intel's
+ 21285 bridge used with Intel's StrongARM processors. More info at
+ <http://www.intel.com/design/bridge/docs/21285_documentation.htm>.
+
+config MTD_IQ80310
+ tristate "CFI Flash device mapped on the XScale IQ80310 board"
+ depends on ARM && MTD_CFI && ARCH_IQ80310
+ help
+ This enables access routines for the flash chips on the Intel XScale
+ IQ80310 evaluation board. If you have one of these boards and would
+ like to use the flash chips on it, say 'Y'.
+
+config MTD_IXP4XX
+ tristate "CFI Flash device mapped on Intel IXP4xx based systems"
+ depends on ARM && MTD_CFI && MTD_COMPLEX_MAPPINGS && ARCH_IXP4XX
+ help
+ This enables MTD access to flash devices on platforms based
+ on Intel's IXP4xx family of network processors such as the
+ IXDP425 and Coyote. If you have an IXP4xx based board and
+ would like to use the flash chips on it, say 'Y'.
+
+config MTD_IXP2000
+ tristate "CFI Flash device mapped on Intel IXP2000 based systems"
+ depends on ARM && MTD_CFI && MTD_COMPLEX_MAPPINGS && ARCH_IXP2000
+ help
+ This enables MTD access to flash devices on platforms based
+ on Intel's IXP2000 family of network processors such as the
+ IXDP425 and Coyote. If you have an IXP2000 based board and
+ would like to use the flash chips on it, say 'Y'.
+
+config MTD_EPXA10DB
+ tristate "CFI Flash device mapped on Epxa10db"
+ depends on ARM && MTD_CFI && MTD_PARTITIONS && ARCH_CAMELOT
+ help
+ This enables support for the flash devices on the Altera
+ Excalibur XA10 Development Board. If you are building a kernel
+ for on of these boards then you should say 'Y' otherwise say 'N'.
+
+config MTD_FORTUNET
+ tristate "CFI Flash device mapped on the FortuNet board"
+ depends on ARM && MTD_CFI && MTD_PARTITIONS && SA1100_FORTUNET
+ help
+ This enables access to the Flash on the FortuNet board. If you
+ have such a board, say 'Y'.
+
+config MTD_AUTCPU12
+ tristate "NV-RAM mapping AUTCPU12 board"
+ depends on ARM && ARCH_AUTCPU12
+ help
+ This enables access to the NV-RAM on autronix autcpu12 board.
+ If you have such a board, say 'Y'.
+
+config MTD_EDB7312
+ tristate "CFI Flash device mapped on EDB7312"
+ depends on ARM && MTD_CFI
+ help
+ This enables access to the CFI Flash on the Cogent EDB7312 board.
+ If you have such a board, say 'Y' here.
+
+config MTD_IMPA7
+ tristate "JEDEC Flash device mapped on impA7"
+ depends on ARM && MTD_JEDECPROBE
+ help
+ This enables access to the NOR Flash on the impA7 board of
+ implementa GmbH. If you have such a board, say 'Y' here.
+
+config MTD_CEIVA
+ tristate "JEDEC Flash device mapped on Ceiva/Polaroid PhotoMax Digital Picture Frame"
+ depends on ARM && MTD_JEDECPROBE && ARCH_CEIVA
+ help
+ This enables access to the flash chips on the Ceiva/Polaroid
+ PhotoMax Digital Picture Frame.
+ If you have such a device, say 'Y'.
+
+config MTD_NOR_TOTO
+ tristate "NOR Flash device on TOTO board"
+ depends on ARM && ARCH_OMAP && OMAP_TOTO
+ help
+ This enables access to the NOR flash on the Texas Instruments
+ TOTO board.
+
+config MTD_H720X
+ tristate "Hynix evaluation board mappings"
+ depends on ARM && MTD_CFI && ( ARCH_H7201 || ARCH_H7202 )
+ help
+ This enables access to the flash chips on the Hynix evaluation boards.
+ If you have such a board, say 'Y'.
+
+config MTD_MPC1211
+ tristate "CFI Flash device mapped on Interface MPC-1211"
+ depends on SUPERH && SH_MPC1211 && MTD_CFI
+ help
+ This enables access to the flash chips on the Interface MPC-1211(CTP/PCI/MPC-SH02).
+ If you have such a board, say 'Y'.
+
+# This needs CFI or JEDEC, depending on the cards found.
+config MTD_PCI
+ tristate "PCI MTD driver"
+ depends on MTD && PCI && MTD_COMPLEX_MAPPINGS
+ help
+ Mapping for accessing flash devices on add-in cards like the Intel XScale
+ IQ80310 card, and the Intel EBSA285 card in blank ROM programming mode
+ (please see the manual for the link settings).
+
+ If you are not sure, say N.
+
+config MTD_PCMCIA
+ tristate "PCMCIA MTD driver"
+ depends on MTD && PCMCIA && MTD_COMPLEX_MAPPINGS && BROKEN
+ help
+ Map driver for accessing PCMCIA linear flash memory cards. These
+ cards are usually around 4-16MiB in size. This does not include
+ Compact Flash cards which are treated as IDE devices.
+
+config MTD_UCLINUX
+ tristate "Generic uClinux RAM/ROM filesystem support"
+ depends on MTD_PARTITIONS && !MMU
+ help
+ Map driver to support image based filesystems for uClinux.
+
+config MTD_WRSBC8260
+ tristate "Map driver for WindRiver PowerQUICC II MPC82xx board"
+ depends on (SBC82xx || SBC8560)
+ select MTD_PARTITIONS
+ select MTD_MAP_BANK_WIDTH_4
+ select MTD_MAP_BANK_WIDTH_1
+ select MTD_CFI_I1
+ select MTD_CFI_I4
+ help
+ Map driver for WindRiver PowerQUICC II MPC82xx board. Drives
+ all three flash regions on CS0, CS1 and CS6 if they are configured
+ correctly by the boot loader.
+
+config MTD_DMV182
+ tristate "Map driver for Dy-4 SVME/DMV-182 board."
+ depends on DMV182
+ select MTD_PARTITIONS
+ select MTD_MAP_BANK_WIDTH_32
+ select MTD_CFI_I8
+ select MTD_CFI_AMDSTD
+ help
+ Map driver for Dy-4 SVME/DMV-182 board.
+
+config MTD_BAST
+ tristate "Map driver for Simtec BAST (EB2410ITX)"
+ depends on ARCH_BAST
+ select MTD_PARTITIONS
+ select MTD_MAP_BANK_WIDTH_16
+ select MTD_JEDECPROBE
+ help
+ Map driver for NOR flash on the Simtec BAST (EB2410ITX).
+
+ Note, this driver *cannot* over-ride the WP link on the
+ board, or currently detect the state of the link.
+
+config MTD_BAST_MAXSIZE
+ int "Maximum size for BAST flash area (MiB)"
+ depends on MTD_BAST
+ default "4"
+
+config MTD_SHARP_SL
+ bool "ROM maped on Sharp SL Series"
+ depends on MTD && ARCH_PXA
+ help
+ This enables access to the flash chip on the Sharp SL Series of PDAs.
+
+endmenu
+
diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile
new file mode 100644
index 000000000000..7ffe02b85301
--- /dev/null
+++ b/drivers/mtd/maps/Makefile
@@ -0,0 +1,73 @@
+#
+# linux/drivers/maps/Makefile
+#
+# $Id: Makefile.common,v 1.23 2005/01/05 17:06:36 dwmw2 Exp $
+
+ifeq ($(CONFIG_MTD_COMPLEX_MAPPINGS),y)
+obj-$(CONFIG_MTD) += map_funcs.o
+endif
+
+# Chip mappings
+obj-$(CONFIG_MTD_CDB89712) += cdb89712.o
+obj-$(CONFIG_MTD_ARM_INTEGRATOR)+= integrator-flash.o
+obj-$(CONFIG_MTD_BAST) += bast-flash.o
+obj-$(CONFIG_MTD_CFI_FLAGADM) += cfi_flagadm.o
+obj-$(CONFIG_MTD_CSTM_MIPS_IXX) += cstm_mips_ixx.o
+obj-$(CONFIG_MTD_DC21285) += dc21285.o
+obj-$(CONFIG_MTD_DILNETPC) += dilnetpc.o
+obj-$(CONFIG_MTD_ELAN_104NC) += elan-104nc.o
+obj-$(CONFIG_MTD_EPXA10DB) += epxa10db-flash.o
+obj-$(CONFIG_MTD_IQ80310) += iq80310.o
+obj-$(CONFIG_MTD_L440GX) += l440gx.o
+obj-$(CONFIG_MTD_AMD76XROM) += amd76xrom.o
+obj-$(CONFIG_MTD_ICHXROM) += ichxrom.o
+obj-$(CONFIG_MTD_TSUNAMI) += tsunami_flash.o
+obj-$(CONFIG_MTD_LUBBOCK) += lubbock-flash.o
+obj-$(CONFIG_MTD_MBX860) += mbx860.o
+obj-$(CONFIG_MTD_CEIVA) += ceiva.o
+obj-$(CONFIG_MTD_OCTAGON) += octagon-5066.o
+obj-$(CONFIG_MTD_PHYSMAP) += physmap.o
+obj-$(CONFIG_MTD_PNC2000) += pnc2000.o
+obj-$(CONFIG_MTD_PCMCIA) += pcmciamtd.o
+obj-$(CONFIG_MTD_RPXLITE) += rpxlite.o
+obj-$(CONFIG_MTD_TQM8XXL) += tqm8xxl.o
+obj-$(CONFIG_MTD_SA1100) += sa1100-flash.o
+obj-$(CONFIG_MTD_IPAQ) += ipaq-flash.o
+obj-$(CONFIG_MTD_SBC_GXX) += sbc_gxx.o
+obj-$(CONFIG_MTD_SC520CDP) += sc520cdp.o
+obj-$(CONFIG_MTD_NETSC520) += netsc520.o
+obj-$(CONFIG_MTD_TS5500) += ts5500_flash.o
+obj-$(CONFIG_MTD_SUN_UFLASH) += sun_uflash.o
+obj-$(CONFIG_MTD_VMAX) += vmax301.o
+obj-$(CONFIG_MTD_SCx200_DOCFLASH)+= scx200_docflash.o
+obj-$(CONFIG_MTD_DBOX2) += dbox2-flash.o
+obj-$(CONFIG_MTD_OCELOT) += ocelot.o
+obj-$(CONFIG_MTD_SOLUTIONENGINE)+= solutionengine.o
+obj-$(CONFIG_MTD_PCI) += pci.o
+obj-$(CONFIG_MTD_PB1XXX) += pb1xxx-flash.o
+obj-$(CONFIG_MTD_DB1X00) += db1x00-flash.o
+obj-$(CONFIG_MTD_PB1550) += pb1550-flash.o
+obj-$(CONFIG_MTD_DB1550) += db1550-flash.o
+obj-$(CONFIG_MTD_LASAT) += lasat.o
+obj-$(CONFIG_MTD_AUTCPU12) += autcpu12-nvram.o
+obj-$(CONFIG_MTD_EDB7312) += edb7312.o
+obj-$(CONFIG_MTD_IMPA7) += impa7.o
+obj-$(CONFIG_MTD_FORTUNET) += fortunet.o
+obj-$(CONFIG_MTD_REDWOOD) += redwood.o
+obj-$(CONFIG_MTD_UCLINUX) += uclinux.o
+obj-$(CONFIG_MTD_NETtel) += nettel.o
+obj-$(CONFIG_MTD_SCB2_FLASH) += scb2_flash.o
+obj-$(CONFIG_MTD_EBONY) += ebony.o
+obj-$(CONFIG_MTD_OCOTEA) += ocotea.o
+obj-$(CONFIG_MTD_BEECH) += beech-mtd.o
+obj-$(CONFIG_MTD_ARCTIC) += arctic-mtd.o
+obj-$(CONFIG_MTD_WALNUT) += walnut.o
+obj-$(CONFIG_MTD_H720X) += h720x-flash.o
+obj-$(CONFIG_MTD_SBC8240) += sbc8240.o
+obj-$(CONFIG_MTD_NOR_TOTO) += omap-toto-flash.o
+obj-$(CONFIG_MTD_MPC1211) += mpc1211.o
+obj-$(CONFIG_MTD_IXP4XX) += ixp4xx.o
+obj-$(CONFIG_MTD_IXP2000) += ixp2000.o
+obj-$(CONFIG_MTD_WRSBC8260) += wr_sbc82xx_flash.o
+obj-$(CONFIG_MTD_DMV182) += dmv182.o
+obj-$(CONFIG_MTD_SHARP_SL) += sharpsl-flash.o
diff --git a/drivers/mtd/maps/amd76xrom.c b/drivers/mtd/maps/amd76xrom.c
new file mode 100644
index 000000000000..51e97b05304e
--- /dev/null
+++ b/drivers/mtd/maps/amd76xrom.c
@@ -0,0 +1,332 @@
+/*
+ * amd76xrom.c
+ *
+ * Normal mappings of chips in physical memory
+ * $Id: amd76xrom.c,v 1.19 2004/11/28 09:40:39 dwmw2 Exp $
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/cfi.h>
+#include <linux/mtd/flashchip.h>
+#include <linux/config.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/list.h>
+
+
+#define xstr(s) str(s)
+#define str(s) #s
+#define MOD_NAME xstr(KBUILD_BASENAME)
+
+#define ADDRESS_NAME_LEN 18
+
+#define ROM_PROBE_STEP_SIZE (64*1024) /* 64KiB */
+
+struct amd76xrom_window {
+ void __iomem *virt;
+ unsigned long phys;
+ unsigned long size;
+ struct list_head maps;
+ struct resource rsrc;
+ struct pci_dev *pdev;
+};
+
+struct amd76xrom_map_info {
+ struct list_head list;
+ struct map_info map;
+ struct mtd_info *mtd;
+ struct resource rsrc;
+ char map_name[sizeof(MOD_NAME) + 2 + ADDRESS_NAME_LEN];
+};
+
+static struct amd76xrom_window amd76xrom_window = {
+ .maps = LIST_HEAD_INIT(amd76xrom_window.maps),
+};
+
+static void amd76xrom_cleanup(struct amd76xrom_window *window)
+{
+ struct amd76xrom_map_info *map, *scratch;
+ u8 byte;
+
+ if (window->pdev) {
+ /* Disable writes through the rom window */
+ pci_read_config_byte(window->pdev, 0x40, &byte);
+ pci_write_config_byte(window->pdev, 0x40, byte & ~1);
+ }
+
+ /* Free all of the mtd devices */
+ list_for_each_entry_safe(map, scratch, &window->maps, list) {
+ if (map->rsrc.parent) {
+ release_resource(&map->rsrc);
+ }
+ del_mtd_device(map->mtd);
+ map_destroy(map->mtd);
+ list_del(&map->list);
+ kfree(map);
+ }
+ if (window->rsrc.parent)
+ release_resource(&window->rsrc);
+
+ if (window->virt) {
+ iounmap(window->virt);
+ window->virt = NULL;
+ window->phys = 0;
+ window->size = 0;
+ window->pdev = NULL;
+ }
+}
+
+
+static int __devinit amd76xrom_init_one (struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ static char *rom_probe_types[] = { "cfi_probe", "jedec_probe", NULL };
+ u8 byte;
+ struct amd76xrom_window *window = &amd76xrom_window;
+ struct amd76xrom_map_info *map = NULL;
+ unsigned long map_top;
+
+ /* Remember the pci dev I find the window in */
+ window->pdev = pdev;
+
+ /* Assume the rom window is properly setup, and find it's size */
+ pci_read_config_byte(pdev, 0x43, &byte);
+ if ((byte & ((1<<7)|(1<<6))) == ((1<<7)|(1<<6))) {
+ window->phys = 0xffb00000; /* 5MiB */
+ }
+ else if ((byte & (1<<7)) == (1<<7)) {
+ window->phys = 0xffc00000; /* 4MiB */
+ }
+ else {
+ window->phys = 0xffff0000; /* 64KiB */
+ }
+ window->size = 0xffffffffUL - window->phys + 1UL;
+
+ /*
+ * Try to reserve the window mem region. If this fails then
+ * it is likely due to a fragment of the window being
+ * "reseved" by the BIOS. In the case that the
+ * request_mem_region() fails then once the rom size is
+ * discovered we will try to reserve the unreserved fragment.
+ */
+ window->rsrc.name = MOD_NAME;
+ window->rsrc.start = window->phys;
+ window->rsrc.end = window->phys + window->size - 1;
+ window->rsrc.flags = IORESOURCE_MEM | IORESOURCE_BUSY;
+ if (request_resource(&iomem_resource, &window->rsrc)) {
+ window->rsrc.parent = NULL;
+ printk(KERN_ERR MOD_NAME
+ " %s(): Unable to register resource"
+ " 0x%.08lx-0x%.08lx - kernel bug?\n",
+ __func__,
+ window->rsrc.start, window->rsrc.end);
+ }
+
+#if 0
+
+ /* Enable the selected rom window */
+ pci_read_config_byte(pdev, 0x43, &byte);
+ pci_write_config_byte(pdev, 0x43, byte | rwindow->segen_bits);
+#endif
+
+ /* Enable writes through the rom window */
+ pci_read_config_byte(pdev, 0x40, &byte);
+ pci_write_config_byte(pdev, 0x40, byte | 1);
+
+ /* FIXME handle registers 0x80 - 0x8C the bios region locks */
+
+ /* For write accesses caches are useless */
+ window->virt = ioremap_nocache(window->phys, window->size);
+ if (!window->virt) {
+ printk(KERN_ERR MOD_NAME ": ioremap(%08lx, %08lx) failed\n",
+ window->phys, window->size);
+ goto out;
+ }
+
+ /* Get the first address to look for an rom chip at */
+ map_top = window->phys;
+#if 1
+ /* The probe sequence run over the firmware hub lock
+ * registers sets them to 0x7 (no access).
+ * Probe at most the last 4M of the address space.
+ */
+ if (map_top < 0xffc00000) {
+ map_top = 0xffc00000;
+ }
+#endif
+ /* Loop through and look for rom chips */
+ while((map_top - 1) < 0xffffffffUL) {
+ struct cfi_private *cfi;
+ unsigned long offset;
+ int i;
+
+ if (!map) {
+ map = kmalloc(sizeof(*map), GFP_KERNEL);
+ }
+ if (!map) {
+ printk(KERN_ERR MOD_NAME ": kmalloc failed");
+ goto out;
+ }
+ memset(map, 0, sizeof(*map));
+ INIT_LIST_HEAD(&map->list);
+ map->map.name = map->map_name;
+ map->map.phys = map_top;
+ offset = map_top - window->phys;
+ map->map.virt = (void __iomem *)
+ (((unsigned long)(window->virt)) + offset);
+ map->map.size = 0xffffffffUL - map_top + 1UL;
+ /* Set the name of the map to the address I am trying */
+ sprintf(map->map_name, "%s @%08lx",
+ MOD_NAME, map->map.phys);
+
+ /* There is no generic VPP support */
+ for(map->map.bankwidth = 32; map->map.bankwidth;
+ map->map.bankwidth >>= 1)
+ {
+ char **probe_type;
+ /* Skip bankwidths that are not supported */
+ if (!map_bankwidth_supported(map->map.bankwidth))
+ continue;
+
+ /* Setup the map methods */
+ simple_map_init(&map->map);
+
+ /* Try all of the probe methods */
+ probe_type = rom_probe_types;
+ for(; *probe_type; probe_type++) {
+ map->mtd = do_map_probe(*probe_type, &map->map);
+ if (map->mtd)
+ goto found;
+ }
+ }
+ map_top += ROM_PROBE_STEP_SIZE;
+ continue;
+ found:
+ /* Trim the size if we are larger than the map */
+ if (map->mtd->size > map->map.size) {
+ printk(KERN_WARNING MOD_NAME
+ " rom(%u) larger than window(%lu). fixing...\n",
+ map->mtd->size, map->map.size);
+ map->mtd->size = map->map.size;
+ }
+ if (window->rsrc.parent) {
+ /*
+ * Registering the MTD device in iomem may not be possible
+ * if there is a BIOS "reserved" and BUSY range. If this
+ * fails then continue anyway.
+ */
+ map->rsrc.name = map->map_name;
+ map->rsrc.start = map->map.phys;
+ map->rsrc.end = map->map.phys + map->mtd->size - 1;
+ map->rsrc.flags = IORESOURCE_MEM | IORESOURCE_BUSY;
+ if (request_resource(&window->rsrc, &map->rsrc)) {
+ printk(KERN_ERR MOD_NAME
+ ": cannot reserve MTD resource\n");
+ map->rsrc.parent = NULL;
+ }
+ }
+
+ /* Make the whole region visible in the map */
+ map->map.virt = window->virt;
+ map->map.phys = window->phys;
+ cfi = map->map.fldrv_priv;
+ for(i = 0; i < cfi->numchips; i++) {
+ cfi->chips[i].start += offset;
+ }
+
+ /* Now that the mtd devices is complete claim and export it */
+ map->mtd->owner = THIS_MODULE;
+ if (add_mtd_device(map->mtd)) {
+ map_destroy(map->mtd);
+ map->mtd = NULL;
+ goto out;
+ }
+
+
+ /* Calculate the new value of map_top */
+ map_top += map->mtd->size;
+
+ /* File away the map structure */
+ list_add(&map->list, &window->maps);
+ map = NULL;
+ }
+
+ out:
+ /* Free any left over map structures */
+ if (map) {
+ kfree(map);
+ }
+ /* See if I have any map structures */
+ if (list_empty(&window->maps)) {
+ amd76xrom_cleanup(window);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+
+static void __devexit amd76xrom_remove_one (struct pci_dev *pdev)
+{
+ struct amd76xrom_window *window = &amd76xrom_window;
+
+ amd76xrom_cleanup(window);
+}
+
+static struct pci_device_id amd76xrom_pci_tbl[] = {
+ { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7410,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7440,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_AMD, 0x7468 }, /* amd8111 support */
+ { 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, amd76xrom_pci_tbl);
+
+#if 0
+static struct pci_driver amd76xrom_driver = {
+ .name = MOD_NAME,
+ .id_table = amd76xrom_pci_tbl,
+ .probe = amd76xrom_init_one,
+ .remove = amd76xrom_remove_one,
+};
+#endif
+
+static int __init init_amd76xrom(void)
+{
+ struct pci_dev *pdev;
+ struct pci_device_id *id;
+ pdev = NULL;
+ for(id = amd76xrom_pci_tbl; id->vendor; id++) {
+ pdev = pci_find_device(id->vendor, id->device, NULL);
+ if (pdev) {
+ break;
+ }
+ }
+ if (pdev) {
+ return amd76xrom_init_one(pdev, &amd76xrom_pci_tbl[0]);
+ }
+ return -ENXIO;
+#if 0
+ return pci_module_init(&amd76xrom_driver);
+#endif
+}
+
+static void __exit cleanup_amd76xrom(void)
+{
+ amd76xrom_remove_one(amd76xrom_window.pdev);
+}
+
+module_init(init_amd76xrom);
+module_exit(cleanup_amd76xrom);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Eric Biederman <ebiederman@lnxi.com>");
+MODULE_DESCRIPTION("MTD map driver for BIOS chips on the AMD76X southbridge");
+
diff --git a/drivers/mtd/maps/arctic-mtd.c b/drivers/mtd/maps/arctic-mtd.c
new file mode 100644
index 000000000000..777276fd0e15
--- /dev/null
+++ b/drivers/mtd/maps/arctic-mtd.c
@@ -0,0 +1,135 @@
+/*
+ * $Id: arctic-mtd.c,v 1.13 2004/11/04 13:24:14 gleixner Exp $
+ *
+ * drivers/mtd/maps/arctic-mtd.c MTD mappings and partition tables for
+ * IBM 405LP Arctic boards.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Copyright (C) 2002, International Business Machines Corporation
+ * All Rights Reserved.
+ *
+ * Bishop Brock
+ * IBM Research, Austin Center for Low-Power Computing
+ * bcbrock@us.ibm.com
+ * March 2002
+ *
+ * modified for Arctic by,
+ * David Gibson
+ * IBM OzLabs, Canberra, Australia
+ * <arctic@gibson.dropbear.id.au>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/io.h>
+#include <asm/ibm4xx.h>
+
+/*
+ * 0 : 0xFE00 0000 - 0xFEFF FFFF : Filesystem 1 (16MiB)
+ * 1 : 0xFF00 0000 - 0xFF4F FFFF : kernel (5.12MiB)
+ * 2 : 0xFF50 0000 - 0xFFF5 FFFF : Filesystem 2 (10.624MiB) (if non-XIP)
+ * 3 : 0xFFF6 0000 - 0xFFFF FFFF : PIBS Firmware (640KiB)
+ */
+
+#define FFS1_SIZE 0x01000000 /* 16MiB */
+#define KERNEL_SIZE 0x00500000 /* 5.12MiB */
+#define FFS2_SIZE 0x00a60000 /* 10.624MiB */
+#define FIRMWARE_SIZE 0x000a0000 /* 640KiB */
+
+
+#define NAME "Arctic Linux Flash"
+#define PADDR SUBZERO_BOOTFLASH_PADDR
+#define BUSWIDTH 2
+#define SIZE SUBZERO_BOOTFLASH_SIZE
+#define PARTITIONS 4
+
+/* Flash memories on these boards are memory resources, accessed big-endian. */
+
+{
+ /* do nothing for now */
+}
+
+static struct map_info arctic_mtd_map = {
+ .name = NAME,
+ .size = SIZE,
+ .bankwidth = BUSWIDTH,
+ .phys = PADDR,
+};
+
+static struct mtd_info *arctic_mtd;
+
+static struct mtd_partition arctic_partitions[PARTITIONS] = {
+ { .name = "Filesystem",
+ .size = FFS1_SIZE,
+ .offset = 0,},
+ { .name = "Kernel",
+ .size = KERNEL_SIZE,
+ .offset = FFS1_SIZE,},
+ { .name = "Filesystem",
+ .size = FFS2_SIZE,
+ .offset = FFS1_SIZE + KERNEL_SIZE,},
+ { .name = "Firmware",
+ .size = FIRMWARE_SIZE,
+ .offset = SUBZERO_BOOTFLASH_SIZE - FIRMWARE_SIZE,},
+};
+
+static int __init
+init_arctic_mtd(void)
+{
+ printk("%s: 0x%08x at 0x%08x\n", NAME, SIZE, PADDR);
+
+ arctic_mtd_map.virt = ioremap(PADDR, SIZE);
+
+ if (!arctic_mtd_map.virt) {
+ printk("%s: failed to ioremap 0x%x\n", NAME, PADDR);
+ return -EIO;
+ }
+ simple_map_init(&arctic_mtd_map);
+
+ printk("%s: probing %d-bit flash bus\n", NAME, BUSWIDTH * 8);
+ arctic_mtd = do_map_probe("cfi_probe", &arctic_mtd_map);
+
+ if (!arctic_mtd)
+ return -ENXIO;
+
+ arctic_mtd->owner = THIS_MODULE;
+
+ return add_mtd_partitions(arctic_mtd, arctic_partitions, PARTITIONS);
+}
+
+static void __exit
+cleanup_arctic_mtd(void)
+{
+ if (arctic_mtd) {
+ del_mtd_partitions(arctic_mtd);
+ map_destroy(arctic_mtd);
+ iounmap((void *) arctic_mtd_map.virt);
+ }
+}
+
+module_init(init_arctic_mtd);
+module_exit(cleanup_arctic_mtd);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Gibson <arctic@gibson.dropbear.id.au>");
+MODULE_DESCRIPTION("MTD map and partitions for IBM 405LP Arctic boards");
diff --git a/drivers/mtd/maps/autcpu12-nvram.c b/drivers/mtd/maps/autcpu12-nvram.c
new file mode 100644
index 000000000000..cf362ccc3c8e
--- /dev/null
+++ b/drivers/mtd/maps/autcpu12-nvram.c
@@ -0,0 +1,127 @@
+/*
+ * NV-RAM memory access on autcpu12
+ * (C) 2002 Thomas Gleixner (gleixner@autronix.de)
+ *
+ * $Id: autcpu12-nvram.c,v 1.8 2004/11/04 13:24:14 gleixner Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <asm/sizes.h>
+#include <asm/hardware.h>
+#include <asm/arch/autcpu12.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+
+static struct mtd_info *sram_mtd;
+
+struct map_info autcpu12_sram_map = {
+ .name = "SRAM",
+ .size = 32768,
+ .bankwidth = 4,
+ .phys = 0x12000000,
+};
+
+static int __init init_autcpu12_sram (void)
+{
+ int err, save0, save1;
+
+ autcpu12_sram_map.virt = ioremap(0x12000000, SZ_128K);
+ if (!autcpu12_sram_map.virt) {
+ printk("Failed to ioremap autcpu12 NV-RAM space\n");
+ err = -EIO;
+ goto out;
+ }
+ simple_map_init(&autcpu_sram_map);
+
+ /*
+ * Check for 32K/128K
+ * read ofs 0
+ * read ofs 0x10000
+ * Write complement to ofs 0x100000
+ * Read and check result on ofs 0x0
+ * Restore contents
+ */
+ save0 = map_read32(&autcpu12_sram_map,0);
+ save1 = map_read32(&autcpu12_sram_map,0x10000);
+ map_write32(&autcpu12_sram_map,~save0,0x10000);
+ /* if we find this pattern on 0x0, we have 32K size
+ * restore contents and exit
+ */
+ if ( map_read32(&autcpu12_sram_map,0) != save0) {
+ map_write32(&autcpu12_sram_map,save0,0x0);
+ goto map;
+ }
+ /* We have a 128K found, restore 0x10000 and set size
+ * to 128K
+ */
+ map_write32(&autcpu12_sram_map,save1,0x10000);
+ autcpu12_sram_map.size = SZ_128K;
+
+map:
+ sram_mtd = do_map_probe("map_ram", &autcpu12_sram_map);
+ if (!sram_mtd) {
+ printk("NV-RAM probe failed\n");
+ err = -ENXIO;
+ goto out_ioremap;
+ }
+
+ sram_mtd->owner = THIS_MODULE;
+ sram_mtd->erasesize = 16;
+
+ if (add_mtd_device(sram_mtd)) {
+ printk("NV-RAM device addition failed\n");
+ err = -ENOMEM;
+ goto out_probe;
+ }
+
+ printk("NV-RAM device size %ldKiB registered on AUTCPU12\n",autcpu12_sram_map.size/SZ_1K);
+
+ return 0;
+
+out_probe:
+ map_destroy(sram_mtd);
+ sram_mtd = 0;
+
+out_ioremap:
+ iounmap((void *)autcpu12_sram_map.virt);
+out:
+ return err;
+}
+
+static void __exit cleanup_autcpu12_maps(void)
+{
+ if (sram_mtd) {
+ del_mtd_device(sram_mtd);
+ map_destroy(sram_mtd);
+ iounmap((void *)autcpu12_sram_map.virt);
+ }
+}
+
+module_init(init_autcpu12_sram);
+module_exit(cleanup_autcpu12_maps);
+
+MODULE_AUTHOR("Thomas Gleixner");
+MODULE_DESCRIPTION("autcpu12 NV-RAM map driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/maps/bast-flash.c b/drivers/mtd/maps/bast-flash.c
new file mode 100644
index 000000000000..44de3a81b277
--- /dev/null
+++ b/drivers/mtd/maps/bast-flash.c
@@ -0,0 +1,227 @@
+/* linux/drivers/mtd/maps/bast_flash.c
+ *
+ * Copyright (c) 2004 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * Simtec Bast (EB2410ITX) NOR MTD Mapping driver
+ *
+ * Changelog:
+ * 20-Sep-2004 BJD Initial version
+ *
+ * $Id: bast-flash.c,v 1.1 2004/09/21 14:29:04 bjd Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/io.h>
+#include <asm/mach-types.h>
+#include <asm/mach/flash.h>
+
+#include <asm/arch/map.h>
+#include <asm/arch/bast-map.h>
+#include <asm/arch/bast-cpld.h>
+
+#ifdef CONFIG_MTD_BAST_MAXSIZE
+#define AREA_MAXSIZE (CONFIG_MTD_BAST_MAXSIZE * (1024*1024))
+#else
+#define AREA_MAXSIZE (32*1024*1024)
+#endif
+
+#define PFX "bast-flash: "
+
+struct bast_flash_info {
+ struct mtd_info *mtd;
+ struct map_info map;
+ struct mtd_partition *partitions;
+ struct resource *area;
+};
+
+static const char *probes[] = { "RedBoot", "cmdlinepart", NULL };
+
+static struct bast_flash_info *to_bast_info(struct device *dev)
+{
+ return (struct bast_flash_info *)dev_get_drvdata(dev);
+}
+
+static void bast_flash_setrw(int to)
+{
+ unsigned int val;
+ unsigned long flags;
+
+ local_irq_save(flags);
+ val = __raw_readb(BAST_VA_CTRL3);
+
+ if (to)
+ val |= BAST_CPLD_CTRL3_ROMWEN;
+ else
+ val &= ~BAST_CPLD_CTRL3_ROMWEN;
+
+ pr_debug("new cpld ctrl3=%02x\n", val);
+
+ __raw_writeb(val, BAST_VA_CTRL3);
+ local_irq_restore(flags);
+}
+
+static int bast_flash_remove(struct device *dev)
+{
+ struct bast_flash_info *info = to_bast_info(dev);
+
+ dev_set_drvdata(dev, NULL);
+
+ if (info == NULL)
+ return 0;
+
+ if (info->map.virt != NULL)
+ iounmap(info->map.virt);
+
+ if (info->mtd) {
+ del_mtd_partitions(info->mtd);
+ map_destroy(info->mtd);
+ }
+
+ if (info->partitions)
+ kfree(info->partitions);
+
+ if (info->area) {
+ release_resource(info->area);
+ kfree(info->area);
+ }
+
+ kfree(info);
+
+ return 0;
+}
+
+static int bast_flash_probe(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct bast_flash_info *info;
+ struct resource *res;
+ int err = 0;
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (info == NULL) {
+ printk(KERN_ERR PFX "no memory for flash info\n");
+ err = -ENOMEM;
+ goto exit_error;
+ }
+
+ memzero(info, sizeof(*info));
+ dev_set_drvdata(dev, info);
+
+ res = pdev->resource; /* assume that the flash has one resource */
+
+ info->map.phys = res->start;
+ info->map.size = res->end - res->start + 1;
+ info->map.name = dev->bus_id;
+ info->map.bankwidth = 2;
+
+ if (info->map.size > AREA_MAXSIZE)
+ info->map.size = AREA_MAXSIZE;
+
+ pr_debug("%s: area %08lx, size %ld\n", __FUNCTION__,
+ info->map.phys, info->map.size);
+
+ info->area = request_mem_region(res->start, info->map.size,
+ pdev->name);
+ if (info->area == NULL) {
+ printk(KERN_ERR PFX "cannot reserve flash memory region\n");
+ err = -ENOENT;
+ goto exit_error;
+ }
+
+ info->map.virt = ioremap(res->start, info->map.size);
+ pr_debug("%s: virt at %08x\n", __FUNCTION__, (int)info->map.virt);
+
+ if (info->map.virt == 0) {
+ printk(KERN_ERR PFX "failed to ioremap() region\n");
+ err = -EIO;
+ goto exit_error;
+ }
+
+ simple_map_init(&info->map);
+
+ /* enable the write to the flash area */
+
+ bast_flash_setrw(1);
+
+ /* probe for the device(s) */
+
+ info->mtd = do_map_probe("jedec_probe", &info->map);
+ if (info->mtd == NULL)
+ info->mtd = do_map_probe("cfi_probe", &info->map);
+
+ if (info->mtd == NULL) {
+ printk(KERN_ERR PFX "map_probe() failed\n");
+ err = -ENXIO;
+ goto exit_error;
+ }
+
+ /* mark ourselves as the owner */
+ info->mtd->owner = THIS_MODULE;
+
+ err = parse_mtd_partitions(info->mtd, probes, &info->partitions, 0);
+ if (err > 0) {
+ err = add_mtd_partitions(info->mtd, info->partitions, err);
+ if (err)
+ printk(KERN_ERR PFX "cannot add/parse partitions\n");
+ }
+
+ if (err == 0)
+ return 0;
+
+ /* fall through to exit error */
+
+ exit_error:
+ bast_flash_remove(dev);
+ return err;
+}
+
+static struct device_driver bast_flash_driver = {
+ .name = "bast-nor",
+ .bus = &platform_bus_type,
+ .probe = bast_flash_probe,
+ .remove = bast_flash_remove,
+};
+
+static int __init bast_flash_init(void)
+{
+ printk("BAST NOR-Flash Driver, (c) 2004 Simtec Electronics\n");
+ return driver_register(&bast_flash_driver);
+}
+
+static void __exit bast_flash_exit(void)
+{
+ driver_unregister(&bast_flash_driver);
+}
+
+module_init(bast_flash_init);
+module_exit(bast_flash_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("BAST MTD Map driver");
diff --git a/drivers/mtd/maps/beech-mtd.c b/drivers/mtd/maps/beech-mtd.c
new file mode 100644
index 000000000000..5e79c9d5da2b
--- /dev/null
+++ b/drivers/mtd/maps/beech-mtd.c
@@ -0,0 +1,112 @@
+/*
+ * $Id: beech-mtd.c,v 1.10 2004/11/04 13:24:14 gleixner Exp $
+ *
+ * drivers/mtd/maps/beech-mtd.c MTD mappings and partition tables for
+ * IBM 405LP Beech boards.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Copyright (C) 2002, International Business Machines Corporation
+ * All Rights Reserved.
+ *
+ * Bishop Brock
+ * IBM Research, Austin Center for Low-Power Computing
+ * bcbrock@us.ibm.com
+ * March 2002
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/io.h>
+#include <asm/ibm4xx.h>
+
+#define NAME "Beech Linux Flash"
+#define PADDR BEECH_BIGFLASH_PADDR
+#define SIZE BEECH_BIGFLASH_SIZE
+#define BUSWIDTH 1
+
+/* Flash memories on these boards are memory resources, accessed big-endian. */
+
+
+static struct map_info beech_mtd_map = {
+ .name = NAME,
+ .size = SIZE,
+ .bankwidth = BUSWIDTH,
+ .phys = PADDR
+};
+
+static struct mtd_info *beech_mtd;
+
+static struct mtd_partition beech_partitions[2] = {
+ {
+ .name = "Linux Kernel",
+ .size = BEECH_KERNEL_SIZE,
+ .offset = BEECH_KERNEL_OFFSET
+ }, {
+ .name = "Free Area",
+ .size = BEECH_FREE_AREA_SIZE,
+ .offset = BEECH_FREE_AREA_OFFSET
+ }
+};
+
+static int __init
+init_beech_mtd(void)
+{
+ printk("%s: 0x%08x at 0x%08x\n", NAME, SIZE, PADDR);
+
+ beech_mtd_map.virt = ioremap(PADDR, SIZE);
+
+ if (!beech_mtd_map.virt) {
+ printk("%s: failed to ioremap 0x%x\n", NAME, PADDR);
+ return -EIO;
+ }
+
+ simple_map_init(&beech_mtd_map);
+
+ printk("%s: probing %d-bit flash bus\n", NAME, BUSWIDTH * 8);
+ beech_mtd = do_map_probe("cfi_probe", &beech_mtd_map);
+
+ if (!beech_mtd)
+ return -ENXIO;
+
+ beech_mtd->owner = THIS_MODULE;
+
+ return add_mtd_partitions(beech_mtd, beech_partitions, 2);
+}
+
+static void __exit
+cleanup_beech_mtd(void)
+{
+ if (beech_mtd) {
+ del_mtd_partitions(beech_mtd);
+ map_destroy(beech_mtd);
+ iounmap((void *) beech_mtd_map.virt);
+ }
+}
+
+module_init(init_beech_mtd);
+module_exit(cleanup_beech_mtd);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Bishop Brock <bcbrock@us.ibm.com>");
+MODULE_DESCRIPTION("MTD map and partitions for IBM 405LP Beech boards");
diff --git a/drivers/mtd/maps/cdb89712.c b/drivers/mtd/maps/cdb89712.c
new file mode 100644
index 000000000000..ab15dac2f936
--- /dev/null
+++ b/drivers/mtd/maps/cdb89712.c
@@ -0,0 +1,268 @@
+/*
+ * Flash on Cirrus CDB89712
+ *
+ * $Id: cdb89712.c,v 1.10 2004/11/04 13:24:14 gleixner Exp $
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <asm/arch/hardware.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+
+
+
+static struct mtd_info *flash_mtd;
+
+struct map_info cdb89712_flash_map = {
+ .name = "flash",
+ .size = FLASH_SIZE,
+ .bankwidth = FLASH_WIDTH,
+ .phys = FLASH_START,
+};
+
+struct resource cdb89712_flash_resource = {
+ .name = "Flash",
+ .start = FLASH_START,
+ .end = FLASH_START + FLASH_SIZE - 1,
+ .flags = IORESOURCE_IO | IORESOURCE_BUSY,
+};
+
+static int __init init_cdb89712_flash (void)
+{
+ int err;
+
+ if (request_resource (&ioport_resource, &cdb89712_flash_resource)) {
+ printk(KERN_NOTICE "Failed to reserve Cdb89712 FLASH space\n");
+ err = -EBUSY;
+ goto out;
+ }
+
+ cdb89712_flash_map.virt = ioremap(FLASH_START, FLASH_SIZE);
+ if (!cdb89712_flash_map.virt) {
+ printk(KERN_NOTICE "Failed to ioremap Cdb89712 FLASH space\n");
+ err = -EIO;
+ goto out_resource;
+ }
+ simple_map_init(&cdb89712_flash_map);
+ flash_mtd = do_map_probe("cfi_probe", &cdb89712_flash_map);
+ if (!flash_mtd) {
+ flash_mtd = do_map_probe("map_rom", &cdb89712_flash_map);
+ if (flash_mtd)
+ flash_mtd->erasesize = 0x10000;
+ }
+ if (!flash_mtd) {
+ printk("FLASH probe failed\n");
+ err = -ENXIO;
+ goto out_ioremap;
+ }
+
+ flash_mtd->owner = THIS_MODULE;
+
+ if (add_mtd_device(flash_mtd)) {
+ printk("FLASH device addition failed\n");
+ err = -ENOMEM;
+ goto out_probe;
+ }
+
+ return 0;
+
+out_probe:
+ map_destroy(flash_mtd);
+ flash_mtd = 0;
+out_ioremap:
+ iounmap((void *)cdb89712_flash_map.virt);
+out_resource:
+ release_resource (&cdb89712_flash_resource);
+out:
+ return err;
+}
+
+
+
+
+
+static struct mtd_info *sram_mtd;
+
+struct map_info cdb89712_sram_map = {
+ .name = "SRAM",
+ .size = SRAM_SIZE,
+ .bankwidth = SRAM_WIDTH,
+ .phys = SRAM_START,
+};
+
+struct resource cdb89712_sram_resource = {
+ .name = "SRAM",
+ .start = SRAM_START,
+ .end = SRAM_START + SRAM_SIZE - 1,
+ .flags = IORESOURCE_IO | IORESOURCE_BUSY,
+};
+
+static int __init init_cdb89712_sram (void)
+{
+ int err;
+
+ if (request_resource (&ioport_resource, &cdb89712_sram_resource)) {
+ printk(KERN_NOTICE "Failed to reserve Cdb89712 SRAM space\n");
+ err = -EBUSY;
+ goto out;
+ }
+
+ cdb89712_sram_map.virt = ioremap(SRAM_START, SRAM_SIZE);
+ if (!cdb89712_sram_map.virt) {
+ printk(KERN_NOTICE "Failed to ioremap Cdb89712 SRAM space\n");
+ err = -EIO;
+ goto out_resource;
+ }
+ simple_map_init(&cdb89712_sram_map);
+ sram_mtd = do_map_probe("map_ram", &cdb89712_sram_map);
+ if (!sram_mtd) {
+ printk("SRAM probe failed\n");
+ err = -ENXIO;
+ goto out_ioremap;
+ }
+
+ sram_mtd->owner = THIS_MODULE;
+ sram_mtd->erasesize = 16;
+
+ if (add_mtd_device(sram_mtd)) {
+ printk("SRAM device addition failed\n");
+ err = -ENOMEM;
+ goto out_probe;
+ }
+
+ return 0;
+
+out_probe:
+ map_destroy(sram_mtd);
+ sram_mtd = 0;
+out_ioremap:
+ iounmap((void *)cdb89712_sram_map.virt);
+out_resource:
+ release_resource (&cdb89712_sram_resource);
+out:
+ return err;
+}
+
+
+
+
+
+
+
+static struct mtd_info *bootrom_mtd;
+
+struct map_info cdb89712_bootrom_map = {
+ .name = "BootROM",
+ .size = BOOTROM_SIZE,
+ .bankwidth = BOOTROM_WIDTH,
+ .phys = BOOTROM_START,
+};
+
+struct resource cdb89712_bootrom_resource = {
+ .name = "BootROM",
+ .start = BOOTROM_START,
+ .end = BOOTROM_START + BOOTROM_SIZE - 1,
+ .flags = IORESOURCE_IO | IORESOURCE_BUSY,
+};
+
+static int __init init_cdb89712_bootrom (void)
+{
+ int err;
+
+ if (request_resource (&ioport_resource, &cdb89712_bootrom_resource)) {
+ printk(KERN_NOTICE "Failed to reserve Cdb89712 BOOTROM space\n");
+ err = -EBUSY;
+ goto out;
+ }
+
+ cdb89712_bootrom_map.virt = ioremap(BOOTROM_START, BOOTROM_SIZE);
+ if (!cdb89712_bootrom_map.virt) {
+ printk(KERN_NOTICE "Failed to ioremap Cdb89712 BootROM space\n");
+ err = -EIO;
+ goto out_resource;
+ }
+ simple_map_init(&cdb89712_bootrom_map);
+ bootrom_mtd = do_map_probe("map_rom", &cdb89712_bootrom_map);
+ if (!bootrom_mtd) {
+ printk("BootROM probe failed\n");
+ err = -ENXIO;
+ goto out_ioremap;
+ }
+
+ bootrom_mtd->owner = THIS_MODULE;
+ bootrom_mtd->erasesize = 0x10000;
+
+ if (add_mtd_device(bootrom_mtd)) {
+ printk("BootROM device addition failed\n");
+ err = -ENOMEM;
+ goto out_probe;
+ }
+
+ return 0;
+
+out_probe:
+ map_destroy(bootrom_mtd);
+ bootrom_mtd = 0;
+out_ioremap:
+ iounmap((void *)cdb89712_bootrom_map.virt);
+out_resource:
+ release_resource (&cdb89712_bootrom_resource);
+out:
+ return err;
+}
+
+
+
+
+
+static int __init init_cdb89712_maps(void)
+{
+
+ printk(KERN_INFO "Cirrus CDB89712 MTD mappings:\n Flash 0x%x at 0x%x\n SRAM 0x%x at 0x%x\n BootROM 0x%x at 0x%x\n",
+ FLASH_SIZE, FLASH_START, SRAM_SIZE, SRAM_START, BOOTROM_SIZE, BOOTROM_START);
+
+ init_cdb89712_flash();
+ init_cdb89712_sram();
+ init_cdb89712_bootrom();
+
+ return 0;
+}
+
+
+static void __exit cleanup_cdb89712_maps(void)
+{
+ if (sram_mtd) {
+ del_mtd_device(sram_mtd);
+ map_destroy(sram_mtd);
+ iounmap((void *)cdb89712_sram_map.virt);
+ release_resource (&cdb89712_sram_resource);
+ }
+
+ if (flash_mtd) {
+ del_mtd_device(flash_mtd);
+ map_destroy(flash_mtd);
+ iounmap((void *)cdb89712_flash_map.virt);
+ release_resource (&cdb89712_flash_resource);
+ }
+
+ if (bootrom_mtd) {
+ del_mtd_device(bootrom_mtd);
+ map_destroy(bootrom_mtd);
+ iounmap((void *)cdb89712_bootrom_map.virt);
+ release_resource (&cdb89712_bootrom_resource);
+ }
+}
+
+module_init(init_cdb89712_maps);
+module_exit(cleanup_cdb89712_maps);
+
+MODULE_AUTHOR("Ray L");
+MODULE_DESCRIPTION("ARM CDB89712 map driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/maps/ceiva.c b/drivers/mtd/maps/ceiva.c
new file mode 100644
index 000000000000..da8584a662f4
--- /dev/null
+++ b/drivers/mtd/maps/ceiva.c
@@ -0,0 +1,350 @@
+/*
+ * Ceiva flash memory driver.
+ * Copyright (C) 2002 Rob Scott <rscott@mtrob.fdns.net>
+ *
+ * Note: this driver supports jedec compatible devices. Modification
+ * for CFI compatible devices should be straight forward: change
+ * jedec_probe to cfi_probe.
+ *
+ * Based on: sa1100-flash.c, which has the following copyright:
+ * Flash memory access on SA11x0 based devices
+ *
+ * (C) 2000 Nicolas Pitre <nico@cam.org>
+ *
+ * $Id: ceiva.c,v 1.11 2004/09/16 23:27:12 gleixner Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/concat.h>
+
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+#include <asm/io.h>
+#include <asm/sizes.h>
+
+/*
+ * This isn't complete yet, so...
+ */
+#define CONFIG_MTD_CEIVA_STATICMAP
+
+#ifdef CONFIG_MTD_CEIVA_STATICMAP
+/*
+ * See include/linux/mtd/partitions.h for definition of the mtd_partition
+ * structure.
+ *
+ * Please note:
+ * 1. The flash size given should be the largest flash size that can
+ * be accomodated.
+ *
+ * 2. The bus width must defined in clps_setup_flash.
+ *
+ * The MTD layer will detect flash chip aliasing and reduce the size of
+ * the map accordingly.
+ *
+ */
+
+#ifdef CONFIG_ARCH_CEIVA
+/* Flash / Partition sizing */
+/* For the 28F8003, we use the block mapping to calcuate the sizes */
+#define MAX_SIZE_KiB (16 + 8 + 8 + 96 + (7*128))
+#define BOOT_PARTITION_SIZE_KiB (16)
+#define PARAMS_PARTITION_SIZE_KiB (8)
+#define KERNEL_PARTITION_SIZE_KiB (4*128)
+/* Use both remaing portion of first flash, and all of second flash */
+#define ROOT_PARTITION_SIZE_KiB (3*128) + (8*128)
+
+static struct mtd_partition ceiva_partitions[] = {
+ {
+ .name = "Ceiva BOOT partition",
+ .size = BOOT_PARTITION_SIZE_KiB*1024,
+ .offset = 0,
+
+ },{
+ .name = "Ceiva parameters partition",
+ .size = PARAMS_PARTITION_SIZE_KiB*1024,
+ .offset = (16 + 8) * 1024,
+ },{
+ .name = "Ceiva kernel partition",
+ .size = (KERNEL_PARTITION_SIZE_KiB)*1024,
+ .offset = 0x20000,
+
+ },{
+ .name = "Ceiva root filesystem partition",
+ .offset = MTDPART_OFS_APPEND,
+ .size = (ROOT_PARTITION_SIZE_KiB)*1024,
+ }
+};
+#endif
+
+static int __init clps_static_partitions(struct mtd_partition **parts)
+{
+ int nb_parts = 0;
+
+#ifdef CONFIG_ARCH_CEIVA
+ if (machine_is_ceiva()) {
+ *parts = ceiva_partitions;
+ nb_parts = ARRAY_SIZE(ceiva_partitions);
+ }
+#endif
+ return nb_parts;
+}
+#endif
+
+struct clps_info {
+ unsigned long base;
+ unsigned long size;
+ int width;
+ void *vbase;
+ struct map_info *map;
+ struct mtd_info *mtd;
+ struct resource *res;
+};
+
+#define NR_SUBMTD 4
+
+static struct clps_info info[NR_SUBMTD];
+
+static int __init clps_setup_mtd(struct clps_info *clps, int nr, struct mtd_info **rmtd)
+{
+ struct mtd_info *subdev[nr];
+ struct map_info *maps;
+ int i, found = 0, ret = 0;
+
+ /*
+ * Allocate the map_info structs in one go.
+ */
+ maps = kmalloc(sizeof(struct map_info) * nr, GFP_KERNEL);
+ if (!maps)
+ return -ENOMEM;
+ memset(maps, 0, sizeof(struct map_info) * nr);
+ /*
+ * Claim and then map the memory regions.
+ */
+ for (i = 0; i < nr; i++) {
+ if (clps[i].base == (unsigned long)-1)
+ break;
+
+ clps[i].res = request_mem_region(clps[i].base, clps[i].size, "clps flash");
+ if (!clps[i].res) {
+ ret = -EBUSY;
+ break;
+ }
+
+ clps[i].map = maps + i;
+
+ clps[i].map->name = "clps flash";
+ clps[i].map->phys = clps[i].base;
+
+ clps[i].vbase = ioremap(clps[i].base, clps[i].size);
+ if (!clps[i].vbase) {
+ ret = -ENOMEM;
+ break;
+ }
+
+ clps[i].map->virt = (void __iomem *)clps[i].vbase;
+ clps[i].map->bankwidth = clps[i].width;
+ clps[i].map->size = clps[i].size;
+
+ simple_map_init(&clps[i].map);
+
+ clps[i].mtd = do_map_probe("jedec_probe", clps[i].map);
+ if (clps[i].mtd == NULL) {
+ ret = -ENXIO;
+ break;
+ }
+ clps[i].mtd->owner = THIS_MODULE;
+ subdev[i] = clps[i].mtd;
+
+ printk(KERN_INFO "clps flash: JEDEC device at 0x%08lx, %dMiB, "
+ "%d-bit\n", clps[i].base, clps[i].mtd->size >> 20,
+ clps[i].width * 8);
+ found += 1;
+ }
+
+ /*
+ * ENXIO is special. It means we didn't find a chip when
+ * we probed. We need to tear down the mapping, free the
+ * resource and mark it as such.
+ */
+ if (ret == -ENXIO) {
+ iounmap(clps[i].vbase);
+ clps[i].vbase = NULL;
+ release_resource(clps[i].res);
+ clps[i].res = NULL;
+ }
+
+ /*
+ * If we found one device, don't bother with concat support.
+ * If we found multiple devices, use concat if we have it
+ * available, otherwise fail.
+ */
+ if (ret == 0 || ret == -ENXIO) {
+ if (found == 1) {
+ *rmtd = subdev[0];
+ ret = 0;
+ } else if (found > 1) {
+ /*
+ * We detected multiple devices. Concatenate
+ * them together.
+ */
+#ifdef CONFIG_MTD_CONCAT
+ *rmtd = mtd_concat_create(subdev, found,
+ "clps flash");
+ if (*rmtd == NULL)
+ ret = -ENXIO;
+#else
+ printk(KERN_ERR "clps flash: multiple devices "
+ "found but MTD concat support disabled.\n");
+ ret = -ENXIO;
+#endif
+ }
+ }
+
+ /*
+ * If we failed, clean up.
+ */
+ if (ret) {
+ do {
+ if (clps[i].mtd)
+ map_destroy(clps[i].mtd);
+ if (clps[i].vbase)
+ iounmap(clps[i].vbase);
+ if (clps[i].res)
+ release_resource(clps[i].res);
+ } while (i--);
+
+ kfree(maps);
+ }
+
+ return ret;
+}
+
+static void __exit clps_destroy_mtd(struct clps_info *clps, struct mtd_info *mtd)
+{
+ int i;
+
+ del_mtd_partitions(mtd);
+
+ if (mtd != clps[0].mtd)
+ mtd_concat_destroy(mtd);
+
+ for (i = NR_SUBMTD; i >= 0; i--) {
+ if (clps[i].mtd)
+ map_destroy(clps[i].mtd);
+ if (clps[i].vbase)
+ iounmap(clps[i].vbase);
+ if (clps[i].res)
+ release_resource(clps[i].res);
+ }
+ kfree(clps[0].map);
+}
+
+/*
+ * We define the memory space, size, and width for the flash memory
+ * space here.
+ */
+
+static int __init clps_setup_flash(void)
+{
+ int nr;
+
+#ifdef CONFIG_ARCH_CEIVA
+ if (machine_is_ceiva()) {
+ info[0].base = CS0_PHYS_BASE;
+ info[0].size = SZ_32M;
+ info[0].width = CEIVA_FLASH_WIDTH;
+ info[1].base = CS1_PHYS_BASE;
+ info[1].size = SZ_32M;
+ info[1].width = CEIVA_FLASH_WIDTH;
+ nr = 2;
+ }
+#endif
+ return nr;
+}
+
+static struct mtd_partition *parsed_parts;
+static const char *probes[] = { "cmdlinepart", "RedBoot", NULL };
+
+static void __init clps_locate_partitions(struct mtd_info *mtd)
+{
+ const char *part_type = NULL;
+ int nr_parts = 0;
+ do {
+ /*
+ * Partition selection stuff.
+ */
+ nr_parts = parse_mtd_partitions(mtd, probes, &parsed_parts, 0);
+ if (nr_parts > 0) {
+ part_type = "command line";
+ break;
+ }
+#ifdef CONFIG_MTD_CEIVA_STATICMAP
+ nr_parts = clps_static_partitions(&parsed_parts);
+ if (nr_parts > 0) {
+ part_type = "static";
+ break;
+ }
+ printk("found: %d partitions\n", nr_parts);
+#endif
+ } while (0);
+
+ if (nr_parts == 0) {
+ printk(KERN_NOTICE "clps flash: no partition info "
+ "available, registering whole flash\n");
+ add_mtd_device(mtd);
+ } else {
+ printk(KERN_NOTICE "clps flash: using %s partition "
+ "definition\n", part_type);
+ add_mtd_partitions(mtd, parsed_parts, nr_parts);
+ }
+
+ /* Always succeeds. */
+}
+
+static void __exit clps_destroy_partitions(void)
+{
+ if (parsed_parts)
+ kfree(parsed_parts);
+}
+
+static struct mtd_info *mymtd;
+
+static int __init clps_mtd_init(void)
+{
+ int ret;
+ int nr;
+
+ nr = clps_setup_flash();
+ if (nr < 0)
+ return nr;
+
+ ret = clps_setup_mtd(info, nr, &mymtd);
+ if (ret)
+ return ret;
+
+ clps_locate_partitions(mymtd);
+
+ return 0;
+}
+
+static void __exit clps_mtd_cleanup(void)
+{
+ clps_destroy_mtd(info, mymtd);
+ clps_destroy_partitions();
+}
+
+module_init(clps_mtd_init);
+module_exit(clps_mtd_cleanup);
+
+MODULE_AUTHOR("Rob Scott");
+MODULE_DESCRIPTION("Cirrus Logic JEDEC map driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/maps/cfi_flagadm.c b/drivers/mtd/maps/cfi_flagadm.c
new file mode 100644
index 000000000000..f72e4f894b32
--- /dev/null
+++ b/drivers/mtd/maps/cfi_flagadm.c
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2001 Flaga hf. Medical Devices, Kri Davsson <kd@flaga.is>
+ *
+ * $Id: cfi_flagadm.c,v 1.14 2004/11/04 13:24:14 gleixner Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+
+/* We split the flash chip up into four parts.
+ * 1: bootloader firts 128k (0x00000000 - 0x0001FFFF) size 0x020000
+ * 2: kernel 640k (0x00020000 - 0x000BFFFF) size 0x0A0000
+ * 3: compressed 1536k root ramdisk (0x000C0000 - 0x0023FFFF) size 0x180000
+ * 4: writeable diskpartition (jffs)(0x00240000 - 0x003FFFFF) size 0x1C0000
+ */
+
+#define FLASH_PHYS_ADDR 0x40000000
+#define FLASH_SIZE 0x400000
+
+#define FLASH_PARTITION0_ADDR 0x00000000
+#define FLASH_PARTITION0_SIZE 0x00020000
+
+#define FLASH_PARTITION1_ADDR 0x00020000
+#define FLASH_PARTITION1_SIZE 0x000A0000
+
+#define FLASH_PARTITION2_ADDR 0x000C0000
+#define FLASH_PARTITION2_SIZE 0x00180000
+
+#define FLASH_PARTITION3_ADDR 0x00240000
+#define FLASH_PARTITION3_SIZE 0x001C0000
+
+
+struct map_info flagadm_map = {
+ .name = "FlagaDM flash device",
+ .size = FLASH_SIZE,
+ .bankwidth = 2,
+};
+
+struct mtd_partition flagadm_parts[] = {
+ {
+ .name = "Bootloader",
+ .offset = FLASH_PARTITION0_ADDR,
+ .size = FLASH_PARTITION0_SIZE
+ },
+ {
+ .name = "Kernel image",
+ .offset = FLASH_PARTITION1_ADDR,
+ .size = FLASH_PARTITION1_SIZE
+ },
+ {
+ .name = "Initial ramdisk image",
+ .offset = FLASH_PARTITION2_ADDR,
+ .size = FLASH_PARTITION2_SIZE
+ },
+ {
+ .name = "Persistant storage",
+ .offset = FLASH_PARTITION3_ADDR,
+ .size = FLASH_PARTITION3_SIZE
+ }
+};
+
+#define PARTITION_COUNT (sizeof(flagadm_parts)/sizeof(struct mtd_partition))
+
+static struct mtd_info *mymtd;
+
+int __init init_flagadm(void)
+{
+ printk(KERN_NOTICE "FlagaDM flash device: %x at %x\n",
+ FLASH_SIZE, FLASH_PHYS_ADDR);
+
+ flagadm_map.phys = FLASH_PHYS_ADDR;
+ flagadm_map.virt = ioremap(FLASH_PHYS_ADDR,
+ FLASH_SIZE);
+
+ if (!flagadm_map.virt) {
+ printk("Failed to ioremap\n");
+ return -EIO;
+ }
+
+ simple_map_init(&flagadm_map);
+
+ mymtd = do_map_probe("cfi_probe", &flagadm_map);
+ if (mymtd) {
+ mymtd->owner = THIS_MODULE;
+ add_mtd_partitions(mymtd, flagadm_parts, PARTITION_COUNT);
+ printk(KERN_NOTICE "FlagaDM flash device initialized\n");
+ return 0;
+ }
+
+ iounmap((void *)flagadm_map.virt);
+ return -ENXIO;
+}
+
+static void __exit cleanup_flagadm(void)
+{
+ if (mymtd) {
+ del_mtd_partitions(mymtd);
+ map_destroy(mymtd);
+ }
+ if (flagadm_map.virt) {
+ iounmap((void *)flagadm_map.virt);
+ flagadm_map.virt = 0;
+ }
+}
+
+module_init(init_flagadm);
+module_exit(cleanup_flagadm);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kri Davsson <kd@flaga.is>");
+MODULE_DESCRIPTION("MTD map driver for Flaga digital module");
diff --git a/drivers/mtd/maps/cstm_mips_ixx.c b/drivers/mtd/maps/cstm_mips_ixx.c
new file mode 100644
index 000000000000..ae9252fbf176
--- /dev/null
+++ b/drivers/mtd/maps/cstm_mips_ixx.c
@@ -0,0 +1,270 @@
+/*
+ * $Id: cstm_mips_ixx.c,v 1.12 2004/11/04 13:24:14 gleixner Exp $
+ *
+ * Mapping of a custom board with both AMD CFI and JEDEC flash in partitions.
+ * Config with both CFI and JEDEC device support.
+ *
+ * Basically physmap.c with the addition of partitions and
+ * an array of mapping info to accomodate more than one flash type per board.
+ *
+ * Copyright 2000 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/config.h>
+#include <linux/delay.h>
+
+#if defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR)
+#define CC_GCR 0xB4013818
+#define CC_GPBCR 0xB401380A
+#define CC_GPBDR 0xB4013808
+#define CC_M68K_DEVICE 1
+#define CC_M68K_FUNCTION 6
+#define CC_CONFADDR 0xB8004000
+#define CC_CONFDATA 0xB8004004
+#define CC_FC_FCR 0xB8002004
+#define CC_FC_DCR 0xB8002008
+#define CC_GPACR 0xB4013802
+#define CC_GPAICR 0xB4013804
+#endif /* defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) */
+
+#if defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR)
+void cstm_mips_ixx_set_vpp(struct map_info *map,int vpp)
+{
+ static DEFINE_SPINLOCK(vpp_lock);
+ static int vpp_count = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&vpp_lock, flags);
+
+ if (vpp) {
+ if (!vpp_count++) {
+ __u16 data;
+ __u8 data1;
+ static u8 first = 1;
+
+ // Set GPIO port B pin3 to high
+ data = *(__u16 *)(CC_GPBCR);
+ data = (data & 0xff0f) | 0x0040;
+ *(__u16 *)CC_GPBCR = data;
+ *(__u8 *)CC_GPBDR = (*(__u8*)CC_GPBDR) | 0x08;
+ if (first) {
+ first = 0;
+ /* need to have this delay for first
+ enabling vpp after powerup */
+ udelay(40);
+ }
+ }
+ } else {
+ if (!--vpp_count) {
+ __u16 data;
+
+ // Set GPIO port B pin3 to high
+ data = *(__u16 *)(CC_GPBCR);
+ data = (data & 0xff3f) | 0x0040;
+ *(__u16 *)CC_GPBCR = data;
+ *(__u8 *)CC_GPBDR = (*(__u8*)CC_GPBDR) & 0xf7;
+ }
+ }
+ spin_unlock_irqrestore(&vpp_lock, flags);
+}
+#endif
+
+/* board and partition description */
+
+#define MAX_PHYSMAP_PARTITIONS 8
+struct cstm_mips_ixx_info {
+ char *name;
+ unsigned long window_addr;
+ unsigned long window_size;
+ int bankwidth;
+ int num_partitions;
+};
+
+#if defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR)
+#define PHYSMAP_NUMBER 1 // number of board desc structs needed, one per contiguous flash type
+const struct cstm_mips_ixx_info cstm_mips_ixx_board_desc[PHYSMAP_NUMBER] =
+{
+ { // 28F128J3A in 2x16 configuration
+ "big flash", // name
+ 0x08000000, // window_addr
+ 0x02000000, // window_size
+ 4, // bankwidth
+ 1, // num_partitions
+ }
+
+};
+static struct mtd_partition cstm_mips_ixx_partitions[PHYSMAP_NUMBER][MAX_PHYSMAP_PARTITIONS] = {
+{ // 28F128J3A in 2x16 configuration
+ {
+ .name = "main partition ",
+ .size = 0x02000000, // 128 x 2 x 128k byte sectors
+ .offset = 0,
+ },
+},
+};
+#else /* defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) */
+#define PHYSMAP_NUMBER 1 // number of board desc structs needed, one per contiguous flash type
+const struct cstm_mips_ixx_info cstm_mips_ixx_board_desc[PHYSMAP_NUMBER] =
+{
+ {
+ "MTD flash", // name
+ CONFIG_MTD_CSTM_MIPS_IXX_START, // window_addr
+ CONFIG_MTD_CSTM_MIPS_IXX_LEN, // window_size
+ CONFIG_MTD_CSTM_MIPS_IXX_BUSWIDTH, // bankwidth
+ 1, // num_partitions
+ },
+
+};
+static struct mtd_partition cstm_mips_ixx_partitions[PHYSMAP_NUMBER][MAX_PHYSMAP_PARTITIONS] = {
+{
+ {
+ .name = "main partition",
+ .size = CONFIG_MTD_CSTM_MIPS_IXX_LEN,
+ .offset = 0,
+ },
+},
+};
+#endif /* defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) */
+
+struct map_info cstm_mips_ixx_map[PHYSMAP_NUMBER];
+
+int __init init_cstm_mips_ixx(void)
+{
+ int i;
+ int jedec;
+ struct mtd_info *mymtd;
+ struct mtd_partition *parts;
+
+ /* Initialize mapping */
+ for (i=0;i<PHYSMAP_NUMBER;i++) {
+ printk(KERN_NOTICE "cstm_mips_ixx flash device: 0x%lx at 0x%lx\n",
+ cstm_mips_ixx_board_desc[i].window_size, cstm_mips_ixx_board_desc[i].window_addr);
+
+
+ cstm_mips_ixx_map[i].phys = cstm_mips_ixx_board_desc[i].window_addr;
+ cstm_mips_ixx_map[i].virt = ioremap(cstm_mips_ixx_board_desc[i].window_addr, cstm_mips_ixx_board_desc[i].window_size);
+ if (!cstm_mips_ixx_map[i].virt) {
+ printk(KERN_WARNING "Failed to ioremap\n");
+ return -EIO;
+ }
+ cstm_mips_ixx_map[i].name = cstm_mips_ixx_board_desc[i].name;
+ cstm_mips_ixx_map[i].size = cstm_mips_ixx_board_desc[i].window_size;
+ cstm_mips_ixx_map[i].bankwidth = cstm_mips_ixx_board_desc[i].bankwidth;
+#if defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR)
+ cstm_mips_ixx_map[i].set_vpp = cstm_mips_ixx_set_vpp;
+#endif
+ simple_map_init(&cstm_mips_ixx_map[i]);
+ //printk(KERN_NOTICE "cstm_mips_ixx: ioremap is %x\n",(unsigned int)(cstm_mips_ixx_map[i].virt));
+ }
+
+#if defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR)
+ setup_ITE_IVR_flash();
+#endif /* defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) */
+
+ for (i=0;i<PHYSMAP_NUMBER;i++) {
+ parts = &cstm_mips_ixx_partitions[i][0];
+ jedec = 0;
+ mymtd = (struct mtd_info *)do_map_probe("cfi_probe", &cstm_mips_ixx_map[i]);
+ //printk(KERN_NOTICE "phymap %d cfi_probe: mymtd is %x\n",i,(unsigned int)mymtd);
+ if (!mymtd) {
+ jedec = 1;
+ mymtd = (struct mtd_info *)do_map_probe("jedec", &cstm_mips_ixx_map[i]);
+ printk(KERN_NOTICE "cstm_mips_ixx %d jedec: mymtd is %x\n",i,(unsigned int)mymtd);
+ }
+ if (mymtd) {
+ mymtd->owner = THIS_MODULE;
+
+ cstm_mips_ixx_map[i].map_priv_2 = (unsigned long)mymtd;
+ add_mtd_partitions(mymtd, parts, cstm_mips_ixx_board_desc[i].num_partitions);
+ }
+ else
+ return -ENXIO;
+ }
+ return 0;
+}
+
+static void __exit cleanup_cstm_mips_ixx(void)
+{
+ int i;
+ struct mtd_info *mymtd;
+
+ for (i=0;i<PHYSMAP_NUMBER;i++) {
+ mymtd = (struct mtd_info *)cstm_mips_ixx_map[i].map_priv_2;
+ if (mymtd) {
+ del_mtd_partitions(mymtd);
+ map_destroy(mymtd);
+ }
+ if (cstm_mips_ixx_map[i].virt) {
+ iounmap((void *)cstm_mips_ixx_map[i].virt);
+ cstm_mips_ixx_map[i].virt = 0;
+ }
+ }
+}
+#if defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR)
+void PCISetULongByOffset(__u32 DevNumber, __u32 FuncNumber, __u32 Offset, __u32 data)
+{
+ __u32 offset;
+
+ offset = ( unsigned long )( 0x80000000 | ( DevNumber << 11 ) + ( FuncNumber << 8 ) + Offset) ;
+
+ *(__u32 *)CC_CONFADDR = offset;
+ *(__u32 *)CC_CONFDATA = data;
+}
+void setup_ITE_IVR_flash()
+{
+ __u32 size, base;
+
+ size = 0x0e000000; // 32MiB
+ base = (0x08000000) >> 8 >>1; // Bug: we must shift one more bit
+
+ /* need to set ITE flash to 32 bits instead of default 8 */
+#ifdef CONFIG_MIPS_IVR
+ *(__u32 *)CC_FC_FCR = 0x55;
+ *(__u32 *)CC_GPACR = 0xfffc;
+#else
+ *(__u32 *)CC_FC_FCR = 0x77;
+#endif
+ /* turn bursting off */
+ *(__u32 *)CC_FC_DCR = 0x0;
+
+ /* setup for one chip 4 byte PCI access */
+ PCISetULongByOffset(CC_M68K_DEVICE, CC_M68K_FUNCTION, 0x60, size | base);
+ PCISetULongByOffset(CC_M68K_DEVICE, CC_M68K_FUNCTION, 0x64, 0x02);
+}
+#endif /* defined(CONFIG_MIPS_ITE8172) || defined(CONFIG_MIPS_IVR) */
+
+module_init(init_cstm_mips_ixx);
+module_exit(cleanup_cstm_mips_ixx);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alice Hennessy <ahennessy@mvista.com>");
+MODULE_DESCRIPTION("MTD map driver for ITE 8172G and Globespan IVR boards");
diff --git a/drivers/mtd/maps/db1550-flash.c b/drivers/mtd/maps/db1550-flash.c
new file mode 100644
index 000000000000..d213888462a4
--- /dev/null
+++ b/drivers/mtd/maps/db1550-flash.c
@@ -0,0 +1,187 @@
+/*
+ * Flash memory access on Alchemy Db1550 board
+ *
+ * $Id: db1550-flash.c,v 1.7 2004/11/04 13:24:14 gleixner Exp $
+ *
+ * (C) 2004 Embedded Edge, LLC, based on db1550-flash.c:
+ * (C) 2003, 2004 Pete Popov <ppopov@embeddedalley.com>
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/io.h>
+
+#ifdef DEBUG_RW
+#define DBG(x...) printk(x)
+#else
+#define DBG(x...)
+#endif
+
+static unsigned long window_addr;
+static unsigned long window_size;
+
+
+static struct map_info db1550_map = {
+ .name = "Db1550 flash",
+};
+
+static unsigned char flash_bankwidth = 4;
+
+/*
+ * Support only 64MB NOR Flash parts
+ */
+
+#if defined(CONFIG_MTD_DB1550_BOOT) && defined(CONFIG_MTD_DB1550_USER)
+#define DB1550_BOTH_BANKS
+#elif defined(CONFIG_MTD_DB1550_BOOT) && !defined(CONFIG_MTD_DB1550_USER)
+#define DB1550_BOOT_ONLY
+#elif !defined(CONFIG_MTD_DB1550_BOOT) && defined(CONFIG_MTD_DB1550_USER)
+#define DB1550_USER_ONLY
+#endif
+
+#ifdef DB1550_BOTH_BANKS
+/* both banks will be used. Combine the first bank and the first
+ * part of the second bank together into a single jffs/jffs2
+ * partition.
+ */
+static struct mtd_partition db1550_partitions[] = {
+ /* assume boot[2:0]:swap is '0000' or '1000', which translates to:
+ * 1C00 0000 1FFF FFFF CE0 64MB Boot NOR Flash
+ * 1800 0000 1BFF FFFF CE0 64MB Param NOR Flash
+ */
+ {
+ .name = "User FS",
+ .size = (0x1FC00000 - 0x18000000),
+ .offset = 0x0000000
+ },{
+ .name = "yamon",
+ .size = 0x0100000,
+ .offset = MTDPART_OFS_APPEND,
+ .mask_flags = MTD_WRITEABLE
+ },{
+ .name = "raw kernel",
+ .size = (0x300000 - 0x40000), /* last 256KB is yamon env */
+ .offset = MTDPART_OFS_APPEND,
+ }
+};
+#elif defined(DB1550_BOOT_ONLY)
+static struct mtd_partition db1550_partitions[] = {
+ /* assume boot[2:0]:swap is '0000' or '1000', which translates to:
+ * 1C00 0000 1FFF FFFF CE0 64MB Boot NOR Flash
+ */
+ {
+ .name = "User FS",
+ .size = 0x03c00000,
+ .offset = 0x0000000
+ },{
+ .name = "yamon",
+ .size = 0x0100000,
+ .offset = MTDPART_OFS_APPEND,
+ .mask_flags = MTD_WRITEABLE
+ },{
+ .name = "raw kernel",
+ .size = (0x300000-0x40000), /* last 256KB is yamon env */
+ .offset = MTDPART_OFS_APPEND,
+ }
+};
+#elif defined(DB1550_USER_ONLY)
+static struct mtd_partition db1550_partitions[] = {
+ /* assume boot[2:0]:swap is '0000' or '1000', which translates to:
+ * 1800 0000 1BFF FFFF CE0 64MB Param NOR Flash
+ */
+ {
+ .name = "User FS",
+ .size = (0x4000000 - 0x200000), /* reserve 2MB for raw kernel */
+ .offset = 0x0000000
+ },{
+ .name = "raw kernel",
+ .size = MTDPART_SIZ_FULL,
+ .offset = MTDPART_OFS_APPEND,
+ }
+};
+#else
+#error MTD_DB1550 define combo error /* should never happen */
+#endif
+
+#define NB_OF(x) (sizeof(x)/sizeof(x[0]))
+
+static struct mtd_info *mymtd;
+
+/*
+ * Probe the flash density and setup window address and size
+ * based on user CONFIG options. There are times when we don't
+ * want the MTD driver to be probing the boot or user flash,
+ * so having the option to enable only one bank is important.
+ */
+int setup_flash_params(void)
+{
+#if defined(DB1550_BOTH_BANKS)
+ window_addr = 0x18000000;
+ window_size = 0x8000000;
+#elif defined(DB1550_BOOT_ONLY)
+ window_addr = 0x1C000000;
+ window_size = 0x4000000;
+#else /* USER ONLY */
+ window_addr = 0x18000000;
+ window_size = 0x4000000;
+#endif
+ return 0;
+}
+
+int __init db1550_mtd_init(void)
+{
+ struct mtd_partition *parts;
+ int nb_parts = 0;
+
+ /* Default flash bankwidth */
+ db1550_map.bankwidth = flash_bankwidth;
+
+ if (setup_flash_params())
+ return -ENXIO;
+
+ /*
+ * Static partition definition selection
+ */
+ parts = db1550_partitions;
+ nb_parts = NB_OF(db1550_partitions);
+ db1550_map.size = window_size;
+
+ /*
+ * Now let's probe for the actual flash. Do it here since
+ * specific machine settings might have been set above.
+ */
+ printk(KERN_NOTICE "Db1550 flash: probing %d-bit flash bus\n",
+ db1550_map.bankwidth*8);
+ db1550_map.virt = ioremap(window_addr, window_size);
+ mymtd = do_map_probe("cfi_probe", &db1550_map);
+ if (!mymtd) return -ENXIO;
+ mymtd->owner = THIS_MODULE;
+
+ add_mtd_partitions(mymtd, parts, nb_parts);
+ return 0;
+}
+
+static void __exit db1550_mtd_cleanup(void)
+{
+ if (mymtd) {
+ del_mtd_partitions(mymtd);
+ map_destroy(mymtd);
+ iounmap((void *) db1550_map.virt);
+ }
+}
+
+module_init(db1550_mtd_init);
+module_exit(db1550_mtd_cleanup);
+
+MODULE_AUTHOR("Embedded Edge, LLC");
+MODULE_DESCRIPTION("Db1550 mtd map driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/maps/db1x00-flash.c b/drivers/mtd/maps/db1x00-flash.c
new file mode 100644
index 000000000000..faa68ec56902
--- /dev/null
+++ b/drivers/mtd/maps/db1x00-flash.c
@@ -0,0 +1,226 @@
+/*
+ * Flash memory access on Alchemy Db1xxx boards
+ *
+ * $Id: db1x00-flash.c,v 1.6 2004/11/04 13:24:14 gleixner Exp $
+ *
+ * (C) 2003 Pete Popov <ppopov@embeddedalley.com>
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/io.h>
+
+#ifdef DEBUG_RW
+#define DBG(x...) printk(x)
+#else
+#define DBG(x...)
+#endif
+
+/* MTD CONFIG OPTIONS */
+#if defined(CONFIG_MTD_DB1X00_BOOT) && defined(CONFIG_MTD_DB1X00_USER)
+#define DB1X00_BOTH_BANKS
+#elif defined(CONFIG_MTD_DB1X00_BOOT) && !defined(CONFIG_MTD_DB1X00_USER)
+#define DB1X00_BOOT_ONLY
+#elif !defined(CONFIG_MTD_DB1X00_BOOT) && defined(CONFIG_MTD_DB1X00_USER)
+#define DB1X00_USER_ONLY
+#endif
+
+static unsigned long window_addr;
+static unsigned long window_size;
+static unsigned long flash_size;
+
+static unsigned short *bcsr = (unsigned short *)0xAE000000;
+static unsigned char flash_bankwidth = 4;
+
+/*
+ * The Db1x boards support different flash densities. We setup
+ * the mtd_partition structures below for default of 64Mbit
+ * flash densities, and override the partitions sizes, if
+ * necessary, after we check the board status register.
+ */
+
+#ifdef DB1X00_BOTH_BANKS
+/* both banks will be used. Combine the first bank and the first
+ * part of the second bank together into a single jffs/jffs2
+ * partition.
+ */
+static struct mtd_partition db1x00_partitions[] = {
+ {
+ .name = "User FS",
+ .size = 0x1c00000,
+ .offset = 0x0000000
+ },{
+ .name = "yamon",
+ .size = 0x0100000,
+ .offset = MTDPART_OFS_APPEND,
+ .mask_flags = MTD_WRITEABLE
+ },{
+ .name = "raw kernel",
+ .size = (0x300000-0x40000), /* last 256KB is env */
+ .offset = MTDPART_OFS_APPEND,
+ }
+};
+#elif defined(DB1X00_BOOT_ONLY)
+static struct mtd_partition db1x00_partitions[] = {
+ {
+ .name = "User FS",
+ .size = 0x00c00000,
+ .offset = 0x0000000
+ },{
+ .name = "yamon",
+ .size = 0x0100000,
+ .offset = MTDPART_OFS_APPEND,
+ .mask_flags = MTD_WRITEABLE
+ },{
+ .name = "raw kernel",
+ .size = (0x300000-0x40000), /* last 256KB is env */
+ .offset = MTDPART_OFS_APPEND,
+ }
+};
+#elif defined(DB1X00_USER_ONLY)
+static struct mtd_partition db1x00_partitions[] = {
+ {
+ .name = "User FS",
+ .size = 0x0e00000,
+ .offset = 0x0000000
+ },{
+ .name = "raw kernel",
+ .size = MTDPART_SIZ_FULL,
+ .offset = MTDPART_OFS_APPEND,
+ }
+};
+#else
+#error MTD_DB1X00 define combo error /* should never happen */
+#endif
+#define NB_OF(x) (sizeof(x)/sizeof(x[0]))
+
+#define NAME "Db1x00 Linux Flash"
+
+static struct map_info db1xxx_mtd_map = {
+ .name = NAME,
+};
+
+static struct mtd_partition *parsed_parts;
+static struct mtd_info *db1xxx_mtd;
+
+/*
+ * Probe the flash density and setup window address and size
+ * based on user CONFIG options. There are times when we don't
+ * want the MTD driver to be probing the boot or user flash,
+ * so having the option to enable only one bank is important.
+ */
+int setup_flash_params(void)
+{
+ switch ((bcsr[2] >> 14) & 0x3) {
+ case 0: /* 64Mbit devices */
+ flash_size = 0x800000; /* 8MB per part */
+#if defined(DB1X00_BOTH_BANKS)
+ window_addr = 0x1E000000;
+ window_size = 0x2000000;
+#elif defined(DB1X00_BOOT_ONLY)
+ window_addr = 0x1F000000;
+ window_size = 0x1000000;
+#else /* USER ONLY */
+ window_addr = 0x1E000000;
+ window_size = 0x1000000;
+#endif
+ break;
+ case 1:
+ /* 128 Mbit devices */
+ flash_size = 0x1000000; /* 16MB per part */
+#if defined(DB1X00_BOTH_BANKS)
+ window_addr = 0x1C000000;
+ window_size = 0x4000000;
+ /* USERFS from 0x1C00 0000 to 0x1FC0 0000 */
+ db1x00_partitions[0].size = 0x3C00000;
+#elif defined(DB1X00_BOOT_ONLY)
+ window_addr = 0x1E000000;
+ window_size = 0x2000000;
+ /* USERFS from 0x1E00 0000 to 0x1FC0 0000 */
+ db1x00_partitions[0].size = 0x1C00000;
+#else /* USER ONLY */
+ window_addr = 0x1C000000;
+ window_size = 0x2000000;
+ /* USERFS from 0x1C00 0000 to 0x1DE00000 */
+ db1x00_partitions[0].size = 0x1DE0000;
+#endif
+ break;
+ case 2:
+ /* 256 Mbit devices */
+ flash_size = 0x4000000; /* 64MB per part */
+#if defined(DB1X00_BOTH_BANKS)
+ return 1;
+#elif defined(DB1X00_BOOT_ONLY)
+ /* Boot ROM flash bank only; no user bank */
+ window_addr = 0x1C000000;
+ window_size = 0x4000000;
+ /* USERFS from 0x1C00 0000 to 0x1FC00000 */
+ db1x00_partitions[0].size = 0x3C00000;
+#else /* USER ONLY */
+ return 1;
+#endif
+ break;
+ default:
+ return 1;
+ }
+ db1xxx_mtd_map.size = window_size;
+ db1xxx_mtd_map.bankwidth = flash_bankwidth;
+ db1xxx_mtd_map.phys = window_addr;
+ db1xxx_mtd_map.bankwidth = flash_bankwidth;
+ return 0;
+}
+
+int __init db1x00_mtd_init(void)
+{
+ struct mtd_partition *parts;
+ int nb_parts = 0;
+
+ if (setup_flash_params())
+ return -ENXIO;
+
+ /*
+ * Static partition definition selection
+ */
+ parts = db1x00_partitions;
+ nb_parts = NB_OF(db1x00_partitions);
+
+ /*
+ * Now let's probe for the actual flash. Do it here since
+ * specific machine settings might have been set above.
+ */
+ printk(KERN_NOTICE "Db1xxx flash: probing %d-bit flash bus\n",
+ db1xxx_mtd_map.bankwidth*8);
+ db1xxx_mtd_map.virt = ioremap(window_addr, window_size);
+ db1xxx_mtd = do_map_probe("cfi_probe", &db1xxx_mtd_map);
+ if (!db1xxx_mtd) return -ENXIO;
+ db1xxx_mtd->owner = THIS_MODULE;
+
+ add_mtd_partitions(db1xxx_mtd, parts, nb_parts);
+ return 0;
+}
+
+static void __exit db1x00_mtd_cleanup(void)
+{
+ if (db1xxx_mtd) {
+ del_mtd_partitions(db1xxx_mtd);
+ map_destroy(db1xxx_mtd);
+ if (parsed_parts)
+ kfree(parsed_parts);
+ }
+}
+
+module_init(db1x00_mtd_init);
+module_exit(db1x00_mtd_cleanup);
+
+MODULE_AUTHOR("Pete Popov");
+MODULE_DESCRIPTION("Db1x00 mtd map driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/maps/dbox2-flash.c b/drivers/mtd/maps/dbox2-flash.c
new file mode 100644
index 000000000000..d850a27a4b59
--- /dev/null
+++ b/drivers/mtd/maps/dbox2-flash.c
@@ -0,0 +1,126 @@
+/*
+ * $Id: dbox2-flash.c,v 1.13 2004/11/04 13:24:14 gleixner Exp $
+ *
+ * D-Box 2 flash driver
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/config.h>
+#include <linux/errno.h>
+
+/* partition_info gives details on the logical partitions that the split the
+ * single flash device into. If the size if zero we use up to the end of the
+ * device. */
+static struct mtd_partition partition_info[]= {
+ {
+ .name = "BR bootloader",
+ .size = 128 * 1024,
+ .offset = 0,
+ .mask_flags = MTD_WRITEABLE
+ },
+ {
+ .name = "FLFS (U-Boot)",
+ .size = 128 * 1024,
+ .offset = MTDPART_OFS_APPEND,
+ .mask_flags = 0
+ },
+ {
+ .name = "Root (SquashFS)",
+ .size = 7040 * 1024,
+ .offset = MTDPART_OFS_APPEND,
+ .mask_flags = 0
+ },
+ {
+ .name = "var (JFFS2)",
+ .size = 896 * 1024,
+ .offset = MTDPART_OFS_APPEND,
+ .mask_flags = 0
+ },
+ {
+ .name = "Flash without bootloader",
+ .size = MTDPART_SIZ_FULL,
+ .offset = 128 * 1024,
+ .mask_flags = 0
+ },
+ {
+ .name = "Complete Flash",
+ .size = MTDPART_SIZ_FULL,
+ .offset = 0,
+ .mask_flags = MTD_WRITEABLE
+ }
+};
+
+#define NUM_PARTITIONS (sizeof(partition_info) / sizeof(partition_info[0]))
+
+#define WINDOW_ADDR 0x10000000
+#define WINDOW_SIZE 0x800000
+
+static struct mtd_info *mymtd;
+
+
+struct map_info dbox2_flash_map = {
+ .name = "D-Box 2 flash memory",
+ .size = WINDOW_SIZE,
+ .bankwidth = 4,
+ .phys = WINDOW_ADDR,
+};
+
+int __init init_dbox2_flash(void)
+{
+ printk(KERN_NOTICE "D-Box 2 flash driver (size->0x%X mem->0x%X)\n", WINDOW_SIZE, WINDOW_ADDR);
+ dbox2_flash_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE);
+
+ if (!dbox2_flash_map.virt) {
+ printk("Failed to ioremap\n");
+ return -EIO;
+ }
+ simple_map_init(&dbox2_flash_map);
+
+ // Probe for dual Intel 28F320 or dual AMD
+ mymtd = do_map_probe("cfi_probe", &dbox2_flash_map);
+ if (!mymtd) {
+ // Probe for single Intel 28F640
+ dbox2_flash_map.bankwidth = 2;
+
+ mymtd = do_map_probe("cfi_probe", &dbox2_flash_map);
+ }
+
+ if (mymtd) {
+ mymtd->owner = THIS_MODULE;
+
+ /* Create MTD devices for each partition. */
+ add_mtd_partitions(mymtd, partition_info, NUM_PARTITIONS);
+
+ return 0;
+ }
+
+ iounmap((void *)dbox2_flash_map.virt);
+ return -ENXIO;
+}
+
+static void __exit cleanup_dbox2_flash(void)
+{
+ if (mymtd) {
+ del_mtd_partitions(mymtd);
+ map_destroy(mymtd);
+ }
+ if (dbox2_flash_map.virt) {
+ iounmap((void *)dbox2_flash_map.virt);
+ dbox2_flash_map.virt = 0;
+ }
+}
+
+module_init(init_dbox2_flash);
+module_exit(cleanup_dbox2_flash);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kri Davsson <kd@flaga.is>, Bastian Blank <waldi@tuxbox.org>, Alexander Wild <wild@te-elektronik.com>");
+MODULE_DESCRIPTION("MTD map driver for D-Box 2 board");
diff --git a/drivers/mtd/maps/dc21285.c b/drivers/mtd/maps/dc21285.c
new file mode 100644
index 000000000000..938c41f2f056
--- /dev/null
+++ b/drivers/mtd/maps/dc21285.c
@@ -0,0 +1,253 @@
+/*
+ * MTD map driver for flash on the DC21285 (the StrongARM-110 companion chip)
+ *
+ * (C) 2000 Nicolas Pitre <nico@cam.org>
+ *
+ * This code is GPL
+ *
+ * $Id: dc21285.c,v 1.22 2004/11/01 13:39:21 rmk Exp $
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/io.h>
+#include <asm/hardware/dec21285.h>
+#include <asm/mach-types.h>
+
+
+static struct mtd_info *dc21285_mtd;
+
+#ifdef CONFIG_ARCH_NETWINDER
+/*
+ * This is really ugly, but it seams to be the only
+ * realiable way to do it, as the cpld state machine
+ * is unpredictible. So we have a 25us penalty per
+ * write access.
+ */
+static void nw_en_write(void)
+{
+ extern spinlock_t gpio_lock;
+ unsigned long flags;
+
+ /*
+ * we want to write a bit pattern XXX1 to Xilinx to enable
+ * the write gate, which will be open for about the next 2ms.
+ */
+ spin_lock_irqsave(&gpio_lock, flags);
+ cpld_modify(1, 1);
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ /*
+ * let the ISA bus to catch on...
+ */
+ udelay(25);
+}
+#else
+#define nw_en_write() do { } while (0)
+#endif
+
+static map_word dc21285_read8(struct map_info *map, unsigned long ofs)
+{
+ map_word val;
+ val.x[0] = *(uint8_t*)(map->virt + ofs);
+ return val;
+}
+
+static map_word dc21285_read16(struct map_info *map, unsigned long ofs)
+{
+ map_word val;
+ val.x[0] = *(uint16_t*)(map->virt + ofs);
+ return val;
+}
+
+static map_word dc21285_read32(struct map_info *map, unsigned long ofs)
+{
+ map_word val;
+ val.x[0] = *(uint32_t*)(map->virt + ofs);
+ return val;
+}
+
+static void dc21285_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len)
+{
+ memcpy(to, (void*)(map->virt + from), len);
+}
+
+static void dc21285_write8(struct map_info *map, const map_word d, unsigned long adr)
+{
+ if (machine_is_netwinder())
+ nw_en_write();
+ *CSR_ROMWRITEREG = adr & 3;
+ adr &= ~3;
+ *(uint8_t*)(map->virt + adr) = d.x[0];
+}
+
+static void dc21285_write16(struct map_info *map, const map_word d, unsigned long adr)
+{
+ if (machine_is_netwinder())
+ nw_en_write();
+ *CSR_ROMWRITEREG = adr & 3;
+ adr &= ~3;
+ *(uint16_t*)(map->virt + adr) = d.x[0];
+}
+
+static void dc21285_write32(struct map_info *map, const map_word d, unsigned long adr)
+{
+ if (machine_is_netwinder())
+ nw_en_write();
+ *(uint32_t*)(map->virt + adr) = d.x[0];
+}
+
+static void dc21285_copy_to_32(struct map_info *map, unsigned long to, const void *from, ssize_t len)
+{
+ while (len > 0) {
+ map_word d;
+ d.x[0] = *((uint32_t*)from)++;
+ dc21285_write32(map, d, to);
+ to += 4;
+ len -= 4;
+ }
+}
+
+static void dc21285_copy_to_16(struct map_info *map, unsigned long to, const void *from, ssize_t len)
+{
+ while (len > 0) {
+ map_word d;
+ d.x[0] = *((uint16_t*)from)++;
+ dc21285_write16(map, d, to);
+ to += 2;
+ len -= 2;
+ }
+}
+
+static void dc21285_copy_to_8(struct map_info *map, unsigned long to, const void *from, ssize_t len)
+{
+ map_word d;
+ d.x[0] = *((uint8_t*)from)++;
+ dc21285_write8(map, d, to);
+ to++;
+ len--;
+}
+
+static struct map_info dc21285_map = {
+ .name = "DC21285 flash",
+ .phys = NO_XIP,
+ .size = 16*1024*1024,
+ .copy_from = dc21285_copy_from,
+};
+
+
+/* Partition stuff */
+#ifdef CONFIG_MTD_PARTITIONS
+static struct mtd_partition *dc21285_parts;
+static const char *probes[] = { "RedBoot", "cmdlinepart", NULL };
+#endif
+
+static int __init init_dc21285(void)
+{
+
+#ifdef CONFIG_MTD_PARTITIONS
+ int nrparts;
+#endif
+
+ /* Determine bankwidth */
+ switch (*CSR_SA110_CNTL & (3<<14)) {
+ case SA110_CNTL_ROMWIDTH_8:
+ dc21285_map.bankwidth = 1;
+ dc21285_map.read = dc21285_read8;
+ dc21285_map.write = dc21285_write8;
+ dc21285_map.copy_to = dc21285_copy_to_8;
+ break;
+ case SA110_CNTL_ROMWIDTH_16:
+ dc21285_map.bankwidth = 2;
+ dc21285_map.read = dc21285_read16;
+ dc21285_map.write = dc21285_write16;
+ dc21285_map.copy_to = dc21285_copy_to_16;
+ break;
+ case SA110_CNTL_ROMWIDTH_32:
+ dc21285_map.bankwidth = 4;
+ dc21285_map.read = dc21285_read32;
+ dc21285_map.write = dc21285_write32;
+ dc21285_map.copy_to = dc21285_copy_to_32;
+ break;
+ default:
+ printk (KERN_ERR "DC21285 flash: undefined bankwidth\n");
+ return -ENXIO;
+ }
+ printk (KERN_NOTICE "DC21285 flash support (%d-bit bankwidth)\n",
+ dc21285_map.bankwidth*8);
+
+ /* Let's map the flash area */
+ dc21285_map.virt = ioremap(DC21285_FLASH, 16*1024*1024);
+ if (!dc21285_map.virt) {
+ printk("Failed to ioremap\n");
+ return -EIO;
+ }
+
+ if (machine_is_ebsa285()) {
+ dc21285_mtd = do_map_probe("cfi_probe", &dc21285_map);
+ } else {
+ dc21285_mtd = do_map_probe("jedec_probe", &dc21285_map);
+ }
+
+ if (!dc21285_mtd) {
+ iounmap(dc21285_map.virt);
+ return -ENXIO;
+ }
+
+ dc21285_mtd->owner = THIS_MODULE;
+
+#ifdef CONFIG_MTD_PARTITIONS
+ nrparts = parse_mtd_partitions(dc21285_mtd, probes, &dc21285_parts, 0);
+ if (nrparts > 0)
+ add_mtd_partitions(dc21285_mtd, dc21285_parts, nrparts);
+ else
+#endif
+ add_mtd_device(dc21285_mtd);
+
+ if(machine_is_ebsa285()) {
+ /*
+ * Flash timing is determined with bits 19-16 of the
+ * CSR_SA110_CNTL. The value is the number of wait cycles, or
+ * 0 for 16 cycles (the default). Cycles are 20 ns.
+ * Here we use 7 for 140 ns flash chips.
+ */
+ /* access time */
+ *CSR_SA110_CNTL = ((*CSR_SA110_CNTL & ~0x000f0000) | (7 << 16));
+ /* burst time */
+ *CSR_SA110_CNTL = ((*CSR_SA110_CNTL & ~0x00f00000) | (7 << 20));
+ /* tristate time */
+ *CSR_SA110_CNTL = ((*CSR_SA110_CNTL & ~0x0f000000) | (7 << 24));
+ }
+
+ return 0;
+}
+
+static void __exit cleanup_dc21285(void)
+{
+#ifdef CONFIG_MTD_PARTITIONS
+ if (dc21285_parts) {
+ del_mtd_partitions(dc21285_mtd);
+ kfree(dc21285_parts);
+ } else
+#endif
+ del_mtd_device(dc21285_mtd);
+
+ map_destroy(dc21285_mtd);
+ iounmap(dc21285_map.virt);
+}
+
+module_init(init_dc21285);
+module_exit(cleanup_dc21285);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nicolas Pitre <nico@cam.org>");
+MODULE_DESCRIPTION("MTD map driver for DC21285 boards");
diff --git a/drivers/mtd/maps/dilnetpc.c b/drivers/mtd/maps/dilnetpc.c
new file mode 100644
index 000000000000..0bc79c93a584
--- /dev/null
+++ b/drivers/mtd/maps/dilnetpc.c
@@ -0,0 +1,495 @@
+/* dilnetpc.c -- MTD map driver for SSV DIL/Net PC Boards "DNP" and "ADNP"
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * $Id: dilnetpc.c,v 1.17 2004/11/28 09:40:39 dwmw2 Exp $
+ *
+ * The DIL/Net PC is a tiny embedded PC board made by SSV Embedded Systems
+ * featuring the AMD Elan SC410 processor. There are two variants of this
+ * board: DNP/1486 and ADNP/1486. The DNP version has 2 megs of flash
+ * ROM (Intel 28F016S3) and 8 megs of DRAM, the ADNP version has 4 megs
+ * flash and 16 megs of RAM.
+ * For details, see http://www.ssv-embedded.de/ssv/pc104/p169.htm
+ * and http://www.ssv-embedded.de/ssv/pc104/p170.htm
+ */
+
+#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 <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/concat.h>
+
+/*
+** The DIL/NetPC keeps its BIOS in two distinct flash blocks.
+** Destroying any of these blocks transforms the DNPC into
+** a paperweight (albeit not a very useful one, considering
+** it only weighs a few grams).
+**
+** Therefore, the BIOS blocks must never be erased or written to
+** except by people who know exactly what they are doing (e.g.
+** to install a BIOS update). These partitions are marked read-only
+** by default, but can be made read/write by undefining
+** DNPC_BIOS_BLOCKS_WRITEPROTECTED:
+*/
+#define DNPC_BIOS_BLOCKS_WRITEPROTECTED
+
+/*
+** The ID string (in ROM) is checked to determine whether we
+** are running on a DNP/1486 or ADNP/1486
+*/
+#define BIOSID_BASE 0x000fe100
+
+#define ID_DNPC "DNP1486"
+#define ID_ADNP "ADNP1486"
+
+/*
+** Address where the flash should appear in CPU space
+*/
+#define FLASH_BASE 0x2000000
+
+/*
+** Chip Setup and Control (CSC) indexed register space
+*/
+#define CSC_INDEX 0x22
+#define CSC_DATA 0x23
+
+#define CSC_MMSWAR 0x30 /* MMS window C-F attributes register */
+#define CSC_MMSWDSR 0x31 /* MMS window C-F device select register */
+
+#define CSC_RBWR 0xa7 /* GPIO Read-Back/Write Register B */
+
+#define CSC_CR 0xd0 /* internal I/O device disable/Echo */
+ /* Z-bus/configuration register */
+
+#define CSC_PCCMDCR 0xf1 /* PC card mode and DMA control register */
+
+
+/*
+** PC Card indexed register space:
+*/
+
+#define PCC_INDEX 0x3e0
+#define PCC_DATA 0x3e1
+
+#define PCC_AWER_B 0x46 /* Socket B Address Window enable register */
+#define PCC_MWSAR_1_Lo 0x58 /* memory window 1 start address low register */
+#define PCC_MWSAR_1_Hi 0x59 /* memory window 1 start address high register */
+#define PCC_MWEAR_1_Lo 0x5A /* memory window 1 stop address low register */
+#define PCC_MWEAR_1_Hi 0x5B /* memory window 1 stop address high register */
+#define PCC_MWAOR_1_Lo 0x5C /* memory window 1 address offset low register */
+#define PCC_MWAOR_1_Hi 0x5D /* memory window 1 address offset high register */
+
+
+/*
+** Access to SC4x0's Chip Setup and Control (CSC)
+** and PC Card (PCC) indexed registers:
+*/
+static inline void setcsc(int reg, unsigned char data)
+{
+ outb(reg, CSC_INDEX);
+ outb(data, CSC_DATA);
+}
+
+static inline unsigned char getcsc(int reg)
+{
+ outb(reg, CSC_INDEX);
+ return(inb(CSC_DATA));
+}
+
+static inline void setpcc(int reg, unsigned char data)
+{
+ outb(reg, PCC_INDEX);
+ outb(data, PCC_DATA);
+}
+
+static inline unsigned char getpcc(int reg)
+{
+ outb(reg, PCC_INDEX);
+ return(inb(PCC_DATA));
+}
+
+
+/*
+************************************************************
+** Enable access to DIL/NetPC's flash by mapping it into
+** the SC4x0's MMS Window C.
+************************************************************
+*/
+static void dnpc_map_flash(unsigned long flash_base, unsigned long flash_size)
+{
+ unsigned long flash_end = flash_base + flash_size - 1;
+
+ /*
+ ** enable setup of MMS windows C-F:
+ */
+ /* - enable PC Card indexed register space */
+ setcsc(CSC_CR, getcsc(CSC_CR) | 0x2);
+ /* - set PC Card controller to operate in standard mode */
+ setcsc(CSC_PCCMDCR, getcsc(CSC_PCCMDCR) & ~1);
+
+ /*
+ ** Program base address and end address of window
+ ** where the flash ROM should appear in CPU address space
+ */
+ setpcc(PCC_MWSAR_1_Lo, (flash_base >> 12) & 0xff);
+ setpcc(PCC_MWSAR_1_Hi, (flash_base >> 20) & 0x3f);
+ setpcc(PCC_MWEAR_1_Lo, (flash_end >> 12) & 0xff);
+ setpcc(PCC_MWEAR_1_Hi, (flash_end >> 20) & 0x3f);
+
+ /* program offset of first flash location to appear in this window (0) */
+ setpcc(PCC_MWAOR_1_Lo, ((0 - flash_base) >> 12) & 0xff);
+ setpcc(PCC_MWAOR_1_Hi, ((0 - flash_base)>> 20) & 0x3f);
+
+ /* set attributes for MMS window C: non-cacheable, write-enabled */
+ setcsc(CSC_MMSWAR, getcsc(CSC_MMSWAR) & ~0x11);
+
+ /* select physical device ROMCS0 (i.e. flash) for MMS Window C */
+ setcsc(CSC_MMSWDSR, getcsc(CSC_MMSWDSR) & ~0x03);
+
+ /* enable memory window 1 */
+ setpcc(PCC_AWER_B, getpcc(PCC_AWER_B) | 0x02);
+
+ /* now disable PC Card indexed register space again */
+ setcsc(CSC_CR, getcsc(CSC_CR) & ~0x2);
+}
+
+
+/*
+************************************************************
+** Disable access to DIL/NetPC's flash by mapping it into
+** the SC4x0's MMS Window C.
+************************************************************
+*/
+static void dnpc_unmap_flash(void)
+{
+ /* - enable PC Card indexed register space */
+ setcsc(CSC_CR, getcsc(CSC_CR) | 0x2);
+
+ /* disable memory window 1 */
+ setpcc(PCC_AWER_B, getpcc(PCC_AWER_B) & ~0x02);
+
+ /* now disable PC Card indexed register space again */
+ setcsc(CSC_CR, getcsc(CSC_CR) & ~0x2);
+}
+
+
+
+/*
+************************************************************
+** Enable/Disable VPP to write to flash
+************************************************************
+*/
+
+static DEFINE_SPINLOCK(dnpc_spin);
+static int vpp_counter = 0;
+/*
+** This is what has to be done for the DNP board ..
+*/
+static void dnp_set_vpp(struct map_info *not_used, int on)
+{
+ spin_lock_irq(&dnpc_spin);
+
+ if (on)
+ {
+ if(++vpp_counter == 1)
+ setcsc(CSC_RBWR, getcsc(CSC_RBWR) & ~0x4);
+ }
+ else
+ {
+ if(--vpp_counter == 0)
+ setcsc(CSC_RBWR, getcsc(CSC_RBWR) | 0x4);
+ else if(vpp_counter < 0)
+ BUG();
+ }
+ spin_unlock_irq(&dnpc_spin);
+}
+
+/*
+** .. and this the ADNP version:
+*/
+static void adnp_set_vpp(struct map_info *not_used, int on)
+{
+ spin_lock_irq(&dnpc_spin);
+
+ if (on)
+ {
+ if(++vpp_counter == 1)
+ setcsc(CSC_RBWR, getcsc(CSC_RBWR) & ~0x8);
+ }
+ else
+ {
+ if(--vpp_counter == 0)
+ setcsc(CSC_RBWR, getcsc(CSC_RBWR) | 0x8);
+ else if(vpp_counter < 0)
+ BUG();
+ }
+ spin_unlock_irq(&dnpc_spin);
+}
+
+
+
+#define DNP_WINDOW_SIZE 0x00200000 /* DNP flash size is 2MiB */
+#define ADNP_WINDOW_SIZE 0x00400000 /* ADNP flash size is 4MiB */
+#define WINDOW_ADDR FLASH_BASE
+
+static struct map_info dnpc_map = {
+ .name = "ADNP Flash Bank",
+ .size = ADNP_WINDOW_SIZE,
+ .bankwidth = 1,
+ .set_vpp = adnp_set_vpp,
+ .phys = WINDOW_ADDR
+};
+
+/*
+** The layout of the flash is somewhat "strange":
+**
+** 1. 960 KiB (15 blocks) : Space for ROM Bootloader and user data
+** 2. 64 KiB (1 block) : System BIOS
+** 3. 960 KiB (15 blocks) : User Data (DNP model) or
+** 3. 3008 KiB (47 blocks) : User Data (ADNP model)
+** 4. 64 KiB (1 block) : System BIOS Entry
+*/
+
+static struct mtd_partition partition_info[]=
+{
+ {
+ .name = "ADNP boot",
+ .offset = 0,
+ .size = 0xf0000,
+ },
+ {
+ .name = "ADNP system BIOS",
+ .offset = MTDPART_OFS_NXTBLK,
+ .size = 0x10000,
+#ifdef DNPC_BIOS_BLOCKS_WRITEPROTECTED
+ .mask_flags = MTD_WRITEABLE,
+#endif
+ },
+ {
+ .name = "ADNP file system",
+ .offset = MTDPART_OFS_NXTBLK,
+ .size = 0x2f0000,
+ },
+ {
+ .name = "ADNP system BIOS entry",
+ .offset = MTDPART_OFS_NXTBLK,
+ .size = MTDPART_SIZ_FULL,
+#ifdef DNPC_BIOS_BLOCKS_WRITEPROTECTED
+ .mask_flags = MTD_WRITEABLE,
+#endif
+ },
+};
+
+#define NUM_PARTITIONS (sizeof(partition_info)/sizeof(partition_info[0]))
+
+static struct mtd_info *mymtd;
+static struct mtd_info *lowlvl_parts[NUM_PARTITIONS];
+static struct mtd_info *merged_mtd;
+
+/*
+** "Highlevel" partition info:
+**
+** Using the MTD concat layer, we can re-arrange partitions to our
+** liking: we construct a virtual MTD device by concatenating the
+** partitions, specifying the sequence such that the boot block
+** is immediately followed by the filesystem block (i.e. the stupid
+** system BIOS block is mapped to a different place). When re-partitioning
+** this concatenated MTD device, we can set the boot block size to
+** an arbitrary (though erase block aligned) value i.e. not one that
+** is dictated by the flash's physical layout. We can thus set the
+** boot block to be e.g. 64 KB (which is fully sufficient if we want
+** to boot an etherboot image) or to -say- 1.5 MB if we want to boot
+** a large kernel image. In all cases, the remainder of the flash
+** is available as file system space.
+*/
+
+static struct mtd_partition higlvl_partition_info[]=
+{
+ {
+ .name = "ADNP boot block",
+ .offset = 0,
+ .size = CONFIG_MTD_DILNETPC_BOOTSIZE,
+ },
+ {
+ .name = "ADNP file system space",
+ .offset = MTDPART_OFS_NXTBLK,
+ .size = ADNP_WINDOW_SIZE-CONFIG_MTD_DILNETPC_BOOTSIZE-0x20000,
+ },
+ {
+ .name = "ADNP system BIOS + BIOS Entry",
+ .offset = MTDPART_OFS_NXTBLK,
+ .size = MTDPART_SIZ_FULL,
+#ifdef DNPC_BIOS_BLOCKS_WRITEPROTECTED
+ .mask_flags = MTD_WRITEABLE,
+#endif
+ },
+};
+
+#define NUM_HIGHLVL_PARTITIONS (sizeof(higlvl_partition_info)/sizeof(partition_info[0]))
+
+
+static int dnp_adnp_probe(void)
+{
+ char *biosid, rc = -1;
+
+ biosid = (char*)ioremap(BIOSID_BASE, 16);
+ if(biosid)
+ {
+ if(!strcmp(biosid, ID_DNPC))
+ rc = 1; /* this is a DNPC */
+ else if(!strcmp(biosid, ID_ADNP))
+ rc = 0; /* this is a ADNPC */
+ }
+ iounmap((void *)biosid);
+ return(rc);
+}
+
+
+static int __init init_dnpc(void)
+{
+ int is_dnp;
+
+ /*
+ ** determine hardware (DNP/ADNP/invalid)
+ */
+ if((is_dnp = dnp_adnp_probe()) < 0)
+ return -ENXIO;
+
+ /*
+ ** Things are set up for ADNP by default
+ ** -> modify all that needs to be different for DNP
+ */
+ if(is_dnp)
+ { /*
+ ** Adjust window size, select correct set_vpp function.
+ ** The partitioning scheme is identical on both DNP
+ ** and ADNP except for the size of the third partition.
+ */
+ int i;
+ dnpc_map.size = DNP_WINDOW_SIZE;
+ dnpc_map.set_vpp = dnp_set_vpp;
+ partition_info[2].size = 0xf0000;
+
+ /*
+ ** increment all string pointers so the leading 'A' gets skipped,
+ ** thus turning all occurrences of "ADNP ..." into "DNP ..."
+ */
+ ++dnpc_map.name;
+ for(i = 0; i < NUM_PARTITIONS; i++)
+ ++partition_info[i].name;
+ higlvl_partition_info[1].size = DNP_WINDOW_SIZE -
+ CONFIG_MTD_DILNETPC_BOOTSIZE - 0x20000;
+ for(i = 0; i < NUM_HIGHLVL_PARTITIONS; i++)
+ ++higlvl_partition_info[i].name;
+ }
+
+ printk(KERN_NOTICE "DIL/Net %s flash: 0x%lx at 0x%lx\n",
+ is_dnp ? "DNPC" : "ADNP", dnpc_map.size, dnpc_map.phys);
+
+ dnpc_map.virt = ioremap_nocache(dnpc_map.phys, dnpc_map.size);
+
+ dnpc_map_flash(dnpc_map.phys, dnpc_map.size);
+
+ if (!dnpc_map.virt) {
+ printk("Failed to ioremap_nocache\n");
+ return -EIO;
+ }
+ simple_map_init(&dnpc_map);
+
+ printk("FLASH virtual address: 0x%p\n", dnpc_map.virt);
+
+ mymtd = do_map_probe("jedec_probe", &dnpc_map);
+
+ if (!mymtd)
+ mymtd = do_map_probe("cfi_probe", &dnpc_map);
+
+ /*
+ ** If flash probes fail, try to make flashes accessible
+ ** at least as ROM. Ajust erasesize in this case since
+ ** the default one (128M) will break our partitioning
+ */
+ if (!mymtd)
+ if((mymtd = do_map_probe("map_rom", &dnpc_map)))
+ mymtd->erasesize = 0x10000;
+
+ if (!mymtd) {
+ iounmap(dnpc_map.virt);
+ return -ENXIO;
+ }
+
+ mymtd->owner = THIS_MODULE;
+
+ /*
+ ** Supply pointers to lowlvl_parts[] array to add_mtd_partitions()
+ ** -> add_mtd_partitions() will _not_ register MTD devices for
+ ** the partitions, but will instead store pointers to the MTD
+ ** objects it creates into our lowlvl_parts[] array.
+ ** NOTE: we arrange the pointers such that the sequence of the
+ ** partitions gets re-arranged: partition #2 follows
+ ** partition #0.
+ */
+ partition_info[0].mtdp = &lowlvl_parts[0];
+ partition_info[1].mtdp = &lowlvl_parts[2];
+ partition_info[2].mtdp = &lowlvl_parts[1];
+ partition_info[3].mtdp = &lowlvl_parts[3];
+
+ add_mtd_partitions(mymtd, partition_info, NUM_PARTITIONS);
+
+ /*
+ ** now create a virtual MTD device by concatenating the for partitions
+ ** (in the sequence given by the lowlvl_parts[] array.
+ */
+ merged_mtd = mtd_concat_create(lowlvl_parts, NUM_PARTITIONS, "(A)DNP Flash Concatenated");
+ if(merged_mtd)
+ { /*
+ ** now partition the new device the way we want it. This time,
+ ** we do not supply mtd pointers in higlvl_partition_info, so
+ ** add_mtd_partitions() will register the devices.
+ */
+ add_mtd_partitions(merged_mtd, higlvl_partition_info, NUM_HIGHLVL_PARTITIONS);
+ }
+
+ return 0;
+}
+
+static void __exit cleanup_dnpc(void)
+{
+ if(merged_mtd) {
+ del_mtd_partitions(merged_mtd);
+ mtd_concat_destroy(merged_mtd);
+ }
+
+ if (mymtd) {
+ del_mtd_partitions(mymtd);
+ map_destroy(mymtd);
+ }
+ if (dnpc_map.virt) {
+ iounmap(dnpc_map.virt);
+ dnpc_unmap_flash();
+ dnpc_map.virt = NULL;
+ }
+}
+
+module_init(init_dnpc);
+module_exit(cleanup_dnpc);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sysgo Real-Time Solutions GmbH");
+MODULE_DESCRIPTION("MTD map driver for SSV DIL/NetPC DNP & ADNP");
diff --git a/drivers/mtd/maps/dmv182.c b/drivers/mtd/maps/dmv182.c
new file mode 100644
index 000000000000..b9bc63503e26
--- /dev/null
+++ b/drivers/mtd/maps/dmv182.c
@@ -0,0 +1,149 @@
+
+/*
+ * drivers/mtd/maps/svme182.c
+ *
+ * Flash map driver for the Dy4 SVME182 board
+ *
+ * $Id: dmv182.c,v 1.5 2004/11/04 13:24:14 gleixner Exp $
+ *
+ * Copyright 2003-2004, TimeSys Corporation
+ *
+ * Based on the SVME181 flash map, by Tom Nelson, Dot4, Inc. for TimeSys Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#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 <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/errno.h>
+
+/*
+ * This driver currently handles only the 16MiB user flash bank 1 on the
+ * board. It does not provide access to bank 0 (contains the Dy4 FFW), bank 2
+ * (VxWorks boot), or the optional 48MiB expansion flash.
+ *
+ * scott.wood@timesys.com: On the newer boards with 128MiB flash, it
+ * now supports the first 96MiB (the boot flash bank containing FFW
+ * is excluded). The VxWorks loader is in partition 1.
+ */
+
+#define FLASH_BASE_ADDR 0xf0000000
+#define FLASH_BANK_SIZE (128*1024*1024)
+
+MODULE_AUTHOR("Scott Wood, TimeSys Corporation <scott.wood@timesys.com>");
+MODULE_DESCRIPTION("User-programmable flash device on the Dy4 SVME182 board");
+MODULE_LICENSE("GPL");
+
+static struct map_info svme182_map = {
+ .name = "Dy4 SVME182",
+ .bankwidth = 32,
+ .size = 128 * 1024 * 1024
+};
+
+#define BOOTIMAGE_PART_SIZE ((6*1024*1024)-RESERVED_PART_SIZE)
+
+// Allow 6MiB for the kernel
+#define NEW_BOOTIMAGE_PART_SIZE (6 * 1024 * 1024)
+// Allow 1MiB for the bootloader
+#define NEW_BOOTLOADER_PART_SIZE (1024 * 1024)
+// Use the remaining 9MiB at the end of flash for the RFS
+#define NEW_RFS_PART_SIZE (0x01000000 - NEW_BOOTLOADER_PART_SIZE - \
+ NEW_BOOTIMAGE_PART_SIZE)
+
+static struct mtd_partition svme182_partitions[] = {
+ // The Lower PABS is only 128KiB, but the partition code doesn't
+ // like partitions that don't end on the largest erase block
+ // size of the device, even if all of the erase blocks in the
+ // partition are small ones. The hardware should prevent
+ // writes to the actual PABS areas.
+ {
+ name: "Lower PABS and CPU 0 bootloader or kernel",
+ size: 6*1024*1024,
+ offset: 0,
+ },
+ {
+ name: "Root Filesystem",
+ size: 10*1024*1024,
+ offset: MTDPART_OFS_NXTBLK
+ },
+ {
+ name: "CPU1 Bootloader",
+ size: 1024*1024,
+ offset: MTDPART_OFS_NXTBLK,
+ },
+ {
+ name: "Extra",
+ size: 110*1024*1024,
+ offset: MTDPART_OFS_NXTBLK
+ },
+ {
+ name: "Foundation Firmware and Upper PABS",
+ size: 1024*1024,
+ offset: MTDPART_OFS_NXTBLK,
+ mask_flags: MTD_WRITEABLE // read-only
+ }
+};
+
+static struct mtd_info *this_mtd;
+
+static int __init init_svme182(void)
+{
+ struct mtd_partition *partitions;
+ int num_parts = sizeof(svme182_partitions) / sizeof(struct mtd_partition);
+
+ partitions = svme182_partitions;
+
+ svme182_map.virt = ioremap(FLASH_BASE_ADDR, svme182_map.size);
+
+ if (svme182_map.virt == 0) {
+ printk("Failed to ioremap FLASH memory area.\n");
+ return -EIO;
+ }
+
+ simple_map_init(&svme182_map);
+
+ this_mtd = do_map_probe("cfi_probe", &svme182_map);
+ if (!this_mtd)
+ {
+ iounmap((void *)svme182_map.virt);
+ return -ENXIO;
+ }
+
+ printk(KERN_NOTICE "SVME182 flash device: %dMiB at 0x%08x\n",
+ this_mtd->size >> 20, FLASH_BASE_ADDR);
+
+ this_mtd->owner = THIS_MODULE;
+ add_mtd_partitions(this_mtd, partitions, num_parts);
+
+ return 0;
+}
+
+static void __exit cleanup_svme182(void)
+{
+ if (this_mtd)
+ {
+ del_mtd_partitions(this_mtd);
+ map_destroy(this_mtd);
+ }
+
+ if (svme182_map.virt)
+ {
+ iounmap((void *)svme182_map.virt);
+ svme182_map.virt = 0;
+ }
+
+ return;
+}
+
+module_init(init_svme182);
+module_exit(cleanup_svme182);
diff --git a/drivers/mtd/maps/ebony.c b/drivers/mtd/maps/ebony.c
new file mode 100644
index 000000000000..b9d9cf4854b6
--- /dev/null
+++ b/drivers/mtd/maps/ebony.c
@@ -0,0 +1,163 @@
+/*
+ * $Id: ebony.c,v 1.15 2004/12/09 18:39:54 holindho Exp $
+ *
+ * Mapping for Ebony user flash
+ *
+ * Matt Porter <mporter@kernel.crashing.org>
+ *
+ * Copyright 2002-2004 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/config.h>
+#include <linux/version.h>
+#include <asm/io.h>
+#include <asm/ibm44x.h>
+#include <platforms/4xx/ebony.h>
+
+static struct mtd_info *flash;
+
+static struct map_info ebony_small_map = {
+ .name = "Ebony small flash",
+ .size = EBONY_SMALL_FLASH_SIZE,
+ .bankwidth = 1,
+};
+
+static struct map_info ebony_large_map = {
+ .name = "Ebony large flash",
+ .size = EBONY_LARGE_FLASH_SIZE,
+ .bankwidth = 1,
+};
+
+static struct mtd_partition ebony_small_partitions[] = {
+ {
+ .name = "OpenBIOS",
+ .offset = 0x0,
+ .size = 0x80000,
+ }
+};
+
+static struct mtd_partition ebony_large_partitions[] = {
+ {
+ .name = "fs",
+ .offset = 0,
+ .size = 0x380000,
+ },
+ {
+ .name = "firmware",
+ .offset = 0x380000,
+ .size = 0x80000,
+ }
+};
+
+int __init init_ebony(void)
+{
+ u8 fpga0_reg;
+ u8 __iomem *fpga0_adr;
+ unsigned long long small_flash_base, large_flash_base;
+
+ fpga0_adr = ioremap64(EBONY_FPGA_ADDR, 16);
+ if (!fpga0_adr)
+ return -ENOMEM;
+
+ fpga0_reg = readb(fpga0_adr);
+ iounmap(fpga0_adr);
+
+ if (EBONY_BOOT_SMALL_FLASH(fpga0_reg) &&
+ !EBONY_FLASH_SEL(fpga0_reg))
+ small_flash_base = EBONY_SMALL_FLASH_HIGH2;
+ else if (EBONY_BOOT_SMALL_FLASH(fpga0_reg) &&
+ EBONY_FLASH_SEL(fpga0_reg))
+ small_flash_base = EBONY_SMALL_FLASH_HIGH1;
+ else if (!EBONY_BOOT_SMALL_FLASH(fpga0_reg) &&
+ !EBONY_FLASH_SEL(fpga0_reg))
+ small_flash_base = EBONY_SMALL_FLASH_LOW2;
+ else
+ small_flash_base = EBONY_SMALL_FLASH_LOW1;
+
+ if (EBONY_BOOT_SMALL_FLASH(fpga0_reg) &&
+ !EBONY_ONBRD_FLASH_EN(fpga0_reg))
+ large_flash_base = EBONY_LARGE_FLASH_LOW;
+ else
+ large_flash_base = EBONY_LARGE_FLASH_HIGH;
+
+ ebony_small_map.phys = small_flash_base;
+ ebony_small_map.virt = ioremap64(small_flash_base,
+ ebony_small_map.size);
+
+ if (!ebony_small_map.virt) {
+ printk("Failed to ioremap flash\n");
+ return -EIO;
+ }
+
+ simple_map_init(&ebony_small_map);
+
+ flash = do_map_probe("jedec_probe", &ebony_small_map);
+ if (flash) {
+ flash->owner = THIS_MODULE;
+ add_mtd_partitions(flash, ebony_small_partitions,
+ ARRAY_SIZE(ebony_small_partitions));
+ } else {
+ printk("map probe failed for flash\n");
+ return -ENXIO;
+ }
+
+ ebony_large_map.phys = large_flash_base;
+ ebony_large_map.virt = ioremap64(large_flash_base,
+ ebony_large_map.size);
+
+ if (!ebony_large_map.virt) {
+ printk("Failed to ioremap flash\n");
+ return -EIO;
+ }
+
+ simple_map_init(&ebony_large_map);
+
+ flash = do_map_probe("jedec_probe", &ebony_large_map);
+ if (flash) {
+ flash->owner = THIS_MODULE;
+ add_mtd_partitions(flash, ebony_large_partitions,
+ ARRAY_SIZE(ebony_large_partitions));
+ } else {
+ printk("map probe failed for flash\n");
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static void __exit cleanup_ebony(void)
+{
+ if (flash) {
+ del_mtd_partitions(flash);
+ map_destroy(flash);
+ }
+
+ if (ebony_small_map.virt) {
+ iounmap(ebony_small_map.virt);
+ ebony_small_map.virt = NULL;
+ }
+
+ if (ebony_large_map.virt) {
+ iounmap(ebony_large_map.virt);
+ ebony_large_map.virt = NULL;
+ }
+}
+
+module_init(init_ebony);
+module_exit(cleanup_ebony);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Matt Porter <mporter@kernel.crashing.org>");
+MODULE_DESCRIPTION("MTD map and partitions for IBM 440GP Ebony boards");
diff --git a/drivers/mtd/maps/edb7312.c b/drivers/mtd/maps/edb7312.c
new file mode 100644
index 000000000000..8b0da394f3fa
--- /dev/null
+++ b/drivers/mtd/maps/edb7312.c
@@ -0,0 +1,147 @@
+/*
+ * $Id: edb7312.c,v 1.13 2004/11/04 13:24:14 gleixner Exp $
+ *
+ * Handle mapping of the NOR flash on Cogent EDB7312 boards
+ *
+ * Copyright 2002 SYSGO Real-Time Solutions GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/config.h>
+
+#ifdef CONFIG_MTD_PARTITIONS
+#include <linux/mtd/partitions.h>
+#endif
+
+#define WINDOW_ADDR 0x00000000 /* physical properties of flash */
+#define WINDOW_SIZE 0x01000000
+#define BUSWIDTH 2
+#define FLASH_BLOCKSIZE_MAIN 0x20000
+#define FLASH_NUMBLOCKS_MAIN 128
+/* can be "cfi_probe", "jedec_probe", "map_rom", NULL }; */
+#define PROBETYPES { "cfi_probe", NULL }
+
+#define MSG_PREFIX "EDB7312-NOR:" /* prefix for our printk()'s */
+#define MTDID "edb7312-nor" /* for mtdparts= partitioning */
+
+static struct mtd_info *mymtd;
+
+struct map_info edb7312nor_map = {
+ .name = "NOR flash on EDB7312",
+ .size = WINDOW_SIZE,
+ .bankwidth = BUSWIDTH,
+ .phys = WINDOW_ADDR,
+};
+
+#ifdef CONFIG_MTD_PARTITIONS
+
+/*
+ * MTD partitioning stuff
+ */
+static struct mtd_partition static_partitions[3] =
+{
+ {
+ .name = "ARMboot",
+ .size = 0x40000,
+ .offset = 0
+ },
+ {
+ .name = "Kernel",
+ .size = 0x200000,
+ .offset = 0x40000
+ },
+ {
+ .name = "RootFS",
+ .size = 0xDC0000,
+ .offset = 0x240000
+ },
+};
+
+static const char *probes[] = { "RedBoot", "cmdlinepart", NULL };
+
+#endif
+
+static int mtd_parts_nb = 0;
+static struct mtd_partition *mtd_parts = 0;
+
+int __init init_edb7312nor(void)
+{
+ static const char *rom_probe_types[] = PROBETYPES;
+ const char **type;
+ const char *part_type = 0;
+
+ printk(KERN_NOTICE MSG_PREFIX "0x%08x at 0x%08x\n",
+ WINDOW_SIZE, WINDOW_ADDR);
+ edb7312nor_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE);
+
+ if (!edb7312nor_map.virt) {
+ printk(MSG_PREFIX "failed to ioremap\n");
+ return -EIO;
+ }
+
+ simple_map_init(&edb7312nor_map);
+
+ mymtd = 0;
+ type = rom_probe_types;
+ for(; !mymtd && *type; type++) {
+ mymtd = do_map_probe(*type, &edb7312nor_map);
+ }
+ if (mymtd) {
+ mymtd->owner = THIS_MODULE;
+
+#ifdef CONFIG_MTD_PARTITIONS
+ mtd_parts_nb = parse_mtd_partitions(mymtd, probes, &mtd_parts, MTDID);
+ if (mtd_parts_nb > 0)
+ part_type = "detected";
+
+ if (mtd_parts_nb == 0)
+ {
+ mtd_parts = static_partitions;
+ mtd_parts_nb = ARRAY_SIZE(static_partitions);
+ part_type = "static";
+ }
+#endif
+ add_mtd_device(mymtd);
+ if (mtd_parts_nb == 0)
+ printk(KERN_NOTICE MSG_PREFIX "no partition info available\n");
+ else
+ {
+ printk(KERN_NOTICE MSG_PREFIX
+ "using %s partition definition\n", part_type);
+ add_mtd_partitions(mymtd, mtd_parts, mtd_parts_nb);
+ }
+ return 0;
+ }
+
+ iounmap((void *)edb7312nor_map.virt);
+ return -ENXIO;
+}
+
+static void __exit cleanup_edb7312nor(void)
+{
+ if (mymtd) {
+ del_mtd_device(mymtd);
+ map_destroy(mymtd);
+ }
+ if (edb7312nor_map.virt) {
+ iounmap((void *)edb7312nor_map.virt);
+ edb7312nor_map.virt = 0;
+ }
+}
+
+module_init(init_edb7312nor);
+module_exit(cleanup_edb7312nor);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Marius Groeger <mag@sysgo.de>");
+MODULE_DESCRIPTION("Generic configurable MTD map driver");
diff --git a/drivers/mtd/maps/elan-104nc.c b/drivers/mtd/maps/elan-104nc.c
new file mode 100644
index 000000000000..e9465f5c069e
--- /dev/null
+++ b/drivers/mtd/maps/elan-104nc.c
@@ -0,0 +1,228 @@
+/* elan-104nc.c -- MTD map driver for Arcom Control Systems ELAN-104NC
+
+ Copyright (C) 2000 Arcom Control System Ltd
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+ $Id: elan-104nc.c,v 1.25 2004/11/28 09:40:39 dwmw2 Exp $
+
+The ELAN-104NC has up to 8 Mibyte of Intel StrataFlash (28F320/28F640) in x16
+mode. This drivers uses the CFI probe and Intel Extended Command Set drivers.
+
+The flash is accessed as follows:
+
+ 32 kbyte memory window at 0xb0000-0xb7fff
+
+ 16 bit I/O port (0x22) for some sort of paging.
+
+The single flash device is divided into 3 partition which appear as separate
+MTD devices.
+
+Linux thinks that the I/O port is used by the PIC and hence check_region() will
+always fail. So we don't do it. I just hope it doesn't break anything.
+*/
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <asm/io.h>
+
+#include <linux/mtd/map.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+
+#define WINDOW_START 0xb0000
+/* Number of bits in offset. */
+#define WINDOW_SHIFT 15
+#define WINDOW_LENGTH (1 << WINDOW_SHIFT)
+/* The bits for the offset into the window. */
+#define WINDOW_MASK (WINDOW_LENGTH-1)
+#define PAGE_IO 0x22
+#define PAGE_IO_SIZE 2
+
+static volatile int page_in_window = -1; // Current page in window.
+static void __iomem *iomapadr;
+static DEFINE_SPINLOCK(elan_104nc_spin);
+
+/* partition_info gives details on the logical partitions that the split the
+ * single flash device into. If the size if zero we use up to the end of the
+ * device. */
+static struct mtd_partition partition_info[]={
+ { .name = "ELAN-104NC flash boot partition",
+ .offset = 0,
+ .size = 640*1024 },
+ { .name = "ELAN-104NC flash partition 1",
+ .offset = 640*1024,
+ .size = 896*1024 },
+ { .name = "ELAN-104NC flash partition 2",
+ .offset = (640+896)*1024 }
+};
+#define NUM_PARTITIONS (sizeof(partition_info)/sizeof(partition_info[0]))
+
+/*
+ * If no idea what is going on here. This is taken from the FlashFX stuff.
+ */
+#define ROMCS 1
+
+static inline void elan_104nc_setup(void)
+{
+ u16 t;
+
+ outw( 0x0023 + ROMCS*2, PAGE_IO );
+ t=inb( PAGE_IO+1 );
+
+ t=(t & 0xf9) | 0x04;
+
+ outw( ((0x0023 + ROMCS*2) | (t << 8)), PAGE_IO );
+}
+
+static inline void elan_104nc_page(struct map_info *map, unsigned long ofs)
+{
+ unsigned long page = ofs >> WINDOW_SHIFT;
+
+ if( page!=page_in_window ) {
+ int cmd1;
+ int cmd2;
+
+ cmd1=(page & 0x700) + 0x0833 + ROMCS*0x4000;
+ cmd2=((page & 0xff) << 8) + 0x0032;
+
+ outw( cmd1, PAGE_IO );
+ outw( cmd2, PAGE_IO );
+
+ page_in_window = page;
+ }
+}
+
+
+static map_word elan_104nc_read16(struct map_info *map, unsigned long ofs)
+{
+ map_word ret;
+ spin_lock(&elan_104nc_spin);
+ elan_104nc_page(map, ofs);
+ ret.x[0] = readw(iomapadr + (ofs & WINDOW_MASK));
+ spin_unlock(&elan_104nc_spin);
+ return ret;
+}
+
+static void elan_104nc_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len)
+{
+ while (len) {
+ unsigned long thislen = len;
+ if (len > (WINDOW_LENGTH - (from & WINDOW_MASK)))
+ thislen = WINDOW_LENGTH-(from & WINDOW_MASK);
+
+ spin_lock(&elan_104nc_spin);
+ elan_104nc_page(map, from);
+ memcpy_fromio(to, iomapadr + (from & WINDOW_MASK), thislen);
+ spin_unlock(&elan_104nc_spin);
+ to += thislen;
+ from += thislen;
+ len -= thislen;
+ }
+}
+
+static void elan_104nc_write16(struct map_info *map, map_word d, unsigned long adr)
+{
+ spin_lock(&elan_104nc_spin);
+ elan_104nc_page(map, adr);
+ writew(d.x[0], iomapadr + (adr & WINDOW_MASK));
+ spin_unlock(&elan_104nc_spin);
+}
+
+static void elan_104nc_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len)
+{
+ while(len) {
+ unsigned long thislen = len;
+ if (len > (WINDOW_LENGTH - (to & WINDOW_MASK)))
+ thislen = WINDOW_LENGTH-(to & WINDOW_MASK);
+
+ spin_lock(&elan_104nc_spin);
+ elan_104nc_page(map, to);
+ memcpy_toio(iomapadr + (to & WINDOW_MASK), from, thislen);
+ spin_unlock(&elan_104nc_spin);
+ to += thislen;
+ from += thislen;
+ len -= thislen;
+ }
+}
+
+static struct map_info elan_104nc_map = {
+ .name = "ELAN-104NC flash",
+ .phys = NO_XIP,
+ .size = 8*1024*1024, /* this must be set to a maximum possible amount
+ of flash so the cfi probe routines find all
+ the chips */
+ .bankwidth = 2,
+ .read = elan_104nc_read16,
+ .copy_from = elan_104nc_copy_from,
+ .write = elan_104nc_write16,
+ .copy_to = elan_104nc_copy_to
+};
+
+/* MTD device for all of the flash. */
+static struct mtd_info *all_mtd;
+
+static void cleanup_elan_104nc(void)
+{
+ if( all_mtd ) {
+ del_mtd_partitions( all_mtd );
+ map_destroy( all_mtd );
+ }
+
+ iounmap(iomapadr);
+}
+
+static int __init init_elan_104nc(void)
+{
+ /* Urg! We use I/O port 0x22 without request_region()ing it,
+ because it's already allocated to the PIC. */
+
+ iomapadr = ioremap(WINDOW_START, WINDOW_LENGTH);
+ if (!iomapadr) {
+ printk( KERN_ERR"%s: failed to ioremap memory region\n",
+ elan_104nc_map.name );
+ return -EIO;
+ }
+
+ printk( KERN_INFO"%s: IO:0x%x-0x%x MEM:0x%x-0x%x\n",
+ elan_104nc_map.name,
+ PAGE_IO, PAGE_IO+PAGE_IO_SIZE-1,
+ WINDOW_START, WINDOW_START+WINDOW_LENGTH-1 );
+
+ elan_104nc_setup();
+
+ /* Probe for chip. */
+ all_mtd = do_map_probe("cfi_probe", &elan_104nc_map );
+ if( !all_mtd ) {
+ cleanup_elan_104nc();
+ return -ENXIO;
+ }
+
+ all_mtd->owner = THIS_MODULE;
+
+ /* Create MTD devices for each partition. */
+ add_mtd_partitions( all_mtd, partition_info, NUM_PARTITIONS );
+
+ return 0;
+}
+
+module_init(init_elan_104nc);
+module_exit(cleanup_elan_104nc);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Arcom Control Systems Ltd.");
+MODULE_DESCRIPTION("MTD map driver for Arcom Control Systems ELAN-104NC");
diff --git a/drivers/mtd/maps/epxa10db-flash.c b/drivers/mtd/maps/epxa10db-flash.c
new file mode 100644
index 000000000000..ab6dbe2b8cce
--- /dev/null
+++ b/drivers/mtd/maps/epxa10db-flash.c
@@ -0,0 +1,176 @@
+/*
+ * Flash memory access on EPXA based devices
+ *
+ * (C) 2000 Nicolas Pitre <nico@cam.org>
+ * Copyright (C) 2001 Altera Corporation
+ * Copyright (C) 2001 Red Hat, Inc.
+ *
+ * $Id: epxa10db-flash.c,v 1.13 2004/11/04 13:24:14 gleixner Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/hardware.h>
+#ifdef CONFIG_EPXA10DB
+#define BOARD_NAME "EPXA10DB"
+#else
+#define BOARD_NAME "EPXA1DB"
+#endif
+
+static int nr_parts = 0;
+static struct mtd_partition *parts;
+
+static struct mtd_info *mymtd;
+
+static int epxa_default_partitions(struct mtd_info *master, struct mtd_partition **pparts);
+
+
+static struct map_info epxa_map = {
+ .name = "EPXA flash",
+ .size = FLASH_SIZE,
+ .bankwidth = 2,
+ .phys = FLASH_START,
+};
+
+static const char *probes[] = { "RedBoot", "afs", NULL };
+
+static int __init epxa_mtd_init(void)
+{
+ int i;
+
+ printk(KERN_NOTICE "%s flash device: 0x%x at 0x%x\n", BOARD_NAME, FLASH_SIZE, FLASH_START);
+
+ epxa_map.virt = ioremap(FLASH_START, FLASH_SIZE);
+ if (!epxa_map.virt) {
+ printk("Failed to ioremap %s flash\n",BOARD_NAME);
+ return -EIO;
+ }
+ simple_map_init(&epxa_map);
+
+ mymtd = do_map_probe("cfi_probe", &epxa_map);
+ if (!mymtd) {
+ iounmap((void *)epxa_map.virt);
+ return -ENXIO;
+ }
+
+ mymtd->owner = THIS_MODULE;
+
+ /* Unlock the flash device. */
+ if(mymtd->unlock){
+ for (i=0; i<mymtd->numeraseregions;i++){
+ int j;
+ for(j=0;j<mymtd->eraseregions[i].numblocks;j++){
+ mymtd->unlock(mymtd,mymtd->eraseregions[i].offset + j * mymtd->eraseregions[i].erasesize,mymtd->eraseregions[i].erasesize);
+ }
+ }
+ }
+
+#ifdef CONFIG_MTD_PARTITIONS
+ nr_parts = parse_mtd_partitions(mymtd, probes, &parts, 0);
+
+ if (nr_parts > 0) {
+ add_mtd_partitions(mymtd, parts, nr_parts);
+ return 0;
+ }
+#endif
+ /* No recognised partitioning schemes found - use defaults */
+ nr_parts = epxa_default_partitions(mymtd, &parts);
+ if (nr_parts > 0) {
+ add_mtd_partitions(mymtd, parts, nr_parts);
+ return 0;
+ }
+
+ /* If all else fails... */
+ add_mtd_device(mymtd);
+ return 0;
+}
+
+static void __exit epxa_mtd_cleanup(void)
+{
+ if (mymtd) {
+ if (nr_parts)
+ del_mtd_partitions(mymtd);
+ else
+ del_mtd_device(mymtd);
+ map_destroy(mymtd);
+ }
+ if (epxa_map.virt) {
+ iounmap((void *)epxa_map.virt);
+ epxa_map.virt = 0;
+ }
+}
+
+
+/*
+ * This will do for now, once we decide which bootldr we're finally
+ * going to use then we'll remove this function and do it properly
+ *
+ * Partions are currently (as offsets from base of flash):
+ * 0x00000000 - 0x003FFFFF - bootloader (!)
+ * 0x00400000 - 0x00FFFFFF - Flashdisk
+ */
+
+static int __init epxa_default_partitions(struct mtd_info *master, struct mtd_partition **pparts)
+{
+ struct mtd_partition *parts;
+ int ret, i;
+ int npartitions = 0;
+ char *names;
+ const char *name = "jffs";
+
+ printk("Using default partitions for %s\n",BOARD_NAME);
+ npartitions=1;
+ parts = kmalloc(npartitions*sizeof(*parts)+strlen(name), GFP_KERNEL);
+ memzero(parts,npartitions*sizeof(*parts)+strlen(name));
+ if (!parts) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ i=0;
+ names = (char *)&parts[npartitions];
+ parts[i].name = names;
+ names += strlen(name) + 1;
+ strcpy(parts[i].name, name);
+
+#ifdef CONFIG_EPXA10DB
+ parts[i].size = FLASH_SIZE-0x00400000;
+ parts[i].offset = 0x00400000;
+#else
+ parts[i].size = FLASH_SIZE-0x00180000;
+ parts[i].offset = 0x00180000;
+#endif
+
+ out:
+ *pparts = parts;
+ return npartitions;
+}
+
+
+module_init(epxa_mtd_init);
+module_exit(epxa_mtd_cleanup);
+
+MODULE_AUTHOR("Clive Davies");
+MODULE_DESCRIPTION("Altera epxa mtd flash map");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/maps/fortunet.c b/drivers/mtd/maps/fortunet.c
new file mode 100644
index 000000000000..068bb6a54520
--- /dev/null
+++ b/drivers/mtd/maps/fortunet.c
@@ -0,0 +1,271 @@
+/* fortunet.c memory map
+ *
+ * $Id: fortunet.c,v 1.9 2004/11/04 13:24:14 gleixner Exp $
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#define MAX_NUM_REGIONS 4
+#define MAX_NUM_PARTITIONS 8
+
+#define DEF_WINDOW_ADDR_PHY 0x00000000
+#define DEF_WINDOW_SIZE 0x00800000 // 8 Mega Bytes
+
+#define MTD_FORTUNET_PK "MTD FortuNet: "
+
+#define MAX_NAME_SIZE 128
+
+struct map_region
+{
+ int window_addr_physical;
+ int altbankwidth;
+ struct map_info map_info;
+ struct mtd_info *mymtd;
+ struct mtd_partition parts[MAX_NUM_PARTITIONS];
+ char map_name[MAX_NAME_SIZE];
+ char parts_name[MAX_NUM_PARTITIONS][MAX_NAME_SIZE];
+};
+
+static struct map_region map_regions[MAX_NUM_REGIONS];
+static int map_regions_set[MAX_NUM_REGIONS] = {0,0,0,0};
+static int map_regions_parts[MAX_NUM_REGIONS] = {0,0,0,0};
+
+
+
+struct map_info default_map = {
+ .size = DEF_WINDOW_SIZE,
+ .bankwidth = 4,
+};
+
+static char * __init get_string_option(char *dest,int dest_size,char *sor)
+{
+ if(!dest_size)
+ return sor;
+ dest_size--;
+ while(*sor)
+ {
+ if(*sor==',')
+ {
+ sor++;
+ break;
+ }
+ else if(*sor=='\"')
+ {
+ sor++;
+ while(*sor)
+ {
+ if(*sor=='\"')
+ {
+ sor++;
+ break;
+ }
+ *dest = *sor;
+ dest++;
+ sor++;
+ dest_size--;
+ if(!dest_size)
+ {
+ *dest = 0;
+ return sor;
+ }
+ }
+ }
+ else
+ {
+ *dest = *sor;
+ dest++;
+ sor++;
+ dest_size--;
+ if(!dest_size)
+ {
+ *dest = 0;
+ return sor;
+ }
+ }
+ }
+ *dest = 0;
+ return sor;
+}
+
+static int __init MTD_New_Region(char *line)
+{
+ char string[MAX_NAME_SIZE];
+ int params[6];
+ get_options (get_string_option(string,sizeof(string),line),6,params);
+ if(params[0]<1)
+ {
+ printk(MTD_FORTUNET_PK "Bad parameters for MTD Region "
+ " name,region-number[,base,size,bankwidth,altbankwidth]\n");
+ return 1;
+ }
+ if((params[1]<0)||(params[1]>=MAX_NUM_REGIONS))
+ {
+ printk(MTD_FORTUNET_PK "Bad region index of %d only have 0..%u regions\n",
+ params[1],MAX_NUM_REGIONS-1);
+ return 1;
+ }
+ memset(&map_regions[params[1]],0,sizeof(map_regions[params[1]]));
+ memcpy(&map_regions[params[1]].map_info,
+ &default_map,sizeof(map_regions[params[1]].map_info));
+ map_regions_set[params[1]] = 1;
+ map_regions[params[1]].window_addr_physical = DEF_WINDOW_ADDR_PHY;
+ map_regions[params[1]].altbankwidth = 2;
+ map_regions[params[1]].mymtd = NULL;
+ map_regions[params[1]].map_info.name = map_regions[params[1]].map_name;
+ strcpy(map_regions[params[1]].map_info.name,string);
+ if(params[0]>1)
+ {
+ map_regions[params[1]].window_addr_physical = params[2];
+ }
+ if(params[0]>2)
+ {
+ map_regions[params[1]].map_info.size = params[3];
+ }
+ if(params[0]>3)
+ {
+ map_regions[params[1]].map_info.bankwidth = params[4];
+ }
+ if(params[0]>4)
+ {
+ map_regions[params[1]].altbankwidth = params[5];
+ }
+ return 1;
+}
+
+static int __init MTD_New_Partition(char *line)
+{
+ char string[MAX_NAME_SIZE];
+ int params[4];
+ get_options (get_string_option(string,sizeof(string),line),4,params);
+ if(params[0]<3)
+ {
+ printk(MTD_FORTUNET_PK "Bad parameters for MTD Partition "
+ " name,region-number,size,offset\n");
+ return 1;
+ }
+ if((params[1]<0)||(params[1]>=MAX_NUM_REGIONS))
+ {
+ printk(MTD_FORTUNET_PK "Bad region index of %d only have 0..%u regions\n",
+ params[1],MAX_NUM_REGIONS-1);
+ return 1;
+ }
+ if(map_regions_parts[params[1]]>=MAX_NUM_PARTITIONS)
+ {
+ printk(MTD_FORTUNET_PK "Out of space for partition in this region\n");
+ return 1;
+ }
+ map_regions[params[1]].parts[map_regions_parts[params[1]]].name =
+ map_regions[params[1]]. parts_name[map_regions_parts[params[1]]];
+ strcpy(map_regions[params[1]].parts[map_regions_parts[params[1]]].name,string);
+ map_regions[params[1]].parts[map_regions_parts[params[1]]].size =
+ params[2];
+ map_regions[params[1]].parts[map_regions_parts[params[1]]].offset =
+ params[3];
+ map_regions[params[1]].parts[map_regions_parts[params[1]]].mask_flags = 0;
+ map_regions_parts[params[1]]++;
+ return 1;
+}
+
+__setup("MTD_Region=", MTD_New_Region);
+__setup("MTD_Partition=", MTD_New_Partition);
+
+/* Backwards-spelling-compatibility */
+__setup("MTD_Partion=", MTD_New_Partition);
+
+int __init init_fortunet(void)
+{
+ int ix,iy;
+ for(iy=ix=0;ix<MAX_NUM_REGIONS;ix++)
+ {
+ if(map_regions_parts[ix]&&(!map_regions_set[ix]))
+ {
+ printk(MTD_FORTUNET_PK "Region %d is not setup (Setting to default)\n",
+ ix);
+ memset(&map_regions[ix],0,sizeof(map_regions[ix]));
+ memcpy(&map_regions[ix].map_info,&default_map,
+ sizeof(map_regions[ix].map_info));
+ map_regions_set[ix] = 1;
+ map_regions[ix].window_addr_physical = DEF_WINDOW_ADDR_PHY;
+ map_regions[ix].altbankwidth = 2;
+ map_regions[ix].mymtd = NULL;
+ map_regions[ix].map_info.name = map_regions[ix].map_name;
+ strcpy(map_regions[ix].map_info.name,"FORTUNET");
+ }
+ if(map_regions_set[ix])
+ {
+ iy++;
+ printk(KERN_NOTICE MTD_FORTUNET_PK "%s flash device at physically "
+ " address %x size %x\n",
+ map_regions[ix].map_info.name,
+ map_regions[ix].window_addr_physical,
+ map_regions[ix].map_info.size);
+
+ map_regions[ix].map_info.phys = map_regions[ix].window_addr_physical,
+
+ map_regions[ix].map_info.virt =
+ ioremap_nocache(
+ map_regions[ix].window_addr_physical,
+ map_regions[ix].map_info.size);
+ if(!map_regions[ix].map_info.virt)
+ {
+ printk(MTD_FORTUNET_PK "%s flash failed to ioremap!\n",
+ map_regions[ix].map_info.name);
+ return -ENXIO;
+ }
+ simple_map_init(&map_regions[ix].map_info);
+
+ printk(KERN_NOTICE MTD_FORTUNET_PK "%s flash is virtually at: %x\n",
+ map_regions[ix].map_info.name,
+ map_regions[ix].map_info.virt);
+ map_regions[ix].mymtd = do_map_probe("cfi_probe",
+ &map_regions[ix].map_info);
+ if((!map_regions[ix].mymtd)&&(
+ map_regions[ix].altbankwidth!=map_regions[ix].map_info.bankwidth))
+ {
+ printk(KERN_NOTICE MTD_FORTUNET_PK "Trying alternate bankwidth "
+ "for %s flash.\n",
+ map_regions[ix].map_info.name);
+ map_regions[ix].map_info.bankwidth =
+ map_regions[ix].altbankwidth;
+ map_regions[ix].mymtd = do_map_probe("cfi_probe",
+ &map_regions[ix].map_info);
+ }
+ map_regions[ix].mymtd->owner = THIS_MODULE;
+ add_mtd_partitions(map_regions[ix].mymtd,
+ map_regions[ix].parts,map_regions_parts[ix]);
+ }
+ }
+ if(iy)
+ return 0;
+ return -ENXIO;
+}
+
+static void __exit cleanup_fortunet(void)
+{
+ int ix;
+ for(ix=0;ix<MAX_NUM_REGIONS;ix++)
+ {
+ if(map_regions_set[ix])
+ {
+ if( map_regions[ix].mymtd )
+ {
+ del_mtd_partitions( map_regions[ix].mymtd );
+ map_destroy( map_regions[ix].mymtd );
+ }
+ iounmap((void *)map_regions[ix].map_info.virt);
+ }
+ }
+}
+
+module_init(init_fortunet);
+module_exit(cleanup_fortunet);
+
+MODULE_AUTHOR("FortuNet, Inc.");
+MODULE_DESCRIPTION("MTD map driver for FortuNet boards");
diff --git a/drivers/mtd/maps/h720x-flash.c b/drivers/mtd/maps/h720x-flash.c
new file mode 100644
index 000000000000..c73828171d9b
--- /dev/null
+++ b/drivers/mtd/maps/h720x-flash.c
@@ -0,0 +1,144 @@
+/*
+ * Flash memory access on Hynix GMS30C7201/HMS30C7202 based
+ * evaluation boards
+ *
+ * $Id: h720x-flash.c,v 1.11 2004/11/04 13:24:14 gleixner Exp $
+ *
+ * (C) 2002 Jungjun Kim <jungjun.kim@hynix.com>
+ * 2003 Thomas Gleixner <tglx@linutronix.de>
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <asm/hardware.h>
+#include <asm/io.h>
+
+static struct mtd_info *mymtd;
+
+static struct map_info h720x_map = {
+ .name = "H720X",
+ .bankwidth = 4,
+ .size = FLASH_SIZE,
+ .phys = FLASH_PHYS,
+};
+
+static struct mtd_partition h720x_partitions[] = {
+ {
+ .name = "ArMon",
+ .size = 0x00080000,
+ .offset = 0,
+ .mask_flags = MTD_WRITEABLE
+ },{
+ .name = "Env",
+ .size = 0x00040000,
+ .offset = 0x00080000,
+ .mask_flags = MTD_WRITEABLE
+ },{
+ .name = "Kernel",
+ .size = 0x00180000,
+ .offset = 0x000c0000,
+ .mask_flags = MTD_WRITEABLE
+ },{
+ .name = "Ramdisk",
+ .size = 0x00400000,
+ .offset = 0x00240000,
+ .mask_flags = MTD_WRITEABLE
+ },{
+ .name = "jffs2",
+ .size = MTDPART_SIZ_FULL,
+ .offset = MTDPART_OFS_APPEND
+ }
+};
+
+#define NUM_PARTITIONS (sizeof(h720x_partitions)/sizeof(h720x_partitions[0]))
+
+static int nr_mtd_parts;
+static struct mtd_partition *mtd_parts;
+static const char *probes[] = { "cmdlinepart", NULL };
+
+/*
+ * Initialize FLASH support
+ */
+int __init h720x_mtd_init(void)
+{
+
+ char *part_type = NULL;
+
+ h720x_map.virt = ioremap(FLASH_PHYS, FLASH_SIZE);
+
+ if (!h720x_map.virt) {
+ printk(KERN_ERR "H720x-MTD: ioremap failed\n");
+ return -EIO;
+ }
+
+ simple_map_init(&h720x_map);
+
+ // Probe for flash bankwidth 4
+ printk (KERN_INFO "H720x-MTD probing 32bit FLASH\n");
+ mymtd = do_map_probe("cfi_probe", &h720x_map);
+ if (!mymtd) {
+ printk (KERN_INFO "H720x-MTD probing 16bit FLASH\n");
+ // Probe for bankwidth 2
+ h720x_map.bankwidth = 2;
+ mymtd = do_map_probe("cfi_probe", &h720x_map);
+ }
+
+ if (mymtd) {
+ mymtd->owner = THIS_MODULE;
+
+#ifdef CONFIG_MTD_PARTITIONS
+ nr_mtd_parts = parse_mtd_partitions(mymtd, probes, &mtd_parts, 0);
+ if (nr_mtd_parts > 0)
+ part_type = "command line";
+#endif
+ if (nr_mtd_parts <= 0) {
+ mtd_parts = h720x_partitions;
+ nr_mtd_parts = NUM_PARTITIONS;
+ part_type = "builtin";
+ }
+ printk(KERN_INFO "Using %s partition table\n", part_type);
+ add_mtd_partitions(mymtd, mtd_parts, nr_mtd_parts);
+ return 0;
+ }
+
+ iounmap((void *)h720x_map.virt);
+ return -ENXIO;
+}
+
+/*
+ * Cleanup
+ */
+static void __exit h720x_mtd_cleanup(void)
+{
+
+ if (mymtd) {
+ del_mtd_partitions(mymtd);
+ map_destroy(mymtd);
+ }
+
+ /* Free partition info, if commandline partition was used */
+ if (mtd_parts && (mtd_parts != h720x_partitions))
+ kfree (mtd_parts);
+
+ if (h720x_map.virt) {
+ iounmap((void *)h720x_map.virt);
+ h720x_map.virt = 0;
+ }
+}
+
+
+module_init(h720x_mtd_init);
+module_exit(h720x_mtd_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>");
+MODULE_DESCRIPTION("MTD map driver for Hynix evaluation boards");
diff --git a/drivers/mtd/maps/ichxrom.c b/drivers/mtd/maps/ichxrom.c
new file mode 100644
index 000000000000..29d1cc1bb426
--- /dev/null
+++ b/drivers/mtd/maps/ichxrom.c
@@ -0,0 +1,383 @@
+/*
+ * ichxrom.c
+ *
+ * Normal mappings of chips in physical memory
+ * $Id: ichxrom.c,v 1.16 2004/11/28 09:40:39 dwmw2 Exp $
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/cfi.h>
+#include <linux/mtd/flashchip.h>
+#include <linux/config.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/list.h>
+
+#define xstr(s) str(s)
+#define str(s) #s
+#define MOD_NAME xstr(KBUILD_BASENAME)
+
+#define ADDRESS_NAME_LEN 18
+
+#define ROM_PROBE_STEP_SIZE (64*1024) /* 64KiB */
+
+#define BIOS_CNTL 0x4e
+#define FWH_DEC_EN1 0xE3
+#define FWH_DEC_EN2 0xF0
+#define FWH_SEL1 0xE8
+#define FWH_SEL2 0xEE
+
+struct ichxrom_window {
+ void __iomem* virt;
+ unsigned long phys;
+ unsigned long size;
+ struct list_head maps;
+ struct resource rsrc;
+ struct pci_dev *pdev;
+};
+
+struct ichxrom_map_info {
+ struct list_head list;
+ struct map_info map;
+ struct mtd_info *mtd;
+ struct resource rsrc;
+ char map_name[sizeof(MOD_NAME) + 2 + ADDRESS_NAME_LEN];
+};
+
+static struct ichxrom_window ichxrom_window = {
+ .maps = LIST_HEAD_INIT(ichxrom_window.maps),
+};
+
+static void ichxrom_cleanup(struct ichxrom_window *window)
+{
+ struct ichxrom_map_info *map, *scratch;
+ u16 word;
+
+ /* Disable writes through the rom window */
+ pci_read_config_word(window->pdev, BIOS_CNTL, &word);
+ pci_write_config_word(window->pdev, BIOS_CNTL, word & ~1);
+
+ /* Free all of the mtd devices */
+ list_for_each_entry_safe(map, scratch, &window->maps, list) {
+ if (map->rsrc.parent)
+ release_resource(&map->rsrc);
+ del_mtd_device(map->mtd);
+ map_destroy(map->mtd);
+ list_del(&map->list);
+ kfree(map);
+ }
+ if (window->rsrc.parent)
+ release_resource(&window->rsrc);
+ if (window->virt) {
+ iounmap(window->virt);
+ window->virt = NULL;
+ window->phys = 0;
+ window->size = 0;
+ window->pdev = NULL;
+ }
+}
+
+
+static int __devinit ichxrom_init_one (struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ static char *rom_probe_types[] = { "cfi_probe", "jedec_probe", NULL };
+ struct ichxrom_window *window = &ichxrom_window;
+ struct ichxrom_map_info *map = NULL;
+ unsigned long map_top;
+ u8 byte;
+ u16 word;
+
+ /* For now I just handle the ichx and I assume there
+ * are not a lot of resources up at the top of the address
+ * space. It is possible to handle other devices in the
+ * top 16MB but it is very painful. Also since
+ * you can only really attach a FWH to an ICHX there
+ * a number of simplifications you can make.
+ *
+ * Also you can page firmware hubs if an 8MB window isn't enough
+ * but don't currently handle that case either.
+ */
+ window->pdev = pdev;
+
+ /* Find a region continuous to the end of the ROM window */
+ window->phys = 0;
+ pci_read_config_byte(pdev, FWH_DEC_EN1, &byte);
+ if (byte == 0xff) {
+ window->phys = 0xffc00000;
+ pci_read_config_byte(pdev, FWH_DEC_EN2, &byte);
+ if ((byte & 0x0f) == 0x0f) {
+ window->phys = 0xff400000;
+ }
+ else if ((byte & 0x0e) == 0x0e) {
+ window->phys = 0xff500000;
+ }
+ else if ((byte & 0x0c) == 0x0c) {
+ window->phys = 0xff600000;
+ }
+ else if ((byte & 0x08) == 0x08) {
+ window->phys = 0xff700000;
+ }
+ }
+ else if ((byte & 0xfe) == 0xfe) {
+ window->phys = 0xffc80000;
+ }
+ else if ((byte & 0xfc) == 0xfc) {
+ window->phys = 0xffd00000;
+ }
+ else if ((byte & 0xf8) == 0xf8) {
+ window->phys = 0xffd80000;
+ }
+ else if ((byte & 0xf0) == 0xf0) {
+ window->phys = 0xffe00000;
+ }
+ else if ((byte & 0xe0) == 0xe0) {
+ window->phys = 0xffe80000;
+ }
+ else if ((byte & 0xc0) == 0xc0) {
+ window->phys = 0xfff00000;
+ }
+ else if ((byte & 0x80) == 0x80) {
+ window->phys = 0xfff80000;
+ }
+
+ if (window->phys == 0) {
+ printk(KERN_ERR MOD_NAME ": Rom window is closed\n");
+ goto out;
+ }
+ window->phys -= 0x400000UL;
+ window->size = (0xffffffffUL - window->phys) + 1UL;
+
+ /* Enable writes through the rom window */
+ pci_read_config_word(pdev, BIOS_CNTL, &word);
+ if (!(word & 1) && (word & (1<<1))) {
+ /* The BIOS will generate an error if I enable
+ * this device, so don't even try.
+ */
+ printk(KERN_ERR MOD_NAME ": firmware access control, I can't enable writes\n");
+ goto out;
+ }
+ pci_write_config_word(pdev, BIOS_CNTL, word | 1);
+
+ /*
+ * Try to reserve the window mem region. If this fails then
+ * it is likely due to the window being "reseved" by the BIOS.
+ */
+ window->rsrc.name = MOD_NAME;
+ window->rsrc.start = window->phys;
+ window->rsrc.end = window->phys + window->size - 1;
+ window->rsrc.flags = IORESOURCE_MEM | IORESOURCE_BUSY;
+ if (request_resource(&iomem_resource, &window->rsrc)) {
+ window->rsrc.parent = NULL;
+ printk(KERN_DEBUG MOD_NAME
+ ": %s(): Unable to register resource"
+ " 0x%.08lx-0x%.08lx - kernel bug?\n",
+ __func__,
+ window->rsrc.start, window->rsrc.end);
+ }
+
+ /* Map the firmware hub into my address space. */
+ window->virt = ioremap_nocache(window->phys, window->size);
+ if (!window->virt) {
+ printk(KERN_ERR MOD_NAME ": ioremap(%08lx, %08lx) failed\n",
+ window->phys, window->size);
+ goto out;
+ }
+
+ /* Get the first address to look for an rom chip at */
+ map_top = window->phys;
+ if ((window->phys & 0x3fffff) != 0) {
+ map_top = window->phys + 0x400000;
+ }
+#if 1
+ /* The probe sequence run over the firmware hub lock
+ * registers sets them to 0x7 (no access).
+ * Probe at most the last 4M of the address space.
+ */
+ if (map_top < 0xffc00000) {
+ map_top = 0xffc00000;
+ }
+#endif
+ /* Loop through and look for rom chips */
+ while((map_top - 1) < 0xffffffffUL) {
+ struct cfi_private *cfi;
+ unsigned long offset;
+ int i;
+
+ if (!map) {
+ map = kmalloc(sizeof(*map), GFP_KERNEL);
+ }
+ if (!map) {
+ printk(KERN_ERR MOD_NAME ": kmalloc failed");
+ goto out;
+ }
+ memset(map, 0, sizeof(*map));
+ INIT_LIST_HEAD(&map->list);
+ map->map.name = map->map_name;
+ map->map.phys = map_top;
+ offset = map_top - window->phys;
+ map->map.virt = (void __iomem *)
+ (((unsigned long)(window->virt)) + offset);
+ map->map.size = 0xffffffffUL - map_top + 1UL;
+ /* Set the name of the map to the address I am trying */
+ sprintf(map->map_name, "%s @%08lx",
+ MOD_NAME, map->map.phys);
+
+ /* Firmware hubs only use vpp when being programmed
+ * in a factory setting. So in-place programming
+ * needs to use a different method.
+ */
+ for(map->map.bankwidth = 32; map->map.bankwidth;
+ map->map.bankwidth >>= 1)
+ {
+ char **probe_type;
+ /* Skip bankwidths that are not supported */
+ if (!map_bankwidth_supported(map->map.bankwidth))
+ continue;
+
+ /* Setup the map methods */
+ simple_map_init(&map->map);
+
+ /* Try all of the probe methods */
+ probe_type = rom_probe_types;
+ for(; *probe_type; probe_type++) {
+ map->mtd = do_map_probe(*probe_type, &map->map);
+ if (map->mtd)
+ goto found;
+ }
+ }
+ map_top += ROM_PROBE_STEP_SIZE;
+ continue;
+ found:
+ /* Trim the size if we are larger than the map */
+ if (map->mtd->size > map->map.size) {
+ printk(KERN_WARNING MOD_NAME
+ " rom(%u) larger than window(%lu). fixing...\n",
+ map->mtd->size, map->map.size);
+ map->mtd->size = map->map.size;
+ }
+ if (window->rsrc.parent) {
+ /*
+ * Registering the MTD device in iomem may not be possible
+ * if there is a BIOS "reserved" and BUSY range. If this
+ * fails then continue anyway.
+ */
+ map->rsrc.name = map->map_name;
+ map->rsrc.start = map->map.phys;
+ map->rsrc.end = map->map.phys + map->mtd->size - 1;
+ map->rsrc.flags = IORESOURCE_MEM | IORESOURCE_BUSY;
+ if (request_resource(&window->rsrc, &map->rsrc)) {
+ printk(KERN_ERR MOD_NAME
+ ": cannot reserve MTD resource\n");
+ map->rsrc.parent = NULL;
+ }
+ }
+
+ /* Make the whole region visible in the map */
+ map->map.virt = window->virt;
+ map->map.phys = window->phys;
+ cfi = map->map.fldrv_priv;
+ for(i = 0; i < cfi->numchips; i++) {
+ cfi->chips[i].start += offset;
+ }
+
+ /* Now that the mtd devices is complete claim and export it */
+ map->mtd->owner = THIS_MODULE;
+ if (add_mtd_device(map->mtd)) {
+ map_destroy(map->mtd);
+ map->mtd = NULL;
+ goto out;
+ }
+
+
+ /* Calculate the new value of map_top */
+ map_top += map->mtd->size;
+
+ /* File away the map structure */
+ list_add(&map->list, &window->maps);
+ map = NULL;
+ }
+
+ out:
+ /* Free any left over map structures */
+ if (map) {
+ kfree(map);
+ }
+ /* See if I have any map structures */
+ if (list_empty(&window->maps)) {
+ ichxrom_cleanup(window);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+
+static void __devexit ichxrom_remove_one (struct pci_dev *pdev)
+{
+ struct ichxrom_window *window = &ichxrom_window;
+ ichxrom_cleanup(window);
+}
+
+static struct pci_device_id ichxrom_pci_tbl[] __devinitdata = {
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { 0, },
+};
+
+MODULE_DEVICE_TABLE(pci, ichxrom_pci_tbl);
+
+#if 0
+static struct pci_driver ichxrom_driver = {
+ .name = MOD_NAME,
+ .id_table = ichxrom_pci_tbl,
+ .probe = ichxrom_init_one,
+ .remove = ichxrom_remove_one,
+};
+#endif
+
+static int __init init_ichxrom(void)
+{
+ struct pci_dev *pdev;
+ struct pci_device_id *id;
+
+ pdev = NULL;
+ for (id = ichxrom_pci_tbl; id->vendor; id++) {
+ pdev = pci_find_device(id->vendor, id->device, NULL);
+ if (pdev) {
+ break;
+ }
+ }
+ if (pdev) {
+ return ichxrom_init_one(pdev, &ichxrom_pci_tbl[0]);
+ }
+ return -ENXIO;
+#if 0
+ return pci_module_init(&ichxrom_driver);
+#endif
+}
+
+static void __exit cleanup_ichxrom(void)
+{
+ ichxrom_remove_one(ichxrom_window.pdev);
+}
+
+module_init(init_ichxrom);
+module_exit(cleanup_ichxrom);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Eric Biederman <ebiederman@lnxi.com>");
+MODULE_DESCRIPTION("MTD map driver for BIOS chips on the ICHX southbridge");
diff --git a/drivers/mtd/maps/impa7.c b/drivers/mtd/maps/impa7.c
new file mode 100644
index 000000000000..cb39172c81d2
--- /dev/null
+++ b/drivers/mtd/maps/impa7.c
@@ -0,0 +1,161 @@
+/*
+ * $Id: impa7.c,v 1.13 2004/11/04 13:24:14 gleixner Exp $
+ *
+ * Handle mapping of the NOR flash on implementa A7 boards
+ *
+ * Copyright 2002 SYSGO Real-Time Solutions GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/config.h>
+
+#ifdef CONFIG_MTD_PARTITIONS
+#include <linux/mtd/partitions.h>
+#endif
+
+#define WINDOW_ADDR0 0x00000000 /* physical properties of flash */
+#define WINDOW_SIZE0 0x00800000
+#define WINDOW_ADDR1 0x10000000 /* physical properties of flash */
+#define WINDOW_SIZE1 0x00800000
+#define NUM_FLASHBANKS 2
+#define BUSWIDTH 4
+
+/* can be { "cfi_probe", "jedec_probe", "map_rom", NULL } */
+#define PROBETYPES { "jedec_probe", NULL }
+
+#define MSG_PREFIX "impA7:" /* prefix for our printk()'s */
+#define MTDID "impa7-%d" /* for mtdparts= partitioning */
+
+static struct mtd_info *impa7_mtd[NUM_FLASHBANKS];
+
+
+static struct map_info impa7_map[NUM_FLASHBANKS] = {
+ {
+ .name = "impA7 NOR Flash Bank #0",
+ .size = WINDOW_SIZE0,
+ .bankwidth = BUSWIDTH,
+ },
+ {
+ .name = "impA7 NOR Flash Bank #1",
+ .size = WINDOW_SIZE1,
+ .bankwidth = BUSWIDTH,
+ },
+};
+
+#ifdef CONFIG_MTD_PARTITIONS
+
+/*
+ * MTD partitioning stuff
+ */
+static struct mtd_partition static_partitions[] =
+{
+ {
+ .name = "FileSystem",
+ .size = 0x800000,
+ .offset = 0x00000000
+ },
+};
+
+static int mtd_parts_nb[NUM_FLASHBANKS];
+static struct mtd_partition *mtd_parts[NUM_FLASHBANKS];
+
+#endif
+
+static const char *probes[] = { "cmdlinepart", NULL };
+
+int __init init_impa7(void)
+{
+ static const char *rom_probe_types[] = PROBETYPES;
+ const char **type;
+ const char *part_type = 0;
+ int i;
+ static struct { u_long addr; u_long size; } pt[NUM_FLASHBANKS] = {
+ { WINDOW_ADDR0, WINDOW_SIZE0 },
+ { WINDOW_ADDR1, WINDOW_SIZE1 },
+ };
+ int devicesfound = 0;
+
+ for(i=0; i<NUM_FLASHBANKS; i++)
+ {
+ printk(KERN_NOTICE MSG_PREFIX "probing 0x%08lx at 0x%08lx\n",
+ pt[i].size, pt[i].addr);
+
+ impa7_map[i].phys = pt[i].addr;
+ impa7_map[i].virt = ioremap(pt[i].addr, pt[i].size);
+ if (!impa7_map[i].virt) {
+ printk(MSG_PREFIX "failed to ioremap\n");
+ return -EIO;
+ }
+ simple_map_init(&impa7_map[i]);
+
+ impa7_mtd[i] = 0;
+ type = rom_probe_types;
+ for(; !impa7_mtd[i] && *type; type++) {
+ impa7_mtd[i] = do_map_probe(*type, &impa7_map[i]);
+ }
+
+ if (impa7_mtd[i]) {
+ impa7_mtd[i]->owner = THIS_MODULE;
+ devicesfound++;
+#ifdef CONFIG_MTD_PARTITIONS
+ mtd_parts_nb[i] = parse_mtd_partitions(impa7_mtd[i],
+ probes,
+ &mtd_parts[i],
+ 0);
+ if (mtd_parts_nb[i] > 0) {
+ part_type = "command line";
+ } else {
+ mtd_parts[i] = static_partitions;
+ mtd_parts_nb[i] = ARRAY_SIZE(static_partitions);
+ part_type = "static";
+ }
+
+ printk(KERN_NOTICE MSG_PREFIX
+ "using %s partition definition\n",
+ part_type);
+ add_mtd_partitions(impa7_mtd[i],
+ mtd_parts[i], mtd_parts_nb[i]);
+#else
+ add_mtd_device(impa7_mtd[i]);
+
+#endif
+ }
+ else
+ iounmap((void *)impa7_map[i].virt);
+ }
+ return devicesfound == 0 ? -ENXIO : 0;
+}
+
+static void __exit cleanup_impa7(void)
+{
+ int i;
+ for (i=0; i<NUM_FLASHBANKS; i++) {
+ if (impa7_mtd[i]) {
+#ifdef CONFIG_MTD_PARTITIONS
+ del_mtd_partitions(impa7_mtd[i]);
+#else
+ del_mtd_device(impa7_mtd[i]);
+#endif
+ map_destroy(impa7_mtd[i]);
+ iounmap((void *)impa7_map[i].virt);
+ impa7_map[i].virt = 0;
+ }
+ }
+}
+
+module_init(init_impa7);
+module_exit(cleanup_impa7);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pavel Bartusek <pba@sysgo.de>");
+MODULE_DESCRIPTION("MTD map driver for implementa impA7");
diff --git a/drivers/mtd/maps/integrator-flash.c b/drivers/mtd/maps/integrator-flash.c
new file mode 100644
index 000000000000..e39a98a0171c
--- /dev/null
+++ b/drivers/mtd/maps/integrator-flash.c
@@ -0,0 +1,217 @@
+/*======================================================================
+
+ drivers/mtd/maps/integrator-flash.c: ARM Integrator flash map driver
+
+ Copyright (C) 2000 ARM Limited
+ Copyright (C) 2003 Deep Blue Solutions Ltd.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ This is access code for flashes using ARM's flash partitioning
+ standards.
+
+ $Id: integrator-flash.c,v 1.18 2004/11/01 13:26:15 rmk Exp $
+
+======================================================================*/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/init.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/mach/flash.h>
+#include <asm/hardware.h>
+#include <asm/io.h>
+#include <asm/system.h>
+
+#ifdef CONFIG_ARCH_P720T
+#define FLASH_BASE (0x04000000)
+#define FLASH_SIZE (64*1024*1024)
+#endif
+
+struct armflash_info {
+ struct flash_platform_data *plat;
+ struct resource *res;
+ struct mtd_partition *parts;
+ struct mtd_info *mtd;
+ struct map_info map;
+};
+
+static void armflash_set_vpp(struct map_info *map, int on)
+{
+ struct armflash_info *info = container_of(map, struct armflash_info, map);
+
+ if (info->plat && info->plat->set_vpp)
+ info->plat->set_vpp(on);
+}
+
+static const char *probes[] = { "cmdlinepart", "RedBoot", "afs", NULL };
+
+static int armflash_probe(struct device *_dev)
+{
+ struct platform_device *dev = to_platform_device(_dev);
+ struct flash_platform_data *plat = dev->dev.platform_data;
+ struct resource *res = dev->resource;
+ unsigned int size = res->end - res->start + 1;
+ struct armflash_info *info;
+ int err;
+ void __iomem *base;
+
+ info = kmalloc(sizeof(struct armflash_info), GFP_KERNEL);
+ if (!info) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ memset(info, 0, sizeof(struct armflash_info));
+
+ info->plat = plat;
+ if (plat && plat->init) {
+ err = plat->init();
+ if (err)
+ goto no_resource;
+ }
+
+ info->res = request_mem_region(res->start, size, "armflash");
+ if (!info->res) {
+ err = -EBUSY;
+ goto no_resource;
+ }
+
+ base = ioremap(res->start, size);
+ if (!base) {
+ err = -ENOMEM;
+ goto no_mem;
+ }
+
+ /*
+ * look for CFI based flash parts fitted to this board
+ */
+ info->map.size = size;
+ info->map.bankwidth = plat->width;
+ info->map.phys = res->start;
+ info->map.virt = base;
+ info->map.name = dev->dev.bus_id;
+ info->map.set_vpp = armflash_set_vpp;
+
+ simple_map_init(&info->map);
+
+ /*
+ * Also, the CFI layer automatically works out what size
+ * of chips we have, and does the necessary identification
+ * for us automatically.
+ */
+ info->mtd = do_map_probe(plat->map_name, &info->map);
+ if (!info->mtd) {
+ err = -ENXIO;
+ goto no_device;
+ }
+
+ info->mtd->owner = THIS_MODULE;
+
+ err = parse_mtd_partitions(info->mtd, probes, &info->parts, 0);
+ if (err > 0) {
+ err = add_mtd_partitions(info->mtd, info->parts, err);
+ if (err)
+ printk(KERN_ERR
+ "mtd partition registration failed: %d\n", err);
+ }
+
+ if (err == 0)
+ dev_set_drvdata(&dev->dev, info);
+
+ /*
+ * If we got an error, free all resources.
+ */
+ if (err < 0) {
+ if (info->mtd) {
+ del_mtd_partitions(info->mtd);
+ map_destroy(info->mtd);
+ }
+ if (info->parts)
+ kfree(info->parts);
+
+ no_device:
+ iounmap(base);
+ no_mem:
+ release_mem_region(res->start, size);
+ no_resource:
+ if (plat && plat->exit)
+ plat->exit();
+ kfree(info);
+ }
+ out:
+ return err;
+}
+
+static int armflash_remove(struct device *_dev)
+{
+ struct platform_device *dev = to_platform_device(_dev);
+ struct armflash_info *info = dev_get_drvdata(&dev->dev);
+
+ dev_set_drvdata(&dev->dev, NULL);
+
+ if (info) {
+ if (info->mtd) {
+ del_mtd_partitions(info->mtd);
+ map_destroy(info->mtd);
+ }
+ if (info->parts)
+ kfree(info->parts);
+
+ iounmap(info->map.virt);
+ release_resource(info->res);
+ kfree(info->res);
+
+ if (info->plat && info->plat->exit)
+ info->plat->exit();
+
+ kfree(info);
+ }
+
+ return 0;
+}
+
+static struct device_driver armflash_driver = {
+ .name = "armflash",
+ .bus = &platform_bus_type,
+ .probe = armflash_probe,
+ .remove = armflash_remove,
+};
+
+static int __init armflash_init(void)
+{
+ return driver_register(&armflash_driver);
+}
+
+static void __exit armflash_exit(void)
+{
+ driver_unregister(&armflash_driver);
+}
+
+module_init(armflash_init);
+module_exit(armflash_exit);
+
+MODULE_AUTHOR("ARM Ltd");
+MODULE_DESCRIPTION("ARM Integrator CFI map driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/maps/ipaq-flash.c b/drivers/mtd/maps/ipaq-flash.c
new file mode 100644
index 000000000000..712401810841
--- /dev/null
+++ b/drivers/mtd/maps/ipaq-flash.c
@@ -0,0 +1,464 @@
+/*
+ * Flash memory access on iPAQ Handhelds (either SA1100 or PXA250 based)
+ *
+ * (C) 2000 Nicolas Pitre <nico@cam.org>
+ * (C) 2002 Hewlett-Packard Company <jamey.hicks@hp.com>
+ * (C) 2003 Christian Pellegrin <chri@ascensit.com>, <chri@infis.univ.ts.it>: concatenation of multiple flashes
+ *
+ * $Id: ipaq-flash.c,v 1.3 2004/11/04 13:24:15 gleixner Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <asm/page.h>
+#include <asm/mach-types.h>
+#include <asm/system.h>
+#include <asm/errno.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#ifdef CONFIG_MTD_CONCAT
+#include <linux/mtd/concat.h>
+#endif
+
+#include <asm/hardware.h>
+#include <asm/arch-sa1100/h3600.h>
+#include <asm/io.h>
+
+
+#ifndef CONFIG_IPAQ_HANDHELD
+#error This is for iPAQ Handhelds only
+#endif
+#ifdef CONFIG_SA1100_JORNADA56X
+
+static void jornada56x_set_vpp(struct map_info *map, int vpp)
+{
+ if (vpp)
+ GPSR = GPIO_GPIO26;
+ else
+ GPCR = GPIO_GPIO26;
+ GPDR |= GPIO_GPIO26;
+}
+
+#endif
+
+#ifdef CONFIG_SA1100_JORNADA720
+
+static void jornada720_set_vpp(struct map_info *map, int vpp)
+{
+ if (vpp)
+ PPSR |= 0x80;
+ else
+ PPSR &= ~0x80;
+ PPDR |= 0x80;
+}
+
+#endif
+
+#define MAX_IPAQ_CS 2 /* Number of CS we are going to test */
+
+#define IPAQ_MAP_INIT(X) \
+ { \
+ name: "IPAQ flash " X, \
+ }
+
+
+static struct map_info ipaq_map[MAX_IPAQ_CS] = {
+ IPAQ_MAP_INIT("bank 1"),
+ IPAQ_MAP_INIT("bank 2")
+};
+
+static struct mtd_info *my_sub_mtd[MAX_IPAQ_CS] = {
+ NULL,
+ NULL
+};
+
+/*
+ * Here are partition information for all known IPAQ-based devices.
+ * See include/linux/mtd/partitions.h for definition of the mtd_partition
+ * structure.
+ *
+ * The *_max_flash_size is the maximum possible mapped flash size which
+ * is not necessarily the actual flash size. It must be no more than
+ * the value specified in the "struct map_desc *_io_desc" mapping
+ * definition for the corresponding machine.
+ *
+ * Please keep these in alphabetical order, and formatted as per existing
+ * entries. Thanks.
+ */
+
+#ifdef CONFIG_IPAQ_HANDHELD
+static unsigned long h3xxx_max_flash_size = 0x04000000;
+static struct mtd_partition h3xxx_partitions[] = {
+ {
+ name: "H3XXX boot firmware",
+#ifndef CONFIG_LAB
+ size: 0x00040000,
+#else
+ size: 0x00080000,
+#endif
+ offset: 0,
+#ifndef CONFIG_LAB
+ mask_flags: MTD_WRITEABLE, /* force read-only */
+#endif
+ },
+ {
+ name: "H3XXX root jffs2",
+#ifndef CONFIG_LAB
+ size: 0x2000000 - 2*0x40000, /* Warning, this is fixed later */
+ offset: 0x00040000,
+#else
+ size: 0x2000000 - 0x40000 - 0x80000, /* Warning, this is fixed later */
+ offset: 0x00080000,
+#endif
+ },
+ {
+ name: "asset",
+ size: 0x40000,
+ offset: 0x2000000 - 0x40000, /* Warning, this is fixed later */
+ mask_flags: MTD_WRITEABLE, /* force read-only */
+ }
+};
+
+#ifndef CONFIG_MTD_CONCAT
+static struct mtd_partition h3xxx_partitions_bank2[] = {
+ /* this is used only on 2 CS machines when concat is not present */
+ {
+ name: "second H3XXX root jffs2",
+ size: 0x1000000 - 0x40000, /* Warning, this is fixed later */
+ offset: 0x00000000,
+ },
+ {
+ name: "second asset",
+ size: 0x40000,
+ offset: 0x1000000 - 0x40000, /* Warning, this is fixed later */
+ mask_flags: MTD_WRITEABLE, /* force read-only */
+ }
+};
+#endif
+
+static DEFINE_SPINLOCK(ipaq_vpp_lock);
+
+static void h3xxx_set_vpp(struct map_info *map, int vpp)
+{
+ static int nest = 0;
+
+ spin_lock(&ipaq_vpp_lock);
+ if (vpp)
+ nest++;
+ else
+ nest--;
+ if (nest)
+ assign_h3600_egpio(IPAQ_EGPIO_VPP_ON, 1);
+ else
+ assign_h3600_egpio(IPAQ_EGPIO_VPP_ON, 0);
+ spin_unlock(&ipaq_vpp_lock);
+}
+
+#endif
+
+#if defined(CONFIG_SA1100_JORNADA56X) || defined(CONFIG_SA1100_JORNADA720)
+static unsigned long jornada_max_flash_size = 0x02000000;
+static struct mtd_partition jornada_partitions[] = {
+ {
+ name: "Jornada boot firmware",
+ size: 0x00040000,
+ offset: 0,
+ mask_flags: MTD_WRITEABLE, /* force read-only */
+ }, {
+ name: "Jornada root jffs2",
+ size: MTDPART_SIZ_FULL,
+ offset: 0x00040000,
+ }
+};
+#endif
+
+
+static struct mtd_partition *parsed_parts;
+static struct mtd_info *mymtd;
+
+static unsigned long cs_phys[] = {
+#ifdef CONFIG_ARCH_SA1100
+ SA1100_CS0_PHYS,
+ SA1100_CS1_PHYS,
+ SA1100_CS2_PHYS,
+ SA1100_CS3_PHYS,
+ SA1100_CS4_PHYS,
+ SA1100_CS5_PHYS,
+#else
+ PXA_CS0_PHYS,
+ PXA_CS1_PHYS,
+ PXA_CS2_PHYS,
+ PXA_CS3_PHYS,
+ PXA_CS4_PHYS,
+ PXA_CS5_PHYS,
+#endif
+};
+
+static const char *part_probes[] = { "cmdlinepart", "RedBoot", NULL };
+
+static int __init h1900_special_case(void);
+
+int __init ipaq_mtd_init(void)
+{
+ struct mtd_partition *parts = NULL;
+ int nb_parts = 0;
+ int parsed_nr_parts = 0;
+ const char *part_type;
+ int i; /* used when we have >1 flash chips */
+ unsigned long tot_flashsize = 0; /* used when we have >1 flash chips */
+
+ /* Default flash bankwidth */
+ // ipaq_map.bankwidth = (MSC0 & MSC_RBW) ? 2 : 4;
+
+ if (machine_is_h1900())
+ {
+ /* For our intents, the h1900 is not a real iPAQ, so we special-case it. */
+ return h1900_special_case();
+ }
+
+ if (machine_is_h3100() || machine_is_h1900())
+ for(i=0; i<MAX_IPAQ_CS; i++)
+ ipaq_map[i].bankwidth = 2;
+ else
+ for(i=0; i<MAX_IPAQ_CS; i++)
+ ipaq_map[i].bankwidth = 4;
+
+ /*
+ * Static partition definition selection
+ */
+ part_type = "static";
+
+ simple_map_init(&ipaq_map[0]);
+ simple_map_init(&ipaq_map[1]);
+
+#ifdef CONFIG_IPAQ_HANDHELD
+ if (machine_is_ipaq()) {
+ parts = h3xxx_partitions;
+ nb_parts = ARRAY_SIZE(h3xxx_partitions);
+ for(i=0; i<MAX_IPAQ_CS; i++) {
+ ipaq_map[i].size = h3xxx_max_flash_size;
+ ipaq_map[i].set_vpp = h3xxx_set_vpp;
+ ipaq_map[i].phys = cs_phys[i];
+ ipaq_map[i].virt = __ioremap(cs_phys[i], 0x04000000, 0, 1);
+ if (machine_is_h3100 () || machine_is_h1900())
+ ipaq_map[i].bankwidth = 2;
+ }
+ if (machine_is_h3600()) {
+ /* No asset partition here */
+ h3xxx_partitions[1].size += 0x40000;
+ nb_parts--;
+ }
+ }
+#endif
+#ifdef CONFIG_ARCH_H5400
+ if (machine_is_h5400()) {
+ ipaq_map[0].size = 0x02000000;
+ ipaq_map[1].size = 0x02000000;
+ ipaq_map[1].phys = 0x02000000;
+ ipaq_map[1].virt = ipaq_map[0].virt + 0x02000000;
+ }
+#endif
+#ifdef CONFIG_ARCH_H1900
+ if (machine_is_h1900()) {
+ ipaq_map[0].size = 0x00400000;
+ ipaq_map[1].size = 0x02000000;
+ ipaq_map[1].phys = 0x00080000;
+ ipaq_map[1].virt = ipaq_map[0].virt + 0x00080000;
+ }
+#endif
+
+#ifdef CONFIG_SA1100_JORNADA56X
+ if (machine_is_jornada56x()) {
+ parts = jornada_partitions;
+ nb_parts = ARRAY_SIZE(jornada_partitions);
+ ipaq_map[0].size = jornada_max_flash_size;
+ ipaq_map[0].set_vpp = jornada56x_set_vpp;
+ ipaq_map[0].virt = (__u32)__ioremap(0x0, 0x04000000, 0, 1);
+ }
+#endif
+#ifdef CONFIG_SA1100_JORNADA720
+ if (machine_is_jornada720()) {
+ parts = jornada_partitions;
+ nb_parts = ARRAY_SIZE(jornada_partitions);
+ ipaq_map[0].size = jornada_max_flash_size;
+ ipaq_map[0].set_vpp = jornada720_set_vpp;
+ }
+#endif
+
+
+ if (machine_is_ipaq()) { /* for iPAQs only */
+ for(i=0; i<MAX_IPAQ_CS; i++) {
+ printk(KERN_NOTICE "iPAQ flash: probing %d-bit flash bus, window=%lx with CFI.\n", ipaq_map[i].bankwidth*8, ipaq_map[i].virt);
+ my_sub_mtd[i] = do_map_probe("cfi_probe", &ipaq_map[i]);
+ if (!my_sub_mtd[i]) {
+ printk(KERN_NOTICE "iPAQ flash: probing %d-bit flash bus, window=%lx with JEDEC.\n", ipaq_map[i].bankwidth*8, ipaq_map[i].virt);
+ my_sub_mtd[i] = do_map_probe("jedec_probe", &ipaq_map[i]);
+ }
+ if (!my_sub_mtd[i]) {
+ printk(KERN_NOTICE "iPAQ flash: failed to find flash.\n");
+ if (i)
+ break;
+ else
+ return -ENXIO;
+ } else
+ printk(KERN_NOTICE "iPAQ flash: found %d bytes\n", my_sub_mtd[i]->size);
+
+ /* do we really need this debugging? --joshua 20030703 */
+ // printk("my_sub_mtd[%d]=%p\n", i, my_sub_mtd[i]);
+ my_sub_mtd[i]->owner = THIS_MODULE;
+ tot_flashsize += my_sub_mtd[i]->size;
+ }
+#ifdef CONFIG_MTD_CONCAT
+ /* fix the asset location */
+# ifdef CONFIG_LAB
+ h3xxx_partitions[1].size = tot_flashsize - 0x40000 - 0x80000 /* extra big boot block */;
+# else
+ h3xxx_partitions[1].size = tot_flashsize - 2 * 0x40000;
+# endif
+ h3xxx_partitions[2].offset = tot_flashsize - 0x40000;
+ /* and concat the devices */
+ mymtd = mtd_concat_create(&my_sub_mtd[0], i,
+ "ipaq");
+ if (!mymtd) {
+ printk("Cannot create iPAQ concat device\n");
+ return -ENXIO;
+ }
+#else
+ mymtd = my_sub_mtd[0];
+
+ /*
+ *In the very near future, command line partition parsing
+ * will use the device name as 'mtd-id' instead of a value
+ * passed to the parse_cmdline_partitions() routine. Since
+ * the bootldr says 'ipaq', make sure it continues to work.
+ */
+ mymtd->name = "ipaq";
+
+ if ((machine_is_h3600())) {
+# ifdef CONFIG_LAB
+ h3xxx_partitions[1].size = my_sub_mtd[0]->size - 0x80000;
+# else
+ h3xxx_partitions[1].size = my_sub_mtd[0]->size - 0x40000;
+# endif
+ nb_parts = 2;
+ } else {
+# ifdef CONFIG_LAB
+ h3xxx_partitions[1].size = my_sub_mtd[0]->size - 0x40000 - 0x80000; /* extra big boot block */
+# else
+ h3xxx_partitions[1].size = my_sub_mtd[0]->size - 2*0x40000;
+# endif
+ h3xxx_partitions[2].offset = my_sub_mtd[0]->size - 0x40000;
+ }
+
+ if (my_sub_mtd[1]) {
+# ifdef CONFIG_LAB
+ h3xxx_partitions_bank2[0].size = my_sub_mtd[1]->size - 0x80000;
+# else
+ h3xxx_partitions_bank2[0].size = my_sub_mtd[1]->size - 0x40000;
+# endif
+ h3xxx_partitions_bank2[1].offset = my_sub_mtd[1]->size - 0x40000;
+ }
+#endif
+ }
+ else {
+ /*
+ * Now let's probe for the actual flash. Do it here since
+ * specific machine settings might have been set above.
+ */
+ printk(KERN_NOTICE "IPAQ flash: probing %d-bit flash bus, window=%lx\n", ipaq_map[0].bankwidth*8, ipaq_map[0].virt);
+ mymtd = do_map_probe("cfi_probe", &ipaq_map[0]);
+ if (!mymtd)
+ return -ENXIO;
+ mymtd->owner = THIS_MODULE;
+ }
+
+
+ /*
+ * Dynamic partition selection stuff (might override the static ones)
+ */
+
+ i = parse_mtd_partitions(mymtd, part_probes, &parsed_parts, 0);
+
+ if (i > 0) {
+ nb_parts = parsed_nr_parts = i;
+ parts = parsed_parts;
+ part_type = "dynamic";
+ }
+
+ if (!parts) {
+ printk(KERN_NOTICE "IPAQ flash: no partition info available, registering whole flash at once\n");
+ add_mtd_device(mymtd);
+#ifndef CONFIG_MTD_CONCAT
+ if (my_sub_mtd[1])
+ add_mtd_device(my_sub_mtd[1]);
+#endif
+ } else {
+ printk(KERN_NOTICE "Using %s partition definition\n", part_type);
+ add_mtd_partitions(mymtd, parts, nb_parts);
+#ifndef CONFIG_MTD_CONCAT
+ if (my_sub_mtd[1])
+ add_mtd_partitions(my_sub_mtd[1], h3xxx_partitions_bank2, ARRAY_SIZE(h3xxx_partitions_bank2));
+#endif
+ }
+
+ return 0;
+}
+
+static void __exit ipaq_mtd_cleanup(void)
+{
+ int i;
+
+ if (mymtd) {
+ del_mtd_partitions(mymtd);
+#ifndef CONFIG_MTD_CONCAT
+ if (my_sub_mtd[1])
+ del_mtd_partitions(my_sub_mtd[1]);
+#endif
+ map_destroy(mymtd);
+#ifdef CONFIG_MTD_CONCAT
+ for(i=0; i<MAX_IPAQ_CS; i++)
+#else
+ for(i=1; i<MAX_IPAQ_CS; i++)
+#endif
+ {
+ if (my_sub_mtd[i])
+ map_destroy(my_sub_mtd[i]);
+ }
+ if (parsed_parts)
+ kfree(parsed_parts);
+ }
+}
+
+static int __init h1900_special_case(void)
+{
+ /* The iPAQ h1900 is a special case - it has weird ROM. */
+ simple_map_init(&ipaq_map[0]);
+ ipaq_map[0].size = 0x80000;
+ ipaq_map[0].set_vpp = h3xxx_set_vpp;
+ ipaq_map[0].phys = 0x0;
+ ipaq_map[0].virt = __ioremap(0x0, 0x04000000, 0, 1);
+ ipaq_map[0].bankwidth = 2;
+
+ printk(KERN_NOTICE "iPAQ flash: probing %d-bit flash bus, window=%lx with JEDEC.\n", ipaq_map[0].bankwidth*8, ipaq_map[0].virt);
+ mymtd = do_map_probe("jedec_probe", &ipaq_map[0]);
+ if (!mymtd)
+ return -ENODEV;
+ add_mtd_device(mymtd);
+ printk(KERN_NOTICE "iPAQ flash: registered h1910 flash\n");
+
+ return 0;
+}
+
+module_init(ipaq_mtd_init);
+module_exit(ipaq_mtd_cleanup);
+
+MODULE_AUTHOR("Jamey Hicks");
+MODULE_DESCRIPTION("IPAQ CFI map driver");
+MODULE_LICENSE("MIT");
diff --git a/drivers/mtd/maps/iq80310.c b/drivers/mtd/maps/iq80310.c
new file mode 100644
index 000000000000..558d014e7acc
--- /dev/null
+++ b/drivers/mtd/maps/iq80310.c
@@ -0,0 +1,119 @@
+/*
+ * $Id: iq80310.c,v 1.20 2004/11/04 13:24:15 gleixner Exp $
+ *
+ * Mapping for the Intel XScale IQ80310 evaluation board
+ *
+ * Author: Nicolas Pitre
+ * Copyright: (C) 2001 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+
+#define WINDOW_ADDR 0
+#define WINDOW_SIZE 8*1024*1024
+#define BUSWIDTH 1
+
+static struct mtd_info *mymtd;
+
+static struct map_info iq80310_map = {
+ .name = "IQ80310 flash",
+ .size = WINDOW_SIZE,
+ .bankwidth = BUSWIDTH,
+ .phys = WINDOW_ADDR
+};
+
+static struct mtd_partition iq80310_partitions[4] = {
+ {
+ .name = "Firmware",
+ .size = 0x00080000,
+ .offset = 0,
+ .mask_flags = MTD_WRITEABLE /* force read-only */
+ },{
+ .name = "Kernel",
+ .size = 0x000a0000,
+ .offset = 0x00080000,
+ },{
+ .name = "Filesystem",
+ .size = 0x00600000,
+ .offset = 0x00120000
+ },{
+ .name = "RedBoot",
+ .size = 0x000e0000,
+ .offset = 0x00720000,
+ .mask_flags = MTD_WRITEABLE
+ }
+};
+
+static struct mtd_info *mymtd;
+static struct mtd_partition *parsed_parts;
+static const char *probes[] = { "RedBoot", "cmdlinepart", NULL };
+
+static int __init init_iq80310(void)
+{
+ struct mtd_partition *parts;
+ int nb_parts = 0;
+ int parsed_nr_parts = 0;
+ int ret;
+
+ iq80310_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE);
+ if (!iq80310_map.virt) {
+ printk("Failed to ioremap\n");
+ return -EIO;
+ }
+ simple_map_init(&iq80310_map);
+
+ mymtd = do_map_probe("cfi_probe", &iq80310_map);
+ if (!mymtd) {
+ iounmap((void *)iq80310_map.virt);
+ return -ENXIO;
+ }
+ mymtd->owner = THIS_MODULE;
+
+ ret = parse_mtd_partitions(mymtd, probes, &parsed_parts, 0);
+
+ if (ret > 0)
+ parsed_nr_parts = ret;
+
+ if (parsed_nr_parts > 0) {
+ parts = parsed_parts;
+ nb_parts = parsed_nr_parts;
+ } else {
+ parts = iq80310_partitions;
+ nb_parts = ARRAY_SIZE(iq80310_partitions);
+ }
+ add_mtd_partitions(mymtd, parts, nb_parts);
+ return 0;
+}
+
+static void __exit cleanup_iq80310(void)
+{
+ if (mymtd) {
+ del_mtd_partitions(mymtd);
+ map_destroy(mymtd);
+ if (parsed_parts)
+ kfree(parsed_parts);
+ }
+ if (iq80310_map.virt)
+ iounmap((void *)iq80310_map.virt);
+}
+
+module_init(init_iq80310);
+module_exit(cleanup_iq80310);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nicolas Pitre <nico@cam.org>");
+MODULE_DESCRIPTION("MTD map driver for Intel XScale IQ80310 evaluation board");
diff --git a/drivers/mtd/maps/ixp2000.c b/drivers/mtd/maps/ixp2000.c
new file mode 100644
index 000000000000..c5b5f447e34b
--- /dev/null
+++ b/drivers/mtd/maps/ixp2000.c
@@ -0,0 +1,280 @@
+/*
+ * $Id: ixp2000.c,v 1.5 2004/11/16 17:15:48 dsaxena Exp $
+ *
+ * drivers/mtd/maps/ixp2000.c
+ *
+ * Mapping for the Intel XScale IXP2000 based systems
+ *
+ * Copyright (C) 2002 Intel Corp.
+ * Copyright (C) 2003-2004 MontaVista Software, Inc.
+ *
+ * Original Author: Naeem M Afzal <naeem.m.afzal@intel.com>
+ * Maintainer: Deepak Saxena <dsaxena@plexity.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+
+#include <asm/io.h>
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+#include <asm/mach/flash.h>
+
+#include <linux/reboot.h>
+
+struct ixp2000_flash_info {
+ struct mtd_info *mtd;
+ struct map_info map;
+ struct mtd_partition *partitions;
+ struct resource *res;
+ int nr_banks;
+};
+
+static inline unsigned long flash_bank_setup(struct map_info *map, unsigned long ofs)
+{
+ unsigned long (*set_bank)(unsigned long) =
+ (unsigned long(*)(unsigned long))map->map_priv_2;
+
+ return (set_bank ? set_bank(ofs) : ofs);
+}
+
+#ifdef __ARMEB__
+/*
+ * Rev A0 and A1 of IXP2400 silicon have a broken addressing unit which
+ * causes the lower address bits to be XORed with 0x11 on 8 bit accesses
+ * and XORed with 0x10 on 16 bit accesses. See the spec update, erratum 44.
+ */
+static int erratum44_workaround = 0;
+
+static inline unsigned long address_fix8_write(unsigned long addr)
+{
+ if (erratum44_workaround) {
+ return (addr ^ 3);
+ }
+ return addr;
+}
+#else
+
+#define address_fix8_write(x) (x)
+#endif
+
+static map_word ixp2000_flash_read8(struct map_info *map, unsigned long ofs)
+{
+ map_word val;
+
+ val.x[0] = *((u8 *)(map->map_priv_1 + flash_bank_setup(map, ofs)));
+ return val;
+}
+
+/*
+ * We can't use the standard memcpy due to the broken SlowPort
+ * address translation on rev A0 and A1 silicon and the fact that
+ * we have banked flash.
+ */
+static void ixp2000_flash_copy_from(struct map_info *map, void *to,
+ unsigned long from, ssize_t len)
+{
+ from = flash_bank_setup(map, from);
+ while(len--)
+ *(__u8 *) to++ = *(__u8 *)(map->map_priv_1 + from++);
+}
+
+static void ixp2000_flash_write8(struct map_info *map, map_word d, unsigned long ofs)
+{
+ *(__u8 *) (address_fix8_write(map->map_priv_1 +
+ flash_bank_setup(map, ofs))) = d.x[0];
+}
+
+static void ixp2000_flash_copy_to(struct map_info *map, unsigned long to,
+ const void *from, ssize_t len)
+{
+ to = flash_bank_setup(map, to);
+ while(len--) {
+ unsigned long tmp = address_fix8_write(map->map_priv_1 + to++);
+ *(__u8 *)(tmp) = *(__u8 *)(from++);
+ }
+}
+
+
+static int ixp2000_flash_remove(struct device *_dev)
+{
+ struct platform_device *dev = to_platform_device(_dev);
+ struct flash_platform_data *plat = dev->dev.platform_data;
+ struct ixp2000_flash_info *info = dev_get_drvdata(&dev->dev);
+
+ dev_set_drvdata(&dev->dev, NULL);
+
+ if(!info)
+ return 0;
+
+ if (info->mtd) {
+ del_mtd_partitions(info->mtd);
+ map_destroy(info->mtd);
+ }
+ if (info->map.map_priv_1)
+ iounmap((void *) info->map.map_priv_1);
+
+ if (info->partitions) {
+ kfree(info->partitions); }
+
+ if (info->res) {
+ release_resource(info->res);
+ kfree(info->res);
+ }
+
+ if (plat->exit)
+ plat->exit();
+
+ return 0;
+}
+
+
+static int ixp2000_flash_probe(struct device *_dev)
+{
+ static const char *probes[] = { "RedBoot", "cmdlinepart", NULL };
+ struct platform_device *dev = to_platform_device(_dev);
+ struct ixp2000_flash_data *ixp_data = dev->dev.platform_data;
+ struct flash_platform_data *plat;
+ struct ixp2000_flash_info *info;
+ unsigned long window_size;
+ int err = -1;
+
+ if (!ixp_data)
+ return -ENODEV;
+
+ plat = ixp_data->platform_data;
+ if (!plat)
+ return -ENODEV;
+
+ window_size = dev->resource->end - dev->resource->start + 1;
+ dev_info(_dev, "Probe of IXP2000 flash(%d banks x %dMiB)\n",
+ ixp_data->nr_banks, ((u32)window_size >> 20));
+
+ if (plat->width != 1) {
+ dev_err(_dev, "IXP2000 MTD map only supports 8-bit mode, asking for %d\n",
+ plat->width * 8);
+ return -EIO;
+ }
+
+ info = kmalloc(sizeof(struct ixp2000_flash_info), GFP_KERNEL);
+ if(!info) {
+ err = -ENOMEM;
+ goto Error;
+ }
+ memzero(info, sizeof(struct ixp2000_flash_info));
+
+ dev_set_drvdata(&dev->dev, info);
+
+ /*
+ * Tell the MTD layer we're not 1:1 mapped so that it does
+ * not attempt to do a direct access on us.
+ */
+ info->map.phys = NO_XIP;
+
+ info->nr_banks = ixp_data->nr_banks;
+ info->map.size = ixp_data->nr_banks * window_size;
+ info->map.bankwidth = 1;
+
+ /*
+ * map_priv_2 is used to store a ptr to to the bank_setup routine
+ */
+ info->map.map_priv_2 = (void __iomem *) ixp_data->bank_setup;
+
+ info->map.name = dev->dev.bus_id;
+ info->map.read = ixp2000_flash_read8;
+ info->map.write = ixp2000_flash_write8;
+ info->map.copy_from = ixp2000_flash_copy_from;
+ info->map.copy_to = ixp2000_flash_copy_to;
+
+ info->res = request_mem_region(dev->resource->start,
+ dev->resource->end - dev->resource->start + 1,
+ dev->dev.bus_id);
+ if (!info->res) {
+ dev_err(_dev, "Could not reserve memory region\n");
+ err = -ENOMEM;
+ goto Error;
+ }
+
+ info->map.map_priv_1 = ioremap(dev->resource->start,
+ dev->resource->end - dev->resource->start + 1);
+ if (!info->map.map_priv_1) {
+ dev_err(_dev, "Failed to ioremap flash region\n");
+ err = -EIO;
+ goto Error;
+ }
+
+ /*
+ * Setup read mode for FLASH
+ */
+ *IXP2000_SLOWPORT_FRM = 1;
+
+#if defined(__ARMEB__)
+ /*
+ * Enable erratum 44 workaround for NPUs with broken slowport
+ */
+
+ erratum44_workaround = ixp2000_has_broken_slowport();
+ dev_info(_dev, "Erratum 44 workaround %s\n",
+ erratum44_workaround ? "enabled" : "disabled");
+#endif
+
+ info->mtd = do_map_probe(plat->map_name, &info->map);
+ if (!info->mtd) {
+ dev_err(_dev, "map_probe failed\n");
+ err = -ENXIO;
+ goto Error;
+ }
+ info->mtd->owner = THIS_MODULE;
+
+ err = parse_mtd_partitions(info->mtd, probes, &info->partitions, 0);
+ if (err > 0) {
+ err = add_mtd_partitions(info->mtd, info->partitions, err);
+ if(err)
+ dev_err(_dev, "Could not parse partitions\n");
+ }
+
+ if (err)
+ goto Error;
+
+ return 0;
+
+Error:
+ ixp2000_flash_remove(_dev);
+ return err;
+}
+
+static struct device_driver ixp2000_flash_driver = {
+ .name = "IXP2000-Flash",
+ .bus = &platform_bus_type,
+ .probe = &ixp2000_flash_probe,
+ .remove = &ixp2000_flash_remove
+};
+
+static int __init ixp2000_flash_init(void)
+{
+ return driver_register(&ixp2000_flash_driver);
+}
+
+static void __exit ixp2000_flash_exit(void)
+{
+ driver_unregister(&ixp2000_flash_driver);
+}
+
+module_init(ixp2000_flash_init);
+module_exit(ixp2000_flash_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>");
+
diff --git a/drivers/mtd/maps/ixp4xx.c b/drivers/mtd/maps/ixp4xx.c
new file mode 100644
index 000000000000..5afe660aa2c4
--- /dev/null
+++ b/drivers/mtd/maps/ixp4xx.c
@@ -0,0 +1,259 @@
+/*
+ * $Id: ixp4xx.c,v 1.7 2004/11/04 13:24:15 gleixner Exp $
+ *
+ * drivers/mtd/maps/ixp4xx.c
+ *
+ * MTD Map file for IXP4XX based systems. Please do not make per-board
+ * changes in here. If your board needs special setup, do it in your
+ * platform level code in arch/arm/mach-ixp4xx/board-setup.c
+ *
+ * Original Author: Intel Corporation
+ * Maintainer: Deepak Saxena <dsaxena@mvista.com>
+ *
+ * Copyright (C) 2002 Intel Corporation
+ * Copyright (C) 2003-2004 MontaVista Software, Inc.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <asm/io.h>
+#include <asm/mach-types.h>
+#include <asm/mach/flash.h>
+
+#include <linux/reboot.h>
+
+#ifndef __ARMEB__
+#define BYTE0(h) ((h) & 0xFF)
+#define BYTE1(h) (((h) >> 8) & 0xFF)
+#else
+#define BYTE0(h) (((h) >> 8) & 0xFF)
+#define BYTE1(h) ((h) & 0xFF)
+#endif
+
+static map_word ixp4xx_read16(struct map_info *map, unsigned long ofs)
+{
+ map_word val;
+ val.x[0] = *(__u16 *) (map->map_priv_1 + ofs);
+ return val;
+}
+
+/*
+ * The IXP4xx expansion bus only allows 16-bit wide acceses
+ * when attached to a 16-bit wide device (such as the 28F128J3A),
+ * so we can't just memcpy_fromio().
+ */
+static void ixp4xx_copy_from(struct map_info *map, void *to,
+ unsigned long from, ssize_t len)
+{
+ int i;
+ u8 *dest = (u8 *) to;
+ u16 *src = (u16 *) (map->map_priv_1 + from);
+ u16 data;
+
+ for (i = 0; i < (len / 2); i++) {
+ data = src[i];
+ dest[i * 2] = BYTE0(data);
+ dest[i * 2 + 1] = BYTE1(data);
+ }
+
+ if (len & 1)
+ dest[len - 1] = BYTE0(src[i]);
+}
+
+/*
+ * Unaligned writes are ignored, causing the 8-bit
+ * probe to fail and proceed to the 16-bit probe (which succeeds).
+ */
+static void ixp4xx_probe_write16(struct map_info *map, map_word d, unsigned long adr)
+{
+ if (!(adr & 1))
+ *(__u16 *) (map->map_priv_1 + adr) = d.x[0];
+}
+
+/*
+ * Fast write16 function without the probing check above
+ */
+static void ixp4xx_write16(struct map_info *map, map_word d, unsigned long adr)
+{
+ *(__u16 *) (map->map_priv_1 + adr) = d.x[0];
+}
+
+struct ixp4xx_flash_info {
+ struct mtd_info *mtd;
+ struct map_info map;
+ struct mtd_partition *partitions;
+ struct resource *res;
+};
+
+static const char *probes[] = { "RedBoot", "cmdlinepart", NULL };
+
+static int ixp4xx_flash_remove(struct device *_dev)
+{
+ struct platform_device *dev = to_platform_device(_dev);
+ struct flash_platform_data *plat = dev->dev.platform_data;
+ struct ixp4xx_flash_info *info = dev_get_drvdata(&dev->dev);
+ map_word d;
+
+ dev_set_drvdata(&dev->dev, NULL);
+
+ if(!info)
+ return 0;
+
+ /*
+ * This is required for a soft reboot to work.
+ */
+ d.x[0] = 0xff;
+ ixp4xx_write16(&info->map, d, 0x55 * 0x2);
+
+ if (info->mtd) {
+ del_mtd_partitions(info->mtd);
+ map_destroy(info->mtd);
+ }
+ if (info->map.map_priv_1)
+ iounmap((void *) info->map.map_priv_1);
+
+ if (info->partitions)
+ kfree(info->partitions);
+
+ if (info->res) {
+ release_resource(info->res);
+ kfree(info->res);
+ }
+
+ if (plat->exit)
+ plat->exit();
+
+ /* Disable flash write */
+ *IXP4XX_EXP_CS0 &= ~IXP4XX_FLASH_WRITABLE;
+
+ return 0;
+}
+
+static int ixp4xx_flash_probe(struct device *_dev)
+{
+ struct platform_device *dev = to_platform_device(_dev);
+ struct flash_platform_data *plat = dev->dev.platform_data;
+ struct ixp4xx_flash_info *info;
+ int err = -1;
+
+ if (!plat)
+ return -ENODEV;
+
+ if (plat->init) {
+ err = plat->init();
+ if (err)
+ return err;
+ }
+
+ info = kmalloc(sizeof(struct ixp4xx_flash_info), GFP_KERNEL);
+ if(!info) {
+ err = -ENOMEM;
+ goto Error;
+ }
+ memzero(info, sizeof(struct ixp4xx_flash_info));
+
+ dev_set_drvdata(&dev->dev, info);
+
+ /*
+ * Enable flash write
+ * TODO: Move this out to board specific code
+ */
+ *IXP4XX_EXP_CS0 |= IXP4XX_FLASH_WRITABLE;
+
+ /*
+ * Tell the MTD layer we're not 1:1 mapped so that it does
+ * not attempt to do a direct access on us.
+ */
+ info->map.phys = NO_XIP;
+ info->map.size = dev->resource->end - dev->resource->start + 1;
+
+ /*
+ * We only support 16-bit accesses for now. If and when
+ * any board use 8-bit access, we'll fixup the driver to
+ * handle that.
+ */
+ info->map.bankwidth = 2;
+ info->map.name = dev->dev.bus_id;
+ info->map.read = ixp4xx_read16,
+ info->map.write = ixp4xx_probe_write16,
+ info->map.copy_from = ixp4xx_copy_from,
+
+ info->res = request_mem_region(dev->resource->start,
+ dev->resource->end - dev->resource->start + 1,
+ "IXP4XXFlash");
+ if (!info->res) {
+ printk(KERN_ERR "IXP4XXFlash: Could not reserve memory region\n");
+ err = -ENOMEM;
+ goto Error;
+ }
+
+ info->map.map_priv_1 = ioremap(dev->resource->start,
+ dev->resource->end - dev->resource->start + 1);
+ if (!info->map.map_priv_1) {
+ printk(KERN_ERR "IXP4XXFlash: Failed to ioremap region\n");
+ err = -EIO;
+ goto Error;
+ }
+
+ info->mtd = do_map_probe(plat->map_name, &info->map);
+ if (!info->mtd) {
+ printk(KERN_ERR "IXP4XXFlash: map_probe failed\n");
+ err = -ENXIO;
+ goto Error;
+ }
+ info->mtd->owner = THIS_MODULE;
+
+ /* Use the fast version */
+ info->map.write = ixp4xx_write16,
+
+ err = parse_mtd_partitions(info->mtd, probes, &info->partitions, 0);
+ if (err > 0) {
+ err = add_mtd_partitions(info->mtd, info->partitions, err);
+ if(err)
+ printk(KERN_ERR "Could not parse partitions\n");
+ }
+
+ if (err)
+ goto Error;
+
+ return 0;
+
+Error:
+ ixp4xx_flash_remove(_dev);
+ return err;
+}
+
+static struct device_driver ixp4xx_flash_driver = {
+ .name = "IXP4XX-Flash",
+ .bus = &platform_bus_type,
+ .probe = ixp4xx_flash_probe,
+ .remove = ixp4xx_flash_remove,
+};
+
+static int __init ixp4xx_flash_init(void)
+{
+ return driver_register(&ixp4xx_flash_driver);
+}
+
+static void __exit ixp4xx_flash_exit(void)
+{
+ driver_unregister(&ixp4xx_flash_driver);
+}
+
+
+module_init(ixp4xx_flash_init);
+module_exit(ixp4xx_flash_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MTD map driver for Intel IXP4xx systems")
+MODULE_AUTHOR("Deepak Saxena");
+
diff --git a/drivers/mtd/maps/l440gx.c b/drivers/mtd/maps/l440gx.c
new file mode 100644
index 000000000000..b08668212ab7
--- /dev/null
+++ b/drivers/mtd/maps/l440gx.c
@@ -0,0 +1,157 @@
+/*
+ * $Id: l440gx.c,v 1.17 2004/11/28 09:40:39 dwmw2 Exp $
+ *
+ * BIOS Flash chip on Intel 440GX board.
+ *
+ * Bugs this currently does not work under linuxBIOS.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/config.h>
+
+#define PIIXE_IOBASE_RESOURCE 11
+
+#define WINDOW_ADDR 0xfff00000
+#define WINDOW_SIZE 0x00100000
+#define BUSWIDTH 1
+
+static u32 iobase;
+#define IOBASE iobase
+#define TRIBUF_PORT (IOBASE+0x37)
+#define VPP_PORT (IOBASE+0x28)
+
+static struct mtd_info *mymtd;
+
+
+/* Is this really the vpp port? */
+static void l440gx_set_vpp(struct map_info *map, int vpp)
+{
+ unsigned long l;
+
+ l = inl(VPP_PORT);
+ if (vpp) {
+ l |= 1;
+ } else {
+ l &= ~1;
+ }
+ outl(l, VPP_PORT);
+}
+
+static struct map_info l440gx_map = {
+ .name = "L440GX BIOS",
+ .size = WINDOW_SIZE,
+ .bankwidth = BUSWIDTH,
+ .phys = WINDOW_ADDR,
+#if 0
+ /* FIXME verify that this is the
+ * appripriate code for vpp enable/disable
+ */
+ .set_vpp = l440gx_set_vpp
+#endif
+};
+
+static int __init init_l440gx(void)
+{
+ struct pci_dev *dev, *pm_dev;
+ struct resource *pm_iobase;
+ __u16 word;
+
+ dev = pci_find_device(PCI_VENDOR_ID_INTEL,
+ PCI_DEVICE_ID_INTEL_82371AB_0, NULL);
+
+ pm_dev = pci_find_device(PCI_VENDOR_ID_INTEL,
+ PCI_DEVICE_ID_INTEL_82371AB_3, NULL);
+
+ if (!dev || !pm_dev) {
+ printk(KERN_NOTICE "L440GX flash mapping: failed to find PIIX4 ISA bridge, cannot continue\n");
+ return -ENODEV;
+ }
+
+ l440gx_map.virt = ioremap_nocache(WINDOW_ADDR, WINDOW_SIZE);
+
+ if (!l440gx_map.virt) {
+ printk(KERN_WARNING "Failed to ioremap L440GX flash region\n");
+ return -ENOMEM;
+ }
+ simple_map_init(&l440gx_map);
+ printk(KERN_NOTICE "window_addr = 0x%08lx\n", (unsigned long)l440gx_map.virt);
+
+ /* Setup the pm iobase resource
+ * This code should move into some kind of generic bridge
+ * driver but for the moment I'm content with getting the
+ * allocation correct.
+ */
+ pm_iobase = &pm_dev->resource[PIIXE_IOBASE_RESOURCE];
+ if (!(pm_iobase->flags & IORESOURCE_IO)) {
+ pm_iobase->name = "pm iobase";
+ pm_iobase->start = 0;
+ pm_iobase->end = 63;
+ pm_iobase->flags = IORESOURCE_IO;
+
+ /* Put the current value in the resource */
+ pci_read_config_dword(pm_dev, 0x40, &iobase);
+ iobase &= ~1;
+ pm_iobase->start += iobase & ~1;
+ pm_iobase->end += iobase & ~1;
+
+ /* Allocate the resource region */
+ if (pci_assign_resource(pm_dev, PIIXE_IOBASE_RESOURCE) != 0) {
+ printk(KERN_WARNING "Could not allocate pm iobase resource\n");
+ iounmap(l440gx_map.virt);
+ return -ENXIO;
+ }
+ }
+ /* Set the iobase */
+ iobase = pm_iobase->start;
+ pci_write_config_dword(pm_dev, 0x40, iobase | 1);
+
+
+ /* Set XBCS# */
+ pci_read_config_word(dev, 0x4e, &word);
+ word |= 0x4;
+ pci_write_config_word(dev, 0x4e, word);
+
+ /* Supply write voltage to the chip */
+ l440gx_set_vpp(&l440gx_map, 1);
+
+ /* Enable the gate on the WE line */
+ outb(inb(TRIBUF_PORT) & ~1, TRIBUF_PORT);
+
+ printk(KERN_NOTICE "Enabled WE line to L440GX BIOS flash chip.\n");
+
+ mymtd = do_map_probe("jedec_probe", &l440gx_map);
+ if (!mymtd) {
+ printk(KERN_NOTICE "JEDEC probe on BIOS chip failed. Using ROM\n");
+ mymtd = do_map_probe("map_rom", &l440gx_map);
+ }
+ if (mymtd) {
+ mymtd->owner = THIS_MODULE;
+
+ add_mtd_device(mymtd);
+ return 0;
+ }
+
+ iounmap(l440gx_map.virt);
+ return -ENXIO;
+}
+
+static void __exit cleanup_l440gx(void)
+{
+ del_mtd_device(mymtd);
+ map_destroy(mymtd);
+
+ iounmap(l440gx_map.virt);
+}
+
+module_init(init_l440gx);
+module_exit(cleanup_l440gx);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("MTD map driver for BIOS chips on Intel L440GX motherboards");
diff --git a/drivers/mtd/maps/lasat.c b/drivers/mtd/maps/lasat.c
new file mode 100644
index 000000000000..c658d4045ef2
--- /dev/null
+++ b/drivers/mtd/maps/lasat.c
@@ -0,0 +1,102 @@
+/*
+ * Flash device on Lasat 100 and 200 boards
+ *
+ * (C) 2002 Brian Murphy <brian@murphy.dk>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * $Id: lasat.c,v 1.9 2004/11/04 13:24:15 gleixner Exp $
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/config.h>
+#include <asm/lasat/lasat.h>
+
+static struct mtd_info *lasat_mtd;
+
+static struct mtd_partition partition_info[LASAT_MTD_LAST];
+static char *lasat_mtd_partnames[] = {"Bootloader", "Service", "Normal", "Filesystem", "Config"};
+
+static void lasat_set_vpp(struct map_info *map, int vpp)
+{
+ if (vpp)
+ *lasat_misc->flash_wp_reg |= 1 << lasat_misc->flash_wp_bit;
+ else
+ *lasat_misc->flash_wp_reg &= ~(1 << lasat_misc->flash_wp_bit);
+}
+
+static struct map_info lasat_map = {
+ .name = "LASAT flash",
+ .bankwidth = 4,
+ .set_vpp = lasat_set_vpp
+};
+
+static int __init init_lasat(void)
+{
+ int i;
+ /* since we use AMD chips and set_vpp is not implimented
+ * for these (yet) we still have to permanently enable flash write */
+ printk(KERN_NOTICE "Unprotecting flash\n");
+ ENABLE_VPP((&lasat_map));
+
+ lasat_map.phys = lasat_flash_partition_start(LASAT_MTD_BOOTLOADER);
+ lasat_map.virt = ioremap_nocache(
+ lasat_map.phys, lasat_board_info.li_flash_size);
+ lasat_map.size = lasat_board_info.li_flash_size;
+
+ simple_map_init(&lasat_map);
+
+ for (i=0; i < LASAT_MTD_LAST; i++)
+ partition_info[i].name = lasat_mtd_partnames[i];
+
+ lasat_mtd = do_map_probe("cfi_probe", &lasat_map);
+
+ if (!lasat_mtd)
+ lasat_mtd = do_map_probe("jedec_probe", &lasat_map);
+
+ if (lasat_mtd) {
+ u32 size, offset = 0;
+
+ lasat_mtd->owner = THIS_MODULE;
+
+ for (i=0; i < LASAT_MTD_LAST; i++) {
+ size = lasat_flash_partition_size(i);
+ partition_info[i].size = size;
+ partition_info[i].offset = offset;
+ offset += size;
+ }
+
+ add_mtd_partitions( lasat_mtd, partition_info, LASAT_MTD_LAST );
+ return 0;
+ }
+
+ return -ENXIO;
+}
+
+static void __exit cleanup_lasat(void)
+{
+ if (lasat_mtd) {
+ del_mtd_partitions(lasat_mtd);
+ map_destroy(lasat_mtd);
+ }
+ if (lasat_map.virt) {
+ lasat_map.virt = 0;
+ }
+}
+
+module_init(init_lasat);
+module_exit(cleanup_lasat);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Brian Murphy <brian@murphy.dk>");
+MODULE_DESCRIPTION("Lasat Safepipe/Masquerade MTD map driver");
diff --git a/drivers/mtd/maps/lubbock-flash.c b/drivers/mtd/maps/lubbock-flash.c
new file mode 100644
index 000000000000..1298de475c9a
--- /dev/null
+++ b/drivers/mtd/maps/lubbock-flash.c
@@ -0,0 +1,168 @@
+/*
+ * $Id: lubbock-flash.c,v 1.19 2004/11/04 13:24:15 gleixner Exp $
+ *
+ * Map driver for the Lubbock developer platform.
+ *
+ * Author: Nicolas Pitre
+ * Copyright: (C) 2001 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/dma-mapping.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <asm/io.h>
+#include <asm/hardware.h>
+#include <asm/arch/pxa-regs.h>
+#include <asm/arch/lubbock.h>
+
+
+#define ROM_ADDR 0x00000000
+#define FLASH_ADDR 0x04000000
+
+#define WINDOW_SIZE 64*1024*1024
+
+static void lubbock_map_inval_cache(struct map_info *map, unsigned long from, ssize_t len)
+{
+ consistent_sync((char *)map->cached + from, len, DMA_FROM_DEVICE);
+}
+
+static struct map_info lubbock_maps[2] = { {
+ .size = WINDOW_SIZE,
+ .phys = 0x00000000,
+ .inval_cache = lubbock_map_inval_cache,
+}, {
+ .size = WINDOW_SIZE,
+ .phys = 0x04000000,
+ .inval_cache = lubbock_map_inval_cache,
+} };
+
+static struct mtd_partition lubbock_partitions[] = {
+ {
+ .name = "Bootloader",
+ .size = 0x00040000,
+ .offset = 0,
+ .mask_flags = MTD_WRITEABLE /* force read-only */
+ },{
+ .name = "Kernel",
+ .size = 0x00100000,
+ .offset = 0x00040000,
+ },{
+ .name = "Filesystem",
+ .size = MTDPART_SIZ_FULL,
+ .offset = 0x00140000
+ }
+};
+
+static struct mtd_info *mymtds[2];
+static struct mtd_partition *parsed_parts[2];
+static int nr_parsed_parts[2];
+
+static const char *probes[] = { "RedBoot", "cmdlinepart", NULL };
+
+static int __init init_lubbock(void)
+{
+ int flashboot = (LUB_CONF_SWITCHES & 1);
+ int ret = 0, i;
+
+ lubbock_maps[0].bankwidth = lubbock_maps[1].bankwidth =
+ (BOOT_DEF & 1) ? 2 : 4;
+
+ /* Compensate for the nROMBT switch which swaps the flash banks */
+ printk(KERN_NOTICE "Lubbock configured to boot from %s (bank %d)\n",
+ flashboot?"Flash":"ROM", flashboot);
+
+ lubbock_maps[flashboot^1].name = "Lubbock Application Flash";
+ lubbock_maps[flashboot].name = "Lubbock Boot ROM";
+
+ for (i = 0; i < 2; i++) {
+ lubbock_maps[i].virt = ioremap(lubbock_maps[i].phys, WINDOW_SIZE);
+ if (!lubbock_maps[i].virt) {
+ printk(KERN_WARNING "Failed to ioremap %s\n", lubbock_maps[i].name);
+ if (!ret)
+ ret = -ENOMEM;
+ continue;
+ }
+ lubbock_maps[i].cached = ioremap_cached(lubbock_maps[i].phys, WINDOW_SIZE);
+ if (!lubbock_maps[i].cached)
+ printk(KERN_WARNING "Failed to ioremap cached %s\n", lubbock_maps[i].name);
+ simple_map_init(&lubbock_maps[i]);
+
+ printk(KERN_NOTICE "Probing %s at physical address 0x%08lx (%d-bit bankwidth)\n",
+ lubbock_maps[i].name, lubbock_maps[i].phys,
+ lubbock_maps[i].bankwidth * 8);
+
+ mymtds[i] = do_map_probe("cfi_probe", &lubbock_maps[i]);
+
+ if (!mymtds[i]) {
+ iounmap((void *)lubbock_maps[i].virt);
+ if (lubbock_maps[i].cached)
+ iounmap(lubbock_maps[i].cached);
+ if (!ret)
+ ret = -EIO;
+ continue;
+ }
+ mymtds[i]->owner = THIS_MODULE;
+
+ ret = parse_mtd_partitions(mymtds[i], probes,
+ &parsed_parts[i], 0);
+
+ if (ret > 0)
+ nr_parsed_parts[i] = ret;
+ }
+
+ if (!mymtds[0] && !mymtds[1])
+ return ret;
+
+ for (i = 0; i < 2; i++) {
+ if (!mymtds[i]) {
+ printk(KERN_WARNING "%s is absent. Skipping\n", lubbock_maps[i].name);
+ } else if (nr_parsed_parts[i]) {
+ add_mtd_partitions(mymtds[i], parsed_parts[i], nr_parsed_parts[i]);
+ } else if (!i) {
+ printk("Using static partitions on %s\n", lubbock_maps[i].name);
+ add_mtd_partitions(mymtds[i], lubbock_partitions, ARRAY_SIZE(lubbock_partitions));
+ } else {
+ printk("Registering %s as whole device\n", lubbock_maps[i].name);
+ add_mtd_device(mymtds[i]);
+ }
+ }
+ return 0;
+}
+
+static void __exit cleanup_lubbock(void)
+{
+ int i;
+ for (i = 0; i < 2; i++) {
+ if (!mymtds[i])
+ continue;
+
+ if (nr_parsed_parts[i] || !i)
+ del_mtd_partitions(mymtds[i]);
+ else
+ del_mtd_device(mymtds[i]);
+
+ map_destroy(mymtds[i]);
+ iounmap((void *)lubbock_maps[i].virt);
+ if (lubbock_maps[i].cached)
+ iounmap(lubbock_maps[i].cached);
+
+ if (parsed_parts[i])
+ kfree(parsed_parts[i]);
+ }
+}
+
+module_init(init_lubbock);
+module_exit(cleanup_lubbock);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nicolas Pitre <nico@cam.org>");
+MODULE_DESCRIPTION("MTD map driver for Intel Lubbock");
diff --git a/drivers/mtd/maps/map_funcs.c b/drivers/mtd/maps/map_funcs.c
new file mode 100644
index 000000000000..38f6a7af53f8
--- /dev/null
+++ b/drivers/mtd/maps/map_funcs.c
@@ -0,0 +1,44 @@
+/*
+ * $Id: map_funcs.c,v 1.9 2004/07/13 22:33:15 dwmw2 Exp $
+ *
+ * Out-of-line map I/O functions for simple maps when CONFIG_COMPLEX_MAPPINGS
+ * is enabled.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <linux/mtd/map.h>
+
+static map_word simple_map_read(struct map_info *map, unsigned long ofs)
+{
+ return inline_map_read(map, ofs);
+}
+
+static void simple_map_write(struct map_info *map, const map_word datum, unsigned long ofs)
+{
+ inline_map_write(map, datum, ofs);
+}
+
+static void simple_map_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len)
+{
+ inline_map_copy_from(map, to, from, len);
+}
+
+static void simple_map_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len)
+{
+ inline_map_copy_to(map, to, from, len);
+}
+
+void simple_map_init(struct map_info *map)
+{
+ BUG_ON(!map_bankwidth_supported(map->bankwidth));
+
+ map->read = simple_map_read;
+ map->write = simple_map_write;
+ map->copy_from = simple_map_copy_from;
+ map->copy_to = simple_map_copy_to;
+}
+
+EXPORT_SYMBOL(simple_map_init);
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/maps/mbx860.c b/drivers/mtd/maps/mbx860.c
new file mode 100644
index 000000000000..c5c6901a4763
--- /dev/null
+++ b/drivers/mtd/maps/mbx860.c
@@ -0,0 +1,100 @@
+/*
+ * $Id: mbx860.c,v 1.8 2004/11/04 13:24:15 gleixner Exp $
+ *
+ * Handle mapping of the flash on MBX860 boards
+ *
+ * Author: Anton Todorov
+ * Copyright: (C) 2001 Emness Technology
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+
+#define WINDOW_ADDR 0xfe000000
+#define WINDOW_SIZE 0x00200000
+
+/* Flash / Partition sizing */
+#define MAX_SIZE_KiB 8192
+#define BOOT_PARTITION_SIZE_KiB 512
+#define KERNEL_PARTITION_SIZE_KiB 5632
+#define APP_PARTITION_SIZE_KiB 2048
+
+#define NUM_PARTITIONS 3
+
+/* partition_info gives details on the logical partitions that the split the
+ * single flash device into. If the size if zero we use up to the end of the
+ * device. */
+static struct mtd_partition partition_info[]={
+ { .name = "MBX flash BOOT partition",
+ .offset = 0,
+ .size = BOOT_PARTITION_SIZE_KiB*1024 },
+ { .name = "MBX flash DATA partition",
+ .offset = BOOT_PARTITION_SIZE_KiB*1024,
+ .size = (KERNEL_PARTITION_SIZE_KiB)*1024 },
+ { .name = "MBX flash APPLICATION partition",
+ .offset = (BOOT_PARTITION_SIZE_KiB+KERNEL_PARTITION_SIZE_KiB)*1024 }
+};
+
+
+static struct mtd_info *mymtd;
+
+struct map_info mbx_map = {
+ .name = "MBX flash",
+ .size = WINDOW_SIZE,
+ .phys = WINDOW_ADDR,
+ .bankwidth = 4,
+};
+
+int __init init_mbx(void)
+{
+ printk(KERN_NOTICE "Motorola MBX flash device: 0x%x at 0x%x\n", WINDOW_SIZE*4, WINDOW_ADDR);
+ mbx_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE * 4);
+
+ if (!mbx_map.virt) {
+ printk("Failed to ioremap\n");
+ return -EIO;
+ }
+ simple_map_init(&mbx_map);
+
+ mymtd = do_map_probe("jedec_probe", &mbx_map);
+ if (mymtd) {
+ mymtd->owner = THIS_MODULE;
+ add_mtd_device(mymtd);
+ add_mtd_partitions(mymtd, partition_info, NUM_PARTITIONS);
+ return 0;
+ }
+
+ iounmap((void *)mbx_map.virt);
+ return -ENXIO;
+}
+
+static void __exit cleanup_mbx(void)
+{
+ if (mymtd) {
+ del_mtd_device(mymtd);
+ map_destroy(mymtd);
+ }
+ if (mbx_map.virt) {
+ iounmap((void *)mbx_map.virt);
+ mbx_map.virt = 0;
+ }
+}
+
+module_init(init_mbx);
+module_exit(cleanup_mbx);
+
+MODULE_AUTHOR("Anton Todorov <a.todorov@emness.com>");
+MODULE_DESCRIPTION("MTD map driver for Motorola MBX860 board");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/maps/mpc1211.c b/drivers/mtd/maps/mpc1211.c
new file mode 100644
index 000000000000..4685e8e13460
--- /dev/null
+++ b/drivers/mtd/maps/mpc1211.c
@@ -0,0 +1,81 @@
+/*
+ * Flash on MPC-1211
+ *
+ * $Id: mpc1211.c,v 1.4 2004/09/16 23:27:13 gleixner Exp $
+ *
+ * (C) 2002 Interface, Saito.K & Jeanne
+ *
+ * GPL'd
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/config.h>
+
+static struct mtd_info *flash_mtd;
+static struct mtd_partition *parsed_parts;
+
+struct map_info mpc1211_flash_map = {
+ .name = "MPC-1211 FLASH",
+ .size = 0x80000,
+ .bankwidth = 1,
+};
+
+static struct mtd_partition mpc1211_partitions[] = {
+ {
+ .name = "IPL & ETH-BOOT",
+ .offset = 0x00000000,
+ .size = 0x10000,
+ },
+ {
+ .name = "Flash FS",
+ .offset = 0x00010000,
+ .size = MTDPART_SIZ_FULL,
+ }
+};
+
+static int __init init_mpc1211_maps(void)
+{
+ int nr_parts;
+
+ mpc1211_flash_map.phys = 0;
+ mpc1211_flash_map.virt = (void __iomem *)P2SEGADDR(0);
+
+ simple_map_init(&mpc1211_flash_map);
+
+ printk(KERN_NOTICE "Probing for flash chips at 0x00000000:\n");
+ flash_mtd = do_map_probe("jedec_probe", &mpc1211_flash_map);
+ if (!flash_mtd) {
+ printk(KERN_NOTICE "Flash chips not detected at either possible location.\n");
+ return -ENXIO;
+ }
+ printk(KERN_NOTICE "MPC-1211: Flash at 0x%08lx\n", mpc1211_flash_map.virt & 0x1fffffff);
+ flash_mtd->module = THIS_MODULE;
+
+ parsed_parts = mpc1211_partitions;
+ nr_parts = ARRAY_SIZE(mpc1211_partitions);
+
+ add_mtd_partitions(flash_mtd, parsed_parts, nr_parts);
+ return 0;
+}
+
+static void __exit cleanup_mpc1211_maps(void)
+{
+ if (parsed_parts)
+ del_mtd_partitions(flash_mtd);
+ else
+ del_mtd_device(flash_mtd);
+ map_destroy(flash_mtd);
+}
+
+module_init(init_mpc1211_maps);
+module_exit(cleanup_mpc1211_maps);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Saito.K & Jeanne <ksaito@interface.co.jp>");
+MODULE_DESCRIPTION("MTD map driver for MPC-1211 boards. Interface");
diff --git a/drivers/mtd/maps/netsc520.c b/drivers/mtd/maps/netsc520.c
new file mode 100644
index 000000000000..ab7e6358d281
--- /dev/null
+++ b/drivers/mtd/maps/netsc520.c
@@ -0,0 +1,140 @@
+/* netsc520.c -- MTD map driver for AMD NetSc520 Demonstration Board
+ *
+ * Copyright (C) 2001 Mark Langsdorf (mark.langsdorf@amd.com)
+ * based on sc520cdp.c by Sysgo Real-Time Solutions GmbH
+ *
+ * $Id: netsc520.c,v 1.13 2004/11/28 09:40:40 dwmw2 Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * The NetSc520 is a demonstration board for the Elan Sc520 processor available
+ * from AMD. It has a single back of 16 megs of 32-bit Flash ROM and another
+ * 16 megs of SDRAM.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+
+/*
+** The single, 16 megabyte flash bank is divided into four virtual
+** partitions. The first partition is 768 KiB and is intended to
+** store the kernel image loaded by the bootstrap loader. The second
+** partition is 256 KiB and holds the BIOS image. The third
+** partition is 14.5 MiB and is intended for the flash file system
+** image. The last partition is 512 KiB and contains another copy
+** of the BIOS image and the reset vector.
+**
+** Only the third partition should be mounted. The first partition
+** should not be mounted, but it can erased and written to using the
+** MTD character routines. The second and fourth partitions should
+** not be touched - it is possible to corrupt the BIOS image by
+** mounting these partitions, and potentially the board will not be
+** recoverable afterwards.
+*/
+
+/* partition_info gives details on the logical partitions that the split the
+ * single flash device into. If the size if zero we use up to the end of the
+ * device. */
+static struct mtd_partition partition_info[]={
+ {
+ .name = "NetSc520 boot kernel",
+ .offset = 0,
+ .size = 0xc0000
+ },
+ {
+ .name = "NetSc520 Low BIOS",
+ .offset = 0xc0000,
+ .size = 0x40000
+ },
+ {
+ .name = "NetSc520 file system",
+ .offset = 0x100000,
+ .size = 0xe80000
+ },
+ {
+ .name = "NetSc520 High BIOS",
+ .offset = 0xf80000,
+ .size = 0x80000
+ },
+};
+#define NUM_PARTITIONS (sizeof(partition_info)/sizeof(partition_info[0]))
+
+#define WINDOW_SIZE 0x00100000
+#define WINDOW_ADDR 0x00200000
+
+static struct map_info netsc520_map = {
+ .name = "netsc520 Flash Bank",
+ .size = WINDOW_SIZE,
+ .bankwidth = 4,
+ .phys = WINDOW_ADDR,
+};
+
+#define NUM_FLASH_BANKS (sizeof(netsc520_map)/sizeof(struct map_info))
+
+static struct mtd_info *mymtd;
+
+static int __init init_netsc520(void)
+{
+ printk(KERN_NOTICE "NetSc520 flash device: 0x%lx at 0x%lx\n", netsc520_map.size, netsc520_map.phys);
+ netsc520_map.virt = ioremap_nocache(netsc520_map.phys, netsc520_map.size);
+
+ if (!netsc520_map.virt) {
+ printk("Failed to ioremap_nocache\n");
+ return -EIO;
+ }
+
+ simple_map_init(&netsc520_map);
+
+ mymtd = do_map_probe("cfi_probe", &netsc520_map);
+ if(!mymtd)
+ mymtd = do_map_probe("map_ram", &netsc520_map);
+ if(!mymtd)
+ mymtd = do_map_probe("map_rom", &netsc520_map);
+
+ if (!mymtd) {
+ iounmap(netsc520_map.virt);
+ return -ENXIO;
+ }
+
+ mymtd->owner = THIS_MODULE;
+ add_mtd_partitions( mymtd, partition_info, NUM_PARTITIONS );
+ return 0;
+}
+
+static void __exit cleanup_netsc520(void)
+{
+ if (mymtd) {
+ del_mtd_partitions(mymtd);
+ map_destroy(mymtd);
+ }
+ if (netsc520_map.virt) {
+ iounmap(netsc520_map.virt);
+ netsc520_map.virt = NULL;
+ }
+}
+
+module_init(init_netsc520);
+module_exit(cleanup_netsc520);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mark Langsdorf <mark.langsdorf@amd.com>");
+MODULE_DESCRIPTION("MTD map driver for AMD NetSc520 Demonstration Board");
diff --git a/drivers/mtd/maps/nettel.c b/drivers/mtd/maps/nettel.c
new file mode 100644
index 000000000000..61be5a4148c9
--- /dev/null
+++ b/drivers/mtd/maps/nettel.c
@@ -0,0 +1,496 @@
+/****************************************************************************/
+
+/*
+ * nettel.c -- mappings for NETtel/SecureEdge/SnapGear (x86) boards.
+ *
+ * (C) Copyright 2000-2001, Greg Ungerer (gerg@snapgear.com)
+ * (C) Copyright 2001-2002, SnapGear (www.snapgear.com)
+ *
+ * $Id: nettel.c,v 1.10 2005/01/05 17:11:29 dwmw2 Exp $
+ */
+
+/****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/cfi.h>
+#include <linux/reboot.h>
+#include <asm/io.h>
+
+/****************************************************************************/
+
+#define INTEL_BUSWIDTH 1
+#define AMD_WINDOW_MAXSIZE 0x00200000
+#define AMD_BUSWIDTH 1
+
+/*
+ * PAR masks and shifts, assuming 64K pages.
+ */
+#define SC520_PAR_ADDR_MASK 0x00003fff
+#define SC520_PAR_ADDR_SHIFT 16
+#define SC520_PAR_TO_ADDR(par) \
+ (((par)&SC520_PAR_ADDR_MASK) << SC520_PAR_ADDR_SHIFT)
+
+#define SC520_PAR_SIZE_MASK 0x01ffc000
+#define SC520_PAR_SIZE_SHIFT 2
+#define SC520_PAR_TO_SIZE(par) \
+ ((((par)&SC520_PAR_SIZE_MASK) << SC520_PAR_SIZE_SHIFT) + (64*1024))
+
+#define SC520_PAR(cs, addr, size) \
+ ((cs) | \
+ ((((size)-(64*1024)) >> SC520_PAR_SIZE_SHIFT) & SC520_PAR_SIZE_MASK) | \
+ (((addr) >> SC520_PAR_ADDR_SHIFT) & SC520_PAR_ADDR_MASK))
+
+#define SC520_PAR_BOOTCS 0x8a000000
+#define SC520_PAR_ROMCS1 0xaa000000
+#define SC520_PAR_ROMCS2 0xca000000 /* Cache disabled, 64K page */
+
+static void *nettel_mmcrp = NULL;
+
+#ifdef CONFIG_MTD_CFI_INTELEXT
+static struct mtd_info *intel_mtd;
+#endif
+static struct mtd_info *amd_mtd;
+
+/****************************************************************************/
+
+/****************************************************************************/
+
+#ifdef CONFIG_MTD_CFI_INTELEXT
+static struct map_info nettel_intel_map = {
+ .name = "SnapGear Intel",
+ .size = 0,
+ .bankwidth = INTEL_BUSWIDTH,
+};
+
+static struct mtd_partition nettel_intel_partitions[] = {
+ {
+ .name = "SnapGear kernel",
+ .offset = 0,
+ .size = 0x000e0000
+ },
+ {
+ .name = "SnapGear filesystem",
+ .offset = 0x00100000,
+ },
+ {
+ .name = "SnapGear config",
+ .offset = 0x000e0000,
+ .size = 0x00020000
+ },
+ {
+ .name = "SnapGear Intel",
+ .offset = 0
+ },
+ {
+ .name = "SnapGear BIOS Config",
+ .offset = 0x007e0000,
+ .size = 0x00020000
+ },
+ {
+ .name = "SnapGear BIOS",
+ .offset = 0x007e0000,
+ .size = 0x00020000
+ },
+};
+#endif
+
+static struct map_info nettel_amd_map = {
+ .name = "SnapGear AMD",
+ .size = AMD_WINDOW_MAXSIZE,
+ .bankwidth = AMD_BUSWIDTH,
+};
+
+static struct mtd_partition nettel_amd_partitions[] = {
+ {
+ .name = "SnapGear BIOS config",
+ .offset = 0x000e0000,
+ .size = 0x00010000
+ },
+ {
+ .name = "SnapGear BIOS",
+ .offset = 0x000f0000,
+ .size = 0x00010000
+ },
+ {
+ .name = "SnapGear AMD",
+ .offset = 0
+ },
+ {
+ .name = "SnapGear high BIOS",
+ .offset = 0x001f0000,
+ .size = 0x00010000
+ }
+};
+
+#define NUM_AMD_PARTITIONS \
+ (sizeof(nettel_amd_partitions)/sizeof(nettel_amd_partitions[0]))
+
+/****************************************************************************/
+
+#ifdef CONFIG_MTD_CFI_INTELEXT
+
+/*
+ * Set the Intel flash back to read mode since some old boot
+ * loaders don't.
+ */
+static int nettel_reboot_notifier(struct notifier_block *nb, unsigned long val, void *v)
+{
+ struct cfi_private *cfi = nettel_intel_map.fldrv_priv;
+ unsigned long b;
+
+ /* Make sure all FLASH chips are put back into read mode */
+ for (b = 0; (b < nettel_intel_partitions[3].size); b += 0x100000) {
+ cfi_send_gen_cmd(0xff, 0x55, b, &nettel_intel_map, cfi,
+ cfi->device_type, NULL);
+ }
+ return(NOTIFY_OK);
+}
+
+static struct notifier_block nettel_notifier_block = {
+ nettel_reboot_notifier, NULL, 0
+};
+
+/*
+ * Erase the configuration file system.
+ * Used to support the software reset button.
+ */
+static void nettel_erasecallback(struct erase_info *done)
+{
+ wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv;
+ wake_up(wait_q);
+}
+
+static struct erase_info nettel_erase;
+
+int nettel_eraseconfig(void)
+{
+ struct mtd_info *mtd;
+ DECLARE_WAITQUEUE(wait, current);
+ wait_queue_head_t wait_q;
+ int ret;
+
+ init_waitqueue_head(&wait_q);
+ mtd = get_mtd_device(NULL, 2);
+ if (mtd) {
+ nettel_erase.mtd = mtd;
+ nettel_erase.callback = nettel_erasecallback;
+ nettel_erase.callback = NULL;
+ nettel_erase.addr = 0;
+ nettel_erase.len = mtd->size;
+ nettel_erase.priv = (u_long) &wait_q;
+ nettel_erase.priv = 0;
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&wait_q, &wait);
+
+ ret = MTD_ERASE(mtd, &nettel_erase);
+ if (ret) {
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&wait_q, &wait);
+ put_mtd_device(mtd);
+ return(ret);
+ }
+
+ schedule(); /* Wait for erase to finish. */
+ remove_wait_queue(&wait_q, &wait);
+
+ put_mtd_device(mtd);
+ }
+
+ return(0);
+}
+
+#else
+
+int nettel_eraseconfig(void)
+{
+ return(0);
+}
+
+#endif
+
+/****************************************************************************/
+
+int __init nettel_init(void)
+{
+ volatile unsigned long *amdpar;
+ unsigned long amdaddr, maxsize;
+ int num_amd_partitions=0;
+#ifdef CONFIG_MTD_CFI_INTELEXT
+ volatile unsigned long *intel0par, *intel1par;
+ unsigned long orig_bootcspar, orig_romcs1par;
+ unsigned long intel0addr, intel0size;
+ unsigned long intel1addr, intel1size;
+ int intelboot, intel0cs, intel1cs;
+ int num_intel_partitions;
+#endif
+ int rc = 0;
+
+ nettel_mmcrp = (void *) ioremap_nocache(0xfffef000, 4096);
+ if (nettel_mmcrp == NULL) {
+ printk("SNAPGEAR: failed to disable MMCR cache??\n");
+ return(-EIO);
+ }
+
+ /* Set CPU clock to be 33.000MHz */
+ *((unsigned char *) (nettel_mmcrp + 0xc64)) = 0x01;
+
+ amdpar = (volatile unsigned long *) (nettel_mmcrp + 0xc4);
+
+#ifdef CONFIG_MTD_CFI_INTELEXT
+ intelboot = 0;
+ intel0cs = SC520_PAR_ROMCS1;
+ intel0par = (volatile unsigned long *) (nettel_mmcrp + 0xc0);
+ intel1cs = SC520_PAR_ROMCS2;
+ intel1par = (volatile unsigned long *) (nettel_mmcrp + 0xbc);
+
+ /*
+ * Save the CS settings then ensure ROMCS1 and ROMCS2 are off,
+ * otherwise they might clash with where we try to map BOOTCS.
+ */
+ orig_bootcspar = *amdpar;
+ orig_romcs1par = *intel0par;
+ *intel0par = 0;
+ *intel1par = 0;
+#endif
+
+ /*
+ * The first thing to do is determine if we have a separate
+ * boot FLASH device. Typically this is a small (1 to 2MB)
+ * AMD FLASH part. It seems that device size is about the
+ * only way to tell if this is the case...
+ */
+ amdaddr = 0x20000000;
+ maxsize = AMD_WINDOW_MAXSIZE;
+
+ *amdpar = SC520_PAR(SC520_PAR_BOOTCS, amdaddr, maxsize);
+ __asm__ ("wbinvd");
+
+ nettel_amd_map.phys = amdaddr;
+ nettel_amd_map.virt = ioremap_nocache(amdaddr, maxsize);
+ if (!nettel_amd_map.virt) {
+ printk("SNAPGEAR: failed to ioremap() BOOTCS\n");
+ return(-EIO);
+ }
+ simple_map_init(&nettel_amd_map);
+
+ if ((amd_mtd = do_map_probe("jedec_probe", &nettel_amd_map))) {
+ printk(KERN_NOTICE "SNAPGEAR: AMD flash device size = %dK\n",
+ amd_mtd->size>>10);
+
+ amd_mtd->owner = THIS_MODULE;
+
+ /* The high BIOS partition is only present for 2MB units */
+ num_amd_partitions = NUM_AMD_PARTITIONS;
+ if (amd_mtd->size < AMD_WINDOW_MAXSIZE)
+ num_amd_partitions--;
+ /* Don't add the partition until after the primary INTEL's */
+
+#ifdef CONFIG_MTD_CFI_INTELEXT
+ /*
+ * Map the Intel flash into memory after the AMD
+ * It has to start on a multiple of maxsize.
+ */
+ maxsize = SC520_PAR_TO_SIZE(orig_romcs1par);
+ if (maxsize < (32 * 1024 * 1024))
+ maxsize = (32 * 1024 * 1024);
+ intel0addr = amdaddr + maxsize;
+#endif
+ } else {
+#ifdef CONFIG_MTD_CFI_INTELEXT
+ /* INTEL boot FLASH */
+ intelboot++;
+
+ if (!orig_romcs1par) {
+ intel0cs = SC520_PAR_BOOTCS;
+ intel0par = (volatile unsigned long *)
+ (nettel_mmcrp + 0xc4);
+ intel1cs = SC520_PAR_ROMCS1;
+ intel1par = (volatile unsigned long *)
+ (nettel_mmcrp + 0xc0);
+
+ intel0addr = SC520_PAR_TO_ADDR(orig_bootcspar);
+ maxsize = SC520_PAR_TO_SIZE(orig_bootcspar);
+ } else {
+ /* Kernel base is on ROMCS1, not BOOTCS */
+ intel0cs = SC520_PAR_ROMCS1;
+ intel0par = (volatile unsigned long *)
+ (nettel_mmcrp + 0xc0);
+ intel1cs = SC520_PAR_BOOTCS;
+ intel1par = (volatile unsigned long *)
+ (nettel_mmcrp + 0xc4);
+
+ intel0addr = SC520_PAR_TO_ADDR(orig_romcs1par);
+ maxsize = SC520_PAR_TO_SIZE(orig_romcs1par);
+ }
+
+ /* Destroy useless AMD MTD mapping */
+ amd_mtd = NULL;
+ iounmap(nettel_amd_map.virt);
+ nettel_amd_map.virt = NULL;
+#else
+ /* Only AMD flash supported */
+ return(-ENXIO);
+#endif
+ }
+
+#ifdef CONFIG_MTD_CFI_INTELEXT
+ /*
+ * We have determined the INTEL FLASH configuration, so lets
+ * go ahead and probe for them now.
+ */
+
+ /* Set PAR to the maximum size */
+ if (maxsize < (32 * 1024 * 1024))
+ maxsize = (32 * 1024 * 1024);
+ *intel0par = SC520_PAR(intel0cs, intel0addr, maxsize);
+
+ /* Turn other PAR off so the first probe doesn't find it */
+ *intel1par = 0;
+
+ /* Probe for the the size of the first Intel flash */
+ nettel_intel_map.size = maxsize;
+ nettel_intel_map.phys = intel0addr;
+ nettel_intel_map.virt = ioremap_nocache(intel0addr, maxsize);
+ if (!nettel_intel_map.virt) {
+ printk("SNAPGEAR: failed to ioremap() ROMCS1\n");
+ return(-EIO);
+ }
+ simple_map_init(&nettel_intel_map);
+
+ intel_mtd = do_map_probe("cfi_probe", &nettel_intel_map);
+ if (!intel_mtd) {
+ iounmap(nettel_intel_map.virt);
+ return(-ENXIO);
+ }
+
+ /* Set PAR to the detected size */
+ intel0size = intel_mtd->size;
+ *intel0par = SC520_PAR(intel0cs, intel0addr, intel0size);
+
+ /*
+ * Map second Intel FLASH right after first. Set its size to the
+ * same maxsize used for the first Intel FLASH.
+ */
+ intel1addr = intel0addr + intel0size;
+ *intel1par = SC520_PAR(intel1cs, intel1addr, maxsize);
+ __asm__ ("wbinvd");
+
+ maxsize += intel0size;
+
+ /* Delete the old map and probe again to do both chips */
+ map_destroy(intel_mtd);
+ intel_mtd = NULL;
+ iounmap(nettel_intel_map.virt);
+
+ nettel_intel_map.size = maxsize;
+ nettel_intel_map.virt = ioremap_nocache(intel0addr, maxsize);
+ if (!nettel_intel_map.virt) {
+ printk("SNAPGEAR: failed to ioremap() ROMCS1/2\n");
+ return(-EIO);
+ }
+
+ intel_mtd = do_map_probe("cfi_probe", &nettel_intel_map);
+ if (! intel_mtd) {
+ iounmap((void *) nettel_intel_map.virt);
+ return(-ENXIO);
+ }
+
+ intel1size = intel_mtd->size - intel0size;
+ if (intel1size > 0) {
+ *intel1par = SC520_PAR(intel1cs, intel1addr, intel1size);
+ __asm__ ("wbinvd");
+ } else {
+ *intel1par = 0;
+ }
+
+ printk(KERN_NOTICE "SNAPGEAR: Intel flash device size = %dK\n",
+ (intel_mtd->size >> 10));
+
+ intel_mtd->owner = THIS_MODULE;
+
+#ifndef CONFIG_BLK_DEV_INITRD
+ ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, 1);
+#endif
+
+ num_intel_partitions = sizeof(nettel_intel_partitions) /
+ sizeof(nettel_intel_partitions[0]);
+
+ if (intelboot) {
+ /*
+ * Adjust offset and size of last boot partition.
+ * Must allow for BIOS region at end of FLASH.
+ */
+ nettel_intel_partitions[1].size = (intel0size + intel1size) -
+ (1024*1024 + intel_mtd->erasesize);
+ nettel_intel_partitions[3].size = intel0size + intel1size;
+ nettel_intel_partitions[4].offset =
+ (intel0size + intel1size) - intel_mtd->erasesize;
+ nettel_intel_partitions[4].size = intel_mtd->erasesize;
+ nettel_intel_partitions[5].offset =
+ nettel_intel_partitions[4].offset;
+ nettel_intel_partitions[5].size =
+ nettel_intel_partitions[4].size;
+ } else {
+ /* No BIOS regions when AMD boot */
+ num_intel_partitions -= 2;
+ }
+ rc = add_mtd_partitions(intel_mtd, nettel_intel_partitions,
+ num_intel_partitions);
+#endif
+
+ if (amd_mtd) {
+ rc = add_mtd_partitions(amd_mtd, nettel_amd_partitions,
+ num_amd_partitions);
+ }
+
+#ifdef CONFIG_MTD_CFI_INTELEXT
+ register_reboot_notifier(&nettel_notifier_block);
+#endif
+
+ return(rc);
+}
+
+/****************************************************************************/
+
+void __exit nettel_cleanup(void)
+{
+#ifdef CONFIG_MTD_CFI_INTELEXT
+ unregister_reboot_notifier(&nettel_notifier_block);
+#endif
+ if (amd_mtd) {
+ del_mtd_partitions(amd_mtd);
+ map_destroy(amd_mtd);
+ }
+ if (nettel_amd_map.virt) {
+ iounmap(nettel_amd_map.virt);
+ nettel_amd_map.virt = NULL;
+ }
+#ifdef CONFIG_MTD_CFI_INTELEXT
+ if (intel_mtd) {
+ del_mtd_partitions(intel_mtd);
+ map_destroy(intel_mtd);
+ }
+ if (nettel_intel_map.virt) {
+ iounmap(nettel_intel_map.virt);
+ nettel_intel_map.virt = 0;
+ }
+#endif
+}
+
+/****************************************************************************/
+
+module_init(nettel_init);
+module_exit(nettel_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Greg Ungerer <gerg@snapgear.com>");
+MODULE_DESCRIPTION("SnapGear/SecureEdge FLASH support");
+
+/****************************************************************************/
diff --git a/drivers/mtd/maps/ocelot.c b/drivers/mtd/maps/ocelot.c
new file mode 100644
index 000000000000..82c3070678c5
--- /dev/null
+++ b/drivers/mtd/maps/ocelot.c
@@ -0,0 +1,175 @@
+/*
+ * $Id: ocelot.c,v 1.16 2005/01/05 18:05:13 dwmw2 Exp $
+ *
+ * Flash on Momenco Ocelot
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#define OCELOT_PLD 0x2c000000
+#define FLASH_WINDOW_ADDR 0x2fc00000
+#define FLASH_WINDOW_SIZE 0x00080000
+#define FLASH_BUSWIDTH 1
+#define NVRAM_WINDOW_ADDR 0x2c800000
+#define NVRAM_WINDOW_SIZE 0x00007FF0
+#define NVRAM_BUSWIDTH 1
+
+static unsigned int cacheflush = 0;
+
+static struct mtd_info *flash_mtd;
+static struct mtd_info *nvram_mtd;
+
+static void ocelot_ram_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf)
+{
+ struct map_info *map = mtd->priv;
+ size_t done = 0;
+
+ /* If we use memcpy, it does word-wide writes. Even though we told the
+ GT64120A that it's an 8-bit wide region, word-wide writes don't work.
+ We end up just writing the first byte of the four to all four bytes.
+ So we have this loop instead */
+ *retlen = len;
+ while(len) {
+ __raw_writeb(*(unsigned char *) from, map->virt + to);
+ from++;
+ to++;
+ len--;
+ }
+}
+
+static struct mtd_partition *parsed_parts;
+
+struct map_info ocelot_flash_map = {
+ .name = "Ocelot boot flash",
+ .size = FLASH_WINDOW_SIZE,
+ .bankwidth = FLASH_BUSWIDTH,
+ .phys = FLASH_WINDOW_ADDR,
+};
+
+struct map_info ocelot_nvram_map = {
+ .name = "Ocelot NVRAM",
+ .size = NVRAM_WINDOW_SIZE,
+ .bankwidth = NVRAM_BUSWIDTH,
+ .phys = NVRAM_WINDOW_ADDR,
+};
+
+static const char *probes[] = { "RedBoot", NULL };
+
+static int __init init_ocelot_maps(void)
+{
+ void *pld;
+ int nr_parts;
+ unsigned char brd_status;
+
+ printk(KERN_INFO "Momenco Ocelot MTD mappings: Flash 0x%x at 0x%x, NVRAM 0x%x at 0x%x\n",
+ FLASH_WINDOW_SIZE, FLASH_WINDOW_ADDR, NVRAM_WINDOW_SIZE, NVRAM_WINDOW_ADDR);
+
+ /* First check whether the flash jumper is present */
+ pld = ioremap(OCELOT_PLD, 0x10);
+ if (!pld) {
+ printk(KERN_NOTICE "Failed to ioremap Ocelot PLD\n");
+ return -EIO;
+ }
+ brd_status = readb(pld+4);
+ iounmap(pld);
+
+ /* Now ioremap the NVRAM space */
+ ocelot_nvram_map.virt = ioremap_nocache(NVRAM_WINDOW_ADDR, NVRAM_WINDOW_SIZE);
+ if (!ocelot_nvram_map.virt) {
+ printk(KERN_NOTICE "Failed to ioremap Ocelot NVRAM space\n");
+ return -EIO;
+ }
+
+ simple_map_init(&ocelot_nvram_map);
+
+ /* And do the RAM probe on it to get an MTD device */
+ nvram_mtd = do_map_probe("map_ram", &ocelot_nvram_map);
+ if (!nvram_mtd) {
+ printk("NVRAM probe failed\n");
+ goto fail_1;
+ }
+ nvram_mtd->owner = THIS_MODULE;
+ nvram_mtd->erasesize = 16;
+ /* Override the write() method */
+ nvram_mtd->write = ocelot_ram_write;
+
+ /* Now map the flash space */
+ ocelot_flash_map.virt = ioremap_nocache(FLASH_WINDOW_ADDR, FLASH_WINDOW_SIZE);
+ if (!ocelot_flash_map.virt) {
+ printk(KERN_NOTICE "Failed to ioremap Ocelot flash space\n");
+ goto fail_2;
+ }
+ /* Now the cached version */
+ ocelot_flash_map.cached = (unsigned long)__ioremap(FLASH_WINDOW_ADDR, FLASH_WINDOW_SIZE, 0);
+
+ simple_map_init(&ocelot_flash_map);
+
+ /* Only probe for flash if the write jumper is present */
+ if (brd_status & 0x40) {
+ flash_mtd = do_map_probe("jedec", &ocelot_flash_map);
+ } else {
+ printk(KERN_NOTICE "Ocelot flash write jumper not present. Treating as ROM\n");
+ }
+ /* If that failed or the jumper's absent, pretend it's ROM */
+ if (!flash_mtd) {
+ flash_mtd = do_map_probe("map_rom", &ocelot_flash_map);
+ /* If we're treating it as ROM, set the erase size */
+ if (flash_mtd)
+ flash_mtd->erasesize = 0x10000;
+ }
+ if (!flash_mtd)
+ goto fail3;
+
+ add_mtd_device(nvram_mtd);
+
+ flash_mtd->owner = THIS_MODULE;
+ nr_parts = parse_mtd_partitions(flash_mtd, probes, &parsed_parts, 0);
+
+ if (nr_parts > 0)
+ add_mtd_partitions(flash_mtd, parsed_parts, nr_parts);
+ else
+ add_mtd_device(flash_mtd);
+
+ return 0;
+
+ fail3:
+ iounmap((void *)ocelot_flash_map.virt);
+ if (ocelot_flash_map.cached)
+ iounmap((void *)ocelot_flash_map.cached);
+ fail_2:
+ map_destroy(nvram_mtd);
+ fail_1:
+ iounmap((void *)ocelot_nvram_map.virt);
+
+ return -ENXIO;
+}
+
+static void __exit cleanup_ocelot_maps(void)
+{
+ del_mtd_device(nvram_mtd);
+ map_destroy(nvram_mtd);
+ iounmap((void *)ocelot_nvram_map.virt);
+
+ if (parsed_parts)
+ del_mtd_partitions(flash_mtd);
+ else
+ del_mtd_device(flash_mtd);
+ map_destroy(flash_mtd);
+ iounmap((void *)ocelot_flash_map.virt);
+ if (ocelot_flash_map.cached)
+ iounmap((void *)ocelot_flash_map.cached);
+}
+
+module_init(init_ocelot_maps);
+module_exit(cleanup_ocelot_maps);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Red Hat, Inc. - David Woodhouse <dwmw2@cambridge.redhat.com>");
+MODULE_DESCRIPTION("MTD map driver for Momenco Ocelot board");
diff --git a/drivers/mtd/maps/ocotea.c b/drivers/mtd/maps/ocotea.c
new file mode 100644
index 000000000000..6e559bc14636
--- /dev/null
+++ b/drivers/mtd/maps/ocotea.c
@@ -0,0 +1,154 @@
+/*
+ * Mapping for Ocotea user flash
+ *
+ * Matt Porter <mporter@kernel.crashing.org>
+ *
+ * Copyright 2002-2004 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/config.h>
+#include <linux/version.h>
+#include <asm/io.h>
+#include <asm/ibm44x.h>
+#include <platforms/4xx/ocotea.h>
+
+static struct mtd_info *flash;
+
+static struct map_info ocotea_small_map = {
+ .name = "Ocotea small flash",
+ .size = OCOTEA_SMALL_FLASH_SIZE,
+ .buswidth = 1,
+};
+
+static struct map_info ocotea_large_map = {
+ .name = "Ocotea large flash",
+ .size = OCOTEA_LARGE_FLASH_SIZE,
+ .buswidth = 1,
+};
+
+static struct mtd_partition ocotea_small_partitions[] = {
+ {
+ .name = "pibs",
+ .offset = 0x0,
+ .size = 0x100000,
+ }
+};
+
+static struct mtd_partition ocotea_large_partitions[] = {
+ {
+ .name = "fs",
+ .offset = 0,
+ .size = 0x300000,
+ },
+ {
+ .name = "firmware",
+ .offset = 0x300000,
+ .size = 0x100000,
+ }
+};
+
+#define NB_OF(x) (sizeof(x)/sizeof(x[0]))
+
+int __init init_ocotea(void)
+{
+ u8 fpga0_reg;
+ u8 *fpga0_adr;
+ unsigned long long small_flash_base, large_flash_base;
+
+ fpga0_adr = ioremap64(OCOTEA_FPGA_ADDR, 16);
+ if (!fpga0_adr)
+ return -ENOMEM;
+
+ fpga0_reg = readb((unsigned long)fpga0_adr);
+ iounmap(fpga0_adr);
+
+ if (OCOTEA_BOOT_LARGE_FLASH(fpga0_reg)) {
+ small_flash_base = OCOTEA_SMALL_FLASH_HIGH;
+ large_flash_base = OCOTEA_LARGE_FLASH_LOW;
+ }
+ else {
+ small_flash_base = OCOTEA_SMALL_FLASH_LOW;
+ large_flash_base = OCOTEA_LARGE_FLASH_HIGH;
+ }
+
+ ocotea_small_map.phys = small_flash_base;
+ ocotea_small_map.virt = ioremap64(small_flash_base,
+ ocotea_small_map.size);
+
+ if (!ocotea_small_map.virt) {
+ printk("Failed to ioremap flash\n");
+ return -EIO;
+ }
+
+ simple_map_init(&ocotea_small_map);
+
+ flash = do_map_probe("map_rom", &ocotea_small_map);
+ if (flash) {
+ flash->owner = THIS_MODULE;
+ add_mtd_partitions(flash, ocotea_small_partitions,
+ NB_OF(ocotea_small_partitions));
+ } else {
+ printk("map probe failed for flash\n");
+ return -ENXIO;
+ }
+
+ ocotea_large_map.phys = large_flash_base;
+ ocotea_large_map.virt = ioremap64(large_flash_base,
+ ocotea_large_map.size);
+
+ if (!ocotea_large_map.virt) {
+ printk("Failed to ioremap flash\n");
+ return -EIO;
+ }
+
+ simple_map_init(&ocotea_large_map);
+
+ flash = do_map_probe("cfi_probe", &ocotea_large_map);
+ if (flash) {
+ flash->owner = THIS_MODULE;
+ add_mtd_partitions(flash, ocotea_large_partitions,
+ NB_OF(ocotea_large_partitions));
+ } else {
+ printk("map probe failed for flash\n");
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static void __exit cleanup_ocotea(void)
+{
+ if (flash) {
+ del_mtd_partitions(flash);
+ map_destroy(flash);
+ }
+
+ if (ocotea_small_map.virt) {
+ iounmap((void *)ocotea_small_map.virt);
+ ocotea_small_map.virt = 0;
+ }
+
+ if (ocotea_large_map.virt) {
+ iounmap((void *)ocotea_large_map.virt);
+ ocotea_large_map.virt = 0;
+ }
+}
+
+module_init(init_ocotea);
+module_exit(cleanup_ocotea);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Matt Porter <mporter@kernel.crashing.org>");
+MODULE_DESCRIPTION("MTD map and partitions for IBM 440GX Ocotea boards");
diff --git a/drivers/mtd/maps/octagon-5066.c b/drivers/mtd/maps/octagon-5066.c
new file mode 100644
index 000000000000..e5ff83de420e
--- /dev/null
+++ b/drivers/mtd/maps/octagon-5066.c
@@ -0,0 +1,248 @@
+// $Id: octagon-5066.c,v 1.26 2004/07/12 22:38:29 dwmw2 Exp $
+/* ######################################################################
+
+ Octagon 5066 MTD Driver.
+
+ The Octagon 5066 is a SBC based on AMD's 586-WB running at 133 MHZ. It
+ comes with a builtin AMD 29F016 flash chip and a socketed EEPROM that
+ is replacable by flash. Both units are mapped through a multiplexer
+ into a 32k memory window at 0xe8000. The control register for the
+ multiplexing unit is located at IO 0x208 with a bit map of
+ 0-5 Page Selection in 32k increments
+ 6-7 Device selection:
+ 00 SSD off
+ 01 SSD 0 (Socket)
+ 10 SSD 1 (Flash chip)
+ 11 undefined
+
+ On each SSD, the first 128k is reserved for use by the bios
+ (actually it IS the bios..) This only matters if you are booting off the
+ flash, you must not put a file system starting there.
+
+ The driver tries to do a detection algorithm to guess what sort of devices
+ are plugged into the sockets.
+
+ ##################################################################### */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <asm/io.h>
+
+#include <linux/mtd/map.h>
+#include <linux/mtd/mtd.h>
+
+#define WINDOW_START 0xe8000
+#define WINDOW_LENGTH 0x8000
+#define WINDOW_SHIFT 27
+#define WINDOW_MASK 0x7FFF
+#define PAGE_IO 0x208
+
+static volatile char page_n_dev = 0;
+static unsigned long iomapadr;
+static DEFINE_SPINLOCK(oct5066_spin);
+
+/*
+ * We use map_priv_1 to identify which device we are.
+ */
+
+static void __oct5066_page(struct map_info *map, __u8 byte)
+{
+ outb(byte,PAGE_IO);
+ page_n_dev = byte;
+}
+
+static inline void oct5066_page(struct map_info *map, unsigned long ofs)
+{
+ __u8 byte = map->map_priv_1 | (ofs >> WINDOW_SHIFT);
+
+ if (page_n_dev != byte)
+ __oct5066_page(map, byte);
+}
+
+
+static map_word oct5066_read8(struct map_info *map, unsigned long ofs)
+{
+ map_word ret;
+ spin_lock(&oct5066_spin);
+ oct5066_page(map, ofs);
+ ret.x[0] = readb(iomapadr + (ofs & WINDOW_MASK));
+ spin_unlock(&oct5066_spin);
+ return ret;
+}
+
+static void oct5066_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len)
+{
+ while(len) {
+ unsigned long thislen = len;
+ if (len > (WINDOW_LENGTH - (from & WINDOW_MASK)))
+ thislen = WINDOW_LENGTH-(from & WINDOW_MASK);
+
+ spin_lock(&oct5066_spin);
+ oct5066_page(map, from);
+ memcpy_fromio(to, iomapadr + from, thislen);
+ spin_unlock(&oct5066_spin);
+ to += thislen;
+ from += thislen;
+ len -= thislen;
+ }
+}
+
+static void oct5066_write8(struct map_info *map, map_word d, unsigned long adr)
+{
+ spin_lock(&oct5066_spin);
+ oct5066_page(map, adr);
+ writeb(d.x[0], iomapadr + (adr & WINDOW_MASK));
+ spin_unlock(&oct5066_spin);
+}
+
+static void oct5066_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len)
+{
+ while(len) {
+ unsigned long thislen = len;
+ if (len > (WINDOW_LENGTH - (to & WINDOW_MASK)))
+ thislen = WINDOW_LENGTH-(to & WINDOW_MASK);
+
+ spin_lock(&oct5066_spin);
+ oct5066_page(map, to);
+ memcpy_toio(iomapadr + to, from, thislen);
+ spin_unlock(&oct5066_spin);
+ to += thislen;
+ from += thislen;
+ len -= thislen;
+ }
+}
+
+static struct map_info oct5066_map[2] = {
+ {
+ .name = "Octagon 5066 Socket",
+ .phys = NO_XIP,
+ .size = 512 * 1024,
+ .bankwidth = 1,
+ .read = oct5066_read8,
+ .copy_from = oct5066_copy_from,
+ .write = oct5066_write8,
+ .copy_to = oct5066_copy_to,
+ .map_priv_1 = 1<<6
+ },
+ {
+ .name = "Octagon 5066 Internal Flash",
+ .phys = NO_XIP,
+ .size = 2 * 1024 * 1024,
+ .bankwidth = 1,
+ .read = oct5066_read8,
+ .copy_from = oct5066_copy_from,
+ .write = oct5066_write8,
+ .copy_to = oct5066_copy_to,
+ .map_priv_1 = 2<<6
+ }
+};
+
+static struct mtd_info *oct5066_mtd[2] = {NULL, NULL};
+
+// OctProbe - Sense if this is an octagon card
+// ---------------------------------------------------------------------
+/* Perform a simple validity test, we map the window select SSD0 and
+ change pages while monitoring the window. A change in the window,
+ controlled by the PAGE_IO port is a functioning 5066 board. This will
+ fail if the thing in the socket is set to a uniform value. */
+static int __init OctProbe(void)
+{
+ unsigned int Base = (1 << 6);
+ unsigned long I;
+ unsigned long Values[10];
+ for (I = 0; I != 20; I++)
+ {
+ outb(Base + (I%10),PAGE_IO);
+ if (I < 10)
+ {
+ // Record the value and check for uniqueness
+ Values[I%10] = readl(iomapadr);
+ if (I > 0 && Values[I%10] == Values[0])
+ return -EAGAIN;
+ }
+ else
+ {
+ // Make sure we get the same values on the second pass
+ if (Values[I%10] != readl(iomapadr))
+ return -EAGAIN;
+ }
+ }
+ return 0;
+}
+
+void cleanup_oct5066(void)
+{
+ int i;
+ for (i=0; i<2; i++) {
+ if (oct5066_mtd[i]) {
+ del_mtd_device(oct5066_mtd[i]);
+ map_destroy(oct5066_mtd[i]);
+ }
+ }
+ iounmap((void *)iomapadr);
+ release_region(PAGE_IO, 1);
+}
+
+int __init init_oct5066(void)
+{
+ int i;
+ int ret = 0;
+
+ // Do an autoprobe sequence
+ if (!request_region(PAGE_IO,1,"Octagon SSD")) {
+ printk(KERN_NOTICE "5066: Page Register in Use\n");
+ return -EAGAIN;
+ }
+ iomapadr = (unsigned long)ioremap(WINDOW_START, WINDOW_LENGTH);
+ if (!iomapadr) {
+ printk(KERN_NOTICE "Failed to ioremap memory region\n");
+ ret = -EIO;
+ goto out_rel;
+ }
+ if (OctProbe() != 0) {
+ printk(KERN_NOTICE "5066: Octagon Probe Failed, is this an Octagon 5066 SBC?\n");
+ iounmap((void *)iomapadr);
+ ret = -EAGAIN;
+ goto out_unmap;
+ }
+
+ // Print out our little header..
+ printk("Octagon 5066 SSD IO:0x%x MEM:0x%x-0x%x\n",PAGE_IO,WINDOW_START,
+ WINDOW_START+WINDOW_LENGTH);
+
+ for (i=0; i<2; i++) {
+ oct5066_mtd[i] = do_map_probe("cfi_probe", &oct5066_map[i]);
+ if (!oct5066_mtd[i])
+ oct5066_mtd[i] = do_map_probe("jedec", &oct5066_map[i]);
+ if (!oct5066_mtd[i])
+ oct5066_mtd[i] = do_map_probe("map_ram", &oct5066_map[i]);
+ if (!oct5066_mtd[i])
+ oct5066_mtd[i] = do_map_probe("map_rom", &oct5066_map[i]);
+ if (oct5066_mtd[i]) {
+ oct5066_mtd[i]->owner = THIS_MODULE;
+ add_mtd_device(oct5066_mtd[i]);
+ }
+ }
+
+ if (!oct5066_mtd[0] && !oct5066_mtd[1]) {
+ cleanup_oct5066();
+ return -ENXIO;
+ }
+
+ return 0;
+
+ out_unmap:
+ iounmap((void *)iomapadr);
+ out_rel:
+ release_region(PAGE_IO, 1);
+ return ret;
+}
+
+module_init(init_oct5066);
+module_exit(cleanup_oct5066);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jason Gunthorpe <jgg@deltatee.com>, David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("MTD map driver for Octagon 5066 Single Board Computer");
diff --git a/drivers/mtd/maps/omap-toto-flash.c b/drivers/mtd/maps/omap-toto-flash.c
new file mode 100644
index 000000000000..496109071cb1
--- /dev/null
+++ b/drivers/mtd/maps/omap-toto-flash.c
@@ -0,0 +1,137 @@
+/*
+ * NOR Flash memory access on TI Toto board
+ *
+ * jzhang@ti.com (C) 2003 Texas Instruments.
+ *
+ * (C) 2002 MontVista Software, Inc.
+ *
+ * $Id: omap-toto-flash.c,v 1.3 2004/09/16 23:27:13 gleixner Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+
+#include <linux/errno.h>
+#include <linux/init.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/hardware.h>
+#include <asm/io.h>
+
+
+#ifndef CONFIG_ARCH_OMAP
+#error This is for OMAP architecture only
+#endif
+
+//these lines need be moved to a hardware header file
+#define OMAP_TOTO_FLASH_BASE 0xd8000000
+#define OMAP_TOTO_FLASH_SIZE 0x80000
+
+static struct map_info omap_toto_map_flash = {
+ .name = "OMAP Toto flash",
+ .bankwidth = 2,
+ .virt = (void __iomem *)OMAP_TOTO_FLASH_BASE,
+};
+
+
+static struct mtd_partition toto_flash_partitions[] = {
+ {
+ .name = "BootLoader",
+ .size = 0x00040000, /* hopefully u-boot will stay 128k + 128*/
+ .offset = 0,
+ .mask_flags = MTD_WRITEABLE, /* force read-only */
+ }, {
+ .name = "ReservedSpace",
+ .size = 0x00030000,
+ .offset = MTDPART_OFS_APPEND,
+ //mask_flags: MTD_WRITEABLE, /* force read-only */
+ }, {
+ .name = "EnvArea", /* bottom 64KiB for env vars */
+ .size = MTDPART_SIZ_FULL,
+ .offset = MTDPART_OFS_APPEND,
+ }
+};
+
+static struct mtd_partition *parsed_parts;
+
+static struct mtd_info *flash_mtd;
+
+static int __init init_flash (void)
+{
+
+ struct mtd_partition *parts;
+ int nb_parts = 0;
+ int parsed_nr_parts = 0;
+ const char *part_type;
+
+ /*
+ * Static partition definition selection
+ */
+ part_type = "static";
+
+ parts = toto_flash_partitions;
+ nb_parts = ARRAY_SIZE(toto_flash_partitions);
+ omap_toto_map_flash.size = OMAP_TOTO_FLASH_SIZE;
+ omap_toto_map_flash.phys = virt_to_phys(OMAP_TOTO_FLASH_BASE);
+
+ simple_map_init(&omap_toto_map_flash);
+ /*
+ * Now let's probe for the actual flash. Do it here since
+ * specific machine settings might have been set above.
+ */
+ printk(KERN_NOTICE "OMAP toto flash: probing %d-bit flash bus\n",
+ omap_toto_map_flash.bankwidth*8);
+ flash_mtd = do_map_probe("jedec_probe", &omap_toto_map_flash);
+ if (!flash_mtd)
+ return -ENXIO;
+
+ if (parsed_nr_parts > 0) {
+ parts = parsed_parts;
+ nb_parts = parsed_nr_parts;
+ }
+
+ if (nb_parts == 0) {
+ printk(KERN_NOTICE "OMAP toto flash: no partition info available,"
+ "registering whole flash at once\n");
+ if (add_mtd_device(flash_mtd)){
+ return -ENXIO;
+ }
+ } else {
+ printk(KERN_NOTICE "Using %s partition definition\n",
+ part_type);
+ return add_mtd_partitions(flash_mtd, parts, nb_parts);
+ }
+ return 0;
+}
+
+int __init omap_toto_mtd_init(void)
+{
+ int status;
+
+ if (status = init_flash()) {
+ printk(KERN_ERR "OMAP Toto Flash: unable to init map for toto flash\n");
+ }
+ return status;
+}
+
+static void __exit omap_toto_mtd_cleanup(void)
+{
+ if (flash_mtd) {
+ del_mtd_partitions(flash_mtd);
+ map_destroy(flash_mtd);
+ if (parsed_parts)
+ kfree(parsed_parts);
+ }
+}
+
+module_init(omap_toto_mtd_init);
+module_exit(omap_toto_mtd_cleanup);
+
+MODULE_AUTHOR("Jian Zhang");
+MODULE_DESCRIPTION("OMAP Toto board map driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/maps/pb1550-flash.c b/drivers/mtd/maps/pb1550-flash.c
new file mode 100644
index 000000000000..1424726a219e
--- /dev/null
+++ b/drivers/mtd/maps/pb1550-flash.c
@@ -0,0 +1,203 @@
+/*
+ * Flash memory access on Alchemy Pb1550 board
+ *
+ * $Id: pb1550-flash.c,v 1.6 2004/11/04 13:24:15 gleixner Exp $
+ *
+ * (C) 2004 Embedded Edge, LLC, based on pb1550-flash.c:
+ * (C) 2003 Pete Popov <ppopov@pacbell.net>
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/io.h>
+#include <asm/au1000.h>
+#include <asm/pb1550.h>
+
+#ifdef DEBUG_RW
+#define DBG(x...) printk(x)
+#else
+#define DBG(x...)
+#endif
+
+static unsigned long window_addr;
+static unsigned long window_size;
+
+
+static struct map_info pb1550_map = {
+ .name = "Pb1550 flash",
+};
+
+static unsigned char flash_bankwidth = 4;
+
+/*
+ * Support only 64MB NOR Flash parts
+ */
+
+#ifdef PB1550_BOTH_BANKS
+/* both banks will be used. Combine the first bank and the first
+ * part of the second bank together into a single jffs/jffs2
+ * partition.
+ */
+static struct mtd_partition pb1550_partitions[] = {
+ /* assume boot[2:0]:swap is '0000' or '1000', which translates to:
+ * 1C00 0000 1FFF FFFF CE0 64MB Boot NOR Flash
+ * 1800 0000 1BFF FFFF CE0 64MB Param NOR Flash
+ */
+ {
+ .name = "User FS",
+ .size = (0x1FC00000 - 0x18000000),
+ .offset = 0x0000000
+ },{
+ .name = "yamon",
+ .size = 0x0100000,
+ .offset = MTDPART_OFS_APPEND,
+ .mask_flags = MTD_WRITEABLE
+ },{
+ .name = "raw kernel",
+ .size = (0x300000 - 0x40000), /* last 256KB is yamon env */
+ .offset = MTDPART_OFS_APPEND,
+ }
+};
+#elif defined(PB1550_BOOT_ONLY)
+static struct mtd_partition pb1550_partitions[] = {
+ /* assume boot[2:0]:swap is '0000' or '1000', which translates to:
+ * 1C00 0000 1FFF FFFF CE0 64MB Boot NOR Flash
+ */
+ {
+ .name = "User FS",
+ .size = 0x03c00000,
+ .offset = 0x0000000
+ },{
+ .name = "yamon",
+ .size = 0x0100000,
+ .offset = MTDPART_OFS_APPEND,
+ .mask_flags = MTD_WRITEABLE
+ },{
+ .name = "raw kernel",
+ .size = (0x300000-0x40000), /* last 256KB is yamon env */
+ .offset = MTDPART_OFS_APPEND,
+ }
+};
+#elif defined(PB1550_USER_ONLY)
+static struct mtd_partition pb1550_partitions[] = {
+ /* assume boot[2:0]:swap is '0000' or '1000', which translates to:
+ * 1800 0000 1BFF FFFF CE0 64MB Param NOR Flash
+ */
+ {
+ .name = "User FS",
+ .size = (0x4000000 - 0x200000), /* reserve 2MB for raw kernel */
+ .offset = 0x0000000
+ },{
+ .name = "raw kernel",
+ .size = MTDPART_SIZ_FULL,
+ .offset = MTDPART_OFS_APPEND,
+ }
+};
+#else
+#error MTD_PB1550 define combo error /* should never happen */
+#endif
+
+#define NB_OF(x) (sizeof(x)/sizeof(x[0]))
+
+static struct mtd_info *mymtd;
+
+/*
+ * Probe the flash density and setup window address and size
+ * based on user CONFIG options. There are times when we don't
+ * want the MTD driver to be probing the boot or user flash,
+ * so having the option to enable only one bank is important.
+ */
+int setup_flash_params(void)
+{
+ u16 boot_swapboot;
+ boot_swapboot = (au_readl(MEM_STSTAT) & (0x7<<1)) |
+ ((bcsr->status >> 6) & 0x1);
+ printk("Pb1550 MTD: boot:swap %d\n", boot_swapboot);
+
+ switch (boot_swapboot) {
+ case 0: /* 512Mbit devices, both enabled */
+ case 1:
+ case 8:
+ case 9:
+#if defined(PB1550_BOTH_BANKS)
+ window_addr = 0x18000000;
+ window_size = 0x8000000;
+#elif defined(PB1550_BOOT_ONLY)
+ window_addr = 0x1C000000;
+ window_size = 0x4000000;
+#else /* USER ONLY */
+ window_addr = 0x1E000000;
+ window_size = 0x4000000;
+#endif
+ break;
+ case 0xC:
+ case 0xD:
+ case 0xE:
+ case 0xF:
+ /* 64 MB Boot NOR Flash is disabled */
+ /* and the start address is moved to 0x0C00000 */
+ window_addr = 0x0C000000;
+ window_size = 0x4000000;
+ default:
+ printk("Pb1550 MTD: unsupported boot:swap setting\n");
+ return 1;
+ }
+ return 0;
+}
+
+int __init pb1550_mtd_init(void)
+{
+ struct mtd_partition *parts;
+ int nb_parts = 0;
+
+ /* Default flash bankwidth */
+ pb1550_map.bankwidth = flash_bankwidth;
+
+ if (setup_flash_params())
+ return -ENXIO;
+
+ /*
+ * Static partition definition selection
+ */
+ parts = pb1550_partitions;
+ nb_parts = NB_OF(pb1550_partitions);
+ pb1550_map.size = window_size;
+
+ /*
+ * Now let's probe for the actual flash. Do it here since
+ * specific machine settings might have been set above.
+ */
+ printk(KERN_NOTICE "Pb1550 flash: probing %d-bit flash bus\n",
+ pb1550_map.bankwidth*8);
+ pb1550_map.virt = ioremap(window_addr, window_size);
+ mymtd = do_map_probe("cfi_probe", &pb1550_map);
+ if (!mymtd) return -ENXIO;
+ mymtd->owner = THIS_MODULE;
+
+ add_mtd_partitions(mymtd, parts, nb_parts);
+ return 0;
+}
+
+static void __exit pb1550_mtd_cleanup(void)
+{
+ if (mymtd) {
+ del_mtd_partitions(mymtd);
+ map_destroy(mymtd);
+ }
+}
+
+module_init(pb1550_mtd_init);
+module_exit(pb1550_mtd_cleanup);
+
+MODULE_AUTHOR("Embedded Edge, LLC");
+MODULE_DESCRIPTION("Pb1550 mtd map driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/maps/pb1xxx-flash.c b/drivers/mtd/maps/pb1xxx-flash.c
new file mode 100644
index 000000000000..06e731540552
--- /dev/null
+++ b/drivers/mtd/maps/pb1xxx-flash.c
@@ -0,0 +1,178 @@
+/*
+ * Flash memory access on Alchemy Pb1xxx boards
+ *
+ * (C) 2001 Pete Popov <ppopov@mvista.com>
+ *
+ * $Id: pb1xxx-flash.c,v 1.14 2004/11/04 13:24:15 gleixner Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/io.h>
+
+#ifdef DEBUG_RW
+#define DBG(x...) printk(x)
+#else
+#define DBG(x...)
+#endif
+
+#ifdef CONFIG_MIPS_PB1000
+
+#define WINDOW_ADDR 0x1F800000
+#define WINDOW_SIZE 0x800000
+
+static struct mtd_partition pb1xxx_partitions[] = {
+ {
+ .name = "yamon env",
+ .size = 0x00020000,
+ .offset = 0,
+ .mask_flags = MTD_WRITEABLE},
+ {
+ .name = "User FS",
+ .size = 0x003e0000,
+ .offset = 0x20000,},
+ {
+ .name = "boot code",
+ .size = 0x100000,
+ .offset = 0x400000,
+ .mask_flags = MTD_WRITEABLE},
+ {
+ .name = "raw/kernel",
+ .size = 0x300000,
+ .offset = 0x500000}
+};
+
+#elif defined(CONFIG_MIPS_PB1500) || defined(CONFIG_MIPS_PB1100)
+
+#if defined(CONFIG_MTD_PB1500_BOOT) && defined(CONFIG_MTD_PB1500_USER)
+/* both 32MB banks will be used. Combine the first 32MB bank and the
+ * first 28MB of the second bank together into a single jffs/jffs2
+ * partition.
+ */
+#define WINDOW_ADDR 0x1C000000
+#define WINDOW_SIZE 0x4000000
+static struct mtd_partition pb1xxx_partitions[] = {
+ {
+ .name = "User FS",
+ .size = 0x3c00000,
+ .offset = 0x0000000
+ },{
+ .name = "yamon",
+ .size = 0x0100000,
+ .offset = 0x3c00000,
+ .mask_flags = MTD_WRITEABLE
+ },{
+ .name = "raw kernel",
+ .size = 0x02c0000,
+ .offset = 0x3d00000
+ }
+};
+#elif defined(CONFIG_MTD_PB1500_BOOT) && !defined(CONFIG_MTD_PB1500_USER)
+#define WINDOW_ADDR 0x1E000000
+#define WINDOW_SIZE 0x2000000
+static struct mtd_partition pb1xxx_partitions[] = {
+ {
+ .name = "User FS",
+ .size = 0x1c00000,
+ .offset = 0x0000000
+ },{
+ .name = "yamon",
+ .size = 0x0100000,
+ .offset = 0x1c00000,
+ .mask_flags = MTD_WRITEABLE
+ },{
+ .name = "raw kernel",
+ .size = 0x02c0000,
+ .offset = 0x1d00000
+ }
+};
+#elif !defined(CONFIG_MTD_PB1500_BOOT) && defined(CONFIG_MTD_PB1500_USER)
+#define WINDOW_ADDR 0x1C000000
+#define WINDOW_SIZE 0x2000000
+static struct mtd_partition pb1xxx_partitions[] = {
+ {
+ .name = "User FS",
+ .size = 0x1e00000,
+ .offset = 0x0000000
+ },{
+ .name = "raw kernel",
+ .size = 0x0200000,
+ .offset = 0x1e00000,
+ }
+};
+#else
+#error MTD_PB1500 define combo error /* should never happen */
+#endif
+#else
+#error Unsupported board
+#endif
+
+#define NAME "Pb1x00 Linux Flash"
+#define PADDR WINDOW_ADDR
+#define BUSWIDTH 4
+#define SIZE WINDOW_SIZE
+#define PARTITIONS 4
+
+static struct map_info pb1xxx_mtd_map = {
+ .name = NAME,
+ .size = SIZE,
+ .bankwidth = BUSWIDTH,
+ .phys = PADDR,
+};
+
+static struct mtd_info *pb1xxx_mtd;
+
+int __init pb1xxx_mtd_init(void)
+{
+ struct mtd_partition *parts;
+ int nb_parts = 0;
+ char *part_type;
+
+ /*
+ * Static partition definition selection
+ */
+ part_type = "static";
+ parts = pb1xxx_partitions;
+ nb_parts = ARRAY_SIZE(pb1xxx_partitions);
+
+ /*
+ * Now let's probe for the actual flash. Do it here since
+ * specific machine settings might have been set above.
+ */
+ printk(KERN_NOTICE "Pb1xxx flash: probing %d-bit flash bus\n",
+ BUSWIDTH*8);
+ pb1xxx_mtd_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE);
+
+ simple_map_init(&pb1xxx_mtd_map);
+
+ pb1xxx_mtd = do_map_probe("cfi_probe", &pb1xxx_mtd_map);
+ if (!pb1xxx_mtd) return -ENXIO;
+ pb1xxx_mtd->owner = THIS_MODULE;
+
+ add_mtd_partitions(pb1xxx_mtd, parts, nb_parts);
+ return 0;
+}
+
+static void __exit pb1xxx_mtd_cleanup(void)
+{
+ if (pb1xxx_mtd) {
+ del_mtd_partitions(pb1xxx_mtd);
+ map_destroy(pb1xxx_mtd);
+ iounmap((void *) pb1xxx_mtd_map.virt);
+ }
+}
+
+module_init(pb1xxx_mtd_init);
+module_exit(pb1xxx_mtd_cleanup);
+
+MODULE_AUTHOR("Pete Popov");
+MODULE_DESCRIPTION("Pb1xxx CFI map driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/maps/pci.c b/drivers/mtd/maps/pci.c
new file mode 100644
index 000000000000..08b60bdc5381
--- /dev/null
+++ b/drivers/mtd/maps/pci.c
@@ -0,0 +1,388 @@
+/*
+ * linux/drivers/mtd/maps/pci.c
+ *
+ * Copyright (C) 2001 Russell King, All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * $Id: pci.c,v 1.9 2004/11/28 09:40:40 dwmw2 Exp $
+ *
+ * Generic PCI memory map driver. We support the following boards:
+ * - Intel IQ80310 ATU.
+ * - Intel EBSA285 (blank rom programming mode). Tested working 27/09/2001
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+struct map_pci_info;
+
+struct mtd_pci_info {
+ int (*init)(struct pci_dev *dev, struct map_pci_info *map);
+ void (*exit)(struct pci_dev *dev, struct map_pci_info *map);
+ unsigned long (*translate)(struct map_pci_info *map, unsigned long ofs);
+ const char *map_name;
+};
+
+struct map_pci_info {
+ struct map_info map;
+ void __iomem *base;
+ void (*exit)(struct pci_dev *dev, struct map_pci_info *map);
+ unsigned long (*translate)(struct map_pci_info *map, unsigned long ofs);
+ struct pci_dev *dev;
+};
+
+static map_word mtd_pci_read8(struct map_info *_map, unsigned long ofs)
+{
+ struct map_pci_info *map = (struct map_pci_info *)_map;
+ map_word val;
+ val.x[0]= readb(map->base + map->translate(map, ofs));
+// printk("read8 : %08lx => %02x\n", ofs, val.x[0]);
+ return val;
+}
+
+#if 0
+static map_word mtd_pci_read16(struct map_info *_map, unsigned long ofs)
+{
+ struct map_pci_info *map = (struct map_pci_info *)_map;
+ map_word val;
+ val.x[0] = readw(map->base + map->translate(map, ofs));
+// printk("read16: %08lx => %04x\n", ofs, val.x[0]);
+ return val;
+}
+#endif
+static map_word mtd_pci_read32(struct map_info *_map, unsigned long ofs)
+{
+ struct map_pci_info *map = (struct map_pci_info *)_map;
+ map_word val;
+ val.x[0] = readl(map->base + map->translate(map, ofs));
+// printk("read32: %08lx => %08x\n", ofs, val.x[0]);
+ return val;
+}
+
+static void mtd_pci_copyfrom(struct map_info *_map, void *to, unsigned long from, ssize_t len)
+{
+ struct map_pci_info *map = (struct map_pci_info *)_map;
+ memcpy_fromio(to, map->base + map->translate(map, from), len);
+}
+
+static void mtd_pci_write8(struct map_info *_map, map_word val, unsigned long ofs)
+{
+ struct map_pci_info *map = (struct map_pci_info *)_map;
+// printk("write8 : %08lx <= %02x\n", ofs, val.x[0]);
+ writeb(val.x[0], map->base + map->translate(map, ofs));
+}
+
+#if 0
+static void mtd_pci_write16(struct map_info *_map, map_word val, unsigned long ofs)
+{
+ struct map_pci_info *map = (struct map_pci_info *)_map;
+// printk("write16: %08lx <= %04x\n", ofs, val.x[0]);
+ writew(val.x[0], map->base + map->translate(map, ofs));
+}
+#endif
+static void mtd_pci_write32(struct map_info *_map, map_word val, unsigned long ofs)
+{
+ struct map_pci_info *map = (struct map_pci_info *)_map;
+// printk("write32: %08lx <= %08x\n", ofs, val.x[0]);
+ writel(val.x[0], map->base + map->translate(map, ofs));
+}
+
+static void mtd_pci_copyto(struct map_info *_map, unsigned long to, const void *from, ssize_t len)
+{
+ struct map_pci_info *map = (struct map_pci_info *)_map;
+ memcpy_toio(map->base + map->translate(map, to), from, len);
+}
+
+static struct map_info mtd_pci_map = {
+ .phys = NO_XIP,
+ .copy_from = mtd_pci_copyfrom,
+ .copy_to = mtd_pci_copyto,
+};
+
+/*
+ * Intel IOP80310 Flash driver
+ */
+
+static int
+intel_iq80310_init(struct pci_dev *dev, struct map_pci_info *map)
+{
+ u32 win_base;
+
+ map->map.bankwidth = 1;
+ map->map.read = mtd_pci_read8,
+ map->map.write = mtd_pci_write8,
+
+ map->map.size = 0x00800000;
+ map->base = ioremap_nocache(pci_resource_start(dev, 0),
+ pci_resource_len(dev, 0));
+
+ if (!map->base)
+ return -ENOMEM;
+
+ /*
+ * We want to base the memory window at Xscale
+ * bus address 0, not 0x1000.
+ */
+ pci_read_config_dword(dev, 0x44, &win_base);
+ pci_write_config_dword(dev, 0x44, 0);
+
+ map->map.map_priv_2 = win_base;
+
+ return 0;
+}
+
+static void
+intel_iq80310_exit(struct pci_dev *dev, struct map_pci_info *map)
+{
+ if (map->base)
+ iounmap(map->base);
+ pci_write_config_dword(dev, 0x44, map->map.map_priv_2);
+}
+
+static unsigned long
+intel_iq80310_translate(struct map_pci_info *map, unsigned long ofs)
+{
+ unsigned long page_addr = ofs & 0x00400000;
+
+ /*
+ * This mundges the flash location so we avoid
+ * the first 80 bytes (they appear to read nonsense).
+ */
+ if (page_addr) {
+ writel(0x00000008, map->base + 0x1558);
+ writel(0x00000000, map->base + 0x1550);
+ } else {
+ writel(0x00000007, map->base + 0x1558);
+ writel(0x00800000, map->base + 0x1550);
+ ofs += 0x00800000;
+ }
+
+ return ofs;
+}
+
+static struct mtd_pci_info intel_iq80310_info = {
+ .init = intel_iq80310_init,
+ .exit = intel_iq80310_exit,
+ .translate = intel_iq80310_translate,
+ .map_name = "cfi_probe",
+};
+
+/*
+ * Intel DC21285 driver
+ */
+
+static int
+intel_dc21285_init(struct pci_dev *dev, struct map_pci_info *map)
+{
+ unsigned long base, len;
+
+ base = pci_resource_start(dev, PCI_ROM_RESOURCE);
+ len = pci_resource_len(dev, PCI_ROM_RESOURCE);
+
+ if (!len || !base) {
+ /*
+ * No ROM resource
+ */
+ base = pci_resource_start(dev, 2);
+ len = pci_resource_len(dev, 2);
+
+ /*
+ * We need to re-allocate PCI BAR2 address range to the
+ * PCI ROM BAR, and disable PCI BAR2.
+ */
+ } else {
+ /*
+ * Hmm, if an address was allocated to the ROM resource, but
+ * not enabled, should we be allocating a new resource for it
+ * or simply enabling it?
+ */
+ if (!(pci_resource_flags(dev, PCI_ROM_RESOURCE) &
+ IORESOURCE_ROM_ENABLE)) {
+ u32 val;
+ pci_resource_flags(dev, PCI_ROM_RESOURCE) |= IORESOURCE_ROM_ENABLE;
+ pci_read_config_dword(dev, PCI_ROM_ADDRESS, &val);
+ val |= PCI_ROM_ADDRESS_ENABLE;
+ pci_write_config_dword(dev, PCI_ROM_ADDRESS, val);
+ printk("%s: enabling expansion ROM\n", pci_name(dev));
+ }
+ }
+
+ if (!len || !base)
+ return -ENXIO;
+
+ map->map.bankwidth = 4;
+ map->map.read = mtd_pci_read32,
+ map->map.write = mtd_pci_write32,
+ map->map.size = len;
+ map->base = ioremap_nocache(base, len);
+
+ if (!map->base)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void
+intel_dc21285_exit(struct pci_dev *dev, struct map_pci_info *map)
+{
+ u32 val;
+
+ if (map->base)
+ iounmap(map->base);
+
+ /*
+ * We need to undo the PCI BAR2/PCI ROM BAR address alteration.
+ */
+ pci_resource_flags(dev, PCI_ROM_RESOURCE) &= ~IORESOURCE_ROM_ENABLE;
+ pci_read_config_dword(dev, PCI_ROM_ADDRESS, &val);
+ val &= ~PCI_ROM_ADDRESS_ENABLE;
+ pci_write_config_dword(dev, PCI_ROM_ADDRESS, val);
+}
+
+static unsigned long
+intel_dc21285_translate(struct map_pci_info *map, unsigned long ofs)
+{
+ return ofs & 0x00ffffc0 ? ofs : (ofs ^ (1 << 5));
+}
+
+static struct mtd_pci_info intel_dc21285_info = {
+ .init = intel_dc21285_init,
+ .exit = intel_dc21285_exit,
+ .translate = intel_dc21285_translate,
+ .map_name = "jedec_probe",
+};
+
+/*
+ * PCI device ID table
+ */
+
+static struct pci_device_id mtd_pci_ids[] = {
+ {
+ .vendor = PCI_VENDOR_ID_INTEL,
+ .device = 0x530d,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ .class = PCI_CLASS_MEMORY_OTHER << 8,
+ .class_mask = 0xffff00,
+ .driver_data = (unsigned long)&intel_iq80310_info,
+ },
+ {
+ .vendor = PCI_VENDOR_ID_DEC,
+ .device = PCI_DEVICE_ID_DEC_21285,
+ .subvendor = 0, /* DC21285 defaults to 0 on reset */
+ .subdevice = 0, /* DC21285 defaults to 0 on reset */
+ .driver_data = (unsigned long)&intel_dc21285_info,
+ },
+ { 0, }
+};
+
+/*
+ * Generic code follows.
+ */
+
+static int __devinit
+mtd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+ struct mtd_pci_info *info = (struct mtd_pci_info *)id->driver_data;
+ struct map_pci_info *map = NULL;
+ struct mtd_info *mtd = NULL;
+ int err;
+
+ err = pci_enable_device(dev);
+ if (err)
+ goto out;
+
+ err = pci_request_regions(dev, "pci mtd");
+ if (err)
+ goto out;
+
+ map = kmalloc(sizeof(*map), GFP_KERNEL);
+ err = -ENOMEM;
+ if (!map)
+ goto release;
+
+ map->map = mtd_pci_map;
+ map->map.name = pci_name(dev);
+ map->dev = dev;
+ map->exit = info->exit;
+ map->translate = info->translate;
+
+ err = info->init(dev, map);
+ if (err)
+ goto release;
+
+ /* tsk - do_map_probe should take const char * */
+ mtd = do_map_probe((char *)info->map_name, &map->map);
+ err = -ENODEV;
+ if (!mtd)
+ goto release;
+
+ mtd->owner = THIS_MODULE;
+ add_mtd_device(mtd);
+
+ pci_set_drvdata(dev, mtd);
+
+ return 0;
+
+release:
+ if (mtd)
+ map_destroy(mtd);
+
+ if (map) {
+ map->exit(dev, map);
+ kfree(map);
+ }
+
+ pci_release_regions(dev);
+out:
+ return err;
+}
+
+static void __devexit
+mtd_pci_remove(struct pci_dev *dev)
+{
+ struct mtd_info *mtd = pci_get_drvdata(dev);
+ struct map_pci_info *map = mtd->priv;
+
+ del_mtd_device(mtd);
+ map_destroy(mtd);
+ map->exit(dev, map);
+ kfree(map);
+
+ pci_set_drvdata(dev, NULL);
+ pci_release_regions(dev);
+}
+
+static struct pci_driver mtd_pci_driver = {
+ .name = "MTD PCI",
+ .probe = mtd_pci_probe,
+ .remove = __devexit_p(mtd_pci_remove),
+ .id_table = mtd_pci_ids,
+};
+
+static int __init mtd_pci_maps_init(void)
+{
+ return pci_module_init(&mtd_pci_driver);
+}
+
+static void __exit mtd_pci_maps_exit(void)
+{
+ pci_unregister_driver(&mtd_pci_driver);
+}
+
+module_init(mtd_pci_maps_init);
+module_exit(mtd_pci_maps_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>");
+MODULE_DESCRIPTION("Generic PCI map driver");
+MODULE_DEVICE_TABLE(pci, mtd_pci_ids);
+
diff --git a/drivers/mtd/maps/pcmciamtd.c b/drivers/mtd/maps/pcmciamtd.c
new file mode 100644
index 000000000000..e37b4c1976e5
--- /dev/null
+++ b/drivers/mtd/maps/pcmciamtd.c
@@ -0,0 +1,860 @@
+/*
+ * $Id: pcmciamtd.c,v 1.51 2004/07/12 22:38:29 dwmw2 Exp $
+ *
+ * pcmciamtd.c - MTD driver for PCMCIA flash memory cards
+ *
+ * Author: Simon Evans <spse@secret.org.uk>
+ *
+ * Copyright (C) 2002 Simon Evans
+ *
+ * Licence: GPL
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <asm/system.h>
+
+#include <pcmcia/version.h>
+#include <pcmcia/cs_types.h>
+#include <pcmcia/cs.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.h>
+
+#include <linux/mtd/map.h>
+#include <linux/mtd/mtd.h>
+
+#ifdef CONFIG_MTD_DEBUG
+static int debug = CONFIG_MTD_DEBUG_VERBOSE;
+MODULE_PARM(debug, "i");
+MODULE_PARM_DESC(debug, "Set Debug Level 0=quiet, 5=noisy");
+#undef DEBUG
+#define DEBUG(n, format, arg...) \
+ if (n <= debug) { \
+ printk(KERN_DEBUG __FILE__ ":%s(): " format "\n", __FUNCTION__ , ## arg); \
+ }
+
+#else
+#undef DEBUG
+#define DEBUG(n, arg...)
+static const int debug = 0;
+#endif
+
+#define err(format, arg...) printk(KERN_ERR "pcmciamtd: " format "\n" , ## arg)
+#define info(format, arg...) printk(KERN_INFO "pcmciamtd: " format "\n" , ## arg)
+#define warn(format, arg...) printk(KERN_WARNING "pcmciamtd: " format "\n" , ## arg)
+
+
+#define DRIVER_DESC "PCMCIA Flash memory card driver"
+#define DRIVER_VERSION "$Revision: 1.51 $"
+
+/* Size of the PCMCIA address space: 26 bits = 64 MB */
+#define MAX_PCMCIA_ADDR 0x4000000
+
+struct pcmciamtd_dev {
+ dev_link_t link; /* PCMCIA link */
+ dev_node_t node; /* device node */
+ caddr_t win_base; /* ioremapped address of PCMCIA window */
+ unsigned int win_size; /* size of window */
+ unsigned int offset; /* offset into card the window currently points at */
+ struct map_info pcmcia_map;
+ struct mtd_info *mtd_info;
+ int vpp;
+ char mtd_name[sizeof(struct cistpl_vers_1_t)];
+};
+
+
+static dev_info_t dev_info = "pcmciamtd";
+static dev_link_t *dev_list;
+
+/* Module parameters */
+
+/* 2 = do 16-bit transfers, 1 = do 8-bit transfers */
+static int bankwidth = 2;
+
+/* Speed of memory accesses, in ns */
+static int mem_speed;
+
+/* Force the size of an SRAM card */
+static int force_size;
+
+/* Force Vpp */
+static int vpp;
+
+/* Set Vpp */
+static int setvpp;
+
+/* Force card to be treated as FLASH, ROM or RAM */
+static int mem_type;
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Simon Evans <spse@secret.org.uk>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_PARM(bankwidth, "i");
+MODULE_PARM_DESC(bankwidth, "Set bankwidth (1=8 bit, 2=16 bit, default=2)");
+MODULE_PARM(mem_speed, "i");
+MODULE_PARM_DESC(mem_speed, "Set memory access speed in ns");
+MODULE_PARM(force_size, "i");
+MODULE_PARM_DESC(force_size, "Force size of card in MiB (1-64)");
+MODULE_PARM(setvpp, "i");
+MODULE_PARM_DESC(setvpp, "Set Vpp (0=Never, 1=On writes, 2=Always on, default=0)");
+MODULE_PARM(vpp, "i");
+MODULE_PARM_DESC(vpp, "Vpp value in 1/10ths eg 33=3.3V 120=12V (Dangerous)");
+MODULE_PARM(mem_type, "i");
+MODULE_PARM_DESC(mem_type, "Set Memory type (0=Flash, 1=RAM, 2=ROM, default=0)");
+
+
+/* read/write{8,16} copy_{from,to} routines with window remapping to access whole card */
+static caddr_t remap_window(struct map_info *map, unsigned long to)
+{
+ struct pcmciamtd_dev *dev = (struct pcmciamtd_dev *)map->map_priv_1;
+ window_handle_t win = (window_handle_t)map->map_priv_2;
+ memreq_t mrq;
+ int ret;
+
+ if(!(dev->link.state & DEV_PRESENT)) {
+ DEBUG(1, "device removed state = 0x%4.4X", dev->link.state);
+ return 0;
+ }
+
+ mrq.CardOffset = to & ~(dev->win_size-1);
+ if(mrq.CardOffset != dev->offset) {
+ DEBUG(2, "Remapping window from 0x%8.8x to 0x%8.8x",
+ dev->offset, mrq.CardOffset);
+ mrq.Page = 0;
+ if( (ret = pcmcia_map_mem_page(win, &mrq)) != CS_SUCCESS) {
+ cs_error(dev->link.handle, MapMemPage, ret);
+ return NULL;
+ }
+ dev->offset = mrq.CardOffset;
+ }
+ return dev->win_base + (to & (dev->win_size-1));
+}
+
+
+static map_word pcmcia_read8_remap(struct map_info *map, unsigned long ofs)
+{
+ caddr_t addr;
+ map_word d = {{0}};
+
+ addr = remap_window(map, ofs);
+ if(!addr)
+ return d;
+
+ d.x[0] = readb(addr);
+ DEBUG(3, "ofs = 0x%08lx (%p) data = 0x%02x", ofs, addr, d.x[0]);
+ return d;
+}
+
+
+static map_word pcmcia_read16_remap(struct map_info *map, unsigned long ofs)
+{
+ caddr_t addr;
+ map_word d = {{0}};
+
+ addr = remap_window(map, ofs);
+ if(!addr)
+ return d;
+
+ d.x[0] = readw(addr);
+ DEBUG(3, "ofs = 0x%08lx (%p) data = 0x%04x", ofs, addr, d.x[0]);
+ return d;
+}
+
+
+static void pcmcia_copy_from_remap(struct map_info *map, void *to, unsigned long from, ssize_t len)
+{
+ struct pcmciamtd_dev *dev = (struct pcmciamtd_dev *)map->map_priv_1;
+ unsigned long win_size = dev->win_size;
+
+ DEBUG(3, "to = %p from = %lu len = %u", to, from, len);
+ while(len) {
+ int toread = win_size - (from & (win_size-1));
+ caddr_t addr;
+
+ if(toread > len)
+ toread = len;
+
+ addr = remap_window(map, from);
+ if(!addr)
+ return;
+
+ DEBUG(4, "memcpy from %p to %p len = %d", addr, to, toread);
+ memcpy_fromio(to, addr, toread);
+ len -= toread;
+ to += toread;
+ from += toread;
+ }
+}
+
+
+static void pcmcia_write8_remap(struct map_info *map, map_word d, unsigned long adr)
+{
+ caddr_t addr = remap_window(map, adr);
+
+ if(!addr)
+ return;
+
+ DEBUG(3, "adr = 0x%08lx (%p) data = 0x%02x", adr, addr, d.x[0]);
+ writeb(d.x[0], addr);
+}
+
+
+static void pcmcia_write16_remap(struct map_info *map, map_word d, unsigned long adr)
+{
+ caddr_t addr = remap_window(map, adr);
+ if(!addr)
+ return;
+
+ DEBUG(3, "adr = 0x%08lx (%p) data = 0x%04x", adr, addr, d.x[0]);
+ writew(d.x[0], addr);
+}
+
+
+static void pcmcia_copy_to_remap(struct map_info *map, unsigned long to, const void *from, ssize_t len)
+{
+ struct pcmciamtd_dev *dev = (struct pcmciamtd_dev *)map->map_priv_1;
+ unsigned long win_size = dev->win_size;
+
+ DEBUG(3, "to = %lu from = %p len = %u", to, from, len);
+ while(len) {
+ int towrite = win_size - (to & (win_size-1));
+ caddr_t addr;
+
+ if(towrite > len)
+ towrite = len;
+
+ addr = remap_window(map, to);
+ if(!addr)
+ return;
+
+ DEBUG(4, "memcpy from %p to %p len = %d", from, addr, towrite);
+ memcpy_toio(addr, from, towrite);
+ len -= towrite;
+ to += towrite;
+ from += towrite;
+ }
+}
+
+
+/* read/write{8,16} copy_{from,to} routines with direct access */
+
+#define DEV_REMOVED(x) (!(*(u_int *)x->map_priv_1 & DEV_PRESENT))
+
+static map_word pcmcia_read8(struct map_info *map, unsigned long ofs)
+{
+ caddr_t win_base = (caddr_t)map->map_priv_2;
+ map_word d = {{0}};
+
+ if(DEV_REMOVED(map))
+ return d;
+
+ d.x[0] = readb(win_base + ofs);
+ DEBUG(3, "ofs = 0x%08lx (%p) data = 0x%02x", ofs, win_base + ofs, d.x[0]);
+ return d;
+}
+
+
+static map_word pcmcia_read16(struct map_info *map, unsigned long ofs)
+{
+ caddr_t win_base = (caddr_t)map->map_priv_2;
+ map_word d = {{0}};
+
+ if(DEV_REMOVED(map))
+ return d;
+
+ d.x[0] = readw(win_base + ofs);
+ DEBUG(3, "ofs = 0x%08lx (%p) data = 0x%04x", ofs, win_base + ofs, d.x[0]);
+ return d;
+}
+
+
+static void pcmcia_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len)
+{
+ caddr_t win_base = (caddr_t)map->map_priv_2;
+
+ if(DEV_REMOVED(map))
+ return;
+
+ DEBUG(3, "to = %p from = %lu len = %u", to, from, len);
+ memcpy_fromio(to, win_base + from, len);
+}
+
+
+static void pcmcia_write8(struct map_info *map, u8 d, unsigned long adr)
+{
+ caddr_t win_base = (caddr_t)map->map_priv_2;
+
+ if(DEV_REMOVED(map))
+ return;
+
+ DEBUG(3, "adr = 0x%08lx (%p) data = 0x%02x", adr, win_base + adr, d);
+ writeb(d, win_base + adr);
+}
+
+
+static void pcmcia_write16(struct map_info *map, u16 d, unsigned long adr)
+{
+ caddr_t win_base = (caddr_t)map->map_priv_2;
+
+ if(DEV_REMOVED(map))
+ return;
+
+ DEBUG(3, "adr = 0x%08lx (%p) data = 0x%04x", adr, win_base + adr, d);
+ writew(d, win_base + adr);
+}
+
+
+static void pcmcia_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len)
+{
+ caddr_t win_base = (caddr_t)map->map_priv_2;
+
+ if(DEV_REMOVED(map))
+ return;
+
+ DEBUG(3, "to = %lu from = %p len = %u", to, from, len);
+ memcpy_toio(win_base + to, from, len);
+}
+
+
+static void pcmciamtd_set_vpp(struct map_info *map, int on)
+{
+ struct pcmciamtd_dev *dev = (struct pcmciamtd_dev *)map->map_priv_1;
+ dev_link_t *link = &dev->link;
+ modconf_t mod;
+ int ret;
+
+ mod.Attributes = CONF_VPP1_CHANGE_VALID | CONF_VPP2_CHANGE_VALID;
+ mod.Vcc = 0;
+ mod.Vpp1 = mod.Vpp2 = on ? dev->vpp : 0;
+
+ DEBUG(2, "dev = %p on = %d vpp = %d\n", dev, on, dev->vpp);
+ ret = pcmcia_modify_configuration(link->handle, &mod);
+ if(ret != CS_SUCCESS) {
+ cs_error(link->handle, ModifyConfiguration, ret);
+ }
+}
+
+
+/* After a card is removed, pcmciamtd_release() will unregister the
+ * device, and release the PCMCIA configuration. If the device is
+ * still open, this will be postponed until it is closed.
+ */
+
+static void pcmciamtd_release(dev_link_t *link)
+{
+ struct pcmciamtd_dev *dev = link->priv;
+
+ DEBUG(3, "link = 0x%p", link);
+
+ if (link->win) {
+ if(dev->win_base) {
+ iounmap(dev->win_base);
+ dev->win_base = NULL;
+ }
+ pcmcia_release_window(link->win);
+ }
+ pcmcia_release_configuration(link->handle);
+ link->state &= ~DEV_CONFIG;
+}
+
+
+static void card_settings(struct pcmciamtd_dev *dev, dev_link_t *link, int *new_name)
+{
+ int rc;
+ tuple_t tuple;
+ cisparse_t parse;
+ u_char buf[64];
+
+ tuple.Attributes = 0;
+ tuple.TupleData = (cisdata_t *)buf;
+ tuple.TupleDataMax = sizeof(buf);
+ tuple.TupleOffset = 0;
+ tuple.DesiredTuple = RETURN_FIRST_TUPLE;
+
+ rc = pcmcia_get_first_tuple(link->handle, &tuple);
+ while(rc == CS_SUCCESS) {
+ rc = pcmcia_get_tuple_data(link->handle, &tuple);
+ if(rc != CS_SUCCESS) {
+ cs_error(link->handle, GetTupleData, rc);
+ break;
+ }
+ rc = pcmcia_parse_tuple(link->handle, &tuple, &parse);
+ if(rc != CS_SUCCESS) {
+ cs_error(link->handle, ParseTuple, rc);
+ break;
+ }
+
+ switch(tuple.TupleCode) {
+ case CISTPL_FORMAT: {
+ cistpl_format_t *t = &parse.format;
+ (void)t; /* Shut up, gcc */
+ DEBUG(2, "Format type: %u, Error Detection: %u, offset = %u, length =%u",
+ t->type, t->edc, t->offset, t->length);
+ break;
+
+ }
+
+ case CISTPL_DEVICE: {
+ cistpl_device_t *t = &parse.device;
+ int i;
+ DEBUG(2, "Common memory:");
+ dev->pcmcia_map.size = t->dev[0].size;
+ for(i = 0; i < t->ndev; i++) {
+ DEBUG(2, "Region %d, type = %u", i, t->dev[i].type);
+ DEBUG(2, "Region %d, wp = %u", i, t->dev[i].wp);
+ DEBUG(2, "Region %d, speed = %u ns", i, t->dev[i].speed);
+ DEBUG(2, "Region %d, size = %u bytes", i, t->dev[i].size);
+ }
+ break;
+ }
+
+ case CISTPL_VERS_1: {
+ cistpl_vers_1_t *t = &parse.version_1;
+ int i;
+ if(t->ns) {
+ dev->mtd_name[0] = '\0';
+ for(i = 0; i < t->ns; i++) {
+ if(i)
+ strcat(dev->mtd_name, " ");
+ strcat(dev->mtd_name, t->str+t->ofs[i]);
+ }
+ }
+ DEBUG(2, "Found name: %s", dev->mtd_name);
+ break;
+ }
+
+ case CISTPL_JEDEC_C: {
+ cistpl_jedec_t *t = &parse.jedec;
+ int i;
+ for(i = 0; i < t->nid; i++) {
+ DEBUG(2, "JEDEC: 0x%02x 0x%02x", t->id[i].mfr, t->id[i].info);
+ }
+ break;
+ }
+
+ case CISTPL_DEVICE_GEO: {
+ cistpl_device_geo_t *t = &parse.device_geo;
+ int i;
+ dev->pcmcia_map.bankwidth = t->geo[0].buswidth;
+ for(i = 0; i < t->ngeo; i++) {
+ DEBUG(2, "region: %d bankwidth = %u", i, t->geo[i].buswidth);
+ DEBUG(2, "region: %d erase_block = %u", i, t->geo[i].erase_block);
+ DEBUG(2, "region: %d read_block = %u", i, t->geo[i].read_block);
+ DEBUG(2, "region: %d write_block = %u", i, t->geo[i].write_block);
+ DEBUG(2, "region: %d partition = %u", i, t->geo[i].partition);
+ DEBUG(2, "region: %d interleave = %u", i, t->geo[i].interleave);
+ }
+ break;
+ }
+
+ default:
+ DEBUG(2, "Unknown tuple code %d", tuple.TupleCode);
+ }
+
+ rc = pcmcia_get_next_tuple(link->handle, &tuple);
+ }
+ if(!dev->pcmcia_map.size)
+ dev->pcmcia_map.size = MAX_PCMCIA_ADDR;
+
+ if(!dev->pcmcia_map.bankwidth)
+ dev->pcmcia_map.bankwidth = 2;
+
+ if(force_size) {
+ dev->pcmcia_map.size = force_size << 20;
+ DEBUG(2, "size forced to %dM", force_size);
+ }
+
+ if(bankwidth) {
+ dev->pcmcia_map.bankwidth = bankwidth;
+ DEBUG(2, "bankwidth forced to %d", bankwidth);
+ }
+
+ dev->pcmcia_map.name = dev->mtd_name;
+ if(!dev->mtd_name[0]) {
+ strcpy(dev->mtd_name, "PCMCIA Memory card");
+ *new_name = 1;
+ }
+
+ DEBUG(1, "Device: Size: %lu Width:%d Name: %s",
+ dev->pcmcia_map.size, dev->pcmcia_map.bankwidth << 3, dev->mtd_name);
+}
+
+
+/* pcmciamtd_config() is scheduled to run after a CARD_INSERTION event
+ * is received, to configure the PCMCIA socket, and to make the
+ * MTD device available to the system.
+ */
+
+#define CS_CHECK(fn, ret) \
+do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0)
+
+static void pcmciamtd_config(dev_link_t *link)
+{
+ struct pcmciamtd_dev *dev = link->priv;
+ struct mtd_info *mtd = NULL;
+ cs_status_t status;
+ win_req_t req;
+ int last_ret = 0, last_fn = 0;
+ int ret;
+ int i;
+ config_info_t t;
+ static char *probes[] = { "jedec_probe", "cfi_probe" };
+ cisinfo_t cisinfo;
+ int new_name = 0;
+
+ DEBUG(3, "link=0x%p", link);
+
+ /* Configure card */
+ link->state |= DEV_CONFIG;
+
+ DEBUG(2, "Validating CIS");
+ ret = pcmcia_validate_cis(link->handle, &cisinfo);
+ if(ret != CS_SUCCESS) {
+ cs_error(link->handle, GetTupleData, ret);
+ } else {
+ DEBUG(2, "ValidateCIS found %d chains", cisinfo.Chains);
+ }
+
+ card_settings(dev, link, &new_name);
+
+ dev->pcmcia_map.phys = NO_XIP;
+ dev->pcmcia_map.copy_from = pcmcia_copy_from_remap;
+ dev->pcmcia_map.copy_to = pcmcia_copy_to_remap;
+ if (dev->pcmcia_map.bankwidth == 1) {
+ dev->pcmcia_map.read = pcmcia_read8_remap;
+ dev->pcmcia_map.write = pcmcia_write8_remap;
+ } else {
+ dev->pcmcia_map.read = pcmcia_read16_remap;
+ dev->pcmcia_map.write = pcmcia_write16_remap;
+ }
+ if(setvpp == 1)
+ dev->pcmcia_map.set_vpp = pcmciamtd_set_vpp;
+
+ /* Request a memory window for PCMCIA. Some architeures can map windows upto the maximum
+ that PCMCIA can support (64MiB) - this is ideal and we aim for a window the size of the
+ whole card - otherwise we try smaller windows until we succeed */
+
+ req.Attributes = WIN_MEMORY_TYPE_CM | WIN_ENABLE;
+ req.Attributes |= (dev->pcmcia_map.bankwidth == 1) ? WIN_DATA_WIDTH_8 : WIN_DATA_WIDTH_16;
+ req.Base = 0;
+ req.AccessSpeed = mem_speed;
+ link->win = (window_handle_t)link->handle;
+ req.Size = (force_size) ? force_size << 20 : MAX_PCMCIA_ADDR;
+ dev->win_size = 0;
+
+ do {
+ int ret;
+ DEBUG(2, "requesting window with size = %dKiB memspeed = %d",
+ req.Size >> 10, req.AccessSpeed);
+ ret = pcmcia_request_window(&link->handle, &req, &link->win);
+ DEBUG(2, "ret = %d dev->win_size = %d", ret, dev->win_size);
+ if(ret) {
+ req.Size >>= 1;
+ } else {
+ DEBUG(2, "Got window of size %dKiB", req.Size >> 10);
+ dev->win_size = req.Size;
+ break;
+ }
+ } while(req.Size >= 0x1000);
+
+ DEBUG(2, "dev->win_size = %d", dev->win_size);
+
+ if(!dev->win_size) {
+ err("Cant allocate memory window");
+ pcmciamtd_release(link);
+ return;
+ }
+ DEBUG(1, "Allocated a window of %dKiB", dev->win_size >> 10);
+
+ /* Get write protect status */
+ CS_CHECK(GetStatus, pcmcia_get_status(link->handle, &status));
+ DEBUG(2, "status value: 0x%x window handle = 0x%8.8lx",
+ status.CardState, (unsigned long)link->win);
+ dev->win_base = ioremap(req.Base, req.Size);
+ if(!dev->win_base) {
+ err("ioremap(%lu, %u) failed", req.Base, req.Size);
+ pcmciamtd_release(link);
+ return;
+ }
+ DEBUG(1, "mapped window dev = %p req.base = 0x%lx base = %p size = 0x%x",
+ dev, req.Base, dev->win_base, req.Size);
+
+ dev->offset = 0;
+ dev->pcmcia_map.map_priv_1 = (unsigned long)dev;
+ dev->pcmcia_map.map_priv_2 = (unsigned long)link->win;
+
+ DEBUG(2, "Getting configuration");
+ CS_CHECK(GetConfigurationInfo, pcmcia_get_configuration_info(link->handle, &t));
+ DEBUG(2, "Vcc = %d Vpp1 = %d Vpp2 = %d", t.Vcc, t.Vpp1, t.Vpp2);
+ dev->vpp = (vpp) ? vpp : t.Vpp1;
+ link->conf.Attributes = 0;
+ link->conf.Vcc = t.Vcc;
+ if(setvpp == 2) {
+ link->conf.Vpp1 = dev->vpp;
+ link->conf.Vpp2 = dev->vpp;
+ } else {
+ link->conf.Vpp1 = 0;
+ link->conf.Vpp2 = 0;
+ }
+
+ link->conf.IntType = INT_MEMORY;
+ link->conf.ConfigBase = t.ConfigBase;
+ link->conf.Status = t.Status;
+ link->conf.Pin = t.Pin;
+ link->conf.Copy = t.Copy;
+ link->conf.ExtStatus = t.ExtStatus;
+ link->conf.ConfigIndex = 0;
+ link->conf.Present = t.Present;
+ DEBUG(2, "Setting Configuration");
+ ret = pcmcia_request_configuration(link->handle, &link->conf);
+ if(ret != CS_SUCCESS) {
+ cs_error(link->handle, RequestConfiguration, ret);
+ }
+
+ if(mem_type == 1) {
+ mtd = do_map_probe("map_ram", &dev->pcmcia_map);
+ } else if(mem_type == 2) {
+ mtd = do_map_probe("map_rom", &dev->pcmcia_map);
+ } else {
+ for(i = 0; i < sizeof(probes) / sizeof(char *); i++) {
+ DEBUG(1, "Trying %s", probes[i]);
+ mtd = do_map_probe(probes[i], &dev->pcmcia_map);
+ if(mtd)
+ break;
+
+ DEBUG(1, "FAILED: %s", probes[i]);
+ }
+ }
+
+ if(!mtd) {
+ DEBUG(1, "Cant find an MTD");
+ pcmciamtd_release(link);
+ return;
+ }
+
+ dev->mtd_info = mtd;
+ mtd->owner = THIS_MODULE;
+
+ if(new_name) {
+ int size = 0;
+ char unit = ' ';
+ /* Since we are using a default name, make it better by adding in the
+ size */
+ if(mtd->size < 1048576) { /* <1MiB in size, show size in KiB */
+ size = mtd->size >> 10;
+ unit = 'K';
+ } else {
+ size = mtd->size >> 20;
+ unit = 'M';
+ }
+ snprintf(dev->mtd_name, sizeof(dev->mtd_name), "%d%ciB %s", size, unit, "PCMCIA Memory card");
+ }
+
+ /* If the memory found is fits completely into the mapped PCMCIA window,
+ use the faster non-remapping read/write functions */
+ if(mtd->size <= dev->win_size) {
+ DEBUG(1, "Using non remapping memory functions");
+ dev->pcmcia_map.map_priv_1 = (unsigned long)&(dev->link.state);
+ dev->pcmcia_map.map_priv_2 = (unsigned long)dev->win_base;
+ if (dev->pcmcia_map.bankwidth == 1) {
+ dev->pcmcia_map.read = pcmcia_read8;
+ dev->pcmcia_map.write = pcmcia_write8;
+ } else {
+ dev->pcmcia_map.read = pcmcia_read16;
+ dev->pcmcia_map.write = pcmcia_write16;
+ }
+ dev->pcmcia_map.copy_from = pcmcia_copy_from;
+ dev->pcmcia_map.copy_to = pcmcia_copy_to;
+ }
+
+ if(add_mtd_device(mtd)) {
+ map_destroy(mtd);
+ dev->mtd_info = NULL;
+ err("Couldnt register MTD device");
+ pcmciamtd_release(link);
+ return;
+ }
+ snprintf(dev->node.dev_name, sizeof(dev->node.dev_name), "mtd%d", mtd->index);
+ info("mtd%d: %s", mtd->index, mtd->name);
+ link->state &= ~DEV_CONFIG_PENDING;
+ link->dev = &dev->node;
+ return;
+
+ cs_failed:
+ cs_error(link->handle, last_fn, last_ret);
+ err("CS Error, exiting");
+ pcmciamtd_release(link);
+ return;
+}
+
+
+/* The card status event handler. Mostly, this schedules other
+ * stuff to run after an event is received. A CARD_REMOVAL event
+ * also sets some flags to discourage the driver from trying
+ * to talk to the card any more.
+ */
+
+static int pcmciamtd_event(event_t event, int priority,
+ event_callback_args_t *args)
+{
+ dev_link_t *link = args->client_data;
+
+ DEBUG(1, "event=0x%06x", event);
+ switch (event) {
+ case CS_EVENT_CARD_REMOVAL:
+ DEBUG(2, "EVENT_CARD_REMOVAL");
+ link->state &= ~DEV_PRESENT;
+ if (link->state & DEV_CONFIG) {
+ struct pcmciamtd_dev *dev = link->priv;
+ if(dev->mtd_info) {
+ del_mtd_device(dev->mtd_info);
+ info("mtd%d: Removed", dev->mtd_info->index);
+ }
+ pcmciamtd_release(link);
+ }
+ break;
+ case CS_EVENT_CARD_INSERTION:
+ DEBUG(2, "EVENT_CARD_INSERTION");
+ link->state |= DEV_PRESENT | DEV_CONFIG_PENDING;
+ pcmciamtd_config(link);
+ break;
+ case CS_EVENT_PM_SUSPEND:
+ DEBUG(2, "EVENT_PM_SUSPEND");
+ link->state |= DEV_SUSPEND;
+ /* Fall through... */
+ case CS_EVENT_RESET_PHYSICAL:
+ DEBUG(2, "EVENT_RESET_PHYSICAL");
+ /* get_lock(link); */
+ break;
+ case CS_EVENT_PM_RESUME:
+ DEBUG(2, "EVENT_PM_RESUME");
+ link->state &= ~DEV_SUSPEND;
+ /* Fall through... */
+ case CS_EVENT_CARD_RESET:
+ DEBUG(2, "EVENT_CARD_RESET");
+ /* free_lock(link); */
+ break;
+ default:
+ DEBUG(2, "Unknown event %d", event);
+ }
+ return 0;
+}
+
+
+/* This deletes a driver "instance". The device is de-registered
+ * with Card Services. If it has been released, all local data
+ * structures are freed. Otherwise, the structures will be freed
+ * when the device is released.
+ */
+
+static void pcmciamtd_detach(dev_link_t *link)
+{
+ DEBUG(3, "link=0x%p", link);
+
+ if(link->state & DEV_CONFIG) {
+ pcmciamtd_release(link);
+ }
+
+ if (link->handle) {
+ int ret;
+ DEBUG(2, "Deregistering with card services");
+ ret = pcmcia_deregister_client(link->handle);
+ if (ret != CS_SUCCESS)
+ cs_error(link->handle, DeregisterClient, ret);
+ }
+
+ link->state |= DEV_STALE_LINK;
+}
+
+
+/* pcmciamtd_attach() creates an "instance" of the driver, allocating
+ * local data structures for one device. The device is registered
+ * with Card Services.
+ */
+
+static dev_link_t *pcmciamtd_attach(void)
+{
+ struct pcmciamtd_dev *dev;
+ dev_link_t *link;
+ client_reg_t client_reg;
+ int ret;
+
+ /* Create new memory card device */
+ dev = kmalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev) return NULL;
+ DEBUG(1, "dev=0x%p", dev);
+
+ memset(dev, 0, sizeof(*dev));
+ link = &dev->link;
+ link->priv = dev;
+
+ link->conf.Attributes = 0;
+ link->conf.IntType = INT_MEMORY;
+
+ link->next = dev_list;
+ dev_list = link;
+
+ /* Register with Card Services */
+ client_reg.dev_info = &dev_info;
+ client_reg.EventMask =
+ CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET |
+ CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL |
+ CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME;
+ client_reg.event_handler = &pcmciamtd_event;
+ client_reg.Version = 0x0210;
+ client_reg.event_callback_args.client_data = link;
+ DEBUG(2, "Calling RegisterClient");
+ ret = pcmcia_register_client(&link->handle, &client_reg);
+ if (ret != 0) {
+ cs_error(link->handle, RegisterClient, ret);
+ pcmciamtd_detach(link);
+ return NULL;
+ }
+ DEBUG(2, "link = %p", link);
+ return link;
+}
+
+
+static struct pcmcia_driver pcmciamtd_driver = {
+ .drv = {
+ .name = "pcmciamtd"
+ },
+ .attach = pcmciamtd_attach,
+ .detach = pcmciamtd_detach,
+ .owner = THIS_MODULE
+};
+
+
+static int __init init_pcmciamtd(void)
+{
+ info(DRIVER_DESC " " DRIVER_VERSION);
+
+ if(bankwidth && bankwidth != 1 && bankwidth != 2) {
+ info("bad bankwidth (%d), using default", bankwidth);
+ bankwidth = 2;
+ }
+ if(force_size && (force_size < 1 || force_size > 64)) {
+ info("bad force_size (%d), using default", force_size);
+ force_size = 0;
+ }
+ if(mem_type && mem_type != 1 && mem_type != 2) {
+ info("bad mem_type (%d), using default", mem_type);
+ mem_type = 0;
+ }
+ return pcmcia_register_driver(&pcmciamtd_driver);
+}
+
+
+static void __exit exit_pcmciamtd(void)
+{
+ DEBUG(1, DRIVER_DESC " unloading");
+ pcmcia_unregister_driver(&pcmciamtd_driver);
+ BUG_ON(dev_list != NULL);
+}
+
+module_init(init_pcmciamtd);
+module_exit(exit_pcmciamtd);
diff --git a/drivers/mtd/maps/physmap.c b/drivers/mtd/maps/physmap.c
new file mode 100644
index 000000000000..b853670bfb81
--- /dev/null
+++ b/drivers/mtd/maps/physmap.c
@@ -0,0 +1,125 @@
+/*
+ * $Id: physmap.c,v 1.37 2004/11/28 09:40:40 dwmw2 Exp $
+ *
+ * Normal mappings of chips in physical memory
+ *
+ * Copyright (C) 2003 MontaVista Software Inc.
+ * Author: Jun Sun, jsun@mvista.com or jsun@junsun.net
+ *
+ * 031022 - [jsun] add run-time configure and partition setup
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/config.h>
+#include <linux/mtd/partitions.h>
+
+static struct mtd_info *mymtd;
+
+struct map_info physmap_map = {
+ .name = "phys_mapped_flash",
+ .phys = CONFIG_MTD_PHYSMAP_START,
+ .size = CONFIG_MTD_PHYSMAP_LEN,
+ .bankwidth = CONFIG_MTD_PHYSMAP_BANKWIDTH,
+};
+
+#ifdef CONFIG_MTD_PARTITIONS
+static struct mtd_partition *mtd_parts;
+static int mtd_parts_nb;
+
+static int num_physmap_partitions;
+static struct mtd_partition *physmap_partitions;
+
+static const char *part_probes[] __initdata = {"cmdlinepart", "RedBoot", NULL};
+
+void physmap_set_partitions(struct mtd_partition *parts, int num_parts)
+{
+ physmap_partitions=parts;
+ num_physmap_partitions=num_parts;
+}
+#endif /* CONFIG_MTD_PARTITIONS */
+
+static int __init init_physmap(void)
+{
+ static const char *rom_probe_types[] = { "cfi_probe", "jedec_probe", "map_rom", NULL };
+ const char **type;
+
+ printk(KERN_NOTICE "physmap flash device: %lx at %lx\n", physmap_map.size, physmap_map.phys);
+ physmap_map.virt = ioremap(physmap_map.phys, physmap_map.size);
+
+ if (!physmap_map.virt) {
+ printk("Failed to ioremap\n");
+ return -EIO;
+ }
+
+ simple_map_init(&physmap_map);
+
+ mymtd = NULL;
+ type = rom_probe_types;
+ for(; !mymtd && *type; type++) {
+ mymtd = do_map_probe(*type, &physmap_map);
+ }
+ if (mymtd) {
+ mymtd->owner = THIS_MODULE;
+
+#ifdef CONFIG_MTD_PARTITIONS
+ mtd_parts_nb = parse_mtd_partitions(mymtd, part_probes,
+ &mtd_parts, 0);
+
+ if (mtd_parts_nb > 0)
+ {
+ add_mtd_partitions (mymtd, mtd_parts, mtd_parts_nb);
+ return 0;
+ }
+
+ if (num_physmap_partitions != 0)
+ {
+ printk(KERN_NOTICE
+ "Using physmap partition definition\n");
+ add_mtd_partitions (mymtd, physmap_partitions, num_physmap_partitions);
+ return 0;
+ }
+
+#endif
+ add_mtd_device(mymtd);
+
+ return 0;
+ }
+
+ iounmap(physmap_map.virt);
+ return -ENXIO;
+}
+
+static void __exit cleanup_physmap(void)
+{
+#ifdef CONFIG_MTD_PARTITIONS
+ if (mtd_parts_nb) {
+ del_mtd_partitions(mymtd);
+ kfree(mtd_parts);
+ } else if (num_physmap_partitions) {
+ del_mtd_partitions(mymtd);
+ } else {
+ del_mtd_device(mymtd);
+ }
+#else
+ del_mtd_device(mymtd);
+#endif
+ map_destroy(mymtd);
+
+ iounmap(physmap_map.virt);
+ physmap_map.virt = NULL;
+}
+
+module_init(init_physmap);
+module_exit(cleanup_physmap);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("Generic configurable MTD map driver");
diff --git a/drivers/mtd/maps/pnc2000.c b/drivers/mtd/maps/pnc2000.c
new file mode 100644
index 000000000000..a0f43dad8985
--- /dev/null
+++ b/drivers/mtd/maps/pnc2000.c
@@ -0,0 +1,93 @@
+/*
+ * pnc2000.c - mapper for Photron PNC-2000 board.
+ *
+ * Copyright (C) 2000 Crossnet Co. <info@crossnet.co.jp>
+ *
+ * This code is GPL
+ *
+ * $Id: pnc2000.c,v 1.17 2004/11/16 18:29:02 dwmw2 Exp $
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+
+#define WINDOW_ADDR 0xbf000000
+#define WINDOW_SIZE 0x00400000
+
+/*
+ * MAP DRIVER STUFF
+ */
+
+
+static struct map_info pnc_map = {
+ .name = "PNC-2000",
+ .size = WINDOW_SIZE,
+ .bankwidth = 4,
+ .phys = 0xFFFFFFFF,
+ .virt = (void __iomem *)WINDOW_ADDR,
+};
+
+
+/*
+ * MTD 'PARTITIONING' STUFF
+ */
+static struct mtd_partition pnc_partitions[3] = {
+ {
+ .name = "PNC-2000 boot firmware",
+ .size = 0x20000,
+ .offset = 0
+ },
+ {
+ .name = "PNC-2000 kernel",
+ .size = 0x1a0000,
+ .offset = 0x20000
+ },
+ {
+ .name = "PNC-2000 filesystem",
+ .size = 0x240000,
+ .offset = 0x1c0000
+ }
+};
+
+/*
+ * This is the master MTD device for which all the others are just
+ * auto-relocating aliases.
+ */
+static struct mtd_info *mymtd;
+
+static int __init init_pnc2000(void)
+{
+ printk(KERN_NOTICE "Photron PNC-2000 flash mapping: %x at %x\n", WINDOW_SIZE, WINDOW_ADDR);
+
+ simple_map_init(&pnc_map);
+
+ mymtd = do_map_probe("cfi_probe", &pnc_map);
+ if (mymtd) {
+ mymtd->owner = THIS_MODULE;
+ return add_mtd_partitions(mymtd, pnc_partitions, 3);
+ }
+
+ return -ENXIO;
+}
+
+static void __exit cleanup_pnc2000(void)
+{
+ if (mymtd) {
+ del_mtd_partitions(mymtd);
+ map_destroy(mymtd);
+ }
+}
+
+module_init(init_pnc2000);
+module_exit(cleanup_pnc2000);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Crossnet Co. <info@crossnet.co.jp>");
+MODULE_DESCRIPTION("MTD map driver for Photron PNC-2000 board");
diff --git a/drivers/mtd/maps/redwood.c b/drivers/mtd/maps/redwood.c
new file mode 100644
index 000000000000..edd01ee4f90b
--- /dev/null
+++ b/drivers/mtd/maps/redwood.c
@@ -0,0 +1,169 @@
+/*
+ * $Id: redwood.c,v 1.10 2004/11/04 13:24:15 gleixner Exp $
+ *
+ * drivers/mtd/maps/redwood.c
+ *
+ * FLASH map for the IBM Redwood 4/5/6 boards.
+ *
+ * Author: MontaVista Software, Inc. <source@mvista.com>
+ *
+ * 2001-2003 (c) MontaVista, Software, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/io.h>
+
+#if !defined (CONFIG_REDWOOD_6)
+
+#define WINDOW_ADDR 0xffc00000
+#define WINDOW_SIZE 0x00400000
+
+#define RW_PART0_OF 0
+#define RW_PART0_SZ 0x10000
+#define RW_PART1_OF RW_PART0_SZ
+#define RW_PART1_SZ 0x200000 - 0x10000
+#define RW_PART2_OF 0x200000
+#define RW_PART2_SZ 0x10000
+#define RW_PART3_OF 0x210000
+#define RW_PART3_SZ 0x200000 - (0x10000 + 0x20000)
+#define RW_PART4_OF 0x3e0000
+#define RW_PART4_SZ 0x20000
+
+static struct mtd_partition redwood_flash_partitions[] = {
+ {
+ .name = "Redwood OpenBIOS Vital Product Data",
+ .offset = RW_PART0_OF,
+ .size = RW_PART0_SZ,
+ .mask_flags = MTD_WRITEABLE /* force read-only */
+ },
+ {
+ .name = "Redwood kernel",
+ .offset = RW_PART1_OF,
+ .size = RW_PART1_SZ
+ },
+ {
+ .name = "Redwood OpenBIOS non-volatile storage",
+ .offset = RW_PART2_OF,
+ .size = RW_PART2_SZ,
+ .mask_flags = MTD_WRITEABLE /* force read-only */
+ },
+ {
+ .name = "Redwood filesystem",
+ .offset = RW_PART3_OF,
+ .size = RW_PART3_SZ
+ },
+ {
+ .name = "Redwood OpenBIOS",
+ .offset = RW_PART4_OF,
+ .size = RW_PART4_SZ,
+ .mask_flags = MTD_WRITEABLE /* force read-only */
+ }
+};
+
+#else /* CONFIG_REDWOOD_6 */
+/* FIXME: the window is bigger - armin */
+#define WINDOW_ADDR 0xff800000
+#define WINDOW_SIZE 0x00800000
+
+#define RW_PART0_OF 0
+#define RW_PART0_SZ 0x400000 /* 4 MiB data */
+#define RW_PART1_OF RW_PART0_OF + RW_PART0_SZ
+#define RW_PART1_SZ 0x10000 /* 64K VPD */
+#define RW_PART2_OF RW_PART1_OF + RW_PART1_SZ
+#define RW_PART2_SZ 0x400000 - (0x10000 + 0x20000)
+#define RW_PART3_OF RW_PART2_OF + RW_PART2_SZ
+#define RW_PART3_SZ 0x20000
+
+static struct mtd_partition redwood_flash_partitions[] = {
+ {
+ .name = "Redwood filesystem",
+ .offset = RW_PART0_OF,
+ .size = RW_PART0_SZ
+ },
+ {
+ .name = "Redwood OpenBIOS Vital Product Data",
+ .offset = RW_PART1_OF,
+ .size = RW_PART1_SZ,
+ .mask_flags = MTD_WRITEABLE /* force read-only */
+ },
+ {
+ .name = "Redwood kernel",
+ .offset = RW_PART2_OF,
+ .size = RW_PART2_SZ
+ },
+ {
+ .name = "Redwood OpenBIOS",
+ .offset = RW_PART3_OF,
+ .size = RW_PART3_SZ,
+ .mask_flags = MTD_WRITEABLE /* force read-only */
+ }
+};
+
+#endif /* CONFIG_REDWOOD_6 */
+
+struct map_info redwood_flash_map = {
+ .name = "IBM Redwood",
+ .size = WINDOW_SIZE,
+ .bankwidth = 2,
+ .phys = WINDOW_ADDR,
+};
+
+
+#define NUM_REDWOOD_FLASH_PARTITIONS \
+ (sizeof(redwood_flash_partitions)/sizeof(redwood_flash_partitions[0]))
+
+static struct mtd_info *redwood_mtd;
+
+int __init init_redwood_flash(void)
+{
+ printk(KERN_NOTICE "redwood: flash mapping: %x at %x\n",
+ WINDOW_SIZE, WINDOW_ADDR);
+
+ redwood_flash_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE);
+
+ if (!redwood_flash_map.virt) {
+ printk("init_redwood_flash: failed to ioremap\n");
+ return -EIO;
+ }
+ simple_map_init(&redwood_flash_map);
+
+ redwood_mtd = do_map_probe("cfi_probe",&redwood_flash_map);
+
+ if (redwood_mtd) {
+ redwood_mtd->owner = THIS_MODULE;
+ return add_mtd_partitions(redwood_mtd,
+ redwood_flash_partitions,
+ NUM_REDWOOD_FLASH_PARTITIONS);
+ }
+
+ return -ENXIO;
+}
+
+static void __exit cleanup_redwood_flash(void)
+{
+ if (redwood_mtd) {
+ del_mtd_partitions(redwood_mtd);
+ /* moved iounmap after map_destroy - armin */
+ map_destroy(redwood_mtd);
+ iounmap((void *)redwood_flash_map.virt);
+ }
+}
+
+module_init(init_redwood_flash);
+module_exit(cleanup_redwood_flash);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("MontaVista Software <source@mvista.com>");
+MODULE_DESCRIPTION("MTD map driver for the IBM Redwood reference boards");
diff --git a/drivers/mtd/maps/rpxlite.c b/drivers/mtd/maps/rpxlite.c
new file mode 100644
index 000000000000..809a0c8e7aaf
--- /dev/null
+++ b/drivers/mtd/maps/rpxlite.c
@@ -0,0 +1,66 @@
+/*
+ * $Id: rpxlite.c,v 1.22 2004/11/04 13:24:15 gleixner Exp $
+ *
+ * Handle mapping of the flash on the RPX Lite and CLLF boards
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+
+
+#define WINDOW_ADDR 0xfe000000
+#define WINDOW_SIZE 0x800000
+
+static struct mtd_info *mymtd;
+
+static struct map_info rpxlite_map = {
+ .name = "RPX",
+ .size = WINDOW_SIZE,
+ .bankwidth = 4,
+ .phys = WINDOW_ADDR,
+};
+
+int __init init_rpxlite(void)
+{
+ printk(KERN_NOTICE "RPX Lite or CLLF flash device: %x at %x\n", WINDOW_SIZE*4, WINDOW_ADDR);
+ rpxlite_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE * 4);
+
+ if (!rpxlite_map.virt) {
+ printk("Failed to ioremap\n");
+ return -EIO;
+ }
+ simple_map_init(&rpxlite_map);
+ mymtd = do_map_probe("cfi_probe", &rpxlite_map);
+ if (mymtd) {
+ mymtd->owner = THIS_MODULE;
+ add_mtd_device(mymtd);
+ return 0;
+ }
+
+ iounmap((void *)rpxlite_map.virt);
+ return -ENXIO;
+}
+
+static void __exit cleanup_rpxlite(void)
+{
+ if (mymtd) {
+ del_mtd_device(mymtd);
+ map_destroy(mymtd);
+ }
+ if (rpxlite_map.virt) {
+ iounmap((void *)rpxlite_map.virt);
+ rpxlite_map.virt = 0;
+ }
+}
+
+module_init(init_rpxlite);
+module_exit(cleanup_rpxlite);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Arnold Christensen <AKC@pel.dk>");
+MODULE_DESCRIPTION("MTD map driver for RPX Lite and CLLF boards");
diff --git a/drivers/mtd/maps/sa1100-flash.c b/drivers/mtd/maps/sa1100-flash.c
new file mode 100644
index 000000000000..0a6f861c4cd6
--- /dev/null
+++ b/drivers/mtd/maps/sa1100-flash.c
@@ -0,0 +1,453 @@
+/*
+ * Flash memory access on SA11x0 based devices
+ *
+ * (C) 2000 Nicolas Pitre <nico@cam.org>
+ *
+ * $Id: sa1100-flash.c,v 1.47 2004/11/01 13:44:36 rmk Exp $
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/err.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/concat.h>
+
+#include <asm/mach-types.h>
+#include <asm/io.h>
+#include <asm/sizes.h>
+#include <asm/mach/flash.h>
+
+#if 0
+/*
+ * This is here for documentation purposes only - until these people
+ * submit their machine types. It will be gone January 2005.
+ */
+static struct mtd_partition consus_partitions[] = {
+ {
+ .name = "Consus boot firmware",
+ .offset = 0,
+ .size = 0x00040000,
+ .mask_flags = MTD_WRITABLE, /* force read-only */
+ }, {
+ .name = "Consus kernel",
+ .offset = 0x00040000,
+ .size = 0x00100000,
+ .mask_flags = 0,
+ }, {
+ .name = "Consus disk",
+ .offset = 0x00140000,
+ /* The rest (up to 16M) for jffs. We could put 0 and
+ make it find the size automatically, but right now
+ i have 32 megs. jffs will use all 32 megs if given
+ the chance, and this leads to horrible problems
+ when you try to re-flash the image because blob
+ won't erase the whole partition. */
+ .size = 0x01000000 - 0x00140000,
+ .mask_flags = 0,
+ }, {
+ /* this disk is a secondary disk, which can be used as
+ needed, for simplicity, make it the size of the other
+ consus partition, although realistically it could be
+ the remainder of the disk (depending on the file
+ system used) */
+ .name = "Consus disk2",
+ .offset = 0x01000000,
+ .size = 0x01000000 - 0x00140000,
+ .mask_flags = 0,
+ }
+};
+
+/* Frodo has 2 x 16M 28F128J3A flash chips in bank 0: */
+static struct mtd_partition frodo_partitions[] =
+{
+ {
+ .name = "bootloader",
+ .size = 0x00040000,
+ .offset = 0x00000000,
+ .mask_flags = MTD_WRITEABLE
+ }, {
+ .name = "bootloader params",
+ .size = 0x00040000,
+ .offset = MTDPART_OFS_APPEND,
+ .mask_flags = MTD_WRITEABLE
+ }, {
+ .name = "kernel",
+ .size = 0x00100000,
+ .offset = MTDPART_OFS_APPEND,
+ .mask_flags = MTD_WRITEABLE
+ }, {
+ .name = "ramdisk",
+ .size = 0x00400000,
+ .offset = MTDPART_OFS_APPEND,
+ .mask_flags = MTD_WRITEABLE
+ }, {
+ .name = "file system",
+ .size = MTDPART_SIZ_FULL,
+ .offset = MTDPART_OFS_APPEND
+ }
+};
+
+static struct mtd_partition jornada56x_partitions[] = {
+ {
+ .name = "bootldr",
+ .size = 0x00040000,
+ .offset = 0,
+ .mask_flags = MTD_WRITEABLE,
+ }, {
+ .name = "rootfs",
+ .size = MTDPART_SIZ_FULL,
+ .offset = MTDPART_OFS_APPEND,
+ }
+};
+
+static void jornada56x_set_vpp(int vpp)
+{
+ if (vpp)
+ GPSR = GPIO_GPIO26;
+ else
+ GPCR = GPIO_GPIO26;
+ GPDR |= GPIO_GPIO26;
+}
+
+/*
+ * Machine Phys Size set_vpp
+ * Consus : SA1100_CS0_PHYS SZ_32M
+ * Frodo : SA1100_CS0_PHYS SZ_32M
+ * Jornada56x: SA1100_CS0_PHYS SZ_32M jornada56x_set_vpp
+ */
+#endif
+
+struct sa_subdev_info {
+ char name[16];
+ struct map_info map;
+ struct mtd_info *mtd;
+ struct flash_platform_data *data;
+};
+
+struct sa_info {
+ struct mtd_partition *parts;
+ struct mtd_info *mtd;
+ int num_subdev;
+ struct sa_subdev_info subdev[0];
+};
+
+static void sa1100_set_vpp(struct map_info *map, int on)
+{
+ struct sa_subdev_info *subdev = container_of(map, struct sa_subdev_info, map);
+ subdev->data->set_vpp(on);
+}
+
+static void sa1100_destroy_subdev(struct sa_subdev_info *subdev)
+{
+ if (subdev->mtd)
+ map_destroy(subdev->mtd);
+ if (subdev->map.virt)
+ iounmap(subdev->map.virt);
+ release_mem_region(subdev->map.phys, subdev->map.size);
+}
+
+static int sa1100_probe_subdev(struct sa_subdev_info *subdev, struct resource *res)
+{
+ unsigned long phys;
+ unsigned int size;
+ int ret;
+
+ phys = res->start;
+ size = res->end - phys + 1;
+
+ /*
+ * Retrieve the bankwidth from the MSC registers.
+ * We currently only implement CS0 and CS1 here.
+ */
+ switch (phys) {
+ default:
+ printk(KERN_WARNING "SA1100 flash: unknown base address "
+ "0x%08lx, assuming CS0\n", phys);
+
+ case SA1100_CS0_PHYS:
+ subdev->map.bankwidth = (MSC0 & MSC_RBW) ? 2 : 4;
+ break;
+
+ case SA1100_CS1_PHYS:
+ subdev->map.bankwidth = ((MSC0 >> 16) & MSC_RBW) ? 2 : 4;
+ break;
+ }
+
+ if (!request_mem_region(phys, size, subdev->name)) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ if (subdev->data->set_vpp)
+ subdev->map.set_vpp = sa1100_set_vpp;
+
+ subdev->map.phys = phys;
+ subdev->map.size = size;
+ subdev->map.virt = ioremap(phys, size);
+ if (!subdev->map.virt) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ simple_map_init(&subdev->map);
+
+ /*
+ * Now let's probe for the actual flash. Do it here since
+ * specific machine settings might have been set above.
+ */
+ subdev->mtd = do_map_probe(subdev->data->map_name, &subdev->map);
+ if (subdev->mtd == NULL) {
+ ret = -ENXIO;
+ goto err;
+ }
+ subdev->mtd->owner = THIS_MODULE;
+
+ printk(KERN_INFO "SA1100 flash: CFI device at 0x%08lx, %dMiB, "
+ "%d-bit\n", phys, subdev->mtd->size >> 20,
+ subdev->map.bankwidth * 8);
+
+ return 0;
+
+ err:
+ sa1100_destroy_subdev(subdev);
+ out:
+ return ret;
+}
+
+static void sa1100_destroy(struct sa_info *info)
+{
+ int i;
+
+ if (info->mtd) {
+ del_mtd_partitions(info->mtd);
+
+#ifdef CONFIG_MTD_CONCAT
+ if (info->mtd != info->subdev[0].mtd)
+ mtd_concat_destroy(info->mtd);
+#endif
+ }
+
+ if (info->parts)
+ kfree(info->parts);
+
+ for (i = info->num_subdev - 1; i >= 0; i--)
+ sa1100_destroy_subdev(&info->subdev[i]);
+ kfree(info);
+}
+
+static struct sa_info *__init
+sa1100_setup_mtd(struct platform_device *pdev, struct flash_platform_data *flash)
+{
+ struct sa_info *info;
+ int nr, size, i, ret = 0;
+
+ /*
+ * Count number of devices.
+ */
+ for (nr = 0; ; nr++)
+ if (!platform_get_resource(pdev, IORESOURCE_MEM, nr))
+ break;
+
+ if (nr == 0) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ size = sizeof(struct sa_info) + sizeof(struct sa_subdev_info) * nr;
+
+ /*
+ * Allocate the map_info structs in one go.
+ */
+ info = kmalloc(size, GFP_KERNEL);
+ if (!info) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ memset(info, 0, size);
+
+ /*
+ * Claim and then map the memory regions.
+ */
+ for (i = 0; i < nr; i++) {
+ struct sa_subdev_info *subdev = &info->subdev[i];
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, i);
+ if (!res)
+ break;
+
+ subdev->map.name = subdev->name;
+ sprintf(subdev->name, "sa1100-%d", i);
+ subdev->data = flash;
+
+ ret = sa1100_probe_subdev(subdev, res);
+ if (ret)
+ break;
+ }
+
+ info->num_subdev = i;
+
+ /*
+ * ENXIO is special. It means we didn't find a chip when we probed.
+ */
+ if (ret != 0 && !(ret == -ENXIO && info->num_subdev > 0))
+ goto err;
+
+ /*
+ * If we found one device, don't bother with concat support. If
+ * we found multiple devices, use concat if we have it available,
+ * otherwise fail. Either way, it'll be called "sa1100".
+ */
+ if (info->num_subdev == 1) {
+ strcpy(info->subdev[0].name, "sa1100");
+ info->mtd = info->subdev[0].mtd;
+ ret = 0;
+ } else if (info->num_subdev > 1) {
+#ifdef CONFIG_MTD_CONCAT
+ struct mtd_info *cdev[nr];
+ /*
+ * We detected multiple devices. Concatenate them together.
+ */
+ for (i = 0; i < info->num_subdev; i++)
+ cdev[i] = info->subdev[i].mtd;
+
+ info->mtd = mtd_concat_create(cdev, info->num_subdev,
+ "sa1100");
+ if (info->mtd == NULL)
+ ret = -ENXIO;
+#else
+ printk(KERN_ERR "SA1100 flash: multiple devices "
+ "found but MTD concat support disabled.\n");
+ ret = -ENXIO;
+#endif
+ }
+
+ if (ret == 0)
+ return info;
+
+ err:
+ sa1100_destroy(info);
+ out:
+ return ERR_PTR(ret);
+}
+
+static const char *part_probes[] = { "cmdlinepart", "RedBoot", NULL };
+
+static int __init sa1100_mtd_probe(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct flash_platform_data *flash = pdev->dev.platform_data;
+ struct mtd_partition *parts;
+ const char *part_type = NULL;
+ struct sa_info *info;
+ int err, nr_parts = 0;
+
+ if (!flash)
+ return -ENODEV;
+
+ info = sa1100_setup_mtd(pdev, flash);
+ if (IS_ERR(info)) {
+ err = PTR_ERR(info);
+ goto out;
+ }
+
+ /*
+ * Partition selection stuff.
+ */
+#ifdef CONFIG_MTD_PARTITIONS
+ nr_parts = parse_mtd_partitions(info->mtd, part_probes, &parts, 0);
+ if (nr_parts > 0) {
+ info->parts = parts;
+ part_type = "dynamic";
+ } else
+#endif
+ {
+ parts = flash->parts;
+ nr_parts = flash->nr_parts;
+ part_type = "static";
+ }
+
+ if (nr_parts == 0) {
+ printk(KERN_NOTICE "SA1100 flash: no partition info "
+ "available, registering whole flash\n");
+ add_mtd_device(info->mtd);
+ } else {
+ printk(KERN_NOTICE "SA1100 flash: using %s partition "
+ "definition\n", part_type);
+ add_mtd_partitions(info->mtd, parts, nr_parts);
+ }
+
+ dev_set_drvdata(dev, info);
+ err = 0;
+
+ out:
+ return err;
+}
+
+static int __exit sa1100_mtd_remove(struct device *dev)
+{
+ struct sa_info *info = dev_get_drvdata(dev);
+ dev_set_drvdata(dev, NULL);
+ sa1100_destroy(info);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int sa1100_mtd_suspend(struct device *dev, u32 state, u32 level)
+{
+ struct sa_info *info = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (info && level == SUSPEND_SAVE_STATE)
+ ret = info->mtd->suspend(info->mtd);
+
+ return ret;
+}
+
+static int sa1100_mtd_resume(struct device *dev, u32 level)
+{
+ struct sa_info *info = dev_get_drvdata(dev);
+ if (info && level == RESUME_RESTORE_STATE)
+ info->mtd->resume(info->mtd);
+ return 0;
+}
+#else
+#define sa1100_mtd_suspend NULL
+#define sa1100_mtd_resume NULL
+#endif
+
+static struct device_driver sa1100_mtd_driver = {
+ .name = "flash",
+ .bus = &platform_bus_type,
+ .probe = sa1100_mtd_probe,
+ .remove = __exit_p(sa1100_mtd_remove),
+ .suspend = sa1100_mtd_suspend,
+ .resume = sa1100_mtd_resume,
+};
+
+static int __init sa1100_mtd_init(void)
+{
+ return driver_register(&sa1100_mtd_driver);
+}
+
+static void __exit sa1100_mtd_exit(void)
+{
+ driver_unregister(&sa1100_mtd_driver);
+}
+
+module_init(sa1100_mtd_init);
+module_exit(sa1100_mtd_exit);
+
+MODULE_AUTHOR("Nicolas Pitre");
+MODULE_DESCRIPTION("SA1100 CFI map driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/maps/sbc8240.c b/drivers/mtd/maps/sbc8240.c
new file mode 100644
index 000000000000..da684d3384e9
--- /dev/null
+++ b/drivers/mtd/maps/sbc8240.c
@@ -0,0 +1,247 @@
+/*
+ * Handle mapping of the flash memory access routines on the SBC8240 board.
+ *
+ * Carolyn Smith, Tektronix, Inc.
+ *
+ * This code is GPLed
+ *
+ * $Id: sbc8240.c,v 1.4 2004/07/12 22:38:29 dwmw2 Exp $
+ *
+ */
+
+/*
+ * The SBC8240 has 2 flash banks.
+ * Bank 0 is a 512 KiB AMD AM29F040B; 8 x 64 KiB sectors.
+ * It contains the U-Boot code (7 sectors) and the environment (1 sector).
+ * Bank 1 is 4 x 1 MiB AMD AM29LV800BT; 15 x 64 KiB sectors, 1 x 32 KiB sector,
+ * 2 x 8 KiB sectors, 1 x 16 KiB sectors.
+ * Both parts are JEDEC compatible.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <asm/io.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/cfi.h>
+
+#ifdef CONFIG_MTD_PARTITIONS
+#include <linux/mtd/partitions.h>
+#endif
+
+#define DEBUG
+
+#ifdef DEBUG
+# define debugk(fmt,args...) printk(fmt ,##args)
+#else
+# define debugk(fmt,args...)
+#endif
+
+
+#define WINDOW_ADDR0 0xFFF00000 /* 512 KiB */
+#define WINDOW_SIZE0 0x00080000
+#define BUSWIDTH0 1
+
+#define WINDOW_ADDR1 0xFF000000 /* 4 MiB */
+#define WINDOW_SIZE1 0x00400000
+#define BUSWIDTH1 8
+
+#define MSG_PREFIX "sbc8240:" /* prefix for our printk()'s */
+#define MTDID "sbc8240-%d" /* for mtdparts= partitioning */
+
+
+static struct map_info sbc8240_map[2] = {
+ {
+ .name = "sbc8240 Flash Bank #0",
+ .size = WINDOW_SIZE0,
+ .bankwidth = BUSWIDTH0,
+ },
+ {
+ .name = "sbc8240 Flash Bank #1",
+ .size = WINDOW_SIZE1,
+ .bankwidth = BUSWIDTH1,
+ }
+};
+
+#define NUM_FLASH_BANKS (sizeof(sbc8240_map) / sizeof(struct map_info))
+
+/*
+ * The following defines the partition layout of SBC8240 boards.
+ *
+ * See include/linux/mtd/partitions.h for definition of the
+ * mtd_partition structure.
+ *
+ * The *_max_flash_size is the maximum possible mapped flash size
+ * which is not necessarily the actual flash size. It must correspond
+ * to the value specified in the mapping definition defined by the
+ * "struct map_desc *_io_desc" for the corresponding machine.
+ */
+
+#ifdef CONFIG_MTD_PARTITIONS
+
+static struct mtd_partition sbc8240_uboot_partitions [] = {
+ /* Bank 0 */
+ {
+ .name = "U-boot", /* U-Boot Firmware */
+ .offset = 0,
+ .size = 0x00070000, /* 7 x 64 KiB sectors */
+ .mask_flags = MTD_WRITEABLE, /* force read-only */
+ },
+ {
+ .name = "environment", /* U-Boot environment */
+ .offset = 0x00070000,
+ .size = 0x00010000, /* 1 x 64 KiB sector */
+ },
+};
+
+static struct mtd_partition sbc8240_fs_partitions [] = {
+ {
+ .name = "jffs", /* JFFS filesystem */
+ .offset = 0,
+ .size = 0x003C0000, /* 4 * 15 * 64KiB */
+ },
+ {
+ .name = "tmp32",
+ .offset = 0x003C0000,
+ .size = 0x00020000, /* 4 * 32KiB */
+ },
+ {
+ .name = "tmp8a",
+ .offset = 0x003E0000,
+ .size = 0x00008000, /* 4 * 8KiB */
+ },
+ {
+ .name = "tmp8b",
+ .offset = 0x003E8000,
+ .size = 0x00008000, /* 4 * 8KiB */
+ },
+ {
+ .name = "tmp16",
+ .offset = 0x003F0000,
+ .size = 0x00010000, /* 4 * 16KiB */
+ }
+};
+
+#define NB_OF(x) (sizeof (x) / sizeof (x[0]))
+
+/* trivial struct to describe partition information */
+struct mtd_part_def
+{
+ int nums;
+ unsigned char *type;
+ struct mtd_partition* mtd_part;
+};
+
+static struct mtd_info *sbc8240_mtd[NUM_FLASH_BANKS];
+static struct mtd_part_def sbc8240_part_banks[NUM_FLASH_BANKS];
+
+
+#endif /* CONFIG_MTD_PARTITIONS */
+
+
+int __init init_sbc8240_mtd (void)
+{
+ static struct _cjs {
+ u_long addr;
+ u_long size;
+ } pt[NUM_FLASH_BANKS] = {
+ {
+ .addr = WINDOW_ADDR0,
+ .size = WINDOW_SIZE0
+ },
+ {
+ .addr = WINDOW_ADDR1,
+ .size = WINDOW_SIZE1
+ },
+ };
+
+ int devicesfound = 0;
+ int i;
+
+ for (i = 0; i < NUM_FLASH_BANKS; i++) {
+ printk (KERN_NOTICE MSG_PREFIX
+ "Probing 0x%08lx at 0x%08lx\n", pt[i].size, pt[i].addr);
+
+ sbc8240_map[i].map_priv_1 =
+ (unsigned long) ioremap (pt[i].addr, pt[i].size);
+ if (!sbc8240_map[i].map_priv_1) {
+ printk (MSG_PREFIX "failed to ioremap\n");
+ return -EIO;
+ }
+ simple_map_init(&sbc8240_mtd[i]);
+
+ sbc8240_mtd[i] = do_map_probe("jedec_probe", &sbc8240_map[i]);
+
+ if (sbc8240_mtd[i]) {
+ sbc8240_mtd[i]->module = THIS_MODULE;
+ devicesfound++;
+ }
+ }
+
+ if (!devicesfound) {
+ printk(KERN_NOTICE MSG_PREFIX
+ "No suppported flash chips found!\n");
+ return -ENXIO;
+ }
+
+#ifdef CONFIG_MTD_PARTITIONS
+ sbc8240_part_banks[0].mtd_part = sbc8240_uboot_partitions;
+ sbc8240_part_banks[0].type = "static image";
+ sbc8240_part_banks[0].nums = NB_OF(sbc8240_uboot_partitions);
+ sbc8240_part_banks[1].mtd_part = sbc8240_fs_partitions;
+ sbc8240_part_banks[1].type = "static file system";
+ sbc8240_part_banks[1].nums = NB_OF(sbc8240_fs_partitions);
+
+ for (i = 0; i < NUM_FLASH_BANKS; i++) {
+
+ if (!sbc8240_mtd[i]) continue;
+ if (sbc8240_part_banks[i].nums == 0) {
+ printk (KERN_NOTICE MSG_PREFIX
+ "No partition info available, registering whole device\n");
+ add_mtd_device(sbc8240_mtd[i]);
+ } else {
+ printk (KERN_NOTICE MSG_PREFIX
+ "Using %s partition definition\n", sbc8240_part_banks[i].mtd_part->name);
+ add_mtd_partitions (sbc8240_mtd[i],
+ sbc8240_part_banks[i].mtd_part,
+ sbc8240_part_banks[i].nums);
+ }
+ }
+#else
+ printk(KERN_NOTICE MSG_PREFIX
+ "Registering %d flash banks at once\n", devicesfound);
+
+ for (i = 0; i < devicesfound; i++) {
+ add_mtd_device(sbc8240_mtd[i]);
+ }
+#endif /* CONFIG_MTD_PARTITIONS */
+
+ return devicesfound == 0 ? -ENXIO : 0;
+}
+
+static void __exit cleanup_sbc8240_mtd (void)
+{
+ int i;
+
+ for (i = 0; i < NUM_FLASH_BANKS; i++) {
+ if (sbc8240_mtd[i]) {
+ del_mtd_device (sbc8240_mtd[i]);
+ map_destroy (sbc8240_mtd[i]);
+ }
+ if (sbc8240_map[i].map_priv_1) {
+ iounmap ((void *) sbc8240_map[i].map_priv_1);
+ sbc8240_map[i].map_priv_1 = 0;
+ }
+ }
+}
+
+module_init (init_sbc8240_mtd);
+module_exit (cleanup_sbc8240_mtd);
+
+MODULE_LICENSE ("GPL");
+MODULE_AUTHOR ("Carolyn Smith <carolyn.smith@tektronix.com>");
+MODULE_DESCRIPTION ("MTD map driver for SBC8240 boards");
+
diff --git a/drivers/mtd/maps/sbc_gxx.c b/drivers/mtd/maps/sbc_gxx.c
new file mode 100644
index 000000000000..65add28bde14
--- /dev/null
+++ b/drivers/mtd/maps/sbc_gxx.c
@@ -0,0 +1,239 @@
+/* sbc_gxx.c -- MTD map driver for Arcom Control Systems SBC-MediaGX,
+ SBC-GXm and SBC-GX1 series boards.
+
+ Copyright (C) 2001 Arcom Control System Ltd
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+
+ $Id: sbc_gxx.c,v 1.33 2004/11/28 09:40:40 dwmw2 Exp $
+
+The SBC-MediaGX / SBC-GXx has up to 16 MiB of
+Intel StrataFlash (28F320/28F640) in x8 mode.
+
+This driver uses the CFI probe and Intel Extended Command Set drivers.
+
+The flash is accessed as follows:
+
+ 16 KiB memory window at 0xdc000-0xdffff
+
+ Two IO address locations for paging
+
+ 0x258
+ bit 0-7: address bit 14-21
+ 0x259
+ bit 0-1: address bit 22-23
+ bit 7: 0 - reset/powered down
+ 1 - device enabled
+
+The single flash device is divided into 3 partition which appear as
+separate MTD devices.
+
+25/04/2001 AJL (Arcom) Modified signon strings and partition sizes
+ (to support bzImages up to 638KiB-ish)
+*/
+
+// Includes
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <asm/io.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+// Defines
+
+// - Hardware specific
+
+#define WINDOW_START 0xdc000
+
+/* Number of bits in offset. */
+#define WINDOW_SHIFT 14
+#define WINDOW_LENGTH (1 << WINDOW_SHIFT)
+
+/* The bits for the offset into the window. */
+#define WINDOW_MASK (WINDOW_LENGTH-1)
+#define PAGE_IO 0x258
+#define PAGE_IO_SIZE 2
+
+/* bit 7 of 0x259 must be 1 to enable device. */
+#define DEVICE_ENABLE 0x8000
+
+// - Flash / Partition sizing
+
+#define MAX_SIZE_KiB 16384
+#define BOOT_PARTITION_SIZE_KiB 768
+#define DATA_PARTITION_SIZE_KiB 1280
+#define APP_PARTITION_SIZE_KiB 6144
+
+// Globals
+
+static volatile int page_in_window = -1; // Current page in window.
+static void __iomem *iomapadr;
+static DEFINE_SPINLOCK(sbc_gxx_spin);
+
+/* partition_info gives details on the logical partitions that the split the
+ * single flash device into. If the size if zero we use up to the end of the
+ * device. */
+static struct mtd_partition partition_info[]={
+ { .name = "SBC-GXx flash boot partition",
+ .offset = 0,
+ .size = BOOT_PARTITION_SIZE_KiB*1024 },
+ { .name = "SBC-GXx flash data partition",
+ .offset = BOOT_PARTITION_SIZE_KiB*1024,
+ .size = (DATA_PARTITION_SIZE_KiB)*1024 },
+ { .name = "SBC-GXx flash application partition",
+ .offset = (BOOT_PARTITION_SIZE_KiB+DATA_PARTITION_SIZE_KiB)*1024 }
+};
+
+#define NUM_PARTITIONS 3
+
+static inline void sbc_gxx_page(struct map_info *map, unsigned long ofs)
+{
+ unsigned long page = ofs >> WINDOW_SHIFT;
+
+ if( page!=page_in_window ) {
+ outw( page | DEVICE_ENABLE, PAGE_IO );
+ page_in_window = page;
+ }
+}
+
+
+static map_word sbc_gxx_read8(struct map_info *map, unsigned long ofs)
+{
+ map_word ret;
+ spin_lock(&sbc_gxx_spin);
+ sbc_gxx_page(map, ofs);
+ ret.x[0] = readb(iomapadr + (ofs & WINDOW_MASK));
+ spin_unlock(&sbc_gxx_spin);
+ return ret;
+}
+
+static void sbc_gxx_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len)
+{
+ while(len) {
+ unsigned long thislen = len;
+ if (len > (WINDOW_LENGTH - (from & WINDOW_MASK)))
+ thislen = WINDOW_LENGTH-(from & WINDOW_MASK);
+
+ spin_lock(&sbc_gxx_spin);
+ sbc_gxx_page(map, from);
+ memcpy_fromio(to, iomapadr + (from & WINDOW_MASK), thislen);
+ spin_unlock(&sbc_gxx_spin);
+ to += thislen;
+ from += thislen;
+ len -= thislen;
+ }
+}
+
+static void sbc_gxx_write8(struct map_info *map, map_word d, unsigned long adr)
+{
+ spin_lock(&sbc_gxx_spin);
+ sbc_gxx_page(map, adr);
+ writeb(d.x[0], iomapadr + (adr & WINDOW_MASK));
+ spin_unlock(&sbc_gxx_spin);
+}
+
+static void sbc_gxx_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len)
+{
+ while(len) {
+ unsigned long thislen = len;
+ if (len > (WINDOW_LENGTH - (to & WINDOW_MASK)))
+ thislen = WINDOW_LENGTH-(to & WINDOW_MASK);
+
+ spin_lock(&sbc_gxx_spin);
+ sbc_gxx_page(map, to);
+ memcpy_toio(iomapadr + (to & WINDOW_MASK), from, thislen);
+ spin_unlock(&sbc_gxx_spin);
+ to += thislen;
+ from += thislen;
+ len -= thislen;
+ }
+}
+
+static struct map_info sbc_gxx_map = {
+ .name = "SBC-GXx flash",
+ .phys = NO_XIP,
+ .size = MAX_SIZE_KiB*1024, /* this must be set to a maximum possible amount
+ of flash so the cfi probe routines find all
+ the chips */
+ .bankwidth = 1,
+ .read = sbc_gxx_read8,
+ .copy_from = sbc_gxx_copy_from,
+ .write = sbc_gxx_write8,
+ .copy_to = sbc_gxx_copy_to
+};
+
+/* MTD device for all of the flash. */
+static struct mtd_info *all_mtd;
+
+static void cleanup_sbc_gxx(void)
+{
+ if( all_mtd ) {
+ del_mtd_partitions( all_mtd );
+ map_destroy( all_mtd );
+ }
+
+ iounmap(iomapadr);
+ release_region(PAGE_IO,PAGE_IO_SIZE);
+}
+
+static int __init init_sbc_gxx(void)
+{
+ iomapadr = ioremap(WINDOW_START, WINDOW_LENGTH);
+ if (!iomapadr) {
+ printk( KERN_ERR"%s: failed to ioremap memory region\n",
+ sbc_gxx_map.name );
+ return -EIO;
+ }
+
+ if (!request_region( PAGE_IO, PAGE_IO_SIZE, "SBC-GXx flash")) {
+ printk( KERN_ERR"%s: IO ports 0x%x-0x%x in use\n",
+ sbc_gxx_map.name,
+ PAGE_IO, PAGE_IO+PAGE_IO_SIZE-1 );
+ iounmap(iomapadr);
+ return -EAGAIN;
+ }
+
+
+ printk( KERN_INFO"%s: IO:0x%x-0x%x MEM:0x%x-0x%x\n",
+ sbc_gxx_map.name,
+ PAGE_IO, PAGE_IO+PAGE_IO_SIZE-1,
+ WINDOW_START, WINDOW_START+WINDOW_LENGTH-1 );
+
+ /* Probe for chip. */
+ all_mtd = do_map_probe( "cfi_probe", &sbc_gxx_map );
+ if( !all_mtd ) {
+ cleanup_sbc_gxx();
+ return -ENXIO;
+ }
+
+ all_mtd->owner = THIS_MODULE;
+
+ /* Create MTD devices for each partition. */
+ add_mtd_partitions(all_mtd, partition_info, NUM_PARTITIONS );
+
+ return 0;
+}
+
+module_init(init_sbc_gxx);
+module_exit(cleanup_sbc_gxx);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Arcom Control Systems Ltd.");
+MODULE_DESCRIPTION("MTD map driver for SBC-GXm and SBC-GX1 series boards");
diff --git a/drivers/mtd/maps/sc520cdp.c b/drivers/mtd/maps/sc520cdp.c
new file mode 100644
index 000000000000..a06ed21e7ed1
--- /dev/null
+++ b/drivers/mtd/maps/sc520cdp.c
@@ -0,0 +1,304 @@
+/* sc520cdp.c -- MTD map driver for AMD SC520 Customer Development Platform
+ *
+ * Copyright (C) 2001 Sysgo Real-Time Solutions GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * $Id: sc520cdp.c,v 1.21 2004/12/13 10:27:08 dedekind Exp $
+ *
+ *
+ * The SC520CDP is an evaluation board for the Elan SC520 processor available
+ * from AMD. It has two banks of 32-bit Flash ROM, each 8 Megabytes in size,
+ * and up to 512 KiB of 8-bit DIL Flash ROM.
+ * For details see http://www.amd.com/products/epd/desiging/evalboards/18.elansc520/520_cdp_brief/index.html
+ */
+
+#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 <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/concat.h>
+
+/*
+** The Embedded Systems BIOS decodes the first FLASH starting at
+** 0x8400000. This is a *terrible* place for it because accessing
+** the flash at this location causes the A22 address line to be high
+** (that's what 0x8400000 binary's ought to be). But this is the highest
+** order address line on the raw flash devices themselves!!
+** This causes the top HALF of the flash to be accessed first. Beyond
+** the physical limits of the flash, the flash chip aliases over (to
+** 0x880000 which causes the bottom half to be accessed. This splits the
+** flash into two and inverts it! If you then try to access this from another
+** program that does NOT do this insanity, then you *will* access the
+** first half of the flash, but not find what you expect there. That
+** stuff is in the *second* half! Similarly, the address used by the
+** BIOS for the second FLASH bank is also quite a bad choice.
+** If REPROGRAM_PAR is defined below (the default), then this driver will
+** choose more useful addresses for the FLASH banks by reprogramming the
+** responsible PARxx registers in the SC520's MMCR region. This will
+** cause the settings to be incompatible with the BIOS's settings, which
+** shouldn't be a problem since you are running Linux, (i.e. the BIOS is
+** not much use anyway). However, if you need to be compatible with
+** the BIOS for some reason, just undefine REPROGRAM_PAR.
+*/
+#define REPROGRAM_PAR
+
+
+
+#ifdef REPROGRAM_PAR
+
+/* These are the addresses we want.. */
+#define WINDOW_ADDR_0 0x08800000
+#define WINDOW_ADDR_1 0x09000000
+#define WINDOW_ADDR_2 0x09800000
+
+/* .. and these are the addresses the BIOS gives us */
+#define WINDOW_ADDR_0_BIOS 0x08400000
+#define WINDOW_ADDR_1_BIOS 0x08c00000
+#define WINDOW_ADDR_2_BIOS 0x09400000
+
+#else
+
+#define WINDOW_ADDR_0 0x08400000
+#define WINDOW_ADDR_1 0x08C00000
+#define WINDOW_ADDR_2 0x09400000
+
+#endif
+
+#define WINDOW_SIZE_0 0x00800000
+#define WINDOW_SIZE_1 0x00800000
+#define WINDOW_SIZE_2 0x00080000
+
+
+static struct map_info sc520cdp_map[] = {
+ {
+ .name = "SC520CDP Flash Bank #0",
+ .size = WINDOW_SIZE_0,
+ .bankwidth = 4,
+ .phys = WINDOW_ADDR_0
+ },
+ {
+ .name = "SC520CDP Flash Bank #1",
+ .size = WINDOW_SIZE_1,
+ .bankwidth = 4,
+ .phys = WINDOW_ADDR_1
+ },
+ {
+ .name = "SC520CDP DIL Flash",
+ .size = WINDOW_SIZE_2,
+ .bankwidth = 1,
+ .phys = WINDOW_ADDR_2
+ },
+};
+
+#define NUM_FLASH_BANKS (sizeof(sc520cdp_map)/sizeof(struct map_info))
+
+static struct mtd_info *mymtd[NUM_FLASH_BANKS];
+static struct mtd_info *merged_mtd;
+
+#ifdef REPROGRAM_PAR
+
+/*
+** The SC520 MMCR (memory mapped control register) region resides
+** at 0xFFFEF000. The 16 Programmable Address Region (PAR) registers
+** are at offset 0x88 in the MMCR:
+*/
+#define SC520_MMCR_BASE 0xFFFEF000
+#define SC520_MMCR_EXTENT 0x1000
+#define SC520_PAR(x) ((0x88/sizeof(unsigned long)) + (x))
+#define NUM_SC520_PAR 16 /* total number of PAR registers */
+
+/*
+** The highest three bits in a PAR register determine what target
+** device is controlled by this PAR. Here, only ROMCS? and BOOTCS
+** devices are of interest.
+*/
+#define SC520_PAR_BOOTCS (0x4<<29)
+#define SC520_PAR_ROMCS0 (0x5<<29)
+#define SC520_PAR_ROMCS1 (0x6<<29)
+#define SC520_PAR_TRGDEV (0x7<<29)
+
+/*
+** Bits 28 thru 26 determine some attributes for the
+** region controlled by the PAR. (We only use non-cacheable)
+*/
+#define SC520_PAR_WRPROT (1<<26) /* write protected */
+#define SC520_PAR_NOCACHE (1<<27) /* non-cacheable */
+#define SC520_PAR_NOEXEC (1<<28) /* code execution denied */
+
+
+/*
+** Bit 25 determines the granularity: 4K or 64K
+*/
+#define SC520_PAR_PG_SIZ4 (0<<25)
+#define SC520_PAR_PG_SIZ64 (1<<25)
+
+/*
+** Build a value to be written into a PAR register.
+** We only need ROM entries, 64K page size:
+*/
+#define SC520_PAR_ENTRY(trgdev, address, size) \
+ ((trgdev) | SC520_PAR_NOCACHE | SC520_PAR_PG_SIZ64 | \
+ (address) >> 16 | (((size) >> 16) - 1) << 14)
+
+struct sc520_par_table
+{
+ unsigned long trgdev;
+ unsigned long new_par;
+ unsigned long default_address;
+};
+
+static struct sc520_par_table par_table[NUM_FLASH_BANKS] =
+{
+ { /* Flash Bank #0: selected by ROMCS0 */
+ SC520_PAR_ROMCS0,
+ SC520_PAR_ENTRY(SC520_PAR_ROMCS0, WINDOW_ADDR_0, WINDOW_SIZE_0),
+ WINDOW_ADDR_0_BIOS
+ },
+ { /* Flash Bank #1: selected by ROMCS1 */
+ SC520_PAR_ROMCS1,
+ SC520_PAR_ENTRY(SC520_PAR_ROMCS1, WINDOW_ADDR_1, WINDOW_SIZE_1),
+ WINDOW_ADDR_1_BIOS
+ },
+ { /* DIL (BIOS) Flash: selected by BOOTCS */
+ SC520_PAR_BOOTCS,
+ SC520_PAR_ENTRY(SC520_PAR_BOOTCS, WINDOW_ADDR_2, WINDOW_SIZE_2),
+ WINDOW_ADDR_2_BIOS
+ }
+};
+
+
+static void sc520cdp_setup_par(void)
+{
+ volatile unsigned long __iomem *mmcr;
+ unsigned long mmcr_val;
+ int i, j;
+
+ /* map in SC520's MMCR area */
+ mmcr = ioremap_nocache(SC520_MMCR_BASE, SC520_MMCR_EXTENT);
+ if(!mmcr) { /* ioremap_nocache failed: skip the PAR reprogramming */
+ /* force physical address fields to BIOS defaults: */
+ for(i = 0; i < NUM_FLASH_BANKS; i++)
+ sc520cdp_map[i].phys = par_table[i].default_address;
+ return;
+ }
+
+ /*
+ ** Find the PARxx registers that are reponsible for activating
+ ** ROMCS0, ROMCS1 and BOOTCS. Reprogram each of these with a
+ ** new value from the table.
+ */
+ for(i = 0; i < NUM_FLASH_BANKS; i++) { /* for each par_table entry */
+ for(j = 0; j < NUM_SC520_PAR; j++) { /* for each PAR register */
+ mmcr_val = mmcr[SC520_PAR(j)];
+ /* if target device field matches, reprogram the PAR */
+ if((mmcr_val & SC520_PAR_TRGDEV) == par_table[i].trgdev)
+ {
+ mmcr[SC520_PAR(j)] = par_table[i].new_par;
+ break;
+ }
+ }
+ if(j == NUM_SC520_PAR)
+ { /* no matching PAR found: try default BIOS address */
+ printk(KERN_NOTICE "Could not find PAR responsible for %s\n",
+ sc520cdp_map[i].name);
+ printk(KERN_NOTICE "Trying default address 0x%lx\n",
+ par_table[i].default_address);
+ sc520cdp_map[i].phys = par_table[i].default_address;
+ }
+ }
+ iounmap(mmcr);
+}
+#endif
+
+
+static int __init init_sc520cdp(void)
+{
+ int i, devices_found = 0;
+
+#ifdef REPROGRAM_PAR
+ /* reprogram PAR registers so flash appears at the desired addresses */
+ sc520cdp_setup_par();
+#endif
+
+ for (i = 0; i < NUM_FLASH_BANKS; i++) {
+ printk(KERN_NOTICE "SC520 CDP flash device: 0x%lx at 0x%lx\n",
+ sc520cdp_map[i].size, sc520cdp_map[i].phys);
+
+ sc520cdp_map[i].virt = ioremap_nocache(sc520cdp_map[i].phys, sc520cdp_map[i].size);
+
+ if (!sc520cdp_map[i].virt) {
+ printk("Failed to ioremap_nocache\n");
+ return -EIO;
+ }
+
+ simple_map_init(&sc520cdp_map[i]);
+
+ mymtd[i] = do_map_probe("cfi_probe", &sc520cdp_map[i]);
+ if(!mymtd[i])
+ mymtd[i] = do_map_probe("jedec_probe", &sc520cdp_map[i]);
+ if(!mymtd[i])
+ mymtd[i] = do_map_probe("map_rom", &sc520cdp_map[i]);
+
+ if (mymtd[i]) {
+ mymtd[i]->owner = THIS_MODULE;
+ ++devices_found;
+ }
+ else {
+ iounmap(sc520cdp_map[i].virt);
+ }
+ }
+ if(devices_found >= 2) {
+ /* Combine the two flash banks into a single MTD device & register it: */
+ merged_mtd = mtd_concat_create(mymtd, 2, "SC520CDP Flash Banks #0 and #1");
+ if(merged_mtd)
+ add_mtd_device(merged_mtd);
+ }
+ if(devices_found == 3) /* register the third (DIL-Flash) device */
+ add_mtd_device(mymtd[2]);
+ return(devices_found ? 0 : -ENXIO);
+}
+
+static void __exit cleanup_sc520cdp(void)
+{
+ int i;
+
+ if (merged_mtd) {
+ del_mtd_device(merged_mtd);
+ mtd_concat_destroy(merged_mtd);
+ }
+ if (mymtd[2])
+ del_mtd_device(mymtd[2]);
+
+ for (i = 0; i < NUM_FLASH_BANKS; i++) {
+ if (mymtd[i])
+ map_destroy(mymtd[i]);
+ if (sc520cdp_map[i].virt) {
+ iounmap(sc520cdp_map[i].virt);
+ sc520cdp_map[i].virt = NULL;
+ }
+ }
+}
+
+module_init(init_sc520cdp);
+module_exit(cleanup_sc520cdp);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sysgo Real-Time Solutions GmbH");
+MODULE_DESCRIPTION("MTD map driver for AMD SC520 Customer Development Platform");
diff --git a/drivers/mtd/maps/scb2_flash.c b/drivers/mtd/maps/scb2_flash.c
new file mode 100644
index 000000000000..5bb3b600e5d0
--- /dev/null
+++ b/drivers/mtd/maps/scb2_flash.c
@@ -0,0 +1,256 @@
+/*
+ * MTD map driver for BIOS Flash on Intel SCB2 boards
+ * $Id: scb2_flash.c,v 1.11 2004/11/28 09:40:40 dwmw2 Exp $
+ * Copyright (C) 2002 Sun Microsystems, Inc.
+ * Tim Hockin <thockin@sun.com>
+ *
+ * A few notes on this MTD map:
+ *
+ * This was developed with a small number of SCB2 boards to test on.
+ * Hopefully, Intel has not introducted too many unaccounted variables in the
+ * making of this board.
+ *
+ * The BIOS marks its own memory region as 'reserved' in the e820 map. We
+ * try to request it here, but if it fails, we carry on anyway.
+ *
+ * This is how the chip is attached, so said the schematic:
+ * * a 4 MiB (32 Mib) 16 bit chip
+ * * a 1 MiB memory region
+ * * A20 and A21 pulled up
+ * * D8-D15 ignored
+ * What this means is that, while we are addressing bytes linearly, we are
+ * really addressing words, and discarding the other byte. This means that
+ * the chip MUST BE at least 2 MiB. This also means that every block is
+ * actually half as big as the chip reports. It also means that accesses of
+ * logical address 0 hit higher-address sections of the chip, not physical 0.
+ * One can only hope that these 4MiB x16 chips were a lot cheaper than 1MiB x8
+ * chips.
+ *
+ * This driver assumes the chip is not write-protected by an external signal.
+ * As of the this writing, that is true, but may change, just to spite me.
+ *
+ * The actual BIOS layout has been mostly reverse engineered. Intel BIOS
+ * updates for this board include 10 related (*.bio - &.bi9) binary files and
+ * another separate (*.bbo) binary file. The 10 files are 64k of data + a
+ * small header. If the headers are stripped off, the 10 64k files can be
+ * concatenated into a 640k image. This is your BIOS image, proper. The
+ * separate .bbo file also has a small header. It is the 'Boot Block'
+ * recovery BIOS. Once the header is stripped, no further prep is needed.
+ * As best I can tell, the BIOS is arranged as such:
+ * offset 0x00000 to 0x4ffff (320k): unknown - SCSI BIOS, etc?
+ * offset 0x50000 to 0xeffff (640k): BIOS proper
+ * offset 0xf0000 ty 0xfffff (64k): Boot Block region
+ *
+ * Intel's BIOS update program flashes the BIOS and Boot Block in separate
+ * steps. Probably a wise thing to do.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/cfi.h>
+#include <linux/config.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+
+#define MODNAME "scb2_flash"
+#define SCB2_ADDR 0xfff00000
+#define SCB2_WINDOW 0x00100000
+
+
+static void __iomem *scb2_ioaddr;
+static struct mtd_info *scb2_mtd;
+static struct map_info scb2_map = {
+ .name = "SCB2 BIOS Flash",
+ .size = 0,
+ .bankwidth = 1,
+};
+static int region_fail;
+
+static int __devinit
+scb2_fixup_mtd(struct mtd_info *mtd)
+{
+ int i;
+ int done = 0;
+ struct map_info *map = mtd->priv;
+ struct cfi_private *cfi = map->fldrv_priv;
+
+ /* barf if this doesn't look right */
+ if (cfi->cfiq->InterfaceDesc != 1) {
+ printk(KERN_ERR MODNAME ": unsupported InterfaceDesc: %#x\n",
+ cfi->cfiq->InterfaceDesc);
+ return -1;
+ }
+
+ /* I wasn't here. I didn't see. dwmw2. */
+
+ /* the chip is sometimes bigger than the map - what a waste */
+ mtd->size = map->size;
+
+ /*
+ * We only REALLY get half the chip, due to the way it is
+ * wired up - D8-D15 are tossed away. We read linear bytes,
+ * but in reality we are getting 1/2 of each 16-bit read,
+ * which LOOKS linear to us. Because CFI code accounts for
+ * things like lock/unlock/erase by eraseregions, we need to
+ * fudge them to reflect this. Erases go like this:
+ * * send an erase to an address
+ * * the chip samples the address and erases the block
+ * * add the block erasesize to the address and repeat
+ * -- the problem is that addresses are 16-bit addressable
+ * -- we end up erasing every-other block
+ */
+ mtd->erasesize /= 2;
+ for (i = 0; i < mtd->numeraseregions; i++) {
+ struct mtd_erase_region_info *region = &mtd->eraseregions[i];
+ region->erasesize /= 2;
+ }
+
+ /*
+ * If the chip is bigger than the map, it is wired with the high
+ * address lines pulled up. This makes us access the top portion of
+ * the chip, so all our erase-region info is wrong. Start cutting from
+ * the bottom.
+ */
+ for (i = 0; !done && i < mtd->numeraseregions; i++) {
+ struct mtd_erase_region_info *region = &mtd->eraseregions[i];
+
+ if (region->numblocks * region->erasesize > mtd->size) {
+ region->numblocks = (mtd->size / region->erasesize);
+ done = 1;
+ } else {
+ region->numblocks = 0;
+ }
+ region->offset = 0;
+ }
+
+ return 0;
+}
+
+/* CSB5's 'Function Control Register' has bits for decoding @ >= 0xffc00000 */
+#define CSB5_FCR 0x41
+#define CSB5_FCR_DECODE_ALL 0x0e
+static int __devinit
+scb2_flash_probe(struct pci_dev *dev, const struct pci_device_id *ent)
+{
+ u8 reg;
+
+ /* enable decoding of the flash region in the south bridge */
+ pci_read_config_byte(dev, CSB5_FCR, &reg);
+ pci_write_config_byte(dev, CSB5_FCR, reg | CSB5_FCR_DECODE_ALL);
+
+ if (!request_mem_region(SCB2_ADDR, SCB2_WINDOW, scb2_map.name)) {
+ /*
+ * The BIOS seems to mark the flash region as 'reserved'
+ * in the e820 map. Warn and go about our business.
+ */
+ printk(KERN_WARNING MODNAME
+ ": warning - can't reserve rom window, continuing\n");
+ region_fail = 1;
+ }
+
+ /* remap the IO window (w/o caching) */
+ scb2_ioaddr = ioremap_nocache(SCB2_ADDR, SCB2_WINDOW);
+ if (!scb2_ioaddr) {
+ printk(KERN_ERR MODNAME ": Failed to ioremap window!\n");
+ if (!region_fail)
+ release_mem_region(SCB2_ADDR, SCB2_WINDOW);
+ return -ENOMEM;
+ }
+
+ scb2_map.phys = SCB2_ADDR;
+ scb2_map.virt = scb2_ioaddr;
+ scb2_map.size = SCB2_WINDOW;
+
+ simple_map_init(&scb2_map);
+
+ /* try to find a chip */
+ scb2_mtd = do_map_probe("cfi_probe", &scb2_map);
+
+ if (!scb2_mtd) {
+ printk(KERN_ERR MODNAME ": flash probe failed!\n");
+ iounmap(scb2_ioaddr);
+ if (!region_fail)
+ release_mem_region(SCB2_ADDR, SCB2_WINDOW);
+ return -ENODEV;
+ }
+
+ scb2_mtd->owner = THIS_MODULE;
+ if (scb2_fixup_mtd(scb2_mtd) < 0) {
+ del_mtd_device(scb2_mtd);
+ map_destroy(scb2_mtd);
+ iounmap(scb2_ioaddr);
+ if (!region_fail)
+ release_mem_region(SCB2_ADDR, SCB2_WINDOW);
+ return -ENODEV;
+ }
+
+ printk(KERN_NOTICE MODNAME ": chip size 0x%x at offset 0x%x\n",
+ scb2_mtd->size, SCB2_WINDOW - scb2_mtd->size);
+
+ add_mtd_device(scb2_mtd);
+
+ return 0;
+}
+
+static void __devexit
+scb2_flash_remove(struct pci_dev *dev)
+{
+ if (!scb2_mtd)
+ return;
+
+ /* disable flash writes */
+ if (scb2_mtd->lock)
+ scb2_mtd->lock(scb2_mtd, 0, scb2_mtd->size);
+
+ del_mtd_device(scb2_mtd);
+ map_destroy(scb2_mtd);
+
+ iounmap(scb2_ioaddr);
+ scb2_ioaddr = NULL;
+
+ if (!region_fail)
+ release_mem_region(SCB2_ADDR, SCB2_WINDOW);
+ pci_set_drvdata(dev, NULL);
+}
+
+static struct pci_device_id scb2_flash_pci_ids[] = {
+ {
+ .vendor = PCI_VENDOR_ID_SERVERWORKS,
+ .device = PCI_DEVICE_ID_SERVERWORKS_CSB5,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID
+ },
+ { 0, }
+};
+
+static struct pci_driver scb2_flash_driver = {
+ .name = "Intel SCB2 BIOS Flash",
+ .id_table = scb2_flash_pci_ids,
+ .probe = scb2_flash_probe,
+ .remove = __devexit_p(scb2_flash_remove),
+};
+
+static int __init
+scb2_flash_init(void)
+{
+ return pci_module_init(&scb2_flash_driver);
+}
+
+static void __exit
+scb2_flash_exit(void)
+{
+ pci_unregister_driver(&scb2_flash_driver);
+}
+
+module_init(scb2_flash_init);
+module_exit(scb2_flash_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Tim Hockin <thockin@sun.com>");
+MODULE_DESCRIPTION("MTD map driver for Intel SCB2 BIOS Flash");
+MODULE_DEVICE_TABLE(pci, scb2_flash_pci_ids);
diff --git a/drivers/mtd/maps/scx200_docflash.c b/drivers/mtd/maps/scx200_docflash.c
new file mode 100644
index 000000000000..0ece3786d6ea
--- /dev/null
+++ b/drivers/mtd/maps/scx200_docflash.c
@@ -0,0 +1,233 @@
+/* linux/drivers/mtd/maps/scx200_docflash.c
+
+ Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
+
+ $Id: scx200_docflash.c,v 1.10 2004/11/28 09:40:40 dwmw2 Exp $
+
+ National Semiconductor SCx200 flash mapped with DOCCS
+*/
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#include <linux/pci.h>
+#include <linux/scx200.h>
+
+#define NAME "scx200_docflash"
+
+MODULE_AUTHOR("Christer Weinigel <wingel@hack.org>");
+MODULE_DESCRIPTION("NatSemi SCx200 DOCCS Flash Driver");
+MODULE_LICENSE("GPL");
+
+static int probe = 0; /* Don't autoprobe */
+static unsigned size = 0x1000000; /* 16 MiB the whole ISA address space */
+static unsigned width = 8; /* Default to 8 bits wide */
+static char *flashtype = "cfi_probe";
+
+module_param(probe, int, 0);
+MODULE_PARM_DESC(probe, "Probe for a BIOS mapping");
+module_param(size, int, 0);
+MODULE_PARM_DESC(size, "Size of the flash mapping");
+module_param(width, int, 0);
+MODULE_PARM_DESC(width, "Data width of the flash mapping (8/16)");
+module_param(flashtype, charp, 0);
+MODULE_PARM_DESC(flashtype, "Type of MTD probe to do");
+
+static struct resource docmem = {
+ .flags = IORESOURCE_MEM,
+ .name = "NatSemi SCx200 DOCCS Flash",
+};
+
+static struct mtd_info *mymtd;
+
+#ifdef CONFIG_MTD_PARTITIONS
+static struct mtd_partition partition_info[] = {
+ {
+ .name = "DOCCS Boot kernel",
+ .offset = 0,
+ .size = 0xc0000
+ },
+ {
+ .name = "DOCCS Low BIOS",
+ .offset = 0xc0000,
+ .size = 0x40000
+ },
+ {
+ .name = "DOCCS File system",
+ .offset = 0x100000,
+ .size = ~0 /* calculate from flash size */
+ },
+ {
+ .name = "DOCCS High BIOS",
+ .offset = ~0, /* calculate from flash size */
+ .size = 0x80000
+ },
+};
+#define NUM_PARTITIONS (sizeof(partition_info)/sizeof(partition_info[0]))
+#endif
+
+
+static struct map_info scx200_docflash_map = {
+ .name = "NatSemi SCx200 DOCCS Flash",
+};
+
+static int __init init_scx200_docflash(void)
+{
+ unsigned u;
+ unsigned base;
+ unsigned ctrl;
+ unsigned pmr;
+ struct pci_dev *bridge;
+
+ printk(KERN_DEBUG NAME ": NatSemi SCx200 DOCCS Flash Driver\n");
+
+ if ((bridge = pci_find_device(PCI_VENDOR_ID_NS,
+ PCI_DEVICE_ID_NS_SCx200_BRIDGE,
+ NULL)) == NULL)
+ return -ENODEV;
+
+ /* check that we have found the configuration block */
+ if (!scx200_cb_present())
+ return -ENODEV;
+
+ if (probe) {
+ /* Try to use the present flash mapping if any */
+ pci_read_config_dword(bridge, SCx200_DOCCS_BASE, &base);
+ pci_read_config_dword(bridge, SCx200_DOCCS_CTRL, &ctrl);
+ pmr = inl(scx200_cb_base + SCx200_PMR);
+
+ if (base == 0
+ || (ctrl & 0x07000000) != 0x07000000
+ || (ctrl & 0x0007ffff) == 0)
+ return -ENODEV;
+
+ size = ((ctrl&0x1fff)<<13) + (1<<13);
+
+ for (u = size; u > 1; u >>= 1)
+ ;
+ if (u != 1)
+ return -ENODEV;
+
+ if (pmr & (1<<6))
+ width = 16;
+ else
+ width = 8;
+
+ docmem.start = base;
+ docmem.end = base + size;
+
+ if (request_resource(&iomem_resource, &docmem)) {
+ printk(KERN_ERR NAME ": unable to allocate memory for flash mapping\n");
+ return -ENOMEM;
+ }
+ } else {
+ for (u = size; u > 1; u >>= 1)
+ ;
+ if (u != 1) {
+ printk(KERN_ERR NAME ": invalid size for flash mapping\n");
+ return -EINVAL;
+ }
+
+ if (width != 8 && width != 16) {
+ printk(KERN_ERR NAME ": invalid bus width for flash mapping\n");
+ return -EINVAL;
+ }
+
+ if (allocate_resource(&iomem_resource, &docmem,
+ size,
+ 0xc0000000, 0xffffffff,
+ size, NULL, NULL)) {
+ printk(KERN_ERR NAME ": unable to allocate memory for flash mapping\n");
+ return -ENOMEM;
+ }
+
+ ctrl = 0x07000000 | ((size-1) >> 13);
+
+ printk(KERN_INFO "DOCCS BASE=0x%08lx, CTRL=0x%08lx\n", (long)docmem.start, (long)ctrl);
+
+ pci_write_config_dword(bridge, SCx200_DOCCS_BASE, docmem.start);
+ pci_write_config_dword(bridge, SCx200_DOCCS_CTRL, ctrl);
+ pmr = inl(scx200_cb_base + SCx200_PMR);
+
+ if (width == 8) {
+ pmr &= ~(1<<6);
+ } else {
+ pmr |= (1<<6);
+ }
+ outl(pmr, scx200_cb_base + SCx200_PMR);
+ }
+
+ printk(KERN_INFO NAME ": DOCCS mapped at 0x%lx-0x%lx, width %d\n",
+ docmem.start, docmem.end, width);
+
+ scx200_docflash_map.size = size;
+ if (width == 8)
+ scx200_docflash_map.bankwidth = 1;
+ else
+ scx200_docflash_map.bankwidth = 2;
+
+ simple_map_init(&scx200_docflash_map);
+
+ scx200_docflash_map.phys = docmem.start;
+ scx200_docflash_map.virt = ioremap(docmem.start, scx200_docflash_map.size);
+ if (!scx200_docflash_map.virt) {
+ printk(KERN_ERR NAME ": failed to ioremap the flash\n");
+ release_resource(&docmem);
+ return -EIO;
+ }
+
+ mymtd = do_map_probe(flashtype, &scx200_docflash_map);
+ if (!mymtd) {
+ printk(KERN_ERR NAME ": unable to detect flash\n");
+ iounmap(scx200_docflash_map.virt);
+ release_resource(&docmem);
+ return -ENXIO;
+ }
+
+ if (size < mymtd->size)
+ printk(KERN_WARNING NAME ": warning, flash mapping is smaller than flash size\n");
+
+ mymtd->owner = THIS_MODULE;
+
+#ifdef CONFIG_MTD_PARTITIONS
+ partition_info[3].offset = mymtd->size-partition_info[3].size;
+ partition_info[2].size = partition_info[3].offset-partition_info[2].offset;
+ add_mtd_partitions(mymtd, partition_info, NUM_PARTITIONS);
+#else
+ add_mtd_device(mymtd);
+#endif
+ return 0;
+}
+
+static void __exit cleanup_scx200_docflash(void)
+{
+ if (mymtd) {
+#ifdef CONFIG_MTD_PARTITIONS
+ del_mtd_partitions(mymtd);
+#else
+ del_mtd_device(mymtd);
+#endif
+ map_destroy(mymtd);
+ }
+ if (scx200_docflash_map.virt) {
+ iounmap(scx200_docflash_map.virt);
+ release_resource(&docmem);
+ }
+}
+
+module_init(init_scx200_docflash);
+module_exit(cleanup_scx200_docflash);
+
+/*
+ Local variables:
+ compile-command: "make -k -C ../../.. SUBDIRS=drivers/mtd/maps modules"
+ c-basic-offset: 8
+ End:
+*/
diff --git a/drivers/mtd/maps/sharpsl-flash.c b/drivers/mtd/maps/sharpsl-flash.c
new file mode 100644
index 000000000000..b3b39cb7c608
--- /dev/null
+++ b/drivers/mtd/maps/sharpsl-flash.c
@@ -0,0 +1,101 @@
+/*
+ * sharpsl-flash.c
+ *
+ * Copyright (C) 2001 Lineo Japan, Inc.
+ * Copyright (C) 2002 SHARP
+ *
+ * $Id: sharpsl-flash.c,v 1.2 2004/11/24 20:38:06 rpurdie Exp $
+ *
+ * based on rpxlite.c,v 1.15 2001/10/02 15:05:14 dwmw2 Exp
+ * Handle mapping of the flash on the RPX Lite and CLLF boards
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#define WINDOW_ADDR 0x00000000
+#define WINDOW_SIZE 0x01000000
+#define BANK_WIDTH 2
+
+static struct mtd_info *mymtd;
+
+struct map_info sharpsl_map = {
+ .name = "sharpsl-flash",
+ .size = WINDOW_SIZE,
+ .bankwidth = BANK_WIDTH,
+ .phys = WINDOW_ADDR
+};
+
+static struct mtd_partition sharpsl_partitions[1] = {
+ {
+ name: "Filesystem",
+ size: 0x006d0000,
+ offset: 0x00120000
+ }
+};
+
+#define NB_OF(x) (sizeof(x)/sizeof(x[0]))
+
+int __init init_sharpsl(void)
+{
+ struct mtd_partition *parts;
+ int nb_parts = 0;
+ char *part_type = "static";
+
+ printk(KERN_NOTICE "Sharp SL series flash device: %x at %x\n", WINDOW_SIZE, WINDOW_ADDR);
+ sharpsl_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE);
+ if (!sharpsl_map.virt) {
+ printk("Failed to ioremap\n");
+ return -EIO;
+ }
+ mymtd = do_map_probe("map_rom", &sharpsl_map);
+ if (!mymtd) {
+ iounmap(sharpsl_map.virt);
+ return -ENXIO;
+ }
+
+ mymtd->owner = THIS_MODULE;
+
+ parts = sharpsl_partitions;
+ nb_parts = NB_OF(sharpsl_partitions);
+
+ printk(KERN_NOTICE "Using %s partision definition\n", part_type);
+ add_mtd_partitions(mymtd, parts, nb_parts);
+
+ return 0;
+}
+
+static void __exit cleanup_sharpsl(void)
+{
+ if (mymtd) {
+ del_mtd_partitions(mymtd);
+ map_destroy(mymtd);
+ }
+ if (sharpsl_map.virt) {
+ iounmap(sharpsl_map.virt);
+ sharpsl_map.virt = 0;
+ }
+}
+
+module_init(init_sharpsl);
+module_exit(cleanup_sharpsl);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("SHARP (Original: Arnold Christensen <AKC@pel.dk>)");
+MODULE_DESCRIPTION("MTD map driver for SHARP SL series");
diff --git a/drivers/mtd/maps/solutionengine.c b/drivers/mtd/maps/solutionengine.c
new file mode 100644
index 000000000000..8ce5d897645c
--- /dev/null
+++ b/drivers/mtd/maps/solutionengine.c
@@ -0,0 +1,137 @@
+/*
+ * $Id: solutionengine.c,v 1.14 2004/09/16 23:27:14 gleixner Exp $
+ *
+ * Flash and EPROM on Hitachi Solution Engine and similar boards.
+ *
+ * (C) 2001 Red Hat, Inc.
+ *
+ * GPL'd
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/config.h>
+#include <linux/errno.h>
+
+static struct mtd_info *flash_mtd;
+static struct mtd_info *eprom_mtd;
+
+static struct mtd_partition *parsed_parts;
+
+struct map_info soleng_eprom_map = {
+ .name = "Solution Engine EPROM",
+ .size = 0x400000,
+ .bankwidth = 4,
+};
+
+struct map_info soleng_flash_map = {
+ .name = "Solution Engine FLASH",
+ .size = 0x400000,
+ .bankwidth = 4,
+};
+
+static const char *probes[] = { "RedBoot", "cmdlinepart", NULL };
+
+#ifdef CONFIG_MTD_SUPERH_RESERVE
+static struct mtd_partition superh_se_partitions[] = {
+ /* Reserved for boot code, read-only */
+ {
+ .name = "flash_boot",
+ .offset = 0x00000000,
+ .size = CONFIG_MTD_SUPERH_RESERVE,
+ .mask_flags = MTD_WRITEABLE,
+ },
+ /* All else is writable (e.g. JFFS) */
+ {
+ .name = "Flash FS",
+ .offset = MTDPART_OFS_NXTBLK,
+ .size = MTDPART_SIZ_FULL,
+ }
+};
+#endif /* CONFIG_MTD_SUPERH_RESERVE */
+
+static int __init init_soleng_maps(void)
+{
+ int nr_parts = 0;
+
+ /* First probe at offset 0 */
+ soleng_flash_map.phys = 0;
+ soleng_flash_map.virt = (void __iomem *)P2SEGADDR(0);
+ soleng_eprom_map.phys = 0x01000000;
+ soleng_eprom_map.virt = (void __iomem *)P1SEGADDR(0x01000000);
+ simple_map_init(&soleng_eprom_map);
+ simple_map_init(&soleng_flash_map);
+
+ printk(KERN_NOTICE "Probing for flash chips at 0x00000000:\n");
+ flash_mtd = do_map_probe("cfi_probe", &soleng_flash_map);
+ if (!flash_mtd) {
+ /* Not there. Try swapping */
+ printk(KERN_NOTICE "Probing for flash chips at 0x01000000:\n");
+ soleng_flash_map.phys = 0x01000000;
+ soleng_flash_map.virt = P2SEGADDR(0x01000000);
+ soleng_eprom_map.phys = 0;
+ soleng_eprom_map.virt = P1SEGADDR(0);
+ flash_mtd = do_map_probe("cfi_probe", &soleng_flash_map);
+ if (!flash_mtd) {
+ /* Eep. */
+ printk(KERN_NOTICE "Flash chips not detected at either possible location.\n");
+ return -ENXIO;
+ }
+ }
+ printk(KERN_NOTICE "Solution Engine: Flash at 0x%08lx, EPROM at 0x%08lx\n",
+ soleng_flash_map.phys & 0x1fffffff,
+ soleng_eprom_map.phys & 0x1fffffff);
+ flash_mtd->owner = THIS_MODULE;
+
+ eprom_mtd = do_map_probe("map_rom", &soleng_eprom_map);
+ if (eprom_mtd) {
+ eprom_mtd->owner = THIS_MODULE;
+ add_mtd_device(eprom_mtd);
+ }
+
+ nr_parts = parse_mtd_partitions(flash_mtd, probes, &parsed_parts, 0);
+
+#ifdef CONFIG_MTD_SUPERH_RESERVE
+ if (nr_parts <= 0) {
+ printk(KERN_NOTICE "Using configured partition at 0x%08x.\n",
+ CONFIG_MTD_SUPERH_RESERVE);
+ parsed_parts = superh_se_partitions;
+ nr_parts = sizeof(superh_se_partitions)/sizeof(*parsed_parts);
+ }
+#endif /* CONFIG_MTD_SUPERH_RESERVE */
+
+ if (nr_parts > 0)
+ add_mtd_partitions(flash_mtd, parsed_parts, nr_parts);
+ else
+ add_mtd_device(flash_mtd);
+
+ return 0;
+}
+
+static void __exit cleanup_soleng_maps(void)
+{
+ if (eprom_mtd) {
+ del_mtd_device(eprom_mtd);
+ map_destroy(eprom_mtd);
+ }
+
+ if (parsed_parts)
+ del_mtd_partitions(flash_mtd);
+ else
+ del_mtd_device(flash_mtd);
+ map_destroy(flash_mtd);
+}
+
+module_init(init_soleng_maps);
+module_exit(cleanup_soleng_maps);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("MTD map driver for Hitachi SolutionEngine (and similar) boards");
+
diff --git a/drivers/mtd/maps/sun_uflash.c b/drivers/mtd/maps/sun_uflash.c
new file mode 100644
index 000000000000..29091d10030a
--- /dev/null
+++ b/drivers/mtd/maps/sun_uflash.c
@@ -0,0 +1,177 @@
+/* $Id: sun_uflash.c,v 1.11 2004/11/04 13:24:15 gleixner Exp $
+ *
+ * sun_uflash - Driver implementation for user-programmable flash
+ * present on many Sun Microsystems SME boardsets.
+ *
+ * This driver does NOT provide access to the OBP-flash for
+ * safety reasons-- use <linux>/drivers/sbus/char/flash.c instead.
+ *
+ * Copyright (c) 2001 Eric Brower (ebrower@usa.net)
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <asm/ebus.h>
+#include <asm/oplib.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+
+#define UFLASH_OBPNAME "flashprom"
+#define UFLASH_DEVNAME "userflash"
+
+#define UFLASH_WINDOW_SIZE 0x200000
+#define UFLASH_BUSWIDTH 1 /* EBus is 8-bit */
+
+MODULE_AUTHOR
+ ("Eric Brower <ebrower@usa.net>");
+MODULE_DESCRIPTION
+ ("User-programmable flash device on Sun Microsystems boardsets");
+MODULE_SUPPORTED_DEVICE
+ ("userflash");
+MODULE_LICENSE
+ ("GPL");
+
+static LIST_HEAD(device_list);
+struct uflash_dev {
+ char * name; /* device name */
+ struct map_info map; /* mtd map info */
+ struct mtd_info * mtd; /* mtd info */
+ struct list_head list;
+};
+
+
+struct map_info uflash_map_templ = {
+ .name = "SUNW,???-????",
+ .size = UFLASH_WINDOW_SIZE,
+ .bankwidth = UFLASH_BUSWIDTH,
+};
+
+int uflash_devinit(struct linux_ebus_device* edev)
+{
+ int iTmp, nregs;
+ struct linux_prom_registers regs[2];
+ struct uflash_dev *pdev;
+
+ iTmp = prom_getproperty(
+ edev->prom_node, "reg", (void *)regs, sizeof(regs));
+ if ((iTmp % sizeof(regs[0])) != 0) {
+ printk("%s: Strange reg property size %d\n",
+ UFLASH_DEVNAME, iTmp);
+ return -ENODEV;
+ }
+
+ nregs = iTmp / sizeof(regs[0]);
+
+ if (nregs != 1) {
+ /* Non-CFI userflash device-- once I find one we
+ * can work on supporting it.
+ */
+ printk("%s: unsupported device at 0x%lx (%d regs): " \
+ "email ebrower@usa.net\n",
+ UFLASH_DEVNAME, edev->resource[0].start, nregs);
+ return -ENODEV;
+ }
+
+ if(0 == (pdev = kmalloc(sizeof(struct uflash_dev), GFP_KERNEL))) {
+ printk("%s: unable to kmalloc new device\n", UFLASH_DEVNAME);
+ return(-ENOMEM);
+ }
+
+ /* copy defaults and tweak parameters */
+ memcpy(&pdev->map, &uflash_map_templ, sizeof(uflash_map_templ));
+ pdev->map.size = regs[0].reg_size;
+
+ iTmp = prom_getproplen(edev->prom_node, "model");
+ pdev->name = kmalloc(iTmp, GFP_KERNEL);
+ prom_getstring(edev->prom_node, "model", pdev->name, iTmp);
+ if(0 != pdev->name && 0 < strlen(pdev->name)) {
+ pdev->map.name = pdev->name;
+ }
+ pdev->map.phys = edev->resource[0].start;
+ pdev->map.virt = ioremap_nocache(edev->resource[0].start, pdev->map.size);
+ if(0 == pdev->map.virt) {
+ printk("%s: failed to map device\n", __FUNCTION__);
+ kfree(pdev->name);
+ kfree(pdev);
+ return(-1);
+ }
+
+ simple_map_init(&pdev->map);
+
+ /* MTD registration */
+ pdev->mtd = do_map_probe("cfi_probe", &pdev->map);
+ if(0 == pdev->mtd) {
+ iounmap(pdev->map.virt);
+ kfree(pdev->name);
+ kfree(pdev);
+ return(-ENXIO);
+ }
+
+ list_add(&pdev->list, &device_list);
+
+ pdev->mtd->owner = THIS_MODULE;
+
+ add_mtd_device(pdev->mtd);
+ return(0);
+}
+
+static int __init uflash_init(void)
+{
+ struct linux_ebus *ebus = NULL;
+ struct linux_ebus_device *edev = NULL;
+
+ for_each_ebus(ebus) {
+ for_each_ebusdev(edev, ebus) {
+ if (!strcmp(edev->prom_name, UFLASH_OBPNAME)) {
+ if(0 > prom_getproplen(edev->prom_node, "user")) {
+ DEBUG(2, "%s: ignoring device at 0x%lx\n",
+ UFLASH_DEVNAME, edev->resource[0].start);
+ } else {
+ uflash_devinit(edev);
+ }
+ }
+ }
+ }
+
+ if(list_empty(&device_list)) {
+ printk("%s: unable to locate device\n", UFLASH_DEVNAME);
+ return -ENODEV;
+ }
+ return(0);
+}
+
+static void __exit uflash_cleanup(void)
+{
+ struct list_head *udevlist;
+ struct uflash_dev *udev;
+
+ list_for_each(udevlist, &device_list) {
+ udev = list_entry(udevlist, struct uflash_dev, list);
+ DEBUG(2, "%s: removing device %s\n",
+ UFLASH_DEVNAME, udev->name);
+
+ if(0 != udev->mtd) {
+ del_mtd_device(udev->mtd);
+ map_destroy(udev->mtd);
+ }
+ if(0 != udev->map.virt) {
+ iounmap(udev->map.virt);
+ udev->map.virt = NULL;
+ }
+ if(0 != udev->name) {
+ kfree(udev->name);
+ }
+ kfree(udev);
+ }
+}
+
+module_init(uflash_init);
+module_exit(uflash_cleanup);
diff --git a/drivers/mtd/maps/tqm8xxl.c b/drivers/mtd/maps/tqm8xxl.c
new file mode 100644
index 000000000000..995e9991cb8d
--- /dev/null
+++ b/drivers/mtd/maps/tqm8xxl.c
@@ -0,0 +1,263 @@
+/*
+ * Handle mapping of the flash memory access routines
+ * on TQM8xxL based devices.
+ *
+ * $Id: tqm8xxl.c,v 1.13 2004/10/20 22:21:53 dwmw2 Exp $
+ *
+ * based on rpxlite.c
+ *
+ * Copyright(C) 2001 Kirk Lee <kirk@hpc.ee.ntu.edu.tw>
+ *
+ * This code is GPLed
+ *
+ */
+
+/*
+ * According to TQM8xxL hardware manual, TQM8xxL series have
+ * following flash memory organisations:
+ * | capacity | | chip type | | bank0 | | bank1 |
+ * 2MiB 512Kx16 2MiB 0
+ * 4MiB 1Mx16 4MiB 0
+ * 8MiB 1Mx16 4MiB 4MiB
+ * Thus, we choose CONFIG_MTD_CFI_I2 & CONFIG_MTD_CFI_B4 at
+ * kernel configuration.
+ */
+#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 <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+
+#define FLASH_ADDR 0x40000000
+#define FLASH_SIZE 0x00800000
+#define FLASH_BANK_MAX 4
+
+// trivial struct to describe partition information
+struct mtd_part_def
+{
+ int nums;
+ unsigned char *type;
+ struct mtd_partition* mtd_part;
+};
+
+//static struct mtd_info *mymtd;
+static struct mtd_info* mtd_banks[FLASH_BANK_MAX];
+static struct map_info* map_banks[FLASH_BANK_MAX];
+static struct mtd_part_def part_banks[FLASH_BANK_MAX];
+static unsigned long num_banks;
+static void __iomem *start_scan_addr;
+
+/*
+ * Here are partition information for all known TQM8xxL series devices.
+ * See include/linux/mtd/partitions.h for definition of the mtd_partition
+ * structure.
+ *
+ * The *_max_flash_size is the maximum possible mapped flash size which
+ * is not necessarily the actual flash size. It must correspond to the
+ * value specified in the mapping definition defined by the
+ * "struct map_desc *_io_desc" for the corresponding machine.
+ */
+
+#ifdef CONFIG_MTD_PARTITIONS
+/* Currently, TQM8xxL has upto 8MiB flash */
+static unsigned long tqm8xxl_max_flash_size = 0x00800000;
+
+/* partition definition for first flash bank
+ * (cf. "drivers/char/flash_config.c")
+ */
+static struct mtd_partition tqm8xxl_partitions[] = {
+ {
+ .name = "ppcboot",
+ .offset = 0x00000000,
+ .size = 0x00020000, /* 128KB */
+ .mask_flags = MTD_WRITEABLE, /* force read-only */
+ },
+ {
+ .name = "kernel", /* default kernel image */
+ .offset = 0x00020000,
+ .size = 0x000e0000,
+ .mask_flags = MTD_WRITEABLE, /* force read-only */
+ },
+ {
+ .name = "user",
+ .offset = 0x00100000,
+ .size = 0x00100000,
+ },
+ {
+ .name = "initrd",
+ .offset = 0x00200000,
+ .size = 0x00200000,
+ }
+};
+/* partition definition for second flash bank */
+static struct mtd_partition tqm8xxl_fs_partitions[] = {
+ {
+ .name = "cramfs",
+ .offset = 0x00000000,
+ .size = 0x00200000,
+ },
+ {
+ .name = "jffs",
+ .offset = 0x00200000,
+ .size = 0x00200000,
+ //.size = MTDPART_SIZ_FULL,
+ }
+};
+#endif
+
+int __init init_tqm_mtd(void)
+{
+ int idx = 0, ret = 0;
+ unsigned long flash_addr, flash_size, mtd_size = 0;
+ /* pointer to TQM8xxL board info data */
+ bd_t *bd = (bd_t *)__res;
+
+ flash_addr = bd->bi_flashstart;
+ flash_size = bd->bi_flashsize;
+
+ //request maximum flash size address space
+ start_scan_addr = ioremap(flash_addr, flash_size);
+ if (!start_scan_addr) {
+ printk(KERN_WARNING "%s:Failed to ioremap address:0x%x\n", __FUNCTION__, flash_addr);
+ return -EIO;
+ }
+
+ for (idx = 0 ; idx < FLASH_BANK_MAX ; idx++) {
+ if(mtd_size >= flash_size)
+ break;
+
+ printk(KERN_INFO "%s: chip probing count %d\n", __FUNCTION__, idx);
+
+ map_banks[idx] = (struct map_info *)kmalloc(sizeof(struct map_info), GFP_KERNEL);
+ if(map_banks[idx] == NULL) {
+ ret = -ENOMEM;
+ /* FIXME: What if some MTD devices were probed already? */
+ goto error_mem;
+ }
+
+ memset((void *)map_banks[idx], 0, sizeof(struct map_info));
+ map_banks[idx]->name = (char *)kmalloc(16, GFP_KERNEL);
+
+ if (!map_banks[idx]->name) {
+ ret = -ENOMEM;
+ /* FIXME: What if some MTD devices were probed already? */
+ goto error_mem;
+ }
+ sprintf(map_banks[idx]->name, "TQM8xxL%d", idx);
+
+ map_banks[idx]->size = flash_size;
+ map_banks[idx]->bankwidth = 4;
+
+ simple_map_init(map_banks[idx]);
+
+ map_banks[idx]->virt = start_scan_addr;
+ map_banks[idx]->phys = flash_addr;
+ /* FIXME: This looks utterly bogus, but I'm trying to
+ preserve the behaviour of the original (shown here)...
+
+ map_banks[idx]->map_priv_1 =
+ start_scan_addr + ((idx > 0) ?
+ (mtd_banks[idx-1] ? mtd_banks[idx-1]->size : 0) : 0);
+ */
+
+ if (idx && mtd_banks[idx-1]) {
+ map_banks[idx]->virt += mtd_banks[idx-1]->size;
+ map_banks[idx]->phys += mtd_banks[idx-1]->size;
+ }
+
+ //start to probe flash chips
+ mtd_banks[idx] = do_map_probe("cfi_probe", map_banks[idx]);
+
+ if (mtd_banks[idx]) {
+ mtd_banks[idx]->owner = THIS_MODULE;
+ mtd_size += mtd_banks[idx]->size;
+ num_banks++;
+
+ printk(KERN_INFO "%s: bank%d, name:%s, size:%dbytes \n", __FUNCTION__, num_banks,
+ mtd_banks[idx]->name, mtd_banks[idx]->size);
+ }
+ }
+
+ /* no supported flash chips found */
+ if (!num_banks) {
+ printk(KERN_NOTICE "TQM8xxL: No support flash chips found!\n");
+ ret = -ENXIO;
+ goto error_mem;
+ }
+
+#ifdef CONFIG_MTD_PARTITIONS
+ /*
+ * Select Static partition definitions
+ */
+ part_banks[0].mtd_part = tqm8xxl_partitions;
+ part_banks[0].type = "Static image";
+ part_banks[0].nums = ARRAY_SIZE(tqm8xxl_partitions);
+
+ part_banks[1].mtd_part = tqm8xxl_fs_partitions;
+ part_banks[1].type = "Static file system";
+ part_banks[1].nums = ARRAY_SIZE(tqm8xxl_fs_partitions);
+
+ for(idx = 0; idx < num_banks ; idx++) {
+ if (part_banks[idx].nums == 0) {
+ printk(KERN_NOTICE "TQM flash%d: no partition info available, registering whole flash at once\n", idx);
+ add_mtd_device(mtd_banks[idx]);
+ } else {
+ printk(KERN_NOTICE "TQM flash%d: Using %s partition definition\n",
+ idx, part_banks[idx].type);
+ add_mtd_partitions(mtd_banks[idx], part_banks[idx].mtd_part,
+ part_banks[idx].nums);
+ }
+ }
+#else
+ printk(KERN_NOTICE "TQM flash: registering %d whole flash banks at once\n", num_banks);
+ for(idx = 0 ; idx < num_banks ; idx++)
+ add_mtd_device(mtd_banks[idx]);
+#endif
+ return 0;
+error_mem:
+ for(idx = 0 ; idx < FLASH_BANK_MAX ; idx++) {
+ if(map_banks[idx] != NULL) {
+ if(map_banks[idx]->name != NULL) {
+ kfree(map_banks[idx]->name);
+ map_banks[idx]->name = NULL;
+ }
+ kfree(map_banks[idx]);
+ map_banks[idx] = NULL;
+ }
+ }
+error:
+ iounmap(start_scan_addr);
+ return ret;
+}
+
+static void __exit cleanup_tqm_mtd(void)
+{
+ unsigned int idx = 0;
+ for(idx = 0 ; idx < num_banks ; idx++) {
+ /* destroy mtd_info previously allocated */
+ if (mtd_banks[idx]) {
+ del_mtd_partitions(mtd_banks[idx]);
+ map_destroy(mtd_banks[idx]);
+ }
+ /* release map_info not used anymore */
+ kfree(map_banks[idx]->name);
+ kfree(map_banks[idx]);
+ }
+
+ if (start_scan_addr) {
+ iounmap(start_scan_addr);
+ start_scan_addr = 0;
+ }
+}
+
+module_init(init_tqm_mtd);
+module_exit(cleanup_tqm_mtd);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kirk Lee <kirk@hpc.ee.ntu.edu.tw>");
+MODULE_DESCRIPTION("MTD map driver for TQM8xxL boards");
diff --git a/drivers/mtd/maps/ts5500_flash.c b/drivers/mtd/maps/ts5500_flash.c
new file mode 100644
index 000000000000..3ebd90f56503
--- /dev/null
+++ b/drivers/mtd/maps/ts5500_flash.c
@@ -0,0 +1,141 @@
+/*
+ * ts5500_flash.c -- MTD map driver for Technology Systems TS-5500 board
+ *
+ * Copyright (C) 2004 Sean Young <sean@mess.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ * Note:
+ * - In order for detection to work, jumper 3 must be set.
+ * - Drive A and B use a proprietary FTL from General Software which isn't
+ * supported as of yet so standard drives can't be mounted; you can create
+ * your own (e.g. jffs) file system.
+ * - If you have created your own jffs file system and the bios overwrites
+ * it during boot, try disabling Drive A: and B: in the boot order.
+ *
+ * $Id: ts5500_flash.c,v 1.2 2004/11/28 09:40:40 dwmw2 Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+
+#ifdef CONFIG_MTD_PARTITIONS
+#include <linux/mtd/partitions.h>
+#endif
+
+#define WINDOW_ADDR 0x09400000
+#define WINDOW_SIZE 0x00200000
+
+static struct map_info ts5500_map = {
+ .name = "TS-5500 Flash",
+ .size = WINDOW_SIZE,
+ .bankwidth = 1,
+ .phys = WINDOW_ADDR
+};
+
+#ifdef CONFIG_MTD_PARTITIONS
+static struct mtd_partition ts5500_partitions[] = {
+ {
+ .name = "Drive A",
+ .offset = 0,
+ .size = 0x0e0000
+ },
+ {
+ .name = "BIOS",
+ .offset = 0x0e0000,
+ .size = 0x020000,
+ },
+ {
+ .name = "Drive B",
+ .offset = 0x100000,
+ .size = 0x100000
+ }
+};
+
+#define NUM_PARTITIONS (sizeof(ts5500_partitions)/sizeof(struct mtd_partition))
+
+#endif
+
+static struct mtd_info *mymtd;
+
+static int __init init_ts5500_map(void)
+{
+ int rc = 0;
+
+ ts5500_map.virt = ioremap_nocache(ts5500_map.phys, ts5500_map.size);
+
+ if(!ts5500_map.virt) {
+ printk(KERN_ERR "Failed to ioremap_nocache\n");
+ rc = -EIO;
+ goto err_out_ioremap;
+ }
+
+ simple_map_init(&ts5500_map);
+
+ mymtd = do_map_probe("jedec_probe", &ts5500_map);
+ if(!mymtd)
+ mymtd = do_map_probe("map_rom", &ts5500_map);
+
+ if(!mymtd) {
+ rc = -ENXIO;
+ goto err_out_map;
+ }
+
+ mymtd->owner = THIS_MODULE;
+#ifdef CONFIG_MTD_PARTITIONS
+ add_mtd_partitions(mymtd, ts5500_partitions, NUM_PARTITIONS);
+#else
+ add_mtd_device(mymtd);
+#endif
+
+ return 0;
+
+err_out_map:
+ map_destroy(mymtd);
+err_out_ioremap:
+ iounmap(ts5500_map.virt);
+
+ return rc;
+}
+
+static void __exit cleanup_ts5500_map(void)
+{
+ if (mymtd) {
+#ifdef CONFIG_MTD_PARTITIONS
+ del_mtd_partitions(mymtd);
+#else
+ del_mtd_device(mymtd);
+#endif
+ map_destroy(mymtd);
+ }
+
+ if (ts5500_map.virt) {
+ iounmap(ts5500_map.virt);
+ ts5500_map.virt = NULL;
+ }
+}
+
+module_init(init_ts5500_map);
+module_exit(cleanup_ts5500_map);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sean Young <sean@mess.org>");
+MODULE_DESCRIPTION("MTD map driver for Techology Systems TS-5500 board");
+
diff --git a/drivers/mtd/maps/tsunami_flash.c b/drivers/mtd/maps/tsunami_flash.c
new file mode 100644
index 000000000000..170d71239e5e
--- /dev/null
+++ b/drivers/mtd/maps/tsunami_flash.c
@@ -0,0 +1,108 @@
+/*
+ * tsunami_flash.c
+ *
+ * flash chip on alpha ds10...
+ * $Id: tsunami_flash.c,v 1.9 2004/07/14 09:52:55 dwmw2 Exp $
+ */
+#include <asm/io.h>
+#include <asm/core_tsunami.h>
+#include <linux/init.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/mtd.h>
+
+#define FLASH_ENABLE_PORT 0x00C00001
+#define FLASH_ENABLE_BYTE 0x01
+#define FLASH_DISABLE_BYTE 0x00
+
+#define MAX_TIG_FLASH_SIZE (12*1024*1024)
+static inline map_word tsunami_flash_read8(struct map_info *map, unsigned long offset)
+{
+ map_word val;
+ val.x[0] = tsunami_tig_readb(offset);
+ return val;
+}
+
+static void tsunami_flash_write8(struct map_info *map, map_word value, unsigned long offset)
+{
+ tsunami_tig_writeb(value.x[0], offset);
+}
+
+static void tsunami_flash_copy_from(
+ struct map_info *map, void *addr, unsigned long offset, ssize_t len)
+{
+ unsigned char *dest;
+ dest = addr;
+ while(len && (offset < MAX_TIG_FLASH_SIZE)) {
+ *dest = tsunami_tig_readb(offset);
+ offset++;
+ dest++;
+ len--;
+ }
+}
+
+static void tsunami_flash_copy_to(
+ struct map_info *map, unsigned long offset,
+ const void *addr, ssize_t len)
+{
+ const unsigned char *src;
+ src = addr;
+ while(len && (offset < MAX_TIG_FLASH_SIZE)) {
+ tsunami_tig_writeb(*src, offset);
+ offset++;
+ src++;
+ len--;
+ }
+}
+
+/*
+ * Deliberately don't provide operations wider than 8 bits. I don't
+ * have then and it scares me to think how you could mess up if
+ * you tried to use them. Buswidth is correctly so I'm safe.
+ */
+static struct map_info tsunami_flash_map = {
+ .name = "flash chip on the Tsunami TIG bus",
+ .size = MAX_TIG_FLASH_SIZE,
+ .phys = NO_XIP;
+ .bankwidth = 1,
+ .read = tsunami_flash_read8,
+ .copy_from = tsunami_flash_copy_from,
+ .write = tsunami_flash_write8,
+ .copy_to = tsunami_flash_copy_to,
+};
+
+static struct mtd_info *tsunami_flash_mtd;
+
+static void __exit cleanup_tsunami_flash(void)
+{
+ struct mtd_info *mtd;
+ mtd = tsunami_flash_mtd;
+ if (mtd) {
+ del_mtd_device(mtd);
+ map_destroy(mtd);
+ }
+ tsunami_flash_mtd = 0;
+}
+
+
+static int __init init_tsunami_flash(void)
+{
+ static const char *rom_probe_types[] = { "cfi_probe", "jedec_probe", "map_rom", NULL };
+ char **type;
+
+ tsunami_tig_writeb(FLASH_ENABLE_BYTE, FLASH_ENABLE_PORT);
+
+ tsunami_flash_mtd = 0;
+ type = rom_probe_types;
+ for(; !tsunami_flash_mtd && *type; type++) {
+ tsunami_flash_mtd = do_map_probe(*type, &tsunami_flash_map);
+ }
+ if (tsunami_flash_mtd) {
+ tsunami_flash_mtd->owner = THIS_MODULE;
+ add_mtd_device(tsunami_flash_mtd);
+ return 0;
+ }
+ return -ENXIO;
+}
+
+module_init(init_tsunami_flash);
+module_exit(cleanup_tsunami_flash);
diff --git a/drivers/mtd/maps/uclinux.c b/drivers/mtd/maps/uclinux.c
new file mode 100644
index 000000000000..811d92e5f5b1
--- /dev/null
+++ b/drivers/mtd/maps/uclinux.c
@@ -0,0 +1,127 @@
+/****************************************************************************/
+
+/*
+ * uclinux.c -- generic memory mapped MTD driver for uclinux
+ *
+ * (C) Copyright 2002, Greg Ungerer (gerg@snapgear.com)
+ *
+ * $Id: uclinux.c,v 1.10 2005/01/05 18:05:13 dwmw2 Exp $
+ */
+
+/****************************************************************************/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/major.h>
+#include <linux/root_dev.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <asm/io.h>
+
+/****************************************************************************/
+
+
+/****************************************************************************/
+
+struct map_info uclinux_ram_map = {
+ .name = "RAM",
+};
+
+struct mtd_info *uclinux_ram_mtdinfo;
+
+/****************************************************************************/
+
+struct mtd_partition uclinux_romfs[] = {
+ { .name = "ROMfs" }
+};
+
+#define NUM_PARTITIONS (sizeof(uclinux_romfs) / sizeof(uclinux_romfs[0]))
+
+/****************************************************************************/
+
+int uclinux_point(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char **mtdbuf)
+{
+ struct map_info *map = mtd->priv;
+ *mtdbuf = (u_char *) (map->virt + ((int) from));
+ *retlen = len;
+ return(0);
+}
+
+/****************************************************************************/
+
+int __init uclinux_mtd_init(void)
+{
+ struct mtd_info *mtd;
+ struct map_info *mapp;
+ extern char _ebss;
+
+ mapp = &uclinux_ram_map;
+ mapp->phys = (unsigned long) &_ebss;
+ mapp->size = PAGE_ALIGN(*((unsigned long *)((&_ebss) + 8)));
+ mapp->bankwidth = 4;
+
+ printk("uclinux[mtd]: RAM probe address=0x%x size=0x%x\n",
+ (int) mapp->map_priv_2, (int) mapp->size);
+
+ mapp->virt = ioremap_nocache(mapp->phys, mapp->size);
+
+ if (mapp->virt == 0) {
+ printk("uclinux[mtd]: ioremap_nocache() failed\n");
+ return(-EIO);
+ }
+
+ simple_map_init(mapp);
+
+ mtd = do_map_probe("map_ram", mapp);
+ if (!mtd) {
+ printk("uclinux[mtd]: failed to find a mapping?\n");
+ iounmap(mapp->virt);
+ return(-ENXIO);
+ }
+
+ mtd->owner = THIS_MODULE;
+ mtd->point = uclinux_point;
+ mtd->priv = mapp;
+
+ uclinux_ram_mtdinfo = mtd;
+ add_mtd_partitions(mtd, uclinux_romfs, NUM_PARTITIONS);
+
+ printk("uclinux[mtd]: set %s to be root filesystem\n",
+ uclinux_romfs[0].name);
+ ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, 0);
+ put_mtd_device(mtd);
+
+ return(0);
+}
+
+/****************************************************************************/
+
+void __exit uclinux_mtd_cleanup(void)
+{
+ if (uclinux_ram_mtdinfo) {
+ del_mtd_partitions(uclinux_ram_mtdinfo);
+ map_destroy(uclinux_ram_mtdinfo);
+ uclinux_ram_mtdinfo = NULL;
+ }
+ if (uclinux_ram_map.map_priv_1) {
+ iounmap((void *) uclinux_ram_map.virt);
+ uclinux_ram_map.virt = 0;
+ }
+}
+
+/****************************************************************************/
+
+module_init(uclinux_mtd_init);
+module_exit(uclinux_mtd_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Greg Ungerer <gerg@snapgear.com>");
+MODULE_DESCRIPTION("Generic RAM based MTD for uClinux");
+
+/****************************************************************************/
diff --git a/drivers/mtd/maps/vmax301.c b/drivers/mtd/maps/vmax301.c
new file mode 100644
index 000000000000..c8c74110ed1b
--- /dev/null
+++ b/drivers/mtd/maps/vmax301.c
@@ -0,0 +1,198 @@
+// $Id: vmax301.c,v 1.30 2004/07/12 22:38:29 dwmw2 Exp $
+/* ######################################################################
+
+ Tempustech VMAX SBC301 MTD Driver.
+
+ The VMAx 301 is a SBC based on . It
+ comes with three builtin AMD 29F016B flash chips and a socket for SRAM or
+ more flash. Each unit has it's own 8k mapping into a settable region
+ (0xD8000). There are two 8k mappings for each MTD, the first is always set
+ to the lower 8k of the device the second is paged. Writing a 16 bit page
+ value to anywhere in the first 8k will cause the second 8k to page around.
+
+ To boot the device a bios extension must be installed into the first 8k
+ of flash that is smart enough to copy itself down, page in the rest of
+ itself and begin executing.
+
+ ##################################################################### */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+
+#include <linux/mtd/map.h>
+#include <linux/mtd/mtd.h>
+
+
+#define WINDOW_START 0xd8000
+#define WINDOW_LENGTH 0x2000
+#define WINDOW_SHIFT 25
+#define WINDOW_MASK 0x1FFF
+
+/* Actually we could use two spinlocks, but we'd have to have
+ more private space in the struct map_info. We lose a little
+ performance like this, but we'd probably lose more by having
+ the extra indirection from having one of the map->map_priv
+ fields pointing to yet another private struct.
+*/
+static DEFINE_SPINLOCK(vmax301_spin);
+
+static void __vmax301_page(struct map_info *map, unsigned long page)
+{
+ writew(page, map->map_priv_2 - WINDOW_LENGTH);
+ map->map_priv_1 = page;
+}
+
+static inline void vmax301_page(struct map_info *map,
+ unsigned long ofs)
+{
+ unsigned long page = (ofs >> WINDOW_SHIFT);
+ if (map->map_priv_1 != page)
+ __vmax301_page(map, page);
+}
+
+static map_word vmax301_read8(struct map_info *map, unsigned long ofs)
+{
+ map_word ret;
+ spin_lock(&vmax301_spin);
+ vmax301_page(map, ofs);
+ ret.x[0] = readb(map->map_priv_2 + (ofs & WINDOW_MASK));
+ spin_unlock(&vmax301_spin);
+ return ret;
+}
+
+static void vmax301_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len)
+{
+ while(len) {
+ unsigned long thislen = len;
+ if (len > (WINDOW_LENGTH - (from & WINDOW_MASK)))
+ thislen = WINDOW_LENGTH-(from & WINDOW_MASK);
+ spin_lock(&vmax301_spin);
+ vmax301_page(map, from);
+ memcpy_fromio(to, map->map_priv_2 + from, thislen);
+ spin_unlock(&vmax301_spin);
+ to += thislen;
+ from += thislen;
+ len -= thislen;
+ }
+}
+
+static void vmax301_write8(struct map_info *map, map_word d, unsigned long adr)
+{
+ spin_lock(&vmax301_spin);
+ vmax301_page(map, adr);
+ writeb(d.x[0], map->map_priv_2 + (adr & WINDOW_MASK));
+ spin_unlock(&vmax301_spin);
+}
+
+static void vmax301_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len)
+{
+ while(len) {
+ unsigned long thislen = len;
+ if (len > (WINDOW_LENGTH - (to & WINDOW_MASK)))
+ thislen = WINDOW_LENGTH-(to & WINDOW_MASK);
+
+ spin_lock(&vmax301_spin);
+ vmax301_page(map, to);
+ memcpy_toio(map->map_priv_2 + to, from, thislen);
+ spin_unlock(&vmax301_spin);
+ to += thislen;
+ from += thislen;
+ len -= thislen;
+ }
+}
+
+static struct map_info vmax_map[2] = {
+ {
+ .name = "VMAX301 Internal Flash",
+ .phys = NO_XIP,
+ .size = 3*2*1024*1024,
+ .bankwidth = 1,
+ .read = vmax301_read8,
+ .copy_from = vmax301_copy_from,
+ .write = vmax301_write8,
+ .copy_to = vmax301_copy_to,
+ .map_priv_1 = WINDOW_START + WINDOW_LENGTH,
+ .map_priv_2 = 0xFFFFFFFF
+ },
+ {
+ .name = "VMAX301 Socket",
+ .phys = NO_XIP,
+ .size = 0,
+ .bankwidth = 1,
+ .read = vmax301_read8,
+ .copy_from = vmax301_copy_from,
+ .write = vmax301_write8,
+ .copy_to = vmax301_copy_to,
+ .map_priv_1 = WINDOW_START + (3*WINDOW_LENGTH),
+ .map_priv_2 = 0xFFFFFFFF
+ }
+};
+
+static struct mtd_info *vmax_mtd[2] = {NULL, NULL};
+
+static void __exit cleanup_vmax301(void)
+{
+ int i;
+
+ for (i=0; i<2; i++) {
+ if (vmax_mtd[i]) {
+ del_mtd_device(vmax_mtd[i]);
+ map_destroy(vmax_mtd[i]);
+ }
+ }
+ iounmap((void *)vmax_map[0].map_priv_1 - WINDOW_START);
+}
+
+int __init init_vmax301(void)
+{
+ int i;
+ unsigned long iomapadr;
+ // Print out our little header..
+ printk("Tempustech VMAX 301 MEM:0x%x-0x%x\n",WINDOW_START,
+ WINDOW_START+4*WINDOW_LENGTH);
+
+ iomapadr = (unsigned long)ioremap(WINDOW_START, WINDOW_LENGTH*4);
+ if (!iomapadr) {
+ printk("Failed to ioremap memory region\n");
+ return -EIO;
+ }
+ /* Put the address in the map's private data area.
+ We store the actual MTD IO address rather than the
+ address of the first half, because it's used more
+ often.
+ */
+ vmax_map[0].map_priv_2 = iomapadr + WINDOW_START;
+ vmax_map[1].map_priv_2 = iomapadr + (3*WINDOW_START);
+
+ for (i=0; i<2; i++) {
+ vmax_mtd[i] = do_map_probe("cfi_probe", &vmax_map[i]);
+ if (!vmax_mtd[i])
+ vmax_mtd[i] = do_map_probe("jedec", &vmax_map[i]);
+ if (!vmax_mtd[i])
+ vmax_mtd[i] = do_map_probe("map_ram", &vmax_map[i]);
+ if (!vmax_mtd[i])
+ vmax_mtd[i] = do_map_probe("map_rom", &vmax_map[i]);
+ if (vmax_mtd[i]) {
+ vmax_mtd[i]->owner = THIS_MODULE;
+ add_mtd_device(vmax_mtd[i]);
+ }
+ }
+
+ if (!vmax_mtd[1] && !vmax_mtd[2]) {
+ iounmap((void *)iomapadr);
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+module_init(init_vmax301);
+module_exit(cleanup_vmax301);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("MTD map driver for Tempustech VMAX SBC301 board");
diff --git a/drivers/mtd/maps/walnut.c b/drivers/mtd/maps/walnut.c
new file mode 100644
index 000000000000..d6137b1b5670
--- /dev/null
+++ b/drivers/mtd/maps/walnut.c
@@ -0,0 +1,122 @@
+/*
+ * $Id: walnut.c,v 1.2 2004/12/10 12:07:42 holindho Exp $
+ *
+ * Mapping for Walnut flash
+ * (used ebony.c as a "framework")
+ *
+ * Heikki Lindholm <holindho@infradead.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/mtd/partitions.h>
+#include <linux/config.h>
+#include <linux/version.h>
+#include <asm/io.h>
+#include <asm/ibm4xx.h>
+#include <platforms/4xx/walnut.h>
+
+/* these should be in platforms/4xx/walnut.h ? */
+#define WALNUT_FLASH_ONBD_N(x) (x & 0x02)
+#define WALNUT_FLASH_SRAM_SEL(x) (x & 0x01)
+#define WALNUT_FLASH_LOW 0xFFF00000
+#define WALNUT_FLASH_HIGH 0xFFF80000
+#define WALNUT_FLASH_SIZE 0x80000
+
+static struct mtd_info *flash;
+
+static struct map_info walnut_map = {
+ .name = "Walnut flash",
+ .size = WALNUT_FLASH_SIZE,
+ .bankwidth = 1,
+};
+
+/* Actually, OpenBIOS is the last 128 KiB of the flash - better
+ * partitioning could be made */
+static struct mtd_partition walnut_partitions[] = {
+ {
+ .name = "OpenBIOS",
+ .offset = 0x0,
+ .size = WALNUT_FLASH_SIZE,
+ /*.mask_flags = MTD_WRITEABLE, */ /* force read-only */
+ }
+};
+
+int __init init_walnut(void)
+{
+ u8 fpga_brds1;
+ void *fpga_brds1_adr;
+ void *fpga_status_adr;
+ unsigned long flash_base;
+
+ /* this should already be mapped (platform/4xx/walnut.c) */
+ fpga_status_adr = ioremap(WALNUT_FPGA_BASE, 8);
+ if (!fpga_status_adr)
+ return -ENOMEM;
+
+ fpga_brds1_adr = fpga_status_adr+5;
+ fpga_brds1 = readb(fpga_brds1_adr);
+ /* iounmap(fpga_status_adr); */
+
+ if (WALNUT_FLASH_ONBD_N(fpga_brds1)) {
+ printk("The on-board flash is disabled (U79 sw 5)!");
+ return -EIO;
+ }
+ if (WALNUT_FLASH_SRAM_SEL(fpga_brds1))
+ flash_base = WALNUT_FLASH_LOW;
+ else
+ flash_base = WALNUT_FLASH_HIGH;
+
+ walnut_map.phys = flash_base;
+ walnut_map.virt =
+ (void __iomem *)ioremap(flash_base, walnut_map.size);
+
+ if (!walnut_map.virt) {
+ printk("Failed to ioremap flash.\n");
+ return -EIO;
+ }
+
+ simple_map_init(&walnut_map);
+
+ flash = do_map_probe("jedec_probe", &walnut_map);
+ if (flash) {
+ flash->owner = THIS_MODULE;
+ add_mtd_partitions(flash, walnut_partitions,
+ ARRAY_SIZE(walnut_partitions));
+ } else {
+ printk("map probe failed for flash\n");
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static void __exit cleanup_walnut(void)
+{
+ if (flash) {
+ del_mtd_partitions(flash);
+ map_destroy(flash);
+ }
+
+ if (walnut_map.virt) {
+ iounmap((void *)walnut_map.virt);
+ walnut_map.virt = 0;
+ }
+}
+
+module_init(init_walnut);
+module_exit(cleanup_walnut);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Heikki Lindholm <holindho@infradead.org>");
+MODULE_DESCRIPTION("MTD map and partitions for IBM 405GP Walnut boards");
diff --git a/drivers/mtd/maps/wr_sbc82xx_flash.c b/drivers/mtd/maps/wr_sbc82xx_flash.c
new file mode 100644
index 000000000000..82b887b05707
--- /dev/null
+++ b/drivers/mtd/maps/wr_sbc82xx_flash.c
@@ -0,0 +1,181 @@
+/*
+ * $Id: wr_sbc82xx_flash.c,v 1.7 2004/11/04 13:24:15 gleixner Exp $
+ *
+ * Map for flash chips on Wind River PowerQUICC II SBC82xx board.
+ *
+ * Copyright (C) 2004 Red Hat, Inc.
+ *
+ * Author: David Woodhouse <dwmw2@infradead.org>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <asm/io.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/map.h>
+#include <linux/config.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/immap_cpm2.h>
+
+static struct mtd_info *sbcmtd[3];
+static struct mtd_partition *sbcmtd_parts[3];
+
+struct map_info sbc82xx_flash_map[3] = {
+ {.name = "Boot flash"},
+ {.name = "Alternate boot flash"},
+ {.name = "User flash"}
+};
+
+static struct mtd_partition smallflash_parts[] = {
+ {
+ .name = "space",
+ .size = 0x100000,
+ .offset = 0,
+ }, {
+ .name = "bootloader",
+ .size = MTDPART_SIZ_FULL,
+ .offset = MTDPART_OFS_APPEND,
+ }
+};
+
+static struct mtd_partition bigflash_parts[] = {
+ {
+ .name = "bootloader",
+ .size = 0x00100000,
+ .offset = 0,
+ }, {
+ .name = "file system",
+ .size = 0x01f00000,
+ .offset = MTDPART_OFS_APPEND,
+ }, {
+ .name = "boot config",
+ .size = 0x00100000,
+ .offset = MTDPART_OFS_APPEND,
+ }, {
+ .name = "space",
+ .size = 0x01f00000,
+ .offset = MTDPART_OFS_APPEND,
+ }
+};
+
+static const char *part_probes[] __initdata = {"cmdlinepart", "RedBoot", NULL};
+
+#define init_sbc82xx_one_flash(map, br, or) \
+do { \
+ (map).phys = (br & 1) ? (br & 0xffff8000) : 0; \
+ (map).size = (br & 1) ? (~(or & 0xffff8000) + 1) : 0; \
+ switch (br & 0x00001800) { \
+ case 0x00000000: \
+ case 0x00000800: (map).bankwidth = 1; break; \
+ case 0x00001000: (map).bankwidth = 2; break; \
+ case 0x00001800: (map).bankwidth = 4; break; \
+ } \
+} while (0);
+
+int __init init_sbc82xx_flash(void)
+{
+ volatile memctl_cpm2_t *mc = &cpm2_immr->im_memctl;
+ int bigflash;
+ int i;
+
+#ifdef CONFIG_SBC8560
+ mc = ioremap(0xff700000 + 0x5000, sizeof(memctl_cpm2_t));
+#else
+ mc = &cpm2_immr->im_memctl;
+#endif
+
+ bigflash = 1;
+ if ((mc->memc_br0 & 0x00001800) == 0x00001800)
+ bigflash = 0;
+
+ init_sbc82xx_one_flash(sbc82xx_flash_map[0], mc->memc_br0, mc->memc_or0);
+ init_sbc82xx_one_flash(sbc82xx_flash_map[1], mc->memc_br6, mc->memc_or6);
+ init_sbc82xx_one_flash(sbc82xx_flash_map[2], mc->memc_br1, mc->memc_or1);
+
+#ifdef CONFIG_SBC8560
+ iounmap((void *) mc);
+#endif
+
+ for (i=0; i<3; i++) {
+ int8_t flashcs[3] = { 0, 6, 1 };
+ int nr_parts;
+
+ printk(KERN_NOTICE "PowerQUICC II %s (%ld MiB on CS%d",
+ sbc82xx_flash_map[i].name,
+ (sbc82xx_flash_map[i].size >> 20),
+ flashcs[i]);
+ if (!sbc82xx_flash_map[i].phys) {
+ /* We know it can't be at zero. */
+ printk("): disabled by bootloader.\n");
+ continue;
+ }
+ printk(" at %08lx)\n", sbc82xx_flash_map[i].phys);
+
+ sbc82xx_flash_map[i].virt = ioremap(sbc82xx_flash_map[i].phys, sbc82xx_flash_map[i].size);
+
+ if (!sbc82xx_flash_map[i].virt) {
+ printk("Failed to ioremap\n");
+ continue;
+ }
+
+ simple_map_init(&sbc82xx_flash_map[i]);
+
+ sbcmtd[i] = do_map_probe("cfi_probe", &sbc82xx_flash_map[i]);
+
+ if (!sbcmtd[i])
+ continue;
+
+ sbcmtd[i]->owner = THIS_MODULE;
+
+ nr_parts = parse_mtd_partitions(sbcmtd[i], part_probes,
+ &sbcmtd_parts[i], 0);
+ if (nr_parts > 0) {
+ add_mtd_partitions (sbcmtd[i], sbcmtd_parts[i], nr_parts);
+ continue;
+ }
+
+ /* No partitioning detected. Use default */
+ if (i == 2) {
+ add_mtd_device(sbcmtd[i]);
+ } else if (i == bigflash) {
+ add_mtd_partitions (sbcmtd[i], bigflash_parts, ARRAY_SIZE(bigflash_parts));
+ } else {
+ add_mtd_partitions (sbcmtd[i], smallflash_parts, ARRAY_SIZE(smallflash_parts));
+ }
+ }
+ return 0;
+}
+
+static void __exit cleanup_sbc82xx_flash(void)
+{
+ int i;
+
+ for (i=0; i<3; i++) {
+ if (!sbcmtd[i])
+ continue;
+
+ if (i<2 || sbcmtd_parts[i])
+ del_mtd_partitions(sbcmtd[i]);
+ else
+ del_mtd_device(sbcmtd[i]);
+
+ kfree(sbcmtd_parts[i]);
+ map_destroy(sbcmtd[i]);
+
+ iounmap((void *)sbc82xx_flash_map[i].virt);
+ sbc82xx_flash_map[i].virt = 0;
+ }
+}
+
+module_init(init_sbc82xx_flash);
+module_exit(cleanup_sbc82xx_flash);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("Flash map driver for WindRiver PowerQUICC II");
diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c
new file mode 100644
index 000000000000..f8d2185819e7
--- /dev/null
+++ b/drivers/mtd/mtd_blkdevs.c
@@ -0,0 +1,478 @@
+/*
+ * $Id: mtd_blkdevs.c,v 1.24 2004/11/16 18:28:59 dwmw2 Exp $
+ *
+ * (C) 2003 David Woodhouse <dwmw2@infradead.org>
+ *
+ * Interface to Linux 2.5 block layer for MTD 'translation layers'.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/fs.h>
+#include <linux/mtd/blktrans.h>
+#include <linux/mtd/mtd.h>
+#include <linux/blkdev.h>
+#include <linux/blkpg.h>
+#include <linux/spinlock.h>
+#include <linux/hdreg.h>
+#include <linux/init.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+#include <linux/devfs_fs_kernel.h>
+
+static LIST_HEAD(blktrans_majors);
+
+extern struct semaphore mtd_table_mutex;
+extern struct mtd_info *mtd_table[];
+
+struct mtd_blkcore_priv {
+ struct completion thread_dead;
+ int exiting;
+ wait_queue_head_t thread_wq;
+ struct request_queue *rq;
+ spinlock_t queue_lock;
+};
+
+static int do_blktrans_request(struct mtd_blktrans_ops *tr,
+ struct mtd_blktrans_dev *dev,
+ struct request *req)
+{
+ unsigned long block, nsect;
+ char *buf;
+
+ block = req->sector;
+ nsect = req->current_nr_sectors;
+ buf = req->buffer;
+
+ if (!(req->flags & REQ_CMD))
+ return 0;
+
+ if (block + nsect > get_capacity(req->rq_disk))
+ return 0;
+
+ switch(rq_data_dir(req)) {
+ case READ:
+ for (; nsect > 0; nsect--, block++, buf += 512)
+ if (tr->readsect(dev, block, buf))
+ return 0;
+ return 1;
+
+ case WRITE:
+ if (!tr->writesect)
+ return 0;
+
+ for (; nsect > 0; nsect--, block++, buf += 512)
+ if (tr->writesect(dev, block, buf))
+ return 0;
+ return 1;
+
+ default:
+ printk(KERN_NOTICE "Unknown request %ld\n", rq_data_dir(req));
+ return 0;
+ }
+}
+
+static int mtd_blktrans_thread(void *arg)
+{
+ struct mtd_blktrans_ops *tr = arg;
+ struct request_queue *rq = tr->blkcore_priv->rq;
+
+ /* we might get involved when memory gets low, so use PF_MEMALLOC */
+ current->flags |= PF_MEMALLOC | PF_NOFREEZE;
+
+ daemonize("%sd", tr->name);
+
+ /* daemonize() doesn't do this for us since some kernel threads
+ actually want to deal with signals. We can't just call
+ exit_sighand() since that'll cause an oops when we finally
+ do exit. */
+ spin_lock_irq(&current->sighand->siglock);
+ sigfillset(&current->blocked);
+ recalc_sigpending();
+ spin_unlock_irq(&current->sighand->siglock);
+
+ spin_lock_irq(rq->queue_lock);
+
+ while (!tr->blkcore_priv->exiting) {
+ struct request *req;
+ struct mtd_blktrans_dev *dev;
+ int res = 0;
+ DECLARE_WAITQUEUE(wait, current);
+
+ req = elv_next_request(rq);
+
+ if (!req) {
+ add_wait_queue(&tr->blkcore_priv->thread_wq, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ spin_unlock_irq(rq->queue_lock);
+
+ schedule();
+ remove_wait_queue(&tr->blkcore_priv->thread_wq, &wait);
+
+ spin_lock_irq(rq->queue_lock);
+
+ continue;
+ }
+
+ dev = req->rq_disk->private_data;
+ tr = dev->tr;
+
+ spin_unlock_irq(rq->queue_lock);
+
+ down(&dev->sem);
+ res = do_blktrans_request(tr, dev, req);
+ up(&dev->sem);
+
+ spin_lock_irq(rq->queue_lock);
+
+ end_request(req, res);
+ }
+ spin_unlock_irq(rq->queue_lock);
+
+ complete_and_exit(&tr->blkcore_priv->thread_dead, 0);
+}
+
+static void mtd_blktrans_request(struct request_queue *rq)
+{
+ struct mtd_blktrans_ops *tr = rq->queuedata;
+ wake_up(&tr->blkcore_priv->thread_wq);
+}
+
+
+static int blktrans_open(struct inode *i, struct file *f)
+{
+ struct mtd_blktrans_dev *dev;
+ struct mtd_blktrans_ops *tr;
+ int ret = -ENODEV;
+
+ dev = i->i_bdev->bd_disk->private_data;
+ tr = dev->tr;
+
+ if (!try_module_get(dev->mtd->owner))
+ goto out;
+
+ if (!try_module_get(tr->owner))
+ goto out_tr;
+
+ /* FIXME: Locking. A hot pluggable device can go away
+ (del_mtd_device can be called for it) without its module
+ being unloaded. */
+ dev->mtd->usecount++;
+
+ ret = 0;
+ if (tr->open && (ret = tr->open(dev))) {
+ dev->mtd->usecount--;
+ module_put(dev->mtd->owner);
+ out_tr:
+ module_put(tr->owner);
+ }
+ out:
+ return ret;
+}
+
+static int blktrans_release(struct inode *i, struct file *f)
+{
+ struct mtd_blktrans_dev *dev;
+ struct mtd_blktrans_ops *tr;
+ int ret = 0;
+
+ dev = i->i_bdev->bd_disk->private_data;
+ tr = dev->tr;
+
+ if (tr->release)
+ ret = tr->release(dev);
+
+ if (!ret) {
+ dev->mtd->usecount--;
+ module_put(dev->mtd->owner);
+ module_put(tr->owner);
+ }
+
+ return ret;
+}
+
+
+static int blktrans_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct mtd_blktrans_dev *dev = inode->i_bdev->bd_disk->private_data;
+ struct mtd_blktrans_ops *tr = dev->tr;
+
+ switch (cmd) {
+ case BLKFLSBUF:
+ if (tr->flush)
+ return tr->flush(dev);
+ /* The core code did the work, we had nothing to do. */
+ return 0;
+
+ case HDIO_GETGEO:
+ if (tr->getgeo) {
+ struct hd_geometry g;
+ int ret;
+
+ memset(&g, 0, sizeof(g));
+ ret = tr->getgeo(dev, &g);
+ if (ret)
+ return ret;
+
+ g.start = get_start_sect(inode->i_bdev);
+ if (copy_to_user((void __user *)arg, &g, sizeof(g)))
+ return -EFAULT;
+ return 0;
+ } /* else */
+ default:
+ return -ENOTTY;
+ }
+}
+
+struct block_device_operations mtd_blktrans_ops = {
+ .owner = THIS_MODULE,
+ .open = blktrans_open,
+ .release = blktrans_release,
+ .ioctl = blktrans_ioctl,
+};
+
+int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
+{
+ struct mtd_blktrans_ops *tr = new->tr;
+ struct list_head *this;
+ int last_devnum = -1;
+ struct gendisk *gd;
+
+ if (!down_trylock(&mtd_table_mutex)) {
+ up(&mtd_table_mutex);
+ BUG();
+ }
+
+ list_for_each(this, &tr->devs) {
+ struct mtd_blktrans_dev *d = list_entry(this, struct mtd_blktrans_dev, list);
+ if (new->devnum == -1) {
+ /* Use first free number */
+ if (d->devnum != last_devnum+1) {
+ /* Found a free devnum. Plug it in here */
+ new->devnum = last_devnum+1;
+ list_add_tail(&new->list, &d->list);
+ goto added;
+ }
+ } else if (d->devnum == new->devnum) {
+ /* Required number taken */
+ return -EBUSY;
+ } else if (d->devnum > new->devnum) {
+ /* Required number was free */
+ list_add_tail(&new->list, &d->list);
+ goto added;
+ }
+ last_devnum = d->devnum;
+ }
+ if (new->devnum == -1)
+ new->devnum = last_devnum+1;
+
+ if ((new->devnum << tr->part_bits) > 256) {
+ return -EBUSY;
+ }
+
+ init_MUTEX(&new->sem);
+ list_add_tail(&new->list, &tr->devs);
+ added:
+ if (!tr->writesect)
+ new->readonly = 1;
+
+ gd = alloc_disk(1 << tr->part_bits);
+ if (!gd) {
+ list_del(&new->list);
+ return -ENOMEM;
+ }
+ gd->major = tr->major;
+ gd->first_minor = (new->devnum) << tr->part_bits;
+ gd->fops = &mtd_blktrans_ops;
+
+ snprintf(gd->disk_name, sizeof(gd->disk_name),
+ "%s%c", tr->name, (tr->part_bits?'a':'0') + new->devnum);
+ snprintf(gd->devfs_name, sizeof(gd->devfs_name),
+ "%s/%c", tr->name, (tr->part_bits?'a':'0') + new->devnum);
+
+ /* 2.5 has capacity in units of 512 bytes while still
+ having BLOCK_SIZE_BITS set to 10. Just to keep us amused. */
+ set_capacity(gd, (new->size * new->blksize) >> 9);
+
+ gd->private_data = new;
+ new->blkcore_priv = gd;
+ gd->queue = tr->blkcore_priv->rq;
+
+ if (new->readonly)
+ set_disk_ro(gd, 1);
+
+ add_disk(gd);
+
+ return 0;
+}
+
+int del_mtd_blktrans_dev(struct mtd_blktrans_dev *old)
+{
+ if (!down_trylock(&mtd_table_mutex)) {
+ up(&mtd_table_mutex);
+ BUG();
+ }
+
+ list_del(&old->list);
+
+ del_gendisk(old->blkcore_priv);
+ put_disk(old->blkcore_priv);
+
+ return 0;
+}
+
+static void blktrans_notify_remove(struct mtd_info *mtd)
+{
+ struct list_head *this, *this2, *next;
+
+ list_for_each(this, &blktrans_majors) {
+ struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list);
+
+ list_for_each_safe(this2, next, &tr->devs) {
+ struct mtd_blktrans_dev *dev = list_entry(this2, struct mtd_blktrans_dev, list);
+
+ if (dev->mtd == mtd)
+ tr->remove_dev(dev);
+ }
+ }
+}
+
+static void blktrans_notify_add(struct mtd_info *mtd)
+{
+ struct list_head *this;
+
+ if (mtd->type == MTD_ABSENT)
+ return;
+
+ list_for_each(this, &blktrans_majors) {
+ struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list);
+
+ tr->add_mtd(tr, mtd);
+ }
+
+}
+
+static struct mtd_notifier blktrans_notifier = {
+ .add = blktrans_notify_add,
+ .remove = blktrans_notify_remove,
+};
+
+int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
+{
+ int ret, i;
+
+ /* Register the notifier if/when the first device type is
+ registered, to prevent the link/init ordering from fucking
+ us over. */
+ if (!blktrans_notifier.list.next)
+ register_mtd_user(&blktrans_notifier);
+
+ tr->blkcore_priv = kmalloc(sizeof(*tr->blkcore_priv), GFP_KERNEL);
+ if (!tr->blkcore_priv)
+ return -ENOMEM;
+
+ memset(tr->blkcore_priv, 0, sizeof(*tr->blkcore_priv));
+
+ down(&mtd_table_mutex);
+
+ ret = register_blkdev(tr->major, tr->name);
+ if (ret) {
+ printk(KERN_WARNING "Unable to register %s block device on major %d: %d\n",
+ tr->name, tr->major, ret);
+ kfree(tr->blkcore_priv);
+ up(&mtd_table_mutex);
+ return ret;
+ }
+ spin_lock_init(&tr->blkcore_priv->queue_lock);
+ init_completion(&tr->blkcore_priv->thread_dead);
+ init_waitqueue_head(&tr->blkcore_priv->thread_wq);
+
+ tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);
+ if (!tr->blkcore_priv->rq) {
+ unregister_blkdev(tr->major, tr->name);
+ kfree(tr->blkcore_priv);
+ up(&mtd_table_mutex);
+ return -ENOMEM;
+ }
+
+ tr->blkcore_priv->rq->queuedata = tr;
+
+ ret = kernel_thread(mtd_blktrans_thread, tr, CLONE_KERNEL);
+ if (ret < 0) {
+ blk_cleanup_queue(tr->blkcore_priv->rq);
+ unregister_blkdev(tr->major, tr->name);
+ kfree(tr->blkcore_priv);
+ up(&mtd_table_mutex);
+ return ret;
+ }
+
+ devfs_mk_dir(tr->name);
+
+ INIT_LIST_HEAD(&tr->devs);
+ list_add(&tr->list, &blktrans_majors);
+
+ for (i=0; i<MAX_MTD_DEVICES; i++) {
+ if (mtd_table[i] && mtd_table[i]->type != MTD_ABSENT)
+ tr->add_mtd(tr, mtd_table[i]);
+ }
+
+ up(&mtd_table_mutex);
+
+ return 0;
+}
+
+int deregister_mtd_blktrans(struct mtd_blktrans_ops *tr)
+{
+ struct list_head *this, *next;
+
+ down(&mtd_table_mutex);
+
+ /* Clean up the kernel thread */
+ tr->blkcore_priv->exiting = 1;
+ wake_up(&tr->blkcore_priv->thread_wq);
+ wait_for_completion(&tr->blkcore_priv->thread_dead);
+
+ /* Remove it from the list of active majors */
+ list_del(&tr->list);
+
+ list_for_each_safe(this, next, &tr->devs) {
+ struct mtd_blktrans_dev *dev = list_entry(this, struct mtd_blktrans_dev, list);
+ tr->remove_dev(dev);
+ }
+
+ devfs_remove(tr->name);
+ blk_cleanup_queue(tr->blkcore_priv->rq);
+ unregister_blkdev(tr->major, tr->name);
+
+ up(&mtd_table_mutex);
+
+ kfree(tr->blkcore_priv);
+
+ if (!list_empty(&tr->devs))
+ BUG();
+ return 0;
+}
+
+static void __exit mtd_blktrans_exit(void)
+{
+ /* No race here -- if someone's currently in register_mtd_blktrans
+ we're screwed anyway. */
+ if (blktrans_notifier.list.next)
+ unregister_mtd_user(&blktrans_notifier);
+}
+
+module_exit(mtd_blktrans_exit);
+
+EXPORT_SYMBOL_GPL(register_mtd_blktrans);
+EXPORT_SYMBOL_GPL(deregister_mtd_blktrans);
+EXPORT_SYMBOL_GPL(add_mtd_blktrans_dev);
+EXPORT_SYMBOL_GPL(del_mtd_blktrans_dev);
+
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Common interface to block layer for MTD 'translation layers'");
diff --git a/drivers/mtd/mtdblock.c b/drivers/mtd/mtdblock.c
new file mode 100644
index 000000000000..b7c32c242bc7
--- /dev/null
+++ b/drivers/mtd/mtdblock.c
@@ -0,0 +1,394 @@
+/*
+ * Direct MTD block device access
+ *
+ * $Id: mtdblock.c,v 1.66 2004/11/25 13:52:52 joern Exp $
+ *
+ * (C) 2000-2003 Nicolas Pitre <nico@cam.org>
+ * (C) 1999-2003 David Woodhouse <dwmw2@infradead.org>
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/blktrans.h>
+
+static struct mtdblk_dev {
+ struct mtd_info *mtd;
+ int count;
+ struct semaphore cache_sem;
+ unsigned char *cache_data;
+ unsigned long cache_offset;
+ unsigned int cache_size;
+ enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state;
+} *mtdblks[MAX_MTD_DEVICES];
+
+/*
+ * Cache stuff...
+ *
+ * Since typical flash erasable sectors are much larger than what Linux's
+ * buffer cache can handle, we must implement read-modify-write on flash
+ * sectors for each block write requests. To avoid over-erasing flash sectors
+ * and to speed things up, we locally cache a whole flash sector while it is
+ * being written to until a different sector is required.
+ */
+
+static void erase_callback(struct erase_info *done)
+{
+ wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv;
+ wake_up(wait_q);
+}
+
+static int erase_write (struct mtd_info *mtd, unsigned long pos,
+ int len, const char *buf)
+{
+ struct erase_info erase;
+ DECLARE_WAITQUEUE(wait, current);
+ wait_queue_head_t wait_q;
+ size_t retlen;
+ int ret;
+
+ /*
+ * First, let's erase the flash block.
+ */
+
+ init_waitqueue_head(&wait_q);
+ erase.mtd = mtd;
+ erase.callback = erase_callback;
+ erase.addr = pos;
+ erase.len = len;
+ erase.priv = (u_long)&wait_q;
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&wait_q, &wait);
+
+ ret = MTD_ERASE(mtd, &erase);
+ if (ret) {
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&wait_q, &wait);
+ printk (KERN_WARNING "mtdblock: erase of region [0x%lx, 0x%x] "
+ "on \"%s\" failed\n",
+ pos, len, mtd->name);
+ return ret;
+ }
+
+ schedule(); /* Wait for erase to finish. */
+ remove_wait_queue(&wait_q, &wait);
+
+ /*
+ * Next, writhe data to flash.
+ */
+
+ ret = MTD_WRITE (mtd, pos, len, &retlen, buf);
+ if (ret)
+ return ret;
+ if (retlen != len)
+ return -EIO;
+ return 0;
+}
+
+
+static int write_cached_data (struct mtdblk_dev *mtdblk)
+{
+ struct mtd_info *mtd = mtdblk->mtd;
+ int ret;
+
+ if (mtdblk->cache_state != STATE_DIRTY)
+ return 0;
+
+ DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: writing cached data for \"%s\" "
+ "at 0x%lx, size 0x%x\n", mtd->name,
+ mtdblk->cache_offset, mtdblk->cache_size);
+
+ ret = erase_write (mtd, mtdblk->cache_offset,
+ mtdblk->cache_size, mtdblk->cache_data);
+ if (ret)
+ return ret;
+
+ /*
+ * Here we could argubly set the cache state to STATE_CLEAN.
+ * However this could lead to inconsistency since we will not
+ * be notified if this content is altered on the flash by other
+ * means. Let's declare it empty and leave buffering tasks to
+ * the buffer cache instead.
+ */
+ mtdblk->cache_state = STATE_EMPTY;
+ return 0;
+}
+
+
+static int do_cached_write (struct mtdblk_dev *mtdblk, unsigned long pos,
+ int len, const char *buf)
+{
+ struct mtd_info *mtd = mtdblk->mtd;
+ unsigned int sect_size = mtdblk->cache_size;
+ size_t retlen;
+ int ret;
+
+ DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: write on \"%s\" at 0x%lx, size 0x%x\n",
+ mtd->name, pos, len);
+
+ if (!sect_size)
+ return MTD_WRITE (mtd, pos, len, &retlen, buf);
+
+ while (len > 0) {
+ unsigned long sect_start = (pos/sect_size)*sect_size;
+ unsigned int offset = pos - sect_start;
+ unsigned int size = sect_size - offset;
+ if( size > len )
+ size = len;
+
+ if (size == sect_size) {
+ /*
+ * We are covering a whole sector. Thus there is no
+ * need to bother with the cache while it may still be
+ * useful for other partial writes.
+ */
+ ret = erase_write (mtd, pos, size, buf);
+ if (ret)
+ return ret;
+ } else {
+ /* Partial sector: need to use the cache */
+
+ if (mtdblk->cache_state == STATE_DIRTY &&
+ mtdblk->cache_offset != sect_start) {
+ ret = write_cached_data(mtdblk);
+ if (ret)
+ return ret;
+ }
+
+ if (mtdblk->cache_state == STATE_EMPTY ||
+ mtdblk->cache_offset != sect_start) {
+ /* fill the cache with the current sector */
+ mtdblk->cache_state = STATE_EMPTY;
+ ret = MTD_READ(mtd, sect_start, sect_size, &retlen, mtdblk->cache_data);
+ if (ret)
+ return ret;
+ if (retlen != sect_size)
+ return -EIO;
+
+ mtdblk->cache_offset = sect_start;
+ mtdblk->cache_size = sect_size;
+ mtdblk->cache_state = STATE_CLEAN;
+ }
+
+ /* write data to our local cache */
+ memcpy (mtdblk->cache_data + offset, buf, size);
+ mtdblk->cache_state = STATE_DIRTY;
+ }
+
+ buf += size;
+ pos += size;
+ len -= size;
+ }
+
+ return 0;
+}
+
+
+static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos,
+ int len, char *buf)
+{
+ struct mtd_info *mtd = mtdblk->mtd;
+ unsigned int sect_size = mtdblk->cache_size;
+ size_t retlen;
+ int ret;
+
+ DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: read on \"%s\" at 0x%lx, size 0x%x\n",
+ mtd->name, pos, len);
+
+ if (!sect_size)
+ return MTD_READ (mtd, pos, len, &retlen, buf);
+
+ while (len > 0) {
+ unsigned long sect_start = (pos/sect_size)*sect_size;
+ unsigned int offset = pos - sect_start;
+ unsigned int size = sect_size - offset;
+ if (size > len)
+ size = len;
+
+ /*
+ * Check if the requested data is already cached
+ * Read the requested amount of data from our internal cache if it
+ * contains what we want, otherwise we read the data directly
+ * from flash.
+ */
+ if (mtdblk->cache_state != STATE_EMPTY &&
+ mtdblk->cache_offset == sect_start) {
+ memcpy (buf, mtdblk->cache_data + offset, size);
+ } else {
+ ret = MTD_READ (mtd, pos, size, &retlen, buf);
+ if (ret)
+ return ret;
+ if (retlen != size)
+ return -EIO;
+ }
+
+ buf += size;
+ pos += size;
+ len -= size;
+ }
+
+ return 0;
+}
+
+static int mtdblock_readsect(struct mtd_blktrans_dev *dev,
+ unsigned long block, char *buf)
+{
+ struct mtdblk_dev *mtdblk = mtdblks[dev->devnum];
+ return do_cached_read(mtdblk, block<<9, 512, buf);
+}
+
+static int mtdblock_writesect(struct mtd_blktrans_dev *dev,
+ unsigned long block, char *buf)
+{
+ struct mtdblk_dev *mtdblk = mtdblks[dev->devnum];
+ if (unlikely(!mtdblk->cache_data && mtdblk->cache_size)) {
+ mtdblk->cache_data = vmalloc(mtdblk->mtd->erasesize);
+ if (!mtdblk->cache_data)
+ return -EINTR;
+ /* -EINTR is not really correct, but it is the best match
+ * documented in man 2 write for all cases. We could also
+ * return -EAGAIN sometimes, but why bother?
+ */
+ }
+ return do_cached_write(mtdblk, block<<9, 512, buf);
+}
+
+static int mtdblock_open(struct mtd_blktrans_dev *mbd)
+{
+ struct mtdblk_dev *mtdblk;
+ struct mtd_info *mtd = mbd->mtd;
+ int dev = mbd->devnum;
+
+ DEBUG(MTD_DEBUG_LEVEL1,"mtdblock_open\n");
+
+ if (mtdblks[dev]) {
+ mtdblks[dev]->count++;
+ return 0;
+ }
+
+ /* OK, it's not open. Create cache info for it */
+ mtdblk = kmalloc(sizeof(struct mtdblk_dev), GFP_KERNEL);
+ if (!mtdblk)
+ return -ENOMEM;
+
+ memset(mtdblk, 0, sizeof(*mtdblk));
+ mtdblk->count = 1;
+ mtdblk->mtd = mtd;
+
+ init_MUTEX (&mtdblk->cache_sem);
+ mtdblk->cache_state = STATE_EMPTY;
+ if ((mtdblk->mtd->flags & MTD_CAP_RAM) != MTD_CAP_RAM &&
+ mtdblk->mtd->erasesize) {
+ mtdblk->cache_size = mtdblk->mtd->erasesize;
+ mtdblk->cache_data = NULL;
+ }
+
+ mtdblks[dev] = mtdblk;
+
+ DEBUG(MTD_DEBUG_LEVEL1, "ok\n");
+
+ return 0;
+}
+
+static int mtdblock_release(struct mtd_blktrans_dev *mbd)
+{
+ int dev = mbd->devnum;
+ struct mtdblk_dev *mtdblk = mtdblks[dev];
+
+ DEBUG(MTD_DEBUG_LEVEL1, "mtdblock_release\n");
+
+ down(&mtdblk->cache_sem);
+ write_cached_data(mtdblk);
+ up(&mtdblk->cache_sem);
+
+ if (!--mtdblk->count) {
+ /* It was the last usage. Free the device */
+ mtdblks[dev] = NULL;
+ if (mtdblk->mtd->sync)
+ mtdblk->mtd->sync(mtdblk->mtd);
+ vfree(mtdblk->cache_data);
+ kfree(mtdblk);
+ }
+ DEBUG(MTD_DEBUG_LEVEL1, "ok\n");
+
+ return 0;
+}
+
+static int mtdblock_flush(struct mtd_blktrans_dev *dev)
+{
+ struct mtdblk_dev *mtdblk = mtdblks[dev->devnum];
+
+ down(&mtdblk->cache_sem);
+ write_cached_data(mtdblk);
+ up(&mtdblk->cache_sem);
+
+ if (mtdblk->mtd->sync)
+ mtdblk->mtd->sync(mtdblk->mtd);
+ return 0;
+}
+
+static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
+{
+ struct mtd_blktrans_dev *dev = kmalloc(sizeof(*dev), GFP_KERNEL);
+
+ if (!dev)
+ return;
+
+ memset(dev, 0, sizeof(*dev));
+
+ dev->mtd = mtd;
+ dev->devnum = mtd->index;
+ dev->blksize = 512;
+ dev->size = mtd->size >> 9;
+ dev->tr = tr;
+
+ if (!(mtd->flags & MTD_WRITEABLE))
+ dev->readonly = 1;
+
+ add_mtd_blktrans_dev(dev);
+}
+
+static void mtdblock_remove_dev(struct mtd_blktrans_dev *dev)
+{
+ del_mtd_blktrans_dev(dev);
+ kfree(dev);
+}
+
+static struct mtd_blktrans_ops mtdblock_tr = {
+ .name = "mtdblock",
+ .major = 31,
+ .part_bits = 0,
+ .open = mtdblock_open,
+ .flush = mtdblock_flush,
+ .release = mtdblock_release,
+ .readsect = mtdblock_readsect,
+ .writesect = mtdblock_writesect,
+ .add_mtd = mtdblock_add_mtd,
+ .remove_dev = mtdblock_remove_dev,
+ .owner = THIS_MODULE,
+};
+
+static int __init init_mtdblock(void)
+{
+ return register_mtd_blktrans(&mtdblock_tr);
+}
+
+static void __exit cleanup_mtdblock(void)
+{
+ deregister_mtd_blktrans(&mtdblock_tr);
+}
+
+module_init(init_mtdblock);
+module_exit(cleanup_mtdblock);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nicolas Pitre <nico@cam.org> et al.");
+MODULE_DESCRIPTION("Caching read/erase/writeback block device emulation access to MTD devices");
diff --git a/drivers/mtd/mtdblock_ro.c b/drivers/mtd/mtdblock_ro.c
new file mode 100644
index 000000000000..0c830ba41ef0
--- /dev/null
+++ b/drivers/mtd/mtdblock_ro.c
@@ -0,0 +1,87 @@
+/*
+ * $Id: mtdblock_ro.c,v 1.19 2004/11/16 18:28:59 dwmw2 Exp $
+ *
+ * (C) 2003 David Woodhouse <dwmw2@infradead.org>
+ *
+ * Simple read-only (writable only for RAM) mtdblock driver
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/blktrans.h>
+
+static int mtdblock_readsect(struct mtd_blktrans_dev *dev,
+ unsigned long block, char *buf)
+{
+ size_t retlen;
+
+ if (dev->mtd->read(dev->mtd, (block * 512), 512, &retlen, buf))
+ return 1;
+ return 0;
+}
+
+static int mtdblock_writesect(struct mtd_blktrans_dev *dev,
+ unsigned long block, char *buf)
+{
+ size_t retlen;
+
+ if (dev->mtd->write(dev->mtd, (block * 512), 512, &retlen, buf))
+ return 1;
+ return 0;
+}
+
+static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
+{
+ struct mtd_blktrans_dev *dev = kmalloc(sizeof(*dev), GFP_KERNEL);
+
+ if (!dev)
+ return;
+
+ memset(dev, 0, sizeof(*dev));
+
+ dev->mtd = mtd;
+ dev->devnum = mtd->index;
+ dev->blksize = 512;
+ dev->size = mtd->size >> 9;
+ dev->tr = tr;
+ if ((mtd->flags & (MTD_CLEAR_BITS|MTD_SET_BITS|MTD_WRITEABLE)) !=
+ (MTD_CLEAR_BITS|MTD_SET_BITS|MTD_WRITEABLE))
+ dev->readonly = 1;
+
+ add_mtd_blktrans_dev(dev);
+}
+
+static void mtdblock_remove_dev(struct mtd_blktrans_dev *dev)
+{
+ del_mtd_blktrans_dev(dev);
+ kfree(dev);
+}
+
+static struct mtd_blktrans_ops mtdblock_tr = {
+ .name = "mtdblock",
+ .major = 31,
+ .part_bits = 0,
+ .readsect = mtdblock_readsect,
+ .writesect = mtdblock_writesect,
+ .add_mtd = mtdblock_add_mtd,
+ .remove_dev = mtdblock_remove_dev,
+ .owner = THIS_MODULE,
+};
+
+static int __init mtdblock_init(void)
+{
+ return register_mtd_blktrans(&mtdblock_tr);
+}
+
+static void __exit mtdblock_exit(void)
+{
+ deregister_mtd_blktrans(&mtdblock_tr);
+}
+
+module_init(mtdblock_init);
+module_exit(mtdblock_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("Simple read-only block device emulation access to MTD devices");
diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c
new file mode 100644
index 000000000000..510ad78312cc
--- /dev/null
+++ b/drivers/mtd/mtdchar.c
@@ -0,0 +1,562 @@
+/*
+ * $Id: mtdchar.c,v 1.66 2005/01/05 18:05:11 dwmw2 Exp $
+ *
+ * Character-device access to raw MTD devices.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/compatmac.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <asm/uaccess.h>
+
+#ifdef CONFIG_DEVFS_FS
+#include <linux/devfs_fs_kernel.h>
+
+static void mtd_notify_add(struct mtd_info* mtd)
+{
+ if (!mtd)
+ return;
+
+ devfs_mk_cdev(MKDEV(MTD_CHAR_MAJOR, mtd->index*2),
+ S_IFCHR | S_IRUGO | S_IWUGO, "mtd/%d", mtd->index);
+
+ devfs_mk_cdev(MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1),
+ S_IFCHR | S_IRUGO, "mtd/%dro", mtd->index);
+}
+
+static void mtd_notify_remove(struct mtd_info* mtd)
+{
+ if (!mtd)
+ return;
+ devfs_remove("mtd/%d", mtd->index);
+ devfs_remove("mtd/%dro", mtd->index);
+}
+
+static struct mtd_notifier notifier = {
+ .add = mtd_notify_add,
+ .remove = mtd_notify_remove,
+};
+
+static inline void mtdchar_devfs_init(void)
+{
+ devfs_mk_dir("mtd");
+ register_mtd_user(&notifier);
+}
+
+static inline void mtdchar_devfs_exit(void)
+{
+ unregister_mtd_user(&notifier);
+ devfs_remove("mtd");
+}
+#else /* !DEVFS */
+#define mtdchar_devfs_init() do { } while(0)
+#define mtdchar_devfs_exit() do { } while(0)
+#endif
+
+static loff_t mtd_lseek (struct file *file, loff_t offset, int orig)
+{
+ struct mtd_info *mtd = file->private_data;
+
+ switch (orig) {
+ case 0:
+ /* SEEK_SET */
+ file->f_pos = offset;
+ break;
+ case 1:
+ /* SEEK_CUR */
+ file->f_pos += offset;
+ break;
+ case 2:
+ /* SEEK_END */
+ file->f_pos =mtd->size + offset;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (file->f_pos < 0)
+ file->f_pos = 0;
+ else if (file->f_pos >= mtd->size)
+ file->f_pos = mtd->size - 1;
+
+ return file->f_pos;
+}
+
+
+
+static int mtd_open(struct inode *inode, struct file *file)
+{
+ int minor = iminor(inode);
+ int devnum = minor >> 1;
+ struct mtd_info *mtd;
+
+ DEBUG(MTD_DEBUG_LEVEL0, "MTD_open\n");
+
+ if (devnum >= MAX_MTD_DEVICES)
+ return -ENODEV;
+
+ /* You can't open the RO devices RW */
+ if ((file->f_mode & 2) && (minor & 1))
+ return -EACCES;
+
+ mtd = get_mtd_device(NULL, devnum);
+
+ if (!mtd)
+ return -ENODEV;
+
+ if (MTD_ABSENT == mtd->type) {
+ put_mtd_device(mtd);
+ return -ENODEV;
+ }
+
+ file->private_data = mtd;
+
+ /* You can't open it RW if it's not a writeable device */
+ if ((file->f_mode & 2) && !(mtd->flags & MTD_WRITEABLE)) {
+ put_mtd_device(mtd);
+ return -EACCES;
+ }
+
+ return 0;
+} /* mtd_open */
+
+/*====================================================================*/
+
+static int mtd_close(struct inode *inode, struct file *file)
+{
+ struct mtd_info *mtd;
+
+ DEBUG(MTD_DEBUG_LEVEL0, "MTD_close\n");
+
+ mtd = file->private_data;
+
+ if (mtd->sync)
+ mtd->sync(mtd);
+
+ put_mtd_device(mtd);
+
+ return 0;
+} /* mtd_close */
+
+/* FIXME: This _really_ needs to die. In 2.5, we should lock the
+ userspace buffer down and use it directly with readv/writev.
+*/
+#define MAX_KMALLOC_SIZE 0x20000
+
+static ssize_t mtd_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)
+{
+ struct mtd_info *mtd = file->private_data;
+ size_t retlen=0;
+ size_t total_retlen=0;
+ int ret=0;
+ int len;
+ char *kbuf;
+
+ DEBUG(MTD_DEBUG_LEVEL0,"MTD_read\n");
+
+ if (*ppos + count > mtd->size)
+ count = mtd->size - *ppos;
+
+ if (!count)
+ return 0;
+
+ /* FIXME: Use kiovec in 2.5 to lock down the user's buffers
+ and pass them directly to the MTD functions */
+ while (count) {
+ if (count > MAX_KMALLOC_SIZE)
+ len = MAX_KMALLOC_SIZE;
+ else
+ len = count;
+
+ kbuf=kmalloc(len,GFP_KERNEL);
+ if (!kbuf)
+ return -ENOMEM;
+
+ ret = MTD_READ(mtd, *ppos, len, &retlen, kbuf);
+ /* Nand returns -EBADMSG on ecc errors, but it returns
+ * the data. For our userspace tools it is important
+ * to dump areas with ecc errors !
+ * Userspace software which accesses NAND this way
+ * must be aware of the fact that it deals with NAND
+ */
+ if (!ret || (ret == -EBADMSG)) {
+ *ppos += retlen;
+ if (copy_to_user(buf, kbuf, retlen)) {
+ kfree(kbuf);
+ return -EFAULT;
+ }
+ else
+ total_retlen += retlen;
+
+ count -= retlen;
+ buf += retlen;
+ }
+ else {
+ kfree(kbuf);
+ return ret;
+ }
+
+ kfree(kbuf);
+ }
+
+ return total_retlen;
+} /* mtd_read */
+
+static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos)
+{
+ struct mtd_info *mtd = file->private_data;
+ char *kbuf;
+ size_t retlen;
+ size_t total_retlen=0;
+ int ret=0;
+ int len;
+
+ DEBUG(MTD_DEBUG_LEVEL0,"MTD_write\n");
+
+ if (*ppos == mtd->size)
+ return -ENOSPC;
+
+ if (*ppos + count > mtd->size)
+ count = mtd->size - *ppos;
+
+ if (!count)
+ return 0;
+
+ while (count) {
+ if (count > MAX_KMALLOC_SIZE)
+ len = MAX_KMALLOC_SIZE;
+ else
+ len = count;
+
+ kbuf=kmalloc(len,GFP_KERNEL);
+ if (!kbuf) {
+ printk("kmalloc is null\n");
+ return -ENOMEM;
+ }
+
+ if (copy_from_user(kbuf, buf, len)) {
+ kfree(kbuf);
+ return -EFAULT;
+ }
+
+ ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf);
+ if (!ret) {
+ *ppos += retlen;
+ total_retlen += retlen;
+ count -= retlen;
+ buf += retlen;
+ }
+ else {
+ kfree(kbuf);
+ return ret;
+ }
+
+ kfree(kbuf);
+ }
+
+ return total_retlen;
+} /* mtd_write */
+
+/*======================================================================
+
+ IOCTL calls for getting device parameters.
+
+======================================================================*/
+static void mtdchar_erase_callback (struct erase_info *instr)
+{
+ wake_up((wait_queue_head_t *)instr->priv);
+}
+
+static int mtd_ioctl(struct inode *inode, struct file *file,
+ u_int cmd, u_long arg)
+{
+ struct mtd_info *mtd = file->private_data;
+ void __user *argp = (void __user *)arg;
+ int ret = 0;
+ u_long size;
+
+ DEBUG(MTD_DEBUG_LEVEL0, "MTD_ioctl\n");
+
+ size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT;
+ if (cmd & IOC_IN) {
+ if (!access_ok(VERIFY_READ, argp, size))
+ return -EFAULT;
+ }
+ if (cmd & IOC_OUT) {
+ if (!access_ok(VERIFY_WRITE, argp, size))
+ return -EFAULT;
+ }
+
+ switch (cmd) {
+ case MEMGETREGIONCOUNT:
+ if (copy_to_user(argp, &(mtd->numeraseregions), sizeof(int)))
+ return -EFAULT;
+ break;
+
+ case MEMGETREGIONINFO:
+ {
+ struct region_info_user ur;
+
+ if (copy_from_user(&ur, argp, sizeof(struct region_info_user)))
+ return -EFAULT;
+
+ if (ur.regionindex >= mtd->numeraseregions)
+ return -EINVAL;
+ if (copy_to_user(argp, &(mtd->eraseregions[ur.regionindex]),
+ sizeof(struct mtd_erase_region_info)))
+ return -EFAULT;
+ break;
+ }
+
+ case MEMGETINFO:
+ if (copy_to_user(argp, mtd, sizeof(struct mtd_info_user)))
+ return -EFAULT;
+ break;
+
+ case MEMERASE:
+ {
+ struct erase_info *erase;
+
+ if(!(file->f_mode & 2))
+ return -EPERM;
+
+ erase=kmalloc(sizeof(struct erase_info),GFP_KERNEL);
+ if (!erase)
+ ret = -ENOMEM;
+ else {
+ wait_queue_head_t waitq;
+ DECLARE_WAITQUEUE(wait, current);
+
+ init_waitqueue_head(&waitq);
+
+ memset (erase,0,sizeof(struct erase_info));
+ if (copy_from_user(&erase->addr, argp,
+ sizeof(struct erase_info_user))) {
+ kfree(erase);
+ return -EFAULT;
+ }
+ erase->mtd = mtd;
+ erase->callback = mtdchar_erase_callback;
+ erase->priv = (unsigned long)&waitq;
+
+ /*
+ FIXME: Allow INTERRUPTIBLE. Which means
+ not having the wait_queue head on the stack.
+
+ If the wq_head is on the stack, and we
+ leave because we got interrupted, then the
+ wq_head is no longer there when the
+ callback routine tries to wake us up.
+ */
+ ret = mtd->erase(mtd, erase);
+ if (!ret) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&waitq, &wait);
+ if (erase->state != MTD_ERASE_DONE &&
+ erase->state != MTD_ERASE_FAILED)
+ schedule();
+ remove_wait_queue(&waitq, &wait);
+ set_current_state(TASK_RUNNING);
+
+ ret = (erase->state == MTD_ERASE_FAILED)?-EIO:0;
+ }
+ kfree(erase);
+ }
+ break;
+ }
+
+ case MEMWRITEOOB:
+ {
+ struct mtd_oob_buf buf;
+ void *databuf;
+ ssize_t retlen;
+
+ if(!(file->f_mode & 2))
+ return -EPERM;
+
+ if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf)))
+ return -EFAULT;
+
+ if (buf.length > 0x4096)
+ return -EINVAL;
+
+ if (!mtd->write_oob)
+ ret = -EOPNOTSUPP;
+ else
+ ret = access_ok(VERIFY_READ, buf.ptr,
+ buf.length) ? 0 : EFAULT;
+
+ if (ret)
+ return ret;
+
+ databuf = kmalloc(buf.length, GFP_KERNEL);
+ if (!databuf)
+ return -ENOMEM;
+
+ if (copy_from_user(databuf, buf.ptr, buf.length)) {
+ kfree(databuf);
+ return -EFAULT;
+ }
+
+ ret = (mtd->write_oob)(mtd, buf.start, buf.length, &retlen, databuf);
+
+ if (copy_to_user(argp + sizeof(uint32_t), &retlen, sizeof(uint32_t)))
+ ret = -EFAULT;
+
+ kfree(databuf);
+ break;
+
+ }
+
+ case MEMREADOOB:
+ {
+ struct mtd_oob_buf buf;
+ void *databuf;
+ ssize_t retlen;
+
+ if (copy_from_user(&buf, argp, sizeof(struct mtd_oob_buf)))
+ return -EFAULT;
+
+ if (buf.length > 0x4096)
+ return -EINVAL;
+
+ if (!mtd->read_oob)
+ ret = -EOPNOTSUPP;
+ else
+ ret = access_ok(VERIFY_WRITE, buf.ptr,
+ buf.length) ? 0 : -EFAULT;
+
+ if (ret)
+ return ret;
+
+ databuf = kmalloc(buf.length, GFP_KERNEL);
+ if (!databuf)
+ return -ENOMEM;
+
+ ret = (mtd->read_oob)(mtd, buf.start, buf.length, &retlen, databuf);
+
+ if (put_user(retlen, (uint32_t __user *)argp))
+ ret = -EFAULT;
+ else if (retlen && copy_to_user(buf.ptr, databuf, retlen))
+ ret = -EFAULT;
+
+ kfree(databuf);
+ break;
+ }
+
+ case MEMLOCK:
+ {
+ struct erase_info_user info;
+
+ if (copy_from_user(&info, argp, sizeof(info)))
+ return -EFAULT;
+
+ if (!mtd->lock)
+ ret = -EOPNOTSUPP;
+ else
+ ret = mtd->lock(mtd, info.start, info.length);
+ break;
+ }
+
+ case MEMUNLOCK:
+ {
+ struct erase_info_user info;
+
+ if (copy_from_user(&info, argp, sizeof(info)))
+ return -EFAULT;
+
+ if (!mtd->unlock)
+ ret = -EOPNOTSUPP;
+ else
+ ret = mtd->unlock(mtd, info.start, info.length);
+ break;
+ }
+
+ case MEMSETOOBSEL:
+ {
+ if (copy_from_user(&mtd->oobinfo, argp, sizeof(struct nand_oobinfo)))
+ return -EFAULT;
+ break;
+ }
+
+ case MEMGETOOBSEL:
+ {
+ if (copy_to_user(argp, &(mtd->oobinfo), sizeof(struct nand_oobinfo)))
+ return -EFAULT;
+ break;
+ }
+
+ case MEMGETBADBLOCK:
+ {
+ loff_t offs;
+
+ if (copy_from_user(&offs, argp, sizeof(loff_t)))
+ return -EFAULT;
+ if (!mtd->block_isbad)
+ ret = -EOPNOTSUPP;
+ else
+ return mtd->block_isbad(mtd, offs);
+ break;
+ }
+
+ case MEMSETBADBLOCK:
+ {
+ loff_t offs;
+
+ if (copy_from_user(&offs, argp, sizeof(loff_t)))
+ return -EFAULT;
+ if (!mtd->block_markbad)
+ ret = -EOPNOTSUPP;
+ else
+ return mtd->block_markbad(mtd, offs);
+ break;
+ }
+
+ default:
+ ret = -ENOTTY;
+ }
+
+ return ret;
+} /* memory_ioctl */
+
+static struct file_operations mtd_fops = {
+ .owner = THIS_MODULE,
+ .llseek = mtd_lseek,
+ .read = mtd_read,
+ .write = mtd_write,
+ .ioctl = mtd_ioctl,
+ .open = mtd_open,
+ .release = mtd_close,
+};
+
+static int __init init_mtdchar(void)
+{
+ if (register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)) {
+ printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n",
+ MTD_CHAR_MAJOR);
+ return -EAGAIN;
+ }
+
+ mtdchar_devfs_init();
+ return 0;
+}
+
+static void __exit cleanup_mtdchar(void)
+{
+ mtdchar_devfs_exit();
+ unregister_chrdev(MTD_CHAR_MAJOR, "mtd");
+}
+
+module_init(init_mtdchar);
+module_exit(cleanup_mtdchar);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("Direct character-device access to MTD devices");
diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c
new file mode 100644
index 000000000000..8f66d093c80d
--- /dev/null
+++ b/drivers/mtd/mtdconcat.c
@@ -0,0 +1,897 @@
+/*
+ * MTD device concatenation layer
+ *
+ * (C) 2002 Robert Kaiser <rkaiser@sysgo.de>
+ *
+ * NAND support by Christian Gan <cgan@iders.ca>
+ *
+ * This code is GPL
+ *
+ * $Id: mtdconcat.c,v 1.9 2004/06/30 15:17:41 dbrown Exp $
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/concat.h>
+
+/*
+ * Our storage structure:
+ * Subdev points to an array of pointers to struct mtd_info objects
+ * which is allocated along with this structure
+ *
+ */
+struct mtd_concat {
+ struct mtd_info mtd;
+ int num_subdev;
+ struct mtd_info **subdev;
+};
+
+/*
+ * how to calculate the size required for the above structure,
+ * including the pointer array subdev points to:
+ */
+#define SIZEOF_STRUCT_MTD_CONCAT(num_subdev) \
+ ((sizeof(struct mtd_concat) + (num_subdev) * sizeof(struct mtd_info *)))
+
+/*
+ * Given a pointer to the MTD object in the mtd_concat structure,
+ * we can retrieve the pointer to that structure with this macro.
+ */
+#define CONCAT(x) ((struct mtd_concat *)(x))
+
+/*
+ * MTD methods which look up the relevant subdevice, translate the
+ * effective address and pass through to the subdevice.
+ */
+
+static int
+concat_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t * retlen, u_char * buf)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+ int err = -EINVAL;
+ int i;
+
+ *retlen = 0;
+
+ for (i = 0; i < concat->num_subdev; i++) {
+ struct mtd_info *subdev = concat->subdev[i];
+ size_t size, retsize;
+
+ if (from >= subdev->size) {
+ /* Not destined for this subdev */
+ size = 0;
+ from -= subdev->size;
+ continue;
+ }
+ if (from + len > subdev->size)
+ /* First part goes into this subdev */
+ size = subdev->size - from;
+ else
+ /* Entire transaction goes into this subdev */
+ size = len;
+
+ err = subdev->read(subdev, from, size, &retsize, buf);
+
+ if (err)
+ break;
+
+ *retlen += retsize;
+ len -= size;
+ if (len == 0)
+ break;
+
+ err = -EINVAL;
+ buf += size;
+ from = 0;
+ }
+ return err;
+}
+
+static int
+concat_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t * retlen, const u_char * buf)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+ int err = -EINVAL;
+ int i;
+
+ if (!(mtd->flags & MTD_WRITEABLE))
+ return -EROFS;
+
+ *retlen = 0;
+
+ for (i = 0; i < concat->num_subdev; i++) {
+ struct mtd_info *subdev = concat->subdev[i];
+ size_t size, retsize;
+
+ if (to >= subdev->size) {
+ size = 0;
+ to -= subdev->size;
+ continue;
+ }
+ if (to + len > subdev->size)
+ size = subdev->size - to;
+ else
+ size = len;
+
+ if (!(subdev->flags & MTD_WRITEABLE))
+ err = -EROFS;
+ else
+ err = subdev->write(subdev, to, size, &retsize, buf);
+
+ if (err)
+ break;
+
+ *retlen += retsize;
+ len -= size;
+ if (len == 0)
+ break;
+
+ err = -EINVAL;
+ buf += size;
+ to = 0;
+ }
+ return err;
+}
+
+static int
+concat_read_ecc(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t * retlen, u_char * buf, u_char * eccbuf,
+ struct nand_oobinfo *oobsel)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+ int err = -EINVAL;
+ int i;
+
+ *retlen = 0;
+
+ for (i = 0; i < concat->num_subdev; i++) {
+ struct mtd_info *subdev = concat->subdev[i];
+ size_t size, retsize;
+
+ if (from >= subdev->size) {
+ /* Not destined for this subdev */
+ size = 0;
+ from -= subdev->size;
+ continue;
+ }
+
+ if (from + len > subdev->size)
+ /* First part goes into this subdev */
+ size = subdev->size - from;
+ else
+ /* Entire transaction goes into this subdev */
+ size = len;
+
+ if (subdev->read_ecc)
+ err = subdev->read_ecc(subdev, from, size,
+ &retsize, buf, eccbuf, oobsel);
+ else
+ err = -EINVAL;
+
+ if (err)
+ break;
+
+ *retlen += retsize;
+ len -= size;
+ if (len == 0)
+ break;
+
+ err = -EINVAL;
+ buf += size;
+ if (eccbuf) {
+ eccbuf += subdev->oobsize;
+ /* in nand.c at least, eccbufs are
+ tagged with 2 (int)eccstatus'; we
+ must account for these */
+ eccbuf += 2 * (sizeof (int));
+ }
+ from = 0;
+ }
+ return err;
+}
+
+static int
+concat_write_ecc(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t * retlen, const u_char * buf, u_char * eccbuf,
+ struct nand_oobinfo *oobsel)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+ int err = -EINVAL;
+ int i;
+
+ if (!(mtd->flags & MTD_WRITEABLE))
+ return -EROFS;
+
+ *retlen = 0;
+
+ for (i = 0; i < concat->num_subdev; i++) {
+ struct mtd_info *subdev = concat->subdev[i];
+ size_t size, retsize;
+
+ if (to >= subdev->size) {
+ size = 0;
+ to -= subdev->size;
+ continue;
+ }
+ if (to + len > subdev->size)
+ size = subdev->size - to;
+ else
+ size = len;
+
+ if (!(subdev->flags & MTD_WRITEABLE))
+ err = -EROFS;
+ else if (subdev->write_ecc)
+ err = subdev->write_ecc(subdev, to, size,
+ &retsize, buf, eccbuf, oobsel);
+ else
+ err = -EINVAL;
+
+ if (err)
+ break;
+
+ *retlen += retsize;
+ len -= size;
+ if (len == 0)
+ break;
+
+ err = -EINVAL;
+ buf += size;
+ if (eccbuf)
+ eccbuf += subdev->oobsize;
+ to = 0;
+ }
+ return err;
+}
+
+static int
+concat_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t * retlen, u_char * buf)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+ int err = -EINVAL;
+ int i;
+
+ *retlen = 0;
+
+ for (i = 0; i < concat->num_subdev; i++) {
+ struct mtd_info *subdev = concat->subdev[i];
+ size_t size, retsize;
+
+ if (from >= subdev->size) {
+ /* Not destined for this subdev */
+ size = 0;
+ from -= subdev->size;
+ continue;
+ }
+ if (from + len > subdev->size)
+ /* First part goes into this subdev */
+ size = subdev->size - from;
+ else
+ /* Entire transaction goes into this subdev */
+ size = len;
+
+ if (subdev->read_oob)
+ err = subdev->read_oob(subdev, from, size,
+ &retsize, buf);
+ else
+ err = -EINVAL;
+
+ if (err)
+ break;
+
+ *retlen += retsize;
+ len -= size;
+ if (len == 0)
+ break;
+
+ err = -EINVAL;
+ buf += size;
+ from = 0;
+ }
+ return err;
+}
+
+static int
+concat_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t * retlen, const u_char * buf)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+ int err = -EINVAL;
+ int i;
+
+ if (!(mtd->flags & MTD_WRITEABLE))
+ return -EROFS;
+
+ *retlen = 0;
+
+ for (i = 0; i < concat->num_subdev; i++) {
+ struct mtd_info *subdev = concat->subdev[i];
+ size_t size, retsize;
+
+ if (to >= subdev->size) {
+ size = 0;
+ to -= subdev->size;
+ continue;
+ }
+ if (to + len > subdev->size)
+ size = subdev->size - to;
+ else
+ size = len;
+
+ if (!(subdev->flags & MTD_WRITEABLE))
+ err = -EROFS;
+ else if (subdev->write_oob)
+ err = subdev->write_oob(subdev, to, size, &retsize,
+ buf);
+ else
+ err = -EINVAL;
+
+ if (err)
+ break;
+
+ *retlen += retsize;
+ len -= size;
+ if (len == 0)
+ break;
+
+ err = -EINVAL;
+ buf += size;
+ to = 0;
+ }
+ return err;
+}
+
+static void concat_erase_callback(struct erase_info *instr)
+{
+ wake_up((wait_queue_head_t *) instr->priv);
+}
+
+static int concat_dev_erase(struct mtd_info *mtd, struct erase_info *erase)
+{
+ int err;
+ wait_queue_head_t waitq;
+ DECLARE_WAITQUEUE(wait, current);
+
+ /*
+ * This code was stol^H^H^H^Hinspired by mtdchar.c
+ */
+ init_waitqueue_head(&waitq);
+
+ erase->mtd = mtd;
+ erase->callback = concat_erase_callback;
+ erase->priv = (unsigned long) &waitq;
+
+ /*
+ * FIXME: Allow INTERRUPTIBLE. Which means
+ * not having the wait_queue head on the stack.
+ */
+ err = mtd->erase(mtd, erase);
+ if (!err) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&waitq, &wait);
+ if (erase->state != MTD_ERASE_DONE
+ && erase->state != MTD_ERASE_FAILED)
+ schedule();
+ remove_wait_queue(&waitq, &wait);
+ set_current_state(TASK_RUNNING);
+
+ err = (erase->state == MTD_ERASE_FAILED) ? -EIO : 0;
+ }
+ return err;
+}
+
+static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+ struct mtd_info *subdev;
+ int i, err;
+ u_int32_t length, offset = 0;
+ struct erase_info *erase;
+
+ if (!(mtd->flags & MTD_WRITEABLE))
+ return -EROFS;
+
+ if (instr->addr > concat->mtd.size)
+ return -EINVAL;
+
+ if (instr->len + instr->addr > concat->mtd.size)
+ return -EINVAL;
+
+ /*
+ * Check for proper erase block alignment of the to-be-erased area.
+ * It is easier to do this based on the super device's erase
+ * region info rather than looking at each particular sub-device
+ * in turn.
+ */
+ if (!concat->mtd.numeraseregions) {
+ /* the easy case: device has uniform erase block size */
+ if (instr->addr & (concat->mtd.erasesize - 1))
+ return -EINVAL;
+ if (instr->len & (concat->mtd.erasesize - 1))
+ return -EINVAL;
+ } else {
+ /* device has variable erase size */
+ struct mtd_erase_region_info *erase_regions =
+ concat->mtd.eraseregions;
+
+ /*
+ * Find the erase region where the to-be-erased area begins:
+ */
+ for (i = 0; i < concat->mtd.numeraseregions &&
+ instr->addr >= erase_regions[i].offset; i++) ;
+ --i;
+
+ /*
+ * Now erase_regions[i] is the region in which the
+ * to-be-erased area begins. Verify that the starting
+ * offset is aligned to this region's erase size:
+ */
+ if (instr->addr & (erase_regions[i].erasesize - 1))
+ return -EINVAL;
+
+ /*
+ * now find the erase region where the to-be-erased area ends:
+ */
+ for (; i < concat->mtd.numeraseregions &&
+ (instr->addr + instr->len) >= erase_regions[i].offset;
+ ++i) ;
+ --i;
+ /*
+ * check if the ending offset is aligned to this region's erase size
+ */
+ if ((instr->addr + instr->len) & (erase_regions[i].erasesize -
+ 1))
+ return -EINVAL;
+ }
+
+ instr->fail_addr = 0xffffffff;
+
+ /* make a local copy of instr to avoid modifying the caller's struct */
+ erase = kmalloc(sizeof (struct erase_info), GFP_KERNEL);
+
+ if (!erase)
+ return -ENOMEM;
+
+ *erase = *instr;
+ length = instr->len;
+
+ /*
+ * find the subdevice where the to-be-erased area begins, adjust
+ * starting offset to be relative to the subdevice start
+ */
+ for (i = 0; i < concat->num_subdev; i++) {
+ subdev = concat->subdev[i];
+ if (subdev->size <= erase->addr) {
+ erase->addr -= subdev->size;
+ offset += subdev->size;
+ } else {
+ break;
+ }
+ }
+
+ /* must never happen since size limit has been verified above */
+ if (i >= concat->num_subdev)
+ BUG();
+
+ /* now do the erase: */
+ err = 0;
+ for (; length > 0; i++) {
+ /* loop for all subdevices affected by this request */
+ subdev = concat->subdev[i]; /* get current subdevice */
+
+ /* limit length to subdevice's size: */
+ if (erase->addr + length > subdev->size)
+ erase->len = subdev->size - erase->addr;
+ else
+ erase->len = length;
+
+ if (!(subdev->flags & MTD_WRITEABLE)) {
+ err = -EROFS;
+ break;
+ }
+ length -= erase->len;
+ if ((err = concat_dev_erase(subdev, erase))) {
+ /* sanity check: should never happen since
+ * block alignment has been checked above */
+ if (err == -EINVAL)
+ BUG();
+ if (erase->fail_addr != 0xffffffff)
+ instr->fail_addr = erase->fail_addr + offset;
+ break;
+ }
+ /*
+ * erase->addr specifies the offset of the area to be
+ * erased *within the current subdevice*. It can be
+ * non-zero only the first time through this loop, i.e.
+ * for the first subdevice where blocks need to be erased.
+ * All the following erases must begin at the start of the
+ * current subdevice, i.e. at offset zero.
+ */
+ erase->addr = 0;
+ offset += subdev->size;
+ }
+ instr->state = erase->state;
+ kfree(erase);
+ if (err)
+ return err;
+
+ if (instr->callback)
+ instr->callback(instr);
+ return 0;
+}
+
+static int concat_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+ int i, err = -EINVAL;
+
+ if ((len + ofs) > mtd->size)
+ return -EINVAL;
+
+ for (i = 0; i < concat->num_subdev; i++) {
+ struct mtd_info *subdev = concat->subdev[i];
+ size_t size;
+
+ if (ofs >= subdev->size) {
+ size = 0;
+ ofs -= subdev->size;
+ continue;
+ }
+ if (ofs + len > subdev->size)
+ size = subdev->size - ofs;
+ else
+ size = len;
+
+ err = subdev->lock(subdev, ofs, size);
+
+ if (err)
+ break;
+
+ len -= size;
+ if (len == 0)
+ break;
+
+ err = -EINVAL;
+ ofs = 0;
+ }
+
+ return err;
+}
+
+static int concat_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+ int i, err = 0;
+
+ if ((len + ofs) > mtd->size)
+ return -EINVAL;
+
+ for (i = 0; i < concat->num_subdev; i++) {
+ struct mtd_info *subdev = concat->subdev[i];
+ size_t size;
+
+ if (ofs >= subdev->size) {
+ size = 0;
+ ofs -= subdev->size;
+ continue;
+ }
+ if (ofs + len > subdev->size)
+ size = subdev->size - ofs;
+ else
+ size = len;
+
+ err = subdev->unlock(subdev, ofs, size);
+
+ if (err)
+ break;
+
+ len -= size;
+ if (len == 0)
+ break;
+
+ err = -EINVAL;
+ ofs = 0;
+ }
+
+ return err;
+}
+
+static void concat_sync(struct mtd_info *mtd)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+ int i;
+
+ for (i = 0; i < concat->num_subdev; i++) {
+ struct mtd_info *subdev = concat->subdev[i];
+ subdev->sync(subdev);
+ }
+}
+
+static int concat_suspend(struct mtd_info *mtd)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+ int i, rc = 0;
+
+ for (i = 0; i < concat->num_subdev; i++) {
+ struct mtd_info *subdev = concat->subdev[i];
+ if ((rc = subdev->suspend(subdev)) < 0)
+ return rc;
+ }
+ return rc;
+}
+
+static void concat_resume(struct mtd_info *mtd)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+ int i;
+
+ for (i = 0; i < concat->num_subdev; i++) {
+ struct mtd_info *subdev = concat->subdev[i];
+ subdev->resume(subdev);
+ }
+}
+
+/*
+ * This function constructs a virtual MTD device by concatenating
+ * num_devs MTD devices. A pointer to the new device object is
+ * stored to *new_dev upon success. This function does _not_
+ * register any devices: this is the caller's responsibility.
+ */
+struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to concatenate */
+ int num_devs, /* number of subdevices */
+ char *name)
+{ /* name for the new device */
+ int i;
+ size_t size;
+ struct mtd_concat *concat;
+ u_int32_t max_erasesize, curr_erasesize;
+ int num_erase_region;
+
+ printk(KERN_NOTICE "Concatenating MTD devices:\n");
+ for (i = 0; i < num_devs; i++)
+ printk(KERN_NOTICE "(%d): \"%s\"\n", i, subdev[i]->name);
+ printk(KERN_NOTICE "into device \"%s\"\n", name);
+
+ /* allocate the device structure */
+ size = SIZEOF_STRUCT_MTD_CONCAT(num_devs);
+ concat = kmalloc(size, GFP_KERNEL);
+ if (!concat) {
+ printk
+ ("memory allocation error while creating concatenated device \"%s\"\n",
+ name);
+ return NULL;
+ }
+ memset(concat, 0, size);
+ concat->subdev = (struct mtd_info **) (concat + 1);
+
+ /*
+ * Set up the new "super" device's MTD object structure, check for
+ * incompatibilites between the subdevices.
+ */
+ concat->mtd.type = subdev[0]->type;
+ concat->mtd.flags = subdev[0]->flags;
+ concat->mtd.size = subdev[0]->size;
+ concat->mtd.erasesize = subdev[0]->erasesize;
+ concat->mtd.oobblock = subdev[0]->oobblock;
+ concat->mtd.oobsize = subdev[0]->oobsize;
+ concat->mtd.ecctype = subdev[0]->ecctype;
+ concat->mtd.eccsize = subdev[0]->eccsize;
+ if (subdev[0]->read_ecc)
+ concat->mtd.read_ecc = concat_read_ecc;
+ if (subdev[0]->write_ecc)
+ concat->mtd.write_ecc = concat_write_ecc;
+ if (subdev[0]->read_oob)
+ concat->mtd.read_oob = concat_read_oob;
+ if (subdev[0]->write_oob)
+ concat->mtd.write_oob = concat_write_oob;
+
+ concat->subdev[0] = subdev[0];
+
+ for (i = 1; i < num_devs; i++) {
+ if (concat->mtd.type != subdev[i]->type) {
+ kfree(concat);
+ printk("Incompatible device type on \"%s\"\n",
+ subdev[i]->name);
+ return NULL;
+ }
+ if (concat->mtd.flags != subdev[i]->flags) {
+ /*
+ * Expect all flags except MTD_WRITEABLE to be
+ * equal on all subdevices.
+ */
+ if ((concat->mtd.flags ^ subdev[i]->
+ flags) & ~MTD_WRITEABLE) {
+ kfree(concat);
+ printk("Incompatible device flags on \"%s\"\n",
+ subdev[i]->name);
+ return NULL;
+ } else
+ /* if writeable attribute differs,
+ make super device writeable */
+ concat->mtd.flags |=
+ subdev[i]->flags & MTD_WRITEABLE;
+ }
+ concat->mtd.size += subdev[i]->size;
+ if (concat->mtd.oobblock != subdev[i]->oobblock ||
+ concat->mtd.oobsize != subdev[i]->oobsize ||
+ concat->mtd.ecctype != subdev[i]->ecctype ||
+ concat->mtd.eccsize != subdev[i]->eccsize ||
+ !concat->mtd.read_ecc != !subdev[i]->read_ecc ||
+ !concat->mtd.write_ecc != !subdev[i]->write_ecc ||
+ !concat->mtd.read_oob != !subdev[i]->read_oob ||
+ !concat->mtd.write_oob != !subdev[i]->write_oob) {
+ kfree(concat);
+ printk("Incompatible OOB or ECC data on \"%s\"\n",
+ subdev[i]->name);
+ return NULL;
+ }
+ concat->subdev[i] = subdev[i];
+
+ }
+
+ concat->num_subdev = num_devs;
+ concat->mtd.name = name;
+
+ /*
+ * NOTE: for now, we do not provide any readv()/writev() methods
+ * because they are messy to implement and they are not
+ * used to a great extent anyway.
+ */
+ concat->mtd.erase = concat_erase;
+ concat->mtd.read = concat_read;
+ concat->mtd.write = concat_write;
+ concat->mtd.sync = concat_sync;
+ concat->mtd.lock = concat_lock;
+ concat->mtd.unlock = concat_unlock;
+ concat->mtd.suspend = concat_suspend;
+ concat->mtd.resume = concat_resume;
+
+ /*
+ * Combine the erase block size info of the subdevices:
+ *
+ * first, walk the map of the new device and see how
+ * many changes in erase size we have
+ */
+ max_erasesize = curr_erasesize = subdev[0]->erasesize;
+ num_erase_region = 1;
+ for (i = 0; i < num_devs; i++) {
+ if (subdev[i]->numeraseregions == 0) {
+ /* current subdevice has uniform erase size */
+ if (subdev[i]->erasesize != curr_erasesize) {
+ /* if it differs from the last subdevice's erase size, count it */
+ ++num_erase_region;
+ curr_erasesize = subdev[i]->erasesize;
+ if (curr_erasesize > max_erasesize)
+ max_erasesize = curr_erasesize;
+ }
+ } else {
+ /* current subdevice has variable erase size */
+ int j;
+ for (j = 0; j < subdev[i]->numeraseregions; j++) {
+
+ /* walk the list of erase regions, count any changes */
+ if (subdev[i]->eraseregions[j].erasesize !=
+ curr_erasesize) {
+ ++num_erase_region;
+ curr_erasesize =
+ subdev[i]->eraseregions[j].
+ erasesize;
+ if (curr_erasesize > max_erasesize)
+ max_erasesize = curr_erasesize;
+ }
+ }
+ }
+ }
+
+ if (num_erase_region == 1) {
+ /*
+ * All subdevices have the same uniform erase size.
+ * This is easy:
+ */
+ concat->mtd.erasesize = curr_erasesize;
+ concat->mtd.numeraseregions = 0;
+ } else {
+ /*
+ * erase block size varies across the subdevices: allocate
+ * space to store the data describing the variable erase regions
+ */
+ struct mtd_erase_region_info *erase_region_p;
+ u_int32_t begin, position;
+
+ concat->mtd.erasesize = max_erasesize;
+ concat->mtd.numeraseregions = num_erase_region;
+ concat->mtd.eraseregions = erase_region_p =
+ kmalloc(num_erase_region *
+ sizeof (struct mtd_erase_region_info), GFP_KERNEL);
+ if (!erase_region_p) {
+ kfree(concat);
+ printk
+ ("memory allocation error while creating erase region list"
+ " for device \"%s\"\n", name);
+ return NULL;
+ }
+
+ /*
+ * walk the map of the new device once more and fill in
+ * in erase region info:
+ */
+ curr_erasesize = subdev[0]->erasesize;
+ begin = position = 0;
+ for (i = 0; i < num_devs; i++) {
+ if (subdev[i]->numeraseregions == 0) {
+ /* current subdevice has uniform erase size */
+ if (subdev[i]->erasesize != curr_erasesize) {
+ /*
+ * fill in an mtd_erase_region_info structure for the area
+ * we have walked so far:
+ */
+ erase_region_p->offset = begin;
+ erase_region_p->erasesize =
+ curr_erasesize;
+ erase_region_p->numblocks =
+ (position - begin) / curr_erasesize;
+ begin = position;
+
+ curr_erasesize = subdev[i]->erasesize;
+ ++erase_region_p;
+ }
+ position += subdev[i]->size;
+ } else {
+ /* current subdevice has variable erase size */
+ int j;
+ for (j = 0; j < subdev[i]->numeraseregions; j++) {
+ /* walk the list of erase regions, count any changes */
+ if (subdev[i]->eraseregions[j].
+ erasesize != curr_erasesize) {
+ erase_region_p->offset = begin;
+ erase_region_p->erasesize =
+ curr_erasesize;
+ erase_region_p->numblocks =
+ (position -
+ begin) / curr_erasesize;
+ begin = position;
+
+ curr_erasesize =
+ subdev[i]->eraseregions[j].
+ erasesize;
+ ++erase_region_p;
+ }
+ position +=
+ subdev[i]->eraseregions[j].
+ numblocks * curr_erasesize;
+ }
+ }
+ }
+ /* Now write the final entry */
+ erase_region_p->offset = begin;
+ erase_region_p->erasesize = curr_erasesize;
+ erase_region_p->numblocks = (position - begin) / curr_erasesize;
+ }
+
+ return &concat->mtd;
+}
+
+/*
+ * This function destroys an MTD object obtained from concat_mtd_devs()
+ */
+
+void mtd_concat_destroy(struct mtd_info *mtd)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+ if (concat->mtd.numeraseregions)
+ kfree(concat->mtd.eraseregions);
+ kfree(concat);
+}
+
+EXPORT_SYMBOL(mtd_concat_create);
+EXPORT_SYMBOL(mtd_concat_destroy);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Robert Kaiser <rkaiser@sysgo.de>");
+MODULE_DESCRIPTION("Generic support for concatenating of MTD devices");
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
new file mode 100644
index 000000000000..9c0315d1b1c4
--- /dev/null
+++ b/drivers/mtd/mtdcore.c
@@ -0,0 +1,419 @@
+/*
+ * $Id: mtdcore.c,v 1.44 2004/11/16 18:28:59 dwmw2 Exp $
+ *
+ * Core registration and callback routines for MTD
+ * drivers and users.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ptrace.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/major.h>
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+#include <linux/init.h>
+#include <linux/mtd/compatmac.h>
+#ifdef CONFIG_PROC_FS
+#include <linux/proc_fs.h>
+#endif
+
+#include <linux/mtd/mtd.h>
+
+/* These are exported solely for the purpose of mtd_blkdevs.c. You
+ should not use them for _anything_ else */
+DECLARE_MUTEX(mtd_table_mutex);
+struct mtd_info *mtd_table[MAX_MTD_DEVICES];
+
+EXPORT_SYMBOL_GPL(mtd_table_mutex);
+EXPORT_SYMBOL_GPL(mtd_table);
+
+static LIST_HEAD(mtd_notifiers);
+
+/**
+ * add_mtd_device - register an MTD device
+ * @mtd: pointer to new MTD device info structure
+ *
+ * Add a device to the list of MTD devices present in the system, and
+ * notify each currently active MTD 'user' of its arrival. Returns
+ * zero on success or 1 on failure, which currently will only happen
+ * if the number of present devices exceeds MAX_MTD_DEVICES (i.e. 16)
+ */
+
+int add_mtd_device(struct mtd_info *mtd)
+{
+ int i;
+
+ down(&mtd_table_mutex);
+
+ for (i=0; i < MAX_MTD_DEVICES; i++)
+ if (!mtd_table[i]) {
+ struct list_head *this;
+
+ mtd_table[i] = mtd;
+ mtd->index = i;
+ mtd->usecount = 0;
+
+ DEBUG(0, "mtd: Giving out device %d to %s\n",i, mtd->name);
+ /* No need to get a refcount on the module containing
+ the notifier, since we hold the mtd_table_mutex */
+ list_for_each(this, &mtd_notifiers) {
+ struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);
+ not->add(mtd);
+ }
+
+ up(&mtd_table_mutex);
+ /* We _know_ we aren't being removed, because
+ our caller is still holding us here. So none
+ of this try_ nonsense, and no bitching about it
+ either. :) */
+ __module_get(THIS_MODULE);
+ return 0;
+ }
+
+ up(&mtd_table_mutex);
+ return 1;
+}
+
+/**
+ * del_mtd_device - unregister an MTD device
+ * @mtd: pointer to MTD device info structure
+ *
+ * Remove a device from the list of MTD devices present in the system,
+ * and notify each currently active MTD 'user' of its departure.
+ * Returns zero on success or 1 on failure, which currently will happen
+ * if the requested device does not appear to be present in the list.
+ */
+
+int del_mtd_device (struct mtd_info *mtd)
+{
+ int ret;
+
+ down(&mtd_table_mutex);
+
+ if (mtd_table[mtd->index] != mtd) {
+ ret = -ENODEV;
+ } else if (mtd->usecount) {
+ printk(KERN_NOTICE "Removing MTD device #%d (%s) with use count %d\n",
+ mtd->index, mtd->name, mtd->usecount);
+ ret = -EBUSY;
+ } else {
+ struct list_head *this;
+
+ /* No need to get a refcount on the module containing
+ the notifier, since we hold the mtd_table_mutex */
+ list_for_each(this, &mtd_notifiers) {
+ struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);
+ not->remove(mtd);
+ }
+
+ mtd_table[mtd->index] = NULL;
+
+ module_put(THIS_MODULE);
+ ret = 0;
+ }
+
+ up(&mtd_table_mutex);
+ return ret;
+}
+
+/**
+ * register_mtd_user - register a 'user' of MTD devices.
+ * @new: pointer to notifier info structure
+ *
+ * Registers a pair of callbacks function to be called upon addition
+ * or removal of MTD devices. Causes the 'add' callback to be immediately
+ * invoked for each MTD device currently present in the system.
+ */
+
+void register_mtd_user (struct mtd_notifier *new)
+{
+ int i;
+
+ down(&mtd_table_mutex);
+
+ list_add(&new->list, &mtd_notifiers);
+
+ __module_get(THIS_MODULE);
+
+ for (i=0; i< MAX_MTD_DEVICES; i++)
+ if (mtd_table[i])
+ new->add(mtd_table[i]);
+
+ up(&mtd_table_mutex);
+}
+
+/**
+ * register_mtd_user - unregister a 'user' of MTD devices.
+ * @new: pointer to notifier info structure
+ *
+ * Removes a callback function pair from the list of 'users' to be
+ * notified upon addition or removal of MTD devices. Causes the
+ * 'remove' callback to be immediately invoked for each MTD device
+ * currently present in the system.
+ */
+
+int unregister_mtd_user (struct mtd_notifier *old)
+{
+ int i;
+
+ down(&mtd_table_mutex);
+
+ module_put(THIS_MODULE);
+
+ for (i=0; i< MAX_MTD_DEVICES; i++)
+ if (mtd_table[i])
+ old->remove(mtd_table[i]);
+
+ list_del(&old->list);
+ up(&mtd_table_mutex);
+ return 0;
+}
+
+
+/**
+ * get_mtd_device - obtain a validated handle for an MTD device
+ * @mtd: last known address of the required MTD device
+ * @num: internal device number of the required MTD device
+ *
+ * Given a number and NULL address, return the num'th entry in the device
+ * table, if any. Given an address and num == -1, search the device table
+ * for a device with that address and return if it's still present. Given
+ * both, return the num'th driver only if its address matches. Return NULL
+ * if not.
+ */
+
+struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num)
+{
+ struct mtd_info *ret = NULL;
+ int i;
+
+ down(&mtd_table_mutex);
+
+ if (num == -1) {
+ for (i=0; i< MAX_MTD_DEVICES; i++)
+ if (mtd_table[i] == mtd)
+ ret = mtd_table[i];
+ } else if (num < MAX_MTD_DEVICES) {
+ ret = mtd_table[num];
+ if (mtd && mtd != ret)
+ ret = NULL;
+ }
+
+ if (ret && !try_module_get(ret->owner))
+ ret = NULL;
+
+ if (ret)
+ ret->usecount++;
+
+ up(&mtd_table_mutex);
+ return ret;
+}
+
+void put_mtd_device(struct mtd_info *mtd)
+{
+ int c;
+
+ down(&mtd_table_mutex);
+ c = --mtd->usecount;
+ up(&mtd_table_mutex);
+ BUG_ON(c < 0);
+
+ module_put(mtd->owner);
+}
+
+/* default_mtd_writev - default mtd writev method for MTD devices that
+ * dont implement their own
+ */
+
+int default_mtd_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;
+
+ if(!mtd->write) {
+ ret = -EROFS;
+ } else {
+ for (i=0; i<count; i++) {
+ if (!vecs[i].iov_len)
+ continue;
+ ret = mtd->write(mtd, to, vecs[i].iov_len, &thislen, vecs[i].iov_base);
+ totlen += thislen;
+ if (ret || thislen != vecs[i].iov_len)
+ break;
+ to += vecs[i].iov_len;
+ }
+ }
+ if (retlen)
+ *retlen = totlen;
+ return ret;
+}
+
+
+/* default_mtd_readv - default mtd readv method for MTD devices that dont
+ * implement their own
+ */
+
+int default_mtd_readv(struct mtd_info *mtd, struct kvec *vecs,
+ unsigned long count, loff_t from, size_t *retlen)
+{
+ unsigned long i;
+ size_t totlen = 0, thislen;
+ int ret = 0;
+
+ if(!mtd->read) {
+ ret = -EIO;
+ } else {
+ for (i=0; i<count; i++) {
+ if (!vecs[i].iov_len)
+ continue;
+ ret = mtd->read(mtd, from, vecs[i].iov_len, &thislen, vecs[i].iov_base);
+ totlen += thislen;
+ if (ret || thislen != vecs[i].iov_len)
+ break;
+ from += vecs[i].iov_len;
+ }
+ }
+ if (retlen)
+ *retlen = totlen;
+ return ret;
+}
+
+
+EXPORT_SYMBOL(add_mtd_device);
+EXPORT_SYMBOL(del_mtd_device);
+EXPORT_SYMBOL(get_mtd_device);
+EXPORT_SYMBOL(put_mtd_device);
+EXPORT_SYMBOL(register_mtd_user);
+EXPORT_SYMBOL(unregister_mtd_user);
+EXPORT_SYMBOL(default_mtd_writev);
+EXPORT_SYMBOL(default_mtd_readv);
+
+/*====================================================================*/
+/* Power management code */
+
+#ifdef CONFIG_PM
+
+#include <linux/pm.h>
+
+static struct pm_dev *mtd_pm_dev = NULL;
+
+static int mtd_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data)
+{
+ int ret = 0, i;
+
+ if (down_trylock(&mtd_table_mutex))
+ return -EAGAIN;
+ if (rqst == PM_SUSPEND) {
+ for (i = 0; ret == 0 && i < MAX_MTD_DEVICES; i++) {
+ if (mtd_table[i] && mtd_table[i]->suspend)
+ ret = mtd_table[i]->suspend(mtd_table[i]);
+ }
+ } else i = MAX_MTD_DEVICES-1;
+
+ if (rqst == PM_RESUME || ret) {
+ for ( ; i >= 0; i--) {
+ if (mtd_table[i] && mtd_table[i]->resume)
+ mtd_table[i]->resume(mtd_table[i]);
+ }
+ }
+ up(&mtd_table_mutex);
+ return ret;
+}
+#endif
+
+/*====================================================================*/
+/* Support for /proc/mtd */
+
+#ifdef CONFIG_PROC_FS
+static struct proc_dir_entry *proc_mtd;
+
+static inline int mtd_proc_info (char *buf, int i)
+{
+ struct mtd_info *this = mtd_table[i];
+
+ if (!this)
+ return 0;
+
+ return sprintf(buf, "mtd%d: %8.8x %8.8x \"%s\"\n", i, this->size,
+ this->erasesize, this->name);
+}
+
+static int mtd_read_proc (char *page, char **start, off_t off, int count,
+ int *eof, void *data_unused)
+{
+ int len, l, i;
+ off_t begin = 0;
+
+ down(&mtd_table_mutex);
+
+ len = sprintf(page, "dev: size erasesize name\n");
+ for (i=0; i< MAX_MTD_DEVICES; i++) {
+
+ l = mtd_proc_info(page + len, i);
+ len += l;
+ if (len+begin > off+count)
+ goto done;
+ if (len+begin < off) {
+ begin += len;
+ len = 0;
+ }
+ }
+
+ *eof = 1;
+
+done:
+ up(&mtd_table_mutex);
+ if (off >= len+begin)
+ return 0;
+ *start = page + (off-begin);
+ return ((count < begin+len-off) ? count : begin+len-off);
+}
+
+#endif /* CONFIG_PROC_FS */
+
+/*====================================================================*/
+/* Init code */
+
+static int __init init_mtd(void)
+{
+#ifdef CONFIG_PROC_FS
+ if ((proc_mtd = create_proc_entry( "mtd", 0, NULL )))
+ proc_mtd->read_proc = mtd_read_proc;
+#endif
+
+#ifdef CONFIG_PM
+ mtd_pm_dev = pm_register(PM_UNKNOWN_DEV, 0, mtd_pm_callback);
+#endif
+ return 0;
+}
+
+static void __exit cleanup_mtd(void)
+{
+#ifdef CONFIG_PM
+ if (mtd_pm_dev) {
+ pm_unregister(mtd_pm_dev);
+ mtd_pm_dev = NULL;
+ }
+#endif
+
+#ifdef CONFIG_PROC_FS
+ if (proc_mtd)
+ remove_proc_entry( "mtd", NULL);
+#endif
+}
+
+module_init(init_mtd);
+module_exit(cleanup_mtd);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("Core MTD registration and access routines");
diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c
new file mode 100644
index 000000000000..96ebb52f24b1
--- /dev/null
+++ b/drivers/mtd/mtdpart.c
@@ -0,0 +1,599 @@
+/*
+ * Simple MTD partitioning layer
+ *
+ * (C) 2000 Nicolas Pitre <nico@cam.org>
+ *
+ * This code is GPL
+ *
+ * $Id: mtdpart.c,v 1.51 2004/11/16 18:28:59 dwmw2 Exp $
+ *
+ * 02-21-2002 Thomas Gleixner <gleixner@autronix.de>
+ * added support for read_oob, write_oob
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/config.h>
+#include <linux/kmod.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/compatmac.h>
+
+/* Our partition linked list */
+static LIST_HEAD(mtd_partitions);
+
+/* Our partition node structure */
+struct mtd_part {
+ struct mtd_info mtd;
+ struct mtd_info *master;
+ u_int32_t offset;
+ int index;
+ struct list_head list;
+ int registered;
+};
+
+/*
+ * Given a pointer to the MTD object in the mtd_part structure, we can retrieve
+ * the pointer to that structure with this macro.
+ */
+#define PART(x) ((struct mtd_part *)(x))
+
+
+/*
+ * MTD methods which simply translate the effective address and pass through
+ * to the _real_ device.
+ */
+
+static int part_read (struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct mtd_part *part = PART(mtd);
+ if (from >= mtd->size)
+ len = 0;
+ else if (from + len > mtd->size)
+ len = mtd->size - from;
+ if (part->master->read_ecc == NULL)
+ return part->master->read (part->master, from + part->offset,
+ len, retlen, buf);
+ else
+ return part->master->read_ecc (part->master, from + part->offset,
+ len, retlen, buf, NULL, &mtd->oobinfo);
+}
+
+static int part_point (struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char **buf)
+{
+ struct mtd_part *part = PART(mtd);
+ if (from >= mtd->size)
+ len = 0;
+ else if (from + len > mtd->size)
+ len = mtd->size - from;
+ return part->master->point (part->master, from + part->offset,
+ len, retlen, buf);
+}
+static void part_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from, size_t len)
+{
+ struct mtd_part *part = PART(mtd);
+
+ part->master->unpoint (part->master, addr, from + part->offset, len);
+}
+
+
+static int part_read_ecc (struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf, u_char *eccbuf, struct nand_oobinfo *oobsel)
+{
+ struct mtd_part *part = PART(mtd);
+ if (oobsel == NULL)
+ oobsel = &mtd->oobinfo;
+ if (from >= mtd->size)
+ len = 0;
+ else if (from + len > mtd->size)
+ len = mtd->size - from;
+ return part->master->read_ecc (part->master, from + part->offset,
+ len, retlen, buf, eccbuf, oobsel);
+}
+
+static int part_read_oob (struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct mtd_part *part = PART(mtd);
+ if (from >= mtd->size)
+ len = 0;
+ else if (from + len > mtd->size)
+ len = mtd->size - from;
+ return part->master->read_oob (part->master, from + part->offset,
+ len, retlen, buf);
+}
+
+static int part_read_user_prot_reg (struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct mtd_part *part = PART(mtd);
+ return part->master->read_user_prot_reg (part->master, from,
+ len, retlen, buf);
+}
+
+static int part_read_fact_prot_reg (struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct mtd_part *part = PART(mtd);
+ return part->master->read_fact_prot_reg (part->master, from,
+ len, retlen, buf);
+}
+
+static int part_write (struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct mtd_part *part = PART(mtd);
+ if (!(mtd->flags & MTD_WRITEABLE))
+ return -EROFS;
+ if (to >= mtd->size)
+ len = 0;
+ else if (to + len > mtd->size)
+ len = mtd->size - to;
+ if (part->master->write_ecc == NULL)
+ return part->master->write (part->master, to + part->offset,
+ len, retlen, buf);
+ else
+ return part->master->write_ecc (part->master, to + part->offset,
+ len, retlen, buf, NULL, &mtd->oobinfo);
+
+}
+
+static int part_write_ecc (struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf,
+ u_char *eccbuf, struct nand_oobinfo *oobsel)
+{
+ struct mtd_part *part = PART(mtd);
+ if (!(mtd->flags & MTD_WRITEABLE))
+ return -EROFS;
+ if (oobsel == NULL)
+ oobsel = &mtd->oobinfo;
+ if (to >= mtd->size)
+ len = 0;
+ else if (to + len > mtd->size)
+ len = mtd->size - to;
+ return part->master->write_ecc (part->master, to + part->offset,
+ len, retlen, buf, eccbuf, oobsel);
+}
+
+static int part_write_oob (struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct mtd_part *part = PART(mtd);
+ if (!(mtd->flags & MTD_WRITEABLE))
+ return -EROFS;
+ if (to >= mtd->size)
+ len = 0;
+ else if (to + len > mtd->size)
+ len = mtd->size - to;
+ return part->master->write_oob (part->master, to + part->offset,
+ len, retlen, buf);
+}
+
+static int part_write_user_prot_reg (struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct mtd_part *part = PART(mtd);
+ return part->master->write_user_prot_reg (part->master, from,
+ len, retlen, buf);
+}
+
+static int part_writev (struct mtd_info *mtd, const struct kvec *vecs,
+ unsigned long count, loff_t to, size_t *retlen)
+{
+ struct mtd_part *part = PART(mtd);
+ if (!(mtd->flags & MTD_WRITEABLE))
+ return -EROFS;
+ if (part->master->writev_ecc == NULL)
+ return part->master->writev (part->master, vecs, count,
+ to + part->offset, retlen);
+ else
+ return part->master->writev_ecc (part->master, vecs, count,
+ to + part->offset, retlen,
+ NULL, &mtd->oobinfo);
+}
+
+static int part_readv (struct mtd_info *mtd, struct kvec *vecs,
+ unsigned long count, loff_t from, size_t *retlen)
+{
+ struct mtd_part *part = PART(mtd);
+ if (part->master->readv_ecc == NULL)
+ return part->master->readv (part->master, vecs, count,
+ from + part->offset, retlen);
+ else
+ return part->master->readv_ecc (part->master, vecs, count,
+ from + part->offset, retlen,
+ NULL, &mtd->oobinfo);
+}
+
+static int part_writev_ecc (struct mtd_info *mtd, const struct kvec *vecs,
+ unsigned long count, loff_t to, size_t *retlen,
+ u_char *eccbuf, struct nand_oobinfo *oobsel)
+{
+ struct mtd_part *part = PART(mtd);
+ if (!(mtd->flags & MTD_WRITEABLE))
+ return -EROFS;
+ if (oobsel == NULL)
+ oobsel = &mtd->oobinfo;
+ return part->master->writev_ecc (part->master, vecs, count,
+ to + part->offset, retlen,
+ eccbuf, oobsel);
+}
+
+static int part_readv_ecc (struct mtd_info *mtd, struct kvec *vecs,
+ unsigned long count, loff_t from, size_t *retlen,
+ u_char *eccbuf, struct nand_oobinfo *oobsel)
+{
+ struct mtd_part *part = PART(mtd);
+ if (oobsel == NULL)
+ oobsel = &mtd->oobinfo;
+ return part->master->readv_ecc (part->master, vecs, count,
+ from + part->offset, retlen,
+ eccbuf, oobsel);
+}
+
+static int part_erase (struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct mtd_part *part = PART(mtd);
+ int ret;
+ if (!(mtd->flags & MTD_WRITEABLE))
+ return -EROFS;
+ if (instr->addr >= mtd->size)
+ return -EINVAL;
+ instr->addr += part->offset;
+ ret = part->master->erase(part->master, instr);
+ return ret;
+}
+
+void mtd_erase_callback(struct erase_info *instr)
+{
+ if (instr->mtd->erase == part_erase) {
+ struct mtd_part *part = PART(instr->mtd);
+
+ if (instr->fail_addr != 0xffffffff)
+ instr->fail_addr -= part->offset;
+ instr->addr -= part->offset;
+ }
+ if (instr->callback)
+ instr->callback(instr);
+}
+EXPORT_SYMBOL_GPL(mtd_erase_callback);
+
+static int part_lock (struct mtd_info *mtd, loff_t ofs, size_t len)
+{
+ struct mtd_part *part = PART(mtd);
+ if ((len + ofs) > mtd->size)
+ return -EINVAL;
+ return part->master->lock(part->master, ofs + part->offset, len);
+}
+
+static int part_unlock (struct mtd_info *mtd, loff_t ofs, size_t len)
+{
+ struct mtd_part *part = PART(mtd);
+ if ((len + ofs) > mtd->size)
+ return -EINVAL;
+ return part->master->unlock(part->master, ofs + part->offset, len);
+}
+
+static void part_sync(struct mtd_info *mtd)
+{
+ struct mtd_part *part = PART(mtd);
+ part->master->sync(part->master);
+}
+
+static int part_suspend(struct mtd_info *mtd)
+{
+ struct mtd_part *part = PART(mtd);
+ return part->master->suspend(part->master);
+}
+
+static void part_resume(struct mtd_info *mtd)
+{
+ struct mtd_part *part = PART(mtd);
+ part->master->resume(part->master);
+}
+
+static int part_block_isbad (struct mtd_info *mtd, loff_t ofs)
+{
+ struct mtd_part *part = PART(mtd);
+ if (ofs >= mtd->size)
+ return -EINVAL;
+ ofs += part->offset;
+ return part->master->block_isbad(part->master, ofs);
+}
+
+static int part_block_markbad (struct mtd_info *mtd, loff_t ofs)
+{
+ struct mtd_part *part = PART(mtd);
+ if (!(mtd->flags & MTD_WRITEABLE))
+ return -EROFS;
+ if (ofs >= mtd->size)
+ return -EINVAL;
+ ofs += part->offset;
+ return part->master->block_markbad(part->master, ofs);
+}
+
+/*
+ * This function unregisters and destroy all slave MTD objects which are
+ * attached to the given master MTD object.
+ */
+
+int del_mtd_partitions(struct mtd_info *master)
+{
+ struct list_head *node;
+ struct mtd_part *slave;
+
+ for (node = mtd_partitions.next;
+ node != &mtd_partitions;
+ node = node->next) {
+ slave = list_entry(node, struct mtd_part, list);
+ if (slave->master == master) {
+ struct list_head *prev = node->prev;
+ __list_del(prev, node->next);
+ if(slave->registered)
+ del_mtd_device(&slave->mtd);
+ kfree(slave);
+ node = prev;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * This function, given a master MTD object and a partition table, creates
+ * and registers slave MTD objects which are bound to the master according to
+ * the partition definitions.
+ * (Q: should we register the master MTD object as well?)
+ */
+
+int add_mtd_partitions(struct mtd_info *master,
+ const struct mtd_partition *parts,
+ int nbparts)
+{
+ struct mtd_part *slave;
+ u_int32_t cur_offset = 0;
+ int i;
+
+ printk (KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);
+
+ for (i = 0; i < nbparts; i++) {
+
+ /* allocate the partition structure */
+ slave = kmalloc (sizeof(*slave), GFP_KERNEL);
+ if (!slave) {
+ printk ("memory allocation error while creating partitions for \"%s\"\n",
+ master->name);
+ del_mtd_partitions(master);
+ return -ENOMEM;
+ }
+ memset(slave, 0, sizeof(*slave));
+ list_add(&slave->list, &mtd_partitions);
+
+ /* set up the MTD object for this partition */
+ slave->mtd.type = master->type;
+ slave->mtd.flags = master->flags & ~parts[i].mask_flags;
+ slave->mtd.size = parts[i].size;
+ slave->mtd.oobblock = master->oobblock;
+ slave->mtd.oobsize = master->oobsize;
+ slave->mtd.ecctype = master->ecctype;
+ slave->mtd.eccsize = master->eccsize;
+
+ slave->mtd.name = parts[i].name;
+ slave->mtd.bank_size = master->bank_size;
+ slave->mtd.owner = master->owner;
+
+ slave->mtd.read = part_read;
+ slave->mtd.write = part_write;
+
+ if(master->point && master->unpoint){
+ slave->mtd.point = part_point;
+ slave->mtd.unpoint = part_unpoint;
+ }
+
+ if (master->read_ecc)
+ slave->mtd.read_ecc = part_read_ecc;
+ if (master->write_ecc)
+ slave->mtd.write_ecc = part_write_ecc;
+ if (master->read_oob)
+ slave->mtd.read_oob = part_read_oob;
+ if (master->write_oob)
+ slave->mtd.write_oob = part_write_oob;
+ if(master->read_user_prot_reg)
+ slave->mtd.read_user_prot_reg = part_read_user_prot_reg;
+ if(master->read_fact_prot_reg)
+ slave->mtd.read_fact_prot_reg = part_read_fact_prot_reg;
+ if(master->write_user_prot_reg)
+ slave->mtd.write_user_prot_reg = part_write_user_prot_reg;
+ if (master->sync)
+ slave->mtd.sync = part_sync;
+ if (!i && master->suspend && master->resume) {
+ slave->mtd.suspend = part_suspend;
+ slave->mtd.resume = part_resume;
+ }
+ if (master->writev)
+ slave->mtd.writev = part_writev;
+ if (master->readv)
+ slave->mtd.readv = part_readv;
+ if (master->writev_ecc)
+ slave->mtd.writev_ecc = part_writev_ecc;
+ if (master->readv_ecc)
+ slave->mtd.readv_ecc = part_readv_ecc;
+ if (master->lock)
+ slave->mtd.lock = part_lock;
+ if (master->unlock)
+ slave->mtd.unlock = part_unlock;
+ if (master->block_isbad)
+ slave->mtd.block_isbad = part_block_isbad;
+ if (master->block_markbad)
+ slave->mtd.block_markbad = part_block_markbad;
+ slave->mtd.erase = part_erase;
+ slave->master = master;
+ slave->offset = parts[i].offset;
+ slave->index = i;
+
+ if (slave->offset == MTDPART_OFS_APPEND)
+ slave->offset = cur_offset;
+ if (slave->offset == MTDPART_OFS_NXTBLK) {
+ u_int32_t emask = master->erasesize-1;
+ slave->offset = (cur_offset + emask) & ~emask;
+ if (slave->offset != cur_offset) {
+ printk(KERN_NOTICE "Moving partition %d: "
+ "0x%08x -> 0x%08x\n", i,
+ cur_offset, slave->offset);
+ }
+ }
+ if (slave->mtd.size == MTDPART_SIZ_FULL)
+ slave->mtd.size = master->size - slave->offset;
+ cur_offset = slave->offset + slave->mtd.size;
+
+ printk (KERN_NOTICE "0x%08x-0x%08x : \"%s\"\n", slave->offset,
+ slave->offset + slave->mtd.size, slave->mtd.name);
+
+ /* let's do some sanity checks */
+ if (slave->offset >= master->size) {
+ /* let's register it anyway to preserve ordering */
+ slave->offset = 0;
+ slave->mtd.size = 0;
+ printk ("mtd: partition \"%s\" is out of reach -- disabled\n",
+ parts[i].name);
+ }
+ if (slave->offset + slave->mtd.size > master->size) {
+ slave->mtd.size = master->size - slave->offset;
+ printk ("mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#x\n",
+ parts[i].name, master->name, slave->mtd.size);
+ }
+ if (master->numeraseregions>1) {
+ /* Deal with variable erase size stuff */
+ int i;
+ struct mtd_erase_region_info *regions = master->eraseregions;
+
+ /* Find the first erase regions which is part of this partition. */
+ for (i=0; i < master->numeraseregions && slave->offset >= regions[i].offset; i++)
+ ;
+
+ for (i--; i < master->numeraseregions && slave->offset + slave->mtd.size > regions[i].offset; i++) {
+ if (slave->mtd.erasesize < regions[i].erasesize) {
+ slave->mtd.erasesize = regions[i].erasesize;
+ }
+ }
+ } else {
+ /* Single erase size */
+ slave->mtd.erasesize = master->erasesize;
+ }
+
+ if ((slave->mtd.flags & MTD_WRITEABLE) &&
+ (slave->offset % slave->mtd.erasesize)) {
+ /* Doesn't start on a boundary of major erase size */
+ /* FIXME: Let it be writable if it is on a boundary of _minor_ erase size though */
+ slave->mtd.flags &= ~MTD_WRITEABLE;
+ printk ("mtd: partition \"%s\" doesn't start on an erase block boundary -- force read-only\n",
+ parts[i].name);
+ }
+ if ((slave->mtd.flags & MTD_WRITEABLE) &&
+ (slave->mtd.size % slave->mtd.erasesize)) {
+ slave->mtd.flags &= ~MTD_WRITEABLE;
+ printk ("mtd: partition \"%s\" doesn't end on an erase block -- force read-only\n",
+ parts[i].name);
+ }
+
+ /* copy oobinfo from master */
+ memcpy(&slave->mtd.oobinfo, &master->oobinfo, sizeof(slave->mtd.oobinfo));
+
+ if(parts[i].mtdp)
+ { /* store the object pointer (caller may or may not register it */
+ *parts[i].mtdp = &slave->mtd;
+ slave->registered = 0;
+ }
+ else
+ {
+ /* register our partition */
+ add_mtd_device(&slave->mtd);
+ slave->registered = 1;
+ }
+ }
+
+ return 0;
+}
+
+EXPORT_SYMBOL(add_mtd_partitions);
+EXPORT_SYMBOL(del_mtd_partitions);
+
+static DEFINE_SPINLOCK(part_parser_lock);
+static LIST_HEAD(part_parsers);
+
+static struct mtd_part_parser *get_partition_parser(const char *name)
+{
+ struct list_head *this;
+ void *ret = NULL;
+ spin_lock(&part_parser_lock);
+
+ list_for_each(this, &part_parsers) {
+ struct mtd_part_parser *p = list_entry(this, struct mtd_part_parser, list);
+
+ if (!strcmp(p->name, name) && try_module_get(p->owner)) {
+ ret = p;
+ break;
+ }
+ }
+ spin_unlock(&part_parser_lock);
+
+ return ret;
+}
+
+int register_mtd_parser(struct mtd_part_parser *p)
+{
+ spin_lock(&part_parser_lock);
+ list_add(&p->list, &part_parsers);
+ spin_unlock(&part_parser_lock);
+
+ return 0;
+}
+
+int deregister_mtd_parser(struct mtd_part_parser *p)
+{
+ spin_lock(&part_parser_lock);
+ list_del(&p->list);
+ spin_unlock(&part_parser_lock);
+ return 0;
+}
+
+int parse_mtd_partitions(struct mtd_info *master, const char **types,
+ struct mtd_partition **pparts, unsigned long origin)
+{
+ struct mtd_part_parser *parser;
+ int ret = 0;
+
+ for ( ; ret <= 0 && *types; types++) {
+ parser = get_partition_parser(*types);
+#ifdef CONFIG_KMOD
+ if (!parser && !request_module("%s", *types))
+ parser = get_partition_parser(*types);
+#endif
+ if (!parser) {
+ printk(KERN_NOTICE "%s partition parsing not available\n",
+ *types);
+ continue;
+ }
+ ret = (*parser->parse_fn)(master, pparts, origin);
+ if (ret > 0) {
+ printk(KERN_NOTICE "%d %s partitions found on MTD device %s\n",
+ ret, parser->name, master->name);
+ }
+ put_partition_parser(parser);
+ }
+ return ret;
+}
+
+EXPORT_SYMBOL_GPL(parse_mtd_partitions);
+EXPORT_SYMBOL_GPL(register_mtd_parser);
+EXPORT_SYMBOL_GPL(deregister_mtd_parser);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nicolas Pitre <nico@cam.org>");
+MODULE_DESCRIPTION("Generic support for partitioning of MTD devices");
+
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
new file mode 100644
index 000000000000..f7801eb730ce
--- /dev/null
+++ b/drivers/mtd/nand/Kconfig
@@ -0,0 +1,207 @@
+# drivers/mtd/nand/Kconfig
+# $Id: Kconfig,v 1.26 2005/01/05 12:42:24 dwmw2 Exp $
+
+menu "NAND Flash Device Drivers"
+ depends on MTD!=n
+
+config MTD_NAND
+ tristate "NAND Device Support"
+ depends on MTD
+ select MTD_NAND_IDS
+ help
+ This enables support for accessing all type of NAND flash
+ devices. For further information see
+ <http://www.linux-mtd.infradead.org/tech/nand.html>.
+
+config MTD_NAND_VERIFY_WRITE
+ bool "Verify NAND page writes"
+ depends on MTD_NAND
+ help
+ This adds an extra check when data is written to the flash. The
+ NAND flash device internally checks only bits transitioning
+ from 1 to 0. There is a rare possibility that even though the
+ device thinks the write was successful, a bit could have been
+ flipped accidentaly due to device wear or something else.
+
+config MTD_NAND_AUTCPU12
+ tristate "SmartMediaCard on autronix autcpu12 board"
+ depends on ARM && MTD_NAND && ARCH_AUTCPU12
+ help
+ This enables the driver for the autronix autcpu12 board to
+ access the SmartMediaCard.
+
+config MTD_NAND_EDB7312
+ tristate "Support for Cirrus Logic EBD7312 evaluation board"
+ depends on ARM && MTD_NAND && ARCH_EDB7312
+ help
+ This enables the driver for the Cirrus Logic EBD7312 evaluation
+ board to access the onboard NAND Flash.
+
+config MTD_NAND_H1900
+ tristate "iPAQ H1900 flash"
+ depends on ARM && MTD_NAND && ARCH_PXA && MTD_PARTITIONS
+ help
+ This enables the driver for the iPAQ h1900 flash.
+
+config MTD_NAND_SPIA
+ tristate "NAND Flash device on SPIA board"
+ depends on ARM && ARCH_P720T && MTD_NAND
+ help
+ If you had to ask, you don't have one. Say 'N'.
+
+config MTD_NAND_TOTO
+ tristate "NAND Flash device on TOTO board"
+ depends on ARM && ARCH_OMAP && MTD_NAND
+ help
+ Support for NAND flash on Texas Instruments Toto platform.
+
+config MTD_NAND_IDS
+ tristate
+
+config MTD_NAND_TX4925NDFMC
+ tristate "SmartMedia Card on Toshiba RBTX4925 reference board"
+ depends on TOSHIBA_RBTX4925 && MTD_NAND && TOSHIBA_RBTX4925_MPLEX_NAND
+ help
+ This enables the driver for the NAND flash device found on the
+ Toshiba RBTX4925 reference board, which is a SmartMediaCard.
+
+config MTD_NAND_TX4938NDFMC
+ tristate "NAND Flash device on Toshiba RBTX4938 reference board"
+ depends on TOSHIBA_RBTX4938 && MTD_NAND && TOSHIBA_RBTX4938_MPLEX_NAND
+ help
+ This enables the driver for the NAND flash device found on the
+ Toshiba RBTX4938 reference board.
+
+config MTD_NAND_AU1550
+ tristate "Au1550 NAND support"
+ depends on SOC_AU1550 && MTD_NAND
+ help
+ This enables the driver for the NAND flash controller on the
+ AMD/Alchemy 1550 SOC.
+
+config MTD_NAND_RTC_FROM4
+ tristate "Renesas Flash ROM 4-slot interface board (FROM_BOARD4)"
+ depends on MTD_NAND && SH_SOLUTION_ENGINE
+ select REED_SOLOMON
+ select REED_SOLOMON_DEC8
+ help
+ This enables the driver for the Renesas Technology AG-AND
+ flash interface board (FROM_BOARD4)
+
+config MTD_NAND_PPCHAMELEONEVB
+ tristate "NAND Flash device on PPChameleonEVB board"
+ depends on PPCHAMELEONEVB && MTD_NAND
+ help
+ This enables the NAND flash driver on the PPChameleon EVB Board.
+
+config MTD_NAND_S3C2410
+ tristate "NAND Flash support for S3C2410 SoC"
+ depends on ARCH_S3C2410 && MTD_NAND
+ help
+ This enables the NAND flash controller on the S3C2410.
+
+ No board specfic support is done by this driver, each board
+ must advertise a platform_device for the driver to attach.
+
+config MTD_NAND_S3C2410_DEBUG
+ bool "S3C2410 NAND driver debug"
+ depends on MTD_NAND_S3C2410
+ help
+ Enable debugging of the S3C2410 NAND driver
+
+config MTD_NAND_S3C2410_HWECC
+ bool "S3C2410 NAND Hardware ECC"
+ depends on MTD_NAND_S3C2410
+ help
+ Enable the use of the S3C2410's internal ECC generator when
+ using NAND. Early versions of the chip have had problems with
+ incorrect ECC generation, and if using these, the default of
+ software ECC is preferable.
+
+ If you lay down a device with the hardware ECC, then you will
+ currently not be able to switch to software, as there is no
+ implementation for ECC method used by the S3C2410
+
+config MTD_NAND_DISKONCHIP
+ tristate "DiskOnChip 2000, Millennium and Millennium Plus (NAND reimplementation) (EXPERIMENTAL)"
+ depends on MTD_NAND && EXPERIMENTAL
+ select REED_SOLOMON
+ select REED_SOLOMON_DEC16
+ help
+ This is a reimplementation of M-Systems DiskOnChip 2000,
+ Millennium and Millennium Plus as a standard NAND device driver,
+ as opposed to the earlier self-contained MTD device drivers.
+ This should enable, among other things, proper JFFS2 operation on
+ these devices.
+
+config MTD_NAND_DISKONCHIP_PROBE_ADVANCED
+ bool "Advanced detection options for DiskOnChip"
+ depends on MTD_NAND_DISKONCHIP
+ help
+ This option allows you to specify nonstandard address at which to
+ probe for a DiskOnChip, or to change the detection options. You
+ are unlikely to need any of this unless you are using LinuxBIOS.
+ Say 'N'.
+
+config MTD_NAND_DISKONCHIP_PROBE_ADDRESS
+ hex "Physical address of DiskOnChip" if MTD_NAND_DISKONCHIP_PROBE_ADVANCED
+ depends on MTD_NAND_DISKONCHIP
+ default "0"
+ ---help---
+ By default, the probe for DiskOnChip devices will look for a
+ DiskOnChip at every multiple of 0x2000 between 0xC8000 and 0xEE000.
+ This option allows you to specify a single address at which to probe
+ for the device, which is useful if you have other devices in that
+ range which get upset when they are probed.
+
+ (Note that on PowerPC, the normal probe will only check at
+ 0xE4000000.)
+
+ Normally, you should leave this set to zero, to allow the probe at
+ the normal addresses.
+
+config MTD_NAND_DISKONCHIP_PROBE_HIGH
+ bool "Probe high addresses"
+ depends on MTD_NAND_DISKONCHIP_PROBE_ADVANCED
+ help
+ By default, the probe for DiskOnChip devices will look for a
+ DiskOnChip at every multiple of 0x2000 between 0xC8000 and 0xEE000.
+ This option changes to make it probe between 0xFFFC8000 and
+ 0xFFFEE000. Unless you are using LinuxBIOS, this is unlikely to be
+ useful to you. Say 'N'.
+
+config MTD_NAND_DISKONCHIP_BBTWRITE
+ bool "Allow BBT writes on DiskOnChip Millennium and 2000TSOP"
+ depends on MTD_NAND_DISKONCHIP
+ help
+ On DiskOnChip devices shipped with the INFTL filesystem (Millennium
+ and 2000 TSOP/Alon), Linux reserves some space at the end of the
+ device for the Bad Block Table (BBT). If you have existing INFTL
+ data on your device (created by non-Linux tools such as M-Systems'
+ DOS drivers), your data might overlap the area Linux wants to use for
+ the BBT. If this is a concern for you, leave this option disabled and
+ Linux will not write BBT data into this area.
+ The downside of leaving this option disabled is that if bad blocks
+ are detected by Linux, they will not be recorded in the BBT, which
+ could cause future problems.
+ Once you enable this option, new filesystems (INFTL or others, created
+ in Linux or other operating systems) will not use the reserved area.
+ The only reason not to enable this option is to prevent damage to
+ preexisting filesystems.
+ Even if you leave this disabled, you can enable BBT writes at module
+ load time (assuming you build diskonchip as a module) with the module
+ parameter "inftl_bbt_write=1".
+
+ config MTD_NAND_SHARPSL
+ bool "Support for NAND Flash on Sharp SL Series (C7xx + others)"
+ depends on MTD_NAND && ARCH_PXA
+
+ config MTD_NAND_NANDSIM
+ bool "Support for NAND Flash Simulator"
+ depends on MTD_NAND && MTD_PARTITIONS
+
+ help
+ The simulator may simulate verious NAND flash chips for the
+ MTD nand layer.
+
+endmenu
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
new file mode 100644
index 000000000000..d9dc8cc2da8c
--- /dev/null
+++ b/drivers/mtd/nand/Makefile
@@ -0,0 +1,24 @@
+#
+# linux/drivers/nand/Makefile
+#
+# $Id: Makefile.common,v 1.15 2004/11/26 12:28:22 dedekind Exp $
+
+obj-$(CONFIG_MTD_NAND) += nand.o nand_ecc.o
+obj-$(CONFIG_MTD_NAND_IDS) += nand_ids.o
+
+obj-$(CONFIG_MTD_NAND_SPIA) += spia.o
+obj-$(CONFIG_MTD_NAND_TOTO) += toto.o
+obj-$(CONFIG_MTD_NAND_AUTCPU12) += autcpu12.o
+obj-$(CONFIG_MTD_NAND_EDB7312) += edb7312.o
+obj-$(CONFIG_MTD_NAND_TX4925NDFMC) += tx4925ndfmc.o
+obj-$(CONFIG_MTD_NAND_TX4938NDFMC) += tx4938ndfmc.o
+obj-$(CONFIG_MTD_NAND_AU1550) += au1550nd.o
+obj-$(CONFIG_MTD_NAND_PPCHAMELEONEVB) += ppchameleonevb.o
+obj-$(CONFIG_MTD_NAND_S3C2410) += s3c2410.o
+obj-$(CONFIG_MTD_NAND_DISKONCHIP) += diskonchip.o
+obj-$(CONFIG_MTD_NAND_H1900) += h1910.o
+obj-$(CONFIG_MTD_NAND_RTC_FROM4) += rtc_from4.o
+obj-$(CONFIG_MTD_NAND_SHARPSL) += sharpsl.o
+obj-$(CONFIG_MTD_NAND_NANDSIM) += nandsim.o
+
+nand-objs = nand_base.o nand_bbt.o
diff --git a/drivers/mtd/nand/au1550nd.c b/drivers/mtd/nand/au1550nd.c
new file mode 100644
index 000000000000..4c7719ce3f48
--- /dev/null
+++ b/drivers/mtd/nand/au1550nd.c
@@ -0,0 +1,477 @@
+/*
+ * drivers/mtd/nand/au1550nd.c
+ *
+ * Copyright (C) 2004 Embedded Edge, LLC
+ *
+ * $Id: au1550nd.c,v 1.11 2004/11/04 12:53:10 gleixner Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <asm/io.h>
+
+/* fixme: this is ugly */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 0)
+#include <asm/mach-au1x00/au1000.h>
+#ifdef CONFIG_MIPS_PB1550
+#include <asm/mach-pb1x00/pb1550.h>
+#endif
+#ifdef CONFIG_MIPS_DB1550
+#include <asm/mach-db1x00/db1x00.h>
+#endif
+#else
+#include <asm/au1000.h>
+#ifdef CONFIG_MIPS_PB1550
+#include <asm/pb1550.h>
+#endif
+#ifdef CONFIG_MIPS_DB1550
+#include <asm/db1x00.h>
+#endif
+#endif
+
+/*
+ * MTD structure for NAND controller
+ */
+static struct mtd_info *au1550_mtd = NULL;
+static void __iomem *p_nand;
+static int nand_width = 1; /* default x8*/
+
+#define NAND_CS 1
+
+/*
+ * Define partitions for flash device
+ */
+const static struct mtd_partition partition_info[] = {
+#ifdef CONFIG_MIPS_PB1550
+#define NUM_PARTITIONS 2
+ {
+ .name = "Pb1550 NAND FS 0",
+ .offset = 0,
+ .size = 8*1024*1024
+ },
+ {
+ .name = "Pb1550 NAND FS 1",
+ .offset = MTDPART_OFS_APPEND,
+ .size = MTDPART_SIZ_FULL
+ }
+#endif
+#ifdef CONFIG_MIPS_DB1550
+#define NUM_PARTITIONS 2
+ {
+ .name = "Db1550 NAND FS 0",
+ .offset = 0,
+ .size = 8*1024*1024
+ },
+ {
+ .name = "Db1550 NAND FS 1",
+ .offset = MTDPART_OFS_APPEND,
+ .size = MTDPART_SIZ_FULL
+ }
+#endif
+};
+
+
+/**
+ * au_read_byte - read one byte from the chip
+ * @mtd: MTD device structure
+ *
+ * read function for 8bit buswith
+ */
+static u_char au_read_byte(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ u_char ret = readb(this->IO_ADDR_R);
+ au_sync();
+ return ret;
+}
+
+/**
+ * au_write_byte - write one byte to the chip
+ * @mtd: MTD device structure
+ * @byte: pointer to data byte to write
+ *
+ * write function for 8it buswith
+ */
+static void au_write_byte(struct mtd_info *mtd, u_char byte)
+{
+ struct nand_chip *this = mtd->priv;
+ writeb(byte, this->IO_ADDR_W);
+ au_sync();
+}
+
+/**
+ * au_read_byte16 - read one byte endianess aware from the chip
+ * @mtd: MTD device structure
+ *
+ * read function for 16bit buswith with
+ * endianess conversion
+ */
+static u_char au_read_byte16(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ u_char ret = (u_char) cpu_to_le16(readw(this->IO_ADDR_R));
+ au_sync();
+ return ret;
+}
+
+/**
+ * au_write_byte16 - write one byte endianess aware to the chip
+ * @mtd: MTD device structure
+ * @byte: pointer to data byte to write
+ *
+ * write function for 16bit buswith with
+ * endianess conversion
+ */
+static void au_write_byte16(struct mtd_info *mtd, u_char byte)
+{
+ struct nand_chip *this = mtd->priv;
+ writew(le16_to_cpu((u16) byte), this->IO_ADDR_W);
+ au_sync();
+}
+
+/**
+ * au_read_word - read one word from the chip
+ * @mtd: MTD device structure
+ *
+ * read function for 16bit buswith without
+ * endianess conversion
+ */
+static u16 au_read_word(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ u16 ret = readw(this->IO_ADDR_R);
+ au_sync();
+ return ret;
+}
+
+/**
+ * au_write_word - write one word to the chip
+ * @mtd: MTD device structure
+ * @word: data word to write
+ *
+ * write function for 16bit buswith without
+ * endianess conversion
+ */
+static void au_write_word(struct mtd_info *mtd, u16 word)
+{
+ struct nand_chip *this = mtd->priv;
+ writew(word, this->IO_ADDR_W);
+ au_sync();
+}
+
+/**
+ * au_write_buf - write buffer to chip
+ * @mtd: MTD device structure
+ * @buf: data buffer
+ * @len: number of bytes to write
+ *
+ * write function for 8bit buswith
+ */
+static void au_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+
+ for (i=0; i<len; i++) {
+ writeb(buf[i], this->IO_ADDR_W);
+ au_sync();
+ }
+}
+
+/**
+ * au_read_buf - read chip data into buffer
+ * @mtd: MTD device structure
+ * @buf: buffer to store date
+ * @len: number of bytes to read
+ *
+ * read function for 8bit buswith
+ */
+static void au_read_buf(struct mtd_info *mtd, u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+
+ for (i=0; i<len; i++) {
+ buf[i] = readb(this->IO_ADDR_R);
+ au_sync();
+ }
+}
+
+/**
+ * au_verify_buf - Verify chip data against buffer
+ * @mtd: MTD device structure
+ * @buf: buffer containing the data to compare
+ * @len: number of bytes to compare
+ *
+ * verify function for 8bit buswith
+ */
+static int au_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+
+ for (i=0; i<len; i++) {
+ if (buf[i] != readb(this->IO_ADDR_R))
+ return -EFAULT;
+ au_sync();
+ }
+
+ return 0;
+}
+
+/**
+ * au_write_buf16 - write buffer to chip
+ * @mtd: MTD device structure
+ * @buf: data buffer
+ * @len: number of bytes to write
+ *
+ * write function for 16bit buswith
+ */
+static void au_write_buf16(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+ u16 *p = (u16 *) buf;
+ len >>= 1;
+
+ for (i=0; i<len; i++) {
+ writew(p[i], this->IO_ADDR_W);
+ au_sync();
+ }
+
+}
+
+/**
+ * au_read_buf16 - read chip data into buffer
+ * @mtd: MTD device structure
+ * @buf: buffer to store date
+ * @len: number of bytes to read
+ *
+ * read function for 16bit buswith
+ */
+static void au_read_buf16(struct mtd_info *mtd, u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+ u16 *p = (u16 *) buf;
+ len >>= 1;
+
+ for (i=0; i<len; i++) {
+ p[i] = readw(this->IO_ADDR_R);
+ au_sync();
+ }
+}
+
+/**
+ * au_verify_buf16 - Verify chip data against buffer
+ * @mtd: MTD device structure
+ * @buf: buffer containing the data to compare
+ * @len: number of bytes to compare
+ *
+ * verify function for 16bit buswith
+ */
+static int au_verify_buf16(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+ u16 *p = (u16 *) buf;
+ len >>= 1;
+
+ for (i=0; i<len; i++) {
+ if (p[i] != readw(this->IO_ADDR_R))
+ return -EFAULT;
+ au_sync();
+ }
+ return 0;
+}
+
+
+static void au1550_hwcontrol(struct mtd_info *mtd, int cmd)
+{
+ register struct nand_chip *this = mtd->priv;
+
+ switch(cmd){
+
+ case NAND_CTL_SETCLE: this->IO_ADDR_W = p_nand + MEM_STNAND_CMD; break;
+ case NAND_CTL_CLRCLE: this->IO_ADDR_W = p_nand + MEM_STNAND_DATA; break;
+
+ case NAND_CTL_SETALE: this->IO_ADDR_W = p_nand + MEM_STNAND_ADDR; break;
+ case NAND_CTL_CLRALE:
+ this->IO_ADDR_W = p_nand + MEM_STNAND_DATA;
+ /* FIXME: Nobody knows why this is neccecary,
+ * but it works only that way */
+ udelay(1);
+ break;
+
+ case NAND_CTL_SETNCE:
+ /* assert (force assert) chip enable */
+ au_writel((1<<(4+NAND_CS)) , MEM_STNDCTL); break;
+ break;
+
+ case NAND_CTL_CLRNCE:
+ /* deassert chip enable */
+ au_writel(0, MEM_STNDCTL); break;
+ break;
+ }
+
+ this->IO_ADDR_R = this->IO_ADDR_W;
+
+ /* Drain the writebuffer */
+ au_sync();
+}
+
+int au1550_device_ready(struct mtd_info *mtd)
+{
+ int ret = (au_readl(MEM_STSTAT) & 0x1) ? 1 : 0;
+ au_sync();
+ return ret;
+}
+
+/*
+ * Main initialization routine
+ */
+int __init au1550_init (void)
+{
+ struct nand_chip *this;
+ u16 boot_swapboot = 0; /* default value */
+ int retval;
+
+ /* Allocate memory for MTD device structure and private data */
+ au1550_mtd = kmalloc (sizeof(struct mtd_info) +
+ sizeof (struct nand_chip), GFP_KERNEL);
+ if (!au1550_mtd) {
+ printk ("Unable to allocate NAND MTD dev structure.\n");
+ return -ENOMEM;
+ }
+
+ /* Get pointer to private data */
+ this = (struct nand_chip *) (&au1550_mtd[1]);
+
+ /* Initialize structures */
+ memset((char *) au1550_mtd, 0, sizeof(struct mtd_info));
+ memset((char *) this, 0, sizeof(struct nand_chip));
+
+ /* Link the private data with the MTD structure */
+ au1550_mtd->priv = this;
+
+
+ /* MEM_STNDCTL: disable ints, disable nand boot */
+ au_writel(0, MEM_STNDCTL);
+
+#ifdef CONFIG_MIPS_PB1550
+ /* set gpio206 high */
+ au_writel(au_readl(GPIO2_DIR) & ~(1<<6), GPIO2_DIR);
+
+ boot_swapboot = (au_readl(MEM_STSTAT) & (0x7<<1)) |
+ ((bcsr->status >> 6) & 0x1);
+ switch (boot_swapboot) {
+ case 0:
+ case 2:
+ case 8:
+ case 0xC:
+ case 0xD:
+ /* x16 NAND Flash */
+ nand_width = 0;
+ break;
+ case 1:
+ case 9:
+ case 3:
+ case 0xE:
+ case 0xF:
+ /* x8 NAND Flash */
+ nand_width = 1;
+ break;
+ default:
+ printk("Pb1550 NAND: bad boot:swap\n");
+ retval = -EINVAL;
+ goto outmem;
+ }
+#endif
+
+ /* Configure RCE1 - should be done by YAMON */
+ au_writel(0x5 | (nand_width << 22), 0xB4001010); /* MEM_STCFG1 */
+ au_writel(NAND_TIMING, 0xB4001014); /* MEM_STTIME1 */
+ au_sync();
+
+ /* setup and enable chip select, MEM_STADDR1 */
+ /* we really need to decode offsets only up till 0x20 */
+ au_writel((1<<28) | (NAND_PHYS_ADDR>>4) |
+ (((NAND_PHYS_ADDR + 0x1000)-1) & (0x3fff<<18)>>18),
+ MEM_STADDR1);
+ au_sync();
+
+ p_nand = ioremap(NAND_PHYS_ADDR, 0x1000);
+
+ /* Set address of hardware control function */
+ this->hwcontrol = au1550_hwcontrol;
+ this->dev_ready = au1550_device_ready;
+ /* 30 us command delay time */
+ this->chip_delay = 30;
+ this->eccmode = NAND_ECC_SOFT;
+
+ this->options = NAND_NO_AUTOINCR;
+
+ if (!nand_width)
+ this->options |= NAND_BUSWIDTH_16;
+
+ this->read_byte = (!nand_width) ? au_read_byte16 : au_read_byte;
+ this->write_byte = (!nand_width) ? au_write_byte16 : au_write_byte;
+ this->write_word = au_write_word;
+ this->read_word = au_read_word;
+ this->write_buf = (!nand_width) ? au_write_buf16 : au_write_buf;
+ this->read_buf = (!nand_width) ? au_read_buf16 : au_read_buf;
+ this->verify_buf = (!nand_width) ? au_verify_buf16 : au_verify_buf;
+
+ /* Scan to find existence of the device */
+ if (nand_scan (au1550_mtd, 1)) {
+ retval = -ENXIO;
+ goto outio;
+ }
+
+ /* Register the partitions */
+ add_mtd_partitions(au1550_mtd, partition_info, NUM_PARTITIONS);
+
+ return 0;
+
+ outio:
+ iounmap ((void *)p_nand);
+
+ outmem:
+ kfree (au1550_mtd);
+ return retval;
+}
+
+module_init(au1550_init);
+
+/*
+ * Clean up routine
+ */
+#ifdef MODULE
+static void __exit au1550_cleanup (void)
+{
+ struct nand_chip *this = (struct nand_chip *) &au1550_mtd[1];
+
+ /* Release resources, unregister device */
+ nand_release (au1550_mtd);
+
+ /* Free the MTD device structure */
+ kfree (au1550_mtd);
+
+ /* Unmap */
+ iounmap ((void *)p_nand);
+}
+module_exit(au1550_cleanup);
+#endif
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Embedded Edge, LLC");
+MODULE_DESCRIPTION("Board-specific glue layer for NAND flash on Pb1550 board");
diff --git a/drivers/mtd/nand/autcpu12.c b/drivers/mtd/nand/autcpu12.c
new file mode 100644
index 000000000000..4afa8ced05ad
--- /dev/null
+++ b/drivers/mtd/nand/autcpu12.c
@@ -0,0 +1,225 @@
+/*
+ * drivers/mtd/autcpu12.c
+ *
+ * Copyright (c) 2002 Thomas Gleixner <tgxl@linutronix.de>
+ *
+ * Derived from drivers/mtd/spia.c
+ * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com)
+ *
+ * $Id: autcpu12.c,v 1.22 2004/11/04 12:53:10 gleixner Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Overview:
+ * This is a device driver for the NAND flash device found on the
+ * autronix autcpu12 board, which is a SmartMediaCard. It supports
+ * 16MiB, 32MiB and 64MiB cards.
+ *
+ *
+ * 02-12-2002 TG Cleanup of module params
+ *
+ * 02-20-2002 TG adjusted for different rd/wr adress support
+ * added support for read device ready/busy line
+ * added page_cache
+ *
+ * 10-06-2002 TG 128K card support added
+ */
+
+#include <linux/version.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <asm/io.h>
+#include <asm/arch/hardware.h>
+#include <asm/sizes.h>
+#include <asm/arch/autcpu12.h>
+
+/*
+ * MTD structure for AUTCPU12 board
+ */
+static struct mtd_info *autcpu12_mtd = NULL;
+
+static int autcpu12_io_base = CS89712_VIRT_BASE;
+static int autcpu12_fio_pbase = AUTCPU12_PHYS_SMC;
+static int autcpu12_fio_ctrl = AUTCPU12_SMC_SELECT_OFFSET;
+static int autcpu12_pedr = AUTCPU12_SMC_PORT_OFFSET;
+static void __iomem * autcpu12_fio_base;
+
+/*
+ * Define partitions for flash devices
+ */
+static struct mtd_partition partition_info16k[] = {
+ { .name = "AUTCPU12 flash partition 1",
+ .offset = 0,
+ .size = 8 * SZ_1M },
+ { .name = "AUTCPU12 flash partition 2",
+ .offset = 8 * SZ_1M,
+ .size = 8 * SZ_1M },
+};
+
+static struct mtd_partition partition_info32k[] = {
+ { .name = "AUTCPU12 flash partition 1",
+ .offset = 0,
+ .size = 8 * SZ_1M },
+ { .name = "AUTCPU12 flash partition 2",
+ .offset = 8 * SZ_1M,
+ .size = 24 * SZ_1M },
+};
+
+static struct mtd_partition partition_info64k[] = {
+ { .name = "AUTCPU12 flash partition 1",
+ .offset = 0,
+ .size = 16 * SZ_1M },
+ { .name = "AUTCPU12 flash partition 2",
+ .offset = 16 * SZ_1M,
+ .size = 48 * SZ_1M },
+};
+
+static struct mtd_partition partition_info128k[] = {
+ { .name = "AUTCPU12 flash partition 1",
+ .offset = 0,
+ .size = 16 * SZ_1M },
+ { .name = "AUTCPU12 flash partition 2",
+ .offset = 16 * SZ_1M,
+ .size = 112 * SZ_1M },
+};
+
+#define NUM_PARTITIONS16K 2
+#define NUM_PARTITIONS32K 2
+#define NUM_PARTITIONS64K 2
+#define NUM_PARTITIONS128K 2
+/*
+ * hardware specific access to control-lines
+*/
+static void autcpu12_hwcontrol(struct mtd_info *mtd, int cmd)
+{
+
+ switch(cmd){
+
+ case NAND_CTL_SETCLE: (*(volatile unsigned char *) (autcpu12_io_base + autcpu12_pedr)) |= AUTCPU12_SMC_CLE; break;
+ case NAND_CTL_CLRCLE: (*(volatile unsigned char *) (autcpu12_io_base + autcpu12_pedr)) &= ~AUTCPU12_SMC_CLE; break;
+
+ case NAND_CTL_SETALE: (*(volatile unsigned char *) (autcpu12_io_base + autcpu12_pedr)) |= AUTCPU12_SMC_ALE; break;
+ case NAND_CTL_CLRALE: (*(volatile unsigned char *) (autcpu12_io_base + autcpu12_pedr)) &= ~AUTCPU12_SMC_ALE; break;
+
+ case NAND_CTL_SETNCE: (*(volatile unsigned char *) (autcpu12_fio_base + autcpu12_fio_ctrl)) = 0x01; break;
+ case NAND_CTL_CLRNCE: (*(volatile unsigned char *) (autcpu12_fio_base + autcpu12_fio_ctrl)) = 0x00; break;
+ }
+}
+
+/*
+* read device ready pin
+*/
+int autcpu12_device_ready(struct mtd_info *mtd)
+{
+
+ return ( (*(volatile unsigned char *) (autcpu12_io_base + autcpu12_pedr)) & AUTCPU12_SMC_RDY) ? 1 : 0;
+
+}
+
+/*
+ * Main initialization routine
+ */
+int __init autcpu12_init (void)
+{
+ struct nand_chip *this;
+ int err = 0;
+
+ /* Allocate memory for MTD device structure and private data */
+ autcpu12_mtd = kmalloc (sizeof(struct mtd_info) + sizeof (struct nand_chip),
+ GFP_KERNEL);
+ if (!autcpu12_mtd) {
+ printk ("Unable to allocate AUTCPU12 NAND MTD device structure.\n");
+ err = -ENOMEM;
+ goto out;
+ }
+
+ /* map physical adress */
+ autcpu12_fio_base = ioremap(autcpu12_fio_pbase,SZ_1K);
+ if(!autcpu12_fio_base){
+ printk("Ioremap autcpu12 SmartMedia Card failed\n");
+ err = -EIO;
+ goto out_mtd;
+ }
+
+ /* Get pointer to private data */
+ this = (struct nand_chip *) (&autcpu12_mtd[1]);
+
+ /* Initialize structures */
+ memset((char *) autcpu12_mtd, 0, sizeof(struct mtd_info));
+ memset((char *) this, 0, sizeof(struct nand_chip));
+
+ /* Link the private data with the MTD structure */
+ autcpu12_mtd->priv = this;
+
+ /* Set address of NAND IO lines */
+ this->IO_ADDR_R = autcpu12_fio_base;
+ this->IO_ADDR_W = autcpu12_fio_base;
+ this->hwcontrol = autcpu12_hwcontrol;
+ this->dev_ready = autcpu12_device_ready;
+ /* 20 us command delay time */
+ this->chip_delay = 20;
+ this->eccmode = NAND_ECC_SOFT;
+
+ /* Enable the following for a flash based bad block table */
+ /*
+ this->options = NAND_USE_FLASH_BBT;
+ */
+ this->options = NAND_USE_FLASH_BBT;
+
+ /* Scan to find existance of the device */
+ if (nand_scan (autcpu12_mtd, 1)) {
+ err = -ENXIO;
+ goto out_ior;
+ }
+
+ /* Register the partitions */
+ switch(autcpu12_mtd->size){
+ case SZ_16M: add_mtd_partitions(autcpu12_mtd, partition_info16k, NUM_PARTITIONS16K); break;
+ case SZ_32M: add_mtd_partitions(autcpu12_mtd, partition_info32k, NUM_PARTITIONS32K); break;
+ case SZ_64M: add_mtd_partitions(autcpu12_mtd, partition_info64k, NUM_PARTITIONS64K); break;
+ case SZ_128M: add_mtd_partitions(autcpu12_mtd, partition_info128k, NUM_PARTITIONS128K); break;
+ default: {
+ printk ("Unsupported SmartMedia device\n");
+ err = -ENXIO;
+ goto out_ior;
+ }
+ }
+ goto out;
+
+out_ior:
+ iounmap((void *)autcpu12_fio_base);
+out_mtd:
+ kfree (autcpu12_mtd);
+out:
+ return err;
+}
+
+module_init(autcpu12_init);
+
+/*
+ * Clean up routine
+ */
+#ifdef MODULE
+static void __exit autcpu12_cleanup (void)
+{
+ /* Release resources, unregister device */
+ nand_release (autcpu12_mtd);
+
+ /* unmap physical adress */
+ iounmap((void *)autcpu12_fio_base);
+
+ /* Free the MTD device structure */
+ kfree (autcpu12_mtd);
+}
+module_exit(autcpu12_cleanup);
+#endif
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>");
+MODULE_DESCRIPTION("Glue layer for SmartMediaCard on autronix autcpu12");
diff --git a/drivers/mtd/nand/diskonchip.c b/drivers/mtd/nand/diskonchip.c
new file mode 100644
index 000000000000..02135c3ac29a
--- /dev/null
+++ b/drivers/mtd/nand/diskonchip.c
@@ -0,0 +1,1782 @@
+/*
+ * drivers/mtd/nand/diskonchip.c
+ *
+ * (C) 2003 Red Hat, Inc.
+ * (C) 2004 Dan Brown <dan_brown@ieee.org>
+ * (C) 2004 Kalev Lember <kalev@smartlink.ee>
+ *
+ * Author: David Woodhouse <dwmw2@infradead.org>
+ * Additional Diskonchip 2000 and Millennium support by Dan Brown <dan_brown@ieee.org>
+ * Diskonchip Millennium Plus support by Kalev Lember <kalev@smartlink.ee>
+ *
+ * Error correction code lifted from the old docecc code
+ * Author: Fabrice Bellard (fabrice.bellard@netgem.com)
+ * Copyright (C) 2000 Netgem S.A.
+ * converted to the generic Reed-Solomon library by Thomas Gleixner <tglx@linutronix.de>
+ *
+ * Interface to generic NAND code for M-Systems DiskOnChip devices
+ *
+ * $Id: diskonchip.c,v 1.45 2005/01/05 18:05:14 dwmw2 Exp $
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/rslib.h>
+#include <linux/moduleparam.h>
+#include <asm/io.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/doc2000.h>
+#include <linux/mtd/compatmac.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/inftl.h>
+
+/* Where to look for the devices? */
+#ifndef CONFIG_MTD_DISKONCHIP_PROBE_ADDRESS
+#define CONFIG_MTD_DISKONCHIP_PROBE_ADDRESS 0
+#endif
+
+static unsigned long __initdata doc_locations[] = {
+#if defined (__alpha__) || defined(__i386__) || defined(__x86_64__)
+#ifdef CONFIG_MTD_DISKONCHIP_PROBE_HIGH
+ 0xfffc8000, 0xfffca000, 0xfffcc000, 0xfffce000,
+ 0xfffd0000, 0xfffd2000, 0xfffd4000, 0xfffd6000,
+ 0xfffd8000, 0xfffda000, 0xfffdc000, 0xfffde000,
+ 0xfffe0000, 0xfffe2000, 0xfffe4000, 0xfffe6000,
+ 0xfffe8000, 0xfffea000, 0xfffec000, 0xfffee000,
+#else /* CONFIG_MTD_DOCPROBE_HIGH */
+ 0xc8000, 0xca000, 0xcc000, 0xce000,
+ 0xd0000, 0xd2000, 0xd4000, 0xd6000,
+ 0xd8000, 0xda000, 0xdc000, 0xde000,
+ 0xe0000, 0xe2000, 0xe4000, 0xe6000,
+ 0xe8000, 0xea000, 0xec000, 0xee000,
+#endif /* CONFIG_MTD_DOCPROBE_HIGH */
+#elif defined(__PPC__)
+ 0xe4000000,
+#elif defined(CONFIG_MOMENCO_OCELOT)
+ 0x2f000000,
+ 0xff000000,
+#elif defined(CONFIG_MOMENCO_OCELOT_G) || defined (CONFIG_MOMENCO_OCELOT_C)
+ 0xff000000,
+##else
+#warning Unknown architecture for DiskOnChip. No default probe locations defined
+#endif
+ 0xffffffff };
+
+static struct mtd_info *doclist = NULL;
+
+struct doc_priv {
+ void __iomem *virtadr;
+ unsigned long physadr;
+ u_char ChipID;
+ u_char CDSNControl;
+ int chips_per_floor; /* The number of chips detected on each floor */
+ int curfloor;
+ int curchip;
+ int mh0_page;
+ int mh1_page;
+ struct mtd_info *nextdoc;
+};
+
+/* Max number of eraseblocks to scan (from start of device) for the (I)NFTL
+ MediaHeader. The spec says to just keep going, I think, but that's just
+ silly. */
+#define MAX_MEDIAHEADER_SCAN 8
+
+/* This is the syndrome computed by the HW ecc generator upon reading an empty
+ page, one with all 0xff for data and stored ecc code. */
+static u_char empty_read_syndrome[6] = { 0x26, 0xff, 0x6d, 0x47, 0x73, 0x7a };
+/* This is the ecc value computed by the HW ecc generator upon writing an empty
+ page, one with all 0xff for data. */
+static u_char empty_write_ecc[6] = { 0x4b, 0x00, 0xe2, 0x0e, 0x93, 0xf7 };
+
+#define INFTL_BBT_RESERVED_BLOCKS 4
+
+#define DoC_is_MillenniumPlus(doc) ((doc)->ChipID == DOC_ChipID_DocMilPlus16 || (doc)->ChipID == DOC_ChipID_DocMilPlus32)
+#define DoC_is_Millennium(doc) ((doc)->ChipID == DOC_ChipID_DocMil)
+#define DoC_is_2000(doc) ((doc)->ChipID == DOC_ChipID_Doc2k)
+
+static void doc200x_hwcontrol(struct mtd_info *mtd, int cmd);
+static void doc200x_select_chip(struct mtd_info *mtd, int chip);
+
+static int debug=0;
+module_param(debug, int, 0);
+
+static int try_dword=1;
+module_param(try_dword, int, 0);
+
+static int no_ecc_failures=0;
+module_param(no_ecc_failures, int, 0);
+
+#ifdef CONFIG_MTD_PARTITIONS
+static int no_autopart=0;
+module_param(no_autopart, int, 0);
+#endif
+
+#ifdef MTD_NAND_DISKONCHIP_BBTWRITE
+static int inftl_bbt_write=1;
+#else
+static int inftl_bbt_write=0;
+#endif
+module_param(inftl_bbt_write, int, 0);
+
+static unsigned long doc_config_location = CONFIG_MTD_DISKONCHIP_PROBE_ADDRESS;
+module_param(doc_config_location, ulong, 0);
+MODULE_PARM_DESC(doc_config_location, "Physical memory address at which to probe for DiskOnChip");
+
+
+/* Sector size for HW ECC */
+#define SECTOR_SIZE 512
+/* The sector bytes are packed into NB_DATA 10 bit words */
+#define NB_DATA (((SECTOR_SIZE + 1) * 8 + 6) / 10)
+/* Number of roots */
+#define NROOTS 4
+/* First consective root */
+#define FCR 510
+/* Number of symbols */
+#define NN 1023
+
+/* the Reed Solomon control structure */
+static struct rs_control *rs_decoder;
+
+/*
+ * The HW decoder in the DoC ASIC's provides us a error syndrome,
+ * which we must convert to a standard syndrom usable by the generic
+ * Reed-Solomon library code.
+ *
+ * Fabrice Bellard figured this out in the old docecc code. I added
+ * some comments, improved a minor bit and converted it to make use
+ * of the generic Reed-Solomon libary. tglx
+ */
+static int doc_ecc_decode (struct rs_control *rs, uint8_t *data, uint8_t *ecc)
+{
+ int i, j, nerr, errpos[8];
+ uint8_t parity;
+ uint16_t ds[4], s[5], tmp, errval[8], syn[4];
+
+ /* Convert the ecc bytes into words */
+ ds[0] = ((ecc[4] & 0xff) >> 0) | ((ecc[5] & 0x03) << 8);
+ ds[1] = ((ecc[5] & 0xfc) >> 2) | ((ecc[2] & 0x0f) << 6);
+ ds[2] = ((ecc[2] & 0xf0) >> 4) | ((ecc[3] & 0x3f) << 4);
+ ds[3] = ((ecc[3] & 0xc0) >> 6) | ((ecc[0] & 0xff) << 2);
+ parity = ecc[1];
+
+ /* Initialize the syndrom buffer */
+ for (i = 0; i < NROOTS; i++)
+ s[i] = ds[0];
+ /*
+ * Evaluate
+ * s[i] = ds[3]x^3 + ds[2]x^2 + ds[1]x^1 + ds[0]
+ * where x = alpha^(FCR + i)
+ */
+ for(j = 1; j < NROOTS; j++) {
+ if(ds[j] == 0)
+ continue;
+ tmp = rs->index_of[ds[j]];
+ for(i = 0; i < NROOTS; i++)
+ s[i] ^= rs->alpha_to[rs_modnn(rs, tmp + (FCR + i) * j)];
+ }
+
+ /* Calc s[i] = s[i] / alpha^(v + i) */
+ for (i = 0; i < NROOTS; i++) {
+ if (syn[i])
+ syn[i] = rs_modnn(rs, rs->index_of[s[i]] + (NN - FCR - i));
+ }
+ /* Call the decoder library */
+ nerr = decode_rs16(rs, NULL, NULL, 1019, syn, 0, errpos, 0, errval);
+
+ /* Incorrectable errors ? */
+ if (nerr < 0)
+ return nerr;
+
+ /*
+ * Correct the errors. The bitpositions are a bit of magic,
+ * but they are given by the design of the de/encoder circuit
+ * in the DoC ASIC's.
+ */
+ for(i = 0;i < nerr; i++) {
+ int index, bitpos, pos = 1015 - errpos[i];
+ uint8_t val;
+ if (pos >= NB_DATA && pos < 1019)
+ continue;
+ if (pos < NB_DATA) {
+ /* extract bit position (MSB first) */
+ pos = 10 * (NB_DATA - 1 - pos) - 6;
+ /* now correct the following 10 bits. At most two bytes
+ can be modified since pos is even */
+ index = (pos >> 3) ^ 1;
+ bitpos = pos & 7;
+ if ((index >= 0 && index < SECTOR_SIZE) ||
+ index == (SECTOR_SIZE + 1)) {
+ val = (uint8_t) (errval[i] >> (2 + bitpos));
+ parity ^= val;
+ if (index < SECTOR_SIZE)
+ data[index] ^= val;
+ }
+ index = ((pos >> 3) + 1) ^ 1;
+ bitpos = (bitpos + 10) & 7;
+ if (bitpos == 0)
+ bitpos = 8;
+ if ((index >= 0 && index < SECTOR_SIZE) ||
+ index == (SECTOR_SIZE + 1)) {
+ val = (uint8_t)(errval[i] << (8 - bitpos));
+ parity ^= val;
+ if (index < SECTOR_SIZE)
+ data[index] ^= val;
+ }
+ }
+ }
+ /* If the parity is wrong, no rescue possible */
+ return parity ? -1 : nerr;
+}
+
+static void DoC_Delay(struct doc_priv *doc, unsigned short cycles)
+{
+ volatile char dummy;
+ int i;
+
+ for (i = 0; i < cycles; i++) {
+ if (DoC_is_Millennium(doc))
+ dummy = ReadDOC(doc->virtadr, NOP);
+ else if (DoC_is_MillenniumPlus(doc))
+ dummy = ReadDOC(doc->virtadr, Mplus_NOP);
+ else
+ dummy = ReadDOC(doc->virtadr, DOCStatus);
+ }
+
+}
+
+#define CDSN_CTRL_FR_B_MASK (CDSN_CTRL_FR_B0 | CDSN_CTRL_FR_B1)
+
+/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */
+static int _DoC_WaitReady(struct doc_priv *doc)
+{
+ void __iomem *docptr = doc->virtadr;
+ unsigned long timeo = jiffies + (HZ * 10);
+
+ if(debug) printk("_DoC_WaitReady...\n");
+ /* Out-of-line routine to wait for chip response */
+ if (DoC_is_MillenniumPlus(doc)) {
+ while ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) {
+ if (time_after(jiffies, timeo)) {
+ printk("_DoC_WaitReady timed out.\n");
+ return -EIO;
+ }
+ udelay(1);
+ cond_resched();
+ }
+ } else {
+ while (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) {
+ if (time_after(jiffies, timeo)) {
+ printk("_DoC_WaitReady timed out.\n");
+ return -EIO;
+ }
+ udelay(1);
+ cond_resched();
+ }
+ }
+
+ return 0;
+}
+
+static inline int DoC_WaitReady(struct doc_priv *doc)
+{
+ void __iomem *docptr = doc->virtadr;
+ int ret = 0;
+
+ if (DoC_is_MillenniumPlus(doc)) {
+ DoC_Delay(doc, 4);
+
+ if ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK)
+ /* Call the out-of-line routine to wait */
+ ret = _DoC_WaitReady(doc);
+ } else {
+ DoC_Delay(doc, 4);
+
+ if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B))
+ /* Call the out-of-line routine to wait */
+ ret = _DoC_WaitReady(doc);
+ DoC_Delay(doc, 2);
+ }
+
+ if(debug) printk("DoC_WaitReady OK\n");
+ return ret;
+}
+
+static void doc2000_write_byte(struct mtd_info *mtd, u_char datum)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+
+ if(debug)printk("write_byte %02x\n", datum);
+ WriteDOC(datum, docptr, CDSNSlowIO);
+ WriteDOC(datum, docptr, 2k_CDSN_IO);
+}
+
+static u_char doc2000_read_byte(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ u_char ret;
+
+ ReadDOC(docptr, CDSNSlowIO);
+ DoC_Delay(doc, 2);
+ ret = ReadDOC(docptr, 2k_CDSN_IO);
+ if (debug) printk("read_byte returns %02x\n", ret);
+ return ret;
+}
+
+static void doc2000_writebuf(struct mtd_info *mtd,
+ const u_char *buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ int i;
+ if (debug)printk("writebuf of %d bytes: ", len);
+ for (i=0; i < len; i++) {
+ WriteDOC_(buf[i], docptr, DoC_2k_CDSN_IO + i);
+ if (debug && i < 16)
+ printk("%02x ", buf[i]);
+ }
+ if (debug) printk("\n");
+}
+
+static void doc2000_readbuf(struct mtd_info *mtd,
+ u_char *buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ int i;
+
+ if (debug)printk("readbuf of %d bytes: ", len);
+
+ for (i=0; i < len; i++) {
+ buf[i] = ReadDOC(docptr, 2k_CDSN_IO + i);
+ }
+}
+
+static void doc2000_readbuf_dword(struct mtd_info *mtd,
+ u_char *buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ int i;
+
+ if (debug) printk("readbuf_dword of %d bytes: ", len);
+
+ if (unlikely((((unsigned long)buf)|len) & 3)) {
+ for (i=0; i < len; i++) {
+ *(uint8_t *)(&buf[i]) = ReadDOC(docptr, 2k_CDSN_IO + i);
+ }
+ } else {
+ for (i=0; i < len; i+=4) {
+ *(uint32_t*)(&buf[i]) = readl(docptr + DoC_2k_CDSN_IO + i);
+ }
+ }
+}
+
+static int doc2000_verifybuf(struct mtd_info *mtd,
+ const u_char *buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ int i;
+
+ for (i=0; i < len; i++)
+ if (buf[i] != ReadDOC(docptr, 2k_CDSN_IO))
+ return -EFAULT;
+ return 0;
+}
+
+static uint16_t __init doc200x_ident_chip(struct mtd_info *mtd, int nr)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ uint16_t ret;
+
+ doc200x_select_chip(mtd, nr);
+ doc200x_hwcontrol(mtd, NAND_CTL_SETCLE);
+ this->write_byte(mtd, NAND_CMD_READID);
+ doc200x_hwcontrol(mtd, NAND_CTL_CLRCLE);
+ doc200x_hwcontrol(mtd, NAND_CTL_SETALE);
+ this->write_byte(mtd, 0);
+ doc200x_hwcontrol(mtd, NAND_CTL_CLRALE);
+
+ ret = this->read_byte(mtd) << 8;
+ ret |= this->read_byte(mtd);
+
+ if (doc->ChipID == DOC_ChipID_Doc2k && try_dword && !nr) {
+ /* First chip probe. See if we get same results by 32-bit access */
+ union {
+ uint32_t dword;
+ uint8_t byte[4];
+ } ident;
+ void __iomem *docptr = doc->virtadr;
+
+ doc200x_hwcontrol(mtd, NAND_CTL_SETCLE);
+ doc2000_write_byte(mtd, NAND_CMD_READID);
+ doc200x_hwcontrol(mtd, NAND_CTL_CLRCLE);
+ doc200x_hwcontrol(mtd, NAND_CTL_SETALE);
+ doc2000_write_byte(mtd, 0);
+ doc200x_hwcontrol(mtd, NAND_CTL_CLRALE);
+
+ ident.dword = readl(docptr + DoC_2k_CDSN_IO);
+ if (((ident.byte[0] << 8) | ident.byte[1]) == ret) {
+ printk(KERN_INFO "DiskOnChip 2000 responds to DWORD access\n");
+ this->read_buf = &doc2000_readbuf_dword;
+ }
+ }
+
+ return ret;
+}
+
+static void __init doc2000_count_chips(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ uint16_t mfrid;
+ int i;
+
+ /* Max 4 chips per floor on DiskOnChip 2000 */
+ doc->chips_per_floor = 4;
+
+ /* Find out what the first chip is */
+ mfrid = doc200x_ident_chip(mtd, 0);
+
+ /* Find how many chips in each floor. */
+ for (i = 1; i < 4; i++) {
+ if (doc200x_ident_chip(mtd, i) != mfrid)
+ break;
+ }
+ doc->chips_per_floor = i;
+ printk(KERN_DEBUG "Detected %d chips per floor.\n", i);
+}
+
+static int doc200x_wait(struct mtd_info *mtd, struct nand_chip *this, int state)
+{
+ struct doc_priv *doc = this->priv;
+
+ int status;
+
+ DoC_WaitReady(doc);
+ this->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
+ DoC_WaitReady(doc);
+ status = (int)this->read_byte(mtd);
+
+ return status;
+}
+
+static void doc2001_write_byte(struct mtd_info *mtd, u_char datum)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+
+ WriteDOC(datum, docptr, CDSNSlowIO);
+ WriteDOC(datum, docptr, Mil_CDSN_IO);
+ WriteDOC(datum, docptr, WritePipeTerm);
+}
+
+static u_char doc2001_read_byte(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+
+ //ReadDOC(docptr, CDSNSlowIO);
+ /* 11.4.5 -- delay twice to allow extended length cycle */
+ DoC_Delay(doc, 2);
+ ReadDOC(docptr, ReadPipeInit);
+ //return ReadDOC(docptr, Mil_CDSN_IO);
+ return ReadDOC(docptr, LastDataRead);
+}
+
+static void doc2001_writebuf(struct mtd_info *mtd,
+ const u_char *buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ int i;
+
+ for (i=0; i < len; i++)
+ WriteDOC_(buf[i], docptr, DoC_Mil_CDSN_IO + i);
+ /* Terminate write pipeline */
+ WriteDOC(0x00, docptr, WritePipeTerm);
+}
+
+static void doc2001_readbuf(struct mtd_info *mtd,
+ u_char *buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ int i;
+
+ /* Start read pipeline */
+ ReadDOC(docptr, ReadPipeInit);
+
+ for (i=0; i < len-1; i++)
+ buf[i] = ReadDOC(docptr, Mil_CDSN_IO + (i & 0xff));
+
+ /* Terminate read pipeline */
+ buf[i] = ReadDOC(docptr, LastDataRead);
+}
+
+static int doc2001_verifybuf(struct mtd_info *mtd,
+ const u_char *buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ int i;
+
+ /* Start read pipeline */
+ ReadDOC(docptr, ReadPipeInit);
+
+ for (i=0; i < len-1; i++)
+ if (buf[i] != ReadDOC(docptr, Mil_CDSN_IO)) {
+ ReadDOC(docptr, LastDataRead);
+ return i;
+ }
+ if (buf[i] != ReadDOC(docptr, LastDataRead))
+ return i;
+ return 0;
+}
+
+static u_char doc2001plus_read_byte(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ u_char ret;
+
+ ReadDOC(docptr, Mplus_ReadPipeInit);
+ ReadDOC(docptr, Mplus_ReadPipeInit);
+ ret = ReadDOC(docptr, Mplus_LastDataRead);
+ if (debug) printk("read_byte returns %02x\n", ret);
+ return ret;
+}
+
+static void doc2001plus_writebuf(struct mtd_info *mtd,
+ const u_char *buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ int i;
+
+ if (debug)printk("writebuf of %d bytes: ", len);
+ for (i=0; i < len; i++) {
+ WriteDOC_(buf[i], docptr, DoC_Mil_CDSN_IO + i);
+ if (debug && i < 16)
+ printk("%02x ", buf[i]);
+ }
+ if (debug) printk("\n");
+}
+
+static void doc2001plus_readbuf(struct mtd_info *mtd,
+ u_char *buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ int i;
+
+ if (debug)printk("readbuf of %d bytes: ", len);
+
+ /* Start read pipeline */
+ ReadDOC(docptr, Mplus_ReadPipeInit);
+ ReadDOC(docptr, Mplus_ReadPipeInit);
+
+ for (i=0; i < len-2; i++) {
+ buf[i] = ReadDOC(docptr, Mil_CDSN_IO);
+ if (debug && i < 16)
+ printk("%02x ", buf[i]);
+ }
+
+ /* Terminate read pipeline */
+ buf[len-2] = ReadDOC(docptr, Mplus_LastDataRead);
+ if (debug && i < 16)
+ printk("%02x ", buf[len-2]);
+ buf[len-1] = ReadDOC(docptr, Mplus_LastDataRead);
+ if (debug && i < 16)
+ printk("%02x ", buf[len-1]);
+ if (debug) printk("\n");
+}
+
+static int doc2001plus_verifybuf(struct mtd_info *mtd,
+ const u_char *buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ int i;
+
+ if (debug)printk("verifybuf of %d bytes: ", len);
+
+ /* Start read pipeline */
+ ReadDOC(docptr, Mplus_ReadPipeInit);
+ ReadDOC(docptr, Mplus_ReadPipeInit);
+
+ for (i=0; i < len-2; i++)
+ if (buf[i] != ReadDOC(docptr, Mil_CDSN_IO)) {
+ ReadDOC(docptr, Mplus_LastDataRead);
+ ReadDOC(docptr, Mplus_LastDataRead);
+ return i;
+ }
+ if (buf[len-2] != ReadDOC(docptr, Mplus_LastDataRead))
+ return len-2;
+ if (buf[len-1] != ReadDOC(docptr, Mplus_LastDataRead))
+ return len-1;
+ return 0;
+}
+
+static void doc2001plus_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ int floor = 0;
+
+ if(debug)printk("select chip (%d)\n", chip);
+
+ if (chip == -1) {
+ /* Disable flash internally */
+ WriteDOC(0, docptr, Mplus_FlashSelect);
+ return;
+ }
+
+ floor = chip / doc->chips_per_floor;
+ chip -= (floor * doc->chips_per_floor);
+
+ /* Assert ChipEnable and deassert WriteProtect */
+ WriteDOC((DOC_FLASH_CE), docptr, Mplus_FlashSelect);
+ this->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+
+ doc->curchip = chip;
+ doc->curfloor = floor;
+}
+
+static void doc200x_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ int floor = 0;
+
+ if(debug)printk("select chip (%d)\n", chip);
+
+ if (chip == -1)
+ return;
+
+ floor = chip / doc->chips_per_floor;
+ chip -= (floor * doc->chips_per_floor);
+
+ /* 11.4.4 -- deassert CE before changing chip */
+ doc200x_hwcontrol(mtd, NAND_CTL_CLRNCE);
+
+ WriteDOC(floor, docptr, FloorSelect);
+ WriteDOC(chip, docptr, CDSNDeviceSelect);
+
+ doc200x_hwcontrol(mtd, NAND_CTL_SETNCE);
+
+ doc->curchip = chip;
+ doc->curfloor = floor;
+}
+
+static void doc200x_hwcontrol(struct mtd_info *mtd, int cmd)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+
+ switch(cmd) {
+ case NAND_CTL_SETNCE:
+ doc->CDSNControl |= CDSN_CTRL_CE;
+ break;
+ case NAND_CTL_CLRNCE:
+ doc->CDSNControl &= ~CDSN_CTRL_CE;
+ break;
+ case NAND_CTL_SETCLE:
+ doc->CDSNControl |= CDSN_CTRL_CLE;
+ break;
+ case NAND_CTL_CLRCLE:
+ doc->CDSNControl &= ~CDSN_CTRL_CLE;
+ break;
+ case NAND_CTL_SETALE:
+ doc->CDSNControl |= CDSN_CTRL_ALE;
+ break;
+ case NAND_CTL_CLRALE:
+ doc->CDSNControl &= ~CDSN_CTRL_ALE;
+ break;
+ case NAND_CTL_SETWP:
+ doc->CDSNControl |= CDSN_CTRL_WP;
+ break;
+ case NAND_CTL_CLRWP:
+ doc->CDSNControl &= ~CDSN_CTRL_WP;
+ break;
+ }
+ if (debug)printk("hwcontrol(%d): %02x\n", cmd, doc->CDSNControl);
+ WriteDOC(doc->CDSNControl, docptr, CDSNControl);
+ /* 11.4.3 -- 4 NOPs after CSDNControl write */
+ DoC_Delay(doc, 4);
+}
+
+static void doc2001plus_command (struct mtd_info *mtd, unsigned command, int column, int page_addr)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+
+ /*
+ * Must terminate write pipeline before sending any commands
+ * to the device.
+ */
+ if (command == NAND_CMD_PAGEPROG) {
+ WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
+ WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
+ }
+
+ /*
+ * Write out the command to the device.
+ */
+ if (command == NAND_CMD_SEQIN) {
+ int readcmd;
+
+ if (column >= mtd->oobblock) {
+ /* OOB area */
+ column -= mtd->oobblock;
+ readcmd = NAND_CMD_READOOB;
+ } else if (column < 256) {
+ /* First 256 bytes --> READ0 */
+ readcmd = NAND_CMD_READ0;
+ } else {
+ column -= 256;
+ readcmd = NAND_CMD_READ1;
+ }
+ WriteDOC(readcmd, docptr, Mplus_FlashCmd);
+ }
+ WriteDOC(command, docptr, Mplus_FlashCmd);
+ WriteDOC(0, docptr, Mplus_WritePipeTerm);
+ WriteDOC(0, docptr, Mplus_WritePipeTerm);
+
+ if (column != -1 || page_addr != -1) {
+ /* Serially input address */
+ if (column != -1) {
+ /* Adjust columns for 16 bit buswidth */
+ if (this->options & NAND_BUSWIDTH_16)
+ column >>= 1;
+ WriteDOC(column, docptr, Mplus_FlashAddress);
+ }
+ if (page_addr != -1) {
+ WriteDOC((unsigned char) (page_addr & 0xff), docptr, Mplus_FlashAddress);
+ WriteDOC((unsigned char) ((page_addr >> 8) & 0xff), docptr, Mplus_FlashAddress);
+ /* One more address cycle for higher density devices */
+ if (this->chipsize & 0x0c000000) {
+ WriteDOC((unsigned char) ((page_addr >> 16) & 0x0f), docptr, Mplus_FlashAddress);
+ printk("high density\n");
+ }
+ }
+ WriteDOC(0, docptr, Mplus_WritePipeTerm);
+ WriteDOC(0, docptr, Mplus_WritePipeTerm);
+ /* deassert ALE */
+ if (command == NAND_CMD_READ0 || command == NAND_CMD_READ1 || command == NAND_CMD_READOOB || command == NAND_CMD_READID)
+ WriteDOC(0, docptr, Mplus_FlashControl);
+ }
+
+ /*
+ * program and erase have their own busy handlers
+ * status and sequential in needs no delay
+ */
+ switch (command) {
+
+ case NAND_CMD_PAGEPROG:
+ case NAND_CMD_ERASE1:
+ case NAND_CMD_ERASE2:
+ case NAND_CMD_SEQIN:
+ case NAND_CMD_STATUS:
+ return;
+
+ case NAND_CMD_RESET:
+ if (this->dev_ready)
+ break;
+ udelay(this->chip_delay);
+ WriteDOC(NAND_CMD_STATUS, docptr, Mplus_FlashCmd);
+ WriteDOC(0, docptr, Mplus_WritePipeTerm);
+ WriteDOC(0, docptr, Mplus_WritePipeTerm);
+ while ( !(this->read_byte(mtd) & 0x40));
+ return;
+
+ /* This applies to read commands */
+ default:
+ /*
+ * If we don't have access to the busy pin, we apply the given
+ * command delay
+ */
+ if (!this->dev_ready) {
+ udelay (this->chip_delay);
+ return;
+ }
+ }
+
+ /* Apply this short delay always to ensure that we do wait tWB in
+ * any case on any machine. */
+ ndelay (100);
+ /* wait until command is processed */
+ while (!this->dev_ready(mtd));
+}
+
+static int doc200x_dev_ready(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+
+ if (DoC_is_MillenniumPlus(doc)) {
+ /* 11.4.2 -- must NOP four times before checking FR/B# */
+ DoC_Delay(doc, 4);
+ if ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) {
+ if(debug)
+ printk("not ready\n");
+ return 0;
+ }
+ if (debug)printk("was ready\n");
+ return 1;
+ } else {
+ /* 11.4.2 -- must NOP four times before checking FR/B# */
+ DoC_Delay(doc, 4);
+ if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) {
+ if(debug)
+ printk("not ready\n");
+ return 0;
+ }
+ /* 11.4.2 -- Must NOP twice if it's ready */
+ DoC_Delay(doc, 2);
+ if (debug)printk("was ready\n");
+ return 1;
+ }
+}
+
+static int doc200x_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
+{
+ /* This is our last resort if we couldn't find or create a BBT. Just
+ pretend all blocks are good. */
+ return 0;
+}
+
+static void doc200x_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+
+ /* Prime the ECC engine */
+ switch(mode) {
+ case NAND_ECC_READ:
+ WriteDOC(DOC_ECC_RESET, docptr, ECCConf);
+ WriteDOC(DOC_ECC_EN, docptr, ECCConf);
+ break;
+ case NAND_ECC_WRITE:
+ WriteDOC(DOC_ECC_RESET, docptr, ECCConf);
+ WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf);
+ break;
+ }
+}
+
+static void doc2001plus_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+
+ /* Prime the ECC engine */
+ switch(mode) {
+ case NAND_ECC_READ:
+ WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
+ WriteDOC(DOC_ECC_EN, docptr, Mplus_ECCConf);
+ break;
+ case NAND_ECC_WRITE:
+ WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
+ WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, Mplus_ECCConf);
+ break;
+ }
+}
+
+/* This code is only called on write */
+static int doc200x_calculate_ecc(struct mtd_info *mtd, const u_char *dat,
+ unsigned char *ecc_code)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ int i;
+ int emptymatch = 1;
+
+ /* flush the pipeline */
+ if (DoC_is_2000(doc)) {
+ WriteDOC(doc->CDSNControl & ~CDSN_CTRL_FLASH_IO, docptr, CDSNControl);
+ WriteDOC(0, docptr, 2k_CDSN_IO);
+ WriteDOC(0, docptr, 2k_CDSN_IO);
+ WriteDOC(0, docptr, 2k_CDSN_IO);
+ WriteDOC(doc->CDSNControl, docptr, CDSNControl);
+ } else if (DoC_is_MillenniumPlus(doc)) {
+ WriteDOC(0, docptr, Mplus_NOP);
+ WriteDOC(0, docptr, Mplus_NOP);
+ WriteDOC(0, docptr, Mplus_NOP);
+ } else {
+ WriteDOC(0, docptr, NOP);
+ WriteDOC(0, docptr, NOP);
+ WriteDOC(0, docptr, NOP);
+ }
+
+ for (i = 0; i < 6; i++) {
+ if (DoC_is_MillenniumPlus(doc))
+ ecc_code[i] = ReadDOC_(docptr, DoC_Mplus_ECCSyndrome0 + i);
+ else
+ ecc_code[i] = ReadDOC_(docptr, DoC_ECCSyndrome0 + i);
+ if (ecc_code[i] != empty_write_ecc[i])
+ emptymatch = 0;
+ }
+ if (DoC_is_MillenniumPlus(doc))
+ WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf);
+ else
+ WriteDOC(DOC_ECC_DIS, docptr, ECCConf);
+#if 0
+ /* If emptymatch=1, we might have an all-0xff data buffer. Check. */
+ if (emptymatch) {
+ /* Note: this somewhat expensive test should not be triggered
+ often. It could be optimized away by examining the data in
+ the writebuf routine, and remembering the result. */
+ for (i = 0; i < 512; i++) {
+ if (dat[i] == 0xff) continue;
+ emptymatch = 0;
+ break;
+ }
+ }
+ /* If emptymatch still =1, we do have an all-0xff data buffer.
+ Return all-0xff ecc value instead of the computed one, so
+ it'll look just like a freshly-erased page. */
+ if (emptymatch) memset(ecc_code, 0xff, 6);
+#endif
+ return 0;
+}
+
+static int doc200x_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc)
+{
+ int i, ret = 0;
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
+ volatile u_char dummy;
+ int emptymatch = 1;
+
+ /* flush the pipeline */
+ if (DoC_is_2000(doc)) {
+ dummy = ReadDOC(docptr, 2k_ECCStatus);
+ dummy = ReadDOC(docptr, 2k_ECCStatus);
+ dummy = ReadDOC(docptr, 2k_ECCStatus);
+ } else if (DoC_is_MillenniumPlus(doc)) {
+ dummy = ReadDOC(docptr, Mplus_ECCConf);
+ dummy = ReadDOC(docptr, Mplus_ECCConf);
+ dummy = ReadDOC(docptr, Mplus_ECCConf);
+ } else {
+ dummy = ReadDOC(docptr, ECCConf);
+ dummy = ReadDOC(docptr, ECCConf);
+ dummy = ReadDOC(docptr, ECCConf);
+ }
+
+ /* Error occured ? */
+ if (dummy & 0x80) {
+ for (i = 0; i < 6; i++) {
+ if (DoC_is_MillenniumPlus(doc))
+ calc_ecc[i] = ReadDOC_(docptr, DoC_Mplus_ECCSyndrome0 + i);
+ else
+ calc_ecc[i] = ReadDOC_(docptr, DoC_ECCSyndrome0 + i);
+ if (calc_ecc[i] != empty_read_syndrome[i])
+ emptymatch = 0;
+ }
+ /* If emptymatch=1, the read syndrome is consistent with an
+ all-0xff data and stored ecc block. Check the stored ecc. */
+ if (emptymatch) {
+ for (i = 0; i < 6; i++) {
+ if (read_ecc[i] == 0xff) continue;
+ emptymatch = 0;
+ break;
+ }
+ }
+ /* If emptymatch still =1, check the data block. */
+ if (emptymatch) {
+ /* Note: this somewhat expensive test should not be triggered
+ often. It could be optimized away by examining the data in
+ the readbuf routine, and remembering the result. */
+ for (i = 0; i < 512; i++) {
+ if (dat[i] == 0xff) continue;
+ emptymatch = 0;
+ break;
+ }
+ }
+ /* If emptymatch still =1, this is almost certainly a freshly-
+ erased block, in which case the ECC will not come out right.
+ We'll suppress the error and tell the caller everything's
+ OK. Because it is. */
+ if (!emptymatch) ret = doc_ecc_decode (rs_decoder, dat, calc_ecc);
+ if (ret > 0)
+ printk(KERN_ERR "doc200x_correct_data corrected %d errors\n", ret);
+ }
+ if (DoC_is_MillenniumPlus(doc))
+ WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf);
+ else
+ WriteDOC(DOC_ECC_DIS, docptr, ECCConf);
+ if (no_ecc_failures && (ret == -1)) {
+ printk(KERN_ERR "suppressing ECC failure\n");
+ ret = 0;
+ }
+ return ret;
+}
+
+//u_char mydatabuf[528];
+
+static struct nand_oobinfo doc200x_oobinfo = {
+ .useecc = MTD_NANDECC_AUTOPLACE,
+ .eccbytes = 6,
+ .eccpos = {0, 1, 2, 3, 4, 5},
+ .oobfree = { {8, 8} }
+};
+
+/* Find the (I)NFTL Media Header, and optionally also the mirror media header.
+ On sucessful return, buf will contain a copy of the media header for
+ further processing. id is the string to scan for, and will presumably be
+ either "ANAND" or "BNAND". If findmirror=1, also look for the mirror media
+ header. The page #s of the found media headers are placed in mh0_page and
+ mh1_page in the DOC private structure. */
+static int __init find_media_headers(struct mtd_info *mtd, u_char *buf,
+ const char *id, int findmirror)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ unsigned offs, end = (MAX_MEDIAHEADER_SCAN << this->phys_erase_shift);
+ int ret;
+ size_t retlen;
+
+ end = min(end, mtd->size); // paranoia
+ for (offs = 0; offs < end; offs += mtd->erasesize) {
+ ret = mtd->read(mtd, offs, mtd->oobblock, &retlen, buf);
+ if (retlen != mtd->oobblock) continue;
+ if (ret) {
+ printk(KERN_WARNING "ECC error scanning DOC at 0x%x\n",
+ offs);
+ }
+ if (memcmp(buf, id, 6)) continue;
+ printk(KERN_INFO "Found DiskOnChip %s Media Header at 0x%x\n", id, offs);
+ if (doc->mh0_page == -1) {
+ doc->mh0_page = offs >> this->page_shift;
+ if (!findmirror) return 1;
+ continue;
+ }
+ doc->mh1_page = offs >> this->page_shift;
+ return 2;
+ }
+ if (doc->mh0_page == -1) {
+ printk(KERN_WARNING "DiskOnChip %s Media Header not found.\n", id);
+ return 0;
+ }
+ /* Only one mediaheader was found. We want buf to contain a
+ mediaheader on return, so we'll have to re-read the one we found. */
+ offs = doc->mh0_page << this->page_shift;
+ ret = mtd->read(mtd, offs, mtd->oobblock, &retlen, buf);
+ if (retlen != mtd->oobblock) {
+ /* Insanity. Give up. */
+ printk(KERN_ERR "Read DiskOnChip Media Header once, but can't reread it???\n");
+ return 0;
+ }
+ return 1;
+}
+
+static inline int __init nftl_partscan(struct mtd_info *mtd,
+ struct mtd_partition *parts)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ int ret = 0;
+ u_char *buf;
+ struct NFTLMediaHeader *mh;
+ const unsigned psize = 1 << this->page_shift;
+ unsigned blocks, maxblocks;
+ int offs, numheaders;
+
+ buf = kmalloc(mtd->oobblock, GFP_KERNEL);
+ if (!buf) {
+ printk(KERN_ERR "DiskOnChip mediaheader kmalloc failed!\n");
+ return 0;
+ }
+ if (!(numheaders=find_media_headers(mtd, buf, "ANAND", 1))) goto out;
+ mh = (struct NFTLMediaHeader *) buf;
+
+//#ifdef CONFIG_MTD_DEBUG_VERBOSE
+// if (CONFIG_MTD_DEBUG_VERBOSE >= 2)
+ printk(KERN_INFO " DataOrgID = %s\n"
+ " NumEraseUnits = %d\n"
+ " FirstPhysicalEUN = %d\n"
+ " FormattedSize = %d\n"
+ " UnitSizeFactor = %d\n",
+ mh->DataOrgID, mh->NumEraseUnits,
+ mh->FirstPhysicalEUN, mh->FormattedSize,
+ mh->UnitSizeFactor);
+//#endif
+
+ blocks = mtd->size >> this->phys_erase_shift;
+ maxblocks = min(32768U, mtd->erasesize - psize);
+
+ if (mh->UnitSizeFactor == 0x00) {
+ /* Auto-determine UnitSizeFactor. The constraints are:
+ - There can be at most 32768 virtual blocks.
+ - There can be at most (virtual block size - page size)
+ virtual blocks (because MediaHeader+BBT must fit in 1).
+ */
+ mh->UnitSizeFactor = 0xff;
+ while (blocks > maxblocks) {
+ blocks >>= 1;
+ maxblocks = min(32768U, (maxblocks << 1) + psize);
+ mh->UnitSizeFactor--;
+ }
+ printk(KERN_WARNING "UnitSizeFactor=0x00 detected. Correct value is assumed to be 0x%02x.\n", mh->UnitSizeFactor);
+ }
+
+ /* NOTE: The lines below modify internal variables of the NAND and MTD
+ layers; variables with have already been configured by nand_scan.
+ Unfortunately, we didn't know before this point what these values
+ should be. Thus, this code is somewhat dependant on the exact
+ implementation of the NAND layer. */
+ if (mh->UnitSizeFactor != 0xff) {
+ this->bbt_erase_shift += (0xff - mh->UnitSizeFactor);
+ mtd->erasesize <<= (0xff - mh->UnitSizeFactor);
+ printk(KERN_INFO "Setting virtual erase size to %d\n", mtd->erasesize);
+ blocks = mtd->size >> this->bbt_erase_shift;
+ maxblocks = min(32768U, mtd->erasesize - psize);
+ }
+
+ if (blocks > maxblocks) {
+ printk(KERN_ERR "UnitSizeFactor of 0x%02x is inconsistent with device size. Aborting.\n", mh->UnitSizeFactor);
+ goto out;
+ }
+
+ /* Skip past the media headers. */
+ offs = max(doc->mh0_page, doc->mh1_page);
+ offs <<= this->page_shift;
+ offs += mtd->erasesize;
+
+ //parts[0].name = " DiskOnChip Boot / Media Header partition";
+ //parts[0].offset = 0;
+ //parts[0].size = offs;
+
+ parts[0].name = " DiskOnChip BDTL partition";
+ parts[0].offset = offs;
+ parts[0].size = (mh->NumEraseUnits - numheaders) << this->bbt_erase_shift;
+
+ offs += parts[0].size;
+ if (offs < mtd->size) {
+ parts[1].name = " DiskOnChip Remainder partition";
+ parts[1].offset = offs;
+ parts[1].size = mtd->size - offs;
+ ret = 2;
+ goto out;
+ }
+ ret = 1;
+out:
+ kfree(buf);
+ return ret;
+}
+
+/* This is a stripped-down copy of the code in inftlmount.c */
+static inline int __init inftl_partscan(struct mtd_info *mtd,
+ struct mtd_partition *parts)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ int ret = 0;
+ u_char *buf;
+ struct INFTLMediaHeader *mh;
+ struct INFTLPartition *ip;
+ int numparts = 0;
+ int blocks;
+ int vshift, lastvunit = 0;
+ int i;
+ int end = mtd->size;
+
+ if (inftl_bbt_write)
+ end -= (INFTL_BBT_RESERVED_BLOCKS << this->phys_erase_shift);
+
+ buf = kmalloc(mtd->oobblock, GFP_KERNEL);
+ if (!buf) {
+ printk(KERN_ERR "DiskOnChip mediaheader kmalloc failed!\n");
+ return 0;
+ }
+
+ if (!find_media_headers(mtd, buf, "BNAND", 0)) goto out;
+ doc->mh1_page = doc->mh0_page + (4096 >> this->page_shift);
+ mh = (struct INFTLMediaHeader *) buf;
+
+ mh->NoOfBootImageBlocks = le32_to_cpu(mh->NoOfBootImageBlocks);
+ mh->NoOfBinaryPartitions = le32_to_cpu(mh->NoOfBinaryPartitions);
+ mh->NoOfBDTLPartitions = le32_to_cpu(mh->NoOfBDTLPartitions);
+ mh->BlockMultiplierBits = le32_to_cpu(mh->BlockMultiplierBits);
+ mh->FormatFlags = le32_to_cpu(mh->FormatFlags);
+ mh->PercentUsed = le32_to_cpu(mh->PercentUsed);
+
+//#ifdef CONFIG_MTD_DEBUG_VERBOSE
+// if (CONFIG_MTD_DEBUG_VERBOSE >= 2)
+ printk(KERN_INFO " bootRecordID = %s\n"
+ " NoOfBootImageBlocks = %d\n"
+ " NoOfBinaryPartitions = %d\n"
+ " NoOfBDTLPartitions = %d\n"
+ " BlockMultiplerBits = %d\n"
+ " FormatFlgs = %d\n"
+ " OsakVersion = %d.%d.%d.%d\n"
+ " PercentUsed = %d\n",
+ mh->bootRecordID, mh->NoOfBootImageBlocks,
+ mh->NoOfBinaryPartitions,
+ mh->NoOfBDTLPartitions,
+ mh->BlockMultiplierBits, mh->FormatFlags,
+ ((unsigned char *) &mh->OsakVersion)[0] & 0xf,
+ ((unsigned char *) &mh->OsakVersion)[1] & 0xf,
+ ((unsigned char *) &mh->OsakVersion)[2] & 0xf,
+ ((unsigned char *) &mh->OsakVersion)[3] & 0xf,
+ mh->PercentUsed);
+//#endif
+
+ vshift = this->phys_erase_shift + mh->BlockMultiplierBits;
+
+ blocks = mtd->size >> vshift;
+ if (blocks > 32768) {
+ printk(KERN_ERR "BlockMultiplierBits=%d is inconsistent with device size. Aborting.\n", mh->BlockMultiplierBits);
+ goto out;
+ }
+
+ blocks = doc->chips_per_floor << (this->chip_shift - this->phys_erase_shift);
+ if (inftl_bbt_write && (blocks > mtd->erasesize)) {
+ printk(KERN_ERR "Writeable BBTs spanning more than one erase block are not yet supported. FIX ME!\n");
+ goto out;
+ }
+
+ /* Scan the partitions */
+ for (i = 0; (i < 4); i++) {
+ ip = &(mh->Partitions[i]);
+ ip->virtualUnits = le32_to_cpu(ip->virtualUnits);
+ ip->firstUnit = le32_to_cpu(ip->firstUnit);
+ ip->lastUnit = le32_to_cpu(ip->lastUnit);
+ ip->flags = le32_to_cpu(ip->flags);
+ ip->spareUnits = le32_to_cpu(ip->spareUnits);
+ ip->Reserved0 = le32_to_cpu(ip->Reserved0);
+
+//#ifdef CONFIG_MTD_DEBUG_VERBOSE
+// if (CONFIG_MTD_DEBUG_VERBOSE >= 2)
+ printk(KERN_INFO " PARTITION[%d] ->\n"
+ " virtualUnits = %d\n"
+ " firstUnit = %d\n"
+ " lastUnit = %d\n"
+ " flags = 0x%x\n"
+ " spareUnits = %d\n",
+ i, ip->virtualUnits, ip->firstUnit,
+ ip->lastUnit, ip->flags,
+ ip->spareUnits);
+//#endif
+
+/*
+ if ((i == 0) && (ip->firstUnit > 0)) {
+ parts[0].name = " DiskOnChip IPL / Media Header partition";
+ parts[0].offset = 0;
+ parts[0].size = mtd->erasesize * ip->firstUnit;
+ numparts = 1;
+ }
+*/
+
+ if (ip->flags & INFTL_BINARY)
+ parts[numparts].name = " DiskOnChip BDK partition";
+ else
+ parts[numparts].name = " DiskOnChip BDTL partition";
+ parts[numparts].offset = ip->firstUnit << vshift;
+ parts[numparts].size = (1 + ip->lastUnit - ip->firstUnit) << vshift;
+ numparts++;
+ if (ip->lastUnit > lastvunit) lastvunit = ip->lastUnit;
+ if (ip->flags & INFTL_LAST) break;
+ }
+ lastvunit++;
+ if ((lastvunit << vshift) < end) {
+ parts[numparts].name = " DiskOnChip Remainder partition";
+ parts[numparts].offset = lastvunit << vshift;
+ parts[numparts].size = end - parts[numparts].offset;
+ numparts++;
+ }
+ ret = numparts;
+out:
+ kfree(buf);
+ return ret;
+}
+
+static int __init nftl_scan_bbt(struct mtd_info *mtd)
+{
+ int ret, numparts;
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ struct mtd_partition parts[2];
+
+ memset((char *) parts, 0, sizeof(parts));
+ /* On NFTL, we have to find the media headers before we can read the
+ BBTs, since they're stored in the media header eraseblocks. */
+ numparts = nftl_partscan(mtd, parts);
+ if (!numparts) return -EIO;
+ this->bbt_td->options = NAND_BBT_ABSPAGE | NAND_BBT_8BIT |
+ NAND_BBT_SAVECONTENT | NAND_BBT_WRITE |
+ NAND_BBT_VERSION;
+ this->bbt_td->veroffs = 7;
+ this->bbt_td->pages[0] = doc->mh0_page + 1;
+ if (doc->mh1_page != -1) {
+ this->bbt_md->options = NAND_BBT_ABSPAGE | NAND_BBT_8BIT |
+ NAND_BBT_SAVECONTENT | NAND_BBT_WRITE |
+ NAND_BBT_VERSION;
+ this->bbt_md->veroffs = 7;
+ this->bbt_md->pages[0] = doc->mh1_page + 1;
+ } else {
+ this->bbt_md = NULL;
+ }
+
+ /* It's safe to set bd=NULL below because NAND_BBT_CREATE is not set.
+ At least as nand_bbt.c is currently written. */
+ if ((ret = nand_scan_bbt(mtd, NULL)))
+ return ret;
+ add_mtd_device(mtd);
+#ifdef CONFIG_MTD_PARTITIONS
+ if (!no_autopart)
+ add_mtd_partitions(mtd, parts, numparts);
+#endif
+ return 0;
+}
+
+static int __init inftl_scan_bbt(struct mtd_info *mtd)
+{
+ int ret, numparts;
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+ struct mtd_partition parts[5];
+
+ if (this->numchips > doc->chips_per_floor) {
+ printk(KERN_ERR "Multi-floor INFTL devices not yet supported.\n");
+ return -EIO;
+ }
+
+ if (DoC_is_MillenniumPlus(doc)) {
+ this->bbt_td->options = NAND_BBT_2BIT | NAND_BBT_ABSPAGE;
+ if (inftl_bbt_write)
+ this->bbt_td->options |= NAND_BBT_WRITE;
+ this->bbt_td->pages[0] = 2;
+ this->bbt_md = NULL;
+ } else {
+ this->bbt_td->options = NAND_BBT_LASTBLOCK | NAND_BBT_8BIT |
+ NAND_BBT_VERSION;
+ if (inftl_bbt_write)
+ this->bbt_td->options |= NAND_BBT_WRITE;
+ this->bbt_td->offs = 8;
+ this->bbt_td->len = 8;
+ this->bbt_td->veroffs = 7;
+ this->bbt_td->maxblocks = INFTL_BBT_RESERVED_BLOCKS;
+ this->bbt_td->reserved_block_code = 0x01;
+ this->bbt_td->pattern = "MSYS_BBT";
+
+ this->bbt_md->options = NAND_BBT_LASTBLOCK | NAND_BBT_8BIT |
+ NAND_BBT_VERSION;
+ if (inftl_bbt_write)
+ this->bbt_md->options |= NAND_BBT_WRITE;
+ this->bbt_md->offs = 8;
+ this->bbt_md->len = 8;
+ this->bbt_md->veroffs = 7;
+ this->bbt_md->maxblocks = INFTL_BBT_RESERVED_BLOCKS;
+ this->bbt_md->reserved_block_code = 0x01;
+ this->bbt_md->pattern = "TBB_SYSM";
+ }
+
+ /* It's safe to set bd=NULL below because NAND_BBT_CREATE is not set.
+ At least as nand_bbt.c is currently written. */
+ if ((ret = nand_scan_bbt(mtd, NULL)))
+ return ret;
+ memset((char *) parts, 0, sizeof(parts));
+ numparts = inftl_partscan(mtd, parts);
+ /* At least for now, require the INFTL Media Header. We could probably
+ do without it for non-INFTL use, since all it gives us is
+ autopartitioning, but I want to give it more thought. */
+ if (!numparts) return -EIO;
+ add_mtd_device(mtd);
+#ifdef CONFIG_MTD_PARTITIONS
+ if (!no_autopart)
+ add_mtd_partitions(mtd, parts, numparts);
+#endif
+ return 0;
+}
+
+static inline int __init doc2000_init(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+
+ this->write_byte = doc2000_write_byte;
+ this->read_byte = doc2000_read_byte;
+ this->write_buf = doc2000_writebuf;
+ this->read_buf = doc2000_readbuf;
+ this->verify_buf = doc2000_verifybuf;
+ this->scan_bbt = nftl_scan_bbt;
+
+ doc->CDSNControl = CDSN_CTRL_FLASH_IO | CDSN_CTRL_ECC_IO;
+ doc2000_count_chips(mtd);
+ mtd->name = "DiskOnChip 2000 (NFTL Model)";
+ return (4 * doc->chips_per_floor);
+}
+
+static inline int __init doc2001_init(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+
+ this->write_byte = doc2001_write_byte;
+ this->read_byte = doc2001_read_byte;
+ this->write_buf = doc2001_writebuf;
+ this->read_buf = doc2001_readbuf;
+ this->verify_buf = doc2001_verifybuf;
+
+ ReadDOC(doc->virtadr, ChipID);
+ ReadDOC(doc->virtadr, ChipID);
+ ReadDOC(doc->virtadr, ChipID);
+ if (ReadDOC(doc->virtadr, ChipID) != DOC_ChipID_DocMil) {
+ /* It's not a Millennium; it's one of the newer
+ DiskOnChip 2000 units with a similar ASIC.
+ Treat it like a Millennium, except that it
+ can have multiple chips. */
+ doc2000_count_chips(mtd);
+ mtd->name = "DiskOnChip 2000 (INFTL Model)";
+ this->scan_bbt = inftl_scan_bbt;
+ return (4 * doc->chips_per_floor);
+ } else {
+ /* Bog-standard Millennium */
+ doc->chips_per_floor = 1;
+ mtd->name = "DiskOnChip Millennium";
+ this->scan_bbt = nftl_scan_bbt;
+ return 1;
+ }
+}
+
+static inline int __init doc2001plus_init(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ struct doc_priv *doc = this->priv;
+
+ this->write_byte = NULL;
+ this->read_byte = doc2001plus_read_byte;
+ this->write_buf = doc2001plus_writebuf;
+ this->read_buf = doc2001plus_readbuf;
+ this->verify_buf = doc2001plus_verifybuf;
+ this->scan_bbt = inftl_scan_bbt;
+ this->hwcontrol = NULL;
+ this->select_chip = doc2001plus_select_chip;
+ this->cmdfunc = doc2001plus_command;
+ this->enable_hwecc = doc2001plus_enable_hwecc;
+
+ doc->chips_per_floor = 1;
+ mtd->name = "DiskOnChip Millennium Plus";
+
+ return 1;
+}
+
+static inline int __init doc_probe(unsigned long physadr)
+{
+ unsigned char ChipID;
+ struct mtd_info *mtd;
+ struct nand_chip *nand;
+ struct doc_priv *doc;
+ void __iomem *virtadr;
+ unsigned char save_control;
+ unsigned char tmp, tmpb, tmpc;
+ int reg, len, numchips;
+ int ret = 0;
+
+ virtadr = ioremap(physadr, DOC_IOREMAP_LEN);
+ if (!virtadr) {
+ printk(KERN_ERR "Diskonchip ioremap failed: 0x%x bytes at 0x%lx\n", DOC_IOREMAP_LEN, physadr);
+ return -EIO;
+ }
+
+ /* It's not possible to cleanly detect the DiskOnChip - the
+ * bootup procedure will put the device into reset mode, and
+ * it's not possible to talk to it without actually writing
+ * to the DOCControl register. So we store the current contents
+ * of the DOCControl register's location, in case we later decide
+ * that it's not a DiskOnChip, and want to put it back how we
+ * found it.
+ */
+ save_control = ReadDOC(virtadr, DOCControl);
+
+ /* Reset the DiskOnChip ASIC */
+ WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET,
+ virtadr, DOCControl);
+ WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET,
+ virtadr, DOCControl);
+
+ /* Enable the DiskOnChip ASIC */
+ WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL,
+ virtadr, DOCControl);
+ WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL,
+ virtadr, DOCControl);
+
+ ChipID = ReadDOC(virtadr, ChipID);
+
+ switch(ChipID) {
+ case DOC_ChipID_Doc2k:
+ reg = DoC_2k_ECCStatus;
+ break;
+ case DOC_ChipID_DocMil:
+ reg = DoC_ECCConf;
+ break;
+ case DOC_ChipID_DocMilPlus16:
+ case DOC_ChipID_DocMilPlus32:
+ case 0:
+ /* Possible Millennium Plus, need to do more checks */
+ /* Possibly release from power down mode */
+ for (tmp = 0; (tmp < 4); tmp++)
+ ReadDOC(virtadr, Mplus_Power);
+
+ /* Reset the Millennium Plus ASIC */
+ tmp = DOC_MODE_RESET | DOC_MODE_MDWREN | DOC_MODE_RST_LAT |
+ DOC_MODE_BDECT;
+ WriteDOC(tmp, virtadr, Mplus_DOCControl);
+ WriteDOC(~tmp, virtadr, Mplus_CtrlConfirm);
+
+ mdelay(1);
+ /* Enable the Millennium Plus ASIC */
+ tmp = DOC_MODE_NORMAL | DOC_MODE_MDWREN | DOC_MODE_RST_LAT |
+ DOC_MODE_BDECT;
+ WriteDOC(tmp, virtadr, Mplus_DOCControl);
+ WriteDOC(~tmp, virtadr, Mplus_CtrlConfirm);
+ mdelay(1);
+
+ ChipID = ReadDOC(virtadr, ChipID);
+
+ switch (ChipID) {
+ case DOC_ChipID_DocMilPlus16:
+ reg = DoC_Mplus_Toggle;
+ break;
+ case DOC_ChipID_DocMilPlus32:
+ printk(KERN_ERR "DiskOnChip Millennium Plus 32MB is not supported, ignoring.\n");
+ default:
+ ret = -ENODEV;
+ goto notfound;
+ }
+ break;
+
+ default:
+ ret = -ENODEV;
+ goto notfound;
+ }
+ /* Check the TOGGLE bit in the ECC register */
+ tmp = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT;
+ tmpb = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT;
+ tmpc = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT;
+ if ((tmp == tmpb) || (tmp != tmpc)) {
+ printk(KERN_WARNING "Possible DiskOnChip at 0x%lx failed TOGGLE test, dropping.\n", physadr);
+ ret = -ENODEV;
+ goto notfound;
+ }
+
+ for (mtd = doclist; mtd; mtd = doc->nextdoc) {
+ unsigned char oldval;
+ unsigned char newval;
+ nand = mtd->priv;
+ doc = nand->priv;
+ /* Use the alias resolution register to determine if this is
+ in fact the same DOC aliased to a new address. If writes
+ to one chip's alias resolution register change the value on
+ the other chip, they're the same chip. */
+ if (ChipID == DOC_ChipID_DocMilPlus16) {
+ oldval = ReadDOC(doc->virtadr, Mplus_AliasResolution);
+ newval = ReadDOC(virtadr, Mplus_AliasResolution);
+ } else {
+ oldval = ReadDOC(doc->virtadr, AliasResolution);
+ newval = ReadDOC(virtadr, AliasResolution);
+ }
+ if (oldval != newval)
+ continue;
+ if (ChipID == DOC_ChipID_DocMilPlus16) {
+ WriteDOC(~newval, virtadr, Mplus_AliasResolution);
+ oldval = ReadDOC(doc->virtadr, Mplus_AliasResolution);
+ WriteDOC(newval, virtadr, Mplus_AliasResolution); // restore it
+ } else {
+ WriteDOC(~newval, virtadr, AliasResolution);
+ oldval = ReadDOC(doc->virtadr, AliasResolution);
+ WriteDOC(newval, virtadr, AliasResolution); // restore it
+ }
+ newval = ~newval;
+ if (oldval == newval) {
+ printk(KERN_DEBUG "Found alias of DOC at 0x%lx to 0x%lx\n", doc->physadr, physadr);
+ goto notfound;
+ }
+ }
+
+ printk(KERN_NOTICE "DiskOnChip found at 0x%lx\n", physadr);
+
+ len = sizeof(struct mtd_info) +
+ sizeof(struct nand_chip) +
+ sizeof(struct doc_priv) +
+ (2 * sizeof(struct nand_bbt_descr));
+ mtd = kmalloc(len, GFP_KERNEL);
+ if (!mtd) {
+ printk(KERN_ERR "DiskOnChip kmalloc (%d bytes) failed!\n", len);
+ ret = -ENOMEM;
+ goto fail;
+ }
+ memset(mtd, 0, len);
+
+ nand = (struct nand_chip *) (mtd + 1);
+ doc = (struct doc_priv *) (nand + 1);
+ nand->bbt_td = (struct nand_bbt_descr *) (doc + 1);
+ nand->bbt_md = nand->bbt_td + 1;
+
+ mtd->priv = nand;
+ mtd->owner = THIS_MODULE;
+
+ nand->priv = doc;
+ nand->select_chip = doc200x_select_chip;
+ nand->hwcontrol = doc200x_hwcontrol;
+ nand->dev_ready = doc200x_dev_ready;
+ nand->waitfunc = doc200x_wait;
+ nand->block_bad = doc200x_block_bad;
+ nand->enable_hwecc = doc200x_enable_hwecc;
+ nand->calculate_ecc = doc200x_calculate_ecc;
+ nand->correct_data = doc200x_correct_data;
+
+ nand->autooob = &doc200x_oobinfo;
+ nand->eccmode = NAND_ECC_HW6_512;
+ nand->options = NAND_USE_FLASH_BBT | NAND_HWECC_SYNDROME;
+
+ doc->physadr = physadr;
+ doc->virtadr = virtadr;
+ doc->ChipID = ChipID;
+ doc->curfloor = -1;
+ doc->curchip = -1;
+ doc->mh0_page = -1;
+ doc->mh1_page = -1;
+ doc->nextdoc = doclist;
+
+ if (ChipID == DOC_ChipID_Doc2k)
+ numchips = doc2000_init(mtd);
+ else if (ChipID == DOC_ChipID_DocMilPlus16)
+ numchips = doc2001plus_init(mtd);
+ else
+ numchips = doc2001_init(mtd);
+
+ if ((ret = nand_scan(mtd, numchips))) {
+ /* DBB note: i believe nand_release is necessary here, as
+ buffers may have been allocated in nand_base. Check with
+ Thomas. FIX ME! */
+ /* nand_release will call del_mtd_device, but we haven't yet
+ added it. This is handled without incident by
+ del_mtd_device, as far as I can tell. */
+ nand_release(mtd);
+ kfree(mtd);
+ goto fail;
+ }
+
+ /* Success! */
+ doclist = mtd;
+ return 0;
+
+notfound:
+ /* Put back the contents of the DOCControl register, in case it's not
+ actually a DiskOnChip. */
+ WriteDOC(save_control, virtadr, DOCControl);
+fail:
+ iounmap(virtadr);
+ return ret;
+}
+
+static void release_nanddoc(void)
+{
+ struct mtd_info *mtd, *nextmtd;
+ struct nand_chip *nand;
+ struct doc_priv *doc;
+
+ for (mtd = doclist; mtd; mtd = nextmtd) {
+ nand = mtd->priv;
+ doc = nand->priv;
+
+ nextmtd = doc->nextdoc;
+ nand_release(mtd);
+ iounmap(doc->virtadr);
+ kfree(mtd);
+ }
+}
+
+static int __init init_nanddoc(void)
+{
+ int i, ret = 0;
+
+ /* We could create the decoder on demand, if memory is a concern.
+ * This way we have it handy, if an error happens
+ *
+ * Symbolsize is 10 (bits)
+ * Primitve polynomial is x^10+x^3+1
+ * first consecutive root is 510
+ * primitve element to generate roots = 1
+ * generator polinomial degree = 4
+ */
+ rs_decoder = init_rs(10, 0x409, FCR, 1, NROOTS);
+ if (!rs_decoder) {
+ printk (KERN_ERR "DiskOnChip: Could not create a RS decoder\n");
+ return -ENOMEM;
+ }
+
+ if (doc_config_location) {
+ printk(KERN_INFO "Using configured DiskOnChip probe address 0x%lx\n", doc_config_location);
+ ret = doc_probe(doc_config_location);
+ if (ret < 0)
+ goto outerr;
+ } else {
+ for (i=0; (doc_locations[i] != 0xffffffff); i++) {
+ doc_probe(doc_locations[i]);
+ }
+ }
+ /* No banner message any more. Print a message if no DiskOnChip
+ found, so the user knows we at least tried. */
+ if (!doclist) {
+ printk(KERN_INFO "No valid DiskOnChip devices found\n");
+ ret = -ENODEV;
+ goto outerr;
+ }
+ return 0;
+outerr:
+ free_rs(rs_decoder);
+ return ret;
+}
+
+static void __exit cleanup_nanddoc(void)
+{
+ /* Cleanup the nand/DoC resources */
+ release_nanddoc();
+
+ /* Free the reed solomon resources */
+ if (rs_decoder) {
+ free_rs(rs_decoder);
+ }
+}
+
+module_init(init_nanddoc);
+module_exit(cleanup_nanddoc);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_DESCRIPTION("M-Systems DiskOnChip 2000, Millennium and Millennium Plus device driver\n");
diff --git a/drivers/mtd/nand/edb7312.c b/drivers/mtd/nand/edb7312.c
new file mode 100644
index 000000000000..5549681ccdce
--- /dev/null
+++ b/drivers/mtd/nand/edb7312.c
@@ -0,0 +1,218 @@
+/*
+ * drivers/mtd/nand/edb7312.c
+ *
+ * Copyright (C) 2002 Marius Grger (mag@sysgo.de)
+ *
+ * Derived from drivers/mtd/nand/autcpu12.c
+ * Copyright (c) 2001 Thomas Gleixner (gleixner@autronix.de)
+ *
+ * $Id: edb7312.c,v 1.11 2004/11/04 12:53:10 gleixner Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Overview:
+ * This is a device driver for the NAND flash device found on the
+ * CLEP7312 board which utilizes the Toshiba TC58V64AFT part. This is
+ * a 64Mibit (8MiB x 8 bits) NAND flash device.
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <asm/io.h>
+#include <asm/arch/hardware.h> /* for CLPS7111_VIRT_BASE */
+#include <asm/sizes.h>
+#include <asm/hardware/clps7111.h>
+
+/*
+ * MTD structure for EDB7312 board
+ */
+static struct mtd_info *ep7312_mtd = NULL;
+
+/*
+ * Values specific to the EDB7312 board (used with EP7312 processor)
+ */
+#define EP7312_FIO_PBASE 0x10000000 /* Phys address of flash */
+#define EP7312_PXDR 0x0001 /*
+ * IO offset to Port B data register
+ * where the CLE, ALE and NCE pins
+ * are wired to.
+ */
+#define EP7312_PXDDR 0x0041 /*
+ * IO offset to Port B data direction
+ * register so we can control the IO
+ * lines.
+ */
+
+/*
+ * Module stuff
+ */
+
+static unsigned long ep7312_fio_pbase = EP7312_FIO_PBASE;
+static void __iomem * ep7312_pxdr = (void __iomem *) EP7312_PXDR;
+static void __iomem * ep7312_pxddr = (void __iomem *) EP7312_PXDDR;
+
+#ifdef CONFIG_MTD_PARTITIONS
+/*
+ * Define static partitions for flash device
+ */
+static struct mtd_partition partition_info[] = {
+ { .name = "EP7312 Nand Flash",
+ .offset = 0,
+ .size = 8*1024*1024 }
+};
+#define NUM_PARTITIONS 1
+
+#endif
+
+
+/*
+ * hardware specific access to control-lines
+ */
+static void ep7312_hwcontrol(struct mtd_info *mtd, int cmd)
+{
+ switch(cmd) {
+
+ case NAND_CTL_SETCLE:
+ clps_writeb(clps_readb(ep7312_pxdr) | 0x10, ep7312_pxdr);
+ break;
+ case NAND_CTL_CLRCLE:
+ clps_writeb(clps_readb(ep7312_pxdr) & ~0x10, ep7312_pxdr);
+ break;
+
+ case NAND_CTL_SETALE:
+ clps_writeb(clps_readb(ep7312_pxdr) | 0x20, ep7312_pxdr);
+ break;
+ case NAND_CTL_CLRALE:
+ clps_writeb(clps_readb(ep7312_pxdr) & ~0x20, ep7312_pxdr);
+ break;
+
+ case NAND_CTL_SETNCE:
+ clps_writeb((clps_readb(ep7312_pxdr) | 0x80) & ~0x40, ep7312_pxdr);
+ break;
+ case NAND_CTL_CLRNCE:
+ clps_writeb((clps_readb(ep7312_pxdr) | 0x80) | 0x40, ep7312_pxdr);
+ break;
+ }
+}
+
+/*
+ * read device ready pin
+ */
+static int ep7312_device_ready(struct mtd_info *mtd)
+{
+ return 1;
+}
+#ifdef CONFIG_MTD_PARTITIONS
+const char *part_probes[] = { "cmdlinepart", NULL };
+#endif
+
+/*
+ * Main initialization routine
+ */
+static int __init ep7312_init (void)
+{
+ struct nand_chip *this;
+ const char *part_type = 0;
+ int mtd_parts_nb = 0;
+ struct mtd_partition *mtd_parts = 0;
+ void __iomem * ep7312_fio_base;
+
+ /* Allocate memory for MTD device structure and private data */
+ ep7312_mtd = kmalloc(sizeof(struct mtd_info) +
+ sizeof(struct nand_chip),
+ GFP_KERNEL);
+ if (!ep7312_mtd) {
+ printk("Unable to allocate EDB7312 NAND MTD device structure.\n");
+ return -ENOMEM;
+ }
+
+ /* map physical adress */
+ ep7312_fio_base = ioremap(ep7312_fio_pbase, SZ_1K);
+ if(!ep7312_fio_base) {
+ printk("ioremap EDB7312 NAND flash failed\n");
+ kfree(ep7312_mtd);
+ return -EIO;
+ }
+
+ /* Get pointer to private data */
+ this = (struct nand_chip *) (&ep7312_mtd[1]);
+
+ /* Initialize structures */
+ memset((char *) ep7312_mtd, 0, sizeof(struct mtd_info));
+ memset((char *) this, 0, sizeof(struct nand_chip));
+
+ /* Link the private data with the MTD structure */
+ ep7312_mtd->priv = this;
+
+ /*
+ * Set GPIO Port B control register so that the pins are configured
+ * to be outputs for controlling the NAND flash.
+ */
+ clps_writeb(0xf0, ep7312_pxddr);
+
+ /* insert callbacks */
+ this->IO_ADDR_R = ep7312_fio_base;
+ this->IO_ADDR_W = ep7312_fio_base;
+ this->hwcontrol = ep7312_hwcontrol;
+ this->dev_ready = ep7312_device_ready;
+ /* 15 us command delay time */
+ this->chip_delay = 15;
+
+ /* Scan to find existence of the device */
+ if (nand_scan (ep7312_mtd, 1)) {
+ iounmap((void *)ep7312_fio_base);
+ kfree (ep7312_mtd);
+ return -ENXIO;
+ }
+
+#ifdef CONFIG_MTD_PARTITIONS
+ ep7312_mtd->name = "edb7312-nand";
+ mtd_parts_nb = parse_mtd_partitions(ep7312_mtd, part_probes,
+ &mtd_parts, 0);
+ if (mtd_parts_nb > 0)
+ part_type = "command line";
+ else
+ mtd_parts_nb = 0;
+#endif
+ if (mtd_parts_nb == 0) {
+ mtd_parts = partition_info;
+ mtd_parts_nb = NUM_PARTITIONS;
+ part_type = "static";
+ }
+
+ /* Register the partitions */
+ printk(KERN_NOTICE "Using %s partition definition\n", part_type);
+ add_mtd_partitions(ep7312_mtd, mtd_parts, mtd_parts_nb);
+
+ /* Return happy */
+ return 0;
+}
+module_init(ep7312_init);
+
+/*
+ * Clean up routine
+ */
+static void __exit ep7312_cleanup (void)
+{
+ struct nand_chip *this = (struct nand_chip *) &ep7312_mtd[1];
+
+ /* Release resources, unregister device */
+ nand_release (ap7312_mtd);
+
+ /* Free internal data buffer */
+ kfree (this->data_buf);
+
+ /* Free the MTD device structure */
+ kfree (ep7312_mtd);
+}
+module_exit(ep7312_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Marius Groeger <mag@sysgo.de>");
+MODULE_DESCRIPTION("MTD map driver for Cogent EDB7312 board");
diff --git a/drivers/mtd/nand/h1910.c b/drivers/mtd/nand/h1910.c
new file mode 100644
index 000000000000..3825a7a0900c
--- /dev/null
+++ b/drivers/mtd/nand/h1910.c
@@ -0,0 +1,208 @@
+/*
+ * drivers/mtd/nand/h1910.c
+ *
+ * Copyright (C) 2003 Joshua Wise (joshua@joshuawise.com)
+ *
+ * Derived from drivers/mtd/nand/edb7312.c
+ * Copyright (C) 2002 Marius Grger (mag@sysgo.de)
+ * Copyright (c) 2001 Thomas Gleixner (gleixner@autronix.de)
+ *
+ * $Id: h1910.c,v 1.5 2004/11/04 12:53:10 gleixner Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Overview:
+ * This is a device driver for the NAND flash device found on the
+ * iPAQ h1910 board which utilizes the Samsung K9F2808 part. This is
+ * a 128Mibit (16MiB x 8 bits) NAND flash device.
+ */
+
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <asm/io.h>
+#include <asm/arch/hardware.h> /* for CLPS7111_VIRT_BASE */
+#include <asm/sizes.h>
+#include <asm/arch/h1900-gpio.h>
+#include <asm/arch/ipaq.h>
+
+/*
+ * MTD structure for EDB7312 board
+ */
+static struct mtd_info *h1910_nand_mtd = NULL;
+
+/*
+ * Module stuff
+ */
+
+#ifdef CONFIG_MTD_PARTITIONS
+/*
+ * Define static partitions for flash device
+ */
+static struct mtd_partition partition_info[] = {
+ { name: "h1910 NAND Flash",
+ offset: 0,
+ size: 16*1024*1024 }
+};
+#define NUM_PARTITIONS 1
+
+#endif
+
+
+/*
+ * hardware specific access to control-lines
+ */
+static void h1910_hwcontrol(struct mtd_info *mtd, int cmd)
+{
+ struct nand_chip* this = (struct nand_chip *) (mtd->priv);
+
+ switch(cmd) {
+
+ case NAND_CTL_SETCLE:
+ this->IO_ADDR_R |= (1 << 2);
+ this->IO_ADDR_W |= (1 << 2);
+ break;
+ case NAND_CTL_CLRCLE:
+ this->IO_ADDR_R &= ~(1 << 2);
+ this->IO_ADDR_W &= ~(1 << 2);
+ break;
+
+ case NAND_CTL_SETALE:
+ this->IO_ADDR_R |= (1 << 3);
+ this->IO_ADDR_W |= (1 << 3);
+ break;
+ case NAND_CTL_CLRALE:
+ this->IO_ADDR_R &= ~(1 << 3);
+ this->IO_ADDR_W &= ~(1 << 3);
+ break;
+
+ case NAND_CTL_SETNCE:
+ break;
+ case NAND_CTL_CLRNCE:
+ break;
+ }
+}
+
+/*
+ * read device ready pin
+ */
+#if 0
+static int h1910_device_ready(struct mtd_info *mtd)
+{
+ return (GPLR(55) & GPIO_bit(55));
+}
+#endif
+
+/*
+ * Main initialization routine
+ */
+static int __init h1910_init (void)
+{
+ struct nand_chip *this;
+ const char *part_type = 0;
+ int mtd_parts_nb = 0;
+ struct mtd_partition *mtd_parts = 0;
+ void __iomem *nandaddr;
+
+ if (!machine_is_h1900())
+ return -ENODEV;
+
+ nandaddr = __ioremap(0x08000000, 0x1000, 0, 1);
+ if (!nandaddr) {
+ printk("Failed to ioremap nand flash.\n");
+ return -ENOMEM;
+ }
+
+ /* Allocate memory for MTD device structure and private data */
+ h1910_nand_mtd = kmalloc(sizeof(struct mtd_info) +
+ sizeof(struct nand_chip),
+ GFP_KERNEL);
+ if (!h1910_nand_mtd) {
+ printk("Unable to allocate h1910 NAND MTD device structure.\n");
+ iounmap ((void *) nandaddr);
+ return -ENOMEM;
+ }
+
+ /* Get pointer to private data */
+ this = (struct nand_chip *) (&h1910_nand_mtd[1]);
+
+ /* Initialize structures */
+ memset((char *) h1910_nand_mtd, 0, sizeof(struct mtd_info));
+ memset((char *) this, 0, sizeof(struct nand_chip));
+
+ /* Link the private data with the MTD structure */
+ h1910_nand_mtd->priv = this;
+
+ /*
+ * Enable VPEN
+ */
+ GPSR(37) = GPIO_bit(37);
+
+ /* insert callbacks */
+ this->IO_ADDR_R = nandaddr;
+ this->IO_ADDR_W = nandaddr;
+ this->hwcontrol = h1910_hwcontrol;
+ this->dev_ready = NULL; /* unknown whether that was correct or not so we will just do it like this */
+ /* 15 us command delay time */
+ this->chip_delay = 50;
+ this->eccmode = NAND_ECC_SOFT;
+ this->options = NAND_NO_AUTOINCR;
+
+ /* Scan to find existence of the device */
+ if (nand_scan (h1910_nand_mtd, 1)) {
+ printk(KERN_NOTICE "No NAND device - returning -ENXIO\n");
+ kfree (h1910_nand_mtd);
+ iounmap ((void *) nandaddr);
+ return -ENXIO;
+ }
+
+#ifdef CONFIG_MTD_CMDLINE_PARTS
+ mtd_parts_nb = parse_cmdline_partitions(h1910_nand_mtd, &mtd_parts,
+ "h1910-nand");
+ if (mtd_parts_nb > 0)
+ part_type = "command line";
+ else
+ mtd_parts_nb = 0;
+#endif
+ if (mtd_parts_nb == 0)
+ {
+ mtd_parts = partition_info;
+ mtd_parts_nb = NUM_PARTITIONS;
+ part_type = "static";
+ }
+
+ /* Register the partitions */
+ printk(KERN_NOTICE "Using %s partition definition\n", part_type);
+ add_mtd_partitions(h1910_nand_mtd, mtd_parts, mtd_parts_nb);
+
+ /* Return happy */
+ return 0;
+}
+module_init(h1910_init);
+
+/*
+ * Clean up routine
+ */
+static void __exit h1910_cleanup (void)
+{
+ struct nand_chip *this = (struct nand_chip *) &h1910_nand_mtd[1];
+
+ /* Release resources, unregister device */
+ nand_release (h1910_nand_mtd);
+
+ /* Release io resource */
+ iounmap ((void *) this->IO_ADDR_W);
+
+ /* Free the MTD device structure */
+ kfree (h1910_nand_mtd);
+}
+module_exit(h1910_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Joshua Wise <joshua at joshuawise dot com>");
+MODULE_DESCRIPTION("NAND flash driver for iPAQ h1910");
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
new file mode 100644
index 000000000000..44d5b128911f
--- /dev/null
+++ b/drivers/mtd/nand/nand_base.c
@@ -0,0 +1,2563 @@
+/*
+ * drivers/mtd/nand.c
+ *
+ * Overview:
+ * This is the generic MTD driver for NAND flash devices. It should be
+ * capable of working with almost all NAND chips currently available.
+ * Basic support for AG-AND chips is provided.
+ *
+ * Additional technical information is available on
+ * http://www.linux-mtd.infradead.org/tech/nand.html
+ *
+ * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com)
+ * 2002 Thomas Gleixner (tglx@linutronix.de)
+ *
+ * 02-08-2004 tglx: support for strange chips, which cannot auto increment
+ * pages on read / read_oob
+ *
+ * 03-17-2004 tglx: Check ready before auto increment check. Simon Bayes
+ * pointed this out, as he marked an auto increment capable chip
+ * as NOAUTOINCR in the board driver.
+ * Make reads over block boundaries work too
+ *
+ * 04-14-2004 tglx: first working version for 2k page size chips
+ *
+ * 05-19-2004 tglx: Basic support for Renesas AG-AND chips
+ *
+ * 09-24-2004 tglx: add support for hardware controllers (e.g. ECC) shared
+ * among multiple independend devices. Suggestions and initial patch
+ * from Ben Dooks <ben-mtd@fluff.org>
+ *
+ * Credits:
+ * David Woodhouse for adding multichip support
+ *
+ * Aleph One Ltd. and Toby Churchill Ltd. for supporting the
+ * rework for 2K page size chips
+ *
+ * TODO:
+ * Enable cached programming for 2k page size chips
+ * Check, if mtd->ecctype should be set to MTD_ECC_HW
+ * if we have HW ecc support.
+ * The AG-AND chips have nice features for speed improvement,
+ * which are not supported yet. Read / program 4 pages in one go.
+ *
+ * $Id: nand_base.c,v 1.126 2004/12/13 11:22:25 lavinen Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/mtd/compatmac.h>
+#include <linux/interrupt.h>
+#include <linux/bitops.h>
+#include <asm/io.h>
+
+#ifdef CONFIG_MTD_PARTITIONS
+#include <linux/mtd/partitions.h>
+#endif
+
+/* Define default oob placement schemes for large and small page devices */
+static struct nand_oobinfo nand_oob_8 = {
+ .useecc = MTD_NANDECC_AUTOPLACE,
+ .eccbytes = 3,
+ .eccpos = {0, 1, 2},
+ .oobfree = { {3, 2}, {6, 2} }
+};
+
+static struct nand_oobinfo nand_oob_16 = {
+ .useecc = MTD_NANDECC_AUTOPLACE,
+ .eccbytes = 6,
+ .eccpos = {0, 1, 2, 3, 6, 7},
+ .oobfree = { {8, 8} }
+};
+
+static struct nand_oobinfo nand_oob_64 = {
+ .useecc = MTD_NANDECC_AUTOPLACE,
+ .eccbytes = 24,
+ .eccpos = {
+ 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55,
+ 56, 57, 58, 59, 60, 61, 62, 63},
+ .oobfree = { {2, 38} }
+};
+
+/* This is used for padding purposes in nand_write_oob */
+static u_char ffchars[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+};
+
+/*
+ * NAND low-level MTD interface functions
+ */
+static void nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len);
+static void nand_read_buf(struct mtd_info *mtd, u_char *buf, int len);
+static int nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len);
+
+static int nand_read (struct mtd_info *mtd, loff_t from, size_t len, size_t * retlen, u_char * buf);
+static int nand_read_ecc (struct mtd_info *mtd, loff_t from, size_t len,
+ size_t * retlen, u_char * buf, u_char * eccbuf, struct nand_oobinfo *oobsel);
+static int nand_read_oob (struct mtd_info *mtd, loff_t from, size_t len, size_t * retlen, u_char * buf);
+static int nand_write (struct mtd_info *mtd, loff_t to, size_t len, size_t * retlen, const u_char * buf);
+static int nand_write_ecc (struct mtd_info *mtd, loff_t to, size_t len,
+ size_t * retlen, const u_char * buf, u_char * eccbuf, struct nand_oobinfo *oobsel);
+static int nand_write_oob (struct mtd_info *mtd, loff_t to, size_t len, size_t * retlen, const u_char *buf);
+static int nand_writev (struct mtd_info *mtd, const struct kvec *vecs,
+ unsigned long count, loff_t to, size_t * retlen);
+static int nand_writev_ecc (struct mtd_info *mtd, const struct kvec *vecs,
+ unsigned long count, loff_t to, size_t * retlen, u_char *eccbuf, struct nand_oobinfo *oobsel);
+static int nand_erase (struct mtd_info *mtd, struct erase_info *instr);
+static void nand_sync (struct mtd_info *mtd);
+
+/* Some internal functions */
+static int nand_write_page (struct mtd_info *mtd, struct nand_chip *this, int page, u_char *oob_buf,
+ struct nand_oobinfo *oobsel, int mode);
+#ifdef CONFIG_MTD_NAND_VERIFY_WRITE
+static int nand_verify_pages (struct mtd_info *mtd, struct nand_chip *this, int page, int numpages,
+ u_char *oob_buf, struct nand_oobinfo *oobsel, int chipnr, int oobmode);
+#else
+#define nand_verify_pages(...) (0)
+#endif
+
+static void nand_get_device (struct nand_chip *this, struct mtd_info *mtd, int new_state);
+
+/**
+ * nand_release_device - [GENERIC] release chip
+ * @mtd: MTD device structure
+ *
+ * Deselect, release chip lock and wake up anyone waiting on the device
+ */
+static void nand_release_device (struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+
+ /* De-select the NAND device */
+ this->select_chip(mtd, -1);
+ /* Do we have a hardware controller ? */
+ if (this->controller) {
+ spin_lock(&this->controller->lock);
+ this->controller->active = NULL;
+ spin_unlock(&this->controller->lock);
+ }
+ /* Release the chip */
+ spin_lock (&this->chip_lock);
+ this->state = FL_READY;
+ wake_up (&this->wq);
+ spin_unlock (&this->chip_lock);
+}
+
+/**
+ * nand_read_byte - [DEFAULT] read one byte from the chip
+ * @mtd: MTD device structure
+ *
+ * Default read function for 8bit buswith
+ */
+static u_char nand_read_byte(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ return readb(this->IO_ADDR_R);
+}
+
+/**
+ * nand_write_byte - [DEFAULT] write one byte to the chip
+ * @mtd: MTD device structure
+ * @byte: pointer to data byte to write
+ *
+ * Default write function for 8it buswith
+ */
+static void nand_write_byte(struct mtd_info *mtd, u_char byte)
+{
+ struct nand_chip *this = mtd->priv;
+ writeb(byte, this->IO_ADDR_W);
+}
+
+/**
+ * nand_read_byte16 - [DEFAULT] read one byte endianess aware from the chip
+ * @mtd: MTD device structure
+ *
+ * Default read function for 16bit buswith with
+ * endianess conversion
+ */
+static u_char nand_read_byte16(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ return (u_char) cpu_to_le16(readw(this->IO_ADDR_R));
+}
+
+/**
+ * nand_write_byte16 - [DEFAULT] write one byte endianess aware to the chip
+ * @mtd: MTD device structure
+ * @byte: pointer to data byte to write
+ *
+ * Default write function for 16bit buswith with
+ * endianess conversion
+ */
+static void nand_write_byte16(struct mtd_info *mtd, u_char byte)
+{
+ struct nand_chip *this = mtd->priv;
+ writew(le16_to_cpu((u16) byte), this->IO_ADDR_W);
+}
+
+/**
+ * nand_read_word - [DEFAULT] read one word from the chip
+ * @mtd: MTD device structure
+ *
+ * Default read function for 16bit buswith without
+ * endianess conversion
+ */
+static u16 nand_read_word(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ return readw(this->IO_ADDR_R);
+}
+
+/**
+ * nand_write_word - [DEFAULT] write one word to the chip
+ * @mtd: MTD device structure
+ * @word: data word to write
+ *
+ * Default write function for 16bit buswith without
+ * endianess conversion
+ */
+static void nand_write_word(struct mtd_info *mtd, u16 word)
+{
+ struct nand_chip *this = mtd->priv;
+ writew(word, this->IO_ADDR_W);
+}
+
+/**
+ * nand_select_chip - [DEFAULT] control CE line
+ * @mtd: MTD device structure
+ * @chip: chipnumber to select, -1 for deselect
+ *
+ * Default select function for 1 chip devices.
+ */
+static void nand_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct nand_chip *this = mtd->priv;
+ switch(chip) {
+ case -1:
+ this->hwcontrol(mtd, NAND_CTL_CLRNCE);
+ break;
+ case 0:
+ this->hwcontrol(mtd, NAND_CTL_SETNCE);
+ break;
+
+ default:
+ BUG();
+ }
+}
+
+/**
+ * nand_write_buf - [DEFAULT] write buffer to chip
+ * @mtd: MTD device structure
+ * @buf: data buffer
+ * @len: number of bytes to write
+ *
+ * Default write function for 8bit buswith
+ */
+static void nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+
+ for (i=0; i<len; i++)
+ writeb(buf[i], this->IO_ADDR_W);
+}
+
+/**
+ * nand_read_buf - [DEFAULT] read chip data into buffer
+ * @mtd: MTD device structure
+ * @buf: buffer to store date
+ * @len: number of bytes to read
+ *
+ * Default read function for 8bit buswith
+ */
+static void nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+
+ for (i=0; i<len; i++)
+ buf[i] = readb(this->IO_ADDR_R);
+}
+
+/**
+ * nand_verify_buf - [DEFAULT] Verify chip data against buffer
+ * @mtd: MTD device structure
+ * @buf: buffer containing the data to compare
+ * @len: number of bytes to compare
+ *
+ * Default verify function for 8bit buswith
+ */
+static int nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+
+ for (i=0; i<len; i++)
+ if (buf[i] != readb(this->IO_ADDR_R))
+ return -EFAULT;
+
+ return 0;
+}
+
+/**
+ * nand_write_buf16 - [DEFAULT] write buffer to chip
+ * @mtd: MTD device structure
+ * @buf: data buffer
+ * @len: number of bytes to write
+ *
+ * Default write function for 16bit buswith
+ */
+static void nand_write_buf16(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+ u16 *p = (u16 *) buf;
+ len >>= 1;
+
+ for (i=0; i<len; i++)
+ writew(p[i], this->IO_ADDR_W);
+
+}
+
+/**
+ * nand_read_buf16 - [DEFAULT] read chip data into buffer
+ * @mtd: MTD device structure
+ * @buf: buffer to store date
+ * @len: number of bytes to read
+ *
+ * Default read function for 16bit buswith
+ */
+static void nand_read_buf16(struct mtd_info *mtd, u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+ u16 *p = (u16 *) buf;
+ len >>= 1;
+
+ for (i=0; i<len; i++)
+ p[i] = readw(this->IO_ADDR_R);
+}
+
+/**
+ * nand_verify_buf16 - [DEFAULT] Verify chip data against buffer
+ * @mtd: MTD device structure
+ * @buf: buffer containing the data to compare
+ * @len: number of bytes to compare
+ *
+ * Default verify function for 16bit buswith
+ */
+static int nand_verify_buf16(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+ u16 *p = (u16 *) buf;
+ len >>= 1;
+
+ for (i=0; i<len; i++)
+ if (p[i] != readw(this->IO_ADDR_R))
+ return -EFAULT;
+
+ return 0;
+}
+
+/**
+ * nand_block_bad - [DEFAULT] Read bad block marker from the chip
+ * @mtd: MTD device structure
+ * @ofs: offset from device start
+ * @getchip: 0, if the chip is already selected
+ *
+ * Check, if the block is bad.
+ */
+static int nand_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
+{
+ int page, chipnr, res = 0;
+ struct nand_chip *this = mtd->priv;
+ u16 bad;
+
+ if (getchip) {
+ page = (int)(ofs >> this->page_shift);
+ chipnr = (int)(ofs >> this->chip_shift);
+
+ /* Grab the lock and see if the device is available */
+ nand_get_device (this, mtd, FL_READING);
+
+ /* Select the NAND device */
+ this->select_chip(mtd, chipnr);
+ } else
+ page = (int) ofs;
+
+ if (this->options & NAND_BUSWIDTH_16) {
+ this->cmdfunc (mtd, NAND_CMD_READOOB, this->badblockpos & 0xFE, page & this->pagemask);
+ bad = cpu_to_le16(this->read_word(mtd));
+ if (this->badblockpos & 0x1)
+ bad >>= 1;
+ if ((bad & 0xFF) != 0xff)
+ res = 1;
+ } else {
+ this->cmdfunc (mtd, NAND_CMD_READOOB, this->badblockpos, page & this->pagemask);
+ if (this->read_byte(mtd) != 0xff)
+ res = 1;
+ }
+
+ if (getchip) {
+ /* Deselect and wake up anyone waiting on the device */
+ nand_release_device(mtd);
+ }
+
+ return res;
+}
+
+/**
+ * nand_default_block_markbad - [DEFAULT] mark a block bad
+ * @mtd: MTD device structure
+ * @ofs: offset from device start
+ *
+ * This is the default implementation, which can be overridden by
+ * a hardware specific driver.
+*/
+static int nand_default_block_markbad(struct mtd_info *mtd, loff_t ofs)
+{
+ struct nand_chip *this = mtd->priv;
+ u_char buf[2] = {0, 0};
+ size_t retlen;
+ int block;
+
+ /* Get block number */
+ block = ((int) ofs) >> this->bbt_erase_shift;
+ this->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1);
+
+ /* Do we have a flash based bad block table ? */
+ if (this->options & NAND_USE_FLASH_BBT)
+ return nand_update_bbt (mtd, ofs);
+
+ /* We write two bytes, so we dont have to mess with 16 bit access */
+ ofs += mtd->oobsize + (this->badblockpos & ~0x01);
+ return nand_write_oob (mtd, ofs , 2, &retlen, buf);
+}
+
+/**
+ * nand_check_wp - [GENERIC] check if the chip is write protected
+ * @mtd: MTD device structure
+ * Check, if the device is write protected
+ *
+ * The function expects, that the device is already selected
+ */
+static int nand_check_wp (struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ /* Check the WP bit */
+ this->cmdfunc (mtd, NAND_CMD_STATUS, -1, -1);
+ return (this->read_byte(mtd) & 0x80) ? 0 : 1;
+}
+
+/**
+ * nand_block_checkbad - [GENERIC] Check if a block is marked bad
+ * @mtd: MTD device structure
+ * @ofs: offset from device start
+ * @getchip: 0, if the chip is already selected
+ * @allowbbt: 1, if its allowed to access the bbt area
+ *
+ * Check, if the block is bad. Either by reading the bad block table or
+ * calling of the scan function.
+ */
+static int nand_block_checkbad (struct mtd_info *mtd, loff_t ofs, int getchip, int allowbbt)
+{
+ struct nand_chip *this = mtd->priv;
+
+ if (!this->bbt)
+ return this->block_bad(mtd, ofs, getchip);
+
+ /* Return info from the table */
+ return nand_isbad_bbt (mtd, ofs, allowbbt);
+}
+
+/**
+ * nand_command - [DEFAULT] Send command to NAND device
+ * @mtd: MTD device structure
+ * @command: the command to be sent
+ * @column: the column address for this command, -1 if none
+ * @page_addr: the page address for this command, -1 if none
+ *
+ * Send command to NAND device. This function is used for small page
+ * devices (256/512 Bytes per page)
+ */
+static void nand_command (struct mtd_info *mtd, unsigned command, int column, int page_addr)
+{
+ register struct nand_chip *this = mtd->priv;
+
+ /* Begin command latch cycle */
+ this->hwcontrol(mtd, NAND_CTL_SETCLE);
+ /*
+ * Write out the command to the device.
+ */
+ if (command == NAND_CMD_SEQIN) {
+ int readcmd;
+
+ if (column >= mtd->oobblock) {
+ /* OOB area */
+ column -= mtd->oobblock;
+ readcmd = NAND_CMD_READOOB;
+ } else if (column < 256) {
+ /* First 256 bytes --> READ0 */
+ readcmd = NAND_CMD_READ0;
+ } else {
+ column -= 256;
+ readcmd = NAND_CMD_READ1;
+ }
+ this->write_byte(mtd, readcmd);
+ }
+ this->write_byte(mtd, command);
+
+ /* Set ALE and clear CLE to start address cycle */
+ this->hwcontrol(mtd, NAND_CTL_CLRCLE);
+
+ if (column != -1 || page_addr != -1) {
+ this->hwcontrol(mtd, NAND_CTL_SETALE);
+
+ /* Serially input address */
+ if (column != -1) {
+ /* Adjust columns for 16 bit buswidth */
+ if (this->options & NAND_BUSWIDTH_16)
+ column >>= 1;
+ this->write_byte(mtd, column);
+ }
+ if (page_addr != -1) {
+ this->write_byte(mtd, (unsigned char) (page_addr & 0xff));
+ this->write_byte(mtd, (unsigned char) ((page_addr >> 8) & 0xff));
+ /* One more address cycle for devices > 32MiB */
+ if (this->chipsize > (32 << 20))
+ this->write_byte(mtd, (unsigned char) ((page_addr >> 16) & 0x0f));
+ }
+ /* Latch in address */
+ this->hwcontrol(mtd, NAND_CTL_CLRALE);
+ }
+
+ /*
+ * program and erase have their own busy handlers
+ * status and sequential in needs no delay
+ */
+ switch (command) {
+
+ case NAND_CMD_PAGEPROG:
+ case NAND_CMD_ERASE1:
+ case NAND_CMD_ERASE2:
+ case NAND_CMD_SEQIN:
+ case NAND_CMD_STATUS:
+ return;
+
+ case NAND_CMD_RESET:
+ if (this->dev_ready)
+ break;
+ udelay(this->chip_delay);
+ this->hwcontrol(mtd, NAND_CTL_SETCLE);
+ this->write_byte(mtd, NAND_CMD_STATUS);
+ this->hwcontrol(mtd, NAND_CTL_CLRCLE);
+ while ( !(this->read_byte(mtd) & 0x40));
+ return;
+
+ /* This applies to read commands */
+ default:
+ /*
+ * If we don't have access to the busy pin, we apply the given
+ * command delay
+ */
+ if (!this->dev_ready) {
+ udelay (this->chip_delay);
+ return;
+ }
+ }
+
+ /* Apply this short delay always to ensure that we do wait tWB in
+ * any case on any machine. */
+ ndelay (100);
+ /* wait until command is processed */
+ while (!this->dev_ready(mtd));
+}
+
+/**
+ * nand_command_lp - [DEFAULT] Send command to NAND large page device
+ * @mtd: MTD device structure
+ * @command: the command to be sent
+ * @column: the column address for this command, -1 if none
+ * @page_addr: the page address for this command, -1 if none
+ *
+ * Send command to NAND device. This is the version for the new large page devices
+ * We dont have the seperate regions as we have in the small page devices.
+ * We must emulate NAND_CMD_READOOB to keep the code compatible.
+ *
+ */
+static void nand_command_lp (struct mtd_info *mtd, unsigned command, int column, int page_addr)
+{
+ register struct nand_chip *this = mtd->priv;
+
+ /* Emulate NAND_CMD_READOOB */
+ if (command == NAND_CMD_READOOB) {
+ column += mtd->oobblock;
+ command = NAND_CMD_READ0;
+ }
+
+
+ /* Begin command latch cycle */
+ this->hwcontrol(mtd, NAND_CTL_SETCLE);
+ /* Write out the command to the device. */
+ this->write_byte(mtd, command);
+ /* End command latch cycle */
+ this->hwcontrol(mtd, NAND_CTL_CLRCLE);
+
+ if (column != -1 || page_addr != -1) {
+ this->hwcontrol(mtd, NAND_CTL_SETALE);
+
+ /* Serially input address */
+ if (column != -1) {
+ /* Adjust columns for 16 bit buswidth */
+ if (this->options & NAND_BUSWIDTH_16)
+ column >>= 1;
+ this->write_byte(mtd, column & 0xff);
+ this->write_byte(mtd, column >> 8);
+ }
+ if (page_addr != -1) {
+ this->write_byte(mtd, (unsigned char) (page_addr & 0xff));
+ this->write_byte(mtd, (unsigned char) ((page_addr >> 8) & 0xff));
+ /* One more address cycle for devices > 128MiB */
+ if (this->chipsize > (128 << 20))
+ this->write_byte(mtd, (unsigned char) ((page_addr >> 16) & 0xff));
+ }
+ /* Latch in address */
+ this->hwcontrol(mtd, NAND_CTL_CLRALE);
+ }
+
+ /*
+ * program and erase have their own busy handlers
+ * status and sequential in needs no delay
+ */
+ switch (command) {
+
+ case NAND_CMD_CACHEDPROG:
+ case NAND_CMD_PAGEPROG:
+ case NAND_CMD_ERASE1:
+ case NAND_CMD_ERASE2:
+ case NAND_CMD_SEQIN:
+ case NAND_CMD_STATUS:
+ return;
+
+
+ case NAND_CMD_RESET:
+ if (this->dev_ready)
+ break;
+ udelay(this->chip_delay);
+ this->hwcontrol(mtd, NAND_CTL_SETCLE);
+ this->write_byte(mtd, NAND_CMD_STATUS);
+ this->hwcontrol(mtd, NAND_CTL_CLRCLE);
+ while ( !(this->read_byte(mtd) & 0x40));
+ return;
+
+ case NAND_CMD_READ0:
+ /* Begin command latch cycle */
+ this->hwcontrol(mtd, NAND_CTL_SETCLE);
+ /* Write out the start read command */
+ this->write_byte(mtd, NAND_CMD_READSTART);
+ /* End command latch cycle */
+ this->hwcontrol(mtd, NAND_CTL_CLRCLE);
+ /* Fall through into ready check */
+
+ /* This applies to read commands */
+ default:
+ /*
+ * If we don't have access to the busy pin, we apply the given
+ * command delay
+ */
+ if (!this->dev_ready) {
+ udelay (this->chip_delay);
+ return;
+ }
+ }
+
+ /* Apply this short delay always to ensure that we do wait tWB in
+ * any case on any machine. */
+ ndelay (100);
+ /* wait until command is processed */
+ while (!this->dev_ready(mtd));
+}
+
+/**
+ * nand_get_device - [GENERIC] Get chip for selected access
+ * @this: the nand chip descriptor
+ * @mtd: MTD device structure
+ * @new_state: the state which is requested
+ *
+ * Get the device and lock it for exclusive access
+ */
+static void nand_get_device (struct nand_chip *this, struct mtd_info *mtd, int new_state)
+{
+ struct nand_chip *active = this;
+
+ DECLARE_WAITQUEUE (wait, current);
+
+ /*
+ * Grab the lock and see if the device is available
+ */
+retry:
+ /* Hardware controller shared among independend devices */
+ if (this->controller) {
+ spin_lock (&this->controller->lock);
+ if (this->controller->active)
+ active = this->controller->active;
+ else
+ this->controller->active = this;
+ spin_unlock (&this->controller->lock);
+ }
+
+ if (active == this) {
+ spin_lock (&this->chip_lock);
+ if (this->state == FL_READY) {
+ this->state = new_state;
+ spin_unlock (&this->chip_lock);
+ return;
+ }
+ }
+ set_current_state (TASK_UNINTERRUPTIBLE);
+ add_wait_queue (&active->wq, &wait);
+ spin_unlock (&active->chip_lock);
+ schedule ();
+ remove_wait_queue (&active->wq, &wait);
+ goto retry;
+}
+
+/**
+ * nand_wait - [DEFAULT] wait until the command is done
+ * @mtd: MTD device structure
+ * @this: NAND chip structure
+ * @state: state to select the max. timeout value
+ *
+ * Wait for command done. This applies to erase and program only
+ * Erase can take up to 400ms and program up to 20ms according to
+ * general NAND and SmartMedia specs
+ *
+*/
+static int nand_wait(struct mtd_info *mtd, struct nand_chip *this, int state)
+{
+
+ unsigned long timeo = jiffies;
+ int status;
+
+ if (state == FL_ERASING)
+ timeo += (HZ * 400) / 1000;
+ else
+ timeo += (HZ * 20) / 1000;
+
+ /* Apply this short delay always to ensure that we do wait tWB in
+ * any case on any machine. */
+ ndelay (100);
+
+ if ((state == FL_ERASING) && (this->options & NAND_IS_AND))
+ this->cmdfunc (mtd, NAND_CMD_STATUS_MULTI, -1, -1);
+ else
+ this->cmdfunc (mtd, NAND_CMD_STATUS, -1, -1);
+
+ while (time_before(jiffies, timeo)) {
+ /* Check, if we were interrupted */
+ if (this->state != state)
+ return 0;
+
+ if (this->dev_ready) {
+ if (this->dev_ready(mtd))
+ break;
+ } else {
+ if (this->read_byte(mtd) & NAND_STATUS_READY)
+ break;
+ }
+ yield ();
+ }
+ status = (int) this->read_byte(mtd);
+ return status;
+}
+
+/**
+ * nand_write_page - [GENERIC] write one page
+ * @mtd: MTD device structure
+ * @this: NAND chip structure
+ * @page: startpage inside the chip, must be called with (page & this->pagemask)
+ * @oob_buf: out of band data buffer
+ * @oobsel: out of band selecttion structre
+ * @cached: 1 = enable cached programming if supported by chip
+ *
+ * Nand_page_program function is used for write and writev !
+ * This function will always program a full page of data
+ * If you call it with a non page aligned buffer, you're lost :)
+ *
+ * Cached programming is not supported yet.
+ */
+static int nand_write_page (struct mtd_info *mtd, struct nand_chip *this, int page,
+ u_char *oob_buf, struct nand_oobinfo *oobsel, int cached)
+{
+ int i, status;
+ u_char ecc_code[32];
+ int eccmode = oobsel->useecc ? this->eccmode : NAND_ECC_NONE;
+ int *oob_config = oobsel->eccpos;
+ int datidx = 0, eccidx = 0, eccsteps = this->eccsteps;
+ int eccbytes = 0;
+
+ /* FIXME: Enable cached programming */
+ cached = 0;
+
+ /* Send command to begin auto page programming */
+ this->cmdfunc (mtd, NAND_CMD_SEQIN, 0x00, page);
+
+ /* Write out complete page of data, take care of eccmode */
+ switch (eccmode) {
+ /* No ecc, write all */
+ case NAND_ECC_NONE:
+ printk (KERN_WARNING "Writing data without ECC to NAND-FLASH is not recommended\n");
+ this->write_buf(mtd, this->data_poi, mtd->oobblock);
+ break;
+
+ /* Software ecc 3/256, write all */
+ case NAND_ECC_SOFT:
+ for (; eccsteps; eccsteps--) {
+ this->calculate_ecc(mtd, &this->data_poi[datidx], ecc_code);
+ for (i = 0; i < 3; i++, eccidx++)
+ oob_buf[oob_config[eccidx]] = ecc_code[i];
+ datidx += this->eccsize;
+ }
+ this->write_buf(mtd, this->data_poi, mtd->oobblock);
+ break;
+ default:
+ eccbytes = this->eccbytes;
+ for (; eccsteps; eccsteps--) {
+ /* enable hardware ecc logic for write */
+ this->enable_hwecc(mtd, NAND_ECC_WRITE);
+ this->write_buf(mtd, &this->data_poi[datidx], this->eccsize);
+ this->calculate_ecc(mtd, &this->data_poi[datidx], ecc_code);
+ for (i = 0; i < eccbytes; i++, eccidx++)
+ oob_buf[oob_config[eccidx]] = ecc_code[i];
+ /* If the hardware ecc provides syndromes then
+ * the ecc code must be written immidiately after
+ * the data bytes (words) */
+ if (this->options & NAND_HWECC_SYNDROME)
+ this->write_buf(mtd, ecc_code, eccbytes);
+ datidx += this->eccsize;
+ }
+ break;
+ }
+
+ /* Write out OOB data */
+ if (this->options & NAND_HWECC_SYNDROME)
+ this->write_buf(mtd, &oob_buf[oobsel->eccbytes], mtd->oobsize - oobsel->eccbytes);
+ else
+ this->write_buf(mtd, oob_buf, mtd->oobsize);
+
+ /* Send command to actually program the data */
+ this->cmdfunc (mtd, cached ? NAND_CMD_CACHEDPROG : NAND_CMD_PAGEPROG, -1, -1);
+
+ if (!cached) {
+ /* call wait ready function */
+ status = this->waitfunc (mtd, this, FL_WRITING);
+ /* See if device thinks it succeeded */
+ if (status & 0x01) {
+ DEBUG (MTD_DEBUG_LEVEL0, "%s: " "Failed write, page 0x%08x, ", __FUNCTION__, page);
+ return -EIO;
+ }
+ } else {
+ /* FIXME: Implement cached programming ! */
+ /* wait until cache is ready*/
+ // status = this->waitfunc (mtd, this, FL_CACHEDRPG);
+ }
+ return 0;
+}
+
+#ifdef CONFIG_MTD_NAND_VERIFY_WRITE
+/**
+ * nand_verify_pages - [GENERIC] verify the chip contents after a write
+ * @mtd: MTD device structure
+ * @this: NAND chip structure
+ * @page: startpage inside the chip, must be called with (page & this->pagemask)
+ * @numpages: number of pages to verify
+ * @oob_buf: out of band data buffer
+ * @oobsel: out of band selecttion structre
+ * @chipnr: number of the current chip
+ * @oobmode: 1 = full buffer verify, 0 = ecc only
+ *
+ * The NAND device assumes that it is always writing to a cleanly erased page.
+ * Hence, it performs its internal write verification only on bits that
+ * transitioned from 1 to 0. The device does NOT verify the whole page on a
+ * byte by byte basis. It is possible that the page was not completely erased
+ * or the page is becoming unusable due to wear. The read with ECC would catch
+ * the error later when the ECC page check fails, but we would rather catch
+ * it early in the page write stage. Better to write no data than invalid data.
+ */
+static int nand_verify_pages (struct mtd_info *mtd, struct nand_chip *this, int page, int numpages,
+ u_char *oob_buf, struct nand_oobinfo *oobsel, int chipnr, int oobmode)
+{
+ int i, j, datidx = 0, oobofs = 0, res = -EIO;
+ int eccsteps = this->eccsteps;
+ int hweccbytes;
+ u_char oobdata[64];
+
+ hweccbytes = (this->options & NAND_HWECC_SYNDROME) ? (oobsel->eccbytes / eccsteps) : 0;
+
+ /* Send command to read back the first page */
+ this->cmdfunc (mtd, NAND_CMD_READ0, 0, page);
+
+ for(;;) {
+ for (j = 0; j < eccsteps; j++) {
+ /* Loop through and verify the data */
+ if (this->verify_buf(mtd, &this->data_poi[datidx], mtd->eccsize)) {
+ DEBUG (MTD_DEBUG_LEVEL0, "%s: " "Failed write verify, page 0x%08x ", __FUNCTION__, page);
+ goto out;
+ }
+ datidx += mtd->eccsize;
+ /* Have we a hw generator layout ? */
+ if (!hweccbytes)
+ continue;
+ if (this->verify_buf(mtd, &this->oob_buf[oobofs], hweccbytes)) {
+ DEBUG (MTD_DEBUG_LEVEL0, "%s: " "Failed write verify, page 0x%08x ", __FUNCTION__, page);
+ goto out;
+ }
+ oobofs += hweccbytes;
+ }
+
+ /* check, if we must compare all data or if we just have to
+ * compare the ecc bytes
+ */
+ if (oobmode) {
+ if (this->verify_buf(mtd, &oob_buf[oobofs], mtd->oobsize - hweccbytes * eccsteps)) {
+ DEBUG (MTD_DEBUG_LEVEL0, "%s: " "Failed write verify, page 0x%08x ", __FUNCTION__, page);
+ goto out;
+ }
+ } else {
+ /* Read always, else autoincrement fails */
+ this->read_buf(mtd, oobdata, mtd->oobsize - hweccbytes * eccsteps);
+
+ if (oobsel->useecc != MTD_NANDECC_OFF && !hweccbytes) {
+ int ecccnt = oobsel->eccbytes;
+
+ for (i = 0; i < ecccnt; i++) {
+ int idx = oobsel->eccpos[i];
+ if (oobdata[idx] != oob_buf[oobofs + idx] ) {
+ DEBUG (MTD_DEBUG_LEVEL0,
+ "%s: Failed ECC write "
+ "verify, page 0x%08x, " "%6i bytes were succesful\n", __FUNCTION__, page, i);
+ goto out;
+ }
+ }
+ }
+ }
+ oobofs += mtd->oobsize - hweccbytes * eccsteps;
+ page++;
+ numpages--;
+
+ /* Apply delay or wait for ready/busy pin
+ * Do this before the AUTOINCR check, so no problems
+ * arise if a chip which does auto increment
+ * is marked as NOAUTOINCR by the board driver.
+ * Do this also before returning, so the chip is
+ * ready for the next command.
+ */
+ if (!this->dev_ready)
+ udelay (this->chip_delay);
+ else
+ while (!this->dev_ready(mtd));
+
+ /* All done, return happy */
+ if (!numpages)
+ return 0;
+
+
+ /* Check, if the chip supports auto page increment */
+ if (!NAND_CANAUTOINCR(this))
+ this->cmdfunc (mtd, NAND_CMD_READ0, 0x00, page);
+ }
+ /*
+ * Terminate the read command. We come here in case of an error
+ * So we must issue a reset command.
+ */
+out:
+ this->cmdfunc (mtd, NAND_CMD_RESET, -1, -1);
+ return res;
+}
+#endif
+
+/**
+ * nand_read - [MTD Interface] MTD compability function for nand_read_ecc
+ * @mtd: MTD device structure
+ * @from: offset to read from
+ * @len: number of bytes to read
+ * @retlen: pointer to variable to store the number of read bytes
+ * @buf: the databuffer to put data
+ *
+ * This function simply calls nand_read_ecc with oob buffer and oobsel = NULL
+*/
+static int nand_read (struct mtd_info *mtd, loff_t from, size_t len, size_t * retlen, u_char * buf)
+{
+ return nand_read_ecc (mtd, from, len, retlen, buf, NULL, NULL);
+}
+
+
+/**
+ * nand_read_ecc - [MTD Interface] Read data with ECC
+ * @mtd: MTD device structure
+ * @from: offset to read from
+ * @len: number of bytes to read
+ * @retlen: pointer to variable to store the number of read bytes
+ * @buf: the databuffer to put data
+ * @oob_buf: filesystem supplied oob data buffer
+ * @oobsel: oob selection structure
+ *
+ * NAND read with ECC
+ */
+static int nand_read_ecc (struct mtd_info *mtd, loff_t from, size_t len,
+ size_t * retlen, u_char * buf, u_char * oob_buf, struct nand_oobinfo *oobsel)
+{
+ int i, j, col, realpage, page, end, ecc, chipnr, sndcmd = 1;
+ int read = 0, oob = 0, ecc_status = 0, ecc_failed = 0;
+ struct nand_chip *this = mtd->priv;
+ u_char *data_poi, *oob_data = oob_buf;
+ u_char ecc_calc[32];
+ u_char ecc_code[32];
+ int eccmode, eccsteps;
+ int *oob_config, datidx;
+ int blockcheck = (1 << (this->phys_erase_shift - this->page_shift)) - 1;
+ int eccbytes;
+ int compareecc = 1;
+ int oobreadlen;
+
+
+ DEBUG (MTD_DEBUG_LEVEL3, "nand_read_ecc: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
+
+ /* Do not allow reads past end of device */
+ if ((from + len) > mtd->size) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_read_ecc: Attempt read beyond end of device\n");
+ *retlen = 0;
+ return -EINVAL;
+ }
+
+ /* Grab the lock and see if the device is available */
+ nand_get_device (this, mtd ,FL_READING);
+
+ /* use userspace supplied oobinfo, if zero */
+ if (oobsel == NULL)
+ oobsel = &mtd->oobinfo;
+
+ /* Autoplace of oob data ? Use the default placement scheme */
+ if (oobsel->useecc == MTD_NANDECC_AUTOPLACE)
+ oobsel = this->autooob;
+
+ eccmode = oobsel->useecc ? this->eccmode : NAND_ECC_NONE;
+ oob_config = oobsel->eccpos;
+
+ /* Select the NAND device */
+ chipnr = (int)(from >> this->chip_shift);
+ this->select_chip(mtd, chipnr);
+
+ /* First we calculate the starting page */
+ realpage = (int) (from >> this->page_shift);
+ page = realpage & this->pagemask;
+
+ /* Get raw starting column */
+ col = from & (mtd->oobblock - 1);
+
+ end = mtd->oobblock;
+ ecc = this->eccsize;
+ eccbytes = this->eccbytes;
+
+ if ((eccmode == NAND_ECC_NONE) || (this->options & NAND_HWECC_SYNDROME))
+ compareecc = 0;
+
+ oobreadlen = mtd->oobsize;
+ if (this->options & NAND_HWECC_SYNDROME)
+ oobreadlen -= oobsel->eccbytes;
+
+ /* Loop until all data read */
+ while (read < len) {
+
+ int aligned = (!col && (len - read) >= end);
+ /*
+ * If the read is not page aligned, we have to read into data buffer
+ * due to ecc, else we read into return buffer direct
+ */
+ if (aligned)
+ data_poi = &buf[read];
+ else
+ data_poi = this->data_buf;
+
+ /* Check, if we have this page in the buffer
+ *
+ * FIXME: Make it work when we must provide oob data too,
+ * check the usage of data_buf oob field
+ */
+ if (realpage == this->pagebuf && !oob_buf) {
+ /* aligned read ? */
+ if (aligned)
+ memcpy (data_poi, this->data_buf, end);
+ goto readdata;
+ }
+
+ /* Check, if we must send the read command */
+ if (sndcmd) {
+ this->cmdfunc (mtd, NAND_CMD_READ0, 0x00, page);
+ sndcmd = 0;
+ }
+
+ /* get oob area, if we have no oob buffer from fs-driver */
+ if (!oob_buf || oobsel->useecc == MTD_NANDECC_AUTOPLACE)
+ oob_data = &this->data_buf[end];
+
+ eccsteps = this->eccsteps;
+
+ switch (eccmode) {
+ case NAND_ECC_NONE: { /* No ECC, Read in a page */
+ static unsigned long lastwhinge = 0;
+ if ((lastwhinge / HZ) != (jiffies / HZ)) {
+ printk (KERN_WARNING "Reading data from NAND FLASH without ECC is not recommended\n");
+ lastwhinge = jiffies;
+ }
+ this->read_buf(mtd, data_poi, end);
+ break;
+ }
+
+ case NAND_ECC_SOFT: /* Software ECC 3/256: Read in a page + oob data */
+ this->read_buf(mtd, data_poi, end);
+ for (i = 0, datidx = 0; eccsteps; eccsteps--, i+=3, datidx += ecc)
+ this->calculate_ecc(mtd, &data_poi[datidx], &ecc_calc[i]);
+ break;
+
+ default:
+ for (i = 0, datidx = 0; eccsteps; eccsteps--, i+=eccbytes, datidx += ecc) {
+ this->enable_hwecc(mtd, NAND_ECC_READ);
+ this->read_buf(mtd, &data_poi[datidx], ecc);
+
+ /* HW ecc with syndrome calculation must read the
+ * syndrome from flash immidiately after the data */
+ if (!compareecc) {
+ /* Some hw ecc generators need to know when the
+ * syndrome is read from flash */
+ this->enable_hwecc(mtd, NAND_ECC_READSYN);
+ this->read_buf(mtd, &oob_data[i], eccbytes);
+ /* We calc error correction directly, it checks the hw
+ * generator for an error, reads back the syndrome and
+ * does the error correction on the fly */
+ if (this->correct_data(mtd, &data_poi[datidx], &oob_data[i], &ecc_code[i]) == -1) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_read_ecc: "
+ "Failed ECC read, page 0x%08x on chip %d\n", page, chipnr);
+ ecc_failed++;
+ }
+ } else {
+ this->calculate_ecc(mtd, &data_poi[datidx], &ecc_calc[i]);
+ }
+ }
+ break;
+ }
+
+ /* read oobdata */
+ this->read_buf(mtd, &oob_data[mtd->oobsize - oobreadlen], oobreadlen);
+
+ /* Skip ECC check, if not requested (ECC_NONE or HW_ECC with syndromes) */
+ if (!compareecc)
+ goto readoob;
+
+ /* Pick the ECC bytes out of the oob data */
+ for (j = 0; j < oobsel->eccbytes; j++)
+ ecc_code[j] = oob_data[oob_config[j]];
+
+ /* correct data, if neccecary */
+ for (i = 0, j = 0, datidx = 0; i < this->eccsteps; i++, datidx += ecc) {
+ ecc_status = this->correct_data(mtd, &data_poi[datidx], &ecc_code[j], &ecc_calc[j]);
+
+ /* Get next chunk of ecc bytes */
+ j += eccbytes;
+
+ /* Check, if we have a fs supplied oob-buffer,
+ * This is the legacy mode. Used by YAFFS1
+ * Should go away some day
+ */
+ if (oob_buf && oobsel->useecc == MTD_NANDECC_PLACE) {
+ int *p = (int *)(&oob_data[mtd->oobsize]);
+ p[i] = ecc_status;
+ }
+
+ if (ecc_status == -1) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_read_ecc: " "Failed ECC read, page 0x%08x\n", page);
+ ecc_failed++;
+ }
+ }
+
+ readoob:
+ /* check, if we have a fs supplied oob-buffer */
+ if (oob_buf) {
+ /* without autoplace. Legacy mode used by YAFFS1 */
+ switch(oobsel->useecc) {
+ case MTD_NANDECC_AUTOPLACE:
+ /* Walk through the autoplace chunks */
+ for (i = 0, j = 0; j < mtd->oobavail; i++) {
+ int from = oobsel->oobfree[i][0];
+ int num = oobsel->oobfree[i][1];
+ memcpy(&oob_buf[oob], &oob_data[from], num);
+ j+= num;
+ }
+ oob += mtd->oobavail;
+ break;
+ case MTD_NANDECC_PLACE:
+ /* YAFFS1 legacy mode */
+ oob_data += this->eccsteps * sizeof (int);
+ default:
+ oob_data += mtd->oobsize;
+ }
+ }
+ readdata:
+ /* Partial page read, transfer data into fs buffer */
+ if (!aligned) {
+ for (j = col; j < end && read < len; j++)
+ buf[read++] = data_poi[j];
+ this->pagebuf = realpage;
+ } else
+ read += mtd->oobblock;
+
+ /* Apply delay or wait for ready/busy pin
+ * Do this before the AUTOINCR check, so no problems
+ * arise if a chip which does auto increment
+ * is marked as NOAUTOINCR by the board driver.
+ */
+ if (!this->dev_ready)
+ udelay (this->chip_delay);
+ else
+ while (!this->dev_ready(mtd));
+
+ if (read == len)
+ break;
+
+ /* For subsequent reads align to page boundary. */
+ col = 0;
+ /* Increment page address */
+ realpage++;
+
+ page = realpage & this->pagemask;
+ /* Check, if we cross a chip boundary */
+ if (!page) {
+ chipnr++;
+ this->select_chip(mtd, -1);
+ this->select_chip(mtd, chipnr);
+ }
+ /* Check, if the chip supports auto page increment
+ * or if we have hit a block boundary.
+ */
+ if (!NAND_CANAUTOINCR(this) || !(page & blockcheck))
+ sndcmd = 1;
+ }
+
+ /* Deselect and wake up anyone waiting on the device */
+ nand_release_device(mtd);
+
+ /*
+ * Return success, if no ECC failures, else -EBADMSG
+ * fs driver will take care of that, because
+ * retlen == desired len and result == -EBADMSG
+ */
+ *retlen = read;
+ return ecc_failed ? -EBADMSG : 0;
+}
+
+/**
+ * nand_read_oob - [MTD Interface] NAND read out-of-band
+ * @mtd: MTD device structure
+ * @from: offset to read from
+ * @len: number of bytes to read
+ * @retlen: pointer to variable to store the number of read bytes
+ * @buf: the databuffer to put data
+ *
+ * NAND read out-of-band data from the spare area
+ */
+static int nand_read_oob (struct mtd_info *mtd, loff_t from, size_t len, size_t * retlen, u_char * buf)
+{
+ int i, col, page, chipnr;
+ struct nand_chip *this = mtd->priv;
+ int blockcheck = (1 << (this->phys_erase_shift - this->page_shift)) - 1;
+
+ DEBUG (MTD_DEBUG_LEVEL3, "nand_read_oob: from = 0x%08x, len = %i\n", (unsigned int) from, (int) len);
+
+ /* Shift to get page */
+ page = (int)(from >> this->page_shift);
+ chipnr = (int)(from >> this->chip_shift);
+
+ /* Mask to get column */
+ col = from & (mtd->oobsize - 1);
+
+ /* Initialize return length value */
+ *retlen = 0;
+
+ /* Do not allow reads past end of device */
+ if ((from + len) > mtd->size) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_read_oob: Attempt read beyond end of device\n");
+ *retlen = 0;
+ return -EINVAL;
+ }
+
+ /* Grab the lock and see if the device is available */
+ nand_get_device (this, mtd , FL_READING);
+
+ /* Select the NAND device */
+ this->select_chip(mtd, chipnr);
+
+ /* Send the read command */
+ this->cmdfunc (mtd, NAND_CMD_READOOB, col, page & this->pagemask);
+ /*
+ * Read the data, if we read more than one page
+ * oob data, let the device transfer the data !
+ */
+ i = 0;
+ while (i < len) {
+ int thislen = mtd->oobsize - col;
+ thislen = min_t(int, thislen, len);
+ this->read_buf(mtd, &buf[i], thislen);
+ i += thislen;
+
+ /* Apply delay or wait for ready/busy pin
+ * Do this before the AUTOINCR check, so no problems
+ * arise if a chip which does auto increment
+ * is marked as NOAUTOINCR by the board driver.
+ */
+ if (!this->dev_ready)
+ udelay (this->chip_delay);
+ else
+ while (!this->dev_ready(mtd));
+
+ /* Read more ? */
+ if (i < len) {
+ page++;
+ col = 0;
+
+ /* Check, if we cross a chip boundary */
+ if (!(page & this->pagemask)) {
+ chipnr++;
+ this->select_chip(mtd, -1);
+ this->select_chip(mtd, chipnr);
+ }
+
+ /* Check, if the chip supports auto page increment
+ * or if we have hit a block boundary.
+ */
+ if (!NAND_CANAUTOINCR(this) || !(page & blockcheck)) {
+ /* For subsequent page reads set offset to 0 */
+ this->cmdfunc (mtd, NAND_CMD_READOOB, 0x0, page & this->pagemask);
+ }
+ }
+ }
+
+ /* Deselect and wake up anyone waiting on the device */
+ nand_release_device(mtd);
+
+ /* Return happy */
+ *retlen = len;
+ return 0;
+}
+
+/**
+ * nand_read_raw - [GENERIC] Read raw data including oob into buffer
+ * @mtd: MTD device structure
+ * @buf: temporary buffer
+ * @from: offset to read from
+ * @len: number of bytes to read
+ * @ooblen: number of oob data bytes to read
+ *
+ * Read raw data including oob into buffer
+ */
+int nand_read_raw (struct mtd_info *mtd, uint8_t *buf, loff_t from, size_t len, size_t ooblen)
+{
+ struct nand_chip *this = mtd->priv;
+ int page = (int) (from >> this->page_shift);
+ int chip = (int) (from >> this->chip_shift);
+ int sndcmd = 1;
+ int cnt = 0;
+ int pagesize = mtd->oobblock + mtd->oobsize;
+ int blockcheck = (1 << (this->phys_erase_shift - this->page_shift)) - 1;
+
+ /* Do not allow reads past end of device */
+ if ((from + len) > mtd->size) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_read_raw: Attempt read beyond end of device\n");
+ return -EINVAL;
+ }
+
+ /* Grab the lock and see if the device is available */
+ nand_get_device (this, mtd , FL_READING);
+
+ this->select_chip (mtd, chip);
+
+ /* Add requested oob length */
+ len += ooblen;
+
+ while (len) {
+ if (sndcmd)
+ this->cmdfunc (mtd, NAND_CMD_READ0, 0, page & this->pagemask);
+ sndcmd = 0;
+
+ this->read_buf (mtd, &buf[cnt], pagesize);
+
+ len -= pagesize;
+ cnt += pagesize;
+ page++;
+
+ if (!this->dev_ready)
+ udelay (this->chip_delay);
+ else
+ while (!this->dev_ready(mtd));
+
+ /* Check, if the chip supports auto page increment */
+ if (!NAND_CANAUTOINCR(this) || !(page & blockcheck))
+ sndcmd = 1;
+ }
+
+ /* Deselect and wake up anyone waiting on the device */
+ nand_release_device(mtd);
+ return 0;
+}
+
+
+/**
+ * nand_prepare_oobbuf - [GENERIC] Prepare the out of band buffer
+ * @mtd: MTD device structure
+ * @fsbuf: buffer given by fs driver
+ * @oobsel: out of band selection structre
+ * @autoplace: 1 = place given buffer into the oob bytes
+ * @numpages: number of pages to prepare
+ *
+ * Return:
+ * 1. Filesystem buffer available and autoplacement is off,
+ * return filesystem buffer
+ * 2. No filesystem buffer or autoplace is off, return internal
+ * buffer
+ * 3. Filesystem buffer is given and autoplace selected
+ * put data from fs buffer into internal buffer and
+ * retrun internal buffer
+ *
+ * Note: The internal buffer is filled with 0xff. This must
+ * be done only once, when no autoplacement happens
+ * Autoplacement sets the buffer dirty flag, which
+ * forces the 0xff fill before using the buffer again.
+ *
+*/
+static u_char * nand_prepare_oobbuf (struct mtd_info *mtd, u_char *fsbuf, struct nand_oobinfo *oobsel,
+ int autoplace, int numpages)
+{
+ struct nand_chip *this = mtd->priv;
+ int i, len, ofs;
+
+ /* Zero copy fs supplied buffer */
+ if (fsbuf && !autoplace)
+ return fsbuf;
+
+ /* Check, if the buffer must be filled with ff again */
+ if (this->oobdirty) {
+ memset (this->oob_buf, 0xff,
+ mtd->oobsize << (this->phys_erase_shift - this->page_shift));
+ this->oobdirty = 0;
+ }
+
+ /* If we have no autoplacement or no fs buffer use the internal one */
+ if (!autoplace || !fsbuf)
+ return this->oob_buf;
+
+ /* Walk through the pages and place the data */
+ this->oobdirty = 1;
+ ofs = 0;
+ while (numpages--) {
+ for (i = 0, len = 0; len < mtd->oobavail; i++) {
+ int to = ofs + oobsel->oobfree[i][0];
+ int num = oobsel->oobfree[i][1];
+ memcpy (&this->oob_buf[to], fsbuf, num);
+ len += num;
+ fsbuf += num;
+ }
+ ofs += mtd->oobavail;
+ }
+ return this->oob_buf;
+}
+
+#define NOTALIGNED(x) (x & (mtd->oobblock-1)) != 0
+
+/**
+ * nand_write - [MTD Interface] compability function for nand_write_ecc
+ * @mtd: MTD device structure
+ * @to: offset to write to
+ * @len: number of bytes to write
+ * @retlen: pointer to variable to store the number of written bytes
+ * @buf: the data to write
+ *
+ * This function simply calls nand_write_ecc with oob buffer and oobsel = NULL
+ *
+*/
+static int nand_write (struct mtd_info *mtd, loff_t to, size_t len, size_t * retlen, const u_char * buf)
+{
+ return (nand_write_ecc (mtd, to, len, retlen, buf, NULL, NULL));
+}
+
+/**
+ * nand_write_ecc - [MTD Interface] NAND write with ECC
+ * @mtd: MTD device structure
+ * @to: offset to write to
+ * @len: number of bytes to write
+ * @retlen: pointer to variable to store the number of written bytes
+ * @buf: the data to write
+ * @eccbuf: filesystem supplied oob data buffer
+ * @oobsel: oob selection structure
+ *
+ * NAND write with ECC
+ */
+static int nand_write_ecc (struct mtd_info *mtd, loff_t to, size_t len,
+ size_t * retlen, const u_char * buf, u_char * eccbuf, struct nand_oobinfo *oobsel)
+{
+ int startpage, page, ret = -EIO, oob = 0, written = 0, chipnr;
+ int autoplace = 0, numpages, totalpages;
+ struct nand_chip *this = mtd->priv;
+ u_char *oobbuf, *bufstart;
+ int ppblock = (1 << (this->phys_erase_shift - this->page_shift));
+
+ DEBUG (MTD_DEBUG_LEVEL3, "nand_write_ecc: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);
+
+ /* Initialize retlen, in case of early exit */
+ *retlen = 0;
+
+ /* Do not allow write past end of device */
+ if ((to + len) > mtd->size) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: Attempt to write past end of page\n");
+ return -EINVAL;
+ }
+
+ /* reject writes, which are not page aligned */
+ if (NOTALIGNED (to) || NOTALIGNED(len)) {
+ printk (KERN_NOTICE "nand_write_ecc: Attempt to write not page aligned data\n");
+ return -EINVAL;
+ }
+
+ /* Grab the lock and see if the device is available */
+ nand_get_device (this, mtd, FL_WRITING);
+
+ /* Calculate chipnr */
+ chipnr = (int)(to >> this->chip_shift);
+ /* Select the NAND device */
+ this->select_chip(mtd, chipnr);
+
+ /* Check, if it is write protected */
+ if (nand_check_wp(mtd))
+ goto out;
+
+ /* if oobsel is NULL, use chip defaults */
+ if (oobsel == NULL)
+ oobsel = &mtd->oobinfo;
+
+ /* Autoplace of oob data ? Use the default placement scheme */
+ if (oobsel->useecc == MTD_NANDECC_AUTOPLACE) {
+ oobsel = this->autooob;
+ autoplace = 1;
+ }
+
+ /* Setup variables and oob buffer */
+ totalpages = len >> this->page_shift;
+ page = (int) (to >> this->page_shift);
+ /* Invalidate the page cache, if we write to the cached page */
+ if (page <= this->pagebuf && this->pagebuf < (page + totalpages))
+ this->pagebuf = -1;
+
+ /* Set it relative to chip */
+ page &= this->pagemask;
+ startpage = page;
+ /* Calc number of pages we can write in one go */
+ numpages = min (ppblock - (startpage & (ppblock - 1)), totalpages);
+ oobbuf = nand_prepare_oobbuf (mtd, eccbuf, oobsel, autoplace, numpages);
+ bufstart = (u_char *)buf;
+
+ /* Loop until all data is written */
+ while (written < len) {
+
+ this->data_poi = (u_char*) &buf[written];
+ /* Write one page. If this is the last page to write
+ * or the last page in this block, then use the
+ * real pageprogram command, else select cached programming
+ * if supported by the chip.
+ */
+ ret = nand_write_page (mtd, this, page, &oobbuf[oob], oobsel, (--numpages > 0));
+ if (ret) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: write_page failed %d\n", ret);
+ goto out;
+ }
+ /* Next oob page */
+ oob += mtd->oobsize;
+ /* Update written bytes count */
+ written += mtd->oobblock;
+ if (written == len)
+ goto cmp;
+
+ /* Increment page address */
+ page++;
+
+ /* Have we hit a block boundary ? Then we have to verify and
+ * if verify is ok, we have to setup the oob buffer for
+ * the next pages.
+ */
+ if (!(page & (ppblock - 1))){
+ int ofs;
+ this->data_poi = bufstart;
+ ret = nand_verify_pages (mtd, this, startpage,
+ page - startpage,
+ oobbuf, oobsel, chipnr, (eccbuf != NULL));
+ if (ret) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: verify_pages failed %d\n", ret);
+ goto out;
+ }
+ *retlen = written;
+
+ ofs = autoplace ? mtd->oobavail : mtd->oobsize;
+ if (eccbuf)
+ eccbuf += (page - startpage) * ofs;
+ totalpages -= page - startpage;
+ numpages = min (totalpages, ppblock);
+ page &= this->pagemask;
+ startpage = page;
+ oobbuf = nand_prepare_oobbuf (mtd, eccbuf, oobsel,
+ autoplace, numpages);
+ /* Check, if we cross a chip boundary */
+ if (!page) {
+ chipnr++;
+ this->select_chip(mtd, -1);
+ this->select_chip(mtd, chipnr);
+ }
+ }
+ }
+ /* Verify the remaining pages */
+cmp:
+ this->data_poi = bufstart;
+ ret = nand_verify_pages (mtd, this, startpage, totalpages,
+ oobbuf, oobsel, chipnr, (eccbuf != NULL));
+ if (!ret)
+ *retlen = written;
+ else
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_write_ecc: verify_pages failed %d\n", ret);
+
+out:
+ /* Deselect and wake up anyone waiting on the device */
+ nand_release_device(mtd);
+
+ return ret;
+}
+
+
+/**
+ * nand_write_oob - [MTD Interface] NAND write out-of-band
+ * @mtd: MTD device structure
+ * @to: offset to write to
+ * @len: number of bytes to write
+ * @retlen: pointer to variable to store the number of written bytes
+ * @buf: the data to write
+ *
+ * NAND write out-of-band
+ */
+static int nand_write_oob (struct mtd_info *mtd, loff_t to, size_t len, size_t * retlen, const u_char * buf)
+{
+ int column, page, status, ret = -EIO, chipnr;
+ struct nand_chip *this = mtd->priv;
+
+ DEBUG (MTD_DEBUG_LEVEL3, "nand_write_oob: to = 0x%08x, len = %i\n", (unsigned int) to, (int) len);
+
+ /* Shift to get page */
+ page = (int) (to >> this->page_shift);
+ chipnr = (int) (to >> this->chip_shift);
+
+ /* Mask to get column */
+ column = to & (mtd->oobsize - 1);
+
+ /* Initialize return length value */
+ *retlen = 0;
+
+ /* Do not allow write past end of page */
+ if ((column + len) > mtd->oobsize) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_write_oob: Attempt to write past end of page\n");
+ return -EINVAL;
+ }
+
+ /* Grab the lock and see if the device is available */
+ nand_get_device (this, mtd, FL_WRITING);
+
+ /* Select the NAND device */
+ this->select_chip(mtd, chipnr);
+
+ /* Reset the chip. Some chips (like the Toshiba TC5832DC found
+ in one of my DiskOnChip 2000 test units) will clear the whole
+ data page too if we don't do this. I have no clue why, but
+ I seem to have 'fixed' it in the doc2000 driver in
+ August 1999. dwmw2. */
+ this->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
+
+ /* Check, if it is write protected */
+ if (nand_check_wp(mtd))
+ goto out;
+
+ /* Invalidate the page cache, if we write to the cached page */
+ if (page == this->pagebuf)
+ this->pagebuf = -1;
+
+ if (NAND_MUST_PAD(this)) {
+ /* Write out desired data */
+ this->cmdfunc (mtd, NAND_CMD_SEQIN, mtd->oobblock, page & this->pagemask);
+ /* prepad 0xff for partial programming */
+ this->write_buf(mtd, ffchars, column);
+ /* write data */
+ this->write_buf(mtd, buf, len);
+ /* postpad 0xff for partial programming */
+ this->write_buf(mtd, ffchars, mtd->oobsize - (len+column));
+ } else {
+ /* Write out desired data */
+ this->cmdfunc (mtd, NAND_CMD_SEQIN, mtd->oobblock + column, page & this->pagemask);
+ /* write data */
+ this->write_buf(mtd, buf, len);
+ }
+ /* Send command to program the OOB data */
+ this->cmdfunc (mtd, NAND_CMD_PAGEPROG, -1, -1);
+
+ status = this->waitfunc (mtd, this, FL_WRITING);
+
+ /* See if device thinks it succeeded */
+ if (status & 0x01) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_write_oob: " "Failed write, page 0x%08x\n", page);
+ ret = -EIO;
+ goto out;
+ }
+ /* Return happy */
+ *retlen = len;
+
+#ifdef CONFIG_MTD_NAND_VERIFY_WRITE
+ /* Send command to read back the data */
+ this->cmdfunc (mtd, NAND_CMD_READOOB, column, page & this->pagemask);
+
+ if (this->verify_buf(mtd, buf, len)) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_write_oob: " "Failed write verify, page 0x%08x\n", page);
+ ret = -EIO;
+ goto out;
+ }
+#endif
+ ret = 0;
+out:
+ /* Deselect and wake up anyone waiting on the device */
+ nand_release_device(mtd);
+
+ return ret;
+}
+
+
+/**
+ * nand_writev - [MTD Interface] compabilty function for nand_writev_ecc
+ * @mtd: MTD device structure
+ * @vecs: the iovectors to write
+ * @count: number of vectors
+ * @to: offset to write to
+ * @retlen: pointer to variable to store the number of written bytes
+ *
+ * NAND write with kvec. This just calls the ecc function
+ */
+static int nand_writev (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count,
+ loff_t to, size_t * retlen)
+{
+ return (nand_writev_ecc (mtd, vecs, count, to, retlen, NULL, NULL));
+}
+
+/**
+ * nand_writev_ecc - [MTD Interface] write with iovec with ecc
+ * @mtd: MTD device structure
+ * @vecs: the iovectors to write
+ * @count: number of vectors
+ * @to: offset to write to
+ * @retlen: pointer to variable to store the number of written bytes
+ * @eccbuf: filesystem supplied oob data buffer
+ * @oobsel: oob selection structure
+ *
+ * NAND write with iovec with ecc
+ */
+static int nand_writev_ecc (struct mtd_info *mtd, const struct kvec *vecs, unsigned long count,
+ loff_t to, size_t * retlen, u_char *eccbuf, struct nand_oobinfo *oobsel)
+{
+ int i, page, len, total_len, ret = -EIO, written = 0, chipnr;
+ int oob, numpages, autoplace = 0, startpage;
+ struct nand_chip *this = mtd->priv;
+ int ppblock = (1 << (this->phys_erase_shift - this->page_shift));
+ u_char *oobbuf, *bufstart;
+
+ /* Preset written len for early exit */
+ *retlen = 0;
+
+ /* Calculate total length of data */
+ total_len = 0;
+ for (i = 0; i < count; i++)
+ total_len += (int) vecs[i].iov_len;
+
+ DEBUG (MTD_DEBUG_LEVEL3,
+ "nand_writev: to = 0x%08x, len = %i, count = %ld\n", (unsigned int) to, (unsigned int) total_len, count);
+
+ /* Do not allow write past end of page */
+ if ((to + total_len) > mtd->size) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_writev: Attempted write past end of device\n");
+ return -EINVAL;
+ }
+
+ /* reject writes, which are not page aligned */
+ if (NOTALIGNED (to) || NOTALIGNED(total_len)) {
+ printk (KERN_NOTICE "nand_write_ecc: Attempt to write not page aligned data\n");
+ return -EINVAL;
+ }
+
+ /* Grab the lock and see if the device is available */
+ nand_get_device (this, mtd, FL_WRITING);
+
+ /* Get the current chip-nr */
+ chipnr = (int) (to >> this->chip_shift);
+ /* Select the NAND device */
+ this->select_chip(mtd, chipnr);
+
+ /* Check, if it is write protected */
+ if (nand_check_wp(mtd))
+ goto out;
+
+ /* if oobsel is NULL, use chip defaults */
+ if (oobsel == NULL)
+ oobsel = &mtd->oobinfo;
+
+ /* Autoplace of oob data ? Use the default placement scheme */
+ if (oobsel->useecc == MTD_NANDECC_AUTOPLACE) {
+ oobsel = this->autooob;
+ autoplace = 1;
+ }
+
+ /* Setup start page */
+ page = (int) (to >> this->page_shift);
+ /* Invalidate the page cache, if we write to the cached page */
+ if (page <= this->pagebuf && this->pagebuf < ((to + total_len) >> this->page_shift))
+ this->pagebuf = -1;
+
+ startpage = page & this->pagemask;
+
+ /* Loop until all kvec' data has been written */
+ len = 0;
+ while (count) {
+ /* If the given tuple is >= pagesize then
+ * write it out from the iov
+ */
+ if ((vecs->iov_len - len) >= mtd->oobblock) {
+ /* Calc number of pages we can write
+ * out of this iov in one go */
+ numpages = (vecs->iov_len - len) >> this->page_shift;
+ /* Do not cross block boundaries */
+ numpages = min (ppblock - (startpage & (ppblock - 1)), numpages);
+ oobbuf = nand_prepare_oobbuf (mtd, NULL, oobsel, autoplace, numpages);
+ bufstart = (u_char *)vecs->iov_base;
+ bufstart += len;
+ this->data_poi = bufstart;
+ oob = 0;
+ for (i = 1; i <= numpages; i++) {
+ /* Write one page. If this is the last page to write
+ * then use the real pageprogram command, else select
+ * cached programming if supported by the chip.
+ */
+ ret = nand_write_page (mtd, this, page & this->pagemask,
+ &oobbuf[oob], oobsel, i != numpages);
+ if (ret)
+ goto out;
+ this->data_poi += mtd->oobblock;
+ len += mtd->oobblock;
+ oob += mtd->oobsize;
+ page++;
+ }
+ /* Check, if we have to switch to the next tuple */
+ if (len >= (int) vecs->iov_len) {
+ vecs++;
+ len = 0;
+ count--;
+ }
+ } else {
+ /* We must use the internal buffer, read data out of each
+ * tuple until we have a full page to write
+ */
+ int cnt = 0;
+ while (cnt < mtd->oobblock) {
+ if (vecs->iov_base != NULL && vecs->iov_len)
+ this->data_buf[cnt++] = ((u_char *) vecs->iov_base)[len++];
+ /* Check, if we have to switch to the next tuple */
+ if (len >= (int) vecs->iov_len) {
+ vecs++;
+ len = 0;
+ count--;
+ }
+ }
+ this->pagebuf = page;
+ this->data_poi = this->data_buf;
+ bufstart = this->data_poi;
+ numpages = 1;
+ oobbuf = nand_prepare_oobbuf (mtd, NULL, oobsel, autoplace, numpages);
+ ret = nand_write_page (mtd, this, page & this->pagemask,
+ oobbuf, oobsel, 0);
+ if (ret)
+ goto out;
+ page++;
+ }
+
+ this->data_poi = bufstart;
+ ret = nand_verify_pages (mtd, this, startpage, numpages, oobbuf, oobsel, chipnr, 0);
+ if (ret)
+ goto out;
+
+ written += mtd->oobblock * numpages;
+ /* All done ? */
+ if (!count)
+ break;
+
+ startpage = page & this->pagemask;
+ /* Check, if we cross a chip boundary */
+ if (!startpage) {
+ chipnr++;
+ this->select_chip(mtd, -1);
+ this->select_chip(mtd, chipnr);
+ }
+ }
+ ret = 0;
+out:
+ /* Deselect and wake up anyone waiting on the device */
+ nand_release_device(mtd);
+
+ *retlen = written;
+ return ret;
+}
+
+/**
+ * single_erease_cmd - [GENERIC] NAND standard block erase command function
+ * @mtd: MTD device structure
+ * @page: the page address of the block which will be erased
+ *
+ * Standard erase command for NAND chips
+ */
+static void single_erase_cmd (struct mtd_info *mtd, int page)
+{
+ struct nand_chip *this = mtd->priv;
+ /* Send commands to erase a block */
+ this->cmdfunc (mtd, NAND_CMD_ERASE1, -1, page);
+ this->cmdfunc (mtd, NAND_CMD_ERASE2, -1, -1);
+}
+
+/**
+ * multi_erease_cmd - [GENERIC] AND specific block erase command function
+ * @mtd: MTD device structure
+ * @page: the page address of the block which will be erased
+ *
+ * AND multi block erase command function
+ * Erase 4 consecutive blocks
+ */
+static void multi_erase_cmd (struct mtd_info *mtd, int page)
+{
+ struct nand_chip *this = mtd->priv;
+ /* Send commands to erase a block */
+ this->cmdfunc (mtd, NAND_CMD_ERASE1, -1, page++);
+ this->cmdfunc (mtd, NAND_CMD_ERASE1, -1, page++);
+ this->cmdfunc (mtd, NAND_CMD_ERASE1, -1, page++);
+ this->cmdfunc (mtd, NAND_CMD_ERASE1, -1, page);
+ this->cmdfunc (mtd, NAND_CMD_ERASE2, -1, -1);
+}
+
+/**
+ * nand_erase - [MTD Interface] erase block(s)
+ * @mtd: MTD device structure
+ * @instr: erase instruction
+ *
+ * Erase one ore more blocks
+ */
+static int nand_erase (struct mtd_info *mtd, struct erase_info *instr)
+{
+ return nand_erase_nand (mtd, instr, 0);
+}
+
+/**
+ * nand_erase_intern - [NAND Interface] erase block(s)
+ * @mtd: MTD device structure
+ * @instr: erase instruction
+ * @allowbbt: allow erasing the bbt area
+ *
+ * Erase one ore more blocks
+ */
+int nand_erase_nand (struct mtd_info *mtd, struct erase_info *instr, int allowbbt)
+{
+ int page, len, status, pages_per_block, ret, chipnr;
+ struct nand_chip *this = mtd->priv;
+
+ DEBUG (MTD_DEBUG_LEVEL3,
+ "nand_erase: start = 0x%08x, len = %i\n", (unsigned int) instr->addr, (unsigned int) instr->len);
+
+ /* Start address must align on block boundary */
+ if (instr->addr & ((1 << this->phys_erase_shift) - 1)) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_erase: Unaligned address\n");
+ return -EINVAL;
+ }
+
+ /* Length must align on block boundary */
+ if (instr->len & ((1 << this->phys_erase_shift) - 1)) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_erase: Length not block aligned\n");
+ return -EINVAL;
+ }
+
+ /* Do not allow erase past end of device */
+ if ((instr->len + instr->addr) > mtd->size) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_erase: Erase past end of device\n");
+ return -EINVAL;
+ }
+
+ instr->fail_addr = 0xffffffff;
+
+ /* Grab the lock and see if the device is available */
+ nand_get_device (this, mtd, FL_ERASING);
+
+ /* Shift to get first page */
+ page = (int) (instr->addr >> this->page_shift);
+ chipnr = (int) (instr->addr >> this->chip_shift);
+
+ /* Calculate pages in each block */
+ pages_per_block = 1 << (this->phys_erase_shift - this->page_shift);
+
+ /* Select the NAND device */
+ this->select_chip(mtd, chipnr);
+
+ /* Check the WP bit */
+ /* Check, if it is write protected */
+ if (nand_check_wp(mtd)) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_erase: Device is write protected!!!\n");
+ instr->state = MTD_ERASE_FAILED;
+ goto erase_exit;
+ }
+
+ /* Loop through the pages */
+ len = instr->len;
+
+ instr->state = MTD_ERASING;
+
+ while (len) {
+ /* Check if we have a bad block, we do not erase bad blocks ! */
+ if (nand_block_checkbad(mtd, ((loff_t) page) << this->page_shift, 0, allowbbt)) {
+ printk (KERN_WARNING "nand_erase: attempt to erase a bad block at page 0x%08x\n", page);
+ instr->state = MTD_ERASE_FAILED;
+ goto erase_exit;
+ }
+
+ /* Invalidate the page cache, if we erase the block which contains
+ the current cached page */
+ if (page <= this->pagebuf && this->pagebuf < (page + pages_per_block))
+ this->pagebuf = -1;
+
+ this->erase_cmd (mtd, page & this->pagemask);
+
+ status = this->waitfunc (mtd, this, FL_ERASING);
+
+ /* See if block erase succeeded */
+ if (status & 0x01) {
+ DEBUG (MTD_DEBUG_LEVEL0, "nand_erase: " "Failed erase, page 0x%08x\n", page);
+ instr->state = MTD_ERASE_FAILED;
+ instr->fail_addr = (page << this->page_shift);
+ goto erase_exit;
+ }
+
+ /* Increment page address and decrement length */
+ len -= (1 << this->phys_erase_shift);
+ page += pages_per_block;
+
+ /* Check, if we cross a chip boundary */
+ if (len && !(page & this->pagemask)) {
+ chipnr++;
+ this->select_chip(mtd, -1);
+ this->select_chip(mtd, chipnr);
+ }
+ }
+ instr->state = MTD_ERASE_DONE;
+
+erase_exit:
+
+ ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO;
+ /* Do call back function */
+ if (!ret)
+ mtd_erase_callback(instr);
+
+ /* Deselect and wake up anyone waiting on the device */
+ nand_release_device(mtd);
+
+ /* Return more or less happy */
+ return ret;
+}
+
+/**
+ * nand_sync - [MTD Interface] sync
+ * @mtd: MTD device structure
+ *
+ * Sync is actually a wait for chip ready function
+ */
+static void nand_sync (struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+
+ DEBUG (MTD_DEBUG_LEVEL3, "nand_sync: called\n");
+
+ /* Grab the lock and see if the device is available */
+ nand_get_device (this, mtd, FL_SYNCING);
+ /* Release it and go back */
+ nand_release_device (mtd);
+}
+
+
+/**
+ * nand_block_isbad - [MTD Interface] Check whether the block at the given offset is bad
+ * @mtd: MTD device structure
+ * @ofs: offset relative to mtd start
+ */
+static int nand_block_isbad (struct mtd_info *mtd, loff_t ofs)
+{
+ /* Check for invalid offset */
+ if (ofs > mtd->size)
+ return -EINVAL;
+
+ return nand_block_checkbad (mtd, ofs, 1, 0);
+}
+
+/**
+ * nand_block_markbad - [MTD Interface] Mark the block at the given offset as bad
+ * @mtd: MTD device structure
+ * @ofs: offset relative to mtd start
+ */
+static int nand_block_markbad (struct mtd_info *mtd, loff_t ofs)
+{
+ struct nand_chip *this = mtd->priv;
+ int ret;
+
+ if ((ret = nand_block_isbad(mtd, ofs))) {
+ /* If it was bad already, return success and do nothing. */
+ if (ret > 0)
+ return 0;
+ return ret;
+ }
+
+ return this->block_markbad(mtd, ofs);
+}
+
+/**
+ * nand_scan - [NAND Interface] Scan for the NAND device
+ * @mtd: MTD device structure
+ * @maxchips: Number of chips to scan for
+ *
+ * This fills out all the not initialized function pointers
+ * with the defaults.
+ * The flash ID is read and the mtd/chip structures are
+ * filled with the appropriate values. Buffers are allocated if
+ * they are not provided by the board driver
+ *
+ */
+int nand_scan (struct mtd_info *mtd, int maxchips)
+{
+ int i, j, nand_maf_id, nand_dev_id, busw;
+ struct nand_chip *this = mtd->priv;
+
+ /* Get buswidth to select the correct functions*/
+ busw = this->options & NAND_BUSWIDTH_16;
+
+ /* check for proper chip_delay setup, set 20us if not */
+ if (!this->chip_delay)
+ this->chip_delay = 20;
+
+ /* check, if a user supplied command function given */
+ if (this->cmdfunc == NULL)
+ this->cmdfunc = nand_command;
+
+ /* check, if a user supplied wait function given */
+ if (this->waitfunc == NULL)
+ this->waitfunc = nand_wait;
+
+ if (!this->select_chip)
+ this->select_chip = nand_select_chip;
+ if (!this->write_byte)
+ this->write_byte = busw ? nand_write_byte16 : nand_write_byte;
+ if (!this->read_byte)
+ this->read_byte = busw ? nand_read_byte16 : nand_read_byte;
+ if (!this->write_word)
+ this->write_word = nand_write_word;
+ if (!this->read_word)
+ this->read_word = nand_read_word;
+ if (!this->block_bad)
+ this->block_bad = nand_block_bad;
+ if (!this->block_markbad)
+ this->block_markbad = nand_default_block_markbad;
+ if (!this->write_buf)
+ this->write_buf = busw ? nand_write_buf16 : nand_write_buf;
+ if (!this->read_buf)
+ this->read_buf = busw ? nand_read_buf16 : nand_read_buf;
+ if (!this->verify_buf)
+ this->verify_buf = busw ? nand_verify_buf16 : nand_verify_buf;
+ if (!this->scan_bbt)
+ this->scan_bbt = nand_default_bbt;
+
+ /* Select the device */
+ this->select_chip(mtd, 0);
+
+ /* Send the command for reading device ID */
+ this->cmdfunc (mtd, NAND_CMD_READID, 0x00, -1);
+
+ /* Read manufacturer and device IDs */
+ nand_maf_id = this->read_byte(mtd);
+ nand_dev_id = this->read_byte(mtd);
+
+ /* Print and store flash device information */
+ for (i = 0; nand_flash_ids[i].name != NULL; i++) {
+
+ if (nand_dev_id != nand_flash_ids[i].id)
+ continue;
+
+ if (!mtd->name) mtd->name = nand_flash_ids[i].name;
+ this->chipsize = nand_flash_ids[i].chipsize << 20;
+
+ /* New devices have all the information in additional id bytes */
+ if (!nand_flash_ids[i].pagesize) {
+ int extid;
+ /* The 3rd id byte contains non relevant data ATM */
+ extid = this->read_byte(mtd);
+ /* The 4th id byte is the important one */
+ extid = this->read_byte(mtd);
+ /* Calc pagesize */
+ mtd->oobblock = 1024 << (extid & 0x3);
+ extid >>= 2;
+ /* Calc oobsize */
+ mtd->oobsize = (8 << (extid & 0x03)) * (mtd->oobblock / 512);
+ extid >>= 2;
+ /* Calc blocksize. Blocksize is multiples of 64KiB */
+ mtd->erasesize = (64 * 1024) << (extid & 0x03);
+ extid >>= 2;
+ /* Get buswidth information */
+ busw = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0;
+
+ } else {
+ /* Old devices have this data hardcoded in the
+ * device id table */
+ mtd->erasesize = nand_flash_ids[i].erasesize;
+ mtd->oobblock = nand_flash_ids[i].pagesize;
+ mtd->oobsize = mtd->oobblock / 32;
+ busw = nand_flash_ids[i].options & NAND_BUSWIDTH_16;
+ }
+
+ /* Check, if buswidth is correct. Hardware drivers should set
+ * this correct ! */
+ if (busw != (this->options & NAND_BUSWIDTH_16)) {
+ printk (KERN_INFO "NAND device: Manufacturer ID:"
+ " 0x%02x, Chip ID: 0x%02x (%s %s)\n", nand_maf_id, nand_dev_id,
+ nand_manuf_ids[i].name , mtd->name);
+ printk (KERN_WARNING
+ "NAND bus width %d instead %d bit\n",
+ (this->options & NAND_BUSWIDTH_16) ? 16 : 8,
+ busw ? 16 : 8);
+ this->select_chip(mtd, -1);
+ return 1;
+ }
+
+ /* Calculate the address shift from the page size */
+ this->page_shift = ffs(mtd->oobblock) - 1;
+ this->bbt_erase_shift = this->phys_erase_shift = ffs(mtd->erasesize) - 1;
+ this->chip_shift = ffs(this->chipsize) - 1;
+
+ /* Set the bad block position */
+ this->badblockpos = mtd->oobblock > 512 ?
+ NAND_LARGE_BADBLOCK_POS : NAND_SMALL_BADBLOCK_POS;
+
+ /* Get chip options, preserve non chip based options */
+ this->options &= ~NAND_CHIPOPTIONS_MSK;
+ this->options |= nand_flash_ids[i].options & NAND_CHIPOPTIONS_MSK;
+ /* Set this as a default. Board drivers can override it, if neccecary */
+ this->options |= NAND_NO_AUTOINCR;
+ /* Check if this is a not a samsung device. Do not clear the options
+ * for chips which are not having an extended id.
+ */
+ if (nand_maf_id != NAND_MFR_SAMSUNG && !nand_flash_ids[i].pagesize)
+ this->options &= ~NAND_SAMSUNG_LP_OPTIONS;
+
+ /* Check for AND chips with 4 page planes */
+ if (this->options & NAND_4PAGE_ARRAY)
+ this->erase_cmd = multi_erase_cmd;
+ else
+ this->erase_cmd = single_erase_cmd;
+
+ /* Do not replace user supplied command function ! */
+ if (mtd->oobblock > 512 && this->cmdfunc == nand_command)
+ this->cmdfunc = nand_command_lp;
+
+ /* Try to identify manufacturer */
+ for (j = 0; nand_manuf_ids[j].id != 0x0; j++) {
+ if (nand_manuf_ids[j].id == nand_maf_id)
+ break;
+ }
+ printk (KERN_INFO "NAND device: Manufacturer ID:"
+ " 0x%02x, Chip ID: 0x%02x (%s %s)\n", nand_maf_id, nand_dev_id,
+ nand_manuf_ids[j].name , nand_flash_ids[i].name);
+ break;
+ }
+
+ if (!nand_flash_ids[i].name) {
+ printk (KERN_WARNING "No NAND device found!!!\n");
+ this->select_chip(mtd, -1);
+ return 1;
+ }
+
+ for (i=1; i < maxchips; i++) {
+ this->select_chip(mtd, i);
+
+ /* Send the command for reading device ID */
+ this->cmdfunc (mtd, NAND_CMD_READID, 0x00, -1);
+
+ /* Read manufacturer and device IDs */
+ if (nand_maf_id != this->read_byte(mtd) ||
+ nand_dev_id != this->read_byte(mtd))
+ break;
+ }
+ if (i > 1)
+ printk(KERN_INFO "%d NAND chips detected\n", i);
+
+ /* Allocate buffers, if neccecary */
+ if (!this->oob_buf) {
+ size_t len;
+ len = mtd->oobsize << (this->phys_erase_shift - this->page_shift);
+ this->oob_buf = kmalloc (len, GFP_KERNEL);
+ if (!this->oob_buf) {
+ printk (KERN_ERR "nand_scan(): Cannot allocate oob_buf\n");
+ return -ENOMEM;
+ }
+ this->options |= NAND_OOBBUF_ALLOC;
+ }
+
+ if (!this->data_buf) {
+ size_t len;
+ len = mtd->oobblock + mtd->oobsize;
+ this->data_buf = kmalloc (len, GFP_KERNEL);
+ if (!this->data_buf) {
+ if (this->options & NAND_OOBBUF_ALLOC)
+ kfree (this->oob_buf);
+ printk (KERN_ERR "nand_scan(): Cannot allocate data_buf\n");
+ return -ENOMEM;
+ }
+ this->options |= NAND_DATABUF_ALLOC;
+ }
+
+ /* Store the number of chips and calc total size for mtd */
+ this->numchips = i;
+ mtd->size = i * this->chipsize;
+ /* Convert chipsize to number of pages per chip -1. */
+ this->pagemask = (this->chipsize >> this->page_shift) - 1;
+ /* Preset the internal oob buffer */
+ memset(this->oob_buf, 0xff, mtd->oobsize << (this->phys_erase_shift - this->page_shift));
+
+ /* If no default placement scheme is given, select an
+ * appropriate one */
+ if (!this->autooob) {
+ /* Select the appropriate default oob placement scheme for
+ * placement agnostic filesystems */
+ switch (mtd->oobsize) {
+ case 8:
+ this->autooob = &nand_oob_8;
+ break;
+ case 16:
+ this->autooob = &nand_oob_16;
+ break;
+ case 64:
+ this->autooob = &nand_oob_64;
+ break;
+ default:
+ printk (KERN_WARNING "No oob scheme defined for oobsize %d\n",
+ mtd->oobsize);
+ BUG();
+ }
+ }
+
+ /* The number of bytes available for the filesystem to place fs dependend
+ * oob data */
+ if (this->options & NAND_BUSWIDTH_16) {
+ mtd->oobavail = mtd->oobsize - (this->autooob->eccbytes + 2);
+ if (this->autooob->eccbytes & 0x01)
+ mtd->oobavail--;
+ } else
+ mtd->oobavail = mtd->oobsize - (this->autooob->eccbytes + 1);
+
+ /*
+ * check ECC mode, default to software
+ * if 3byte/512byte hardware ECC is selected and we have 256 byte pagesize
+ * fallback to software ECC
+ */
+ this->eccsize = 256; /* set default eccsize */
+ this->eccbytes = 3;
+
+ switch (this->eccmode) {
+ case NAND_ECC_HW12_2048:
+ if (mtd->oobblock < 2048) {
+ printk(KERN_WARNING "2048 byte HW ECC not possible on %d byte page size, fallback to SW ECC\n",
+ mtd->oobblock);
+ this->eccmode = NAND_ECC_SOFT;
+ this->calculate_ecc = nand_calculate_ecc;
+ this->correct_data = nand_correct_data;
+ } else
+ this->eccsize = 2048;
+ break;
+
+ case NAND_ECC_HW3_512:
+ case NAND_ECC_HW6_512:
+ case NAND_ECC_HW8_512:
+ if (mtd->oobblock == 256) {
+ printk (KERN_WARNING "512 byte HW ECC not possible on 256 Byte pagesize, fallback to SW ECC \n");
+ this->eccmode = NAND_ECC_SOFT;
+ this->calculate_ecc = nand_calculate_ecc;
+ this->correct_data = nand_correct_data;
+ } else
+ this->eccsize = 512; /* set eccsize to 512 */
+ break;
+
+ case NAND_ECC_HW3_256:
+ break;
+
+ case NAND_ECC_NONE:
+ printk (KERN_WARNING "NAND_ECC_NONE selected by board driver. This is not recommended !!\n");
+ this->eccmode = NAND_ECC_NONE;
+ break;
+
+ case NAND_ECC_SOFT:
+ this->calculate_ecc = nand_calculate_ecc;
+ this->correct_data = nand_correct_data;
+ break;
+
+ default:
+ printk (KERN_WARNING "Invalid NAND_ECC_MODE %d\n", this->eccmode);
+ BUG();
+ }
+
+ /* Check hardware ecc function availability and adjust number of ecc bytes per
+ * calculation step
+ */
+ switch (this->eccmode) {
+ case NAND_ECC_HW12_2048:
+ this->eccbytes += 4;
+ case NAND_ECC_HW8_512:
+ this->eccbytes += 2;
+ case NAND_ECC_HW6_512:
+ this->eccbytes += 3;
+ case NAND_ECC_HW3_512:
+ case NAND_ECC_HW3_256:
+ if (this->calculate_ecc && this->correct_data && this->enable_hwecc)
+ break;
+ printk (KERN_WARNING "No ECC functions supplied, Hardware ECC not possible\n");
+ BUG();
+ }
+
+ mtd->eccsize = this->eccsize;
+
+ /* Set the number of read / write steps for one page to ensure ECC generation */
+ switch (this->eccmode) {
+ case NAND_ECC_HW12_2048:
+ this->eccsteps = mtd->oobblock / 2048;
+ break;
+ case NAND_ECC_HW3_512:
+ case NAND_ECC_HW6_512:
+ case NAND_ECC_HW8_512:
+ this->eccsteps = mtd->oobblock / 512;
+ break;
+ case NAND_ECC_HW3_256:
+ case NAND_ECC_SOFT:
+ this->eccsteps = mtd->oobblock / 256;
+ break;
+
+ case NAND_ECC_NONE:
+ this->eccsteps = 1;
+ break;
+ }
+
+ /* Initialize state, waitqueue and spinlock */
+ this->state = FL_READY;
+ init_waitqueue_head (&this->wq);
+ spin_lock_init (&this->chip_lock);
+
+ /* De-select the device */
+ this->select_chip(mtd, -1);
+
+ /* Invalidate the pagebuffer reference */
+ this->pagebuf = -1;
+
+ /* Fill in remaining MTD driver data */
+ mtd->type = MTD_NANDFLASH;
+ mtd->flags = MTD_CAP_NANDFLASH | MTD_ECC;
+ mtd->ecctype = MTD_ECC_SW;
+ mtd->erase = nand_erase;
+ mtd->point = NULL;
+ mtd->unpoint = NULL;
+ mtd->read = nand_read;
+ mtd->write = nand_write;
+ mtd->read_ecc = nand_read_ecc;
+ mtd->write_ecc = nand_write_ecc;
+ mtd->read_oob = nand_read_oob;
+ mtd->write_oob = nand_write_oob;
+ mtd->readv = NULL;
+ mtd->writev = nand_writev;
+ mtd->writev_ecc = nand_writev_ecc;
+ mtd->sync = nand_sync;
+ mtd->lock = NULL;
+ mtd->unlock = NULL;
+ mtd->suspend = NULL;
+ mtd->resume = NULL;
+ mtd->block_isbad = nand_block_isbad;
+ mtd->block_markbad = nand_block_markbad;
+
+ /* and make the autooob the default one */
+ memcpy(&mtd->oobinfo, this->autooob, sizeof(mtd->oobinfo));
+
+ mtd->owner = THIS_MODULE;
+
+ /* Build bad block table */
+ return this->scan_bbt (mtd);
+}
+
+/**
+ * nand_release - [NAND Interface] Free resources held by the NAND device
+ * @mtd: MTD device structure
+*/
+void nand_release (struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+
+#ifdef CONFIG_MTD_PARTITIONS
+ /* Deregister partitions */
+ del_mtd_partitions (mtd);
+#endif
+ /* Deregister the device */
+ del_mtd_device (mtd);
+
+ /* Free bad block table memory, if allocated */
+ if (this->bbt)
+ kfree (this->bbt);
+ /* Buffer allocated by nand_scan ? */
+ if (this->options & NAND_OOBBUF_ALLOC)
+ kfree (this->oob_buf);
+ /* Buffer allocated by nand_scan ? */
+ if (this->options & NAND_DATABUF_ALLOC)
+ kfree (this->data_buf);
+}
+
+EXPORT_SYMBOL (nand_scan);
+EXPORT_SYMBOL (nand_release);
+
+MODULE_LICENSE ("GPL");
+MODULE_AUTHOR ("Steven J. Hill <sjhill@realitydiluted.com>, Thomas Gleixner <tglx@linutronix.de>");
+MODULE_DESCRIPTION ("Generic NAND flash driver code");
diff --git a/drivers/mtd/nand/nand_bbt.c b/drivers/mtd/nand/nand_bbt.c
new file mode 100644
index 000000000000..9a1949751c1f
--- /dev/null
+++ b/drivers/mtd/nand/nand_bbt.c
@@ -0,0 +1,1056 @@
+/*
+ * drivers/mtd/nand_bbt.c
+ *
+ * Overview:
+ * Bad block table support for the NAND driver
+ *
+ * Copyright (C) 2004 Thomas Gleixner (tglx@linutronix.de)
+ *
+ * $Id: nand_bbt.c,v 1.28 2004/11/13 10:19:09 gleixner Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Description:
+ *
+ * When nand_scan_bbt is called, then it tries to find the bad block table
+ * depending on the options in the bbt descriptor(s). If a bbt is found
+ * then the contents are read and the memory based bbt is created. If a
+ * mirrored bbt is selected then the mirror is searched too and the
+ * versions are compared. If the mirror has a greater version number
+ * than the mirror bbt is used to build the memory based bbt.
+ * If the tables are not versioned, then we "or" the bad block information.
+ * If one of the bbt's is out of date or does not exist it is (re)created.
+ * If no bbt exists at all then the device is scanned for factory marked
+ * good / bad blocks and the bad block tables are created.
+ *
+ * For manufacturer created bbts like the one found on M-SYS DOC devices
+ * the bbt is searched and read but never created
+ *
+ * The autogenerated bad block table is located in the last good blocks
+ * of the device. The table is mirrored, so it can be updated eventually.
+ * The table is marked in the oob area with an ident pattern and a version
+ * number which indicates which of both tables is more up to date.
+ *
+ * The table uses 2 bits per block
+ * 11b: block is good
+ * 00b: block is factory marked bad
+ * 01b, 10b: block is marked bad due to wear
+ *
+ * The memory bad block table uses the following scheme:
+ * 00b: block is good
+ * 01b: block is marked bad due to wear
+ * 10b: block is reserved (to protect the bbt area)
+ * 11b: block is factory marked bad
+ *
+ * Multichip devices like DOC store the bad block info per floor.
+ *
+ * Following assumptions are made:
+ * - bbts start at a page boundary, if autolocated on a block boundary
+ * - the space neccecary for a bbt in FLASH does not exceed a block boundary
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/mtd/compatmac.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+
+
+/**
+ * check_pattern - [GENERIC] check if a pattern is in the buffer
+ * @buf: the buffer to search
+ * @len: the length of buffer to search
+ * @paglen: the pagelength
+ * @td: search pattern descriptor
+ *
+ * Check for a pattern at the given place. Used to search bad block
+ * tables and good / bad block identifiers.
+ * If the SCAN_EMPTY option is set then check, if all bytes except the
+ * pattern area contain 0xff
+ *
+*/
+static int check_pattern (uint8_t *buf, int len, int paglen, struct nand_bbt_descr *td)
+{
+ int i, end;
+ uint8_t *p = buf;
+
+ end = paglen + td->offs;
+ if (td->options & NAND_BBT_SCANEMPTY) {
+ for (i = 0; i < end; i++) {
+ if (p[i] != 0xff)
+ return -1;
+ }
+ }
+ p += end;
+
+ /* Compare the pattern */
+ for (i = 0; i < td->len; i++) {
+ if (p[i] != td->pattern[i])
+ return -1;
+ }
+
+ p += td->len;
+ end += td->len;
+ if (td->options & NAND_BBT_SCANEMPTY) {
+ for (i = end; i < len; i++) {
+ if (*p++ != 0xff)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/**
+ * read_bbt - [GENERIC] Read the bad block table starting from page
+ * @mtd: MTD device structure
+ * @buf: temporary buffer
+ * @page: the starting page
+ * @num: the number of bbt descriptors to read
+ * @bits: number of bits per block
+ * @offs: offset in the memory table
+ * @reserved_block_code: Pattern to identify reserved blocks
+ *
+ * Read the bad block table starting from page.
+ *
+ */
+static int read_bbt (struct mtd_info *mtd, uint8_t *buf, int page, int num,
+ int bits, int offs, int reserved_block_code)
+{
+ int res, i, j, act = 0;
+ struct nand_chip *this = mtd->priv;
+ size_t retlen, len, totlen;
+ loff_t from;
+ uint8_t msk = (uint8_t) ((1 << bits) - 1);
+
+ totlen = (num * bits) >> 3;
+ from = ((loff_t)page) << this->page_shift;
+
+ while (totlen) {
+ len = min (totlen, (size_t) (1 << this->bbt_erase_shift));
+ res = mtd->read_ecc (mtd, from, len, &retlen, buf, NULL, this->autooob);
+ if (res < 0) {
+ if (retlen != len) {
+ printk (KERN_INFO "nand_bbt: Error reading bad block table\n");
+ return res;
+ }
+ printk (KERN_WARNING "nand_bbt: ECC error while reading bad block table\n");
+ }
+
+ /* Analyse data */
+ for (i = 0; i < len; i++) {
+ uint8_t dat = buf[i];
+ for (j = 0; j < 8; j += bits, act += 2) {
+ uint8_t tmp = (dat >> j) & msk;
+ if (tmp == msk)
+ continue;
+ if (reserved_block_code &&
+ (tmp == reserved_block_code)) {
+ printk (KERN_DEBUG "nand_read_bbt: Reserved block at 0x%08x\n",
+ ((offs << 2) + (act >> 1)) << this->bbt_erase_shift);
+ this->bbt[offs + (act >> 3)] |= 0x2 << (act & 0x06);
+ continue;
+ }
+ /* Leave it for now, if its matured we can move this
+ * message to MTD_DEBUG_LEVEL0 */
+ printk (KERN_DEBUG "nand_read_bbt: Bad block at 0x%08x\n",
+ ((offs << 2) + (act >> 1)) << this->bbt_erase_shift);
+ /* Factory marked bad or worn out ? */
+ if (tmp == 0)
+ this->bbt[offs + (act >> 3)] |= 0x3 << (act & 0x06);
+ else
+ this->bbt[offs + (act >> 3)] |= 0x1 << (act & 0x06);
+ }
+ }
+ totlen -= len;
+ from += len;
+ }
+ return 0;
+}
+
+/**
+ * read_abs_bbt - [GENERIC] Read the bad block table starting at a given page
+ * @mtd: MTD device structure
+ * @buf: temporary buffer
+ * @td: descriptor for the bad block table
+ * @chip: read the table for a specific chip, -1 read all chips.
+ * Applies only if NAND_BBT_PERCHIP option is set
+ *
+ * Read the bad block table for all chips starting at a given page
+ * We assume that the bbt bits are in consecutive order.
+*/
+static int read_abs_bbt (struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *td, int chip)
+{
+ struct nand_chip *this = mtd->priv;
+ int res = 0, i;
+ int bits;
+
+ bits = td->options & NAND_BBT_NRBITS_MSK;
+ if (td->options & NAND_BBT_PERCHIP) {
+ int offs = 0;
+ for (i = 0; i < this->numchips; i++) {
+ if (chip == -1 || chip == i)
+ res = read_bbt (mtd, buf, td->pages[i], this->chipsize >> this->bbt_erase_shift, bits, offs, td->reserved_block_code);
+ if (res)
+ return res;
+ offs += this->chipsize >> (this->bbt_erase_shift + 2);
+ }
+ } else {
+ res = read_bbt (mtd, buf, td->pages[0], mtd->size >> this->bbt_erase_shift, bits, 0, td->reserved_block_code);
+ if (res)
+ return res;
+ }
+ return 0;
+}
+
+/**
+ * read_abs_bbts - [GENERIC] Read the bad block table(s) for all chips starting at a given page
+ * @mtd: MTD device structure
+ * @buf: temporary buffer
+ * @td: descriptor for the bad block table
+ * @md: descriptor for the bad block table mirror
+ *
+ * Read the bad block table(s) for all chips starting at a given page
+ * We assume that the bbt bits are in consecutive order.
+ *
+*/
+static int read_abs_bbts (struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *td,
+ struct nand_bbt_descr *md)
+{
+ struct nand_chip *this = mtd->priv;
+
+ /* Read the primary version, if available */
+ if (td->options & NAND_BBT_VERSION) {
+ nand_read_raw (mtd, buf, td->pages[0] << this->page_shift, mtd->oobblock, mtd->oobsize);
+ td->version[0] = buf[mtd->oobblock + td->veroffs];
+ printk (KERN_DEBUG "Bad block table at page %d, version 0x%02X\n", td->pages[0], td->version[0]);
+ }
+
+ /* Read the mirror version, if available */
+ if (md && (md->options & NAND_BBT_VERSION)) {
+ nand_read_raw (mtd, buf, md->pages[0] << this->page_shift, mtd->oobblock, mtd->oobsize);
+ md->version[0] = buf[mtd->oobblock + md->veroffs];
+ printk (KERN_DEBUG "Bad block table at page %d, version 0x%02X\n", md->pages[0], md->version[0]);
+ }
+
+ return 1;
+}
+
+/**
+ * create_bbt - [GENERIC] Create a bad block table by scanning the device
+ * @mtd: MTD device structure
+ * @buf: temporary buffer
+ * @bd: descriptor for the good/bad block search pattern
+ * @chip: create the table for a specific chip, -1 read all chips.
+ * Applies only if NAND_BBT_PERCHIP option is set
+ *
+ * Create a bad block table by scanning the device
+ * for the given good/bad block identify pattern
+ */
+static void create_bbt (struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *bd, int chip)
+{
+ struct nand_chip *this = mtd->priv;
+ int i, j, numblocks, len, scanlen;
+ int startblock;
+ loff_t from;
+ size_t readlen, ooblen;
+
+ printk (KERN_INFO "Scanning device for bad blocks\n");
+
+ if (bd->options & NAND_BBT_SCANALLPAGES)
+ len = 1 << (this->bbt_erase_shift - this->page_shift);
+ else {
+ if (bd->options & NAND_BBT_SCAN2NDPAGE)
+ len = 2;
+ else
+ len = 1;
+ }
+ scanlen = mtd->oobblock + mtd->oobsize;
+ readlen = len * mtd->oobblock;
+ ooblen = len * mtd->oobsize;
+
+ if (chip == -1) {
+ /* Note that numblocks is 2 * (real numblocks) here, see i+=2 below as it
+ * makes shifting and masking less painful */
+ numblocks = mtd->size >> (this->bbt_erase_shift - 1);
+ startblock = 0;
+ from = 0;
+ } else {
+ if (chip >= this->numchips) {
+ printk (KERN_WARNING "create_bbt(): chipnr (%d) > available chips (%d)\n",
+ chip + 1, this->numchips);
+ return;
+ }
+ numblocks = this->chipsize >> (this->bbt_erase_shift - 1);
+ startblock = chip * numblocks;
+ numblocks += startblock;
+ from = startblock << (this->bbt_erase_shift - 1);
+ }
+
+ for (i = startblock; i < numblocks;) {
+ nand_read_raw (mtd, buf, from, readlen, ooblen);
+ for (j = 0; j < len; j++) {
+ if (check_pattern (&buf[j * scanlen], scanlen, mtd->oobblock, bd)) {
+ this->bbt[i >> 3] |= 0x03 << (i & 0x6);
+ printk (KERN_WARNING "Bad eraseblock %d at 0x%08x\n",
+ i >> 1, (unsigned int) from);
+ break;
+ }
+ }
+ i += 2;
+ from += (1 << this->bbt_erase_shift);
+ }
+}
+
+/**
+ * search_bbt - [GENERIC] scan the device for a specific bad block table
+ * @mtd: MTD device structure
+ * @buf: temporary buffer
+ * @td: descriptor for the bad block table
+ *
+ * Read the bad block table by searching for a given ident pattern.
+ * Search is preformed either from the beginning up or from the end of
+ * the device downwards. The search starts always at the start of a
+ * block.
+ * If the option NAND_BBT_PERCHIP is given, each chip is searched
+ * for a bbt, which contains the bad block information of this chip.
+ * This is neccecary to provide support for certain DOC devices.
+ *
+ * The bbt ident pattern resides in the oob area of the first page
+ * in a block.
+ */
+static int search_bbt (struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *td)
+{
+ struct nand_chip *this = mtd->priv;
+ int i, chips;
+ int bits, startblock, block, dir;
+ int scanlen = mtd->oobblock + mtd->oobsize;
+ int bbtblocks;
+
+ /* Search direction top -> down ? */
+ if (td->options & NAND_BBT_LASTBLOCK) {
+ startblock = (mtd->size >> this->bbt_erase_shift) -1;
+ dir = -1;
+ } else {
+ startblock = 0;
+ dir = 1;
+ }
+
+ /* Do we have a bbt per chip ? */
+ if (td->options & NAND_BBT_PERCHIP) {
+ chips = this->numchips;
+ bbtblocks = this->chipsize >> this->bbt_erase_shift;
+ startblock &= bbtblocks - 1;
+ } else {
+ chips = 1;
+ bbtblocks = mtd->size >> this->bbt_erase_shift;
+ }
+
+ /* Number of bits for each erase block in the bbt */
+ bits = td->options & NAND_BBT_NRBITS_MSK;
+
+ for (i = 0; i < chips; i++) {
+ /* Reset version information */
+ td->version[i] = 0;
+ td->pages[i] = -1;
+ /* Scan the maximum number of blocks */
+ for (block = 0; block < td->maxblocks; block++) {
+ int actblock = startblock + dir * block;
+ /* Read first page */
+ nand_read_raw (mtd, buf, actblock << this->bbt_erase_shift, mtd->oobblock, mtd->oobsize);
+ if (!check_pattern(buf, scanlen, mtd->oobblock, td)) {
+ td->pages[i] = actblock << (this->bbt_erase_shift - this->page_shift);
+ if (td->options & NAND_BBT_VERSION) {
+ td->version[i] = buf[mtd->oobblock + td->veroffs];
+ }
+ break;
+ }
+ }
+ startblock += this->chipsize >> this->bbt_erase_shift;
+ }
+ /* Check, if we found a bbt for each requested chip */
+ for (i = 0; i < chips; i++) {
+ if (td->pages[i] == -1)
+ printk (KERN_WARNING "Bad block table not found for chip %d\n", i);
+ else
+ printk (KERN_DEBUG "Bad block table found at page %d, version 0x%02X\n", td->pages[i], td->version[i]);
+ }
+ return 0;
+}
+
+/**
+ * search_read_bbts - [GENERIC] scan the device for bad block table(s)
+ * @mtd: MTD device structure
+ * @buf: temporary buffer
+ * @td: descriptor for the bad block table
+ * @md: descriptor for the bad block table mirror
+ *
+ * Search and read the bad block table(s)
+*/
+static int search_read_bbts (struct mtd_info *mtd, uint8_t *buf,
+ struct nand_bbt_descr *td, struct nand_bbt_descr *md)
+{
+ /* Search the primary table */
+ search_bbt (mtd, buf, td);
+
+ /* Search the mirror table */
+ if (md)
+ search_bbt (mtd, buf, md);
+
+ /* Force result check */
+ return 1;
+}
+
+
+/**
+ * write_bbt - [GENERIC] (Re)write the bad block table
+ *
+ * @mtd: MTD device structure
+ * @buf: temporary buffer
+ * @td: descriptor for the bad block table
+ * @md: descriptor for the bad block table mirror
+ * @chipsel: selector for a specific chip, -1 for all
+ *
+ * (Re)write the bad block table
+ *
+*/
+static int write_bbt (struct mtd_info *mtd, uint8_t *buf,
+ struct nand_bbt_descr *td, struct nand_bbt_descr *md, int chipsel)
+{
+ struct nand_chip *this = mtd->priv;
+ struct nand_oobinfo oobinfo;
+ struct erase_info einfo;
+ int i, j, res, chip = 0;
+ int bits, startblock, dir, page, offs, numblocks, sft, sftmsk;
+ int nrchips, bbtoffs, pageoffs;
+ uint8_t msk[4];
+ uint8_t rcode = td->reserved_block_code;
+ size_t retlen, len = 0;
+ loff_t to;
+
+ if (!rcode)
+ rcode = 0xff;
+ /* Write bad block table per chip rather than per device ? */
+ if (td->options & NAND_BBT_PERCHIP) {
+ numblocks = (int) (this->chipsize >> this->bbt_erase_shift);
+ /* Full device write or specific chip ? */
+ if (chipsel == -1) {
+ nrchips = this->numchips;
+ } else {
+ nrchips = chipsel + 1;
+ chip = chipsel;
+ }
+ } else {
+ numblocks = (int) (mtd->size >> this->bbt_erase_shift);
+ nrchips = 1;
+ }
+
+ /* Loop through the chips */
+ for (; chip < nrchips; chip++) {
+
+ /* There was already a version of the table, reuse the page
+ * This applies for absolute placement too, as we have the
+ * page nr. in td->pages.
+ */
+ if (td->pages[chip] != -1) {
+ page = td->pages[chip];
+ goto write;
+ }
+
+ /* Automatic placement of the bad block table */
+ /* Search direction top -> down ? */
+ if (td->options & NAND_BBT_LASTBLOCK) {
+ startblock = numblocks * (chip + 1) - 1;
+ dir = -1;
+ } else {
+ startblock = chip * numblocks;
+ dir = 1;
+ }
+
+ for (i = 0; i < td->maxblocks; i++) {
+ int block = startblock + dir * i;
+ /* Check, if the block is bad */
+ switch ((this->bbt[block >> 2] >> (2 * (block & 0x03))) & 0x03) {
+ case 0x01:
+ case 0x03:
+ continue;
+ }
+ page = block << (this->bbt_erase_shift - this->page_shift);
+ /* Check, if the block is used by the mirror table */
+ if (!md || md->pages[chip] != page)
+ goto write;
+ }
+ printk (KERN_ERR "No space left to write bad block table\n");
+ return -ENOSPC;
+write:
+
+ /* Set up shift count and masks for the flash table */
+ bits = td->options & NAND_BBT_NRBITS_MSK;
+ switch (bits) {
+ case 1: sft = 3; sftmsk = 0x07; msk[0] = 0x00; msk[1] = 0x01; msk[2] = ~rcode; msk[3] = 0x01; break;
+ case 2: sft = 2; sftmsk = 0x06; msk[0] = 0x00; msk[1] = 0x01; msk[2] = ~rcode; msk[3] = 0x03; break;
+ case 4: sft = 1; sftmsk = 0x04; msk[0] = 0x00; msk[1] = 0x0C; msk[2] = ~rcode; msk[3] = 0x0f; break;
+ case 8: sft = 0; sftmsk = 0x00; msk[0] = 0x00; msk[1] = 0x0F; msk[2] = ~rcode; msk[3] = 0xff; break;
+ default: return -EINVAL;
+ }
+
+ bbtoffs = chip * (numblocks >> 2);
+
+ to = ((loff_t) page) << this->page_shift;
+
+ memcpy (&oobinfo, this->autooob, sizeof(oobinfo));
+ oobinfo.useecc = MTD_NANDECC_PLACEONLY;
+
+ /* Must we save the block contents ? */
+ if (td->options & NAND_BBT_SAVECONTENT) {
+ /* Make it block aligned */
+ to &= ~((loff_t) ((1 << this->bbt_erase_shift) - 1));
+ len = 1 << this->bbt_erase_shift;
+ res = mtd->read_ecc (mtd, to, len, &retlen, buf, &buf[len], &oobinfo);
+ if (res < 0) {
+ if (retlen != len) {
+ printk (KERN_INFO "nand_bbt: Error reading block for writing the bad block table\n");
+ return res;
+ }
+ printk (KERN_WARNING "nand_bbt: ECC error while reading block for writing bad block table\n");
+ }
+ /* Calc the byte offset in the buffer */
+ pageoffs = page - (int)(to >> this->page_shift);
+ offs = pageoffs << this->page_shift;
+ /* Preset the bbt area with 0xff */
+ memset (&buf[offs], 0xff, (size_t)(numblocks >> sft));
+ /* Preset the bbt's oob area with 0xff */
+ memset (&buf[len + pageoffs * mtd->oobsize], 0xff,
+ ((len >> this->page_shift) - pageoffs) * mtd->oobsize);
+ if (td->options & NAND_BBT_VERSION) {
+ buf[len + (pageoffs * mtd->oobsize) + td->veroffs] = td->version[chip];
+ }
+ } else {
+ /* Calc length */
+ len = (size_t) (numblocks >> sft);
+ /* Make it page aligned ! */
+ len = (len + (mtd->oobblock-1)) & ~(mtd->oobblock-1);
+ /* Preset the buffer with 0xff */
+ memset (buf, 0xff, len + (len >> this->page_shift) * mtd->oobsize);
+ offs = 0;
+ /* Pattern is located in oob area of first page */
+ memcpy (&buf[len + td->offs], td->pattern, td->len);
+ if (td->options & NAND_BBT_VERSION) {
+ buf[len + td->veroffs] = td->version[chip];
+ }
+ }
+
+ /* walk through the memory table */
+ for (i = 0; i < numblocks; ) {
+ uint8_t dat;
+ dat = this->bbt[bbtoffs + (i >> 2)];
+ for (j = 0; j < 4; j++ , i++) {
+ int sftcnt = (i << (3 - sft)) & sftmsk;
+ /* Do not store the reserved bbt blocks ! */
+ buf[offs + (i >> sft)] &= ~(msk[dat & 0x03] << sftcnt);
+ dat >>= 2;
+ }
+ }
+
+ memset (&einfo, 0, sizeof (einfo));
+ einfo.mtd = mtd;
+ einfo.addr = (unsigned long) to;
+ einfo.len = 1 << this->bbt_erase_shift;
+ res = nand_erase_nand (mtd, &einfo, 1);
+ if (res < 0) {
+ printk (KERN_WARNING "nand_bbt: Error during block erase: %d\n", res);
+ return res;
+ }
+
+ res = mtd->write_ecc (mtd, to, len, &retlen, buf, &buf[len], &oobinfo);
+ if (res < 0) {
+ printk (KERN_WARNING "nand_bbt: Error while writing bad block table %d\n", res);
+ return res;
+ }
+ printk (KERN_DEBUG "Bad block table written to 0x%08x, version 0x%02X\n",
+ (unsigned int) to, td->version[chip]);
+
+ /* Mark it as used */
+ td->pages[chip] = page;
+ }
+ return 0;
+}
+
+/**
+ * nand_memory_bbt - [GENERIC] create a memory based bad block table
+ * @mtd: MTD device structure
+ * @bd: descriptor for the good/bad block search pattern
+ *
+ * The function creates a memory based bbt by scanning the device
+ * for manufacturer / software marked good / bad blocks
+*/
+static int nand_memory_bbt (struct mtd_info *mtd, struct nand_bbt_descr *bd)
+{
+ struct nand_chip *this = mtd->priv;
+
+ /* Ensure that we only scan for the pattern and nothing else */
+ bd->options = 0;
+ create_bbt (mtd, this->data_buf, bd, -1);
+ return 0;
+}
+
+/**
+ * check_create - [GENERIC] create and write bbt(s) if neccecary
+ * @mtd: MTD device structure
+ * @buf: temporary buffer
+ * @bd: descriptor for the good/bad block search pattern
+ *
+ * The function checks the results of the previous call to read_bbt
+ * and creates / updates the bbt(s) if neccecary
+ * Creation is neccecary if no bbt was found for the chip/device
+ * Update is neccecary if one of the tables is missing or the
+ * version nr. of one table is less than the other
+*/
+static int check_create (struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *bd)
+{
+ int i, chips, writeops, chipsel, res;
+ struct nand_chip *this = mtd->priv;
+ struct nand_bbt_descr *td = this->bbt_td;
+ struct nand_bbt_descr *md = this->bbt_md;
+ struct nand_bbt_descr *rd, *rd2;
+
+ /* Do we have a bbt per chip ? */
+ if (td->options & NAND_BBT_PERCHIP)
+ chips = this->numchips;
+ else
+ chips = 1;
+
+ for (i = 0; i < chips; i++) {
+ writeops = 0;
+ rd = NULL;
+ rd2 = NULL;
+ /* Per chip or per device ? */
+ chipsel = (td->options & NAND_BBT_PERCHIP) ? i : -1;
+ /* Mirrored table avilable ? */
+ if (md) {
+ if (td->pages[i] == -1 && md->pages[i] == -1) {
+ writeops = 0x03;
+ goto create;
+ }
+
+ if (td->pages[i] == -1) {
+ rd = md;
+ td->version[i] = md->version[i];
+ writeops = 1;
+ goto writecheck;
+ }
+
+ if (md->pages[i] == -1) {
+ rd = td;
+ md->version[i] = td->version[i];
+ writeops = 2;
+ goto writecheck;
+ }
+
+ if (td->version[i] == md->version[i]) {
+ rd = td;
+ if (!(td->options & NAND_BBT_VERSION))
+ rd2 = md;
+ goto writecheck;
+ }
+
+ if (((int8_t) (td->version[i] - md->version[i])) > 0) {
+ rd = td;
+ md->version[i] = td->version[i];
+ writeops = 2;
+ } else {
+ rd = md;
+ td->version[i] = md->version[i];
+ writeops = 1;
+ }
+
+ goto writecheck;
+
+ } else {
+ if (td->pages[i] == -1) {
+ writeops = 0x01;
+ goto create;
+ }
+ rd = td;
+ goto writecheck;
+ }
+create:
+ /* Create the bad block table by scanning the device ? */
+ if (!(td->options & NAND_BBT_CREATE))
+ continue;
+
+ /* Create the table in memory by scanning the chip(s) */
+ create_bbt (mtd, buf, bd, chipsel);
+
+ td->version[i] = 1;
+ if (md)
+ md->version[i] = 1;
+writecheck:
+ /* read back first ? */
+ if (rd)
+ read_abs_bbt (mtd, buf, rd, chipsel);
+ /* If they weren't versioned, read both. */
+ if (rd2)
+ read_abs_bbt (mtd, buf, rd2, chipsel);
+
+ /* Write the bad block table to the device ? */
+ if ((writeops & 0x01) && (td->options & NAND_BBT_WRITE)) {
+ res = write_bbt (mtd, buf, td, md, chipsel);
+ if (res < 0)
+ return res;
+ }
+
+ /* Write the mirror bad block table to the device ? */
+ if ((writeops & 0x02) && md && (md->options & NAND_BBT_WRITE)) {
+ res = write_bbt (mtd, buf, md, td, chipsel);
+ if (res < 0)
+ return res;
+ }
+ }
+ return 0;
+}
+
+/**
+ * mark_bbt_regions - [GENERIC] mark the bad block table regions
+ * @mtd: MTD device structure
+ * @td: bad block table descriptor
+ *
+ * The bad block table regions are marked as "bad" to prevent
+ * accidental erasures / writes. The regions are identified by
+ * the mark 0x02.
+*/
+static void mark_bbt_region (struct mtd_info *mtd, struct nand_bbt_descr *td)
+{
+ struct nand_chip *this = mtd->priv;
+ int i, j, chips, block, nrblocks, update;
+ uint8_t oldval, newval;
+
+ /* Do we have a bbt per chip ? */
+ if (td->options & NAND_BBT_PERCHIP) {
+ chips = this->numchips;
+ nrblocks = (int)(this->chipsize >> this->bbt_erase_shift);
+ } else {
+ chips = 1;
+ nrblocks = (int)(mtd->size >> this->bbt_erase_shift);
+ }
+
+ for (i = 0; i < chips; i++) {
+ if ((td->options & NAND_BBT_ABSPAGE) ||
+ !(td->options & NAND_BBT_WRITE)) {
+ if (td->pages[i] == -1) continue;
+ block = td->pages[i] >> (this->bbt_erase_shift - this->page_shift);
+ block <<= 1;
+ oldval = this->bbt[(block >> 3)];
+ newval = oldval | (0x2 << (block & 0x06));
+ this->bbt[(block >> 3)] = newval;
+ if ((oldval != newval) && td->reserved_block_code)
+ nand_update_bbt(mtd, block << (this->bbt_erase_shift - 1));
+ continue;
+ }
+ update = 0;
+ if (td->options & NAND_BBT_LASTBLOCK)
+ block = ((i + 1) * nrblocks) - td->maxblocks;
+ else
+ block = i * nrblocks;
+ block <<= 1;
+ for (j = 0; j < td->maxblocks; j++) {
+ oldval = this->bbt[(block >> 3)];
+ newval = oldval | (0x2 << (block & 0x06));
+ this->bbt[(block >> 3)] = newval;
+ if (oldval != newval) update = 1;
+ block += 2;
+ }
+ /* If we want reserved blocks to be recorded to flash, and some
+ new ones have been marked, then we need to update the stored
+ bbts. This should only happen once. */
+ if (update && td->reserved_block_code)
+ nand_update_bbt(mtd, (block - 2) << (this->bbt_erase_shift - 1));
+ }
+}
+
+/**
+ * nand_scan_bbt - [NAND Interface] scan, find, read and maybe create bad block table(s)
+ * @mtd: MTD device structure
+ * @bd: descriptor for the good/bad block search pattern
+ *
+ * The function checks, if a bad block table(s) is/are already
+ * available. If not it scans the device for manufacturer
+ * marked good / bad blocks and writes the bad block table(s) to
+ * the selected place.
+ *
+ * The bad block table memory is allocated here. It must be freed
+ * by calling the nand_free_bbt function.
+ *
+*/
+int nand_scan_bbt (struct mtd_info *mtd, struct nand_bbt_descr *bd)
+{
+ struct nand_chip *this = mtd->priv;
+ int len, res = 0;
+ uint8_t *buf;
+ struct nand_bbt_descr *td = this->bbt_td;
+ struct nand_bbt_descr *md = this->bbt_md;
+
+ len = mtd->size >> (this->bbt_erase_shift + 2);
+ /* Allocate memory (2bit per block) */
+ this->bbt = kmalloc (len, GFP_KERNEL);
+ if (!this->bbt) {
+ printk (KERN_ERR "nand_scan_bbt: Out of memory\n");
+ return -ENOMEM;
+ }
+ /* Clear the memory bad block table */
+ memset (this->bbt, 0x00, len);
+
+ /* If no primary table decriptor is given, scan the device
+ * to build a memory based bad block table
+ */
+ if (!td)
+ return nand_memory_bbt(mtd, bd);
+
+ /* Allocate a temporary buffer for one eraseblock incl. oob */
+ len = (1 << this->bbt_erase_shift);
+ len += (len >> this->page_shift) * mtd->oobsize;
+ buf = kmalloc (len, GFP_KERNEL);
+ if (!buf) {
+ printk (KERN_ERR "nand_bbt: Out of memory\n");
+ kfree (this->bbt);
+ this->bbt = NULL;
+ return -ENOMEM;
+ }
+
+ /* Is the bbt at a given page ? */
+ if (td->options & NAND_BBT_ABSPAGE) {
+ res = read_abs_bbts (mtd, buf, td, md);
+ } else {
+ /* Search the bad block table using a pattern in oob */
+ res = search_read_bbts (mtd, buf, td, md);
+ }
+
+ if (res)
+ res = check_create (mtd, buf, bd);
+
+ /* Prevent the bbt regions from erasing / writing */
+ mark_bbt_region (mtd, td);
+ if (md)
+ mark_bbt_region (mtd, md);
+
+ kfree (buf);
+ return res;
+}
+
+
+/**
+ * nand_update_bbt - [NAND Interface] update bad block table(s)
+ * @mtd: MTD device structure
+ * @offs: the offset of the newly marked block
+ *
+ * The function updates the bad block table(s)
+*/
+int nand_update_bbt (struct mtd_info *mtd, loff_t offs)
+{
+ struct nand_chip *this = mtd->priv;
+ int len, res = 0, writeops = 0;
+ int chip, chipsel;
+ uint8_t *buf;
+ struct nand_bbt_descr *td = this->bbt_td;
+ struct nand_bbt_descr *md = this->bbt_md;
+
+ if (!this->bbt || !td)
+ return -EINVAL;
+
+ len = mtd->size >> (this->bbt_erase_shift + 2);
+ /* Allocate a temporary buffer for one eraseblock incl. oob */
+ len = (1 << this->bbt_erase_shift);
+ len += (len >> this->page_shift) * mtd->oobsize;
+ buf = kmalloc (len, GFP_KERNEL);
+ if (!buf) {
+ printk (KERN_ERR "nand_update_bbt: Out of memory\n");
+ return -ENOMEM;
+ }
+
+ writeops = md != NULL ? 0x03 : 0x01;
+
+ /* Do we have a bbt per chip ? */
+ if (td->options & NAND_BBT_PERCHIP) {
+ chip = (int) (offs >> this->chip_shift);
+ chipsel = chip;
+ } else {
+ chip = 0;
+ chipsel = -1;
+ }
+
+ td->version[chip]++;
+ if (md)
+ md->version[chip]++;
+
+ /* Write the bad block table to the device ? */
+ if ((writeops & 0x01) && (td->options & NAND_BBT_WRITE)) {
+ res = write_bbt (mtd, buf, td, md, chipsel);
+ if (res < 0)
+ goto out;
+ }
+ /* Write the mirror bad block table to the device ? */
+ if ((writeops & 0x02) && md && (md->options & NAND_BBT_WRITE)) {
+ res = write_bbt (mtd, buf, md, td, chipsel);
+ }
+
+out:
+ kfree (buf);
+ return res;
+}
+
+/* Define some generic bad / good block scan pattern which are used
+ * while scanning a device for factory marked good / bad blocks
+ *
+ * The memory based patterns just
+ */
+static uint8_t scan_ff_pattern[] = { 0xff, 0xff };
+
+static struct nand_bbt_descr smallpage_memorybased = {
+ .options = 0,
+ .offs = 5,
+ .len = 1,
+ .pattern = scan_ff_pattern
+};
+
+static struct nand_bbt_descr largepage_memorybased = {
+ .options = 0,
+ .offs = 0,
+ .len = 2,
+ .pattern = scan_ff_pattern
+};
+
+static struct nand_bbt_descr smallpage_flashbased = {
+ .options = NAND_BBT_SCANEMPTY | NAND_BBT_SCANALLPAGES,
+ .offs = 5,
+ .len = 1,
+ .pattern = scan_ff_pattern
+};
+
+static struct nand_bbt_descr largepage_flashbased = {
+ .options = NAND_BBT_SCANEMPTY | NAND_BBT_SCANALLPAGES,
+ .offs = 0,
+ .len = 2,
+ .pattern = scan_ff_pattern
+};
+
+static uint8_t scan_agand_pattern[] = { 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC7 };
+
+static struct nand_bbt_descr agand_flashbased = {
+ .options = NAND_BBT_SCANEMPTY | NAND_BBT_SCANALLPAGES,
+ .offs = 0x20,
+ .len = 6,
+ .pattern = scan_agand_pattern
+};
+
+/* Generic flash bbt decriptors
+*/
+static uint8_t bbt_pattern[] = {'B', 'b', 't', '0' };
+static uint8_t mirror_pattern[] = {'1', 't', 'b', 'B' };
+
+static struct nand_bbt_descr bbt_main_descr = {
+ .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE
+ | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP,
+ .offs = 8,
+ .len = 4,
+ .veroffs = 12,
+ .maxblocks = 4,
+ .pattern = bbt_pattern
+};
+
+static struct nand_bbt_descr bbt_mirror_descr = {
+ .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE
+ | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP,
+ .offs = 8,
+ .len = 4,
+ .veroffs = 12,
+ .maxblocks = 4,
+ .pattern = mirror_pattern
+};
+
+/**
+ * nand_default_bbt - [NAND Interface] Select a default bad block table for the device
+ * @mtd: MTD device structure
+ *
+ * This function selects the default bad block table
+ * support for the device and calls the nand_scan_bbt function
+ *
+*/
+int nand_default_bbt (struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+
+ /* Default for AG-AND. We must use a flash based
+ * bad block table as the devices have factory marked
+ * _good_ blocks. Erasing those blocks leads to loss
+ * of the good / bad information, so we _must_ store
+ * this information in a good / bad table during
+ * startup
+ */
+ if (this->options & NAND_IS_AND) {
+ /* Use the default pattern descriptors */
+ if (!this->bbt_td) {
+ this->bbt_td = &bbt_main_descr;
+ this->bbt_md = &bbt_mirror_descr;
+ }
+ this->options |= NAND_USE_FLASH_BBT;
+ return nand_scan_bbt (mtd, &agand_flashbased);
+ }
+
+
+ /* Is a flash based bad block table requested ? */
+ if (this->options & NAND_USE_FLASH_BBT) {
+ /* Use the default pattern descriptors */
+ if (!this->bbt_td) {
+ this->bbt_td = &bbt_main_descr;
+ this->bbt_md = &bbt_mirror_descr;
+ }
+ if (!this->badblock_pattern) {
+ this->badblock_pattern = (mtd->oobblock > 512) ?
+ &largepage_flashbased : &smallpage_flashbased;
+ }
+ } else {
+ this->bbt_td = NULL;
+ this->bbt_md = NULL;
+ if (!this->badblock_pattern) {
+ this->badblock_pattern = (mtd->oobblock > 512) ?
+ &largepage_memorybased : &smallpage_memorybased;
+ }
+ }
+ return nand_scan_bbt (mtd, this->badblock_pattern);
+}
+
+/**
+ * nand_isbad_bbt - [NAND Interface] Check if a block is bad
+ * @mtd: MTD device structure
+ * @offs: offset in the device
+ * @allowbbt: allow access to bad block table region
+ *
+*/
+int nand_isbad_bbt (struct mtd_info *mtd, loff_t offs, int allowbbt)
+{
+ struct nand_chip *this = mtd->priv;
+ int block;
+ uint8_t res;
+
+ /* Get block number * 2 */
+ block = (int) (offs >> (this->bbt_erase_shift - 1));
+ res = (this->bbt[block >> 3] >> (block & 0x06)) & 0x03;
+
+ DEBUG (MTD_DEBUG_LEVEL2, "nand_isbad_bbt(): bbt info for offs 0x%08x: (block %d) 0x%02x\n",
+ (unsigned int)offs, res, block >> 1);
+
+ switch ((int)res) {
+ case 0x00: return 0;
+ case 0x01: return 1;
+ case 0x02: return allowbbt ? 0 : 1;
+ }
+ return 1;
+}
+
+EXPORT_SYMBOL (nand_scan_bbt);
+EXPORT_SYMBOL (nand_default_bbt);
diff --git a/drivers/mtd/nand/nand_ecc.c b/drivers/mtd/nand/nand_ecc.c
new file mode 100644
index 000000000000..2e341b75437a
--- /dev/null
+++ b/drivers/mtd/nand/nand_ecc.c
@@ -0,0 +1,250 @@
+/*
+ * This file contains an ECC algorithm from Toshiba that detects and
+ * corrects 1 bit errors in a 256 byte block of data.
+ *
+ * drivers/mtd/nand/nand_ecc.c
+ *
+ * Copyright (C) 2000-2004 Steven J. Hill (sjhill@realitydiluted.com)
+ * Toshiba America Electronics Components, Inc.
+ *
+ * $Id: nand_ecc.c,v 1.14 2004/06/16 15:34:37 gleixner Exp $
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 or (at your option) any
+ * later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this file; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * As a special exception, if other files instantiate templates or use
+ * macros or inline functions from these files, or you compile these
+ * files and link them with other works to produce a work based on these
+ * files, these files do not by themselves cause the resulting work to be
+ * covered by the GNU General Public License. However the source code for
+ * these files must still be made available in accordance with section (3)
+ * of the GNU General Public License.
+ *
+ * This exception does not invalidate any other reasons why a work based on
+ * this file might be covered by the GNU General Public License.
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mtd/nand_ecc.h>
+
+/*
+ * Pre-calculated 256-way 1 byte column parity
+ */
+static const u_char nand_ecc_precalc_table[] = {
+ 0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a, 0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00,
+ 0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f, 0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65,
+ 0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c, 0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66,
+ 0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59, 0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03,
+ 0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33, 0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69,
+ 0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56, 0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c,
+ 0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55, 0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f,
+ 0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30, 0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a,
+ 0x6a, 0x3f, 0x3c, 0x69, 0x33, 0x66, 0x65, 0x30, 0x30, 0x65, 0x66, 0x33, 0x69, 0x3c, 0x3f, 0x6a,
+ 0x0f, 0x5a, 0x59, 0x0c, 0x56, 0x03, 0x00, 0x55, 0x55, 0x00, 0x03, 0x56, 0x0c, 0x59, 0x5a, 0x0f,
+ 0x0c, 0x59, 0x5a, 0x0f, 0x55, 0x00, 0x03, 0x56, 0x56, 0x03, 0x00, 0x55, 0x0f, 0x5a, 0x59, 0x0c,
+ 0x69, 0x3c, 0x3f, 0x6a, 0x30, 0x65, 0x66, 0x33, 0x33, 0x66, 0x65, 0x30, 0x6a, 0x3f, 0x3c, 0x69,
+ 0x03, 0x56, 0x55, 0x00, 0x5a, 0x0f, 0x0c, 0x59, 0x59, 0x0c, 0x0f, 0x5a, 0x00, 0x55, 0x56, 0x03,
+ 0x66, 0x33, 0x30, 0x65, 0x3f, 0x6a, 0x69, 0x3c, 0x3c, 0x69, 0x6a, 0x3f, 0x65, 0x30, 0x33, 0x66,
+ 0x65, 0x30, 0x33, 0x66, 0x3c, 0x69, 0x6a, 0x3f, 0x3f, 0x6a, 0x69, 0x3c, 0x66, 0x33, 0x30, 0x65,
+ 0x00, 0x55, 0x56, 0x03, 0x59, 0x0c, 0x0f, 0x5a, 0x5a, 0x0f, 0x0c, 0x59, 0x03, 0x56, 0x55, 0x00
+};
+
+
+/**
+ * nand_trans_result - [GENERIC] create non-inverted ECC
+ * @reg2: line parity reg 2
+ * @reg3: line parity reg 3
+ * @ecc_code: ecc
+ *
+ * Creates non-inverted ECC code from line parity
+ */
+static void nand_trans_result(u_char reg2, u_char reg3,
+ u_char *ecc_code)
+{
+ u_char a, b, i, tmp1, tmp2;
+
+ /* Initialize variables */
+ a = b = 0x80;
+ tmp1 = tmp2 = 0;
+
+ /* Calculate first ECC byte */
+ for (i = 0; i < 4; i++) {
+ if (reg3 & a) /* LP15,13,11,9 --> ecc_code[0] */
+ tmp1 |= b;
+ b >>= 1;
+ if (reg2 & a) /* LP14,12,10,8 --> ecc_code[0] */
+ tmp1 |= b;
+ b >>= 1;
+ a >>= 1;
+ }
+
+ /* Calculate second ECC byte */
+ b = 0x80;
+ for (i = 0; i < 4; i++) {
+ if (reg3 & a) /* LP7,5,3,1 --> ecc_code[1] */
+ tmp2 |= b;
+ b >>= 1;
+ if (reg2 & a) /* LP6,4,2,0 --> ecc_code[1] */
+ tmp2 |= b;
+ b >>= 1;
+ a >>= 1;
+ }
+
+ /* Store two of the ECC bytes */
+ ecc_code[0] = tmp1;
+ ecc_code[1] = tmp2;
+}
+
+/**
+ * nand_calculate_ecc - [NAND Interface] Calculate 3 byte ECC code for 256 byte block
+ * @mtd: MTD block structure
+ * @dat: raw data
+ * @ecc_code: buffer for ECC
+ */
+int nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)
+{
+ u_char idx, reg1, reg2, reg3;
+ int j;
+
+ /* Initialize variables */
+ reg1 = reg2 = reg3 = 0;
+ ecc_code[0] = ecc_code[1] = ecc_code[2] = 0;
+
+ /* Build up column parity */
+ for(j = 0; j < 256; j++) {
+
+ /* Get CP0 - CP5 from table */
+ idx = nand_ecc_precalc_table[dat[j]];
+ reg1 ^= (idx & 0x3f);
+
+ /* All bit XOR = 1 ? */
+ if (idx & 0x40) {
+ reg3 ^= (u_char) j;
+ reg2 ^= ~((u_char) j);
+ }
+ }
+
+ /* Create non-inverted ECC code from line parity */
+ nand_trans_result(reg2, reg3, ecc_code);
+
+ /* Calculate final ECC code */
+ ecc_code[0] = ~ecc_code[0];
+ ecc_code[1] = ~ecc_code[1];
+ ecc_code[2] = ((~reg1) << 2) | 0x03;
+ return 0;
+}
+
+/**
+ * nand_correct_data - [NAND Interface] Detect and correct bit error(s)
+ * @mtd: MTD block structure
+ * @dat: raw data read from the chip
+ * @read_ecc: ECC from the chip
+ * @calc_ecc: the ECC calculated from raw data
+ *
+ * Detect and correct a 1 bit error for 256 byte block
+ */
+int nand_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc)
+{
+ u_char a, b, c, d1, d2, d3, add, bit, i;
+
+ /* Do error detection */
+ d1 = calc_ecc[0] ^ read_ecc[0];
+ d2 = calc_ecc[1] ^ read_ecc[1];
+ d3 = calc_ecc[2] ^ read_ecc[2];
+
+ if ((d1 | d2 | d3) == 0) {
+ /* No errors */
+ return 0;
+ }
+ else {
+ a = (d1 ^ (d1 >> 1)) & 0x55;
+ b = (d2 ^ (d2 >> 1)) & 0x55;
+ c = (d3 ^ (d3 >> 1)) & 0x54;
+
+ /* Found and will correct single bit error in the data */
+ if ((a == 0x55) && (b == 0x55) && (c == 0x54)) {
+ c = 0x80;
+ add = 0;
+ a = 0x80;
+ for (i=0; i<4; i++) {
+ if (d1 & c)
+ add |= a;
+ c >>= 2;
+ a >>= 1;
+ }
+ c = 0x80;
+ for (i=0; i<4; i++) {
+ if (d2 & c)
+ add |= a;
+ c >>= 2;
+ a >>= 1;
+ }
+ bit = 0;
+ b = 0x04;
+ c = 0x80;
+ for (i=0; i<3; i++) {
+ if (d3 & c)
+ bit |= b;
+ c >>= 2;
+ b >>= 1;
+ }
+ b = 0x01;
+ a = dat[add];
+ a ^= (b << bit);
+ dat[add] = a;
+ return 1;
+ }
+ else {
+ i = 0;
+ while (d1) {
+ if (d1 & 0x01)
+ ++i;
+ d1 >>= 1;
+ }
+ while (d2) {
+ if (d2 & 0x01)
+ ++i;
+ d2 >>= 1;
+ }
+ while (d3) {
+ if (d3 & 0x01)
+ ++i;
+ d3 >>= 1;
+ }
+ if (i == 1) {
+ /* ECC Code Error Correction */
+ read_ecc[0] = calc_ecc[0];
+ read_ecc[1] = calc_ecc[1];
+ read_ecc[2] = calc_ecc[2];
+ return 2;
+ }
+ else {
+ /* Uncorrectable Error */
+ return -1;
+ }
+ }
+ }
+
+ /* Should never happen */
+ return -1;
+}
+
+EXPORT_SYMBOL(nand_calculate_ecc);
+EXPORT_SYMBOL(nand_correct_data);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Steven J. Hill <sjhill@realitydiluted.com>");
+MODULE_DESCRIPTION("Generic NAND ECC support");
diff --git a/drivers/mtd/nand/nand_ids.c b/drivers/mtd/nand/nand_ids.c
new file mode 100644
index 000000000000..2d8c4321275b
--- /dev/null
+++ b/drivers/mtd/nand/nand_ids.c
@@ -0,0 +1,129 @@
+/*
+ * drivers/mtd/nandids.c
+ *
+ * Copyright (C) 2002 Thomas Gleixner (tglx@linutronix.de)
+ *
+ * $Id: nand_ids.c,v 1.10 2004/05/26 13:40:12 gleixner Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+#include <linux/module.h>
+#include <linux/mtd/nand.h>
+/*
+* Chip ID list
+*
+* Name. ID code, pagesize, chipsize in MegaByte, eraseblock size,
+* options
+*
+* Pagesize; 0, 256, 512
+* 0 get this information from the extended chip ID
++ 256 256 Byte page size
+* 512 512 Byte page size
+*/
+struct nand_flash_dev nand_flash_ids[] = {
+ {"NAND 1MiB 5V 8-bit", 0x6e, 256, 1, 0x1000, 0},
+ {"NAND 2MiB 5V 8-bit", 0x64, 256, 2, 0x1000, 0},
+ {"NAND 4MiB 5V 8-bit", 0x6b, 512, 4, 0x2000, 0},
+ {"NAND 1MiB 3,3V 8-bit", 0xe8, 256, 1, 0x1000, 0},
+ {"NAND 1MiB 3,3V 8-bit", 0xec, 256, 1, 0x1000, 0},
+ {"NAND 2MiB 3,3V 8-bit", 0xea, 256, 2, 0x1000, 0},
+ {"NAND 4MiB 3,3V 8-bit", 0xd5, 512, 4, 0x2000, 0},
+ {"NAND 4MiB 3,3V 8-bit", 0xe3, 512, 4, 0x2000, 0},
+ {"NAND 4MiB 3,3V 8-bit", 0xe5, 512, 4, 0x2000, 0},
+ {"NAND 8MiB 3,3V 8-bit", 0xd6, 512, 8, 0x2000, 0},
+
+ {"NAND 8MiB 1,8V 8-bit", 0x39, 512, 8, 0x2000, 0},
+ {"NAND 8MiB 3,3V 8-bit", 0xe6, 512, 8, 0x2000, 0},
+ {"NAND 8MiB 1,8V 16-bit", 0x49, 512, 8, 0x2000, NAND_BUSWIDTH_16},
+ {"NAND 8MiB 3,3V 16-bit", 0x59, 512, 8, 0x2000, NAND_BUSWIDTH_16},
+
+ {"NAND 16MiB 1,8V 8-bit", 0x33, 512, 16, 0x4000, 0},
+ {"NAND 16MiB 3,3V 8-bit", 0x73, 512, 16, 0x4000, 0},
+ {"NAND 16MiB 1,8V 16-bit", 0x43, 512, 16, 0x4000, NAND_BUSWIDTH_16},
+ {"NAND 16MiB 3,3V 16-bit", 0x53, 512, 16, 0x4000, NAND_BUSWIDTH_16},
+
+ {"NAND 32MiB 1,8V 8-bit", 0x35, 512, 32, 0x4000, 0},
+ {"NAND 32MiB 3,3V 8-bit", 0x75, 512, 32, 0x4000, 0},
+ {"NAND 32MiB 1,8V 16-bit", 0x45, 512, 32, 0x4000, NAND_BUSWIDTH_16},
+ {"NAND 32MiB 3,3V 16-bit", 0x55, 512, 32, 0x4000, NAND_BUSWIDTH_16},
+
+ {"NAND 64MiB 1,8V 8-bit", 0x36, 512, 64, 0x4000, 0},
+ {"NAND 64MiB 3,3V 8-bit", 0x76, 512, 64, 0x4000, 0},
+ {"NAND 64MiB 1,8V 16-bit", 0x46, 512, 64, 0x4000, NAND_BUSWIDTH_16},
+ {"NAND 64MiB 3,3V 16-bit", 0x56, 512, 64, 0x4000, NAND_BUSWIDTH_16},
+
+ {"NAND 128MiB 1,8V 8-bit", 0x78, 512, 128, 0x4000, 0},
+ {"NAND 128MiB 3,3V 8-bit", 0x79, 512, 128, 0x4000, 0},
+ {"NAND 128MiB 1,8V 16-bit", 0x72, 512, 128, 0x4000, NAND_BUSWIDTH_16},
+ {"NAND 128MiB 3,3V 16-bit", 0x74, 512, 128, 0x4000, NAND_BUSWIDTH_16},
+
+ {"NAND 256MiB 3,3V 8-bit", 0x71, 512, 256, 0x4000, 0},
+
+ {"NAND 512MiB 3,3V 8-bit", 0xDC, 512, 512, 0x4000, 0},
+
+ /* These are the new chips with large page size. The pagesize
+ * and the erasesize is determined from the extended id bytes
+ */
+ /* 1 Gigabit */
+ {"NAND 128MiB 1,8V 8-bit", 0xA1, 0, 128, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
+ {"NAND 128MiB 3,3V 8-bit", 0xF1, 0, 128, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
+ {"NAND 128MiB 1,8V 16-bit", 0xB1, 0, 128, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_BUSWIDTH_16 | NAND_NO_AUTOINCR},
+ {"NAND 128MiB 3,3V 16-bit", 0xC1, 0, 128, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_BUSWIDTH_16 | NAND_NO_AUTOINCR},
+
+ /* 2 Gigabit */
+ {"NAND 256MiB 1,8V 8-bit", 0xAA, 0, 256, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
+ {"NAND 256MiB 3,3V 8-bit", 0xDA, 0, 256, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
+ {"NAND 256MiB 1,8V 16-bit", 0xBA, 0, 256, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_BUSWIDTH_16 | NAND_NO_AUTOINCR},
+ {"NAND 256MiB 3,3V 16-bit", 0xCA, 0, 256, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_BUSWIDTH_16 | NAND_NO_AUTOINCR},
+
+ /* 4 Gigabit */
+ {"NAND 512MiB 1,8V 8-bit", 0xAC, 0, 512, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
+ {"NAND 512MiB 3,3V 8-bit", 0xDC, 0, 512, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
+ {"NAND 512MiB 1,8V 16-bit", 0xBC, 0, 512, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_BUSWIDTH_16 | NAND_NO_AUTOINCR},
+ {"NAND 512MiB 3,3V 16-bit", 0xCC, 0, 512, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_BUSWIDTH_16 | NAND_NO_AUTOINCR},
+
+ /* 8 Gigabit */
+ {"NAND 1GiB 1,8V 8-bit", 0xA3, 0, 1024, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
+ {"NAND 1GiB 3,3V 8-bit", 0xD3, 0, 1024, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
+ {"NAND 1GiB 1,8V 16-bit", 0xB3, 0, 1024, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_BUSWIDTH_16 | NAND_NO_AUTOINCR},
+ {"NAND 1GiB 3,3V 16-bit", 0xC3, 0, 1024, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_BUSWIDTH_16 | NAND_NO_AUTOINCR},
+
+ /* 16 Gigabit */
+ {"NAND 2GiB 1,8V 8-bit", 0xA5, 0, 2048, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
+ {"NAND 2GiB 3,3V 8-bit", 0xD5, 0, 2048, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_NO_AUTOINCR},
+ {"NAND 2GiB 1,8V 16-bit", 0xB5, 0, 2048, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_BUSWIDTH_16 | NAND_NO_AUTOINCR},
+ {"NAND 2GiB 3,3V 16-bit", 0xC5, 0, 2048, 0, NAND_SAMSUNG_LP_OPTIONS | NAND_BUSWIDTH_16 | NAND_NO_AUTOINCR},
+
+ /* Renesas AND 1 Gigabit. Those chips do not support extended id and have a strange page/block layout !
+ * The chosen minimum erasesize is 4 * 2 * 2048 = 16384 Byte, as those chips have an array of 4 page planes
+ * 1 block = 2 pages, but due to plane arrangement the blocks 0-3 consists of page 0 + 4,1 + 5, 2 + 6, 3 + 7
+ * Anyway JFFS2 would increase the eraseblock size so we chose a combined one which can be erased in one go
+ * There are more speed improvements for reads and writes possible, but not implemented now
+ */
+ {"AND 128MiB 3,3V 8-bit", 0x01, 2048, 128, 0x4000, NAND_IS_AND | NAND_NO_AUTOINCR | NAND_4PAGE_ARRAY},
+
+ {NULL,}
+};
+
+/*
+* Manufacturer ID list
+*/
+struct nand_manufacturers nand_manuf_ids[] = {
+ {NAND_MFR_TOSHIBA, "Toshiba"},
+ {NAND_MFR_SAMSUNG, "Samsung"},
+ {NAND_MFR_FUJITSU, "Fujitsu"},
+ {NAND_MFR_NATIONAL, "National"},
+ {NAND_MFR_RENESAS, "Renesas"},
+ {NAND_MFR_STMICRO, "ST Micro"},
+ {0x0, "Unknown"}
+};
+
+EXPORT_SYMBOL (nand_manuf_ids);
+EXPORT_SYMBOL (nand_flash_ids);
+
+MODULE_LICENSE ("GPL");
+MODULE_AUTHOR ("Thomas Gleixner <tglx@linutronix.de>");
+MODULE_DESCRIPTION ("Nand device & manufacturer ID's");
diff --git a/drivers/mtd/nand/nandsim.c b/drivers/mtd/nand/nandsim.c
new file mode 100644
index 000000000000..13feefd7d8ca
--- /dev/null
+++ b/drivers/mtd/nand/nandsim.c
@@ -0,0 +1,1613 @@
+/*
+ * NAND flash simulator.
+ *
+ * Author: Artem B. Bityuckiy <dedekind@oktetlabs.ru>, <dedekind@infradead.org>
+ *
+ * Copyright (C) 2004 Nokia Corporation
+ *
+ * Note: NS means "NAND Simulator".
+ * Note: Input means input TO flash chip, output means output FROM chip.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
+ *
+ * $Id: nandsim.c,v 1.7 2004/12/06 11:53:06 dedekind Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/delay.h>
+#ifdef CONFIG_NS_ABS_POS
+#include <asm/io.h>
+#endif
+
+
+/* Default simulator parameters values */
+#if !defined(CONFIG_NANDSIM_FIRST_ID_BYTE) || \
+ !defined(CONFIG_NANDSIM_SECOND_ID_BYTE) || \
+ !defined(CONFIG_NANDSIM_THIRD_ID_BYTE) || \
+ !defined(CONFIG_NANDSIM_FOURTH_ID_BYTE)
+#define CONFIG_NANDSIM_FIRST_ID_BYTE 0x98
+#define CONFIG_NANDSIM_SECOND_ID_BYTE 0x39
+#define CONFIG_NANDSIM_THIRD_ID_BYTE 0xFF /* No byte */
+#define CONFIG_NANDSIM_FOURTH_ID_BYTE 0xFF /* No byte */
+#endif
+
+#ifndef CONFIG_NANDSIM_ACCESS_DELAY
+#define CONFIG_NANDSIM_ACCESS_DELAY 25
+#endif
+#ifndef CONFIG_NANDSIM_PROGRAMM_DELAY
+#define CONFIG_NANDSIM_PROGRAMM_DELAY 200
+#endif
+#ifndef CONFIG_NANDSIM_ERASE_DELAY
+#define CONFIG_NANDSIM_ERASE_DELAY 2
+#endif
+#ifndef CONFIG_NANDSIM_OUTPUT_CYCLE
+#define CONFIG_NANDSIM_OUTPUT_CYCLE 40
+#endif
+#ifndef CONFIG_NANDSIM_INPUT_CYCLE
+#define CONFIG_NANDSIM_INPUT_CYCLE 50
+#endif
+#ifndef CONFIG_NANDSIM_BUS_WIDTH
+#define CONFIG_NANDSIM_BUS_WIDTH 8
+#endif
+#ifndef CONFIG_NANDSIM_DO_DELAYS
+#define CONFIG_NANDSIM_DO_DELAYS 0
+#endif
+#ifndef CONFIG_NANDSIM_LOG
+#define CONFIG_NANDSIM_LOG 0
+#endif
+#ifndef CONFIG_NANDSIM_DBG
+#define CONFIG_NANDSIM_DBG 0
+#endif
+
+static uint first_id_byte = CONFIG_NANDSIM_FIRST_ID_BYTE;
+static uint second_id_byte = CONFIG_NANDSIM_SECOND_ID_BYTE;
+static uint third_id_byte = CONFIG_NANDSIM_THIRD_ID_BYTE;
+static uint fourth_id_byte = CONFIG_NANDSIM_FOURTH_ID_BYTE;
+static uint access_delay = CONFIG_NANDSIM_ACCESS_DELAY;
+static uint programm_delay = CONFIG_NANDSIM_PROGRAMM_DELAY;
+static uint erase_delay = CONFIG_NANDSIM_ERASE_DELAY;
+static uint output_cycle = CONFIG_NANDSIM_OUTPUT_CYCLE;
+static uint input_cycle = CONFIG_NANDSIM_INPUT_CYCLE;
+static uint bus_width = CONFIG_NANDSIM_BUS_WIDTH;
+static uint do_delays = CONFIG_NANDSIM_DO_DELAYS;
+static uint log = CONFIG_NANDSIM_LOG;
+static uint dbg = CONFIG_NANDSIM_DBG;
+
+module_param(first_id_byte, uint, 0400);
+module_param(second_id_byte, uint, 0400);
+module_param(third_id_byte, uint, 0400);
+module_param(fourth_id_byte, uint, 0400);
+module_param(access_delay, uint, 0400);
+module_param(programm_delay, uint, 0400);
+module_param(erase_delay, uint, 0400);
+module_param(output_cycle, uint, 0400);
+module_param(input_cycle, uint, 0400);
+module_param(bus_width, uint, 0400);
+module_param(do_delays, uint, 0400);
+module_param(log, uint, 0400);
+module_param(dbg, uint, 0400);
+
+MODULE_PARM_DESC(first_id_byte, "The fist byte returned by NAND Flash 'read ID' command (manufaturer ID)");
+MODULE_PARM_DESC(second_id_byte, "The second byte returned by NAND Flash 'read ID' command (chip ID)");
+MODULE_PARM_DESC(third_id_byte, "The third byte returned by NAND Flash 'read ID' command");
+MODULE_PARM_DESC(fourth_id_byte, "The fourth byte returned by NAND Flash 'read ID' command");
+MODULE_PARM_DESC(access_delay, "Initial page access delay (microiseconds)");
+MODULE_PARM_DESC(programm_delay, "Page programm delay (microseconds");
+MODULE_PARM_DESC(erase_delay, "Sector erase delay (milliseconds)");
+MODULE_PARM_DESC(output_cycle, "Word output (from flash) time (nanodeconds)");
+MODULE_PARM_DESC(input_cycle, "Word input (to flash) time (nanodeconds)");
+MODULE_PARM_DESC(bus_width, "Chip's bus width (8- or 16-bit)");
+MODULE_PARM_DESC(do_delays, "Simulate NAND delays using busy-waits if not zero");
+MODULE_PARM_DESC(log, "Perform logging if not zero");
+MODULE_PARM_DESC(dbg, "Output debug information if not zero");
+
+/* The largest possible page size */
+#define NS_LARGEST_PAGE_SIZE 2048
+
+/* The prefix for simulator output */
+#define NS_OUTPUT_PREFIX "[nandsim]"
+
+/* Simulator's output macros (logging, debugging, warning, error) */
+#define NS_LOG(args...) \
+ do { if (log) printk(KERN_DEBUG NS_OUTPUT_PREFIX " log: " args); } while(0)
+#define NS_DBG(args...) \
+ do { if (dbg) printk(KERN_DEBUG NS_OUTPUT_PREFIX " debug: " args); } while(0)
+#define NS_WARN(args...) \
+ do { printk(KERN_WARNING NS_OUTPUT_PREFIX " warnig: " args); } while(0)
+#define NS_ERR(args...) \
+ do { printk(KERN_ERR NS_OUTPUT_PREFIX " errorr: " args); } while(0)
+
+/* Busy-wait delay macros (microseconds, milliseconds) */
+#define NS_UDELAY(us) \
+ do { if (do_delays) udelay(us); } while(0)
+#define NS_MDELAY(us) \
+ do { if (do_delays) mdelay(us); } while(0)
+
+/* Is the nandsim structure initialized ? */
+#define NS_IS_INITIALIZED(ns) ((ns)->geom.totsz != 0)
+
+/* Good operation completion status */
+#define NS_STATUS_OK(ns) (NAND_STATUS_READY | (NAND_STATUS_WP * ((ns)->lines.wp == 0)))
+
+/* Operation failed completion status */
+#define NS_STATUS_FAILED(ns) (NAND_STATUS_FAIL | NS_STATUS_OK(ns))
+
+/* Calculate the page offset in flash RAM image by (row, column) address */
+#define NS_RAW_OFFSET(ns) \
+ (((ns)->regs.row << (ns)->geom.pgshift) + ((ns)->regs.row * (ns)->geom.oobsz) + (ns)->regs.column)
+
+/* Calculate the OOB offset in flash RAM image by (row, column) address */
+#define NS_RAW_OFFSET_OOB(ns) (NS_RAW_OFFSET(ns) + ns->geom.pgsz)
+
+/* After a command is input, the simulator goes to one of the following states */
+#define STATE_CMD_READ0 0x00000001 /* read data from the beginning of page */
+#define STATE_CMD_READ1 0x00000002 /* read data from the second half of page */
+#define STATE_CMD_READSTART 0x00000003 /* read data second command (large page devices) */
+#define STATE_CMD_PAGEPROG 0x00000004 /* start page programm */
+#define STATE_CMD_READOOB 0x00000005 /* read OOB area */
+#define STATE_CMD_ERASE1 0x00000006 /* sector erase first command */
+#define STATE_CMD_STATUS 0x00000007 /* read status */
+#define STATE_CMD_STATUS_M 0x00000008 /* read multi-plane status (isn't implemented) */
+#define STATE_CMD_SEQIN 0x00000009 /* sequential data imput */
+#define STATE_CMD_READID 0x0000000A /* read ID */
+#define STATE_CMD_ERASE2 0x0000000B /* sector erase second command */
+#define STATE_CMD_RESET 0x0000000C /* reset */
+#define STATE_CMD_MASK 0x0000000F /* command states mask */
+
+/* After an addres is input, the simulator goes to one of these states */
+#define STATE_ADDR_PAGE 0x00000010 /* full (row, column) address is accepted */
+#define STATE_ADDR_SEC 0x00000020 /* sector address was accepted */
+#define STATE_ADDR_ZERO 0x00000030 /* one byte zero address was accepted */
+#define STATE_ADDR_MASK 0x00000030 /* address states mask */
+
+/* Durind data input/output the simulator is in these states */
+#define STATE_DATAIN 0x00000100 /* waiting for data input */
+#define STATE_DATAIN_MASK 0x00000100 /* data input states mask */
+
+#define STATE_DATAOUT 0x00001000 /* waiting for page data output */
+#define STATE_DATAOUT_ID 0x00002000 /* waiting for ID bytes output */
+#define STATE_DATAOUT_STATUS 0x00003000 /* waiting for status output */
+#define STATE_DATAOUT_STATUS_M 0x00004000 /* waiting for multi-plane status output */
+#define STATE_DATAOUT_MASK 0x00007000 /* data output states mask */
+
+/* Previous operation is done, ready to accept new requests */
+#define STATE_READY 0x00000000
+
+/* This state is used to mark that the next state isn't known yet */
+#define STATE_UNKNOWN 0x10000000
+
+/* Simulator's actions bit masks */
+#define ACTION_CPY 0x00100000 /* copy page/OOB to the internal buffer */
+#define ACTION_PRGPAGE 0x00200000 /* programm the internal buffer to flash */
+#define ACTION_SECERASE 0x00300000 /* erase sector */
+#define ACTION_ZEROOFF 0x00400000 /* don't add any offset to address */
+#define ACTION_HALFOFF 0x00500000 /* add to address half of page */
+#define ACTION_OOBOFF 0x00600000 /* add to address OOB offset */
+#define ACTION_MASK 0x00700000 /* action mask */
+
+#define NS_OPER_NUM 12 /* Number of operations supported by the simulator */
+#define NS_OPER_STATES 6 /* Maximum number of states in operation */
+
+#define OPT_ANY 0xFFFFFFFF /* any chip supports this operation */
+#define OPT_PAGE256 0x00000001 /* 256-byte page chips */
+#define OPT_PAGE512 0x00000002 /* 512-byte page chips */
+#define OPT_PAGE2048 0x00000008 /* 2048-byte page chips */
+#define OPT_SMARTMEDIA 0x00000010 /* SmartMedia technology chips */
+#define OPT_AUTOINCR 0x00000020 /* page number auto inctimentation is possible */
+#define OPT_PAGE512_8BIT 0x00000040 /* 512-byte page chips with 8-bit bus width */
+#define OPT_LARGEPAGE (OPT_PAGE2048) /* 2048-byte page chips */
+#define OPT_SMALLPAGE (OPT_PAGE256 | OPT_PAGE512) /* 256 and 512-byte page chips */
+
+/* Remove action bits ftom state */
+#define NS_STATE(x) ((x) & ~ACTION_MASK)
+
+/*
+ * Maximum previous states which need to be saved. Currently saving is
+ * only needed for page programm operation with preceeded read command
+ * (which is only valid for 512-byte pages).
+ */
+#define NS_MAX_PREVSTATES 1
+
+/*
+ * The structure which describes all the internal simulator data.
+ */
+struct nandsim {
+ struct mtd_partition part;
+
+ uint busw; /* flash chip bus width (8 or 16) */
+ u_char ids[4]; /* chip's ID bytes */
+ uint32_t options; /* chip's characteristic bits */
+ uint32_t state; /* current chip state */
+ uint32_t nxstate; /* next expected state */
+
+ uint32_t *op; /* current operation, NULL operations isn't known yet */
+ uint32_t pstates[NS_MAX_PREVSTATES]; /* previous states */
+ uint16_t npstates; /* number of previous states saved */
+ uint16_t stateidx; /* current state index */
+
+ /* The simulated NAND flash image */
+ union flash_media {
+ u_char *byte;
+ uint16_t *word;
+ } mem;
+
+ /* Internal buffer of page + OOB size bytes */
+ union internal_buffer {
+ u_char *byte; /* for byte access */
+ uint16_t *word; /* for 16-bit word access */
+ } buf;
+
+ /* NAND flash "geometry" */
+ struct nandsin_geometry {
+ uint32_t totsz; /* total flash size, bytes */
+ uint32_t secsz; /* flash sector (erase block) size, bytes */
+ uint pgsz; /* NAND flash page size, bytes */
+ uint oobsz; /* page OOB area size, bytes */
+ uint32_t totszoob; /* total flash size including OOB, bytes */
+ uint pgszoob; /* page size including OOB , bytes*/
+ uint secszoob; /* sector size including OOB, bytes */
+ uint pgnum; /* total number of pages */
+ uint pgsec; /* number of pages per sector */
+ uint secshift; /* bits number in sector size */
+ uint pgshift; /* bits number in page size */
+ uint oobshift; /* bits number in OOB size */
+ uint pgaddrbytes; /* bytes per page address */
+ uint secaddrbytes; /* bytes per sector address */
+ uint idbytes; /* the number ID bytes that this chip outputs */
+ } geom;
+
+ /* NAND flash internal registers */
+ struct nandsim_regs {
+ unsigned command; /* the command register */
+ u_char status; /* the status register */
+ uint row; /* the page number */
+ uint column; /* the offset within page */
+ uint count; /* internal counter */
+ uint num; /* number of bytes which must be processed */
+ uint off; /* fixed page offset */
+ } regs;
+
+ /* NAND flash lines state */
+ struct ns_lines_status {
+ int ce; /* chip Enable */
+ int cle; /* command Latch Enable */
+ int ale; /* address Latch Enable */
+ int wp; /* write Protect */
+ } lines;
+};
+
+/*
+ * Operations array. To perform any operation the simulator must pass
+ * through the correspondent states chain.
+ */
+static struct nandsim_operations {
+ uint32_t reqopts; /* options which are required to perform the operation */
+ uint32_t states[NS_OPER_STATES]; /* operation's states */
+} ops[NS_OPER_NUM] = {
+ /* Read page + OOB from the beginning */
+ {OPT_SMALLPAGE, {STATE_CMD_READ0 | ACTION_ZEROOFF, STATE_ADDR_PAGE | ACTION_CPY,
+ STATE_DATAOUT, STATE_READY}},
+ /* Read page + OOB from the second half */
+ {OPT_PAGE512_8BIT, {STATE_CMD_READ1 | ACTION_HALFOFF, STATE_ADDR_PAGE | ACTION_CPY,
+ STATE_DATAOUT, STATE_READY}},
+ /* Read OOB */
+ {OPT_SMALLPAGE, {STATE_CMD_READOOB | ACTION_OOBOFF, STATE_ADDR_PAGE | ACTION_CPY,
+ STATE_DATAOUT, STATE_READY}},
+ /* Programm page starting from the beginning */
+ {OPT_ANY, {STATE_CMD_SEQIN, STATE_ADDR_PAGE, STATE_DATAIN,
+ STATE_CMD_PAGEPROG | ACTION_PRGPAGE, STATE_READY}},
+ /* Programm page starting from the beginning */
+ {OPT_SMALLPAGE, {STATE_CMD_READ0, STATE_CMD_SEQIN | ACTION_ZEROOFF, STATE_ADDR_PAGE,
+ STATE_DATAIN, STATE_CMD_PAGEPROG | ACTION_PRGPAGE, STATE_READY}},
+ /* Programm page starting from the second half */
+ {OPT_PAGE512, {STATE_CMD_READ1, STATE_CMD_SEQIN | ACTION_HALFOFF, STATE_ADDR_PAGE,
+ STATE_DATAIN, STATE_CMD_PAGEPROG | ACTION_PRGPAGE, STATE_READY}},
+ /* Programm OOB */
+ {OPT_SMALLPAGE, {STATE_CMD_READOOB, STATE_CMD_SEQIN | ACTION_OOBOFF, STATE_ADDR_PAGE,
+ STATE_DATAIN, STATE_CMD_PAGEPROG | ACTION_PRGPAGE, STATE_READY}},
+ /* Erase sector */
+ {OPT_ANY, {STATE_CMD_ERASE1, STATE_ADDR_SEC, STATE_CMD_ERASE2 | ACTION_SECERASE, STATE_READY}},
+ /* Read status */
+ {OPT_ANY, {STATE_CMD_STATUS, STATE_DATAOUT_STATUS, STATE_READY}},
+ /* Read multi-plane status */
+ {OPT_SMARTMEDIA, {STATE_CMD_STATUS_M, STATE_DATAOUT_STATUS_M, STATE_READY}},
+ /* Read ID */
+ {OPT_ANY, {STATE_CMD_READID, STATE_ADDR_ZERO, STATE_DATAOUT_ID, STATE_READY}},
+ /* Large page devices read page */
+ {OPT_LARGEPAGE, {STATE_CMD_READ0, STATE_ADDR_PAGE, STATE_CMD_READSTART | ACTION_CPY,
+ STATE_DATAOUT, STATE_READY}}
+};
+
+/* MTD structure for NAND controller */
+static struct mtd_info *nsmtd;
+
+static u_char ns_verify_buf[NS_LARGEST_PAGE_SIZE];
+
+/*
+ * Initialize the nandsim structure.
+ *
+ * RETURNS: 0 if success, -ERRNO if failure.
+ */
+static int
+init_nandsim(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = (struct nand_chip *)mtd->priv;
+ struct nandsim *ns = (struct nandsim *)(chip->priv);
+ int i;
+
+ if (NS_IS_INITIALIZED(ns)) {
+ NS_ERR("init_nandsim: nandsim is already initialized\n");
+ return -EIO;
+ }
+
+ /* Force mtd to not do delays */
+ chip->chip_delay = 0;
+
+ /* Initialize the NAND flash parameters */
+ ns->busw = chip->options & NAND_BUSWIDTH_16 ? 16 : 8;
+ ns->geom.totsz = mtd->size;
+ ns->geom.pgsz = mtd->oobblock;
+ ns->geom.oobsz = mtd->oobsize;
+ ns->geom.secsz = mtd->erasesize;
+ ns->geom.pgszoob = ns->geom.pgsz + ns->geom.oobsz;
+ ns->geom.pgnum = ns->geom.totsz / ns->geom.pgsz;
+ ns->geom.totszoob = ns->geom.totsz + ns->geom.pgnum * ns->geom.oobsz;
+ ns->geom.secshift = ffs(ns->geom.secsz) - 1;
+ ns->geom.pgshift = chip->page_shift;
+ ns->geom.oobshift = ffs(ns->geom.oobsz) - 1;
+ ns->geom.pgsec = ns->geom.secsz / ns->geom.pgsz;
+ ns->geom.secszoob = ns->geom.secsz + ns->geom.oobsz * ns->geom.pgsec;
+ ns->options = 0;
+
+ if (ns->geom.pgsz == 256) {
+ ns->options |= OPT_PAGE256;
+ }
+ else if (ns->geom.pgsz == 512) {
+ ns->options |= (OPT_PAGE512 | OPT_AUTOINCR);
+ if (ns->busw == 8)
+ ns->options |= OPT_PAGE512_8BIT;
+ } else if (ns->geom.pgsz == 2048) {
+ ns->options |= OPT_PAGE2048;
+ } else {
+ NS_ERR("init_nandsim: unknown page size %u\n", ns->geom.pgsz);
+ return -EIO;
+ }
+
+ if (ns->options & OPT_SMALLPAGE) {
+ if (ns->geom.totsz < (64 << 20)) {
+ ns->geom.pgaddrbytes = 3;
+ ns->geom.secaddrbytes = 2;
+ } else {
+ ns->geom.pgaddrbytes = 4;
+ ns->geom.secaddrbytes = 3;
+ }
+ } else {
+ if (ns->geom.totsz <= (128 << 20)) {
+ ns->geom.pgaddrbytes = 5;
+ ns->geom.secaddrbytes = 2;
+ } else {
+ ns->geom.pgaddrbytes = 5;
+ ns->geom.secaddrbytes = 3;
+ }
+ }
+
+ /* Detect how many ID bytes the NAND chip outputs */
+ for (i = 0; nand_flash_ids[i].name != NULL; i++) {
+ if (second_id_byte != nand_flash_ids[i].id)
+ continue;
+ if (!(nand_flash_ids[i].options & NAND_NO_AUTOINCR))
+ ns->options |= OPT_AUTOINCR;
+ }
+
+ if (ns->busw == 16)
+ NS_WARN("16-bit flashes support wasn't tested\n");
+
+ printk("flash size: %u MiB\n", ns->geom.totsz >> 20);
+ printk("page size: %u bytes\n", ns->geom.pgsz);
+ printk("OOB area size: %u bytes\n", ns->geom.oobsz);
+ printk("sector size: %u KiB\n", ns->geom.secsz >> 10);
+ printk("pages number: %u\n", ns->geom.pgnum);
+ printk("pages per sector: %u\n", ns->geom.pgsec);
+ printk("bus width: %u\n", ns->busw);
+ printk("bits in sector size: %u\n", ns->geom.secshift);
+ printk("bits in page size: %u\n", ns->geom.pgshift);
+ printk("bits in OOB size: %u\n", ns->geom.oobshift);
+ printk("flash size with OOB: %u KiB\n", ns->geom.totszoob >> 10);
+ printk("page address bytes: %u\n", ns->geom.pgaddrbytes);
+ printk("sector address bytes: %u\n", ns->geom.secaddrbytes);
+ printk("options: %#x\n", ns->options);
+
+ /* Map / allocate and initialize the flash image */
+#ifdef CONFIG_NS_ABS_POS
+ ns->mem.byte = ioremap(CONFIG_NS_ABS_POS, ns->geom.totszoob);
+ if (!ns->mem.byte) {
+ NS_ERR("init_nandsim: failed to map the NAND flash image at address %p\n",
+ (void *)CONFIG_NS_ABS_POS);
+ return -ENOMEM;
+ }
+#else
+ ns->mem.byte = vmalloc(ns->geom.totszoob);
+ if (!ns->mem.byte) {
+ NS_ERR("init_nandsim: unable to allocate %u bytes for flash image\n",
+ ns->geom.totszoob);
+ return -ENOMEM;
+ }
+ memset(ns->mem.byte, 0xFF, ns->geom.totszoob);
+#endif
+
+ /* Allocate / initialize the internal buffer */
+ ns->buf.byte = kmalloc(ns->geom.pgszoob, GFP_KERNEL);
+ if (!ns->buf.byte) {
+ NS_ERR("init_nandsim: unable to allocate %u bytes for the internal buffer\n",
+ ns->geom.pgszoob);
+ goto error;
+ }
+ memset(ns->buf.byte, 0xFF, ns->geom.pgszoob);
+
+ /* Fill the partition_info structure */
+ ns->part.name = "NAND simulator partition";
+ ns->part.offset = 0;
+ ns->part.size = ns->geom.totsz;
+
+ return 0;
+
+error:
+#ifdef CONFIG_NS_ABS_POS
+ iounmap(ns->mem.byte);
+#else
+ vfree(ns->mem.byte);
+#endif
+
+ return -ENOMEM;
+}
+
+/*
+ * Free the nandsim structure.
+ */
+static void
+free_nandsim(struct nandsim *ns)
+{
+ kfree(ns->buf.byte);
+
+#ifdef CONFIG_NS_ABS_POS
+ iounmap(ns->mem.byte);
+#else
+ vfree(ns->mem.byte);
+#endif
+
+ return;
+}
+
+/*
+ * Returns the string representation of 'state' state.
+ */
+static char *
+get_state_name(uint32_t state)
+{
+ switch (NS_STATE(state)) {
+ case STATE_CMD_READ0:
+ return "STATE_CMD_READ0";
+ case STATE_CMD_READ1:
+ return "STATE_CMD_READ1";
+ case STATE_CMD_PAGEPROG:
+ return "STATE_CMD_PAGEPROG";
+ case STATE_CMD_READOOB:
+ return "STATE_CMD_READOOB";
+ case STATE_CMD_READSTART:
+ return "STATE_CMD_READSTART";
+ case STATE_CMD_ERASE1:
+ return "STATE_CMD_ERASE1";
+ case STATE_CMD_STATUS:
+ return "STATE_CMD_STATUS";
+ case STATE_CMD_STATUS_M:
+ return "STATE_CMD_STATUS_M";
+ case STATE_CMD_SEQIN:
+ return "STATE_CMD_SEQIN";
+ case STATE_CMD_READID:
+ return "STATE_CMD_READID";
+ case STATE_CMD_ERASE2:
+ return "STATE_CMD_ERASE2";
+ case STATE_CMD_RESET:
+ return "STATE_CMD_RESET";
+ case STATE_ADDR_PAGE:
+ return "STATE_ADDR_PAGE";
+ case STATE_ADDR_SEC:
+ return "STATE_ADDR_SEC";
+ case STATE_ADDR_ZERO:
+ return "STATE_ADDR_ZERO";
+ case STATE_DATAIN:
+ return "STATE_DATAIN";
+ case STATE_DATAOUT:
+ return "STATE_DATAOUT";
+ case STATE_DATAOUT_ID:
+ return "STATE_DATAOUT_ID";
+ case STATE_DATAOUT_STATUS:
+ return "STATE_DATAOUT_STATUS";
+ case STATE_DATAOUT_STATUS_M:
+ return "STATE_DATAOUT_STATUS_M";
+ case STATE_READY:
+ return "STATE_READY";
+ case STATE_UNKNOWN:
+ return "STATE_UNKNOWN";
+ }
+
+ NS_ERR("get_state_name: unknown state, BUG\n");
+ return NULL;
+}
+
+/*
+ * Check if command is valid.
+ *
+ * RETURNS: 1 if wrong command, 0 if right.
+ */
+static int
+check_command(int cmd)
+{
+ switch (cmd) {
+
+ case NAND_CMD_READ0:
+ case NAND_CMD_READSTART:
+ case NAND_CMD_PAGEPROG:
+ case NAND_CMD_READOOB:
+ case NAND_CMD_ERASE1:
+ case NAND_CMD_STATUS:
+ case NAND_CMD_SEQIN:
+ case NAND_CMD_READID:
+ case NAND_CMD_ERASE2:
+ case NAND_CMD_RESET:
+ case NAND_CMD_READ1:
+ return 0;
+
+ case NAND_CMD_STATUS_MULTI:
+ default:
+ return 1;
+ }
+}
+
+/*
+ * Returns state after command is accepted by command number.
+ */
+static uint32_t
+get_state_by_command(unsigned command)
+{
+ switch (command) {
+ case NAND_CMD_READ0:
+ return STATE_CMD_READ0;
+ case NAND_CMD_READ1:
+ return STATE_CMD_READ1;
+ case NAND_CMD_PAGEPROG:
+ return STATE_CMD_PAGEPROG;
+ case NAND_CMD_READSTART:
+ return STATE_CMD_READSTART;
+ case NAND_CMD_READOOB:
+ return STATE_CMD_READOOB;
+ case NAND_CMD_ERASE1:
+ return STATE_CMD_ERASE1;
+ case NAND_CMD_STATUS:
+ return STATE_CMD_STATUS;
+ case NAND_CMD_STATUS_MULTI:
+ return STATE_CMD_STATUS_M;
+ case NAND_CMD_SEQIN:
+ return STATE_CMD_SEQIN;
+ case NAND_CMD_READID:
+ return STATE_CMD_READID;
+ case NAND_CMD_ERASE2:
+ return STATE_CMD_ERASE2;
+ case NAND_CMD_RESET:
+ return STATE_CMD_RESET;
+ }
+
+ NS_ERR("get_state_by_command: unknown command, BUG\n");
+ return 0;
+}
+
+/*
+ * Move an address byte to the correspondent internal register.
+ */
+static inline void
+accept_addr_byte(struct nandsim *ns, u_char bt)
+{
+ uint byte = (uint)bt;
+
+ if (ns->regs.count < (ns->geom.pgaddrbytes - ns->geom.secaddrbytes))
+ ns->regs.column |= (byte << 8 * ns->regs.count);
+ else {
+ ns->regs.row |= (byte << 8 * (ns->regs.count -
+ ns->geom.pgaddrbytes +
+ ns->geom.secaddrbytes));
+ }
+
+ return;
+}
+
+/*
+ * Switch to STATE_READY state.
+ */
+static inline void
+switch_to_ready_state(struct nandsim *ns, u_char status)
+{
+ NS_DBG("switch_to_ready_state: switch to %s state\n", get_state_name(STATE_READY));
+
+ ns->state = STATE_READY;
+ ns->nxstate = STATE_UNKNOWN;
+ ns->op = NULL;
+ ns->npstates = 0;
+ ns->stateidx = 0;
+ ns->regs.num = 0;
+ ns->regs.count = 0;
+ ns->regs.off = 0;
+ ns->regs.row = 0;
+ ns->regs.column = 0;
+ ns->regs.status = status;
+}
+
+/*
+ * If the operation isn't known yet, try to find it in the global array
+ * of supported operations.
+ *
+ * Operation can be unknown because of the following.
+ * 1. New command was accepted and this is the firs call to find the
+ * correspondent states chain. In this case ns->npstates = 0;
+ * 2. There is several operations which begin with the same command(s)
+ * (for example program from the second half and read from the
+ * second half operations both begin with the READ1 command). In this
+ * case the ns->pstates[] array contains previous states.
+ *
+ * Thus, the function tries to find operation containing the following
+ * states (if the 'flag' parameter is 0):
+ * ns->pstates[0], ... ns->pstates[ns->npstates], ns->state
+ *
+ * If (one and only one) matching operation is found, it is accepted (
+ * ns->ops, ns->state, ns->nxstate are initialized, ns->npstate is
+ * zeroed).
+ *
+ * If there are several maches, the current state is pushed to the
+ * ns->pstates.
+ *
+ * The operation can be unknown only while commands are input to the chip.
+ * As soon as address command is accepted, the operation must be known.
+ * In such situation the function is called with 'flag' != 0, and the
+ * operation is searched using the following pattern:
+ * ns->pstates[0], ... ns->pstates[ns->npstates], <address input>
+ *
+ * It is supposed that this pattern must either match one operation on
+ * none. There can't be ambiguity in that case.
+ *
+ * If no matches found, the functions does the following:
+ * 1. if there are saved states present, try to ignore them and search
+ * again only using the last command. If nothing was found, switch
+ * to the STATE_READY state.
+ * 2. if there are no saved states, switch to the STATE_READY state.
+ *
+ * RETURNS: -2 - no matched operations found.
+ * -1 - several matches.
+ * 0 - operation is found.
+ */
+static int
+find_operation(struct nandsim *ns, uint32_t flag)
+{
+ int opsfound = 0;
+ int i, j, idx = 0;
+
+ for (i = 0; i < NS_OPER_NUM; i++) {
+
+ int found = 1;
+
+ if (!(ns->options & ops[i].reqopts))
+ /* Ignore operations we can't perform */
+ continue;
+
+ if (flag) {
+ if (!(ops[i].states[ns->npstates] & STATE_ADDR_MASK))
+ continue;
+ } else {
+ if (NS_STATE(ns->state) != NS_STATE(ops[i].states[ns->npstates]))
+ continue;
+ }
+
+ for (j = 0; j < ns->npstates; j++)
+ if (NS_STATE(ops[i].states[j]) != NS_STATE(ns->pstates[j])
+ && (ns->options & ops[idx].reqopts)) {
+ found = 0;
+ break;
+ }
+
+ if (found) {
+ idx = i;
+ opsfound += 1;
+ }
+ }
+
+ if (opsfound == 1) {
+ /* Exact match */
+ ns->op = &ops[idx].states[0];
+ if (flag) {
+ /*
+ * In this case the find_operation function was
+ * called when address has just began input. But it isn't
+ * yet fully input and the current state must
+ * not be one of STATE_ADDR_*, but the STATE_ADDR_*
+ * state must be the next state (ns->nxstate).
+ */
+ ns->stateidx = ns->npstates - 1;
+ } else {
+ ns->stateidx = ns->npstates;
+ }
+ ns->npstates = 0;
+ ns->state = ns->op[ns->stateidx];
+ ns->nxstate = ns->op[ns->stateidx + 1];
+ NS_DBG("find_operation: operation found, index: %d, state: %s, nxstate %s\n",
+ idx, get_state_name(ns->state), get_state_name(ns->nxstate));
+ return 0;
+ }
+
+ if (opsfound == 0) {
+ /* Nothing was found. Try to ignore previous commands (if any) and search again */
+ if (ns->npstates != 0) {
+ NS_DBG("find_operation: no operation found, try again with state %s\n",
+ get_state_name(ns->state));
+ ns->npstates = 0;
+ return find_operation(ns, 0);
+
+ }
+ NS_DBG("find_operation: no operations found\n");
+ switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
+ return -2;
+ }
+
+ if (flag) {
+ /* This shouldn't happen */
+ NS_DBG("find_operation: BUG, operation must be known if address is input\n");
+ return -2;
+ }
+
+ NS_DBG("find_operation: there is still ambiguity\n");
+
+ ns->pstates[ns->npstates++] = ns->state;
+
+ return -1;
+}
+
+/*
+ * If state has any action bit, perform this action.
+ *
+ * RETURNS: 0 if success, -1 if error.
+ */
+static int
+do_state_action(struct nandsim *ns, uint32_t action)
+{
+ int i, num;
+ int busdiv = ns->busw == 8 ? 1 : 2;
+
+ action &= ACTION_MASK;
+
+ /* Check that page address input is correct */
+ if (action != ACTION_SECERASE && ns->regs.row >= ns->geom.pgnum) {
+ NS_WARN("do_state_action: wrong page number (%#x)\n", ns->regs.row);
+ return -1;
+ }
+
+ switch (action) {
+
+ case ACTION_CPY:
+ /*
+ * Copy page data to the internal buffer.
+ */
+
+ /* Column shouldn't be very large */
+ if (ns->regs.column >= (ns->geom.pgszoob - ns->regs.off)) {
+ NS_ERR("do_state_action: column number is too large\n");
+ break;
+ }
+ num = ns->geom.pgszoob - ns->regs.off - ns->regs.column;
+ memcpy(ns->buf.byte, ns->mem.byte + NS_RAW_OFFSET(ns) + ns->regs.off, num);
+
+ NS_DBG("do_state_action: (ACTION_CPY:) copy %d bytes to int buf, raw offset %d\n",
+ num, NS_RAW_OFFSET(ns) + ns->regs.off);
+
+ if (ns->regs.off == 0)
+ NS_LOG("read page %d\n", ns->regs.row);
+ else if (ns->regs.off < ns->geom.pgsz)
+ NS_LOG("read page %d (second half)\n", ns->regs.row);
+ else
+ NS_LOG("read OOB of page %d\n", ns->regs.row);
+
+ NS_UDELAY(access_delay);
+ NS_UDELAY(input_cycle * ns->geom.pgsz / 1000 / busdiv);
+
+ break;
+
+ case ACTION_SECERASE:
+ /*
+ * Erase sector.
+ */
+
+ if (ns->lines.wp) {
+ NS_ERR("do_state_action: device is write-protected, ignore sector erase\n");
+ return -1;
+ }
+
+ if (ns->regs.row >= ns->geom.pgnum - ns->geom.pgsec
+ || (ns->regs.row & ~(ns->geom.secsz - 1))) {
+ NS_ERR("do_state_action: wrong sector address (%#x)\n", ns->regs.row);
+ return -1;
+ }
+
+ ns->regs.row = (ns->regs.row <<
+ 8 * (ns->geom.pgaddrbytes - ns->geom.secaddrbytes)) | ns->regs.column;
+ ns->regs.column = 0;
+
+ NS_DBG("do_state_action: erase sector at address %#x, off = %d\n",
+ ns->regs.row, NS_RAW_OFFSET(ns));
+ NS_LOG("erase sector %d\n", ns->regs.row >> (ns->geom.secshift - ns->geom.pgshift));
+
+ memset(ns->mem.byte + NS_RAW_OFFSET(ns), 0xFF, ns->geom.secszoob);
+
+ NS_MDELAY(erase_delay);
+
+ break;
+
+ case ACTION_PRGPAGE:
+ /*
+ * Programm page - move internal buffer data to the page.
+ */
+
+ if (ns->lines.wp) {
+ NS_WARN("do_state_action: device is write-protected, programm\n");
+ return -1;
+ }
+
+ num = ns->geom.pgszoob - ns->regs.off - ns->regs.column;
+ if (num != ns->regs.count) {
+ NS_ERR("do_state_action: too few bytes were input (%d instead of %d)\n",
+ ns->regs.count, num);
+ return -1;
+ }
+
+ for (i = 0; i < num; i++)
+ ns->mem.byte[NS_RAW_OFFSET(ns) + ns->regs.off + i] &= ns->buf.byte[i];
+
+ NS_DBG("do_state_action: copy %d bytes from int buf to (%#x, %#x), raw off = %d\n",
+ num, ns->regs.row, ns->regs.column, NS_RAW_OFFSET(ns) + ns->regs.off);
+ NS_LOG("programm page %d\n", ns->regs.row);
+
+ NS_UDELAY(programm_delay);
+ NS_UDELAY(output_cycle * ns->geom.pgsz / 1000 / busdiv);
+
+ break;
+
+ case ACTION_ZEROOFF:
+ NS_DBG("do_state_action: set internal offset to 0\n");
+ ns->regs.off = 0;
+ break;
+
+ case ACTION_HALFOFF:
+ if (!(ns->options & OPT_PAGE512_8BIT)) {
+ NS_ERR("do_state_action: BUG! can't skip half of page for non-512"
+ "byte page size 8x chips\n");
+ return -1;
+ }
+ NS_DBG("do_state_action: set internal offset to %d\n", ns->geom.pgsz/2);
+ ns->regs.off = ns->geom.pgsz/2;
+ break;
+
+ case ACTION_OOBOFF:
+ NS_DBG("do_state_action: set internal offset to %d\n", ns->geom.pgsz);
+ ns->regs.off = ns->geom.pgsz;
+ break;
+
+ default:
+ NS_DBG("do_state_action: BUG! unknown action\n");
+ }
+
+ return 0;
+}
+
+/*
+ * Switch simulator's state.
+ */
+static void
+switch_state(struct nandsim *ns)
+{
+ if (ns->op) {
+ /*
+ * The current operation have already been identified.
+ * Just follow the states chain.
+ */
+
+ ns->stateidx += 1;
+ ns->state = ns->nxstate;
+ ns->nxstate = ns->op[ns->stateidx + 1];
+
+ NS_DBG("switch_state: operation is known, switch to the next state, "
+ "state: %s, nxstate: %s\n",
+ get_state_name(ns->state), get_state_name(ns->nxstate));
+
+ /* See, whether we need to do some action */
+ if ((ns->state & ACTION_MASK) && do_state_action(ns, ns->state) < 0) {
+ switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
+ return;
+ }
+
+ } else {
+ /*
+ * We don't yet know which operation we perform.
+ * Try to identify it.
+ */
+
+ /*
+ * The only event causing the switch_state function to
+ * be called with yet unknown operation is new command.
+ */
+ ns->state = get_state_by_command(ns->regs.command);
+
+ NS_DBG("switch_state: operation is unknown, try to find it\n");
+
+ if (find_operation(ns, 0) != 0)
+ return;
+
+ if ((ns->state & ACTION_MASK) && do_state_action(ns, ns->state) < 0) {
+ switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
+ return;
+ }
+ }
+
+ /* For 16x devices column means the page offset in words */
+ if ((ns->nxstate & STATE_ADDR_MASK) && ns->busw == 16) {
+ NS_DBG("switch_state: double the column number for 16x device\n");
+ ns->regs.column <<= 1;
+ }
+
+ if (NS_STATE(ns->nxstate) == STATE_READY) {
+ /*
+ * The current state is the last. Return to STATE_READY
+ */
+
+ u_char status = NS_STATUS_OK(ns);
+
+ /* In case of data states, see if all bytes were input/output */
+ if ((ns->state & (STATE_DATAIN_MASK | STATE_DATAOUT_MASK))
+ && ns->regs.count != ns->regs.num) {
+ NS_WARN("switch_state: not all bytes were processed, %d left\n",
+ ns->regs.num - ns->regs.count);
+ status = NS_STATUS_FAILED(ns);
+ }
+
+ NS_DBG("switch_state: operation complete, switch to STATE_READY state\n");
+
+ switch_to_ready_state(ns, status);
+
+ return;
+ } else if (ns->nxstate & (STATE_DATAIN_MASK | STATE_DATAOUT_MASK)) {
+ /*
+ * If the next state is data input/output, switch to it now
+ */
+
+ ns->state = ns->nxstate;
+ ns->nxstate = ns->op[++ns->stateidx + 1];
+ ns->regs.num = ns->regs.count = 0;
+
+ NS_DBG("switch_state: the next state is data I/O, switch, "
+ "state: %s, nxstate: %s\n",
+ get_state_name(ns->state), get_state_name(ns->nxstate));
+
+ /*
+ * Set the internal register to the count of bytes which
+ * are expected to be input or output
+ */
+ switch (NS_STATE(ns->state)) {
+ case STATE_DATAIN:
+ case STATE_DATAOUT:
+ ns->regs.num = ns->geom.pgszoob - ns->regs.off - ns->regs.column;
+ break;
+
+ case STATE_DATAOUT_ID:
+ ns->regs.num = ns->geom.idbytes;
+ break;
+
+ case STATE_DATAOUT_STATUS:
+ case STATE_DATAOUT_STATUS_M:
+ ns->regs.count = ns->regs.num = 0;
+ break;
+
+ default:
+ NS_ERR("switch_state: BUG! unknown data state\n");
+ }
+
+ } else if (ns->nxstate & STATE_ADDR_MASK) {
+ /*
+ * If the next state is address input, set the internal
+ * register to the number of expected address bytes
+ */
+
+ ns->regs.count = 0;
+
+ switch (NS_STATE(ns->nxstate)) {
+ case STATE_ADDR_PAGE:
+ ns->regs.num = ns->geom.pgaddrbytes;
+
+ break;
+ case STATE_ADDR_SEC:
+ ns->regs.num = ns->geom.secaddrbytes;
+ break;
+
+ case STATE_ADDR_ZERO:
+ ns->regs.num = 1;
+ break;
+
+ default:
+ NS_ERR("switch_state: BUG! unknown address state\n");
+ }
+ } else {
+ /*
+ * Just reset internal counters.
+ */
+
+ ns->regs.num = 0;
+ ns->regs.count = 0;
+ }
+}
+
+static void
+ns_hwcontrol(struct mtd_info *mtd, int cmd)
+{
+ struct nandsim *ns = (struct nandsim *)((struct nand_chip *)mtd->priv)->priv;
+
+ switch (cmd) {
+
+ /* set CLE line high */
+ case NAND_CTL_SETCLE:
+ NS_DBG("ns_hwcontrol: start command latch cycles\n");
+ ns->lines.cle = 1;
+ break;
+
+ /* set CLE line low */
+ case NAND_CTL_CLRCLE:
+ NS_DBG("ns_hwcontrol: stop command latch cycles\n");
+ ns->lines.cle = 0;
+ break;
+
+ /* set ALE line high */
+ case NAND_CTL_SETALE:
+ NS_DBG("ns_hwcontrol: start address latch cycles\n");
+ ns->lines.ale = 1;
+ break;
+
+ /* set ALE line low */
+ case NAND_CTL_CLRALE:
+ NS_DBG("ns_hwcontrol: stop address latch cycles\n");
+ ns->lines.ale = 0;
+ break;
+
+ /* set WP line high */
+ case NAND_CTL_SETWP:
+ NS_DBG("ns_hwcontrol: enable write protection\n");
+ ns->lines.wp = 1;
+ break;
+
+ /* set WP line low */
+ case NAND_CTL_CLRWP:
+ NS_DBG("ns_hwcontrol: disable write protection\n");
+ ns->lines.wp = 0;
+ break;
+
+ /* set CE line low */
+ case NAND_CTL_SETNCE:
+ NS_DBG("ns_hwcontrol: enable chip\n");
+ ns->lines.ce = 1;
+ break;
+
+ /* set CE line high */
+ case NAND_CTL_CLRNCE:
+ NS_DBG("ns_hwcontrol: disable chip\n");
+ ns->lines.ce = 0;
+ break;
+
+ default:
+ NS_ERR("hwcontrol: unknown command\n");
+ }
+
+ return;
+}
+
+static u_char
+ns_nand_read_byte(struct mtd_info *mtd)
+{
+ struct nandsim *ns = (struct nandsim *)((struct nand_chip *)mtd->priv)->priv;
+ u_char outb = 0x00;
+
+ /* Sanity and correctness checks */
+ if (!ns->lines.ce) {
+ NS_ERR("read_byte: chip is disabled, return %#x\n", (uint)outb);
+ return outb;
+ }
+ if (ns->lines.ale || ns->lines.cle) {
+ NS_ERR("read_byte: ALE or CLE pin is high, return %#x\n", (uint)outb);
+ return outb;
+ }
+ if (!(ns->state & STATE_DATAOUT_MASK)) {
+ NS_WARN("read_byte: unexpected data output cycle, state is %s "
+ "return %#x\n", get_state_name(ns->state), (uint)outb);
+ return outb;
+ }
+
+ /* Status register may be read as many times as it is wanted */
+ if (NS_STATE(ns->state) == STATE_DATAOUT_STATUS) {
+ NS_DBG("read_byte: return %#x status\n", ns->regs.status);
+ return ns->regs.status;
+ }
+
+ /* Check if there is any data in the internal buffer which may be read */
+ if (ns->regs.count == ns->regs.num) {
+ NS_WARN("read_byte: no more data to output, return %#x\n", (uint)outb);
+ return outb;
+ }
+
+ switch (NS_STATE(ns->state)) {
+ case STATE_DATAOUT:
+ if (ns->busw == 8) {
+ outb = ns->buf.byte[ns->regs.count];
+ ns->regs.count += 1;
+ } else {
+ outb = (u_char)cpu_to_le16(ns->buf.word[ns->regs.count >> 1]);
+ ns->regs.count += 2;
+ }
+ break;
+ case STATE_DATAOUT_ID:
+ NS_DBG("read_byte: read ID byte %d, total = %d\n", ns->regs.count, ns->regs.num);
+ outb = ns->ids[ns->regs.count];
+ ns->regs.count += 1;
+ break;
+ default:
+ BUG();
+ }
+
+ if (ns->regs.count == ns->regs.num) {
+ NS_DBG("read_byte: all bytes were read\n");
+
+ /*
+ * The OPT_AUTOINCR allows to read next conseqitive pages without
+ * new read operation cycle.
+ */
+ if ((ns->options & OPT_AUTOINCR) && NS_STATE(ns->state) == STATE_DATAOUT) {
+ ns->regs.count = 0;
+ if (ns->regs.row + 1 < ns->geom.pgnum)
+ ns->regs.row += 1;
+ NS_DBG("read_byte: switch to the next page (%#x)\n", ns->regs.row);
+ do_state_action(ns, ACTION_CPY);
+ }
+ else if (NS_STATE(ns->nxstate) == STATE_READY)
+ switch_state(ns);
+
+ }
+
+ return outb;
+}
+
+static void
+ns_nand_write_byte(struct mtd_info *mtd, u_char byte)
+{
+ struct nandsim *ns = (struct nandsim *)((struct nand_chip *)mtd->priv)->priv;
+
+ /* Sanity and correctness checks */
+ if (!ns->lines.ce) {
+ NS_ERR("write_byte: chip is disabled, ignore write\n");
+ return;
+ }
+ if (ns->lines.ale && ns->lines.cle) {
+ NS_ERR("write_byte: ALE and CLE pins are high simultaneously, ignore write\n");
+ return;
+ }
+
+ if (ns->lines.cle == 1) {
+ /*
+ * The byte written is a command.
+ */
+
+ if (byte == NAND_CMD_RESET) {
+ NS_LOG("reset chip\n");
+ switch_to_ready_state(ns, NS_STATUS_OK(ns));
+ return;
+ }
+
+ /*
+ * Chip might still be in STATE_DATAOUT
+ * (if OPT_AUTOINCR feature is supported), STATE_DATAOUT_STATUS or
+ * STATE_DATAOUT_STATUS_M state. If so, switch state.
+ */
+ if (NS_STATE(ns->state) == STATE_DATAOUT_STATUS
+ || NS_STATE(ns->state) == STATE_DATAOUT_STATUS_M
+ || ((ns->options & OPT_AUTOINCR) && NS_STATE(ns->state) == STATE_DATAOUT))
+ switch_state(ns);
+
+ /* Check if chip is expecting command */
+ if (NS_STATE(ns->nxstate) != STATE_UNKNOWN && !(ns->nxstate & STATE_CMD_MASK)) {
+ /*
+ * We are in situation when something else (not command)
+ * was expected but command was input. In this case ignore
+ * previous command(s)/state(s) and accept the last one.
+ */
+ NS_WARN("write_byte: command (%#x) wasn't expected, expected state is %s, "
+ "ignore previous states\n", (uint)byte, get_state_name(ns->nxstate));
+ switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
+ }
+
+ /* Check that the command byte is correct */
+ if (check_command(byte)) {
+ NS_ERR("write_byte: unknown command %#x\n", (uint)byte);
+ return;
+ }
+
+ NS_DBG("command byte corresponding to %s state accepted\n",
+ get_state_name(get_state_by_command(byte)));
+ ns->regs.command = byte;
+ switch_state(ns);
+
+ } else if (ns->lines.ale == 1) {
+ /*
+ * The byte written is an address.
+ */
+
+ if (NS_STATE(ns->nxstate) == STATE_UNKNOWN) {
+
+ NS_DBG("write_byte: operation isn't known yet, identify it\n");
+
+ if (find_operation(ns, 1) < 0)
+ return;
+
+ if ((ns->state & ACTION_MASK) && do_state_action(ns, ns->state) < 0) {
+ switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
+ return;
+ }
+
+ ns->regs.count = 0;
+ switch (NS_STATE(ns->nxstate)) {
+ case STATE_ADDR_PAGE:
+ ns->regs.num = ns->geom.pgaddrbytes;
+ break;
+ case STATE_ADDR_SEC:
+ ns->regs.num = ns->geom.secaddrbytes;
+ break;
+ case STATE_ADDR_ZERO:
+ ns->regs.num = 1;
+ break;
+ default:
+ BUG();
+ }
+ }
+
+ /* Check that chip is expecting address */
+ if (!(ns->nxstate & STATE_ADDR_MASK)) {
+ NS_ERR("write_byte: address (%#x) isn't expected, expected state is %s, "
+ "switch to STATE_READY\n", (uint)byte, get_state_name(ns->nxstate));
+ switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
+ return;
+ }
+
+ /* Check if this is expected byte */
+ if (ns->regs.count == ns->regs.num) {
+ NS_ERR("write_byte: no more address bytes expected\n");
+ switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
+ return;
+ }
+
+ accept_addr_byte(ns, byte);
+
+ ns->regs.count += 1;
+
+ NS_DBG("write_byte: address byte %#x was accepted (%d bytes input, %d expected)\n",
+ (uint)byte, ns->regs.count, ns->regs.num);
+
+ if (ns->regs.count == ns->regs.num) {
+ NS_DBG("address (%#x, %#x) is accepted\n", ns->regs.row, ns->regs.column);
+ switch_state(ns);
+ }
+
+ } else {
+ /*
+ * The byte written is an input data.
+ */
+
+ /* Check that chip is expecting data input */
+ if (!(ns->state & STATE_DATAIN_MASK)) {
+ NS_ERR("write_byte: data input (%#x) isn't expected, state is %s, "
+ "switch to %s\n", (uint)byte,
+ get_state_name(ns->state), get_state_name(STATE_READY));
+ switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
+ return;
+ }
+
+ /* Check if this is expected byte */
+ if (ns->regs.count == ns->regs.num) {
+ NS_WARN("write_byte: %u input bytes has already been accepted, ignore write\n",
+ ns->regs.num);
+ return;
+ }
+
+ if (ns->busw == 8) {
+ ns->buf.byte[ns->regs.count] = byte;
+ ns->regs.count += 1;
+ } else {
+ ns->buf.word[ns->regs.count >> 1] = cpu_to_le16((uint16_t)byte);
+ ns->regs.count += 2;
+ }
+ }
+
+ return;
+}
+
+static int
+ns_device_ready(struct mtd_info *mtd)
+{
+ NS_DBG("device_ready\n");
+ return 1;
+}
+
+static uint16_t
+ns_nand_read_word(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = (struct nand_chip *)mtd->priv;
+
+ NS_DBG("read_word\n");
+
+ return chip->read_byte(mtd) | (chip->read_byte(mtd) << 8);
+}
+
+static void
+ns_nand_write_word(struct mtd_info *mtd, uint16_t word)
+{
+ struct nand_chip *chip = (struct nand_chip *)mtd->priv;
+
+ NS_DBG("write_word\n");
+
+ chip->write_byte(mtd, word & 0xFF);
+ chip->write_byte(mtd, word >> 8);
+}
+
+static void
+ns_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ struct nandsim *ns = (struct nandsim *)((struct nand_chip *)mtd->priv)->priv;
+
+ /* Check that chip is expecting data input */
+ if (!(ns->state & STATE_DATAIN_MASK)) {
+ NS_ERR("write_buf: data input isn't expected, state is %s, "
+ "switch to STATE_READY\n", get_state_name(ns->state));
+ switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
+ return;
+ }
+
+ /* Check if these are expected bytes */
+ if (ns->regs.count + len > ns->regs.num) {
+ NS_ERR("write_buf: too many input bytes\n");
+ switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
+ return;
+ }
+
+ memcpy(ns->buf.byte + ns->regs.count, buf, len);
+ ns->regs.count += len;
+
+ if (ns->regs.count == ns->regs.num) {
+ NS_DBG("write_buf: %d bytes were written\n", ns->regs.count);
+ }
+}
+
+static void
+ns_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
+{
+ struct nandsim *ns = (struct nandsim *)((struct nand_chip *)mtd->priv)->priv;
+
+ /* Sanity and correctness checks */
+ if (!ns->lines.ce) {
+ NS_ERR("read_buf: chip is disabled\n");
+ return;
+ }
+ if (ns->lines.ale || ns->lines.cle) {
+ NS_ERR("read_buf: ALE or CLE pin is high\n");
+ return;
+ }
+ if (!(ns->state & STATE_DATAOUT_MASK)) {
+ NS_WARN("read_buf: unexpected data output cycle, current state is %s\n",
+ get_state_name(ns->state));
+ return;
+ }
+
+ if (NS_STATE(ns->state) != STATE_DATAOUT) {
+ int i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = ((struct nand_chip *)mtd->priv)->read_byte(mtd);
+
+ return;
+ }
+
+ /* Check if these are expected bytes */
+ if (ns->regs.count + len > ns->regs.num) {
+ NS_ERR("read_buf: too many bytes to read\n");
+ switch_to_ready_state(ns, NS_STATUS_FAILED(ns));
+ return;
+ }
+
+ memcpy(buf, ns->buf.byte + ns->regs.count, len);
+ ns->regs.count += len;
+
+ if (ns->regs.count == ns->regs.num) {
+ if ((ns->options & OPT_AUTOINCR) && NS_STATE(ns->state) == STATE_DATAOUT) {
+ ns->regs.count = 0;
+ if (ns->regs.row + 1 < ns->geom.pgnum)
+ ns->regs.row += 1;
+ NS_DBG("read_buf: switch to the next page (%#x)\n", ns->regs.row);
+ do_state_action(ns, ACTION_CPY);
+ }
+ else if (NS_STATE(ns->nxstate) == STATE_READY)
+ switch_state(ns);
+ }
+
+ return;
+}
+
+static int
+ns_nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ ns_nand_read_buf(mtd, (u_char *)&ns_verify_buf[0], len);
+
+ if (!memcmp(buf, &ns_verify_buf[0], len)) {
+ NS_DBG("verify_buf: the buffer is OK\n");
+ return 0;
+ } else {
+ NS_DBG("verify_buf: the buffer is wrong\n");
+ return -EFAULT;
+ }
+}
+
+/*
+ * Having only NAND chip IDs we call nand_scan which detects NAND flash
+ * parameters and then calls scan_bbt in order to scan/find/build the
+ * NAND flash bad block table. But since at that moment the NAND flash
+ * image isn't allocated in the simulator, errors arise. To avoid this
+ * we redefine the scan_bbt callback and initialize the nandsim structure
+ * before the flash media scanning.
+ */
+int ns_scan_bbt(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = (struct nand_chip *)mtd->priv;
+ struct nandsim *ns = (struct nandsim *)(chip->priv);
+ int retval;
+
+ if (!NS_IS_INITIALIZED(ns))
+ if ((retval = init_nandsim(mtd)) != 0) {
+ NS_ERR("scan_bbt: can't initialize the nandsim structure\n");
+ return retval;
+ }
+ if ((retval = nand_default_bbt(mtd)) != 0) {
+ free_nandsim(ns);
+ return retval;
+ }
+
+ return 0;
+}
+
+/*
+ * Module initialization function
+ */
+int __init ns_init_module(void)
+{
+ struct nand_chip *chip;
+ struct nandsim *nand;
+ int retval = -ENOMEM;
+
+ if (bus_width != 8 && bus_width != 16) {
+ NS_ERR("wrong bus width (%d), use only 8 or 16\n", bus_width);
+ return -EINVAL;
+ }
+
+ /* Allocate and initialize mtd_info, nand_chip and nandsim structures */
+ nsmtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip)
+ + sizeof(struct nandsim), GFP_KERNEL);
+ if (!nsmtd) {
+ NS_ERR("unable to allocate core structures.\n");
+ return -ENOMEM;
+ }
+ memset(nsmtd, 0, sizeof(struct mtd_info) + sizeof(struct nand_chip) +
+ sizeof(struct nandsim));
+ chip = (struct nand_chip *)(nsmtd + 1);
+ nsmtd->priv = (void *)chip;
+ nand = (struct nandsim *)(chip + 1);
+ chip->priv = (void *)nand;
+
+ /*
+ * Register simulator's callbacks.
+ */
+ chip->hwcontrol = ns_hwcontrol;
+ chip->read_byte = ns_nand_read_byte;
+ chip->dev_ready = ns_device_ready;
+ chip->scan_bbt = ns_scan_bbt;
+ chip->write_byte = ns_nand_write_byte;
+ chip->write_buf = ns_nand_write_buf;
+ chip->read_buf = ns_nand_read_buf;
+ chip->verify_buf = ns_nand_verify_buf;
+ chip->write_word = ns_nand_write_word;
+ chip->read_word = ns_nand_read_word;
+ chip->eccmode = NAND_ECC_SOFT;
+
+ /*
+ * Perform minimum nandsim structure initialization to handle
+ * the initial ID read command correctly
+ */
+ if (third_id_byte != 0xFF || fourth_id_byte != 0xFF)
+ nand->geom.idbytes = 4;
+ else
+ nand->geom.idbytes = 2;
+ nand->regs.status = NS_STATUS_OK(nand);
+ nand->nxstate = STATE_UNKNOWN;
+ nand->options |= OPT_PAGE256; /* temporary value */
+ nand->ids[0] = first_id_byte;
+ nand->ids[1] = second_id_byte;
+ nand->ids[2] = third_id_byte;
+ nand->ids[3] = fourth_id_byte;
+ if (bus_width == 16) {
+ nand->busw = 16;
+ chip->options |= NAND_BUSWIDTH_16;
+ }
+
+ if ((retval = nand_scan(nsmtd, 1)) != 0) {
+ NS_ERR("can't register NAND Simulator\n");
+ if (retval > 0)
+ retval = -ENXIO;
+ goto error;
+ }
+
+ /* Register NAND as one big partition */
+ add_mtd_partitions(nsmtd, &nand->part, 1);
+
+ return 0;
+
+error:
+ kfree(nsmtd);
+
+ return retval;
+}
+
+module_init(ns_init_module);
+
+/*
+ * Module clean-up function
+ */
+static void __exit ns_cleanup_module(void)
+{
+ struct nandsim *ns = (struct nandsim *)(((struct nand_chip *)nsmtd->priv)->priv);
+
+ free_nandsim(ns); /* Free nandsim private resources */
+ nand_release(nsmtd); /* Unregisterd drived */
+ kfree(nsmtd); /* Free other structures */
+}
+
+module_exit(ns_cleanup_module);
+
+MODULE_LICENSE ("GPL");
+MODULE_AUTHOR ("Artem B. Bityuckiy");
+MODULE_DESCRIPTION ("The NAND flash simulator");
+
diff --git a/drivers/mtd/nand/ppchameleonevb.c b/drivers/mtd/nand/ppchameleonevb.c
new file mode 100644
index 000000000000..e510a83d7bdb
--- /dev/null
+++ b/drivers/mtd/nand/ppchameleonevb.c
@@ -0,0 +1,420 @@
+/*
+ * drivers/mtd/nand/ppchameleonevb.c
+ *
+ * Copyright (C) 2003 DAVE Srl (info@wawnet.biz)
+ *
+ * Derived from drivers/mtd/nand/edb7312.c
+ *
+ *
+ * $Id: ppchameleonevb.c,v 1.6 2004/11/05 16:07:16 kalev Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Overview:
+ * This is a device driver for the NAND flash devices found on the
+ * PPChameleon/PPChameleonEVB system.
+ * PPChameleon options (autodetected):
+ * - BA model: no NAND
+ * - ME model: 32MB (Samsung K9F5608U0B)
+ * - HI model: 128MB (Samsung K9F1G08UOM)
+ * PPChameleonEVB options:
+ * - 32MB (Samsung K9F5608U0B)
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <asm/io.h>
+#include <platforms/PPChameleonEVB.h>
+
+#undef USE_READY_BUSY_PIN
+#define USE_READY_BUSY_PIN
+/* see datasheets (tR) */
+#define NAND_BIG_DELAY_US 25
+#define NAND_SMALL_DELAY_US 10
+
+/* handy sizes */
+#define SZ_4M 0x00400000
+#define NAND_SMALL_SIZE 0x02000000
+#define NAND_MTD_NAME "ppchameleon-nand"
+#define NAND_EVB_MTD_NAME "ppchameleonevb-nand"
+
+/* GPIO pins used to drive NAND chip mounted on processor module */
+#define NAND_nCE_GPIO_PIN (0x80000000 >> 1)
+#define NAND_CLE_GPIO_PIN (0x80000000 >> 2)
+#define NAND_ALE_GPIO_PIN (0x80000000 >> 3)
+#define NAND_RB_GPIO_PIN (0x80000000 >> 4)
+/* GPIO pins used to drive NAND chip mounted on EVB */
+#define NAND_EVB_nCE_GPIO_PIN (0x80000000 >> 14)
+#define NAND_EVB_CLE_GPIO_PIN (0x80000000 >> 15)
+#define NAND_EVB_ALE_GPIO_PIN (0x80000000 >> 16)
+#define NAND_EVB_RB_GPIO_PIN (0x80000000 >> 31)
+
+/*
+ * MTD structure for PPChameleonEVB board
+ */
+static struct mtd_info *ppchameleon_mtd = NULL;
+static struct mtd_info *ppchameleonevb_mtd = NULL;
+
+/*
+ * Module stuff
+ */
+static unsigned long ppchameleon_fio_pbase = CFG_NAND0_PADDR;
+static unsigned long ppchameleonevb_fio_pbase = CFG_NAND1_PADDR;
+
+#ifdef MODULE
+module_param(ppchameleon_fio_pbase, ulong, 0);
+module_param(ppchameleonevb_fio_pbase, ulong, 0);
+#else
+__setup("ppchameleon_fio_pbase=",ppchameleon_fio_pbase);
+__setup("ppchameleonevb_fio_pbase=",ppchameleonevb_fio_pbase);
+#endif
+
+#ifdef CONFIG_MTD_PARTITIONS
+/*
+ * Define static partitions for flash devices
+ */
+static struct mtd_partition partition_info_hi[] = {
+ { name: "PPChameleon HI Nand Flash",
+ offset: 0,
+ size: 128*1024*1024 }
+};
+
+static struct mtd_partition partition_info_me[] = {
+ { name: "PPChameleon ME Nand Flash",
+ offset: 0,
+ size: 32*1024*1024 }
+};
+
+static struct mtd_partition partition_info_evb[] = {
+ { name: "PPChameleonEVB Nand Flash",
+ offset: 0,
+ size: 32*1024*1024 }
+};
+
+#define NUM_PARTITIONS 1
+
+extern int parse_cmdline_partitions(struct mtd_info *master,
+ struct mtd_partition **pparts,
+ const char *mtd_id);
+#endif
+
+
+/*
+ * hardware specific access to control-lines
+ */
+static void ppchameleon_hwcontrol(struct mtd_info *mtdinfo, int cmd)
+{
+ switch(cmd) {
+
+ case NAND_CTL_SETCLE:
+ MACRO_NAND_CTL_SETCLE((unsigned long)CFG_NAND0_PADDR);
+ break;
+ case NAND_CTL_CLRCLE:
+ MACRO_NAND_CTL_CLRCLE((unsigned long)CFG_NAND0_PADDR);
+ break;
+ case NAND_CTL_SETALE:
+ MACRO_NAND_CTL_SETALE((unsigned long)CFG_NAND0_PADDR);
+ break;
+ case NAND_CTL_CLRALE:
+ MACRO_NAND_CTL_CLRALE((unsigned long)CFG_NAND0_PADDR);
+ break;
+ case NAND_CTL_SETNCE:
+ MACRO_NAND_ENABLE_CE((unsigned long)CFG_NAND0_PADDR);
+ break;
+ case NAND_CTL_CLRNCE:
+ MACRO_NAND_DISABLE_CE((unsigned long)CFG_NAND0_PADDR);
+ break;
+ }
+}
+
+static void ppchameleonevb_hwcontrol(struct mtd_info *mtdinfo, int cmd)
+{
+ switch(cmd) {
+
+ case NAND_CTL_SETCLE:
+ MACRO_NAND_CTL_SETCLE((unsigned long)CFG_NAND1_PADDR);
+ break;
+ case NAND_CTL_CLRCLE:
+ MACRO_NAND_CTL_CLRCLE((unsigned long)CFG_NAND1_PADDR);
+ break;
+ case NAND_CTL_SETALE:
+ MACRO_NAND_CTL_SETALE((unsigned long)CFG_NAND1_PADDR);
+ break;
+ case NAND_CTL_CLRALE:
+ MACRO_NAND_CTL_CLRALE((unsigned long)CFG_NAND1_PADDR);
+ break;
+ case NAND_CTL_SETNCE:
+ MACRO_NAND_ENABLE_CE((unsigned long)CFG_NAND1_PADDR);
+ break;
+ case NAND_CTL_CLRNCE:
+ MACRO_NAND_DISABLE_CE((unsigned long)CFG_NAND1_PADDR);
+ break;
+ }
+}
+
+#ifdef USE_READY_BUSY_PIN
+/*
+ * read device ready pin
+ */
+static int ppchameleon_device_ready(struct mtd_info *minfo)
+{
+ if (in_be32((volatile unsigned*)GPIO0_IR) & NAND_RB_GPIO_PIN)
+ return 1;
+ return 0;
+}
+
+static int ppchameleonevb_device_ready(struct mtd_info *minfo)
+{
+ if (in_be32((volatile unsigned*)GPIO0_IR) & NAND_EVB_RB_GPIO_PIN)
+ return 1;
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_MTD_PARTITIONS
+const char *part_probes[] = { "cmdlinepart", NULL };
+const char *part_probes_evb[] = { "cmdlinepart", NULL };
+#endif
+
+/*
+ * Main initialization routine
+ */
+static int __init ppchameleonevb_init (void)
+{
+ struct nand_chip *this;
+ const char *part_type = 0;
+ int mtd_parts_nb = 0;
+ struct mtd_partition *mtd_parts = 0;
+ void __iomem *ppchameleon_fio_base;
+ void __iomem *ppchameleonevb_fio_base;
+
+
+ /*********************************
+ * Processor module NAND (if any) *
+ *********************************/
+ /* Allocate memory for MTD device structure and private data */
+ ppchameleon_mtd = kmalloc(sizeof(struct mtd_info) +
+ sizeof(struct nand_chip), GFP_KERNEL);
+ if (!ppchameleon_mtd) {
+ printk("Unable to allocate PPChameleon NAND MTD device structure.\n");
+ return -ENOMEM;
+ }
+
+ /* map physical address */
+ ppchameleon_fio_base = ioremap(ppchameleon_fio_pbase, SZ_4M);
+ if(!ppchameleon_fio_base) {
+ printk("ioremap PPChameleon NAND flash failed\n");
+ kfree(ppchameleon_mtd);
+ return -EIO;
+ }
+
+ /* Get pointer to private data */
+ this = (struct nand_chip *) (&ppchameleon_mtd[1]);
+
+ /* Initialize structures */
+ memset((char *) ppchameleon_mtd, 0, sizeof(struct mtd_info));
+ memset((char *) this, 0, sizeof(struct nand_chip));
+
+ /* Link the private data with the MTD structure */
+ ppchameleon_mtd->priv = this;
+
+ /* Initialize GPIOs */
+ /* Pin mapping for NAND chip */
+ /*
+ CE GPIO_01
+ CLE GPIO_02
+ ALE GPIO_03
+ R/B GPIO_04
+ */
+ /* output select */
+ out_be32((volatile unsigned*)GPIO0_OSRH, in_be32((volatile unsigned*)GPIO0_OSRH) & 0xC0FFFFFF);
+ /* three-state select */
+ out_be32((volatile unsigned*)GPIO0_TSRH, in_be32((volatile unsigned*)GPIO0_TSRH) & 0xC0FFFFFF);
+ /* enable output driver */
+ out_be32((volatile unsigned*)GPIO0_TCR, in_be32((volatile unsigned*)GPIO0_TCR) | NAND_nCE_GPIO_PIN | NAND_CLE_GPIO_PIN | NAND_ALE_GPIO_PIN);
+#ifdef USE_READY_BUSY_PIN
+ /* three-state select */
+ out_be32((volatile unsigned*)GPIO0_TSRH, in_be32((volatile unsigned*)GPIO0_TSRH) & 0xFF3FFFFF);
+ /* high-impedecence */
+ out_be32((volatile unsigned*)GPIO0_TCR, in_be32((volatile unsigned*)GPIO0_TCR) & (~NAND_RB_GPIO_PIN));
+ /* input select */
+ out_be32((volatile unsigned*)GPIO0_ISR1H, (in_be32((volatile unsigned*)GPIO0_ISR1H) & 0xFF3FFFFF) | 0x00400000);
+#endif
+
+ /* insert callbacks */
+ this->IO_ADDR_R = ppchameleon_fio_base;
+ this->IO_ADDR_W = ppchameleon_fio_base;
+ this->hwcontrol = ppchameleon_hwcontrol;
+#ifdef USE_READY_BUSY_PIN
+ this->dev_ready = ppchameleon_device_ready;
+#endif
+ this->chip_delay = NAND_BIG_DELAY_US;
+ /* ECC mode */
+ this->eccmode = NAND_ECC_SOFT;
+
+ /* Scan to find existence of the device (it could not be mounted) */
+ if (nand_scan (ppchameleon_mtd, 1)) {
+ iounmap((void *)ppchameleon_fio_base);
+ kfree (ppchameleon_mtd);
+ goto nand_evb_init;
+ }
+
+#ifndef USE_READY_BUSY_PIN
+ /* Adjust delay if necessary */
+ if (ppchameleon_mtd->size == NAND_SMALL_SIZE)
+ this->chip_delay = NAND_SMALL_DELAY_US;
+#endif
+
+#ifdef CONFIG_MTD_PARTITIONS
+ ppchameleon_mtd->name = "ppchameleon-nand";
+ mtd_parts_nb = parse_mtd_partitions(ppchameleon_mtd, part_probes, &mtd_parts, 0);
+ if (mtd_parts_nb > 0)
+ part_type = "command line";
+ else
+ mtd_parts_nb = 0;
+#endif
+ if (mtd_parts_nb == 0)
+ {
+ if (ppchameleon_mtd->size == NAND_SMALL_SIZE)
+ mtd_parts = partition_info_me;
+ else
+ mtd_parts = partition_info_hi;
+ mtd_parts_nb = NUM_PARTITIONS;
+ part_type = "static";
+ }
+
+ /* Register the partitions */
+ printk(KERN_NOTICE "Using %s partition definition\n", part_type);
+ add_mtd_partitions(ppchameleon_mtd, mtd_parts, mtd_parts_nb);
+
+nand_evb_init:
+ /****************************
+ * EVB NAND (always present) *
+ ****************************/
+ /* Allocate memory for MTD device structure and private data */
+ ppchameleonevb_mtd = kmalloc(sizeof(struct mtd_info) +
+ sizeof(struct nand_chip), GFP_KERNEL);
+ if (!ppchameleonevb_mtd) {
+ printk("Unable to allocate PPChameleonEVB NAND MTD device structure.\n");
+ return -ENOMEM;
+ }
+
+ /* map physical address */
+ ppchameleonevb_fio_base = ioremap(ppchameleonevb_fio_pbase, SZ_4M);
+ if(!ppchameleonevb_fio_base) {
+ printk("ioremap PPChameleonEVB NAND flash failed\n");
+ kfree(ppchameleonevb_mtd);
+ return -EIO;
+ }
+
+ /* Get pointer to private data */
+ this = (struct nand_chip *) (&ppchameleonevb_mtd[1]);
+
+ /* Initialize structures */
+ memset((char *) ppchameleonevb_mtd, 0, sizeof(struct mtd_info));
+ memset((char *) this, 0, sizeof(struct nand_chip));
+
+ /* Link the private data with the MTD structure */
+ ppchameleonevb_mtd->priv = this;
+
+ /* Initialize GPIOs */
+ /* Pin mapping for NAND chip */
+ /*
+ CE GPIO_14
+ CLE GPIO_15
+ ALE GPIO_16
+ R/B GPIO_31
+ */
+ /* output select */
+ out_be32((volatile unsigned*)GPIO0_OSRH, in_be32((volatile unsigned*)GPIO0_OSRH) & 0xFFFFFFF0);
+ out_be32((volatile unsigned*)GPIO0_OSRL, in_be32((volatile unsigned*)GPIO0_OSRL) & 0x3FFFFFFF);
+ /* three-state select */
+ out_be32((volatile unsigned*)GPIO0_TSRH, in_be32((volatile unsigned*)GPIO0_TSRH) & 0xFFFFFFF0);
+ out_be32((volatile unsigned*)GPIO0_TSRL, in_be32((volatile unsigned*)GPIO0_TSRL) & 0x3FFFFFFF);
+ /* enable output driver */
+ out_be32((volatile unsigned*)GPIO0_TCR, in_be32((volatile unsigned*)GPIO0_TCR) | NAND_EVB_nCE_GPIO_PIN |
+ NAND_EVB_CLE_GPIO_PIN | NAND_EVB_ALE_GPIO_PIN);
+#ifdef USE_READY_BUSY_PIN
+ /* three-state select */
+ out_be32((volatile unsigned*)GPIO0_TSRL, in_be32((volatile unsigned*)GPIO0_TSRL) & 0xFFFFFFFC);
+ /* high-impedecence */
+ out_be32((volatile unsigned*)GPIO0_TCR, in_be32((volatile unsigned*)GPIO0_TCR) & (~NAND_EVB_RB_GPIO_PIN));
+ /* input select */
+ out_be32((volatile unsigned*)GPIO0_ISR1L, (in_be32((volatile unsigned*)GPIO0_ISR1L) & 0xFFFFFFFC) | 0x00000001);
+#endif
+
+ /* insert callbacks */
+ this->IO_ADDR_R = ppchameleonevb_fio_base;
+ this->IO_ADDR_W = ppchameleonevb_fio_base;
+ this->hwcontrol = ppchameleonevb_hwcontrol;
+#ifdef USE_READY_BUSY_PIN
+ this->dev_ready = ppchameleonevb_device_ready;
+#endif
+ this->chip_delay = NAND_SMALL_DELAY_US;
+
+ /* ECC mode */
+ this->eccmode = NAND_ECC_SOFT;
+
+ /* Scan to find existence of the device */
+ if (nand_scan (ppchameleonevb_mtd, 1)) {
+ iounmap((void *)ppchameleonevb_fio_base);
+ kfree (ppchameleonevb_mtd);
+ return -ENXIO;
+ }
+
+#ifdef CONFIG_MTD_PARTITIONS
+ ppchameleonevb_mtd->name = NAND_EVB_MTD_NAME;
+ mtd_parts_nb = parse_mtd_partitions(ppchameleonevb_mtd, part_probes_evb, &mtd_parts, 0);
+ if (mtd_parts_nb > 0)
+ part_type = "command line";
+ else
+ mtd_parts_nb = 0;
+#endif
+ if (mtd_parts_nb == 0)
+ {
+ mtd_parts = partition_info_evb;
+ mtd_parts_nb = NUM_PARTITIONS;
+ part_type = "static";
+ }
+
+ /* Register the partitions */
+ printk(KERN_NOTICE "Using %s partition definition\n", part_type);
+ add_mtd_partitions(ppchameleonevb_mtd, mtd_parts, mtd_parts_nb);
+
+ /* Return happy */
+ return 0;
+}
+module_init(ppchameleonevb_init);
+
+/*
+ * Clean up routine
+ */
+static void __exit ppchameleonevb_cleanup (void)
+{
+ struct nand_chip *this;
+
+ /* Release resources, unregister device(s) */
+ nand_release (ppchameleon_mtd);
+ nand_release (ppchameleonevb_mtd);
+
+ /* Release iomaps */
+ this = (struct nand_chip *) &ppchameleon_mtd[1];
+ iounmap((void *) this->IO_ADDR_R;
+ this = (struct nand_chip *) &ppchameleonevb_mtd[1];
+ iounmap((void *) this->IO_ADDR_R;
+
+ /* Free the MTD device structure */
+ kfree (ppchameleon_mtd);
+ kfree (ppchameleonevb_mtd);
+}
+module_exit(ppchameleonevb_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("DAVE Srl <support-ppchameleon@dave-tech.it>");
+MODULE_DESCRIPTION("MTD map driver for DAVE Srl PPChameleonEVB board");
diff --git a/drivers/mtd/nand/rtc_from4.c b/drivers/mtd/nand/rtc_from4.c
new file mode 100644
index 000000000000..02305a2adca7
--- /dev/null
+++ b/drivers/mtd/nand/rtc_from4.c
@@ -0,0 +1,559 @@
+/*
+ * drivers/mtd/nand/rtc_from4.c
+ *
+ * Copyright (C) 2004 Red Hat, Inc.
+ *
+ * Derived from drivers/mtd/nand/spia.c
+ * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com)
+ *
+ * $Id: rtc_from4.c,v 1.7 2004/11/04 12:53:10 gleixner Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Overview:
+ * This is a device driver for the AG-AND flash device found on the
+ * Renesas Technology Corp. Flash ROM 4-slot interface board (FROM_BOARD4),
+ * which utilizes the Renesas HN29V1G91T-30 part.
+ * This chip is a 1 GBibit (128MiB x 8 bits) AG-AND flash device.
+ */
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/rslib.h>
+#include <linux/module.h>
+#include <linux/mtd/compatmac.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <asm/io.h>
+
+/*
+ * MTD structure for Renesas board
+ */
+static struct mtd_info *rtc_from4_mtd = NULL;
+
+#define RTC_FROM4_MAX_CHIPS 2
+
+/* HS77x9 processor register defines */
+#define SH77X9_BCR1 ((volatile unsigned short *)(0xFFFFFF60))
+#define SH77X9_BCR2 ((volatile unsigned short *)(0xFFFFFF62))
+#define SH77X9_WCR1 ((volatile unsigned short *)(0xFFFFFF64))
+#define SH77X9_WCR2 ((volatile unsigned short *)(0xFFFFFF66))
+#define SH77X9_MCR ((volatile unsigned short *)(0xFFFFFF68))
+#define SH77X9_PCR ((volatile unsigned short *)(0xFFFFFF6C))
+#define SH77X9_FRQCR ((volatile unsigned short *)(0xFFFFFF80))
+
+/*
+ * Values specific to the Renesas Technology Corp. FROM_BOARD4 (used with HS77x9 processor)
+ */
+/* Address where flash is mapped */
+#define RTC_FROM4_FIO_BASE 0x14000000
+
+/* CLE and ALE are tied to address lines 5 & 4, respectively */
+#define RTC_FROM4_CLE (1 << 5)
+#define RTC_FROM4_ALE (1 << 4)
+
+/* address lines A24-A22 used for chip selection */
+#define RTC_FROM4_NAND_ADDR_SLOT3 (0x00800000)
+#define RTC_FROM4_NAND_ADDR_SLOT4 (0x00C00000)
+#define RTC_FROM4_NAND_ADDR_FPGA (0x01000000)
+/* mask address lines A24-A22 used for chip selection */
+#define RTC_FROM4_NAND_ADDR_MASK (RTC_FROM4_NAND_ADDR_SLOT3 | RTC_FROM4_NAND_ADDR_SLOT4 | RTC_FROM4_NAND_ADDR_FPGA)
+
+/* FPGA status register for checking device ready (bit zero) */
+#define RTC_FROM4_FPGA_SR (RTC_FROM4_NAND_ADDR_FPGA | 0x00000002)
+#define RTC_FROM4_DEVICE_READY 0x0001
+
+/* FPGA Reed-Solomon ECC Control register */
+
+#define RTC_FROM4_RS_ECC_CTL (RTC_FROM4_NAND_ADDR_FPGA | 0x00000050)
+#define RTC_FROM4_RS_ECC_CTL_CLR (1 << 7)
+#define RTC_FROM4_RS_ECC_CTL_GEN (1 << 6)
+#define RTC_FROM4_RS_ECC_CTL_FD_E (1 << 5)
+
+/* FPGA Reed-Solomon ECC code base */
+#define RTC_FROM4_RS_ECC (RTC_FROM4_NAND_ADDR_FPGA | 0x00000060)
+#define RTC_FROM4_RS_ECCN (RTC_FROM4_NAND_ADDR_FPGA | 0x00000080)
+
+/* FPGA Reed-Solomon ECC check register */
+#define RTC_FROM4_RS_ECC_CHK (RTC_FROM4_NAND_ADDR_FPGA | 0x00000070)
+#define RTC_FROM4_RS_ECC_CHK_ERROR (1 << 7)
+
+/* Undefine for software ECC */
+#define RTC_FROM4_HWECC 1
+
+/*
+ * Module stuff
+ */
+static void __iomem *rtc_from4_fio_base = P2SEGADDR(RTC_FROM4_FIO_BASE);
+
+const static struct mtd_partition partition_info[] = {
+ {
+ .name = "Renesas flash partition 1",
+ .offset = 0,
+ .size = MTDPART_SIZ_FULL
+ },
+};
+#define NUM_PARTITIONS 1
+
+/*
+ * hardware specific flash bbt decriptors
+ * Note: this is to allow debugging by disabling
+ * NAND_BBT_CREATE and/or NAND_BBT_WRITE
+ *
+ */
+static uint8_t bbt_pattern[] = {'B', 'b', 't', '0' };
+static uint8_t mirror_pattern[] = {'1', 't', 'b', 'B' };
+
+static struct nand_bbt_descr rtc_from4_bbt_main_descr = {
+ .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE
+ | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP,
+ .offs = 40,
+ .len = 4,
+ .veroffs = 44,
+ .maxblocks = 4,
+ .pattern = bbt_pattern
+};
+
+static struct nand_bbt_descr rtc_from4_bbt_mirror_descr = {
+ .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE
+ | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP,
+ .offs = 40,
+ .len = 4,
+ .veroffs = 44,
+ .maxblocks = 4,
+ .pattern = mirror_pattern
+};
+
+
+
+#ifdef RTC_FROM4_HWECC
+
+/* the Reed Solomon control structure */
+static struct rs_control *rs_decoder;
+
+/*
+ * hardware specific Out Of Band information
+ */
+static struct nand_oobinfo rtc_from4_nand_oobinfo = {
+ .useecc = MTD_NANDECC_AUTOPLACE,
+ .eccbytes = 32,
+ .eccpos = {
+ 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31},
+ .oobfree = { {32, 32} }
+};
+
+/* Aargh. I missed the reversed bit order, when I
+ * was talking to Renesas about the FPGA.
+ *
+ * The table is used for bit reordering and inversion
+ * of the ecc byte which we get from the FPGA
+ */
+static uint8_t revbits[256] = {
+ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+ 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+ 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+ 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+ 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+ 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+ 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+ 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+ 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+ 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+ 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+ 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+ 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+ 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+ 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+ 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+ 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
+};
+
+#endif
+
+
+
+/*
+ * rtc_from4_hwcontrol - hardware specific access to control-lines
+ * @mtd: MTD device structure
+ * @cmd: hardware control command
+ *
+ * Address lines (A5 and A4) are used to control Command and Address Latch
+ * Enable on this board, so set the read/write address appropriately.
+ *
+ * Chip Enable is also controlled by the Chip Select (CS5) and
+ * Address lines (A24-A22), so no action is required here.
+ *
+ */
+static void rtc_from4_hwcontrol(struct mtd_info *mtd, int cmd)
+{
+ struct nand_chip* this = (struct nand_chip *) (mtd->priv);
+
+ switch(cmd) {
+
+ case NAND_CTL_SETCLE:
+ this->IO_ADDR_W = (void __iomem *)((unsigned long)this->IO_ADDR_W | RTC_FROM4_CLE);
+ break;
+ case NAND_CTL_CLRCLE:
+ this->IO_ADDR_W = (void __iomem *)((unsigned long)this->IO_ADDR_W & ~RTC_FROM4_CLE);
+ break;
+
+ case NAND_CTL_SETALE:
+ this->IO_ADDR_W = (void __iomem *)((unsigned long)this->IO_ADDR_W | RTC_FROM4_ALE);
+ break;
+ case NAND_CTL_CLRALE:
+ this->IO_ADDR_W = (void __iomem *)((unsigned long)this->IO_ADDR_W & ~RTC_FROM4_ALE);
+ break;
+
+ case NAND_CTL_SETNCE:
+ break;
+ case NAND_CTL_CLRNCE:
+ break;
+
+ }
+}
+
+
+/*
+ * rtc_from4_nand_select_chip - hardware specific chip select
+ * @mtd: MTD device structure
+ * @chip: Chip to select (0 == slot 3, 1 == slot 4)
+ *
+ * The chip select is based on address lines A24-A22.
+ * This driver uses flash slots 3 and 4 (A23-A22).
+ *
+ */
+static void rtc_from4_nand_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct nand_chip *this = mtd->priv;
+
+ this->IO_ADDR_R = (void __iomem *)((unsigned long)this->IO_ADDR_R & ~RTC_FROM4_NAND_ADDR_MASK);
+ this->IO_ADDR_W = (void __iomem *)((unsigned long)this->IO_ADDR_W & ~RTC_FROM4_NAND_ADDR_MASK);
+
+ switch(chip) {
+
+ case 0: /* select slot 3 chip */
+ this->IO_ADDR_R = (void __iomem *)((unsigned long)this->IO_ADDR_R | RTC_FROM4_NAND_ADDR_SLOT3);
+ this->IO_ADDR_W = (void __iomem *)((unsigned long)this->IO_ADDR_W | RTC_FROM4_NAND_ADDR_SLOT3);
+ break;
+ case 1: /* select slot 4 chip */
+ this->IO_ADDR_R = (void __iomem *)((unsigned long)this->IO_ADDR_R | RTC_FROM4_NAND_ADDR_SLOT4);
+ this->IO_ADDR_W = (void __iomem *)((unsigned long)this->IO_ADDR_W | RTC_FROM4_NAND_ADDR_SLOT4);
+ break;
+
+ }
+}
+
+
+
+/*
+ * rtc_from4_nand_device_ready - hardware specific ready/busy check
+ * @mtd: MTD device structure
+ *
+ * This board provides the Ready/Busy state in the status register
+ * of the FPGA. Bit zero indicates the RDY(1)/BSY(0) signal.
+ *
+ */
+static int rtc_from4_nand_device_ready(struct mtd_info *mtd)
+{
+ unsigned short status;
+
+ status = *((volatile unsigned short *)(rtc_from4_fio_base + RTC_FROM4_FPGA_SR));
+
+ return (status & RTC_FROM4_DEVICE_READY);
+
+}
+
+#ifdef RTC_FROM4_HWECC
+/*
+ * rtc_from4_enable_hwecc - hardware specific hardware ECC enable function
+ * @mtd: MTD device structure
+ * @mode: I/O mode; read or write
+ *
+ * enable hardware ECC for data read or write
+ *
+ */
+static void rtc_from4_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+ volatile unsigned short * rs_ecc_ctl = (volatile unsigned short *)(rtc_from4_fio_base + RTC_FROM4_RS_ECC_CTL);
+ unsigned short status;
+
+ switch (mode) {
+ case NAND_ECC_READ :
+ status = RTC_FROM4_RS_ECC_CTL_CLR
+ | RTC_FROM4_RS_ECC_CTL_FD_E;
+
+ *rs_ecc_ctl = status;
+ break;
+
+ case NAND_ECC_READSYN :
+ status = 0x00;
+
+ *rs_ecc_ctl = status;
+ break;
+
+ case NAND_ECC_WRITE :
+ status = RTC_FROM4_RS_ECC_CTL_CLR
+ | RTC_FROM4_RS_ECC_CTL_GEN
+ | RTC_FROM4_RS_ECC_CTL_FD_E;
+
+ *rs_ecc_ctl = status;
+ break;
+
+ default:
+ BUG();
+ break;
+ }
+
+}
+
+/*
+ * rtc_from4_calculate_ecc - hardware specific code to read ECC code
+ * @mtd: MTD device structure
+ * @dat: buffer containing the data to generate ECC codes
+ * @ecc_code ECC codes calculated
+ *
+ * The ECC code is calculated by the FPGA. All we have to do is read the values
+ * from the FPGA registers.
+ *
+ * Note: We read from the inverted registers, since data is inverted before
+ * the code is calculated. So all 0xff data (blank page) results in all 0xff rs code
+ *
+ */
+static void rtc_from4_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)
+{
+ volatile unsigned short * rs_eccn = (volatile unsigned short *)(rtc_from4_fio_base + RTC_FROM4_RS_ECCN);
+ unsigned short value;
+ int i;
+
+ for (i = 0; i < 8; i++) {
+ value = *rs_eccn;
+ ecc_code[i] = (unsigned char)value;
+ rs_eccn++;
+ }
+ ecc_code[7] |= 0x0f; /* set the last four bits (not used) */
+}
+
+/*
+ * rtc_from4_correct_data - hardware specific code to correct data using ECC code
+ * @mtd: MTD device structure
+ * @buf: buffer containing the data to generate ECC codes
+ * @ecc1 ECC codes read
+ * @ecc2 ECC codes calculated
+ *
+ * The FPGA tells us fast, if there's an error or not. If no, we go back happy
+ * else we read the ecc results from the fpga and call the rs library to decode
+ * and hopefully correct the error
+ *
+ * For now I use the code, which we read from the FLASH to use the RS lib,
+ * as the syndrom conversion has a unresolved issue.
+ */
+static int rtc_from4_correct_data(struct mtd_info *mtd, const u_char *buf, u_char *ecc1, u_char *ecc2)
+{
+ int i, j, res;
+ unsigned short status;
+ uint16_t par[6], syn[6], tmp;
+ uint8_t ecc[8];
+ volatile unsigned short *rs_ecc;
+
+ status = *((volatile unsigned short *)(rtc_from4_fio_base + RTC_FROM4_RS_ECC_CHK));
+
+ if (!(status & RTC_FROM4_RS_ECC_CHK_ERROR)) {
+ return 0;
+ }
+
+ /* Read the syndrom pattern from the FPGA and correct the bitorder */
+ rs_ecc = (volatile unsigned short *)(rtc_from4_fio_base + RTC_FROM4_RS_ECC);
+ for (i = 0; i < 8; i++) {
+ ecc[i] = revbits[(*rs_ecc) & 0xFF];
+ rs_ecc++;
+ }
+
+ /* convert into 6 10bit syndrome fields */
+ par[5] = rs_decoder->index_of[(((uint16_t)ecc[0] >> 0) & 0x0ff) |
+ (((uint16_t)ecc[1] << 8) & 0x300)];
+ par[4] = rs_decoder->index_of[(((uint16_t)ecc[1] >> 2) & 0x03f) |
+ (((uint16_t)ecc[2] << 6) & 0x3c0)];
+ par[3] = rs_decoder->index_of[(((uint16_t)ecc[2] >> 4) & 0x00f) |
+ (((uint16_t)ecc[3] << 4) & 0x3f0)];
+ par[2] = rs_decoder->index_of[(((uint16_t)ecc[3] >> 6) & 0x003) |
+ (((uint16_t)ecc[4] << 2) & 0x3fc)];
+ par[1] = rs_decoder->index_of[(((uint16_t)ecc[5] >> 0) & 0x0ff) |
+ (((uint16_t)ecc[6] << 8) & 0x300)];
+ par[0] = (((uint16_t)ecc[6] >> 2) & 0x03f) | (((uint16_t)ecc[7] << 6) & 0x3c0);
+
+ /* Convert to computable syndrome */
+ for (i = 0; i < 6; i++) {
+ syn[i] = par[0];
+ for (j = 1; j < 6; j++)
+ if (par[j] != rs_decoder->nn)
+ syn[i] ^= rs_decoder->alpha_to[rs_modnn(rs_decoder, par[j] + i * j)];
+
+ /* Convert to index form */
+ syn[i] = rs_decoder->index_of[syn[i]];
+ }
+
+ /* Let the library code do its magic.*/
+ res = decode_rs8(rs_decoder, buf, par, 512, syn, 0, NULL, 0xff, NULL);
+ if (res > 0) {
+ DEBUG (MTD_DEBUG_LEVEL0, "rtc_from4_correct_data: "
+ "ECC corrected %d errors on read\n", res);
+ }
+ return res;
+}
+#endif
+
+/*
+ * Main initialization routine
+ */
+int __init rtc_from4_init (void)
+{
+ struct nand_chip *this;
+ unsigned short bcr1, bcr2, wcr2;
+
+ /* Allocate memory for MTD device structure and private data */
+ rtc_from4_mtd = kmalloc(sizeof(struct mtd_info) + sizeof (struct nand_chip),
+ GFP_KERNEL);
+ if (!rtc_from4_mtd) {
+ printk ("Unable to allocate Renesas NAND MTD device structure.\n");
+ return -ENOMEM;
+ }
+
+ /* Get pointer to private data */
+ this = (struct nand_chip *) (&rtc_from4_mtd[1]);
+
+ /* Initialize structures */
+ memset((char *) rtc_from4_mtd, 0, sizeof(struct mtd_info));
+ memset((char *) this, 0, sizeof(struct nand_chip));
+
+ /* Link the private data with the MTD structure */
+ rtc_from4_mtd->priv = this;
+
+ /* set area 5 as PCMCIA mode to clear the spec of tDH(Data hold time;9ns min) */
+ bcr1 = *SH77X9_BCR1 & ~0x0002;
+ bcr1 |= 0x0002;
+ *SH77X9_BCR1 = bcr1;
+
+ /* set */
+ bcr2 = *SH77X9_BCR2 & ~0x0c00;
+ bcr2 |= 0x0800;
+ *SH77X9_BCR2 = bcr2;
+
+ /* set area 5 wait states */
+ wcr2 = *SH77X9_WCR2 & ~0x1c00;
+ wcr2 |= 0x1c00;
+ *SH77X9_WCR2 = wcr2;
+
+ /* Set address of NAND IO lines */
+ this->IO_ADDR_R = rtc_from4_fio_base;
+ this->IO_ADDR_W = rtc_from4_fio_base;
+ /* Set address of hardware control function */
+ this->hwcontrol = rtc_from4_hwcontrol;
+ /* Set address of chip select function */
+ this->select_chip = rtc_from4_nand_select_chip;
+ /* command delay time (in us) */
+ this->chip_delay = 100;
+ /* return the status of the Ready/Busy line */
+ this->dev_ready = rtc_from4_nand_device_ready;
+
+#ifdef RTC_FROM4_HWECC
+ printk(KERN_INFO "rtc_from4_init: using hardware ECC detection.\n");
+
+ this->eccmode = NAND_ECC_HW8_512;
+ this->options |= NAND_HWECC_SYNDROME;
+ /* set the nand_oobinfo to support FPGA H/W error detection */
+ this->autooob = &rtc_from4_nand_oobinfo;
+ this->enable_hwecc = rtc_from4_enable_hwecc;
+ this->calculate_ecc = rtc_from4_calculate_ecc;
+ this->correct_data = rtc_from4_correct_data;
+#else
+ printk(KERN_INFO "rtc_from4_init: using software ECC detection.\n");
+
+ this->eccmode = NAND_ECC_SOFT;
+#endif
+
+ /* set the bad block tables to support debugging */
+ this->bbt_td = &rtc_from4_bbt_main_descr;
+ this->bbt_md = &rtc_from4_bbt_mirror_descr;
+
+ /* Scan to find existence of the device */
+ if (nand_scan(rtc_from4_mtd, RTC_FROM4_MAX_CHIPS)) {
+ kfree(rtc_from4_mtd);
+ return -ENXIO;
+ }
+
+ /* Register the partitions */
+ add_mtd_partitions(rtc_from4_mtd, partition_info, NUM_PARTITIONS);
+
+#ifdef RTC_FROM4_HWECC
+ /* We could create the decoder on demand, if memory is a concern.
+ * This way we have it handy, if an error happens
+ *
+ * Symbolsize is 10 (bits)
+ * Primitve polynomial is x^10+x^3+1
+ * first consecutive root is 0
+ * primitve element to generate roots = 1
+ * generator polinomial degree = 6
+ */
+ rs_decoder = init_rs(10, 0x409, 0, 1, 6);
+ if (!rs_decoder) {
+ printk (KERN_ERR "Could not create a RS decoder\n");
+ nand_release(rtc_from4_mtd);
+ kfree(rtc_from4_mtd);
+ return -ENOMEM;
+ }
+#endif
+ /* Return happy */
+ return 0;
+}
+module_init(rtc_from4_init);
+
+
+/*
+ * Clean up routine
+ */
+#ifdef MODULE
+static void __exit rtc_from4_cleanup (void)
+{
+ /* Release resource, unregister partitions */
+ nand_release(rtc_from4_mtd);
+
+ /* Free the MTD device structure */
+ kfree (rtc_from4_mtd);
+
+#ifdef RTC_FROM4_HWECC
+ /* Free the reed solomon resources */
+ if (rs_decoder) {
+ free_rs(rs_decoder);
+ }
+#endif
+}
+module_exit(rtc_from4_cleanup);
+#endif
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("d.marlin <dmarlin@redhat.com");
+MODULE_DESCRIPTION("Board-specific glue layer for AG-AND flash on Renesas FROM_BOARD4");
+
diff --git a/drivers/mtd/nand/s3c2410.c b/drivers/mtd/nand/s3c2410.c
new file mode 100644
index 000000000000..d05e9b97947d
--- /dev/null
+++ b/drivers/mtd/nand/s3c2410.c
@@ -0,0 +1,704 @@
+/* linux/drivers/mtd/nand/s3c2410.c
+ *
+ * Copyright (c) 2004 Simtec Electronics
+ * Ben Dooks <ben@simtec.co.uk>
+ *
+ * Samsung S3C2410 NAND driver
+ *
+ * Changelog:
+ * 21-Sep-2004 BJD Initial version
+ * 23-Sep-2004 BJD Mulitple device support
+ * 28-Sep-2004 BJD Fixed ECC placement for Hardware mode
+ * 12-Oct-2004 BJD Fixed errors in use of platform data
+ *
+ * $Id: s3c2410.c,v 1.7 2005/01/05 18:05:14 dwmw2 Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <config/mtd/nand/s3c2410/hwecc.h>
+#include <config/mtd/nand/s3c2410/debug.h>
+
+#ifdef CONFIG_MTD_NAND_S3C2410_DEBUG
+#define DEBUG
+#endif
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/io.h>
+#include <asm/mach-types.h>
+#include <asm/hardware/clock.h>
+
+#include <asm/arch/regs-nand.h>
+#include <asm/arch/nand.h>
+
+#define PFX "s3c2410-nand: "
+
+#ifdef CONFIG_MTD_NAND_S3C2410_HWECC
+static int hardware_ecc = 1;
+#else
+static int hardware_ecc = 0;
+#endif
+
+/* new oob placement block for use with hardware ecc generation
+ */
+
+static struct nand_oobinfo nand_hw_eccoob = {
+ .useecc = MTD_NANDECC_AUTOPLACE,
+ .eccbytes = 3,
+ .eccpos = {0, 1, 2 },
+ .oobfree = { {8, 8} }
+};
+
+/* controller and mtd information */
+
+struct s3c2410_nand_info;
+
+struct s3c2410_nand_mtd {
+ struct mtd_info mtd;
+ struct nand_chip chip;
+ struct s3c2410_nand_set *set;
+ struct s3c2410_nand_info *info;
+ int scan_res;
+};
+
+/* overview of the s3c2410 nand state */
+
+struct s3c2410_nand_info {
+ /* mtd info */
+ struct nand_hw_control controller;
+ struct s3c2410_nand_mtd *mtds;
+ struct s3c2410_platform_nand *platform;
+
+ /* device info */
+ struct device *device;
+ struct resource *area;
+ struct clk *clk;
+ void *regs;
+ int mtd_count;
+};
+
+/* conversion functions */
+
+static struct s3c2410_nand_mtd *s3c2410_nand_mtd_toours(struct mtd_info *mtd)
+{
+ return container_of(mtd, struct s3c2410_nand_mtd, mtd);
+}
+
+static struct s3c2410_nand_info *s3c2410_nand_mtd_toinfo(struct mtd_info *mtd)
+{
+ return s3c2410_nand_mtd_toours(mtd)->info;
+}
+
+static struct s3c2410_nand_info *to_nand_info(struct device *dev)
+{
+ return dev_get_drvdata(dev);
+}
+
+static struct s3c2410_platform_nand *to_nand_plat(struct device *dev)
+{
+ return dev->platform_data;
+}
+
+/* timing calculations */
+
+#define NS_IN_KHZ 10000000
+
+static int s3c2410_nand_calc_rate(int wanted, unsigned long clk, int max)
+{
+ int result;
+
+ result = (wanted * NS_IN_KHZ) / clk;
+ result++;
+
+ pr_debug("result %d from %ld, %d\n", result, clk, wanted);
+
+ if (result > max) {
+ printk("%d ns is too big for current clock rate %ld\n",
+ wanted, clk);
+ return -1;
+ }
+
+ if (result < 1)
+ result = 1;
+
+ return result;
+}
+
+#define to_ns(ticks,clk) (((clk) * (ticks)) / NS_IN_KHZ)
+
+/* controller setup */
+
+static int s3c2410_nand_inithw(struct s3c2410_nand_info *info,
+ struct device *dev)
+{
+ struct s3c2410_platform_nand *plat = to_nand_plat(dev);
+ unsigned int tacls, twrph0, twrph1;
+ unsigned long clkrate = clk_get_rate(info->clk);
+ unsigned long cfg;
+
+ /* calculate the timing information for the controller */
+
+ if (plat != NULL) {
+ tacls = s3c2410_nand_calc_rate(plat->tacls, clkrate, 8);
+ twrph0 = s3c2410_nand_calc_rate(plat->twrph0, clkrate, 8);
+ twrph1 = s3c2410_nand_calc_rate(plat->twrph1, clkrate, 8);
+ } else {
+ /* default timings */
+ tacls = 8;
+ twrph0 = 8;
+ twrph1 = 8;
+ }
+
+ if (tacls < 0 || twrph0 < 0 || twrph1 < 0) {
+ printk(KERN_ERR PFX "cannot get timings suitable for board\n");
+ return -EINVAL;
+ }
+
+ printk(KERN_INFO PFX "timing: Tacls %ldns, Twrph0 %ldns, Twrph1 %ldns\n",
+ to_ns(tacls, clkrate),
+ to_ns(twrph0, clkrate),
+ to_ns(twrph1, clkrate));
+
+ cfg = S3C2410_NFCONF_EN;
+ cfg |= S3C2410_NFCONF_TACLS(tacls-1);
+ cfg |= S3C2410_NFCONF_TWRPH0(twrph0-1);
+ cfg |= S3C2410_NFCONF_TWRPH1(twrph1-1);
+
+ pr_debug(PFX "NF_CONF is 0x%lx\n", cfg);
+
+ writel(cfg, info->regs + S3C2410_NFCONF);
+ return 0;
+}
+
+/* select chip */
+
+static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip)
+{
+ struct s3c2410_nand_info *info;
+ struct s3c2410_nand_mtd *nmtd;
+ struct nand_chip *this = mtd->priv;
+ unsigned long cur;
+
+ nmtd = this->priv;
+ info = nmtd->info;
+
+ cur = readl(info->regs + S3C2410_NFCONF);
+
+ if (chip == -1) {
+ cur |= S3C2410_NFCONF_nFCE;
+ } else {
+ if (chip > nmtd->set->nr_chips) {
+ printk(KERN_ERR PFX "chip %d out of range\n", chip);
+ return;
+ }
+
+ if (info->platform != NULL) {
+ if (info->platform->select_chip != NULL)
+ (info->platform->select_chip)(nmtd->set, chip);
+ }
+
+ cur &= ~S3C2410_NFCONF_nFCE;
+ }
+
+ writel(cur, info->regs + S3C2410_NFCONF);
+}
+
+/* command and control functions */
+
+static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd)
+{
+ struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
+ unsigned long cur;
+
+ switch (cmd) {
+ case NAND_CTL_SETNCE:
+ cur = readl(info->regs + S3C2410_NFCONF);
+ cur &= ~S3C2410_NFCONF_nFCE;
+ writel(cur, info->regs + S3C2410_NFCONF);
+ break;
+
+ case NAND_CTL_CLRNCE:
+ cur = readl(info->regs + S3C2410_NFCONF);
+ cur |= S3C2410_NFCONF_nFCE;
+ writel(cur, info->regs + S3C2410_NFCONF);
+ break;
+
+ /* we don't need to implement these */
+ case NAND_CTL_SETCLE:
+ case NAND_CTL_CLRCLE:
+ case NAND_CTL_SETALE:
+ case NAND_CTL_CLRALE:
+ pr_debug(PFX "s3c2410_nand_hwcontrol(%d) unusedn", cmd);
+ break;
+ }
+}
+
+/* s3c2410_nand_command
+ *
+ * This function implements sending commands and the relevant address
+ * information to the chip, via the hardware controller. Since the
+ * S3C2410 generates the correct ALE/CLE signaling automatically, we
+ * do not need to use hwcontrol.
+*/
+
+static void s3c2410_nand_command (struct mtd_info *mtd, unsigned command,
+ int column, int page_addr)
+{
+ register struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
+ register struct nand_chip *this = mtd->priv;
+
+ /*
+ * Write out the command to the device.
+ */
+ if (command == NAND_CMD_SEQIN) {
+ int readcmd;
+
+ if (column >= mtd->oobblock) {
+ /* OOB area */
+ column -= mtd->oobblock;
+ readcmd = NAND_CMD_READOOB;
+ } else if (column < 256) {
+ /* First 256 bytes --> READ0 */
+ readcmd = NAND_CMD_READ0;
+ } else {
+ column -= 256;
+ readcmd = NAND_CMD_READ1;
+ }
+
+ writeb(readcmd, info->regs + S3C2410_NFCMD);
+ }
+ writeb(command, info->regs + S3C2410_NFCMD);
+
+ /* Set ALE and clear CLE to start address cycle */
+
+ if (column != -1 || page_addr != -1) {
+
+ /* Serially input address */
+ if (column != -1) {
+ /* Adjust columns for 16 bit buswidth */
+ if (this->options & NAND_BUSWIDTH_16)
+ column >>= 1;
+ writeb(column, info->regs + S3C2410_NFADDR);
+ }
+ if (page_addr != -1) {
+ writeb((unsigned char) (page_addr), info->regs + S3C2410_NFADDR);
+ writeb((unsigned char) (page_addr >> 8), info->regs + S3C2410_NFADDR);
+ /* One more address cycle for higher density devices */
+ if (this->chipsize & 0x0c000000)
+ writeb((unsigned char) ((page_addr >> 16) & 0x0f),
+ info->regs + S3C2410_NFADDR);
+ }
+ /* Latch in address */
+ }
+
+ /*
+ * program and erase have their own busy handlers
+ * status and sequential in needs no delay
+ */
+ switch (command) {
+
+ case NAND_CMD_PAGEPROG:
+ case NAND_CMD_ERASE1:
+ case NAND_CMD_ERASE2:
+ case NAND_CMD_SEQIN:
+ case NAND_CMD_STATUS:
+ return;
+
+ case NAND_CMD_RESET:
+ if (this->dev_ready)
+ break;
+
+ udelay(this->chip_delay);
+ writeb(NAND_CMD_STATUS, info->regs + S3C2410_NFCMD);
+
+ while ( !(this->read_byte(mtd) & 0x40));
+ return;
+
+ /* This applies to read commands */
+ default:
+ /*
+ * If we don't have access to the busy pin, we apply the given
+ * command delay
+ */
+ if (!this->dev_ready) {
+ udelay (this->chip_delay);
+ return;
+ }
+ }
+
+ /* Apply this short delay always to ensure that we do wait tWB in
+ * any case on any machine. */
+ ndelay (100);
+ /* wait until command is processed */
+ while (!this->dev_ready(mtd));
+}
+
+
+/* s3c2410_nand_devready()
+ *
+ * returns 0 if the nand is busy, 1 if it is ready
+*/
+
+static int s3c2410_nand_devready(struct mtd_info *mtd)
+{
+ struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
+
+ return readb(info->regs + S3C2410_NFSTAT) & S3C2410_NFSTAT_BUSY;
+}
+
+/* ECC handling functions */
+
+static int s3c2410_nand_correct_data(struct mtd_info *mtd, u_char *dat,
+ u_char *read_ecc, u_char *calc_ecc)
+{
+ pr_debug("s3c2410_nand_correct_data(%p,%p,%p,%p)\n",
+ mtd, dat, read_ecc, calc_ecc);
+
+ pr_debug("eccs: read %02x,%02x,%02x vs calc %02x,%02x,%02x\n",
+ read_ecc[0], read_ecc[1], read_ecc[2],
+ calc_ecc[0], calc_ecc[1], calc_ecc[2]);
+
+ if (read_ecc[0] == calc_ecc[0] &&
+ read_ecc[1] == calc_ecc[1] &&
+ read_ecc[2] == calc_ecc[2])
+ return 0;
+
+ /* we curently have no method for correcting the error */
+
+ return -1;
+}
+
+static void s3c2410_nand_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+ struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
+ unsigned long ctrl;
+
+ ctrl = readl(info->regs + S3C2410_NFCONF);
+ ctrl |= S3C2410_NFCONF_INITECC;
+ writel(ctrl, info->regs + S3C2410_NFCONF);
+}
+
+static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd,
+ const u_char *dat, u_char *ecc_code)
+{
+ struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
+
+ ecc_code[0] = readb(info->regs + S3C2410_NFECC + 0);
+ ecc_code[1] = readb(info->regs + S3C2410_NFECC + 1);
+ ecc_code[2] = readb(info->regs + S3C2410_NFECC + 2);
+
+ pr_debug("calculate_ecc: returning ecc %02x,%02x,%02x\n",
+ ecc_code[0], ecc_code[1], ecc_code[2]);
+
+ return 0;
+}
+
+
+/* over-ride the standard functions for a little more speed? */
+
+static void s3c2410_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+ readsb(this->IO_ADDR_R, buf, len);
+}
+
+static void s3c2410_nand_write_buf(struct mtd_info *mtd,
+ const u_char *buf, int len)
+{
+ struct nand_chip *this = mtd->priv;
+ writesb(this->IO_ADDR_W, buf, len);
+}
+
+/* device management functions */
+
+static int s3c2410_nand_remove(struct device *dev)
+{
+ struct s3c2410_nand_info *info = to_nand_info(dev);
+
+ dev_set_drvdata(dev, NULL);
+
+ if (info == NULL)
+ return 0;
+
+ /* first thing we need to do is release all our mtds
+ * and their partitions, then go through freeing the
+ * resources used
+ */
+
+ if (info->mtds != NULL) {
+ struct s3c2410_nand_mtd *ptr = info->mtds;
+ int mtdno;
+
+ for (mtdno = 0; mtdno < info->mtd_count; mtdno++, ptr++) {
+ pr_debug("releasing mtd %d (%p)\n", mtdno, ptr);
+ nand_release(&ptr->mtd);
+ }
+
+ kfree(info->mtds);
+ }
+
+ /* free the common resources */
+
+ if (info->clk != NULL && !IS_ERR(info->clk)) {
+ clk_disable(info->clk);
+ clk_unuse(info->clk);
+ clk_put(info->clk);
+ }
+
+ if (info->regs != NULL) {
+ iounmap(info->regs);
+ info->regs = NULL;
+ }
+
+ if (info->area != NULL) {
+ release_resource(info->area);
+ kfree(info->area);
+ info->area = NULL;
+ }
+
+ kfree(info);
+
+ return 0;
+}
+
+#ifdef CONFIG_MTD_PARTITIONS
+static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info,
+ struct s3c2410_nand_mtd *mtd,
+ struct s3c2410_nand_set *set)
+{
+ if (set == NULL)
+ return add_mtd_device(&mtd->mtd);
+
+ if (set->nr_partitions > 0 && set->partitions != NULL) {
+ return add_mtd_partitions(&mtd->mtd,
+ set->partitions,
+ set->nr_partitions);
+ }
+
+ return add_mtd_device(&mtd->mtd);
+}
+#else
+static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info,
+ struct s3c2410_nand_mtd *mtd,
+ struct s3c2410_nand_set *set)
+{
+ return add_mtd_device(&mtd->mtd);
+}
+#endif
+
+/* s3c2410_nand_init_chip
+ *
+ * init a single instance of an chip
+*/
+
+static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
+ struct s3c2410_nand_mtd *nmtd,
+ struct s3c2410_nand_set *set)
+{
+ struct nand_chip *chip = &nmtd->chip;
+
+ chip->IO_ADDR_R = (char *)info->regs + S3C2410_NFDATA;
+ chip->IO_ADDR_W = (char *)info->regs + S3C2410_NFDATA;
+ chip->hwcontrol = s3c2410_nand_hwcontrol;
+ chip->dev_ready = s3c2410_nand_devready;
+ chip->cmdfunc = s3c2410_nand_command;
+ chip->write_buf = s3c2410_nand_write_buf;
+ chip->read_buf = s3c2410_nand_read_buf;
+ chip->select_chip = s3c2410_nand_select_chip;
+ chip->chip_delay = 50;
+ chip->priv = nmtd;
+ chip->options = 0;
+ chip->controller = &info->controller;
+
+ nmtd->info = info;
+ nmtd->mtd.priv = chip;
+ nmtd->set = set;
+
+ if (hardware_ecc) {
+ chip->correct_data = s3c2410_nand_correct_data;
+ chip->enable_hwecc = s3c2410_nand_enable_hwecc;
+ chip->calculate_ecc = s3c2410_nand_calculate_ecc;
+ chip->eccmode = NAND_ECC_HW3_512;
+ chip->autooob = &nand_hw_eccoob;
+ } else {
+ chip->eccmode = NAND_ECC_SOFT;
+ }
+}
+
+/* s3c2410_nand_probe
+ *
+ * called by device layer when it finds a device matching
+ * one our driver can handled. This code checks to see if
+ * it can allocate all necessary resources then calls the
+ * nand layer to look for devices
+*/
+
+static int s3c2410_nand_probe(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s3c2410_platform_nand *plat = to_nand_plat(dev);
+ struct s3c2410_nand_info *info;
+ struct s3c2410_nand_mtd *nmtd;
+ struct s3c2410_nand_set *sets;
+ struct resource *res;
+ int err = 0;
+ int size;
+ int nr_sets;
+ int setno;
+
+ pr_debug("s3c2410_nand_probe(%p)\n", dev);
+
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (info == NULL) {
+ printk(KERN_ERR PFX "no memory for flash info\n");
+ err = -ENOMEM;
+ goto exit_error;
+ }
+
+ memzero(info, sizeof(*info));
+ dev_set_drvdata(dev, info);
+
+ spin_lock_init(&info->controller.lock);
+
+ /* get the clock source and enable it */
+
+ info->clk = clk_get(dev, "nand");
+ if (IS_ERR(info->clk)) {
+ printk(KERN_ERR PFX "failed to get clock");
+ err = -ENOENT;
+ goto exit_error;
+ }
+
+ clk_use(info->clk);
+ clk_enable(info->clk);
+
+ /* allocate and map the resource */
+
+ res = pdev->resource; /* assume that the flash has one resource */
+ size = res->end - res->start + 1;
+
+ info->area = request_mem_region(res->start, size, pdev->name);
+
+ if (info->area == NULL) {
+ printk(KERN_ERR PFX "cannot reserve register region\n");
+ err = -ENOENT;
+ goto exit_error;
+ }
+
+ info->device = dev;
+ info->platform = plat;
+ info->regs = ioremap(res->start, size);
+
+ if (info->regs == NULL) {
+ printk(KERN_ERR PFX "cannot reserve register region\n");
+ err = -EIO;
+ goto exit_error;
+ }
+
+ printk(KERN_INFO PFX "mapped registers at %p\n", info->regs);
+
+ /* initialise the hardware */
+
+ err = s3c2410_nand_inithw(info, dev);
+ if (err != 0)
+ goto exit_error;
+
+ sets = (plat != NULL) ? plat->sets : NULL;
+ nr_sets = (plat != NULL) ? plat->nr_sets : 1;
+
+ info->mtd_count = nr_sets;
+
+ /* allocate our information */
+
+ size = nr_sets * sizeof(*info->mtds);
+ info->mtds = kmalloc(size, GFP_KERNEL);
+ if (info->mtds == NULL) {
+ printk(KERN_ERR PFX "failed to allocate mtd storage\n");
+ err = -ENOMEM;
+ goto exit_error;
+ }
+
+ memzero(info->mtds, size);
+
+ /* initialise all possible chips */
+
+ nmtd = info->mtds;
+
+ for (setno = 0; setno < nr_sets; setno++, nmtd++) {
+ pr_debug("initialising set %d (%p, info %p)\n",
+ setno, nmtd, info);
+
+ s3c2410_nand_init_chip(info, nmtd, sets);
+
+ nmtd->scan_res = nand_scan(&nmtd->mtd,
+ (sets) ? sets->nr_chips : 1);
+
+ if (nmtd->scan_res == 0) {
+ s3c2410_nand_add_partition(info, nmtd, sets);
+ }
+
+ if (sets != NULL)
+ sets++;
+ }
+
+ pr_debug("initialised ok\n");
+ return 0;
+
+ exit_error:
+ s3c2410_nand_remove(dev);
+
+ if (err == 0)
+ err = -EINVAL;
+ return err;
+}
+
+static struct device_driver s3c2410_nand_driver = {
+ .name = "s3c2410-nand",
+ .bus = &platform_bus_type,
+ .probe = s3c2410_nand_probe,
+ .remove = s3c2410_nand_remove,
+};
+
+static int __init s3c2410_nand_init(void)
+{
+ printk("S3C2410 NAND Driver, (c) 2004 Simtec Electronics\n");
+ return driver_register(&s3c2410_nand_driver);
+}
+
+static void __exit s3c2410_nand_exit(void)
+{
+ driver_unregister(&s3c2410_nand_driver);
+}
+
+module_init(s3c2410_nand_init);
+module_exit(s3c2410_nand_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("S3C2410 MTD NAND driver");
diff --git a/drivers/mtd/nand/sharpsl.c b/drivers/mtd/nand/sharpsl.c
new file mode 100755
index 000000000000..29572793334c
--- /dev/null
+++ b/drivers/mtd/nand/sharpsl.c
@@ -0,0 +1,260 @@
+/*
+ * drivers/mtd/nand/sharpsl.c
+ *
+ * Copyright (C) 2004 Richard Purdie
+ *
+ * $Id: sharpsl.c,v 1.3 2005/01/03 14:53:50 rpurdie Exp $
+ *
+ * Based on Sharp's NAND driver sharp_sl.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/genhd.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/mtd/partitions.h>
+#include <linux/interrupt.h>
+#include <asm/io.h>
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+
+static void __iomem *sharpsl_io_base;
+static int sharpsl_phys_base = 0x0C000000;
+
+/* register offset */
+#define ECCLPLB sharpsl_io_base+0x00 /* line parity 7 - 0 bit */
+#define ECCLPUB sharpsl_io_base+0x04 /* line parity 15 - 8 bit */
+#define ECCCP sharpsl_io_base+0x08 /* column parity 5 - 0 bit */
+#define ECCCNTR sharpsl_io_base+0x0C /* ECC byte counter */
+#define ECCCLRR sharpsl_io_base+0x10 /* cleare ECC */
+#define FLASHIO sharpsl_io_base+0x14 /* Flash I/O */
+#define FLASHCTL sharpsl_io_base+0x18 /* Flash Control */
+
+/* Flash control bit */
+#define FLRYBY (1 << 5)
+#define FLCE1 (1 << 4)
+#define FLWP (1 << 3)
+#define FLALE (1 << 2)
+#define FLCLE (1 << 1)
+#define FLCE0 (1 << 0)
+
+
+/*
+ * MTD structure for SharpSL
+ */
+static struct mtd_info *sharpsl_mtd = NULL;
+
+/*
+ * Define partitions for flash device
+ */
+#define DEFAULT_NUM_PARTITIONS 3
+
+static int nr_partitions;
+static struct mtd_partition sharpsl_nand_default_partition_info[] = {
+ {
+ .name = "System Area",
+ .offset = 0,
+ .size = 7 * 1024 * 1024,
+ },
+ {
+ .name = "Root Filesystem",
+ .offset = 7 * 1024 * 1024,
+ .size = 30 * 1024 * 1024,
+ },
+ {
+ .name = "Home Filesystem",
+ .offset = MTDPART_OFS_APPEND ,
+ .size = MTDPART_SIZ_FULL ,
+ },
+};
+
+/*
+ * hardware specific access to control-lines
+ */
+static void
+sharpsl_nand_hwcontrol(struct mtd_info* mtd, int cmd)
+{
+ switch (cmd) {
+ case NAND_CTL_SETCLE:
+ writeb(readb(FLASHCTL) | FLCLE, FLASHCTL);
+ break;
+ case NAND_CTL_CLRCLE:
+ writeb(readb(FLASHCTL) & ~FLCLE, FLASHCTL);
+ break;
+
+ case NAND_CTL_SETALE:
+ writeb(readb(FLASHCTL) | FLALE, FLASHCTL);
+ break;
+ case NAND_CTL_CLRALE:
+ writeb(readb(FLASHCTL) & ~FLALE, FLASHCTL);
+ break;
+
+ case NAND_CTL_SETNCE:
+ writeb(readb(FLASHCTL) & ~(FLCE0|FLCE1), FLASHCTL);
+ break;
+ case NAND_CTL_CLRNCE:
+ writeb(readb(FLASHCTL) | (FLCE0|FLCE1), FLASHCTL);
+ break;
+ }
+}
+
+static uint8_t scan_ff_pattern[] = { 0xff, 0xff };
+
+static struct nand_bbt_descr sharpsl_bbt = {
+ .options = 0,
+ .offs = 4,
+ .len = 2,
+ .pattern = scan_ff_pattern
+};
+
+static int
+sharpsl_nand_dev_ready(struct mtd_info* mtd)
+{
+ return !((readb(FLASHCTL) & FLRYBY) == 0);
+}
+
+static void
+sharpsl_nand_enable_hwecc(struct mtd_info* mtd, int mode)
+{
+ writeb(0 ,ECCCLRR);
+}
+
+static int
+sharpsl_nand_calculate_ecc(struct mtd_info* mtd, const u_char* dat,
+ u_char* ecc_code)
+{
+ ecc_code[0] = ~readb(ECCLPUB);
+ ecc_code[1] = ~readb(ECCLPLB);
+ ecc_code[2] = (~readb(ECCCP) << 2) | 0x03;
+ return readb(ECCCNTR) != 0;
+}
+
+
+#ifdef CONFIG_MTD_PARTITIONS
+const char *part_probes[] = { "cmdlinepart", NULL };
+#endif
+
+
+/*
+ * Main initialization routine
+ */
+int __init
+sharpsl_nand_init(void)
+{
+ struct nand_chip *this;
+ struct mtd_partition* sharpsl_partition_info;
+ int err = 0;
+
+ /* Allocate memory for MTD device structure and private data */
+ sharpsl_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip),
+ GFP_KERNEL);
+ if (!sharpsl_mtd) {
+ printk ("Unable to allocate SharpSL NAND MTD device structure.\n");
+ return -ENOMEM;
+ }
+
+ /* map physical adress */
+ sharpsl_io_base = ioremap(sharpsl_phys_base, 0x1000);
+ if(!sharpsl_io_base){
+ printk("ioremap to access Sharp SL NAND chip failed\n");
+ kfree(sharpsl_mtd);
+ return -EIO;
+ }
+
+ /* Get pointer to private data */
+ this = (struct nand_chip *) (&sharpsl_mtd[1]);
+
+ /* Initialize structures */
+ memset((char *) sharpsl_mtd, 0, sizeof(struct mtd_info));
+ memset((char *) this, 0, sizeof(struct nand_chip));
+
+ /* Link the private data with the MTD structure */
+ sharpsl_mtd->priv = this;
+
+ /*
+ * PXA initialize
+ */
+ writeb(readb(FLASHCTL) | FLWP, FLASHCTL);
+
+ /* Set address of NAND IO lines */
+ this->IO_ADDR_R = FLASHIO;
+ this->IO_ADDR_W = FLASHIO;
+ /* Set address of hardware control function */
+ this->hwcontrol = sharpsl_nand_hwcontrol;
+ this->dev_ready = sharpsl_nand_dev_ready;
+ /* 15 us command delay time */
+ this->chip_delay = 15;
+ /* set eccmode using hardware ECC */
+ this->eccmode = NAND_ECC_HW3_256;
+ this->enable_hwecc = sharpsl_nand_enable_hwecc;
+ this->calculate_ecc = sharpsl_nand_calculate_ecc;
+ this->correct_data = nand_correct_data;
+ this->badblock_pattern = &sharpsl_bbt;
+
+ /* Scan to find existence of the device */
+ err=nand_scan(sharpsl_mtd,1);
+ if (err) {
+ iounmap(sharpsl_io_base);
+ kfree(sharpsl_mtd);
+ return err;
+ }
+
+ /* Register the partitions */
+ sharpsl_mtd->name = "sharpsl-nand";
+ nr_partitions = parse_mtd_partitions(sharpsl_mtd, part_probes,
+ &sharpsl_partition_info, 0);
+
+ if (nr_partitions <= 0) {
+ nr_partitions = DEFAULT_NUM_PARTITIONS;
+ sharpsl_partition_info = sharpsl_nand_default_partition_info;
+ if (machine_is_poodle()) {
+ sharpsl_partition_info[1].size=22 * 1024 * 1024;
+ } else if (machine_is_corgi() || machine_is_shepherd()) {
+ sharpsl_partition_info[1].size=25 * 1024 * 1024;
+ } else if (machine_is_husky()) {
+ sharpsl_partition_info[1].size=53 * 1024 * 1024;
+ }
+ }
+
+ if (machine_is_husky()) {
+ /* Need to use small eraseblock size for backward compatibility */
+ sharpsl_mtd->flags |= MTD_NO_VIRTBLOCKS;
+ }
+
+ add_mtd_partitions(sharpsl_mtd, sharpsl_partition_info, nr_partitions);
+
+ /* Return happy */
+ return 0;
+}
+module_init(sharpsl_nand_init);
+
+/*
+ * Clean up routine
+ */
+#ifdef MODULE
+static void __exit sharpsl_nand_cleanup(void)
+{
+ struct nand_chip *this = (struct nand_chip *) &sharpsl_mtd[1];
+
+ /* Release resources, unregister device */
+ nand_release(sharpsl_mtd);
+
+ iounmap(sharpsl_io_base);
+
+ /* Free the MTD device structure */
+ kfree(sharpsl_mtd);
+}
+module_exit(sharpsl_nand_cleanup);
+#endif
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>");
+MODULE_DESCRIPTION("Device specific logic for NAND flash on Sharp SL-C7xx Series");
diff --git a/drivers/mtd/nand/spia.c b/drivers/mtd/nand/spia.c
new file mode 100644
index 000000000000..b777c412b758
--- /dev/null
+++ b/drivers/mtd/nand/spia.c
@@ -0,0 +1,173 @@
+/*
+ * drivers/mtd/nand/spia.c
+ *
+ * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com)
+ *
+ *
+ * 10-29-2001 TG change to support hardwarespecific access
+ * to controllines (due to change in nand.c)
+ * page_cache added
+ *
+ * $Id: spia.c,v 1.24 2004/11/04 12:53:10 gleixner Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Overview:
+ * This is a device driver for the NAND flash device found on the
+ * SPIA board which utilizes the Toshiba TC58V64AFT part. This is
+ * a 64Mibit (8MiB x 8 bits) NAND flash device.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <asm/io.h>
+
+/*
+ * MTD structure for SPIA board
+ */
+static struct mtd_info *spia_mtd = NULL;
+
+/*
+ * Values specific to the SPIA board (used with EP7212 processor)
+ */
+#define SPIA_IO_BASE 0xd0000000 /* Start of EP7212 IO address space */
+#define SPIA_FIO_BASE 0xf0000000 /* Address where flash is mapped */
+#define SPIA_PEDR 0x0080 /*
+ * IO offset to Port E data register
+ * where the CLE, ALE and NCE pins
+ * are wired to.
+ */
+#define SPIA_PEDDR 0x00c0 /*
+ * IO offset to Port E data direction
+ * register so we can control the IO
+ * lines.
+ */
+
+/*
+ * Module stuff
+ */
+
+static int spia_io_base = SPIA_IO_BASE;
+static int spia_fio_base = SPIA_FIO_BASE;
+static int spia_pedr = SPIA_PEDR;
+static int spia_peddr = SPIA_PEDDR;
+
+module_param(spia_io_base, int, 0);
+module_param(spia_fio_base, int, 0);
+module_param(spia_pedr, int, 0);
+module_param(spia_peddr, int, 0);
+
+/*
+ * Define partitions for flash device
+ */
+const static struct mtd_partition partition_info[] = {
+ {
+ .name = "SPIA flash partition 1",
+ .offset = 0,
+ .size = 2*1024*1024
+ },
+ {
+ .name = "SPIA flash partition 2",
+ .offset = 2*1024*1024,
+ .size = 6*1024*1024
+ }
+};
+#define NUM_PARTITIONS 2
+
+
+/*
+ * hardware specific access to control-lines
+*/
+static void spia_hwcontrol(struct mtd_info *mtd, int cmd){
+
+ switch(cmd){
+
+ case NAND_CTL_SETCLE: (*(volatile unsigned char *) (spia_io_base + spia_pedr)) |= 0x01; break;
+ case NAND_CTL_CLRCLE: (*(volatile unsigned char *) (spia_io_base + spia_pedr)) &= ~0x01; break;
+
+ case NAND_CTL_SETALE: (*(volatile unsigned char *) (spia_io_base + spia_pedr)) |= 0x02; break;
+ case NAND_CTL_CLRALE: (*(volatile unsigned char *) (spia_io_base + spia_pedr)) &= ~0x02; break;
+
+ case NAND_CTL_SETNCE: (*(volatile unsigned char *) (spia_io_base + spia_pedr)) &= ~0x04; break;
+ case NAND_CTL_CLRNCE: (*(volatile unsigned char *) (spia_io_base + spia_pedr)) |= 0x04; break;
+ }
+}
+
+/*
+ * Main initialization routine
+ */
+int __init spia_init (void)
+{
+ struct nand_chip *this;
+
+ /* Allocate memory for MTD device structure and private data */
+ spia_mtd = kmalloc (sizeof(struct mtd_info) + sizeof (struct nand_chip),
+ GFP_KERNEL);
+ if (!spia_mtd) {
+ printk ("Unable to allocate SPIA NAND MTD device structure.\n");
+ return -ENOMEM;
+ }
+
+ /* Get pointer to private data */
+ this = (struct nand_chip *) (&spia_mtd[1]);
+
+ /* Initialize structures */
+ memset((char *) spia_mtd, 0, sizeof(struct mtd_info));
+ memset((char *) this, 0, sizeof(struct nand_chip));
+
+ /* Link the private data with the MTD structure */
+ spia_mtd->priv = this;
+
+ /*
+ * Set GPIO Port E control register so that the pins are configured
+ * to be outputs for controlling the NAND flash.
+ */
+ (*(volatile unsigned char *) (spia_io_base + spia_peddr)) = 0x07;
+
+ /* Set address of NAND IO lines */
+ this->IO_ADDR_R = (void __iomem *) spia_fio_base;
+ this->IO_ADDR_W = (void __iomem *) spia_fio_base;
+ /* Set address of hardware control function */
+ this->hwcontrol = spia_hwcontrol;
+ /* 15 us command delay time */
+ this->chip_delay = 15;
+
+ /* Scan to find existence of the device */
+ if (nand_scan (spia_mtd, 1)) {
+ kfree (spia_mtd);
+ return -ENXIO;
+ }
+
+ /* Register the partitions */
+ add_mtd_partitions(spia_mtd, partition_info, NUM_PARTITIONS);
+
+ /* Return happy */
+ return 0;
+}
+module_init(spia_init);
+
+/*
+ * Clean up routine
+ */
+#ifdef MODULE
+static void __exit spia_cleanup (void)
+{
+ /* Release resources, unregister device */
+ nand_release (spia_mtd);
+
+ /* Free the MTD device structure */
+ kfree (spia_mtd);
+}
+module_exit(spia_cleanup);
+#endif
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Steven J. Hill <sjhill@realitydiluted.com");
+MODULE_DESCRIPTION("Board-specific glue layer for NAND flash on SPIA board");
diff --git a/drivers/mtd/nand/toto.c b/drivers/mtd/nand/toto.c
new file mode 100644
index 000000000000..52c808fb5fa9
--- /dev/null
+++ b/drivers/mtd/nand/toto.c
@@ -0,0 +1,205 @@
+/*
+ * drivers/mtd/nand/toto.c
+ *
+ * Copyright (c) 2003 Texas Instruments
+ *
+ * Derived from drivers/mtd/autcpu12.c
+ *
+ * Copyright (c) 2002 Thomas Gleixner <tgxl@linutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Overview:
+ * This is a device driver for the NAND flash device found on the
+ * TI fido board. It supports 32MiB and 64MiB cards
+ *
+ * $Id: toto.c,v 1.4 2004/10/05 13:50:20 gleixner Exp $
+ */
+
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <asm/io.h>
+#include <asm/arch/hardware.h>
+#include <asm/sizes.h>
+#include <asm/arch/toto.h>
+#include <asm/arch-omap1510/hardware.h>
+#include <asm/arch/gpio.h>
+
+/*
+ * MTD structure for TOTO board
+ */
+static struct mtd_info *toto_mtd = NULL;
+
+static unsigned long toto_io_base = OMAP_FLASH_1_BASE;
+
+#define CONFIG_NAND_WORKAROUND 1
+
+#define NAND_NCE 0x4000
+#define NAND_CLE 0x1000
+#define NAND_ALE 0x0002
+#define NAND_MASK (NAND_CLE | NAND_ALE | NAND_NCE)
+
+#define T_NAND_CTL_CLRALE(iob) gpiosetout(NAND_ALE, 0)
+#define T_NAND_CTL_SETALE(iob) gpiosetout(NAND_ALE, NAND_ALE)
+#ifdef CONFIG_NAND_WORKAROUND /* "some" dev boards busted, blue wired to rts2 :( */
+#define T_NAND_CTL_CLRCLE(iob) gpiosetout(NAND_CLE, 0); rts2setout(2, 2)
+#define T_NAND_CTL_SETCLE(iob) gpiosetout(NAND_CLE, NAND_CLE); rts2setout(2, 0)
+#else
+#define T_NAND_CTL_CLRCLE(iob) gpiosetout(NAND_CLE, 0)
+#define T_NAND_CTL_SETCLE(iob) gpiosetout(NAND_CLE, NAND_CLE)
+#endif
+#define T_NAND_CTL_SETNCE(iob) gpiosetout(NAND_NCE, 0)
+#define T_NAND_CTL_CLRNCE(iob) gpiosetout(NAND_NCE, NAND_NCE)
+
+/*
+ * Define partitions for flash devices
+ */
+
+static struct mtd_partition partition_info64M[] = {
+ { .name = "toto kernel partition 1",
+ .offset = 0,
+ .size = 2 * SZ_1M },
+ { .name = "toto file sys partition 2",
+ .offset = 2 * SZ_1M,
+ .size = 14 * SZ_1M },
+ { .name = "toto user partition 3",
+ .offset = 16 * SZ_1M,
+ .size = 16 * SZ_1M },
+ { .name = "toto devboard extra partition 4",
+ .offset = 32 * SZ_1M,
+ .size = 32 * SZ_1M },
+};
+
+static struct mtd_partition partition_info32M[] = {
+ { .name = "toto kernel partition 1",
+ .offset = 0,
+ .size = 2 * SZ_1M },
+ { .name = "toto file sys partition 2",
+ .offset = 2 * SZ_1M,
+ .size = 14 * SZ_1M },
+ { .name = "toto user partition 3",
+ .offset = 16 * SZ_1M,
+ .size = 16 * SZ_1M },
+};
+
+#define NUM_PARTITIONS32M 3
+#define NUM_PARTITIONS64M 4
+/*
+ * hardware specific access to control-lines
+*/
+
+static void toto_hwcontrol(struct mtd_info *mtd, int cmd)
+{
+
+ udelay(1); /* hopefully enough time for tc make proceding write to clear */
+ switch(cmd){
+
+ case NAND_CTL_SETCLE: T_NAND_CTL_SETCLE(cmd); break;
+ case NAND_CTL_CLRCLE: T_NAND_CTL_CLRCLE(cmd); break;
+
+ case NAND_CTL_SETALE: T_NAND_CTL_SETALE(cmd); break;
+ case NAND_CTL_CLRALE: T_NAND_CTL_CLRALE(cmd); break;
+
+ case NAND_CTL_SETNCE: T_NAND_CTL_SETNCE(cmd); break;
+ case NAND_CTL_CLRNCE: T_NAND_CTL_CLRNCE(cmd); break;
+ }
+ udelay(1); /* allow time to ensure gpio state to over take memory write */
+}
+
+/*
+ * Main initialization routine
+ */
+int __init toto_init (void)
+{
+ struct nand_chip *this;
+ int err = 0;
+
+ /* Allocate memory for MTD device structure and private data */
+ toto_mtd = kmalloc (sizeof(struct mtd_info) + sizeof (struct nand_chip),
+ GFP_KERNEL);
+ if (!toto_mtd) {
+ printk (KERN_WARNING "Unable to allocate toto NAND MTD device structure.\n");
+ err = -ENOMEM;
+ goto out;
+ }
+
+ /* Get pointer to private data */
+ this = (struct nand_chip *) (&toto_mtd[1]);
+
+ /* Initialize structures */
+ memset((char *) toto_mtd, 0, sizeof(struct mtd_info));
+ memset((char *) this, 0, sizeof(struct nand_chip));
+
+ /* Link the private data with the MTD structure */
+ toto_mtd->priv = this;
+
+ /* Set address of NAND IO lines */
+ this->IO_ADDR_R = toto_io_base;
+ this->IO_ADDR_W = toto_io_base;
+ this->hwcontrol = toto_hwcontrol;
+ this->dev_ready = NULL;
+ /* 25 us command delay time */
+ this->chip_delay = 30;
+ this->eccmode = NAND_ECC_SOFT;
+
+ /* Scan to find existance of the device */
+ if (nand_scan (toto_mtd, 1)) {
+ err = -ENXIO;
+ goto out_mtd;
+ }
+
+ /* Register the partitions */
+ switch(toto_mtd->size){
+ case SZ_64M: add_mtd_partitions(toto_mtd, partition_info64M, NUM_PARTITIONS64M); break;
+ case SZ_32M: add_mtd_partitions(toto_mtd, partition_info32M, NUM_PARTITIONS32M); break;
+ default: {
+ printk (KERN_WARNING "Unsupported Nand device\n");
+ err = -ENXIO;
+ goto out_buf;
+ }
+ }
+
+ gpioreserve(NAND_MASK); /* claim our gpios */
+ archflashwp(0,0); /* open up flash for writing */
+
+ goto out;
+
+out_buf:
+ kfree (this->data_buf);
+out_mtd:
+ kfree (toto_mtd);
+out:
+ return err;
+}
+
+module_init(toto_init);
+
+/*
+ * Clean up routine
+ */
+static void __exit toto_cleanup (void)
+{
+ /* Release resources, unregister device */
+ nand_release (toto_mtd);
+
+ /* Free the MTD device structure */
+ kfree (toto_mtd);
+
+ /* stop flash writes */
+ archflashwp(0,1);
+
+ /* release gpios to system */
+ gpiorelease(NAND_MASK);
+}
+module_exit(toto_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Richard Woodruff <r-woodruff2@ti.com>");
+MODULE_DESCRIPTION("Glue layer for NAND flash on toto board");
diff --git a/drivers/mtd/nand/tx4925ndfmc.c b/drivers/mtd/nand/tx4925ndfmc.c
new file mode 100644
index 000000000000..bba688830c9b
--- /dev/null
+++ b/drivers/mtd/nand/tx4925ndfmc.c
@@ -0,0 +1,416 @@
+/*
+ * drivers/mtd/tx4925ndfmc.c
+ *
+ * Overview:
+ * This is a device driver for the NAND flash device found on the
+ * Toshiba RBTX4925 reference board, which is a SmartMediaCard. It supports
+ * 16MiB, 32MiB and 64MiB cards.
+ *
+ * Author: MontaVista Software, Inc. source@mvista.com
+ *
+ * Derived from drivers/mtd/autcpu12.c
+ * Copyright (c) 2001 Thomas Gleixner (gleixner@autronix.de)
+ *
+ * $Id: tx4925ndfmc.c,v 1.5 2004/10/05 13:50:20 gleixner Exp $
+ *
+ * Copyright (C) 2001 Toshiba Corporation
+ *
+ * 2003 (c) MontaVista Software, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+#include <asm/tx4925/tx4925_nand.h>
+
+extern struct nand_oobinfo jffs2_oobinfo;
+
+/*
+ * MTD structure for RBTX4925 board
+ */
+static struct mtd_info *tx4925ndfmc_mtd = NULL;
+
+/*
+ * Define partitions for flash devices
+ */
+
+static struct mtd_partition partition_info16k[] = {
+ { .name = "RBTX4925 flash partition 1",
+ .offset = 0,
+ .size = 8 * 0x00100000 },
+ { .name = "RBTX4925 flash partition 2",
+ .offset = 8 * 0x00100000,
+ .size = 8 * 0x00100000 },
+};
+
+static struct mtd_partition partition_info32k[] = {
+ { .name = "RBTX4925 flash partition 1",
+ .offset = 0,
+ .size = 8 * 0x00100000 },
+ { .name = "RBTX4925 flash partition 2",
+ .offset = 8 * 0x00100000,
+ .size = 24 * 0x00100000 },
+};
+
+static struct mtd_partition partition_info64k[] = {
+ { .name = "User FS",
+ .offset = 0,
+ .size = 16 * 0x00100000 },
+ { .name = "RBTX4925 flash partition 2",
+ .offset = 16 * 0x00100000,
+ .size = 48 * 0x00100000},
+};
+
+static struct mtd_partition partition_info128k[] = {
+ { .name = "Skip bad section",
+ .offset = 0,
+ .size = 16 * 0x00100000 },
+ { .name = "User FS",
+ .offset = 16 * 0x00100000,
+ .size = 112 * 0x00100000 },
+};
+#define NUM_PARTITIONS16K 2
+#define NUM_PARTITIONS32K 2
+#define NUM_PARTITIONS64K 2
+#define NUM_PARTITIONS128K 2
+
+/*
+ * hardware specific access to control-lines
+*/
+static void tx4925ndfmc_hwcontrol(struct mtd_info *mtd, int cmd)
+{
+
+ switch(cmd){
+
+ case NAND_CTL_SETCLE:
+ tx4925_ndfmcptr->mcr |= TX4925_NDFMCR_CLE;
+ break;
+ case NAND_CTL_CLRCLE:
+ tx4925_ndfmcptr->mcr &= ~TX4925_NDFMCR_CLE;
+ break;
+ case NAND_CTL_SETALE:
+ tx4925_ndfmcptr->mcr |= TX4925_NDFMCR_ALE;
+ break;
+ case NAND_CTL_CLRALE:
+ tx4925_ndfmcptr->mcr &= ~TX4925_NDFMCR_ALE;
+ break;
+ case NAND_CTL_SETNCE:
+ tx4925_ndfmcptr->mcr |= TX4925_NDFMCR_CE;
+ break;
+ case NAND_CTL_CLRNCE:
+ tx4925_ndfmcptr->mcr &= ~TX4925_NDFMCR_CE;
+ break;
+ case NAND_CTL_SETWP:
+ tx4925_ndfmcptr->mcr |= TX4925_NDFMCR_WE;
+ break;
+ case NAND_CTL_CLRWP:
+ tx4925_ndfmcptr->mcr &= ~TX4925_NDFMCR_WE;
+ break;
+ }
+}
+
+/*
+* read device ready pin
+*/
+static int tx4925ndfmc_device_ready(struct mtd_info *mtd)
+{
+ int ready;
+ ready = (tx4925_ndfmcptr->sr & TX4925_NDSFR_BUSY) ? 0 : 1;
+ return ready;
+}
+void tx4925ndfmc_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+ /* reset first */
+ tx4925_ndfmcptr->mcr |= TX4925_NDFMCR_ECC_CNTL_MASK;
+ tx4925_ndfmcptr->mcr &= ~TX4925_NDFMCR_ECC_CNTL_MASK;
+ tx4925_ndfmcptr->mcr |= TX4925_NDFMCR_ECC_CNTL_ENAB;
+}
+static void tx4925ndfmc_disable_ecc(void)
+{
+ tx4925_ndfmcptr->mcr &= ~TX4925_NDFMCR_ECC_CNTL_MASK;
+}
+static void tx4925ndfmc_enable_read_ecc(void)
+{
+ tx4925_ndfmcptr->mcr &= ~TX4925_NDFMCR_ECC_CNTL_MASK;
+ tx4925_ndfmcptr->mcr |= TX4925_NDFMCR_ECC_CNTL_READ;
+}
+void tx4925ndfmc_readecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code){
+ int i;
+ u_char *ecc = ecc_code;
+ tx4925ndfmc_enable_read_ecc();
+ for (i = 0;i < 6;i++,ecc++)
+ *ecc = tx4925_read_nfmc(&(tx4925_ndfmcptr->dtr));
+ tx4925ndfmc_disable_ecc();
+}
+void tx4925ndfmc_device_setup(void)
+{
+
+ *(unsigned char *)0xbb005000 &= ~0x08;
+
+ /* reset NDFMC */
+ tx4925_ndfmcptr->rstr |= TX4925_NDFRSTR_RST;
+ while (tx4925_ndfmcptr->rstr & TX4925_NDFRSTR_RST);
+
+ /* setup BusSeparete, Hold Time, Strobe Pulse Width */
+ tx4925_ndfmcptr->mcr = TX4925_BSPRT ? TX4925_NDFMCR_BSPRT : 0;
+ tx4925_ndfmcptr->spr = TX4925_HOLD << 4 | TX4925_SPW;
+}
+static u_char tx4925ndfmc_nand_read_byte(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ return tx4925_read_nfmc(this->IO_ADDR_R);
+}
+
+static void tx4925ndfmc_nand_write_byte(struct mtd_info *mtd, u_char byte)
+{
+ struct nand_chip *this = mtd->priv;
+ tx4925_write_nfmc(byte, this->IO_ADDR_W);
+}
+
+static void tx4925ndfmc_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+
+ for (i=0; i<len; i++)
+ tx4925_write_nfmc(buf[i], this->IO_ADDR_W);
+}
+
+static void tx4925ndfmc_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+
+ for (i=0; i<len; i++)
+ buf[i] = tx4925_read_nfmc(this->IO_ADDR_R);
+}
+
+static int tx4925ndfmc_nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+
+ for (i=0; i<len; i++)
+ if (buf[i] != tx4925_read_nfmc(this->IO_ADDR_R))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * Send command to NAND device
+ */
+static void tx4925ndfmc_nand_command (struct mtd_info *mtd, unsigned command, int column, int page_addr)
+{
+ register struct nand_chip *this = mtd->priv;
+
+ /* Begin command latch cycle */
+ this->hwcontrol(mtd, NAND_CTL_SETCLE);
+ /*
+ * Write out the command to the device.
+ */
+ if (command == NAND_CMD_SEQIN) {
+ int readcmd;
+
+ if (column >= mtd->oobblock) {
+ /* OOB area */
+ column -= mtd->oobblock;
+ readcmd = NAND_CMD_READOOB;
+ } else if (column < 256) {
+ /* First 256 bytes --> READ0 */
+ readcmd = NAND_CMD_READ0;
+ } else {
+ column -= 256;
+ readcmd = NAND_CMD_READ1;
+ }
+ this->write_byte(mtd, readcmd);
+ }
+ this->write_byte(mtd, command);
+
+ /* Set ALE and clear CLE to start address cycle */
+ this->hwcontrol(mtd, NAND_CTL_CLRCLE);
+
+ if (column != -1 || page_addr != -1) {
+ this->hwcontrol(mtd, NAND_CTL_SETALE);
+
+ /* Serially input address */
+ if (column != -1)
+ this->write_byte(mtd, column);
+ if (page_addr != -1) {
+ this->write_byte(mtd, (unsigned char) (page_addr & 0xff));
+ this->write_byte(mtd, (unsigned char) ((page_addr >> 8) & 0xff));
+ /* One more address cycle for higher density devices */
+ if (mtd->size & 0x0c000000)
+ this->write_byte(mtd, (unsigned char) ((page_addr >> 16) & 0x0f));
+ }
+ /* Latch in address */
+ this->hwcontrol(mtd, NAND_CTL_CLRALE);
+ }
+
+ /*
+ * program and erase have their own busy handlers
+ * status and sequential in needs no delay
+ */
+ switch (command) {
+
+ case NAND_CMD_PAGEPROG:
+ /* Turn off WE */
+ this->hwcontrol (mtd, NAND_CTL_CLRWP);
+ return;
+
+ case NAND_CMD_SEQIN:
+ /* Turn on WE */
+ this->hwcontrol (mtd, NAND_CTL_SETWP);
+ return;
+
+ case NAND_CMD_ERASE1:
+ case NAND_CMD_ERASE2:
+ case NAND_CMD_STATUS:
+ return;
+
+ case NAND_CMD_RESET:
+ if (this->dev_ready)
+ break;
+ this->hwcontrol(mtd, NAND_CTL_SETCLE);
+ this->write_byte(mtd, NAND_CMD_STATUS);
+ this->hwcontrol(mtd, NAND_CTL_CLRCLE);
+ while ( !(this->read_byte(mtd) & 0x40));
+ return;
+
+ /* This applies to read commands */
+ default:
+ /*
+ * If we don't have access to the busy pin, we apply the given
+ * command delay
+ */
+ if (!this->dev_ready) {
+ udelay (this->chip_delay);
+ return;
+ }
+ }
+
+ /* wait until command is processed */
+ while (!this->dev_ready(mtd));
+}
+
+#ifdef CONFIG_MTD_CMDLINE_PARTS
+extern int parse_cmdline_partitions(struct mtd_info *master, struct mtd_partitio
+n **pparts, char *);
+#endif
+
+/*
+ * Main initialization routine
+ */
+extern int nand_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc);
+int __init tx4925ndfmc_init (void)
+{
+ struct nand_chip *this;
+ int err = 0;
+
+ /* Allocate memory for MTD device structure and private data */
+ tx4925ndfmc_mtd = kmalloc (sizeof(struct mtd_info) + sizeof (struct nand_chip),
+ GFP_KERNEL);
+ if (!tx4925ndfmc_mtd) {
+ printk ("Unable to allocate RBTX4925 NAND MTD device structure.\n");
+ err = -ENOMEM;
+ goto out;
+ }
+
+ tx4925ndfmc_device_setup();
+
+ /* io is indirect via a register so don't need to ioremap address */
+
+ /* Get pointer to private data */
+ this = (struct nand_chip *) (&tx4925ndfmc_mtd[1]);
+
+ /* Initialize structures */
+ memset((char *) tx4925ndfmc_mtd, 0, sizeof(struct mtd_info));
+ memset((char *) this, 0, sizeof(struct nand_chip));
+
+ /* Link the private data with the MTD structure */
+ tx4925ndfmc_mtd->priv = this;
+
+ /* Set address of NAND IO lines */
+ this->IO_ADDR_R = (void __iomem *)&(tx4925_ndfmcptr->dtr);
+ this->IO_ADDR_W = (void __iomem *)&(tx4925_ndfmcptr->dtr);
+ this->hwcontrol = tx4925ndfmc_hwcontrol;
+ this->enable_hwecc = tx4925ndfmc_enable_hwecc;
+ this->calculate_ecc = tx4925ndfmc_readecc;
+ this->correct_data = nand_correct_data;
+ this->eccmode = NAND_ECC_HW6_512;
+ this->dev_ready = tx4925ndfmc_device_ready;
+ /* 20 us command delay time */
+ this->chip_delay = 20;
+ this->read_byte = tx4925ndfmc_nand_read_byte;
+ this->write_byte = tx4925ndfmc_nand_write_byte;
+ this->cmdfunc = tx4925ndfmc_nand_command;
+ this->write_buf = tx4925ndfmc_nand_write_buf;
+ this->read_buf = tx4925ndfmc_nand_read_buf;
+ this->verify_buf = tx4925ndfmc_nand_verify_buf;
+
+ /* Scan to find existance of the device */
+ if (nand_scan (tx4925ndfmc_mtd, 1)) {
+ err = -ENXIO;
+ goto out_ior;
+ }
+
+ /* Register the partitions */
+#ifdef CONFIG_MTD_CMDLINE_PARTS
+ {
+ int mtd_parts_nb = 0;
+ struct mtd_partition *mtd_parts = 0;
+ mtd_parts_nb = parse_cmdline_partitions(tx4925ndfmc_mtd, &mtd_parts, "tx4925ndfmc");
+ if (mtd_parts_nb > 0)
+ add_mtd_partitions(tx4925ndfmc_mtd, mtd_parts, mtd_parts_nb);
+ else
+ add_mtd_device(tx4925ndfmc_mtd);
+ }
+#else /* ifdef CONFIG_MTD_CMDLINE_PARTS */
+ switch(tx4925ndfmc_mtd->size){
+ case 0x01000000: add_mtd_partitions(tx4925ndfmc_mtd, partition_info16k, NUM_PARTITIONS16K); break;
+ case 0x02000000: add_mtd_partitions(tx4925ndfmc_mtd, partition_info32k, NUM_PARTITIONS32K); break;
+ case 0x04000000: add_mtd_partitions(tx4925ndfmc_mtd, partition_info64k, NUM_PARTITIONS64K); break;
+ case 0x08000000: add_mtd_partitions(tx4925ndfmc_mtd, partition_info128k, NUM_PARTITIONS128K); break;
+ default: {
+ printk ("Unsupported SmartMedia device\n");
+ err = -ENXIO;
+ goto out_ior;
+ }
+ }
+#endif /* ifdef CONFIG_MTD_CMDLINE_PARTS */
+ goto out;
+
+out_ior:
+out:
+ return err;
+}
+
+module_init(tx4925ndfmc_init);
+
+/*
+ * Clean up routine
+ */
+#ifdef MODULE
+static void __exit tx4925ndfmc_cleanup (void)
+{
+ /* Release resources, unregister device */
+ nand_release (tx4925ndfmc_mtd);
+
+ /* Free the MTD device structure */
+ kfree (tx4925ndfmc_mtd);
+}
+module_exit(tx4925ndfmc_cleanup);
+#endif
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alice Hennessy <ahennessy@mvista.com>");
+MODULE_DESCRIPTION("Glue layer for SmartMediaCard on Toshiba RBTX4925");
diff --git a/drivers/mtd/nand/tx4938ndfmc.c b/drivers/mtd/nand/tx4938ndfmc.c
new file mode 100644
index 000000000000..df26e58820b3
--- /dev/null
+++ b/drivers/mtd/nand/tx4938ndfmc.c
@@ -0,0 +1,406 @@
+/*
+ * drivers/mtd/nand/tx4938ndfmc.c
+ *
+ * Overview:
+ * This is a device driver for the NAND flash device connected to
+ * TX4938 internal NAND Memory Controller.
+ * TX4938 NDFMC is almost same as TX4925 NDFMC, but register size are 64 bit.
+ *
+ * Author: source@mvista.com
+ *
+ * Based on spia.c by Steven J. Hill
+ *
+ * $Id: tx4938ndfmc.c,v 1.4 2004/10/05 13:50:20 gleixner Exp $
+ *
+ * Copyright (C) 2000-2001 Toshiba Corporation
+ *
+ * 2003 (c) MontaVista Software, Inc. This file is licensed under the
+ * terms of the GNU General Public License version 2. This program is
+ * licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+#include <linux/config.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/mtd/partitions.h>
+#include <asm/io.h>
+#include <asm/bootinfo.h>
+#include <linux/delay.h>
+#include <asm/tx4938/rbtx4938.h>
+
+extern struct nand_oobinfo jffs2_oobinfo;
+
+/*
+ * MTD structure for TX4938 NDFMC
+ */
+static struct mtd_info *tx4938ndfmc_mtd;
+
+/*
+ * Define partitions for flash device
+ */
+#define flush_wb() (void)tx4938_ndfmcptr->mcr;
+
+#define NUM_PARTITIONS 3
+#define NUMBER_OF_CIS_BLOCKS 24
+#define SIZE_OF_BLOCK 0x00004000
+#define NUMBER_OF_BLOCK_PER_ZONE 1024
+#define SIZE_OF_ZONE (NUMBER_OF_BLOCK_PER_ZONE * SIZE_OF_BLOCK)
+#ifndef CONFIG_MTD_CMDLINE_PARTS
+/*
+ * You can use the following sample of MTD partitions
+ * on the NAND Flash Memory 32MB or more.
+ *
+ * The following figure shows the image of the sample partition on
+ * the 32MB NAND Flash Memory.
+ *
+ * Block No.
+ * 0 +-----------------------------+ ------
+ * | CIS | ^
+ * 24 +-----------------------------+ |
+ * | kernel image | | Zone 0
+ * | | |
+ * +-----------------------------+ |
+ * 1023 | unused area | v
+ * +-----------------------------+ ------
+ * 1024 | JFFS2 | ^
+ * | | |
+ * | | | Zone 1
+ * | | |
+ * | | |
+ * | | v
+ * 2047 +-----------------------------+ ------
+ *
+ */
+static struct mtd_partition partition_info[NUM_PARTITIONS] = {
+ {
+ .name = "RBTX4938 CIS Area",
+ .offset = 0,
+ .size = (NUMBER_OF_CIS_BLOCKS * SIZE_OF_BLOCK),
+ .mask_flags = MTD_WRITEABLE /* This partition is NOT writable */
+ },
+ {
+ .name = "RBTX4938 kernel image",
+ .offset = MTDPART_OFS_APPEND,
+ .size = 8 * 0x00100000, /* 8MB (Depends on size of kernel image) */
+ .mask_flags = MTD_WRITEABLE /* This partition is NOT writable */
+ },
+ {
+ .name = "Root FS (JFFS2)",
+ .offset = (0 + SIZE_OF_ZONE), /* start address of next zone */
+ .size = MTDPART_SIZ_FULL
+ },
+};
+#endif
+
+static void tx4938ndfmc_hwcontrol(struct mtd_info *mtd, int cmd)
+{
+ switch (cmd) {
+ case NAND_CTL_SETCLE:
+ tx4938_ndfmcptr->mcr |= TX4938_NDFMCR_CLE;
+ break;
+ case NAND_CTL_CLRCLE:
+ tx4938_ndfmcptr->mcr &= ~TX4938_NDFMCR_CLE;
+ break;
+ case NAND_CTL_SETALE:
+ tx4938_ndfmcptr->mcr |= TX4938_NDFMCR_ALE;
+ break;
+ case NAND_CTL_CLRALE:
+ tx4938_ndfmcptr->mcr &= ~TX4938_NDFMCR_ALE;
+ break;
+ /* TX4938_NDFMCR_CE bit is 0:high 1:low */
+ case NAND_CTL_SETNCE:
+ tx4938_ndfmcptr->mcr |= TX4938_NDFMCR_CE;
+ break;
+ case NAND_CTL_CLRNCE:
+ tx4938_ndfmcptr->mcr &= ~TX4938_NDFMCR_CE;
+ break;
+ case NAND_CTL_SETWP:
+ tx4938_ndfmcptr->mcr |= TX4938_NDFMCR_WE;
+ break;
+ case NAND_CTL_CLRWP:
+ tx4938_ndfmcptr->mcr &= ~TX4938_NDFMCR_WE;
+ break;
+ }
+}
+static int tx4938ndfmc_dev_ready(struct mtd_info *mtd)
+{
+ flush_wb();
+ return !(tx4938_ndfmcptr->sr & TX4938_NDFSR_BUSY);
+}
+static void tx4938ndfmc_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)
+{
+ u32 mcr = tx4938_ndfmcptr->mcr;
+ mcr &= ~TX4938_NDFMCR_ECC_ALL;
+ tx4938_ndfmcptr->mcr = mcr | TX4938_NDFMCR_ECC_OFF;
+ tx4938_ndfmcptr->mcr = mcr | TX4938_NDFMCR_ECC_READ;
+ ecc_code[1] = tx4938_ndfmcptr->dtr;
+ ecc_code[0] = tx4938_ndfmcptr->dtr;
+ ecc_code[2] = tx4938_ndfmcptr->dtr;
+ tx4938_ndfmcptr->mcr = mcr | TX4938_NDFMCR_ECC_OFF;
+}
+static void tx4938ndfmc_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+ u32 mcr = tx4938_ndfmcptr->mcr;
+ mcr &= ~TX4938_NDFMCR_ECC_ALL;
+ tx4938_ndfmcptr->mcr = mcr | TX4938_NDFMCR_ECC_RESET;
+ tx4938_ndfmcptr->mcr = mcr | TX4938_NDFMCR_ECC_OFF;
+ tx4938_ndfmcptr->mcr = mcr | TX4938_NDFMCR_ECC_ON;
+}
+
+static u_char tx4938ndfmc_nand_read_byte(struct mtd_info *mtd)
+{
+ struct nand_chip *this = mtd->priv;
+ return tx4938_read_nfmc(this->IO_ADDR_R);
+}
+
+static void tx4938ndfmc_nand_write_byte(struct mtd_info *mtd, u_char byte)
+{
+ struct nand_chip *this = mtd->priv;
+ tx4938_write_nfmc(byte, this->IO_ADDR_W);
+}
+
+static void tx4938ndfmc_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+
+ for (i=0; i<len; i++)
+ tx4938_write_nfmc(buf[i], this->IO_ADDR_W);
+}
+
+static void tx4938ndfmc_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+
+ for (i=0; i<len; i++)
+ buf[i] = tx4938_read_nfmc(this->IO_ADDR_R);
+}
+
+static int tx4938ndfmc_nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+ int i;
+ struct nand_chip *this = mtd->priv;
+
+ for (i=0; i<len; i++)
+ if (buf[i] != tx4938_read_nfmc(this->IO_ADDR_R))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * Send command to NAND device
+ */
+static void tx4938ndfmc_nand_command (struct mtd_info *mtd, unsigned command, int column, int page_addr)
+{
+ register struct nand_chip *this = mtd->priv;
+
+ /* Begin command latch cycle */
+ this->hwcontrol(mtd, NAND_CTL_SETCLE);
+ /*
+ * Write out the command to the device.
+ */
+ if (command == NAND_CMD_SEQIN) {
+ int readcmd;
+
+ if (column >= mtd->oobblock) {
+ /* OOB area */
+ column -= mtd->oobblock;
+ readcmd = NAND_CMD_READOOB;
+ } else if (column < 256) {
+ /* First 256 bytes --> READ0 */
+ readcmd = NAND_CMD_READ0;
+ } else {
+ column -= 256;
+ readcmd = NAND_CMD_READ1;
+ }
+ this->write_byte(mtd, readcmd);
+ }
+ this->write_byte(mtd, command);
+
+ /* Set ALE and clear CLE to start address cycle */
+ this->hwcontrol(mtd, NAND_CTL_CLRCLE);
+
+ if (column != -1 || page_addr != -1) {
+ this->hwcontrol(mtd, NAND_CTL_SETALE);
+
+ /* Serially input address */
+ if (column != -1)
+ this->write_byte(mtd, column);
+ if (page_addr != -1) {
+ this->write_byte(mtd, (unsigned char) (page_addr & 0xff));
+ this->write_byte(mtd, (unsigned char) ((page_addr >> 8) & 0xff));
+ /* One more address cycle for higher density devices */
+ if (mtd->size & 0x0c000000)
+ this->write_byte(mtd, (unsigned char) ((page_addr >> 16) & 0x0f));
+ }
+ /* Latch in address */
+ this->hwcontrol(mtd, NAND_CTL_CLRALE);
+ }
+
+ /*
+ * program and erase have their own busy handlers
+ * status and sequential in needs no delay
+ */
+ switch (command) {
+
+ case NAND_CMD_PAGEPROG:
+ /* Turn off WE */
+ this->hwcontrol (mtd, NAND_CTL_CLRWP);
+ return;
+
+ case NAND_CMD_SEQIN:
+ /* Turn on WE */
+ this->hwcontrol (mtd, NAND_CTL_SETWP);
+ return;
+
+ case NAND_CMD_ERASE1:
+ case NAND_CMD_ERASE2:
+ case NAND_CMD_STATUS:
+ return;
+
+ case NAND_CMD_RESET:
+ if (this->dev_ready)
+ break;
+ this->hwcontrol(mtd, NAND_CTL_SETCLE);
+ this->write_byte(mtd, NAND_CMD_STATUS);
+ this->hwcontrol(mtd, NAND_CTL_CLRCLE);
+ while ( !(this->read_byte(mtd) & 0x40));
+ return;
+
+ /* This applies to read commands */
+ default:
+ /*
+ * If we don't have access to the busy pin, we apply the given
+ * command delay
+ */
+ if (!this->dev_ready) {
+ udelay (this->chip_delay);
+ return;
+ }
+ }
+
+ /* wait until command is processed */
+ while (!this->dev_ready(mtd));
+}
+
+#ifdef CONFIG_MTD_CMDLINE_PARTS
+extern int parse_cmdline_partitions(struct mtd_info *master, struct mtd_partition **pparts, char *);
+#endif
+/*
+ * Main initialization routine
+ */
+int __init tx4938ndfmc_init (void)
+{
+ struct nand_chip *this;
+ int bsprt = 0, hold = 0xf, spw = 0xf;
+ int protected = 0;
+
+ if ((*rbtx4938_piosel_ptr & 0x0c) != 0x08) {
+ printk("TX4938 NDFMC: disabled by IOC PIOSEL\n");
+ return -ENODEV;
+ }
+ bsprt = 1;
+ hold = 2;
+ spw = 9 - 1; /* 8 GBUSCLK = 80ns (@ GBUSCLK 100MHz) */
+
+ if ((tx4938_ccfgptr->pcfg &
+ (TX4938_PCFG_ATA_SEL|TX4938_PCFG_ISA_SEL|TX4938_PCFG_NDF_SEL))
+ != TX4938_PCFG_NDF_SEL) {
+ printk("TX4938 NDFMC: disabled by PCFG.\n");
+ return -ENODEV;
+ }
+
+ /* reset NDFMC */
+ tx4938_ndfmcptr->rstr |= TX4938_NDFRSTR_RST;
+ while (tx4938_ndfmcptr->rstr & TX4938_NDFRSTR_RST)
+ ;
+ /* setup BusSeparete, Hold Time, Strobe Pulse Width */
+ tx4938_ndfmcptr->mcr = bsprt ? TX4938_NDFMCR_BSPRT : 0;
+ tx4938_ndfmcptr->spr = hold << 4 | spw;
+
+ /* Allocate memory for MTD device structure and private data */
+ tx4938ndfmc_mtd = kmalloc (sizeof(struct mtd_info) + sizeof (struct nand_chip),
+ GFP_KERNEL);
+ if (!tx4938ndfmc_mtd) {
+ printk ("Unable to allocate TX4938 NDFMC MTD device structure.\n");
+ return -ENOMEM;
+ }
+
+ /* Get pointer to private data */
+ this = (struct nand_chip *) (&tx4938ndfmc_mtd[1]);
+
+ /* Initialize structures */
+ memset((char *) tx4938ndfmc_mtd, 0, sizeof(struct mtd_info));
+ memset((char *) this, 0, sizeof(struct nand_chip));
+
+ /* Link the private data with the MTD structure */
+ tx4938ndfmc_mtd->priv = this;
+
+ /* Set address of NAND IO lines */
+ this->IO_ADDR_R = (unsigned long)&tx4938_ndfmcptr->dtr;
+ this->IO_ADDR_W = (unsigned long)&tx4938_ndfmcptr->dtr;
+ this->hwcontrol = tx4938ndfmc_hwcontrol;
+ this->dev_ready = tx4938ndfmc_dev_ready;
+ this->calculate_ecc = tx4938ndfmc_calculate_ecc;
+ this->correct_data = nand_correct_data;
+ this->enable_hwecc = tx4938ndfmc_enable_hwecc;
+ this->eccmode = NAND_ECC_HW3_256;
+ this->chip_delay = 100;
+ this->read_byte = tx4938ndfmc_nand_read_byte;
+ this->write_byte = tx4938ndfmc_nand_write_byte;
+ this->cmdfunc = tx4938ndfmc_nand_command;
+ this->write_buf = tx4938ndfmc_nand_write_buf;
+ this->read_buf = tx4938ndfmc_nand_read_buf;
+ this->verify_buf = tx4938ndfmc_nand_verify_buf;
+
+ /* Scan to find existance of the device */
+ if (nand_scan (tx4938ndfmc_mtd, 1)) {
+ kfree (tx4938ndfmc_mtd);
+ return -ENXIO;
+ }
+
+ if (protected) {
+ printk(KERN_INFO "TX4938 NDFMC: write protected.\n");
+ tx4938ndfmc_mtd->flags &= ~(MTD_WRITEABLE | MTD_ERASEABLE);
+ }
+
+#ifdef CONFIG_MTD_CMDLINE_PARTS
+ {
+ int mtd_parts_nb = 0;
+ struct mtd_partition *mtd_parts = 0;
+ mtd_parts_nb = parse_cmdline_partitions(tx4938ndfmc_mtd, &mtd_parts, "tx4938ndfmc");
+ if (mtd_parts_nb > 0)
+ add_mtd_partitions(tx4938ndfmc_mtd, mtd_parts, mtd_parts_nb);
+ else
+ add_mtd_device(tx4938ndfmc_mtd);
+ }
+#else
+ add_mtd_partitions(tx4938ndfmc_mtd, partition_info, NUM_PARTITIONS );
+#endif
+
+ return 0;
+}
+module_init(tx4938ndfmc_init);
+
+/*
+ * Clean up routine
+ */
+static void __exit tx4938ndfmc_cleanup (void)
+{
+ /* Release resources, unregister device */
+ nand_release (tx4938ndfmc_mtd);
+
+ /* Free the MTD device structure */
+ kfree (tx4938ndfmc_mtd);
+}
+module_exit(tx4938ndfmc_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Alice Hennessy <ahennessy@mvista.com>");
+MODULE_DESCRIPTION("Board-specific glue layer for NAND flash on TX4938 NDFMC");
diff --git a/drivers/mtd/nftlcore.c b/drivers/mtd/nftlcore.c
new file mode 100644
index 000000000000..b2014043634f
--- /dev/null
+++ b/drivers/mtd/nftlcore.c
@@ -0,0 +1,767 @@
+/* Linux driver for NAND Flash Translation Layer */
+/* (c) 1999 Machine Vision Holdings, Inc. */
+/* Author: David Woodhouse <dwmw2@infradead.org> */
+/* $Id: nftlcore.c,v 1.97 2004/11/16 18:28:59 dwmw2 Exp $ */
+
+/*
+ The contents of this file are distributed under the GNU General
+ Public License version 2. The author places no additional
+ restrictions of any kind on it.
+ */
+
+#define PRERELEASE
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/hdreg.h>
+
+#include <linux/kmod.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/nftl.h>
+#include <linux/mtd/blktrans.h>
+
+/* maximum number of loops while examining next block, to have a
+ chance to detect consistency problems (they should never happen
+ because of the checks done in the mounting */
+
+#define MAX_LOOPS 10000
+
+
+static void nftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
+{
+ struct NFTLrecord *nftl;
+ unsigned long temp;
+
+ if (mtd->type != MTD_NANDFLASH)
+ return;
+ /* OK, this is moderately ugly. But probably safe. Alternatives? */
+ if (memcmp(mtd->name, "DiskOnChip", 10))
+ return;
+
+ if (!mtd->block_isbad) {
+ printk(KERN_ERR
+"NFTL no longer supports the old DiskOnChip drivers loaded via docprobe.\n"
+"Please use the new diskonchip driver under the NAND subsystem.\n");
+ return;
+ }
+
+ DEBUG(MTD_DEBUG_LEVEL1, "NFTL: add_mtd for %s\n", mtd->name);
+
+ nftl = kmalloc(sizeof(struct NFTLrecord), GFP_KERNEL);
+
+ if (!nftl) {
+ printk(KERN_WARNING "NFTL: out of memory for data structures\n");
+ return;
+ }
+ memset(nftl, 0, sizeof(*nftl));
+
+ nftl->mbd.mtd = mtd;
+ nftl->mbd.devnum = -1;
+ nftl->mbd.blksize = 512;
+ nftl->mbd.tr = tr;
+ memcpy(&nftl->oobinfo, &mtd->oobinfo, sizeof(struct nand_oobinfo));
+ nftl->oobinfo.useecc = MTD_NANDECC_PLACEONLY;
+
+ if (NFTL_mount(nftl) < 0) {
+ printk(KERN_WARNING "NFTL: could not mount device\n");
+ kfree(nftl);
+ return;
+ }
+
+ /* OK, it's a new one. Set up all the data structures. */
+
+ /* Calculate geometry */
+ nftl->cylinders = 1024;
+ nftl->heads = 16;
+
+ temp = nftl->cylinders * nftl->heads;
+ nftl->sectors = nftl->mbd.size / temp;
+ if (nftl->mbd.size % temp) {
+ nftl->sectors++;
+ temp = nftl->cylinders * nftl->sectors;
+ nftl->heads = nftl->mbd.size / temp;
+
+ if (nftl->mbd.size % temp) {
+ nftl->heads++;
+ temp = nftl->heads * nftl->sectors;
+ nftl->cylinders = nftl->mbd.size / temp;
+ }
+ }
+
+ if (nftl->mbd.size != nftl->heads * nftl->cylinders * nftl->sectors) {
+ /*
+ Oh no we don't have
+ mbd.size == heads * cylinders * sectors
+ */
+ printk(KERN_WARNING "NFTL: cannot calculate a geometry to "
+ "match size of 0x%lx.\n", nftl->mbd.size);
+ printk(KERN_WARNING "NFTL: using C:%d H:%d S:%d "
+ "(== 0x%lx sects)\n",
+ nftl->cylinders, nftl->heads , nftl->sectors,
+ (long)nftl->cylinders * (long)nftl->heads *
+ (long)nftl->sectors );
+ }
+
+ if (add_mtd_blktrans_dev(&nftl->mbd)) {
+ if (nftl->ReplUnitTable)
+ kfree(nftl->ReplUnitTable);
+ if (nftl->EUNtable)
+ kfree(nftl->EUNtable);
+ kfree(nftl);
+ return;
+ }
+#ifdef PSYCHO_DEBUG
+ printk(KERN_INFO "NFTL: Found new nftl%c\n", nftl->mbd.devnum + 'a');
+#endif
+}
+
+static void nftl_remove_dev(struct mtd_blktrans_dev *dev)
+{
+ struct NFTLrecord *nftl = (void *)dev;
+
+ DEBUG(MTD_DEBUG_LEVEL1, "NFTL: remove_dev (i=%d)\n", dev->devnum);
+
+ del_mtd_blktrans_dev(dev);
+ if (nftl->ReplUnitTable)
+ kfree(nftl->ReplUnitTable);
+ if (nftl->EUNtable)
+ kfree(nftl->EUNtable);
+ kfree(nftl);
+}
+
+#ifdef CONFIG_NFTL_RW
+
+/* Actual NFTL access routines */
+/* NFTL_findfreeblock: Find a free Erase Unit on the NFTL partition. This function is used
+ * when the give Virtual Unit Chain
+ */
+static u16 NFTL_findfreeblock(struct NFTLrecord *nftl, int desperate )
+{
+ /* For a given Virtual Unit Chain: find or create a free block and
+ add it to the chain */
+ /* We're passed the number of the last EUN in the chain, to save us from
+ having to look it up again */
+ u16 pot = nftl->LastFreeEUN;
+ int silly = nftl->nb_blocks;
+
+ /* Normally, we force a fold to happen before we run out of free blocks completely */
+ if (!desperate && nftl->numfreeEUNs < 2) {
+ DEBUG(MTD_DEBUG_LEVEL1, "NFTL_findfreeblock: there are too few free EUNs\n");
+ return 0xffff;
+ }
+
+ /* Scan for a free block */
+ do {
+ if (nftl->ReplUnitTable[pot] == BLOCK_FREE) {
+ nftl->LastFreeEUN = pot;
+ nftl->numfreeEUNs--;
+ return pot;
+ }
+
+ /* This will probably point to the MediaHdr unit itself,
+ right at the beginning of the partition. But that unit
+ (and the backup unit too) should have the UCI set
+ up so that it's not selected for overwriting */
+ if (++pot > nftl->lastEUN)
+ pot = le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN);
+
+ if (!silly--) {
+ printk("Argh! No free blocks found! LastFreeEUN = %d, "
+ "FirstEUN = %d\n", nftl->LastFreeEUN,
+ le16_to_cpu(nftl->MediaHdr.FirstPhysicalEUN));
+ return 0xffff;
+ }
+ } while (pot != nftl->LastFreeEUN);
+
+ return 0xffff;
+}
+
+static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned pendingblock )
+{
+ u16 BlockMap[MAX_SECTORS_PER_UNIT];
+ unsigned char BlockLastState[MAX_SECTORS_PER_UNIT];
+ unsigned char BlockFreeFound[MAX_SECTORS_PER_UNIT];
+ unsigned int thisEUN;
+ int block;
+ int silly;
+ unsigned int targetEUN;
+ struct nftl_oob oob;
+ int inplace = 1;
+ size_t retlen;
+
+ memset(BlockMap, 0xff, sizeof(BlockMap));
+ memset(BlockFreeFound, 0, sizeof(BlockFreeFound));
+
+ thisEUN = nftl->EUNtable[thisVUC];
+
+ if (thisEUN == BLOCK_NIL) {
+ printk(KERN_WARNING "Trying to fold non-existent "
+ "Virtual Unit Chain %d!\n", thisVUC);
+ return BLOCK_NIL;
+ }
+
+ /* Scan to find the Erase Unit which holds the actual data for each
+ 512-byte block within the Chain.
+ */
+ silly = MAX_LOOPS;
+ targetEUN = BLOCK_NIL;
+ while (thisEUN <= nftl->lastEUN ) {
+ unsigned int status, foldmark;
+
+ targetEUN = thisEUN;
+ for (block = 0; block < nftl->EraseSize / 512; block ++) {
+ MTD_READOOB(nftl->mbd.mtd,
+ (thisEUN * nftl->EraseSize) + (block * 512),
+ 16 , &retlen, (char *)&oob);
+ if (block == 2) {
+ foldmark = oob.u.c.FoldMark | oob.u.c.FoldMark1;
+ if (foldmark == FOLD_MARK_IN_PROGRESS) {
+ DEBUG(MTD_DEBUG_LEVEL1,
+ "Write Inhibited on EUN %d\n", thisEUN);
+ inplace = 0;
+ } else {
+ /* There's no other reason not to do inplace,
+ except ones that come later. So we don't need
+ to preserve inplace */
+ inplace = 1;
+ }
+ }
+ status = oob.b.Status | oob.b.Status1;
+ BlockLastState[block] = status;
+
+ switch(status) {
+ case SECTOR_FREE:
+ BlockFreeFound[block] = 1;
+ break;
+
+ case SECTOR_USED:
+ if (!BlockFreeFound[block])
+ BlockMap[block] = thisEUN;
+ else
+ printk(KERN_WARNING
+ "SECTOR_USED found after SECTOR_FREE "
+ "in Virtual Unit Chain %d for block %d\n",
+ thisVUC, block);
+ break;
+ case SECTOR_DELETED:
+ if (!BlockFreeFound[block])
+ BlockMap[block] = BLOCK_NIL;
+ else
+ printk(KERN_WARNING
+ "SECTOR_DELETED found after SECTOR_FREE "
+ "in Virtual Unit Chain %d for block %d\n",
+ thisVUC, block);
+ break;
+
+ case SECTOR_IGNORE:
+ break;
+ default:
+ printk("Unknown status for block %d in EUN %d: %x\n",
+ block, thisEUN, status);
+ }
+ }
+
+ if (!silly--) {
+ printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%x\n",
+ thisVUC);
+ return BLOCK_NIL;
+ }
+
+ thisEUN = nftl->ReplUnitTable[thisEUN];
+ }
+
+ if (inplace) {
+ /* We're being asked to be a fold-in-place. Check
+ that all blocks which actually have data associated
+ with them (i.e. BlockMap[block] != BLOCK_NIL) are
+ either already present or SECTOR_FREE in the target
+ block. If not, we're going to have to fold out-of-place
+ anyway.
+ */
+ for (block = 0; block < nftl->EraseSize / 512 ; block++) {
+ if (BlockLastState[block] != SECTOR_FREE &&
+ BlockMap[block] != BLOCK_NIL &&
+ BlockMap[block] != targetEUN) {
+ DEBUG(MTD_DEBUG_LEVEL1, "Setting inplace to 0. VUC %d, "
+ "block %d was %x lastEUN, "
+ "and is in EUN %d (%s) %d\n",
+ thisVUC, block, BlockLastState[block],
+ BlockMap[block],
+ BlockMap[block]== targetEUN ? "==" : "!=",
+ targetEUN);
+ inplace = 0;
+ break;
+ }
+ }
+
+ if (pendingblock >= (thisVUC * (nftl->EraseSize / 512)) &&
+ pendingblock < ((thisVUC + 1)* (nftl->EraseSize / 512)) &&
+ BlockLastState[pendingblock - (thisVUC * (nftl->EraseSize / 512))] !=
+ SECTOR_FREE) {
+ DEBUG(MTD_DEBUG_LEVEL1, "Pending write not free in EUN %d. "
+ "Folding out of place.\n", targetEUN);
+ inplace = 0;
+ }
+ }
+
+ if (!inplace) {
+ DEBUG(MTD_DEBUG_LEVEL1, "Cannot fold Virtual Unit Chain %d in place. "
+ "Trying out-of-place\n", thisVUC);
+ /* We need to find a targetEUN to fold into. */
+ targetEUN = NFTL_findfreeblock(nftl, 1);
+ if (targetEUN == BLOCK_NIL) {
+ /* Ouch. Now we're screwed. We need to do a
+ fold-in-place of another chain to make room
+ for this one. We need a better way of selecting
+ which chain to fold, because makefreeblock will
+ only ask us to fold the same one again.
+ */
+ printk(KERN_WARNING
+ "NFTL_findfreeblock(desperate) returns 0xffff.\n");
+ return BLOCK_NIL;
+ }
+ } else {
+ /* We put a fold mark in the chain we are folding only if
+ we fold in place to help the mount check code. If we do
+ not fold in place, it is possible to find the valid
+ chain by selecting the longer one */
+ oob.u.c.FoldMark = oob.u.c.FoldMark1 = cpu_to_le16(FOLD_MARK_IN_PROGRESS);
+ oob.u.c.unused = 0xffffffff;
+ MTD_WRITEOOB(nftl->mbd.mtd, (nftl->EraseSize * targetEUN) + 2 * 512 + 8,
+ 8, &retlen, (char *)&oob.u);
+ }
+
+ /* OK. We now know the location of every block in the Virtual Unit Chain,
+ and the Erase Unit into which we are supposed to be copying.
+ Go for it.
+ */
+ DEBUG(MTD_DEBUG_LEVEL1,"Folding chain %d into unit %d\n", thisVUC, targetEUN);
+ for (block = 0; block < nftl->EraseSize / 512 ; block++) {
+ unsigned char movebuf[512];
+ int ret;
+
+ /* If it's in the target EUN already, or if it's pending write, do nothing */
+ if (BlockMap[block] == targetEUN ||
+ (pendingblock == (thisVUC * (nftl->EraseSize / 512) + block))) {
+ continue;
+ }
+
+ /* copy only in non free block (free blocks can only
+ happen in case of media errors or deleted blocks) */
+ if (BlockMap[block] == BLOCK_NIL)
+ continue;
+
+ ret = MTD_READ(nftl->mbd.mtd, (nftl->EraseSize * BlockMap[block]) + (block * 512),
+ 512, &retlen, movebuf);
+ if (ret < 0) {
+ ret = MTD_READ(nftl->mbd.mtd, (nftl->EraseSize * BlockMap[block])
+ + (block * 512), 512, &retlen,
+ movebuf);
+ if (ret != -EIO)
+ printk("Error went away on retry.\n");
+ }
+ memset(&oob, 0xff, sizeof(struct nftl_oob));
+ oob.b.Status = oob.b.Status1 = SECTOR_USED;
+ MTD_WRITEECC(nftl->mbd.mtd, (nftl->EraseSize * targetEUN) + (block * 512),
+ 512, &retlen, movebuf, (char *)&oob, &nftl->oobinfo);
+ }
+
+ /* add the header so that it is now a valid chain */
+ oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum
+ = cpu_to_le16(thisVUC);
+ oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum = 0xffff;
+
+ MTD_WRITEOOB(nftl->mbd.mtd, (nftl->EraseSize * targetEUN) + 8,
+ 8, &retlen, (char *)&oob.u);
+
+ /* OK. We've moved the whole lot into the new block. Now we have to free the original blocks. */
+
+ /* At this point, we have two different chains for this Virtual Unit, and no way to tell
+ them apart. If we crash now, we get confused. However, both contain the same data, so we
+ shouldn't actually lose data in this case. It's just that when we load up on a medium which
+ has duplicate chains, we need to free one of the chains because it's not necessary any more.
+ */
+ thisEUN = nftl->EUNtable[thisVUC];
+ DEBUG(MTD_DEBUG_LEVEL1,"Want to erase\n");
+
+ /* For each block in the old chain (except the targetEUN of course),
+ free it and make it available for future use */
+ while (thisEUN <= nftl->lastEUN && thisEUN != targetEUN) {
+ unsigned int EUNtmp;
+
+ EUNtmp = nftl->ReplUnitTable[thisEUN];
+
+ if (NFTL_formatblock(nftl, thisEUN) < 0) {
+ /* could not erase : mark block as reserved
+ */
+ nftl->ReplUnitTable[thisEUN] = BLOCK_RESERVED;
+ } else {
+ /* correctly erased : mark it as free */
+ nftl->ReplUnitTable[thisEUN] = BLOCK_FREE;
+ nftl->numfreeEUNs++;
+ }
+ thisEUN = EUNtmp;
+ }
+
+ /* Make this the new start of chain for thisVUC */
+ nftl->ReplUnitTable[targetEUN] = BLOCK_NIL;
+ nftl->EUNtable[thisVUC] = targetEUN;
+
+ return targetEUN;
+}
+
+static u16 NFTL_makefreeblock( struct NFTLrecord *nftl , unsigned pendingblock)
+{
+ /* This is the part that needs some cleverness applied.
+ For now, I'm doing the minimum applicable to actually
+ get the thing to work.
+ Wear-levelling and other clever stuff needs to be implemented
+ and we also need to do some assessment of the results when
+ the system loses power half-way through the routine.
+ */
+ u16 LongestChain = 0;
+ u16 ChainLength = 0, thislen;
+ u16 chain, EUN;
+
+ for (chain = 0; chain < le32_to_cpu(nftl->MediaHdr.FormattedSize) / nftl->EraseSize; chain++) {
+ EUN = nftl->EUNtable[chain];
+ thislen = 0;
+
+ while (EUN <= nftl->lastEUN) {
+ thislen++;
+ //printk("VUC %d reaches len %d with EUN %d\n", chain, thislen, EUN);
+ EUN = nftl->ReplUnitTable[EUN] & 0x7fff;
+ if (thislen > 0xff00) {
+ printk("Endless loop in Virtual Chain %d: Unit %x\n",
+ chain, EUN);
+ }
+ if (thislen > 0xff10) {
+ /* Actually, don't return failure. Just ignore this chain and
+ get on with it. */
+ thislen = 0;
+ break;
+ }
+ }
+
+ if (thislen > ChainLength) {
+ //printk("New longest chain is %d with length %d\n", chain, thislen);
+ ChainLength = thislen;
+ LongestChain = chain;
+ }
+ }
+
+ if (ChainLength < 2) {
+ printk(KERN_WARNING "No Virtual Unit Chains available for folding. "
+ "Failing request\n");
+ return 0xffff;
+ }
+
+ return NFTL_foldchain (nftl, LongestChain, pendingblock);
+}
+
+/* NFTL_findwriteunit: Return the unit number into which we can write
+ for this block. Make it available if it isn't already
+*/
+static inline u16 NFTL_findwriteunit(struct NFTLrecord *nftl, unsigned block)
+{
+ u16 lastEUN;
+ u16 thisVUC = block / (nftl->EraseSize / 512);
+ unsigned int writeEUN;
+ unsigned long blockofs = (block * 512) & (nftl->EraseSize -1);
+ size_t retlen;
+ int silly, silly2 = 3;
+ struct nftl_oob oob;
+
+ do {
+ /* Scan the media to find a unit in the VUC which has
+ a free space for the block in question.
+ */
+
+ /* This condition catches the 0x[7f]fff cases, as well as
+ being a sanity check for past-end-of-media access
+ */
+ lastEUN = BLOCK_NIL;
+ writeEUN = nftl->EUNtable[thisVUC];
+ silly = MAX_LOOPS;
+ while (writeEUN <= nftl->lastEUN) {
+ struct nftl_bci bci;
+ size_t retlen;
+ unsigned int status;
+
+ lastEUN = writeEUN;
+
+ MTD_READOOB(nftl->mbd.mtd, (writeEUN * nftl->EraseSize) + blockofs,
+ 8, &retlen, (char *)&bci);
+
+ DEBUG(MTD_DEBUG_LEVEL2, "Status of block %d in EUN %d is %x\n",
+ block , writeEUN, le16_to_cpu(bci.Status));
+
+ status = bci.Status | bci.Status1;
+ switch(status) {
+ case SECTOR_FREE:
+ return writeEUN;
+
+ case SECTOR_DELETED:
+ case SECTOR_USED:
+ case SECTOR_IGNORE:
+ break;
+ default:
+ // Invalid block. Don't use it any more. Must implement.
+ break;
+ }
+
+ if (!silly--) {
+ printk(KERN_WARNING
+ "Infinite loop in Virtual Unit Chain 0x%x\n",
+ thisVUC);
+ return 0xffff;
+ }
+
+ /* Skip to next block in chain */
+ writeEUN = nftl->ReplUnitTable[writeEUN];
+ }
+
+ /* OK. We didn't find one in the existing chain, or there
+ is no existing chain. */
+
+ /* Try to find an already-free block */
+ writeEUN = NFTL_findfreeblock(nftl, 0);
+
+ if (writeEUN == BLOCK_NIL) {
+ /* That didn't work - there were no free blocks just
+ waiting to be picked up. We're going to have to fold
+ a chain to make room.
+ */
+
+ /* First remember the start of this chain */
+ //u16 startEUN = nftl->EUNtable[thisVUC];
+
+ //printk("Write to VirtualUnitChain %d, calling makefreeblock()\n", thisVUC);
+ writeEUN = NFTL_makefreeblock(nftl, 0xffff);
+
+ if (writeEUN == BLOCK_NIL) {
+ /* OK, we accept that the above comment is
+ lying - there may have been free blocks
+ last time we called NFTL_findfreeblock(),
+ but they are reserved for when we're
+ desperate. Well, now we're desperate.
+ */
+ DEBUG(MTD_DEBUG_LEVEL1, "Using desperate==1 to find free EUN to accommodate write to VUC %d\n", thisVUC);
+ writeEUN = NFTL_findfreeblock(nftl, 1);
+ }
+ if (writeEUN == BLOCK_NIL) {
+ /* Ouch. This should never happen - we should
+ always be able to make some room somehow.
+ If we get here, we've allocated more storage
+ space than actual media, or our makefreeblock
+ routine is missing something.
+ */
+ printk(KERN_WARNING "Cannot make free space.\n");
+ return BLOCK_NIL;
+ }
+ //printk("Restarting scan\n");
+ lastEUN = BLOCK_NIL;
+ continue;
+ }
+
+ /* We've found a free block. Insert it into the chain. */
+
+ if (lastEUN != BLOCK_NIL) {
+ thisVUC |= 0x8000; /* It's a replacement block */
+ } else {
+ /* The first block in a new chain */
+ nftl->EUNtable[thisVUC] = writeEUN;
+ }
+
+ /* set up the actual EUN we're writing into */
+ /* Both in our cache... */
+ nftl->ReplUnitTable[writeEUN] = BLOCK_NIL;
+
+ /* ... and on the flash itself */
+ MTD_READOOB(nftl->mbd.mtd, writeEUN * nftl->EraseSize + 8, 8,
+ &retlen, (char *)&oob.u);
+
+ oob.u.a.VirtUnitNum = oob.u.a.SpareVirtUnitNum = cpu_to_le16(thisVUC);
+
+ MTD_WRITEOOB(nftl->mbd.mtd, writeEUN * nftl->EraseSize + 8, 8,
+ &retlen, (char *)&oob.u);
+
+ /* we link the new block to the chain only after the
+ block is ready. It avoids the case where the chain
+ could point to a free block */
+ if (lastEUN != BLOCK_NIL) {
+ /* Both in our cache... */
+ nftl->ReplUnitTable[lastEUN] = writeEUN;
+ /* ... and on the flash itself */
+ MTD_READOOB(nftl->mbd.mtd, (lastEUN * nftl->EraseSize) + 8,
+ 8, &retlen, (char *)&oob.u);
+
+ oob.u.a.ReplUnitNum = oob.u.a.SpareReplUnitNum
+ = cpu_to_le16(writeEUN);
+
+ MTD_WRITEOOB(nftl->mbd.mtd, (lastEUN * nftl->EraseSize) + 8,
+ 8, &retlen, (char *)&oob.u);
+ }
+
+ return writeEUN;
+
+ } while (silly2--);
+
+ printk(KERN_WARNING "Error folding to make room for Virtual Unit Chain 0x%x\n",
+ thisVUC);
+ return 0xffff;
+}
+
+static int nftl_writeblock(struct mtd_blktrans_dev *mbd, unsigned long block,
+ char *buffer)
+{
+ struct NFTLrecord *nftl = (void *)mbd;
+ u16 writeEUN;
+ unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1);
+ size_t retlen;
+ struct nftl_oob oob;
+
+ writeEUN = NFTL_findwriteunit(nftl, block);
+
+ if (writeEUN == BLOCK_NIL) {
+ printk(KERN_WARNING
+ "NFTL_writeblock(): Cannot find block to write to\n");
+ /* If we _still_ haven't got a block to use, we're screwed */
+ return 1;
+ }
+
+ memset(&oob, 0xff, sizeof(struct nftl_oob));
+ oob.b.Status = oob.b.Status1 = SECTOR_USED;
+ MTD_WRITEECC(nftl->mbd.mtd, (writeEUN * nftl->EraseSize) + blockofs,
+ 512, &retlen, (char *)buffer, (char *)&oob, &nftl->oobinfo);
+ /* need to write SECTOR_USED flags since they are not written in mtd_writeecc */
+
+ return 0;
+}
+#endif /* CONFIG_NFTL_RW */
+
+static int nftl_readblock(struct mtd_blktrans_dev *mbd, unsigned long block,
+ char *buffer)
+{
+ struct NFTLrecord *nftl = (void *)mbd;
+ u16 lastgoodEUN;
+ u16 thisEUN = nftl->EUNtable[block / (nftl->EraseSize / 512)];
+ unsigned long blockofs = (block * 512) & (nftl->EraseSize - 1);
+ unsigned int status;
+ int silly = MAX_LOOPS;
+ size_t retlen;
+ struct nftl_bci bci;
+
+ lastgoodEUN = BLOCK_NIL;
+
+ if (thisEUN != BLOCK_NIL) {
+ while (thisEUN < nftl->nb_blocks) {
+ if (MTD_READOOB(nftl->mbd.mtd, (thisEUN * nftl->EraseSize) + blockofs,
+ 8, &retlen, (char *)&bci) < 0)
+ status = SECTOR_IGNORE;
+ else
+ status = bci.Status | bci.Status1;
+
+ switch (status) {
+ case SECTOR_FREE:
+ /* no modification of a sector should follow a free sector */
+ goto the_end;
+ case SECTOR_DELETED:
+ lastgoodEUN = BLOCK_NIL;
+ break;
+ case SECTOR_USED:
+ lastgoodEUN = thisEUN;
+ break;
+ case SECTOR_IGNORE:
+ break;
+ default:
+ printk("Unknown status for block %ld in EUN %d: %x\n",
+ block, thisEUN, status);
+ break;
+ }
+
+ if (!silly--) {
+ printk(KERN_WARNING "Infinite loop in Virtual Unit Chain 0x%lx\n",
+ block / (nftl->EraseSize / 512));
+ return 1;
+ }
+ thisEUN = nftl->ReplUnitTable[thisEUN];
+ }
+ }
+
+ the_end:
+ if (lastgoodEUN == BLOCK_NIL) {
+ /* the requested block is not on the media, return all 0x00 */
+ memset(buffer, 0, 512);
+ } else {
+ loff_t ptr = (lastgoodEUN * nftl->EraseSize) + blockofs;
+ size_t retlen;
+ if (MTD_READ(nftl->mbd.mtd, ptr, 512, &retlen, buffer))
+ return -EIO;
+ }
+ return 0;
+}
+
+static int nftl_getgeo(struct mtd_blktrans_dev *dev, struct hd_geometry *geo)
+{
+ struct NFTLrecord *nftl = (void *)dev;
+
+ geo->heads = nftl->heads;
+ geo->sectors = nftl->sectors;
+ geo->cylinders = nftl->cylinders;
+
+ return 0;
+}
+
+/****************************************************************************
+ *
+ * Module stuff
+ *
+ ****************************************************************************/
+
+
+static struct mtd_blktrans_ops nftl_tr = {
+ .name = "nftl",
+ .major = NFTL_MAJOR,
+ .part_bits = NFTL_PARTN_BITS,
+ .getgeo = nftl_getgeo,
+ .readsect = nftl_readblock,
+#ifdef CONFIG_NFTL_RW
+ .writesect = nftl_writeblock,
+#endif
+ .add_mtd = nftl_add_mtd,
+ .remove_dev = nftl_remove_dev,
+ .owner = THIS_MODULE,
+};
+
+extern char nftlmountrev[];
+
+static int __init init_nftl(void)
+{
+ printk(KERN_INFO "NFTL driver: nftlcore.c $Revision: 1.97 $, nftlmount.c %s\n", nftlmountrev);
+
+ return register_mtd_blktrans(&nftl_tr);
+}
+
+static void __exit cleanup_nftl(void)
+{
+ deregister_mtd_blktrans(&nftl_tr);
+}
+
+module_init(init_nftl);
+module_exit(cleanup_nftl);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>, Fabrice Bellard <fabrice.bellard@netgem.com> et al.");
+MODULE_DESCRIPTION("Support code for NAND Flash Translation Layer, used on M-Systems DiskOnChip 2000 and Millennium");
diff --git a/drivers/mtd/nftlmount.c b/drivers/mtd/nftlmount.c
new file mode 100644
index 000000000000..84afd9029f53
--- /dev/null
+++ b/drivers/mtd/nftlmount.c
@@ -0,0 +1,770 @@
+/*
+ * NFTL mount code with extensive checks
+ *
+ * Author: Fabrice Bellard (fabrice.bellard@netgem.com)
+ * Copyright (C) 2000 Netgem S.A.
+ *
+ * $Id: nftlmount.c,v 1.40 2004/11/22 14:38:29 kalev Exp $
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <asm/errno.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/nftl.h>
+
+#define SECTORSIZE 512
+
+char nftlmountrev[]="$Revision: 1.40 $";
+
+/* find_boot_record: Find the NFTL Media Header and its Spare copy which contains the
+ * various device information of the NFTL partition and Bad Unit Table. Update
+ * the ReplUnitTable[] table accroding to the Bad Unit Table. ReplUnitTable[]
+ * is used for management of Erase Unit in other routines in nftl.c and nftlmount.c
+ */
+static int find_boot_record(struct NFTLrecord *nftl)
+{
+ struct nftl_uci1 h1;
+ unsigned int block, boot_record_count = 0;
+ size_t retlen;
+ u8 buf[SECTORSIZE];
+ struct NFTLMediaHeader *mh = &nftl->MediaHdr;
+ unsigned int i;
+
+ /* Assume logical EraseSize == physical erasesize for starting the scan.
+ We'll sort it out later if we find a MediaHeader which says otherwise */
+ /* Actually, we won't. The new DiskOnChip driver has already scanned
+ the MediaHeader and adjusted the virtual erasesize it presents in
+ the mtd device accordingly. We could even get rid of
+ nftl->EraseSize if there were any point in doing so. */
+ nftl->EraseSize = nftl->mbd.mtd->erasesize;
+ nftl->nb_blocks = nftl->mbd.mtd->size / nftl->EraseSize;
+
+ nftl->MediaUnit = BLOCK_NIL;
+ nftl->SpareMediaUnit = BLOCK_NIL;
+
+ /* search for a valid boot record */
+ for (block = 0; block < nftl->nb_blocks; block++) {
+ int ret;
+
+ /* Check for ANAND header first. Then can whinge if it's found but later
+ checks fail */
+ ret = MTD_READ(nftl->mbd.mtd, block * nftl->EraseSize, SECTORSIZE, &retlen, buf);
+ /* We ignore ret in case the ECC of the MediaHeader is invalid
+ (which is apparently acceptable) */
+ if (retlen != SECTORSIZE) {
+ static int warncount = 5;
+
+ if (warncount) {
+ printk(KERN_WARNING "Block read at 0x%x of mtd%d failed: %d\n",
+ block * nftl->EraseSize, nftl->mbd.mtd->index, ret);
+ if (!--warncount)
+ printk(KERN_WARNING "Further failures for this block will not be printed\n");
+ }
+ continue;
+ }
+
+ if (retlen < 6 || memcmp(buf, "ANAND", 6)) {
+ /* ANAND\0 not found. Continue */
+#if 0
+ printk(KERN_DEBUG "ANAND header not found at 0x%x in mtd%d\n",
+ block * nftl->EraseSize, nftl->mbd.mtd->index);
+#endif
+ continue;
+ }
+
+ /* To be safer with BIOS, also use erase mark as discriminant */
+ if ((ret = MTD_READOOB(nftl->mbd.mtd, block * nftl->EraseSize + SECTORSIZE + 8,
+ 8, &retlen, (char *)&h1) < 0)) {
+ printk(KERN_WARNING "ANAND header found at 0x%x in mtd%d, but OOB data read failed (err %d)\n",
+ block * nftl->EraseSize, nftl->mbd.mtd->index, ret);
+ continue;
+ }
+
+#if 0 /* Some people seem to have devices without ECC or erase marks
+ on the Media Header blocks. There are enough other sanity
+ checks in here that we can probably do without it.
+ */
+ if (le16_to_cpu(h1.EraseMark | h1.EraseMark1) != ERASE_MARK) {
+ printk(KERN_NOTICE "ANAND header found at 0x%x in mtd%d, but erase mark not present (0x%04x,0x%04x instead)\n",
+ block * nftl->EraseSize, nftl->mbd.mtd->index,
+ le16_to_cpu(h1.EraseMark), le16_to_cpu(h1.EraseMark1));
+ continue;
+ }
+
+ /* Finally reread to check ECC */
+ if ((ret = MTD_READECC(nftl->mbd.mtd, block * nftl->EraseSize, SECTORSIZE,
+ &retlen, buf, (char *)&oob, NULL) < 0)) {
+ printk(KERN_NOTICE "ANAND header found at 0x%x in mtd%d, but ECC read failed (err %d)\n",
+ block * nftl->EraseSize, nftl->mbd.mtd->index, ret);
+ continue;
+ }
+
+ /* Paranoia. Check the ANAND header is still there after the ECC read */
+ if (memcmp(buf, "ANAND", 6)) {
+ printk(KERN_NOTICE "ANAND header found at 0x%x in mtd%d, but went away on reread!\n",
+ block * nftl->EraseSize, nftl->mbd.mtd->index);
+ printk(KERN_NOTICE "New data are: %02x %02x %02x %02x %02x %02x\n",
+ buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
+ continue;
+ }
+#endif
+ /* OK, we like it. */
+
+ if (boot_record_count) {
+ /* We've already processed one. So we just check if
+ this one is the same as the first one we found */
+ if (memcmp(mh, buf, sizeof(struct NFTLMediaHeader))) {
+ printk(KERN_NOTICE "NFTL Media Headers at 0x%x and 0x%x disagree.\n",
+ nftl->MediaUnit * nftl->EraseSize, block * nftl->EraseSize);
+ /* if (debug) Print both side by side */
+ if (boot_record_count < 2) {
+ /* We haven't yet seen two real ones */
+ return -1;
+ }
+ continue;
+ }
+ if (boot_record_count == 1)
+ nftl->SpareMediaUnit = block;
+
+ /* Mark this boot record (NFTL MediaHeader) block as reserved */
+ nftl->ReplUnitTable[block] = BLOCK_RESERVED;
+
+
+ boot_record_count++;
+ continue;
+ }
+
+ /* This is the first we've seen. Copy the media header structure into place */
+ memcpy(mh, buf, sizeof(struct NFTLMediaHeader));
+
+ /* Do some sanity checks on it */
+#if 0
+The new DiskOnChip driver scans the MediaHeader itself, and presents a virtual
+erasesize based on UnitSizeFactor. So the erasesize we read from the mtd
+device is already correct.
+ if (mh->UnitSizeFactor == 0) {
+ printk(KERN_NOTICE "NFTL: UnitSizeFactor 0x00 detected. This violates the spec but we think we know what it means...\n");
+ } else if (mh->UnitSizeFactor < 0xfc) {
+ printk(KERN_NOTICE "Sorry, we don't support UnitSizeFactor 0x%02x\n",
+ mh->UnitSizeFactor);
+ return -1;
+ } else if (mh->UnitSizeFactor != 0xff) {
+ printk(KERN_NOTICE "WARNING: Support for NFTL with UnitSizeFactor 0x%02x is experimental\n",
+ mh->UnitSizeFactor);
+ nftl->EraseSize = nftl->mbd.mtd->erasesize << (0xff - mh->UnitSizeFactor);
+ nftl->nb_blocks = nftl->mbd.mtd->size / nftl->EraseSize;
+ }
+#endif
+ nftl->nb_boot_blocks = le16_to_cpu(mh->FirstPhysicalEUN);
+ if ((nftl->nb_boot_blocks + 2) >= nftl->nb_blocks) {
+ printk(KERN_NOTICE "NFTL Media Header sanity check failed:\n");
+ printk(KERN_NOTICE "nb_boot_blocks (%d) + 2 > nb_blocks (%d)\n",
+ nftl->nb_boot_blocks, nftl->nb_blocks);
+ return -1;
+ }
+
+ nftl->numvunits = le32_to_cpu(mh->FormattedSize) / nftl->EraseSize;
+ if (nftl->numvunits > (nftl->nb_blocks - nftl->nb_boot_blocks - 2)) {
+ printk(KERN_NOTICE "NFTL Media Header sanity check failed:\n");
+ printk(KERN_NOTICE "numvunits (%d) > nb_blocks (%d) - nb_boot_blocks(%d) - 2\n",
+ nftl->numvunits, nftl->nb_blocks, nftl->nb_boot_blocks);
+ return -1;
+ }
+
+ nftl->mbd.size = nftl->numvunits * (nftl->EraseSize / SECTORSIZE);
+
+ /* If we're not using the last sectors in the device for some reason,
+ reduce nb_blocks accordingly so we forget they're there */
+ nftl->nb_blocks = le16_to_cpu(mh->NumEraseUnits) + le16_to_cpu(mh->FirstPhysicalEUN);
+
+ /* XXX: will be suppressed */
+ nftl->lastEUN = nftl->nb_blocks - 1;
+
+ /* memory alloc */
+ nftl->EUNtable = kmalloc(nftl->nb_blocks * sizeof(u16), GFP_KERNEL);
+ if (!nftl->EUNtable) {
+ printk(KERN_NOTICE "NFTL: allocation of EUNtable failed\n");
+ return -ENOMEM;
+ }
+
+ nftl->ReplUnitTable = kmalloc(nftl->nb_blocks * sizeof(u16), GFP_KERNEL);
+ if (!nftl->ReplUnitTable) {
+ kfree(nftl->EUNtable);
+ printk(KERN_NOTICE "NFTL: allocation of ReplUnitTable failed\n");
+ return -ENOMEM;
+ }
+
+ /* mark the bios blocks (blocks before NFTL MediaHeader) as reserved */
+ for (i = 0; i < nftl->nb_boot_blocks; i++)
+ nftl->ReplUnitTable[i] = BLOCK_RESERVED;
+ /* mark all remaining blocks as potentially containing data */
+ for (; i < nftl->nb_blocks; i++) {
+ nftl->ReplUnitTable[i] = BLOCK_NOTEXPLORED;
+ }
+
+ /* Mark this boot record (NFTL MediaHeader) block as reserved */
+ nftl->ReplUnitTable[block] = BLOCK_RESERVED;
+
+ /* read the Bad Erase Unit Table and modify ReplUnitTable[] accordingly */
+ for (i = 0; i < nftl->nb_blocks; i++) {
+#if 0
+The new DiskOnChip driver already scanned the bad block table. Just query it.
+ if ((i & (SECTORSIZE - 1)) == 0) {
+ /* read one sector for every SECTORSIZE of blocks */
+ if ((ret = MTD_READECC(nftl->mbd.mtd, block * nftl->EraseSize +
+ i + SECTORSIZE, SECTORSIZE, &retlen, buf,
+ (char *)&oob, NULL)) < 0) {
+ printk(KERN_NOTICE "Read of bad sector table failed (err %d)\n",
+ ret);
+ kfree(nftl->ReplUnitTable);
+ kfree(nftl->EUNtable);
+ return -1;
+ }
+ }
+ /* mark the Bad Erase Unit as RESERVED in ReplUnitTable */
+ if (buf[i & (SECTORSIZE - 1)] != 0xff)
+ nftl->ReplUnitTable[i] = BLOCK_RESERVED;
+#endif
+ if (nftl->mbd.mtd->block_isbad(nftl->mbd.mtd, i * nftl->EraseSize))
+ nftl->ReplUnitTable[i] = BLOCK_RESERVED;
+ }
+
+ nftl->MediaUnit = block;
+ boot_record_count++;
+
+ } /* foreach (block) */
+
+ return boot_record_count?0:-1;
+}
+
+static int memcmpb(void *a, int c, int n)
+{
+ int i;
+ for (i = 0; i < n; i++) {
+ if (c != ((unsigned char *)a)[i])
+ return 1;
+ }
+ return 0;
+}
+
+/* check_free_sector: check if a free sector is actually FREE, i.e. All 0xff in data and oob area */
+static int check_free_sectors(struct NFTLrecord *nftl, unsigned int address, int len,
+ int check_oob)
+{
+ int i;
+ size_t retlen;
+ u8 buf[SECTORSIZE + nftl->mbd.mtd->oobsize];
+
+ for (i = 0; i < len; i += SECTORSIZE) {
+ if (MTD_READECC(nftl->mbd.mtd, address, SECTORSIZE, &retlen, buf, &buf[SECTORSIZE], &nftl->oobinfo) < 0)
+ return -1;
+ if (memcmpb(buf, 0xff, SECTORSIZE) != 0)
+ return -1;
+
+ if (check_oob) {
+ if (memcmpb(buf + SECTORSIZE, 0xff, nftl->mbd.mtd->oobsize) != 0)
+ return -1;
+ }
+ address += SECTORSIZE;
+ }
+
+ return 0;
+}
+
+/* NFTL_format: format a Erase Unit by erasing ALL Erase Zones in the Erase Unit and
+ * Update NFTL metadata. Each erase operation is checked with check_free_sectors
+ *
+ * Return: 0 when succeed, -1 on error.
+ *
+ * ToDo: 1. Is it neceressary to check_free_sector after erasing ??
+ */
+int NFTL_formatblock(struct NFTLrecord *nftl, int block)
+{
+ size_t retlen;
+ unsigned int nb_erases, erase_mark;
+ struct nftl_uci1 uci;
+ struct erase_info *instr = &nftl->instr;
+
+ /* Read the Unit Control Information #1 for Wear-Leveling */
+ if (MTD_READOOB(nftl->mbd.mtd, block * nftl->EraseSize + SECTORSIZE + 8,
+ 8, &retlen, (char *)&uci) < 0)
+ goto default_uci1;
+
+ erase_mark = le16_to_cpu ((uci.EraseMark | uci.EraseMark1));
+ if (erase_mark != ERASE_MARK) {
+ default_uci1:
+ uci.EraseMark = cpu_to_le16(ERASE_MARK);
+ uci.EraseMark1 = cpu_to_le16(ERASE_MARK);
+ uci.WearInfo = cpu_to_le32(0);
+ }
+
+ memset(instr, 0, sizeof(struct erase_info));
+
+ /* XXX: use async erase interface, XXX: test return code */
+ instr->mtd = nftl->mbd.mtd;
+ instr->addr = block * nftl->EraseSize;
+ instr->len = nftl->EraseSize;
+ MTD_ERASE(nftl->mbd.mtd, instr);
+
+ if (instr->state == MTD_ERASE_FAILED) {
+ printk("Error while formatting block %d\n", block);
+ goto fail;
+ }
+
+ /* increase and write Wear-Leveling info */
+ nb_erases = le32_to_cpu(uci.WearInfo);
+ nb_erases++;
+
+ /* wrap (almost impossible with current flashs) or free block */
+ if (nb_erases == 0)
+ nb_erases = 1;
+
+ /* check the "freeness" of Erase Unit before updating metadata
+ * FixMe: is this check really necessary ? since we have check the
+ * return code after the erase operation. */
+ if (check_free_sectors(nftl, instr->addr, nftl->EraseSize, 1) != 0)
+ goto fail;
+
+ uci.WearInfo = le32_to_cpu(nb_erases);
+ if (MTD_WRITEOOB(nftl->mbd.mtd, block * nftl->EraseSize + SECTORSIZE + 8, 8,
+ &retlen, (char *)&uci) < 0)
+ goto fail;
+ return 0;
+fail:
+ /* could not format, update the bad block table (caller is responsible
+ for setting the ReplUnitTable to BLOCK_RESERVED on failure) */
+ nftl->mbd.mtd->block_markbad(nftl->mbd.mtd, instr->addr);
+ return -1;
+}
+
+/* check_sectors_in_chain: Check that each sector of a Virtual Unit Chain is correct.
+ * Mark as 'IGNORE' each incorrect sector. This check is only done if the chain
+ * was being folded when NFTL was interrupted.
+ *
+ * The check_free_sectors in this function is neceressary. There is a possible
+ * situation that after writing the Data area, the Block Control Information is
+ * not updated according (due to power failure or something) which leaves the block
+ * in an umconsistent state. So we have to check if a block is really FREE in this
+ * case. */
+static void check_sectors_in_chain(struct NFTLrecord *nftl, unsigned int first_block)
+{
+ unsigned int block, i, status;
+ struct nftl_bci bci;
+ int sectors_per_block;
+ size_t retlen;
+
+ sectors_per_block = nftl->EraseSize / SECTORSIZE;
+ block = first_block;
+ for (;;) {
+ for (i = 0; i < sectors_per_block; i++) {
+ if (MTD_READOOB(nftl->mbd.mtd, block * nftl->EraseSize + i * SECTORSIZE,
+ 8, &retlen, (char *)&bci) < 0)
+ status = SECTOR_IGNORE;
+ else
+ status = bci.Status | bci.Status1;
+
+ switch(status) {
+ case SECTOR_FREE:
+ /* verify that the sector is really free. If not, mark
+ as ignore */
+ if (memcmpb(&bci, 0xff, 8) != 0 ||
+ check_free_sectors(nftl, block * nftl->EraseSize + i * SECTORSIZE,
+ SECTORSIZE, 0) != 0) {
+ printk("Incorrect free sector %d in block %d: "
+ "marking it as ignored\n",
+ i, block);
+
+ /* sector not free actually : mark it as SECTOR_IGNORE */
+ bci.Status = SECTOR_IGNORE;
+ bci.Status1 = SECTOR_IGNORE;
+ MTD_WRITEOOB(nftl->mbd.mtd,
+ block * nftl->EraseSize + i * SECTORSIZE,
+ 8, &retlen, (char *)&bci);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* proceed to next Erase Unit on the chain */
+ block = nftl->ReplUnitTable[block];
+ if (!(block == BLOCK_NIL || block < nftl->nb_blocks))
+ printk("incorrect ReplUnitTable[] : %d\n", block);
+ if (block == BLOCK_NIL || block >= nftl->nb_blocks)
+ break;
+ }
+}
+
+/* calc_chain_lenght: Walk through a Virtual Unit Chain and estimate chain length */
+static int calc_chain_length(struct NFTLrecord *nftl, unsigned int first_block)
+{
+ unsigned int length = 0, block = first_block;
+
+ for (;;) {
+ length++;
+ /* avoid infinite loops, although this is guaranted not to
+ happen because of the previous checks */
+ if (length >= nftl->nb_blocks) {
+ printk("nftl: length too long %d !\n", length);
+ break;
+ }
+
+ block = nftl->ReplUnitTable[block];
+ if (!(block == BLOCK_NIL || block < nftl->nb_blocks))
+ printk("incorrect ReplUnitTable[] : %d\n", block);
+ if (block == BLOCK_NIL || block >= nftl->nb_blocks)
+ break;
+ }
+ return length;
+}
+
+/* format_chain: Format an invalid Virtual Unit chain. It frees all the Erase Units in a
+ * Virtual Unit Chain, i.e. all the units are disconnected.
+ *
+ * It is not stricly correct to begin from the first block of the chain because
+ * if we stop the code, we may see again a valid chain if there was a first_block
+ * flag in a block inside it. But is it really a problem ?
+ *
+ * FixMe: Figure out what the last statesment means. What if power failure when we are
+ * in the for (;;) loop formatting blocks ??
+ */
+static void format_chain(struct NFTLrecord *nftl, unsigned int first_block)
+{
+ unsigned int block = first_block, block1;
+
+ printk("Formatting chain at block %d\n", first_block);
+
+ for (;;) {
+ block1 = nftl->ReplUnitTable[block];
+
+ printk("Formatting block %d\n", block);
+ if (NFTL_formatblock(nftl, block) < 0) {
+ /* cannot format !!!! Mark it as Bad Unit */
+ nftl->ReplUnitTable[block] = BLOCK_RESERVED;
+ } else {
+ nftl->ReplUnitTable[block] = BLOCK_FREE;
+ }
+
+ /* goto next block on the chain */
+ block = block1;
+
+ if (!(block == BLOCK_NIL || block < nftl->nb_blocks))
+ printk("incorrect ReplUnitTable[] : %d\n", block);
+ if (block == BLOCK_NIL || block >= nftl->nb_blocks)
+ break;
+ }
+}
+
+/* check_and_mark_free_block: Verify that a block is free in the NFTL sense (valid erase mark) or
+ * totally free (only 0xff).
+ *
+ * Definition: Free Erase Unit -- A properly erased/formatted Free Erase Unit should have meet the
+ * following critia:
+ * 1. */
+static int check_and_mark_free_block(struct NFTLrecord *nftl, int block)
+{
+ struct nftl_uci1 h1;
+ unsigned int erase_mark;
+ size_t retlen;
+
+ /* check erase mark. */
+ if (MTD_READOOB(nftl->mbd.mtd, block * nftl->EraseSize + SECTORSIZE + 8, 8,
+ &retlen, (char *)&h1) < 0)
+ return -1;
+
+ erase_mark = le16_to_cpu ((h1.EraseMark | h1.EraseMark1));
+ if (erase_mark != ERASE_MARK) {
+ /* if no erase mark, the block must be totally free. This is
+ possible in two cases : empty filsystem or interrupted erase (very unlikely) */
+ if (check_free_sectors (nftl, block * nftl->EraseSize, nftl->EraseSize, 1) != 0)
+ return -1;
+
+ /* free block : write erase mark */
+ h1.EraseMark = cpu_to_le16(ERASE_MARK);
+ h1.EraseMark1 = cpu_to_le16(ERASE_MARK);
+ h1.WearInfo = cpu_to_le32(0);
+ if (MTD_WRITEOOB(nftl->mbd.mtd, block * nftl->EraseSize + SECTORSIZE + 8, 8,
+ &retlen, (char *)&h1) < 0)
+ return -1;
+ } else {
+#if 0
+ /* if erase mark present, need to skip it when doing check */
+ for (i = 0; i < nftl->EraseSize; i += SECTORSIZE) {
+ /* check free sector */
+ if (check_free_sectors (nftl, block * nftl->EraseSize + i,
+ SECTORSIZE, 0) != 0)
+ return -1;
+
+ if (MTD_READOOB(nftl->mbd.mtd, block * nftl->EraseSize + i,
+ 16, &retlen, buf) < 0)
+ return -1;
+ if (i == SECTORSIZE) {
+ /* skip erase mark */
+ if (memcmpb(buf, 0xff, 8))
+ return -1;
+ } else {
+ if (memcmpb(buf, 0xff, 16))
+ return -1;
+ }
+ }
+#endif
+ }
+
+ return 0;
+}
+
+/* get_fold_mark: Read fold mark from Unit Control Information #2, we use FOLD_MARK_IN_PROGRESS
+ * to indicate that we are in the progression of a Virtual Unit Chain folding. If the UCI #2
+ * is FOLD_MARK_IN_PROGRESS when mounting the NFTL, the (previous) folding process is interrupted
+ * for some reason. A clean up/check of the VUC is neceressary in this case.
+ *
+ * WARNING: return 0 if read error
+ */
+static int get_fold_mark(struct NFTLrecord *nftl, unsigned int block)
+{
+ struct nftl_uci2 uci;
+ size_t retlen;
+
+ if (MTD_READOOB(nftl->mbd.mtd, block * nftl->EraseSize + 2 * SECTORSIZE + 8,
+ 8, &retlen, (char *)&uci) < 0)
+ return 0;
+
+ return le16_to_cpu((uci.FoldMark | uci.FoldMark1));
+}
+
+int NFTL_mount(struct NFTLrecord *s)
+{
+ int i;
+ unsigned int first_logical_block, logical_block, rep_block, nb_erases, erase_mark;
+ unsigned int block, first_block, is_first_block;
+ int chain_length, do_format_chain;
+ struct nftl_uci0 h0;
+ struct nftl_uci1 h1;
+ size_t retlen;
+
+ /* search for NFTL MediaHeader and Spare NFTL Media Header */
+ if (find_boot_record(s) < 0) {
+ printk("Could not find valid boot record\n");
+ return -1;
+ }
+
+ /* init the logical to physical table */
+ for (i = 0; i < s->nb_blocks; i++) {
+ s->EUNtable[i] = BLOCK_NIL;
+ }
+
+ /* first pass : explore each block chain */
+ first_logical_block = 0;
+ for (first_block = 0; first_block < s->nb_blocks; first_block++) {
+ /* if the block was not already explored, we can look at it */
+ if (s->ReplUnitTable[first_block] == BLOCK_NOTEXPLORED) {
+ block = first_block;
+ chain_length = 0;
+ do_format_chain = 0;
+
+ for (;;) {
+ /* read the block header. If error, we format the chain */
+ if (MTD_READOOB(s->mbd.mtd, block * s->EraseSize + 8, 8,
+ &retlen, (char *)&h0) < 0 ||
+ MTD_READOOB(s->mbd.mtd, block * s->EraseSize + SECTORSIZE + 8, 8,
+ &retlen, (char *)&h1) < 0) {
+ s->ReplUnitTable[block] = BLOCK_NIL;
+ do_format_chain = 1;
+ break;
+ }
+
+ logical_block = le16_to_cpu ((h0.VirtUnitNum | h0.SpareVirtUnitNum));
+ rep_block = le16_to_cpu ((h0.ReplUnitNum | h0.SpareReplUnitNum));
+ nb_erases = le32_to_cpu (h1.WearInfo);
+ erase_mark = le16_to_cpu ((h1.EraseMark | h1.EraseMark1));
+
+ is_first_block = !(logical_block >> 15);
+ logical_block = logical_block & 0x7fff;
+
+ /* invalid/free block test */
+ if (erase_mark != ERASE_MARK || logical_block >= s->nb_blocks) {
+ if (chain_length == 0) {
+ /* if not currently in a chain, we can handle it safely */
+ if (check_and_mark_free_block(s, block) < 0) {
+ /* not really free: format it */
+ printk("Formatting block %d\n", block);
+ if (NFTL_formatblock(s, block) < 0) {
+ /* could not format: reserve the block */
+ s->ReplUnitTable[block] = BLOCK_RESERVED;
+ } else {
+ s->ReplUnitTable[block] = BLOCK_FREE;
+ }
+ } else {
+ /* free block: mark it */
+ s->ReplUnitTable[block] = BLOCK_FREE;
+ }
+ /* directly examine the next block. */
+ goto examine_ReplUnitTable;
+ } else {
+ /* the block was in a chain : this is bad. We
+ must format all the chain */
+ printk("Block %d: free but referenced in chain %d\n",
+ block, first_block);
+ s->ReplUnitTable[block] = BLOCK_NIL;
+ do_format_chain = 1;
+ break;
+ }
+ }
+
+ /* we accept only first blocks here */
+ if (chain_length == 0) {
+ /* this block is not the first block in chain :
+ ignore it, it will be included in a chain
+ later, or marked as not explored */
+ if (!is_first_block)
+ goto examine_ReplUnitTable;
+ first_logical_block = logical_block;
+ } else {
+ if (logical_block != first_logical_block) {
+ printk("Block %d: incorrect logical block: %d expected: %d\n",
+ block, logical_block, first_logical_block);
+ /* the chain is incorrect : we must format it,
+ but we need to read it completly */
+ do_format_chain = 1;
+ }
+ if (is_first_block) {
+ /* we accept that a block is marked as first
+ block while being last block in a chain
+ only if the chain is being folded */
+ if (get_fold_mark(s, block) != FOLD_MARK_IN_PROGRESS ||
+ rep_block != 0xffff) {
+ printk("Block %d: incorrectly marked as first block in chain\n",
+ block);
+ /* the chain is incorrect : we must format it,
+ but we need to read it completly */
+ do_format_chain = 1;
+ } else {
+ printk("Block %d: folding in progress - ignoring first block flag\n",
+ block);
+ }
+ }
+ }
+ chain_length++;
+ if (rep_block == 0xffff) {
+ /* no more blocks after */
+ s->ReplUnitTable[block] = BLOCK_NIL;
+ break;
+ } else if (rep_block >= s->nb_blocks) {
+ printk("Block %d: referencing invalid block %d\n",
+ block, rep_block);
+ do_format_chain = 1;
+ s->ReplUnitTable[block] = BLOCK_NIL;
+ break;
+ } else if (s->ReplUnitTable[rep_block] != BLOCK_NOTEXPLORED) {
+ /* same problem as previous 'is_first_block' test:
+ we accept that the last block of a chain has
+ the first_block flag set if folding is in
+ progress. We handle here the case where the
+ last block appeared first */
+ if (s->ReplUnitTable[rep_block] == BLOCK_NIL &&
+ s->EUNtable[first_logical_block] == rep_block &&
+ get_fold_mark(s, first_block) == FOLD_MARK_IN_PROGRESS) {
+ /* EUNtable[] will be set after */
+ printk("Block %d: folding in progress - ignoring first block flag\n",
+ rep_block);
+ s->ReplUnitTable[block] = rep_block;
+ s->EUNtable[first_logical_block] = BLOCK_NIL;
+ } else {
+ printk("Block %d: referencing block %d already in another chain\n",
+ block, rep_block);
+ /* XXX: should handle correctly fold in progress chains */
+ do_format_chain = 1;
+ s->ReplUnitTable[block] = BLOCK_NIL;
+ }
+ break;
+ } else {
+ /* this is OK */
+ s->ReplUnitTable[block] = rep_block;
+ block = rep_block;
+ }
+ }
+
+ /* the chain was completely explored. Now we can decide
+ what to do with it */
+ if (do_format_chain) {
+ /* invalid chain : format it */
+ format_chain(s, first_block);
+ } else {
+ unsigned int first_block1, chain_to_format, chain_length1;
+ int fold_mark;
+
+ /* valid chain : get foldmark */
+ fold_mark = get_fold_mark(s, first_block);
+ if (fold_mark == 0) {
+ /* cannot get foldmark : format the chain */
+ printk("Could read foldmark at block %d\n", first_block);
+ format_chain(s, first_block);
+ } else {
+ if (fold_mark == FOLD_MARK_IN_PROGRESS)
+ check_sectors_in_chain(s, first_block);
+
+ /* now handle the case where we find two chains at the
+ same virtual address : we select the longer one,
+ because the shorter one is the one which was being
+ folded if the folding was not done in place */
+ first_block1 = s->EUNtable[first_logical_block];
+ if (first_block1 != BLOCK_NIL) {
+ /* XXX: what to do if same length ? */
+ chain_length1 = calc_chain_length(s, first_block1);
+ printk("Two chains at blocks %d (len=%d) and %d (len=%d)\n",
+ first_block1, chain_length1, first_block, chain_length);
+
+ if (chain_length >= chain_length1) {
+ chain_to_format = first_block1;
+ s->EUNtable[first_logical_block] = first_block;
+ } else {
+ chain_to_format = first_block;
+ }
+ format_chain(s, chain_to_format);
+ } else {
+ s->EUNtable[first_logical_block] = first_block;
+ }
+ }
+ }
+ }
+ examine_ReplUnitTable:;
+ }
+
+ /* second pass to format unreferenced blocks and init free block count */
+ s->numfreeEUNs = 0;
+ s->LastFreeEUN = le16_to_cpu(s->MediaHdr.FirstPhysicalEUN);
+
+ for (block = 0; block < s->nb_blocks; block++) {
+ if (s->ReplUnitTable[block] == BLOCK_NOTEXPLORED) {
+ printk("Unreferenced block %d, formatting it\n", block);
+ if (NFTL_formatblock(s, block) < 0)
+ s->ReplUnitTable[block] = BLOCK_RESERVED;
+ else
+ s->ReplUnitTable[block] = BLOCK_FREE;
+ }
+ if (s->ReplUnitTable[block] == BLOCK_FREE) {
+ s->numfreeEUNs++;
+ s->LastFreeEUN = block;
+ }
+ }
+
+ return 0;
+}
diff --git a/drivers/mtd/redboot.c b/drivers/mtd/redboot.c
new file mode 100644
index 000000000000..13f9e992bef8
--- /dev/null
+++ b/drivers/mtd/redboot.c
@@ -0,0 +1,235 @@
+/*
+ * $Id: redboot.c,v 1.17 2004/11/22 11:33:56 ijc Exp $
+ *
+ * Parse RedBoot-style Flash Image System (FIS) tables and
+ * produce a Linux partition array to match.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/vmalloc.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+
+struct fis_image_desc {
+ unsigned char name[16]; // Null terminated name
+ unsigned long flash_base; // Address within FLASH of image
+ unsigned long mem_base; // Address in memory where it executes
+ unsigned long size; // Length of image
+ unsigned long entry_point; // Execution entry point
+ unsigned long data_length; // Length of actual data
+ unsigned char _pad[256-(16+7*sizeof(unsigned long))];
+ unsigned long desc_cksum; // Checksum over image descriptor
+ unsigned long file_cksum; // Checksum over image data
+};
+
+struct fis_list {
+ struct fis_image_desc *img;
+ struct fis_list *next;
+};
+
+static int directory = CONFIG_MTD_REDBOOT_DIRECTORY_BLOCK;
+module_param(directory, int, 0);
+
+static inline int redboot_checksum(struct fis_image_desc *img)
+{
+ /* RedBoot doesn't actually write the desc_cksum field yet AFAICT */
+ return 1;
+}
+
+static int parse_redboot_partitions(struct mtd_info *master,
+ struct mtd_partition **pparts,
+ unsigned long fis_origin)
+{
+ int nrparts = 0;
+ struct fis_image_desc *buf;
+ struct mtd_partition *parts;
+ struct fis_list *fl = NULL, *tmp_fl;
+ int ret, i;
+ size_t retlen;
+ char *names;
+ char *nullname;
+ int namelen = 0;
+ int nulllen = 0;
+ int numslots;
+ unsigned long offset;
+#ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
+ static char nullstring[] = "unallocated";
+#endif
+
+ buf = vmalloc(master->erasesize);
+
+ if (!buf)
+ return -ENOMEM;
+
+ if ( directory < 0 )
+ offset = master->size + directory*master->erasesize;
+ else
+ offset = directory*master->erasesize;
+
+ printk(KERN_NOTICE "Searching for RedBoot partition table in %s at offset 0x%lx\n",
+ master->name, offset);
+
+ ret = master->read(master, offset,
+ master->erasesize, &retlen, (void *)buf);
+
+ if (ret)
+ goto out;
+
+ if (retlen != master->erasesize) {
+ ret = -EIO;
+ goto out;
+ }
+
+ numslots = (master->erasesize / sizeof(struct fis_image_desc));
+ for (i = 0; i < numslots; i++) {
+ if (buf[i].name[0] == 0xff) {
+ i = numslots;
+ break;
+ }
+ if (!memcmp(buf[i].name, "FIS directory", 14))
+ break;
+ }
+ if (i == numslots) {
+ /* Didn't find it */
+ printk(KERN_NOTICE "No RedBoot partition table detected in %s\n",
+ master->name);
+ ret = 0;
+ goto out;
+ }
+
+ for (i = 0; i < numslots; i++) {
+ struct fis_list *new_fl, **prev;
+
+ if (buf[i].name[0] == 0xff)
+ break;
+ if (!redboot_checksum(&buf[i]))
+ break;
+
+ new_fl = kmalloc(sizeof(struct fis_list), GFP_KERNEL);
+ namelen += strlen(buf[i].name)+1;
+ if (!new_fl) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ new_fl->img = &buf[i];
+ if (fis_origin) {
+ buf[i].flash_base -= fis_origin;
+ } else {
+ buf[i].flash_base &= master->size-1;
+ }
+
+ /* I'm sure the JFFS2 code has done me permanent damage.
+ * I now think the following is _normal_
+ */
+ prev = &fl;
+ while(*prev && (*prev)->img->flash_base < new_fl->img->flash_base)
+ prev = &(*prev)->next;
+ new_fl->next = *prev;
+ *prev = new_fl;
+
+ nrparts++;
+ }
+#ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
+ if (fl->img->flash_base) {
+ nrparts++;
+ nulllen = sizeof(nullstring);
+ }
+
+ for (tmp_fl = fl; tmp_fl->next; tmp_fl = tmp_fl->next) {
+ if (tmp_fl->img->flash_base + tmp_fl->img->size + master->erasesize <= tmp_fl->next->img->flash_base) {
+ nrparts++;
+ nulllen = sizeof(nullstring);
+ }
+ }
+#endif
+ parts = kmalloc(sizeof(*parts)*nrparts + nulllen + namelen, GFP_KERNEL);
+
+ if (!parts) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ memset(parts, 0, sizeof(*parts)*nrparts + nulllen + namelen);
+
+ nullname = (char *)&parts[nrparts];
+#ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
+ if (nulllen > 0) {
+ strcpy(nullname, nullstring);
+ }
+#endif
+ names = nullname + nulllen;
+
+ i=0;
+
+#ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
+ if (fl->img->flash_base) {
+ parts[0].name = nullname;
+ parts[0].size = fl->img->flash_base;
+ parts[0].offset = 0;
+ i++;
+ }
+#endif
+ for ( ; i<nrparts; i++) {
+ parts[i].size = fl->img->size;
+ parts[i].offset = fl->img->flash_base;
+ parts[i].name = names;
+
+ strcpy(names, fl->img->name);
+#ifdef CONFIG_MTD_REDBOOT_PARTS_READONLY
+ if (!memcmp(names, "RedBoot", 8) ||
+ !memcmp(names, "RedBoot config", 15) ||
+ !memcmp(names, "FIS directory", 14)) {
+ parts[i].mask_flags = MTD_WRITEABLE;
+ }
+#endif
+ names += strlen(names)+1;
+
+#ifdef CONFIG_MTD_REDBOOT_PARTS_UNALLOCATED
+ if(fl->next && fl->img->flash_base + fl->img->size + master->erasesize <= fl->next->img->flash_base) {
+ i++;
+ parts[i].offset = parts[i-1].size + parts[i-1].offset;
+ parts[i].size = fl->next->img->flash_base - parts[i].offset;
+ parts[i].name = nullname;
+ }
+#endif
+ tmp_fl = fl;
+ fl = fl->next;
+ kfree(tmp_fl);
+ }
+ ret = nrparts;
+ *pparts = parts;
+ out:
+ while (fl) {
+ struct fis_list *old = fl;
+ fl = fl->next;
+ kfree(old);
+ }
+ vfree(buf);
+ return ret;
+}
+
+static struct mtd_part_parser redboot_parser = {
+ .owner = THIS_MODULE,
+ .parse_fn = parse_redboot_partitions,
+ .name = "RedBoot",
+};
+
+static int __init redboot_parser_init(void)
+{
+ return register_mtd_parser(&redboot_parser);
+}
+
+static void __exit redboot_parser_exit(void)
+{
+ deregister_mtd_parser(&redboot_parser);
+}
+
+module_init(redboot_parser_init);
+module_exit(redboot_parser_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Red Hat, Inc. - David Woodhouse <dwmw2@cambridge.redhat.com>");
+MODULE_DESCRIPTION("Parsing code for RedBoot Flash Image System (FIS) tables");