diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index bcb0e681f3ae..7eb405bef987 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -118,3 +118,12 @@ config QTI_SDPM_CLOCK_MONITOR for different clock rate change notifications and write the clock rate into the SDPM CSR register. This driver will receive the clock list and the CSR details from devicetree. + +config REGULATOR_COOLING_DEVICE + tristate "Regulator voltage floor cooling device" + depends on REGULATOR && THERMAL_OF + help + This implements a mitigation device to place a minimum voltage floor + on a particular regulator. This mitigation device will be used by low + temperature reliability rules to mitigate a regulator at nominal + voltage. diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 5e1b537090f7..73a016bc161c 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -19,3 +19,4 @@ obj-$(CONFIG_QTI_THERMAL_LIMITS_DCVS) += msm_lmh_dcvs.o obj-$(CONFIG_QTI_CPU_VOLTAGE_COOLING_DEVICE) += cpu_voltage_cooling.o obj-$(CONFIG_QTI_POLICY_ENGINE_SENSOR) += policy_engine.o obj-${CONFIG_QTI_SDPM_CLOCK_MONITOR} += sdpm_clk.o +obj-$(CONFIG_REGULATOR_COOLING_DEVICE) += regulator_cdev.o diff --git a/drivers/thermal/qcom/regulator_cdev.c b/drivers/thermal/qcom/regulator_cdev.c new file mode 100644 index 000000000000..33ff71176ee0 --- /dev/null +++ b/drivers/thermal/qcom/regulator_cdev.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include + +#define REG_CDEV_DRIVER "reg-cooling-device" + +struct reg_cooling_device { + struct regulator *reg; + struct thermal_cooling_device *cool_dev; + unsigned int cur_state; + unsigned int *lvl; + unsigned int lvl_ct; + char reg_name[THERMAL_NAME_LENGTH]; + bool reg_enable; +}; + +static int reg_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct reg_cooling_device *reg_dev = cdev->devdata; + + *state = reg_dev->lvl_ct; + return 0; +} + +static int reg_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct reg_cooling_device *reg_dev = cdev->devdata; + + *state = reg_dev->cur_state; + return 0; +} + +static int reg_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct reg_cooling_device *reg_dev = cdev->devdata; + int ret = 0; + + if (state > reg_dev->lvl_ct) + return -EINVAL; + + if (reg_dev->cur_state == state) + return ret; + + ret = regulator_set_voltage(reg_dev->reg, + reg_dev->lvl[state], INT_MAX); + if (ret) { + dev_err(&cdev->device, + "switching to floor %lu err:%d\n", + state, ret); + return ret; + } + if (reg_dev->reg_enable && !state) { + ret = regulator_disable(reg_dev->reg); + if (ret) { + dev_err(&cdev->device, + "regulator disable err:%d\n", ret); + return ret; + } + reg_dev->reg_enable = false; + } else if (!reg_dev->reg_enable && state) { + ret = regulator_enable(reg_dev->reg); + if (ret) { + dev_err(&cdev->device, + "regulator enable err:%d\n", ret); + return ret; + } + reg_dev->reg_enable = true; + } + reg_dev->cur_state = state; + + return ret; +} + +static struct thermal_cooling_device_ops reg_device_ops = { + .get_max_state = reg_get_max_state, + .get_cur_state = reg_get_cur_state, + .set_cur_state = reg_set_cur_state, +}; + +static int reg_cdev_probe(struct platform_device *pdev) +{ + struct reg_cooling_device *reg_dev; + int ret = 0; + struct device_node *np; + + np = dev_of_node(&pdev->dev); + if (!np) { + dev_err(&pdev->dev, + "of node not available for cooling device\n"); + return -EINVAL; + } + + reg_dev = devm_kzalloc(&pdev->dev, sizeof(*reg_dev), GFP_KERNEL); + if (!reg_dev) + return -ENOMEM; + + reg_dev->reg = devm_regulator_get(&pdev->dev, "regulator-cdev"); + if (IS_ERR_OR_NULL(reg_dev->reg)) { + ret = PTR_ERR(reg_dev->reg); + dev_err(&pdev->dev, "regulator register err:%d\n", ret); + return ret; + } + ret = of_property_count_u32_elems(np, "regulator-levels"); + if (ret <= 0) { + dev_err(&pdev->dev, "Invalid levels err:%d\n", ret); + return ret; + } + reg_dev->lvl_ct = ret; + reg_dev->lvl = devm_kcalloc(&pdev->dev, reg_dev->lvl_ct, + sizeof(*reg_dev->lvl), GFP_KERNEL); + if (!reg_dev->lvl) + return -ENOMEM; + ret = of_property_read_u32_array(np, "regulator-levels", + reg_dev->lvl, reg_dev->lvl_ct); + if (ret) { + dev_err(&pdev->dev, "cdev level fetch err:%d\n", ret); + return ret; + } + /* level count is an index and it depicts the max possible index */ + reg_dev->lvl_ct--; + reg_dev->cur_state = 0; + reg_dev->reg_enable = false; + strlcpy(reg_dev->reg_name, np->name, THERMAL_NAME_LENGTH); + + reg_dev->cool_dev = thermal_of_cooling_device_register( + np, reg_dev->reg_name, reg_dev, + ®_device_ops); + if (IS_ERR(reg_dev->cool_dev)) { + ret = PTR_ERR(reg_dev->cool_dev); + dev_err(&pdev->dev, "regulator cdev register err:%d\n", + ret); + return ret; + } + + return ret; +} + +static const struct of_device_id reg_cdev_of_match[] = { + {.compatible = "qcom,regulator-cooling-device", }, + {}, +}; + +static struct platform_driver reg_cdev_driver = { + .driver = { + .name = REG_CDEV_DRIVER, + .of_match_table = reg_cdev_of_match, + }, + .probe = reg_cdev_probe, +}; +builtin_platform_driver(reg_cdev_driver); +MODULE_LICENSE("GPL v2");