On 11/05/17 07:09, Ludovic Desroches wrote:
On Wed, May 10, 2017 at 11:49:07AM +0300, Eugen Hristev wrote:Hello, Thanks for the suggestions and reply. Please see my answers inline Eugen On 07.05.2017 18:01, Jonathan Cameron wrote:On 04/05/17 13:13, Eugen Hristev wrote:Added support for the external hardware trigger on pin ADTRG, integrated the three possible edge triggers into the subsystem and created buffer management for data retrieval Signed-off-by: Eugen Hristev <eugen.hristev@xxxxxxxxxxxxx> --- Changes in v2: - Moved buffer allocation and freeing into the preenable and postdisable callbacks. We have a total of scan bytes that can vary a lot depending on each channel enabled at a certain point.How much? Having dynamic allocations are likely to prove just as costly as you'll almost always end up allocating a lot more than you ask for. I'm counting worst case of 18x 2byte channels + timestamp = 48 bytes. A quick google suggests you'll always allocate 32 bytes whatever with slab (lower for slob). So not a huge saving... Worth the hassle?I will modify the allocation of scan_bytes to make it static.- made the at91 trigger list part of state structure - made the iio trigger list preallocated in state structure - moved irq enabling/disabling into the try_reenable callbackI'd have liked a little explanation on why we need to explicitly disable the irq. It will have little effect it if triggers too often as the trigger consumers are all oneshot anyway.Regarding the irq, the discussion is a bit longer. As it is now, the interrupt is not a oneshot threaded one, because only the top half handler is installed, and the threaded one is NULL. Calling trigger_poll without disabling the interrupt will make the handler loop, so that is the reason for disabling and reenabling the interrupt. One solution is to change it to oneshot threaded interrupt and call trigger_poll_chained to make it a nested handling. This will make a longer response time for conversions though. One other option is to do irq_save and irq_restore before issuing the trigger poll, but that limits the usability of the trigger by any other device I guess. I did the behavior with disabling and enabling the interrupt considering the old at91 driver and the stm32-adc driver which looks to be fairly similar with this implementation. Can you please advise on which approach you think it's best for this driver ? So I can try that, and resend the patch.
Leave it as is. Thanks for the explanation.
- on trigger disable must write disable registries as wellBasically looks good. A few questions inline. Jonathandrivers/iio/adc/at91-sama5d2_adc.c | 231 ++++++++++++++++++++++++++++++++++++- 1 file changed, 228 insertions(+), 3 deletions(-) diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c index e10dca3..11f5570 100644 --- a/drivers/iio/adc/at91-sama5d2_adc.c +++ b/drivers/iio/adc/at91-sama5d2_adc.c @@ -23,8 +23,15 @@ #include <linux/platform_device.h> #include <linux/sched.h> #include <linux/wait.h> +#include <linux/slab.h> + #include <linux/iio/iio.h> #include <linux/iio/sysfs.h> +#include <linux/iio/buffer.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + #include <linux/regulator/consumer.h> /* Control Register */ @@ -132,6 +139,17 @@ #define AT91_SAMA5D2_PRESSR 0xbc /* Trigger Register */ #define AT91_SAMA5D2_TRGR 0xc0 +/* Mask for TRGMOD field of TRGR register */ +#define AT91_SAMA5D2_TRGR_TRGMOD_MASK GENMASK(2, 0) +/* No trigger, only software trigger can start conversions */ +#define AT91_SAMA5D2_TRGR_TRGMOD_NO_TRIGGER 0 +/* Trigger Mode external trigger rising edge */ +#define AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_RISE 1 +/* Trigger Mode external trigger falling edge */ +#define AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_FALL 2 +/* Trigger Mode external trigger any edge */ +#define AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_ANY 3 + /* Correction Select Register */ #define AT91_SAMA5D2_COSR 0xd0 /* Correction Value Register */ @@ -145,14 +163,20 @@ /* Version Register */ #define AT91_SAMA5D2_VERSION 0xfc +#define AT91_SAMA5D2_HW_TRIG_CNT 3 +#define AT91_SAMA5D2_SINGLE_CHAN_CNT 12 +#define AT91_SAMA5D2_DIFF_CHAN_CNT 6 + #define AT91_SAMA5D2_CHAN_SINGLE(num, addr) \ { \ .type = IIO_VOLTAGE, \ .channel = num, \ .address = addr, \ + .scan_index = num, \ .scan_type = { \ .sign = 'u', \ .realbits = 12, \ + .storagebits = 16, \ }, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ @@ -168,9 +192,11 @@ .channel = num, \ .channel2 = num2, \ .address = addr, \ + .scan_index = num + AT91_SAMA5D2_SINGLE_CHAN_CNT, \ .scan_type = { \ .sign = 's', \ .realbits = 12, \ + .storagebits = 16, \ }, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ @@ -188,18 +214,25 @@ struct at91_adc_soc_info { unsigned max_sample_rate; }; +struct at91_adc_trigger { + char *name; + unsigned int trgmod_value; +}; + struct at91_adc_state { void __iomem *base; int irq; struct clk *per_clk; struct regulator *reg; struct regulator *vref; + u16 *buffer; int vref_uv; const struct iio_chan_spec *chan; bool conversion_done; u32 conversion_value; struct at91_adc_soc_info soc_info; wait_queue_head_t wq_data_available; + struct iio_trigger *trig[AT91_SAMA5D2_HW_TRIG_CNT]; /* * lock to prevent concurrent 'single conversion' requests through * sysfs. @@ -207,6 +240,21 @@ struct at91_adc_state { struct mutex lock; }; +static const struct at91_adc_trigger at91_adc_trigger_list[] = { + { + .name = "external-rising", + .trgmod_value = AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_RISE, + }, + { + .name = "external-falling", + .trgmod_value = AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_FALL, + }, + { + .name = "external-any", + .trgmod_value = AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_ANY, + },Hmm. Should this be a userspace configurable option? Feels rather like it is an element of the hardware - reflecting the characteristics of some hardware device sat on the pin.The user can choose from sysfs which trigger is best suited for the use case, since all three triggers are provided and can be connected to the buffer. It reflects more the triggering capability of the ADC rather than any different hardware device sitting on the pinI am also in favour of a userspace configurable option. For sure it's hardware related but on our board we only provide a trigger pin, we don't know which hardware the customer will put on this pin.
hmm. OK I'm persuaded I think. Could do this with devicetree overlays or similar. So follow up question is whether this is the right interface. Can all 3 run at once sensibly? If not are we not looking at a control parameter for a single trigger?
Regards Ludovic+}; + static const struct iio_chan_spec at91_adc_channels[] = { AT91_SAMA5D2_CHAN_SINGLE(0, 0x50), AT91_SAMA5D2_CHAN_SINGLE(1, 0x54), @@ -226,8 +274,168 @@ static const struct iio_chan_spec at91_adc_channels[] = { AT91_SAMA5D2_CHAN_DIFF(6, 7, 0x68), AT91_SAMA5D2_CHAN_DIFF(8, 9, 0x70), AT91_SAMA5D2_CHAN_DIFF(10, 11, 0x78), + IIO_CHAN_SOFT_TIMESTAMP(AT91_SAMA5D2_SINGLE_CHAN_CNT + + AT91_SAMA5D2_DIFF_CHAN_CNT + 1), }; +static int at91_adc_configure_trigger(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio = iio_trigger_get_drvdata(trig); + struct at91_adc_state *st = iio_priv(indio); + u32 status = at91_adc_readl(st, AT91_SAMA5D2_TRGR); + u8 bit; + int i; + + /* clear TRGMOD */ + status &= ~AT91_SAMA5D2_TRGR_TRGMOD_MASK; + + if (state) + for (i = 0; i < AT91_SAMA5D2_HW_TRIG_CNT; i++) { + if (!strstr(trig->name, + at91_adc_trigger_list[i].name)) { + status |= at91_adc_trigger_list[i].trgmod_value; + break; + } + } + + /* set/unset hw trigger */ + at91_adc_writel(st, AT91_SAMA5D2_TRGR, status); + + for_each_set_bit(bit, indio->active_scan_mask, indio->num_channels) { + struct iio_chan_spec const *chan = indio->channels + bit; + + if (state) { + at91_adc_writel(st, AT91_SAMA5D2_CHER, + BIT(chan->channel)); + at91_adc_writel(st, AT91_SAMA5D2_IER, + BIT(chan->channel)); + } else { + at91_adc_writel(st, AT91_SAMA5D2_IDR, + BIT(chan->channel)); + at91_adc_writel(st, AT91_SAMA5D2_CHDR, + BIT(chan->channel)); + } + } + + return 0; +} + +static int at91_adc_reenable_trigger(struct iio_trigger *trig) +{ + struct iio_dev *indio = iio_trigger_get_drvdata(trig); + struct at91_adc_state *st = iio_priv(indio); + + enable_irq(st->irq); + return 0; +} + +static const struct iio_trigger_ops at91_adc_trigger_ops = { + .owner = THIS_MODULE, + .set_trigger_state = &at91_adc_configure_trigger, + .try_reenable = &at91_adc_reenable_trigger, +}; + +static int at91_adc_buffer_preenable(struct iio_dev *indio_dev) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + + st->buffer = kmalloc(indio_dev->scan_bytes, GFP_KERNEL);Why allocate it here? Presumably the biggest it can get is fairlysmall? Unless very large, just make sure you allocate enough directly in st in the first place.Yes , as discussed above will change to static allocation.+ + if (!st->buffer) + return -ENOMEM; + + return 0; +} + +static int at91_adc_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct at91_adc_state *st = iio_priv(indio_dev); + + kfree(st->buffer); + + return 0; +} + +static const struct iio_buffer_setup_ops at91_buffer_setup_ops = { + .preenable = &at91_adc_buffer_preenable, + .postenable = &iio_triggered_buffer_postenable, + .predisable = &iio_triggered_buffer_predisable, + .postdisable = &at91_adc_buffer_postdisable, +}; + +static struct iio_trigger *at91_adc_allocate_trigger(struct iio_dev *indio, + char *trigger_name) +{ + struct iio_trigger *trig; + int ret; + + trig = devm_iio_trigger_alloc(&indio->dev, "%s-dev%d-%s", indio->name, + indio->id, trigger_name); + if (!trig) + return NULL; + + trig->dev.parent = indio->dev.parent; + iio_trigger_set_drvdata(trig, indio); + trig->ops = &at91_adc_trigger_ops; + + ret = devm_iio_trigger_register(&indio->dev, trig); + + if (ret) + return NULL; + + return trig; +} + +static int at91_adc_trigger_init(struct iio_dev *indio) +{ + struct at91_adc_state *st = iio_priv(indio); + int i; + + for (i = 0; i < AT91_SAMA5D2_HW_TRIG_CNT; i++) { + st->trig[i] = at91_adc_allocate_trigger(indio, + at91_adc_trigger_list[i].name); + if (!st->trig[i]) { + dev_err(&indio->dev, + "could not allocate trigger %d\n", i); + return -ENOMEM; + } + } + + return 0; +} + +static irqreturn_t at91_adc_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio = pf->indio_dev; + struct at91_adc_state *st = iio_priv(indio); + int i = 0; + u8 bit; + + for_each_set_bit(bit, indio->active_scan_mask, indio->num_channels) { + struct iio_chan_spec const *chan = indio->channels + bit; + + st->buffer[i] = at91_adc_readl(st, chan->address); + i++; + } + + iio_push_to_buffers_with_timestamp(indio, st->buffer, pf->timestamp); + + iio_trigger_notify_done(indio->trig); + + /* Needed to ACK the DRDY interruption */ + at91_adc_readl(st, AT91_SAMA5D2_LCDR); + + return IRQ_HANDLED; +} + +static int at91_adc_buffer_init(struct iio_dev *indio) +{ + return devm_iio_triggered_buffer_setup(&indio->dev, indio, + &iio_pollfunc_store_time, + &at91_adc_trigger_handler, &at91_buffer_setup_ops); +} + static unsigned at91_adc_startup_time(unsigned startup_time_min, unsigned adc_clk_khz) { @@ -293,14 +501,19 @@ static irqreturn_t at91_adc_interrupt(int irq, void *private) u32 status = at91_adc_readl(st, AT91_SAMA5D2_ISR); u32 imr = at91_adc_readl(st, AT91_SAMA5D2_IMR); - if (status & imr) { + if (!(status & imr)) + return IRQ_NONE; + + if (iio_buffer_enabled(indio)) { + disable_irq_nosync(irq); + iio_trigger_poll(indio->trig); + } else { st->conversion_value = at91_adc_readl(st, st->chan->address); st->conversion_done = true; wake_up_interruptible(&st->wq_data_available); - return IRQ_HANDLED; } - return IRQ_NONE; + return IRQ_HANDLED; } static int at91_adc_read_raw(struct iio_dev *indio_dev, @@ -499,6 +712,18 @@ static int at91_adc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, indio_dev); + ret = at91_adc_buffer_init(indio_dev); + if (ret < 0) { + dev_err(&pdev->dev, "couldn't initialize the buffer.\n"); + goto per_clk_disable_unprepare; + } + + ret = at91_adc_trigger_init(indio_dev); + if (ret < 0) { + dev_err(&pdev->dev, "couldn't setup the triggers.\n"); + goto per_clk_disable_unprepare; + } + ret = iio_device_register(indio_dev); if (ret < 0) goto per_clk_disable_unprepare;-- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html
-- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html