Re: [PATCH v3 2/6] iio: pulse: add TI ECAP driver

From: Jonathan Cameron
Date: Sun Apr 03 2016 - 06:41:48 EST


On 28/03/16 16:30, Jonathan Cameron wrote:
> On 05/02/14 19:01, Matt Porter wrote:
>> Adds support for capturing PWM signals using the TI ECAP peripheral.
>> This driver supports triggered buffer capture of pulses on multiple
>> ECAP instances. In addition, the driver supports configurable polarity
>> of the signal to be captured.
>>
>> Signed-off-by: Matt Porter <mporter@xxxxxxxxxx>
> Hi Matt,
>
> Given I had a personal message asking about this a few weeks ago, I thought I'd
> ask... Any plans to resurrect this?
Guess that's a no :(

If anyone wants a a driver to get kernel driver merging experience with this would
be a good one. Hardware is readily available I think as the beaglebone black has one
of these exposed - there is no support for it in the TI tree either that I can see.

I would cc'd the beagleboard google group but can't actually figure out which is
the right one. Michael, if you know could you reply, with such a group cc'd?
(or anyone else for that matter)

I'd kind of like to pick this one up but will be a while before I clear enough time
to do so (possibly another couple of years!)

Jonathan



>> ---
>> drivers/iio/pulse/Kconfig | 20 ++
>> drivers/iio/pulse/Makefile | 6 +
>> drivers/iio/pulse/tiecap.c | 491 +++++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 517 insertions(+)
>> create mode 100644 drivers/iio/pulse/Kconfig
>> create mode 100644 drivers/iio/pulse/Makefile
>> create mode 100644 drivers/iio/pulse/tiecap.c
>>
>> diff --git a/drivers/iio/pulse/Kconfig b/drivers/iio/pulse/Kconfig
>> new file mode 100644
>> index 0000000..9864d4b
>> --- /dev/null
>> +++ b/drivers/iio/pulse/Kconfig
>> @@ -0,0 +1,20 @@
>> +#
>> +# Pulse Capture Devices
>> +#
>> +# When adding new entries keep the list in alphabetical order
>> +
>> +menu "Pulse Capture Devices"
>> +
>> +config IIO_TIECAP
>> + tristate "TI ECAP Pulse Capture"
>> + depends on SOC_AM33XX
>> + select IIO_BUFFER
>> + select IIO_TRIGGERED_BUFFER
>> + help
>> + If you say yes here you get support for the TI ECAP peripheral
>> + in pulse capture mode.
>> +
>> + This driver can also be built as a module. If so, the module
>> + will be called tiecap
>> +
>> +endmenu
>> diff --git a/drivers/iio/pulse/Makefile b/drivers/iio/pulse/Makefile
>> new file mode 100644
>> index 0000000..94d4b00
>> --- /dev/null
>> +++ b/drivers/iio/pulse/Makefile
>> @@ -0,0 +1,6 @@
>> +#
>> +# Makefile for IIO PWM Capture Devices
>> +#
>> +
>> +# When adding new entries keep the list in alphabetical order
>> +obj-$(CONFIG_IIO_TIECAP) += tiecap.o
>> diff --git a/drivers/iio/pulse/tiecap.c b/drivers/iio/pulse/tiecap.c
>> new file mode 100644
>> index 0000000..fd96745
>> --- /dev/null
>> +++ b/drivers/iio/pulse/tiecap.c
>> @@ -0,0 +1,491 @@
>> +/*
>> + * ECAP IIO pulse capture driver
>> + *
>> + * Copyright (C) 2014 Linaro Limited
>> + * Author: Matt Porter <mporter@xxxxxxxxxx>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> + * (at your option) any later version.
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/err.h>
>> +#include <linux/iio/buffer.h>
>> +#include <linux/iio/iio.h>
>> +#include <linux/iio/sysfs.h>
>> +#include <linux/iio/trigger.h>
>> +#include <linux/iio/trigger_consumer.h>
>> +#include <linux/iio/triggered_buffer.h>
>> +#include <linux/io.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/irq.h>
>> +#include <linux/module.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pm_runtime.h>
>> +
>> +#include "../../pwm/pwm-tipwmss.h"
>> +
>> +/* ECAP regs and bits */
>> +#define CAP1 0x08
>> +#define CAP2 0x0c
>> +#define ECCTL1 0x28
>> +#define ECCTL1_RUN_FREE BIT(15)
>> +#define ECCTL1_CAPLDEN BIT(8)
>> +#define ECCTL1_CAP2POL BIT(2)
>> +#define ECCTL1_CTRRST1 BIT(1)
>> +#define ECCTL1_CAP1POL BIT(0)
>> +#define ECCTL2 0x2a
>> +#define ECCTL2_SYNCO_SEL_DIS BIT(7)
>> +#define ECCTL2_TSCTR_FREERUN BIT(4)
>> +#define ECCTL2_REARM BIT(3)
>> +#define ECCTL2_STOP_WRAP_2 BIT(1)
>> +#define ECEINT 0x2c
>> +#define ECFLG 0x2e
>> +#define ECCLR 0x30
>> +#define ECINT_CTRCMP BIT(7)
>> +#define ECINT_CTRPRD BIT(6)
>> +#define ECINT_CTROVF BIT(5)
>> +#define ECINT_CEVT4 BIT(4)
>> +#define ECINT_CEVT3 BIT(3)
>> +#define ECINT_CEVT2 BIT(2)
>> +#define ECINT_CEVT1 BIT(1)
>> +#define ECINT_ALL (ECINT_CTRCMP | \
>> + ECINT_CTRPRD | \
>> + ECINT_CTROVF | \
>> + ECINT_CEVT4 | \
>> + ECINT_CEVT3 | \
>> + ECINT_CEVT2 | \
>> + ECINT_CEVT1)
>> +
>> +/* ECAP driver flags */
>> +#define ECAP_POLARITY_HIGH BIT(1)
>> +#define ECAP_ENABLED BIT(0)
>> +
>> +struct ecap_context {
>> + u32 cap1;
>> + u32 cap2;
>> + u16 ecctl1;
>> + u16 ecctl2;
>> + u16 eceint;
>> +};
>> +
>> +struct ecap_state {
>> + unsigned long flags;
>> + unsigned int clk_rate;
>> + void __iomem *regs;
>> + u32 *buf;
>> + struct ecap_context ctx;
>> +};
>> +
>> +#define dev_to_ecap_state(d) iio_priv(dev_to_iio_dev(d))
>> +
>> +static const struct iio_chan_spec ecap_channels[] = {
>> + {
>> + .type = IIO_PULSE,
>> + .info_mask_separate =
>> + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
>> + .scan_index = 0,
>> + .scan_type = {
>> + .sign = 'u',
>> + .realbits = 32,
>> + .storagebits = 32,
>> + .endianness = IIO_LE,
>> + },
>> + },
>> + IIO_CHAN_SOFT_TIMESTAMP(1)
>> +};
>> +
>> +static ssize_t ecap_attr_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct ecap_state *state = dev_to_ecap_state(dev);
>> +
>> + return sprintf(buf, "%d\n",
>> + test_bit(ECAP_POLARITY_HIGH, &state->flags));
>> +}
>> +
>> +static ssize_t ecap_attr_store(struct device *dev,
>> + struct device_attribute *attr,
>> + const char *buf,
>> + size_t len)
>> +{
>> + int ret;
>> + bool val;
>> + struct ecap_state *state = dev_to_ecap_state(dev);
>> +
>> + if (test_bit(ECAP_ENABLED, &state->flags))
>> + return -EINVAL;
>> +
>> + ret = strtobool(buf, &val);
>> + if (ret)
>> + return ret;
>> +
>> + if (val)
>> + set_bit(ECAP_POLARITY_HIGH, &state->flags);
>> + else
>> + clear_bit(ECAP_POLARITY_HIGH, &state->flags);
>> +
>> + return len;
>> +}
>> +
>> +static IIO_DEVICE_ATTR(pulse_polarity, S_IRUGO | S_IWUSR,
>> + ecap_attr_show, ecap_attr_store, 0);
>> +
>> +static struct attribute *ecap_attributes[] = {
>> + &iio_dev_attr_pulse_polarity.dev_attr.attr,
>> + NULL,
>> +};
>> +
>> +static struct attribute_group ecap_attribute_group = {
>> + .attrs = ecap_attributes,
>> +};
>> +
>> +static int ecap_read_raw(struct iio_dev *idev,
>> + struct iio_chan_spec const *ch, int *val,
>> + int *val2, long mask)
>> +{
>> + struct ecap_state *state = iio_priv(idev);
>> +
>> + switch (mask) {
>> + case IIO_CHAN_INFO_RAW:
>> + /*
>> + * Always return 0 as a pulse width sample
>> + * is only valid in a triggered condition
>> + */
>> + *val = 0;
>> + return IIO_VAL_INT;
>> + case IIO_CHAN_INFO_SCALE:
>> + *val = 0;
>> + *val2 = NSEC_PER_SEC / state->clk_rate;
>> + return IIO_VAL_INT_PLUS_NANO;
>> + default:
>> + return -EINVAL;
>> + }
>> +}
>> +
>> +static const struct iio_info ecap_info = {
>> + .driver_module = THIS_MODULE,
>> + .attrs = &ecap_attribute_group,
>> + .read_raw = &ecap_read_raw,
>> +};
>> +
>> +static irqreturn_t ecap_trigger_handler(int irq, void *private)
>> +{
>> + struct iio_poll_func *pf = private;
>> + struct iio_dev *idev = pf->indio_dev;
>> + struct ecap_state *state = iio_priv(idev);
>> +
>> + /* Read pulse counter value */
>> + *state->buf = readl(state->regs + CAP2);
>> +
>> + iio_push_to_buffers_with_timestamp(idev, state->buf, iio_get_time_ns());
>> +
>> + iio_trigger_notify_done(idev->trig);
>> +
>> + return IRQ_HANDLED;
>> +};
>> +
>> +
>> +static const struct iio_trigger_ops iio_interrupt_trigger_ops = {
>> + .owner = THIS_MODULE,
>> +};
>> +
>> +static irqreturn_t ecap_interrupt_handler(int irq, void *private)
>> +{
>> + struct iio_dev *idev = private;
>> + struct ecap_state *state = iio_priv(idev);
>> + u16 ints;
>> +
>> + iio_trigger_poll(idev->trig, 0);
>> +
>> + /* Clear CAP2 interrupt */
>> + ints = readw(state->regs + ECFLG);
>> + if (ints & ECINT_CEVT2)
>> + writew(ECINT_CEVT2, state->regs + ECCLR);
>> + else
>> + dev_warn(&idev->dev, "unhandled interrupt flagged: %04x\n",
>> + ints);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static int ecap_buffer_predisable(struct iio_dev *idev)
>> +{
>> + struct ecap_state *state = iio_priv(idev);
>> + int ret = 0;
>> + u16 ecctl2;
>> +
>> + /* Stop capture */
>> + clear_bit(ECAP_ENABLED, &state->flags);
>> + ecctl2 = readw(state->regs + ECCTL2) & ~ECCTL2_TSCTR_FREERUN;
>> + writew(ecctl2, state->regs + ECCTL2);
>> +
>> + /* Disable and clear all interrupts */
>> + writew(0, state->regs + ECEINT);
>> + writew(ECINT_ALL, state->regs + ECCLR);
>> +
>> + ret = iio_triggered_buffer_predisable(idev);
>> +
>> + pm_runtime_put_sync(idev->dev.parent);
>> +
>> + return ret;
>> +}
>> +
>> +static int ecap_buffer_postenable(struct iio_dev *idev)
>> +{
>> + struct ecap_state *state = iio_priv(idev);
>> + int ret = 0;
>> + u16 ecctl1, ecctl2;
>> +
>> + pm_runtime_get_sync(idev->dev.parent);
>> +
>> + /* Configure pulse polarity */
>> + ecctl1 = readw(state->regs + ECCTL1);
>> + if (test_bit(ECAP_POLARITY_HIGH, &state->flags)) {
>> + /* CAP1 rising, CAP2 falling */
>> + ecctl1 |= ECCTL1_CAP2POL;
>> + ecctl1 &= ~ECCTL1_CAP1POL;
>> + } else {
>> + /* CAP1 falling, CAP2 rising */
>> + ecctl1 &= ~ECCTL1_CAP2POL;
>> + ecctl1 |= ECCTL1_CAP1POL;
>> + }
>> + writew(ecctl1, state->regs + ECCTL1);
>> +
>> + /* Enable CAP2 interrupt */
>> + writew(ECINT_CEVT2, state->regs + ECEINT);
>> +
>> + /* Enable capture */
>> + ecctl2 = readw(state->regs + ECCTL2);
>> + ecctl2 |= ECCTL2_TSCTR_FREERUN | ECCTL2_REARM;
>> + writew(ecctl2, state->regs + ECCTL2);
>> + set_bit(ECAP_ENABLED, &state->flags);
>> +
>> + ret = iio_triggered_buffer_postenable(idev);
>> +
>> + return ret;
>> +}
>> +
>> +static const struct iio_buffer_setup_ops ecap_buffer_setup_ops = {
>> + .postenable = &ecap_buffer_postenable,
>> + .predisable = &ecap_buffer_predisable,
>> +};
>> +
>> +static void ecap_init_hw(struct iio_dev *idev)
>> +{
>> + struct ecap_state *state = iio_priv(idev);
>> +
>> + clear_bit(ECAP_ENABLED, &state->flags);
>> + set_bit(ECAP_POLARITY_HIGH, &state->flags);
>> +
>> + writew(ECCTL1_RUN_FREE | ECCTL1_CAPLDEN |
>> + ECCTL1_CAP2POL | ECCTL1_CTRRST1,
>> + state->regs + ECCTL1);
>> +
>> + writew(ECCTL2_SYNCO_SEL_DIS | ECCTL2_STOP_WRAP_2,
>> + state->regs + ECCTL2);
>> +}
>> +
>> +static const struct of_device_id ecap_of_ids[] = {
>> + { .compatible = "ti,am33xx-ecap" },
>> + { /* sentinel */ },
>> +};
>> +MODULE_DEVICE_TABLE(of, ecap_of_ids);
>> +
>> +static int ecap_probe(struct platform_device *pdev)
>> +{
>> + int irq, ret;
>> + struct iio_dev *idev;
>> + struct ecap_state *state;
>> + struct resource *r;
>> + struct clk *clk;
>> + struct iio_trigger *trig;
>> + u16 status;
>> +
>> + idev = devm_iio_device_alloc(&pdev->dev, sizeof(struct ecap_state));
>> + if (!idev)
>> + return -ENOMEM;
>> +
>> + state = iio_priv(idev);
>> +
>> + clk = devm_clk_get(&pdev->dev, "fck");
>> + if (IS_ERR(clk)) {
>> + dev_err(&pdev->dev, "failed to get clock\n");
>> + return PTR_ERR(clk);
>> + }
>> +
>> + state->clk_rate = clk_get_rate(clk);
>> + if (!state->clk_rate) {
>> + dev_err(&pdev->dev, "failed to get clock rate\n");
>> + return -EINVAL;
>> + }
>> +
>> + platform_set_drvdata(pdev, idev);
>> +
>> + idev->dev.parent = &pdev->dev;
>> + idev->name = dev_name(&pdev->dev);
>> + idev->modes = INDIO_DIRECT_MODE;
>> + idev->info = &ecap_info;
>> + idev->channels = ecap_channels;
>> + /* One h/w capture and one s/w timestamp channel per instance */
>> + idev->num_channels = ARRAY_SIZE(ecap_channels);
>> +
>> + trig = devm_iio_trigger_alloc(&pdev->dev, "%s-dev%d",
>> + idev->name, idev->id);
>> + if (!trig)
>> + return -ENOMEM;
>> + trig->dev.parent = idev->dev.parent;
>> + iio_trigger_set_drvdata(trig, idev);
>> + trig->ops = &iio_interrupt_trigger_ops;
>> +
>> + ret = iio_trigger_register(trig);
>> + if (ret) {
>> + dev_err(&pdev->dev, "failed to register trigger\n");
>> + return ret;
>> + }
>> +
>> + ret = iio_triggered_buffer_setup(idev, NULL,
>> + &ecap_trigger_handler,
>> + &ecap_buffer_setup_ops);
>> + if (ret)
>> + return ret;
>> +
>> + irq = platform_get_irq(pdev, 0);
>> + if (irq < 0) {
>> + dev_err(&pdev->dev, "no irq is specified\n");
>> + return irq;
>> + }
>> + ret = devm_request_irq(&pdev->dev, irq,
>> + &ecap_interrupt_handler,
>> + 0, dev_name(&pdev->dev), idev);
>> + if (ret) {
>> + dev_err(&pdev->dev, "unable to request irq\n");
>> + goto uninit_buffer;
>> + }
>> +
>> + r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + state->regs = devm_ioremap_resource(&pdev->dev, r);
>> + if (IS_ERR(state->regs)) {
>> + dev_err(&pdev->dev, "unable to remap registers\n");
>> + ret = PTR_ERR(state->regs);
>> + goto uninit_buffer;
>> + };
>> +
>> + ret = iio_device_register(idev);
>> + if (ret < 0) {
>> + dev_err(&pdev->dev, "unable to register device\n");
>> + goto uninit_buffer;
>> + }
>> +
>> + state->buf = devm_kzalloc(&idev->dev, idev->scan_bytes, GFP_KERNEL);
>> + if (!state->buf) {
>> + ret = -ENOMEM;
>> + goto uninit_buffer;
>> + }
>> +
>> + pm_runtime_enable(&pdev->dev);
>> + pm_runtime_get_sync(&pdev->dev);
>> +
>> + status = pwmss_submodule_state_change(pdev->dev.parent,
>> + PWMSS_ECAPCLK_EN);
>> + if (!(status & PWMSS_ECAPCLK_EN_ACK)) {
>> + dev_err(&pdev->dev, "failed to enable PWMSS config space clock\n");
>> + ret = -EINVAL;
>> + goto pwmss_clk_failure;
>> + }
>> +
>> + ecap_init_hw(idev);
>> +
>> + pm_runtime_put_sync(&pdev->dev);
>> +
>> + return 0;
>> +
>> +pwmss_clk_failure:
>> + pm_runtime_put_sync(&pdev->dev);
>> + pm_runtime_disable(&pdev->dev);
>> + iio_device_unregister(idev);
>> +
>> +uninit_buffer:
>> + iio_triggered_buffer_cleanup(idev);
>> +
>> + return ret;
>> +}
>> +
>> +static int ecap_remove(struct platform_device *pdev)
>> +{
>> + struct iio_dev *idev = platform_get_drvdata(pdev);
>> +
>> + pm_runtime_get_sync(&pdev->dev);
>> +
>> + pwmss_submodule_state_change(pdev->dev.parent, PWMSS_ECAPCLK_STOP_REQ);
>> +
>> + pm_runtime_put_sync(&pdev->dev);
>> + pm_runtime_disable(&pdev->dev);
>> +
>> + iio_device_unregister(idev);
>> + iio_triggered_buffer_cleanup(idev);
>> +
>> + return 0;
>> +}
>> +
>> +#ifdef CONFIG_PM_SLEEP
>> +static int ecap_suspend(struct device *dev)
>> +{
>> + struct ecap_state *state = dev_to_ecap_state(dev);
>> +
>> + pm_runtime_get_sync(dev);
>> + state->ctx.cap1 = readl(state->regs + CAP1);
>> + state->ctx.cap2 = readl(state->regs + CAP2);
>> + state->ctx.eceint = readw(state->regs + ECEINT);
>> + state->ctx.ecctl1 = readw(state->regs + ECCTL1);
>> + state->ctx.ecctl2 = readw(state->regs + ECCTL2);
>> + pm_runtime_put_sync(dev);
>> +
>> + /* If capture was active, disable ECAP */
>> + if (test_bit(ECAP_ENABLED, &state->flags))
>> + pm_runtime_put_sync(dev);
>> +
>> + return 0;
>> +}
>> +
>> +static int ecap_resume(struct device *dev)
>> +{
>> + struct ecap_state *state = dev_to_ecap_state(dev);
>> +
>> + /* If capture was active, enable ECAP */
>> + if (test_bit(ECAP_ENABLED, &state->flags))
>> + pm_runtime_get_sync(dev);
>> +
>> + pm_runtime_get_sync(dev);
>> + writel(state->ctx.cap1, state->regs + CAP1);
>> + writel(state->ctx.cap2, state->regs + CAP2);
>> + writew(state->ctx.eceint, state->regs + ECEINT);
>> + writew(state->ctx.ecctl1, state->regs + ECCTL1);
>> + writew(state->ctx.ecctl2, state->regs + ECCTL2);
>> + pm_runtime_put_sync(dev);
>> +
>> + return 0;
>> +}
>> +#endif
>> +
>> +static SIMPLE_DEV_PM_OPS(ecap_pm_ops, ecap_suspend, ecap_resume);
>> +
>> +static struct platform_driver ecap_iio_driver = {
>> + .driver = {
>> + .name = "ecap",
>> + .owner = THIS_MODULE,
>> + .of_match_table = of_match_ptr(ecap_of_ids),
>> + .pm = &ecap_pm_ops,
>> + },
>> + .probe = ecap_probe,
>> + .remove = ecap_remove,
>> +};
>> +
>> +module_platform_driver(ecap_iio_driver);
>> +
>> +MODULE_DESCRIPTION("ECAP IIO pulse capture driver");
>> +MODULE_AUTHOR("Matt Porter <mporter@xxxxxxxxxx>");
>> +MODULE_LICENSE("GPL");
>>
>
> --
> 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
>