iio: adc: stm32: add support for differential channels
authorFabrice Gasnier <fabrice.gasnier@st.com>
Wed, 25 Oct 2017 09:27:45 +0000 (11:27 +0200)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Sat, 2 Dec 2017 10:40:28 +0000 (10:40 +0000)
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>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/iio/adc/stm32-adc.c

index 99981de41e0dd461d5be03c6c0413e0129de0ec9..da7fef376ed88550ef9899e332d84ff186061dff 100644 (file)
@@ -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                              /* 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
@@ -945,15 +953,19 @@ pwr_dwn:
  * 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)
@@ -1225,10 +1237,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 vinp,
-                                   int scan_index, u32 smp)
+                                   u32 vinn, int scan_index, bool differential)
 {
        struct stm32_adc *adc = iio_priv(indio_dev);
        char *name = adc->chan_name[vinp];
 
        chan->type = IIO_VOLTAGE;
        chan->channel = vinp;
-       snprintf(name, STM32_ADC_CH_SZ, "in%d", vinp);
+       if (differential) {
+               chan->differential = 1;
+               chan->channel2 = vinn;
+               snprintf(name, STM32_ADC_CH_SZ, "in%d-in%d", vinp, vinn);
+       } else {
+               snprintf(name, STM32_ADC_CH_SZ, "in%d", vinp);
+       }
        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;