diff options
author | David S. Miller <davem@davemloft.net> | 2020-09-09 14:22:42 -0700 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2020-09-09 14:22:42 -0700 |
commit | 4a056990e16e20f20f305daa53250bd01aaf9ad6 (patch) | |
tree | b506390e0ccb2e131584f20c56c07ca195868785 | |
parent | b599a5b9e16698424bced2429e935bee056dcf88 (diff) | |
parent | f5015a594c50c29155a029f6470ce2afd3e65c57 (diff) |
Merge branch 'Marvell-PP2-2-PTP-support'
Russell King says:
====================
Marvell PP2.2 PTP support
This series adds PTP support for PP2.2 hardware to the mvpp2 driver.
Tested on the Macchiatobin eth1 port.
Note that on the Macchiatobin, eth0 uses a separate TAI block from
eth1, and there is no hardware synchronisation between the two.
====================
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | drivers/net/ethernet/marvell/Kconfig | 6 | ||||
-rw-r--r-- | drivers/net/ethernet/marvell/mvpp2/Makefile | 3 | ||||
-rw-r--r-- | drivers/net/ethernet/marvell/mvpp2/mvpp2.h | 202 | ||||
-rw-r--r-- | drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c | 410 | ||||
-rw-r--r-- | drivers/net/ethernet/marvell/mvpp2/mvpp2_tai.c | 457 |
5 files changed, 1028 insertions, 50 deletions
diff --git a/drivers/net/ethernet/marvell/Kconfig b/drivers/net/ethernet/marvell/Kconfig index ef4f35ba077d..a599e44a36a8 100644 --- a/drivers/net/ethernet/marvell/Kconfig +++ b/drivers/net/ethernet/marvell/Kconfig @@ -92,6 +92,12 @@ config MVPP2 This driver supports the network interface units in the Marvell ARMADA 375, 7K and 8K SoCs. +config MVPP2_PTP + bool "Marvell Armada 8K Enable PTP support" + depends on NETWORK_PHY_TIMESTAMPING + depends on (PTP_1588_CLOCK = y && MVPP2 = y) || \ + (PTP_1588_CLOCK && MVPP2 = m) + config PXA168_ETH tristate "Marvell pxa168 ethernet support" depends on HAS_IOMEM diff --git a/drivers/net/ethernet/marvell/mvpp2/Makefile b/drivers/net/ethernet/marvell/mvpp2/Makefile index 51f65a202c6e..9bd8e7964b40 100644 --- a/drivers/net/ethernet/marvell/mvpp2/Makefile +++ b/drivers/net/ethernet/marvell/mvpp2/Makefile @@ -4,4 +4,5 @@ # obj-$(CONFIG_MVPP2) := mvpp2.o -mvpp2-objs := mvpp2_main.o mvpp2_prs.o mvpp2_cls.o mvpp2_debugfs.o +mvpp2-y := mvpp2_main.o mvpp2_prs.o mvpp2_cls.o mvpp2_debugfs.o +mvpp2-$(CONFIG_MVPP2_PTP) += mvpp2_tai.o diff --git a/drivers/net/ethernet/marvell/mvpp2/mvpp2.h b/drivers/net/ethernet/marvell/mvpp2/mvpp2.h index ecb5f4616a36..834775843067 100644 --- a/drivers/net/ethernet/marvell/mvpp2/mvpp2.h +++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2.h @@ -12,6 +12,7 @@ #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/netdevice.h> +#include <linux/net_tstamp.h> #include <linux/phy.h> #include <linux/phylink.h> #include <net/flow_offload.h> @@ -461,8 +462,12 @@ #define MVPP22_CTRL4_DP_CLK_SEL BIT(5) #define MVPP22_CTRL4_SYNC_BYPASS_DIS BIT(6) #define MVPP22_CTRL4_QSGMII_BYPASS_ACTIVE BIT(7) +#define MVPP22_GMAC_INT_SUM_STAT 0xa0 +#define MVPP22_GMAC_INT_SUM_STAT_INTERNAL BIT(1) +#define MVPP22_GMAC_INT_SUM_STAT_PTP BIT(2) #define MVPP22_GMAC_INT_SUM_MASK 0xa4 #define MVPP22_GMAC_INT_SUM_MASK_LINK_STAT BIT(1) +#define MVPP22_GMAC_INT_SUM_MASK_PTP BIT(2) /* Per-port XGMAC registers. PPv2.2 only, only for GOP port 0, * relative to port->base. @@ -488,9 +493,13 @@ #define MVPP22_XLG_CTRL3_MACMODESELECT_MASK (7 << 13) #define MVPP22_XLG_CTRL3_MACMODESELECT_GMAC (0 << 13) #define MVPP22_XLG_CTRL3_MACMODESELECT_10G (1 << 13) +#define MVPP22_XLG_EXT_INT_STAT 0x158 +#define MVPP22_XLG_EXT_INT_STAT_XLG BIT(1) +#define MVPP22_XLG_EXT_INT_STAT_PTP BIT(7) #define MVPP22_XLG_EXT_INT_MASK 0x15c #define MVPP22_XLG_EXT_INT_MASK_XLG BIT(1) #define MVPP22_XLG_EXT_INT_MASK_GIG BIT(2) +#define MVPP22_XLG_EXT_INT_MASK_PTP BIT(7) #define MVPP22_XLG_CTRL4_REG 0x184 #define MVPP22_XLG_CTRL4_FWD_FC BIT(5) #define MVPP22_XLG_CTRL4_FWD_PFC BIT(6) @@ -501,6 +510,70 @@ #define MVPP22_SMI_MISC_CFG_REG 0x1204 #define MVPP22_SMI_POLLING_EN BIT(10) +/* TAI registers, PPv2.2 only, relative to priv->iface_base */ +#define MVPP22_TAI_INT_CAUSE 0x1400 +#define MVPP22_TAI_INT_MASK 0x1404 +#define MVPP22_TAI_CR0 0x1408 +#define MVPP22_TAI_CR1 0x140c +#define MVPP22_TAI_TCFCR0 0x1410 +#define MVPP22_TAI_TCFCR1 0x1414 +#define MVPP22_TAI_TCFCR2 0x1418 +#define MVPP22_TAI_FATWR 0x141c +#define MVPP22_TAI_TOD_STEP_NANO_CR 0x1420 +#define MVPP22_TAI_TOD_STEP_FRAC_HIGH 0x1424 +#define MVPP22_TAI_TOD_STEP_FRAC_LOW 0x1428 +#define MVPP22_TAI_TAPDC_HIGH 0x142c +#define MVPP22_TAI_TAPDC_LOW 0x1430 +#define MVPP22_TAI_TGTOD_SEC_HIGH 0x1434 +#define MVPP22_TAI_TGTOD_SEC_MED 0x1438 +#define MVPP22_TAI_TGTOD_SEC_LOW 0x143c +#define MVPP22_TAI_TGTOD_NANO_HIGH 0x1440 +#define MVPP22_TAI_TGTOD_NANO_LOW 0x1444 +#define MVPP22_TAI_TGTOD_FRAC_HIGH 0x1448 +#define MVPP22_TAI_TGTOD_FRAC_LOW 0x144c +#define MVPP22_TAI_TLV_SEC_HIGH 0x1450 +#define MVPP22_TAI_TLV_SEC_MED 0x1454 +#define MVPP22_TAI_TLV_SEC_LOW 0x1458 +#define MVPP22_TAI_TLV_NANO_HIGH 0x145c +#define MVPP22_TAI_TLV_NANO_LOW 0x1460 +#define MVPP22_TAI_TLV_FRAC_HIGH 0x1464 +#define MVPP22_TAI_TLV_FRAC_LOW 0x1468 +#define MVPP22_TAI_TCV0_SEC_HIGH 0x146c +#define MVPP22_TAI_TCV0_SEC_MED 0x1470 +#define MVPP22_TAI_TCV0_SEC_LOW 0x1474 +#define MVPP22_TAI_TCV0_NANO_HIGH 0x1478 +#define MVPP22_TAI_TCV0_NANO_LOW 0x147c +#define MVPP22_TAI_TCV0_FRAC_HIGH 0x1480 +#define MVPP22_TAI_TCV0_FRAC_LOW 0x1484 +#define MVPP22_TAI_TCV1_SEC_HIGH 0x1488 +#define MVPP22_TAI_TCV1_SEC_MED 0x148c +#define MVPP22_TAI_TCV1_SEC_LOW 0x1490 +#define MVPP22_TAI_TCV1_NANO_HIGH 0x1494 +#define MVPP22_TAI_TCV1_NANO_LOW 0x1498 +#define MVPP22_TAI_TCV1_FRAC_HIGH 0x149c +#define MVPP22_TAI_TCV1_FRAC_LOW 0x14a0 +#define MVPP22_TAI_TCSR 0x14a4 +#define MVPP22_TAI_TUC_LSB 0x14a8 +#define MVPP22_TAI_GFM_SEC_HIGH 0x14ac +#define MVPP22_TAI_GFM_SEC_MED 0x14b0 +#define MVPP22_TAI_GFM_SEC_LOW 0x14b4 +#define MVPP22_TAI_GFM_NANO_HIGH 0x14b8 +#define MVPP22_TAI_GFM_NANO_LOW 0x14bc +#define MVPP22_TAI_GFM_FRAC_HIGH 0x14c0 +#define MVPP22_TAI_GFM_FRAC_LOW 0x14c4 +#define MVPP22_TAI_PCLK_DA_HIGH 0x14c8 +#define MVPP22_TAI_PCLK_DA_LOW 0x14cc +#define MVPP22_TAI_CTCR 0x14d0 +#define MVPP22_TAI_PCLK_CCC_HIGH 0x14d4 +#define MVPP22_TAI_PCLK_CCC_LOW 0x14d8 +#define MVPP22_TAI_DTC_HIGH 0x14dc +#define MVPP22_TAI_DTC_LOW 0x14e0 +#define MVPP22_TAI_CCC_HIGH 0x14e4 +#define MVPP22_TAI_CCC_LOW 0x14e8 +#define MVPP22_TAI_ICICE 0x14f4 +#define MVPP22_TAI_ICICC_LOW 0x14f8 +#define MVPP22_TAI_TUC_MSB 0x14fc + #define MVPP22_GMAC_BASE(port) (0x7000 + (port) * 0x1000 + 0xe00) #define MVPP2_CAUSE_TXQ_SENT_DESC_ALL_MASK 0xff @@ -527,6 +600,46 @@ #define MVPP22_XPCS_CFG0_PCS_MODE(n) ((n) << 3) #define MVPP22_XPCS_CFG0_ACTIVE_LANE(n) ((n) << 5) +/* PTP registers. PPv2.2 only */ +#define MVPP22_PTP_BASE(port) (0x7800 + (port * 0x1000)) +#define MVPP22_PTP_INT_CAUSE 0x00 +#define MVPP22_PTP_INT_CAUSE_QUEUE1 BIT(6) +#define MVPP22_PTP_INT_CAUSE_QUEUE0 BIT(5) +#define MVPP22_PTP_INT_MASK 0x04 +#define MVPP22_PTP_INT_MASK_QUEUE1 BIT(6) +#define MVPP22_PTP_INT_MASK_QUEUE0 BIT(5) +#define MVPP22_PTP_GCR 0x08 +#define MVPP22_PTP_GCR_RX_RESET BIT(13) +#define MVPP22_PTP_GCR_TX_RESET BIT(1) +#define MVPP22_PTP_GCR_TSU_ENABLE BIT(0) +#define MVPP22_PTP_TX_Q0_R0 0x0c +#define MVPP22_PTP_TX_Q0_R1 0x10 +#define MVPP22_PTP_TX_Q0_R2 0x14 +#define MVPP22_PTP_TX_Q1_R0 0x18 +#define MVPP22_PTP_TX_Q1_R1 0x1c +#define MVPP22_PTP_TX_Q1_R2 0x20 +#define MVPP22_PTP_TPCR 0x24 +#define MVPP22_PTP_V1PCR 0x28 +#define MVPP22_PTP_V2PCR 0x2c +#define MVPP22_PTP_Y1731PCR 0x30 +#define MVPP22_PTP_NTPTSPCR 0x34 +#define MVPP22_PTP_NTPRXPCR 0x38 +#define MVPP22_PTP_NTPTXPCR 0x3c +#define MVPP22_PTP_WAMPPCR 0x40 +#define MVPP22_PTP_NAPCR 0x44 +#define MVPP22_PTP_FAPCR 0x48 +#define MVPP22_PTP_CAPCR 0x50 +#define MVPP22_PTP_ATAPCR 0x54 +#define MVPP22_PTP_ACTAPCR 0x58 +#define MVPP22_PTP_CATAPCR 0x5c +#define MVPP22_PTP_CACTAPCR 0x60 +#define MVPP22_PTP_AITAPCR 0x64 +#define MVPP22_PTP_CAITAPCR 0x68 +#define MVPP22_PTP_CITAPCR 0x6c +#define MVPP22_PTP_NTP_OFF_HIGH 0x70 +#define MVPP22_PTP_NTP_OFF_LOW 0x74 +#define MVPP22_PTP_TX_PIPE_STATUS_DELAY 0x78 + /* System controller registers. Accessed through a regmap. */ #define GENCONF_SOFT_RESET1 0x1108 #define GENCONF_SOFT_RESET1_GOP BIT(6) @@ -692,6 +805,43 @@ enum mvpp2_prs_l3_cast { MVPP2_PRS_L3_BROAD_CAST }; +/* PTP descriptor constants. The low bits of the descriptor are stored + * separately from the high bits. + */ +#define MVPP22_PTP_DESC_MASK_LOW 0xfff + +/* PTPAction */ +enum mvpp22_ptp_action { + MVPP22_PTP_ACTION_NONE = 0, + MVPP22_PTP_ACTION_FORWARD = 1, + MVPP22_PTP_ACTION_CAPTURE = 3, + /* The following have not been verified */ + MVPP22_PTP_ACTION_ADDTIME = 4, + MVPP22_PTP_ACTION_ADDCORRECTEDTIME = 5, + MVPP22_PTP_ACTION_CAPTUREADDTIME = 6, + MVPP22_PTP_ACTION_CAPTUREADDCORRECTEDTIME = 7, + MVPP22_PTP_ACTION_ADDINGRESSTIME = 8, + MVPP22_PTP_ACTION_CAPTUREADDINGRESSTIME = 9, + MVPP22_PTP_ACTION_CAPTUREINGRESSTIME = 10, +}; + +/* PTPPacketFormat */ +enum mvpp22_ptp_packet_format { + MVPP22_PTP_PKT_FMT_PTPV2 = 0, + MVPP22_PTP_PKT_FMT_PTPV1 = 1, + MVPP22_PTP_PKT_FMT_Y1731 = 2, + MVPP22_PTP_PKT_FMT_NTPTS = 3, + MVPP22_PTP_PKT_FMT_NTPRX = 4, + MVPP22_PTP_PKT_FMT_NTPTX = 5, + MVPP22_PTP_PKT_FMT_TWAMP = 6, +}; + +#define MVPP22_PTP_ACTION(x) (((x) & 15) << 0) +#define MVPP22_PTP_PACKETFORMAT(x) (((x) & 7) << 4) +#define MVPP22_PTP_MACTIMESTAMPINGEN BIT(11) +#define MVPP22_PTP_TIMESTAMPENTRYID(x) (((x) & 31) << 12) +#define MVPP22_PTP_TIMESTAMPQUEUESELECT BIT(18) + /* BM constants */ #define MVPP2_BM_JUMBO_BUF_NUM 512 #define MVPP2_BM_LONG_BUF_NUM 1024 @@ -759,6 +909,8 @@ enum mvpp2_prs_l3_cast { #define MVPP2_DESC_DMA_MASK DMA_BIT_MASK(40) +struct mvpp2_tai; + /* Definitions */ struct mvpp2_dbgfs_entries; @@ -794,6 +946,7 @@ struct mvpp2 { /* List of pointers to port structures */ int port_count; struct mvpp2_port *port_list[MVPP2_MAX_PORTS]; + struct mvpp2_tai *tai; /* Number of Tx threads used */ unsigned int nthreads; @@ -907,6 +1060,11 @@ struct mvpp2_ethtool_fs { struct ethtool_rxnfc rxnfc; }; +struct mvpp2_hwtstamp_queue { + struct sk_buff *skb[32]; + u8 next; +}; + struct mvpp2_port { u8 id; @@ -915,7 +1073,7 @@ struct mvpp2_port { */ int gop_id; - int link_irq; + int port_irq; struct mvpp2 *priv; @@ -990,6 +1148,11 @@ struct mvpp2_port { * them from 0 */ int rss_ctx[MVPP22_N_RSS_TABLES]; + + bool hwtstamp; + bool rx_hwtstamp; + enum hwtstamp_tx_types tx_hwtstamp_type; + struct mvpp2_hwtstamp_queue tx_hwtstamp_queue[2]; }; /* The mvpp2_tx_desc and mvpp2_rx_desc structures describe the @@ -1058,7 +1221,8 @@ struct mvpp22_tx_desc { u8 packet_offset; u8 phys_txq; __le16 data_size; - __le64 reserved1; + __le32 ptp_descriptor; + __le32 reserved2; __le64 buf_dma_addr_ptp; __le64 buf_cookie_misc; }; @@ -1069,7 +1233,7 @@ struct mvpp22_rx_desc { __le16 reserved1; __le16 data_size; __le32 reserved2; - __le32 reserved3; + __le32 timestamp; __le64 buf_dma_addr_key_hash; __le64 buf_cookie_misc; }; @@ -1249,4 +1413,36 @@ void mvpp2_dbgfs_init(struct mvpp2 *priv, const char *name); void mvpp2_dbgfs_cleanup(struct mvpp2 *priv); +#ifdef CONFIG_MVPP2_PTP +int mvpp22_tai_probe(struct device *dev, struct mvpp2 *priv); +void mvpp22_tai_tstamp(struct mvpp2_tai *tai, u32 tstamp, + struct skb_shared_hwtstamps *hwtstamp); +void mvpp22_tai_start(struct mvpp2_tai *tai); +void mvpp22_tai_stop(struct mvpp2_tai *tai); +int mvpp22_tai_ptp_clock_index(struct mvpp2_tai *tai); +#else +static inline int mvpp22_tai_probe(struct device *dev, struct mvpp2 *priv) +{ + return 0; +} +static inline void mvpp22_tai_tstamp(struct mvpp2_tai *tai, u32 tstamp, + struct skb_shared_hwtstamps *hwtstamp) +{ +} +static inline void mvpp22_tai_start(struct mvpp2_tai *tai) +{ +} +static inline void mvpp22_tai_stop(struct mvpp2_tai *tai) +{ +} +static inline int mvpp22_tai_ptp_clock_index(struct mvpp2_tai *tai) +{ + return -1; +} +#endif + +static inline bool mvpp22_rx_hwtstamping(struct mvpp2_port *port) +{ + return IS_ENABLED(CONFIG_MVPP2_PTP) && port->rx_hwtstamp; +} #endif diff --git a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c index ee8b6a9037ce..7d86940747d1 100644 --- a/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c +++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_main.c @@ -28,6 +28,7 @@ #include <linux/phy.h> #include <linux/phylink.h> #include <linux/phy/phy.h> +#include <linux/ptp_classify.h> #include <linux/clk.h> #include <linux/hrtimer.h> #include <linux/ktime.h> @@ -1379,6 +1380,10 @@ static void mvpp22_gop_setup_irq(struct mvpp2_port *port) { u32 val; + mvpp2_modify(port->base + MVPP22_GMAC_INT_SUM_MASK, + MVPP22_GMAC_INT_SUM_MASK_PTP, + MVPP22_GMAC_INT_SUM_MASK_PTP); + if (port->phylink || phy_interface_mode_is_rgmii(port->phy_interface) || phy_interface_mode_is_8023z(port->phy_interface) || @@ -1392,6 +1397,10 @@ static void mvpp22_gop_setup_irq(struct mvpp2_port *port) val = readl(port->base + MVPP22_XLG_INT_MASK); val |= MVPP22_XLG_INT_MASK_LINK; writel(val, port->base + MVPP22_XLG_INT_MASK); + + mvpp2_modify(port->base + MVPP22_XLG_EXT_INT_MASK, + MVPP22_XLG_EXT_INT_MASK_PTP, + MVPP22_XLG_EXT_INT_MASK_PTP); } mvpp22_gop_unmask_irq(port); @@ -2974,44 +2983,67 @@ static irqreturn_t mvpp2_isr(int irq, void *dev_id) return IRQ_HANDLED; } -/* Per-port interrupt for link status changes */ -static irqreturn_t mvpp2_link_status_isr(int irq, void *dev_id) +static void mvpp2_isr_handle_ptp_queue(struct mvpp2_port *port, int nq) { - struct mvpp2_port *port = (struct mvpp2_port *)dev_id; - struct net_device *dev = port->dev; - bool event = false, link = false; - u32 val; + struct skb_shared_hwtstamps shhwtstamps; + struct mvpp2_hwtstamp_queue *queue; + struct sk_buff *skb; + void __iomem *ptp_q; + unsigned int id; + u32 r0, r1, r2; - mvpp22_gop_mask_irq(port); + ptp_q = port->priv->iface_base + MVPP22_PTP_BASE(port->gop_id); + if (nq) + ptp_q += MVPP22_PTP_TX_Q1_R0 - MVPP22_PTP_TX_Q0_R0; - if (mvpp2_port_supports_xlg(port) && - mvpp2_is_xlg(port->phy_interface)) { - val = readl(port->base + MVPP22_XLG_INT_STAT); - if (val & MVPP22_XLG_INT_STAT_LINK) { - event = true; - val = readl(port->base + MVPP22_XLG_STATUS); - if (val & MVPP22_XLG_STATUS_LINK_UP) - link = true; - } - } else if (phy_interface_mode_is_rgmii(port->phy_interface) || - phy_interface_mode_is_8023z(port->phy_interface) || - port->phy_interface == PHY_INTERFACE_MODE_SGMII) { - val = readl(port->base + MVPP22_GMAC_INT_STAT); - if (val & MVPP22_GMAC_INT_STAT_LINK) { - event = true; - val = readl(port->base + MVPP2_GMAC_STATUS0); - if (val & MVPP2_GMAC_STATUS0_LINK_UP) - link = true; + queue = &port->tx_hwtstamp_queue[nq]; + + while (1) { + r0 = readl_relaxed(ptp_q + MVPP22_PTP_TX_Q0_R0) & 0xffff; + if (!r0) + break; + + r1 = readl_relaxed(ptp_q + MVPP22_PTP_TX_Q0_R1) & 0xffff; + r2 = readl_relaxed(ptp_q + MVPP22_PTP_TX_Q0_R2) & 0xffff; + + id = (r0 >> 1) & 31; + + skb = queue->skb[id]; + queue->skb[id] = NULL; + if (skb) { + u32 ts = r2 << 19 | r1 << 3 | r0 >> 13; + + mvpp22_tai_tstamp(port->priv->tai, ts, &shhwtstamps); + skb_tstamp_tx(skb, &shhwtstamps); + dev_kfree_skb_any(skb); } } +} + +static void mvpp2_isr_handle_ptp(struct mvpp2_port *port) +{ + void __iomem *ptp; + u32 val; + + ptp = port->priv->iface_base + MVPP22_PTP_BASE(port->gop_id); + val = readl(ptp + MVPP22_PTP_INT_CAUSE); + if (val & MVPP22_PTP_INT_CAUSE_QUEUE0) + mvpp2_isr_handle_ptp_queue(port, 0); + if (val & MVPP22_PTP_INT_CAUSE_QUEUE1) + mvpp2_isr_handle_ptp_queue(port, 1); +} + +static void mvpp2_isr_handle_link(struct mvpp2_port *port, bool link) +{ + struct net_device *dev = port->dev; if (port->phylink) { phylink_mac_change(port->phylink, link); - goto handled; + return; } - if (!netif_running(dev) || !event) - goto handled; + if (!netif_running(dev)) + return; if (link) { mvpp2_interrupts_enable(port); @@ -3028,8 +3060,67 @@ static irqreturn_t mvpp2_link_status_isr(int irq, void *dev_id) mvpp2_interrupts_disable(port); } +} + +static void mvpp2_isr_handle_xlg(struct mvpp2_port *port) +{ + bool link; + u32 val; + + val = readl(port->base + MVPP22_XLG_INT_STAT); + if (val & MVPP22_XLG_INT_STAT_LINK) { + val = readl(port->base + MVPP22_XLG_STATUS); + if (val & MVPP22_XLG_STATUS_LINK_UP) + link = true; + mvpp2_isr_handle_link(port, link); + } +} + +static void mvpp2_isr_handle_gmac_internal(struct mvpp2_port *port) +{ + bool link; + u32 val; + + if (phy_interface_mode_is_rgmii(port->phy_interface) || + phy_interface_mode_is_8023z(port->phy_interface) || + port->phy_interface == PHY_INTERFACE_MODE_SGMII) { + val = readl(port->base + MVPP22_GMAC_INT_STAT); + if (val & MVPP22_GMAC_INT_STAT_LINK) { + val = readl(port->base + MVPP2_GMAC_STATUS0); + if (val & MVPP2_GMAC_STATUS0_LINK_UP) + link = true; + mvpp2_isr_handle_link(port, link); + } + } +} + +/* Per-port interrupt for link status changes */ +static irqreturn_t mvpp2_port_isr(int irq, void *dev_id) +{ + struct mvpp2_port *port = (struct mvpp2_port *)dev_id; + u32 val; + + mvpp22_gop_mask_irq(port); + + if (mvpp2_port_supports_xlg(port) && + mvpp2_is_xlg(port->phy_interface)) { + /* Check the external status register */ + val = readl(port->base + MVPP22_XLG_EXT_INT_STAT); + if (val & MVPP22_XLG_EXT_INT_STAT_XLG) + mvpp2_isr_handle_xlg(port); + if (val & MVPP22_XLG_EXT_INT_STAT_PTP) + mvpp2_isr_handle_ptp(port); + } else { + /* If it's not the XLG, we must be using the GMAC. + * Check the summary status. + */ + val = readl(port->base + MVPP22_GMAC_INT_SUM_STAT); + if (val & MVPP22_GMAC_INT_SUM_STAT_INTERNAL) + mvpp2_isr_handle_gmac_internal(port); + if (val & MVPP22_GMAC_INT_SUM_STAT_PTP) + mvpp2_isr_handle_ptp(port); + } -handled: mvpp22_gop_unmask_irq(port); return IRQ_HANDLED; } @@ -3421,7 +3512,7 @@ static int mvpp2_rx(struct mvpp2_port *port, struct napi_struct *napi, unsigned int frag_size; dma_addr_t dma_addr; phys_addr_t phys_addr; - u32 rx_status; + u32 rx_status, timestamp; int pool, rx_bytes, err, ret; void *data; @@ -3499,6 +3590,15 @@ static int mvpp2_rx(struct mvpp2_port *port, struct napi_struct *napi, goto err_drop_frame; } + /* If we have RX hardware timestamping enabled, grab the + * timestamp from the queue and convert. + */ + if (mvpp22_rx_hwtstamping(port)) { + timestamp = le32_to_cpu(rx_desc->pp22.timestamp); + mvpp22_tai_tstamp(port->priv->tai, timestamp, + skb_hwtstamps(skb)); + } + err = mvpp2_rx_refill(port, bm_pool, pp, pool); if (err) { netdev_err(port->dev, "failed to refill BM pools\n"); @@ -3573,6 +3673,92 @@ tx_desc_unmap_put(struct mvpp2_port *port, struct mvpp2_tx_queue *txq, mvpp2_txq_desc_put(txq); } +static void mvpp2_txdesc_clear_ptp(struct mvpp2_port *port, + struct mvpp2_tx_desc *desc) +{ + /* We only need to clear the low bits */ + if (port->priv->hw_version != MVPP21) + desc->pp22.ptp_descriptor &= + cpu_to_le32(~MVPP22_PTP_DESC_MASK_LOW); +} + +static bool mvpp2_tx_hw_tstamp(struct mvpp2_port *port, + struct mvpp2_tx_desc *tx_desc, + struct sk_buff *skb) +{ + struct mvpp2_hwtstamp_queue *queue; + unsigned int mtype, type, i; + struct ptp_header *hdr; + u64 ptpdesc; + + if (port->priv->hw_version == MVPP21 || + port->tx_hwtstamp_type == HWTSTAMP_TX_OFF) + return false; + + type = ptp_classify_raw(skb); + if (!type) + return false; + + hdr = ptp_parse_header(skb, type); + if (!hdr) + return false; + + ptpdesc = MVPP22_PTP_MACTIMESTAMPINGEN | + MVPP22_PTP_ACTION_CAPTURE; + queue = &port->tx_hwtstamp_queue[0]; + + switch (type & PTP_CLASS_VMASK) { + case PTP_CLASS_V1: + ptpdesc |= MVPP22_PTP_PACKETFORMAT(MVPP22_PTP_PKT_FMT_PTPV1); + break; + + case PTP_CLASS_V2: + ptpdesc |= MVPP22_PTP_PACKETFORMAT(MVPP22_PTP_PKT_FMT_PTPV2); + mtype = hdr->tsmt & 15; + /* Direct PTP Sync messages to queue 1 */ + if (mtype == 0) { + ptpdesc |= MVPP22_PTP_TIMESTAMPQUEUESELECT; + queue = &port->tx_hwtstamp_queue[1]; + } + break; + } + + /* Take a reference on the skb and insert into our queue */ + i = queue->next; + queue->next = (i + 1) & 31; + if (queue->skb[i]) + dev_kfree_skb_any(queue->skb[i]); + queue->skb[i] = skb_get(skb); + + ptpdesc |= MVPP22_PTP_TIMESTAMPENTRYID(i); + + /* + * 3:0 - PTPAction + * 6:4 - PTPPacketFormat + * 7 - PTP_CF_WraparoundCheckEn + * 9:8 - IngressTimestampSeconds[1:0] + * 10 - Reserved + * 11 - MACTimestampingEn + * 17:12 - PTP_TimestampQueueEntryID[5:0] + * 18 - PTPTimestampQueueSelect + * 19 - UDPChecksumUpdateEn + * 27:20 - TimestampOffset + * PTP, NTPTransmit, OWAMP/TWAMP - L3 to PTP header + * NTPTs, Y.1731 - L3 to timestamp entry + * 35:28 - UDP Checksum Offset + * + * stored in tx descriptor bits 75:64 (11:0) and 191:168 (35:12) + */ + tx_desc->pp22.ptp_descriptor &= + cpu_to_le32(~MVPP22_PTP_DESC_MASK_LOW); + tx_desc->pp22.ptp_descriptor |= + cpu_to_le32(ptpdesc & MVPP22_PTP_DESC_MASK_LOW); + tx_desc->pp22.buf_dma_addr_ptp &= cpu_to_le64(~0xffffff0000000000ULL); + tx_desc->pp22.buf_dma_addr_ptp |= cpu_to_le64((ptpdesc >> 12) << 40); + + return true; +} + /* Handle tx fragmentation processing */ static int mvpp2_tx_frag_process(struct mvpp2_port *port, struct sk_buff *skb, struct mvpp2_tx_queue *aggr_txq, @@ -3589,6 +3775,7 @@ static int mvpp2_tx_frag_process(struct mvpp2_port *port, struct sk_buff *skb, void *addr = skb_frag_address(frag); tx_desc = mvpp2_txq_next_desc_get(aggr_txq); + mvpp2_txdesc_clear_ptp(port, tx_desc); mvpp2_txdesc_txq_set(port, tx_desc, txq->id); mvpp2_txdesc_size_set(port, tx_desc, skb_frag_size(frag)); @@ -3638,6 +3825,7 @@ static inline void mvpp2_tso_put_hdr(struct sk_buff *skb, struct mvpp2_tx_desc *tx_desc = mvpp2_txq_next_desc_get(aggr_txq); dma_addr_t addr; + mvpp2_txdesc_clear_ptp(port, tx_desc); mvpp2_txdesc_txq_set(port, tx_desc, txq->id); mvpp2_txdesc_size_set(port, tx_desc, hdr_sz); @@ -3662,6 +3850,7 @@ static inline int mvpp2_tso_put_data(struct sk_buff *skb, struct mvpp2_tx_desc *tx_desc = mvpp2_txq_next_desc_get(aggr_txq); dma_addr_t buf_dma_addr; + mvpp2_txdesc_clear_ptp(port, tx_desc); mvpp2_txdesc_txq_set(port, tx_desc, txq->id); mvpp2_txdesc_size_set(port, tx_desc, sz); @@ -3778,6 +3967,9 @@ static netdev_tx_t mvpp2_tx(struct sk_buff *skb, struct net_device *dev) /* Get a descriptor for the first part of the packet */ tx_desc = mvpp2_txq_next_desc_get(aggr_txq); + if (!(skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) || + !mvpp2_tx_hw_tstamp(port, tx_desc, skb)) + mvpp2_txdesc_clear_ptp(port, tx_desc); mvpp2_txdesc_txq_set(port, tx_desc, txq->id); mvpp2_txdesc_size_set(port, tx_desc, skb_headlen(skb)); @@ -4211,12 +4403,13 @@ static int mvpp2_open(struct net_device *dev) valid = true; } - if (priv->hw_version == MVPP22 && port->link_irq) { - err = request_irq(port->link_irq, mvpp2_link_status_isr, 0, + if (priv->hw_version == MVPP22 && port->port_irq) { + err = request_irq(port->port_irq, mvpp2_port_isr, 0, dev->name, port); if (err) { - netdev_err(port->dev, "cannot request link IRQ %d\n", - port->link_irq); + netdev_err(port->dev, + "cannot request port link/ptp IRQ %d\n", + port->port_irq); goto err_free_irq; } @@ -4227,7 +4420,7 @@ static int mvpp2_open(struct net_device *dev) valid = true; } else { - port->link_irq = 0; + port->port_irq = 0; } if (!valid) { @@ -4271,8 +4464,8 @@ static int mvpp2_stop(struct net_device *dev) if (port->phylink) phylink_disconnect_phy(port->phylink); - if (port->link_irq) - free_irq(port->link_irq, port); + if (port->port_irq) + free_irq(port->port_irq, port); mvpp2_irqs_deinit(port); if (!port->has_tx_irqs) { @@ -4532,10 +4725,124 @@ mvpp2_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats) stats->tx_dropped = dev->stats.tx_dropped; } +static int mvpp2_set_ts_config(struct mvpp2_port *port, struct ifreq *ifr) +{ + struct hwtstamp_config config; + void __iomem *ptp; + u32 gcr, int_mask; + + if (copy_from_user(&config, ifr->ifr_data, sizeof(config))) + return -EFAULT; + + if (config.flags) + return -EINVAL; + + if (config.tx_type != HWTSTAMP_TX_OFF && + config.tx_type != HWTSTAMP_TX_ON) + return -ERANGE; + + ptp = port->priv->iface_base + MVPP22_PTP_BASE(port->gop_id); + + int_mask = gcr = 0; + if (config.tx_type != HWTSTAMP_TX_OFF) { + gcr |= MVPP22_PTP_GCR_TSU_ENABLE | MVPP22_PTP_GCR_TX_RESET; + int_mask |= MVPP22_PTP_INT_MASK_QUEUE1 | + MVPP22_PTP_INT_MASK_QUEUE0; + } + + /* It seems we must also release the TX reset when enabling the TSU */ + if (config.rx_filter != HWTSTAMP_FILTER_NONE) + gcr |= MVPP22_PTP_GCR_TSU_ENABLE | MVPP22_PTP_GCR_RX_RESET | + MVPP22_PTP_GCR_TX_RESET; + + if (gcr & MVPP22_PTP_GCR_TSU_ENABLE) + mvpp22_tai_start(port->priv->tai); + + if (config.rx_filter != HWTSTAMP_FILTER_NONE) { + config.rx_filter = HWTSTAMP_FILTER_ALL; + mvpp2_modify(ptp + MVPP22_PTP_GCR, + MVPP22_PTP_GCR_RX_RESET | + MVPP22_PTP_GCR_TX_RESET | + MVPP22_PTP_GCR_TSU_ENABLE, gcr); + port->rx_hwtstamp = true; + } else { + port->rx_hwtstamp = false; + mvpp2_modify(ptp + MVPP22_PTP_GCR, + MVPP22_PTP_GCR_RX_RESET | + MVPP22_PTP_GCR_TX_RESET | + MVPP22_PTP_GCR_TSU_ENABLE, gcr); + } + + mvpp2_modify(ptp + MVPP22_PTP_INT_MASK, + MVPP22_PTP_INT_MASK_QUEUE1 | + MVPP22_PTP_INT_MASK_QUEUE0, int_mask); + + if (!(gcr & MVPP22_PTP_GCR_TSU_ENABLE)) + mvpp22_tai_stop(port->priv->tai); + + port->tx_hwtstamp_type = config.tx_type; + + if (copy_to_user(ifr->ifr_data, &config, sizeof(config))) + return -EFAULT; + + return 0; +} + +static int mvpp2_get_ts_config(struct mvpp2_port *port, struct ifreq *ifr) +{ + struct hwtstamp_config config; + + memset(&config, 0, sizeof(config)); + + config.tx_type = port->tx_hwtstamp_type; + config.rx_filter = port->rx_hwtstamp ? + HWTSTAMP_FILTER_ALL : HWTSTAMP_FILTER_NONE; + + if (copy_to_user(ifr->ifr_data, &config, sizeof(config))) + return -EFAULT; + + return 0; +} + +static int mvpp2_ethtool_get_ts_info(struct net_device *dev, + struct ethtool_ts_info *info) +{ + struct mvpp2_port *port = netdev_priv(dev); + + if (!port->hwtstamp) + return -EOPNOTSUPP; + + info->phc_index = mvpp22_tai_ptp_clock_index(port->priv->tai); + info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE | + SOF_TIMESTAMPING_RX_SOFTWARE | + SOF_TIMESTAMPING_SOFTWARE | + SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RAW_HARDWARE; + info->tx_types = BIT(HWTSTAMP_TX_OFF) | + BIT(HWTSTAMP_TX_ON); + info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) | + BIT(HWTSTAMP_FILTER_ALL); + + return 0; +} + static int mvpp2_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { struct mvpp2_port *port = netdev_priv(dev); + switch (cmd) { + case SIOCSHWTSTAMP: + if (port->hwtstamp) + return mvpp2_set_ts_config(port, ifr); + break; + + case SIOCGHWTSTAMP: + if (port->hwtstamp) + return mvpp2_get_ts_config(port, ifr); + break; + } + if (!port->phylink) return -ENOTSUPP; @@ -5005,6 +5312,7 @@ static const struct ethtool_ops mvpp2_eth_tool_ops = { ETHTOOL_COALESCE_MAX_FRAMES, .nway_reset = mvpp2_ethtool_nway_reset, .get_link = ethtool_op_get_link, + .get_ts_info = mvpp2_ethtool_get_ts_info, .set_coalesce = mvpp2_ethtool_set_coalesce, .get_coalesce = mvpp2_ethtool_get_coalesce, .get_drvinfo = mvpp2_ethtool_get_drvinfo, @@ -6037,16 +6345,16 @@ static int mvpp2_port_probe(struct platform_device *pdev, goto err_free_netdev; if (port_node) - port->link_irq = of_irq_get_byname(port_node, "link"); + port->port_irq = of_irq_get_byname(port_node, "link"); else - port->link_irq = fwnode_irq_get(port_fwnode, port->nqvecs + 1); - if (port->link_irq == -EPROBE_DEFER) { + port->port_irq = fwnode_irq_get(port_fwnode, port->nqvecs + 1); + if (port->port_irq == -EPROBE_DEFER) { err = -EPROBE_DEFER; goto err_deinit_qvecs; } - if (port->link_irq <= 0) + if (port->port_irq <= 0) /* the link irq is optional */ - port->link_irq = 0; + port->port_irq = 0; if (fwnode_property_read_bool(port_fwnode, "marvell,loopback")) port->flags |= MVPP2_F_LOOPBACK; @@ -6083,6 +6391,12 @@ static int mvpp2_port_probe(struct platform_device *pdev, port->stats_base = port->priv->iface_base + MVPP22_MIB_COUNTERS_OFFSET + port->gop_id * MVPP22_MIB_COUNTERS_PORT_SZ; + + /* We may want a property to describe whether we should use + * MAC hardware timestamping. + */ + if (priv->tai) + port->hwtstamp = true; } /* Alloc per-cpu and ethtool stats */ @@ -6210,8 +6524,8 @@ err_free_txq_pcpu: err_free_stats: free_percpu(port->stats); err_free_irq: - if (port->link_irq) - irq_dispose_mapping(port->link_irq); + if (port->port_irq) + irq_dispose_mapping(port->port_irq); err_deinit_qvecs: mvpp2_queue_vectors_deinit(port); err_free_netdev: @@ -6232,8 +6546,8 @@ static void mvpp2_port_remove(struct mvpp2_port *port) for (i = 0; i < port->ntxqs; i++) free_percpu(port->txqs[i]->pcpu); mvpp2_queue_vectors_deinit(port); - if (port->link_irq) - irq_dispose_mapping(port->link_irq); + if (port->port_irq) + irq_dispose_mapping(port->port_irq); free_netdev(port->dev); } @@ -6645,6 +6959,10 @@ static int mvpp2_probe(struct platform_device *pdev) goto err_axi_clk; } + err = mvpp22_tai_probe(&pdev->dev, priv); + if (err < 0) + goto err_axi_clk; + /* Initialize ports */ fwnode_for_each_available_child_node(fwnode, port_fwnode) { err = mvpp2_port_probe(pdev, port_fwnode, priv); diff --git a/drivers/net/ethernet/marvell/mvpp2/mvpp2_tai.c b/drivers/net/ethernet/marvell/mvpp2/mvpp2_tai.c new file mode 100644 index 000000000000..95862aff49f1 --- /dev/null +++ b/drivers/net/ethernet/marvell/mvpp2/mvpp2_tai.c @@ -0,0 +1,457 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell PP2.2 TAI support + * + * Note: + * Do NOT use the event capture support. + * Do Not even set the MPP muxes to allow PTP_EVENT_REQ to be used. + * It will disrupt the operation of this driver, and there is nothing + * that this driver can do to prevent that. Even using PTP_EVENT_REQ + * as an output will be seen as a trigger input, which can't be masked. + * When ever a trigger input is seen, the action in the TCFCR0_TCF + * field will be performed - whether it is a set, increment, decrement + * read, or frequency update. + * + * Other notes (useful, not specified in the documentation): + * - PTP_PULSE_OUT (PTP_EVENT_REQ MPP) + * It looks like the hardware can't generate a pulse at nsec=0. (The + * output doesn't trigger if the nsec field is zero.) + * Note: when configured as an output via the register at 0xfX441120, + * the input is still very much alive, and will trigger the current TCF + * function. + * - PTP_CLK_OUT (PTP_TRIG_GEN MPP) + * This generates a "PPS" signal determined by the CCC registers. It + * seems this is not aligned to the TOD counter in any way (it may be + * initially, but if you specify a non-round second interval, it won't, + * and you can't easily get it back.) + * - PTP_PCLK_OUT + * This generates a 50% duty cycle clock based on the TOD counter, and + * seems it can be set to any period of 1ns resolution. It is probably + * limited by the TOD step size. Its period is defined by the PCLK_CCC + * registers. Again, its alignment to the second is questionable. + * + * Consequently, we support none of these. + */ +#include <linux/io.h> +#include <linux/ptp_clock_kernel.h> +#include <linux/slab.h> + +#include "mvpp2.h" + +#define CR0_SW_NRESET BIT(0) + +#define TCFCR0_PHASE_UPDATE_ENABLE BIT(8) +#define TCFCR0_TCF_MASK (7 << 2) +#define TCFCR0_TCF_UPDATE (0 << 2) +#define TCFCR0_TCF_FREQUPDATE (1 << 2) +#define TCFCR0_TCF_INCREMENT (2 << 2) +#define TCFCR0_TCF_DECREMENT (3 << 2) +#define TCFCR0_TCF_CAPTURE (4 << 2) +#define TCFCR0_TCF_NOP (7 << 2) +#define TCFCR0_TCF_TRIGGER BIT(0) + +#define TCSR_CAPTURE_1_VALID BIT(1) +#define TCSR_CAPTURE_0_VALID BIT(0) + +struct mvpp2_tai { + struct ptp_clock_info caps; + struct ptp_clock *ptp_clock; + void __iomem *base; + spinlock_t lock; + u64 period; // nanosecond period in 32.32 fixed point + /* This timestamp is updated every two seconds */ + struct timespec64 stamp; +}; + +static void mvpp2_tai_modify(void __iomem *reg, u32 mask, u32 set) +{ + u32 val; + + val = readl_relaxed(reg) & ~mask; + val |= set & mask; + writel(val, reg); +} + +static void mvpp2_tai_write(u32 val, void __iomem *reg) +{ + writel_relaxed(val & 0xffff, reg); +} + +static u32 mvpp2_tai_read(void __iomem *reg) +{ + return readl_relaxed(reg) & 0xffff; +} + +static struct mvpp2_tai *ptp_to_tai(struct ptp_clock_info *ptp) +{ + return container_of(ptp, struct mvpp2_tai, caps); +} + +static void mvpp22_tai_read_ts(struct timespec64 *ts, void __iomem *base) +{ + ts->tv_sec = (u64)mvpp2_tai_read(base + 0) << 32 | + mvpp2_tai_read(base + 4) << 16 | + mvpp2_tai_read(base + 8); + + ts->tv_nsec = mvpp2_tai_read(base + 12) << 16 | + mvpp2_tai_read(base + 16); + + /* Read and discard fractional part */ + readl_relaxed(base + 20); + readl_relaxed(base + 24); +} + +static void mvpp2_tai_write_tlv(const struct timespec64 *ts, u32 frac, + void __iomem *base) +{ + mvpp2_tai_write(ts->tv_sec >> 32, base + MVPP22_TAI_TLV_SEC_HIGH); + mvpp2_tai_write(ts->tv_sec >> 16, base + MVPP22_TAI_TLV_SEC_MED); + mvpp2_tai_write(ts->tv_sec, base + MVPP22_TAI_TLV_SEC_LOW); + mvpp2_tai_write(ts->tv_nsec >> 16, base + MVPP22_TAI_TLV_NANO_HIGH); + mvpp2_tai_write(ts->tv_nsec, base + MVPP22_TAI_TLV_NANO_LOW); + mvpp2_tai_write(frac >> 16, base + MVPP22_TAI_TLV_FRAC_HIGH); + mvpp2_tai_write(frac, base + MVPP22_TAI_TLV_FRAC_LOW); +} + +static void mvpp2_tai_op(u32 op, void __iomem *base) +{ + /* Trigger the operation. Note that an external unmaskable + * event on PTP_EVENT_REQ will also trigger this action. + */ + mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, + TCFCR0_TCF_MASK | TCFCR0_TCF_TRIGGER, + op | TCFCR0_TCF_TRIGGER); + mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, TCFCR0_TCF_MASK, + TCFCR0_TCF_NOP); +} + +/* The adjustment has a range of +0.5ns to -0.5ns in 2^32 steps, so has units + * of 2^-32 ns. + * + * units(s) = 1 / (2^32 * 10^9) + * fractional = abs_scaled_ppm / (2^16 * 10^6) + * + * What we want to achieve: + * freq_adjusted = freq_nominal * (1 + fractional) + * freq_delta = freq_adjusted - freq_nominal => positive = faster + * freq_delta = freq_nominal * (1 + fractional) - freq_nominal + * So: freq_delta = freq_nominal * fractional + * + * However, we are dealing with periods, so: + * period_adjusted = period_nominal / (1 + fractional) + * period_delta = period_nominal - period_adjusted => positive = faster + * period_delta = period_nominal * fractional / (1 + fractional) + * + * Hence: + * period_delta = period_nominal * abs_scaled_ppm / + * (2^16 * 10^6 + abs_scaled_ppm) + * + * To avoid overflow, we reduce both sides of the divide operation by a factor + * of 16. + */ +static u64 mvpp22_calc_frac_ppm(struct mvpp2_tai *tai, long abs_scaled_ppm) +{ + u64 val = tai->period * abs_scaled_ppm >> 4; + + return div_u64(val, (1000000 << 12) + (abs_scaled_ppm >> 4)); +} + +static s32 mvpp22_calc_max_adj(struct mvpp2_tai *tai) +{ + return 1000000; +} + +static int mvpp22_tai_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) +{ + struct mvpp2_tai *tai = ptp_to_tai(ptp); + unsigned long flags; + void __iomem *base; + bool neg_adj; + s32 frac; + u64 val; + + neg_adj = scaled_ppm < 0; + if (neg_adj) + scaled_ppm = -scaled_ppm; + + val = mvpp22_calc_frac_ppm(tai, scaled_ppm); + + /* Convert to a signed 32-bit adjustment */ + if (neg_adj) { + /* -S32_MIN warns, -val < S32_MIN fails, so go for the easy + * solution. + */ + if (val > 0x80000000) + return -ERANGE; + + frac = -val; + } else { + if (val > S32_MAX) + return -ERANGE; + + frac = val; + } + + base = tai->base; + spin_lock_irqsave(&tai->lock, flags); + mvpp2_tai_write(frac >> 16, base + MVPP22_TAI_TLV_FRAC_HIGH); + mvpp2_tai_write(frac, base + MVPP22_TAI_TLV_FRAC_LOW); + mvpp2_tai_op(TCFCR0_TCF_FREQUPDATE, base); + spin_unlock_irqrestore(&tai->lock, flags); + + return 0; +} + +static int mvpp22_tai_adjtime(struct ptp_clock_info *ptp, s64 delta) +{ + struct mvpp2_tai *tai = ptp_to_tai(ptp); + struct timespec64 ts; + unsigned long flags; + void __iomem *base; + u32 tcf; + + /* We can't deal with S64_MIN */ + if (delta == S64_MIN) + return -ERANGE; + + if (delta < 0) { + delta = -delta; + tcf = TCFCR0_TCF_DECREMENT; + } else { + tcf = TCFCR0_TCF_INCREMENT; + } + + ts = ns_to_timespec64(delta); + + base = tai->base; + spin_lock_irqsave(&tai->lock, flags); + mvpp2_tai_write_tlv(&ts, 0, base); + mvpp2_tai_op(tcf, base); + spin_unlock_irqrestore(&tai->lock, flags); + + return 0; +} + +static int mvpp22_tai_gettimex64(struct ptp_clock_info *ptp, + struct timespec64 *ts, + struct ptp_system_timestamp *sts) +{ + struct mvpp2_tai *tai = ptp_to_tai(ptp); + unsigned long flags; + void __iomem *base; + u32 tcsr; + int ret; + + base = tai->base; + spin_lock_irqsave(&tai->lock, flags); + /* XXX: the only way to read the PTP time is for the CPU to trigger + * an event. However, there is no way to distinguish between the CPU + * triggered event, and an external event on PTP_EVENT_REQ. So this + * is incompatible with external use of PTP_EVENT_REQ. + */ + ptp_read_system_prets(sts); + mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, + TCFCR0_TCF_MASK | TCFCR0_TCF_TRIGGER, + TCFCR0_TCF_CAPTURE | TCFCR0_TCF_TRIGGER); + ptp_read_system_postts(sts); + mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, TCFCR0_TCF_MASK, + TCFCR0_TCF_NOP); + + tcsr = readl(base + MVPP22_TAI_TCSR); + if (tcsr & TCSR_CAPTURE_1_VALID) { + mvpp22_tai_read_ts(ts, base + MVPP22_TAI_TCV1_SEC_HIGH); + ret = 0; + } else if (tcsr & TCSR_CAPTURE_0_VALID) { + mvpp22_tai_read_ts(ts, base + MVPP22_TAI_TCV0_SEC_HIGH); + ret = 0; + } else { + /* We don't seem to have a reading... */ + ret = -EBUSY; + } + spin_unlock_irqrestore(&tai->lock, flags); + + return ret; +} + +static int mvpp22_tai_settime64(struct ptp_clock_info *ptp, + const struct timespec64 *ts) +{ + struct mvpp2_tai *tai = ptp_to_tai(ptp); + unsigned long flags; + void __iomem *base; + + base = tai->base; + spin_lock_irqsave(&tai->lock, flags); + mvpp2_tai_write_tlv(ts, 0, base); + + /* Trigger an update to load the value from the TLV registers + * into the TOD counter. Note that an external unmaskable event on + * PTP_EVENT_REQ will also trigger this action. + */ + mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, + TCFCR0_PHASE_UPDATE_ENABLE | + TCFCR0_TCF_MASK | TCFCR0_TCF_TRIGGER, + TCFCR0_TCF_UPDATE | TCFCR0_TCF_TRIGGER); + mvpp2_tai_modify(base + MVPP22_TAI_TCFCR0, TCFCR0_TCF_MASK, + TCFCR0_TCF_NOP); + spin_unlock_irqrestore(&tai->lock, flags); + + return 0; +} + +static long mvpp22_tai_aux_work(struct ptp_clock_info *ptp) +{ + struct mvpp2_tai *tai = ptp_to_tai(ptp); + + mvpp22_tai_gettimex64(ptp, &tai->stamp, NULL); + + return msecs_to_jiffies(2000); +} + +static void mvpp22_tai_set_step(struct mvpp2_tai *tai) +{ + void __iomem *base = tai->base; + u32 nano, frac; + + nano = upper_32_bits(tai->period); + frac = lower_32_bits(tai->period); + + /* As the fractional nanosecond is a signed offset, if the MSB (sign) + * bit is set, we have to increment the whole nanoseconds. + */ + if (frac >= 0x80000000) + nano += 1; + + mvpp2_tai_write(nano, base + MVPP22_TAI_TOD_STEP_NANO_CR); + mvpp2_tai_write(frac >> 16, base + MVPP22_TAI_TOD_STEP_FRAC_HIGH); + mvpp2_tai_write(frac, base + MVPP22_TAI_TOD_STEP_FRAC_LOW); +} + +static void mvpp22_tai_init(struct mvpp2_tai *tai) +{ + void __iomem *base = tai->base; + + mvpp22_tai_set_step(tai); + + /* Release the TAI reset */ + mvpp2_tai_modify(base + MVPP22_TAI_CR0, CR0_SW_NRESET, CR0_SW_NRESET); +} + +int mvpp22_tai_ptp_clock_index(struct mvpp2_tai *tai) +{ + return ptp_clock_index(tai->ptp_clock); +} + +void mvpp22_tai_tstamp(struct mvpp2_tai *tai, u32 tstamp, + struct skb_shared_hwtstamps *hwtstamp) +{ + struct timespec64 ts; + int delta; + + /* The tstamp consists of 2 bits of seconds and 30 bits of nanoseconds. + * We use our stored timestamp (tai->stamp) to form a full timestamp, + * and we must read the seconds exactly once. + */ + ts.tv_sec = READ_ONCE(tai->stamp.tv_sec); + ts.tv_nsec = tstamp & 0x3fffffff; + + /* Calculate the delta in seconds between our stored timestamp and + * the value read from the queue. Allow timestamps one second in the + * past, otherwise consider them to be in the future. + */ + delta = ((tstamp >> 30) - (ts.tv_sec & 3)) & 3; + if (delta == 3) + delta -= 4; + ts.tv_sec += delta; + + memset(hwtstamp, 0, sizeof(*hwtstamp)); + hwtstamp->hwtstamp = timespec64_to_ktime(ts); +} + +void mvpp22_tai_start(struct mvpp2_tai *tai) +{ + long delay; + + delay = mvpp22_tai_aux_work(&tai->caps); + + ptp_schedule_worker(tai->ptp_clock, delay); +} + +void mvpp22_tai_stop(struct mvpp2_tai *tai) +{ + ptp_cancel_worker_sync(tai->ptp_clock); +} + +static void mvpp22_tai_remove(void *priv) +{ + struct mvpp2_tai *tai = priv; + + if (!IS_ERR(tai->ptp_clock)) + ptp_clock_unregister(tai->ptp_clock); +} + +int mvpp22_tai_probe(struct device *dev, struct mvpp2 *priv) +{ + struct mvpp2_tai *tai; + int ret; + + tai = devm_kzalloc(dev, sizeof(*tai), GFP_KERNEL); + if (!tai) + return -ENOMEM; + + spin_lock_init(&tai->lock); + + tai->base = priv->iface_base; + + /* The step size consists of three registers - a 16-bit nanosecond step + * size, and a 32-bit fractional nanosecond step size split over two + * registers. The fractional nanosecond step size has units of 2^-32ns. + * + * To calculate this, we calculate: + * (10^9 + freq / 2) / (freq * 2^-32) + * which gives us the nanosecond step to the nearest integer in 16.32 + * fixed point format, and the fractional part of the step size with + * the MSB inverted. With rounding of the fractional nanosecond, and + * simplification, this becomes: + * (10^9 << 32 + freq << 31 + (freq + 1) >> 1) / freq + * + * So: + * div = (10^9 << 32 + freq << 31 + (freq + 1) >> 1) / freq + * nano = upper_32_bits(div); + * frac = lower_32_bits(div) ^ 0x80000000; + * Will give the values for the registers. + * + * This is all seems perfect, but alas it is not when considering the + * whole story. The system is clocked from 25MHz, which is multiplied + * by a PLL to 1GHz, and then divided by three, giving 333333333Hz + * (recurring). This gives exactly 3ns, but using 333333333Hz with + * the above gives an error of 13*2^-32ns. + * + * Consequently, we use the period rather than calculating from the + * frequency. + */ + tai->period = 3ULL << 32; + + mvpp22_tai_init(tai); + + tai->caps.owner = THIS_MODULE; + strscpy(tai->caps.name, "Marvell PP2.2", sizeof(tai->caps.name)); + tai->caps.max_adj = mvpp22_calc_max_adj(tai); + tai->caps.adjfine = mvpp22_tai_adjfine; + tai->caps.adjtime = mvpp22_tai_adjtime; + tai->caps.gettimex64 = mvpp22_tai_gettimex64; + tai->caps.settime64 = mvpp22_tai_settime64; + tai->caps.do_aux_work = mvpp22_tai_aux_work; + + ret = devm_add_action(dev, mvpp22_tai_remove, tai); + if (ret) + return ret; + + tai->ptp_clock = ptp_clock_register(&tai->caps, dev); + if (IS_ERR(tai->ptp_clock)) + return PTR_ERR(tai->ptp_clock); + + priv->tai = tai; + + return 0; +} |