/* * Copyright (c) 2018 MediaTek Inc. * Author: Yong Wu * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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 "); MODULE_LICENSE("GPL v2");