435 lines · 10.2 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * Freescale LBC and UPM routines. * * Copyright © 2007-2008  MontaVista Software, Inc. * Copyright © 2010 Freescale Semiconductor * * Author: Anton Vorontsov <avorontsov@ru.mvista.com> * Author: Jack Lan <Jack.Lan@freescale.com> * Author: Roy Zang <tie-fei.zang@freescale.com> */ #include <linux/init.h>#include <linux/export.h>#include <linux/kernel.h>#include <linux/compiler.h>#include <linux/spinlock.h>#include <linux/types.h>#include <linux/io.h>#include <linux/of.h>#include <linux/of_address.h>#include <linux/of_irq.h>#include <linux/slab.h>#include <linux/sched.h>#include <linux/platform_device.h>#include <linux/interrupt.h>#include <linux/syscore_ops.h>#include <asm/fsl_lbc.h> static DEFINE_SPINLOCK(fsl_lbc_lock);struct fsl_lbc_ctrl *fsl_lbc_ctrl_dev;EXPORT_SYMBOL(fsl_lbc_ctrl_dev); /** * fsl_lbc_addr - convert the base address * @addr_base:	base address of the memory bank * * This function converts a base address of lbc into the right format for the * BR register. If the SOC has eLBC then it returns 32bit physical address * else it converts a 34bit local bus physical address to correct format of * 32bit address for BR register (Example: MPC8641). */u32 fsl_lbc_addr(phys_addr_t addr_base){	struct device_node *np = fsl_lbc_ctrl_dev->dev->of_node;	u32 addr = addr_base & 0xffff8000; 	if (of_device_is_compatible(np, "fsl,elbc"))		return addr; 	return addr | ((addr_base & 0x300000000ull) >> 19);}EXPORT_SYMBOL(fsl_lbc_addr); /** * fsl_lbc_find - find Localbus bank * @addr_base:	base address of the memory bank * * This function walks LBC banks comparing "Base address" field of the BR * registers with the supplied addr_base argument. When bases match this * function returns bank number (starting with 0), otherwise it returns * appropriate errno value. */int fsl_lbc_find(phys_addr_t addr_base){	int i;	struct fsl_lbc_regs __iomem *lbc; 	if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs)		return -ENODEV; 	lbc = fsl_lbc_ctrl_dev->regs;	for (i = 0; i < ARRAY_SIZE(lbc->bank); i++) {		u32 br = in_be32(&lbc->bank[i].br);		u32 or = in_be32(&lbc->bank[i].or); 		if (br & BR_V && (br & or & BR_BA) == fsl_lbc_addr(addr_base))			return i;	} 	return -ENOENT;}EXPORT_SYMBOL(fsl_lbc_find); /** * fsl_upm_find - find pre-programmed UPM via base address * @addr_base:	base address of the memory bank controlled by the UPM * @upm:	pointer to the allocated fsl_upm structure * * This function fills fsl_upm structure so you can use it with the rest of * UPM API. On success this function returns 0, otherwise it returns * appropriate errno value. */int fsl_upm_find(phys_addr_t addr_base, struct fsl_upm *upm){	int bank;	u32 br;	struct fsl_lbc_regs __iomem *lbc; 	bank = fsl_lbc_find(addr_base);	if (bank < 0)		return bank; 	if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs)		return -ENODEV; 	lbc = fsl_lbc_ctrl_dev->regs;	br = in_be32(&lbc->bank[bank].br); 	switch (br & BR_MSEL) {	case BR_MS_UPMA:		upm->mxmr = &lbc->mamr;		break;	case BR_MS_UPMB:		upm->mxmr = &lbc->mbmr;		break;	case BR_MS_UPMC:		upm->mxmr = &lbc->mcmr;		break;	default:		return -EINVAL;	} 	switch (br & BR_PS) {	case BR_PS_8:		upm->width = 8;		break;	case BR_PS_16:		upm->width = 16;		break;	case BR_PS_32:		upm->width = 32;		break;	default:		return -EINVAL;	} 	return 0;}EXPORT_SYMBOL(fsl_upm_find); /** * fsl_upm_run_pattern - actually run an UPM pattern * @upm:	pointer to the fsl_upm structure obtained via fsl_upm_find * @io_base:	remapped pointer to where memory access should happen * @mar:	MAR register content during pattern execution * * This function triggers dummy write to the memory specified by the io_base, * thus UPM pattern actually executed. Note that mar usage depends on the * pre-programmed AMX bits in the UPM RAM. */int fsl_upm_run_pattern(struct fsl_upm *upm, void __iomem *io_base, u32 mar){	int ret = 0;	unsigned long flags; 	if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs)		return -ENODEV; 	spin_lock_irqsave(&fsl_lbc_lock, flags); 	out_be32(&fsl_lbc_ctrl_dev->regs->mar, mar); 	switch (upm->width) {	case 8:		out_8(io_base, 0x0);		break;	case 16:		out_be16(io_base, 0x0);		break;	case 32:		out_be32(io_base, 0x0);		break;	default:		ret = -EINVAL;		break;	} 	spin_unlock_irqrestore(&fsl_lbc_lock, flags); 	return ret;}EXPORT_SYMBOL(fsl_upm_run_pattern); static int fsl_lbc_ctrl_init(struct fsl_lbc_ctrl *ctrl,			     struct device_node *node){	struct fsl_lbc_regs __iomem *lbc = ctrl->regs; 	/* clear event registers */	setbits32(&lbc->ltesr, LTESR_CLEAR);	out_be32(&lbc->lteatr, 0);	out_be32(&lbc->ltear, 0);	out_be32(&lbc->lteccr, LTECCR_CLEAR);	out_be32(&lbc->ltedr, LTEDR_ENABLE); 	/* Set the monitor timeout value to the maximum for erratum A001 */	if (of_device_is_compatible(node, "fsl,elbc"))		clrsetbits_be32(&lbc->lbcr, LBCR_BMT, LBCR_BMTPS); 	return 0;} /* * NOTE: This interrupt is used to report localbus events of various kinds, * such as transaction errors on the chipselects. */ static irqreturn_t fsl_lbc_ctrl_irq(int irqno, void *data){	struct fsl_lbc_ctrl *ctrl = data;	struct fsl_lbc_regs __iomem *lbc = ctrl->regs;	u32 status;	unsigned long flags; 	spin_lock_irqsave(&fsl_lbc_lock, flags);	status = in_be32(&lbc->ltesr);	if (!status) {		spin_unlock_irqrestore(&fsl_lbc_lock, flags);		return IRQ_NONE;	} 	out_be32(&lbc->ltesr, LTESR_CLEAR);	out_be32(&lbc->lteatr, 0);	out_be32(&lbc->ltear, 0);	ctrl->irq_status = status; 	if (status & LTESR_BM)		dev_err(ctrl->dev, "Local bus monitor time-out: "			"LTESR 0x%08X\n", status);	if (status & LTESR_WP)		dev_err(ctrl->dev, "Write protect error: "			"LTESR 0x%08X\n", status);	if (status & LTESR_ATMW)		dev_err(ctrl->dev, "Atomic write error: "			"LTESR 0x%08X\n", status);	if (status & LTESR_ATMR)		dev_err(ctrl->dev, "Atomic read error: "			"LTESR 0x%08X\n", status);	if (status & LTESR_CS)		dev_err(ctrl->dev, "Chip select error: "			"LTESR 0x%08X\n", status);	if (status & LTESR_FCT) {		dev_err(ctrl->dev, "FCM command time-out: "			"LTESR 0x%08X\n", status);		smp_wmb();		wake_up(&ctrl->irq_wait);	}	if (status & LTESR_PAR) {		dev_err(ctrl->dev, "Parity or Uncorrectable ECC error: "			"LTESR 0x%08X\n", status);		smp_wmb();		wake_up(&ctrl->irq_wait);	}	if (status & LTESR_CC) {		smp_wmb();		wake_up(&ctrl->irq_wait);	}	if (status & ~LTESR_MASK)		dev_err(ctrl->dev, "Unknown error: "			"LTESR 0x%08X\n", status);	spin_unlock_irqrestore(&fsl_lbc_lock, flags);	return IRQ_HANDLED;} /* * fsl_lbc_ctrl_probe * * called by device layer when it finds a device matching * one our driver can handled. This code allocates all of * the resources needed for the controller only.  The * resources for the NAND banks themselves are allocated * in the chip probe function.*/ static int fsl_lbc_ctrl_probe(struct platform_device *dev){	int ret; 	if (!dev->dev.of_node) {		dev_err(&dev->dev, "Device OF-Node is NULL");		return -EFAULT;	} 	fsl_lbc_ctrl_dev = kzalloc_obj(*fsl_lbc_ctrl_dev);	if (!fsl_lbc_ctrl_dev)		return -ENOMEM; 	dev_set_drvdata(&dev->dev, fsl_lbc_ctrl_dev); 	spin_lock_init(&fsl_lbc_ctrl_dev->lock);	init_waitqueue_head(&fsl_lbc_ctrl_dev->irq_wait); 	fsl_lbc_ctrl_dev->regs = of_iomap(dev->dev.of_node, 0);	if (!fsl_lbc_ctrl_dev->regs) {		dev_err(&dev->dev, "failed to get memory region\n");		ret = -ENODEV;		goto err;	} 	fsl_lbc_ctrl_dev->irq[0] = irq_of_parse_and_map(dev->dev.of_node, 0);	if (!fsl_lbc_ctrl_dev->irq[0]) {		dev_err(&dev->dev, "failed to get irq resource\n");		ret = -ENODEV;		goto err;	} 	fsl_lbc_ctrl_dev->dev = &dev->dev; 	ret = fsl_lbc_ctrl_init(fsl_lbc_ctrl_dev, dev->dev.of_node);	if (ret < 0)		goto err; 	ret = request_irq(fsl_lbc_ctrl_dev->irq[0], fsl_lbc_ctrl_irq, 0,				"fsl-lbc", fsl_lbc_ctrl_dev);	if (ret != 0) {		dev_err(&dev->dev, "failed to install irq (%d)\n",			fsl_lbc_ctrl_dev->irq[0]);		ret = fsl_lbc_ctrl_dev->irq[0];		goto err;	} 	fsl_lbc_ctrl_dev->irq[1] = irq_of_parse_and_map(dev->dev.of_node, 1);	if (fsl_lbc_ctrl_dev->irq[1]) {		ret = request_irq(fsl_lbc_ctrl_dev->irq[1], fsl_lbc_ctrl_irq,				IRQF_SHARED, "fsl-lbc-err", fsl_lbc_ctrl_dev);		if (ret) {			dev_err(&dev->dev, "failed to install irq (%d)\n",					fsl_lbc_ctrl_dev->irq[1]);			ret = fsl_lbc_ctrl_dev->irq[1];			goto err1;		}	} 	/* Enable interrupts for any detected events */	out_be32(&fsl_lbc_ctrl_dev->regs->lteir, LTEIR_ENABLE); 	return 0; err1:	free_irq(fsl_lbc_ctrl_dev->irq[0], fsl_lbc_ctrl_dev);err:	iounmap(fsl_lbc_ctrl_dev->regs);	kfree(fsl_lbc_ctrl_dev);	fsl_lbc_ctrl_dev = NULL;	return ret;} #ifdef CONFIG_SUSPEND /* save lbc registers */static int fsl_lbc_syscore_suspend(void *data){	struct fsl_lbc_ctrl *ctrl;	struct fsl_lbc_regs __iomem *lbc; 	ctrl = fsl_lbc_ctrl_dev;	if (!ctrl)		goto out; 	lbc = ctrl->regs;	if (!lbc)		goto out; 	ctrl->saved_regs = kmalloc_obj(struct fsl_lbc_regs);	if (!ctrl->saved_regs)		return -ENOMEM; 	_memcpy_fromio(ctrl->saved_regs, lbc, sizeof(struct fsl_lbc_regs)); out:	return 0;} /* restore lbc registers */static void fsl_lbc_syscore_resume(void *data){	struct fsl_lbc_ctrl *ctrl;	struct fsl_lbc_regs __iomem *lbc; 	ctrl = fsl_lbc_ctrl_dev;	if (!ctrl)		goto out; 	lbc = ctrl->regs;	if (!lbc)		goto out; 	if (ctrl->saved_regs) {		_memcpy_toio(lbc, ctrl->saved_regs,				sizeof(struct fsl_lbc_regs));		kfree(ctrl->saved_regs);		ctrl->saved_regs = NULL;	} out:	return;}#endif /* CONFIG_SUSPEND */ static const struct of_device_id fsl_lbc_match[] = {	{ .compatible = "fsl,elbc", },	{ .compatible = "fsl,pq3-localbus", },	{ .compatible = "fsl,pq2-localbus", },	{ .compatible = "fsl,pq2pro-localbus", },	{},}; #ifdef CONFIG_SUSPENDstatic const struct syscore_ops lbc_syscore_pm_ops = {	.suspend = fsl_lbc_syscore_suspend,	.resume = fsl_lbc_syscore_resume,}; static struct syscore lbc_syscore_pm = {	.ops = &lbc_syscore_pm_ops,};#endif static struct platform_driver fsl_lbc_ctrl_driver = {	.driver = {		.name = "fsl-lbc",		.of_match_table = fsl_lbc_match,	},	.probe = fsl_lbc_ctrl_probe,}; static int __init fsl_lbc_init(void){#ifdef CONFIG_SUSPEND	register_syscore(&lbc_syscore_pm);#endif	return platform_driver_register(&fsl_lbc_ctrl_driver);}subsys_initcall(fsl_lbc_init);