diff options
| author | Rajesh Kemisetti <rajeshk@codeaurora.org> | 2016-12-20 10:47:38 +0530 |
|---|---|---|
| committer | Rajesh Kemisetti <rajeshk@codeaurora.org> | 2017-02-09 16:14:44 +0530 |
| commit | 381862aa59c91e413cdbfd0772b73b5a81ae48c2 (patch) | |
| tree | 679d183445b0745818e57eb09158951c9430eefc /drivers/soc | |
| parent | 2aa89ab3ff59a788321bc6af782d639cfc8dab1f (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/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/soc/qcom/Makefile | 1 | ||||
| -rw-r--r-- | drivers/soc/qcom/cx_ipeak.c | 202 |
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); |
