From: qianfan Zhao <[email protected]>

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 <[email protected]>
---
 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,
+};
-- 
2.17.1

Reply via email to