319 lines
7.3 KiB
C
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");
|