Add support for PMC Time-Aware GPIO (TGPIO) hardware that is present
on upcoming Intel platforms. The hardware logic is driven by the ART
clock. The current hardware has two GPIO pins. Input interrupts are
not implemented in hardware.

Signed-off-by: Zqiang <[email protected]>
---
 kernel/drivers/ptp/Kconfig     |  11 +
 kernel/drivers/ptp/Makefile    |   3 +
 kernel/drivers/ptp/pmc-tgpio.c | 827 +++++++++++++++++++++++++++++++++
 3 files changed, 841 insertions(+)
 create mode 100644 kernel/drivers/ptp/pmc-tgpio.c

diff --git a/kernel/drivers/ptp/Kconfig b/kernel/drivers/ptp/Kconfig
index cc22ffe..12e1e4b 100644
--- a/kernel/drivers/ptp/Kconfig
+++ b/kernel/drivers/ptp/Kconfig
@@ -6,4 +6,15 @@ config XENO_DRIVERS_PTP
        help
        This driver adds support for PTP clocks as RT devices.
 
+config XENO_DRIVERS_PTP_PMC_TGPIO
+       tristate "Intel PMC Timed GPIO"
+       depends on XENO_DRIVERS_PTP
+       default n
+       help
+       This driver adds support for Intel PMC Timed-Aware GPIO (TGPIO)
+       note: BIOS configuration is required to properly assign the pin.
+       To enable PMC Time-Aware GPIO, configure the following BIOS options:
+       Intel Advanced Menu > PCH IO Configuration > Enabled Timed GPIO0 
[Enable]
+       Intel Advanced Menu > PCH IO Configuration > Enabled Timed GPIO1 
[Enable]
+
 endmenu
diff --git a/kernel/drivers/ptp/Makefile b/kernel/drivers/ptp/Makefile
index bcb2faa..aa1671c 100644
--- a/kernel/drivers/ptp/Makefile
+++ b/kernel/drivers/ptp/Makefile
@@ -1,2 +1,5 @@
 obj-$(CONFIG_XENO_DRIVERS_PTP) += xeno_ptp.o
 xeno_ptp-y := ptp.o
+
+obj-$(CONFIG_XENO_DRIVERS_PTP_PMC_TGPIO) += xeno_pmc-tgpio.o
+xeno_pmc-tgpio-y := pmc-tgpio.o
diff --git a/kernel/drivers/ptp/pmc-tgpio.c b/kernel/drivers/ptp/pmc-tgpio.c
new file mode 100644
index 0000000..837d735
--- /dev/null
+++ b/kernel/drivers/ptp/pmc-tgpio.c
@@ -0,0 +1,827 @@
+/**
+ * I/O handling lifted from drivers/ptp/ptp-intel-pmc-tgpio.c
+ *
+ * Derived from the Linux PMC TGPIO driver
+ * Intel Timed GPIO Controller Driver
+ *
+ * Copyright (C) 2018 Intel Corporation
+ * Author: Christopher Hall <[email protected]>
+ *
+ * RTDM integration by:
+ * Copyright (C) 2022 Zqiang <[email protected]>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#include <linux/acpi.h>
+#include <linux/bitops.h>
+#include <linux/gpio.h>
+#include <linux/debugfs.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <asm/tsc.h>
+#include <linux/delay.h>
+#include "ptp.h"
+
+#define TGPIOCTL                       0x00
+#define TGPIOCOMPV31_0                 0x10
+#define TGPIOCOMPV63_32                        0x14
+#define TGPIOPIV31_0                   0x18
+#define TGPIOPIV63_32                  0x1c
+#define TGPIOTCV31_0                   0x20
+#define TGPIOTCV63_32                  0x24
+#define TGPIOECCV31_0                  0x28
+#define TGPIOECCV63_32                 0x2c
+#define TGPIOEC31_0                    0x30
+#define TGPIOEC63_32                   0x34
+
+/* Control Register */
+#define TGPIOCTL_EN                    BIT(0)
+#define TGPIOCTL_DIR                   BIT(1)
+#define TGPIOCTL_EP                    GENMASK(3, 2)
+#define TGPIOCTL_EP_RISING_EDGE                (0 << 2)
+#define TGPIOCTL_EP_FALLING_EDGE       (1 << 2)
+#define TGPIOCTL_EP_TOGGLE_EDGE                (2 << 2)
+#define TGPIOCTL_PM                    BIT(4)
+
+#define DRIVER_NAME                    "intel-pmc-tgpio"
+#define MAX_PINS_PER_PLAT              (2)
+
+#define PTP_PEROUT_FREQ_ADJ            (1<<3)
+#define PTP_PINDESC_EVTCNTVALID                (1<<0)
+#define PTP_PINDESC_INPUTPOLL          (1<<1)
+
+typedef char *plat_acpi_resource[MAX_PINS_PER_PLAT];
+
+/* Each row represent a platform that supports PMC TGPIO */
+static const plat_acpi_resource acpi_plat_map[] = {
+       { "INTC1021", "INTC1022" },
+       { "INTC1023", "INTC1024" },
+};
+
+#define PLATFORM_COUNT ARRAY_SIZE(acpi_plat_map)
+
+struct intel_pmc_tgpio_t {
+       struct mutex                    init_lock;
+       rtdm_lock_t                     lock;
+       struct rt_ptp_clock             *clock;
+       struct rt_ptp_clock_info            info;
+
+       const plat_acpi_resource        *plat_res;
+
+       struct intel_pmc_tgpio_pin {
+               void __iomem            *base;
+               bool                     busy;
+               ktime_t                  curr_ns;
+               u64                      curr_art;
+               struct dentry           *root;
+               struct debugfs_regset32 *regset;
+               struct platform_device  *pdev;
+       } pin[MAX_PINS_PER_PLAT];
+};
+
+static struct intel_pmc_tgpio_t *intel_pmc_tgpio;
+
+#define to_intel_pmc_tgpio(i) \
+       (container_of((i), struct intel_pmc_tgpio_t, info))
+
+#define ts64_to_ptp_clock_time(x) ((struct rt_ptp_clock_time){.sec = 
(x).tv_sec, \
+                       .nsec = (x).tv_nsec})
+
+#define ptp_clock_time_to_ts64(x) ((struct timespec64){.tv_sec = (x).sec, \
+                                                      .tv_nsec = (x).nsec})
+
+static inline u32 intel_pmc_tgpio_readl
+       (struct intel_pmc_tgpio_t *tgpio, u32 offset, unsigned int index)
+{
+       return readl(tgpio->pin[index].base + offset);
+}
+
+static inline void intel_pmc_tgpio_writel
+       (struct intel_pmc_tgpio_t *tgpio, u32 offset,
+                       unsigned int index, u32 value)
+{
+       writel(value, tgpio->pin[index].base + offset);
+}
+
+#define INTEL_PMC_TGPIO_RD_REG(offset, index)                  \
+       (intel_pmc_tgpio_readl((tgpio), (offset), (index)))
+#define INTEL_PMC_TGPIO_WR_REG(offset, index, value)           \
+       (intel_pmc_tgpio_writel((tgpio), (offset), (index), (value)))
+
+static const struct debugfs_reg32 intel_pmc_tgpio_regs[] = {
+       {
+               .name = "TGPIOCTL",
+               .offset = TGPIOCTL
+       },
+       {
+               .name = "TGPIOCOMPV31_0",
+               .offset = TGPIOCOMPV31_0
+       },
+       {
+               .name = "TGPIOCOMPV63_32",
+               .offset = TGPIOCOMPV63_32
+       },
+       {
+               .name = "TGPIOPIV31_0",
+               .offset = TGPIOPIV31_0
+       },
+       {
+               .name = "TGPIOPIV63_32",
+               .offset = TGPIOPIV63_32
+       },
+       {
+               .name = "TGPIOECCV31_0",
+               .offset = TGPIOECCV31_0
+       },
+       {
+               .name = "TGPIOECCV63_32",
+               .offset = TGPIOECCV63_32
+       },
+       {
+               .name = "TGPIOEC31_0",
+               .offset = TGPIOEC31_0
+       },
+       {
+               .name = "TGPIOEC63_32",
+               .offset = TGPIOEC63_32
+       },
+};
+
+static void intel_pmc_tgpio_pre_config(
+       struct intel_pmc_tgpio_t *tgpio, unsigned int index, unsigned int ctrl,
+       unsigned int ctrl_new, int *enable_toggle)
+{
+       if (ctrl_new != ctrl) {
+               INTEL_PMC_TGPIO_WR_REG(TGPIOCTL, index, ctrl);
+               INTEL_PMC_TGPIO_WR_REG(TGPIOCTL, index, ctrl_new);
+               *enable_toggle = *enable_toggle > 0 ? 2 : -1;
+       }
+}
+
+static void intel_pmc_tgpio_post_config(
+       struct intel_pmc_tgpio_t *tgpio, unsigned int index, unsigned int ctrl,
+       int enable_toggle)
+{
+       if (enable_toggle > 0)
+               ctrl |= TGPIOCTL_EN;
+       if (enable_toggle > 1 || enable_toggle < -1)
+               INTEL_PMC_TGPIO_WR_REG(TGPIOCTL, index, ctrl);
+}
+
+static int intel_pmc_tgpio_config_input
+       (struct intel_pmc_tgpio_t *tgpio, struct rt_ptp_extts_request *extts,
+               int on)
+{
+       u32 ctrl, ctrl_new;
+       int enable_toggle;
+
+       /*
+        * enable_toggle meaning:
+        *
+        *      -2: Change from enabled state to disabled state
+        *      -1: Keep disabled state
+        *       1: Keep enabled state
+        *       2: Change from disabled state to enabled state
+        */
+
+       ctrl = INTEL_PMC_TGPIO_RD_REG(TGPIOCTL, extts->index);
+       enable_toggle = ctrl & TGPIOCTL_EN ? 1 : -1;
+       ctrl &= ~TGPIOCTL_EN;
+       ctrl_new = ctrl;
+
+       if (on) {
+               int rising_cap, falling_cap;
+
+               ctrl_new &= ~TGPIOCTL_EP;
+
+               rising_cap = extts->flags & RT_PTP_RISING_EDGE;
+               falling_cap = extts->flags & RT_PTP_FALLING_EDGE;
+
+               /* By default capture rising and falling edges */
+               if (rising_cap && !falling_cap)
+                       ctrl_new |= TGPIOCTL_EP_RISING_EDGE;
+               else if (!rising_cap && falling_cap)
+                       ctrl_new |= TGPIOCTL_EP_FALLING_EDGE;
+               else
+                       ctrl_new |= TGPIOCTL_EP_TOGGLE_EDGE;
+
+               ctrl_new |= TGPIOCTL_DIR;
+               enable_toggle = enable_toggle < 0 ? 2 : 1;
+       } else {
+               enable_toggle = enable_toggle > 0 ? -2 : -1;
+       }
+
+       intel_pmc_tgpio_pre_config
+               (tgpio, extts->index, ctrl, ctrl_new, &enable_toggle);
+       intel_pmc_tgpio_post_config
+               (tgpio, extts->index, ctrl_new, enable_toggle);
+
+       return 0;
+}
+
+#define MAX_ATOMIC_PERIOD              ((u32)-1/*cycles*/)
+#define MIN_OUTPUT_PERIOD              (3/*cycles*/)
+#define FREQ_CHANGE_BLACKOUT_THRESH    ((NSEC_PER_SEC/1000)*4/*ms*/)
+#define FREQ_CHANGE_SLEEP_MIN          (20/*us*/)
+#define FREQ_CHANGE_SLEEP_WINDOW       (FREQ_CHANGE_SLEEP_MIN*5)
+
+static int _intel_pmc_tgpio_config_output(
+       struct intel_pmc_tgpio_t *tgpio, unsigned int index,
+       struct timespec64 new_start_ns, struct timespec64 new_period_ns,
+       unsigned int flags, int on)
+{
+       u32     ctrl, ctrl_new;
+       u64     new_period;
+       int     enable_toggle;
+       /*
+        * enable_toggle meaning:
+        *
+        *      -2: Change from enabled state to disabled state
+        *      -1: Keep disabled state
+        *       1: Keep enabled state
+        *       2: Change from disabled state to enabled state
+        */
+
+       ctrl = INTEL_PMC_TGPIO_RD_REG(TGPIOCTL, index);
+       enable_toggle = ctrl & TGPIOCTL_EN ? 1 : -1;
+       ctrl &= ~TGPIOCTL_EN;
+       ctrl_new = ctrl;
+
+       /* Frequency adjustment flag is only valid if we're already running */
+       if (flags & PTP_PEROUT_FREQ_ADJ &&
+                       (enable_toggle != 1 || !(ctrl & TGPIOCTL_PM)))
+               return -EINVAL;
+
+       new_period = convert_tsc_ns_to_art_duration(&new_period_ns);
+
+       /* Don't use a period less than the minimum */
+       if (!(flags & RT_PTP_PEROUT_ONE_SHOT) && new_period < MIN_OUTPUT_PERIOD)
+               return -EINVAL;
+
+       /*
+        * In the unlikely case of an adjustment from a small period (< 4ms)
+        * to a large period (>>4 sec) change to a "transitory" period first
+        */
+       if (flags & RT_PTP_PEROUT_FREQ_ADJ &&
+               tgpio->pin[index].curr_ns < FREQ_CHANGE_BLACKOUT_THRESH &&
+               new_period >> 32) {
+               struct timespec64 transit_period_ns;
+               int err;
+
+               transit_period_ns =
+                       convert_art_to_tsc_ns_duration(MAX_ATOMIC_PERIOD);
+               /* If transit period is too small we recurse infinitely */
+               if (timespec64_to_ktime(transit_period_ns) <
+                                               FREQ_CHANGE_BLACKOUT_THRESH)
+                       return -EINVAL;
+               err = _intel_pmc_tgpio_config_output
+                       (tgpio, index, (struct timespec64){0, 0},
+                       transit_period_ns, flags, on);
+               if (err)
+                       return err;
+       }
+
+       /* Don't make a change to/from 64 bit period across an output edge */
+       if (flags & RT_PTP_PEROUT_FREQ_ADJ &&
+               tgpio->pin[index].curr_ns >= FREQ_CHANGE_BLACKOUT_THRESH &&
+               (new_period >> 32 || tgpio->pin[index].curr_art >> 32)) {
+               u64                     next_edge;
+               u32                     next_edge_lo1, next_edge_lo2;
+               struct timespec64       next_edge_tsc;
+               struct timespec64       tsc_now, tsc_tmp;
+               ktime_t                 delta;
+
+               /* Calculate time delta until next edge */
+               tsc_tmp = get_tsc_ns_now(NULL);
+               next_edge_lo2 =
+                       INTEL_PMC_TGPIO_RD_REG(TGPIOCOMPV31_0, index);
+               do {
+                       tsc_now = tsc_tmp;
+                       next_edge_lo1 = next_edge_lo2;
+                       next_edge =
+                               INTEL_PMC_TGPIO_RD_REG(TGPIOCOMPV63_32, index);
+                       tsc_tmp = get_tsc_ns_now(NULL);
+                       next_edge_lo2 =
+                               INTEL_PMC_TGPIO_RD_REG(TGPIOCOMPV31_0, index);
+               } while (next_edge_lo1 != next_edge_lo2);
+               next_edge <<= 32;
+               next_edge |= next_edge_lo1;
+
+               next_edge_tsc = convert_art_to_tsc_ns(next_edge);
+               delta = timespec64_to_ktime
+                               (timespec64_sub(next_edge_tsc, tsc_now));
+
+               /* If there's a chance our write will get stepped on, wait */
+               if (delta < FREQ_CHANGE_BLACKOUT_THRESH) {
+                       unsigned long   min, max;
+                       min = delta;
+                       min /= 1000;
+                       max = min + FREQ_CHANGE_SLEEP_WINDOW;
+                       min = min < FREQ_CHANGE_SLEEP_MIN ?
+                               FREQ_CHANGE_SLEEP_MIN : min;
+               }
+       }
+
+       if (on || flags & RT_PTP_PEROUT_ONE_SHOT) {
+               /* We only use toggle edge */
+               ctrl_new &= ~TGPIOCTL_EP;
+               ctrl_new |= TGPIOCTL_EP_TOGGLE_EDGE;
+               ctrl_new &= ~TGPIOCTL_DIR;
+
+               if (flags & RT_PTP_PEROUT_ONE_SHOT)
+                       ctrl_new &= ~TGPIOCTL_PM;
+               else
+                       ctrl_new |= TGPIOCTL_PM;
+
+               enable_toggle = enable_toggle < 0 ?  2 :  1;
+       } else {
+               enable_toggle = enable_toggle > 0 ? -2 : -1;
+       }
+
+       intel_pmc_tgpio_pre_config(tgpio, index, ctrl, ctrl_new, 
&enable_toggle);
+
+       if (enable_toggle >= 0 && !(flags & RT_PTP_PEROUT_FREQ_ADJ)) {
+               u64 new_start = convert_tsc_ns_to_art(&new_start_ns);
+
+               INTEL_PMC_TGPIO_WR_REG
+                       (TGPIOCOMPV63_32, index, new_start >> 32);
+               INTEL_PMC_TGPIO_WR_REG
+                       (TGPIOCOMPV31_0, index, new_start & 0xFFFFFFFF);
+       }
+
+       if (enable_toggle >= 0 && !(flags & RT_PTP_PEROUT_ONE_SHOT)) {
+               INTEL_PMC_TGPIO_WR_REG
+                       (TGPIOPIV31_0, index, new_period & 0xFFFFFFFF);
+               INTEL_PMC_TGPIO_WR_REG
+                       (TGPIOPIV63_32, index, new_period >> 32);
+
+               tgpio->pin[index].curr_ns = timespec64_to_ktime(new_period_ns);
+               tgpio->pin[index].curr_art = new_period;
+       }
+
+       intel_pmc_tgpio_post_config(tgpio, index, ctrl_new, enable_toggle);
+       return 0;
+}
+
+static int intel_pmc_tgpio_config_output
+               (struct intel_pmc_tgpio_t *tgpio,
+                       struct rt_ptp_perout_request *perout, int on)
+{
+       struct timespec64       new_start_ns;
+       struct timespec64       new_period_ns;
+
+       if (on || perout->flags & RT_PTP_PEROUT_ONE_SHOT) {
+               new_start_ns = ptp_clock_time_to_ts64(perout->start);
+               new_period_ns = ptp_clock_time_to_ts64(perout->period);
+               new_period_ns = ktime_to_timespec64
+                       (ktime_divns(timespec64_to_ktime(new_period_ns), 2));
+       }
+
+       return _intel_pmc_tgpio_config_output
+                       (tgpio, perout->index, new_start_ns, new_period_ns,
+                               perout->flags, on);
+}
+
+static int intel_pmc_tgpio_enable
+       (struct rt_ptp_clock_info *info, struct rt_ptp_clock_request *req, int 
on)
+{
+       struct intel_pmc_tgpio_t        *tgpio = to_intel_pmc_tgpio(info);
+       int                              ret = -EOPNOTSUPP;
+       unsigned int                     index;
+       rtdm_lockctx_t                  lock_ctx;
+
+       switch (req->type) {
+       case RT_PTP_CLK_REQ_EXTTS:
+               index = req->extts.index;
+               break;
+       case RT_PTP_CLK_REQ_PEROUT:
+               index = req->perout.index;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       rtdm_lock_get_irqsave(&intel_pmc_tgpio->lock, lock_ctx);
+       if (intel_pmc_tgpio->pin[index].base != NULL) {
+               switch (req->type) {
+               case RT_PTP_CLK_REQ_EXTTS:
+                       ret = intel_pmc_tgpio_config_input
+                                       (tgpio, &req->extts, on);
+                       break;
+               case RT_PTP_CLK_REQ_PEROUT:
+                       ret = intel_pmc_tgpio_config_output
+                                       (tgpio, &req->perout, on);
+                       break;
+               default:
+                       ret = -EINVAL;
+                       break;
+               }
+       } else {
+               ret = -ENODEV;
+       }
+       rtdm_lock_put_irqrestore(&intel_pmc_tgpio->lock, lock_ctx);
+       return ret;
+}
+
+
+static int intel_pmc_tgpio_get_time_fn(ktime_t *device_time,
+               struct system_counterval_t *system_counter, void *_tgpio)
+{
+       struct timespec64 now_ns;
+
+       now_ns = get_tsc_ns_now(system_counter);
+       *device_time = timespec64_to_ktime(now_ns);
+       return 0;
+}
+
+static int intel_pmc_tgpio_getcrosststamp
+       (struct rt_ptp_clock_info *info, struct system_device_crosststamp *cts)
+{
+       struct intel_pmc_tgpio_t        *tgpio = to_intel_pmc_tgpio(info);
+
+       return get_device_system_crosststamp
+                       (intel_pmc_tgpio_get_time_fn, tgpio, NULL, cts);
+}
+
+static int intel_pmc_tgpio_counttstamp
+       (struct rt_ptp_clock_info *info, struct rt_ptp_event_count_tstamp 
*count)
+{
+       struct intel_pmc_tgpio_t        *tgpio = to_intel_pmc_tgpio(info);
+       u32                              dt_hi_s;
+       u32                              dt_hi_e;
+       u32                              dt_lo;
+       struct timespec64                dt_ts;
+       struct timespec64                tsc_now;
+       static u32                       dt_lo_prev[2] = { 0, 0 };
+       static struct timespec64         dt_ts_prev[2] = {{ 0, 0 }};
+       static unsigned long long        prev_count[2] = { 0, 0 };
+       rtdm_lockctx_t                   lock_ctx;
+
+       rtdm_lock_get_irqsave(&intel_pmc_tgpio->lock, lock_ctx);
+       tsc_now = get_tsc_ns_now(NULL);
+       dt_hi_s = convert_tsc_ns_to_art(&tsc_now) >> 32;
+
+       /* Reading lower 32-bit word of Time Capture Value (TCV) loads */
+       /* the event time and event count capture */
+       dt_lo = INTEL_PMC_TGPIO_RD_REG(TGPIOTCV31_0, count->index);
+       count->event_count =
+               INTEL_PMC_TGPIO_RD_REG(TGPIOECCV63_32, count->index);
+       count->event_count <<= 32;
+       count->event_count |=
+               INTEL_PMC_TGPIO_RD_REG(TGPIOECCV31_0, count->index);
+       dt_hi_e = INTEL_PMC_TGPIO_RD_REG(TGPIOTCV63_32, count->index);
+
+       if (dt_hi_e != dt_hi_s && dt_lo >> 31)
+               dt_ts = convert_art_to_tsc_ns(((u64)dt_hi_s << 32) | dt_lo);
+       else
+               dt_ts = convert_art_to_tsc_ns(((u64)dt_hi_e << 32) | dt_lo);
+
+       /* Return previous device time if the event_count
+        * isn't incremented with TCV value
+        */
+       if (count->event_count == prev_count[count->index] ||
+                       dt_lo_prev[count->index] == dt_lo) {
+               count->event_count = prev_count[count->index];
+               dt_lo = dt_lo_prev[count->index];
+               dt_ts = dt_ts_prev[count->index];
+       }
+
+       count->device_time = ts64_to_ptp_clock_time(dt_ts);
+       prev_count[count->index] = count->event_count;
+       dt_lo_prev[count->index] = dt_lo;
+       dt_ts_prev[count->index] = dt_ts;
+
+       rtdm_lock_put_irqrestore(&intel_pmc_tgpio->lock, lock_ctx);
+       return 0;
+}
+
+static int intel_pmc_tgpio_verify(
+       struct rt_ptp_clock_info *ptp, unsigned int pin,
+       enum rt_ptp_pin_function func, unsigned int chan)
+{
+       int ret = 0;
+
+       if (func == RT_PTP_PF_PHYSYNC)
+               return -1;
+
+       /* pin/channel is fixed for TGPIO hardware */
+       if (ptp->pin_config[pin].chan != chan)
+               return -1;
+
+       /* The pin has been removed, but the PTP interface is still here */
+       if (intel_pmc_tgpio->pin[pin].base == NULL)
+               ret = -1;
+       return 0;
+}
+
+static struct rt_ptp_pin_desc intel_pmc_tgpio_pin_config[MAX_PINS_PER_PLAT];
+
+static const struct rt_ptp_clock_info intel_pmc_tgpio_info = {
+       .owner          = THIS_MODULE,
+       .name           = "Intel PMC TGPIO",
+       .max_adj        = 0,
+       .n_pins         = 0,
+       .n_ext_ts       = 0,
+       .n_per_out      = 0,
+       .pin_config     = intel_pmc_tgpio_pin_config,
+       .enable         = intel_pmc_tgpio_enable,
+       .getcrosststamp = intel_pmc_tgpio_getcrosststamp,
+       .counttstamp    = intel_pmc_tgpio_counttstamp,
+       .verify         = intel_pmc_tgpio_verify,
+};
+
+static int ptp_device_register(struct platform_device *pdev)
+{
+       intel_pmc_tgpio->clock = rt_ptp_clock_register(&intel_pmc_tgpio->info,
+                                                       "pmc-tgpio");
+       if (IS_ERR(intel_pmc_tgpio->clock))
+               return PTR_ERR(intel_pmc_tgpio->clock);
+
+       return 0;
+}
+
+static const plat_acpi_resource *find_plat_acpi_resource
+       (struct platform_device *pdev, int *n_pins)
+{
+       unsigned int                    index;
+       unsigned int                    pin_index;
+       bool                            found = false;
+       const plat_acpi_resource        *ret;
+
+       for (pin_index = 0; pin_index < MAX_PINS_PER_PLAT; ++pin_index) {
+               for (index = 0; index < PLATFORM_COUNT; ++index) {
+                       char *res_name = acpi_plat_map[index][pin_index];
+
+                       if (res_name == NULL)
+                               continue;
+                       if (strncmp(pdev->name, res_name,
+                                       strlen(res_name)) == 0) {
+                               found = true;
+                               break;
+                       }
+               }
+               if (found)
+                       break;
+       }
+       ret = acpi_plat_map + index;
+       if (pin_index == MAX_PINS_PER_PLAT)
+               return ERR_PTR(-ENODEV);
+
+       /* Count the number of pins */
+       *n_pins = 0;
+       for (pin_index = 0; pin_index < MAX_PINS_PER_PLAT; ++pin_index) {
+               if ((*ret)[pin_index] == NULL)
+                       break;
+               *n_pins += 1;
+       }
+       return ret;
+}
+
+static int intel_pmc_tgpio_probe(struct platform_device *pdev)
+{
+       struct intel_pmc_tgpio_t        *tgpio = intel_pmc_tgpio;
+       unsigned int                    pin_index;
+       unsigned int                    pin_count;
+       struct intel_pmc_tgpio_pin      *tgpio_pin;
+       struct rt_ptp_pin_desc          *tgpio_pin_desc;
+       struct resource                 *res;
+       char                            pinname_tmpl[] = DRIVER_NAME"-pin//";
+       int                             ret = 0;
+
+       mutex_lock(&tgpio->init_lock);
+
+       /* Find and cache the list of ACPI resources for this platform */
+       if (tgpio->plat_res == NULL) {
+               tgpio->plat_res = find_plat_acpi_resource
+                       (pdev, &tgpio->info.n_pins);
+               tgpio->info.n_ext_ts = tgpio->info.n_per_out
+                       = tgpio->info.n_pins;
+       }
+       if (IS_ERR(tgpio->plat_res)) {
+               ret = PTR_ERR(tgpio->plat_res);
+               goto unlock;
+       }
+
+       /* Find an available pin */
+       for (pin_index = 0; pin_index < MAX_PINS_PER_PLAT; ++pin_index) {
+               if (tgpio->pin[pin_index].base == NULL)
+                       break;
+       }
+       if (pin_index == MAX_PINS_PER_PLAT) {
+               ret = -ENODEV;
+               goto unlock;
+       }
+
+       tgpio_pin = tgpio->pin + pin_index;
+       tgpio_pin_desc = tgpio->info.pin_config + pin_index;
+       tgpio_pin->pdev = pdev;
+       platform_set_drvdata(pdev, tgpio);
+       pin_count = pin_index + 1;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       tgpio_pin->base = devm_ioremap_resource(&pdev->dev, res);
+       if (!tgpio_pin->base) {
+               ret = -ENOMEM;
+               goto unlock;
+       }
+
+       /* Find the pin corresponding to the ACPI ID */
+       for (pin_index = 0; pin_index < MAX_PINS_PER_PLAT; ++pin_index) {
+               const char *cmpr = (const char *)(*tgpio->plat_res)[pin_index];
+
+               if (strncmp(cmpr, pdev->name, strlen(cmpr)) == 0)
+                       break;
+       }
+       if (pin_index == MAX_PINS_PER_PLAT) {
+               ret = -ENODEV;
+               goto unlock;
+       }
+
+       tgpio_pin->regset = devm_kzalloc(&pdev->dev,
+                                       sizeof(*tgpio_pin->regset), GFP_KERNEL);
+       if (!tgpio_pin->regset) {
+               ret = -ENOMEM;
+               goto unlock;
+       }
+
+       tgpio_pin->regset->regs = intel_pmc_tgpio_regs;
+       tgpio_pin->regset->nregs = ARRAY_SIZE(intel_pmc_tgpio_regs);
+       tgpio_pin->regset->base = tgpio_pin->base;
+
+       /* Pin names are 1 indexed (+1) because hardware works like that */
+       snprintf(strchr(pinname_tmpl, '/'), 3, "%02u", pin_index+1);
+       tgpio_pin->root = debugfs_create_dir(pinname_tmpl, NULL);
+       debugfs_create_regset32("regdump", 0444, tgpio_pin->root,
+                                                       tgpio_pin->regset);
+       strncpy(tgpio_pin_desc->name, pinname_tmpl,
+                               sizeof(tgpio_pin_desc->name));
+
+       if (tgpio->info.n_pins == pin_count)
+               ret = ptp_device_register(pdev);
+
+unlock:
+       mutex_unlock(&tgpio->init_lock);
+       return ret;
+}
+
+static int intel_pmc_tgpio_remove(struct platform_device *pdev)
+{
+       struct intel_pmc_tgpio_t        *tgpio = platform_get_drvdata(pdev);
+       unsigned int                     pin_index;
+       struct intel_pmc_tgpio_pin      *tgpio_pin;
+       int ret = 0;
+
+       mutex_lock(&tgpio->init_lock);
+
+       for (pin_index = 0; pin_index < MAX_PINS_PER_PLAT; ++pin_index) {
+               tgpio_pin = tgpio->pin + pin_index;
+               if (tgpio_pin->pdev == pdev || tgpio_pin->pdev == NULL)
+                       break;
+       }
+       if (pin_index == MAX_PINS_PER_PLAT || tgpio_pin->pdev == NULL) {
+               ret = -ENODEV;
+               goto unlock;
+       }
+
+       tgpio_pin->base = NULL;
+       debugfs_remove_recursive(tgpio_pin->root);
+unlock:
+       mutex_unlock(&tgpio->init_lock);
+       return ret;
+}
+
+static const struct acpi_device_id intel_pmc_acpi_match[] = {
+       { "INTC1021", 0 }, /* EHL */
+       { "INTC1022", 0 }, /* EHL */
+       { "INTC1023", 0 }, /* TGL */
+       { "INTC1024", 0 }, /* TGL */
+       {  },
+};
+
+MODULE_ALIAS("acpi*:INTC1021:*");
+MODULE_ALIAS("acpi*:INTC1022:*");
+MODULE_ALIAS("acpi*:INTC1023:*");
+MODULE_ALIAS("acpi*:INTC1024:*");
+
+static struct platform_driver intel_pmc_tgpio_driver = {
+       .probe          = intel_pmc_tgpio_probe,
+       .remove         = intel_pmc_tgpio_remove,
+       .driver         = {
+               .name   = DRIVER_NAME,
+       },
+};
+
+static struct acpi_device_id *construct_acpi_match_table(void)
+{
+       int                      acpi_id_count  = 0;
+       int                      plat_iter;
+       int                      pin_iter;
+       struct acpi_device_id   *table_iter;
+       struct acpi_device_id   *ret;
+
+       /* Walk through all ACPI IDs constructing driver table */
+       for (plat_iter = 0; plat_iter < PLATFORM_COUNT; ++plat_iter) {
+               for (pin_iter = 0; pin_iter < MAX_PINS_PER_PLAT; ++pin_iter) {
+                       if (acpi_plat_map[plat_iter][pin_iter])
+                               ++acpi_id_count;
+                       else
+                               break;
+               }
+       }
+       /* Leave NULL entry at the end */
+       ret = kzalloc(sizeof(*ret)*(acpi_id_count+1), GFP_KERNEL);
+       if (ret == NULL)
+               ret = ERR_PTR(-ENOMEM);
+
+       table_iter = ret;
+       for (plat_iter = 0; plat_iter < PLATFORM_COUNT; ++plat_iter) {
+               for (pin_iter = 0; pin_iter < MAX_PINS_PER_PLAT; ++pin_iter) {
+                       if (acpi_plat_map[plat_iter][pin_iter]) {
+                               strncpy(table_iter->id,
+                                       acpi_plat_map[plat_iter][pin_iter],
+                                       sizeof(table_iter->id));
+                               ++table_iter;
+                       } else {
+                               break;
+                       }
+               }
+       }
+       return ret;
+}
+
+static int intel_pmc_tgpio_init(void)
+{
+       unsigned int    iter;
+       int             ret;
+
+       if (!boot_cpu_has(X86_FEATURE_TSC_KNOWN_FREQ))
+               return -ENXIO;
+
+       intel_pmc_tgpio_driver.driver.acpi_match_table =
+               ACPI_PTR(construct_acpi_match_table());
+       if (IS_ERR(intel_pmc_tgpio_driver.driver.acpi_match_table)) {
+               ret = PTR_ERR(intel_pmc_tgpio_driver.driver.acpi_match_table);
+               goto alloc_acpi_table;
+       }
+
+       intel_pmc_tgpio =
+               kmalloc(sizeof(struct intel_pmc_tgpio_t), GFP_KERNEL);
+       if (intel_pmc_tgpio == NULL) {
+               ret = -ENOMEM;
+               goto alloc_tgpio;
+       }
+
+       rtdm_lock_init(&intel_pmc_tgpio->lock);
+       mutex_init(&intel_pmc_tgpio->init_lock);
+       intel_pmc_tgpio->info = intel_pmc_tgpio_info;
+       intel_pmc_tgpio->plat_res = NULL;
+       for (iter = 0; iter < MAX_PINS_PER_PLAT; ++iter) {
+               intel_pmc_tgpio->info.pin_config[iter].name[0] = '\0';
+               intel_pmc_tgpio->info.pin_config[iter].index = iter;
+               intel_pmc_tgpio->info.pin_config[iter].chan = iter;
+               intel_pmc_tgpio->info.pin_config[iter].func = RT_PTP_PF_NONE;
+               intel_pmc_tgpio->info.pin_config[iter].flags =
+                       RT_PTP_PINDESC_INPUTPOLL | RT_PTP_PINDESC_EVTCNTVALID;
+               intel_pmc_tgpio->pin[iter].busy = false;
+               intel_pmc_tgpio->pin[iter].root = NULL;
+               intel_pmc_tgpio->pin[iter].base = NULL;
+       }
+
+       ret = platform_driver_register(&intel_pmc_tgpio_driver);
+       if (ret)
+               goto driver_register;
+
+       return 0;
+driver_register:
+       kfree(intel_pmc_tgpio);
+alloc_tgpio:
+       kfree(intel_pmc_tgpio_driver.driver.acpi_match_table);
+alloc_acpi_table:
+       return ret;
+}
+
+static void intel_pmc_tgpio_exit(void)
+{
+       rt_ptp_clock_unregister(intel_pmc_tgpio->clock);
+       platform_driver_unregister(&intel_pmc_tgpio_driver);
+       kfree(intel_pmc_tgpio);
+       kfree(intel_pmc_tgpio_driver.driver.acpi_match_table);
+}
+
+module_init(intel_pmc_tgpio_init);
+module_exit(intel_pmc_tgpio_exit);
-- 
2.25.1


Reply via email to