// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2021 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define SSC_SYSFS_VLOGIC_BOUND_SUPPORT #if IS_ENABLED(CONFIG_MTK_AEE_FEATURE) #include #endif #if IS_ENABLED(CONFIG_ARM_SCMI_PROTOCOL) #include #include #endif #if IS_ENABLED(CONFIG_GPU_SUPPORT) #include static unsigned int gpueb_enable; #endif #if IS_ENABLED(CONFIG_MTK_AEE_FEATURE) #define ssc_aee_print(string, args...) do {\ char ssc_name[100];\ int ret;\ ret = snprintf(ssc_name, 100, "[SSC] "string, ##args); \ if (ret > 0)\ aee_kernel_exception_api(__FILE__, __LINE__, \ DB_OPT_MMPROFILE_BUFFER | DB_OPT_NE_JBT_TRACES, \ ssc_name, "[SSC] error:"string, ##args); \ pr_info("[SSC] error:"string, ##args); \ } while (0) #else #define ssc_aee_print(string, args...) \ pr_info("[SSC] error:"string, ##args) #endif #define MTK_SSC_DTS_COMPATIBLE "mediatek,ssc" #define MTK_GPU_DTS_COMPATIBLE "mediatek,gpueb" #define SSC_VCORE_REGULATOR "dvfsrc-vcore" #define PLT_SSC_INIT (0x504C5403) /*magic*/ struct kobject *ssc_kobj; EXPORT_SYMBOL_GPL(ssc_kobj); unsigned int ssc_enable; EXPORT_SYMBOL_GPL(ssc_enable); static BLOCKING_NOTIFIER_HEAD(vlogic_bound_chain); static struct regulator *ssc_vcore_voter; static int set_vcore_vlogic_bound(en) { int ret; if (!ssc_vcore_voter) { pr_info("[SSC] invalid vcore regulator\n"); return -1; } if (en) ret = regulator_set_voltage(ssc_vcore_voter, 600000, INT_MAX); else ret = regulator_set_voltage(ssc_vcore_voter, 575000, INT_MAX); if (ret) { pr_info("[SSC] vcore vlogic bound %s fail\n", en ? "enable" : "disable"); return -1; } return 0; } static int set_gpu_vlogic_bound(int en) { int ret = 0; #if IS_ENABLED(CONFIG_GPU_SUPPORT) /* floor value for GPU = mV * 100 */ if (en) ret = gpufreq_set_limit(TARGET_DEFAULT, LIMIT_SRAMRC, GPUPPM_KEEP_IDX, 60000); else ret = gpufreq_set_limit(TARGET_DEFAULT, LIMIT_SRAMRC, GPUPPM_KEEP_IDX, GPUPPM_RESET_IDX); if (ret) pr_info("[SSC] gpu vlogic bound %s fail\n", en ? "enable" : "disable"); #endif return ret; } static int ssc_vlogic_bound_event(struct notifier_block *notifier, unsigned long event, void *data) { unsigned int request_id = *((unsigned int *)data); pr_info("[SSC] request ID = 0x%x, event = 0x%lx\n", request_id, event); switch (event) { case SSC_ENABLE_VLOGIC_BOUND: set_gpu_vlogic_bound(1); set_vcore_vlogic_bound(1); return NOTIFY_DONE; case SSC_DISABLE_VLOGIC_BOUND: set_vcore_vlogic_bound(0); set_gpu_vlogic_bound(0); return NOTIFY_DONE; case SSC_TIMEOUT: return NOTIFY_DONE; default: return NOTIFY_BAD; } return NOTIFY_OK; } static struct notifier_block ssc_vlogic_notifier_func = { .notifier_call = ssc_vlogic_bound_event, .priority = 0, }; static int ssc_vlogic_bound_call_chain(unsigned int val, unsigned int request_id) { int ret; ret = blocking_notifier_call_chain(&vlogic_bound_chain, val, &request_id); if (ret == NOTIFY_DONE || ret == NOTIFY_OK) return 0; else return -1; } static atomic_t vlogic_bound_counter; int ssc_vlogic_bound_register_notifier(struct notifier_block *nb) { return blocking_notifier_chain_register(&vlogic_bound_chain, nb); } EXPORT_SYMBOL_GPL(ssc_vlogic_bound_register_notifier); int ssc_vlogic_bound_unregister_notifier(struct notifier_block *nb) { return blocking_notifier_chain_unregister(&vlogic_bound_chain, nb); } EXPORT_SYMBOL_GPL(ssc_vlogic_bound_unregister_notifier); int ssc_enable_vlogic_bound(int request_id) { /* check counter value */ if (atomic_read(&vlogic_bound_counter) < 0) return -1; /* check request_id */ if (request_id < 0 || request_id >= SSC_REQUEST_NUM) return -1; if (atomic_inc_return(&vlogic_bound_counter) == 1) return ssc_vlogic_bound_call_chain(SSC_ENABLE_VLOGIC_BOUND, request_id); /* vlogic has been bounded */ return 0; } EXPORT_SYMBOL_GPL(ssc_enable_vlogic_bound); int ssc_disable_vlogic_bound(int request_id) { /* check counter value */ if (atomic_read(&vlogic_bound_counter) < 0) return -1; /* check request_id */ if (request_id < 0 || request_id >= SSC_REQUEST_NUM) return -1; if (atomic_dec_return(&vlogic_bound_counter) == 0) return ssc_vlogic_bound_call_chain(SSC_DISABLE_VLOGIC_BOUND, request_id); /* vlogic still bounded */ return 0; } EXPORT_SYMBOL_GPL(ssc_disable_vlogic_bound); #ifdef SSC_SYSFS_VLOGIC_BOUND_SUPPORT static ssize_t vlogic_bound_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { char mode[10]; if (sscanf(buf, "%9s", mode) != 1) return -EPERM; if (strcmp(mode, "enable") == 0) ssc_enable_vlogic_bound(SSC_SW); else if (strcmp(mode, "disable") == 0) ssc_disable_vlogic_bound(SSC_SW); return count; } static ssize_t vlogic_bound_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int len = 0; len = snprintf(buf, PAGE_SIZE, "[SSC] bound cnt = %d\n", atomic_read(&vlogic_bound_counter)); return len; } DEFINE_ATTR_RW(vlogic_bound); #endif static unsigned int safe_vlogic_uV = 0xFFFFFFFF; unsigned int ssc_get_safe_vlogic_uV(void) { return safe_vlogic_uV; } #define SSC_TIMEOUT_NUM 6 static const char * const ssc_timeout_name[] = { "SSC", "GPU", "ISP", "CORE", "APU", "SRAM", }; #if IS_ENABLED(CONFIG_ARM_SCMI_PROTOCOL) static int ssc_scmi_feature_id; static int plt_scmi_feature_id; #define SSC_TIMEOUT_MASK_SHIFT (16) static void ssc_notification_handler(u32 feature_id, scmi_tinysys_report *report) { #define LOG_BUF_SIZE 30 char log_buf[LOG_BUF_SIZE] = { 0 }; int log_size = 0; int i, timeout; int timeout_mask_shift = SSC_TIMEOUT_MASK_SHIFT; if (report->p1 == SSC_STATUS_ERR) { /* SSC timeout notifiy */ ssc_vlogic_bound_call_chain(SSC_TIMEOUT, SSC_ERR); timeout = report->p2; pr_info("[SSC] %s, timeout_sta = 0x%x, sram_sta = 0x%x, misc = 0x%x\n", __func__, report->p2, report->p3, report->p4); for (i = 1 ; i < SSC_TIMEOUT_NUM ; i++) { timeout_mask_shift = (i - 1) * 2 + SSC_TIMEOUT_MASK_SHIFT; if ((timeout & (0x1U << i)) && ((timeout & (0x3 << timeout_mask_shift)) == 0x0)) { log_size += scnprintf(log_buf + log_size, LOG_BUF_SIZE - log_size, "%s", ssc_timeout_name[i]); } } pr_info("[SSC] CRDISPATCH_KEY: SSC VIOLATION: %s\n", log_buf); BUG_ON(1); } } #endif static DEFINE_SPINLOCK(ssc_locker); static const struct of_device_id ssc_of_ids[] = { {.compatible = "mediatek,ssc",}, {} }; static int mt_ssc_pdrv_probe(struct platform_device *pdev) { ssc_vcore_voter = regulator_get(&pdev->dev, SSC_VCORE_REGULATOR); if (IS_ERR(ssc_vcore_voter)) { pr_info("[SSC] get ssc vcore regulator fail\n"); ssc_vcore_voter = NULL; return -1; } return 0; } static int mt_ssc_pdrv_remove(struct platform_device *pdev) { return 0; } static struct platform_driver mt_ssc_pdrv = { .probe = mt_ssc_pdrv_probe, .remove = mt_ssc_pdrv_remove, .driver = { .name = "ssc_dvfs", .owner = THIS_MODULE, .of_match_table = ssc_of_ids, }, }; static int __init ssc_init(void) { struct device_node *ssc_node; int ret; unsigned long flags; #if IS_ENABLED(CONFIG_ARM_SCMI_PROTOCOL) struct scmi_tinysys_status rvalue; struct scmi_tinysys_info_st *tinfo; #endif pr_info("[SSC] %s\n", __func__); ret = platform_driver_register(&mt_ssc_pdrv); if (ret) pr_info("[SSC] fail to register SSC platform driver\n"); spin_lock_irqsave(&ssc_locker, flags); ssc_node = of_find_compatible_node(NULL, NULL, MTK_SSC_DTS_COMPATIBLE); ssc_enable = 0; if (ssc_node) { ret = of_property_read_u32(ssc_node, MTK_SSC_SAFE_VLOGIC_STRING, &safe_vlogic_uV); pr_info("[SSC] safe_vlogic_uV = %d uV", safe_vlogic_uV); /* This property is not defined*/ if (ret) safe_vlogic_uV = 0xFFFFFFFF; ret = of_property_read_u32(ssc_node, "ssc_enable", &ssc_enable); if (ret) ssc_enable = 0; of_node_put(ssc_node); } if (ssc_enable == 0) { spin_unlock_irqrestore(&ssc_locker, flags); pr_info("[SSC] disabled\n"); return 0; } /* set gpu vlogic bound 0.6V if gpueb not ready */ #if IS_ENABLED(CONFIG_GPU_SUPPORT) ssc_node = of_find_compatible_node(NULL, NULL, MTK_GPU_DTS_COMPATIBLE); if (ssc_node) { ret = of_property_read_u32(ssc_node, "gpueb-support", &gpueb_enable); pr_info("[SSC] gpueb enable = 0x%x\n", gpueb_enable); if (gpueb_enable == 0) gpufreq_set_limit(TARGET_DEFAULT, LIMIT_SRAMRC, GPUPPM_KEEP_IDX, 60000); of_node_put(ssc_node); } #endif spin_unlock_irqrestore(&ssc_locker, flags); /* scmi interface initialization */ #if IS_ENABLED(CONFIG_ARM_SCMI_PROTOCOL) tinfo = get_scmi_tinysys_info(); if (!tinfo) { pr_info("[SSC] get SCMI info fail\n"); goto SKIP_SCMI; } ret = of_property_read_u32(tinfo->sdev->dev.of_node, "scmi_ssc", &ssc_scmi_feature_id); pr_info("[SSC] ssc scmi id = %d\n", ssc_scmi_feature_id); scmi_tinysys_register_event_notifier(ssc_scmi_feature_id, (f_handler_t)ssc_notification_handler); ret = scmi_tinysys_event_notify(ssc_scmi_feature_id, 1); if (ret < 0) pr_info("[SSC] SCMI notify register fail\n"); ret = of_property_read_u32(tinfo->sdev->dev.of_node, "scmi-plt", &plt_scmi_feature_id); ret = scmi_tinysys_common_set(tinfo->ph, plt_scmi_feature_id, PLT_SSC_INIT, 0, 0, 0, 0); if (ret) ssc_aee_print("[SSC] SCMI common set fail!\n"); else pr_info("[SSC] notify done! (r1:%d r2:%d r3:%d)\n", rvalue.r1, rvalue.r2, rvalue.r3); SKIP_SCMI: #endif ssc_vlogic_bound_register_notifier(&ssc_vlogic_notifier_func); atomic_set(&vlogic_bound_counter, 0); /* create sysfs entry for voltage bound */ ssc_kobj = kobject_create_and_add("ssc", kernel_kobj); #ifdef SSC_SYSFS_VLOGIC_BOUND_SUPPORT if (ssc_kobj) ret = sysfs_create_file(ssc_kobj, __ATTR_OF(vlogic_bound)); #endif return 0; } static void __exit ssc_deinit(void) { if (ssc_enable == 0) return; #ifdef SSC_SYSFS_VLOGIC_BOUND_SUPPORT sysfs_remove_file(ssc_kobj, __ATTR_OF(vlogic_bound)); #endif kobject_del(ssc_kobj); ssc_vlogic_bound_unregister_notifier(&ssc_vlogic_notifier_func); } module_init(ssc_init); module_exit(ssc_deinit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("mtk ssc module"); MODULE_AUTHOR("MediaTek Inc.");