summaryrefslogtreecommitdiff
path: root/drivers/soc
diff options
context:
space:
mode:
authorRajesh Kemisetti <rajeshk@codeaurora.org>2016-12-20 10:47:38 +0530
committerRajesh Kemisetti <rajeshk@codeaurora.org>2017-02-09 16:14:44 +0530
commit381862aa59c91e413cdbfd0772b73b5a81ae48c2 (patch)
tree679d183445b0745818e57eb09158951c9430eefc /drivers/soc
parent2aa89ab3ff59a788321bc6af782d639cfc8dab1f (diff)
soc: qcom: Add support for Cx iPeak limit driver
Implement common driver to limit Cx ipeak based on voting from various clients in multimedia. Change-Id: Ie0a57e49f7a8ba8a4fa3aa7f50dd0947f8e9d11b Signed-off-by: Rajesh Kemisetti <rajeshk@codeaurora.org>
Diffstat (limited to 'drivers/soc')
-rw-r--r--drivers/soc/qcom/Kconfig9
-rw-r--r--drivers/soc/qcom/Makefile1
-rw-r--r--drivers/soc/qcom/cx_ipeak.c202
3 files changed, 212 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 4a315d8f5534..5c5412371e33 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -863,4 +863,13 @@ config QCOM_EARLY_RANDOM
may not be truly random. Select this option to make an early call
to get some random data to put in the pool. If unsure, say N.
+config QCOM_CX_IPEAK
+ bool "Common driver to handle Cx iPeak limitation"
+ help
+ Cx ipeak HW module is used to limit the current drawn by various subsystem
+ blocks on Cx power rail. Each client needs to set their
+ bit in tcsr register if it is going to cross its own threshold. If all
+ clients are going to cross their thresholds then Cx ipeak hw module will raise
+ an interrupt to cDSP block to throttle cDSP fmax.
+
source "drivers/soc/qcom/memshare/Kconfig"
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index f56c6bf1539a..623e389aff1f 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -101,3 +101,4 @@ obj-$(CONFIG_QSEE_IPC_IRQ_BRIDGE) += qsee_ipc_irq_bridge.o
obj-$(CONFIG_WCD_DSP_GLINK) += wcd-dsp-glink.o
obj-$(CONFIG_QCOM_SMCINVOKE) += smcinvoke.o
obj-$(CONFIG_QCOM_EARLY_RANDOM) += early_random.o
+obj-$(CONFIG_QCOM_CX_IPEAK) += cx_ipeak.o
diff --git a/drivers/soc/qcom/cx_ipeak.c b/drivers/soc/qcom/cx_ipeak.c
new file mode 100644
index 000000000000..c5933a285522
--- /dev/null
+++ b/drivers/soc/qcom/cx_ipeak.c
@@ -0,0 +1,202 @@
+/* Copyright (c) 2016-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/module.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/printk.h>
+#include <linux/spinlock.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+
+#include <soc/qcom/cx_ipeak.h>
+
+#define TCSR_CXIP_LM_VOTE_BYPASS_OFFSET 0x4
+#define TCSR_CXIP_LM_VOTE_CLEAR_OFFSET 0x8
+#define TCSR_CXIP_LM_VOTE_SET_OFFSET 0xC
+#define TCSR_CXIP_LM_VOTE_FEATURE_ENABLE_OFFSET 0x10
+#define TCSR_CXIP_LM_TRS_OFFSET 0x24
+
+#define CXIP_POLL_TIMEOUT_US (50 * 1000)
+
+static struct cx_ipeak_device {
+ spinlock_t vote_lock;
+ void __iomem *tcsr_vptr;
+} device_ipeak;
+
+struct cx_ipeak_client {
+ int vote_count;
+ unsigned int vote_mask;
+ struct cx_ipeak_device *dev;
+};
+
+/**
+ * cx_ipeak_register() - allocate client structure and fill device private and
+ * bit details.
+ * @dev_node: device node of the client
+ * @client_name: property name of the client
+ *
+ * Allocate client memory and fill the structure with device private and bit
+ *
+ */
+struct cx_ipeak_client *cx_ipeak_register(struct device_node *dev_node,
+ const char *client_name)
+{
+ struct of_phandle_args cx_spec;
+ struct cx_ipeak_client *client;
+ unsigned int reg_enable, reg_bypass;
+ int ret;
+
+ ret = of_parse_phandle_with_fixed_args(dev_node, client_name,
+ 1, 0, &cx_spec);
+ if (ret)
+ return ERR_PTR(-EINVAL);
+
+ if (!of_device_is_available(cx_spec.np))
+ return NULL;
+
+ if (device_ipeak.tcsr_vptr == NULL)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ if (cx_spec.args[0] > 31)
+ return ERR_PTR(-EINVAL);
+
+ reg_enable = readl_relaxed(device_ipeak.tcsr_vptr +
+ TCSR_CXIP_LM_VOTE_FEATURE_ENABLE_OFFSET);
+ reg_bypass = readl_relaxed(device_ipeak.tcsr_vptr +
+ TCSR_CXIP_LM_VOTE_BYPASS_OFFSET) &
+ BIT(cx_spec.args[0]);
+
+ if (!reg_enable || reg_bypass)
+ return NULL;
+
+ client = kzalloc(sizeof(struct cx_ipeak_client), GFP_KERNEL);
+ if (!client)
+ return ERR_PTR(-ENOMEM);
+
+ client->vote_mask = BIT(cx_spec.args[0]);
+ client->dev = &device_ipeak;
+
+ return client;
+}
+EXPORT_SYMBOL(cx_ipeak_register);
+
+/**
+ * cx_ipeak_unregister() - unregister client
+ * @client: client address to free
+ *
+ * Free the client memory
+ */
+void cx_ipeak_unregister(struct cx_ipeak_client *client)
+{
+ kfree(client);
+}
+EXPORT_SYMBOL(cx_ipeak_unregister);
+
+/*
+ * cx_ipeak_update() - Set/Clear client vote for Cx iPeak limit
+ * manager to throttle cDSP.
+ * @client: client handle.
+ * @vote: True to set the vote and False for reset.
+ *
+ * Receives vote from each client and decides whether to throttle cDSP or not.
+ * This function is NOP for the targets which does not support TCSR Cx iPeak.
+ */
+int cx_ipeak_update(struct cx_ipeak_client *client, bool vote)
+{
+ unsigned int reg_val;
+ int ret = 0;
+
+ /* Check for client and device availability and proceed */
+ if (client == NULL || client->dev->tcsr_vptr == NULL)
+ return ret;
+
+ spin_lock(&client->dev->vote_lock);
+
+ if (vote) {
+ if (client->vote_count == 0) {
+ writel_relaxed(client->vote_mask,
+ client->dev->tcsr_vptr +
+ TCSR_CXIP_LM_VOTE_SET_OFFSET);
+
+ /*
+ * Do a dummy read to give enough time for TRS register
+ * to become 1 when the last client votes.
+ */
+ readl_relaxed(client->dev->tcsr_vptr +
+ TCSR_CXIP_LM_TRS_OFFSET);
+
+ ret = readl_poll_timeout(client->dev->tcsr_vptr +
+ TCSR_CXIP_LM_TRS_OFFSET, reg_val, !reg_val,
+ 0, CXIP_POLL_TIMEOUT_US);
+ if (ret) {
+ writel_relaxed(client->vote_mask,
+ client->dev->tcsr_vptr +
+ TCSR_CXIP_LM_VOTE_CLEAR_OFFSET);
+ goto done;
+ }
+ }
+ client->vote_count++;
+ } else {
+ if (client->vote_count > 0) {
+ client->vote_count--;
+ if (client->vote_count == 0) {
+ writel_relaxed(client->vote_mask,
+ client->dev->tcsr_vptr +
+ TCSR_CXIP_LM_VOTE_CLEAR_OFFSET);
+ }
+ } else
+ ret = -EINVAL;
+ }
+
+done:
+ spin_unlock(&client->dev->vote_lock);
+ return ret;
+}
+EXPORT_SYMBOL(cx_ipeak_update);
+
+static int cx_ipeak_probe(struct platform_device *pdev)
+{
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ device_ipeak.tcsr_vptr = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(device_ipeak.tcsr_vptr))
+ return PTR_ERR(device_ipeak.tcsr_vptr);
+
+ spin_lock_init(&device_ipeak.vote_lock);
+ return 0;
+}
+
+static const struct of_device_id cx_ipeak_match_table[] = {
+ { .compatible = "qcom,cx-ipeak-sdm660"},
+ {}
+};
+
+static struct platform_driver cx_ipeak_platform_driver = {
+ .probe = cx_ipeak_probe,
+ .driver = {
+ .name = "cx_ipeak",
+ .of_match_table = cx_ipeak_match_table,
+ .suppress_bind_attrs = true,
+ }
+};
+
+static int __init cx_ipeak_init(void)
+{
+ return platform_driver_register(&cx_ipeak_platform_driver);
+}
+
+arch_initcall(cx_ipeak_init);