Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next v2 6/9] net: ethernet: oa_tc6: Hardware timestamp support for OA TC6 framework
@ 2026-05-11 18:19 Selvamani Rajagopal
  2026-05-11 19:49 ` Andrew Lunn
  0 siblings, 1 reply; 2+ messages in thread
From: Selvamani Rajagopal @ 2026-05-11 18:19 UTC (permalink / raw)
  To: Piergiorgio Beruto, parthiban.veerasooran@microchip.com,
	andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, pabeni@redhat.com, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org

Added hardware timestamp support as defined in Open Alliance
10BASET1x MACPHY_Serial_Interface specification. ioctl interface
added for ethtool to report and set the hardware timestamp feature

Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
---
 drivers/net/ethernet/oa_tc6.c | 321 ++++++++++++++++++++++++++++++++--
 include/linux/oa_tc6.h        |   5 +
 2 files changed, 312 insertions(+), 14 deletions(-)

diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index beeac008f..48bd75f42 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -24,9 +24,16 @@
 #define OA_TC6_REG_CONFIG0			0x0004
 #define CONFIG0_SYNC				BIT(15)
 #define CONFIG0_ZARFE_ENABLE			BIT(12)
+#define CONFIG0_FTSE_ENABLE			BIT(7)
+#define CONFIG0_FTSS_ENABLE			BIT(6)
+#define CONFIG0_FTS_ENABLE_MASK			GENMASK(7, 6)
 
 /* 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)
@@ -40,6 +47,10 @@
 
 /* Interrupt Mask Register #0 */
 #define OA_TC6_REG_INT_MASK0			0x000C
+#define INT_MASK0_TTSCACM			BIT(10)
+#define INT_MASK0_TTSCABM			BIT(9)
+#define INT_MASK0_TTSCAAM			BIT(8)
+#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)
@@ -64,6 +75,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 */
@@ -75,6 +87,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)
 
@@ -105,6 +119,22 @@
 #define STATUS0_RESETC_POLL_DELAY		1000
 #define STATUS0_RESETC_POLL_TIMEOUT		1000000
 
+#define OA_TC6_REG_TTSCA_HIGH			(0x1010)
+#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
+
+struct oa_tc6_ts_info_rx {
+	bool rtsa;
+	bool rtsp;
+};
+
+struct oa_tc6_ts_info_tx {
+	u8 tsc;
+};
+
 /* Internal structure for MAC-PHY drivers */
 struct oa_tc6 {
 	struct device *dev;
@@ -130,6 +160,9 @@ struct oa_tc6 {
 	bool rx_buf_overflow;
 	bool int_flag;
 	int vend1_mms;
+	u8 tx_ts_idx;
+	struct hwtstamp_config ts_config;
+	struct list_head tx_ts_skb_q;
 };
 
 enum oa_tc6_header_type {
@@ -191,6 +224,238 @@ static int oa_tc6_get_parity(u32 p)
 	return !((p >> 28) & 1);
 }
 
+static inline struct oa_tc6_ts_info_tx *oa_tc6_tsinfo_tx(struct sk_buff *skb)
+{
+	return (struct oa_tc6_ts_info_tx *)((skb)->cb);
+}
+
+static inline 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->ts_config.tx_type != HWTSTAMP_TX_ON || !skb ||
+	    (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) == 0)
+		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(&tc6->spi->dev, "Multiple skb with tsc = %d\n", tsc);
+		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);
+		}
+
+		dev_kfree_skb(skb);
+	}
+	return ret;
+}
+
+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_FTS_ENABLE_MASK;
+	else
+		cfg0 &= ~CONFIG0_FTS_ENABLE_MASK;
+
+	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;
+	}
+
+	// apply configuration
+	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;
+	}
+out:
+	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];
+
+	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));
+}
+
+/**
+ * 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)
+{
+	cfg->tx_type = tc6->ts_config.tx_type;
+	cfg->rx_filter = tc6->ts_config.rx_filter;
+}
+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)
+{
+	switch (cfg->tx_type) {
+	case HWTSTAMP_TX_OFF:
+	case HWTSTAMP_TX_ON:
+		break;
+	default:
+		return -ERANGE;
+	}
+	tc6->ts_config.tx_type = cfg->tx_type;
+	if (cfg->rx_filter == HWTSTAMP_FILTER_NONE)
+		tc6->ts_config.rx_filter = cfg->rx_filter;
+	else
+		tc6->ts_config.rx_filter = HWTSTAMP_FILTER_ALL;
+	return oa_tc6_set_hwtstamp_settings(tc6);
+}
+EXPORT_SYMBOL_GPL(oa_tc6_hwtstamp_set);
+
+/**
+ * oa_tc6_hwtstamp_ioctl - ioctl interface for hardware timestamp
+ * @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_hwtstamp_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd)
+{
+	struct kernel_hwtstamp_config kcfg;
+	struct hwtstamp_config tscfg;
+	int ret = 0;
+
+	if (!netif_running(tc6->netdev))
+		return -EINVAL;
+
+	switch (cmd) {
+	case SIOCSHWTSTAMP:
+		if (copy_from_user(&tscfg, rq->ifr_data, sizeof(tscfg)))
+			return -EFAULT;
+
+		if (tscfg.flags)
+			return -EINVAL;
+		memset(&kcfg, 0, sizeof(kcfg));
+		kcfg.tx_type = tscfg.tx_type;
+		kcfg.rx_filter = tscfg.rx_filter;
+
+		ret = oa_tc6_hwtstamp_set(tc6, &kcfg);
+		break;
+
+	case SIOCGHWTSTAMP:
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+	if (copy_to_user(rq->ifr_data, &tc6->ts_config,
+			 sizeof(tc6->ts_config)))
+		ret = -EFAULT;
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_hwtstamp_ioctl);
+
 /**
  * Add vendor specific MDIO_MMD to OA TC6 MMS mapper value.
  * @tc6: oa_tc6 struct.
@@ -697,6 +962,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) {
@@ -768,10 +1036,15 @@ 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;
 
+	if ((tc6->netdev->hw_features & NETIF_F_RXFCS) != 0)
+		skb_trim(tc6->rx_skb, tc6->rx_skb->len - ETH_FCS_LEN);
+
 	netif_rx(tc6->rx_skb);
 
 	tc6->rx_skb = NULL;
@@ -782,24 +1055,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;
 
@@ -810,11 +1088,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;
 
@@ -859,7 +1137,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 */
@@ -867,7 +1145,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 */
@@ -892,7 +1170,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 */
@@ -946,13 +1224,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);
 
@@ -971,6 +1251,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.
@@ -980,8 +1261,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
@@ -1002,12 +1285,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;
 }
 
@@ -1025,6 +1313,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;
@@ -1041,7 +1331,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 +
@@ -1232,6 +1522,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);
@@ -1263,6 +1554,8 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
 	mutex_init(&tc6->spi_ctrl_lock);
 	spin_lock_init(&tc6->tx_skb_lock);
 	tc6->vend1_mms = -EOPNOTSUPP;
+	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;
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index d3c74555f..7fab8db3f 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -26,4 +26,9 @@ int oa_tc6_read_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
 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);
 void oa_tc6_set_vend1_mms(struct oa_tc6 *tc6, int mms);
+int oa_tc6_hwtstamp_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd);
+void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6,
+			 struct kernel_hwtstamp_config *cfg);
+int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6,
+			struct kernel_hwtstamp_config *cfg);
 #endif /* _LINUX_OA_TC6_H */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-05-11 19:49 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-11 18:19 [PATCH net-next v2 6/9] net: ethernet: oa_tc6: Hardware timestamp support for OA TC6 framework Selvamani Rajagopal
2026-05-11 19:49 ` Andrew Lunn

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox