Commit 0b0dc113 authored by Tony Lindgren's avatar Tony Lindgren

MTD: OMAP: Add various OMAP drivers

Adds NOR and NAND drivers for OMAP.
Signed-off-by: default avatarTony Lindgren <tony@atomide.com>
parent 56023db1
......@@ -588,6 +588,15 @@ config MTD_MPC1211
This enables access to the flash chips on the Interface MPC-1211(CTP/PCI/MPC-SH02).
If you have such a board, say 'Y'.
config MTD_OMAP_NOR
tristate "TI OMAP board mappings"
depends on MTD_CFI && ARCH_OMAP
help
This enables access to the NOR flash chips on TI OMAP-based
boards defining flash platform devices and flash platform data.
These boards include the Innovator, H2, H3, OSK, Perseus2, and
more. If you have such a board, say 'Y'.
# This needs CFI or JEDEC, depending on the cards found.
config MTD_PCI
tristate "PCI MTD driver"
......
......@@ -71,3 +71,4 @@ obj-$(CONFIG_MTD_IXP2000) += ixp2000.o
obj-$(CONFIG_MTD_WRSBC8260) += wr_sbc82xx_flash.o
obj-$(CONFIG_MTD_DMV182) += dmv182.o
obj-$(CONFIG_MTD_SHARP_SL) += sharpsl-flash.o
obj-$(CONFIG_MTD_OMAP_NOR) += omap_nor.o
/*
* Flash memory support for various TI OMAP boards
*
* Copyright (C) 2001-2002 MontaVista Software Inc.
* Copyright (C) 2003-2004 Texas Instruments
* Copyright (C) 2004 Nokia Corporation
*
* Assembled using driver code copyright the companies above
* and written by David Brownell, Jian Zhang <jzhang@ti.com>,
* Tony Lindgren <tony@atomide.com> and others.
*
* 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* 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.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/mtd/partitions.h>
#include <asm/io.h>
#include <asm/hardware.h>
#include <asm/mach-types.h>
#include <asm/mach/flash.h>
#include <asm/arch/tc.h>
#ifdef CONFIG_MTD_PARTITIONS
static const char *part_probes[] = { /* "RedBoot", */ "cmdlinepart", NULL };
#endif
struct omapflash_info {
struct mtd_partition *parts;
struct mtd_info *mtd;
struct map_info map;
};
static void omap_set_vpp(struct map_info *map, int enable)
{
static int count;
if (enable) {
if (count++ == 0)
OMAP_EMIFS_CONFIG_REG |= OMAP_EMIFS_CONFIG_WP;
} else {
if (count && (--count == 0))
OMAP_EMIFS_CONFIG_REG &= ~OMAP_EMIFS_CONFIG_WP;
}
}
static int __devinit omapflash_probe(struct device *dev)
{
int err;
struct omapflash_info *info;
struct platform_device *pdev = to_platform_device(dev);
struct flash_platform_data *pdata = pdev->dev.platform_data;
struct resource *res = pdev->resource;
unsigned long size = res->end - res->start + 1;
info = kmalloc(sizeof(struct omapflash_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
memset(info, 0, sizeof(struct omapflash_info));
if (!request_mem_region(res->start, size, "flash")) {
err = -EBUSY;
goto out_free_info;
}
info->map.virt = ioremap(res->start, size);
if (!info->map.virt) {
err = -ENOMEM;
goto out_release_mem_region;
}
info->map.name = pdev->dev.bus_id;
info->map.phys = res->start;
info->map.size = size;
info->map.bankwidth = pdata->width;
info->map.set_vpp = omap_set_vpp;
simple_map_init(&info->map);
info->mtd = do_map_probe(pdata->map_name, &info->map);
if (!info->mtd) {
err = -EIO;
goto out_iounmap;
}
info->mtd->owner = THIS_MODULE;
#ifdef CONFIG_MTD_PARTITIONS
err = parse_mtd_partitions(info->mtd, part_probes, &info->parts, 0);
if (err > 0)
add_mtd_partitions(info->mtd, info->parts, err);
else if (err < 0 && pdata->parts)
add_mtd_partitions(info->mtd, pdata->parts, pdata->nr_parts);
else
#endif
add_mtd_device(info->mtd);
dev_set_drvdata(&pdev->dev, info);
return 0;
out_iounmap:
iounmap(info->map.virt);
out_release_mem_region:
release_mem_region(res->start, size);
out_free_info:
kfree(info);
return err;
}
static int __devexit omapflash_remove(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct omapflash_info *info = dev_get_drvdata(&pdev->dev);
dev_set_drvdata(&pdev->dev, NULL);
if (info) {
if (info->parts) {
del_mtd_partitions(info->mtd);
kfree(info->parts);
} else
del_mtd_device(info->mtd);
map_destroy(info->mtd);
release_mem_region(info->map.phys, info->map.size);
iounmap((void __iomem *) info->map.virt);
kfree(info);
}
return 0;
}
static struct device_driver omapflash_driver = {
.name = "omapflash",
.bus = &platform_bus_type,
.probe = omapflash_probe,
.remove = __devexit_p(omapflash_remove),
};
static int __init omapflash_init(void)
{
return driver_register(&omapflash_driver);
}
static void __exit omapflash_exit(void)
{
driver_unregister(&omapflash_driver);
}
module_init(omapflash_init);
module_exit(omapflash_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MTD NOR map driver for TI OMAP boards");
......@@ -49,6 +49,12 @@ config MTD_NAND_SPIA
help
If you had to ask, you don't have one. Say 'N'.
config MTD_NAND_OMAP
tristate "NAND Flash device on OMAP H3/H2 board"
depends on ARM && ARCH_OMAP1 && MTD_NAND && (MACH_OMAP_H2 || MACH_OMAP_H3 || MACH_NETSTAR)
help
Support for NAND flash on Texas Instruments H3/H2 platform.
config MTD_NAND_TOTO
tristate "NAND Flash device on TOTO board"
depends on ARM && ARCH_OMAP && MTD_NAND
......@@ -203,5 +209,12 @@ config MTD_NAND_DISKONCHIP_BBTWRITE
help
The simulator may simulate verious NAND flash chips for the
MTD nand layer.
config MTD_NAND_OMAP_HW
bool "OMAP HW NAND Flash controller support"
depends on ARM && ARCH_OMAP16XX && MTD_NAND
help
Driver for TI OMAP16xx hardware NAND flash controller.
endmenu
......@@ -20,5 +20,7 @@ obj-$(CONFIG_MTD_NAND_H1900) += h1910.o
obj-$(CONFIG_MTD_NAND_RTC_FROM4) += rtc_from4.o
obj-$(CONFIG_MTD_NAND_SHARPSL) += sharpsl.o
obj-$(CONFIG_MTD_NAND_NANDSIM) += nandsim.o
obj-$(CONFIG_MTD_NAND_OMAP) += omap-nand-flash.o
obj-$(CONFIG_MTD_NAND_OMAP_HW) += omap-hw.o
nand-objs = nand_base.o nand_bbt.o
/*
* drivers/mtd/nand/omap-hw.c
*
* This is the MTD driver for OMAP 1710 internal HW nand controller.
*
* Copyright (C) 2004 Nokia Corporation
*
* Author: Jarkko Lavinen <jarkko.lavinen@nokia.com>
*
* Dma patches by Juha Yrjl <juha.yrjola@nokia.com>
*
* $Id: omap-hw.c,v 1.1 2004/12/08 00:00:01 jlavi Exp $
*
* 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.
*
* 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; see the file COPYING. If not, write to the Free Software
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/wait.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/partitions.h>
#include <linux/mtd/nand_ecc.h>
#include <linux/dma-mapping.h>
#include <asm/io.h>
#include <asm/arch/board.h>
#include <asm/arch/dma.h>
#include <asm/hardware/clock.h>
#define NAND_BASE 0xfffbcc00
#define NND_REVISION 0x00
#define NND_ACCESS 0x04
#define NND_ADDR_SRC 0x08
#define NND_CTRL 0x10
#define NND_MASK 0x14
#define NND_STATUS 0x18
#define NND_READY 0x1c
#define NND_COMMAND 0x20
#define NND_COMMAND_SEC 0x24
#define NND_ECC_SELECT 0x28
#define NND_ECC_START 0x2c
#define NND_ECC_9 0x4c
#define NND_RESET 0x50
#define NND_FIFO 0x54
#define NND_FIFOCTRL 0x58
#define NND_PSC_CLK 0x5c
#define NND_SYSTEST 0x60
#define NND_SYSCFG 0x64
#define NND_SYSSTATUS 0x68
#define NND_FIFOTEST1 0x6c
#define NND_FIFOTEST2 0x70
#define NND_FIFOTEST3 0x74
#define NND_FIFOTEST4 0x78
#define NND_PSC1_CLK 0x8c
#define NND_PSC2_CLK 0x90
#define NND_CMD_READ1_LOWER 0x00
#define NND_CMD_WRITE1_LOWER 0x00
#define NND_CMD_READ1_UPPER 0x01
#define NND_CMD_WRITE1_UPPER 0x01
#define NND_CMD_PROGRAM_END 0x10
#define NND_CMD_READ2_SPARE 0x50
#define NND_CMD_WRITE2_SPARE 0x50
#define NND_CMD_ERASE 0x60
#define NND_CMD_STATUS 0x70
#define NND_CMD_PROGRAM 0x80
#define NND_CMD_READ_ID 0x90
#define NND_CMD_ERASE_END 0xD0
#define NND_CMD_RESET 0xFF
#define NAND_Ecc_P1e (1 << 0)
#define NAND_Ecc_P2e (1 << 1)
#define NAND_Ecc_P4e (1 << 2)
#define NAND_Ecc_P8e (1 << 3)
#define NAND_Ecc_P16e (1 << 4)
#define NAND_Ecc_P32e (1 << 5)
#define NAND_Ecc_P64e (1 << 6)
#define NAND_Ecc_P128e (1 << 7)
#define NAND_Ecc_P256e (1 << 8)
#define NAND_Ecc_P512e (1 << 9)
#define NAND_Ecc_P1024e (1 << 10)
#define NAND_Ecc_P2048e (1 << 11)
#define NAND_Ecc_P1o (1 << 16)
#define NAND_Ecc_P2o (1 << 17)
#define NAND_Ecc_P4o (1 << 18)
#define NAND_Ecc_P8o (1 << 19)
#define NAND_Ecc_P16o (1 << 20)
#define NAND_Ecc_P32o (1 << 21)
#define NAND_Ecc_P64o (1 << 22)
#define NAND_Ecc_P128o (1 << 23)
#define NAND_Ecc_P256o (1 << 24)
#define NAND_Ecc_P512o (1 << 25)
#define NAND_Ecc_P1024o (1 << 26)
#define NAND_Ecc_P2048o (1 << 27)
#define TF(value) (value ? 1 : 0)
#define P2048e(a) (TF(a & NAND_Ecc_P2048e) << 0 )
#define P2048o(a) (TF(a & NAND_Ecc_P2048o) << 1 )
#define P1e(a) (TF(a & NAND_Ecc_P1e) << 2 )
#define P1o(a) (TF(a & NAND_Ecc_P1o) << 3 )
#define P2e(a) (TF(a & NAND_Ecc_P2e) << 4 )
#define P2o(a) (TF(a & NAND_Ecc_P2o) << 5 )
#define P4e(a) (TF(a & NAND_Ecc_P4e) << 6 )
#define P4o(a) (TF(a & NAND_Ecc_P4o) << 7 )
#define P8e(a) (TF(a & NAND_Ecc_P8e) << 0 )
#define P8o(a) (TF(a & NAND_Ecc_P8o) << 1 )
#define P16e(a) (TF(a & NAND_Ecc_P16e) << 2 )
#define P16o(a) (TF(a & NAND_Ecc_P16o) << 3 )
#define P32e(a) (TF(a & NAND_Ecc_P32e) << 4 )
#define P32o(a) (TF(a & NAND_Ecc_P32o) << 5 )
#define P64e(a) (TF(a & NAND_Ecc_P64e) << 6 )
#define P64o(a) (TF(a & NAND_Ecc_P64o) << 7 )
#define P128e(a) (TF(a & NAND_Ecc_P128e) << 0 )
#define P128o(a) (TF(a & NAND_Ecc_P128o) << 1 )
#define P256e(a) (TF(a & NAND_Ecc_P256e) << 2 )
#define P256o(a) (TF(a & NAND_Ecc_P256o) << 3 )
#define P512e(a) (TF(a & NAND_Ecc_P512e) << 4 )
#define P512o(a) (TF(a & NAND_Ecc_P512o) << 5 )
#define P1024e(a) (TF(a & NAND_Ecc_P1024e) << 6 )
#define P1024o(a) (TF(a & NAND_Ecc_P1024o) << 7 )
#define P8e_s(a) (TF(a & NAND_Ecc_P8e) << 0 )
#define P8o_s(a) (TF(a & NAND_Ecc_P8o) << 1 )
#define P16e_s(a) (TF(a & NAND_Ecc_P16e) << 2 )
#define P16o_s(a) (TF(a & NAND_Ecc_P16o) << 3 )
#define P1e_s(a) (TF(a & NAND_Ecc_P1e) << 4 )
#define P1o_s(a) (TF(a & NAND_Ecc_P1o) << 5 )
#define P2e_s(a) (TF(a & NAND_Ecc_P2e) << 6 )
#define P2o_s(a) (TF(a & NAND_Ecc_P2o) << 7 )
#define P4e_s(a) (TF(a & NAND_Ecc_P4e) << 0 )
#define P4o_s(a) (TF(a & NAND_Ecc_P4o) << 1 )
extern struct nand_oobinfo jffs2_oobinfo;
/*
* MTD structure for OMAP board
*/
static struct mtd_info *omap_mtd;
static struct clk *omap_nand_clk;
static unsigned long omap_nand_base = io_p2v(NAND_BASE);
static inline u32 nand_read_reg(int idx)
{
return __raw_readl(omap_nand_base + idx);
}
static inline void nand_write_reg(int idx, u32 val)
{
__raw_writel(val, omap_nand_base + idx);
}
static inline u8 nand_read_reg8(int idx)
{
return __raw_readb(omap_nand_base + idx);
}
static inline void nand_write_reg8(int idx, u8 val)
{
__raw_writeb(val, omap_nand_base + idx);
}
static void omap_nand_select_chip(struct mtd_info *mtd, int chip)
{
u32 l;
switch(chip) {
case -1:
l = nand_read_reg(NND_CTRL);
l |= (1 << 8) | (1 << 10) | (1 << 12) | (1 << 14);
nand_write_reg(NND_CTRL, l);
break;
case 0:
/* Also CS1, CS2, CS4 would be available */
l = nand_read_reg(NND_CTRL);
l &= ~(1 << 8);
nand_write_reg(NND_CTRL, l);
break;
default:
BUG();
}
}
static void nand_dma_cb(int lch, u16 ch_status, void *data)
{
complete((struct completion *) data);
}
static inline int omap_nand_dma_transfer(struct mtd_info *mtd, void *addr,
unsigned int u32_count, int is_write)
{
const int block_size = 16;
unsigned int block_count, len;
int r, dma_ch;
struct completion comp;
unsigned long fifo_reg;
r = omap_request_dma(OMAP_DMA_NAND, "NAND", nand_dma_cb, &comp, &dma_ch);
if (r < 0)
return r;
block_count = u32_count * 4 / block_size;
nand_write_reg(NND_FIFOCTRL, (block_size << 24) | block_count);
fifo_reg = NAND_BASE + NND_FIFO;
if (is_write) {
omap_set_dma_dest_params(dma_ch, OMAP_DMA_PORT_TIPB,
OMAP_DMA_AMODE_CONSTANT, fifo_reg);
omap_set_dma_src_params(dma_ch, OMAP_DMA_PORT_EMIFF,
OMAP_DMA_AMODE_POST_INC,
virt_to_phys(addr));
// omap_set_dma_src_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_4);
/* Set POSTWRITE bit */
nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) | (1 << 16));
} else {
omap_set_dma_src_params(dma_ch, OMAP_DMA_PORT_TIPB,
OMAP_DMA_AMODE_CONSTANT, fifo_reg);
omap_set_dma_dest_params(dma_ch, OMAP_DMA_PORT_EMIFF,
OMAP_DMA_AMODE_POST_INC,
virt_to_phys(addr));
// omap_set_dma_dest_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_8);
/* Set PREFETCH bit */
nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) | (1 << 17));
}
omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S32, block_size / 4,
block_count, OMAP_DMA_SYNC_FRAME);
init_completion(&comp);
len = u32_count << 2;
consistent_sync(addr, len, DMA_TO_DEVICE);
omap_start_dma(dma_ch);
wait_for_completion(&comp);
omap_free_dma(dma_ch);
if (!is_write)
consistent_sync(addr, len, DMA_FROM_DEVICE);
nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) & ~((1 << 16) | (1 << 17)));
return 0;
}
static void fifo_read(u32 *out, unsigned int len)
{
const int block_size = 16;
unsigned long status_reg, fifo_reg;
int c;
status_reg = omap_nand_base + NND_STATUS;
fifo_reg = omap_nand_base + NND_FIFO;
len = len * 4 / block_size;
nand_write_reg(NND_FIFOCTRL, (block_size << 24) | len);
nand_write_reg(NND_STATUS, 0x0f);
nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) | (1 << 17));
c = block_size / 4;
while (len--) {
int i;
while ((__raw_readl(status_reg) & (1 << 2)) == 0);
__raw_writel(0x0f, status_reg);
for (i = 0; i < c; i++) {
u32 l = __raw_readl(fifo_reg);
*out++ = l;
}
}
nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) & ~(1 << 17));
nand_write_reg(NND_STATUS, 0x0f);
}
static void omap_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
{
unsigned long access_reg;
if (likely(((unsigned long) buf & 3) == 0 && (len & 3) == 0)) {
int u32_count = len >> 2;
u32 *dest = (u32 *) buf;
/* If the transfer is big enough and the length divisible by
* 16, we try to use DMA transfer, or FIFO copy in case of
* DMA failure (e.g. all channels busy) */
if (u32_count > 64 && (u32_count & 3) == 0) {
#if 1
if (omap_nand_dma_transfer(mtd, buf, u32_count, 0) == 0)
return;
#endif
/* In case of an error, fallback to FIFO copy */
fifo_read((u32 *) buf, u32_count);
return;
}
access_reg = omap_nand_base + NND_ACCESS;
/* Small buffers we just read directly */
while (u32_count--)
*dest++ = __raw_readl(access_reg);
} else {
/* If we're not word-aligned, we use byte copy */
access_reg = omap_nand_base + NND_ACCESS;
while (len--)
*buf++ = __raw_readb(access_reg);
}
}
static void omap_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
{
if (likely(((unsigned long) buf & 3) == 0 && (len & 3) == 0)) {
const u32 *src = (const u32 *) buf;
len >>= 2;
#if 0
/* If the transfer is big enough and length divisible by 16,
* we try to use DMA transfer. */
if (len > 256 / 4 && (len & 3) == 0) {
if (omap_nand_dma_transfer(mtd, (void *) buf, len, 1) == 0)
return;
/* In case of an error, fallback to CPU copy */
}
#endif
while (len--)
nand_write_reg(NND_ACCESS, *src++);
} else {
while (len--)
nand_write_reg8(NND_ACCESS, *buf++);
}
}
static int omap_nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
{
if (likely(((unsigned long) buf & 3) == 0 && (len & 3) == 0)) {
const u32 *dest = (const u32 *) buf;
len >>= 2;
while (len--)
if (*dest++ != nand_read_reg(NND_ACCESS))
return -EFAULT;
} else {
while (len--)
if (*buf++ != nand_read_reg8(NND_ACCESS))
return -EFAULT;
}
return 0;
}
static u_char omap_nand_read_byte(struct mtd_info *mtd)
{
return nand_read_reg8(NND_ACCESS);
}
static void omap_nand_write_byte(struct mtd_info *mtd, u_char byte)
{
nand_write_reg8(NND_ACCESS, byte);
}
static int omap_nand_dev_ready(struct mtd_info *mtd)
{
u32 l;
l = nand_read_reg(NND_READY);
return l & 0x01;
}
static int nand_write_command(u8 cmd, u32 addr, int addr_valid)
{
if (addr_valid) {
nand_write_reg(NND_ADDR_SRC, addr);
nand_write_reg8(NND_COMMAND, cmd);
} else {
nand_write_reg(NND_ADDR_SRC, 0);
nand_write_reg8(NND_COMMAND_SEC, cmd);
}
while (!omap_nand_dev_ready(NULL));
return 0;
}
/*
* Send command to NAND device
*/
static void omap_nand_command(struct mtd_info *mtd, unsigned command, int column, int page_addr)
{
struct nand_chip *this = mtd->priv;
/*
* Write out the command to the device.
*/
if (command == NAND_CMD_SEQIN) {
int readcmd;
if (column >= mtd->oobblock) {
/* OOB area */
column -= mtd->oobblock;
readcmd = NAND_CMD_READOOB;
} else if (column < 256) {
/* First 256 bytes --> READ0 */
readcmd = NAND_CMD_READ0;
} else {
column -= 256;
readcmd = NAND_CMD_READ1;
}
nand_write_command(readcmd, 0, 0);
}
switch (command) {
case NAND_CMD_RESET:
case NAND_CMD_PAGEPROG:
case NAND_CMD_STATUS:
case NAND_CMD_ERASE2:
nand_write_command(command, 0, 0);
break;
case NAND_CMD_ERASE1:
nand_write_command(command, ((page_addr & 0xFFFFFF00) << 1) | (page_addr & 0XFF), 1);
break;
default:
nand_write_command(command, (page_addr << this->page_shift) | column, 1);
}
}
static void omap_nand_command_lp(struct mtd_info *mtd, unsigned command, int column, int page_addr)
{
struct nand_chip *this = mtd->priv;
if (command == NAND_CMD_READOOB) {
column += mtd->oobblock;
command = NAND_CMD_READ0;
}
switch (command) {
case NAND_CMD_RESET:
case NAND_CMD_PAGEPROG:
case NAND_CMD_STATUS:
case NAND_CMD_ERASE2:
nand_write_command(command, 0, 0);
break;
case NAND_CMD_ERASE1:
nand_write_command(command, page_addr << this->page_shift >> 11, 1);
break;
default:
nand_write_command(command, (page_addr << 16) | column, 1);
}
if (command == NAND_CMD_READ0)
nand_write_command(NAND_CMD_READSTART, 0, 0);
}
/*
* Generate non-inverted ECC bytes.
*
* 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.
*/
static int omap_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)
{
u32 l;
int reg;
int n;
struct nand_chip *this = mtd->priv;
if (this->eccmode == NAND_ECC_HW12_2048)
n = 4;
else
n = 1;
reg = NND_ECC_START;
while (n--) {
l = nand_read_reg(reg);
*ecc_code++ = l; // P128e, ..., P1e
*ecc_code++ = l >> 16; // P128o, ..., P1o
// P2048o, P1024o, P512o, P256o, P2048e, P1024e, P512e, P256e
*ecc_code++ = ((l >> 8) & 0x0f) | ((l >> 20) & 0xf0);
reg += 4;
}
return 0;
}
/*
* This function will generate true ECC value, which can be used
* when correcting data read from NAND flash memory core
*/
static void gen_true_ecc(u8 *ecc_buf)
{
u32 tmp = ecc_buf[0] | (ecc_buf[1] << 16) | ((ecc_buf[2] & 0xF0) << 20) | ((ecc_buf[2] & 0x0F) << 8);
ecc_buf[0] = ~(P64o(tmp) | P64e(tmp) | P32o(tmp) | P32e(tmp) | P16o(tmp) | P16e(tmp) | P8o(tmp) | P8e(tmp) );
ecc_buf[1] = ~(P1024o(tmp) | P1024e(tmp) | P512o(tmp) | P512e(tmp) | P256o(tmp) | P256e(tmp) | P128o(tmp) | P128e(tmp));
ecc_buf[2] = ~( P4o(tmp) | P4e(tmp) | P2o(tmp) | P2e(tmp) | P1o(tmp) | P1e(tmp) | P2048o(tmp) | P2048e(tmp));
}
/*
* This function compares two ECC's and indicates if there is an error.
* If the error can be corrected it will be corrected to the buffer
*/
static int omap_nand_compare_ecc(u8 *ecc_data1, /* read from NAND memory */
u8 *ecc_data2, /* read from register */
u8 *page_data)
{
uint i;
u8 tmp0_bit[8], tmp1_bit[8], tmp2_bit[8];
u8 comp0_bit[8], comp1_bit[8], comp2_bit[8];
u8 ecc_bit[24];
u8 ecc_sum = 0;
u8 find_bit = 0;
uint find_byte = 0;
int isEccFF;
isEccFF = ((*(u32 *)ecc_data1 & 0xFFFFFF) == 0xFFFFFF);
gen_true_ecc(ecc_data1);
gen_true_ecc(ecc_data2);
for (i = 0; i <= 2; i++) {
*(ecc_data1 + i) = ~(*(ecc_data1 + i));
*(ecc_data2 + i) = ~(*(ecc_data2 + i));
}
for (i = 0; i < 8; i++) {
tmp0_bit[i] = *ecc_data1 % 2;
*ecc_data1 = *ecc_data1 / 2;
}
for (i = 0; i < 8; i++) {
tmp1_bit[i] = *(ecc_data1 + 1) % 2;
*(ecc_data1 + 1) = *(ecc_data1 + 1) / 2;
}
for (i = 0; i < 8; i++) {
tmp2_bit[i] = *(ecc_data1 + 2) % 2;
*(ecc_data1 + 2) = *(ecc_data1 + 2) / 2;
}
for (i = 0; i < 8; i++) {
comp0_bit[i] = *ecc_data2 % 2;
*ecc_data2 = *ecc_data2 / 2;
}
for (i = 0; i < 8; i++) {
comp1_bit[i] = *(ecc_data2 + 1) % 2;
*(ecc_data2 + 1) = *(ecc_data2 + 1) / 2;
}
for (i = 0; i < 8; i++) {
comp2_bit[i] = *(ecc_data2 + 2) % 2;
*(ecc_data2 + 2) = *(ecc_data2 + 2) / 2;
}
for (i = 0; i< 6; i++ )
ecc_bit[i] = tmp2_bit[i + 2] ^ comp2_bit[i + 2];
for (i = 0; i < 8; i++)
ecc_bit[i + 6] = tmp0_bit[i] ^ comp0_bit[i];
for (i = 0; i < 8; i++)
ecc_bit[i + 14] = tmp1_bit[i] ^ comp1_bit[i];
ecc_bit[22] = tmp2_bit[0] ^ comp2_bit[0];
ecc_bit[23] = tmp2_bit[1] ^ comp2_bit[1];
for (i = 0; i < 24; i++)
ecc_sum += ecc_bit[i];
switch (ecc_sum) {
case 0:
/* Not reached because this function is not called if
ECC values are equal */
return 0;
case 1:
/* Uncorrectable error */
DEBUG (MTD_DEBUG_LEVEL0, "ECC UNCORRECTED_ERROR 1\n");
return -1;
case 12:
/* Correctable error */
find_byte = (ecc_bit[23] << 8) +
(ecc_bit[21] << 7) +
(ecc_bit[19] << 6) +
(ecc_bit[17] << 5) +
(ecc_bit[15] << 4) +
(ecc_bit[13] << 3) +
(ecc_bit[11] << 2) +
(ecc_bit[9] << 1) +
ecc_bit[7];
find_bit = (ecc_bit[5] << 2) + (ecc_bit[3] << 1) + ecc_bit[1];
DEBUG (MTD_DEBUG_LEVEL0, "Correcting single bit ECC error at offset: %d, bit: %d\n", find_byte, find_bit);
page_data[find_byte] ^= (1 << find_bit);
return 0;
default:
if (isEccFF) {
if (ecc_data2[0] == 0 && ecc_data2[1] == 0 && ecc_data2[2] == 0)
return 0;
}
DEBUG (MTD_DEBUG_LEVEL0, "UNCORRECTED_ERROR default\n");
return -1;
}
}
static int omap_nand_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc)
{
struct nand_chip *this;
int block_count = 0, i, r;
this = mtd->priv;
if (this->eccmode == NAND_ECC_HW12_2048)
block_count = 4;
else
block_count = 1;
for (i = 0; i < block_count; i++) {
if (memcmp(read_ecc, calc_ecc, 3) != 0) {
r = omap_nand_compare_ecc(read_ecc, calc_ecc, dat);
if (r < 0)
return r;
}
read_ecc += 3;
calc_ecc += 3;
dat += 512;
}
return 0;
}
static void omap_nand_enable_hwecc(struct mtd_info *mtd, int mode)
{
nand_write_reg(NND_RESET, 0x01);
}
static int omap_nand_scan_bbt(struct mtd_info *mtd)
{
return 0;
}
#ifdef CONFIG_MTD_CMDLINE_PARTS
extern int mtdpart_setup(char *);
static int __init add_dynamic_parts(struct mtd_info *mtd)
{
static const char *part_parsers[] = { "cmdlinepart", NULL };
struct mtd_partition *parts;
const struct omap_flash_part_config *cfg;
char *part_str = NULL;
size_t part_str_len;
int c;
cfg = omap_get_var_config(OMAP_TAG_FLASH_PART, &part_str_len);
if (cfg != NULL) {
part_str = kmalloc(part_str_len + 1, GFP_KERNEL);
if (part_str == NULL)
return -ENOMEM;
memcpy(part_str, cfg->part_table, part_str_len);
part_str[part_str_len] = '\0';
mtdpart_setup(part_str);
}
c = parse_mtd_partitions(omap_mtd, part_parsers, &parts, 0);
if (part_str != NULL) {
mtdpart_setup(NULL);
kfree(part_str);
}
if (c <= 0)
return -1;
add_mtd_partitions(mtd, parts, c);
return 0;
}
#else
static inline int add_dynamic_parts(struct mtd_info *mtd)
{
return -1;
}
#endif
static inline int calc_psc(int ns, int cycle_ps)
{
return (ns * 1000 + (cycle_ps - 1)) / cycle_ps;
}
static void set_psc_regs(int psc_ns, int psc1_ns, int psc2_ns)
{
int psc[3], i;
unsigned long rate, ps;
rate = clk_get_rate(omap_nand_clk);
ps = 1000000000 / (rate / 1000);
psc[0] = calc_psc(psc_ns, ps);
psc[1] = calc_psc(psc1_ns, ps);
psc[2] = calc_psc(psc2_ns, ps);
for (i = 0; i < 3; i++) {
if (psc[i] == 0)
psc[i] = 1;
else if (psc[i] > 256)
psc[i] = 256;
}
nand_write_reg(NND_PSC_CLK, psc[0] - 1);
nand_write_reg(NND_PSC1_CLK, psc[1] - 1);
nand_write_reg(NND_PSC2_CLK, psc[2] - 1);
printk(KERN_INFO "omap-hw-nand: using PSC values %d, %d, %d\n", psc[0], psc[1], psc[2]);
}
/*
* Main initialization routine
*/
static int __init omap_nand_init(void)
{
struct nand_chip *this;
int err = 0;
u32 l;
omap_nand_clk = clk_get(NULL, "armper_ck");
BUG_ON(omap_nand_clk == NULL);
clk_use(omap_nand_clk);
l = nand_read_reg(NND_REVISION);
printk(KERN_INFO "omap-hw-nand: OMAP NAND Controller rev. %d.%d\n", l>>4, l & 0xf);
/* Reset the NAND Controller */
nand_write_reg(NND_SYSCFG, 0x02);
while ((nand_read_reg(NND_SYSSTATUS) & 0x01) == 0);
/* No Prefetch, no postwrite, write prot & enable pairs disabled,
addres counter set to send 4 byte addresses to flash,
A8 is set not to be sent to flash (erase addre needs formatting),
choose little endian, enable 512 byte ECC logic,
*/
nand_write_reg(NND_CTRL, 0xFF01);
/* Allocate memory for MTD device structure and private data */
omap_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL);
if (!omap_mtd) {
printk(KERN_WARNING "omap-hw-nand: Unable to allocate OMAP NAND MTD device structure.\n");
err = -ENOMEM;
goto free_clock;
}
/* Get pointer to private data */
this = (struct nand_chip *) (&omap_mtd[1]);
/* Initialize structures */
memset((char *) omap_mtd, 0, sizeof(struct mtd_info));
memset((char *) this, 0, sizeof(struct nand_chip));
/* Link the private data with the MTD structure */
omap_mtd->priv = this;
omap_mtd->name = "omap-nand";
/* Used from chip select and nand_command() */
this->read_byte = omap_nand_read_byte;
this->write_byte = omap_nand_write_byte;
this->select_chip = omap_nand_select_chip;
this->dev_ready = omap_nand_dev_ready;
this->chip_delay = 0;
this->eccmode = NAND_ECC_HW3_512;
this->cmdfunc = omap_nand_command;
this->write_buf = omap_nand_write_buf;
this->read_buf = omap_nand_read_buf;
this->verify_buf = omap_nand_verify_buf;
this->calculate_ecc = omap_nand_calculate_ecc;
this->correct_data = omap_nand_correct_data;
this->enable_hwecc = omap_nand_enable_hwecc;
this->scan_bbt = omap_nand_scan_bbt;
nand_write_reg(NND_PSC_CLK, 10);
/* Scan to find existance of the device */
if (nand_scan(omap_mtd, 1)) {
err = -ENXIO;
goto out_mtd;
}
set_psc_regs(25, 15, 35);
if (this->page_shift == 11) {
this->cmdfunc = omap_nand_command_lp;
l = nand_read_reg(NND_CTRL);
l |= 1 << 4; /* Set the A8 bit in CTRL reg */
nand_write_reg(NND_CTRL, l);
this->eccmode = NAND_ECC_HW12_2048;
this->eccsteps = 1;
this->eccsize = 2048;
this->eccbytes = 12;
omap_mtd->eccsize = 2048;
nand_write_reg(NND_ECC_SELECT, 6);
}
this->options |= NAND_NO_AUTOINCR;
err = add_dynamic_parts(omap_mtd);
if (err < 0) {
printk(KERN_ERR "omap-hw-nand: no partitions defined\n");
err = -ENODEV;
nand_release(omap_mtd);
goto out_mtd;
}
/* init completed */
return 0;
out_mtd:
kfree(omap_mtd);
free_clock:
clk_put(omap_nand_clk);
return err;
}
module_init(omap_nand_init);
/*
* Clean up routine
*/
static void __exit omap_nand_cleanup (void)
{
clk_unuse(omap_nand_clk);
clk_put(omap_nand_clk);
nand_release(omap_mtd);
kfree(omap_mtd);
}
module_exit(omap_nand_cleanup);
/*
* drivers/mtd/nand/omap-nand-flash.c
*
* Copyright (c) 2004 Texas Instruments
* Jian Zhang <jzhang@ti.com>
* Copyright (c) 2004 David Brownell
*
* Derived from drivers/mtd/autcpu12.c
*
* Copyright (c) 2002 Thomas Gleixner <tgxl@linutronix.de>
*
* 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.
*
* Overview:
* This is a device driver for the NAND flash device found on the
* TI H3/H2 boards. It supports 16-bit 32MiB Samsung k9f5616 chip.
*
*/
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/partitions.h>
#include <asm/io.h>
#include <asm/arch/hardware.h>
#include <asm/arch/gpio.h>
#include <asm/arch/mux.h>
#include <asm/arch/tc.h>
#include <asm/sizes.h>
#include <asm/mach-types.h>
#define H3_NAND_RB_GPIO_PIN 10
#define H2_NAND_RB_GPIO_PIN 62
#define NETSTAR_NAND_RB_GPIO_PIN 1
/*
* MTD structure for H3 board
*/
static struct mtd_info *omap_nand_mtd = NULL;
static void __iomem *omap_nand_flash_base;
/*
* Define partitions for flash devices
*/
#ifdef CONFIG_MTD_PARTITIONS
static struct mtd_partition static_partition[] = {
{ .name = "Booting Image",
.offset = 0,
.size = 64 * 1024,
.mask_flags = MTD_WRITEABLE /* force read-only */
},
{ .name = "U-Boot",
.offset = MTDPART_OFS_APPEND,
.size = 256 * 1024,
.mask_flags = MTD_WRITEABLE /* force read-only */
},
{ .name = "U-Boot Environment",
.offset = MTDPART_OFS_APPEND,
.size = 192 * 1024
},
{ .name = "Kernel",
.offset = MTDPART_OFS_APPEND,
.size = 2 * SZ_1M
},
{ .name = "File System",
.size = MTDPART_SIZ_FULL,
.offset = MTDPART_OFS_APPEND,
},
};
const char *part_probes[] = { "cmdlinepart", NULL, };
#endif
/* H2/H3 maps two address LSBs to CLE and ALE; MSBs make CS_2B */
#define MASK_CLE 0x02
#define MASK_ALE 0x04
/*
* hardware specific access to control-lines
*/
static void omap_nand_hwcontrol(struct mtd_info *mtd, int cmd)
{
struct nand_chip *this = mtd->priv;
u32 IO_ADDR_W = (u32) this->IO_ADDR_W;
IO_ADDR_W &= ~(MASK_ALE|MASK_CLE);
switch(cmd){
case NAND_CTL_SETCLE: IO_ADDR_W |= MASK_CLE; break;
case NAND_CTL_SETALE: IO_ADDR_W |= MASK_ALE; break;
}
this->IO_ADDR_W = (void __iomem *) IO_ADDR_W;
}
/*
* chip busy R/B detection
*/
static int omap_nand_ready(struct mtd_info *mtd)
{
if (machine_is_omap_h3())
return omap_get_gpio_datain(H3_NAND_RB_GPIO_PIN);
if (machine_is_omap_h2())
return omap_get_gpio_datain(H2_NAND_RB_GPIO_PIN);
if (machine_is_netstar())
return omap_get_gpio_datain(NETSTAR_NAND_RB_GPIO_PIN);
}
/* Scan to find existance of the device at omap_nand_flash_base.
This also allocates oob and data internal buffers */
static int probe_nand_chip(void)
{
struct nand_chip *this;
this = (struct nand_chip *) (&omap_nand_mtd[1]);
/* Initialize structures */
memset((char *) this, 0, sizeof(struct nand_chip));
this->IO_ADDR_R = omap_nand_flash_base;
this->IO_ADDR_W = omap_nand_flash_base;
this->options = NAND_SAMSUNG_LP_OPTIONS;
this->hwcontrol = omap_nand_hwcontrol;
this->eccmode = NAND_ECC_SOFT;
/* try 16-bit chip first */
this->options |= NAND_BUSWIDTH_16;
if (nand_scan (omap_nand_mtd, 1)) {
if (machine_is_omap_h3())
return -ENXIO;
/* then try 8-bit chip for H2 */
memset((char *) this, 0, sizeof(struct nand_chip));
this->IO_ADDR_R = omap_nand_flash_base;
this->IO_ADDR_W = omap_nand_flash_base;
this->options = NAND_SAMSUNG_LP_OPTIONS;
this->hwcontrol = omap_nand_hwcontrol;
this->eccmode = NAND_ECC_SOFT;
if (nand_scan (omap_nand_mtd, 1)) {
return -ENXIO;
}
}
return 0;
}
/*
* Main initialization routine
*/
int __init omap_nand_init (void)
{
struct nand_chip *this;
struct mtd_partition *dynamic_partition = 0;
int err = 0;
int nandboot = 0;
if (!(machine_is_omap_h2() || machine_is_omap_h3() || machine_is_netstar()))
return -ENODEV;
/* Allocate memory for MTD device structure and private data */
omap_nand_mtd = kmalloc (sizeof(struct mtd_info) + sizeof (struct nand_chip),
GFP_KERNEL);
if (!omap_nand_mtd) {
printk (KERN_WARNING "Unable to allocate NAND MTD device structure.\n");
err = -ENOMEM;
goto out;
}
/* Get pointer to private data */
this = (struct nand_chip *) (&omap_nand_mtd[1]);
/* Initialize structures */
memset((char *) omap_nand_mtd, 0, sizeof(struct mtd_info) + sizeof(struct nand_chip));
/* Link the private data with the MTD structure */
omap_nand_mtd->priv = this;
if (machine_is_omap_h2()) {
/* FIXME on H2, R/B needs M7_1610_GPIO62 ... */
this->chip_delay = 15;
omap_cfg_reg(L3_1610_FLASH_CS2B_OE);
omap_cfg_reg(M8_1610_FLASH_CS2B_WE);
} else if (machine_is_omap_h3()) {
if (omap_request_gpio(H3_NAND_RB_GPIO_PIN) != 0) {
printk(KERN_ERR "NAND: Unable to get GPIO pin for R/B, use delay\n");
/* 15 us command delay time */
this->chip_delay = 15;
} else {
/* GPIO10 for input. it is in GPIO1 module */
omap_set_gpio_direction(H3_NAND_RB_GPIO_PIN, 1);
/* GPIO10 Func_MUX_CTRL reg bit 29:27, Configure V2 to mode1 as GPIO */
/* GPIO10 pullup/down register, Enable pullup on GPIO10 */
omap_cfg_reg(V2_1710_GPIO10);
this->dev_ready = omap_nand_ready;
}
} else if (machine_is_netstar()) {
if (omap_request_gpio(NETSTAR_NAND_RB_GPIO_PIN) != 0) {
printk(KERN_ERR "NAND: Unable to get GPIO pin for R/B, use delay\n");
/* 15 us command delay time */
this->chip_delay = 15;
} else {
omap_set_gpio_direction(NETSTAR_NAND_RB_GPIO_PIN, 1);
this->dev_ready = omap_nand_ready;
}
}
/* try the first address */
omap_nand_flash_base = ioremap(OMAP_NAND_FLASH_START1, SZ_4K);
if (probe_nand_chip()){
nandboot = 1;
/* try the second address */
iounmap(omap_nand_flash_base);
omap_nand_flash_base = ioremap(OMAP_NAND_FLASH_START2, SZ_4K);
if (probe_nand_chip()){
iounmap(omap_nand_flash_base);
err = -ENXIO;
goto out_mtd;
}
}
/* Register the partitions */
switch(omap_nand_mtd->size) {
case SZ_128M:
if (!(machine_is_netstar()))
goto out_unsupported;
/* fall through */
case SZ_32M:
#ifdef CONFIG_MTD_PARTITIONS
err = parse_mtd_partitions(omap_nand_mtd, part_probes,
&dynamic_partition, 0);
if (err > 0)
err = add_mtd_partitions(omap_nand_mtd,
dynamic_partition, err);
else if (nandboot)
err = add_mtd_partitions(omap_nand_mtd,
static_partition,
ARRAY_SIZE(static_partition));
else
#endif
err = add_mtd_device(omap_nand_mtd);
if (err)
goto out_buf;
break;
out_unsupported:
default:
printk(KERN_WARNING "Unsupported NAND device\n");
err = -ENXIO;
goto out_buf;
}
goto out;
out_buf:
nand_release (omap_nand_mtd);
if (this->dev_ready) {
if (machine_is_omap_h2())
omap_free_gpio(H2_NAND_RB_GPIO_PIN);
else if (machine_is_omap_h3())
omap_free_gpio(H3_NAND_RB_GPIO_PIN);
else if (machine_is_netstar())
omap_free_gpio(NETSTAR_NAND_RB_GPIO_PIN);
}
iounmap(omap_nand_flash_base);
out_mtd:
kfree (omap_nand_mtd);
out:
return err;
}
module_init(omap_nand_init);
/*
* Clean up routine
*/
static void __exit omap_nand_cleanup (void)
{
struct nand_chip *this = omap_nand_mtd->priv;
if (this->dev_ready) {
if (machine_is_omap_h2())
omap_free_gpio(H2_NAND_RB_GPIO_PIN);
else if (machine_is_omap_h3())
omap_free_gpio(H3_NAND_RB_GPIO_PIN);
else if (machine_is_netstar())
omap_free_gpio(NETSTAR_NAND_RB_GPIO_PIN);
}
/* nand_release frees MTD partitions, MTD structure
and nand internal buffers*/
nand_release (omap_nand_mtd);
kfree (omap_nand_mtd);
iounmap(omap_nand_flash_base);
}
module_exit(omap_nand_cleanup);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jian Zhang <jzhang@ti.com>");
MODULE_DESCRIPTION("Glue layer for NAND flash on H2/H3 boards");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment