[RFC PATCH 2/3] iio: adc: sun4i-gpadc-iio: add support for H3 thermal sensor

Icenowy Zheng icenowy at aosc.io
Tue Mar 28 10:30:12 PDT 2017


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.

An option is added to gpadc_data struct, to indicate whether this device
is a new-generation Allwinner thermal sensor.

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 <icenowy at aosc.io>
---
 drivers/iio/adc/sun4i-gpadc-iio.c | 130 ++++++++++++++++++++++++++++++++------
 include/linux/mfd/sun4i-gpadc.h   |  33 +++++++++-
 2 files changed, 141 insertions(+), 22 deletions(-)

diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c
index 74705aa37982..7512b1cae877 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>
 
@@ -56,6 +58,7 @@ struct gpadc_data {
 	unsigned int	tp_adc_select;
 	unsigned int	(*adc_chan_select)(unsigned int chan);
 	unsigned int	adc_chan_mask;
+	bool		gen2_ths;
 };
 
 static const struct gpadc_data sun4i_gpadc_data = {
@@ -88,7 +91,20 @@ static const struct gpadc_data sun6i_gpadc_data = {
 static const struct gpadc_data sun8i_a33_gpadc_data = {
 	.temp_offset = -1662,
 	.temp_scale = 162,
-	.tp_mode_en = SUN8I_GPADC_CTRL1_CHOP_TEMP_EN,
+	.tp_mode_en = SUN8I_A23_GPADC_CTRL1_CHOP_TEMP_EN,
+};
+
+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,
+	.gen2_ths = true,
 };
 
 struct sun4i_gpadc_iio {
@@ -103,6 +119,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			*ths_clk;
+	struct reset_control		*reset;
 	/* prevents concurrent reads of temperature and ADC */
 	struct mutex			mutex;
 };
@@ -274,7 +293,11 @@ 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);
+		if (info->data->gen2_ths)
+			regmap_read(info->regmap, SUN8I_H3_GPADC_TEMP_DATA,
+				    val);
+		else
+			regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, val);
 
 		pm_runtime_mark_last_busy(indio_dev->dev.parent);
 		pm_runtime_put_autosuspend(indio_dev->dev.parent);
@@ -386,10 +409,15 @@ static int sun4i_gpadc_runtime_suspend(struct device *dev)
 {
 	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 */
-	regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
+	if (info->data->gen2_ths) {
+		/* Disable temperature sensor */
+		regmap_write(info->regmap, SUN8I_H3_GPADC_CTRL2, 0);
+	} else {
+		/* Disable the ADC on IP */
+		regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0);
+		/* Disable temperature sensor on IP */
+		regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
+	}
 
 	return 0;
 }
@@ -398,19 +426,36 @@ static int sun4i_gpadc_runtime_resume(struct device *dev)
 {
 	struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
 
-	/* clkin = 6MHz */
-	regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
-		     SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
-		     SUN4I_GPADC_CTRL0_FS_DIV(7) |
-		     SUN4I_GPADC_CTRL0_T_ACQ(63));
-	regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en);
-	regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
-		     SUN4I_GPADC_CTRL3_FILTER_EN |
-		     SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
-	/* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~0.6s */
-	regmap_write(info->regmap, SUN4I_GPADC_TPR,
-		     SUN4I_GPADC_TPR_TEMP_ENABLE |
-		     SUN4I_GPADC_TPR_TEMP_PERIOD(800));
+	if (info->data->gen2_ths) {
+		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));
+	} else {
+		/* clkin = 6MHz */
+		regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
+			     SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
+			     SUN4I_GPADC_CTRL0_FS_DIV(7) |
+			     SUN4I_GPADC_CTRL0_T_ACQ(63));
+		regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
+			     info->data->tp_mode_en);
+		regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
+			     SUN4I_GPADC_CTRL3_FILTER_EN |
+			     SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
+		/*
+		 * period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin;
+		 * ~0.6s
+		 */
+		regmap_write(info->regmap, SUN4I_GPADC_TPR,
+			     SUN4I_GPADC_TPR_TEMP_ENABLE |
+			     SUN4I_GPADC_TPR_TEMP_PERIOD(800));
+	}
 
 	return 0;
 }
@@ -494,6 +539,10 @@ 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 */ }
 };
 
@@ -529,6 +578,43 @@ static int sun4i_gpadc_probe_dt(struct platform_device *pdev,
 		return ret;
 	}
 
+	if (info->data->gen2_ths) {
+		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;
+
+		info->ths_bus_clk = devm_clk_get(&pdev->dev, "bus");
+		if (IS_ERR(info->ths_bus_clk)) {
+			ret = PTR_ERR(info->ths_bus_clk);
+			return ret;
+		}
+
+		ret = clk_prepare_enable(info->ths_bus_clk);
+		if (ret)
+			return ret;
+
+		info->ths_clk = devm_clk_get(&pdev->dev, "ths");
+		if (IS_ERR(info->ths_clk)) {
+			ret = PTR_ERR(info->ths_clk);
+			return ret;
+		}
+
+		/* Running at 6MHz */
+		ret = clk_set_rate(info->ths_clk, 6000000);
+		if (ret)
+			return ret;
+
+		ret = clk_prepare_enable(info->ths_clk);
+		if (ret)
+			return ret;
+	}
+
 	if (!IS_ENABLED(CONFIG_THERMAL_OF))
 		return 0;
 
@@ -691,6 +777,12 @@ static int sun4i_gpadc_remove(struct platform_device *pdev)
 	if (!info->no_irq && IS_ENABLED(CONFIG_THERMAL_OF))
 		iio_map_array_unregister(indio_dev);
 
+	if (info->data->gen2_ths) {
+		clk_disable_unprepare(info->ths_clk);
+		clk_disable_unprepare(info->ths_bus_clk);
+		reset_control_deassert(info->reset);
+	}
+
 	return 0;
 }
 
diff --git a/include/linux/mfd/sun4i-gpadc.h b/include/linux/mfd/sun4i-gpadc.h
index 139872c2e0fe..f794a2988a93 100644
--- a/include/linux/mfd/sun4i-gpadc.h
+++ b/include/linux/mfd/sun4i-gpadc.h
@@ -38,9 +38,12 @@
 #define SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(x)		(GENMASK(3, 0) & BIT(x))
 #define SUN6I_GPADC_CTRL1_ADC_CHAN_MASK			GENMASK(3, 0)
 
-/* TP_CTRL1 bits for sun8i SoCs */
-#define SUN8I_GPADC_CTRL1_CHOP_TEMP_EN			BIT(8)
-#define SUN8I_GPADC_CTRL1_GPADC_CALI_EN			BIT(7)
+/* TP_CTRL1 bits for sun8i A23/A33 SoCs */
+#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
 
@@ -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.12.0




More information about the linux-arm-kernel mailing list