2005-11-07 18:15:49 +07:00
|
|
|
/*
|
2005-04-17 05:20:36 +07:00
|
|
|
* drivers/mtd/nand/diskonchip.c
|
|
|
|
*
|
|
|
|
* (C) 2003 Red Hat, Inc.
|
|
|
|
* (C) 2004 Dan Brown <dan_brown@ieee.org>
|
|
|
|
* (C) 2004 Kalev Lember <kalev@smartlink.ee>
|
|
|
|
*
|
|
|
|
* Author: David Woodhouse <dwmw2@infradead.org>
|
|
|
|
* Additional Diskonchip 2000 and Millennium support by Dan Brown <dan_brown@ieee.org>
|
|
|
|
* Diskonchip Millennium Plus support by Kalev Lember <kalev@smartlink.ee>
|
2005-11-07 18:15:49 +07:00
|
|
|
*
|
2005-04-17 05:20:36 +07:00
|
|
|
* Error correction code lifted from the old docecc code
|
2005-11-07 18:15:49 +07:00
|
|
|
* Author: Fabrice Bellard (fabrice.bellard@netgem.com)
|
2005-04-17 05:20:36 +07:00
|
|
|
* Copyright (C) 2000 Netgem S.A.
|
|
|
|
* converted to the generic Reed-Solomon library by Thomas Gleixner <tglx@linutronix.de>
|
2005-11-07 18:15:49 +07:00
|
|
|
*
|
2005-04-17 05:20:36 +07:00
|
|
|
* Interface to generic NAND code for M-Systems DiskOnChip devices
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/sched.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/rslib.h>
|
|
|
|
#include <linux/moduleparam.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 15:04:11 +07:00
|
|
|
#include <linux/slab.h>
|
2015-08-11 10:07:05 +07:00
|
|
|
#include <linux/io.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
#include <linux/mtd/mtd.h>
|
|
|
|
#include <linux/mtd/nand.h>
|
|
|
|
#include <linux/mtd/doc2000.h>
|
|
|
|
#include <linux/mtd/partitions.h>
|
|
|
|
#include <linux/mtd/inftl.h>
|
2011-07-04 02:17:31 +07:00
|
|
|
#include <linux/module.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/* Where to look for the devices? */
|
2005-02-01 03:36:46 +07:00
|
|
|
#ifndef CONFIG_MTD_NAND_DISKONCHIP_PROBE_ADDRESS
|
|
|
|
#define CONFIG_MTD_NAND_DISKONCHIP_PROBE_ADDRESS 0
|
2005-04-17 05:20:36 +07:00
|
|
|
#endif
|
|
|
|
|
2013-08-12 16:40:47 +07:00
|
|
|
static unsigned long doc_locations[] __initdata = {
|
2005-04-17 05:20:36 +07:00
|
|
|
#if defined (__alpha__) || defined(__i386__) || defined(__x86_64__)
|
2005-02-01 03:36:46 +07:00
|
|
|
#ifdef CONFIG_MTD_NAND_DISKONCHIP_PROBE_HIGH
|
2005-11-07 18:15:49 +07:00
|
|
|
0xfffc8000, 0xfffca000, 0xfffcc000, 0xfffce000,
|
2005-04-17 05:20:36 +07:00
|
|
|
0xfffd0000, 0xfffd2000, 0xfffd4000, 0xfffd6000,
|
2005-11-07 18:15:49 +07:00
|
|
|
0xfffd8000, 0xfffda000, 0xfffdc000, 0xfffde000,
|
|
|
|
0xfffe0000, 0xfffe2000, 0xfffe4000, 0xfffe6000,
|
2005-04-17 05:20:36 +07:00
|
|
|
0xfffe8000, 0xfffea000, 0xfffec000, 0xfffee000,
|
2013-07-08 11:39:20 +07:00
|
|
|
#else
|
2005-11-07 18:15:49 +07:00
|
|
|
0xc8000, 0xca000, 0xcc000, 0xce000,
|
2005-04-17 05:20:36 +07:00
|
|
|
0xd0000, 0xd2000, 0xd4000, 0xd6000,
|
2005-11-07 18:15:49 +07:00
|
|
|
0xd8000, 0xda000, 0xdc000, 0xde000,
|
|
|
|
0xe0000, 0xe2000, 0xe4000, 0xe6000,
|
2005-04-17 05:20:36 +07:00
|
|
|
0xe8000, 0xea000, 0xec000, 0xee000,
|
2013-07-08 11:39:20 +07:00
|
|
|
#endif
|
2005-04-17 05:20:36 +07:00
|
|
|
#endif
|
|
|
|
0xffffffff };
|
|
|
|
|
|
|
|
static struct mtd_info *doclist = NULL;
|
|
|
|
|
|
|
|
struct doc_priv {
|
|
|
|
void __iomem *virtadr;
|
|
|
|
unsigned long physadr;
|
|
|
|
u_char ChipID;
|
|
|
|
u_char CDSNControl;
|
2006-05-14 00:07:53 +07:00
|
|
|
int chips_per_floor; /* The number of chips detected on each floor */
|
2005-04-17 05:20:36 +07:00
|
|
|
int curfloor;
|
|
|
|
int curchip;
|
|
|
|
int mh0_page;
|
|
|
|
int mh1_page;
|
|
|
|
struct mtd_info *nextdoc;
|
2015-02-28 17:13:10 +07:00
|
|
|
|
|
|
|
/* Handle the last stage of initialization (BBT scan, partitioning) */
|
|
|
|
int (*late_init)(struct mtd_info *mtd);
|
2005-04-17 05:20:36 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
/* This is the syndrome computed by the HW ecc generator upon reading an empty
|
|
|
|
page, one with all 0xff for data and stored ecc code. */
|
|
|
|
static u_char empty_read_syndrome[6] = { 0x26, 0xff, 0x6d, 0x47, 0x73, 0x7a };
|
2006-05-14 00:07:53 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/* This is the ecc value computed by the HW ecc generator upon writing an empty
|
|
|
|
page, one with all 0xff for data. */
|
|
|
|
static u_char empty_write_ecc[6] = { 0x4b, 0x00, 0xe2, 0x0e, 0x93, 0xf7 };
|
|
|
|
|
|
|
|
#define INFTL_BBT_RESERVED_BLOCKS 4
|
|
|
|
|
|
|
|
#define DoC_is_MillenniumPlus(doc) ((doc)->ChipID == DOC_ChipID_DocMilPlus16 || (doc)->ChipID == DOC_ChipID_DocMilPlus32)
|
|
|
|
#define DoC_is_Millennium(doc) ((doc)->ChipID == DOC_ChipID_DocMil)
|
|
|
|
#define DoC_is_2000(doc) ((doc)->ChipID == DOC_ChipID_Doc2k)
|
|
|
|
|
2006-05-24 04:25:53 +07:00
|
|
|
static void doc200x_hwcontrol(struct mtd_info *mtd, int cmd,
|
|
|
|
unsigned int bitmask);
|
2005-04-17 05:20:36 +07:00
|
|
|
static void doc200x_select_chip(struct mtd_info *mtd, int chip);
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
static int debug = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
module_param(debug, int, 0);
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
static int try_dword = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
module_param(try_dword, int, 0);
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
static int no_ecc_failures = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
module_param(no_ecc_failures, int, 0);
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
static int no_autopart = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
module_param(no_autopart, int, 0);
|
2005-03-30 03:57:48 +07:00
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
static int show_firmware_partition = 0;
|
2005-03-30 03:57:48 +07:00
|
|
|
module_param(show_firmware_partition, int, 0);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2007-03-08 06:33:25 +07:00
|
|
|
#ifdef CONFIG_MTD_NAND_DISKONCHIP_BBTWRITE
|
2006-05-14 00:07:53 +07:00
|
|
|
static int inftl_bbt_write = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
#else
|
2006-05-14 00:07:53 +07:00
|
|
|
static int inftl_bbt_write = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
#endif
|
|
|
|
module_param(inftl_bbt_write, int, 0);
|
|
|
|
|
2005-02-01 03:36:46 +07:00
|
|
|
static unsigned long doc_config_location = CONFIG_MTD_NAND_DISKONCHIP_PROBE_ADDRESS;
|
2005-04-17 05:20:36 +07:00
|
|
|
module_param(doc_config_location, ulong, 0);
|
|
|
|
MODULE_PARM_DESC(doc_config_location, "Physical memory address at which to probe for DiskOnChip");
|
|
|
|
|
|
|
|
/* Sector size for HW ECC */
|
|
|
|
#define SECTOR_SIZE 512
|
|
|
|
/* The sector bytes are packed into NB_DATA 10 bit words */
|
|
|
|
#define NB_DATA (((SECTOR_SIZE + 1) * 8 + 6) / 10)
|
|
|
|
/* Number of roots */
|
|
|
|
#define NROOTS 4
|
|
|
|
/* First consective root */
|
|
|
|
#define FCR 510
|
|
|
|
/* Number of symbols */
|
|
|
|
#define NN 1023
|
|
|
|
|
|
|
|
/* the Reed Solomon control structure */
|
|
|
|
static struct rs_control *rs_decoder;
|
|
|
|
|
2005-11-07 18:15:49 +07:00
|
|
|
/*
|
2005-04-17 05:20:36 +07:00
|
|
|
* The HW decoder in the DoC ASIC's provides us a error syndrome,
|
2011-06-24 04:12:08 +07:00
|
|
|
* which we must convert to a standard syndrome usable by the generic
|
2005-04-17 05:20:36 +07:00
|
|
|
* Reed-Solomon library code.
|
|
|
|
*
|
|
|
|
* Fabrice Bellard figured this out in the old docecc code. I added
|
|
|
|
* some comments, improved a minor bit and converted it to make use
|
2011-03-31 08:57:33 +07:00
|
|
|
* of the generic Reed-Solomon library. tglx
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2006-05-14 00:07:53 +07:00
|
|
|
static int doc_ecc_decode(struct rs_control *rs, uint8_t *data, uint8_t *ecc)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
int i, j, nerr, errpos[8];
|
|
|
|
uint8_t parity;
|
|
|
|
uint16_t ds[4], s[5], tmp, errval[8], syn[4];
|
|
|
|
|
2010-05-27 08:45:39 +07:00
|
|
|
memset(syn, 0, sizeof(syn));
|
2005-04-17 05:20:36 +07:00
|
|
|
/* Convert the ecc bytes into words */
|
|
|
|
ds[0] = ((ecc[4] & 0xff) >> 0) | ((ecc[5] & 0x03) << 8);
|
|
|
|
ds[1] = ((ecc[5] & 0xfc) >> 2) | ((ecc[2] & 0x0f) << 6);
|
|
|
|
ds[2] = ((ecc[2] & 0xf0) >> 4) | ((ecc[3] & 0x3f) << 4);
|
|
|
|
ds[3] = ((ecc[3] & 0xc0) >> 6) | ((ecc[0] & 0xff) << 2);
|
|
|
|
parity = ecc[1];
|
|
|
|
|
2011-06-24 04:12:08 +07:00
|
|
|
/* Initialize the syndrome buffer */
|
2005-04-17 05:20:36 +07:00
|
|
|
for (i = 0; i < NROOTS; i++)
|
|
|
|
s[i] = ds[0];
|
2005-11-07 18:15:49 +07:00
|
|
|
/*
|
|
|
|
* Evaluate
|
2005-04-17 05:20:36 +07:00
|
|
|
* s[i] = ds[3]x^3 + ds[2]x^2 + ds[1]x^1 + ds[0]
|
|
|
|
* where x = alpha^(FCR + i)
|
|
|
|
*/
|
2006-05-14 00:07:53 +07:00
|
|
|
for (j = 1; j < NROOTS; j++) {
|
|
|
|
if (ds[j] == 0)
|
2005-04-17 05:20:36 +07:00
|
|
|
continue;
|
|
|
|
tmp = rs->index_of[ds[j]];
|
2006-05-14 00:07:53 +07:00
|
|
|
for (i = 0; i < NROOTS; i++)
|
2005-04-17 05:20:36 +07:00
|
|
|
s[i] ^= rs->alpha_to[rs_modnn(rs, tmp + (FCR + i) * j)];
|
|
|
|
}
|
|
|
|
|
2010-05-27 08:45:39 +07:00
|
|
|
/* Calc syn[i] = s[i] / alpha^(v + i) */
|
2005-04-17 05:20:36 +07:00
|
|
|
for (i = 0; i < NROOTS; i++) {
|
2010-05-27 08:45:39 +07:00
|
|
|
if (s[i])
|
2006-05-14 00:07:53 +07:00
|
|
|
syn[i] = rs_modnn(rs, rs->index_of[s[i]] + (NN - FCR - i));
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
/* Call the decoder library */
|
|
|
|
nerr = decode_rs16(rs, NULL, NULL, 1019, syn, 0, errpos, 0, errval);
|
|
|
|
|
|
|
|
/* Incorrectable errors ? */
|
|
|
|
if (nerr < 0)
|
|
|
|
return nerr;
|
|
|
|
|
2005-11-07 18:15:49 +07:00
|
|
|
/*
|
2005-04-17 05:20:36 +07:00
|
|
|
* Correct the errors. The bitpositions are a bit of magic,
|
|
|
|
* but they are given by the design of the de/encoder circuit
|
|
|
|
* in the DoC ASIC's.
|
|
|
|
*/
|
2006-05-14 00:07:53 +07:00
|
|
|
for (i = 0; i < nerr; i++) {
|
2005-04-17 05:20:36 +07:00
|
|
|
int index, bitpos, pos = 1015 - errpos[i];
|
|
|
|
uint8_t val;
|
|
|
|
if (pos >= NB_DATA && pos < 1019)
|
|
|
|
continue;
|
|
|
|
if (pos < NB_DATA) {
|
|
|
|
/* extract bit position (MSB first) */
|
|
|
|
pos = 10 * (NB_DATA - 1 - pos) - 6;
|
|
|
|
/* now correct the following 10 bits. At most two bytes
|
|
|
|
can be modified since pos is even */
|
|
|
|
index = (pos >> 3) ^ 1;
|
|
|
|
bitpos = pos & 7;
|
2006-05-14 00:07:53 +07:00
|
|
|
if ((index >= 0 && index < SECTOR_SIZE) || index == (SECTOR_SIZE + 1)) {
|
2005-04-17 05:20:36 +07:00
|
|
|
val = (uint8_t) (errval[i] >> (2 + bitpos));
|
|
|
|
parity ^= val;
|
|
|
|
if (index < SECTOR_SIZE)
|
|
|
|
data[index] ^= val;
|
|
|
|
}
|
|
|
|
index = ((pos >> 3) + 1) ^ 1;
|
|
|
|
bitpos = (bitpos + 10) & 7;
|
|
|
|
if (bitpos == 0)
|
|
|
|
bitpos = 8;
|
2006-05-14 00:07:53 +07:00
|
|
|
if ((index >= 0 && index < SECTOR_SIZE) || index == (SECTOR_SIZE + 1)) {
|
|
|
|
val = (uint8_t) (errval[i] << (8 - bitpos));
|
2005-04-17 05:20:36 +07:00
|
|
|
parity ^= val;
|
|
|
|
if (index < SECTOR_SIZE)
|
|
|
|
data[index] ^= val;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* If the parity is wrong, no rescue possible */
|
2007-10-21 04:16:32 +07:00
|
|
|
return parity ? -EBADMSG : nerr;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void DoC_Delay(struct doc_priv *doc, unsigned short cycles)
|
|
|
|
{
|
|
|
|
volatile char dummy;
|
|
|
|
int i;
|
2005-11-07 18:15:49 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
for (i = 0; i < cycles; i++) {
|
|
|
|
if (DoC_is_Millennium(doc))
|
|
|
|
dummy = ReadDOC(doc->virtadr, NOP);
|
|
|
|
else if (DoC_is_MillenniumPlus(doc))
|
|
|
|
dummy = ReadDOC(doc->virtadr, Mplus_NOP);
|
|
|
|
else
|
|
|
|
dummy = ReadDOC(doc->virtadr, DOCStatus);
|
|
|
|
}
|
2005-11-07 18:15:49 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
#define CDSN_CTRL_FR_B_MASK (CDSN_CTRL_FR_B0 | CDSN_CTRL_FR_B1)
|
|
|
|
|
|
|
|
/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */
|
|
|
|
static int _DoC_WaitReady(struct doc_priv *doc)
|
|
|
|
{
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned long timeo = jiffies + (HZ * 10);
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("_DoC_WaitReady...\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
/* Out-of-line routine to wait for chip response */
|
|
|
|
if (DoC_is_MillenniumPlus(doc)) {
|
|
|
|
while ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) {
|
|
|
|
if (time_after(jiffies, timeo)) {
|
|
|
|
printk("_DoC_WaitReady timed out.\n");
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
udelay(1);
|
|
|
|
cond_resched();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
while (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) {
|
|
|
|
if (time_after(jiffies, timeo)) {
|
|
|
|
printk("_DoC_WaitReady timed out.\n");
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
udelay(1);
|
|
|
|
cond_resched();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int DoC_WaitReady(struct doc_priv *doc)
|
|
|
|
{
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (DoC_is_MillenniumPlus(doc)) {
|
|
|
|
DoC_Delay(doc, 4);
|
|
|
|
|
|
|
|
if ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK)
|
|
|
|
/* Call the out-of-line routine to wait */
|
|
|
|
ret = _DoC_WaitReady(doc);
|
|
|
|
} else {
|
|
|
|
DoC_Delay(doc, 4);
|
|
|
|
|
|
|
|
if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B))
|
|
|
|
/* Call the out-of-line routine to wait */
|
|
|
|
ret = _DoC_WaitReady(doc);
|
|
|
|
DoC_Delay(doc, 2);
|
|
|
|
}
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("DoC_WaitReady OK\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void doc2000_write_byte(struct mtd_info *mtd, u_char datum)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("write_byte %02x\n", datum);
|
2005-04-17 05:20:36 +07:00
|
|
|
WriteDOC(datum, docptr, CDSNSlowIO);
|
|
|
|
WriteDOC(datum, docptr, 2k_CDSN_IO);
|
|
|
|
}
|
|
|
|
|
|
|
|
static u_char doc2000_read_byte(struct mtd_info *mtd)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
u_char ret;
|
|
|
|
|
|
|
|
ReadDOC(docptr, CDSNSlowIO);
|
|
|
|
DoC_Delay(doc, 2);
|
|
|
|
ret = ReadDOC(docptr, 2k_CDSN_IO);
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("read_byte returns %02x\n", ret);
|
2005-04-17 05:20:36 +07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
static void doc2000_writebuf(struct mtd_info *mtd, const u_char *buf, int len)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
int i;
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("writebuf of %d bytes: ", len);
|
|
|
|
for (i = 0; i < len; i++) {
|
2005-04-17 05:20:36 +07:00
|
|
|
WriteDOC_(buf[i], docptr, DoC_2k_CDSN_IO + i);
|
|
|
|
if (debug && i < 16)
|
|
|
|
printk("%02x ", buf[i]);
|
|
|
|
}
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
static void doc2000_readbuf(struct mtd_info *mtd, u_char *buf, int len)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
|
|
|
int i;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("readbuf of %d bytes: ", len);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
for (i = 0; i < len; i++) {
|
2005-04-17 05:20:36 +07:00
|
|
|
buf[i] = ReadDOC(docptr, 2k_CDSN_IO + i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
static void doc2000_readbuf_dword(struct mtd_info *mtd, u_char *buf, int len)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
|
|
|
int i;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("readbuf_dword of %d bytes: ", len);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
if (unlikely((((unsigned long)buf) | len) & 3)) {
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
*(uint8_t *) (&buf[i]) = ReadDOC(docptr, 2k_CDSN_IO + i);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
} else {
|
2006-05-14 00:07:53 +07:00
|
|
|
for (i = 0; i < len; i += 4) {
|
|
|
|
*(uint32_t *) (&buf[i]) = readl(docptr + DoC_2k_CDSN_IO + i);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint16_t __init doc200x_ident_chip(struct mtd_info *mtd, int nr)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
|
|
|
uint16_t ret;
|
|
|
|
|
|
|
|
doc200x_select_chip(mtd, nr);
|
2006-05-24 04:25:53 +07:00
|
|
|
doc200x_hwcontrol(mtd, NAND_CMD_READID,
|
|
|
|
NAND_CTRL_CLE | NAND_CTRL_CHANGE);
|
|
|
|
doc200x_hwcontrol(mtd, 0, NAND_CTRL_ALE | NAND_CTRL_CHANGE);
|
|
|
|
doc200x_hwcontrol(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
|
2005-11-07 18:15:49 +07:00
|
|
|
|
2011-04-27 13:28:26 +07:00
|
|
|
/* We can't use dev_ready here, but at least we wait for the
|
2005-11-07 18:15:49 +07:00
|
|
|
* command to complete
|
2005-02-23 04:48:25 +07:00
|
|
|
*/
|
|
|
|
udelay(50);
|
2005-11-07 18:15:49 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
ret = this->read_byte(mtd) << 8;
|
|
|
|
ret |= this->read_byte(mtd);
|
|
|
|
|
|
|
|
if (doc->ChipID == DOC_ChipID_Doc2k && try_dword && !nr) {
|
|
|
|
/* First chip probe. See if we get same results by 32-bit access */
|
|
|
|
union {
|
|
|
|
uint32_t dword;
|
|
|
|
uint8_t byte[4];
|
|
|
|
} ident;
|
|
|
|
void __iomem *docptr = doc->virtadr;
|
|
|
|
|
2006-05-24 04:25:53 +07:00
|
|
|
doc200x_hwcontrol(mtd, NAND_CMD_READID,
|
|
|
|
NAND_CTRL_CLE | NAND_CTRL_CHANGE);
|
|
|
|
doc200x_hwcontrol(mtd, 0, NAND_CTRL_ALE | NAND_CTRL_CHANGE);
|
|
|
|
doc200x_hwcontrol(mtd, NAND_CMD_NONE,
|
|
|
|
NAND_NCE | NAND_CTRL_CHANGE);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2005-02-23 04:48:25 +07:00
|
|
|
udelay(50);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
ident.dword = readl(docptr + DoC_2k_CDSN_IO);
|
|
|
|
if (((ident.byte[0] << 8) | ident.byte[1]) == ret) {
|
|
|
|
printk(KERN_INFO "DiskOnChip 2000 responds to DWORD access\n");
|
|
|
|
this->read_buf = &doc2000_readbuf_dword;
|
|
|
|
}
|
|
|
|
}
|
2005-11-07 18:15:49 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __init doc2000_count_chips(struct mtd_info *mtd)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
|
|
|
uint16_t mfrid;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* Max 4 chips per floor on DiskOnChip 2000 */
|
|
|
|
doc->chips_per_floor = 4;
|
|
|
|
|
|
|
|
/* Find out what the first chip is */
|
|
|
|
mfrid = doc200x_ident_chip(mtd, 0);
|
|
|
|
|
|
|
|
/* Find how many chips in each floor. */
|
|
|
|
for (i = 1; i < 4; i++) {
|
|
|
|
if (doc200x_ident_chip(mtd, i) != mfrid)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
doc->chips_per_floor = i;
|
|
|
|
printk(KERN_DEBUG "Detected %d chips per floor.\n", i);
|
|
|
|
}
|
|
|
|
|
2006-06-21 01:05:05 +07:00
|
|
|
static int doc200x_wait(struct mtd_info *mtd, struct nand_chip *this)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct doc_priv *doc = this->priv;
|
|
|
|
|
|
|
|
int status;
|
2005-11-07 18:15:49 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
DoC_WaitReady(doc);
|
|
|
|
this->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
|
|
|
|
DoC_WaitReady(doc);
|
|
|
|
status = (int)this->read_byte(mtd);
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void doc2001_write_byte(struct mtd_info *mtd, u_char datum)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
WriteDOC(datum, docptr, CDSNSlowIO);
|
|
|
|
WriteDOC(datum, docptr, Mil_CDSN_IO);
|
|
|
|
WriteDOC(datum, docptr, WritePipeTerm);
|
|
|
|
}
|
|
|
|
|
|
|
|
static u_char doc2001_read_byte(struct mtd_info *mtd)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
//ReadDOC(docptr, CDSNSlowIO);
|
|
|
|
/* 11.4.5 -- delay twice to allow extended length cycle */
|
|
|
|
DoC_Delay(doc, 2);
|
|
|
|
ReadDOC(docptr, ReadPipeInit);
|
|
|
|
//return ReadDOC(docptr, Mil_CDSN_IO);
|
|
|
|
return ReadDOC(docptr, LastDataRead);
|
|
|
|
}
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
static void doc2001_writebuf(struct mtd_info *mtd, const u_char *buf, int len)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
int i;
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
for (i = 0; i < len; i++)
|
2005-04-17 05:20:36 +07:00
|
|
|
WriteDOC_(buf[i], docptr, DoC_Mil_CDSN_IO + i);
|
|
|
|
/* Terminate write pipeline */
|
|
|
|
WriteDOC(0x00, docptr, WritePipeTerm);
|
|
|
|
}
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
static void doc2001_readbuf(struct mtd_info *mtd, u_char *buf, int len)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
int i;
|
|
|
|
|
|
|
|
/* Start read pipeline */
|
|
|
|
ReadDOC(docptr, ReadPipeInit);
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
for (i = 0; i < len - 1; i++)
|
2005-04-17 05:20:36 +07:00
|
|
|
buf[i] = ReadDOC(docptr, Mil_CDSN_IO + (i & 0xff));
|
|
|
|
|
|
|
|
/* Terminate read pipeline */
|
|
|
|
buf[i] = ReadDOC(docptr, LastDataRead);
|
|
|
|
}
|
|
|
|
|
|
|
|
static u_char doc2001plus_read_byte(struct mtd_info *mtd)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
u_char ret;
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
ReadDOC(docptr, Mplus_ReadPipeInit);
|
|
|
|
ReadDOC(docptr, Mplus_ReadPipeInit);
|
|
|
|
ret = ReadDOC(docptr, Mplus_LastDataRead);
|
|
|
|
if (debug)
|
|
|
|
printk("read_byte returns %02x\n", ret);
|
2005-04-17 05:20:36 +07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
static void doc2001plus_writebuf(struct mtd_info *mtd, const u_char *buf, int len)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
int i;
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("writebuf of %d bytes: ", len);
|
|
|
|
for (i = 0; i < len; i++) {
|
2005-04-17 05:20:36 +07:00
|
|
|
WriteDOC_(buf[i], docptr, DoC_Mil_CDSN_IO + i);
|
|
|
|
if (debug && i < 16)
|
|
|
|
printk("%02x ", buf[i]);
|
|
|
|
}
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
static void doc2001plus_readbuf(struct mtd_info *mtd, u_char *buf, int len)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
int i;
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("readbuf of %d bytes: ", len);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/* Start read pipeline */
|
|
|
|
ReadDOC(docptr, Mplus_ReadPipeInit);
|
|
|
|
ReadDOC(docptr, Mplus_ReadPipeInit);
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
for (i = 0; i < len - 2; i++) {
|
2005-04-17 05:20:36 +07:00
|
|
|
buf[i] = ReadDOC(docptr, Mil_CDSN_IO);
|
|
|
|
if (debug && i < 16)
|
|
|
|
printk("%02x ", buf[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Terminate read pipeline */
|
2006-05-14 00:07:53 +07:00
|
|
|
buf[len - 2] = ReadDOC(docptr, Mplus_LastDataRead);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (debug && i < 16)
|
2006-05-14 00:07:53 +07:00
|
|
|
printk("%02x ", buf[len - 2]);
|
|
|
|
buf[len - 1] = ReadDOC(docptr, Mplus_LastDataRead);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (debug && i < 16)
|
2006-05-14 00:07:53 +07:00
|
|
|
printk("%02x ", buf[len - 1]);
|
|
|
|
if (debug)
|
|
|
|
printk("\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void doc2001plus_select_chip(struct mtd_info *mtd, int chip)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
int floor = 0;
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("select chip (%d)\n", chip);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (chip == -1) {
|
|
|
|
/* Disable flash internally */
|
|
|
|
WriteDOC(0, docptr, Mplus_FlashSelect);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
floor = chip / doc->chips_per_floor;
|
2006-05-14 00:07:53 +07:00
|
|
|
chip -= (floor * doc->chips_per_floor);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/* Assert ChipEnable and deassert WriteProtect */
|
|
|
|
WriteDOC((DOC_FLASH_CE), docptr, Mplus_FlashSelect);
|
|
|
|
this->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
|
|
|
|
|
|
|
|
doc->curchip = chip;
|
|
|
|
doc->curfloor = floor;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void doc200x_select_chip(struct mtd_info *mtd, int chip)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
int floor = 0;
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("select chip (%d)\n", chip);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (chip == -1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
floor = chip / doc->chips_per_floor;
|
2006-05-14 00:07:53 +07:00
|
|
|
chip -= (floor * doc->chips_per_floor);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/* 11.4.4 -- deassert CE before changing chip */
|
2006-05-24 04:25:53 +07:00
|
|
|
doc200x_hwcontrol(mtd, NAND_CMD_NONE, 0 | NAND_CTRL_CHANGE);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
WriteDOC(floor, docptr, FloorSelect);
|
|
|
|
WriteDOC(chip, docptr, CDSNDeviceSelect);
|
|
|
|
|
2006-05-24 04:25:53 +07:00
|
|
|
doc200x_hwcontrol(mtd, NAND_CMD_NONE, NAND_NCE | NAND_CTRL_CHANGE);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
doc->curchip = chip;
|
|
|
|
doc->curfloor = floor;
|
|
|
|
}
|
|
|
|
|
2006-05-24 04:25:53 +07:00
|
|
|
#define CDSN_CTRL_MSK (CDSN_CTRL_CE | CDSN_CTRL_CLE | CDSN_CTRL_ALE)
|
|
|
|
|
|
|
|
static void doc200x_hwcontrol(struct mtd_info *mtd, int cmd,
|
|
|
|
unsigned int ctrl)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-05-24 04:25:53 +07:00
|
|
|
if (ctrl & NAND_CTRL_CHANGE) {
|
|
|
|
doc->CDSNControl &= ~CDSN_CTRL_MSK;
|
|
|
|
doc->CDSNControl |= ctrl & CDSN_CTRL_MSK;
|
|
|
|
if (debug)
|
|
|
|
printk("hwcontrol(%d): %02x\n", cmd, doc->CDSNControl);
|
|
|
|
WriteDOC(doc->CDSNControl, docptr, CDSNControl);
|
|
|
|
/* 11.4.3 -- 4 NOPs after CSDNControl write */
|
|
|
|
DoC_Delay(doc, 4);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2006-05-24 04:28:48 +07:00
|
|
|
if (cmd != NAND_CMD_NONE) {
|
|
|
|
if (DoC_is_2000(doc))
|
|
|
|
doc2000_write_byte(mtd, cmd);
|
|
|
|
else
|
|
|
|
doc2001_write_byte(mtd, cmd);
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
static void doc2001plus_command(struct mtd_info *mtd, unsigned command, int column, int page_addr)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Must terminate write pipeline before sending any commands
|
|
|
|
* to the device.
|
|
|
|
*/
|
|
|
|
if (command == NAND_CMD_PAGEPROG) {
|
|
|
|
WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
|
|
|
|
WriteDOC(0x00, docptr, Mplus_WritePipeTerm);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Write out the command to the device.
|
|
|
|
*/
|
|
|
|
if (command == NAND_CMD_SEQIN) {
|
|
|
|
int readcmd;
|
|
|
|
|
2006-05-23 04:18:05 +07:00
|
|
|
if (column >= mtd->writesize) {
|
2005-04-17 05:20:36 +07:00
|
|
|
/* OOB area */
|
2006-05-23 04:18:05 +07:00
|
|
|
column -= mtd->writesize;
|
2005-04-17 05:20:36 +07:00
|
|
|
readcmd = NAND_CMD_READOOB;
|
|
|
|
} else if (column < 256) {
|
|
|
|
/* First 256 bytes --> READ0 */
|
|
|
|
readcmd = NAND_CMD_READ0;
|
|
|
|
} else {
|
|
|
|
column -= 256;
|
|
|
|
readcmd = NAND_CMD_READ1;
|
|
|
|
}
|
|
|
|
WriteDOC(readcmd, docptr, Mplus_FlashCmd);
|
|
|
|
}
|
|
|
|
WriteDOC(command, docptr, Mplus_FlashCmd);
|
|
|
|
WriteDOC(0, docptr, Mplus_WritePipeTerm);
|
|
|
|
WriteDOC(0, docptr, Mplus_WritePipeTerm);
|
|
|
|
|
|
|
|
if (column != -1 || page_addr != -1) {
|
|
|
|
/* Serially input address */
|
|
|
|
if (column != -1) {
|
|
|
|
/* Adjust columns for 16 bit buswidth */
|
mtd: nand: force NAND_CMD_READID onto 8-bit bus
The NAND command helpers tend to automatically shift the column address
for x16 bus devices, since most commands expect a word address, not a
byte address. The Read ID command, however, expects an 8-bit address
(i.e., 0x00, 0x20, or 0x40 should not be translated to 0x00, 0x10, or
0x20).
This fixes the column address for a few drivers which imitate the
nand_base defaults. Note that I don't touch sh_flctl.c, since it already
handles this problem slightly differently (note its comment "READID is
always performed using an 8-bit bus").
I have not tested this patch, as I only have x8 parts up for testing at
this point. Hopefully that can change soon...
Signed-off-by: Brian Norris <computersforpeace@gmail.com>
Tested-by: Ezequiel Garcia <ezequiel.garcia@free-electrons.com>
Tested-By: Pekon Gupta <pekon@ti.com>
2014-01-30 05:08:12 +07:00
|
|
|
if (this->options & NAND_BUSWIDTH_16 &&
|
|
|
|
!nand_opcode_8bits(command))
|
2005-04-17 05:20:36 +07:00
|
|
|
column >>= 1;
|
|
|
|
WriteDOC(column, docptr, Mplus_FlashAddress);
|
|
|
|
}
|
|
|
|
if (page_addr != -1) {
|
2006-05-14 00:07:53 +07:00
|
|
|
WriteDOC((unsigned char)(page_addr & 0xff), docptr, Mplus_FlashAddress);
|
|
|
|
WriteDOC((unsigned char)((page_addr >> 8) & 0xff), docptr, Mplus_FlashAddress);
|
2005-04-17 05:20:36 +07:00
|
|
|
/* One more address cycle for higher density devices */
|
|
|
|
if (this->chipsize & 0x0c000000) {
|
2006-05-14 00:07:53 +07:00
|
|
|
WriteDOC((unsigned char)((page_addr >> 16) & 0x0f), docptr, Mplus_FlashAddress);
|
2005-04-17 05:20:36 +07:00
|
|
|
printk("high density\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
WriteDOC(0, docptr, Mplus_WritePipeTerm);
|
|
|
|
WriteDOC(0, docptr, Mplus_WritePipeTerm);
|
|
|
|
/* deassert ALE */
|
2006-05-14 00:07:53 +07:00
|
|
|
if (command == NAND_CMD_READ0 || command == NAND_CMD_READ1 ||
|
|
|
|
command == NAND_CMD_READOOB || command == NAND_CMD_READID)
|
2005-04-17 05:20:36 +07:00
|
|
|
WriteDOC(0, docptr, Mplus_FlashControl);
|
|
|
|
}
|
|
|
|
|
2005-11-07 18:15:49 +07:00
|
|
|
/*
|
2005-04-17 05:20:36 +07:00
|
|
|
* program and erase have their own busy handlers
|
|
|
|
* status and sequential in needs no delay
|
2006-05-14 00:07:53 +07:00
|
|
|
*/
|
2005-04-17 05:20:36 +07:00
|
|
|
switch (command) {
|
|
|
|
|
|
|
|
case NAND_CMD_PAGEPROG:
|
|
|
|
case NAND_CMD_ERASE1:
|
|
|
|
case NAND_CMD_ERASE2:
|
|
|
|
case NAND_CMD_SEQIN:
|
|
|
|
case NAND_CMD_STATUS:
|
|
|
|
return;
|
|
|
|
|
|
|
|
case NAND_CMD_RESET:
|
|
|
|
if (this->dev_ready)
|
|
|
|
break;
|
|
|
|
udelay(this->chip_delay);
|
|
|
|
WriteDOC(NAND_CMD_STATUS, docptr, Mplus_FlashCmd);
|
|
|
|
WriteDOC(0, docptr, Mplus_WritePipeTerm);
|
|
|
|
WriteDOC(0, docptr, Mplus_WritePipeTerm);
|
2006-05-14 00:07:53 +07:00
|
|
|
while (!(this->read_byte(mtd) & 0x40)) ;
|
2005-04-17 05:20:36 +07:00
|
|
|
return;
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
/* This applies to read commands */
|
2005-04-17 05:20:36 +07:00
|
|
|
default:
|
2005-11-07 18:15:49 +07:00
|
|
|
/*
|
2005-04-17 05:20:36 +07:00
|
|
|
* If we don't have access to the busy pin, we apply the given
|
|
|
|
* command delay
|
2006-05-14 00:07:53 +07:00
|
|
|
*/
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!this->dev_ready) {
|
2006-05-14 00:07:53 +07:00
|
|
|
udelay(this->chip_delay);
|
2005-04-17 05:20:36 +07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Apply this short delay always to ensure that we do wait tWB in
|
|
|
|
* any case on any machine. */
|
2006-05-14 00:07:53 +07:00
|
|
|
ndelay(100);
|
2005-04-17 05:20:36 +07:00
|
|
|
/* wait until command is processed */
|
2006-05-14 00:07:53 +07:00
|
|
|
while (!this->dev_ready(mtd)) ;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int doc200x_dev_ready(struct mtd_info *mtd)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (DoC_is_MillenniumPlus(doc)) {
|
|
|
|
/* 11.4.2 -- must NOP four times before checking FR/B# */
|
|
|
|
DoC_Delay(doc, 4);
|
|
|
|
if ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) {
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
2005-04-17 05:20:36 +07:00
|
|
|
printk("not ready\n");
|
|
|
|
return 0;
|
|
|
|
}
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("was ready\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
/* 11.4.2 -- must NOP four times before checking FR/B# */
|
|
|
|
DoC_Delay(doc, 4);
|
|
|
|
if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) {
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
2005-04-17 05:20:36 +07:00
|
|
|
printk("not ready\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* 11.4.2 -- Must NOP twice if it's ready */
|
|
|
|
DoC_Delay(doc, 2);
|
2006-05-14 00:07:53 +07:00
|
|
|
if (debug)
|
|
|
|
printk("was ready\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int doc200x_block_bad(struct mtd_info *mtd, loff_t ofs, int getchip)
|
|
|
|
{
|
|
|
|
/* This is our last resort if we couldn't find or create a BBT. Just
|
|
|
|
pretend all blocks are good. */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void doc200x_enable_hwecc(struct mtd_info *mtd, int mode)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/* Prime the ECC engine */
|
2006-05-14 00:07:53 +07:00
|
|
|
switch (mode) {
|
2005-04-17 05:20:36 +07:00
|
|
|
case NAND_ECC_READ:
|
|
|
|
WriteDOC(DOC_ECC_RESET, docptr, ECCConf);
|
|
|
|
WriteDOC(DOC_ECC_EN, docptr, ECCConf);
|
|
|
|
break;
|
|
|
|
case NAND_ECC_WRITE:
|
|
|
|
WriteDOC(DOC_ECC_RESET, docptr, ECCConf);
|
|
|
|
WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void doc2001plus_enable_hwecc(struct mtd_info *mtd, int mode)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/* Prime the ECC engine */
|
2006-05-14 00:07:53 +07:00
|
|
|
switch (mode) {
|
2005-04-17 05:20:36 +07:00
|
|
|
case NAND_ECC_READ:
|
|
|
|
WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
|
|
|
|
WriteDOC(DOC_ECC_EN, docptr, Mplus_ECCConf);
|
|
|
|
break;
|
|
|
|
case NAND_ECC_WRITE:
|
|
|
|
WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf);
|
|
|
|
WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, Mplus_ECCConf);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This code is only called on write */
|
2006-05-14 00:07:53 +07:00
|
|
|
static int doc200x_calculate_ecc(struct mtd_info *mtd, const u_char *dat, unsigned char *ecc_code)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2005-04-17 05:20:36 +07:00
|
|
|
int i;
|
|
|
|
int emptymatch = 1;
|
|
|
|
|
|
|
|
/* flush the pipeline */
|
|
|
|
if (DoC_is_2000(doc)) {
|
|
|
|
WriteDOC(doc->CDSNControl & ~CDSN_CTRL_FLASH_IO, docptr, CDSNControl);
|
|
|
|
WriteDOC(0, docptr, 2k_CDSN_IO);
|
|
|
|
WriteDOC(0, docptr, 2k_CDSN_IO);
|
|
|
|
WriteDOC(0, docptr, 2k_CDSN_IO);
|
|
|
|
WriteDOC(doc->CDSNControl, docptr, CDSNControl);
|
|
|
|
} else if (DoC_is_MillenniumPlus(doc)) {
|
|
|
|
WriteDOC(0, docptr, Mplus_NOP);
|
|
|
|
WriteDOC(0, docptr, Mplus_NOP);
|
|
|
|
WriteDOC(0, docptr, Mplus_NOP);
|
|
|
|
} else {
|
|
|
|
WriteDOC(0, docptr, NOP);
|
|
|
|
WriteDOC(0, docptr, NOP);
|
|
|
|
WriteDOC(0, docptr, NOP);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < 6; i++) {
|
|
|
|
if (DoC_is_MillenniumPlus(doc))
|
|
|
|
ecc_code[i] = ReadDOC_(docptr, DoC_Mplus_ECCSyndrome0 + i);
|
2005-11-07 18:15:49 +07:00
|
|
|
else
|
2005-04-17 05:20:36 +07:00
|
|
|
ecc_code[i] = ReadDOC_(docptr, DoC_ECCSyndrome0 + i);
|
|
|
|
if (ecc_code[i] != empty_write_ecc[i])
|
|
|
|
emptymatch = 0;
|
|
|
|
}
|
|
|
|
if (DoC_is_MillenniumPlus(doc))
|
|
|
|
WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf);
|
|
|
|
else
|
|
|
|
WriteDOC(DOC_ECC_DIS, docptr, ECCConf);
|
|
|
|
#if 0
|
|
|
|
/* If emptymatch=1, we might have an all-0xff data buffer. Check. */
|
|
|
|
if (emptymatch) {
|
|
|
|
/* Note: this somewhat expensive test should not be triggered
|
|
|
|
often. It could be optimized away by examining the data in
|
|
|
|
the writebuf routine, and remembering the result. */
|
|
|
|
for (i = 0; i < 512; i++) {
|
2006-05-14 00:07:53 +07:00
|
|
|
if (dat[i] == 0xff)
|
|
|
|
continue;
|
2005-04-17 05:20:36 +07:00
|
|
|
emptymatch = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* If emptymatch still =1, we do have an all-0xff data buffer.
|
|
|
|
Return all-0xff ecc value instead of the computed one, so
|
|
|
|
it'll look just like a freshly-erased page. */
|
2006-05-14 00:07:53 +07:00
|
|
|
if (emptymatch)
|
|
|
|
memset(ecc_code, 0xff, 6);
|
2005-04-17 05:20:36 +07:00
|
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-05-25 15:07:16 +07:00
|
|
|
static int doc200x_correct_data(struct mtd_info *mtd, u_char *dat,
|
|
|
|
u_char *read_ecc, u_char *isnull)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
int i, ret = 0;
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2006-05-14 00:07:53 +07:00
|
|
|
void __iomem *docptr = doc->virtadr;
|
2006-05-25 15:07:16 +07:00
|
|
|
uint8_t calc_ecc[6];
|
2005-04-17 05:20:36 +07:00
|
|
|
volatile u_char dummy;
|
|
|
|
int emptymatch = 1;
|
2005-11-07 18:15:49 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/* flush the pipeline */
|
|
|
|
if (DoC_is_2000(doc)) {
|
|
|
|
dummy = ReadDOC(docptr, 2k_ECCStatus);
|
|
|
|
dummy = ReadDOC(docptr, 2k_ECCStatus);
|
|
|
|
dummy = ReadDOC(docptr, 2k_ECCStatus);
|
|
|
|
} else if (DoC_is_MillenniumPlus(doc)) {
|
|
|
|
dummy = ReadDOC(docptr, Mplus_ECCConf);
|
|
|
|
dummy = ReadDOC(docptr, Mplus_ECCConf);
|
|
|
|
dummy = ReadDOC(docptr, Mplus_ECCConf);
|
|
|
|
} else {
|
|
|
|
dummy = ReadDOC(docptr, ECCConf);
|
|
|
|
dummy = ReadDOC(docptr, ECCConf);
|
|
|
|
dummy = ReadDOC(docptr, ECCConf);
|
|
|
|
}
|
2005-11-07 18:15:49 +07:00
|
|
|
|
2011-03-31 08:57:33 +07:00
|
|
|
/* Error occurred ? */
|
2005-04-17 05:20:36 +07:00
|
|
|
if (dummy & 0x80) {
|
|
|
|
for (i = 0; i < 6; i++) {
|
|
|
|
if (DoC_is_MillenniumPlus(doc))
|
|
|
|
calc_ecc[i] = ReadDOC_(docptr, DoC_Mplus_ECCSyndrome0 + i);
|
|
|
|
else
|
|
|
|
calc_ecc[i] = ReadDOC_(docptr, DoC_ECCSyndrome0 + i);
|
|
|
|
if (calc_ecc[i] != empty_read_syndrome[i])
|
|
|
|
emptymatch = 0;
|
|
|
|
}
|
|
|
|
/* If emptymatch=1, the read syndrome is consistent with an
|
|
|
|
all-0xff data and stored ecc block. Check the stored ecc. */
|
|
|
|
if (emptymatch) {
|
|
|
|
for (i = 0; i < 6; i++) {
|
2006-05-14 00:07:53 +07:00
|
|
|
if (read_ecc[i] == 0xff)
|
|
|
|
continue;
|
2005-04-17 05:20:36 +07:00
|
|
|
emptymatch = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* If emptymatch still =1, check the data block. */
|
|
|
|
if (emptymatch) {
|
2006-05-14 00:07:53 +07:00
|
|
|
/* Note: this somewhat expensive test should not be triggered
|
|
|
|
often. It could be optimized away by examining the data in
|
|
|
|
the readbuf routine, and remembering the result. */
|
2005-04-17 05:20:36 +07:00
|
|
|
for (i = 0; i < 512; i++) {
|
2006-05-14 00:07:53 +07:00
|
|
|
if (dat[i] == 0xff)
|
|
|
|
continue;
|
2005-04-17 05:20:36 +07:00
|
|
|
emptymatch = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* If emptymatch still =1, this is almost certainly a freshly-
|
|
|
|
erased block, in which case the ECC will not come out right.
|
|
|
|
We'll suppress the error and tell the caller everything's
|
|
|
|
OK. Because it is. */
|
2006-05-14 00:07:53 +07:00
|
|
|
if (!emptymatch)
|
|
|
|
ret = doc_ecc_decode(rs_decoder, dat, calc_ecc);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (ret > 0)
|
|
|
|
printk(KERN_ERR "doc200x_correct_data corrected %d errors\n", ret);
|
2005-11-07 18:15:49 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
if (DoC_is_MillenniumPlus(doc))
|
|
|
|
WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf);
|
|
|
|
else
|
|
|
|
WriteDOC(DOC_ECC_DIS, docptr, ECCConf);
|
2011-09-21 08:34:25 +07:00
|
|
|
if (no_ecc_failures && mtd_is_eccerr(ret)) {
|
2005-04-17 05:20:36 +07:00
|
|
|
printk(KERN_ERR "suppressing ECC failure\n");
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
2005-11-07 18:15:49 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
//u_char mydatabuf[528];
|
|
|
|
|
2005-04-07 21:22:58 +07:00
|
|
|
/* The strange out-of-order .oobfree list below is a (possibly unneeded)
|
|
|
|
* attempt to retain compatibility. It used to read:
|
|
|
|
* .oobfree = { {8, 8} }
|
|
|
|
* Since that leaves two bytes unusable, it was changed. But the following
|
|
|
|
* scheme might affect existing jffs2 installs by moving the cleanmarker:
|
|
|
|
* .oobfree = { {6, 10} }
|
|
|
|
* jffs2 seems to handle the above gracefully, but the current scheme seems
|
|
|
|
* safer. The only problem with it is that any code that parses oobfree must
|
|
|
|
* be able to handle out-of-order segments.
|
|
|
|
*/
|
2006-05-28 03:16:10 +07:00
|
|
|
static struct nand_ecclayout doc200x_oobinfo = {
|
2006-05-14 00:07:53 +07:00
|
|
|
.eccbytes = 6,
|
|
|
|
.eccpos = {0, 1, 2, 3, 4, 5},
|
|
|
|
.oobfree = {{8, 8}, {6, 2}}
|
2005-04-17 05:20:36 +07:00
|
|
|
};
|
2005-11-07 18:15:49 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/* Find the (I)NFTL Media Header, and optionally also the mirror media header.
|
tree-wide: fix assorted typos all over the place
That is "success", "unknown", "through", "performance", "[re|un]mapping"
, "access", "default", "reasonable", "[con]currently", "temperature"
, "channel", "[un]used", "application", "example","hierarchy", "therefore"
, "[over|under]flow", "contiguous", "threshold", "enough" and others.
Signed-off-by: André Goddard Rosa <andre.goddard@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
2009-11-14 22:09:05 +07:00
|
|
|
On successful return, buf will contain a copy of the media header for
|
2005-04-17 05:20:36 +07:00
|
|
|
further processing. id is the string to scan for, and will presumably be
|
|
|
|
either "ANAND" or "BNAND". If findmirror=1, also look for the mirror media
|
|
|
|
header. The page #s of the found media headers are placed in mh0_page and
|
|
|
|
mh1_page in the DOC private structure. */
|
2006-05-14 00:07:53 +07:00
|
|
|
static int __init find_media_headers(struct mtd_info *mtd, u_char *buf, const char *id, int findmirror)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
2005-03-30 03:57:48 +07:00
|
|
|
unsigned offs;
|
2005-04-17 05:20:36 +07:00
|
|
|
int ret;
|
|
|
|
size_t retlen;
|
|
|
|
|
2005-03-30 03:57:48 +07:00
|
|
|
for (offs = 0; offs < mtd->size; offs += mtd->erasesize) {
|
2011-12-23 22:30:16 +07:00
|
|
|
ret = mtd_read(mtd, offs, mtd->writesize, &retlen, buf);
|
2006-05-23 04:18:05 +07:00
|
|
|
if (retlen != mtd->writesize)
|
2006-05-14 00:07:53 +07:00
|
|
|
continue;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (ret) {
|
2006-05-14 00:07:53 +07:00
|
|
|
printk(KERN_WARNING "ECC error scanning DOC at 0x%x\n", offs);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2006-05-14 00:07:53 +07:00
|
|
|
if (memcmp(buf, id, 6))
|
|
|
|
continue;
|
2005-04-17 05:20:36 +07:00
|
|
|
printk(KERN_INFO "Found DiskOnChip %s Media Header at 0x%x\n", id, offs);
|
|
|
|
if (doc->mh0_page == -1) {
|
|
|
|
doc->mh0_page = offs >> this->page_shift;
|
2006-05-14 00:07:53 +07:00
|
|
|
if (!findmirror)
|
|
|
|
return 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
doc->mh1_page = offs >> this->page_shift;
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
if (doc->mh0_page == -1) {
|
|
|
|
printk(KERN_WARNING "DiskOnChip %s Media Header not found.\n", id);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* Only one mediaheader was found. We want buf to contain a
|
|
|
|
mediaheader on return, so we'll have to re-read the one we found. */
|
|
|
|
offs = doc->mh0_page << this->page_shift;
|
2011-12-23 22:30:16 +07:00
|
|
|
ret = mtd_read(mtd, offs, mtd->writesize, &retlen, buf);
|
2006-05-23 04:18:05 +07:00
|
|
|
if (retlen != mtd->writesize) {
|
2005-04-17 05:20:36 +07:00
|
|
|
/* Insanity. Give up. */
|
|
|
|
printk(KERN_ERR "Read DiskOnChip Media Header once, but can't reread it???\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
static inline int __init nftl_partscan(struct mtd_info *mtd, struct mtd_partition *parts)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
|
|
|
int ret = 0;
|
|
|
|
u_char *buf;
|
|
|
|
struct NFTLMediaHeader *mh;
|
|
|
|
const unsigned psize = 1 << this->page_shift;
|
2005-03-30 03:57:48 +07:00
|
|
|
int numparts = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned blocks, maxblocks;
|
|
|
|
int offs, numheaders;
|
|
|
|
|
2006-05-23 04:18:05 +07:00
|
|
|
buf = kmalloc(mtd->writesize, GFP_KERNEL);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!buf) {
|
|
|
|
return 0;
|
|
|
|
}
|
2006-05-14 00:07:53 +07:00
|
|
|
if (!(numheaders = find_media_headers(mtd, buf, "ANAND", 1)))
|
|
|
|
goto out;
|
|
|
|
mh = (struct NFTLMediaHeader *)buf;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-07-31 02:34:57 +07:00
|
|
|
le16_to_cpus(&mh->NumEraseUnits);
|
|
|
|
le16_to_cpus(&mh->FirstPhysicalEUN);
|
|
|
|
le32_to_cpus(&mh->FormattedSize);
|
2005-02-01 05:22:24 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
printk(KERN_INFO " DataOrgID = %s\n"
|
|
|
|
" NumEraseUnits = %d\n"
|
|
|
|
" FirstPhysicalEUN = %d\n"
|
|
|
|
" FormattedSize = %d\n"
|
|
|
|
" UnitSizeFactor = %d\n",
|
|
|
|
mh->DataOrgID, mh->NumEraseUnits,
|
|
|
|
mh->FirstPhysicalEUN, mh->FormattedSize,
|
|
|
|
mh->UnitSizeFactor);
|
|
|
|
|
|
|
|
blocks = mtd->size >> this->phys_erase_shift;
|
|
|
|
maxblocks = min(32768U, mtd->erasesize - psize);
|
|
|
|
|
|
|
|
if (mh->UnitSizeFactor == 0x00) {
|
|
|
|
/* Auto-determine UnitSizeFactor. The constraints are:
|
|
|
|
- There can be at most 32768 virtual blocks.
|
|
|
|
- There can be at most (virtual block size - page size)
|
2006-05-14 00:07:53 +07:00
|
|
|
virtual blocks (because MediaHeader+BBT must fit in 1).
|
|
|
|
*/
|
2005-04-17 05:20:36 +07:00
|
|
|
mh->UnitSizeFactor = 0xff;
|
|
|
|
while (blocks > maxblocks) {
|
|
|
|
blocks >>= 1;
|
|
|
|
maxblocks = min(32768U, (maxblocks << 1) + psize);
|
|
|
|
mh->UnitSizeFactor--;
|
|
|
|
}
|
|
|
|
printk(KERN_WARNING "UnitSizeFactor=0x00 detected. Correct value is assumed to be 0x%02x.\n", mh->UnitSizeFactor);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* NOTE: The lines below modify internal variables of the NAND and MTD
|
|
|
|
layers; variables with have already been configured by nand_scan.
|
|
|
|
Unfortunately, we didn't know before this point what these values
|
2011-03-31 08:57:33 +07:00
|
|
|
should be. Thus, this code is somewhat dependent on the exact
|
2005-04-17 05:20:36 +07:00
|
|
|
implementation of the NAND layer. */
|
|
|
|
if (mh->UnitSizeFactor != 0xff) {
|
|
|
|
this->bbt_erase_shift += (0xff - mh->UnitSizeFactor);
|
|
|
|
mtd->erasesize <<= (0xff - mh->UnitSizeFactor);
|
|
|
|
printk(KERN_INFO "Setting virtual erase size to %d\n", mtd->erasesize);
|
|
|
|
blocks = mtd->size >> this->bbt_erase_shift;
|
|
|
|
maxblocks = min(32768U, mtd->erasesize - psize);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (blocks > maxblocks) {
|
|
|
|
printk(KERN_ERR "UnitSizeFactor of 0x%02x is inconsistent with device size. Aborting.\n", mh->UnitSizeFactor);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Skip past the media headers. */
|
|
|
|
offs = max(doc->mh0_page, doc->mh1_page);
|
|
|
|
offs <<= this->page_shift;
|
|
|
|
offs += mtd->erasesize;
|
|
|
|
|
2005-03-30 03:57:48 +07:00
|
|
|
if (show_firmware_partition == 1) {
|
|
|
|
parts[0].name = " DiskOnChip Firmware / Media Header partition";
|
|
|
|
parts[0].offset = 0;
|
|
|
|
parts[0].size = offs;
|
|
|
|
numparts = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
parts[numparts].name = " DiskOnChip BDTL partition";
|
|
|
|
parts[numparts].offset = offs;
|
|
|
|
parts[numparts].size = (mh->NumEraseUnits - numheaders) << this->bbt_erase_shift;
|
|
|
|
|
|
|
|
offs += parts[numparts].size;
|
|
|
|
numparts++;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (offs < mtd->size) {
|
2005-03-30 03:57:48 +07:00
|
|
|
parts[numparts].name = " DiskOnChip Remainder partition";
|
|
|
|
parts[numparts].offset = offs;
|
|
|
|
parts[numparts].size = mtd->size - offs;
|
|
|
|
numparts++;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2005-03-30 03:57:48 +07:00
|
|
|
|
|
|
|
ret = numparts;
|
2006-05-14 00:07:53 +07:00
|
|
|
out:
|
2005-04-17 05:20:36 +07:00
|
|
|
kfree(buf);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This is a stripped-down copy of the code in inftlmount.c */
|
2006-05-14 00:07:53 +07:00
|
|
|
static inline int __init inftl_partscan(struct mtd_info *mtd, struct mtd_partition *parts)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
|
|
|
int ret = 0;
|
|
|
|
u_char *buf;
|
|
|
|
struct INFTLMediaHeader *mh;
|
|
|
|
struct INFTLPartition *ip;
|
|
|
|
int numparts = 0;
|
|
|
|
int blocks;
|
|
|
|
int vshift, lastvunit = 0;
|
|
|
|
int i;
|
|
|
|
int end = mtd->size;
|
|
|
|
|
|
|
|
if (inftl_bbt_write)
|
|
|
|
end -= (INFTL_BBT_RESERVED_BLOCKS << this->phys_erase_shift);
|
|
|
|
|
2006-05-23 04:18:05 +07:00
|
|
|
buf = kmalloc(mtd->writesize, GFP_KERNEL);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!buf) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
if (!find_media_headers(mtd, buf, "BNAND", 0))
|
|
|
|
goto out;
|
2005-04-17 05:20:36 +07:00
|
|
|
doc->mh1_page = doc->mh0_page + (4096 >> this->page_shift);
|
2006-05-14 00:07:53 +07:00
|
|
|
mh = (struct INFTLMediaHeader *)buf;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-07-31 02:34:57 +07:00
|
|
|
le32_to_cpus(&mh->NoOfBootImageBlocks);
|
|
|
|
le32_to_cpus(&mh->NoOfBinaryPartitions);
|
|
|
|
le32_to_cpus(&mh->NoOfBDTLPartitions);
|
|
|
|
le32_to_cpus(&mh->BlockMultiplierBits);
|
|
|
|
le32_to_cpus(&mh->FormatFlags);
|
|
|
|
le32_to_cpus(&mh->PercentUsed);
|
2005-11-07 18:15:49 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
printk(KERN_INFO " bootRecordID = %s\n"
|
|
|
|
" NoOfBootImageBlocks = %d\n"
|
|
|
|
" NoOfBinaryPartitions = %d\n"
|
|
|
|
" NoOfBDTLPartitions = %d\n"
|
|
|
|
" BlockMultiplerBits = %d\n"
|
|
|
|
" FormatFlgs = %d\n"
|
|
|
|
" OsakVersion = %d.%d.%d.%d\n"
|
|
|
|
" PercentUsed = %d\n",
|
|
|
|
mh->bootRecordID, mh->NoOfBootImageBlocks,
|
|
|
|
mh->NoOfBinaryPartitions,
|
|
|
|
mh->NoOfBDTLPartitions,
|
|
|
|
mh->BlockMultiplierBits, mh->FormatFlags,
|
|
|
|
((unsigned char *) &mh->OsakVersion)[0] & 0xf,
|
|
|
|
((unsigned char *) &mh->OsakVersion)[1] & 0xf,
|
|
|
|
((unsigned char *) &mh->OsakVersion)[2] & 0xf,
|
|
|
|
((unsigned char *) &mh->OsakVersion)[3] & 0xf,
|
|
|
|
mh->PercentUsed);
|
|
|
|
|
|
|
|
vshift = this->phys_erase_shift + mh->BlockMultiplierBits;
|
|
|
|
|
|
|
|
blocks = mtd->size >> vshift;
|
|
|
|
if (blocks > 32768) {
|
|
|
|
printk(KERN_ERR "BlockMultiplierBits=%d is inconsistent with device size. Aborting.\n", mh->BlockMultiplierBits);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
blocks = doc->chips_per_floor << (this->chip_shift - this->phys_erase_shift);
|
|
|
|
if (inftl_bbt_write && (blocks > mtd->erasesize)) {
|
|
|
|
printk(KERN_ERR "Writeable BBTs spanning more than one erase block are not yet supported. FIX ME!\n");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Scan the partitions */
|
|
|
|
for (i = 0; (i < 4); i++) {
|
|
|
|
ip = &(mh->Partitions[i]);
|
2008-07-31 02:34:57 +07:00
|
|
|
le32_to_cpus(&ip->virtualUnits);
|
|
|
|
le32_to_cpus(&ip->firstUnit);
|
|
|
|
le32_to_cpus(&ip->lastUnit);
|
|
|
|
le32_to_cpus(&ip->flags);
|
|
|
|
le32_to_cpus(&ip->spareUnits);
|
|
|
|
le32_to_cpus(&ip->Reserved0);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
printk(KERN_INFO " PARTITION[%d] ->\n"
|
|
|
|
" virtualUnits = %d\n"
|
|
|
|
" firstUnit = %d\n"
|
|
|
|
" lastUnit = %d\n"
|
|
|
|
" flags = 0x%x\n"
|
|
|
|
" spareUnits = %d\n",
|
|
|
|
i, ip->virtualUnits, ip->firstUnit,
|
|
|
|
ip->lastUnit, ip->flags,
|
|
|
|
ip->spareUnits);
|
|
|
|
|
2005-03-30 03:57:48 +07:00
|
|
|
if ((show_firmware_partition == 1) &&
|
|
|
|
(i == 0) && (ip->firstUnit > 0)) {
|
2005-04-17 05:20:36 +07:00
|
|
|
parts[0].name = " DiskOnChip IPL / Media Header partition";
|
|
|
|
parts[0].offset = 0;
|
|
|
|
parts[0].size = mtd->erasesize * ip->firstUnit;
|
|
|
|
numparts = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ip->flags & INFTL_BINARY)
|
|
|
|
parts[numparts].name = " DiskOnChip BDK partition";
|
|
|
|
else
|
|
|
|
parts[numparts].name = " DiskOnChip BDTL partition";
|
|
|
|
parts[numparts].offset = ip->firstUnit << vshift;
|
|
|
|
parts[numparts].size = (1 + ip->lastUnit - ip->firstUnit) << vshift;
|
|
|
|
numparts++;
|
2006-05-14 00:07:53 +07:00
|
|
|
if (ip->lastUnit > lastvunit)
|
|
|
|
lastvunit = ip->lastUnit;
|
|
|
|
if (ip->flags & INFTL_LAST)
|
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
lastvunit++;
|
|
|
|
if ((lastvunit << vshift) < end) {
|
|
|
|
parts[numparts].name = " DiskOnChip Remainder partition";
|
|
|
|
parts[numparts].offset = lastvunit << vshift;
|
|
|
|
parts[numparts].size = end - parts[numparts].offset;
|
|
|
|
numparts++;
|
|
|
|
}
|
|
|
|
ret = numparts;
|
2006-05-14 00:07:53 +07:00
|
|
|
out:
|
2005-04-17 05:20:36 +07:00
|
|
|
kfree(buf);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __init nftl_scan_bbt(struct mtd_info *mtd)
|
|
|
|
{
|
|
|
|
int ret, numparts;
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
|
|
|
struct mtd_partition parts[2];
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
memset((char *)parts, 0, sizeof(parts));
|
2005-04-17 05:20:36 +07:00
|
|
|
/* On NFTL, we have to find the media headers before we can read the
|
|
|
|
BBTs, since they're stored in the media header eraseblocks. */
|
|
|
|
numparts = nftl_partscan(mtd, parts);
|
2006-05-14 00:07:53 +07:00
|
|
|
if (!numparts)
|
|
|
|
return -EIO;
|
2005-04-17 05:20:36 +07:00
|
|
|
this->bbt_td->options = NAND_BBT_ABSPAGE | NAND_BBT_8BIT |
|
|
|
|
NAND_BBT_SAVECONTENT | NAND_BBT_WRITE |
|
|
|
|
NAND_BBT_VERSION;
|
|
|
|
this->bbt_td->veroffs = 7;
|
|
|
|
this->bbt_td->pages[0] = doc->mh0_page + 1;
|
|
|
|
if (doc->mh1_page != -1) {
|
|
|
|
this->bbt_md->options = NAND_BBT_ABSPAGE | NAND_BBT_8BIT |
|
|
|
|
NAND_BBT_SAVECONTENT | NAND_BBT_WRITE |
|
|
|
|
NAND_BBT_VERSION;
|
|
|
|
this->bbt_md->veroffs = 7;
|
|
|
|
this->bbt_md->pages[0] = doc->mh1_page + 1;
|
|
|
|
} else {
|
|
|
|
this->bbt_md = NULL;
|
|
|
|
}
|
|
|
|
|
2015-02-28 17:13:10 +07:00
|
|
|
ret = this->scan_bbt(mtd);
|
|
|
|
if (ret)
|
2005-04-17 05:20:36 +07:00
|
|
|
return ret;
|
2015-02-28 17:13:10 +07:00
|
|
|
|
2015-06-02 06:17:17 +07:00
|
|
|
return mtd_device_register(mtd, parts, no_autopart ? 0 : numparts);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int __init inftl_scan_bbt(struct mtd_info *mtd)
|
|
|
|
{
|
|
|
|
int ret, numparts;
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
|
|
|
struct mtd_partition parts[5];
|
|
|
|
|
|
|
|
if (this->numchips > doc->chips_per_floor) {
|
|
|
|
printk(KERN_ERR "Multi-floor INFTL devices not yet supported.\n");
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (DoC_is_MillenniumPlus(doc)) {
|
|
|
|
this->bbt_td->options = NAND_BBT_2BIT | NAND_BBT_ABSPAGE;
|
|
|
|
if (inftl_bbt_write)
|
|
|
|
this->bbt_td->options |= NAND_BBT_WRITE;
|
|
|
|
this->bbt_td->pages[0] = 2;
|
|
|
|
this->bbt_md = NULL;
|
|
|
|
} else {
|
2006-05-14 00:07:53 +07:00
|
|
|
this->bbt_td->options = NAND_BBT_LASTBLOCK | NAND_BBT_8BIT | NAND_BBT_VERSION;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (inftl_bbt_write)
|
|
|
|
this->bbt_td->options |= NAND_BBT_WRITE;
|
|
|
|
this->bbt_td->offs = 8;
|
|
|
|
this->bbt_td->len = 8;
|
|
|
|
this->bbt_td->veroffs = 7;
|
|
|
|
this->bbt_td->maxblocks = INFTL_BBT_RESERVED_BLOCKS;
|
|
|
|
this->bbt_td->reserved_block_code = 0x01;
|
|
|
|
this->bbt_td->pattern = "MSYS_BBT";
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
this->bbt_md->options = NAND_BBT_LASTBLOCK | NAND_BBT_8BIT | NAND_BBT_VERSION;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (inftl_bbt_write)
|
|
|
|
this->bbt_md->options |= NAND_BBT_WRITE;
|
|
|
|
this->bbt_md->offs = 8;
|
|
|
|
this->bbt_md->len = 8;
|
|
|
|
this->bbt_md->veroffs = 7;
|
|
|
|
this->bbt_md->maxblocks = INFTL_BBT_RESERVED_BLOCKS;
|
|
|
|
this->bbt_md->reserved_block_code = 0x01;
|
|
|
|
this->bbt_md->pattern = "TBB_SYSM";
|
|
|
|
}
|
|
|
|
|
2015-02-28 17:13:10 +07:00
|
|
|
ret = this->scan_bbt(mtd);
|
|
|
|
if (ret)
|
2005-04-17 05:20:36 +07:00
|
|
|
return ret;
|
2015-02-28 17:13:10 +07:00
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
memset((char *)parts, 0, sizeof(parts));
|
2005-04-17 05:20:36 +07:00
|
|
|
numparts = inftl_partscan(mtd, parts);
|
|
|
|
/* At least for now, require the INFTL Media Header. We could probably
|
|
|
|
do without it for non-INFTL use, since all it gives us is
|
|
|
|
autopartitioning, but I want to give it more thought. */
|
2006-05-14 00:07:53 +07:00
|
|
|
if (!numparts)
|
|
|
|
return -EIO;
|
2015-06-02 06:17:17 +07:00
|
|
|
return mtd_device_register(mtd, parts, no_autopart ? 0 : numparts);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline int __init doc2000_init(struct mtd_info *mtd)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
|
|
|
|
|
|
|
this->read_byte = doc2000_read_byte;
|
|
|
|
this->write_buf = doc2000_writebuf;
|
|
|
|
this->read_buf = doc2000_readbuf;
|
2015-02-28 17:13:10 +07:00
|
|
|
doc->late_init = nftl_scan_bbt;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
doc->CDSNControl = CDSN_CTRL_FLASH_IO | CDSN_CTRL_ECC_IO;
|
|
|
|
doc2000_count_chips(mtd);
|
|
|
|
mtd->name = "DiskOnChip 2000 (NFTL Model)";
|
|
|
|
return (4 * doc->chips_per_floor);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int __init doc2001_init(struct mtd_info *mtd)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
|
|
|
|
|
|
|
this->read_byte = doc2001_read_byte;
|
|
|
|
this->write_buf = doc2001_writebuf;
|
|
|
|
this->read_buf = doc2001_readbuf;
|
|
|
|
|
|
|
|
ReadDOC(doc->virtadr, ChipID);
|
|
|
|
ReadDOC(doc->virtadr, ChipID);
|
|
|
|
ReadDOC(doc->virtadr, ChipID);
|
|
|
|
if (ReadDOC(doc->virtadr, ChipID) != DOC_ChipID_DocMil) {
|
|
|
|
/* It's not a Millennium; it's one of the newer
|
2005-11-07 18:15:49 +07:00
|
|
|
DiskOnChip 2000 units with a similar ASIC.
|
2005-04-17 05:20:36 +07:00
|
|
|
Treat it like a Millennium, except that it
|
|
|
|
can have multiple chips. */
|
|
|
|
doc2000_count_chips(mtd);
|
|
|
|
mtd->name = "DiskOnChip 2000 (INFTL Model)";
|
2015-02-28 17:13:10 +07:00
|
|
|
doc->late_init = inftl_scan_bbt;
|
2005-04-17 05:20:36 +07:00
|
|
|
return (4 * doc->chips_per_floor);
|
|
|
|
} else {
|
|
|
|
/* Bog-standard Millennium */
|
|
|
|
doc->chips_per_floor = 1;
|
|
|
|
mtd->name = "DiskOnChip Millennium";
|
2015-02-28 17:13:10 +07:00
|
|
|
doc->late_init = nftl_scan_bbt;
|
2005-04-17 05:20:36 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int __init doc2001plus_init(struct mtd_info *mtd)
|
|
|
|
{
|
|
|
|
struct nand_chip *this = mtd->priv;
|
|
|
|
struct doc_priv *doc = this->priv;
|
|
|
|
|
|
|
|
this->read_byte = doc2001plus_read_byte;
|
|
|
|
this->write_buf = doc2001plus_writebuf;
|
|
|
|
this->read_buf = doc2001plus_readbuf;
|
2015-02-28 17:13:10 +07:00
|
|
|
doc->late_init = inftl_scan_bbt;
|
2006-05-24 04:25:53 +07:00
|
|
|
this->cmd_ctrl = NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
this->select_chip = doc2001plus_select_chip;
|
|
|
|
this->cmdfunc = doc2001plus_command;
|
2006-05-23 20:59:58 +07:00
|
|
|
this->ecc.hwctl = doc2001plus_enable_hwecc;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
doc->chips_per_floor = 1;
|
|
|
|
mtd->name = "DiskOnChip Millennium Plus";
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2006-01-15 04:20:43 +07:00
|
|
|
static int __init doc_probe(unsigned long physadr)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
unsigned char ChipID;
|
|
|
|
struct mtd_info *mtd;
|
|
|
|
struct nand_chip *nand;
|
|
|
|
struct doc_priv *doc;
|
|
|
|
void __iomem *virtadr;
|
|
|
|
unsigned char save_control;
|
|
|
|
unsigned char tmp, tmpb, tmpc;
|
|
|
|
int reg, len, numchips;
|
|
|
|
int ret = 0;
|
|
|
|
|
2014-03-20 05:24:37 +07:00
|
|
|
if (!request_mem_region(physadr, DOC_IOREMAP_LEN, "DiskOnChip"))
|
2013-11-13 18:59:25 +07:00
|
|
|
return -EBUSY;
|
2005-04-17 05:20:36 +07:00
|
|
|
virtadr = ioremap(physadr, DOC_IOREMAP_LEN);
|
|
|
|
if (!virtadr) {
|
|
|
|
printk(KERN_ERR "Diskonchip ioremap failed: 0x%x bytes at 0x%lx\n", DOC_IOREMAP_LEN, physadr);
|
2013-11-13 18:59:25 +07:00
|
|
|
ret = -EIO;
|
|
|
|
goto error_ioremap;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/* It's not possible to cleanly detect the DiskOnChip - the
|
|
|
|
* bootup procedure will put the device into reset mode, and
|
|
|
|
* it's not possible to talk to it without actually writing
|
|
|
|
* to the DOCControl register. So we store the current contents
|
|
|
|
* of the DOCControl register's location, in case we later decide
|
|
|
|
* that it's not a DiskOnChip, and want to put it back how we
|
2005-11-07 18:15:49 +07:00
|
|
|
* found it.
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
|
|
|
save_control = ReadDOC(virtadr, DOCControl);
|
|
|
|
|
|
|
|
/* Reset the DiskOnChip ASIC */
|
2006-05-14 00:07:53 +07:00
|
|
|
WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET, virtadr, DOCControl);
|
|
|
|
WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET, virtadr, DOCControl);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/* Enable the DiskOnChip ASIC */
|
2006-05-14 00:07:53 +07:00
|
|
|
WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL, virtadr, DOCControl);
|
|
|
|
WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL, virtadr, DOCControl);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
ChipID = ReadDOC(virtadr, ChipID);
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
switch (ChipID) {
|
2005-04-17 05:20:36 +07:00
|
|
|
case DOC_ChipID_Doc2k:
|
|
|
|
reg = DoC_2k_ECCStatus;
|
|
|
|
break;
|
|
|
|
case DOC_ChipID_DocMil:
|
|
|
|
reg = DoC_ECCConf;
|
|
|
|
break;
|
|
|
|
case DOC_ChipID_DocMilPlus16:
|
|
|
|
case DOC_ChipID_DocMilPlus32:
|
|
|
|
case 0:
|
|
|
|
/* Possible Millennium Plus, need to do more checks */
|
|
|
|
/* Possibly release from power down mode */
|
|
|
|
for (tmp = 0; (tmp < 4); tmp++)
|
|
|
|
ReadDOC(virtadr, Mplus_Power);
|
|
|
|
|
|
|
|
/* Reset the Millennium Plus ASIC */
|
2006-05-14 00:07:53 +07:00
|
|
|
tmp = DOC_MODE_RESET | DOC_MODE_MDWREN | DOC_MODE_RST_LAT | DOC_MODE_BDECT;
|
2005-04-17 05:20:36 +07:00
|
|
|
WriteDOC(tmp, virtadr, Mplus_DOCControl);
|
|
|
|
WriteDOC(~tmp, virtadr, Mplus_CtrlConfirm);
|
|
|
|
|
|
|
|
mdelay(1);
|
|
|
|
/* Enable the Millennium Plus ASIC */
|
2006-05-14 00:07:53 +07:00
|
|
|
tmp = DOC_MODE_NORMAL | DOC_MODE_MDWREN | DOC_MODE_RST_LAT | DOC_MODE_BDECT;
|
2005-04-17 05:20:36 +07:00
|
|
|
WriteDOC(tmp, virtadr, Mplus_DOCControl);
|
|
|
|
WriteDOC(~tmp, virtadr, Mplus_CtrlConfirm);
|
|
|
|
mdelay(1);
|
|
|
|
|
|
|
|
ChipID = ReadDOC(virtadr, ChipID);
|
|
|
|
|
|
|
|
switch (ChipID) {
|
|
|
|
case DOC_ChipID_DocMilPlus16:
|
|
|
|
reg = DoC_Mplus_Toggle;
|
|
|
|
break;
|
|
|
|
case DOC_ChipID_DocMilPlus32:
|
|
|
|
printk(KERN_ERR "DiskOnChip Millennium Plus 32MB is not supported, ignoring.\n");
|
|
|
|
default:
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto notfound;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto notfound;
|
|
|
|
}
|
|
|
|
/* Check the TOGGLE bit in the ECC register */
|
2006-05-14 00:07:53 +07:00
|
|
|
tmp = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT;
|
2005-04-17 05:20:36 +07:00
|
|
|
tmpb = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT;
|
|
|
|
tmpc = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT;
|
|
|
|
if ((tmp == tmpb) || (tmp != tmpc)) {
|
|
|
|
printk(KERN_WARNING "Possible DiskOnChip at 0x%lx failed TOGGLE test, dropping.\n", physadr);
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto notfound;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (mtd = doclist; mtd; mtd = doc->nextdoc) {
|
|
|
|
unsigned char oldval;
|
|
|
|
unsigned char newval;
|
|
|
|
nand = mtd->priv;
|
|
|
|
doc = nand->priv;
|
|
|
|
/* Use the alias resolution register to determine if this is
|
|
|
|
in fact the same DOC aliased to a new address. If writes
|
|
|
|
to one chip's alias resolution register change the value on
|
|
|
|
the other chip, they're the same chip. */
|
|
|
|
if (ChipID == DOC_ChipID_DocMilPlus16) {
|
|
|
|
oldval = ReadDOC(doc->virtadr, Mplus_AliasResolution);
|
|
|
|
newval = ReadDOC(virtadr, Mplus_AliasResolution);
|
|
|
|
} else {
|
|
|
|
oldval = ReadDOC(doc->virtadr, AliasResolution);
|
|
|
|
newval = ReadDOC(virtadr, AliasResolution);
|
|
|
|
}
|
|
|
|
if (oldval != newval)
|
|
|
|
continue;
|
|
|
|
if (ChipID == DOC_ChipID_DocMilPlus16) {
|
|
|
|
WriteDOC(~newval, virtadr, Mplus_AliasResolution);
|
|
|
|
oldval = ReadDOC(doc->virtadr, Mplus_AliasResolution);
|
2006-05-14 00:07:53 +07:00
|
|
|
WriteDOC(newval, virtadr, Mplus_AliasResolution); // restore it
|
2005-04-17 05:20:36 +07:00
|
|
|
} else {
|
|
|
|
WriteDOC(~newval, virtadr, AliasResolution);
|
|
|
|
oldval = ReadDOC(doc->virtadr, AliasResolution);
|
2006-05-14 00:07:53 +07:00
|
|
|
WriteDOC(newval, virtadr, AliasResolution); // restore it
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
newval = ~newval;
|
|
|
|
if (oldval == newval) {
|
|
|
|
printk(KERN_DEBUG "Found alias of DOC at 0x%lx to 0x%lx\n", doc->physadr, physadr);
|
|
|
|
goto notfound;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
printk(KERN_NOTICE "DiskOnChip found at 0x%lx\n", physadr);
|
|
|
|
|
|
|
|
len = sizeof(struct mtd_info) +
|
2006-05-14 00:07:53 +07:00
|
|
|
sizeof(struct nand_chip) + sizeof(struct doc_priv) + (2 * sizeof(struct nand_bbt_descr));
|
2006-11-16 02:10:29 +07:00
|
|
|
mtd = kzalloc(len, GFP_KERNEL);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!mtd) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
nand = (struct nand_chip *) (mtd + 1);
|
|
|
|
doc = (struct doc_priv *) (nand + 1);
|
|
|
|
nand->bbt_td = (struct nand_bbt_descr *) (doc + 1);
|
|
|
|
nand->bbt_md = nand->bbt_td + 1;
|
|
|
|
|
|
|
|
mtd->priv = nand;
|
|
|
|
mtd->owner = THIS_MODULE;
|
|
|
|
|
|
|
|
nand->priv = doc;
|
|
|
|
nand->select_chip = doc200x_select_chip;
|
2006-05-24 04:25:53 +07:00
|
|
|
nand->cmd_ctrl = doc200x_hwcontrol;
|
2005-04-17 05:20:36 +07:00
|
|
|
nand->dev_ready = doc200x_dev_ready;
|
|
|
|
nand->waitfunc = doc200x_wait;
|
|
|
|
nand->block_bad = doc200x_block_bad;
|
2006-05-23 17:00:46 +07:00
|
|
|
nand->ecc.hwctl = doc200x_enable_hwecc;
|
|
|
|
nand->ecc.calculate = doc200x_calculate_ecc;
|
|
|
|
nand->ecc.correct = doc200x_correct_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2006-05-28 03:16:10 +07:00
|
|
|
nand->ecc.layout = &doc200x_oobinfo;
|
2006-05-23 17:00:46 +07:00
|
|
|
nand->ecc.mode = NAND_ECC_HW_SYNDROME;
|
|
|
|
nand->ecc.size = 512;
|
|
|
|
nand->ecc.bytes = 6;
|
2012-03-12 04:21:11 +07:00
|
|
|
nand->ecc.strength = 2;
|
2011-06-01 06:31:23 +07:00
|
|
|
nand->bbt_options = NAND_BBT_USE_FLASH;
|
2015-02-28 17:13:10 +07:00
|
|
|
/* Skip the automatic BBT scan so we can run it manually */
|
|
|
|
nand->options |= NAND_SKIP_BBTSCAN;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
doc->physadr = physadr;
|
|
|
|
doc->virtadr = virtadr;
|
|
|
|
doc->ChipID = ChipID;
|
|
|
|
doc->curfloor = -1;
|
|
|
|
doc->curchip = -1;
|
|
|
|
doc->mh0_page = -1;
|
|
|
|
doc->mh1_page = -1;
|
|
|
|
doc->nextdoc = doclist;
|
|
|
|
|
|
|
|
if (ChipID == DOC_ChipID_Doc2k)
|
|
|
|
numchips = doc2000_init(mtd);
|
|
|
|
else if (ChipID == DOC_ChipID_DocMilPlus16)
|
|
|
|
numchips = doc2001plus_init(mtd);
|
|
|
|
else
|
|
|
|
numchips = doc2001_init(mtd);
|
|
|
|
|
2015-02-28 17:13:10 +07:00
|
|
|
if ((ret = nand_scan(mtd, numchips)) || (ret = doc->late_init(mtd))) {
|
2005-04-17 05:20:36 +07:00
|
|
|
/* DBB note: i believe nand_release is necessary here, as
|
|
|
|
buffers may have been allocated in nand_base. Check with
|
|
|
|
Thomas. FIX ME! */
|
2011-05-23 16:23:19 +07:00
|
|
|
/* nand_release will call mtd_device_unregister, but we
|
|
|
|
haven't yet added it. This is handled without incident by
|
|
|
|
mtd_device_unregister, as far as I can tell. */
|
2005-04-17 05:20:36 +07:00
|
|
|
nand_release(mtd);
|
|
|
|
kfree(mtd);
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Success! */
|
|
|
|
doclist = mtd;
|
|
|
|
return 0;
|
|
|
|
|
2006-05-14 00:07:53 +07:00
|
|
|
notfound:
|
2005-04-17 05:20:36 +07:00
|
|
|
/* Put back the contents of the DOCControl register, in case it's not
|
|
|
|
actually a DiskOnChip. */
|
|
|
|
WriteDOC(save_control, virtadr, DOCControl);
|
2006-05-14 00:07:53 +07:00
|
|
|
fail:
|
2005-04-17 05:20:36 +07:00
|
|
|
iounmap(virtadr);
|
2013-11-13 18:59:25 +07:00
|
|
|
|
|
|
|
error_ioremap:
|
|
|
|
release_mem_region(physadr, DOC_IOREMAP_LEN);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void release_nanddoc(void)
|
|
|
|
{
|
2006-05-14 00:07:53 +07:00
|
|
|
struct mtd_info *mtd, *nextmtd;
|
2005-04-17 05:20:36 +07:00
|
|
|
struct nand_chip *nand;
|
|
|
|
struct doc_priv *doc;
|
|
|
|
|
|
|
|
for (mtd = doclist; mtd; mtd = nextmtd) {
|
|
|
|
nand = mtd->priv;
|
|
|
|
doc = nand->priv;
|
|
|
|
|
|
|
|
nextmtd = doc->nextdoc;
|
|
|
|
nand_release(mtd);
|
|
|
|
iounmap(doc->virtadr);
|
2013-11-13 18:59:25 +07:00
|
|
|
release_mem_region(doc->physadr, DOC_IOREMAP_LEN);
|
2005-04-17 05:20:36 +07:00
|
|
|
kfree(mtd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __init init_nanddoc(void)
|
|
|
|
{
|
|
|
|
int i, ret = 0;
|
|
|
|
|
|
|
|
/* We could create the decoder on demand, if memory is a concern.
|
2005-11-07 18:15:49 +07:00
|
|
|
* This way we have it handy, if an error happens
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
|
|
|
* Symbolsize is 10 (bits)
|
|
|
|
* Primitve polynomial is x^10+x^3+1
|
|
|
|
* first consecutive root is 510
|
|
|
|
* primitve element to generate roots = 1
|
|
|
|
* generator polinomial degree = 4
|
|
|
|
*/
|
|
|
|
rs_decoder = init_rs(10, 0x409, FCR, 1, NROOTS);
|
2006-05-14 00:07:53 +07:00
|
|
|
if (!rs_decoder) {
|
|
|
|
printk(KERN_ERR "DiskOnChip: Could not create a RS decoder\n");
|
2005-04-17 05:20:36 +07:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (doc_config_location) {
|
|
|
|
printk(KERN_INFO "Using configured DiskOnChip probe address 0x%lx\n", doc_config_location);
|
|
|
|
ret = doc_probe(doc_config_location);
|
|
|
|
if (ret < 0)
|
|
|
|
goto outerr;
|
|
|
|
} else {
|
2006-05-14 00:07:53 +07:00
|
|
|
for (i = 0; (doc_locations[i] != 0xffffffff); i++) {
|
2005-04-17 05:20:36 +07:00
|
|
|
doc_probe(doc_locations[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* No banner message any more. Print a message if no DiskOnChip
|
|
|
|
found, so the user knows we at least tried. */
|
|
|
|
if (!doclist) {
|
|
|
|
printk(KERN_INFO "No valid DiskOnChip devices found\n");
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto outerr;
|
|
|
|
}
|
|
|
|
return 0;
|
2006-05-14 00:07:53 +07:00
|
|
|
outerr:
|
2005-04-17 05:20:36 +07:00
|
|
|
free_rs(rs_decoder);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit cleanup_nanddoc(void)
|
|
|
|
{
|
|
|
|
/* Cleanup the nand/DoC resources */
|
|
|
|
release_nanddoc();
|
|
|
|
|
|
|
|
/* Free the reed solomon resources */
|
|
|
|
if (rs_decoder) {
|
|
|
|
free_rs(rs_decoder);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(init_nanddoc);
|
|
|
|
module_exit(cleanup_nanddoc);
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
|
2009-01-30 17:08:35 +07:00
|
|
|
MODULE_DESCRIPTION("M-Systems DiskOnChip 2000, Millennium and Millennium Plus device driver");
|