// 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/mod_devicetable.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(sizeof(*fsl_lbc_ctrl_dev), GFP_KERNEL); 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) { 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(sizeof(struct fsl_lbc_regs), GFP_KERNEL); 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) { 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_SUSPEND static struct syscore_ops lbc_syscore_pm_ops = { .suspend = fsl_lbc_syscore_suspend, .resume = fsl_lbc_syscore_resume, }; #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_ops(&lbc_syscore_pm_ops); #endif return platform_driver_register(&fsl_lbc_ctrl_driver); } subsys_initcall(fsl_lbc_init);