in nand/raw/diskonchip.c [1299:1520]
static int __init doc_probe(unsigned long physadr)
{
struct nand_chip *nand = NULL;
struct doc_priv *doc = NULL;
unsigned char ChipID;
struct mtd_info *mtd;
void __iomem *virtadr;
unsigned char save_control;
unsigned char tmp, tmpb, tmpc;
int reg, len, numchips;
int ret = 0;
if (!request_mem_region(physadr, DOC_IOREMAP_LEN, "DiskOnChip"))
return -EBUSY;
virtadr = ioremap(physadr, DOC_IOREMAP_LEN);
if (!virtadr) {
pr_err("Diskonchip ioremap failed: 0x%x bytes at 0x%lx\n",
DOC_IOREMAP_LEN, physadr);
ret = -EIO;
goto error_ioremap;
}
/* 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
* found it.
*/
save_control = ReadDOC(virtadr, DOCControl);
/* Reset the DiskOnChip ASIC */
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);
/* Enable the DiskOnChip ASIC */
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);
ChipID = ReadDOC(virtadr, ChipID);
switch (ChipID) {
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 */
tmp = DOC_MODE_RESET | DOC_MODE_MDWREN | DOC_MODE_RST_LAT | DOC_MODE_BDECT;
WriteDOC(tmp, virtadr, Mplus_DOCControl);
WriteDOC(~tmp, virtadr, Mplus_CtrlConfirm);
usleep_range(1000, 2000);
/* Enable the Millennium Plus ASIC */
tmp = DOC_MODE_NORMAL | DOC_MODE_MDWREN | DOC_MODE_RST_LAT | DOC_MODE_BDECT;
WriteDOC(tmp, virtadr, Mplus_DOCControl);
WriteDOC(~tmp, virtadr, Mplus_CtrlConfirm);
usleep_range(1000, 2000);
ChipID = ReadDOC(virtadr, ChipID);
switch (ChipID) {
case DOC_ChipID_DocMilPlus16:
reg = DoC_Mplus_Toggle;
break;
case DOC_ChipID_DocMilPlus32:
pr_err("DiskOnChip Millennium Plus 32MB is not supported, ignoring.\n");
fallthrough;
default:
ret = -ENODEV;
goto notfound;
}
break;
default:
ret = -ENODEV;
goto notfound;
}
/* Check the TOGGLE bit in the ECC register */
tmp = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT;
tmpb = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT;
tmpc = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT;
if ((tmp == tmpb) || (tmp != tmpc)) {
pr_warn("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_to_nand(mtd);
doc = nand_get_controller_data(nand);
/* 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);
WriteDOC(newval, virtadr, Mplus_AliasResolution); // restore it
} else {
WriteDOC(~newval, virtadr, AliasResolution);
oldval = ReadDOC(doc->virtadr, AliasResolution);
WriteDOC(newval, virtadr, AliasResolution); // restore it
}
newval = ~newval;
if (oldval == newval) {
pr_debug("Found alias of DOC at 0x%lx to 0x%lx\n",
doc->physadr, physadr);
goto notfound;
}
}
pr_notice("DiskOnChip found at 0x%lx\n", physadr);
len = sizeof(struct nand_chip) + sizeof(struct doc_priv) +
(2 * sizeof(struct nand_bbt_descr));
nand = kzalloc(len, GFP_KERNEL);
if (!nand) {
ret = -ENOMEM;
goto fail;
}
/*
* Allocate a RS codec instance
*
* 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
*/
doc = (struct doc_priv *) (nand + 1);
doc->rs_decoder = init_rs(10, 0x409, FCR, 1, NROOTS);
if (!doc->rs_decoder) {
pr_err("DiskOnChip: Could not create a RS codec\n");
ret = -ENOMEM;
goto fail;
}
nand_controller_init(&doc->base);
if (ChipID == DOC_ChipID_DocMilPlus16)
doc->base.ops = &doc2001plus_ops;
else
doc->base.ops = &doc200x_ops;
mtd = nand_to_mtd(nand);
nand->bbt_td = (struct nand_bbt_descr *) (doc + 1);
nand->bbt_md = nand->bbt_td + 1;
mtd->owner = THIS_MODULE;
mtd_set_ooblayout(mtd, &doc200x_ooblayout_ops);
nand->controller = &doc->base;
nand_set_controller_data(nand, doc);
nand->bbt_options = NAND_BBT_USE_FLASH;
/* Skip the automatic BBT scan so we can run it manually */
nand->options |= NAND_SKIP_BBTSCAN | NAND_NO_BBM_QUIRK;
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);
if ((ret = nand_scan(nand, numchips)) || (ret = doc->late_init(mtd))) {
/* DBB note: i believe nand_cleanup is necessary here, as
buffers may have been allocated in nand_base. Check with
Thomas. FIX ME! */
nand_cleanup(nand);
goto fail;
}
/* Success! */
doclist = mtd;
return 0;
notfound:
/* Put back the contents of the DOCControl register, in case it's not
actually a DiskOnChip. */
WriteDOC(save_control, virtadr, DOCControl);
fail:
if (doc)
free_rs(doc->rs_decoder);
kfree(nand);
iounmap(virtadr);
error_ioremap:
release_mem_region(physadr, DOC_IOREMAP_LEN);
return ret;
}