| /* |
| *Copyright (c) 2014-2018, 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/devfreq.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/irqreturn.h> |
| #include <linux/list.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <soc/qcom/rpm-smd.h> |
| #include "governor.h" |
| #include "devfreq_spdm.h" |
| |
| enum msm_spdm_rt_res { |
| SPDM_RES_ID = 1, |
| SPDM_RES_TYPE = 0x63707362, |
| SPDM_KEY = 0x00006e65, |
| SPDM_SIZE = 4, |
| }; |
| |
| static LIST_HEAD(devfreqs); |
| static DEFINE_MUTEX(devfreqs_lock); |
| |
| static int enable_clocks(void) |
| { |
| struct msm_rpm_request *rpm_req; |
| int id; |
| const int one = 1; |
| |
| rpm_req = msm_rpm_create_request(MSM_RPM_CTX_ACTIVE_SET, SPDM_RES_TYPE, |
| SPDM_RES_ID, 1); |
| if (IS_ERR_OR_NULL(rpm_req)) |
| return -ENODEV; |
| msm_rpm_add_kvp_data(rpm_req, SPDM_KEY, (const uint8_t *)&one, |
| sizeof(int)); |
| id = msm_rpm_send_request(rpm_req); |
| msm_rpm_wait_for_ack(id); |
| msm_rpm_free_request(rpm_req); |
| |
| return 0; |
| } |
| |
| static int disable_clocks(void) |
| { |
| struct msm_rpm_request *rpm_req; |
| int id; |
| const int zero = 0; |
| |
| rpm_req = msm_rpm_create_request(MSM_RPM_CTX_ACTIVE_SET, SPDM_RES_TYPE, |
| SPDM_RES_ID, 1); |
| if (IS_ERR_OR_NULL(rpm_req)) |
| return -ENODEV; |
| msm_rpm_add_kvp_data(rpm_req, SPDM_KEY, (const uint8_t *)&zero, |
| sizeof(int)); |
| id = msm_rpm_send_request(rpm_req); |
| msm_rpm_wait_for_ack(id); |
| msm_rpm_free_request(rpm_req); |
| |
| return 0; |
| } |
| |
| static irqreturn_t threaded_isr(int irq, void *dev_id) |
| { |
| struct spdm_data *data; |
| struct spdm_args desc = { { 0 } }; |
| int ext_status = 0; |
| |
| /* call hyp to get bw_vote */ |
| desc.arg[0] = SPDM_CMD_GET_BW_ALL; |
| ext_status = spdm_ext_call(&desc, 1); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| mutex_lock(&devfreqs_lock); |
| list_for_each_entry(data, &devfreqs, list) { |
| if (data == NULL || data->devfreq == NULL) { |
| pr_err("Spurious interrupts\n"); |
| break; |
| } |
| if (data->spdm_client == desc.ret[0]) { |
| devfreq_monitor_suspend(data->devfreq); |
| mutex_lock(&data->devfreq->lock); |
| data->action = SPDM_UP; |
| data->new_bw = |
| (desc.ret[1] * 1000) >> 6; |
| update_devfreq(data->devfreq); |
| data->action = SPDM_DOWN; |
| mutex_unlock(&data->devfreq->lock); |
| devfreq_monitor_resume(data->devfreq); |
| break; |
| } |
| } |
| mutex_unlock(&devfreqs_lock); |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t isr(int irq, void *dev_id) |
| { |
| return IRQ_WAKE_THREAD; |
| } |
| |
| static int gov_spdm_hyp_target_bw(struct devfreq *devfreq, unsigned long *freq) |
| { |
| struct devfreq_dev_status status; |
| int ret = -EINVAL; |
| int usage; |
| struct spdm_args desc = { { 0 } }; |
| int ext_status = 0; |
| u64 bw_ret; |
| |
| if (!devfreq || !devfreq->profile || !devfreq->profile->get_dev_status) |
| return ret; |
| |
| ret = devfreq->profile->get_dev_status(devfreq->dev.parent, &status); |
| if (ret) |
| return ret; |
| |
| usage = (status.busy_time * 100) / status.total_time; |
| |
| if (usage > 0) { |
| /* up was already called as part of hyp, so just use the |
| * already stored values. |
| */ |
| *freq = ((struct spdm_data *)devfreq->data)->new_bw; |
| } else { |
| desc.arg[0] = SPDM_CMD_GET_BW_SPECIFIC; |
| desc.arg[1] = ((struct spdm_data *)devfreq->data)->spdm_client; |
| ext_status = spdm_ext_call(&desc, 2); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| bw_ret = desc.ret[0] * 1000; |
| *freq = bw_ret >> 6; |
| } |
| |
| return 0; |
| } |
| |
| static int gov_spdm_hyp_eh(struct devfreq *devfreq, unsigned int event, |
| void *data) |
| { |
| struct spdm_args desc = { { 0 } }; |
| int ext_status = 0; |
| struct spdm_data *spdm_data = (struct spdm_data *)devfreq->data; |
| int i; |
| |
| switch (event) { |
| case DEVFREQ_GOV_START: |
| mutex_lock(&devfreqs_lock); |
| list_add(&spdm_data->list, &devfreqs); |
| mutex_unlock(&devfreqs_lock); |
| /* call hyp with config data */ |
| desc.arg[0] = SPDM_CMD_CFG_PORTS; |
| desc.arg[1] = spdm_data->spdm_client; |
| desc.arg[2] = spdm_data->config_data.num_ports; |
| for (i = 0; i < spdm_data->config_data.num_ports; i++) |
| desc.arg[i+3] = spdm_data->config_data.ports[i]; |
| ext_status = spdm_ext_call(&desc, |
| spdm_data->config_data.num_ports + 3); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| |
| desc.arg[0] = SPDM_CMD_CFG_FLTR; |
| desc.arg[1] = spdm_data->spdm_client; |
| desc.arg[2] = spdm_data->config_data.aup; |
| desc.arg[3] = spdm_data->config_data.adown; |
| desc.arg[4] = spdm_data->config_data.bucket_size; |
| ext_status = spdm_ext_call(&desc, 5); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| |
| desc.arg[0] = SPDM_CMD_CFG_PL; |
| desc.arg[1] = spdm_data->spdm_client; |
| for (i = 0; i < SPDM_PL_COUNT - 1; i++) |
| desc.arg[i+2] = spdm_data->config_data.pl_freqs[i]; |
| ext_status = spdm_ext_call(&desc, SPDM_PL_COUNT + 1); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| |
| desc.arg[0] = SPDM_CMD_CFG_REJRATE_LOW; |
| desc.arg[1] = spdm_data->spdm_client; |
| desc.arg[2] = spdm_data->config_data.reject_rate[0]; |
| desc.arg[3] = spdm_data->config_data.reject_rate[1]; |
| ext_status = spdm_ext_call(&desc, 4); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| desc.arg[0] = SPDM_CMD_CFG_REJRATE_MED; |
| desc.arg[1] = spdm_data->spdm_client; |
| desc.arg[2] = spdm_data->config_data.reject_rate[2]; |
| desc.arg[3] = spdm_data->config_data.reject_rate[3]; |
| ext_status = spdm_ext_call(&desc, 4); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| desc.arg[0] = SPDM_CMD_CFG_REJRATE_HIGH; |
| desc.arg[1] = spdm_data->spdm_client; |
| desc.arg[2] = spdm_data->config_data.reject_rate[4]; |
| desc.arg[3] = spdm_data->config_data.reject_rate[5]; |
| ext_status = spdm_ext_call(&desc, 4); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| |
| desc.arg[0] = SPDM_CMD_CFG_RESPTIME_LOW; |
| desc.arg[1] = spdm_data->spdm_client; |
| desc.arg[2] = spdm_data->config_data.response_time_us[0]; |
| desc.arg[3] = spdm_data->config_data.response_time_us[1]; |
| ext_status = spdm_ext_call(&desc, 4); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| desc.arg[0] = SPDM_CMD_CFG_RESPTIME_MED; |
| desc.arg[1] = spdm_data->spdm_client; |
| desc.arg[2] = spdm_data->config_data.response_time_us[2]; |
| desc.arg[3] = spdm_data->config_data.response_time_us[3]; |
| ext_status = spdm_ext_call(&desc, 4); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| desc.arg[0] = SPDM_CMD_CFG_RESPTIME_HIGH; |
| desc.arg[1] = spdm_data->spdm_client; |
| desc.arg[2] = spdm_data->config_data.response_time_us[4]; |
| desc.arg[3] = spdm_data->config_data.response_time_us[5]; |
| ext_status = spdm_ext_call(&desc, 4); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| |
| desc.arg[0] = SPDM_CMD_CFG_CCIRESPTIME_LOW; |
| desc.arg[1] = spdm_data->spdm_client; |
| desc.arg[2] = spdm_data->config_data.cci_response_time_us[0]; |
| desc.arg[3] = spdm_data->config_data.cci_response_time_us[1]; |
| ext_status = spdm_ext_call(&desc, 4); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| desc.arg[0] = SPDM_CMD_CFG_CCIRESPTIME_MED; |
| desc.arg[1] = spdm_data->spdm_client; |
| desc.arg[2] = spdm_data->config_data.cci_response_time_us[2]; |
| desc.arg[3] = spdm_data->config_data.cci_response_time_us[3]; |
| ext_status = spdm_ext_call(&desc, 4); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| desc.arg[0] = SPDM_CMD_CFG_CCIRESPTIME_HIGH; |
| desc.arg[1] = spdm_data->spdm_client; |
| desc.arg[2] = spdm_data->config_data.cci_response_time_us[4]; |
| desc.arg[3] = spdm_data->config_data.cci_response_time_us[5]; |
| ext_status = spdm_ext_call(&desc, 4); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| |
| desc.arg[0] = SPDM_CMD_CFG_MAXCCI; |
| desc.arg[1] = spdm_data->spdm_client; |
| desc.arg[2] = spdm_data->config_data.max_cci_freq; |
| 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); |
| |
| desc.arg[0] = SPDM_CMD_CFG_VOTES; |
| desc.arg[1] = spdm_data->spdm_client; |
| desc.arg[2] = spdm_data->config_data.upstep; |
| desc.arg[3] = spdm_data->config_data.downstep; |
| desc.arg[4] = spdm_data->config_data.max_vote; |
| desc.arg[5] = spdm_data->config_data.up_step_multp; |
| ext_status = spdm_ext_call(&desc, 6); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| |
| /* call hyp enable/commit */ |
| desc.arg[0] = SPDM_CMD_ENABLE; |
| desc.arg[1] = spdm_data->spdm_client; |
| desc.arg[2] = 0; |
| 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); |
| mutex_lock(&devfreqs_lock); |
| /* |
| * the spdm device probe will fail so remove it from |
| * the list to prevent accessing a deleted pointer in |
| * the future |
| */ |
| list_del(&spdm_data->list); |
| mutex_unlock(&devfreqs_lock); |
| return -EINVAL; |
| } |
| spdm_data->enabled = true; |
| devfreq_monitor_start(devfreq); |
| break; |
| |
| case DEVFREQ_GOV_STOP: |
| devfreq_monitor_stop(devfreq); |
| /* find devfreq in list and remove it */ |
| mutex_lock(&devfreqs_lock); |
| list_del(&spdm_data->list); |
| mutex_unlock(&devfreqs_lock); |
| |
| /* call hypvervisor to disable */ |
| desc.arg[0] = SPDM_CMD_DISABLE; |
| desc.arg[1] = spdm_data->spdm_client; |
| ext_status = spdm_ext_call(&desc, 2); |
| if (ext_status) |
| pr_err("External command %u failed with error %u", |
| (int)desc.arg[0], ext_status); |
| spdm_data->enabled = false; |
| break; |
| |
| case DEVFREQ_GOV_INTERVAL: |
| devfreq_interval_update(devfreq, (unsigned int *)data); |
| break; |
| |
| case DEVFREQ_GOV_SUSPEND: |
| devfreq_monitor_suspend(devfreq); |
| break; |
| |
| case DEVFREQ_GOV_RESUME: |
| devfreq_monitor_resume(devfreq); |
| break; |
| |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static struct devfreq_governor spdm_hyp_gov = { |
| .name = "spdm_bw_hyp", |
| .get_target_freq = gov_spdm_hyp_target_bw, |
| .event_handler = gov_spdm_hyp_eh, |
| }; |
| |
| static int probe(struct platform_device *pdev) |
| { |
| int ret = -EINVAL; |
| int *irq = 0; |
| |
| irq = devm_kzalloc(&pdev->dev, sizeof(int), GFP_KERNEL); |
| if (!irq) |
| return -ENOMEM; |
| platform_set_drvdata(pdev, irq); |
| |
| ret = devfreq_add_governor(&spdm_hyp_gov); |
| if (ret) |
| goto nogov; |
| |
| *irq = platform_get_irq_byname(pdev, "spdm-irq"); |
| ret = request_threaded_irq(*irq, isr, threaded_isr, |
| IRQF_TRIGGER_HIGH | IRQF_ONESHOT, |
| spdm_hyp_gov.name, pdev); |
| if (ret) |
| goto no_irq; |
| |
| enable_clocks(); |
| return 0; |
| |
| no_irq: |
| devfreq_remove_governor(&spdm_hyp_gov); |
| nogov: |
| devm_kfree(&pdev->dev, irq); |
| return ret; |
| } |
| |
| static int remove(struct platform_device *pdev) |
| { |
| int *irq = 0; |
| |
| disable_clocks(); |
| irq = platform_get_drvdata(pdev); |
| free_irq(*irq, pdev); |
| devfreq_remove_governor(&spdm_hyp_gov); |
| devm_kfree(&pdev->dev, irq); |
| return 0; |
| } |
| |
| static const struct of_device_id gov_spdm_match[] = { |
| {.compatible = "qcom,gov_spdm_hyp"}, |
| {} |
| }; |
| |
| static struct platform_driver gov_spdm_hyp_drvr = { |
| .driver = { |
| .name = "gov_spdm_hyp", |
| .owner = THIS_MODULE, |
| .of_match_table = gov_spdm_match, |
| }, |
| .probe = probe, |
| .remove = remove, |
| }; |
| |
| static int __init governor_spdm_bw_hyp(void) |
| { |
| return platform_driver_register(&gov_spdm_hyp_drvr); |
| } |
| |
| module_init(governor_spdm_bw_hyp); |
| |
| MODULE_LICENSE("GPL v2"); |