429 lines
11 KiB
C
429 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2021 MediaTek Inc.
|
|
*/
|
|
#include <linux/bits.h>
|
|
#include <linux/device.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/thermal.h>
|
|
|
|
#define READ_TIA_REG_COUNT_MAX 3
|
|
|
|
#define get_tia_rc_sel(val, offset, mask) (((val) & (mask)) >> (offset))
|
|
#define is_adc_data_valid(val, bit) (((val) & BIT(bit)) != 0)
|
|
#define get_adc_data(val, bit) ((val) & GENMASK(bit, 0))
|
|
#define adc_data_to_v_in(val) (1900 - (((val) * 1000) >> 14))
|
|
|
|
/**
|
|
* struct tia_data - parameters to parse the data from TIA
|
|
* @valid_bit: valid bit in TIA DATA register
|
|
* @rc_offset: RC bit offset in TIA DATA register
|
|
* @rc_mask: bitmask for RC bit
|
|
* @rc_sel_to_value: function to get default pullup resistance value
|
|
*/
|
|
struct tia_data {
|
|
unsigned int valid_bit;
|
|
unsigned int rc_offset;
|
|
unsigned int rc_mask;
|
|
unsigned int (*rc_sel_to_value)(unsigned int sel);
|
|
};
|
|
|
|
/**
|
|
* struct pmic_auxadc_data - parameters and callback functions for NTC
|
|
* resistance value calculation
|
|
* @is_initialized: indicate the auxadc data was been initialized or not
|
|
* @default_pullup_v: voltage of internal pullup resistance
|
|
* @pullup_v: pullup voltage of each pullup resistance type. It should
|
|
* equal to default_pullup_v if no extra input buffer for SDM.
|
|
* @pullup_r: pullup resistance value
|
|
* @num_of_pullup_r_type: number of pullup resistance type
|
|
* @pullup_r_calibration: calculate the parameters for actual NTC resitance
|
|
* value calculation. Set to NULL if no extra input
|
|
* buffer for SDM.
|
|
* @tia_param: parameters to parse auxadc value from TIA DATA register
|
|
*/
|
|
struct pmic_auxadc_data {
|
|
bool is_initialized;
|
|
unsigned int default_pullup_v;
|
|
unsigned int *pullup_v;
|
|
unsigned int *pullup_r;
|
|
unsigned int num_of_pullup_r_type;
|
|
int (*pullup_r_calibration)(struct device *dev,
|
|
struct pmic_auxadc_data *adc_data);
|
|
unsigned long long (*adc2volt)(unsigned int adc_raw);
|
|
struct tia_data *tia_param;
|
|
};
|
|
|
|
struct board_ntc_info {
|
|
struct device *dev;
|
|
int *lookup_table;
|
|
int lookup_table_num;
|
|
void __iomem *data_reg;
|
|
void __iomem *dbg_reg;
|
|
void __iomem *en_reg;
|
|
struct pmic_auxadc_data *adc_data;
|
|
};
|
|
|
|
unsigned int tia2_rc_sel_to_value(unsigned int sel)
|
|
{
|
|
unsigned int resistance;
|
|
|
|
switch (sel) {
|
|
case 1:
|
|
resistance = 30000; /* 30K */
|
|
break;
|
|
case 2:
|
|
resistance = 400000; /* 400K */
|
|
break;
|
|
case 0:
|
|
default:
|
|
resistance = 100000; /* 100K */
|
|
break;
|
|
}
|
|
|
|
return resistance;
|
|
}
|
|
|
|
unsigned int tia3_rc_sel_to_value(unsigned int sel)
|
|
{
|
|
unsigned int resistance;
|
|
|
|
switch (sel) {
|
|
case 0:
|
|
default:
|
|
resistance = 100000; /* 100K */
|
|
break;
|
|
}
|
|
|
|
return resistance;
|
|
}
|
|
|
|
unsigned long long mt6685_adc2volt(unsigned int adc_raw)
|
|
{
|
|
return ((unsigned long long)adc_raw * 184000) >> 15;
|
|
}
|
|
|
|
unsigned long long mt6377_adc2volt(unsigned int adc_raw)
|
|
{
|
|
return ((unsigned long long)adc_raw * 184000) >> 15;
|
|
}
|
|
|
|
static struct tia_data tia2_data = {
|
|
.valid_bit = 15,
|
|
.rc_offset = 16,
|
|
.rc_mask = GENMASK(17, 16),
|
|
.rc_sel_to_value = tia2_rc_sel_to_value,
|
|
};
|
|
|
|
static struct tia_data tia3_data = {
|
|
.valid_bit = 15,
|
|
.rc_offset = 16,
|
|
.rc_mask = GENMASK(17, 16),
|
|
.rc_sel_to_value = tia3_rc_sel_to_value,
|
|
};
|
|
|
|
static struct pmic_auxadc_data mt6685_pmic_auxadc_data = {
|
|
.default_pullup_v = 184000,
|
|
.num_of_pullup_r_type = 3,
|
|
.pullup_r_calibration = NULL,
|
|
.adc2volt = mt6685_adc2volt,
|
|
.tia_param = &tia2_data,
|
|
};
|
|
|
|
static struct pmic_auxadc_data mt6377_pmic_auxadc_data = {
|
|
.default_pullup_v = 184000,
|
|
.num_of_pullup_r_type = 1,
|
|
.pullup_r_calibration = NULL,
|
|
.adc2volt = mt6377_adc2volt,
|
|
.tia_param = &tia3_data,
|
|
};
|
|
|
|
static const struct of_device_id board_ntc_of_match[] = {
|
|
{
|
|
.compatible = "mediatek,mt6685-tia-ntc",
|
|
.data = (void *)&mt6685_pmic_auxadc_data,
|
|
},
|
|
{
|
|
.compatible = "mediatek,mt6377-tia-ntc",
|
|
.data = (void *)&mt6377_pmic_auxadc_data,
|
|
},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, board_ntc_of_match);
|
|
|
|
static int board_ntc_r_to_temp(struct board_ntc_info *ntc_info,
|
|
int val)
|
|
{
|
|
int temp, temp_hi, temp_lo, r_hi, r_lo;
|
|
int i;
|
|
|
|
for (i = 0; i < ntc_info->lookup_table_num; i++) {
|
|
if (val >= ntc_info->lookup_table[2 * i + 1])
|
|
break;
|
|
}
|
|
|
|
if (i == 0) {
|
|
temp = ntc_info->lookup_table[0];
|
|
} else if (i >= ntc_info->lookup_table_num) {
|
|
temp = ntc_info->lookup_table[2 *
|
|
(ntc_info->lookup_table_num - 1)];
|
|
} else {
|
|
r_hi = ntc_info->lookup_table[2 * i - 1];
|
|
r_lo = ntc_info->lookup_table[2 * i + 1];
|
|
|
|
temp_hi = ntc_info->lookup_table[2 * i - 2];
|
|
temp_lo = ntc_info->lookup_table[2 * i];
|
|
|
|
temp = temp_hi + mult_frac(temp_lo - temp_hi, val - r_hi,
|
|
r_lo - r_hi);
|
|
}
|
|
|
|
return temp;
|
|
}
|
|
|
|
static unsigned int calculate_r_ntc(unsigned long long v_in,
|
|
unsigned int pullup_r, unsigned int pullup_v)
|
|
{
|
|
unsigned int r_ntc;
|
|
|
|
if (v_in >= pullup_v)
|
|
return 0;
|
|
|
|
r_ntc = (unsigned int)(v_in * pullup_r / (pullup_v - v_in));
|
|
|
|
return r_ntc;
|
|
}
|
|
|
|
static int board_ntc_get_temp(void *data, int *temp)
|
|
{
|
|
struct board_ntc_info *ntc_info = (struct board_ntc_info *)data;
|
|
struct pmic_auxadc_data *adc_data = ntc_info->adc_data;
|
|
struct tia_data *tia_param = ntc_info->adc_data->tia_param;
|
|
unsigned int val, r_type, r_ntc, dbg_reg, en_reg, count = 0;
|
|
unsigned long long v_in;
|
|
bool is_val_valid, is_rtype_valid;
|
|
static DEFINE_RATELIMIT_STATE(ratelimit, 5 * HZ, 10);
|
|
|
|
ratelimit_set_flags(&ratelimit, RATELIMIT_MSG_ON_RELEASE);
|
|
|
|
while (count < READ_TIA_REG_COUNT_MAX) {
|
|
val = readl(ntc_info->data_reg);
|
|
|
|
is_val_valid = is_adc_data_valid(val, tia_param->valid_bit);
|
|
if (!is_val_valid) {
|
|
if (ntc_info->dbg_reg && ntc_info->en_reg) {
|
|
dbg_reg = readl(ntc_info->dbg_reg);
|
|
en_reg = readl(ntc_info->en_reg);
|
|
dev_info(ntc_info->dev,
|
|
"TIA data invalid, 0x%x, dbg=0x%x, en=0x%x\n",
|
|
val, dbg_reg, en_reg);
|
|
} else {
|
|
dev_info(ntc_info->dev, "TIA data invalid, 0x%x\n", val);
|
|
}
|
|
goto RETRY;
|
|
}
|
|
|
|
r_type = get_tia_rc_sel(val, tia_param->rc_offset, tia_param->rc_mask);
|
|
if (r_type >= adc_data->num_of_pullup_r_type) {
|
|
dev_info(ntc_info->dev, "Invalid r_type = %d\n", r_type);
|
|
is_rtype_valid = false;
|
|
goto RETRY;
|
|
} else {
|
|
is_rtype_valid = true;
|
|
}
|
|
|
|
break;
|
|
RETRY:
|
|
mdelay(1);
|
|
count++;
|
|
}
|
|
|
|
if (!is_val_valid)
|
|
return -EAGAIN;
|
|
|
|
if (!is_rtype_valid)
|
|
return -EINVAL;
|
|
|
|
if (!ntc_info->adc_data->adc2volt) {
|
|
dev_err(ntc_info->dev, "adc2volt should exist\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
v_in = ntc_info->adc_data->adc2volt(get_adc_data(val, tia_param->valid_bit - 1));
|
|
r_ntc = calculate_r_ntc(v_in, adc_data->pullup_r[r_type],
|
|
adc_data->pullup_v[r_type]);
|
|
|
|
if (!r_ntc) {
|
|
dev_err(ntc_info->dev,
|
|
"r_ntc is 0! v_in/pullup_r/pullup_v=%d/%d/%d\n",
|
|
v_in, adc_data->pullup_r[r_type],
|
|
adc_data->pullup_v[r_type]);
|
|
*temp = THERMAL_TEMP_INVALID;
|
|
} else {
|
|
*temp = board_ntc_r_to_temp(ntc_info, r_ntc);
|
|
}
|
|
|
|
if (__ratelimit(&ratelimit)) {
|
|
dev_info(ntc_info->dev, "val=0x%x, v_in/r_type/r_ntc/t=%d/%d/%d/%d\n",
|
|
val, v_in, r_type, r_ntc, *temp);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct thermal_zone_of_device_ops board_ntc_ops = {
|
|
.get_temp = board_ntc_get_temp,
|
|
};
|
|
|
|
static int board_ntc_init_auxadc_data(struct device *dev,
|
|
struct pmic_auxadc_data *adc_data)
|
|
{
|
|
int ret = 0, size, i;
|
|
int num = adc_data->num_of_pullup_r_type;
|
|
|
|
if (num <= 0)
|
|
return -EINVAL;
|
|
|
|
size = sizeof(*adc_data->pullup_v) * num * 2;
|
|
adc_data->pullup_v = devm_kzalloc(dev, size, GFP_KERNEL);
|
|
if (!adc_data->pullup_v)
|
|
return -ENOMEM;
|
|
|
|
adc_data->pullup_r = (unsigned int *)(adc_data->pullup_v + num);
|
|
if (adc_data->pullup_r_calibration) {
|
|
ret = adc_data->pullup_r_calibration(dev, adc_data);
|
|
} else {
|
|
for (i = 0; i < num; i++) {
|
|
adc_data->pullup_r[i] =
|
|
adc_data->tia_param->rc_sel_to_value(i);
|
|
adc_data->pullup_v[i] = adc_data->default_pullup_v;
|
|
|
|
dev_info(dev, "%d: default pullup_r=%d, pullup_v=%d\n",
|
|
i, adc_data->pullup_r[i],
|
|
adc_data->pullup_v[i]);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int board_ntc_parse_lookup_table(struct device *dev,
|
|
struct board_ntc_info *ntc_info)
|
|
{
|
|
struct device_node *np = dev->of_node;
|
|
int num, ret;
|
|
|
|
num = of_property_count_elems_of_size(np, "temperature-lookup-table",
|
|
sizeof(unsigned int));
|
|
if (num < 0) {
|
|
dev_err(dev, "lookup table is not found\n");
|
|
return num;
|
|
}
|
|
|
|
if (num % 2) {
|
|
dev_err(dev, "temp vs ADC value in table are unpaired\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ntc_info->lookup_table = devm_kcalloc(dev, num,
|
|
sizeof(*ntc_info->lookup_table),
|
|
GFP_KERNEL);
|
|
if (!ntc_info->lookup_table)
|
|
return -ENOMEM;
|
|
|
|
ret = of_property_read_u32_array(np, "temperature-lookup-table",
|
|
(unsigned int *)ntc_info->lookup_table,
|
|
num);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Failed to read temperature lookup table: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
ntc_info->lookup_table_num = num / 2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int board_ntc_probe(struct platform_device *pdev)
|
|
{
|
|
struct board_ntc_info *ntc_info;
|
|
struct resource *res;
|
|
void __iomem *tia_reg;
|
|
struct thermal_zone_device *tz_dev;
|
|
int ret;
|
|
|
|
if (!pdev->dev.of_node) {
|
|
dev_err(&pdev->dev, "Only DT based supported\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
ntc_info = devm_kzalloc(&pdev->dev, sizeof(*ntc_info), GFP_KERNEL);
|
|
if (!ntc_info)
|
|
return -ENOMEM;
|
|
|
|
ret = board_ntc_parse_lookup_table(&pdev->dev, ntc_info);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ntc_info->dev = &pdev->dev;
|
|
ntc_info->adc_data = (struct pmic_auxadc_data *)
|
|
of_device_get_match_data(&pdev->dev);
|
|
if (!ntc_info->adc_data->is_initialized) {
|
|
ret = board_ntc_init_auxadc_data(&pdev->dev,
|
|
ntc_info->adc_data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ntc_info->adc_data->is_initialized = true;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, ntc_info);
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
tia_reg = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(tia_reg))
|
|
return PTR_ERR(tia_reg);
|
|
|
|
ntc_info->data_reg = tia_reg;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
tia_reg = devm_ioremap_resource(&pdev->dev, res);
|
|
if (!IS_ERR(tia_reg))
|
|
ntc_info->en_reg = tia_reg;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
|
|
tia_reg = devm_ioremap_resource(&pdev->dev, res);
|
|
if (!IS_ERR(tia_reg))
|
|
ntc_info->dbg_reg = tia_reg;
|
|
|
|
tz_dev = devm_thermal_zone_of_sensor_register(
|
|
&pdev->dev, 0, ntc_info, &board_ntc_ops);
|
|
if (IS_ERR(tz_dev)) {
|
|
ret = PTR_ERR(tz_dev);
|
|
dev_err(&pdev->dev, "Thermal zone sensor register fail:%d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver board_ntc_driver = {
|
|
.probe = board_ntc_probe,
|
|
.driver = {
|
|
.name = "mtk-board-ntc",
|
|
.of_match_table = board_ntc_of_match,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(board_ntc_driver);
|
|
|
|
MODULE_AUTHOR("Shun-Yao Yang <brian-sy.yang@mediatek.com>");
|
|
MODULE_DESCRIPTION("Mediatek on board NTC driver via TIA HW");
|
|
MODULE_LICENSE("GPL v2");
|