diff mbox series

[RFC,v1,04/20] gpio: Add input code to Intel PMC Timed I/O Driver

Message ID 20210824164801.28896-5-lakshmi.sowjanya.d@intel.com
State New
Headers show
Series Review Request: Add support for Intel PMC | expand

Commit Message

D, Lakshmi Sowjanya Aug. 24, 2021, 4:47 p.m. UTC
From: Christopher Hall <christopher.s.hall@intel.com>

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@intel.com>
Signed-off-by: Tamal Saha <tamal.saha@intel.com>
Co-developed-by: Lakshmi Sowjanya D <lakshmi.sowjanya.d@intel.com>
Signed-off-by: Lakshmi Sowjanya D <lakshmi.sowjanya.d@intel.com>
Reviewed-by: Mark Gross <mgross@linux.intel.com>
---
 drivers/gpio/gpio-intel-tio-pmc.c | 220 +++++++++++++++++++++++++++++-
 1 file changed, 219 insertions(+), 1 deletion(-)
diff mbox series

Patch

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;