162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Amlogic Thermal Sensor Driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2017 Huan Biao <huan.biao@amlogic.com>
662306a36Sopenharmony_ci * Copyright (C) 2019 Guillaume La Roque <glaroque@baylibre.com>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Register value to celsius temperature formulas:
962306a36Sopenharmony_ci *	Read_Val	    m * U
1062306a36Sopenharmony_ci * U = ---------, Uptat = ---------
1162306a36Sopenharmony_ci *	2^16		  1 + n * U
1262306a36Sopenharmony_ci *
1362306a36Sopenharmony_ci * Temperature = A * ( Uptat + u_efuse / 2^16 )- B
1462306a36Sopenharmony_ci *
1562306a36Sopenharmony_ci *  A B m n : calibration parameters
1662306a36Sopenharmony_ci *  u_efuse : fused calibration value, it's a signed 16 bits value
1762306a36Sopenharmony_ci */
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <linux/bitfield.h>
2062306a36Sopenharmony_ci#include <linux/clk.h>
2162306a36Sopenharmony_ci#include <linux/io.h>
2262306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
2362306a36Sopenharmony_ci#include <linux/module.h>
2462306a36Sopenharmony_ci#include <linux/of.h>
2562306a36Sopenharmony_ci#include <linux/platform_device.h>
2662306a36Sopenharmony_ci#include <linux/regmap.h>
2762306a36Sopenharmony_ci#include <linux/thermal.h>
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#include "thermal_hwmon.h"
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define TSENSOR_CFG_REG1			0x4
3262306a36Sopenharmony_ci	#define TSENSOR_CFG_REG1_RSET_VBG	BIT(12)
3362306a36Sopenharmony_ci	#define TSENSOR_CFG_REG1_RSET_ADC	BIT(11)
3462306a36Sopenharmony_ci	#define TSENSOR_CFG_REG1_VCM_EN		BIT(10)
3562306a36Sopenharmony_ci	#define TSENSOR_CFG_REG1_VBG_EN		BIT(9)
3662306a36Sopenharmony_ci	#define TSENSOR_CFG_REG1_OUT_CTL	BIT(6)
3762306a36Sopenharmony_ci	#define TSENSOR_CFG_REG1_FILTER_EN	BIT(5)
3862306a36Sopenharmony_ci	#define TSENSOR_CFG_REG1_DEM_EN		BIT(3)
3962306a36Sopenharmony_ci	#define TSENSOR_CFG_REG1_CH_SEL		GENMASK(1, 0)
4062306a36Sopenharmony_ci	#define TSENSOR_CFG_REG1_ENABLE		\
4162306a36Sopenharmony_ci		(TSENSOR_CFG_REG1_FILTER_EN |	\
4262306a36Sopenharmony_ci		 TSENSOR_CFG_REG1_VCM_EN |	\
4362306a36Sopenharmony_ci		 TSENSOR_CFG_REG1_VBG_EN |	\
4462306a36Sopenharmony_ci		 TSENSOR_CFG_REG1_DEM_EN |	\
4562306a36Sopenharmony_ci		 TSENSOR_CFG_REG1_CH_SEL)
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci#define TSENSOR_STAT0			0x40
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci#define TSENSOR_STAT9			0x64
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci#define TSENSOR_READ_TEMP_MASK		GENMASK(15, 0)
5262306a36Sopenharmony_ci#define TSENSOR_TEMP_MASK		GENMASK(11, 0)
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci#define TSENSOR_TRIM_SIGN_MASK		BIT(15)
5562306a36Sopenharmony_ci#define TSENSOR_TRIM_TEMP_MASK		GENMASK(14, 0)
5662306a36Sopenharmony_ci#define TSENSOR_TRIM_VERSION_MASK	GENMASK(31, 24)
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci#define TSENSOR_TRIM_VERSION(_version)	\
5962306a36Sopenharmony_ci	FIELD_GET(TSENSOR_TRIM_VERSION_MASK, _version)
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci#define TSENSOR_TRIM_CALIB_VALID_MASK	(GENMASK(3, 2) | BIT(7))
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci#define TSENSOR_CALIB_OFFSET	1
6462306a36Sopenharmony_ci#define TSENSOR_CALIB_SHIFT	4
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci/**
6762306a36Sopenharmony_ci * struct amlogic_thermal_soc_calib_data
6862306a36Sopenharmony_ci * @A: calibration parameters
6962306a36Sopenharmony_ci * @B: calibration parameters
7062306a36Sopenharmony_ci * @m: calibration parameters
7162306a36Sopenharmony_ci * @n: calibration parameters
7262306a36Sopenharmony_ci *
7362306a36Sopenharmony_ci * This structure is required for configuration of amlogic thermal driver.
7462306a36Sopenharmony_ci */
7562306a36Sopenharmony_cistruct amlogic_thermal_soc_calib_data {
7662306a36Sopenharmony_ci	int A;
7762306a36Sopenharmony_ci	int B;
7862306a36Sopenharmony_ci	int m;
7962306a36Sopenharmony_ci	int n;
8062306a36Sopenharmony_ci};
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci/**
8362306a36Sopenharmony_ci * struct amlogic_thermal_data
8462306a36Sopenharmony_ci * @u_efuse_off: register offset to read fused calibration value
8562306a36Sopenharmony_ci * @calibration_parameters: calibration parameters structure pointer
8662306a36Sopenharmony_ci * @regmap_config: regmap config for the device
8762306a36Sopenharmony_ci * This structure is required for configuration of amlogic thermal driver.
8862306a36Sopenharmony_ci */
8962306a36Sopenharmony_cistruct amlogic_thermal_data {
9062306a36Sopenharmony_ci	int u_efuse_off;
9162306a36Sopenharmony_ci	const struct amlogic_thermal_soc_calib_data *calibration_parameters;
9262306a36Sopenharmony_ci	const struct regmap_config *regmap_config;
9362306a36Sopenharmony_ci};
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistruct amlogic_thermal {
9662306a36Sopenharmony_ci	struct platform_device *pdev;
9762306a36Sopenharmony_ci	const struct amlogic_thermal_data *data;
9862306a36Sopenharmony_ci	struct regmap *regmap;
9962306a36Sopenharmony_ci	struct regmap *sec_ao_map;
10062306a36Sopenharmony_ci	struct clk *clk;
10162306a36Sopenharmony_ci	struct thermal_zone_device *tzd;
10262306a36Sopenharmony_ci	u32 trim_info;
10362306a36Sopenharmony_ci};
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci/*
10662306a36Sopenharmony_ci * Calculate a temperature value from a temperature code.
10762306a36Sopenharmony_ci * The unit of the temperature is degree milliCelsius.
10862306a36Sopenharmony_ci */
10962306a36Sopenharmony_cistatic int amlogic_thermal_code_to_millicelsius(struct amlogic_thermal *pdata,
11062306a36Sopenharmony_ci						int temp_code)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	const struct amlogic_thermal_soc_calib_data *param =
11362306a36Sopenharmony_ci					pdata->data->calibration_parameters;
11462306a36Sopenharmony_ci	int temp;
11562306a36Sopenharmony_ci	s64 factor, Uptat, uefuse;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	uefuse = pdata->trim_info & TSENSOR_TRIM_SIGN_MASK ?
11862306a36Sopenharmony_ci			     ~(pdata->trim_info & TSENSOR_TRIM_TEMP_MASK) + 1 :
11962306a36Sopenharmony_ci			     (pdata->trim_info & TSENSOR_TRIM_TEMP_MASK);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	factor = param->n * temp_code;
12262306a36Sopenharmony_ci	factor = div_s64(factor, 100);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	Uptat = temp_code * param->m;
12562306a36Sopenharmony_ci	Uptat = div_s64(Uptat, 100);
12662306a36Sopenharmony_ci	Uptat = Uptat * BIT(16);
12762306a36Sopenharmony_ci	Uptat = div_s64(Uptat, BIT(16) + factor);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	temp = (Uptat + uefuse) * param->A;
13062306a36Sopenharmony_ci	temp = div_s64(temp, BIT(16));
13162306a36Sopenharmony_ci	temp = (temp - param->B) * 100;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	return temp;
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistatic int amlogic_thermal_initialize(struct amlogic_thermal *pdata)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	int ret = 0;
13962306a36Sopenharmony_ci	int ver;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	regmap_read(pdata->sec_ao_map, pdata->data->u_efuse_off,
14262306a36Sopenharmony_ci		    &pdata->trim_info);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	ver = TSENSOR_TRIM_VERSION(pdata->trim_info);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	if ((ver & TSENSOR_TRIM_CALIB_VALID_MASK) == 0) {
14762306a36Sopenharmony_ci		ret = -EINVAL;
14862306a36Sopenharmony_ci		dev_err(&pdata->pdev->dev,
14962306a36Sopenharmony_ci			"tsensor thermal calibration not supported: 0x%x!\n",
15062306a36Sopenharmony_ci			ver);
15162306a36Sopenharmony_ci	}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	return ret;
15462306a36Sopenharmony_ci}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_cistatic int amlogic_thermal_enable(struct amlogic_thermal *data)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	int ret;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	ret = clk_prepare_enable(data->clk);
16162306a36Sopenharmony_ci	if (ret)
16262306a36Sopenharmony_ci		return ret;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	regmap_update_bits(data->regmap, TSENSOR_CFG_REG1,
16562306a36Sopenharmony_ci			   TSENSOR_CFG_REG1_ENABLE, TSENSOR_CFG_REG1_ENABLE);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	return 0;
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_cistatic int amlogic_thermal_disable(struct amlogic_thermal *data)
17162306a36Sopenharmony_ci{
17262306a36Sopenharmony_ci	regmap_update_bits(data->regmap, TSENSOR_CFG_REG1,
17362306a36Sopenharmony_ci			   TSENSOR_CFG_REG1_ENABLE, 0);
17462306a36Sopenharmony_ci	clk_disable_unprepare(data->clk);
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	return 0;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic int amlogic_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	unsigned int tval;
18262306a36Sopenharmony_ci	struct amlogic_thermal *pdata = thermal_zone_device_priv(tz);
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	if (!pdata)
18562306a36Sopenharmony_ci		return -EINVAL;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	regmap_read(pdata->regmap, TSENSOR_STAT0, &tval);
18862306a36Sopenharmony_ci	*temp =
18962306a36Sopenharmony_ci	   amlogic_thermal_code_to_millicelsius(pdata,
19062306a36Sopenharmony_ci						tval & TSENSOR_READ_TEMP_MASK);
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	return 0;
19362306a36Sopenharmony_ci}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic const struct thermal_zone_device_ops amlogic_thermal_ops = {
19662306a36Sopenharmony_ci	.get_temp	= amlogic_thermal_get_temp,
19762306a36Sopenharmony_ci};
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_cistatic const struct regmap_config amlogic_thermal_regmap_config_g12a = {
20062306a36Sopenharmony_ci	.reg_bits = 8,
20162306a36Sopenharmony_ci	.val_bits = 32,
20262306a36Sopenharmony_ci	.reg_stride = 4,
20362306a36Sopenharmony_ci	.max_register = TSENSOR_STAT9,
20462306a36Sopenharmony_ci};
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_cistatic const struct amlogic_thermal_soc_calib_data amlogic_thermal_g12a = {
20762306a36Sopenharmony_ci	.A = 9411,
20862306a36Sopenharmony_ci	.B = 3159,
20962306a36Sopenharmony_ci	.m = 424,
21062306a36Sopenharmony_ci	.n = 324,
21162306a36Sopenharmony_ci};
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_cistatic const struct amlogic_thermal_data amlogic_thermal_g12a_cpu_param = {
21462306a36Sopenharmony_ci	.u_efuse_off = 0x128,
21562306a36Sopenharmony_ci	.calibration_parameters = &amlogic_thermal_g12a,
21662306a36Sopenharmony_ci	.regmap_config = &amlogic_thermal_regmap_config_g12a,
21762306a36Sopenharmony_ci};
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_cistatic const struct amlogic_thermal_data amlogic_thermal_g12a_ddr_param = {
22062306a36Sopenharmony_ci	.u_efuse_off = 0xf0,
22162306a36Sopenharmony_ci	.calibration_parameters = &amlogic_thermal_g12a,
22262306a36Sopenharmony_ci	.regmap_config = &amlogic_thermal_regmap_config_g12a,
22362306a36Sopenharmony_ci};
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cistatic const struct of_device_id of_amlogic_thermal_match[] = {
22662306a36Sopenharmony_ci	{
22762306a36Sopenharmony_ci		.compatible = "amlogic,g12a-ddr-thermal",
22862306a36Sopenharmony_ci		.data = &amlogic_thermal_g12a_ddr_param,
22962306a36Sopenharmony_ci	},
23062306a36Sopenharmony_ci	{
23162306a36Sopenharmony_ci		.compatible = "amlogic,g12a-cpu-thermal",
23262306a36Sopenharmony_ci		.data = &amlogic_thermal_g12a_cpu_param,
23362306a36Sopenharmony_ci	},
23462306a36Sopenharmony_ci	{ /* sentinel */ }
23562306a36Sopenharmony_ci};
23662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, of_amlogic_thermal_match);
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_cistatic int amlogic_thermal_probe(struct platform_device *pdev)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	struct amlogic_thermal *pdata;
24162306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
24262306a36Sopenharmony_ci	void __iomem *base;
24362306a36Sopenharmony_ci	int ret;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
24662306a36Sopenharmony_ci	if (!pdata)
24762306a36Sopenharmony_ci		return -ENOMEM;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	pdata->data = of_device_get_match_data(dev);
25062306a36Sopenharmony_ci	pdata->pdev = pdev;
25162306a36Sopenharmony_ci	platform_set_drvdata(pdev, pdata);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	base = devm_platform_ioremap_resource(pdev, 0);
25462306a36Sopenharmony_ci	if (IS_ERR(base))
25562306a36Sopenharmony_ci		return PTR_ERR(base);
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	pdata->regmap = devm_regmap_init_mmio(dev, base,
25862306a36Sopenharmony_ci					      pdata->data->regmap_config);
25962306a36Sopenharmony_ci	if (IS_ERR(pdata->regmap))
26062306a36Sopenharmony_ci		return PTR_ERR(pdata->regmap);
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	pdata->clk = devm_clk_get(dev, NULL);
26362306a36Sopenharmony_ci	if (IS_ERR(pdata->clk))
26462306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(pdata->clk), "failed to get clock\n");
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	pdata->sec_ao_map = syscon_regmap_lookup_by_phandle
26762306a36Sopenharmony_ci		(pdev->dev.of_node, "amlogic,ao-secure");
26862306a36Sopenharmony_ci	if (IS_ERR(pdata->sec_ao_map)) {
26962306a36Sopenharmony_ci		dev_err(dev, "syscon regmap lookup failed.\n");
27062306a36Sopenharmony_ci		return PTR_ERR(pdata->sec_ao_map);
27162306a36Sopenharmony_ci	}
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	pdata->tzd = devm_thermal_of_zone_register(&pdev->dev,
27462306a36Sopenharmony_ci						   0,
27562306a36Sopenharmony_ci						   pdata,
27662306a36Sopenharmony_ci						   &amlogic_thermal_ops);
27762306a36Sopenharmony_ci	if (IS_ERR(pdata->tzd)) {
27862306a36Sopenharmony_ci		ret = PTR_ERR(pdata->tzd);
27962306a36Sopenharmony_ci		dev_err(dev, "Failed to register tsensor: %d\n", ret);
28062306a36Sopenharmony_ci		return ret;
28162306a36Sopenharmony_ci	}
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	devm_thermal_add_hwmon_sysfs(&pdev->dev, pdata->tzd);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	ret = amlogic_thermal_initialize(pdata);
28662306a36Sopenharmony_ci	if (ret)
28762306a36Sopenharmony_ci		return ret;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	ret = amlogic_thermal_enable(pdata);
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	return ret;
29262306a36Sopenharmony_ci}
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_cistatic int amlogic_thermal_remove(struct platform_device *pdev)
29562306a36Sopenharmony_ci{
29662306a36Sopenharmony_ci	struct amlogic_thermal *data = platform_get_drvdata(pdev);
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	return amlogic_thermal_disable(data);
29962306a36Sopenharmony_ci}
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_cistatic int __maybe_unused amlogic_thermal_suspend(struct device *dev)
30262306a36Sopenharmony_ci{
30362306a36Sopenharmony_ci	struct amlogic_thermal *data = dev_get_drvdata(dev);
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	return amlogic_thermal_disable(data);
30662306a36Sopenharmony_ci}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cistatic int __maybe_unused amlogic_thermal_resume(struct device *dev)
30962306a36Sopenharmony_ci{
31062306a36Sopenharmony_ci	struct amlogic_thermal *data = dev_get_drvdata(dev);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	return amlogic_thermal_enable(data);
31362306a36Sopenharmony_ci}
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(amlogic_thermal_pm_ops,
31662306a36Sopenharmony_ci			 amlogic_thermal_suspend, amlogic_thermal_resume);
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_cistatic struct platform_driver amlogic_thermal_driver = {
31962306a36Sopenharmony_ci	.driver = {
32062306a36Sopenharmony_ci		.name		= "amlogic_thermal",
32162306a36Sopenharmony_ci		.pm		= &amlogic_thermal_pm_ops,
32262306a36Sopenharmony_ci		.of_match_table = of_amlogic_thermal_match,
32362306a36Sopenharmony_ci	},
32462306a36Sopenharmony_ci	.probe	= amlogic_thermal_probe,
32562306a36Sopenharmony_ci	.remove	= amlogic_thermal_remove,
32662306a36Sopenharmony_ci};
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_cimodule_platform_driver(amlogic_thermal_driver);
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ciMODULE_AUTHOR("Guillaume La Roque <glaroque@baylibre.com>");
33162306a36Sopenharmony_ciMODULE_DESCRIPTION("Amlogic thermal driver");
33262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
333