forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
341 lines
8.5 KiB
341 lines
8.5 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
/* |
|
* Amlogic Thermal Sensor Driver |
|
* |
|
* Copyright (C) 2017 Huan Biao <[email protected]> |
|
* Copyright (C) 2019 Guillaume La Roque <[email protected]> |
|
* |
|
* Register value to celsius temperature formulas: |
|
* Read_Val m * U |
|
* U = ---------, Uptat = --------- |
|
* 2^16 1 + n * U |
|
* |
|
* Temperature = A * ( Uptat + u_efuse / 2^16 )- B |
|
* |
|
* A B m n : calibration parameters |
|
* u_efuse : fused calibration value, it's a signed 16 bits value |
|
*/ |
|
|
|
#include <linux/bitfield.h> |
|
#include <linux/clk.h> |
|
#include <linux/io.h> |
|
#include <linux/mfd/syscon.h> |
|
#include <linux/module.h> |
|
#include <linux/of.h> |
|
#include <linux/of_address.h> |
|
#include <linux/of_device.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/regmap.h> |
|
#include <linux/thermal.h> |
|
|
|
#include "thermal_core.h" |
|
#include "thermal_hwmon.h" |
|
|
|
#define TSENSOR_CFG_REG1 0x4 |
|
#define TSENSOR_CFG_REG1_RSET_VBG BIT(12) |
|
#define TSENSOR_CFG_REG1_RSET_ADC BIT(11) |
|
#define TSENSOR_CFG_REG1_VCM_EN BIT(10) |
|
#define TSENSOR_CFG_REG1_VBG_EN BIT(9) |
|
#define TSENSOR_CFG_REG1_OUT_CTL BIT(6) |
|
#define TSENSOR_CFG_REG1_FILTER_EN BIT(5) |
|
#define TSENSOR_CFG_REG1_DEM_EN BIT(3) |
|
#define TSENSOR_CFG_REG1_CH_SEL GENMASK(1, 0) |
|
#define TSENSOR_CFG_REG1_ENABLE \ |
|
(TSENSOR_CFG_REG1_FILTER_EN | \ |
|
TSENSOR_CFG_REG1_VCM_EN | \ |
|
TSENSOR_CFG_REG1_VBG_EN | \ |
|
TSENSOR_CFG_REG1_DEM_EN | \ |
|
TSENSOR_CFG_REG1_CH_SEL) |
|
|
|
#define TSENSOR_STAT0 0x40 |
|
|
|
#define TSENSOR_STAT9 0x64 |
|
|
|
#define TSENSOR_READ_TEMP_MASK GENMASK(15, 0) |
|
#define TSENSOR_TEMP_MASK GENMASK(11, 0) |
|
|
|
#define TSENSOR_TRIM_SIGN_MASK BIT(15) |
|
#define TSENSOR_TRIM_TEMP_MASK GENMASK(14, 0) |
|
#define TSENSOR_TRIM_VERSION_MASK GENMASK(31, 24) |
|
|
|
#define TSENSOR_TRIM_VERSION(_version) \ |
|
FIELD_GET(TSENSOR_TRIM_VERSION_MASK, _version) |
|
|
|
#define TSENSOR_TRIM_CALIB_VALID_MASK (GENMASK(3, 2) | BIT(7)) |
|
|
|
#define TSENSOR_CALIB_OFFSET 1 |
|
#define TSENSOR_CALIB_SHIFT 4 |
|
|
|
/** |
|
* struct amlogic_thermal_soc_calib_data |
|
* @A: calibration parameters |
|
* @B: calibration parameters |
|
* @m: calibration parameters |
|
* @n: calibration parameters |
|
* |
|
* This structure is required for configuration of amlogic thermal driver. |
|
*/ |
|
struct amlogic_thermal_soc_calib_data { |
|
int A; |
|
int B; |
|
int m; |
|
int n; |
|
}; |
|
|
|
/** |
|
* struct amlogic_thermal_data |
|
* @u_efuse_off: register offset to read fused calibration value |
|
* @calibration_parameters: calibration parameters structure pointer |
|
* @regmap_config: regmap config for the device |
|
* This structure is required for configuration of amlogic thermal driver. |
|
*/ |
|
struct amlogic_thermal_data { |
|
int u_efuse_off; |
|
const struct amlogic_thermal_soc_calib_data *calibration_parameters; |
|
const struct regmap_config *regmap_config; |
|
}; |
|
|
|
struct amlogic_thermal { |
|
struct platform_device *pdev; |
|
const struct amlogic_thermal_data *data; |
|
struct regmap *regmap; |
|
struct regmap *sec_ao_map; |
|
struct clk *clk; |
|
struct thermal_zone_device *tzd; |
|
u32 trim_info; |
|
}; |
|
|
|
/* |
|
* Calculate a temperature value from a temperature code. |
|
* The unit of the temperature is degree milliCelsius. |
|
*/ |
|
static int amlogic_thermal_code_to_millicelsius(struct amlogic_thermal *pdata, |
|
int temp_code) |
|
{ |
|
const struct amlogic_thermal_soc_calib_data *param = |
|
pdata->data->calibration_parameters; |
|
int temp; |
|
s64 factor, Uptat, uefuse; |
|
|
|
uefuse = pdata->trim_info & TSENSOR_TRIM_SIGN_MASK ? |
|
~(pdata->trim_info & TSENSOR_TRIM_TEMP_MASK) + 1 : |
|
(pdata->trim_info & TSENSOR_TRIM_TEMP_MASK); |
|
|
|
factor = param->n * temp_code; |
|
factor = div_s64(factor, 100); |
|
|
|
Uptat = temp_code * param->m; |
|
Uptat = div_s64(Uptat, 100); |
|
Uptat = Uptat * BIT(16); |
|
Uptat = div_s64(Uptat, BIT(16) + factor); |
|
|
|
temp = (Uptat + uefuse) * param->A; |
|
temp = div_s64(temp, BIT(16)); |
|
temp = (temp - param->B) * 100; |
|
|
|
return temp; |
|
} |
|
|
|
static int amlogic_thermal_initialize(struct amlogic_thermal *pdata) |
|
{ |
|
int ret = 0; |
|
int ver; |
|
|
|
regmap_read(pdata->sec_ao_map, pdata->data->u_efuse_off, |
|
&pdata->trim_info); |
|
|
|
ver = TSENSOR_TRIM_VERSION(pdata->trim_info); |
|
|
|
if ((ver & TSENSOR_TRIM_CALIB_VALID_MASK) == 0) { |
|
ret = -EINVAL; |
|
dev_err(&pdata->pdev->dev, |
|
"tsensor thermal calibration not supported: 0x%x!\n", |
|
ver); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int amlogic_thermal_enable(struct amlogic_thermal *data) |
|
{ |
|
int ret; |
|
|
|
ret = clk_prepare_enable(data->clk); |
|
if (ret) |
|
return ret; |
|
|
|
regmap_update_bits(data->regmap, TSENSOR_CFG_REG1, |
|
TSENSOR_CFG_REG1_ENABLE, TSENSOR_CFG_REG1_ENABLE); |
|
|
|
return 0; |
|
} |
|
|
|
static int amlogic_thermal_disable(struct amlogic_thermal *data) |
|
{ |
|
regmap_update_bits(data->regmap, TSENSOR_CFG_REG1, |
|
TSENSOR_CFG_REG1_ENABLE, 0); |
|
clk_disable_unprepare(data->clk); |
|
|
|
return 0; |
|
} |
|
|
|
static int amlogic_thermal_get_temp(void *data, int *temp) |
|
{ |
|
unsigned int tval; |
|
struct amlogic_thermal *pdata = data; |
|
|
|
if (!data) |
|
return -EINVAL; |
|
|
|
regmap_read(pdata->regmap, TSENSOR_STAT0, &tval); |
|
*temp = |
|
amlogic_thermal_code_to_millicelsius(pdata, |
|
tval & TSENSOR_READ_TEMP_MASK); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct thermal_zone_of_device_ops amlogic_thermal_ops = { |
|
.get_temp = amlogic_thermal_get_temp, |
|
}; |
|
|
|
static const struct regmap_config amlogic_thermal_regmap_config_g12a = { |
|
.reg_bits = 8, |
|
.val_bits = 32, |
|
.reg_stride = 4, |
|
.max_register = TSENSOR_STAT9, |
|
}; |
|
|
|
static const struct amlogic_thermal_soc_calib_data amlogic_thermal_g12a = { |
|
.A = 9411, |
|
.B = 3159, |
|
.m = 424, |
|
.n = 324, |
|
}; |
|
|
|
static const struct amlogic_thermal_data amlogic_thermal_g12a_cpu_param = { |
|
.u_efuse_off = 0x128, |
|
.calibration_parameters = &amlogic_thermal_g12a, |
|
.regmap_config = &amlogic_thermal_regmap_config_g12a, |
|
}; |
|
|
|
static const struct amlogic_thermal_data amlogic_thermal_g12a_ddr_param = { |
|
.u_efuse_off = 0xf0, |
|
.calibration_parameters = &amlogic_thermal_g12a, |
|
.regmap_config = &amlogic_thermal_regmap_config_g12a, |
|
}; |
|
|
|
static const struct of_device_id of_amlogic_thermal_match[] = { |
|
{ |
|
.compatible = "amlogic,g12a-ddr-thermal", |
|
.data = &amlogic_thermal_g12a_ddr_param, |
|
}, |
|
{ |
|
.compatible = "amlogic,g12a-cpu-thermal", |
|
.data = &amlogic_thermal_g12a_cpu_param, |
|
}, |
|
{ /* sentinel */ } |
|
}; |
|
MODULE_DEVICE_TABLE(of, of_amlogic_thermal_match); |
|
|
|
static int amlogic_thermal_probe(struct platform_device *pdev) |
|
{ |
|
struct amlogic_thermal *pdata; |
|
struct device *dev = &pdev->dev; |
|
void __iomem *base; |
|
int ret; |
|
|
|
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); |
|
if (!pdata) |
|
return -ENOMEM; |
|
|
|
pdata->data = of_device_get_match_data(dev); |
|
pdata->pdev = pdev; |
|
platform_set_drvdata(pdev, pdata); |
|
|
|
base = devm_platform_ioremap_resource(pdev, 0); |
|
if (IS_ERR(base)) { |
|
dev_err(dev, "failed to get io address\n"); |
|
return PTR_ERR(base); |
|
} |
|
|
|
pdata->regmap = devm_regmap_init_mmio(dev, base, |
|
pdata->data->regmap_config); |
|
if (IS_ERR(pdata->regmap)) |
|
return PTR_ERR(pdata->regmap); |
|
|
|
pdata->clk = devm_clk_get(dev, NULL); |
|
if (IS_ERR(pdata->clk)) { |
|
if (PTR_ERR(pdata->clk) != -EPROBE_DEFER) |
|
dev_err(dev, "failed to get clock\n"); |
|
return PTR_ERR(pdata->clk); |
|
} |
|
|
|
pdata->sec_ao_map = syscon_regmap_lookup_by_phandle |
|
(pdev->dev.of_node, "amlogic,ao-secure"); |
|
if (IS_ERR(pdata->sec_ao_map)) { |
|
dev_err(dev, "syscon regmap lookup failed.\n"); |
|
return PTR_ERR(pdata->sec_ao_map); |
|
} |
|
|
|
pdata->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, |
|
0, |
|
pdata, |
|
&amlogic_thermal_ops); |
|
if (IS_ERR(pdata->tzd)) { |
|
ret = PTR_ERR(pdata->tzd); |
|
dev_err(dev, "Failed to register tsensor: %d\n", ret); |
|
return ret; |
|
} |
|
|
|
if (devm_thermal_add_hwmon_sysfs(pdata->tzd)) |
|
dev_warn(&pdev->dev, "Failed to add hwmon sysfs attributes\n"); |
|
|
|
ret = amlogic_thermal_initialize(pdata); |
|
if (ret) |
|
return ret; |
|
|
|
ret = amlogic_thermal_enable(pdata); |
|
|
|
return ret; |
|
} |
|
|
|
static int amlogic_thermal_remove(struct platform_device *pdev) |
|
{ |
|
struct amlogic_thermal *data = platform_get_drvdata(pdev); |
|
|
|
return amlogic_thermal_disable(data); |
|
} |
|
|
|
static int __maybe_unused amlogic_thermal_suspend(struct device *dev) |
|
{ |
|
struct amlogic_thermal *data = dev_get_drvdata(dev); |
|
|
|
return amlogic_thermal_disable(data); |
|
} |
|
|
|
static int __maybe_unused amlogic_thermal_resume(struct device *dev) |
|
{ |
|
struct amlogic_thermal *data = dev_get_drvdata(dev); |
|
|
|
return amlogic_thermal_enable(data); |
|
} |
|
|
|
static SIMPLE_DEV_PM_OPS(amlogic_thermal_pm_ops, |
|
amlogic_thermal_suspend, amlogic_thermal_resume); |
|
|
|
static struct platform_driver amlogic_thermal_driver = { |
|
.driver = { |
|
.name = "amlogic_thermal", |
|
.pm = &amlogic_thermal_pm_ops, |
|
.of_match_table = of_amlogic_thermal_match, |
|
}, |
|
.probe = amlogic_thermal_probe, |
|
.remove = amlogic_thermal_remove, |
|
}; |
|
|
|
module_platform_driver(amlogic_thermal_driver); |
|
|
|
MODULE_AUTHOR("Guillaume La Roque <[email protected]>"); |
|
MODULE_DESCRIPTION("Amlogic thermal driver"); |
|
MODULE_LICENSE("GPL v2");
|
|
|