summaryrefslogtreecommitdiff
path: root/documentation
diff options
context:
space:
mode:
authorGuEe-GUI <[email protected]>2026-06-14 11:37:08 +0800
committerRbb666 <[email protected]>2026-06-29 16:55:18 +0800
commit11ca1660069bbeaba478a8c8be5dbf2a3c3fb60c (patch)
tree29cc9051a874121b056d77e5dc7b9d31742c29a6 /documentation
parent1f9146897be1d898bea2a3ed0492c2d0a0507320 (diff)
[dm][ptp] support Precision Time Protocol (PTP) IEEE 1588 clock
Signed-off-by: GuEe-GUI <[email protected]>
Diffstat (limited to 'documentation')
-rw-r--r--documentation/6.components/device-driver/INDEX.md1
-rw-r--r--documentation/6.components/device-driver/ptp/ptp.md395
2 files changed, 396 insertions, 0 deletions
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`