Subject: [PATCH 08/13 v3] ARM: OMAP3: Add NAND support From: Dirk Behme Add NAND support Signed-off-by: Dirk Behme --- Changes in version v3: - Fix/rewrite/update NAND driver and seperate it into an own patch as proposed by Scott Wood common/cmd_nand.c | 29 +++ drivers/mtd/nand/Makefile | 1 drivers/mtd/nand/omap3.c | 370 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 400 insertions(+) Index: u-boot-arm/drivers/mtd/nand/Makefile =================================================================== --- u-boot-arm.orig/drivers/mtd/nand/Makefile +++ u-boot-arm/drivers/mtd/nand/Makefile @@ -38,6 +38,7 @@ endif COBJS-$(CONFIG_NAND_FSL_ELBC) += fsl_elbc_nand.o COBJS-$(CONFIG_NAND_FSL_UPM) += fsl_upm.o COBJS-$(CONFIG_NAND_S3C64XX) += s3c64xx.o +COBJS-$(CONFIG_NAND_OMAP3) += omap3.o endif COBJS := $(COBJS-y) Index: u-boot-arm/drivers/mtd/nand/omap3.c =================================================================== --- /dev/null +++ u-boot-arm/drivers/mtd/nand/omap3.c @@ -0,0 +1,370 @@ +/* + * (C) Copyright 2004-2008 Texas Instruments, + * Rohit Choraria + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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 +#include +#include +#include +#include + +unsigned char cs; +volatile unsigned long gpmc_cs_base_add; + +#define GPMC_BUF_EMPTY 0 +#define GPMC_BUF_FULL 1 + +/* + * omap_nand_hwcontrol - Set the address pointers corretly for the + * following address/data/command operation + */ +static void omap_nand_hwcontrol(struct mtd_info *mtd, int cmd, + unsigned int ctrl) +{ + register struct nand_chip *this = mtd->priv; + + /* Point the IO_ADDR to DATA and ADDRESS registers instead + of chip address */ + switch (ctrl) { + case NAND_CTRL_CHANGE | NAND_CTRL_CLE: + this->IO_ADDR_W = (void *) gpmc_cs_base_add + GPMC_NAND_CMD; + this->IO_ADDR_R = (void *) gpmc_cs_base_add + GPMC_NAND_DAT; + break; + case NAND_CTRL_CHANGE | NAND_CTRL_ALE: + this->IO_ADDR_W = (void *) gpmc_cs_base_add + GPMC_NAND_ADR; + this->IO_ADDR_R = (void *) gpmc_cs_base_add + GPMC_NAND_DAT; + break; + case NAND_CTRL_CHANGE | NAND_NCE: + this->IO_ADDR_W = (void *) gpmc_cs_base_add + GPMC_NAND_DAT; + this->IO_ADDR_R = (void *) gpmc_cs_base_add + GPMC_NAND_DAT; + break; + } + + if (cmd != NAND_CMD_NONE) + writeb(cmd, this->IO_ADDR_W); +} + +/* + * omap_nand_wait - called primarily after a program/erase operation + * so that we access NAND again only after the device + * is ready again. + * @mtd: MTD device structure + * @chip: nand_chip structure + */ +static int omap_nand_wait(struct mtd_info *mtd, struct nand_chip *chip) +{ + register struct nand_chip *this = mtd->priv; + int status = 0; + + this->IO_ADDR_W = (void *) gpmc_cs_base_add + GPMC_NAND_CMD; + this->IO_ADDR_R = (void *) gpmc_cs_base_add + GPMC_NAND_DAT; + /* Send the status command and loop until the device is free */ + while (!(status & 0x40)) { + writeb(NAND_CMD_STATUS & 0xFF, this->IO_ADDR_W); + status = readb(this->IO_ADDR_R); + } + return status; +} + +/* + * omap_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 omap_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); + while (GPMC_BUF_EMPTY == (readl(GPMC_STATUS) & GPMC_BUF_FULL)); + } +} + +/* + * omap_hwecc_init - Initialize the Hardware ECC for NAND flash in + * GPMC controller + * @mtd: MTD device structure + * + */ +static void omap_hwecc_init(struct nand_chip *chip) +{ + unsigned long val = 0x0; + + /* Init ECC Control Register */ + /* Clear all ECC | Enable Reg1 */ + val = ((0x00000001 << 8) | 0x00000001); + writel(val, GPMC_BASE + GPMC_ECC_CONTROL); + writel(0x3fcff000, GPMC_BASE + GPMC_ECC_SIZE_CONFIG); +} + +/* + * gen_true_ecc - This function will generate true ECC value, which + * can be used when correcting data read from NAND flash memory core + * + * @ecc_buf: buffer to store ecc code + * + * @return: re-formatted ECC value + */ +static unsigned int gen_true_ecc(u8 *ecc_buf) +{ + return ecc_buf[0] | (ecc_buf[1] << 16) | ((ecc_buf[2] & 0xF0) << 20) | + ((ecc_buf[2] & 0x0F) << 8); +} + +/* + * omap_correct_data - Compares the ecc read from nand spare area with ECC + * registers values and corrects one bit error if it has occured + * Further details can be had from OMAP TRM and the following selected links: + * http://en.wikipedia.org/wiki/Hamming_code + * http://www.cs.utexas.edu/users/plaxton/c/337/05f/slides/ErrorCorrection-4.pdf + * + * @mtd: MTD device structure + * @dat: page data + * @read_ecc: ecc read from nand flash + * @calc_ecc: ecc read from ECC registers + * + * @return 0 if data is OK or corrected, else returns -1 + */ +static int omap_correct_data(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + unsigned int orig_ecc, new_ecc, res, hm; + unsigned short parity_bits, byte; + unsigned char bit; + + /* Regenerate the orginal ECC */ + orig_ecc = gen_true_ecc(read_ecc); + new_ecc = gen_true_ecc(calc_ecc); + /* Get the XOR of real ecc */ + res = orig_ecc ^ new_ecc; + if (res) { + /* Get the hamming width */ + hm = hweight32(res); + /* Single bit errors can be corrected! */ + if (hm == 12) { + /* Correctable data! */ + parity_bits = res >> 16; + bit = (parity_bits & 0x7); + byte = (parity_bits >> 3) & 0x1FF; + /* Flip the bit to correct */ + dat[byte] ^= (0x1 << bit); + } else if (hm == 1) { + printf("Error: Ecc is wrong\n"); + /* ECC itself is corrupted */ + return 2; + } else { + printf("Error: Bad compare! failed\n"); + /* detected 2 bit error */ + return -1; + } + } + return 0; +} + +/* + * omap_calculate_ecc - Generate non-inverted ECC bytes. + * + * Using noninverted ECC can be considered ugly since writing a blank + * page ie. padding will clear the ECC bytes. This is no problem as + * long nobody is trying to write data on the seemingly unused page. + * Reading an erased page will produce an ECC mismatch between + * generated and read ECC bytes that has to be dealt with separately. + * @mtd: MTD structure + * @dat: unused + * @ecc_code: ecc_code buffer + */ +static int omap_calculate_ecc(struct mtd_info *mtd, const u_char *dat, + u_char *ecc_code) +{ + unsigned long val = 0x0; + unsigned long reg; + + /* Start Reading from HW ECC1_Result = 0x200 */ + reg = (unsigned long) (GPMC_BASE + GPMC_ECC1_RESULT); + val = readl(reg); + + ecc_code[0] = val & 0xFF; + ecc_code[1] = (val >> 16) & 0xFF; + ecc_code[2] = ((val >> 8) & 0x0F) | ((val >> 20) & 0xF0); + + /* Stop reading anymore ECC vals and clear old results + * enable will be called if more reads are required */ + reg = (unsigned long) (GPMC_BASE + GPMC_ECC_CONFIG); + writel(0x000, reg); + + return 0; +} + +/* + * omap_enable_ecc - This function enables the hardware ecc functionality + * @mtd: MTD device structure + * @mode: Read/Write mode + */ +static void omap_enable_hwecc(struct mtd_info *mtd, int mode) +{ + struct nand_chip *chip = mtd->priv; + unsigned int val, dev_width = (chip->options & NAND_BUSWIDTH_16) >> 1; + + switch (mode) { + case NAND_ECC_READ: + case NAND_ECC_WRITE: + /* Clear the ecc result registers + * select ecc reg as 1 + */ + writel(0x101, GPMC_BASE + GPMC_ECC_CONTROL); + /* Size 0 = 0xFF, Size1 is 0xFF - both are 512 bytes + * tell all regs to generate size0 sized regs + * we just have a single ECC engine for all CS + */ + writel(0x3FCFF000, GPMC_BASE + GPMC_ECC_SIZE_CONFIG); + val = (dev_width << 7) | (cs << 1) | (0x1); + writel(val, GPMC_BASE + GPMC_ECC_CONFIG); + break; + default: + printf("Error: Unrecognized Mode[%d]!\n", mode); + break; + } +} + +static struct nand_ecclayout hw_nand_oob_64 = { + .eccbytes = 12, + .eccpos = { + 2, 3, 4, 5, + 6, 7, 8, 9, + 10, 11, 12, 13}, + .oobfree = { {20, 50} } /* don't care */ +}; + +static struct nand_ecclayout sw_nand_oob_64 = { + .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} } +}; + +void omap_nand_switch_ecc(struct mtd_info *mtd, int hardware) +{ + struct nand_chip *nand = mtd->priv; + + if (!hardware) { + nand->ecc.mode = NAND_ECC_SOFT; + nand->ecc.layout = &sw_nand_oob_64; + nand->ecc.size = 256; /* set default eccsize */ + nand->ecc.bytes = 3; + nand->ecc.steps = 8; + nand->ecc.hwctl = 0; + nand->ecc.calculate = nand_calculate_ecc; + nand->ecc.correct = nand_correct_data; + } else { + nand->ecc.mode = NAND_ECC_HW; + nand->ecc.layout = &hw_nand_oob_64; + nand->ecc.size = 512; + nand->ecc.bytes = 3; + nand->ecc.steps = 4; + nand->ecc.hwctl = omap_enable_hwecc; + nand->ecc.correct = omap_correct_data; + nand->ecc.calculate = omap_calculate_ecc; + omap_hwecc_init(nand); + } +} + +/* + * Board-specific NAND initialization. The following members of the + * argument are board-specific: + * - 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 + * - waitfunc: hardwarespecific function for accesing device ready/busy line + * - ecc.hwctl: function to enable (reset) hardware ecc generator + * - 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 + */ +int board_nand_init(struct nand_chip *nand) +{ + int gpmc_config = 0; + cs = 0; + while (cs <= GPMC_MAX_CS) { + /* Each GPMC set for a single CS is at offset 0x30 */ + /* already remapped for us */ + gpmc_cs_base_add = (GPMC_CONFIG_CS0 + (cs * 0x30)); + /* xloader/Uboot would have written the NAND type for us + * NOTE: This is a temporary measure and cannot handle ONENAND. + * The proper way of doing this is to pass the setup of + * u-boot up to kernel using kernel params - something on + * the lines of machineID + */ + /* Check if NAND type is set */ + if ((readl(gpmc_cs_base_add + GPMC_CONFIG1) & 0xC00) == + 0x800) { + /* Found it!! */ + break; + } + cs++; + } + if (cs > GPMC_MAX_CS) { + printf("NAND: Unable to find NAND settings in " \ + "GPMC Configuration - quitting\n"); + } + + gpmc_config = readl(GPMC_CONFIG); + /* Disable Write protect */ + gpmc_config |= 0x10; + writel(gpmc_config, GPMC_CONFIG); + + nand->IO_ADDR_R = (int *) gpmc_cs_base_add + GPMC_NAND_DAT; + nand->IO_ADDR_W = (int *) gpmc_cs_base_add + GPMC_NAND_CMD; + + nand->cmd_ctrl = omap_nand_hwcontrol; + nand->options = NAND_NO_PADDING | NAND_CACHEPRG | NAND_NO_AUTOINCR | + NAND_BUSWIDTH_16 | NAND_NO_AUTOINCR; + + // FIXME: How to get common nand_read_buf16 here? It's static... + //nand->read_buf = nand_read_buf16; + /* Use custom write buf due to additional delay */ + nand->write_buf = omap_nand_write_buf16; + nand->ecc.mode = NAND_ECC_SOFT; + /* if RDY/BSY line is connected to OMAP then use the omap ready + * function and the generic nand_wait function which reads the + * status register after monitoring the RDY/BSY line. Otherwise + * use a standard chip delay which is slightly more than tR + * (AC Timing) of the NAND device and read the status register + * until you get a failure or success + */ + nand->waitfunc = omap_nand_wait; + nand->chip_delay = 50; + + return 0; +} Index: u-boot-arm/common/cmd_nand.c =================================================================== --- u-boot-arm.orig/common/cmd_nand.c +++ u-boot-arm/common/cmd_nand.c @@ -38,6 +38,11 @@ int find_dev_and_part(const char *id, st u8 *part_num, struct part_info **part); #endif +#if defined(CONFIG_OMAP) && (defined(CONFIG_OMAP3_BEAGLE)\ + || defined(CONFIG_OMAP3_EVM) || defined(CONFIG_OVERO)) +extern void omap_nand_switch_ecc(nand_info_t *nand, int hardware); +#endif + static int nand_dump(nand_info_t *nand, ulong off, int only_oob) { int i; @@ -234,6 +239,10 @@ int do_nand(cmd_tbl_t * cmdtp, int flag, strncmp(cmd, "read", 4) != 0 && strncmp(cmd, "write", 5) != 0 && strcmp(cmd, "scrub") != 0 && strcmp(cmd, "markbad") != 0 && strcmp(cmd, "biterr") != 0 && +#if defined(CONFIG_OMAP) && (defined(CONFIG_OMAP3_BEAGLE)\ + || defined(CONFIG_OMAP3_EVM) || defined(CONFIG_OVERO)) + strncmp(cmd, "ecc", 3) != 0 && +#endif strcmp(cmd, "lock") != 0 && strcmp(cmd, "unlock") != 0 ) goto usage; @@ -318,6 +327,22 @@ int do_nand(cmd_tbl_t * cmdtp, int flag, } +#if defined(CONFIG_OMAP) && (defined(CONFIG_OMAP3_BEAGLE) \ + || defined(CONFIG_OMAP3_EVM) || defined(CONFIG_OVERO)) + if (strncmp(cmd, "ecc", 3) == 0) { + if (argc < 2) + goto usage; + if (strncmp(argv[2], "hw", 2) == 0) + omap_nand_switch_ecc(nand, 1); + else if (strncmp(argv[2], "sw", 2) == 0) + omap_nand_switch_ecc(nand, 0); + else + goto usage; + + return 0; + } +#endif + if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) { int read; @@ -483,6 +508,10 @@ U_BOOT_CMD(nand, 5, 1, do_nand, "nand scrub - really clean NAND erasing bad blocks (UNSAFE)\n" "nand markbad off - mark bad block at offset (UNSAFE)\n" "nand biterr off - make a bit error at offset (UNSAFE)\n" +#if defined(CONFIG_OMAP) && (defined(CONFIG_OMAP3_BEAGLE) \ + || defined(CONFIG_OMAP3_EVM) || defined(CONFIG_OVERO)) + "nand ecc [hw/sw] - switch the ecc calculation algorithm \n" +#endif "nand lock [tight] [status]\n" " bring nand to lock state or display locked pages\n" "nand unlock [offset] [size] - unlock section\n");