diff mbox series

[2/2] net: fec: Reload PTP registers after link-state change

Message ID 20240916141931.742734-2-csokas.bence@prolan.hu (mailing list archive)
State Superseded
Headers show
Series [1/2] net: fec: Restart PPS after link state change | expand

Commit Message

Csókás Bence Sept. 16, 2024, 2:19 p.m. UTC
On link-state change, the controller gets reset,
which clears all PTP registers, including PHC time,
calibrated clock correction values etc. For correct
IEEE 1588 operation we need to restore these after
the reset.

Signed-off-by: Csókás, Bence <csokas.bence@prolan.hu>
---
 drivers/net/ethernet/freescale/fec.h      |  7 +++++
 drivers/net/ethernet/freescale/fec_main.c |  4 +++
 drivers/net/ethernet/freescale/fec_ptp.c  | 35 +++++++++++++++++++++++
 3 files changed, 46 insertions(+)

Comments

Frank Li Sept. 16, 2024, 3:05 p.m. UTC | #1
On Mon, Sep 16, 2024 at 04:19:31PM +0200, Csókás, Bence wrote:
> On link-state change, the controller gets reset,
> which clears all PTP registers, including PHC time,
> calibrated clock correction values etc. For correct
> IEEE 1588 operation we need to restore these after
> the reset.

I am not sure if it necessary. timer will be big offset after reset. ptpd
should set_time then do clock frequency adjust, supposed just few ms, ptp
time will get resync.

of course, restore these value may reduce the resync time.

Frank

>
> Signed-off-by: Csókás, Bence <csokas.bence@prolan.hu>
> ---
>  drivers/net/ethernet/freescale/fec.h      |  7 +++++
>  drivers/net/ethernet/freescale/fec_main.c |  4 +++
>  drivers/net/ethernet/freescale/fec_ptp.c  | 35 +++++++++++++++++++++++
>  3 files changed, 46 insertions(+)
>
> diff --git a/drivers/net/ethernet/freescale/fec.h b/drivers/net/ethernet/freescale/fec.h
> index afa0bfb974e6..efe770fe337d 100644
> --- a/drivers/net/ethernet/freescale/fec.h
> +++ b/drivers/net/ethernet/freescale/fec.h
> @@ -691,11 +691,18 @@ struct fec_enet_private {
>  	/* XDP BPF Program */
>  	struct bpf_prog *xdp_prog;
>
> +	struct {
> +		u64 ns_sys, ns_phc;
> +		u32 at_corr;
> +		u8 at_inc_corr;
> +	} ptp_saved_state;
> +
>  	u64 ethtool_stats[];
>  };
>
>  void fec_ptp_init(struct platform_device *pdev, int irq_idx);
>  void fec_ptp_restore_state(struct fec_enet_private *fep);
> +void fec_ptp_save_state(struct fec_enet_private *fep);
>  void fec_ptp_stop(struct platform_device *pdev);
>  void fec_ptp_start_cyclecounter(struct net_device *ndev);
>  int fec_ptp_set(struct net_device *ndev, struct kernel_hwtstamp_config *config,
> diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c
> index 531b51091e7d..570f8a14d975 100644
> --- a/drivers/net/ethernet/freescale/fec_main.c
> +++ b/drivers/net/ethernet/freescale/fec_main.c
> @@ -1077,6 +1077,8 @@ fec_restart(struct net_device *ndev)
>  	u32 rcntl = OPT_FRAME_SIZE | 0x04;
>  	u32 ecntl = FEC_ECR_ETHEREN;
>
> +	fec_ptp_save_state(fep);
> +
>  	/* Whack a reset.  We should wait for this.
>  	 * For i.MX6SX SOC, enet use AXI bus, we use disable MAC
>  	 * instead of reset MAC itself.
> @@ -1338,6 +1340,8 @@ fec_stop(struct net_device *ndev)
>  			netdev_err(ndev, "Graceful transmit stop did not complete!\n");
>  	}
>
> +	fec_ptp_save_state(fep);
> +
>  	/* Whack a reset.  We should wait for this.
>  	 * For i.MX6SX SOC, enet use AXI bus, we use disable MAC
>  	 * instead of reset MAC itself.
> diff --git a/drivers/net/ethernet/freescale/fec_ptp.c b/drivers/net/ethernet/freescale/fec_ptp.c
> index c5b89352373a..8011a6f3c4be 100644
> --- a/drivers/net/ethernet/freescale/fec_ptp.c
> +++ b/drivers/net/ethernet/freescale/fec_ptp.c
> @@ -770,9 +770,44 @@ void fec_ptp_init(struct platform_device *pdev, int irq_idx)
>  	schedule_delayed_work(&fep->time_keep, HZ);
>  }
>
> +void fec_ptp_save_state(struct fec_enet_private *fep)
> +{
> +	unsigned long flags;
> +	u32 atime_inc_corr;
> +
> +	spin_lock_irqsave(&fep->tmreg_lock, flags);
> +
> +	fep->ptp_saved_state.ns_phc = timecounter_read(&fep->tc);
> +	fep->ptp_saved_state.ns_sys = ktime_get_ns();
> +
> +	fep->ptp_saved_state.at_corr = readl(fep->hwp + FEC_ATIME_CORR);
> +	atime_inc_corr = readl(fep->hwp + FEC_ATIME_INC) & FEC_T_INC_CORR_MASK;
> +	fep->ptp_saved_state.at_inc_corr = (u8)(atime_inc_corr >> FEC_T_INC_CORR_OFFSET);
> +
> +	spin_unlock_irqrestore(&fep->tmreg_lock, flags);
> +}
> +
>  /* Restore PTP functionality after a reset */
>  void fec_ptp_restore_state(struct fec_enet_private *fep)
>  {
> +	u32 atime_inc = readl(fep->hwp + FEC_ATIME_INC) & FEC_T_INC_MASK;
> +	unsigned long flags;
> +	u32 counter;
> +	u64 ns;
> +
> +	spin_lock_irqsave(&fep->tmreg_lock, flags);
> +
> +	writel(fep->ptp_saved_state.at_corr, fep->hwp + FEC_ATIME_CORR);
> +	atime_inc |= ((u32)fep->ptp_saved_state.at_inc_corr) << FEC_T_INC_CORR_OFFSET;
> +	writel(atime_inc, fep->hwp + FEC_ATIME_INC);
> +
> +	ns = ktime_get_ns() - fep->ptp_saved_state.ns_sys + fep->ptp_saved_state.ns_phc;
> +	counter = ns & fep->cc.mask;
> +	writel(counter, fep->hwp + FEC_ATIME);
> +	timecounter_init(&fep->tc, &fep->cc, ns);
> +
> +	spin_unlock_irqrestore(&fep->tmreg_lock, flags);
> +
>  	/* Restart PPS if needed */
>  	if (fep->pps_enable) {
>  		/* Reset turned it off, so adjust our status flag */
> --
> 2.34.1
>
>
Csókás Bence Sept. 17, 2024, 7:53 a.m. UTC | #2
Hi!

On 9/16/24 17:05, Frank Li wrote:
> On Mon, Sep 16, 2024 at 04:19:31PM +0200, Csókás, Bence wrote:
>> On link-state change, the controller gets reset,
>> which clears all PTP registers, including PHC time,
>> calibrated clock correction values etc. For correct
>> IEEE 1588 operation we need to restore these after
>> the reset.
> 
> I am not sure if it necessary. timer will be big offset after reset. ptpd
> should set_time then do clock frequency adjust, supposed just few ms, ptp
> time will get resync.
> 
> of course, restore these value may reduce the resync time.
> 
> Frank

There's 3 problems with that:
1. ATCORR, ATINC and ATPER will not be restored, therefore precision 
will be immediately lost.
2. ptpd does NOT set the time, only once, on startup. Currently, on 
link-down, ptpd tries to correct for the missing 54 years by making the 
PHC tick 3% faster (therefore the PPS signal will have a frequency error 
as well), which will never get it there. One work-around is to 
periodically re-start ptpd, but this is obviously sub-optimal.
3. If the PTP server goes away, there's no way to restore the time. 
Whereas if you save and reload it, you can continue, although with 
degraded precision.

Bence
Frank Li Sept. 17, 2024, 3:23 p.m. UTC | #3
On Tue, Sep 17, 2024 at 09:53:07AM +0200, Csókás Bence wrote:
> Hi!
>
> On 9/16/24 17:05, Frank Li wrote:
> > On Mon, Sep 16, 2024 at 04:19:31PM +0200, Csókás, Bence wrote:
> > > On link-state change, the controller gets reset,
> > > which clears all PTP registers, including PHC time,
> > > calibrated clock correction values etc. For correct
> > > IEEE 1588 operation we need to restore these after
> > > the reset.
> >
> > I am not sure if it necessary. timer will be big offset after reset. ptpd
> > should set_time then do clock frequency adjust, supposed just few ms, ptp
> > time will get resync.
> >
> > of course, restore these value may reduce the resync time.
> >
> > Frank
>
> There's 3 problems with that:
> 1. ATCORR, ATINC and ATPER will not be restored, therefore precision will be
> immediately lost.

Yes, It will be good if you have compare time back to sync with/without
save and restore.

I think ptpd always to make it sync again, just take little bit longer.

> 2. ptpd does NOT set the time, only once, on startup. Currently, on
> link-down, ptpd tries to correct for the missing 54 years by making the PHC
> tick 3% faster (therefore the PPS signal will have a frequency error as
> well), which will never get it there. One work-around is to periodically
> re-start ptpd, but this is obviously sub-optimal.

Supposed it should be ptpd bug. I remember time draft is bigger enough, it
should be set the time again. Maybe ptp change the policy.

For example, if system suspend 1min and resume back, absolute offset will
be 1min offset. ptpd should set the time to catch up offset firstly, then
resync frequency.

> 3. If the PTP server goes away, there's no way to restore the time. Whereas
> if you save and reload it, you can continue, although with degraded
> precision.

This one make sense, you can add to commit message.

>
> Bence
>
diff mbox series

Patch

diff --git a/drivers/net/ethernet/freescale/fec.h b/drivers/net/ethernet/freescale/fec.h
index afa0bfb974e6..efe770fe337d 100644
--- a/drivers/net/ethernet/freescale/fec.h
+++ b/drivers/net/ethernet/freescale/fec.h
@@ -691,11 +691,18 @@  struct fec_enet_private {
 	/* XDP BPF Program */
 	struct bpf_prog *xdp_prog;
 
+	struct {
+		u64 ns_sys, ns_phc;
+		u32 at_corr;
+		u8 at_inc_corr;
+	} ptp_saved_state;
+
 	u64 ethtool_stats[];
 };
 
 void fec_ptp_init(struct platform_device *pdev, int irq_idx);
 void fec_ptp_restore_state(struct fec_enet_private *fep);
+void fec_ptp_save_state(struct fec_enet_private *fep);
 void fec_ptp_stop(struct platform_device *pdev);
 void fec_ptp_start_cyclecounter(struct net_device *ndev);
 int fec_ptp_set(struct net_device *ndev, struct kernel_hwtstamp_config *config,
diff --git a/drivers/net/ethernet/freescale/fec_main.c b/drivers/net/ethernet/freescale/fec_main.c
index 531b51091e7d..570f8a14d975 100644
--- a/drivers/net/ethernet/freescale/fec_main.c
+++ b/drivers/net/ethernet/freescale/fec_main.c
@@ -1077,6 +1077,8 @@  fec_restart(struct net_device *ndev)
 	u32 rcntl = OPT_FRAME_SIZE | 0x04;
 	u32 ecntl = FEC_ECR_ETHEREN;
 
+	fec_ptp_save_state(fep);
+
 	/* Whack a reset.  We should wait for this.
 	 * For i.MX6SX SOC, enet use AXI bus, we use disable MAC
 	 * instead of reset MAC itself.
@@ -1338,6 +1340,8 @@  fec_stop(struct net_device *ndev)
 			netdev_err(ndev, "Graceful transmit stop did not complete!\n");
 	}
 
+	fec_ptp_save_state(fep);
+
 	/* Whack a reset.  We should wait for this.
 	 * For i.MX6SX SOC, enet use AXI bus, we use disable MAC
 	 * instead of reset MAC itself.
diff --git a/drivers/net/ethernet/freescale/fec_ptp.c b/drivers/net/ethernet/freescale/fec_ptp.c
index c5b89352373a..8011a6f3c4be 100644
--- a/drivers/net/ethernet/freescale/fec_ptp.c
+++ b/drivers/net/ethernet/freescale/fec_ptp.c
@@ -770,9 +770,44 @@  void fec_ptp_init(struct platform_device *pdev, int irq_idx)
 	schedule_delayed_work(&fep->time_keep, HZ);
 }
 
+void fec_ptp_save_state(struct fec_enet_private *fep)
+{
+	unsigned long flags;
+	u32 atime_inc_corr;
+
+	spin_lock_irqsave(&fep->tmreg_lock, flags);
+
+	fep->ptp_saved_state.ns_phc = timecounter_read(&fep->tc);
+	fep->ptp_saved_state.ns_sys = ktime_get_ns();
+
+	fep->ptp_saved_state.at_corr = readl(fep->hwp + FEC_ATIME_CORR);
+	atime_inc_corr = readl(fep->hwp + FEC_ATIME_INC) & FEC_T_INC_CORR_MASK;
+	fep->ptp_saved_state.at_inc_corr = (u8)(atime_inc_corr >> FEC_T_INC_CORR_OFFSET);
+
+	spin_unlock_irqrestore(&fep->tmreg_lock, flags);
+}
+
 /* Restore PTP functionality after a reset */
 void fec_ptp_restore_state(struct fec_enet_private *fep)
 {
+	u32 atime_inc = readl(fep->hwp + FEC_ATIME_INC) & FEC_T_INC_MASK;
+	unsigned long flags;
+	u32 counter;
+	u64 ns;
+
+	spin_lock_irqsave(&fep->tmreg_lock, flags);
+
+	writel(fep->ptp_saved_state.at_corr, fep->hwp + FEC_ATIME_CORR);
+	atime_inc |= ((u32)fep->ptp_saved_state.at_inc_corr) << FEC_T_INC_CORR_OFFSET;
+	writel(atime_inc, fep->hwp + FEC_ATIME_INC);
+
+	ns = ktime_get_ns() - fep->ptp_saved_state.ns_sys + fep->ptp_saved_state.ns_phc;
+	counter = ns & fep->cc.mask;
+	writel(counter, fep->hwp + FEC_ATIME);
+	timecounter_init(&fep->tc, &fep->cc, ns);
+
+	spin_unlock_irqrestore(&fep->tmreg_lock, flags);
+
 	/* Restart PPS if needed */
 	if (fep->pps_enable) {
 		/* Reset turned it off, so adjust our status flag */