diff options
Diffstat (limited to 'documentation/6.components/device-driver')
| -rw-r--r-- | documentation/6.components/device-driver/INDEX.md | 1 | ||||
| -rw-r--r-- | documentation/6.components/device-driver/ptp/ptp.md | 395 |
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` |
