diff --git a/arch/arm64/configs/vendor/holi_QGKI.config b/arch/arm64/configs/vendor/holi_QGKI.config index a8ad342e0705..0215d654e38c 100644 --- a/arch/arm64/configs/vendor/holi_QGKI.config +++ b/arch/arm64/configs/vendor/holi_QGKI.config @@ -196,3 +196,4 @@ CONFIG_PSTORE_PMSG=y CONFIG_SYN_COOKIES=y CONFIG_PANIC_TIMEOUT=5 CONFIG_CRYPTO_SHA1_ARM64_CE=y +CONFIG_QTI_CX_IPEAK_COOLING_DEVICE=y diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index cf6263cedae6..05905a9a4c00 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -181,3 +181,12 @@ config QTI_LMH_CPU_VDD_COOLING_DEVICE These cooling devices will be used by QTI chipset to place a request to limits hardware for a minimum CPU railway voltage corner at cold temperature condition. + +config QTI_CX_IPEAK_COOLING_DEVICE + tristate "QTI CX IPeak cooling device" + depends on THERMAL_OF && QTI_THERMAL + help + This implements a mitigation device to place a thermal client vote + to CXIP LM hardware. When all pre-defined clients on CX rail including + thermal client set their vote, CXIP LM hardware throttles the clients + on the CX rail. diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index ca0429f7e633..9d4d54d9a0b0 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -26,3 +26,4 @@ obj-$(CONFIG_QTI_LIMITS_ISENSE_CDSP) += msm_isense_cdsp.o obj-$(CONFIG_QTI_DDR_COOLING_DEVICE) += ddr_cdev.o obj-$(CONFIG_MAX31760_FAN_CONTROLLER) += max31760_fan.o obj-$(CONFIG_QTI_LMH_CPU_VDD_COOLING_DEVICE) += lmh_cpu_vdd_cdev.o +obj-$(CONFIG_QTI_CX_IPEAK_COOLING_DEVICE) += cx_ipeak_cdev.o diff --git a/drivers/thermal/qcom/cx_ipeak_cdev.c b/drivers/thermal/qcom/cx_ipeak_cdev.c new file mode 100644 index 000000000000..5dc6135484b8 --- /dev/null +++ b/drivers/thermal/qcom/cx_ipeak_cdev.c @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2019, 2021 The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ + +#include +#include +#include +#include +#include +#include + +#define CXIP_LM_CDEV_DRIVER "cx-ipeak-cooling-device" +#define CXIP_LM_CDEV_MAX_STATE 1 + +#define CXIP_LM_VOTE_STATUS 0x0 +#define CXIP_LM_BYPASS 0x4 +#define CXIP_LM_VOTE_CLEAR 0x8 +#define CXIP_LM_VOTE_SET 0xc +#define CXIP_LM_FEATURE_EN 0x10 +#define CXIP_LM_BYPASS_VAL 0xff20 +#define CXIP_LM_THERM_VOTE_VAL 0x80 +#define CXIP_LM_FEATURE_EN_VAL 0x1 + +struct cxip_lm_cooling_device { + struct thermal_cooling_device *cool_dev; + char cdev_name[THERMAL_NAME_LENGTH]; + void *cx_ip_reg_base; + unsigned int therm_clnt; + unsigned int *bypass_clnts; + unsigned int bypass_clnt_cnt; + bool state; +}; + +static void cxip_lm_therm_vote_apply(struct cxip_lm_cooling_device *cxip_dev, + bool vote) +{ + int vote_offset = 0, val = 0, sts_offset = 0; + + if (!cxip_dev->therm_clnt) { + vote_offset = vote ? CXIP_LM_VOTE_SET : CXIP_LM_VOTE_CLEAR; + val = CXIP_LM_THERM_VOTE_VAL; + sts_offset = CXIP_LM_VOTE_STATUS; + } else { + vote_offset = cxip_dev->therm_clnt; + val = vote ? 0x1 : 0x0; + sts_offset = vote_offset; + } + + writel_relaxed(val, cxip_dev->cx_ip_reg_base + vote_offset); + pr_debug("%s vote for cxip_lm. vote:0x%x\n", + vote ? "Applied" : "Cleared", + readl_relaxed(cxip_dev->cx_ip_reg_base + sts_offset)); +} + +static void cxip_lm_initialize_cxip_hw(struct cxip_lm_cooling_device *cxip_dev) +{ + int i = 0; + + /* Set CXIP LM proxy vote for clients who are not participating */ + if (cxip_dev->bypass_clnt_cnt) + for (i = 0; i < cxip_dev->bypass_clnt_cnt; i++) + writel_relaxed(0x1, cxip_dev->cx_ip_reg_base + + cxip_dev->bypass_clnts[i]); + else if (!cxip_dev->therm_clnt) + writel_relaxed(CXIP_LM_BYPASS_VAL, + cxip_dev->cx_ip_reg_base + CXIP_LM_BYPASS); + + /* Enable CXIP LM HW */ + writel_relaxed(CXIP_LM_FEATURE_EN_VAL, cxip_dev->cx_ip_reg_base + + CXIP_LM_FEATURE_EN); +} + +static int cxip_lm_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = CXIP_LM_CDEV_MAX_STATE; + + return 0; +} + +static int cxip_lm_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct cxip_lm_cooling_device *cxip_dev = cdev->devdata; + int ret = 0; + + if (state > CXIP_LM_CDEV_MAX_STATE) + return -EINVAL; + + if (cxip_dev->state == state) + return 0; + + cxip_lm_therm_vote_apply(cxip_dev, state); + cxip_dev->state = state; + + return ret; +} + +static int cxip_lm_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct cxip_lm_cooling_device *cxip_dev = cdev->devdata; + + *state = cxip_dev->state; + + return 0; +} + +static struct thermal_cooling_device_ops cxip_lm_device_ops = { + .get_max_state = cxip_lm_get_max_state, + .get_cur_state = cxip_lm_get_cur_state, + .set_cur_state = cxip_lm_set_cur_state, +}; + +static int cxip_lm_cdev_remove(struct platform_device *pdev) +{ + struct cxip_lm_cooling_device *cxip_dev = + (struct cxip_lm_cooling_device *)dev_get_drvdata(&pdev->dev); + + if (cxip_dev) { + if (cxip_dev->cool_dev) { + thermal_cooling_device_unregister(cxip_dev->cool_dev); + cxip_dev->cool_dev = NULL; + } + + if (cxip_dev->cx_ip_reg_base) + cxip_lm_therm_vote_apply(cxip_dev->cx_ip_reg_base, + false); + } + + return 0; +} + +static int cxip_lm_get_devicetree_data(struct platform_device *pdev, + struct cxip_lm_cooling_device *cxip_dev, + struct device_node *np) +{ + int ret = 0; + + ret = of_property_read_u32(np, "qcom,thermal-client-offset", + &cxip_dev->therm_clnt); + if (ret) { + dev_dbg(&pdev->dev, + "error for qcom,thermal-client-offset. ret:%d\n", + ret); + cxip_dev->therm_clnt = 0; + ret = 0; + return ret; + } + + ret = of_property_count_u32_elems(np, "qcom,bypass-client-list"); + if (ret <= 0) { + dev_dbg(&pdev->dev, "Invalid number of clients err:%d\n", ret); + ret = 0; + return ret; + } + cxip_dev->bypass_clnt_cnt = ret; + + cxip_dev->bypass_clnts = devm_kcalloc(&pdev->dev, + cxip_dev->bypass_clnt_cnt, + sizeof(*cxip_dev->bypass_clnts), GFP_KERNEL); + if (!cxip_dev->bypass_clnts) + return -ENOMEM; + + ret = of_property_read_u32_array(np, "qcom,bypass-client-list", + cxip_dev->bypass_clnts, cxip_dev->bypass_clnt_cnt); + if (ret) { + dev_dbg(&pdev->dev, "bypass client list err:%d, cnt:%d\n", + ret, cxip_dev->bypass_clnt_cnt); + cxip_dev->bypass_clnt_cnt = 0; + ret = 0; + } + + return ret; +} + +static int cxip_lm_cdev_probe(struct platform_device *pdev) +{ + struct cxip_lm_cooling_device *cxip_dev = NULL; + int ret = 0; + struct device_node *np; + struct resource *res = NULL; + + np = dev_of_node(&pdev->dev); + if (!np) { + dev_err(&pdev->dev, + "of node not available for cxip_lm cdev\n"); + return -EINVAL; + } + + cxip_dev = devm_kzalloc(&pdev->dev, sizeof(*cxip_dev), GFP_KERNEL); + if (!cxip_dev) + return -ENOMEM; + + ret = cxip_lm_get_devicetree_data(pdev, cxip_dev, np); + if (ret) + return ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, + "cxip_lm platform get resource failed\n"); + return -ENODEV; + } + + cxip_dev->cx_ip_reg_base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!cxip_dev->cx_ip_reg_base) { + dev_err(&pdev->dev, "cxip_lm reg remap failed\n"); + return -ENOMEM; + } + + cxip_lm_initialize_cxip_hw(cxip_dev); + + /* Set thermal vote till we get first vote from TF */ + cxip_dev->state = true; + cxip_lm_therm_vote_apply(cxip_dev, cxip_dev->state); + + strlcpy(cxip_dev->cdev_name, np->name, THERMAL_NAME_LENGTH); + cxip_dev->cool_dev = thermal_of_cooling_device_register( + np, cxip_dev->cdev_name, cxip_dev, + &cxip_lm_device_ops); + if (IS_ERR(cxip_dev->cool_dev)) { + ret = PTR_ERR(cxip_dev->cool_dev); + dev_err(&pdev->dev, "cxip_lm cdev register err:%d\n", + ret); + cxip_dev->cool_dev = NULL; + cxip_lm_therm_vote_apply(cxip_dev->cx_ip_reg_base, + false); + return ret; + } + + dev_set_drvdata(&pdev->dev, cxip_dev); + + return ret; +} + +static const struct of_device_id cxip_lm_cdev_of_match[] = { + {.compatible = "qcom,cxip-lm-cooling-device", }, + {} +}; + +static struct platform_driver cxip_lm_cdev_driver = { + .driver = { + .name = CXIP_LM_CDEV_DRIVER, + .of_match_table = cxip_lm_cdev_of_match, + }, + .probe = cxip_lm_cdev_probe, + .remove = cxip_lm_cdev_remove, +}; +builtin_platform_driver(cxip_lm_cdev_driver); +MODULE_LICENSE("GPL v2");