From e27a837f09cc3187be7f04efc2003713632beb31 Mon Sep 17 00:00:00 2001 From: Manaf Meethalavalappu Pallikunhi Date: Tue, 8 Jun 2021 19:30:02 +0530 Subject: [PATCH] soc: qcom: Add snapshot for Cx iPeak limit driver Add snapshot of common driver to limit Cx ipeak based on voting from various clients in multimedia. Change-Id: I800cc3a08783f9f33761e1a68a2c3072c7294bc0 Signed-off-by: Manaf Meethalavalappu Pallikunhi --- drivers/soc/qcom/Kconfig | 9 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/cx_ipeak.c | 545 ++++++++++++++++++++++++++++++++++++ include/soc/qcom/cx_ipeak.h | 55 ++++ 4 files changed, 610 insertions(+) create mode 100644 drivers/soc/qcom/cx_ipeak.c create mode 100644 include/soc/qcom/cx_ipeak.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 69e47acd8f51..dc81f16da5e0 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -1062,6 +1062,15 @@ config QCOM_WDT_CORE watchdog module must be loaded along with this for watchdog functionality. +config QCOM_CX_IPEAK + tristate "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. + config QCOM_SOC_WATCHDOG tristate "Qualcomm Technologies, Inc. Soc Watchdog" depends on QCOM_WDT_CORE diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index d6fbdcb8fb12..c2eb202f561a 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -120,6 +120,7 @@ obj-$(CONFIG_MSM_SLATECOM_INTERFACE) += slatecom_interface.o obj-$(CONFIG_QCOM_AOP_SET_DDR) += aop-set-ddr.o obj-$(CONFIG_CPU_V7) += idle-v7.o obj-$(CONFIG_DUMP_XBL_LOG) += dump_boot_log.o +obj-$(CONFIG_QCOM_CX_IPEAK) += cx_ipeak.o # GNSS driver obj-$(CONFIG_GNSS_SIRF) += gnsssirf/ obj-$(CONFIG_MSM_SEB) += slate_events_bridge.o diff --git a/drivers/soc/qcom/cx_ipeak.c b/drivers/soc/qcom/cx_ipeak.c new file mode 100644 index 000000000000..72d7ecac9b02 --- /dev/null +++ b/drivers/soc/qcom/cx_ipeak.c @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2021, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TCSR_CXIP_LM_VOTE_FEATURE_ENABLE_OFFSET 0x10 + +/* v1 register set */ +#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_TRS_OFFSET 0x24 + +/* v2 register set */ +#define TCSR_CXIP_LM_VOTE_CLIENTx_BYPASS_OFFSET 0x4 +#define TCSR_CXIP_LM_DANGER_OFFSET 0x24 + +#define CXIP_CLIENT_OFFSET 0x1000 +#define CXIP_CLIENT10_OFFSET 0x3000 +#define CXIP_VICTIM_OFFSET 0xB000 + +#define CXIP_POLL_TIMEOUT_US (50 * 1000) + +#define CXIP_VICTIMS 3 +#define VICTIM_ENTRIES 3 + +struct cx_ipeak_client; + +struct cx_ipeak_core_ops { + int (*update)(struct cx_ipeak_client *client, bool vote); + struct cx_ipeak_client* (*register_client)(int client_id); +}; + +static struct cx_ipeak_victims { + u32 client_id; + u32 victim_id; + u32 freq_limit; + void *data; + cx_ipeak_victim_fn victim_cb; + struct cx_ipeak_client *client; +} victim_list[CXIP_VICTIMS]; + +static struct cx_ipeak_device { + struct platform_device *pdev; + struct mutex vote_lock; + struct mutex throttle_lock; + void __iomem *tcsr_vptr; + struct cx_ipeak_core_ops *core_ops; + u32 victims_count; + int danger_intr_num; + int safe_intr_num; +} device_ipeak; + +struct cx_ipeak_client { + u32 vote_count; + unsigned int offset; + u32 client_id; + bool danger_assert; + struct cx_ipeak_device *dev; +}; + + +/** + * cx_ipeak_register() - allocate client structure and fill device private and + * offset 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 = NULL; + int ret; + + ret = of_parse_phandle_with_args(dev_node, client_name, + "#size-cells", 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); + + if (device_ipeak.core_ops) + client = device_ipeak.core_ops->register_client + (cx_spec.args[0]); + + client->client_id = cx_spec.args[0]; + + return client; +} +EXPORT_SYMBOL(cx_ipeak_register); + +static struct cx_ipeak_client *cx_ipeak_register_v1(int client_id) +{ + struct cx_ipeak_client *client; + unsigned int reg_enable, reg_bypass; + void __iomem *vptr = device_ipeak.tcsr_vptr; + + reg_enable = readl_relaxed(device_ipeak.tcsr_vptr + + TCSR_CXIP_LM_VOTE_FEATURE_ENABLE_OFFSET); + reg_bypass = readl_relaxed(vptr + + TCSR_CXIP_LM_VOTE_BYPASS_OFFSET) & + BIT(client_id); + if (!reg_enable || reg_bypass) + return NULL; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return ERR_PTR(-ENOMEM); + + client->offset = BIT(client_id); + client->dev = &device_ipeak; + + return client; +} + +static struct cx_ipeak_client *cx_ipeak_register_v2(int client_id) +{ + unsigned int reg_bypass, reg_enable; + struct cx_ipeak_client *client; + unsigned int client_offset = 0; + void __iomem *vptr = device_ipeak.tcsr_vptr; + int i; + + for (i = 0; i <= client_id; i++) + client_offset += CXIP_CLIENT_OFFSET; + + if (client_id >= 10) + client_offset += CXIP_CLIENT10_OFFSET; + + reg_enable = readl_relaxed(device_ipeak.tcsr_vptr + + TCSR_CXIP_LM_VOTE_FEATURE_ENABLE_OFFSET); + reg_bypass = readl_relaxed(vptr + client_offset + + TCSR_CXIP_LM_VOTE_CLIENTx_BYPASS_OFFSET) & + BIT(0); + + if (!reg_enable || reg_bypass) + return NULL; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return ERR_PTR(-ENOMEM); + + client->offset = client_offset; + client->dev = &device_ipeak; + + return client; +} + +/** + * cx_ipeak_victim_register - victim registration API to handle + * the cx ipeak hw interrupts (danger/safe) to throttle freq. + * @client: cx ipeak client + * @victim_cb: callback function of victim + * @data: data to be passed to victim while handling irq + */ +int cx_ipeak_victim_register(struct cx_ipeak_client *client, + cx_ipeak_victim_fn victim_cb, void *data) +{ + int i = 0; + + if (!victim_cb) + return -EINVAL; + + for (i = 0; i < device_ipeak.victims_count; i++) + if (client->client_id == victim_list[i].client_id) { + victim_list[i].victim_cb = victim_cb; + victim_list[i].data = data; + victim_list[i].client = client; + return 0; + } + + return -ENOENT; +} +EXPORT_SYMBOL(cx_ipeak_victim_register); + +/** + * cx_ipeak_victim_unregister - unregister victim client from + * cx_ipeak driver. + * @client: cx ipeak client + */ + +void cx_ipeak_victim_unregister(struct cx_ipeak_client *client) +{ + int i = 0; + + for (i = 0; i < device_ipeak.victims_count; i++) + if (client->client_id == victim_list[i].client_id) { + victim_list[i].victim_cb = NULL; + victim_list[i].data = NULL; + victim_list[i].client = NULL; + } +} +EXPORT_SYMBOL(cx_ipeak_victim_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) +{ + /* Check for client and device availability and proceed */ + if (!client) + return 0; + + if (!client->dev || !client->dev->core_ops || !client->dev->tcsr_vptr) + return -EINVAL; + + return client->dev->core_ops->update(client, vote); +} +EXPORT_SYMBOL(cx_ipeak_update); + +static int cx_ipeak_update_v1(struct cx_ipeak_client *client, bool vote) +{ + unsigned int reg_val; + int ret = 0; + + mutex_lock(&client->dev->vote_lock); + + if (vote) { + if (client->vote_count == 0) { + writel_relaxed(client->offset, + 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->offset, + 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->offset, + client->dev->tcsr_vptr + + TCSR_CXIP_LM_VOTE_CLEAR_OFFSET); + } + } else + ret = -EINVAL; + } + +done: + mutex_unlock(&client->dev->vote_lock); + return ret; +} + +static int cx_ipeak_update_v2(struct cx_ipeak_client *client, bool vote) +{ + u32 reg_val; + int ret = 0; + + mutex_lock(&client->dev->vote_lock); + + if (vote) { + if (client->vote_count == 0) { + writel_relaxed(BIT(0), + client->dev->tcsr_vptr + + client->offset); + + ret = readl_poll_timeout( + client->dev->tcsr_vptr + + TCSR_CXIP_LM_DANGER_OFFSET, + reg_val, !reg_val || + client->danger_assert, + 0, CXIP_POLL_TIMEOUT_US); + /* + * If poll exits due to danger assert condition return + * error to client to avoid voting. + */ + if (client->danger_assert) + ret = -ETIMEDOUT; + + if (ret) { + writel_relaxed(0, + client->dev->tcsr_vptr + + client->offset); + goto done; + } + } + client->vote_count++; + } else { + if (client->vote_count > 0) { + client->vote_count--; + if (client->vote_count == 0) { + writel_relaxed(0, + client->dev->tcsr_vptr + + client->offset); + } + } else { + ret = -EINVAL; + } + } + +done: + mutex_unlock(&client->dev->vote_lock); + return ret; +} + +static irqreturn_t cx_ipeak_irq_soft_handler(int irq, void *data) +{ + int i; + irqreturn_t ret = IRQ_NONE; + + mutex_lock(&device_ipeak.throttle_lock); + + for (i = 0; i < device_ipeak.victims_count; i++) { + cx_ipeak_victim_fn victim_cb = victim_list[i].victim_cb; + struct cx_ipeak_client *victim_client = victim_list[i].client; + + if (!victim_cb || !victim_client) + continue; + + if (irq == device_ipeak.danger_intr_num) { + + victim_client->danger_assert = true; + + /* + * To set frequency limit at victim client + * side in danger interrupt case + */ + + ret = victim_cb(victim_list[i].data, + victim_list[i].freq_limit); + + if (ret) { + dev_err(&device_ipeak.pdev->dev, + "Unable to throttle client:%d freq:%d\n", + victim_list[i].client_id, + victim_list[i].freq_limit); + victim_client->danger_assert = false; + ret = IRQ_HANDLED; + goto done; + } + + writel_relaxed(1, (device_ipeak.tcsr_vptr + + CXIP_VICTIM_OFFSET + + ((victim_list[i].victim_id)* + CXIP_CLIENT_OFFSET))); + + ret = IRQ_HANDLED; + } else if (irq == device_ipeak.safe_intr_num) { + victim_client->danger_assert = false; + /* + * To remove frequency limit at victim client + * side in safe interrupt case + */ + ret = victim_cb(victim_list[i].data, 0); + + if (ret) + dev_err(&device_ipeak.pdev->dev, "Unable to remove freq limit client:%d\n", + victim_list[i].client_id); + + writel_relaxed(0, (device_ipeak.tcsr_vptr + + CXIP_VICTIM_OFFSET + + ((victim_list[i].victim_id)* + CXIP_CLIENT_OFFSET))); + ret = IRQ_HANDLED; + } + } +done: + mutex_unlock(&device_ipeak.throttle_lock); + return ret; +} + +int cx_ipeak_request_irq(struct platform_device *pdev, const char *name, + irq_handler_t handler, irq_handler_t thread_fn, void *data) +{ + int ret, num = platform_get_irq_byname(pdev, name); + + if (num < 0) + return num; + + ret = devm_request_threaded_irq(&pdev->dev, num, handler, thread_fn, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, name, data); + + if (ret) + dev_err(&pdev->dev, "Unable to get interrupt %s: %d\n", + name, ret); + + return ret ? ret : num; +} + +/** + * 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); + +struct cx_ipeak_core_ops core_ops_v1 = { + .update = cx_ipeak_update_v1, + .register_client = cx_ipeak_register_v1, +}; + +struct cx_ipeak_core_ops core_ops_v2 = { + .update = cx_ipeak_update_v2, + .register_client = cx_ipeak_register_v2, +}; + +static int cx_ipeak_probe(struct platform_device *pdev) +{ + struct resource *res; + int i, ret, count; + u32 victim_en; + + 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); + + if (of_device_is_compatible(pdev->dev.of_node, "qcom,cx-ipeak-v1")) + device_ipeak.core_ops = &core_ops_v1; + else if (of_device_is_compatible(pdev->dev.of_node, + "qcom,cx-ipeak-v2")) + device_ipeak.core_ops = &core_ops_v2; + else + device_ipeak.core_ops = NULL; + + victim_en = of_property_read_bool(pdev->dev.of_node, + "victims_table"); + + if (victim_en) { + count = of_property_count_u32_elems(pdev->dev.of_node, + "victims_table"); + + if (((count%VICTIM_ENTRIES) != 0) || + ((count/VICTIM_ENTRIES) > CXIP_VICTIMS)) + return -EINVAL; + + for (i = 0; i < (count/VICTIM_ENTRIES); i++) { + ret = of_property_read_u32_index(pdev->dev.of_node, + "victims_table", i*VICTIM_ENTRIES, + &victim_list[i].client_id); + + if (ret) + return ret; + + ret = of_property_read_u32_index(pdev->dev.of_node, + "victims_table", (i*VICTIM_ENTRIES) + 1, + &victim_list[i].victim_id); + + if (ret) + return ret; + + ret = of_property_read_u32_index(pdev->dev.of_node, + "victims_table", (i*VICTIM_ENTRIES) + 2, + &victim_list[i].freq_limit); + + if (ret) + return ret; + + device_ipeak.victims_count++; + } + + device_ipeak.danger_intr_num = cx_ipeak_request_irq(pdev, + "cx_ipeak_danger", NULL, + cx_ipeak_irq_soft_handler, NULL); + + if (device_ipeak.danger_intr_num < 0) + return device_ipeak.danger_intr_num; + + device_ipeak.safe_intr_num = cx_ipeak_request_irq(pdev, + "cx_ipeak_safe", NULL, + cx_ipeak_irq_soft_handler, NULL); + + if (device_ipeak.safe_intr_num < 0) + return device_ipeak.safe_intr_num; + + } + + device_ipeak.pdev = pdev; + mutex_init(&device_ipeak.vote_lock); + mutex_init(&device_ipeak.throttle_lock); + return 0; +} + +static const struct of_device_id cx_ipeak_match_table[] = { + { .compatible = "qcom,cx-ipeak-v1"}, + { .compatible = "qcom,cx-ipeak-v2"}, + {} +}; + +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); + +MODULE_LICENSE("GPL v2"); diff --git a/include/soc/qcom/cx_ipeak.h b/include/soc/qcom/cx_ipeak.h new file mode 100644 index 000000000000..e07ac81fe127 --- /dev/null +++ b/include/soc/qcom/cx_ipeak.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018, 2021 The Linux Foundation. All rights reserved. + */ + +#ifndef __SOC_COM_CX_IPEAK_H +#define __SOC_COM_CX_IPEAK_H + +typedef int (*cx_ipeak_victim_fn)(void *data, u32 freq_limit); + +struct device_node; +struct cx_ipeak_client; + +#if IS_ENABLED(CONFIG_QCOM_CX_IPEAK) + +struct cx_ipeak_client *cx_ipeak_register(struct device_node *dev_node, + const char *client_name); +void cx_ipeak_unregister(struct cx_ipeak_client *client); +int cx_ipeak_update(struct cx_ipeak_client *ipeak_client, bool vote); +int cx_ipeak_victim_register(struct cx_ipeak_client *client, + cx_ipeak_victim_fn victim_cb, void *data); +void cx_ipeak_victim_unregister(struct cx_ipeak_client *client); + +#else + +static inline struct cx_ipeak_client *cx_ipeak_register( + struct device_node *dev_node, + const char *client_name) +{ + return NULL; +} + +static inline void cx_ipeak_unregister(struct cx_ipeak_client *client) +{ +} + +static inline int cx_ipeak_update(struct cx_ipeak_client *ipeak_client, + bool vote) +{ + return 0; +} + +static inline int cx_ipeak_victim_register(struct cx_ipeak_client *client, + cx_ipeak_victim_fn victim_cb, void *data) +{ + return 0; +} + +static inline void cx_ipeak_victim_unregister(struct cx_ipeak_client *client) +{ +} + +#endif + +#endif /*__SOC_COM_CX_IPEAK_H*/