// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2015-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "aon-ivc-dbg-messages.h" #define AON_REQUEST_MASK 0xF #define AON_REQUESTS_TOTAL (AON_REQUEST_TYPE_MAX + 1) #define TX_BLOCK_PERIOD 20 #define AON_ROOT 0 #define AON_MODS 1 #define AON_ADCC 2 #define IVC_DBG_CH_FRAME_SIZE 64 #define MODS_DEFAULT_VAL 0xFFFF #define MODS_DEFAULT_LOOPS 10 #define MODS_DEFAULT_CHANS 0x1 #define MODS_BASIC_TEST 0x0 #define MODS_DMA_MEM2MEM 0x1 #define MODS_DMA_IO2MEM 0x2 #define MODS_ADCC_SINGLE 0x3 #define MODS_ADCC_CONT 0x4 #define ADCC_MODE_SINGLE_SHOT 1 #define ADCC_MODE_CONT 0 #define ADCC_CLK_SRC_OSC 0 #define ADCC_CLK_SRC_PLLP 1 #define AONFW_BOOT 1 struct tegra_aondbg { struct device *dev; struct tegra_aon *aon; struct mbox_client cl; struct mbox_chan *mbox; struct dentry *aon_root; bool supports_adcc; }; static struct tegra_aondbg aondbg_dev; struct aon_dbgfs_node { char *name; u32 id; u8 pdr_id; mode_t mode; struct completion *wait_on; const struct file_operations *fops; char data[IVC_DBG_CH_FRAME_SIZE]; }; struct dbgfs_dir { const char *name; struct dentry *dir; struct dbgfs_dir *parent; }; static struct dbgfs_dir aon_dbgfs_dirs[] = { {.name = "aon", .parent = NULL}, {.name = "aon_mods", .parent = &aon_dbgfs_dirs[AON_ROOT]}, {.name = "adcc", .parent = &aon_dbgfs_dirs[AON_MODS]} }; static u32 mods_result = MODS_DEFAULT_VAL; static u32 mods_dma_chans = MODS_DEFAULT_CHANS; static u32 mods_adcc_chans = MODS_DEFAULT_CHANS; static u32 mods_case = MODS_BASIC_TEST; static u32 mods_loops = MODS_DEFAULT_LOOPS; static u32 mods_adcc_smpl_dur = 16; static u32 mods_adcc_avg_window = 1024; static u32 mods_adcc_clk_src = ADCC_CLK_SRC_OSC; static u64 mods_adcc_chans_data; static u64 mods_adcc_dac_lb_data; static unsigned int completion_timeout = 50; static DEFINE_MUTEX(aon_mutex); static DEFINE_SPINLOCK(mods); static DEFINE_SPINLOCK(completion); static DEFINE_SPINLOCK(loops); static DEFINE_SPINLOCK(mods_dma); static DEFINE_SPINLOCK(mods_adcc); static struct aon_dbgfs_node aon_nodes[]; static void set_mods_result(u32 result) { spin_lock(&mods); mods_result = result; spin_unlock(&mods); } static unsigned int get_mods_result(void) { u32 val; spin_lock(&mods); val = mods_result; spin_unlock(&mods); return val; } static void set_completion_timeout(unsigned int timeout) { spin_lock(&completion); completion_timeout = timeout; spin_unlock(&completion); } static unsigned int get_completion_timeout(void) { unsigned int val; spin_lock(&completion); val = completion_timeout; spin_unlock(&completion); return val; } static void set_mods_loops(u32 count) { spin_lock(&loops); mods_loops = count; spin_unlock(&loops); } static unsigned int get_mods_loops(void) { unsigned int val; spin_lock(&loops); val = mods_loops; spin_unlock(&loops); return val; } static void set_mods_dma_chans(u32 dma_chans) { spin_lock(&mods_dma); mods_dma_chans = dma_chans; spin_unlock(&mods_dma); } static unsigned int get_mods_dma_chans(void) { unsigned int val; spin_lock(&mods_dma); val = mods_dma_chans; spin_unlock(&mods_dma); return val; } static void set_mods_adcc_chans(u64 chans) { spin_lock(&mods_adcc); mods_adcc_chans = (u32) (chans & 0xFFFFFFFFUL); spin_unlock(&mods_adcc); } static unsigned int get_mods_adcc_chans(void) { unsigned int val; spin_lock(&mods_adcc); val = mods_adcc_chans; spin_unlock(&mods_adcc); return val; } static void set_mods_adcc_smpl_dur(u64 dur) { spin_lock(&mods_adcc); mods_adcc_smpl_dur = (u32)(dur & 0xFFFFFFFFUL); spin_unlock(&mods_adcc); } static unsigned int get_mods_adcc_smpl_dur(void) { unsigned int val; spin_lock(&mods_adcc); val = mods_adcc_smpl_dur; spin_unlock(&mods_adcc); return val; } static void set_mods_adcc_avg_window(u64 avg) { spin_lock(&mods_adcc); mods_adcc_avg_window = (u32) (avg & 0xFFFFFFFFUL); spin_unlock(&mods_adcc); } static unsigned int get_mods_adcc_avg_window(void) { unsigned int val; spin_lock(&mods_adcc); val = mods_adcc_avg_window; spin_unlock(&mods_adcc); return val; } static void set_mods_adcc_clk_src(u64 src) { spin_lock(&mods_adcc); mods_adcc_clk_src = (u32)(src & 0xFFFFFFFFUL); spin_unlock(&mods_adcc); } static u64 get_mods_adcc_chans_data(void) { u32 val; spin_lock(&mods_adcc); val = (u32)(mods_adcc_chans_data & 0xFFFFFFFFUL); spin_unlock(&mods_adcc); return val; } static void set_mods_adcc_chans_data(u64 adcc_data) { spin_lock(&mods_adcc); mods_adcc_chans_data = adcc_data; spin_unlock(&mods_adcc); } static u64 get_mods_adcc_dac_lb_data(void) { u64 val; spin_lock(&mods_adcc); val = mods_adcc_dac_lb_data; spin_unlock(&mods_adcc); return val; } static void set_mods_adcc_dac_lb_data(u64 lb_data) { spin_lock(&mods_adcc); mods_adcc_dac_lb_data = lb_data; spin_unlock(&mods_adcc); } static unsigned int get_mods_adcc_clk_src(void) { unsigned int val; spin_lock(&mods_adcc); val = mods_adcc_clk_src; spin_unlock(&mods_adcc); return val; } static void aon_create_mods_req(struct aon_dbg_request *req, u32 data) { switch (data) { case MODS_BASIC_TEST: break; case MODS_DMA_MEM2MEM: case MODS_DMA_IO2MEM: req->data.mods_req.dma_chans = get_mods_dma_chans(); break; case MODS_ADCC_SINGLE: req->data.mods_req.adcc.chans = get_mods_adcc_chans(); req->data.mods_req.adcc.mode = ADCC_MODE_SINGLE_SHOT; req->data.mods_req.adcc.sampling_dur = get_mods_adcc_smpl_dur(); req->data.mods_req.adcc.avg_window = get_mods_adcc_avg_window(); req->data.mods_req.adcc.clk_src = get_mods_adcc_clk_src(); req->data.mods_req.adcc.lb_data = get_mods_adcc_dac_lb_data(); break; case MODS_ADCC_CONT: req->data.mods_req.adcc.chans = get_mods_adcc_chans(); req->data.mods_req.adcc.mode = ADCC_MODE_CONT; req->data.mods_req.adcc.sampling_dur = get_mods_adcc_smpl_dur(); req->data.mods_req.adcc.avg_window = get_mods_adcc_avg_window(); req->data.mods_req.adcc.clk_src = get_mods_adcc_clk_src(); req->data.mods_req.adcc.lb_data = get_mods_adcc_dac_lb_data(); break; } } static struct aon_dbg_response *aon_create_ivc_dbg_req(u32 request, u32 flag, u32 data) { struct tegra_aondbg *aondbg = &aondbg_dev; struct aon_dbg_request req; struct aon_dbg_response *resp; struct tegra_aon_mbox_msg msg; int ret = 0; unsigned int timeout = 0; if (aondbg->dev == NULL) return (void *)-EPROBE_DEFER; req.req_type = request & AON_REQUEST_MASK; switch (req.req_type) { case AON_MODS_CASE: req.data.mods_req.loops = get_mods_loops(); req.data.mods_req.mods_case = data; aon_create_mods_req(&req, data); break; case AON_MODS_CRC: case AON_PING: break; case AON_QUERY_TAG: break; default: dev_err(aondbg->dev, "Invalid aon dbg request\n"); return ERR_PTR(-EINVAL); } msg.length = sizeof(struct aon_dbg_request); msg.data = (void *)&req; ret = mbox_send_message(aondbg->mbox, (void *)&msg); if (ret < 0) { dev_err(aondbg->dev, "mbox_send_message failed\n"); return ERR_PTR(ret); } timeout = get_completion_timeout(); ret = wait_for_completion_timeout(aon_nodes[req.req_type].wait_on, msecs_to_jiffies(timeout)); if (!ret) { dev_err(aondbg->dev, "No response\n"); return ERR_PTR(-ETIMEDOUT); } resp = (void *)aon_nodes[req.req_type].data; if (resp->resp_type > AON_REQUEST_TYPE_MAX) { dev_err(aondbg->dev, "Invalid aon dbg response\n"); return ERR_PTR(-EIO); } if (resp->status != AON_DBG_STATUS_OK) { dev_err(aondbg->dev, "Request failed\n"); return ERR_PTR(-EIO); } return resp; } static int aon_boot_show(void *data, u64 *val) { struct tegra_aon *aon = aondbg_dev.aon; if (aon->ast_config_complete && aon->reset_complete && aon->load_complete) *val = 1; return 0; } static int aon_boot_store(void *data, u64 val) { return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_boot_fops, aon_boot_show, aon_boot_store, "%lld\n"); static ssize_t aon_get_fwtag(u32 context, char **data) { struct aon_dbg_response *resp; int ret = 0; *data = NULL; resp = aon_create_ivc_dbg_req(context, READ, 0); if (IS_ERR(resp)) ret = PTR_ERR(resp); else *data = resp->data.tag_resp.tag; return ret; } static int aon_tag_show(struct seq_file *file, void *param) { char *data; int ret; mutex_lock(&aon_mutex); ret = aon_get_fwtag(*(u32 *)file->private, &data); if (ret >= 0) seq_printf(file, "%s\n", data); mutex_unlock(&aon_mutex); return ret; } static int aon_tag_open(struct inode *inode, struct file *file) { return single_open(file, aon_tag_show, inode->i_private); } static const struct file_operations aon_tag_fops = { .open = aon_tag_open, .read = seq_read, .llseek = seq_lseek, .release = single_release }; static int __aon_do_ping(void) { struct aon_dbg_response *resp; int challenge = 8; int ret = 0; resp = aon_create_ivc_dbg_req(AON_PING, READ, challenge); if (IS_ERR(resp)) { ret = PTR_ERR(resp); goto exit; } ret = resp->data.ping_resp.reply; if (ret != challenge * 2) ret = -EINVAL; exit: return ret; } static int aon_ping_show(void *data, u64 *val) { ktime_t tm; int ret = 0; mutex_lock(&aon_mutex); tm = ktime_get(); ret = __aon_do_ping(); tm = ktime_sub(ktime_get(), tm); *val = (ret < 0) ? tm : ret; mutex_unlock(&aon_mutex); return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_ping_fops, aon_ping_show, NULL, "%lld\n"); static int aon_mods_loops_show(void *data, u64 *val) { *val = get_mods_loops(); return 0; } static int aon_mods_loops_store(void *data, u64 val) { set_mods_loops(val); return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_mods_loops_fops, aon_mods_loops_show, aon_mods_loops_store, "%lld\n"); static int aon_mods_case_show(void *data, u64 *val) { *val = mods_case; return 0; } static int aon_mods_case_store(void *data, u64 val) { struct aon_dbg_response *resp; int ret = 0; int i; u64 adcc_data = 0U; u64 ch_data = 0U; if (val > MODS_ADCC_CONT) { ret = -1; dev_err(aondbg_dev.dev, "Invalid mods case\n"); goto out; } if (val > MODS_DMA_IO2MEM && val <= MODS_ADCC_CONT) { if (!aondbg_dev.supports_adcc) { ret = -1; dev_err(aondbg_dev.dev, "no adcc on this platform\n"); goto out; } } mutex_lock(&aon_mutex); set_mods_result(MODS_DEFAULT_VAL); resp = aon_create_ivc_dbg_req(*(u32 *)data, WRITE, val); if (IS_ERR(resp)) { ret = PTR_ERR(resp); } else { set_mods_result(resp->status); if (val == MODS_ADCC_SINGLE || val == MODS_ADCC_CONT) { adcc_data = 0U; for (i = 0; i < ADCC_NCHANS; i++) { ch_data = resp->data.adcc_resp.ch_data[i]; adcc_data |= (ch_data & 0x3FFU) << (i * 10); } set_mods_adcc_chans_data(adcc_data); } } mutex_unlock(&aon_mutex); out: return ret; } DEFINE_SIMPLE_ATTRIBUTE(aon_mods_case_fops, aon_mods_case_show, aon_mods_case_store, "%lld\n"); static int aon_mods_result_show(void *data, u64 *val) { *val = get_mods_result(); return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_mods_result_fops, aon_mods_result_show, NULL, "%lld\n"); static int aon_mods_crc_show(void *data, u64 *val) { struct aon_dbg_response *resp; int ret = 0; mutex_lock(&aon_mutex); resp = aon_create_ivc_dbg_req(*(u32 *)data, READ, 0); if (IS_ERR(resp)) ret = PTR_ERR(resp); else *val = resp->data.crc_resp.crc; mutex_unlock(&aon_mutex); return ret; } DEFINE_SIMPLE_ATTRIBUTE(aon_mods_crc_fops, aon_mods_crc_show, NULL, "%llx\n"); static int aon_mods_dma_show(void *data, u64 *val) { *val = get_mods_dma_chans(); return 0; } static int aon_mods_dma_store(void *data, u64 val) { set_mods_dma_chans(val); return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_mods_dma_fops, aon_mods_dma_show, aon_mods_dma_store, "%lld\n"); static int aon_mods_adcc_chans_show(void *data, u64 *val) { *val = get_mods_adcc_chans(); return 0; } static int aon_mods_adcc_chans_store(void *data, u64 val) { set_mods_adcc_chans(val); return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_mods_adcc_chans_fops, aon_mods_adcc_chans_show, aon_mods_adcc_chans_store, "%lld\n"); static int aon_mods_adcc_smpl_show(void *data, u64 *val) { *val = get_mods_adcc_smpl_dur(); return 0; } static int aon_mods_adcc_smpl_store(void *data, u64 val) { set_mods_adcc_smpl_dur(val); return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_mods_adcc_smpl_fops, aon_mods_adcc_smpl_show, aon_mods_adcc_smpl_store, "%lld\n"); static int aon_mods_adcc_avg_show(void *data, u64 *val) { *val = get_mods_adcc_avg_window(); return 0; } static int aon_mods_adcc_avg_store(void *data, u64 val) { set_mods_adcc_avg_window(val); return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_mods_adcc_avg_fops, aon_mods_adcc_avg_show, aon_mods_adcc_avg_store, "%lld\n"); static int aon_mods_adcc_clk_show(void *data, u64 *val) { *val = get_mods_adcc_clk_src(); return 0; } static int aon_mods_adcc_clk_store(void *data, u64 val) { set_mods_adcc_clk_src(val); return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_mods_adcc_clk_fops, aon_mods_adcc_clk_show, aon_mods_adcc_clk_store, "%lld\n"); static int aon_mods_adcc_data_show(void *data, u64 *val) { *val = get_mods_adcc_chans_data(); return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_mods_adcc_data_fops, aon_mods_adcc_data_show, NULL, "%lld\n"); static int aon_mods_adcc_dac_show(void *data, u64 *val) { *val = get_mods_adcc_dac_lb_data(); return 0; } static int aon_mods_adcc_dac_store(void *data, u64 val) { set_mods_adcc_dac_lb_data(val); return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_mods_adcc_dac_fops, aon_mods_adcc_dac_show, aon_mods_adcc_dac_store, "%lld\n"); static int aon_timeout_show(void *data, u64 *val) { *val = get_completion_timeout(); return 0; } static int aon_timeout_store(void *data, u64 val) { set_completion_timeout(val); return 0; } DEFINE_SIMPLE_ATTRIBUTE(aon_timeout_fops, aon_timeout_show, aon_timeout_store, "%lld\n"); static struct aon_dbgfs_node aon_nodes[] = { {.name = "boot", .id = AON_BOOT, .pdr_id = AON_ROOT, .mode = 0644, .fops = &aon_boot_fops, }, {.name = "loops", .pdr_id = AON_MODS, .mode = 0644, .fops = &aon_mods_loops_fops, }, {.name = "result", .id = AON_MODS_RESULT, .pdr_id = AON_MODS, .mode = 0444, .fops = &aon_mods_result_fops,}, {.name = "crc", .id = AON_MODS_CRC, .pdr_id = AON_MODS, .mode = 0444, .fops = &aon_mods_crc_fops,}, {.name = "case", .id = AON_MODS_CASE, .pdr_id = AON_MODS, .mode = 0644, .fops = &aon_mods_case_fops,}, {.name = "dma_channels", .pdr_id = AON_MODS, .mode = 0644, .fops = &aon_mods_dma_fops,}, {.name = "adcc_chans", .pdr_id = AON_ADCC, .mode = 0644, .fops = &aon_mods_adcc_chans_fops,}, {.name = "sampling_dur", .pdr_id = AON_ADCC, .mode = 0644, .fops = &aon_mods_adcc_smpl_fops,}, {.name = "avg_window", .pdr_id = AON_ADCC, .mode = 0644, .fops = &aon_mods_adcc_avg_fops,}, {.name = "clk_src", .pdr_id = AON_ADCC, .mode = 0644, .fops = &aon_mods_adcc_clk_fops,}, {.name = "adcc_data", .pdr_id = AON_ADCC, .mode = 0644, .fops = &aon_mods_adcc_data_fops,}, {.name = "dac", .pdr_id = AON_ADCC, .mode = 0644, .fops = &aon_mods_adcc_dac_fops,}, {.name = "ping", .id = AON_PING, .pdr_id = AON_ROOT, .mode = 0644, .fops = &aon_ping_fops,}, {.name = "tag", .id = AON_QUERY_TAG, .pdr_id = AON_ROOT, .mode = 0644, .fops = &aon_tag_fops,}, {.name = "completion_timeout", .pdr_id = AON_ROOT, .mode = 0644, .fops = &aon_timeout_fops,}, }; static void tegra_aondbg_recv_msg(struct mbox_client *cl, void *rx_msg) { struct tegra_aon_mbox_msg *msg; struct aon_dbg_response *resp; msg = (struct tegra_aon_mbox_msg *)rx_msg; resp = (void *)msg->data; if (resp->resp_type > AON_REQUEST_TYPE_MAX) { dev_err(aondbg_dev.dev, "Multiple request types in 1 response\n"); return; } memcpy(aon_nodes[resp->resp_type].data, msg->data, IVC_DBG_CH_FRAME_SIZE); complete(aon_nodes[resp->resp_type].wait_on); } static int aon_dbg_init(struct tegra_aondbg *aon) { struct dentry *d; struct dentry *parent_dir; struct dbgfs_dir *dbgdir; int i; d = debugfs_create_dir(aon_dbgfs_dirs[AON_ROOT].name, NULL); if (IS_ERR_OR_NULL(d)) goto clean; aon_dbgfs_dirs[AON_ROOT].dir = d; aon->aon_root = d; for (i = 1; i < ARRAY_SIZE(aon_dbgfs_dirs); i++) { dbgdir = &aon_dbgfs_dirs[i]; d = debugfs_create_dir(dbgdir->name, dbgdir->parent->dir); if (IS_ERR_OR_NULL(d)) goto clean; dbgdir->dir = d; } for (i = 0; i < ARRAY_SIZE(aon_nodes); i++) { parent_dir = aon_dbgfs_dirs[aon_nodes[i].pdr_id].dir; d = debugfs_create_file(aon_nodes[i].name, aon_nodes[i].mode, parent_dir, &aon_nodes[i].id, aon_nodes[i].fops); if (IS_ERR_OR_NULL(d)) goto clean; } return 0; clean: debugfs_remove_recursive(aon->aon_root); return PTR_ERR(d); } #if defined(CONFIG_DEBUG_FS) int tegra_aon_debugfs_create(struct tegra_aon *aon) { struct tegra_aondbg *aondbg = &aondbg_dev; struct device *dev = aon->dev; struct device_node *np = dev->of_node; int count; int ret = 0; int i; if (!debugfs_initialized()) { ret = -ENODEV; goto exit; } if (np == NULL) { dev_err(dev, "tegra-aondbg: DT data required.\n"); ret = -EINVAL; goto exit; } count = of_count_phandle_with_args(np, "mboxes", "#mbox-cells"); if (count != 1) { dev_err(dev, "incorrect mboxes property in '%pOF'\n", np); ret = -EINVAL; goto exit; } if (of_property_read_bool(np, NV("adcc"))) aondbg->supports_adcc = true; else aondbg->supports_adcc = false; aondbg->dev = aon->dev; aondbg->aon = aon; aondbg->cl.dev = aon->dev; aondbg->cl.tx_block = true; aondbg->cl.tx_tout = TX_BLOCK_PERIOD; aondbg->cl.knows_txdone = false; aondbg->cl.rx_callback = tegra_aondbg_recv_msg; aondbg->mbox = mbox_request_channel(&aondbg->cl, 0); if (IS_ERR(aondbg->mbox)) { ret = PTR_ERR(aondbg->mbox); if (ret != -EPROBE_DEFER) { dev_warn(dev, "can't get mailbox channel (%d)\n", ret); } goto exit; } dev_dbg(dev, "aondbg->mbox = %p\n", aondbg->mbox); for (i = 0; i < ARRAY_SIZE(aon_nodes); i++) { aon_nodes[i].wait_on = devm_kzalloc(aon->dev, sizeof(struct completion), GFP_KERNEL); if (!aon_nodes[i].wait_on) { dev_err(dev, "out of memory.\n"); ret = -ENOMEM; goto exit; } init_completion(aon_nodes[i].wait_on); } ret = aon_dbg_init(aondbg); if (ret) { dev_err(dev, "failed to create debugfs nodes.\n"); goto exit; } exit: return ret; } void tegra_aon_debugfs_remove(struct tegra_aon *aon) { struct tegra_aondbg *aondbg = &aondbg_dev; mbox_free_channel(aondbg->mbox); debugfs_remove_recursive(aondbg->aon_root); } #endif