aboutsummaryrefslogtreecommitdiff
path: root/drivers/mtd/nand
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd/nand')
-rw-r--r--drivers/mtd/nand/Kconfig31
-rw-r--r--drivers/mtd/nand/Makefile2
-rw-r--r--drivers/mtd/nand/alauda.c742
-rw-r--r--drivers/mtd/nand/bf5xx_nand.c788
-rw-r--r--drivers/mtd/nand/cafe_nand.c53
-rw-r--r--drivers/mtd/nand/diskonchip.c2
-rw-r--r--drivers/mtd/nand/excite_nandflash.c1
-rw-r--r--drivers/mtd/nand/nand_base.c9
-rw-r--r--drivers/mtd/nand/nand_ids.c1
-rw-r--r--drivers/mtd/nand/nandsim.c8
-rw-r--r--drivers/mtd/nand/ndfc.c8
-rw-r--r--drivers/mtd/nand/s3c2410.c4
12 files changed, 1630 insertions, 19 deletions
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index f1d60b6f048..8f9c3baeb38 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -91,6 +91,25 @@ config MTD_NAND_AU1550
This enables the driver for the NAND flash controller on the
AMD/Alchemy 1550 SOC.
+config MTD_NAND_BF5XX
+ tristate "Blackfin on-chip NAND Flash Controller driver"
+ depends on BF54x && MTD_NAND
+ help
+ This enables the Blackfin on-chip NAND flash controller
+
+ No board specific support is done by this driver, each board
+ must advertise a platform_device for the driver to attach.
+
+ This driver can also be built as a module. If so, the module
+ will be called bf5xx-nand.
+
+config MTD_NAND_BF5XX_HWECC
+ bool "BF5XX NAND Hardware ECC"
+ depends on MTD_NAND_BF5XX
+ help
+ Enable the use of the BF5XX's internal ECC generator when
+ using NAND.
+
config MTD_NAND_RTC_FROM4
tristate "Renesas Flash ROM 4-slot interface board (FROM_BOARD4)"
depends on SH_SOLUTION_ENGINE
@@ -134,10 +153,10 @@ config MTD_NAND_S3C2410_HWECC
config MTD_NAND_NDFC
tristate "NDFC NanD Flash Controller"
- depends on 44x
+ depends on 4xx && !PPC_MERGE
select MTD_NAND_ECC_SMC
help
- NDFC Nand Flash Controllers are integrated in EP44x SoCs
+ NDFC Nand Flash Controllers are integrated in IBM/AMCC's 4xx SoCs
config MTD_NAND_S3C2410_CLKSTOP
bool "S3C2410 NAND IDLE clock stop"
@@ -237,7 +256,7 @@ config MTD_NAND_CAFE
select REED_SOLOMON
select REED_SOLOMON_DEC16
help
- Use NAND flash attached to the CAFÉ chip designed for the $100
+ Use NAND flash attached to the CAFÉ chip designed for the OLPC
laptop.
config MTD_NAND_CS553X
@@ -280,5 +299,11 @@ config MTD_NAND_PLATFORM
devices. You will need to provide platform-specific functions
via platform_data.
+config MTD_ALAUDA
+ tristate "MTD driver for Olympus MAUSB-10 and Fijufilm DPC-R1"
+ depends on MTD_NAND && USB
+ help
+ These two (and possibly other) Alauda-based cardreaders for
+ SmartMedia and xD allow raw flash access.
endif # MTD_NAND
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index edba1db14bf..3ad6c0165da 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -13,6 +13,7 @@ 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_AU1550) += au1550nd.o
+obj-$(CONFIG_MTD_NAND_BF5XX) += bf5xx_nand.o
obj-$(CONFIG_MTD_NAND_PPCHAMELEONEVB) += ppchameleonevb.o
obj-$(CONFIG_MTD_NAND_S3C2410) += s3c2410.o
obj-$(CONFIG_MTD_NAND_DISKONCHIP) += diskonchip.o
@@ -27,5 +28,6 @@ obj-$(CONFIG_MTD_NAND_AT91) += at91_nand.o
obj-$(CONFIG_MTD_NAND_CM_X270) += cmx270_nand.o
obj-$(CONFIG_MTD_NAND_BASLER_EXCITE) += excite_nandflash.o
obj-$(CONFIG_MTD_NAND_PLATFORM) += plat_nand.o
+obj-$(CONFIG_MTD_ALAUDA) += alauda.o
nand-objs := nand_base.o nand_bbt.o
diff --git a/drivers/mtd/nand/alauda.c b/drivers/mtd/nand/alauda.c
new file mode 100644
index 00000000000..257937cd99b
--- /dev/null
+++ b/drivers/mtd/nand/alauda.c
@@ -0,0 +1,742 @@
+/*
+ * MTD driver for Alauda chips
+ *
+ * Copyright (C) 2007 Joern Engel <joern@logfs.org>
+ *
+ * Based on drivers/usb/usb-skeleton.c which is:
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
+ * and on drivers/usb/storage/alauda.c, which is:
+ * (c) 2005 Daniel Drake <dsd@gentoo.org>
+ *
+ * Idea and initial work by Arnd Bergmann <arnd@arndb.de>
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand_ecc.h>
+
+/* Control commands */
+#define ALAUDA_GET_XD_MEDIA_STATUS 0x08
+#define ALAUDA_ACK_XD_MEDIA_CHANGE 0x0a
+#define ALAUDA_GET_XD_MEDIA_SIG 0x86
+
+/* Common prefix */
+#define ALAUDA_BULK_CMD 0x40
+
+/* The two ports */
+#define ALAUDA_PORT_XD 0x00
+#define ALAUDA_PORT_SM 0x01
+
+/* Bulk commands */
+#define ALAUDA_BULK_READ_PAGE 0x84
+#define ALAUDA_BULK_READ_OOB 0x85 /* don't use, there's a chip bug */
+#define ALAUDA_BULK_READ_BLOCK 0x94
+#define ALAUDA_BULK_ERASE_BLOCK 0xa3
+#define ALAUDA_BULK_WRITE_PAGE 0xa4
+#define ALAUDA_BULK_WRITE_BLOCK 0xb4
+#define ALAUDA_BULK_RESET_MEDIA 0xe0
+
+/* Address shifting */
+#define PBA_LO(pba) ((pba & 0xF) << 5)
+#define PBA_HI(pba) (pba >> 3)
+#define PBA_ZONE(pba) (pba >> 11)
+
+#define TIMEOUT HZ
+
+static struct usb_device_id alauda_table [] = {
+ { USB_DEVICE(0x0584, 0x0008) }, /* Fujifilm DPC-R1 */
+ { USB_DEVICE(0x07b4, 0x010a) }, /* Olympus MAUSB-10 */
+ { }
+};
+MODULE_DEVICE_TABLE(usb, alauda_table);
+
+struct alauda_card {
+ u8 id; /* id byte */
+ u8 chipshift; /* 1<<chipshift total size */
+ u8 pageshift; /* 1<<pageshift page size */
+ u8 blockshift; /* 1<<blockshift block size */
+};
+
+struct alauda {
+ struct usb_device *dev;
+ struct usb_interface *interface;
+ struct mtd_info *mtd;
+ struct alauda_card *card;
+ struct mutex card_mutex;
+ u32 pagemask;
+ u32 bytemask;
+ u32 blockmask;
+ unsigned int write_out;
+ unsigned int bulk_in;
+ unsigned int bulk_out;
+ u8 port;
+ struct kref kref;
+};
+
+static struct alauda_card alauda_card_ids[] = {
+ /* NAND flash */
+ { 0x6e, 20, 8, 12}, /* 1 MB */
+ { 0xe8, 20, 8, 12}, /* 1 MB */
+ { 0xec, 20, 8, 12}, /* 1 MB */
+ { 0x64, 21, 8, 12}, /* 2 MB */
+ { 0xea, 21, 8, 12}, /* 2 MB */
+ { 0x6b, 22, 9, 13}, /* 4 MB */
+ { 0xe3, 22, 9, 13}, /* 4 MB */
+ { 0xe5, 22, 9, 13}, /* 4 MB */
+ { 0xe6, 23, 9, 13}, /* 8 MB */
+ { 0x73, 24, 9, 14}, /* 16 MB */
+ { 0x75, 25, 9, 14}, /* 32 MB */
+ { 0x76, 26, 9, 14}, /* 64 MB */
+ { 0x79, 27, 9, 14}, /* 128 MB */
+ { 0x71, 28, 9, 14}, /* 256 MB */
+
+ /* MASK ROM */
+ { 0x5d, 21, 9, 13}, /* 2 MB */
+ { 0xd5, 22, 9, 13}, /* 4 MB */
+ { 0xd6, 23, 9, 13}, /* 8 MB */
+ { 0x57, 24, 9, 13}, /* 16 MB */
+ { 0x58, 25, 9, 13}, /* 32 MB */
+ { }
+};
+
+static struct alauda_card *get_card(u8 id)
+{
+ struct alauda_card *card;
+
+ for (card = alauda_card_ids; card->id; card++)
+ if (card->id == id)
+ return card;
+ return NULL;
+}
+
+static void alauda_delete(struct kref *kref)
+{
+ struct alauda *al = container_of(kref, struct alauda, kref);
+
+ if (al->mtd) {
+ del_mtd_device(al->mtd);
+ kfree(al->mtd);
+ }
+ usb_put_dev(al->dev);
+ kfree(al);
+}
+
+static int alauda_get_media_status(struct alauda *al, void *buf)
+{
+ int ret;
+
+ mutex_lock(&al->card_mutex);
+ ret = usb_control_msg(al->dev, usb_rcvctrlpipe(al->dev, 0),
+ ALAUDA_GET_XD_MEDIA_STATUS, 0xc0, 0, 1, buf, 2, HZ);
+ mutex_unlock(&al->card_mutex);
+ return ret;
+}
+
+static int alauda_ack_media(struct alauda *al)
+{
+ int ret;
+
+ mutex_lock(&al->card_mutex);
+ ret = usb_control_msg(al->dev, usb_sndctrlpipe(al->dev, 0),
+ ALAUDA_ACK_XD_MEDIA_CHANGE, 0x40, 0, 1, NULL, 0, HZ);
+ mutex_unlock(&al->card_mutex);
+ return ret;
+}
+
+static int alauda_get_media_signatures(struct alauda *al, void *buf)
+{
+ int ret;
+
+ mutex_lock(&al->card_mutex);
+ ret = usb_control_msg(al->dev, usb_rcvctrlpipe(al->dev, 0),
+ ALAUDA_GET_XD_MEDIA_SIG, 0xc0, 0, 0, buf, 4, HZ);
+ mutex_unlock(&al->card_mutex);
+ return ret;
+}
+
+static void alauda_reset(struct alauda *al)
+{
+ u8 command[] = {
+ ALAUDA_BULK_CMD, ALAUDA_BULK_RESET_MEDIA, 0, 0,
+ 0, 0, 0, 0, al->port
+ };
+ mutex_lock(&al->card_mutex);
+ usb_bulk_msg(al->dev, al->bulk_out, command, 9, NULL, HZ);
+ mutex_unlock(&al->card_mutex);
+}
+
+static void correct_data(void *buf, void *read_ecc,
+ int *corrected, int *uncorrected)
+{
+ u8 calc_ecc[3];
+ int err;
+
+ nand_calculate_ecc(NULL, buf, calc_ecc);
+ err = nand_correct_data(NULL, buf, read_ecc, calc_ecc);
+ if (err) {
+ if (err > 0)
+ (*corrected)++;
+ else
+ (*uncorrected)++;
+ }
+}
+
+struct alauda_sg_request {
+ struct urb *urb[3];
+ struct completion comp;
+};
+
+static void alauda_complete(struct urb *urb)
+{
+ struct completion *comp = urb->context;
+
+ if (comp)
+ complete(comp);
+}
+
+static int __alauda_read_page(struct mtd_info *mtd, loff_t from, void *buf,
+ void *oob)
+{
+ struct alauda_sg_request sg;
+ struct alauda *al = mtd->priv;
+ u32 pba = from >> al->card->blockshift;
+ u32 page = (from >> al->card->pageshift) & al->pagemask;
+ u8 command[] = {
+ ALAUDA_BULK_CMD, ALAUDA_BULK_READ_PAGE, PBA_HI(pba),
+ PBA_ZONE(pba), 0, PBA_LO(pba) + page, 1, 0, al->port
+ };
+ int i, err;
+
+ for (i=0; i<3; i++)
+ sg.urb[i] = NULL;
+
+ err = -ENOMEM;
+ for (i=0; i<3; i++) {
+ sg.urb[i] = usb_alloc_urb(0, GFP_NOIO);
+ if (!sg.urb[i])
+ goto out;
+ }
+ init_completion(&sg.comp);
+ usb_fill_bulk_urb(sg.urb[0], al->dev, al->bulk_out, command, 9,
+ alauda_complete, NULL);
+ usb_fill_bulk_urb(sg.urb[1], al->dev, al->bulk_in, buf, mtd->writesize,
+ alauda_complete, NULL);
+ usb_fill_bulk_urb(sg.urb[2], al->dev, al->bulk_in, oob, 16,
+ alauda_complete, &sg.comp);
+
+ mutex_lock(&al->card_mutex);
+ for (i=0; i<3; i++) {
+ err = usb_submit_urb(sg.urb[i], GFP_NOIO);
+ if (err)
+ goto cancel;
+ }
+ if (!wait_for_completion_timeout(&sg.comp, TIMEOUT)) {
+ err = -ETIMEDOUT;
+cancel:
+ for (i=0; i<3; i++) {
+ usb_kill_urb(sg.urb[i]);
+ }
+ }
+ mutex_unlock(&al->card_mutex);
+
+out:
+ usb_free_urb(sg.urb[0]);
+ usb_free_urb(sg.urb[1]);
+ usb_free_urb(sg.urb[2]);
+ return err;
+}
+
+static int alauda_read_page(struct mtd_info *mtd, loff_t from,
+ void *buf, u8 *oob, int *corrected, int *uncorrected)
+{
+ int err;
+
+ err = __alauda_read_page(mtd, from, buf, oob);
+ if (err)
+ return err;
+ correct_data(buf, oob+13, corrected, uncorrected);
+ correct_data(buf+256, oob+8, corrected, uncorrected);
+ return 0;
+}
+
+static int alauda_write_page(struct mtd_info *mtd, loff_t to, void *buf,
+ void *oob)
+{
+ struct alauda_sg_request sg;
+ struct alauda *al = mtd->priv;
+ u32 pba = to >> al->card->blockshift;
+ u32 page = (to >> al->card->pageshift) & al->pagemask;
+ u8 command[] = {
+ ALAUDA_BULK_CMD, ALAUDA_BULK_WRITE_PAGE, PBA_HI(pba),
+ PBA_ZONE(pba), 0, PBA_LO(pba) + page, 32, 0, al->port
+ };
+ int i, err;
+
+ for (i=0; i<3; i++)
+ sg.urb[i] = NULL;
+
+ err = -ENOMEM;
+ for (i=0; i<3; i++) {
+ sg.urb[i] = usb_alloc_urb(0, GFP_NOIO);
+ if (!sg.urb[i])
+ goto out;
+ }
+ init_completion(&sg.comp);
+ usb_fill_bulk_urb(sg.urb[0], al->dev, al->bulk_out, command, 9,
+ alauda_complete, NULL);
+ usb_fill_bulk_urb(sg.urb[1], al->dev, al->write_out, buf,mtd->writesize,
+ alauda_complete, NULL);
+ usb_fill_bulk_urb(sg.urb[2], al->dev, al->write_out, oob, 16,
+ alauda_complete, &sg.comp);
+
+ mutex_lock(&al->card_mutex);
+ for (i=0; i<3; i++) {
+ err = usb_submit_urb(sg.urb[i], GFP_NOIO);
+ if (err)
+ goto cancel;
+ }
+ if (!wait_for_completion_timeout(&sg.comp, TIMEOUT)) {
+ err = -ETIMEDOUT;
+cancel:
+ for (i=0; i<3; i++) {
+ usb_kill_urb(sg.urb[i]);
+ }
+ }
+ mutex_unlock(&al->card_mutex);
+
+out:
+ usb_free_urb(sg.urb[0]);
+ usb_free_urb(sg.urb[1]);
+ usb_free_urb(sg.urb[2]);
+ return err;
+}
+
+static int alauda_erase_block(struct mtd_info *mtd, loff_t ofs)
+{
+ struct alauda_sg_request sg;
+ struct alauda *al = mtd->priv;
+ u32 pba = ofs >> al->card->blockshift;
+ u8 command[] = {
+ ALAUDA_BULK_CMD, ALAUDA_BULK_ERASE_BLOCK, PBA_HI(pba),
+ PBA_ZONE(pba), 0, PBA_LO(pba), 0x02, 0, al->port
+ };
+ u8 buf[2];
+ int i, err;
+
+ for (i=0; i<2; i++)
+ sg.urb[i] = NULL;
+
+ err = -ENOMEM;
+ for (i=0; i<2; i++) {
+ sg.urb[i] = usb_alloc_urb(0, GFP_NOIO);
+ if (!sg.urb[i])
+ goto out;
+ }
+ init_completion(&sg.comp);
+ usb_fill_bulk_urb(sg.urb[0], al->dev, al->bulk_out, command, 9,
+ alauda_complete, NULL);
+ usb_fill_bulk_urb(sg.urb[1], al->dev, al->bulk_in, buf, 2,
+ alauda_complete, &sg.comp);
+
+ mutex_lock(&al->card_mutex);
+ for (i=0; i<2; i++) {
+ err = usb_submit_urb(sg.urb[i], GFP_NOIO);
+ if (err)
+ goto cancel;
+ }
+ if (!wait_for_completion_timeout(&sg.comp, TIMEOUT)) {
+ err = -ETIMEDOUT;
+cancel:
+ for (i=0; i<2; i++) {
+ usb_kill_urb(sg.urb[i]);
+ }
+ }
+ mutex_unlock(&al->card_mutex);
+
+out:
+ usb_free_urb(sg.urb[0]);
+ usb_free_urb(sg.urb[1]);
+ return err;
+}
+
+static int alauda_read_oob(struct mtd_info *mtd, loff_t from, void *oob)
+{
+ static u8 ignore_buf[512]; /* write only */
+
+ return __alauda_read_page(mtd, from, ignore_buf, oob);
+}
+
+static int popcount8(u8 c)
+{
+ int ret = 0;
+
+ for ( ; c; c>>=1)
+ ret += c & 1;
+ return ret;
+}
+
+static int alauda_isbad(struct mtd_info *mtd, loff_t ofs)
+{
+ u8 oob[16];
+ int err;
+
+ err = alauda_read_oob(mtd, ofs, oob);
+ if (err)
+ return err;
+
+ /* A block is marked bad if two or more bits are zero */
+ return popcount8(oob[5]) >= 7 ? 0 : 1;
+}
+
+static int alauda_bounce_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct alauda *al = mtd->priv;
+ void *bounce_buf;
+ int err, corrected=0, uncorrected=0;
+
+ bounce_buf = kmalloc(mtd->writesize, GFP_KERNEL);
+ if (!bounce_buf)
+ return -ENOMEM;
+
+ *retlen = len;
+ while (len) {
+ u8 oob[16];
+ size_t byte = from & al->bytemask;
+ size_t cplen = min(len, mtd->writesize - byte);
+
+ err = alauda_read_page(mtd, from, bounce_buf, oob,
+ &corrected, &uncorrected);
+ if (err)
+ goto out;
+
+ memcpy(buf, bounce_buf + byte, cplen);
+ buf += cplen;
+ from += cplen;
+ len -= cplen;
+ }
+ err = 0;
+ if (corrected)
+ err = -EUCLEAN;
+ if (uncorrected)
+ err = -EBADMSG;
+out:
+ kfree(bounce_buf);
+ return err;
+}
+
+static int alauda_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct alauda *al = mtd->priv;
+ int err, corrected=0, uncorrected=0;
+
+ if ((from & al->bytemask) || (len & al->bytemask))
+ return alauda_bounce_read(mtd, from, len, retlen, buf);
+
+ *retlen = len;
+ while (len) {
+ u8 oob[16];
+
+ err = alauda_read_page(mtd, from, buf, oob,
+ &corrected, &uncorrected);
+ if (err)
+ return err;
+
+ buf += mtd->writesize;
+ from += mtd->writesize;
+ len -= mtd->writesize;
+ }
+ err = 0;
+ if (corrected)
+ err = -EUCLEAN;
+ if (uncorrected)
+ err = -EBADMSG;
+ return err;
+}
+
+static int alauda_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct alauda *al = mtd->priv;
+ int err;
+
+ if ((to & al->bytemask) || (len & al->bytemask))
+ return -EINVAL;
+
+ *retlen = len;
+ while (len) {
+ u32 page = (to >> al->card->pageshift) & al->pagemask;
+ u8 oob[16] = { 'h', 'e', 'l', 'l', 'o', 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+ /* don't write to bad blocks */
+ if (page == 0) {
+ err = alauda_isbad(mtd, to);
+ if (err) {
+ return -EIO;
+ }
+ }
+ nand_calculate_ecc(mtd, buf, &oob[13]);
+ nand_calculate_ecc(mtd, buf+256, &oob[8]);
+
+ err = alauda_write_page(mtd, to, (void*)buf, oob);
+ if (err)
+ return err;
+
+ buf += mtd->writesize;
+ to += mtd->writesize;
+ len -= mtd->writesize;
+ }
+ return 0;
+}
+
+static int __alauda_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct alauda *al = mtd->priv;
+ u32 ofs = instr->addr;
+ u32 len = instr->len;
+ int err;
+
+ if ((ofs & al->blockmask) || (len & al->blockmask))
+ return -EINVAL;
+
+ while (len) {
+ /* don't erase bad blocks */
+ err = alauda_isbad(mtd, ofs);
+ if (err > 0)
+ err = -EIO;
+ if (err < 0)
+ return err;
+
+ err = alauda_erase_block(mtd, ofs);
+ if (err < 0)
+ return err;
+
+ ofs += mtd->erasesize;
+ len -= mtd->erasesize;
+ }
+ return 0;
+}
+
+static int alauda_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ int err;
+
+ err = __alauda_erase(mtd, instr);
+ instr->state = err ? MTD_ERASE_FAILED : MTD_ERASE_DONE;
+ mtd_erase_callback(instr);
+ return err;
+}
+
+static int alauda_init_media(struct alauda *al)
+{
+ u8 buf[4], *b0=buf, *b1=buf+1;
+ struct alauda_card *card;
+ struct mtd_info *mtd;
+ int err;
+
+ mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
+ if (!mtd)
+ return -ENOMEM;
+
+ for (;;) {
+ err = alauda_get_media_status(al, buf);
+ if (err < 0)
+ goto error;
+ if (*b0 & 0x10)
+ break;
+ msleep(20);
+ }
+
+ err = alauda_ack_media(al);
+ if (err)
+ goto error;
+
+ msleep(10);
+
+ err = alauda_get_media_status(al, buf);
+ if (err < 0)
+ goto error;
+
+ if (*b0 != 0x14) {
+ /* media not ready */
+ err = -EIO;
+ goto error;
+ }
+ err = alauda_get_media_signatures(al, buf);
+ if (err < 0)
+ goto error;
+
+ card = get_card(*b1);
+ if (!card) {
+ printk(KERN_ERR"Alauda: unknown card id %02x\n", *b1);
+ err = -EIO;
+ goto error;
+ }
+ printk(KERN_INFO"pagesize=%x\nerasesize=%x\nsize=%xMiB\n",
+ 1<<card->pageshift, 1<<card->blockshift,
+ 1<<(card->chipshift-20));
+ al->card = card;
+ al->pagemask = (1 << (card->blockshift - card->pageshift)) - 1;
+ al->bytemask = (1 << card->pageshift) - 1;
+ al->blockmask = (1 << card->blockshift) - 1;
+
+ mtd->name = "alauda";
+ mtd->size = 1<<card->chipshift;
+ mtd->erasesize = 1<<card->blockshift;
+ mtd->writesize = 1<<card->pageshift;
+ mtd->type = MTD_NANDFLASH;
+ mtd->flags = MTD_CAP_NANDFLASH;
+ mtd->read = alauda_read;
+ mtd->write = alauda_write;
+ mtd->erase = alauda_erase;
+ mtd->block_isbad = alauda_isbad;
+ mtd->priv = al;
+ mtd->owner = THIS_MODULE;
+
+ err = add_mtd_device(mtd);
+ if (err) {
+ err = -ENFILE;
+ goto error;
+ }
+
+ al->mtd = mtd;
+ alauda_reset(al); /* no clue whether this is necessary */
+ return 0;
+error:
+ kfree(mtd);
+ return err;
+}
+
+static int alauda_check_media(struct alauda *al)
+{
+ u8 buf[2], *b0 = buf, *b1 = buf+1;
+ int err;
+
+ err = alauda_get_media_status(al, buf);
+ if (err < 0)
+ return err;
+
+ if ((*b1 & 0x01) == 0) {
+ /* door open */
+ return -EIO;
+ }
+ if ((*b0 & 0x80) || ((*b0 & 0x1F) == 0x10)) {
+ /* no media ? */
+ return -EIO;
+ }
+ if (*b0 & 0x08) {
+ /* media change ? */
+ return alauda_init_media(al);
+ }
+ return 0;
+}
+
+static int alauda_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct alauda *al;
+ struct usb_host_interface *iface;
+ struct usb_endpoint_descriptor *ep,
+ *ep_in=NULL, *ep_out=NULL, *ep_wr=NULL;
+ int i, err = -ENOMEM;
+
+ al = kzalloc(2*sizeof(*al), GFP_KERNEL);
+ if (!al)
+ goto error;
+
+ kref_init(&al->kref);
+ usb_set_intfdata(interface, al);
+
+ al->dev = usb_get_dev(interface_to_usbdev(interface));
+ al->interface = interface;
+
+ iface = interface->cur_altsetting;
+ for (i = 0; i < iface->desc.bNumEndpoints; ++i) {
+ ep = &iface->endpoint[i].desc;
+
+ if (usb_endpoint_is_bulk_in(ep)) {
+ ep_in = ep;
+ } else if (usb_endpoint_is_bulk_out(ep)) {
+ if (i==0)
+ ep_wr = ep;
+ else
+ ep_out = ep;
+ }
+ }
+ err = -EIO;
+ if (!ep_wr || !ep_in || !ep_out)
+ goto error;
+
+ al->write_out = usb_sndbulkpipe(al->dev,
+ ep_wr->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+ al->bulk_in = usb_rcvbulkpipe(al->dev,
+ ep_in->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+ al->bulk_out = usb_sndbulkpipe(al->dev,
+ ep_out->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+
+ /* second device is identical up to now */
+ memcpy(al+1, al, sizeof(*al));
+
+ mutex_init(&al[0].card_mutex);
+ mutex_init(&al[1].card_mutex);
+
+ al[0].port = ALAUDA_PORT_XD;
+ al[1].port = ALAUDA_PORT_SM;
+
+ info("alauda probed");
+ alauda_check_media(al);
+ alauda_check_media(al+1);
+
+ return 0;
+
+error:
+ if (al)
+ kref_put(&al->kref, alauda_delete);
+ return err;
+}
+
+static void alauda_disconnect(struct usb_interface *interface)
+{
+ struct alauda *al;
+
+ al = usb_get_intfdata(interface);
+ usb_set_intfdata(interface, NULL);
+
+ /* FIXME: prevent more I/O from starting */
+
+ /* decrement our usage count */
+ if (al)
+ kref_put(&al->kref, alauda_delete);
+
+ info("alauda gone");
+}
+
+static struct usb_driver alauda_driver = {
+ .name = "alauda",
+ .probe = alauda_probe,
+ .disconnect = alauda_disconnect,
+ .id_table = alauda_table,
+};
+
+static int __init alauda_init(void)
+{
+ return usb_register(&alauda_driver);
+}
+
+static void __exit alauda_exit(void)
+{
+ usb_deregister(&alauda_driver);
+}
+
+module_init(alauda_init);
+module_exit(alauda_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/nand/bf5xx_nand.c b/drivers/mtd/nand/bf5xx_nand.c
new file mode 100644
index 00000000000..1657ecd7488
--- /dev/null
+++ b/drivers/mtd/nand/bf5xx_nand.c
@@ -0,0 +1,788 @@
+/* linux/drivers/mtd/nand/bf5xx_nand.c
+ *
+ * Copyright 2006-2007 Analog Devices Inc.
+ * http://blackfin.uclinux.org/
+ * Bryan Wu <bryan.wu@analog.com>
+ *
+ * Blackfin BF5xx on-chip NAND flash controler driver
+ *
+ * Derived from drivers/mtd/nand/s3c2410.c
+ * Copyright (c) 2007 Ben Dooks <ben@simtec.co.uk>
+ *
+ * Derived from drivers/mtd/nand/cafe.c
+ * Copyright © 2006 Red Hat, Inc.
+ * Copyright © 2006 David Woodhouse <dwmw2@infradead.org>
+ *
+ * Changelog:
+ * 12-Jun-2007 Bryan Wu: Initial version
+ * 18-Jul-2007 Bryan Wu:
+ * - ECC_HW and ECC_SW supported
+ * - DMA supported in ECC_HW
+ * - YAFFS tested as rootfs in both ECC_HW and ECC_SW
+ *
+ * TODO:
+ * Enable JFFS2 over NAND as rootfs
+ *
+ * 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/platform_device.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/bitops.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/blackfin.h>
+#include <asm/dma.h>
+#include <asm/cacheflush.h>
+#include <asm/nand.h>
+#include <asm/portmux.h>
+
+#define DRV_NAME "bf5xx-nand"
+#define DRV_VERSION "1.2"
+#define DRV_AUTHOR "Bryan Wu <bryan.wu@analog.com>"
+#define DRV_DESC "BF5xx on-chip NAND FLash Controller Driver"
+
+#ifdef CONFIG_MTD_NAND_BF5XX_HWECC
+static int hardware_ecc = 1;
+#else
+static int hardware_ecc;
+#endif
+
+static unsigned short bfin_nfc_pin_req[] = {P_NAND_CE, P_NAND_RB, 0};
+
+/*
+ * Data structures for bf5xx nand flash controller driver
+ */
+
+/* bf5xx nand info */
+struct bf5xx_nand_info {
+ /* mtd info */
+ struct nand_hw_control controller;
+ struct mtd_info mtd;
+ struct nand_chip chip;
+
+ /* platform info */
+ struct bf5xx_nand_platform *platform;
+
+ /* device info */
+ struct device *device;
+
+ /* DMA stuff */
+ struct completion dma_completion;
+};
+
+/*
+ * Conversion functions
+ */
+static struct bf5xx_nand_info *mtd_to_nand_info(struct mtd_info *mtd)
+{
+ return container_of(mtd, struct bf5xx_nand_info, mtd);
+}
+
+static struct bf5xx_nand_info *to_nand_info(struct platform_device *pdev)
+{
+ return platform_get_drvdata(pdev);
+}
+
+static struct bf5xx_nand_platform *to_nand_plat(struct platform_device *pdev)
+{
+ return pdev->dev.platform_data;
+}
+
+/*
+ * struct nand_chip interface function pointers
+ */
+
+/*
+ * bf5xx_nand_hwcontrol
+ *
+ * Issue command and address cycles to the chip
+ */
+static void bf5xx_nand_hwcontrol(struct mtd_info *mtd, int cmd,
+ unsigned int ctrl)
+{
+ if (cmd == NAND_CMD_NONE)
+ return;
+
+ while (bfin_read_NFC_STAT() & WB_FULL)
+ cpu_relax();
+
+ if (ctrl & NAND_CLE)
+ bfin_write_NFC_CMD(cmd);
+ else
+ bfin_write_NFC_ADDR(cmd);
+ SSYNC();
+}
+
+/*
+ * bf5xx_nand_devready()
+ *
+ * returns 0 if the nand is busy, 1 if it is ready
+ */
+static int bf5xx_nand_devready(struct mtd_info *mtd)
+{
+ unsigned short val = bfin_read_NFC_IRQSTAT();
+
+ if ((val & NBUSYIRQ) == NBUSYIRQ)
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * ECC functions
+ * These allow the bf5xx to use the controller's ECC
+ * generator block to ECC the data as it passes through
+ */
+
+/*
+ * ECC error correction function
+ */
+static int bf5xx_nand_correct_data_256(struct mtd_info *mtd, u_char *dat,
+ u_char *read_ecc, u_char *calc_ecc)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ u32 syndrome[5];
+ u32 calced, stored;
+ int i;
+ unsigned short failing_bit, failing_byte;
+ u_char data;
+
+ calced = calc_ecc[0] | (calc_ecc[1] << 8) | (calc_ecc[2] << 16);
+ stored = read_ecc[0] | (read_ecc[1] << 8) | (read_ecc[2] << 16);
+
+ syndrome[0] = (calced ^ stored);
+
+ /*
+ * syndrome 0: all zero
+ * No error in data
+ * No action
+ */
+ if (!syndrome[0] || !calced || !stored)
+ return 0;
+
+ /*
+ * sysdrome 0: only one bit is one
+ * ECC data was incorrect
+ * No action
+ */
+ if (hweight32(syndrome[0]) == 1) {
+ dev_err(info->device, "ECC data was incorrect!\n");
+ return 1;
+ }
+
+ syndrome[1] = (calced & 0x7FF) ^ (stored & 0x7FF);
+ syndrome[2] = (calced & 0x7FF) ^ ((calced >> 11) & 0x7FF);
+ syndrome[3] = (stored & 0x7FF) ^ ((stored >> 11) & 0x7FF);
+ syndrome[4] = syndrome[2] ^ syndrome[3];
+
+ for (i = 0; i < 5; i++)
+ dev_info(info->device, "syndrome[%d] 0x%08x\n", i, syndrome[i]);
+
+ dev_info(info->device,
+ "calced[0x%08x], stored[0x%08x]\n",
+ calced, stored);
+
+ /*
+ * sysdrome 0: exactly 11 bits are one, each parity
+ * and parity' pair is 1 & 0 or 0 & 1.
+ * 1-bit correctable error
+ * Correct the error
+ */
+ if (hweight32(syndrome[0]) == 11 && syndrome[4] == 0x7FF) {
+ dev_info(info->device,
+ "1-bit correctable error, correct it.\n");
+ dev_info(info->device,
+ "syndrome[1] 0x%08x\n", syndrome[1]);
+
+ failing_bit = syndrome[1] & 0x7;
+ failing_byte = syndrome[1] >> 0x3;
+ data = *(dat + failing_byte);
+ data = data ^ (0x1 << failing_bit);
+ *(dat + failing_byte) = data;
+
+ return 0;
+ }
+
+ /*
+ * sysdrome 0: random data
+ * More than 1-bit error, non-correctable error
+ * Discard data, mark bad block
+ */
+ dev_err(info->device,
+ "More than 1-bit error, non-correctable error.\n");
+ dev_err(info->device,
+ "Please discard data, mark bad block\n");
+
+ return 1;
+}
+
+static int bf5xx_nand_correct_data(struct mtd_info *mtd, u_char *dat,
+ u_char *read_ecc, u_char *calc_ecc)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ unsigned short page_size = (plat->page_size ? 512 : 256);
+ int ret;
+
+ ret = bf5xx_nand_correct_data_256(mtd, dat, read_ecc, calc_ecc);
+
+ /* If page size is 512, correct second 256 bytes */
+ if (page_size == 512) {
+ dat += 256;
+ read_ecc += 8;
+ calc_ecc += 8;
+ ret = bf5xx_nand_correct_data_256(mtd, dat, read_ecc, calc_ecc);
+ }
+
+ return ret;
+}
+
+static void bf5xx_nand_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+ return;
+}
+
+static int bf5xx_nand_calculate_ecc(struct mtd_info *mtd,
+ const u_char *dat, u_char *ecc_code)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ u16 page_size = (plat->page_size ? 512 : 256);
+ u16 ecc0, ecc1;
+ u32 code[2];
+ u8 *p;
+ int bytes = 3, i;
+
+ /* first 4 bytes ECC code for 256 page size */
+ ecc0 = bfin_read_NFC_ECC0();
+ ecc1 = bfin_read_NFC_ECC1();
+
+ code[0] = (ecc0 & 0x3FF) | ((ecc1 & 0x3FF) << 11);
+
+ dev_dbg(info->device, "returning ecc 0x%08x\n", code[0]);
+
+ /* second 4 bytes ECC code for 512 page size */
+ if (page_size == 512) {
+ ecc0 = bfin_read_NFC_ECC2();
+ ecc1 = bfin_read_NFC_ECC3();
+ code[1] = (ecc0 & 0x3FF) | ((ecc1 & 0x3FF) << 11);
+ bytes = 6;
+ dev_dbg(info->device, "returning ecc 0x%08x\n", code[1]);
+ }
+
+ p = (u8 *)code;
+ for (i = 0; i < bytes; i++)
+ ecc_code[i] = p[i];
+
+ return 0;
+}
+
+/*
+ * PIO mode for buffer writing and reading
+ */
+static void bf5xx_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+ int i;
+ unsigned short val;
+
+ /*
+ * Data reads are requested by first writing to NFC_DATA_RD
+ * and then reading back from NFC_READ.
+ */
+ for (i = 0; i < len; i++) {
+ while (bfin_read_NFC_STAT() & WB_FULL)
+ cpu_relax();
+
+ /* Contents do not matter */
+ bfin_write_NFC_DATA_RD(0x0000);
+ SSYNC();
+
+ while ((bfin_read_NFC_IRQSTAT() & RD_RDY) != RD_RDY)
+ cpu_relax();
+
+ buf[i] = bfin_read_NFC_READ();
+
+ val = bfin_read_NFC_IRQSTAT();
+ val |= RD_RDY;
+ bfin_write_NFC_IRQSTAT(val);
+ SSYNC();
+ }
+}
+
+static uint8_t bf5xx_nand_read_byte(struct mtd_info *mtd)
+{
+ uint8_t val;
+
+ bf5xx_nand_read_buf(mtd, &val, 1);
+
+ return val;
+}
+
+static void bf5xx_nand_write_buf(struct mtd_info *mtd,
+ const uint8_t *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ while (bfin_read_NFC_STAT() & WB_FULL)
+ cpu_relax();
+
+ bfin_write_NFC_DATA_WR(buf[i]);
+ SSYNC();
+ }
+}
+
+static void bf5xx_nand_read_buf16(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+ int i;
+ u16 *p = (u16 *) buf;
+ len >>= 1;
+
+ /*
+ * Data reads are requested by first writing to NFC_DATA_RD
+ * and then reading back from NFC_READ.
+ */
+ bfin_write_NFC_DATA_RD(0x5555);
+
+ SSYNC();
+
+ for (i = 0; i < len; i++)
+ p[i] = bfin_read_NFC_READ();
+}
+
+static void bf5xx_nand_write_buf16(struct mtd_info *mtd,
+ const uint8_t *buf, int len)
+{
+ int i;
+ u16 *p = (u16 *) buf;
+ len >>= 1;
+
+ for (i = 0; i < len; i++)
+ bfin_write_NFC_DATA_WR(p[i]);
+
+ SSYNC();
+}
+
+/*
+ * DMA functions for buffer writing and reading
+ */
+static irqreturn_t bf5xx_nand_dma_irq(int irq, void *dev_id)
+{
+ struct bf5xx_nand_info *info = dev_id;
+
+ clear_dma_irqstat(CH_NFC);
+ disable_dma(CH_NFC);
+ complete(&info->dma_completion);
+
+ return IRQ_HANDLED;
+}
+
+static int bf5xx_nand_dma_rw(struct mtd_info *mtd,
+ uint8_t *buf, int is_read)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ unsigned short page_size = (plat->page_size ? 512 : 256);
+ unsigned short val;
+
+ dev_dbg(info->device, " mtd->%p, buf->%p, is_read %d\n",
+ mtd, buf, is_read);
+
+ /*
+ * Before starting a dma transfer, be sure to invalidate/flush
+ * the cache over the address range of your DMA buffer to
+ * prevent cache coherency problems. Otherwise very subtle bugs
+ * can be introduced to your driver.
+ */
+ if (is_read)
+ invalidate_dcache_range((unsigned int)buf,
+ (unsigned int)(buf + page_size));
+ else
+ flush_dcache_range((unsigned int)buf,
+ (unsigned int)(buf + page_size));
+
+ /*
+ * This register must be written before each page is
+ * transferred to generate the correct ECC register
+ * values.
+ */
+ bfin_write_NFC_RST(0x1);
+ SSYNC();
+
+ disable_dma(CH_NFC);
+ clear_dma_irqstat(CH_NFC);
+
+ /* setup DMA register with Blackfin DMA API */
+ set_dma_config(CH_NFC, 0x0);
+ set_dma_start_addr(CH_NFC, (unsigned long) buf);
+ set_dma_x_count(CH_NFC, (page_size >> 2));
+ set_dma_x_modify(CH_NFC, 4);
+
+ /* setup write or read operation */
+ val = DI_EN | WDSIZE_32;
+ if (is_read)
+ val |= WNR;
+ set_dma_config(CH_NFC, val);
+ enable_dma(CH_NFC);
+
+ /* Start PAGE read/write operation */
+ if (is_read)
+ bfin_write_NFC_PGCTL(0x1);
+ else
+ bfin_write_NFC_PGCTL(0x2);
+ wait_for_completion(&info->dma_completion);
+
+ return 0;
+}
+
+static void bf5xx_nand_dma_read_buf(struct mtd_info *mtd,
+ uint8_t *buf, int len)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ unsigned short page_size = (plat->page_size ? 512 : 256);
+
+ dev_dbg(info->device, "mtd->%p, buf->%p, int %d\n", mtd, buf, len);
+
+ if (len == page_size)
+ bf5xx_nand_dma_rw(mtd, buf, 1);
+ else
+ bf5xx_nand_read_buf(mtd, buf, len);
+}
+
+static void bf5xx_nand_dma_write_buf(struct mtd_info *mtd,
+ const uint8_t *buf, int len)
+{
+ struct bf5xx_nand_info *info = mtd_to_nand_info(mtd);
+ struct bf5xx_nand_platform *plat = info->platform;
+ unsigned short page_size = (plat->page_size ? 512 : 256);
+
+ dev_dbg(info->device, "mtd->%p, buf->%p, len %d\n", mtd, buf, len);
+
+ if (len == page_size)
+ bf5xx_nand_dma_rw(mtd, (uint8_t *)buf, 0);
+ else
+ bf5xx_nand_write_buf(mtd, buf, len);
+}
+
+/*
+ * System initialization functions
+ */
+
+static int bf5xx_nand_dma_init(struct bf5xx_nand_info *info)
+{
+ int ret;
+ unsigned short val;
+
+ /* Do not use dma */
+ if (!hardware_ecc)
+ return 0;
+
+ init_completion(&info->dma_completion);
+
+ /* Setup DMAC1 channel mux for NFC which shared with SDH */
+ val = bfin_read_DMAC1_PERIMUX();
+ val &= 0xFFFE;
+ bfin_write_DMAC1_PERIMUX(val);
+ SSYNC();
+
+ /* Request NFC DMA channel */
+ ret = request_dma(CH_NFC, "BF5XX NFC driver");
+ if (ret < 0) {
+ dev_err(info->device, " unable to get DMA channel\n");
+ return ret;
+ }
+
+ set_dma_callback(CH_NFC, (void *) bf5xx_nand_dma_irq, (void *) info);
+
+ /* Turn off the DMA channel first */
+ disable_dma(CH_NFC);
+ return 0;
+}
+
+/*
+ * BF5XX NFC hardware initialization
+ * - pin mux setup
+ * - clear interrupt status
+ */
+static int bf5xx_nand_hw_init(struct bf5xx_nand_info *info)
+{
+ int err = 0;
+ unsigned short val;
+ struct bf5xx_nand_platform *plat = info->platform;
+
+ /* setup NFC_CTL register */
+ dev_info(info->device,
+ "page_size=%d, data_width=%d, wr_dly=%d, rd_dly=%d\n",
+ (plat->page_size ? 512 : 256),
+ (plat->data_width ? 16 : 8),
+ plat->wr_dly, plat->rd_dly);
+
+ val = (plat->page_size << NFC_PG_SIZE_OFFSET) |
+ (plat->data_width << NFC_NWIDTH_OFFSET) |
+ (plat->rd_dly << NFC_RDDLY_OFFSET) |
+ (plat->rd_dly << NFC_WRDLY_OFFSET);
+ dev_dbg(info->device, "NFC_CTL is 0x%04x\n", val);
+
+ bfin_write_NFC_CTL(val);
+ SSYNC();
+
+ /* clear interrupt status */
+ bfin_write_NFC_IRQMASK(0x0);
+ SSYNC();
+ val = bfin_read_NFC_IRQSTAT();
+ bfin_write_NFC_IRQSTAT(val);
+ SSYNC();
+
+ if (peripheral_request_list(bfin_nfc_pin_req, DRV_NAME)) {
+ printk(KERN_ERR DRV_NAME
+ ": Requesting Peripherals failed\n");
+ return -EFAULT;
+ }
+
+ /* DMA initialization */
+ if (bf5xx_nand_dma_init(info))
+ err = -ENXIO;
+
+ return err;
+}
+
+/*
+ * Device management interface
+ */
+static int bf5xx_nand_add_partition(struct bf5xx_nand_info *info)
+{
+ struct mtd_info *mtd = &info->mtd;
+
+#ifdef CONFIG_MTD_PARTITIONS
+ struct mtd_partition *parts = info->platform->partitions;
+ int nr = info->platform->nr_partitions;
+
+ return add_mtd_partitions(mtd, parts, nr);
+#else
+ return add_mtd_device(mtd);
+#endif
+}
+
+static int bf5xx_nand_remove(struct platform_device *pdev)
+{
+ struct bf5xx_nand_info *info = to_nand_info(pdev);
+ struct mtd_info *mtd = NULL;
+
+ platform_set_drvdata(pdev, NULL);
+
+ /* first thing we need to do is release all our mtds
+ * and their partitions, then go through freeing the
+ * resources used
+ */
+ mtd = &info->mtd;
+ if (mtd) {
+ nand_release(mtd);
+ kfree(mtd);
+ }
+
+ peripheral_free_list(bfin_nfc_pin_req);
+
+ /* free the common resources */
+ kfree(info);
+
+ return 0;
+}
+
+/*
+ * bf5xx_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 bf5xx_nand_probe(struct platform_device *pdev)
+{
+ struct bf5xx_nand_platform *plat = to_nand_plat(pdev);
+ struct bf5xx_nand_info *info = NULL;
+ struct nand_chip *chip = NULL;
+ struct mtd_info *mtd = NULL;
+ int err = 0;
+
+ dev_dbg(&pdev->dev, "(%p)\n", pdev);
+
+ if (!plat) {
+ dev_err(&pdev->dev, "no platform specific information\n");
+ goto exit_error;
+ }
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (info == NULL) {
+ dev_err(&pdev->dev, "no memory for flash info\n");
+ err = -ENOMEM;
+ goto exit_error;
+ }
+
+ platform_set_drvdata(pdev, info);
+
+ spin_lock_init(&info->controller.lock);
+ init_waitqueue_head(&info->controller.wq);
+
+ info->device = &pdev->dev;
+ info->platform = plat;
+
+ /* initialise chip data struct */
+ chip = &info->chip;
+
+ if (plat->data_width)
+ chip->options |= NAND_BUSWIDTH_16;
+
+ chip->options |= NAND_CACHEPRG | NAND_SKIP_BBTSCAN;
+
+ chip->read_buf = (plat->data_width) ?
+ bf5xx_nand_read_buf16 : bf5xx_nand_read_buf;
+ chip->write_buf = (plat->data_width) ?
+ bf5xx_nand_write_buf16 : bf5xx_nand_write_buf;
+
+ chip->read_byte = bf5xx_nand_read_byte;
+
+ chip->cmd_ctrl = bf5xx_nand_hwcontrol;
+ chip->dev_ready = bf5xx_nand_devready;
+
+ chip->priv = &info->mtd;
+ chip->controller = &info->controller;
+
+ chip->IO_ADDR_R = (void __iomem *) NFC_READ;
+ chip->IO_ADDR_W = (void __iomem *) NFC_DATA_WR;
+
+ chip->chip_delay = 0;
+
+ /* initialise mtd info data struct */
+ mtd = &info->mtd;
+ mtd->priv = chip;
+ mtd->owner = THIS_MODULE;
+
+ /* initialise the hardware */
+ err = bf5xx_nand_hw_init(info);
+ if (err != 0)
+ goto exit_error;
+
+ /* setup hardware ECC data struct */
+ if (hardware_ecc) {
+ if (plat->page_size == NFC_PG_SIZE_256) {
+ chip->ecc.bytes = 3;
+ chip->ecc.size = 256;
+ } else if (plat->page_size == NFC_PG_SIZE_512) {
+ chip->ecc.bytes = 6;
+ chip->ecc.size = 512;
+ }
+
+ chip->read_buf = bf5xx_nand_dma_read_buf;
+ chip->write_buf = bf5xx_nand_dma_write_buf;
+ chip->ecc.calculate = bf5xx_nand_calculate_ecc;
+ chip->ecc.correct = bf5xx_nand_correct_data;
+ chip->ecc.mode = NAND_ECC_HW;
+ chip->ecc.hwctl = bf5xx_nand_enable_hwecc;
+ } else {
+ chip->ecc.mode = NAND_ECC_SOFT;
+ }
+
+ /* scan hardware nand chip and setup mtd info data struct */
+ if (nand_scan(mtd, 1)) {
+ err = -ENXIO;
+ goto exit_error;
+ }
+
+ /* add NAND partition */
+ bf5xx_nand_add_partition(info);
+
+ dev_dbg(&pdev->dev, "initialised ok\n");
+ return 0;
+
+exit_error:
+ bf5xx_nand_remove(pdev);
+
+ if (err == 0)
+ err = -EINVAL;
+ return err;
+}
+
+/* PM Support */
+#ifdef CONFIG_PM
+
+static int bf5xx_nand_suspend(struct platform_device *dev, pm_message_t pm)
+{
+ struct bf5xx_nand_info *info = platform_get_drvdata(dev);
+
+ return 0;
+}
+
+static int bf5xx_nand_resume(struct platform_device *dev)
+{
+ struct bf5xx_nand_info *info = platform_get_drvdata(dev);
+
+ if (info)
+ bf5xx_nand_hw_init(info);
+
+ return 0;
+}
+
+#else
+#define bf5xx_nand_suspend NULL
+#define bf5xx_nand_resume NULL
+#endif
+
+/* driver device registration */
+static struct platform_driver bf5xx_nand_driver = {
+ .probe = bf5xx_nand_probe,
+ .remove = bf5xx_nand_remove,
+ .suspend = bf5xx_nand_suspend,
+ .resume = bf5xx_nand_resume,
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init bf5xx_nand_init(void)
+{
+ printk(KERN_INFO "%s, Version %s (c) 2007 Analog Devices, Inc.\n",
+ DRV_DESC, DRV_VERSION);
+
+ return platform_driver_register(&bf5xx_nand_driver);
+}
+
+static void __exit bf5xx_nand_exit(void)
+{
+ platform_driver_unregister(&bf5xx_nand_driver);
+}
+
+module_init(bf5xx_nand_init);
+module_exit(bf5xx_nand_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR(DRV_AUTHOR);
+MODULE_DESCRIPTION(DRV_DESC);
diff --git a/drivers/mtd/nand/cafe_nand.c b/drivers/mtd/nand/cafe_nand.c
index 6f32a35eb10..1e811715211 100644
--- a/drivers/mtd/nand/cafe_nand.c
+++ b/drivers/mtd/nand/cafe_nand.c
@@ -80,7 +80,7 @@ module_param(regdebug, int, 0644);
static int checkecc = 1;
module_param(checkecc, int, 0644);
-static int numtimings;
+static unsigned int numtimings;
static int timing[3];
module_param_array(timing, int, &numtimings, 0644);
@@ -623,6 +623,11 @@ static int __devinit cafe_nand_probe(struct pci_dev *pdev,
uint32_t ctrl;
int err = 0;
+ /* Very old versions shared the same PCI ident for all three
+ functions on the chip. Verify the class too... */
+ if ((pdev->class >> 8) != PCI_CLASS_MEMORY_FLASH)
+ return -ENODEV;
+
err = pci_enable_device(pdev);
if (err)
return err;
@@ -816,21 +821,57 @@ static void __devexit cafe_nand_remove(struct pci_dev *pdev)
}
static struct pci_device_id cafe_nand_tbl[] = {
- { 0x11ab, 0x4100, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_MEMORY_FLASH << 8, 0xFFFF0 },
- { 0, }
+ { 0x11ab, 0x4100, PCI_ANY_ID, PCI_ANY_ID },
+ { }
};
MODULE_DEVICE_TABLE(pci, cafe_nand_tbl);
+static int cafe_nand_resume(struct pci_dev *pdev)
+{
+ uint32_t ctrl;
+ struct mtd_info *mtd = pci_get_drvdata(pdev);
+ struct cafe_priv *cafe = mtd->priv;
+
+ /* Start off by resetting the NAND controller completely */
+ cafe_writel(cafe, 1, NAND_RESET);
+ cafe_writel(cafe, 0, NAND_RESET);
+ cafe_writel(cafe, 0xffffffff, NAND_IRQ_MASK);
+
+ /* Restore timing configuration */
+ cafe_writel(cafe, timing[0], NAND_TIMING1);
+ cafe_writel(cafe, timing[1], NAND_TIMING2);
+ cafe_writel(cafe, timing[2], NAND_TIMING3);
+
+ /* Disable master reset, enable NAND clock */
+ ctrl = cafe_readl(cafe, GLOBAL_CTRL);
+ ctrl &= 0xffffeff0;
+ ctrl |= 0x00007000;
+ cafe_writel(cafe, ctrl | 0x05, GLOBAL_CTRL);
+ cafe_writel(cafe, ctrl | 0x0a, GLOBAL_CTRL);
+ cafe_writel(cafe, 0, NAND_DMA_CTRL);
+ cafe_writel(cafe, 0x7006, GLOBAL_CTRL);
+ cafe_writel(cafe, 0x700a, GLOBAL_CTRL);
+
+ /* Set up DMA address */
+ cafe_writel(cafe, cafe->dmaaddr & 0xffffffff, NAND_DMA_ADDR0);
+ if (sizeof(cafe->dmaaddr) > 4)
+ /* Shift in two parts to shut the compiler up */
+ cafe_writel(cafe, (cafe->dmaaddr >> 16) >> 16, NAND_DMA_ADDR1);
+ else
+ cafe_writel(cafe, 0, NAND_DMA_ADDR1);
+
+ /* Enable NAND IRQ in global IRQ mask register */
+ cafe_writel(cafe, 0x80000007, GLOBAL_IRQ_MASK);
+ return 0;
+}
+
static struct pci_driver cafe_nand_pci_driver = {
.name = "CAFÉ NAND",
.id_table = cafe_nand_tbl,
.probe = cafe_nand_probe,
.remove = __devexit_p(cafe_nand_remove),
-#ifdef CONFIG_PMx
- .suspend = cafe_nand_suspend,
.resume = cafe_nand_resume,
-#endif
};
static int cafe_nand_init(void)
diff --git a/drivers/mtd/nand/diskonchip.c b/drivers/mtd/nand/diskonchip.c
index e96259f22cc..ab9f5c5db38 100644
--- a/drivers/mtd/nand/diskonchip.c
+++ b/drivers/mtd/nand/diskonchip.c
@@ -56,8 +56,6 @@ static unsigned long __initdata doc_locations[] = {
#endif /* CONFIG_MTD_DOCPROBE_HIGH */
#elif defined(__PPC__)
0xe4000000,
-#elif defined(CONFIG_MOMENCO_OCELOT_G)
- 0xff000000,
#else
#warning Unknown architecture for DiskOnChip. No default probe locations defined
#endif
diff --git a/drivers/mtd/nand/excite_nandflash.c b/drivers/mtd/nand/excite_nandflash.c
index 7e9afc4c775..bed87290dec 100644
--- a/drivers/mtd/nand/excite_nandflash.c
+++ b/drivers/mtd/nand/excite_nandflash.c
@@ -27,7 +27,6 @@
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/err.h>
-#include <linux/kernel.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index 24ac6778b1a..b4e0e772389 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -7,7 +7,7 @@
* Basic support for AG-AND chips is provided.
*
* Additional technical information is available on
- * http://www.linux-mtd.infradead.org/tech/nand.html
+ * http://www.linux-mtd.infradead.org/doc/nand.html
*
* Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com)
* 2002-2006 Thomas Gleixner (tglx@linutronix.de)
@@ -2069,13 +2069,14 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr,
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);
+ /* Do call back function */
+ if (!ret)
+ mtd_erase_callback(instr);
+
/*
* If BBT requires refresh and erase was successful, rewrite any
* selected bad block tables
diff --git a/drivers/mtd/nand/nand_ids.c b/drivers/mtd/nand/nand_ids.c
index 2fc674a190c..a3e3ab0185d 100644
--- a/drivers/mtd/nand/nand_ids.c
+++ b/drivers/mtd/nand/nand_ids.c
@@ -141,6 +141,7 @@ struct nand_manufacturers nand_manuf_ids[] = {
{NAND_MFR_STMICRO, "ST Micro"},
{NAND_MFR_HYNIX, "Hynix"},
{NAND_MFR_MICRON, "Micron"},
+ {NAND_MFR_AMD, "AMD"},
{0x0, "Unknown"}
};
diff --git a/drivers/mtd/nand/nandsim.c b/drivers/mtd/nand/nandsim.c
index 205df0f771f..a7574807dc4 100644
--- a/drivers/mtd/nand/nandsim.c
+++ b/drivers/mtd/nand/nandsim.c
@@ -1272,7 +1272,13 @@ static int prog_page(struct nandsim *ns, int num)
mypage = NS_GET_PAGE(ns);
if (mypage->byte == NULL) {
NS_DBG("prog_page: allocating page %d\n", ns->regs.row);
- mypage->byte = kmalloc(ns->geom.pgszoob, GFP_KERNEL);
+ /*
+ * We allocate memory with GFP_NOFS because a flash FS may
+ * utilize this. If it is holding an FS lock, then gets here,
+ * then kmalloc runs writeback which goes to the FS again
+ * and deadlocks. This was seen in practice.
+ */
+ mypage->byte = kmalloc(ns->geom.pgszoob, GFP_NOFS);
if (mypage->byte == NULL) {
NS_ERR("prog_page: error allocating memory for page %d\n", ns->regs.row);
return -1;
diff --git a/drivers/mtd/nand/ndfc.c b/drivers/mtd/nand/ndfc.c
index fd7a8d5ba29..1c0e89f00e8 100644
--- a/drivers/mtd/nand/ndfc.c
+++ b/drivers/mtd/nand/ndfc.c
@@ -24,7 +24,11 @@
#include <linux/platform_device.h>
#include <asm/io.h>
+#ifdef CONFIG_40x
+#include <asm/ibm405.h>
+#else
#include <asm/ibm44x.h>
+#endif
struct ndfc_nand_mtd {
struct mtd_info mtd;
@@ -230,7 +234,11 @@ static int ndfc_nand_probe(struct platform_device *pdev)
struct ndfc_controller *ndfc = &ndfc_ctrl;
unsigned long long phys = settings->ndfc_erpn | res->start;
+#ifndef CONFIG_PHYS_64BIT
+ ndfc->ndfcbase = ioremap((phys_addr_t)phys, res->end - res->start + 1);
+#else
ndfc->ndfcbase = ioremap64(phys, res->end - res->start + 1);
+#endif
if (!ndfc->ndfcbase) {
printk(KERN_ERR "NDFC: ioremap failed\n");
return -EIO;
diff --git a/drivers/mtd/nand/s3c2410.c b/drivers/mtd/nand/s3c2410.c
index 5fac4c421a2..b79a9cf2d16 100644
--- a/drivers/mtd/nand/s3c2410.c
+++ b/drivers/mtd/nand/s3c2410.c
@@ -60,8 +60,8 @@
#include <asm/io.h>
-#include <asm/arch/regs-nand.h>
-#include <asm/arch/nand.h>
+#include <asm/plat-s3c/regs-nand.h>
+#include <asm/plat-s3c/nand.h>
#ifdef CONFIG_MTD_NAND_S3C2410_HWECC
static int hardware_ecc = 1;