diff --git a/drivers/Makefile b/drivers/Makefile index a65546b6..880248be 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -14,6 +14,7 @@ obj-m += i2c/busses/ obj-m += mfd/ obj-m += misc/ obj-m += net/ +obj-m += nvpps/ obj-m += pci/ obj-m += pinctrl/ obj-m += platform/tegra/ diff --git a/drivers/nvpps/Makefile b/drivers/nvpps/Makefile new file mode 100644 index 00000000..383b75d9 --- /dev/null +++ b/drivers/nvpps/Makefile @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: GPL-2.0-only + +# +# NVPPS driver and PTP Framework. +# + +obj-m += nvpps.o +nvpps-y += ../platform/tegra/ptp-notifier.o +nvpps-y += nvpps_main.o diff --git a/drivers/nvpps/nvpps_main.c b/drivers/nvpps/nvpps_main.c new file mode 100644 index 00000000..04c8b45e --- /dev/null +++ b/drivers/nvpps/nvpps_main.c @@ -0,0 +1,1214 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +/* the following contrl flags are for + * debugging pirpose only + */ +/* #define NVPPS_ARM_COUNTER_PROFILING */ +/* #define NVPPS_EQOS_REG_PROFILING */ + + +#define MAX_NVPPS_SOURCES 1 +#define NVPPS_DEF_MODE NVPPS_MODE_GPIO + +/* statics */ +static struct class *s_nvpps_class; +static dev_t s_nvpps_devt; +static DEFINE_MUTEX(s_nvpps_lock); +static DEFINE_IDR(s_nvpps_idr); + +static char *interface_name = "eth0"; + + +/* platform device instance data */ +struct nvpps_device_data { + struct platform_device *pdev; + struct cdev cdev; + struct device *dev; + unsigned int id; + unsigned int gpio_pin; + int irq; + bool irq_registered; + bool use_gpio_int_timesatmp; + + bool pps_event_id_valid; + unsigned int pps_event_id; + u32 actual_evt_mode; + u64 tsc; + u64 phc; + u64 secondary_phc; + u64 irq_latency; + u64 tsc_res_ns; + raw_spinlock_t lock; + struct mutex ts_lock; + + u32 evt_mode; + u32 tsc_mode; + + struct timer_list timer; + struct timer_list tsc_timer; + + volatile bool timer_inited; + + wait_queue_head_t pps_event_queue; + struct fasync_struct *pps_event_async_queue; + struct tegra_gte_ev_desc *gte_ev_desc; + + bool memmap_phc_regs; + char *iface_nm; + char *sec_iface_nm; + void __iomem *mac_base_addr; + u32 sts_offset; + u32 stns_offset; + void __iomem *tsc_reg_map_base; + bool platform_is_orin; + u32 tsc_ptp_src; + bool only_timer_mode; + bool pri_ptp_failed; + bool sec_ptp_failed; +}; + + +/* file instance data */ +struct nvpps_file_data { + struct nvpps_device_data *pdev_data; + unsigned int pps_event_id_rd; +}; + + +/* MAC Base addrs */ +#define T194_EQOS_BASE_ADDR 0x2490000 +#define T234_EQOS_BASE_ADDR 0x2310000 +#define EQOS_STSR_OFFSET 0xb08 +#define EQOS_STNSR_OFFSET 0xb0c +#define T234_MGBE0_BASE_ADDR 0x6810000 +#define T234_MGBE1_BASE_ADDR 0x6910000 +#define T234_MGBE2_BASE_ADDR 0x6a10000 +#define T234_MGBE3_BASE_ADDR 0x6b10000 +#define MGBE_STSR_OFFSET 0xd08 +#define MGBE_STNSR_OFFSET 0xd0c + +#define TSC_CAPTURE_CONFIGURATION_PTX_0 0xc6a015c +#define TSC_LOCKING_CONTROL_0 0xc6a01ec +#define TSC_LOCKING_STATUS_0 0xc6a01f0 + +#define TSC_MAPPED_RANGE 0x100 + +/* Below are the tsc control and status register offset from + * ioremapped virtual base region stored in tsc_reg_map_base. + */ +#define TSC_LOCK_CTRL_REG_OFF 0x90 +#define TSC_LOCK_STAT_REG_OFF 0x94 + +#define SRC_SELECT_BIT_OFFSET 8 +#define SRC_SELECT_BITS 0xff + +#define TSC_PTP_SRC_EQOS 0 +#define TSC_PTP_SRC_MGBE0 1 +#define TSC_PTP_SRC_MGBE1 2 +#define TSC_PTP_SRC_MGBE2 3 +#define TSC_PTP_SRC_MGBE3 4 + +#define TSC_LOCKED_STATUS_BIT_OFFSET 1 +#define TSC_ALIGNED_STATUS_BIT_OFFSET 0 + +#define TSC_LOCK_CTRL_ALIGN_BIT_OFFSET 0 + +#define TSC_POLL_TIMER 1000 +#define BASE_ADDRESS pdev_data->mac_base_addr +#define MAC_STNSR_TSSS_LPOS 0 +#define MAC_STNSR_TSSS_HPOS 30 + +#define GET_VALUE(data, lbit, hbit) ((data >> lbit) & (~(~0<<(hbit-lbit+1)))) +#define MAC_STNSR_OFFSET (BASE_ADDRESS + pdev_data->stns_offset) +#define MAC_STNSR_RD(data) do {\ + (data) = ioread32(MAC_STNSR_OFFSET);\ +} while (0) +#define MAC_STSR_OFFSET (BASE_ADDRESS + pdev_data->sts_offset) +#define MAC_STSR_RD(data) do {\ + (data) = ioread32(MAC_STSR_OFFSET);\ +} while (0) + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0) +static inline u64 __arch_counter_get_cntvct(void) +{ + u64 cval; + + asm volatile("mrs %0, cntvct_el0" : "=r" (cval)); + + return cval; +} +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0) */ + +/* + * Get PTP time + * Clients may call this API whenever PTP time is needed. + * If PTP time source is not registered, returns -EINVAL + * + * This API is available irrespective of nvpps dt availablity + * When nvpps dt node is not present, interface name will + * default to "eth0". + */ +int nvpps_get_ptp_ts(void *ts) +{ + //TODO : should we support this API with memmapped method + return tegra_get_hwtime(interface_name, ts, PTP_HWTIME); +} +EXPORT_SYMBOL(nvpps_get_ptp_ts); + +static inline u64 get_systime(struct nvpps_device_data *pdev_data, u64 *tsc) +{ + u64 ns1, ns2, ns; + u32 varmac_stnsr1, varmac_stnsr2; + u32 varmac_stsr; + + /* read the PHC */ + MAC_STNSR_RD(varmac_stnsr1); + MAC_STSR_RD(varmac_stsr); + /* read the TSC */ + *tsc = __arch_counter_get_cntvct(); + + /* read the nsec part of the PHC one more time */ + MAC_STNSR_RD(varmac_stnsr2); + + ns1 = GET_VALUE(varmac_stnsr1, MAC_STNSR_TSSS_LPOS, MAC_STNSR_TSSS_HPOS); + ns2 = GET_VALUE(varmac_stnsr2, MAC_STNSR_TSSS_LPOS, MAC_STNSR_TSSS_HPOS); + + /* if ns1 is greater than ns2, it means nsec counter rollover + * happened. In that case read the updated sec counter again + */ + if (ns1 > ns2) { + /* let's read the TSC again */ + *tsc = __arch_counter_get_cntvct(); + /* read the second portion of the PHC */ + MAC_STSR_RD(varmac_stsr); + /* convert sec/high time value to nanosecond */ + ns = ns2 + (varmac_stsr * 1000000000ull); + } else { + /* convert sec/high time value to nanosecond */ + ns = ns1 + (varmac_stsr * 1000000000ull); + } + + return ns; +} + + + +/* + * Report the PPS event + */ +static void nvpps_get_ts(struct nvpps_device_data *pdev_data, bool in_isr) +{ + u64 tsc = 0; + u64 irq_tsc = 0; + u64 phc = 0; + u64 secondary_phc = 0; + u64 irq_latency = 0; + unsigned long flags; + struct ptp_tsc_data ptp_tsc_ts = {0}, sec_ptp_tsc_ts = {0}; + + if (in_isr) { + /* initialize irq_tsc to the current TSC just in case the + * gpio_timestamp_read call failed so the irq_tsc can be + * closer to when the interrupt actually occured + */ + irq_tsc = __arch_counter_get_cntvct(); + if (pdev_data->use_gpio_int_timesatmp) { + int err; + int gte_event_found = 0; + int safety = 33; /* GTE driver fifo depth is 32, plus one for margin */ + struct tegra_gte_ev_detail hts; + + /* 1PPS TSC timestamp is isochronous in nature + * we only need the last event + */ + do { + err = tegra_gte_retrieve_event(pdev_data->gte_ev_desc, &hts); + if (!err) { + irq_tsc = hts.ts_raw; + gte_event_found = 1; + } + /* decrement the count so we don't risk looping + * here forever + */ + safety--; + } while (!err && (safety >= 0)); + if (gte_event_found == 0) { + dev_err(pdev_data->dev, "failed to read timestamp data err(%d)\n", err); + } + if (safety < 0) { + dev_err(pdev_data->dev, "tegra_gte_retrieve_event succeed beyond its fifo size err(%d)!)\n", err); + } + } + } + + /* get the PTP timestamp */ + if (pdev_data->memmap_phc_regs) { + /* get both the phc(using memmap reg) and tsc */ + phc = get_systime(pdev_data, &tsc); + /*TODO : support fetching ptp offset using memmap method */ + } else { + /* get PTP_TSC concurrent timestamp(using ptp notifier) from MAC driver */ + if (tegra_get_hwtime(pdev_data->iface_nm, &ptp_tsc_ts, PTP_TSC_HWTIME)) { + /* check flag to print ptp failure msg */ + if (!pdev_data->pri_ptp_failed) { + dev_warn_ratelimited(pdev_data->dev, + "failed to get PTP_TSC concurrent timestamp from interface(%s)\nMake sure ptp is running\n", + pdev_data->iface_nm); + pdev_data->pri_ptp_failed = true; + } + } else { + pdev_data->pri_ptp_failed = false; + phc = ptp_tsc_ts.ptp_ts; + tsc = ptp_tsc_ts.tsc_ts / pdev_data->tsc_res_ns; + } + + + if ((pdev_data->platform_is_orin) && + /* primary & secondary ptp interface are not same */ + (strncmp(pdev_data->iface_nm, pdev_data->sec_iface_nm, strlen(pdev_data->iface_nm)))) { + + /* get PTP_TSC concurrent timestamp(using ptp notifier) from MAC + * driver for secondary interface + */ + if (tegra_get_hwtime(pdev_data->sec_iface_nm, &sec_ptp_tsc_ts, PTP_TSC_HWTIME)) { + /* check flag to print ptp failure msg */ + if (!pdev_data->sec_ptp_failed) { + dev_warn_ratelimited(pdev_data->dev, + "failed to get PTP_TSC concurrent timestamp for secondary interface(%s)\nMake sure ptp is running\n", + pdev_data->sec_iface_nm); + pdev_data->sec_ptp_failed = true; + } + } else { + pdev_data->sec_ptp_failed = false; + + /* Adjust secondary iface's PTP TS to primary iface's concurrent PTP_TSC TS */ + secondary_phc = sec_ptp_tsc_ts.ptp_ts - (sec_ptp_tsc_ts.tsc_ts - ptp_tsc_ts.tsc_ts); + } + } + } + +#ifdef NVPPS_ARM_COUNTER_PROFILING + { + u64 tmp; + int i; + irq_tsc = __arch_counter_get_cntvct(); + for (i = 0; i < 98; i++) { + tmp = __arch_counter_get_cntvct(); + } + tsc = __arch_counter_get_cntvct(); + } +#endif /* NVPPS_ARM_COUNTER_PROFILING */ + +#ifdef NVPPS_EQOS_REG_PROFILING + { + u32 varmac_stnsr; + u32 varmac_stsr; + int i; + irq_tsc = __arch_counter_get_cntvct(); + for (i = 0; i < 100; i++) { + MAC_STNSR_RD(varmac_stnsr); + MAC_STSR_RD(varmac_stsr) + } + tsc = __arch_counter_get_cntvct(); + } +#endif /* NVPPS_EQOS_REG_PROFILING */ + + /* get the interrupt latency */ + if (irq_tsc) { + irq_latency = (tsc - irq_tsc) * pdev_data->tsc_res_ns; + } + + raw_spin_lock_irqsave(&pdev_data->lock, flags); + pdev_data->pps_event_id_valid = true; + pdev_data->pps_event_id++; + pdev_data->tsc = irq_tsc ? irq_tsc : tsc; + /* adjust the ptp time for the interrupt latency */ +#if defined (NVPPS_ARM_COUNTER_PROFILING) || defined (NVPPS_EQOS_REG_PROFILING) + pdev_data->phc = phc; +#else /* !NVPPS_ARM_COUNTER_PROFILING && !NVPPS_EQOS_REG_PROFILING */ + pdev_data->phc = phc ? phc - irq_latency : phc; +#endif /* NVPPS_ARM_COUNTER_PROFILING || NVPPS_EQOS_REG_PROFILING */ + pdev_data->irq_latency = irq_latency; + pdev_data->actual_evt_mode = in_isr ? NVPPS_MODE_GPIO : NVPPS_MODE_TIMER; + /* Re-adjust secondary iface's PTP TS to irq_tsc TS, + * irq_latency will be 0 if TIMER mode, >0 if GPIO mode + */ + pdev_data->secondary_phc = secondary_phc ? secondary_phc - irq_latency : secondary_phc; + raw_spin_unlock_irqrestore(&pdev_data->lock, flags); + + /* event notification */ + wake_up_interruptible(&pdev_data->pps_event_queue); + kill_fasync(&pdev_data->pps_event_async_queue, SIGIO, POLL_IN); +} + + +static irqreturn_t nvpps_gpio_isr(int irq, void *data) +{ + struct nvpps_device_data *pdev_data = (struct nvpps_device_data *)data; + + /* get timestamps for this event */ + nvpps_get_ts(pdev_data, true); + + return IRQ_HANDLED; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) +static void tsc_timer_callback(unsigned long data) +{ + struct nvpps_device_data *pdev_data = (struct nvpps_device_data *)data; +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0) */ +static void tsc_timer_callback(struct timer_list *t) +{ + struct nvpps_device_data *pdev_data = (struct nvpps_device_data *)from_timer(pdev_data, t, tsc_timer); +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0) */ + uint32_t tsc_lock_status; + tsc_lock_status = readl(pdev_data->tsc_reg_map_base + TSC_LOCK_STAT_REG_OFF); + /* Incase TSC is not locked clear ALIGNED bit(RW1C) so that + * TSC starts to lock to the PTP again based on the PTP + * source selected in TSC registers. + */ + if (!(tsc_lock_status & BIT(TSC_LOCKED_STATUS_BIT_OFFSET))) { + uint32_t lock_control; + dev_info(pdev_data->dev, "tsc_lock_stat:%x\n", tsc_lock_status); + /* Write 1 to TSC_LOCKING_STATUS_0.ALIGNED to clear it */ + writel(tsc_lock_status | BIT(TSC_ALIGNED_STATUS_BIT_OFFSET), + pdev_data->tsc_reg_map_base + TSC_LOCK_STAT_REG_OFF); + + lock_control = readl(pdev_data->tsc_reg_map_base + + TSC_LOCK_CTRL_REG_OFF); + /* Write 1 to TSC_LOCKING_CONTROL_0.ALIGN */ + writel(lock_control | BIT(TSC_LOCK_CTRL_ALIGN_BIT_OFFSET), + pdev_data->tsc_reg_map_base + TSC_LOCK_CTRL_REG_OFF); + } + + /* set the next expire time */ + mod_timer(&pdev_data->tsc_timer, jiffies + msecs_to_jiffies(TSC_POLL_TIMER)); +} + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0) +static void nvpps_timer_callback(unsigned long data) +{ + struct nvpps_device_data *pdev_data = (struct nvpps_device_data *)data; +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0) */ +static void nvpps_timer_callback(struct timer_list *t) +{ + struct nvpps_device_data *pdev_data = (struct nvpps_device_data *)from_timer(pdev_data, t, timer); +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0) */ + /* get timestamps for this event */ + nvpps_get_ts(pdev_data, false); + + /* set the next expire time */ + if (pdev_data->timer_inited) { + mod_timer(&pdev_data->timer, jiffies + msecs_to_jiffies(1000)); + } +} + +/* spawn timer to monitor TSC to PTP lock and re-activate + locking process if its not locked in the handler */ +static int set_mode_tsc(struct nvpps_device_data *pdev_data) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) + setup_timer(&pdev_data->tsc_timer, + tsc_timer_callback, + (unsigned long)pdev_data); +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) */ + timer_setup(&pdev_data->tsc_timer, + tsc_timer_callback, + 0); +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) */ + mod_timer(&pdev_data->tsc_timer, jiffies + msecs_to_jiffies(1000)); + + return 0; +} + +static int set_mode(struct nvpps_device_data *pdev_data, u32 mode) +{ + int err = 0; + if (mode != pdev_data->evt_mode) { + switch (mode) { + case NVPPS_MODE_GPIO: + if (!pdev_data->only_timer_mode) { + if (pdev_data->timer_inited) { + pdev_data->timer_inited = false; + del_timer_sync(&pdev_data->timer); + } + if (!pdev_data->irq_registered) { + /* register IRQ handler */ + err = devm_request_irq(pdev_data->dev, pdev_data->irq, nvpps_gpio_isr, + IRQF_TRIGGER_RISING, "nvpps_isr", pdev_data); + if (err) { + dev_err(pdev_data->dev, "failed to acquire IRQ %d\n", pdev_data->irq); + } else { + pdev_data->irq_registered = true; + dev_info(pdev_data->dev, "Registered IRQ %d for nvpps\n", pdev_data->irq); + } + } + } else { + dev_err(pdev_data->dev, "unable to switch mode. Only timer mode is supported\n"); + err = -EINVAL; + } + break; + + case NVPPS_MODE_TIMER: + if (pdev_data->irq_registered) { + /* unregister IRQ handler */ + devm_free_irq(pdev_data->dev, pdev_data->irq, pdev_data); + pdev_data->irq_registered = false; + dev_info(pdev_data->dev, "removed IRQ %d for nvpps\n", pdev_data->irq); + } + if (!pdev_data->timer_inited) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) + setup_timer(&pdev_data->timer, + nvpps_timer_callback, + (unsigned long)pdev_data); +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) */ + timer_setup(&pdev_data->timer, + nvpps_timer_callback, + 0); +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) */ + pdev_data->timer_inited = true; + /* setup timer interval to 1000 msecs */ + mod_timer(&pdev_data->timer, jiffies + msecs_to_jiffies(1000)); + } + break; + + default: + return -EINVAL; + } + } + return err; +} + + + +/* Character device stuff */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0) +static unsigned int nvpps_poll(struct file *file, poll_table *wait) +{ +#else +static __poll_t nvpps_poll(struct file *file, poll_table *wait) +{ +#endif + struct nvpps_file_data *pfile_data = (struct nvpps_file_data *)file->private_data; + struct nvpps_device_data *pdev_data = pfile_data->pdev_data; + + poll_wait(file, &pdev_data->pps_event_queue, wait); + if (pdev_data->pps_event_id_valid && + (pfile_data->pps_event_id_rd != pdev_data->pps_event_id)) { + return POLLIN | POLLRDNORM; + } else { + return 0; + } +} + + +static int nvpps_fasync(int fd, struct file *file, int on) +{ + struct nvpps_file_data *pfile_data = (struct nvpps_file_data *)file->private_data; + struct nvpps_device_data *pdev_data = pfile_data->pdev_data; + + return fasync_helper(fd, file, on, &pdev_data->pps_event_async_queue); +} + + +static long nvpps_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct nvpps_file_data *pfile_data = (struct nvpps_file_data *)file->private_data; + struct nvpps_device_data *pdev_data = pfile_data->pdev_data; + struct nvpps_params params; + void __user *uarg = (void __user *)arg; + int err; + + switch (cmd) { + case NVPPS_GETVERSION: { + struct nvpps_version version; + + dev_dbg(pdev_data->dev, "NVPPS_GETVERSION\n"); + + /* Get the current parameters */ + version.version.major = NVPPS_VERSION_MAJOR; + version.version.minor = NVPPS_VERSION_MINOR; + version.api.major = NVPPS_API_MAJOR; + version.api.minor = NVPPS_API_MINOR; + + err = copy_to_user(uarg, &version, sizeof(struct nvpps_version)); + if (err) { + return -EFAULT; + } + break; + } + + case NVPPS_GETPARAMS: + dev_dbg(pdev_data->dev, "NVPPS_GETPARAMS\n"); + + /* Get the current parameters */ + params.evt_mode = pdev_data->evt_mode; + params.tsc_mode = pdev_data->tsc_mode; + + err = copy_to_user(uarg, ¶ms, sizeof(struct nvpps_params)); + if (err) { + return -EFAULT; + } + break; + + case NVPPS_SETPARAMS: + dev_dbg(pdev_data->dev, "NVPPS_SETPARAMS\n"); + + err = copy_from_user(¶ms, uarg, sizeof(struct nvpps_params)); + if (err) { + return -EFAULT; + } + err = set_mode(pdev_data, params.evt_mode); + if (err) { + dev_dbg(pdev_data->dev, "switch_mode to %d failed err(%d)\n", params.evt_mode, err); + return err; + } + pdev_data->evt_mode = params.evt_mode; + pdev_data->tsc_mode = params.tsc_mode; + break; + + case NVPPS_GETEVENT: { + struct nvpps_timeevent time_event; + unsigned long flags; + + dev_dbg(pdev_data->dev, "NVPPS_GETEVENT\n"); + + /* Return the fetched timestamp */ + raw_spin_lock_irqsave(&pdev_data->lock, flags); + pfile_data->pps_event_id_rd = pdev_data->pps_event_id; + time_event.evt_nb = pdev_data->pps_event_id; + time_event.tsc = pdev_data->tsc; + time_event.ptp = pdev_data->phc; + time_event.secondary_ptp = pdev_data->secondary_phc; + time_event.irq_latency = pdev_data->irq_latency; + raw_spin_unlock_irqrestore(&pdev_data->lock, flags); + if (NVPPS_TSC_NSEC == pdev_data->tsc_mode) { + time_event.tsc *= pdev_data->tsc_res_ns; + } + time_event.tsc_res_ns = pdev_data->tsc_res_ns; + /* return the mode when the time event actually occured */ + time_event.evt_mode = pdev_data->actual_evt_mode; + time_event.tsc_mode = pdev_data->tsc_mode; + + err = copy_to_user(uarg, &time_event, sizeof(struct nvpps_timeevent)); + if (err) { + return -EFAULT; + } + + break; + } + + case NVPPS_GETTIMESTAMP: { + struct nvpps_timestamp_struct time_stamp; + u64 ns; + u32 reminder; + u64 tsc1, tsc2; + + tsc1 = __arch_counter_get_cntvct(); + + dev_dbg(pdev_data->dev, "NVPPS_GETTIMESTAMP\n"); + + err = copy_from_user(&time_stamp, uarg, + sizeof(struct nvpps_timestamp_struct)); + if (err) + return -EFAULT; + + mutex_lock(&pdev_data->ts_lock); + switch (time_stamp.clockid) { + case CLOCK_REALTIME: +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) + ktime_get_real_ts(&time_stamp.kernel_ts); +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) */ + ktime_get_real_ts64(&time_stamp.kernel_ts); +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) */ + break; + + case CLOCK_MONOTONIC: +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) + ktime_get_ts(&time_stamp.kernel_ts); +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) */ + ktime_get_ts64(&time_stamp.kernel_ts); +#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 15, 0) */ + break; + + default: + dev_dbg(pdev_data->dev, + "ioctl: Unsupported clockid\n"); + } + + err = tegra_get_hwtime(pdev_data->iface_nm, &ns, PTP_HWTIME); + mutex_unlock(&pdev_data->ts_lock); + if (err) { + dev_dbg(pdev_data->dev, + "pdev_data->dev, HW PTP not running\n"); + return err; + } + time_stamp.hw_ptp_ts.tv_sec = div_u64_rem(ns, + 1000000000ULL, + &reminder); + time_stamp.hw_ptp_ts.tv_nsec = reminder; + + tsc2 = __arch_counter_get_cntvct(); + time_stamp.extra[0] = + (tsc2 - tsc1) * pdev_data->tsc_res_ns; + + err = copy_to_user(uarg, &time_stamp, + sizeof(struct nvpps_timestamp_struct)); + if (err) + return -EFAULT; + break; + } + + default: + return -ENOTTY; + } + + return 0; +} + + + +static int nvpps_open(struct inode *inode, struct file *file) +{ + struct nvpps_device_data *pdev_data = container_of(inode->i_cdev, struct nvpps_device_data, cdev); + struct nvpps_file_data *pfile_data; + + pfile_data = kzalloc(sizeof(struct nvpps_file_data), GFP_KERNEL); + if (!pfile_data) { + dev_err(&pdev_data->pdev->dev, "nvpps_open kzalloc() failed\n"); + return -ENOMEM; + } + + pfile_data->pdev_data = pdev_data; + pfile_data->pps_event_id_rd = (unsigned int)-1; + + file->private_data = pfile_data; + kobject_get(&pdev_data->dev->kobj); + return 0; +} + + + +static int nvpps_close(struct inode *inode, struct file *file) +{ + struct nvpps_device_data *pdev_data = container_of(inode->i_cdev, struct nvpps_device_data, cdev); + + if (file->private_data) { + kfree(file->private_data); + } + kobject_put(&pdev_data->dev->kobj); + return 0; +} + + + +static const struct file_operations nvpps_fops = { + .owner = THIS_MODULE, + .poll = nvpps_poll, + .fasync = nvpps_fasync, + .unlocked_ioctl = nvpps_ioctl, + .open = nvpps_open, + .release = nvpps_close, +}; + + + +static void nvpps_dev_release(struct device *dev) +{ + struct nvpps_device_data *pdev_data = dev_get_drvdata(dev); + + cdev_del(&pdev_data->cdev); + + mutex_lock(&s_nvpps_lock); + idr_remove(&s_nvpps_idr, pdev_data->id); + mutex_unlock(&s_nvpps_lock); + + kfree(dev); +} + +static void nvpps_fill_default_mac_phc_info(struct platform_device *pdev, + struct nvpps_device_data *pdev_data) +{ + bool use_eqos_mac = false; + struct device_node *np = pdev->dev.of_node; + + pdev_data->platform_is_orin = false; + + /* Get default params from dt */ + pdev_data->iface_nm = (char *)of_get_property(np, "interface", NULL); + pdev_data->sec_iface_nm = (char *)of_get_property(np, "sec_interface", NULL); + pdev_data->memmap_phc_regs = of_property_read_bool(np, "memmap_phc_regs"); + + /* For orin */ + if (of_machine_is_compatible("nvidia,tegra234")) { + pdev_data->platform_is_orin = true; + + /* set default seconday interface for ptp timestamp */ + if (pdev_data->sec_iface_nm == NULL) { + pdev_data->sec_iface_nm = devm_kstrdup(&pdev->dev, "eqos_0", GFP_KERNEL); + } + + if (pdev_data->memmap_phc_regs) { + /* TODO: Add support to map secondary interfaces PHC registers */ + dev_info(&pdev->dev, "using mem mapped MAC PHC reg method\n"); + if (pdev_data->iface_nm == NULL) { + pdev_data->iface_nm = devm_kstrdup(&pdev->dev, "eqos_0", GFP_KERNEL); + dev_warn(&pdev->dev, "interface property not provided. Using default interface(%s)\n", pdev_data->iface_nm); + use_eqos_mac = true; + } else { + if (!strncmp(pdev_data->iface_nm, "eqos_0", sizeof("eqos_0"))) { + use_eqos_mac = true; + } else if (!strncmp(pdev_data->iface_nm, "mgbe0_0", sizeof("mgbe0_0"))) { + /* remap base address for mgbe0_0 */ + pdev_data->mac_base_addr = devm_ioremap(&pdev->dev, T234_MGBE0_BASE_ADDR, SZ_4K); + dev_info(&pdev->dev, "map MGBE0_0 to (%p)\n", pdev_data->mac_base_addr); + pdev_data->sts_offset = MGBE_STSR_OFFSET; + pdev_data->stns_offset = MGBE_STNSR_OFFSET; + } else if (!strncmp(pdev_data->iface_nm, "mgbe1_0", sizeof("mgbe1_0"))) { + /* remap base address for mgbe1_0 */ + pdev_data->mac_base_addr = devm_ioremap(&pdev->dev, T234_MGBE1_BASE_ADDR, SZ_4K); + dev_info(&pdev->dev, "map MGBE1_0 to (%p)\n", pdev_data->mac_base_addr); + pdev_data->sts_offset = MGBE_STSR_OFFSET; + pdev_data->stns_offset = MGBE_STNSR_OFFSET; + } else if (!strncmp(pdev_data->iface_nm, "mgbe2_0", sizeof("mgbe2_0"))) { + /* remap base address for mgbe2_0 */ + pdev_data->mac_base_addr = devm_ioremap(&pdev->dev, T234_MGBE2_BASE_ADDR, SZ_4K); + dev_info(&pdev->dev, "map MGBE2_0 to (%p)\n", pdev_data->mac_base_addr); + pdev_data->sts_offset = MGBE_STSR_OFFSET; + pdev_data->stns_offset = MGBE_STNSR_OFFSET; + } else if (!strncmp(pdev_data->iface_nm, "mgbe3_0", sizeof("mgbe3_0"))) { + /* remap base address for mgbe3_0 */ + pdev_data->mac_base_addr = devm_ioremap(&pdev->dev, T234_MGBE3_BASE_ADDR, SZ_4K); + dev_info(&pdev->dev, "map MGBE3_0 to (%p)\n", pdev_data->mac_base_addr); + pdev_data->sts_offset = MGBE_STSR_OFFSET; + pdev_data->stns_offset = MGBE_STNSR_OFFSET; + } else { + dev_warn(&pdev->dev, "Invalid interface(%s). Using default interface(eqos_0)\n", pdev_data->iface_nm); + pdev_data->iface_nm = devm_kstrdup(&pdev->dev, "eqos_0", GFP_KERNEL); + use_eqos_mac = true; + } + } + + if (use_eqos_mac) { + /* remap base address for eqos */ + pdev_data->mac_base_addr = devm_ioremap(&pdev->dev, T234_EQOS_BASE_ADDR, SZ_4K); + dev_info(&pdev->dev, "map EQOS to (%p)\n", pdev_data->mac_base_addr); + pdev_data->sts_offset = EQOS_STSR_OFFSET; + pdev_data->stns_offset = EQOS_STNSR_OFFSET; + } + } else { + /* Using ptp-notifier method */ + if (pdev_data->iface_nm) { + if ((!strncmp(pdev_data->iface_nm, "eqos_0", sizeof("eqos_0"))) || + (!strncmp(pdev_data->iface_nm, "mgbe0_0", sizeof("mgbe0_0"))) || + (!strncmp(pdev_data->iface_nm, "mgbe1_0", sizeof("mgbe1_0"))) || + (!strncmp(pdev_data->iface_nm, "mgbe2_0", sizeof("mgbe2_0"))) || + (!strncmp(pdev_data->iface_nm, "mgbe3_0", sizeof("mgbe3_0")))) { + dev_info(&pdev->dev, "using ptp notifier method with interface(%s)\n", pdev_data->iface_nm); + } else { + dev_warn(&pdev->dev, "Invalid interface(%s). Using default interface(eqos_0)\n", pdev_data->iface_nm); + pdev_data->iface_nm = devm_kstrdup(&pdev->dev, "eqos_0", GFP_KERNEL); + } + } else { + pdev_data->iface_nm = devm_kstrdup(&pdev->dev, "eqos_0", GFP_KERNEL); + dev_info(&pdev->dev, "using ptp notifier method with interface(%s)\n", pdev_data->iface_nm); + } + } + } else { + if (pdev_data->memmap_phc_regs) { + if (!(pdev_data->iface_nm && (strncmp(pdev_data->iface_nm, "eqos_0", sizeof("eqos_0")) == 0))) { + dev_warn(&pdev->dev, "Invalid interface(%s). Using default interface(eqos_0)\n", pdev_data->iface_nm); + pdev_data->iface_nm = devm_kstrdup(&pdev->dev, "eqos_0", GFP_KERNEL); + } + + dev_info(&pdev->dev, "using mem mapped MAC PHC reg method with %s MAC\n", pdev_data->iface_nm); + /* remap base address for eqos */ + pdev_data->mac_base_addr = devm_ioremap(&pdev->dev, T194_EQOS_BASE_ADDR, SZ_4K); + dev_info(&pdev->dev, "map EQOS to (%p)\n", pdev_data->mac_base_addr); + pdev_data->sts_offset = EQOS_STSR_OFFSET; + pdev_data->stns_offset = EQOS_STNSR_OFFSET; + } else { + pdev_data->iface_nm = devm_kstrdup(&pdev->dev, "eqos_0", GFP_KERNEL); + dev_info(&pdev->dev, "using ptp notifier method with default interface(%s)\n", pdev_data->iface_nm); + } + } + + interface_name = devm_kstrdup(&pdev->dev, pdev_data->iface_nm, GFP_KERNEL); +} + + +static int nvpps_probe(struct platform_device *pdev) +{ + struct nvpps_device_data *pdev_data; + struct device_node *np = pdev->dev.of_node; + dev_t devt; + int err; + struct device_node *np_gte; + uint32_t tsc_config_ptx_0; + + dev_info(&pdev->dev, "%s\n", __FUNCTION__); + + if (!np) { + dev_err(&pdev->dev, "no valid device node, probe failed\n"); + return -EINVAL; + } + + pdev_data = devm_kzalloc(&pdev->dev, sizeof(struct nvpps_device_data), GFP_KERNEL); + if (!pdev_data) { + return -ENOMEM; + } + + err = of_get_gpio(np, 0); + if (err == -EPROBE_DEFER) { + return err; + } else if (err < 0) { + dev_warn(&pdev->dev, "PPS GPIO not provided in DT, only Timer mode available\n"); + pdev_data->only_timer_mode = true; + } else { + pdev_data->gpio_pin = (unsigned int)err; + dev_info(&pdev->dev, "gpio_pin(%d)\n", pdev_data->gpio_pin); + + /* GPIO setup */ + if (gpio_is_valid(pdev_data->gpio_pin)) { + err = devm_gpio_request(&pdev->dev, pdev_data->gpio_pin, "gpio_pps"); + if (err) { + dev_err(&pdev->dev, "failed to request GPIO %u\n", + pdev_data->gpio_pin); + return err; + } + + err = gpio_direction_input(pdev_data->gpio_pin); + if (err) { + dev_err(&pdev->dev, "failed to set pin direction\n"); + return -EINVAL; + } + + /* IRQ setup */ + err = gpio_to_irq(pdev_data->gpio_pin); + if (err < 0) { + dev_err(&pdev->dev, "failed to map GPIO to IRQ: %d\n", err); + return -EINVAL; + } + pdev_data->irq = err; + dev_info(&pdev->dev, "gpio_to_irq(%d)\n", pdev_data->irq); + } + } + + nvpps_fill_default_mac_phc_info(pdev, pdev_data); + + init_waitqueue_head(&pdev_data->pps_event_queue); + raw_spin_lock_init(&pdev_data->lock); + mutex_init(&pdev_data->ts_lock); + pdev_data->pdev = pdev; + pdev_data->evt_mode = 0; /* NVPPS_MODE_GPIO */ + pdev_data->tsc_mode = NVPPS_TSC_NSEC; + #define _PICO_SECS (1000000000000ULL) + pdev_data->tsc_res_ns = (_PICO_SECS / (u64)arch_timer_get_cntfrq()) / 1000; + #undef _PICO_SECS + dev_info(&pdev->dev, "tsc_res_ns(%llu)\n", pdev_data->tsc_res_ns); + + /* character device setup */ +#ifndef NVPPS_NO_DT + s_nvpps_class = class_create(THIS_MODULE, "nvpps"); + if (IS_ERR(s_nvpps_class)) { + dev_err(&pdev->dev, "failed to allocate class\n"); + return PTR_ERR(s_nvpps_class); + } + + err = alloc_chrdev_region(&s_nvpps_devt, 0, MAX_NVPPS_SOURCES, "nvpps"); + if (err < 0) { + dev_err(&pdev->dev, "failed to allocate char device region\n"); + class_destroy(s_nvpps_class); + return err; + } +#endif /* !NVPPS_NO_DT */ + + /* get an idr for the device */ + mutex_lock(&s_nvpps_lock); + err = idr_alloc(&s_nvpps_idr, pdev_data, 0, MAX_NVPPS_SOURCES, GFP_KERNEL); + if (err < 0) { + if (err == -ENOSPC) { + dev_err(&pdev->dev, "nvpps: out of idr \n"); + err = -EBUSY; + } + mutex_unlock(&s_nvpps_lock); + return err; + } + pdev_data->id = err; + mutex_unlock(&s_nvpps_lock); + + /* associate the cdev with the file operations */ + cdev_init(&pdev_data->cdev, &nvpps_fops); + + /* build up the device number */ + devt = MKDEV(MAJOR(s_nvpps_devt), pdev_data->id); + pdev_data->cdev.owner = THIS_MODULE; + + /* create the device node */ + pdev_data->dev = device_create(s_nvpps_class, NULL, devt, pdev_data, "nvpps%d", pdev_data->id); + if (IS_ERR(pdev_data->dev)) { + err = PTR_ERR(pdev_data->dev); + goto error_ret; + } + + pdev_data->dev->release = nvpps_dev_release; + + err = cdev_add(&pdev_data->cdev, devt, 1); + if (err) { + dev_err(&pdev->dev, "nvpps: failed to add char device %d:%d\n", + MAJOR(s_nvpps_devt), pdev_data->id); + device_destroy(s_nvpps_class, pdev_data->dev->devt); + goto error_ret; + } + + dev_info(&pdev->dev, "nvpps cdev(%d:%d)\n", MAJOR(s_nvpps_devt), pdev_data->id); + platform_set_drvdata(pdev, pdev_data); + + np_gte = of_find_compatible_node(NULL, NULL, "nvidia,tegra234-gte-aon"); + if (!np_gte) { + np_gte = of_find_compatible_node(NULL, NULL, "nvidia,tegra194-gte-aon"); + } + if (!np_gte) { + pdev_data->use_gpio_int_timesatmp = false; + dev_err(&pdev->dev, "of_find_compatible_node failed\n"); + } else { + pdev_data->gte_ev_desc = tegra_gte_register_event(np_gte, pdev_data->gpio_pin); + if (IS_ERR(pdev_data->gte_ev_desc)) { + pdev_data->use_gpio_int_timesatmp = false; + dev_err(&pdev->dev, "tegra_gte_register_event err = %d\n", (int)PTR_ERR(pdev_data->gte_ev_desc)); + } else { + pdev_data->use_gpio_int_timesatmp = true; + dev_info(pdev_data->dev, "tegra_gte_register_event succeed\n"); + } + } + + /* setup PPS event hndler */ + err = set_mode(pdev_data, + (pdev_data->only_timer_mode) ? NVPPS_MODE_TIMER : NVPPS_MODE_GPIO); + if (err) { + dev_err(&pdev->dev, "set_mode failed err = %d\n", err); + device_destroy(s_nvpps_class, pdev_data->dev->devt); + goto error_ret; + } + pdev_data->evt_mode = (pdev_data->only_timer_mode) ? NVPPS_MODE_TIMER : NVPPS_MODE_GPIO; + + if (pdev_data->platform_is_orin) { + struct resource *tsc_mem; + + tsc_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (tsc_mem == NULL) { + err = -ENODEV; + dev_err(&pdev->dev, "TSC memory resource not defined\n"); + device_destroy(s_nvpps_class, pdev_data->dev->devt); +#ifndef NVPPS_NO_DT + class_destroy(s_nvpps_class); +#endif + goto error_ret; + } + + pdev_data->tsc_reg_map_base = ioremap(tsc_mem->start, resource_size(tsc_mem)); + if (!pdev_data->tsc_reg_map_base) { + dev_err(&pdev->dev, "TSC register ioremap failed\n"); + device_destroy(s_nvpps_class, pdev_data->dev->devt); + err = -ENOMEM; + goto error_ret; + } + + if (!strncmp(pdev_data->iface_nm, "mgbe0_0", sizeof("mgbe0_0"))) { + pdev_data->tsc_ptp_src = (TSC_PTP_SRC_MGBE0 << SRC_SELECT_BIT_OFFSET); + } else if (!strncmp(pdev_data->iface_nm, "mgbe1_0", sizeof("mgbe1_0"))) { + pdev_data->tsc_ptp_src = (TSC_PTP_SRC_MGBE1 << SRC_SELECT_BIT_OFFSET); + } else if (!strncmp(pdev_data->iface_nm, "mgbe2_0", sizeof("mgbe2_0"))) { + pdev_data->tsc_ptp_src = (TSC_PTP_SRC_MGBE2 << SRC_SELECT_BIT_OFFSET); + } else if (!strncmp(pdev_data->iface_nm, "mgbe3_0", sizeof("mgbe3_0"))) { + pdev_data->tsc_ptp_src = (TSC_PTP_SRC_MGBE3 << SRC_SELECT_BIT_OFFSET); + } else { + pdev_data->tsc_ptp_src = (TSC_PTP_SRC_EQOS << SRC_SELECT_BIT_OFFSET); + } + + tsc_config_ptx_0 = readl(pdev_data->tsc_reg_map_base); + /* clear and set the ptp src based on ethernet interface passed + * from dt for tsc to lock onto. + */ + tsc_config_ptx_0 = tsc_config_ptx_0 & + ~(SRC_SELECT_BITS << SRC_SELECT_BIT_OFFSET); + tsc_config_ptx_0 = tsc_config_ptx_0 | pdev_data->tsc_ptp_src; + writel(tsc_config_ptx_0, pdev_data->tsc_reg_map_base); + tsc_config_ptx_0 = readl(pdev_data->tsc_reg_map_base); + dev_info(&pdev->dev, "TSC config ptx 0x%x\n", tsc_config_ptx_0); + + set_mode_tsc(pdev_data); + } + + return 0; + +error_ret: + cdev_del(&pdev_data->cdev); + mutex_lock(&s_nvpps_lock); + idr_remove(&s_nvpps_idr, pdev_data->id); + mutex_unlock(&s_nvpps_lock); + return err; +} + + +static int nvpps_remove(struct platform_device *pdev) +{ + struct nvpps_device_data *pdev_data = platform_get_drvdata(pdev); + + dev_info(&pdev->dev, "%s\n", __FUNCTION__); + + if (pdev_data) { + if (pdev_data->timer_inited) { + pdev_data->timer_inited = false; + del_timer_sync(&pdev_data->timer); + } + if (pdev_data->use_gpio_int_timesatmp) { + if (!IS_ERR_OR_NULL(pdev_data->gte_ev_desc)) { + tegra_gte_unregister_event(pdev_data->gte_ev_desc); + } + pdev_data->use_gpio_int_timesatmp = false; + } + if (pdev_data->memmap_phc_regs) { + devm_iounmap(&pdev->dev, pdev_data->mac_base_addr); + dev_info(&pdev->dev, "unmap MAC reg space %p for nvpps\n", + pdev_data->mac_base_addr); + } + if (pdev_data->platform_is_orin) { + del_timer_sync(&pdev_data->tsc_timer); + iounmap(pdev_data->tsc_reg_map_base); + } + device_destroy(s_nvpps_class, pdev_data->dev->devt); + } + +#ifndef NVPPS_NO_DT + class_unregister(s_nvpps_class); + class_destroy(s_nvpps_class); + unregister_chrdev_region(s_nvpps_devt, MAX_NVPPS_SOURCES); +#endif /* !NVPPS_NO_DT */ + return 0; +} + + +#ifdef CONFIG_PM +static int nvpps_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* struct nvpps_device_data *pdev_data = platform_get_drvdata(pdev); */ + + return 0; +} + +static int nvpps_resume(struct platform_device *pdev) +{ + /* struct nvpps_device_data *pdev_data = platform_get_drvdata(pdev); */ + + return 0; +} +#endif /* CONFIG_PM */ + + +#ifndef NVPPS_NO_DT +static const struct of_device_id nvpps_of_table[] = { + { .compatible = "nvidia,tegra194-nvpps", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, nvpps_of_table); +#endif /* !NVPPS_NO_DT */ + + +static struct platform_driver nvpps_plat_driver = { + .driver = { + .name = KBUILD_MODNAME, + .owner = THIS_MODULE, +#ifndef NVPPS_NO_DT + .of_match_table = of_match_ptr(nvpps_of_table), +#endif /* !NVPPS_NO_DT */ + }, + .probe = nvpps_probe, + .remove = nvpps_remove, +#ifdef CONFIG_PM + .suspend = nvpps_suspend, + .resume = nvpps_resume, +#endif /* CONFIG_PM */ +}; + + +#ifdef NVPPS_NO_DT +/* module init + */ +static int __init nvpps_init(void) +{ + int err; + + printk("%s\n", __FUNCTION__); + + s_nvpps_class = class_create(THIS_MODULE, "nvpps"); + if (IS_ERR(s_nvpps_class)) { + printk("nvpps: failed to allocate class\n"); + return PTR_ERR(s_nvpps_class); + } + + err = alloc_chrdev_region(&s_nvpps_devt, 0, MAX_NVPPS_SOURCES, "nvpps"); + if (err < 0) { + printk("nvpps: failed to allocate char device region\n"); + class_destroy(s_nvpps_class); + return err; + } + + printk("nvpps registered\n"); + + return platform_driver_register(&nvpps_plat_driver); +} + + +/* module fini + */ +static void __exit nvpps_exit(void) +{ + printk("%s\n", __FUNCTION__); + platform_driver_unregister(&nvpps_plat_driver); + + class_unregister(s_nvpps_class); + class_destroy(s_nvpps_class); + unregister_chrdev_region(s_nvpps_devt, MAX_NVPPS_SOURCES); +} + +#endif /* NVPPS_NO_DT */ + + +#ifdef NVPPS_NO_DT +module_init(nvpps_init); +module_exit(nvpps_exit); +#else /* !NVPPS_NO_DT */ +module_platform_driver(nvpps_plat_driver); +#endif /* NVPPS_NO_DT */ + +MODULE_DESCRIPTION("NVidia Tegra PPS Driver"); +MODULE_AUTHOR("David Tao tehyut@nvidia.com"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/tegra/ptp-notifier.c b/drivers/platform/tegra/ptp-notifier.c new file mode 100644 index 00000000..ff734f71 --- /dev/null +++ b/drivers/platform/tegra/ptp-notifier.c @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#include +#include +#include + +static int (*get_systime[MAX_MAC_INSTANCES])(struct net_device *, void *, int); +static struct net_device *registered_ndev[MAX_MAC_INSTANCES]; +static DEFINE_RAW_SPINLOCK(ptp_notifier_lock); +static ATOMIC_NOTIFIER_HEAD(tegra_hwtime_chain_head); + +/* Clients register for notification of hwtime change events */ +int tegra_register_hwtime_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&tegra_hwtime_chain_head, nb); +} +EXPORT_SYMBOL(tegra_register_hwtime_notifier); + +/* Clients unregister for notification of hwtime change events */ +int tegra_unregister_hwtime_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&tegra_hwtime_chain_head, nb); +} +EXPORT_SYMBOL(tegra_unregister_hwtime_notifier); + +/* Trigger notification of hwtime change to all registered clients */ +int tegra_hwtime_notifier_call_chain(unsigned int val, void *v) +{ + int ret = atomic_notifier_call_chain(&tegra_hwtime_chain_head, val, v); + + return notifier_to_errno(ret); +} + +void tegra_register_hwtime_source(int (*func)(struct net_device *, void *, int), + struct net_device *ndev) +{ + unsigned long flags; + int index = 0; + + for (index = 0; index < MAX_MAC_INSTANCES; index++) { + if (registered_ndev[index] == ndev) { + pr_debug("source is already registered for this intf\n"); + /* Didn't call the notifier as we are not using + * tegra_register_hwtime_notifier anywhere + */ + return; + } + } + raw_spin_lock_irqsave(&ptp_notifier_lock, flags); + for (index = 0; index < MAX_MAC_INSTANCES; index++) { + if (get_systime[index] == NULL) { + get_systime[index] = func; + registered_ndev[index] = ndev; + break; + } + } + if (index == MAX_MAC_INSTANCES) + pr_err("Maximum registrations reached\n"); + + raw_spin_unlock_irqrestore(&ptp_notifier_lock, flags); + + /* Notify HW time stamp update to registered clients. + * NULL callback parameter. We use a separate timestamp + * function to peek MAC time. + */ + tegra_hwtime_notifier_call_chain(0, NULL); +} +EXPORT_SYMBOL(tegra_register_hwtime_source); + +void tegra_unregister_hwtime_source(struct net_device *dev) +{ + unsigned long flags; + int index = 0; + + raw_spin_lock_irqsave(&ptp_notifier_lock, flags); + for (index = 0; index < MAX_MAC_INSTANCES; index++) { + if (dev == registered_ndev[index]) + break; + } + if (index == MAX_MAC_INSTANCES) { + pr_debug("Trying to unregister non-registered hwtime source"); + raw_spin_unlock_irqrestore(&ptp_notifier_lock, flags); + return; + } + get_systime[index] = NULL; + registered_ndev[index] = NULL; + raw_spin_unlock_irqrestore(&ptp_notifier_lock, flags); +} +EXPORT_SYMBOL(tegra_unregister_hwtime_source); + +int tegra_get_hwtime(const char *intf_name, void *ts, int ts_type) +{ + unsigned long flags; + int ret = 0, index = 0; + struct net_device *dev; + + raw_spin_lock_irqsave(&ptp_notifier_lock, flags); + if (!intf_name || !ts) { + pr_err("passed Interface_name or time-stamp ptr is NULL"); + raw_spin_unlock_irqrestore(&ptp_notifier_lock, flags); + return -1; + } + dev = dev_get_by_name(&init_net, intf_name); + + if (!dev || !(dev->flags & IFF_UP)) { + pr_debug("dev is NULL or intf is not up for %s\n", intf_name); + ret = -EINVAL; + goto out; + } + for (index = 0; index < MAX_MAC_INSTANCES; index++) { + if (dev == registered_ndev[index]) + break; + } + if (index == MAX_MAC_INSTANCES) { + pr_debug("Interface: %s is not registered to get HW time", intf_name); + ret = -EINVAL; + goto out; + } + + if (get_systime[index]) + ret = (get_systime[index])(dev, ts, ts_type); + else + ret = -EINVAL; + +out: + if (dev) + dev_put(dev); + raw_spin_unlock_irqrestore(&ptp_notifier_lock, flags); + + return ret; +} +EXPORT_SYMBOL(tegra_get_hwtime); + +MODULE_LICENSE("GPL"); diff --git a/include/linux/nvpps.h b/include/linux/nvpps.h new file mode 100644 index 00000000..17d2af22 --- /dev/null +++ b/include/linux/nvpps.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef __NVPPS_H__ +#define __NVPPS_H__ + +/* + * Get PTP time + * Clients may call the API every and anytime PTP time is needed. + * If PTP time source is not registered, returns -EINVAL + * + * This API is available irrespective of nvpps dt availablity + * When nvpps dt node is not present, interface name will + * default to "eth0". + */ +int nvpps_get_ptp_ts(void *ts); + +#endif /* __NVPPS_H__ */ diff --git a/include/linux/platform/tegra/ptp-notifier.h b/include/linux/platform/tegra/ptp-notifier.h new file mode 100644 index 00000000..9b800ff6 --- /dev/null +++ b/include/linux/platform/tegra/ptp-notifier.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef __PTP_NOTIFIER_H +#define __PTP_NOTIFIER_H + +#include +#include +#include + +#define PTP_HWTIME 1 +#define PTP_TSC_HWTIME 2 +#define TSC_HIGH_SHIFT 32U + +/** Max of 5 interfaces created per IP multiply by number of IP */ +#define MAX_MAC_INSTANCES 25 + +/** + * @brief ptp_tsc_data - Struture used to store TSC and PTP time + * information. + */ +struct ptp_tsc_data { + /** PTP TimeStamp in nSec*/ + u64 ptp_ts; + /** TSC TimeStamp in nSec*/ + u64 tsc_ts; +}; + +/* register / unregister HW time source */ +void tegra_register_hwtime_source(int (*func)(struct net_device *, void *, int), + struct net_device *dev); +void tegra_unregister_hwtime_source(struct net_device *dev); + +/* clients registering / unregistering for time update events */ +int tegra_register_hwtime_notifier(struct notifier_block *nb); +int tegra_unregister_hwtime_notifier(struct notifier_block *nb); + +/* Notify time updates to registered clients */ +int tegra_hwtime_notifier_call_chain(unsigned int val, void *v); + +/* + * Get HW time counter. + * Clients may call the API every anytime PTP/TSC time is needed. + * If HW time source is not registered, returns -EINVAL + */ +int tegra_get_hwtime(const char *intf_name, void *ts, int ts_type); + +#endif /* __PTP_NOTIFIER_H */ diff --git a/include/linux/tegra-gte.h b/include/linux/tegra-gte.h new file mode 100644 index 00000000..4c570bf9 --- /dev/null +++ b/include/linux/tegra-gte.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef _LINUX_TEGRA_GTE_ENGINE_H +#define _LINUX_TEGRA_GTE_ENGINE_H + +#include + +struct tegra_gte_ev_desc { + int id; + u32 ev_bit; + u32 slice; +}; + +/* GTE hardware timestamping event details */ +struct tegra_gte_ev_detail { + u64 ts_raw; /* raw counter value */ + u64 ts_ns; /* counter value converted into nano seconds */ + int dir; /* direction of the event */ +}; + +#ifdef CONFIG_TEGRA_HTS_GTE +/* + * GTE event registration function + * + * Parameters: + * + * Input: + * @np: device node of the interested GTE device + * @ev_id: event id + * + * Returns: + * Returns ERR_PTR in case of failure or valid + * struct tegra_gte_ev_desc for success. + * + * Note: API is not stable and subject to change. + */ +struct tegra_gte_ev_desc *tegra_gte_register_event(struct device_node *np, + u32 ev_id); + +/* + * GTE event un-registration function + * + * Parameters: + * + * Input: + * @desc: This parameter should be the same as returned from register + * + * Returns: + * Returns 0 for success and any other value for the failure + * + * Note: API is not stable and subject to change. + */ +int tegra_gte_unregister_event(struct tegra_gte_ev_desc *desc); + +/* + * GTE event retrieval function + * + * Parameters: + * + * Input: + * @desc: This parameter should be the same as returned from register + * + * Output: + * @hts: hts event details + * + * Returns: + * Returns 0 for success and any other value for the failure + * + * Note: API is not stable and subject to change. + */ +int tegra_gte_retrieve_event(const struct tegra_gte_ev_desc *desc, + struct tegra_gte_ev_detail *hts); + +#else /* ! CONFIG_TEGRA_HTS_GTE */ +static inline struct tegra_gte_ev_desc *tegra_gte_register_event( + struct device_node *np, u32 ev_id) +{ + return ERR_PTR(-ENOSYS); +} + +static inline int tegra_gte_unregister_event(struct tegra_gte_ev_desc *desc) +{ + return -ENOSYS; +} + +static inline int tegra_gte_retrieve_event(const struct tegra_gte_ev_desc *desc, + struct tegra_gte_ev_detail *hts) +{ + return -ENOSYS; +} + +#endif /* ! CONFIG_TEGRA_HTS_GTE */ +#endif diff --git a/include/uapi/linux/nvpps_ioctl.h b/include/uapi/linux/nvpps_ioctl.h new file mode 100644 index 00000000..c85dcf1c --- /dev/null +++ b/include/uapi/linux/nvpps_ioctl.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#ifndef __UAPI_NVPPS_IOCTL_H__ +#define __UAPI_NVPPS_IOCTL_H__ + +#include +#include + +struct nvpps_version { + struct _version { + __u32 major; + __u32 minor; + } version; + struct _api { + __u32 major; + __u32 minor; + } api; +}; + +#define NVPPS_VERSION_MAJOR 0 +#define NVPPS_VERSION_MINOR 2 +#define NVPPS_API_MAJOR 0 +#define NVPPS_API_MINOR 4 + +struct nvpps_params { + __u32 evt_mode; + __u32 tsc_mode; +}; + + +/* evt_mode */ +#define NVPPS_MODE_GPIO 0x01 +#define NVPPS_MODE_TIMER 0x02 + +/* tsc_mode */ +#define NVPPS_TSC_NSEC 0 +#define NVPPS_TSC_COUNTER 1 + + +struct nvpps_timeevent { + __u32 evt_nb; + __u64 tsc; + __u64 ptp; + __u64 secondary_ptp; + __u64 tsc_res_ns; + __u32 evt_mode; + __u32 tsc_mode; + __u64 irq_latency; +}; + +#ifndef _LINUX_TIME64_H +typedef __s64 time64_t; +typedef __u64 timeu64_t; + +struct timespec64 { + time64_t tv_sec; /* seconds */ + long tv_nsec; /* nanoseconds */ +}; +#endif + +struct nvpps_timestamp_struct { + clockid_t clockid; + struct timespec64 kernel_ts; + struct timespec64 hw_ptp_ts; + __u64 extra[2]; +}; + + +#define NVPPS_GETVERSION _IOR('p', 0x1, struct nvpps_version *) +#define NVPPS_GETPARAMS _IOR('p', 0x2, struct nvpps_params *) +#define NVPPS_SETPARAMS _IOW('p', 0x3, struct nvpps_params *) +#define NVPPS_GETEVENT _IOR('p', 0x4, struct nvpps_timeevent *) +#define NVPPS_GETTIMESTAMP _IOWR('p', 0x5, struct nvpps_timestamp_struct *) + +#endif /* __UAPI_NVPPS_IOCTL_H__ */