441 lines
10 KiB
C
441 lines
10 KiB
C
/*
|
|
*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 <linux/clk.h>
|
|
#include <linux/device.h>
|
|
#include <linux/devfreq.h>
|
|
#include <linux/init.h>
|
|
#include <linux/ipc_logging.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/msm-bus.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#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");
|