[PATCH net-next v3 06/14] net: ethernet: oa_tc6: Support for hardware timestamp
From: Selvamani Rajagopal <hidden>
Date: 2026-05-29 18:40:11
Also in:
lkml
Subsystem:
networking drivers, open alliance 10base-t1s macphy serial interface framework, the rest · Maintainers:
Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Parthiban Veerasooran, Linus Torvalds
PTP register/unregister calls are implemented in oa_tc6_ptp.c. The APIs that work with the hardware for timestamp is provided by vendor code as it may be vendor dependent. Interface for ndo_hwtstamp_set/get, ioctl, control and status callback for ethtool are provided to support hardware timestamp feature. Besides ioctl interface, hardware timestamp functions that handles header and footer data are in oa_tc6.c. Helper functions are in oa_tc6_tstamp.c. Signed-off-by: Selvamani Rajagopal <redacted> --- MAINTAINERS | 1 + drivers/net/ethernet/oa_tc6/Makefile | 2 +- drivers/net/ethernet/oa_tc6/oa_tc6.c | 214 +++++++++++++++++-- drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c | 67 ++++++ drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h | 33 +++ drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c | 202 +++++++++++++++++ include/linux/oa_tc6.h | 12 ++ 7 files changed, 516 insertions(+), 15 deletions(-) create mode 100644 drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c create mode 100644 drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 2568ff304d9d..37a9e45e01f4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS@@ -19995,6 +19995,7 @@ F: drivers/rtc/rtc-optee.c OPEN ALLIANCE 10BASE-T1S MACPHY SERIAL INTERFACE FRAMEWORK M: Parthiban Veerasooran <parthiban.veerasooran@microchip.com> +M: Selva Rajagopal <selvamani.rajagopal@onsemi.com> (timestamp support) L: netdev@vger.kernel.org S: Maintained F: Documentation/networking/oa-tc6-framework.rst
diff --git a/drivers/net/ethernet/oa_tc6/Makefile b/drivers/net/ethernet/oa_tc6/Makefile
index ef79f2039016..53238024202e 100644
--- a/drivers/net/ethernet/oa_tc6/Makefile
+++ b/drivers/net/ethernet/oa_tc6/Makefile@@ -4,4 +4,4 @@ # obj-$(CONFIG_OA_TC6) := oa_tc6_mod.o -oa_tc6_mod-objs := oa_tc6.o +oa_tc6_mod-objs := oa_tc6.o oa_tc6_ptp.o oa_tc6_tstamp.o
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6.c b/drivers/net/ethernet/oa_tc6/oa_tc6.c
index c7d70d37ba53..d49220b49852 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6.c@@ -13,6 +13,15 @@ #include "oa_tc6_std_def.h" +struct oa_tc6_ts_info_rx { + bool rtsa; + bool rtsp; +}; + +struct oa_tc6_ts_info_tx { + u8 tsc; +}; + static int oa_tc6_spi_transfer(struct oa_tc6 *tc6, enum oa_tc6_header_type header_type, u16 length) {
@@ -47,6 +56,152 @@ static int oa_tc6_get_parity(u32 p) return !((p >> 28) & 1); } +static struct oa_tc6_ts_info_tx *oa_tc6_tsinfo_tx(struct sk_buff *skb) +{ + return (struct oa_tc6_ts_info_tx *)((skb)->cb); +} + +static struct oa_tc6_ts_info_rx *oa_tc6_tsinfo_rx(struct sk_buff *skb) +{ + return (struct oa_tc6_ts_info_rx *)((skb)->cb); +} + +static void oa_tc6_defer_for_hwtstamp(struct oa_tc6 *tc6, + struct sk_buff *skb) +{ + if (!tc6->hw_tstamp_enabled) + return; + if (!skb || (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) == 0) + return; + if (tc6->ts_config.tx_type != HWTSTAMP_TX_ON) { + tc6->tx_hwtstamp_lost++; + return; + } + + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + u8 ret = tc6->tx_ts_idx++; + + if (ret == OA_TC6_TTSCC_REG_ID) + tc6->tx_ts_idx = OA_TC6_TTSCA_REG_ID; + oa_tc6_tsinfo_tx(skb)->tsc = ret; + + list_add_tail(&skb->list, &tc6->tx_ts_skb_q); +} + +static int oa_tc6_process_deferred_skb(struct oa_tc6 *tc6, u8 tsc) +{ + struct skb_shared_hwtstamps tstamp; + struct oa_tc6_ts_info_tx *ski; + struct sk_buff *skb, *tmp; + bool found = false; + int ret = 0; + + /* Size of data must match OA_TC6_TSTAMP_SZ */ + u32 data[2]; + + list_for_each_entry_safe(skb, tmp, &tc6->tx_ts_skb_q, list) { + ski = oa_tc6_tsinfo_tx(skb); + if (ski->tsc != tsc) + continue; + if (found) { + dev_warn_ratelimited(&tc6->spi->dev, + "Multiple skbs. tsc = %d\n", + tsc); + tc6->tx_hwtstamp_err++; + } + found = true; + list_del(&skb->list); + + /* Retrieve the timestamping info */ + ret = oa_tc6_read_registers(tc6, + OA_TC6_REG_TTSCA_HIGH + + 2 * (tsc - 1), &data[0], 2); + + if (!ret) { + tstamp.hwtstamp = ktime_set(data[0], data[1]); + skb_tstamp_tx(skb, &tstamp); + tc6->tx_hwtstamp_pkts++; + } + + dev_kfree_skb(skb); + } + return ret; +} + +static void oa_tc6_events_handle(struct oa_tc6 *tc6, u32 val) +{ + /* Check TX timestamping */ + if (val & STATUS0_TTSCAA) + oa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCA_REG_ID); + + if (val & STATUS0_TTSCAB) + oa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCB_REG_ID); + + if (val & STATUS0_TTSCAC) + oa_tc6_process_deferred_skb(tc6, OA_TC6_TTSCC_REG_ID); +} + +static void oa_tc6_update_ts_in_rx_skb(struct oa_tc6 *tc6) +{ + struct sk_buff *skb = tc6->rx_skb; + struct oa_tc6_ts_info_rx *ski; + u32 ts[2]; + + if (!tc6->hw_tstamp_enabled) + return; + ski = oa_tc6_tsinfo_rx(skb); + if (!ski->rtsa) + return; + + ts[0] = be32_to_cpu(*((u32 *)(skb->data))); + ts[1] = be32_to_cpu(*((u32 *)(skb->data) + 1)); + + /* Check parity */ + if ((oa_tc6_get_parity(ts[0]) ^ oa_tc6_get_parity(ts[1])) == + !ski->rtsp) { + struct skb_shared_hwtstamps *hw_ts; + + /* Report timestamp to the upper layers */ + hw_ts = skb_hwtstamps(skb); + memset(hw_ts, 0, sizeof(*hw_ts)); + hw_ts->hwtstamp = ktime_set(ts[0], ts[1]); + } + skb_pull(skb, sizeof(ts)); +} + +static int oa_tc6_update_standard_capability(struct oa_tc6 *tc6) +{ + u32 regval = 0; + int ret; + + ret = oa_tc6_read_register(tc6, OA_TC6_REG_STDCAP, ®val); + if (ret) + return ret; + if (regval & STDCAP_FRAME_TIMESTAMP_CAPABILITY) + tc6->hw_tstamp_supported = true; + return 0; +} + +/** + * oa_tc6_ioctl - generic ioctl interface for MAC-PHY drivers. + * @tc6: oa_tc6 struct. + * @rq: request from socket interface + * @cmd: value to set/get timestamp configuration + * + * Return: 0 on success otherwise failed. + */ +int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd) +{ + if (!netif_running(tc6->netdev)) + return -EINVAL; + + if (cmd == SIOCSHWTSTAMP || cmd == SIOCGHWTSTAMP) + return oa_tc6_tstamp_ioctl(tc6, rq, cmd); + else + return phy_do_ioctl_running(tc6->netdev, rq, cmd); +} +EXPORT_SYMBOL_GPL(oa_tc6_ioctl); + static __be32 oa_tc6_prepare_ctrl_header(u32 addr, u8 length, enum oa_tc6_register_op reg_op) {
@@ -538,6 +693,9 @@ static int oa_tc6_process_extended_status(struct oa_tc6 *tc6) return ret; } + if ((value & STATUS0_TTSCA_MASK) != 0) + oa_tc6_events_handle(tc6, value & STATUS0_TTSCA_MASK); + /* Clear the error interrupts status */ ret = oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, value); if (ret) {
@@ -609,6 +767,8 @@ static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer) static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6) { + oa_tc6_update_ts_in_rx_skb(tc6); + tc6->rx_skb->protocol = eth_type_trans(tc6->rx_skb, tc6->netdev); tc6->netdev->stats.rx_packets++; tc6->netdev->stats.rx_bytes += tc6->rx_skb->len;
@@ -623,24 +783,29 @@ static void oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length) memcpy(skb_put(tc6->rx_skb, length), payload, length); } -static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6) +static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6, u32 footer) { + struct oa_tc6_ts_info_rx *ski; + tc6->rx_skb = netdev_alloc_skb_ip_align(tc6->netdev, tc6->netdev->mtu + - ETH_HLEN + ETH_FCS_LEN); + ETH_HLEN + ETH_FCS_LEN + OA_TC6_TSTAMP_SZ); if (!tc6->rx_skb) { tc6->netdev->stats.rx_dropped++; return -ENOMEM; } + ski = oa_tc6_tsinfo_rx(tc6->rx_skb); + ski->rtsa = FIELD_GET(OA_TC6_DATA_FOOTER_RTSA_VALID, footer); + ski->rtsp = FIELD_GET(OA_TC6_DATA_FOOTER_RTSP_VALID, footer); return 0; } static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload, - u16 size) + u16 size, u32 footer) { int ret; - ret = oa_tc6_allocate_rx_skb(tc6); + ret = oa_tc6_allocate_rx_skb(tc6, footer); if (ret) return ret;
@@ -651,11 +816,11 @@ static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload, return 0; } -static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size) +static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size, u32 footer) { int ret; - ret = oa_tc6_allocate_rx_skb(tc6); + ret = oa_tc6_allocate_rx_skb(tc6, footer); if (ret) return ret;
@@ -700,7 +865,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data, size = end_byte_offset + 1 - start_byte_offset; return oa_tc6_prcs_complete_rx_frame(tc6, &data[start_byte_offset], - size); + size, footer); } /* Process the chunk with only rx frame start */
@@ -708,7 +873,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data, size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset; return oa_tc6_prcs_rx_frame_start(tc6, &data[start_byte_offset], - size); + size, footer); } /* Process the chunk with only rx frame end */
@@ -733,7 +898,7 @@ static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data, size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset; return oa_tc6_prcs_rx_frame_start(tc6, &data[start_byte_offset], - size); + size, footer); } /* Process the chunk with ongoing rx frame data */
@@ -787,13 +952,15 @@ static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length) } static __be32 oa_tc6_prepare_data_header(bool data_valid, bool start_valid, - bool end_valid, u8 end_byte_offset) + bool end_valid, u8 end_byte_offset, + u8 tsc) { u32 header = FIELD_PREP(OA_TC6_DATA_HEADER_DATA_NOT_CTRL, OA_TC6_DATA_HEADER) | FIELD_PREP(OA_TC6_DATA_HEADER_DATA_VALID, data_valid) | FIELD_PREP(OA_TC6_DATA_HEADER_START_VALID, start_valid) | FIELD_PREP(OA_TC6_DATA_HEADER_END_VALID, end_valid) | + FIELD_PREP(OA_TC6_DATA_HEADER_TSC_OFFSET, tsc) | FIELD_PREP(OA_TC6_DATA_HEADER_END_BYTE_OFFSET, end_byte_offset);
@@ -812,6 +979,7 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6) enum oa_tc6_data_start_valid_info start_valid; u8 end_byte_offset = 0; u16 length_to_copy; + u8 tsc = 0; /* Initial value is assigned here to avoid more than 80 characters in * the declaration place.
@@ -821,8 +989,10 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6) /* Set start valid if the current tx chunk contains the start of the tx * ethernet frame. */ - if (!tc6->tx_skb_offset) + if (!tc6->tx_skb_offset) { start_valid = OA_TC6_DATA_START_VALID; + tsc = oa_tc6_tsinfo_tx(tc6->ongoing_tx_skb)->tsc; + } /* If the remaining tx skb length is more than the chunk payload size of * 64 bytes then copy only 64 bytes and leave the ongoing tx skb for
@@ -843,12 +1013,17 @@ static void oa_tc6_add_tx_skb_to_spi_buf(struct oa_tc6 *tc6) tc6->tx_skb_offset = 0; tc6->netdev->stats.tx_bytes += tc6->ongoing_tx_skb->len; tc6->netdev->stats.tx_packets++; - kfree_skb(tc6->ongoing_tx_skb); + + /* Free the ones that are not saved for later processing, + * like timestamping. + */ + if (!(skb_shinfo(tc6->ongoing_tx_skb)->tx_flags & SKBTX_IN_PROGRESS)) + kfree_skb(tc6->ongoing_tx_skb); tc6->ongoing_tx_skb = NULL; } *tx_buf = oa_tc6_prepare_data_header(OA_TC6_DATA_VALID, start_valid, - end_valid, end_byte_offset); + end_valid, end_byte_offset, tsc); tc6->spi_data_tx_buf_offset += OA_TC6_CHUNK_SIZE; }
@@ -866,6 +1041,8 @@ static u16 oa_tc6_prepare_spi_tx_buf_for_tx_skbs(struct oa_tc6 *tc6) tc6->ongoing_tx_skb = tc6->waiting_tx_skb; tc6->waiting_tx_skb = NULL; spin_unlock_bh(&tc6->tx_skb_lock); + oa_tc6_defer_for_hwtstamp(tc6, + tc6->ongoing_tx_skb); } if (!tc6->ongoing_tx_skb) break;
@@ -882,7 +1059,7 @@ static void oa_tc6_add_empty_chunks_to_spi_buf(struct oa_tc6 *tc6, header = oa_tc6_prepare_data_header(OA_TC6_DATA_INVALID, OA_TC6_DATA_START_INVALID, - OA_TC6_DATA_END_INVALID, 0); + OA_TC6_DATA_END_INVALID, 0, false); while (needed_empty_chunks--) { __be32 *tx_buf = tc6->spi_data_tx_buf +
@@ -1073,6 +1250,7 @@ netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb) spin_lock_bh(&tc6->tx_skb_lock); tc6->waiting_tx_skb = skb; spin_unlock_bh(&tc6->tx_skb_lock); + oa_tc6_tsinfo_tx(skb)->tsc = 0; /* Wake spi kthread to perform spi transfer */ wake_up_interruptible(&tc6->spi_wq);
@@ -1103,6 +1281,8 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev) SET_NETDEV_DEV(netdev, &spi->dev); mutex_init(&tc6->spi_ctrl_lock); spin_lock_init(&tc6->tx_skb_lock); + tc6->tx_ts_idx = OA_TC6_TTSCA_REG_ID; + INIT_LIST_HEAD(&tc6->tx_ts_skb_q); /* Set the SPI controller to pump at realtime priority */ tc6->spi->rt = true;
@@ -1168,6 +1348,12 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev) goto phy_exit; } + ret = oa_tc6_update_standard_capability(tc6); + if (ret) { + dev_err(&tc6->spi->dev, "Failed to read capability\n"); + goto phy_exit; + } + init_waitqueue_head(&tc6->spi_wq); tc6->spi_thread = kthread_run(oa_tc6_spi_thread_handler, tc6,
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c b/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c
new file mode 100644
index 000000000000..921191ec6829
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c@@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Support for hardware timestamping feature for OPEN Alliance + * 10BASE‑T1x MAC‑PHY Serial Interface framework + * + * Author: Selva Rajagopal <selvamani.rajagopal@onsemi.com> + */ + +#include <linux/hrtimer.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/phylink.h> +#include <linux/spi/spi.h> +#include <linux/oa_tc6.h> +#include <linux/net_tstamp.h> +#include <linux/ptp_clock_kernel.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/ktime.h> +#include <linux/errno.h> + +#include "oa_tc6_std_def.h" + +/** + * oa_tc6_ptp_register - Registers clock related callbacks + * @tc6: oa_tc6 struct. + * @info: Describes a PTP hardware clock + * + * Description: Vendors are expected to set the hardware timestamp + * related callbacks before calling this function. + */ +int oa_tc6_ptp_register(struct oa_tc6 *tc6, struct ptp_clock_info *info) +{ + /* Not supporting hardware timestamp isn't an error */ + if (!tc6->hw_tstamp_supported) + return 0; + + snprintf(info->name, sizeof(info->name), "%s", + "OA TC6 PTP clock"); + tc6->ptp_clock = ptp_clock_register(info, &tc6->spi->dev); + if (IS_ERR(tc6->ptp_clock)) { + dev_err(&tc6->spi->dev, "Registration of %s failed", + info->name); + return -EFAULT; + } + dev_info(&tc6->spi->dev, "%s registered. index %d", info->name, + ptp_clock_index(tc6->ptp_clock)); + return 0; +} +EXPORT_SYMBOL_GPL(oa_tc6_ptp_register); + +/** + * oa_tc6_ptp_unregister - Unregisters clock related callbacks + * @tc6: oa_tc6 struct. + */ +void oa_tc6_ptp_unregister(struct oa_tc6 *tc6) +{ + if (tc6->ptp_clock) + ptp_clock_unregister(tc6->ptp_clock); +} +EXPORT_SYMBOL_GPL(oa_tc6_ptp_unregister); + +MODULE_DESCRIPTION("OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface Lib"); +MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>"); +MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
index 2d8e28fb46fc..3a12b3228f30 100644
--- a/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_std_def.h@@ -22,6 +22,7 @@ /* Standard Capabilities Register */ #define OA_TC6_REG_STDCAP 0x0002 #define STDCAP_DIRECT_PHY_REG_ACCESS BIT(8) +#define STDCAP_FRAME_TIMESTAMP_CAPABILITY BIT(6) /* Reset Control and Status Register */ #define OA_TC6_REG_RESET 0x0003
@@ -31,9 +32,14 @@ #define OA_TC6_REG_CONFIG0 0x0004 #define CONFIG0_SYNC BIT(15) #define CONFIG0_ZARFE_ENABLE BIT(12) +#define CONFIG0_FTSE_ENABLE BIT(7) /* Status Register #0 */ #define OA_TC6_REG_STATUS0 0x0008 +#define STATUS0_TTSCAC BIT(10) +#define STATUS0_TTSCAB BIT(9) +#define STATUS0_TTSCAA BIT(8) +#define STATUS0_TTSCA_MASK GENMASK(10, 8) #define STATUS0_RESETC BIT(6) /* Reset Complete */ #define STATUS0_HEADER_ERROR BIT(5) #define STATUS0_LOSS_OF_FRAME_ERROR BIT(4)
@@ -47,6 +53,7 @@ /* Interrupt Mask Register #0 */ #define OA_TC6_REG_INT_MASK0 0x000C +#define INT_MASK0_TTSCA_MASK GENMASK(10, 8) #define INT_MASK0_HEADER_ERR_MASK BIT(5) #define INT_MASK0_LOSS_OF_FRAME_ERR_MASK BIT(4) #define INT_MASK0_RX_BUFFER_OVERFLOW_ERR_MASK BIT(3)
@@ -56,6 +63,9 @@ #define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00 #define OA_TC6_PHY_STD_REG_ADDR_MASK 0x1F +/* Tx timestamp capture register A (high) */ +#define OA_TC6_REG_TTSCA_HIGH (0x1010) + /* Control command header */ #define OA_TC6_CTRL_HEADER_DATA_NOT_CTRL BIT(31) #define OA_TC6_CTRL_HEADER_WRITE_NOT_READ BIT(29)
@@ -71,6 +81,7 @@ #define OA_TC6_DATA_HEADER_START_WORD_OFFSET GENMASK(19, 16) #define OA_TC6_DATA_HEADER_END_VALID BIT(14) #define OA_TC6_DATA_HEADER_END_BYTE_OFFSET GENMASK(13, 8) +#define OA_TC6_DATA_HEADER_TSC_OFFSET GENMASK(7, 6) #define OA_TC6_DATA_HEADER_PARITY BIT(0) /* Data footer */
@@ -82,6 +93,8 @@ #define OA_TC6_DATA_FOOTER_START_VALID BIT(20) #define OA_TC6_DATA_FOOTER_START_WORD_OFFSET GENMASK(19, 16) #define OA_TC6_DATA_FOOTER_END_VALID BIT(14) +#define OA_TC6_DATA_FOOTER_RTSA_VALID BIT(7) +#define OA_TC6_DATA_FOOTER_RTSP_VALID BIT(6) #define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET GENMASK(13, 8) #define OA_TC6_DATA_FOOTER_TX_CREDITS GENMASK(5, 1)
@@ -103,6 +116,12 @@ #define STATUS0_RESETC_POLL_DELAY 1000 #define STATUS0_RESETC_POLL_TIMEOUT 1000000 +#define OA_TC6_TSTAMP_SZ 8 + +#define OA_TC6_TTSCA_REG_ID 1 +#define OA_TC6_TTSCB_REG_ID 2 +#define OA_TC6_TTSCC_REG_ID 3 + /* Internal structure for MAC-PHY drivers */ struct oa_tc6 { struct device *dev;
@@ -127,6 +146,17 @@ struct oa_tc6 { u8 rx_chunks_available; bool rx_buf_overflow; bool int_flag; + struct ptp_clock_info ptp_clock_info; + struct hwtstamp_config ts_config; + struct list_head tx_ts_skb_q; + struct ptp_clock *ptp_clock; + bool hw_tstamp_supported; + bool hw_tstamp_enabled; + u32 tx_hwtstamp_pkts; + u32 tx_hwtstamp_lost; + u32 tx_hwtstamp_err; + int vend1_mms; + u8 tx_ts_idx; }; enum oa_tc6_header_type {
@@ -153,5 +183,8 @@ enum oa_tc6_data_end_valid_info { OA_TC6_DATA_END_INVALID, OA_TC6_DATA_END_VALID, }; + +int oa_tc6_tstamp_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd); + #endif /* OA_TC6_STD_DEF_H */
diff --git a/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c
new file mode 100644
index 000000000000..272701a4081d
--- /dev/null
+++ b/drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c@@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface framework + * + * Author: Selva Rajagopal <selvamani.rajagopal@onsemi.com> + */ + +#include <linux/bitfield.h> +#include <linux/iopoll.h> +#include <linux/mdio.h> +#include <linux/phy.h> +#include <linux/oa_tc6.h> + +#include "oa_tc6_std_def.h" + +static int oa_tc6_set_hwtstamp_settings(struct oa_tc6 *tc6) +{ + u32 cfg0, irqm, status0; + int ret; + + ret = oa_tc6_read_register(tc6, OA_TC6_REG_CONFIG0, &cfg0); + if (ret) { + dev_err(&tc6->spi->dev, "Failed to read CFG0 register\n"); + goto out; + } + + ret = oa_tc6_read_register(tc6, OA_TC6_REG_INT_MASK0, &irqm); + if (ret) { + dev_err(&tc6->spi->dev, "failed to read IRQM register\n"); + goto out; + } + + if (tc6->ts_config.tx_type == HWTSTAMP_TX_ON || + tc6->ts_config.rx_filter == HWTSTAMP_FILTER_ALL) + cfg0 |= CONFIG0_FTSE_ENABLE; + else + cfg0 &= ~CONFIG0_FTSE_ENABLE; + + if (tc6->ts_config.tx_type == HWTSTAMP_TX_ON) + irqm &= ~INT_MASK0_TTSCA_MASK; + else + irqm |= INT_MASK0_TTSCA_MASK; + + /* Clear timestamp related IRQs */ + status0 = STATUS0_TTSCA_MASK; + ret = oa_tc6_write_register(tc6, OA_TC6_REG_STATUS0, status0); + if (ret) { + dev_err(&tc6->spi->dev, "failed to write STATUS0 register\n"); + goto out; + } + + ret = oa_tc6_write_register(tc6, OA_TC6_REG_INT_MASK0, irqm); + if (ret) { + dev_err(&tc6->spi->dev, "failed to write IRQM register\n"); + goto out; + } + + ret = oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, cfg0); + if (ret) { + dev_err(&tc6->spi->dev, "failed to write CFG0 register\n"); + goto out; + } + if (cfg0 & CONFIG0_FTSE_ENABLE) + tc6->hw_tstamp_enabled = true; + else + tc6->hw_tstamp_enabled = false; +out: + return ret; +} + +/** + * oa_tc6_hwtstamp_get - gets hardware timestamp config + * @tc6: oa_tc6 struct. + * @cfg: kernel copy of hardware timestamp config + */ +void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6, + struct kernel_hwtstamp_config *cfg) +{ + hwtstamp_config_to_kernel(cfg, &tc6->ts_config); +} +EXPORT_SYMBOL_GPL(oa_tc6_hwtstamp_get); + +/** + * oa_tc6_hwtstamp_set - sets hardware timestamp config + * @tc6: oa_tc6 struct. + * @cfg: kernel copy of hardware timestamp config + * + * Return: 0 on success otherwise failed. + */ +int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6, + struct kernel_hwtstamp_config *cfg) +{ + if (!netif_running(tc6->netdev)) + return -EIO; + + if (!tc6->hw_tstamp_supported) + return -EOPNOTSUPP; + + switch (cfg->tx_type) { + case HWTSTAMP_TX_OFF: + case HWTSTAMP_TX_ON: + break; + default: + return -ERANGE; + } + + switch (cfg->rx_filter) { + case HWTSTAMP_FILTER_NONE: + case HWTSTAMP_FILTER_ALL: + case HWTSTAMP_FILTER_SOME: + case HWTSTAMP_FILTER_PTP_V1_L4_EVENT: + case HWTSTAMP_FILTER_PTP_V1_L4_SYNC: + case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ: + case HWTSTAMP_FILTER_PTP_V2_L4_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L4_SYNC: + case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ: + case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: + case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ: + case HWTSTAMP_FILTER_PTP_V2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_SYNC: + case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ: + case HWTSTAMP_FILTER_NTP_ALL: + break; + default: + return -ERANGE; + } + hwtstamp_config_from_kernel(&tc6->ts_config, cfg); + + /* Supports timestamping all traffic */ + if (cfg->rx_filter != HWTSTAMP_FILTER_NONE) + tc6->ts_config.rx_filter = HWTSTAMP_FILTER_ALL; + return oa_tc6_set_hwtstamp_settings(tc6); +} +EXPORT_SYMBOL_GPL(oa_tc6_hwtstamp_set); + +/** + * oa_tc6_get_ts_stats - Provides timestamping stats + * @tc6: oa_tc6 struct. + * @ts_stats: ethtool data structure to fill in + */ +void oa_tc6_get_ts_stats(struct oa_tc6 *tc6, + struct ethtool_ts_stats *stats) +{ + stats->pkts = tc6->tx_hwtstamp_pkts; + stats->err = tc6->tx_hwtstamp_err; + stats->lost = tc6->tx_hwtstamp_lost; +} +EXPORT_SYMBOL_GPL(oa_tc6_get_ts_stats); + +int oa_tc6_tstamp_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd) +{ + struct kernel_hwtstamp_config kcfg; + struct hwtstamp_config tscfg; + int ret = 0; + + if (!tc6->hw_tstamp_supported) + return -EOPNOTSUPP; + + if (cmd == SIOCSHWTSTAMP) { + if (copy_from_user(&tscfg, rq->ifr_data, + sizeof(tscfg))) + return -EFAULT; + + if (tscfg.flags) + return -EINVAL; + hwtstamp_config_to_kernel(&kcfg, &tscfg); + ret = oa_tc6_hwtstamp_set(tc6, &kcfg); + if (ret) + return ret; + } + if (copy_to_user(rq->ifr_data, &tc6->ts_config, + sizeof(tc6->ts_config))) + ret = -EFAULT; + return ret; +} + +/** + * oa_tc6_get_ts_info - Provides timestamp info for ethtool + * @tc6: oa_tc6 struct. + * @info: ethtool timestamping info structure + * @ts_stats: ethtool data structure to fill in + */ +int oa_tc6_get_ts_info(struct oa_tc6 *tc6, + struct kernel_ethtool_ts_info *info) +{ + if (!tc6->ptp_clock) + return ethtool_op_get_ts_info(tc6->netdev, info); + + info->so_timestamping = SOF_TIMESTAMPING_RAW_HARDWARE | + SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_RX_HARDWARE; + info->phc_index = ptp_clock_index(tc6->ptp_clock); + info->tx_types = BIT(HWTSTAMP_TX_ON); + info->rx_filters = BIT(HWTSTAMP_FILTER_ALL); + return 0; +} +EXPORT_SYMBOL_GPL(oa_tc6_get_ts_info); + +MODULE_DESCRIPTION("OPEN Alliance 10BASE‑T1x MAC‑PHY Serial Interface Lib"); +MODULE_AUTHOR("Selva Rajagopal <selvamani.rajagopal@onsemi.com>"); +MODULE_LICENSE("GPL");
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 0d5cae460850..c1eb1350ff27 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h@@ -12,6 +12,7 @@ #include <linux/etherdevice.h> #include <linux/spi/spi.h> +#include <linux/ptp_clock_kernel.h> /* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in * the OPEN Alliance specification.
@@ -34,4 +35,15 @@ int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[], u8 length); netdev_tx_t oa_tc6_start_xmit(struct oa_tc6 *tc6, struct sk_buff *skb); int oa_tc6_zero_align_receive_frame_enable(struct oa_tc6 *tc6); +int oa_tc6_ptp_register(struct oa_tc6 *tc6, struct ptp_clock_info *info); +int oa_tc6_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd); +int oa_tc6_get_ts_info(struct oa_tc6 *tc6, + struct kernel_ethtool_ts_info *ts_info); +void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6, + struct kernel_hwtstamp_config *cfg); +void oa_tc6_get_ts_stats(struct oa_tc6 *tc6, + struct ethtool_ts_stats *ts_stats); +int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6, + struct kernel_hwtstamp_config *cfg); +void oa_tc6_ptp_unregister(struct oa_tc6 *tc6); #endif /* _LINUX_OA_TC6_H */
--
2.43.0