606 lines
16 KiB
C
606 lines
16 KiB
C
/**
|
|
* @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;
|
|
unsigned char ecc_parity_pairs;
|
|
unsigned int ecc_config;
|
|
};
|
|
|
|
/* Typical BOOTROM oob layouts-requires hwecc **/
|
|
|
|
/** Large Page x8 NAND device Layout */
|
|
static struct nand_ecclayout ecc_lp_x8 = {
|
|
.eccbytes = 12,
|
|
.eccpos = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12},
|
|
.oobfree = {
|
|
{
|
|
.offset = 60,
|
|
.length = 2,
|
|
}
|
|
}
|
|
};
|
|
|
|
/** Large Page x16 NAND device Layout */
|
|
static struct nand_ecclayout ecc_lp_x16 = {
|
|
.eccbytes = 12,
|
|
.eccpos = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13},
|
|
.oobfree = {
|
|
{
|
|
.offset = 60,
|
|
.length = 2,
|
|
}
|
|
}
|
|
};
|
|
|
|
/** Small Page x8 NAND device Layout */
|
|
static struct nand_ecclayout ecc_sp_x8 = {
|
|
.eccbytes = 3,
|
|
.eccpos = {1, 2, 3},
|
|
.oobfree = {
|
|
{
|
|
.offset = 14,
|
|
.length = 2,
|
|
}
|
|
}
|
|
};
|
|
|
|
/** Small Page x16 NAND device Layout */
|
|
static struct nand_ecclayout ecc_sp_x16 = {
|
|
.eccbytes = 3,
|
|
.eccpos = {2, 3, 4},
|
|
.oobfree = {
|
|
{
|
|
.offset = 14,
|
|
.length = 2 }
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @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;
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
struct nand_ecclayout *layout, *lsp, *llp;
|
|
|
|
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 (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
|
|
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);
|
|
|
|
/* first scan to find the device and get the page size */
|
|
if (nand_scan_ident(minfo, 1)) {
|
|
err = -ENXIO;
|
|
goto out_release_mem;
|
|
}
|
|
|
|
switch (pdata->device_width) {
|
|
case 8:
|
|
lsp = &ecc_sp_x8;
|
|
llp = &ecc_lp_x8;
|
|
break;
|
|
case 16:
|
|
lsp = &ecc_sp_x16;
|
|
llp = &ecc_lp_x16;
|
|
break;
|
|
default:
|
|
err = -EINVAL;
|
|
goto out_release_mem;
|
|
}
|
|
|
|
switch (minfo->writesize) {
|
|
case 512:
|
|
layout = lsp;
|
|
break;
|
|
case 2048:
|
|
layout = llp;
|
|
break;
|
|
default:
|
|
err = -EINVAL;
|
|
goto out_release_mem;
|
|
}
|
|
|
|
/* second phase scan */
|
|
if (nand_scan_tail(minfo)) {
|
|
err = -ENXIO;
|
|
goto out_release_mem;
|
|
}
|
|
|
|
if (pdata->plat_options & NAND_HWECC_ENABLE)
|
|
nand->ecc.layout = layout;
|
|
|
|
/* 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);
|