On Wed, May 10, 2017 at 11:49:07AM +0300, Eugen Hristev wrote:Leave it as is. Thanks for the explanation.
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,How much? Having dynamic allocations are likely to prove just as costly as you'll
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.
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 structureI'd have liked a little explanation on why we need to explicitly disable
- made the iio trigger list preallocated in state structure
- moved irq enabling/disabling into the try_reenable callback
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.
hmm. OK I'm persuaded I think.
The user can choose from sysfs which trigger- on trigger disable must write disable registries as wellBasically looks good. A few questions inline.
Jonathan
Hmm. Should this be a userspace configurable option? Feels rather like
drivers/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,
+ },
it is an element of the hardware - reflecting the characteristics of some
hardware device sat on the pin.
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 pin
I 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.
Regards
Ludovic
+};small? Unless very large, just make sure you allocate enough
+
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 fairly
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