Message ID | 20240417170054.140587-1-alisa.roman@analog.com |
---|---|
Headers | show |
Series | iio: adc: ad7192: Add AD7194 support | expand |
On Wed, Apr 17, 2024 at 8:01 PM Alisa-Dariana Roman <alisadariana@gmail.com> wrote: > > Unlike the other AD719Xs, AD7194 has configurable differential > channels. The user can dynamically configure them in the devicetree. > > Also modify config AD7192 description for better scaling. ... > + device_for_each_child_node(dev, child) { You can use scoped variant AFAIU that's available in Jonathan's tree. > + *ad7194_channels = ad7194_chan_diff; > + ad7194_channels->scan_index = index++; > + ret = ad7194_parse_channel(child, ad7194_channels); > + if (ret) { > + fwnode_handle_put(child); With the above this wouldn't be needed. > + return ret; > + } > + ad7194_channels++; > + }
On Wed, Apr 17, 2024 at 8:01 PM Alisa-Dariana Roman <alisadariana@gmail.com> wrote: > > AINCOM should actually be a supply. AINx inputs are referenced to AINCOM > in pseduo-differential operation mode. AINCOM voltage represets the pseudo > offset of corresponding channels. ... > + case IIO_VOLTAGE: > + if (st->aincom_mv && !chan->differential) > + *val += DIV_ROUND_CLOSEST_ULL((u64)st->aincom_mv * 1000000000, It's quite easy to make a mistake in this long constant. Can you use an appropriate one from units.h? > + st->scale_avail[gain][1]); > + return IIO_VAL_INT; ... > + aincom = devm_regulator_get_optional(&spi->dev, "aincom"); > + if (!IS_ERR(aincom)) { Why not a positive condition? > + ret = regulator_enable(aincom); > + if (ret) { > + dev_err(&spi->dev, "Failed to enable specified AINCOM supply\n"); return dev_err_probe(); > + return ret; > + } > + > + ret = devm_add_action_or_reset(&spi->dev, ad7192_reg_disable, aincom); > + if (ret) > + return ret; > + > + ret = regulator_get_voltage(aincom); > + if (ret < 0) > + return dev_err_probe(&spi->dev, ret, > + "Device tree error, AINCOM voltage undefined\n"); > + st->aincom_mv = ret / 1000; > + } else { > + st->aincom_mv = 0; > + } ... > @@ -1113,6 +1145,7 @@ static int ad7192_probe(struct spi_device *spi) > st->int_vref_mv = ret / 1000; > > st->chip_info = spi_get_device_match_data(spi); > + > indio_dev->name = st->chip_info->name; > indio_dev->modes = INDIO_DIRECT_MODE; > indio_dev->channels = st->chip_info->channels; Stray change.
On Wed, 17 Apr 2024 20:00:50 +0300 Alisa-Dariana Roman <alisadariana@gmail.com> wrote: > Replace custom attribute filter_low_pass_3db_frequency_available with > standard attribute. > > Store the available values in ad7192_state struct. > > The function that used to compute those values replaced by > ad7192_update_filter_freq_avail(). > > Function ad7192_show_filter_avail() is no longer needed. > > Note that the initial available values are hardcoded. > > Signed-off-by: Alisa-Dariana Roman <alisa.roman@analog.com> > Reviewed-by: David Lechner <dlechner@baylibre.com> Locking comment inline. Note that I'm fairly sure there is an existing bug because the 3db filter write isn't protecting st->conf. I only noticed this because I was checking you'd fixed the locking issue noted by David in v5. Jonathan > --- > drivers/iio/adc/ad7192.c | 67 ++++++++++++++++++---------------------- > 1 file changed, 30 insertions(+), 37 deletions(-) > > diff --git a/drivers/iio/adc/ad7192.c b/drivers/iio/adc/ad7192.c > index 7bcc7e2aa2a2..fe8dbb68a8ba 100644 > --- a/drivers/iio/adc/ad7192.c > +++ b/drivers/iio/adc/ad7192.c > @@ -190,6 +190,7 @@ struct ad7192_state { > u32 mode; > u32 conf; > u32 scale_avail[8][2]; > + u32 filter_freq_avail[4][2]; > u32 oversampling_ratio_avail[4]; > u8 gpocon; > u8 clock_sel; > @@ -473,6 +474,16 @@ static int ad7192_setup(struct iio_dev *indio_dev, struct device *dev) > st->oversampling_ratio_avail[2] = 8; > st->oversampling_ratio_avail[3] = 16; > > + st->filter_freq_avail[0][0] = 600; > + st->filter_freq_avail[1][0] = 800; > + st->filter_freq_avail[2][0] = 2300; > + st->filter_freq_avail[3][0] = 2720; > + > + st->filter_freq_avail[0][1] = 1000; > + st->filter_freq_avail[1][1] = 1000; > + st->filter_freq_avail[2][1] = 1000; > + st->filter_freq_avail[3][1] = 1000; > + > return 0; > } > > @@ -586,48 +597,24 @@ static int ad7192_get_f_adc(struct ad7192_state *st) > f_order * FIELD_GET(AD7192_MODE_RATE_MASK, st->mode)); > } > > -static void ad7192_get_available_filter_freq(struct ad7192_state *st, > - int *freq) > +static void ad7192_update_filter_freq_avail(struct ad7192_state *st) > { > unsigned int fadc; > > /* Formulas for filter at page 25 of the datasheet */ > fadc = ad7192_compute_f_adc(st, false, true); > - freq[0] = DIV_ROUND_CLOSEST(fadc * 240, 1024); > + st->filter_freq_avail[0][0] = DIV_ROUND_CLOSEST(fadc * 240, 1024); > > fadc = ad7192_compute_f_adc(st, true, true); > - freq[1] = DIV_ROUND_CLOSEST(fadc * 240, 1024); > + st->filter_freq_avail[1][0] = DIV_ROUND_CLOSEST(fadc * 240, 1024); > > fadc = ad7192_compute_f_adc(st, false, false); > - freq[2] = DIV_ROUND_CLOSEST(fadc * 230, 1024); > + st->filter_freq_avail[2][0] = DIV_ROUND_CLOSEST(fadc * 230, 1024); > > fadc = ad7192_compute_f_adc(st, true, false); > - freq[3] = DIV_ROUND_CLOSEST(fadc * 272, 1024); > + st->filter_freq_avail[3][0] = DIV_ROUND_CLOSEST(fadc * 272, 1024); > } > > -static ssize_t ad7192_show_filter_avail(struct device *dev, > - struct device_attribute *attr, > - char *buf) > -{ > - struct iio_dev *indio_dev = dev_to_iio_dev(dev); > - struct ad7192_state *st = iio_priv(indio_dev); > - unsigned int freq_avail[4], i; > - size_t len = 0; > - > - ad7192_get_available_filter_freq(st, freq_avail); > - > - for (i = 0; i < ARRAY_SIZE(freq_avail); i++) > - len += sysfs_emit_at(buf, len, "%d.%03d ", freq_avail[i] / 1000, > - freq_avail[i] % 1000); > - > - buf[len - 1] = '\n'; > - > - return len; > -} > - > -static IIO_DEVICE_ATTR(filter_low_pass_3db_frequency_available, > - 0444, ad7192_show_filter_avail, NULL, 0); > - > static IIO_DEVICE_ATTR(bridge_switch_en, 0644, > ad7192_show_bridge_switch, ad7192_set, > AD7192_REG_GPOCON); > @@ -637,7 +624,6 @@ static IIO_DEVICE_ATTR(ac_excitation_en, 0644, > AD7192_REG_CONF); > > static struct attribute *ad7192_attributes[] = { > - &iio_dev_attr_filter_low_pass_3db_frequency_available.dev_attr.attr, > &iio_dev_attr_bridge_switch_en.dev_attr.attr, > NULL > }; > @@ -647,7 +633,6 @@ static const struct attribute_group ad7192_attribute_group = { > }; > > static struct attribute *ad7195_attributes[] = { > - &iio_dev_attr_filter_low_pass_3db_frequency_available.dev_attr.attr, > &iio_dev_attr_bridge_switch_en.dev_attr.attr, > &iio_dev_attr_ac_excitation_en.dev_attr.attr, > NULL > @@ -665,17 +650,15 @@ static unsigned int ad7192_get_temp_scale(bool unipolar) > static int ad7192_set_3db_filter_freq(struct ad7192_state *st, > int val, int val2) > { > - int freq_avail[4], i, ret, freq; > + int i, ret, freq; > unsigned int diff_new, diff_old; > int idx = 0; > > diff_old = U32_MAX; > freq = val * 1000 + val2; > > - ad7192_get_available_filter_freq(st, freq_avail); > - > - for (i = 0; i < ARRAY_SIZE(freq_avail); i++) { > - diff_new = abs(freq - freq_avail[i]); > + for (i = 0; i < ARRAY_SIZE(st->filter_freq_avail); i++) { > + diff_new = abs(freq - st->filter_freq_avail[i][0]); > if (diff_new < diff_old) { > diff_old = diff_new; > idx = i; > @@ -826,6 +809,7 @@ static int ad7192_write_raw(struct iio_dev *indio_dev, > st->mode &= ~AD7192_MODE_RATE_MASK; > st->mode |= FIELD_PREP(AD7192_MODE_RATE_MASK, div); > ad_sd_write_reg(&st->sd, AD7192_REG_MODE, 3, st->mode); > + ad7192_update_filter_freq_avail(st); This now needs to take the mutex. There are a bunch of writes in this update function and in theory it two sysfs writes could occur at the same time hitting this path and the one below resulting in an inconsistent set of values ending up in st->filter_freq_avail[x] Would be a minor effect and is pretty unlikely but might as well close it down. Note that I'm fairly sure the update of the 3db filter frequency is already capable of corrupting st->conf because it doesn't take the mutex. With that in mind, I'd suggestion moving the mutex outside the switch statement. > break; > case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: > ret = ad7192_set_3db_filter_freq(st, val, val2 / 1000); > @@ -845,6 +829,7 @@ static int ad7192_write_raw(struct iio_dev *indio_dev, > 3, st->mode); > break; > } > + ad7192_update_filter_freq_avail(st); > mutex_unlock(&st->lock); > break; > default: > @@ -888,6 +873,12 @@ static int ad7192_read_avail(struct iio_dev *indio_dev, > /* Values are stored in a 2D matrix */ > *length = ARRAY_SIZE(st->scale_avail) * 2; > > + return IIO_AVAIL_LIST; > + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: > + *vals = (int *)st->filter_freq_avail; > + *type = IIO_VAL_FRACTIONAL; > + *length = ARRAY_SIZE(st->filter_freq_avail) * 2; > + > return IIO_AVAIL_LIST; > case IIO_CHAN_INFO_OVERSAMPLING_RATIO: > *vals = (int *)st->oversampling_ratio_avail; > @@ -956,7 +947,9 @@ static const struct iio_info ad7195_info = { > BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ > (_mask_all), \ > .info_mask_shared_by_type_available = (_mask_type_av), \ > - .info_mask_shared_by_all_available = (_mask_all_av), \ > + .info_mask_shared_by_all_available = \ > + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY) | \ > + (_mask_all_av), \ > .ext_info = (_ext_info), \ > .scan_index = (_si), \ > .scan_type = { \
On Wed, 17 Apr 2024 20:00:52 +0300 Alisa-Dariana Roman <alisadariana@gmail.com> wrote: > AINCOM should actually be a supply. AINx inputs are referenced to AINCOM > in pseduo-differential operation mode. AINCOM voltage represets the > offset of corresponding channels. > > Signed-off-by: Alisa-Dariana Roman <alisa.roman@analog.com> One request inline to make my life easier :) Otherwise, I noticed a subset of what Andy picked up on so won't repeat his comments! Thanks, Jonathan > --- > drivers/iio/adc/ad7192.c | 37 +++++++++++++++++++++++++++++++++++-- > 1 file changed, 35 insertions(+), 2 deletions(-) > > diff --git a/drivers/iio/adc/ad7192.c b/drivers/iio/adc/ad7192.c > index fe8dbb68a8ba..8d56cf889973 100644 > --- a/drivers/iio/adc/ad7192.c > +++ b/drivers/iio/adc/ad7192.c > @@ -186,6 +186,7 @@ struct ad7192_state { > struct regulator *vref; > struct clk *mclk; > u16 int_vref_mv; > + u32 aincom_mv; > u32 fclk; > u32 mode; > u32 conf; > @@ -742,10 +743,19 @@ static int ad7192_read_raw(struct iio_dev *indio_dev, > *val = -(1 << (chan->scan_type.realbits - 1)); > else > *val = 0; > + switch (chan->type) { > + case IIO_VOLTAGE: > + if (st->aincom_mv && !chan->differential) > + *val += DIV_ROUND_CLOSEST_ULL((u64)st->aincom_mv * 1000000000, > + st->scale_avail[gain][1]); > + return IIO_VAL_INT; > /* Kelvin to Celsius */ > - if (chan->type == IIO_TEMP) > + case IIO_TEMP: > *val -= 273 * ad7192_get_temp_scale(unipolar); > - return IIO_VAL_INT; > + return IIO_VAL_INT; > + default: > + return -EINVAL; > + } > case IIO_CHAN_INFO_SAMP_FREQ: > *val = DIV_ROUND_CLOSEST(ad7192_get_f_adc(st), 1024); > return IIO_VAL_INT; > @@ -1052,6 +1062,7 @@ static int ad7192_probe(struct spi_device *spi) > { > struct ad7192_state *st; > struct iio_dev *indio_dev; > + struct regulator *aincom; > int ret; > > if (!spi->irq) { > @@ -1067,6 +1078,27 @@ static int ad7192_probe(struct spi_device *spi) > > mutex_init(&st->lock); > As we are going around again, could you do me a favour and add a comment here along the lines of. /* * aincom is optional to maintain compatibility with older DT. * Newer firmware should provide a zero volt fixed supply if * this is wired to ground. */ Aim being to discourage this getting cut and paste into other drivers that support the common voltage from the start! > + aincom = devm_regulator_get_optional(&spi->dev, "aincom"); > + if (!IS_ERR(aincom)) { > + ret = regulator_enable(aincom); > + if (ret) { > + dev_err(&spi->dev, "Failed to enable specified AINCOM supply\n"); > + return ret; > + } > + > + ret = devm_add_action_or_reset(&spi->dev, ad7192_reg_disable, aincom); > + if (ret) > + return ret; > + > + ret = regulator_get_voltage(aincom); > + if (ret < 0) > + return dev_err_probe(&spi->dev, ret, > + "Device tree error, AINCOM voltage undefined\n"); > + st->aincom_mv = ret / 1000; > + } else { > + st->aincom_mv = 0; > + } > + > st->avdd = devm_regulator_get(&spi->dev, "avdd"); > if (IS_ERR(st->avdd)) > return PTR_ERR(st->avdd); > @@ -1113,6 +1145,7 @@ static int ad7192_probe(struct spi_device *spi) > st->int_vref_mv = ret / 1000; > > st->chip_info = spi_get_device_match_data(spi); > + > indio_dev->name = st->chip_info->name; > indio_dev->modes = INDIO_DIRECT_MODE; > indio_dev->channels = st->chip_info->channels;
On Wed, 17 Apr 2024 20:06:00 +0300 Andy Shevchenko <andy.shevchenko@gmail.com> wrote: > On Wed, Apr 17, 2024 at 8:01 PM Alisa-Dariana Roman > <alisadariana@gmail.com> wrote: > > > > Unlike the other AD719Xs, AD7194 has configurable differential > > channels. The user can dynamically configure them in the devicetree. > > > > Also modify config AD7192 description for better scaling. > > ... > > > + device_for_each_child_node(dev, child) { > > You can use scoped variant AFAIU that's available in Jonathan's tree. > > > + *ad7194_channels = ad7194_chan_diff; > > + ad7194_channels->scan_index = index++; > > + ret = ad7194_parse_channel(child, ad7194_channels); > > + if (ret) { > > > + fwnode_handle_put(child); > > With the above this wouldn't be needed. > As it's a minor improvement, and I tend not to like unnecessary interdependence of series in my tree (until they are in char-misc and hence no chance of them changing), I'm fine with not using that new functionality here. That will change once it's upstream of course! I will send a pull request to Greg nice and early this cycle so that should be in my upstream soon. I'm also fine with you using this if you want to though! Jonathan > > + return ret; > > + } > > + ad7194_channels++; > > + } >
On Wed, 17 Apr 2024 20:00:54 +0300 Alisa-Dariana Roman <alisadariana@gmail.com> wrote: > Unlike the other AD719Xs, AD7194 has configurable differential > channels. The user can dynamically configure them in the devicetree. > > Also modify config AD7192 description for better scaling. > > Signed-off-by: Alisa-Dariana Roman <alisa.roman@analog.com> Late feedback (sorry!) but I think we should resolve the single ended channel description so that it sits at the same level in DT binding as do differential channels. Current situation just feels inconsistent. See DT binding reply. Should be easy to do now, and wouldn't be possible to add later. It would be possible to add support for moving to per channel DT entries for a driver that previously assumed all should be available, but we can't easily move from assuming all single ended channels are but differential are specified by DT child nodes. Jonathan > diff --git a/drivers/iio/adc/ad7192.c b/drivers/iio/adc/ad7192.c > index 8d56cf889973..dc113405f1bc 100644 > --- a/drivers/iio/adc/ad7192.c > +++ b/drivers/iio/adc/ad7192.c > @@ -1,6 +1,6 @@ > +static int ad7194_parse_channels(struct iio_dev *indio_dev) > +{ > + struct device *dev = indio_dev->dev.parent; > + struct iio_chan_spec *ad7194_channels; > + struct fwnode_handle *child; > + struct iio_chan_spec ad7194_chan = AD7193_CHANNEL(0, 0, 0); > + struct iio_chan_spec ad7194_chan_diff = AD7193_DIFF_CHANNEL(0, 0, 0, 0); > + struct iio_chan_spec ad7194_chan_temp = AD719x_TEMP_CHANNEL(0, 0); > + struct iio_chan_spec ad7194_chan_timestamp = IIO_CHAN_SOFT_TIMESTAMP(0); > + unsigned int num_channels, index = 0, ain_chan; > + int ret; > + > + num_channels = device_get_child_node_count(dev); > + if (num_channels > AD7194_CH_DIFF_NR_MAX) > + return -EINVAL; > + > + num_channels += AD7194_CH_BASE_NR; > + > + ad7194_channels = devm_kcalloc(dev, num_channels, > + sizeof(*ad7194_channels), GFP_KERNEL); > + if (!ad7194_channels) > + return -ENOMEM; > + > + indio_dev->channels = ad7194_channels; > + indio_dev->num_channels = num_channels; > + > + device_for_each_child_node(dev, child) { > + *ad7194_channels = ad7194_chan_diff; > + ad7194_channels->scan_index = index++; > + ret = ad7194_parse_channel(child, ad7194_channels); > + if (ret) { > + fwnode_handle_put(child); > + return ret; > + } > + ad7194_channels++; > + } > + > + *ad7194_channels = ad7194_chan_temp; > + ad7194_channels->scan_index = index++; > + ad7194_channels->address = AD7194_CH_TEMP; > + ad7194_channels++; > + > + for (ain_chan = 1; ain_chan <= 16; ain_chan++) { I think it's worth making these more similar to the differential channels. Seems odd to allow DT to provide that list, but not the same for single ended. See comment on binding. Should be fairly easy to add now, and if we leave it until later, there won't be a way to move this forwards because we won't be able to tell if no single ended channels in DT means none are relevant, or if it means the DT file predates us adding per single ended channel description. > + *ad7194_channels = ad7194_chan; > + ad7194_channels->scan_index = index++; > + ad7194_channels->channel = ain_chan; > + ad7194_channels->address = AD7194_CH_DIFF(ain_chan, 0); > + ad7194_channels++; > + } > + > + *ad7194_channels = ad7194_chan_timestamp; > + ad7194_channels->scan_index = index; > + > + return 0; > +}