802 lines
19 KiB
C
802 lines
19 KiB
C
|
/*
|
||
|
* Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify it
|
||
|
* under the terms and conditions of the GNU General Public License,
|
||
|
* version 2, as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope 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 <asm/traps.h>
|
||
|
#include <linux/clk.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_device.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/platform/tegra/bridge_mca.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/ioport.h>
|
||
|
#include <soc/tegra/chip-id.h>
|
||
|
|
||
|
#define BUS_ADDR_MASK 0x3fffffff
|
||
|
#define BUS_ERROR_TYPE_MASK 0x3e0
|
||
|
#define BUS_ERROR_TYPE_SHIFT 5
|
||
|
|
||
|
static struct bridge_mca_error t18x_axi_errors[] = {
|
||
|
{.desc = "Reserved"}, /* 0 */
|
||
|
{.desc = "Illegal TZ G0 security access"},
|
||
|
{.desc = "Illegal TZ G1 security access"},
|
||
|
{.desc = "Illegal TZ G2 security access"},
|
||
|
{.desc = "Illegal TZ G3 security access"},
|
||
|
{.desc = "Illegal TZ G4 security access"}, /* 5 */
|
||
|
{.desc = "Illegal TZ G5 security access"},
|
||
|
{.desc = "Illegal TZ G6 security access"},
|
||
|
{.desc = "Illegal TZ G7 security access"},
|
||
|
{.desc = "Illegal priviledged access"},
|
||
|
{.desc = "Address decode error generated by bridge"}, /* 10 */
|
||
|
{.desc = "Insecure access to a slave"},
|
||
|
{.desc = "Insecure master ID access"},
|
||
|
{.desc = "Unsupported FIXED access type"},
|
||
|
{.desc = "Unsupported WRAP access type"},
|
||
|
{.desc = "Unsupported INCR access type"}, /* 15 */
|
||
|
{.desc = "Unsupported burst size error"},
|
||
|
{.desc = "Unsupported alignment type error"},
|
||
|
{.desc = "Timeout error"},
|
||
|
{.desc = "Illegal BE access error"},
|
||
|
{.desc = "Slave error"}, /* 20 */
|
||
|
{.desc = "Slave decode error"},
|
||
|
{.desc = "Bridge error"},
|
||
|
};
|
||
|
|
||
|
static char *src_ids[] = {
|
||
|
"Reserved", /* 0000 */
|
||
|
"CCPLEX", /* 0001 */
|
||
|
"CCPLEX-DPMU", /* 0010 */
|
||
|
"BPMP", /* 0011 */
|
||
|
"SPE", /* 0100 */
|
||
|
"CPE/SCE", /* 0101 */
|
||
|
"DMA_PER", /* 0110 */
|
||
|
"TSECA", /* 0111 */
|
||
|
"TSECB", /* 1000 */
|
||
|
"JTAGM", /* 1001 */
|
||
|
"CSITE", /* 1010 */
|
||
|
"APE", /* 1011 */
|
||
|
"Reserved", /* 1100 */
|
||
|
"Reserved", /* 1101 */
|
||
|
"Reserved", /* 1110 */
|
||
|
"Bridge", /* 1111 */
|
||
|
};
|
||
|
|
||
|
static char *dma_modes[] = {
|
||
|
"APB Read/MC Write",
|
||
|
"Memory to Memory Copy",
|
||
|
"MC Read/APB Write",
|
||
|
"Fixed Pattern"
|
||
|
};
|
||
|
|
||
|
static LIST_HEAD(bridge_list);
|
||
|
static DEFINE_RAW_SPINLOCK(bridge_lock);
|
||
|
|
||
|
static void print_mca(struct seq_file *file, const char *fmt, ...)
|
||
|
{
|
||
|
va_list args;
|
||
|
struct va_format vaf;
|
||
|
|
||
|
va_start(args, fmt);
|
||
|
|
||
|
if (file) {
|
||
|
seq_vprintf(file, fmt, args);
|
||
|
} else {
|
||
|
vaf.fmt = fmt;
|
||
|
vaf.va = &args;
|
||
|
pr_crit("%pV", &vaf);
|
||
|
}
|
||
|
|
||
|
va_end(args);
|
||
|
}
|
||
|
|
||
|
static void print_cache(struct seq_file *file, u32 cache)
|
||
|
{
|
||
|
if ((cache & 0x3) == 0x0) {
|
||
|
print_mca(file, "\tCache: 0x%x -- Strongly Ordered\n", cache);
|
||
|
return;
|
||
|
}
|
||
|
if ((cache & 0x3) == 0x1) {
|
||
|
print_mca(file, "\tCache: 0x%x -- Device\n", cache);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
switch (cache) {
|
||
|
case 0x2:
|
||
|
print_mca(file,
|
||
|
"\tCache: 0x%x -- Non-cacheable/Non-Bufferable\n",
|
||
|
cache);
|
||
|
break;
|
||
|
case 0x3:
|
||
|
print_mca(file,
|
||
|
"\tCache: 0x%x -- Non-cacheable/Bufferable\n",
|
||
|
cache);
|
||
|
break;
|
||
|
default:
|
||
|
print_mca(file, "\tCache: 0x%x -- Cacheable\n", cache);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void print_prot(struct seq_file *file, u32 prot)
|
||
|
{
|
||
|
char *data_str;
|
||
|
char *secure_str;
|
||
|
char *priv_str;
|
||
|
|
||
|
data_str = (prot & 0x4) ? "Instruction" : "Data";
|
||
|
secure_str = (prot & 0x2) ? "Non-Secure" : "Secure";
|
||
|
priv_str = (prot & 0x1) ? "Privileged" : "Unprivileged";
|
||
|
|
||
|
print_mca(file, "\tProtection: 0x%x -- %s, %s, %s Access\n",
|
||
|
prot, priv_str, secure_str, data_str);
|
||
|
}
|
||
|
|
||
|
static void print_axi_id(struct seq_file *file, u32 src_id, u32 axi_id)
|
||
|
{
|
||
|
char *cp;
|
||
|
|
||
|
switch (src_id) {
|
||
|
case BRIDGE_SRCID_CCPLEX:
|
||
|
case BRIDGE_SRCID_CCPLEX_DPMU:
|
||
|
if (axi_id & 0x40) {
|
||
|
print_mca(file, "\tAXI_ID: 0x%x -- DPMU\n", axi_id);
|
||
|
} else {
|
||
|
if (axi_id < 0x04)
|
||
|
print_mca(file,
|
||
|
"\tAXI_ID: 0x%x -- Denver Core %d\n",
|
||
|
axi_id, axi_id);
|
||
|
else if (axi_id < 0x08)
|
||
|
print_mca(file,
|
||
|
"\tAXI_ID: 0x%x -- A57 Core %d\n",
|
||
|
axi_id, axi_id - 0x4);
|
||
|
else
|
||
|
print_mca(file,
|
||
|
"\tAXI_ID: 0x%x -- Pool B\n", axi_id);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case BRIDGE_SRCID_BPMP:
|
||
|
switch ((axi_id >> 3) & 0x7) {
|
||
|
case 0x0:
|
||
|
cp = "DMAD2NIC";
|
||
|
break;
|
||
|
case 0x1:
|
||
|
cp = "CPUD2NIC";
|
||
|
break;
|
||
|
case 0x2:
|
||
|
cp = "CPU2NIC";
|
||
|
break;
|
||
|
case 0x6:
|
||
|
cp = "CVC2NIC";
|
||
|
break;
|
||
|
default:
|
||
|
cp = "Unknown";
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
print_mca(file,
|
||
|
"\tAXI_ID: 0x%x -- Submaster ID: %s, ID: %d\n",
|
||
|
axi_id, cp, axi_id & 0x7);
|
||
|
break;
|
||
|
|
||
|
case BRIDGE_SRCID_CPE_SCE:
|
||
|
case BRIDGE_SRCID_SPE:
|
||
|
switch ((axi_id >> 4) & 0x3) {
|
||
|
case 0x0:
|
||
|
cp = "DMAD2NIC";
|
||
|
break;
|
||
|
case 0x2:
|
||
|
cp = "CPUD2NIC";
|
||
|
break;
|
||
|
default:
|
||
|
cp = "Unknown";
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
print_mca(file,
|
||
|
"\tAXI_ID: 0x%x -- Submaster ID: %s, ID: %d\n",
|
||
|
axi_id, cp, axi_id & 0xf);
|
||
|
break;
|
||
|
|
||
|
case BRIDGE_SRCID_APE:
|
||
|
print_mca(file,
|
||
|
"\tAXI_ID: 0x%x -- %s Access, ID: 0x%x\n",
|
||
|
axi_id,
|
||
|
(axi_id & 0x40) ? "ADMA" : "ADSP",
|
||
|
axi_id & 0x3f);
|
||
|
break;
|
||
|
|
||
|
case BRIDGE_SRCID_DMA_PER:
|
||
|
cp = dma_modes[(axi_id >> 5) & 0x3];
|
||
|
print_mca(file,
|
||
|
"\tAXI_ID: 0x%x -- DMA Mode = %s, Channel ID = 0x%x\n",
|
||
|
axi_id, cp, axi_id & 0x1f);
|
||
|
break;
|
||
|
|
||
|
case BRIDGE_SRCID_CSITE:
|
||
|
if (axi_id & 0x1) {
|
||
|
print_mca(file, "\tAXI_ID: 0x%x -- CSITE\n", axi_id);
|
||
|
} else {
|
||
|
switch (axi_id) {
|
||
|
case 0x00:
|
||
|
print_mca(file, "\tAXI_ID: 0x%x -- MC Port 0\n",
|
||
|
axi_id);
|
||
|
break;
|
||
|
case 0x02:
|
||
|
print_mca(file, "\tAXI_ID: 0x%x -- MC Port 1\n",
|
||
|
axi_id);
|
||
|
break;
|
||
|
default:
|
||
|
print_mca(file, "\tAXI_ID: 0x%x\n", axi_id);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
print_mca(file, "\tAXI_ID: 0x%x\n", axi_id);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
static void print_bank_info(struct seq_file *file, struct bridge_mca_bank *bank)
|
||
|
{
|
||
|
int count = 0;
|
||
|
u32 bus_addr;
|
||
|
u32 bus_status;
|
||
|
u32 error_type;
|
||
|
u32 id;
|
||
|
u32 len;
|
||
|
u32 prot;
|
||
|
u32 axi_id;
|
||
|
u32 src_id;
|
||
|
u32 cache;
|
||
|
u32 burst;
|
||
|
u64 addr;
|
||
|
struct resource *res = NULL;
|
||
|
|
||
|
while (bank->error_fifo_count(bank->vaddr)) {
|
||
|
bus_status = bank->error_status(bank->vaddr);
|
||
|
bus_addr = bank->error_status(bank->vaddr);
|
||
|
if (((bus_status >> 16) & 0xffff) == 0xdead &&
|
||
|
((bus_addr >> 16) & 0xffff) == 0xdead)
|
||
|
break;
|
||
|
|
||
|
if (count > 0)
|
||
|
print_mca(file,
|
||
|
"======================================\n");
|
||
|
|
||
|
addr = get_bridge_addr(bus_addr);
|
||
|
burst = get_bridge_burst(bus_addr);
|
||
|
|
||
|
id = get_bridge_id(bus_status);
|
||
|
error_type = get_bridge_err_type(bus_status);
|
||
|
len = get_bridge_len(bus_status);
|
||
|
prot = get_bridge_prot(bus_status);
|
||
|
axi_id = get_bridge_axi_id(bus_status);
|
||
|
src_id = get_bridge_src_id(bus_status);
|
||
|
cache = get_bridge_cache(bus_status);
|
||
|
|
||
|
print_mca(file, "Raw FIFO Entry: %d\n", count);
|
||
|
print_mca(file, "\tADDR: 0x%x\n", bus_addr);
|
||
|
print_mca(file, "\tSTAT: 0x%x\n", bus_status);
|
||
|
print_mca(file, "--------------------------------------\n");
|
||
|
|
||
|
print_mca(file, "Decoded FIFO Entry: %d\n", count);
|
||
|
print_mca(file, "\tDirection: %s\n",
|
||
|
bus_status & BRIDGE_RW ? "READ" : "WRITE");
|
||
|
print_mca(file, "\tBridge ID: 0x%x\n", id);
|
||
|
print_mca(file, "\tError Type: 0x%x -- %s\n", error_type,
|
||
|
(error_type >= bank->max_error ? "Unknown" :
|
||
|
bank->errors[error_type].desc));
|
||
|
print_mca(file, "\tLength: %d\n", len);
|
||
|
print_prot(file, prot);
|
||
|
|
||
|
print_mca(file, "\tSource ID: 0x%x -- %s\n",
|
||
|
src_id, src_ids[src_id]);
|
||
|
print_axi_id(file, src_id, axi_id);
|
||
|
print_cache(file, cache);
|
||
|
print_mca(file, "\tBurst: 0x%x\n", burst);
|
||
|
|
||
|
res = locate_resource(&iomem_resource, addr);
|
||
|
if (res == NULL)
|
||
|
print_mca(file, "\tAddress: 0x%llx (Unknown Device)\n",
|
||
|
addr);
|
||
|
else
|
||
|
print_mca(file, "\tAddress: 0x%llx -- %s + 0x%llx\n",
|
||
|
addr, res->name, addr - res->start);
|
||
|
|
||
|
count += 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void print_bank(struct bridge_mca_bank *bank)
|
||
|
{
|
||
|
if (bank->error_fifo_count(bank->vaddr) == 0)
|
||
|
return;
|
||
|
|
||
|
pr_crit("**************************************\n");
|
||
|
pr_crit("CPU%d Machine check error in %s@0x%llx:\n",
|
||
|
smp_processor_id(), bank->name, bank->bank);
|
||
|
print_bank_info(NULL, bank);
|
||
|
pr_crit("**************************************\n");
|
||
|
}
|
||
|
|
||
|
static int bridge_serr_hook(struct pt_regs *regs, int reason,
|
||
|
unsigned int esr, void *priv)
|
||
|
{
|
||
|
struct bridge_mca_bank *bank = priv;
|
||
|
int retval = 1;
|
||
|
|
||
|
if (!bank->seen_error &&
|
||
|
bank->error_fifo_count(bank->vaddr)) {
|
||
|
print_bank(bank);
|
||
|
bank->seen_error = 1;
|
||
|
retval = 0;
|
||
|
}
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_DEBUG_FS
|
||
|
static DEFINE_MUTEX(bridge_mca_mutex);
|
||
|
static struct dentry *bridge_timeout_dir;
|
||
|
static struct dentry *bridge_timeout_node;
|
||
|
static int created_root = false;
|
||
|
|
||
|
static int bridge_mca_show(struct seq_file *file, void *data)
|
||
|
{
|
||
|
struct bridge_mca_bank *bank;
|
||
|
|
||
|
mutex_lock(&bridge_mca_mutex);
|
||
|
|
||
|
list_for_each_entry(bank, &bridge_list, node) {
|
||
|
print_mca(file, "Bridge %s@0x%llx:\n",
|
||
|
bank->name, bank->bank);
|
||
|
|
||
|
if (bank->error_fifo_count(bank->vaddr) == 0x0) {
|
||
|
print_mca(file, "\tNo Errors\n");
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
print_bank_info(file, bank);
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&bridge_mca_mutex);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int bridge_mca_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
return single_open(file, bridge_mca_show, inode->i_private);
|
||
|
}
|
||
|
|
||
|
static const struct file_operations bridge_mca_fops = {
|
||
|
.open = bridge_mca_open,
|
||
|
.read = seq_read,
|
||
|
.llseek = seq_lseek,
|
||
|
.release = single_release
|
||
|
};
|
||
|
|
||
|
static int bridge_timeout_show(struct seq_file *s, void *unused)
|
||
|
{
|
||
|
struct device *dev = s->private;
|
||
|
struct resource *res_base;
|
||
|
struct bridge_mca_bank *bank;
|
||
|
unsigned long flags;
|
||
|
struct platform_device *pdev;
|
||
|
|
||
|
if (WARN_ON(!dev))
|
||
|
return -EINVAL;
|
||
|
|
||
|
pdev = to_platform_device(dev);
|
||
|
|
||
|
res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||
|
if (!res_base) {
|
||
|
dev_err(&pdev->dev, "Could not find base address");
|
||
|
return -ENOENT;
|
||
|
}
|
||
|
|
||
|
raw_spin_lock_irqsave(&bridge_lock, flags);
|
||
|
list_for_each_entry(bank, &bridge_list, node) {
|
||
|
if (bank->bank == res_base->start) {
|
||
|
seq_printf(s, "timeout = %u\n", bank->timeout);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
raw_spin_unlock_irqrestore(&bridge_lock, flags);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static ssize_t bridge_timeout_set(struct file *file,
|
||
|
const char __user *addr, size_t len, loff_t *pos)
|
||
|
{
|
||
|
struct seq_file *m = file->private_data;
|
||
|
struct device *dev = m ? m->private : NULL;
|
||
|
struct resource *res_base;
|
||
|
struct bridge_mca_bank *bank;
|
||
|
unsigned long flags;
|
||
|
struct platform_device *pdev;
|
||
|
u32 timeout;
|
||
|
int ret;
|
||
|
|
||
|
if (WARN_ON(!dev))
|
||
|
return -EINVAL;
|
||
|
|
||
|
pdev = to_platform_device(dev);
|
||
|
|
||
|
ret = kstrtouint_from_user(addr, len, 10, &timeout);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
|
||
|
if (timeout > 100000) {
|
||
|
dev_err(&pdev->dev, "bad value of timeout = %u\n",
|
||
|
timeout);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||
|
if (!res_base) {
|
||
|
dev_err(&pdev->dev, "Could not find base address");
|
||
|
return -ENOENT;
|
||
|
}
|
||
|
|
||
|
raw_spin_lock_irqsave(&bridge_lock, flags);
|
||
|
list_for_each_entry(bank, &bridge_list, node) {
|
||
|
if (bank->bank == res_base->start)
|
||
|
break;
|
||
|
}
|
||
|
raw_spin_unlock_irqrestore(&bridge_lock, flags);
|
||
|
|
||
|
bank->setup_timeout(bank->vaddr, timeout, &pdev->dev);
|
||
|
bank->timeout = timeout;
|
||
|
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
static int bridge_timeout_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
return single_open(file, bridge_timeout_show, inode->i_private);
|
||
|
}
|
||
|
|
||
|
static const struct file_operations timeout_fops = {
|
||
|
.open = bridge_timeout_open,
|
||
|
.read = seq_read,
|
||
|
.write = bridge_timeout_set,
|
||
|
.llseek = seq_lseek,
|
||
|
.release = single_release
|
||
|
};
|
||
|
|
||
|
static int bridge_timeout_dbgfs_init(struct bridge_mca_bank *bank,
|
||
|
struct device *dev)
|
||
|
{
|
||
|
char devname[50];
|
||
|
|
||
|
snprintf(devname, sizeof(devname), "%s@0x%llx", bank->name, bank->bank);
|
||
|
|
||
|
bridge_timeout_dir = debugfs_create_dir(devname, NULL);
|
||
|
if (!bridge_timeout_dir) {
|
||
|
dev_err(dev, "Failed to create bridge timeout debugfs dir");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
bridge_timeout_node = debugfs_create_file("timeout", S_IRUGO,
|
||
|
bridge_timeout_dir, dev, &timeout_fops);
|
||
|
if (!bridge_timeout_node) {
|
||
|
dev_err(dev, "Failed to create brige_timeout_node");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
}
|
||
|
|
||
|
static int bridge_mca_dbgfs_init(void)
|
||
|
{
|
||
|
struct dentry *d;
|
||
|
if (!created_root) {
|
||
|
d = debugfs_create_file("tegra_bridge_mca",
|
||
|
S_IRUGO, NULL, NULL, &bridge_mca_fops);
|
||
|
if (IS_ERR_OR_NULL(d)) {
|
||
|
pr_err("%s: could not create 'tegra_bridge_mca' node\n",
|
||
|
__func__);
|
||
|
return PTR_ERR(d);
|
||
|
}
|
||
|
created_root = true;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
#else
|
||
|
static int bridge_mca_dbgfs_init(void) { return 0; }
|
||
|
static int bridge_timeout_dbgfs_init(struct bridge_mca_bank *bank,
|
||
|
struct device *dev) { return 0; }
|
||
|
#endif
|
||
|
|
||
|
#define AXIAPB_TIMEOUT_TIMER 0x2c8
|
||
|
#define AXI2APB_ERROR_STATUS 0x2ec
|
||
|
#define AXI2APB_FIFO_STATUS3 0x2f8
|
||
|
#define AXI2APB_FIFO_ERROR_SHIFT 13
|
||
|
#define AXI2APB_FIFO_ERROR_MASK 0x1f
|
||
|
|
||
|
static unsigned int axi2apb_error_status(void __iomem *addr)
|
||
|
{
|
||
|
unsigned int error_status;
|
||
|
|
||
|
error_status = readl(addr+AXI2APB_ERROR_STATUS);
|
||
|
|
||
|
writel(0xFFFFFFFF, addr+AXI2APB_ERROR_STATUS);
|
||
|
return error_status;
|
||
|
}
|
||
|
|
||
|
static unsigned int axi2apb_error_fifo_count(void __iomem *addr)
|
||
|
{
|
||
|
unsigned int fifo_status;
|
||
|
|
||
|
fifo_status = readl(addr+AXI2APB_FIFO_STATUS3);
|
||
|
|
||
|
fifo_status >>= AXI2APB_FIFO_ERROR_SHIFT;
|
||
|
fifo_status &= AXI2APB_FIFO_ERROR_MASK;
|
||
|
return fifo_status;
|
||
|
}
|
||
|
|
||
|
static unsigned int axi2apb_setup_timeout(void __iomem *addr, u32 timeout,
|
||
|
struct device *dev)
|
||
|
{
|
||
|
unsigned long rate;
|
||
|
struct clk *clk;
|
||
|
u32 value = 0;
|
||
|
|
||
|
clk = devm_clk_get(dev, "axi_cbb");
|
||
|
if (IS_ERR(clk)) {
|
||
|
dev_info(dev, "can not get axi_cbb clock\n");
|
||
|
return PTR_ERR(clk);
|
||
|
}
|
||
|
rate = clk_get_rate(clk);
|
||
|
/*get rate in MHz*/
|
||
|
rate = rate / 1000000;
|
||
|
dev_info(dev, "axi_cbb clk rate = %lu MHZ, timeout = %u useconds\n",
|
||
|
rate, timeout);
|
||
|
|
||
|
/*get timeout val for programming timer reg*/
|
||
|
if (rate < 0x1000 && timeout < 0x100000 && rate > 0 && timeout > 0) {
|
||
|
value = (u32)(rate * timeout);
|
||
|
/* set timeout. By default, timeout config is enabled*/
|
||
|
writel(value, addr + AXIAPB_TIMEOUT_TIMER);
|
||
|
dev_info(dev, "enabled timeout = %u\n", value);
|
||
|
} else
|
||
|
dev_err(dev, "error enabling timeout = %u: rate=%lu, timeout=%u\n",
|
||
|
value, rate, timeout);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct tegra_bridge_data axi2apb_data = {
|
||
|
.name = "AXI2APB",
|
||
|
.error_status = axi2apb_error_status,
|
||
|
.error_fifo_count = axi2apb_error_fifo_count,
|
||
|
.setup_timeout = axi2apb_setup_timeout,
|
||
|
.errors = t18x_axi_errors,
|
||
|
.max_error = ARRAY_SIZE(t18x_axi_errors),
|
||
|
.timeout = 97000 /* Default value of timer */
|
||
|
};
|
||
|
|
||
|
|
||
|
#define AXIP2P_SLV_RD_RESP_TIMER 0xf8
|
||
|
#define AXIP2P_SLV_WR_RESP_TIMER 0xfc
|
||
|
#define AXIP2P_TIMEOUT_CONFIG 0x104
|
||
|
#define AXIP2P_TIMEOUT_CONFIG_EN 0x00000001
|
||
|
#define AXIP2P_ERROR_STATUS 0x11c
|
||
|
#define AXIP2P_FIFO_STATUS 0x120
|
||
|
#define AXIP2P_FIFO_ERROR_SHIFT 26
|
||
|
#define AXIP2P_FIFO_ERROR_MASK 0x3f
|
||
|
|
||
|
static unsigned int axip2p_error_status(void __iomem *addr)
|
||
|
{
|
||
|
unsigned int error_status;
|
||
|
|
||
|
error_status = readl(addr+AXIP2P_ERROR_STATUS);
|
||
|
|
||
|
writel(0xFFFFFFFF, addr+AXIP2P_ERROR_STATUS);
|
||
|
return error_status;
|
||
|
}
|
||
|
|
||
|
static unsigned int axip2p_error_fifo_count(void __iomem *addr)
|
||
|
{
|
||
|
unsigned int fifo_status;
|
||
|
|
||
|
fifo_status = readl(addr+AXIP2P_FIFO_STATUS);
|
||
|
|
||
|
fifo_status >>= AXIP2P_FIFO_ERROR_SHIFT;
|
||
|
fifo_status &= AXIP2P_FIFO_ERROR_MASK;
|
||
|
return fifo_status;
|
||
|
}
|
||
|
|
||
|
static unsigned int axip2p_setup_timeout(void __iomem *addr, u32 timeout,
|
||
|
struct device *dev)
|
||
|
{
|
||
|
unsigned long rate;
|
||
|
struct clk *clk;
|
||
|
u32 value = 0, tc_value = 0;
|
||
|
|
||
|
clk = devm_clk_get(dev, "axi_cbb");
|
||
|
if (IS_ERR(clk)) {
|
||
|
dev_info(dev, "can not get axi_cbb clock\n");
|
||
|
return PTR_ERR(clk);
|
||
|
}
|
||
|
rate = clk_get_rate(clk);
|
||
|
/*get rate in MHz*/
|
||
|
rate = rate / 1000000;
|
||
|
dev_info(dev, "axi_cbb clk rate = %lu MHZ, timeout = %u useconds\n",
|
||
|
rate, timeout);
|
||
|
|
||
|
/*get timeout val for programming timer reg*/
|
||
|
if (rate < 0x1000 && timeout < 0x100000 && rate > 0 && timeout > 0) {
|
||
|
value = (u32)(rate * timeout);
|
||
|
/*Program timeout*/
|
||
|
writel(value, addr + AXIP2P_SLV_RD_RESP_TIMER);
|
||
|
writel(value, addr + AXIP2P_SLV_WR_RESP_TIMER);
|
||
|
/* Enable timeout */
|
||
|
tc_value = readl(addr + AXIP2P_TIMEOUT_CONFIG);
|
||
|
writel(tc_value | AXIP2P_TIMEOUT_CONFIG_EN, addr +
|
||
|
AXIP2P_TIMEOUT_CONFIG);
|
||
|
dev_info(dev, "enabled timeout = %u\n", value);
|
||
|
} else
|
||
|
dev_err(dev, "error enabling timeout = %u: rate=%lu, timeout=%u\n",
|
||
|
value, rate, timeout);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct tegra_bridge_data axip2p_data = {
|
||
|
.name = "AXIP2P",
|
||
|
.error_status = axip2p_error_status,
|
||
|
.error_fifo_count = axip2p_error_fifo_count,
|
||
|
.setup_timeout = axip2p_setup_timeout,
|
||
|
.errors = t18x_axi_errors,
|
||
|
.max_error = ARRAY_SIZE(t18x_axi_errors),
|
||
|
.timeout = 97000 /* Default value of timer */
|
||
|
};
|
||
|
|
||
|
static struct of_device_id tegra18_bridge_match[] = {
|
||
|
{.compatible = "nvidia,tegra186-AXI2APB-bridge",
|
||
|
.data = &axi2apb_data},
|
||
|
{.compatible = "nvidia,tegra186-AXIP2P-bridge",
|
||
|
.data = &axip2p_data},
|
||
|
{},
|
||
|
};
|
||
|
|
||
|
MODULE_DEVICE_TABLE(of, tegra18_bridge_match);
|
||
|
|
||
|
static int tegra18_bridge_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct resource *res_base;
|
||
|
struct bridge_mca_bank *bank;
|
||
|
const struct tegra_bridge_data *bdata;
|
||
|
struct serr_hook *hook;
|
||
|
const struct of_device_id *match;
|
||
|
unsigned long flags;
|
||
|
int rc;
|
||
|
u32 timeout;
|
||
|
|
||
|
/*
|
||
|
* Bridges don't exist on the simulator
|
||
|
*/
|
||
|
if (tegra_cpu_is_asim())
|
||
|
return 0;
|
||
|
|
||
|
match = of_match_device(of_match_ptr(tegra18_bridge_match),
|
||
|
&pdev->dev);
|
||
|
|
||
|
if (!match) {
|
||
|
dev_err(&pdev->dev, "No device match found");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
bdata = match->data;
|
||
|
|
||
|
res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||
|
if (!res_base) {
|
||
|
dev_err(&pdev->dev, "Could not find base address");
|
||
|
return -ENOENT;
|
||
|
}
|
||
|
|
||
|
rc = bridge_mca_dbgfs_init();
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
|
||
|
bank = devm_kzalloc(&pdev->dev, sizeof(*bank), GFP_KERNEL);
|
||
|
bank->bank = res_base->start;
|
||
|
bank->vaddr = devm_ioremap_resource(&pdev->dev, res_base);
|
||
|
if(IS_ERR(bank->vaddr))
|
||
|
return -EPERM;
|
||
|
|
||
|
bank->name = bdata->name;
|
||
|
bank->error_status = bdata->error_status;
|
||
|
bank->error_fifo_count = bdata->error_fifo_count;
|
||
|
bank->errors = bdata->errors;
|
||
|
bank->max_error = bdata->max_error;
|
||
|
bank->setup_timeout = bdata->setup_timeout;
|
||
|
bank->seen_error = 0;
|
||
|
bank->timeout = bdata->timeout;
|
||
|
rc = bridge_timeout_dbgfs_init(bank, &pdev->dev);
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
|
||
|
hook = devm_kzalloc(&pdev->dev, sizeof(*hook), GFP_KERNEL);
|
||
|
hook->fn = bridge_serr_hook;
|
||
|
hook->priv = bank;
|
||
|
bank->hook = hook;
|
||
|
|
||
|
raw_spin_lock_irqsave(&bridge_lock, flags);
|
||
|
list_add(&bank->node, &bridge_list);
|
||
|
raw_spin_unlock_irqrestore(&bridge_lock, flags);
|
||
|
|
||
|
register_serr_hook(hook);
|
||
|
|
||
|
/*
|
||
|
* Flush out (and report) any early bridge errors.
|
||
|
*/
|
||
|
print_bank_info(NULL, bank);
|
||
|
|
||
|
if (of_property_read_u32(pdev->dev.of_node, "timeout", &timeout) == 0) {
|
||
|
if (timeout > 0 && timeout < 100000) {
|
||
|
bank->setup_timeout(bank->vaddr, timeout, &pdev->dev);
|
||
|
bank->timeout = timeout;
|
||
|
}
|
||
|
else
|
||
|
dev_err(&pdev->dev, "error setting up timeout = %u\n",
|
||
|
timeout);
|
||
|
}
|
||
|
|
||
|
dev_info(&pdev->dev, "bridge probed OK\n");
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int tegra18_bridge_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
struct resource *res_base;
|
||
|
struct bridge_mca_bank *bank;
|
||
|
unsigned long flags;
|
||
|
|
||
|
res_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||
|
if (!res_base)
|
||
|
return 0;
|
||
|
|
||
|
raw_spin_lock_irqsave(&bridge_lock, flags);
|
||
|
list_for_each_entry(bank, &bridge_list, node) {
|
||
|
if (bank->bank == res_base->start) {
|
||
|
unregister_serr_hook(bank->hook);
|
||
|
list_del(&bank->node);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
raw_spin_unlock_irqrestore(&bridge_lock, flags);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct platform_driver platform_driver = {
|
||
|
.probe = tegra18_bridge_probe,
|
||
|
.remove = tegra18_bridge_remove,
|
||
|
.driver = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.name = "tegra18-bridge",
|
||
|
.of_match_table = of_match_ptr(tegra18_bridge_match),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
module_platform_driver(platform_driver);
|
||
|
|
||
|
MODULE_LICENSE("GPL v2");
|
||
|
MODULE_DESCRIPTION("Bridge Machine Check / SError handler");
|