// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "dvfsrc-debug.h" #include "dvfsrc-common.h" #include static struct mtk_dvfsrc *dvfsrc_drv; static DEFINE_MUTEX(dump_lock); static int dvfsrc_query_debug_info(u32 id) { struct mtk_dvfsrc *dvfsrc = dvfsrc_drv; const struct dvfsrc_config *config; int ret; config = dvfsrc_drv->dvd->config; ret = config->query_request(dvfsrc, id); return ret; } static u32 dvfsrc_query_md_scenario(void) { struct mtk_dvfsrc *dvfsrc = dvfsrc_drv; const struct dvfsrc_config *config; int ret; config = dvfsrc_drv->dvd->config; ret = config->query_request(dvfsrc, DVFSRC_MD_SCENARIO_REQ); return ret; } static void mtk_dvfsrc_get_perf_state(struct mtk_dvfsrc *dvfsrc, struct device_node *np) { int i; for (i = 0; i < dvfsrc->num_perf; i++) { dvfsrc->perfs[i] = dvfsrc_get_required_opp_performance_state(np, i); } } static void dvfsrc_setup_vopp_table(struct mtk_dvfsrc *dvfsrc) { int i; struct arm_smccc_res ares; u32 num_vopp; arm_smccc_smc(MTK_SIP_VCOREFS_CONTROL, MTK_SIP_VCOREFS_GET_NUM_V, 0, 0, 0, 0, 0, 0, &ares); if (!ares.a0) dvfsrc->num_vopp = ares.a1; else return; num_vopp = dvfsrc->num_vopp; dvfsrc->vopp_uv_tlb = devm_kzalloc(dvfsrc->dev, num_vopp * sizeof(u32), GFP_KERNEL); if (!dvfsrc->vopp_uv_tlb) return; for (i = 0; i < num_vopp; i++) { arm_smccc_smc(MTK_SIP_VCOREFS_CONTROL, MTK_SIP_VCOREFS_GET_VCORE_UV, i, 0, 0, 0, 0, 0, &ares); if (!ares.a0) dvfsrc->vopp_uv_tlb[i] = ares.a1; else { dvfsrc->num_vopp = 0; break; } } for (i = 0; i < num_vopp; i++) dev_info(dvfsrc->dev, "dvfsrc gear uv[%d] = %d\n", i, dvfsrc->vopp_uv_tlb[i]); } static int dvfsrc_vcore_check(struct notifier_block *b, unsigned long l, void *v) { u32 gear = l; int vcore_uv = 0; int predict_uv; struct mtk_dvfsrc *dvfsrc; dvfsrc = container_of(b, struct mtk_dvfsrc, dvfsrc_vchk_notifier); if ((!dvfsrc->vcore_power) || (gear > dvfsrc->num_vopp)) return NOTIFY_DONE; predict_uv = dvfsrc->vopp_uv_tlb[gear]; vcore_uv = regulator_get_voltage(dvfsrc->vcore_power); if (vcore_uv < predict_uv) { dev_info(dvfsrc->dev, "VCORE CHECK FAIL= %d %d, %d\n", gear, vcore_uv, predict_uv); return NOTIFY_BAD; } return NOTIFY_DONE; } static int dvfsrc_dump_info(struct notifier_block *b, unsigned long l, void *v) { char *p; ssize_t dump_size = DUMP_BUF_SIZE - 1; const struct dvfsrc_config *config; struct mtk_dvfsrc *dvfsrc; dvfsrc = container_of(b, struct mtk_dvfsrc, dvfsrc_dump_notifier); config = dvfsrc->dvd->config; mutex_lock(&dump_lock); p = dvfsrc->dump_buf; config->dump_info(dvfsrc, p, dump_size); pr_info("%s", dvfsrc->dump_buf); p = dvfsrc->dump_buf; config->dump_reg(dvfsrc, p, dump_size); pr_info("%s", dvfsrc->dump_buf); p = dvfsrc->dump_buf; config->dump_record(dvfsrc, p, dump_size); pr_info("%s", dvfsrc->dump_buf); if (config->dump_spm_info) { p = dvfsrc->dump_buf; config->dump_spm_info(dvfsrc, p, dump_size); pr_info("%s", dvfsrc->dump_buf); } mutex_unlock(&dump_lock); return NOTIFY_DONE; } static void dvfsrc_vchk_notifier(struct mtk_dvfsrc *dvfsrc) { dvfsrc->dvfsrc_vchk_notifier.notifier_call = dvfsrc_vcore_check; register_dvfsrc_vchk_notifier(&dvfsrc->dvfsrc_vchk_notifier); } static void dvfsrc_dump_notifier(struct mtk_dvfsrc *dvfsrc) { dvfsrc->dvfsrc_dump_notifier.notifier_call = dvfsrc_dump_info; register_dvfsrc_dump_notifier(&dvfsrc->dvfsrc_dump_notifier); } static int mtk_dvfsrc_debug_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct platform_device *parent_dev; struct resource *res; struct mtk_dvfsrc *dvfsrc; struct device_node *np = pdev->dev.of_node; parent_dev = to_platform_device(dev->parent); dvfsrc = devm_kzalloc(&pdev->dev, sizeof(*dvfsrc), GFP_KERNEL); if (!dvfsrc) return -ENOMEM; dvfsrc->dvd = of_device_get_match_data(&pdev->dev); dvfsrc->dev = &pdev->dev; dvfsrc->num_perf = of_count_phandle_with_args(np, "required-opps", NULL); if (dvfsrc->num_perf > 0) { dvfsrc->perfs = devm_kzalloc(&pdev->dev, dvfsrc->num_perf * sizeof(int), GFP_KERNEL); if (!dvfsrc->perfs) return -ENOMEM; mtk_dvfsrc_get_perf_state(dvfsrc, np); } else { dvfsrc->num_perf = 0; } res = platform_get_resource_byname(parent_dev, IORESOURCE_MEM, "dvfsrc"); if (!res) { dev_info(dev, "dvfsrc debug resource not found\n"); return -ENODEV; } dvfsrc->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (IS_ERR(dvfsrc->regs)) return PTR_ERR(dvfsrc->regs); res = platform_get_resource_byname(parent_dev, IORESOURCE_MEM, "spm"); if (res) { dvfsrc->spm_regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (IS_ERR(dvfsrc->spm_regs)) dvfsrc->spm_regs = NULL; } dvfsrc->vcore_power = regulator_get_optional(&pdev->dev, "vcore"); if (IS_ERR(dvfsrc->vcore_power)) { dev_info(dev, "get debug vcore failed = %ld\n", PTR_ERR(dvfsrc->vcore_power)); dvfsrc->vcore_power = NULL; } if (!dvfsrc->dvd->pmqos_enable) { dvfsrc->dvfsrc_vcore_power = regulator_get_optional(&pdev->dev, "rc-vcore"); if (IS_ERR(dvfsrc->dvfsrc_vcore_power)) { dev_info(dev, "get dvfsrc_vcore failed = %ld\n", PTR_ERR(dvfsrc->dvfsrc_vcore_power)); dvfsrc->dvfsrc_vcore_power = NULL; } dvfsrc->dvfsrc_vscp_power = regulator_get_optional(&pdev->dev, "rc-vscp"); if (IS_ERR(dvfsrc->dvfsrc_vscp_power)) { dev_info(dev, "get dvfsrc vscp failed = %ld\n", PTR_ERR(dvfsrc->dvfsrc_vscp_power)); dvfsrc->dvfsrc_vscp_power = NULL; } dvfsrc->path = of_icc_get(&pdev->dev, "icc-bw-port"); if (IS_ERR(dvfsrc->path)) { dev_info(dev, "get icc-bw-port fail\n"); dvfsrc->path = NULL; } } dvfsrc->force_opp_idx = mtk_dvfsrc_query_opp_info(MTK_DVFSRC_NUM_DVFS_OPP); dvfsrc_register_sysfs(dev); dvfsrc_setup_vopp_table(dvfsrc); dvfsrc_vchk_notifier(dvfsrc); dvfsrc_dump_notifier(dvfsrc); dvfsrc_drv = dvfsrc; register_dvfsrc_debug_handler(dvfsrc_query_debug_info); register_dvfsrc_md_scenario_handler(dvfsrc_query_md_scenario); platform_set_drvdata(pdev, dvfsrc); return 0; } static const struct dvfsrc_debug_data mt6779_data = { .pmqos_enable = true, .config = &mt6779_dvfsrc_config, }; static const struct dvfsrc_debug_data mt6761_data = { .pmqos_enable = true, .config = &mt6761_dvfsrc_config, }; static int mtk_dvfsrc_debug_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mtk_dvfsrc *dvfsrc = platform_get_drvdata(pdev); unregister_dvfsrc_vchk_notifier(&dvfsrc->dvfsrc_vchk_notifier); dvfsrc_unregister_sysfs(dev); dvfsrc_drv = NULL; return 0; } static const struct of_device_id mtk_dvfsrc_debug_of_match[] = { { .compatible = "mediatek,mt6779-dvfsrc-debug", .data = &mt6779_data, }, { .compatible = "mediatek,mt6761-dvfsrc-debug", .data = &mt6761_data, }, { .compatible = "mediatek,mt6765-dvfsrc-debug", .data = &mt6761_data, }, { /* sentinel */ }, }; static struct platform_driver mtk_dvfsrc_debug_driver = { .probe = mtk_dvfsrc_debug_probe, .remove = mtk_dvfsrc_debug_remove, .driver = { .name = "mtk-dvfsrc-debug", .of_match_table = of_match_ptr(mtk_dvfsrc_debug_of_match), }, }; static int __init mtk_dvfsrc_debug_init(void) { return platform_driver_register(&mtk_dvfsrc_debug_driver); } late_initcall_sync(mtk_dvfsrc_debug_init) static void __exit mtk_dvfsrc_debug_exit(void) { platform_driver_unregister(&mtk_dvfsrc_debug_driver); } module_exit(mtk_dvfsrc_debug_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("MTK DVFSRC debug driver");