2022-09-03 05:05:36

by Oleksij Rempel

[permalink] [raw]
Subject: [PATCH v5 2/3] iio: adc: tsc2046: add vref support

If VREF pin is attached, we should use external VREF source instead of
the internal. Otherwise we will get wrong measurements on some of the channel
types.

Signed-off-by: Oleksij Rempel <[email protected]>
---
changes v5:
- add "the" before channel
- refactor error handling on regulator registration
- use MILLI instead of 1000
changes v4:
- use vref_reg pointer instead of bool use_internal_vref
- move regulator registration to a separate function
- rework error handling
- add devm_add_action_or_reset
---
drivers/iio/adc/ti-tsc2046.c | 57 ++++++++++++++++++++++++++++++++++--
1 file changed, 55 insertions(+), 2 deletions(-)

diff --git a/drivers/iio/adc/ti-tsc2046.c b/drivers/iio/adc/ti-tsc2046.c
index 0d9436a69cbfb..c7601b29b3bef 100644
--- a/drivers/iio/adc/ti-tsc2046.c
+++ b/drivers/iio/adc/ti-tsc2046.c
@@ -8,7 +8,9 @@
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/module.h>
+#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
+#include <linux/units.h>

#include <asm/unaligned.h>

@@ -175,6 +177,8 @@ struct tsc2046_adc_priv {
u32 time_per_bit_ns;

struct tsc2046_adc_ch_cfg ch_cfg[TI_TSC2046_MAX_CHAN];
+ unsigned int vref_mv;
+ struct regulator *vref_reg;
};

#define TI_TSC2046_V_CHAN(index, bits, name) \
@@ -252,7 +256,9 @@ static u8 tsc2046_adc_get_cmd(struct tsc2046_adc_priv *priv, int ch_idx,
case TI_TSC2046_ADDR_AUX:
case TI_TSC2046_ADDR_VBAT:
case TI_TSC2046_ADDR_TEMP0:
- pd |= TI_TSC2046_SER | TI_TSC2046_PD1_VREF_ON;
+ pd |= TI_TSC2046_SER;
+ if (!priv->vref_reg)
+ pd |= TI_TSC2046_PD1_VREF_ON;
}

return TI_TSC2046_START | FIELD_PREP(TI_TSC2046_ADDR, ch_idx) | pd;
@@ -468,7 +474,7 @@ static int tsc2046_adc_read_raw(struct iio_dev *indio_dev,
* So, it is better to use external voltage-divider driver
* instead, which is calculating complete chain.
*/
- *val = TI_TSC2046_INT_VREF;
+ *val = priv->vref_mv;
*val2 = chan->scan_type.realbits;
return IIO_VAL_FRACTIONAL_LOG2;
}
@@ -740,6 +746,49 @@ static void tsc2046_adc_parse_fwnode(struct tsc2046_adc_priv *priv)
}
}

+static void tsc2046_adc_regulator_disable(void *data)
+{
+ struct tsc2046_adc_priv *priv = data;
+
+ regulator_disable(priv->vref_reg);
+}
+
+static int tsc2046_adc_configure_regulator(struct tsc2046_adc_priv *priv)
+{
+ struct device *dev = &priv->spi->dev;
+ int ret;
+
+ priv->vref_reg = devm_regulator_get_optional(dev, "vref");
+ if (IS_ERR(priv->vref_reg)) {
+ /* If regulator exists but can't be get, return an error */
+ if (PTR_ERR(priv->vref_reg) != -ENODEV)
+ return PTR_ERR(priv->vref_reg);
+ priv->vref_reg = NULL;
+ }
+ if (!priv->vref_reg) {
+ /* Use internal reference */
+ priv->vref_mv = TI_TSC2046_INT_VREF;
+ return 0;
+ }
+
+ ret = regulator_enable(priv->vref_reg);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, tsc2046_adc_regulator_disable,
+ priv);
+ if (ret)
+ return ret;
+
+ ret = regulator_get_voltage(priv->vref_reg);
+ if (ret < 0)
+ return ret;
+
+ priv->vref_mv = ret / MILLI;
+
+ return 0;
+}
+
static int tsc2046_adc_probe(struct spi_device *spi)
{
const struct tsc2046_adc_dcfg *dcfg;
@@ -781,6 +830,10 @@ static int tsc2046_adc_probe(struct spi_device *spi)
indio_dev->num_channels = dcfg->num_channels;
indio_dev->info = &tsc2046_adc_info;

+ ret = tsc2046_adc_configure_regulator(priv);
+ if (ret)
+ return ret;
+
tsc2046_adc_parse_fwnode(priv);

ret = tsc2046_adc_setup_spi_msg(priv);
--
2.30.2


2022-09-03 09:35:33

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v5 2/3] iio: adc: tsc2046: add vref support

On Sat, Sep 3, 2022 at 8:03 AM Oleksij Rempel <[email protected]> wrote:
>
> If VREF pin is attached, we should use external VREF source instead of
> the internal. Otherwise we will get wrong measurements on some of the channel
> types.

FWIW,
Reviewed-by: Andy Shevchenko <[email protected]>

> Signed-off-by: Oleksij Rempel <[email protected]>
> ---
> changes v5:
> - add "the" before channel
> - refactor error handling on regulator registration
> - use MILLI instead of 1000
> changes v4:
> - use vref_reg pointer instead of bool use_internal_vref
> - move regulator registration to a separate function
> - rework error handling
> - add devm_add_action_or_reset
> ---
> drivers/iio/adc/ti-tsc2046.c | 57 ++++++++++++++++++++++++++++++++++--
> 1 file changed, 55 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/iio/adc/ti-tsc2046.c b/drivers/iio/adc/ti-tsc2046.c
> index 0d9436a69cbfb..c7601b29b3bef 100644
> --- a/drivers/iio/adc/ti-tsc2046.c
> +++ b/drivers/iio/adc/ti-tsc2046.c
> @@ -8,7 +8,9 @@
> #include <linux/bitfield.h>
> #include <linux/delay.h>
> #include <linux/module.h>
> +#include <linux/regulator/consumer.h>
> #include <linux/spi/spi.h>
> +#include <linux/units.h>
>
> #include <asm/unaligned.h>
>
> @@ -175,6 +177,8 @@ struct tsc2046_adc_priv {
> u32 time_per_bit_ns;
>
> struct tsc2046_adc_ch_cfg ch_cfg[TI_TSC2046_MAX_CHAN];
> + unsigned int vref_mv;
> + struct regulator *vref_reg;

I would put pointer first since it will make sizeof(priv) 4 bytes less
on some architectures due to padding elimination.

> };
>
> #define TI_TSC2046_V_CHAN(index, bits, name) \
> @@ -252,7 +256,9 @@ static u8 tsc2046_adc_get_cmd(struct tsc2046_adc_priv *priv, int ch_idx,
> case TI_TSC2046_ADDR_AUX:
> case TI_TSC2046_ADDR_VBAT:
> case TI_TSC2046_ADDR_TEMP0:
> - pd |= TI_TSC2046_SER | TI_TSC2046_PD1_VREF_ON;
> + pd |= TI_TSC2046_SER;
> + if (!priv->vref_reg)
> + pd |= TI_TSC2046_PD1_VREF_ON;
> }
>
> return TI_TSC2046_START | FIELD_PREP(TI_TSC2046_ADDR, ch_idx) | pd;
> @@ -468,7 +474,7 @@ static int tsc2046_adc_read_raw(struct iio_dev *indio_dev,
> * So, it is better to use external voltage-divider driver
> * instead, which is calculating complete chain.
> */
> - *val = TI_TSC2046_INT_VREF;
> + *val = priv->vref_mv;
> *val2 = chan->scan_type.realbits;
> return IIO_VAL_FRACTIONAL_LOG2;
> }
> @@ -740,6 +746,49 @@ static void tsc2046_adc_parse_fwnode(struct tsc2046_adc_priv *priv)
> }
> }
>
> +static void tsc2046_adc_regulator_disable(void *data)
> +{
> + struct tsc2046_adc_priv *priv = data;
> +
> + regulator_disable(priv->vref_reg);
> +}
> +
> +static int tsc2046_adc_configure_regulator(struct tsc2046_adc_priv *priv)
> +{
> + struct device *dev = &priv->spi->dev;
> + int ret;
> +
> + priv->vref_reg = devm_regulator_get_optional(dev, "vref");
> + if (IS_ERR(priv->vref_reg)) {
> + /* If regulator exists but can't be get, return an error */
> + if (PTR_ERR(priv->vref_reg) != -ENODEV)
> + return PTR_ERR(priv->vref_reg);
> + priv->vref_reg = NULL;
> + }
> + if (!priv->vref_reg) {
> + /* Use internal reference */
> + priv->vref_mv = TI_TSC2046_INT_VREF;
> + return 0;
> + }
> +
> + ret = regulator_enable(priv->vref_reg);
> + if (ret)
> + return ret;
> +
> + ret = devm_add_action_or_reset(dev, tsc2046_adc_regulator_disable,
> + priv);
> + if (ret)
> + return ret;
> +
> + ret = regulator_get_voltage(priv->vref_reg);
> + if (ret < 0)
> + return ret;
> +
> + priv->vref_mv = ret / MILLI;
> +
> + return 0;
> +}
> +
> static int tsc2046_adc_probe(struct spi_device *spi)
> {
> const struct tsc2046_adc_dcfg *dcfg;
> @@ -781,6 +830,10 @@ static int tsc2046_adc_probe(struct spi_device *spi)
> indio_dev->num_channels = dcfg->num_channels;
> indio_dev->info = &tsc2046_adc_info;
>
> + ret = tsc2046_adc_configure_regulator(priv);
> + if (ret)
> + return ret;
> +
> tsc2046_adc_parse_fwnode(priv);
>
> ret = tsc2046_adc_setup_spi_msg(priv);
> --
> 2.30.2
>


--
With Best Regards,
Andy Shevchenko