diff options
Diffstat (limited to 'drivers/mtd/nand/nand_omap_gpmc.c')
-rw-r--r-- | drivers/mtd/nand/nand_omap_gpmc.c | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/drivers/mtd/nand/nand_omap_gpmc.c b/drivers/mtd/nand/nand_omap_gpmc.c new file mode 100644 index 0000000000..c6647e56ed --- /dev/null +++ b/drivers/mtd/nand/nand_omap_gpmc.c @@ -0,0 +1,534 @@ +/** + * @file + * @brief Provide Generic GPMC NAND implementation for OMAP platforms + * + * FileName: arch/arm/mach-omap/gpmc_nand.c + * + * GPMC has a NAND controller inbuilt. This provides a generic implementation + * for board files to register a nand device. drivers/nand/nand_base.c takes + * care of identifing the type of device, size etc. + * + * A typical device registration is as follows: + * + * @code + * static struct device_d my_nand_device = { + * .name = "gpmc_nand", + * .id = some identifier you need to show.. e.g. "gpmc_nand0" + * .map_base = GPMC base address + * .size = GPMC address map size. + * .platform_data = platform data - required - explained below + * }; + * platform data required: + * static struct gpmc_nand_platform_data nand_plat = { + * .cs = give the chip select of the device + * .device_width = what is the width of the device 8 or 16? + * .max_timeout = delay desired for operation + * .wait_mon_pin = do you use wait monitoring? if so wait pin + * .plat_options = platform options. + * NAND_HWECC_ENABLE/DISABLE - hw ecc enable/disable + * NAND_WAITPOL_LOW/HIGH - wait pin polarity + * .oob = if you would like to replace oob with a custom OOB. + * .nand_setup = if you would like a special setup function to be called + * .priv = any params you'd like to save(e.g. like nand_setup to use) + *}; + * then in your code, you'd device_register(&my_nand_device); + * @endcode + * + * Note: + * @li Enable CONFIG_NAND_OMAP_GPMC_HWECC in menuconfig to get H/w ECC support + * @li You may choose to register two "devices" for the same CS to get BOTH + * hwecc and swecc devices. + * @li You can choose to have your own OOB definition for compliance with ROM + * code organization - only if you dont want to use NAND's default oob layout. + * see GPMC_NAND_ECC_LP_x8_LAYOUT etc.. + * + * @see gpmc_nand_platform_data + * @warning Remember to initialize GPMC before initializing the nand dev. + */ +/* + * (C) Copyright 2008 + * Texas Instruments, <www.ti.com> + * Nishanth Menon <x0nishan@ti.com> + * + * Based on: + * drivers/mtd/nand/omap2.c from linux kernel + * + * Copyright (c) 2004 Texas Instruments, Jian Zhang <jzhang@ti.com> + * Copyright (c) 2004 Micron Technology Inc. + * Copyright (c) 2004 David Brownell + * + * 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 <common.h> +#include <errno.h> +#include <init.h> +#include <driver.h> +#include <malloc.h> +#include <clock.h> +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/nand_ecc.h> +#include <asm/io.h> +#include <mach/silicon.h> +#include <mach/gpmc.h> +#include <mach/gpmc_nand.h> + +/* Enable me to get tons of debug messages -for use without jtag */ +#if 0 +#define gpmcnand_dbg(FORMAT, ARGS...) fprintf(stdout,\ + "gpmc_nand:%s:%d:Entry:"FORMAT"\n",\ + __func__, __LINE__, ARGS) +#else +#define gpmcnand_dbg(FORMAT, ARGS...) +#endif +#define gpmcnand_err(ARGS...) fprintf(stderr, "omapnand: " ARGS); + +/** internal structure maintained for nand information */ +struct gpmc_nand_info { + struct nand_hw_control controller; + struct device_d *pdev; + struct gpmc_nand_platform_data *pdata; + struct nand_chip nand; + struct mtd_info minfo; + int gpmc_cs; + void *gpmc_command; + void *gpmc_address; + void *gpmc_data; + unsigned long gpmc_base; + unsigned char wait_mon_mask; + uint64_t timeout; + unsigned inuse:1; + unsigned wait_pol:1; +#ifdef CONFIG_NAND_OMAP_GPMC_HWECC + unsigned char ecc_parity_pairs; + unsigned int ecc_config; +#endif +}; + +/** + * @brief calls the platform specific dev_ready functionds + * + * @param mtd - mtd info structure + * + * @return + */ +static int omap_dev_ready(struct mtd_info *mtd) +{ + struct nand_chip *nand = (struct nand_chip *)(mtd->priv); + struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); + uint64_t start = get_time_ns(); + unsigned long comp; + + gpmcnand_dbg("mtd=%x", (unsigned int)mtd); + /* What do we mean by assert and de-assert? */ + comp = (oinfo->wait_pol == NAND_WAITPOL_HIGH) ? + oinfo->wait_mon_mask : 0x0; + while (1) { + /* Breakout condition */ + if (is_timeout(start, oinfo->timeout)) { + gpmcnand_err("timedout\n"); + return -ETIMEDOUT; + } + /* if the wait is released, we are good to go */ + if (comp == + (readl(oinfo->gpmc_base + GPMC_STATUS) && + oinfo->wait_mon_mask)) + break; + } + return 0; +} + +/** + * @brief This function will enable or disable the Write Protect feature on + * NAND device. GPMC has a single WP bit for all CS devices.. + * + * @param oinfo omap nand info + * @param mode 0-disable else enable + * + * @return none + */ +static void gpmc_nand_wp(struct gpmc_nand_info *oinfo, int mode) +{ + unsigned long config = readl(oinfo->gpmc_base + GPMC_CFG); + + gpmcnand_dbg("mode=%x", mode); + if (mode) + config &= ~(NAND_WP_BIT); /* WP is ON */ + else + config |= (NAND_WP_BIT); /* WP is OFF */ + + writel(config, oinfo->gpmc_base + GPMC_CFG); +} + +/** + * @brief respond to hw event change request + * + * MTD layer uses NAND_CTRL_CLE etc to control selection of the latch + * we hoodwink by changing the R and W registers according to the state + * we are requested. + * + * @param mtd - mtd info structure + * @param cmd command mtd layer is requesting + * + * @return none + */ +static void omap_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) +{ + struct nand_chip *nand = (struct nand_chip *)(mtd->priv); + struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); + gpmcnand_dbg("mtd=%x nand=%x cmd=%x ctrl = %x", (unsigned int)mtd, nand, + cmd, ctrl); + switch (ctrl) { + case NAND_CTRL_CHANGE | NAND_CTRL_CLE: + nand->IO_ADDR_W = oinfo->gpmc_command; + nand->IO_ADDR_R = oinfo->gpmc_data; + break; + + case NAND_CTRL_CHANGE | NAND_CTRL_ALE: + nand->IO_ADDR_W = oinfo->gpmc_address; + nand->IO_ADDR_R = oinfo->gpmc_data; + break; + + case NAND_CTRL_CHANGE | NAND_NCE: + nand->IO_ADDR_W = oinfo->gpmc_data; + nand->IO_ADDR_R = oinfo->gpmc_data; + break; + } + + if (cmd != NAND_CMD_NONE) + writeb(cmd, nand->IO_ADDR_W); + return; +} + +#ifdef CONFIG_NAND_OMAP_GPMC_HWECC + +/** + * @brief This function will generate true ECC value, which can be used + * when correcting data read from NAND flash memory core + * + * @param ecc_buf buffer to store ecc code + * + * @return re-formatted ECC value + */ +static unsigned int gen_true_ecc(u8 *ecc_buf) +{ + gpmcnand_dbg("ecc_buf=%x 1, 2 3 = %x %x %x", (unsigned int)ecc_buf, + ecc_buf[0], ecc_buf[1], ecc_buf[2]); + return ecc_buf[0] | (ecc_buf[1] << 16) | ((ecc_buf[2] & 0xF0) << 20) | + ((ecc_buf[2] & 0x0F) << 8); +} + +/** + * @brief 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 + * + * @param mtd - mtd info structure + * @param dat page data + * @param read_ecc ecc readback + * @param calc_ecc calculated ecc (from reg) + * + * @return 0 if data is OK or corrected, else returns -1 + */ +static int omap_correct_data(struct mtd_info *mtd, uint8_t *dat, + uint8_t *read_ecc, uint8_t *calc_ecc) +{ + unsigned int orig_ecc, new_ecc, res, hm; + unsigned short parity_bits, byte; + unsigned char bit; + struct nand_chip *nand = (struct nand_chip *)(mtd->priv); + struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); + + gpmcnand_dbg("mtd=%x dat=%x read_ecc=%x calc_ecc=%x", (unsigned int)mtd, + (unsigned int)dat, (unsigned int)read_ecc, + (unsigned int)calc_ecc); + + /* 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 == oinfo->ecc_parity_pairs) { + /* 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) { + gpmcnand_err("Ecc is wrong\n"); + /* ECC itself is corrupted */ + return 2; + } else { + gpmcnand_err("bad compare! failed\n"); + /* detected 2 bit error */ + return -1; + } + } + return 0; +} + +/** + * @brief 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. + * + * @param mtd - mtd info structure + * @param dat data being written + * @param ecc_code ecc code returned back to nand layer + * + * @return 0 + */ +static int omap_calculate_ecc(struct mtd_info *mtd, const uint8_t *dat, + uint8_t *ecc_code) +{ + struct nand_chip *nand = (struct nand_chip *)(mtd->priv); + struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); + unsigned int val; + gpmcnand_dbg("mtd=%x dat=%x ecc_code=%x", (unsigned int)mtd, + (unsigned int)dat, (unsigned int)ecc_code); + debug("ecc 0 1 2 = %x %x %x", ecc_code[0], ecc_code[1], ecc_code[2]); + + /* Since we smartly tell mtd driver to use eccsize of 512, only + * ECC Reg1 will be used.. we just read that */ + val = readl(oinfo->gpmc_base + GPMC_ECC1_RESULT); + 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 */ + writel(0x000, oinfo->gpmc_base + GPMC_ECC_CONFIG); + return 0; +} + +/* + * omap_enable_ecc - This function enables the hardware ecc functionality + * @param mtd - mtd info structure + * @param mode - Read/Write mode + */ +static void omap_enable_hwecc(struct mtd_info *mtd, int mode) +{ + struct nand_chip *nand = (struct nand_chip *)(mtd->priv); + struct gpmc_nand_info *oinfo = (struct gpmc_nand_info *)(nand->priv); + gpmcnand_dbg("mtd=%x mode=%x", (unsigned int)mtd, mode); + switch (mode) { + case NAND_ECC_READ: + case NAND_ECC_WRITE: + /* Clear the ecc result registers + * select ecc reg as 1 + */ + writel(0x101, oinfo->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, oinfo->gpmc_base + + GPMC_ECC_SIZE_CONFIG); + writel(oinfo->ecc_config, oinfo->gpmc_base + + GPMC_ECC_CONFIG); + break; + default: + gpmcnand_err("Error: Unrecognized Mode[%d]!\n", mode); + break; + } +} +#endif /* CONFIG_NAND_OMAP_GPMC_HWECC */ + +/** + * @brief nand device probe. + * + * @param pdev -matching device + * + * @return -failure reason or give 0 + */ +static int gpmc_nand_probe(struct device_d *pdev) +{ + struct gpmc_nand_info *oinfo; + struct gpmc_nand_platform_data *pdata; + struct nand_chip *nand; + struct mtd_info *minfo; + unsigned long cs_base; + int err; + + gpmcnand_dbg("pdev=%x", (unsigned int)pdev); + pdata = (struct gpmc_nand_platform_data *)pdev->platform_data; + if (pdata == NULL) { + gpmcnand_err("platform data missing\n"); + return -ENODEV; + } + + oinfo = calloc(1, sizeof(struct gpmc_nand_info)); + if (!oinfo) { + gpmcnand_err("oinfo alloc failed!\n"); + return -ENOMEM; + } + + /* fill up my data structures */ + oinfo->pdev = pdev; + oinfo->pdata = pdata; + pdev->platform_data = (void *)oinfo; + + nand = &oinfo->nand; + nand->priv = (void *)oinfo; + + minfo = &oinfo->minfo; + minfo->priv = (void *)nand; + + if (pdata->cs >= GPMC_NUM_CS) { + gpmcnand_err("Invalid CS!\n"); + err = -EINVAL; + goto out_release_mem; + } + /* Setup register specific data */ + oinfo->gpmc_cs = pdata->cs; + oinfo->gpmc_base = pdev->map_base; + cs_base = oinfo->gpmc_base + GPMC_CONFIG1_0 + + (pdata->cs * GPMC_CONFIG_CS_SIZE); + oinfo->gpmc_command = (void *)(cs_base + GPMC_CS_NAND_COMMAND); + oinfo->gpmc_address = (void *)(cs_base + GPMC_CS_NAND_ADDRESS); + oinfo->gpmc_data = (void *)(cs_base + GPMC_CS_NAND_DATA); + oinfo->timeout = pdata->max_timeout; + debug("GPMC Details:\n" + "GPMC BASE=%x\n" + "CMD=%x\n" + "ADDRESS=%x\n" + "DATA=%x\n" + "CS_BASE=%x\n", + oinfo->gpmc_base, oinfo->gpmc_command, + oinfo->gpmc_address, oinfo->gpmc_data, cs_base); + + /* If we are 16 bit dev, our gpmc config tells us that */ + if ((readl(cs_base) & 0x3000) == 0x1000) { + debug("16 bit dev\n"); + nand->options |= NAND_BUSWIDTH_16; + } + + /* Same data register for in and out */ + nand->IO_ADDR_W = nand->IO_ADDR_R = (void *)oinfo->gpmc_data; + /* + * 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 + */ + if (pdata->wait_mon_pin > 4) { + gpmcnand_err("Invalid wait monitoring pin\n"); + err = -EINVAL; + goto out_release_mem; + } + if (pdata->wait_mon_pin) { + /* Set up the wait monitoring mask + * This is GPMC_STATUS reg relevant */ + oinfo->wait_mon_mask = (0x1 << (pdata->wait_mon_pin - 1)) << 8; + oinfo->wait_pol = (pdata->plat_options & NAND_WAITPOL_MASK); + nand->dev_ready = omap_dev_ready; + nand->chip_delay = 0; + } else { + /* use the default nand_wait function */ + nand->chip_delay = 50; + } + + /* Use default cmdfunc */ + /* nand cmd control */ + nand->cmd_ctrl = omap_hwcontrol; + + /* Dont do a bbt scan at the start */ + nand->options |= NAND_SKIP_BBTSCAN; + + /* State my controller */ + nand->controller = &oinfo->controller; + + /* if a different placement scheme is requested */ + if (pdata->oob) + nand->ecc.layout = pdata->oob; + +#ifdef CONFIG_NAND_OMAP_GPMC_HWECC + if (pdata->plat_options & NAND_HWECC_ENABLE) { + /* Program how many columns we expect+ + * enable the cs we want and enable the engine + */ + oinfo->ecc_config = (pdata->cs << 1) | + ((nand->options & NAND_BUSWIDTH_16) ? + (0x1 << 7) : 0x0) | 0x1; + nand->ecc.hwctl = omap_enable_hwecc; + nand->ecc.calculate = omap_calculate_ecc; + nand->ecc.correct = omap_correct_data; + nand->ecc.mode = NAND_ECC_HW; + nand->ecc.size = 512; + nand->ecc.bytes = 3; + nand->ecc.steps = nand->ecc.layout->eccbytes / nand->ecc.bytes; + oinfo->ecc_parity_pairs = 12; + } else +#endif + nand->ecc.mode = NAND_ECC_SOFT; + + /* All information is ready.. now lets call setup, if present */ + if (pdata->nand_setup) { + err = pdata->nand_setup(pdata); + if (err) { + gpmcnand_err("pdataform setup failed\n"); + goto out_release_mem; + } + } + /* Remove write protection */ + gpmc_nand_wp(oinfo, 0); + + /* we do not know what state of device we have is, so + * Send a reset to the device + * 8 bit write will work on 16 and 8 bit devices + */ + writeb(NAND_CMD_RESET, oinfo->gpmc_command); + mdelay(1); + + /* In normal mode, we scan to get just the device + * presence and then to get the device geometry + */ + if (nand_scan(minfo, 1)) { + gpmcnand_err("device scan failed\n"); + err = -ENXIO; + goto out_release_mem; + } + + /* We are all set to register with the system now! */ + err = add_mtd_device(minfo); + if (err) { + gpmcnand_err("device registration failed\n"); + goto out_release_mem; + } + return 0; + +out_release_mem: + if (oinfo) + free(oinfo); + + gpmcnand_err("Failed!!\n"); + return err; +} + +/** GMPC nand driver -> device registered by platforms */ +static struct driver_d gpmc_nand_driver = { + .name = "gpmc_nand", + .probe = gpmc_nand_probe, +}; + +static int gpmc_nand_init(void) +{ + return register_driver(&gpmc_nand_driver); +} + +device_initcall(gpmc_nand_init); |