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;
next prev parent 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.