810 lines
21 KiB
C

/* linux/drivers/mtd/nand/s3c_nand.c
*
* Copyright (c) 2007 Samsung Electronics
*
* Samsung S3C NAND driver
*
* $Id: s3c_nand.c,v 1.20 2008/04/30 07:36:39 ihlee215 Exp $
*
* 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
*
* Based on nand driver from Ben Dooks <ben@simtec.co.uk>
* modified by scsuh. based on au1550nd.c
*
* Many functions about hardware ecc are implemented by jsgood.
*/
/* Simple H/W Table for Implementation of S3C nand driver
* by scsuh
* ------------------------------------------------------------------
* | En/Dis CE | required | |
* | En/Dis ALE | X | * nand controller does |
* | En/Dis CLE | X | * nand controller does |
* | Wait/Ready | required | |
* | Write Command | required | |
* | Write Address | required | |
* | Write Data | required | |
* | Read Data | required | |
* | WP on/off | required | * board specific |
* | AP Specific Init | required | |
* ------------------------------------------------------------------
*/
#include <linux/module.h>
#include <linux/delay.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/err.h>
#include <linux/clk.h>
#include <linux/jiffies.h>
#include <linux/sched.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand_ecc.h>
#include <linux/mtd/partitions.h>
#include <asm/io.h>
#include <asm/mach-types.h>
#include <asm/arch/nand.h>
#include <asm/arch/regs-nand.h>
struct s3c_nand_info {
/* mtd info */
struct nand_hw_control controller;
struct s3c_nand_mtd_info *mtds;
struct s3c2410_platform_nand *platform;
/* device info */
struct device *device;
struct resource *area;
struct clk *clk;
void __iomem *regs;
void __iomem *sel_reg;
int sel_bit;
int mtd_count;
//enum s3c_cpu_type cpu_type;
};
static struct s3c_nand_info s3c_nand;
static struct mtd_info *s3c_mtd = NULL;
/* Nand flash definition values by jsgood */
#define S3C_NAND_TYPE_UNKNOWN 0x0
#define S3C_NAND_TYPE_SLC 0x1
#define S3C_NAND_TYPE_MLC 0x2
/*
* Cached progamming disabled for now, Not sure if its worth the
* trouble. The speed gain is not very impressive. (2.3->2.6Mib/s)
*
* if want to use cached program, define next
* by jsgood (modified to keep prevent rule)
*/
#undef CONFIG_MTD_NAND_S3C_CACHEDPROG
/* Nand flash global values by jsgood */
int cur_ecc_mode = 0;
int nand_type = S3C_NAND_TYPE_UNKNOWN;
/* Nand flash oob definition for SLC 512b page size by jsgood */
static struct nand_ecclayout s3c_nand_oob_16 = {
.eccbytes = 3,
.eccpos = {2, 3, 4},
.oobfree = {
{.offset = 6,
.length = 10}}
};
#if 1
/* Nand flash oob definition for SLC 2k page size by jsgood */
static struct nand_ecclayout s3c_nand_oob_64 = {
.eccbytes = 4,
.eccpos = {40, 41, 42, 43},
.oobfree = {
{.offset = 2,
.length = 38}}
};
#else
static struct nand_ecclayout s3c_nand_oob_64 = {
.eccbytes = 4,
.eccpos = {56, 57, 58, 59},
.oobfree = {
{2, 6}, {13, 3}, {18, 6}, {29, 3},
{34, 6}, {45, 3}, {50, 6}, {61, 3}}
};
#endif
/* Nand flash oob definition for MLC 2k page size by jsgood */
static struct nand_ecclayout s3c_nand_oob_mlc_64 = {
.eccbytes = 32,
.eccpos = {
32, 33, 34, 35, 36, 37, 38, 39,
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 = {
{.offset = 2,
.length = 28}}
};
#if defined(CONFIG_MTD_NAND_S3C_DEBUG)
/*
* Function to print out oob buffer for debugging
* Written by jsgood
*/
void print_oob(const char *header, struct mtd_info *mtd)
{
int i;
struct nand_chip *chip = mtd->priv;
printk("%s:\t", header);
for(i = 0; i < 64; i++)
printk("%02x ", chip->oob_poi[i]);
printk("\n");
}
EXPORT_SYMBOL(print_oob);
#endif
/*
* Hardware specific access to control-lines function
* Written by jsgood
*/
static void s3c_nand_hwcontrol(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
unsigned int cur;
void __iomem *regs = s3c_nand.regs;
if (ctrl & NAND_CTRL_CHANGE) {
if (ctrl & NAND_NCE) {
if (dat != NAND_CMD_NONE) {
cur = readl(regs + S3C2440_NFCONT);
cur &= ~S3C2412_NFCONT_nFCE0;
writel(cur, regs + S3C2440_NFCONT);
}
} else {
cur = readl(regs + S3C2440_NFCONT);
cur |= S3C2412_NFCONT_nFCE0;
writel(cur, regs + S3C2440_NFCONT);
}
}
if (dat != NAND_CMD_NONE) {
if (ctrl & NAND_CLE)
writeb(dat, regs + S3C2440_NFCMD);
else if (ctrl & NAND_ALE)
writeb(dat, regs + S3C2440_NFADDR);
}
}
/*
* Function for checking device ready pin
* Written by jsgood
*/
static int s3c_nand_device_ready(struct mtd_info *mtd)
{
void __iomem *regs = s3c_nand.regs;
/* it's to check the RnB nand signal bit and return to device ready condition in nand_base.c */
return ((readl(regs + S3C2412_NFSTAT) & S3C2410_NFSTAT_BUSY));
}
/*
* Flash based bbt used
*/
static int s3c_nand_scan_bbt(struct mtd_info *mtd)
{
return nand_default_bbt(mtd);
}
#if defined(CONFIG_MTD_NAND_S3C_HWECC)
#if 0
/*
* S3C Nand flash chip enable function
* Written by jsgood
*/
static void s3c_nand_ce_on(struct mtd_info *mtd)
{
struct nand_chip *chip = mtd->priv;
chip->cmd_ctrl(mtd, 0x0, NAND_NCE | NAND_CTRL_CHANGE);
nand_wait_ready(mtd);
}
/*
* S3C Nand flash chip disable function
* Written by jsgood
*/
static void s3c_nand_ce_off(struct mtd_info *mtd)
{
struct nand_chip *chip = mtd->priv;
chip->cmd_ctrl(mtd, NAND_CMD_NONE, NAND_CTRL_CHANGE);
nand_wait_ready(mtd);
}
#endif
/*
* Function for checking ECCEncDone in NFSTAT
* Written by jsgood
*/
static void s3c_nand_wait_enc(void)
{
/* S3C2412 can not check S3C2412_NFSTAT_ECC_ENCDONE */
#if defined(CONFIG_S3C2443) || defined(CONFIG_S3C6400) || defined(CONFIG_S3C2450) || defined(CONFIG_CPU_S3C2416)
void __iomem *regs = s3c_nand.regs;
unsigned long timeo = jiffies;
timeo += 16; /* when Hz=200, jiffies interval 1/200=5mS, waiting for 80mS 80/5 = 16 */
/* Apply this short delay always to ensure that we do wait tWB in
* any case on any machine. */
while (time_before(jiffies, timeo)) {
if( readl(regs + S3C2412_NFSTAT) & S3C2412_NFSTAT_ECC_ENCDONE )
break;
cond_resched();
}
#else
return;
#endif
}
/*
* Function for checking ECCDecDone in NFSTAT
* Written by jsgood
*/
static void s3c_nand_wait_dec(void)
{
void __iomem *regs = s3c_nand.regs;
unsigned long timeo = jiffies;
timeo += 16; /* when Hz=200, jiffies interval 1/200=5mS, waiting for 80mS 80/5 = 16 */
/* Apply this short delay always to ensure that we do wait tWB in
* any case on any machine. */
while (time_before(jiffies, timeo)) {
if(readl(regs + S3C2412_NFSTAT) & S3C2412_NFSTAT_ECC_DECDONE)
break;
cond_resched();
}
}
/*
* Function for checking ECC Busy
* Written by jsgood
*/
static void s3c_nand_wait_ecc_busy(void)
{
void __iomem *regs = s3c_nand.regs;
unsigned long timeo = jiffies;
timeo += 16; /* when Hz=200, jiffies interval 1/200=5mS, waiting for 80mS 80/5 = 16 */
/* Apply this short delay always to ensure that we do wait tWB in
* any case on any machine. */
while (time_before(jiffies, timeo)) {
if (!(readl(regs + S3C2412_NFMECC_ERR0) & S3C_NFSTAT0_ECCBUSY))
break;
cond_resched();
}
}
/*
* This function is called before encoding ecc codes to ready ecc engine.
* Written by jsgood
*/
void s3c_nand_enable_hwecc(struct mtd_info *mtd, int mode)
{
struct nand_chip *nand = mtd->priv;
u_long nfcont;
u_long nfconf;
void __iomem *regs = s3c_nand.regs;
cur_ecc_mode = mode;
#if defined(CONFIG_CPU_S3C2443) || defined(CONFIG_CPU_S3C2450) || defined(CONFIG_CPU_S3C2416) || defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C6400) || defined(CONFIG_CPU_S3C6410)
nfconf = readl(regs + S3C2410_NFCONF);
if (((nand->cellinfo >> 2) & 0x3) == 0)
nfconf &= ~S3C2412_NFCONF_ECC_MLC; /* SLC */
else
nfconf |= S3C2412_NFCONF_ECC_MLC; /* MLC */
writel(nfconf, regs + S3C2410_NFCONF);
#endif
/* Init main ECC & unlock */
nfcont = readl(regs + S3C2440_NFCONT);
nfcont |= S3C_NFCONT_INITECC;
nfcont &= ~S3C2412_NFCONT_MAIN_ECC_LOCK;
#if defined(CONFIG_CPU_S3C2443) || defined(CONFIG_CPU_S3C2450) || defined(CONFIG_CPU_S3C2416) || defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C6400) || defined(CONFIG_CPU_S3C6410)
if (mode == NAND_ECC_WRITE)
nfcont |= S3C2412_NFCONT_ECC4_DIRWR;
else if (mode == NAND_ECC_READ)
nfcont &= ~S3C2412_NFCONT_ECC4_DIRWR;
#endif
writel(nfcont, regs + S3C2440_NFCONT);
}
/*
* This function is called immediately after encoding ecc codes.
* This function returns encoded ecc codes.
* Written by jsgood
*/
int s3c_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)
{
u_long nfcont, nfmecc0, nfmecc1;
void __iomem *regs = s3c_nand.regs;
/* Lock */
nfcont = readl(regs + S3C2440_NFCONT);
nfcont |= S3C2412_NFCONT_MAIN_ECC_LOCK;
writel(nfcont, regs + S3C2440_NFCONT);
if (nand_type == S3C_NAND_TYPE_SLC) {
nfmecc0 = readl(regs + S3C2412_NFMECC0);
ecc_code[0] = nfmecc0 & 0xff;
ecc_code[1] = (nfmecc0 >> 8) & 0xff;
ecc_code[2] = (nfmecc0 >> 16) & 0xff;
ecc_code[3] = (nfmecc0 >> 24) & 0xff;
} else {
if (cur_ecc_mode == NAND_ECC_READ)
s3c_nand_wait_dec();
else {
s3c_nand_wait_enc();
nfmecc0 = readl(regs + S3C2412_NFMECC0);
nfmecc1 = readl(regs + S3C2412_NFMECC1);
ecc_code[0] = nfmecc0 & 0xff;
ecc_code[1] = (nfmecc0 >> 8) & 0xff;
ecc_code[2] = (nfmecc0 >> 16) & 0xff;
ecc_code[3] = (nfmecc0 >> 24) & 0xff;
ecc_code[4] = nfmecc1 & 0xff;
ecc_code[5] = (nfmecc1 >> 8) & 0xff;
ecc_code[6] = (nfmecc1 >> 16) & 0xff;
ecc_code[7] = (nfmecc1 >> 24) & 0xff;
}
}
return 0;
}
/*
* This function determines whether read data is good or not.
* If SLC, must write ecc codes to controller before reading status bit.
* If MLC, status bit is already set, so only reading is needed.
* If status bit is good, return 0.
* If correctable errors occured, do that.
* If uncorrectable errors occured, return -1.
* Written by jsgood
*/
int s3c_nand_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc)
{
int ret = -1;
u_long nfestat0, nfestat1, nfmeccdata0, nfmeccdata1, nfmlcbitpt;
u_char err_type;
void __iomem *regs = s3c_nand.regs;
if (!dat) {
printk("No page data.\n");
return ret;
}
if (nand_type == S3C_NAND_TYPE_SLC) {
/* SLC: Write ECC data to compare */
nfmeccdata0 = (calc_ecc[1] << 16) | calc_ecc[0];
nfmeccdata1 = (calc_ecc[3] << 16) | calc_ecc[2];
writel(nfmeccdata0, regs + S3C2440_NFECCD0);
writel(nfmeccdata1, regs + S3C2440_NFECCD1);
/* Read ECC status */
nfestat0 = readl(regs + S3C2412_NFMECC_ERR0);
err_type = nfestat0 & 0x3;
switch (err_type) {
case 0: /* No error */
ret = 0;
break;
case 1: /* 1 bit error (Correctable)
(nfestat0 >> 7) & 0x7ff :error byte number
(nfestat0 >> 4) & 0x7 :error bit number */
printk("S3C NAND: 1 bit error detected at byte %ld. Correcting from "
"0x%02x ", (nfestat0 >> 7) & 0x7ff, dat[(nfestat0 >> 7) & 0x7ff]);
dat[(nfestat0 >> 7) & 0x7ff] ^= (1 << ((nfestat0 >> 4) & 0x7));
printk("to 0x%02x...OK\n", dat[(nfestat0 >> 7) & 0x7ff]);
ret = 1;
break;
case 2: /* Multiple error */
case 3: /* ECC area error */
printk("S3C NAND: ECC uncorrectable error detected. Not correctable.\n");
ret = -1;
break;
}
} else {
/* MLC: */
s3c_nand_wait_ecc_busy();
nfestat0 = readl(regs + S3C2412_NFMECC_ERR0);
nfestat1 = readl(regs + S3C2412_NFMECC_ERR1);
nfmlcbitpt = readl(regs + S3C_NFMLCBITPT);
err_type = (nfestat0 >> 26) & 0x7;
/* No error, If free page (all 0xff) */
if ((nfestat0 >> 29) & 0x1) {
err_type = 0;
} else {
/* No error, If all 0xff from 17th byte in oob (in case of JFFS2 format) */
if (dat) {
if (dat[17] == 0xff && dat[26] == 0xff && dat[35] == 0xff && dat[44] == 0xff && dat[54] == 0xff)
err_type = 0;
}
}
switch (err_type) {
case 5: /* Uncorrectable */
printk("S3C NAND: ECC uncorrectable error detected.\n");
ret = -1;
break;
case 4: /* 4 bit error (Correctable) */
dat[(nfestat1 >> 16) & 0x3ff] ^= ((nfmlcbitpt >> 24) & 0xff);
case 3: /* 3 bit error (Correctable) */
dat[nfestat1 & 0x3ff] ^= ((nfmlcbitpt >> 16) & 0xff);
case 2: /* 2 bit error (Correctable) */
dat[(nfestat0 >> 16) & 0x3ff] ^= ((nfmlcbitpt >> 8) & 0xff);
case 1: /* 1 bit error (Correctable) */
printk("S3C NAND: %d bit(s) error detected. Corrected successfully.\n", err_type);
dat[nfestat0 & 0x3ff] ^= (nfmlcbitpt & 0xff);
ret = err_type;
break;
case 0: /* No error */
ret = 0;
break;
}
}
return ret;
}
/*
* Hardware specific page read function for MLC.
* Written by jsgood
*/
int s3c_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip,
uint8_t *buf)
{
int i, stat, eccsize = chip->ecc.size;
int eccbytes = chip->ecc.bytes;
int eccsteps = chip->ecc.steps;
int col = 0;
uint8_t *p = buf;
uint32_t *mecc_pos = chip->ecc.layout->eccpos;
/* Step1: read whole oob */
col = mtd->writesize;
chip->cmdfunc(mtd, NAND_CMD_RNDOUT, col, -1);
chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
col = 0;
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
chip->cmdfunc(mtd, NAND_CMD_RNDOUT, col, -1);
chip->ecc.hwctl(mtd, NAND_ECC_READ);
chip->read_buf(mtd, p, eccsize);
chip->write_buf(mtd, chip->oob_poi + mecc_pos[0] + ((chip->ecc.steps - eccsteps) * eccbytes), eccbytes);
chip->ecc.calculate(mtd, 0, 0);
stat = chip->ecc.correct(mtd, p, 0, 0);
if (stat == -1)
mtd->ecc_stats.failed++;
col = eccsize * (chip->ecc.steps + 1 - eccsteps);
}
return 0;
}
/*
* Hardware specific page write function for MLC.
* Written by jsgood
*/
void s3c_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
const uint8_t *buf)
{
int i, eccsize = chip->ecc.size;
int eccbytes = chip->ecc.bytes;
int eccsteps = chip->ecc.steps;
const uint8_t *p = buf;
uint8_t *ecc_calc = chip->buffers->ecccalc;
uint32_t *mecc_pos = chip->ecc.layout->eccpos;
/* Step1: write main data and encode mecc */
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
chip->write_buf(mtd, p, eccsize);
chip->ecc.calculate(mtd, p, &ecc_calc[i]);
}
/* Step2: save encoded mecc */
for (i = 0; i < chip->ecc.total; i++)
chip->oob_poi[mecc_pos[i]] = ecc_calc[i];
chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
}
#endif
/* s3c_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 s3c_nand_probe(struct platform_device *pdev)
{
struct s3c_nand_mtd_info *plat_info = pdev->dev.platform_data;
struct mtd_partition *partition_info = (struct mtd_partition *)plat_info->partition;
struct nand_chip *nand;
struct resource *res;
int err = 0;
int ret = 0;
int i, size;
u_char tmp;
struct nand_flash_dev *type = NULL;
/* get the clock source and enable it */
s3c_nand.clk = clk_get(&pdev->dev, "nand");
if (IS_ERR(s3c_nand.clk)) {
dev_err(&pdev->dev, "failed to get clock");
err = -ENOENT;
goto exit_error;
}
clk_enable(s3c_nand.clk);
/* allocate and map the resource */
/* currently we assume we have the one resource */
res = pdev->resource;
size = res->end - res->start + 1;
s3c_nand.area = request_mem_region(res->start, size, pdev->name);
if (s3c_nand.area == NULL) {
dev_err(&pdev->dev, "cannot reserve register region\n");
err = -ENOENT;
goto exit_error;
}
s3c_nand.device = &pdev->dev;
s3c_nand.regs = ioremap(res->start, size);
//info->cpu_type = cpu_type;
if (s3c_nand.regs == NULL) {
dev_err(&pdev->dev, "cannot reserve register region\n");
err = -EIO;
goto exit_error;
}
/* allocate memory for MTD device structure and private data */
s3c_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL);
if (!s3c_mtd) {
printk("Unable to allocate NAND MTD dev structure.\n");
return -ENOMEM;
}
/* Get pointer to private data */
nand = (struct nand_chip *) (&s3c_mtd[1]);
/* Initialize structures */
memset((char *) s3c_mtd, 0, sizeof(struct mtd_info));
memset((char *) nand, 0, sizeof(struct nand_chip));
/* Link the private data with the MTD structure */
s3c_mtd->priv = nand;
for (i = 0; i < plat_info->chip_nr; i++) {
nand->IO_ADDR_R = (char *)(s3c_nand.regs+S3C2440_NFDATA);
nand->IO_ADDR_W = (char *)(s3c_nand.regs+S3C2440_NFDATA);
nand->cmd_ctrl = s3c_nand_hwcontrol;
nand->dev_ready = s3c_nand_device_ready;
nand->scan_bbt = s3c_nand_scan_bbt;
nand->options = 0;
#if defined(CONFIG_MTD_NAND_S3C_CACHEDPROG)
nand->options |= NAND_CACHEPRG;
#endif
#if defined(CONFIG_MTD_NAND_S3C_FLASH_BBT)
nand->options |= NAND_USE_FLASH_BBT;
#else
nand->options |= NAND_SKIP_BBTSCAN;
#endif
#if defined(CONFIG_MTD_NAND_S3C_HWECC)
nand->ecc.mode = NAND_ECC_HW;
nand->ecc.hwctl = s3c_nand_enable_hwecc;
nand->ecc.calculate = s3c_nand_calculate_ecc;
nand->ecc.correct = s3c_nand_correct_data;
s3c_nand_hwcontrol(0, NAND_CMD_READID, NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
s3c_nand_hwcontrol(0, 0x00, NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE);
s3c_nand_hwcontrol(0, 0x00, NAND_NCE | NAND_ALE);
s3c_nand_hwcontrol(0, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
s3c_nand_device_ready(0);
tmp = readb(nand->IO_ADDR_R); /* Maf. ID */
tmp = readb(nand->IO_ADDR_R); /* Device ID */
for (i = 0; nand_flash_ids[i].name != NULL; i++) {
if (tmp == nand_flash_ids[i].id) {
type = &nand_flash_ids[i];
break;
}
}
if (!type) {
printk("Unknown NAND Device.\n");
goto exit_error;
}
nand->cellinfo = readb(nand->IO_ADDR_R); /* 3rd byte */
tmp = readb(nand->IO_ADDR_R); /* 4th byte */
if (!type->pagesize) {
if (((nand->cellinfo >> 2) & 0x3) == 0) {
nand_type = S3C_NAND_TYPE_SLC;
if ((1024 << (tmp & 0x3)) > 512) {
nand->ecc.size = 2048;
nand->ecc.bytes = 4;
nand->ecc.layout = &s3c_nand_oob_64;
} else {
nand->ecc.size = 512;
nand->ecc.bytes = 3;
nand->ecc.layout = &s3c_nand_oob_16;
}
} else {
nand_type = S3C_NAND_TYPE_MLC;
nand->options |= NAND_NO_SUBPAGE_WRITE; /* NOP = 1 if MLC */
nand->ecc.read_page = s3c_nand_read_page;
nand->ecc.write_page = s3c_nand_write_page;
nand->ecc.size = 512;
nand->ecc.bytes = 8; /* really 7 bytes */
nand->ecc.layout = &s3c_nand_oob_mlc_64;
}
} else {
/*Qisa Qube for EVT2 2k page*/
#ifdef CONFIG_QISDA_E600_EVT0
nand_type = S3C_NAND_TYPE_SLC;
nand->ecc.size = 512;
nand->cellinfo = 0;
nand->ecc.bytes = 3;
nand->ecc.layout = &s3c_nand_oob_16;
#else
nand_type = S3C_NAND_TYPE_SLC;
nand->ecc.size = 2048;
nand->cellinfo = 0;
nand->ecc.bytes = 4;
nand->ecc.layout = &s3c_nand_oob_64;
#endif
/*Qisa Qube for EVT2 2k page*/
}
printk("S3C NAND Driver is using hardware ECC.\n");
#else
nand->ecc.mode = NAND_ECC_SOFT;
printk("S3C NAND Driver is using software ECC.\n");
#endif
if (nand_scan(s3c_mtd, 1)) {
ret = -ENXIO;
goto exit_error;
}
/* Register the partitions */
add_mtd_partitions(s3c_mtd, partition_info, plat_info->mtd_part_nr);
}
pr_debug("initialized ok\n");
return 0;
exit_error:
kfree(s3c_mtd);
return ret;
}
/* PM Support */
#if defined(CONFIG_PM)
static int s3c_nand_suspend(struct platform_device *dev, pm_message_t pm)
{
return 0;
}
static int s3c_nand_resume(struct platform_device *dev)
{
return 0;
}
#else
#define s3c_nand_suspend NULL
#define s3c_nand_resume NULL
#endif
/* device management functions */
static int s3c_nand_remove(struct platform_device *dev)
{
platform_set_drvdata(dev, NULL);
return 0;
}
static struct platform_driver s3c_nand_driver = {
.probe = s3c_nand_probe,
.remove = s3c_nand_remove,
.suspend = s3c_nand_suspend,
.resume = s3c_nand_resume,
.driver = {
.name = "s3c-nand",
.owner = THIS_MODULE,
},
};
static int __init s3c_nand_init(void)
{
printk("S3C NAND Driver, (c) 2007 Samsung Electronics\n");
return platform_driver_register(&s3c_nand_driver);
}
static void __exit s3c_nand_exit(void)
{
platform_driver_unregister(&s3c_nand_driver);
}
module_init(s3c_nand_init);
module_exit(s3c_nand_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jinsung Yang <jsgood.yang@samsung.com>");
MODULE_DESCRIPTION("S3C MTD NAND driver");