]> git.sur5r.net Git - u-boot/commitdiff
Blackfin: add driver for on-chip NAND controller
authorMike Frysinger <vapier@gentoo.org>
Thu, 27 Nov 2008 02:43:06 +0000 (21:43 -0500)
committerMike Frysinger <vapier@gentoo.org>
Mon, 2 Feb 2009 17:27:07 +0000 (12:27 -0500)
This is a port of the Linux Blackfin on-chip NFC driver to U-Boot.

Signed-off-by: Mike Frysinger <vapier@gentoo.org>
drivers/mtd/nand/Makefile
drivers/mtd/nand/bfin_nand.c [new file with mode: 0644]

index f7b2b2223da25b51ccd73b0f00a11f1c3af42157..24edb27cf13f7c90810263473b461979be77476d 100644 (file)
@@ -35,6 +35,7 @@ COBJS-y += nand_ids.o
 COBJS-y += nand_util.o
 endif
 
+COBJS-$(CONFIG_DRIVER_NAND_BFIN) += bfin_nand.o
 COBJS-$(CONFIG_NAND_FSL_ELBC) += fsl_elbc_nand.o
 COBJS-$(CONFIG_NAND_FSL_UPM) += fsl_upm.o
 COBJS-$(CONFIG_NAND_S3C64XX) += s3c64xx.o
diff --git a/drivers/mtd/nand/bfin_nand.c b/drivers/mtd/nand/bfin_nand.c
new file mode 100644 (file)
index 0000000..f6a0835
--- /dev/null
@@ -0,0 +1,385 @@
+/*
+ * Driver for Blackfin on-chip NAND controller.
+ *
+ * Enter bugs at http://blackfin.uclinux.org/
+ *
+ * Copyright (c) 2007-2008 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+/* TODO:
+ * - move bit defines into mach-common/bits/nand.h
+ * - try and replace all IRQSTAT usage with STAT polling
+ * - have software ecc mode use same algo as hw ecc ?
+ */
+
+#include <common.h>
+#include <asm/io.h>
+
+#ifdef DEBUG
+# define pr_stamp() printf("%s:%s:%i: here i am\n", __FILE__, __func__, __LINE__)
+#else
+# define pr_stamp()
+#endif
+
+#include <nand.h>
+
+#include <asm/blackfin.h>
+
+/* Bit masks for NFC_CTL */
+
+#define                    WR_DLY  0xf        /* Write Strobe Delay */
+#define                    RD_DLY  0xf0       /* Read Strobe Delay */
+#define                    NWIDTH  0x100      /* NAND Data Width */
+#define                   PG_SIZE  0x200      /* Page Size */
+
+/* Bit masks for NFC_STAT */
+
+#define                     NBUSY  0x1        /* Not Busy */
+#define                   WB_FULL  0x2        /* Write Buffer Full */
+#define                PG_WR_STAT  0x4        /* Page Write Pending */
+#define                PG_RD_STAT  0x8        /* Page Read Pending */
+#define                  WB_EMPTY  0x10       /* Write Buffer Empty */
+
+/* Bit masks for NFC_IRQSTAT */
+
+#define                  NBUSYIRQ  0x1        /* Not Busy IRQ */
+#define                    WB_OVF  0x2        /* Write Buffer Overflow */
+#define                   WB_EDGE  0x4        /* Write Buffer Edge Detect */
+#define                    RD_RDY  0x8        /* Read Data Ready */
+#define                   WR_DONE  0x10       /* Page Write Done */
+
+#define NAND_IS_512() (CONFIG_BFIN_NFC_CTL_VAL & 0x200)
+
+/*
+ * hardware specific access to control-lines
+ */
+static void bfin_nfc_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
+{
+       pr_stamp();
+
+       if (cmd == NAND_CMD_NONE)
+               return;
+
+       while (bfin_read_NFC_STAT() & WB_FULL)
+               continue;
+
+       if (ctrl & NAND_CLE)
+               bfin_write_NFC_CMD(cmd);
+       else
+               bfin_write_NFC_ADDR(cmd);
+       SSYNC();
+}
+
+int bfin_nfc_devready(struct mtd_info *mtd)
+{
+       pr_stamp();
+       return (bfin_read_NFC_STAT() & NBUSY ? 1 : 0);
+}
+
+/*
+ * PIO mode for buffer writing and reading
+ */
+static void bfin_nfc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+       pr_stamp();
+
+       int i;
+
+       /*
+        * 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)
+                       if (ctrlc())
+                               return;
+
+               /* Contents do not matter */
+               bfin_write_NFC_DATA_RD(0x0000);
+
+               while (!(bfin_read_NFC_IRQSTAT() & RD_RDY))
+                       if (ctrlc())
+                               return;
+
+               buf[i] = bfin_read_NFC_READ();
+
+               bfin_write_NFC_IRQSTAT(RD_RDY);
+       }
+}
+
+static uint8_t bfin_nfc_read_byte(struct mtd_info *mtd)
+{
+       pr_stamp();
+
+       uint8_t val;
+       bfin_nfc_read_buf(mtd, &val, 1);
+       return val;
+}
+
+static void bfin_nfc_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
+{
+       pr_stamp();
+
+       int i;
+
+       for (i = 0; i < len; ++i) {
+               while (bfin_read_NFC_STAT() & WB_FULL)
+                       if (ctrlc())
+                               return;
+
+               bfin_write_NFC_DATA_WR(buf[i]);
+       }
+}
+
+/*
+ * ECC functions
+ * These allow the bfin to use the controller's ECC
+ * generator block to ECC the data as it passes through
+ */
+
+/*
+ * ECC error correction function
+ */
+static int bfin_nfc_correct_data_256(struct mtd_info *mtd, u_char *dat,
+                                       u_char *read_ecc, u_char *calc_ecc)
+{
+       u32 syndrome[5];
+       u32 calced, stored;
+       unsigned short failing_bit, failing_byte;
+       u_char data;
+
+       pr_stamp();
+
+       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)
+               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];
+
+       /*
+        * 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) {
+               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
+        */
+
+       return 1;
+}
+
+static int bfin_nfc_correct_data(struct mtd_info *mtd, u_char *dat,
+                                       u_char *read_ecc, u_char *calc_ecc)
+{
+       int ret;
+
+       pr_stamp();
+
+       ret = bfin_nfc_correct_data_256(mtd, dat, read_ecc, calc_ecc);
+
+       /* If page size is 512, correct second 256 bytes */
+       if (NAND_IS_512()) {
+               dat += 256;
+               read_ecc += 8;
+               calc_ecc += 8;
+               ret |= bfin_nfc_correct_data_256(mtd, dat, read_ecc, calc_ecc);
+       }
+
+       return ret;
+}
+
+static void reset_ecc(void)
+{
+       bfin_write_NFC_RST(0x1);
+       while (bfin_read_NFC_RST() & 1)
+               continue;
+}
+
+static void bfin_nfc_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+       reset_ecc();
+}
+
+static int bfin_nfc_calculate_ecc(struct mtd_info *mtd,
+               const u_char *dat, u_char *ecc_code)
+{
+       u16 ecc0, ecc1;
+       u32 code[2];
+       u8 *p;
+
+       pr_stamp();
+
+       /* first 4 bytes ECC code for 256 page size */
+       ecc0 = bfin_read_NFC_ECC0();
+       ecc1 = bfin_read_NFC_ECC1();
+
+       code[0] = (ecc0 & 0x7FF) | ((ecc1 & 0x7FF) << 11);
+
+       /* first 3 bytes in ecc_code for 256 page size */
+       p = (u8 *) code;
+       memcpy(ecc_code, p, 3);
+
+       /* second 4 bytes ECC code for 512 page size */
+       if (NAND_IS_512()) {
+               ecc0 = bfin_read_NFC_ECC2();
+               ecc1 = bfin_read_NFC_ECC3();
+               code[1] = (ecc0 & 0x7FF) | ((ecc1 & 0x7FF) << 11);
+
+               /* second 3 bytes in ecc_code for second 256
+                * bytes of 512 page size
+                */
+               p = (u8 *) (code + 1);
+               memcpy((ecc_code + 3), p, 3);
+       }
+
+       reset_ecc();
+
+       return 0;
+}
+
+#ifdef CONFIG_BFIN_NFC_BOOTROM_ECC
+# define BOOTROM_ECC 1
+#else
+# define BOOTROM_ECC 0
+#endif
+
+static uint8_t bbt_pattern[] = { 0xff };
+
+static struct nand_bbt_descr bootrom_bbt = {
+       .options = 0,
+       .offs = 63,
+       .len = 1,
+       .pattern = bbt_pattern,
+};
+
+static struct nand_ecclayout bootrom_ecclayout = {
+       .eccbytes = 24,
+       .eccpos = {
+               0x8 * 0, 0x8 * 0 + 1, 0x8 * 0 + 2,
+               0x8 * 1, 0x8 * 1 + 1, 0x8 * 1 + 2,
+               0x8 * 2, 0x8 * 2 + 1, 0x8 * 2 + 2,
+               0x8 * 3, 0x8 * 3 + 1, 0x8 * 3 + 2,
+               0x8 * 4, 0x8 * 4 + 1, 0x8 * 4 + 2,
+               0x8 * 5, 0x8 * 5 + 1, 0x8 * 5 + 2,
+               0x8 * 6, 0x8 * 6 + 1, 0x8 * 6 + 2,
+               0x8 * 7, 0x8 * 7 + 1, 0x8 * 7 + 2
+       },
+       .oobfree = {
+               { 0x8 * 0 + 3, 5 },
+               { 0x8 * 1 + 3, 5 },
+               { 0x8 * 2 + 3, 5 },
+               { 0x8 * 3 + 3, 5 },
+               { 0x8 * 4 + 3, 5 },
+               { 0x8 * 5 + 3, 5 },
+               { 0x8 * 6 + 3, 5 },
+               { 0x8 * 7 + 3, 5 },
+       }
+};
+
+/*
+ * Board-specific NAND initialization. The following members of the
+ * argument are board-specific (per include/linux/mtd/nand.h):
+ * - IO_ADDR_R?: address to read the 8 I/O lines of the flash device
+ * - IO_ADDR_W?: address to write the 8 I/O lines of the flash device
+ * - cmd_ctrl: hardwarespecific function for accesing control-lines
+ * - dev_ready: hardwarespecific function for  accesing device ready/busy line
+ * - enable_hwecc?: function to enable (reset)  hardware ecc generator. Must
+ *   only be provided if a hardware ECC is available
+ * - ecc.mode: mode of ecc, see defines
+ * - chip_delay: chip dependent delay for transfering data from array to
+ *   read regs (tR)
+ * - options: various chip options. They can partly be set to inform
+ *   nand_scan about special functionality. See the defines for further
+ *   explanation
+ * Members with a "?" were not set in the merged testing-NAND branch,
+ * so they are not set here either.
+ */
+int board_nand_init(struct nand_chip *chip)
+{
+       pr_stamp();
+
+       /* set width/ecc/timings/etc... */
+       bfin_write_NFC_CTL(CONFIG_BFIN_NFC_CTL_VAL);
+
+       /* clear interrupt status */
+       bfin_write_NFC_IRQMASK(0x0);
+       bfin_write_NFC_IRQSTAT(0xffff);
+
+       /* enable GPIO function enable register */
+#ifdef __ADSPBF54x__
+       bfin_write_PORTJ_FER(bfin_read_PORTJ_FER() | 6);
+#elif defined(__ADSPBF52x__)
+       bfin_write_PORTH_FER(bfin_read_PORTH_FER() | 0xFCFF);
+       bfin_write_PORTH_MUX(0);
+#else
+# error no support for this variant
+#endif
+
+       chip->cmd_ctrl = bfin_nfc_cmd_ctrl;
+       chip->read_buf = bfin_nfc_read_buf;
+       chip->write_buf = bfin_nfc_write_buf;
+       chip->read_byte = bfin_nfc_read_byte;
+
+#ifdef CONFIG_BFIN_NFC_NO_HW_ECC
+# define ECC_HW 0
+#else
+# define ECC_HW 1
+#endif
+       if (ECC_HW) {
+               if (BOOTROM_ECC) {
+                       chip->badblock_pattern = &bootrom_bbt;
+                       chip->ecc.layout = &bootrom_ecclayout;
+               }
+               if (!NAND_IS_512()) {
+                       chip->ecc.bytes = 3;
+                       chip->ecc.size = 256;
+               } else {
+                       chip->ecc.bytes = 6;
+                       chip->ecc.size = 512;
+               }
+               chip->ecc.mode = NAND_ECC_HW;
+               chip->ecc.calculate = bfin_nfc_calculate_ecc;
+               chip->ecc.correct   = bfin_nfc_correct_data;
+               chip->ecc.hwctl     = bfin_nfc_enable_hwecc;
+       } else
+               chip->ecc.mode = NAND_ECC_SOFT;
+       chip->dev_ready = bfin_nfc_devready;
+       chip->chip_delay = 0;
+
+       return 0;
+}