static int __init doc_probe()

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;
}