diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig index 5412715d943b0..e706e929e6089 100644 --- a/drivers/net/ethernet/xilinx/Kconfig +++ b/drivers/net/ethernet/xilinx/Kconfig @@ -51,4 +51,32 @@ config XILINX_LL_TEMAC This driver supports the Xilinx 10/100/1000 LocalLink TEMAC core used in Xilinx Spartan and Virtex FPGAs +config XILINX_TSN + bool "Enable Xilinx's TSN IP" + default n + ---help--- + Enable Xilinx's TSN IP. + +config XILINX_TSN_PTP + bool "Generate hardware packet timestamps using Xilinx's TSN IP" + depends on XILINX_TSN + select PTP_1588_CLOCK + default y + ---help--- + Generate hardare packet timestamps. This is to facilitate IEE 1588. + +config XILINX_TSN_QBV + bool "Support Qbv protocol in TSN" + depends on XILINX_TSN_PTP + select PTP_1588_CLOCK + default y + ---help--- + Enables TSN Qbv protocol. + +config XILINX_TSN_SWITCH + bool "Support TSN switch" + depends on XILINX_TSN + default y + ---help--- + Enable Xilinx's TSN Switch support. endif # NET_VENDOR_XILINX diff --git a/drivers/net/ethernet/xilinx/Makefile b/drivers/net/ethernet/xilinx/Makefile index f8790d8743d28..b59c879725795 100644 --- a/drivers/net/ethernet/xilinx/Makefile +++ b/drivers/net/ethernet/xilinx/Makefile @@ -6,6 +6,9 @@ ll_temac-objs := ll_temac_main.o ll_temac_mdio.o obj-$(CONFIG_XILINX_LL_TEMAC) += ll_temac.o obj-$(CONFIG_XILINX_EMACLITE) += xilinx_emaclite.o +obj-$(CONFIG_XILINX_TSN_PTP) += xilinx_tsn_ptp_xmit.o xilinx_tsn_ptp_clock.o +obj-$(CONFIG_XILINX_TSN_QBV) += xilinx_tsn_shaper.o +obj-$(CONFIG_XILINX_TSN_SWITCH) += xilinx_tsn_switch.o xilinx_emac-objs := xilinx_axienet_main.o xilinx_axienet_mdio.o xilinx_axienet_dma.o obj-$(CONFIG_XILINX_AXI_EMAC) += xilinx_emac.o obj-$(CONFIG_AXIENET_HAS_MCDMA) += xilinx_axienet_mcdma.o diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet.h b/drivers/net/ethernet/xilinx/xilinx_axienet.h index d0731f19c9da6..229832e633c99 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet.h +++ b/drivers/net/ethernet/xilinx/xilinx_axienet.h @@ -578,11 +578,27 @@ struct aximcdma_bd { #define DESC_DMA_MAP_SINGLE 0 #define DESC_DMA_MAP_PAGE 1 -#if defined(CONFIG_AXIENET_HAS_MCDMA) +#if defined(CONFIG_XILINX_TSN) +#define XAE_MAX_QUEUES 5 +#elif defined(CONFIG_AXIENET_HAS_MCDMA) #define XAE_MAX_QUEUES 16 #else #define XAE_MAX_QUEUES 1 #endif + +#ifdef CONFIG_XILINX_TSN +/* TSN queues range is 2 to 5. For eg: for num_tc = 2 minimum queues = 2; + * for num_tc = 3 with sideband signalling maximum queues = 5 + */ +#define XAE_MAX_TSN_TC 3 +#define XAE_TSN_MIN_QUEUES 2 +#endif + +enum axienet_tsn_ioctl { + SIOCCHIOCTL = SIOCDEVPRIVATE, + SIOC_GET_SCHED, +}; + /** * struct axienet_local - axienet private per device data * @ndev: Pointer for net_device to which it will be attached. @@ -597,6 +613,20 @@ struct aximcdma_bd { * @num_rx_queues: Total number of Rx DMA queues * @dq: DMA queues data * @phy_mode: Phy type to identify between MII/GMII/RGMII/SGMII/1000 Base-X + * @is_tsn: Denotes a tsn port + * @temac_no: Denotes the port number in TSN IP + * @num_tc: Total number of TSN Traffic classes + * @timer_priv: PTP timer private data pointer + * @ptp_tx_irq: PTP tx irq + * @ptp_rx_irq: PTP rx irq + * @rtc_irq: PTP RTC irq + * @qbv_irq: QBV shed irq + * @ptp_ts_type: ptp time stamp type - 1 or 2 step mode + * @ptp_rx_hw_pointer: ptp rx hw pointer + * @ptp_rx_sw_pointer: ptp rx sw pointer + * @ptp_txq: PTP tx queue header + * @tx_tstamp_work: PTP timestamping work queue + * @ptp_tx_lock: PTP tx lock * @dma_err_tasklet: Tasklet structure to process Axi DMA errors * @eth_irq: Axi Ethernet IRQ number * @options: AxiEthernet option word @@ -655,11 +685,32 @@ struct axienet_local { struct tasklet_struct dma_err_tasklet[XAE_MAX_QUEUES]; struct napi_struct napi[XAE_MAX_QUEUES]; /* NAPI Structure */ + #define XAE_TEMAC1 0 + #define XAE_TEMAC2 1 + u8 temac_no; u16 num_tx_queues; /* Number of TX DMA queues */ u16 num_rx_queues; /* Number of RX DMA queues */ struct axienet_dma_q *dq[XAE_MAX_QUEUES]; /* DMA queue data*/ phy_interface_t phy_mode; + + bool is_tsn; +#ifdef CONFIG_XILINX_TSN + u16 num_tc; +#ifdef CONFIG_XILINX_TSN_PTP + void *timer_priv; + int ptp_tx_irq; + int ptp_rx_irq; + int rtc_irq; + int qbv_irq; + int ptp_ts_type; + u8 ptp_rx_hw_pointer; + u8 ptp_rx_sw_pointer; + struct sk_buff_head ptp_txq; + struct work_struct tx_tstamp_work; + spinlock_t ptp_tx_lock; /* TSN PTP tx lock*/ +#endif +#endif int eth_irq; u32 options; /* Current options word */ @@ -683,7 +734,7 @@ struct axienet_local { bool eth_hasptp; const struct axienet_config *axienet_config; -#ifdef CONFIG_XILINX_AXI_EMAC_HWTSTAMP +#if defined(CONFIG_XILINX_AXI_EMAC_HWTSTAMP) || defined(CONFIG_XILINX_TSN_PTP) void __iomem *tx_ts_regs; void __iomem *rx_ts_regs; struct hwtstamp_config tstamp_config; @@ -967,6 +1018,15 @@ int axienet_mdio_enable(struct axienet_local *lp); void axienet_mdio_disable(struct axienet_local *lp); int axienet_mdio_setup(struct axienet_local *lp); void axienet_mdio_teardown(struct axienet_local *lp); +#ifdef CONFIG_XILINX_TSN_PTP +void axienet_tx_tstamp(struct work_struct *work); +#endif +#ifdef CONFIG_XILINX_TSN_QBV +int axienet_qbv_init(struct net_device *ndev); +void axienet_qbv_remove(struct net_device *ndev); +int axienet_set_schedule(struct net_device *ndev, void __user *useraddr); +int axienet_get_schedule(struct net_device *ndev, void __user *useraddr); +#endif int axienet_mdio_wait_until_ready(struct axienet_local *lp); void __maybe_unused axienet_bd_free(struct net_device *ndev, struct axienet_dma_q *q); diff --git a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c index d87a3e17c802e..b69abd8059056 100644 --- a/drivers/net/ethernet/xilinx/xilinx_axienet_main.c +++ b/drivers/net/ethernet/xilinx/xilinx_axienet_main.c @@ -48,6 +48,10 @@ #include "xilinx_axienet.h" +#ifdef CONFIG_XILINX_TSN_PTP +#include "xilinx_tsn_ptp.h" +#include "xilinx_tsn_timer.h" +#endif /* Descriptors defines for Tx and Rx DMA */ #define TX_BD_NUM_DEFAULT 64 #define RX_BD_NUM_DEFAULT 1024 @@ -64,6 +68,11 @@ #define XXVENET_TS_HEADER_LEN 4 #define NS_PER_SEC 1000000000ULL /* Nanoseconds per second */ +#ifdef CONFIG_XILINX_TSN_PTP +int axienet_phc_index = -1; +EXPORT_SYMBOL(axienet_phc_index); +#endif + /* Option table for setting up Axi Ethernet hardware options */ static struct axienet_option axienet_options[] = { /* Turn on jumbo packet support for both Rx and Tx */ @@ -432,9 +441,14 @@ static void axienet_device_reset(struct net_device *ndev) axienet_iow(lp, XXV_GT_RESET_OFFSET, val); } - for_each_rx_dma_queue(lp, i) { - q = lp->dq[i]; - __axienet_device_reset(q); + if (!lp->is_tsn || lp->temac_no == XAE_TEMAC1) { + for_each_rx_dma_queue(lp, i) { + q = lp->dq[i]; + __axienet_device_reset(q); +#ifndef CONFIG_AXIENET_HAS_MCDMA + __axienet_device_reset(q); +#endif + } } lp->max_frm_size = XAE_MAX_VLAN_FRAME_SIZE; @@ -451,9 +465,11 @@ static void axienet_device_reset(struct net_device *ndev) lp->options |= XAE_OPTION_JUMBO; } - if (axienet_dma_bd_init(ndev)) { - netdev_err(ndev, "%s: descriptor allocation failed\n", - __func__); + if (!lp->is_tsn || lp->temac_no == XAE_TEMAC1) { + if (axienet_dma_bd_init(ndev)) { + netdev_err(ndev, "%s: descriptor allocation failed\n", + __func__); + } } if (lp->axienet_config->mactype != XAXIENET_10G_25G) { @@ -474,14 +490,16 @@ static void axienet_device_reset(struct net_device *ndev) netdev_err(ndev, "XXV MAC block lock not complete! Cross-check the MAC ref clock configuration\n"); } #ifdef CONFIG_XILINX_AXI_EMAC_HWTSTAMP - axienet_rxts_iow(lp, XAXIFIFO_TXTS_RDFR, - XAXIFIFO_TXTS_RESET_MASK); - axienet_rxts_iow(lp, XAXIFIFO_TXTS_SRR, - XAXIFIFO_TXTS_RESET_MASK); - axienet_txts_iow(lp, XAXIFIFO_TXTS_RDFR, - XAXIFIFO_TXTS_RESET_MASK); - axienet_txts_iow(lp, XAXIFIFO_TXTS_SRR, - XAXIFIFO_TXTS_RESET_MASK); + if (!lp->is_tsn) { + axienet_rxts_iow(lp, XAXIFIFO_TXTS_RDFR, + XAXIFIFO_TXTS_RESET_MASK); + axienet_rxts_iow(lp, XAXIFIFO_TXTS_SRR, + XAXIFIFO_TXTS_RESET_MASK); + axienet_txts_iow(lp, XAXIFIFO_TXTS_RDFR, + XAXIFIFO_TXTS_RESET_MASK); + axienet_txts_iow(lp, XAXIFIFO_TXTS_SRR, + XAXIFIFO_TXTS_RESET_MASK); + } #endif } @@ -885,6 +903,53 @@ static void axienet_create_tsheader(u8 *buf, u8 msg_type, } #endif +#ifdef CONFIG_XILINX_TSN +static inline u16 get_tsn_queue(u8 pcp, u16 num_tc) +{ + u16 queue = 0; + + /* For 3 queue system, RE queue is 1 and ST queue is 2 + * For 2 queue system, ST queue is 1. BE queue is always 0 + */ + if (pcp == 4) { + if (num_tc == 2) + queue = 1; + else + queue = 2; + } else if ((num_tc == 3) && (pcp == 2 || pcp == 3)) { + queue = 1; + } + + return queue; +} + +static inline u16 tsn_queue_mapping(const struct sk_buff *skb, u16 num_tc) +{ + int queue = 0; + u16 vlan_tci; + u8 pcp; + + struct ethhdr *hdr = (struct ethhdr *)skb->data; + u16 ether_type = ntohs(hdr->h_proto); + + if (unlikely(ether_type == ETH_P_8021Q)) { + struct vlan_ethhdr *vhdr = (struct vlan_ethhdr *)skb->data; + + /* ether_type = ntohs(vhdr->h_vlan_encapsulated_proto); */ + + vlan_tci = ntohs(vhdr->h_vlan_TCI); + + pcp = (vlan_tci & VLAN_PRIO_MASK) >> VLAN_PRIO_SHIFT; + pr_debug("vlan_tci: %x\n", vlan_tci); + pr_debug("pcp: %d\n", pcp); + + queue = get_tsn_queue(pcp, num_tc); + } + pr_debug("selected queue: %d\n", queue); + return queue; +} +#endif + #ifdef CONFIG_XILINX_AXI_EMAC_HWTSTAMP static int axienet_skb_tstsmp(struct sk_buff **__skb, struct axienet_dma_q *q, struct net_device *ndev) @@ -1005,6 +1070,24 @@ static int axienet_queue_xmit(struct sk_buff *skb, return NETDEV_TX_OK; } } + +#ifdef CONFIG_XILINX_TSN + if (unlikely(lp->is_tsn)) { + map = tsn_queue_mapping(skb, lp->num_tc); +#ifdef CONFIG_XILINX_TSN_PTP + const struct ethhdr *eth; + + eth = (struct ethhdr *)skb->data; + /* check if skb is a PTP frame ? */ + if (eth->h_proto == htons(ETH_P_1588)) + return axienet_ptp_xmit(skb, ndev); +#endif + if (lp->temac_no == XAE_TEMAC2) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + } +#endif num_frag = skb_shinfo(skb)->nr_frags; q = lp->dq[map]; @@ -1220,6 +1303,7 @@ static int axienet_recv(struct net_device *ndev, int budget, skb_put(skb, length); #ifdef CONFIG_XILINX_AXI_EMAC_HWTSTAMP + if (!lp->is_tsn) { if ((lp->tstamp_config.rx_filter == HWTSTAMP_FILTER_ALL || lp->eth_hasptp) && (lp->axienet_config->mactype != XAXIENET_10G_25G)) { u32 sec, nsec; @@ -1248,6 +1332,7 @@ static int axienet_recv(struct net_device *ndev, int budget, } else if (lp->axienet_config->mactype == XAXIENET_10G_25G) { axienet_rx_hwtstamp(lp, skb); } + } #endif skb->protocol = eth_type_trans(skb, ndev); /*skb_checksum_none_assert(skb);*/ @@ -1463,7 +1548,7 @@ static int axienet_mii_init(struct net_device *ndev) */ static int axienet_open(struct net_device *ndev) { - int ret = 0, i; + int ret = 0, i = 0; struct axienet_local *lp = netdev_priv(ndev); struct axienet_dma_q *q; u32 reg, err; @@ -1501,17 +1586,17 @@ static int axienet_open(struct net_device *ndev) else phy_start(phydev); } - - /* Enable tasklets for Axi DMA error handling */ - for_each_rx_dma_queue(lp, i) { + if (!lp->is_tsn || lp->temac_no == XAE_TEMAC1) { + /* Enable tasklets for Axi DMA error handling */ + for_each_rx_dma_queue(lp, i) { #ifdef CONFIG_AXIENET_HAS_MCDMA - tasklet_init(&lp->dma_err_tasklet[i], - axienet_mcdma_err_handler, - (unsigned long)lp->dq[i]); + tasklet_init(&lp->dma_err_tasklet[i], + axienet_mcdma_err_handler, + (unsigned long)lp->dq[i]); #else - tasklet_init(&lp->dma_err_tasklet[i], - axienet_dma_err_handler, - (unsigned long)lp->dq[i]); + tasklet_init(&lp->dma_err_tasklet[i], + axienet_dma_err_handler, + (unsigned long)lp->dq[i]); #endif /* Enable NAPI scheduling before enabling Axi DMA Rx IRQ, or you @@ -1554,7 +1639,29 @@ static int axienet_open(struct net_device *ndev) if (ret) goto err_rx_irq; #endif + } } +#ifdef CONFIG_XILINX_TSN_PTP + if (lp->is_tsn) { + INIT_WORK(&lp->tx_tstamp_work, axienet_tx_tstamp); + skb_queue_head_init(&lp->ptp_txq); + + lp->ptp_rx_hw_pointer = 0; + lp->ptp_rx_sw_pointer = 0xff; + + axienet_iow(lp, PTP_RX_CONTROL_OFFSET, PTP_RX_PACKET_CLEAR); + + ret = request_irq(lp->ptp_rx_irq, axienet_ptp_rx_irq, + 0, "ptp_rx", ndev); + if (ret) + goto err_ptp_rx_irq; + + ret = request_irq(lp->ptp_tx_irq, axienet_ptp_tx_irq, + 0, "ptp_tx", ndev); + if (ret) + goto err_ptp_rx_irq; + } +#endif if (lp->phy_mode == XXE_PHY_TYPE_USXGMII) { netdev_dbg(ndev, "RX reg: 0x%x\n", @@ -1656,6 +1763,9 @@ static int axienet_open(struct net_device *ndev) if (phydev) phy_disconnect(phydev); phydev = NULL; +#ifdef CONFIG_XILINX_TSN_PTP +err_ptp_rx_irq: +#endif for_each_rx_dma_queue(lp, i) tasklet_kill(&lp->dma_err_tasklet[i]); dev_err(lp->dev, "request_irq() failed\n"); @@ -1685,38 +1795,39 @@ static int axienet_stop(struct net_device *ndev) lp->axienet_config->setoptions(ndev, lp->options & ~(XAE_OPTION_TXEN | XAE_OPTION_RXEN)); - for_each_tx_dma_queue(lp, i) { - q = lp->dq[i]; - cr = axienet_dma_in32(q, XAXIDMA_RX_CR_OFFSET); - cr &= ~(XAXIDMA_CR_RUNSTOP_MASK | XAXIDMA_IRQ_ALL_MASK); - axienet_dma_out32(q, XAXIDMA_RX_CR_OFFSET, cr); + if (!lp->is_tsn || lp->temac_no == XAE_TEMAC1) { + for_each_tx_dma_queue(lp, i) { + q = lp->dq[i]; + cr = axienet_dma_in32(q, XAXIDMA_RX_CR_OFFSET); + cr &= ~(XAXIDMA_CR_RUNSTOP_MASK | XAXIDMA_IRQ_ALL_MASK); + axienet_dma_out32(q, XAXIDMA_RX_CR_OFFSET, cr); - cr = axienet_dma_in32(q, XAXIDMA_TX_CR_OFFSET); - cr &= ~(XAXIDMA_CR_RUNSTOP_MASK | XAXIDMA_IRQ_ALL_MASK); - axienet_dma_out32(q, XAXIDMA_TX_CR_OFFSET, cr); + cr = axienet_dma_in32(q, XAXIDMA_TX_CR_OFFSET); + cr &= ~(XAXIDMA_CR_RUNSTOP_MASK | XAXIDMA_IRQ_ALL_MASK); + axienet_dma_out32(q, XAXIDMA_TX_CR_OFFSET, cr); - axienet_iow(lp, XAE_IE_OFFSET, 0); + axienet_iow(lp, XAE_IE_OFFSET, 0); - /* Give DMAs a chance to halt gracefully */ - sr = axienet_dma_in32(q, XAXIDMA_RX_SR_OFFSET); - for (count = 0; !(sr & XAXIDMA_SR_HALT_MASK) && count < 5; ++count) { - msleep(20); + /* Give DMAs a chance to halt gracefully */ sr = axienet_dma_in32(q, XAXIDMA_RX_SR_OFFSET); - } + for (count = 0; !(sr & XAXIDMA_SR_HALT_MASK) && count < 5; ++count) { + msleep(20); + sr = axienet_dma_in32(q, XAXIDMA_RX_SR_OFFSET); + } - sr = axienet_dma_in32(q, XAXIDMA_TX_SR_OFFSET); - for (count = 0; !(sr & XAXIDMA_SR_HALT_MASK) && count < 5; ++count) { - msleep(20); sr = axienet_dma_in32(q, XAXIDMA_TX_SR_OFFSET); - } + for (count = 0; !(sr & XAXIDMA_SR_HALT_MASK) && count < 5; ++count) { + msleep(20); + sr = axienet_dma_in32(q, XAXIDMA_TX_SR_OFFSET); + } - /* Do a reset to ensure DMA is really stopped */ - if (lp->axienet_config->mactype != XAXIENET_10G_25G) { - mutex_lock(&lp->mii_bus->mdio_lock); - axienet_mdio_disable(lp); - } + /* Do a reset to ensure DMA is really stopped */ + if (lp->axienet_config->mactype != XAXIENET_10G_25G) { + mutex_lock(&lp->mii_bus->mdio_lock); + axienet_mdio_disable(lp); + } - __axienet_device_reset(q); + __axienet_device_reset(q); if (lp->axienet_config->mactype != XAXIENET_10G_25G) { axienet_mdio_enable(lp); @@ -1732,14 +1843,21 @@ static int axienet_stop(struct net_device *ndev) tasklet_kill(&lp->dma_err_tasklet[i]); free_irq(q->rx_irq, ndev); } - +#ifdef CONFIG_XILINX_TSN_PTP + if (lp->is_tsn) { + free_irq(lp->ptp_tx_irq, ndev); + free_irq(lp->ptp_rx_irq, ndev); + } +#endif if ((lp->axienet_config->mactype == XAXIENET_1G) && !lp->eth_hasnobuf) free_irq(lp->eth_irq, ndev); if (ndev->phydev) phy_disconnect(ndev->phydev); - axienet_dma_bd_release(ndev); + if (lp->temac_no != XAE_TEMAC2) + axienet_dma_bd_release(ndev); + } return 0; } @@ -1807,7 +1925,7 @@ static void axienet_poll_controller(struct net_device *ndev) } #endif -#ifdef CONFIG_XILINX_AXI_EMAC_HWTSTAMP +#if defined(CONFIG_XILINX_AXI_EMAC_HWTSTAMP) || defined(CONFIG_XILINX_TSN_PTP) /** * axienet_set_timestamp_mode - sets up the hardware for the requested mode * @lp: Pointer to axienet local structure @@ -1820,6 +1938,29 @@ static int axienet_set_timestamp_mode(struct axienet_local *lp, { u32 regval; +#ifdef CONFIG_XILINX_TSN_PTP + if (lp->is_tsn) { + /* reserved for future extensions */ + if (config->flags) + return -EINVAL; + + if (config->tx_type < HWTSTAMP_TX_OFF || + config->tx_type > HWTSTAMP_TX_ONESTEP_SYNC) + return -ERANGE; + + lp->ptp_ts_type = config->tx_type; + + /* On RX always timestamp everything */ + switch (config->rx_filter) { + case HWTSTAMP_FILTER_NONE: + break; + default: + config->rx_filter = HWTSTAMP_FILTER_ALL; + } + return 0; + } +#endif + /* reserved for future extensions */ if (config->flags) return -EINVAL; @@ -1914,7 +2055,7 @@ static int axienet_get_ts_config(struct axienet_local *lp, struct ifreq *ifr) /* Ioctl MII Interface */ static int axienet_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) { -#if defined(CONFIG_XILINX_AXI_EMAC_HWTSTAMP) +#if defined(CONFIG_XILINX_AXI_EMAC_HWTSTAMP) || defined(CONFIG_XILINX_TSN_PTP) struct axienet_local *lp = netdev_priv(dev); #endif @@ -1928,12 +2069,19 @@ static int axienet_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) if (!dev->phydev) return -EOPNOTSUPP; return phy_mii_ioctl(dev->phydev, rq, cmd); -#ifdef CONFIG_XILINX_AXI_EMAC_HWTSTAMP +#if defined(CONFIG_XILINX_AXI_EMAC_HWTSTAMP) || defined(CONFIG_XILINX_TSN_PTP) case SIOCSHWTSTAMP: return axienet_set_ts_config(lp, rq); case SIOCGHWTSTAMP: return axienet_get_ts_config(lp, rq); #endif +#ifdef CONFIG_XILINX_TSN_QBV + case SIOCCHIOCTL: + return axienet_set_schedule(dev, rq->ifr_data); + case SIOC_GET_SCHED: + return axienet_get_schedule(dev, rq->ifr_data); +#endif + default: return -EOPNOTSUPP; } @@ -2230,7 +2378,7 @@ static int axienet_ethtools_set_coalesce(struct net_device *ndev, return 0; } -#ifdef CONFIG_XILINX_AXI_EMAC_HWTSTAMP +#if defined(CONFIG_XILINX_AXI_EMAC_HWTSTAMP) || defined(CONFIG_XILINX_TSN_PTP) /** * axienet_ethtools_get_ts_info - Get h/w timestamping capabilities. * @ndev: Pointer to net_device structure @@ -2249,6 +2397,9 @@ static int axienet_ethtools_get_ts_info(struct net_device *ndev, (1 << HWTSTAMP_FILTER_ALL); info->phc_index = 0; +#ifdef CONFIG_XILINX_TSN_PTP + info->phc_index = axienet_phc_index; +#endif return 0; } #endif @@ -2264,7 +2415,7 @@ static const struct ethtool_ops axienet_ethtool_ops = { .set_pauseparam = axienet_ethtools_set_pauseparam, .get_coalesce = axienet_ethtools_get_coalesce, .set_coalesce = axienet_ethtools_set_coalesce, -#ifdef CONFIG_XILINX_AXI_EMAC_HWTSTAMP +#if defined(CONFIG_XILINX_AXI_EMAC_HWTSTAMP) || defined(CONFIG_XILINX_TSN_PTP) .get_ts_info = axienet_ethtools_get_ts_info, #endif .get_link_ksettings = phy_ethtool_get_link_ksettings, @@ -2724,6 +2875,7 @@ static const struct of_device_id axienet_of_match[] = { { .compatible = "xlnx,ten-gig-eth-mac", .data = &axienet_10g_config}, { .compatible = "xlnx,xxv-ethernet-1.0", .data = &axienet_10g25g_config}, + { .compatible = "xlnx,tsn-ethernet-1.00.a", .data = &axienet_1g_config}, { .compatible = "xlnx,xxv-usxgmii-ethernet-1.0", .data = &axienet_usxgmii_config}, {}, @@ -2760,14 +2912,23 @@ static int axienet_probe(struct platform_device *pdev) u32 value; u16 num_queues = XAE_MAX_QUEUES; bool slave = false; + bool is_tsn = false; + is_tsn = of_property_read_bool(pdev->dev.of_node, "xlnx,tsn"); ret = of_property_read_u16(pdev->dev.of_node, "xlnx,num-queues", &num_queues); if (ret) { + if (!is_tsn) { #ifndef CONFIG_AXIENET_HAS_MCDMA - num_queues = 1; + num_queues = 1; #endif + } } +#ifdef CONFIG_XILINX_TSN + if (is_tsn && (num_queues < XAE_TSN_MIN_QUEUES || + num_queues > XAE_MAX_QUEUES)) + num_queues = XAE_MAX_QUEUES; +#endif ndev = alloc_etherdev_mq(sizeof(*lp), num_queues); if (!ndev) @@ -2791,9 +2952,17 @@ static int axienet_probe(struct platform_device *pdev) lp->options = XAE_OPTION_DEFAULTS; lp->num_tx_queues = num_queues; lp->num_rx_queues = num_queues; + lp->is_tsn = is_tsn; lp->rx_bd_num = RX_BD_NUM_DEFAULT; lp->tx_bd_num = TX_BD_NUM_DEFAULT; +#ifdef CONFIG_XILINX_TSN + ret = of_property_read_u16(pdev->dev.of_node, "xlnx,num-tc", + &lp->num_tc); + if (ret || (lp->num_tc != 2 && lp->num_tc != 3)) + lp->num_tc = XAE_MAX_TSN_TC; +#endif + /* Map device registers */ ethres = platform_get_resource(pdev, IORESOURCE_MEM, 0); lp->regs = devm_ioremap_resource(&pdev->dev, ethres); @@ -2801,6 +2970,14 @@ static int axienet_probe(struct platform_device *pdev) ret = PTR_ERR(lp->regs); goto free_netdev; } +#ifdef CONFIG_XILINX_TSN + slave = of_property_read_bool(pdev->dev.of_node, + "xlnx,tsn-slave"); + if (slave) + lp->temac_no = XAE_TEMAC2; + else + lp->temac_no = XAE_TEMAC1; +#endif lp->regs_start = ethres->start; /* Setup checksum offload, but default to off if not specified */ @@ -2887,58 +3064,64 @@ static int axienet_probe(struct platform_device *pdev) lp->eth_irq = platform_get_irq(pdev, 0); #ifdef CONFIG_XILINX_AXI_EMAC_HWTSTAMP - struct resource txtsres, rxtsres; + if (!lp->is_tsn) { + struct resource txtsres, rxtsres; - /* Find AXI Stream FIFO */ - np = of_parse_phandle(pdev->dev.of_node, "axififo-connected", 0); - if (IS_ERR(np)) { - dev_err(&pdev->dev, "could not find TX Timestamp FIFO\n"); - ret = PTR_ERR(np); - goto free_netdev; - } - - ret = of_address_to_resource(np, 0, &txtsres); - if (ret) { - dev_err(&pdev->dev, "unable to get Tx Timestamp resource\n"); - goto free_netdev; - } - - lp->tx_ts_regs = devm_ioremap_resource(&pdev->dev, &txtsres); - if (IS_ERR(lp->tx_ts_regs)) { - dev_err(&pdev->dev, "could not map Tx Timestamp regs\n"); - ret = PTR_ERR(lp->tx_ts_regs); - goto free_netdev; - } - - if (lp->axienet_config->mactype == XAXIENET_10G_25G) { - np = of_parse_phandle(pdev->dev.of_node, "xlnx,rxtsfifo", + /* Find AXI Stream FIFO */ + np = of_parse_phandle(pdev->dev.of_node, "axififo-connected", 0); if (IS_ERR(np)) { - dev_err(&pdev->dev, - "couldn't find rx-timestamp FIFO\n"); + dev_err(&pdev->dev, "could not find TX Timestamp FIFO\n"); ret = PTR_ERR(np); goto free_netdev; } - ret = of_address_to_resource(np, 0, &rxtsres); + ret = of_address_to_resource(np, 0, &txtsres); if (ret) { dev_err(&pdev->dev, - "unable to get rx-timestamp resource\n"); + "unable to get Tx Timestamp resource\n"); goto free_netdev; } - lp->rx_ts_regs = devm_ioremap_resource(&pdev->dev, &rxtsres); - if (IS_ERR(lp->rx_ts_regs)) { - dev_err(&pdev->dev, "couldn't map rx-timestamp regs\n"); - ret = PTR_ERR(lp->rx_ts_regs); + lp->tx_ts_regs = devm_ioremap_resource(&pdev->dev, &txtsres); + if (IS_ERR(lp->tx_ts_regs)) { + dev_err(&pdev->dev, "could not map Tx Timestamp regs\n"); + ret = PTR_ERR(lp->tx_ts_regs); goto free_netdev; } - lp->tx_ptpheader = devm_kzalloc(&pdev->dev, + + if (lp->axienet_config->mactype == XAXIENET_10G_25G) { + np = of_parse_phandle(pdev->dev.of_node, + "xlnx,rxtsfifo", 0); + if (IS_ERR(np)) { + dev_err(&pdev->dev, + "couldn't find rx-timestamp FIFO\n"); + ret = PTR_ERR(np); + goto free_netdev; + } + + ret = of_address_to_resource(np, 0, &rxtsres); + if (ret) { + dev_err(&pdev->dev, + "unable to get rx-timestamp resource\n"); + goto free_netdev; + } + + lp->rx_ts_regs = devm_ioremap_resource(&pdev->dev, + &rxtsres); + if (IS_ERR(lp->rx_ts_regs)) { + dev_err(&pdev->dev, + "couldn't map rx-timestamp regs\n"); + ret = PTR_ERR(lp->rx_ts_regs); + goto free_netdev; + } + lp->tx_ptpheader = devm_kzalloc(&pdev->dev, XXVENET_TS_HEADER_LEN, GFP_KERNEL); - } + } - of_node_put(np); + of_node_put(np); + } #endif if (!slave) { #ifdef CONFIG_AXIENET_HAS_MCDMA @@ -3037,6 +3220,33 @@ static int axienet_probe(struct platform_device *pdev) goto err_disable_clk; } +#ifdef CONFIG_XILINX_TSN_PTP + if (lp->is_tsn) { + lp->ptp_rx_irq = platform_get_irq_byname(pdev, "ptp_rx"); + + lp->ptp_tx_irq = platform_get_irq_byname(pdev, "ptp_tx"); + + lp->qbv_irq = platform_get_irq_byname(pdev, "qbv_irq"); + + pr_debug("ptp RX irq: %d\n", lp->ptp_rx_irq); + pr_debug("ptp TX irq: %d\n", lp->ptp_tx_irq); + pr_debug("qbv_irq: %d\n", lp->qbv_irq); + + spin_lock_init(&lp->ptp_tx_lock); + + if (lp->temac_no == XAE_TEMAC1) { + axienet_ptp_timer_probe((lp->regs + XAE_RTC_OFFSET), + pdev); + + /* enable VLAN */ + lp->options |= XAE_OPTION_VLAN; + axienet_setoptions(lp->ndev, lp->options); +#ifdef CONFIG_XILINX_TSN_QBV + axienet_qbv_init(ndev); +#endif + } + } +#endif return 0; err_disable_clk: @@ -3053,10 +3263,18 @@ static int axienet_remove(struct platform_device *pdev) struct axienet_local *lp = netdev_priv(ndev); int i; - for_each_rx_dma_queue(lp, i) - netif_napi_del(&lp->napi[i]); - unregister_netdev(ndev); - axienet_clk_disable(pdev); + if (!lp->is_tsn || lp->temac_no == XAE_TEMAC1) { + for_each_rx_dma_queue(lp, i) + netif_napi_del(&lp->napi[i]); + } +#ifdef CONFIG_XILINX_TSN_PTP + axienet_ptp_timer_remove(lp->timer_priv); +#ifdef CONFIG_XILINX_TSN_QBV + axienet_qbv_remove(ndev); +#endif +#endif + unregister_netdev(ndev); + axienet_clk_disable(pdev); if (lp->mii_bus) axienet_mdio_teardown(lp); diff --git a/drivers/net/ethernet/xilinx/xilinx_tsn_ptp.h b/drivers/net/ethernet/xilinx/xilinx_tsn_ptp.h new file mode 100644 index 0000000000000..d81b0acf12f0f --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_tsn_ptp.h @@ -0,0 +1,88 @@ +/* + * Xilinx TSN PTP header + * + * Copyright (C) 2017 Xilinx, Inc. + * + * Author: Syed S + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _TSN_PTP_H_ +#define _TSN_PTP_H_ + +#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) + +#define PTP_MSG_TYPE_MASK BIT(3) +#define PTP_TYPE_SYNC 0x0 +#define PTP_TYPE_FOLLOW_UP 0x8 +#define PTP_TYPE_PDELAYREQ 0x2 +#define PTP_TYPE_PDELAYRESP 0x3 +#define PTP_TYPE_PDELAYRESP_FOLLOW_UP 0xA +#define PTP_TYPE_ANNOUNCE 0xB +#define PTP_TYPE_SIGNALING 0xC + +#define PTP_TX_CONTROL_OFFSET 0x00012000 /**< Tx PTP Control Reg */ +#define PTP_RX_CONTROL_OFFSET 0x00012004 /**< Rx PTP Control Reg */ +#define RX_FILTER_CONTROL 0x00012008 /**< Rx Filter Ctrl Reg */ + +#define PTP_RX_BASE_OFFSET 0x00010000 +#define PTP_RX_CONTROL_OFFSET 0x00012004 /**< Rx PTP Control Reg */ +#define PTP_RX_PACKET_FIELD_MASK 0x00000F00 +#define PTP_RX_PACKET_CLEAR 0x00000001 + +#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 + +#define PTP_TX_SYNC_OFFSET 0x00011000 +#define PTP_TX_FOLLOW_UP_OFFSET 0x00011100 +#define PTP_TX_PDELAYREQ_OFFSET 0x00011200 +#define PTP_TX_PDELAYRESP_OFFSET 0x00011300 +#define PTP_TX_PDELAYRESP_FOLLOW_UP_OFFSET 0x00011400 +#define PTP_TX_ANNOUNCE_OFFSET 0x00011500 +#define PTP_TX_SIGNALING_OFFSET 0x00011600 +#define PTP_TX_GENERIC_OFFSET 0x00011700 +#define PTP_TX_SEND_SYNC_FRAME_MASK 0x00000001 +#define PTP_TX_SEND_FOLLOWUP_FRAME_MASK 0x00000002 +#define PTP_TX_SEND_PDELAYREQ_FRAME_MASK 0x00000004 +#define PTP_TX_SEND_PDELAYRESP_FRAME_MASK 0x00000008 +#define PTP_TX_SEND_PDELAYRESPFOLLOWUP_FRAME_MASK 0x00000010 +#define PTP_TX_SEND_ANNOUNCE_FRAME_MASK 0x00000020 +#define PTP_TX_SEND_FRAME6_BIT_MASK 0x00000040 +#define PTP_TX_SEND_FRAME7_BIT_MASK 0x00000080 +#define PTP_TX_FRAME_WAITING_MASK 0x0000ff00 +#define PTP_TX_FRAME_WAITING_SHIFT 8 +#define PTP_TX_WAIT_SYNC_FRAME_MASK 0x00000100 +#define PTP_TX_WAIT_FOLLOWUP_FRAME_MASK 0x00000200 +#define PTP_TX_WAIT_PDELAYREQ_FRAME_MASK 0x00000400 +#define PTP_TX_WAIT_PDELAYRESP_FRAME_MASK 0x00000800 +#define PTP_TX_WAIT_PDELAYRESPFOLLOWUP_FRAME_MASK 0x00001000 +#define PTP_TX_WAIT_ANNOUNCE_FRAME_MASK 0x00002000 +#define PTP_TX_WAIT_FRAME6_BIT_MASK 0x00004000 +#define PTP_TX_WAIT_FRAME7_BIT_MASK 0x00008000 +#define PTP_TX_WAIT_ALL_FRAMES_MASK 0x0000FF00 +#define PTP_TX_PACKET_FIELD_MASK 0x00070000 +#define PTP_TX_PACKET_FIELD_SHIFT 16 +/* 1-step Correction Field offset 802.1 ASrev */ +#define PTP_CRCT_FIELD_OFFSET 22 +/* 1-step Time Of Day offset 1588-2008 */ +#define PTP_TOD_FIELD_OFFSET 48 + +int axienet_ptp_xmit(struct sk_buff *skb, struct net_device *ndev); +irqreturn_t axienet_ptp_rx_irq(int irq, void *_ndev); +irqreturn_t axienet_ptp_tx_irq(int irq, void *_ndev); + +#endif diff --git a/drivers/net/ethernet/xilinx/xilinx_tsn_ptp_clock.c b/drivers/net/ethernet/xilinx/xilinx_tsn_ptp_clock.c new file mode 100644 index 0000000000000..05c9060196942 --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_tsn_ptp_clock.c @@ -0,0 +1,325 @@ +/* + * Xilinx FPGA Xilinx TSN PTP protocol clock Controller module. + * + * Copyright (c) 2017 Xilinx Pvt., Ltd + * + * Author: Syed S + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xilinx_tsn_timer.h" + +struct xlnx_ptp_timer { + struct device *dev; + void __iomem *baseaddr; + struct ptp_clock *ptp_clock; + struct ptp_clock_info ptp_clock_info; + spinlock_t reg_lock; /* ptp timer lock */ + int irq; + int pps_enable; + int countpulse; +}; + +static void xlnx_tod_read(struct xlnx_ptp_timer *timer, struct timespec64 *ts) +{ + u32 sec, nsec; + + nsec = in_be32(timer->baseaddr + XTIMER1588_CURRENT_RTC_NS); + sec = in_be32(timer->baseaddr + XTIMER1588_CURRENT_RTC_SEC_L); + + ts->tv_sec = sec; + ts->tv_nsec = nsec; +} + +static void xlnx_rtc_offset_write(struct xlnx_ptp_timer *timer, + const struct timespec64 *ts) +{ + pr_debug("%s: sec: %ld nsec: %ld\n", __func__, ts->tv_sec, ts->tv_nsec); + + out_be32((timer->baseaddr + XTIMER1588_RTC_OFFSET_SEC_H), 0); + out_be32((timer->baseaddr + XTIMER1588_RTC_OFFSET_SEC_L), + (ts->tv_sec)); + out_be32((timer->baseaddr + XTIMER1588_RTC_OFFSET_NS), ts->tv_nsec); +} + +static void xlnx_rtc_offset_read(struct xlnx_ptp_timer *timer, + struct timespec64 *ts) +{ + ts->tv_sec = in_be32(timer->baseaddr + XTIMER1588_RTC_OFFSET_SEC_L); + ts->tv_nsec = in_be32(timer->baseaddr + XTIMER1588_RTC_OFFSET_NS); +} + +/* PTP clock operations + */ +static int xlnx_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb) +{ + struct xlnx_ptp_timer *timer = container_of(ptp, struct xlnx_ptp_timer, + ptp_clock_info); + + int neg_adj = 0; + u64 freq; + u32 diff, incval; + + /* This number should be replaced by a call to get the frequency + * from the device-tree. Currently assumes 125MHz + */ + incval = 0x800000; + /* for 156.25 MHZ Ref clk the value is incval = 0x800000; */ + + if (ppb < 0) { + neg_adj = 1; + ppb = -ppb; + } + + freq = incval; + freq *= ppb; + diff = div_u64(freq, 1000000000ULL); + + pr_debug("%s: adj: %d ppb: %d\n", __func__, diff, ppb); + + incval = neg_adj ? (incval - diff) : (incval + diff); + out_be32((timer->baseaddr + XTIMER1588_RTC_INCREMENT), incval); + return 0; +} + +static int xlnx_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + unsigned long flags; + struct xlnx_ptp_timer *timer = container_of(ptp, struct xlnx_ptp_timer, + ptp_clock_info); + struct timespec64 now, then = ns_to_timespec64(delta); + + spin_lock_irqsave(&timer->reg_lock, flags); + + xlnx_rtc_offset_read(timer, &now); + + now = timespec64_add(now, then); + + xlnx_rtc_offset_write(timer, (const struct timespec64 *)&now); + spin_unlock_irqrestore(&timer->reg_lock, flags); + + return 0; +} + +static int xlnx_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts) +{ + unsigned long flags; + struct xlnx_ptp_timer *timer = container_of(ptp, struct xlnx_ptp_timer, + ptp_clock_info); + spin_lock_irqsave(&timer->reg_lock, flags); + + xlnx_tod_read(timer, ts); + + spin_unlock_irqrestore(&timer->reg_lock, flags); + return 0; +} + +/** + * xlnx_ptp_settime - Set the current time on the hardware clock + * @ptp: ptp clock structure + * @ts: timespec64 containing the new time for the cycle counter + * + * Return: 0 in all cases. + * + * The seconds register is written first, then the nanosecond + * The hardware loads the entire new value when a nanosecond register + * is written + **/ +static int xlnx_ptp_settime(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + struct xlnx_ptp_timer *timer = container_of(ptp, struct xlnx_ptp_timer, + ptp_clock_info); + struct timespec64 delta, tod; + struct timespec64 offset; + unsigned long flags; + + spin_lock_irqsave(&timer->reg_lock, flags); + + /* First zero the offset */ + offset.tv_sec = 0; + offset.tv_nsec = 0; + xlnx_rtc_offset_write(timer, &offset); + + /* Get the current timer value */ + xlnx_tod_read(timer, &tod); + + /* Subtract the current reported time from our desired time */ + delta = timespec64_sub(*ts, tod); + + /* Don't write a negative offset */ + if (delta.tv_sec <= 0) { + delta.tv_sec = 0; + if (delta.tv_nsec < 0) + delta.tv_nsec = 0; + } + + xlnx_rtc_offset_write(timer, &delta); + spin_unlock_irqrestore(&timer->reg_lock, flags); + return 0; +} + +static int xlnx_ptp_enable(struct ptp_clock_info *ptp, + struct ptp_clock_request *rq, int on) +{ + struct xlnx_ptp_timer *timer = container_of(ptp, struct xlnx_ptp_timer, + ptp_clock_info); + + switch (rq->type) { + case PTP_CLK_REQ_PPS: + timer->pps_enable = 1; + return 0; + default: + break; + } + + return -EOPNOTSUPP; +} + +static struct ptp_clock_info xlnx_ptp_clock_info = { + .owner = THIS_MODULE, + .name = "Xilinx Timer", + .max_adj = 999999999, + .n_ext_ts = 0, + .pps = 1, + .adjfreq = xlnx_ptp_adjfreq, + .adjtime = xlnx_ptp_adjtime, + .gettime64 = xlnx_ptp_gettime, + .settime64 = xlnx_ptp_settime, + .enable = xlnx_ptp_enable, +}; + +/* module operations */ + +/** + * xlnx_ptp_timer_isr - Interrupt Service Routine + * @irq: IRQ number + * @priv: pointer to the timer structure + * + * Returns: IRQ_HANDLED for all cases + * + * Handles the timer interrupt. The timer interrupt fires 128 times per + * secound. When our count reaches 128 emit a PTP_CLOCK_PPS event + */ +static irqreturn_t xlnx_ptp_timer_isr(int irq, void *priv) +{ + struct xlnx_ptp_timer *timer = (struct xlnx_ptp_timer *)priv; + struct ptp_clock_event event; + + event.type = PTP_CLOCK_PPS; + ++timer->countpulse; + if (timer->countpulse >= PULSESIN1PPS) { + timer->countpulse = 0; + if ((timer->ptp_clock) && (timer->pps_enable)) + ptp_clock_event(timer->ptp_clock, &event); + } + out_be32((timer->baseaddr + XTIMER1588_INTERRUPT), + (1 << XTIMER1588_INT_SHIFT)); + + return IRQ_HANDLED; +} + +int axienet_ptp_timer_remove(void *priv) +{ + struct xlnx_ptp_timer *timer = (struct xlnx_ptp_timer *)priv; + + free_irq(timer->irq, (void *)timer); + + axienet_phc_index = -1; + if (timer->ptp_clock) { + ptp_clock_unregister(timer->ptp_clock); + timer->ptp_clock = NULL; + } + kfree(timer); + return 0; +} + +int axienet_get_phc_index(void *priv) +{ + struct xlnx_ptp_timer *timer = (struct xlnx_ptp_timer *)priv; + + if (timer->ptp_clock) + return ptp_clock_index(timer->ptp_clock); + else + return -1; +} + +void *axienet_ptp_timer_probe(void __iomem *base, struct platform_device *pdev) +{ + struct xlnx_ptp_timer *timer; + struct timespec64 ts; + int err = 0; + + timer = kzalloc(sizeof(*timer), GFP_KERNEL); + if (!timer) + return NULL; + + timer->baseaddr = base; + + timer->irq = platform_get_irq_byname(pdev, "interrupt_ptp_timer"); + + if (timer->irq < 0) { + timer->irq = platform_get_irq_byname(pdev, "rtc_irq"); + if (timer->irq > 0) { + pr_err("ptp timer interrupt name 'rtc_irq' is" + "deprecated\n"); + } else { + pr_err("ptp timer interrupt not found\n"); + kfree(timer); + return NULL; + } + } + spin_lock_init(&timer->reg_lock); + + timer->ptp_clock_info = xlnx_ptp_clock_info; + + timer->ptp_clock = ptp_clock_register(&timer->ptp_clock_info, + &pdev->dev); + + if (IS_ERR(timer->ptp_clock)) { + err = PTR_ERR(timer->ptp_clock); + pr_debug("Failed to register ptp clock\n"); + goto out; + } + + axienet_phc_index = ptp_clock_index(timer->ptp_clock); + + ts = ktime_to_timespec64(ktime_get_real()); + + xlnx_ptp_settime(&timer->ptp_clock_info, &ts); + + /* Enable interrupts */ + err = request_irq(timer->irq, + xlnx_ptp_timer_isr, + 0, + "ptp_rtc", + (void *)timer); + if (err) + goto err_irq; + + return timer; + +err_irq: + ptp_clock_unregister(timer->ptp_clock); +out: + timer->ptp_clock = NULL; + return NULL; +} diff --git a/drivers/net/ethernet/xilinx/xilinx_tsn_ptp_xmit.c b/drivers/net/ethernet/xilinx/xilinx_tsn_ptp_xmit.c new file mode 100644 index 0000000000000..831b4b7b50852 --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_tsn_ptp_xmit.c @@ -0,0 +1,369 @@ +/* + * Xilinx FPGA Xilinx TSN PTP transfer protocol module. + * + * Copyright (c) 2017 Xilinx Pvt., Ltd + * + * Author: Syed S + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "xilinx_axienet.h" +#include "xilinx_tsn_ptp.h" +#include "xilinx_tsn_timer.h" +#include + +#define PTP_ONE_SECOND 1000000000 /**< Value in ns */ + +#define msg_type_string(type) \ + ((type) == PTP_TYPE_SYNC) ? "SYNC" : \ + ((type) == PTP_TYPE_FOLLOW_UP) ? "FOLLOW_UP" : \ + ((type) == PTP_TYPE_PDELAYREQ) ? "PDELAY_REQ" : \ + ((type) == PTP_TYPE_PDELAYRESP) ? "PDELAY_RESP" : \ + ((type) == PTP_TYPE_PDELAYRESP_FOLLOW_UP) ? "PDELAY_RESP_FOLLOW_UP" : \ + ((type) == PTP_TYPE_ANNOUNCE) ? "ANNOUNCE" : \ + "UNKNOWN" + +/** + * memcpy_fromio_32 - copy ptp buffer from HW + * @lp: Pointer to axienet local structure + * @offset: Offset in the PTP buffer + * @data: Destination buffer + * @len: Len to copy + * + * This functions copies the data from PTP buffer to destination data buffer + */ +static void memcpy_fromio_32(struct axienet_local *lp, + unsigned long offset, u8 *data, size_t len) +{ + while (len >= 4) { + *(u32 *)data = axienet_ior(lp, offset); + len -= 4; + offset += 4; + data += 4; + } + + if (len > 0) { + u32 leftover = axienet_ior(lp, offset); + u8 *src = (u8 *)&leftover; + + while (len) { + *data++ = *src++; + len--; + } + } +} + +/** + * memcpy_toio_32 - copy ptp buffer from HW + * @lp: Pointer to axienet local structure + * @offset: Offset in the PTP buffer + * @data: Source data + * @len: Len to copy + * + * This functions copies the source data to desination ptp buffer + */ +static void memcpy_toio_32(struct axienet_local *lp, + unsigned long offset, u8 *data, size_t len) +{ + while (len >= 4) { + axienet_iow(lp, offset, *(u32 *)data); + len -= 4; + offset += 4; + data += 4; + } + + if (len > 0) { + u32 leftover = 0; + u8 *dest = (u8 *)&leftover; + + while (len) { + *dest++ = *data++; + len--; + } + axienet_iow(lp, offset, leftover); + } +} + +static int is_sync(struct sk_buff *skb) +{ + u8 *msg_type; + + msg_type = (u8 *)skb->data + ETH_HLEN; + + return (*msg_type & 0xf) == PTP_TYPE_SYNC; +} + +/** + * axienet_ptp_xmit - xmit skb using PTP HW + * @skb: sk_buff pointer that contains data to be Txed. + * @ndev: Pointer to net_device 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 axienet_ptp_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + u8 msg_type; + struct axienet_local *lp = netdev_priv(ndev); + unsigned long flags; + u8 tx_frame_waiting; + u8 free_index; + u32 cmd1_field = 0; + u32 cmd2_field = 0; + + msg_type = *(u8 *)(skb->data + ETH_HLEN); + + pr_debug(" -->XMIT: protocol: %x message: %s frame_len: %d\n", + skb->protocol, + msg_type_string(msg_type & 0xf), skb->len); + + tx_frame_waiting = (axienet_ior(lp, PTP_TX_CONTROL_OFFSET) & + PTP_TX_FRAME_WAITING_MASK) >> + PTP_TX_FRAME_WAITING_SHIFT; + + /* we reached last frame */ + if (tx_frame_waiting & (1 << 7)) { + if (!netif_queue_stopped(ndev)) + netif_stop_queue(ndev); + pr_debug("tx_frame_waiting: %d\n", tx_frame_waiting); + return NETDEV_TX_BUSY; + } + + /* go to next available slot */ + free_index = fls(tx_frame_waiting); + + /* write the len */ + if (lp->ptp_ts_type == HWTSTAMP_TX_ONESTEP_SYNC && + is_sync(skb)) { + /* enable 1STEP SYNC */ + cmd1_field |= PTP_TX_CMD_1STEP_SHIFT; + cmd2_field |= PTP_TOD_FIELD_OFFSET; + } + + cmd1_field |= skb->len; + + axienet_iow(lp, PTP_TX_BUFFER_OFFSET(free_index), cmd1_field); + axienet_iow(lp, PTP_TX_BUFFER_OFFSET(free_index) + + PTP_TX_BUFFER_CMD2_FIELD, cmd2_field); + memcpy_toio_32(lp, + (PTP_TX_BUFFER_OFFSET(free_index) + + PTP_TX_CMD_FIELD_LEN), + skb->data, skb->len); + + /* send the frame */ + axienet_iow(lp, PTP_TX_CONTROL_OFFSET, (1 << free_index)); + + if (lp->ptp_ts_type != HWTSTAMP_TX_ONESTEP_SYNC || + (!is_sync(skb))) { + spin_lock_irqsave(&lp->ptp_tx_lock, flags); + skb->cb[0] = free_index; + skb_queue_tail(&lp->ptp_txq, skb); + + if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) + skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; + + skb_tx_timestamp(skb); + spin_unlock_irqrestore(&lp->ptp_tx_lock, flags); + } + return NETDEV_TX_OK; +} + +/** + * axienet_set_timestamp - timestamp skb with HW timestamp + * @lp: Pointer to axienet local structure + * @hwtstamps: Pointer to skb timestamp structure + * @offset: offset of the timestamp in the PTP buffer + * + * Return: None. + * + */ +static void axienet_set_timestamp(struct axienet_local *lp, + struct skb_shared_hwtstamps *hwtstamps, + unsigned int offset) +{ + u32 captured_ns; + u32 captured_sec; + + captured_ns = axienet_ior(lp, offset + 4); + captured_sec = axienet_ior(lp, offset); + + /* Upper 32 bits contain s, lower 32 bits contain ns. */ + hwtstamps->hwtstamp = ktime_set(captured_sec, + captured_ns); +} + +/** + * axienet_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 axienet_ptp_recv(struct net_device *ndev) +{ + struct axienet_local *lp = 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; + + pr_debug("%s:\n ", __func__); + + while (((lp->ptp_rx_hw_pointer & 0xf) != + (lp->ptp_rx_sw_pointer & 0xf))) { + skb = netdev_alloc_skb(ndev, PTP_RX_FRAME_SIZE); + + lp->ptp_rx_sw_pointer += 1; + + ptp_frame_base_addr = PTP_RX_BASE_OFFSET + + ((lp->ptp_rx_sw_pointer & 0xf) * + PTP_RX_HWBUF_SIZE); + + memset(skb->data, 0x0, PTP_RX_FRAME_SIZE); + + memcpy_fromio_32(lp, 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; + + pr_debug(" -->RECV: protocol: %x message: %s frame_len: %d\n", + skb->protocol, msg_type_string(msg_type & 0xf), + skb->len); + /* timestamp only event messages */ + if (!(msg_type & PTP_MSG_TYPE_MASK)) { + axienet_set_timestamp(lp, skb_hwtstamps(skb), + (ptp_frame_base_addr + + PTP_HW_TSTAMP_OFFSET)); + } + + netif_rx(skb); + } + ndev->stats.rx_packets += packets; + ndev->stats.rx_bytes += bytes; +} + +/** + * axienet_ptp_rx_irq - PTP RX ISR handler + * @irq: irq number + * @_ndev: net_device pointer + * + * Return: IRQ_HANDLED for all cases. + */ +irqreturn_t axienet_ptp_rx_irq(int irq, void *_ndev) +{ + struct net_device *ndev = _ndev; + struct axienet_local *lp = netdev_priv(ndev); + + pr_debug("%s: received\n ", __func__); + lp->ptp_rx_hw_pointer = (axienet_ior(lp, PTP_RX_CONTROL_OFFSET) + & PTP_RX_PACKET_FIELD_MASK) >> 8; + + axienet_ptp_recv(ndev); + + return IRQ_HANDLED; +} + +/** + * axienet_tx_tstamp - timestamp skb on trasmit path + * @work: Pointer to work_struct structure + * + * This adds TX timestamp to skb + */ +void axienet_tx_tstamp(struct work_struct *work) +{ + struct axienet_local *lp = container_of(work, struct axienet_local, + tx_tstamp_work); + struct net_device *ndev = lp->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(&lp->ptp_tx_lock, flags); + + tx_packet = (axienet_ior(lp, PTP_TX_CONTROL_OFFSET) & + PTP_TX_PACKET_FIELD_MASK) >> + PTP_TX_PACKET_FIELD_SHIFT; + + while ((skb = __skb_dequeue(&lp->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(&lp->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) { + axienet_set_timestamp(lp, &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(&lp->ptp_tx_lock, flags); +} + +/** + * axienet_ptp_tx_irq - PTP TX irq handler + * @irq: irq number + * @_ndev: net_device pointer + * + * Return: IRQ_HANDLED for all cases. + * + */ +irqreturn_t axienet_ptp_tx_irq(int irq, void *_ndev) +{ + struct net_device *ndev = _ndev; + struct axienet_local *lp = netdev_priv(ndev); + + pr_debug("%s: got tx interrupt\n", __func__); + + /* read ctrl register to clear the interrupt */ + axienet_ior(lp, PTP_TX_CONTROL_OFFSET); + + schedule_work(&lp->tx_tstamp_work); + + netif_wake_queue(ndev); + + return IRQ_HANDLED; +} diff --git a/drivers/net/ethernet/xilinx/xilinx_tsn_shaper.c b/drivers/net/ethernet/xilinx/xilinx_tsn_shaper.c new file mode 100644 index 0000000000000..e7a054b78a6ed --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_tsn_shaper.c @@ -0,0 +1,232 @@ +/* + * Xilinx FPGA Xilinx TSN QBV sheduler module. + * + * Copyright (c) 2017 Xilinx Pvt., Ltd + * + * Author: Syed S + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "xilinx_axienet.h" +#include "xilinx_tsn_shaper.h" + +static inline int axienet_map_gs_to_hw(struct axienet_local *lp, u32 gs) +{ + u8 be_queue = 0; + u8 re_queue = 1; + u8 st_queue = 2; + unsigned int acl_bit_map = 0; + + if (lp->num_tc == 2) + st_queue = 1; + + if (gs & GS_BE_OPEN) + acl_bit_map |= (1 << be_queue); + if (gs & GS_ST_OPEN) + acl_bit_map |= (1 << st_queue); + if (lp->num_tc == 3 && (gs & GS_RE_OPEN)) + acl_bit_map |= (1 << re_queue); + + return acl_bit_map; +} + +static int __axienet_set_schedule(struct net_device *ndev, struct qbv_info *qbv) +{ + struct axienet_local *lp = netdev_priv(ndev); + u16 i; + unsigned int acl_bit_map = 0; + u32 u_config_change = 0; + u8 port = qbv->port; + + if (qbv->cycle_time == 0) { + /* clear the gate enable bit */ + u_config_change &= ~CC_ADMIN_GATE_ENABLE_BIT; + /* open all the gates */ + u_config_change |= CC_ADMIN_GATE_STATE_SHIFT; + + axienet_iow(lp, CONFIG_CHANGE(port), u_config_change); + + return 0; + } + + if (axienet_ior(lp, PORT_STATUS(port)) & 1) { + if (qbv->force) { + u_config_change &= ~CC_ADMIN_GATE_ENABLE_BIT; + axienet_iow(lp, CONFIG_CHANGE(port), u_config_change); + } else { + return -EALREADY; + } + } + /* write admin time */ + axienet_iow(lp, ADMIN_CYCLE_TIME_DENOMINATOR(port), + qbv->cycle_time & CYCLE_TIME_DENOMINATOR_MASK); + + axienet_iow(lp, ADMIN_BASE_TIME_NS(port), qbv->ptp_time_ns); + + axienet_iow(lp, ADMIN_BASE_TIME_SEC(port), + qbv->ptp_time_sec & 0xFFFFFFFF); + axienet_iow(lp, ADMIN_BASE_TIME_SECS(port), + (qbv->ptp_time_sec >> 32) & BASE_TIME_SECS_MASK); + + u_config_change = axienet_ior(lp, CONFIG_CHANGE(port)); + + u_config_change &= ~(CC_ADMIN_CTRL_LIST_LENGTH_MASK << + CC_ADMIN_CTRL_LIST_LENGTH_SHIFT); + u_config_change |= (qbv->list_length & CC_ADMIN_CTRL_LIST_LENGTH_MASK) + << CC_ADMIN_CTRL_LIST_LENGTH_SHIFT; + + /* program each list */ + for (i = 0; i < qbv->list_length; i++) { + acl_bit_map = axienet_map_gs_to_hw(lp, qbv->acl_gate_state[i]); + axienet_iow(lp, ADMIN_CTRL_LIST(port, i), + (acl_bit_map & (ACL_GATE_STATE_MASK)) << + ACL_GATE_STATE_SHIFT); + + /* set the time for each entry */ + axienet_iow(lp, ADMIN_CTRL_LIST_TIME(port, i), + qbv->acl_gate_time[i] & CTRL_LIST_TIME_INTERVAL_MASK); + } + + /* clear interrupt status */ + axienet_iow(lp, INT_STATUS(port), 0); + + /* kick in new config change */ + u_config_change |= CC_ADMIN_CONFIG_CHANGE_BIT; + + /* enable gate */ + u_config_change |= CC_ADMIN_GATE_ENABLE_BIT; + + /* start */ + axienet_iow(lp, CONFIG_CHANGE(port), u_config_change); + + return 0; +} + +int axienet_set_schedule(struct net_device *ndev, void __user *useraddr) +{ + struct qbv_info *config; + int ret; + + config = kmalloc(sizeof(*config), GFP_KERNEL); + if (!config) + return -ENOMEM; + + if (copy_from_user(config, useraddr, sizeof(struct qbv_info))) { + ret = -EFAULT; + goto out; + } + + pr_debug("setting new schedule\n"); + + ret = __axienet_set_schedule(ndev, config); +out: + kfree(config); + return ret; +} + +static int __axienet_get_schedule(struct net_device *ndev, struct qbv_info *qbv) +{ + struct axienet_local *lp = netdev_priv(ndev); + u16 i = 0; + u32 u_value = 0; + u8 port = qbv->port; + + if (!(axienet_ior(lp, CONFIG_CHANGE(port)) & + CC_ADMIN_GATE_ENABLE_BIT)) { + qbv->cycle_time = 0; + return 0; + } + + u_value = axienet_ior(lp, GATE_STATE(port)); + qbv->list_length = (u_value >> CC_ADMIN_CTRL_LIST_LENGTH_SHIFT) & + CC_ADMIN_CTRL_LIST_LENGTH_MASK; + + u_value = axienet_ior(lp, OPER_CYCLE_TIME_DENOMINATOR(port)); + qbv->cycle_time = u_value & CYCLE_TIME_DENOMINATOR_MASK; + + u_value = axienet_ior(lp, OPER_BASE_TIME_NS(port)); + qbv->ptp_time_ns = u_value & OPER_BASE_TIME_NS_MASK; + + qbv->ptp_time_sec = axienet_ior(lp, OPER_BASE_TIME_SEC(port)); + u_value = axienet_ior(lp, OPER_BASE_TIME_SECS(port)); + qbv->ptp_time_sec |= (u64)(u_value & BASE_TIME_SECS_MASK) << 32; + + for (i = 0; i < qbv->list_length; i++) { + u_value = axienet_ior(lp, OPER_CTRL_LIST(port, i)); + qbv->acl_gate_state[i] = (u_value >> ACL_GATE_STATE_SHIFT) & + ACL_GATE_STATE_MASK; + /** + * In 2Q system, the actual ST Gate state value is 2, + * for user the ST Gate state value is always 4. + */ + if (lp->num_tc == 2 && qbv->acl_gate_state[i] == 2) + qbv->acl_gate_state[i] = 4; + + u_value = axienet_ior(lp, OPER_CTRL_LIST_TIME(port, i)); + qbv->acl_gate_time[i] = u_value & CTRL_LIST_TIME_INTERVAL_MASK; + } + return 0; +} + +int axienet_get_schedule(struct net_device *ndev, void __user *useraddr) +{ + struct qbv_info *qbv; + int ret = 0; + + qbv = kmalloc(sizeof(*qbv), GFP_KERNEL); + if (!qbv) + return -ENOMEM; + + if (copy_from_user(qbv, useraddr, sizeof(struct qbv_info))) { + ret = -EFAULT; + goto out; + } + + __axienet_get_schedule(ndev, qbv); + + if (copy_to_user(useraddr, qbv, sizeof(struct qbv_info))) + ret = -EFAULT; +out: + kfree(qbv); + return ret; +} + +static irqreturn_t axienet_qbv_irq(int irq, void *_ndev) +{ + struct net_device *ndev = _ndev; + struct axienet_local *lp = netdev_priv(ndev); + u8 port = 0; /* TODO */ + + /* clear status */ + axienet_iow(lp, INT_CLEAR(port), 0); + + return IRQ_HANDLED; +} + +int axienet_qbv_init(struct net_device *ndev) +{ + struct axienet_local *lp = netdev_priv(ndev); + int rc; + + rc = request_irq(lp->qbv_irq, axienet_qbv_irq, 0, ndev->name, ndev); + if (rc) + goto err_qbv_irq; + +err_qbv_irq: + return rc; +} + +void axienet_qbv_remove(struct net_device *ndev) +{ + struct axienet_local *lp = netdev_priv(ndev); + + free_irq(lp->qbv_irq, ndev); +} diff --git a/drivers/net/ethernet/xilinx/xilinx_tsn_shaper.h b/drivers/net/ethernet/xilinx/xilinx_tsn_shaper.h new file mode 100644 index 0000000000000..ac2e54d0e1347 --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_tsn_shaper.h @@ -0,0 +1,151 @@ +/* + * Xilinx TSN QBV scheduler header + * + * Copyright (C) 2017 Xilinx, Inc. + * + * Author: Syed S + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XILINX_TSN_SHAPER_H +#define XILINX_TSN_SHAPER_H + +/* 0x0 CONFIG_CHANGE + * 0x8 GATE_STATE + * 0x10 ADMIN_CTRL_LIST_LENGTH + * 0x18 ADMIN_CYCLE_TIME_DENOMINATOR + * 0x20 ADMIN_BASE_TIME_NS + * 0x24 ADMIN_BASE_TIME_SEC + * 0x28 ADMIN_BASE_TIME_SECS + * 0x30 INT_STAT + * 0x34 INT_EN + * 0x38 INT_CLR + * 0x3c STATUS + * 0x40 CONFIG_CHANGE_TIME_NS + * 0x44 CONFIG_CHANGE_TIME_SEC + * 0x48 CONFIG_CHANGE_TIME_SECS + * 0x50 OPER_CTRL_LIST_LENGTH + * 0x58 OPER_CYCLE_TIME_DENOMINATOR + * 0x60 OPER_BASE_TIME_NS + * 0x64 OPER_BASE_TIME_SEC + * 0x68 OPER_BASE_TIME_SECS + * 0x6c BE_XMIT_OVRRUN_CNT + * 0x74 RES_XMIT_OVRRUN_CNT + * 0x7c ST_XMIT_OVRRUN_CNT + */ + +enum hw_port { + PORT_EP = 0, + PORT_TEMAC_1, + PORT_TEMAC_2, +}; + + /* EP */ /* TEMAC1 */ /* TEMAC2*/ +static u32 qbv_reg_map[3] = { 0x0, 0x14000, 0x14000 }; + +/* 0x14000 0x14FFC Time Schedule Registers (Control & Status) + * 0x15000 0x15FFF Time Schedule Control List Entries + */ + +#define TIME_SCHED_BASE(port) qbv_reg_map[(port)] + +#define CTRL_LIST_BASE(port) (TIME_SCHED_BASE(port) + 0x1000) + +/* control list entries + * admin control list 0 : 31 + * "Time interval between two gate entries" must be greater than + * "time required to transmit biggest supported frame" on that queue when + * the gate for the queue is going from open to close state. + */ +#define ADMIN_CTRL_LIST(port, n) (CTRL_LIST_BASE(port) + ((n) * 8)) +#define ACL_GATE_STATE_SHIFT 8 +#define ACL_GATE_STATE_MASK 0x7 +#define ADMIN_CTRL_LIST_TIME(port, n) (ADMIN_CTRL_LIST((port), n) + 4) + +#define OPER_CTRL_LIST(port, n) (CTRL_LIST_BASE(port) + 0x800 + ((n) * 8)) +#define OPER_CTRL_LIST_TIME(port, n) (OPER_CTRL_LIST(port, n) + 4) +#define CTRL_LIST_TIME_INTERVAL_MASK 0xFFFFF + +#define CONFIG_CHANGE(port) (TIME_SCHED_BASE(port) + 0x0) +#define CC_ADMIN_GATE_STATE_SHIFT 0x7 +#define CC_ADMIN_GATE_STATE_MASK (7) +#define CC_ADMIN_CTRL_LIST_LENGTH_SHIFT (8) +#define CC_ADMIN_CTRL_LIST_LENGTH_MASK (0x1FF) +/* This request bit is set when all the related Admin* filelds are populated. + * This bit is set by S/W and clear by core when core start with new schedule. + * Once set it can only be cleared by core or hard/soft reset. + */ +#define CC_ADMIN_CONFIG_CHANGE_BIT BIT(30) +#define CC_ADMIN_GATE_ENABLE_BIT BIT(31) + +#define GATE_STATE(port) (TIME_SCHED_BASE(port) + 0x8) +#define GS_OPER_GATE_STATE_SHIFT (0) +#define GS_OPER_GATE_STATE_MASK (0x7) +#define GS_OPER_CTRL_LIST_LENGTH_SHIFT (8) +#define GS_OPER_CTRL_LIST_LENGTH_MASK (0x3F) +#define GS_SUP_MAX_LIST_LENGTH_SHIFT (16) +#define GS_SUP_MAX_LIST_LENGTH_MASK (0x3F) +#define GS_TICK_GRANULARITY_SHIFT (24) +#define GS_TICK_GRANULARITY_MASK (0x3F) + +#define ADMIN_CYCLE_TIME_DENOMINATOR(port) (TIME_SCHED_BASE(port) + 0x18) +#define ADMIN_BASE_TIME_NS(port) (TIME_SCHED_BASE(port) + 0x20) +#define ADMIN_BASE_TIME_SEC(port) (TIME_SCHED_BASE(port) + 0x24) +#define ADMIN_BASE_TIME_SECS(port) (TIME_SCHED_BASE(port) + 0x28) + +#define INT_STATUS(port) (TIME_SCHED_BASE(port) + 0x30) +#define INT_ENABLE(port) (TIME_SCHED_BASE(port) + 0x34) +#define INT_CLEAR(port) (TIME_SCHED_BASE(port) + 0x38) +#define PORT_STATUS(port) (TIME_SCHED_BASE(port) + 0x3c) + +/* Config Change time is valid after Config Pending bit is set. */ +#define CONFIG_CHANGE_TIME_NS(port) (TIME_SCHED_BASE((port)) + 0x40) +#define CONFIG_CHANGE_TIME_SEC(port) (TIME_SCHED_BASE((port)) + 0x44) +#define CONFIG_CHANGE_TIME_SECS(port) (TIME_SCHED_BASE((port)) + 0x48) + +#define OPER_CONTROL_LIST_LENGTH(port) (TIME_SCHED_BASE(port) + 0x50) +#define OPER_CYCLE_TIME_DENOMINATOR(port) (TIME_SCHED_BASE(port) + 0x58) +#define CYCLE_TIME_DENOMINATOR_MASK (0x3FFFFFFF) + +#define OPER_BASE_TIME_NS(port) (TIME_SCHED_BASE(port) + 0x60) +#define OPER_BASE_TIME_NS_MASK (0x3FFFFFFF) +#define OPER_BASE_TIME_SEC(port) (TIME_SCHED_BASE(port) + 0x64) +#define OPER_BASE_TIME_SECS(port) (TIME_SCHED_BASE(port) + 0x68) +#define BASE_TIME_SECS_MASK (0xFFFF) + +#define BE_XMIT_OVERRUN_COUNT(port) (TIME_SCHED_BASE(port) + 0x6c) +#define RES_XMIT_OVERRUN_COUNT(port) (TIME_SCHED_BASE(port) + 0x74) +#define ST_XMIT_OVERRUN_COUNT(port) (TIME_SCHED_BASE(port) + 0x7c) + +/* internally hw deals with queues only, + * in 3q system ST acl bitmap would be would 1 << 2 + * in 2q system ST acl bitmap would be 1 << 1 + * But this is confusing to users. + * so use the following fixed gate state and internally + * map them to hw + */ +#define GS_BE_OPEN BIT(0) +#define GS_RE_OPEN BIT(1) +#define GS_ST_OPEN BIT(2) +#define QBV_MAX_ENTRIES 256 + +struct qbv_info { + u8 port; + u8 force; + u32 cycle_time; + u64 ptp_time_sec; + u32 ptp_time_ns; + u32 list_length; + u32 acl_gate_state[QBV_MAX_ENTRIES]; + u32 acl_gate_time[QBV_MAX_ENTRIES]; +}; + +#endif /* XILINX_TSN_SHAPER_H */ diff --git a/drivers/net/ethernet/xilinx/xilinx_tsn_switch.c b/drivers/net/ethernet/xilinx/xilinx_tsn_switch.c new file mode 100644 index 0000000000000..7e6df2543a51c --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_tsn_switch.c @@ -0,0 +1,664 @@ +/* + * Xilinx FPGA Xilinx TSN Switch Controller driver. + * + * Copyright (c) 2017 Xilinx Pvt., Ltd + * + * Author: Saurabh Sengar + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "xilinx_tsn_switch.h" +#include +#include +#include + +static struct miscdevice switch_dev; +struct axienet_local lp; + +#define ADD 1 +#define DELETE 0 + +#define PMAP_EGRESS_QUEUE_MASK 0x7 +#define PMAP_EGRESS_QUEUE0_SELECT 0x0 +#define PMAP_EGRESS_QUEUE1_SELECT 0x1 +#define PMAP_EGRESS_QUEUE2_SELECT 0x2 +#define PMAP_PRIORITY0_SHIFT 0 +#define PMAP_PRIORITY1_SHIFT 4 +#define PMAP_PRIORITY2_SHIFT 8 +#define PMAP_PRIORITY3_SHIFT 12 +#define PMAP_PRIORITY4_SHIFT 16 +#define PMAP_PRIORITY5_SHIFT 20 +#define PMAP_PRIORITY6_SHIFT 24 +#define PMAP_PRIORITY7_SHIFT 28 +#define SDL_EN_CAM_IPV_SHIFT 28 +#define SDL_CAM_IPV_SHIFT 29 + +#define SDL_CAM_WR_ENABLE BIT(0) +#define SDL_CAM_ADD_ENTRY 0x1 +#define SDL_CAM_DELETE_ENTRY 0x3 +#define SDL_CAM_VLAN_SHIFT 16 +#define SDL_CAM_VLAN_MASK 0xFFF +#define SDL_CAM_IPV_MASK 0x7 +#define SDL_CAM_PORT_LIST_SHIFT 8 +#define SDL_GATEID_SHIFT 16 +#define SDL_CAM_FWD_TO_EP BIT(0) +#define SDL_CAM_FWD_TO_PORT_1 BIT(1) +#define SDL_CAM_FWD_TO_PORT_2 BIT(2) +#define SDL_CAM_EP_ACTION_LIST_SHIFT 0 +#define SDL_CAM_MAC_ACTION_LIST_SHIFT 4 +#define SDL_CAM_DEST_MAC_XLATION BIT(0) +#define SDL_CAM_VLAN_ID_XLATION BIT(1) +#define SDL_CAM_UNTAG_FRAME BIT(2) + +/* Match table for of_platform binding */ +static const struct of_device_id tsnswitch_of_match[] = { + { .compatible = "xlnx,tsn-switch", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, tsnswitch_of_match); + +static int switch_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int switch_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/* set_frame_filter_option Frame Filtering Type Field Options */ +static void set_frame_filter_opt(u16 type1, u16 type2) +{ + int type = axienet_ior(&lp, XAS_FRM_FLTR_TYPE_FIELD_OPT_OFFSET); + + if (type1) + type = (type & 0x0000FFFF) | (type1 << 16); + if (type2) + type = (type & 0xFFFF0000) | type2; + axienet_iow(&lp, XAS_FRM_FLTR_TYPE_FIELD_OPT_OFFSET, type); +} + +/* MAC Port-1 Management Queueing Options */ +static void set_mac1_mngmntq(u32 config) +{ + axienet_iow(&lp, XAS_MAC1_MNG_Q_OPTION_OFFSET, config); +} + +/* MAC Port-2 Management Queueing Options */ +static void set_mac2_mngmntq(u32 config) +{ + axienet_iow(&lp, XAS_MAC2_MNG_Q_OPTION_OFFSET, config); +} + +/** + * set_switch_regs - read the various status of switch + * @data: Pointer which will be writen to switch + */ +static void set_switch_regs(struct switch_data *data) +{ + int tmp; + u8 mac_addr[6]; + + axienet_iow(&lp, XAS_CONTROL_OFFSET, data->switch_ctrl); + axienet_iow(&lp, XAS_PMAP_OFFSET, data->switch_prt); + mac_addr[0] = data->sw_mac_addr[0]; + mac_addr[1] = data->sw_mac_addr[1]; + mac_addr[2] = data->sw_mac_addr[2]; + mac_addr[3] = data->sw_mac_addr[3]; + mac_addr[4] = data->sw_mac_addr[4]; + mac_addr[5] = data->sw_mac_addr[5]; + axienet_iow(&lp, XAS_MAC_LSB_OFFSET, + (mac_addr[0] << 24) | (mac_addr[1] << 16) | + (mac_addr[2] << 8) | (mac_addr[3])); + axienet_iow(&lp, XAS_MAC_MSB_OFFSET, (mac_addr[4] << 8) | mac_addr[5]); + + /* Threshold */ + tmp = (data->thld_ep_mac[0].t1 << 16) | data->thld_ep_mac[0].t2; + axienet_iow(&lp, XAS_EP2MAC_ST_FIFOT_OFFSET, tmp); + + tmp = (data->thld_ep_mac[1].t1 << 16) | data->thld_ep_mac[1].t2; + axienet_iow(&lp, XAS_EP2MAC_RE_FIFOT_OFFSET, tmp); + + tmp = (data->thld_ep_mac[2].t1 << 16) | data->thld_ep_mac[2].t2; + axienet_iow(&lp, XAS_EP2MAC_BE_FIFOT_OFFSET, tmp); + + tmp = (data->thld_mac_mac[0].t1 << 16) | data->thld_mac_mac[0].t2; + axienet_iow(&lp, XAS_MAC2MAC_ST_FIFOT_OFFSET, tmp); + + tmp = (data->thld_mac_mac[1].t1 << 16) | data->thld_mac_mac[1].t2; + axienet_iow(&lp, XAS_MAC2MAC_RE_FIFOT_OFFSET, tmp); + + tmp = (data->thld_mac_mac[2].t1 << 16) | data->thld_mac_mac[2].t2; + axienet_iow(&lp, XAS_MAC2MAC_BE_FIFOT_OFFSET, tmp); + + /* Port VLAN ID */ + axienet_iow(&lp, XAS_EP_PORT_VLAN_OFFSET, data->ep_vlan); + axienet_iow(&lp, XAS_MAC_PORT_VLAN_OFFSET, data->mac_vlan); + + /* max frame size */ + axienet_iow(&lp, XAS_ST_MAX_FRAME_SIZE_OFFSET, data->max_frame_sc_que); + axienet_iow(&lp, XAS_RE_MAX_FRAME_SIZE_OFFSET, data->max_frame_res_que); + axienet_iow(&lp, XAS_BE_MAX_FRAME_SIZE_OFFSET, data->max_frame_be_que); +} + +/** + * get_switch_regs - read the various status of switch + * @data: Pointer which will return the switch status + */ +static void get_switch_regs(struct switch_data *data) +{ + int tmp; + + data->switch_status = axienet_ior(&lp, XAS_STATUS_OFFSET); + data->switch_ctrl = axienet_ior(&lp, XAS_CONTROL_OFFSET); + data->switch_prt = axienet_ior(&lp, XAS_PMAP_OFFSET); + tmp = axienet_ior(&lp, XAS_MAC_LSB_OFFSET); + data->sw_mac_addr[0] = (tmp & 0xFF000000) >> 24; + data->sw_mac_addr[1] = (tmp & 0xFF0000) >> 16; + data->sw_mac_addr[2] = (tmp & 0xFF00) >> 8; + data->sw_mac_addr[3] = (tmp & 0xFF); + tmp = axienet_ior(&lp, XAS_MAC_MSB_OFFSET); + data->sw_mac_addr[4] = (tmp & 0xFF00) >> 8; + data->sw_mac_addr[5] = (tmp & 0xFF); + + /* Threshold */ + tmp = axienet_ior(&lp, XAS_EP2MAC_ST_FIFOT_OFFSET); + data->thld_ep_mac[0].t1 = ((tmp >> 16) & 0xFFFF); + data->thld_ep_mac[0].t2 = tmp & (0xFFFF); + + tmp = axienet_ior(&lp, XAS_EP2MAC_RE_FIFOT_OFFSET); + data->thld_ep_mac[1].t1 = ((tmp >> 16) & 0xFFFF); + data->thld_ep_mac[1].t2 = tmp & (0xFFFF); + + tmp = axienet_ior(&lp, XAS_EP2MAC_BE_FIFOT_OFFSET); + data->thld_ep_mac[2].t1 = ((tmp >> 16) & 0xFFFF); + data->thld_ep_mac[2].t2 = tmp & (0xFFFF); + + tmp = axienet_ior(&lp, XAS_MAC2MAC_ST_FIFOT_OFFSET); + data->thld_mac_mac[0].t1 = ((tmp >> 16) & 0xFFFF); + data->thld_mac_mac[0].t2 = tmp & (0xFFFF); + + tmp = axienet_ior(&lp, XAS_MAC2MAC_RE_FIFOT_OFFSET); + data->thld_mac_mac[1].t1 = ((tmp >> 16) & 0xFFFF); + data->thld_mac_mac[1].t2 = tmp & (0xFFFF); + + tmp = axienet_ior(&lp, XAS_MAC2MAC_BE_FIFOT_OFFSET); + data->thld_mac_mac[2].t1 = ((tmp >> 16) & 0xFFFF); + data->thld_mac_mac[2].t2 = tmp & (0xFFFF); + + /* Port VLAN ID */ + data->ep_vlan = axienet_ior(&lp, XAS_EP_PORT_VLAN_OFFSET); + data->mac_vlan = axienet_ior(&lp, XAS_MAC_PORT_VLAN_OFFSET); + + /* max frame size */ + data->max_frame_sc_que = (axienet_ior(&lp, + XAS_ST_MAX_FRAME_SIZE_OFFSET) & 0xFFFF); + data->max_frame_res_que = (axienet_ior(&lp, + XAS_RE_MAX_FRAME_SIZE_OFFSET) & 0xFFFF); + data->max_frame_be_que = (axienet_ior(&lp, + XAS_BE_MAX_FRAME_SIZE_OFFSET) & 0xFFFF); + + /* frame filter type options*/ + tmp = axienet_ior(&lp, XAS_FRM_FLTR_TYPE_FIELD_OPT_OFFSET); + data->typefield.type2 = (tmp & 0xFFFF0000) >> 16; + data->typefield.type2 = tmp & 0x0000FFFF; + + /* MAC Port 1 Management Q option*/ + data->mac1_config = axienet_ior(&lp, XAS_MAC1_MNG_Q_OPTION_OFFSET); + /* MAC Port 2 Management Q option*/ + data->mac2_config = axienet_ior(&lp, XAS_MAC2_MNG_Q_OPTION_OFFSET); + + /* Port VLAN Membership control*/ + data->port_vlan_mem_ctrl = axienet_ior(&lp, XAS_VLAN_MEMB_CTRL_REG); + /* Port VLAN Membership read data*/ + data->port_vlan_mem_data = axienet_ior(&lp, XAS_VLAN_MEMB_DATA_REG); +} + +/** + * get_memory_static_counter - get memory static counters value + * @data: Value to be programmed + */ +static void get_memory_static_counter(struct switch_data *data) +{ + data->mem_arr_cnt.cam_lookup.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_CAM_LOOKUP); + data->mem_arr_cnt.cam_lookup.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_CAM_LOOKUP + 0x4); + + data->mem_arr_cnt.multicast_fr.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_MULTCAST); + data->mem_arr_cnt.multicast_fr.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_MULTCAST + 0x4); + + data->mem_arr_cnt.err_mac1.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_MAC1); + data->mem_arr_cnt.err_mac1.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_MAC1 + 0x4); + + data->mem_arr_cnt.err_mac2.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_MAC2); + data->mem_arr_cnt.err_mac2.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_MAC2 + 0x4); + + data->mem_arr_cnt.sc_mac1_ep.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_SC_MAC1_EP); + data->mem_arr_cnt.sc_mac1_ep.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_SC_MAC1_EP + 0x4); + data->mem_arr_cnt.res_mac1_ep.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_RES_MAC1_EP); + data->mem_arr_cnt.res_mac1_ep.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_RES_MAC1_EP + 0x4); + data->mem_arr_cnt.be_mac1_ep.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_BE_MAC1_EP); + data->mem_arr_cnt.be_mac1_ep.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_BE_MAC1_EP + 0x4); + data->mem_arr_cnt.err_sc_mac1_ep.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_SC_MAC1_EP); + data->mem_arr_cnt.err_sc_mac1_ep.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_SC_MAC1_EP + 0x4); + data->mem_arr_cnt.err_res_mac1_ep.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_RES_MAC1_EP); + data->mem_arr_cnt.err_res_mac1_ep.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_RES_MAC1_EP + 0x4); + data->mem_arr_cnt.err_be_mac1_ep.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_BE_MAC1_EP); + data->mem_arr_cnt.err_be_mac1_ep.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_BE_MAC1_EP + 0x4); + + data->mem_arr_cnt.sc_mac2_ep.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_SC_MAC2_EP); + data->mem_arr_cnt.sc_mac2_ep.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_SC_MAC2_EP + 0x4); + data->mem_arr_cnt.res_mac2_ep.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_RES_MAC2_EP); + data->mem_arr_cnt.res_mac2_ep.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_RES_MAC2_EP + 0x4); + data->mem_arr_cnt.be_mac2_ep.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_BE_MAC2_EP); + data->mem_arr_cnt.be_mac2_ep.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_BE_MAC2_EP + 0x4); + data->mem_arr_cnt.err_sc_mac2_ep.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_SC_MAC2_EP); + data->mem_arr_cnt.err_sc_mac2_ep.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_SC_MAC2_EP + 0x4); + data->mem_arr_cnt.err_res_mac2_ep.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_RES_MAC2_EP); + data->mem_arr_cnt.err_res_mac2_ep.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_RES_MAC2_EP + 0x4); + data->mem_arr_cnt.err_be_mac2_ep.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_BE_MAC2_EP); + data->mem_arr_cnt.err_be_mac2_ep.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_BE_MAC2_EP + 0x4); + + data->mem_arr_cnt.sc_ep_mac1.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_SC_EP_MAC1); + data->mem_arr_cnt.sc_ep_mac1.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_SC_EP_MAC1 + 0x4); + data->mem_arr_cnt.res_ep_mac1.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_RES_EP_MAC1); + data->mem_arr_cnt.res_ep_mac1.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_RES_EP_MAC1 + 0x4); + data->mem_arr_cnt.be_ep_mac1.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_BE_EP_MAC1); + data->mem_arr_cnt.be_ep_mac1.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_BE_EP_MAC1 + 0x4); + data->mem_arr_cnt.err_sc_ep_mac1.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_SC_EP_MAC1); + data->mem_arr_cnt.err_sc_ep_mac1.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_SC_EP_MAC1 + 0x4); + data->mem_arr_cnt.err_res_ep_mac1.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_RES_EP_MAC1); + data->mem_arr_cnt.err_res_ep_mac1.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_RES_EP_MAC1 + 0x4); + data->mem_arr_cnt.err_be_ep_mac1.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_BE_EP_MAC1); + data->mem_arr_cnt.err_be_ep_mac1.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_BE_EP_MAC1 + 0x4); + + data->mem_arr_cnt.sc_mac2_mac1.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_SC_MAC2_MAC1); + data->mem_arr_cnt.sc_mac2_mac1.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_SC_MAC2_MAC1 + 0x4); + data->mem_arr_cnt.res_mac2_mac1.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_RES_MAC2_MAC1); + data->mem_arr_cnt.res_mac2_mac1.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_RES_MAC2_MAC1 + 0x4); + data->mem_arr_cnt.be_mac2_mac1.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_BE_MAC2_MAC1); + data->mem_arr_cnt.be_mac2_mac1.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_BE_MAC2_MAC1 + 0x4); + data->mem_arr_cnt.err_sc_mac2_mac1.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_SC_MAC2_MAC1); + data->mem_arr_cnt.err_sc_mac2_mac1.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_SC_MAC2_MAC1 + 0x4); + data->mem_arr_cnt.err_res_mac2_mac1.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_RES_MAC2_MAC1); + data->mem_arr_cnt.err_res_mac2_mac1.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_RES_MAC2_MAC1 + 0x4); + data->mem_arr_cnt.err_be_mac2_mac1.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_BE_MAC2_MAC1); + data->mem_arr_cnt.err_be_mac2_mac1.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_BE_MAC2_MAC1 + 0x4); + + data->mem_arr_cnt.sc_ep_mac2.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_SC_EP_MAC2); + data->mem_arr_cnt.sc_ep_mac2.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_SC_EP_MAC2 + 0x4); + data->mem_arr_cnt.res_ep_mac2.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_RES_EP_MAC2); + data->mem_arr_cnt.res_ep_mac2.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_RES_EP_MAC2 + 0x4); + data->mem_arr_cnt.be_ep_mac2.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_BE_EP_MAC2); + data->mem_arr_cnt.be_ep_mac2.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_BE_EP_MAC2 + 0x4); + data->mem_arr_cnt.err_sc_ep_mac2.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_SC_EP_MAC2); + data->mem_arr_cnt.err_sc_ep_mac2.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_SC_EP_MAC2 + 0x4); + data->mem_arr_cnt.err_res_ep_mac2.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_RES_EP_MAC2); + data->mem_arr_cnt.err_res_ep_mac2.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_RES_EP_MAC2 + 0x4); + data->mem_arr_cnt.err_be_ep_mac2.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_BE_EP_MAC2); + data->mem_arr_cnt.err_be_ep_mac2.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_BE_EP_MAC2 + 0x4); + + data->mem_arr_cnt.sc_mac1_mac2.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_SC_MAC1_MAC2); + data->mem_arr_cnt.sc_mac1_mac2.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_SC_MAC1_MAC2 + 0x4); + data->mem_arr_cnt.res_mac1_mac2.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_RES_MAC1_MAC2); + data->mem_arr_cnt.res_mac1_mac2.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_RES_MAC1_MAC2 + 0x4); + data->mem_arr_cnt.be_mac1_mac2.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_BE_MAC1_MAC2); + data->mem_arr_cnt.be_mac1_mac2.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_BE_MAC1_MAC2 + 0x4); + data->mem_arr_cnt.err_sc_mac1_mac2.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_SC_MAC1_MAC2); + data->mem_arr_cnt.err_sc_mac1_mac2.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_SC_MAC1_MAC2 + 0x4); + data->mem_arr_cnt.err_res_mac1_mac2.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_RES_MAC1_MAC2); + data->mem_arr_cnt.err_res_mac1_mac2.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_RES_MAC1_MAC2 + 0x4); + data->mem_arr_cnt.err_be_mac1_mac2.lsb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_BE_MAC1_MAC2); + data->mem_arr_cnt.err_be_mac1_mac2.msb = axienet_ior(&lp, + XAS_MEM_STCNTR_ERR_BE_MAC1_MAC2 + 0x4); +} + +static void add_delete_cam_entry(struct cam_struct data, u8 add) +{ + u32 port_action = 0; + u32 tv2 = 0; + u32 timeout = 20000; + + /* wait for cam init done */ + while (!(axienet_ior(&lp, XAS_SDL_CAM_STATUS_OFFSET) & + SDL_CAM_WR_ENABLE) && timeout) + timeout--; + + if (!timeout) + pr_warn("CAM init took longer time!!"); + /* mac and vlan */ + axienet_iow(&lp, XAS_SDL_CAM_KEY1_OFFSET, + (data.dest_addr[0] << 24) | (data.dest_addr[1] << 16) | + (data.dest_addr[2] << 8) | (data.dest_addr[3])); + axienet_iow(&lp, XAS_SDL_CAM_KEY2_OFFSET, + ((data.dest_addr[4] << 8) | data.dest_addr[5]) | + ((data.vlanid & SDL_CAM_VLAN_MASK) << SDL_CAM_VLAN_SHIFT)); + + /* TV 1 and TV 2 */ + axienet_iow(&lp, XAS_SDL_CAM_TV1_OFFSET, + (data.src_addr[0] << 24) | (data.src_addr[1] << 16) | + (data.src_addr[2] << 8) | (data.src_addr[3])); + + tv2 = ((data.src_addr[4] << 8) | data.src_addr[5]) | + ((data.tv_vlanid & SDL_CAM_VLAN_MASK) << SDL_CAM_VLAN_SHIFT); + + axienet_iow(&lp, XAS_SDL_CAM_TV2_OFFSET, tv2); + + if (data.tv_en) + port_action = ((SDL_CAM_DEST_MAC_XLATION | + SDL_CAM_VLAN_ID_XLATION) << SDL_CAM_MAC_ACTION_LIST_SHIFT); + + port_action = port_action | (data.fwd_port << SDL_CAM_PORT_LIST_SHIFT); + + + /* port action */ + axienet_iow(&lp, XAS_SDL_CAM_PORT_ACT_OFFSET, port_action); + + if (add) + axienet_iow(&lp, XAS_SDL_CAM_CTRL_OFFSET, SDL_CAM_ADD_ENTRY); + else + axienet_iow(&lp, XAS_SDL_CAM_CTRL_OFFSET, SDL_CAM_DELETE_ENTRY); + + timeout = 20000; + /* wait for write to complete */ + while ((axienet_ior(&lp, XAS_SDL_CAM_CTRL_OFFSET) & + SDL_CAM_WR_ENABLE) && timeout) + timeout--; + + if (!timeout) + pr_warn("CAM write took longer time!!"); +} + +static void port_vlan_mem_ctrl(u32 port_vlan_mem) +{ + axienet_iow(&lp, XAS_VLAN_MEMB_CTRL_REG, port_vlan_mem); +} + +static long switch_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + long retval = 0; + struct switch_data data; + + switch (cmd) { + case GET_STATUS_SWITCH: + /* Switch configurations */ + get_switch_regs(&data); + + /* Memory static counter*/ + get_memory_static_counter(&data); + if (copy_to_user((char __user *)arg, &data, sizeof(data))) { + pr_err("Copy to user failed\n"); + retval = -EINVAL; + goto end; + } + break; + + case SET_STATUS_SWITCH: + if (copy_from_user(&data, (char __user *)arg, sizeof(data))) { + pr_err("Copy from user failed\n"); + retval = -EINVAL; + goto end; + } + set_switch_regs(&data); + break; + + case ADD_CAM_ENTRY: + if (copy_from_user(&data, (char __user *)arg, sizeof(data))) { + pr_err("Copy from user failed\n"); + retval = -EINVAL; + goto end; + } + add_delete_cam_entry(data.cam_data, ADD); + break; + + case DELETE_CAM_ENTRY: + if (copy_from_user(&data, (char __user *)arg, sizeof(data))) { + pr_err("Copy from user failed\n"); + retval = -EINVAL; + goto end; + } + add_delete_cam_entry(data.cam_data, DELETE); + break; + + case PORT_VLAN_MEM_CTRL: + if (copy_from_user(&data, (char __user *)arg, sizeof(data))) { + pr_err("Copy from user failed\n"); + retval = -EINVAL; + goto end; + } + port_vlan_mem_ctrl(data.port_vlan_mem_ctrl); + break; + + case SET_FRAME_TYPE_FIELD: + if (copy_from_user(&data, (char __user *)arg, sizeof(data))) { + pr_err("Copy from user failed\n"); + retval = -EINVAL; + goto end; + } + set_frame_filter_opt(data.typefield.type1, + data.typefield.type2); + break; + + case SET_MAC1_MNGMNT_Q_CONFIG: + if (copy_from_user(&data, (char __user *)arg, sizeof(data))) { + pr_err("Copy from user failed\n"); + retval = -EINVAL; + goto end; + } + set_mac1_mngmntq(data.mac1_config); + break; + + case SET_MAC2_MNGMNT_Q_CONFIG: + if (copy_from_user(&data, (char __user *)arg, sizeof(data))) { + pr_err("Copy from user failed\n"); + retval = -EINVAL; + goto end; + } + set_mac2_mngmntq(data.mac2_config); + break; + + } +end: + return retval; +} + +static const struct file_operations switch_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = switch_ioctl, + .open = switch_open, + .release = switch_release, +}; + +static int tsn_switch_init(void) +{ + int ret; + + switch_dev.minor = MISC_DYNAMIC_MINOR; + switch_dev.name = "switch"; + switch_dev.fops = &switch_fops; + ret = misc_register(&switch_dev); + if (ret < 0) { + pr_err("Switch driver registration failed!\n"); + return ret; + } + + pr_debug("Xilinx TSN Switch driver initialized!\n"); + return 0; +} + +static int tsn_switch_cam_init(u16 num_q) +{ + u32 pmap; + u32 timeout = 20000; + + /* wait for switch init done */ + while (!(axienet_ior(&lp, XAS_STATUS_OFFSET) & + SDL_CAM_WR_ENABLE) && timeout) + timeout--; + + if (!timeout) + pr_warn("Switch init took longer time!!"); + + if (num_q == 3) { + /* map pcp = 2,3 to queue1 + * pcp = 4 to queue2 + */ + pmap = ((PMAP_EGRESS_QUEUE1_SELECT << PMAP_PRIORITY2_SHIFT) | + (PMAP_EGRESS_QUEUE1_SELECT << PMAP_PRIORITY3_SHIFT) | + (PMAP_EGRESS_QUEUE2_SELECT << PMAP_PRIORITY4_SHIFT)); + } else if (num_q == 2) { + /* pcp = 4 to queue1 */ + pmap = (PMAP_EGRESS_QUEUE1_SELECT << PMAP_PRIORITY4_SHIFT); + } + + axienet_iow(&lp, XAS_PMAP_OFFSET, pmap); + + timeout = 20000; + /* wait for cam init done */ + while (!(axienet_ior(&lp, XAS_SDL_CAM_STATUS_OFFSET) & + SDL_CAM_WR_ENABLE) && timeout) + timeout--; + + if (!timeout) + pr_warn("CAM init took longer time!!"); + + return 0; +} + +static int tsnswitch_probe(struct platform_device *pdev) +{ + struct resource *swt; + int ret; + u16 num_tc; + + pr_info("TSN Switch probe\n"); + /* Map device registers */ + swt = platform_get_resource(pdev, IORESOURCE_MEM, 0); + lp.regs = devm_ioremap_resource(&pdev->dev, swt); + if (IS_ERR(lp.regs)) + return PTR_ERR(lp.regs); + + ret = of_property_read_u16(pdev->dev.of_node, "xlnx,num-tc", + &num_tc); + if (ret || (num_tc != 2 && num_tc != 3)) + num_tc = XAE_MAX_TSN_TC; + + pr_info("TSN Switch Initializing ....\n"); + ret = tsn_switch_init(); + if (ret) + return ret; + pr_info("TSN CAM Initializing ....\n"); + ret = tsn_switch_cam_init(num_tc); + + return ret; +} + +static int tsnswitch_remove(struct platform_device *pdev) +{ + misc_deregister(&switch_dev); + return 0; +} + +static struct platform_driver tsnswitch_driver = { + .probe = tsnswitch_probe, + .remove = tsnswitch_remove, + .driver = { + .name = "xilinx_tsnswitch", + .of_match_table = tsnswitch_of_match, + }, +}; + +module_platform_driver(tsnswitch_driver); + +MODULE_DESCRIPTION("Xilinx TSN Switch driver"); +MODULE_AUTHOR("Xilinx"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/ethernet/xilinx/xilinx_tsn_switch.h b/drivers/net/ethernet/xilinx/xilinx_tsn_switch.h new file mode 100644 index 0000000000000..f0295ce515bc8 --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_tsn_switch.h @@ -0,0 +1,238 @@ +/* + * Xilinx TSN core switch header + * + * Copyright (C) 2017 Xilinx, Inc. + * + * Author: Saurabh Sengar + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef XILINX_TSN_SWITCH_H +#define XILINX_TSN_SWITCH_H + +#include "xilinx_axienet.h" + +/* ioctls */ +#define GET_STATUS_SWITCH 0x16 +#define SET_STATUS_SWITCH 0x17 +#define ADD_CAM_ENTRY 0x18 +#define DELETE_CAM_ENTRY 0x19 +#define PORT_VLAN_MEM_CTRL 0x20 +#define SET_FRAME_TYPE_FIELD 0x21 +#define SET_MAC1_MNGMNT_Q_CONFIG 0x22 +#define SET_MAC2_MNGMNT_Q_CONFIG 0x23 +#define CONFIG_METER_MEM 0x24 +#define CONFIG_GATE_MEM 0x25 +#define PSFP_CONTROL 0x26 +#define GET_STATIC_PSFP_COUNTER 0x27 +#define GET_METER_REG 0x28 +#define GET_STREAM_FLTR_CONFIG 0x29 +#define CONFIG_MEMBER_MEM 0x2A +#define CONFIG_INGRESS_FLTR 0x2B +#define FRER_CONTROL 0x2C +#define GET_STATIC_FRER_COUNTER 0x2D +#define GET_MEMBER_REG 0x2E +#define GET_INGRESS_FLTR 0x2F + +/* Xilinx Axi Switch Offsets*/ +#define XAS_STATUS_OFFSET 0x00000 +#define XAS_CONTROL_OFFSET 0x00004 +#define XAS_PMAP_OFFSET 0x00008 +#define XAS_MAC_LSB_OFFSET 0x0000C +#define XAS_MAC_MSB_OFFSET 0x00010 +#define XAS_EP2MAC_ST_FIFOT_OFFSET 0x00020 +#define XAS_EP2MAC_RE_FIFOT_OFFSET 0x00024 +#define XAS_EP2MAC_BE_FIFOT_OFFSET 0x00028 +#define XAS_MAC2MAC_ST_FIFOT_OFFSET 0x00030 +#define XAS_MAC2MAC_RE_FIFOT_OFFSET 0x00034 +#define XAS_MAC2MAC_BE_FIFOT_OFFSET 0x00038 +#define XAS_EP_PORT_VLAN_OFFSET 0x00040 +#define XAS_MAC_PORT_VLAN_OFFSET 0x00044 +#define XAS_FRM_FLTR_TYPE_FIELD_OPT_OFFSET 0x00050 +#define XAS_MAC2_MNG_Q_OPTION_OFFSET 0x00054 +#define XAS_MAC1_MNG_Q_OPTION_OFFSET 0x00058 +#define XAS_ST_MAX_FRAME_SIZE_OFFSET 0x00060 +#define XAS_RE_MAX_FRAME_SIZE_OFFSET 0x00064 +#define XAS_BE_MAX_FRAME_SIZE_OFFSET 0x00068 + +/* Memory static counters */ +#define XAS_MEM_STCNTR_CAM_LOOKUP 0x00400 +#define XAS_MEM_STCNTR_MULTCAST 0x00408 +#define XAS_MEM_STCNTR_ERR_MAC1 0x00410 +#define XAS_MEM_STCNTR_ERR_MAC2 0x00418 +#define XAS_MEM_STCNTR_SC_MAC1_EP 0x00420 +#define XAS_MEM_STCNTR_RES_MAC1_EP 0x00428 +#define XAS_MEM_STCNTR_BE_MAC1_EP 0x00430 +#define XAS_MEM_STCNTR_ERR_SC_MAC1_EP 0x00438 +#define XAS_MEM_STCNTR_ERR_RES_MAC1_EP 0x00440 +#define XAS_MEM_STCNTR_ERR_BE_MAC1_EP 0x00448 +#define XAS_MEM_STCNTR_SC_MAC2_EP 0x00458 +#define XAS_MEM_STCNTR_RES_MAC2_EP 0x00460 +#define XAS_MEM_STCNTR_BE_MAC2_EP 0x00468 +#define XAS_MEM_STCNTR_ERR_SC_MAC2_EP 0x00470 +#define XAS_MEM_STCNTR_ERR_RES_MAC2_EP 0x00478 +#define XAS_MEM_STCNTR_ERR_BE_MAC2_EP 0x00480 +#define XAS_MEM_STCNTR_SC_EP_MAC1 0x00490 +#define XAS_MEM_STCNTR_RES_EP_MAC1 0x00498 +#define XAS_MEM_STCNTR_BE_EP_MAC1 0x004A0 +#define XAS_MEM_STCNTR_ERR_SC_EP_MAC1 0x004A8 +#define XAS_MEM_STCNTR_ERR_RES_EP_MAC1 0x004B0 +#define XAS_MEM_STCNTR_ERR_BE_EP_MAC1 0x004B8 +#define XAS_MEM_STCNTR_SC_MAC2_MAC1 0x004C0 +#define XAS_MEM_STCNTR_RES_MAC2_MAC1 0x004C8 +#define XAS_MEM_STCNTR_BE_MAC2_MAC1 0x004D0 +#define XAS_MEM_STCNTR_ERR_SC_MAC2_MAC1 0x004D8 +#define XAS_MEM_STCNTR_ERR_RES_MAC2_MAC1 0x004E0 +#define XAS_MEM_STCNTR_ERR_BE_MAC2_MAC1 0x004E8 +#define XAS_MEM_STCNTR_SC_EP_MAC2 0x004F0 +#define XAS_MEM_STCNTR_RES_EP_MAC2 0x004F8 +#define XAS_MEM_STCNTR_BE_EP_MAC2 0x00500 +#define XAS_MEM_STCNTR_ERR_SC_EP_MAC2 0x00508 +#define XAS_MEM_STCNTR_ERR_RES_EP_MAC2 0x00510 +#define XAS_MEM_STCNTR_ERR_BE_EP_MAC2 0x00518 +#define XAS_MEM_STCNTR_SC_MAC1_MAC2 0x00520 +#define XAS_MEM_STCNTR_RES_MAC1_MAC2 0x00528 +#define XAS_MEM_STCNTR_BE_MAC1_MAC2 0x00530 +#define XAS_MEM_STCNTR_ERR_SC_MAC1_MAC2 0x00538 +#define XAS_MEM_STCNTR_ERR_RES_MAC1_MAC2 0x00540 +#define XAS_MEM_STCNTR_ERR_BE_MAC1_MAC2 0x00548 + +/* Stream Destination Lookup CAM */ +#define XAS_SDL_CAM_CTRL_OFFSET 0x1000 +#define XAS_SDL_CAM_STATUS_OFFSET 0x1004 +#define XAS_SDL_CAM_KEY1_OFFSET 0x1008 +#define XAS_SDL_CAM_KEY2_OFFSET 0x100C +#define XAS_SDL_CAM_TV1_OFFSET 0x1010 +#define XAS_SDL_CAM_TV2_OFFSET 0x1014 +#define XAS_SDL_CAM_PORT_ACT_OFFSET 0x1018 + +/* Port VLAN Membership Memory */ +#define XAS_VLAN_MEMB_CTRL_REG 0x1100 +#define XAS_VLAN_MEMB_DATA_REG 0x1104 + + +/* PSFP Statistics Counters */ +#define TOTAL_PSFP_FRAMES_OFFSET 0x2000 +#define FLTR_INGS_PORT_ERR_OFFSET 0x2800 +#define FLTR_STDU_ERR_OFFSET 0x3000 +#define METER_ERR_OFFSET 0x3800 + + +/* 64 bit counter*/ +struct static_cntr { + u32 msb; + u32 lsb; +}; + + +/********* Switch Structures Starts ***********/ +struct thershold { + u16 t1; + u16 t2; +}; + +/* memory static counters */ +struct mem_static_arr_cntr { + struct static_cntr cam_lookup; + struct static_cntr multicast_fr; + struct static_cntr err_mac1; + struct static_cntr err_mac2; + struct static_cntr sc_mac1_ep; + struct static_cntr res_mac1_ep; + struct static_cntr be_mac1_ep; + struct static_cntr err_sc_mac1_ep; + struct static_cntr err_res_mac1_ep; + struct static_cntr err_be_mac1_ep; + struct static_cntr sc_mac2_ep; + struct static_cntr res_mac2_ep; + struct static_cntr be_mac2_ep; + struct static_cntr err_sc_mac2_ep; + struct static_cntr err_res_mac2_ep; + struct static_cntr err_be_mac2_ep; + struct static_cntr sc_ep_mac1; + struct static_cntr res_ep_mac1; + struct static_cntr be_ep_mac1; + struct static_cntr err_sc_ep_mac1; + struct static_cntr err_res_ep_mac1; + struct static_cntr err_be_ep_mac1; + struct static_cntr sc_mac2_mac1; + struct static_cntr res_mac2_mac1; + struct static_cntr be_mac2_mac1; + struct static_cntr err_sc_mac2_mac1; + struct static_cntr err_res_mac2_mac1; + struct static_cntr err_be_mac2_mac1; + struct static_cntr sc_ep_mac2; + struct static_cntr res_ep_mac2; + struct static_cntr be_ep_mac2; + struct static_cntr err_sc_ep_mac2; + struct static_cntr err_res_ep_mac2; + struct static_cntr err_be_ep_mac2; + struct static_cntr sc_mac1_mac2; + struct static_cntr res_mac1_mac2; + struct static_cntr be_mac1_mac2; + struct static_cntr err_sc_mac1_mac2; + struct static_cntr err_res_mac1_mac2; + struct static_cntr err_be_mac1_mac2; +}; + +/* CAM structure */ +struct cam_struct { + u8 src_addr[6]; + u8 dest_addr[6]; + u16 vlanid; + u16 tv_vlanid; + u8 fwd_port; + bool tv_en; + u8 gate_id; + u8 ipv; + bool en_ipv; +}; + +/*Frame Filtering Type Field Option */ +struct ff_type { + u16 type1; + u16 type2; +}; + +/* Core switch structure*/ +struct switch_data { + u32 switch_status; + u32 switch_ctrl; + u32 switch_prt; + u8 sw_mac_addr[6]; + /*0 - schedule, 1 - reserved, 2 - best effort queue*/ + struct thershold thld_ep_mac[3]; + struct thershold thld_mac_mac[3]; + u32 ep_vlan; + u32 mac_vlan; + u32 max_frame_sc_que; + u32 max_frame_res_que; + u32 max_frame_be_que; + /* Memory counters */ + struct mem_static_arr_cntr mem_arr_cnt; + /* CAM */ + struct cam_struct cam_data; +/* Frame Filtering Type Field Option */ + struct ff_type typefield; +/* MAC Port-1 Management Queueing Options */ + int mac1_config; +/* MAC Port-2 Management Queueing Options */ + int mac2_config; +/* Port VLAN Membership Registers */ + int port_vlan_mem_ctrl; + char port_vlan_mem_data; +}; + +/********* Switch Structures ends ***********/ + +extern struct axienet_local lp; + +#endif /* XILINX_TSN_SWITCH_H */ diff --git a/drivers/net/ethernet/xilinx/xilinx_tsn_timer.h b/drivers/net/ethernet/xilinx/xilinx_tsn_timer.h new file mode 100644 index 0000000000000..4bb74e78d89ad --- /dev/null +++ b/drivers/net/ethernet/xilinx/xilinx_tsn_timer.h @@ -0,0 +1,73 @@ +/* + * Xilinx FPGA Xilinx TSN timer module header. + * + * Copyright (c) 2017 Xilinx Pvt., Ltd + * + * Author: Syed S + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _XILINX_TSN_H_ +#define _XILINX_TSN_H_ + +#include + +#define XAE_RTC_OFFSET 0x12800 +/* RTC Nanoseconds Field Offset Register */ +#define XTIMER1588_RTC_OFFSET_NS 0x00000 +/* RTC Seconds Field Offset Register - Low */ +#define XTIMER1588_RTC_OFFSET_SEC_L 0x00008 +/* RTC Seconds Field Offset Register - High */ +#define XTIMER1588_RTC_OFFSET_SEC_H 0x0000C +/* RTC Increment */ +#define XTIMER1588_RTC_INCREMENT 0x00010 +/* Current TOD Nanoseconds - RO */ +#define XTIMER1588_CURRENT_RTC_NS 0x00014 +/* Current TOD Seconds -Low RO */ +#define XTIMER1588_CURRENT_RTC_SEC_L 0x00018 +/* Current TOD Seconds -High RO */ +#define XTIMER1588_CURRENT_RTC_SEC_H 0x0001C +#define XTIMER1588_SYNTONIZED_NS 0x0002C +#define XTIMER1588_SYNTONIZED_SEC_L 0x00030 +#define XTIMER1588_SYNTONIZED_SEC_H 0x00034 +/* Write to Bit 0 to clear the interrupt */ +#define XTIMER1588_INTERRUPT 0x00020 +/* 8kHz Pulse Offset Register */ +#define XTIMER1588_8KPULSE 0x00024 +/* Correction Field - Low */ +#define XTIMER1588_CF_L 0x0002C +/* Correction Field - Low */ +#define XTIMER1588_CF_H 0x00030 + +#define XTIMER1588_RTC_MASK ((1 << 26) - 1) +#define XTIMER1588_INT_SHIFT 0 +#define NANOSECOND_BITS 20 +#define NANOSECOND_MASK ((1 << NANOSECOND_BITS) - 1) +#define SECOND_MASK ((1 << (32 - NANOSECOND_BITS)) - 1) +#define XTIMER1588_RTC_INCREMENT_SHIFT 20 +#define PULSESIN1PPS 128 + +/* Read/Write access to the registers */ +#ifndef out_be32 +#if defined(CONFIG_ARCH_ZYNQ) || defined(CONFIG_ARCH_ZYNQMP) +#define in_be32(offset) __raw_readl(offset) +#define out_be32(offset, val) __raw_writel(val, offset) +#endif +#endif + +/* The tsn ptp module will set this variable */ +extern int axienet_phc_index; + +void *axienet_ptp_timer_probe(void __iomem *base, + struct platform_device *pdev); +int axienet_ptp_timer_remove(void *priv); +int axienet_get_phc_index(void *priv); +#endif