9658028c2b
Add support for trend estimation callback. Since policy engine level violation reads the violation status as temperature value, the trend stays stable for level clear cases. This will not help in mitigating using step wise algorithm. Adding this trend estimation callback will return a dropping trend always during violation completely cleared cases and follow thermal core trend for all other cases. This helps step wise algorithm to clear mitigation properly especially some other sensor also triggered the same cooling device at same time. Change-Id: Id3236566c1fcdafc26165f6353c09db6b729591a Signed-off-by: Manaf Meethalavalappu Pallikunhi <quic_manafm@quicinc.com>
216 lines
5.6 KiB
C
216 lines
5.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/thermal.h>
|
|
#include <linux/err.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/interrupt.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_INT_STATUS1_OFFSET 0x630
|
|
#define PE_INTR_CFG 0x11000
|
|
#define PE_INTR_CLEAR 0x11111
|
|
#define PE_STS_CLEAR 0xFFFF
|
|
#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 pe_sensor_get_trend(void *data, int trip, enum thermal_trend *trend)
|
|
{
|
|
struct pe_sensor_data *pe_sens = (struct pe_sensor_data *)data;
|
|
struct thermal_zone_device *tz = pe_sens->tz_dev;
|
|
int value, last_value;
|
|
|
|
if (!tz)
|
|
return -EINVAL;
|
|
|
|
value = READ_ONCE(tz->temperature);
|
|
last_value = READ_ONCE(tz->last_temperature);
|
|
|
|
if (!value)
|
|
*trend = THERMAL_TREND_DROPPING;
|
|
else if (value > last_value)
|
|
*trend = THERMAL_TREND_RAISING;
|
|
else if (value < last_value)
|
|
*trend = THERMAL_TREND_DROPPING;
|
|
else
|
|
*trend = THERMAL_TREND_STABLE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
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,
|
|
.get_trend = pe_sensor_get_trend,
|
|
};
|
|
|
|
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;
|
|
|
|
writel_relaxed(PE_INTR_CLEAR, pe_sens->regmap + PE_INT_STATUS_OFFSET);
|
|
writel_relaxed(PE_STS_CLEAR, pe_sens->regmap + PE_INT_STATUS1_OFFSET);
|
|
|
|
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);
|
|
thermal_zone_device_update(pe_sens->tz_dev,
|
|
THERMAL_TRIP_VIOLATED);
|
|
} else
|
|
mutex_unlock(&pe_sens->mutex);
|
|
|
|
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);
|
|
writel_relaxed(PE_STS_CLEAR, pe_sens->regmap + PE_INT_STATUS1_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;
|
|
}
|
|
enable_irq_wake(pe_sens->irq_num);
|
|
|
|
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");
|