[PATCH net-next 3/5] net: ethernet: oa_tc6: hardware timestamp support added

From: Selvamani Rajagopal

Date: Fri May 01 2026 - 15:18:27 EST


mii_bus structure can be allocated/populated from vendor's code
to accommodate vendor side customization.

Timestamp support implemented following OPEN Alliance 10BASE-T1x
MACPHY Serial Interface specification.

Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@xxxxxxxxxx>

removed doc from OA TC6 sources

Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@xxxxxxxxxx>
---
drivers/net/ethernet/oa_tc6.c | 385 ++++++++++++++++++++++++++++++----
include/linux/oa_tc6.h | 26 ++-
2 files changed, 371 insertions(+), 40 deletions(-)

diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 91a906a79..092b41cb5 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,10 +47,14 @@

/* 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_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)
#define INT_MASK0_TX_PROTOCOL_ERR_MASK BIT(0)
+#define INT_MASK0_TTSCA_MASK GENMASK(10, 8)

/* PHY Clause 22 registers base address and mask */
#define OA_TC6_PHY_STD_REG_ADDR_BASE 0xFF00
@@ -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,18 +87,11 @@
#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)

-/* PHY - Clause 45 registers memory map selector (MMS) as per table 6 in the
- * OPEN Alliance specification.
- */
-#define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */
-#define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */
-#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */
-#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */
-#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */
-
#define OA_TC6_CTRL_HEADER_SIZE 4
#define OA_TC6_CTRL_REG_VALUE_SIZE 4
#define OA_TC6_CTRL_IGNORED_SIZE 4
@@ -105,9 +110,25 @@
#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;
+ void *priv; /* Vendor's private driver data structure */
struct net_device *netdev;
struct phy_device *phydev;
struct mii_bus *mdiobus;
@@ -129,6 +150,9 @@ struct oa_tc6 {
u8 rx_chunks_available;
bool rx_buf_overflow;
bool int_flag;
+ u8 tx_ts_idx;
+ struct hwtstamp_config ts_config;
+ struct list_head tx_ts_skb_q;
};

enum oa_tc6_header_type {
@@ -156,6 +180,210 @@ enum oa_tc6_data_end_valid_info {
OA_TC6_DATA_END_VALID,
};

+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;
+}
+
+/**
+ * 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;
+
+ 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);
+
+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 int oa_tc6_spi_transfer(struct oa_tc6 *tc6,
enum oa_tc6_header_type header_type, u16 length)
{
@@ -177,7 +405,7 @@ static int oa_tc6_spi_transfer(struct oa_tc6 *tc6,
return spi_sync(tc6->spi, &msg);
}

-static int oa_tc6_get_parity(u32 p)
+static u32 oa_tc6_get_parity(u32 p)
{
/* Public domain code snippet, lifted from
* http://www-graphics.stanford.edu/~seander/bithacks.html
@@ -422,7 +650,7 @@ static int oa_tc6_mdiobus_read(struct mii_bus *bus, int addr, int regnum)
{
struct oa_tc6 *tc6 = bus->priv;
u32 regval;
- bool ret;
+ int ret;

ret = oa_tc6_read_register(tc6, OA_TC6_PHY_STD_REG_ADDR_BASE |
(regnum & OA_TC6_PHY_STD_REG_ADDR_MASK),
@@ -496,15 +724,20 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
{
int ret;

- tc6->mdiobus = mdiobus_alloc();
+ /* If vendor didn't provide mdiobus APIs, generic APIs are used */
if (!tc6->mdiobus) {
- netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
- return -ENOMEM;
+ tc6->mdiobus = mdiobus_alloc();
+ if (!tc6->mdiobus) {
+ netdev_err(tc6->netdev, "MDIO bus alloc failed\n");
+ return -ENOMEM;
+ }
+ tc6->mdiobus->read = oa_tc6_mdiobus_read;
+ tc6->mdiobus->write = oa_tc6_mdiobus_write;
+ tc6->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45;
+ tc6->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45;
+ tc6->mdiobus->name = "oa-tc6-mdiobus";
}

- tc6->mdiobus->priv = tc6;
- tc6->mdiobus->read = oa_tc6_mdiobus_read;
- tc6->mdiobus->write = oa_tc6_mdiobus_write;
/* OPEN Alliance 10BASE-T1x compliance MAC-PHYs will have both C22 and
* C45 registers space. If the PHY is discovered via C22 bus protocol it
* assumes it uses C22 protocol and always uses C22 registers indirect
@@ -515,10 +748,8 @@ static int oa_tc6_mdiobus_register(struct oa_tc6 *tc6)
* drivers can set .read_mmd/.write_mmd in the PHY driver to call
* .read_c45/.write_c45. Ex: drivers/net/phy/microchip_t1s.c
*/
- tc6->mdiobus->read_c45 = oa_tc6_mdiobus_read_c45;
- tc6->mdiobus->write_c45 = oa_tc6_mdiobus_write_c45;
- tc6->mdiobus->name = "oa-tc6-mdiobus";
- tc6->mdiobus->parent = tc6->dev;
+ tc6->mdiobus->priv = tc6;
+ tc6->mdiobus->parent = &tc6->spi->dev;

snprintf(tc6->mdiobus->id, ARRAY_SIZE(tc6->mdiobus->id), "%s",
dev_name(&tc6->spi->dev));
@@ -583,7 +814,7 @@ static void oa_tc6_phy_exit(struct oa_tc6 *tc6)
oa_tc6_mdiobus_unregister(tc6);
}

-static int oa_tc6_read_status0(struct oa_tc6 *tc6)
+static u32 oa_tc6_read_status0(struct oa_tc6 *tc6)
{
u32 regval;
int ret;
@@ -681,6 +912,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) {
@@ -750,12 +984,42 @@ static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer)
return 0;
}

+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));
+}
+
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;
@@ -766,24 +1030,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;

@@ -794,11 +1063,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;

@@ -843,7 +1112,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 */
@@ -851,7 +1120,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 */
@@ -876,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 ongoing rx frame data */
@@ -930,13 +1199,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);

@@ -955,6 +1226,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.
@@ -964,8 +1236,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
@@ -986,12 +1260,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;
}

@@ -1009,6 +1288,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;
@@ -1025,7 +1306,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 +
@@ -1071,7 +1352,11 @@ static int oa_tc6_try_spi_transfer(struct oa_tc6 *tc6)

if (tc6->int_flag) {
tc6->int_flag = false;
- if (spi_len == 0) {
+
+ /* If nothing to transmit and interface isn't up,
+ * avoid sending empty chunks.
+ */
+ if (spi_len == 0 && netif_running(tc6->netdev)) {
oa_tc6_add_empty_chunks_to_spi_buf(tc6, 1);
spi_len = OA_TC6_CHUNK_SIZE;
}
@@ -1216,6 +1501,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);
@@ -1226,13 +1512,17 @@ EXPORT_SYMBOL_GPL(oa_tc6_start_xmit);

/**
* oa_tc6_init - allocates and initializes oa_tc6 structure.
+ * @priv: vendor driver's private structure
* @spi: device with which data will be exchanged.
* @netdev: network device interface structure.
+ * @bus: vendor allocated mii_bus structure. Can be NULL.
*
* Return: pointer reference to the oa_tc6 structure if the MAC-PHY
* initialization is successful otherwise NULL.
*/
-struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
+struct oa_tc6 *oa_tc6_init(void *priv, struct spi_device *spi,
+ struct net_device *netdev,
+ struct mii_bus *bus)
{
struct oa_tc6 *tc6;
int ret;
@@ -1241,11 +1531,15 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
if (!tc6)
return NULL;

+ tc6->priv = priv;
+ tc6->mdiobus = bus;
+ tc6->tx_ts_idx = OA_TC6_TTSCA_REG_ID;
tc6->spi = spi;
tc6->netdev = netdev;
SET_NETDEV_DEV(netdev, &spi->dev);
mutex_init(&tc6->spi_ctrl_lock);
spin_lock_init(&tc6->tx_skb_lock);
+ INIT_LIST_HEAD(&tc6->tx_ts_skb_q);

/* Set the SPI controller to pump at realtime priority */
tc6->spi->rt = true;
@@ -1350,6 +1644,19 @@ struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev)
}
EXPORT_SYMBOL_GPL(oa_tc6_init);

+/**
+ * oa_tc6_priv - get vendor private structure
+ * @tc6: oa_tc6 struct
+ *
+ * Return: pointer reference to the vendor's private structure.
+ * Must be called after successful call to oa_tc6_init.
+ */
+void *oa_tc6_priv(struct oa_tc6 *tc6)
+{
+ return tc6->priv;
+}
+EXPORT_SYMBOL_GPL(oa_tc6_priv);
+
/**
* oa_tc6_exit - exit function.
* @tc6: oa_tc6 struct.
diff --git a/include/linux/oa_tc6.h b/include/linux/oa_tc6.h
index 15f58e3c5..5f5822282 100644
--- a/include/linux/oa_tc6.h
+++ b/include/linux/oa_tc6.h
@@ -7,12 +7,27 @@
* Author: Parthiban Veerasooran <parthiban.veerasooran@xxxxxxxxxxxxx>
*/

+#ifndef _LINUX_OA_TC6_H
+#define _LINUX_OA_TC6_H
+
#include <linux/etherdevice.h>
#include <linux/spi/spi.h>
+#include <linux/phy.h>
+
+/* PHY - Clause 45 registers memory map selector (MMS) as per table 6 in the
+ * OPEN Alliance specification.
+ */
+#define OA_TC6_PHY_C45_PCS_MMS2 2 /* MMD 3 */
+#define OA_TC6_PHY_C45_PMA_PMD_MMS3 3 /* MMD 1 */
+#define OA_TC6_PHY_C45_VS_PLCA_MMS4 4 /* MMD 31 */
+#define OA_TC6_PHY_C45_AUTO_NEG_MMS5 5 /* MMD 7 */
+#define OA_TC6_PHY_C45_POWER_UNIT_MMS6 6 /* MMD 13 */

struct oa_tc6;

-struct oa_tc6 *oa_tc6_init(struct spi_device *spi, struct net_device *netdev);
+struct oa_tc6 *oa_tc6_init(void *priv, struct spi_device *spi,
+ struct net_device *netdev,
+ struct mii_bus *bus);
void oa_tc6_exit(struct oa_tc6 *tc6);
int oa_tc6_write_register(struct oa_tc6 *tc6, u32 address, u32 value);
int oa_tc6_write_registers(struct oa_tc6 *tc6, u32 address, u32 value[],
@@ -22,3 +37,12 @@ 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_hwtstamp_ioctl(struct oa_tc6 *tc6, struct ifreq *rq, int cmd);
+int oa_tc6_hwtstamp_set(struct oa_tc6 *tc6,
+ struct kernel_hwtstamp_config *cfg);
+void oa_tc6_hwtstamp_get(struct oa_tc6 *tc6,
+ struct kernel_hwtstamp_config *cfg);
+void *oa_tc6_priv(struct oa_tc6 *tc6);
+
+#endif /* _LINUX_OA_TC6_H */
+
--
2.43.0


Public Information