/* *Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. * *This program is free software; you can redistribute it and/or modify *it under the terms of the GNU General Public License version 2 and *only version 2 as published by the Free Software Foundation. * *This program is distributed in the hope that it will be useful, *but WITHOUT ANY WARRANTY; without even the implied warranty of *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "governor.h" #include "devfreq_spdm.h" static void *spdm_ipc_log_ctxt; #define DEVFREQ_SPDM_DEFAULT_WINDOW_MS 100 #define SPDM_IPC_LOG_PAGES 5 #define SPDM_IPC_LOG(x...) do { \ pr_debug(x); \ if (spdm_ipc_log_ctxt) \ ipc_log_string(spdm_ipc_log_ctxt, x); \ } while (0) #define COPY_SIZE(x, y) ((x) <= (y) ? (x) : (y)) static int change_bw(struct device *dev, unsigned long *freq, u32 flags) { struct spdm_data *data = 0; int i; int next_idx; int ret = 0; struct spdm_args desc = { { 0 } }; int ext_status = 0; if (!dev || !freq) return -EINVAL; data = dev_get_drvdata(dev); if (!data) return -EINVAL; if (data->devfreq->previous_freq == *freq) goto update_thresholds; next_idx = data->cur_idx + 1; next_idx = next_idx % 2; for (i = 0; i < data->pdata->usecase[next_idx].num_paths; i++) data->pdata->usecase[next_idx].vectors[i].ab = (*freq) << 6; data->cur_idx = next_idx; ret = msm_bus_scale_client_update_request(data->bus_scale_client_id, data->cur_idx); update_thresholds: desc.arg[0] = SPDM_CMD_ENABLE; desc.arg[1] = data->spdm_client; desc.arg[2] = (clk_get_rate(data->cci_clk)) / 1000; ext_status = spdm_ext_call(&desc, 3); if (ext_status) pr_err("External command %u failed with error %u", (int)desc.arg[0], ext_status); return ret; } static int get_cur_bw(struct device *dev, unsigned long *freq) { struct spdm_data *data = 0; if (!dev || !freq) return -EINVAL; data = dev_get_drvdata(dev); if (!data) return -EINVAL; *freq = data->pdata->usecase[data->cur_idx].vectors[0].ab >> 6; return 0; } static int get_dev_status(struct device *dev, struct devfreq_dev_status *status) { struct spdm_data *data = 0; int ret; if (!dev || !status) return -EINVAL; data = dev_get_drvdata(dev); if (!data) return -EINVAL; /* determine if we want to go up or down based on the notification */ if (data->action == SPDM_UP) status->busy_time = 255; else status->busy_time = 0; status->total_time = 255; ret = get_cur_bw(dev, &status->current_frequency); if (ret) return ret; return 0; } static int populate_config_data(struct spdm_data *data, struct platform_device *pdev) { int ret = -EINVAL; struct device_node *node = pdev->dev.of_node; struct property *prop = 0; ret = of_property_read_u32(node, "qcom,max-vote", &data->config_data.max_vote); if (ret) return ret; ret = of_property_read_u32(node, "qcom,bw-upstep", &data->config_data.upstep); if (ret) return ret; ret = of_property_read_u32(node, "qcom,bw-dwnstep", &data->config_data.downstep); if (ret) return ret; ret = of_property_read_u32(node, "qcom,alpha-up", &data->config_data.aup); if (ret) return ret; ret = of_property_read_u32(node, "qcom,alpha-down", &data->config_data.adown); if (ret) return ret; ret = of_property_read_u32(node, "qcom,bucket-size", &data->config_data.bucket_size); if (ret) return ret; ret = of_property_read_u32_array(node, "qcom,pl-freqs", data->config_data.pl_freqs, SPDM_PL_COUNT - 1); if (ret) return ret; ret = of_property_read_u32_array(node, "qcom,reject-rate", data->config_data.reject_rate, SPDM_PL_COUNT * 2); if (ret) return ret; ret = of_property_read_u32_array(node, "qcom,response-time-us", data->config_data.response_time_us, SPDM_PL_COUNT * 2); if (ret) return ret; ret = of_property_read_u32_array(node, "qcom,cci-response-time-us", data->config_data.cci_response_time_us, SPDM_PL_COUNT * 2); if (ret) return ret; ret = of_property_read_u32(node, "qcom,max-cci-freq", &data->config_data.max_cci_freq); if (ret) return ret; ret = of_property_read_u32(node, "qcom,up-step-multp", &data->config_data.up_step_multp); if (ret) return ret; prop = of_find_property(node, "qcom,ports", 0); if (!prop) return -EINVAL; data->config_data.num_ports = prop->length / sizeof(u32); data->config_data.ports = devm_kzalloc(&pdev->dev, prop->length, GFP_KERNEL); if (!data->config_data.ports) return -ENOMEM; ret = of_property_read_u32_array(node, "qcom,ports", data->config_data.ports, data->config_data.num_ports); if (ret) { devm_kfree(&pdev->dev, data->config_data.ports); data->config_data.ports = NULL; return ret; } return 0; } static int populate_spdm_data(struct spdm_data *data, struct platform_device *pdev) { int ret = -EINVAL; struct device_node *node = pdev->dev.of_node; ret = populate_config_data(data, pdev); if (ret) return ret; ret = of_property_read_u32(node, "qcom,spdm-client", &data->spdm_client); if (ret) goto no_client; ret = of_property_read_u32(node, "qcom,spdm-interval", &data->window); if (ret) data->window = DEVFREQ_SPDM_DEFAULT_WINDOW_MS; data->pdata = msm_bus_cl_get_pdata(pdev); if (!data->pdata) { ret = -EINVAL; goto no_pdata; } return 0; no_client: no_pdata: devm_kfree(&pdev->dev, data->config_data.ports); data->config_data.ports = NULL; return ret; } int __spdm_hyp_call(struct spdm_args *args, int num_args) { struct hvc_desc desc = { { 0 } }; int status; memcpy(desc.arg, args->arg, COPY_SIZE(sizeof(desc.arg), sizeof(args->arg))); SPDM_IPC_LOG("hvc call fn:0x%x, cmd:%llu, num_args:%d\n", HVC_FN_SIP(SPDM_HYP_FNID), desc.arg[0], num_args); status = hvc(HVC_FN_SIP(SPDM_HYP_FNID), &desc); memcpy(args->ret, desc.ret, COPY_SIZE(sizeof(args->ret), sizeof(desc.ret))); SPDM_IPC_LOG("hvc return fn:0x%x cmd:%llu Ret[0]:%llu Ret[1]:%llu\n", HVC_FN_SIP(SPDM_HYP_FNID), desc.arg[0], desc.ret[0], desc.ret[1]); return status; } int __spdm_scm_call(struct spdm_args *args, int num_args) { int status = 0; SPDM_IPC_LOG("%s:svc_id:%d,cmd_id:%d,cmd:%llu,num_args:%d\n", __func__, SPDM_SCM_SVC_ID, SPDM_SCM_CMD_ID, args->arg[0], num_args); if (!is_scm_armv8()) { status = scm_call(SPDM_SCM_SVC_ID, SPDM_SCM_CMD_ID, args->arg, sizeof(args->arg), args->ret, sizeof(args->ret)); } else { struct scm_desc desc = {0}; /* * Need to hard code this, this is a requirement from TZ syscall * interface. */ desc.arginfo = SCM_ARGS(6); memcpy(desc.args, args->arg, COPY_SIZE(sizeof(desc.args), sizeof(args->arg))); status = scm_call2(SCM_SIP_FNID(SPDM_SCM_SVC_ID, SPDM_SCM_CMD_ID), &desc); memcpy(args->ret, desc.ret, COPY_SIZE(sizeof(args->ret), sizeof(desc.ret))); } SPDM_IPC_LOG("%s:svc_id:%d,cmd_id:%d,cmd:%llu,Ret[0]:%llu,Ret[1]:%llu\n" , __func__, SPDM_SCM_SVC_ID, SPDM_SCM_CMD_ID, args->arg[0], args->ret[0], args->ret[1]); return status; } static int probe(struct platform_device *pdev) { struct spdm_data *data = 0; int ret = -EINVAL; struct spdm_args desc = { { 0 } }; int ext_status = 0; data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; data->action = SPDM_DOWN; platform_set_drvdata(pdev, data); ret = populate_spdm_data(data, pdev); if (ret) goto bad_of; desc.arg[0] = SPDM_CMD_GET_VERSION; ext_status = spdm_ext_call(&desc, 1); if (ext_status) { pr_err("%s:External command %u failed with error %u\n", __func__, (int)desc.arg[0], ext_status); goto bad_of; } if (desc.ret[0] < SPDM_TZ_VERSION) { pr_err("%s: Version mismatch expected 0x%x got 0x%x", __func__, SPDM_TZ_VERSION, (int)desc.arg[0]); goto bad_of; } data->bus_scale_client_id = msm_bus_scale_register_client(data->pdata); if (!data->bus_scale_client_id) { ret = -EINVAL; goto no_bus_scaling; } data->cci_clk = clk_get(&pdev->dev, "cci_clk"); if (IS_ERR(data->cci_clk)) { ret = PTR_ERR(data->cci_clk); goto no_clock; } data->profile = devm_kzalloc(&pdev->dev, sizeof(*(data->profile)), GFP_KERNEL); if (!data->profile) { ret = -ENOMEM; goto no_profile; } data->profile->target = change_bw; data->profile->get_dev_status = get_dev_status; data->profile->get_cur_freq = get_cur_bw; data->profile->polling_ms = data->window; data->devfreq = devfreq_add_device(&pdev->dev, data->profile, "spdm_bw_hyp", data); if (IS_ERR(data->devfreq)) { ret = PTR_ERR(data->devfreq); goto no_spdm_device; } spdm_init_debugfs(&pdev->dev); spdm_ipc_log_ctxt = ipc_log_context_create(SPDM_IPC_LOG_PAGES, "devfreq_spdm", 0); if (IS_ERR_OR_NULL(spdm_ipc_log_ctxt)) { pr_err("%s: Failed to create IPC log context\n", __func__); spdm_ipc_log_ctxt = NULL; } return 0; no_spdm_device: devm_kfree(&pdev->dev, data->profile); no_profile: no_clock: msm_bus_scale_unregister_client(data->bus_scale_client_id); no_bus_scaling: devm_kfree(&pdev->dev, data->config_data.ports); bad_of: devm_kfree(&pdev->dev, data); platform_set_drvdata(pdev, NULL); return ret; } static int remove(struct platform_device *pdev) { struct spdm_data *data = 0; data = platform_get_drvdata(pdev); spdm_remove_debugfs(data); if (data->devfreq) devfreq_remove_device(data->devfreq); if (data->profile) devm_kfree(&pdev->dev, data->profile); if (data->bus_scale_client_id) msm_bus_scale_unregister_client(data->bus_scale_client_id); if (data->config_data.ports) devm_kfree(&pdev->dev, data->config_data.ports); devm_kfree(&pdev->dev, data); platform_set_drvdata(pdev, NULL); if (spdm_ipc_log_ctxt) ipc_log_context_destroy(spdm_ipc_log_ctxt); return 0; } static const struct of_device_id devfreq_spdm_match[] = { {.compatible = "qcom,devfreq_spdm"}, {} }; static struct platform_driver devfreq_spdm_drvr = { .driver = { .name = "devfreq_spdm", .owner = THIS_MODULE, .of_match_table = devfreq_spdm_match, }, .probe = probe, .remove = remove, }; static int __init devfreq_spdm_init(void) { return platform_driver_register(&devfreq_spdm_drvr); } module_init(devfreq_spdm_init); MODULE_LICENSE("GPL v2");