diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index abeddfad23ab..a6e4bd543514 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -100,3 +100,12 @@ config QTI_CPU_VOLTAGE_COOLING_DEVICE cluster to be mitigated together based on the voltages. This will decrease or increase the voltages in a cluster based on thermal conditions. + +config QTI_POLICY_ENGINE_SENSOR + tristate "QTI Policy Engine Sensor device" + depends on THERMAL_OF && QTI_THERMAL + help + This enables the QTI Policy Engine sensor device. This driver will + register the policy engine recommendation with thermal framework as + a sensor. This will enable to provide configuration to mitigate + cooling devices when a recommendation is sent from hardware. diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 25c36312c2ed..c2f0b8c0b30f 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_QTI_BCL_PMIC5) += bcl_pmic5.o obj-$(CONFIG_QTI_BCL_SOC_DRIVER) += bcl_soc.o 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 diff --git a/drivers/thermal/qcom/policy_engine.c b/drivers/thermal/qcom/policy_engine.c new file mode 100644 index 000000000000..004870c5751a --- /dev/null +++ b/drivers/thermal/qcom/policy_engine.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../thermal_core.h" + +#define PE_SENS_DRIVER "policy-engine-sensor" +#define PE_INT_ENABLE_OFFSET 0x530 +#define PE_STATUS_OFFSET 0x590 +#define PE_INT_STATUS_OFFSET 0x620 +#define PE_INTR_CFG 0x11000 +#define PE_INTR_CLEAR 0x11111 +#define PE_READ_MITIGATION_IDX(val) ((val >> 16) & 0x1F) + +struct pe_sensor_data { + struct device *dev; + struct thermal_zone_device *tz_dev; + int32_t high_thresh; + int32_t low_thresh; + int32_t irq_num; + void __iomem *regmap; + struct mutex mutex; +}; + +static int fetch_mitigation_table_idx(struct pe_sensor_data *pe_sens, int *temp) +{ + u32 data = 0; + + data = readl_relaxed(pe_sens->regmap + PE_STATUS_OFFSET); + *temp = PE_READ_MITIGATION_IDX(data); + dev_dbg(pe_sens->dev, "PE data:%d index:%d\n", data, *temp); + + return 0; +} + +static int pe_sensor_read(void *data, int *temp) +{ + struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data; + + return fetch_mitigation_table_idx(pe_sens, temp); +} + +static int pe_sensor_set_trips(void *data, int low, int high) +{ + struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data; + + mutex_lock(&pe_sens->mutex); + if (pe_sens->high_thresh == high && + pe_sens->low_thresh == low) + goto unlock_exit; + pe_sens->high_thresh = high; + pe_sens->low_thresh = low; + dev_dbg(pe_sens->dev, "PE rail set trip. high:%d low:%d\n", + high, low); + +unlock_exit: + mutex_unlock(&pe_sens->mutex); + return 0; +} + +static struct thermal_zone_of_device_ops pe_sensor_ops = { + .get_temp = pe_sensor_read, + .set_trips = pe_sensor_set_trips, +}; + +static irqreturn_t pe_handle_irq(int irq, void *data) +{ + struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data; + int val = 0, ret = 0; + + ret = fetch_mitigation_table_idx(pe_sens, &val); + if (ret) + return IRQ_HANDLED; + + mutex_lock(&pe_sens->mutex); + dev_dbg(pe_sens->dev, "Policy Engine interrupt fired value:%d\n", val); + if (pe_sens->tz_dev && (val >= pe_sens->high_thresh || + val <= pe_sens->low_thresh)) { + mutex_unlock(&pe_sens->mutex); + of_thermal_handle_trip_temp(pe_sens->dev, pe_sens->tz_dev, val); + } else + mutex_unlock(&pe_sens->mutex); + writel_relaxed(PE_INTR_CLEAR, pe_sens->regmap + PE_INT_STATUS_OFFSET); + + return IRQ_HANDLED; +} + +static int pe_sens_device_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret = 0; + struct pe_sensor_data *pe_sens; + struct resource *res; + + pe_sens = devm_kzalloc(dev, sizeof(*pe_sens), GFP_KERNEL); + if (!pe_sens) + return -ENOMEM; + pe_sens->dev = dev; + pe_sens->high_thresh = INT_MAX; + pe_sens->low_thresh = INT_MIN; + mutex_init(&pe_sens->mutex); + + dev_set_drvdata(dev, pe_sens); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "Couldn't get MEM resource\n"); + return -EINVAL; + } + dev_dbg(dev, "pe@0x%x size:%d\n", res->start, + resource_size(res)); + + pe_sens->regmap = devm_ioremap_resource(dev, res); + if (!pe_sens->regmap) { + dev_err(dev, "Couldn't get regmap\n"); + return -EINVAL; + } + + pe_sens->irq_num = platform_get_irq(pdev, 0); + if (pe_sens->irq_num < 0) { + dev_err(dev, "Couldn't get irq number\n"); + return pe_sens->irq_num; + } + pe_sens->tz_dev = devm_thermal_zone_of_sensor_register( + dev, 0, pe_sens, &pe_sensor_ops); + if (IS_ERR_OR_NULL(pe_sens->tz_dev)) { + ret = PTR_ERR(pe_sens->tz_dev); + if (ret != -ENODEV) + dev_err(dev, "sensor register failed. ret:%d\n", ret); + pe_sens->tz_dev = NULL; + return ret; + } + writel_relaxed(PE_INTR_CFG, pe_sens->regmap + PE_INT_ENABLE_OFFSET); + writel_relaxed(PE_INTR_CLEAR, pe_sens->regmap + PE_INT_STATUS_OFFSET); + ret = devm_request_threaded_irq(dev, pe_sens->irq_num, NULL, + pe_handle_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + dev_name(dev), pe_sens); + if (ret) { + dev_err(dev, "Couldn't get irq registered\n"); + thermal_zone_of_sensor_unregister(pe_sens->dev, + pe_sens->tz_dev); + return ret; + } + dev_dbg(dev, "PE sensor register success\n"); + + return 0; +} + +static int pe_sens_device_remove(struct platform_device *pdev) +{ + struct pe_sensor_data *pe_sens = + (struct pe_sensor_data *)dev_get_drvdata(&pdev->dev); + + thermal_zone_of_sensor_unregister(pe_sens->dev, pe_sens->tz_dev); + + return 0; +} + +static const struct of_device_id pe_sens_device_match[] = { + {.compatible = "qcom,policy-engine"}, + {} +}; + +static struct platform_driver pe_sens_device_driver = { + .probe = pe_sens_device_probe, + .remove = pe_sens_device_remove, + .driver = { + .name = PE_SENS_DRIVER, + .of_match_table = pe_sens_device_match, + }, +}; + +module_platform_driver(pe_sens_device_driver); +MODULE_LICENSE("GPL v2");