summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcomponents/drivers/Kconfig1
-rwxr-xr-xcomponents/drivers/include/drivers/ptp.h201
-rw-r--r--components/drivers/include/rtdevice.h4
-rwxr-xr-xcomponents/drivers/ptp/Kconfig8
-rwxr-xr-xcomponents/drivers/ptp/SConscript15
-rwxr-xr-xcomponents/drivers/ptp/ptp.c650
-rw-r--r--components/libc/compilers/common/ctime.c156
-rw-r--r--components/libc/compilers/common/include/sys/time.h8
-rw-r--r--documentation/6.components/device-driver/INDEX.md1
-rw-r--r--documentation/6.components/device-driver/ptp/ptp.md395
10 files changed, 1439 insertions, 0 deletions
diff --git a/components/drivers/Kconfig b/components/drivers/Kconfig
index efe7431a55..db0334f42b 100755
--- a/components/drivers/Kconfig
+++ b/components/drivers/Kconfig
@@ -21,6 +21,7 @@ rsource "touch/Kconfig"
rsource "graphic/Kconfig"
rsource "hwcrypto/Kconfig"
rsource "wlan/Kconfig"
+rsource "ptp/Kconfig"
rsource "led/Kconfig"
rsource "input/Kconfig"
rsource "mailbox/Kconfig"
diff --git a/components/drivers/include/drivers/ptp.h b/components/drivers/include/drivers/ptp.h
new file mode 100755
index 0000000000..f12c2fea27
--- /dev/null
+++ b/components/drivers/include/drivers/ptp.h
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2023-04-23 GuEe-GUI first version
+ */
+
+#ifndef __PTP_H__
+#define __PTP_H__
+
+#include <rthw.h>
+#include <rtthread.h>
+#include <drivers/misc.h>
+
+struct rt_ptp_pin;
+struct rt_ptp_ops;
+
+struct rt_ptp_clock
+{
+ struct rt_device parent;
+
+ const struct rt_ptp_ops *ops;
+
+ /* Maximum frequency adjustment in ppb (parts per billion), required when adjfreq is implemented */
+ rt_int32_t max_freq;
+ rt_int32_t alarm_nr;
+ rt_int32_t pins_nr;
+ rt_int32_t extts_nr;
+ rt_int32_t perout_nr;
+ rt_int32_t pps;
+
+ rt_list_t notifier_nodes;
+ struct rt_spinlock nodes_lock;
+
+ struct rt_ptp_pin *pins;
+ struct rt_mutex pin_mutex;
+};
+#define rt_device_to_ptp_clock(dev) rt_container_of(dev, struct rt_ptp_clock, parent)
+
+struct rt_ptp_clock_time
+{
+ rt_int64_t sec;
+ rt_int32_t nsec;
+};
+
+enum
+{
+ PTP_CLOCK_EV_ALARM,
+ PTP_CLOCK_EV_EXTTS,
+ PTP_CLOCK_EV_EXTOFF,
+ PTP_CLOCK_EV_PPS,
+ PTP_CLOCK_EV_PPSUSR,
+
+ PTP_CLOCK_EV_MAX,
+};
+
+struct rt_ptp_clock_event
+{
+ rt_uint32_t type;
+ rt_uint32_t index;
+
+ union
+ {
+ rt_uint64_t timestamp;
+ rt_int64_t offset;
+ struct rt_ptp_clock_time pps_times;
+ };
+};
+
+struct rt_ptp_clock_notifier
+{
+ rt_list_t list;
+ rt_uint32_t event_mask;
+
+ struct rt_ptp_clock *ptp;
+ void (*callback)(struct rt_ptp_clock_notifier *notifier, struct rt_ptp_clock_event *ev);
+
+ void *priv;
+};
+
+enum
+{
+ PTP_PIN_FUNC_NONE,
+ PTP_PIN_FUNC_EXTTS,
+ PTP_PIN_FUNC_PEROUT,
+ PTP_PIN_FUNC_PHYSYNC,
+
+ PTP_PIN_FUNC_MAX,
+};
+
+struct rt_ptp_pin
+{
+ char name[64];
+
+ rt_uint32_t func;
+ rt_uint32_t chan;
+};
+
+struct rt_ptp_request_extts
+{
+ rt_uint32_t chan;
+
+#define PTP_ENABLE_FEATURE RT_BIT(0)
+#define PTP_RISING_EDGE RT_BIT(1)
+#define PTP_FALLING_EDGE RT_BIT(2)
+#define PTP_STRICT_FLAGS RT_BIT(3)
+#define PTP_EXTTS_EDGES (PTP_RISING_EDGE | PTP_FALLING_EDGE)
+ rt_uint32_t flags;
+};
+
+struct rt_ptp_request_perout
+{
+ rt_uint32_t chan;
+
+#define PTP_PEROUT_ONE_SHOT RT_BIT(0)
+#define PTP_PEROUT_DUTY_CYCLE RT_BIT(1)
+#define PTP_PEROUT_PHASE RT_BIT(2)
+ rt_uint32_t flags;
+
+ /* Desired period, zero means disable */
+ struct rt_ptp_clock_time period;
+ /* The "on" time of the signal, must be lower than the period */
+ struct rt_ptp_clock_time on;
+ /* Absolute start time or phase offset */
+ struct rt_ptp_clock_time start_phase;
+};
+
+enum
+{
+ PTP_CLK_REQ_EXTTS,
+ PTP_CLK_REQ_PEROUT,
+ PTP_CLK_REQ_PPS,
+
+ PTP_CLK_REQ_MAX,
+};
+
+struct rt_ptp_clock_request
+{
+ rt_uint32_t type;
+
+ union
+ {
+ struct rt_ptp_request_extts extts;
+ struct rt_ptp_request_perout perout;
+ };
+};
+
+struct rt_ptp_ops
+{
+ rt_err_t (*adjfreq)(struct rt_ptp_clock *ptp, rt_base_t freq);
+ rt_err_t (*adjphase)(struct rt_ptp_clock *ptp, rt_int32_t phase);
+ rt_err_t (*getmaxphase)(struct rt_ptp_clock *ptp, rt_int32_t *out_maxphase);
+ rt_err_t (*adjtime)(struct rt_ptp_clock *ptp, rt_int64_t delta);
+ rt_err_t (*gettime)(struct rt_ptp_clock *ptp, struct rt_ptp_clock_time *ts);
+ rt_err_t (*settime)(struct rt_ptp_clock *ptp, const struct rt_ptp_clock_time *ts);
+ rt_err_t (*enable)(struct rt_ptp_clock *ptp, struct rt_ptp_clock_request *request, rt_bool_t on);
+ rt_err_t (*verify)(struct rt_ptp_clock *ptp, rt_uint32_t pin, rt_uint32_t func, rt_uint32_t chan);
+ rt_err_t (*getsnapshot)(struct rt_ptp_clock *ptp, struct rt_ptp_clock_time *ts);
+};
+
+rt_err_t rt_ptp_clock_register(struct rt_ptp_clock *ptp);
+rt_err_t rt_ptp_clock_unregister(struct rt_ptp_clock *ptp);
+
+rt_err_t rt_ptp_clock_notifier_register(struct rt_ptp_clock *ptp,
+ struct rt_ptp_clock_notifier *notifier);
+rt_err_t rt_ptp_clock_notifier_unregister(struct rt_ptp_clock *ptp,
+ struct rt_ptp_clock_notifier *notifier);
+
+void rt_ptp_clock_event(struct rt_ptp_clock *ptp, struct rt_ptp_clock_event *ev);
+
+rt_err_t rt_ptp_adjfreq(struct rt_ptp_clock *ptp, rt_base_t freq);
+
+rt_err_t rt_ptp_adjphase(struct rt_ptp_clock *ptp, rt_int32_t phase);
+rt_err_t rt_ptp_getmaxphase(struct rt_ptp_clock *ptp, rt_int32_t *out_maxphase);
+
+rt_err_t rt_ptp_adjtime(struct rt_ptp_clock *ptp, struct rt_ptp_clock_time *ts);
+
+rt_err_t rt_ptp_gettime(struct rt_ptp_clock *ptp, struct rt_ptp_clock_time *ts);
+rt_err_t rt_ptp_settime(struct rt_ptp_clock *ptp, const struct rt_ptp_clock_time *ts);
+
+rt_err_t rt_ptp_request_extts(struct rt_ptp_clock *ptp,
+ struct rt_ptp_request_extts *extts);
+rt_err_t rt_ptp_request_perout(struct rt_ptp_clock *ptp,
+ struct rt_ptp_request_perout *perout);
+rt_err_t rt_ptp_enable_pps(struct rt_ptp_clock *ptp, rt_bool_t on);
+
+rt_err_t rt_ptp_get_pin_func(struct rt_ptp_clock *ptp,
+ rt_uint32_t pin, struct rt_ptp_pin *out_pin);
+/*
+ * Assign pin function only updates pin routing metadata.
+ * Call rt_ptp_request_extts() or rt_ptp_request_perout() separately to enable hardware.
+ */
+rt_err_t rt_ptp_set_pin_func(struct rt_ptp_clock *ptp,
+ rt_uint32_t pin, rt_uint32_t func, rt_uint32_t chan);
+
+rt_err_t rt_ptp_getsnapshot(struct rt_ptp_clock *ptp, struct rt_ptp_clock_time *ts);
+
+#endif /* __PTP_H__ */
diff --git a/components/drivers/include/rtdevice.h b/components/drivers/include/rtdevice.h
index b416b5e928..e6eb43bc57 100644
--- a/components/drivers/include/rtdevice.h
+++ b/components/drivers/include/rtdevice.h
@@ -64,6 +64,10 @@ extern "C" {
#include "drivers/led.h"
#endif /* RT_USING_LED */
+#ifdef RT_USING_PTP
+#include "drivers/ptp.h"
+#endif /* RT_USING_PTP */
+
#ifdef RT_USING_INPUT
#include "drivers/input.h"
#ifdef RT_INPUT_UAPI
diff --git a/components/drivers/ptp/Kconfig b/components/drivers/ptp/Kconfig
new file mode 100755
index 0000000000..b07d9ae686
--- /dev/null
+++ b/components/drivers/ptp/Kconfig
@@ -0,0 +1,8 @@
+menuconfig RT_USING_PTP
+ bool "Using Precision Time Protocol (PTP) IEEE 1588 clock support"
+ depends on RT_USING_DM
+ default n
+
+if RT_USING_PTP
+ osource "$(SOC_DM_PTP_DIR)/Kconfig"
+endif
diff --git a/components/drivers/ptp/SConscript b/components/drivers/ptp/SConscript
new file mode 100755
index 0000000000..fb10e8a905
--- /dev/null
+++ b/components/drivers/ptp/SConscript
@@ -0,0 +1,15 @@
+from building import *
+
+group = []
+
+if not GetDepend(['RT_USING_PTP']):
+ Return('group')
+
+cwd = GetCurrentDir()
+CPPPATH = [cwd + '/../include']
+
+src = ['ptp.c']
+
+group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
+
+Return('group')
diff --git a/components/drivers/ptp/ptp.c b/components/drivers/ptp/ptp.c
new file mode 100755
index 0000000000..bde7a50186
--- /dev/null
+++ b/components/drivers/ptp/ptp.c
@@ -0,0 +1,650 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2023-04-23 GuEe-GUI first version
+ */
+
+#include <rthw.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#define DBG_TAG "rtdm.ptp"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+#define NSEC_PER_SEC 1000000000L
+
+static struct rt_dm_ida ptp_ida = RT_DM_IDA_INIT(PTP);
+
+#ifdef RT_USING_DEVICE_OPS
+const static struct rt_device_ops _ptp_ops =
+{
+};
+#endif
+
+rt_err_t rt_ptp_clock_register(struct rt_ptp_clock *ptp)
+{
+ rt_err_t err;
+ int device_id;
+ const char *dev_name;
+ char name[RT_NAME_MAX];
+
+ if (!ptp || !ptp->ops)
+ {
+ return -RT_EINVAL;
+ }
+
+ if ((device_id = rt_dm_ida_alloc(&ptp_ida)) < 0)
+ {
+ return -RT_EFULL;
+ }
+
+ rt_dm_dev_set_name(&ptp->parent, "ptp%u", device_id);
+ dev_name = rt_dm_dev_get_name(&ptp->parent);
+
+ if (ptp->pins_nr)
+ {
+ if (!ptp->pins)
+ {
+ LOG_E("%s: found %d pin but pins is NULL", dev_name, ptp->pins_nr);
+ err = -RT_EINVAL;
+ goto _fail;
+ }
+
+ if (!ptp->ops->verify)
+ {
+ LOG_E("%s: found %d pin but verify is not supported", dev_name, ptp->pins_nr);
+ err = -RT_EINVAL;
+ goto _fail;
+ }
+
+ rt_snprintf(name, sizeof(name), "%s-pin", dev_name);
+ rt_mutex_init(&ptp->pin_mutex, name, RT_IPC_FLAG_PRIO);
+ }
+
+ if ((ptp->perout_nr || ptp->extts_nr) && !ptp->ops->enable)
+ {
+ LOG_E("%s: perout(%u) or extts(%u) but enable is not supported",
+ dev_name, ptp->perout_nr, ptp->extts_nr);
+ err = -RT_EINVAL;
+ goto _free_mutex;
+ }
+
+ if (ptp->ops->adjfreq && ptp->max_freq <= 0)
+ {
+ LOG_E("%s: adjfreq is supported but max_freq is not set", dev_name);
+ err = -RT_EINVAL;
+ goto _free_mutex;
+ }
+
+ rt_list_init(&ptp->notifier_nodes);
+ rt_spin_lock_init(&ptp->nodes_lock);
+
+ /* Just make a search interface */
+ ptp->parent.type = RT_Device_Class_Char;
+#ifdef RT_USING_DEVICE_OPS
+ ptp->parent.ops = ptp->parent.ops ? : &_ptp_ops;
+#endif
+ ptp->parent.master_id = ptp_ida.master_id;
+ ptp->parent.device_id = device_id;
+
+ if ((err = rt_device_register(&ptp->parent, dev_name, RT_DEVICE_FLAG_DEACTIVATE)))
+ {
+ goto _free_mutex;
+ }
+
+ return RT_EOK;
+
+_free_mutex:
+ if (ptp->pins_nr)
+ {
+ rt_mutex_detach(&ptp->pin_mutex);
+ }
+
+_fail:
+ rt_dm_ida_free(&ptp_ida, device_id);
+
+ return err;
+}
+
+rt_err_t rt_ptp_clock_unregister(struct rt_ptp_clock *ptp)
+{
+ struct rt_ptp_request_perout perout;
+ struct rt_ptp_request_extts extts;
+
+ if (!ptp)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (ptp->parent.ref_count)
+ {
+ LOG_E("%s: there is %u user",
+ rt_dm_dev_get_name(&ptp->parent), ptp->parent.ref_count);
+ return -RT_EINVAL;
+ }
+
+ if (!rt_list_isempty(&ptp->notifier_nodes))
+ {
+ LOG_W("%s: notifier not unregister", rt_dm_dev_get_name(&ptp->parent));
+ }
+
+ rt_memset(&extts, 0, sizeof(extts));
+
+ for (int i = 0; i < ptp->extts_nr; ++i)
+ {
+ extts.chan = i;
+ rt_ptp_request_extts(ptp, &extts);
+ }
+
+ rt_memset(&perout, 0, sizeof(perout));
+ perout.flags = PTP_PEROUT_ONE_SHOT;
+
+ for (int i = 0; i < ptp->perout_nr; ++i)
+ {
+ perout.chan = i;
+ rt_ptp_request_perout(ptp, &perout);
+ }
+
+ rt_ptp_enable_pps(ptp, RT_FALSE);
+
+ if (ptp->pins_nr)
+ {
+ rt_mutex_detach(&ptp->pin_mutex);
+ }
+
+ rt_dm_ida_free(&ptp_ida, ptp->parent.device_id);
+
+ return rt_device_unregister(&ptp->parent);
+}
+
+rt_err_t rt_ptp_clock_notifier_register(struct rt_ptp_clock *ptp,
+ struct rt_ptp_clock_notifier *notifier)
+{
+ if (!ptp || !notifier)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (!notifier->event_mask)
+ {
+ LOG_E("%s: clock notifier event mask is empty, try to set 0~%u",
+ rt_dm_dev_get_name(&ptp->parent), PTP_CLOCK_EV_MAX - 1);
+ return -RT_EINVAL;
+ }
+
+ notifier->ptp = ptp;
+ rt_list_init(&notifier->list);
+
+ rt_spin_lock(&ptp->nodes_lock);
+ rt_list_insert_after(&ptp->notifier_nodes, &notifier->list);
+ rt_spin_unlock(&ptp->nodes_lock);
+
+ return RT_EOK;
+}
+
+rt_err_t rt_ptp_clock_notifier_unregister(struct rt_ptp_clock *ptp,
+ struct rt_ptp_clock_notifier *notifier)
+{
+ if (!ptp || !notifier)
+ {
+ return -RT_EINVAL;
+ }
+
+ rt_spin_lock(&ptp->nodes_lock);
+ rt_list_remove(&notifier->list);
+ rt_spin_unlock(&ptp->nodes_lock);
+
+ return RT_EOK;
+}
+
+void rt_ptp_clock_event(struct rt_ptp_clock *ptp, struct rt_ptp_clock_event *ev)
+{
+ struct rt_ptp_clock_notifier *notifier, *next_notifier;
+
+ RT_ASSERT(ptp != RT_NULL);
+ RT_ASSERT(ev->type < PTP_CLOCK_EV_MAX);
+
+ rt_spin_lock(&ptp->nodes_lock);
+
+ rt_list_for_each_entry_safe(notifier, next_notifier, &ptp->notifier_nodes, list)
+ {
+ if (notifier->event_mask & RT_BIT(ev->type))
+ {
+ rt_spin_unlock(&ptp->nodes_lock);
+
+ notifier->callback(notifier, ev);
+
+ rt_spin_lock(&ptp->nodes_lock);
+ }
+ }
+
+ rt_spin_unlock(&ptp->nodes_lock);
+}
+
+rt_err_t rt_ptp_adjfreq(struct rt_ptp_clock *ptp, rt_base_t freq)
+{
+ if (!ptp)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (!ptp->ops->adjfreq)
+ {
+ return -RT_ENOSYS;
+ }
+
+ if (ptp->max_freq <= 0)
+ {
+ if (freq != 0)
+ {
+ return -RT_EINVAL;
+ }
+
+ return RT_EOK;
+ }
+
+ if (freq > ptp->max_freq || freq < -ptp->max_freq)
+ {
+ return -RT_EINVAL;
+ }
+
+ return ptp->ops->adjfreq(ptp, freq);
+}
+
+rt_err_t rt_ptp_adjphase(struct rt_ptp_clock *ptp, rt_int32_t phase)
+{
+ rt_err_t err;
+ rt_int32_t maxphase;
+
+ if (!ptp)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (!ptp->ops->adjphase)
+ {
+ return -RT_ENOSYS;
+ }
+
+ if ((err = rt_ptp_getmaxphase(ptp, &maxphase)))
+ {
+ return err;
+ }
+
+ if (phase > maxphase || phase < -maxphase)
+ {
+ return -RT_EINVAL;
+ }
+
+ return ptp->ops->adjphase(ptp, phase);
+}
+
+rt_err_t rt_ptp_getmaxphase(struct rt_ptp_clock *ptp, rt_int32_t *out_maxphase)
+{
+ if (!ptp || !out_maxphase)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (!ptp->ops->getmaxphase)
+ {
+ return -RT_ENOSYS;
+ }
+
+ return ptp->ops->getmaxphase(ptp, out_maxphase);
+}
+
+rt_err_t rt_ptp_adjtime(struct rt_ptp_clock *ptp, struct rt_ptp_clock_time *ts)
+{
+ rt_int64_t delta;
+
+ if (!ptp || !ts)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (!ptp->ops->adjtime)
+ {
+ return -RT_ENOSYS;
+ }
+
+ if (ts->nsec < 0 || (rt_int64_t)ts->nsec >= NSEC_PER_SEC)
+ {
+ return -RT_EINVAL;
+ }
+
+ delta = ts->sec * NSEC_PER_SEC + (rt_int64_t)ts->nsec;
+
+ return ptp->ops->adjtime(ptp, delta);
+}
+
+rt_err_t rt_ptp_gettime(struct rt_ptp_clock *ptp, struct rt_ptp_clock_time *ts)
+{
+ if (!ptp || !ts)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (!ptp->ops->gettime)
+ {
+ return -RT_ENOSYS;
+ }
+
+ return ptp->ops->gettime(ptp, ts);
+}
+
+rt_err_t rt_ptp_settime(struct rt_ptp_clock *ptp, const struct rt_ptp_clock_time *ts)
+{
+ if (!ptp || !ts)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (!ptp->ops->settime)
+ {
+ return -RT_ENOSYS;
+ }
+
+ return ptp->ops->settime(ptp, ts);
+}
+
+rt_err_t rt_ptp_request_extts(struct rt_ptp_clock *ptp,
+ struct rt_ptp_request_extts *extts)
+{
+ rt_bool_t enable;
+ struct rt_ptp_clock_request request;
+
+ if (!ptp || !extts)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (extts->chan >= ptp->extts_nr)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (extts->flags & PTP_STRICT_FLAGS)
+ {
+ /* Ensure one of the rising/falling edge bits is set */
+ if ((extts->flags & PTP_ENABLE_FEATURE) &&
+ (extts->flags & PTP_EXTTS_EDGES) == 0)
+ {
+ return -RT_EINVAL;
+ }
+ }
+
+ if (!ptp->ops->enable)
+ {
+ return -RT_ENOSYS;
+ }
+
+ request.type = PTP_CLK_REQ_EXTTS;
+ rt_memcpy(&request.extts, extts, sizeof(*extts));
+ enable = !!(extts->flags & PTP_ENABLE_FEATURE);
+
+ return ptp->ops->enable(ptp, &request, enable);
+}
+
+rt_err_t rt_ptp_request_perout(struct rt_ptp_clock *ptp,
+ struct rt_ptp_request_perout *perout)
+{
+ rt_bool_t enable;
+ struct rt_ptp_clock_request request;
+
+ if (!ptp || !perout)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (perout->chan >= ptp->perout_nr)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (perout->flags & PTP_STRICT_FLAGS)
+ {
+ if (perout->flags & PTP_PEROUT_DUTY_CYCLE)
+ {
+ /* The duty cycle must be subunitary */
+ if (perout->on.sec > perout->period.sec ||
+ (perout->on.sec == perout->period.sec &&
+ perout->on.nsec > perout->period.nsec))
+ {
+ return -RT_EINVAL;
+ }
+ }
+
+ if (perout->flags & PTP_PEROUT_PHASE)
+ {
+ /*
+ * The phase should be specified modulo the period,
+ * therefore anything equal or larger than 1 period is invalid
+ */
+ if (perout->start_phase.sec > perout->period.sec ||
+ (perout->start_phase.sec == perout->period.sec &&
+ perout->start_phase.nsec >= perout->period.nsec))
+ {
+ return -RT_EINVAL;
+ }
+ }
+ }
+
+ if (!ptp->ops->enable)
+ {
+ return -RT_ENOSYS;
+ }
+
+ request.type = PTP_CLK_REQ_PEROUT;
+ rt_memcpy(&request.perout, perout, sizeof(*perout));
+ enable = perout->period.sec || perout->period.nsec;
+
+ return ptp->ops->enable(ptp, &request, enable);
+}
+
+rt_err_t rt_ptp_enable_pps(struct rt_ptp_clock *ptp, rt_bool_t on)
+{
+ struct rt_ptp_clock_request request;
+
+ if (!ptp)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (!ptp->ops->enable)
+ {
+ return -RT_ENOSYS;
+ }
+
+ request.type = PTP_CLK_REQ_PPS;
+
+ return ptp->ops->enable(ptp, &request, on);
+}
+
+rt_err_t rt_ptp_get_pin_func(struct rt_ptp_clock *ptp,
+ rt_uint32_t pin, struct rt_ptp_pin *out_pin)
+{
+ if (!ptp || !out_pin)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (pin >= ptp->pins_nr)
+ {
+ if (!ptp->pins_nr)
+ {
+ return -RT_ENOSYS;
+ }
+
+ return -RT_EINVAL;
+ }
+
+ rt_mutex_take(&ptp->pin_mutex, RT_WAITING_FOREVER);
+
+ rt_memcpy(out_pin, &ptp->pins[pin], sizeof(*out_pin));
+
+ rt_mutex_release(&ptp->pin_mutex);
+
+ return RT_EOK;
+}
+
+static rt_err_t ptp_disable_pin_func(struct rt_ptp_clock *ptp,
+ rt_uint32_t func, rt_uint32_t chan)
+{
+ rt_err_t err = RT_EOK;
+ struct rt_ptp_request_extts extts;
+ struct rt_ptp_request_perout perout;
+
+ switch (func)
+ {
+ case PTP_PIN_FUNC_NONE:
+ case PTP_PIN_FUNC_PHYSYNC:
+ break;
+
+ case PTP_PIN_FUNC_EXTTS:
+ rt_memset(&extts, 0, sizeof(extts));
+ extts.chan = chan;
+ err = rt_ptp_request_extts(ptp, &extts);
+ break;
+
+ case PTP_PIN_FUNC_PEROUT:
+ rt_memset(&perout, 0, sizeof(perout));
+ perout.chan = chan;
+ err = rt_ptp_request_perout(ptp, &perout);
+ break;
+
+ default:
+ err = -RT_EINVAL;
+ break;
+ }
+
+ return err;
+}
+
+static rt_err_t ptp_set_pin_func(struct rt_ptp_clock *ptp,
+ rt_uint32_t pin, rt_uint32_t func, rt_uint32_t chan)
+{
+ int i;
+ rt_err_t err;
+ struct rt_ptp_pin *pin1 = RT_NULL, *pin2 = &ptp->pins[pin];
+
+ for (i = 0; i < ptp->pins_nr; ++i)
+ {
+ if (ptp->pins[i].func == func && ptp->pins[i].chan == chan)
+ {
+ pin1 = &ptp->pins[i];
+ break;
+ }
+ }
+
+ /* Configure already, do nothing */
+ if (pin1 && i == pin)
+ {
+ return RT_EOK;
+ }
+
+ switch (func)
+ {
+ case PTP_PIN_FUNC_NONE:
+ break;
+
+ case PTP_PIN_FUNC_EXTTS:
+ if (chan >= ptp->extts_nr)
+ {
+ return -RT_EINVAL;
+ }
+ break;
+
+ case PTP_PIN_FUNC_PEROUT:
+ if (chan >= ptp->perout_nr)
+ {
+ return -RT_EINVAL;
+ }
+ break;
+
+ case PTP_PIN_FUNC_PHYSYNC:
+ if (chan != 0)
+ {
+ return -RT_EINVAL;
+ }
+ break;
+
+ default:
+ return -RT_EINVAL;
+ }
+
+ if ((err = ptp->ops->verify(ptp, pin, func, chan)))
+ {
+ LOG_E("%s: pin %u function %u channel %u error = %s",
+ rt_dm_dev_get_name(&ptp->parent), pin, func, chan, rt_strerror(err));
+ return err;
+ }
+
+ /* Disable configure before, update new configure */
+ if (pin1)
+ {
+ ptp_disable_pin_func(ptp, func, chan);
+ pin1->func = PTP_PIN_FUNC_NONE;
+ pin1->chan = 0;
+ }
+
+ ptp_disable_pin_func(ptp, pin2->func, pin2->chan);
+ pin2->func = func;
+ pin2->chan = chan;
+
+ return RT_EOK;
+}
+
+rt_err_t rt_ptp_set_pin_func(struct rt_ptp_clock *ptp,
+ rt_uint32_t pin, rt_uint32_t func, rt_uint32_t chan)
+{
+ rt_err_t err;
+
+ if (!ptp)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (pin >= ptp->pins_nr)
+ {
+ if (!ptp->pins_nr)
+ {
+ return -RT_ENOSYS;
+ }
+
+ return -RT_EINVAL;
+ }
+
+ if (func >= PTP_PIN_FUNC_MAX)
+ {
+ return -RT_EINVAL;
+ }
+
+ rt_mutex_take(&ptp->pin_mutex, RT_WAITING_FOREVER);
+
+ err = ptp_set_pin_func(ptp, pin, func, chan);
+
+ rt_mutex_release(&ptp->pin_mutex);
+
+ return err;
+}
+
+rt_err_t rt_ptp_getsnapshot(struct rt_ptp_clock *ptp, struct rt_ptp_clock_time *ts)
+{
+ if (!ptp || !ts)
+ {
+ return -RT_EINVAL;
+ }
+
+ if (!ptp->ops->getsnapshot)
+ {
+ return -RT_ENOSYS;
+ }
+
+ return ptp->ops->getsnapshot(ptp, ts);
+}
diff --git a/components/libc/compilers/common/ctime.c b/components/libc/compilers/common/ctime.c
index 3e1ea77fd7..60d5dc74a4 100644
--- a/components/libc/compilers/common/ctime.c
+++ b/components/libc/compilers/common/ctime.c
@@ -45,6 +45,13 @@
#ifdef RT_USING_CLOCK_TIME
#include <drivers/clock_time.h>
#endif
+#ifdef RT_USING_PTP
+#include <drivers/ptp.h>
+#ifdef RT_USING_POSIX_FS
+#include <dfs.h>
+#include <dfs_file.h>
+#endif /* RT_USING_POSIX_FS */
+#endif /* RT_USING_PTP */
#define DBG_TAG "time"
#define DBG_LVL DBG_INFO
@@ -611,6 +618,134 @@ RTM_EXPORT(nanosleep);
#if defined(RT_USING_POSIX_CLOCK) && defined(RT_USING_CLOCK_TIME)
+#ifdef RT_USING_PTP
+
+static rt_bool_t _clockid_is_ptp_fd(clockid_t clockid)
+{
+ return (clockid & 7) == CLOCKFD;
+}
+
+static struct rt_ptp_clock *_ptp_clock_from_clockid(clockid_t clockid)
+{
+ unsigned int fd;
+ struct rt_ptp_clock *ptp;
+ rt_device_t dev;
+
+#ifndef RT_USING_POSIX_FS
+ RT_UNUSED(fd);
+ RT_UNUSED(ptp);
+ RT_UNUSED(dev);
+ return RT_NULL;
+#else
+ struct dfs_file *file;
+
+ if (!_clockid_is_ptp_fd(clockid))
+ {
+ return RT_NULL;
+ }
+
+ fd = CLOCKID_TO_FD(clockid);
+ file = fd_get((int)fd);
+ if (!file || !file->vnode || !file->vnode->data)
+ {
+ return RT_NULL;
+ }
+
+ dev = (rt_device_t)file->vnode->data;
+ if (dev->type != RT_Device_Class_Char)
+ {
+ return RT_NULL;
+ }
+
+ if (rt_strncmp(rt_dm_dev_get_name(dev), "ptp", 3) != 0)
+ {
+ return RT_NULL;
+ }
+
+ ptp = rt_device_to_ptp_clock(dev);
+ if (!ptp->ops || !ptp->ops->gettime)
+ {
+ return RT_NULL;
+ }
+
+ return ptp;
+#endif /* RT_USING_POSIX_FS */
+}
+
+static int _clock_gettime_ptp(clockid_t clockid, struct timespec *tp)
+{
+ struct rt_ptp_clock *ptp;
+ struct rt_ptp_clock_time ts;
+ rt_err_t err;
+
+ ptp = _ptp_clock_from_clockid(clockid);
+ if (!ptp)
+ {
+ rt_set_errno(EINVAL);
+ return -1;
+ }
+
+ err = rt_ptp_gettime(ptp, &ts);
+ if (err != RT_EOK)
+ {
+ rt_set_errno(EINVAL);
+ return -1;
+ }
+
+ tp->tv_sec = (time_t)ts.sec;
+ tp->tv_nsec = ts.nsec;
+
+ return 0;
+}
+
+static int _clock_settime_ptp(clockid_t clockid, const struct timespec *tp)
+{
+ struct rt_ptp_clock *ptp;
+ struct rt_ptp_clock_time ts;
+ rt_err_t err;
+
+ ptp = _ptp_clock_from_clockid(clockid);
+ if (!ptp)
+ {
+ rt_set_errno(EINVAL);
+ return -1;
+ }
+
+ if (!ptp->ops->settime)
+ {
+ rt_set_errno(EPERM);
+ return -1;
+ }
+
+ ts.sec = tp->tv_sec;
+ ts.nsec = (rt_int32_t)tp->tv_nsec;
+
+ err = rt_ptp_settime(ptp, &ts);
+ if (err != RT_EOK)
+ {
+ rt_set_errno(EINVAL);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int _clock_getres_ptp(clockid_t clockid, struct timespec *res)
+{
+ if (!_ptp_clock_from_clockid(clockid))
+ {
+ rt_set_errno(EINVAL);
+ return -1;
+ }
+
+ res->tv_sec = 0;
+ res->tv_nsec = 1;
+
+ return 0;
+}
+
+#endif /* RT_USING_PTP */
+
int clock_getres(clockid_t clockid, struct timespec *res)
{
if (res == RT_NULL)
@@ -619,6 +754,13 @@ int clock_getres(clockid_t clockid, struct timespec *res)
return -1;
}
+#ifdef RT_USING_PTP
+ if (_clockid_is_ptp_fd(clockid))
+ {
+ return _clock_getres_ptp(clockid, res);
+ }
+#endif /* RT_USING_PTP */
+
switch (clockid)
{
case CLOCK_REALTIME: // use RTC
@@ -652,6 +794,13 @@ int clock_gettime(clockid_t clockid, struct timespec *tp)
return -1;
}
+#ifdef RT_USING_PTP
+ if (_clockid_is_ptp_fd(clockid))
+ {
+ return _clock_gettime_ptp(clockid, tp);
+ }
+#endif /* RT_USING_PTP */
+
switch (clockid)
{
case CLOCK_REALTIME: // use RTC
@@ -750,6 +899,13 @@ int clock_settime(clockid_t clockid, const struct timespec *tp)
return -1;
}
+#ifdef RT_USING_PTP
+ if (_clockid_is_ptp_fd(clockid))
+ {
+ return _clock_settime_ptp(clockid, tp);
+ }
+#endif /* RT_USING_PTP */
+
switch (clockid)
{
#ifdef RT_USING_RTC
diff --git a/components/libc/compilers/common/include/sys/time.h b/components/libc/compilers/common/include/sys/time.h
index 817fe78676..8804433eeb 100644
--- a/components/libc/compilers/common/include/sys/time.h
+++ b/components/libc/compilers/common/include/sys/time.h
@@ -198,6 +198,14 @@ int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
#define CLOCK_TAI 11 // newlib says they don't have this definition, make the compiler happy
#endif /* CLOCK_TAI */
+#ifdef RT_USING_PTP
+#ifndef CLOCKFD
+#define CLOCKFD 3
+#endif /* CLOCKFD */
+#define FD_TO_CLOCKID(fd) ((clockid_t)((~(clockid_t)(fd) << 3) | CLOCKFD))
+#define CLOCKID_TO_FD(clk) ((unsigned int)(~((unsigned int)(clk) >> 3)))
+#endif /* RT_USING_PTP */
+
#endif /* defined(RT_USING_POSIX_CLOCK) || defined (RT_USING_POSIX_TIMER) */
#ifdef RT_USING_POSIX_CLOCK
diff --git a/documentation/6.components/device-driver/INDEX.md b/documentation/6.components/device-driver/INDEX.md
index a2659b4449..56f005df1a 100644
--- a/documentation/6.components/device-driver/INDEX.md
+++ b/documentation/6.components/device-driver/INDEX.md
@@ -17,6 +17,7 @@
- @subpage page_device_rtc_dm
- @subpage page_device_clock_time
- @subpage page_device_hwtimer
+- @subpage page_device_ptp
- @subpage page_device_watchdog
- @subpage page_device_wlan
- @subpage page_device_sensor
diff --git a/documentation/6.components/device-driver/ptp/ptp.md b/documentation/6.components/device-driver/ptp/ptp.md
new file mode 100644
index 0000000000..f56ab3295d
--- /dev/null
+++ b/documentation/6.components/device-driver/ptp/ptp.md
@@ -0,0 +1,395 @@
+@page page_device_ptp PTP clock subsystem
+
+# PTP clock subsystem (API)
+
+Header: **`components/drivers/include/drivers/ptp.h`**. Core: **`components/drivers/ptp/ptp.c`**.
+
+The PTP layer wraps **IEEE 1588 hardware clocks** (PHC) behind a small, Linux-style **`rt_ptp_ops`** interface. Hardware drivers expose **read/set time**, **frequency and phase adjustment**, **external timestamp (EXTTS)**, **periodic output (PEROUT)**, **PPS**, optional **GPIO pin routing**, and **snapshot** reads. Upper layers (PTP protocol stacks, time sync services, test tools) call the **`rt_ptp_*`** helpers directly; the registered **`rt_device`** is mainly a **named handle** for lookup (`ptp0`, `ptp1`, …).
+
+Requires **`RT_USING_PTP`** (Kconfig) and **`RT_USING_DM`**.
+
+---
+
+## When to use this API
+
+| Use PTP when… | Prefer something else when… |
+| --- | --- |
+| The SoC or NIC provides a **dedicated PHC** with sub-microsecond discipline. | **Calendar time only** — @ref page_device_rtc or POSIX `time()`. |
+| You need **ppb frequency trim**, **phase step**, or **hardware timestamping** of external events. | **OS tick / hrtimer scheduling** — @ref page_device_hwtimer or @ref page_device_clock_time_core. |
+| Firmware or DT describes **EXTTS / PEROUT / PPS pins** on a PTP block. | **Software NTP only** — no PHC driver required. |
+
+---
+
+## Enable in menuconfig
+
+```text
+RT-Thread Components →
+ Device Drivers →
+ [*] Using Precision Time Protocol (PTP) IEEE 1588 clock support
+```
+
+SoC-specific options may be added under **`$(SOC_DM_PTP_DIR)/Kconfig`** when present.
+
+---
+
+## Roles
+
+| Role | Responsibility |
+| --- | --- |
+| **PHC driver** | Fills **`struct rt_ptp_clock`**, implements **`rt_ptp_ops`**, calls **`rt_ptp_clock_register`**. Reports capabilities (`max_freq`, `alarm_nr`, `extts_nr`, `perout_nr`, `pps`, `pins_nr`). Raises **`rt_ptp_clock_event`** from ISR or bottom-half when hardware events occur. |
+| **Client / stack** | Obtains **`struct rt_ptp_clock *`** (from probe, DT phandle, or **`rt_device_find("ptp0")`** + **`rt_device_to_ptp_clock`**). Uses **`rt_ptp_gettime` / `rt_ptp_adjfreq` / …** and optional **`rt_ptp_clock_notifier`** callbacks. |
+
+The core registers each clock as **`RT_Device_Class_Char`** with **`RT_DEVICE_FLAG_DEACTIVATE`**. There is **no** standard `read`/`write`/`control` path in **`ptp.c`** — all operations go through the **`rt_ptp_*`** API.
+
+---
+
+## Data structures
+
+### Time format
+
+```c
+struct rt_ptp_clock_time {
+ rt_int64_t sec;
+ rt_int32_t nsec; /* 0 .. 999_999_999 when passed through rt_ptp_adjtime */
+};
+```
+
+### Clock object
+
+```c
+struct rt_ptp_clock {
+ struct rt_device parent;
+ const struct rt_ptp_ops *ops;
+
+ rt_int32_t max_freq; /* ppb limit; required if ops->adjfreq is set */
+ rt_int32_t alarm_nr;
+ rt_int32_t pins_nr;
+ rt_int32_t extts_nr;
+ rt_int32_t perout_nr;
+ rt_int32_t pps;
+
+ rt_list_t notifier_nodes;
+ struct rt_spinlock nodes_lock;
+
+ struct rt_ptp_pin *pins; /* pins_nr entries when pins_nr > 0 */
+ struct rt_mutex pin_mutex;
+};
+```
+
+Convert from a generic device pointer:
+
+```c
+#define rt_device_to_ptp_clock(dev) rt_container_of(dev, struct rt_ptp_clock, parent)
+```
+
+### Driver operations
+
+```c
+struct rt_ptp_ops {
+ rt_err_t (*adjfreq)(struct rt_ptp_clock *ptp, rt_base_t freq);
+ rt_err_t (*adjphase)(struct rt_ptp_clock *ptp, rt_int32_t phase);
+ rt_err_t (*getmaxphase)(struct rt_ptp_clock *ptp, rt_int32_t *out_maxphase);
+ rt_err_t (*adjtime)(struct rt_ptp_clock *ptp, rt_int64_t delta);
+ rt_err_t (*gettime)(struct rt_ptp_clock *ptp, struct rt_ptp_clock_time *ts);
+ rt_err_t (*settime)(struct rt_ptp_clock *ptp, const struct rt_ptp_clock_time *ts);
+ rt_err_t (*enable)(struct rt_ptp_clock *ptp, struct rt_ptp_clock_request *request, rt_bool_t on);
+ rt_err_t (*verify)(struct rt_ptp_clock *ptp, rt_uint32_t pin, rt_uint32_t func, rt_uint32_t chan);
+ rt_err_t (*getsnapshot)(struct rt_ptp_clock *ptp, struct rt_ptp_clock_time *ts);
+};
+```
+
+| Callback | Purpose |
+| --- | --- |
+| **`adjfreq`** | Fine frequency adjustment in **parts per billion (ppb)**. Core clamps to **`±max_freq`**. |
+| **`adjphase`** | Phase shift in nanoseconds; core clamps using **`getmaxphase`**. |
+| **`getmaxphase`** | Reports maximum absolute phase adjustment. |
+| **`adjtime`** | Step the clock by **`delta`** nanoseconds (signed). |
+| **`gettime` / `settime`** | Read or write absolute PHC time. |
+| **`enable`** | Enable/disable **EXTTS**, **PEROUT**, or **PPS** (see **`struct rt_ptp_clock_request`**). |
+| **`verify`** | Validate pin/function/channel routing before metadata is updated. **Required** when **`pins_nr > 0`**. |
+| **`getsnapshot`** | Read a latched / snapshot time (hardware-specific). |
+
+Unimplemented ops return **`-RT_ENOSYS`** from the matching **`rt_ptp_*`** wrapper.
+
+---
+
+## Register and unregister
+
+```c
+rt_err_t rt_ptp_clock_register(struct rt_ptp_clock *ptp);
+rt_err_t rt_ptp_clock_unregister(struct rt_ptp_clock *ptp);
+```
+
+**`rt_ptp_clock_register`** checks:
+
+| Condition | Error |
+| --- | --- |
+| **`ptp` or `ops` is NULL** | **`-RT_EINVAL`** |
+| **`pins_nr > 0`** but **`pins` is NULL** or **`verify` is NULL** | **`-RT_EINVAL`** |
+| **`extts_nr` or `perout_nr` > 0** but **`enable` is NULL** | **`-RT_EINVAL`** |
+| **`adjfreq` is non-NULL** but **`max_freq <= 0`** | **`-RT_EINVAL`** |
+| IDA exhausted | **`-RT_EFULL`** |
+
+On success the device name is **`ptp<N>`** (for example **`ptp0`**).
+
+**`rt_ptp_clock_unregister`**:
+
+- Fails with **`-RT_EINVAL`** if **`ref_count != 0`**
+- Warns if notifiers are still registered
+- Disables all **EXTTS** channels, **PEROUT** channels, and **PPS**
+- Detaches **`pin_mutex`** when **`pins_nr > 0`**
+
+---
+
+## Time discipline API
+
+```c
+rt_err_t rt_ptp_adjfreq(struct rt_ptp_clock *ptp, rt_base_t freq);
+rt_err_t rt_ptp_adjphase(struct rt_ptp_clock *ptp, rt_int32_t phase);
+rt_err_t rt_ptp_getmaxphase(struct rt_ptp_clock *ptp, rt_int32_t *out_maxphase);
+rt_err_t rt_ptp_adjtime(struct rt_ptp_clock *ptp, struct rt_ptp_clock_time *ts);
+rt_err_t rt_ptp_gettime(struct rt_ptp_clock *ptp, struct rt_ptp_clock_time *ts);
+rt_err_t rt_ptp_settime(struct rt_ptp_clock *ptp, const struct rt_ptp_clock_time *ts);
+rt_err_t rt_ptp_getsnapshot(struct rt_ptp_clock *ptp, struct rt_ptp_clock_time *ts);
+```
+
+**`rt_ptp_adjtime`** converts **`ts->sec` / `ts->nsec`** to a signed nanosecond delta and forwards it to **`ops->adjtime`**. **`nsec`** must be in **`[0, 10⁹)`**.
+
+**`rt_ptp_adjfreq`**: if **`max_freq <= 0`**, only **`freq == 0`** is accepted (no-op success).
+
+---
+
+## EXTTS, PEROUT, and PPS
+
+### External timestamps (EXTTS)
+
+```c
+struct rt_ptp_request_extts {
+ rt_uint32_t chan;
+ rt_uint32_t flags; /* PTP_ENABLE_FEATURE, PTP_RISING_EDGE, PTP_FALLING_EDGE, PTP_STRICT_FLAGS */
+};
+
+rt_err_t rt_ptp_request_extts(struct rt_ptp_clock *ptp, struct rt_ptp_request_extts *extts);
+```
+
+- **`chan`** must be **`< extts_nr`**
+- With **`PTP_STRICT_FLAGS`**, enabling requires at least one of **`PTP_RISING_EDGE`** or **`PTP_FALLING_EDGE`**
+- Clearing **`PTP_ENABLE_FEATURE`** (or zero flags) disables the channel via **`enable(..., RT_FALSE)`**
+
+### Periodic output (PEROUT)
+
+```c
+struct rt_ptp_request_perout {
+ rt_uint32_t chan;
+ rt_uint32_t flags; /* PTP_PEROUT_ONE_SHOT, PTP_PEROUT_DUTY_CYCLE, PTP_PEROUT_PHASE */
+ struct rt_ptp_clock_time period; /* zero period disables output */
+ struct rt_ptp_clock_time on; /* high time when PTP_PEROUT_DUTY_CYCLE */
+ struct rt_ptp_clock_time start_phase; /* start or phase offset when PTP_PEROUT_PHASE */
+};
+
+rt_err_t rt_ptp_request_perout(struct rt_ptp_clock *ptp, struct rt_ptp_request_perout *perout);
+```
+
+- **`chan`** must be **`< perout_nr`**
+- With **`PTP_STRICT_FLAGS`**: duty **`on`** must not exceed **`period`**; **`start_phase`** must be strictly less than one period when **`PTP_PEROUT_PHASE`** is set
+- Enable is inferred: **`period.sec || period.nsec`**
+
+### PPS
+
+```c
+rt_err_t rt_ptp_enable_pps(struct rt_ptp_clock *ptp, rt_bool_t on);
+```
+
+Uses **`request.type = PTP_CLK_REQ_PPS`** and **`ops->enable`**.
+
+---
+
+## GPIO pin routing
+
+When **`pins_nr > 0`**, the core keeps a **`pins[]`** table describing which physical pin is mapped to which function and channel.
+
+```c
+enum {
+ PTP_PIN_FUNC_NONE,
+ PTP_PIN_FUNC_EXTTS,
+ PTP_PIN_FUNC_PEROUT,
+ PTP_PIN_FUNC_PHYSYNC,
+};
+
+rt_err_t rt_ptp_get_pin_func(struct rt_ptp_clock *ptp, rt_uint32_t pin, struct rt_ptp_pin *out_pin);
+rt_err_t rt_ptp_set_pin_func(struct rt_ptp_clock *ptp, rt_uint32_t pin, rt_uint32_t func, rt_uint32_t chan);
+```
+
+**Important:** **`rt_ptp_set_pin_func`** only updates **routing metadata** and disables the previous function on affected pins. It does **not** start EXTTS or PEROUT hardware. After assigning a pin, call **`rt_ptp_request_extts`** or **`rt_ptp_request_perout`** separately.
+
+**`rt_ptp_set_pin_func`** flow:
+
+1. Validate **`func`** / **`chan`** against **`extts_nr`**, **`perout_nr`**, or PHYSYNC rules
+2. Call **`ops->verify`**
+3. Disable any other pin already using the same **`func` + `chan`**
+4. Disable the old function on the target pin, then store the new mapping
+
+Access to **`pins[]`** is serialized with **`pin_mutex`**.
+
+---
+
+## Event notifiers
+
+Hardware drivers call **`rt_ptp_clock_event`** when an alarm fires, an external timestamp is captured, PPS is observed, etc.
+
+```c
+enum {
+ PTP_CLOCK_EV_ALARM,
+ PTP_CLOCK_EV_EXTTS,
+ PTP_CLOCK_EV_EXTOFF,
+ PTP_CLOCK_EV_PPS,
+ PTP_CLOCK_EV_PPSUSR,
+};
+
+struct rt_ptp_clock_event {
+ rt_uint32_t type;
+ rt_uint32_t index;
+ union {
+ rt_uint64_t timestamp;
+ rt_int64_t offset;
+ struct rt_ptp_clock_time pps_times;
+ };
+};
+
+struct rt_ptp_clock_notifier {
+ rt_list_t list;
+ rt_uint32_t event_mask; /* RT_BIT(PTP_CLOCK_EV_*) bits */
+ struct rt_ptp_clock *ptp;
+ void (*callback)(struct rt_ptp_clock_notifier *notifier, struct rt_ptp_clock_event *ev);
+ void *priv;
+};
+
+rt_err_t rt_ptp_clock_notifier_register(struct rt_ptp_clock *ptp, struct rt_ptp_clock_notifier *notifier);
+rt_err_t rt_ptp_clock_notifier_unregister(struct rt_ptp_clock *ptp, struct rt_ptp_clock_notifier *notifier);
+void rt_ptp_clock_event(struct rt_ptp_clock *ptp, struct rt_ptp_clock_event *ev);
+```
+
+**`event_mask`** must be non-zero at register time. **`rt_ptp_clock_event`** walks registered notifiers under **`nodes_lock`** and invokes matching callbacks **without holding the lock** during the callback.
+
+---
+
+## Example (PHC driver sketch)
+
+```c
+#include <drivers/ptp.h>
+
+static rt_err_t my_ptp_gettime(struct rt_ptp_clock *ptp, struct rt_ptp_clock_time *ts)
+{
+ /* read hardware counter */
+ ts->sec = 0;
+ ts->nsec = 0;
+ return RT_EOK;
+}
+
+static rt_err_t my_ptp_adjfreq(struct rt_ptp_clock *ptp, rt_base_t freq)
+{
+ /* program frequency trim in ppb */
+ return RT_EOK;
+}
+
+static rt_err_t my_ptp_enable(struct rt_ptp_clock *ptp,
+ struct rt_ptp_clock_request *req, rt_bool_t on)
+{
+ switch (req->type) {
+ case PTP_CLK_REQ_EXTTS:
+ /* configure edge detect on req->extts.chan */
+ break;
+ case PTP_CLK_REQ_PEROUT:
+ /* program req->perout period/on */
+ break;
+ case PTP_CLK_REQ_PPS:
+ /* enable/disable PPS output */
+ break;
+ default:
+ return -RT_EINVAL;
+ }
+ return RT_EOK;
+}
+
+static const struct rt_ptp_ops my_ptp_ops = {
+ .gettime = my_ptp_gettime,
+ .settime = my_ptp_settime,
+ .adjfreq = my_ptp_adjfreq,
+ .adjtime = my_ptp_adjtime,
+ .enable = my_ptp_enable,
+ .verify = my_ptp_verify, /* required when pins_nr > 0 */
+};
+
+static struct rt_ptp_clock my_ptp = {
+ .ops = &my_ptp_ops,
+ .max_freq = 500000, /* 500 ppm if 1 ppb == 1 Hz at 1 GHz; driver-defined scale */
+ .extts_nr = 2,
+ .perout_nr = 1,
+ .pps = 1,
+};
+
+static int my_ptp_probe(struct rt_platform_device *pdev)
+{
+ return rt_ptp_clock_register(&my_ptp);
+}
+```
+
+### Client lookup
+
+```c
+struct rt_ptp_clock *ptp;
+rt_device_t dev = rt_device_find("ptp0");
+
+if (dev)
+ ptp = rt_device_to_ptp_clock(dev);
+
+if (ptp) {
+ struct rt_ptp_clock_time ts;
+ rt_ptp_gettime(ptp, &ts);
+}
+```
+
+### POSIX dynamic clock (`clock_gettime`)
+
+When **`RT_USING_POSIX_CLOCK`**, **`RT_USING_POSIX_FS`**, and **`RT_USING_PTP`** are enabled, PHC devices follow the Linux [dynamic clock](https://www.man7.org/linux/man-pages/man3/clock_gettime.3.html) model:
+
+```c
+#include <fcntl.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+int fd = open("/dev/ptp0", O_RDWR);
+clockid_t clkid = FD_TO_CLOCKID(fd);
+struct timespec ts;
+
+clock_gettime(clkid, &ts);
+clock_settime(clkid, &ts); /* requires ops->settime */
+clock_getres(clkid, &ts);
+```
+
+Macros in **`sys/time.h`**: **`CLOCKFD`**, **`FD_TO_CLOCKID`**, **`CLOCKID_TO_FD`**. Implementation: **`components/libc/compilers/common/ctime.c`**.
+
+The fd must refer to an **open** `/dev/ptpN` device node (devfs). **`CLOCK_REALTIME`** still uses RTC; dynamic **`clockid_t`** values are separate from fixed POSIX clock IDs.
+
+---
+
+## Pitfalls
+
+| Issue | Mitigation |
+| --- | --- |
+| **`set_pin_func` without `request_extts` / `request_perout`** | Pin table updates only; explicitly enable the feature afterward. |
+| **`adjfreq` without `max_freq`** | Registration fails; set **`max_freq`** to the hardware ppb limit. |
+| **Notifier left registered at unregister** | Core logs a warning; unregister notifiers in client teardown. |
+| **Calling `rt_ptp_clock_event` with wrong `type`** | **`RT_ASSERT`** in debug builds; keep **`type < PTP_CLOCK_EV_MAX`**. |
+| **Blocking in notifier callback** | Callback runs with lock dropped but may be invoked from interrupt context depending on the driver — keep callbacks short or defer work. |
+| **Confusing PTP with RTC** | RTC is wall-clock calendar time; PTP PHC is for **precision sync** and **hardware timestamping**. |
+
+---
+
+## See also
+
+- @ref page_device_rtc — calendar RTC
+- @ref page_device_clock_time_core — kernel clock source / clock event devices
+- @ref page_device_hwtimer — general-purpose hardware timers
+- `components/drivers/ptp/ptp.c`
+- `components/drivers/include/drivers/ptp.h`