[linux-yocto] [PATCH v2 03/39] arch/arm/mach-axxia: kernel files to support the mach-axxia
Cristian Bercaru
cristian.bercaru at windriver.com
Thu May 21 02:40:28 PDT 2015
From: Charlie Paul <cpaul.windriver at gmail.com>
These files are added and modified to support the LSI axxia
5500 board.
Signed-off-by: Charlie Paul <cpaul.windriver at gmail.com>
---
arch/arm/include/asm/lsi/acp_ncr.h | 45 +
arch/arm/mach-axxia/Kconfig | 39 +-
arch/arm/mach-axxia/Makefile | 19 +-
arch/arm/mach-axxia/Makefile.boot | 5 +
arch/arm/mach-axxia/axxia-gic.c | 1643 ++++++++++++++++++++++++
arch/arm/mach-axxia/axxia.c | 237 +++-
arch/arm/mach-axxia/axxia.h | 10 +
arch/arm/mach-axxia/axxia_circular_queue.c | 63 +
arch/arm/mach-axxia/axxia_circular_queue.h | 30 +
arch/arm/mach-axxia/clock.c | 109 ++
arch/arm/mach-axxia/ddr_retention.c | 352 +++++
arch/arm/mach-axxia/ddr_shutdown.c | 330 +++++
arch/arm/mach-axxia/headsmp.S | 71 +
arch/arm/mach-axxia/include/mach/axxia-gic.h | 17 +
arch/arm/mach-axxia/include/mach/debug-macro.S | 21 +
arch/arm/mach-axxia/include/mach/entry-macro.S | 5 +
arch/arm/mach-axxia/include/mach/gpio.h | 1 +
arch/arm/mach-axxia/include/mach/hardware.h | 24 +
arch/arm/mach-axxia/include/mach/io.h | 39 +
arch/arm/mach-axxia/include/mach/irqs.h | 5 +
arch/arm/mach-axxia/include/mach/ncr.h | 44 +
arch/arm/mach-axxia/include/mach/pci.h | 6 +
arch/arm/mach-axxia/include/mach/rio.h | 44 +
arch/arm/mach-axxia/include/mach/system.h | 33 +
arch/arm/mach-axxia/include/mach/timers.h | 39 +
arch/arm/mach-axxia/include/mach/timex.h | 23 +
arch/arm/mach-axxia/include/mach/uncompress.h | 65 +
arch/arm/mach-axxia/io.c | 40 +
arch/arm/mach-axxia/pci.c | 1134 ++++++++++++++++
arch/arm/mach-axxia/pci.h | 1 +
arch/arm/mach-axxia/platsmp.c | 299 ++++-
arch/arm/mach-axxia/smon.c | 223 ++++
arch/arm/mach-axxia/smon.h | 72 ++
arch/arm/mach-axxia/ssp-gpio.c | 136 ++
arch/arm/mach-axxia/timers.c | 224 ++++
35 files changed, 5394 insertions(+), 54 deletions(-)
create mode 100644 arch/arm/include/asm/lsi/acp_ncr.h
create mode 100644 arch/arm/mach-axxia/Makefile.boot
create mode 100644 arch/arm/mach-axxia/axxia-gic.c
create mode 100644 arch/arm/mach-axxia/axxia.h
create mode 100644 arch/arm/mach-axxia/axxia_circular_queue.c
create mode 100644 arch/arm/mach-axxia/axxia_circular_queue.h
create mode 100644 arch/arm/mach-axxia/clock.c
create mode 100644 arch/arm/mach-axxia/ddr_retention.c
create mode 100644 arch/arm/mach-axxia/ddr_shutdown.c
create mode 100644 arch/arm/mach-axxia/headsmp.S
create mode 100644 arch/arm/mach-axxia/include/mach/axxia-gic.h
create mode 100644 arch/arm/mach-axxia/include/mach/debug-macro.S
create mode 100644 arch/arm/mach-axxia/include/mach/entry-macro.S
create mode 100644 arch/arm/mach-axxia/include/mach/gpio.h
create mode 100644 arch/arm/mach-axxia/include/mach/hardware.h
create mode 100644 arch/arm/mach-axxia/include/mach/io.h
create mode 100644 arch/arm/mach-axxia/include/mach/irqs.h
create mode 100644 arch/arm/mach-axxia/include/mach/ncr.h
create mode 100644 arch/arm/mach-axxia/include/mach/pci.h
create mode 100644 arch/arm/mach-axxia/include/mach/rio.h
create mode 100644 arch/arm/mach-axxia/include/mach/system.h
create mode 100644 arch/arm/mach-axxia/include/mach/timers.h
create mode 100644 arch/arm/mach-axxia/include/mach/timex.h
create mode 100644 arch/arm/mach-axxia/include/mach/uncompress.h
create mode 100644 arch/arm/mach-axxia/io.c
create mode 100644 arch/arm/mach-axxia/pci.c
create mode 100644 arch/arm/mach-axxia/pci.h
create mode 100644 arch/arm/mach-axxia/smon.c
create mode 100644 arch/arm/mach-axxia/smon.h
create mode 100644 arch/arm/mach-axxia/ssp-gpio.c
create mode 100644 arch/arm/mach-axxia/timers.c
diff --git a/arch/arm/include/asm/lsi/acp_ncr.h b/arch/arm/include/asm/lsi/acp_ncr.h
new file mode 100644
index 0000000..114a36b
--- /dev/null
+++ b/arch/arm/include/asm/lsi/acp_ncr.h
@@ -0,0 +1,45 @@
+/*
+ * asm/lsi/acp_ncr.h
+ *
+ * Copyright (C) 2010 LSI
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __DRIVERS_LSI_ACP_NCR_H
+#define __DRIVERS_LSI_ACP_NCR_H
+
+#ifndef NCP_REGION_ID
+#define NCP_REGION_ID(node, target) \
+(unsigned long)((((node) & 0xffff) << 16) | ((target) & 0xffff))
+#endif
+
+#ifndef NCP_NODE_ID
+#define NCP_NODE_ID(region) (((region) >> 16) & 0xffff)
+#endif
+
+#ifndef NCP_TARGET_ID
+#define NCP_TARGET_ID(region) ((region) & 0xffff)
+#endif
+
+int ncr_read(unsigned long, unsigned long, int, void *);
+int ncr_write(unsigned long, unsigned long, int, void *);
+
+int is_asic(void);
+
+extern int acp_mdio_read(unsigned long, unsigned long, unsigned short *, int);
+extern int acp_mdio_write(unsigned long, unsigned long, unsigned short, int);
+
+#endif /* __DRIVERS_LSI_ACP_NCR_H */
diff --git a/arch/arm/mach-axxia/Kconfig b/arch/arm/mach-axxia/Kconfig
index 8be7e0a..4a4922f 100644
--- a/arch/arm/mach-axxia/Kconfig
+++ b/arch/arm/mach-axxia/Kconfig
@@ -1,16 +1,31 @@
-config ARCH_AXXIA
- bool "LSI Axxia platforms" if (ARCH_MULTI_V7 && ARM_LPAE)
- select ARCH_DMA_ADDR_T_64BIT
- select ARM_AMBA
- select ARM_GIC
- select ARM_TIMER_SP804
- select HAVE_ARM_ARCH_TIMER
- select MFD_SYSCON
- select MIGHT_HAVE_PCI
- select PCI_DOMAINS if PCI
- select ZONE_DMA
+menu "Axxia platform type"
+ depends on ARCH_AXXIA
+
+config ARCH_AXXIA_GIC
+ bool "Multi-cluster ARM GIC support for the LSI Axxia platforms"
+ select IRQ_DOMAIN
+ select MULTI_IRQ_HANDLER
help
- This enables support for the LSI Axxia devices.
+ The LSI Axxia platforms can have up to four clusters, each having
+ four cores (so a total of 16 cores). This requires the use of a
+ distributed interrupt system in lieu of of a single ARM GIC.
+
+ This option enables support for this multi-cluster setup.
+config ARCH_AXXIA_DT
+ bool "Device Tree support for LSI Axxia platforms"
+ select ARCH_AXXIA_GIC
+ select ARM_PATCH_PHYS_VIRT
+ select AUTO_ZRELADDR
+ select CPU_V7
+ select HAVE_SMP
+ select MIGHT_HAVE_CACHE_L2X0
+ select USE_OF
+ help
The LSI Axxia platforms require a Flattened Device Tree to be passed
to the kernel.
+
+ If your bootloader supports Flattened Device Tree based booting,
+ say Y here.
+
+endmenu
diff --git a/arch/arm/mach-axxia/Makefile b/arch/arm/mach-axxia/Makefile
index ec4f68b..b665db8 100644
--- a/arch/arm/mach-axxia/Makefile
+++ b/arch/arm/mach-axxia/Makefile
@@ -1,2 +1,17 @@
-obj-y += axxia.o
-obj-$(CONFIG_SMP) += platsmp.o
+#
+# Makefile for the linux kernel.
+#
+obj-y += axxia.o
+obj-y += clock.o
+obj-y += io.o
+obj-y += ssp-gpio.o
+#obj-y += ncr.o
+obj-y += timers.o
+obj-y += pci.o
+obj-y += ddr_retention.o ddr_shutdown.o
+obj-y += axxia_circular_queue.o
+obj-$(CONFIG_SMP) += platsmp.o headsmp.o
+obj-$(CONFIG_ARCH_AXXIA_GIC) += axxia-gic.o
+obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o lsi_power_management.o
+obj-$(CONFIG_AXXIA_RIO) += rapidio.o
+obj-$(CONFIG_HW_PERF_EVENTS) += perf_event_platform.o smon.o
diff --git a/arch/arm/mach-axxia/Makefile.boot b/arch/arm/mach-axxia/Makefile.boot
new file mode 100644
index 0000000..c6dd891
--- /dev/null
+++ b/arch/arm/mach-axxia/Makefile.boot
@@ -0,0 +1,5 @@
+# Those numbers are used only by the non-DT V2P-CA9 platform
+# The DT-enabled ones require CONFIG_AUTO_ZRELADDR=y
+ zreladdr-y += 0x60008000
+params_phys-y := 0x60000100
+initrd_phys-y := 0x60800000
diff --git a/arch/arm/mach-axxia/axxia-gic.c b/arch/arm/mach-axxia/axxia-gic.c
new file mode 100644
index 0000000..7ee51d7
--- /dev/null
+++ b/arch/arm/mach-axxia/axxia-gic.c
@@ -0,0 +1,1643 @@
+/*
+ * linux/arch/arm/mach-axxia/axxia-gic.c
+ *
+ * Cloned from linux/arch/arm/common/gic.c
+ *
+ * Copyright (C) 2013 LSI Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Interrupt architecture for the Axxia:
+ *
+ * o The Axxia chip can have up to four clusters, and each cluster has
+ * an ARM GIC interrupt controller.
+ *
+ * o In each GIC, there is one Interrupt Distributor, which receives
+ * interrupts from system devices and sends them to the Interrupt
+ * Controllers.
+ *
+ * o There is one CPU Interface per CPU, which sends interrupts sent
+ * by the Distributor, and interrupts generated locally, to the
+ * associated CPU. The base address of the CPU interface is usually
+ * aliased so that the same address points to different chips depending
+ * on the CPU it is accessed from.
+ *
+ * o The Axxia chip uses a distributed interrupt interface that's used
+ * for IPI messaging between clusters. Therefore, this design does not
+ * use the GIC software generated interrupts (0 - 16).
+ *
+ * Note that IRQs 0-31 are special - they are local to each CPU.
+ * As such, the enable set/clear, pending set/clear and active bit
+ * registers are banked per-cpu for these sources.
+ */
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/cpu_pm.h>
+#include <linux/irqdomain.h>
+#include <linux/irqchip/arm-gic.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+
+#include <asm/exception.h>
+#include <asm/smp_plat.h>
+#include <asm/mach/irq.h>
+
+#include <mach/axxia-gic.h>
+#include "lsi_power_management.h"
+#include "axxia_circular_queue.h"
+
+#define MAX_GIC_INTERRUPTS 1020
+
+static u32 irq_cpuid[MAX_GIC_INTERRUPTS];
+static void __iomem *ipi_mask_reg_base;
+static void __iomem *ipi_send_reg_base;
+
+/* AXM IPI numbers */
+enum axxia_ext_ipi_num {
+ IPI0_CPU0 = 227, /* Axm IPI 195 */
+ IPI0_CPU1,
+ IPI0_CPU2,
+ IPI0_CPU3,
+ IPI1_CPU0, /* Axm IPI 199 */
+ IPI1_CPU1,
+ IPI1_CPU2,
+ IPI1_CPU3,
+ IPI2_CPU0, /* Axm IPI 203 */
+ IPI2_CPU1,
+ IPI2_CPU2,
+ IPI2_CPU3,
+ IPI3_CPU0, /* Axm IPI 207 */
+ IPI3_CPU1,
+ IPI3_CPU2,
+ IPI3_CPU3,
+ MAX_AXM_IPI_NUM
+};
+
+/* MUX Message types. */
+enum axxia_mux_msg_type {
+ MUX_MSG_CALL_FUNC = 0,
+ MUX_MSG_CALL_FUNC_SINGLE,
+ MUX_MSG_CPU_STOP,
+ MUX_MSG_CPU_WAKEUP,
+ MUX_MSG_IRQ_WORK,
+ MUX_MSG_COMPLETION
+};
+
+struct axxia_mux_msg {
+ u32 msg;
+};
+
+static DEFINE_PER_CPU_SHARED_ALIGNED(struct axxia_mux_msg, ipi_mux_msg);
+
+static void muxed_ipi_message_pass(const struct cpumask *mask,
+ enum axxia_mux_msg_type ipi_num)
+{
+ struct axxia_mux_msg *info;
+ int cpu;
+
+ for_each_cpu(cpu, mask) {
+ info = &per_cpu(ipi_mux_msg, cpu_logical_map(cpu));
+ info->msg |= 1 << ipi_num;
+ }
+}
+
+#ifdef CONFIG_SMP
+static void axxia_ipi_demux(struct pt_regs *regs)
+{
+ struct axxia_mux_msg *info = this_cpu_ptr(&ipi_mux_msg);
+ u32 all;
+
+ do {
+ all = xchg(&info->msg, 0);
+ if (all & (1 << MUX_MSG_CALL_FUNC))
+ handle_IPI(3, regs); /* 3 = ARM IPI_CALL_FUNC */
+ if (all & (1 << MUX_MSG_CALL_FUNC_SINGLE))
+ handle_IPI(4, regs); /* 4 = ARM IPI_CALL_FUNC_SINGLE */
+ if (all & (1 << MUX_MSG_CPU_STOP))
+ handle_IPI(5, regs); /* 5 = ARM IPI_CPU_STOP */
+ if (all & (1 << MUX_MSG_IRQ_WORK))
+ handle_IPI(6, regs); /* 6 = ARM IPI_IRQ_WORK */
+ if (all & (1 << MUX_MSG_COMPLETION))
+ handle_IPI(7, regs); /* 7 = ARM IPI_COMPLETION */
+ if (all & (1 << MUX_MSG_CPU_WAKEUP))
+ handle_IPI(0, regs); /* 0 = ARM IPI_WAKEUP */
+ } while (info->msg);
+}
+#endif
+
+union gic_base {
+ void __iomem *common_base;
+ void __percpu __iomem **percpu_base;
+};
+
+struct gic_chip_data {
+ union gic_base dist_base;
+ union gic_base cpu_base;
+#ifdef CONFIG_CPU_PM
+ u32 saved_spi_enable[DIV_ROUND_UP(MAX_GIC_INTERRUPTS, 32)]
+ [MAX_NUM_CLUSTERS];
+ u32 saved_spi_conf[DIV_ROUND_UP(MAX_GIC_INTERRUPTS, 16)]
+ [MAX_NUM_CLUSTERS];
+ u32 saved_spi_target[DIV_ROUND_UP(MAX_GIC_INTERRUPTS, 4)]
+ [MAX_NUM_CLUSTERS];
+ u32 __percpu *saved_ppi_enable[MAX_NUM_CLUSTERS];
+ u32 __percpu *saved_ppi_conf[MAX_NUM_CLUSTERS];
+#endif
+ struct irq_domain *domain;
+ unsigned int gic_irqs;
+ unsigned long dist_init_done;
+};
+
+enum gic_rpc_func_mask {
+ IRQ_MASK = 0x01,
+ IRQ_UNMASK = 0x02,
+ SET_TYPE = 0x04,
+ SET_AFFINITY = 0x08,
+ CLR_AFFINITY = 0x10,
+ GIC_NOTIFIER = 0x20,
+ MAX_GIC_FUNC_MASK
+};
+
+
+#ifdef CONFIG_CPU_PM
+struct gic_notifier_data {
+ struct notifier_block *self;
+ unsigned long cmd;
+ void *v;
+
+};
+#endif
+
+struct gic_rpc_data {
+ struct irq_data *d;
+ u32 func_mask;
+ u32 cpu, oldcpu;
+ u32 type;
+ bool update_enable;
+ const struct cpumask *mask_val;
+#ifdef CONFIG_CPU_PM
+ struct gic_notifier_data gn_data;
+#endif
+};
+
+
+static DEFINE_RAW_SPINLOCK(irq_controller_lock);
+
+static DEFINE_MUTEX(irq_bus_lock);
+
+static struct gic_chip_data gic_data __read_mostly;
+static struct gic_rpc_data gic_rpc_data = {NULL, 0, 0, 0, 0, NULL};
+
+#define gic_data_dist_base(d) ((d)->dist_base.common_base)
+#define gic_data_cpu_base(d) ((d)->cpu_base.common_base)
+#define gic_set_base_accessor(d, f)
+
+static inline void __iomem *gic_dist_base(struct irq_data *d)
+{
+ struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d);
+
+ return gic_data_dist_base(gic_data);
+}
+
+static inline void __iomem *gic_cpu_base(struct irq_data *d)
+{
+ struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d);
+
+ return gic_data_cpu_base(gic_data);
+}
+
+static inline unsigned int gic_irq(struct irq_data *d)
+{
+ return d->hwirq;
+}
+
+
+/*************************** CIRCULAR QUEUE **************************************/
+struct circular_queue_t axxia_circ_q;
+static void axxia_gic_flush_affinity_queue(struct work_struct *dummy);
+static void gic_set_affinity_remote(void *info);
+static void gic_clr_affinity_remote(void *info);
+
+static DECLARE_WORK(axxia_gic_affinity_work, axxia_gic_flush_affinity_queue);
+static DEFINE_MUTEX(affinity_lock);
+
+enum axxia_affinity_mode {
+ AFFINITY_CLEAR_LOCAL = 1,
+ AFFINITY_CLEAR_OTHER_CLUSTER,
+ AFFINITY_SET_LOCAL,
+ AFFINITY_SET_OTHER_CLUSTER
+};
+
+static void axxia_gic_flush_affinity_queue(struct work_struct *dummy)
+{
+
+ void *qdata;
+ struct gic_rpc_data *rpc_data;
+
+ while (axxia_get_item(&axxia_circ_q, &qdata) != -1) {
+
+ rpc_data = (struct gic_rpc_data *) qdata;
+ if (rpc_data->func_mask == SET_AFFINITY) {
+ if (cpu_online(rpc_data->cpu)) {
+ smp_call_function_single(rpc_data->cpu, gic_set_affinity_remote,
+ qdata, 1);
+ }
+ } else if (rpc_data->func_mask == CLR_AFFINITY) {
+ if (cpu_online(rpc_data->cpu)) {
+ smp_call_function_single(rpc_data->cpu, gic_clr_affinity_remote,
+ qdata, 1);
+ }
+ }
+ kfree(qdata);
+ }
+
+}
+
+/*
+ * This GIC driver implements IRQ management routines (e.g., gic_mask_irq,
+ * etc.) that work across multiple clusters. Since a core cannot directly
+ * manipulate GIC registers on another cluster, the Linux RPC mechanism
+ * (smp_call_function_single) is used to remotely execute these IRQ management
+ * routines. However, the IRQ management routines are invoked in thread
+ * context (interrupts disabled on the local core), and for this reason,
+ * smp_call_function_single() cannot be used directly.
+ *
+ * The Linux interrupt code has a mechanism, which is called bus lock/unlock,
+ * which was created for irq chips hanging off slow busses like i2c/spi. The
+ * bus lock is mutex that is used to serialize bus accesses. We take advantage
+ *
+ * of this feature here, because we can think of IRQ management routines having
+ * to remotely execute on other clusters as a "slow bus" action. Doing this
+ * here serializes all IRQ management interfaces and guarantees that different
+ * callers cannot interfere.
+ *
+ * So the way this works is as follows:
+ *
+ * ==> Start IRQ management action
+ * chip->bus_lock() <== Mutex is taken
+ * raw_spin_lock_irq(&irqdesc->lock) <== Interrupts disabled on local core
+ * chip->(GIC IRQ management routine) <== IRQ mgmt routine is executed. If
+ * the intended target core is on the
+ * the same core, then the work is
+ * done here. If the target core is on
+ * another cluster, then a global
+ * structure (gic_rpc_data) is filled
+ * in to pass along to a remote routine
+ * to execute, and no work is done yet.
+ * raw_spin_unlock_irq(&irqdesc->lock) <== Interrupts are re-enabled
+ * chip->bus_unlock() <== If the gic_rpc_data global was
+ * filled in, then the specified
+ * remote routine is executed via
+ * smp_call_function_single(). The
+ * mutex is then given. Note that
+ * here, IRQs are already re-enabled,
+ * so its safe to use the RPC here.
+ * <== End IRQ management action
+ *
+ * The gic_rpc_data global is filled in by the chip callback routines (e.g.,
+ * gic_mask_irq, gic_set_type, etc.). The bus lock/unlock routines are
+ * implemented as gic_irq_lock() and gic_irq_sync_unlock() respectively.
+ *
+ */
+
+/*
+ * Routines to acknowledge, disable and enable interrupts.
+ */
+
+static void _gic_mask_irq(struct irq_data *d, bool do_mask)
+{
+ u32 mask = 1 << (gic_irq(d) % 32);
+
+ raw_spin_lock(&irq_controller_lock);
+ if (do_mask)
+ writel_relaxed(mask, gic_dist_base(d) + GIC_DIST_ENABLE_CLEAR
+ + (gic_irq(d) / 32) * 4);
+ else
+ writel_relaxed(mask, gic_dist_base(d) + GIC_DIST_ENABLE_SET
+ + (gic_irq(d) / 32) * 4);
+ raw_spin_unlock(&irq_controller_lock);
+}
+
+/*
+ * Functions called by smp_call_function_single() must take the form:
+ *
+ * static void foo(void *)
+ *
+ */
+static void gic_mask_remote(void *info)
+{
+ struct irq_data *d = (struct irq_data *)info;
+
+ _gic_mask_irq(d, 1);
+}
+static void gic_unmask_remote(void *info)
+{
+ struct irq_data *d = (struct irq_data *)info;
+
+ _gic_mask_irq(d, 0);
+}
+
+static void gic_mask_unmask(struct irq_data *d, bool do_mask)
+{
+ u32 pcpu = cpu_logical_map(smp_processor_id());
+ u32 irqid = gic_irq(d);
+
+ BUG_ON(!irqs_disabled());
+
+ if (irqid >= MAX_GIC_INTERRUPTS)
+ return;
+
+ /* Don't mess with the AXM IPIs. */
+ if ((irqid >= IPI0_CPU0) && (irqid < MAX_AXM_IPI_NUM))
+ return;
+
+ /* Don't mess with the PMU IRQ either. */
+ if (irqid == IRQ_PMU)
+ return;
+
+ /* Deal with PPI interrupts directly. */
+ if ((irqid > 16) && (irqid < 32)) {
+ _gic_mask_irq(d, do_mask);
+ return;
+ }
+
+ /*
+ * If the cpu that this interrupt is assigned to falls within
+ * the same cluster as the cpu we're currently running on, do
+ * the IRQ [un]masking directly. Otherwise, use the RPC mechanism
+ * to remotely do the masking.
+ */
+ if ((irq_cpuid[irqid] / CORES_PER_CLUSTER) ==
+ (pcpu / CORES_PER_CLUSTER)) {
+ _gic_mask_irq(d, do_mask);
+ } else {
+ if (do_mask)
+ gic_rpc_data.func_mask |= IRQ_MASK;
+ else
+ gic_rpc_data.func_mask |= IRQ_UNMASK;
+ gic_rpc_data.cpu = irq_cpuid[irqid];
+ gic_rpc_data.d = d;
+ }
+}
+
+static void gic_mask_irq(struct irq_data *d)
+{
+ gic_mask_unmask(d, true);
+}
+
+static void gic_unmask_irq(struct irq_data *d)
+{
+ gic_mask_unmask(d, false);
+}
+
+static void gic_eoi_irq(struct irq_data *d)
+{
+ /*
+ * This always runs on the same cpu that is handling
+ * an IRQ, so no need to worry about running this on
+ * remote clusters.
+ */
+ writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_EOI);
+}
+
+static int _gic_set_type(struct irq_data *d, unsigned int type)
+{
+ void __iomem *base = gic_dist_base(d);
+ unsigned int gicirq = gic_irq(d);
+ u32 enablemask = 1 << (gicirq % 32);
+ u32 enableoff = (gicirq / 32) * 4;
+ u32 confmask = 0x2 << ((gicirq % 16) * 2);
+ u32 confoff = (gicirq / 16) * 4;
+ bool enabled = false;
+ u32 val;
+
+ raw_spin_lock(&irq_controller_lock);
+
+ val = readl_relaxed(base + GIC_DIST_CONFIG + confoff);
+ if (type == IRQ_TYPE_LEVEL_HIGH)
+ val &= ~confmask;
+ else if (type == IRQ_TYPE_EDGE_RISING)
+ val |= confmask;
+
+ /*
+ * As recommended by the ARM GIC architecture spec, disable the
+ * interrupt before changing the configuration. We cannot rely
+ * on IRQCHIP_SET_TYPE_MASKED behavior for this.
+ */
+ if (readl_relaxed(base + GIC_DIST_ENABLE_SET + enableoff)
+ & enablemask) {
+ writel_relaxed(enablemask,
+ base + GIC_DIST_ENABLE_CLEAR + enableoff);
+ enabled = true;
+ }
+
+ writel_relaxed(val, base + GIC_DIST_CONFIG + confoff);
+
+ if (enabled)
+ writel_relaxed(enablemask,
+ base + GIC_DIST_ENABLE_SET + enableoff);
+
+ raw_spin_unlock(&irq_controller_lock);
+
+ return IRQ_SET_MASK_OK;
+}
+
+/*
+ * Functions called by smp_call_function_single() must take the form:
+ *
+ * static void foo(void *)
+ *
+ */
+static void gic_set_type_remote(void *info)
+{
+ struct gic_rpc_data *rpc = (struct gic_rpc_data *)info;
+
+ _gic_set_type(rpc->d, rpc->type);
+}
+
+static int gic_set_type(struct irq_data *d, unsigned int type)
+{
+ unsigned int gicirq = gic_irq(d);
+
+ BUG_ON(!irqs_disabled());
+
+ /* Interrupt configuration for SGIs can't be changed. */
+ if (gicirq < 16)
+ return -EINVAL;
+
+ /* Interrupt configuration for the AXM IPIs can't be changed. */
+ if ((gicirq >= IPI0_CPU0) && (gicirq < MAX_AXM_IPI_NUM))
+ return -EINVAL;
+
+ /* We only support two interrupt trigger types. */
+ if (type != IRQ_TYPE_LEVEL_HIGH && type != IRQ_TYPE_EDGE_RISING)
+ return -EINVAL;
+
+ /*
+ * Duplicate IRQ type settings across all clusters. Run
+ * directly for this cluster, use RPC for all others.
+ */
+ _gic_set_type(d, type);
+
+ gic_rpc_data.d = d;
+ gic_rpc_data.func_mask |= SET_TYPE;
+ gic_rpc_data.cpu = cpu_logical_map(smp_processor_id());
+ gic_rpc_data.type = type;
+
+ return IRQ_SET_MASK_OK;
+}
+
+static int gic_retrigger(struct irq_data *d)
+{
+ return -ENXIO;
+}
+
+static void gic_set_irq_target(void __iomem *dist_base,
+ u32 irqid, u32 cpu, bool set)
+{
+ void __iomem *reg;
+ unsigned int shift;
+ u32 val;
+ u32 mask = 0;
+ u32 bit;
+
+ reg = dist_base + GIC_DIST_TARGET + (irqid & ~3);
+ shift = (irqid % 4) * 8;
+ mask = 0xff << shift;
+
+ val = readl_relaxed(reg) & ~mask;
+
+ if (!set)
+ /* Clear affinity, mask IRQ. */
+ writel_relaxed(val, reg);
+ else {
+ bit = 1 << ((cpu_logical_map(cpu) % CORES_PER_CLUSTER) + shift);
+ writel_relaxed(val | bit, reg);
+ }
+
+}
+
+static int _gic_clear_affinity(struct irq_data *d, u32 cpu, bool update_enable)
+{
+
+ u32 enable_mask, enable_offset;
+
+ raw_spin_lock(&irq_controller_lock);
+
+ gic_set_irq_target(gic_dist_base(d), gic_irq(d), cpu, false);
+
+ if (update_enable) {
+ enable_mask = 1 << (gic_irq(d) % 32);
+ enable_offset = 4 * (gic_irq(d) / 32);
+ writel_relaxed(enable_mask,
+ gic_data_dist_base(&gic_data) + GIC_DIST_ENABLE_CLEAR + enable_offset);
+ }
+
+ raw_spin_unlock(&irq_controller_lock);
+
+ return IRQ_SET_MASK_OK;
+
+}
+
+static int _gic_set_affinity(struct irq_data *d,
+ u32 cpu,
+ bool update_enable)
+{
+ u32 enable_mask, enable_offset;
+
+ raw_spin_lock(&irq_controller_lock);
+
+ gic_set_irq_target(gic_dist_base(d), gic_irq(d), cpu, true);
+
+ if (update_enable) {
+ enable_mask = 1 << (gic_irq(d) % 32);
+ enable_offset = 4 * (gic_irq(d) / 32);
+ writel_relaxed(enable_mask,
+ gic_data_dist_base(&gic_data) + GIC_DIST_ENABLE_SET + enable_offset);
+ }
+
+ raw_spin_unlock(&irq_controller_lock);
+
+ return IRQ_SET_MASK_OK;
+}
+
+/*
+ * Functions called by smp_call_function_single() must take the form:
+ *
+ * static void foo(void *)
+ *
+ */
+static void gic_set_affinity_remote(void *info)
+{
+ struct gic_rpc_data *rpc = (struct gic_rpc_data *)info;
+
+ _gic_set_affinity(rpc->d, rpc->cpu, rpc->update_enable);
+
+}
+static void gic_clr_affinity_remote(void *info)
+{
+ struct gic_rpc_data *rpc = (struct gic_rpc_data *)info;
+
+ _gic_clear_affinity(rpc->d, rpc->oldcpu, rpc->update_enable);
+
+}
+
+static int gic_set_affinity(struct irq_data *d,
+ const struct cpumask *mask_val,
+ bool force)
+{
+ u32 pcpu;
+ unsigned int irqid;
+ struct cpumask *affinity_mask;
+ u32 mask;
+ u32 oldcpu;
+ struct gic_rpc_data *gic_rpc_ptr;
+ int rval;
+ bool new_same_core = false;
+ bool old_same_core = false;
+ bool update_enable = false;
+ u32 clear_needed = 0;
+ u32 set_needed = 0;
+ u32 add_cpu;
+ u32 del_cpu;
+
+ BUG_ON(!irqs_disabled());
+
+
+ pcpu = cpu_logical_map(smp_processor_id());
+ irqid = gic_irq(d);
+ affinity_mask = (struct cpumask *)mask_val;
+ oldcpu = irq_cpuid[irqid];
+
+ if (irqid >= MAX_GIC_INTERRUPTS)
+ return -EINVAL;
+
+ /* Interrupt affinity for the AXM IPIs can't be changed. */
+ if ((irqid >= IPI0_CPU0) && (irqid < MAX_AXM_IPI_NUM))
+ return IRQ_SET_MASK_OK;
+
+ if (force)
+ add_cpu = cpumask_any(cpu_online_mask);
+ else
+ add_cpu = cpumask_any_and(affinity_mask, cpu_online_mask);
+
+ if (add_cpu >= nr_cpu_ids) {
+ pr_err("ERROR: no cpus left\n");
+ return -EINVAL;
+ }
+
+ del_cpu = oldcpu;
+
+ if (add_cpu == del_cpu)
+ return IRQ_SET_MASK_OK;
+
+ new_same_core =
+ ((add_cpu / CORES_PER_CLUSTER) == (pcpu / CORES_PER_CLUSTER)) ?
+ true : false;
+ old_same_core =
+ ((del_cpu / CORES_PER_CLUSTER) == (pcpu / CORES_PER_CLUSTER)) ?
+ true : false;
+
+ update_enable = ((add_cpu / CORES_PER_CLUSTER) == (del_cpu / CORES_PER_CLUSTER)) ? false : true;
+
+ if (new_same_core) {
+
+ if (old_same_core) {
+ clear_needed = AFFINITY_CLEAR_LOCAL;
+ set_needed = AFFINITY_SET_LOCAL;
+ } else {
+ set_needed = AFFINITY_SET_LOCAL;
+ clear_needed = AFFINITY_CLEAR_OTHER_CLUSTER;
+ }
+
+ } else {
+
+ if (old_same_core) {
+ set_needed = AFFINITY_SET_OTHER_CLUSTER;
+ clear_needed = AFFINITY_CLEAR_LOCAL;
+ } else {
+ set_needed = AFFINITY_SET_OTHER_CLUSTER;
+ clear_needed = AFFINITY_CLEAR_OTHER_CLUSTER;
+ }
+ }
+
+
+ /*
+ * We clear first to make sure the affinity mask always has a bit set,
+ * especially when the two cpus are in the same cluster.
+ */
+ if (irqid != IRQ_PMU) {
+ if (clear_needed == AFFINITY_CLEAR_LOCAL) {
+
+ _gic_clear_affinity(d, del_cpu, update_enable);
+
+ } else if (clear_needed == AFFINITY_CLEAR_OTHER_CLUSTER) {
+
+ mask = 0xf << ((oldcpu / CORES_PER_CLUSTER) * 4);
+ del_cpu = cpumask_any_and((struct cpumask *)&mask,
+ cpu_online_mask);
+
+ if (del_cpu < nr_cpu_ids) {
+
+ gic_rpc_ptr = kmalloc(sizeof(struct gic_rpc_data), GFP_KERNEL);
+ if (!gic_rpc_ptr) {
+ pr_err(
+ "ERROR: failed to get memory for workqueue to set affinity false\n");
+ mutex_unlock(&affinity_lock);
+ return -ENOMEM;
+ }
+
+ gic_rpc_ptr->func_mask = CLR_AFFINITY;
+ gic_rpc_ptr->cpu = del_cpu;
+ gic_rpc_ptr->oldcpu = oldcpu;
+ gic_rpc_ptr->d = d;
+ gic_rpc_ptr->update_enable = update_enable;
+ get_cpu();
+ rval = axxia_put_item(&axxia_circ_q, (void *) gic_rpc_ptr);
+ put_cpu();
+ if (rval) {
+ pr_err(
+ "ERROR: failed to add CLR_AFFINITY request for cpu: %d\n",
+ del_cpu);
+ kfree((void *) gic_rpc_ptr);
+ mutex_unlock(&affinity_lock);
+ return rval;
+ }
+ schedule_work_on(0, &axxia_gic_affinity_work);
+ } else
+ pr_err("ERROR: no CPUs left\n");
+ }
+ }
+
+ if (set_needed == AFFINITY_SET_LOCAL) {
+
+ _gic_set_affinity(d, add_cpu, update_enable);
+
+ } else if (set_needed == AFFINITY_SET_OTHER_CLUSTER) {
+
+ gic_rpc_ptr = kmalloc(sizeof(struct gic_rpc_data), GFP_KERNEL);
+ if (!gic_rpc_ptr) {
+ pr_err(
+ "ERROR: failed to get memory for workqueue to set affinity false\n");
+ mutex_unlock(&affinity_lock);
+ return -ENOMEM;
+ }
+
+ gic_rpc_ptr->func_mask = SET_AFFINITY;
+ gic_rpc_ptr->cpu = add_cpu;
+ gic_rpc_ptr->update_enable = update_enable;
+ gic_rpc_ptr->d = d;
+ get_cpu();
+ rval = axxia_put_item(&axxia_circ_q, (void *) gic_rpc_ptr);
+ put_cpu();
+ if (rval) {
+ pr_err("ERROR: failed to add SET_AFFINITY request for cpu: %d\n",
+ add_cpu);
+ kfree((void *) gic_rpc_ptr);
+ mutex_unlock(&affinity_lock);
+ return rval;
+ }
+ schedule_work_on(0, &axxia_gic_affinity_work);
+
+ }
+
+ /* Update Axxia IRQ affinity table with the new physical CPU number. */
+ irq_cpuid[irqid] = cpu_logical_map(add_cpu);
+
+ return IRQ_SET_MASK_OK;
+}
+
+#ifdef CONFIG_PM
+static int gic_set_wake(struct irq_data *d, unsigned int on)
+{
+ int ret = -ENXIO;
+
+ return ret;
+}
+
+#else
+#define gic_set_wake NULL
+#endif
+
+#ifdef CONFIG_HOTPLUG_CPU
+static u32 get_cluster_id(void)
+{
+ u32 mpidr, cluster;
+
+ mpidr = read_cpuid_mpidr();
+ cluster = MPIDR_AFFINITY_LEVEL(mpidr, 1);
+
+ /*
+ * Cluster ID should always be between 0 and 3.
+ * Anything else, return 0.
+ */
+ if (cluster >= MAX_NUM_CLUSTERS)
+ cluster = 0;
+
+ return cluster;
+}
+#endif
+
+#ifdef CONFIG_CPU_PM
+
+/*
+ * Saves the GIC distributor registers during suspend or idle. Must be called
+ * with interrupts disabled but before powering down the GIC. After calling
+ * this function, no interrupts will be delivered by the GIC, and another
+ * platform-specific wakeup source must be enabled.
+ */
+static void gic_dist_save(void)
+{
+ unsigned int gic_irqs;
+ void __iomem *dist_base;
+ int i;
+ u32 this_cluster;
+
+ this_cluster = get_cluster_id();
+
+ gic_irqs = gic_data.gic_irqs;
+ dist_base = gic_data_dist_base(&gic_data);
+
+ if (!dist_base)
+ return;
+
+ for (i = 0; i < DIV_ROUND_UP(gic_irqs, 16); i++)
+ gic_data.saved_spi_conf[i][this_cluster] =
+ readl_relaxed(dist_base + GIC_DIST_CONFIG + i * 4);
+
+ for (i = 0; i < DIV_ROUND_UP(gic_irqs, 4); i++)
+ gic_data.saved_spi_target[i][this_cluster] =
+ readl_relaxed(dist_base + GIC_DIST_TARGET + i * 4);
+
+ for (i = 0; i < DIV_ROUND_UP(gic_irqs, 32); i++)
+ gic_data.saved_spi_enable[i][this_cluster] =
+ readl_relaxed(dist_base + GIC_DIST_ENABLE_SET + i * 4);
+}
+
+/*
+ * Restores the GIC distributor registers during resume or when coming out of
+ * idle. Must be called before enabling interrupts. If a level interrupt
+ * that occured while the GIC was suspended is still present, it will be
+ * handled normally, but any edge interrupts that occured will not be seen by
+ * the GIC and need to be handled by the platform-specific wakeup source.
+ */
+static void gic_dist_restore(void)
+{
+ unsigned int gic_irqs;
+ unsigned int i;
+ void __iomem *dist_base;
+ u32 this_cluster;
+
+ this_cluster = get_cluster_id();
+
+ gic_irqs = gic_data.gic_irqs;
+ dist_base = gic_data_dist_base(&gic_data);
+
+ if (!dist_base)
+ return;
+
+ writel_relaxed(0, dist_base + GIC_DIST_CTRL);
+
+ for (i = 0; i < DIV_ROUND_UP(gic_irqs, 16); i++)
+ writel_relaxed(gic_data.saved_spi_conf[i][this_cluster],
+ dist_base + GIC_DIST_CONFIG + i * 4);
+
+ for (i = 0; i < DIV_ROUND_UP(gic_irqs, 4); i++)
+ writel_relaxed(0xa0a0a0a0,
+ dist_base + GIC_DIST_PRI + i * 4);
+
+ for (i = 0; i < DIV_ROUND_UP(gic_irqs, 4); i++)
+ writel_relaxed(gic_data.saved_spi_target[i][this_cluster],
+ dist_base + GIC_DIST_TARGET + i * 4);
+
+ for (i = 0; i < DIV_ROUND_UP(gic_irqs, 32); i++)
+ writel_relaxed(gic_data.saved_spi_enable[i][this_cluster],
+ dist_base + GIC_DIST_ENABLE_SET + i * 4);
+
+ writel_relaxed(1, dist_base + GIC_DIST_CTRL);
+}
+
+static void gic_cpu_save(void)
+{
+ int i;
+ u32 *ptr;
+ void __iomem *dist_base;
+ void __iomem *cpu_base;
+ u32 this_cluster;
+
+ this_cluster = get_cluster_id();
+
+ dist_base = gic_data_dist_base(&gic_data);
+ cpu_base = gic_data_cpu_base(&gic_data);
+
+ if (!dist_base || !cpu_base)
+ return;
+
+ ptr = __this_cpu_ptr(gic_data.saved_ppi_enable[this_cluster]);
+ for (i = 0; i < DIV_ROUND_UP(32, 32); i++)
+ ptr[i] = readl_relaxed(dist_base + GIC_DIST_ENABLE_SET + i * 4);
+
+ ptr = __this_cpu_ptr(gic_data.saved_ppi_conf[this_cluster]);
+ for (i = 0; i < DIV_ROUND_UP(32, 16); i++)
+ ptr[i] = readl_relaxed(dist_base + GIC_DIST_CONFIG + i * 4);
+
+}
+
+static void gic_cpu_restore(void)
+{
+ int i;
+ u32 *ptr;
+ void __iomem *dist_base;
+ void __iomem *cpu_base;
+ u32 this_cluster;
+
+ this_cluster = get_cluster_id();
+
+ dist_base = gic_data_dist_base(&gic_data);
+ cpu_base = gic_data_cpu_base(&gic_data);
+
+ if (!dist_base || !cpu_base)
+ return;
+
+ ptr = __this_cpu_ptr(gic_data.saved_ppi_enable[this_cluster]);
+ for (i = 0; i < DIV_ROUND_UP(32, 32); i++)
+ writel_relaxed(ptr[i], dist_base + GIC_DIST_ENABLE_SET + i * 4);
+
+ ptr = __this_cpu_ptr(gic_data.saved_ppi_conf[this_cluster]);
+ for (i = 0; i < DIV_ROUND_UP(32, 16); i++)
+ writel_relaxed(ptr[i], dist_base + GIC_DIST_CONFIG + i * 4);
+
+ for (i = 0; i < DIV_ROUND_UP(32, 4); i++)
+ writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4);
+
+ writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK);
+ writel_relaxed(1, cpu_base + GIC_CPU_CTRL);
+}
+
+static int _gic_notifier(struct notifier_block *self,
+ unsigned long cmd, void *v)
+{
+ switch (cmd) {
+ case CPU_PM_ENTER:
+ gic_cpu_save();
+ break;
+ case CPU_PM_ENTER_FAILED:
+ case CPU_PM_EXIT:
+ gic_cpu_restore();
+ break;
+ case CPU_CLUSTER_PM_ENTER:
+ gic_dist_save();
+ break;
+ case CPU_CLUSTER_PM_ENTER_FAILED:
+ case CPU_CLUSTER_PM_EXIT:
+ gic_dist_restore();
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+/* Mechanism for forwarding PM events to other clusters. */
+struct gic_notifier_wrapper_struct {
+ struct notifier_block *self;
+ unsigned long cmd;
+ void *v;
+};
+
+/*
+ * Functions called by smp_call_function_single() must take the form:
+ *
+ * static void foo(void *)
+ *
+ */
+static void gic_notifier_remote(void *info)
+{
+ struct gic_rpc_data *rpc = (struct gic_rpc_data *)info;
+
+ _gic_notifier(rpc->gn_data.self, rpc->gn_data.cmd, rpc->gn_data.v);
+}
+
+static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v)
+{
+ /* Execute on this cluster. */
+ _gic_notifier(self, cmd, v);
+
+ /* Use RPC mechanism to execute this at other clusters. */
+ gic_rpc_data.func_mask |= GIC_NOTIFIER;
+ gic_rpc_data.cpu = cpu_logical_map(smp_processor_id());
+ gic_rpc_data.gn_data.self = self;
+ gic_rpc_data.gn_data.cmd = cmd;
+ gic_rpc_data.gn_data.v = v;
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block gic_notifier_block = {
+ .notifier_call = gic_notifier,
+};
+
+static void __init gic_pm_init(struct gic_chip_data *gic)
+{
+ int i;
+
+ for (i = 0; i < MAX_NUM_CLUSTERS; i++) {
+ gic->saved_ppi_enable[i] =
+ __alloc_percpu(DIV_ROUND_UP(32, 32) * 4, sizeof(u32));
+ BUG_ON(!gic->saved_ppi_enable[i]);
+
+ gic->saved_ppi_conf[i] =
+ __alloc_percpu(DIV_ROUND_UP(32, 16) * 4, sizeof(u32));
+ BUG_ON(!gic->saved_ppi_conf[i]);
+ }
+
+ if (gic == &gic_data)
+ cpu_pm_register_notifier(&gic_notifier_block);
+}
+#else
+static void __init gic_pm_init(struct gic_chip_data *gic)
+{
+}
+#endif /* CONFIG_CPU_PM */
+
+/*
+ * GIC bus lock/unlock routines.
+ */
+
+static void gic_irq_lock(struct irq_data *d)
+{
+ /* Take the bus lock. */
+ mutex_lock(&irq_bus_lock);
+}
+
+static void gic_irq_sync_unlock(struct irq_data *d)
+{
+ int i, j, cpu;
+ int nr_cluster_ids = ((nr_cpu_ids - 1) / CORES_PER_CLUSTER) + 1;
+
+
+ if (gic_rpc_data.func_mask & IRQ_MASK) {
+ smp_call_function_single(gic_rpc_data.cpu,
+ gic_mask_remote,
+ d, 1);
+ }
+
+ if (gic_rpc_data.func_mask & IRQ_UNMASK) {
+ smp_call_function_single(gic_rpc_data.cpu,
+ gic_unmask_remote,
+ d, 1);
+ }
+
+ if (gic_rpc_data.func_mask & SET_TYPE) {
+ for (i = 0; i < nr_cluster_ids; i++) {
+
+ /* No need to run on local cluster. */
+ if (i == (gic_rpc_data.cpu / CORES_PER_CLUSTER))
+ continue;
+
+ /*
+ * Have some core in each cluster execute this,
+ * Start with the first core on that cluster.
+ */
+ cpu = i * CORES_PER_CLUSTER;
+ for (j = cpu; j < cpu + CORES_PER_CLUSTER; j++) {
+ if (cpu_online(j)) {
+ smp_call_function_single(j,
+ gic_set_type_remote,
+ &gic_rpc_data, 1);
+ break;
+ }
+ }
+ }
+ }
+
+#ifdef CONFIG_CPU_PM
+ if (gic_rpc_data.func_mask & GIC_NOTIFIER) {
+ for (i = 0; i < nr_cluster_ids; i++) {
+ /* No need to run on local cluster. */
+ if (i == (gic_rpc_data.cpu / CORES_PER_CLUSTER))
+ continue;
+
+ /*
+ * Have some core in each cluster execute this,
+ * Start with the first core on that cluster.
+ */
+ cpu = i * CORES_PER_CLUSTER;
+ for (j = cpu; j < cpu + CORES_PER_CLUSTER; j++) {
+ if (cpu_online(j)) {
+ smp_call_function_single(j,
+ gic_notifier_remote,
+ &gic_rpc_data, 1);
+ break;
+ }
+ }
+ }
+ }
+#endif
+
+ /* Reset RPC data. */
+ gic_rpc_data.func_mask = 0;
+
+ /* Give the bus lock. */
+ mutex_unlock(&irq_bus_lock);
+
+}
+
+static
+asmlinkage void __exception_irq_entry axxia_gic_handle_irq(struct pt_regs *regs)
+{
+ u32 irqstat, irqnr;
+ struct gic_chip_data *gic = &gic_data;
+ void __iomem *cpu_base = gic_data_cpu_base(gic);
+ void __iomem *dist_base = gic_data_dist_base(gic);
+ u32 pcpu = cpu_logical_map(smp_processor_id());
+ u32 cluster = pcpu / CORES_PER_CLUSTER;
+ u32 next, mask;
+
+ do {
+ irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
+ irqnr = irqstat & ~0x1c00;
+
+ if (likely(irqnr > 15 && irqnr <= MAX_GIC_INTERRUPTS)) {
+ irqnr = irq_find_mapping(gic->domain, irqnr);
+
+ /*
+ * Check if this is an external Axxia IPI interrupt.
+ * Translate to a standard ARM internal IPI number.
+ * The Axxia only has 4 IPI interrupts, so we
+ * multiplex various ARM IPIs into a single line
+ * as outlined below:
+ *
+ * IPI0_CPUx = IPI_TIMER (1)
+ * IPI1_CPUx = IPI_RESCHEDULE (2)
+ * IPI2_CPUx = IPI_CALL_FUNC (3) |
+ * IPI_CALL_FUNC_SINGLE (4) |
+ * IPI_CPU_STOP (5) |
+ * IPI_WAKEUP (0)
+ * IPI3_CPUx = Not Used
+ *
+ * Note that if the ipi_msg_type enum changes in
+ * arch/arm/kernel/smp.c then this will have to be
+ * updated as well.
+ */
+ switch (irqnr) {
+#ifdef CONFIG_SMP
+ case IPI0_CPU0:
+ case IPI0_CPU1:
+ case IPI0_CPU2:
+ case IPI0_CPU3:
+ writel_relaxed(irqnr, cpu_base + GIC_CPU_EOI);
+ handle_IPI(1, regs);
+ break;
+
+ case IPI1_CPU0:
+ case IPI1_CPU1:
+ case IPI1_CPU2:
+ case IPI1_CPU3:
+ writel_relaxed(irqnr, cpu_base + GIC_CPU_EOI);
+ handle_IPI(2, regs);
+ break;
+
+ case IPI2_CPU0:
+ case IPI2_CPU1:
+ case IPI2_CPU2:
+ case IPI2_CPU3:
+ writel_relaxed(irqnr, cpu_base + GIC_CPU_EOI);
+ axxia_ipi_demux(regs);
+ break;
+
+ case IPI3_CPU0:
+ case IPI3_CPU1:
+ case IPI3_CPU2:
+ case IPI3_CPU3:
+ /* Not currently used */
+ writel_relaxed(irqnr, cpu_base + GIC_CPU_EOI);
+ break;
+#endif
+
+ case IRQ_PMU:
+ /*
+ * The PMU IRQ line is OR'ed among all cores
+ * within a cluster, so no way to tell which
+ * core actually generated the interrupt.
+ * Therefore, rotate PMU IRQ affinity to allow
+ * perf to work accurately as possible. Skip
+ * over offline cpus.
+ */
+ do {
+ next = (++pcpu % CORES_PER_CLUSTER) +
+ (cluster * CORES_PER_CLUSTER);
+ } while (!cpu_online(next));
+
+ mask = 0x01 << (next % CORES_PER_CLUSTER);
+ raw_spin_lock(&irq_controller_lock);
+ writeb_relaxed(mask, dist_base +
+ GIC_DIST_TARGET + IRQ_PMU);
+ raw_spin_unlock(&irq_controller_lock);
+ /* Fall through ... */
+
+ default:
+ /* External interrupt */
+ handle_IRQ(irqnr, regs);
+ break;
+ }
+ continue;
+ }
+ if (irqnr < 16) {
+ writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
+#ifdef CONFIG_SMP
+ handle_IPI(irqnr, regs);
+#endif
+ continue;
+ }
+ break;
+ } while (1);
+}
+
+static struct irq_chip gic_chip = {
+ .name = "GIC",
+ .irq_bus_lock = gic_irq_lock,
+ .irq_bus_sync_unlock = gic_irq_sync_unlock,
+ .irq_mask = gic_mask_irq,
+ .irq_unmask = gic_unmask_irq,
+ .irq_eoi = gic_eoi_irq,
+ .irq_set_type = gic_set_type,
+ .irq_retrigger = gic_retrigger,
+ .irq_set_affinity = gic_set_affinity,
+ .irq_set_wake = gic_set_wake,
+};
+
+static void __init gic_axxia_init(struct gic_chip_data *gic)
+{
+ int i;
+ u32 cpumask;
+
+ /*
+ * Initialize the Axxia IRQ affinity table. All non-IPI
+ * interrupts are initially assigned to physical cpu 0.
+ */
+ for (i = 0; i < MAX_GIC_INTERRUPTS; i++)
+ irq_cpuid[i] = 0;
+
+ /* Unmask all Axxia IPI interrupts */
+ cpumask = 0;
+ for (i = 0; i < nr_cpu_ids; i++)
+ cpumask |= 1 << i;
+ for (i = 0; i < nr_cpu_ids; i++)
+ writel_relaxed(cpumask, ipi_mask_reg_base + 0x40 + i * 4);
+}
+
+static void gic_dist_init(struct gic_chip_data *gic)
+{
+ unsigned int i;
+ unsigned int gic_irqs = gic->gic_irqs;
+ void __iomem *base = gic_data_dist_base(gic);
+ u32 cpu = cpu_logical_map(smp_processor_id());
+ u8 cpumask_8;
+ u32 confmask;
+ u32 confoff;
+ u32 enablemask;
+ u32 enableoff;
+ u32 val;
+#ifdef CONFIG_HOTPLUG_CPU
+ u32 this_cluster = get_cluster_id();
+ u32 powered_on = 0;
+ u32 ccpu;
+#endif
+
+ /* Initialize the distributor interface once per CPU cluster */
+#ifdef CONFIG_HOTPLUG_CPU
+ if ((test_and_set_bit(get_cluster_id(), &gic->dist_init_done)) && (!cluster_power_up[this_cluster]))
+ return;
+#endif
+
+ writel_relaxed(0, base + GIC_DIST_CTRL);
+
+ /*################################# CONFIG IRQS ####################################*/
+
+ /*
+ * Set all global interrupts to be level triggered, active low.
+ */
+ for (i = 32; i < gic_irqs; i += 16)
+ writel_relaxed(0, base + GIC_DIST_CONFIG + i * 4 / 16);
+
+ /*
+ * Set Axxia IPI interrupts to be edge triggered.
+ */
+ for (i = IPI0_CPU0; i < MAX_AXM_IPI_NUM; i++) {
+ confmask = 0x2 << ((i % 16) * 2);
+ confoff = (i / 16) * 4;
+ val = readl_relaxed(base + GIC_DIST_CONFIG + confoff);
+ val |= confmask;
+ writel_relaxed(val, base + GIC_DIST_CONFIG + confoff);
+ }
+
+ /*################################# PRIORITY ####################################*/
+ /*
+ * Set priority on PPI and SGI interrupts
+ */
+ for (i = 0; i < 32; i += 4)
+ writel_relaxed(0xa0a0a0a0,
+ base + GIC_DIST_PRI + i * 4 / 4);
+
+ /*
+ * Set priority on all global interrupts.
+ */
+ for (i = 32; i < gic_irqs; i += 4)
+ writel_relaxed(0xa0a0a0a0, base + GIC_DIST_PRI + i * 4 / 4);
+
+
+ /*################################# TARGET ####################################*/
+ /*
+ * Set all global interrupts to this CPU only.
+ * (Only do this for the first core on cluster 0).
+ */
+ if (cpu == 0)
+ for (i = 32; i < gic_irqs; i += 4)
+ writel_relaxed(0x01010101, base + GIC_DIST_TARGET + i * 4 / 4);
+
+ /*
+ * Set Axxia IPI interrupts for all CPUs in this cluster.
+ */
+#ifdef CONFIG_HOTPLUG_CPU
+ powered_on = (~pm_cpu_powered_down) & 0xFFFF;
+#endif
+
+ for (i = IPI0_CPU0; i < MAX_AXM_IPI_NUM; i++) {
+ cpumask_8 = 1 << ((i - IPI0_CPU0) % 4);
+#ifdef CONFIG_HOTPLUG_CPU
+ ccpu = (this_cluster * 4) + ((i - IPI0_CPU0) % CORES_PER_CLUSTER);
+ if ((1 << ccpu) & powered_on)
+ writeb_relaxed(cpumask_8, base + GIC_DIST_TARGET + i);
+ else
+ writeb_relaxed(0x00, base + GIC_DIST_TARGET + i);
+#else
+ writeb_relaxed(cpumask_8, base + GIC_DIST_TARGET + i);
+#endif
+
+ }
+
+ /*################################# ENABLE IRQS ####################################*/
+ /*
+ * Do the initial enable of the Axxia IPI interrupts here.
+ * NOTE: Writing a 0 to this register has no effect, so
+ * no need to read and OR in bits, just writing is OK.
+ */
+
+#ifdef CONFIG_HOTPLUG_CPU
+ powered_on = (~pm_cpu_powered_down) & 0xFFFF;
+#endif
+
+ for (i = IPI0_CPU0; i < MAX_AXM_IPI_NUM; i++) {
+ enablemask = 1 << (i % 32);
+ enableoff = (i / 32) * 4;
+#ifdef CONFIG_HOTPLUG_CPU
+ ccpu = (this_cluster * 4) + ((i - IPI0_CPU0) % CORES_PER_CLUSTER);
+ if ((1 << ccpu) & powered_on)
+ writel_relaxed(enablemask, base + GIC_DIST_ENABLE_SET + enableoff);
+#else
+ writel_relaxed(enablemask, base + GIC_DIST_ENABLE_SET + enableoff);
+#endif
+ }
+
+ /*
+ * Do the initial enable of the PMU IRQ here.
+ */
+ enablemask = 1 << (IRQ_PMU % 32);
+ enableoff = (IRQ_PMU / 32) * 4;
+ writel_relaxed(enablemask, base + GIC_DIST_ENABLE_SET + enableoff);
+
+
+ writel_relaxed(1, base + GIC_DIST_CTRL);
+
+}
+
+static void gic_cpu_init(struct gic_chip_data *gic)
+{
+
+ void __iomem *dist_base = gic_data_dist_base(gic);
+ void __iomem *base = gic_data_cpu_base(gic);
+ int i;
+ u32 enablemask;
+ u32 enableoff;
+ u32 ccpu;
+ u32 cpu = smp_processor_id();
+ u32 cluster = cpu / CORES_PER_CLUSTER;
+ u32 cpumask_8;
+
+ /*
+ * Deal with the banked PPI and SGI interrupts - disable all
+ * PPI interrupts, and also all SGI interrupts (we don't use
+ * SGIs in the Axxia).
+ */
+ writel_relaxed(0xffffffff, dist_base + GIC_DIST_ENABLE_CLEAR);
+
+#ifdef CONFIG_HOTPLUG_CPU
+ if (!cluster_power_up[cluster]) {
+#endif
+ writel_relaxed(0, dist_base + GIC_DIST_CTRL);
+ for (i = IPI0_CPU0; i < MAX_AXM_IPI_NUM; i++) {
+ cpumask_8 = 1 << ((i - IPI0_CPU0) % 4);
+ enablemask = 1 << (i % 32);
+ enableoff = (i / 32) * 4;
+ ccpu = (cluster * 4) + ((i - IPI0_CPU0) % CORES_PER_CLUSTER);
+ if (ccpu == cpu) {
+ writeb_relaxed(cpumask_8, dist_base + GIC_DIST_TARGET + i);
+ writel_relaxed(enablemask, dist_base + GIC_DIST_ENABLE_SET + enableoff);
+ }
+ }
+ writel_relaxed(1, dist_base + GIC_DIST_CTRL);
+#ifdef CONFIG_HOTPLUG_CPU
+ }
+#endif
+
+ writel_relaxed(0xf0, base + GIC_CPU_PRIMASK);
+
+ writel_relaxed(1, base + GIC_CPU_CTRL);
+
+}
+
+void axxia_gic_raise_softirq(const struct cpumask *mask, unsigned int irq)
+{
+ int cpu;
+ unsigned long map = 0;
+ unsigned int regoffset;
+ u32 phys_cpu = cpu_logical_map(smp_processor_id());
+
+ /* Sanity check the physical cpu number */
+ if (phys_cpu >= nr_cpu_ids) {
+ pr_err("Invalid cpu num (%d) >= max (%d)\n",
+ phys_cpu, nr_cpu_ids);
+ return;
+ }
+
+ /* Convert our logical CPU mask into a physical one. */
+ for_each_cpu(cpu, mask)
+ map |= 1 << cpu_logical_map(cpu);
+
+ /*
+ * Convert the standard ARM IPI number (as defined in
+ * arch/arm/kernel/smp.c) to an Axxia IPI interrupt.
+ * The Axxia sends IPI interrupts to other cores via
+ * the use of "IPI send" registers. Each register is
+ * specific to a sending CPU and IPI number. For example:
+ * regoffset 0x0 = CPU0 uses to send IPI0 to other CPUs
+ * regoffset 0x4 = CPU0 uses to send IPI1 to other CPUs
+ * ...
+ * regoffset 0x1000 = CPU1 uses to send IPI0 to other CPUs
+ * regoffset 0x1004 = CPU1 uses to send IPI1 to other CPUs
+ * ...
+ */
+
+ if (phys_cpu < 8)
+ regoffset = phys_cpu * 0x1000;
+ else
+ regoffset = (phys_cpu - 8) * 0x1000 + 0x10000;
+
+ switch (irq) {
+ case 0: /* IPI_WAKEUP */
+ regoffset += 0x8; /* Axxia IPI2 */
+ muxed_ipi_message_pass(mask, MUX_MSG_CPU_WAKEUP);
+ break;
+
+ case 1: /* IPI_TIMER */
+ regoffset += 0x0; /* Axxia IPI0 */
+ break;
+
+ case 2: /* IPI_RESCHEDULE */
+ regoffset += 0x4; /* Axxia IPI1 */
+ break;
+
+ case 3: /* IPI_CALL_FUNC */
+ regoffset += 0x8; /* Axxia IPI2 */
+ muxed_ipi_message_pass(mask, MUX_MSG_CALL_FUNC);
+ break;
+
+ case 4: /* IPI_CALL_FUNC_SINGLE */
+ regoffset += 0x8; /* Axxia IPI2 */
+ muxed_ipi_message_pass(mask, MUX_MSG_CALL_FUNC_SINGLE);
+ break;
+
+ case 5: /* IPI_CPU_STOP */
+ regoffset += 0x8; /* Axxia IPI2 */
+ muxed_ipi_message_pass(mask, MUX_MSG_CPU_STOP);
+ break;
+
+ case 6: /* IPI_IRQ_WORK */
+ regoffset += 0x8; /* Axxia IPI2 */
+ muxed_ipi_message_pass(mask, MUX_MSG_IRQ_WORK);
+ break;
+
+ case 7: /* IPI_COMPLETE */
+ regoffset += 0x8; /* Axxia IPI2 */
+ muxed_ipi_message_pass(mask, MUX_MSG_COMPLETION);
+ break;
+
+ default:
+ /* Unknown ARM IPI */
+ pr_err("Unknown ARM IPI num (%d)!\n", irq);
+ return;
+ }
+
+ /*
+ * Ensure that stores to Normal memory are visible to the
+ * other CPUs before issuing the IPI.
+ */
+ dsb();
+
+ /* Axxia chip uses external SPI interrupts for IPI functionality. */
+ writel_relaxed(map, ipi_send_reg_base + regoffset);
+}
+
+static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hw)
+{
+ if (hw < 32) {
+ irq_set_percpu_devid(irq);
+ irq_set_chip_and_handler(irq, &gic_chip,
+ handle_percpu_devid_irq);
+ set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);
+ } else {
+ irq_set_chip_and_handler(irq, &gic_chip,
+ handle_fasteoi_irq);
+ set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
+ }
+ irq_set_chip_data(irq, d->host_data);
+ return 0;
+}
+
+static int gic_irq_domain_xlate(struct irq_domain *d,
+ struct device_node *controller,
+ const u32 *intspec,
+ unsigned int intsize,
+ unsigned long *out_hwirq,
+ unsigned int *out_type)
+{
+ if (d->of_node != controller)
+ return -EINVAL;
+ if (intsize < 3)
+ return -EINVAL;
+
+ /* Get the interrupt number and add 16 to skip over SGIs */
+ *out_hwirq = intspec[1] + 16;
+
+ /* For SPIs, we need to add 16 more to get the GIC irq ID number */
+ if (!intspec[0])
+ *out_hwirq += 16;
+
+ *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK;
+ return 0;
+}
+
+const struct irq_domain_ops gic_irq_domain_ops = {
+ .map = gic_irq_domain_map,
+ .xlate = gic_irq_domain_xlate,
+};
+
+void __init axxia_gic_init_bases(int irq_start,
+ void __iomem *dist_base,
+ void __iomem *cpu_base,
+ struct device_node *node)
+{
+ irq_hw_number_t hwirq_base;
+ struct gic_chip_data *gic;
+ int gic_irqs, irq_base;
+
+ gic = &gic_data;
+
+ /* Normal, sane GIC... */
+ gic->dist_base.common_base = dist_base;
+ gic->cpu_base.common_base = cpu_base;
+ gic_set_base_accessor(gic, gic_get_common_base);
+
+ /*
+ * For primary GICs, skip over SGIs.
+ * For secondary GICs, skip over PPIs, too.
+ */
+ if ((irq_start & 31) > 0) {
+ hwirq_base = 16;
+ if (irq_start != -1)
+ irq_start = (irq_start & ~31) + 16;
+ } else {
+ hwirq_base = 32;
+ }
+
+ /*
+ * Find out how many interrupts are supported.
+ * The GIC only supports up to 1020 interrupt sources.
+ */
+ gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
+ gic_irqs = (gic_irqs + 1) * 32;
+ if (gic_irqs > MAX_GIC_INTERRUPTS)
+ gic_irqs = MAX_GIC_INTERRUPTS;
+ gic->gic_irqs = gic_irqs;
+
+ gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */
+ irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, numa_node_id());
+ if (IS_ERR_VALUE(irq_base)) {
+ WARN(1,
+ "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
+ irq_start);
+ irq_base = irq_start;
+ }
+ gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
+ hwirq_base, &gic_irq_domain_ops, gic);
+ if (WARN_ON(!gic->domain))
+ return;
+#ifdef CONFIG_SMP
+ set_smp_cross_call(axxia_gic_raise_softirq);
+#endif
+ set_handle_irq(axxia_gic_handle_irq);
+
+ gic_axxia_init(gic);
+ gic_dist_init(gic);
+ gic_cpu_init(gic);
+ gic_pm_init(gic);
+
+ axxia_initialize_queue(&axxia_circ_q);
+
+}
+
+#ifdef CONFIG_SMP
+void axxia_gic_secondary_init(void)
+{
+ struct gic_chip_data *gic = &gic_data;
+
+ gic_dist_init(gic);
+ gic_cpu_init(&gic_data);
+}
+#endif
+
+#ifdef CONFIG_OF
+
+int __init axxia_gic_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ void __iomem *cpu_base;
+ void __iomem *dist_base;
+
+ if (WARN_ON(!node))
+ return -ENODEV;
+
+ dist_base = of_iomap(node, 0);
+ WARN(!dist_base, "unable to map gic dist registers\n");
+
+ cpu_base = of_iomap(node, 1);
+ WARN(!cpu_base, "unable to map gic cpu registers\n");
+
+ ipi_mask_reg_base = of_iomap(node, 4);
+ WARN(!ipi_mask_reg_base, "unable to map Axxia IPI mask registers\n");
+
+ ipi_send_reg_base = of_iomap(node, 5);
+ WARN(!ipi_send_reg_base, "unable to map Axxia IPI send registers\n");
+
+ axxia_gic_init_bases(-1, dist_base, cpu_base, node);
+
+
+
+ return 0;
+}
+#endif
diff --git a/arch/arm/mach-axxia/axxia.c b/arch/arm/mach-axxia/axxia.c
index 19e5a1d..40b4186ca 100644
--- a/arch/arm/mach-axxia/axxia.c
+++ b/arch/arm/mach-axxia/axxia.c
@@ -1,5 +1,7 @@
/*
- * Support for the LSI Axxia SoC devices based on ARM cores.
+ * arch/arm/mach-axxia/axxia.c
+ *
+ * Support for the LSI Axxia boards based on ARM cores.
*
* Copyright (C) 2012 LSI
*
@@ -12,17 +14,240 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+
+#include <linux/amba/bus.h>
+#include <linux/amba/mmci.h>
+#include <linux/amba/pl022.h>
+#include <linux/amba/pl061.h>
+#include <linux/device.h>
+#include <linux/of_address.h>
+#include <linux/of_fdt.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/smsc911x.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#ifdef CONFIG_ARM_ARCH_TIMER
+#include <asm/arch_timer.h>
+#endif
+#include <asm/sizes.h>
+#include <asm/pmu.h>
#include <asm/mach/arch.h>
+#include <asm/mach/map.h>
+#include <asm/kexec.h>
+#include <asm/mach/time.h>
+#include <asm/hardware/cache-l2x0.h>
+#include <mach/hardware.h>
+#include <mach/timers.h>
+#include <mach/axxia-gic.h>
+#include <linux/irqchip/arm-gic.h>
+#include <mach/ncr.h>
+#include "axxia.h"
+#include "pci.h"
+#ifdef CONFIG_AXXIA_RIO
+#include <mach/rio.h>
+#endif
static const char *axxia_dt_match[] __initconst = {
- "lsi,axm5516",
- "lsi,axm5516-sim",
- "lsi,axm5516-emu",
+ "lsi,axm5500",
NULL
};
-DT_MACHINE_START(AXXIA_DT, "LSI Axxia AXM55XX")
- .dt_compat = axxia_dt_match,
+static void __iomem *base;
+
+#ifdef CONFIG_KEXEC
+
+static void __iomem *dickens;
+
+static void set_l3_pstate(u32 newstate)
+{
+ static const u8 hnf[] = {
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27
+ };
+ int i;
+ u32 status;
+
+ for (i = 0; i < ARRAY_SIZE(hnf); ++i)
+ writel(newstate, dickens + (hnf[i] << 16) + 0x10);
+
+ for (i = 0; i < ARRAY_SIZE(hnf); ++i) {
+ int retry;
+
+ for (retry = 10000; retry > 0; --retry) {
+ status = readl(dickens + (hnf[i] << 16) + 0x18);
+ if (((status >> 2) & 3) == newstate)
+ break;
+ udelay(1);
+ }
+ BUG_ON(retry == 0);
+ }
+}
+
+static void
+flush_l3(void)
+{
+ /* Shutdown to flush */
+ set_l3_pstate(0);
+ /* ...and then back up again */
+ set_l3_pstate(3);
+}
+
+#endif
+
+static struct map_desc axxia_static_mappings[] __initdata = {
+#ifdef CONFIG_DEBUG_LL
+ {
+ .virtual = AXXIA_DEBUG_UART_VIRT,
+ .pfn = __phys_to_pfn(AXXIA_DEBUG_UART_PHYS),
+ .length = SZ_4K,
+ .type = MT_DEVICE
+ },
+#endif
+};
+
+void __init axxia_dt_map_io(void)
+{
+ iotable_init(axxia_static_mappings, ARRAY_SIZE(axxia_static_mappings));
+}
+
+void __init axxia_dt_init_early(void)
+{
+ init_dma_coherent_pool_size(SZ_1M);
+}
+
+static struct of_device_id axxia_irq_match[] __initdata = {
+ {
+ .compatible = "arm,cortex-a15-gic",
+ .data = axxia_gic_of_init,
+ },
+ { }
+};
+
+static void __init axxia_dt_init_irq(void)
+{
+ of_irq_init(axxia_irq_match);
+}
+
+void __init axxia_dt_timer_init(void)
+{
+ int is_sim;
+
+ is_sim = of_find_compatible_node(NULL, NULL, "lsi,axm5500-sim") != NULL;
+
+ axxia_init_clocks(is_sim);
+
+ of_clk_init(NULL);
+ clocksource_of_init();
+}
+
+static struct mmci_platform_data mmc_plat_data = {
+ .ocr_mask = MMC_VDD_32_33 | MMC_VDD_33_34,
+ .status = NULL,
+ .gpio_wp = -ENOSYS,
+ .gpio_cd = -ENOSYS
+};
+
+static struct of_dev_auxdata axxia_auxdata_lookup[] __initdata = {
+ OF_DEV_AUXDATA("arm,primecell", 0x20101E0000ULL,
+ "mmci", &mmc_plat_data),
+ {}
+};
+
+static struct resource axxia_pmu_resources[] = {
+ [0] = {
+ .start = IRQ_PMU,
+ .end = IRQ_PMU,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct platform_device pmu_device = {
+ .name = "arm-pmu",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(axxia_pmu_resources),
+ .resource = axxia_pmu_resources,
+};
+
+static int
+axxia_bus_notifier(struct notifier_block *nb, unsigned long event, void *obj)
+{
+ struct device *dev = obj;
+
+ if (event != BUS_NOTIFY_ADD_DEVICE)
+ return NOTIFY_DONE;
+
+ if (!of_property_read_bool(dev->of_node, "dma-coherent"))
+ return NOTIFY_DONE;
+
+ set_dma_ops(dev, &arm_coherent_dma_ops);
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block axxia_platform_nb = {
+ .notifier_call = axxia_bus_notifier,
+};
+
+static struct notifier_block axxia_amba_nb = {
+ .notifier_call = axxia_bus_notifier,
+};
+
+void __init axxia_dt_init(void)
+{
+ base = ioremap(0x2010000000, 0x40000);
+#ifdef CONFIG_KEXEC
+ if (!of_find_compatible_node(NULL, NULL, "lsi,axm5500-sim")) {
+ dickens = ioremap(0x2000000000, SZ_4M);
+ kexec_reinit = flush_l3;
+ }
+#endif
+
+ bus_register_notifier(&platform_bus_type, &axxia_platform_nb);
+ bus_register_notifier(&amba_bustype, &axxia_amba_nb);
+
+ of_platform_populate(NULL, of_default_bus_match_table,
+ axxia_auxdata_lookup, NULL);
+ pm_power_off = NULL; /* TBD */
+
+ ncr_init();
+ axxia_ddr_retention_init();
+
+ axxia_pcie_init();
+
+#ifdef CONFIG_AXXIA_RIO
+ axxia_rapidio_init();
+#endif
+
+ platform_device_register(&pmu_device);
+}
+
+static void axxia_restart(enum reboot_mode reboot, const char *cmd)
+{
+ writel(0x000000ab, base + 0x31000); /* Access Key */
+ writel(0x00000040, base + 0x31004); /* Intrnl Boot, 0xffff0000 Target */
+ writel(0x80000000, base + 0x3180c); /* Set ResetReadDone */
+ writel(0x00080802, base + 0x31008); /* Chip Reset */
+}
+
+DT_MACHINE_START(AXXIA_DT, "LSI Axxia")
+ .dt_compat = axxia_dt_match,
+ .smp = smp_ops(axxia_smp_ops),
+ .map_io = axxia_dt_map_io,
+ .init_early = axxia_dt_init_early,
+ .init_irq = axxia_dt_init_irq,
+ .init_time = axxia_dt_timer_init,
+ .init_machine = axxia_dt_init,
+ .restart = axxia_restart,
+#if defined(CONFIG_ZONE_DMA) && defined(CONFIG_ARM_LPAE)
+ .dma_zone_size = (4ULL * SZ_1G) - 1,
+#endif
MACHINE_END
diff --git a/arch/arm/mach-axxia/axxia.h b/arch/arm/mach-axxia/axxia.h
new file mode 100644
index 0000000..000adc8
--- /dev/null
+++ b/arch/arm/mach-axxia/axxia.h
@@ -0,0 +1,10 @@
+#ifndef _AXXIA_H
+
+void axxia_init_clocks(int is_sim);
+void axxia_ddr_retention_init(void);
+void axxia_platform_cpu_die(unsigned int cpu);
+int axxia_platform_cpu_kill(unsigned int cpu);
+
+extern struct smp_operations axxia_smp_ops;
+
+#endif
diff --git a/arch/arm/mach-axxia/axxia_circular_queue.c b/arch/arm/mach-axxia/axxia_circular_queue.c
new file mode 100644
index 0000000..971aead
--- /dev/null
+++ b/arch/arm/mach-axxia/axxia_circular_queue.c
@@ -0,0 +1,63 @@
+/*
+ * axxia_circular_queue.c
+ *
+ * Created on: Sep 30, 2014
+ * Author: z8cpaul
+ */
+
+
+#include <asm/exception.h>
+#include "axxia_circular_queue.h"
+
+
+void axxia_initialize_queue(struct circular_queue_t *queue)
+{
+ int i;
+
+ queue->valid_items = 0;
+ queue->first = 0;
+ queue->last = 0;
+
+ for (i = 0; i < MAX_ITEMS; i++)
+ queue->data[i] = NULL;
+
+ return;
+}
+
+bool axxia_is_empty(struct circular_queue_t *queue)
+{
+
+ if (queue->valid_items == 0)
+ return true;
+ else
+ return false;
+}
+
+int axxia_put_item(struct circular_queue_t *queue, void *item_value)
+
+{
+ if (queue->valid_items >= MAX_ITEMS) {
+ pr_err("ERROR: queue is full\n");
+ return -EINVAL;
+ } else {
+ queue->valid_items++;
+ queue->data[queue->last] = item_value;
+ queue->last = (queue->last + 1) % MAX_ITEMS;
+ }
+ return 0;
+}
+
+
+int axxia_get_item(struct circular_queue_t *queue, void **item_value)
+{
+
+ if (axxia_is_empty(queue)) {
+ return -1;
+ } else {
+ *item_value = queue->data[queue->first];
+ queue->first = (queue->first + 1) % MAX_ITEMS;
+ queue->valid_items--;
+ }
+ return 0;
+
+}
diff --git a/arch/arm/mach-axxia/axxia_circular_queue.h b/arch/arm/mach-axxia/axxia_circular_queue.h
new file mode 100644
index 0000000..0fe88a0
--- /dev/null
+++ b/arch/arm/mach-axxia/axxia_circular_queue.h
@@ -0,0 +1,30 @@
+/*
+ * axxia_circular_queue.h
+ *
+ * Created on: Sep 30, 2014
+ * Author: z8cpaul
+ */
+
+#ifndef AXXIA_CIRCULAR_QUEUE_H_
+#define AXXIA_CIRCULAR_QUEUE_H_
+
+#define MAX_ITEMS 1020
+
+struct circular_queue_t
+
+{
+ int first;
+ int last;
+ int valid_items;
+ void *data[MAX_ITEMS];
+};
+
+void axxia_initialize_queue(struct circular_queue_t *queue);
+
+bool axxia_is_empty(struct circular_queue_t *queue);
+
+int axxia_put_item(struct circular_queue_t *queue, void *item_value);
+
+int axxia_get_item(struct circular_queue_t *queue, void **item_value);
+
+#endif
diff --git a/arch/arm/mach-axxia/clock.c b/arch/arm/mach-axxia/clock.c
new file mode 100644
index 0000000..6295795
--- /dev/null
+++ b/arch/arm/mach-axxia/clock.c
@@ -0,0 +1,109 @@
+/*
+ * linux/arch/arm/mach-axxia/clock.c
+ *
+ * Copyright (C) 2012 LSI
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/of_fdt.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+
+
+#define clk_register_clkdev(_clk, _conid, _devfmt, ...) \
+ do { \
+ struct clk_lookup *cl; \
+ cl = clkdev_alloc(_clk, _conid, _devfmt, ## __VA_ARGS__); \
+ clkdev_add(cl); \
+ } while (0)
+
+enum clk_ids {
+ clk_cpu,
+ clk_per,
+ clk_mmc,
+ clk_apb,
+ clk_1mhz,
+ NR_CLK_IDS
+};
+
+static struct dt_clk_lookup {
+ const char *path;
+ const char *name;
+ enum clk_ids id;
+ u32 default_freq;
+} dt_clks[] = {
+ {"/clocks/cpu", "clk_cpu", clk_cpu, 1400000000 },
+ {"/clocks/peripheral", "clk_per", clk_per, 200000000 },
+ {"/clocks/emmc", "clk_mmc", clk_mmc, 200000000 },
+};
+
+static struct clk *clk[NR_CLK_IDS];
+
+static void axxia_register_clks(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dt_clks); ++i) {
+ struct dt_clk_lookup *c = &dt_clks[i];
+ struct device_node *np = of_find_node_by_path(c->path);
+ u32 freq;
+
+ if (!np || of_property_read_u32(np, "frequency", &freq)) {
+ pr_warn("axxia: No 'frequency' in %s\n", c->path);
+ freq = c->default_freq;
+ }
+ clk[c->id] = clk_register_fixed_rate(NULL, c->name, NULL,
+ CLK_IS_ROOT, freq);
+ }
+
+ /* APB clock dummy */
+ clk[clk_apb] = clk_register_fixed_rate(NULL, "apb_pclk", NULL,
+ CLK_IS_ROOT, 1000000);
+
+ clk[clk_1mhz] = clk_register_fixed_rate(NULL, "clk_1mhz", NULL,
+ CLK_IS_ROOT, 1000000);
+}
+
+void __init
+axxia_init_clocks(int is_sim)
+{
+ int i;
+
+ pr_info("axxia: init_clocks: is_sim=%d\n", is_sim);
+
+ axxia_register_clks();
+
+ /* PL011 UARTs */
+ clk_register_clkdev(clk[clk_per], NULL, "2010080000.uart");
+ clk_register_clkdev(clk[clk_per], NULL, "2010081000.uart");
+ clk_register_clkdev(clk[clk_per], NULL, "2010082000.uart");
+ clk_register_clkdev(clk[clk_per], NULL, "2010083000.uart");
+
+ /* PL022 SSP */
+ clk_register_clkdev(clk[clk_per], NULL, "2010088000.ssp");
+
+ /* I2C */
+ clk_register_clkdev(clk[clk_per], NULL, "2010084000.i2c");
+ clk_register_clkdev(clk[clk_per], NULL, "2010085000.i2c");
+ clk_register_clkdev(clk[clk_per], NULL, "2010086000.i2c");
+ clk_register_clkdev(clk[clk_per], NULL, "2010087000.i2c");
+
+ /* SP804 timers */
+ clk_register_clkdev(clk[is_sim ? clk_1mhz : clk_per], NULL, "sp804");
+ for (i = 0; i < 8; i++)
+ clk_register_clkdev(clk[is_sim ? clk_1mhz : clk_per],
+ NULL, "axxia-timer%d", i);
+
+ /* PL180 MMCI */
+ clk_register_clkdev(clk[clk_mmc], NULL, "mmci");
+
+ clk_register_clkdev(clk[clk_apb], "apb_pclk", NULL);
+}
diff --git a/arch/arm/mach-axxia/ddr_retention.c b/arch/arm/mach-axxia/ddr_retention.c
new file mode 100644
index 0000000..bc3f79a
--- /dev/null
+++ b/arch/arm/mach-axxia/ddr_retention.c
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2013 LSI Corporation
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/cpu.h>
+#include <linux/reboot.h>
+#include <linux/syscore_ops.h>
+#include <linux/proc_fs.h>
+#include <linux/prefetch.h>
+#include <linux/delay.h>
+
+#include <linux/of.h>
+#include <asm/io.h>
+#include <asm/cacheflush.h>
+#include <mach/ncr.h>
+
+static void __iomem *nca;
+static void __iomem *apb;
+static void __iomem *dickens;
+static int ddr_retention_enabled;
+
+enum {
+ AXXIA_ENGINE_CAAL,
+ AXXIA_ENGINE_CNAL
+};
+
+unsigned long
+ncp_caal_regions_acp55xx[] = {
+ NCP_REGION_ID(0x0b, 0x05), /* SPPV2 */
+ NCP_REGION_ID(0x0c, 0x05), /* SED */
+ NCP_REGION_ID(0x0e, 0x05), /* DPI_HFA */
+ NCP_REGION_ID(0x14, 0x05), /* MTM */
+ NCP_REGION_ID(0x14, 0x0a), /* MTM2 */
+ NCP_REGION_ID(0x15, 0x00), /* MME */
+ NCP_REGION_ID(0x16, 0x05), /* NCAV2 */
+ NCP_REGION_ID(0x16, 0x10), /* NCAV22 */
+ NCP_REGION_ID(0x17, 0x05), /* EIOAM1 */
+ NCP_REGION_ID(0x19, 0x05), /* TMGR */
+ NCP_REGION_ID(0x1a, 0x05), /* MPPY */
+ NCP_REGION_ID(0x1a, 0x23), /* MPPY2 */
+ NCP_REGION_ID(0x1a, 0x21), /* MPPY3 */
+ NCP_REGION_ID(0x1b, 0x05), /* PIC */
+ NCP_REGION_ID(0x1c, 0x05), /* PAB */
+ NCP_REGION_ID(0x1f, 0x05), /* EIOAM0 */
+ NCP_REGION_ID(0x31, 0x05), /* ISB */
+ NCP_REGION_ID(0xff, 0xff)
+};
+
+unsigned long
+ncp_cnal_regions_acp55xx[] = {
+ NCP_REGION_ID(0x28, 0x05), /* EIOASM0 */
+ NCP_REGION_ID(0x29, 0x05), /* EIOASM1 */
+ NCP_REGION_ID(0x2a, 0x05), /* EIOAS2 */
+ NCP_REGION_ID(0x2b, 0x05), /* EIOAS3 */
+ NCP_REGION_ID(0x2c, 0x05), /* EIOAS4 */
+ NCP_REGION_ID(0x2d, 0x05), /* EIOAS5 */
+ NCP_REGION_ID(0x32, 0x05), /* ISBS */
+ NCP_REGION_ID(0xff, 0xff)
+};
+
+
+/*
+ ------------------------------------------------------------------------------
+ flush_l3
+
+ This is NOT a general function to flush the L3 cache. There are a number of
+ assumptions that are not usually true...
+
+ 1) All other cores are " quiesced".
+ 2) There is no need to worry about preemption or interrupts.
+*/
+
+static void
+flush_l3(void)
+{
+
+ unsigned long hnf_offsets[] = {
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27
+ };
+
+ int i;
+ unsigned long status;
+ int retries;
+
+ for (i = 0; i < (sizeof(hnf_offsets) / sizeof(unsigned long)); ++i)
+ writel(0x0, dickens + (0x10000 * hnf_offsets[i]) + 0x10);
+
+ for (i = 0; i < (sizeof(hnf_offsets) / sizeof(unsigned long)); ++i) {
+ retries = 10000;
+
+ do {
+ status = readl(dickens +
+ (0x10000 * hnf_offsets[i]) + 0x18);
+ udelay(1);
+ } while ((0 < --retries) && (0x0 != (status & 0xf)));
+
+ if (0 == retries)
+ BUG();
+ }
+
+ for (i = 0; i < (sizeof(hnf_offsets) / sizeof(unsigned long)); ++i)
+ writel(0x3, dickens + (0x10000 * hnf_offsets[i]) + 0x10);
+
+ for (i = 0; i < (sizeof(hnf_offsets) / sizeof(unsigned long)); ++i) {
+ retries = 10000;
+
+ do {
+ status = readl(dickens +
+ (0x10000 * hnf_offsets[i]) + 0x18);
+ udelay(1);
+ } while ((0 < --retries) && (0xc != (status & 0xf)));
+
+ if (0 == retries)
+ BUG();
+ }
+
+ asm volatile ("dsb" : : : "memory");
+
+}
+
+static void
+quiesce_vp_engine(int engineType)
+{
+ unsigned long *pEngineRegions;
+ unsigned long ortOff, owtOff;
+ unsigned long *pRegion;
+ unsigned ort, owt;
+ unsigned long buf = 0;
+ unsigned short node, target;
+ int loop;
+
+ pr_info("quiescing VP engines...\n");
+
+ switch (engineType) {
+ case AXXIA_ENGINE_CNAL:
+ pEngineRegions = ncp_cnal_regions_acp55xx;
+ ortOff = 0x1c0;
+ owtOff = 0x1c4;
+ break;
+
+ case AXXIA_ENGINE_CAAL:
+ pEngineRegions = ncp_caal_regions_acp55xx;
+ ortOff = 0xf8;
+ owtOff = 0xfc;
+ break;
+
+ default:
+ return;
+ }
+
+ pRegion = pEngineRegions;
+
+ while (*pRegion != NCP_REGION_ID(0xff, 0xff)) {
+ /* set read/write transaction limits to zero */
+ ncr_write(*pRegion, 0x8, 4, &buf);
+ ncr_write(*pRegion, 0xc, 4, &buf);
+ pRegion++;
+ }
+
+ pRegion = pEngineRegions;
+ loop = 0;
+
+ while (*pRegion != NCP_REGION_ID(0xff, 0xff)) {
+ node = (*pRegion & 0xffff0000) >> 16;
+ target = *pRegion & 0x0000ffff;
+ /* read the number of outstanding read/write transactions */
+ ncr_read(*pRegion, ortOff, 4, &ort);
+ ncr_read(*pRegion, owtOff, 4, &owt);
+
+ if ((ort == 0) && (owt == 0)) {
+ /* this engine has been quiesced, move on to the next */
+ pr_info("quiesced region 0x%02x.0x%02x\n",
+ node, target);
+ pRegion++;
+ } else {
+ if (loop++ > 10000) {
+ pr_info(
+ "Unable to quiesce region 0x%02x.0x%02x ort=0x%x, owt=0x%x\n",
+ node, target, ort, owt);
+ pRegion++;
+ loop = 0;
+ continue;
+ }
+ }
+ }
+}
+
+static inline void cpu_disable_l2_prefetch(void)
+{
+ unsigned int v;
+
+ /*
+ * MRC p15, 1, <Rt>, c15, c0, 3; Read L2 Prefetch Control Register
+ * MCR p15, 1, <Rt>, c15, c0, 3; Write L2 Prefetch Control Register
+ *
+ */
+ asm volatile(
+ " mrc p15, 1, %0, c15, c0, 3\n"
+ " and %0, %0, #0x0000\n"
+ " mcr p15, 1, %0, c15, c0, 3\n"
+ : "=&r" (v)
+ :
+ : "cc");
+
+ isb();
+}
+
+static inline void
+reset_elm_trace(void)
+{
+ /* reset and disable ELM trace */
+ ncr_register_write(htonl(0x000fff04), (unsigned *) (apb + 0x68000));
+ ncr_register_write(htonl(0x000fff04), (unsigned *) (apb + 0x78000));
+
+ /* reset ELM statistics */
+ ncr_register_write(htonl(0x00001), (unsigned *) (apb + 0x60230));
+ ncr_register_write(htonl(0x00001), (unsigned *) (apb + 0x70230));
+
+ /* enable ELM trace */
+ ncr_register_write(htonl(0x000fff01), (unsigned *) (apb + 0x68000));
+ ncr_register_write(htonl(0x000fff01), (unsigned *) (apb + 0x78000));
+}
+
+
+extern void ncp_ddr_shutdown(void *, void *, unsigned long);
+
+
+void
+initiate_retention_reset(void)
+{
+ unsigned long ctl_244 = 0;
+ unsigned long value;
+ unsigned cpu_id;
+
+ volatile long tmp;
+ volatile long *ptmp;
+
+ if (0 == ddr_retention_enabled) {
+ pr_info("DDR Retention Reset is Not Enabled\n");
+ return;
+ }
+
+ if (NULL == nca || NULL == apb || NULL == dickens)
+ BUG();
+
+ preempt_disable();
+ cpu_id = smp_processor_id();
+
+ /* send stop message to other CPUs */
+ local_irq_disable();
+ local_fiq_disable();
+ asm volatile ("dsb" : : : "memory");
+ asm volatile ("dmb" : : : "memory");
+ system_state = SYSTEM_RESTART;
+ smp_send_stop();
+ udelay(1000);
+
+ flush_cache_all();
+ flush_l3();
+
+ /* TODO - quiesce VP engines */
+ quiesce_vp_engine(AXXIA_ENGINE_CAAL);
+ quiesce_vp_engine(AXXIA_ENGINE_CNAL);
+
+
+ /* unlock reset register for later */
+ writel(0x000000ab, apb + 0x31000); /* Access Key */
+
+ /* prepare to put DDR in self refresh power-down mode */
+ /* first read the CTL_244 register and OR in the LP_CMD value */
+ ncr_read(NCP_REGION_ID(34, 0), 0x3d0, 4, &ctl_244);
+ ctl_244 |= 0x000a0000;
+
+ /* belts & braces: put secondary CPUs into reset */
+ value = ~(1 << cpu_id);
+ value &= 0xffff;
+ ncr_register_write(htonl(value), (unsigned *) (apb + 0x31030));
+
+ /* load entire ddr_shutdown function into L2 cache */
+ ptmp = (long *) ncp_ddr_shutdown;
+ do {
+ tmp += *ptmp++;
+ } while (ptmp < (long *) (ncp_ddr_shutdown + 0x1000));
+
+ asm volatile ("isb" : : : "memory");
+
+ /* disable L2 prefetching */
+ cpu_disable_l2_prefetch();
+
+ /* reset ELM DDR access trace buffer */
+ reset_elm_trace();
+
+ /* call cache resident ddr shutdown function */
+ ncp_ddr_shutdown(nca, apb, ctl_244);
+}
+EXPORT_SYMBOL(initiate_retention_reset);
+
+static ssize_t
+axxia_ddr_retention_trigger(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ initiate_retention_reset();
+ return 0;
+}
+
+static const struct file_operations axxia_ddr_retention_proc_ops = {
+ .write = axxia_ddr_retention_trigger,
+ .llseek = noop_llseek,
+};
+
+void
+axxia_ddr_retention_init(void)
+{
+ /*
+ * Only available on ASIC systems.
+ */
+
+ if (of_find_compatible_node(NULL, NULL, "lsi,axm5500-amarillo")) {
+ /* Create /proc entry. */
+ if (!proc_create("driver/axxia_ddr_retention_reset",
+ S_IWUSR, NULL, &axxia_ddr_retention_proc_ops)) {
+ pr_info("Failed to register DDR retention proc entry\n");
+ } else {
+ apb = ioremap(0x2010000000, 0x80000);
+ nca = ioremap(0x002020100000ULL, 0x20000);
+ dickens = ioremap(0x2000000000, 0x1000000);
+ ddr_retention_enabled = 1;
+ pr_info("DDR Retention Reset Initialized\n");
+ }
+ } else {
+ pr_info("DDR Retention Reset is Not Available\n");
+ }
+}
diff --git a/arch/arm/mach-axxia/ddr_shutdown.c b/arch/arm/mach-axxia/ddr_shutdown.c
new file mode 100644
index 0000000..5f27f2e
--- /dev/null
+++ b/arch/arm/mach-axxia/ddr_shutdown.c
@@ -0,0 +1,330 @@
+
+#include <asm/io.h>
+
+/*
+ * private copies of the ioread/write macros
+ * These are defined with a different barrier
+ * to avoid the outer_sync() call that's part
+ * of the normal barrier.
+ */
+#define pvt_ioread32be(p) ({ unsigned int __v = be32_to_cpu((__force __be32)__raw_readl(p)); dsb(); __v; })
+#define pvt_iowrite32be(v, p) ({ dsb(); __raw_writel((__force __u32)cpu_to_be32(v), p); })
+
+#define pvt_ioread32(p) ({ unsigned int __v = (__raw_readl(p)); dsb(); __v; })
+#define pvt_iowrite32(v, p) ({ dsb(); __raw_writel((__force __u32)(v), p); })
+
+/* #define DDR_SHUTDOWN_DEBUG */
+#ifdef DDR_SHUTDOWN_DEBUG
+#define dbg_write(v, p) pvt_iowrite32be(v, p)
+#else
+#define dbg_write(v, p)
+#endif
+
+/*
+ * Wait For Completion timeout
+ * how many loops to wait for the config ring access to complete
+ */
+#define WFC_TIMEOUT (400000)
+
+/*
+ * DDR status timeout
+ * how many times we read the DDR status waiting for self refresh complete
+ */
+#define DDR_TIMEOUT (1000)
+
+void ncp_ddr_shutdown(void *nca, void *apb, unsigned long ctl_244)
+{
+ unsigned long value;
+ int two_elms = 0;
+ int wfc_loop = 0;
+ int ddr_loop = 0;
+
+ /* determine if we are in one or two ELM/SMEM mode */
+ value = pvt_ioread32(apb + 0x60004);
+ two_elms = (value & 0x00000200);
+
+ /*
+ * Issue command to put SMEM0 into self-refresh mode
+ *
+ * ncpWrite 0x22.0.0x3d0
+ */
+ dbg_write(0xaaaa0001, (unsigned *)(nca + 0x1200));
+
+ /* write register value into CDAR[0] */
+ pvt_iowrite32be(ctl_244, (unsigned *)(nca + 0x1000));
+ /* CDR2 - Node.target */
+ pvt_iowrite32be(0x00002200, (unsigned *)(nca + 0xf8));
+ /* CDR1 - word offset 0xf4 (byte offset 0x3d0) */
+ pvt_iowrite32be(0x000000f4, (unsigned *)(nca + 0xf4));
+ /* CDR0 - write command */
+ pvt_iowrite32be(0x80050003, (unsigned *)(nca + 0xf0));
+ wfc_loop = 0;
+ do {
+ if (wfc_loop++ > WFC_TIMEOUT) {
+ dbg_write(value, (unsigned *)(nca + 0x11fc));
+ dbg_write(0xffff0001, (unsigned *)(nca + 0x1200));
+ goto do_reset;
+ }
+ dbg_write(wfc_loop, (unsigned *)(nca + 0x11f8));
+ value = pvt_ioread32be((unsigned *)
+ (nca + 0xf0));
+ } while ((0x80000000UL & value));
+ dbg_write(0xaaaa0002, (unsigned *)(nca + 0x1200));
+
+ if (two_elms) {
+ /*
+ * Issue command to put SMEM1 into self-refresh mode
+ *
+ * ncpWrite 0x0f.0.0x3d0
+ */
+ /* CDR2 - Node.target */
+ pvt_iowrite32be(0x00000f00, (unsigned *)(nca + 0xf8));
+ /* CDR0 - write command */
+ pvt_iowrite32be(0x80050003, (unsigned *)(nca + 0xf0));
+ wfc_loop = 0;
+ do {
+ if (wfc_loop++ > WFC_TIMEOUT) {
+ dbg_write(value, (unsigned *)(nca + 0x11fc));
+ dbg_write(0xffff0002,
+ (unsigned *)(nca + 0x1200));
+ goto do_reset;
+ }
+ value = pvt_ioread32be((unsigned *)
+ (nca + 0xf0));
+ } while ((0x80000000UL & value));
+ }
+
+ dbg_write(0xaaaa0003, (unsigned *)(nca + 0x1200));
+
+ /*
+ * Poll for SMEM0 refresh-mode command completion
+ */
+ /* CDR1 - word offset 0x104 (byte offset 0x410) */
+ pvt_iowrite32be(0x00000104, (unsigned *)(nca + 0xf4));
+ /* CDR2 - Node.target */
+ pvt_iowrite32be(0x00002200, (unsigned *)(nca + 0xf8));
+ ddr_loop = 0;
+ do {
+ if (ddr_loop++ > DDR_TIMEOUT) {
+ dbg_write(value, (unsigned *)(nca + 0x11fc));
+ dbg_write(0xffff0003, (unsigned *)(nca + 0x1200));
+ goto do_reset;
+ }
+ pvt_iowrite32be(wfc_loop, (unsigned *)
+ (nca + 0x11f0));
+
+ /* issue config ring read */
+ pvt_iowrite32be(0x80040003, (unsigned *)
+ (nca + 0xf0));
+ wfc_loop = 0;
+ do {
+ if (wfc_loop++ > WFC_TIMEOUT) {
+ dbg_write(value, (unsigned *)(nca + 0x11fc));
+ dbg_write(0xffff0004,
+ (unsigned *)(nca + 0x1200));
+ goto do_reset;
+ }
+ value = pvt_ioread32be((unsigned *)
+ (nca + 0xf0));
+ } while ((0x80000000UL & value));
+
+ value = pvt_ioread32be((unsigned *)
+ (nca + 0x1000));
+
+ } while ((value & 0x0200) == 0);
+ dbg_write(0xaaaa0004, (unsigned *)(nca + 0x1200));
+
+ if (two_elms) {
+ /*
+ * Poll for SMEM1 refresh-mode command completion
+ */
+ /* CDR2 - Node.target */
+ pvt_iowrite32be(0x00000f00, (unsigned *)(nca + 0xf8));
+ ddr_loop = 0;
+ do {
+ if (ddr_loop++ > DDR_TIMEOUT) {
+ dbg_write(value, (unsigned *)(nca + 0x11fc));
+ dbg_write(0xffff0005,
+ (unsigned *)(nca + 0x1200));
+ goto do_reset;
+ }
+
+ /* issue config ring read */
+ pvt_iowrite32be(0x80040003, (unsigned *)(nca + 0xf0));
+ wfc_loop = 0;
+ do {
+ if (wfc_loop++ > WFC_TIMEOUT) {
+ dbg_write(value,
+ (unsigned *)(nca + 0x11fc));
+ dbg_write(0xffff0006,
+ (unsigned *)(nca + 0x1200));
+ goto do_reset;
+ }
+ value =
+ pvt_ioread32be((unsigned *)(nca + 0xf0));
+ } while ((0x80000000UL & value));
+
+ value = pvt_ioread32be((unsigned *)
+ (nca + 0x1000));
+ wfc_loop++;
+ } while ((value & 0x0200) == 0);
+ }
+
+ dbg_write(0xaaaa0005, (unsigned *)(nca + 0x1200));
+
+ /*
+ * Tell U-Boot to do a DDR retention-reset
+ * (i.e. set bit 0 of persist_scratch register)
+ */
+ pvt_iowrite32(0x00000001, apb + 0x300dc);
+
+ dbg_write(0xaaaa0006, (unsigned *)(nca + 0x1200));
+do_reset:
+ /*
+ * Issue Chip reset
+ */
+ /* Intrnl Boot, 0xffff0000 Target */
+ pvt_iowrite32(0x00000040, apb + 0x31004);
+ /* Set ResetReadDone */
+ pvt_iowrite32(0x80000000, apb + 0x3180c);
+ /* Chip Reset */
+ pvt_iowrite32(0x00080802, apb + 0x31008);
+
+ while (1)
+ ;
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+}
+
+void ncp_ddr_shutdown_dummy(void)
+{
+ wfi();
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+ __asm__ __volatile__("nop\n\t");
+}
diff --git a/arch/arm/mach-axxia/headsmp.S b/arch/arm/mach-axxia/headsmp.S
new file mode 100644
index 0000000..b4fe409
--- /dev/null
+++ b/arch/arm/mach-axxia/headsmp.S
@@ -0,0 +1,71 @@
+/*
+ * linux/arch/arm/mach-axxia/headsmp.S
+ *
+ * Cloned from linux/arch/arm/mach-realview/headsmp.S
+ *
+ * Copyright (c) 2003 ARM Limited
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/linkage.h>
+#include <linux/init.h>
+
+ __CPUINIT
+
+/*
+ * Axxia specific entry point for secondary CPUs. This provides
+ * a "holding pen" into which all secondary cores are held until we're
+ * ready for them to initialise.
+ */
+ENTRY(axxia_secondary_startup)
+ mrc p15, 0, r0, c0, c0, 5
+ bic r0, #0xff000000
+ adr r4, 1f
+ ldmia r4, {r5, r6}
+ sub r4, r4, r5
+ add r6, r6, r4
+pen: ldr r7, [r6]
+ /*
+ * It seems that just looping over read/compare kills starves
+ * ethernet, this will happen if we start the kernel with
+ * maxcpus=X where X < 16. Which we really want in order to
+ * isolate cores.
+ *
+ * FIXME: We should really use wfi or wfe here
+ */
+ mov r4, #0x7000
+__delay:
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ nop
+ subs r4,r4,#1
+ bne __delay
+ cmp r7, r0
+ bne pen
+
+ /*
+ * We've been released from the holding pen: secondary_stack
+ * should now contain the SVC stack for this core
+ */
+ b secondary_startup
+ENDPROC(axxia_secondary_startup)
+
+ .align 2
+1: .long .
+ .long pen_release
diff --git a/arch/arm/mach-axxia/include/mach/axxia-gic.h b/arch/arm/mach-axxia/include/mach/axxia-gic.h
new file mode 100644
index 0000000..b9e5574
--- /dev/null
+++ b/arch/arm/mach-axxia/include/mach/axxia-gic.h
@@ -0,0 +1,17 @@
+/*
+ * arch/arm/mach-axxia/axxia-gic.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef __AXXIA_GIC_H
+#define __AXXIA_GIC_H
+
+void axxia_gic_raise_softirq(const struct cpumask *mask, unsigned int irq);
+void axxia_gic_secondary_init(void);
+int __init axxia_gic_of_init(struct device_node *node,
+ struct device_node *parent);
+void axxia_gic_dump_mask(char *tmp, const struct cpumask *mask);
+void axxia_gic_kill_cpu(u32 rcpu);
+#endif
diff --git a/arch/arm/mach-axxia/include/mach/debug-macro.S b/arch/arm/mach-axxia/include/mach/debug-macro.S
new file mode 100644
index 0000000..1a28f4a
--- /dev/null
+++ b/arch/arm/mach-axxia/include/mach/debug-macro.S
@@ -0,0 +1,21 @@
+/* arch/arm/mach-axxia/include/mach/debug-macro.S
+ *
+ * Debugging macro include header
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <mach/hardware.h>
+
+#if !defined(CONFIG_ARM_LPAE)
+#error "Axxia Peripherals Are Only Accessible Using the LPAE!"
+#endif
+
+ .macro addruart, rp, rv, tmp
+ ldr \rp, =(AXXIA_DEBUG_UART_PHYS & 0xffffffff)
+ ldr \rv, =AXXIA_DEBUG_UART_VIRT
+ .endm
+
+#include <asm/hardware/debug-pl01x.S>
diff --git a/arch/arm/mach-axxia/include/mach/entry-macro.S b/arch/arm/mach-axxia/include/mach/entry-macro.S
new file mode 100644
index 0000000..a14f9e6
--- /dev/null
+++ b/arch/arm/mach-axxia/include/mach/entry-macro.S
@@ -0,0 +1,5 @@
+ .macro disable_fiq
+ .endm
+
+ .macro arch_ret_to_user, tmp1, tmp2
+ .endm
diff --git a/arch/arm/mach-axxia/include/mach/gpio.h b/arch/arm/mach-axxia/include/mach/gpio.h
new file mode 100644
index 0000000..40a8c17
--- /dev/null
+++ b/arch/arm/mach-axxia/include/mach/gpio.h
@@ -0,0 +1 @@
+/* empty */
diff --git a/arch/arm/mach-axxia/include/mach/hardware.h b/arch/arm/mach-axxia/include/mach/hardware.h
new file mode 100644
index 0000000..2f3686e
--- /dev/null
+++ b/arch/arm/mach-axxia/include/mach/hardware.h
@@ -0,0 +1,24 @@
+/*
+ * arch/arm/mach-axxia/include/mach/hardware.h
+ *
+ * Copyright (c) 2013 LSI Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+#ifndef __MACH_HARDWARE_H
+#define __MACH_HARDWARE_H
+
+#define AXXIA_UART0_PHYS 0x2010080000
+#define AXXIA_UART1_PHYS 0x2010081000
+#define AXXIA_UART2_PHYS 0x2010082000
+#define AXXIA_UART3_PHYS 0x2010083000
+
+#ifdef CONFIG_DEBUG_LL_AXXIA_UART0
+#define AXXIA_DEBUG_UART_VIRT 0xf0080000
+#define AXXIA_DEBUG_UART_PHYS AXXIA_UART0_PHYS
+#endif
+
+#endif
diff --git a/arch/arm/mach-axxia/include/mach/io.h b/arch/arm/mach-axxia/include/mach/io.h
new file mode 100644
index 0000000..a4eddd9
--- /dev/null
+++ b/arch/arm/mach-axxia/include/mach/io.h
@@ -0,0 +1,39 @@
+/*
+ * arch/arm/mach-vexpress/include/mach/io.h
+ *
+ * Copyright (C) 2003 ARM Limited
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __ASM_ARM_ARCH_IO_H
+#define __ASM_ARM_ARCH_IO_H
+
+#include <linux/types.h>
+
+#define __io(a) __typesafe_io(a)
+#define __mem_pci(a) (a)
+
+/*
+ Make the first argument to ioremap() be phys_addr_t (64 bits in this
+ case) instead of unsigned long. When __arch_ioremap is defiend,
+ __arch_iounmap must be defined also. Just use the default for
+ iounmap().
+*/
+
+void __iomem *__axxia_arch_ioremap(phys_addr_t, size_t, unsigned int);
+#define __arch_ioremap __axxia_arch_ioremap
+#define __arch_iounmap __arm_iounmap
+
+#endif
diff --git a/arch/arm/mach-axxia/include/mach/irqs.h b/arch/arm/mach-axxia/include/mach/irqs.h
new file mode 100644
index 0000000..ebff99a
--- /dev/null
+++ b/arch/arm/mach-axxia/include/mach/irqs.h
@@ -0,0 +1,5 @@
+#define IRQ_LOCALTIMER 29
+#define IRQ_LOCALWDOG 30
+#define IRQ_PMU 222
+#define AXXIA_MSI_FIRST 224
+#define NR_IRQS 256
diff --git a/arch/arm/mach-axxia/include/mach/ncr.h b/arch/arm/mach-axxia/include/mach/ncr.h
new file mode 100644
index 0000000..926d366
--- /dev/null
+++ b/arch/arm/mach-axxia/include/mach/ncr.h
@@ -0,0 +1,44 @@
+/*
+ * arch/arm/mach-axxia/include/mach/ncr.h
+ *
+ * Copyright (C) 2010 LSI
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __ARCH_ARM_MACH_AXXIA_NCR_H
+#define __ARCH_ARM_MACH_AXXIA_NCR_H
+
+#ifndef NCP_REGION_ID
+#define NCP_REGION_ID(node, target) \
+((unsigned long) ((((node) & 0xffff) << 16) | ((target) & 0xffff)))
+#endif
+
+#ifndef NCP_NODE_ID
+#define NCP_NODE_ID(region) (((region) >> 16) & 0xffff)
+#endif
+
+#ifndef NCP_TARGET_ID
+#define NCP_TARGET_ID(region) ((region) & 0xffff)
+#endif
+
+unsigned long ncr_register_read(unsigned *);
+void ncr_register_write(const unsigned, unsigned *);
+int ncr_read(unsigned long, unsigned long, int, void *);
+int ncr_write(unsigned long, unsigned long, int, void *);
+int ncr_init(void);
+void ncr_exit(void);
+
+#endif /* __ARCH_ARM_MACH_AXXIA_NCR_H */
diff --git a/arch/arm/mach-axxia/include/mach/pci.h b/arch/arm/mach-axxia/include/mach/pci.h
new file mode 100644
index 0000000..3260654
--- /dev/null
+++ b/arch/arm/mach-axxia/include/mach/pci.h
@@ -0,0 +1,6 @@
+#ifndef _AXXIA_PCI_H
+#define _AXXIA_PCI_H
+
+void __init axxia_pcie_init(void);
+
+#endif
diff --git a/arch/arm/mach-axxia/include/mach/rio.h b/arch/arm/mach-axxia/include/mach/rio.h
new file mode 100644
index 0000000..a004480
--- /dev/null
+++ b/arch/arm/mach-axxia/include/mach/rio.h
@@ -0,0 +1,44 @@
+/*
+ * Helper module for board specific RAPIDIO bus registration
+ *
+ * Copyright (C) 2014 LSI Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+#ifndef __ASM__ARCH_AXXIA_RAPIDIO_H
+#define __ASM__ARCH_AXXIA_RAPIDIO_H
+#include <linux/io.h>
+
+#define IN_SRIO8(a, v, ec) do { \
+ v = ioread8(a); ec = 0; \
+ } while (0)
+#define IN_SRIO16(a, v, ec) do { \
+ v = ioread16be(a); ec = 0; \
+ } while (0)
+#define IN_SRIO32(a, v, ec) do { \
+ v = ioread32be(a); ec = 0; \
+ } while (0)
+
+#define OUT_SRIO8(a, v) iowrite8(v, a)
+#define OUT_SRIO16(a, v) iowrite16be(v, a)
+#define OUT_SRIO32(a, v) iowrite32be(v, a)
+
+int axxia_rapidio_board_init(struct platform_device *dev, int devnum,
+ int *portndx);
+
+int axxia_rapidio_init(void);
+
+#endif /* __ASM__ARCH_AXXIA_RAPIDIO_H */
diff --git a/arch/arm/mach-axxia/include/mach/system.h b/arch/arm/mach-axxia/include/mach/system.h
new file mode 100644
index 0000000..f653a8e
--- /dev/null
+++ b/arch/arm/mach-axxia/include/mach/system.h
@@ -0,0 +1,33 @@
+/*
+ * arch/arm/mach-vexpress/include/mach/system.h
+ *
+ * Copyright (C) 2003 ARM Limited
+ * Copyright (C) 2000 Deep Blue Solutions Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __ASM_ARCH_SYSTEM_H
+#define __ASM_ARCH_SYSTEM_H
+
+static inline void arch_idle(void)
+{
+ /*
+ * This should do all the clock switching
+ * and wait for interrupt tricks
+ */
+ cpu_do_idle();
+}
+
+#endif
diff --git a/arch/arm/mach-axxia/include/mach/timers.h b/arch/arm/mach-axxia/include/mach/timers.h
new file mode 100644
index 0000000..8aa49c9
--- /dev/null
+++ b/arch/arm/mach-axxia/include/mach/timers.h
@@ -0,0 +1,39 @@
+/*
+ * arch/arm/mach-axxia/include/mach/timers.h
+ *
+ * Copyright (C) 2012 LSI
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ This is based on arch/arm/include/asm/hardware/timer-sp.h
+
+ See arch/arm/mach-axxia/timers.c for details.
+ */
+
+#include <asm/hardware/timer-sp.h>
+
+#define AXXIA_TIMER_1_BASE 0x00
+#define AXXIA_TIMER_2_BASE 0x20
+#define AXXIA_TIMER_3_BASE 0x40
+#define AXXIA_TIMER_4_BASE 0x60
+#define AXXIA_TIMER_5_BASE 0x80
+#define AXXIA_TIMER_6_BASE 0xa0
+#define AXXIA_TIMER_7_BASE 0xc0
+#define AXXIA_TIMER_8_BASE 0xe0
+
+void axxia_timer_clocksource_init(void __iomem *, const char *);
+void axxia_timer_clockevents_init(void __iomem *, unsigned int, const char *);
diff --git a/arch/arm/mach-axxia/include/mach/timex.h b/arch/arm/mach-axxia/include/mach/timex.h
new file mode 100644
index 0000000..00029ba
--- /dev/null
+++ b/arch/arm/mach-axxia/include/mach/timex.h
@@ -0,0 +1,23 @@
+/*
+ * arch/arm/mach-vexpress/include/mach/timex.h
+ *
+ * RealView architecture timex specifications
+ *
+ * Copyright (C) 2003 ARM Limited
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#define CLOCK_TICK_RATE (50000000 / 16)
diff --git a/arch/arm/mach-axxia/include/mach/uncompress.h b/arch/arm/mach-axxia/include/mach/uncompress.h
new file mode 100644
index 0000000..a386048
--- /dev/null
+++ b/arch/arm/mach-axxia/include/mach/uncompress.h
@@ -0,0 +1,65 @@
+/*
+ * arch/arm/mach-vexpress/include/mach/uncompress.h
+ *
+ * Copyright (C) 2003 ARM Limited
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#define AMBA_UART_DR(base) (*(volatile unsigned char *)((base) + 0x00))
+#define AMBA_UART_LCRH(base) (*(volatile unsigned char *)((base) + 0x2c))
+#define AMBA_UART_CR(base) (*(volatile unsigned char *)((base) + 0x30))
+#define AMBA_UART_FR(base) (*(volatile unsigned char *)((base) + 0x18))
+
+
+#if defined(CONFIG_DEBUG_VEXPRESS_CA9X4_UART)
+#define get_uart_base() (0x10000000 + 0x00009000)
+#elif defined(CONFIG_DEBUG_VEXPRESS_RS1_UART)
+#define get_uart_base() (0x1c000000 + 0x00090000)
+#else
+#define get_uart_base() (0UL)
+#endif
+
+/*
+ * This does not append a newline
+ */
+static inline void putc(int c)
+{
+ unsigned long base = get_uart_base();
+
+ if (!base)
+ return;
+
+ while (AMBA_UART_FR(base) & (1 << 5))
+ barrier();
+
+ AMBA_UART_DR(base) = c;
+}
+
+static inline void flush(void)
+{
+ unsigned long base = get_uart_base();
+
+ if (!base)
+ return;
+
+ while (AMBA_UART_FR(base) & (1 << 3))
+ barrier();
+}
+
+/*
+ * nothing to do
+ */
+#define arch_decomp_setup()
+#define arch_decomp_wdog()
diff --git a/arch/arm/mach-axxia/io.c b/arch/arm/mach-axxia/io.c
new file mode 100644
index 0000000..bf473f9
--- /dev/null
+++ b/arch/arm/mach-axxia/io.c
@@ -0,0 +1,40 @@
+/*
+ * arch/arm/mach-axxia/io.c
+ *
+ * Support for the LSI Axxia boards based on ARM cores.
+ *
+ * Copyright (C) 2012 LSI
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <asm/page.h>
+#include <asm/io.h>
+
+void __iomem *
+__axxia_arch_ioremap(phys_addr_t physical_address, size_t size,
+ unsigned int flags)
+{
+ unsigned long pfn;
+ unsigned long offset;
+
+ pfn = (unsigned long)((physical_address >> PAGE_SHIFT) & 0xffffffffULL);
+ offset = (unsigned long)(physical_address & (PAGE_SIZE - 1));
+
+ return __arm_ioremap_pfn(pfn, offset, size, flags);
+}
+EXPORT_SYMBOL(__axxia_arch_ioremap);
diff --git a/arch/arm/mach-axxia/pci.c b/arch/arm/mach-axxia/pci.c
new file mode 100644
index 0000000..08978ec
--- /dev/null
+++ b/arch/arm/mach-axxia/pci.c
@@ -0,0 +1,1134 @@
+/*
+ * arch/arm/mach-axxia/pci.c
+ *
+ * PCIe support for AXM55xx.
+ *
+ * Copyright (C) 2013 LSI
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/of_irq.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/msi.h>
+#include <linux/kernel_stat.h>
+#include <asm/sizes.h>
+#include <asm/mach/pci.h>
+#include <asm/irq.h>
+#include <asm/mach/irq.h>
+#include <asm-generic/errno-base.h>
+#include <mach/pci.h>
+
+
+#define PCIE_CONFIG (0x1000)
+#define PCIE_STATUS (0x1004)
+#define PCIE_CORE_DEBUG (0x1008)
+#define PCIE_LOOPBACK_FAIL (0x100C)
+#define PCIE_MPAGE_U(n) (0x1010 + (n * 8)) /* n = 0..7 */
+#define PCIE_MPAGE_L(n) (0x1014 + (n * 8)) /* n = 0..7 */
+#define PCIE_TPAGE_BAR0(n) (0x1050 + (n * 4)) /* n = 0..7 */
+#define PCIE_TPAGE_32 (0<<31) /* AXI 32-bit access */
+#define PCIE_TPAGE_128 (1<<31) /* AXI 128-bit access */
+#define PCIE_TPAGE_BAR1(n) (0x1070 + (n * 4)) /* n = 0..7 */
+#define PCIE_TPAGE_BAR2(n) (0x1090 + (n * 4)) /* n = 0..7 */
+#define PCIE_MSG_IN_FIFO (0x10B0)
+#define PCIE_MSG_IN_FIFO_STATUS (0x10B4)
+#define PCIE_MSG_OUT (0x10B8)
+#define PCIE_TRN_ORDER_STATUS (0x10BC)
+#define PCIE_INT0_STATUS (0x10C0)
+#define PCIE_INT0_ENABLE (0x10C4)
+#define PCIE_INT0_FORCE (0x10C8)
+#define INT0_MSI 0x80000000U
+#define INT0_INT_ASSERTED 0x08000000U
+#define INT0_INT_DEASSERTED 0x04000000U
+#define INT0_ERROR 0x73FFFFABU
+#define PCIE_PHY_STATUS0 (0x10CC)
+#define PCIE_PHY_STATUS1 (0x10D0)
+#define PCIE_PHY_CONTROL0 (0x10D4)
+#define PCIE_PHY_CONTROL1 (0x10D8)
+#define PCIE_PHY_CONTROL2 (0x10DC)
+#define PCIE_RESERVED_E0 (0x10E0)
+#define PCIE_RESERVED_E4 (0x10E4)
+#define PCIE_RESERVED_E8 (0x10E8)
+#define PCIE_AXI_MASTER_WR (0x10EC)
+#define PCIE_LINK_STATUS (0x117C)
+#define PCIE_EP_BAR2_CFG (0x1184)
+#define PCIE_AXI_MSI_ADDR (0x1190)
+#define PCIE_INT1_STATUS (0x11C4)
+#define PCIE_INT1_ENABLE (0x11C8)
+#define PCIE_INT1_FORCE (0x11CC)
+#define INT1_DOORBELL 0x00000001U
+#define PCIE_RC_BAR0_SIZE (0x11F4)
+#define PCIE_MSI0_STATUS (0x1230)
+#define PCIE_MSI0_ENABLE (0x1234)
+#define PCIE_MSI0_FORCE (0x1238)
+#define PCIE_MSI1_STATUS(_grp) (0x123C+(_grp)*12)
+#define PCIE_MSI1_ENABLE(_grp) (0x1240+(_grp)*12)
+#define PCIE_MSI1_FORCE(_grp) (0x1244+(_grp)*12)
+
+/* Every MPAGE register maps 128MB in the AXI memory range */
+#define MPAGE_SIZE (128U<<20)
+
+/* We have 7 MPAGE registers available for outbound window (one reserved for
+ * mapping PCI configuration space).
+ */
+#define MAX_OUTBOUND_SIZE (7 * MPAGE_SIZE)
+
+/* Number of IRQs allocated to MSI */
+#define NUM_MSI_IRQ (NR_IRQS - AXXIA_MSI_FIRST)
+
+/* Bitmap for allocated MSIs */
+static DECLARE_BITMAP(msi_irq_in_use, NUM_MSI_IRQ);
+
+static const struct resource pcie_outbound_default[] = {
+ [0] = {
+ .start = 0,
+ .end = 0,
+ .flags = IORESOURCE_MEM
+ },
+ [1] = {
+ .start = 0,
+ .end = 0,
+ .flags = IORESOURCE_MEM
+ }
+};
+
+struct axxia_pciex_port {
+ char name[16];
+ unsigned int index;
+ u8 root_bus_nr;
+ bool link_up;
+ int irq[17]; /* 1 legacy, 16 MSI */
+ void __iomem *regs;
+ void __iomem *cfg_data;
+ u32 last_mpage;
+ int endpoint;
+ struct device_node *node;
+ struct resource utl_regs;
+ struct resource cfg_space;
+ /* Outbound PCI base address */
+ u64 pci_addr;
+ /* Outbound range in (physical) CPU addresses */
+ struct resource outbound;
+ /* Inbound PCI base address */
+ u64 pci_bar;
+ /* Inbound range in (physical) CPU addresses */
+ struct resource inbound;
+ /* Virtual and physical (CPU space) address for MSI table */
+ void *msi_virt;
+ dma_addr_t msi_phys;
+ /* PCI memory space address for MSI table */
+ u32 msi_pci_addr;
+};
+
+#define PCIE_MAX_PORTS 2
+static struct axxia_pciex_port *axxia_pciex_ports;
+
+static void pcie_msi_dispatch(u32 group, struct axxia_pciex_port *port);
+
+static void
+fixup_axxia_pci_bridge(struct pci_dev *dev)
+{
+ /* if we aren't a PCIe don't bother */
+ if (!pci_find_capability(dev, PCI_CAP_ID_EXP))
+ return;
+
+ /* Set the class appropriately for a bridge device */
+ dev_info(&dev->dev,
+ "Fixup PCI Class to PCI_CLASS_BRIDGE_HOST for %04x:%04x\n",
+ dev->vendor, dev->device);
+ dev->class = PCI_CLASS_BRIDGE_HOST << 8;
+ /* Make the bridge transparent */
+ dev->transparent = 1;
+}
+
+DECLARE_PCI_FIXUP_HEADER(0x1000, 0x5101, fixup_axxia_pci_bridge);
+DECLARE_PCI_FIXUP_HEADER(0x1000, 0x5108, fixup_axxia_pci_bridge);
+DECLARE_PCI_FIXUP_HEADER(0x1000, 0x5120, fixup_axxia_pci_bridge);
+
+/* Convert to Bus# to PCIe port# */
+static struct axxia_pciex_port *bus_to_port(struct pci_bus *bus)
+{
+ return axxia_pciex_ports + pci_domain_nr(bus);
+}
+
+/*
+ * Validate the Bus#/Device#/Function#
+ */
+static int
+axxia_pciex_validate_bdf(struct pci_bus *bus, unsigned int devfn)
+{
+ struct axxia_pciex_port *port;
+
+ port = bus_to_port(bus);
+
+ /* Endpoint can not generate upstream(remote) config cycles */
+ if (port->endpoint)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ if (((!((PCI_FUNC(devfn) == 0) && (PCI_SLOT(devfn) == 0)))
+ && (bus->number == port->root_bus_nr))
+ || (!(PCI_SLOT(devfn) == 0)
+ && (bus->number == port->root_bus_nr+1))) {
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ }
+ return 0;
+}
+
+/*
+ * Return the configuration access base address
+ */
+static void __iomem *
+axxia_pciex_get_config_base(struct axxia_pciex_port *port,
+ struct pci_bus *bus,
+ unsigned int devfn)
+{
+ int relbus, dev, fn;
+ unsigned mpage;
+
+ if (bus->number == port->root_bus_nr)
+ return port->regs;
+
+ relbus = bus->number - (port->root_bus_nr + 1);
+ dev = PCI_SLOT(devfn);
+ fn = PCI_FUNC(devfn);
+
+ if (dev > 31)
+ return NULL;
+
+ /* Build the mpage register (MPAGE[4]=1 for cfg access) */
+ mpage = (fn << 19) | (bus->number << 11) | (dev << 6) | (1<<4);
+
+ /* Primary bus */
+ if (relbus && (bus->number != port->root_bus_nr))
+ mpage |= 1<<5;
+
+ if (mpage != port->last_mpage) {
+ writel(0, port->regs + PCIE_MPAGE_U(7));
+ writel(mpage, port->regs + PCIE_MPAGE_L(7));
+ port->last_mpage = mpage;
+ }
+
+ return port->cfg_data;
+}
+
+/*
+ * Read PCI config space
+ */
+static int
+arm_pciex_axxia_read_config(struct pci_bus *bus,
+ unsigned int devfn,
+ int offset,
+ int len,
+ u32 *val)
+{
+ struct axxia_pciex_port *port = bus_to_port(bus);
+ void __iomem *addr;
+ u32 bus_addr;
+ u32 val32;
+ int bo = offset & 0x3;
+ int rc = PCIBIOS_SUCCESSFUL;
+ u32 bus_addr1;
+
+ if (axxia_pciex_validate_bdf(bus, devfn) != 0)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ addr = axxia_pciex_get_config_base(port, bus, devfn);
+
+ if (!addr) {
+ *val = 0;
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ }
+
+ /*
+ * addressing is different for local config access vs.
+ * access through the mapped config space.
+ */
+ if (bus->number == 0) {
+ int wo = offset & 0xfffffffc;
+
+ bus_addr = (u32)addr + wo;
+ bus_addr1 = bus_addr;
+ } else {
+ /*
+ * mapped config space only supports 32-bit access
+ *
+ * AXI address [3:0] is not used at all.
+ * AXI address[9:4] becomes register number.
+ * AXI address[13:10] becomes Ext. register number
+ * AXI address[17:14] becomes 1st DWBE for configuration
+ * read only.
+ * AXI address[29:27] is used to select one of 8 Mpage
+ * registers.
+ */
+ bus_addr = (u32) addr + (offset << 2);
+ bus_addr1 = bus_addr;
+
+ switch (len) {
+ case 1:
+ bus_addr |= ((1 << bo)) << 14;
+ break;
+ case 2:
+ bus_addr |= ((3 << bo)) << 14;
+ break;
+ default:
+ bus_addr |= (0xf) << 14;
+ break;
+ }
+ }
+ /*
+ * do the read
+ */
+ val32 = readl((u32 __iomem *)bus_addr);
+
+ switch (len) {
+ case 1:
+ *val = (val32 >> (bo * 8)) & 0xff;
+ break;
+ case 2:
+ *val = (val32 >> (bo * 8)) & 0xffff;
+ break;
+ default:
+ *val = val32;
+ break;
+ }
+
+#ifdef PRINT_CONFIG_ACCESSES
+ pr_info("acp_read_config for PCIE%d: %3d fn=0x%04x o=0x%04x l=%d a=0x%08x v=0x%08x, dev=%d\n",
+ port->index, bus->number, devfn, offset, len,
+ bus_addr, *val, PCI_SLOT(devfn));
+#endif
+ return rc;
+}
+
+/*
+ * Write PCI config space.
+ */
+static int
+arm_pciex_axxia_write_config(struct pci_bus *bus,
+ unsigned int devfn,
+ int offset,
+ int len,
+ u32 val)
+{
+ struct axxia_pciex_port *port = bus_to_port(bus);
+ void __iomem *addr;
+ u32 bus_addr;
+ u32 val32;
+
+ if (axxia_pciex_validate_bdf(bus, devfn) != 0)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ addr = axxia_pciex_get_config_base(port, bus, devfn);
+
+ if (!addr)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ /*
+ * addressing is different for local config access vs.
+ * access through the mapped config space. We need to
+ * translate the offset for mapped config access
+ */
+ if (bus->number == 0) {
+ /* the local ACP RC only supports 32-bit dword config access,
+ * so if this is a byte or 16-bit word access we need to
+ * perform a read-modify write
+ */
+ if (len == 4) {
+ bus_addr = (u32) addr + offset;
+ } else {
+ int bs = ((offset & 0x3) * 8);
+
+ bus_addr = (u32) addr + (offset & 0xfffffffc);
+ val32 = readl((u32 __iomem *)bus_addr);
+
+ if (len == 2) {
+ val32 = (val32 & ~(0xffff << bs))
+ | ((val & 0xffff) << bs);
+ } else {
+ val32 = (val32 & ~(0xff << bs))
+ | ((val & 0xff) << bs);
+ }
+
+ val = val32;
+ len = 4;
+ }
+ } else {
+ bus_addr = (u32) addr + (offset << 2) + (offset & 0x3);
+ }
+
+#ifdef PRINT_CONFIG_ACCESSES
+ pr_info("acp_write_config: bus=%3d devfn=0x%04x offset=0x%04x len=%d addr=0x%08x val=0x%08x\n",
+ bus->number, devfn, offset, len, bus_addr, val);
+#endif
+
+ switch (len) {
+ case 1:
+ writeb(val, (u8 __iomem *)(bus_addr));
+ break;
+ case 2:
+ writew(val, (u16 __iomem *)(bus_addr));
+ break;
+ default:
+ writel(val, (u32 __iomem *)(bus_addr));
+ break;
+ }
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static struct pci_ops axxia_pciex_pci_ops = {
+ .read = arm_pciex_axxia_read_config,
+ .write = arm_pciex_axxia_write_config,
+};
+
+/*
+ * pcie_doorbell_isr
+ *
+ * This ISR is for doorbell interrupts for
+ * Endpoint mode which has a dedicated IRQ line
+ * This support expects kernel module to handle the doorbell
+ * interrupt
+ */
+static irqreturn_t
+pcie_doorbell_isr(int irq, void *arg)
+{
+ struct axxia_pciex_port *port = arg;
+ void __iomem *mbase = port->regs;
+ u32 intr1_status;
+
+ intr1_status = readl(mbase + PCIE_INT1_STATUS);
+
+ if (intr1_status & INT1_DOORBELL) {
+ /* EP doorbell interrupt. This support expects kernel module
+ * to handle this doorbell interrupt
+ */
+ /* Clear it */
+ writel(INT1_DOORBELL, mbase + PCIE_INT1_STATUS);
+ }
+ return IRQ_NONE;
+}
+
+
+/*
+ * pcie_legacy_isr
+ *
+ * The interrupt line for this handler is shared between the PCIE controller
+ * itself (for status and error interrupts) and devices using legacy PCI
+ * interupt signalling. Statis and error interrupts are serviced here and this
+ * handler will return IRQ_HANDLED. If the reasont is the assertion of a device
+ * legacy interrupt, this handler returns IRQ_NONE the next action on this line
+ * will be called (the PCI EP interrupt service routine).
+ */
+static irqreturn_t
+pcie_legacy_isr(int irq, void *arg)
+{
+ struct axxia_pciex_port *port = arg;
+ void __iomem *mbase = port->regs;
+ u32 intr_status, intr1_status;
+ irqreturn_t retval = IRQ_HANDLED;
+
+ /* read the PEI interrupt status register */
+ intr_status = readl(mbase + PCIE_INT0_STATUS);
+ intr1_status = readl(mbase + PCIE_INT1_STATUS);
+
+ /* check if this is a PCIe message not from an external device */
+ if (intr_status & INT0_ERROR) {
+ u32 int_enb;
+ u32 offset;
+
+ pr_info("PCIE%d: Error interrupt %#x\n",
+ port->index, intr_status);
+
+ pr_info("PCIE%d: link status = %#x\n",
+ port->index, readl(mbase + PCIE_LINK_STATUS));
+
+ if (intr_status & 0x00020000) {
+ pr_info("PCIE%d: t2a_fn_indp_err_stat = %#x\n",
+ port->index, readl(mbase+0x1170));
+ int_enb = readl(mbase + PCIE_INT0_ENABLE);
+ int_enb &= 0xfffdffff;
+ writel(int_enb, mbase + PCIE_INT0_ENABLE);
+ }
+
+ if (intr_status & 0x00040000) {
+ pr_info("PCIE%d: t2a_fn_indp_other_err_stat = %#x\n",
+ port->index, readl(mbase+0x1174));
+ int_enb = readl(mbase + PCIE_INT0_ENABLE);
+ int_enb &= 0xfffbffff;
+ writel(int_enb, mbase + PCIE_INT0_ENABLE);
+ }
+
+ if (intr_status & 0x00000800) {
+ pr_info("PCIE%d: config=%#x status=%#x\n",
+ port->index,
+ readl(mbase + PCIE_CONFIG),
+ readl(mbase + PCIE_STATUS));
+ int_enb = readl(mbase + PCIE_INT0_ENABLE);
+ int_enb &= 0xfffff7ff;
+ writel(int_enb, mbase + PCIE_INT0_ENABLE);
+ }
+
+ /*
+ * Dump all the potentially interesting PEI registers
+ */
+ for (offset = 0x114c; offset <= 0x1180; offset += 4) {
+ pr_err(" [0x%04x] = 0x%08x\n",
+ offset, readl(mbase + offset));
+ }
+ } else if (intr_status & INT0_INT_ASSERTED) {
+ /* Empty the message FIFO */
+ while ((readl(port->regs + PCIE_MSG_IN_FIFO_STATUS) & 1) == 0)
+ (void) readl(port->regs + PCIE_MSG_IN_FIFO);
+ /* Next handler in chain will service this interrupt */
+ retval = IRQ_NONE;
+ } else if (intr_status & INT0_MSI) {
+ u32 msi_status = readl(port->regs + PCIE_MSI0_STATUS);
+
+ if (msi_status == 0) {
+ retval = IRQ_NONE;
+ } else {
+ u32 group = ffs(msi_status) - 1;
+
+ pcie_msi_dispatch(group, port);
+ }
+ }
+
+ if (intr1_status & INT1_DOORBELL) {
+ /* RC doorbell interrupt. This support expects kernel module
+ * to handle this doorbell interrupt
+ */
+ /* Clear it */
+ writel(INT1_DOORBELL, mbase + PCIE_INT1_STATUS);
+ return IRQ_NONE;
+ }
+
+ /*
+ * We clear all the interrupts in the PEI status, even though
+ * interrupts from external devices have not yet been handled.
+ * That should be okay, since the PCI IRQ in the GIC won't be
+ * re-enabled until all external handlers have been called.
+ */
+ writel(intr_status, mbase + PCIE_INT0_STATUS);
+ return retval;
+}
+
+/*
+ * MSI handler
+ *
+ * This is the handler for PCIE MSI service. It will decode the signalled MSI
+ * using the following hierarchy of status bits. This handler is installed as a
+ * chained handler for each of the 16 interrupt lines on the top-level
+ * interrupt controller. When a pending MSI is found, this handler forwards the
+ * interrupt service to the corresponding MSI IRQs (numbered from
+ * AXXIA_MSI_FIRST..NR_IRQS).
+ *
+ * PCIE_MSI1_STATUS(group)
+ *
+ * PCIE_MSI0_STATUS +----------+
+ * | MSI |
+ * +----------+ +----------+ | 0..15 |
+ * | ARM GIC | | GROUP | /-----------+ |
+ * | +----+ 0..15 +-------/ | |
+ * | | | | | |
+ * | +----+ +-------\ +----------+
+ * | | | | \
+ * | +----+ | \ +----------+
+ * | | | | \ | MSI |
+ * | +----+ | \ | 16..31 |
+ * | | | | \-------+ |
+ * | +----+ | | |
+ * | | | | | |
+ * | | | | +----------+
+ * | | . | |
+ * | | . | | ...
+ * | | . | |
+ * | | | | +----------+
+ * | | | | | MSI |
+ * | +----+ +--------------------+ 240..255 |
+ * | | | | | |
+ * +----------+ +----------+ | |
+ * | |
+ * +----------+
+ */
+static void
+pcie_msi_dispatch(u32 group, struct axxia_pciex_port *port)
+{
+ u32 status;
+
+ /* Check next level interrupt status */
+ status = readl(port->regs + PCIE_MSI1_STATUS(group)) & 0xffff;
+ while (status) {
+ u32 line = ffs(status) - 1;
+
+ status &= ~(1 << line);
+ /* Clear interrupt on sub-level */
+ writel((1 << line), port->regs + PCIE_MSI1_STATUS(group));
+ generic_handle_irq(AXXIA_MSI_FIRST + (group * 16) + line);
+ }
+
+ /* Clear interrupt on top-level*/
+ writel(1 << group, port->regs + PCIE_MSI0_STATUS);
+}
+
+static void
+pcie_msi_irq_handler(unsigned int irq, struct irq_desc *desc)
+{
+ struct axxia_pciex_port *port = &axxia_pciex_ports[0];
+ u32 group = irq - port->irq[1];
+ u32 status;
+
+ /* Check if interrupt is pending */
+ status = readl(port->regs + PCIE_MSI0_STATUS);
+ if (status & (1 << group)) {
+ kstat_incr_irq_this_cpu(irq);
+ /* Handle the PCIe interrupt */
+ pcie_msi_dispatch(group, port);
+ } else {
+ handle_bad_irq(irq, desc);
+ }
+ /* Signal end-of-interrupt */
+ irq_desc_get_chip(desc)->irq_eoi(&desc->irq_data);
+}
+
+/* PCIe setup function */
+static int axxia_pcie_setup(int portno, struct pci_sys_data *sys)
+{
+ struct axxia_pciex_port *port = &axxia_pciex_ports[sys->domain];
+ u32 pci_config, pci_status, link_state;
+ int i, num_pages, err, ret;
+ u32 outbound_size;
+ u32 inbound_size;
+ u64 dest;
+
+ port->root_bus_nr = sys->busnr;
+
+ /* Map PCIe bridge control registers */
+ port->regs = ioremap(port->utl_regs.start,
+ resource_size(&port->utl_regs));
+ if (!port->regs) {
+ pr_err("PCIE%d: Failed to map control registers\n",
+ sys->domain);
+ goto fail;
+ }
+
+ /* Map range for access to PCI configuration space */
+ port->cfg_data = ioremap(port->cfg_space.start,
+ resource_size(&port->cfg_space));
+ if (!port->cfg_data) {
+ pr_err("PCIE%d: Failed to map config space\n", sys->domain);
+ goto fail;
+ }
+
+ pci_add_resource_offset(&sys->resources, &port->outbound,
+ port->outbound.start - port->pci_addr);
+
+ /* add IO_RESOURCE for legacy support expected by bios32 driver
+ * not used by AXM55xx */
+ sys->io_res.start = (sys->domain * 0x100);
+ sys->io_res.end = sys->io_res.start + 0xff;
+ sys->io_res.flags = IORESOURCE_IO;
+ sys->io_res.name = sys->io_res_name;
+ sprintf(sys->io_res_name, "PCI%d I/O", sys->domain);
+ pr_info("PCIE%d: ioport start = %#llx (PCI) -> %#llx\n",
+ sys->domain, sys->io_res.start, sys->io_res.end);
+
+ ret = request_resource(&ioport_resource, &sys->io_res);
+ if (ret) {
+ pr_err("PCI: unable to allocate I/O port region (%d)\n", ret);
+ goto fail;
+ }
+ pci_add_resource_offset(&sys->resources, &sys->io_res,
+ sys->io_offset);
+
+ /* Status/error interrupt */
+ port->irq[0] = irq_of_parse_and_map(port->node, 0);
+ err = request_irq(port->irq[0], pcie_legacy_isr, IRQF_SHARED,
+ "pcie", port);
+ if (err) {
+ pr_err("PCIE%d: Failed to request IRQ#%d (%d)\n",
+ sys->domain, port->irq[0], err);
+ release_resource(&sys->io_res);
+ goto fail;
+ }
+
+ /* MSI interrupts */
+ for (i = 1; i <= 16; i++) {
+ port->irq[i] = irq_of_parse_and_map(port->node, i);
+ if (!port->irq[i])
+ break;
+ irq_set_chained_handler(port->irq[i], pcie_msi_irq_handler);
+ }
+
+ /* Setup as root complex */
+ pci_config = readl(port->regs + PCIE_CONFIG);
+ pci_status = readl(port->regs + PCIE_STATUS);
+ link_state = (pci_status >> 8) & 0x3f;
+ pr_info("PCIE%d: status=0x%08x, link state=%#x\n",
+ port->index, pci_status, link_state);
+
+ /* make sure the ACP device is configured as PCI Root Complex */
+ if ((pci_status & 0x18) != 0x18) {
+ /* Endpoint */
+ pr_err("PCIE%d: Device is not Root Complex\n", port->index);
+ if (sys->domain == 0) {
+ /* PEI0 */
+ err = request_irq(port->irq[0]+3, pcie_doorbell_isr,
+ IRQF_SHARED, "pcie_db", port);
+ if (err) {
+ pr_err("PCIE%d: Failed to request IRQ#%d (%d)\n",
+ sys->domain, port->irq[0], err);
+ release_resource(&sys->io_res);
+ goto fail;
+ }
+ } else if (sys->domain == 1) {
+ /* PEI1 */
+ err = request_irq(port->irq[0]+2, pcie_doorbell_isr,
+ IRQF_SHARED, "pcie_db", port);
+ if (err) {
+ pr_err("PCIE%d: Failed to request IRQ#%d (%d)\n",
+ sys->domain, port->irq[0], err);
+ release_resource(&sys->io_res);
+ goto fail;
+ }
+ }
+ /* Enable doorbell interrupts */
+ writel(INT1_DOORBELL,
+ port->regs + PCIE_INT1_ENABLE);
+ release_resource(&sys->io_res);
+ return 0;
+ }
+
+ /* Make sure the link is up */
+ if (link_state != 0xb) {
+ /* Reset */
+ pr_warn("PCIE%d: Link in bad state - resetting\n", port->index);
+ pci_config |= 1;
+ writel(pci_config, port->regs + PCIE_CONFIG);
+ msleep(1000);
+ pci_status = readl(port->regs + PCIE_STATUS);
+ link_state = (pci_status & 0x3f00) >> 8;
+ pr_warn("PCIE%d: (after reset) link state=%#x\n",
+ port->index, link_state);
+ if (link_state != 0xb) {
+ pr_warn("PCIE%d: Link in bad state - giving up!\n",
+ port->index);
+ release_resource(&sys->io_res);
+ goto fail;
+ }
+ }
+
+ /*
+ * Setup outbound PCI Memory Window
+ */
+
+ outbound_size = resource_size(&port->outbound);
+ num_pages = (outbound_size + MPAGE_SIZE - 1) / MPAGE_SIZE;
+ dest = port->pci_addr;
+ for (i = 0; i < num_pages; i++) {
+ u32 mpage_u = dest >> 32;
+ u32 mpage_l = (u32)dest & ~(MPAGE_SIZE-1);
+
+ writel(mpage_u, port->regs + PCIE_MPAGE_U(i));
+ writel(mpage_l, port->regs + PCIE_MPAGE_L(i));
+ pr_debug("PCIE%d: MPAGE(%d) = %08x %08x\n",
+ port->index, i, mpage_u, mpage_l);
+ dest += MPAGE_SIZE;
+ }
+
+ /*
+ * Setup inbound PCI window
+ */
+
+ /* Configure the inbound window size */
+ inbound_size = (u32) resource_size(&port->inbound);
+ writel(~(inbound_size-1), port->regs + PCIE_RC_BAR0_SIZE);
+
+ /* Verify BAR0 size */
+ {
+ u32 bar0_size;
+
+ writel(~0, port->regs + PCI_BASE_ADDRESS_0);
+ bar0_size = readl(port->regs + PCI_BASE_ADDRESS_0);
+ if ((bar0_size & ~0xf) != ~(inbound_size-1))
+ pr_err("PCIE%d: Config BAR0 failed\n", port->index);
+ }
+
+ /* Set the BASE0 address to start of PCIe base */
+ writel(port->pci_bar, port->regs + PCI_BASE_ADDRESS_0);
+
+ /* Set the BASE1 address to 0x0 */
+ writel(0x0, port->regs + PCI_BASE_ADDRESS_1);
+
+
+ /* Setup TPAGE registers for inbound mapping
+ *
+ * We set the MSB of each TPAGE to select 128-bit AXI access. For the
+ * address field we simply program an incrementing value to map
+ * consecutive pages
+ */
+ for (i = 0; i < 8; i++)
+ writel(PCIE_TPAGE_128 | i, port->regs + PCIE_TPAGE_BAR0(i));
+
+
+ /* Enable all legacy/status/error interrupts */
+ writel(INT0_MSI | INT0_INT_ASSERTED | INT0_ERROR,
+ port->regs + PCIE_INT0_ENABLE);
+
+ /* Enable doorbell interrupts */
+ writel(INT1_DOORBELL,
+ port->regs + PCIE_INT1_ENABLE);
+
+ /* Enable all MSI interrupt groups */
+ writel(0xFFFF, port->regs + PCIE_MSI0_ENABLE);
+ /* Enable all lines in all subgroups */
+ for (i = 0; i < 16; i++)
+ writel(0xFFFF, port->regs + PCIE_MSI1_ENABLE(i));
+
+ return 1;
+fail:
+ if (port->cfg_data)
+ iounmap(port->cfg_data);
+ if (port->regs)
+ iounmap(port->regs);
+ return 0;
+}
+
+/*
+ * Allocate MSI page. A MSI is generated when EP writes to this PCI address.
+ * The region must be 1Kb to manage 256 MSIs.
+ */
+static void *
+pcie_alloc_msi_table(struct pci_dev *pdev, struct axxia_pciex_port *port)
+{
+ u32 msi_lower;
+ void *msi_virt;
+
+ if (dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64)) != 0 &&
+ dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)) != 0) {
+ dev_err(&pdev->dev, "No memory for MSI table\n");
+ return NULL;
+ }
+
+ msi_virt = dma_alloc_coherent(&pdev->dev, 1024,
+ &port->msi_phys,
+ GFP_KERNEL);
+ if (msi_virt) {
+ msi_lower = (u32)port->msi_phys;
+ /* Please note we have 1:1 mapping for inbound */
+ port->msi_pci_addr = port->inbound.start + msi_lower;
+ writel(msi_lower>>10, port->regs + PCIE_AXI_MSI_ADDR);
+ }
+
+ return msi_virt;
+}
+
+
+/*
+ * Scan PCIe bus
+ */
+static struct pci_bus *
+axxia_pcie_scan_bus(int nr, struct pci_sys_data *sys)
+{
+ if (WARN_ON(nr >= PCIE_MAX_PORTS))
+ return NULL;
+
+ return pci_scan_root_bus(NULL, sys->busnr, &axxia_pciex_pci_ops,
+ sys, &sys->resources);
+}
+
+
+
+static int
+axxia_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
+{
+ struct pci_sys_data *sys = dev->sysdata;
+ struct axxia_pciex_port *port = &axxia_pciex_ports[sys->domain];
+
+ return port->irq[0];
+}
+
+/* IRQ chip ops for MSI IRQs */
+static struct irq_chip axxia_msi_chip = {
+ .name = "PCI-MSI",
+ .irq_enable = unmask_msi_irq,
+ .irq_disable = mask_msi_irq,
+ .irq_mask = mask_msi_irq,
+ .irq_unmask = unmask_msi_irq,
+};
+
+/* Port definition struct */
+static struct hw_pci axxia_pcie_hw[] = {
+ [0] = {
+ .nr_controllers = 1,
+ .domain = 0,
+ .setup = axxia_pcie_setup,
+ .scan = axxia_pcie_scan_bus,
+ .map_irq = axxia_pcie_map_irq
+ },
+ [1] = {
+ .nr_controllers = 1,
+ .domain = 1,
+ .setup = axxia_pcie_setup,
+ .scan = axxia_pcie_scan_bus,
+ .map_irq = axxia_pcie_map_irq
+ }
+};
+
+static void
+axxia_probe_pciex_bridge(struct device_node *np)
+{
+ struct axxia_pciex_port *port;
+ u32 portno;
+ const char *val;
+ const u32 *field;
+ int rlen;
+ int pna = of_n_addr_cells(np); /* address-size of parent */
+ u32 pci_space;
+ u64 pci_addr;
+ u64 cpu_addr;
+ u64 size;
+
+ /* Check if device is enabled */
+ if (!of_device_is_available(np))
+ return;
+
+ /* Get the port number from the device-tree */
+ if (of_property_read_u32(np, "port", &portno) != 0) {
+ pr_err("%s: No 'port' property\n", np->full_name);
+ return;
+ }
+
+ if (portno >= PCIE_MAX_PORTS) {
+ pr_err("%s: Port %d out of range\n", np->full_name, portno);
+ return;
+ }
+
+ port = &axxia_pciex_ports[portno];
+ port->index = portno;
+ snprintf(port->name, sizeof(port->name) - 1, "PCIE%d", portno);
+ port->node = of_node_get(np);
+
+ /* Check if device_type property is set to "pci" or "pci-endpoint".
+ * Resulting from this setup this PCIe port will be configured
+ * as root-complex or as endpoint.
+ */
+ val = of_get_property(port->node, "device_type", NULL);
+ port->endpoint = val && strcmp(val, "pci-endpoint") == 0;
+
+ /* Fetch address range for PCIE config space */
+ if (of_address_to_resource(np, 0, &port->cfg_space)) {
+ pr_err("PCIE%d: No resource for PCIe config space\n", portno);
+ return;
+ }
+
+ /* Fetch address range for host bridge internal registers */
+ if (of_address_to_resource(np, 1, &port->utl_regs)) {
+ pr_err("PCIE%d: No resource for PCIe registers\n", portno);
+ return;
+ }
+
+ if (request_resource(&iomem_resource, &port->utl_regs))
+ return;
+
+ /*
+ * Outbound PCI memory window
+ */
+
+ /* Defaults */
+ port->outbound = pcie_outbound_default[portno];
+ port->outbound.name = port->name;
+
+ field = of_get_property(np, "ranges", &rlen);
+ if (field) {
+ pci_space = of_read_number(field + 0, 1);
+ switch ((pci_space >> 24) & 3) {
+ case 0:
+ pr_err("PCIE%d: Invalid 'ranges'\n", portno);
+ break;
+ case 1: /* PCI IO Space */
+ pr_err("PCIE%d: PCI IO not supported\n", portno);
+ break;
+ case 2: /* PCI MEM 32-bit */
+ case 3: /* PCI MEM 64-bit */
+ cpu_addr = of_read_number(field + 3, 2);
+ size = of_read_number(field + 5, 2);
+ port->outbound.start = cpu_addr;
+ port->outbound.end = cpu_addr + size - 1;
+ port->pci_addr = of_read_number(field + 1, 2);
+ break;
+ }
+ }
+
+ if (resource_size(&port->outbound) > MAX_OUTBOUND_SIZE) {
+ pr_err("PCIE%d: Outbound window too large (using max %#x)\n",
+ portno, MAX_OUTBOUND_SIZE);
+ port->outbound.end = (port->outbound.start +
+ MAX_OUTBOUND_SIZE - 1);
+ }
+
+ if (request_resource(&iomem_resource, &port->outbound)) {
+ pr_err("PCIE%d: Memory resource request failed\n", portno);
+ return;
+ }
+
+ if (request_resource(&iomem_resource, &port->cfg_space)) {
+ pr_err("PCIE%d: Config space request failed\n", portno);
+ return;
+ }
+
+ pr_info("PCIE%d: Outbound %#llx..%#llx (CPU) -> %#llx (PCI)\n",
+ portno,
+ port->outbound.start, port->outbound.end,
+ port->pci_addr);
+
+ /*
+ * Inbound PCI memory window
+ */
+
+ /* Default 4GB */
+ port->inbound.name = "PCIE DMA";
+ port->inbound.start = 0x00000000;
+ port->inbound.end = 0xffffffff;
+ port->inbound.flags = IORESOURCE_MEM | IORESOURCE_PREFETCH;
+
+ /* Get dma-ranges property */
+ field = of_get_property(np, "dma-ranges", &rlen);
+ if (!field) {
+ pr_info("PCIE%d: No 'dma-ranges' property, using defaults\n",
+ portno);
+ } else {
+ BUILD_BUG_ON(sizeof(resource_size_t) != sizeof(u64));
+
+ /* Limited to one inbound window for now... */
+ pci_space = of_read_number(field + 0, 1);
+ pci_addr = of_read_number(field + 1, 2);
+ cpu_addr = of_read_number(field + 3, pna);
+ size = of_read_number(field + pna + 3, 2);
+
+ port->inbound.start = cpu_addr;
+ port->inbound.end = cpu_addr + size - 1;
+ port->pci_bar = pci_addr;
+ }
+
+ pr_info("PCIE%d: Inbound %#llx (PCI) -> %#llx..%#llx (CPU)\n",
+ portno,
+ port->pci_bar,
+ port->inbound.start, port->inbound.end);
+}
+
+/*
+ * Allocate a MSI interrupt number and IRQ (the IRQ is a constant offset from
+ * the MSI index). The kernel IRQs for MSI are in a range above hardware IRQs.
+ *
+ * First call also allocates the 1Kb MSI table and configures the controller.
+ */
+int
+arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc)
+{
+ struct axxia_pciex_port *port = bus_to_port(pdev->bus);
+ struct msi_msg msg;
+ int pos, irq;
+
+ /* Allocate MSI table on demand when first device needs it */
+ if (!port->msi_virt)
+ port->msi_virt = pcie_alloc_msi_table(pdev, port);
+
+ if (!port->msi_virt)
+ return -ENOMEM;
+
+ /* Find available MSI */
+again:
+ pos = find_first_zero_bit(msi_irq_in_use, NUM_MSI_IRQ);
+ /* Offset to get the IRQ */
+ irq = AXXIA_MSI_FIRST + pos;
+ if (irq >= NR_IRQS)
+ return -ENOSYS;
+ if (test_and_set_bit(pos, msi_irq_in_use))
+ goto again;
+
+ /* Initialize IRQ descriptor */
+ irq_init_desc(irq);
+ if (irq_set_msi_desc(irq, desc) != 0) {
+ dev_err(&pdev->dev, "Bad IRQ descriptor for IRQ%d\n", irq);
+ clear_bit(pos, msi_irq_in_use);
+ return -EINVAL;
+ }
+ /* Use a simple handle for our "SW" MSI IRQs */
+ irq_set_chip_and_handler(irq, &axxia_msi_chip, handle_simple_irq);
+ set_irq_flags(irq, IRQF_VALID);
+
+ /* Configure PCI device with its MSI address */
+ msg.address_hi = 0x0;
+ msg.address_lo = port->msi_pci_addr + 4*pos;
+ msg.data = irq;
+ write_msi_msg(irq, &msg);
+
+ return 0;
+}
+
+/*
+ * Called by the generic MSI layer to free MSI IRQ.
+ */
+void
+arch_teardown_msi_irq(unsigned int irq)
+{
+ int pos = irq - AXXIA_MSI_FIRST;
+
+ if (0 <= pos && pos < NR_IRQS) {
+ clear_bit(pos, msi_irq_in_use);
+ irq_init_desc(irq);
+ }
+}
+
+/**
+ * Initialize PCIe controller(s) found in the device tree.
+ */
+void __init
+axxia_pcie_init(void)
+{
+ struct device_node *np;
+ int num_ports = 0;
+
+ /* allocate memory */
+ axxia_pciex_ports = kzalloc(PCIE_MAX_PORTS *
+ sizeof(struct axxia_pciex_port),
+ GFP_KERNEL);
+ if (!axxia_pciex_ports) {
+ pr_err("PCIE: No memory\n");
+ return;
+ }
+
+
+ for_each_compatible_node(np, NULL, "lsi,plb-pciex") {
+ if (!of_device_is_available(np)) {
+ num_ports++;
+ continue;
+ }
+
+ axxia_probe_pciex_bridge(np);
+ pci_common_init(&axxia_pcie_hw[num_ports]);
+ if (++num_ports == PCIE_MAX_PORTS)
+ break;
+ }
+}
diff --git a/arch/arm/mach-axxia/pci.h b/arch/arm/mach-axxia/pci.h
new file mode 100644
index 0000000..d4211a4
--- /dev/null
+++ b/arch/arm/mach-axxia/pci.h
@@ -0,0 +1 @@
+void axxia_pcie_init(void);
diff --git a/arch/arm/mach-axxia/platsmp.c b/arch/arm/mach-axxia/platsmp.c
index 959d4df..f5faf5d 100644
--- a/arch/arm/mach-axxia/platsmp.c
+++ b/arch/arm/mach-axxia/platsmp.c
@@ -1,63 +1,255 @@
/*
- * linux/arch/arm/mach-axxia/platsmp.c
+ * linux/arch/arm/mach-axxia/platsmp.c
*
- * Copyright (C) 2012 LSI Corporation
+ * Copyright (C) 2012 LSI
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
-
#include <linux/init.h>
-#include <linux/io.h>
+#include <linux/errno.h>
#include <linux/smp.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
#include <linux/of.h>
-#include <linux/of_address.h>
+#include <linux/of_fdt.h>
+#include <asm/smp_plat.h>
#include <asm/cacheflush.h>
+#include <linux/irqchip/arm-gic.h>
+#include <asm/mach/map.h>
+#include <asm/virt.h>
+
+#include "axxia.h"
+#include "lsi_power_management.h"
+#include "axxia_circular_queue.h"
+#include <mach/axxia-gic.h>
+
+extern void axxia_secondary_startup(void);
+extern void axxia_cpu_power_management_gic_control(u32 cpu, bool enable);
+extern void axxia_dist_power_management_gic_control(bool enable);
+extern struct circular_queue_t axxia_circ_q;
+
+#define SYSCON_PHYS_ADDR 0x002010030000ULL
-/* Syscon register offsets for releasing cores from reset */
-#define SC_CRIT_WRITE_KEY 0x1000
-#define SC_RST_CPU_HOLD 0x1010
+static int __cpuinitdata wfe_fixup;
+static int wfe_available;
+
+inline void
+__axxia_arch_wfe(void)
+{
+ if (0 != wfe_available)
+ wfe();
+}
+EXPORT_SYMBOL(__axxia_arch_wfe);
/*
- * Write the kernel entry point for secondary CPUs to the specified address
+ * Check if we need to enable cross-cluster SEV workaround for a bug in
+ * revision 1.0 silicon (which could cause event signals (from SEV instruction)
+ * to get lost between clusters). As a workaround, we set the bit 7 in CP15
+ * ACTLR (enable WFE as a no-operation) for each core.
*/
-static void write_release_addr(u32 release_phys)
+static void __init check_fixup_sev(void __iomem *syscon)
{
- u32 *virt = (u32 *) phys_to_virt(release_phys);
- writel_relaxed(virt_to_phys(secondary_startup), virt);
- /* Make sure this store is visible to other CPUs */
- smp_wmb();
- __cpuc_flush_dcache_area(virt, sizeof(u32));
+ u32 pfuse = readl(syscon + 0x34);
+ u32 chip_type = pfuse & 0x1f;
+ u32 chip_ver = (pfuse >> 5) & 0x3f;
+
+ /* Set flag for secondary cores */
+ wfe_fixup = (chip_type == 0 || chip_type == 9) && (chip_ver == 0);
+ pr_info("axxia: Cross-cluster SEV fixup: %s\n", wfe_fixup ? "yes" : "no");
}
-static int axxia_boot_secondary(unsigned int cpu, struct task_struct *idle)
+static void do_fixup_sev(void)
{
- struct device_node *syscon_np;
- void __iomem *syscon;
u32 tmp;
- syscon_np = of_find_compatible_node(NULL, NULL, "lsi,axxia-syscon");
- if (!syscon_np)
- return -ENOENT;
+ if (wfe_fixup) {
+ asm volatile("mrc\tp15, 0, %0, c1, c0, 1" : "=r"(tmp));
+ tmp |= (1<<7);
+ asm volatile("mcr\tp15, 0, %0, c1, c0, 1" :: "r"(tmp));
+ isb();
+ }
+}
+
+/*
+ * Write pen_release in a way that is guaranteed to be visible to all
+ * observers, irrespective of whether they're taking part in coherency
+ * or not. This is necessary for the hotplug code to work reliably.
+ */
+static void write_pen_release(int val)
+{
+ pen_release = val;
- syscon = of_iomap(syscon_np, 0);
- if (!syscon)
- return -ENOMEM;
+ /* Set up memory barrier */
+ smp_wmb();
+ __cpuc_flush_dcache_area((void *)&pen_release, sizeof(pen_release));
+ outer_clean_range(__pa(&pen_release), __pa(&pen_release + 1));
+}
- tmp = readl(syscon + SC_RST_CPU_HOLD);
- writel(0xab, syscon + SC_CRIT_WRITE_KEY);
- tmp &= ~(1 << cpu);
- writel(tmp, syscon + SC_RST_CPU_HOLD);
+static DEFINE_RAW_SPINLOCK(boot_lock);
- return 0;
+void axxia_secondary_init(unsigned int cpu)
+{
+ int phys_cpu;
+ int phys_cluster;
+
+ phys_cpu = cpu_logical_map(cpu);
+ phys_cluster = phys_cpu / 4;
+
+ /*
+ * Only execute this when powering up a cpu for hotplug.
+ */
+ if (!pm_in_progress[phys_cpu]) {
+ /* Fixup for cross-cluster SEV */
+ do_fixup_sev();
+
+ axxia_gic_secondary_init();
+ } else {
+
+#ifdef CONFIG_HOTPLUG_CPU_COMPLETE_POWER_DOWN
+ if (cluster_power_up[phys_cluster])
+ pm_cluster_logical_powerup();
+ pm_cpu_logical_powerup();
+#endif
+ get_cpu();
+ axxia_gic_secondary_init();
+ put_cpu();
+
+#ifdef CONFIG_HOTPLUG_CPU_COMPLETE_POWER_DOWN
+ cluster_power_up[phys_cluster] = false;
+ pm_in_progress[phys_cpu] = false;
+#endif
+ }
+
+ /*
+ * Let the primary processor know we're out of the
+ * pen, then head off into the C entry point.
+ */
+ write_pen_release(-1);
+
+ /*
+ * Synchronise with the boot thread.
+ */
+ _raw_spin_lock(&boot_lock);
+ _raw_spin_unlock(&boot_lock);
+}
+
+int axxia_boot_secondary(unsigned int cpu, struct task_struct *idle)
+{
+
+ int phys_cpu, cluster;
+ unsigned long timeout;
+ unsigned long powered_down_cpu;
+ u32 i;
+ u32 dummy;
+
+
+ /*
+ * Set synchronisation state between this boot processor
+ * and the secondary one.
+ */
+ _raw_spin_lock(&boot_lock);
+
+ phys_cpu = cpu_logical_map(cpu);
+
+ powered_down_cpu = pm_get_powered_down_cpu();
+
+ if (powered_down_cpu & (1 << phys_cpu)) {
+ pm_in_progress[phys_cpu] = true;
+ pm_cpu_powerup(phys_cpu);
+ }
+
+ /*
+ * In the Axxia, the bootloader does not put the secondary cores
+ * into a wait-for-event (wfe) or wait-for-interrupt (wfi) state
+ * because of the multi-cluster design (i.e., there's no way for
+ * the primary core in cluster 0 to send an event or interrupt
+ * to secondary cores in the other clusters).
+ *
+ * Instead, the secondary cores are immediately put into a loop
+ * that polls the "pen_release" global and MPIDR register. The two
+ * are compared and if they match, a secondary core then executes
+ * the Axxia secondary startup code.
+ *
+ * Here we convert the "cpu" variable to be compatible with the
+ * ARM MPIDR register format (CLUSTERID and CPUID):
+ *
+ * Bits: |11 10 9 8|7 6 5 4 3 2|1 0
+ * | CLUSTER | Reserved |CPU
+ */
+ cluster = (phys_cpu / 4) << 8;
+ phys_cpu = cluster + (phys_cpu % 4);
+
+ /* Release the specified core */
+ write_pen_release(phys_cpu);
+
+ /* Send a wakeup IPI to get the idled cpu out of WFI state */
+ arch_send_wakeup_ipi_mask(cpumask_of(cpu));
+
+
+ /* Wait for so long, then give up if nothing happens ... */
+ timeout = jiffies + (1 * HZ);
+ while (time_before(jiffies, timeout)) {
+ /* Remove memroy barrier */
+ smp_rmb();
+
+ if (pen_release == -1)
+ break;
+
+ /* Wait 10 cycles */
+ for (i = 0; i < 10; i++)
+ dummy = i;
+ }
+
+ /*
+ * Now the secondary core is starting up let it run its
+ * calibrations, then wait for it to finish.
+ */
+ _raw_spin_unlock(&boot_lock);
+
+ return pen_release != -1 ? -ENOSYS : 0;
+}
+
+static __init struct device_node *get_cpu_node(int cpu)
+{
+ struct device_node *np;
+
+ for_each_node_by_type(np, "cpu") {
+ u32 reg;
+
+ if (of_property_read_u32(np, "reg", ®))
+ continue;
+ if (reg == cpu_logical_map(cpu))
+ return np;
+ }
+
+ return NULL;
}
static void __init axxia_smp_prepare_cpus(unsigned int max_cpus)
{
+ void __iomem *syscon;
int cpu_count = 0;
int cpu;
+ syscon = ioremap(SYSCON_PHYS_ADDR, SZ_64K);
+ if (WARN_ON(!syscon))
+ return;
+
+ check_fixup_sev(syscon);
+ do_fixup_sev();
+
+ if (of_find_compatible_node(NULL, NULL,
+ "lsi,axm5500-sim") != NULL ||
+ of_find_compatible_node(NULL, NULL,
+ "lsi,axm5500-emu") != NULL)
+ wfe_available = 0;
+ else
+ wfe_available = 1;
+
/*
* Initialise the present map, which describes the set of CPUs actually
* populated at the present time.
@@ -65,25 +257,64 @@ static void __init axxia_smp_prepare_cpus(unsigned int max_cpus)
for_each_possible_cpu(cpu) {
struct device_node *np;
u32 release_phys;
+ u32 *release_virt;
- np = of_get_cpu_node(cpu, NULL);
+ np = get_cpu_node(cpu);
if (!np)
continue;
if (of_property_read_u32(np, "cpu-release-addr", &release_phys))
continue;
+ /*
+ * Release all physical cpus since we might want to
+ * bring them online later.
+ */
+ if (cpu != 0) {
+ u32 phys_cpu = cpu_logical_map(cpu);
+ u32 tmp = readl(syscon + 0x1010);
+
+ writel(0xab, syscon + 0x1000);
+ tmp &= ~(1 << phys_cpu);
+ writel(tmp, syscon + 0x1010);
+ }
+
if (cpu_count < max_cpus) {
set_cpu_present(cpu, true);
cpu_count++;
}
- if (release_phys != 0)
- write_release_addr(release_phys);
+ /*
+ * This is the entry point of the routine that the secondary
+ * cores will execute once they are released from their
+ * "holding pen".
+ */
+ if (release_phys != 0) {
+ int is_kmapped = pfn_valid(__phys_to_pfn(release_phys));
+
+ if (is_kmapped)
+ release_virt = phys_to_virt(release_phys);
+ else
+ release_virt = ioremap(release_phys, PAGE_SIZE);
+ *release_virt = virt_to_phys(axxia_secondary_startup);
+ /* Setup memory barrier */
+ smp_wmb();
+ __cpuc_flush_dcache_area(release_virt, sizeof(u32));
+ if (!is_kmapped)
+ iounmap(release_virt);
+ }
}
+
+ iounmap(syscon);
+
}
-static struct smp_operations axxia_smp_ops __initdata = {
+struct smp_operations axxia_smp_ops __initdata = {
.smp_prepare_cpus = axxia_smp_prepare_cpus,
+ .smp_secondary_init = axxia_secondary_init,
.smp_boot_secondary = axxia_boot_secondary,
+#ifdef CONFIG_HOTPLUG_CPU
+ .cpu_die = axxia_platform_cpu_die,
+ .cpu_kill = axxia_platform_cpu_kill,
+#endif
+
};
-CPU_METHOD_OF_DECLARE(axxia_smp, "lsi,syscon-release", &axxia_smp_ops);
diff --git a/arch/arm/mach-axxia/smon.c b/arch/arm/mach-axxia/smon.c
new file mode 100644
index 0000000..313a3ce
--- /dev/null
+++ b/arch/arm/mach-axxia/smon.c
@@ -0,0 +1,223 @@
+/*
+ * linux/arch/arm/mach-axxia/smon.c
+ *
+ * Platform perf helper module for generic VP statistical monitor
+ *
+ * Copyright (C) 2013 LSI Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+#include <linux/io.h>
+
+#include <../../../drivers/misc/lsi-ncr.h>
+
+#include "smon.h"
+
+static void memcpy32_fromio(uint32_t *dest, uint32_t *src, uint32_t len)
+{
+ uint32_t i;
+
+ for (i = 0; i < len; i++)
+ dest[i] = ioread32(src + i);
+}
+
+static void memcpy32_toio(uint32_t *dest, uint32_t *src, uint32_t len)
+{
+ uint32_t i;
+
+ for (i = 0; i < len; i++)
+ iowrite32(src[i], dest + i);
+}
+
+void smon_init_ncp(struct smon_s *smon, uint32_t node, uint32_t target,
+ uint32_t offset)
+{
+ smon->assigned[0] = UNASSIGNED;
+ smon->assigned[1] = UNASSIGNED;
+ smon->type = NCP_SMON;
+ smon->node = node;
+ smon->target = target;
+ smon->offset = offset;
+}
+
+void smon_init_mem(struct smon_s *smon, uint64_t addr, uint32_t offset)
+{
+ smon->assigned[0] = UNASSIGNED;
+ smon->assigned[1] = UNASSIGNED;
+ smon->type = MEM_SMON;
+ smon->addr = ioremap(addr, SZ_4K);
+ smon->offset = offset;
+
+ if (smon->addr == NULL)
+ pr_err("axxia perf, smon can't remap memory %lld\n", addr);
+}
+
+void smon_stop_if_unassigned(struct smon_s *smon)
+{
+ uint32_t control = 0;
+
+ if (smon->assigned[0] == UNASSIGNED &&
+ smon->assigned[1] == UNASSIGNED) {
+ ncr_write(NCP_REGION_ID(smon->node, smon->target), smon->offset,
+ 1 * REG_SZ, &control);
+ }
+}
+
+uint32_t smon_allocate(struct smon_s *smon, uint8_t event)
+{
+ if (smon->assigned[0] == UNASSIGNED) {
+ smon->events[0] = event;
+ smon->assigned[0] = ASSIGNED;
+ } else if (smon->assigned[1] == UNASSIGNED) {
+ smon->events[1] = event;
+ smon->assigned[1] = ASSIGNED;
+ } else {
+ pr_warn("smon_allocate, no counter availible\n");
+ return -ENOCOUNTER;
+ }
+
+ return 0;
+}
+
+uint32_t smon_deallocate(struct smon_s *smon, uint8_t event)
+{
+ if ((smon->assigned[0] == ASSIGNED) && (smon->events[0] == event))
+ smon->assigned[0] = UNASSIGNED;
+ else if ((smon->assigned[1] == ASSIGNED) && (smon->events[1] == event))
+ smon->assigned[1] = UNASSIGNED;
+ else
+ return -ENOCOUNTER;
+
+ return 0;
+}
+
+uint32_t smon_event_active(struct smon_s *smon, uint8_t event)
+{
+ if ((smon->assigned[0] == ASSIGNED) && (smon->events[0] == event))
+ return 0;
+ else if ((smon->assigned[1] == ASSIGNED) && (smon->events[1] == event))
+ return 0;
+
+ return -ENOCOUNTER;
+}
+
+uint32_t smon_read(struct smon_s *smon, uint8_t event)
+{
+ uint32_t deltacount;
+
+ if (smon->type == NCP_SMON)
+ ncr_read(NCP_REGION_ID(smon->node, smon->target), smon->offset,
+ 8 * REG_SZ, &smon->regs);
+ else if (smon->type == MEM_SMON)
+ memcpy32_fromio((uint32_t *)&smon->regs,
+ (uint32_t *)smon->addr + smon->offset, 8);
+
+ if ((smon->assigned[0] == ASSIGNED) &&
+ (smon->events[0] == event)) {
+ if (smon->regs.count0 >= smon->lastread[0])
+ deltacount = smon->regs.count0 - smon->lastread[0];
+ else
+ deltacount = 0xffffffff - smon->lastread[0]
+ + smon->regs.count0;
+
+ smon->lastread[0] = smon->regs.count0;
+
+ return deltacount;
+ } else if ((smon->assigned[1] == ASSIGNED) &&
+ (smon->events[1] == event)) {
+ if (smon->regs.count1 >= smon->lastread[1])
+ deltacount = smon->regs.count1 - smon->lastread[1];
+ else
+ deltacount = 0xffffffff - smon->lastread[1]
+ + smon->regs.count1;
+
+ smon->lastread[1] = smon->regs.count1;
+
+ return deltacount;
+ }
+
+ return -ENOEVENT;
+}
+
+uint32_t smon_start(struct smon_s *smon, uint8_t event)
+{
+ /* get currect configuration */
+ if (smon->type == NCP_SMON)
+ ncr_read(NCP_REGION_ID(smon->node, smon->target), smon->offset,
+ 8 * REG_SZ, &smon->regs);
+ else if (smon->type == MEM_SMON)
+ memcpy32_fromio((uint32_t *)&smon->regs,
+ (uint32_t *)smon->addr + smon->offset, 8);
+
+ smon->regs.control = 1; /* run counters */
+
+ if ((smon->assigned[0] == ASSIGNED) && (smon->events[0] == event)) {
+ smon->regs.event0 = event;
+ smon->regs.count0 = 0;
+ smon->lastread[0] = 0;
+
+ if (smon->type == NCP_SMON) {
+ /* write configuration, but do not change count reg */
+ ncr_write(NCP_REGION_ID(smon->node, smon->target),
+ smon->offset, 2 * REG_SZ, &smon->regs);
+
+ /* clear this events counter register */
+ ncr_write(NCP_REGION_ID(smon->node, smon->target),
+ smon->offset + 4 * REG_SZ, 1 * REG_SZ,
+ &smon->regs.count0);
+ } else if (smon->type == MEM_SMON) {
+ /* write configuration, but do not change count reg */
+ memcpy32_toio((uint32_t *)smon->addr + smon->offset,
+ (uint32_t *)&smon->regs, 2);
+
+ /* clear this events counter register */
+ memcpy32_toio((uint32_t *)smon->addr + smon->offset + 4,
+ (uint32_t *)&smon->regs.count0, 1);
+
+ }
+
+ } else if ((smon->assigned[1] == ASSIGNED)
+ && (smon->events[1] == event)) {
+ smon->regs.event1 = event;
+ smon->regs.count1 = 0;
+ smon->lastread[1] = 0;
+
+ if (smon->type == NCP_SMON) {
+ /* write configuration, but do not change count reg */
+ ncr_write(NCP_REGION_ID(smon->node, smon->target),
+ smon->offset, 2 * REG_SZ, &smon->regs);
+
+ /* clear this events counter register */
+ ncr_write(NCP_REGION_ID(smon->node, smon->target),
+ smon->offset + 5 * REG_SZ, 1 * REG_SZ,
+ &smon->regs.count1);
+ } else if (smon->type == MEM_SMON) {
+ /* write configuration, but do not change count reg */
+ memcpy32_toio((uint32_t *)smon->addr + smon->offset,
+ (uint32_t *)&smon->regs, 2);
+
+ /* clear this events counter register */
+ memcpy32_toio((uint32_t *)smon->addr + smon->offset + 5,
+ (uint32_t *)&smon->regs.count1, 1);
+ }
+
+ } else {
+ pr_warn("smon_start, no counter availible\n");
+ return -ENOCOUNTER;
+ }
+
+ return 0;
+}
diff --git a/arch/arm/mach-axxia/smon.h b/arch/arm/mach-axxia/smon.h
new file mode 100644
index 0000000..d3573cd
--- /dev/null
+++ b/arch/arm/mach-axxia/smon.h
@@ -0,0 +1,72 @@
+/*
+ * Helper module for board specific I2C bus registration
+ *
+ * Copyright (C) 2014 LSI Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+#ifndef __ASM__ARCH_AXXIA_SMON_H
+#define __ASM__ARCH_AXXIA_SMON_H
+
+#include <linux/kernel.h>
+
+struct smon_reg_s {
+ uint32_t control;
+ uint8_t event0;
+ uint8_t event1;
+ uint16_t reserved;
+ uint32_t compare0;
+ uint32_t compare1;
+ uint32_t count0;
+ uint32_t count1;
+ uint32_t time;
+ uint32_t maxtime;
+};
+
+struct smon_s {
+ struct smon_reg_s regs;
+ uint32_t type; /* NCP_SMON or MEM_SMON */
+ uint32_t *addr; /* MEM_SMON */
+ uint32_t node; /* NCP_SMON */
+ uint32_t target; /* " */
+ uint32_t offset;
+ uint32_t lastread[2];
+ uint8_t assigned[2];
+ uint8_t events[2];
+};
+
+#define REG_SZ 4
+
+#define MEM_SMON 0
+#define NCP_SMON 1
+
+#define UNASSIGNED 0
+#define ASSIGNED 1
+
+#define ENOCOUNTER 1
+#define ENOEVENT 2
+
+void smon_init_ncp(struct smon_s *smon, uint32_t node, uint32_t target,
+ uint32_t offset);
+void smon_init_mem(struct smon_s *smon, uint64_t addr, uint32_t offset);
+void smon_stop_if_unassigned(struct smon_s *smon);
+uint32_t smon_allocate(struct smon_s *smon, uint8_t event);
+uint32_t smon_deallocate(struct smon_s *smon, uint8_t event);
+uint32_t smon_event_active(struct smon_s *smon, uint8_t event);
+uint32_t smon_read(struct smon_s *smon, uint8_t event);
+uint32_t smon_start(struct smon_s *smon, uint8_t event);
+
+#endif /* __ASM__ARCH_AXXIA_SMON_H */
diff --git a/arch/arm/mach-axxia/ssp-gpio.c b/arch/arm/mach-axxia/ssp-gpio.c
new file mode 100644
index 0000000..7ddf748
--- /dev/null
+++ b/arch/arm/mach-axxia/ssp-gpio.c
@@ -0,0 +1,136 @@
+/*
+ * GPIO interface for SSP chip select pins.
+ *
+ * Copyright (C) 2013 LSI Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/of.h>
+
+struct gpio_dev {
+ void __iomem *regs;
+ struct gpio_chip gpio_chip;
+};
+
+static int
+ssp_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct gpio_dev *priv = dev_get_drvdata(chip->dev);
+ u32 tmp = readl(priv->regs + 0x30);
+
+ return !!(tmp & (1<<offset));
+}
+
+static int
+ssp_gpio_direction_out(struct gpio_chip *chip, unsigned offset, int value)
+{
+ /* Pins are only outputs */
+ return 0;
+}
+
+static void
+ssp_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct gpio_dev *priv = dev_get_drvdata(chip->dev);
+ u32 tmp = readl(priv->regs + 0x30);
+
+ if (value)
+ tmp |= (1<<offset);
+ else
+ tmp &= ~(1<<offset);
+ writel(tmp, priv->regs + 0x30);
+}
+
+static int
+ssp_gpio_probe(struct platform_device *pdev)
+{
+ struct gpio_dev *priv;
+ struct resource *io;
+ struct gpio_chip *chip;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+
+ io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!io)
+ return -EINVAL;
+
+ priv->regs = devm_ioremap_nocache(&pdev->dev,
+ io->start, resource_size(io));
+ if (!priv->regs)
+ return -ENXIO;
+
+ chip = &priv->gpio_chip;
+ chip->dev = &pdev->dev;
+#ifdef CONFIG_OF_GPIO
+ chip->of_node = pdev->dev.of_node;
+#endif
+ chip->get = ssp_gpio_get;
+ chip->direction_output = ssp_gpio_direction_out;
+ chip->set = ssp_gpio_set;
+ chip->label = "ssp-gpio";
+ chip->owner = THIS_MODULE;
+ chip->base = -1;
+ chip->ngpio = 5;
+
+ /* Deassert all */
+ writel(0x1f, priv->regs + 0x30);
+
+ ret = gpiochip_add(chip);
+ if (ret < 0)
+ dev_err(&pdev->dev, "could not register gpiochip, %d\n", ret);
+
+ return ret;
+}
+
+static int
+ssp_gpio_remove(struct platform_device *pdev)
+{
+ struct gpio_dev *priv = dev_get_drvdata(&pdev->dev);
+
+ gpiochip_remove(&priv->gpio_chip);
+ return 0;
+}
+
+static const struct of_device_id ssp_gpio_id_table[] = {
+ { .compatible = "lsi, ssp-gpio" },
+ {}
+};
+MODULE_DEVICE_TABLE(platform, ssp_gpio_id_table);
+
+static struct platform_driver ssp_gpio_driver = {
+ .driver = {
+ .name = "ssp-gpio",
+ .owner = THIS_MODULE,
+ .of_match_table = ssp_gpio_id_table
+ },
+ .probe = ssp_gpio_probe,
+ .remove = ssp_gpio_remove,
+};
+
+module_platform_driver(ssp_gpio_driver);
+
+MODULE_AUTHOR("LSI Corporation");
+MODULE_DESCRIPTION("GPIO interface for SSP chip selects");
+MODULE_LICENSE("GPL");
diff --git a/arch/arm/mach-axxia/timers.c b/arch/arm/mach-axxia/timers.c
new file mode 100644
index 0000000..7d6e564
--- /dev/null
+++ b/arch/arm/mach-axxia/timers.c
@@ -0,0 +1,224 @@
+/*
+ * arch/arm/mach-axxia/timers.c
+ *
+ * Copyright (C) 2012 LSI
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ This is based on arch/arm/common/timer-sp.c.
+
+ The timers used are SP804s, but, there are 8 timers instead of 2,
+ AND the ID registers are missing.
+ */
+
+#include <linux/clk.h>
+#include <linux/clocksource.h>
+#include <linux/clockchips.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/sched_clock.h>
+#include <asm/hardware/arm_timer.h>
+
+struct axxia_timer {
+ struct clock_event_device dev;
+ struct irqaction irqaction;
+ void __iomem *base;
+ unsigned long reload;
+};
+
+#define timer_to_clock_event(_x) container_of(_x, struct axxia_timer, dev)
+
+static void __iomem *sched_clock_base;
+
+static u64 sp804_read(void)
+{
+ return ~readl_relaxed(sched_clock_base + TIMER_VALUE);
+}
+
+/**
+ * axxia_timer_set_mode
+ */
+static void
+axxia_timer_set_mode(enum clock_event_mode mode, struct clock_event_device *evt)
+{
+ struct axxia_timer *timer = timer_to_clock_event(evt);
+ unsigned long ctrl = TIMER_CTRL_32BIT | TIMER_CTRL_IE;
+
+ pr_info("axxia_timer_set_mode: CPU#%d set mode %d on timer %s\n",
+ smp_processor_id(), mode, timer->dev.name);
+
+ writel(ctrl, timer->base + TIMER_CTRL);
+
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ writel(timer->reload, timer->base + TIMER_LOAD);
+ ctrl |= TIMER_CTRL_PERIODIC | TIMER_CTRL_ENABLE;
+ break;
+
+ case CLOCK_EVT_MODE_ONESHOT:
+ /* period set, and timer enabled in 'next_event' hook */
+ ctrl |= TIMER_CTRL_ONESHOT;
+ break;
+
+ case CLOCK_EVT_MODE_UNUSED:
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ default:
+ break;
+ }
+
+ writel(ctrl, timer->base + TIMER_CTRL);
+}
+
+/**
+ * axxia_timer_set_next_event
+ *
+ */
+static int
+axxia_timer_set_next_event(unsigned long next, struct clock_event_device *evt)
+{
+ struct axxia_timer *timer = timer_to_clock_event(evt);
+ unsigned long ctrl;
+
+ ctrl = readl(timer->base + TIMER_CTRL);
+ writel(next, timer->base + TIMER_LOAD);
+ writel(ctrl | TIMER_CTRL_ENABLE, timer->base + TIMER_CTRL);
+
+ return 0;
+}
+
+
+/**
+ * axxia_timer_handler - IRQ handler for the timer.
+ *
+ */
+static irqreturn_t
+axxia_timer_handler(int irq, void *dev_id)
+{
+ struct axxia_timer *timer = (struct axxia_timer *)dev_id;
+
+ /* clear the interrupt */
+ writel(1, timer->base + TIMER_INTCLR);
+
+ timer->dev.event_handler(&timer->dev);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * axxia_timer_get_clock_rate
+ *
+ */
+static long __init
+axxia_timer_get_clock_rate(const char *name)
+{
+ struct clk *clk;
+ long rate;
+ int err;
+
+ clk = clk_get_sys("sp804", name);
+ if (IS_ERR(clk)) {
+ pr_err("sp804: %s clock not found: %d\n", name,
+ (int)PTR_ERR(clk));
+ return PTR_ERR(clk);
+ }
+
+ err = clk_prepare(clk);
+ if (err) {
+ pr_err("sp804: %s clock failed to prepare: %d\n", name, err);
+ clk_put(clk);
+ return err;
+ }
+
+ err = clk_enable(clk);
+ if (err) {
+ pr_err("sp804: %s clock failed to enable: %d\n", name, err);
+ clk_unprepare(clk);
+ clk_put(clk);
+ return err;
+ }
+
+ rate = clk_get_rate(clk);
+ if (rate < 0) {
+ pr_err("sp804: %s clock failed to get rate: %ld\n", name, rate);
+ clk_disable(clk);
+ clk_unprepare(clk);
+ clk_put(clk);
+ }
+
+ return rate;
+}
+
+void __init
+axxia_timer_clocksource_init(void __iomem *base, const char *name)
+{
+ long rate;
+
+ rate = axxia_timer_get_clock_rate(name);
+ if (WARN_ON(rate < 0))
+ return;
+
+ /* Setup timer 0 as free-running clocksource */
+ writel(0, base + TIMER_CTRL);
+ writel(0xffffffff, base + TIMER_LOAD);
+ writel(0xffffffff, base + TIMER_VALUE);
+ writel(TIMER_CTRL_32BIT | TIMER_CTRL_ENABLE | TIMER_CTRL_PERIODIC,
+ base + TIMER_CTRL);
+
+ clocksource_mmio_init(base + TIMER_VALUE, name,
+ rate, 200, 32, clocksource_mmio_readl_down);
+
+ sched_clock_base = base;
+ sched_clock_register(sp804_read, 32, rate);
+}
+
+void __init
+axxia_timer_clockevents_init(void __iomem *base,
+ unsigned int irq, const char *name)
+{
+ struct axxia_timer *evt;
+ long rate;
+
+ rate = axxia_timer_get_clock_rate(name);
+ if (WARN_ON(rate < 0))
+ return;
+
+ evt = kzalloc(sizeof(*evt), GFP_KERNEL);
+ if (evt == NULL)
+ return;
+
+ evt->dev.features = CLOCK_EVT_FEAT_PERIODIC |
+ CLOCK_EVT_FEAT_ONESHOT,
+ evt->dev.set_mode = axxia_timer_set_mode,
+ evt->dev.set_next_event = axxia_timer_set_next_event,
+ evt->dev.rating = 400,
+ evt->dev.name = name;
+ evt->dev.irq = irq;
+ evt->dev.cpumask = cpu_all_mask,
+ evt->base = base;
+ evt->reload = DIV_ROUND_CLOSEST(rate, HZ);
+
+ evt->irqaction.name = name;
+ evt->irqaction.flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL;
+ evt->irqaction.handler = axxia_timer_handler;
+ evt->irqaction.dev_id = evt;
+
+ setup_irq(irq, &evt->irqaction);
+ clockevents_config_and_register(&evt->dev, rate, 0xf, 0xffffffff);
+}
--
1.7.9.5
More information about the linux-yocto
mailing list