summaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-socfpga
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-socfpga')
-rw-r--r--arch/arm/mach-socfpga/Kconfig30
-rw-r--r--arch/arm/mach-socfpga/Makefile10
-rw-r--r--arch/arm/mach-socfpga/core.h42
-rw-r--r--arch/arm/mach-socfpga/headsmp.S33
-rw-r--r--arch/arm/mach-socfpga/l2_cache.c79
-rw-r--r--arch/arm/mach-socfpga/ocram.c171
-rw-r--r--arch/arm/mach-socfpga/platsmp.c130
-rw-r--r--arch/arm/mach-socfpga/pm.c139
-rw-r--r--arch/arm/mach-socfpga/self-refresh.S125
-rw-r--r--arch/arm/mach-socfpga/socfpga.c120
10 files changed, 879 insertions, 0 deletions
diff --git a/arch/arm/mach-socfpga/Kconfig b/arch/arm/mach-socfpga/Kconfig
new file mode 100644
index 000000000..b62ae4daf
--- /dev/null
+++ b/arch/arm/mach-socfpga/Kconfig
@@ -0,0 +1,30 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menuconfig ARCH_SOCFPGA
+ bool "Altera SOCFPGA family"
+ depends on ARCH_MULTI_V7
+ select ARCH_HAS_RESET_CONTROLLER
+ select ARCH_SUPPORTS_BIG_ENDIAN
+ select ARM_AMBA
+ select ARM_GIC
+ select CACHE_L2X0
+ select DW_APB_TIMER_OF
+ select GPIO_PL061 if GPIOLIB
+ select HAVE_ARM_SCU
+ select HAVE_ARM_TWD if SMP
+ select MFD_SYSCON
+ select ARM_ERRATA_754322
+ select ARM_ERRATA_764369 if SMP
+ select ARM_ERRATA_775420
+ select PL310_ERRATA_588369
+ select PL310_ERRATA_727915
+ select PL310_ERRATA_753970 if PL310
+ select PL310_ERRATA_769419
+ select RESET_CONTROLLER
+
+if ARCH_SOCFPGA
+config SOCFPGA_SUSPEND
+ bool "Suspend to RAM on SOCFPGA"
+ help
+ Select this if you want to enable Suspend-to-RAM on SOCFPGA
+ platforms.
+endif
diff --git a/arch/arm/mach-socfpga/Makefile b/arch/arm/mach-socfpga/Makefile
new file mode 100644
index 000000000..9ec31fad7
--- /dev/null
+++ b/arch/arm/mach-socfpga/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the linux kernel.
+#
+
+obj-y := socfpga.o
+obj-$(CONFIG_SMP) += headsmp.o platsmp.o
+obj-$(CONFIG_SOCFPGA_SUSPEND) += pm.o self-refresh.o
+obj-$(CONFIG_EDAC_ALTERA_L2C) += l2_cache.o
+obj-$(CONFIG_EDAC_ALTERA_OCRAM) += ocram.o
diff --git a/arch/arm/mach-socfpga/core.h b/arch/arm/mach-socfpga/core.h
new file mode 100644
index 000000000..18f01190d
--- /dev/null
+++ b/arch/arm/mach-socfpga/core.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright 2012 Pavel Machek <pavel@denx.de>
+ * Copyright (C) 2012-2015 Altera Corporation
+ */
+
+#ifndef __MACH_CORE_H
+#define __MACH_CORE_H
+
+#define SOCFPGA_RSTMGR_CTRL 0x04
+#define SOCFPGA_RSTMGR_MODMPURST 0x10
+#define SOCFPGA_RSTMGR_MODPERRST 0x14
+#define SOCFPGA_RSTMGR_BRGMODRST 0x1c
+
+#define SOCFPGA_A10_RSTMGR_CTRL 0xC
+#define SOCFPGA_A10_RSTMGR_MODMPURST 0x20
+
+/* System Manager bits */
+#define RSTMGR_CTRL_SWCOLDRSTREQ 0x1 /* Cold Reset */
+#define RSTMGR_CTRL_SWWARMRSTREQ 0x2 /* Warm Reset */
+
+#define RSTMGR_MPUMODRST_CPU1 0x2 /* CPU1 Reset */
+
+void socfpga_init_l2_ecc(void);
+void socfpga_init_ocram_ecc(void);
+void socfpga_init_arria10_l2_ecc(void);
+void socfpga_init_arria10_ocram_ecc(void);
+
+extern void __iomem *sys_manager_base_addr;
+extern void __iomem *rst_manager_base_addr;
+extern void __iomem *sdr_ctl_base_addr;
+
+u32 socfpga_sdram_self_refresh(u32 sdr_base);
+extern unsigned int socfpga_sdram_self_refresh_sz;
+
+extern char secondary_trampoline[], secondary_trampoline_end[];
+
+extern unsigned long socfpga_cpu1start_addr;
+
+#define SOCFPGA_SCU_VIRT_BASE 0xfee00000
+
+#endif
diff --git a/arch/arm/mach-socfpga/headsmp.S b/arch/arm/mach-socfpga/headsmp.S
new file mode 100644
index 000000000..54f1844ea
--- /dev/null
+++ b/arch/arm/mach-socfpga/headsmp.S
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2003 ARM Limited
+ * Copyright (c) u-boot contributors
+ * Copyright (c) 2012 Pavel Machek <pavel@denx.de>
+ */
+#include <linux/linkage.h>
+#include <linux/init.h>
+#include <asm/memory.h>
+#include <asm/assembler.h>
+
+ .arch armv7-a
+ .arm
+
+ENTRY(secondary_trampoline)
+ /* CPU1 will always fetch from 0x0 when it is brought out of reset.
+ * Thus, we can just subtract the PAGE_OFFSET to get the physical
+ * address of &cpu1start_addr. This would not work for platforms
+ * where the physical memory does not start at 0x0.
+ */
+ARM_BE8(setend be)
+ adr r0, 1f
+ ldmia r0, {r1, r2}
+ sub r2, r2, #PAGE_OFFSET
+ ldr r3, [r2]
+ ldr r4, [r3]
+ARM_BE8(rev r4, r4)
+ bx r4
+
+ .align
+1: .long .
+ .long socfpga_cpu1start_addr
+ENTRY(secondary_trampoline_end)
diff --git a/arch/arm/mach-socfpga/l2_cache.c b/arch/arm/mach-socfpga/l2_cache.c
new file mode 100644
index 000000000..99fb95361
--- /dev/null
+++ b/arch/arm/mach-socfpga/l2_cache.c
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright Altera Corporation (C) 2016. All rights reserved.
+ */
+#include <linux/io.h>
+#include <linux/of_platform.h>
+#include <linux/of_address.h>
+
+#include "core.h"
+
+/* A10 System Manager L2 ECC Control register */
+#define A10_MPU_CTRL_L2_ECC_OFST 0x0
+#define A10_MPU_CTRL_L2_ECC_EN BIT(0)
+
+/* A10 System Manager Global IRQ Mask register */
+#define A10_SYSMGR_ECC_INTMASK_CLR_OFST 0x98
+#define A10_SYSMGR_ECC_INTMASK_CLR_L2 BIT(0)
+
+/* A10 System Manager L2 ECC IRQ Clear register */
+#define A10_SYSMGR_MPU_CLEAR_L2_ECC_OFST 0xA8
+#define A10_SYSMGR_MPU_CLEAR_L2_ECC (BIT(31) | BIT(15))
+
+void socfpga_init_l2_ecc(void)
+{
+ struct device_node *np;
+ void __iomem *mapped_l2_edac_addr;
+
+ np = of_find_compatible_node(NULL, NULL, "altr,socfpga-l2-ecc");
+ if (!np) {
+ pr_err("Unable to find socfpga-l2-ecc in dtb\n");
+ return;
+ }
+
+ mapped_l2_edac_addr = of_iomap(np, 0);
+ of_node_put(np);
+ if (!mapped_l2_edac_addr) {
+ pr_err("Unable to find L2 ECC mapping in dtb\n");
+ return;
+ }
+
+ /* Enable ECC */
+ writel(0x01, mapped_l2_edac_addr);
+ iounmap(mapped_l2_edac_addr);
+}
+
+void socfpga_init_arria10_l2_ecc(void)
+{
+ struct device_node *np;
+ void __iomem *mapped_l2_edac_addr;
+
+ /* Find the L2 EDAC device tree node */
+ np = of_find_compatible_node(NULL, NULL, "altr,socfpga-a10-l2-ecc");
+ if (!np) {
+ pr_err("Unable to find socfpga-a10-l2-ecc in dtb\n");
+ return;
+ }
+
+ mapped_l2_edac_addr = of_iomap(np, 0);
+ of_node_put(np);
+ if (!mapped_l2_edac_addr) {
+ pr_err("Unable to find L2 ECC mapping in dtb\n");
+ return;
+ }
+
+ if (!sys_manager_base_addr) {
+ pr_err("System Manager not mapped for L2 ECC\n");
+ goto exit;
+ }
+ /* Clear any pending IRQs */
+ writel(A10_SYSMGR_MPU_CLEAR_L2_ECC, (sys_manager_base_addr +
+ A10_SYSMGR_MPU_CLEAR_L2_ECC_OFST));
+ /* Enable ECC */
+ writel(A10_SYSMGR_ECC_INTMASK_CLR_L2, sys_manager_base_addr +
+ A10_SYSMGR_ECC_INTMASK_CLR_OFST);
+ writel(A10_MPU_CTRL_L2_ECC_EN, mapped_l2_edac_addr +
+ A10_MPU_CTRL_L2_ECC_OFST);
+exit:
+ iounmap(mapped_l2_edac_addr);
+}
diff --git a/arch/arm/mach-socfpga/ocram.c b/arch/arm/mach-socfpga/ocram.c
new file mode 100644
index 000000000..b4d397e83
--- /dev/null
+++ b/arch/arm/mach-socfpga/ocram.c
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright Altera Corporation (C) 2016. All rights reserved.
+ */
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/genalloc.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+
+#include "core.h"
+
+#define ALTR_OCRAM_CLEAR_ECC 0x00000018
+#define ALTR_OCRAM_ECC_EN 0x00000019
+
+void socfpga_init_ocram_ecc(void)
+{
+ struct device_node *np;
+ void __iomem *mapped_ocr_edac_addr;
+
+ /* Find the OCRAM EDAC device tree node */
+ np = of_find_compatible_node(NULL, NULL, "altr,socfpga-ocram-ecc");
+ if (!np) {
+ pr_err("Unable to find socfpga-ocram-ecc\n");
+ return;
+ }
+
+ mapped_ocr_edac_addr = of_iomap(np, 0);
+ of_node_put(np);
+ if (!mapped_ocr_edac_addr) {
+ pr_err("Unable to map OCRAM ecc regs.\n");
+ return;
+ }
+
+ /* Clear any pending OCRAM ECC interrupts, then enable ECC */
+ writel(ALTR_OCRAM_CLEAR_ECC, mapped_ocr_edac_addr);
+ writel(ALTR_OCRAM_ECC_EN, mapped_ocr_edac_addr);
+
+ iounmap(mapped_ocr_edac_addr);
+}
+
+/* Arria10 OCRAM Section */
+#define ALTR_A10_ECC_CTRL_OFST 0x08
+#define ALTR_A10_OCRAM_ECC_EN_CTL (BIT(1) | BIT(0))
+#define ALTR_A10_ECC_INITA BIT(16)
+
+#define ALTR_A10_ECC_INITSTAT_OFST 0x0C
+#define ALTR_A10_ECC_INITCOMPLETEA BIT(0)
+#define ALTR_A10_ECC_INITCOMPLETEB BIT(8)
+
+#define ALTR_A10_ECC_ERRINTEN_OFST 0x10
+#define ALTR_A10_ECC_SERRINTEN BIT(0)
+
+#define ALTR_A10_ECC_INTSTAT_OFST 0x20
+#define ALTR_A10_ECC_SERRPENA BIT(0)
+#define ALTR_A10_ECC_DERRPENA BIT(8)
+#define ALTR_A10_ECC_ERRPENA_MASK (ALTR_A10_ECC_SERRPENA | \
+ ALTR_A10_ECC_DERRPENA)
+/* ECC Manager Defines */
+#define A10_SYSMGR_ECC_INTMASK_SET_OFST 0x94
+#define A10_SYSMGR_ECC_INTMASK_CLR_OFST 0x98
+#define A10_SYSMGR_ECC_INTMASK_OCRAM BIT(1)
+
+#define ALTR_A10_ECC_INIT_WATCHDOG_10US 10000
+
+static inline void ecc_set_bits(u32 bit_mask, void __iomem *ioaddr)
+{
+ u32 value = readl(ioaddr);
+
+ value |= bit_mask;
+ writel(value, ioaddr);
+}
+
+static inline void ecc_clear_bits(u32 bit_mask, void __iomem *ioaddr)
+{
+ u32 value = readl(ioaddr);
+
+ value &= ~bit_mask;
+ writel(value, ioaddr);
+}
+
+static inline int ecc_test_bits(u32 bit_mask, void __iomem *ioaddr)
+{
+ u32 value = readl(ioaddr);
+
+ return (value & bit_mask) ? 1 : 0;
+}
+
+/*
+ * This function uses the memory initialization block in the Arria10 ECC
+ * controller to initialize/clear the entire memory data and ECC data.
+ */
+static int altr_init_memory_port(void __iomem *ioaddr)
+{
+ int limit = ALTR_A10_ECC_INIT_WATCHDOG_10US;
+
+ ecc_set_bits(ALTR_A10_ECC_INITA, (ioaddr + ALTR_A10_ECC_CTRL_OFST));
+ while (limit--) {
+ if (ecc_test_bits(ALTR_A10_ECC_INITCOMPLETEA,
+ (ioaddr + ALTR_A10_ECC_INITSTAT_OFST)))
+ break;
+ udelay(1);
+ }
+ if (limit < 0)
+ return -EBUSY;
+
+ /* Clear any pending ECC interrupts */
+ writel(ALTR_A10_ECC_ERRPENA_MASK,
+ (ioaddr + ALTR_A10_ECC_INTSTAT_OFST));
+
+ return 0;
+}
+
+void socfpga_init_arria10_ocram_ecc(void)
+{
+ struct device_node *np;
+ int ret = 0;
+ void __iomem *ecc_block_base;
+
+ if (!sys_manager_base_addr) {
+ pr_err("SOCFPGA: sys-mgr is not initialized\n");
+ return;
+ }
+
+ /* Find the OCRAM EDAC device tree node */
+ np = of_find_compatible_node(NULL, NULL, "altr,socfpga-a10-ocram-ecc");
+ if (!np) {
+ pr_err("Unable to find socfpga-a10-ocram-ecc\n");
+ return;
+ }
+
+ /* Map the ECC Block */
+ ecc_block_base = of_iomap(np, 0);
+ of_node_put(np);
+ if (!ecc_block_base) {
+ pr_err("Unable to map OCRAM ECC block\n");
+ return;
+ }
+
+ /* Disable ECC */
+ writel(ALTR_A10_OCRAM_ECC_EN_CTL,
+ sys_manager_base_addr + A10_SYSMGR_ECC_INTMASK_SET_OFST);
+ ecc_clear_bits(ALTR_A10_ECC_SERRINTEN,
+ (ecc_block_base + ALTR_A10_ECC_ERRINTEN_OFST));
+ ecc_clear_bits(ALTR_A10_OCRAM_ECC_EN_CTL,
+ (ecc_block_base + ALTR_A10_ECC_CTRL_OFST));
+
+ /* Ensure all writes complete */
+ wmb();
+
+ /* Use HW initialization block to initialize memory for ECC */
+ ret = altr_init_memory_port(ecc_block_base);
+ if (ret) {
+ pr_err("ECC: cannot init OCRAM PORTA memory\n");
+ goto exit;
+ }
+
+ /* Enable ECC */
+ ecc_set_bits(ALTR_A10_OCRAM_ECC_EN_CTL,
+ (ecc_block_base + ALTR_A10_ECC_CTRL_OFST));
+ ecc_set_bits(ALTR_A10_ECC_SERRINTEN,
+ (ecc_block_base + ALTR_A10_ECC_ERRINTEN_OFST));
+ writel(ALTR_A10_OCRAM_ECC_EN_CTL,
+ sys_manager_base_addr + A10_SYSMGR_ECC_INTMASK_CLR_OFST);
+
+ /* Ensure all writes complete */
+ wmb();
+exit:
+ iounmap(ecc_block_base);
+}
diff --git a/arch/arm/mach-socfpga/platsmp.c b/arch/arm/mach-socfpga/platsmp.c
new file mode 100644
index 000000000..201191cf6
--- /dev/null
+++ b/arch/arm/mach-socfpga/platsmp.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2010-2011 Calxeda, Inc.
+ * Copyright 2012 Pavel Machek <pavel@denx.de>
+ * Based on platsmp.c, Copyright (C) 2002 ARM Ltd.
+ * Copyright (C) 2012 Altera Corporation
+ */
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/smp.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#include <asm/cacheflush.h>
+#include <asm/smp_scu.h>
+#include <asm/smp_plat.h>
+
+#include "core.h"
+
+static int socfpga_boot_secondary(unsigned int cpu, struct task_struct *idle)
+{
+ int trampoline_size = secondary_trampoline_end - secondary_trampoline;
+
+ if (socfpga_cpu1start_addr) {
+ /* This will put CPU #1 into reset. */
+ writel(RSTMGR_MPUMODRST_CPU1,
+ rst_manager_base_addr + SOCFPGA_RSTMGR_MODMPURST);
+
+ memcpy(phys_to_virt(0), secondary_trampoline, trampoline_size);
+
+ writel(__pa_symbol(secondary_startup),
+ sys_manager_base_addr + (socfpga_cpu1start_addr & 0x000000ff));
+
+ flush_cache_all();
+ smp_wmb();
+ outer_clean_range(0, trampoline_size);
+
+ /* This will release CPU #1 out of reset. */
+ writel(0, rst_manager_base_addr + SOCFPGA_RSTMGR_MODMPURST);
+ }
+
+ return 0;
+}
+
+static int socfpga_a10_boot_secondary(unsigned int cpu, struct task_struct *idle)
+{
+ int trampoline_size = secondary_trampoline_end - secondary_trampoline;
+
+ if (socfpga_cpu1start_addr) {
+ writel(RSTMGR_MPUMODRST_CPU1, rst_manager_base_addr +
+ SOCFPGA_A10_RSTMGR_MODMPURST);
+ memcpy(phys_to_virt(0), secondary_trampoline, trampoline_size);
+
+ writel(__pa_symbol(secondary_startup),
+ sys_manager_base_addr + (socfpga_cpu1start_addr & 0x00000fff));
+
+ flush_cache_all();
+ smp_wmb();
+ outer_clean_range(0, trampoline_size);
+
+ /* This will release CPU #1 out of reset. */
+ writel(0, rst_manager_base_addr + SOCFPGA_A10_RSTMGR_MODMPURST);
+ }
+
+ return 0;
+}
+
+static void __init socfpga_smp_prepare_cpus(unsigned int max_cpus)
+{
+ struct device_node *np;
+ void __iomem *socfpga_scu_base_addr;
+
+ np = of_find_compatible_node(NULL, NULL, "arm,cortex-a9-scu");
+ if (!np) {
+ pr_err("%s: missing scu\n", __func__);
+ return;
+ }
+
+ socfpga_scu_base_addr = of_iomap(np, 0);
+ if (!socfpga_scu_base_addr)
+ return;
+ scu_enable(socfpga_scu_base_addr);
+}
+
+#ifdef CONFIG_HOTPLUG_CPU
+/*
+ * platform-specific code to shutdown a CPU
+ *
+ * Called with IRQs disabled
+ */
+static void socfpga_cpu_die(unsigned int cpu)
+{
+ /* Do WFI. If we wake up early, go back into WFI */
+ while (1)
+ cpu_do_idle();
+}
+
+/*
+ * We need a dummy function so that platform_can_cpu_hotplug() knows
+ * we support CPU hotplug. However, the function does not need to do
+ * anything, because CPUs going offline just do WFI. We could reset
+ * the CPUs but it would increase power consumption.
+ */
+static int socfpga_cpu_kill(unsigned int cpu)
+{
+ return 1;
+}
+#endif
+
+static const struct smp_operations socfpga_smp_ops __initconst = {
+ .smp_prepare_cpus = socfpga_smp_prepare_cpus,
+ .smp_boot_secondary = socfpga_boot_secondary,
+#ifdef CONFIG_HOTPLUG_CPU
+ .cpu_die = socfpga_cpu_die,
+ .cpu_kill = socfpga_cpu_kill,
+#endif
+};
+
+static const struct smp_operations socfpga_a10_smp_ops __initconst = {
+ .smp_prepare_cpus = socfpga_smp_prepare_cpus,
+ .smp_boot_secondary = socfpga_a10_boot_secondary,
+#ifdef CONFIG_HOTPLUG_CPU
+ .cpu_die = socfpga_cpu_die,
+ .cpu_kill = socfpga_cpu_kill,
+#endif
+};
+
+CPU_METHOD_OF_DECLARE(socfpga_smp, "altr,socfpga-smp", &socfpga_smp_ops);
+CPU_METHOD_OF_DECLARE(socfpga_a10_smp, "altr,socfpga-a10-smp", &socfpga_a10_smp_ops);
diff --git a/arch/arm/mach-socfpga/pm.c b/arch/arm/mach-socfpga/pm.c
new file mode 100644
index 000000000..365c0428b
--- /dev/null
+++ b/arch/arm/mach-socfpga/pm.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * arch/arm/mach-socfpga/pm.c
+ *
+ * Copyright (C) 2014-2015 Altera Corporation. All rights reserved.
+ *
+ * with code from pm-imx6.c
+ * Copyright 2011-2014 Freescale Semiconductor, Inc.
+ * Copyright 2011 Linaro Ltd.
+ */
+
+#include <linux/bitops.h>
+#include <linux/genalloc.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/of_platform.h>
+#include <linux/suspend.h>
+#include <asm/suspend.h>
+#include <asm/fncpy.h>
+#include "core.h"
+
+/* Pointer to function copied to ocram */
+static u32 (*socfpga_sdram_self_refresh_in_ocram)(u32 sdr_base);
+
+static int socfpga_setup_ocram_self_refresh(void)
+{
+ struct platform_device *pdev;
+ phys_addr_t ocram_pbase;
+ struct device_node *np;
+ struct gen_pool *ocram_pool;
+ unsigned long ocram_base;
+ void __iomem *suspend_ocram_base;
+ int ret = 0;
+
+ np = of_find_compatible_node(NULL, NULL, "mmio-sram");
+ if (!np) {
+ pr_err("%s: Unable to find mmio-sram in dtb\n", __func__);
+ return -ENODEV;
+ }
+
+ pdev = of_find_device_by_node(np);
+ if (!pdev) {
+ pr_warn("%s: failed to find ocram device!\n", __func__);
+ ret = -ENODEV;
+ goto put_node;
+ }
+
+ ocram_pool = gen_pool_get(&pdev->dev, NULL);
+ if (!ocram_pool) {
+ pr_warn("%s: ocram pool unavailable!\n", __func__);
+ ret = -ENODEV;
+ goto put_device;
+ }
+
+ ocram_base = gen_pool_alloc(ocram_pool, socfpga_sdram_self_refresh_sz);
+ if (!ocram_base) {
+ pr_warn("%s: unable to alloc ocram!\n", __func__);
+ ret = -ENOMEM;
+ goto put_device;
+ }
+
+ ocram_pbase = gen_pool_virt_to_phys(ocram_pool, ocram_base);
+
+ suspend_ocram_base = __arm_ioremap_exec(ocram_pbase,
+ socfpga_sdram_self_refresh_sz,
+ false);
+ if (!suspend_ocram_base) {
+ pr_warn("%s: __arm_ioremap_exec failed!\n", __func__);
+ ret = -ENOMEM;
+ goto put_device;
+ }
+
+ /* Copy the code that puts DDR in self refresh to ocram */
+ socfpga_sdram_self_refresh_in_ocram =
+ (void *)fncpy(suspend_ocram_base,
+ &socfpga_sdram_self_refresh,
+ socfpga_sdram_self_refresh_sz);
+
+ WARN(!socfpga_sdram_self_refresh_in_ocram,
+ "could not copy function to ocram");
+ if (!socfpga_sdram_self_refresh_in_ocram)
+ ret = -EFAULT;
+
+put_device:
+ put_device(&pdev->dev);
+put_node:
+ of_node_put(np);
+
+ return ret;
+}
+
+static int socfpga_pm_suspend(unsigned long arg)
+{
+ u32 ret;
+
+ if (!sdr_ctl_base_addr)
+ return -EFAULT;
+
+ ret = socfpga_sdram_self_refresh_in_ocram((u32)sdr_ctl_base_addr);
+
+ pr_debug("%s self-refresh loops request=%d exit=%d\n", __func__,
+ ret & 0xffff, (ret >> 16) & 0xffff);
+
+ return 0;
+}
+
+static int socfpga_pm_enter(suspend_state_t state)
+{
+ switch (state) {
+ case PM_SUSPEND_MEM:
+ outer_disable();
+ cpu_suspend(0, socfpga_pm_suspend);
+ outer_resume();
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static const struct platform_suspend_ops socfpga_pm_ops = {
+ .valid = suspend_valid_only_mem,
+ .enter = socfpga_pm_enter,
+};
+
+static int __init socfpga_pm_init(void)
+{
+ int ret;
+
+ ret = socfpga_setup_ocram_self_refresh();
+ if (ret)
+ return ret;
+
+ suspend_set_ops(&socfpga_pm_ops);
+ pr_info("SoCFPGA initialized for DDR self-refresh during suspend.\n");
+
+ return 0;
+}
+arch_initcall(socfpga_pm_init);
diff --git a/arch/arm/mach-socfpga/self-refresh.S b/arch/arm/mach-socfpga/self-refresh.S
new file mode 100644
index 000000000..649f27790
--- /dev/null
+++ b/arch/arm/mach-socfpga/self-refresh.S
@@ -0,0 +1,125 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2014-2015 Altera Corporation. All rights reserved.
+ */
+#include <linux/linkage.h>
+#include <asm/assembler.h>
+
+#define MAX_LOOP_COUNT 1000
+
+/* Register offset */
+#define SDR_CTRLGRP_LOWPWREQ_ADDR 0x54
+#define SDR_CTRLGRP_LOWPWRACK_ADDR 0x58
+
+/* Bitfield positions */
+#define SELFRSHREQ_POS 3
+#define SELFRSHREQ_MASK 0x8
+
+#define SELFRFSHACK_POS 1
+#define SELFRFSHACK_MASK 0x2
+
+ /*
+ * This code assumes that when the bootloader configured
+ * the sdram controller for the DDR on the board it
+ * configured the following fields depending on the DDR
+ * vendor/configuration:
+ *
+ * sdr.ctrlcfg.lowpwreq.selfrfshmask
+ * sdr.ctrlcfg.lowpwrtiming.clkdisablecycles
+ * sdr.ctrlcfg.dramtiming4.selfrfshexit
+ */
+
+ .arch armv7-a
+ .text
+ .align 3
+
+ /*
+ * socfpga_sdram_self_refresh
+ *
+ * r0 : sdr_ctl_base_addr
+ * r1 : temp storage of return value
+ * r2 : temp storage of register values
+ * r3 : loop counter
+ *
+ * return value: lower 16 bits: loop count going into self refresh
+ * upper 16 bits: loop count exiting self refresh
+ */
+ENTRY(socfpga_sdram_self_refresh)
+ /* Enable dynamic clock gating in the Power Control Register. */
+ mrc p15, 0, r2, c15, c0, 0
+ orr r2, r2, #1
+ mcr p15, 0, r2, c15, c0, 0
+
+ /* Enable self refresh: set sdr.ctrlgrp.lowpwreq.selfrshreq = 1 */
+ ldr r2, [r0, #SDR_CTRLGRP_LOWPWREQ_ADDR]
+ orr r2, r2, #SELFRSHREQ_MASK
+ str r2, [r0, #SDR_CTRLGRP_LOWPWREQ_ADDR]
+
+ /* Poll until sdr.ctrlgrp.lowpwrack.selfrfshack == 1 or hit max loops */
+ mov r3, #0
+while_ack_0:
+ ldr r2, [r0, #SDR_CTRLGRP_LOWPWRACK_ADDR]
+ and r2, r2, #SELFRFSHACK_MASK
+ cmp r2, #SELFRFSHACK_MASK
+ beq ack_1
+
+ add r3, #1
+ cmp r3, #MAX_LOOP_COUNT
+ bne while_ack_0
+
+ack_1:
+ mov r1, r3
+
+ /*
+ * Execute an ISB instruction to ensure that all of the
+ * CP15 register changes have been committed.
+ */
+ isb
+
+ /*
+ * Execute a barrier instruction to ensure that all cache,
+ * TLB and branch predictor maintenance operations issued
+ * by any CPU in the cluster have completed.
+ */
+ dsb
+ dmb
+
+ wfi
+
+ /* Disable self-refresh: set sdr.ctrlgrp.lowpwreq.selfrshreq = 0 */
+ ldr r2, [r0, #SDR_CTRLGRP_LOWPWREQ_ADDR]
+ bic r2, r2, #SELFRSHREQ_MASK
+ str r2, [r0, #SDR_CTRLGRP_LOWPWREQ_ADDR]
+
+ /* Poll until sdr.ctrlgrp.lowpwrack.selfrfshack == 0 or hit max loops */
+ mov r3, #0
+while_ack_1:
+ ldr r2, [r0, #SDR_CTRLGRP_LOWPWRACK_ADDR]
+ and r2, r2, #SELFRFSHACK_MASK
+ cmp r2, #SELFRFSHACK_MASK
+ bne ack_0
+
+ add r3, #1
+ cmp r3, #MAX_LOOP_COUNT
+ bne while_ack_1
+
+ack_0:
+ /*
+ * Prepare return value:
+ * Shift loop count for exiting self refresh into upper 16 bits.
+ * Leave loop count for requesting self refresh in lower 16 bits.
+ */
+ mov r3, r3, lsl #16
+ add r1, r1, r3
+
+ /* Disable dynamic clock gating in the Power Control Register. */
+ mrc p15, 0, r2, c15, c0, 0
+ bic r2, r2, #1
+ mcr p15, 0, r2, c15, c0, 0
+
+ mov r0, r1 @ return value
+ bx lr @ return
+
+ENDPROC(socfpga_sdram_self_refresh)
+ENTRY(socfpga_sdram_self_refresh_sz)
+ .word . - socfpga_sdram_self_refresh
diff --git a/arch/arm/mach-socfpga/socfpga.c b/arch/arm/mach-socfpga/socfpga.c
new file mode 100644
index 000000000..9e4cb2ffd
--- /dev/null
+++ b/arch/arm/mach-socfpga/socfpga.c
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2012-2015 Altera Corporation
+ */
+#include <linux/irqchip.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/reboot.h>
+#include <linux/reset/socfpga.h>
+
+#include <asm/hardware/cache-l2x0.h>
+#include <asm/mach/arch.h>
+#include <asm/mach/map.h>
+#include <asm/cacheflush.h>
+
+#include "core.h"
+
+void __iomem *sys_manager_base_addr;
+void __iomem *rst_manager_base_addr;
+void __iomem *sdr_ctl_base_addr;
+unsigned long socfpga_cpu1start_addr;
+
+static void __init socfpga_sysmgr_init(void)
+{
+ struct device_node *np;
+
+ np = of_find_compatible_node(NULL, NULL, "altr,sys-mgr");
+
+ if (of_property_read_u32(np, "cpu1-start-addr",
+ (u32 *) &socfpga_cpu1start_addr))
+ pr_err("SMP: Need cpu1-start-addr in device tree.\n");
+
+ /* Ensure that socfpga_cpu1start_addr is visible to other CPUs */
+ smp_wmb();
+ sync_cache_w(&socfpga_cpu1start_addr);
+
+ sys_manager_base_addr = of_iomap(np, 0);
+
+ np = of_find_compatible_node(NULL, NULL, "altr,rst-mgr");
+ rst_manager_base_addr = of_iomap(np, 0);
+
+ np = of_find_compatible_node(NULL, NULL, "altr,sdr-ctl");
+ sdr_ctl_base_addr = of_iomap(np, 0);
+}
+
+static void __init socfpga_init_irq(void)
+{
+ irqchip_init();
+ socfpga_sysmgr_init();
+ if (IS_ENABLED(CONFIG_EDAC_ALTERA_L2C))
+ socfpga_init_l2_ecc();
+
+ if (IS_ENABLED(CONFIG_EDAC_ALTERA_OCRAM))
+ socfpga_init_ocram_ecc();
+ socfpga_reset_init();
+}
+
+static void __init socfpga_arria10_init_irq(void)
+{
+ irqchip_init();
+ socfpga_sysmgr_init();
+ if (IS_ENABLED(CONFIG_EDAC_ALTERA_L2C))
+ socfpga_init_arria10_l2_ecc();
+ if (IS_ENABLED(CONFIG_EDAC_ALTERA_OCRAM))
+ socfpga_init_arria10_ocram_ecc();
+ socfpga_reset_init();
+}
+
+static void socfpga_cyclone5_restart(enum reboot_mode mode, const char *cmd)
+{
+ u32 temp;
+
+ temp = readl(rst_manager_base_addr + SOCFPGA_RSTMGR_CTRL);
+
+ if (mode == REBOOT_WARM)
+ temp |= RSTMGR_CTRL_SWWARMRSTREQ;
+ else
+ temp |= RSTMGR_CTRL_SWCOLDRSTREQ;
+ writel(temp, rst_manager_base_addr + SOCFPGA_RSTMGR_CTRL);
+}
+
+static void socfpga_arria10_restart(enum reboot_mode mode, const char *cmd)
+{
+ u32 temp;
+
+ temp = readl(rst_manager_base_addr + SOCFPGA_A10_RSTMGR_CTRL);
+
+ if (mode == REBOOT_WARM)
+ temp |= RSTMGR_CTRL_SWWARMRSTREQ;
+ else
+ temp |= RSTMGR_CTRL_SWCOLDRSTREQ;
+ writel(temp, rst_manager_base_addr + SOCFPGA_A10_RSTMGR_CTRL);
+}
+
+static const char *altera_dt_match[] = {
+ "altr,socfpga",
+ NULL
+};
+
+DT_MACHINE_START(SOCFPGA, "Altera SOCFPGA")
+ .l2c_aux_val = 0,
+ .l2c_aux_mask = ~0,
+ .init_irq = socfpga_init_irq,
+ .restart = socfpga_cyclone5_restart,
+ .dt_compat = altera_dt_match,
+MACHINE_END
+
+static const char *altera_a10_dt_match[] = {
+ "altr,socfpga-arria10",
+ NULL
+};
+
+DT_MACHINE_START(SOCFPGA_A10, "Altera SOCFPGA Arria10")
+ .l2c_aux_val = 0,
+ .l2c_aux_mask = ~0,
+ .init_irq = socfpga_arria10_init_irq,
+ .restart = socfpga_arria10_restart,
+ .dt_compat = altera_a10_dt_match,
+MACHINE_END