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 ...@@ -588,6 +588,15 @@ config MTD_MPC1211
This enables access to the flash chips on the Interface MPC-1211(CTP/PCI/MPC-SH02). This enables access to the flash chips on the Interface MPC-1211(CTP/PCI/MPC-SH02).
If you have such a board, say 'Y'. 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. # This needs CFI or JEDEC, depending on the cards found.
config MTD_PCI config MTD_PCI
tristate "PCI MTD driver" tristate "PCI MTD driver"
......
...@@ -71,3 +71,4 @@ obj-$(CONFIG_MTD_IXP2000) += ixp2000.o ...@@ -71,3 +71,4 @@ obj-$(CONFIG_MTD_IXP2000) += ixp2000.o
obj-$(CONFIG_MTD_WRSBC8260) += wr_sbc82xx_flash.o obj-$(CONFIG_MTD_WRSBC8260) += wr_sbc82xx_flash.o
obj-$(CONFIG_MTD_DMV182) += dmv182.o obj-$(CONFIG_MTD_DMV182) += dmv182.o
obj-$(CONFIG_MTD_SHARP_SL) += sharpsl-flash.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 ...@@ -49,6 +49,12 @@ config MTD_NAND_SPIA
help help
If you had to ask, you don't have one. Say 'N'. 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 config MTD_NAND_TOTO
tristate "NAND Flash device on TOTO board" tristate "NAND Flash device on TOTO board"
depends on ARM && ARCH_OMAP && MTD_NAND depends on ARM && ARCH_OMAP && MTD_NAND
...@@ -203,5 +209,12 @@ config MTD_NAND_DISKONCHIP_BBTWRITE ...@@ -203,5 +209,12 @@ config MTD_NAND_DISKONCHIP_BBTWRITE
help help
The simulator may simulate verious NAND flash chips for the The simulator may simulate verious NAND flash chips for the
MTD nand layer. 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 endmenu
...@@ -20,5 +20,7 @@ obj-$(CONFIG_MTD_NAND_H1900) += h1910.o ...@@ -20,5 +20,7 @@ obj-$(CONFIG_MTD_NAND_H1900) += h1910.o
obj-$(CONFIG_MTD_NAND_RTC_FROM4) += rtc_from4.o obj-$(CONFIG_MTD_NAND_RTC_FROM4) += rtc_from4.o
obj-$(CONFIG_MTD_NAND_SHARPSL) += sharpsl.o obj-$(CONFIG_MTD_NAND_SHARPSL) += sharpsl.o
obj-$(CONFIG_MTD_NAND_NANDSIM) += nandsim.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 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