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
