blob: e7a826811e8e8765f4153aadfbed8ad9d52edf51 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2023 MediaTek Inc. All Rights Reserved.
*
* Author: Alvin Kuo <alvin.kuog@mediatek.com>
* Ren-Ting Wang <ren-ting.wang@mediatek.com>
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/of.h>
#include "tops/internal.h"
#include "tops/mcu.h"
#include "tops/trm-debugfs.h"
#include "tops/trm-fs.h"
#include "tops/trm-mcu.h"
#include "tops/trm.h"
#define TOPS_OCD_RETRY_TIMES (3)
#define TOPS_OCD_DCRSET (0x200C)
#define ENABLE_OCD (1 << 0)
#define DEBUG_INT (1 << 1)
#define TOPS_OCD_DSR (0x2010)
#define EXEC_DONE (1 << 0)
#define EXEC_EXCE (1 << 1)
#define EXEC_BUSY (1 << 2)
#define STOPPED (1 << 4)
#define DEBUG_PEND_HOST (1 << 17)
#define TOPS_OCD_DDR (0x2014)
#define TOPS_OCD_DIR0EXEC (0x201C)
struct tops_ocd_dev {
void __iomem *base;
u32 base_offset;
struct clk *debugsys_clk;
};
static struct tops_ocd_dev ocd;
struct core_dump_fram cd_frams[CORE_TOPS_NUM];
static inline void ocd_write(struct tops_ocd_dev *ocd, u32 reg, u32 val)
{
writel(val, ocd->base + ocd->base_offset + reg);
}
static inline u32 ocd_read(struct tops_ocd_dev *ocd, u32 reg)
{
return readl(ocd->base + ocd->base_offset + reg);
}
static inline void ocd_set(struct tops_ocd_dev *ocd, u32 reg, u32 mask)
{
setbits(ocd->base + ocd->base_offset + reg, mask);
}
static inline void ocd_clr(struct tops_ocd_dev *ocd, u32 reg, u32 mask)
{
clrbits(ocd->base + ocd->base_offset + reg, mask);
}
static int core_exec_instr(u32 instr)
{
u32 rty = 0;
int ret;
ocd_set(&ocd, TOPS_OCD_DSR, EXEC_DONE);
ocd_set(&ocd, TOPS_OCD_DSR, EXEC_EXCE);
ocd_write(&ocd, TOPS_OCD_DIR0EXEC, instr);
while ((ocd_read(&ocd, TOPS_OCD_DSR) & EXEC_BUSY)) {
if (rty++ < TOPS_OCD_RETRY_TIMES) {
usleep_range(1000, 1500);
} else {
TRM_ERR("run instruction(0x%x) timeout\n", instr);
ret = -1;
goto out;
}
}
ret = ocd_read(&ocd, TOPS_OCD_DSR) & EXEC_EXCE ? -1 : 0;
if (ret)
TRM_ERR("run instruction(0x%x) fail\n", instr);
out:
return ret;
}
static int core_dump(struct core_dump_fram *cd_fram)
{
cd_fram->magic = CORE_DUMP_FRAM_MAGIC;
cd_fram->num_areg = XCHAL_NUM_AREG;
/*
* save
* PC, PS, WINDOWSTART, WINDOWBASE,
* EPC1, EXCCAUSE, EXCVADDR, EXCSAVE1
*/
core_exec_instr(0x13f500);
core_exec_instr(0x03b500);
core_exec_instr(0x136800);
cd_fram->pc = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x03e600);
core_exec_instr(0x136800);
cd_fram->ps = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x034900);
core_exec_instr(0x136800);
cd_fram->windowstart = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x034800);
core_exec_instr(0x136800);
cd_fram->windowbase = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x03b100);
core_exec_instr(0x136800);
cd_fram->epc1 = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x03e800);
core_exec_instr(0x136800);
cd_fram->exccause = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x03ee00);
core_exec_instr(0x136800);
cd_fram->excvaddr = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x03d100);
core_exec_instr(0x136800);
cd_fram->excsave1 = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x03f500);
/*
* save
* a0, a1, a2, a3, a4, a5, a6, a7
*/
core_exec_instr(0x136800);
cd_fram->areg[0] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x136810);
cd_fram->areg[1] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x136820);
cd_fram->areg[2] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x136830);
cd_fram->areg[3] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x136840);
cd_fram->areg[4] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x136850);
cd_fram->areg[5] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x136860);
cd_fram->areg[6] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x136870);
cd_fram->areg[7] = ocd_read(&ocd, TOPS_OCD_DDR);
/*
* save
* a8, a9, a10, a11, a12, a13, a14, a15
*/
core_exec_instr(0x136880);
cd_fram->areg[8] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x136890);
cd_fram->areg[9] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368a0);
cd_fram->areg[10] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368b0);
cd_fram->areg[11] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368c0);
cd_fram->areg[12] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368d0);
cd_fram->areg[13] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368e0);
cd_fram->areg[14] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368f0);
cd_fram->areg[15] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x408020);
/*
* save
* a16, a17, a18, a19, a20, a21, a22, a23
*/
core_exec_instr(0x136880);
cd_fram->areg[16] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x136890);
cd_fram->areg[17] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368a0);
cd_fram->areg[18] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368b0);
cd_fram->areg[19] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368c0);
cd_fram->areg[20] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368d0);
cd_fram->areg[21] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368e0);
cd_fram->areg[22] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368f0);
cd_fram->areg[23] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x408020);
/*
* save
* a24, a25, a26, a27, a28, a29, a30, a31
*/
core_exec_instr(0x136880);
cd_fram->areg[24] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x136890);
cd_fram->areg[25] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368a0);
cd_fram->areg[26] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368b0);
cd_fram->areg[27] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368c0);
cd_fram->areg[28] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368d0);
cd_fram->areg[29] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368e0);
cd_fram->areg[30] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x1368f0);
cd_fram->areg[31] = ocd_read(&ocd, TOPS_OCD_DDR);
core_exec_instr(0x408040);
core_exec_instr(0xf1e000);
return 0;
}
static int __mtk_trm_mcu_core_dump(enum core_id core)
{
u32 rty = 0;
int ret;
ocd.base_offset = (core == CORE_MGMT) ? (0x0) : (0x5000 + (core * 0x4000));
/* enable OCD */
ocd_set(&ocd, TOPS_OCD_DCRSET, ENABLE_OCD);
/* assert debug interrupt to core */
ocd_set(&ocd, TOPS_OCD_DCRSET, DEBUG_INT);
/* wait core into stopped state */
while (!(ocd_read(&ocd, TOPS_OCD_DSR) & STOPPED)) {
if (rty++ < TOPS_OCD_RETRY_TIMES) {
usleep_range(10000, 15000);
} else {
TRM_ERR("wait core(%d) into stopped state timeout\n", core);
ret = -1;
goto out;
}
}
/* deassert debug interrupt to core */
ocd_set(&ocd, TOPS_OCD_DSR, DEBUG_PEND_HOST);
/* dump core's registers and let core into running state */
ret = core_dump(&cd_frams[core]);
out:
return ret;
}
int mtk_trm_mcu_core_dump(void)
{
enum core_id core;
int ret;
ret = clk_prepare_enable(ocd.debugsys_clk);
if (ret) {
TRM_ERR("debugsys clk enable failed: %d\n", ret);
goto out;
}
memset(cd_frams, 0, sizeof(cd_frams));
for (core = CORE_OFFLOAD_0; core <= CORE_MGMT; core++) {
ret = __mtk_trm_mcu_core_dump(core);
if (ret)
break;
}
clk_disable_unprepare(ocd.debugsys_clk);
out:
return ret;
}
static int mtk_tops_ocd_probe(struct platform_device *pdev)
{
struct resource *res = NULL;
int ret;
trm_dev = &pdev->dev;
ocd.debugsys_clk = devm_clk_get(trm_dev, "debugsys");
if (IS_ERR(ocd.debugsys_clk)) {
TRM_ERR("get debugsys clk failed: %ld\n", PTR_ERR(ocd.debugsys_clk));
return PTR_ERR(ocd.debugsys_clk);
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tops-ocd-base");
if (!res)
return -ENXIO;
ocd.base = devm_ioremap(trm_dev, res->start, resource_size(res));
if (!ocd.base)
return -ENOMEM;
ret = mtk_trm_debugfs_init();
if (ret)
return ret;
ret = mtk_trm_fs_init();
if (ret)
return ret;
TRM_INFO("tops-ocd init done\n");
return 0;
}
static int mtk_tops_ocd_remove(struct platform_device *pdev)
{
mtk_trm_fs_deinit();
mtk_trm_debugfs_deinit();
return 0;
}
static struct of_device_id mtk_tops_ocd_match[] = {
{ .compatible = "mediatek,tops-ocd", },
{ },
};
static struct platform_driver mtk_tops_ocd_driver = {
.probe = mtk_tops_ocd_probe,
.remove = mtk_tops_ocd_remove,
.driver = {
.name = "mediatek,tops-ocd",
.owner = THIS_MODULE,
.of_match_table = mtk_tops_ocd_match,
},
};
int __init mtk_tops_trm_mcu_init(void)
{
return platform_driver_register(&mtk_tops_ocd_driver);
}
void __exit mtk_tops_trm_mcu_exit(void)
{
platform_driver_unregister(&mtk_tops_ocd_driver);
}