
From: qianfan Zhao qianfanguijin@163.com
Portting sun8i_thermal.c from linux-5.15 drivers and drop interrupt features. Next is an example from allwinner T3:
=> temperature get thermal-sensor@1c24c00 thermal-sensor@1c24c00: 39437 C => thermal-sensor@1c24c00: 39776 C => thermal-sensor@1c24c00: 39437 C
Signed-off-by: qianfan Zhao qianfanguijin@163.com --- drivers/thermal/Kconfig | 8 + drivers/thermal/Makefile | 1 + drivers/thermal/sun8i_thermal.c | 368 ++++++++++++++++++++++++++++++++ 3 files changed, 377 insertions(+) create mode 100644 drivers/thermal/sun8i_thermal.c
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 97d4163e8e..be3118175f 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -41,4 +41,12 @@ config TI_DRA7_THERMAL Enable thermal support for for the Texas Instruments DRA752 SoC family. The driver supports reading CPU temperature.
+config SUN8I_THERMAL + bool "Temperature sensor driver for allwinner sunxi SOCs" + depends on ARCH_SUNXI + select REGMAP + help + Enable thermal support for for the sunxi SoC family. + The driver supports reading CPU temperature. + endif # if DM_THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 8acc7d20cb..9f419962cc 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o obj-$(CONFIG_IMX_SCU_THERMAL) += imx_scu_thermal.o obj-$(CONFIG_TI_DRA7_THERMAL) += ti-bandgap.o obj-$(CONFIG_IMX_TMU) += imx_tmu.o +obj-$(CONFIG_SUN8I_THERMAL) += sun8i_thermal.o diff --git a/drivers/thermal/sun8i_thermal.c b/drivers/thermal/sun8i_thermal.c new file mode 100644 index 0000000000..51ea81ccf7 --- /dev/null +++ b/drivers/thermal/sun8i_thermal.c @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Thermal sensor driver for Allwinner SOC + * Based on the linux driver + * + * Copyright (C) 2023 qianfan Zhao + */ +#include <config.h> +#include <common.h> +#include <asm/global_data.h> +#include <asm/io.h> +#include <asm/arch/clock.h> +#include <asm/arch/sys_proto.h> +#include <dm.h> +#include <dm/device-internal.h> +#include <dm/device.h> +#include <errno.h> +#include <regmap.h> +#include <reset.h> +#include <clk.h> +#include <linux/delay.h> +#include <dm/device_compat.h> +#include <malloc.h> +#include <thermal.h> + +#define FT_TEMP_MASK GENMASK(11, 0) +#define TEMP_CALIB_MASK GENMASK(11, 0) +#define CALIBRATE_DEFAULT 0x800 + +#define SUN8I_THS_CTRL0 0x00 +#define SUN8I_THS_CTRL2 0x40 +#define SUN8I_THS_IC 0x44 +#define SUN8I_THS_IS 0x48 +#define SUN8I_THS_MFC 0x70 +#define SUN8I_THS_TEMP_CALIB 0x74 +#define SUN8I_THS_TEMP_DATA 0x80 + +#define SUN50I_THS_CTRL0 0x00 +#define SUN50I_H6_THS_ENABLE 0x04 +#define SUN50I_H6_THS_PC 0x08 +#define SUN50I_H6_THS_DIC 0x10 +#define SUN50I_H6_THS_DIS 0x20 +#define SUN50I_H6_THS_MFC 0x30 +#define SUN50I_H6_THS_TEMP_CALIB 0xa0 +#define SUN50I_H6_THS_TEMP_DATA 0xc0 + +#define SUN8I_THS_CTRL0_T_ACQ0(x) (GENMASK(15, 0) & (x)) +#define SUN8I_THS_CTRL2_T_ACQ1(x) ((GENMASK(15, 0) & (x)) << 16) +#define SUN8I_THS_DATA_IRQ_STS(x) BIT(x + 8) + +#define SUN50I_THS_CTRL0_T_ACQ(x) ((GENMASK(15, 0) & (x)) << 16) +#define SUN50I_THS_FILTER_EN BIT(2) +#define SUN50I_THS_FILTER_TYPE(x) (GENMASK(1, 0) & (x)) +#define SUN50I_H6_THS_PC_TEMP_PERIOD(x) ((GENMASK(19, 0) & (x)) << 12) +#define SUN50I_H6_THS_DATA_IRQ_STS(x) BIT(x) + +struct ths_device; + +struct ths_thermal_chip { + bool has_mod_clk; + bool has_bus_clk_reset; + int sensor_num; + int offset; + int scale; + int ft_deviation; + int temp_data_base; + int (*init)(struct ths_device *tmdev); + int (*calc_temp)(struct ths_device *tmdev, + int id, int reg); +}; + +struct ths_device { + const struct ths_thermal_chip *chip; + struct udevice *dev; + struct regmap *regmap; + struct reset_ctl *reset; + struct clk *bus_clk; + struct clk *mod_clk; +}; + +/* Temp Unit: millidegree Celsius */ +static int sun8i_ths_calc_temp(struct ths_device *tmdev, + int id, int reg) +{ + return tmdev->chip->offset - (reg * tmdev->chip->scale / 10); +} + +static int sun50i_h5_calc_temp(struct ths_device *tmdev, + int id, int reg) +{ + if (reg >= 0x500) + return -1191 * reg / 10 + 223000; + else if (!id) + return -1452 * reg / 10 + 259000; + else + return -1590 * reg / 10 + 276000; +} + +static int sun8i_ths_get_temp(struct udevice *dev, int *temp) +{ + struct ths_device *tmdev = dev_get_plat(dev); + int val = 0; + + regmap_read(tmdev->regmap, tmdev->chip->temp_data_base, &val); + + /* ths have no data yet */ + if (!val) + return -EAGAIN; + + *temp = tmdev->chip->calc_temp(tmdev, 0, val); + /* + * According to the original sdk, there are some platforms(rarely) + * that add a fixed offset value after calculating the temperature + * value. We can't simply put it on the formula for calculating the + * temperature above, because the formula for calculating the + * temperature above is also used when the sensor is calibrated. If + * do this, the correct calibration formula is hard to know. + */ + *temp += tmdev->chip->ft_deviation; + + return 0; +} + +static int sun8i_ths_resource_init(struct ths_device *tmdev) +{ + struct udevice *dev = tmdev->dev; + int ret; + + tmdev->regmap = devm_regmap_init(dev, NULL, NULL, NULL); + if (IS_ERR(tmdev->regmap)) + return PTR_ERR(tmdev->regmap); + + if (tmdev->chip->has_bus_clk_reset) { + tmdev->reset = devm_reset_control_get_by_index(dev, 0); + if (IS_ERR(tmdev->reset)) + return PTR_ERR(tmdev->reset); + + tmdev->bus_clk = devm_clk_get(dev, "bus"); + if (IS_ERR(tmdev->bus_clk)) + return PTR_ERR(tmdev->bus_clk); + } + + if (tmdev->chip->has_mod_clk) { + tmdev->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(tmdev->mod_clk)) + return PTR_ERR(tmdev->mod_clk); + } + + ret = reset_deassert(tmdev->reset); + if (ret) + return ret; + + ret = clk_prepare_enable(tmdev->bus_clk); + if (ret) + goto assert_reset; + + ret = clk_prepare_enable(tmdev->mod_clk); + if (ret) + goto bus_disable; + + return 0; + +bus_disable: + clk_disable_unprepare(tmdev->bus_clk); +assert_reset: + reset_assert(tmdev->reset); + + return ret; +} + +static int sun8i_h3_thermal_init(struct ths_device *tmdev) +{ + int val; + + /* average over 4 samples */ + regmap_write(tmdev->regmap, SUN8I_THS_MFC, + SUN50I_THS_FILTER_EN | + SUN50I_THS_FILTER_TYPE(1)); + /* + * clkin = 24MHz + * filter_samples = 4 + * period = 0.25s + * + * x = period * clkin / 4096 / filter_samples - 1 + * = 365 + */ + val = GENMASK(7 + tmdev->chip->sensor_num, 8); + regmap_write(tmdev->regmap, SUN8I_THS_IC, + SUN50I_H6_THS_PC_TEMP_PERIOD(365) | val); + /* + * T_acq = 20us + * clkin = 24MHz + * + * x = T_acq * clkin - 1 + * = 479 + */ + regmap_write(tmdev->regmap, SUN8I_THS_CTRL0, + SUN8I_THS_CTRL0_T_ACQ0(479)); + val = GENMASK(tmdev->chip->sensor_num - 1, 0); + regmap_write(tmdev->regmap, SUN8I_THS_CTRL2, + SUN8I_THS_CTRL2_T_ACQ1(479) | val); + + return 0; +} + +/* + * Without this undocumented value, the returned temperatures would + * be higher than real ones by about 20C. + */ +#define SUN50I_H6_CTRL0_UNK 0x0000002f + +static int sun50i_h6_thermal_init(struct ths_device *tmdev) +{ + int val; + + /* + * T_acq = 20us + * clkin = 24MHz + * + * x = T_acq * clkin - 1 + * = 479 + */ + regmap_write(tmdev->regmap, SUN50I_THS_CTRL0, + SUN50I_H6_CTRL0_UNK | SUN50I_THS_CTRL0_T_ACQ(479)); + /* average over 4 samples */ + regmap_write(tmdev->regmap, SUN50I_H6_THS_MFC, + SUN50I_THS_FILTER_EN | + SUN50I_THS_FILTER_TYPE(1)); + /* + * clkin = 24MHz + * filter_samples = 4 + * period = 0.25s + * + * x = period * clkin / 4096 / filter_samples - 1 + * = 365 + */ + regmap_write(tmdev->regmap, SUN50I_H6_THS_PC, + SUN50I_H6_THS_PC_TEMP_PERIOD(365)); + /* enable sensor */ + val = GENMASK(tmdev->chip->sensor_num - 1, 0); + regmap_write(tmdev->regmap, SUN50I_H6_THS_ENABLE, val); + /* thermal data interrupt enable */ + val = GENMASK(tmdev->chip->sensor_num - 1, 0); + regmap_write(tmdev->regmap, SUN50I_H6_THS_DIC, val); + + return 0; +} + +static int sun8i_ths_probe(struct udevice *udev) +{ + struct ths_device *tmdev = dev_get_plat(udev); + int ret; + + tmdev->dev = udev; + + tmdev->chip = + (const struct ths_thermal_chip *)dev_get_driver_data(udev); + if (!tmdev->chip) + return -EINVAL; + + ret = sun8i_ths_resource_init(tmdev); + if (ret) + return ret; + + ret = tmdev->chip->init(tmdev); + if (ret) + return ret; + + return 0; +} + +static const struct dm_thermal_ops sun8i_ths_ops = { + .get_temp = sun8i_ths_get_temp, +}; + +static const struct ths_thermal_chip sun8i_a83t_ths = { + .sensor_num = 3, + .scale = 705, + .offset = 191668, + .temp_data_base = SUN8I_THS_TEMP_DATA, + .init = sun8i_h3_thermal_init, + .calc_temp = sun8i_ths_calc_temp, +}; + +static const struct ths_thermal_chip sun8i_h3_ths = { + .sensor_num = 1, + .scale = 1211, + .offset = 217000, + .has_mod_clk = true, + .has_bus_clk_reset = true, + .temp_data_base = SUN8I_THS_TEMP_DATA, + .init = sun8i_h3_thermal_init, + .calc_temp = sun8i_ths_calc_temp, +}; + +static const struct ths_thermal_chip sun8i_r40_ths = { + .sensor_num = 2, + .offset = 251086, + .scale = 1130, + .has_mod_clk = true, + .has_bus_clk_reset = true, + .temp_data_base = SUN8I_THS_TEMP_DATA, + .init = sun8i_h3_thermal_init, + .calc_temp = sun8i_ths_calc_temp, +}; + +static const struct ths_thermal_chip sun50i_a64_ths = { + .sensor_num = 3, + .offset = 260890, + .scale = 1170, + .has_mod_clk = true, + .has_bus_clk_reset = true, + .temp_data_base = SUN8I_THS_TEMP_DATA, + .init = sun8i_h3_thermal_init, + .calc_temp = sun8i_ths_calc_temp, +}; + +static const struct ths_thermal_chip sun50i_a100_ths = { + .sensor_num = 3, + .has_bus_clk_reset = true, + .ft_deviation = 8000, + .offset = 187744, + .scale = 672, + .temp_data_base = SUN50I_H6_THS_TEMP_DATA, + .init = sun50i_h6_thermal_init, + .calc_temp = sun8i_ths_calc_temp, +}; + +static const struct ths_thermal_chip sun50i_h5_ths = { + .sensor_num = 2, + .has_mod_clk = true, + .has_bus_clk_reset = true, + .temp_data_base = SUN8I_THS_TEMP_DATA, + .init = sun8i_h3_thermal_init, + .calc_temp = sun50i_h5_calc_temp, +}; + +static const struct ths_thermal_chip sun50i_h6_ths = { + .sensor_num = 2, + .has_bus_clk_reset = true, + .ft_deviation = 7000, + .offset = 187744, + .scale = 672, + .temp_data_base = SUN50I_H6_THS_TEMP_DATA, + .init = sun50i_h6_thermal_init, + .calc_temp = sun8i_ths_calc_temp, +}; + +static const struct udevice_id of_ths_match[] = { + { .compatible = "allwinner,sun8i-a83t-ths", .data = (ulong)&sun8i_a83t_ths }, + { .compatible = "allwinner,sun8i-h3-ths", .data = (ulong)&sun8i_h3_ths }, + { .compatible = "allwinner,sun8i-r40-ths", .data = (ulong)&sun8i_r40_ths }, + { .compatible = "allwinner,sun50i-a64-ths", .data = (ulong)&sun50i_a64_ths }, + { .compatible = "allwinner,sun50i-a100-ths", .data = (ulong)&sun50i_a100_ths }, + { .compatible = "allwinner,sun50i-h5-ths", .data = (ulong)&sun50i_h5_ths }, + { .compatible = "allwinner,sun50i-h6-ths", .data = (ulong)&sun50i_h6_ths }, + { /* sentinel */ }, +}; + +U_BOOT_DRIVER(sun8i_thermal) = { + .name = "sun8i_thermal", + .id = UCLASS_THERMAL, + .ops = &sun8i_ths_ops, + .of_match = of_ths_match, + .probe = sun8i_ths_probe, + .plat_auto = sizeof(struct ths_device), + .flags = DM_FLAG_PRE_RELOC, +};