diff --git a/Documentation/devicetree/bindings/thermal/regulator-cdev.txt b/Documentation/devicetree/bindings/thermal/regulator-cdev.txt new file mode 100644 index 000000000000..7c9abe205344 --- /dev/null +++ b/Documentation/devicetree/bindings/thermal/regulator-cdev.txt @@ -0,0 +1,38 @@ +Regulator cooling device. + +The regulator cooling device, will be used to place a voltage floor +restriction on a rail. + +Properties: + +- compatible: + Usage: required + Value type: + Definition: shall be "qcom,regulator-cooling-device" + +- cdev-supply: + Usage: required + Value type: + Definition: phandle to the regulator to which the cooling device will + place a floor mitigation. + +- regulator-levels: + Usage: required + Value type: + Definition: Array of regulator voltages the cooling device should + use to place a floor restriction. The voltages should + be specified in descending order. + +- #cooling-cells: Must be 2. Please refer to + for more + details. + +Example: + + mv_cdev: mx-cdev-lvl { + compatible = "qcom,regulator-cooling-device"; + cdev-supply = <®ulator-cdev-supply>; + regulator-levels = ; + #cooling-cells = <2>; + }; diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index 5ea48d91ff75..75730040a8df 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -29,3 +29,14 @@ config QTI_AOP_REG_COOLING_DEVICE device will be used by QTI chipset to place a floor voltage restriction at low temperatures. The cooling device will message the AOP using mail box to establish the floor voltage. + +config REGULATOR_COOLING_DEVICE + bool "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. + + If you want this support, you should say Y here. diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 748911498c7d..e15a69d79515 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o tsens-8996.o obj-$(CONFIG_QTI_THERMAL_LIMITS_DCVS) += msm_lmh_dcvs.o lmh_dbg.o obj-$(CONFIG_QTI_AOP_REG_COOLING_DEVICE) += regulator_aop_cdev.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..6c074511cbad --- /dev/null +++ b/drivers/thermal/qcom/regulator_cdev.c @@ -0,0 +1,184 @@ +/* Copyright (c) 2017-2018, 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 +#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 min_reg_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_min_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct reg_cooling_device *reg_dev = cdev->devdata; + + *state = reg_dev->min_reg_state; + return 0; +} + +static int reg_set_min_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) + state = reg_dev->lvl_ct; + + if (reg_dev->min_reg_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 == reg_dev->lvl_ct) { + 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 != reg_dev->lvl_ct) { + 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->min_reg_state = state; + + return ret; +} + +static int reg_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + *state = 0; + return 0; +} + +static int reg_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + /* regulator cooling device doesn't support voltage ceil */ + return 0; +} + +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, + .set_min_state = reg_set_min_state, + .get_min_state = reg_get_min_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->min_reg_state = reg_dev->lvl_ct; + 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);