blob: 338e1a61aa84f06488b052bdb5b4423d81ce579e [file] [log] [blame]
/*
* Copyright (c) 2018 MediaTek Inc.
* Author: Yong Wu <yong.wu@mediatek.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 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.
*/
/*
* This file help debug multimedia HW smi-larb hang and monitor smi-larb.
*/
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/sched/clock.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <soc/mediatek/smi.h>
/* m4u/smi-common use this struction. */
struct smi_test_dev {
void __iomem *base;
struct device *dev;
};
struct smi_larb_dev {
void __iomem *base;
struct device *dev;
int larbid;
struct smi_test_dev *m4u;
struct smi_test_dev *common;
};
#ifndef MTK_LARB_NR_MAX
#define MTK_LARB_NR_MAX 16
#endif
struct mtk_smi_dbg {
struct smi_larb_dev larb[MTK_LARB_NR_MAX];
struct smi_test_dev m4u[2];
struct smi_test_dev smi_common[2];
bool res_init;
/*
*Sometime don't touch the larb power and clock to avoid affect their
*status.
*-false: defaultly, enable the power/clk before dump.
*-true: Don't enable the power/clk before dump.
*/
bool larb_power_disable;
int mon_larb_mode; /* 0. all, 1: read, 2: write.*/
int mon_common_read;
unsigned long long mon_timeout;
};
static const char smi_m4u_str[] = "mediatek,mt8168-m4u";
static int mtk_smi_debug_res_init(struct mtk_smi_dbg *dbgmng)
{
struct device_node *m4u_dev_node = NULL;
struct platform_device *pdev;
struct resource *res;
struct smi_larb_dev *larb;
struct smi_test_dev *m4u;
struct smi_test_dev *common;
int m4uidx = 0;
int ret, larb_nr, i;
/* Find M4U device. */
do {
m4u_dev_node = of_find_compatible_node(m4u_dev_node, NULL,
smi_m4u_str);
if (!m4u_dev_node)
break;
pdev = of_find_device_by_node(m4u_dev_node);
of_node_put(m4u_dev_node);
if (!pdev)
return -ENODEV;
m4u = &dbgmng->m4u[m4uidx];
m4u->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
m4u->base = devm_ioremap_nocache(m4u->dev, res->start,
0x1000);
if (IS_ERR(m4u->base))
return PTR_ERR(m4u->base);
/* Parse the larbs */
larb_nr = of_count_phandle_with_args(m4u_dev_node,
"mediatek,larbs", NULL);
if (larb_nr < 0)
return larb_nr;
for (i = 0; i < larb_nr; i++) {
struct device_node *larbnode, *smi_node;
struct platform_device *plarbdev, *pcommondev;
unsigned int idx;
larbnode = of_parse_phandle(m4u_dev_node,
"mediatek,larbs", i);
if (!larbnode)
return -EINVAL;
if (!of_device_is_available(larbnode))
continue;
ret = of_property_read_u32(larbnode, "mediatek,larb-id",
&idx);
if (ret)
idx = i;
larb = &dbgmng->larb[idx];
WARN_ON(!!larb->dev);
larb->larbid = idx;
plarbdev = of_find_device_by_node(larbnode);
of_node_put(larbnode);
larb->dev = &plarbdev->dev;
res = platform_get_resource(plarbdev, IORESOURCE_MEM,
0);
larb->base = devm_ioremap_nocache(larb->dev,
res->start, 0x1000);
if (IS_ERR(larb->base))
return PTR_ERR(larb->base);
larb->m4u = m4u;
/* Parse the smi-common in smi-larb */
smi_node = of_parse_phandle(larbnode, "mediatek,smi",
0);
if (!smi_node)
return -EINVAL;
pcommondev = of_find_device_by_node(smi_node);
of_node_put(smi_node);
common = &dbgmng->smi_common[0];
if (!common->dev)
common->dev = &pcommondev->dev;
if (common->dev != &pcommondev->dev) {
/* Try smi common1 */
common = &dbgmng->smi_common[1];
if (!common->dev)
common->dev = &pcommondev->dev;
}
if (!common->base) {
res = platform_get_resource(pcommondev,
IORESOURCE_MEM, 0);
common->base = devm_ioremap_nocache(common->dev,
res->start,
0x1000);
if (IS_ERR(common->base))
return PTR_ERR(common->base);
}
larb->common = common;
dev_info(larb->dev, "id %d, common %s.\n",
larb->larbid, dev_name(common->dev));
}
m4uidx++;
} while (m4u_dev_node);
dbgmng->mon_common_read = 1; /* common default dump read. */
dbgmng->mon_timeout = 167000000ULL; /* 167ms */
dbgmng->res_init = true;
return 0;
}
static int smi_larb_power_get(struct device *dev, unsigned int id,
bool larb_power_disable)
{
if (larb_power_disable)
return 0;
return pm_runtime_get_sync(dev);
}
static void smi_larb_power_put(struct device *dev, unsigned int id,
bool larb_power_disable)
{
if (larb_power_disable)
return;
pm_runtime_put_sync(dev);
}
static void smi_m4u_dump(struct device *dev, void __iomem *base)
{
if (!base)
return;
dev_info(dev, "0x48:0x%x(0xa->common 0x2). 0x54:0x%x.\n",
readl_relaxed(base + 0x48), readl_relaxed(base + 0x54));
}
static void smi_larb_dump(struct device *dev, void __iomem *base)
{
int i, j, cnt = 5;
if (!base) {
dev_info(dev, "base is null\n");
return;
}
dev_info(dev, "0xb0(fifo status) 0x%x-0x%x-0x%x. 0x24 0x%x\n",
readl_relaxed(base + 0xb0), readl_relaxed(base + 0xb4),
readl_relaxed(base + 0xb8), readl_relaxed(base + 0x24));
dev_info(dev, "0xa0(violation) 0x%x-0x%x-0x%x-0x%x(0 is expected)\n",
readl_relaxed(base + 0xa0), readl_relaxed(base + 0xa4),
readl_relaxed(base + 0xa8), readl_relaxed(base + 0xac));
dev_info(dev, "0x0c(slp_en) 0x%x, 0xc8(ext_ongoing) 0x%x\n",
readl_relaxed(base + 0x00c), readl_relaxed(base + 0x0c8));
dev_info(dev, "0x60(outstanding) 0x%x, below is 0x200+x\n",
readl_relaxed(base + 0x60));
for (i = 0; i < 32; i += 8) /* 32 */
dev_info(dev, "0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x\n",
readl_relaxed(base + 0x200 + i * 4),
readl_relaxed(base + 0x200 + (i + 1) * 4),
readl_relaxed(base + 0x200 + (i + 2) * 4),
readl_relaxed(base + 0x200 + (i + 3) * 4),
readl_relaxed(base + 0x200 + (i + 4) * 4),
readl_relaxed(base + 0x200 + (i + 5) * 4),
readl_relaxed(base + 0x200 + (i + 6) * 4),
readl_relaxed(base + 0x200 + (i + 7) * 4));
for (i = 0; i < 32; i += 8) /* 32 */
dev_info(dev, "mmu:0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x\n",
readl_relaxed(base + 0x380 + i * 4),
readl_relaxed(base + 0x380 + (i + 1) * 4),
readl_relaxed(base + 0x380 + (i + 2) * 4),
readl_relaxed(base + 0x380 + (i + 3) * 4),
readl_relaxed(base + 0x380 + (i + 4) * 4),
readl_relaxed(base + 0x380 + (i + 5) * 4),
readl_relaxed(base + 0x380 + (i + 6) * 4),
readl_relaxed(base + 0x380 + (i + 7) * 4));
for (j = 0; j < cnt; j++) {
u32 status = readl_relaxed(base + 0x0);
dev_info(dev,
"dump(%d/%d):0x0(%s) 0x%x. 0xbc 0x%x Outstand:\n",
j, cnt, status ? "busy" : "idle", status,
readl_relaxed(base + 0xbc));
for (i = 0; i < 32; i += 8) /* 32 */
dev_info(dev,
"0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x\n",
readl_relaxed(base + 0x280 + i * 4),
readl_relaxed(base + 0x280 + (i + 1) * 4),
readl_relaxed(base + 0x280 + (i + 2) * 4),
readl_relaxed(base + 0x280 + (i + 3) * 4),
readl_relaxed(base + 0x280 + (i + 4) * 4),
readl_relaxed(base + 0x280 + (i + 5) * 4),
readl_relaxed(base + 0x280 + (i + 6) * 4),
readl_relaxed(base + 0x280 + (i + 7) * 4));
}
dev_info(dev, "larb dump done\n");
}
static void smi_common_dump(struct device *dev, void __iomem *base)
{
int i, cnt = 5;
dev_info(dev, "0x220 0x%x 0x100 0x%x 0x300(dcm) 0x%x\n",
readl_relaxed(base + 0x220), readl_relaxed(base + 0x100),
readl_relaxed(base + 0x300));
if (readl_relaxed(base + 0x234) != 0x06080608)
dev_info(dev, "0x234 fail 0x%x\n", readl_relaxed(base + 0x234));
if (readl_relaxed(base + 0x238) != 0x05070507)
dev_info(dev, "0x238 fail 0x%x\n", readl_relaxed(base + 0x238));
if (readl_relaxed(base + 0x23c) != 0x05070507)
dev_info(dev, "0x23c fail 0x%x\n", readl_relaxed(base + 0x23c));
dev_info(dev, "clamp:0x3c0 0x%x(should 0), 0x3c8:0x%x.\n",
readl_relaxed(base + 0x3c0), readl_relaxed(base + 0x3c8));
for (i = 0; i < cnt; i++) {
u32 status = readl_relaxed(base + 0x440);
dev_info(
dev,
"axi input(%d/%d):0x400 0x%x-0x%x-0x%x-0x%x,0x%x-0x%x-0x%x-0x%x\n",
i, cnt,
readl_relaxed(base + 0x400), readl_relaxed(base + 0x404),
readl_relaxed(base + 0x408), readl_relaxed(base + 0x40c),
readl_relaxed(base + 0x410), readl_relaxed(base + 0x414),
readl_relaxed(base + 0x418), readl_relaxed(base + 0x41c)
);
dev_info(
dev, "axi output:0x430 0x%x-0x%x, 0x440 0x%x(%s)\n",
readl_relaxed(base + 0x430), readl_relaxed(base + 0x434),
status, (status & 0x1) ? "idle" : "busy");
}
}
static void smi_larb_monitor_start(struct device *dev, void __iomem *base,
unsigned int portid, int mode)
{
u32 reg;
writel(1, base + 0x404); /* clear */
writel_relaxed(portid, base + 0x408);
reg = 0x1 << 9;
reg |= (mode & 0x3) << 2; /* read/write.*/
writel_relaxed(reg, base + 0x40c);
writel(0x1, base + 0x400); /* start */
}
static void smi_larb_monitor_stop(struct device *dev, void __iomem *base)
{
writel(0x0, base + 0x400); /* stop */
dev_info(dev, "stop larb monitor and dump:\n");
dev_info(dev, "0x410 0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x.\n",
readl_relaxed(base + 0x410), readl_relaxed(base + 0x414),
readl_relaxed(base + 0x418), readl_relaxed(base + 0x41c),
readl_relaxed(base + 0x420), readl_relaxed(base + 0x424),
readl_relaxed(base + 0x428));
}
static void smi_common_monitor_start(struct device *dev, void __iomem *base,
bool read)
{
/*0x1b0[7:4] axi output id, 0 or 1. default always is 0. */
writel_relaxed(0x1, base + 0x1b0);
writel_relaxed(!read, base + 0x1ac);
writel_relaxed(0x1, base + 0x1a4); /* clear */
writel(0x1, base + 0x1a0); /* start */
}
static void smi_common_monitor_stop(struct device *dev, void __iomem *base)
{
writel(0x0, base + 0x1a0); /* stop */
dev_info(dev, "stop common monitor and dump:\n");
dev_info(dev, "0x1c0 0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x.\n",
readl_relaxed(base + 0x1c0), readl_relaxed(base + 0x1c4),
readl_relaxed(base + 0x1c8), readl_relaxed(base + 0x1cc),
readl_relaxed(base + 0x1d0), readl_relaxed(base + 0x1d4),
readl_relaxed(base + 0x1d8));
}
/*
* param: 0xabcd
* a: test case
* 0: dump hang reg; 3: set monitor type(read/write) 4: mon timeout
* 1: monitor start;
* b: larb id commmon mode(0:read; 1:write) timeout ms
* cd: port id larbmod(0:both, 1:read, 2:write) timeout ms
*/
static int smi_debug_set(void *data, u64 val)
{
unsigned int testcase = (val & 0xf000) >> 12;
unsigned int larbid = (val & 0xf00) >> 8;
unsigned int portid = (val & 0xff);
struct mtk_smi_dbg *dbgmng = data;
unsigned long long start, end;
struct smi_larb_dev *larb;
struct smi_test_dev *smi_common, *m4u;
struct device *larbdev;
int ret = 0;
pr_info("%s:val=%llx, case%d larb %d, port %d\n",
__func__, val, testcase, larbid, portid);
if (larbid >= MTK_LARB_NR_MAX)
return -EINVAL;
if (!dbgmng->res_init) {
ret = mtk_smi_debug_res_init(dbgmng);
if (ret) {
pr_info("smi debug res init fail\n");
return ret;
}
}
larb = &dbgmng->larb[larbid];
larbdev = larb->dev;
smi_common = larb->common;
m4u = larb->m4u;
if (!smi_common || !larbdev) {
pr_info("smi dev is null\n");
return -EINVAL;
}
switch (testcase) {
case 0: {/* hang dump. */
ret = smi_larb_power_get(larbdev, larbid,
dbgmng->larb_power_disable);
if (ret < 0)
dev_info(larbdev, "power/clk enable fail\n");
smi_common_dump(smi_common->dev, smi_common->base);
smi_m4u_dump(m4u->dev, m4u->base);
smi_larb_dump(larbdev, larb->base);
smi_larb_power_put(larbdev, larbid, dbgmng->larb_power_disable);
}
break;
case 1: {/* larb monitor start.*/
ret = smi_larb_power_get(larbdev, larbid,
dbgmng->larb_power_disable);
if (ret < 0)
dev_info(larbdev, "power/clk enable fail\n");
dev_info(larbdev, "begin to monitor larb %d port %d\n",
larbid, portid);
start = sched_clock();
/* larb default dump both(read and write). */
smi_larb_monitor_start(larbdev, larb->base,
portid, dbgmng->mon_larb_mode);
/* common default dump read. */
smi_common_monitor_start(smi_common->dev, smi_common->base,
!!dbgmng->mon_common_read);
/* Suppose this is 4k video case.
* 60fps, then 1s/60 = 16.7ms per frame.
* Only monitor 10 frame here. that is 10*16.7ms = 167ms
*/
do {
end = sched_clock(); /* unit is ns */
usleep_range(1000, 3000);
} while (end - start < dbgmng->mon_timeout);
smi_larb_monitor_stop(larbdev, larb->base);
smi_common_monitor_stop(smi_common->dev, smi_common->base);
smi_larb_power_put(larbdev, larbid, dbgmng->larb_power_disable);
dev_info(larbdev, "monitor larb %d, port %d done\n",
larbid, portid);
}
break;
case 3: {/* monitor type */
int larbmod = portid & 0x3; /*larb: 1: read, 2 write. 0: both */
bool commonread = !larbid; /*common: 0: read, 1: write */
if (larbmod != 1 && larbmod != 2)
larbmod = 0;
dbgmng->mon_larb_mode = larbmod;
dbgmng->mon_common_read = commonread;
dev_info(larbdev, "larbmod %d. common read %d\n",
larbmod, commonread);
}
break;
case 4: {
unsigned int time_ms = val & 0xfff;
dbgmng->mon_timeout = time_ms * 1000000ULL;
pr_info("monitor timeout is %d ms\n", time_ms);
}
break;
case 5:
dbgmng->larb_power_disable = !dbgmng->larb_power_disable;
pr_info("larb_power_disable %d.\n", dbgmng->larb_power_disable);
break;
default:
pr_err("smi_dbg_set error,val=%llu\n", val);
}
return ret;
}
static int smi_debug_get(void *data, u64 *val)
{
pr_info("help smi larb debug.\n");
*val = 0;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(smi_debug_fops, smi_debug_get,
smi_debug_set, "%llu\n");
static int __init mtk_smi_debug_init(void)
{
struct mtk_smi_dbg *dbg_mng;
struct dentry *dbg;
dbg_mng = kzalloc(sizeof(*dbg_mng), GFP_KERNEL);
if (!dbg_mng)
return -ENOMEM;
dbg = debugfs_create_file("smi", 0644, NULL, dbg_mng, &smi_debug_fops);
if (IS_ERR(dbg)) {
kfree(dbg_mng);
return PTR_ERR(dbg);
}
return 0;
}
late_initcall(mtk_smi_debug_init);
MODULE_DESCRIPTION("MEDIATEK SMI debug driver");
MODULE_AUTHOR("Yong Wu <yong.wu@mediatek.com>");
MODULE_LICENSE("GPL v2");