Commit 4a056990 authored by David S. Miller's avatar David S. Miller

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: default avatarDavid S. Miller <davem@davemloft.net>
parents b599a5b9 f5015a59
......@@ -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
......
......@@ -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
......@@ -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
......@@ -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 @@ static int mvpp2_port_probe(struct platform_device *pdev,
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);
......
// 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;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment