mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2026-09-22 11:30:01 +08:00
[dm][ptp] support Precision Time Protocol (PTP) IEEE 1588 clock
Signed-off-by: GuEe-GUI <2991707448@qq.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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`
|
||||
Reference in New Issue
Block a user