// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "slbc_ops.h" #include "npu_scp_ipi.h" #include "aov_rpmsg.h" #include "aov_recovery.h" #define MAX_DEBUG_CMD_SIZE (1024) #define APUSYS_AOV_KVERSION "v1.1.0.1-ko" #define STATE_TIMEOUT_MS (20) struct apusys_aov_ctx { atomic_t aov_enabled; struct device *dev; struct completion comp; struct npu_scp_ipi_param npu_scp_msg; struct mutex scp_ipi_lock; atomic_t response_arrived; struct dentry *dbg_dir; struct dentry *dbg_node; }; static struct apusys_aov_ctx *aov_ctx; static struct dentry *apusys_dbg_root; static int apusys_aov_test_show(struct seq_file *s, void *unused) { // struct apusys_aov_ctx *ctx = (struct apusys_aov_ctx *)s->private; struct npu_scp_ipi_param send_msg = { 0, 0, 0, 0 }; struct npu_scp_ipi_param recv_msg = { 0, 0, 0, 0 }; int ret, major, minor, subminor, dev; send_msg.cmd = NPU_SCP_SYSTEM; send_msg.act = NPU_SCP_SYSTEM_GET_VERSION; ret = npu_scp_ipi_send(&send_msg, &recv_msg, SCP_IPI_TIMEOUT_MS); if (ret) { seq_printf(s, "%s failed to send scp ipi, ret %d\n", __func__, ret); return 0; } major = (recv_msg.ret >> 24) & 0xff; minor = (recv_msg.ret >> 16) & 0xff; subminor = (recv_msg.ret >> 8) & 0xff; dev = recv_msg.ret & 0xff; seq_printf(s, "AOV APU KO Version: %s\n", APUSYS_AOV_KVERSION); seq_printf(s, "AOV APU Version: %d.%d.%d.%d\n", major, minor, subminor, dev); return 0; } static int apusys_aov_test_open(struct inode *inode, struct file *flip) { return single_open(flip, apusys_aov_test_show, inode->i_private); } static inline bool args_compare(char *lstr, char *sstr, char *var) { if (!var) return false; if (lstr && sstr) { size_t llens = min(strlen(lstr), strlen(var)); size_t slens = min(strlen(sstr), strlen(var)); return ((strncmp((lstr), (var), llens)) == 0) || ((strncmp((sstr), (var), slens)) == 0); } else if (lstr) { size_t llens = min(strlen(lstr), strlen(var)); return ((strncmp((lstr), (var), llens)) == 0); } else return false; } static inline char *args_get_next_str(char **str) { static char *empty_str = ""; char *token = NULL; if (str) token = strsep(str, " "); if (token == NULL) return empty_str; return token; } static int apusys_aov_start_testcase(struct seq_file *s, struct apusys_aov_ctx *ctx, unsigned int testcase) { struct npu_scp_ipi_param send_msg = { 0, 0, 0, 0 }; struct npu_scp_ipi_param recv_msg = { 0, 0, 0, 0 }; int ret; dev_info(ctx->dev, "%s Start testcase %u\n", __func__, testcase); send_msg.cmd = NPU_SCP_TEST; send_msg.act = NPU_SCP_TEST_START; send_msg.arg = testcase; ret = npu_scp_ipi_send(&send_msg, &recv_msg, TESTCASE_TIMEOUT_MS); if (ret) { dev_info(ctx->dev, "%s failed to send scp ipi, ret %d\n", __func__, ret); return ret; } if (recv_msg.ret == NPU_SCP_RET_TEST_START_ERR) { dev_info(ctx->dev, "%s failed to start testcase %u, ret %d\n", __func__, testcase, recv_msg.ret); return recv_msg.ret; } dev_info(ctx->dev, "%s testcase %u is running\n", __func__, testcase); return recv_msg.ret; } static int apusys_aov_stop_testcase(struct seq_file *s, struct apusys_aov_ctx *ctx, unsigned int testcase) { struct npu_scp_ipi_param send_msg = { 0, 0, 0, 0 }; struct npu_scp_ipi_param recv_msg = { 0, 0, 0, 0 }; int ret; dev_info(ctx->dev, "%s Stop testcase %u\n", __func__, testcase); send_msg.cmd = NPU_SCP_TEST; send_msg.act = NPU_SCP_TEST_STOP; send_msg.arg = testcase; ret = npu_scp_ipi_send(&send_msg, &recv_msg, TESTCASE_TIMEOUT_MS); if (ret) { dev_info(ctx->dev, "%s failed to send scp ipi, ret %d\n", __func__, ret); return ret; } if (recv_msg.ret == NPU_SCP_RET_TEST_STOP_ERR) { dev_info(ctx->dev, "%s failed to stop testcase %u\n", __func__, testcase); return recv_msg.ret; } if (recv_msg.ret) dev_info(ctx->dev, "%s ERROR! testcase %u return error %d\n", __func__, testcase, recv_msg.ret, recv_msg.ret); else dev_info(ctx->dev, "%s testcase %u done successfully, run %d times, ret %d\n", __func__, testcase, recv_msg.arg, recv_msg.ret); return recv_msg.ret; } static int npu_system_cmd_to_scp(struct apusys_aov_ctx *ctx, struct npu_scp_ipi_param *param) { struct npu_scp_ipi_param send_msg = { 0, 0, 0, 0 }; int ret = 0; switch (param->act) { case NPU_SCP_SYSTEM_FORCE_TO_SUSPEND: send_msg.cmd = NPU_SCP_SYSTEM; send_msg.act = NPU_SCP_SYSTEM_FORCE_TO_SUSPEND; ret = npu_scp_ipi_send(&send_msg, NULL, SCP_IPI_TIMEOUT_MS); if (ret) dev_info(ctx->dev, "%s failed to send scp ipi, ret %d\n", __func__, ret); break; case NPU_SCP_SYSTEM_FORCE_TO_RESUME: send_msg.cmd = NPU_SCP_SYSTEM; send_msg.act = NPU_SCP_SYSTEM_FORCE_TO_RESUME; ret = npu_scp_ipi_send(&send_msg, NULL, SCP_IPI_TIMEOUT_MS); if (ret) dev_info(ctx->dev, "%s failed to send scp ipi, ret %d\n", __func__, ret); break; default: dev_info(ctx->dev, "%s Not supported act %d\n", __func__, param->act); break; } return ret; } static ssize_t apusys_aov_test_write(struct file *flip, const char __user *buffer, size_t count, loff_t *f_pos) { struct seq_file *s = (struct seq_file *)flip->private_data; struct apusys_aov_ctx *ctx = (struct apusys_aov_ctx *)s->private; char *user_input, *cmd_str; unsigned int testcase = 0; bool trigger_start = false; bool trigger_stop = false; if (count > MAX_DEBUG_CMD_SIZE) return -EINVAL; user_input = kzalloc(count + 1, GFP_KERNEL); if (!user_input) return -ENOMEM; if (!access_ok(buffer, count)) { dev_info(ctx->dev, "%s unreadable user buffer\n", __func__); goto out; } if (copy_from_user(user_input, buffer, count)) { dev_info(ctx->dev, "%s Failed to copy_from_user\n", __func__); goto out; } user_input[count] = '\0'; cmd_str = user_input; dev_info(ctx->dev, "%s Get testing cmd %s\n", __func__, cmd_str); while (cmd_str != NULL) { char *token = args_get_next_str(&cmd_str); if (args_compare("--testcase", "-t", token)) { unsigned int number; token = args_get_next_str(&cmd_str); if (kstrtouint(token, 10, &number)) { dev_info(ctx->dev, "%s Failed to get %s number\n", __func__, token); break; } testcase = number; } else if (args_compare("--system", "-s", token)) { struct npu_scp_ipi_param param = { 0, 0, 0, 0 }; unsigned int act; token = args_get_next_str(&cmd_str); if (kstrtouint(token, 10, &act)) break; param.cmd = NPU_SCP_SYSTEM; param.act = act; npu_system_cmd_to_scp(ctx, ¶m); } else if (args_compare("--start", NULL, token)) { trigger_start = true; } else if (args_compare("--stop", NULL, token)) { trigger_stop = true; } else { dev_info(ctx->dev, "%s Unrecognized args: %s\n", __func__, token); goto out; } } if (trigger_stop) apusys_aov_stop_testcase(s, ctx, testcase); if (trigger_start) apusys_aov_start_testcase(s, ctx, testcase); out: kfree(user_input); return count; } static const struct file_operations apusys_aov_test_fops = { .open = apusys_aov_test_open, .write = apusys_aov_test_write, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int npu_system_handler(struct apusys_aov_ctx *ctx, struct npu_scp_ipi_param *param) { switch (param->act) { case NPU_SCP_SYSTEM_FUNCTION_ENABLE: atomic_set(&ctx->aov_enabled, 1); dev_info(ctx->dev, "%s Turn on apu aov\n", __func__); break; case NPU_SCP_SYSTEM_FUNCTION_DISABLE: atomic_set(&ctx->aov_enabled, 0); dev_info(ctx->dev, "%s Turn off apu aov\n", __func__); break; default: dev_info(ctx->dev, "%s Not supported act %d\n", __func__, param->act); break; } return 0; } int npu_scp_ipi_send(struct npu_scp_ipi_param *send_msg, struct npu_scp_ipi_param *ret_msg, uint32_t timeout_ms) { struct apusys_aov_ctx *ctx = aov_ctx; int ret = 0; if (!ctx) { pr_info("%s apusys aov context is not available\n", __func__); return -ENODEV; } mutex_lock(&ctx->scp_ipi_lock); atomic_set(&ctx->response_arrived, 0); do { ret = mtk_ipi_send(&scp_ipidev, IPI_OUT_NPU_SCP, IPI_SEND_WAIT, send_msg, 4, 0); if (ret < 0 && ret != IPI_PIN_BUSY) { dev_info(ctx->dev, "%s scp busy, ret %d\n", __func__, ret); break; } } while (ret == IPI_PIN_BUSY); if (ret != IPI_ACTION_DONE) { dev_info(ctx->dev, "%s failed to send scp ipi, ret %d\n", __func__, ret); goto out; } if (ret_msg) { // wait npu_scp_ipi_callback reinit_completion(&ctx->comp); ret = wait_for_completion_timeout(&ctx->comp, msecs_to_jiffies(timeout_ms)); if (!ret) { dev_info(ctx->dev, "%s wait for scp ipi timeout\n", __func__); ret = -EFAULT; goto out; } if (!atomic_read(&ctx->response_arrived)) { dev_info(ctx->dev, "%s No response\n", __func__); ret = -ENODATA; goto out; } ret_msg->cmd = ctx->npu_scp_msg.cmd; ret_msg->act = ctx->npu_scp_msg.act; ret_msg->arg = ctx->npu_scp_msg.arg; ret_msg->ret = ctx->npu_scp_msg.ret; } ret = 0; out: mutex_unlock(&ctx->scp_ipi_lock); return ret; } static int npu_scp_ipi_callback(unsigned int id, void *prdata, void *data, unsigned int len) { int ret = 0; struct apusys_aov_ctx *ctx = (struct apusys_aov_ctx *)prdata; struct npu_scp_ipi_param *recv_msg = (struct npu_scp_ipi_param *)data; if (!ctx || !recv_msg) { pr_info("%s failed to get context or mssage\n", __func__); return -EINVAL; } ctx->npu_scp_msg.cmd = recv_msg->cmd; ctx->npu_scp_msg.act = recv_msg->act; ctx->npu_scp_msg.arg = recv_msg->arg; ctx->npu_scp_msg.ret = recv_msg->ret; switch (recv_msg->cmd) { case NPU_SCP_RESPONSE: atomic_set(&ctx->response_arrived, 1); complete(&ctx->comp); ret = 0; break; case NPU_SCP_SYSTEM: ret = npu_system_handler(ctx, recv_msg); break; case NPU_SCP_NP_MDW: ret = scp_mdw_handler(recv_msg); break; case NPU_SCP_RECOVERY: ret = aov_recovery_handler(recv_msg); break; default: dev_info(ctx->dev, "%s get undefined cmd %d\n", __func__, recv_msg->cmd); ret = -EINVAL; break; } if (ret) dev_info(ctx->dev, "%s ERROR, cmd %d ret %d\n", __func__, recv_msg->cmd, ret); return 0; } static int apusys_aov_probe(struct platform_device *pdev) { struct apusys_aov_ctx *ctx; int ret; dev_info(&pdev->dev, "%s version %s\n", __func__, APUSYS_AOV_KVERSION); ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; atomic_set(&ctx->aov_enabled, 0); atomic_set(&ctx->response_arrived, 0); mutex_init(&ctx->scp_ipi_lock); init_completion(&ctx->comp); ret = mtk_ipi_register(&scp_ipidev, IPI_IN_SCP_NPU, npu_scp_ipi_callback, ctx, &ctx->npu_scp_msg); if (ret != SCP_IPI_DONE) dev_info(&pdev->dev, "%s Failed to register scp ipi, ret %d\n", __func__, ret); ctx->dbg_dir = debugfs_create_dir("aov", apusys_dbg_root); ctx->dbg_node = debugfs_create_file("test", 0444, ctx->dbg_dir, ctx, &apusys_aov_test_fops); if (IS_ERR_OR_NULL(ctx->dbg_node)) { dev_info(&pdev->dev, "%s Failed to create apusys aov test node\n", __func__); return -ENODEV; } ctx->dev = &pdev->dev; platform_set_drvdata(pdev, ctx); aov_ctx = ctx; dev_info(&pdev->dev, "%s Successfully\n", __func__); return ret; } static int apusys_aov_suspend_late(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct apusys_aov_ctx *ctx = platform_get_drvdata(pdev); struct npu_scp_ipi_param send_msg = { 0, 0, 0, 0 }; int ret; if (!atomic_read(&ctx->aov_enabled)) { dev_dbg(ctx->dev, "%s aov is disabled\n", __func__); return 0; } send_msg.cmd = NPU_SCP_STATE_CHANGE; send_msg.act = NPU_SCP_STATE_CHANGE_TO_SUSPEND; ret = npu_scp_ipi_send(&send_msg, NULL, STATE_TIMEOUT_MS); if (ret) dev_info(ctx->dev, "%s failed to send scp ipi, ret %d\n", __func__, ret); dev_info(ctx->dev, "%s send suspend done\n", __func__); return 0; } static int apusys_aov_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct apusys_aov_ctx *ctx = platform_get_drvdata(pdev); struct npu_scp_ipi_param send_msg = { 0, 0, 0, 0 }; int ret; if (!atomic_read(&ctx->aov_enabled)) { dev_dbg(ctx->dev, "%s aov is disabled\n", __func__); return 0; } send_msg.cmd = NPU_SCP_STATE_CHANGE; send_msg.act = NPU_SCP_STATE_CHANGE_TO_RESUME; ret = npu_scp_ipi_send(&send_msg, NULL, STATE_TIMEOUT_MS); if (ret) { // If SCP is not responding, release SLB anyway. int slb_ref = 0; struct slbc_data slb_data = { .uid = UID_AOV_APU, .type = TP_BUFFER, .timeout = 10, }; dev_info(ctx->dev, "%s failed to send scp ipi, ret %d\n", __func__, ret); slb_ref = slbc_status(&slb_data); if (slb_ref > 0) { dev_info(ctx->dev, "%s forcibly release AOV_APU slb\n", __func__); ret = slbc_release(&slb_data); if (ret) { dev_info(ctx->dev, "%s Failed to release AOV_APU slb, ret %d\n", __func__, ret); } } } dev_info(ctx->dev, "%s send resume done\n", __func__); return 0; } static int apusys_aov_remove(struct platform_device *pdev) { struct apusys_aov_ctx *ctx = platform_get_drvdata(pdev); dev_info(ctx->dev, "%s +++\n", __func__); complete_all(&ctx->comp); debugfs_remove_recursive(ctx->dbg_dir); aov_ctx = NULL; dev_info(ctx->dev, "%s ---\n", __func__); return 0; } static const struct dev_pm_ops apusys_aov_pm_cb = { .suspend_late = apusys_aov_suspend_late, .resume = apusys_aov_resume, }; static const struct of_device_id apusys_aov_of_match[] = { { .compatible = "mediatek,apusys_aov", }, {}, }; MODULE_DEVICE_TABLE(of, apusys_aov_of_match); static struct platform_driver apusys_aov_driver = { .probe = apusys_aov_probe, .remove = apusys_aov_remove, .driver = { .name = "apusys_aov", .owner = THIS_MODULE, .of_match_table = apusys_aov_of_match, .pm = &apusys_aov_pm_cb, }, }; int aov_init(struct apusys_core_info *info) { int ret; if (info) apusys_dbg_root = info->dbg_root; ret = platform_driver_register(&apusys_aov_driver); if (ret) pr_info("%s driver register fail\n", __func__); aov_recovery_init(info); aov_rpmsg_init(info); return ret; } void aov_exit(void) { aov_rpmsg_exit(); aov_recovery_exit(); platform_driver_unregister(&apusys_aov_driver); }