Rtwo/kernel/motorola/sm8550/drivers/thermal/qcom/qcom-spmi-mbg-tm.c
2025-09-30 19:22:48 -05:00

319 lines
7.3 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
//Copyright (c) 2023-2024, Qualcomm Innovation Center, Inc. All rights reserved.
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/suspend.h>
#include <linux/thermal.h>
#include <linux/iio/consumer.h>
#include "thermal_zone_internal.h"
#include "../thermal_core.h"
#define MBG_TEMP_MON_MM_MON2_FAULT_STATUS 0x50
#define MON_FAULT_STATUS_MASK GENMASK(7, 6)
#define MON_FAULT_POLARITY_STATUS_MASK GENMASK(5, 4)
#define MON_FAULT_STATUS_LVL1 BIT(6)
#define MON_FAULT_POLARITY_STATUS_UPR BIT(4)
#define MON2_LVL1_UP_THRESH 0x59
#define MBG_TEMP_MON_MM_MON2_MISC_CFG 0x5f
#define UP_THRESH_EN BIT(1)
#define STEP_MV 8
#define MBG_DEFAULT_TEMP_MV 600
#define MBG_TEMP_CONSTANT 1000
struct mbg_tm_chip {
struct regmap *map;
struct device *dev;
struct thermal_zone_device *tz_dev;
struct mutex lock;
unsigned int base;
int irq;
int last_temp;
bool last_temp_set;
struct iio_channel *adc;
};
struct mbg_map_table {
int min_temp;
int max_temp;
int vtemp0;
int tc;
int t0;
};
static const struct mbg_map_table map_table[] = {
/* minT maxT vtemp0 tc t0 */
{ -60000, -40000, 4337, 1967, -60000 },
{ -40000, -20000, 4731, 1964, -40000 },
{ -20000, 0, 5124, 1957, -20000 },
{ 0, 20000, 5515, 1949, 0 },
{ 20000, 40000, 5905, 1940, 20000 },
{ 40000, 60000, 6293, 1930, 40000 },
{ 60000, 80000, 6679, 1921, 60000 },
{ 80000, 100000, 7064, 1910, 80000 },
{ 100000, 120000, 7446, 1896, 100000 },
{ 120000, 140000, 7825, 1878, 120000 },
{ 140000, 160000, 8201, 1859, 140000 },
};
static int mbg_tm_read(struct mbg_tm_chip *chip, u16 addr, int *data)
{
return regmap_read(chip->map, chip->base + addr, data);
}
static int mbg_tm_write(struct mbg_tm_chip *chip, u16 addr, int data)
{
return regmap_write(chip->map, chip->base + addr, data);
}
static int mbg_tm_reg_update(struct mbg_tm_chip *chip, u16 addr, u8 mask, u8 val)
{
return regmap_write_bits(chip->map, chip->base + addr, mask, val);
}
static int mbg_tm_get_temp(void *data, int *temp)
{
struct mbg_tm_chip *chip = data;
int ret, milli_celsius;
if (!temp)
return -EINVAL;
if (chip->last_temp_set) {
pr_debug("last_temp: %d\n", chip->last_temp);
chip->last_temp_set = false;
*temp = chip->last_temp;
return 0;
}
ret = iio_read_channel_processed(chip->adc, &milli_celsius);
if (ret < 0) {
dev_err(chip->dev, "failed to read iio channel %d\n", ret);
return ret;
}
*temp = milli_celsius;
return 0;
}
static int temp_to_vtemp(int temp)
{
int idx, vtemp, tc = 0, t0 = 0, vtemp0 = 0;
for (idx = 0; idx < ARRAY_SIZE(map_table); idx++)
if (temp >= map_table[idx].min_temp &&
temp < map_table[idx].max_temp) {
tc = map_table[idx].tc;
t0 = map_table[idx].t0;
vtemp0 = map_table[idx].vtemp0;
break;
}
/*
* Formula to calculate vtemp from a given temp
* vtemp = (t-t0) * tc + vtemp0
* tc, t0 and vtemp0 values are mentioned in the map_table array.
*/
vtemp = (temp - t0)/1000 * tc/1000 + vtemp0/10;
return abs(vtemp - MBG_DEFAULT_TEMP_MV)/STEP_MV;
}
static int mbg_tm_set_trip_temp(void *data, int low_thresh, int temp)
{
struct mbg_tm_chip *chip = data;
int ret = 0, vtemp = 0;
mutex_lock(&chip->lock);
if (temp != INT_MAX) {
mbg_tm_reg_update(chip, MBG_TEMP_MON_MM_MON2_MISC_CFG,
UP_THRESH_EN, UP_THRESH_EN);
vtemp = temp_to_vtemp(temp);
ret = mbg_tm_write(chip, MON2_LVL1_UP_THRESH, vtemp);
if (ret < 0) {
mutex_unlock(&chip->lock);
return ret;
}
} else {
mbg_tm_reg_update(chip, MBG_TEMP_MON_MM_MON2_MISC_CFG,
UP_THRESH_EN, 0);
}
mutex_unlock(&chip->lock);
/*
* Configure the last_temp one degree higher, to ensure the
* violated temp is returned to thermal framework after the
* violation happens. This is needed to account for the
* inaccuracy in the conversion formula used which leads
* to the thermal framework setting back the same thresholds
* in case the temperature it reads does not show violation.
*/
chip->last_temp = temp + MBG_TEMP_CONSTANT;
return ret;
}
static int mbg_tm_get_trend(void *data, int trip, enum thermal_trend *trend)
{
struct mbg_tm_chip *chip = data;
return qti_tz_get_trend(chip->tz_dev, trip, trend);
}
static const struct thermal_zone_of_device_ops mbg_tm_ops = {
.get_temp = mbg_tm_get_temp,
.set_trips = mbg_tm_set_trip_temp,
.get_trend = mbg_tm_get_trend,
};
static irqreturn_t mbg_tm_isr(int irq, void *data)
{
struct mbg_tm_chip *chip = data;
int ret;
int val = 0;
mutex_lock(&chip->lock);
ret = mbg_tm_read(chip, MBG_TEMP_MON_MM_MON2_FAULT_STATUS, &val);
mutex_unlock(&chip->lock);
if (ret < 0)
return IRQ_HANDLED;
if ((val & MON_FAULT_STATUS_MASK) & MON_FAULT_STATUS_LVL1) {
if ((val & MON_FAULT_POLARITY_STATUS_MASK) & MON_FAULT_POLARITY_STATUS_UPR) {
chip->last_temp_set = true;
thermal_zone_device_update(chip->tz_dev,
THERMAL_TRIP_VIOLATED);
pr_debug("Notifying Thermal, val=%d\n", val);
} else {
pr_debug("High trip not violated, ignoring IRQ\n");
}
}
return IRQ_HANDLED;
}
static int mbg_tm_probe(struct platform_device *pdev)
{
struct mbg_tm_chip *chip;
struct device_node *node;
u32 res;
int ret = 0;
node = pdev->dev.of_node;
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
dev_set_drvdata(&pdev->dev, chip);
chip->dev = &pdev->dev;
mutex_init(&chip->lock);
chip->map = dev_get_regmap(pdev->dev.parent, NULL);
if (!chip->map)
return -ENXIO;
ret = of_property_read_u32(node, "reg", &res);
if (ret < 0)
return ret;
chip->base = res;
chip->irq = platform_get_irq(pdev, 0);
if (chip->irq < 0)
return chip->irq;
chip->adc = devm_iio_channel_get(&pdev->dev, "thermal");
if (IS_ERR(chip->adc))
return PTR_ERR(chip->adc);
chip->tz_dev = devm_thermal_zone_of_sensor_register(&pdev->dev,
0, chip, &mbg_tm_ops);
if (IS_ERR(chip->tz_dev)) {
dev_err(&pdev->dev, "failed to register sensor\n");
return PTR_ERR(chip->tz_dev);
}
ret = devm_request_threaded_irq(&pdev->dev, chip->irq, NULL,
mbg_tm_isr, IRQF_ONESHOT, node->name, chip);
return ret;
}
static int mbg_tm_suspend(struct device *dev)
{
#ifdef CONFIG_DEEPSLEEP
struct mbg_tm_chip *chip = dev_get_drvdata(dev);
if (pm_suspend_via_firmware())
if (chip->irq > 0) {
disable_irq(chip->irq);
devm_free_irq(dev, chip->irq, chip);
}
#endif
return 0;
}
static int mbg_tm_resume(struct device *dev)
{
#ifdef CONFIG_DEEPSLEEP
struct mbg_tm_chip *chip = dev_get_drvdata(dev);
struct device_node *node = dev->of_node;
int ret;
if (pm_suspend_via_firmware())
if (chip->irq > 0) {
ret = devm_request_threaded_irq(dev, chip->irq, NULL,
mbg_tm_isr, IRQF_ONESHOT, node->name, chip);
if (ret < 0)
return ret;
}
#endif
return 0;
}
static const struct dev_pm_ops mbg_tm_pm_ops = {
.suspend = mbg_tm_suspend,
.resume = mbg_tm_resume,
};
static const struct of_device_id mbg_tm_match_table[] = {
{ .compatible = "qcom,spmi-mgb-tm" },
{ }
};
MODULE_DEVICE_TABLE(of, mbg_tm_match_table);
static struct platform_driver mbg_tm_driver = {
.driver = {
.name = "qcom-spmi-mbg-tm",
.of_match_table = mbg_tm_match_table,
.pm = &mbg_tm_pm_ops,
},
.probe = mbg_tm_probe,
};
module_platform_driver(mbg_tm_driver);
MODULE_DESCRIPTION("PMIC MBG Temperature monitor driver");
MODULE_LICENSE("GPL");