diff mbox series

[v2,10/15] iio: adc: rzg2l_adc: Add support for channel 8

Message ID 20241206111337.726244-11-claudiu.beznea.uj@bp.renesas.com (mailing list archive)
State New
Delegated to: Geert Uytterhoeven
Headers show
Series iio: adc: rzg2l_adc: Add support for RZ/G3S | expand

Commit Message

Claudiu Beznea Dec. 6, 2024, 11:13 a.m. UTC
From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>

The ADC on the Renesas RZ/G3S SoC includes an additional channel (channel
8) dedicated to reading temperature values from the Thermal Sensor Unit
(TSU). There is a direct in-SoC connection between the ADC and TSU IPs.

To read the temperature reported by the TSU, a different sampling rate
(compared to channels 0-7) must be configured in the ADM3 register.

The rzg2l_adc driver has been updated to support reading the TSU
temperature.

Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
---

Changes in v2:
- adjusted the RZG2L_ADC_MAX_CHANNELS
- introduced rzg2l_adc_ch_to_adsmp_index() and used it accordingly
- made the IIO_TEMP channel as raw channel as requested in the
  review process. I also realized having it as scale channel is
  wrong as the ADC doesn't actually report a temperature but a
  raw value that is then converted to a temperature with the
  help of the TSU (Thermal Sensor Unit) driver. Code from the
  TSU driver (not yet published) that reads the TSU sensor through
  the ADC and coverts the raw value to a temperature value is as
  follows:


// ...

#define TSU_READ_STEPS		8

/* Default calibration values, if FUSE values are missing */
#define SW_CALIB0_VAL	1297
#define SW_CALIB1_VAL	751

#define MCELSIUS(temp)		(temp * MILLIDEGREE_PER_DEGREE)

struct rzg3s_thermal_priv {
	void __iomem *base;
	struct device *dev;
	struct thermal_zone_device *tz;
	struct reset_control *rstc;
	struct iio_channel *channel;
	u16 calib0;
	u16 calib1;
};

// ...

static int rzg3s_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
{
	struct rzg3s_thermal_priv *priv = thermal_zone_device_priv(tz);
	struct device *dev = priv->dev;
	u32 ts_code_ave = 0;
	int ret, val;

	ret = pm_runtime_resume_and_get(dev);
	if (ret)
		return ret;

	for (u8 i = 0; i < TSU_READ_STEPS; i++) {
		ret = iio_read_channel_raw(priv->channel, &val);
		if (ret < 0)
			goto rpm_put;
		
		ts_code_ave += val;
		/*
		 * According to HW manual (section 40.4.4 Procedure for Measuring the Temperature)
		 * we need to wait here at leat 3us.
		 */
		usleep_range(5, 10);
	}

	ret = 0;
	ts_code_ave = DIV_ROUND_CLOSEST(ts_code_ave, TSU_READ_STEPS);

	/*
	 * According to HW manual (section 40.4.4 Procedure for Measuring the Temperature)
	 * the formula to compute the temperature is as follows;
	 *
	 * Tj = (ts_code_ave - priv->calib0) * (165 / (priv->calib0 - priv->calib1)) - 40
	 */
	*temp = DIV_ROUND_CLOSEST_ULL(((u64)(ts_code_ave - priv->calib1) * 165),
				      (priv->calib0 - priv->calib1)) - 40;

	/* Round it up to 0.5 degrees Celsius and report it in Mili Celsius. */
	*temp = roundup(MCELSIUS(*temp), 500);

rpm_put:
	pm_runtime_mark_last_busy(dev);
	pm_runtime_put_autosuspend(dev);

	return ret;
}

// ...


 drivers/iio/adc/rzg2l_adc.c | 62 ++++++++++++++++++++++++++-----------
 1 file changed, 44 insertions(+), 18 deletions(-)

Comments

Lad, Prabhakar Dec. 8, 2024, 9:19 p.m. UTC | #1
On Fri, Dec 6, 2024 at 11:16 AM Claudiu <claudiu.beznea@tuxon.dev> wrote:
>
> From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
>
> The ADC on the Renesas RZ/G3S SoC includes an additional channel (channel
> 8) dedicated to reading temperature values from the Thermal Sensor Unit
> (TSU). There is a direct in-SoC connection between the ADC and TSU IPs.
>
> To read the temperature reported by the TSU, a different sampling rate
> (compared to channels 0-7) must be configured in the ADM3 register.
>
> The rzg2l_adc driver has been updated to support reading the TSU
> temperature.
>
> Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
> ---
>
> Changes in v2:
> - adjusted the RZG2L_ADC_MAX_CHANNELS
> - introduced rzg2l_adc_ch_to_adsmp_index() and used it accordingly
> - made the IIO_TEMP channel as raw channel as requested in the
>   review process. I also realized having it as scale channel is
>   wrong as the ADC doesn't actually report a temperature but a
>   raw value that is then converted to a temperature with the
>   help of the TSU (Thermal Sensor Unit) driver. Code from the
>   TSU driver (not yet published) that reads the TSU sensor through
>   the ADC and coverts the raw value to a temperature value is as
>   follows:
>
>
> // ...
>
> #define TSU_READ_STEPS          8
>
> /* Default calibration values, if FUSE values are missing */
> #define SW_CALIB0_VAL   1297
> #define SW_CALIB1_VAL   751
>
> #define MCELSIUS(temp)          (temp * MILLIDEGREE_PER_DEGREE)
>
> struct rzg3s_thermal_priv {
>         void __iomem *base;
>         struct device *dev;
>         struct thermal_zone_device *tz;
>         struct reset_control *rstc;
>         struct iio_channel *channel;
>         u16 calib0;
>         u16 calib1;
> };
>
> // ...
>
> static int rzg3s_thermal_get_temp(struct thermal_zone_device *tz, int *temp)
> {
>         struct rzg3s_thermal_priv *priv = thermal_zone_device_priv(tz);
>         struct device *dev = priv->dev;
>         u32 ts_code_ave = 0;
>         int ret, val;
>
>         ret = pm_runtime_resume_and_get(dev);
>         if (ret)
>                 return ret;
>
>         for (u8 i = 0; i < TSU_READ_STEPS; i++) {
>                 ret = iio_read_channel_raw(priv->channel, &val);
>                 if (ret < 0)
>                         goto rpm_put;
>
>                 ts_code_ave += val;
>                 /*
>                  * According to HW manual (section 40.4.4 Procedure for Measuring the Temperature)
>                  * we need to wait here at leat 3us.
>                  */
>                 usleep_range(5, 10);
>         }
>
>         ret = 0;
>         ts_code_ave = DIV_ROUND_CLOSEST(ts_code_ave, TSU_READ_STEPS);
>
>         /*
>          * According to HW manual (section 40.4.4 Procedure for Measuring the Temperature)
>          * the formula to compute the temperature is as follows;
>          *
>          * Tj = (ts_code_ave - priv->calib0) * (165 / (priv->calib0 - priv->calib1)) - 40
>          */
>         *temp = DIV_ROUND_CLOSEST_ULL(((u64)(ts_code_ave - priv->calib1) * 165),
>                                       (priv->calib0 - priv->calib1)) - 40;
>
>         /* Round it up to 0.5 degrees Celsius and report it in Mili Celsius. */
>         *temp = roundup(MCELSIUS(*temp), 500);
>
> rpm_put:
>         pm_runtime_mark_last_busy(dev);
>         pm_runtime_put_autosuspend(dev);
>
>         return ret;
> }
>
> // ...
>
>
>  drivers/iio/adc/rzg2l_adc.c | 62 ++++++++++++++++++++++++++-----------
>  1 file changed, 44 insertions(+), 18 deletions(-)
>
Reviewed-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>

Cheers,
Prabhakar

> diff --git a/drivers/iio/adc/rzg2l_adc.c b/drivers/iio/adc/rzg2l_adc.c
> index 6740912f83c5..e8dbc5dfbea1 100644
> --- a/drivers/iio/adc/rzg2l_adc.c
> +++ b/drivers/iio/adc/rzg2l_adc.c
> @@ -52,12 +52,13 @@
>  #define RZG2L_ADCR(n)                  (0x30 + ((n) * 0x4))
>  #define RZG2L_ADCR_AD_MASK             GENMASK(11, 0)
>
> -#define RZG2L_ADC_MAX_CHANNELS         8
> +#define RZG2L_ADC_MAX_CHANNELS         9
>  #define RZG2L_ADC_TIMEOUT              usecs_to_jiffies(1 * 4)
>
>  /**
>   * struct rzg2l_adc_hw_params - ADC hardware specific parameters
> - * @default_adsmp: default ADC sampling period (see ADM3 register)
> + * @default_adsmp: default ADC sampling period (see ADM3 register); index 0 is
> + * used for voltage channels, index 1 is used for temperature channel
>   * @adsmp_mask: ADC sampling period mask (see ADM3 register)
>   * @adint_inten_mask: conversion end interrupt mask (see ADINT register)
>   * @default_adcmp: default ADC cmp (see ADM3 register)
> @@ -65,7 +66,7 @@
>   * @adivc: specifies if ADVIC register is available
>   */
>  struct rzg2l_adc_hw_params {
> -       u16 default_adsmp;
> +       u16 default_adsmp[2];
>         u16 adsmp_mask;
>         u16 adint_inten_mask;
>         u8 default_adcmp;
> @@ -89,15 +90,26 @@ struct rzg2l_adc {
>         u16 last_val[RZG2L_ADC_MAX_CHANNELS];
>  };
>
> -static const char * const rzg2l_adc_channel_name[] = {
> -       "adc0",
> -       "adc1",
> -       "adc2",
> -       "adc3",
> -       "adc4",
> -       "adc5",
> -       "adc6",
> -       "adc7",
> +/**
> + * struct rzg2l_adc_channel - ADC channel descriptor
> + * @name: ADC channel name
> + * @type: ADC channel type
> + */
> +struct rzg2l_adc_channel {
> +       const char * const name;
> +       enum iio_chan_type type;
> +};
> +
> +static const struct rzg2l_adc_channel rzg2l_adc_channels[] = {
> +       { "adc0", IIO_VOLTAGE },
> +       { "adc1", IIO_VOLTAGE },
> +       { "adc2", IIO_VOLTAGE },
> +       { "adc3", IIO_VOLTAGE },
> +       { "adc4", IIO_VOLTAGE },
> +       { "adc5", IIO_VOLTAGE },
> +       { "adc6", IIO_VOLTAGE },
> +       { "adc7", IIO_VOLTAGE },
> +       { "adc8", IIO_TEMP },
>  };
>
>  static unsigned int rzg2l_adc_readl(struct rzg2l_adc *adc, u32 reg)
> @@ -163,9 +175,18 @@ static void rzg2l_set_trigger(struct rzg2l_adc *adc)
>         rzg2l_adc_writel(adc, RZG2L_ADM(1), reg);
>  }
>
> +static u8 rzg2l_adc_ch_to_adsmp_index(u8 ch)
> +{
> +       if (rzg2l_adc_channels[ch].type == IIO_VOLTAGE)
> +               return 0;
> +
> +       return 1;
> +}
> +
>  static int rzg2l_adc_conversion_setup(struct rzg2l_adc *adc, u8 ch)
>  {
>         const struct rzg2l_adc_hw_params *hw_params = adc->hw_params;
> +       u8 index = rzg2l_adc_ch_to_adsmp_index(ch);
>         u32 reg;
>
>         if (rzg2l_adc_readl(adc, RZG2L_ADM(0)) & RZG2L_ADM0_ADBSY)
> @@ -179,6 +200,11 @@ static int rzg2l_adc_conversion_setup(struct rzg2l_adc *adc, u8 ch)
>         reg |= BIT(ch);
>         rzg2l_adc_writel(adc, RZG2L_ADM(2), reg);
>
> +       reg = rzg2l_adc_readl(adc, RZG2L_ADM(3));
> +       reg &= ~hw_params->adsmp_mask;
> +       reg |= hw_params->default_adsmp[index];
> +       rzg2l_adc_writel(adc, RZG2L_ADM(3), reg);
> +
>         /*
>          * Setup ADINT
>          * INTS[31] - Select pulse signal
> @@ -235,7 +261,7 @@ static int rzg2l_adc_read_raw(struct iio_dev *indio_dev,
>
>         switch (mask) {
>         case IIO_CHAN_INFO_RAW: {
> -               if (chan->type != IIO_VOLTAGE)
> +               if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
>                         return -EINVAL;
>
>                 guard(mutex)(&adc->lock);
> @@ -258,7 +284,7 @@ static int rzg2l_adc_read_label(struct iio_dev *iio_dev,
>                                 const struct iio_chan_spec *chan,
>                                 char *label)
>  {
> -       return sysfs_emit(label, "%s\n", rzg2l_adc_channel_name[chan->channel]);
> +       return sysfs_emit(label, "%s\n", rzg2l_adc_channels[chan->channel].name);
>  }
>
>  static const struct iio_info rzg2l_adc_iio_info = {
> @@ -332,11 +358,11 @@ static int rzg2l_adc_parse_properties(struct platform_device *pdev, struct rzg2l
>                 if (channel >= hw_params->num_channels)
>                         return -EINVAL;
>
> -               chan_array[i].type = IIO_VOLTAGE;
> +               chan_array[i].type = rzg2l_adc_channels[channel].type;
>                 chan_array[i].indexed = 1;
>                 chan_array[i].channel = channel;
>                 chan_array[i].info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> -               chan_array[i].datasheet_name = rzg2l_adc_channel_name[channel];
> +               chan_array[i].datasheet_name = rzg2l_adc_channels[channel].name;
>                 i++;
>         }
>
> @@ -386,7 +412,7 @@ static int rzg2l_adc_hw_init(struct device *dev, struct rzg2l_adc *adc)
>         reg &= ~RZG2L_ADM3_ADCMP_MASK;
>         reg &= ~hw_params->adsmp_mask;
>         reg |= FIELD_PREP(RZG2L_ADM3_ADCMP_MASK, hw_params->default_adcmp) |
> -              hw_params->default_adsmp;
> +              hw_params->default_adsmp[0];
>
>         rzg2l_adc_writel(adc, RZG2L_ADM(3), reg);
>
> @@ -469,7 +495,7 @@ static int rzg2l_adc_probe(struct platform_device *pdev)
>  static const struct rzg2l_adc_hw_params rzg2l_hw_params = {
>         .num_channels = 8,
>         .default_adcmp = 0xe,
> -       .default_adsmp = 0x578,
> +       .default_adsmp = { 0x578 },
>         .adsmp_mask = GENMASK(15, 0),
>         .adint_inten_mask = GENMASK(7, 0),
>         .adivc = true
> --
> 2.39.2
>
>
diff mbox series

Patch

diff --git a/drivers/iio/adc/rzg2l_adc.c b/drivers/iio/adc/rzg2l_adc.c
index 6740912f83c5..e8dbc5dfbea1 100644
--- a/drivers/iio/adc/rzg2l_adc.c
+++ b/drivers/iio/adc/rzg2l_adc.c
@@ -52,12 +52,13 @@ 
 #define RZG2L_ADCR(n)			(0x30 + ((n) * 0x4))
 #define RZG2L_ADCR_AD_MASK		GENMASK(11, 0)
 
-#define RZG2L_ADC_MAX_CHANNELS		8
+#define RZG2L_ADC_MAX_CHANNELS		9
 #define RZG2L_ADC_TIMEOUT		usecs_to_jiffies(1 * 4)
 
 /**
  * struct rzg2l_adc_hw_params - ADC hardware specific parameters
- * @default_adsmp: default ADC sampling period (see ADM3 register)
+ * @default_adsmp: default ADC sampling period (see ADM3 register); index 0 is
+ * used for voltage channels, index 1 is used for temperature channel
  * @adsmp_mask: ADC sampling period mask (see ADM3 register)
  * @adint_inten_mask: conversion end interrupt mask (see ADINT register)
  * @default_adcmp: default ADC cmp (see ADM3 register)
@@ -65,7 +66,7 @@ 
  * @adivc: specifies if ADVIC register is available
  */
 struct rzg2l_adc_hw_params {
-	u16 default_adsmp;
+	u16 default_adsmp[2];
 	u16 adsmp_mask;
 	u16 adint_inten_mask;
 	u8 default_adcmp;
@@ -89,15 +90,26 @@  struct rzg2l_adc {
 	u16 last_val[RZG2L_ADC_MAX_CHANNELS];
 };
 
-static const char * const rzg2l_adc_channel_name[] = {
-	"adc0",
-	"adc1",
-	"adc2",
-	"adc3",
-	"adc4",
-	"adc5",
-	"adc6",
-	"adc7",
+/**
+ * struct rzg2l_adc_channel - ADC channel descriptor
+ * @name: ADC channel name
+ * @type: ADC channel type
+ */
+struct rzg2l_adc_channel {
+	const char * const name;
+	enum iio_chan_type type;
+};
+
+static const struct rzg2l_adc_channel rzg2l_adc_channels[] = {
+	{ "adc0", IIO_VOLTAGE },
+	{ "adc1", IIO_VOLTAGE },
+	{ "adc2", IIO_VOLTAGE },
+	{ "adc3", IIO_VOLTAGE },
+	{ "adc4", IIO_VOLTAGE },
+	{ "adc5", IIO_VOLTAGE },
+	{ "adc6", IIO_VOLTAGE },
+	{ "adc7", IIO_VOLTAGE },
+	{ "adc8", IIO_TEMP },
 };
 
 static unsigned int rzg2l_adc_readl(struct rzg2l_adc *adc, u32 reg)
@@ -163,9 +175,18 @@  static void rzg2l_set_trigger(struct rzg2l_adc *adc)
 	rzg2l_adc_writel(adc, RZG2L_ADM(1), reg);
 }
 
+static u8 rzg2l_adc_ch_to_adsmp_index(u8 ch)
+{
+	if (rzg2l_adc_channels[ch].type == IIO_VOLTAGE)
+		return 0;
+
+	return 1;
+}
+
 static int rzg2l_adc_conversion_setup(struct rzg2l_adc *adc, u8 ch)
 {
 	const struct rzg2l_adc_hw_params *hw_params = adc->hw_params;
+	u8 index = rzg2l_adc_ch_to_adsmp_index(ch);
 	u32 reg;
 
 	if (rzg2l_adc_readl(adc, RZG2L_ADM(0)) & RZG2L_ADM0_ADBSY)
@@ -179,6 +200,11 @@  static int rzg2l_adc_conversion_setup(struct rzg2l_adc *adc, u8 ch)
 	reg |= BIT(ch);
 	rzg2l_adc_writel(adc, RZG2L_ADM(2), reg);
 
+	reg = rzg2l_adc_readl(adc, RZG2L_ADM(3));
+	reg &= ~hw_params->adsmp_mask;
+	reg |= hw_params->default_adsmp[index];
+	rzg2l_adc_writel(adc, RZG2L_ADM(3), reg);
+
 	/*
 	 * Setup ADINT
 	 * INTS[31] - Select pulse signal
@@ -235,7 +261,7 @@  static int rzg2l_adc_read_raw(struct iio_dev *indio_dev,
 
 	switch (mask) {
 	case IIO_CHAN_INFO_RAW: {
-		if (chan->type != IIO_VOLTAGE)
+		if (chan->type != IIO_VOLTAGE && chan->type != IIO_TEMP)
 			return -EINVAL;
 
 		guard(mutex)(&adc->lock);
@@ -258,7 +284,7 @@  static int rzg2l_adc_read_label(struct iio_dev *iio_dev,
 				const struct iio_chan_spec *chan,
 				char *label)
 {
-	return sysfs_emit(label, "%s\n", rzg2l_adc_channel_name[chan->channel]);
+	return sysfs_emit(label, "%s\n", rzg2l_adc_channels[chan->channel].name);
 }
 
 static const struct iio_info rzg2l_adc_iio_info = {
@@ -332,11 +358,11 @@  static int rzg2l_adc_parse_properties(struct platform_device *pdev, struct rzg2l
 		if (channel >= hw_params->num_channels)
 			return -EINVAL;
 
-		chan_array[i].type = IIO_VOLTAGE;
+		chan_array[i].type = rzg2l_adc_channels[channel].type;
 		chan_array[i].indexed = 1;
 		chan_array[i].channel = channel;
 		chan_array[i].info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
-		chan_array[i].datasheet_name = rzg2l_adc_channel_name[channel];
+		chan_array[i].datasheet_name = rzg2l_adc_channels[channel].name;
 		i++;
 	}
 
@@ -386,7 +412,7 @@  static int rzg2l_adc_hw_init(struct device *dev, struct rzg2l_adc *adc)
 	reg &= ~RZG2L_ADM3_ADCMP_MASK;
 	reg &= ~hw_params->adsmp_mask;
 	reg |= FIELD_PREP(RZG2L_ADM3_ADCMP_MASK, hw_params->default_adcmp) |
-	       hw_params->default_adsmp;
+	       hw_params->default_adsmp[0];
 
 	rzg2l_adc_writel(adc, RZG2L_ADM(3), reg);
 
@@ -469,7 +495,7 @@  static int rzg2l_adc_probe(struct platform_device *pdev)
 static const struct rzg2l_adc_hw_params rzg2l_hw_params = {
 	.num_channels = 8,
 	.default_adcmp = 0xe,
-	.default_adsmp = 0x578,
+	.default_adsmp = { 0x578 },
 	.adsmp_mask = GENMASK(15, 0),
 	.adint_inten_mask = GENMASK(7, 0),
 	.adivc = true