summaryrefslogtreecommitdiffstats
path: root/arch/powerpc/platforms/52xx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
commit5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch)
treea94efe259b9009378be6d90eb30d2b019d95c194 /arch/powerpc/platforms/52xx
parentInitial commit. (diff)
downloadlinux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz
linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'arch/powerpc/platforms/52xx')
-rw-r--r--arch/powerpc/platforms/52xx/Kconfig61
-rw-r--r--arch/powerpc/platforms/52xx/Makefile18
-rw-r--r--arch/powerpc/platforms/52xx/efika.c236
-rw-r--r--arch/powerpc/platforms/52xx/lite5200.c195
-rw-r--r--arch/powerpc/platforms/52xx/lite5200_pm.c250
-rw-r--r--arch/powerpc/platforms/52xx/lite5200_sleep.S415
-rw-r--r--arch/powerpc/platforms/52xx/media5200.c252
-rw-r--r--arch/powerpc/platforms/52xx/mpc5200_simple.c81
-rw-r--r--arch/powerpc/platforms/52xx/mpc52xx_common.c343
-rw-r--r--arch/powerpc/platforms/52xx/mpc52xx_gpt.c788
-rw-r--r--arch/powerpc/platforms/52xx/mpc52xx_lpbfifo.c581
-rw-r--r--arch/powerpc/platforms/52xx/mpc52xx_pci.c428
-rw-r--r--arch/powerpc/platforms/52xx/mpc52xx_pic.c518
-rw-r--r--arch/powerpc/platforms/52xx/mpc52xx_pm.c204
-rw-r--r--arch/powerpc/platforms/52xx/mpc52xx_sleep.S155
15 files changed, 4525 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/52xx/Kconfig b/arch/powerpc/platforms/52xx/Kconfig
new file mode 100644
index 000000000..99d60acc2
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/Kconfig
@@ -0,0 +1,61 @@
+# SPDX-License-Identifier: GPL-2.0
+config PPC_MPC52xx
+ bool "52xx-based boards"
+ depends on PPC_BOOK3S_32
+ select COMMON_CLK
+ select HAVE_PCI
+
+config PPC_MPC5200_SIMPLE
+ bool "Generic support for simple MPC5200 based boards"
+ depends on PPC_MPC52xx
+ select DEFAULT_UIMAGE
+ help
+ This option enables support for a simple MPC52xx based boards which
+ do not need a custom platform specific setup. Such boards are
+ supported assuming the following:
+
+ - GPIO pins are configured by the firmware,
+ - CDM configuration (clocking) is setup correctly by firmware,
+ - if the 'fsl,has-wdt' property is present in one of the
+ gpt nodes, then it is safe to use such gpt to reset the board,
+ - PCI is supported if enabled in the kernel configuration
+ and if there is a PCI bus node defined in the device tree.
+
+ Boards that are compatible with this generic platform support
+ are:
+ intercontrol,digsy-mtc
+ phytec,pcm030
+ phytec,pcm032
+ promess,motionpro
+ schindler,cm5200
+ tqc,tqm5200
+
+config PPC_EFIKA
+ bool "bPlan Efika 5k2. MPC5200B based computer"
+ depends on PPC_MPC52xx
+ select PPC_RTAS
+ select PPC_NATIVE
+
+config PPC_LITE5200
+ bool "Freescale Lite5200 Eval Board"
+ depends on PPC_MPC52xx
+ select DEFAULT_UIMAGE
+
+config PPC_MEDIA5200
+ bool "Freescale Media5200 Eval Board"
+ depends on PPC_MPC52xx
+ select DEFAULT_UIMAGE
+
+config PPC_MPC5200_BUGFIX
+ bool "MPC5200 (L25R) bugfix support"
+ depends on PPC_MPC52xx
+ help
+ Enable workarounds for original MPC5200 errata. This is not required
+ for MPC5200B based boards.
+
+ It is safe to say 'Y' here
+
+config PPC_MPC5200_LPBFIFO
+ tristate "MPC5200 LocalPlus bus FIFO driver"
+ depends on PPC_MPC52xx && PPC_BESTCOMM
+ select PPC_BESTCOMM_GEN_BD
diff --git a/arch/powerpc/platforms/52xx/Makefile b/arch/powerpc/platforms/52xx/Makefile
new file mode 100644
index 000000000..f40d48eab
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/Makefile
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for 52xx based boards
+#
+obj-y += mpc52xx_pic.o mpc52xx_common.o mpc52xx_gpt.o
+obj-$(CONFIG_PCI) += mpc52xx_pci.o
+
+obj-$(CONFIG_PPC_MPC5200_SIMPLE) += mpc5200_simple.o
+obj-$(CONFIG_PPC_EFIKA) += efika.o
+obj-$(CONFIG_PPC_LITE5200) += lite5200.o
+obj-$(CONFIG_PPC_MEDIA5200) += media5200.o
+
+obj-$(CONFIG_PM) += mpc52xx_sleep.o mpc52xx_pm.o
+ifdef CONFIG_PPC_LITE5200
+ obj-$(CONFIG_PM) += lite5200_sleep.o lite5200_pm.o
+endif
+
+obj-$(CONFIG_PPC_MPC5200_LPBFIFO) += mpc52xx_lpbfifo.o
diff --git a/arch/powerpc/platforms/52xx/efika.c b/arch/powerpc/platforms/52xx/efika.c
new file mode 100644
index 000000000..4514a6f74
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/efika.c
@@ -0,0 +1,236 @@
+/*
+ * Efika 5K2 platform code
+ * Some code really inspired from the lite5200b platform.
+ *
+ * Copyright (C) 2006 bplan GmbH
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/init.h>
+#include <generated/utsrelease.h>
+#include <linux/pci.h>
+#include <linux/of.h>
+#include <asm/dma.h>
+#include <asm/prom.h>
+#include <asm/time.h>
+#include <asm/machdep.h>
+#include <asm/rtas.h>
+#include <asm/mpc52xx.h>
+
+#define EFIKA_PLATFORM_NAME "Efika"
+
+
+/* ------------------------------------------------------------------------ */
+/* PCI accesses thru RTAS */
+/* ------------------------------------------------------------------------ */
+
+#ifdef CONFIG_PCI
+
+/*
+ * Access functions for PCI config space using RTAS calls.
+ */
+static int rtas_read_config(struct pci_bus *bus, unsigned int devfn, int offset,
+ int len, u32 * val)
+{
+ struct pci_controller *hose = pci_bus_to_host(bus);
+ unsigned long addr = (offset & 0xff) | ((devfn & 0xff) << 8)
+ | (((bus->number - hose->first_busno) & 0xff) << 16)
+ | (hose->global_number << 24);
+ int ret = -1;
+ int rval;
+
+ rval = rtas_call(rtas_token("read-pci-config"), 2, 2, &ret, addr, len);
+ *val = ret;
+ return rval ? PCIBIOS_DEVICE_NOT_FOUND : PCIBIOS_SUCCESSFUL;
+}
+
+static int rtas_write_config(struct pci_bus *bus, unsigned int devfn,
+ int offset, int len, u32 val)
+{
+ struct pci_controller *hose = pci_bus_to_host(bus);
+ unsigned long addr = (offset & 0xff) | ((devfn & 0xff) << 8)
+ | (((bus->number - hose->first_busno) & 0xff) << 16)
+ | (hose->global_number << 24);
+ int rval;
+
+ rval = rtas_call(rtas_token("write-pci-config"), 3, 1, NULL,
+ addr, len, val);
+ return rval ? PCIBIOS_DEVICE_NOT_FOUND : PCIBIOS_SUCCESSFUL;
+}
+
+static struct pci_ops rtas_pci_ops = {
+ .read = rtas_read_config,
+ .write = rtas_write_config,
+};
+
+
+static void __init efika_pcisetup(void)
+{
+ const int *bus_range;
+ int len;
+ struct pci_controller *hose;
+ struct device_node *root;
+ struct device_node *pcictrl;
+
+ root = of_find_node_by_path("/");
+ if (root == NULL) {
+ printk(KERN_WARNING EFIKA_PLATFORM_NAME
+ ": Unable to find the root node\n");
+ return;
+ }
+
+ for_each_child_of_node(root, pcictrl)
+ if (of_node_name_eq(pcictrl, "pci"))
+ break;
+
+ of_node_put(root);
+
+ if (pcictrl == NULL) {
+ printk(KERN_WARNING EFIKA_PLATFORM_NAME
+ ": Unable to find the PCI bridge node\n");
+ return;
+ }
+
+ bus_range = of_get_property(pcictrl, "bus-range", &len);
+ if (bus_range == NULL || len < 2 * sizeof(int)) {
+ printk(KERN_WARNING EFIKA_PLATFORM_NAME
+ ": Can't get bus-range for %pOF\n", pcictrl);
+ goto out_put;
+ }
+
+ if (bus_range[1] == bus_range[0])
+ printk(KERN_INFO EFIKA_PLATFORM_NAME ": PCI bus %d",
+ bus_range[0]);
+ else
+ printk(KERN_INFO EFIKA_PLATFORM_NAME ": PCI buses %d..%d",
+ bus_range[0], bus_range[1]);
+ printk(" controlled by %pOF\n", pcictrl);
+ printk("\n");
+
+ hose = pcibios_alloc_controller(pcictrl);
+ if (!hose) {
+ printk(KERN_WARNING EFIKA_PLATFORM_NAME
+ ": Can't allocate PCI controller structure for %pOF\n",
+ pcictrl);
+ goto out_put;
+ }
+
+ hose->first_busno = bus_range[0];
+ hose->last_busno = bus_range[1];
+ hose->ops = &rtas_pci_ops;
+
+ pci_process_bridge_OF_ranges(hose, pcictrl, 0);
+ return;
+out_put:
+ of_node_put(pcictrl);
+}
+
+#else
+static void __init efika_pcisetup(void)
+{}
+#endif
+
+
+
+/* ------------------------------------------------------------------------ */
+/* Platform setup */
+/* ------------------------------------------------------------------------ */
+
+static void efika_show_cpuinfo(struct seq_file *m)
+{
+ struct device_node *root;
+ const char *revision;
+ const char *codegendescription;
+ const char *codegenvendor;
+
+ root = of_find_node_by_path("/");
+ if (!root)
+ return;
+
+ revision = of_get_property(root, "revision", NULL);
+ codegendescription = of_get_property(root, "CODEGEN,description", NULL);
+ codegenvendor = of_get_property(root, "CODEGEN,vendor", NULL);
+
+ if (codegendescription)
+ seq_printf(m, "machine\t\t: %s\n", codegendescription);
+ else
+ seq_printf(m, "machine\t\t: Efika\n");
+
+ if (revision)
+ seq_printf(m, "revision\t: %s\n", revision);
+
+ if (codegenvendor)
+ seq_printf(m, "vendor\t\t: %s\n", codegenvendor);
+
+ of_node_put(root);
+}
+
+#ifdef CONFIG_PM
+static void efika_suspend_prepare(void __iomem *mbar)
+{
+ u8 pin = 4; /* GPIO_WKUP_4 (GPIO_PSC6_0 - IRDA_RX) */
+ u8 level = 1; /* wakeup on high level */
+ /* IOW. to wake it up, short pins 1 and 3 on IRDA connector */
+ mpc52xx_set_wakeup_gpio(pin, level);
+}
+#endif
+
+static void __init efika_setup_arch(void)
+{
+ rtas_initialize();
+
+ /* Map important registers from the internal memory map */
+ mpc52xx_map_common_devices();
+
+ efika_pcisetup();
+
+#ifdef CONFIG_PM
+ mpc52xx_suspend.board_suspend_prepare = efika_suspend_prepare;
+ mpc52xx_pm_init();
+#endif
+
+ if (ppc_md.progress)
+ ppc_md.progress("Linux/PPC " UTS_RELEASE " running on Efika ;-)\n", 0x0);
+}
+
+static int __init efika_probe(void)
+{
+ const char *model = of_get_property(of_root, "model", NULL);
+
+ if (model == NULL)
+ return 0;
+ if (strcmp(model, "EFIKA5K2"))
+ return 0;
+
+ DMA_MODE_READ = 0x44;
+ DMA_MODE_WRITE = 0x48;
+
+ pm_power_off = rtas_power_off;
+
+ return 1;
+}
+
+define_machine(efika)
+{
+ .name = EFIKA_PLATFORM_NAME,
+ .probe = efika_probe,
+ .setup_arch = efika_setup_arch,
+ .init = mpc52xx_declare_of_platform_devices,
+ .show_cpuinfo = efika_show_cpuinfo,
+ .init_IRQ = mpc52xx_init_irq,
+ .get_irq = mpc52xx_get_irq,
+ .restart = rtas_restart,
+ .halt = rtas_halt,
+ .set_rtc_time = rtas_set_rtc_time,
+ .get_rtc_time = rtas_get_rtc_time,
+ .progress = rtas_progress,
+ .get_boot_time = rtas_get_boot_time,
+ .calibrate_decr = generic_calibrate_decr,
+#ifdef CONFIG_PCI
+ .phys_mem_access_prot = pci_phys_mem_access_prot,
+#endif
+};
+
diff --git a/arch/powerpc/platforms/52xx/lite5200.c b/arch/powerpc/platforms/52xx/lite5200.c
new file mode 100644
index 000000000..3181aac08
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/lite5200.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Freescale Lite5200 board support
+ *
+ * Written by: Grant Likely <grant.likely@secretlab.ca>
+ *
+ * Copyright (C) Secret Lab Technologies Ltd. 2006. All rights reserved.
+ * Copyright 2006 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Description:
+ */
+
+#undef DEBUG
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/root_dev.h>
+#include <linux/initrd.h>
+#include <asm/time.h>
+#include <asm/io.h>
+#include <asm/machdep.h>
+#include <asm/prom.h>
+#include <asm/mpc52xx.h>
+
+/* ************************************************************************
+ *
+ * Setup the architecture
+ *
+ */
+
+/* mpc5200 device tree match tables */
+static const struct of_device_id mpc5200_cdm_ids[] __initconst = {
+ { .compatible = "fsl,mpc5200-cdm", },
+ { .compatible = "mpc5200-cdm", },
+ {}
+};
+
+static const struct of_device_id mpc5200_gpio_ids[] __initconst = {
+ { .compatible = "fsl,mpc5200-gpio", },
+ { .compatible = "mpc5200-gpio", },
+ {}
+};
+
+/*
+ * Fix clock configuration.
+ *
+ * Firmware is supposed to be responsible for this. If you are creating a
+ * new board port, do *NOT* duplicate this code. Fix your boot firmware
+ * to set it correctly in the first place
+ */
+static void __init
+lite5200_fix_clock_config(void)
+{
+ struct device_node *np;
+ struct mpc52xx_cdm __iomem *cdm;
+ /* Map zones */
+ np = of_find_matching_node(NULL, mpc5200_cdm_ids);
+ cdm = of_iomap(np, 0);
+ of_node_put(np);
+ if (!cdm) {
+ printk(KERN_ERR "%s() failed; expect abnormal behaviour\n",
+ __func__);
+ return;
+ }
+
+ /* Use internal 48 Mhz */
+ out_8(&cdm->ext_48mhz_en, 0x00);
+ out_8(&cdm->fd_enable, 0x01);
+ if (in_be32(&cdm->rstcfg) & 0x40) /* Assumes 33Mhz clock */
+ out_be16(&cdm->fd_counters, 0x0001);
+ else
+ out_be16(&cdm->fd_counters, 0x5555);
+
+ /* Unmap the regs */
+ iounmap(cdm);
+}
+
+/*
+ * Fix setting of port_config register.
+ *
+ * Firmware is supposed to be responsible for this. If you are creating a
+ * new board port, do *NOT* duplicate this code. Fix your boot firmware
+ * to set it correctly in the first place
+ */
+static void __init
+lite5200_fix_port_config(void)
+{
+ struct device_node *np;
+ struct mpc52xx_gpio __iomem *gpio;
+ u32 port_config;
+
+ np = of_find_matching_node(NULL, mpc5200_gpio_ids);
+ gpio = of_iomap(np, 0);
+ of_node_put(np);
+ if (!gpio) {
+ printk(KERN_ERR "%s() failed. expect abnormal behavior\n",
+ __func__);
+ return;
+ }
+
+ /* Set port config */
+ port_config = in_be32(&gpio->port_config);
+
+ port_config &= ~0x00800000; /* 48Mhz internal, pin is GPIO */
+
+ port_config &= ~0x00007000; /* USB port : Differential mode */
+ port_config |= 0x00001000; /* USB 1 only */
+
+ port_config &= ~0x03000000; /* ATA CS is on csb_4/5 */
+ port_config |= 0x01000000;
+
+ pr_debug("port_config: old:%x new:%x\n",
+ in_be32(&gpio->port_config), port_config);
+ out_be32(&gpio->port_config, port_config);
+
+ /* Unmap zone */
+ iounmap(gpio);
+}
+
+#ifdef CONFIG_PM
+static void lite5200_suspend_prepare(void __iomem *mbar)
+{
+ u8 pin = 1; /* GPIO_WKUP_1 (GPIO_PSC2_4) */
+ u8 level = 0; /* wakeup on low level */
+ mpc52xx_set_wakeup_gpio(pin, level);
+
+ /*
+ * power down usb port
+ * this needs to be called before of-ohci suspend code
+ */
+
+ /* set ports to "power switched" and "powered at the same time"
+ * USB Rh descriptor A: NPS = 0, PSM = 0 */
+ out_be32(mbar + 0x1048, in_be32(mbar + 0x1048) & ~0x300);
+ /* USB Rh status: LPS = 1 - turn off power */
+ out_be32(mbar + 0x1050, 0x00000001);
+}
+
+static void lite5200_resume_finish(void __iomem *mbar)
+{
+ /* USB Rh status: LPSC = 1 - turn on power */
+ out_be32(mbar + 0x1050, 0x00010000);
+}
+#endif
+
+static void __init lite5200_setup_arch(void)
+{
+ if (ppc_md.progress)
+ ppc_md.progress("lite5200_setup_arch()", 0);
+
+ /* Map important registers from the internal memory map */
+ mpc52xx_map_common_devices();
+
+ /* Some mpc5200 & mpc5200b related configuration */
+ mpc5200_setup_xlb_arbiter();
+
+ /* Fix things that firmware should have done. */
+ lite5200_fix_clock_config();
+ lite5200_fix_port_config();
+
+#ifdef CONFIG_PM
+ mpc52xx_suspend.board_suspend_prepare = lite5200_suspend_prepare;
+ mpc52xx_suspend.board_resume_finish = lite5200_resume_finish;
+ lite5200_pm_init();
+#endif
+
+ mpc52xx_setup_pci();
+}
+
+static const char * const board[] __initconst = {
+ "fsl,lite5200",
+ "fsl,lite5200b",
+ NULL,
+};
+
+/*
+ * Called very early, MMU is off, device-tree isn't unflattened
+ */
+static int __init lite5200_probe(void)
+{
+ return of_device_compatible_match(of_root, board);
+}
+
+define_machine(lite5200) {
+ .name = "lite5200",
+ .probe = lite5200_probe,
+ .setup_arch = lite5200_setup_arch,
+ .init = mpc52xx_declare_of_platform_devices,
+ .init_IRQ = mpc52xx_init_irq,
+ .get_irq = mpc52xx_get_irq,
+ .restart = mpc52xx_restart,
+ .calibrate_decr = generic_calibrate_decr,
+};
diff --git a/arch/powerpc/platforms/52xx/lite5200_pm.c b/arch/powerpc/platforms/52xx/lite5200_pm.c
new file mode 100644
index 000000000..e7da22d1d
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/lite5200_pm.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/init.h>
+#include <linux/suspend.h>
+#include <asm/io.h>
+#include <asm/time.h>
+#include <asm/mpc52xx.h>
+#include <asm/switch_to.h>
+
+/* defined in lite5200_sleep.S and only used here */
+extern void lite5200_low_power(void __iomem *sram, void __iomem *mbar);
+
+static struct mpc52xx_cdm __iomem *cdm;
+static struct mpc52xx_intr __iomem *pic;
+static struct mpc52xx_sdma __iomem *bes;
+static struct mpc52xx_xlb __iomem *xlb;
+static struct mpc52xx_gpio __iomem *gps;
+static struct mpc52xx_gpio_wkup __iomem *gpw;
+static void __iomem *pci;
+static void __iomem *sram;
+static const int sram_size = 0x4000; /* 16 kBytes */
+static void __iomem *mbar;
+
+static suspend_state_t lite5200_pm_target_state;
+
+static int lite5200_pm_valid(suspend_state_t state)
+{
+ switch (state) {
+ case PM_SUSPEND_STANDBY:
+ case PM_SUSPEND_MEM:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int lite5200_pm_begin(suspend_state_t state)
+{
+ if (lite5200_pm_valid(state)) {
+ lite5200_pm_target_state = state;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int lite5200_pm_prepare(void)
+{
+ struct device_node *np;
+ const struct of_device_id immr_ids[] = {
+ { .compatible = "fsl,mpc5200-immr", },
+ { .compatible = "fsl,mpc5200b-immr", },
+ { .type = "soc", .compatible = "mpc5200", }, /* lite5200 */
+ { .type = "builtin", .compatible = "mpc5200", }, /* efika */
+ {}
+ };
+ u64 regaddr64 = 0;
+ const u32 *regaddr_p;
+
+ /* deep sleep? let mpc52xx code handle that */
+ if (lite5200_pm_target_state == PM_SUSPEND_STANDBY)
+ return mpc52xx_pm_prepare();
+
+ if (lite5200_pm_target_state != PM_SUSPEND_MEM)
+ return -EINVAL;
+
+ /* map registers */
+ np = of_find_matching_node(NULL, immr_ids);
+ regaddr_p = of_get_address(np, 0, NULL, NULL);
+ if (regaddr_p)
+ regaddr64 = of_translate_address(np, regaddr_p);
+ of_node_put(np);
+
+ mbar = ioremap((u32) regaddr64, 0xC000);
+ if (!mbar) {
+ printk(KERN_ERR "%s:%i Error mapping registers\n", __func__, __LINE__);
+ return -ENOSYS;
+ }
+
+ cdm = mbar + 0x200;
+ pic = mbar + 0x500;
+ gps = mbar + 0xb00;
+ gpw = mbar + 0xc00;
+ pci = mbar + 0xd00;
+ bes = mbar + 0x1200;
+ xlb = mbar + 0x1f00;
+ sram = mbar + 0x8000;
+
+ return 0;
+}
+
+/* save and restore registers not bound to any real devices */
+static struct mpc52xx_cdm scdm;
+static struct mpc52xx_intr spic;
+static struct mpc52xx_sdma sbes;
+static struct mpc52xx_xlb sxlb;
+static struct mpc52xx_gpio sgps;
+static struct mpc52xx_gpio_wkup sgpw;
+static char spci[0x200];
+
+static void lite5200_save_regs(void)
+{
+ _memcpy_fromio(&spic, pic, sizeof(*pic));
+ _memcpy_fromio(&sbes, bes, sizeof(*bes));
+ _memcpy_fromio(&scdm, cdm, sizeof(*cdm));
+ _memcpy_fromio(&sxlb, xlb, sizeof(*xlb));
+ _memcpy_fromio(&sgps, gps, sizeof(*gps));
+ _memcpy_fromio(&sgpw, gpw, sizeof(*gpw));
+ _memcpy_fromio(spci, pci, 0x200);
+
+ _memcpy_fromio(saved_sram, sram, sram_size);
+}
+
+static void lite5200_restore_regs(void)
+{
+ int i;
+ _memcpy_toio(sram, saved_sram, sram_size);
+
+ /* PCI Configuration */
+ _memcpy_toio(pci, spci, 0x200);
+
+ /*
+ * GPIOs. Interrupt Master Enable has higher address then other
+ * registers, so just memcpy is ok.
+ */
+ _memcpy_toio(gpw, &sgpw, sizeof(*gpw));
+ _memcpy_toio(gps, &sgps, sizeof(*gps));
+
+
+ /* XLB Arbitrer */
+ out_be32(&xlb->snoop_window, sxlb.snoop_window);
+ out_be32(&xlb->master_priority, sxlb.master_priority);
+ out_be32(&xlb->master_pri_enable, sxlb.master_pri_enable);
+
+ /* enable */
+ out_be32(&xlb->int_enable, sxlb.int_enable);
+ out_be32(&xlb->config, sxlb.config);
+
+
+ /* CDM - Clock Distribution Module */
+ out_8(&cdm->ipb_clk_sel, scdm.ipb_clk_sel);
+ out_8(&cdm->pci_clk_sel, scdm.pci_clk_sel);
+
+ out_8(&cdm->ext_48mhz_en, scdm.ext_48mhz_en);
+ out_8(&cdm->fd_enable, scdm.fd_enable);
+ out_be16(&cdm->fd_counters, scdm.fd_counters);
+
+ out_be32(&cdm->clk_enables, scdm.clk_enables);
+
+ out_8(&cdm->osc_disable, scdm.osc_disable);
+
+ out_be16(&cdm->mclken_div_psc1, scdm.mclken_div_psc1);
+ out_be16(&cdm->mclken_div_psc2, scdm.mclken_div_psc2);
+ out_be16(&cdm->mclken_div_psc3, scdm.mclken_div_psc3);
+ out_be16(&cdm->mclken_div_psc6, scdm.mclken_div_psc6);
+
+
+ /* BESTCOMM */
+ out_be32(&bes->taskBar, sbes.taskBar);
+ out_be32(&bes->currentPointer, sbes.currentPointer);
+ out_be32(&bes->endPointer, sbes.endPointer);
+ out_be32(&bes->variablePointer, sbes.variablePointer);
+
+ out_8(&bes->IntVect1, sbes.IntVect1);
+ out_8(&bes->IntVect2, sbes.IntVect2);
+ out_be16(&bes->PtdCntrl, sbes.PtdCntrl);
+
+ for (i=0; i<32; i++)
+ out_8(&bes->ipr[i], sbes.ipr[i]);
+
+ out_be32(&bes->cReqSelect, sbes.cReqSelect);
+ out_be32(&bes->task_size0, sbes.task_size0);
+ out_be32(&bes->task_size1, sbes.task_size1);
+ out_be32(&bes->MDEDebug, sbes.MDEDebug);
+ out_be32(&bes->ADSDebug, sbes.ADSDebug);
+ out_be32(&bes->Value1, sbes.Value1);
+ out_be32(&bes->Value2, sbes.Value2);
+ out_be32(&bes->Control, sbes.Control);
+ out_be32(&bes->Status, sbes.Status);
+ out_be32(&bes->PTDDebug, sbes.PTDDebug);
+
+ /* restore tasks */
+ for (i=0; i<16; i++)
+ out_be16(&bes->tcr[i], sbes.tcr[i]);
+
+ /* enable interrupts */
+ out_be32(&bes->IntPend, sbes.IntPend);
+ out_be32(&bes->IntMask, sbes.IntMask);
+
+
+ /* PIC */
+ out_be32(&pic->per_pri1, spic.per_pri1);
+ out_be32(&pic->per_pri2, spic.per_pri2);
+ out_be32(&pic->per_pri3, spic.per_pri3);
+
+ out_be32(&pic->main_pri1, spic.main_pri1);
+ out_be32(&pic->main_pri2, spic.main_pri2);
+
+ out_be32(&pic->enc_status, spic.enc_status);
+
+ /* unmask and enable interrupts */
+ out_be32(&pic->per_mask, spic.per_mask);
+ out_be32(&pic->main_mask, spic.main_mask);
+ out_be32(&pic->ctrl, spic.ctrl);
+}
+
+static int lite5200_pm_enter(suspend_state_t state)
+{
+ /* deep sleep? let mpc52xx code handle that */
+ if (state == PM_SUSPEND_STANDBY) {
+ return mpc52xx_pm_enter(state);
+ }
+
+ lite5200_save_regs();
+
+ /* effectively save FP regs */
+ enable_kernel_fp();
+
+ lite5200_low_power(sram, mbar);
+
+ lite5200_restore_regs();
+
+ iounmap(mbar);
+ return 0;
+}
+
+static void lite5200_pm_finish(void)
+{
+ /* deep sleep? let mpc52xx code handle that */
+ if (lite5200_pm_target_state == PM_SUSPEND_STANDBY)
+ mpc52xx_pm_finish();
+}
+
+static void lite5200_pm_end(void)
+{
+ lite5200_pm_target_state = PM_SUSPEND_ON;
+}
+
+static const struct platform_suspend_ops lite5200_pm_ops = {
+ .valid = lite5200_pm_valid,
+ .begin = lite5200_pm_begin,
+ .prepare = lite5200_pm_prepare,
+ .enter = lite5200_pm_enter,
+ .finish = lite5200_pm_finish,
+ .end = lite5200_pm_end,
+};
+
+int __init lite5200_pm_init(void)
+{
+ suspend_set_ops(&lite5200_pm_ops);
+ return 0;
+}
diff --git a/arch/powerpc/platforms/52xx/lite5200_sleep.S b/arch/powerpc/platforms/52xx/lite5200_sleep.S
new file mode 100644
index 000000000..afee8b151
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/lite5200_sleep.S
@@ -0,0 +1,415 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <asm/reg.h>
+#include <asm/ppc_asm.h>
+#include <asm/processor.h>
+#include <asm/cache.h>
+
+
+#define SDRAM_CTRL 0x104
+#define SC_MODE_EN (1<<31)
+#define SC_CKE (1<<30)
+#define SC_REF_EN (1<<28)
+#define SC_SOFT_PRE (1<<1)
+
+#define GPIOW_GPIOE 0xc00
+#define GPIOW_DDR 0xc08
+#define GPIOW_DVO 0xc0c
+
+#define CDM_CE 0x214
+#define CDM_SDRAM (1<<3)
+
+
+/* helpers... beware: r10 and r4 are overwritten */
+#define SAVE_SPRN(reg, addr) \
+ mfspr r10, SPRN_##reg; \
+ stw r10, ((addr)*4)(r4);
+
+#define LOAD_SPRN(reg, addr) \
+ lwz r10, ((addr)*4)(r4); \
+ mtspr SPRN_##reg, r10; \
+ sync; \
+ isync;
+
+
+ .data
+registers:
+ .space 0x5c*4
+ .text
+
+/* ---------------------------------------------------------------------- */
+/* low-power mode with help of M68HLC908QT1 */
+
+ .globl lite5200_low_power
+lite5200_low_power:
+
+ mr r7, r3 /* save SRAM va */
+ mr r8, r4 /* save MBAR va */
+
+ /* setup wakeup address for u-boot at physical location 0x0 */
+ lis r3, CONFIG_KERNEL_START@h
+ lis r4, lite5200_wakeup@h
+ ori r4, r4, lite5200_wakeup@l
+ sub r4, r4, r3
+ stw r4, 0(r3)
+
+
+ /*
+ * save stuff BDI overwrites
+ * 0xf0 (0xe0->0x100 gets overwritten when BDI connected;
+ * even when CONFIG_BDI_SWITCH is disabled and MMU XLAT commented; heisenbug?))
+ * WARNING: self-refresh doesn't seem to work when BDI2000 is connected,
+ * possibly because BDI sets SDRAM registers before wakeup code does
+ */
+ lis r4, registers@h
+ ori r4, r4, registers@l
+ lwz r10, 0xf0(r3)
+ stw r10, (0x1d*4)(r4)
+
+ /* save registers to r4 [destroys r10] */
+ SAVE_SPRN(LR, 0x1c)
+ bl save_regs
+
+ /* flush caches [destroys r3, r4] */
+ bl flush_data_cache
+
+
+ /* copy code to sram */
+ mr r4, r7
+ li r3, (sram_code_end - sram_code)/4
+ mtctr r3
+ lis r3, sram_code@h
+ ori r3, r3, sram_code@l
+1:
+ lwz r5, 0(r3)
+ stw r5, 0(r4)
+ addi r3, r3, 4
+ addi r4, r4, 4
+ bdnz 1b
+
+ /* get tb_ticks_per_usec */
+ lis r3, tb_ticks_per_usec@h
+ lwz r11, tb_ticks_per_usec@l(r3)
+
+ /* disable I and D caches */
+ mfspr r3, SPRN_HID0
+ ori r3, r3, HID0_ICE | HID0_DCE
+ xori r3, r3, HID0_ICE | HID0_DCE
+ sync; isync;
+ mtspr SPRN_HID0, r3
+ sync; isync;
+
+ /* jump to sram */
+ mtlr r7
+ blrl
+ /* doesn't return */
+
+
+sram_code:
+ /* self refresh */
+ lwz r4, SDRAM_CTRL(r8)
+
+ /* send NOP (precharge) */
+ oris r4, r4, SC_MODE_EN@h /* mode_en */
+ stw r4, SDRAM_CTRL(r8)
+ sync
+
+ ori r4, r4, SC_SOFT_PRE /* soft_pre */
+ stw r4, SDRAM_CTRL(r8)
+ sync
+ xori r4, r4, SC_SOFT_PRE
+
+ xoris r4, r4, SC_MODE_EN@h /* !mode_en */
+ stw r4, SDRAM_CTRL(r8)
+ sync
+
+ /* delay (for NOP to finish) */
+ li r12, 1
+ bl udelay
+
+ /*
+ * mode_en must not be set when enabling self-refresh
+ * send AR with CKE low (self-refresh)
+ */
+ oris r4, r4, (SC_REF_EN | SC_CKE)@h
+ xoris r4, r4, (SC_CKE)@h /* ref_en !cke */
+ stw r4, SDRAM_CTRL(r8)
+ sync
+
+ /* delay (after !CKE there should be two cycles) */
+ li r12, 1
+ bl udelay
+
+ /* disable clock */
+ lwz r4, CDM_CE(r8)
+ ori r4, r4, CDM_SDRAM
+ xori r4, r4, CDM_SDRAM
+ stw r4, CDM_CE(r8)
+ sync
+
+ /* delay a bit */
+ li r12, 1
+ bl udelay
+
+
+ /* turn off with QT chip */
+ li r4, 0x02
+ stb r4, GPIOW_GPIOE(r8) /* enable gpio_wkup1 */
+ sync
+
+ stb r4, GPIOW_DVO(r8) /* "output" high */
+ sync
+ stb r4, GPIOW_DDR(r8) /* output */
+ sync
+ stb r4, GPIOW_DVO(r8) /* output high */
+ sync
+
+ /* 10uS delay */
+ li r12, 10
+ bl udelay
+
+ /* turn off */
+ li r4, 0
+ stb r4, GPIOW_DVO(r8) /* output low */
+ sync
+
+ /* wait until we're offline */
+ 1:
+ b 1b
+
+
+ /* local udelay in sram is needed */
+ udelay: /* r11 - tb_ticks_per_usec, r12 - usecs, overwrites r13 */
+ mullw r12, r12, r11
+ mftb r13 /* start */
+ add r12, r13, r12 /* end */
+ 1:
+ mftb r13 /* current */
+ cmp cr0, r13, r12
+ blt 1b
+ blr
+
+sram_code_end:
+
+
+
+/* uboot jumps here on resume */
+lite5200_wakeup:
+ bl restore_regs
+
+
+ /* HIDs, MSR */
+ LOAD_SPRN(HID1, 0x19)
+ LOAD_SPRN(HID2, 0x1a)
+
+
+ /* address translation is tricky (see turn_on_mmu) */
+ mfmsr r10
+ ori r10, r10, MSR_DR | MSR_IR
+
+
+ mtspr SPRN_SRR1, r10
+ lis r10, mmu_on@h
+ ori r10, r10, mmu_on@l
+ mtspr SPRN_SRR0, r10
+ sync
+ rfi
+mmu_on:
+ /* kernel offset (r4 is still set from restore_registers) */
+ addis r4, r4, CONFIG_KERNEL_START@h
+
+
+ /* restore MSR */
+ lwz r10, (4*0x1b)(r4)
+ mtmsr r10
+ sync; isync;
+
+ /* invalidate caches */
+ mfspr r10, SPRN_HID0
+ ori r5, r10, HID0_ICFI | HID0_DCI
+ mtspr SPRN_HID0, r5 /* invalidate caches */
+ sync; isync;
+ mtspr SPRN_HID0, r10
+ sync; isync;
+
+ /* enable caches */
+ lwz r10, (4*0x18)(r4)
+ mtspr SPRN_HID0, r10 /* restore (enable caches, DPM) */
+ /* ^ this has to be after address translation set in MSR */
+ sync
+ isync
+
+
+ /* restore 0xf0 (BDI2000) */
+ lis r3, CONFIG_KERNEL_START@h
+ lwz r10, (0x1d*4)(r4)
+ stw r10, 0xf0(r3)
+
+ LOAD_SPRN(LR, 0x1c)
+
+
+ blr
+_ASM_NOKPROBE_SYMBOL(lite5200_wakeup)
+
+
+/* ---------------------------------------------------------------------- */
+/* boring code: helpers */
+
+/* save registers */
+#define SAVE_BAT(n, addr) \
+ SAVE_SPRN(DBAT##n##L, addr); \
+ SAVE_SPRN(DBAT##n##U, addr+1); \
+ SAVE_SPRN(IBAT##n##L, addr+2); \
+ SAVE_SPRN(IBAT##n##U, addr+3);
+
+#define SAVE_SR(n, addr) \
+ mfsr r10, n; \
+ stw r10, ((addr)*4)(r4);
+
+#define SAVE_4SR(n, addr) \
+ SAVE_SR(n, addr); \
+ SAVE_SR(n+1, addr+1); \
+ SAVE_SR(n+2, addr+2); \
+ SAVE_SR(n+3, addr+3);
+
+save_regs:
+ stw r0, 0(r4)
+ stw r1, 0x4(r4)
+ stw r2, 0x8(r4)
+ stmw r11, 0xc(r4) /* 0xc -> 0x5f, (0x18*4-1) */
+
+ SAVE_SPRN(HID0, 0x18)
+ SAVE_SPRN(HID1, 0x19)
+ SAVE_SPRN(HID2, 0x1a)
+ mfmsr r10
+ stw r10, (4*0x1b)(r4)
+ /*SAVE_SPRN(LR, 0x1c) have to save it before the call */
+ /* 0x1d reserved by 0xf0 */
+ SAVE_SPRN(RPA, 0x1e)
+ SAVE_SPRN(SDR1, 0x1f)
+
+ /* save MMU regs */
+ SAVE_BAT(0, 0x20)
+ SAVE_BAT(1, 0x24)
+ SAVE_BAT(2, 0x28)
+ SAVE_BAT(3, 0x2c)
+ SAVE_BAT(4, 0x30)
+ SAVE_BAT(5, 0x34)
+ SAVE_BAT(6, 0x38)
+ SAVE_BAT(7, 0x3c)
+
+ SAVE_4SR(0, 0x40)
+ SAVE_4SR(4, 0x44)
+ SAVE_4SR(8, 0x48)
+ SAVE_4SR(12, 0x4c)
+
+ SAVE_SPRN(SPRG0, 0x50)
+ SAVE_SPRN(SPRG1, 0x51)
+ SAVE_SPRN(SPRG2, 0x52)
+ SAVE_SPRN(SPRG3, 0x53)
+ SAVE_SPRN(SPRG4, 0x54)
+ SAVE_SPRN(SPRG5, 0x55)
+ SAVE_SPRN(SPRG6, 0x56)
+ SAVE_SPRN(SPRG7, 0x57)
+
+ SAVE_SPRN(IABR, 0x58)
+ SAVE_SPRN(DABR, 0x59)
+ SAVE_SPRN(TBRL, 0x5a)
+ SAVE_SPRN(TBRU, 0x5b)
+
+ blr
+
+
+/* restore registers */
+#define LOAD_BAT(n, addr) \
+ LOAD_SPRN(DBAT##n##L, addr); \
+ LOAD_SPRN(DBAT##n##U, addr+1); \
+ LOAD_SPRN(IBAT##n##L, addr+2); \
+ LOAD_SPRN(IBAT##n##U, addr+3);
+
+#define LOAD_SR(n, addr) \
+ lwz r10, ((addr)*4)(r4); \
+ mtsr n, r10;
+
+#define LOAD_4SR(n, addr) \
+ LOAD_SR(n, addr); \
+ LOAD_SR(n+1, addr+1); \
+ LOAD_SR(n+2, addr+2); \
+ LOAD_SR(n+3, addr+3);
+
+restore_regs:
+ lis r4, registers@h
+ ori r4, r4, registers@l
+
+ /* MMU is not up yet */
+ subis r4, r4, CONFIG_KERNEL_START@h
+
+ lwz r0, 0(r4)
+ lwz r1, 0x4(r4)
+ lwz r2, 0x8(r4)
+ lmw r11, 0xc(r4)
+
+ /*
+ * these are a bit tricky
+ *
+ * 0x18 - HID0
+ * 0x19 - HID1
+ * 0x1a - HID2
+ * 0x1b - MSR
+ * 0x1c - LR
+ * 0x1d - reserved by 0xf0 (BDI2000)
+ */
+ LOAD_SPRN(RPA, 0x1e);
+ LOAD_SPRN(SDR1, 0x1f);
+
+ /* restore MMU regs */
+ LOAD_BAT(0, 0x20)
+ LOAD_BAT(1, 0x24)
+ LOAD_BAT(2, 0x28)
+ LOAD_BAT(3, 0x2c)
+ LOAD_BAT(4, 0x30)
+ LOAD_BAT(5, 0x34)
+ LOAD_BAT(6, 0x38)
+ LOAD_BAT(7, 0x3c)
+
+ LOAD_4SR(0, 0x40)
+ LOAD_4SR(4, 0x44)
+ LOAD_4SR(8, 0x48)
+ LOAD_4SR(12, 0x4c)
+
+ /* rest of regs */
+ LOAD_SPRN(SPRG0, 0x50);
+ LOAD_SPRN(SPRG1, 0x51);
+ LOAD_SPRN(SPRG2, 0x52);
+ LOAD_SPRN(SPRG3, 0x53);
+ LOAD_SPRN(SPRG4, 0x54);
+ LOAD_SPRN(SPRG5, 0x55);
+ LOAD_SPRN(SPRG6, 0x56);
+ LOAD_SPRN(SPRG7, 0x57);
+
+ LOAD_SPRN(IABR, 0x58);
+ LOAD_SPRN(DABR, 0x59);
+ LOAD_SPRN(TBWL, 0x5a); /* these two have separate R/W regs */
+ LOAD_SPRN(TBWU, 0x5b);
+
+ blr
+_ASM_NOKPROBE_SYMBOL(restore_regs)
+
+
+
+/* cache flushing code. copied from arch/ppc/boot/util.S */
+#define NUM_CACHE_LINES (128*8)
+
+/*
+ * Flush data cache
+ * Do this by just reading lots of stuff into the cache.
+ */
+flush_data_cache:
+ lis r3,CONFIG_KERNEL_START@h
+ ori r3,r3,CONFIG_KERNEL_START@l
+ li r4,NUM_CACHE_LINES
+ mtctr r4
+1:
+ lwz r4,0(r3)
+ addi r3,r3,L1_CACHE_BYTES /* Next line, please */
+ bdnz 1b
+ blr
diff --git a/arch/powerpc/platforms/52xx/media5200.c b/arch/powerpc/platforms/52xx/media5200.c
new file mode 100644
index 000000000..07c5bc4ed
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/media5200.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Support for 'media5200-platform' compatible boards.
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ *
+ * Description:
+ * This code implements support for the Freescape Media5200 platform
+ * (built around the MPC5200 SoC).
+ *
+ * Notable characteristic of the Media5200 is the presence of an FPGA
+ * that has all external IRQ lines routed through it. This file implements
+ * a cascaded interrupt controller driver which attaches itself to the
+ * Virtual IRQ subsystem after the primary mpc5200 interrupt controller
+ * is initialized.
+ */
+
+#undef DEBUG
+
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <asm/time.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/mpc52xx.h>
+
+static const struct of_device_id mpc5200_gpio_ids[] __initconst = {
+ { .compatible = "fsl,mpc5200-gpio", },
+ { .compatible = "mpc5200-gpio", },
+ {}
+};
+
+/* FPGA register set */
+#define MEDIA5200_IRQ_ENABLE (0x40c)
+#define MEDIA5200_IRQ_STATUS (0x410)
+#define MEDIA5200_NUM_IRQS (6)
+#define MEDIA5200_IRQ_SHIFT (32 - MEDIA5200_NUM_IRQS)
+
+struct media5200_irq {
+ void __iomem *regs;
+ spinlock_t lock;
+ struct irq_domain *irqhost;
+};
+struct media5200_irq media5200_irq;
+
+static void media5200_irq_unmask(struct irq_data *d)
+{
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&media5200_irq.lock, flags);
+ val = in_be32(media5200_irq.regs + MEDIA5200_IRQ_ENABLE);
+ val |= 1 << (MEDIA5200_IRQ_SHIFT + irqd_to_hwirq(d));
+ out_be32(media5200_irq.regs + MEDIA5200_IRQ_ENABLE, val);
+ spin_unlock_irqrestore(&media5200_irq.lock, flags);
+}
+
+static void media5200_irq_mask(struct irq_data *d)
+{
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&media5200_irq.lock, flags);
+ val = in_be32(media5200_irq.regs + MEDIA5200_IRQ_ENABLE);
+ val &= ~(1 << (MEDIA5200_IRQ_SHIFT + irqd_to_hwirq(d)));
+ out_be32(media5200_irq.regs + MEDIA5200_IRQ_ENABLE, val);
+ spin_unlock_irqrestore(&media5200_irq.lock, flags);
+}
+
+static struct irq_chip media5200_irq_chip = {
+ .name = "Media5200 FPGA",
+ .irq_unmask = media5200_irq_unmask,
+ .irq_mask = media5200_irq_mask,
+ .irq_mask_ack = media5200_irq_mask,
+};
+
+static void media5200_irq_cascade(struct irq_desc *desc)
+{
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ int sub_virq, val;
+ u32 status, enable;
+
+ /* Mask off the cascaded IRQ */
+ raw_spin_lock(&desc->lock);
+ chip->irq_mask(&desc->irq_data);
+ raw_spin_unlock(&desc->lock);
+
+ /* Ask the FPGA for IRQ status. If 'val' is 0, then no irqs
+ * are pending. 'ffs()' is 1 based */
+ status = in_be32(media5200_irq.regs + MEDIA5200_IRQ_ENABLE);
+ enable = in_be32(media5200_irq.regs + MEDIA5200_IRQ_STATUS);
+ val = ffs((status & enable) >> MEDIA5200_IRQ_SHIFT);
+ if (val) {
+ sub_virq = irq_linear_revmap(media5200_irq.irqhost, val - 1);
+ /* pr_debug("%s: virq=%i s=%.8x e=%.8x hwirq=%i subvirq=%i\n",
+ * __func__, virq, status, enable, val - 1, sub_virq);
+ */
+ generic_handle_irq(sub_virq);
+ }
+
+ /* Processing done; can reenable the cascade now */
+ raw_spin_lock(&desc->lock);
+ chip->irq_ack(&desc->irq_data);
+ if (!irqd_irq_disabled(&desc->irq_data))
+ chip->irq_unmask(&desc->irq_data);
+ raw_spin_unlock(&desc->lock);
+}
+
+static int media5200_irq_map(struct irq_domain *h, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ pr_debug("%s: h=%p, virq=%i, hwirq=%i\n", __func__, h, virq, (int)hw);
+ irq_set_chip_data(virq, &media5200_irq);
+ irq_set_chip_and_handler(virq, &media5200_irq_chip, handle_level_irq);
+ irq_set_status_flags(virq, IRQ_LEVEL);
+ return 0;
+}
+
+static int media5200_irq_xlate(struct irq_domain *h, struct device_node *ct,
+ const u32 *intspec, unsigned int intsize,
+ irq_hw_number_t *out_hwirq,
+ unsigned int *out_flags)
+{
+ if (intsize != 2)
+ return -1;
+
+ pr_debug("%s: bank=%i, number=%i\n", __func__, intspec[0], intspec[1]);
+ *out_hwirq = intspec[1];
+ *out_flags = IRQ_TYPE_NONE;
+ return 0;
+}
+
+static const struct irq_domain_ops media5200_irq_ops = {
+ .map = media5200_irq_map,
+ .xlate = media5200_irq_xlate,
+};
+
+/*
+ * Setup Media5200 IRQ mapping
+ */
+static void __init media5200_init_irq(void)
+{
+ struct device_node *fpga_np;
+ int cascade_virq;
+
+ /* First setup the regular MPC5200 interrupt controller */
+ mpc52xx_init_irq();
+
+ /* Now find the FPGA IRQ */
+ fpga_np = of_find_compatible_node(NULL, NULL, "fsl,media5200-fpga");
+ if (!fpga_np)
+ goto out;
+ pr_debug("%s: found fpga node: %pOF\n", __func__, fpga_np);
+
+ media5200_irq.regs = of_iomap(fpga_np, 0);
+ if (!media5200_irq.regs)
+ goto out;
+ pr_debug("%s: mapped to %p\n", __func__, media5200_irq.regs);
+
+ cascade_virq = irq_of_parse_and_map(fpga_np, 0);
+ if (!cascade_virq)
+ goto out;
+ pr_debug("%s: cascaded on virq=%i\n", __func__, cascade_virq);
+
+ /* Disable all FPGA IRQs */
+ out_be32(media5200_irq.regs + MEDIA5200_IRQ_ENABLE, 0);
+
+ spin_lock_init(&media5200_irq.lock);
+
+ media5200_irq.irqhost = irq_domain_add_linear(fpga_np,
+ MEDIA5200_NUM_IRQS, &media5200_irq_ops, &media5200_irq);
+ if (!media5200_irq.irqhost)
+ goto out;
+ pr_debug("%s: allocated irqhost\n", __func__);
+
+ irq_set_handler_data(cascade_virq, &media5200_irq);
+ irq_set_chained_handler(cascade_virq, media5200_irq_cascade);
+
+ return;
+
+ out:
+ pr_err("Could not find Media5200 FPGA; PCI interrupts will not work\n");
+}
+
+/*
+ * Setup the architecture
+ */
+static void __init media5200_setup_arch(void)
+{
+
+ struct device_node *np;
+ struct mpc52xx_gpio __iomem *gpio;
+ u32 port_config;
+
+ if (ppc_md.progress)
+ ppc_md.progress("media5200_setup_arch()", 0);
+
+ /* Map important registers from the internal memory map */
+ mpc52xx_map_common_devices();
+
+ /* Some mpc5200 & mpc5200b related configuration */
+ mpc5200_setup_xlb_arbiter();
+
+ mpc52xx_setup_pci();
+
+ np = of_find_matching_node(NULL, mpc5200_gpio_ids);
+ gpio = of_iomap(np, 0);
+ of_node_put(np);
+ if (!gpio) {
+ printk(KERN_ERR "%s() failed. expect abnormal behavior\n",
+ __func__);
+ return;
+ }
+
+ /* Set port config */
+ port_config = in_be32(&gpio->port_config);
+
+ port_config &= ~0x03000000; /* ATA CS is on csb_4/5 */
+ port_config |= 0x01000000;
+
+ out_be32(&gpio->port_config, port_config);
+
+ /* Unmap zone */
+ iounmap(gpio);
+
+}
+
+/* list of the supported boards */
+static const char * const board[] __initconst = {
+ "fsl,media5200",
+ NULL
+};
+
+/*
+ * Called very early, MMU is off, device-tree isn't unflattened
+ */
+static int __init media5200_probe(void)
+{
+ return of_device_compatible_match(of_root, board);
+}
+
+define_machine(media5200_platform) {
+ .name = "media5200-platform",
+ .probe = media5200_probe,
+ .setup_arch = media5200_setup_arch,
+ .init = mpc52xx_declare_of_platform_devices,
+ .init_IRQ = media5200_init_irq,
+ .get_irq = mpc52xx_get_irq,
+ .restart = mpc52xx_restart,
+ .calibrate_decr = generic_calibrate_decr,
+};
diff --git a/arch/powerpc/platforms/52xx/mpc5200_simple.c b/arch/powerpc/platforms/52xx/mpc5200_simple.c
new file mode 100644
index 000000000..2d01e9b2e
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/mpc5200_simple.c
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Support for 'mpc5200-simple-platform' compatible boards.
+ *
+ * Written by Marian Balakowicz <m8@semihalf.com>
+ * Copyright (C) 2007 Semihalf
+ *
+ * Description:
+ * This code implements support for a simple MPC52xx based boards which
+ * do not need a custom platform specific setup. Such boards are
+ * supported assuming the following:
+ *
+ * - GPIO pins are configured by the firmware,
+ * - CDM configuration (clocking) is setup correctly by firmware,
+ * - if the 'fsl,has-wdt' property is present in one of the
+ * gpt nodes, then it is safe to use such gpt to reset the board,
+ * - PCI is supported if enabled in the kernel configuration
+ * and if there is a PCI bus node defined in the device tree.
+ *
+ * Boards that are compatible with this generic platform support
+ * are listed in a 'board' table.
+ */
+
+#undef DEBUG
+#include <asm/time.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/mpc52xx.h>
+
+/*
+ * Setup the architecture
+ */
+static void __init mpc5200_simple_setup_arch(void)
+{
+ if (ppc_md.progress)
+ ppc_md.progress("mpc5200_simple_setup_arch()", 0);
+
+ /* Map important registers from the internal memory map */
+ mpc52xx_map_common_devices();
+
+ /* Some mpc5200 & mpc5200b related configuration */
+ mpc5200_setup_xlb_arbiter();
+
+ mpc52xx_setup_pci();
+}
+
+/* list of the supported boards */
+static const char *board[] __initdata = {
+ "anonymous,a3m071",
+ "anonymous,a4m072",
+ "anon,charon",
+ "ifm,o2d",
+ "intercontrol,digsy-mtc",
+ "manroland,mucmc52",
+ "manroland,uc101",
+ "phytec,pcm030",
+ "phytec,pcm032",
+ "promess,motionpro",
+ "schindler,cm5200",
+ "tqc,tqm5200",
+ NULL
+};
+
+/*
+ * Called very early, MMU is off, device-tree isn't unflattened
+ */
+static int __init mpc5200_simple_probe(void)
+{
+ return of_device_compatible_match(of_root, board);
+}
+
+define_machine(mpc5200_simple_platform) {
+ .name = "mpc5200-simple-platform",
+ .probe = mpc5200_simple_probe,
+ .setup_arch = mpc5200_simple_setup_arch,
+ .init = mpc52xx_declare_of_platform_devices,
+ .init_IRQ = mpc52xx_init_irq,
+ .get_irq = mpc52xx_get_irq,
+ .restart = mpc52xx_restart,
+ .calibrate_decr = generic_calibrate_decr,
+};
diff --git a/arch/powerpc/platforms/52xx/mpc52xx_common.c b/arch/powerpc/platforms/52xx/mpc52xx_common.c
new file mode 100644
index 000000000..565e3a83d
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/mpc52xx_common.c
@@ -0,0 +1,343 @@
+/*
+ *
+ * Utility functions for the Freescale MPC52xx.
+ *
+ * Copyright (C) 2006 Sylvain Munaut <tnt@246tNt.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ */
+
+#undef DEBUG
+
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/export.h>
+#include <asm/io.h>
+#include <asm/prom.h>
+#include <asm/mpc52xx.h>
+
+/* MPC5200 device tree match tables */
+static const struct of_device_id mpc52xx_xlb_ids[] __initconst = {
+ { .compatible = "fsl,mpc5200-xlb", },
+ { .compatible = "mpc5200-xlb", },
+ {}
+};
+static const struct of_device_id mpc52xx_bus_ids[] __initconst = {
+ { .compatible = "fsl,mpc5200-immr", },
+ { .compatible = "fsl,mpc5200b-immr", },
+ { .compatible = "simple-bus", },
+
+ /* depreciated matches; shouldn't be used in new device trees */
+ { .compatible = "fsl,lpb", },
+ { .type = "builtin", .compatible = "mpc5200", }, /* efika */
+ { .type = "soc", .compatible = "mpc5200", }, /* lite5200 */
+ {}
+};
+
+/*
+ * This variable is mapped in mpc52xx_map_wdt() and used in mpc52xx_restart().
+ * Permanent mapping is required because mpc52xx_restart() can be called
+ * from interrupt context while node mapping (which calls ioremap())
+ * cannot be used at such point.
+ */
+static DEFINE_SPINLOCK(mpc52xx_lock);
+static struct mpc52xx_gpt __iomem *mpc52xx_wdt;
+static struct mpc52xx_cdm __iomem *mpc52xx_cdm;
+
+/*
+ * Configure the XLB arbiter settings to match what Linux expects.
+ */
+void __init
+mpc5200_setup_xlb_arbiter(void)
+{
+ struct device_node *np;
+ struct mpc52xx_xlb __iomem *xlb;
+
+ np = of_find_matching_node(NULL, mpc52xx_xlb_ids);
+ xlb = of_iomap(np, 0);
+ of_node_put(np);
+ if (!xlb) {
+ printk(KERN_ERR __FILE__ ": "
+ "Error mapping XLB in mpc52xx_setup_cpu(). "
+ "Expect some abnormal behavior\n");
+ return;
+ }
+
+ /* Configure the XLB Arbiter priorities */
+ out_be32(&xlb->master_pri_enable, 0xff);
+ out_be32(&xlb->master_priority, 0x11111111);
+
+ /*
+ * Disable XLB pipelining
+ * (cfr errate 292. We could do this only just before ATA PIO
+ * transaction and re-enable it afterwards ...)
+ * Not needed on MPC5200B.
+ */
+ if ((mfspr(SPRN_SVR) & MPC5200_SVR_MASK) == MPC5200_SVR)
+ out_be32(&xlb->config, in_be32(&xlb->config) | MPC52xx_XLB_CFG_PLDIS);
+
+ iounmap(xlb);
+}
+
+/*
+ * This variable is mapped in mpc52xx_map_common_devices and
+ * used in mpc5200_psc_ac97_gpio_reset().
+ */
+static DEFINE_SPINLOCK(gpio_lock);
+struct mpc52xx_gpio __iomem *simple_gpio;
+struct mpc52xx_gpio_wkup __iomem *wkup_gpio;
+
+/**
+ * mpc52xx_declare_of_platform_devices: register internal devices and children
+ * of the localplus bus to the of_platform
+ * bus.
+ */
+void __init mpc52xx_declare_of_platform_devices(void)
+{
+ /* Find all the 'platform' devices and register them. */
+ if (of_platform_populate(NULL, mpc52xx_bus_ids, NULL, NULL))
+ pr_err(__FILE__ ": Error while populating devices from DT\n");
+}
+
+/*
+ * match tables used by mpc52xx_map_common_devices()
+ */
+static const struct of_device_id mpc52xx_gpt_ids[] __initconst = {
+ { .compatible = "fsl,mpc5200-gpt", },
+ { .compatible = "mpc5200-gpt", }, /* old */
+ {}
+};
+static const struct of_device_id mpc52xx_cdm_ids[] __initconst = {
+ { .compatible = "fsl,mpc5200-cdm", },
+ { .compatible = "mpc5200-cdm", }, /* old */
+ {}
+};
+static const struct of_device_id mpc52xx_gpio_simple[] __initconst = {
+ { .compatible = "fsl,mpc5200-gpio", },
+ {}
+};
+static const struct of_device_id mpc52xx_gpio_wkup[] __initconst = {
+ { .compatible = "fsl,mpc5200-gpio-wkup", },
+ {}
+};
+
+
+/**
+ * mpc52xx_map_common_devices: iomap devices required by common code
+ */
+void __init
+mpc52xx_map_common_devices(void)
+{
+ struct device_node *np;
+
+ /* mpc52xx_wdt is mapped here and used in mpc52xx_restart,
+ * possibly from a interrupt context. wdt is only implement
+ * on a gpt0, so check has-wdt property before mapping.
+ */
+ for_each_matching_node(np, mpc52xx_gpt_ids) {
+ if (of_get_property(np, "fsl,has-wdt", NULL) ||
+ of_get_property(np, "has-wdt", NULL)) {
+ mpc52xx_wdt = of_iomap(np, 0);
+ of_node_put(np);
+ break;
+ }
+ }
+
+ /* Clock Distribution Module, used by PSC clock setting function */
+ np = of_find_matching_node(NULL, mpc52xx_cdm_ids);
+ mpc52xx_cdm = of_iomap(np, 0);
+ of_node_put(np);
+
+ /* simple_gpio registers */
+ np = of_find_matching_node(NULL, mpc52xx_gpio_simple);
+ simple_gpio = of_iomap(np, 0);
+ of_node_put(np);
+
+ /* wkup_gpio registers */
+ np = of_find_matching_node(NULL, mpc52xx_gpio_wkup);
+ wkup_gpio = of_iomap(np, 0);
+ of_node_put(np);
+}
+
+/**
+ * mpc52xx_set_psc_clkdiv: Set clock divider in the CDM for PSC ports
+ *
+ * @psc_id: id of psc port; must be 1,2,3 or 6
+ * @clkdiv: clock divider value to put into CDM PSC register.
+ */
+int mpc52xx_set_psc_clkdiv(int psc_id, int clkdiv)
+{
+ unsigned long flags;
+ u16 __iomem *reg;
+ u32 val;
+ u32 mask;
+ u32 mclken_div;
+
+ if (!mpc52xx_cdm)
+ return -ENODEV;
+
+ mclken_div = 0x8000 | (clkdiv & 0x1FF);
+ switch (psc_id) {
+ case 1: reg = &mpc52xx_cdm->mclken_div_psc1; mask = 0x20; break;
+ case 2: reg = &mpc52xx_cdm->mclken_div_psc2; mask = 0x40; break;
+ case 3: reg = &mpc52xx_cdm->mclken_div_psc3; mask = 0x80; break;
+ case 6: reg = &mpc52xx_cdm->mclken_div_psc6; mask = 0x10; break;
+ default:
+ return -ENODEV;
+ }
+
+ /* Set the rate and enable the clock */
+ spin_lock_irqsave(&mpc52xx_lock, flags);
+ out_be16(reg, mclken_div);
+ val = in_be32(&mpc52xx_cdm->clk_enables);
+ out_be32(&mpc52xx_cdm->clk_enables, val | mask);
+ spin_unlock_irqrestore(&mpc52xx_lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(mpc52xx_set_psc_clkdiv);
+
+/**
+ * mpc52xx_get_xtal_freq - Get SYS_XTAL_IN frequency for a device
+ *
+ * @node: device node
+ *
+ * Returns the frequency of the external oscillator clock connected
+ * to the SYS_XTAL_IN pin, or 0 if it cannot be determined.
+ */
+unsigned int mpc52xx_get_xtal_freq(struct device_node *node)
+{
+ u32 val;
+ unsigned int freq;
+
+ if (!mpc52xx_cdm)
+ return 0;
+
+ freq = mpc5xxx_get_bus_frequency(node);
+ if (!freq)
+ return 0;
+
+ if (in_8(&mpc52xx_cdm->ipb_clk_sel) & 0x1)
+ freq *= 2;
+
+ val = in_be32(&mpc52xx_cdm->rstcfg);
+ if (val & (1 << 5))
+ freq *= 8;
+ else
+ freq *= 4;
+ if (val & (1 << 6))
+ freq /= 12;
+ else
+ freq /= 16;
+
+ return freq;
+}
+EXPORT_SYMBOL(mpc52xx_get_xtal_freq);
+
+/**
+ * mpc52xx_restart: ppc_md->restart hook for mpc5200 using the watchdog timer
+ */
+void __noreturn mpc52xx_restart(char *cmd)
+{
+ local_irq_disable();
+
+ /* Turn on the watchdog and wait for it to expire.
+ * It effectively does a reset. */
+ if (mpc52xx_wdt) {
+ out_be32(&mpc52xx_wdt->mode, 0x00000000);
+ out_be32(&mpc52xx_wdt->count, 0x000000ff);
+ out_be32(&mpc52xx_wdt->mode, 0x00009004);
+ } else
+ printk(KERN_ERR __FILE__ ": "
+ "mpc52xx_restart: Can't access wdt. "
+ "Restart impossible, system halted.\n");
+
+ while (1);
+}
+
+#define PSC1_RESET 0x1
+#define PSC1_SYNC 0x4
+#define PSC1_SDATA_OUT 0x1
+#define PSC2_RESET 0x2
+#define PSC2_SYNC (0x4<<4)
+#define PSC2_SDATA_OUT (0x1<<4)
+#define MPC52xx_GPIO_PSC1_MASK 0x7
+#define MPC52xx_GPIO_PSC2_MASK (0x7<<4)
+
+/**
+ * mpc5200_psc_ac97_gpio_reset: Use gpio pins to reset the ac97 bus
+ *
+ * @psc: psc number to reset (only psc 1 and 2 support ac97)
+ */
+int mpc5200_psc_ac97_gpio_reset(int psc_number)
+{
+ unsigned long flags;
+ u32 gpio;
+ u32 mux;
+ int out;
+ int reset;
+ int sync;
+
+ if ((!simple_gpio) || (!wkup_gpio))
+ return -ENODEV;
+
+ switch (psc_number) {
+ case 0:
+ reset = PSC1_RESET; /* AC97_1_RES */
+ sync = PSC1_SYNC; /* AC97_1_SYNC */
+ out = PSC1_SDATA_OUT; /* AC97_1_SDATA_OUT */
+ gpio = MPC52xx_GPIO_PSC1_MASK;
+ break;
+ case 1:
+ reset = PSC2_RESET; /* AC97_2_RES */
+ sync = PSC2_SYNC; /* AC97_2_SYNC */
+ out = PSC2_SDATA_OUT; /* AC97_2_SDATA_OUT */
+ gpio = MPC52xx_GPIO_PSC2_MASK;
+ break;
+ default:
+ pr_err(__FILE__ ": Unable to determine PSC, no ac97 "
+ "cold-reset will be performed\n");
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&gpio_lock, flags);
+
+ /* Reconfiure pin-muxing to gpio */
+ mux = in_be32(&simple_gpio->port_config);
+ out_be32(&simple_gpio->port_config, mux & (~gpio));
+
+ /* enable gpio pins for output */
+ setbits8(&wkup_gpio->wkup_gpioe, reset);
+ setbits32(&simple_gpio->simple_gpioe, sync | out);
+
+ setbits8(&wkup_gpio->wkup_ddr, reset);
+ setbits32(&simple_gpio->simple_ddr, sync | out);
+
+ /* Assert cold reset */
+ clrbits32(&simple_gpio->simple_dvo, sync | out);
+ clrbits8(&wkup_gpio->wkup_dvo, reset);
+
+ /* wait for 1 us */
+ udelay(1);
+
+ /* Deassert reset */
+ setbits8(&wkup_gpio->wkup_dvo, reset);
+
+ /* wait at least 200ns */
+ /* 7 ~= (200ns * timebase) / ns2sec */
+ __delay(7);
+
+ /* Restore pin-muxing */
+ out_be32(&simple_gpio->port_config, mux);
+
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL(mpc5200_psc_ac97_gpio_reset);
diff --git a/arch/powerpc/platforms/52xx/mpc52xx_gpt.c b/arch/powerpc/platforms/52xx/mpc52xx_gpt.c
new file mode 100644
index 000000000..8c0d324f6
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/mpc52xx_gpt.c
@@ -0,0 +1,788 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MPC5200 General Purpose Timer device driver
+ *
+ * Copyright (c) 2009 Secret Lab Technologies Ltd.
+ * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
+ *
+ * This file is a driver for the the General Purpose Timer (gpt) devices
+ * found on the MPC5200 SoC. Each timer has an IO pin which can be used
+ * for GPIO or can be used to raise interrupts. The timer function can
+ * be used independently from the IO pin, or it can be used to control
+ * output signals or measure input signals.
+ *
+ * This driver supports the GPIO and IRQ controller functions of the GPT
+ * device. Timer functions are not yet supported.
+ *
+ * The timer gpt0 can be used as watchdog (wdt). If the wdt mode is used,
+ * this prevents the use of any gpt0 gpt function (i.e. they will fail with
+ * -EBUSY). Thus, the safety wdt function always has precedence over the gpt
+ * function. If the kernel has been compiled with CONFIG_WATCHDOG_NOWAYOUT,
+ * this means that gpt0 is locked in wdt mode until the next reboot - this
+ * may be a requirement in safety applications.
+ *
+ * To use the GPIO function, the following two properties must be added
+ * to the device tree node for the gpt device (typically in the .dts file
+ * for the board):
+ * gpio-controller;
+ * #gpio-cells = < 2 >;
+ * This driver will register the GPIO pin if it finds the gpio-controller
+ * property in the device tree.
+ *
+ * To use the IRQ controller function, the following two properties must
+ * be added to the device tree node for the gpt device:
+ * interrupt-controller;
+ * #interrupt-cells = < 1 >;
+ * The IRQ controller binding only uses one cell to specify the interrupt,
+ * and the IRQ flags are encoded in the cell. A cell is not used to encode
+ * the IRQ number because the GPT only has a single IRQ source. For flags,
+ * a value of '1' means rising edge sensitive and '2' means falling edge.
+ *
+ * The GPIO and the IRQ controller functions can be used at the same time,
+ * but in this use case the IO line will only work as an input. Trying to
+ * use it as a GPIO output will not work.
+ *
+ * When using the GPIO line as an output, it can either be driven as normal
+ * IO, or it can be an Open Collector (OC) output. At the moment it is the
+ * responsibility of either the bootloader or the platform setup code to set
+ * the output mode. This driver does not change the output mode setting.
+ */
+
+#include <linux/device.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/watchdog.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/module.h>
+#include <asm/div64.h>
+#include <asm/mpc52xx.h>
+
+MODULE_DESCRIPTION("Freescale MPC52xx gpt driver");
+MODULE_AUTHOR("Sascha Hauer, Grant Likely, Albrecht Dreß");
+MODULE_LICENSE("GPL");
+
+/**
+ * struct mpc52xx_gpt - Private data structure for MPC52xx GPT driver
+ * @dev: pointer to device structure
+ * @regs: virtual address of GPT registers
+ * @lock: spinlock to coordinate between different functions.
+ * @gc: gpio_chip instance structure; used when GPIO is enabled
+ * @irqhost: Pointer to irq_domain instance; used when IRQ mode is supported
+ * @wdt_mode: only relevant for gpt0: bit 0 (MPC52xx_GPT_CAN_WDT) indicates
+ * if the gpt may be used as wdt, bit 1 (MPC52xx_GPT_IS_WDT) indicates
+ * if the timer is actively used as wdt which blocks gpt functions
+ */
+struct mpc52xx_gpt_priv {
+ struct list_head list; /* List of all GPT devices */
+ struct device *dev;
+ struct mpc52xx_gpt __iomem *regs;
+ raw_spinlock_t lock;
+ struct irq_domain *irqhost;
+ u32 ipb_freq;
+ u8 wdt_mode;
+
+#if defined(CONFIG_GPIOLIB)
+ struct gpio_chip gc;
+#endif
+};
+
+LIST_HEAD(mpc52xx_gpt_list);
+DEFINE_MUTEX(mpc52xx_gpt_list_mutex);
+
+#define MPC52xx_GPT_MODE_MS_MASK (0x07)
+#define MPC52xx_GPT_MODE_MS_IC (0x01)
+#define MPC52xx_GPT_MODE_MS_OC (0x02)
+#define MPC52xx_GPT_MODE_MS_PWM (0x03)
+#define MPC52xx_GPT_MODE_MS_GPIO (0x04)
+
+#define MPC52xx_GPT_MODE_GPIO_MASK (0x30)
+#define MPC52xx_GPT_MODE_GPIO_OUT_LOW (0x20)
+#define MPC52xx_GPT_MODE_GPIO_OUT_HIGH (0x30)
+
+#define MPC52xx_GPT_MODE_COUNTER_ENABLE (0x1000)
+#define MPC52xx_GPT_MODE_CONTINUOUS (0x0400)
+#define MPC52xx_GPT_MODE_OPEN_DRAIN (0x0200)
+#define MPC52xx_GPT_MODE_IRQ_EN (0x0100)
+#define MPC52xx_GPT_MODE_WDT_EN (0x8000)
+
+#define MPC52xx_GPT_MODE_ICT_MASK (0x030000)
+#define MPC52xx_GPT_MODE_ICT_RISING (0x010000)
+#define MPC52xx_GPT_MODE_ICT_FALLING (0x020000)
+#define MPC52xx_GPT_MODE_ICT_TOGGLE (0x030000)
+
+#define MPC52xx_GPT_MODE_WDT_PING (0xa5)
+
+#define MPC52xx_GPT_STATUS_IRQMASK (0x000f)
+
+#define MPC52xx_GPT_CAN_WDT (1 << 0)
+#define MPC52xx_GPT_IS_WDT (1 << 1)
+
+
+/* ---------------------------------------------------------------------
+ * Cascaded interrupt controller hooks
+ */
+
+static void mpc52xx_gpt_irq_unmask(struct irq_data *d)
+{
+ struct mpc52xx_gpt_priv *gpt = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gpt->lock, flags);
+ setbits32(&gpt->regs->mode, MPC52xx_GPT_MODE_IRQ_EN);
+ raw_spin_unlock_irqrestore(&gpt->lock, flags);
+}
+
+static void mpc52xx_gpt_irq_mask(struct irq_data *d)
+{
+ struct mpc52xx_gpt_priv *gpt = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gpt->lock, flags);
+ clrbits32(&gpt->regs->mode, MPC52xx_GPT_MODE_IRQ_EN);
+ raw_spin_unlock_irqrestore(&gpt->lock, flags);
+}
+
+static void mpc52xx_gpt_irq_ack(struct irq_data *d)
+{
+ struct mpc52xx_gpt_priv *gpt = irq_data_get_irq_chip_data(d);
+
+ out_be32(&gpt->regs->status, MPC52xx_GPT_STATUS_IRQMASK);
+}
+
+static int mpc52xx_gpt_irq_set_type(struct irq_data *d, unsigned int flow_type)
+{
+ struct mpc52xx_gpt_priv *gpt = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+ u32 reg;
+
+ dev_dbg(gpt->dev, "%s: virq=%i type=%x\n", __func__, d->irq, flow_type);
+
+ raw_spin_lock_irqsave(&gpt->lock, flags);
+ reg = in_be32(&gpt->regs->mode) & ~MPC52xx_GPT_MODE_ICT_MASK;
+ if (flow_type & IRQF_TRIGGER_RISING)
+ reg |= MPC52xx_GPT_MODE_ICT_RISING;
+ if (flow_type & IRQF_TRIGGER_FALLING)
+ reg |= MPC52xx_GPT_MODE_ICT_FALLING;
+ out_be32(&gpt->regs->mode, reg);
+ raw_spin_unlock_irqrestore(&gpt->lock, flags);
+
+ return 0;
+}
+
+static struct irq_chip mpc52xx_gpt_irq_chip = {
+ .name = "MPC52xx GPT",
+ .irq_unmask = mpc52xx_gpt_irq_unmask,
+ .irq_mask = mpc52xx_gpt_irq_mask,
+ .irq_ack = mpc52xx_gpt_irq_ack,
+ .irq_set_type = mpc52xx_gpt_irq_set_type,
+};
+
+static void mpc52xx_gpt_irq_cascade(struct irq_desc *desc)
+{
+ struct mpc52xx_gpt_priv *gpt = irq_desc_get_handler_data(desc);
+ int sub_virq;
+ u32 status;
+
+ status = in_be32(&gpt->regs->status) & MPC52xx_GPT_STATUS_IRQMASK;
+ if (status) {
+ sub_virq = irq_linear_revmap(gpt->irqhost, 0);
+ generic_handle_irq(sub_virq);
+ }
+}
+
+static int mpc52xx_gpt_irq_map(struct irq_domain *h, unsigned int virq,
+ irq_hw_number_t hw)
+{
+ struct mpc52xx_gpt_priv *gpt = h->host_data;
+
+ dev_dbg(gpt->dev, "%s: h=%p, virq=%i\n", __func__, h, virq);
+ irq_set_chip_data(virq, gpt);
+ irq_set_chip_and_handler(virq, &mpc52xx_gpt_irq_chip, handle_edge_irq);
+
+ return 0;
+}
+
+static int mpc52xx_gpt_irq_xlate(struct irq_domain *h, struct device_node *ct,
+ const u32 *intspec, unsigned int intsize,
+ irq_hw_number_t *out_hwirq,
+ unsigned int *out_flags)
+{
+ struct mpc52xx_gpt_priv *gpt = h->host_data;
+
+ dev_dbg(gpt->dev, "%s: flags=%i\n", __func__, intspec[0]);
+
+ if ((intsize < 1) || (intspec[0] > 3)) {
+ dev_err(gpt->dev, "bad irq specifier in %pOF\n", ct);
+ return -EINVAL;
+ }
+
+ *out_hwirq = 0; /* The GPT only has 1 IRQ line */
+ *out_flags = intspec[0];
+
+ return 0;
+}
+
+static const struct irq_domain_ops mpc52xx_gpt_irq_ops = {
+ .map = mpc52xx_gpt_irq_map,
+ .xlate = mpc52xx_gpt_irq_xlate,
+};
+
+static void
+mpc52xx_gpt_irq_setup(struct mpc52xx_gpt_priv *gpt, struct device_node *node)
+{
+ int cascade_virq;
+ unsigned long flags;
+ u32 mode;
+
+ cascade_virq = irq_of_parse_and_map(node, 0);
+ if (!cascade_virq)
+ return;
+
+ gpt->irqhost = irq_domain_add_linear(node, 1, &mpc52xx_gpt_irq_ops, gpt);
+ if (!gpt->irqhost) {
+ dev_err(gpt->dev, "irq_domain_add_linear() failed\n");
+ return;
+ }
+
+ irq_set_handler_data(cascade_virq, gpt);
+ irq_set_chained_handler(cascade_virq, mpc52xx_gpt_irq_cascade);
+
+ /* If the GPT is currently disabled, then change it to be in Input
+ * Capture mode. If the mode is non-zero, then the pin could be
+ * already in use for something. */
+ raw_spin_lock_irqsave(&gpt->lock, flags);
+ mode = in_be32(&gpt->regs->mode);
+ if ((mode & MPC52xx_GPT_MODE_MS_MASK) == 0)
+ out_be32(&gpt->regs->mode, mode | MPC52xx_GPT_MODE_MS_IC);
+ raw_spin_unlock_irqrestore(&gpt->lock, flags);
+
+ dev_dbg(gpt->dev, "%s() complete. virq=%i\n", __func__, cascade_virq);
+}
+
+
+/* ---------------------------------------------------------------------
+ * GPIOLIB hooks
+ */
+#if defined(CONFIG_GPIOLIB)
+static int mpc52xx_gpt_gpio_get(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct mpc52xx_gpt_priv *gpt = gpiochip_get_data(gc);
+
+ return (in_be32(&gpt->regs->status) >> 8) & 1;
+}
+
+static void
+mpc52xx_gpt_gpio_set(struct gpio_chip *gc, unsigned int gpio, int v)
+{
+ struct mpc52xx_gpt_priv *gpt = gpiochip_get_data(gc);
+ unsigned long flags;
+ u32 r;
+
+ dev_dbg(gpt->dev, "%s: gpio:%d v:%d\n", __func__, gpio, v);
+ r = v ? MPC52xx_GPT_MODE_GPIO_OUT_HIGH : MPC52xx_GPT_MODE_GPIO_OUT_LOW;
+
+ raw_spin_lock_irqsave(&gpt->lock, flags);
+ clrsetbits_be32(&gpt->regs->mode, MPC52xx_GPT_MODE_GPIO_MASK, r);
+ raw_spin_unlock_irqrestore(&gpt->lock, flags);
+}
+
+static int mpc52xx_gpt_gpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
+{
+ struct mpc52xx_gpt_priv *gpt = gpiochip_get_data(gc);
+ unsigned long flags;
+
+ dev_dbg(gpt->dev, "%s: gpio:%d\n", __func__, gpio);
+
+ raw_spin_lock_irqsave(&gpt->lock, flags);
+ clrbits32(&gpt->regs->mode, MPC52xx_GPT_MODE_GPIO_MASK);
+ raw_spin_unlock_irqrestore(&gpt->lock, flags);
+
+ return 0;
+}
+
+static int
+mpc52xx_gpt_gpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+ mpc52xx_gpt_gpio_set(gc, gpio, val);
+ return 0;
+}
+
+static void
+mpc52xx_gpt_gpio_setup(struct mpc52xx_gpt_priv *gpt, struct device_node *node)
+{
+ int rc;
+
+ /* Only setup GPIO if the device tree claims the GPT is
+ * a GPIO controller */
+ if (!of_find_property(node, "gpio-controller", NULL))
+ return;
+
+ gpt->gc.label = kasprintf(GFP_KERNEL, "%pOF", node);
+ if (!gpt->gc.label) {
+ dev_err(gpt->dev, "out of memory\n");
+ return;
+ }
+
+ gpt->gc.ngpio = 1;
+ gpt->gc.direction_input = mpc52xx_gpt_gpio_dir_in;
+ gpt->gc.direction_output = mpc52xx_gpt_gpio_dir_out;
+ gpt->gc.get = mpc52xx_gpt_gpio_get;
+ gpt->gc.set = mpc52xx_gpt_gpio_set;
+ gpt->gc.base = -1;
+ gpt->gc.of_node = node;
+
+ /* Setup external pin in GPIO mode */
+ clrsetbits_be32(&gpt->regs->mode, MPC52xx_GPT_MODE_MS_MASK,
+ MPC52xx_GPT_MODE_MS_GPIO);
+
+ rc = gpiochip_add_data(&gpt->gc, gpt);
+ if (rc)
+ dev_err(gpt->dev, "gpiochip_add_data() failed; rc=%i\n", rc);
+
+ dev_dbg(gpt->dev, "%s() complete.\n", __func__);
+}
+#else /* defined(CONFIG_GPIOLIB) */
+static void
+mpc52xx_gpt_gpio_setup(struct mpc52xx_gpt_priv *p, struct device_node *np) { }
+#endif /* defined(CONFIG_GPIOLIB) */
+
+/***********************************************************************
+ * Timer API
+ */
+
+/**
+ * mpc52xx_gpt_from_irq - Return the GPT device associated with an IRQ number
+ * @irq: irq of timer.
+ */
+struct mpc52xx_gpt_priv *mpc52xx_gpt_from_irq(int irq)
+{
+ struct mpc52xx_gpt_priv *gpt;
+ struct list_head *pos;
+
+ /* Iterate over the list of timers looking for a matching device */
+ mutex_lock(&mpc52xx_gpt_list_mutex);
+ list_for_each(pos, &mpc52xx_gpt_list) {
+ gpt = container_of(pos, struct mpc52xx_gpt_priv, list);
+ if (gpt->irqhost && irq == irq_linear_revmap(gpt->irqhost, 0)) {
+ mutex_unlock(&mpc52xx_gpt_list_mutex);
+ return gpt;
+ }
+ }
+ mutex_unlock(&mpc52xx_gpt_list_mutex);
+
+ return NULL;
+}
+EXPORT_SYMBOL(mpc52xx_gpt_from_irq);
+
+static int mpc52xx_gpt_do_start(struct mpc52xx_gpt_priv *gpt, u64 period,
+ int continuous, int as_wdt)
+{
+ u32 clear, set;
+ u64 clocks;
+ u32 prescale;
+ unsigned long flags;
+
+ clear = MPC52xx_GPT_MODE_MS_MASK | MPC52xx_GPT_MODE_CONTINUOUS;
+ set = MPC52xx_GPT_MODE_MS_GPIO | MPC52xx_GPT_MODE_COUNTER_ENABLE;
+ if (as_wdt) {
+ clear |= MPC52xx_GPT_MODE_IRQ_EN;
+ set |= MPC52xx_GPT_MODE_WDT_EN;
+ } else if (continuous)
+ set |= MPC52xx_GPT_MODE_CONTINUOUS;
+
+ /* Determine the number of clocks in the requested period. 64 bit
+ * arithmatic is done here to preserve the precision until the value
+ * is scaled back down into the u32 range. Period is in 'ns', bus
+ * frequency is in Hz. */
+ clocks = period * (u64)gpt->ipb_freq;
+ do_div(clocks, 1000000000); /* Scale it down to ns range */
+
+ /* This device cannot handle a clock count greater than 32 bits */
+ if (clocks > 0xffffffff)
+ return -EINVAL;
+
+ /* Calculate the prescaler and count values from the clocks value.
+ * 'clocks' is the number of clock ticks in the period. The timer
+ * has 16 bit precision and a 16 bit prescaler. Prescaler is
+ * calculated by integer dividing the clocks by 0x10000 (shifting
+ * down 16 bits) to obtain the smallest possible divisor for clocks
+ * to get a 16 bit count value.
+ *
+ * Note: the prescale register is '1' based, not '0' based. ie. a
+ * value of '1' means divide the clock by one. 0xffff divides the
+ * clock by 0xffff. '0x0000' does not divide by zero, but wraps
+ * around and divides by 0x10000. That is why prescale must be
+ * a u32 variable, not a u16, for this calculation. */
+ prescale = (clocks >> 16) + 1;
+ do_div(clocks, prescale);
+ if (clocks > 0xffff) {
+ pr_err("calculation error; prescale:%x clocks:%llx\n",
+ prescale, clocks);
+ return -EINVAL;
+ }
+
+ /* Set and enable the timer, reject an attempt to use a wdt as gpt */
+ raw_spin_lock_irqsave(&gpt->lock, flags);
+ if (as_wdt)
+ gpt->wdt_mode |= MPC52xx_GPT_IS_WDT;
+ else if ((gpt->wdt_mode & MPC52xx_GPT_IS_WDT) != 0) {
+ raw_spin_unlock_irqrestore(&gpt->lock, flags);
+ return -EBUSY;
+ }
+ out_be32(&gpt->regs->count, prescale << 16 | clocks);
+ clrsetbits_be32(&gpt->regs->mode, clear, set);
+ raw_spin_unlock_irqrestore(&gpt->lock, flags);
+
+ return 0;
+}
+
+/**
+ * mpc52xx_gpt_start_timer - Set and enable the GPT timer
+ * @gpt: Pointer to gpt private data structure
+ * @period: period of timer in ns; max. ~130s @ 33MHz IPB clock
+ * @continuous: set to 1 to make timer continuous free running
+ *
+ * An interrupt will be generated every time the timer fires
+ */
+int mpc52xx_gpt_start_timer(struct mpc52xx_gpt_priv *gpt, u64 period,
+ int continuous)
+{
+ return mpc52xx_gpt_do_start(gpt, period, continuous, 0);
+}
+EXPORT_SYMBOL(mpc52xx_gpt_start_timer);
+
+/**
+ * mpc52xx_gpt_stop_timer - Stop a gpt
+ * @gpt: Pointer to gpt private data structure
+ *
+ * Returns an error if attempting to stop a wdt
+ */
+int mpc52xx_gpt_stop_timer(struct mpc52xx_gpt_priv *gpt)
+{
+ unsigned long flags;
+
+ /* reject the operation if the timer is used as watchdog (gpt 0 only) */
+ raw_spin_lock_irqsave(&gpt->lock, flags);
+ if ((gpt->wdt_mode & MPC52xx_GPT_IS_WDT) != 0) {
+ raw_spin_unlock_irqrestore(&gpt->lock, flags);
+ return -EBUSY;
+ }
+
+ clrbits32(&gpt->regs->mode, MPC52xx_GPT_MODE_COUNTER_ENABLE);
+ raw_spin_unlock_irqrestore(&gpt->lock, flags);
+ return 0;
+}
+EXPORT_SYMBOL(mpc52xx_gpt_stop_timer);
+
+/**
+ * mpc52xx_gpt_timer_period - Read the timer period
+ * @gpt: Pointer to gpt private data structure
+ *
+ * Returns the timer period in ns
+ */
+u64 mpc52xx_gpt_timer_period(struct mpc52xx_gpt_priv *gpt)
+{
+ u64 period;
+ u64 prescale;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gpt->lock, flags);
+ period = in_be32(&gpt->regs->count);
+ raw_spin_unlock_irqrestore(&gpt->lock, flags);
+
+ prescale = period >> 16;
+ period &= 0xffff;
+ if (prescale == 0)
+ prescale = 0x10000;
+ period = period * prescale * 1000000000ULL;
+ do_div(period, (u64)gpt->ipb_freq);
+ return period;
+}
+EXPORT_SYMBOL(mpc52xx_gpt_timer_period);
+
+#if defined(CONFIG_MPC5200_WDT)
+/***********************************************************************
+ * Watchdog API for gpt0
+ */
+
+#define WDT_IDENTITY "mpc52xx watchdog on GPT0"
+
+/* wdt_is_active stores whether or not the /dev/watchdog device is opened */
+static unsigned long wdt_is_active;
+
+/* wdt-capable gpt */
+static struct mpc52xx_gpt_priv *mpc52xx_gpt_wdt;
+
+/* low-level wdt functions */
+static inline void mpc52xx_gpt_wdt_ping(struct mpc52xx_gpt_priv *gpt_wdt)
+{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gpt_wdt->lock, flags);
+ out_8((u8 *) &gpt_wdt->regs->mode, MPC52xx_GPT_MODE_WDT_PING);
+ raw_spin_unlock_irqrestore(&gpt_wdt->lock, flags);
+}
+
+/* wdt misc device api */
+static ssize_t mpc52xx_wdt_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ struct mpc52xx_gpt_priv *gpt_wdt = file->private_data;
+ mpc52xx_gpt_wdt_ping(gpt_wdt);
+ return 0;
+}
+
+static const struct watchdog_info mpc5200_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = WDT_IDENTITY,
+};
+
+static long mpc52xx_wdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct mpc52xx_gpt_priv *gpt_wdt = file->private_data;
+ int __user *data = (int __user *)arg;
+ int timeout;
+ u64 real_timeout;
+ int ret = 0;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = copy_to_user(data, &mpc5200_wdt_info,
+ sizeof(mpc5200_wdt_info));
+ if (ret)
+ ret = -EFAULT;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(0, data);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ mpc52xx_gpt_wdt_ping(gpt_wdt);
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(timeout, data);
+ if (ret)
+ break;
+ real_timeout = (u64) timeout * 1000000000ULL;
+ ret = mpc52xx_gpt_do_start(gpt_wdt, real_timeout, 0, 1);
+ if (ret)
+ break;
+ /* fall through and return the timeout */
+
+ case WDIOC_GETTIMEOUT:
+ /* we need to round here as to avoid e.g. the following
+ * situation:
+ * - timeout requested is 1 second;
+ * - real timeout @33MHz is 999997090ns
+ * - the int divide by 10^9 will return 0.
+ */
+ real_timeout =
+ mpc52xx_gpt_timer_period(gpt_wdt) + 500000000ULL;
+ do_div(real_timeout, 1000000000ULL);
+ timeout = (int) real_timeout;
+ ret = put_user(timeout, data);
+ break;
+
+ default:
+ ret = -ENOTTY;
+ }
+ return ret;
+}
+
+static int mpc52xx_wdt_open(struct inode *inode, struct file *file)
+{
+ int ret;
+
+ /* sanity check */
+ if (!mpc52xx_gpt_wdt)
+ return -ENODEV;
+
+ /* /dev/watchdog can only be opened once */
+ if (test_and_set_bit(0, &wdt_is_active))
+ return -EBUSY;
+
+ /* Set and activate the watchdog with 30 seconds timeout */
+ ret = mpc52xx_gpt_do_start(mpc52xx_gpt_wdt, 30ULL * 1000000000ULL,
+ 0, 1);
+ if (ret) {
+ clear_bit(0, &wdt_is_active);
+ return ret;
+ }
+
+ file->private_data = mpc52xx_gpt_wdt;
+ return stream_open(inode, file);
+}
+
+static int mpc52xx_wdt_release(struct inode *inode, struct file *file)
+{
+ /* note: releasing the wdt in NOWAYOUT-mode does not stop it */
+#if !defined(CONFIG_WATCHDOG_NOWAYOUT)
+ struct mpc52xx_gpt_priv *gpt_wdt = file->private_data;
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&gpt_wdt->lock, flags);
+ clrbits32(&gpt_wdt->regs->mode,
+ MPC52xx_GPT_MODE_COUNTER_ENABLE | MPC52xx_GPT_MODE_WDT_EN);
+ gpt_wdt->wdt_mode &= ~MPC52xx_GPT_IS_WDT;
+ raw_spin_unlock_irqrestore(&gpt_wdt->lock, flags);
+#endif
+ clear_bit(0, &wdt_is_active);
+ return 0;
+}
+
+
+static const struct file_operations mpc52xx_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = mpc52xx_wdt_write,
+ .unlocked_ioctl = mpc52xx_wdt_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = mpc52xx_wdt_open,
+ .release = mpc52xx_wdt_release,
+};
+
+static struct miscdevice mpc52xx_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &mpc52xx_wdt_fops,
+};
+
+static int mpc52xx_gpt_wdt_init(void)
+{
+ int err;
+
+ /* try to register the watchdog misc device */
+ err = misc_register(&mpc52xx_wdt_miscdev);
+ if (err)
+ pr_err("%s: cannot register watchdog device\n", WDT_IDENTITY);
+ else
+ pr_info("%s: watchdog device registered\n", WDT_IDENTITY);
+ return err;
+}
+
+static int mpc52xx_gpt_wdt_setup(struct mpc52xx_gpt_priv *gpt,
+ const u32 *period)
+{
+ u64 real_timeout;
+
+ /* remember the gpt for the wdt operation */
+ mpc52xx_gpt_wdt = gpt;
+
+ /* configure the wdt if the device tree contained a timeout */
+ if (!period || *period == 0)
+ return 0;
+
+ real_timeout = (u64) *period * 1000000000ULL;
+ if (mpc52xx_gpt_do_start(gpt, real_timeout, 0, 1))
+ dev_warn(gpt->dev, "starting as wdt failed\n");
+ else
+ dev_info(gpt->dev, "watchdog set to %us timeout\n", *period);
+ return 0;
+}
+
+#else
+
+static int mpc52xx_gpt_wdt_init(void)
+{
+ return 0;
+}
+
+static inline int mpc52xx_gpt_wdt_setup(struct mpc52xx_gpt_priv *gpt,
+ const u32 *period)
+{
+ return 0;
+}
+
+#endif /* CONFIG_MPC5200_WDT */
+
+/* ---------------------------------------------------------------------
+ * of_platform bus binding code
+ */
+static int mpc52xx_gpt_probe(struct platform_device *ofdev)
+{
+ struct mpc52xx_gpt_priv *gpt;
+
+ gpt = devm_kzalloc(&ofdev->dev, sizeof *gpt, GFP_KERNEL);
+ if (!gpt)
+ return -ENOMEM;
+
+ raw_spin_lock_init(&gpt->lock);
+ gpt->dev = &ofdev->dev;
+ gpt->ipb_freq = mpc5xxx_get_bus_frequency(ofdev->dev.of_node);
+ gpt->regs = of_iomap(ofdev->dev.of_node, 0);
+ if (!gpt->regs)
+ return -ENOMEM;
+
+ dev_set_drvdata(&ofdev->dev, gpt);
+
+ mpc52xx_gpt_gpio_setup(gpt, ofdev->dev.of_node);
+ mpc52xx_gpt_irq_setup(gpt, ofdev->dev.of_node);
+
+ mutex_lock(&mpc52xx_gpt_list_mutex);
+ list_add(&gpt->list, &mpc52xx_gpt_list);
+ mutex_unlock(&mpc52xx_gpt_list_mutex);
+
+ /* check if this device could be a watchdog */
+ if (of_get_property(ofdev->dev.of_node, "fsl,has-wdt", NULL) ||
+ of_get_property(ofdev->dev.of_node, "has-wdt", NULL)) {
+ const u32 *on_boot_wdt;
+
+ gpt->wdt_mode = MPC52xx_GPT_CAN_WDT;
+ on_boot_wdt = of_get_property(ofdev->dev.of_node,
+ "fsl,wdt-on-boot", NULL);
+ if (on_boot_wdt) {
+ dev_info(gpt->dev, "used as watchdog\n");
+ gpt->wdt_mode |= MPC52xx_GPT_IS_WDT;
+ } else
+ dev_info(gpt->dev, "can function as watchdog\n");
+ mpc52xx_gpt_wdt_setup(gpt, on_boot_wdt);
+ }
+
+ return 0;
+}
+
+static int mpc52xx_gpt_remove(struct platform_device *ofdev)
+{
+ return -EBUSY;
+}
+
+static const struct of_device_id mpc52xx_gpt_match[] = {
+ { .compatible = "fsl,mpc5200-gpt", },
+
+ /* Depreciated compatible values; don't use for new dts files */
+ { .compatible = "fsl,mpc5200-gpt-gpio", },
+ { .compatible = "mpc5200-gpt", },
+ {}
+};
+
+static struct platform_driver mpc52xx_gpt_driver = {
+ .driver = {
+ .name = "mpc52xx-gpt",
+ .of_match_table = mpc52xx_gpt_match,
+ },
+ .probe = mpc52xx_gpt_probe,
+ .remove = mpc52xx_gpt_remove,
+};
+
+static int __init mpc52xx_gpt_init(void)
+{
+ return platform_driver_register(&mpc52xx_gpt_driver);
+}
+
+/* Make sure GPIOs and IRQs get set up before anyone tries to use them */
+subsys_initcall(mpc52xx_gpt_init);
+device_initcall(mpc52xx_gpt_wdt_init);
diff --git a/arch/powerpc/platforms/52xx/mpc52xx_lpbfifo.c b/arch/powerpc/platforms/52xx/mpc52xx_lpbfifo.c
new file mode 100644
index 000000000..22e264bd3
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/mpc52xx_lpbfifo.c
@@ -0,0 +1,581 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LocalPlus Bus FIFO driver for the Freescale MPC52xx.
+ *
+ * Copyright (C) 2009 Secret Lab Technologies Ltd.
+ *
+ * Todo:
+ * - Add support for multiple requests to be queued.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/spinlock.h>
+#include <linux/module.h>
+#include <asm/io.h>
+#include <asm/prom.h>
+#include <asm/mpc52xx.h>
+#include <asm/time.h>
+
+#include <linux/fsl/bestcomm/bestcomm.h>
+#include <linux/fsl/bestcomm/bestcomm_priv.h>
+#include <linux/fsl/bestcomm/gen_bd.h>
+
+MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
+MODULE_DESCRIPTION("MPC5200 LocalPlus FIFO device driver");
+MODULE_LICENSE("GPL");
+
+#define LPBFIFO_REG_PACKET_SIZE (0x00)
+#define LPBFIFO_REG_START_ADDRESS (0x04)
+#define LPBFIFO_REG_CONTROL (0x08)
+#define LPBFIFO_REG_ENABLE (0x0C)
+#define LPBFIFO_REG_BYTES_DONE_STATUS (0x14)
+#define LPBFIFO_REG_FIFO_DATA (0x40)
+#define LPBFIFO_REG_FIFO_STATUS (0x44)
+#define LPBFIFO_REG_FIFO_CONTROL (0x48)
+#define LPBFIFO_REG_FIFO_ALARM (0x4C)
+
+struct mpc52xx_lpbfifo {
+ struct device *dev;
+ phys_addr_t regs_phys;
+ void __iomem *regs;
+ int irq;
+ spinlock_t lock;
+
+ struct bcom_task *bcom_tx_task;
+ struct bcom_task *bcom_rx_task;
+ struct bcom_task *bcom_cur_task;
+
+ /* Current state data */
+ struct mpc52xx_lpbfifo_request *req;
+ int dma_irqs_enabled;
+};
+
+/* The MPC5200 has only one fifo, so only need one instance structure */
+static struct mpc52xx_lpbfifo lpbfifo;
+
+/**
+ * mpc52xx_lpbfifo_kick - Trigger the next block of data to be transferred
+ */
+static void mpc52xx_lpbfifo_kick(struct mpc52xx_lpbfifo_request *req)
+{
+ size_t transfer_size = req->size - req->pos;
+ struct bcom_bd *bd;
+ void __iomem *reg;
+ u32 *data;
+ int i;
+ int bit_fields;
+ int dma = !(req->flags & MPC52XX_LPBFIFO_FLAG_NO_DMA);
+ int write = req->flags & MPC52XX_LPBFIFO_FLAG_WRITE;
+ int poll_dma = req->flags & MPC52XX_LPBFIFO_FLAG_POLL_DMA;
+
+ /* Set and clear the reset bits; is good practice in User Manual */
+ out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x01010000);
+
+ /* set master enable bit */
+ out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x00000001);
+ if (!dma) {
+ /* While the FIFO can be setup for transfer sizes as large as
+ * 16M-1, the FIFO itself is only 512 bytes deep and it does
+ * not generate interrupts for FIFO full events (only transfer
+ * complete will raise an IRQ). Therefore when not using
+ * Bestcomm to drive the FIFO it needs to either be polled, or
+ * transfers need to constrained to the size of the fifo.
+ *
+ * This driver restricts the size of the transfer
+ */
+ if (transfer_size > 512)
+ transfer_size = 512;
+
+ /* Load the FIFO with data */
+ if (write) {
+ reg = lpbfifo.regs + LPBFIFO_REG_FIFO_DATA;
+ data = req->data + req->pos;
+ for (i = 0; i < transfer_size; i += 4)
+ out_be32(reg, *data++);
+ }
+
+ /* Unmask both error and completion irqs */
+ out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x00000301);
+ } else {
+ /* Choose the correct direction
+ *
+ * Configure the watermarks so DMA will always complete correctly.
+ * It may be worth experimenting with the ALARM value to see if
+ * there is a performance impacit. However, if it is wrong there
+ * is a risk of DMA not transferring the last chunk of data
+ */
+ if (write) {
+ out_be32(lpbfifo.regs + LPBFIFO_REG_FIFO_ALARM, 0x1e4);
+ out_8(lpbfifo.regs + LPBFIFO_REG_FIFO_CONTROL, 7);
+ lpbfifo.bcom_cur_task = lpbfifo.bcom_tx_task;
+ } else {
+ out_be32(lpbfifo.regs + LPBFIFO_REG_FIFO_ALARM, 0x1ff);
+ out_8(lpbfifo.regs + LPBFIFO_REG_FIFO_CONTROL, 0);
+ lpbfifo.bcom_cur_task = lpbfifo.bcom_rx_task;
+
+ if (poll_dma) {
+ if (lpbfifo.dma_irqs_enabled) {
+ disable_irq(bcom_get_task_irq(lpbfifo.bcom_rx_task));
+ lpbfifo.dma_irqs_enabled = 0;
+ }
+ } else {
+ if (!lpbfifo.dma_irqs_enabled) {
+ enable_irq(bcom_get_task_irq(lpbfifo.bcom_rx_task));
+ lpbfifo.dma_irqs_enabled = 1;
+ }
+ }
+ }
+
+ bd = bcom_prepare_next_buffer(lpbfifo.bcom_cur_task);
+ bd->status = transfer_size;
+ if (!write) {
+ /*
+ * In the DMA read case, the DMA doesn't complete,
+ * possibly due to incorrect watermarks in the ALARM
+ * and CONTROL regs. For now instead of trying to
+ * determine the right watermarks that will make this
+ * work, just increase the number of bytes the FIFO is
+ * expecting.
+ *
+ * When submitting another operation, the FIFO will get
+ * reset, so the condition of the FIFO waiting for a
+ * non-existent 4 bytes will get cleared.
+ */
+ transfer_size += 4; /* BLECH! */
+ }
+ bd->data[0] = req->data_phys + req->pos;
+ bcom_submit_next_buffer(lpbfifo.bcom_cur_task, NULL);
+
+ /* error irq & master enabled bit */
+ bit_fields = 0x00000201;
+
+ /* Unmask irqs */
+ if (write && (!poll_dma))
+ bit_fields |= 0x00000100; /* completion irq too */
+ out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, bit_fields);
+ }
+
+ /* Set transfer size, width, chip select and READ mode */
+ out_be32(lpbfifo.regs + LPBFIFO_REG_START_ADDRESS,
+ req->offset + req->pos);
+ out_be32(lpbfifo.regs + LPBFIFO_REG_PACKET_SIZE, transfer_size);
+
+ bit_fields = req->cs << 24 | 0x000008;
+ if (!write)
+ bit_fields |= 0x010000; /* read mode */
+ out_be32(lpbfifo.regs + LPBFIFO_REG_CONTROL, bit_fields);
+
+ /* Kick it off */
+ if (!lpbfifo.req->defer_xfer_start)
+ out_8(lpbfifo.regs + LPBFIFO_REG_PACKET_SIZE, 0x01);
+ if (dma)
+ bcom_enable(lpbfifo.bcom_cur_task);
+}
+
+/**
+ * mpc52xx_lpbfifo_irq - IRQ handler for LPB FIFO
+ *
+ * On transmit, the dma completion irq triggers before the fifo completion
+ * triggers. Handle the dma completion here instead of the LPB FIFO Bestcomm
+ * task completion irq because everything is not really done until the LPB FIFO
+ * completion irq triggers.
+ *
+ * In other words:
+ * For DMA, on receive, the "Fat Lady" is the bestcom completion irq. on
+ * transmit, the fifo completion irq is the "Fat Lady". The opera (or in this
+ * case the DMA/FIFO operation) is not finished until the "Fat Lady" sings.
+ *
+ * Reasons for entering this routine:
+ * 1) PIO mode rx and tx completion irq
+ * 2) DMA interrupt mode tx completion irq
+ * 3) DMA polled mode tx
+ *
+ * Exit conditions:
+ * 1) Transfer aborted
+ * 2) FIFO complete without DMA; more data to do
+ * 3) FIFO complete without DMA; all data transferred
+ * 4) FIFO complete using DMA
+ *
+ * Condition 1 can occur regardless of whether or not DMA is used.
+ * It requires executing the callback to report the error and exiting
+ * immediately.
+ *
+ * Condition 2 requires programming the FIFO with the next block of data
+ *
+ * Condition 3 requires executing the callback to report completion
+ *
+ * Condition 4 means the same as 3, except that we also retrieve the bcom
+ * buffer so DMA doesn't get clogged up.
+ *
+ * To make things trickier, the spinlock must be dropped before
+ * executing the callback, otherwise we could end up with a deadlock
+ * or nested spinlock condition. The out path is non-trivial, so
+ * extra fiddling is done to make sure all paths lead to the same
+ * outbound code.
+ */
+static irqreturn_t mpc52xx_lpbfifo_irq(int irq, void *dev_id)
+{
+ struct mpc52xx_lpbfifo_request *req;
+ u32 status = in_8(lpbfifo.regs + LPBFIFO_REG_BYTES_DONE_STATUS);
+ void __iomem *reg;
+ u32 *data;
+ int count, i;
+ int do_callback = 0;
+ u32 ts;
+ unsigned long flags;
+ int dma, write, poll_dma;
+
+ spin_lock_irqsave(&lpbfifo.lock, flags);
+ ts = get_tbl();
+
+ req = lpbfifo.req;
+ if (!req) {
+ spin_unlock_irqrestore(&lpbfifo.lock, flags);
+ pr_err("bogus LPBFIFO IRQ\n");
+ return IRQ_HANDLED;
+ }
+
+ dma = !(req->flags & MPC52XX_LPBFIFO_FLAG_NO_DMA);
+ write = req->flags & MPC52XX_LPBFIFO_FLAG_WRITE;
+ poll_dma = req->flags & MPC52XX_LPBFIFO_FLAG_POLL_DMA;
+
+ if (dma && !write) {
+ spin_unlock_irqrestore(&lpbfifo.lock, flags);
+ pr_err("bogus LPBFIFO IRQ (dma and not writing)\n");
+ return IRQ_HANDLED;
+ }
+
+ if ((status & 0x01) == 0) {
+ goto out;
+ }
+
+ /* check abort bit */
+ if (status & 0x10) {
+ out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x01010000);
+ do_callback = 1;
+ goto out;
+ }
+
+ /* Read result from hardware */
+ count = in_be32(lpbfifo.regs + LPBFIFO_REG_BYTES_DONE_STATUS);
+ count &= 0x00ffffff;
+
+ if (!dma && !write) {
+ /* copy the data out of the FIFO */
+ reg = lpbfifo.regs + LPBFIFO_REG_FIFO_DATA;
+ data = req->data + req->pos;
+ for (i = 0; i < count; i += 4)
+ *data++ = in_be32(reg);
+ }
+
+ /* Update transfer position and count */
+ req->pos += count;
+
+ /* Decide what to do next */
+ if (req->size - req->pos)
+ mpc52xx_lpbfifo_kick(req); /* more work to do */
+ else
+ do_callback = 1;
+
+ out:
+ /* Clear the IRQ */
+ out_8(lpbfifo.regs + LPBFIFO_REG_BYTES_DONE_STATUS, 0x01);
+
+ if (dma && (status & 0x11)) {
+ /*
+ * Count the DMA as complete only when the FIFO completion
+ * status or abort bits are set.
+ *
+ * (status & 0x01) should always be the case except sometimes
+ * when using polled DMA.
+ *
+ * (status & 0x10) {transfer aborted}: This case needs more
+ * testing.
+ */
+ bcom_retrieve_buffer(lpbfifo.bcom_cur_task, &status, NULL);
+ }
+ req->last_byte = ((u8 *)req->data)[req->size - 1];
+
+ /* When the do_callback flag is set; it means the transfer is finished
+ * so set the FIFO as idle */
+ if (do_callback)
+ lpbfifo.req = NULL;
+
+ if (irq != 0) /* don't increment on polled case */
+ req->irq_count++;
+
+ req->irq_ticks += get_tbl() - ts;
+ spin_unlock_irqrestore(&lpbfifo.lock, flags);
+
+ /* Spinlock is released; it is now safe to call the callback */
+ if (do_callback && req->callback)
+ req->callback(req);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * mpc52xx_lpbfifo_bcom_irq - IRQ handler for LPB FIFO Bestcomm task
+ *
+ * Only used when receiving data.
+ */
+static irqreturn_t mpc52xx_lpbfifo_bcom_irq(int irq, void *dev_id)
+{
+ struct mpc52xx_lpbfifo_request *req;
+ unsigned long flags;
+ u32 status;
+ u32 ts;
+
+ spin_lock_irqsave(&lpbfifo.lock, flags);
+ ts = get_tbl();
+
+ req = lpbfifo.req;
+ if (!req || (req->flags & MPC52XX_LPBFIFO_FLAG_NO_DMA)) {
+ spin_unlock_irqrestore(&lpbfifo.lock, flags);
+ return IRQ_HANDLED;
+ }
+
+ if (irq != 0) /* don't increment on polled case */
+ req->irq_count++;
+
+ if (!bcom_buffer_done(lpbfifo.bcom_cur_task)) {
+ spin_unlock_irqrestore(&lpbfifo.lock, flags);
+
+ req->buffer_not_done_cnt++;
+ if ((req->buffer_not_done_cnt % 1000) == 0)
+ pr_err("transfer stalled\n");
+
+ return IRQ_HANDLED;
+ }
+
+ bcom_retrieve_buffer(lpbfifo.bcom_cur_task, &status, NULL);
+
+ req->last_byte = ((u8 *)req->data)[req->size - 1];
+
+ req->pos = status & 0x00ffffff;
+
+ /* Mark the FIFO as idle */
+ lpbfifo.req = NULL;
+
+ /* Release the lock before calling out to the callback. */
+ req->irq_ticks += get_tbl() - ts;
+ spin_unlock_irqrestore(&lpbfifo.lock, flags);
+
+ if (req->callback)
+ req->callback(req);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * mpc52xx_lpbfifo_bcom_poll - Poll for DMA completion
+ */
+void mpc52xx_lpbfifo_poll(void)
+{
+ struct mpc52xx_lpbfifo_request *req = lpbfifo.req;
+ int dma = !(req->flags & MPC52XX_LPBFIFO_FLAG_NO_DMA);
+ int write = req->flags & MPC52XX_LPBFIFO_FLAG_WRITE;
+
+ /*
+ * For more information, see comments on the "Fat Lady"
+ */
+ if (dma && write)
+ mpc52xx_lpbfifo_irq(0, NULL);
+ else
+ mpc52xx_lpbfifo_bcom_irq(0, NULL);
+}
+EXPORT_SYMBOL(mpc52xx_lpbfifo_poll);
+
+/**
+ * mpc52xx_lpbfifo_submit - Submit an LPB FIFO transfer request.
+ * @req: Pointer to request structure
+ */
+int mpc52xx_lpbfifo_submit(struct mpc52xx_lpbfifo_request *req)
+{
+ unsigned long flags;
+
+ if (!lpbfifo.regs)
+ return -ENODEV;
+
+ spin_lock_irqsave(&lpbfifo.lock, flags);
+
+ /* If the req pointer is already set, then a transfer is in progress */
+ if (lpbfifo.req) {
+ spin_unlock_irqrestore(&lpbfifo.lock, flags);
+ return -EBUSY;
+ }
+
+ /* Setup the transfer */
+ lpbfifo.req = req;
+ req->irq_count = 0;
+ req->irq_ticks = 0;
+ req->buffer_not_done_cnt = 0;
+ req->pos = 0;
+
+ mpc52xx_lpbfifo_kick(req);
+ spin_unlock_irqrestore(&lpbfifo.lock, flags);
+ return 0;
+}
+EXPORT_SYMBOL(mpc52xx_lpbfifo_submit);
+
+int mpc52xx_lpbfifo_start_xfer(struct mpc52xx_lpbfifo_request *req)
+{
+ unsigned long flags;
+
+ if (!lpbfifo.regs)
+ return -ENODEV;
+
+ spin_lock_irqsave(&lpbfifo.lock, flags);
+
+ /*
+ * If the req pointer is already set and a transfer was
+ * started on submit, then this transfer is in progress
+ */
+ if (lpbfifo.req && !lpbfifo.req->defer_xfer_start) {
+ spin_unlock_irqrestore(&lpbfifo.lock, flags);
+ return -EBUSY;
+ }
+
+ /*
+ * If the req was previously submitted but not
+ * started, start it now
+ */
+ if (lpbfifo.req && lpbfifo.req == req &&
+ lpbfifo.req->defer_xfer_start) {
+ out_8(lpbfifo.regs + LPBFIFO_REG_PACKET_SIZE, 0x01);
+ }
+
+ spin_unlock_irqrestore(&lpbfifo.lock, flags);
+ return 0;
+}
+EXPORT_SYMBOL(mpc52xx_lpbfifo_start_xfer);
+
+void mpc52xx_lpbfifo_abort(struct mpc52xx_lpbfifo_request *req)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&lpbfifo.lock, flags);
+ if (lpbfifo.req == req) {
+ /* Put it into reset and clear the state */
+ bcom_gen_bd_rx_reset(lpbfifo.bcom_rx_task);
+ bcom_gen_bd_tx_reset(lpbfifo.bcom_tx_task);
+ out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x01010000);
+ lpbfifo.req = NULL;
+ }
+ spin_unlock_irqrestore(&lpbfifo.lock, flags);
+}
+EXPORT_SYMBOL(mpc52xx_lpbfifo_abort);
+
+static int mpc52xx_lpbfifo_probe(struct platform_device *op)
+{
+ struct resource res;
+ int rc = -ENOMEM;
+
+ if (lpbfifo.dev != NULL)
+ return -ENOSPC;
+
+ lpbfifo.irq = irq_of_parse_and_map(op->dev.of_node, 0);
+ if (!lpbfifo.irq)
+ return -ENODEV;
+
+ if (of_address_to_resource(op->dev.of_node, 0, &res))
+ return -ENODEV;
+ lpbfifo.regs_phys = res.start;
+ lpbfifo.regs = of_iomap(op->dev.of_node, 0);
+ if (!lpbfifo.regs)
+ return -ENOMEM;
+
+ spin_lock_init(&lpbfifo.lock);
+
+ /* Put FIFO into reset */
+ out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x01010000);
+
+ /* Register the interrupt handler */
+ rc = request_irq(lpbfifo.irq, mpc52xx_lpbfifo_irq, 0,
+ "mpc52xx-lpbfifo", &lpbfifo);
+ if (rc)
+ goto err_irq;
+
+ /* Request the Bestcomm receive (fifo --> memory) task and IRQ */
+ lpbfifo.bcom_rx_task =
+ bcom_gen_bd_rx_init(2, res.start + LPBFIFO_REG_FIFO_DATA,
+ BCOM_INITIATOR_SCLPC, BCOM_IPR_SCLPC,
+ 16*1024*1024);
+ if (!lpbfifo.bcom_rx_task)
+ goto err_bcom_rx;
+
+ rc = request_irq(bcom_get_task_irq(lpbfifo.bcom_rx_task),
+ mpc52xx_lpbfifo_bcom_irq, 0,
+ "mpc52xx-lpbfifo-rx", &lpbfifo);
+ if (rc)
+ goto err_bcom_rx_irq;
+
+ lpbfifo.dma_irqs_enabled = 1;
+
+ /* Request the Bestcomm transmit (memory --> fifo) task and IRQ */
+ lpbfifo.bcom_tx_task =
+ bcom_gen_bd_tx_init(2, res.start + LPBFIFO_REG_FIFO_DATA,
+ BCOM_INITIATOR_SCLPC, BCOM_IPR_SCLPC);
+ if (!lpbfifo.bcom_tx_task)
+ goto err_bcom_tx;
+
+ lpbfifo.dev = &op->dev;
+ return 0;
+
+ err_bcom_tx:
+ free_irq(bcom_get_task_irq(lpbfifo.bcom_rx_task), &lpbfifo);
+ err_bcom_rx_irq:
+ bcom_gen_bd_rx_release(lpbfifo.bcom_rx_task);
+ err_bcom_rx:
+ free_irq(lpbfifo.irq, &lpbfifo);
+ err_irq:
+ iounmap(lpbfifo.regs);
+ lpbfifo.regs = NULL;
+
+ dev_err(&op->dev, "mpc52xx_lpbfifo_probe() failed\n");
+ return -ENODEV;
+}
+
+
+static int mpc52xx_lpbfifo_remove(struct platform_device *op)
+{
+ if (lpbfifo.dev != &op->dev)
+ return 0;
+
+ /* Put FIFO in reset */
+ out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x01010000);
+
+ /* Release the bestcomm transmit task */
+ free_irq(bcom_get_task_irq(lpbfifo.bcom_tx_task), &lpbfifo);
+ bcom_gen_bd_tx_release(lpbfifo.bcom_tx_task);
+
+ /* Release the bestcomm receive task */
+ free_irq(bcom_get_task_irq(lpbfifo.bcom_rx_task), &lpbfifo);
+ bcom_gen_bd_rx_release(lpbfifo.bcom_rx_task);
+
+ free_irq(lpbfifo.irq, &lpbfifo);
+ iounmap(lpbfifo.regs);
+ lpbfifo.regs = NULL;
+ lpbfifo.dev = NULL;
+
+ return 0;
+}
+
+static const struct of_device_id mpc52xx_lpbfifo_match[] = {
+ { .compatible = "fsl,mpc5200-lpbfifo", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, mpc52xx_lpbfifo_match);
+
+static struct platform_driver mpc52xx_lpbfifo_driver = {
+ .driver = {
+ .name = "mpc52xx-lpbfifo",
+ .of_match_table = mpc52xx_lpbfifo_match,
+ },
+ .probe = mpc52xx_lpbfifo_probe,
+ .remove = mpc52xx_lpbfifo_remove,
+};
+module_platform_driver(mpc52xx_lpbfifo_driver);
diff --git a/arch/powerpc/platforms/52xx/mpc52xx_pci.c b/arch/powerpc/platforms/52xx/mpc52xx_pci.c
new file mode 100644
index 000000000..af0f79995
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/mpc52xx_pci.c
@@ -0,0 +1,428 @@
+/*
+ * PCI code for the Freescale MPC52xx embedded CPU.
+ *
+ * Copyright (C) 2006 Secret Lab Technologies Ltd.
+ * Grant Likely <grant.likely@secretlab.ca>
+ * Copyright (C) 2004 Sylvain Munaut <tnt@246tNt.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#undef DEBUG
+
+#include <linux/pci.h>
+#include <asm/mpc52xx.h>
+#include <asm/delay.h>
+#include <asm/machdep.h>
+#include <linux/kernel.h>
+
+
+/* ======================================================================== */
+/* Structures mapping & Defines for PCI Unit */
+/* ======================================================================== */
+
+#define MPC52xx_PCI_GSCR_BM 0x40000000
+#define MPC52xx_PCI_GSCR_PE 0x20000000
+#define MPC52xx_PCI_GSCR_SE 0x10000000
+#define MPC52xx_PCI_GSCR_XLB2PCI_MASK 0x07000000
+#define MPC52xx_PCI_GSCR_XLB2PCI_SHIFT 24
+#define MPC52xx_PCI_GSCR_IPG2PCI_MASK 0x00070000
+#define MPC52xx_PCI_GSCR_IPG2PCI_SHIFT 16
+#define MPC52xx_PCI_GSCR_BME 0x00004000
+#define MPC52xx_PCI_GSCR_PEE 0x00002000
+#define MPC52xx_PCI_GSCR_SEE 0x00001000
+#define MPC52xx_PCI_GSCR_PR 0x00000001
+
+
+#define MPC52xx_PCI_IWBTAR_TRANSLATION(proc_ad,pci_ad,size) \
+ ( ( (proc_ad) & 0xff000000 ) | \
+ ( (((size) - 1) >> 8) & 0x00ff0000 ) | \
+ ( ((pci_ad) >> 16) & 0x0000ff00 ) )
+
+#define MPC52xx_PCI_IWCR_PACK(win0,win1,win2) (((win0) << 24) | \
+ ((win1) << 16) | \
+ ((win2) << 8))
+
+#define MPC52xx_PCI_IWCR_DISABLE 0x0
+#define MPC52xx_PCI_IWCR_ENABLE 0x1
+#define MPC52xx_PCI_IWCR_READ 0x0
+#define MPC52xx_PCI_IWCR_READ_LINE 0x2
+#define MPC52xx_PCI_IWCR_READ_MULTI 0x4
+#define MPC52xx_PCI_IWCR_MEM 0x0
+#define MPC52xx_PCI_IWCR_IO 0x8
+
+#define MPC52xx_PCI_TCR_P 0x01000000
+#define MPC52xx_PCI_TCR_LD 0x00010000
+#define MPC52xx_PCI_TCR_WCT8 0x00000008
+
+#define MPC52xx_PCI_TBATR_DISABLE 0x0
+#define MPC52xx_PCI_TBATR_ENABLE 0x1
+
+struct mpc52xx_pci {
+ u32 idr; /* PCI + 0x00 */
+ u32 scr; /* PCI + 0x04 */
+ u32 ccrir; /* PCI + 0x08 */
+ u32 cr1; /* PCI + 0x0C */
+ u32 bar0; /* PCI + 0x10 */
+ u32 bar1; /* PCI + 0x14 */
+ u8 reserved1[16]; /* PCI + 0x18 */
+ u32 ccpr; /* PCI + 0x28 */
+ u32 sid; /* PCI + 0x2C */
+ u32 erbar; /* PCI + 0x30 */
+ u32 cpr; /* PCI + 0x34 */
+ u8 reserved2[4]; /* PCI + 0x38 */
+ u32 cr2; /* PCI + 0x3C */
+ u8 reserved3[32]; /* PCI + 0x40 */
+ u32 gscr; /* PCI + 0x60 */
+ u32 tbatr0; /* PCI + 0x64 */
+ u32 tbatr1; /* PCI + 0x68 */
+ u32 tcr; /* PCI + 0x6C */
+ u32 iw0btar; /* PCI + 0x70 */
+ u32 iw1btar; /* PCI + 0x74 */
+ u32 iw2btar; /* PCI + 0x78 */
+ u8 reserved4[4]; /* PCI + 0x7C */
+ u32 iwcr; /* PCI + 0x80 */
+ u32 icr; /* PCI + 0x84 */
+ u32 isr; /* PCI + 0x88 */
+ u32 arb; /* PCI + 0x8C */
+ u8 reserved5[104]; /* PCI + 0x90 */
+ u32 car; /* PCI + 0xF8 */
+ u8 reserved6[4]; /* PCI + 0xFC */
+};
+
+/* MPC5200 device tree match tables */
+const struct of_device_id mpc52xx_pci_ids[] __initconst = {
+ { .type = "pci", .compatible = "fsl,mpc5200-pci", },
+ { .type = "pci", .compatible = "mpc5200-pci", },
+ {}
+};
+
+/* ======================================================================== */
+/* PCI configuration access */
+/* ======================================================================== */
+
+static int
+mpc52xx_pci_read_config(struct pci_bus *bus, unsigned int devfn,
+ int offset, int len, u32 *val)
+{
+ struct pci_controller *hose = pci_bus_to_host(bus);
+ u32 value;
+
+ if (ppc_md.pci_exclude_device)
+ if (ppc_md.pci_exclude_device(hose, bus->number, devfn))
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ out_be32(hose->cfg_addr,
+ (1 << 31) |
+ (bus->number << 16) |
+ (devfn << 8) |
+ (offset & 0xfc));
+ mb();
+
+#if defined(CONFIG_PPC_MPC5200_BUGFIX)
+ if (bus->number) {
+ /* workaround for the bug 435 of the MPC5200 (L25R);
+ * Don't do 32 bits config access during type-1 cycles */
+ switch (len) {
+ case 1:
+ value = in_8(((u8 __iomem *)hose->cfg_data) +
+ (offset & 3));
+ break;
+ case 2:
+ value = in_le16(((u16 __iomem *)hose->cfg_data) +
+ ((offset>>1) & 1));
+ break;
+
+ default:
+ value = in_le16((u16 __iomem *)hose->cfg_data) |
+ (in_le16(((u16 __iomem *)hose->cfg_data) + 1) << 16);
+ break;
+ }
+ }
+ else
+#endif
+ {
+ value = in_le32(hose->cfg_data);
+
+ if (len != 4) {
+ value >>= ((offset & 0x3) << 3);
+ value &= 0xffffffff >> (32 - (len << 3));
+ }
+ }
+
+ *val = value;
+
+ out_be32(hose->cfg_addr, 0);
+ mb();
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int
+mpc52xx_pci_write_config(struct pci_bus *bus, unsigned int devfn,
+ int offset, int len, u32 val)
+{
+ struct pci_controller *hose = pci_bus_to_host(bus);
+ u32 value, mask;
+
+ if (ppc_md.pci_exclude_device)
+ if (ppc_md.pci_exclude_device(hose, bus->number, devfn))
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ out_be32(hose->cfg_addr,
+ (1 << 31) |
+ (bus->number << 16) |
+ (devfn << 8) |
+ (offset & 0xfc));
+ mb();
+
+#if defined(CONFIG_PPC_MPC5200_BUGFIX)
+ if (bus->number) {
+ /* workaround for the bug 435 of the MPC5200 (L25R);
+ * Don't do 32 bits config access during type-1 cycles */
+ switch (len) {
+ case 1:
+ out_8(((u8 __iomem *)hose->cfg_data) +
+ (offset & 3), val);
+ break;
+ case 2:
+ out_le16(((u16 __iomem *)hose->cfg_data) +
+ ((offset>>1) & 1), val);
+ break;
+
+ default:
+ out_le16((u16 __iomem *)hose->cfg_data,
+ (u16)val);
+ out_le16(((u16 __iomem *)hose->cfg_data) + 1,
+ (u16)(val>>16));
+ break;
+ }
+ }
+ else
+#endif
+ {
+ if (len != 4) {
+ value = in_le32(hose->cfg_data);
+
+ offset = (offset & 0x3) << 3;
+ mask = (0xffffffff >> (32 - (len << 3)));
+ mask <<= offset;
+
+ value &= ~mask;
+ val = value | ((val << offset) & mask);
+ }
+
+ out_le32(hose->cfg_data, val);
+ }
+ mb();
+
+ out_be32(hose->cfg_addr, 0);
+ mb();
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static struct pci_ops mpc52xx_pci_ops = {
+ .read = mpc52xx_pci_read_config,
+ .write = mpc52xx_pci_write_config
+};
+
+
+/* ======================================================================== */
+/* PCI setup */
+/* ======================================================================== */
+
+static void __init
+mpc52xx_pci_setup(struct pci_controller *hose,
+ struct mpc52xx_pci __iomem *pci_regs, phys_addr_t pci_phys)
+{
+ struct resource *res;
+ u32 tmp;
+ int iwcr0 = 0, iwcr1 = 0, iwcr2 = 0;
+
+ pr_debug("mpc52xx_pci_setup(hose=%p, pci_regs=%p)\n", hose, pci_regs);
+
+ /* pci_process_bridge_OF_ranges() found all our addresses for us;
+ * now store them in the right places */
+ hose->cfg_addr = &pci_regs->car;
+ hose->cfg_data = hose->io_base_virt;
+
+ /* Control regs */
+ tmp = in_be32(&pci_regs->scr);
+ tmp |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY;
+ out_be32(&pci_regs->scr, tmp);
+
+ /* Memory windows */
+ res = &hose->mem_resources[0];
+ if (res->flags) {
+ pr_debug("mem_resource[0] = "
+ "{.start=%llx, .end=%llx, .flags=%llx}\n",
+ (unsigned long long)res->start,
+ (unsigned long long)res->end,
+ (unsigned long long)res->flags);
+ out_be32(&pci_regs->iw0btar,
+ MPC52xx_PCI_IWBTAR_TRANSLATION(res->start, res->start,
+ resource_size(res)));
+ iwcr0 = MPC52xx_PCI_IWCR_ENABLE | MPC52xx_PCI_IWCR_MEM;
+ if (res->flags & IORESOURCE_PREFETCH)
+ iwcr0 |= MPC52xx_PCI_IWCR_READ_MULTI;
+ else
+ iwcr0 |= MPC52xx_PCI_IWCR_READ;
+ }
+
+ res = &hose->mem_resources[1];
+ if (res->flags) {
+ pr_debug("mem_resource[1] = {.start=%x, .end=%x, .flags=%lx}\n",
+ res->start, res->end, res->flags);
+ out_be32(&pci_regs->iw1btar,
+ MPC52xx_PCI_IWBTAR_TRANSLATION(res->start, res->start,
+ resource_size(res)));
+ iwcr1 = MPC52xx_PCI_IWCR_ENABLE | MPC52xx_PCI_IWCR_MEM;
+ if (res->flags & IORESOURCE_PREFETCH)
+ iwcr1 |= MPC52xx_PCI_IWCR_READ_MULTI;
+ else
+ iwcr1 |= MPC52xx_PCI_IWCR_READ;
+ }
+
+ /* IO resources */
+ res = &hose->io_resource;
+ if (!res) {
+ printk(KERN_ERR "%s: Didn't find IO resources\n", __FILE__);
+ return;
+ }
+ pr_debug(".io_resource={.start=%llx,.end=%llx,.flags=%llx} "
+ ".io_base_phys=0x%p\n",
+ (unsigned long long)res->start,
+ (unsigned long long)res->end,
+ (unsigned long long)res->flags, (void*)hose->io_base_phys);
+ out_be32(&pci_regs->iw2btar,
+ MPC52xx_PCI_IWBTAR_TRANSLATION(hose->io_base_phys,
+ res->start,
+ resource_size(res)));
+ iwcr2 = MPC52xx_PCI_IWCR_ENABLE | MPC52xx_PCI_IWCR_IO;
+
+ /* Set all the IWCR fields at once; they're in the same reg */
+ out_be32(&pci_regs->iwcr, MPC52xx_PCI_IWCR_PACK(iwcr0, iwcr1, iwcr2));
+
+ /* Map IMMR onto PCI bus */
+ pci_phys &= 0xfffc0000; /* bar0 has only 14 significant bits */
+ out_be32(&pci_regs->tbatr0, MPC52xx_PCI_TBATR_ENABLE | pci_phys);
+ out_be32(&pci_regs->bar0, PCI_BASE_ADDRESS_MEM_PREFETCH | pci_phys);
+
+ /* Map memory onto PCI bus */
+ out_be32(&pci_regs->tbatr1, MPC52xx_PCI_TBATR_ENABLE);
+ out_be32(&pci_regs->bar1, PCI_BASE_ADDRESS_MEM_PREFETCH);
+
+ out_be32(&pci_regs->tcr, MPC52xx_PCI_TCR_LD | MPC52xx_PCI_TCR_WCT8);
+
+ tmp = in_be32(&pci_regs->gscr);
+#if 0
+ /* Reset the exteral bus ( internal PCI controller is NOT reset ) */
+ /* Not necessary and can be a bad thing if for example the bootloader
+ is displaying a splash screen or ... Just left here for
+ documentation purpose if anyone need it */
+ out_be32(&pci_regs->gscr, tmp | MPC52xx_PCI_GSCR_PR);
+ udelay(50);
+#endif
+
+ /* Make sure the PCI bridge is out of reset */
+ out_be32(&pci_regs->gscr, tmp & ~MPC52xx_PCI_GSCR_PR);
+}
+
+static void
+mpc52xx_pci_fixup_resources(struct pci_dev *dev)
+{
+ int i;
+
+ pr_debug("mpc52xx_pci_fixup_resources() %.4x:%.4x\n",
+ dev->vendor, dev->device);
+
+ /* We don't rely on boot loader for PCI and resets all
+ devices */
+ for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
+ struct resource *res = &dev->resource[i];
+ if (res->end > res->start) { /* Only valid resources */
+ res->end -= res->start;
+ res->start = 0;
+ res->flags |= IORESOURCE_UNSET;
+ }
+ }
+
+ /* The PCI Host bridge of MPC52xx has a prefetch memory resource
+ fixed to 1Gb. Doesn't fit in the resource system so we remove it */
+ if ( (dev->vendor == PCI_VENDOR_ID_MOTOROLA) &&
+ ( dev->device == PCI_DEVICE_ID_MOTOROLA_MPC5200
+ || dev->device == PCI_DEVICE_ID_MOTOROLA_MPC5200B) ) {
+ struct resource *res = &dev->resource[1];
+ res->start = res->end = res->flags = 0;
+ }
+}
+
+int __init
+mpc52xx_add_bridge(struct device_node *node)
+{
+ int len;
+ struct mpc52xx_pci __iomem *pci_regs;
+ struct pci_controller *hose;
+ const int *bus_range;
+ struct resource rsrc;
+
+ pr_debug("Adding MPC52xx PCI host bridge %pOF\n", node);
+
+ pci_add_flags(PCI_REASSIGN_ALL_BUS);
+
+ if (of_address_to_resource(node, 0, &rsrc) != 0) {
+ printk(KERN_ERR "Can't get %pOF resources\n", node);
+ return -EINVAL;
+ }
+
+ bus_range = of_get_property(node, "bus-range", &len);
+ if (bus_range == NULL || len < 2 * sizeof(int)) {
+ printk(KERN_WARNING "Can't get %pOF bus-range, assume bus 0\n",
+ node);
+ bus_range = NULL;
+ }
+
+ /* There are some PCI quirks on the 52xx, register the hook to
+ * fix them. */
+ ppc_md.pcibios_fixup_resources = mpc52xx_pci_fixup_resources;
+
+ /* Alloc and initialize the pci controller. Values in the device
+ * tree are needed to configure the 52xx PCI controller. Rather
+ * than parse the tree here, let pci_process_bridge_OF_ranges()
+ * do it for us and extract the values after the fact */
+ hose = pcibios_alloc_controller(node);
+ if (!hose)
+ return -ENOMEM;
+
+ hose->first_busno = bus_range ? bus_range[0] : 0;
+ hose->last_busno = bus_range ? bus_range[1] : 0xff;
+
+ hose->ops = &mpc52xx_pci_ops;
+
+ pci_regs = ioremap(rsrc.start, resource_size(&rsrc));
+ if (!pci_regs)
+ return -ENOMEM;
+
+ pci_process_bridge_OF_ranges(hose, node, 1);
+
+ /* Finish setting up PCI using values obtained by
+ * pci_proces_bridge_OF_ranges */
+ mpc52xx_pci_setup(hose, pci_regs, rsrc.start);
+
+ return 0;
+}
+
+void __init mpc52xx_setup_pci(void)
+{
+ struct device_node *pci;
+
+ pci = of_find_matching_node(NULL, mpc52xx_pci_ids);
+ if (!pci)
+ return;
+
+ mpc52xx_add_bridge(pci);
+ of_node_put(pci);
+}
diff --git a/arch/powerpc/platforms/52xx/mpc52xx_pic.c b/arch/powerpc/platforms/52xx/mpc52xx_pic.c
new file mode 100644
index 000000000..76a8102bd
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/mpc52xx_pic.c
@@ -0,0 +1,518 @@
+/*
+ *
+ * Programmable Interrupt Controller functions for the Freescale MPC52xx.
+ *
+ * Copyright (C) 2008 Secret Lab Technologies Ltd.
+ * Copyright (C) 2006 bplan GmbH
+ * Copyright (C) 2004 Sylvain Munaut <tnt@246tNt.com>
+ * Copyright (C) 2003 Montavista Software, Inc
+ *
+ * Based on the code from the 2.4 kernel by
+ * Dale Farnsworth <dfarnsworth@mvista.com> and Kent Borg.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ */
+
+/*
+ * This is the device driver for the MPC5200 interrupt controller.
+ *
+ * hardware overview
+ * -----------------
+ * The MPC5200 interrupt controller groups the all interrupt sources into
+ * three groups called 'critical', 'main', and 'peripheral'. The critical
+ * group has 3 irqs, External IRQ0, slice timer 0 irq, and wake from deep
+ * sleep. Main group include the other 3 external IRQs, slice timer 1, RTC,
+ * gpios, and the general purpose timers. Peripheral group contains the
+ * remaining irq sources from all of the on-chip peripherals (PSCs, Ethernet,
+ * USB, DMA, etc).
+ *
+ * virqs
+ * -----
+ * The Linux IRQ subsystem requires that each irq source be assigned a
+ * system wide unique IRQ number starting at 1 (0 means no irq). Since
+ * systems can have multiple interrupt controllers, the virtual IRQ (virq)
+ * infrastructure lets each interrupt controller to define a local set
+ * of IRQ numbers and the virq infrastructure maps those numbers into
+ * a unique range of the global IRQ# space.
+ *
+ * To define a range of virq numbers for this controller, this driver first
+ * assigns a number to each of the irq groups (called the level 1 or L1
+ * value). Within each group individual irq sources are also assigned a
+ * number, as defined by the MPC5200 user guide, and refers to it as the
+ * level 2 or L2 value. The virq number is determined by shifting up the
+ * L1 value by MPC52xx_IRQ_L1_OFFSET and ORing it with the L2 value.
+ *
+ * For example, the TMR0 interrupt is irq 9 in the main group. The
+ * virq for TMR0 is calculated by ((1 << MPC52xx_IRQ_L1_OFFSET) | 9).
+ *
+ * The observant reader will also notice that this driver defines a 4th
+ * interrupt group called 'bestcomm'. The bestcomm group isn't physically
+ * part of the MPC5200 interrupt controller, but it is used here to assign
+ * a separate virq number for each bestcomm task (since any of the 16
+ * bestcomm tasks can cause the bestcomm interrupt to be raised). When a
+ * bestcomm interrupt occurs (peripheral group, irq 0) this driver determines
+ * which task needs servicing and returns the irq number for that task. This
+ * allows drivers which use bestcomm to define their own interrupt handlers.
+ *
+ * irq_chip structures
+ * -------------------
+ * For actually manipulating IRQs (masking, enabling, clearing, etc) this
+ * driver defines four separate 'irq_chip' structures, one for the main
+ * group, one for the peripherals group, one for the bestcomm group and one
+ * for external interrupts. The irq_chip structures provide the hooks needed
+ * to manipulate each IRQ source, and since each group is has a separate set
+ * of registers for controlling the irq, it makes sense to divide up the
+ * hooks along those lines.
+ *
+ * You'll notice that there is not an irq_chip for the critical group and
+ * you'll also notice that there is an irq_chip defined for external
+ * interrupts even though there is no external interrupt group. The reason
+ * for this is that the four external interrupts are all managed with the same
+ * register even though one of the external IRQs is in the critical group and
+ * the other three are in the main group. For this reason it makes sense for
+ * the 4 external irqs to be managed using a separate set of hooks. The
+ * reason there is no crit irq_chip is that of the 3 irqs in the critical
+ * group, only external interrupt is actually support at this time by this
+ * driver and since external interrupt is the only one used, it can just
+ * be directed to make use of the external irq irq_chip.
+ *
+ * device tree bindings
+ * --------------------
+ * The device tree bindings for this controller reflect the two level
+ * organization of irqs in the device. #interrupt-cells = <3> where the
+ * first cell is the group number [0..3], the second cell is the irq
+ * number in the group, and the third cell is the sense type (level/edge).
+ * For reference, the following is a list of the interrupt property values
+ * associated with external interrupt sources on the MPC5200 (just because
+ * it is non-obvious to determine what the interrupts property should be
+ * when reading the mpc5200 manual and it is a frequently asked question).
+ *
+ * External interrupts:
+ * <0 0 n> external irq0, n is sense (n=0: level high,
+ * <1 1 n> external irq1, n is sense n=1: edge rising,
+ * <1 2 n> external irq2, n is sense n=2: edge falling,
+ * <1 3 n> external irq3, n is sense n=3: level low)
+ */
+#undef DEBUG
+
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/of.h>
+#include <asm/io.h>
+#include <asm/prom.h>
+#include <asm/mpc52xx.h>
+
+/* HW IRQ mapping */
+#define MPC52xx_IRQ_L1_CRIT (0)
+#define MPC52xx_IRQ_L1_MAIN (1)
+#define MPC52xx_IRQ_L1_PERP (2)
+#define MPC52xx_IRQ_L1_SDMA (3)
+
+#define MPC52xx_IRQ_L1_OFFSET (6)
+#define MPC52xx_IRQ_L1_MASK (0x00c0)
+#define MPC52xx_IRQ_L2_MASK (0x003f)
+
+#define MPC52xx_IRQ_HIGHTESTHWIRQ (0xd0)
+
+
+/* MPC5200 device tree match tables */
+static const struct of_device_id mpc52xx_pic_ids[] __initconst = {
+ { .compatible = "fsl,mpc5200-pic", },
+ { .compatible = "mpc5200-pic", },
+ {}
+};
+static const struct of_device_id mpc52xx_sdma_ids[] __initconst = {
+ { .compatible = "fsl,mpc5200-bestcomm", },
+ { .compatible = "mpc5200-bestcomm", },
+ {}
+};
+
+static struct mpc52xx_intr __iomem *intr;
+static struct mpc52xx_sdma __iomem *sdma;
+static struct irq_domain *mpc52xx_irqhost = NULL;
+
+static unsigned char mpc52xx_map_senses[4] = {
+ IRQ_TYPE_LEVEL_HIGH,
+ IRQ_TYPE_EDGE_RISING,
+ IRQ_TYPE_EDGE_FALLING,
+ IRQ_TYPE_LEVEL_LOW,
+};
+
+/* Utility functions */
+static inline void io_be_setbit(u32 __iomem *addr, int bitno)
+{
+ out_be32(addr, in_be32(addr) | (1 << bitno));
+}
+
+static inline void io_be_clrbit(u32 __iomem *addr, int bitno)
+{
+ out_be32(addr, in_be32(addr) & ~(1 << bitno));
+}
+
+/*
+ * IRQ[0-3] interrupt irq_chip
+ */
+static void mpc52xx_extirq_mask(struct irq_data *d)
+{
+ int l2irq = irqd_to_hwirq(d) & MPC52xx_IRQ_L2_MASK;
+ io_be_clrbit(&intr->ctrl, 11 - l2irq);
+}
+
+static void mpc52xx_extirq_unmask(struct irq_data *d)
+{
+ int l2irq = irqd_to_hwirq(d) & MPC52xx_IRQ_L2_MASK;
+ io_be_setbit(&intr->ctrl, 11 - l2irq);
+}
+
+static void mpc52xx_extirq_ack(struct irq_data *d)
+{
+ int l2irq = irqd_to_hwirq(d) & MPC52xx_IRQ_L2_MASK;
+ io_be_setbit(&intr->ctrl, 27-l2irq);
+}
+
+static int mpc52xx_extirq_set_type(struct irq_data *d, unsigned int flow_type)
+{
+ u32 ctrl_reg, type;
+ int l2irq = irqd_to_hwirq(d) & MPC52xx_IRQ_L2_MASK;
+ void *handler = handle_level_irq;
+
+ pr_debug("%s: irq=%x. l2=%d flow_type=%d\n", __func__,
+ (int) irqd_to_hwirq(d), l2irq, flow_type);
+
+ switch (flow_type) {
+ case IRQF_TRIGGER_HIGH: type = 0; break;
+ case IRQF_TRIGGER_RISING: type = 1; handler = handle_edge_irq; break;
+ case IRQF_TRIGGER_FALLING: type = 2; handler = handle_edge_irq; break;
+ case IRQF_TRIGGER_LOW: type = 3; break;
+ default:
+ type = 0;
+ }
+
+ ctrl_reg = in_be32(&intr->ctrl);
+ ctrl_reg &= ~(0x3 << (22 - (l2irq * 2)));
+ ctrl_reg |= (type << (22 - (l2irq * 2)));
+ out_be32(&intr->ctrl, ctrl_reg);
+
+ irq_set_handler_locked(d, handler);
+
+ return 0;
+}
+
+static struct irq_chip mpc52xx_extirq_irqchip = {
+ .name = "MPC52xx External",
+ .irq_mask = mpc52xx_extirq_mask,
+ .irq_unmask = mpc52xx_extirq_unmask,
+ .irq_ack = mpc52xx_extirq_ack,
+ .irq_set_type = mpc52xx_extirq_set_type,
+};
+
+/*
+ * Main interrupt irq_chip
+ */
+static int mpc52xx_null_set_type(struct irq_data *d, unsigned int flow_type)
+{
+ return 0; /* Do nothing so that the sense mask will get updated */
+}
+
+static void mpc52xx_main_mask(struct irq_data *d)
+{
+ int l2irq = irqd_to_hwirq(d) & MPC52xx_IRQ_L2_MASK;
+ io_be_setbit(&intr->main_mask, 16 - l2irq);
+}
+
+static void mpc52xx_main_unmask(struct irq_data *d)
+{
+ int l2irq = irqd_to_hwirq(d) & MPC52xx_IRQ_L2_MASK;
+ io_be_clrbit(&intr->main_mask, 16 - l2irq);
+}
+
+static struct irq_chip mpc52xx_main_irqchip = {
+ .name = "MPC52xx Main",
+ .irq_mask = mpc52xx_main_mask,
+ .irq_mask_ack = mpc52xx_main_mask,
+ .irq_unmask = mpc52xx_main_unmask,
+ .irq_set_type = mpc52xx_null_set_type,
+};
+
+/*
+ * Peripherals interrupt irq_chip
+ */
+static void mpc52xx_periph_mask(struct irq_data *d)
+{
+ int l2irq = irqd_to_hwirq(d) & MPC52xx_IRQ_L2_MASK;
+ io_be_setbit(&intr->per_mask, 31 - l2irq);
+}
+
+static void mpc52xx_periph_unmask(struct irq_data *d)
+{
+ int l2irq = irqd_to_hwirq(d) & MPC52xx_IRQ_L2_MASK;
+ io_be_clrbit(&intr->per_mask, 31 - l2irq);
+}
+
+static struct irq_chip mpc52xx_periph_irqchip = {
+ .name = "MPC52xx Peripherals",
+ .irq_mask = mpc52xx_periph_mask,
+ .irq_mask_ack = mpc52xx_periph_mask,
+ .irq_unmask = mpc52xx_periph_unmask,
+ .irq_set_type = mpc52xx_null_set_type,
+};
+
+/*
+ * SDMA interrupt irq_chip
+ */
+static void mpc52xx_sdma_mask(struct irq_data *d)
+{
+ int l2irq = irqd_to_hwirq(d) & MPC52xx_IRQ_L2_MASK;
+ io_be_setbit(&sdma->IntMask, l2irq);
+}
+
+static void mpc52xx_sdma_unmask(struct irq_data *d)
+{
+ int l2irq = irqd_to_hwirq(d) & MPC52xx_IRQ_L2_MASK;
+ io_be_clrbit(&sdma->IntMask, l2irq);
+}
+
+static void mpc52xx_sdma_ack(struct irq_data *d)
+{
+ int l2irq = irqd_to_hwirq(d) & MPC52xx_IRQ_L2_MASK;
+ out_be32(&sdma->IntPend, 1 << l2irq);
+}
+
+static struct irq_chip mpc52xx_sdma_irqchip = {
+ .name = "MPC52xx SDMA",
+ .irq_mask = mpc52xx_sdma_mask,
+ .irq_unmask = mpc52xx_sdma_unmask,
+ .irq_ack = mpc52xx_sdma_ack,
+ .irq_set_type = mpc52xx_null_set_type,
+};
+
+/**
+ * mpc52xx_is_extirq - Returns true if hwirq number is for an external IRQ
+ */
+static int mpc52xx_is_extirq(int l1, int l2)
+{
+ return ((l1 == 0) && (l2 == 0)) ||
+ ((l1 == 1) && (l2 >= 1) && (l2 <= 3));
+}
+
+/**
+ * mpc52xx_irqhost_xlate - translate virq# from device tree interrupts property
+ */
+static int mpc52xx_irqhost_xlate(struct irq_domain *h, struct device_node *ct,
+ const u32 *intspec, unsigned int intsize,
+ irq_hw_number_t *out_hwirq,
+ unsigned int *out_flags)
+{
+ int intrvect_l1;
+ int intrvect_l2;
+ int intrvect_type;
+ int intrvect_linux;
+
+ if (intsize != 3)
+ return -1;
+
+ intrvect_l1 = (int)intspec[0];
+ intrvect_l2 = (int)intspec[1];
+ intrvect_type = (int)intspec[2] & 0x3;
+
+ intrvect_linux = (intrvect_l1 << MPC52xx_IRQ_L1_OFFSET) &
+ MPC52xx_IRQ_L1_MASK;
+ intrvect_linux |= intrvect_l2 & MPC52xx_IRQ_L2_MASK;
+
+ *out_hwirq = intrvect_linux;
+ *out_flags = IRQ_TYPE_LEVEL_LOW;
+ if (mpc52xx_is_extirq(intrvect_l1, intrvect_l2))
+ *out_flags = mpc52xx_map_senses[intrvect_type];
+
+ pr_debug("return %x, l1=%d, l2=%d\n", intrvect_linux, intrvect_l1,
+ intrvect_l2);
+ return 0;
+}
+
+/**
+ * mpc52xx_irqhost_map - Hook to map from virq to an irq_chip structure
+ */
+static int mpc52xx_irqhost_map(struct irq_domain *h, unsigned int virq,
+ irq_hw_number_t irq)
+{
+ int l1irq;
+ int l2irq;
+ struct irq_chip *irqchip;
+ void *hndlr;
+ int type;
+ u32 reg;
+
+ l1irq = (irq & MPC52xx_IRQ_L1_MASK) >> MPC52xx_IRQ_L1_OFFSET;
+ l2irq = irq & MPC52xx_IRQ_L2_MASK;
+
+ /*
+ * External IRQs are handled differently by the hardware so they are
+ * handled by a dedicated irq_chip structure.
+ */
+ if (mpc52xx_is_extirq(l1irq, l2irq)) {
+ reg = in_be32(&intr->ctrl);
+ type = mpc52xx_map_senses[(reg >> (22 - l2irq * 2)) & 0x3];
+ if ((type == IRQ_TYPE_EDGE_FALLING) ||
+ (type == IRQ_TYPE_EDGE_RISING))
+ hndlr = handle_edge_irq;
+ else
+ hndlr = handle_level_irq;
+
+ irq_set_chip_and_handler(virq, &mpc52xx_extirq_irqchip, hndlr);
+ pr_debug("%s: External IRQ%i virq=%x, hw=%x. type=%x\n",
+ __func__, l2irq, virq, (int)irq, type);
+ return 0;
+ }
+
+ /* It is an internal SOC irq. Choose the correct irq_chip */
+ switch (l1irq) {
+ case MPC52xx_IRQ_L1_MAIN: irqchip = &mpc52xx_main_irqchip; break;
+ case MPC52xx_IRQ_L1_PERP: irqchip = &mpc52xx_periph_irqchip; break;
+ case MPC52xx_IRQ_L1_SDMA: irqchip = &mpc52xx_sdma_irqchip; break;
+ case MPC52xx_IRQ_L1_CRIT:
+ pr_warn("%s: Critical IRQ #%d is unsupported! Nopping it.\n",
+ __func__, l2irq);
+ irq_set_chip(virq, &no_irq_chip);
+ return 0;
+ }
+
+ irq_set_chip_and_handler(virq, irqchip, handle_level_irq);
+ pr_debug("%s: virq=%x, l1=%i, l2=%i\n", __func__, virq, l1irq, l2irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops mpc52xx_irqhost_ops = {
+ .xlate = mpc52xx_irqhost_xlate,
+ .map = mpc52xx_irqhost_map,
+};
+
+/**
+ * mpc52xx_init_irq - Initialize and register with the virq subsystem
+ *
+ * Hook for setting up IRQs on an mpc5200 system. A pointer to this function
+ * is to be put into the machine definition structure.
+ *
+ * This function searches the device tree for an MPC5200 interrupt controller,
+ * initializes it, and registers it with the virq subsystem.
+ */
+void __init mpc52xx_init_irq(void)
+{
+ u32 intr_ctrl;
+ struct device_node *picnode;
+ struct device_node *np;
+
+ /* Remap the necessary zones */
+ picnode = of_find_matching_node(NULL, mpc52xx_pic_ids);
+ intr = of_iomap(picnode, 0);
+ if (!intr)
+ panic(__FILE__ ": find_and_map failed on 'mpc5200-pic'. "
+ "Check node !");
+
+ np = of_find_matching_node(NULL, mpc52xx_sdma_ids);
+ sdma = of_iomap(np, 0);
+ of_node_put(np);
+ if (!sdma)
+ panic(__FILE__ ": find_and_map failed on 'mpc5200-bestcomm'. "
+ "Check node !");
+
+ pr_debug("MPC5200 IRQ controller mapped to 0x%p\n", intr);
+
+ /* Disable all interrupt sources. */
+ out_be32(&sdma->IntPend, 0xffffffff); /* 1 means clear pending */
+ out_be32(&sdma->IntMask, 0xffffffff); /* 1 means disabled */
+ out_be32(&intr->per_mask, 0x7ffffc00); /* 1 means disabled */
+ out_be32(&intr->main_mask, 0x00010fff); /* 1 means disabled */
+ intr_ctrl = in_be32(&intr->ctrl);
+ intr_ctrl &= 0x00ff0000; /* Keeps IRQ[0-3] config */
+ intr_ctrl |= 0x0f000000 | /* clear IRQ 0-3 */
+ 0x00001000 | /* MEE master external enable */
+ 0x00000000 | /* 0 means disable IRQ 0-3 */
+ 0x00000001; /* CEb route critical normally */
+ out_be32(&intr->ctrl, intr_ctrl);
+
+ /* Zero a bunch of the priority settings. */
+ out_be32(&intr->per_pri1, 0);
+ out_be32(&intr->per_pri2, 0);
+ out_be32(&intr->per_pri3, 0);
+ out_be32(&intr->main_pri1, 0);
+ out_be32(&intr->main_pri2, 0);
+
+ /*
+ * As last step, add an irq host to translate the real
+ * hw irq information provided by the ofw to linux virq
+ */
+ mpc52xx_irqhost = irq_domain_add_linear(picnode,
+ MPC52xx_IRQ_HIGHTESTHWIRQ,
+ &mpc52xx_irqhost_ops, NULL);
+
+ if (!mpc52xx_irqhost)
+ panic(__FILE__ ": Cannot allocate the IRQ host\n");
+
+ irq_set_default_host(mpc52xx_irqhost);
+
+ pr_info("MPC52xx PIC is up and running!\n");
+}
+
+/**
+ * mpc52xx_get_irq - Get pending interrupt number hook function
+ *
+ * Called by the interrupt handler to determine what IRQ handler needs to be
+ * executed.
+ *
+ * Status of pending interrupts is determined by reading the encoded status
+ * register. The encoded status register has three fields; one for each of the
+ * types of interrupts defined by the controller - 'critical', 'main' and
+ * 'peripheral'. This function reads the status register and returns the IRQ
+ * number associated with the highest priority pending interrupt. 'Critical'
+ * interrupts have the highest priority, followed by 'main' interrupts, and
+ * then 'peripheral'.
+ *
+ * The mpc5200 interrupt controller can be configured to boost the priority
+ * of individual 'peripheral' interrupts. If this is the case then a special
+ * value will appear in either the crit or main fields indicating a high
+ * or medium priority peripheral irq has occurred.
+ *
+ * This function checks each of the 3 irq request fields and returns the
+ * first pending interrupt that it finds.
+ *
+ * This function also identifies a 4th type of interrupt; 'bestcomm'. Each
+ * bestcomm DMA task can raise the bestcomm peripheral interrupt. When this
+ * occurs at task-specific IRQ# is decoded so that each task can have its
+ * own IRQ handler.
+ */
+unsigned int mpc52xx_get_irq(void)
+{
+ u32 status;
+ int irq;
+
+ status = in_be32(&intr->enc_status);
+ if (status & 0x00000400) { /* critical */
+ irq = (status >> 8) & 0x3;
+ if (irq == 2) /* high priority peripheral */
+ goto peripheral;
+ irq |= (MPC52xx_IRQ_L1_CRIT << MPC52xx_IRQ_L1_OFFSET);
+ } else if (status & 0x00200000) { /* main */
+ irq = (status >> 16) & 0x1f;
+ if (irq == 4) /* low priority peripheral */
+ goto peripheral;
+ irq |= (MPC52xx_IRQ_L1_MAIN << MPC52xx_IRQ_L1_OFFSET);
+ } else if (status & 0x20000000) { /* peripheral */
+ peripheral:
+ irq = (status >> 24) & 0x1f;
+ if (irq == 0) { /* bestcomm */
+ status = in_be32(&sdma->IntPend);
+ irq = ffs(status) - 1;
+ irq |= (MPC52xx_IRQ_L1_SDMA << MPC52xx_IRQ_L1_OFFSET);
+ } else {
+ irq |= (MPC52xx_IRQ_L1_PERP << MPC52xx_IRQ_L1_OFFSET);
+ }
+ } else {
+ return 0;
+ }
+
+ return irq_linear_revmap(mpc52xx_irqhost, irq);
+}
diff --git a/arch/powerpc/platforms/52xx/mpc52xx_pm.c b/arch/powerpc/platforms/52xx/mpc52xx_pm.c
new file mode 100644
index 000000000..b1d208ded
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/mpc52xx_pm.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/init.h>
+#include <linux/suspend.h>
+#include <linux/io.h>
+#include <asm/time.h>
+#include <asm/cacheflush.h>
+#include <asm/mpc52xx.h>
+
+/* these are defined in mpc52xx_sleep.S, and only used here */
+extern void mpc52xx_deep_sleep(void __iomem *sram, void __iomem *sdram_regs,
+ struct mpc52xx_cdm __iomem *, struct mpc52xx_intr __iomem*);
+extern void mpc52xx_ds_sram(void);
+extern const long mpc52xx_ds_sram_size;
+extern void mpc52xx_ds_cached(void);
+extern const long mpc52xx_ds_cached_size;
+
+static void __iomem *mbar;
+static void __iomem *sdram;
+static struct mpc52xx_cdm __iomem *cdm;
+static struct mpc52xx_intr __iomem *intr;
+static struct mpc52xx_gpio_wkup __iomem *gpiow;
+static void __iomem *sram;
+static int sram_size;
+
+struct mpc52xx_suspend mpc52xx_suspend;
+
+static int mpc52xx_pm_valid(suspend_state_t state)
+{
+ switch (state) {
+ case PM_SUSPEND_STANDBY:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+int mpc52xx_set_wakeup_gpio(u8 pin, u8 level)
+{
+ u16 tmp;
+
+ /* enable gpio */
+ out_8(&gpiow->wkup_gpioe, in_8(&gpiow->wkup_gpioe) | (1 << pin));
+ /* set as input */
+ out_8(&gpiow->wkup_ddr, in_8(&gpiow->wkup_ddr) & ~(1 << pin));
+ /* enable deep sleep interrupt */
+ out_8(&gpiow->wkup_inten, in_8(&gpiow->wkup_inten) | (1 << pin));
+ /* low/high level creates wakeup interrupt */
+ tmp = in_be16(&gpiow->wkup_itype);
+ tmp &= ~(0x3 << (pin * 2));
+ tmp |= (!level + 1) << (pin * 2);
+ out_be16(&gpiow->wkup_itype, tmp);
+ /* master enable */
+ out_8(&gpiow->wkup_maste, 1);
+
+ return 0;
+}
+
+int mpc52xx_pm_prepare(void)
+{
+ struct device_node *np;
+ const struct of_device_id immr_ids[] = {
+ { .compatible = "fsl,mpc5200-immr", },
+ { .compatible = "fsl,mpc5200b-immr", },
+ { .type = "soc", .compatible = "mpc5200", }, /* lite5200 */
+ { .type = "builtin", .compatible = "mpc5200", }, /* efika */
+ {}
+ };
+ struct resource res;
+
+ /* map the whole register space */
+ np = of_find_matching_node(NULL, immr_ids);
+
+ if (of_address_to_resource(np, 0, &res)) {
+ pr_err("mpc52xx_pm_prepare(): could not get IMMR address\n");
+ of_node_put(np);
+ return -ENOSYS;
+ }
+
+ mbar = ioremap(res.start, 0xc000); /* we should map whole region including SRAM */
+
+ of_node_put(np);
+ if (!mbar) {
+ pr_err("mpc52xx_pm_prepare(): could not map registers\n");
+ return -ENOSYS;
+ }
+ /* these offsets are from mpc5200 users manual */
+ sdram = mbar + 0x100;
+ cdm = mbar + 0x200;
+ intr = mbar + 0x500;
+ gpiow = mbar + 0xc00;
+ sram = mbar + 0x8000; /* Those will be handled by the */
+ sram_size = 0x4000; /* bestcomm driver soon */
+
+ /* call board suspend code, if applicable */
+ if (mpc52xx_suspend.board_suspend_prepare)
+ mpc52xx_suspend.board_suspend_prepare(mbar);
+ else {
+ printk(KERN_ALERT "%s: %i don't know how to wake up the board\n",
+ __func__, __LINE__);
+ goto out_unmap;
+ }
+
+ return 0;
+
+ out_unmap:
+ iounmap(mbar);
+ return -ENOSYS;
+}
+
+
+char saved_sram[0x4000];
+
+int mpc52xx_pm_enter(suspend_state_t state)
+{
+ u32 clk_enables;
+ u32 msr, hid0;
+ u32 intr_main_mask;
+ void __iomem * irq_0x500 = (void __iomem *)CONFIG_KERNEL_START + 0x500;
+ unsigned long irq_0x500_stop = (unsigned long)irq_0x500 + mpc52xx_ds_cached_size;
+ char saved_0x500[0x600-0x500];
+
+ if (WARN_ON(mpc52xx_ds_cached_size > sizeof(saved_0x500)))
+ return -ENOMEM;
+
+ /* disable all interrupts in PIC */
+ intr_main_mask = in_be32(&intr->main_mask);
+ out_be32(&intr->main_mask, intr_main_mask | 0x1ffff);
+
+ /* don't let DEC expire any time soon */
+ mtspr(SPRN_DEC, 0x7fffffff);
+
+ /* save SRAM */
+ memcpy(saved_sram, sram, sram_size);
+
+ /* copy low level suspend code to sram */
+ memcpy(sram, mpc52xx_ds_sram, mpc52xx_ds_sram_size);
+
+ out_8(&cdm->ccs_sleep_enable, 1);
+ out_8(&cdm->osc_sleep_enable, 1);
+ out_8(&cdm->ccs_qreq_test, 1);
+
+ /* disable all but SDRAM and bestcomm (SRAM) clocks */
+ clk_enables = in_be32(&cdm->clk_enables);
+ out_be32(&cdm->clk_enables, clk_enables & 0x00088000);
+
+ /* disable power management */
+ msr = mfmsr();
+ mtmsr(msr & ~MSR_POW);
+
+ /* enable sleep mode, disable others */
+ hid0 = mfspr(SPRN_HID0);
+ mtspr(SPRN_HID0, (hid0 & ~(HID0_DOZE | HID0_NAP | HID0_DPM)) | HID0_SLEEP);
+
+ /* save original, copy our irq handler, flush from dcache and invalidate icache */
+ memcpy(saved_0x500, irq_0x500, mpc52xx_ds_cached_size);
+ memcpy(irq_0x500, mpc52xx_ds_cached, mpc52xx_ds_cached_size);
+ flush_icache_range((unsigned long)irq_0x500, irq_0x500_stop);
+
+ /* call low-level sleep code */
+ mpc52xx_deep_sleep(sram, sdram, cdm, intr);
+
+ /* restore original irq handler */
+ memcpy(irq_0x500, saved_0x500, mpc52xx_ds_cached_size);
+ flush_icache_range((unsigned long)irq_0x500, irq_0x500_stop);
+
+ /* restore old power mode */
+ mtmsr(msr & ~MSR_POW);
+ mtspr(SPRN_HID0, hid0);
+ mtmsr(msr);
+
+ out_be32(&cdm->clk_enables, clk_enables);
+ out_8(&cdm->ccs_sleep_enable, 0);
+ out_8(&cdm->osc_sleep_enable, 0);
+
+ /* restore SRAM */
+ memcpy(sram, saved_sram, sram_size);
+
+ /* reenable interrupts in PIC */
+ out_be32(&intr->main_mask, intr_main_mask);
+
+ return 0;
+}
+
+void mpc52xx_pm_finish(void)
+{
+ /* call board resume code */
+ if (mpc52xx_suspend.board_resume_finish)
+ mpc52xx_suspend.board_resume_finish(mbar);
+
+ iounmap(mbar);
+}
+
+static const struct platform_suspend_ops mpc52xx_pm_ops = {
+ .valid = mpc52xx_pm_valid,
+ .prepare = mpc52xx_pm_prepare,
+ .enter = mpc52xx_pm_enter,
+ .finish = mpc52xx_pm_finish,
+};
+
+int __init mpc52xx_pm_init(void)
+{
+ suspend_set_ops(&mpc52xx_pm_ops);
+ return 0;
+}
diff --git a/arch/powerpc/platforms/52xx/mpc52xx_sleep.S b/arch/powerpc/platforms/52xx/mpc52xx_sleep.S
new file mode 100644
index 000000000..a66eb311b
--- /dev/null
+++ b/arch/powerpc/platforms/52xx/mpc52xx_sleep.S
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include <asm/reg.h>
+#include <asm/ppc_asm.h>
+#include <asm/processor.h>
+
+
+.text
+
+_GLOBAL(mpc52xx_deep_sleep)
+mpc52xx_deep_sleep: /* args r3-r6: SRAM, SDRAM regs, CDM regs, INTR regs */
+
+ /* enable interrupts */
+ mfmsr r7
+ ori r7, r7, 0x8000 /* EE */
+ mtmsr r7
+ sync; isync;
+
+ li r10, 0 /* flag that irq handler sets */
+
+ /* enable tmr7 (or any other) interrupt */
+ lwz r8, 0x14(r6) /* intr->main_mask */
+ ori r8, r8, 0x1
+ xori r8, r8, 0x1
+ stw r8, 0x14(r6)
+ sync
+
+ /* emulate tmr7 interrupt */
+ li r8, 0x1
+ stw r8, 0x40(r6) /* intr->main_emulate */
+ sync
+
+ /* wait for it to happen */
+1:
+ cmpi cr0, r10, 1
+ bne cr0, 1b
+
+ /* lock icache */
+ mfspr r10, SPRN_HID0
+ ori r10, r10, 0x2000
+ sync; isync;
+ mtspr SPRN_HID0, r10
+ sync; isync;
+
+
+ mflr r9 /* save LR */
+
+ /* jump to sram */
+ mtlr r3
+ blrl
+
+ mtlr r9 /* restore LR */
+
+ /* unlock icache */
+ mfspr r10, SPRN_HID0
+ ori r10, r10, 0x2000
+ xori r10, r10, 0x2000
+ sync; isync;
+ mtspr SPRN_HID0, r10
+ sync; isync;
+
+
+ /* return to C code */
+ blr
+
+
+_GLOBAL(mpc52xx_ds_sram)
+mpc52xx_ds_sram:
+ /* put SDRAM into self-refresh */
+ lwz r8, 0x4(r4) /* sdram->ctrl */
+
+ oris r8, r8, 0x8000 /* mode_en */
+ stw r8, 0x4(r4)
+ sync
+
+ ori r8, r8, 0x0002 /* soft_pre */
+ stw r8, 0x4(r4)
+ sync
+ xori r8, r8, 0x0002
+
+ xoris r8, r8, 0x8000 /* !mode_en */
+ stw r8, 0x4(r4)
+ sync
+
+ oris r8, r8, 0x5000
+ xoris r8, r8, 0x4000 /* ref_en !cke */
+ stw r8, 0x4(r4)
+ sync
+
+ /* disable SDRAM clock */
+ lwz r8, 0x14(r5) /* cdm->clkenable */
+ ori r8, r8, 0x0008
+ xori r8, r8, 0x0008
+ stw r8, 0x14(r5)
+ sync
+
+
+ /* put mpc5200 to sleep */
+ mfmsr r10
+ oris r10, r10, 0x0004 /* POW = 1 */
+ sync; isync;
+ mtmsr r10
+ sync; isync;
+
+
+ /* enable clock */
+ lwz r8, 0x14(r5)
+ ori r8, r8, 0x0008
+ stw r8, 0x14(r5)
+ sync
+
+ /* get ram out of self-refresh */
+ lwz r8, 0x4(r4)
+ oris r8, r8, 0x5000 /* cke ref_en */
+ stw r8, 0x4(r4)
+ sync
+
+ blr
+_GLOBAL(mpc52xx_ds_sram_size)
+mpc52xx_ds_sram_size:
+ .long $-mpc52xx_ds_sram
+
+
+/* ### interrupt handler for wakeup from deep-sleep ### */
+_GLOBAL(mpc52xx_ds_cached)
+mpc52xx_ds_cached:
+ mtspr SPRN_SPRG0, r7
+ mtspr SPRN_SPRG1, r8
+
+ /* disable emulated interrupt */
+ mfspr r7, 311 /* MBAR */
+ addi r7, r7, 0x540 /* intr->main_emul */
+ li r8, 0
+ stw r8, 0(r7)
+ sync
+ dcbf 0, r7
+
+ /* acknowledge wakeup, so CCS releases power pown */
+ mfspr r7, 311 /* MBAR */
+ addi r7, r7, 0x524 /* intr->enc_status */
+ lwz r8, 0(r7)
+ ori r8, r8, 0x0400
+ stw r8, 0(r7)
+ sync
+ dcbf 0, r7
+
+ /* flag - we handled the interrupt */
+ li r10, 1
+
+ mfspr r8, SPRN_SPRG1
+ mfspr r7, SPRN_SPRG0
+
+ rfi
+_GLOBAL(mpc52xx_ds_cached_size)
+mpc52xx_ds_cached_size:
+ .long $-mpc52xx_ds_cached