net: ngbe: Add support for 1PPS and TOD

Implement support for generating a 1pps output signal on SDP0.
And support custom firmware to output TOD.

Signed-off-by: Jiawen Wu <jiawenwu@trustnetic.com>
Link: https://patch.msgid.link/20250218023432.146536-5-jiawenwu@trustnetic.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jiawen Wu
2025-02-18 10:34:32 +08:00
committed by Jakub Kicinski
parent 704145a854
commit 2d8967e86c
7 changed files with 250 additions and 4 deletions

View File

@@ -393,6 +393,25 @@ int wx_host_interface_command(struct wx *wx, u32 *buffer,
}
EXPORT_SYMBOL(wx_host_interface_command);
int wx_set_pps(struct wx *wx, bool enable, u64 nsec, u64 cycles)
{
struct wx_hic_set_pps pps_cmd;
pps_cmd.hdr.cmd = FW_PPS_SET_CMD;
pps_cmd.hdr.buf_len = FW_PPS_SET_LEN;
pps_cmd.hdr.cmd_or_resp.cmd_resv = FW_CEM_CMD_RESERVED;
pps_cmd.lan_id = wx->bus.func;
pps_cmd.enable = (u8)enable;
pps_cmd.nsec = nsec;
pps_cmd.cycles = cycles;
pps_cmd.hdr.checksum = FW_DEFAULT_CHECKSUM;
return wx_host_interface_command(wx, (u32 *)&pps_cmd,
sizeof(pps_cmd),
WX_HI_COMMAND_TIMEOUT,
false);
}
/**
* wx_read_ee_hostif_data - Read EEPROM word using a host interface cmd
* assuming that the semaphore is already obtained.

View File

@@ -18,6 +18,7 @@ void wx_control_hw(struct wx *wx, bool drv);
int wx_mng_present(struct wx *wx);
int wx_host_interface_command(struct wx *wx, u32 *buffer,
u32 length, u32 timeout, bool return_data);
int wx_set_pps(struct wx *wx, bool enable, u64 nsec, u64 cycles);
int wx_read_ee_hostif(struct wx *wx, u16 offset, u16 *data);
int wx_read_ee_hostif_buffer(struct wx *wx,
u16 offset, u16 words, u16 *data);

View File

@@ -88,6 +88,9 @@ static int wx_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
timecounter_adjtime(&wx->hw_tc, delta);
write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
if (wx->ptp_setup_sdp)
wx->ptp_setup_sdp(wx);
return 0;
}
@@ -118,6 +121,9 @@ static int wx_ptp_settime64(struct ptp_clock_info *ptp,
timecounter_init(&wx->hw_tc, &wx->hw_cc, ns);
write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
if (wx->ptp_setup_sdp)
wx->ptp_setup_sdp(wx);
return 0;
}
@@ -324,6 +330,160 @@ static long wx_ptp_do_aux_work(struct ptp_clock_info *ptp)
return ts_done ? 1 : HZ;
}
static u64 wx_ptp_trigger_calc(struct wx *wx)
{
struct cyclecounter *cc = &wx->hw_cc;
unsigned long flags;
u64 ns = 0;
u32 rem;
/* Read the current clock time, and save the cycle counter value */
write_seqlock_irqsave(&wx->hw_tc_lock, flags);
ns = timecounter_read(&wx->hw_tc);
wx->pps_edge_start = wx->hw_tc.cycle_last;
write_sequnlock_irqrestore(&wx->hw_tc_lock, flags);
wx->pps_edge_end = wx->pps_edge_start;
/* Figure out how far past the next second we are */
div_u64_rem(ns, WX_NS_PER_SEC, &rem);
/* Figure out how many nanoseconds to add to round the clock edge up
* to the next full second
*/
rem = (WX_NS_PER_SEC - rem);
/* Adjust the clock edge to align with the next full second. */
wx->pps_edge_start += div_u64(((u64)rem << cc->shift), cc->mult);
wx->pps_edge_end += div_u64(((u64)(rem + wx->pps_width) <<
cc->shift), cc->mult);
return (ns + rem);
}
static int wx_ptp_setup_sdp(struct wx *wx)
{
struct cyclecounter *cc = &wx->hw_cc;
u32 tsauxc;
u64 nsec;
if (wx->pps_width >= WX_NS_PER_SEC) {
wx_err(wx, "PTP pps width cannot be longer than 1s!\n");
return -EINVAL;
}
/* disable the pin first */
wr32ptp(wx, WX_TSC_1588_AUX_CTL, 0);
WX_WRITE_FLUSH(wx);
if (!test_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags)) {
if (wx->pps_enabled) {
wx->pps_enabled = false;
wx_set_pps(wx, false, 0, 0);
}
return 0;
}
wx->pps_enabled = true;
nsec = wx_ptp_trigger_calc(wx);
wx_set_pps(wx, wx->pps_enabled, nsec, wx->pps_edge_start);
tsauxc = WX_TSC_1588_AUX_CTL_PLSG | WX_TSC_1588_AUX_CTL_EN_TT0 |
WX_TSC_1588_AUX_CTL_EN_TT1 | WX_TSC_1588_AUX_CTL_EN_TS0;
wr32ptp(wx, WX_TSC_1588_TRGT_L(0), (u32)wx->pps_edge_start);
wr32ptp(wx, WX_TSC_1588_TRGT_H(0), (u32)(wx->pps_edge_start >> 32));
wr32ptp(wx, WX_TSC_1588_TRGT_L(1), (u32)wx->pps_edge_end);
wr32ptp(wx, WX_TSC_1588_TRGT_H(1), (u32)(wx->pps_edge_end >> 32));
wr32ptp(wx, WX_TSC_1588_SDP(0),
WX_TSC_1588_SDP_FUN_SEL_TT0 | WX_TSC_1588_SDP_OUT_LEVEL_H);
wr32ptp(wx, WX_TSC_1588_SDP(1), WX_TSC_1588_SDP_FUN_SEL_TS0);
wr32ptp(wx, WX_TSC_1588_AUX_CTL, tsauxc);
wr32ptp(wx, WX_TSC_1588_INT_EN, WX_TSC_1588_INT_EN_TT1);
WX_WRITE_FLUSH(wx);
/* Adjust the clock edge to align with the next full second. */
wx->sec_to_cc = div_u64(((u64)WX_NS_PER_SEC << cc->shift), cc->mult);
return 0;
}
static int wx_ptp_feature_enable(struct ptp_clock_info *ptp,
struct ptp_clock_request *rq, int on)
{
struct wx *wx = container_of(ptp, struct wx, ptp_caps);
/**
* When PPS is enabled, unmask the interrupt for the ClockOut
* feature, so that the interrupt handler can send the PPS
* event when the clock SDP triggers. Clear mask when PPS is
* disabled
*/
if (rq->type != PTP_CLK_REQ_PEROUT || !wx->ptp_setup_sdp)
return -EOPNOTSUPP;
/* Reject requests with unsupported flags */
if (rq->perout.flags & ~(PTP_PEROUT_DUTY_CYCLE |
PTP_PEROUT_PHASE))
return -EOPNOTSUPP;
if (rq->perout.phase.sec || rq->perout.phase.nsec) {
wx_err(wx, "Absolute start time not supported.\n");
return -EINVAL;
}
if (rq->perout.period.sec != 1 || rq->perout.period.nsec) {
wx_err(wx, "Only 1pps is supported.\n");
return -EINVAL;
}
if (rq->perout.flags & PTP_PEROUT_DUTY_CYCLE) {
struct timespec64 ts_on;
ts_on.tv_sec = rq->perout.on.sec;
ts_on.tv_nsec = rq->perout.on.nsec;
wx->pps_width = timespec64_to_ns(&ts_on);
} else {
wx->pps_width = 120000000;
}
if (on)
set_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
else
clear_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
return wx->ptp_setup_sdp(wx);
}
void wx_ptp_check_pps_event(struct wx *wx)
{
u32 tsauxc, int_status;
/* this check is necessary in case the interrupt was enabled via some
* alternative means (ex. debug_fs). Better to check here than
* everywhere that calls this function.
*/
if (!wx->ptp_clock)
return;
int_status = rd32ptp(wx, WX_TSC_1588_INT_ST);
if (int_status & WX_TSC_1588_INT_ST_TT1) {
/* disable the pin first */
wr32ptp(wx, WX_TSC_1588_AUX_CTL, 0);
WX_WRITE_FLUSH(wx);
wx_ptp_trigger_calc(wx);
tsauxc = WX_TSC_1588_AUX_CTL_PLSG | WX_TSC_1588_AUX_CTL_EN_TT0 |
WX_TSC_1588_AUX_CTL_EN_TT1 | WX_TSC_1588_AUX_CTL_EN_TS0;
wr32ptp(wx, WX_TSC_1588_TRGT_L(0), (u32)wx->pps_edge_start);
wr32ptp(wx, WX_TSC_1588_TRGT_H(0), (u32)(wx->pps_edge_start >> 32));
wr32ptp(wx, WX_TSC_1588_TRGT_L(1), (u32)wx->pps_edge_end);
wr32ptp(wx, WX_TSC_1588_TRGT_H(1), (u32)(wx->pps_edge_end >> 32));
wr32ptp(wx, WX_TSC_1588_AUX_CTL, tsauxc);
WX_WRITE_FLUSH(wx);
}
}
EXPORT_SYMBOL(wx_ptp_check_pps_event);
static long wx_ptp_create_clock(struct wx *wx)
{
struct net_device *netdev = wx->netdev;
@@ -338,17 +498,22 @@ static long wx_ptp_create_clock(struct wx *wx)
wx->ptp_caps.owner = THIS_MODULE;
wx->ptp_caps.n_alarm = 0;
wx->ptp_caps.n_ext_ts = 0;
wx->ptp_caps.n_per_out = 0;
wx->ptp_caps.pps = 0;
wx->ptp_caps.adjfine = wx_ptp_adjfine;
wx->ptp_caps.adjtime = wx_ptp_adjtime;
wx->ptp_caps.gettimex64 = wx_ptp_gettimex64;
wx->ptp_caps.settime64 = wx_ptp_settime64;
wx->ptp_caps.do_aux_work = wx_ptp_do_aux_work;
if (wx->mac.type == wx_mac_em)
if (wx->mac.type == wx_mac_em) {
wx->ptp_caps.max_adj = 500000000;
else
wx->ptp_caps.n_per_out = 1;
wx->ptp_setup_sdp = wx_ptp_setup_sdp;
wx->ptp_caps.enable = wx_ptp_feature_enable;
} else {
wx->ptp_caps.max_adj = 250000000;
wx->ptp_caps.n_per_out = 0;
wx->ptp_setup_sdp = NULL;
}
wx->ptp_clock = ptp_clock_register(&wx->ptp_caps, &wx->pdev->dev);
if (IS_ERR(wx->ptp_clock)) {
@@ -580,6 +745,12 @@ void wx_ptp_reset(struct wx *wx)
wx->last_overflow_check = jiffies;
ptp_schedule_worker(wx->ptp_clock, HZ);
/* Now that the shift has been calculated and the systime
* registers reset, (re-)enable the Clock out feature
*/
if (wx->ptp_setup_sdp)
wx->ptp_setup_sdp(wx);
}
EXPORT_SYMBOL(wx_ptp_reset);
@@ -620,6 +791,10 @@ void wx_ptp_suspend(struct wx *wx)
if (!test_and_clear_bit(WX_STATE_PTP_RUNNING, wx->state))
return;
clear_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
if (wx->ptp_setup_sdp)
wx->ptp_setup_sdp(wx);
wx_ptp_clear_tx_timestamp(wx);
}
EXPORT_SYMBOL(wx_ptp_suspend);

View File

@@ -4,6 +4,7 @@
#ifndef _WX_PTP_H_
#define _WX_PTP_H_
void wx_ptp_check_pps_event(struct wx *wx);
void wx_ptp_reset_cyclecounter(struct wx *wx);
void wx_ptp_reset(struct wx *wx);
void wx_ptp_init(struct wx *wx);

View File

@@ -281,6 +281,23 @@
#define WX_TSC_1588_SYSTIML 0x11F0C
#define WX_TSC_1588_SYSTIMH 0x11F10
#define WX_TSC_1588_INC 0x11F14
#define WX_TSC_1588_INT_ST 0x11F20
#define WX_TSC_1588_INT_ST_TT1 BIT(5)
#define WX_TSC_1588_INT_EN 0x11F24
#define WX_TSC_1588_INT_EN_TT1 BIT(5)
#define WX_TSC_1588_AUX_CTL 0x11F28
#define WX_TSC_1588_AUX_CTL_EN_TS0 BIT(8)
#define WX_TSC_1588_AUX_CTL_EN_TT1 BIT(2)
#define WX_TSC_1588_AUX_CTL_PLSG BIT(1)
#define WX_TSC_1588_AUX_CTL_EN_TT0 BIT(0)
#define WX_TSC_1588_TRGT_L(i) (0x11F2C + ((i) * 8)) /* [0,1] */
#define WX_TSC_1588_TRGT_H(i) (0x11F30 + ((i) * 8)) /* [0,1] */
#define WX_TSC_1588_SDP(i) (0x11F5C + ((i) * 4)) /* [0,3] */
#define WX_TSC_1588_SDP_OUT_LEVEL_H FIELD_PREP(BIT(4), 0)
#define WX_TSC_1588_SDP_OUT_LEVEL_L FIELD_PREP(BIT(4), 1)
#define WX_TSC_1588_SDP_FUN_SEL_MASK GENMASK(2, 0)
#define WX_TSC_1588_SDP_FUN_SEL_TT0 FIELD_PREP(WX_TSC_1588_SDP_FUN_SEL_MASK, 1)
#define WX_TSC_1588_SDP_FUN_SEL_TS0 FIELD_PREP(WX_TSC_1588_SDP_FUN_SEL_MASK, 5)
/************************************** MNG ********************************/
#define WX_MNG_SWFW_SYNC 0x1E008
@@ -410,6 +427,8 @@ enum WX_MSCA_CMD_value {
#define FW_CEM_CMD_RESERVED 0X0
#define FW_CEM_MAX_RETRIES 3
#define FW_CEM_RESP_STATUS_SUCCESS 0x1
#define FW_PPS_SET_CMD 0xF6
#define FW_PPS_SET_LEN 0x14
#define WX_SW_REGION_PTR 0x1C
@@ -730,6 +749,15 @@ struct wx_hic_reset {
u16 reset_type;
};
struct wx_hic_set_pps {
struct wx_hic_hdr hdr;
u8 lan_id;
u8 enable;
u16 pad2;
u64 nsec;
u64 cycles;
};
/* Bus parameters */
struct wx_bus_info {
u8 func;
@@ -1068,6 +1096,7 @@ enum wx_pf_flags {
WX_FLAG_FDIR_PERFECT,
WX_FLAG_RX_HWTSTAMP_ENABLED,
WX_FLAG_RX_HWTSTAMP_IN_REGISTER,
WX_FLAG_PTP_PPS_ENABLED,
WX_PF_FLAGS_NBITS /* must be last */
};
@@ -1168,7 +1197,13 @@ struct wx {
void (*atr)(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype);
void (*configure_fdir)(struct wx *wx);
void (*do_reset)(struct net_device *netdev);
int (*ptp_setup_sdp)(struct wx *wx);
bool pps_enabled;
u64 pps_width;
u64 pps_edge_start;
u64 pps_edge_end;
u64 sec_to_cc;
u32 base_incval;
u32 tx_hwtstamp_pkts;
u32 tx_hwtstamp_timeouts;

View File

@@ -168,7 +168,7 @@ static irqreturn_t ngbe_intr(int __always_unused irq, void *data)
struct wx_q_vector *q_vector;
struct wx *wx = data;
struct pci_dev *pdev;
u32 eicr;
u32 eicr, eicr_misc;
q_vector = wx->q_vector[0];
pdev = wx->pdev;
@@ -186,6 +186,10 @@ static irqreturn_t ngbe_intr(int __always_unused irq, void *data)
if (!(pdev->msi_enabled))
wr32(wx, WX_PX_INTA, 1);
eicr_misc = wx_misc_isb(wx, WX_ISB_MISC);
if (unlikely(eicr_misc & NGBE_PX_MISC_IC_TIMESYNC))
wx_ptp_check_pps_event(wx);
wx->isb_mem[WX_ISB_MISC] = 0;
/* would disable interrupts here but it is auto disabled */
napi_schedule_irqoff(&q_vector->napi);
@@ -199,6 +203,12 @@ static irqreturn_t ngbe_intr(int __always_unused irq, void *data)
static irqreturn_t ngbe_msix_other(int __always_unused irq, void *data)
{
struct wx *wx = data;
u32 eicr;
eicr = wx_misc_isb(wx, WX_ISB_MISC);
if (unlikely(eicr & NGBE_PX_MISC_IC_TIMESYNC))
wx_ptp_check_pps_event(wx);
/* re-enable the original interrupt state, no lsc, no queues */
if (netif_running(wx->netdev))

View File

@@ -70,15 +70,20 @@
/* Extended Interrupt Enable Set */
#define NGBE_PX_MISC_IEN_DEV_RST BIT(10)
#define NGBE_PX_MISC_IEN_TIMESYNC BIT(11)
#define NGBE_PX_MISC_IEN_ETH_LK BIT(18)
#define NGBE_PX_MISC_IEN_INT_ERR BIT(20)
#define NGBE_PX_MISC_IEN_GPIO BIT(26)
#define NGBE_PX_MISC_IEN_MASK ( \
NGBE_PX_MISC_IEN_DEV_RST | \
NGBE_PX_MISC_IEN_TIMESYNC | \
NGBE_PX_MISC_IEN_ETH_LK | \
NGBE_PX_MISC_IEN_INT_ERR | \
NGBE_PX_MISC_IEN_GPIO)
/* Extended Interrupt Cause Read */
#define NGBE_PX_MISC_IC_TIMESYNC BIT(11) /* time sync */
#define NGBE_INTR_ALL 0x1FF
#define NGBE_INTR_MISC BIT(0)