All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jonathan Cameron <jic23@kernel.org>
To: Fabrice Gasnier <fabrice.gasnier@st.com>
Cc: <robh+dt@kernel.org>, <linux@armlinux.org.uk>,
	<mark.rutland@arm.com>, <mcoquelin.stm32@gmail.com>,
	<alexandre.torgue@st.com>, <lars@metafoo.de>, <knaack.h@gmx.de>,
	<pmeerw@pmeerw.net>, <linux-iio@vger.kernel.org>,
	<devicetree@vger.kernel.org>,
	<linux-arm-kernel@lists.infradead.org>,
	<linux-kernel@vger.kernel.org>, <benjamin.gaignard@linaro.org>,
	<benjamin.gaignard@st.com>
Subject: Re: [PATCH 3/3] iio: adc: stm32: add support for differential channels
Date: Sat, 21 Oct 2017 20:25:49 +0100	[thread overview]
Message-ID: <20171021202549.7dad79c1@archlinux> (raw)
In-Reply-To: <1508246145-19417-4-git-send-email-fabrice.gasnier@st.com>

On Tue, 17 Oct 2017 15:15:45 +0200
Fabrice Gasnier <fabrice.gasnier@st.com> wrote:

> STM32H7 ADC channels can be configured either as single ended or
> differential with 'st,adc-channels' or 'st,adc-diff-channels'
> (positive and negative input pair: <vinp vinn>, ...).
> 
> Differential channels have different offset and scale, from spec:
> raw value = (full_scale / 2) * (1 + (vinp - vinn) / vref).
> Add offset attribute.
> 
> Differential channels are selected by DIFSEL register. Negative
> inputs must be added to pre-selected channels as well (PCSEL).
> 
> Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
Looks fine to me. Will wait to see what others think on the binding in
particular.  I suppose that given we allow control over which single ended
channels are registered, it makes sense to do it for differential channels
as well.

Thanks,

Jonathan
> ---
>  drivers/iio/adc/stm32-adc.c | 123 +++++++++++++++++++++++++++++++++++++-------
>  1 file changed, 103 insertions(+), 20 deletions(-)
> 
> diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c
> index c95e6f7..cc7ca50 100644
> --- a/drivers/iio/adc/stm32-adc.c
> +++ b/drivers/iio/adc/stm32-adc.c
> @@ -92,6 +92,7 @@
>  #define STM32H7_ADC_SQR3		0x38
>  #define STM32H7_ADC_SQR4		0x3C
>  #define STM32H7_ADC_DR			0x40
> +#define STM32H7_ADC_DIFSEL		0xC0
>  #define STM32H7_ADC_CALFACT		0xC4
>  #define STM32H7_ADC_CALFACT2		0xC8
>  
> @@ -154,7 +155,7 @@ enum stm32h7_adc_dmngt {
>  #define STM32H7_BOOST_CLKRATE		20000000UL
>  
>  #define STM32_ADC_CH_MAX		20	/* max number of channels */
> -#define STM32_ADC_CH_SZ			5	/* max channel name size */
> +#define STM32_ADC_CH_SZ			10	/* max channel name size */
>  #define STM32_ADC_MAX_SQ		16	/* SQ1..SQ16 */
>  #define STM32_ADC_MAX_SMP		7	/* SMPx range is [0..7] */
>  #define STM32_ADC_TIMEOUT_US		100000
> @@ -299,6 +300,7 @@ struct stm32_adc_cfg {
>   * @rx_buf:		dma rx buffer cpu address
>   * @rx_dma_buf:		dma rx buffer bus address
>   * @rx_buf_sz:		dma rx buffer size
> + * @difsel		bitmask to set single-ended/differential channel
>   * @pcsel		bitmask to preselect channels on some devices
>   * @smpr_val:		sampling time settings (e.g. smpr1 / smpr2)
>   * @cal:		optional calibration data on some devices
> @@ -321,12 +323,18 @@ struct stm32_adc {
>  	u8			*rx_buf;
>  	dma_addr_t		rx_dma_buf;
>  	unsigned int		rx_buf_sz;
> +	u32			difsel;
>  	u32			pcsel;
>  	u32			smpr_val[2];
>  	struct stm32_adc_calib	cal;
>  	char			chan_name[STM32_ADC_CH_MAX][STM32_ADC_CH_SZ];
>  };
>  
> +struct stm32_adc_diff_channel {
> +	u32 vinp;
> +	u32 vinn;
> +};
> +
>  /**
>   * struct stm32_adc_info - stm32 ADC, per instance config data
>   * @max_channels:	Number of channels
> @@ -944,15 +952,19 @@ static int stm32h7_adc_selfcalib(struct stm32_adc *adc)
>   * stm32h7_adc_prepare() - Leave power down mode to enable ADC.
>   * @adc: stm32 adc instance
>   * Leave power down mode.
> + * Configure channels as single ended or differential before enabling ADC.
>   * Enable ADC.
>   * Restore calibration data.
> - * Pre-select channels that may be used in PCSEL (required by input MUX / IO).
> + * Pre-select channels that may be used in PCSEL (required by input MUX / IO):
> + * - Only one input is selected for single ended (e.g. 'vinp')
> + * - Two inputs are selected for differential channels (e.g. 'vinp' & 'vinn')
>   */
>  static int stm32h7_adc_prepare(struct stm32_adc *adc)
>  {
>  	int ret;
>  
>  	stm32h7_adc_exit_pwr_down(adc);
> +	stm32_adc_writel(adc, STM32H7_ADC_DIFSEL, adc->difsel);
>  
>  	ret = stm32h7_adc_enable(adc);
>  	if (ret)
> @@ -1224,10 +1236,23 @@ static int stm32_adc_read_raw(struct iio_dev *indio_dev,
>  		return ret;
>  
>  	case IIO_CHAN_INFO_SCALE:
> -		*val = adc->common->vref_mv;
> -		*val2 = chan->scan_type.realbits;
> +		if (chan->differential) {
> +			*val = adc->common->vref_mv * 2;
> +			*val2 = chan->scan_type.realbits;
> +		} else {
> +			*val = adc->common->vref_mv;
> +			*val2 = chan->scan_type.realbits;
> +		}
>  		return IIO_VAL_FRACTIONAL_LOG2;
>  
> +	case IIO_CHAN_INFO_OFFSET:
> +		if (chan->differential)
> +			/* ADC_full_scale / 2 */
> +			*val = -((1 << chan->scan_type.realbits) / 2);
> +		else
> +			*val = 0;
> +		return IIO_VAL_INT;
> +
>  	default:
>  		return -EINVAL;
>  	}
> @@ -1591,29 +1616,39 @@ static void stm32_adc_smpr_init(struct stm32_adc *adc, int channel, u32 smp_ns)
>  
>  static void stm32_adc_chan_init_one(struct iio_dev *indio_dev,
>  				    struct iio_chan_spec *chan, u32 val,
> -				    int scan_index, u32 smp)
> +				    u32 val2, int scan_index, bool differential)
val and val2 are slightly odd names in here.
>  {
>  	struct stm32_adc *adc = iio_priv(indio_dev);
>  	char *name = adc->chan_name[val];
>  
>  	chan->type = IIO_VOLTAGE;
>  	chan->channel = val;
> -	snprintf(name, STM32_ADC_CH_SZ, "in%d", val);
> +	if (differential) {
> +		chan->differential = 1;
> +		chan->channel2 = val2;
> +		snprintf(name, STM32_ADC_CH_SZ, "in%d-in%d", val, val2);
> +	} else {
> +		snprintf(name, STM32_ADC_CH_SZ, "in%d", val);
> +	}
>  	chan->datasheet_name = name;
>  	chan->scan_index = scan_index;
>  	chan->indexed = 1;
>  	chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> -	chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
> +	chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |
> +					 BIT(IIO_CHAN_INFO_OFFSET);
>  	chan->scan_type.sign = 'u';
>  	chan->scan_type.realbits = adc->cfg->adc_info->resolutions[adc->res];
>  	chan->scan_type.storagebits = 16;
>  	chan->ext_info = stm32_adc_ext_info;
>  
> -	/* Prepare sampling time settings */
> -	stm32_adc_smpr_init(adc, chan->channel, smp);
> -
>  	/* pre-build selected channels mask */
>  	adc->pcsel |= BIT(chan->channel);
> +	if (differential) {
> +		/* pre-build diff channels mask */
> +		adc->difsel |= BIT(chan->channel);
> +		/* Also add negative input to pre-selected channels */
> +		adc->pcsel |= BIT(chan->channel2);
> +	}
>  }
>  
>  static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
> @@ -1621,17 +1656,40 @@ static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
>  	struct device_node *node = indio_dev->dev.of_node;
>  	struct stm32_adc *adc = iio_priv(indio_dev);
>  	const struct stm32_adc_info *adc_info = adc->cfg->adc_info;
> +	struct stm32_adc_diff_channel diff[STM32_ADC_CH_MAX];
>  	struct property *prop;
>  	const __be32 *cur;
>  	struct iio_chan_spec *channels;
> -	int scan_index = 0, num_channels, ret;
> +	int scan_index = 0, num_channels = 0, num_diff = 0, ret, i;
>  	u32 val, smp = 0;
>  
> -	num_channels = of_property_count_u32_elems(node, "st,adc-channels");
> -	if (num_channels < 0 ||
> -	    num_channels > adc_info->max_channels) {
> +	ret = of_property_count_u32_elems(node, "st,adc-channels");
> +	if (ret > adc_info->max_channels) {
>  		dev_err(&indio_dev->dev, "Bad st,adc-channels?\n");
> -		return num_channels < 0 ? num_channels : -EINVAL;
> +		return -EINVAL;
> +	} else if (ret > 0) {
> +		num_channels += ret;
> +	}
> +
> +	ret = of_property_count_elems_of_size(node, "st,adc-diff-channels",
> +					      sizeof(*diff));
> +	if (ret > adc_info->max_channels) {
> +		dev_err(&indio_dev->dev, "Bad st,adc-diff-channels?\n");
> +		return -EINVAL;
> +	} else if (ret > 0) {
> +		int size = ret * sizeof(*diff) / sizeof(u32);
> +
> +		num_diff = ret;
> +		num_channels += ret;
> +		ret = of_property_read_u32_array(node, "st,adc-diff-channels",
> +						 (u32 *)diff, size);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (!num_channels) {
> +		dev_err(&indio_dev->dev, "No channels configured\n");
> +		return -ENODATA;
>  	}
>  
>  	/* Optional sample time is provided either for each, or all channels */
> @@ -1652,6 +1710,33 @@ static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
>  			return -EINVAL;
>  		}
>  
> +		/* Channel can't be configured both as single-ended & diff */
> +		for (i = 0; i < num_diff; i++) {
> +			if (val == diff[i].vinp) {
> +				dev_err(&indio_dev->dev,
> +					"channel %d miss-configured\n",	val);
> +				return -EINVAL;
> +			}
> +		}
> +		stm32_adc_chan_init_one(indio_dev, &channels[scan_index], val,
> +					0, scan_index, false);
> +		scan_index++;
> +	}
> +
> +	for (i = 0; i < num_diff; i++) {
> +		if (diff[i].vinp >= adc_info->max_channels ||
> +		    diff[i].vinn >= adc_info->max_channels) {
> +			dev_err(&indio_dev->dev, "Invalid channel in%d-in%d\n",
> +				diff[i].vinp, diff[i].vinn);
> +			return -EINVAL;
> +		}
> +		stm32_adc_chan_init_one(indio_dev, &channels[scan_index],
> +					diff[i].vinp, diff[i].vinn, scan_index,
> +					true);
> +		scan_index++;
> +	}
> +
> +	for (i = 0; i < scan_index; i++) {
>  		/*
>  		 * Using of_property_read_u32_index(), smp value will only be
>  		 * modified if valid u32 value can be decoded. This allows to
> @@ -1659,11 +1744,9 @@ static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
>  		 * value per channel.
>  		 */
>  		of_property_read_u32_index(node, "st,min-sample-time-nsecs",
> -					   scan_index, &smp);
> -
> -		stm32_adc_chan_init_one(indio_dev, &channels[scan_index],
> -					val, scan_index, smp);
> -		scan_index++;
> +					   i, &smp);
> +		/* Prepare sampling time settings */
> +		stm32_adc_smpr_init(adc, channels[i].channel, smp);
>  	}
>  
>  	indio_dev->num_channels = scan_index;


WARNING: multiple messages have this Message-ID (diff)
From: jic23@kernel.org (Jonathan Cameron)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH 3/3] iio: adc: stm32: add support for differential channels
Date: Sat, 21 Oct 2017 20:25:49 +0100	[thread overview]
Message-ID: <20171021202549.7dad79c1@archlinux> (raw)
In-Reply-To: <1508246145-19417-4-git-send-email-fabrice.gasnier@st.com>

On Tue, 17 Oct 2017 15:15:45 +0200
Fabrice Gasnier <fabrice.gasnier@st.com> wrote:

> STM32H7 ADC channels can be configured either as single ended or
> differential with 'st,adc-channels' or 'st,adc-diff-channels'
> (positive and negative input pair: <vinp vinn>, ...).
> 
> Differential channels have different offset and scale, from spec:
> raw value = (full_scale / 2) * (1 + (vinp - vinn) / vref).
> Add offset attribute.
> 
> Differential channels are selected by DIFSEL register. Negative
> inputs must be added to pre-selected channels as well (PCSEL).
> 
> Signed-off-by: Fabrice Gasnier <fabrice.gasnier@st.com>
Looks fine to me. Will wait to see what others think on the binding in
particular.  I suppose that given we allow control over which single ended
channels are registered, it makes sense to do it for differential channels
as well.

Thanks,

Jonathan
> ---
>  drivers/iio/adc/stm32-adc.c | 123 +++++++++++++++++++++++++++++++++++++-------
>  1 file changed, 103 insertions(+), 20 deletions(-)
> 
> diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c
> index c95e6f7..cc7ca50 100644
> --- a/drivers/iio/adc/stm32-adc.c
> +++ b/drivers/iio/adc/stm32-adc.c
> @@ -92,6 +92,7 @@
>  #define STM32H7_ADC_SQR3		0x38
>  #define STM32H7_ADC_SQR4		0x3C
>  #define STM32H7_ADC_DR			0x40
> +#define STM32H7_ADC_DIFSEL		0xC0
>  #define STM32H7_ADC_CALFACT		0xC4
>  #define STM32H7_ADC_CALFACT2		0xC8
>  
> @@ -154,7 +155,7 @@ enum stm32h7_adc_dmngt {
>  #define STM32H7_BOOST_CLKRATE		20000000UL
>  
>  #define STM32_ADC_CH_MAX		20	/* max number of channels */
> -#define STM32_ADC_CH_SZ			5	/* max channel name size */
> +#define STM32_ADC_CH_SZ			10	/* max channel name size */
>  #define STM32_ADC_MAX_SQ		16	/* SQ1..SQ16 */
>  #define STM32_ADC_MAX_SMP		7	/* SMPx range is [0..7] */
>  #define STM32_ADC_TIMEOUT_US		100000
> @@ -299,6 +300,7 @@ struct stm32_adc_cfg {
>   * @rx_buf:		dma rx buffer cpu address
>   * @rx_dma_buf:		dma rx buffer bus address
>   * @rx_buf_sz:		dma rx buffer size
> + * @difsel		bitmask to set single-ended/differential channel
>   * @pcsel		bitmask to preselect channels on some devices
>   * @smpr_val:		sampling time settings (e.g. smpr1 / smpr2)
>   * @cal:		optional calibration data on some devices
> @@ -321,12 +323,18 @@ struct stm32_adc {
>  	u8			*rx_buf;
>  	dma_addr_t		rx_dma_buf;
>  	unsigned int		rx_buf_sz;
> +	u32			difsel;
>  	u32			pcsel;
>  	u32			smpr_val[2];
>  	struct stm32_adc_calib	cal;
>  	char			chan_name[STM32_ADC_CH_MAX][STM32_ADC_CH_SZ];
>  };
>  
> +struct stm32_adc_diff_channel {
> +	u32 vinp;
> +	u32 vinn;
> +};
> +
>  /**
>   * struct stm32_adc_info - stm32 ADC, per instance config data
>   * @max_channels:	Number of channels
> @@ -944,15 +952,19 @@ static int stm32h7_adc_selfcalib(struct stm32_adc *adc)
>   * stm32h7_adc_prepare() - Leave power down mode to enable ADC.
>   * @adc: stm32 adc instance
>   * Leave power down mode.
> + * Configure channels as single ended or differential before enabling ADC.
>   * Enable ADC.
>   * Restore calibration data.
> - * Pre-select channels that may be used in PCSEL (required by input MUX / IO).
> + * Pre-select channels that may be used in PCSEL (required by input MUX / IO):
> + * - Only one input is selected for single ended (e.g. 'vinp')
> + * - Two inputs are selected for differential channels (e.g. 'vinp' & 'vinn')
>   */
>  static int stm32h7_adc_prepare(struct stm32_adc *adc)
>  {
>  	int ret;
>  
>  	stm32h7_adc_exit_pwr_down(adc);
> +	stm32_adc_writel(adc, STM32H7_ADC_DIFSEL, adc->difsel);
>  
>  	ret = stm32h7_adc_enable(adc);
>  	if (ret)
> @@ -1224,10 +1236,23 @@ static int stm32_adc_read_raw(struct iio_dev *indio_dev,
>  		return ret;
>  
>  	case IIO_CHAN_INFO_SCALE:
> -		*val = adc->common->vref_mv;
> -		*val2 = chan->scan_type.realbits;
> +		if (chan->differential) {
> +			*val = adc->common->vref_mv * 2;
> +			*val2 = chan->scan_type.realbits;
> +		} else {
> +			*val = adc->common->vref_mv;
> +			*val2 = chan->scan_type.realbits;
> +		}
>  		return IIO_VAL_FRACTIONAL_LOG2;
>  
> +	case IIO_CHAN_INFO_OFFSET:
> +		if (chan->differential)
> +			/* ADC_full_scale / 2 */
> +			*val = -((1 << chan->scan_type.realbits) / 2);
> +		else
> +			*val = 0;
> +		return IIO_VAL_INT;
> +
>  	default:
>  		return -EINVAL;
>  	}
> @@ -1591,29 +1616,39 @@ static void stm32_adc_smpr_init(struct stm32_adc *adc, int channel, u32 smp_ns)
>  
>  static void stm32_adc_chan_init_one(struct iio_dev *indio_dev,
>  				    struct iio_chan_spec *chan, u32 val,
> -				    int scan_index, u32 smp)
> +				    u32 val2, int scan_index, bool differential)
val and val2 are slightly odd names in here.
>  {
>  	struct stm32_adc *adc = iio_priv(indio_dev);
>  	char *name = adc->chan_name[val];
>  
>  	chan->type = IIO_VOLTAGE;
>  	chan->channel = val;
> -	snprintf(name, STM32_ADC_CH_SZ, "in%d", val);
> +	if (differential) {
> +		chan->differential = 1;
> +		chan->channel2 = val2;
> +		snprintf(name, STM32_ADC_CH_SZ, "in%d-in%d", val, val2);
> +	} else {
> +		snprintf(name, STM32_ADC_CH_SZ, "in%d", val);
> +	}
>  	chan->datasheet_name = name;
>  	chan->scan_index = scan_index;
>  	chan->indexed = 1;
>  	chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> -	chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
> +	chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |
> +					 BIT(IIO_CHAN_INFO_OFFSET);
>  	chan->scan_type.sign = 'u';
>  	chan->scan_type.realbits = adc->cfg->adc_info->resolutions[adc->res];
>  	chan->scan_type.storagebits = 16;
>  	chan->ext_info = stm32_adc_ext_info;
>  
> -	/* Prepare sampling time settings */
> -	stm32_adc_smpr_init(adc, chan->channel, smp);
> -
>  	/* pre-build selected channels mask */
>  	adc->pcsel |= BIT(chan->channel);
> +	if (differential) {
> +		/* pre-build diff channels mask */
> +		adc->difsel |= BIT(chan->channel);
> +		/* Also add negative input to pre-selected channels */
> +		adc->pcsel |= BIT(chan->channel2);
> +	}
>  }
>  
>  static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
> @@ -1621,17 +1656,40 @@ static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
>  	struct device_node *node = indio_dev->dev.of_node;
>  	struct stm32_adc *adc = iio_priv(indio_dev);
>  	const struct stm32_adc_info *adc_info = adc->cfg->adc_info;
> +	struct stm32_adc_diff_channel diff[STM32_ADC_CH_MAX];
>  	struct property *prop;
>  	const __be32 *cur;
>  	struct iio_chan_spec *channels;
> -	int scan_index = 0, num_channels, ret;
> +	int scan_index = 0, num_channels = 0, num_diff = 0, ret, i;
>  	u32 val, smp = 0;
>  
> -	num_channels = of_property_count_u32_elems(node, "st,adc-channels");
> -	if (num_channels < 0 ||
> -	    num_channels > adc_info->max_channels) {
> +	ret = of_property_count_u32_elems(node, "st,adc-channels");
> +	if (ret > adc_info->max_channels) {
>  		dev_err(&indio_dev->dev, "Bad st,adc-channels?\n");
> -		return num_channels < 0 ? num_channels : -EINVAL;
> +		return -EINVAL;
> +	} else if (ret > 0) {
> +		num_channels += ret;
> +	}
> +
> +	ret = of_property_count_elems_of_size(node, "st,adc-diff-channels",
> +					      sizeof(*diff));
> +	if (ret > adc_info->max_channels) {
> +		dev_err(&indio_dev->dev, "Bad st,adc-diff-channels?\n");
> +		return -EINVAL;
> +	} else if (ret > 0) {
> +		int size = ret * sizeof(*diff) / sizeof(u32);
> +
> +		num_diff = ret;
> +		num_channels += ret;
> +		ret = of_property_read_u32_array(node, "st,adc-diff-channels",
> +						 (u32 *)diff, size);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (!num_channels) {
> +		dev_err(&indio_dev->dev, "No channels configured\n");
> +		return -ENODATA;
>  	}
>  
>  	/* Optional sample time is provided either for each, or all channels */
> @@ -1652,6 +1710,33 @@ static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
>  			return -EINVAL;
>  		}
>  
> +		/* Channel can't be configured both as single-ended & diff */
> +		for (i = 0; i < num_diff; i++) {
> +			if (val == diff[i].vinp) {
> +				dev_err(&indio_dev->dev,
> +					"channel %d miss-configured\n",	val);
> +				return -EINVAL;
> +			}
> +		}
> +		stm32_adc_chan_init_one(indio_dev, &channels[scan_index], val,
> +					0, scan_index, false);
> +		scan_index++;
> +	}
> +
> +	for (i = 0; i < num_diff; i++) {
> +		if (diff[i].vinp >= adc_info->max_channels ||
> +		    diff[i].vinn >= adc_info->max_channels) {
> +			dev_err(&indio_dev->dev, "Invalid channel in%d-in%d\n",
> +				diff[i].vinp, diff[i].vinn);
> +			return -EINVAL;
> +		}
> +		stm32_adc_chan_init_one(indio_dev, &channels[scan_index],
> +					diff[i].vinp, diff[i].vinn, scan_index,
> +					true);
> +		scan_index++;
> +	}
> +
> +	for (i = 0; i < scan_index; i++) {
>  		/*
>  		 * Using of_property_read_u32_index(), smp value will only be
>  		 * modified if valid u32 value can be decoded. This allows to
> @@ -1659,11 +1744,9 @@ static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
>  		 * value per channel.
>  		 */
>  		of_property_read_u32_index(node, "st,min-sample-time-nsecs",
> -					   scan_index, &smp);
> -
> -		stm32_adc_chan_init_one(indio_dev, &channels[scan_index],
> -					val, scan_index, smp);
> -		scan_index++;
> +					   i, &smp);
> +		/* Prepare sampling time settings */
> +		stm32_adc_smpr_init(adc, channels[i].channel, smp);
>  	}
>  
>  	indio_dev->num_channels = scan_index;

WARNING: multiple messages have this Message-ID (diff)
From: Jonathan Cameron <jic23-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
To: Fabrice Gasnier <fabrice.gasnier-qxv4g6HH51o@public.gmane.org>
Cc: robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org,
	linux-I+IVW8TIWO2tmTQ+vhA3Yw@public.gmane.org,
	mark.rutland-5wv7dgnIgG8@public.gmane.org,
	mcoquelin.stm32-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org,
	alexandre.torgue-qxv4g6HH51o@public.gmane.org,
	lars-Qo5EllUWu/uELgA04lAiVw@public.gmane.org,
	knaack.h-Mmb7MZpHnFY@public.gmane.org,
	pmeerw-jW+XmwGofnusTnJN9+BGXg@public.gmane.org,
	linux-iio-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	benjamin.gaignard-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
	benjamin.gaignard-qxv4g6HH51o@public.gmane.org
Subject: Re: [PATCH 3/3] iio: adc: stm32: add support for differential channels
Date: Sat, 21 Oct 2017 20:25:49 +0100	[thread overview]
Message-ID: <20171021202549.7dad79c1@archlinux> (raw)
In-Reply-To: <1508246145-19417-4-git-send-email-fabrice.gasnier-qxv4g6HH51o@public.gmane.org>

On Tue, 17 Oct 2017 15:15:45 +0200
Fabrice Gasnier <fabrice.gasnier-qxv4g6HH51o@public.gmane.org> wrote:

> STM32H7 ADC channels can be configured either as single ended or
> differential with 'st,adc-channels' or 'st,adc-diff-channels'
> (positive and negative input pair: <vinp vinn>, ...).
> 
> Differential channels have different offset and scale, from spec:
> raw value = (full_scale / 2) * (1 + (vinp - vinn) / vref).
> Add offset attribute.
> 
> Differential channels are selected by DIFSEL register. Negative
> inputs must be added to pre-selected channels as well (PCSEL).
> 
> Signed-off-by: Fabrice Gasnier <fabrice.gasnier-qxv4g6HH51o@public.gmane.org>
Looks fine to me. Will wait to see what others think on the binding in
particular.  I suppose that given we allow control over which single ended
channels are registered, it makes sense to do it for differential channels
as well.

Thanks,

Jonathan
> ---
>  drivers/iio/adc/stm32-adc.c | 123 +++++++++++++++++++++++++++++++++++++-------
>  1 file changed, 103 insertions(+), 20 deletions(-)
> 
> diff --git a/drivers/iio/adc/stm32-adc.c b/drivers/iio/adc/stm32-adc.c
> index c95e6f7..cc7ca50 100644
> --- a/drivers/iio/adc/stm32-adc.c
> +++ b/drivers/iio/adc/stm32-adc.c
> @@ -92,6 +92,7 @@
>  #define STM32H7_ADC_SQR3		0x38
>  #define STM32H7_ADC_SQR4		0x3C
>  #define STM32H7_ADC_DR			0x40
> +#define STM32H7_ADC_DIFSEL		0xC0
>  #define STM32H7_ADC_CALFACT		0xC4
>  #define STM32H7_ADC_CALFACT2		0xC8
>  
> @@ -154,7 +155,7 @@ enum stm32h7_adc_dmngt {
>  #define STM32H7_BOOST_CLKRATE		20000000UL
>  
>  #define STM32_ADC_CH_MAX		20	/* max number of channels */
> -#define STM32_ADC_CH_SZ			5	/* max channel name size */
> +#define STM32_ADC_CH_SZ			10	/* max channel name size */
>  #define STM32_ADC_MAX_SQ		16	/* SQ1..SQ16 */
>  #define STM32_ADC_MAX_SMP		7	/* SMPx range is [0..7] */
>  #define STM32_ADC_TIMEOUT_US		100000
> @@ -299,6 +300,7 @@ struct stm32_adc_cfg {
>   * @rx_buf:		dma rx buffer cpu address
>   * @rx_dma_buf:		dma rx buffer bus address
>   * @rx_buf_sz:		dma rx buffer size
> + * @difsel		bitmask to set single-ended/differential channel
>   * @pcsel		bitmask to preselect channels on some devices
>   * @smpr_val:		sampling time settings (e.g. smpr1 / smpr2)
>   * @cal:		optional calibration data on some devices
> @@ -321,12 +323,18 @@ struct stm32_adc {
>  	u8			*rx_buf;
>  	dma_addr_t		rx_dma_buf;
>  	unsigned int		rx_buf_sz;
> +	u32			difsel;
>  	u32			pcsel;
>  	u32			smpr_val[2];
>  	struct stm32_adc_calib	cal;
>  	char			chan_name[STM32_ADC_CH_MAX][STM32_ADC_CH_SZ];
>  };
>  
> +struct stm32_adc_diff_channel {
> +	u32 vinp;
> +	u32 vinn;
> +};
> +
>  /**
>   * struct stm32_adc_info - stm32 ADC, per instance config data
>   * @max_channels:	Number of channels
> @@ -944,15 +952,19 @@ static int stm32h7_adc_selfcalib(struct stm32_adc *adc)
>   * stm32h7_adc_prepare() - Leave power down mode to enable ADC.
>   * @adc: stm32 adc instance
>   * Leave power down mode.
> + * Configure channels as single ended or differential before enabling ADC.
>   * Enable ADC.
>   * Restore calibration data.
> - * Pre-select channels that may be used in PCSEL (required by input MUX / IO).
> + * Pre-select channels that may be used in PCSEL (required by input MUX / IO):
> + * - Only one input is selected for single ended (e.g. 'vinp')
> + * - Two inputs are selected for differential channels (e.g. 'vinp' & 'vinn')
>   */
>  static int stm32h7_adc_prepare(struct stm32_adc *adc)
>  {
>  	int ret;
>  
>  	stm32h7_adc_exit_pwr_down(adc);
> +	stm32_adc_writel(adc, STM32H7_ADC_DIFSEL, adc->difsel);
>  
>  	ret = stm32h7_adc_enable(adc);
>  	if (ret)
> @@ -1224,10 +1236,23 @@ static int stm32_adc_read_raw(struct iio_dev *indio_dev,
>  		return ret;
>  
>  	case IIO_CHAN_INFO_SCALE:
> -		*val = adc->common->vref_mv;
> -		*val2 = chan->scan_type.realbits;
> +		if (chan->differential) {
> +			*val = adc->common->vref_mv * 2;
> +			*val2 = chan->scan_type.realbits;
> +		} else {
> +			*val = adc->common->vref_mv;
> +			*val2 = chan->scan_type.realbits;
> +		}
>  		return IIO_VAL_FRACTIONAL_LOG2;
>  
> +	case IIO_CHAN_INFO_OFFSET:
> +		if (chan->differential)
> +			/* ADC_full_scale / 2 */
> +			*val = -((1 << chan->scan_type.realbits) / 2);
> +		else
> +			*val = 0;
> +		return IIO_VAL_INT;
> +
>  	default:
>  		return -EINVAL;
>  	}
> @@ -1591,29 +1616,39 @@ static void stm32_adc_smpr_init(struct stm32_adc *adc, int channel, u32 smp_ns)
>  
>  static void stm32_adc_chan_init_one(struct iio_dev *indio_dev,
>  				    struct iio_chan_spec *chan, u32 val,
> -				    int scan_index, u32 smp)
> +				    u32 val2, int scan_index, bool differential)
val and val2 are slightly odd names in here.
>  {
>  	struct stm32_adc *adc = iio_priv(indio_dev);
>  	char *name = adc->chan_name[val];
>  
>  	chan->type = IIO_VOLTAGE;
>  	chan->channel = val;
> -	snprintf(name, STM32_ADC_CH_SZ, "in%d", val);
> +	if (differential) {
> +		chan->differential = 1;
> +		chan->channel2 = val2;
> +		snprintf(name, STM32_ADC_CH_SZ, "in%d-in%d", val, val2);
> +	} else {
> +		snprintf(name, STM32_ADC_CH_SZ, "in%d", val);
> +	}
>  	chan->datasheet_name = name;
>  	chan->scan_index = scan_index;
>  	chan->indexed = 1;
>  	chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> -	chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
> +	chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) |
> +					 BIT(IIO_CHAN_INFO_OFFSET);
>  	chan->scan_type.sign = 'u';
>  	chan->scan_type.realbits = adc->cfg->adc_info->resolutions[adc->res];
>  	chan->scan_type.storagebits = 16;
>  	chan->ext_info = stm32_adc_ext_info;
>  
> -	/* Prepare sampling time settings */
> -	stm32_adc_smpr_init(adc, chan->channel, smp);
> -
>  	/* pre-build selected channels mask */
>  	adc->pcsel |= BIT(chan->channel);
> +	if (differential) {
> +		/* pre-build diff channels mask */
> +		adc->difsel |= BIT(chan->channel);
> +		/* Also add negative input to pre-selected channels */
> +		adc->pcsel |= BIT(chan->channel2);
> +	}
>  }
>  
>  static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
> @@ -1621,17 +1656,40 @@ static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
>  	struct device_node *node = indio_dev->dev.of_node;
>  	struct stm32_adc *adc = iio_priv(indio_dev);
>  	const struct stm32_adc_info *adc_info = adc->cfg->adc_info;
> +	struct stm32_adc_diff_channel diff[STM32_ADC_CH_MAX];
>  	struct property *prop;
>  	const __be32 *cur;
>  	struct iio_chan_spec *channels;
> -	int scan_index = 0, num_channels, ret;
> +	int scan_index = 0, num_channels = 0, num_diff = 0, ret, i;
>  	u32 val, smp = 0;
>  
> -	num_channels = of_property_count_u32_elems(node, "st,adc-channels");
> -	if (num_channels < 0 ||
> -	    num_channels > adc_info->max_channels) {
> +	ret = of_property_count_u32_elems(node, "st,adc-channels");
> +	if (ret > adc_info->max_channels) {
>  		dev_err(&indio_dev->dev, "Bad st,adc-channels?\n");
> -		return num_channels < 0 ? num_channels : -EINVAL;
> +		return -EINVAL;
> +	} else if (ret > 0) {
> +		num_channels += ret;
> +	}
> +
> +	ret = of_property_count_elems_of_size(node, "st,adc-diff-channels",
> +					      sizeof(*diff));
> +	if (ret > adc_info->max_channels) {
> +		dev_err(&indio_dev->dev, "Bad st,adc-diff-channels?\n");
> +		return -EINVAL;
> +	} else if (ret > 0) {
> +		int size = ret * sizeof(*diff) / sizeof(u32);
> +
> +		num_diff = ret;
> +		num_channels += ret;
> +		ret = of_property_read_u32_array(node, "st,adc-diff-channels",
> +						 (u32 *)diff, size);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	if (!num_channels) {
> +		dev_err(&indio_dev->dev, "No channels configured\n");
> +		return -ENODATA;
>  	}
>  
>  	/* Optional sample time is provided either for each, or all channels */
> @@ -1652,6 +1710,33 @@ static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
>  			return -EINVAL;
>  		}
>  
> +		/* Channel can't be configured both as single-ended & diff */
> +		for (i = 0; i < num_diff; i++) {
> +			if (val == diff[i].vinp) {
> +				dev_err(&indio_dev->dev,
> +					"channel %d miss-configured\n",	val);
> +				return -EINVAL;
> +			}
> +		}
> +		stm32_adc_chan_init_one(indio_dev, &channels[scan_index], val,
> +					0, scan_index, false);
> +		scan_index++;
> +	}
> +
> +	for (i = 0; i < num_diff; i++) {
> +		if (diff[i].vinp >= adc_info->max_channels ||
> +		    diff[i].vinn >= adc_info->max_channels) {
> +			dev_err(&indio_dev->dev, "Invalid channel in%d-in%d\n",
> +				diff[i].vinp, diff[i].vinn);
> +			return -EINVAL;
> +		}
> +		stm32_adc_chan_init_one(indio_dev, &channels[scan_index],
> +					diff[i].vinp, diff[i].vinn, scan_index,
> +					true);
> +		scan_index++;
> +	}
> +
> +	for (i = 0; i < scan_index; i++) {
>  		/*
>  		 * Using of_property_read_u32_index(), smp value will only be
>  		 * modified if valid u32 value can be decoded. This allows to
> @@ -1659,11 +1744,9 @@ static int stm32_adc_chan_of_init(struct iio_dev *indio_dev)
>  		 * value per channel.
>  		 */
>  		of_property_read_u32_index(node, "st,min-sample-time-nsecs",
> -					   scan_index, &smp);
> -
> -		stm32_adc_chan_init_one(indio_dev, &channels[scan_index],
> -					val, scan_index, smp);
> -		scan_index++;
> +					   i, &smp);
> +		/* Prepare sampling time settings */
> +		stm32_adc_smpr_init(adc, channels[i].channel, smp);
>  	}
>  
>  	indio_dev->num_channels = scan_index;

  reply	other threads:[~2017-10-21 19:25 UTC|newest]

Thread overview: 45+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-10-17 13:15 [PATCH 0/3] iio: adc: stm32: Add support for differential channels Fabrice Gasnier
2017-10-17 13:15 ` Fabrice Gasnier
2017-10-17 13:15 ` Fabrice Gasnier
2017-10-17 13:15 ` [PATCH 1/3] dt-bindings: iio: adc: stm32: add support for diff channels Fabrice Gasnier
2017-10-17 13:15   ` Fabrice Gasnier
2017-10-17 13:15   ` Fabrice Gasnier
2017-10-21 17:54   ` Jonathan Cameron
2017-10-21 17:54     ` Jonathan Cameron
2017-10-21 17:54     ` Jonathan Cameron
2017-10-21 17:55     ` Jonathan Cameron
2017-10-21 17:55       ` Jonathan Cameron
2017-10-21 17:55       ` Jonathan Cameron
2017-10-21 19:23     ` Jonathan Cameron
2017-10-21 19:23       ` Jonathan Cameron
2017-10-21 19:23       ` Jonathan Cameron
2017-10-23  8:06       ` Fabrice Gasnier
2017-10-23  8:06         ` Fabrice Gasnier
2017-10-23  8:06         ` Fabrice Gasnier
2017-10-23 13:09         ` Jonathan Cameron
2017-10-23 13:09           ` Jonathan Cameron
2017-10-23 13:09           ` Jonathan Cameron
2017-10-24 16:41   ` Rob Herring
2017-10-24 16:41     ` Rob Herring
2017-10-24 16:41     ` Rob Herring
2017-10-24 18:42     ` Jonathan Cameron
2017-10-24 18:42       ` Jonathan Cameron
2017-10-24 18:42       ` Jonathan Cameron
2017-10-25  8:05       ` Fabrice Gasnier
2017-10-25  8:05         ` Fabrice Gasnier
2017-10-25  8:05         ` Fabrice Gasnier
2017-10-17 13:15 ` [PATCH 2/3] iio: adc: stm32: remove const channel names definition Fabrice Gasnier
2017-10-17 13:15   ` Fabrice Gasnier
2017-10-17 13:15   ` Fabrice Gasnier
2017-10-21 17:59   ` Jonathan Cameron
2017-10-21 17:59     ` Jonathan Cameron
2017-10-21 17:59     ` Jonathan Cameron
2017-10-17 13:15 ` [PATCH 3/3] iio: adc: stm32: add support for differential channels Fabrice Gasnier
2017-10-17 13:15   ` Fabrice Gasnier
2017-10-17 13:15   ` Fabrice Gasnier
2017-10-21 19:25   ` Jonathan Cameron [this message]
2017-10-21 19:25     ` Jonathan Cameron
2017-10-21 19:25     ` Jonathan Cameron
2017-10-23  8:09     ` Fabrice Gasnier
2017-10-23  8:09       ` Fabrice Gasnier
2017-10-23  8:09       ` Fabrice Gasnier

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20171021202549.7dad79c1@archlinux \
    --to=jic23@kernel.org \
    --cc=alexandre.torgue@st.com \
    --cc=benjamin.gaignard@linaro.org \
    --cc=benjamin.gaignard@st.com \
    --cc=devicetree@vger.kernel.org \
    --cc=fabrice.gasnier@st.com \
    --cc=knaack.h@gmx.de \
    --cc=lars@metafoo.de \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@armlinux.org.uk \
    --cc=mark.rutland@arm.com \
    --cc=mcoquelin.stm32@gmail.com \
    --cc=pmeerw@pmeerw.net \
    --cc=robh+dt@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.