mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-07 08:48:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
70
drivers/bus/Kconfig
Normal file
70
drivers/bus/Kconfig
Normal file
|
@ -0,0 +1,70 @@
|
|||
#
|
||||
# Bus Devices
|
||||
#
|
||||
|
||||
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
|
||||
OCP2SCP and in OMAP5, both USB PHY and SATA PHY is connected via
|
||||
OCP2SCP.
|
||||
|
||||
config OMAP_INTERCONNECT
|
||||
tristate "OMAP INTERCONNECT DRIVER"
|
||||
depends on ARCH_OMAP2PLUS
|
||||
|
||||
help
|
||||
Driver to enable OMAP interconnect error handling driver.
|
||||
|
||||
config ARM_CCI
|
||||
bool "ARM CCI driver support"
|
||||
depends on ARM && OF && CPU_V7
|
||||
help
|
||||
Driver supporting the CCI cache coherent interconnect for ARM
|
||||
platforms.
|
||||
|
||||
config ARM_CCN
|
||||
bool "ARM CCN driver support"
|
||||
depends on ARM || ARM64
|
||||
depends on PERF_EVENTS
|
||||
help
|
||||
PMU (perf) driver supporting the ARM CCN (Cache Coherent Network)
|
||||
interconnect.
|
||||
|
||||
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
|
17
drivers/bus/Makefile
Normal file
17
drivers/bus/Makefile
Normal file
|
@ -0,0 +1,17 @@
|
|||
#
|
||||
# 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
|
||||
|
||||
# Interconnect bus drivers for ARM platforms
|
||||
obj-$(CONFIG_ARM_CCI) += arm-cci.o
|
||||
obj-$(CONFIG_ARM_CCN) += arm-ccn.o
|
||||
|
||||
obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o
|
1133
drivers/bus/arm-cci.c
Normal file
1133
drivers/bus/arm-cci.c
Normal file
File diff suppressed because it is too large
Load diff
1393
drivers/bus/arm-ccn.c
Normal file
1393
drivers/bus/arm-ccn.c
Normal file
File diff suppressed because it is too large
Load diff
289
drivers/bus/brcmstb_gisb.c
Normal file
289
drivers/bus/brcmstb_gisb.c
Normal file
|
@ -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_ioremap_resource(&pdev->dev, r);
|
||||
if (IS_ERR(gdev->base))
|
||||
return PTR_ERR(gdev->base);
|
||||
|
||||
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);
|
217
drivers/bus/imx-weim.c
Normal file
217
drivers/bus/imx-weim.c
Normal file
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* 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,
|
||||
of_default_bus_match_table,
|
||||
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");
|
956
drivers/bus/mvebu-mbus.c
Normal file
956
drivers/bus/mvebu-mbus.c
Normal file
|
@ -0,0 +1,956 @@
|
|||
/*
|
||||
* 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 */
|
||||
|
||||
/* On Armada XP, 375 and 38x the MBus window 13 has the remap
|
||||
* capability, like windows 0 to 7. However, the mvebu-mbus driver
|
||||
* isn't currently taking into account this special case, which means
|
||||
* that when window 13 is actually used, the remap registers are left
|
||||
* to 0, making the device using this MBus window unavailable. The
|
||||
* quick fix for stable is to not use window 13. A follow up patch
|
||||
* will correctly handle this window.
|
||||
*/
|
||||
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);
|
||||
|
||||
if (win == 13)
|
||||
return false;
|
||||
|
||||
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
|
90
drivers/bus/omap-ocp2scp.c
Normal file
90
drivers/bus/omap-ocp2scp.c
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* omap-ocp2scp.c - transform ocp interface protocol to scp protocol
|
||||
*
|
||||
* Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
|
||||
static int ocp2scp_remove_devices(struct device *dev, void *c)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
|
||||
platform_device_unregister(pdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int omap_ocp2scp_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
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");
|
||||
goto err0;
|
||||
}
|
||||
}
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err0:
|
||||
device_for_each_child(&pdev->dev, NULL, ocp2scp_remove_devices);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int omap_ocp2scp_remove(struct platform_device *pdev)
|
||||
{
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
device_for_each_child(&pdev->dev, NULL, ocp2scp_remove_devices);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id omap_ocp2scp_id_table[] = {
|
||||
{ .compatible = "ti,omap-ocp2scp" },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, omap_ocp2scp_id_table);
|
||||
#endif
|
||||
|
||||
static struct platform_driver omap_ocp2scp_driver = {
|
||||
.probe = omap_ocp2scp_probe,
|
||||
.remove = omap_ocp2scp_remove,
|
||||
.driver = {
|
||||
.name = "omap-ocp2scp",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(omap_ocp2scp_id_table),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(omap_ocp2scp_driver);
|
||||
|
||||
MODULE_ALIAS("platform: omap-ocp2scp");
|
||||
MODULE_AUTHOR("Texas Instruments Inc.");
|
||||
MODULE_DESCRIPTION("OMAP OCP2SCP driver");
|
||||
MODULE_LICENSE("GPL v2");
|
377
drivers/bus/omap_l3_noc.c
Normal file
377
drivers/bus/omap_l3_noc.c
Normal file
|
@ -0,0 +1,377 @@
|
|||
/*
|
||||
* OMAP L3 Interconnect error handling driver
|
||||
*
|
||||
* 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 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/init.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"
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*
|
||||
* 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:
|
||||
* - Unsupported CMD.
|
||||
* L3 tries to access target while it is idle
|
||||
* - OCP disconnect.
|
||||
* - Address hole error:
|
||||
* If DSS/ISS/FDIF/USBHOSTFS access a target where they
|
||||
* do not have connectivity, the error is logged in
|
||||
* their default target which is DMM2.
|
||||
*
|
||||
* On High Secure devices, firewall errors are possible and those
|
||||
* can be trapped as well. But the trapping is implemented as part
|
||||
* secure software and hence need not be implemented here.
|
||||
*/
|
||||
static 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;
|
||||
}
|
||||
|
||||
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 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->num_modules; i++) {
|
||||
/*
|
||||
* Read the regerr register of the clock domain
|
||||
* to determine the source
|
||||
*/
|
||||
base = l3->l3_base[i];
|
||||
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);
|
||||
|
||||
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 */
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
dev_err(l3->dev, "L3 %s IRQ not handled!!\n",
|
||||
inttype ? "debug" : "application");
|
||||
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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 = 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);
|
||||
|
||||
/* Get mem resources */
|
||||
for (i = 0, res_idx = 0; i < l3->num_modules; i++) {
|
||||
struct resource *res;
|
||||
|
||||
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 = devm_request_irq(l3->dev, l3->debug_irq, l3_interrupt_handler,
|
||||
IRQF_DISABLED, "l3-dbg-irq", l3);
|
||||
if (ret) {
|
||||
dev_err(l3->dev, "request_irq failed for %d\n",
|
||||
l3->debug_irq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
l3->app_irq = platform_get_irq(pdev, 1);
|
||||
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 ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
/**
|
||||
* l3_resume_noirq() - resume function for l3_noc
|
||||
* @dev: pointer to l3_noc device structure
|
||||
*
|
||||
* We only have the resume handler only since we
|
||||
* have already maintained the delta register
|
||||
* configuration as part of configuring the system
|
||||
*/
|
||||
static int l3_resume_noirq(struct device *dev)
|
||||
{
|
||||
struct omap_l3 *l3 = dev_get_drvdata(dev);
|
||||
int i;
|
||||
struct l3_flagmux_data *flag_mux;
|
||||
void __iomem *base, *mask_regx = NULL;
|
||||
u32 mask_val;
|
||||
|
||||
for (i = 0; i < l3->num_modules; i++) {
|
||||
base = l3->l3_base[i];
|
||||
flag_mux = l3->l3_flagmux[i];
|
||||
if (!flag_mux->mask_app_bits && !flag_mux->mask_dbg_bits)
|
||||
continue;
|
||||
|
||||
mask_regx = base + flag_mux->offset + L3_FLAGMUX_MASK0 +
|
||||
(L3_APPLICATION_ERROR << 3);
|
||||
mask_val = readl_relaxed(mask_regx);
|
||||
mask_val &= ~(flag_mux->mask_app_bits);
|
||||
|
||||
writel_relaxed(mask_val, mask_regx);
|
||||
mask_regx = base + flag_mux->offset + L3_FLAGMUX_MASK0 +
|
||||
(L3_DEBUG_ERROR << 3);
|
||||
mask_val = readl_relaxed(mask_regx);
|
||||
mask_val &= ~(flag_mux->mask_dbg_bits);
|
||||
|
||||
writel_relaxed(mask_val, mask_regx);
|
||||
}
|
||||
|
||||
/* Dummy read to force OCP barrier */
|
||||
if (mask_regx)
|
||||
(void)readl(mask_regx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops l3_dev_pm_ops = {
|
||||
.resume_noirq = l3_resume_noirq,
|
||||
};
|
||||
|
||||
#define L3_DEV_PM_OPS (&l3_dev_pm_ops)
|
||||
#else
|
||||
#define L3_DEV_PM_OPS NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver omap_l3_driver = {
|
||||
.probe = omap_l3_probe,
|
||||
.driver = {
|
||||
.name = "omap_l3_noc",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = L3_DEV_PM_OPS,
|
||||
.of_match_table = of_match_ptr(l3_noc_match),
|
||||
},
|
||||
};
|
||||
|
||||
static int __init omap_l3_init(void)
|
||||
{
|
||||
return platform_driver_register(&omap_l3_driver);
|
||||
}
|
||||
postcore_initcall_sync(omap_l3_init);
|
||||
|
||||
static void __exit omap_l3_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&omap_l3_driver);
|
||||
}
|
||||
module_exit(omap_l3_exit);
|
475
drivers/bus/omap_l3_noc.h
Normal file
475
drivers/bus/omap_l3_noc.h
Normal file
|
@ -0,0 +1,475 @@
|
|||
/*
|
||||
* OMAP L3 Interconnect error handling driver header
|
||||
*
|
||||
* 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 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.
|
||||
*/
|
||||
#ifndef __OMAP_L3_NOC_H
|
||||
#define __OMAP_L3_NOC_H
|
||||
|
||||
#define MAX_L3_MODULES 3
|
||||
#define MAX_CLKDM_TARGETS 31
|
||||
|
||||
#define CLEAR_STDERR_LOG (1 << 31)
|
||||
#define CUSTOM_ERROR 0x2
|
||||
#define STANDARD_ERROR 0x0
|
||||
#define INBAND_ERROR 0x0
|
||||
#define L3_APPLICATION_ERROR 0x0
|
||||
#define L3_DEBUG_ERROR 0x1
|
||||
|
||||
/* L3 TARG register offsets */
|
||||
#define L3_TARG_STDERRLOG_MAIN 0x48
|
||||
#define L3_TARG_STDERRLOG_HDR 0x4c
|
||||
#define L3_TARG_STDERRLOG_MSTADDR 0x50
|
||||
#define L3_TARG_STDERRLOG_INFO 0x58
|
||||
#define L3_TARG_STDERRLOG_SLVOFSLSB 0x5c
|
||||
#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",
|
||||
};
|
||||
|
||||
/**
|
||||
* struct l3_masters_data - L3 Master information
|
||||
* @id: ID of the L3 Master
|
||||
* @name: master name
|
||||
*/
|
||||
struct l3_masters_data {
|
||||
u32 id;
|
||||
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[] = {
|
||||
{ 0x00, "MPU"},
|
||||
{ 0x04, "CS_ADP"},
|
||||
{ 0x05, "xxx"},
|
||||
{ 0x08, "DSP"},
|
||||
{ 0x0C, "IVAHD"},
|
||||
{ 0x10, "ISS"},
|
||||
{ 0x11, "DucatiM3"},
|
||||
{ 0x12, "FaceDetect"},
|
||||
{ 0x14, "SDMA_Rd"},
|
||||
{ 0x15, "SDMA_Wr"},
|
||||
{ 0x16, "xxx"},
|
||||
{ 0x17, "xxx"},
|
||||
{ 0x18, "SGX"},
|
||||
{ 0x1C, "DSS"},
|
||||
{ 0x20, "C2C"},
|
||||
{ 0x22, "xxx"},
|
||||
{ 0x23, "xxx"},
|
||||
{ 0x24, "HSI"},
|
||||
{ 0x28, "MMC1"},
|
||||
{ 0x29, "MMC2"},
|
||||
{ 0x2A, "MMC6"},
|
||||
{ 0x2C, "UNIPRO1"},
|
||||
{ 0x30, "USBHOSTHS"},
|
||||
{ 0x31, "USBOTGHS"},
|
||||
{ 0x32, "USBHOSTFS"}
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
/* 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",},
|
||||
};
|
||||
|
||||
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" }
|
||||
};
|
||||
|
||||
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 */
|
297
drivers/bus/omap_l3_smx.c
Normal file
297
drivers/bus/omap_l3_smx.c
Normal file
|
@ -0,0 +1,297 @@
|
|||
/*
|
||||
* OMAP3XXX L3 Interconnect Driver
|
||||
*
|
||||
* Copyright (C) 2011 Texas Corporation
|
||||
* Felipe Balbi <balbi@ti.com>
|
||||
* Santosh Shilimkar <santosh.shilimkar@ti.com>
|
||||
* Sricharan <r.sricharan@ti.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
* USA
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include "omap_l3_smx.h"
|
||||
|
||||
static inline u64 omap3_l3_readll(void __iomem *base, u16 reg)
|
||||
{
|
||||
return __raw_readll(base + reg);
|
||||
}
|
||||
|
||||
static inline void omap3_l3_writell(void __iomem *base, u16 reg, u64 value)
|
||||
{
|
||||
__raw_writell(value, base + reg);
|
||||
}
|
||||
|
||||
static inline enum omap3_l3_code omap3_l3_decode_error_code(u64 error)
|
||||
{
|
||||
return (error & 0x0f000000) >> L3_ERROR_LOG_CODE;
|
||||
}
|
||||
|
||||
static inline u32 omap3_l3_decode_addr(u64 error_addr)
|
||||
{
|
||||
return error_addr & 0xffffffff;
|
||||
}
|
||||
|
||||
static inline unsigned omap3_l3_decode_cmd(u64 error)
|
||||
{
|
||||
return (error & 0x07) >> L3_ERROR_LOG_CMD;
|
||||
}
|
||||
|
||||
static inline enum omap3_l3_initiator_id omap3_l3_decode_initid(u64 error)
|
||||
{
|
||||
return (error & 0xff00) >> L3_ERROR_LOG_INITID;
|
||||
}
|
||||
|
||||
static inline unsigned omap3_l3_decode_req_info(u64 error)
|
||||
{
|
||||
return (error >> 32) & 0xffff;
|
||||
}
|
||||
|
||||
static char *omap3_l3_code_string(u8 code)
|
||||
{
|
||||
switch (code) {
|
||||
case OMAP_L3_CODE_NOERROR:
|
||||
return "No Error";
|
||||
case OMAP_L3_CODE_UNSUP_CMD:
|
||||
return "Unsupported Command";
|
||||
case OMAP_L3_CODE_ADDR_HOLE:
|
||||
return "Address Hole";
|
||||
case OMAP_L3_CODE_PROTECT_VIOLATION:
|
||||
return "Protection Violation";
|
||||
case OMAP_L3_CODE_IN_BAND_ERR:
|
||||
return "In-band Error";
|
||||
case OMAP_L3_CODE_REQ_TOUT_NOT_ACCEPT:
|
||||
return "Request Timeout Not Accepted";
|
||||
case OMAP_L3_CODE_REQ_TOUT_NO_RESP:
|
||||
return "Request Timeout, no response";
|
||||
default:
|
||||
return "UNKNOWN error";
|
||||
}
|
||||
}
|
||||
|
||||
static char *omap3_l3_initiator_string(u8 initid)
|
||||
{
|
||||
switch (initid) {
|
||||
case OMAP_L3_LCD:
|
||||
return "LCD";
|
||||
case OMAP_L3_SAD2D:
|
||||
return "SAD2D";
|
||||
case OMAP_L3_IA_MPU_SS_1:
|
||||
case OMAP_L3_IA_MPU_SS_2:
|
||||
case OMAP_L3_IA_MPU_SS_3:
|
||||
case OMAP_L3_IA_MPU_SS_4:
|
||||
case OMAP_L3_IA_MPU_SS_5:
|
||||
return "MPU";
|
||||
case OMAP_L3_IA_IVA_SS_1:
|
||||
case OMAP_L3_IA_IVA_SS_2:
|
||||
case OMAP_L3_IA_IVA_SS_3:
|
||||
return "IVA_SS";
|
||||
case OMAP_L3_IA_IVA_SS_DMA_1:
|
||||
case OMAP_L3_IA_IVA_SS_DMA_2:
|
||||
case OMAP_L3_IA_IVA_SS_DMA_3:
|
||||
case OMAP_L3_IA_IVA_SS_DMA_4:
|
||||
case OMAP_L3_IA_IVA_SS_DMA_5:
|
||||
case OMAP_L3_IA_IVA_SS_DMA_6:
|
||||
return "IVA_SS_DMA";
|
||||
case OMAP_L3_IA_SGX:
|
||||
return "SGX";
|
||||
case OMAP_L3_IA_CAM_1:
|
||||
case OMAP_L3_IA_CAM_2:
|
||||
case OMAP_L3_IA_CAM_3:
|
||||
return "CAM";
|
||||
case OMAP_L3_IA_DAP:
|
||||
return "DAP";
|
||||
case OMAP_L3_SDMA_WR_1:
|
||||
case OMAP_L3_SDMA_WR_2:
|
||||
return "SDMA_WR";
|
||||
case OMAP_L3_SDMA_RD_1:
|
||||
case OMAP_L3_SDMA_RD_2:
|
||||
case OMAP_L3_SDMA_RD_3:
|
||||
case OMAP_L3_SDMA_RD_4:
|
||||
return "SDMA_RD";
|
||||
case OMAP_L3_USBOTG:
|
||||
return "USB_OTG";
|
||||
case OMAP_L3_USBHOST:
|
||||
return "USB_HOST";
|
||||
default:
|
||||
return "UNKNOWN Initiator";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* omap3_l3_block_irq - handles a register block's irq
|
||||
* @l3: struct omap3_l3 *
|
||||
* @base: register block base address
|
||||
* @error: L3_ERROR_LOG register of our block
|
||||
*
|
||||
* Called in hard-irq context. Caller should take care of locking
|
||||
*
|
||||
* OMAP36xx TRM gives, on page 2001, Figure 9-10, the Typical Error
|
||||
* Analysis Sequence, we are following that sequence here, please
|
||||
* refer to that Figure for more information on the subject.
|
||||
*/
|
||||
static irqreturn_t omap3_l3_block_irq(struct omap3_l3 *l3,
|
||||
u64 error, int error_addr)
|
||||
{
|
||||
u8 code = omap3_l3_decode_error_code(error);
|
||||
u8 initid = omap3_l3_decode_initid(error);
|
||||
u8 multi = error & L3_ERROR_LOG_MULTI;
|
||||
u32 address = omap3_l3_decode_addr(error_addr);
|
||||
|
||||
pr_err("%s seen by %s %s at address %x\n",
|
||||
omap3_l3_code_string(code),
|
||||
omap3_l3_initiator_string(initid),
|
||||
multi ? "Multiple Errors" : "", address);
|
||||
WARN_ON(1);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t omap3_l3_app_irq(int irq, void *_l3)
|
||||
{
|
||||
struct omap3_l3 *l3 = _l3;
|
||||
u64 status, clear;
|
||||
u64 error;
|
||||
u64 error_addr;
|
||||
u64 err_source = 0;
|
||||
void __iomem *base;
|
||||
int int_type;
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
|
||||
int_type = irq == l3->app_irq ? L3_APPLICATION_ERROR : L3_DEBUG_ERROR;
|
||||
if (!int_type) {
|
||||
status = omap3_l3_readll(l3->rt, L3_SI_FLAG_STATUS_0);
|
||||
/*
|
||||
* if we have a timeout error, there's nothing we can
|
||||
* do besides rebooting the board. So let's BUG on any
|
||||
* of such errors and handle the others. timeout error
|
||||
* is severe and not expected to occur.
|
||||
*/
|
||||
BUG_ON(status & L3_STATUS_0_TIMEOUT_MASK);
|
||||
} else {
|
||||
status = omap3_l3_readll(l3->rt, L3_SI_FLAG_STATUS_1);
|
||||
/* No timeout error for debug sources */
|
||||
}
|
||||
|
||||
/* identify the error source */
|
||||
err_source = __ffs(status);
|
||||
|
||||
base = l3->rt + omap3_l3_bases[int_type][err_source];
|
||||
error = omap3_l3_readll(base, L3_ERROR_LOG);
|
||||
if (error) {
|
||||
error_addr = omap3_l3_readll(base, L3_ERROR_LOG_ADDR);
|
||||
ret |= omap3_l3_block_irq(l3, error, error_addr);
|
||||
}
|
||||
|
||||
/* Clear the status register */
|
||||
clear = (L3_AGENT_STATUS_CLEAR_IA << int_type) |
|
||||
L3_AGENT_STATUS_CLEAR_TA;
|
||||
omap3_l3_writell(base, L3_AGENT_STATUS, clear);
|
||||
|
||||
/* clear the error log register */
|
||||
omap3_l3_writell(base, L3_ERROR_LOG, error);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __init omap3_l3_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct omap3_l3 *l3;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
l3 = kzalloc(sizeof(*l3), GFP_KERNEL);
|
||||
if (!l3)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, l3);
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "couldn't find resource\n");
|
||||
ret = -ENODEV;
|
||||
goto err0;
|
||||
}
|
||||
l3->rt = ioremap(res->start, resource_size(res));
|
||||
if (!l3->rt) {
|
||||
dev_err(&pdev->dev, "ioremap failed\n");
|
||||
ret = -ENOMEM;
|
||||
goto err0;
|
||||
}
|
||||
|
||||
l3->debug_irq = platform_get_irq(pdev, 0);
|
||||
ret = request_irq(l3->debug_irq, omap3_l3_app_irq,
|
||||
IRQF_DISABLED | IRQF_TRIGGER_RISING,
|
||||
"l3-debug-irq", l3);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "couldn't request debug irq\n");
|
||||
goto err1;
|
||||
}
|
||||
|
||||
l3->app_irq = platform_get_irq(pdev, 1);
|
||||
ret = request_irq(l3->app_irq, omap3_l3_app_irq,
|
||||
IRQF_DISABLED | IRQF_TRIGGER_RISING,
|
||||
"l3-app-irq", l3);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "couldn't request app irq\n");
|
||||
goto err2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err2:
|
||||
free_irq(l3->debug_irq, l3);
|
||||
err1:
|
||||
iounmap(l3->rt);
|
||||
err0:
|
||||
kfree(l3);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __exit omap3_l3_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct omap3_l3 *l3 = platform_get_drvdata(pdev);
|
||||
|
||||
free_irq(l3->app_irq, l3);
|
||||
free_irq(l3->debug_irq, l3);
|
||||
iounmap(l3->rt);
|
||||
kfree(l3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver omap3_l3_driver = {
|
||||
.remove = __exit_p(omap3_l3_remove),
|
||||
.driver = {
|
||||
.name = "omap_l3_smx",
|
||||
},
|
||||
};
|
||||
|
||||
static int __init omap3_l3_init(void)
|
||||
{
|
||||
return platform_driver_probe(&omap3_l3_driver, omap3_l3_probe);
|
||||
}
|
||||
postcore_initcall_sync(omap3_l3_init);
|
||||
|
||||
static void __exit omap3_l3_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&omap3_l3_driver);
|
||||
}
|
||||
module_exit(omap3_l3_exit);
|
338
drivers/bus/omap_l3_smx.h
Normal file
338
drivers/bus/omap_l3_smx.h
Normal file
|
@ -0,0 +1,338 @@
|
|||
/*
|
||||
* OMAP3XXX L3 Interconnect Driver header
|
||||
*
|
||||
* Copyright (C) 2011 Texas Corporation
|
||||
* Felipe Balbi <balbi@ti.com>
|
||||
* Santosh Shilimkar <santosh.shilimkar@ti.com>
|
||||
* sricharan <r.sricharan@ti.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
* USA
|
||||
*/
|
||||
#ifndef __ARCH_ARM_MACH_OMAP2_L3_INTERCONNECT_3XXX_H
|
||||
#define __ARCH_ARM_MACH_OMAP2_L3_INTERCONNECT_3XXX_H
|
||||
|
||||
/* Register definitions. All 64-bit wide */
|
||||
#define L3_COMPONENT 0x000
|
||||
#define L3_CORE 0x018
|
||||
#define L3_AGENT_CONTROL 0x020
|
||||
#define L3_AGENT_STATUS 0x028
|
||||
#define L3_ERROR_LOG 0x058
|
||||
|
||||
#define L3_ERROR_LOG_MULTI (1 << 31)
|
||||
#define L3_ERROR_LOG_SECONDARY (1 << 30)
|
||||
|
||||
#define L3_ERROR_LOG_ADDR 0x060
|
||||
|
||||
/* Register definitions for Sideband Interconnect */
|
||||
#define L3_SI_CONTROL 0x020
|
||||
#define L3_SI_FLAG_STATUS_0 0x510
|
||||
|
||||
static const u64 shift = 1;
|
||||
|
||||
#define L3_STATUS_0_MPUIA_BRST (shift << 0)
|
||||
#define L3_STATUS_0_MPUIA_RSP (shift << 1)
|
||||
#define L3_STATUS_0_MPUIA_INBAND (shift << 2)
|
||||
#define L3_STATUS_0_IVAIA_BRST (shift << 6)
|
||||
#define L3_STATUS_0_IVAIA_RSP (shift << 7)
|
||||
#define L3_STATUS_0_IVAIA_INBAND (shift << 8)
|
||||
#define L3_STATUS_0_SGXIA_BRST (shift << 9)
|
||||
#define L3_STATUS_0_SGXIA_RSP (shift << 10)
|
||||
#define L3_STATUS_0_SGXIA_MERROR (shift << 11)
|
||||
#define L3_STATUS_0_CAMIA_BRST (shift << 12)
|
||||
#define L3_STATUS_0_CAMIA_RSP (shift << 13)
|
||||
#define L3_STATUS_0_CAMIA_INBAND (shift << 14)
|
||||
#define L3_STATUS_0_DISPIA_BRST (shift << 15)
|
||||
#define L3_STATUS_0_DISPIA_RSP (shift << 16)
|
||||
#define L3_STATUS_0_DMARDIA_BRST (shift << 18)
|
||||
#define L3_STATUS_0_DMARDIA_RSP (shift << 19)
|
||||
#define L3_STATUS_0_DMAWRIA_BRST (shift << 21)
|
||||
#define L3_STATUS_0_DMAWRIA_RSP (shift << 22)
|
||||
#define L3_STATUS_0_USBOTGIA_BRST (shift << 24)
|
||||
#define L3_STATUS_0_USBOTGIA_RSP (shift << 25)
|
||||
#define L3_STATUS_0_USBOTGIA_INBAND (shift << 26)
|
||||
#define L3_STATUS_0_USBHOSTIA_BRST (shift << 27)
|
||||
#define L3_STATUS_0_USBHOSTIA_INBAND (shift << 28)
|
||||
#define L3_STATUS_0_SMSTA_REQ (shift << 48)
|
||||
#define L3_STATUS_0_GPMCTA_REQ (shift << 49)
|
||||
#define L3_STATUS_0_OCMRAMTA_REQ (shift << 50)
|
||||
#define L3_STATUS_0_OCMROMTA_REQ (shift << 51)
|
||||
#define L3_STATUS_0_IVATA_REQ (shift << 54)
|
||||
#define L3_STATUS_0_SGXTA_REQ (shift << 55)
|
||||
#define L3_STATUS_0_SGXTA_SERROR (shift << 56)
|
||||
#define L3_STATUS_0_GPMCTA_SERROR (shift << 57)
|
||||
#define L3_STATUS_0_L4CORETA_REQ (shift << 58)
|
||||
#define L3_STATUS_0_L4PERTA_REQ (shift << 59)
|
||||
#define L3_STATUS_0_L4EMUTA_REQ (shift << 60)
|
||||
#define L3_STATUS_0_MAD2DTA_REQ (shift << 61)
|
||||
|
||||
#define L3_STATUS_0_TIMEOUT_MASK (L3_STATUS_0_MPUIA_BRST \
|
||||
| L3_STATUS_0_MPUIA_RSP \
|
||||
| L3_STATUS_0_IVAIA_BRST \
|
||||
| L3_STATUS_0_IVAIA_RSP \
|
||||
| L3_STATUS_0_SGXIA_BRST \
|
||||
| L3_STATUS_0_SGXIA_RSP \
|
||||
| L3_STATUS_0_CAMIA_BRST \
|
||||
| L3_STATUS_0_CAMIA_RSP \
|
||||
| L3_STATUS_0_DISPIA_BRST \
|
||||
| L3_STATUS_0_DISPIA_RSP \
|
||||
| L3_STATUS_0_DMARDIA_BRST \
|
||||
| L3_STATUS_0_DMARDIA_RSP \
|
||||
| L3_STATUS_0_DMAWRIA_BRST \
|
||||
| L3_STATUS_0_DMAWRIA_RSP \
|
||||
| L3_STATUS_0_USBOTGIA_BRST \
|
||||
| L3_STATUS_0_USBOTGIA_RSP \
|
||||
| L3_STATUS_0_USBHOSTIA_BRST \
|
||||
| L3_STATUS_0_SMSTA_REQ \
|
||||
| L3_STATUS_0_GPMCTA_REQ \
|
||||
| L3_STATUS_0_OCMRAMTA_REQ \
|
||||
| L3_STATUS_0_OCMROMTA_REQ \
|
||||
| L3_STATUS_0_IVATA_REQ \
|
||||
| L3_STATUS_0_SGXTA_REQ \
|
||||
| L3_STATUS_0_L4CORETA_REQ \
|
||||
| L3_STATUS_0_L4PERTA_REQ \
|
||||
| L3_STATUS_0_L4EMUTA_REQ \
|
||||
| L3_STATUS_0_MAD2DTA_REQ)
|
||||
|
||||
#define L3_SI_FLAG_STATUS_1 0x530
|
||||
|
||||
#define L3_STATUS_1_MPU_DATAIA (1 << 0)
|
||||
#define L3_STATUS_1_DAPIA0 (1 << 3)
|
||||
#define L3_STATUS_1_DAPIA1 (1 << 4)
|
||||
#define L3_STATUS_1_IVAIA (1 << 6)
|
||||
|
||||
#define L3_PM_ERROR_LOG 0x020
|
||||
#define L3_PM_CONTROL 0x028
|
||||
#define L3_PM_ERROR_CLEAR_SINGLE 0x030
|
||||
#define L3_PM_ERROR_CLEAR_MULTI 0x038
|
||||
#define L3_PM_REQ_INFO_PERMISSION(n) (0x048 + (0x020 * n))
|
||||
#define L3_PM_READ_PERMISSION(n) (0x050 + (0x020 * n))
|
||||
#define L3_PM_WRITE_PERMISSION(n) (0x058 + (0x020 * n))
|
||||
#define L3_PM_ADDR_MATCH(n) (0x060 + (0x020 * n))
|
||||
|
||||
/* L3 error log bit fields. Common for IA and TA */
|
||||
#define L3_ERROR_LOG_CODE 24
|
||||
#define L3_ERROR_LOG_INITID 8
|
||||
#define L3_ERROR_LOG_CMD 0
|
||||
|
||||
/* L3 agent status bit fields. */
|
||||
#define L3_AGENT_STATUS_CLEAR_IA 0x10000000
|
||||
#define L3_AGENT_STATUS_CLEAR_TA 0x01000000
|
||||
|
||||
#define OMAP34xx_IRQ_L3_APP 10
|
||||
#define L3_APPLICATION_ERROR 0x0
|
||||
#define L3_DEBUG_ERROR 0x1
|
||||
|
||||
enum omap3_l3_initiator_id {
|
||||
/* LCD has 1 ID */
|
||||
OMAP_L3_LCD = 29,
|
||||
/* SAD2D has 1 ID */
|
||||
OMAP_L3_SAD2D = 28,
|
||||
/* MPU has 5 IDs */
|
||||
OMAP_L3_IA_MPU_SS_1 = 27,
|
||||
OMAP_L3_IA_MPU_SS_2 = 26,
|
||||
OMAP_L3_IA_MPU_SS_3 = 25,
|
||||
OMAP_L3_IA_MPU_SS_4 = 24,
|
||||
OMAP_L3_IA_MPU_SS_5 = 23,
|
||||
/* IVA2.2 SS has 3 IDs*/
|
||||
OMAP_L3_IA_IVA_SS_1 = 22,
|
||||
OMAP_L3_IA_IVA_SS_2 = 21,
|
||||
OMAP_L3_IA_IVA_SS_3 = 20,
|
||||
/* IVA 2.2 SS DMA has 6 IDS */
|
||||
OMAP_L3_IA_IVA_SS_DMA_1 = 19,
|
||||
OMAP_L3_IA_IVA_SS_DMA_2 = 18,
|
||||
OMAP_L3_IA_IVA_SS_DMA_3 = 17,
|
||||
OMAP_L3_IA_IVA_SS_DMA_4 = 16,
|
||||
OMAP_L3_IA_IVA_SS_DMA_5 = 15,
|
||||
OMAP_L3_IA_IVA_SS_DMA_6 = 14,
|
||||
/* SGX has 1 ID */
|
||||
OMAP_L3_IA_SGX = 13,
|
||||
/* CAM has 3 ID */
|
||||
OMAP_L3_IA_CAM_1 = 12,
|
||||
OMAP_L3_IA_CAM_2 = 11,
|
||||
OMAP_L3_IA_CAM_3 = 10,
|
||||
/* DAP has 1 ID */
|
||||
OMAP_L3_IA_DAP = 9,
|
||||
/* SDMA WR has 2 IDs */
|
||||
OMAP_L3_SDMA_WR_1 = 8,
|
||||
OMAP_L3_SDMA_WR_2 = 7,
|
||||
/* SDMA RD has 4 IDs */
|
||||
OMAP_L3_SDMA_RD_1 = 6,
|
||||
OMAP_L3_SDMA_RD_2 = 5,
|
||||
OMAP_L3_SDMA_RD_3 = 4,
|
||||
OMAP_L3_SDMA_RD_4 = 3,
|
||||
/* HSUSB OTG has 1 ID */
|
||||
OMAP_L3_USBOTG = 2,
|
||||
/* HSUSB HOST has 1 ID */
|
||||
OMAP_L3_USBHOST = 1,
|
||||
};
|
||||
|
||||
enum omap3_l3_code {
|
||||
OMAP_L3_CODE_NOERROR = 0,
|
||||
OMAP_L3_CODE_UNSUP_CMD = 1,
|
||||
OMAP_L3_CODE_ADDR_HOLE = 2,
|
||||
OMAP_L3_CODE_PROTECT_VIOLATION = 3,
|
||||
OMAP_L3_CODE_IN_BAND_ERR = 4,
|
||||
/* codes 5 and 6 are reserved */
|
||||
OMAP_L3_CODE_REQ_TOUT_NOT_ACCEPT = 7,
|
||||
OMAP_L3_CODE_REQ_TOUT_NO_RESP = 8,
|
||||
/* codes 9 - 15 are also reserved */
|
||||
};
|
||||
|
||||
struct omap3_l3 {
|
||||
struct device *dev;
|
||||
struct clk *ick;
|
||||
|
||||
/* memory base*/
|
||||
void __iomem *rt;
|
||||
|
||||
int debug_irq;
|
||||
int app_irq;
|
||||
|
||||
/* true when and inband functional error occurs */
|
||||
unsigned inband:1;
|
||||
};
|
||||
|
||||
/* offsets for l3 agents in order with the Flag status register */
|
||||
static unsigned int omap3_l3_app_bases[] = {
|
||||
/* MPU IA */
|
||||
0x1400,
|
||||
0x1400,
|
||||
0x1400,
|
||||
/* RESERVED */
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
/* IVA 2.2 IA */
|
||||
0x1800,
|
||||
0x1800,
|
||||
0x1800,
|
||||
/* SGX IA */
|
||||
0x1c00,
|
||||
0x1c00,
|
||||
/* RESERVED */
|
||||
0,
|
||||
/* CAMERA IA */
|
||||
0x5800,
|
||||
0x5800,
|
||||
0x5800,
|
||||
/* DISPLAY IA */
|
||||
0x5400,
|
||||
0x5400,
|
||||
/* RESERVED */
|
||||
0,
|
||||
/*SDMA RD IA */
|
||||
0x4c00,
|
||||
0x4c00,
|
||||
/* RESERVED */
|
||||
0,
|
||||
/* SDMA WR IA */
|
||||
0x5000,
|
||||
0x5000,
|
||||
/* RESERVED */
|
||||
0,
|
||||
/* USB OTG IA */
|
||||
0x4400,
|
||||
0x4400,
|
||||
0x4400,
|
||||
/* USB HOST IA */
|
||||
0x4000,
|
||||
0x4000,
|
||||
/* RESERVED */
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
/* SAD2D IA */
|
||||
0x3000,
|
||||
0x3000,
|
||||
0x3000,
|
||||
/* RESERVED */
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
/* SMA TA */
|
||||
0x2000,
|
||||
/* GPMC TA */
|
||||
0x2400,
|
||||
/* OCM RAM TA */
|
||||
0x2800,
|
||||
/* OCM ROM TA */
|
||||
0x2C00,
|
||||
/* L4 CORE TA */
|
||||
0x6800,
|
||||
/* L4 PER TA */
|
||||
0x6c00,
|
||||
/* IVA 2.2 TA */
|
||||
0x6000,
|
||||
/* SGX TA */
|
||||
0x6400,
|
||||
/* L4 EMU TA */
|
||||
0x7000,
|
||||
/* GPMC TA */
|
||||
0x2400,
|
||||
/* L4 CORE TA */
|
||||
0x6800,
|
||||
/* L4 PER TA */
|
||||
0x6c00,
|
||||
/* L4 EMU TA */
|
||||
0x7000,
|
||||
/* MAD2D TA */
|
||||
0x3400,
|
||||
/* RESERVED */
|
||||
0,
|
||||
0,
|
||||
};
|
||||
|
||||
static unsigned int omap3_l3_debug_bases[] = {
|
||||
/* MPU DATA IA */
|
||||
0x1400,
|
||||
/* RESERVED */
|
||||
0,
|
||||
0,
|
||||
/* DAP IA */
|
||||
0x5c00,
|
||||
0x5c00,
|
||||
/* RESERVED */
|
||||
0,
|
||||
/* IVA 2.2 IA */
|
||||
0x1800,
|
||||
/* REST RESERVED */
|
||||
};
|
||||
|
||||
static u32 *omap3_l3_bases[] = {
|
||||
omap3_l3_app_bases,
|
||||
omap3_l3_debug_bases,
|
||||
};
|
||||
|
||||
/*
|
||||
* REVISIT define __raw_readll/__raw_writell here, but move them to
|
||||
* <asm/io.h> at some point
|
||||
*/
|
||||
#define __raw_writell(v, a) (__chk_io_ptr(a), \
|
||||
*(volatile u64 __force *)(a) = (v))
|
||||
#define __raw_readll(a) (__chk_io_ptr(a), \
|
||||
*(volatile u64 __force *)(a))
|
||||
|
||||
#endif
|
202
drivers/bus/vexpress-config.c
Normal file
202
drivers/bus/vexpress-config.c
Normal file
|
@ -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);
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue