From: Christopher Hall <christopher.s.hall@xxxxxxxxx> Implement poll() and setup_poll() methods in the PMC Timed I/O Driver for added GPIO lib functionality. The setup_poll() code configures the hardware to listen for events on the Timed I/O interface. The poll() interface returns the timestamp of the last event or -EAGAIN if no events are available. Use timekeeping event get_device_system_crosststamp() interface to translate ART/TSC to CLOCK_REALTIME. The poll operation is driven from user space and may not occur within the same timekeeping interval as the actual event, necessiating the use of the get_device_system_crosststamp() with an inteepolation window. Uses snapshotting interface to extend the translation/interpolation window beyond the current timekeeping interval because, poll operation is driven from user space and may not occur within the same timekeeping interval as the actual event. . Necessitating use of the get_device_system_crosststamp() with an interpolation window. The ktime snapshot is guaranteed to be updated at least every 1/8 second using a work queue item minimizing the interpolation window. Signed-off-by: Christopher Hall <christopher.s.hall@xxxxxxxxx> Signed-off-by: Tamal Saha <tamal.saha@xxxxxxxxx> Co-developed-by: Lakshmi Sowjanya D <lakshmi.sowjanya.d@xxxxxxxxx> Signed-off-by: Lakshmi Sowjanya D <lakshmi.sowjanya.d@xxxxxxxxx> Reviewed-by: Mark Gross <mgross@xxxxxxxxxxxxxxx> --- drivers/gpio/gpio-intel-tio-pmc.c | 220 +++++++++++++++++++++++++++++- 1 file changed, 219 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-intel-tio-pmc.c b/drivers/gpio/gpio-intel-tio-pmc.c index e6436b56ebea..7e5e61054dea 100644 --- a/drivers/gpio/gpio-intel-tio-pmc.c +++ b/drivers/gpio/gpio-intel-tio-pmc.c @@ -8,6 +8,7 @@ #include <linux/debugfs.h> #include <linux/gpio/driver.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/platform_device.h> #include <uapi/linux/gpio.h> @@ -33,6 +34,9 @@ #define TGPIOCTL_PM BIT(4) #define DRIVER_NAME "intel-pmc-tio" +#define GPIO_COUNT 1 +#define INPUT_SNAPSHOT_FREQ 8 +#define INPUT_SNAPSHOT_COUNT 3 struct intel_pmc_tio_chip { struct gpio_chip gch; @@ -40,8 +44,29 @@ struct intel_pmc_tio_chip { struct dentry *root; struct debugfs_regset32 *regset; void __iomem *base; + struct mutex lock; /* Protects 'ctrl', time */ + struct delayed_work input_work; + bool input_work_running; + bool systime_valid; + unsigned int systime_index; + struct system_time_snapshot systime_snapshot[INPUT_SNAPSHOT_COUNT]; + u64 last_event_count; + u64 last_art_timestamp; }; +struct intel_pmc_tio_get_time_arg { + struct intel_pmc_tio_chip *tio; + u32 eflags; + u32 event_id; + u64 abs_event_count; +}; + +#define gch_to_intel_pmc_tio(i) \ + (container_of((i), struct intel_pmc_tio_chip, gch)) + +#define inws_to_intel_pmc_tio(i) \ + (container_of((i), struct intel_pmc_tio_chip, input_work.work)) + static const struct debugfs_reg32 intel_pmc_tio_regs[] = { { .name = "TGPIOCTL", @@ -81,6 +106,193 @@ static const struct debugfs_reg32 intel_pmc_tio_regs[] = { }, }; +static inline u32 intel_pmc_tio_readl(struct intel_pmc_tio_chip *tio, + u32 offset) +{ + return readl(tio->base + offset); +} + +static inline void intel_pmc_tio_writel(struct intel_pmc_tio_chip *tio, + u32 offset, u32 value) +{ + writel(value, tio->base + offset); +} + +#define INTEL_PMC_TIO_RD_REG(offset)( \ + intel_pmc_tio_readl((tio), (offset))) +#define INTEL_PMC_TIO_WR_REG(offset, value)( \ + intel_pmc_tio_writel((tio), (offset), (value))) + +static void intel_pmc_tio_enable_input(struct intel_pmc_tio_chip *tio, + u32 eflags) +{ + bool rising, falling; + u32 ctrl; + + /* Disable */ + ctrl = INTEL_PMC_TIO_RD_REG(TGPIOCTL); + ctrl &= ~TGPIOCTL_EN; + INTEL_PMC_TIO_WR_REG(TGPIOCTL, ctrl); + + tio->last_event_count = 0; + + /* Configure Input */ + ctrl |= TGPIOCTL_DIR; + ctrl &= ~TGPIOCTL_EP; + + rising = eflags & GPIO_V2_LINE_FLAG_EDGE_RISING; + falling = eflags & GPIO_V2_LINE_FLAG_EDGE_FALLING; + if (rising && falling) + ctrl |= TGPIOCTL_EP_TOGGLE_EDGE; + else if (rising) + ctrl |= TGPIOCTL_EP_RISING_EDGE; + else + ctrl |= TGPIOCTL_EP_FALLING_EDGE; + + /* Enable */ + INTEL_PMC_TIO_WR_REG(TGPIOCTL, ctrl); + ctrl |= TGPIOCTL_EN; + INTEL_PMC_TIO_WR_REG(TGPIOCTL, ctrl); +} + +static void intel_pmc_tio_input_work(struct work_struct *input_work) +{ + struct intel_pmc_tio_chip *tio = inws_to_intel_pmc_tio(input_work); + + mutex_lock(&tio->lock); + + tio->systime_index = (tio->systime_index + 1) % INPUT_SNAPSHOT_COUNT; + if (tio->systime_index == INPUT_SNAPSHOT_COUNT - 1) + tio->systime_valid = true; + ktime_get_snapshot(&tio->systime_snapshot[tio->systime_index]); + schedule_delayed_work(&tio->input_work, HZ / INPUT_SNAPSHOT_FREQ); + + mutex_unlock(&tio->lock); +} + +static void intel_pmc_tio_start_input_work(struct intel_pmc_tio_chip *tio) +{ + if (tio->input_work_running) + return; + + tio->systime_index = 0; + tio->systime_valid = false; + ktime_get_snapshot(&tio->systime_snapshot[tio->systime_index]); + + schedule_delayed_work(&tio->input_work, HZ / INPUT_SNAPSHOT_FREQ); + tio->input_work_running = true; +} + +static void intel_pmc_tio_stop_input_work(struct intel_pmc_tio_chip *tio) +{ + if (!tio->input_work_running) + return; + + cancel_delayed_work_sync(&tio->input_work); + tio->input_work_running = false; +} + +static int intel_pmc_tio_setup_poll(struct gpio_chip *chip, unsigned int offset, + u32 *eflags) +{ + struct intel_pmc_tio_chip *tio; + + if (offset != 0) + return -EINVAL; + + tio = gch_to_intel_pmc_tio(chip); + + mutex_lock(&tio->lock); + intel_pmc_tio_start_input_work(tio); + intel_pmc_tio_enable_input(tio, *eflags); + mutex_unlock(&tio->lock); + + return 0; +} + +static int intel_pmc_tio_get_time(ktime_t *device_time, + struct system_counterval_t *system_counterval, + void *ctx) +{ + struct intel_pmc_tio_get_time_arg *arg = (typeof(arg))ctx; + struct intel_pmc_tio_chip *tio = arg->tio; + u32 flags = arg->eflags; + u64 abs_event_count; + u32 rel_event_count; + u64 art_timestamp; + u32 dt_hi_s; + u32 dt_hi_e; + int err = 0; + u32 dt_lo; + + /* Upper 64 bits of TCV are unlocked, don't use */ + dt_hi_s = read_art_time() >> 32; + dt_lo = INTEL_PMC_TIO_RD_REG(TGPIOTCV31_0); + abs_event_count = INTEL_PMC_TIO_RD_REG(TGPIOECCV63_32); + abs_event_count <<= 32; + abs_event_count |= INTEL_PMC_TIO_RD_REG(TGPIOECCV31_0); + dt_hi_e = read_art_time() >> 32; + + art_timestamp = ((dt_hi_e != dt_hi_s) && !(dt_lo & 0x80000000)) ? + dt_hi_e : dt_hi_s; + art_timestamp <<= 32; + art_timestamp |= dt_lo; + + rel_event_count = abs_event_count - tio->last_event_count; + if (rel_event_count == 0 || art_timestamp == tio->last_art_timestamp) { + err = -EAGAIN; + goto out; + } + + tio->last_art_timestamp = art_timestamp; + + *system_counterval = convert_art_to_tsc(art_timestamp); + arg->abs_event_count = abs_event_count; + arg->event_id = 0; + arg->event_id |= (flags & GPIO_V2_LINE_FLAG_EDGE_RISING) ? + GPIO_V2_LINE_EVENT_RISING_EDGE : 0; + arg->event_id |= (flags & GPIO_V2_LINE_FLAG_EDGE_FALLING) ? + GPIO_V2_LINE_EVENT_FALLING_EDGE : 0; + +out: + return err; +} + +static int intel_pmc_tio_do_poll(struct gpio_chip *chip, unsigned int offset, + u32 eflags, struct gpioevent_poll_data *data) +{ + struct intel_pmc_tio_chip *tio = gch_to_intel_pmc_tio(chip); + struct intel_pmc_tio_get_time_arg arg = { + .eflags = eflags, .tio = tio }; + struct system_device_crosststamp xtstamp; + unsigned int i, stop; + int err = -EAGAIN; + + mutex_lock(&tio->lock); + + i = tio->systime_index; + stop = tio->systime_valid ? + tio->systime_index : INPUT_SNAPSHOT_COUNT - 1; + do { + err = get_device_system_crosststamp(intel_pmc_tio_get_time, + &arg, + &tio->systime_snapshot[i], + &xtstamp); + if (!err) { + data->timestamp = ktime_to_ns(xtstamp.sys_realtime); + data->id = arg.event_id; + tio->last_event_count = arg.abs_event_count; + } + if (!err || err == -EAGAIN) + break; + i = (i + (INPUT_SNAPSHOT_COUNT - 1)) % INPUT_SNAPSHOT_COUNT; + } while (i != stop); + + mutex_unlock(&tio->lock); + + return err; +} + static int intel_pmc_tio_probe(struct platform_device *pdev) { struct intel_pmc_tio_chip *tio; @@ -111,10 +323,14 @@ static int intel_pmc_tio_probe(struct platform_device *pdev) debugfs_create_regset32("regdump", 0444, tio->root, tio->regset); tio->gch.label = pdev->name; - tio->gch.ngpio = 0; + tio->gch.ngpio = GPIO_COUNT; tio->gch.base = -1; + tio->gch.setup_poll = intel_pmc_tio_setup_poll; + tio->gch.do_poll = intel_pmc_tio_do_poll; platform_set_drvdata(pdev, tio); + mutex_init(&tio->lock); + INIT_DELAYED_WORK(&tio->input_work, intel_pmc_tio_input_work); err = devm_gpiochip_add_data(&pdev->dev, &tio->gch, tio); if (err < 0) @@ -136,6 +352,8 @@ static int intel_pmc_tio_remove(struct platform_device *pdev) if (!tio) return -ENODEV; + intel_pmc_tio_stop_input_work(tio); + mutex_destroy(&tio->lock); debugfs_remove_recursive(tio->root); return 0; -- 2.17.1