blob: 3508412e40d27f94ede0d518dfe3b70e431dacbc [file] [log] [blame]
/*
* 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");