Add support for PTP (Precision Time Protocol) packet transmission and timestamping for Xilinx TSN Ethernet MAC. This implementation provides hardware-assisted packet timestamping for IEEE 1588 PTP synchronization. Key features added: - PTP TX/RX buffer management with 8 TX and 16 RX buffers - Hardware timestamp extraction for transmitted and received packets - Dedicated PTP packet queue and transmission path - Interrupt-driven timestamp retrieval via work queues - Support for 2-step PTP mode (HWTSTAMP_TX_ON) - PTP packet filtering based on ETH_P_1588 ethertype Signed-off-by: Srinivas Neeli --- drivers/net/ethernet/xilinx/tsn/Makefile | 2 +- drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h | 58 +++ .../net/ethernet/xilinx/tsn/xilinx_tsn_emac.c | 178 ++++++- .../ethernet/xilinx/tsn/xilinx_tsn_ptp_xmit.c | 451 ++++++++++++++++++ 4 files changed, 683 insertions(+), 6 deletions(-) create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_xmit.c diff --git a/drivers/net/ethernet/xilinx/tsn/Makefile b/drivers/net/ethernet/xilinx/tsn/Makefile index 0faa5233221b..a39cc7ca1533 100644 --- a/drivers/net/ethernet/xilinx/tsn/Makefile +++ b/drivers/net/ethernet/xilinx/tsn/Makefile @@ -1,2 +1,2 @@ obj-$(CONFIG_XILINX_TSN) :=xilinx_tsn.o -xilinx_tsn-objs := xilinx_tsn_main.o xilinx_tsn_ep.o xilinx_tsn_emac.o xilinx_tsn_mdio.o xilinx_tsn_switch.o xilinx_tsn_ptp_clock.o +xilinx_tsn-objs := xilinx_tsn_main.o xilinx_tsn_ep.o xilinx_tsn_emac.o xilinx_tsn_mdio.o xilinx_tsn_switch.o xilinx_tsn_ptp_clock.o xilinx_tsn_ptp_xmit.o diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h index 0cce916825ea..c8339ecef2f6 100644 --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h @@ -137,6 +137,37 @@ /* PTP Timer Register Base Offset */ #define TSN_PTP_TIMER_OFFSET 0x12800 +/* PTP register offsets */ +#define PTP_TX_CONTROL_OFFSET 0x00012000 +#define PTP_RX_CONTROL_OFFSET 0x00012004 + +/* PTP RX buffer configuration */ +#define PTP_RX_BASE_OFFSET 0x00010000 +#define PTP_RX_PACKET_FIELD_MASK 0x00000F00 +#define PTP_RX_PACKET_CLEAR 0x00000001 + +/* PTP TX buffer configuration */ +#define PTP_TX_BUFFER_OFFSET(index) (0x00011000 + (index) * 0x100) +#define PTP_TX_CMD_FIELD_LEN 8 +#define PTP_TX_CMD_1STEP_SHIFT BIT(16) +#define PTP_TX_BUFFER_CMD2_FIELD 0x4 + +/* PTP TX control and status masks */ +#define PTP_TX_FRAME_WAITING_MASK 0x0000ff00 +#define PTP_TX_FRAME_WAITING_SHIFT 8 +#define PTP_TX_PACKET_FIELD_MASK 0x00070000 +#define PTP_TX_PACKET_FIELD_SHIFT 16 + +/* PTP timestamp and buffer definitions */ +#define PTP_HW_TSTAMP_SIZE 8 /* 64 bit timestamp */ +#define PTP_RX_HWBUF_SIZE 256 +#define PTP_RX_FRAME_SIZE 252 +#define PTP_HW_TSTAMP_OFFSET (PTP_RX_HWBUF_SIZE - PTP_HW_TSTAMP_SIZE) + +/* PTP message type definitions */ +#define PTP_MSG_TYPE_MASK BIT(3) +#define PTP_TYPE_SYNC 0x0 + /** * struct tsn_ptp_timer - PTP timer private data * @dev: Device pointer @@ -175,6 +206,16 @@ struct tsn_ptp_timer { * @mii_clk_div: MDIO clock divider value * @emac_num: EMAC instance number (1 or 2) * @irq: Interrupt number for this EMAC + * @ptp_rx_irq: PTP RX interrupt number + * @ptp_tx_irq: PTP TX interrupt number + * @ptp_txq: PTP TX packet queue for timestamping + * @ptp_tx_lock: Spinlock for PTP TX queue + * @tx_tstamp_work: Work structure for TX timestamp processing + * @ptp_rx_hw_pointer: Hardware pointer for PTP RX packets + * @ptp_rx_sw_pointer: Software pointer for PTP RX packets + * @ptp_ts_type: PTP timestamp type configuration + * @tstamp_config: Hardware timestamp config structure + * @current_rx_filter : Current rx filter */ struct tsn_emac { struct net_device *ndev; @@ -189,6 +230,16 @@ struct tsn_emac { u8 mii_clk_div; int emac_num; int irq; + int ptp_rx_irq; + int ptp_tx_irq; + struct sk_buff_head ptp_txq; + spinlock_t ptp_tx_lock; /* Protect PTP TX queue */ + struct work_struct tx_tstamp_work; + u8 ptp_rx_hw_pointer; + u8 ptp_rx_sw_pointer; + int ptp_ts_type; + struct hwtstamp_config tstamp_config; + int current_rx_filter; }; /* @@ -369,4 +420,11 @@ int tsn_switch_init(struct platform_device *pdev); void tsn_switch_exit(struct platform_device *pdev); int tsn_ptp_timer_init(struct tsn_emac *emac, struct device_node *emac_np); void tsn_ptp_timer_exit(struct tsn_emac *emac); +int tsn_ptp_get_irq_info(struct tsn_emac *emac, struct device_node *emac_np); +int tsn_ptp_init_and_register_irqs(struct tsn_emac *emac); +void tsn_ptp_unregister_irqs(struct tsn_emac *emac); +int tsn_ptp_xmit(struct sk_buff *skb, struct tsn_emac *emac); +void tsn_ptp_tx_tstamp(struct work_struct *work); +irqreturn_t tsn_ptp_rx_irq(int irq, void *data); +irqreturn_t tsn_ptp_tx_irq(int irq, void *data); #endif /* XILINX_TSN_H */ diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c index b7d7ba0de717..4d0780a29fc9 100644 --- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c @@ -61,16 +61,28 @@ static int emac_open(struct net_device *ndev) { struct tsn_emac *emac = netdev_priv(ndev); struct phy_device *phydev = NULL; + int ret; + + /* Register PTP interrupts */ + ret = tsn_ptp_init_and_register_irqs(emac); + if (ret) { + dev_err(emac->common->dev, + "EMAC %d: Failed to register PTP interrupts: %d\n", + emac->emac_num, ret); + return ret; + } if (emac->phy_node) { phydev = of_phy_connect(emac->ndev, emac->phy_node, tsn_adjust_link_tsn, emac->phy_flags, emac->phy_mode); - if (!phydev) + if (!phydev) { dev_err(emac->common->dev, "of_phy_connect() failed\n"); - else - phy_start(phydev); + tsn_ptp_unregister_irqs(emac); + return -ENODEV; + } + phy_start(phydev); } return 0; @@ -87,9 +99,13 @@ static int emac_open(struct net_device *ndev) */ static int emac_stop(struct net_device *ndev) { + struct tsn_emac *emac = netdev_priv(ndev); + if (ndev->phydev) phy_disconnect(ndev->phydev); + tsn_ptp_unregister_irqs(emac); + return 0; } @@ -120,16 +136,158 @@ static int emac_validate_addr(struct net_device *ndev) static netdev_tx_t emac_start_xmit(struct sk_buff *skb, struct net_device *ndev) { struct tsn_emac *emac = netdev_priv(ndev); + u16 queue = skb_get_queue_mapping(skb); + + if (queue == emac->common->num_priorities) + return tsn_ptp_xmit(skb, emac); return tsn_start_xmit_dmaengine(emac->common, skb, ndev); } +/** + * emac_select_queue - select queue for packet transmission + * @ndev: Pointer to net_device structure + * @skb: socket buffer containing the packet + * @sb_dev: fallback device (not used) + * + * Return: Queue index for PTP packets or default queue + * + * This function selects the appropriate queue for packet transmission. + * PTP packets (ETH_P_1588) are directed to a dedicated PTP queue. + */ +static u16 emac_select_queue(struct net_device *ndev, + struct sk_buff *skb, + struct net_device *sb_dev) +{ + struct tsn_emac *emac = netdev_priv(ndev); + struct tsn_priv *common = emac->common; + struct ethhdr *hdr = (struct ethhdr *)skb->data; + + /* PTP over Ethernet (Layer 2) */ + if (hdr->h_proto == htons(ETH_P_1588)) + return common->num_priorities; + return netdev_pick_tx(ndev, skb, sb_dev); +} + +/** + * emac_set_timestamp_mode - sets up the hardware for the requested mode + * @emac: Pointer to TSN EMAC structure + * @config: the hwtstamp configuration requested + * + * Return: 0 on success, Negative value on errors + */ +static int emac_set_timestamp_mode(struct tsn_emac *emac, + struct hwtstamp_config *config) +{ + /* reserved for future extensions */ + if (config->flags) + return -EINVAL; + + if (config->tx_type < HWTSTAMP_TX_OFF || + config->tx_type > HWTSTAMP_TX_ON) + return -ERANGE; + + emac->ptp_ts_type = config->tx_type; + + /* On RX always timestamp everything */ + switch (config->rx_filter) { + case HWTSTAMP_FILTER_NONE: + emac->current_rx_filter = HWTSTAMP_FILTER_NONE; + break; + case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: + emac->current_rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; + config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; + break; + default: + return -ERANGE; + } + return 0; +} + +/** + * emac_set_ts_config - user entry point for timestamp mode + * @emac: Pointer to TSN EMAC structure + * @ifr: ioctl data + * + * Set hardware to the requested more. If unsupported return an error + * with no changes. Otherwise, store the mode for future reference + * + * Return: 0 on success, Negative value on errors + */ +static int emac_set_ts_config(struct tsn_emac *emac, struct ifreq *ifr) +{ + struct hwtstamp_config config; + int err; + + if (copy_from_user(&config, ifr->ifr_data, sizeof(config))) + return -EFAULT; + + err = emac_set_timestamp_mode(emac, &config); + if (err) + return err; + + /* save these settings for future reference */ + memcpy(&emac->tstamp_config, &config, sizeof(emac->tstamp_config)); + + return copy_to_user(ifr->ifr_data, &config, + sizeof(config)) ? -EFAULT : 0; +} + +/** + * emac_get_ts_config - return the current timestamp configuration + * to the user + * @emac: pointer to TSN EMAC structure + * @ifr: ioctl data + * + * Return: 0 on success, Negative value on errors + */ +static int emac_get_ts_config(struct tsn_emac *emac, struct ifreq *ifr) +{ + struct hwtstamp_config *config = &emac->tstamp_config; + + return copy_to_user(ifr->ifr_data, config, + sizeof(*config)) ? -EFAULT : 0; +} + +/** + * emac_ioctl - Ioctl MII Interface + * @dev: Pointer to net_device structure + * @rq: ioctl request structure + * @cmd: ioctl command + * + * Return: 0 on success, Negative value on errors + */ +static int emac_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) +{ + struct tsn_emac *emac = netdev_priv(dev); + + if (!netif_running(dev)) + return -EINVAL; + + switch (cmd) { + case SIOCGMIIPHY: + case SIOCGMIIREG: + case SIOCSMIIREG: + if (!dev->phydev) + return -EOPNOTSUPP; + return phy_mii_ioctl(dev->phydev, rq, cmd); + case SIOCSHWTSTAMP: + return emac_set_ts_config(emac, rq); + case SIOCGHWTSTAMP: + return emac_get_ts_config(emac, rq); + default: + return -EOPNOTSUPP; + } +} + static const struct net_device_ops emac_netdev_ops = { .ndo_open = emac_open, .ndo_stop = emac_stop, .ndo_start_xmit = emac_start_xmit, .ndo_set_mac_address = tsn_ndo_set_mac_address, .ndo_validate_addr = emac_validate_addr, + .ndo_select_queue = emac_select_queue, + .ndo_eth_ioctl = emac_ioctl, }; /** @@ -171,7 +329,7 @@ static int emac_get_ts_info(struct net_device *ndev, BIT(HWTSTAMP_TX_ON); info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) | - BIT(HWTSTAMP_FILTER_ALL); + BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT); if (common->phc_index >= 0) info->phc_index = common->phc_index; @@ -225,7 +383,9 @@ int tsn_emac_init(struct platform_device *pdev) goto err_cleanup_all; } - ndev = alloc_etherdev(sizeof(*emac)); + ndev = alloc_etherdev_mqs(sizeof(*emac), + common->num_tx_queues + 1, + common->num_rx_queues); if (!ndev) { ret = -ENOMEM; of_node_put(emac_np); @@ -279,6 +439,14 @@ int tsn_emac_init(struct platform_device *pdev) goto err_teardown_mdio; } } + + ret = tsn_ptp_get_irq_info(emac, emac_np); + if (ret) { + dev_err(dev, "Failed to get PTP IRQ info for EMAC %d: %d\n", + emac->emac_num, ret); + goto err_remove_ptp; + } + ret = register_netdev(ndev); if (ret) { dev_err(dev, "Failed to register net device for MAC %d\n", mac_id); diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_xmit.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_xmit.c new file mode 100644 index 000000000000..0a2850ed42ad --- /dev/null +++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_xmit.c @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Xilinx FPGA Xilinx TSN PTP transfer protocol module. + * + */ + +#include "xilinx_tsn.h" + +/** + * ptp_iow - write to PTP register + * @emac: Pointer to TSN EMAC structure + * @off: Register offset + * @val: Value to write + * + * This function writes to PTP control registers. + */ +static inline void ptp_iow(struct tsn_emac *emac, off_t off, u32 val) +{ + iowrite32(val, emac->regs + off); +} + +/** + * ptp_ior - read from PTP register + * @emac: Pointer to TSN EMAC structure + * @off: Register offset + * + * Return: Register value + * + * This function reads from PTP control registers. + */ +static inline u32 ptp_ior(struct tsn_emac *emac, u32 off) +{ + return ioread32(emac->regs + off); +} + +/** + * memcpy_fromio_32 - copy ptp buffer from HW + * @emac: Pointer to TSN EMAC structure + * @offset: Offset in the PTP buffer + * @data: Destination buffer + * @len: Length to copy + * + * This functions copies the data from PTP buffer to destination data buffer + */ +static void memcpy_fromio_32(struct tsn_emac *emac, + unsigned long offset, u8 *data, size_t len) +{ + while (len >= 4) { + *(u32 *)data = ptp_ior(emac, offset); + len -= 4; + offset += 4; + data += 4; + } + + if (len > 0) { + u32 leftover = ptp_ior(emac, offset); + u8 *src = (u8 *)&leftover; + + while (len) { + *data++ = *src++; + len--; + } + } +} + +/** + * memcpy_toio_32 - copy ptp buffer to HW + * @emac: Pointer to TSN EMAC structure + * @offset: Offset in the PTP buffer + * @data: Source data + * @len: Length to copy + * + * This functions copies the source data to destination ptp buffer + */ +static void memcpy_toio_32(struct tsn_emac *emac, + unsigned long offset, u8 *data, size_t len) +{ + while (len >= 4) { + ptp_iow(emac, offset, *(u32 *)data); + len -= 4; + offset += 4; + data += 4; + } + + if (len > 0) { + u32 leftover = 0; + u8 *dest = (u8 *)&leftover; + + while (len) { + *dest++ = *data++; + len--; + } + ptp_iow(emac, offset, leftover); + } +} + +/** + * tsn_ptp_xmit - xmit skb using PTP HW + * @skb: sk_buff pointer that contains data to be Txed. + * @emac: Pointer to TSN EMAC structure. + * + * Return: NETDEV_TX_OK, on success + * NETDEV_TX_BUSY, if any of the descriptors are not free + * + * This function is called to transmit a PTP skb. The function uses + * the free PTP TX buffer entry and sends the frame + */ +int tsn_ptp_xmit(struct sk_buff *skb, struct tsn_emac *emac) +{ + u16 queue = skb_get_queue_mapping(skb); + u8 tx_frame_waiting; + u32 cmd1_field = 0; + u32 cmd2_field = 0; + u8 free_index; + + tx_frame_waiting = (ptp_ior(emac, PTP_TX_CONTROL_OFFSET) & + PTP_TX_FRAME_WAITING_MASK) >> + PTP_TX_FRAME_WAITING_SHIFT; + + /* we reached last frame */ + if (tx_frame_waiting & (1 << 7)) { + netif_stop_subqueue(emac->ndev, queue); + emac->ndev->stats.tx_dropped++; + netdev_dbg(emac->ndev, "PTP TX buffers full: 0x%x\n", tx_frame_waiting); + return NETDEV_TX_BUSY; + } + + /* go to next available slot */ + free_index = fls(tx_frame_waiting); + + cmd1_field |= skb->len; + + ptp_iow(emac, PTP_TX_BUFFER_OFFSET(free_index), cmd1_field); + ptp_iow(emac, PTP_TX_BUFFER_OFFSET(free_index) + + PTP_TX_BUFFER_CMD2_FIELD, cmd2_field); + memcpy_toio_32(emac, + (PTP_TX_BUFFER_OFFSET(free_index) + + PTP_TX_CMD_FIELD_LEN), + skb->data, skb->len); + + /* send the frame */ + ptp_iow(emac, PTP_TX_CONTROL_OFFSET, (1 << free_index)); + + scoped_guard(spinlock_irq, &emac->ptp_tx_lock) { + skb->cb[0] = free_index; + skb_queue_tail(&emac->ptp_txq, skb); + + if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + } + skb_tx_timestamp(skb); + + return NETDEV_TX_OK; +} + +/** + * tsn_set_timestamp - timestamp skb with HW timestamp + * @emac: Pointer to TSN EMAC structure + * @hwtstamps: Pointer to skb timestamp structure + * @offset: offset of the timestamp in the PTP buffer + * + * Return: None. + * + */ +static void tsn_set_timestamp(struct tsn_emac *emac, + struct skb_shared_hwtstamps *hwtstamps, + unsigned int offset) +{ + u32 captured_ns; + u32 captured_sec; + + captured_ns = ptp_ior(emac, offset + 4); + captured_sec = ptp_ior(emac, offset); + + hwtstamps->hwtstamp = ktime_set(captured_sec, captured_ns); +} + +/** + * tsn_ptp_recv - receive ptp buffer in skb from HW + * @ndev: Pointer to net_device structure. + * + * This function is called from the ptp rx isr. It allocates skb, and + * copies the ptp rx buffer data to it and calls netif_rx for further + * processing. + * + */ +static void tsn_ptp_recv(struct net_device *ndev) +{ + struct tsn_emac *emac = netdev_priv(ndev); + unsigned long ptp_frame_base_addr = 0; + struct sk_buff *skb; + u16 msg_len; + u8 msg_type; + u32 bytes = 0; + u32 packets = 0; + + if (!ndev || !netif_running(ndev)) + return; + + while (((emac->ptp_rx_hw_pointer & 0xf) != + (emac->ptp_rx_sw_pointer & 0xf))) { + skb = netdev_alloc_skb(ndev, PTP_RX_FRAME_SIZE); + if (!skb) { + ndev->stats.rx_dropped++; + emac->ptp_rx_sw_pointer += 1; + continue; + } + emac->ptp_rx_sw_pointer += 1; + + ptp_frame_base_addr = PTP_RX_BASE_OFFSET + + ((emac->ptp_rx_sw_pointer & 0xf) * + PTP_RX_HWBUF_SIZE); + + memcpy_fromio_32(emac, ptp_frame_base_addr, skb->data, + PTP_RX_FRAME_SIZE); + + msg_type = *(u8 *)(skb->data + ETH_HLEN) & 0xf; + msg_len = *(u16 *)(skb->data + ETH_HLEN + 2); + + skb_put(skb, ntohs(msg_len) + ETH_HLEN); + + bytes += skb->len; + packets++; + + skb->protocol = eth_type_trans(skb, ndev); + skb->ip_summed = CHECKSUM_UNNECESSARY; + + /* timestamp only event messages */ + if (!(msg_type & PTP_MSG_TYPE_MASK)) { + tsn_set_timestamp(emac, skb_hwtstamps(skb), + (ptp_frame_base_addr + + PTP_HW_TSTAMP_OFFSET)); + } + + netif_rx(skb); + } + ndev->stats.rx_packets += packets; + ndev->stats.rx_bytes += bytes; +} + +/** + * tsn_ptp_rx_irq - PTP RX ISR handler + * @irq: irq number + * @data: net_device pointer + * + * Return: IRQ_HANDLED for all cases. + */ +irqreturn_t tsn_ptp_rx_irq(int irq, void *data) +{ + struct tsn_emac *emac = data; + + emac->ptp_rx_hw_pointer = (ptp_ior(emac, PTP_RX_CONTROL_OFFSET) + & PTP_RX_PACKET_FIELD_MASK) >> 8; + + tsn_ptp_recv(emac->ndev); + + return IRQ_HANDLED; +} + +/** + * tsn_ptp_tx_tstamp - timestamp skb on transmit path + * @work: Pointer to work_struct structure + * + * This adds TX timestamp to skb + */ +void tsn_ptp_tx_tstamp(struct work_struct *work) +{ + struct tsn_emac *emac = container_of(work, struct tsn_emac, + tx_tstamp_work); + struct net_device *ndev = emac->ndev; + struct skb_shared_hwtstamps hwtstamps; + struct sk_buff *skb; + unsigned long ts_reg_offset; + unsigned long flags; + u8 tx_packet; + u8 index; + u32 bytes = 0; + u32 packets = 0; + + memset(&hwtstamps, 0, sizeof(struct skb_shared_hwtstamps)); + + spin_lock_irqsave(&emac->ptp_tx_lock, flags); + + tx_packet = (ptp_ior(emac, PTP_TX_CONTROL_OFFSET) & + PTP_TX_PACKET_FIELD_MASK) >> + PTP_TX_PACKET_FIELD_SHIFT; + + while ((skb = __skb_dequeue(&emac->ptp_txq)) != NULL) { + index = skb->cb[0]; + + /* dequeued packet yet to be xmited? */ + if (index > tx_packet) { + /* enqueue it back and break */ + skb_queue_tail(&emac->ptp_txq, skb); + break; + } + /* time stamp reg offset */ + ts_reg_offset = PTP_TX_BUFFER_OFFSET(index) + + PTP_HW_TSTAMP_OFFSET; + + if (skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS) { + tsn_set_timestamp(emac, &hwtstamps, ts_reg_offset); + skb_tstamp_tx(skb, &hwtstamps); + } + + bytes += skb->len; + packets++; + dev_kfree_skb_any(skb); + } + ndev->stats.tx_packets += packets; + ndev->stats.tx_bytes += bytes; + + spin_unlock_irqrestore(&emac->ptp_tx_lock, flags); +} + +/** + * tsn_ptp_tx_irq - PTP TX irq handler + * @irq: irq number + * @data: net_device pointer + * + * Return: IRQ_HANDLED for all cases. + * + */ +irqreturn_t tsn_ptp_tx_irq(int irq, void *data) +{ + struct tsn_emac *emac = data; + + if (!emac || !emac->ndev || !emac->common) + return IRQ_HANDLED; + + /* read ctrl register to clear the interrupt */ + ptp_ior(emac, PTP_TX_CONTROL_OFFSET); + + schedule_work(&emac->tx_tstamp_work); + if (__netif_subqueue_stopped(emac->ndev, emac->common->num_priorities)) + netif_wake_subqueue(emac->ndev, emac->common->num_priorities); + + return IRQ_HANDLED; +} + +/** + * tsn_ptp_get_irq_info - Get PTP interrupt information from device tree + * @emac: Pointer to TSN EMAC structure + * @emac_np: Device tree node for EMAC + * + * Return: 0 on success, negative error code on failure + * + * This function retrieves PTP RX and TX interrupt numbers from device tree. + */ +int tsn_ptp_get_irq_info(struct tsn_emac *emac, struct device_node *emac_np) +{ + struct device *dev = emac->common->dev; + + emac->ptp_rx_irq = of_irq_get_byname(emac_np, "interrupt_ptp_rx"); + if (emac->ptp_rx_irq < 0) { + dev_err(dev, + "EMAC %d: Failed to get mandatory 'interrupt_ptp_rx': %d\n", + emac->emac_num, emac->ptp_rx_irq); + return emac->ptp_rx_irq; + } + + emac->ptp_tx_irq = of_irq_get_byname(emac_np, "interrupt_ptp_tx"); + if (emac->ptp_tx_irq < 0) { + dev_err(dev, + "EMAC %d: Failed to get mandatory 'interrupt_ptp_tx': %d\n", + emac->emac_num, emac->ptp_tx_irq); + return emac->ptp_tx_irq; + } + + dev_info(dev, "EMAC %d: PTP IRQs - RX: %d, TX: %d\n", + emac->emac_num, emac->ptp_rx_irq, emac->ptp_tx_irq); + + return 0; +} + +/** + * tsn_ptp_init_and_register_irqs - Initialize PTP subsystem and register interrupts + * @emac: Pointer to TSN EMAC structure + * + * Return: 0 on success, negative error code on failure + * + * This function initializes the PTP packet handling subsystem and registers + * interrupt handlers for PTP RX and TX events. + */ +int tsn_ptp_init_and_register_irqs(struct tsn_emac *emac) +{ + struct device *dev = emac->common->dev; + int ret; + + /* Initialize PTP TX queue and lock */ + skb_queue_head_init(&emac->ptp_txq); + spin_lock_init(&emac->ptp_tx_lock); + INIT_WORK(&emac->tx_tstamp_work, tsn_ptp_tx_tstamp); + + /* Initialize PTP RX pointers */ + emac->current_rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; + emac->ptp_ts_type = HWTSTAMP_TX_ON; + emac->ptp_rx_hw_pointer = 0; + emac->ptp_rx_sw_pointer = 0xff; + + /* Clear PTP RX control register */ + ptp_iow(emac, PTP_RX_CONTROL_OFFSET, PTP_RX_PACKET_CLEAR); + + /* Register PTP RX interrupt */ + ret = request_irq(emac->ptp_rx_irq, tsn_ptp_rx_irq, 0, + "tsn_ptp_rx", emac); + if (ret) { + dev_err(dev, "EMAC %d: Failed to register PTP RX IRQ %d: %d\n", + emac->emac_num, emac->ptp_rx_irq, ret); + return ret; + } + + /* Register PTP TX interrupt */ + ret = request_irq(emac->ptp_tx_irq, tsn_ptp_tx_irq, 0, + "tsn_ptp_tx", emac); + if (ret) { + dev_err(dev, "EMAC %d: Failed to register PTP TX IRQ %d: %d\n", + emac->emac_num, emac->ptp_tx_irq, ret); + free_irq(emac->ptp_rx_irq, emac); + return ret; + } + + dev_info(dev, "EMAC %d: PTP interrupts registered\n", emac->emac_num); + return 0; +} + +/** + * tsn_ptp_unregister_irqs - Unregister PTP interrupts + * @emac: Pointer to TSN EMAC structure + * + * This function unregisters PTP RX and TX interrupt handlers and cleans up + * PTP TX queue. Called during interface close. + */ +void tsn_ptp_unregister_irqs(struct tsn_emac *emac) +{ + struct sk_buff *skb; + + if (emac->ptp_tx_irq > 0) + free_irq(emac->ptp_tx_irq, emac); + + if (emac->ptp_rx_irq > 0) + free_irq(emac->ptp_rx_irq, emac); + + cancel_work_sync(&emac->tx_tstamp_work); + + while ((skb = skb_dequeue(&emac->ptp_txq)) != NULL) + dev_kfree_skb_any(skb); + + dev_info(emac->common->dev, "EMAC %d: PTP interrupts unregistered\n", + emac->emac_num); +} -- 2.25.1