aboutsummaryrefslogtreecommitdiff
path: root/drivers/bus
diff options
context:
space:
mode:
authorMeizu OpenSource <patchwork@meizu.com>2016-08-15 10:19:42 +0800
committerMeizu OpenSource <patchwork@meizu.com>2016-08-15 10:19:42 +0800
commitd2e1446d81725c351dc73a03b397ce043fb18452 (patch)
tree4dbc616b7f92aea39cd697a9084205ddb805e344 /drivers/bus
first commit
Diffstat (limited to 'drivers/bus')
-rw-r--r--drivers/bus/Kconfig36
-rw-r--r--drivers/bus/Makefile11
-rw-r--r--drivers/bus/arm-cci.c922
-rw-r--r--drivers/bus/mvebu-mbus.c864
-rw-r--r--drivers/bus/omap-ocp2scp.c150
-rw-r--r--drivers/bus/omap_l3_noc.c266
-rw-r--r--drivers/bus/omap_l3_noc.h176
-rw-r--r--drivers/bus/omap_l3_smx.c297
-rw-r--r--drivers/bus/omap_l3_smx.h338
9 files changed, 3060 insertions, 0 deletions
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig
new file mode 100644
index 000000000..5286e2d33
--- /dev/null
+++ b/drivers/bus/Kconfig
@@ -0,0 +1,36 @@
+#
+# Bus Devices
+#
+
+menu "Bus devices"
+
+config MVEBU_MBUS
+ bool
+ depends on PLAT_ORION
+ help
+ Driver needed for the MBus configuration on Marvell EBU SoCs
+ (Kirkwood, Dove, Orion5x, MV78XX0 and Armada 370/XP).
+
+config OMAP_OCP2SCP
+ tristate "OMAP OCP2SCP DRIVER"
+ depends on ARCH_OMAP2PLUS
+ help
+ Driver to enable ocp2scp module which transforms ocp interface
+ protocol to scp protocol. In OMAP4, USB PHY is connected via
+ OCP2SCP and in OMAP5, both USB PHY and SATA PHY is connected via
+ OCP2SCP.
+
+config OMAP_INTERCONNECT
+ tristate "OMAP INTERCONNECT DRIVER"
+ depends on ARCH_OMAP2PLUS
+
+ help
+ Driver to enable OMAP interconnect error handling driver.
+
+config ARM_CCI
+ bool "ARM CCI driver support"
+ depends on ARM
+ help
+ Driver supporting the CCI cache coherent interconnect for ARM
+ platforms.
+endmenu
diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile
new file mode 100644
index 000000000..670cea443
--- /dev/null
+++ b/drivers/bus/Makefile
@@ -0,0 +1,11 @@
+#
+# Makefile for the bus drivers.
+#
+
+obj-$(CONFIG_MVEBU_MBUS) += mvebu-mbus.o
+obj-$(CONFIG_OMAP_OCP2SCP) += omap-ocp2scp.o
+
+# Interconnect bus driver for OMAP SoCs.
+obj-$(CONFIG_OMAP_INTERCONNECT) += omap_l3_smx.o omap_l3_noc.o
+# CCI cache coherent interconnect for ARM platforms
+obj-$(CONFIG_ARM_CCI) += arm-cci.o
diff --git a/drivers/bus/arm-cci.c b/drivers/bus/arm-cci.c
new file mode 100644
index 000000000..88fde3b6c
--- /dev/null
+++ b/drivers/bus/arm-cci.c
@@ -0,0 +1,922 @@
+/*
+ * CCI cache coherent interconnect driver
+ *
+ * Copyright (C) 2013 ARM Ltd.
+ * Author: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
+ *
+ * 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 "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/arm-cci.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+
+#include <asm/cacheflush.h>
+#include <asm/irq_regs.h>
+#include <asm/pmu.h>
+#include <asm/smp_plat.h>
+
+#define DRIVER_NAME "CCI"
+
+#define CCI_PORT_CTRL 0x0
+#define CCI_CTRL_STATUS 0xc
+
+#define CCI_ENABLE_SNOOP_REQ 0x1
+#define CCI_ENABLE_DVM_REQ 0x2
+#define CCI_ENABLE_REQ (CCI_ENABLE_SNOOP_REQ | CCI_ENABLE_DVM_REQ)
+
+struct cci_nb_ports {
+ unsigned int nb_ace;
+ unsigned int nb_ace_lite;
+};
+
+enum cci_ace_port_type {
+ ACE_INVALID_PORT = 0x0,
+ ACE_PORT,
+ ACE_LITE_PORT,
+};
+
+struct cci_ace_port {
+ void __iomem *base;
+ unsigned long phys;
+ enum cci_ace_port_type type;
+ struct device_node *dn;
+};
+
+static struct cci_ace_port *ports;
+static unsigned int nb_cci_ports;
+
+static void __iomem *cci_ctrl_base;
+static unsigned long cci_ctrl_phys;
+
+#ifdef CONFIG_HW_PERF_EVENTS
+
+static void __iomem *cci_pmu_base;
+
+#define CCI400_PMCR 0x0100
+
+#define CCI400_PMU_CYCLE_CNTR_BASE 0x0000
+#define CCI400_PMU_CNTR_BASE(idx) (CCI400_PMU_CYCLE_CNTR_BASE + (idx) * 0x1000)
+
+#define CCI400_PMCR_CEN 0x00000001
+#define CCI400_PMCR_RST 0x00000002
+#define CCI400_PMCR_CCR 0x00000004
+#define CCI400_PMCR_CCD 0x00000008
+#define CCI400_PMCR_EX 0x00000010
+#define CCI400_PMCR_DP 0x00000020
+#define CCI400_PMCR_NCNT_MASK 0x0000F800
+#define CCI400_PMCR_NCNT_SHIFT 11
+
+#define CCI400_PMU_EVT_SEL 0x000
+#define CCI400_PMU_CNTR 0x004
+#define CCI400_PMU_CNTR_CTRL 0x008
+#define CCI400_PMU_OVERFLOW 0x00C
+
+#define CCI400_PMU_OVERFLOW_FLAG 1
+
+enum cci400_perf_events {
+ CCI400_PMU_CYCLES = 0xFF
+};
+
+#define CCI400_PMU_EVENT_MASK 0xff
+#define CCI400_PMU_EVENT_SOURCE(event) ((event >> 5) & 0x7)
+#define CCI400_PMU_EVENT_CODE(event) (event & 0x1f)
+
+#define CCI400_PMU_EVENT_SOURCE_S0 0
+#define CCI400_PMU_EVENT_SOURCE_S4 4
+#define CCI400_PMU_EVENT_SOURCE_M0 5
+#define CCI400_PMU_EVENT_SOURCE_M2 7
+
+#define CCI400_PMU_EVENT_SLAVE_MIN 0x0
+#define CCI400_PMU_EVENT_SLAVE_MAX 0x13
+
+#define CCI400_PMU_EVENT_MASTER_MIN 0x14
+#define CCI400_PMU_EVENT_MASTER_MAX 0x1A
+
+#define CCI400_PMU_MAX_HW_EVENTS 5 /* CCI PMU has 4 counters + 1 cycle counter */
+
+#define CCI400_PMU_CYCLE_COUNTER_IDX 0
+#define CCI400_PMU_COUNTER0_IDX 1
+#define CCI400_PMU_COUNTER_LAST(cci_pmu) (CCI400_PMU_CYCLE_COUNTER_IDX + cci_pmu->num_events - 1)
+
+
+static struct perf_event *events[CCI400_PMU_MAX_HW_EVENTS];
+static unsigned long used_mask[BITS_TO_LONGS(CCI400_PMU_MAX_HW_EVENTS)];
+static struct pmu_hw_events cci_hw_events = {
+ .events = events,
+ .used_mask = used_mask,
+};
+
+static int cci_pmu_validate_hw_event(u8 hw_event)
+{
+ u8 ev_source = CCI400_PMU_EVENT_SOURCE(hw_event);
+ u8 ev_code = CCI400_PMU_EVENT_CODE(hw_event);
+
+ if (ev_source <= CCI400_PMU_EVENT_SOURCE_S4 && ev_code <= CCI400_PMU_EVENT_SLAVE_MAX)
+ return hw_event;
+ else if (CCI400_PMU_EVENT_SOURCE_M0 <= ev_source &&
+ ev_source <= CCI400_PMU_EVENT_SOURCE_M2 &&
+ CCI400_PMU_EVENT_MASTER_MIN <= ev_code && ev_code <= CCI400_PMU_EVENT_MASTER_MAX)
+ return hw_event;
+
+ return -EINVAL;
+}
+
+static inline int cci_pmu_counter_is_valid(struct arm_pmu *cci_pmu, int idx)
+{
+ return CCI400_PMU_CYCLE_COUNTER_IDX <= idx && idx <= CCI400_PMU_COUNTER_LAST(cci_pmu);
+}
+
+static inline u32 cci_pmu_read_register(int idx, unsigned int offset)
+{
+ return readl_relaxed(cci_pmu_base + CCI400_PMU_CNTR_BASE(idx) + offset);
+}
+
+static inline void cci_pmu_write_register(u32 value, int idx, unsigned int offset)
+{
+ return writel_relaxed(value, cci_pmu_base + CCI400_PMU_CNTR_BASE(idx) + offset);
+}
+
+static inline void cci_pmu_disable_counter(int idx)
+{
+ cci_pmu_write_register(0, idx, CCI400_PMU_CNTR_CTRL);
+}
+
+static inline void cci_pmu_enable_counter(int idx)
+{
+ cci_pmu_write_register(1, idx, CCI400_PMU_CNTR_CTRL);
+}
+
+static inline void cci_pmu_select_event(int idx, unsigned long event)
+{
+ event &= CCI400_PMU_EVENT_MASK;
+ cci_pmu_write_register(event, idx, CCI400_PMU_EVT_SEL);
+}
+
+static u32 cci_pmu_get_max_counters(void)
+{
+ u32 n_cnts = (readl_relaxed(cci_ctrl_base + CCI400_PMCR) &
+ CCI400_PMCR_NCNT_MASK) >> CCI400_PMCR_NCNT_SHIFT;
+
+ /* add 1 for cycle counter */
+ return n_cnts + 1;
+}
+
+static struct pmu_hw_events *cci_pmu_get_hw_events(void)
+{
+ return &cci_hw_events;
+}
+
+static int cci_pmu_get_event_idx(struct pmu_hw_events *hw, struct perf_event *event)
+{
+ struct arm_pmu *cci_pmu = to_arm_pmu(event->pmu);
+ struct hw_perf_event *hw_event = &event->hw;
+ unsigned long cci_event = hw_event->config_base & CCI400_PMU_EVENT_MASK;
+ int idx;
+
+ if (cci_event == CCI400_PMU_CYCLES) {
+ if (test_and_set_bit(CCI400_PMU_CYCLE_COUNTER_IDX, hw->used_mask))
+ return -EAGAIN;
+
+ return CCI400_PMU_CYCLE_COUNTER_IDX;
+ }
+
+ for (idx = CCI400_PMU_COUNTER0_IDX; idx <= CCI400_PMU_COUNTER_LAST(cci_pmu); ++idx) {
+ if (!test_and_set_bit(idx, hw->used_mask))
+ return idx;
+ }
+
+ /* No counters available */
+ return -EAGAIN;
+}
+
+static int cci_pmu_map_event(struct perf_event *event)
+{
+ int mapping;
+ u8 config = event->attr.config & CCI400_PMU_EVENT_MASK;
+
+ if (event->attr.type < PERF_TYPE_MAX)
+ return -ENOENT;
+
+ /* 0xff is used to represent CCI Cycles */
+ if (config == 0xff)
+ mapping = config;
+ else
+ mapping = cci_pmu_validate_hw_event(config);
+
+ return mapping;
+}
+
+static int cci_pmu_request_irq(struct arm_pmu *cci_pmu, irq_handler_t handler)
+{
+ int irq, err, i = 0;
+ struct platform_device *pmu_device = cci_pmu->plat_device;
+
+ if (unlikely(!pmu_device))
+ return -ENODEV;
+
+ /* CCI exports 6 interrupts - 1 nERRORIRQ + 5 nEVNTCNTOVERFLOW (PMU)
+ nERRORIRQ will be handled by secure firmware on TC2. So we
+ assume that all CCI interrupts listed in the linux device
+ tree are PMU interrupts.
+
+ The following code should then be able to handle different routing
+ of the CCI PMU interrupts.
+ */
+ while ((irq = platform_get_irq(pmu_device, i)) > 0) {
+ err = request_irq(irq, handler, 0, "arm-cci-pmu", cci_pmu);
+ if (err) {
+ dev_err(&pmu_device->dev,
+ "unable to request IRQ%d for ARM CCI PMU counters\n", irq);
+ return err;
+ }
+ i++;
+ }
+
+ return 0;
+}
+
+static irqreturn_t cci_pmu_handle_irq(int irq_num, void *dev)
+{
+ struct arm_pmu *cci_pmu = (struct arm_pmu *)dev;
+ struct pmu_hw_events *events = cci_pmu->get_hw_events();
+ struct perf_sample_data data;
+ struct pt_regs *regs;
+ int idx;
+
+ regs = get_irq_regs();
+
+ /* Iterate over counters and update the corresponding perf events.
+ This should work regardless of whether we have per-counter overflow
+ interrupt or a combined overflow interrupt. */
+ for (idx = CCI400_PMU_CYCLE_COUNTER_IDX; idx <= CCI400_PMU_COUNTER_LAST(cci_pmu); idx++) {
+ struct perf_event *event = events->events[idx];
+ struct hw_perf_event *hw_counter;
+
+ if (!event)
+ continue;
+
+ hw_counter = &event->hw;
+
+ /* Did this counter overflow? */
+ if (!(cci_pmu_read_register(idx, CCI400_PMU_OVERFLOW) & CCI400_PMU_OVERFLOW_FLAG))
+ continue;
+ cci_pmu_write_register(CCI400_PMU_OVERFLOW_FLAG, idx, CCI400_PMU_OVERFLOW);
+
+ armpmu_event_update(event);
+ perf_sample_data_init(&data, 0, hw_counter->last_period);
+ if (!armpmu_event_set_period(event))
+ continue;
+
+ if (perf_event_overflow(event, &data, regs))
+ cci_pmu->disable(event);
+ }
+
+ irq_work_run();
+ return IRQ_HANDLED;
+}
+
+static void cci_pmu_free_irq(struct arm_pmu *cci_pmu)
+{
+ int irq, i = 0;
+ struct platform_device *pmu_device = cci_pmu->plat_device;
+
+ while ((irq = platform_get_irq(pmu_device, i)) > 0) {
+ free_irq(irq, cci_pmu);
+ i++;
+ }
+}
+
+static void cci_pmu_enable_event(struct perf_event *event)
+{
+ unsigned long flags;
+ struct arm_pmu *cci_pmu = to_arm_pmu(event->pmu);
+ struct pmu_hw_events *events = cci_pmu->get_hw_events();
+ struct hw_perf_event *hw_counter = &event->hw;
+ int idx = hw_counter->idx;
+
+ if (unlikely(!cci_pmu_counter_is_valid(cci_pmu, idx))) {
+ dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx);
+ return;
+ }
+
+ raw_spin_lock_irqsave(&events->pmu_lock, flags);
+
+ /* Configure the event to count, unless you are counting cycles */
+ if (idx != CCI400_PMU_CYCLE_COUNTER_IDX)
+ cci_pmu_select_event(idx, hw_counter->config_base);
+
+ cci_pmu_enable_counter(idx);
+
+ raw_spin_unlock_irqrestore(&events->pmu_lock, flags);
+}
+
+static void cci_pmu_disable_event(struct perf_event *event)
+{
+ unsigned long flags;
+ struct arm_pmu *cci_pmu = to_arm_pmu(event->pmu);
+ struct pmu_hw_events *events = cci_pmu->get_hw_events();
+ struct hw_perf_event *hw_counter = &event->hw;
+ int idx = hw_counter->idx;
+
+ if (unlikely(!cci_pmu_counter_is_valid(cci_pmu, idx))) {
+ dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx);
+ return;
+ }
+
+ raw_spin_lock_irqsave(&events->pmu_lock, flags);
+
+ cci_pmu_disable_counter(idx);
+
+ raw_spin_unlock_irqrestore(&events->pmu_lock, flags);
+}
+
+static void cci_pmu_start(struct arm_pmu *cci_pmu)
+{
+ u32 val;
+ unsigned long flags;
+ struct pmu_hw_events *events = cci_pmu->get_hw_events();
+
+ raw_spin_lock_irqsave(&events->pmu_lock, flags);
+
+ /* Enable all the PMU counters. */
+ val = readl(cci_ctrl_base + CCI400_PMCR) | CCI400_PMCR_CEN;
+ writel(val, cci_ctrl_base + CCI400_PMCR);
+
+ raw_spin_unlock_irqrestore(&events->pmu_lock, flags);
+}
+
+static void cci_pmu_stop(struct arm_pmu *cci_pmu)
+{
+ u32 val;
+ unsigned long flags;
+ struct pmu_hw_events *events = cci_pmu->get_hw_events();
+
+ raw_spin_lock_irqsave(&events->pmu_lock, flags);
+
+ /* Disable all the PMU counters. */
+ val = readl(cci_ctrl_base + CCI400_PMCR) & ~CCI400_PMCR_CEN;
+ writel(val, cci_ctrl_base + CCI400_PMCR);
+
+ raw_spin_unlock_irqrestore(&events->pmu_lock, flags);
+}
+
+static u32 cci_pmu_read_counter(struct perf_event *event)
+{
+ struct arm_pmu *cci_pmu = to_arm_pmu(event->pmu);
+ struct hw_perf_event *hw_counter = &event->hw;
+ int idx = hw_counter->idx;
+ u32 value;
+
+ if (unlikely(!cci_pmu_counter_is_valid(cci_pmu, idx))) {
+ dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx);
+ return 0;
+ }
+ value = cci_pmu_read_register(idx, CCI400_PMU_CNTR);
+
+ return value;
+}
+
+static void cci_pmu_write_counter(struct perf_event *event, u32 value)
+{
+ struct arm_pmu *cci_pmu = to_arm_pmu(event->pmu);
+ struct hw_perf_event *hw_counter = &event->hw;
+ int idx = hw_counter->idx;
+
+ if (unlikely(!cci_pmu_counter_is_valid(cci_pmu, idx)))
+ dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx);
+ else
+ cci_pmu_write_register(value, idx, CCI400_PMU_CNTR);
+}
+
+static struct arm_pmu cci_pmu = {
+ .name = DRIVER_NAME,
+ .max_period = (1LLU << 32) - 1,
+ .get_hw_events = cci_pmu_get_hw_events,
+ .get_event_idx = cci_pmu_get_event_idx,
+ .map_event = cci_pmu_map_event,
+ .request_irq = cci_pmu_request_irq,
+ .handle_irq = cci_pmu_handle_irq,
+ .free_irq = cci_pmu_free_irq,
+ .enable = cci_pmu_enable_event,
+ .disable = cci_pmu_disable_event,
+ .start = cci_pmu_start,
+ .stop = cci_pmu_stop,
+ .read_counter = cci_pmu_read_counter,
+ .write_counter = cci_pmu_write_counter,
+};
+
+static int cci_pmu_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ cci_pmu_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(cci_pmu_base))
+ return PTR_ERR(cci_pmu_base);
+
+ cci_pmu.plat_device = pdev;
+ cci_pmu.num_events = cci_pmu_get_max_counters();
+ raw_spin_lock_init(&cci_hw_events.pmu_lock);
+ cpumask_setall(&cci_pmu.valid_cpus);
+
+ return armpmu_register(&cci_pmu, -1);
+}
+
+static const struct of_device_id arm_cci_pmu_matches[] = {
+ {.compatible = "arm,cci-400-pmu"},
+ {},
+};
+
+static struct platform_driver cci_pmu_platform_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = arm_cci_pmu_matches,
+ },
+ .probe = cci_pmu_probe,
+};
+
+static int __init cci_pmu_init(void)
+{
+ if (platform_driver_register(&cci_pmu_platform_driver))
+ WARN(1, "unable to register CCI platform driver\n");
+ return 0;
+}
+
+#else
+
+static int __init cci_pmu_init(void)
+{
+ return 0;
+}
+
+#endif /* CONFIG_HW_PERF_EVENTS */
+
+struct cpu_port {
+ u64 mpidr;
+ u32 port;
+};
+
+/*
+ * Use the port MSB as valid flag, shift can be made dynamic
+ * by computing number of bits required for port indexes.
+ * Code disabling CCI cpu ports runs with D-cache invalidated
+ * and SCTLR bit clear so data accesses must be kept to a minimum
+ * to improve performance; for now shift is left static to
+ * avoid one more data access while disabling the CCI port.
+ */
+#define PORT_VALID_SHIFT 31
+#define PORT_VALID (0x1 << PORT_VALID_SHIFT)
+
+static inline void init_cpu_port(struct cpu_port *port, u32 index, u64 mpidr)
+{
+ port->port = PORT_VALID | index;
+ port->mpidr = mpidr;
+}
+
+static inline bool cpu_port_is_valid(struct cpu_port *port)
+{
+ return !!(port->port & PORT_VALID);
+}
+
+static inline bool cpu_port_match(struct cpu_port *port, u64 mpidr)
+{
+ return port->mpidr == (mpidr & MPIDR_HWID_BITMASK);
+}
+
+static struct cpu_port cpu_port[NR_CPUS];
+
+/**
+ * __cci_ace_get_port - Function to retrieve the port index connected to
+ * a cpu or device.
+ *
+ * @dn: device node of the device to look-up
+ * @type: port type
+ *
+ * Return value:
+ * - CCI port index if success
+ * - -ENODEV if failure
+ */
+static int __cci_ace_get_port(struct device_node *dn, int type)
+{
+ int i;
+ bool ace_match;
+ struct device_node *cci_portn;
+
+ cci_portn = of_parse_phandle(dn, "cci-control-port", 0);
+ for (i = 0; i < nb_cci_ports; i++) {
+ ace_match = ports[i].type == type;
+ if (ace_match && cci_portn == ports[i].dn)
+ return i;
+ }
+ return -ENODEV;
+}
+
+int cci_ace_get_port(struct device_node *dn)
+{
+ return __cci_ace_get_port(dn, ACE_LITE_PORT);
+}
+EXPORT_SYMBOL_GPL(cci_ace_get_port);
+
+static void __init cci_ace_init_ports(void)
+{
+ int port, ac, cpu;
+ u64 hwid;
+ const u32 *cell;
+ struct device_node *cpun, *cpus;
+
+ cpus = of_find_node_by_path("/cpus");
+ if (WARN(!cpus, "Missing cpus node, bailing out\n"))
+ return;
+
+ if (WARN_ON(of_property_read_u32(cpus, "#address-cells", &ac)))
+ ac = of_n_addr_cells(cpus);
+
+ /*
+ * Port index look-up speeds up the function disabling ports by CPU,
+ * since the logical to port index mapping is done once and does
+ * not change after system boot.
+ * The stashed index array is initialized for all possible CPUs
+ * at probe time.
+ */
+ for_each_child_of_node(cpus, cpun) {
+ if (of_node_cmp(cpun->type, "cpu"))
+ continue;
+ cell = of_get_property(cpun, "reg", NULL);
+ if (WARN(!cell, "%s: missing reg property\n", cpun->full_name))
+ continue;
+
+ hwid = of_read_number(cell, ac);
+ cpu = get_logical_index(hwid & MPIDR_HWID_BITMASK);
+
+ if (cpu < 0 || !cpu_possible(cpu))
+ continue;
+ port = __cci_ace_get_port(cpun, ACE_PORT);
+ if (port < 0)
+ continue;
+
+ init_cpu_port(&cpu_port[cpu], port, cpu_logical_map(cpu));
+ }
+
+ for_each_possible_cpu(cpu) {
+ WARN(!cpu_port_is_valid(&cpu_port[cpu]),
+ "CPU %u does not have an associated CCI port\n", cpu);
+ }
+}
+
+/*
+ * Functions to enable/disable a CCI interconnect slave port
+ *
+ * They are called by low-level power management code to disable slave
+ * interfaces snoops and DVM broadcast.
+ * Since they may execute with cache data allocation disabled and
+ * after the caches have been cleaned and invalidated the functions provide
+ * no explicit locking since they may run with D-cache disabled, so normal
+ * cacheable kernel locks based on ldrex/strex may not work.
+ * Locking has to be provided by BSP implementations to ensure proper
+ * operations.
+ */
+
+/**
+ * cci_port_control() - function to control a CCI port
+ *
+ * @port: index of the port to setup
+ * @enable: if true enables the port, if false disables it
+ */
+static void notrace cci_port_control(unsigned int port, bool enable)
+{
+ void __iomem *base = ports[port].base;
+
+ writel_relaxed(enable ? CCI_ENABLE_REQ : 0, base + CCI_PORT_CTRL);
+ /*
+ * This function is called from power down procedures
+ * and must not execute any instruction that might
+ * cause the processor to be put in a quiescent state
+ * (eg wfi). Hence, cpu_relax() can not be added to this
+ * read loop to optimize power, since it might hide possibly
+ * disruptive operations.
+ */
+ while (readl_relaxed(cci_ctrl_base + CCI_CTRL_STATUS) & 0x1);
+}
+
+/**
+ * cci_disable_port_by_cpu() - function to disable a CCI port by CPU
+ * reference
+ *
+ * @mpidr: mpidr of the CPU whose CCI port should be disabled
+ *
+ * Disabling a CCI port for a CPU implies disabling the CCI port
+ * controlling that CPU cluster. Code disabling CPU CCI ports
+ * must make sure that the CPU running the code is the last active CPU
+ * in the cluster ie all other CPUs are quiescent in a low power state.
+ *
+ * Return:
+ * 0 on success
+ * -ENODEV on port look-up failure
+ */
+int notrace cci_disable_port_by_cpu(u64 mpidr)
+{
+ int cpu;
+ bool is_valid;
+ for (cpu = 0; cpu < nr_cpu_ids; cpu++) {
+ is_valid = cpu_port_is_valid(&cpu_port[cpu]);
+ if (is_valid && cpu_port_match(&cpu_port[cpu], mpidr)) {
+ cci_port_control(cpu_port[cpu].port, false);
+ return 0;
+ }
+ }
+ return -ENODEV;
+}
+EXPORT_SYMBOL_GPL(cci_disable_port_by_cpu);
+
+/**
+ * cci_enable_port_for_self() - enable a CCI port for calling CPU
+ *
+ * Enabling a CCI port for the calling CPU implies enabling the CCI
+ * port controlling that CPU's cluster. Caller must make sure that the
+ * CPU running the code is the first active CPU in the cluster and all
+ * other CPUs are quiescent in a low power state or waiting for this CPU
+ * to complete the CCI initialization.
+ *
+ * Because this is called when the MMU is still off and with no stack,
+ * the code must be position independent and ideally rely on callee
+ * clobbered registers only. To achieve this we must code this function
+ * entirely in assembler.
+ *
+ * On success this returns with the proper CCI port enabled. In case of
+ * any failure this never returns as the inability to enable the CCI is
+ * fatal and there is no possible recovery at this stage.
+ */
+asmlinkage void __naked cci_enable_port_for_self(void)
+{
+ asm volatile ("\n"
+ " mrc p15, 0, r0, c0, c0, 5 @ get MPIDR value\n"
+ " and r0, r0, #" __stringify(MPIDR_HWID_BITMASK) "\n"
+ " adr r1, 5f\n"
+ " ldr r2, [r1]\n"
+ " add r1, r1, r2 @ &cpu_port\n"
+ " add ip, r1, %[sizeof_cpu_port]\n"
+ /* Loop over the cpu_port array looking for a matching MPIDR */
+ "1: ldr r2, [r1, %[offsetof_cpu_port_mpidr_lsb]]\n"
+ " cmp r2, r0 @ compare MPIDR \n" " bne 2f\n"
+ /* Found a match, now test port validity */
+ " ldr r3, [r1, %[offsetof_cpu_port_port]]\n"
+ " tst r3, #" __stringify(PORT_VALID) " \n" " bne 3f\n"
+ /* no match, loop with the next cpu_port entry */
+ "2: add r1, r1, %[sizeof_struct_cpu_port]\n"
+ " cmp r1, ip @ done? \n" " blo 1b\n"
+ /* CCI port not found -- cheaply try to stall this CPU */
+ "cci_port_not_found:\n"
+ " wfi \n" " wfe \n" " b cci_port_not_found\n"
+ /* Use matched port index to look up the corresponding ports entry */
+ "3: bic r3, r3, #" __stringify(PORT_VALID) "\n"
+ " adr r0, 6f\n"
+ " ldmia r0, {r1, r2}\n"
+ " sub r1, r1, r0 @ virt - phys\n"
+ " ldr r0, [r0, r2] @ *(&ports)\n"
+ " mov r2, %[sizeof_struct_ace_port]\n"
+ " mla r0, r2, r3, r0 @ &ports[index]\n"
+ " sub r0, r0, r1 @ virt_to_phys()\n"
+ /* Enable the CCI port */
+ " ldr r0, [r0, %[offsetof_port_phys]]\n"
+ " mov r3, #" __stringify(CCI_ENABLE_REQ) "\n"
+ " str r3, [r0, #" __stringify(CCI_PORT_CTRL) "]\n"
+ /* poll the status reg for completion */
+ " adr r1, 7f\n"
+ " ldr r0, [r1]\n"
+ " ldr r0, [r0, r1] @ cci_ctrl_base\n"
+ "4: ldr r1, [r0, #" __stringify(CCI_CTRL_STATUS)
+ "] \n" " tst r1, #1\n"
+ " bne 4b\n"
+ " mov r0, #0\n"
+ " bx lr\n"
+ " .align 2\n"
+ "5: .word cpu_port - .\n"
+ "6: .word .\n"
+ " .word ports - 6b\n"
+ "7: .word cci_ctrl_phys - . \n" : :
+ [sizeof_cpu_port] "i"(sizeof(cpu_port)),
+#ifndef __ARMEB__
+ [offsetof_cpu_port_mpidr_lsb] "i"(offsetof(struct cpu_port, mpidr)),
+#else
+ [offsetof_cpu_port_mpidr_lsb] "i"(offsetof(struct cpu_port, mpidr) + 4),
+#endif
+ [offsetof_cpu_port_port] "i"(offsetof(struct cpu_port, port)),
+ [sizeof_struct_cpu_port] "i"(sizeof(struct cpu_port)),
+ [sizeof_struct_ace_port] "i"(sizeof(struct cci_ace_port)),
+ [offsetof_port_phys] "i"(offsetof(struct cci_ace_port, phys)));
+
+ unreachable();
+}
+
+/**
+ * __cci_control_port_by_device() - function to control a CCI port by device
+ * reference
+ *
+ * @dn: device node pointer of the device whose CCI port should be
+ * controlled
+ * @enable: if true enables the port, if false disables it
+ *
+ * Return:
+ * 0 on success
+ * -ENODEV on port look-up failure
+ */
+int notrace __cci_control_port_by_device(struct device_node *dn, bool enable)
+{
+ int port;
+
+ if (!dn)
+ return -ENODEV;
+
+ port = __cci_ace_get_port(dn, ACE_LITE_PORT);
+ if (WARN_ONCE(port < 0, "node %s ACE lite port look-up failure\n", dn->full_name))
+ return -ENODEV;
+ cci_port_control(port, enable);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__cci_control_port_by_device);
+
+/**
+ * __cci_control_port_by_index() - function to control a CCI port by port index
+ *
+ * @port: port index previously retrieved with cci_ace_get_port()
+ * @enable: if true enables the port, if false disables it
+ *
+ * Return:
+ * 0 on success
+ * -ENODEV on port index out of range
+ * -EPERM if operation carried out on an ACE PORT
+ */
+int notrace __cci_control_port_by_index(u32 port, bool enable)
+{
+ if (port >= nb_cci_ports || ports[port].type == ACE_INVALID_PORT)
+ return -ENODEV;
+ /*
+ * CCI control for ports connected to CPUS is extremely fragile
+ * and must be made to go through a specific and controlled
+ * interface (ie cci_disable_port_by_cpu(); control by general purpose
+ * indexing is therefore disabled for ACE ports.
+ */
+ if (ports[port].type == ACE_PORT)
+ return -EPERM;
+
+ cci_port_control(port, enable);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(__cci_control_port_by_index);
+
+static const struct cci_nb_ports cci400_ports = {
+ .nb_ace = 2,
+ .nb_ace_lite = 3
+};
+
+static const struct of_device_id arm_cci_matches[] = {
+ {.compatible = "arm,cci-400", .data = &cci400_ports},
+ {},
+};
+
+static const struct of_device_id arm_cci_ctrl_if_matches[] = {
+ {.compatible = "arm,cci-400-ctrl-if",},
+ {},
+};
+
+static int __init cci_probe(void)
+{
+ struct cci_nb_ports const *cci_config;
+ int ret, i, nb_ace = 0, nb_ace_lite = 0;
+ struct device_node *np, *cp;
+ struct resource res;
+ const char *match_str;
+ bool is_ace;
+
+ np = of_find_matching_node(NULL, arm_cci_matches);
+ if (!np)
+ return -ENODEV;
+
+ cci_config = of_match_node(arm_cci_matches, np)->data;
+ if (!cci_config)
+ return -ENODEV;
+
+ nb_cci_ports = cci_config->nb_ace + cci_config->nb_ace_lite;
+
+ ports = kcalloc(sizeof(*ports), nb_cci_ports, GFP_KERNEL);
+ if (!ports)
+ return -ENOMEM;
+
+ ret = of_address_to_resource(np, 0, &res);
+ if (!ret) {
+ cci_ctrl_base = ioremap(res.start, resource_size(&res));
+ cci_ctrl_phys = res.start;
+ }
+ if (ret || !cci_ctrl_base) {
+ WARN(1, "unable to ioremap CCI ctrl\n");
+ ret = -ENXIO;
+ goto memalloc_err;
+ }
+
+ for_each_child_of_node(np, cp) {
+ if (!of_match_node(arm_cci_ctrl_if_matches, cp))
+ continue;
+
+ i = nb_ace + nb_ace_lite;
+
+ if (i >= nb_cci_ports)
+ break;
+
+ if (of_property_read_string(cp, "interface-type", &match_str)) {
+ WARN(1, "node %s missing interface-type property\n", cp->full_name);
+ continue;
+ }
+ is_ace = strcmp(match_str, "ace") == 0;
+ if (!is_ace && strcmp(match_str, "ace-lite")) {
+ WARN(1, "node %s containing invalid interface-type property, skipping it\n",
+ cp->full_name);
+ continue;
+ }
+
+ ret = of_address_to_resource(cp, 0, &res);
+ if (!ret) {
+ ports[i].base = ioremap(res.start, resource_size(&res));
+ ports[i].phys = res.start;
+ }
+ if (ret || !ports[i].base) {
+ WARN(1, "unable to ioremap CCI port %d\n", i);
+ continue;
+ }
+
+ if (is_ace) {
+ if (WARN_ON(nb_ace >= cci_config->nb_ace))
+ continue;
+ ports[i].type = ACE_PORT;
+ ++nb_ace;
+ } else {
+ if (WARN_ON(nb_ace_lite >= cci_config->nb_ace_lite))
+ continue;
+ ports[i].type = ACE_LITE_PORT;
+ ++nb_ace_lite;
+ }
+ ports[i].dn = cp;
+ }
+
+ /* initialize a stashed array of ACE ports to speed-up look-up */
+ cci_ace_init_ports();
+
+ /*
+ * Multi-cluster systems may need this data when non-coherent, during
+ * cluster power-up/power-down. Make sure it reaches main memory.
+ */
+ sync_cache_w(&cci_ctrl_base);
+ sync_cache_w(&cci_ctrl_phys);
+ sync_cache_w(&ports);
+ sync_cache_w(&cpu_port);
+ __sync_cache_range_w(ports, sizeof(*ports) * nb_cci_ports);
+ pr_info("ARM CCI driver probed\n");
+ return 0;
+
+ memalloc_err:
+
+ kfree(ports);
+ return ret;
+}
+
+static int cci_init_status = -EAGAIN;
+static DEFINE_MUTEX(cci_probing);
+
+static int __init cci_init(void)
+{
+ if (cci_init_status != -EAGAIN)
+ return cci_init_status;
+
+ mutex_lock(&cci_probing);
+ if (cci_init_status == -EAGAIN)
+ cci_init_status = cci_probe();
+ mutex_unlock(&cci_probing);
+ return cci_init_status;
+}
+
+/*
+ * To sort out early init calls ordering a helper function is provided to
+ * check if the CCI driver has beed initialized. Function check if the driver
+ * has been initialized, if not it calls the init function that probes
+ * the driver and updates the return value.
+ */
+bool __init cci_probed(void)
+{
+ return cci_init() == 0;
+}
+EXPORT_SYMBOL_GPL(cci_probed);
+
+early_initcall(cci_init);
+core_initcall(cci_pmu_init);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ARM CCI support");
diff --git a/drivers/bus/mvebu-mbus.c b/drivers/bus/mvebu-mbus.c
new file mode 100644
index 000000000..5dcc8305a
--- /dev/null
+++ b/drivers/bus/mvebu-mbus.c
@@ -0,0 +1,864 @@
+/*
+ * Address map functions for Marvell EBU SoCs (Kirkwood, Armada
+ * 370/XP, Dove, Orion5x and MV78xx0)
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ *
+ * The Marvell EBU SoCs have a configurable physical address space:
+ * the physical address at which certain devices (PCIe, NOR, NAND,
+ * etc.) sit can be configured. The configuration takes place through
+ * two sets of registers:
+ *
+ * - One to configure the access of the CPU to the devices. Depending
+ * on the families, there are between 8 and 20 configurable windows,
+ * each can be use to create a physical memory window that maps to a
+ * specific device. Devices are identified by a tuple (target,
+ * attribute).
+ *
+ * - One to configure the access to the CPU to the SDRAM. There are
+ * either 2 (for Dove) or 4 (for other families) windows to map the
+ * SDRAM into the physical address space.
+ *
+ * This driver:
+ *
+ * - Reads out the SDRAM address decoding windows at initialization
+ * time, and fills the mvebu_mbus_dram_info structure with these
+ * informations. The exported function mv_mbus_dram_info() allow
+ * device drivers to get those informations related to the SDRAM
+ * address decoding windows. This is because devices also have their
+ * own windows (configured through registers that are part of each
+ * device register space), and therefore the drivers for Marvell
+ * devices have to configure those device -> SDRAM windows to ensure
+ * that DMA works properly.
+ *
+ * - Provides an API for platform code or device drivers to
+ * dynamically add or remove address decoding windows for the CPU ->
+ * device accesses. This API is mvebu_mbus_add_window(),
+ * mvebu_mbus_add_window_remap_flags() and
+ * mvebu_mbus_del_window(). Since the (target, attribute) values
+ * differ from one SoC family to another, the API uses a 'const char
+ * *' string to identify devices, and this driver is responsible for
+ * knowing the mapping between the name of a device and its
+ * corresponding (target, attribute) in the current SoC family.
+ *
+ * - Provides a debugfs interface in /sys/kernel/debug/mvebu-mbus/ to
+ * see the list of CPU -> SDRAM windows and their configuration
+ * (file 'sdram') and the list of CPU -> devices windows and their
+ * configuration (file 'devices').
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/mbus.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/debugfs.h>
+
+/*
+ * DDR target is the same on all platforms.
+ */
+#define TARGET_DDR 0
+
+/*
+ * CPU Address Decode Windows registers
+ */
+#define WIN_CTRL_OFF 0x0000
+#define WIN_CTRL_ENABLE BIT(0)
+#define WIN_CTRL_TGT_MASK 0xf0
+#define WIN_CTRL_TGT_SHIFT 4
+#define WIN_CTRL_ATTR_MASK 0xff00
+#define WIN_CTRL_ATTR_SHIFT 8
+#define WIN_CTRL_SIZE_MASK 0xffff0000
+#define WIN_CTRL_SIZE_SHIFT 16
+#define WIN_BASE_OFF 0x0004
+#define WIN_BASE_LOW 0xffff0000
+#define WIN_BASE_HIGH 0xf
+#define WIN_REMAP_LO_OFF 0x0008
+#define WIN_REMAP_LOW 0xffff0000
+#define WIN_REMAP_HI_OFF 0x000c
+
+#define ATTR_HW_COHERENCY (0x1 << 4)
+
+#define DDR_BASE_CS_OFF(n) (0x0000 + ((n) << 3))
+#define DDR_BASE_CS_HIGH_MASK 0xf
+#define DDR_BASE_CS_LOW_MASK 0xff000000
+#define DDR_SIZE_CS_OFF(n) (0x0004 + ((n) << 3))
+#define DDR_SIZE_ENABLED BIT(0)
+#define DDR_SIZE_CS_MASK 0x1c
+#define DDR_SIZE_CS_SHIFT 2
+#define DDR_SIZE_MASK 0xff000000
+
+#define DOVE_DDR_BASE_CS_OFF(n) ((n) << 4)
+
+struct mvebu_mbus_mapping {
+ const char *name;
+ u8 target;
+ u8 attr;
+ u8 attrmask;
+};
+
+/*
+ * Masks used for the 'attrmask' field of mvebu_mbus_mapping. They
+ * allow to get the real attribute value, discarding the special bits
+ * used to select a PCI MEM region or a PCI WA region. This allows the
+ * debugfs code to reverse-match the name of a device from its
+ * target/attr values.
+ *
+ * For all devices except PCI, all bits of 'attr' must be
+ * considered. For most SoCs, only bit 3 should be ignored (it allows
+ * to select between PCI MEM and PCI I/O). On Orion5x however, there
+ * is the special bit 5 to select a PCI WA region.
+ */
+#define MAPDEF_NOMASK 0xff
+#define MAPDEF_PCIMASK 0xf7
+#define MAPDEF_ORIONPCIMASK 0xd7
+
+/* Macro used to define one mvebu_mbus_mapping entry */
+#define MAPDEF(__n, __t, __a, __m) \
+ { .name = __n, .target = __t, .attr = __a, .attrmask = __m }
+
+struct mvebu_mbus_state;
+
+struct mvebu_mbus_soc_data {
+ unsigned int num_wins;
+ unsigned int num_remappable_wins;
+ unsigned int (*win_cfg_offset)(const int win);
+ void (*setup_cpu_target)(struct mvebu_mbus_state *s);
+ int (*show_cpu_target)(struct mvebu_mbus_state *s,
+ struct seq_file *seq, void *v);
+ const struct mvebu_mbus_mapping *map;
+};
+
+struct mvebu_mbus_state {
+ void __iomem *mbuswins_base;
+ void __iomem *sdramwins_base;
+ struct dentry *debugfs_root;
+ struct dentry *debugfs_sdram;
+ struct dentry *debugfs_devs;
+ const struct mvebu_mbus_soc_data *soc;
+ int hw_io_coherency;
+};
+
+static struct mvebu_mbus_state mbus_state;
+
+static struct mbus_dram_target_info mvebu_mbus_dram_info;
+const struct mbus_dram_target_info *mv_mbus_dram_info(void)
+{
+ return &mvebu_mbus_dram_info;
+}
+EXPORT_SYMBOL_GPL(mv_mbus_dram_info);
+
+/*
+ * Functions to manipulate the address decoding windows
+ */
+
+static void mvebu_mbus_read_window(struct mvebu_mbus_state *mbus,
+ int win, int *enabled, u64 *base,
+ u32 *size, u8 *target, u8 *attr,
+ u64 *remap)
+{
+ void __iomem *addr = mbus->mbuswins_base +
+ mbus->soc->win_cfg_offset(win);
+ u32 basereg = readl(addr + WIN_BASE_OFF);
+ u32 ctrlreg = readl(addr + WIN_CTRL_OFF);
+
+ if (!(ctrlreg & WIN_CTRL_ENABLE)) {
+ *enabled = 0;
+ return;
+ }
+
+ *enabled = 1;
+ *base = ((u64)basereg & WIN_BASE_HIGH) << 32;
+ *base |= (basereg & WIN_BASE_LOW);
+ *size = (ctrlreg | ~WIN_CTRL_SIZE_MASK) + 1;
+
+ if (target)
+ *target = (ctrlreg & WIN_CTRL_TGT_MASK) >> WIN_CTRL_TGT_SHIFT;
+
+ if (attr)
+ *attr = (ctrlreg & WIN_CTRL_ATTR_MASK) >> WIN_CTRL_ATTR_SHIFT;
+
+ if (remap) {
+ if (win < mbus->soc->num_remappable_wins) {
+ u32 remap_low = readl(addr + WIN_REMAP_LO_OFF);
+ u32 remap_hi = readl(addr + WIN_REMAP_HI_OFF);
+ *remap = ((u64)remap_hi << 32) | remap_low;
+ } else
+ *remap = 0;
+ }
+}
+
+static void mvebu_mbus_disable_window(struct mvebu_mbus_state *mbus,
+ int win)
+{
+ void __iomem *addr;
+
+ addr = mbus->mbuswins_base + mbus->soc->win_cfg_offset(win);
+
+ writel(0, addr + WIN_BASE_OFF);
+ writel(0, addr + WIN_CTRL_OFF);
+ if (win < mbus->soc->num_remappable_wins) {
+ writel(0, addr + WIN_REMAP_LO_OFF);
+ writel(0, addr + WIN_REMAP_HI_OFF);
+ }
+}
+
+/* Checks whether the given window number is available */
+static int mvebu_mbus_window_is_free(struct mvebu_mbus_state *mbus,
+ const int win)
+{
+ void __iomem *addr = mbus->mbuswins_base +
+ mbus->soc->win_cfg_offset(win);
+ u32 ctrl = readl(addr + WIN_CTRL_OFF);
+ return !(ctrl & WIN_CTRL_ENABLE);
+}
+
+/*
+ * Checks whether the given (base, base+size) area doesn't overlap an
+ * existing region
+ */
+static int mvebu_mbus_window_conflicts(struct mvebu_mbus_state *mbus,
+ phys_addr_t base, size_t size,
+ u8 target, u8 attr)
+{
+ u64 end = (u64)base + size;
+ int win;
+
+ for (win = 0; win < mbus->soc->num_wins; win++) {
+ u64 wbase, wend;
+ u32 wsize;
+ u8 wtarget, wattr;
+ int enabled;
+
+ mvebu_mbus_read_window(mbus, win,
+ &enabled, &wbase, &wsize,
+ &wtarget, &wattr, NULL);
+
+ if (!enabled)
+ continue;
+
+ wend = wbase + wsize;
+
+ /*
+ * Check if the current window overlaps with the
+ * proposed physical range
+ */
+ if ((u64)base < wend && end > wbase)
+ return 0;
+ }
+
+ return 1;
+}
+
+static int mvebu_mbus_find_window(struct mvebu_mbus_state *mbus,
+ phys_addr_t base, size_t size)
+{
+ int win;
+
+ for (win = 0; win < mbus->soc->num_wins; win++) {
+ u64 wbase;
+ u32 wsize;
+ int enabled;
+
+ mvebu_mbus_read_window(mbus, win,
+ &enabled, &wbase, &wsize,
+ NULL, NULL, NULL);
+
+ if (!enabled)
+ continue;
+
+ if (base == wbase && size == wsize)
+ return win;
+ }
+
+ return -ENODEV;
+}
+
+static int mvebu_mbus_setup_window(struct mvebu_mbus_state *mbus,
+ int win, phys_addr_t base, size_t size,
+ phys_addr_t remap, u8 target,
+ u8 attr)
+{
+ void __iomem *addr = mbus->mbuswins_base +
+ mbus->soc->win_cfg_offset(win);
+ u32 ctrl, remap_addr;
+
+ ctrl = ((size - 1) & WIN_CTRL_SIZE_MASK) |
+ (attr << WIN_CTRL_ATTR_SHIFT) |
+ (target << WIN_CTRL_TGT_SHIFT) |
+ WIN_CTRL_ENABLE;
+
+ writel(base & WIN_BASE_LOW, addr + WIN_BASE_OFF);
+ writel(ctrl, addr + WIN_CTRL_OFF);
+ if (win < mbus->soc->num_remappable_wins) {
+ if (remap == MVEBU_MBUS_NO_REMAP)
+ remap_addr = base;
+ else
+ remap_addr = remap;
+ writel(remap_addr & WIN_REMAP_LOW, addr + WIN_REMAP_LO_OFF);
+ writel(0, addr + WIN_REMAP_HI_OFF);
+ }
+
+ return 0;
+}
+
+static int mvebu_mbus_alloc_window(struct mvebu_mbus_state *mbus,
+ phys_addr_t base, size_t size,
+ phys_addr_t remap, u8 target,
+ u8 attr)
+{
+ int win;
+
+ if (remap == MVEBU_MBUS_NO_REMAP) {
+ for (win = mbus->soc->num_remappable_wins;
+ win < mbus->soc->num_wins; win++)
+ if (mvebu_mbus_window_is_free(mbus, win))
+ return mvebu_mbus_setup_window(mbus, win, base,
+ size, remap,
+ target, attr);
+ }
+
+
+ for (win = 0; win < mbus->soc->num_wins; win++)
+ if (mvebu_mbus_window_is_free(mbus, win))
+ return mvebu_mbus_setup_window(mbus, win, base, size,
+ remap, target, attr);
+
+ return -ENOMEM;
+}
+
+/*
+ * Debugfs debugging
+ */
+
+/* Common function used for Dove, Kirkwood, Armada 370/XP and Orion 5x */
+static int mvebu_sdram_debug_show_orion(struct mvebu_mbus_state *mbus,
+ struct seq_file *seq, void *v)
+{
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ u32 basereg = readl(mbus->sdramwins_base + DDR_BASE_CS_OFF(i));
+ u32 sizereg = readl(mbus->sdramwins_base + DDR_SIZE_CS_OFF(i));
+ u64 base;
+ u32 size;
+
+ if (!(sizereg & DDR_SIZE_ENABLED)) {
+ seq_printf(seq, "[%d] disabled\n", i);
+ continue;
+ }
+
+ base = ((u64)basereg & DDR_BASE_CS_HIGH_MASK) << 32;
+ base |= basereg & DDR_BASE_CS_LOW_MASK;
+ size = (sizereg | ~DDR_SIZE_MASK);
+
+ seq_printf(seq, "[%d] %016llx - %016llx : cs%d\n",
+ i, (unsigned long long)base,
+ (unsigned long long)base + size + 1,
+ (sizereg & DDR_SIZE_CS_MASK) >> DDR_SIZE_CS_SHIFT);
+ }
+
+ return 0;
+}
+
+/* Special function for Dove */
+static int mvebu_sdram_debug_show_dove(struct mvebu_mbus_state *mbus,
+ struct seq_file *seq, void *v)
+{
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ u32 map = readl(mbus->sdramwins_base + DOVE_DDR_BASE_CS_OFF(i));
+ u64 base;
+ u32 size;
+
+ if (!(map & 1)) {
+ seq_printf(seq, "[%d] disabled\n", i);
+ continue;
+ }
+
+ base = map & 0xff800000;
+ size = 0x100000 << (((map & 0x000f0000) >> 16) - 4);
+
+ seq_printf(seq, "[%d] %016llx - %016llx : cs%d\n",
+ i, (unsigned long long)base,
+ (unsigned long long)base + size, i);
+ }
+
+ return 0;
+}
+
+static int mvebu_sdram_debug_show(struct seq_file *seq, void *v)
+{
+ struct mvebu_mbus_state *mbus = &mbus_state;
+ return mbus->soc->show_cpu_target(mbus, seq, v);
+}
+
+static int mvebu_sdram_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mvebu_sdram_debug_show, inode->i_private);
+}
+
+static const struct file_operations mvebu_sdram_debug_fops = {
+ .open = mvebu_sdram_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int mvebu_devs_debug_show(struct seq_file *seq, void *v)
+{
+ struct mvebu_mbus_state *mbus = &mbus_state;
+ int win;
+
+ for (win = 0; win < mbus->soc->num_wins; win++) {
+ u64 wbase, wremap;
+ u32 wsize;
+ u8 wtarget, wattr;
+ int enabled, i;
+ const char *name;
+
+ mvebu_mbus_read_window(mbus, win,
+ &enabled, &wbase, &wsize,
+ &wtarget, &wattr, &wremap);
+
+ if (!enabled) {
+ seq_printf(seq, "[%02d] disabled\n", win);
+ continue;
+ }
+
+
+ for (i = 0; mbus->soc->map[i].name; i++)
+ if (mbus->soc->map[i].target == wtarget &&
+ mbus->soc->map[i].attr ==
+ (wattr & mbus->soc->map[i].attrmask))
+ break;
+
+ name = mbus->soc->map[i].name ?: "unknown";
+
+ seq_printf(seq, "[%02d] %016llx - %016llx : %s",
+ win, (unsigned long long)wbase,
+ (unsigned long long)(wbase + wsize), name);
+
+ if (win < mbus->soc->num_remappable_wins) {
+ seq_printf(seq, " (remap %016llx)\n",
+ (unsigned long long)wremap);
+ } else
+ seq_printf(seq, "\n");
+ }
+
+ return 0;
+}
+
+static int mvebu_devs_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mvebu_devs_debug_show, inode->i_private);
+}
+
+static const struct file_operations mvebu_devs_debug_fops = {
+ .open = mvebu_devs_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/*
+ * SoC-specific functions and definitions
+ */
+
+static unsigned int orion_mbus_win_offset(int win)
+{
+ return win << 4;
+}
+
+static unsigned int armada_370_xp_mbus_win_offset(int win)
+{
+ /* The register layout is a bit annoying and the below code
+ * tries to cope with it.
+ * - At offset 0x0, there are the registers for the first 8
+ * windows, with 4 registers of 32 bits per window (ctrl,
+ * base, remap low, remap high)
+ * - Then at offset 0x80, there is a hole of 0x10 bytes for
+ * the internal registers base address and internal units
+ * sync barrier register.
+ * - Then at offset 0x90, there the registers for 12
+ * windows, with only 2 registers of 32 bits per window
+ * (ctrl, base).
+ */
+ if (win < 8)
+ return win << 4;
+ else
+ return 0x90 + ((win - 8) << 3);
+}
+
+static unsigned int mv78xx0_mbus_win_offset(int win)
+{
+ if (win < 8)
+ return win << 4;
+ else
+ return 0x900 + ((win - 8) << 4);
+}
+
+static void __init
+mvebu_mbus_default_setup_cpu_target(struct mvebu_mbus_state *mbus)
+{
+ int i;
+ int cs;
+
+ mvebu_mbus_dram_info.mbus_dram_target_id = TARGET_DDR;
+
+ for (i = 0, cs = 0; i < 4; i++) {
+ u32 base = readl(mbus->sdramwins_base + DDR_BASE_CS_OFF(i));
+ u32 size = readl(mbus->sdramwins_base + DDR_SIZE_CS_OFF(i));
+
+ /*
+ * We only take care of entries for which the chip
+ * select is enabled, and that don't have high base
+ * address bits set (devices can only access the first
+ * 32 bits of the memory).
+ */
+ if ((size & DDR_SIZE_ENABLED) &&
+ !(base & DDR_BASE_CS_HIGH_MASK)) {
+ struct mbus_dram_window *w;
+
+ w = &mvebu_mbus_dram_info.cs[cs++];
+ w->cs_index = i;
+ w->mbus_attr = 0xf & ~(1 << i);
+ if (mbus->hw_io_coherency)
+ w->mbus_attr |= ATTR_HW_COHERENCY;
+ w->base = base & DDR_BASE_CS_LOW_MASK;
+ w->size = (size | ~DDR_SIZE_MASK) + 1;
+ }
+ }
+ mvebu_mbus_dram_info.num_cs = cs;
+}
+
+static void __init
+mvebu_mbus_dove_setup_cpu_target(struct mvebu_mbus_state *mbus)
+{
+ int i;
+ int cs;
+
+ mvebu_mbus_dram_info.mbus_dram_target_id = TARGET_DDR;
+
+ for (i = 0, cs = 0; i < 2; i++) {
+ u32 map = readl(mbus->sdramwins_base + DOVE_DDR_BASE_CS_OFF(i));
+
+ /*
+ * Chip select enabled?
+ */
+ if (map & 1) {
+ struct mbus_dram_window *w;
+
+ w = &mvebu_mbus_dram_info.cs[cs++];
+ w->cs_index = i;
+ w->mbus_attr = 0; /* CS address decoding done inside */
+ /* the DDR controller, no need to */
+ /* provide attributes */
+ w->base = map & 0xff800000;
+ w->size = 0x100000 << (((map & 0x000f0000) >> 16) - 4);
+ }
+ }
+
+ mvebu_mbus_dram_info.num_cs = cs;
+}
+
+static const struct mvebu_mbus_mapping armada_370_map[] = {
+ MAPDEF("bootrom", 1, 0xe0, MAPDEF_NOMASK),
+ MAPDEF("devbus-boot", 1, 0x2f, MAPDEF_NOMASK),
+ MAPDEF("devbus-cs0", 1, 0x3e, MAPDEF_NOMASK),
+ MAPDEF("devbus-cs1", 1, 0x3d, MAPDEF_NOMASK),
+ MAPDEF("devbus-cs2", 1, 0x3b, MAPDEF_NOMASK),
+ MAPDEF("devbus-cs3", 1, 0x37, MAPDEF_NOMASK),
+ MAPDEF("pcie0.0", 4, 0xe0, MAPDEF_PCIMASK),
+ MAPDEF("pcie1.0", 8, 0xe0, MAPDEF_PCIMASK),
+ {},
+};
+
+static const struct mvebu_mbus_soc_data armada_370_mbus_data = {
+ .num_wins = 20,
+ .num_remappable_wins = 8,
+ .win_cfg_offset = armada_370_xp_mbus_win_offset,
+ .setup_cpu_target = mvebu_mbus_default_setup_cpu_target,
+ .show_cpu_target = mvebu_sdram_debug_show_orion,
+ .map = armada_370_map,
+};
+
+static const struct mvebu_mbus_mapping armada_xp_map[] = {
+ MAPDEF("bootrom", 1, 0x1d, MAPDEF_NOMASK),
+ MAPDEF("devbus-boot", 1, 0x2f, MAPDEF_NOMASK),
+ MAPDEF("devbus-cs0", 1, 0x3e, MAPDEF_NOMASK),
+ MAPDEF("devbus-cs1", 1, 0x3d, MAPDEF_NOMASK),
+ MAPDEF("devbus-cs2", 1, 0x3b, MAPDEF_NOMASK),
+ MAPDEF("devbus-cs3", 1, 0x37, MAPDEF_NOMASK),
+ MAPDEF("pcie0.0", 4, 0xe0, MAPDEF_PCIMASK),
+ MAPDEF("pcie0.1", 4, 0xd0, MAPDEF_PCIMASK),
+ MAPDEF("pcie0.2", 4, 0xb0, MAPDEF_PCIMASK),
+ MAPDEF("pcie0.3", 4, 0x70, MAPDEF_PCIMASK),
+ MAPDEF("pcie1.0", 8, 0xe0, MAPDEF_PCIMASK),
+ MAPDEF("pcie1.1", 8, 0xd0, MAPDEF_PCIMASK),
+ MAPDEF("pcie1.2", 8, 0xb0, MAPDEF_PCIMASK),
+ MAPDEF("pcie1.3", 8, 0x70, MAPDEF_PCIMASK),
+ MAPDEF("pcie2.0", 4, 0xf0, MAPDEF_PCIMASK),
+ MAPDEF("pcie3.0", 8, 0xf0, MAPDEF_PCIMASK),
+ {},
+};
+
+static const struct mvebu_mbus_soc_data armada_xp_mbus_data = {
+ .num_wins = 20,
+ .num_remappable_wins = 8,
+ .win_cfg_offset = armada_370_xp_mbus_win_offset,
+ .setup_cpu_target = mvebu_mbus_default_setup_cpu_target,
+ .show_cpu_target = mvebu_sdram_debug_show_orion,
+ .map = armada_xp_map,
+};
+
+static const struct mvebu_mbus_mapping kirkwood_map[] = {
+ MAPDEF("pcie0.0", 4, 0xe0, MAPDEF_PCIMASK),
+ MAPDEF("pcie1.0", 4, 0xd0, MAPDEF_PCIMASK),
+ MAPDEF("sram", 3, 0x01, MAPDEF_NOMASK),
+ MAPDEF("nand", 1, 0x2f, MAPDEF_NOMASK),
+ {},
+};
+
+static const struct mvebu_mbus_soc_data kirkwood_mbus_data = {
+ .num_wins = 8,
+ .num_remappable_wins = 4,
+ .win_cfg_offset = orion_mbus_win_offset,
+ .setup_cpu_target = mvebu_mbus_default_setup_cpu_target,
+ .show_cpu_target = mvebu_sdram_debug_show_orion,
+ .map = kirkwood_map,
+};
+
+static const struct mvebu_mbus_mapping dove_map[] = {
+ MAPDEF("pcie0.0", 0x4, 0xe0, MAPDEF_PCIMASK),
+ MAPDEF("pcie1.0", 0x8, 0xe0, MAPDEF_PCIMASK),
+ MAPDEF("cesa", 0x3, 0x01, MAPDEF_NOMASK),
+ MAPDEF("bootrom", 0x1, 0xfd, MAPDEF_NOMASK),
+ MAPDEF("scratchpad", 0xd, 0x0, MAPDEF_NOMASK),
+ {},
+};
+
+static const struct mvebu_mbus_soc_data dove_mbus_data = {
+ .num_wins = 8,
+ .num_remappable_wins = 4,
+ .win_cfg_offset = orion_mbus_win_offset,
+ .setup_cpu_target = mvebu_mbus_dove_setup_cpu_target,
+ .show_cpu_target = mvebu_sdram_debug_show_dove,
+ .map = dove_map,
+};
+
+static const struct mvebu_mbus_mapping orion5x_map[] = {
+ MAPDEF("pcie0.0", 4, 0x51, MAPDEF_ORIONPCIMASK),
+ MAPDEF("pci0.0", 3, 0x51, MAPDEF_ORIONPCIMASK),
+ MAPDEF("devbus-boot", 1, 0x0f, MAPDEF_NOMASK),
+ MAPDEF("devbus-cs0", 1, 0x1e, MAPDEF_NOMASK),
+ MAPDEF("devbus-cs1", 1, 0x1d, MAPDEF_NOMASK),
+ MAPDEF("devbus-cs2", 1, 0x1b, MAPDEF_NOMASK),
+ MAPDEF("sram", 0, 0x00, MAPDEF_NOMASK),
+ {},
+};
+
+/*
+ * Some variants of Orion5x have 4 remappable windows, some other have
+ * only two of them.
+ */
+static const struct mvebu_mbus_soc_data orion5x_4win_mbus_data = {
+ .num_wins = 8,
+ .num_remappable_wins = 4,
+ .win_cfg_offset = orion_mbus_win_offset,
+ .setup_cpu_target = mvebu_mbus_default_setup_cpu_target,
+ .show_cpu_target = mvebu_sdram_debug_show_orion,
+ .map = orion5x_map,
+};
+
+static const struct mvebu_mbus_soc_data orion5x_2win_mbus_data = {
+ .num_wins = 8,
+ .num_remappable_wins = 2,
+ .win_cfg_offset = orion_mbus_win_offset,
+ .setup_cpu_target = mvebu_mbus_default_setup_cpu_target,
+ .show_cpu_target = mvebu_sdram_debug_show_orion,
+ .map = orion5x_map,
+};
+
+static const struct mvebu_mbus_mapping mv78xx0_map[] = {
+ MAPDEF("pcie0.0", 4, 0xe0, MAPDEF_PCIMASK),
+ MAPDEF("pcie0.1", 4, 0xd0, MAPDEF_PCIMASK),
+ MAPDEF("pcie0.2", 4, 0xb0, MAPDEF_PCIMASK),
+ MAPDEF("pcie0.3", 4, 0x70, MAPDEF_PCIMASK),
+ MAPDEF("pcie1.0", 8, 0xe0, MAPDEF_PCIMASK),
+ MAPDEF("pcie1.1", 8, 0xd0, MAPDEF_PCIMASK),
+ MAPDEF("pcie1.2", 8, 0xb0, MAPDEF_PCIMASK),
+ MAPDEF("pcie1.3", 8, 0x70, MAPDEF_PCIMASK),
+ MAPDEF("pcie2.0", 4, 0xf0, MAPDEF_PCIMASK),
+ MAPDEF("pcie3.0", 8, 0xf0, MAPDEF_PCIMASK),
+ {},
+};
+
+static const struct mvebu_mbus_soc_data mv78xx0_mbus_data = {
+ .num_wins = 14,
+ .num_remappable_wins = 8,
+ .win_cfg_offset = mv78xx0_mbus_win_offset,
+ .setup_cpu_target = mvebu_mbus_default_setup_cpu_target,
+ .show_cpu_target = mvebu_sdram_debug_show_orion,
+ .map = mv78xx0_map,
+};
+
+/*
+ * The driver doesn't yet have a DT binding because the details of
+ * this DT binding still need to be sorted out. However, as a
+ * preparation, we already use of_device_id to match a SoC description
+ * string against the SoC specific details of this driver.
+ */
+static const struct of_device_id of_mvebu_mbus_ids[] = {
+ { .compatible = "marvell,armada370-mbus",
+ .data = &armada_370_mbus_data, },
+ { .compatible = "marvell,armadaxp-mbus",
+ .data = &armada_xp_mbus_data, },
+ { .compatible = "marvell,kirkwood-mbus",
+ .data = &kirkwood_mbus_data, },
+ { .compatible = "marvell,dove-mbus",
+ .data = &dove_mbus_data, },
+ { .compatible = "marvell,orion5x-88f5281-mbus",
+ .data = &orion5x_4win_mbus_data, },
+ { .compatible = "marvell,orion5x-88f5182-mbus",
+ .data = &orion5x_2win_mbus_data, },
+ { .compatible = "marvell,orion5x-88f5181-mbus",
+ .data = &orion5x_2win_mbus_data, },
+ { .compatible = "marvell,orion5x-88f6183-mbus",
+ .data = &orion5x_4win_mbus_data, },
+ { .compatible = "marvell,mv78xx0-mbus",
+ .data = &mv78xx0_mbus_data, },
+ { },
+};
+
+/*
+ * Public API of the driver
+ */
+int mvebu_mbus_add_window_remap_flags(const char *devname, phys_addr_t base,
+ size_t size, phys_addr_t remap,
+ unsigned int flags)
+{
+ struct mvebu_mbus_state *s = &mbus_state;
+ u8 target, attr;
+ int i;
+
+ if (!s->soc->map)
+ return -ENODEV;
+
+ for (i = 0; s->soc->map[i].name; i++)
+ if (!strcmp(s->soc->map[i].name, devname))
+ break;
+
+ if (!s->soc->map[i].name) {
+ pr_err("mvebu-mbus: unknown device '%s'\n", devname);
+ return -ENODEV;
+ }
+
+ target = s->soc->map[i].target;
+ attr = s->soc->map[i].attr;
+
+ if (flags == MVEBU_MBUS_PCI_MEM)
+ attr |= 0x8;
+ else if (flags == MVEBU_MBUS_PCI_WA)
+ attr |= 0x28;
+
+ if (!mvebu_mbus_window_conflicts(s, base, size, target, attr)) {
+ pr_err("mvebu-mbus: cannot add window '%s', conflicts with another window\n",
+ devname);
+ return -EINVAL;
+ }
+
+ return mvebu_mbus_alloc_window(s, base, size, remap, target, attr);
+
+}
+
+int mvebu_mbus_add_window(const char *devname, phys_addr_t base, size_t size)
+{
+ return mvebu_mbus_add_window_remap_flags(devname, base, size,
+ MVEBU_MBUS_NO_REMAP, 0);
+}
+
+int mvebu_mbus_del_window(phys_addr_t base, size_t size)
+{
+ int win;
+
+ win = mvebu_mbus_find_window(&mbus_state, base, size);
+ if (win < 0)
+ return win;
+
+ mvebu_mbus_disable_window(&mbus_state, win);
+ return 0;
+}
+
+static __init int mvebu_mbus_debugfs_init(void)
+{
+ struct mvebu_mbus_state *s = &mbus_state;
+
+ /*
+ * If no base has been initialized, doesn't make sense to
+ * register the debugfs entries. We may be on a multiplatform
+ * kernel that isn't running a Marvell EBU SoC.
+ */
+ if (!s->mbuswins_base)
+ return 0;
+
+ s->debugfs_root = debugfs_create_dir("mvebu-mbus", NULL);
+ if (s->debugfs_root) {
+ s->debugfs_sdram = debugfs_create_file("sdram", S_IRUGO,
+ s->debugfs_root, NULL,
+ &mvebu_sdram_debug_fops);
+ s->debugfs_devs = debugfs_create_file("devices", S_IRUGO,
+ s->debugfs_root, NULL,
+ &mvebu_devs_debug_fops);
+ }
+
+ return 0;
+}
+fs_initcall(mvebu_mbus_debugfs_init);
+
+int __init mvebu_mbus_init(const char *soc, phys_addr_t mbuswins_phys_base,
+ size_t mbuswins_size,
+ phys_addr_t sdramwins_phys_base,
+ size_t sdramwins_size)
+{
+ struct mvebu_mbus_state *mbus = &mbus_state;
+ const struct of_device_id *of_id;
+ int win;
+
+ for (of_id = of_mvebu_mbus_ids; of_id->compatible; of_id++)
+ if (!strcmp(of_id->compatible, soc))
+ break;
+
+ if (!of_id->compatible) {
+ pr_err("mvebu-mbus: could not find a matching SoC family\n");
+ return -ENODEV;
+ }
+
+ mbus->soc = of_id->data;
+
+ mbus->mbuswins_base = ioremap(mbuswins_phys_base, mbuswins_size);
+ if (!mbus->mbuswins_base)
+ return -ENOMEM;
+
+ mbus->sdramwins_base = ioremap(sdramwins_phys_base, sdramwins_size);
+ if (!mbus->sdramwins_base) {
+ iounmap(mbus_state.mbuswins_base);
+ return -ENOMEM;
+ }
+
+ if (of_find_compatible_node(NULL, NULL, "marvell,coherency-fabric"))
+ mbus->hw_io_coherency = 1;
+
+ for (win = 0; win < mbus->soc->num_wins; win++)
+ mvebu_mbus_disable_window(mbus, win);
+
+ mbus->soc->setup_cpu_target(mbus);
+
+ return 0;
+}
diff --git a/drivers/bus/omap-ocp2scp.c b/drivers/bus/omap-ocp2scp.c
new file mode 100644
index 000000000..fe7191663
--- /dev/null
+++ b/drivers/bus/omap-ocp2scp.c
@@ -0,0 +1,150 @@
+/*
+ * omap-ocp2scp.c - transform ocp interface protocol to scp protocol
+ *
+ * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com
+ * 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.
+ *
+ * Author: Kishon Vijay Abraham I <kishon@ti.com>
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_data/omap_ocp2scp.h>
+
+/**
+ * _count_resources - count for the number of resources
+ * @res: struct resource *
+ *
+ * Count and return the number of resources populated for the device that is
+ * connected to ocp2scp.
+ */
+static unsigned _count_resources(struct resource *res)
+{
+ int cnt = 0;
+
+ while (res->start != res->end) {
+ cnt++;
+ res++;
+ }
+
+ return cnt;
+}
+
+static int ocp2scp_remove_devices(struct device *dev, void *c)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ platform_device_unregister(pdev);
+
+ return 0;
+}
+
+static int omap_ocp2scp_probe(struct platform_device *pdev)
+{
+ int ret;
+ unsigned res_cnt, i;
+ struct device_node *np = pdev->dev.of_node;
+ struct platform_device *pdev_child;
+ struct omap_ocp2scp_platform_data *pdata = pdev->dev.platform_data;
+ struct omap_ocp2scp_dev *dev;
+
+ if (np) {
+ ret = of_platform_populate(np, NULL, NULL, &pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to add resources for ocp2scp child\n");
+ goto err0;
+ }
+ } else if (pdata) {
+ for (i = 0, dev = *pdata->devices; i < pdata->dev_cnt; i++,
+ dev++) {
+ res_cnt = _count_resources(dev->res);
+
+ pdev_child = platform_device_alloc(dev->drv_name,
+ PLATFORM_DEVID_AUTO);
+ if (!pdev_child) {
+ dev_err(&pdev->dev,
+ "failed to allocate mem for ocp2scp child\n");
+ goto err0;
+ }
+
+ ret = platform_device_add_resources(pdev_child,
+ dev->res, res_cnt);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to add resources for ocp2scp child\n");
+ goto err1;
+ }
+
+ pdev_child->dev.parent = &pdev->dev;
+
+ ret = platform_device_add(pdev_child);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to register ocp2scp child device\n");
+ goto err1;
+ }
+ }
+ } else {
+ dev_err(&pdev->dev, "OCP2SCP initialized without plat data\n");
+ return -EINVAL;
+ }
+
+ pm_runtime_enable(&pdev->dev);
+
+ return 0;
+
+err1:
+ platform_device_put(pdev_child);
+
+err0:
+ device_for_each_child(&pdev->dev, NULL, ocp2scp_remove_devices);
+
+ return ret;
+}
+
+static int omap_ocp2scp_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+ device_for_each_child(&pdev->dev, NULL, ocp2scp_remove_devices);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id omap_ocp2scp_id_table[] = {
+ { .compatible = "ti,omap-ocp2scp" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, omap_ocp2scp_id_table);
+#endif
+
+static struct platform_driver omap_ocp2scp_driver = {
+ .probe = omap_ocp2scp_probe,
+ .remove = omap_ocp2scp_remove,
+ .driver = {
+ .name = "omap-ocp2scp",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(omap_ocp2scp_id_table),
+ },
+};
+
+module_platform_driver(omap_ocp2scp_driver);
+
+MODULE_ALIAS("platform: omap-ocp2scp");
+MODULE_AUTHOR("Texas Instruments Inc.");
+MODULE_DESCRIPTION("OMAP OCP2SCP driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/bus/omap_l3_noc.c b/drivers/bus/omap_l3_noc.c
new file mode 100644
index 000000000..feeecae62
--- /dev/null
+++ b/drivers/bus/omap_l3_noc.c
@@ -0,0 +1,266 @@
+/*
+ * OMAP4XXX L3 Interconnect error handling driver
+ *
+ * Copyright (C) 2011 Texas Corporation
+ * Santosh Shilimkar <santosh.shilimkar@ti.com>
+ * Sricharan <r.sricharan@ti.com>
+ *
+ * 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/init.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+
+#include "omap_l3_noc.h"
+
+/*
+ * Interrupt Handler for L3 error detection.
+ * 1) Identify the L3 clockdomain partition to which the error belongs to.
+ * 2) Identify the slave where the error information is logged
+ * 3) Print the logged information.
+ * 4) Add dump stack to provide kernel trace.
+ *
+ * Two Types of errors :
+ * 1) Custom errors in L3 :
+ * Target like DMM/FW/EMIF generates SRESP=ERR error
+ * 2) Standard L3 error:
+ * - Unsupported CMD.
+ * L3 tries to access target while it is idle
+ * - OCP disconnect.
+ * - Address hole error:
+ * If DSS/ISS/FDIF/USBHOSTFS access a target where they
+ * do not have connectivity, the error is logged in
+ * their default target which is DMM2.
+ *
+ * On High Secure devices, firewall errors are possible and those
+ * can be trapped as well. But the trapping is implemented as part
+ * secure software and hence need not be implemented here.
+ */
+static irqreturn_t l3_interrupt_handler(int irq, void *_l3)
+{
+
+ struct omap4_l3 *l3 = _l3;
+ int inttype, i, k;
+ int err_src = 0;
+ u32 std_err_main, err_reg, clear, masterid;
+ void __iomem *base, *l3_targ_base;
+ char *target_name, *master_name = "UN IDENTIFIED";
+
+ /* Get the Type of interrupt */
+ inttype = irq == l3->app_irq ? L3_APPLICATION_ERROR : L3_DEBUG_ERROR;
+
+ for (i = 0; i < L3_MODULES; i++) {
+ /*
+ * Read the regerr register of the clock domain
+ * to determine the source
+ */
+ base = l3->l3_base[i];
+ err_reg = __raw_readl(base + l3_flagmux[i] +
+ + L3_FLAGMUX_REGERR0 + (inttype << 3));
+
+ /* Get the corresponding error and analyse */
+ if (err_reg) {
+ /* Identify the source from control status register */
+ err_src = __ffs(err_reg);
+
+ /* Read the stderrlog_main_source from clk domain */
+ l3_targ_base = base + *(l3_targ[i] + err_src);
+ std_err_main = __raw_readl(l3_targ_base +
+ L3_TARG_STDERRLOG_MAIN);
+ masterid = __raw_readl(l3_targ_base +
+ L3_TARG_STDERRLOG_MSTADDR);
+
+ switch (std_err_main & CUSTOM_ERROR) {
+ case STANDARD_ERROR:
+ target_name =
+ l3_targ_inst_name[i][err_src];
+ WARN(true, "L3 standard error: TARGET:%s at address 0x%x\n",
+ target_name,
+ __raw_readl(l3_targ_base +
+ L3_TARG_STDERRLOG_SLVOFSLSB));
+ /* clear the std error log*/
+ clear = std_err_main | CLEAR_STDERR_LOG;
+ writel(clear, l3_targ_base +
+ L3_TARG_STDERRLOG_MAIN);
+ break;
+
+ case CUSTOM_ERROR:
+ target_name =
+ l3_targ_inst_name[i][err_src];
+ for (k = 0; k < NUM_OF_L3_MASTERS; k++) {
+ if (masterid == l3_masters[k].id)
+ master_name =
+ l3_masters[k].name;
+ }
+ WARN(true, "L3 custom error: MASTER:%s TARGET:%s\n",
+ master_name, target_name);
+ /* clear the std error log*/
+ clear = std_err_main | CLEAR_STDERR_LOG;
+ writel(clear, l3_targ_base +
+ L3_TARG_STDERRLOG_MAIN);
+ break;
+
+ default:
+ /* Nothing to be handled here as of now */
+ break;
+ }
+ /* Error found so break the for loop */
+ break;
+ }
+ }
+ return IRQ_HANDLED;
+}
+
+static int omap4_l3_probe(struct platform_device *pdev)
+{
+ static struct omap4_l3 *l3;
+ struct resource *res;
+ int ret;
+
+ l3 = kzalloc(sizeof(*l3), GFP_KERNEL);
+ if (!l3)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, l3);
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "couldn't find resource 0\n");
+ ret = -ENODEV;
+ goto err0;
+ }
+
+ l3->l3_base[0] = ioremap(res->start, resource_size(res));
+ if (!l3->l3_base[0]) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto err0;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res) {
+ dev_err(&pdev->dev, "couldn't find resource 1\n");
+ ret = -ENODEV;
+ goto err1;
+ }
+
+ l3->l3_base[1] = ioremap(res->start, resource_size(res));
+ if (!l3->l3_base[1]) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto err1;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ if (!res) {
+ dev_err(&pdev->dev, "couldn't find resource 2\n");
+ ret = -ENODEV;
+ goto err2;
+ }
+
+ l3->l3_base[2] = ioremap(res->start, resource_size(res));
+ if (!l3->l3_base[2]) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto err2;
+ }
+
+ /*
+ * Setup interrupt Handlers
+ */
+ l3->debug_irq = platform_get_irq(pdev, 0);
+ ret = request_irq(l3->debug_irq,
+ l3_interrupt_handler,
+ IRQF_DISABLED, "l3-dbg-irq", l3);
+ if (ret) {
+ pr_crit("L3: request_irq failed to register for 0x%x\n",
+ l3->debug_irq);
+ goto err3;
+ }
+
+ l3->app_irq = platform_get_irq(pdev, 1);
+ ret = request_irq(l3->app_irq,
+ l3_interrupt_handler,
+ IRQF_DISABLED, "l3-app-irq", l3);
+ if (ret) {
+ pr_crit("L3: request_irq failed to register for 0x%x\n",
+ l3->app_irq);
+ goto err4;
+ }
+
+ return 0;
+
+err4:
+ free_irq(l3->debug_irq, l3);
+err3:
+ iounmap(l3->l3_base[2]);
+err2:
+ iounmap(l3->l3_base[1]);
+err1:
+ iounmap(l3->l3_base[0]);
+err0:
+ kfree(l3);
+ return ret;
+}
+
+static int omap4_l3_remove(struct platform_device *pdev)
+{
+ struct omap4_l3 *l3 = platform_get_drvdata(pdev);
+
+ free_irq(l3->app_irq, l3);
+ free_irq(l3->debug_irq, l3);
+ iounmap(l3->l3_base[0]);
+ iounmap(l3->l3_base[1]);
+ iounmap(l3->l3_base[2]);
+ kfree(l3);
+
+ return 0;
+}
+
+#if defined(CONFIG_OF)
+static const struct of_device_id l3_noc_match[] = {
+ {.compatible = "ti,omap4-l3-noc", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, l3_noc_match);
+#else
+#define l3_noc_match NULL
+#endif
+
+static struct platform_driver omap4_l3_driver = {
+ .probe = omap4_l3_probe,
+ .remove = omap4_l3_remove,
+ .driver = {
+ .name = "omap_l3_noc",
+ .owner = THIS_MODULE,
+ .of_match_table = l3_noc_match,
+ },
+};
+
+static int __init omap4_l3_init(void)
+{
+ return platform_driver_register(&omap4_l3_driver);
+}
+postcore_initcall_sync(omap4_l3_init);
+
+static void __exit omap4_l3_exit(void)
+{
+ platform_driver_unregister(&omap4_l3_driver);
+}
+module_exit(omap4_l3_exit);
diff --git a/drivers/bus/omap_l3_noc.h b/drivers/bus/omap_l3_noc.h
new file mode 100644
index 000000000..a6ce34dc4
--- /dev/null
+++ b/drivers/bus/omap_l3_noc.h
@@ -0,0 +1,176 @@
+/*
+ * OMAP4XXX L3 Interconnect error handling driver header
+ *
+ * Copyright (C) 2011 Texas Corporation
+ * Santosh Shilimkar <santosh.shilimkar@ti.com>
+ * sricharan <r.sricharan@ti.com>
+ *
+ * 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_OMAP2_L3_INTERCONNECT_3XXX_H
+#define __ARCH_ARM_MACH_OMAP2_L3_INTERCONNECT_3XXX_H
+
+#define L3_MODULES 3
+#define CLEAR_STDERR_LOG (1 << 31)
+#define CUSTOM_ERROR 0x2
+#define STANDARD_ERROR 0x0
+#define INBAND_ERROR 0x0
+#define L3_APPLICATION_ERROR 0x0
+#define L3_DEBUG_ERROR 0x1
+
+/* L3 TARG register offsets */
+#define L3_TARG_STDERRLOG_MAIN 0x48
+#define L3_TARG_STDERRLOG_SLVOFSLSB 0x5c
+#define L3_TARG_STDERRLOG_MSTADDR 0x68
+#define L3_FLAGMUX_REGERR0 0xc
+
+#define NUM_OF_L3_MASTERS (sizeof(l3_masters)/sizeof(l3_masters[0]))
+
+static u32 l3_flagmux[L3_MODULES] = {
+ 0x500,
+ 0x1000,
+ 0X0200
+};
+
+/* L3 Target standard Error register offsets */
+static u32 l3_targ_inst_clk1[] = {
+ 0x100, /* DMM1 */
+ 0x200, /* DMM2 */
+ 0x300, /* ABE */
+ 0x400, /* L4CFG */
+ 0x600, /* CLK2 PWR DISC */
+ 0x0, /* Host CLK1 */
+ 0x900 /* L4 Wakeup */
+};
+
+static u32 l3_targ_inst_clk2[] = {
+ 0x500, /* CORTEX M3 */
+ 0x300, /* DSS */
+ 0x100, /* GPMC */
+ 0x400, /* ISS */
+ 0x700, /* IVAHD */
+ 0xD00, /* missing in TRM corresponds to AES1*/
+ 0x900, /* L4 PER0*/
+ 0x200, /* OCMRAM */
+ 0x100, /* missing in TRM corresponds to GPMC sERROR*/
+ 0x600, /* SGX */
+ 0x800, /* SL2 */
+ 0x1600, /* C2C */
+ 0x1100, /* missing in TRM corresponds PWR DISC CLK1*/
+ 0xF00, /* missing in TRM corrsponds to SHA1*/
+ 0xE00, /* missing in TRM corresponds to AES2*/
+ 0xC00, /* L4 PER3 */
+ 0xA00, /* L4 PER1*/
+ 0xB00, /* L4 PER2*/
+ 0x0, /* HOST CLK2 */
+ 0x1800, /* CAL */
+ 0x1700 /* LLI */
+};
+
+static u32 l3_targ_inst_clk3[] = {
+ 0x0100 /* EMUSS */,
+ 0x0300, /* DEBUGSS_CT_TBR */
+ 0x0 /* HOST CLK3 */
+};
+
+static struct l3_masters_data {
+ u32 id;
+ char name[10];
+} l3_masters[] = {
+ { 0x0 , "MPU"},
+ { 0x10, "CS_ADP"},
+ { 0x14, "xxx"},
+ { 0x20, "DSP"},
+ { 0x30, "IVAHD"},
+ { 0x40, "ISS"},
+ { 0x44, "DucatiM3"},
+ { 0x48, "FaceDetect"},
+ { 0x50, "SDMA_Rd"},
+ { 0x54, "SDMA_Wr"},
+ { 0x58, "xxx"},
+ { 0x5C, "xxx"},
+ { 0x60, "SGX"},
+ { 0x70, "DSS"},
+ { 0x80, "C2C"},
+ { 0x88, "xxx"},
+ { 0x8C, "xxx"},
+ { 0x90, "HSI"},
+ { 0xA0, "MMC1"},
+ { 0xA4, "MMC2"},
+ { 0xA8, "MMC6"},
+ { 0xB0, "UNIPRO1"},
+ { 0xC0, "USBHOSTHS"},
+ { 0xC4, "USBOTGHS"},
+ { 0xC8, "USBHOSTFS"}
+};
+
+static char *l3_targ_inst_name[L3_MODULES][21] = {
+ {
+ "DMM1",
+ "DMM2",
+ "ABE",
+ "L4CFG",
+ "CLK2 PWR DISC",
+ "HOST CLK1",
+ "L4 WAKEUP"
+ },
+ {
+ "CORTEX M3" ,
+ "DSS ",
+ "GPMC ",
+ "ISS ",
+ "IVAHD ",
+ "AES1",
+ "L4 PER0",
+ "OCMRAM ",
+ "GPMC sERROR",
+ "SGX ",
+ "SL2 ",
+ "C2C ",
+ "PWR DISC CLK1",
+ "SHA1",
+ "AES2",
+ "L4 PER3",
+ "L4 PER1",
+ "L4 PER2",
+ "HOST CLK2",
+ "CAL",
+ "LLI"
+ },
+ {
+ "EMUSS",
+ "DEBUG SOURCE",
+ "HOST CLK3"
+ },
+};
+
+static u32 *l3_targ[L3_MODULES] = {
+ l3_targ_inst_clk1,
+ l3_targ_inst_clk2,
+ l3_targ_inst_clk3,
+};
+
+struct omap4_l3 {
+ struct device *dev;
+ struct clk *ick;
+
+ /* memory base */
+ void __iomem *l3_base[L3_MODULES];
+
+ int debug_irq;
+ int app_irq;
+};
+#endif
diff --git a/drivers/bus/omap_l3_smx.c b/drivers/bus/omap_l3_smx.c
new file mode 100644
index 000000000..acc216491
--- /dev/null
+++ b/drivers/bus/omap_l3_smx.c
@@ -0,0 +1,297 @@
+/*
+ * OMAP3XXX L3 Interconnect Driver
+ *
+ * Copyright (C) 2011 Texas Corporation
+ * Felipe Balbi <balbi@ti.com>
+ * Santosh Shilimkar <santosh.shilimkar@ti.com>
+ * Sricharan <r.sricharan@ti.com>
+ *
+ * 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/slab.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include "omap_l3_smx.h"
+
+static inline u64 omap3_l3_readll(void __iomem *base, u16 reg)
+{
+ return __raw_readll(base + reg);
+}
+
+static inline void omap3_l3_writell(void __iomem *base, u16 reg, u64 value)
+{
+ __raw_writell(value, base + reg);
+}
+
+static inline enum omap3_l3_code omap3_l3_decode_error_code(u64 error)
+{
+ return (error & 0x0f000000) >> L3_ERROR_LOG_CODE;
+}
+
+static inline u32 omap3_l3_decode_addr(u64 error_addr)
+{
+ return error_addr & 0xffffffff;
+}
+
+static inline unsigned omap3_l3_decode_cmd(u64 error)
+{
+ return (error & 0x07) >> L3_ERROR_LOG_CMD;
+}
+
+static inline enum omap3_l3_initiator_id omap3_l3_decode_initid(u64 error)
+{
+ return (error & 0xff00) >> L3_ERROR_LOG_INITID;
+}
+
+static inline unsigned omap3_l3_decode_req_info(u64 error)
+{
+ return (error >> 32) & 0xffff;
+}
+
+static char *omap3_l3_code_string(u8 code)
+{
+ switch (code) {
+ case OMAP_L3_CODE_NOERROR:
+ return "No Error";
+ case OMAP_L3_CODE_UNSUP_CMD:
+ return "Unsupported Command";
+ case OMAP_L3_CODE_ADDR_HOLE:
+ return "Address Hole";
+ case OMAP_L3_CODE_PROTECT_VIOLATION:
+ return "Protection Violation";
+ case OMAP_L3_CODE_IN_BAND_ERR:
+ return "In-band Error";
+ case OMAP_L3_CODE_REQ_TOUT_NOT_ACCEPT:
+ return "Request Timeout Not Accepted";
+ case OMAP_L3_CODE_REQ_TOUT_NO_RESP:
+ return "Request Timeout, no response";
+ default:
+ return "UNKNOWN error";
+ }
+}
+
+static char *omap3_l3_initiator_string(u8 initid)
+{
+ switch (initid) {
+ case OMAP_L3_LCD:
+ return "LCD";
+ case OMAP_L3_SAD2D:
+ return "SAD2D";
+ case OMAP_L3_IA_MPU_SS_1:
+ case OMAP_L3_IA_MPU_SS_2:
+ case OMAP_L3_IA_MPU_SS_3:
+ case OMAP_L3_IA_MPU_SS_4:
+ case OMAP_L3_IA_MPU_SS_5:
+ return "MPU";
+ case OMAP_L3_IA_IVA_SS_1:
+ case OMAP_L3_IA_IVA_SS_2:
+ case OMAP_L3_IA_IVA_SS_3:
+ return "IVA_SS";
+ case OMAP_L3_IA_IVA_SS_DMA_1:
+ case OMAP_L3_IA_IVA_SS_DMA_2:
+ case OMAP_L3_IA_IVA_SS_DMA_3:
+ case OMAP_L3_IA_IVA_SS_DMA_4:
+ case OMAP_L3_IA_IVA_SS_DMA_5:
+ case OMAP_L3_IA_IVA_SS_DMA_6:
+ return "IVA_SS_DMA";
+ case OMAP_L3_IA_SGX:
+ return "SGX";
+ case OMAP_L3_IA_CAM_1:
+ case OMAP_L3_IA_CAM_2:
+ case OMAP_L3_IA_CAM_3:
+ return "CAM";
+ case OMAP_L3_IA_DAP:
+ return "DAP";
+ case OMAP_L3_SDMA_WR_1:
+ case OMAP_L3_SDMA_WR_2:
+ return "SDMA_WR";
+ case OMAP_L3_SDMA_RD_1:
+ case OMAP_L3_SDMA_RD_2:
+ case OMAP_L3_SDMA_RD_3:
+ case OMAP_L3_SDMA_RD_4:
+ return "SDMA_RD";
+ case OMAP_L3_USBOTG:
+ return "USB_OTG";
+ case OMAP_L3_USBHOST:
+ return "USB_HOST";
+ default:
+ return "UNKNOWN Initiator";
+ }
+}
+
+/*
+ * omap3_l3_block_irq - handles a register block's irq
+ * @l3: struct omap3_l3 *
+ * @base: register block base address
+ * @error: L3_ERROR_LOG register of our block
+ *
+ * Called in hard-irq context. Caller should take care of locking
+ *
+ * OMAP36xx TRM gives, on page 2001, Figure 9-10, the Typical Error
+ * Analysis Sequence, we are following that sequence here, please
+ * refer to that Figure for more information on the subject.
+ */
+static irqreturn_t omap3_l3_block_irq(struct omap3_l3 *l3,
+ u64 error, int error_addr)
+{
+ u8 code = omap3_l3_decode_error_code(error);
+ u8 initid = omap3_l3_decode_initid(error);
+ u8 multi = error & L3_ERROR_LOG_MULTI;
+ u32 address = omap3_l3_decode_addr(error_addr);
+
+ pr_err("%s seen by %s %s at address %x\n",
+ omap3_l3_code_string(code),
+ omap3_l3_initiator_string(initid),
+ multi ? "Multiple Errors" : "", address);
+ WARN_ON(1);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t omap3_l3_app_irq(int irq, void *_l3)
+{
+ struct omap3_l3 *l3 = _l3;
+ u64 status, clear;
+ u64 error;
+ u64 error_addr;
+ u64 err_source = 0;
+ void __iomem *base;
+ int int_type;
+ irqreturn_t ret = IRQ_NONE;
+
+ int_type = irq == l3->app_irq ? L3_APPLICATION_ERROR : L3_DEBUG_ERROR;
+ if (!int_type) {
+ status = omap3_l3_readll(l3->rt, L3_SI_FLAG_STATUS_0);
+ /*
+ * if we have a timeout error, there's nothing we can
+ * do besides rebooting the board. So let's BUG on any
+ * of such errors and handle the others. timeout error
+ * is severe and not expected to occur.
+ */
+ BUG_ON(status & L3_STATUS_0_TIMEOUT_MASK);
+ } else {
+ status = omap3_l3_readll(l3->rt, L3_SI_FLAG_STATUS_1);
+ /* No timeout error for debug sources */
+ }
+
+ /* identify the error source */
+ err_source = __ffs(status);
+
+ base = l3->rt + omap3_l3_bases[int_type][err_source];
+ error = omap3_l3_readll(base, L3_ERROR_LOG);
+ if (error) {
+ error_addr = omap3_l3_readll(base, L3_ERROR_LOG_ADDR);
+ ret |= omap3_l3_block_irq(l3, error, error_addr);
+ }
+
+ /* Clear the status register */
+ clear = (L3_AGENT_STATUS_CLEAR_IA << int_type) |
+ L3_AGENT_STATUS_CLEAR_TA;
+ omap3_l3_writell(base, L3_AGENT_STATUS, clear);
+
+ /* clear the error log register */
+ omap3_l3_writell(base, L3_ERROR_LOG, error);
+
+ return ret;
+}
+
+static int __init omap3_l3_probe(struct platform_device *pdev)
+{
+ struct omap3_l3 *l3;
+ struct resource *res;
+ int ret;
+
+ l3 = kzalloc(sizeof(*l3), GFP_KERNEL);
+ if (!l3)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, l3);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "couldn't find resource\n");
+ ret = -ENODEV;
+ goto err0;
+ }
+ l3->rt = ioremap(res->start, resource_size(res));
+ if (!l3->rt) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto err0;
+ }
+
+ l3->debug_irq = platform_get_irq(pdev, 0);
+ ret = request_irq(l3->debug_irq, omap3_l3_app_irq,
+ IRQF_DISABLED | IRQF_TRIGGER_RISING,
+ "l3-debug-irq", l3);
+ if (ret) {
+ dev_err(&pdev->dev, "couldn't request debug irq\n");
+ goto err1;
+ }
+
+ l3->app_irq = platform_get_irq(pdev, 1);
+ ret = request_irq(l3->app_irq, omap3_l3_app_irq,
+ IRQF_DISABLED | IRQF_TRIGGER_RISING,
+ "l3-app-irq", l3);
+ if (ret) {
+ dev_err(&pdev->dev, "couldn't request app irq\n");
+ goto err2;
+ }
+
+ return 0;
+
+err2:
+ free_irq(l3->debug_irq, l3);
+err1:
+ iounmap(l3->rt);
+err0:
+ kfree(l3);
+ return ret;
+}
+
+static int __exit omap3_l3_remove(struct platform_device *pdev)
+{
+ struct omap3_l3 *l3 = platform_get_drvdata(pdev);
+
+ free_irq(l3->app_irq, l3);
+ free_irq(l3->debug_irq, l3);
+ iounmap(l3->rt);
+ kfree(l3);
+
+ return 0;
+}
+
+static struct platform_driver omap3_l3_driver = {
+ .remove = __exit_p(omap3_l3_remove),
+ .driver = {
+ .name = "omap_l3_smx",
+ },
+};
+
+static int __init omap3_l3_init(void)
+{
+ return platform_driver_probe(&omap3_l3_driver, omap3_l3_probe);
+}
+postcore_initcall_sync(omap3_l3_init);
+
+static void __exit omap3_l3_exit(void)
+{
+ platform_driver_unregister(&omap3_l3_driver);
+}
+module_exit(omap3_l3_exit);
diff --git a/drivers/bus/omap_l3_smx.h b/drivers/bus/omap_l3_smx.h
new file mode 100644
index 000000000..4f3cebca4
--- /dev/null
+++ b/drivers/bus/omap_l3_smx.h
@@ -0,0 +1,338 @@
+/*
+ * OMAP3XXX L3 Interconnect Driver header
+ *
+ * Copyright (C) 2011 Texas Corporation
+ * Felipe Balbi <balbi@ti.com>
+ * Santosh Shilimkar <santosh.shilimkar@ti.com>
+ * sricharan <r.sricharan@ti.com>
+ *
+ * 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_OMAP2_L3_INTERCONNECT_3XXX_H
+#define __ARCH_ARM_MACH_OMAP2_L3_INTERCONNECT_3XXX_H
+
+/* Register definitions. All 64-bit wide */
+#define L3_COMPONENT 0x000
+#define L3_CORE 0x018
+#define L3_AGENT_CONTROL 0x020
+#define L3_AGENT_STATUS 0x028
+#define L3_ERROR_LOG 0x058
+
+#define L3_ERROR_LOG_MULTI (1 << 31)
+#define L3_ERROR_LOG_SECONDARY (1 << 30)
+
+#define L3_ERROR_LOG_ADDR 0x060
+
+/* Register definitions for Sideband Interconnect */
+#define L3_SI_CONTROL 0x020
+#define L3_SI_FLAG_STATUS_0 0x510
+
+static const u64 shift = 1;
+
+#define L3_STATUS_0_MPUIA_BRST (shift << 0)
+#define L3_STATUS_0_MPUIA_RSP (shift << 1)
+#define L3_STATUS_0_MPUIA_INBAND (shift << 2)
+#define L3_STATUS_0_IVAIA_BRST (shift << 6)
+#define L3_STATUS_0_IVAIA_RSP (shift << 7)
+#define L3_STATUS_0_IVAIA_INBAND (shift << 8)
+#define L3_STATUS_0_SGXIA_BRST (shift << 9)
+#define L3_STATUS_0_SGXIA_RSP (shift << 10)
+#define L3_STATUS_0_SGXIA_MERROR (shift << 11)
+#define L3_STATUS_0_CAMIA_BRST (shift << 12)
+#define L3_STATUS_0_CAMIA_RSP (shift << 13)
+#define L3_STATUS_0_CAMIA_INBAND (shift << 14)
+#define L3_STATUS_0_DISPIA_BRST (shift << 15)
+#define L3_STATUS_0_DISPIA_RSP (shift << 16)
+#define L3_STATUS_0_DMARDIA_BRST (shift << 18)
+#define L3_STATUS_0_DMARDIA_RSP (shift << 19)
+#define L3_STATUS_0_DMAWRIA_BRST (shift << 21)
+#define L3_STATUS_0_DMAWRIA_RSP (shift << 22)
+#define L3_STATUS_0_USBOTGIA_BRST (shift << 24)
+#define L3_STATUS_0_USBOTGIA_RSP (shift << 25)
+#define L3_STATUS_0_USBOTGIA_INBAND (shift << 26)
+#define L3_STATUS_0_USBHOSTIA_BRST (shift << 27)
+#define L3_STATUS_0_USBHOSTIA_INBAND (shift << 28)
+#define L3_STATUS_0_SMSTA_REQ (shift << 48)
+#define L3_STATUS_0_GPMCTA_REQ (shift << 49)
+#define L3_STATUS_0_OCMRAMTA_REQ (shift << 50)
+#define L3_STATUS_0_OCMROMTA_REQ (shift << 51)
+#define L3_STATUS_0_IVATA_REQ (shift << 54)
+#define L3_STATUS_0_SGXTA_REQ (shift << 55)
+#define L3_STATUS_0_SGXTA_SERROR (shift << 56)
+#define L3_STATUS_0_GPMCTA_SERROR (shift << 57)
+#define L3_STATUS_0_L4CORETA_REQ (shift << 58)
+#define L3_STATUS_0_L4PERTA_REQ (shift << 59)
+#define L3_STATUS_0_L4EMUTA_REQ (shift << 60)
+#define L3_STATUS_0_MAD2DTA_REQ (shift << 61)
+
+#define L3_STATUS_0_TIMEOUT_MASK (L3_STATUS_0_MPUIA_BRST \
+ | L3_STATUS_0_MPUIA_RSP \
+ | L3_STATUS_0_IVAIA_BRST \
+ | L3_STATUS_0_IVAIA_RSP \
+ | L3_STATUS_0_SGXIA_BRST \
+ | L3_STATUS_0_SGXIA_RSP \
+ | L3_STATUS_0_CAMIA_BRST \
+ | L3_STATUS_0_CAMIA_RSP \
+ | L3_STATUS_0_DISPIA_BRST \
+ | L3_STATUS_0_DISPIA_RSP \
+ | L3_STATUS_0_DMARDIA_BRST \
+ | L3_STATUS_0_DMARDIA_RSP \
+ | L3_STATUS_0_DMAWRIA_BRST \
+ | L3_STATUS_0_DMAWRIA_RSP \
+ | L3_STATUS_0_USBOTGIA_BRST \
+ | L3_STATUS_0_USBOTGIA_RSP \
+ | L3_STATUS_0_USBHOSTIA_BRST \
+ | L3_STATUS_0_SMSTA_REQ \
+ | L3_STATUS_0_GPMCTA_REQ \
+ | L3_STATUS_0_OCMRAMTA_REQ \
+ | L3_STATUS_0_OCMROMTA_REQ \
+ | L3_STATUS_0_IVATA_REQ \
+ | L3_STATUS_0_SGXTA_REQ \
+ | L3_STATUS_0_L4CORETA_REQ \
+ | L3_STATUS_0_L4PERTA_REQ \
+ | L3_STATUS_0_L4EMUTA_REQ \
+ | L3_STATUS_0_MAD2DTA_REQ)
+
+#define L3_SI_FLAG_STATUS_1 0x530
+
+#define L3_STATUS_1_MPU_DATAIA (1 << 0)
+#define L3_STATUS_1_DAPIA0 (1 << 3)
+#define L3_STATUS_1_DAPIA1 (1 << 4)
+#define L3_STATUS_1_IVAIA (1 << 6)
+
+#define L3_PM_ERROR_LOG 0x020
+#define L3_PM_CONTROL 0x028
+#define L3_PM_ERROR_CLEAR_SINGLE 0x030
+#define L3_PM_ERROR_CLEAR_MULTI 0x038
+#define L3_PM_REQ_INFO_PERMISSION(n) (0x048 + (0x020 * n))
+#define L3_PM_READ_PERMISSION(n) (0x050 + (0x020 * n))
+#define L3_PM_WRITE_PERMISSION(n) (0x058 + (0x020 * n))
+#define L3_PM_ADDR_MATCH(n) (0x060 + (0x020 * n))
+
+/* L3 error log bit fields. Common for IA and TA */
+#define L3_ERROR_LOG_CODE 24
+#define L3_ERROR_LOG_INITID 8
+#define L3_ERROR_LOG_CMD 0
+
+/* L3 agent status bit fields. */
+#define L3_AGENT_STATUS_CLEAR_IA 0x10000000
+#define L3_AGENT_STATUS_CLEAR_TA 0x01000000
+
+#define OMAP34xx_IRQ_L3_APP 10
+#define L3_APPLICATION_ERROR 0x0
+#define L3_DEBUG_ERROR 0x1
+
+enum omap3_l3_initiator_id {
+ /* LCD has 1 ID */
+ OMAP_L3_LCD = 29,
+ /* SAD2D has 1 ID */
+ OMAP_L3_SAD2D = 28,
+ /* MPU has 5 IDs */
+ OMAP_L3_IA_MPU_SS_1 = 27,
+ OMAP_L3_IA_MPU_SS_2 = 26,
+ OMAP_L3_IA_MPU_SS_3 = 25,
+ OMAP_L3_IA_MPU_SS_4 = 24,
+ OMAP_L3_IA_MPU_SS_5 = 23,
+ /* IVA2.2 SS has 3 IDs*/
+ OMAP_L3_IA_IVA_SS_1 = 22,
+ OMAP_L3_IA_IVA_SS_2 = 21,
+ OMAP_L3_IA_IVA_SS_3 = 20,
+ /* IVA 2.2 SS DMA has 6 IDS */
+ OMAP_L3_IA_IVA_SS_DMA_1 = 19,
+ OMAP_L3_IA_IVA_SS_DMA_2 = 18,
+ OMAP_L3_IA_IVA_SS_DMA_3 = 17,
+ OMAP_L3_IA_IVA_SS_DMA_4 = 16,
+ OMAP_L3_IA_IVA_SS_DMA_5 = 15,
+ OMAP_L3_IA_IVA_SS_DMA_6 = 14,
+ /* SGX has 1 ID */
+ OMAP_L3_IA_SGX = 13,
+ /* CAM has 3 ID */
+ OMAP_L3_IA_CAM_1 = 12,
+ OMAP_L3_IA_CAM_2 = 11,
+ OMAP_L3_IA_CAM_3 = 10,
+ /* DAP has 1 ID */
+ OMAP_L3_IA_DAP = 9,
+ /* SDMA WR has 2 IDs */
+ OMAP_L3_SDMA_WR_1 = 8,
+ OMAP_L3_SDMA_WR_2 = 7,
+ /* SDMA RD has 4 IDs */
+ OMAP_L3_SDMA_RD_1 = 6,
+ OMAP_L3_SDMA_RD_2 = 5,
+ OMAP_L3_SDMA_RD_3 = 4,
+ OMAP_L3_SDMA_RD_4 = 3,
+ /* HSUSB OTG has 1 ID */
+ OMAP_L3_USBOTG = 2,
+ /* HSUSB HOST has 1 ID */
+ OMAP_L3_USBHOST = 1,
+};
+
+enum omap3_l3_code {
+ OMAP_L3_CODE_NOERROR = 0,
+ OMAP_L3_CODE_UNSUP_CMD = 1,
+ OMAP_L3_CODE_ADDR_HOLE = 2,
+ OMAP_L3_CODE_PROTECT_VIOLATION = 3,
+ OMAP_L3_CODE_IN_BAND_ERR = 4,
+ /* codes 5 and 6 are reserved */
+ OMAP_L3_CODE_REQ_TOUT_NOT_ACCEPT = 7,
+ OMAP_L3_CODE_REQ_TOUT_NO_RESP = 8,
+ /* codes 9 - 15 are also reserved */
+};
+
+struct omap3_l3 {
+ struct device *dev;
+ struct clk *ick;
+
+ /* memory base*/
+ void __iomem *rt;
+
+ int debug_irq;
+ int app_irq;
+
+ /* true when and inband functional error occurs */
+ unsigned inband:1;
+};
+
+/* offsets for l3 agents in order with the Flag status register */
+static unsigned int omap3_l3_app_bases[] = {
+ /* MPU IA */
+ 0x1400,
+ 0x1400,
+ 0x1400,
+ /* RESERVED */
+ 0,
+ 0,
+ 0,
+ /* IVA 2.2 IA */
+ 0x1800,
+ 0x1800,
+ 0x1800,
+ /* SGX IA */
+ 0x1c00,
+ 0x1c00,
+ /* RESERVED */
+ 0,
+ /* CAMERA IA */
+ 0x5800,
+ 0x5800,
+ 0x5800,
+ /* DISPLAY IA */
+ 0x5400,
+ 0x5400,
+ /* RESERVED */
+ 0,
+ /*SDMA RD IA */
+ 0x4c00,
+ 0x4c00,
+ /* RESERVED */
+ 0,
+ /* SDMA WR IA */
+ 0x5000,
+ 0x5000,
+ /* RESERVED */
+ 0,
+ /* USB OTG IA */
+ 0x4400,
+ 0x4400,
+ 0x4400,
+ /* USB HOST IA */
+ 0x4000,
+ 0x4000,
+ /* RESERVED */
+ 0,
+ 0,
+ 0,
+ 0,
+ /* SAD2D IA */
+ 0x3000,
+ 0x3000,
+ 0x3000,
+ /* RESERVED */
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ /* SMA TA */
+ 0x2000,
+ /* GPMC TA */
+ 0x2400,
+ /* OCM RAM TA */
+ 0x2800,
+ /* OCM ROM TA */
+ 0x2C00,
+ /* L4 CORE TA */
+ 0x6800,
+ /* L4 PER TA */
+ 0x6c00,
+ /* IVA 2.2 TA */
+ 0x6000,
+ /* SGX TA */
+ 0x6400,
+ /* L4 EMU TA */
+ 0x7000,
+ /* GPMC TA */
+ 0x2400,
+ /* L4 CORE TA */
+ 0x6800,
+ /* L4 PER TA */
+ 0x6c00,
+ /* L4 EMU TA */
+ 0x7000,
+ /* MAD2D TA */
+ 0x3400,
+ /* RESERVED */
+ 0,
+ 0,
+};
+
+static unsigned int omap3_l3_debug_bases[] = {
+ /* MPU DATA IA */
+ 0x1400,
+ /* RESERVED */
+ 0,
+ 0,
+ /* DAP IA */
+ 0x5c00,
+ 0x5c00,
+ /* RESERVED */
+ 0,
+ /* IVA 2.2 IA */
+ 0x1800,
+ /* REST RESERVED */
+};
+
+static u32 *omap3_l3_bases[] = {
+ omap3_l3_app_bases,
+ omap3_l3_debug_bases,
+};
+
+/*
+ * REVISIT define __raw_readll/__raw_writell here, but move them to
+ * <asm/io.h> at some point
+ */
+#define __raw_writell(v, a) (__chk_io_ptr(a), \
+ *(volatile u64 __force *)(a) = (v))
+#define __raw_readll(a) (__chk_io_ptr(a), \
+ *(volatile u64 __force *)(a))
+
+#endif