diff options
Diffstat (limited to 'drivers/mtd/nand/s3c2410.c')
-rw-r--r-- | drivers/mtd/nand/s3c2410.c | 252 |
1 files changed, 135 insertions, 117 deletions
diff --git a/drivers/mtd/nand/s3c2410.c b/drivers/mtd/nand/s3c2410.c index 5b55599739f3..2c262fe03d8a 100644 --- a/drivers/mtd/nand/s3c2410.c +++ b/drivers/mtd/nand/s3c2410.c @@ -18,8 +18,9 @@ * 20-Jun-2005 BJD Updated s3c2440 support, fixed timing bug * 08-Jul-2005 BJD Fix OOPS when no platform data supplied * 20-Oct-2005 BJD Fix timing calculation bug + * 14-Jan-2006 BJD Allow clock to be stopped when idle * - * $Id: s3c2410.c,v 1.20 2005/11/07 11:14:31 gleixner Exp $ + * $Id: s3c2410.c,v 1.23 2006/04/01 18:06:29 bjd 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 @@ -36,9 +37,6 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <config/mtd/nand/s3c2410/hwecc.h> -#include <config/mtd/nand/s3c2410/debug.h> - #ifdef CONFIG_MTD_NAND_S3C2410_DEBUG #define DEBUG #endif @@ -73,14 +71,20 @@ static int hardware_ecc = 1; static int hardware_ecc = 0; #endif +#ifdef CONFIG_MTD_NAND_S3C2410_CLKSTOP +static int clock_stop = 1; +#else +static const int clock_stop = 0; +#endif + + /* new oob placement block for use with hardware ecc generation */ -static struct nand_oobinfo nand_hw_eccoob = { - .useecc = MTD_NANDECC_AUTOPLACE, - .eccbytes = 3, - .eccpos = {0, 1, 2 }, - .oobfree = { {8, 8} } +static struct nand_ecclayout nand_hw_eccoob = { + .eccbytes = 3, + .eccpos = {0, 1, 2}, + .oobfree = {{8, 8}} }; /* controller and mtd information */ @@ -135,6 +139,11 @@ static struct s3c2410_platform_nand *to_nand_plat(struct platform_device *dev) return dev->dev.platform_data; } +static inline int allow_clk_stop(struct s3c2410_nand_info *info) +{ + return clock_stop; +} + /* timing calculations */ #define NS_IN_KHZ 1000000 @@ -149,8 +158,7 @@ static int s3c2410_nand_calc_rate(int wanted, unsigned long clk, int max) pr_debug("result %d from %ld, %d\n", result, clk, wanted); if (result > max) { - printk("%d ns is too big for current clock rate %ld\n", - wanted, clk); + printk("%d ns is too big for current clock rate %ld\n", wanted, clk); return -1; } @@ -164,8 +172,7 @@ static int s3c2410_nand_calc_rate(int wanted, unsigned long clk, int max) /* controller setup */ -static int s3c2410_nand_inithw(struct s3c2410_nand_info *info, - struct platform_device *pdev) +static int s3c2410_nand_inithw(struct s3c2410_nand_info *info, struct platform_device *pdev) { struct s3c2410_platform_nand *plat = to_nand_plat(pdev); unsigned long clkrate = clk_get_rate(info->clk); @@ -177,7 +184,7 @@ static int s3c2410_nand_inithw(struct s3c2410_nand_info *info, clkrate /= 1000; /* turn clock into kHz for ease of use */ if (plat != NULL) { - tacls = s3c2410_nand_calc_rate(plat->tacls, clkrate, 4); + tacls = s3c2410_nand_calc_rate(plat->tacls, clkrate, 4); twrph0 = s3c2410_nand_calc_rate(plat->twrph0, clkrate, 8); twrph1 = s3c2410_nand_calc_rate(plat->twrph1, clkrate, 8); } else { @@ -193,19 +200,22 @@ static int s3c2410_nand_inithw(struct s3c2410_nand_info *info, } printk(KERN_INFO PFX "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n", - tacls, to_ns(tacls, clkrate), - twrph0, to_ns(twrph0, clkrate), - twrph1, to_ns(twrph1, clkrate)); + tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate), twrph1, to_ns(twrph1, clkrate)); if (!info->is_s3c2440) { - cfg = S3C2410_NFCONF_EN; - cfg |= S3C2410_NFCONF_TACLS(tacls-1); - cfg |= S3C2410_NFCONF_TWRPH0(twrph0-1); - cfg |= S3C2410_NFCONF_TWRPH1(twrph1-1); + cfg = S3C2410_NFCONF_EN; + cfg |= S3C2410_NFCONF_TACLS(tacls - 1); + cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1); + cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1); } else { - cfg = S3C2440_NFCONF_TACLS(tacls-1); - cfg |= S3C2440_NFCONF_TWRPH0(twrph0-1); - cfg |= S3C2440_NFCONF_TWRPH1(twrph1-1); + cfg = S3C2440_NFCONF_TACLS(tacls - 1); + cfg |= S3C2440_NFCONF_TWRPH0(twrph0 - 1); + cfg |= S3C2440_NFCONF_TWRPH1(twrph1 - 1); + + /* enable the controller and de-assert nFCE */ + + writel(S3C2440_NFCONT_ENABLE | S3C2440_NFCONT_ENABLE, + info->regs + S3C2440_NFCONT); } pr_debug(PFX "NF_CONF is 0x%lx\n", cfg); @@ -229,7 +239,10 @@ static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip) info = nmtd->info; bit = (info->is_s3c2440) ? S3C2440_NFCONT_nFCE : S3C2410_NFCONF_nFCE; - reg = info->regs+((info->is_s3c2440) ? S3C2440_NFCONT:S3C2410_NFCONF); + reg = info->regs + ((info->is_s3c2440) ? S3C2440_NFCONT : S3C2410_NFCONF); + + if (chip != -1 && allow_clk_stop(info)) + clk_enable(info->clk); cur = readl(reg); @@ -243,77 +256,51 @@ static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip) if (info->platform != NULL) { if (info->platform->select_chip != NULL) - (info->platform->select_chip)(nmtd->set, chip); + (info->platform->select_chip) (nmtd->set, chip); } cur &= ~bit; } writel(cur, reg); + + if (chip == -1 && allow_clk_stop(info)) + clk_disable(info->clk); } -/* command and control functions - * - * Note, these all use tglx's method of changing the IO_ADDR_W field - * to make the code simpler, and use the nand layer's code to issue the - * command and address sequences via the proper IO ports. +/* s3c2410_nand_hwcontrol * + * Issue command and address cycles to the chip */ -static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd) +static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd, + unsigned int ctrl) { struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - struct nand_chip *chip = mtd->priv; - - switch (cmd) { - case NAND_CTL_SETNCE: - case NAND_CTL_CLRNCE: - printk(KERN_ERR "%s: called for NCE\n", __FUNCTION__); - break; - - case NAND_CTL_SETCLE: - chip->IO_ADDR_W = info->regs + S3C2410_NFCMD; - break; - - case NAND_CTL_SETALE: - chip->IO_ADDR_W = info->regs + S3C2410_NFADDR; - break; - - /* NAND_CTL_CLRCLE: */ - /* NAND_CTL_CLRALE: */ - default: - chip->IO_ADDR_W = info->regs + S3C2410_NFDATA; - break; - } + + if (cmd == NAND_CMD_NONE) + return; + + if (ctrl & NAND_CLE) + writeb(cmd, info->regs + S3C2410_NFCMD); + else + writeb(cmd, info->regs + S3C2410_NFADDR); } /* command and control functions */ -static void s3c2440_nand_hwcontrol(struct mtd_info *mtd, int cmd) +static void s3c2440_nand_hwcontrol(struct mtd_info *mtd, int cmd, + unsigned int ctrl) { struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - struct nand_chip *chip = mtd->priv; - - switch (cmd) { - case NAND_CTL_SETNCE: - case NAND_CTL_CLRNCE: - printk(KERN_ERR "%s: called for NCE\n", __FUNCTION__); - break; - - case NAND_CTL_SETCLE: - chip->IO_ADDR_W = info->regs + S3C2440_NFCMD; - break; - - case NAND_CTL_SETALE: - chip->IO_ADDR_W = info->regs + S3C2440_NFADDR; - break; - - /* NAND_CTL_CLRCLE: */ - /* NAND_CTL_CLRALE: */ - default: - chip->IO_ADDR_W = info->regs + S3C2440_NFDATA; - break; - } + + if (cmd == NAND_CMD_NONE) + return; + + if (ctrl & NAND_CLE) + writeb(cmd, info->regs + S3C2440_NFCMD); + else + writeb(cmd, info->regs + S3C2440_NFADDR); } /* s3c2410_nand_devready() @@ -330,22 +317,16 @@ static int s3c2410_nand_devready(struct mtd_info *mtd) return readb(info->regs + S3C2410_NFSTAT) & S3C2410_NFSTAT_BUSY; } - /* ECC handling functions */ -static int s3c2410_nand_correct_data(struct mtd_info *mtd, u_char *dat, - u_char *read_ecc, u_char *calc_ecc) +static int s3c2410_nand_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc) { - pr_debug("s3c2410_nand_correct_data(%p,%p,%p,%p)\n", - mtd, dat, read_ecc, calc_ecc); + pr_debug("s3c2410_nand_correct_data(%p,%p,%p,%p)\n", mtd, dat, read_ecc, calc_ecc); pr_debug("eccs: read %02x,%02x,%02x vs calc %02x,%02x,%02x\n", - read_ecc[0], read_ecc[1], read_ecc[2], - calc_ecc[0], calc_ecc[1], calc_ecc[2]); + read_ecc[0], read_ecc[1], read_ecc[2], calc_ecc[0], calc_ecc[1], calc_ecc[2]); - if (read_ecc[0] == calc_ecc[0] && - read_ecc[1] == calc_ecc[1] && - read_ecc[2] == calc_ecc[2]) + if (read_ecc[0] == calc_ecc[0] && read_ecc[1] == calc_ecc[1] && read_ecc[2] == calc_ecc[2]) return 0; /* we curently have no method for correcting the error */ @@ -378,8 +359,7 @@ static void s3c2440_nand_enable_hwecc(struct mtd_info *mtd, int mode) writel(ctrl | S3C2440_NFCONT_INITECC, info->regs + S3C2440_NFCONT); } -static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, - const u_char *dat, u_char *ecc_code) +static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code) { struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); @@ -387,15 +367,12 @@ static int s3c2410_nand_calculate_ecc(struct mtd_info *mtd, ecc_code[1] = readb(info->regs + S3C2410_NFECC + 1); ecc_code[2] = readb(info->regs + S3C2410_NFECC + 2); - pr_debug("calculate_ecc: returning ecc %02x,%02x,%02x\n", - ecc_code[0], ecc_code[1], ecc_code[2]); + pr_debug("calculate_ecc: returning ecc %02x,%02x,%02x\n", ecc_code[0], ecc_code[1], ecc_code[2]); return 0; } - -static int s3c2440_nand_calculate_ecc(struct mtd_info *mtd, - const u_char *dat, u_char *ecc_code) +static int s3c2440_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code) { struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); unsigned long ecc = readl(info->regs + S3C2440_NFMECC0); @@ -404,13 +381,11 @@ static int s3c2440_nand_calculate_ecc(struct mtd_info *mtd, ecc_code[1] = ecc >> 8; ecc_code[2] = ecc >> 16; - pr_debug("calculate_ecc: returning ecc %02x,%02x,%02x\n", - ecc_code[0], ecc_code[1], ecc_code[2]); + pr_debug("calculate_ecc: returning ecc %02x,%02x,%02x\n", ecc_code[0], ecc_code[1], ecc_code[2]); return 0; } - /* over-ride the standard functions for a little more speed. We can * use read/write block to move the data buffers to/from the controller */ @@ -421,8 +396,7 @@ static void s3c2410_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len) readsb(this->IO_ADDR_R, buf, len); } -static void s3c2410_nand_write_buf(struct mtd_info *mtd, - const u_char *buf, int len) +static void s3c2410_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len) { struct nand_chip *this = mtd->priv; writesb(this->IO_ADDR_W, buf, len); @@ -459,7 +433,8 @@ static int s3c2410_nand_remove(struct platform_device *pdev) /* free the common resources */ if (info->clk != NULL && !IS_ERR(info->clk)) { - clk_disable(info->clk); + if (!allow_clk_stop(info)) + clk_disable(info->clk); clk_put(info->clk); } @@ -488,9 +463,7 @@ static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info, return add_mtd_device(&mtd->mtd); if (set->nr_partitions > 0 && set->partitions != NULL) { - return add_mtd_partitions(&mtd->mtd, - set->partitions, - set->nr_partitions); + return add_mtd_partitions(&mtd->mtd, set->partitions, set->nr_partitions); } return add_mtd_device(&mtd->mtd); @@ -517,7 +490,7 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, chip->IO_ADDR_R = info->regs + S3C2410_NFDATA; chip->IO_ADDR_W = info->regs + S3C2410_NFDATA; - chip->hwcontrol = s3c2410_nand_hwcontrol; + chip->cmd_ctrl = s3c2410_nand_hwcontrol; chip->dev_ready = s3c2410_nand_devready; chip->write_buf = s3c2410_nand_write_buf; chip->read_buf = s3c2410_nand_read_buf; @@ -530,26 +503,29 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, if (info->is_s3c2440) { chip->IO_ADDR_R = info->regs + S3C2440_NFDATA; chip->IO_ADDR_W = info->regs + S3C2440_NFDATA; - chip->hwcontrol = s3c2440_nand_hwcontrol; + chip->cmd_ctrl = s3c2440_nand_hwcontrol; } nmtd->info = info; nmtd->mtd.priv = chip; + nmtd->mtd.owner = THIS_MODULE; nmtd->set = set; if (hardware_ecc) { - chip->correct_data = s3c2410_nand_correct_data; - chip->enable_hwecc = s3c2410_nand_enable_hwecc; - chip->calculate_ecc = s3c2410_nand_calculate_ecc; - chip->eccmode = NAND_ECC_HW3_512; - chip->autooob = &nand_hw_eccoob; + chip->ecc.correct = s3c2410_nand_correct_data; + chip->ecc.hwctl = s3c2410_nand_enable_hwecc; + chip->ecc.calculate = s3c2410_nand_calculate_ecc; + chip->ecc.mode = NAND_ECC_HW; + chip->ecc.size = 512; + chip->ecc.bytes = 3; + chip->ecc.layout = &nand_hw_eccoob; if (info->is_s3c2440) { - chip->enable_hwecc = s3c2440_nand_enable_hwecc; - chip->calculate_ecc = s3c2440_nand_calculate_ecc; + chip->ecc.hwctl = s3c2440_nand_enable_hwecc; + chip->ecc.calculate = s3c2440_nand_calculate_ecc; } } else { - chip->eccmode = NAND_ECC_SOFT; + chip->ecc.mode = NAND_ECC_SOFT; } } @@ -654,13 +630,11 @@ static int s3c24xx_nand_probe(struct platform_device *pdev, int is_s3c2440) nmtd = info->mtds; for (setno = 0; setno < nr_sets; setno++, nmtd++) { - pr_debug("initialising set %d (%p, info %p)\n", - setno, nmtd, info); + pr_debug("initialising set %d (%p, info %p)\n", setno, nmtd, info); s3c2410_nand_init_chip(info, nmtd, sets); - nmtd->scan_res = nand_scan(&nmtd->mtd, - (sets) ? sets->nr_chips : 1); + nmtd->scan_res = nand_scan(&nmtd->mtd, (sets) ? sets->nr_chips : 1); if (nmtd->scan_res == 0) { s3c2410_nand_add_partition(info, nmtd, sets); @@ -670,6 +644,11 @@ static int s3c24xx_nand_probe(struct platform_device *pdev, int is_s3c2440) sets++; } + if (allow_clk_stop(info)) { + dev_info(&pdev->dev, "clock idle support enabled\n"); + clk_disable(info->clk); + } + pr_debug("initialised ok\n"); return 0; @@ -681,6 +660,41 @@ static int s3c24xx_nand_probe(struct platform_device *pdev, int is_s3c2440) return err; } +/* PM Support */ +#ifdef CONFIG_PM + +static int s3c24xx_nand_suspend(struct platform_device *dev, pm_message_t pm) +{ + struct s3c2410_nand_info *info = platform_get_drvdata(dev); + + if (info) { + if (!allow_clk_stop(info)) + clk_disable(info->clk); + } + + return 0; +} + +static int s3c24xx_nand_resume(struct platform_device *dev) +{ + struct s3c2410_nand_info *info = platform_get_drvdata(dev); + + if (info) { + clk_enable(info->clk); + s3c2410_nand_inithw(info, dev); + + if (allow_clk_stop(info)) + clk_disable(info->clk); + } + + return 0; +} + +#else +#define s3c24xx_nand_suspend NULL +#define s3c24xx_nand_resume NULL +#endif + /* driver device registration */ static int s3c2410_nand_probe(struct platform_device *dev) @@ -696,6 +710,8 @@ static int s3c2440_nand_probe(struct platform_device *dev) static struct platform_driver s3c2410_nand_driver = { .probe = s3c2410_nand_probe, .remove = s3c2410_nand_remove, + .suspend = s3c24xx_nand_suspend, + .resume = s3c24xx_nand_resume, .driver = { .name = "s3c2410-nand", .owner = THIS_MODULE, @@ -705,6 +721,8 @@ static struct platform_driver s3c2410_nand_driver = { static struct platform_driver s3c2440_nand_driver = { .probe = s3c2440_nand_probe, .remove = s3c2410_nand_remove, + .suspend = s3c24xx_nand_suspend, + .resume = s3c24xx_nand_resume, .driver = { .name = "s3c2440-nand", .owner = THIS_MODULE, |