|
@@ -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
|
|
@@ -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;
|