diff options
Diffstat (limited to 'drivers/iio/adc/at91-sama5d2_adc.c')
-rw-r--r-- | drivers/iio/adc/at91-sama5d2_adc.c | 329 |
1 files changed, 313 insertions, 16 deletions
diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c index e10dca3ed74b..bc5b38e3a147 100644 --- a/drivers/iio/adc/at91-sama5d2_adc.c +++ b/drivers/iio/adc/at91-sama5d2_adc.c @@ -25,6 +25,11 @@ #include <linux/wait.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/pinctrl/consumer.h> #include <linux/regulator/consumer.h> /* Control Register */ @@ -132,6 +137,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 +161,29 @@ /* 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 + +/* + * Maximum number of bytes to hold conversion from all channels + * plus the timestamp + */ +#define AT91_BUFFER_MAX_BYTES ((AT91_SAMA5D2_SINGLE_CHAN_CNT + \ + AT91_SAMA5D2_DIFF_CHAN_CNT) * 2 + 8) + +#define AT91_BUFFER_MAX_HWORDS (AT91_BUFFER_MAX_BYTES / 2) + #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 +199,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,6 +221,12 @@ struct at91_adc_soc_info { unsigned max_sample_rate; }; +struct at91_adc_trigger { + char *name; + unsigned int trgmod_value; + unsigned int edge_type; +}; + struct at91_adc_state { void __iomem *base; int irq; @@ -195,11 +234,14 @@ struct at91_adc_state { struct regulator *reg; struct regulator *vref; int vref_uv; + struct iio_trigger *trig; + const struct at91_adc_trigger *selected_trig; 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; + u16 buffer[AT91_BUFFER_MAX_HWORDS]; /* * lock to prevent concurrent 'single conversion' requests through * sysfs. @@ -207,6 +249,24 @@ 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, + .edge_type = IRQ_TYPE_EDGE_RISING, + }, + { + .name = "external_falling", + .trgmod_value = AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_FALL, + .edge_type = IRQ_TYPE_EDGE_FALLING, + }, + { + .name = "external_any", + .trgmod_value = AT91_SAMA5D2_TRGR_TRGMOD_EXT_TRIG_ANY, + .edge_type = IRQ_TYPE_EDGE_BOTH, + }, +}; + static const struct iio_chan_spec at91_adc_channels[] = { AT91_SAMA5D2_CHAN_SINGLE(0, 0x50), AT91_SAMA5D2_CHAN_SINGLE(1, 0x54), @@ -226,12 +286,132 @@ 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; + + /* clear TRGMOD */ + status &= ~AT91_SAMA5D2_TRGR_TRGMOD_MASK; + + if (state) + status |= st->selected_trig->trgmod_value; + + /* 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); + + /* Needed to ACK the DRDY interruption */ + at91_adc_readl(st, AT91_SAMA5D2_LCDR); + 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 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 ERR_PTR(ret); + + return trig; +} + +static int at91_adc_trigger_init(struct iio_dev *indio) +{ + struct at91_adc_state *st = iio_priv(indio); + + st->trig = at91_adc_allocate_trigger(indio, st->selected_trig->name); + if (IS_ERR(st->trig)) { + dev_err(&indio->dev, + "could not allocate trigger\n"); + return PTR_ERR(st->trig); + } + + 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); + + 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, NULL); +} + static unsigned at91_adc_startup_time(unsigned startup_time_min, unsigned adc_clk_khz) { - const unsigned startup_lookup[] = { + static const unsigned int startup_lookup[] = { 0, 8, 16, 24, 64, 80, 96, 112, 512, 576, 640, 704, @@ -293,14 +473,18 @@ 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, @@ -313,6 +497,11 @@ static int at91_adc_read_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_RAW: + /* we cannot use software trigger if hw trigger enabled */ + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + mutex_lock(&st->lock); st->chan = chan; @@ -344,6 +533,8 @@ static int at91_adc_read_raw(struct iio_dev *indio_dev, at91_adc_writel(st, AT91_SAMA5D2_CHDR, BIT(chan->channel)); mutex_unlock(&st->lock); + + iio_device_release_direct_mode(indio_dev); return ret; case IIO_CHAN_INFO_SCALE: @@ -386,12 +577,27 @@ static const struct iio_info at91_adc_info = { .driver_module = THIS_MODULE, }; +static void at91_adc_hw_init(struct at91_adc_state *st) +{ + at91_adc_writel(st, AT91_SAMA5D2_CR, AT91_SAMA5D2_CR_SWRST); + at91_adc_writel(st, AT91_SAMA5D2_IDR, 0xffffffff); + /* + * Transfer field must be set to 2 according to the datasheet and + * allows different analog settings for each channel. + */ + at91_adc_writel(st, AT91_SAMA5D2_MR, + AT91_SAMA5D2_MR_TRANSFER(2) | AT91_SAMA5D2_MR_ANACH); + + at91_adc_setup_samp_freq(st, st->soc_info.min_sample_rate); +} + static int at91_adc_probe(struct platform_device *pdev) { struct iio_dev *indio_dev; struct at91_adc_state *st; struct resource *res; - int ret; + int ret, i; + u32 edge_type; indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*st)); if (!indio_dev) @@ -432,6 +638,27 @@ static int at91_adc_probe(struct platform_device *pdev) return ret; } + ret = of_property_read_u32(pdev->dev.of_node, + "atmel,trigger-edge-type", &edge_type); + if (ret) { + dev_err(&pdev->dev, + "invalid or missing value for atmel,trigger-edge-type\n"); + return ret; + } + + st->selected_trig = NULL; + + for (i = 0; i < AT91_SAMA5D2_HW_TRIG_CNT; i++) + if (at91_adc_trigger_list[i].edge_type == edge_type) { + st->selected_trig = &at91_adc_trigger_list[i]; + break; + } + + if (!st->selected_trig) { + dev_err(&pdev->dev, "invalid external trigger edge value\n"); + return -EINVAL; + } + init_waitqueue_head(&st->wq_data_available); mutex_init(&st->lock); @@ -482,16 +709,7 @@ static int at91_adc_probe(struct platform_device *pdev) goto vref_disable; } - at91_adc_writel(st, AT91_SAMA5D2_CR, AT91_SAMA5D2_CR_SWRST); - at91_adc_writel(st, AT91_SAMA5D2_IDR, 0xffffffff); - /* - * Transfer field must be set to 2 according to the datasheet and - * allows different analog settings for each channel. - */ - at91_adc_writel(st, AT91_SAMA5D2_MR, - AT91_SAMA5D2_MR_TRANSFER(2) | AT91_SAMA5D2_MR_ANACH); - - at91_adc_setup_samp_freq(st, st->soc_info.min_sample_rate); + at91_adc_hw_init(st); ret = clk_prepare_enable(st->per_clk); if (ret) @@ -499,10 +717,25 @@ 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; + dev_info(&pdev->dev, "setting up trigger as %s\n", + st->selected_trig->name); + dev_info(&pdev->dev, "version: %x\n", readl_relaxed(st->base + AT91_SAMA5D2_VERSION)); @@ -532,6 +765,69 @@ static int at91_adc_remove(struct platform_device *pdev) return 0; } +static __maybe_unused int at91_adc_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = + platform_get_drvdata(to_platform_device(dev)); + struct at91_adc_state *st = iio_priv(indio_dev); + + /* + * Do a sofware reset of the ADC before we go to suspend. + * this will ensure that all pins are free from being muxed by the ADC + * and can be used by for other devices. + * Otherwise, ADC will hog them and we can't go to suspend mode. + */ + at91_adc_writel(st, AT91_SAMA5D2_CR, AT91_SAMA5D2_CR_SWRST); + + clk_disable_unprepare(st->per_clk); + regulator_disable(st->vref); + regulator_disable(st->reg); + + return pinctrl_pm_select_sleep_state(dev); +} + +static __maybe_unused int at91_adc_resume(struct device *dev) +{ + struct iio_dev *indio_dev = + platform_get_drvdata(to_platform_device(dev)); + struct at91_adc_state *st = iio_priv(indio_dev); + int ret; + + ret = pinctrl_pm_select_default_state(dev); + if (ret) + goto resume_failed; + + ret = regulator_enable(st->reg); + if (ret) + goto resume_failed; + + ret = regulator_enable(st->vref); + if (ret) + goto reg_disable_resume; + + ret = clk_prepare_enable(st->per_clk); + if (ret) + goto vref_disable_resume; + + at91_adc_hw_init(st); + + /* reconfiguring trigger hardware state */ + if (iio_buffer_enabled(indio_dev)) + at91_adc_configure_trigger(st->trig, true); + + return 0; + +vref_disable_resume: + regulator_disable(st->vref); +reg_disable_resume: + regulator_disable(st->reg); +resume_failed: + dev_err(&indio_dev->dev, "failed to resume\n"); + return ret; +} + +static SIMPLE_DEV_PM_OPS(at91_adc_pm_ops, at91_adc_suspend, at91_adc_resume); + static const struct of_device_id at91_adc_dt_match[] = { { .compatible = "atmel,sama5d2-adc", @@ -547,6 +843,7 @@ static struct platform_driver at91_adc_driver = { .driver = { .name = "at91-sama5d2_adc", .of_match_table = at91_adc_dt_match, + .pm = &at91_adc_pm_ops, }, }; module_platform_driver(at91_adc_driver) |