695 lines
20 KiB
C
695 lines
20 KiB
C
/*
|
|
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
|
|
* http://www.samsung.com
|
|
*
|
|
* BUS Monitor Debugging Driver for Samsung EXYNOS SoC
|
|
* By Hosung Kim (hosung0.kim@samsung.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.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/io.h>
|
|
#include <linux/list.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/exynos-busmon.h>
|
|
|
|
#define BUSMON_REG_FAULTEN (0x08)
|
|
#define BUSMON_REG_ERRVLD (0x0C)
|
|
#define BUSMON_REG_ERRCLR (0x10)
|
|
#define BUSMON_REG_ERRLOG0 (0x14)
|
|
#define BUSMON_REG_ERRLOG1 (0x18)
|
|
#define BUSMON_REG_ERRLOG2 (0x1C)
|
|
#define BUSMON_REG_ERRLOG3 (0x20)
|
|
#define BUSMON_REG_ERRLOG4 (0x24)
|
|
#define BUSMON_REG_ERRLOG5 (0x28)
|
|
#define BUSMON_EINVAL (99)
|
|
|
|
#define START (0)
|
|
#define END (1)
|
|
#define ARRAY_BITS (2)
|
|
#define ARRAY_SUBRANGE_MAX (1024)
|
|
|
|
#define NEED_TO_CHECK (0xCAFE)
|
|
|
|
/* Error Code Description */
|
|
static char *busmon_errcode[] = {
|
|
"0x0, SLV (Error Detect by the Slave)",
|
|
"0x1, DEC (Decode error)",
|
|
"0x2, UNS (Access type unsupported in target NIU)",
|
|
"0x3, DISC(Disconnected Target or NOC domain)",
|
|
"0x4, SEC (Security error)",
|
|
"0x5, HIDE(Hidden security error)",
|
|
"0x6, TMO (Timeout error)",
|
|
"Invalid errorcode",
|
|
};
|
|
|
|
/* Opcode Description */
|
|
static char *busmon_opcode[] = {
|
|
"0x0, RD (INCR Read)",
|
|
"0x1, RDW (WRAP Read)",
|
|
"0x2, RDEX(Exclusive Read)",
|
|
"0x3, RDLK(Locked Read)",
|
|
"0x4, WR (INCR Write)",
|
|
"0x5, WRW (WRAP Write)",
|
|
"0x6, WREX(Exclusive Write)",
|
|
"0x7, WRLK(Locked Write)",
|
|
"Invalid opcode",
|
|
};
|
|
|
|
#define BUSMON_INIT_DESC_STRING "init-desc"
|
|
#define BUSMON_TARGET_DESC_STRING "target-desc"
|
|
#define BUSMON_USERSIGNAL_DESC_STRING "usersignal-desc"
|
|
#define BUSMON_UNSUPPORTED_STRING "unsupported"
|
|
|
|
struct busmon_timeout {
|
|
char *name;
|
|
void __iomem *regs;
|
|
u32 enabled;
|
|
u32 enable_bit;
|
|
u32 range_bits[ARRAY_BITS];
|
|
struct list_head list;
|
|
};
|
|
|
|
struct busmon_platdata {
|
|
/* RouteID Information Bits */
|
|
u32 init_bits[ARRAY_BITS];
|
|
u32 target_bits[ARRAY_BITS];
|
|
u32 sub_bits[ARRAY_BITS];
|
|
u32 seq_bits[ARRAY_BITS];
|
|
|
|
/* Registers Bits */
|
|
u32 faulten_bits[ARRAY_BITS];
|
|
u32 errvld_bits[ARRAY_BITS];
|
|
u32 errclr_bits[ARRAY_BITS];
|
|
u32 errlog0_lock_bits[ARRAY_BITS];
|
|
u32 errlog0_opc_bits[ARRAY_BITS];
|
|
u32 errlog0_errcode_bits[ARRAY_BITS];
|
|
u32 errlog0_len1_bits[ARRAY_BITS];
|
|
u32 errlog0_format_bits[ARRAY_BITS];
|
|
u32 errlog1_bits[ARRAY_BITS];
|
|
u32 errlog2_bits[ARRAY_BITS];
|
|
u32 errlog3_bits[ARRAY_BITS];
|
|
u32 errlog4_bits[ARRAY_BITS];
|
|
u32 errlog5_bits[ARRAY_BITS];
|
|
u32 errlog5_axcache_bits[ARRAY_BITS];
|
|
u32 errlog5_axdomain_bits[ARRAY_BITS];
|
|
u32 errlog5_axuser_bits[ARRAY_BITS];
|
|
u32 errlog5_axprot_bits[ARRAY_BITS];
|
|
u32 errlog5_axqos_bits[ARRAY_BITS];
|
|
u32 errlog5_axsnoop_bits[ARRAY_BITS];
|
|
|
|
u32 init_num;
|
|
u32 target_num;
|
|
u32 sub_num;
|
|
u32 sub_array;
|
|
|
|
u32 init_flow;
|
|
u32 target_flow;
|
|
u32 subrange;
|
|
u64 target_addr;
|
|
|
|
u32 enabled;
|
|
|
|
u32 sub_index[ARRAY_SUBRANGE_MAX];
|
|
u32 sub_addr[ARRAY_SUBRANGE_MAX];
|
|
|
|
struct busmon_notifier notifier_info;
|
|
|
|
/* timeout block list */
|
|
struct list_head timeout_list;
|
|
};
|
|
|
|
struct busmon_dev {
|
|
struct device *dev;
|
|
struct busmon_platdata *pdata;
|
|
struct of_device_id *match;
|
|
int irq;
|
|
int id;
|
|
void __iomem *regs;
|
|
spinlock_t ctrl_lock;
|
|
};
|
|
|
|
struct busmon_panic_block {
|
|
struct notifier_block nb_panic_block;
|
|
struct busmon_dev *pdev;
|
|
};
|
|
|
|
/* declare notifier_list */
|
|
static ATOMIC_NOTIFIER_HEAD(busmon_notifier_list);
|
|
|
|
static const struct of_device_id busmon_dt_match[] = {
|
|
{ .compatible = "samsung,exynos-busmonitor",
|
|
.data = NULL, },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, busmon_dt_match);
|
|
|
|
static char* busmon_get_string(struct device_node *np,
|
|
const char* desc_str,
|
|
unsigned int desc_num)
|
|
{
|
|
const char *desc_ret;
|
|
int ret;
|
|
|
|
ret = of_property_read_string_index(np, desc_str, desc_num,
|
|
(const char **)&desc_ret);
|
|
if (ret)
|
|
desc_ret = NULL;
|
|
|
|
return (char *)desc_ret;
|
|
}
|
|
|
|
static unsigned int busmon_get_bits(u32 *bits, unsigned int val)
|
|
{
|
|
unsigned int ret = 0, i;
|
|
|
|
/* If bits[START] has BUSMON_EINVAL value, it must be exit */
|
|
if (bits[END] != BUSMON_EINVAL) {
|
|
/* Make masking value by checking from start-bit to end-bit */
|
|
for (i = bits[START]; i <= bits[END]; i++)
|
|
ret = (ret | (1 << i));
|
|
}
|
|
return ret & val;
|
|
}
|
|
|
|
static void busmon_logging_dump_raw(struct busmon_dev *busmon)
|
|
{
|
|
struct busmon_platdata *pdata = busmon->pdata;
|
|
unsigned int errlog0, errlog1, errlog2, errlog3, errlog4, errlog5, opcode, errcode;
|
|
unsigned int axcache, axdomain, axuser, axprot, axqos, axsnoop;
|
|
char *init_desc, *target_desc, *user_desc;
|
|
|
|
errlog0 = __raw_readl(busmon->regs + BUSMON_REG_ERRLOG0);
|
|
errlog1 = __raw_readl(busmon->regs + BUSMON_REG_ERRLOG1);
|
|
errlog2 = __raw_readl(busmon->regs + BUSMON_REG_ERRLOG2);
|
|
errlog3 = __raw_readl(busmon->regs + BUSMON_REG_ERRLOG3);
|
|
errlog4 = __raw_readl(busmon->regs + BUSMON_REG_ERRLOG4);
|
|
errlog5 = __raw_readl(busmon->regs + BUSMON_REG_ERRLOG5);
|
|
|
|
init_desc = busmon_get_string(busmon->dev->of_node,
|
|
BUSMON_INIT_DESC_STRING, pdata->init_flow);
|
|
target_desc = busmon_get_string(busmon->dev->of_node,
|
|
BUSMON_TARGET_DESC_STRING, pdata->target_flow);
|
|
opcode = busmon_get_bits(pdata->errlog0_opc_bits, errlog0) >>
|
|
pdata->errlog0_opc_bits[START];
|
|
errcode = busmon_get_bits(pdata->errlog0_errcode_bits, errlog0) >>
|
|
pdata->errlog0_errcode_bits[START];
|
|
axcache = busmon_get_bits(pdata->errlog5_axcache_bits, errlog5) >>
|
|
pdata->errlog5_axcache_bits[START];
|
|
axdomain = busmon_get_bits(pdata->errlog5_axdomain_bits, errlog5) >>
|
|
pdata->errlog5_axdomain_bits[START];
|
|
axuser = busmon_get_bits(pdata->errlog5_axuser_bits, errlog5) >>
|
|
pdata->errlog5_axuser_bits[START];
|
|
user_desc = busmon_get_string(busmon->dev->of_node,
|
|
BUSMON_USERSIGNAL_DESC_STRING,
|
|
(pdata->init_flow << 4) | axuser);
|
|
axprot = busmon_get_bits(pdata->errlog5_axprot_bits, errlog5) >>
|
|
pdata->errlog5_axprot_bits[START];
|
|
axqos = busmon_get_bits(pdata->errlog5_axqos_bits, errlog5) >>
|
|
pdata->errlog5_axqos_bits[START];
|
|
axsnoop = busmon_get_bits(pdata->errlog5_axsnoop_bits, errlog5) >>
|
|
pdata->errlog5_axsnoop_bits[START];
|
|
|
|
/* Check overflow */
|
|
if (ARRAY_SIZE(busmon_opcode) <= opcode)
|
|
opcode = ARRAY_SIZE(busmon_opcode) - 1;
|
|
if (ARRAY_SIZE(busmon_errcode) <= errcode)
|
|
errcode = ARRAY_SIZE(busmon_errcode) - 1;
|
|
|
|
dev_err(busmon->dev, "Error detected by BUS Monitor\n"
|
|
"=======================================================\n");
|
|
dev_err(busmon->dev,
|
|
"\nDebugging Information (1)\n"
|
|
"\tPath : %s -> %s\n"
|
|
"\topcode : %s\n"
|
|
"\tErrorCode : %s\n"
|
|
"\tLength : 0x%x (bytes)\n"
|
|
"\tAddress : 0x%llx\n"
|
|
"\tFormat : 0x%x\n"
|
|
"\tinitflow : 0x%x\n"
|
|
"\ttargetflow : 0x%x\n"
|
|
"\tsubrange : 0x%x\n"
|
|
"=======================================================\n",
|
|
IS_ERR_OR_NULL(init_desc) ? BUSMON_UNSUPPORTED_STRING : init_desc,
|
|
IS_ERR_OR_NULL(target_desc) ? BUSMON_UNSUPPORTED_STRING : target_desc,
|
|
busmon_opcode[opcode], busmon_errcode[errcode],
|
|
(busmon_get_bits(pdata->errlog0_len1_bits, errlog0) >>
|
|
pdata->errlog0_len1_bits[START]) + 1,
|
|
pdata->target_addr,
|
|
busmon_get_bits(pdata->errlog0_format_bits, errlog0) >>
|
|
pdata->errlog0_format_bits[START],
|
|
pdata->init_flow, pdata->target_flow, pdata->subrange);
|
|
|
|
dev_err(busmon->dev,
|
|
"\nDebugging information (2)\n"
|
|
"\tAXUSER : 0x%x, Master IP: %s\n"
|
|
"\tAXCACHE : 0x%x\n"
|
|
"\tAXDOMAIN : 0x%x\n"
|
|
"\tAXPROT : 0x%x\n"
|
|
"\tAXQOS : 0x%x\n"
|
|
"\tAXSNOOP : 0x%x\n"
|
|
"=======================================================\n",
|
|
axuser, IS_ERR_OR_NULL(user_desc) ? BUSMON_UNSUPPORTED_STRING : user_desc,
|
|
axcache, axdomain, axprot, axqos, axsnoop);
|
|
|
|
dev_err(busmon->dev,
|
|
"\nErrlog Raw Registers\n"
|
|
"\tErrLog0 : 0x%x\n"
|
|
"\tErrLog1 : 0x%x\n"
|
|
"\tErrLog2 : 0x%x\n"
|
|
"\tErrLog3 : 0x%x\n"
|
|
"\tErrLog4 : 0x%x\n"
|
|
"\tErrLog5 : 0x%x\n"
|
|
"=======================================================\n",
|
|
errlog0, errlog1, errlog2, errlog3, errlog4, errlog5);
|
|
|
|
if (!pdata->target_addr)
|
|
dev_err(busmon->dev, "Address is not valid, Needs to check\n");
|
|
|
|
/* Fill the information for notifier call funcion */
|
|
pdata->notifier_info.init_desc = init_desc;
|
|
pdata->notifier_info.target_desc = target_desc;
|
|
pdata->notifier_info.masterip_desc = user_desc;
|
|
pdata->notifier_info.masterip_idx = axuser;
|
|
pdata->notifier_info.target_addr = pdata->target_addr;
|
|
}
|
|
|
|
static void busmon_logging_parse_route(struct busmon_dev *busmon)
|
|
{
|
|
struct busmon_platdata *pdata = busmon->pdata;
|
|
|
|
unsigned int init_id, target_id, sub_id, val, bits;
|
|
unsigned int errlog3 = 0, errlog4 = 0, i;
|
|
|
|
val = __raw_readl(busmon->regs + BUSMON_REG_ERRLOG1);
|
|
bits = busmon_get_bits(pdata->errlog1_bits, val);
|
|
|
|
init_id = busmon_get_bits(pdata->init_bits, bits) >> pdata->init_bits[START];
|
|
target_id = busmon_get_bits(pdata->target_bits, bits) >> pdata->target_bits[START];
|
|
sub_id = busmon_get_bits(pdata->sub_bits, bits) >> pdata->sub_bits[START];
|
|
|
|
pdata->init_flow = init_id;
|
|
pdata->target_flow = target_id;
|
|
pdata->subrange = sub_id;
|
|
|
|
/* Calculate target address */
|
|
errlog3 = __raw_readl(busmon->regs + BUSMON_REG_ERRLOG3);
|
|
errlog4 = __raw_readl(busmon->regs + BUSMON_REG_ERRLOG4);
|
|
|
|
errlog3 = busmon_get_bits(pdata->errlog3_bits, errlog3) >> pdata->errlog3_bits[START];
|
|
errlog4 = busmon_get_bits(pdata->errlog4_bits, errlog4) >> pdata->errlog4_bits[START];
|
|
|
|
val = (init_id * (pdata->target_num * pdata->sub_num)) +
|
|
(target_id * pdata->sub_num) + sub_id;
|
|
|
|
for (i = 0; i < pdata->sub_array; i++) {
|
|
if (pdata->sub_index[i] == val) {
|
|
if (pdata->sub_addr[i] == NEED_TO_CHECK) {
|
|
pdata->target_addr = 0;
|
|
} else {
|
|
pdata->target_addr = ((u64)errlog4 << 32);
|
|
pdata->target_addr |= (errlog3 + pdata->sub_addr[i]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void busmon_logging_dump(struct busmon_dev *busmon)
|
|
{
|
|
busmon_logging_parse_route(busmon);
|
|
busmon_logging_dump_raw(busmon);
|
|
}
|
|
|
|
static irqreturn_t busmon_logging_irq(int irq, void *data)
|
|
{
|
|
struct busmon_dev *busmon = (struct busmon_dev *)data;
|
|
struct busmon_platdata *pdata = busmon->pdata;
|
|
unsigned int bits;
|
|
unsigned int val;
|
|
|
|
/* Check error has been logged */
|
|
val = __raw_readl(busmon->regs + BUSMON_REG_ERRVLD);
|
|
bits = busmon_get_bits(pdata->errvld_bits, val);
|
|
|
|
if (bits) {
|
|
char *init_desc;
|
|
|
|
dev_info(busmon->dev, "BUS monitor information: %d interrupt occurs.\n", (irq - 32));
|
|
busmon_logging_dump(busmon);
|
|
|
|
/* error clear */
|
|
bits = busmon_get_bits(pdata->errclr_bits, 1);
|
|
__raw_writel(bits, busmon->regs + BUSMON_REG_ERRCLR);
|
|
|
|
/* This code is for finding out the source */
|
|
init_desc = busmon_get_string(busmon->dev->of_node,
|
|
BUSMON_INIT_DESC_STRING, pdata->init_flow);
|
|
|
|
/* call notifier_call_chain of busmon */
|
|
atomic_notifier_call_chain(&busmon_notifier_list, 0, &pdata->notifier_info);
|
|
|
|
if (init_desc && !strncmp(init_desc, "CPU", strlen("CPU")))
|
|
dev_err(busmon->dev, "Error detected by BUS monitor.\n");
|
|
else
|
|
panic("Error detected by BUS monitor.");
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
void busmon_notifier_chain_register(struct notifier_block *block)
|
|
{
|
|
atomic_notifier_chain_register(&busmon_notifier_list, block);
|
|
}
|
|
|
|
static int busmon_logging_panic_handler(struct notifier_block *nb,
|
|
unsigned long l, void *buf)
|
|
{
|
|
struct busmon_panic_block *busmon_panic = (struct busmon_panic_block *)nb;
|
|
struct busmon_dev *busmon = busmon_panic->pdev;
|
|
struct busmon_platdata *pdata = busmon->pdata;
|
|
unsigned int bits;
|
|
unsigned int val;
|
|
|
|
if (!IS_ERR_OR_NULL(busmon)) {
|
|
/* Check error has been logged */
|
|
val = __raw_readl(busmon->regs + BUSMON_REG_ERRVLD);
|
|
bits = busmon_get_bits(pdata->errvld_bits, val);
|
|
|
|
if (bits)
|
|
busmon_logging_dump(busmon);
|
|
else
|
|
dev_info(busmon->dev,
|
|
"BUS monitor did not detect any error.\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void busmon_timeout_init(struct busmon_dev *busmon)
|
|
{
|
|
struct busmon_timeout *timeout;
|
|
struct list_head *entry;
|
|
u32 val;
|
|
|
|
if (list_empty(&busmon->pdata->timeout_list))
|
|
return;
|
|
|
|
list_for_each(entry, &busmon->pdata->timeout_list) {
|
|
timeout = list_entry(entry, struct busmon_timeout, list);
|
|
if (timeout && timeout->enabled) {
|
|
val = __raw_readl(timeout->regs);
|
|
val |= (0x1) << timeout->enable_bit;
|
|
__raw_writel(val, timeout->regs);
|
|
|
|
dev_dbg(busmon->dev,
|
|
"Exynos Bus Monitor timeout enabled(%s, bit:%d)\n",
|
|
timeout->name, timeout->enable_bit);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void busmon_logging_init(struct busmon_dev *busmon)
|
|
{
|
|
struct busmon_platdata *pdata = busmon->pdata;
|
|
unsigned int bits;
|
|
|
|
if (pdata->enabled) {
|
|
/* first of all, error clear at occurs previous */
|
|
bits = busmon_get_bits(pdata->errclr_bits, 1);
|
|
__raw_writel(bits, busmon->regs + BUSMON_REG_ERRCLR);
|
|
|
|
/* enable logging init */
|
|
bits = busmon_get_bits(pdata->faulten_bits, 1);
|
|
__raw_writel(bits, busmon->regs + BUSMON_REG_FAULTEN);
|
|
}
|
|
dev_dbg(busmon->dev, "Exynos BUS Monitor logging %s\n",
|
|
pdata->enabled ? "enabled" : "disabled");
|
|
}
|
|
|
|
static int busmon_dt_parse(struct device_node *np,
|
|
struct busmon_dev *busmon)
|
|
{
|
|
struct busmon_platdata *pdata = busmon->pdata;
|
|
struct device_node *time_np, *time_child_np = NULL;
|
|
struct busmon_timeout *timeout;
|
|
u32 regs[2];
|
|
int ret;
|
|
|
|
if (!np || !pdata) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/* Error logging enabled */
|
|
of_property_read_u32(np, "enabled", &pdata->enabled);
|
|
|
|
/* Read BUS Logging setting */
|
|
of_property_read_u32_array(np, "seq-bits", pdata->seq_bits, 2);
|
|
of_property_read_u32_array(np, "sub-bits", pdata->sub_bits, 2);
|
|
of_property_read_u32_array(np, "target-bits", pdata->target_bits, 2);
|
|
of_property_read_u32_array(np, "init-bits", pdata->init_bits, 2);
|
|
|
|
of_property_read_u32_array(np, "faulten-bits", pdata->faulten_bits, 2);
|
|
of_property_read_u32_array(np, "errvld-bits", pdata->errvld_bits, 2);
|
|
of_property_read_u32_array(np, "errclr-bits", pdata->errclr_bits, 2);
|
|
|
|
of_property_read_u32_array(np, "errlog0-lock-bits", pdata->errlog0_lock_bits, 2);
|
|
of_property_read_u32_array(np, "errlog0-opc-bits", pdata->errlog0_opc_bits, 2);
|
|
of_property_read_u32_array(np, "errlog0-errcode-bits", pdata->errlog0_errcode_bits, 2);
|
|
of_property_read_u32_array(np, "errlog0-len1-bits", pdata->errlog0_len1_bits, 2);
|
|
of_property_read_u32_array(np, "errlog0-format-bits", pdata->errlog0_format_bits, 2);
|
|
of_property_read_u32_array(np, "errlog1-bits", pdata->errlog1_bits, 2);
|
|
of_property_read_u32_array(np, "errlog2-bits", pdata->errlog2_bits, 2);
|
|
of_property_read_u32_array(np, "errlog3-bits", pdata->errlog3_bits, 2);
|
|
of_property_read_u32_array(np, "errlog4-bits", pdata->errlog4_bits, 2);
|
|
of_property_read_u32_array(np, "errlog5-bits", pdata->errlog5_bits, 2);
|
|
|
|
/* errlog5's slot bits are different for each */
|
|
ret = of_property_read_u32_array(np, "errlog5-axcache-bits",
|
|
pdata->errlog5_axcache_bits, 2);
|
|
if (ret) {
|
|
pdata->errlog5_axcache_bits[START] = 0;
|
|
pdata->errlog5_axcache_bits[END] = BUSMON_EINVAL;
|
|
}
|
|
ret = of_property_read_u32_array(np, "errlog5-axdomain-bits",
|
|
pdata->errlog5_axdomain_bits, 2);
|
|
if (ret) {
|
|
pdata->errlog5_axdomain_bits[START] = 0;
|
|
pdata->errlog5_axdomain_bits[END] = BUSMON_EINVAL;
|
|
}
|
|
ret = of_property_read_u32_array(np, "errlog5-axuser-bits",
|
|
pdata->errlog5_axuser_bits, 2);
|
|
if (ret) {
|
|
pdata->errlog5_axuser_bits[START] = 0;
|
|
pdata->errlog5_axuser_bits[END] = BUSMON_EINVAL;
|
|
}
|
|
ret = of_property_read_u32_array(np, "errlog5-axprot-bits",
|
|
pdata->errlog5_axprot_bits, 2);
|
|
if (ret) {
|
|
pdata->errlog5_axprot_bits[START] = 0;
|
|
pdata->errlog5_axprot_bits[END] = BUSMON_EINVAL;
|
|
}
|
|
ret = of_property_read_u32_array(np, "errlog5-axqos-bits",
|
|
pdata->errlog5_axqos_bits, 2);
|
|
if (ret) {
|
|
pdata->errlog5_axqos_bits[START] = 0;
|
|
pdata->errlog5_axqos_bits[END] = BUSMON_EINVAL;
|
|
}
|
|
ret = of_property_read_u32_array(np, "errlog5-axsnoop-bits",
|
|
pdata->errlog5_axsnoop_bits, 2);
|
|
if (ret) {
|
|
pdata->errlog5_axsnoop_bits[START] = 0;
|
|
pdata->errlog5_axsnoop_bits[END] = BUSMON_EINVAL;
|
|
}
|
|
|
|
of_property_read_u32(np, "init-num", &pdata->init_num);
|
|
of_property_read_u32(np, "target-num", &pdata->target_num);
|
|
of_property_read_u32(np, "sub-num", &pdata->sub_num);
|
|
of_property_read_u32(np, "sub-array", &pdata->sub_array);
|
|
|
|
of_property_read_u32_array(np, "sub-index", pdata->sub_index, pdata->sub_array);
|
|
of_property_read_u32_array(np, "sub-addr", pdata->sub_addr, pdata->sub_array);
|
|
|
|
/* Mandatory parsing is done */
|
|
ret = 0;
|
|
|
|
/* Check BUS Timeout setting(Option) */
|
|
INIT_LIST_HEAD(&pdata->timeout_list);
|
|
time_np = of_get_child_by_name(np, "timeout");
|
|
if (!time_np)
|
|
goto out;
|
|
|
|
/* BUS timeout setting */
|
|
while ((time_child_np = of_get_next_child(time_np, time_child_np)) != NULL) {
|
|
timeout = devm_kzalloc(busmon->dev,
|
|
sizeof(struct busmon_timeout), GFP_KERNEL);
|
|
if (!timeout) {
|
|
dev_err(busmon->dev,
|
|
"failed to allocate memory for busmon-timeout\n");
|
|
continue;
|
|
}
|
|
if (of_property_read_string(time_child_np, "nickname",
|
|
(const char **)&timeout->name)) {
|
|
dev_err(busmon->dev,
|
|
"failed to get nickname property\n");
|
|
continue;
|
|
}
|
|
|
|
of_property_read_u32_array(time_child_np, "reg", regs, 2);
|
|
timeout->regs = ioremap(regs[0], regs[1]);
|
|
if (!timeout->regs) {
|
|
dev_err(busmon->dev,
|
|
"failed to ioremap for busmon-timeout: %s\n",
|
|
timeout->name);
|
|
devm_kfree(busmon->dev, timeout);
|
|
continue;
|
|
}
|
|
of_property_read_u32(time_child_np, "enabled",
|
|
&timeout->enabled);
|
|
of_property_read_u32(time_child_np, "enable-bit",
|
|
&timeout->enable_bit);
|
|
of_property_read_u32_array(time_child_np, "range-bits",
|
|
timeout->range_bits, 2);
|
|
list_add(&timeout->list, &pdata->timeout_list);
|
|
}
|
|
of_node_put(time_np);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static int busmon_probe(struct platform_device *pdev)
|
|
{
|
|
struct busmon_dev *busmon;
|
|
struct busmon_platdata *pdata = NULL;
|
|
struct busmon_panic_block *busmon_panic = NULL;
|
|
const struct of_device_id *match;
|
|
struct resource *res;
|
|
int ret;
|
|
|
|
busmon = devm_kzalloc(&pdev->dev, sizeof(struct busmon_dev), GFP_KERNEL);
|
|
if (!busmon) {
|
|
dev_err(&pdev->dev, "failed to allocate memory for driver's "
|
|
"private data\n");
|
|
return -ENOMEM;
|
|
}
|
|
busmon->dev = &pdev->dev;
|
|
match = of_match_node(busmon_dt_match, pdev->dev.of_node);
|
|
busmon->match = (struct of_device_id *)match;
|
|
|
|
spin_lock_init(&busmon->ctrl_lock);
|
|
|
|
pdata = devm_kzalloc(&pdev->dev, sizeof(struct busmon_platdata), GFP_KERNEL);
|
|
if (!pdata) {
|
|
dev_err(&pdev->dev, "failed to allocate memory for driver's "
|
|
"platform data\n");
|
|
return -ENOMEM;
|
|
}
|
|
busmon->pdata = pdata;
|
|
|
|
ret = busmon_dt_parse(pdev->dev.of_node, busmon);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to assign device tree parsing\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!res)
|
|
return -ENXIO;
|
|
|
|
busmon->regs = devm_ioremap_resource(&pdev->dev, res);
|
|
if (busmon->regs == NULL) {
|
|
dev_err(&pdev->dev, "failed to claim register region\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
|
|
if (!res)
|
|
return -ENXIO;
|
|
|
|
busmon->irq = res->start;
|
|
ret = devm_request_irq(&pdev->dev, busmon->irq, busmon_logging_irq,
|
|
0, dev_name(&pdev->dev), busmon);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "irq request failed\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
busmon_panic = devm_kzalloc(&pdev->dev,
|
|
sizeof(struct busmon_panic_block), GFP_KERNEL);
|
|
if (!busmon_panic) {
|
|
dev_err(&pdev->dev, "failed to allocate memory for driver's "
|
|
"panic handler data\n");
|
|
} else {
|
|
busmon_panic->nb_panic_block.notifier_call =
|
|
busmon_logging_panic_handler;
|
|
busmon_panic->pdev = busmon;
|
|
atomic_notifier_chain_register(&panic_notifier_list,
|
|
&busmon_panic->nb_panic_block);
|
|
}
|
|
|
|
platform_set_drvdata(pdev, busmon);
|
|
|
|
busmon_timeout_init(busmon);
|
|
busmon_logging_init(busmon);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int busmon_remove(struct platform_device *pdev)
|
|
{
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int busmon_suspend(struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int busmon_resume(struct device *dev)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct busmon_dev *busmon = platform_get_drvdata(pdev);
|
|
|
|
busmon_timeout_init(busmon);
|
|
busmon_logging_init(busmon);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static SIMPLE_DEV_PM_OPS(busmon_pm_ops,
|
|
busmon_suspend,
|
|
busmon_resume);
|
|
#define BUSMON_PM (busmon_pm_ops)
|
|
#else
|
|
#define BUSMON_PM NULL
|
|
#endif
|
|
|
|
static struct platform_driver exynos_busmon_driver = {
|
|
.probe = busmon_probe,
|
|
.remove = busmon_remove,
|
|
.driver = {
|
|
.name = "exynos-busmon",
|
|
.of_match_table = busmon_dt_match,
|
|
.pm = &busmon_pm_ops,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(exynos_busmon_driver);
|
|
|
|
MODULE_DESCRIPTION("Samsung Exynos BUS MONITOR DRIVER");
|
|
MODULE_AUTHOR("Hosung Kim <hosung0.kim@samsung.com");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:exynos-busmon");
|