mirror of
https://github.com/torvalds/linux.git
synced 2024-12-24 20:01:55 +00:00
iio: adc: stm32: add support for differential channels
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>
This commit is contained in:
parent
0bae72aa8a
commit
3fb2e24ed7
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user