/* 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 * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 "); MODULE_DESCRIPTION("S3C MTD NAND driver");