devfreq: spdm: Add snapshot of devfreq-spdm driver

This is a snapshot of the devfreq-spdm driver as of msm-3.18
'commit fa156811a90f ("ASoC: msm: enable group config for both
tx and rx").

Change-Id: I11fbcbfbca8b9bc30b9b43d08557fd5774e1444d
Signed-off-by: Odelu Kukatla <okukatla@codeaurora.org>
diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
index b8effac..3a2239c 100644
--- a/drivers/devfreq/Kconfig
+++ b/drivers/devfreq/Kconfig
@@ -135,6 +135,15 @@
 	  it can conflict with existing profiling tools. This governor is
 	  unlikely to be useful for other devices.
 
+config DEVFREQ_GOV_SPDM_HYP
+	bool "QTI SPDM Hypervisor Governor"
+	depends on ARCH_QCOM
+	help
+	  Hypervisor based governor for CPU bandwidth voting
+	  for QTI chipsets.
+	  Sets the frequency using a "on-demand" algorithm.
+	  This governor is unlikely to be useful for other devices.
+
 config DEVFREQ_GOV_MEMLAT
 	tristate "HW monitor based governor for device BW"
 	depends on ARM_MEMLAT_MON
@@ -227,6 +236,24 @@
 	  agnostic interface to so that some of the devfreq governors can be
 	  shared across SoCs.
 
+config SPDM_SCM
+	bool "QTI SPDM SCM based call support"
+	depends on DEVFREQ_SPDM
+	help
+	  SPDM driver support the dcvs algorithm logic being accessed via
+	  scm or hvc calls. This adds the support for SPDM interaction to
+          tz via SCM based call. If not selected then Hypervior interaction
+          will be activated.
+
+config DEVFREQ_SPDM
+	bool "QTI SPDM based bandwidth voting"
+	depends on ARCH_QCOM
+	select DEVFREQ_GOV_SPDM_HYP
+	help
+	  This adds the support for SPDM based bandwidth voting on QTI chipsets.
+	  This driver allows any SPDM based client to vote for bandwidth.
+	  Used with the QTI SPDM Hypervisor Governor.
+
 source "drivers/devfreq/event/Kconfig"
 
 endif # PM_DEVFREQ
diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
index f248e02..0202f66 100644
--- a/drivers/devfreq/Makefile
+++ b/drivers/devfreq/Makefile
@@ -15,6 +15,7 @@
 obj-$(CONFIG_QCOM_M4M_HWMON)		+= m4m-hwmon.o
 obj-$(CONFIG_DEVFREQ_GOV_QCOM_BW_HWMON)	+= governor_bw_hwmon.o
 obj-$(CONFIG_DEVFREQ_GOV_QCOM_CACHE_HWMON)	+= governor_cache_hwmon.o
+obj-$(CONFIG_DEVFREQ_GOV_SPDM_HYP)	+= governor_spdm_bw_hyp.o
 obj-$(CONFIG_DEVFREQ_GOV_MEMLAT)       += governor_memlat.o
 
 # DEVFREQ Drivers
@@ -23,6 +24,7 @@
 obj-$(CONFIG_ARM_TEGRA_DEVFREQ)		+= tegra-devfreq.o
 obj-$(CONFIG_QCOM_DEVFREQ_DEVBW)		+= devfreq_devbw.o
 obj-$(CONFIG_DEVFREQ_SIMPLE_DEV)	+= devfreq_simple_dev.o
+obj-$(CONFIG_DEVFREQ_SPDM)		+= devfreq_spdm.o devfreq_spdm_debugfs.o
 
 # DEVFREQ Event Drivers
 obj-$(CONFIG_PM_DEVFREQ_EVENT)		+= event/
diff --git a/drivers/devfreq/devfreq_spdm.c b/drivers/devfreq/devfreq_spdm.c
new file mode 100644
index 0000000..3290a2a
--- /dev/null
+++ b/drivers/devfreq/devfreq_spdm.c
@@ -0,0 +1,444 @@
+/*
+ *Copyright (c) 2014-2015, 2017, 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;
+}
+
+#ifdef CONFIG_MSM_HVC
+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;
+}
+#endif
+
+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");
diff --git a/drivers/devfreq/devfreq_spdm.h b/drivers/devfreq/devfreq_spdm.h
new file mode 100644
index 0000000..1e5ab03
--- /dev/null
+++ b/drivers/devfreq/devfreq_spdm.h
@@ -0,0 +1,131 @@
+/*
+ *Copyright (c) 2014-2015, 2017, 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.
+ */
+
+#ifndef DEVFREQ_SPDM_H
+#define DEVFREQ_SPDM_H
+
+#include <linux/list.h>
+#ifdef CONFIG_MSM_HVC
+#include <soc/qcom/hvc.h>
+#endif
+#include <soc/qcom/scm.h>
+
+enum pl_levels { SPDM_PL1, SPDM_PL2, SPDM_PL3, SPDM_PL_COUNT };
+enum actions { SPDM_UP, SPDM_DOWN };
+enum spdm_client { SPDM_CLIENT_CPU, SPDM_CLIENT_GPU, SPDM_CLIENT_COUNT };
+
+struct spdm_config_data {
+	/* in MB/s */
+	u32 upstep;
+	u32 downstep;
+	u32 up_step_multp;
+
+	u32 num_ports;
+	u32 *ports;
+	u32 aup;
+	u32 adown;
+	u32 bucket_size;
+
+	/*
+	 * If We define n PL levels we need n-1 frequencies to tell
+	 * where to change from one pl to another
+	 */
+	/* hz */
+	u32 pl_freqs[SPDM_PL_COUNT - 1];
+	/*
+	 * We have a low threshold and a high threhold for each pl to support
+	 * the two port solution so we need twice as many entries as
+	 * performance levels
+	 */
+	/* in 100th's of a percent */
+	u32 reject_rate[SPDM_PL_COUNT * 2];
+	u32 response_time_us[SPDM_PL_COUNT * 2];
+	u32 cci_response_time_us[SPDM_PL_COUNT * 2];
+	/* hz */
+	u32 max_cci_freq;
+	/* in MB/s */
+	u32 max_vote;
+
+};
+
+struct spdm_data {
+	/* bus scaling data */
+	int cur_idx;
+	struct msm_bus_scale_pdata *pdata;
+	u32 bus_scale_client_id;
+	/* in mb/s */
+	u32 new_bw;
+
+	/* devfreq data */
+	struct devfreq *devfreq;
+	struct devfreq_dev_profile *profile;
+	unsigned long action;
+	int window;
+	struct clk *cci_clk;
+
+	/* spdm hw/gov data */
+	struct spdm_config_data config_data;
+
+	enum spdm_client spdm_client;
+	/* list used by governor to keep track of spdm devices */
+	struct list_head list;
+
+	struct dentry *debugfs_dir;
+
+	bool enabled;
+};
+
+extern void spdm_init_debugfs(struct device *dev);
+extern void spdm_remove_debugfs(struct spdm_data *data);
+
+#define SPDM_HYP_FNID 5
+#define SPDM_SCM_SVC_ID 0x9
+#define SPDM_SCM_CMD_ID 0x4
+#define SPDM_TZ_VERSION 0x20000 /* TZ SPDM driver version */
+/* SPDM CMD ID's for hypervisor/SCM */
+#define SPDM_CMD_GET_VERSION 0
+#define SPDM_CMD_GET_BW_ALL 1
+#define SPDM_CMD_GET_BW_SPECIFIC 2
+#define SPDM_CMD_ENABLE 3
+#define SPDM_CMD_DISABLE 4
+#define SPDM_CMD_CFG_PORTS 5
+#define SPDM_CMD_CFG_FLTR 6
+#define SPDM_CMD_CFG_PL 7
+#define SPDM_CMD_CFG_REJRATE_LOW 8
+#define SPDM_CMD_CFG_REJRATE_MED 9
+#define SPDM_CMD_CFG_REJRATE_HIGH 10
+#define SPDM_CMD_CFG_RESPTIME_LOW 11
+#define SPDM_CMD_CFG_RESPTIME_MED 12
+#define SPDM_CMD_CFG_RESPTIME_HIGH 13
+#define SPDM_CMD_CFG_CCIRESPTIME_LOW 14
+#define SPDM_CMD_CFG_CCIRESPTIME_MED 15
+#define SPDM_CMD_CFG_CCIRESPTIME_HIGH 16
+#define SPDM_CMD_CFG_MAXCCI 17
+#define SPDM_CMD_CFG_VOTES 18
+
+#define SPDM_MAX_ARGS 6
+#define SPDM_MAX_RETS 3
+
+struct spdm_args {
+	u64 arg[SPDM_MAX_ARGS];
+	u64 ret[SPDM_MAX_RETS];
+};
+
+#ifdef CONFIG_SPDM_SCM
+extern int __spdm_scm_call(struct spdm_args *args, int num_args);
+#define spdm_ext_call __spdm_scm_call
+#else
+extern int __spdm_hyp_call(struct spdm_args *args, int num_args);
+#define spdm_ext_call __spdm_hyp_call
+#endif
+#endif
diff --git a/drivers/devfreq/devfreq_spdm_debugfs.c b/drivers/devfreq/devfreq_spdm_debugfs.c
new file mode 100644
index 0000000..4e49d5b
--- /dev/null
+++ b/drivers/devfreq/devfreq_spdm_debugfs.c
@@ -0,0 +1,848 @@
+/*
+ *Copyright (c) 2014-2015, 2017, 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/debugfs.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/msm-bus.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include "devfreq_spdm.h"
+#include "governor.h"
+
+static int spdm_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+	return 0;
+}
+
+static char buf[PAGE_SIZE];
+
+static ssize_t enable_write(struct file *file, const char __user *data,
+			    size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	int i;
+	int next_idx;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, data, size)) {
+		goto err;
+		size = -EINVAL;
+	}
+
+	if (sscanf(buf, "%u\n", &i) != 1) {
+		size = -EINVAL;
+		goto err;
+	}
+	i = !!i;
+
+	if (i == spdm_data->enabled)
+		goto out;
+
+	spdm_data->devfreq->governor->event_handler(spdm_data->devfreq,
+						    i ? DEVFREQ_GOV_START :
+						    DEVFREQ_GOV_STOP, NULL);
+
+	if (!i) {
+		next_idx = spdm_data->cur_idx + 1;
+		next_idx = next_idx % 2;
+
+		for (i = 0; i < spdm_data->pdata->usecase[next_idx].num_paths;
+		     i++)
+			spdm_data->pdata->usecase[next_idx].vectors[i].ab = 0;
+
+		spdm_data->cur_idx = next_idx;
+		msm_bus_scale_client_update_request
+		    (spdm_data->bus_scale_client_id, spdm_data->cur_idx);
+	}
+
+out:
+	*offset += size;
+err:
+	memset(buf, 0, sizeof(buf));
+	return size;
+}
+
+static ssize_t enable_read(struct file *file, char __user *data,
+			   size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	int len = 32;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	len = scnprintf(buf, size, "%u\n", spdm_data->enabled);
+	len = simple_read_from_buffer(data, size, offset, buf, len);
+
+	memset(buf, 0, sizeof(buf));
+	return len;
+}
+
+static const struct file_operations enable_fops = {
+	.open = spdm_open,
+	.write = enable_write,
+	.read = enable_read,
+};
+
+static ssize_t pl_write(struct file *file, const char __user *data,
+			size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	struct spdm_args desc = { { 0 } };
+	int ext_status = 0;
+	int i;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, data, size)) {
+		size = -EINVAL;
+		goto out;
+	}
+
+	if (sscanf(buf, "%u %u\n", &spdm_data->config_data.pl_freqs[0],
+	       &spdm_data->config_data.pl_freqs[1]) != 2) {
+		size = -EINVAL;
+		goto out;
+	}
+
+	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);
+	*offset += size;
+out:
+	memset(buf, 0, sizeof(buf));
+	return size;
+
+}
+
+static ssize_t pl_read(struct file *file, char __user *data,
+		       size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	int i = 32;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	i = scnprintf(buf, size, "%u %u\n", spdm_data->config_data.pl_freqs[0],
+		     spdm_data->config_data.pl_freqs[1]);
+	i = simple_read_from_buffer(data, size, offset, buf, i);
+
+	memset(buf, 0, sizeof(buf));
+	return i;
+}
+
+static const struct file_operations pl_fops = {
+	.open = spdm_open,
+	.write = pl_write,
+	.read = pl_read,
+};
+
+static ssize_t rejrate_low_write(struct file *file, const char __user *data,
+				 size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	struct spdm_args desc = { { 0 } };
+	int ext_status = 0;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, data, size)) {
+		size = -EINVAL;
+		goto out;
+	}
+
+	if (sscanf(buf, "%u %u\n", &spdm_data->config_data.reject_rate[0],
+	       &spdm_data->config_data.reject_rate[1]) != 2) {
+		size = -EINVAL;
+		goto out;
+	}
+
+	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);
+	*offset += size;
+out:
+	memset(buf, 0, sizeof(buf));
+	return size;
+}
+
+static ssize_t rejrate_low_read(struct file *file, char __user *data,
+				size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	int i = 32;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	i = scnprintf(buf, size, "%u %u\n",
+		     spdm_data->config_data.reject_rate[0],
+		     spdm_data->config_data.reject_rate[1]);
+
+	i = simple_read_from_buffer(data, size, offset, buf, i);
+
+	memset(buf, 0, sizeof(buf));
+	return i;
+}
+
+static const struct file_operations rrl_fops = {
+	.open = spdm_open,
+	.write = rejrate_low_write,
+	.read = rejrate_low_read,
+};
+
+static ssize_t rejrate_med_write(struct file *file, const char __user *data,
+				 size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	struct spdm_args desc = { { 0 } };
+	int ext_status = 0;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, data, size)) {
+		size = -EINVAL;
+		goto out;
+	}
+	if (sscanf(buf, "%u %u\n", &spdm_data->config_data.reject_rate[2],
+	       &spdm_data->config_data.reject_rate[3]) != 2) {
+		size = -EINVAL;
+		goto out;
+	}
+
+	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);
+	*offset += size;
+out:
+	memset(buf, 0, sizeof(buf));
+	return size;
+}
+
+static ssize_t rejrate_med_read(struct file *file, char __user *data,
+				size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	int i = 32;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	i = scnprintf(buf, size, "%u %u\n",
+		     spdm_data->config_data.reject_rate[2],
+		     spdm_data->config_data.reject_rate[3]);
+
+	i = simple_read_from_buffer(data, size, offset, buf, i);
+	memset(buf, 0, sizeof(buf));
+	return i;
+}
+
+static const struct file_operations rrm_fops = {
+	.open = spdm_open,
+	.write = rejrate_med_write,
+	.read = rejrate_med_read,
+};
+
+static ssize_t rejrate_high_write(struct file *file, const char __user *data,
+				  size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	struct spdm_args desc = { { 0 } };
+	int ext_status = 0;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, data, size)) {
+		size = -EINVAL;
+		goto out;
+	}
+	if (sscanf(buf, "%u %u\n", &spdm_data->config_data.reject_rate[4],
+	       &spdm_data->config_data.reject_rate[5]) != 2) {
+		size = -EINVAL;
+		goto out;
+	}
+
+	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);
+	*offset += size;
+out:
+	memset(buf, 0, sizeof(buf));
+	return size;
+}
+
+static ssize_t rejrate_high_read(struct file *file, char __user *data,
+				 size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	int i = 32;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	i = scnprintf(buf, size, "%u %u\n",
+		     spdm_data->config_data.reject_rate[4],
+		     spdm_data->config_data.reject_rate[5]);
+
+	i = simple_read_from_buffer(data, size, offset, buf, i);
+	memset(buf, 0, sizeof(buf));
+	return i;
+}
+
+static const struct file_operations rrh_fops = {
+	.open = spdm_open,
+	.write = rejrate_high_write,
+	.read = rejrate_high_read,
+};
+
+static ssize_t resptime_low_write(struct file *file, const char __user *data,
+				  size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	struct spdm_args desc = { { 0 } };
+	int ext_status = 0;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, data, size)) {
+		size = -EINVAL;
+		goto out;
+	}
+	if (sscanf(buf, "%u %u\n", &spdm_data->config_data.response_time_us[0],
+	       &spdm_data->config_data.response_time_us[1]) != 2) {
+		size = -EINVAL;
+		goto out;
+	}
+
+	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);
+	*offset += size;
+out:
+	memset(buf, 0, sizeof(buf));
+	return size;
+}
+
+static ssize_t resptime_low_read(struct file *file, char __user *data,
+				 size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	int i = 32;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	i = scnprintf(buf, size, "%u %u\n",
+		     spdm_data->config_data.response_time_us[0],
+		     spdm_data->config_data.response_time_us[1]);
+
+	i = simple_read_from_buffer(data, size, offset, buf, i);
+	memset(buf, 0, sizeof(buf));
+	return i;
+}
+
+static const struct file_operations rtl_fops = {
+	.open = spdm_open,
+	.write = resptime_low_write,
+	.read = resptime_low_read,
+};
+
+static ssize_t resptime_med_write(struct file *file, const char __user *data,
+				  size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	struct spdm_args desc = { { 0 } };
+	int ext_status = 0;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, data, size)) {
+		size = -EINVAL;
+		goto out;
+	}
+	if (sscanf(buf, "%u %u\n", &spdm_data->config_data.response_time_us[2],
+	       &spdm_data->config_data.response_time_us[3]) != 2) {
+		size = -EINVAL;
+		goto out;
+	}
+
+	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);
+	*offset += size;
+out:
+	memset(buf, 0, sizeof(buf));
+	return size;
+}
+
+static ssize_t resptime_med_read(struct file *file, char __user *data,
+				 size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	int i = 32;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	i = scnprintf(buf, size, "%u %u\n",
+		     spdm_data->config_data.response_time_us[2],
+		     spdm_data->config_data.response_time_us[3]);
+
+	i = simple_read_from_buffer(data, size, offset, buf, i);
+	memset(buf, 0, sizeof(buf));
+	return i;
+}
+
+static const struct file_operations rtm_fops = {
+	.open = spdm_open,
+	.write = resptime_med_write,
+	.read = resptime_med_read,
+};
+
+static ssize_t resptime_high_write(struct file *file, const char __user *data,
+				   size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	struct spdm_args desc = { { 0 } };
+	int ext_status = 0;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, data, size)) {
+		size = -EINVAL;
+		goto out;
+	}
+	if (sscanf(buf, "%u %u\n", &spdm_data->config_data.response_time_us[4],
+	       &spdm_data->config_data.response_time_us[5]) != 2) {
+		size = -EINVAL;
+		goto out;
+	}
+
+	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);
+	*offset += size;
+out:
+	memset(buf, 0, sizeof(buf));
+	return size;
+}
+
+static ssize_t resptime_high_read(struct file *file, char __user *data,
+				  size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	int i = 32;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	i = scnprintf(buf, size, "%u %u\n",
+		     spdm_data->config_data.response_time_us[4],
+		     spdm_data->config_data.response_time_us[5]);
+
+	i = simple_read_from_buffer(data, size, offset, buf, i);
+	memset(buf, 0, sizeof(buf));
+	return i;
+}
+
+static const struct file_operations rth_fops = {
+	.open = spdm_open,
+	.write = resptime_high_write,
+	.read = resptime_high_read,
+};
+
+static ssize_t cciresptime_low_write(struct file *file,
+				     const char __user *data, size_t size,
+				     loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	struct spdm_args desc = { { 0 } };
+	int ext_status = 0;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, data, size)) {
+		size = -EINVAL;
+		goto out;
+	}
+	if (sscanf(buf, "%u %u\n",
+		   &spdm_data->config_data.cci_response_time_us[0],
+		   &spdm_data->config_data.cci_response_time_us[1]) != 2) {
+		size = -EINVAL;
+		goto out;
+	}
+
+	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);
+	*offset += size;
+out:
+	memset(buf, 0, sizeof(buf));
+	return size;
+}
+
+static ssize_t cciresptime_low_read(struct file *file, char __user *data,
+				    size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	int i = 32;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	i = scnprintf(buf, size, "%u %u\n",
+		     spdm_data->config_data.cci_response_time_us[0],
+		     spdm_data->config_data.cci_response_time_us[1]);
+
+	i = simple_read_from_buffer(data, size, offset, buf, i);
+	memset(buf, 0, sizeof(buf));
+	return i;
+}
+
+static const struct file_operations ccil_fops = {
+	.open = spdm_open,
+	.write = cciresptime_low_write,
+	.read = cciresptime_low_read,
+};
+
+static ssize_t cciresptime_med_write(struct file *file,
+				     const char __user *data, size_t size,
+				     loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	struct spdm_args desc = { { 0 } };
+	int ext_status = 0;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, data, size)) {
+		size = -EINVAL;
+		goto out;
+	}
+	if (sscanf(buf, "%u %u\n",
+		   &spdm_data->config_data.cci_response_time_us[2],
+		   &spdm_data->config_data.cci_response_time_us[3]) != 2) {
+		size = -EINVAL;
+		goto out;
+	}
+
+	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);
+	*offset += size;
+out:
+	memset(buf, 0, sizeof(buf));
+	return size;
+}
+
+static ssize_t cciresptime_med_read(struct file *file, char __user *data,
+				    size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	int i = 32;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	i = scnprintf(buf, size, "%u %u\n",
+		     spdm_data->config_data.cci_response_time_us[2],
+		     spdm_data->config_data.cci_response_time_us[3]);
+
+	i = simple_read_from_buffer(data, size, offset, buf, i);
+	memset(buf, 0, sizeof(buf));
+	return i;
+}
+
+static const struct file_operations ccim_fops = {
+	.open = spdm_open,
+	.write = cciresptime_med_write,
+	.read = cciresptime_med_read,
+};
+
+static ssize_t cciresptime_high_write(struct file *file,
+				      const char __user *data,
+				      size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	struct spdm_args desc = { { 0 } };
+	int ext_status = 0;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, data, size)) {
+		size = -EINVAL;
+		goto out;
+	}
+	if (sscanf(buf, "%u %u\n",
+		   &spdm_data->config_data.cci_response_time_us[4],
+		   &spdm_data->config_data.cci_response_time_us[5]) != 2){
+		size = -EINVAL;
+		goto out;
+	}
+
+	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);
+	*offset += size;
+out:
+	memset(buf, 0, sizeof(buf));
+	return size;
+}
+
+static ssize_t cciresptime_high_read(struct file *file, char __user *data,
+				     size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	int i = 32;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	i = scnprintf(buf, size, "%u %u\n",
+		     spdm_data->config_data.cci_response_time_us[4],
+		     spdm_data->config_data.cci_response_time_us[5]);
+
+	i = simple_read_from_buffer(data, size, offset, buf, i);
+	memset(buf, 0, sizeof(buf));
+	return i;
+}
+
+static const struct file_operations ccih_fops = {
+	.open = spdm_open,
+	.write = cciresptime_high_write,
+	.read = cciresptime_high_read,
+};
+
+static ssize_t cci_max_write(struct file *file, const char __user *data,
+			     size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	struct spdm_args desc = { { 0 } };
+	int ext_status = 0;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, data, size)) {
+		size = -EINVAL;
+		goto out;
+	}
+	if (sscanf(buf, "%u\n", &spdm_data->config_data.max_cci_freq) != 1) {
+		size = -EINVAL;
+		goto out;
+	}
+
+	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);
+	*offset += size;
+out:
+	memset(buf, 0, sizeof(buf));
+	return size;
+}
+
+static ssize_t cci_max_read(struct file *file, char __user *data,
+			    size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	int i = 32;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	i = scnprintf(buf, size, "%u\n", spdm_data->config_data.max_cci_freq);
+
+	i = simple_read_from_buffer(data, size, offset, buf, i);
+	memset(buf, 0, sizeof(buf));
+	return i;
+}
+
+static const struct file_operations ccimax_fops = {
+	.open = spdm_open,
+	.write = cci_max_write,
+	.read = cci_max_read,
+};
+
+static ssize_t vote_cfg_write(struct file *file, const char __user *data,
+			      size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	struct spdm_args desc = { { 0 } };
+	int ext_status = 0;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, data, size)) {
+		size = -EINVAL;
+		goto out;
+	}
+	if (sscanf(buf, "%u %u %u %u\n", &spdm_data->config_data.upstep,
+	       &spdm_data->config_data.downstep,
+	       &spdm_data->config_data.max_vote,
+	       &spdm_data->config_data.up_step_multp) != 4) {
+		size = -EINVAL;
+		goto out;
+	}
+
+	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);
+	*offset += size;
+out:
+	memset(buf, 0, sizeof(buf));
+	return size;
+}
+
+static ssize_t vote_cfg_read(struct file *file, char __user *data,
+			     size_t size, loff_t *offset)
+{
+	struct spdm_data *spdm_data = file->private_data;
+	int i = 32;
+
+	if (size > sizeof(buf))
+		return -EINVAL;
+
+	i = scnprintf(buf, size, "%u %u %u %u\n",
+		     spdm_data->config_data.upstep,
+		     spdm_data->config_data.downstep,
+		     spdm_data->config_data.max_vote,
+		     spdm_data->config_data.up_step_multp);
+
+	i = simple_read_from_buffer(data, size, offset, buf, i);
+	memset(buf, 0, sizeof(buf));
+	return i;
+}
+
+static const struct file_operations vote_fops = {
+	.open = spdm_open,
+	.write = vote_cfg_write,
+	.read = vote_cfg_read,
+};
+
+void spdm_init_debugfs(struct device *dev)
+{
+	struct spdm_data *data = 0;
+
+	data = dev_get_drvdata(dev);
+	data->debugfs_dir = debugfs_create_dir(dev_name(dev), NULL);
+
+	debugfs_create_file("enable", 0600, data->debugfs_dir, data,
+			    &enable_fops);
+	debugfs_create_file("pl_freqs", 0600, data->debugfs_dir, data,
+			    &pl_fops);
+	debugfs_create_file("rej_rate_low", 0600, data->debugfs_dir, data,
+			    &rrl_fops);
+	debugfs_create_file("rej_rate_med", 0600, data->debugfs_dir, data,
+			    &rrm_fops);
+	debugfs_create_file("rej_rate_high", 0600, data->debugfs_dir, data,
+			    &rrh_fops);
+	debugfs_create_file("resp_time_low", 0600, data->debugfs_dir, data,
+			    &rtl_fops);
+	debugfs_create_file("resp_time_med", 0600, data->debugfs_dir, data,
+			    &rtm_fops);
+	debugfs_create_file("resp_time_high", 0600, data->debugfs_dir, data,
+			    &rth_fops);
+	debugfs_create_file("cci_resp_time_low", 0600, data->debugfs_dir, data,
+			    &ccil_fops);
+	debugfs_create_file("cci_resp_time_med", 0600, data->debugfs_dir, data,
+			    &ccim_fops);
+	debugfs_create_file("cci_resp_time_high", 0600, data->debugfs_dir,
+			    data, &ccih_fops);
+	debugfs_create_file("cci_max", 0600, data->debugfs_dir, data,
+			    &ccimax_fops);
+	debugfs_create_file("vote_cfg", 0600, data->debugfs_dir, data,
+			    &vote_fops);
+}
+
+void spdm_remove_debugfs(struct spdm_data *data)
+{
+	debugfs_remove_recursive(data->debugfs_dir);
+}
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/devfreq/governor_spdm_bw_hyp.c b/drivers/devfreq/governor_spdm_bw_hyp.c
new file mode 100644
index 0000000..5751ab6
--- /dev/null
+++ b/drivers/devfreq/governor_spdm_bw_hyp.c
@@ -0,0 +1,419 @@
+/*
+ *Copyright (c) 2014-2017, 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 (!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 (!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");