From: Adrian Hunter Date: Thu, 24 Jan 2008 15:00:12 +0000 (+0200) Subject: ARM: OMAP: updated N800/N810 support for OneNAND X-Git-Tag: v2.6.24-omap1~26 X-Git-Url: http://www.pilppa.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=3ae4b7e31cd51af2e2184c640c9f50f574acd449;p=linux-2.6-omap-h63xx.git ARM: OMAP: updated N800/N810 support for OneNAND Updates omap2 support for OneNAND on N800/N810. Signed-off-by: Adrian Hunter Signed-off-by: Tony Lindgren --- diff --git a/arch/arm/mach-omap2/board-n800-flash.c b/arch/arm/mach-omap2/board-n800-flash.c index 528ee52a377..f7403b9333b 100644 --- a/arch/arm/mach-omap2/board-n800-flash.c +++ b/arch/arm/mach-omap2/board-n800-flash.c @@ -21,7 +21,7 @@ static struct mtd_partition n800_partitions[8]; -static int n800_onenand_setup(void __iomem *); +static int n800_onenand_setup(void __iomem *, int freq); static struct omap_onenand_platform_data n800_onenand_data = { .cs = 0, @@ -49,56 +49,103 @@ static void omap2_onenand_writew(unsigned short value, void __iomem *addr) writew(value, addr); } -static int omap2_onenand_set_sync_mode(int cs, void __iomem *onenand_base) +static int omap2_onenand_set_sync_mode(int cs, void __iomem *onenand_base, + int freq) { - const int min_gpmc_clk_period = 18; struct gpmc_timings t; + int min_gpmc_clk_period, t_ces, t_avds, t_avdh, t_avdp, t_wpl, t_wea; int tick_ns, div, fclk_offset_ns, fclk_offset, gpmc_clk_ns, latency; + int err; u32 reg; - tick_ns = gpmc_round_ns_to_ticks(1); +again: + switch (freq) { + case 83: + min_gpmc_clk_period = 12; /* 83 MHz */ + t_ces = 5; + t_avds = 5; + t_avdh = 6; + t_avdp = 12; + t_wpl = 40; + t_wea = 15; + break; + case 66: + min_gpmc_clk_period = 15; /* 66 MHz */ + t_ces = 6; + t_avds = 5; + t_avdh = 6; + t_avdp = 12; + t_wpl = 40; + t_wea = 15; + break; + default: + min_gpmc_clk_period = 18; /* 54 MHz */ + t_ces = 7; + t_avds = 7; + t_avdh = 7; + t_avdp = 12; + t_wpl = 40; + t_wea = 15; + break; + } + + tick_ns = gpmc_ticks_to_ns(1); div = gpmc_cs_calc_divider(cs, min_gpmc_clk_period); - gpmc_clk_ns = div * tick_ns; - if (gpmc_clk_ns >= 24) + gpmc_clk_ns = gpmc_ticks_to_ns(div); + if (gpmc_clk_ns >= 25) /* 40 MHz*/ latency = 3; else latency = 4; - /* Configure OneNAND for sync read */ - reg = omap2_onenand_readw(onenand_base + ONENAND_REG_SYS_CFG1); - reg &= ~((0x7 << ONENAND_SYS_CFG1_BRL_SHIFT) | (0x7 << 9)); - reg |= (latency << ONENAND_SYS_CFG1_BRL_SHIFT) | - ONENAND_SYS_CFG1_SYNC_READ | - ONENAND_SYS_CFG1_BL_16; - omap2_onenand_writew(reg, onenand_base + ONENAND_REG_SYS_CFG1); + if (div == 1) { + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG2); + reg |= (1 << 7); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG2, reg); + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG3); + reg |= (1 << 7); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG3, reg); + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG4); + reg |= (1 << 7); + reg |= (1 << 23); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG4, reg); + } else { + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG2); + reg &= ~(1 << 7); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG2, reg); + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG3); + reg &= ~(1 << 7); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG3, reg); + reg = gpmc_cs_read_reg(cs, GPMC_CS_CONFIG4); + reg &= ~(1 << 7); + reg &= ~(1 << 23); + gpmc_cs_write_reg(cs, GPMC_CS_CONFIG4, reg); + } - /* FIXME: Get timings from platform data */ /* Set syncronous read timings */ memset(&t, 0, sizeof(t)); t.sync_clk = min_gpmc_clk_period; t.cs_on = 0; - t.adv_on = gpmc_round_ns_to_ticks(7); - fclk_offset_ns = t.adv_on + gpmc_round_ns_to_ticks(7); - fclk_offset = fclk_offset_ns / gpmc_round_ns_to_ticks(1); + t.adv_on = 0; + fclk_offset_ns = gpmc_round_ns_to_ticks(max_t(int, t_ces, t_avds)); + fclk_offset = gpmc_ns_to_ticks(fclk_offset_ns); t.page_burst_access = gpmc_clk_ns; /* Read */ - t.adv_rd_off = fclk_offset_ns + gpmc_round_ns_to_ticks(7); + t.adv_rd_off = gpmc_ticks_to_ns(fclk_offset + gpmc_ns_to_ticks(t_avdh)); t.oe_on = t.adv_rd_off; - t.access = fclk_offset_ns + (latency + 1) * gpmc_clk_ns; + t.access = gpmc_ticks_to_ns(fclk_offset + (latency + 1) * div); t.oe_off = t.access + gpmc_round_ns_to_ticks(1); t.cs_rd_off = t.oe_off; - t.rd_cycle = t.cs_rd_off + gpmc_round_ns_to_ticks(17); + t.rd_cycle = gpmc_ticks_to_ns(fclk_offset + (latency + 1) * div + div); /* Write */ - t.adv_wr_off = t.adv_on + gpmc_round_ns_to_ticks(12); - t.we_on = t.adv_wr_off + gpmc_round_ns_to_ticks(1); - t.we_off = t.we_on + gpmc_round_ns_to_ticks(40); + t.adv_wr_off = t.adv_on + gpmc_round_ns_to_ticks(t_avdp); + t.we_on = t.adv_wr_off + gpmc_round_ns_to_ticks(t_avdh); + t.we_off = t.we_on + gpmc_round_ns_to_ticks(t_wpl); t.cs_wr_off = t.we_off + gpmc_round_ns_to_ticks(1); - t.wr_cycle = t.cs_wr_off + gpmc_round_ns_to_ticks(1); + t.wr_cycle = t.we_off + gpmc_round_ns_to_ticks(t_wea); /* Configure GPMC for synchronous read */ - fclk_offset %= div; gpmc_cs_write_reg(cs, GPMC_CS_CONFIG1, GPMC_CONFIG1_WRAPBURST_SUPP | GPMC_CONFIG1_READMULTIPLE_SUPP | @@ -111,16 +158,49 @@ static int omap2_onenand_set_sync_mode(int cs, void __iomem *onenand_base) GPMC_CONFIG1_DEVICETYPE_NOR | GPMC_CONFIG1_MUXADDDATA); - return gpmc_cs_set_timings(cs, &t); + err = gpmc_cs_set_timings(cs, &t); + if (err) + return err; + + if (!freq) { + /* Very first call freq is not known */ + reg = omap2_onenand_readw(onenand_base + ONENAND_REG_VERSION_ID); + switch ((reg >> 4) & 0xf) { + case 0: + freq = 40; + break; + case 1: + freq = 54; + break; + case 2: + freq = 66; + break; + case 3: + freq = 83; + break; + } + if (freq && freq != 54) + goto again; + } + + /* Configure OneNAND for sync read */ + reg = omap2_onenand_readw(onenand_base + ONENAND_REG_SYS_CFG1); + reg &= ~((0x7 << ONENAND_SYS_CFG1_BRL_SHIFT) | (0x7 << 9)); + reg |= (latency << ONENAND_SYS_CFG1_BRL_SHIFT) | + ONENAND_SYS_CFG1_SYNC_READ | + ONENAND_SYS_CFG1_BL_16; + omap2_onenand_writew(reg, onenand_base + ONENAND_REG_SYS_CFG1); + + return 0; } -static int n800_onenand_setup(void __iomem *onenand_base) +static int n800_onenand_setup(void __iomem *onenand_base, int freq) { struct omap_onenand_platform_data *datap = &n800_onenand_data; struct device *dev = &n800_onenand_device.dev; /* Set sync timings in GPMC */ - if (omap2_onenand_set_sync_mode(datap->cs, onenand_base) < 0) { + if (omap2_onenand_set_sync_mode(datap->cs, onenand_base, freq) < 0) { dev_err(dev, "Unable to set synchronous mode\n"); return -EINVAL; } diff --git a/arch/arm/mach-omap2/gpmc.c b/arch/arm/mach-omap2/gpmc.c index 1b54820d621..2b0e07dc5f9 100644 --- a/arch/arm/mach-omap2/gpmc.c +++ b/arch/arm/mach-omap2/gpmc.c @@ -117,6 +117,11 @@ unsigned int gpmc_ns_to_ticks(unsigned int time_ns) return (time_ns * 1000 + tick_ps - 1) / tick_ps; } +unsigned int gpmc_ticks_to_ns(unsigned int ticks) +{ + return ticks * gpmc_get_fclk_period() / 1000; +} + unsigned int gpmc_round_ns_to_ticks(unsigned int time_ns) { unsigned long ticks = gpmc_ns_to_ticks(time_ns); diff --git a/drivers/mtd/onenand/omap2.c b/drivers/mtd/onenand/omap2.c index 3255135fe3e..fe831a84e0c 100644 --- a/drivers/mtd/onenand/omap2.c +++ b/drivers/mtd/onenand/omap2.c @@ -61,6 +61,8 @@ struct omap2_onenand { struct completion irq_done; struct completion dma_done; int dma_channel; + int freq; + int (*setup)(void __iomem *base, int freq); }; static unsigned short omap2_onenand_readw(void __iomem *addr) @@ -98,7 +100,23 @@ static int omap2_onenand_wait(struct mtd_info *mtd, int state) u32 syscfg; if (state == FL_RESETING) { - udelay(1); + int i; + + for (i = 0; i < 20; i++) { + udelay(1); + interrupt = omap2_onenand_readw(info->onenand.base + ONENAND_REG_INTERRUPT); + if (interrupt & ONENAND_INT_MASTER) + break; + } + ctrl = omap2_onenand_readw(info->onenand.base + ONENAND_REG_CTRL_STATUS); + if (ctrl & ONENAND_CTRL_ERROR) { + printk(KERN_ERR "onenand_wait: reset error! ctrl 0x%04x intr 0x%04x\n", ctrl, interrupt); + return -EIO; + } + if (!(interrupt & ONENAND_INT_RESET)) { + printk(KERN_ERR "onenand_wait: reset timeout! ctrl 0x%04x intr 0x%04x\n", ctrl, interrupt); + return -EIO; + } return 0; } @@ -164,7 +182,7 @@ retry: printk(KERN_ERR "onenand_wait: controller error = 0x%04x\n", ctrl); if (ctrl & ONENAND_CTRL_LOCK) printk(KERN_ERR "onenand_erase: Device is write protected!!!\n"); - return ctrl; + return -EIO; } if (ctrl & 0xFE9F) @@ -173,11 +191,12 @@ retry: if (interrupt & ONENAND_INT_READ) { int ecc = omap2_onenand_readw(info->onenand.base + ONENAND_REG_ECC_STATUS); if (ecc) { - printk(KERN_ERR "onenand_wait: ECC error = 0x%04x\n", ecc); if (ecc & ONENAND_ECC_2BIT_ALL) { + printk(KERN_ERR "onenand_wait: ECC error = 0x%04x\n", ecc); mtd->ecc_stats.failed++; - return ecc; + return -EBADMSG; } else if (ecc & ONENAND_ECC_1BIT_ALL) + printk(KERN_NOTICE "onenand_wait: correctable ECC error = 0x%04x\n", ecc); mtd->ecc_stats.corrected++; } } else if (state == FL_READING) { @@ -289,6 +308,29 @@ static int omap2_onenand_write_bufferram(struct mtd_info *mtd, int area, return 0; } +static struct platform_driver omap2_onenand_driver; + +static int __adjust_timing(struct device *dev, void *data) +{ + int ret = 0; + struct omap2_onenand *info; + + info = dev_get_drvdata(dev); + + BUG_ON(info->setup == NULL); + + /* DMA is not in use so this is all that is needed */ + ret = info->setup(info->onenand.base, info->freq); + + return ret; +} + +int omap2_onenand_rephase(void) +{ + return driver_for_each_device(&omap2_onenand_driver.driver, NULL, + NULL, __adjust_timing); +} + static void __devexit omap2_onenand_shutdown(struct platform_device *pdev) { struct omap2_onenand *info = dev_get_drvdata(&pdev->dev); @@ -346,12 +388,13 @@ static int __devinit omap2_onenand_probe(struct platform_device *pdev) } if (pdata->onenand_setup != NULL) { - r = pdata->onenand_setup(info->onenand.base); + r = pdata->onenand_setup(info->onenand.base, info->freq); if (r < 0) { dev_err(&pdev->dev, "Onenand platform setup failed: %d\n", r); goto err_iounmap; } - } + info->setup = pdata->onenand_setup; + } if (info->gpio_irq) { if ((r = omap_request_gpio(info->gpio_irq)) < 0) { @@ -401,6 +444,21 @@ static int __devinit omap2_onenand_probe(struct platform_device *pdev) if ((r = onenand_scan(&info->mtd, 1)) < 0) goto err_release_dma; + switch ((info->onenand.version_id >> 4) & 0xf) { + case 0: + info->freq = 40; + break; + case 1: + info->freq = 54; + break; + case 2: + info->freq = 66; + break; + case 3: + info->freq = 83; + break; + } + #ifdef CONFIG_MTD_PARTITIONS if (pdata->parts != NULL) r = add_mtd_partitions(&info->mtd, pdata->parts, pdata->nr_parts); diff --git a/include/asm-arm/arch-omap/gpmc.h b/include/asm-arm/arch-omap/gpmc.h index e6e132244aa..53dc2461d9c 100644 --- a/include/asm-arm/arch-omap/gpmc.h +++ b/include/asm-arm/arch-omap/gpmc.h @@ -87,6 +87,7 @@ struct gpmc_timings { }; extern unsigned int gpmc_ns_to_ticks(unsigned int time_ns); +extern unsigned int gpmc_ticks_to_ns(unsigned int ticks); extern unsigned int gpmc_round_ns_to_ticks(unsigned int time_ns); extern unsigned long gpmc_get_fclk_period(void); diff --git a/include/asm-arm/arch-omap/onenand.h b/include/asm-arm/arch-omap/onenand.h index 6c959d0ce47..f0f28d3ad24 100644 --- a/include/asm-arm/arch-omap/onenand.h +++ b/include/asm-arm/arch-omap/onenand.h @@ -16,6 +16,8 @@ struct omap_onenand_platform_data { int gpio_irq; struct mtd_partition *parts; int nr_parts; - int (*onenand_setup)(void __iomem *); + int (*onenand_setup)(void __iomem *, int freq); int dma_channel; }; + +int omap2_onenand_rephase(void);