Jonathan Cameron wrote: > On Wed, 24 Jul 2024 11:08:33 +0200 > Matteo Martelli <matteomartelli3@xxxxxxxxx> wrote: > > > Add support for Microchip PAC1921 Power/Current monitor. > > > > Implemented features: > > * capture of bus voltage, sense voltage, current and power measurements > > in free-run integration mode > > * support for both raw and triggered buffer reading > > * support for overflow events > > * scale attributes to control voltage and current gains > > * oversampling ratio attribute to control the number of integration > > samples > > * sampling rate attribute that reflects the integration period > > * userspace attribute and DT parameter to control shunt resistor > > * simple power management support > > > > Limitations: > > * operation mode fixed to free-run integration > > * READ/INT pin and OUT pin not supported > > * no controls for measurement resolutions and filters > > > > Signed-off-by: Matteo Martelli <matteomartelli3@xxxxxxxxx> > I had a few more bits of feedback + one change was necessary because of > this crossing with Nuno's series making iio_dev->masklength private. > Rather than go around again for such trivial things, > I've applied it to the testing branch of iio.git with the following diff. > Note I'll rebase that tree on rc1 once available at which point it'll become > the togreg branch and get picked up by linux-next etc. > > There are a few things inline that I commented on but didn't touch, so > please also take a look at those and shout if I messed anything up! > I've been known to make trivial changes that break a driver completely :( > > Thanks, > > Jonathan > Thanks Jonathan, I reviewed your diff and also tested this version on the HW. All looks good to me. Please check also my comments below. > > diff --git a/drivers/iio/adc/pac1921.c b/drivers/iio/adc/pac1921.c > index a21dd772467e..d04c6685d780 100644 > --- a/drivers/iio/adc/pac1921.c > +++ b/drivers/iio/adc/pac1921.c > @@ -75,12 +75,12 @@ enum pac1921_mxsl { > * Vbus scale (mV) = max_vbus (mV) / dv_gain / resolution > */ > static const int pac1921_vbus_scales[][2] = { > - {31, 280547409}, /* dv_gain x1 */ > - {15, 640273704}, /* dv_gain x2 */ > - {7, 820136852}, /* dv_gain x4 */ > - {3, 910068426}, /* dv_gain x8 */ > - {1, 955034213}, /* dv_gain x16 */ > - {0, 977517106} /* dv_gain x32 */ > + { 31, 280547409 }, /* dv_gain x1 */ > + { 15, 640273704 }, /* dv_gain x2 */ > + { 7, 820136852 }, /* dv_gain x4 */ > + { 3, 910068426 }, /* dv_gain x8 */ > + { 1, 955034213 }, /* dv_gain x16 */ > + { 0, 977517106 }, /* dv_gain x32 */ > }; > > /* > @@ -91,14 +91,14 @@ static const int pac1921_vbus_scales[][2] = { > * Vsense scale (mV) = max_vsense (mV) / di_gain / resolution > */ > static const int pac1921_vsense_scales[][2] = { > - {0, 97751710}, /* di_gain x1 */ > - {0, 48875855}, /* di_gain x2 */ > - {0, 24437927}, /* di_gain x4 */ > - {0, 12218963}, /* di_gain x8 */ > - {0, 6109481}, /* di_gain x16 */ > - {0, 3054740}, /* di_gain x32 */ > - {0, 1527370}, /* di_gain x64 */ > - {0, 763685} /* di_gain x128 */ > + { 0, 97751710 }, /* di_gain x1 */ > + { 0, 48875855 }, /* di_gain x2 */ > + { 0, 24437927 }, /* di_gain x4 */ > + { 0, 12218963 }, /* di_gain x8 */ > + { 0, 6109481 }, /* di_gain x16 */ > + { 0, 3054740 }, /* di_gain x32 */ > + { 0, 1527370 }, /* di_gain x64 */ > + { 0, 763685 }, /* di_gain x128 */ > }; > > /* > @@ -334,7 +334,7 @@ static int pac1921_read_res(struct pac1921_priv *priv, unsigned long reg, > if (ret) > return ret; > > - *val = (u16)FIELD_GET(PAC1921_RES_MASK, get_unaligned_be16(val)); > + *val = FIELD_GET(PAC1921_RES_MASK, get_unaligned_be16(val)); > > return 0; > } > @@ -612,7 +612,7 @@ static int pac1921_update_int_num_samples(struct pac1921_priv *priv, > if (priv->n_samples == n_samples) > return 0; > > - reg_val = (u8)FIELD_PREP(PAC1921_INT_CFG_SMPL_MASK, n_samples); > + reg_val = FIELD_PREP(PAC1921_INT_CFG_SMPL_MASK, n_samples); > > ret = pac1921_update_cfg_reg(priv, PAC1921_REG_INT_CFG, > PAC1921_INT_CFG_SMPL_MASK, reg_val); > @@ -1017,7 +1017,7 @@ static irqreturn_t pac1921_trigger_handler(int irq, void *p) > if (ret) > goto done; > > - for_each_set_bit(bit, idev->active_scan_mask, idev->masklength) { > + iio_for_each_active_channel(idev, bit) { > u16 val; > > ret = pac1921_read_res(priv, idev->channels[ch].address, &val); > @@ -1054,8 +1054,8 @@ static int pac1921_init(struct pac1921_priv *priv) > return ret; > > /* Configure gains, use 14-bits measurement resolution (HW default) */ > - val = (u8)FIELD_PREP(PAC1921_GAIN_DI_GAIN_MASK, priv->di_gain) | > - (u8)FIELD_PREP(PAC1921_GAIN_DV_GAIN_MASK, priv->dv_gain); > + val = FIELD_PREP(PAC1921_GAIN_DI_GAIN_MASK, priv->di_gain) | > + FIELD_PREP(PAC1921_GAIN_DV_GAIN_MASK, priv->dv_gain); > ret = regmap_write(priv->regmap, PAC1921_REG_GAIN_CFG, val); > if (ret) > return ret; > @@ -1067,7 +1067,7 @@ static int pac1921_init(struct pac1921_priv *priv) > * - set READ/INT pin override (RIOV) to control operation mode via > * register instead of pin > */ > - val = (u8)FIELD_PREP(PAC1921_INT_CFG_SMPL_MASK, priv->n_samples) | > + val = FIELD_PREP(PAC1921_INT_CFG_SMPL_MASK, priv->n_samples) | > PAC1921_INT_CFG_VSFEN | PAC1921_INT_CFG_VBFEN | > PAC1921_INT_CFG_RIOV; > ret = regmap_write(priv->regmap, PAC1921_REG_INT_CFG, val); > @@ -1080,8 +1080,8 @@ static int pac1921_init(struct pac1921_priv *priv) > * - OUT pin full scale range: 3V (HW detault) > * - no timeout, no sleep, no sleep override, no recalc (HW defaults) > */ > - val = (u8)FIELD_PREP(PAC1921_CONTROL_MXSL_MASK, > - PAC1921_MXSL_VPOWER_FREE_RUN); > + val = FIELD_PREP(PAC1921_CONTROL_MXSL_MASK, > + PAC1921_MXSL_VPOWER_FREE_RUN); > ret = regmap_write(priv->regmap, PAC1921_REG_CONTROL, val); > if (ret) > return ret; > @@ -1153,7 +1153,6 @@ static void pac1921_regulator_disable(void *data) > > static int pac1921_probe(struct i2c_client *client) > { > - const struct i2c_device_id *id = i2c_client_get_device_id(client); > struct device *dev = &client->dev; > struct pac1921_priv *priv; > struct iio_dev *indio_dev; > @@ -1202,8 +1201,7 @@ static int pac1921_probe(struct i2c_client *client) > ret = devm_add_action_or_reset(dev, pac1921_regulator_disable, > priv->vdd); > if (ret) > - return dev_err_probe( > - dev, ret, > + return dev_err_probe(dev, ret, > "Cannot add action for vdd regulator disposal\n"); > > msleep(PAC1921_POWERUP_TIME_MS); > @@ -1214,7 +1212,7 @@ static int pac1921_probe(struct i2c_client *client) > > priv->iio_info = pac1921_iio; > > - indio_dev->name = id->name; > + indio_dev->name = "pac1921"; > indio_dev->info = &priv->iio_info; > indio_dev->modes = INDIO_DIRECT_MODE; > indio_dev->channels = pac1921_channels; > > > > + > > +/* > > + * Emit on sysfs the list of available scales contained in scales_tbl > > + * > > + * TODO:: this function can be replaced with iio_format_avail_list() if the > > + * latter will ever be exported. > > You could just have added a precursor patch doing that. > If you have time I'd certainly consider a patch that does export that function > and uses it here. > I wasn't sure that one usage was enough to justify the export. I could definitely do it, I am assuming it would now go to a new patch series since this has already been merged into testing, right? > > + * > > + * Must be called with lock held if the scales_tbl can change runtime (e.g. for > > + * the current scales table) > > + */ > > +static ssize_t pac1921_format_scale_avail(const int (*const scales_tbl)[2], > > + size_t size, char *buf) > > +{ > > + ssize_t len = 0; > > + > > + for (unsigned int i = 0; i < size; i++) { > > + if (i != 0) { > > + len += sysfs_emit_at(buf, len, " "); > > + if (len >= PAGE_SIZE) > > + return -EFBIG; > > + } > > + len += sysfs_emit_at(buf, len, "%d.%09d", scales_tbl[i][0], > > + scales_tbl[i][1]); > > + if (len >= PAGE_SIZE) > > + return -EFBIG; > > + } > > + > > + len += sysfs_emit_at(buf, len, "\n"); > > + return len; > > +} > > + > > +/* > > + * Read available scales for a specific channel > > + * > > + * NOTE: using extended info insted of iio.read_avail() because access to > > + * current scales must be locked as they depend on shunt resistor which may > > + * change runtime. Caller of iio.read_avail() would access the table unlocked > > + * instead. > > That's a corner case we should think about closing. Would require an indicator > to read_avail that the buffer it has been passed is a snapshot that it should > free on completion of the string building. I don't like passing ownership > of data around like that, but it is fiddly to do anything else given > any simple double buffering is subject to race conditions. > If I understand your suggestion the driver would allocate a new table and copy the values into it at each read_avail() call. Then iio_read_channel_info_avail() would free the buffer if some sort of free-after-use indicator flag is set. I guess such indicator might be set via an additional read_avail function argument (would be an extensive API change) or maybe via a new iio_chan_spec attribute. > An alternative would use a key of sometype to associate individual read_avail > calls with new ones to read_avail_release_resource. That might be cleaner. > Are you referring to introduce a new read_avail_realease_resource callback that would be called at the end of iio_read_channel_info_avail() if set? Similarly to the previous point the driver would allocate a new table and copy the values into it at each read_avail() call, but the driver would also define a release callback to free such table. If otherwise you are referring to something less trivial, is there a similar API in the kernel that can be referred to for clarity? > oh well, a cleanup job for another day. I suspect we have drivers today > that are subject to tearing of their available lists. > I've just taken a quick look at the other drivers and the following twos seem to have the race condition issue since they are updating an available table during a write_raw() call and also exposing it during a read_avail() call: * drivers/iio/light/as73211.c: see int_time_avail table * drivers/iio/adc/ad7192.c: see filter_freq_avail table There might be others, I've only looked into those that seemed likely to have this issue after some trivial greps. Is there already a common way for iio to keep track of open issues (e.g. Issue tracker/TODO lists/etc)? > > + */ > > +static ssize_t pac1921_read_scale_avail(struct iio_dev *indio_dev, > > + uintptr_t private, > > + const struct iio_chan_spec *chan, > > + char *buf) > > +{ > > + struct pac1921_priv *priv = iio_priv(indio_dev); > > + const int (*scales_tbl)[2]; > > + size_t size; > > + > > + switch (chan->channel) { > > + case PAC1921_CHAN_VBUS: > > + scales_tbl = pac1921_vbus_scales; > > + size = ARRAY_SIZE(pac1921_vbus_scales); > > + return pac1921_format_scale_avail(scales_tbl, size, buf); > > + > > + case PAC1921_CHAN_VSENSE: > > + scales_tbl = pac1921_vsense_scales; > > + size = ARRAY_SIZE(pac1921_vsense_scales); > > + return pac1921_format_scale_avail(scales_tbl, size, buf); > > + > > + case PAC1921_CHAN_CURRENT: { > > + guard(mutex)(&priv->lock); > > + scales_tbl = priv->current_scales; > > + size = ARRAY_SIZE(priv->current_scales); > > + return pac1921_format_scale_avail(scales_tbl, size, buf); > > + } > > + default: > > + return -EINVAL; > > + } > > +} > > + > > +#define PAC1921_EXT_INFO_SCALE_AVAIL { \ > > + .name = "scale_available", \ > > + .read = pac1921_read_scale_avail, \ > > + .shared = IIO_SEPARATE, \ > > +} > > + > > +static const struct iio_chan_spec_ext_info pac1921_ext_info_voltage[] = { > > + PAC1921_EXT_INFO_SCALE_AVAIL, > > + {} > > +}; > > > > + > > +static irqreturn_t pac1921_trigger_handler(int irq, void *p) > > +{ > > + struct iio_poll_func *pf = p; > > + struct iio_dev *idev = pf->indio_dev; > > + struct pac1921_priv *priv = iio_priv(idev); > > + int ret; > > + int bit; > > + int ch = 0; > > + > > + guard(mutex)(&priv->lock); > > + > > + if (!pac1921_data_ready(priv)) > > Interesting corner case that maybe could have done with a comment. > Seems could be triggered by a spurious interrupt, or sampling too early. > > I think only the second one is likely to happen, so shouldn't be a > problem to acknowledge that trigger. > Yes, my intent here was to prevent userspace from receiving invalid data if sampled too early: for example the user could arm a timer that would trigger an interrupt before the first integration is complete. This could happen not just after the first driver initialization phase but also after a configuration change (gains or number of integration samples reflecting a user change of scale or oversampling_ratio respectively). > > + goto done; > > + > > + ret = pac1921_check_push_overflow(idev, pf->timestamp); > > + if (ret) > > + goto done; > > + > > + for_each_set_bit(bit, idev->active_scan_mask, idev->masklength) { > > This needs an update as we crossed with Nuno's series that removes access > to masklength. I can fix whilst applying by using > iio_for_each_active_channel() > > > > + u16 val; > > + > > + ret = pac1921_read_res(priv, idev->channels[ch].address, &val); > > + if (ret) > > + goto done; > > + > > + priv->scan.chan[ch++] = val; > > + } > > + > > + iio_push_to_buffers_with_timestamp(idev, &priv->scan, pf->timestamp); > > + > > +done: > > + iio_trigger_notify_done(idev->trig); > > + > > + return IRQ_HANDLED; > > +} > > + > > +/* > > + * Initialize device by writing initial configuration and putting it into > > + * integration state. > > + * > > + * Must be called with lock held when called after first initialization > > + * (e.g. from pm resume) > > + */ > > +static int pac1921_init(struct pac1921_priv *priv) > > +{ > > + unsigned int val; > > + int ret; > > + > > + /* Enter READ state before configuration */ > > + ret = regmap_update_bits(priv->regmap, PAC1921_REG_INT_CFG, > > + PAC1921_INT_CFG_INTEN, 0); > > + if (ret) > > + return ret; > > + > > + /* Configure gains, use 14-bits measurement resolution (HW default) */ > > + val = (u8)FIELD_PREP(PAC1921_GAIN_DI_GAIN_MASK, priv->di_gain) | > > + (u8)FIELD_PREP(PAC1921_GAIN_DV_GAIN_MASK, priv->dv_gain); > > Why are these cases needed? > Each of those values is going to fit in a u8 just fine and it's getting > written to a much larger variable. > FIELD_PREP result type would be a long unsigned int due to the GENMASK type and -Wconversion would trigger a warning. The explicit casts is just to address -Wconversion warnings and to "state" that such casts are safe. In this way with -Wconversion (KBUILD_EXTRA_WARN=3) one could easily spot those other implicit casts that would end up with unwanted data corruption. I thought it to be a common practice and I also saw it in some other kernel patches, for example https://lore.kernel.org/all/1540883612.2354.2.camel@xxxxxxxxxxxx/ , but maybe it's not that common as I thought. I also see that maybe in this case casting to unsigned int would have likely been more clear. Thanks, Matteo Martelli