diff options
Diffstat (limited to 'drivers/bus')
| -rw-r--r-- | drivers/bus/Kconfig | 41 | ||||
| -rw-r--r-- | drivers/bus/Makefile | 7 | ||||
| -rw-r--r-- | drivers/bus/arm-cci.c | 1132 | ||||
| -rw-r--r-- | drivers/bus/brcmstb_gisb.c | 289 | ||||
| -rw-r--r-- | drivers/bus/imx-weim.c | 215 | ||||
| -rw-r--r-- | drivers/bus/mvebu-mbus.c | 943 | ||||
| -rw-r--r-- | drivers/bus/omap-ocp2scp.c | 14 | ||||
| -rw-r--r-- | drivers/bus/omap_l3_noc.c | 406 | ||||
| -rw-r--r-- | drivers/bus/omap_l3_noc.h | 545 | ||||
| -rw-r--r-- | drivers/bus/vexpress-config.c | 202 |
10 files changed, 3488 insertions, 306 deletions
diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index bbec35d21fe..1f37d9870e7 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -4,8 +4,32 @@ menu "Bus devices" +config BRCMSTB_GISB_ARB + bool "Broadcom STB GISB bus arbiter" + depends on ARM + help + Driver for the Broadcom Set Top Box System-on-a-chip internal bus + arbiter. This driver provides timeout and target abort error handling + and internal bus master decoding. + +config IMX_WEIM + bool "Freescale EIM DRIVER" + depends on ARCH_MXC + help + Driver for i.MX WEIM controller. + The WEIM(Wireless External Interface Module) works like a bus. + You can attach many different devices on it, such as NOR, onenand. + +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 @@ -18,4 +42,21 @@ config OMAP_INTERCONNECT help Driver to enable OMAP interconnect error handling driver. + +config ARM_CCI + bool "ARM CCI driver support" + depends on ARM && OF && CPU_V7 + help + Driver supporting the CCI cache coherent interconnect for ARM + platforms. + +config VEXPRESS_CONFIG + bool "Versatile Express configuration bus" + default y if ARCH_VEXPRESS + depends on ARM || ARM64 + depends on OF + select REGMAP + help + Platform configuration infrastructure for the ARM Ltd. + Versatile Express. endmenu diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index 45d997c8545..6a4ea7e4af1 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -2,7 +2,14 @@ # Makefile for the bus drivers. # +obj-$(CONFIG_BRCMSTB_GISB_ARB) += brcmstb_gisb.o +obj-$(CONFIG_IMX_WEIM) += imx-weim.o +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 + +obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o diff --git a/drivers/bus/arm-cci.c b/drivers/bus/arm-cci.c new file mode 100644 index 00000000000..5a86da97a70 --- /dev/null +++ b/drivers/bus/arm-cci.c @@ -0,0 +1,1132 @@ +/* + * 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/of_address.h> +#include <linux/of_irq.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#include <asm/cacheflush.h> +#include <asm/irq_regs.h> +#include <asm/pmu.h> +#include <asm/smp_plat.h> + +#define DRIVER_NAME "CCI-400" +#define DRIVER_NAME_PMU DRIVER_NAME " PMU" + +#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 + +#define CCI_PMCR 0x0100 +#define CCI_PID2 0x0fe8 + +#define CCI_PMCR_CEN 0x00000001 +#define CCI_PMCR_NCNT_MASK 0x0000f800 +#define CCI_PMCR_NCNT_SHIFT 11 + +#define CCI_PID2_REV_MASK 0xf0 +#define CCI_PID2_REV_SHIFT 4 + +/* Port ids */ +#define CCI_PORT_S0 0 +#define CCI_PORT_S1 1 +#define CCI_PORT_S2 2 +#define CCI_PORT_S3 3 +#define CCI_PORT_S4 4 +#define CCI_PORT_M0 5 +#define CCI_PORT_M1 6 +#define CCI_PORT_M2 7 + +#define CCI_REV_R0 0 +#define CCI_REV_R1 1 +#define CCI_REV_R1_PX 5 + +#define CCI_PMU_EVT_SEL 0x000 +#define CCI_PMU_CNTR 0x004 +#define CCI_PMU_CNTR_CTRL 0x008 +#define CCI_PMU_OVRFLW 0x00c + +#define CCI_PMU_OVRFLW_FLAG 1 + +#define CCI_PMU_CNTR_BASE(idx) ((idx) * SZ_4K) + +/* + * Instead of an event id to monitor CCI cycles, a dedicated counter is + * provided. Use 0xff to represent CCI cycles and hope that no future revisions + * make use of this event in hardware. + */ +enum cci400_perf_events { + CCI_PMU_CYCLES = 0xff +}; + +#define CCI_PMU_EVENT_MASK 0xff +#define CCI_PMU_EVENT_SOURCE(event) ((event >> 5) & 0x7) +#define CCI_PMU_EVENT_CODE(event) (event & 0x1f) + +#define CCI_PMU_MAX_HW_EVENTS 5 /* CCI PMU has 4 counters + 1 cycle counter */ + +#define CCI_PMU_CYCLE_CNTR_IDX 0 +#define CCI_PMU_CNTR0_IDX 1 +#define CCI_PMU_CNTR_LAST(cci_pmu) (CCI_PMU_CYCLE_CNTR_IDX + cci_pmu->num_events - 1) + +/* + * CCI PMU event id is an 8-bit value made of two parts - bits 7:5 for one of 8 + * ports and bits 4:0 are event codes. There are different event codes + * associated with each port type. + * + * Additionally, the range of events associated with the port types changed + * between Rev0 and Rev1. + * + * The constants below define the range of valid codes for each port type for + * the different revisions and are used to validate the event to be monitored. + */ + +#define CCI_REV_R0_SLAVE_PORT_MIN_EV 0x00 +#define CCI_REV_R0_SLAVE_PORT_MAX_EV 0x13 +#define CCI_REV_R0_MASTER_PORT_MIN_EV 0x14 +#define CCI_REV_R0_MASTER_PORT_MAX_EV 0x1a + +#define CCI_REV_R1_SLAVE_PORT_MIN_EV 0x00 +#define CCI_REV_R1_SLAVE_PORT_MAX_EV 0x14 +#define CCI_REV_R1_MASTER_PORT_MIN_EV 0x00 +#define CCI_REV_R1_MASTER_PORT_MAX_EV 0x11 + +struct pmu_port_event_ranges { + u8 slave_min; + u8 slave_max; + u8 master_min; + u8 master_max; +}; + +static struct pmu_port_event_ranges port_event_range[] = { + [CCI_REV_R0] = { + .slave_min = CCI_REV_R0_SLAVE_PORT_MIN_EV, + .slave_max = CCI_REV_R0_SLAVE_PORT_MAX_EV, + .master_min = CCI_REV_R0_MASTER_PORT_MIN_EV, + .master_max = CCI_REV_R0_MASTER_PORT_MAX_EV, + }, + [CCI_REV_R1] = { + .slave_min = CCI_REV_R1_SLAVE_PORT_MIN_EV, + .slave_max = CCI_REV_R1_SLAVE_PORT_MAX_EV, + .master_min = CCI_REV_R1_MASTER_PORT_MIN_EV, + .master_max = CCI_REV_R1_MASTER_PORT_MAX_EV, + }, +}; + +/* + * Export different PMU names for the different revisions so userspace knows + * because the event ids are different + */ +static char *const pmu_names[] = { + [CCI_REV_R0] = "CCI_400", + [CCI_REV_R1] = "CCI_400_r1", +}; + +struct cci_pmu_drv_data { + void __iomem *base; + struct arm_pmu *cci_pmu; + int nr_irqs; + int irqs[CCI_PMU_MAX_HW_EVENTS]; + unsigned long active_irqs; + struct perf_event *events[CCI_PMU_MAX_HW_EVENTS]; + unsigned long used_mask[BITS_TO_LONGS(CCI_PMU_MAX_HW_EVENTS)]; + struct pmu_port_event_ranges *port_ranges; + struct pmu_hw_events hw_events; +}; +static struct cci_pmu_drv_data *pmu; + +static bool is_duplicate_irq(int irq, int *irqs, int nr_irqs) +{ + int i; + + for (i = 0; i < nr_irqs; i++) + if (irq == irqs[i]) + return true; + + return false; +} + +static int probe_cci_revision(void) +{ + int rev; + rev = readl_relaxed(cci_ctrl_base + CCI_PID2) & CCI_PID2_REV_MASK; + rev >>= CCI_PID2_REV_SHIFT; + + if (rev < CCI_REV_R1_PX) + return CCI_REV_R0; + else + return CCI_REV_R1; +} + +static struct pmu_port_event_ranges *port_range_by_rev(void) +{ + int rev = probe_cci_revision(); + + return &port_event_range[rev]; +} + +static int pmu_is_valid_slave_event(u8 ev_code) +{ + return pmu->port_ranges->slave_min <= ev_code && + ev_code <= pmu->port_ranges->slave_max; +} + +static int pmu_is_valid_master_event(u8 ev_code) +{ + return pmu->port_ranges->master_min <= ev_code && + ev_code <= pmu->port_ranges->master_max; +} + +static int pmu_validate_hw_event(u8 hw_event) +{ + u8 ev_source = CCI_PMU_EVENT_SOURCE(hw_event); + u8 ev_code = CCI_PMU_EVENT_CODE(hw_event); + + switch (ev_source) { + case CCI_PORT_S0: + case CCI_PORT_S1: + case CCI_PORT_S2: + case CCI_PORT_S3: + case CCI_PORT_S4: + /* Slave Interface */ + if (pmu_is_valid_slave_event(ev_code)) + return hw_event; + break; + case CCI_PORT_M0: + case CCI_PORT_M1: + case CCI_PORT_M2: + /* Master Interface */ + if (pmu_is_valid_master_event(ev_code)) + return hw_event; + break; + } + + return -ENOENT; +} + +static int pmu_is_valid_counter(struct arm_pmu *cci_pmu, int idx) +{ + return CCI_PMU_CYCLE_CNTR_IDX <= idx && + idx <= CCI_PMU_CNTR_LAST(cci_pmu); +} + +static u32 pmu_read_register(int idx, unsigned int offset) +{ + return readl_relaxed(pmu->base + CCI_PMU_CNTR_BASE(idx) + offset); +} + +static void pmu_write_register(u32 value, int idx, unsigned int offset) +{ + return writel_relaxed(value, pmu->base + CCI_PMU_CNTR_BASE(idx) + offset); +} + +static void pmu_disable_counter(int idx) +{ + pmu_write_register(0, idx, CCI_PMU_CNTR_CTRL); +} + +static void pmu_enable_counter(int idx) +{ + pmu_write_register(1, idx, CCI_PMU_CNTR_CTRL); +} + +static void pmu_set_event(int idx, unsigned long event) +{ + event &= CCI_PMU_EVENT_MASK; + pmu_write_register(event, idx, CCI_PMU_EVT_SEL); +} + +static u32 pmu_get_max_counters(void) +{ + u32 n_cnts = (readl_relaxed(cci_ctrl_base + CCI_PMCR) & + CCI_PMCR_NCNT_MASK) >> CCI_PMCR_NCNT_SHIFT; + + /* add 1 for cycle counter */ + return n_cnts + 1; +} + +static struct pmu_hw_events *pmu_get_hw_events(void) +{ + return &pmu->hw_events; +} + +static int 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 & CCI_PMU_EVENT_MASK; + int idx; + + if (cci_event == CCI_PMU_CYCLES) { + if (test_and_set_bit(CCI_PMU_CYCLE_CNTR_IDX, hw->used_mask)) + return -EAGAIN; + + return CCI_PMU_CYCLE_CNTR_IDX; + } + + for (idx = CCI_PMU_CNTR0_IDX; idx <= CCI_PMU_CNTR_LAST(cci_pmu); ++idx) + if (!test_and_set_bit(idx, hw->used_mask)) + return idx; + + /* No counters available */ + return -EAGAIN; +} + +static int pmu_map_event(struct perf_event *event) +{ + int mapping; + u8 config = event->attr.config & CCI_PMU_EVENT_MASK; + + if (event->attr.type < PERF_TYPE_MAX) + return -ENOENT; + + if (config == CCI_PMU_CYCLES) + mapping = config; + else + mapping = pmu_validate_hw_event(config); + + return mapping; +} + +static int pmu_request_irq(struct arm_pmu *cci_pmu, irq_handler_t handler) +{ + int i; + struct platform_device *pmu_device = cci_pmu->plat_device; + + if (unlikely(!pmu_device)) + return -ENODEV; + + if (pmu->nr_irqs < 1) { + dev_err(&pmu_device->dev, "no irqs for CCI PMUs defined\n"); + return -ENODEV; + } + + /* + * Register all available CCI PMU interrupts. In the interrupt handler + * we iterate over the counters checking for interrupt source (the + * overflowing counter) and clear it. + * + * This should allow handling of non-unique interrupt for the counters. + */ + for (i = 0; i < pmu->nr_irqs; i++) { + int err = request_irq(pmu->irqs[i], handler, IRQF_SHARED, + "arm-cci-pmu", cci_pmu); + if (err) { + dev_err(&pmu_device->dev, "unable to request IRQ%d for ARM CCI PMU counters\n", + pmu->irqs[i]); + return err; + } + + set_bit(i, &pmu->active_irqs); + } + + return 0; +} + +static irqreturn_t pmu_handle_irq(int irq_num, void *dev) +{ + unsigned long flags; + 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, handled = IRQ_NONE; + + raw_spin_lock_irqsave(&events->pmu_lock, flags); + 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 = CCI_PMU_CYCLE_CNTR_IDX; idx <= CCI_PMU_CNTR_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 (!pmu_read_register(idx, CCI_PMU_OVRFLW) & CCI_PMU_OVRFLW_FLAG) + continue; + + pmu_write_register(CCI_PMU_OVRFLW_FLAG, idx, CCI_PMU_OVRFLW); + + handled = IRQ_HANDLED; + + 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); + } + raw_spin_unlock_irqrestore(&events->pmu_lock, flags); + + return IRQ_RETVAL(handled); +} + +static void pmu_free_irq(struct arm_pmu *cci_pmu) +{ + int i; + + for (i = 0; i < pmu->nr_irqs; i++) { + if (!test_and_clear_bit(i, &pmu->active_irqs)) + continue; + + free_irq(pmu->irqs[i], cci_pmu); + } +} + +static void 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(!pmu_is_valid_counter(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 != CCI_PMU_CYCLE_CNTR_IDX) + pmu_set_event(idx, hw_counter->config_base); + + pmu_enable_counter(idx); + + raw_spin_unlock_irqrestore(&events->pmu_lock, flags); +} + +static void pmu_disable_event(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; + + if (unlikely(!pmu_is_valid_counter(cci_pmu, idx))) { + dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx); + return; + } + + pmu_disable_counter(idx); +} + +static void 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_relaxed(cci_ctrl_base + CCI_PMCR) | CCI_PMCR_CEN; + writel(val, cci_ctrl_base + CCI_PMCR); + + raw_spin_unlock_irqrestore(&events->pmu_lock, flags); +} + +static void 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_relaxed(cci_ctrl_base + CCI_PMCR) & ~CCI_PMCR_CEN; + writel(val, cci_ctrl_base + CCI_PMCR); + + raw_spin_unlock_irqrestore(&events->pmu_lock, flags); +} + +static u32 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(!pmu_is_valid_counter(cci_pmu, idx))) { + dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx); + return 0; + } + value = pmu_read_register(idx, CCI_PMU_CNTR); + + return value; +} + +static void 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(!pmu_is_valid_counter(cci_pmu, idx))) + dev_err(&cci_pmu->plat_device->dev, "Invalid CCI PMU counter %d\n", idx); + else + pmu_write_register(value, idx, CCI_PMU_CNTR); +} + +static int cci_pmu_init(struct arm_pmu *cci_pmu, struct platform_device *pdev) +{ + *cci_pmu = (struct arm_pmu){ + .name = pmu_names[probe_cci_revision()], + .max_period = (1LLU << 32) - 1, + .get_hw_events = pmu_get_hw_events, + .get_event_idx = pmu_get_event_idx, + .map_event = pmu_map_event, + .request_irq = pmu_request_irq, + .handle_irq = pmu_handle_irq, + .free_irq = pmu_free_irq, + .enable = pmu_enable_event, + .disable = pmu_disable_event, + .start = pmu_start, + .stop = pmu_stop, + .read_counter = pmu_read_counter, + .write_counter = pmu_write_counter, + }; + + cci_pmu->plat_device = pdev; + cci_pmu->num_events = pmu_get_max_counters(); + + return armpmu_register(cci_pmu, -1); +} + +static const struct of_device_id arm_cci_pmu_matches[] = { + { + .compatible = "arm,cci-400-pmu", + }, + {}, +}; + +static int cci_pmu_probe(struct platform_device *pdev) +{ + struct resource *res; + int i, ret, irq; + + pmu = devm_kzalloc(&pdev->dev, sizeof(*pmu), GFP_KERNEL); + if (!pmu) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pmu->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pmu->base)) + return -ENOMEM; + + /* + * CCI PMU has 5 overflow signals - one per counter; but some may be tied + * together to a common interrupt. + */ + pmu->nr_irqs = 0; + for (i = 0; i < CCI_PMU_MAX_HW_EVENTS; i++) { + irq = platform_get_irq(pdev, i); + if (irq < 0) + break; + + if (is_duplicate_irq(irq, pmu->irqs, pmu->nr_irqs)) + continue; + + pmu->irqs[pmu->nr_irqs++] = irq; + } + + /* + * Ensure that the device tree has as many interrupts as the number + * of counters. + */ + if (i < CCI_PMU_MAX_HW_EVENTS) { + dev_warn(&pdev->dev, "In-correct number of interrupts: %d, should be %d\n", + i, CCI_PMU_MAX_HW_EVENTS); + return -EINVAL; + } + + pmu->port_ranges = port_range_by_rev(); + if (!pmu->port_ranges) { + dev_warn(&pdev->dev, "CCI PMU version not supported\n"); + return -EINVAL; + } + + pmu->cci_pmu = devm_kzalloc(&pdev->dev, sizeof(*(pmu->cci_pmu)), GFP_KERNEL); + if (!pmu->cci_pmu) + return -ENOMEM; + + pmu->hw_events.events = pmu->events; + pmu->hw_events.used_mask = pmu->used_mask; + raw_spin_lock_init(&pmu->hw_events.pmu_lock); + + ret = cci_pmu_init(pmu->cci_pmu, pdev); + if (ret) + return ret; + + return 0; +} + +static int cci_platform_probe(struct platform_device *pdev) +{ + if (!cci_probed()) + return -ENODEV; + + return of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); +} + +#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 cci_ace_init_ports(void) +{ + int port, cpu; + struct device_node *cpun; + + /* + * 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_possible_cpu(cpu) { + /* too early to use cpu->of_node */ + cpun = of_get_cpu_node(cpu, NULL); + + if (WARN(!cpun, "Missing cpu device node\n")) + 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" +" .arch armv7-a\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, %[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, %[cci_control_status_bits] \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)), + [cci_enable_req] "i" cpu_to_le32(CCI_ENABLE_REQ), + [cci_control_status_bits] "i" cpu_to_le32(1), +#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 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(nb_cci_ports, sizeof(*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 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; +} + +#ifdef CONFIG_HW_PERF_EVENTS +static struct platform_driver cci_pmu_driver = { + .driver = { + .name = DRIVER_NAME_PMU, + .of_match_table = arm_cci_pmu_matches, + }, + .probe = cci_pmu_probe, +}; + +static struct platform_driver cci_platform_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = arm_cci_matches, + }, + .probe = cci_platform_probe, +}; + +static int __init cci_platform_init(void) +{ + int ret; + + ret = platform_driver_register(&cci_pmu_driver); + if (ret) + return ret; + + return platform_driver_register(&cci_platform_driver); +} + +#else + +static int __init cci_platform_init(void) +{ + return 0; +} + +#endif +/* + * 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 cci_probed(void) +{ + return cci_init() == 0; +} +EXPORT_SYMBOL_GPL(cci_probed); + +early_initcall(cci_init); +core_initcall(cci_platform_init); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ARM CCI support"); diff --git a/drivers/bus/brcmstb_gisb.c b/drivers/bus/brcmstb_gisb.c new file mode 100644 index 00000000000..6159b7752a6 --- /dev/null +++ b/drivers/bus/brcmstb_gisb.c @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2014 Broadcom 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. + */ + +#include <linux/init.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/sysfs.h> +#include <linux/io.h> +#include <linux/string.h> +#include <linux/device.h> +#include <linux/list.h> +#include <linux/of.h> +#include <linux/bitops.h> + +#include <asm/bug.h> +#include <asm/signal.h> + +#define ARB_TIMER 0x008 +#define ARB_ERR_CAP_CLR 0x7e4 +#define ARB_ERR_CAP_CLEAR (1 << 0) +#define ARB_ERR_CAP_HI_ADDR 0x7e8 +#define ARB_ERR_CAP_ADDR 0x7ec +#define ARB_ERR_CAP_DATA 0x7f0 +#define ARB_ERR_CAP_STATUS 0x7f4 +#define ARB_ERR_CAP_STATUS_TIMEOUT (1 << 12) +#define ARB_ERR_CAP_STATUS_TEA (1 << 11) +#define ARB_ERR_CAP_STATUS_BS_SHIFT (1 << 2) +#define ARB_ERR_CAP_STATUS_BS_MASK 0x3c +#define ARB_ERR_CAP_STATUS_WRITE (1 << 1) +#define ARB_ERR_CAP_STATUS_VALID (1 << 0) +#define ARB_ERR_CAP_MASTER 0x7f8 + +struct brcmstb_gisb_arb_device { + void __iomem *base; + struct mutex lock; + struct list_head next; + u32 valid_mask; + const char *master_names[sizeof(u32) * BITS_PER_BYTE]; +}; + +static LIST_HEAD(brcmstb_gisb_arb_device_list); + +static ssize_t gisb_arb_get_timeout(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct brcmstb_gisb_arb_device *gdev = platform_get_drvdata(pdev); + u32 timeout; + + mutex_lock(&gdev->lock); + timeout = ioread32(gdev->base + ARB_TIMER); + mutex_unlock(&gdev->lock); + + return sprintf(buf, "%d", timeout); +} + +static ssize_t gisb_arb_set_timeout(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct brcmstb_gisb_arb_device *gdev = platform_get_drvdata(pdev); + int val, ret; + + ret = kstrtoint(buf, 10, &val); + if (ret < 0) + return ret; + + if (val == 0 || val >= 0xffffffff) + return -EINVAL; + + mutex_lock(&gdev->lock); + iowrite32(val, gdev->base + ARB_TIMER); + mutex_unlock(&gdev->lock); + + return count; +} + +static const char * +brcmstb_gisb_master_to_str(struct brcmstb_gisb_arb_device *gdev, + u32 masters) +{ + u32 mask = gdev->valid_mask & masters; + + if (hweight_long(mask) != 1) + return NULL; + + return gdev->master_names[ffs(mask) - 1]; +} + +static int brcmstb_gisb_arb_decode_addr(struct brcmstb_gisb_arb_device *gdev, + const char *reason) +{ + u32 cap_status; + unsigned long arb_addr; + u32 master; + const char *m_name; + char m_fmt[11]; + + cap_status = ioread32(gdev->base + ARB_ERR_CAP_STATUS); + + /* Invalid captured address, bail out */ + if (!(cap_status & ARB_ERR_CAP_STATUS_VALID)) + return 1; + + /* Read the address and master */ + arb_addr = ioread32(gdev->base + ARB_ERR_CAP_ADDR) & 0xffffffff; +#if (IS_ENABLED(CONFIG_PHYS_ADDR_T_64BIT)) + arb_addr |= (u64)ioread32(gdev->base + ARB_ERR_CAP_HI_ADDR) << 32; +#endif + master = ioread32(gdev->base + ARB_ERR_CAP_MASTER); + + m_name = brcmstb_gisb_master_to_str(gdev, master); + if (!m_name) { + snprintf(m_fmt, sizeof(m_fmt), "0x%08x", master); + m_name = m_fmt; + } + + pr_crit("%s: %s at 0x%lx [%c %s], core: %s\n", + __func__, reason, arb_addr, + cap_status & ARB_ERR_CAP_STATUS_WRITE ? 'W' : 'R', + cap_status & ARB_ERR_CAP_STATUS_TIMEOUT ? "timeout" : "", + m_name); + + /* clear the GISB error */ + iowrite32(ARB_ERR_CAP_CLEAR, gdev->base + ARB_ERR_CAP_CLR); + + return 0; +} + +static int brcmstb_bus_error_handler(unsigned long addr, unsigned int fsr, + struct pt_regs *regs) +{ + int ret = 0; + struct brcmstb_gisb_arb_device *gdev; + + /* iterate over each GISB arb registered handlers */ + list_for_each_entry(gdev, &brcmstb_gisb_arb_device_list, next) + ret |= brcmstb_gisb_arb_decode_addr(gdev, "bus error"); + /* + * If it was an imprecise abort, then we need to correct the + * return address to be _after_ the instruction. + */ + if (fsr & (1 << 10)) + regs->ARM_pc += 4; + + return ret; +} + +void __init brcmstb_hook_fault_code(void) +{ + hook_fault_code(22, brcmstb_bus_error_handler, SIGBUS, 0, + "imprecise external abort"); +} + +static irqreturn_t brcmstb_gisb_timeout_handler(int irq, void *dev_id) +{ + brcmstb_gisb_arb_decode_addr(dev_id, "timeout"); + + return IRQ_HANDLED; +} + +static irqreturn_t brcmstb_gisb_tea_handler(int irq, void *dev_id) +{ + brcmstb_gisb_arb_decode_addr(dev_id, "target abort"); + + return IRQ_HANDLED; +} + +static DEVICE_ATTR(gisb_arb_timeout, S_IWUSR | S_IRUGO, + gisb_arb_get_timeout, gisb_arb_set_timeout); + +static struct attribute *gisb_arb_sysfs_attrs[] = { + &dev_attr_gisb_arb_timeout.attr, + NULL, +}; + +static struct attribute_group gisb_arb_sysfs_attr_group = { + .attrs = gisb_arb_sysfs_attrs, +}; + +static int brcmstb_gisb_arb_probe(struct platform_device *pdev) +{ + struct device_node *dn = pdev->dev.of_node; + struct brcmstb_gisb_arb_device *gdev; + struct resource *r; + int err, timeout_irq, tea_irq; + unsigned int num_masters, j = 0; + int i, first, last; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + timeout_irq = platform_get_irq(pdev, 0); + tea_irq = platform_get_irq(pdev, 1); + + gdev = devm_kzalloc(&pdev->dev, sizeof(*gdev), GFP_KERNEL); + if (!gdev) + return -ENOMEM; + + mutex_init(&gdev->lock); + INIT_LIST_HEAD(&gdev->next); + + gdev->base = devm_request_and_ioremap(&pdev->dev, r); + if (!gdev->base) + return -ENOMEM; + + err = devm_request_irq(&pdev->dev, timeout_irq, + brcmstb_gisb_timeout_handler, 0, pdev->name, + gdev); + if (err < 0) + return err; + + err = devm_request_irq(&pdev->dev, tea_irq, + brcmstb_gisb_tea_handler, 0, pdev->name, + gdev); + if (err < 0) + return err; + + /* If we do not have a valid mask, assume all masters are enabled */ + if (of_property_read_u32(dn, "brcm,gisb-arb-master-mask", + &gdev->valid_mask)) + gdev->valid_mask = 0xffffffff; + + /* Proceed with reading the litteral names if we agree on the + * number of masters + */ + num_masters = of_property_count_strings(dn, + "brcm,gisb-arb-master-names"); + if (hweight_long(gdev->valid_mask) == num_masters) { + first = ffs(gdev->valid_mask) - 1; + last = fls(gdev->valid_mask) - 1; + + for (i = first; i < last; i++) { + if (!(gdev->valid_mask & BIT(i))) + continue; + + of_property_read_string_index(dn, + "brcm,gisb-arb-master-names", j, + &gdev->master_names[i]); + j++; + } + } + + err = sysfs_create_group(&pdev->dev.kobj, &gisb_arb_sysfs_attr_group); + if (err) + return err; + + platform_set_drvdata(pdev, gdev); + + list_add_tail(&gdev->next, &brcmstb_gisb_arb_device_list); + + dev_info(&pdev->dev, "registered mem: %p, irqs: %d, %d\n", + gdev->base, timeout_irq, tea_irq); + + return 0; +} + +static const struct of_device_id brcmstb_gisb_arb_of_match[] = { + { .compatible = "brcm,gisb-arb" }, + { }, +}; + +static struct platform_driver brcmstb_gisb_arb_driver = { + .probe = brcmstb_gisb_arb_probe, + .driver = { + .name = "brcm-gisb-arb", + .owner = THIS_MODULE, + .of_match_table = brcmstb_gisb_arb_of_match, + }, +}; + +static int __init brcm_gisb_driver_init(void) +{ + return platform_driver_register(&brcmstb_gisb_arb_driver); +} + +module_init(brcm_gisb_driver_init); diff --git a/drivers/bus/imx-weim.c b/drivers/bus/imx-weim.c new file mode 100644 index 00000000000..f8ee13c7bf7 --- /dev/null +++ b/drivers/bus/imx-weim.c @@ -0,0 +1,215 @@ +/* + * EIM driver for Freescale's i.MX chips + * + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/of_device.h> +#include <linux/mfd/syscon.h> +#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h> +#include <linux/regmap.h> + +struct imx_weim_devtype { + unsigned int cs_count; + unsigned int cs_regs_count; + unsigned int cs_stride; +}; + +static const struct imx_weim_devtype imx1_weim_devtype = { + .cs_count = 6, + .cs_regs_count = 2, + .cs_stride = 0x08, +}; + +static const struct imx_weim_devtype imx27_weim_devtype = { + .cs_count = 6, + .cs_regs_count = 3, + .cs_stride = 0x10, +}; + +static const struct imx_weim_devtype imx50_weim_devtype = { + .cs_count = 4, + .cs_regs_count = 6, + .cs_stride = 0x18, +}; + +static const struct imx_weim_devtype imx51_weim_devtype = { + .cs_count = 6, + .cs_regs_count = 6, + .cs_stride = 0x18, +}; + +static const struct of_device_id weim_id_table[] = { + /* i.MX1/21 */ + { .compatible = "fsl,imx1-weim", .data = &imx1_weim_devtype, }, + /* i.MX25/27/31/35 */ + { .compatible = "fsl,imx27-weim", .data = &imx27_weim_devtype, }, + /* i.MX50/53/6Q */ + { .compatible = "fsl,imx50-weim", .data = &imx50_weim_devtype, }, + { .compatible = "fsl,imx6q-weim", .data = &imx50_weim_devtype, }, + /* i.MX51 */ + { .compatible = "fsl,imx51-weim", .data = &imx51_weim_devtype, }, + { } +}; +MODULE_DEVICE_TABLE(of, weim_id_table); + +static int __init imx_weim_gpr_setup(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct property *prop; + const __be32 *p; + struct regmap *gpr; + u32 gprvals[4] = { + 05, /* CS0(128M) CS1(0M) CS2(0M) CS3(0M) */ + 033, /* CS0(64M) CS1(64M) CS2(0M) CS3(0M) */ + 0113, /* CS0(64M) CS1(32M) CS2(32M) CS3(0M) */ + 01111, /* CS0(32M) CS1(32M) CS2(32M) CS3(32M) */ + }; + u32 gprval = 0; + u32 val; + int cs = 0; + int i = 0; + + gpr = syscon_regmap_lookup_by_phandle(np, "fsl,weim-cs-gpr"); + if (IS_ERR(gpr)) { + dev_dbg(&pdev->dev, "failed to find weim-cs-gpr\n"); + return 0; + } + + of_property_for_each_u32(np, "ranges", prop, p, val) { + if (i % 4 == 0) { + cs = val; + } else if (i % 4 == 3 && val) { + val = (val / SZ_32M) | 1; + gprval |= val << cs * 3; + } + i++; + } + + if (i == 0 || i % 4) + goto err; + + for (i = 0; i < ARRAY_SIZE(gprvals); i++) { + if (gprval == gprvals[i]) { + /* Found it. Set up IOMUXC_GPR1[11:0] with it. */ + regmap_update_bits(gpr, IOMUXC_GPR1, 0xfff, gprval); + return 0; + } + } + +err: + dev_err(&pdev->dev, "Invalid 'ranges' configuration\n"); + return -EINVAL; +} + +/* Parse and set the timing for this device. */ +static int __init weim_timing_setup(struct device_node *np, void __iomem *base, + const struct imx_weim_devtype *devtype) +{ + u32 cs_idx, value[devtype->cs_regs_count]; + int i, ret; + + /* get the CS index from this child node's "reg" property. */ + ret = of_property_read_u32(np, "reg", &cs_idx); + if (ret) + return ret; + + if (cs_idx >= devtype->cs_count) + return -EINVAL; + + ret = of_property_read_u32_array(np, "fsl,weim-cs-timing", + value, devtype->cs_regs_count); + if (ret) + return ret; + + /* set the timing for WEIM */ + for (i = 0; i < devtype->cs_regs_count; i++) + writel(value[i], base + cs_idx * devtype->cs_stride + i * 4); + + return 0; +} + +static int __init weim_parse_dt(struct platform_device *pdev, + void __iomem *base) +{ + const struct of_device_id *of_id = of_match_device(weim_id_table, + &pdev->dev); + const struct imx_weim_devtype *devtype = of_id->data; + struct device_node *child; + int ret; + + if (devtype == &imx50_weim_devtype) { + ret = imx_weim_gpr_setup(pdev); + if (ret) + return ret; + } + + for_each_child_of_node(pdev->dev.of_node, child) { + if (!child->name) + continue; + + ret = weim_timing_setup(child, base, devtype); + if (ret) { + dev_err(&pdev->dev, "%s set timing failed.\n", + child->full_name); + return ret; + } + } + + ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); + if (ret) + dev_err(&pdev->dev, "%s fail to create devices.\n", + pdev->dev.of_node->full_name); + return ret; +} + +static int __init weim_probe(struct platform_device *pdev) +{ + struct resource *res; + struct clk *clk; + void __iomem *base; + int ret; + + /* get the resource */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + /* get the clock */ + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + ret = clk_prepare_enable(clk); + if (ret) + return ret; + + /* parse the device node */ + ret = weim_parse_dt(pdev, base); + if (ret) + clk_disable_unprepare(clk); + else + dev_info(&pdev->dev, "Driver registered.\n"); + + return ret; +} + +static struct platform_driver weim_driver = { + .driver = { + .name = "imx-weim", + .owner = THIS_MODULE, + .of_match_table = weim_id_table, + }, +}; +module_platform_driver_probe(weim_driver, weim_probe); + +MODULE_AUTHOR("Freescale Semiconductor Inc."); +MODULE_DESCRIPTION("i.MX EIM Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/bus/mvebu-mbus.c b/drivers/bus/mvebu-mbus.c new file mode 100644 index 00000000000..26c3779d871 --- /dev/null +++ b/drivers/bus/mvebu-mbus.c @@ -0,0 +1,943 @@ +/* + * 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_by_id(), + * mvebu_mbus_add_window_remap_by_id() and + * mvebu_mbus_del_window(). + * + * - 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'). + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#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> +#include <linux/log2.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_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); +}; + +struct mvebu_mbus_state { + void __iomem *mbuswins_base; + void __iomem *sdramwins_base; + struct dentry *debugfs_root; + struct dentry *debugfs_sdram; + struct dentry *debugfs_devs; + struct resource pcie_mem_aperture; + struct resource pcie_io_aperture; + 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; + + if (!is_power_of_2(size)) { + WARN(true, "Invalid MBus window size: 0x%zx\n", size); + return -EINVAL; + } + + if ((base & (phys_addr_t)(size - 1)) != 0) { + WARN(true, "Invalid MBus base/size: %pa len 0x%zx\n", &base, + size); + return -EINVAL; + } + + 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; + + mvebu_mbus_read_window(mbus, win, + &enabled, &wbase, &wsize, + &wtarget, &wattr, &wremap); + + if (!enabled) { + seq_printf(seq, "[%02d] disabled\n", win); + continue; + } + + seq_printf(seq, "[%02d] %016llx - %016llx : %04x:%04x", + win, (unsigned long long)wbase, + (unsigned long long)(wbase + wsize), wtarget, wattr); + + if (!is_power_of_2(wsize) || + ((wbase & (u64)(wsize - 1)) != 0)) + seq_puts(seq, " (Invalid base/size!!)"); + + 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_soc_data armada_370_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, +}; + +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, +}; + +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, +}; + +/* + * 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, +}; + +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, +}; + +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, +}; + +static const struct of_device_id of_mvebu_mbus_ids[] = { + { .compatible = "marvell,armada370-mbus", + .data = &armada_370_xp_mbus_data, }, + { .compatible = "marvell,armadaxp-mbus", + .data = &armada_370_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_by_id(unsigned int target, + unsigned int attribute, + phys_addr_t base, size_t size, + phys_addr_t remap) +{ + struct mvebu_mbus_state *s = &mbus_state; + + if (!mvebu_mbus_window_conflicts(s, base, size, target, attribute)) { + pr_err("cannot add window '%x:%x', conflicts with another window\n", + target, attribute); + return -EINVAL; + } + + return mvebu_mbus_alloc_window(s, base, size, remap, target, attribute); +} + +int mvebu_mbus_add_window_by_id(unsigned int target, unsigned int attribute, + phys_addr_t base, size_t size) +{ + return mvebu_mbus_add_window_remap_by_id(target, attribute, base, + size, MVEBU_MBUS_NO_REMAP); +} + +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; +} + +void mvebu_mbus_get_pcie_mem_aperture(struct resource *res) +{ + if (!res) + return; + *res = mbus_state.pcie_mem_aperture; +} + +void mvebu_mbus_get_pcie_io_aperture(struct resource *res) +{ + if (!res) + return; + *res = mbus_state.pcie_io_aperture; +} + +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); + +static int __init mvebu_mbus_common_init(struct mvebu_mbus_state *mbus, + phys_addr_t mbuswins_phys_base, + size_t mbuswins_size, + phys_addr_t sdramwins_phys_base, + size_t sdramwins_size) +{ + int win; + + 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; + } + + for (win = 0; win < mbus->soc->num_wins; win++) + mvebu_mbus_disable_window(mbus, win); + + mbus->soc->setup_cpu_target(mbus); + + return 0; +} + +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) +{ + const struct of_device_id *of_id; + + for (of_id = of_mvebu_mbus_ids; of_id->compatible[0]; of_id++) + if (!strcmp(of_id->compatible, soc)) + break; + + if (!of_id->compatible[0]) { + pr_err("could not find a matching SoC family\n"); + return -ENODEV; + } + + mbus_state.soc = of_id->data; + + return mvebu_mbus_common_init(&mbus_state, + mbuswins_phys_base, + mbuswins_size, + sdramwins_phys_base, + sdramwins_size); +} + +#ifdef CONFIG_OF +/* + * The window IDs in the ranges DT property have the following format: + * - bits 28 to 31: MBus custom field + * - bits 24 to 27: window target ID + * - bits 16 to 23: window attribute ID + * - bits 0 to 15: unused + */ +#define CUSTOM(id) (((id) & 0xF0000000) >> 24) +#define TARGET(id) (((id) & 0x0F000000) >> 24) +#define ATTR(id) (((id) & 0x00FF0000) >> 16) + +static int __init mbus_dt_setup_win(struct mvebu_mbus_state *mbus, + u32 base, u32 size, + u8 target, u8 attr) +{ + if (!mvebu_mbus_window_conflicts(mbus, base, size, target, attr)) { + pr_err("cannot add window '%04x:%04x', conflicts with another window\n", + target, attr); + return -EBUSY; + } + + if (mvebu_mbus_alloc_window(mbus, base, size, MVEBU_MBUS_NO_REMAP, + target, attr)) { + pr_err("cannot add window '%04x:%04x', too many windows\n", + target, attr); + return -ENOMEM; + } + return 0; +} + +static int __init +mbus_parse_ranges(struct device_node *node, + int *addr_cells, int *c_addr_cells, int *c_size_cells, + int *cell_count, const __be32 **ranges_start, + const __be32 **ranges_end) +{ + const __be32 *prop; + int ranges_len, tuple_len; + + /* Allow a node with no 'ranges' property */ + *ranges_start = of_get_property(node, "ranges", &ranges_len); + if (*ranges_start == NULL) { + *addr_cells = *c_addr_cells = *c_size_cells = *cell_count = 0; + *ranges_start = *ranges_end = NULL; + return 0; + } + *ranges_end = *ranges_start + ranges_len / sizeof(__be32); + + *addr_cells = of_n_addr_cells(node); + + prop = of_get_property(node, "#address-cells", NULL); + *c_addr_cells = be32_to_cpup(prop); + + prop = of_get_property(node, "#size-cells", NULL); + *c_size_cells = be32_to_cpup(prop); + + *cell_count = *addr_cells + *c_addr_cells + *c_size_cells; + tuple_len = (*cell_count) * sizeof(__be32); + + if (ranges_len % tuple_len) { + pr_warn("malformed ranges entry '%s'\n", node->name); + return -EINVAL; + } + return 0; +} + +static int __init mbus_dt_setup(struct mvebu_mbus_state *mbus, + struct device_node *np) +{ + int addr_cells, c_addr_cells, c_size_cells; + int i, ret, cell_count; + const __be32 *r, *ranges_start, *ranges_end; + + ret = mbus_parse_ranges(np, &addr_cells, &c_addr_cells, + &c_size_cells, &cell_count, + &ranges_start, &ranges_end); + if (ret < 0) + return ret; + + for (i = 0, r = ranges_start; r < ranges_end; r += cell_count, i++) { + u32 windowid, base, size; + u8 target, attr; + + /* + * An entry with a non-zero custom field do not + * correspond to a static window, so skip it. + */ + windowid = of_read_number(r, 1); + if (CUSTOM(windowid)) + continue; + + target = TARGET(windowid); + attr = ATTR(windowid); + + base = of_read_number(r + c_addr_cells, addr_cells); + size = of_read_number(r + c_addr_cells + addr_cells, + c_size_cells); + ret = mbus_dt_setup_win(mbus, base, size, target, attr); + if (ret < 0) + return ret; + } + return 0; +} + +static void __init mvebu_mbus_get_pcie_resources(struct device_node *np, + struct resource *mem, + struct resource *io) +{ + u32 reg[2]; + int ret; + + /* + * These are optional, so we make sure that resource_size(x) will + * return 0. + */ + memset(mem, 0, sizeof(struct resource)); + mem->end = -1; + memset(io, 0, sizeof(struct resource)); + io->end = -1; + + ret = of_property_read_u32_array(np, "pcie-mem-aperture", reg, ARRAY_SIZE(reg)); + if (!ret) { + mem->start = reg[0]; + mem->end = mem->start + reg[1] - 1; + mem->flags = IORESOURCE_MEM; + } + + ret = of_property_read_u32_array(np, "pcie-io-aperture", reg, ARRAY_SIZE(reg)); + if (!ret) { + io->start = reg[0]; + io->end = io->start + reg[1] - 1; + io->flags = IORESOURCE_IO; + } +} + +int __init mvebu_mbus_dt_init(bool is_coherent) +{ + struct resource mbuswins_res, sdramwins_res; + struct device_node *np, *controller; + const struct of_device_id *of_id; + const __be32 *prop; + int ret; + + np = of_find_matching_node_and_match(NULL, of_mvebu_mbus_ids, &of_id); + if (!np) { + pr_err("could not find a matching SoC family\n"); + return -ENODEV; + } + + mbus_state.soc = of_id->data; + + prop = of_get_property(np, "controller", NULL); + if (!prop) { + pr_err("required 'controller' property missing\n"); + return -EINVAL; + } + + controller = of_find_node_by_phandle(be32_to_cpup(prop)); + if (!controller) { + pr_err("could not find an 'mbus-controller' node\n"); + return -ENODEV; + } + + if (of_address_to_resource(controller, 0, &mbuswins_res)) { + pr_err("cannot get MBUS register address\n"); + return -EINVAL; + } + + if (of_address_to_resource(controller, 1, &sdramwins_res)) { + pr_err("cannot get SDRAM register address\n"); + return -EINVAL; + } + + mbus_state.hw_io_coherency = is_coherent; + + /* Get optional pcie-{mem,io}-aperture properties */ + mvebu_mbus_get_pcie_resources(np, &mbus_state.pcie_mem_aperture, + &mbus_state.pcie_io_aperture); + + ret = mvebu_mbus_common_init(&mbus_state, + mbuswins_res.start, + resource_size(&mbuswins_res), + sdramwins_res.start, + resource_size(&sdramwins_res)); + if (ret) + return ret; + + /* Setup statically declared windows in the DT */ + return mbus_dt_setup(&mbus_state, np); +} +#endif diff --git a/drivers/bus/omap-ocp2scp.c b/drivers/bus/omap-ocp2scp.c index ff63560b846..5511f9814dd 100644 --- a/drivers/bus/omap-ocp2scp.c +++ b/drivers/bus/omap-ocp2scp.c @@ -32,18 +32,20 @@ static int ocp2scp_remove_devices(struct device *dev, void *c) return 0; } -static int __devinit omap_ocp2scp_probe(struct platform_device *pdev) +static int omap_ocp2scp_probe(struct platform_device *pdev) { - int ret; - struct device_node *np = pdev->dev.of_node; + int ret; + struct device_node *np = pdev->dev.of_node; 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"); + dev_err(&pdev->dev, + "failed to add resources for ocp2scp child\n"); goto err0; } } + pm_runtime_enable(&pdev->dev); return 0; @@ -54,7 +56,7 @@ err0: return ret; } -static int __devexit omap_ocp2scp_remove(struct platform_device *pdev) +static int omap_ocp2scp_remove(struct platform_device *pdev) { pm_runtime_disable(&pdev->dev); device_for_each_child(&pdev->dev, NULL, ocp2scp_remove_devices); @@ -72,7 +74,7 @@ MODULE_DEVICE_TABLE(of, omap_ocp2scp_id_table); static struct platform_driver omap_ocp2scp_driver = { .probe = omap_ocp2scp_probe, - .remove = __devexit_p(omap_ocp2scp_remove), + .remove = omap_ocp2scp_remove, .driver = { .name = "omap-ocp2scp", .owner = THIS_MODULE, diff --git a/drivers/bus/omap_l3_noc.c b/drivers/bus/omap_l3_noc.c index ab911a33f8a..531ae591783 100644 --- a/drivers/bus/omap_l3_noc.c +++ b/drivers/bus/omap_l3_noc.c @@ -1,43 +1,45 @@ /* - * OMAP4XXX L3 Interconnect error handling driver + * OMAP L3 Interconnect error handling driver * - * Copyright (C) 2011 Texas Corporation + * Copyright (C) 2011-2014 Texas Instruments Incorporated - http://www.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. + * 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 + * 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. - * - * 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/io.h> #include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of.h> +#include <linux/platform_device.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. +/** + * l3_handle_target() - Handle Target specific parse and reporting + * @l3: pointer to l3 struct + * @base: base address of clkdm + * @flag_mux: flagmux corresponding to the event + * @err_src: error source index of the slave (target) * - * Two Types of errors : + * This does the second part of the error interrupt handling: + * 3) Parse in the slave information + * 4) Print the logged information. + * 5) Add dump stack to provide kernel trace. + * 6) Clear the source if known. + * + * This handles two types of errors: * 1) Custom errors in L3 : * Target like DMM/FW/EMIF generates SRESP=ERR error * 2) Standard L3 error: @@ -53,214 +55,264 @@ * 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) +static int l3_handle_target(struct omap_l3 *l3, void __iomem *base, + struct l3_flagmux_data *flag_mux, int err_src) { + int k; + u32 std_err_main, clear, masterid; + u8 op_code, m_req_info; + void __iomem *l3_targ_base; + void __iomem *l3_targ_stderr, *l3_targ_slvofslsb, *l3_targ_mstaddr; + void __iomem *l3_targ_hdr, *l3_targ_info; + struct l3_target_data *l3_targ_inst; + struct l3_masters_data *master; + char *target_name, *master_name = "UN IDENTIFIED"; + char *err_description; + char err_string[30] = { 0 }; + char info_string[60] = { 0 }; + + /* We DONOT expect err_src to go out of bounds */ + BUG_ON(err_src > MAX_CLKDM_TARGETS); + + if (err_src < flag_mux->num_targ_data) { + l3_targ_inst = &flag_mux->l3_targ[err_src]; + target_name = l3_targ_inst->name; + l3_targ_base = base + l3_targ_inst->offset; + } else { + target_name = L3_TARGET_NOT_SUPPORTED; + } - struct omap4_l3 *l3 = _l3; - int inttype, i, k; + if (target_name == L3_TARGET_NOT_SUPPORTED) + return -ENODEV; + + /* Read the stderrlog_main_source from clk domain */ + l3_targ_stderr = l3_targ_base + L3_TARG_STDERRLOG_MAIN; + l3_targ_slvofslsb = l3_targ_base + L3_TARG_STDERRLOG_SLVOFSLSB; + + std_err_main = readl_relaxed(l3_targ_stderr); + + switch (std_err_main & CUSTOM_ERROR) { + case STANDARD_ERROR: + err_description = "Standard"; + snprintf(err_string, sizeof(err_string), + ": At Address: 0x%08X ", + readl_relaxed(l3_targ_slvofslsb)); + + l3_targ_mstaddr = l3_targ_base + L3_TARG_STDERRLOG_MSTADDR; + l3_targ_hdr = l3_targ_base + L3_TARG_STDERRLOG_HDR; + l3_targ_info = l3_targ_base + L3_TARG_STDERRLOG_INFO; + break; + + case CUSTOM_ERROR: + err_description = "Custom"; + + l3_targ_mstaddr = l3_targ_base + + L3_TARG_STDERRLOG_CINFO_MSTADDR; + l3_targ_hdr = l3_targ_base + L3_TARG_STDERRLOG_CINFO_OPCODE; + l3_targ_info = l3_targ_base + L3_TARG_STDERRLOG_CINFO_INFO; + break; + + default: + /* Nothing to be handled here as of now */ + return 0; + } + + /* STDERRLOG_MSTADDR Stores the NTTP master address. */ + masterid = (readl_relaxed(l3_targ_mstaddr) & + l3->mst_addr_mask) >> __ffs(l3->mst_addr_mask); + + for (k = 0, master = l3->l3_masters; k < l3->num_masters; + k++, master++) { + if (masterid == master->id) { + master_name = master->name; + break; + } + } + + op_code = readl_relaxed(l3_targ_hdr) & 0x7; + + m_req_info = readl_relaxed(l3_targ_info) & 0xF; + snprintf(info_string, sizeof(info_string), + ": %s in %s mode during %s access", + (m_req_info & BIT(0)) ? "Opcode Fetch" : "Data Access", + (m_req_info & BIT(1)) ? "Supervisor" : "User", + (m_req_info & BIT(3)) ? "Debug" : "Functional"); + + WARN(true, + "%s:L3 %s Error: MASTER %s TARGET %s (%s)%s%s\n", + dev_name(l3->dev), + err_description, + master_name, target_name, + l3_transaction_type[op_code], + err_string, info_string); + + /* clear the std error log*/ + clear = std_err_main | CLEAR_STDERR_LOG; + writel_relaxed(clear, l3_targ_stderr); + + return 0; +} + +/** + * l3_interrupt_handler() - interrupt handler for l3 events + * @irq: irq number + * @_l3: pointer to l3 structure + * + * 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 + * ... handle the slave event.. + * 7) if the slave is unknown, mask out the slave. + */ +static irqreturn_t l3_interrupt_handler(int irq, void *_l3) +{ + struct omap_l3 *l3 = _l3; + int inttype, i, ret; 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"; + u32 err_reg, mask_val; + void __iomem *base, *mask_reg; + struct l3_flagmux_data *flag_mux; /* Get the Type of interrupt */ inttype = irq == l3->app_irq ? L3_APPLICATION_ERROR : L3_DEBUG_ERROR; - for (i = 0; i < L3_MODULES; i++) { + for (i = 0; i < l3->num_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)); + flag_mux = l3->l3_flagmux[i]; + err_reg = readl_relaxed(base + flag_mux->offset + + L3_FLAGMUX_REGERR0 + (inttype << 3)); + + err_reg &= ~(inttype ? flag_mux->mask_app_bits : + flag_mux->mask_dbg_bits); /* 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; + ret = l3_handle_target(l3, base, flag_mux, err_src); + + /* + * Certain plaforms may have "undocumented" status + * pending on boot. So dont generate a severe warning + * here. Just mask it off to prevent the error from + * reoccuring and locking up the system. + */ + if (ret) { + dev_err(l3->dev, + "L3 %s error: target %d mod:%d %s\n", + inttype ? "debug" : "application", + err_src, i, "(unclearable)"); + + mask_reg = base + flag_mux->offset + + L3_FLAGMUX_MASK0 + (inttype << 3); + mask_val = readl_relaxed(mask_reg); + mask_val &= ~(1 << err_src); + writel_relaxed(mask_val, mask_reg); + + /* Mark these bits as to be ignored */ + if (inttype) + flag_mux->mask_app_bits |= 1 << err_src; + else + flag_mux->mask_dbg_bits |= 1 << err_src; } - /* Error found so break the for loop */ - break; + + /* Error found so break the for loop */ + break; } } return IRQ_HANDLED; } -static int __devinit omap4_l3_probe(struct platform_device *pdev) +static const struct of_device_id l3_noc_match[] = { + {.compatible = "ti,omap4-l3-noc", .data = &omap_l3_data}, + {.compatible = "ti,dra7-l3-noc", .data = &dra_l3_data}, + {.compatible = "ti,am4372-l3-noc", .data = &am4372_l3_data}, + {}, +}; +MODULE_DEVICE_TABLE(of, l3_noc_match); + +static int omap_l3_probe(struct platform_device *pdev) { - static struct omap4_l3 *l3; - struct resource *res; - int ret; + const struct of_device_id *of_id; + static struct omap_l3 *l3; + int ret, i, res_idx; + + of_id = of_match_device(l3_noc_match, &pdev->dev); + if (!of_id) { + dev_err(&pdev->dev, "OF data missing\n"); + return -EINVAL; + } - l3 = kzalloc(sizeof(*l3), GFP_KERNEL); + l3 = devm_kzalloc(&pdev->dev, sizeof(*l3), GFP_KERNEL); if (!l3) return -ENOMEM; + memcpy(l3, of_id->data, sizeof(*l3)); + l3->dev = &pdev->dev; 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; - } + /* Get mem resources */ + for (i = 0, res_idx = 0; i < l3->num_modules; i++) { + struct resource *res; - 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; + if (l3->l3_base[i] == L3_BASE_IS_SUBMODULE) { + /* First entry cannot be submodule */ + BUG_ON(i == 0); + l3->l3_base[i] = l3->l3_base[i - 1]; + continue; + } + res = platform_get_resource(pdev, IORESOURCE_MEM, res_idx); + l3->l3_base[i] = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(l3->l3_base[i])) { + dev_err(l3->dev, "ioremap %d failed\n", i); + return PTR_ERR(l3->l3_base[i]); + } + res_idx++; } /* * 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); + ret = devm_request_irq(l3->dev, 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; + dev_err(l3->dev, "request_irq failed for %d\n", + l3->debug_irq); + return ret; } 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; - } + ret = devm_request_irq(l3->dev, l3->app_irq, l3_interrupt_handler, + IRQF_DISABLED, "l3-app-irq", l3); + if (ret) + dev_err(l3->dev, "request_irq failed for %d\n", l3->app_irq); - 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 __devexit 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 = __devexit_p(omap4_l3_remove), +static struct platform_driver omap_l3_driver = { + .probe = omap_l3_probe, .driver = { .name = "omap_l3_noc", .owner = THIS_MODULE, - .of_match_table = l3_noc_match, + .of_match_table = of_match_ptr(l3_noc_match), }, }; -static int __init omap4_l3_init(void) +static int __init omap_l3_init(void) { - return platform_driver_register(&omap4_l3_driver); + return platform_driver_register(&omap_l3_driver); } -postcore_initcall_sync(omap4_l3_init); +postcore_initcall_sync(omap_l3_init); -static void __exit omap4_l3_exit(void) +static void __exit omap_l3_exit(void) { - platform_driver_unregister(&omap4_l3_driver); + platform_driver_unregister(&omap_l3_driver); } -module_exit(omap4_l3_exit); +module_exit(omap_l3_exit); diff --git a/drivers/bus/omap_l3_noc.h b/drivers/bus/omap_l3_noc.h index a6ce34dc481..551e0106143 100644 --- a/drivers/bus/omap_l3_noc.h +++ b/drivers/bus/omap_l3_noc.h @@ -1,29 +1,25 @@ /* - * OMAP4XXX L3 Interconnect error handling driver header + * OMAP L3 Interconnect error handling driver header * - * Copyright (C) 2011 Texas Corporation + * Copyright (C) 2011-2014 Texas Instruments Incorporated - http://www.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. + * 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 + * 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. - * - * 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 +#ifndef __OMAP_L3_NOC_H +#define __OMAP_L3_NOC_H + +#define MAX_L3_MODULES 3 +#define MAX_CLKDM_TARGETS 31 -#define L3_MODULES 3 #define CLEAR_STDERR_LOG (1 << 31) #define CUSTOM_ERROR 0x2 #define STANDARD_ERROR 0x0 @@ -33,63 +29,165 @@ /* L3 TARG register offsets */ #define L3_TARG_STDERRLOG_MAIN 0x48 +#define L3_TARG_STDERRLOG_HDR 0x4c +#define L3_TARG_STDERRLOG_MSTADDR 0x50 +#define L3_TARG_STDERRLOG_INFO 0x58 #define L3_TARG_STDERRLOG_SLVOFSLSB 0x5c -#define L3_TARG_STDERRLOG_MSTADDR 0x68 +#define L3_TARG_STDERRLOG_CINFO_INFO 0x64 +#define L3_TARG_STDERRLOG_CINFO_MSTADDR 0x68 +#define L3_TARG_STDERRLOG_CINFO_OPCODE 0x6c #define L3_FLAGMUX_REGERR0 0xc +#define L3_FLAGMUX_MASK0 0x8 + +#define L3_TARGET_NOT_SUPPORTED NULL + +#define L3_BASE_IS_SUBMODULE ((void __iomem *)(1 << 0)) + +static const char * const l3_transaction_type[] = { + /* 0 0 0 */ "Idle", + /* 0 0 1 */ "Write", + /* 0 1 0 */ "Read", + /* 0 1 1 */ "ReadEx", + /* 1 0 0 */ "Read Link", + /* 1 0 1 */ "Write Non-Posted", + /* 1 1 0 */ "Write Conditional", + /* 1 1 1 */ "Write Broadcast", +}; -#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 { +/** + * struct l3_masters_data - L3 Master information + * @id: ID of the L3 Master + * @name: master name + */ +struct l3_masters_data { u32 id; - char name[10]; -} l3_masters[] = { + char *name; +}; + +/** + * struct l3_target_data - L3 Target information + * @offset: Offset from base for L3 Target + * @name: Target name + * + * Target information is organized indexed by bit field definitions. + */ +struct l3_target_data { + u32 offset; + char *name; +}; + +/** + * struct l3_flagmux_data - Flag Mux information + * @offset: offset from base for flagmux register + * @l3_targ: array indexed by flagmux index (bit offset) pointing to the + * target data. unsupported ones are marked with + * L3_TARGET_NOT_SUPPORTED + * @num_targ_data: number of entries in target data + * @mask_app_bits: ignore these from raw application irq status + * @mask_dbg_bits: ignore these from raw debug irq status + */ +struct l3_flagmux_data { + u32 offset; + struct l3_target_data *l3_targ; + u8 num_targ_data; + u32 mask_app_bits; + u32 mask_dbg_bits; +}; + + +/** + * struct omap_l3 - Description of data relevant for L3 bus. + * @dev: device representing the bus (populated runtime) + * @l3_base: base addresses of modules (populated runtime if 0) + * if set to L3_BASE_IS_SUBMODULE, then uses previous + * module index as the base address + * @l3_flag_mux: array containing flag mux data per module + * offset from corresponding module base indexed per + * module. + * @num_modules: number of clock domains / modules. + * @l3_masters: array pointing to master data containing name and register + * offset for the master. + * @num_master: number of masters + * @mst_addr_mask: Mask representing MSTADDR information of NTTP packet + * @debug_irq: irq number of the debug interrupt (populated runtime) + * @app_irq: irq number of the application interrupt (populated runtime) + */ +struct omap_l3 { + struct device *dev; + + void __iomem *l3_base[MAX_L3_MODULES]; + struct l3_flagmux_data **l3_flagmux; + int num_modules; + + struct l3_masters_data *l3_masters; + int num_masters; + u32 mst_addr_mask; + + int debug_irq; + int app_irq; +}; + +static struct l3_target_data omap_l3_target_data_clk1[] = { + {0x100, "DMM1",}, + {0x200, "DMM2",}, + {0x300, "ABE",}, + {0x400, "L4CFG",}, + {0x600, "CLK2PWRDISC",}, + {0x0, "HOSTCLK1",}, + {0x900, "L4WAKEUP",}, +}; + +static struct l3_flagmux_data omap_l3_flagmux_clk1 = { + .offset = 0x500, + .l3_targ = omap_l3_target_data_clk1, + .num_targ_data = ARRAY_SIZE(omap_l3_target_data_clk1), +}; + + +static struct l3_target_data omap_l3_target_data_clk2[] = { + {0x500, "CORTEXM3",}, + {0x300, "DSS",}, + {0x100, "GPMC",}, + {0x400, "ISS",}, + {0x700, "IVAHD",}, + {0xD00, "AES1",}, + {0x900, "L4PER0",}, + {0x200, "OCMRAM",}, + {0x100, "GPMCsERROR",}, + {0x600, "SGX",}, + {0x800, "SL2",}, + {0x1600, "C2C",}, + {0x1100, "PWRDISCCLK1",}, + {0xF00, "SHA1",}, + {0xE00, "AES2",}, + {0xC00, "L4PER3",}, + {0xA00, "L4PER1",}, + {0xB00, "L4PER2",}, + {0x0, "HOSTCLK2",}, + {0x1800, "CAL",}, + {0x1700, "LLI",}, +}; + +static struct l3_flagmux_data omap_l3_flagmux_clk2 = { + .offset = 0x1000, + .l3_targ = omap_l3_target_data_clk2, + .num_targ_data = ARRAY_SIZE(omap_l3_target_data_clk2), +}; + + +static struct l3_target_data omap_l3_target_data_clk3[] = { + {0x0100, "EMUSS",}, + {0x0300, "DEBUG SOURCE",}, + {0x0, "HOST CLK3",}, +}; + +static struct l3_flagmux_data omap_l3_flagmux_clk3 = { + .offset = 0x0200, + .l3_targ = omap_l3_target_data_clk3, + .num_targ_data = ARRAY_SIZE(omap_l3_target_data_clk3), +}; + +static struct l3_masters_data omap_l3_masters[] = { { 0x0 , "MPU"}, { 0x10, "CS_ADP"}, { 0x14, "xxx"}, @@ -117,60 +215,261 @@ static struct l3_masters_data { { 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; +static struct l3_flagmux_data *omap_l3_flagmux[] = { + &omap_l3_flagmux_clk1, + &omap_l3_flagmux_clk2, + &omap_l3_flagmux_clk3, +}; + +static const struct omap_l3 omap_l3_data = { + .l3_flagmux = omap_l3_flagmux, + .num_modules = ARRAY_SIZE(omap_l3_flagmux), + .l3_masters = omap_l3_masters, + .num_masters = ARRAY_SIZE(omap_l3_masters), + /* The 6 MSBs of register field used to distinguish initiator */ + .mst_addr_mask = 0xFC, +}; - /* memory base */ - void __iomem *l3_base[L3_MODULES]; +/* DRA7 data */ +static struct l3_target_data dra_l3_target_data_clk1[] = { + {0x2a00, "AES1",}, + {0x0200, "DMM_P1",}, + {0x0600, "DSP2_SDMA",}, + {0x0b00, "EVE2",}, + {0x1300, "DMM_P2",}, + {0x2c00, "AES2",}, + {0x0300, "DSP1_SDMA",}, + {0x0a00, "EVE1",}, + {0x0c00, "EVE3",}, + {0x0d00, "EVE4",}, + {0x2900, "DSS",}, + {0x0100, "GPMC",}, + {0x3700, "PCIE1",}, + {0x1600, "IVA_CONFIG",}, + {0x1800, "IVA_SL2IF",}, + {0x0500, "L4_CFG",}, + {0x1d00, "L4_WKUP",}, + {0x3800, "PCIE2",}, + {0x3300, "SHA2_1",}, + {0x1200, "GPU",}, + {0x1000, "IPU1",}, + {0x1100, "IPU2",}, + {0x2000, "TPCC_EDMA",}, + {0x2e00, "TPTC1_EDMA",}, + {0x2b00, "TPTC2_EDMA",}, + {0x0700, "VCP1",}, + {0x2500, "L4_PER2_P3",}, + {0x0e00, "L4_PER3_P3",}, + {0x2200, "MMU1",}, + {0x1400, "PRUSS1",}, + {0x1500, "PRUSS2"}, + {0x0800, "VCP1",}, +}; - int debug_irq; - int app_irq; +static struct l3_flagmux_data dra_l3_flagmux_clk1 = { + .offset = 0x803500, + .l3_targ = dra_l3_target_data_clk1, + .num_targ_data = ARRAY_SIZE(dra_l3_target_data_clk1), +}; + +static struct l3_target_data dra_l3_target_data_clk2[] = { + {0x0, "HOST CLK1",}, + {0x0, "HOST CLK2",}, + {0xdead, L3_TARGET_NOT_SUPPORTED,}, + {0x3400, "SHA2_2",}, + {0x0900, "BB2D",}, + {0xdead, L3_TARGET_NOT_SUPPORTED,}, + {0x2100, "L4_PER1_P3",}, + {0x1c00, "L4_PER1_P1",}, + {0x1f00, "L4_PER1_P2",}, + {0x2300, "L4_PER2_P1",}, + {0x2400, "L4_PER2_P2",}, + {0x2600, "L4_PER3_P1",}, + {0x2700, "L4_PER3_P2",}, + {0x2f00, "MCASP1",}, + {0x3000, "MCASP2",}, + {0x3100, "MCASP3",}, + {0x2800, "MMU2",}, + {0x0f00, "OCMC_RAM1",}, + {0x1700, "OCMC_RAM2",}, + {0x1900, "OCMC_RAM3",}, + {0x1e00, "OCMC_ROM",}, + {0x3900, "QSPI",}, +}; + +static struct l3_flagmux_data dra_l3_flagmux_clk2 = { + .offset = 0x803600, + .l3_targ = dra_l3_target_data_clk2, + .num_targ_data = ARRAY_SIZE(dra_l3_target_data_clk2), +}; + +static struct l3_target_data dra_l3_target_data_clk3[] = { + {0x0100, "L3_INSTR"}, + {0x0300, "DEBUGSS_CT_TBR"}, + {0x0, "HOST CLK3"}, +}; + +static struct l3_flagmux_data dra_l3_flagmux_clk3 = { + .offset = 0x200, + .l3_targ = dra_l3_target_data_clk3, + .num_targ_data = ARRAY_SIZE(dra_l3_target_data_clk3), +}; + +static struct l3_masters_data dra_l3_masters[] = { + { 0x0, "MPU" }, + { 0x4, "CS_DAP" }, + { 0x5, "IEEE1500_2_OCP" }, + { 0x8, "DSP1_MDMA" }, + { 0x9, "DSP1_CFG" }, + { 0xA, "DSP1_DMA" }, + { 0xB, "DSP2_MDMA" }, + { 0xC, "DSP2_CFG" }, + { 0xD, "DSP2_DMA" }, + { 0xE, "IVA" }, + { 0x10, "EVE1_P1" }, + { 0x11, "EVE2_P1" }, + { 0x12, "EVE3_P1" }, + { 0x13, "EVE4_P1" }, + { 0x14, "PRUSS1 PRU1" }, + { 0x15, "PRUSS1 PRU2" }, + { 0x16, "PRUSS2 PRU1" }, + { 0x17, "PRUSS2 PRU2" }, + { 0x18, "IPU1" }, + { 0x19, "IPU2" }, + { 0x1A, "SDMA" }, + { 0x1B, "CDMA" }, + { 0x1C, "TC1_EDMA" }, + { 0x1D, "TC2_EDMA" }, + { 0x20, "DSS" }, + { 0x21, "MMU1" }, + { 0x22, "PCIE1" }, + { 0x23, "MMU2" }, + { 0x24, "VIP1" }, + { 0x25, "VIP2" }, + { 0x26, "VIP3" }, + { 0x27, "VPE" }, + { 0x28, "GPU_P1" }, + { 0x29, "BB2D" }, + { 0x29, "GPU_P2" }, + { 0x2B, "GMAC_SW" }, + { 0x2C, "USB3" }, + { 0x2D, "USB2_SS" }, + { 0x2E, "USB2_ULPI_SS1" }, + { 0x2F, "USB2_ULPI_SS2" }, + { 0x30, "CSI2_1" }, + { 0x31, "CSI2_2" }, + { 0x33, "SATA" }, + { 0x34, "EVE1_P2" }, + { 0x35, "EVE2_P2" }, + { 0x36, "EVE3_P2" }, + { 0x37, "EVE4_P2" } }; -#endif + +static struct l3_flagmux_data *dra_l3_flagmux[] = { + &dra_l3_flagmux_clk1, + &dra_l3_flagmux_clk2, + &dra_l3_flagmux_clk3, +}; + +static const struct omap_l3 dra_l3_data = { + .l3_base = { [1] = L3_BASE_IS_SUBMODULE }, + .l3_flagmux = dra_l3_flagmux, + .num_modules = ARRAY_SIZE(dra_l3_flagmux), + .l3_masters = dra_l3_masters, + .num_masters = ARRAY_SIZE(dra_l3_masters), + /* The 6 MSBs of register field used to distinguish initiator */ + .mst_addr_mask = 0xFC, +}; + +/* AM4372 data */ +static struct l3_target_data am4372_l3_target_data_200f[] = { + {0xf00, "EMIF",}, + {0x1200, "DES",}, + {0x400, "OCMCRAM",}, + {0x700, "TPTC0",}, + {0x800, "TPTC1",}, + {0x900, "TPTC2"}, + {0xb00, "TPCC",}, + {0xd00, "DEBUGSS",}, + {0xdead, L3_TARGET_NOT_SUPPORTED,}, + {0x200, "SHA",}, + {0xc00, "SGX530",}, + {0x500, "AES0",}, + {0xa00, "L4_FAST",}, + {0x300, "MPUSS_L2_RAM",}, + {0x100, "ICSS",}, +}; + +static struct l3_flagmux_data am4372_l3_flagmux_200f = { + .offset = 0x1000, + .l3_targ = am4372_l3_target_data_200f, + .num_targ_data = ARRAY_SIZE(am4372_l3_target_data_200f), +}; + +static struct l3_target_data am4372_l3_target_data_100s[] = { + {0x100, "L4_PER_0",}, + {0x200, "L4_PER_1",}, + {0x300, "L4_PER_2",}, + {0x400, "L4_PER_3",}, + {0x800, "McASP0",}, + {0x900, "McASP1",}, + {0xC00, "MMCHS2",}, + {0x700, "GPMC",}, + {0xD00, "L4_FW",}, + {0xdead, L3_TARGET_NOT_SUPPORTED,}, + {0x500, "ADCTSC",}, + {0xE00, "L4_WKUP",}, + {0xA00, "MAG_CARD",}, +}; + +static struct l3_flagmux_data am4372_l3_flagmux_100s = { + .offset = 0x600, + .l3_targ = am4372_l3_target_data_100s, + .num_targ_data = ARRAY_SIZE(am4372_l3_target_data_100s), +}; + +static struct l3_masters_data am4372_l3_masters[] = { + { 0x0, "M1 (128-bit)"}, + { 0x1, "M2 (64-bit)"}, + { 0x4, "DAP"}, + { 0x5, "P1500"}, + { 0xC, "ICSS0"}, + { 0xD, "ICSS1"}, + { 0x14, "Wakeup Processor"}, + { 0x18, "TPTC0 Read"}, + { 0x19, "TPTC0 Write"}, + { 0x1A, "TPTC1 Read"}, + { 0x1B, "TPTC1 Write"}, + { 0x1C, "TPTC2 Read"}, + { 0x1D, "TPTC2 Write"}, + { 0x20, "SGX530"}, + { 0x21, "OCP WP Traffic Probe"}, + { 0x22, "OCP WP DMA Profiling"}, + { 0x23, "OCP WP Event Trace"}, + { 0x25, "DSS"}, + { 0x28, "Crypto DMA RD"}, + { 0x29, "Crypto DMA WR"}, + { 0x2C, "VPFE0"}, + { 0x2D, "VPFE1"}, + { 0x30, "GEMAC"}, + { 0x34, "USB0 RD"}, + { 0x35, "USB0 WR"}, + { 0x36, "USB1 RD"}, + { 0x37, "USB1 WR"}, +}; + +static struct l3_flagmux_data *am4372_l3_flagmux[] = { + &am4372_l3_flagmux_200f, + &am4372_l3_flagmux_100s, +}; + +static const struct omap_l3 am4372_l3_data = { + .l3_flagmux = am4372_l3_flagmux, + .num_modules = ARRAY_SIZE(am4372_l3_flagmux), + .l3_masters = am4372_l3_masters, + .num_masters = ARRAY_SIZE(am4372_l3_masters), + /* All 6 bits of register field used to distinguish initiator */ + .mst_addr_mask = 0x3F, +}; + +#endif /* __OMAP_L3_NOC_H */ diff --git a/drivers/bus/vexpress-config.c b/drivers/bus/vexpress-config.c new file mode 100644 index 00000000000..a64763b6b5f --- /dev/null +++ b/drivers/bus/vexpress-config.c @@ -0,0 +1,202 @@ +/* + * 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. + * + * Copyright (C) 2014 ARM Limited + */ + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/vexpress.h> + + +struct vexpress_config_bridge { + struct vexpress_config_bridge_ops *ops; + void *context; +}; + + +static DEFINE_MUTEX(vexpress_config_mutex); +static struct class *vexpress_config_class; +static u32 vexpress_config_site_master = VEXPRESS_SITE_MASTER; + + +void vexpress_config_set_master(u32 site) +{ + vexpress_config_site_master = site; +} + +u32 vexpress_config_get_master(void) +{ + return vexpress_config_site_master; +} + +void vexpress_config_lock(void *arg) +{ + mutex_lock(&vexpress_config_mutex); +} + +void vexpress_config_unlock(void *arg) +{ + mutex_unlock(&vexpress_config_mutex); +} + + +static void vexpress_config_find_prop(struct device_node *node, + const char *name, u32 *val) +{ + /* Default value */ + *val = 0; + + of_node_get(node); + while (node) { + if (of_property_read_u32(node, name, val) == 0) { + of_node_put(node); + return; + } + node = of_get_next_parent(node); + } +} + +int vexpress_config_get_topo(struct device_node *node, u32 *site, + u32 *position, u32 *dcc) +{ + vexpress_config_find_prop(node, "arm,vexpress,site", site); + if (*site == VEXPRESS_SITE_MASTER) + *site = vexpress_config_site_master; + if (WARN_ON(vexpress_config_site_master == VEXPRESS_SITE_MASTER)) + return -EINVAL; + vexpress_config_find_prop(node, "arm,vexpress,position", position); + vexpress_config_find_prop(node, "arm,vexpress,dcc", dcc); + + return 0; +} + + +static void vexpress_config_devres_release(struct device *dev, void *res) +{ + struct vexpress_config_bridge *bridge = dev_get_drvdata(dev->parent); + struct regmap *regmap = res; + + bridge->ops->regmap_exit(regmap, bridge->context); +} + +struct regmap *devm_regmap_init_vexpress_config(struct device *dev) +{ + struct vexpress_config_bridge *bridge; + struct regmap *regmap; + struct regmap **res; + + if (WARN_ON(dev->parent->class != vexpress_config_class)) + return ERR_PTR(-ENODEV); + + bridge = dev_get_drvdata(dev->parent); + if (WARN_ON(!bridge)) + return ERR_PTR(-EINVAL); + + res = devres_alloc(vexpress_config_devres_release, sizeof(*res), + GFP_KERNEL); + if (!res) + return ERR_PTR(-ENOMEM); + + regmap = bridge->ops->regmap_init(dev, bridge->context); + if (IS_ERR(regmap)) { + devres_free(res); + return regmap; + } + + *res = regmap; + devres_add(dev, res); + + return regmap; +} +EXPORT_SYMBOL_GPL(devm_regmap_init_vexpress_config); + +struct device *vexpress_config_bridge_register(struct device *parent, + struct vexpress_config_bridge_ops *ops, void *context) +{ + struct device *dev; + struct vexpress_config_bridge *bridge; + + if (!vexpress_config_class) { + vexpress_config_class = class_create(THIS_MODULE, + "vexpress-config"); + if (IS_ERR(vexpress_config_class)) + return (void *)vexpress_config_class; + } + + dev = device_create(vexpress_config_class, parent, 0, + NULL, "%s.bridge", dev_name(parent)); + + if (IS_ERR(dev)) + return dev; + + bridge = devm_kmalloc(dev, sizeof(*bridge), GFP_KERNEL); + if (!bridge) { + put_device(dev); + device_unregister(dev); + return ERR_PTR(-ENOMEM); + } + bridge->ops = ops; + bridge->context = context; + + dev_set_drvdata(dev, bridge); + + dev_dbg(parent, "Registered bridge '%s', parent node %p\n", + dev_name(dev), parent->of_node); + + return dev; +} + + +static int vexpress_config_node_match(struct device *dev, const void *data) +{ + const struct device_node *node = data; + + dev_dbg(dev, "Parent node %p, looking for %p\n", + dev->parent->of_node, node); + + return dev->parent->of_node == node; +} + +static int vexpress_config_populate(struct device_node *node) +{ + struct device_node *bridge; + struct device *parent; + + bridge = of_parse_phandle(node, "arm,vexpress,config-bridge", 0); + if (!bridge) + return -EINVAL; + + parent = class_find_device(vexpress_config_class, NULL, bridge, + vexpress_config_node_match); + if (WARN_ON(!parent)) + return -ENODEV; + + return of_platform_populate(node, NULL, NULL, parent); +} + +static int __init vexpress_config_init(void) +{ + int err = 0; + struct device_node *node; + + /* Need the config devices early, before the "normal" devices... */ + for_each_compatible_node(node, NULL, "arm,vexpress,config-bus") { + err = vexpress_config_populate(node); + if (err) + break; + } + + return err; +} +postcore_initcall(vexpress_config_init); + |
