]> www.infradead.org Git - users/hch/misc.git/commitdiff
net: ngbe: Add support for 1PPS and TOD
authorJiawen Wu <jiawenwu@trustnetic.com>
Tue, 18 Feb 2025 02:34:32 +0000 (10:34 +0800)
committerJakub Kicinski <kuba@kernel.org>
Thu, 20 Feb 2025 22:59:37 +0000 (14:59 -0800)
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>
drivers/net/ethernet/wangxun/libwx/wx_hw.c
drivers/net/ethernet/wangxun/libwx/wx_hw.h
drivers/net/ethernet/wangxun/libwx/wx_ptp.c
drivers/net/ethernet/wangxun/libwx/wx_ptp.h
drivers/net/ethernet/wangxun/libwx/wx_type.h
drivers/net/ethernet/wangxun/ngbe/ngbe_main.c
drivers/net/ethernet/wangxun/ngbe/ngbe_type.h

index deaf670c160ebf41096dcdf26bc154b640969b51..907d13ade40484286deff678f4ac8a70490fb1af 100644 (file)
@@ -393,6 +393,25 @@ rel_out:
 }
 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.
index 11fb33349482490b8de9e34ab506c5c4061e7fc6..b883342bb57677f1679efcb9fc3bc4ae1a011cdc 100644 (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);
index 76986e41afe00647c35febfaec8228a51c42f5f1..07c015ba338f4f2b9e0ffc86939a6268cebe555d 100644 (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);
index 8742d27973636705854567a832dd0f16c1491d7e..50db90a6e3ee667688dc29d0902317d89de89443 100644 (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);
index 0fabfa90d4e71087b8d2ad895ddb1e478e60b48f..db446e690dc7e87ac78d96a12ace70299fcc31db 100644 (file)
 #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;
index c60a96cc350860d2fa8a08997ba7675c7a76981b..a6159214ec0a989fea701079f281ea8dd8ff023e 100644 (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))
index f48ed7fc1805ab0cd89e4bcbafd9ef3406668f0f..992adbb98c7d127b71137b2b69fe412ffdc8d0f6 100644 (file)
 
 /* 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)