mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-11-29 10:37:33 +02:00
dc3d3f1c49
it's basically also provided by ingenic and nativly based on 2.6.27, adjusted to fit into the OpenWrt-environment
1039 lines
30 KiB
C
Executable File
1039 lines
30 KiB
C
Executable File
/*
|
|
* linux/drivers/mtd/nand/jz4740_nand.c
|
|
*
|
|
* Copyright (c) 2005 - 2007 Ingenic Semiconductor Inc.
|
|
*
|
|
* Ingenic JZ4740 NAND driver
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/init.h>
|
|
#include <linux/highmem.h>
|
|
|
|
#include <linux/mtd/mtd.h>
|
|
#include <linux/mtd/nand.h>
|
|
#include <linux/mtd/nand_ecc.h>
|
|
#include <linux/mtd/partitions.h>
|
|
|
|
#include <asm/io.h>
|
|
#include <asm/jzsoc.h>
|
|
|
|
#define NAND_DATA_PORT1 0xB8000000 /* read-write area in static bank 1 */
|
|
#define NAND_DATA_PORT2 0xB4000000 /* read-write area in static bank 2 */
|
|
#define NAND_DATA_PORT3 0xAC000000 /* read-write area in static bank 3 */
|
|
#define NAND_DATA_PORT4 0xA8000000 /* read-write area in static bank 4 */
|
|
|
|
#define PAR_SIZE 9
|
|
|
|
#define __nand_enable() (REG_EMC_NFCSR |= EMC_NFCSR_NFE1 | EMC_NFCSR_NFCE1)
|
|
#define __nand_disable() (REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE1)
|
|
|
|
#define __nand_ecc_enable() (REG_EMC_NFECR = EMC_NFECR_ECCE | EMC_NFECR_ERST )
|
|
#define __nand_ecc_disable() (REG_EMC_NFECR &= ~EMC_NFECR_ECCE)
|
|
|
|
#define __nand_select_hm_ecc() (REG_EMC_NFECR &= ~EMC_NFECR_RS )
|
|
#define __nand_select_rs_ecc() (REG_EMC_NFECR |= EMC_NFECR_RS)
|
|
|
|
#define __nand_read_hm_ecc() (REG_EMC_NFECC & 0x00ffffff)
|
|
|
|
#define __nand_rs_ecc_encoding() (REG_EMC_NFECR |= EMC_NFECR_RS_ENCODING)
|
|
#define __nand_rs_ecc_decoding() (REG_EMC_NFECR &= ~EMC_NFECR_RS_ENCODING)
|
|
#define __nand_ecc_encode_sync() while (!(REG_EMC_NFINTS & EMC_NFINTS_ENCF))
|
|
#define __nand_ecc_decode_sync() while (!(REG_EMC_NFINTS & EMC_NFINTS_DECF))
|
|
|
|
/*
|
|
* MTD structure for JzSOC board
|
|
*/
|
|
static struct mtd_info *jz_mtd = NULL;
|
|
extern struct mtd_info *jz_mtd1;
|
|
extern char all_use_planes;
|
|
extern int global_page; /* for two-plane operations */
|
|
|
|
/*
|
|
* Define partitions for flash devices
|
|
*/
|
|
#ifdef CONFIG_JZ4740_PAVO
|
|
static struct mtd_partition partition_info[] = {
|
|
{ name: "NAND BOOT partition",
|
|
offset: 0 * 0x100000,
|
|
size: 4 * 0x100000,
|
|
use_planes: 0 },
|
|
{ name: "NAND KERNEL partition",
|
|
offset: 4 * 0x100000,
|
|
size: 4 * 0x100000,
|
|
use_planes: 0 },
|
|
{ name: "NAND ROOTFS partition",
|
|
offset: 8 * 0x100000,
|
|
size: 120 * 0x100000,
|
|
use_planes: 0 },
|
|
{ name: "NAND DATA1 partition",
|
|
offset: 128 * 0x100000,
|
|
size: 128 * 0x100000,
|
|
use_planes: 1 },
|
|
{ name: "NAND DATA2 partition",
|
|
offset: 256 * 0x100000,
|
|
size: 256 * 0x100000,
|
|
use_planes: 1 },
|
|
{ name: "NAND VFAT partition",
|
|
offset: 512 * 0x100000,
|
|
size: 512 * 0x100000,
|
|
use_planes: 1 },
|
|
};
|
|
|
|
|
|
/* Define max reserved bad blocks for each partition.
|
|
* This is used by the mtdblock-jz.c NAND FTL driver only.
|
|
*
|
|
* The NAND FTL driver reserves some good blocks which can't be
|
|
* seen by the upper layer. When the bad block number of a partition
|
|
* exceeds the max reserved blocks, then there is no more reserved
|
|
* good blocks to be used by the NAND FTL driver when another bad
|
|
* block generated.
|
|
*/
|
|
static int partition_reserved_badblocks[] = {
|
|
2, /* reserved blocks of mtd0 */
|
|
2, /* reserved blocks of mtd1 */
|
|
10, /* reserved blocks of mtd2 */
|
|
10, /* reserved blocks of mtd3 */
|
|
20, /* reserved blocks of mtd4 */
|
|
20}; /* reserved blocks of mtd5 */
|
|
#endif /* CONFIG_JZ4740_PAVO */
|
|
|
|
#ifdef CONFIG_JZ4740_LEO
|
|
static struct mtd_partition partition_info[] = {
|
|
{ name: "NAND BOOT partition",
|
|
offset: 0 * 0x100000,
|
|
size: 4 * 0x100000 },
|
|
{ name: "NAND KERNEL partition",
|
|
offset: 4 * 0x100000,
|
|
size: 4 * 0x100000 },
|
|
{ name: "NAND ROOTFS partition",
|
|
offset: 8 * 0x100000,
|
|
size: 56 * 0x100000 },
|
|
{ name: "NAND VFAT partition",
|
|
offset: 64 * 0x100000,
|
|
size: 64 * 0x100000 },
|
|
};
|
|
static int partition_reserved_badblocks[] = {
|
|
2, /* reserved blocks of mtd0 */
|
|
2, /* reserved blocks of mtd1 */
|
|
10, /* reserved blocks of mtd2 */
|
|
10}; /* reserved blocks of mtd3 */
|
|
#endif /* CONFIG_JZ4740_LEO */
|
|
|
|
#ifdef CONFIG_JZ4740_LYRA
|
|
static struct mtd_partition partition_info[] = {
|
|
{ name: "NAND BOOT partition",
|
|
offset: 0 * 0x100000,
|
|
size: 4 * 0x100000 },
|
|
{ name: "NAND KERNEL partition",
|
|
offset: 4 * 0x100000,
|
|
size: 4 * 0x100000 },
|
|
{ name: "NAND ROOTFS partition",
|
|
offset: 8 * 0x100000,
|
|
size: 120 * 0x100000 },
|
|
{ name: "NAND DATA1 partition",
|
|
offset: 128 * 0x100000,
|
|
size: 128 * 0x100000 },
|
|
{ name: "NAND DATA2 partition",
|
|
offset: 256 * 0x100000,
|
|
size: 256 * 0x100000 },
|
|
{ name: "NAND VFAT partition",
|
|
offset: 512 * 0x100000,
|
|
size: 512 * 0x100000 },
|
|
};
|
|
|
|
/* Define max reserved bad blocks for each partition.
|
|
* This is used by the mtdblock-jz.c NAND FTL driver only.
|
|
*
|
|
* The NAND FTL driver reserves some good blocks which can't be
|
|
* seen by the upper layer. When the bad block number of a partition
|
|
* exceeds the max reserved blocks, then there is no more reserved
|
|
* good blocks to be used by the NAND FTL driver when another bad
|
|
* block generated.
|
|
*/
|
|
static int partition_reserved_badblocks[] = {
|
|
2, /* reserved blocks of mtd0 */
|
|
2, /* reserved blocks of mtd1 */
|
|
10, /* reserved blocks of mtd2 */
|
|
10, /* reserved blocks of mtd3 */
|
|
20, /* reserved blocks of mtd4 */
|
|
20}; /* reserved blocks of mtd5 */
|
|
#endif /* CONFIG_JZ4740_LYRA */
|
|
|
|
#ifdef CONFIG_JZ4725_DIPPER
|
|
static struct mtd_partition partition_info[] = {
|
|
{ name: "NAND BOOT partition",
|
|
offset: 0 * 0x100000,
|
|
size: 4 * 0x100000 },
|
|
{ name: "NAND KERNEL partition",
|
|
offset: 4 * 0x100000,
|
|
size: 4 * 0x100000 },
|
|
{ name: "NAND ROOTFS partition",
|
|
offset: 8 * 0x100000,
|
|
size: 56 * 0x100000 },
|
|
{ name: "NAND VFAT partition",
|
|
offset: 64 * 0x100000,
|
|
size: 64 * 0x100000 },
|
|
};
|
|
|
|
/* Define max reserved bad blocks for each partition.
|
|
* This is used by the mtdblock-jz.c NAND FTL driver only.
|
|
*
|
|
* The NAND FTL driver reserves some good blocks which can't be
|
|
* seen by the upper layer. When the bad block number of a partition
|
|
* exceeds the max reserved blocks, then there is no more reserved
|
|
* good blocks to be used by the NAND FTL driver when another bad
|
|
* block generated.
|
|
*/
|
|
static int partition_reserved_badblocks[] = {
|
|
2, /* reserved blocks of mtd0 */
|
|
2, /* reserved blocks of mtd1 */
|
|
10, /* reserved blocks of mtd2 */
|
|
10}; /* reserved blocks of mtd3 */
|
|
#endif /* CONFIG_JZ4740_DIPPER */
|
|
|
|
#ifdef CONFIG_JZ4720_VIRGO
|
|
static struct mtd_partition partition_info[] = {
|
|
{ name: "NAND BOOT partition",
|
|
offset: 0 * 0x100000,
|
|
size: 4 * 0x100000 },
|
|
{ name: "NAND KERNEL partition",
|
|
offset: 4 * 0x100000,
|
|
size: 4 * 0x100000 },
|
|
{ name: "NAND ROOTFS partition",
|
|
offset: 8 * 0x100000,
|
|
size: 120 * 0x100000 },
|
|
{ name: "NAND DATA1 partition",
|
|
offset: 128 * 0x100000,
|
|
size: 128 * 0x100000 },
|
|
{ name: "NAND DATA2 partition",
|
|
offset: 256 * 0x100000,
|
|
size: 256 * 0x100000 },
|
|
{ name: "NAND VFAT partition",
|
|
offset: 512 * 0x100000,
|
|
size: 512 * 0x100000 },
|
|
};
|
|
|
|
|
|
/* Define max reserved bad blocks for each partition.
|
|
* This is used by the mtdblock-jz.c NAND FTL driver only.
|
|
*
|
|
* The NAND FTL driver reserves some good blocks which can't be
|
|
* seen by the upper layer. When the bad block number of a partition
|
|
* exceeds the max reserved blocks, then there is no more reserved
|
|
* good blocks to be used by the NAND FTL driver when another bad
|
|
* block generated.
|
|
*/
|
|
static int partition_reserved_badblocks[] = {
|
|
2, /* reserved blocks of mtd0 */
|
|
2, /* reserved blocks of mtd1 */
|
|
10, /* reserved blocks of mtd2 */
|
|
10, /* reserved blocks of mtd3 */
|
|
20, /* reserved blocks of mtd4 */
|
|
20}; /* reserved blocks of mtd5 */
|
|
#endif /* CONFIG_JZ4720_VIRGO */
|
|
/*-------------------------------------------------------------------------
|
|
* Following three functions are exported and used by the mtdblock-jz.c
|
|
* NAND FTL driver only.
|
|
*/
|
|
|
|
unsigned short get_mtdblock_write_verify_enable(void)
|
|
{
|
|
#ifdef CONFIG_MTD_MTDBLOCK_WRITE_VERIFY_ENABLE
|
|
return 1;
|
|
#endif
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(get_mtdblock_write_verify_enable);
|
|
|
|
unsigned short get_mtdblock_oob_copies(void)
|
|
{
|
|
return CONFIG_MTD_OOB_COPIES;
|
|
}
|
|
EXPORT_SYMBOL(get_mtdblock_oob_copies);
|
|
|
|
int *get_jz_badblock_table(void)
|
|
{
|
|
return partition_reserved_badblocks;
|
|
}
|
|
EXPORT_SYMBOL(get_jz_badblock_table);
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static void jz_hwcontrol(struct mtd_info *mtd, int dat,
|
|
unsigned int ctrl)
|
|
{
|
|
struct nand_chip *this = (struct nand_chip *)(mtd->priv);
|
|
unsigned int nandaddr = (unsigned int)this->IO_ADDR_W;
|
|
extern u8 nand_nce; /* in nand_base.c, indicates which chip select is used for current nand chip */
|
|
|
|
if (ctrl & NAND_CTRL_CHANGE) {
|
|
if (ctrl & NAND_NCE) {
|
|
switch (nand_nce) {
|
|
case NAND_NCE1:
|
|
this->IO_ADDR_W = this->IO_ADDR_R = (void __iomem *)NAND_DATA_PORT1;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE2;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE3;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE4;
|
|
REG_EMC_NFCSR |= EMC_NFCSR_NFCE1;
|
|
break;
|
|
case NAND_NCE2:
|
|
this->IO_ADDR_W = this->IO_ADDR_R = (void __iomem *)NAND_DATA_PORT2;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE1;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE3;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE4;
|
|
REG_EMC_NFCSR |= EMC_NFCSR_NFCE2;
|
|
break;
|
|
case NAND_NCE3:
|
|
this->IO_ADDR_W = this->IO_ADDR_R = (void __iomem *)NAND_DATA_PORT3;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE1;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE2;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE4;
|
|
REG_EMC_NFCSR |= EMC_NFCSR_NFCE3;
|
|
break;
|
|
case NAND_NCE4:
|
|
this->IO_ADDR_W = this->IO_ADDR_R = (void __iomem *)NAND_DATA_PORT4;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE1;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE2;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE3;
|
|
REG_EMC_NFCSR |= EMC_NFCSR_NFCE4;
|
|
break;
|
|
default:
|
|
printk("error: no nand_nce 0x%x\n",nand_nce);
|
|
break;
|
|
}
|
|
} else {
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE1;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE2;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE3;
|
|
REG_EMC_NFCSR &= ~EMC_NFCSR_NFCE4;
|
|
}
|
|
|
|
if ( ctrl & NAND_ALE )
|
|
nandaddr = (unsigned int)((unsigned long)(this->IO_ADDR_W) | 0x00010000);
|
|
else
|
|
nandaddr = (unsigned int)((unsigned long)(this->IO_ADDR_W) & ~0x00010000);
|
|
|
|
if ( ctrl & NAND_CLE )
|
|
nandaddr = nandaddr | 0x00008000;
|
|
else
|
|
nandaddr = nandaddr & ~0x00008000;
|
|
}
|
|
|
|
this->IO_ADDR_W = (void __iomem *)nandaddr;
|
|
if (dat != NAND_CMD_NONE)
|
|
writeb(dat, this->IO_ADDR_W);
|
|
}
|
|
|
|
static int jz_device_ready(struct mtd_info *mtd)
|
|
{
|
|
int ready, wait = 10;
|
|
while (wait--);
|
|
ready = __gpio_get_pin(94);
|
|
return ready;
|
|
}
|
|
|
|
/*
|
|
* EMC setup
|
|
*/
|
|
static void jz_device_setup(void)
|
|
{
|
|
// PORT 0:
|
|
// ...
|
|
// PORT 1:
|
|
// PIN/BIT N FUNC0 FUNC1
|
|
// 25 CS1# -
|
|
// 26 CS2# -
|
|
// 27 CS3# -
|
|
// 28 CS4# -
|
|
#define GPIO_CS2_N (32+26)
|
|
#define GPIO_CS3_N (32+27)
|
|
#define GPIO_CS4_N (32+28)
|
|
#define SMCR_VAL 0x0d221200
|
|
|
|
/* Set NFE bit */
|
|
REG_EMC_NFCSR |= EMC_NFCSR_NFE1;
|
|
/* Read/Write timings */
|
|
REG_EMC_SMCR1 = SMCR_VAL;
|
|
|
|
#if defined(CONFIG_MTD_NAND_CS2)
|
|
/* Set CS2# pin as function 0 */
|
|
__gpio_as_func0(GPIO_CS2_N);
|
|
REG_EMC_NFCSR |= EMC_NFCSR_NFE2;
|
|
REG_EMC_SMCR2 = SMCR_VAL;
|
|
#endif
|
|
|
|
#if defined(CONFIG_MTD_NAND_CS3)
|
|
__gpio_as_func0(GPIO_CS3_N);
|
|
REG_EMC_NFCSR |= EMC_NFCSR_NFE3;
|
|
REG_EMC_SMCR3 = SMCR_VAL;
|
|
#endif
|
|
|
|
#if defined(CONFIG_MTD_NAND_CS4)
|
|
__gpio_as_func0(GPIO_CS4_N);
|
|
REG_EMC_NFCSR |= EMC_NFCSR_NFE4;
|
|
REG_EMC_SMCR4 = SMCR_VAL;
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_MTD_HW_HM_ECC
|
|
|
|
static int jzsoc_nand_calculate_hm_ecc(struct mtd_info* mtd,
|
|
const u_char* dat, u_char* ecc_code)
|
|
{
|
|
unsigned int calc_ecc;
|
|
unsigned char *tmp;
|
|
|
|
__nand_ecc_disable();
|
|
|
|
calc_ecc = ~(__nand_read_hm_ecc()) | 0x00030000;
|
|
|
|
tmp = (unsigned char *)&calc_ecc;
|
|
//adjust eccbytes order for compatible with software ecc
|
|
ecc_code[0] = tmp[1];
|
|
ecc_code[1] = tmp[0];
|
|
ecc_code[2] = tmp[2];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void jzsoc_nand_enable_hm_hwecc(struct mtd_info* mtd, int mode)
|
|
{
|
|
__nand_ecc_enable();
|
|
__nand_select_hm_ecc();
|
|
}
|
|
|
|
static int jzsoc_nand_hm_correct_data(struct mtd_info *mtd, u_char *dat,
|
|
u_char *read_ecc, u_char *calc_ecc)
|
|
{
|
|
u_char a, b, c, d1, d2, d3, add, bit, i;
|
|
|
|
/* Do error detection */
|
|
d1 = calc_ecc[0] ^ read_ecc[0];
|
|
d2 = calc_ecc[1] ^ read_ecc[1];
|
|
d3 = calc_ecc[2] ^ read_ecc[2];
|
|
|
|
if ((d1 | d2 | d3) == 0) {
|
|
/* No errors */
|
|
return 0;
|
|
}
|
|
else {
|
|
a = (d1 ^ (d1 >> 1)) & 0x55;
|
|
b = (d2 ^ (d2 >> 1)) & 0x55;
|
|
c = (d3 ^ (d3 >> 1)) & 0x54;
|
|
|
|
/* Found and will correct single bit error in the data */
|
|
if ((a == 0x55) && (b == 0x55) && (c == 0x54)) {
|
|
c = 0x80;
|
|
add = 0;
|
|
a = 0x80;
|
|
for (i=0; i<4; i++) {
|
|
if (d1 & c)
|
|
add |= a;
|
|
c >>= 2;
|
|
a >>= 1;
|
|
}
|
|
c = 0x80;
|
|
for (i=0; i<4; i++) {
|
|
if (d2 & c)
|
|
add |= a;
|
|
c >>= 2;
|
|
a >>= 1;
|
|
}
|
|
bit = 0;
|
|
b = 0x04;
|
|
c = 0x80;
|
|
for (i=0; i<3; i++) {
|
|
if (d3 & c)
|
|
bit |= b;
|
|
c >>= 2;
|
|
b >>= 1;
|
|
}
|
|
b = 0x01;
|
|
a = dat[add];
|
|
a ^= (b << bit);
|
|
dat[add] = a;
|
|
return 0;
|
|
}
|
|
else {
|
|
i = 0;
|
|
while (d1) {
|
|
if (d1 & 0x01)
|
|
++i;
|
|
d1 >>= 1;
|
|
}
|
|
while (d2) {
|
|
if (d2 & 0x01)
|
|
++i;
|
|
d2 >>= 1;
|
|
}
|
|
while (d3) {
|
|
if (d3 & 0x01)
|
|
++i;
|
|
d3 >>= 1;
|
|
}
|
|
if (i == 1) {
|
|
/* ECC Code Error Correction */
|
|
read_ecc[0] = calc_ecc[0];
|
|
read_ecc[1] = calc_ecc[1];
|
|
read_ecc[2] = calc_ecc[2];
|
|
return 0;
|
|
}
|
|
else {
|
|
/* Uncorrectable Error */
|
|
printk("NAND: uncorrectable ECC error\n");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Should never happen */
|
|
return -1;
|
|
}
|
|
|
|
#endif /* CONFIG_MTD_HW_HM_ECC */
|
|
|
|
#ifdef CONFIG_MTD_HW_RS_ECC
|
|
|
|
static void jzsoc_nand_enable_rs_hwecc(struct mtd_info* mtd, int mode)
|
|
{
|
|
REG_EMC_NFINTS = 0x0;
|
|
__nand_ecc_enable();
|
|
__nand_select_rs_ecc();
|
|
|
|
if (mode == NAND_ECC_READ)
|
|
__nand_rs_ecc_decoding();
|
|
|
|
if (mode == NAND_ECC_WRITE)
|
|
__nand_rs_ecc_encoding();
|
|
}
|
|
|
|
static void jzsoc_rs_correct(unsigned char *dat, int idx, int mask)
|
|
{
|
|
int i;
|
|
|
|
idx--;
|
|
|
|
i = idx + (idx >> 3);
|
|
if (i >= 512)
|
|
return;
|
|
|
|
mask <<= (idx & 0x7);
|
|
|
|
dat[i] ^= mask & 0xff;
|
|
if (i < 511)
|
|
dat[i+1] ^= (mask >> 8) & 0xff;
|
|
}
|
|
|
|
/*
|
|
* calc_ecc points to oob_buf for us
|
|
*/
|
|
static int jzsoc_nand_rs_correct_data(struct mtd_info *mtd, u_char *dat,
|
|
u_char *read_ecc, u_char *calc_ecc)
|
|
{
|
|
volatile u8 *paraddr = (volatile u8 *)EMC_NFPAR0;
|
|
short k;
|
|
u32 stat;
|
|
|
|
/* Set PAR values */
|
|
for (k = 0; k < PAR_SIZE; k++) {
|
|
*paraddr++ = read_ecc[k];
|
|
}
|
|
|
|
/* Set PRDY */
|
|
REG_EMC_NFECR |= EMC_NFECR_PRDY;
|
|
|
|
/* Wait for completion */
|
|
__nand_ecc_decode_sync();
|
|
__nand_ecc_disable();
|
|
|
|
/* Check decoding */
|
|
stat = REG_EMC_NFINTS;
|
|
|
|
if (stat & EMC_NFINTS_ERR) {
|
|
/* Error occurred */
|
|
if (stat & EMC_NFINTS_UNCOR) {
|
|
printk("NAND: Uncorrectable ECC error\n");
|
|
return -1;
|
|
} else {
|
|
u32 errcnt = (stat & EMC_NFINTS_ERRCNT_MASK) >> EMC_NFINTS_ERRCNT_BIT;
|
|
switch (errcnt) {
|
|
case 4:
|
|
jzsoc_rs_correct(dat, (REG_EMC_NFERR3 & EMC_NFERR_INDEX_MASK) >> EMC_NFERR_INDEX_BIT, (REG_EMC_NFERR3 & EMC_NFERR_MASK_MASK) >> EMC_NFERR_MASK_BIT);
|
|
/* FALL-THROUGH */
|
|
case 3:
|
|
jzsoc_rs_correct(dat, (REG_EMC_NFERR2 & EMC_NFERR_INDEX_MASK) >> EMC_NFERR_INDEX_BIT, (REG_EMC_NFERR2 & EMC_NFERR_MASK_MASK) >> EMC_NFERR_MASK_BIT);
|
|
/* FALL-THROUGH */
|
|
case 2:
|
|
jzsoc_rs_correct(dat, (REG_EMC_NFERR1 & EMC_NFERR_INDEX_MASK) >> EMC_NFERR_INDEX_BIT, (REG_EMC_NFERR1 & EMC_NFERR_MASK_MASK) >> EMC_NFERR_MASK_BIT);
|
|
/* FALL-THROUGH */
|
|
case 1:
|
|
jzsoc_rs_correct(dat, (REG_EMC_NFERR0 & EMC_NFERR_INDEX_MASK) >> EMC_NFERR_INDEX_BIT, (REG_EMC_NFERR0 & EMC_NFERR_MASK_MASK) >> EMC_NFERR_MASK_BIT);
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int jzsoc_nand_calculate_rs_ecc(struct mtd_info* mtd, const u_char* dat,
|
|
u_char* ecc_code)
|
|
{
|
|
volatile u8 *paraddr = (volatile u8 *)EMC_NFPAR0;
|
|
short i;
|
|
|
|
__nand_ecc_encode_sync();
|
|
__nand_ecc_disable();
|
|
|
|
for(i = 0; i < PAR_SIZE; i++) {
|
|
ecc_code[i] = *paraddr++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* CONFIG_MTD_HW_RS_ECC */
|
|
|
|
/* Nand optimized functions */
|
|
static int dma_chan;
|
|
static unsigned int dma_src_phys_addr, dma_dst_phys_addr;
|
|
extern int jz_request_dma(int dev_id, const char *dev_str,
|
|
irqreturn_t (*irqhandler)(int, void *),
|
|
unsigned long irqflags, void *irq_dev_id);
|
|
|
|
static void dma_setup(void)
|
|
{
|
|
/* Request DMA channel and setup irq handler */
|
|
dma_chan = jz_request_dma(DMA_ID_AUTO, "auto", NULL, IRQF_DISABLED, NULL);
|
|
if (dma_chan < 0) {
|
|
printk("Setup irq for nand failed!\n");
|
|
return;
|
|
} else
|
|
printk("Nand DMA request channel %d.\n",dma_chan);
|
|
}
|
|
|
|
static void jz4740_nand_read_buf(struct mtd_info *mtd, uint8_t *buf, int len)
|
|
{
|
|
int i;
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
|
if ((len <= 32) || (len & 0xf) || ((u32)buf >= (u32)high_memory))
|
|
{
|
|
for (i = 0; i < len; i++)
|
|
buf[i] = readb(chip->IO_ADDR_R);
|
|
} else {
|
|
REG_DMAC_DRSR(dma_chan) = DMAC_DRSR_RS_AUTO;
|
|
dma_src_phys_addr = CPHYSADDR(chip->IO_ADDR_R);
|
|
dma_dst_phys_addr = CPHYSADDR(buf);
|
|
dma_cache_inv((u32)buf, len);
|
|
REG_DMAC_DSAR(dma_chan) = dma_src_phys_addr;
|
|
REG_DMAC_DTAR(dma_chan) = dma_dst_phys_addr;
|
|
REG_DMAC_DTCR(dma_chan) = len / 16;
|
|
REG_DMAC_DCMD(dma_chan) = DMAC_DCMD_DAI | DMAC_DCMD_SWDH_8 | DMAC_DCMD_DWDH_32 | DMAC_DCMD_DS_16BYTE;
|
|
REG_DMAC_DCCSR(dma_chan) = DMAC_DCCSR_NDES | DMAC_DCCSR_EN;
|
|
REG_DMAC_DMACR = DMAC_DMACR_DMAE; /* global DMA enable bit */
|
|
|
|
while(!(REG_DMAC_DCCSR(dma_chan) & DMAC_DCCSR_TT));
|
|
REG_DMAC_DCCSR(dma_chan) &= ~DMAC_DCCSR_EN; /* disable DMA */
|
|
__dmac_channel_clear_transmit_end(dma_chan);
|
|
}
|
|
}
|
|
|
|
static void jz4740_nand_write_buf(struct mtd_info *mtd, const uint8_t *buf, int len)
|
|
{
|
|
int i;
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
|
if ((len <= 32) || (len & 0xf) || ((u32)buf >= (u32)high_memory))
|
|
{
|
|
for (i = 0; i < len; i++)
|
|
writeb(buf[i], chip->IO_ADDR_W);
|
|
} else {
|
|
REG_DMAC_DRSR(dma_chan) = DMAC_DRSR_RS_AUTO;
|
|
dma_dst_phys_addr = CPHYSADDR(chip->IO_ADDR_R);
|
|
dma_src_phys_addr = CPHYSADDR(buf);
|
|
dma_cache_wback((unsigned long)buf, len);
|
|
REG_DMAC_DSAR(dma_chan) = dma_src_phys_addr;
|
|
REG_DMAC_DTAR(dma_chan) = dma_dst_phys_addr;
|
|
REG_DMAC_DTCR(dma_chan) = len / 16;
|
|
REG_DMAC_DCMD(dma_chan) = DMAC_DCMD_SAI | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_8 | DMAC_DCMD_DS_16BYTE ;
|
|
REG_DMAC_DCCSR(dma_chan) = DMAC_DCCSR_NDES | DMAC_DCCSR_EN;
|
|
REG_DMAC_DMACR = DMAC_DMACR_DMAE; /* global DMA enable bit */
|
|
|
|
while(!(REG_DMAC_DCCSR(dma_chan) & DMAC_DCCSR_TT));
|
|
REG_DMAC_DCCSR(dma_chan) &= ~DMAC_DCCSR_EN; /* disable DMA */
|
|
__dmac_channel_clear_transmit_end(dma_chan);
|
|
}
|
|
}
|
|
|
|
static int nand_read_page_hwecc_rs_planes(struct mtd_info *mtd, struct nand_chip *chip,
|
|
uint8_t *buf)
|
|
{
|
|
int i, eccsize = chip->ecc.size;
|
|
int eccbytes = chip->ecc.bytes;
|
|
int eccsteps = chip->ecc.steps >> 1;
|
|
uint8_t *p;
|
|
uint8_t *ecc_calc = chip->buffers->ecccalc;
|
|
uint8_t *ecc_code = chip->buffers->ecccode;
|
|
uint32_t *eccpos = chip->ecc.layout->eccpos;
|
|
uint32_t page;
|
|
uint8_t flag = 0;
|
|
int oobsize = mtd->oobsize >> 1;
|
|
int ppb = mtd->erasesize / mtd->writesize;
|
|
int ecctotal = chip->ecc.total >> 1;
|
|
|
|
page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */
|
|
|
|
/* Read first page */
|
|
chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
|
|
chip->read_buf(mtd, chip->oob_poi, oobsize);
|
|
for (i = 0; i < ecctotal; i++) {
|
|
ecc_code[i] = chip->oob_poi[eccpos[i]];
|
|
if (ecc_code[i] != 0xff) flag = 1;
|
|
}
|
|
|
|
p = buf;
|
|
chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0x00, -1);
|
|
for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
|
|
int stat;
|
|
if (flag) {
|
|
chip->ecc.hwctl(mtd, NAND_ECC_READ);
|
|
chip->read_buf(mtd, p, eccsize);
|
|
stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]);
|
|
if (stat < 0)
|
|
mtd->ecc_stats.failed++;
|
|
else
|
|
mtd->ecc_stats.corrected += stat;
|
|
}
|
|
else {
|
|
chip->ecc.hwctl(mtd, NAND_ECC_READ);
|
|
chip->read_buf(mtd, p, eccsize);
|
|
}
|
|
}
|
|
/* Read second page */
|
|
page += ppb;
|
|
flag = 0;
|
|
chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
|
|
chip->read_buf(mtd, chip->oob_poi + oobsize, oobsize);
|
|
for (i = 0; i < ecctotal; i++) {
|
|
ecc_code[i] = chip->oob_poi[oobsize + eccpos[i]];
|
|
if (ecc_code[i] != 0xff) flag = 1;
|
|
}
|
|
|
|
chip->cmdfunc(mtd, NAND_CMD_RNDOUT, 0x00, -1);
|
|
eccsteps = chip->ecc.steps >> 1;
|
|
for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
|
|
int stat;
|
|
if (flag) {
|
|
chip->ecc.hwctl(mtd, NAND_ECC_READ);
|
|
chip->read_buf(mtd, p, eccsize);
|
|
stat = chip->ecc.correct(mtd, p, &ecc_code[i], &ecc_calc[i]);
|
|
if (stat < 0)
|
|
mtd->ecc_stats.failed++;
|
|
else
|
|
mtd->ecc_stats.corrected += stat;
|
|
}
|
|
else {
|
|
chip->ecc.hwctl(mtd, NAND_ECC_READ);
|
|
chip->read_buf(mtd, p, eccsize);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int nand_read_oob_std_planes(struct mtd_info *mtd, struct nand_chip *chip,
|
|
int global_page, int sndcmd)
|
|
{
|
|
int page;
|
|
int oobsize = mtd->oobsize >> 1;
|
|
int ppb = mtd->erasesize / mtd->writesize;
|
|
|
|
page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */
|
|
|
|
/* Read first page OOB */
|
|
if (sndcmd) {
|
|
chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
|
|
}
|
|
chip->read_buf(mtd, chip->oob_poi, oobsize);
|
|
/* Read second page OOB */
|
|
page += ppb;
|
|
if (sndcmd) {
|
|
chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
|
|
sndcmd = 0;
|
|
}
|
|
chip->read_buf(mtd, chip->oob_poi+oobsize, oobsize);
|
|
return 0;
|
|
}
|
|
|
|
static int nand_write_oob_std_planes(struct mtd_info *mtd, struct nand_chip *chip,
|
|
int global_page)
|
|
{
|
|
int status = 0,page;
|
|
int pagesize = mtd->writesize >> 1;
|
|
int oobsize = mtd->oobsize >> 1;
|
|
int ppb = mtd->erasesize / mtd->writesize;
|
|
const uint8_t *buf = chip->oob_poi;
|
|
|
|
page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */
|
|
|
|
/* send cmd 0x80, the MSB should be valid if realplane is 4 */
|
|
if (chip->realplanenum == 2)
|
|
chip->cmdfunc(mtd, 0x80, pagesize, 0x00);
|
|
else
|
|
chip->cmdfunc(mtd, 0x80, pagesize, page & (1 << (chip->chip_shift - chip->page_shift)));
|
|
|
|
chip->write_buf(mtd, buf, oobsize);
|
|
/* Send first command to program the OOB data */
|
|
chip->cmdfunc(mtd, 0x11, -1, -1);
|
|
ndelay(100);
|
|
status = chip->waitfunc(mtd, chip);
|
|
|
|
page += ppb;
|
|
buf += oobsize;
|
|
chip->cmdfunc(mtd, 0x81, pagesize, page);
|
|
chip->write_buf(mtd, buf, oobsize);
|
|
/* Send command to program the OOB data */
|
|
chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
|
|
/* Wait long R/B */
|
|
ndelay(100);
|
|
status = chip->waitfunc(mtd, chip);
|
|
|
|
return status & NAND_STATUS_FAIL ? -EIO : 0;
|
|
}
|
|
|
|
static void nand_write_page_hwecc_planes(struct mtd_info *mtd, struct nand_chip *chip,
|
|
const uint8_t *buf)
|
|
{
|
|
int i, eccsize = chip->ecc.size;
|
|
int eccbytes = chip->ecc.bytes;
|
|
int eccsteps = chip->ecc.steps >> 1;
|
|
uint8_t *ecc_calc = chip->buffers->ecccalc;
|
|
uint8_t *p = (uint8_t *)buf;
|
|
uint32_t *eccpos = chip->ecc.layout->eccpos;
|
|
int oobsize = mtd->oobsize >> 1;
|
|
int ppb = mtd->erasesize / mtd->writesize;
|
|
int ecctotal = chip->ecc.total >> 1;
|
|
int page;
|
|
|
|
page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */
|
|
|
|
/* send cmd 0x80, the MSB should be valid if realplane is 4 */
|
|
if (chip->realplanenum == 2)
|
|
chip->cmdfunc(mtd, 0x80, 0x00, 0x00);
|
|
else
|
|
chip->cmdfunc(mtd, 0x80, 0x00, page & (1 << (chip->chip_shift - chip->page_shift)));
|
|
|
|
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
|
|
chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
|
|
chip->write_buf(mtd, p, eccsize);
|
|
chip->ecc.calculate(mtd, p, &ecc_calc[i]);
|
|
}
|
|
for (i = 0; i < ecctotal; i++)
|
|
chip->oob_poi[eccpos[i]] = ecc_calc[i];
|
|
|
|
chip->write_buf(mtd, chip->oob_poi, oobsize);
|
|
|
|
chip->cmdfunc(mtd, 0x11, -1, -1); /* send cmd 0x11 */
|
|
ndelay(100);
|
|
while(!chip->dev_ready(mtd));
|
|
|
|
page += ppb;
|
|
chip->cmdfunc(mtd, 0x81, 0x00, page); /* send cmd 0x81 */
|
|
eccsteps = chip->ecc.steps >> 1;
|
|
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
|
|
chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
|
|
chip->write_buf(mtd, p, eccsize);
|
|
chip->ecc.calculate(mtd, p, &ecc_calc[i]);
|
|
}
|
|
|
|
for (i = 0; i < ecctotal; i++)
|
|
chip->oob_poi[eccpos[i]] = ecc_calc[i];
|
|
|
|
chip->write_buf(mtd, chip->oob_poi, oobsize);
|
|
}
|
|
|
|
static void single_erase_cmd_planes(struct mtd_info *mtd, int global_page)
|
|
{
|
|
struct nand_chip *chip = mtd->priv;
|
|
|
|
/* Send commands to erase a block */
|
|
int page;
|
|
int ppb = mtd->erasesize / mtd->writesize;
|
|
|
|
page = (global_page / ppb) * ppb + global_page; /* = global_page%ppb + (global_page/ppb)*ppb*2 */
|
|
|
|
/* send cmd 0x60, the MSB should be valid if realplane is 4 */
|
|
if (chip->realplanenum == 2)
|
|
chip->cmdfunc(mtd, 0x60, -1, 0x00);
|
|
else
|
|
chip->cmdfunc(mtd, 0x60, -1, page & (1 << (chip->chip_shift - chip->page_shift)));
|
|
|
|
page += ppb;
|
|
chip->cmdfunc(mtd, 0x60, -1, page & (~(ppb-1))); /* send cmd 0x60 */
|
|
|
|
chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1); /* send cmd 0xd0 */
|
|
/* Do not need wait R/B or check status */
|
|
}
|
|
|
|
/*
|
|
* Main initialization routine
|
|
*/
|
|
int __init jznand_init(void)
|
|
{
|
|
struct nand_chip *this;
|
|
int nr_partitions, ret, i;
|
|
|
|
/* Allocate memory for MTD device structure and private data */
|
|
jz_mtd = kmalloc (sizeof(struct mtd_info) + sizeof (struct nand_chip),
|
|
GFP_KERNEL);
|
|
if (!jz_mtd) {
|
|
printk ("Unable to allocate JzSOC NAND MTD device structure.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
jz_mtd1 = kmalloc (sizeof(struct mtd_info) + sizeof (struct nand_chip),
|
|
GFP_KERNEL);
|
|
if (!jz_mtd1) {
|
|
printk ("Unable to allocate JzSOC NAND MTD device structure 1.\n");
|
|
kfree(jz_mtd);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Get pointer to private data */
|
|
this = (struct nand_chip *) (&jz_mtd[1]);
|
|
|
|
/* Initialize structures */
|
|
memset((char *) jz_mtd, 0, sizeof(struct mtd_info));
|
|
memset((char *) this, 0, sizeof(struct nand_chip));
|
|
|
|
/* Link the private data with the MTD structure */
|
|
jz_mtd->priv = this;
|
|
|
|
/* Set & initialize NAND Flash controller */
|
|
jz_device_setup();
|
|
|
|
/* Set address of NAND IO lines */
|
|
this->IO_ADDR_R = (void __iomem *) NAND_DATA_PORT1;
|
|
this->IO_ADDR_W = (void __iomem *) NAND_DATA_PORT1;
|
|
this->cmd_ctrl = jz_hwcontrol;
|
|
this->dev_ready = jz_device_ready;
|
|
|
|
#ifdef CONFIG_MTD_HW_HM_ECC
|
|
this->ecc.calculate = jzsoc_nand_calculate_hm_ecc;
|
|
this->ecc.correct = jzsoc_nand_hm_correct_data;
|
|
this->ecc.hwctl = jzsoc_nand_enable_hm_hwecc;
|
|
this->ecc.mode = NAND_ECC_HW;
|
|
this->ecc.size = 256;
|
|
this->ecc.bytes = 3;
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_MTD_HW_RS_ECC
|
|
this->ecc.calculate = jzsoc_nand_calculate_rs_ecc;
|
|
this->ecc.correct = jzsoc_nand_rs_correct_data;
|
|
this->ecc.hwctl = jzsoc_nand_enable_rs_hwecc;
|
|
this->ecc.mode = NAND_ECC_HW;
|
|
this->ecc.size = 512;
|
|
this->ecc.bytes = 9;
|
|
#endif
|
|
|
|
#ifdef CONFIG_MTD_SW_HM_ECC
|
|
this->ecc.mode = NAND_ECC_SOFT;
|
|
#endif
|
|
/* 20 us command delay time */
|
|
this->chip_delay = 20;
|
|
|
|
#ifdef CONFIG_MTD_NAND_DMA
|
|
dma_setup();
|
|
#endif
|
|
/* Scan to find existance of the device */
|
|
ret = nand_scan_ident(jz_mtd, NAND_MAX_CHIPS);
|
|
if (!ret) {
|
|
if (this->planenum == 2) {
|
|
/* reset nand functions */
|
|
this->erase_cmd = single_erase_cmd_planes;
|
|
this->ecc.read_page = nand_read_page_hwecc_rs_planes; //Muti planes read
|
|
this->ecc.write_page = nand_write_page_hwecc_planes;
|
|
this->ecc.read_oob = nand_read_oob_std_planes;
|
|
this->ecc.write_oob = nand_write_oob_std_planes;
|
|
#ifdef CONFIG_MTD_NAND_DMA
|
|
this->write_buf = jz4740_nand_write_buf;
|
|
this->read_buf = jz4740_nand_read_buf;
|
|
#endif
|
|
printk(KERN_INFO "Nand using two-plane mode, "
|
|
"and resized to writesize:%d oobsize:%d blocksize:0x%x \n",
|
|
jz_mtd->writesize, jz_mtd->oobsize, jz_mtd->erasesize);
|
|
}
|
|
}
|
|
|
|
/* Determine whether all the partitions will use multiple planes if supported */
|
|
nr_partitions = sizeof(partition_info) / sizeof(struct mtd_partition);
|
|
all_use_planes = 1;
|
|
for (i = 0; i < nr_partitions; i++) {
|
|
all_use_planes &= partition_info[i].use_planes;
|
|
}
|
|
|
|
if (!ret)
|
|
ret = nand_scan_tail(jz_mtd);
|
|
|
|
if (ret){
|
|
kfree (jz_mtd1);
|
|
kfree (jz_mtd);
|
|
return -ENXIO;
|
|
}
|
|
|
|
/* Register the partitions */
|
|
printk (KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nr_partitions, jz_mtd->name);
|
|
|
|
if ((this->planenum == 2) && !all_use_planes) {
|
|
for (i = 0; i < nr_partitions; i++) {
|
|
if (partition_info[i].use_planes)
|
|
add_mtd_partitions(jz_mtd, &partition_info[i], 1);
|
|
else
|
|
add_mtd_partitions(jz_mtd1, &partition_info[i], 1);
|
|
}
|
|
} else {
|
|
kfree(jz_mtd1);
|
|
add_mtd_partitions(jz_mtd, partition_info, nr_partitions);
|
|
}
|
|
return 0;
|
|
}
|
|
module_init(jznand_init);
|
|
|
|
/*
|
|
* Clean up routine
|
|
*/
|
|
#ifdef MODULE
|
|
static void __exit jznand_cleanup(void)
|
|
{
|
|
struct nand_chip *this = (struct nand_chip *) &jz_mtd[1];
|
|
|
|
/* Unregister partitions */
|
|
del_mtd_partitions(jz_mtd);
|
|
|
|
/* Unregister the device */
|
|
del_mtd_device (jz_mtd);
|
|
|
|
/* Free internal data buffers */
|
|
kfree (this->data_buf);
|
|
|
|
/* Free the MTD device structure */
|
|
if ((this->planenum == 2) && !all_use_planes)
|
|
kfree (jz_mtd1);
|
|
kfree (jz_mtd);
|
|
}
|
|
module_exit(jznand_cleanup);
|
|
#endif
|