[Detail] support mt8168 ion/iommu/smi Test: build ok MTK-Commit-Id: ded5548261cdaf9889ffd89911745f60573e1915 Change-Id: I4b799afbc5ba6631b6a17bb3ee6f462f52c5e1d5 CR-Id: ALPS04877382 Feature: [Module]ION/M4U Signed-off-by: Scott Wang <scott.wang@mediatek.com>
520 lines
14 KiB
C
520 lines
14 KiB
C
/*
|
|
* 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");
|