On Wed, 31 Mar 2021 at 15:05, Jonathan Cameron <jic23@xxxxxxxxxx> wrote: > > On Wed, 31 Mar 2021 10:29:51 +0300 > Alexandru Ardelean <ardeleanalex@xxxxxxxxx> wrote: > > > On Sun, Mar 14, 2021 at 8:17 PM Jonathan Cameron <jic23@xxxxxxxxxx> wrote: > > > > > > From: Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx> > > > > > > Note this doesn't support everything the chip can do as we ignore > > > window mode for now (in window / out of window). > > > > > > * Given the chip doesn't have any way of disabling the threshold > > > pins, use disable_irq() etc to mask them except when we actually > > > want them enabled (previously events were always enabled). > > > Note there are race conditions, but using the current state from > > > the status register and disabling interrupts across changes in > > > type of event should mean those races result in interrupts, > > > but no events to userspace. > > > > > > * Correctly reflect that there is one threshold line per channel. > > > > > > * Only take notice of rising edge. If anyone wants the other edge > > > then they should set the other threshold (they are available for > > > rising and falling directions). This isn't perfect but it makes > > > it a lot simpler. > > > > > > * If insufficient interrupts are specified in firnware, don't support > > > events. > > > > > > * Adaptive events use the same pos/neg values of thrMD as non adaptive > > > ones. > > > > > > Tested against qemu based emulation. > > > > > > > Overall looks ok. > > A few minor issues with the device-matching and OF. > > And maybe with the interrupt handling. > > Hopefully answers below address those two. Looks good. Thanks for the clarifications. I'm still a bit paranoid about the int_enabled stuff, but I'll take your word for it. Reviewed-by: Alexandru Ardelean <ardeleanalex@xxxxxxxxx> > > > > > > > > > Signed-off-by: Jonathan Cameron <Jonathan.Cameron@xxxxxxxxxx> > > > Link: https://lore.kernel.org/r/20210207154623.433442-13-jic23@xxxxxxxxxx > > > --- > > > drivers/staging/iio/cdc/ad7150.c | 258 ++++++++++++++++++------------- > > > 1 file changed, 153 insertions(+), 105 deletions(-) > > > > > > diff --git a/drivers/staging/iio/cdc/ad7150.c b/drivers/staging/iio/cdc/ad7150.c > > > index f8183c540d5c..24be97456c03 100644 > > > --- a/drivers/staging/iio/cdc/ad7150.c > > > +++ b/drivers/staging/iio/cdc/ad7150.c > > > @@ -11,6 +11,7 @@ > > > #include <linux/kernel.h> > > > #include <linux/slab.h> > > > #include <linux/i2c.h> > > > +#include <linux/irq.h> > > > #include <linux/module.h> > > > > > > #include <linux/iio/iio.h> > > > @@ -60,8 +61,6 @@ enum { > > > /** > > > * struct ad7150_chip_info - instance specific chip data > > > * @client: i2c client for this device > > > - * @current_event: device always has one type of event enabled. > > > - * This element stores the event code of the current one. > > > * @threshold: thresholds for simple capacitance value events > > > * @thresh_sensitivity: threshold for simple capacitance offset > > > * from 'average' value. > > > @@ -70,21 +69,23 @@ enum { > > > * value jumps to current value. Note made up of two fields, > > > * 3:0 are for timeout receding - applies if below lower threshold > > > * 7:4 are for timeout approaching - applies if above upper threshold > > > - * @old_state: store state from previous event, allowing confirmation > > > - * of new condition. > > > - * @conversion_mode: the current conversion mode. > > > * @state_lock: ensure consistent state of this structure wrt the > > > * hardware. > > > + * @interrupts: one or two interrupt numbers depending on device type. > > > + * @int_enabled: is a given interrupt currently enabled. > > > + * @type: threshold type > > > + * @dir: threshold direction > > > */ > > > struct ad7150_chip_info { > > > struct i2c_client *client; > > > - u64 current_event; > > > u16 threshold[2][2]; > > > u8 thresh_sensitivity[2][2]; > > > u8 thresh_timeout[2][2]; > > > - int old_state; > > > - char *conversion_mode; > > > struct mutex state_lock; > > > + int interrupts[2]; > > > + bool int_enabled[2]; > > > + enum iio_event_type type; > > > + enum iio_event_direction dir; > > > }; > > > > > > /* > > > @@ -158,8 +159,8 @@ static int ad7150_read_event_config(struct iio_dev *indio_dev, > > > switch (type) { > > > case IIO_EV_TYPE_THRESH_ADAPTIVE: > > > if (dir == IIO_EV_DIR_RISING) > > > - return !thrfixed && (threshtype == 0x3); > > > - return !thrfixed && (threshtype == 0x2); > > > + return !thrfixed && (threshtype == 0x1); > > > + return !thrfixed && (threshtype == 0x0); > > > case IIO_EV_TYPE_THRESH: > > > if (dir == IIO_EV_DIR_RISING) > > > return thrfixed && (threshtype == 0x1); > > > @@ -179,11 +180,9 @@ static int ad7150_write_event_params(struct iio_dev *indio_dev, > > > { > > > struct ad7150_chip_info *chip = iio_priv(indio_dev); > > > int rising = (dir == IIO_EV_DIR_RISING); > > > - u64 event_code; > > > > > > - event_code = IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, chan, type, dir); > > > - > > > - if (event_code != chip->current_event) > > > + /* Only update value live, if parameter is in use */ > > > + if ((type != chip->type) || (dir != chip->dir)) > > > return 0; > > > > > > switch (type) { > > > @@ -231,52 +230,91 @@ static int ad7150_write_event_config(struct iio_dev *indio_dev, > > > int ret; > > > struct ad7150_chip_info *chip = iio_priv(indio_dev); > > > int rising = (dir == IIO_EV_DIR_RISING); > > > - u64 event_code; > > > - > > > - /* Something must always be turned on */ > > > - if (!state) > > > - return -EINVAL; > > > > > > - event_code = IIO_UNMOD_EVENT_CODE(chan->type, chan->channel, type, dir); > > > - if (event_code == chip->current_event) > > > + /* > > > + * There is only a single shared control and no on chip > > > + * interrupt disables for the two interrupt lines. > > > + * So, enabling will switch the events configured to enable > > > + * whatever was most recently requested and if necessary enable_irq() > > > + * the interrupt and any disable will disable_irq() for that > > > + * channels interrupt. > > > + */ > > > + if (!state) { > > > + if ((chip->int_enabled[chan->channel]) && > > > + (type == chip->type) && (dir == chip->dir)) { > > > + disable_irq(chip->interrupts[chan->channel]); > > > + chip->int_enabled[chan->channel] = false; > > > + } > > > return 0; > > > + } > > > + > > > mutex_lock(&chip->state_lock); > > > - ret = i2c_smbus_read_byte_data(chip->client, AD7150_CFG); > > > - if (ret < 0) > > > - goto error_ret; > > > + if ((type != chip->type) || (dir != chip->dir)) { > > > > > > - cfg = ret & ~((0x03 << 5) | BIT(7)); > > > + /* > > > + * Need to temporarily disable both interrupts if > > > + * enabled - this is to avoid races around changing > > > + * config and thresholds. > > > + * Note enable/disable_irq() are reference counted so > > > + * no need to check if already enabled. > > > + */ > > > + disable_irq(chip->interrupts[0]); > > > + disable_irq(chip->interrupts[1]); > > > > > > - switch (type) { > > > - case IIO_EV_TYPE_THRESH_ADAPTIVE: > > > - adaptive = 1; > > > - if (rising) > > > - thresh_type = 0x3; > > > - else > > > - thresh_type = 0x2; > > > - break; > > > - case IIO_EV_TYPE_THRESH: > > > - adaptive = 0; > > > - if (rising) > > > - thresh_type = 0x1; > > > - else > > > - thresh_type = 0x0; > > > - break; > > > - default: > > > - ret = -EINVAL; > > > - goto error_ret; > > > - } > > > + ret = i2c_smbus_read_byte_data(chip->client, AD7150_CFG); > > > + if (ret < 0) > > > + goto error_ret; > > > > > > - cfg |= (!adaptive << 7) | (thresh_type << 5); > > > + cfg = ret & ~((0x03 << 5) | BIT(7)); > > > > > > - ret = i2c_smbus_write_byte_data(chip->client, AD7150_CFG, cfg); > > > - if (ret < 0) > > > - goto error_ret; > > > + switch (type) { > > > + case IIO_EV_TYPE_THRESH_ADAPTIVE: > > > + adaptive = 1; > > > + if (rising) > > > + thresh_type = 0x1; > > > + else > > > + thresh_type = 0x0; > > > + break; > > > + case IIO_EV_TYPE_THRESH: > > > + adaptive = 0; > > > + if (rising) > > > + thresh_type = 0x1; > > > + else > > > + thresh_type = 0x0; > > > > this can be ignored; it's a minor nit; > > the if (rising) {} else {} block looks duplicate; > > it could be moved after the switch() block; > > If you are happy with rest I'll just roll that in whilst applying. > > > > > > + break; > > > + default: > > > + ret = -EINVAL; > > > + goto error_ret; > > > + } > > > > > > - chip->current_event = event_code; > > > + cfg |= (!adaptive << 7) | (thresh_type << 5); > > > + > > > + ret = i2c_smbus_write_byte_data(chip->client, AD7150_CFG, cfg); > > > + if (ret < 0) > > > + goto error_ret; > > > + > > > + /* > > > + * There is a potential race condition here, but not easy > > > + * to close given we can't disable the interrupt at the > > > + * chip side of things. Rely on the status bit. > > > + */ > > > + chip->type = type; > > > + chip->dir = dir; > > > + > > > + /* update control attributes */ > > > + ret = ad7150_write_event_params(indio_dev, chan->channel, type, > > > + dir); > > > + if (ret) > > > + goto error_ret; > > > + /* reenable any irq's we disabled whilst changing mode */ > > > + enable_irq(chip->interrupts[0]); > > > + enable_irq(chip->interrupts[1]); > > > > i'm wondering if we need to set 'chip->int_enabled[ for 0 & 1 ] = true;' > > tbh i am not sure if there is a chance of mismatch between > > 'int_enabled' and the actual enable_irq() > > > > maybe it might make sense to add an ad7150_{enable/disable}_irq() > > that checks the per-channel 'int_enabled' field; > > Ah. I'm playing a bit of a game here. (there is a comment at the irq_disable() calls > that was meant to indicate what was going on). > > There is a difference between when chip->int_enabled[] is changed in conjunction > with enable_irq()/disable_irq() and when enable_irq()/disable_irq() are change on their own. > I reserve the right remain paranoid about it :p > The intent is that int_enabled[] reflects non-transient state of whether > the interrupt is enabled - I don't want to update it during transient states. > > We are fine to do the disable_irq() calls on irqs that aren't enabled because > disable_irq() calls can be safely nested. Only when you have more enable_irq() > calls (including startup if it's auto enabled) than you have disable_irq() calls > will the interrupt be enabled. > > https://elixir.bootlin.com/linux/latest/source/kernel/irq/manage.c#L771 > https://elixir.bootlin.com/linux/latest/source/include/linux/irqdesc.h#L28 > The depth field in irq_desc is used for this. > > > > > > > > + } > > > + if (!chip->int_enabled[chan->channel]) { > > > + enable_irq(chip->interrupts[chan->channel]); > > > + chip->int_enabled[chan->channel] = true; > > > + } > > > > > > - /* update control attributes */ > > > - ret = ad7150_write_event_params(indio_dev, chan->channel, type, dir); > > > error_ret: > > > mutex_unlock(&chip->state_lock); > > > > > > @@ -434,59 +472,44 @@ static const struct iio_chan_spec ad7151_channels_no_irq[] = { > > > AD7150_CAPACITANCE_CHAN_NO_IRQ(0), > > > }; > > > > > > -static irqreturn_t ad7150_event_handler(int irq, void *private) > > > +static irqreturn_t __ad7150_event_handler(void *private, u8 status_mask, > > > + int channel) > > > { > > > struct iio_dev *indio_dev = private; > > > struct ad7150_chip_info *chip = iio_priv(indio_dev); > > > - u8 int_status; > > > s64 timestamp = iio_get_time_ns(indio_dev); > > > - int ret; > > > + int int_status; > > > > > > - ret = i2c_smbus_read_byte_data(chip->client, AD7150_STATUS); > > > - if (ret < 0) > > > + int_status = i2c_smbus_read_byte_data(chip->client, AD7150_STATUS); > > > + if (int_status < 0) > > > return IRQ_HANDLED; > > > > > > - int_status = ret; > > > - > > > - if ((int_status & AD7150_STATUS_OUT1) && > > > - !(chip->old_state & AD7150_STATUS_OUT1)) > > > - iio_push_event(indio_dev, > > > - IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, > > > - 0, > > > - IIO_EV_TYPE_THRESH, > > > - IIO_EV_DIR_RISING), > > > - timestamp); > > > - else if ((!(int_status & AD7150_STATUS_OUT1)) && > > > - (chip->old_state & AD7150_STATUS_OUT1)) > > > - iio_push_event(indio_dev, > > > - IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, > > > - 0, > > > - IIO_EV_TYPE_THRESH, > > > - IIO_EV_DIR_FALLING), > > > - timestamp); > > > - > > > - if ((int_status & AD7150_STATUS_OUT2) && > > > - !(chip->old_state & AD7150_STATUS_OUT2)) > > > - iio_push_event(indio_dev, > > > - IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, > > > - 1, > > > - IIO_EV_TYPE_THRESH, > > > - IIO_EV_DIR_RISING), > > > - timestamp); > > > - else if ((!(int_status & AD7150_STATUS_OUT2)) && > > > - (chip->old_state & AD7150_STATUS_OUT2)) > > > - iio_push_event(indio_dev, > > > - IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, > > > - 1, > > > - IIO_EV_TYPE_THRESH, > > > - IIO_EV_DIR_FALLING), > > > - timestamp); > > > - /* store the status to avoid repushing same events */ > > > - chip->old_state = int_status; > > > + /* > > > + * There are race conditions around enabling and disabling that > > > + * could easily land us here with a spurious interrupt. > > > + * Just eat it if so. > > > + */ > > > + if (!(int_status & status_mask)) > > > + return IRQ_HANDLED; > > > + > > > + iio_push_event(indio_dev, > > > + IIO_UNMOD_EVENT_CODE(IIO_CAPACITANCE, channel, > > > + chip->type, chip->dir), > > > + timestamp); > > > > > > return IRQ_HANDLED; > > > } > > > > > > +static irqreturn_t ad7150_event_handler_ch1(int irq, void *private) > > > +{ > > > + return __ad7150_event_handler(private, AD7150_STATUS_OUT1, 0); > > > +} > > > + > > > +static irqreturn_t ad7150_event_handler_ch2(int irq, void *private) > > > +{ > > > + return __ad7150_event_handler(private, AD7150_STATUS_OUT2, 1); > > > +} > > > + > > > static IIO_CONST_ATTR(in_capacitance_thresh_adaptive_timeout_available, > > > "[0 0.01 0.15]"); > > > > > > @@ -533,12 +556,44 @@ static int ad7150_probe(struct i2c_client *client, > > > > > > indio_dev->modes = INDIO_DIRECT_MODE; > > > > > > - if (client->irq) { > > > + chip->interrupts[0] = fwnode_irq_get(dev_fwnode(&client->dev), 0); > > > + if (chip->interrupts[0] < 0) > > > + return chip->interrupts[0]; > > > + if (id->driver_data == AD7150) { > > > > checking 'id->driver_data' will be flaky after the of_match_table is added; > > It's not, though the path is a bit obscure and only works if you don't use > probe_new() - if using probe_new() you to call i2c_match_id() directly. > > I remember raising this in a review I did a few years back and chasing it down. > I just sanity checked and it still works ;) > ack; thanks for the info; > So, even if we do an of_based registration the following happens > > of_i2c_register_device() > -> of_i2c_get_board_info() // gets the of_node pointer > -> i2c_new_client_device(adap, &info); > .. some time later the i2c_bus_type.probe() gets called for the devices we added > and > > > -> i2c_device_probe() > -> driver->probe(client, i2c_match_id(driver->id_table, client)) > -> i2c_match_id() //matches against id table and hence gets us the driver_data. > > > > > either we defer the of_match_table patch, or we update it somehow; > > maybe a chip_info struct is an idea; > > otherwise we need to do void pointer to int conversions [which look nasty]; > > > > i'm also seeing that there is a 'type' field inside of_device_id that > > is underused, but looks useful for cases like this; > > i'm also a bit paranoid to use it; because it may not be compatible > > with acpi_device_id > > We can refactor this at some point, but I don't think it is necessary to move > the driver out of staging as a lot of drivers are doing things the same > way we do it here. ack > > > > > > + chip->interrupts[1] = fwnode_irq_get(dev_fwnode(&client->dev), 1); > > > + if (chip->interrupts[1] < 0) > > > + return chip->interrupts[1]; > > > + } > > > + if (chip->interrupts[0] && > > > + (id->driver_data == AD7151 || chip->interrupts[1])) { > > > + irq_set_status_flags(chip->interrupts[0], IRQ_NOAUTOEN); > > > + ret = devm_request_threaded_irq(&client->dev, > > > + chip->interrupts[0], > > > + NULL, > > > + &ad7150_event_handler_ch1, > > > + IRQF_TRIGGER_RISING | > > > + IRQF_ONESHOT, > > > + "ad7150_irq1", > > > + indio_dev); > > > + if (ret) > > > + return ret; > > > + > > > indio_dev->info = &ad7150_info; > > > switch (id->driver_data) { > > > case AD7150: > > > indio_dev->channels = ad7150_channels; > > > indio_dev->num_channels = ARRAY_SIZE(ad7150_channels); > > > + irq_set_status_flags(chip->interrupts[1], IRQ_NOAUTOEN); > > > + ret = devm_request_threaded_irq(&client->dev, > > > + chip->interrupts[1], > > > + NULL, > > > + &ad7150_event_handler_ch2, > > > + IRQF_TRIGGER_RISING | > > > + IRQF_ONESHOT, > > > + "ad7150_irq2", > > > + indio_dev); > > > + if (ret) > > > + return ret; > > > break; > > > case AD7151: > > > indio_dev->channels = ad7151_channels; > > > @@ -548,25 +603,18 @@ static int ad7150_probe(struct i2c_client *client, > > > return -EINVAL; > > > } > > > > > > - ret = devm_request_threaded_irq(&client->dev, client->irq, > > > - NULL, > > > - &ad7150_event_handler, > > > - IRQF_TRIGGER_RISING | > > > - IRQF_ONESHOT, > > > - "ad7150_irq1", > > > - indio_dev); > > > - if (ret) > > > - return ret; > > > } else { > > > indio_dev->info = &ad7150_info_no_irq; > > > switch (id->driver_data) { > > > case AD7150: > > > indio_dev->channels = ad7150_channels_no_irq; > > > - indio_dev->num_channels = ARRAY_SIZE(ad7150_channels_no_irq); > > > + indio_dev->num_channels = > > > + ARRAY_SIZE(ad7150_channels_no_irq); > > > break; > > > case AD7151: > > > indio_dev->channels = ad7151_channels_no_irq; > > > - indio_dev->num_channels = ARRAY_SIZE(ad7151_channels_no_irq); > > > + indio_dev->num_channels = > > > + ARRAY_SIZE(ad7151_channels_no_irq); > > > break; > > > default: > > > return -EINVAL; > > > -- > > > 2.30.2 > > > >