diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index 779ddd63b387..cf6263cedae6 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -172,3 +172,12 @@ config MAX31760_FAN_CONTROLLER This driver uses i2c bus to control registers for setting different fan speed. Also, this driver initializes the power for the fan controller. + +config QTI_LMH_CPU_VDD_COOLING_DEVICE + tristate "QTI CPU LMH VDD Voltage cooling devices" + depends on THERMAL_OF && QTI_THERMAL + help + This enables the QTI limits hardware CPU VDD cooling devices. + 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. diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index f4d4650fd70a..ca0429f7e633 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -25,3 +25,4 @@ obj-$(CONFIG_QTI_RPM_SMD_COOLING_DEVICE) += rpm_smd_cooling_device.o 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 diff --git a/drivers/thermal/qcom/lmh_cpu_vdd_cdev.c b/drivers/thermal/qcom/lmh_cpu_vdd_cdev.c new file mode 100644 index 000000000000..284a444e04a4 --- /dev/null +++ b/drivers/thermal/qcom/lmh_cpu_vdd_cdev.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019, 2021, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LMH_CPU_VDD_MAX_LVL 1 +#define LIMITS_CLUSTER_MIN_FREQ_OFFSET 0x3C0 + +struct lmh_cpu_vdd_cdev { + struct list_head node; + int id; + bool cpu_vdd_state; + void *min_freq_reg; + struct thermal_cooling_device *cdev; +}; + +static DEFINE_IDA(lmh_cpu_vdd_ida); +static DEFINE_MUTEX(lmh_cpu_vdd_lock); +static LIST_HEAD(lmh_cpu_vdd_list); + +static int lmh_cpu_vdd_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct lmh_cpu_vdd_cdev *vdd_cdev = cdev->devdata; + + if (state > LMH_CPU_VDD_MAX_LVL) + return -EINVAL; + + state = !!state; + /* Check if the old cooling action is same as new cooling action */ + if (vdd_cdev->cpu_vdd_state == state) + return 0; + + writel_relaxed(state, vdd_cdev->min_freq_reg); + vdd_cdev->cpu_vdd_state = state; + + pr_debug("%s limits CPU VDD restriction for %s\n", + state ? "Triggered" : "Cleared", vdd_cdev->cdev->type); + + return 0; +} + +static int lmh_cpu_vdd_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct lmh_cpu_vdd_cdev *lmh_cpu_vdd_cdev = cdev->devdata; + + *state = (lmh_cpu_vdd_cdev->cpu_vdd_state) ? + LMH_CPU_VDD_MAX_LVL : 0; + + return 0; +} + +static int lmh_cpu_vdd_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = LMH_CPU_VDD_MAX_LVL; + return 0; +} + +static struct thermal_cooling_device_ops lmh_cpu_vdd_cooling_ops = { + .get_max_state = lmh_cpu_vdd_get_max_state, + .get_cur_state = lmh_cpu_vdd_get_cur_state, + .set_cur_state = lmh_cpu_vdd_set_cur_state, +}; + + +static int lmh_cpu_vdd_probe(struct platform_device *pdev) +{ + int ret = -1; + struct lmh_cpu_vdd_cdev *lmh_cpu_vdd_cdev; + struct device_node *dn = pdev->dev.of_node; + uint32_t min_reg; + char cdev_name[THERMAL_NAME_LENGTH] = ""; + const __be32 *addr; + + lmh_cpu_vdd_cdev = devm_kzalloc(&pdev->dev, sizeof(*lmh_cpu_vdd_cdev), + GFP_KERNEL); + if (!lmh_cpu_vdd_cdev) + return -ENOMEM; + + addr = of_get_address(dn, 0, NULL, NULL); + if (!addr) { + dev_err(&pdev->dev, "Property llm-base-addr not found\n"); + return -EINVAL; + } + + min_reg = be32_to_cpu(addr[0]) + LIMITS_CLUSTER_MIN_FREQ_OFFSET; + lmh_cpu_vdd_cdev->min_freq_reg = devm_ioremap(&pdev->dev, min_reg, 0x4); + if (!lmh_cpu_vdd_cdev->min_freq_reg) { + dev_err(&pdev->dev, "lmh cpu vdd register remap failed\n"); + return -ENOMEM; + } + + mutex_lock(&lmh_cpu_vdd_lock); + ret = ida_simple_get(&lmh_cpu_vdd_ida, 0, 0, GFP_KERNEL); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to create ida\n"); + goto unlock_exit; + } + lmh_cpu_vdd_cdev->id = ret; + ret = 0; + + snprintf(cdev_name, THERMAL_NAME_LENGTH, "lmh-cpu-vdd%d", + lmh_cpu_vdd_cdev->id); + + lmh_cpu_vdd_cdev->cdev = thermal_of_cooling_device_register( + dn, + cdev_name, + lmh_cpu_vdd_cdev, + &lmh_cpu_vdd_cooling_ops); + if (IS_ERR(lmh_cpu_vdd_cdev->cdev)) { + ret = PTR_ERR(lmh_cpu_vdd_cdev->cdev); + dev_err(&pdev->dev, "Cooling register failed for %s, ret:%d\n", + cdev_name, ret); + lmh_cpu_vdd_cdev->cdev = NULL; + goto remove_ida; + } + list_add(&lmh_cpu_vdd_cdev->node, &lmh_cpu_vdd_list); + mutex_unlock(&lmh_cpu_vdd_lock); + + pr_debug("Cooling device [%s] registered.\n", cdev_name); + + return ret; + +remove_ida: + ida_simple_remove(&lmh_cpu_vdd_ida, lmh_cpu_vdd_cdev->id); + +unlock_exit: + mutex_unlock(&lmh_cpu_vdd_lock); + + return ret; +} + +static int lmh_cpu_vdd_remove(struct platform_device *pdev) +{ + struct lmh_cpu_vdd_cdev *lmh_cpu_vdd, *c_next; + + mutex_lock(&lmh_cpu_vdd_lock); + list_for_each_entry_safe(lmh_cpu_vdd, c_next, + &lmh_cpu_vdd_list, node) { + if (lmh_cpu_vdd->cdev) { + thermal_cooling_device_unregister( + lmh_cpu_vdd->cdev); + lmh_cpu_vdd->cdev = NULL; + } + ida_simple_remove(&lmh_cpu_vdd_ida, lmh_cpu_vdd->id); + list_del(&lmh_cpu_vdd->node); + } + mutex_unlock(&lmh_cpu_vdd_lock); + + return 0; +} +static const struct of_device_id lmh_cpu_vdd_match[] = { + { .compatible = "qcom,lmh-cpu-vdd", }, + {}, +}; + +static struct platform_driver lmh_cpu_vdd_driver = { + .probe = lmh_cpu_vdd_probe, + .remove = lmh_cpu_vdd_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = lmh_cpu_vdd_match, + }, +}; +builtin_platform_driver(lmh_cpu_vdd_driver); +MODULE_LICENSE("GPL v2");