This adds support for the Allwinner H3 thermal sensor.

Allwinner H3 has a thermal sensor like the one in A33, but have its
registers nearly all re-arranged, sample clock moved to CCU and a pair
of bus clock and reset added. It's also the base of newer SoCs' thermal
sensors.

Some new options is added to gpadc_data struct, to mark the difference
between the old GPADCs and THS's and the new THS's.

Thermal sampling via interrupts are still not supported, and polling
is used instead.

The thermal sensors on A64 and H5 is like the one on H3, but with of
course different formula factors.

Signed-off-by: Icenowy Zheng <icen...@aosc.io>
---
Changes in v3:
- Clock name changes.
- Fixed some small issues pointed out by Quentin.

 drivers/iio/adc/sun4i-gpadc-iio.c | 228 +++++++++++++++++++++++++++++++-------
 include/linux/mfd/sun4i-gpadc.h   |  27 +++++
 2 files changed, 215 insertions(+), 40 deletions(-)

diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c 
b/drivers/iio/adc/sun4i-gpadc-iio.c
index 41769bc6a429..5c79ba4d5ef5 100644
--- a/drivers/iio/adc/sun4i-gpadc-iio.c
+++ b/drivers/iio/adc/sun4i-gpadc-iio.c
@@ -22,6 +22,7 @@
  * shutdown for not being used.
  */
 
+#include <linux/clk.h>
 #include <linux/completion.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
@@ -31,6 +32,7 @@
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
 #include <linux/regmap.h>
+#include <linux/reset.h>
 #include <linux/thermal.h>
 #include <linux/delay.h>
 
@@ -49,6 +51,8 @@ static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
        return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
 }
 
+struct sun4i_gpadc_iio;
+
 struct gpadc_data {
        int             temp_offset;
        int             temp_scale;
@@ -56,39 +60,12 @@ struct gpadc_data {
        unsigned int    tp_adc_select;
        unsigned int    (*adc_chan_select)(unsigned int chan);
        unsigned int    adc_chan_mask;
-};
-
-static const struct gpadc_data sun4i_gpadc_data = {
-       .temp_offset = -1932,
-       .temp_scale = 133,
-       .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
-       .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
-       .adc_chan_select = &sun4i_gpadc_chan_select,
-       .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
-};
-
-static const struct gpadc_data sun5i_gpadc_data = {
-       .temp_offset = -1447,
-       .temp_scale = 100,
-       .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
-       .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
-       .adc_chan_select = &sun4i_gpadc_chan_select,
-       .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
-};
-
-static const struct gpadc_data sun6i_gpadc_data = {
-       .temp_offset = -1623,
-       .temp_scale = 167,
-       .tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
-       .tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
-       .adc_chan_select = &sun6i_gpadc_chan_select,
-       .adc_chan_mask = SUN6I_GPADC_CTRL1_ADC_CHAN_MASK,
-};
-
-static const struct gpadc_data sun8i_a33_gpadc_data = {
-       .temp_offset = -1662,
-       .temp_scale = 162,
-       .tp_mode_en = SUN8I_A23_GPADC_CTRL1_CHOP_TEMP_EN,
+       unsigned int    temp_data;
+       int             (*sample_start)(struct sun4i_gpadc_iio *info);
+       int             (*sample_end)(struct sun4i_gpadc_iio *info);
+       bool            has_bus_clk;
+       bool            has_bus_rst;
+       bool            has_mod_clk;
 };
 
 struct sun4i_gpadc_iio {
@@ -103,6 +80,9 @@ struct sun4i_gpadc_iio {
        atomic_t                        ignore_temp_data_irq;
        const struct gpadc_data         *data;
        bool                            no_irq;
+       struct clk                      *ths_bus_clk;
+       struct clk                      *mod_clk;
+       struct reset_control            *reset;
        /* prevents concurrent reads of temperature and ADC */
        struct mutex                    mutex;
        struct thermal_zone_device      *tzd;
@@ -276,7 +256,7 @@ static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, 
int *val)
        if (info->no_irq) {
                pm_runtime_get_sync(indio_dev->dev.parent);
 
-               regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, val);
+               regmap_read(info->regmap, info->data->temp_data, val);
 
                pm_runtime_mark_last_busy(indio_dev->dev.parent);
                pm_runtime_put_autosuspend(indio_dev->dev.parent);
@@ -384,10 +364,8 @@ static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int 
irq, void *dev_id)
        return IRQ_HANDLED;
 }
 
-static int sun4i_gpadc_runtime_suspend(struct device *dev)
+static int sun4i_gpadc_sample_end(struct sun4i_gpadc_iio *info)
 {
-       struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
-
        /* Disable the ADC on IP */
        regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0);
        /* Disable temperature sensor on IP */
@@ -396,10 +374,23 @@ static int sun4i_gpadc_runtime_suspend(struct device *dev)
        return 0;
 }
 
-static int sun4i_gpadc_runtime_resume(struct device *dev)
+static int sun8i_h3_gpadc_sample_end(struct sun4i_gpadc_iio *info)
+{
+       /* Disable temperature sensor */
+       regmap_write(info->regmap, SUN8I_H3_GPADC_CTRL2, 0);
+
+       return 0;
+}
+
+static int sun4i_gpadc_runtime_suspend(struct device *dev)
 {
        struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
 
+       return info->data->sample_end(info);
+}
+
+static int sun4i_gpadc_sample_start(struct sun4i_gpadc_iio *info)
+{
        /* clkin = 6MHz */
        regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
                     SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
@@ -417,6 +408,29 @@ static int sun4i_gpadc_runtime_resume(struct device *dev)
        return 0;
 }
 
+static int sun8i_h3_gpadc_sample_start(struct sun4i_gpadc_iio *info)
+{
+       regmap_write(info->regmap, SUN8I_H3_GPADC_CTRL2,
+                    SUN8I_H3_GPADC_CTRL2_TEMP_SENSE_EN |
+                    SUN8I_H3_GPADC_CTRL2_T_ACQ1(31));
+       regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
+                    SUN4I_GPADC_CTRL0_T_ACQ(31));
+       regmap_write(info->regmap, SUN8I_H3_GPADC_CTRL3,
+                    SUN4I_GPADC_CTRL3_FILTER_EN |
+                    SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
+       regmap_write(info->regmap, SUN8I_H3_GPADC_INTC,
+                    SUN8I_H3_GPADC_INTC_TEMP_PERIOD(800));
+
+       return 0;
+}
+
+static int sun4i_gpadc_runtime_resume(struct device *dev)
+{
+       struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
+
+       return info->data->sample_start(info);
+}
+
 static int sun4i_gpadc_get_temp(void *data, int *temp)
 {
        struct sun4i_gpadc_iio *info = data;
@@ -491,11 +505,78 @@ static int sun4i_irq_init(struct platform_device *pdev, 
const char *name,
        return 0;
 }
 
+static const struct gpadc_data sun4i_gpadc_data = {
+       .temp_offset = -1932,
+       .temp_scale = 133,
+       .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
+       .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
+       .adc_chan_select = &sun4i_gpadc_chan_select,
+       .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
+       .temp_data = SUN4I_GPADC_TEMP_DATA,
+       .sample_start = sun4i_gpadc_sample_start,
+       .sample_end = sun4i_gpadc_sample_end,
+};
+
+static const struct gpadc_data sun5i_gpadc_data = {
+       .temp_offset = -1447,
+       .temp_scale = 100,
+       .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
+       .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
+       .adc_chan_select = &sun4i_gpadc_chan_select,
+       .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
+       .temp_data = SUN4I_GPADC_TEMP_DATA,
+       .sample_start = sun4i_gpadc_sample_start,
+       .sample_end = sun4i_gpadc_sample_end,
+};
+
+static const struct gpadc_data sun6i_gpadc_data = {
+       .temp_offset = -1623,
+       .temp_scale = 167,
+       .tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
+       .tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
+       .adc_chan_select = &sun6i_gpadc_chan_select,
+       .adc_chan_mask = SUN6I_GPADC_CTRL1_ADC_CHAN_MASK,
+       .temp_data = SUN4I_GPADC_TEMP_DATA,
+       .sample_start = sun4i_gpadc_sample_start,
+       .sample_end = sun4i_gpadc_sample_end,
+};
+
+static const struct gpadc_data sun8i_a33_gpadc_data = {
+       .temp_offset = -1662,
+       .temp_scale = 162,
+       .tp_mode_en = SUN8I_A23_GPADC_CTRL1_CHOP_TEMP_EN,
+       .temp_data = SUN4I_GPADC_TEMP_DATA,
+       .sample_start = sun4i_gpadc_sample_start,
+       .sample_end = sun4i_gpadc_sample_end,
+};
+
+static const struct gpadc_data sun8i_h3_gpadc_data = {
+       /*
+        * The original formula on the datasheet seems to be wrong.
+        * These factors are calculated based on the formula in the BSP
+        * kernel, which is originally Tem = 217 - (T / 8.253), in which Tem
+        * is the temperature in Celsius degree and T is the raw value
+        * from the sensor.
+        */
+       .temp_offset = -1791,
+       .temp_scale = -121,
+       .temp_data = SUN8I_H3_GPADC_TEMP_DATA,
+       .sample_start = sun8i_h3_gpadc_sample_start,
+       .sample_end = sun8i_h3_gpadc_sample_end,
+       .has_bus_clk = true,
+       .has_bus_rst = true,
+       .has_mod_clk = true,
+};
+
 static const struct of_device_id sun4i_gpadc_of_id[] = {
        {
                .compatible = "allwinner,sun8i-a33-ths",
                .data = &sun8i_a33_gpadc_data,
        },
+       {
+               .compatible = "allwinner,sun8i-h3-ths",
+               .data = &sun8i_h3_gpadc_data,
+       },
        { /* sentinel */ }
 };
 
@@ -530,17 +611,75 @@ static int sun4i_gpadc_probe_dt(struct platform_device 
*pdev,
                return ret;
        }
 
+       if (info->data->has_bus_rst) {
+               info->reset = devm_reset_control_get(&pdev->dev, NULL);
+               if (IS_ERR(info->reset)) {
+                       ret = PTR_ERR(info->reset);
+                       return ret;
+               }
+
+               ret = reset_control_deassert(info->reset);
+               if (ret)
+                       return ret;
+       }
+
+       if (info->data->has_bus_clk) {
+               info->ths_bus_clk = devm_clk_get(&pdev->dev, "bus");
+               if (IS_ERR(info->ths_bus_clk)) {
+                       ret = PTR_ERR(info->ths_bus_clk);
+                       goto assert_reset;
+               }
+
+               ret = clk_prepare_enable(info->ths_bus_clk);
+               if (ret)
+                       goto assert_reset;
+       }
+
+       if (info->data->has_mod_clk) {
+               info->mod_clk = devm_clk_get(&pdev->dev, "mod");
+               if (IS_ERR(info->mod_clk)) {
+                       ret = PTR_ERR(info->mod_clk);
+                       goto disable_bus_clk;
+               }
+
+               /* Running at 6MHz */
+               ret = clk_set_rate(info->mod_clk, 6000000);
+               if (ret)
+                       goto disable_bus_clk;
+
+               ret = clk_prepare_enable(info->mod_clk);
+               if (ret)
+                       goto disable_bus_clk;
+       }
+
        if (!IS_ENABLED(CONFIG_THERMAL_OF))
                return 0;
 
        info->sensor_device = &pdev->dev;
        info->tzd = thermal_zone_of_sensor_register(info->sensor_device, 0,
                                                    info, &sun4i_ts_tz_ops);
-       if (IS_ERR(info->tzd))
+       if (IS_ERR(info->tzd)) {
                dev_err(&pdev->dev, "could not register thermal sensor: %ld\n",
                        PTR_ERR(info->tzd));
+               ret = PTR_ERR(info->tzd);
+               goto disable_mod_clk;
+       }
+
+       return 0;
+
+disable_mod_clk:
+       if (info->data->has_mod_clk)
+               clk_disable_unprepare(info->mod_clk);
+
+disable_bus_clk:
+       if (info->data->has_bus_clk)
+               clk_disable_unprepare(info->ths_bus_clk);
+
+assert_reset:
+       if (info->data->has_bus_rst)
+               reset_control_assert(info->reset);
 
-       return PTR_ERR_OR_ZERO(info->tzd);
+       return ret;
 }
 
 static int sun4i_gpadc_probe_mfd(struct platform_device *pdev,
@@ -699,6 +838,15 @@ static int sun4i_gpadc_remove(struct platform_device *pdev)
        if (!info->no_irq)
                iio_map_array_unregister(indio_dev);
 
+       if (info->data->has_mod_clk)
+               clk_disable_unprepare(info->mod_clk);
+
+       if (info->data->has_bus_clk)
+               clk_disable_unprepare(info->ths_bus_clk);
+
+       if (info->data->has_bus_rst)
+               reset_control_assert(info->reset);
+
        return 0;
 }
 
diff --git a/include/linux/mfd/sun4i-gpadc.h b/include/linux/mfd/sun4i-gpadc.h
index d31d962bb7d8..f794a2988a93 100644
--- a/include/linux/mfd/sun4i-gpadc.h
+++ b/include/linux/mfd/sun4i-gpadc.h
@@ -42,6 +42,9 @@
 #define SUN8I_A23_GPADC_CTRL1_CHOP_TEMP_EN             BIT(8)
 #define SUN8I_A23_GPADC_CTRL1_GPADC_CALI_EN            BIT(7)
 
+/* TP_CTRL1 bits for SoCs after H3 */
+#define SUN8I_H3_GPADC_CTRL1_GPADC_CALI_EN             BIT(17)
+
 #define SUN4I_GPADC_CTRL2                              0x08
 
 #define SUN4I_GPADC_CTRL2_TP_SENSITIVE_ADJUST(x)       ((GENMASK(3, 0) & (x)) 
<< 28)
@@ -49,7 +52,17 @@
 #define SUN4I_GPADC_CTRL2_PRE_MEA_EN                   BIT(24)
 #define SUN4I_GPADC_CTRL2_PRE_MEA_THRE_CNT(x)          (GENMASK(23, 0) & (x))
 
+#define SUN8I_H3_GPADC_CTRL2                           0x40
+
+#define SUN8I_H3_GPADC_CTRL2_TEMP_SENSE_EN             BIT(0)
+#define SUN8I_H3_GPADC_CTRL2_T_ACQ1(x)                 ((GENMASK(15, 0) * (x)) 
<< 16)
+
 #define SUN4I_GPADC_CTRL3                              0x0c
+/*
+ * This register is named "Average filter Control Register" in H3 Datasheet,
+ * but the register's definition is the same as the old CTRL3 register.
+ */
+#define SUN8I_H3_GPADC_CTRL3                           0x70
 
 #define SUN4I_GPADC_CTRL3_FILTER_EN                    BIT(2)
 #define SUN4I_GPADC_CTRL3_FILTER_TYPE(x)               (GENMASK(1, 0) & (x))
@@ -71,6 +84,13 @@
 #define SUN4I_GPADC_INT_FIFOC_TP_UP_IRQ_EN             BIT(1)
 #define SUN4I_GPADC_INT_FIFOC_TP_DOWN_IRQ_EN           BIT(0)
 
+#define SUN8I_H3_GPADC_INTC                            0x44
+
+#define SUN8I_H3_GPADC_INTC_TEMP_PERIOD(x)             ((GENMASK(19, 0) & (x)) 
<< 12)
+#define SUN8I_H3_GPADC_INTC_TEMP_DATA                  BIT(8)
+#define SUN8I_H3_GPADC_INTC_TEMP_SHUT                  BIT(4)
+#define SUN8I_H3_GPADC_INTC_TEMP_ALARM                 BIT(0)
+
 #define SUN4I_GPADC_INT_FIFOS                          0x14
 
 #define SUN4I_GPADC_INT_FIFOS_TEMP_DATA_PENDING                BIT(18)
@@ -80,9 +100,16 @@
 #define SUN4I_GPADC_INT_FIFOS_TP_UP_PENDING            BIT(1)
 #define SUN4I_GPADC_INT_FIFOS_TP_DOWN_PENDING          BIT(0)
 
+#define SUN8I_H3_GPADC_INTS                            0x44
+
+#define SUN8I_H3_GPADC_INTS_TEMP_DATA                  BIT(8)
+#define SUN8I_H3_GPADC_INTS_TEMP_SHUT                  BIT(4)
+#define SUN8I_H3_GPADC_INTS_TEMP_ALARM                 BIT(0)
+
 #define SUN4I_GPADC_CDAT                               0x1c
 #define SUN4I_GPADC_TEMP_DATA                          0x20
 #define SUN4I_GPADC_DATA                               0x24
+#define SUN8I_H3_GPADC_TEMP_DATA                       0x80
 
 #define SUN4I_GPADC_IRQ_FIFO_DATA                      0
 #define SUN4I_GPADC_IRQ_TEMP_DATA                      1
-- 
2.13.0

Reply via email to