mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2025-01-15 22:36:34 +07:00
29dd908d35
Currently when the driver unloads and PTP is enabled, the delayed work
that prevents the timecounter from expiring becomes a ticking time bomb.
The kernel will schedule the work thread within 60 seconds of driver
removal, but the work handler is no longer there, leading to this
strange and inconclusive stack trace:
[ 64.473112] Unable to handle kernel paging request at virtual address 79746970
[ 64.480340] pgd = 008c4af9
[ 64.483042] [79746970] *pgd=00000000
[ 64.486620] Internal error: Oops: 80000005 [#1] SMP ARM
[ 64.491820] Modules linked in:
[ 64.494871] CPU: 0 PID: 0 Comm: swapper/0 Not tainted 5.2.0-rc5-01634-ge3a2773ba9e5 #1246
[ 64.503007] Hardware name: Freescale LS1021A
[ 64.507259] PC is at 0x79746970
[ 64.510393] LR is at call_timer_fn+0x3c/0x18c
[ 64.514729] pc : [<79746970>] lr : [<c03bd734>] psr: 60010113
[ 64.520965] sp : c1901de0 ip : 00000000 fp : c1903080
[ 64.526163] r10: c1901e38 r9 : ffffe000 r8 : c19064ac
[ 64.531363] r7 : 79746972 r6 : e98dd260 r5 : 00000100 r4 : c1a9e4a0
[ 64.537859] r3 : c1900000 r2 : ffffa400 r1 : 79746972 r0 : e98dd260
[ 64.544359] Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment none
[ 64.551460] Control: 10c5387d Table: a8a2806a DAC: 00000051
[ 64.557176] Process swapper/0 (pid: 0, stack limit = 0x1ddb27f0)
[ 64.563147] Stack: (0xc1901de0 to 0xc1902000)
[ 64.567481] 1de0: eb6a4918 3d60d7c3 c1a9e554 e98dd260 eb6a34c0 c1a9e4a0 ffffa400 c19064ac
[ 64.575616] 1e00: ffffe000 c03bd95c c1901e34 c1901e34 eb6a34c0 c1901e30 c1903d00 c186f4c0
[ 64.583751] 1e20: c1906488 29e34000 c1903080 c03bdca4 00000000 eaa6f218 00000000 eb6a45c0
[ 64.591886] 1e40: eb6a45c0 20010193 00000003 c03c0a68 20010193 3f7231be c1903084 00000002
[ 64.600022] 1e60: 00000082 00000001 ffffe000 c1a9e0a4 00000100 c0302298 02b64722 0000000f
[ 64.608157] 1e80: c186b3c8 c1877540 c19064ac 0000000a c186b350 ffffa401 c1903d00 c1107348
[ 64.616292] 1ea0: 00200102 c0d87a14 ea823c00 ffffe000 00000012 00000000 00000000 ea810800
[ 64.624427] 1ec0: f0803000 c1876ba8 00000000 c034c784 c18774b8 c039fb50 c1906c90 c1978aac
[ 64.632562] 1ee0: f080200c f0802000 c1901f10 c0709ca8 c03091a0 60010013 ffffffff c1901f44
[ 64.640697] 1f00: 00000000 c1900000 c1876ba8 c0301a8c 00000000 000070a0 eb6ac1a0 c031da60
[ 64.648832] 1f20: ffffe000 c19064ac c19064f0 00000001 00000000 c1906488 c1876ba8 00000000
[ 64.656967] 1f40: ffffffff c1901f60 c030919c c03091a0 60010013 ffffffff 00000051 00000000
[ 64.665102] 1f60: ffffe000 c0376aa4 c1a9da37 ffffffff 00000037 3f7231be c1ab20c0 000000cc
[ 64.673238] 1f80: c1906488 c1906480 ffffffff 00000037 c1ab20c0 c1ab20c0 00000001 c0376e1c
[ 64.681373] 1fa0: c1ab2118 c1700ea8 ffffffff ffffffff 00000000 c1700754 c17dfa40 ebfffd80
[ 64.689509] 1fc0: 00000000 c17dfa40 3f7733be 00000000 00000000 c1700330 00000051 10c0387d
[ 64.697644] 1fe0: 00000000 8f000000 410fc075 10c5387d 00000000 00000000 00000000 00000000
[ 64.705788] [<c03bd734>] (call_timer_fn) from [<c03bd95c>] (expire_timers+0xd8/0x144)
[ 64.713579] [<c03bd95c>] (expire_timers) from [<c03bdca4>] (run_timer_softirq+0xe4/0x1dc)
[ 64.721716] [<c03bdca4>] (run_timer_softirq) from [<c0302298>] (__do_softirq+0x130/0x3c8)
[ 64.729854] [<c0302298>] (__do_softirq) from [<c034c784>] (irq_exit+0xbc/0xd8)
[ 64.737040] [<c034c784>] (irq_exit) from [<c039fb50>] (__handle_domain_irq+0x60/0xb4)
[ 64.744833] [<c039fb50>] (__handle_domain_irq) from [<c0709ca8>] (gic_handle_irq+0x58/0x9c)
[ 64.753143] [<c0709ca8>] (gic_handle_irq) from [<c0301a8c>] (__irq_svc+0x6c/0x90)
[ 64.760583] Exception stack(0xc1901f10 to 0xc1901f58)
[ 64.765605] 1f00: 00000000 000070a0 eb6ac1a0 c031da60
[ 64.773740] 1f20: ffffe000 c19064ac c19064f0 00000001 00000000 c1906488 c1876ba8 00000000
[ 64.781873] 1f40: ffffffff c1901f60 c030919c c03091a0 60010013 ffffffff
[ 64.788456] [<c0301a8c>] (__irq_svc) from [<c03091a0>] (arch_cpu_idle+0x38/0x3c)
[ 64.795816] [<c03091a0>] (arch_cpu_idle) from [<c0376aa4>] (do_idle+0x1bc/0x298)
[ 64.803175] [<c0376aa4>] (do_idle) from [<c0376e1c>] (cpu_startup_entry+0x18/0x1c)
[ 64.810707] [<c0376e1c>] (cpu_startup_entry) from [<c1700ea8>] (start_kernel+0x480/0x4ac)
[ 64.818839] Code: bad PC value
[ 64.821890] ---[ end trace e226ed97b1c584cd ]---
[ 64.826482] Kernel panic - not syncing: Fatal exception in interrupt
[ 64.832807] CPU1: stopping
[ 64.835501] CPU: 1 PID: 0 Comm: swapper/1 Tainted: G D 5.2.0-rc5-01634-ge3a2773ba9e5 #1246
[ 64.845013] Hardware name: Freescale LS1021A
[ 64.849266] [<c0312394>] (unwind_backtrace) from [<c030cc74>] (show_stack+0x10/0x14)
[ 64.856972] [<c030cc74>] (show_stack) from [<c0ff4138>] (dump_stack+0xb4/0xc8)
[ 64.864159] [<c0ff4138>] (dump_stack) from [<c0310854>] (handle_IPI+0x3bc/0x3dc)
[ 64.871519] [<c0310854>] (handle_IPI) from [<c0709ce8>] (gic_handle_irq+0x98/0x9c)
[ 64.879050] [<c0709ce8>] (gic_handle_irq) from [<c0301a8c>] (__irq_svc+0x6c/0x90)
[ 64.886489] Exception stack(0xea8cbf60 to 0xea8cbfa8)
[ 64.891514] bf60: 00000000 0000307c eb6c11a0 c031da60 ffffe000 c19064ac c19064f0 00000002
[ 64.899649] bf80: 00000000 c1906488 c1876ba8 00000000 00000000 ea8cbfb0 c030919c c03091a0
[ 64.907780] bfa0: 600d0013 ffffffff
[ 64.911250] [<c0301a8c>] (__irq_svc) from [<c03091a0>] (arch_cpu_idle+0x38/0x3c)
[ 64.918609] [<c03091a0>] (arch_cpu_idle) from [<c0376aa4>] (do_idle+0x1bc/0x298)
[ 64.925967] [<c0376aa4>] (do_idle) from [<c0376e1c>] (cpu_startup_entry+0x18/0x1c)
[ 64.933496] [<c0376e1c>] (cpu_startup_entry) from [<803025cc>] (0x803025cc)
[ 64.940422] Rebooting in 3 seconds..
In this case, what happened is that the DSA driver failed to probe at
boot time due to a PHY issue during phylink_connect_phy:
[ 2.245607] fsl-gianfar soc:ethernet@2d90000 eth2: error -19 setting up slave phy
[ 2.258051] sja1105 spi0.1: failed to create slave for port 0.0
Fixes: bb77f36ac2
("net: dsa: sja1105: Add support for the PTP clock")
Signed-off-by: Vladimir Oltean <olteanv@gmail.com>
Acked-by: Willem de Bruijn <willemb@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
394 lines
12 KiB
C
394 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright (c) 2019, Vladimir Oltean <olteanv@gmail.com>
|
|
*/
|
|
#include "sja1105.h"
|
|
|
|
/* The adjfine API clamps ppb between [-32,768,000, 32,768,000], and
|
|
* therefore scaled_ppm between [-2,147,483,648, 2,147,483,647].
|
|
* Set the maximum supported ppb to a round value smaller than the maximum.
|
|
*
|
|
* Percentually speaking, this is a +/- 0.032x adjustment of the
|
|
* free-running counter (0.968x to 1.032x).
|
|
*/
|
|
#define SJA1105_MAX_ADJ_PPB 32000000
|
|
#define SJA1105_SIZE_PTP_CMD 4
|
|
|
|
/* Timestamps are in units of 8 ns clock ticks (equivalent to a fixed
|
|
* 125 MHz clock) so the scale factor (MULT / SHIFT) needs to be 8.
|
|
* Furthermore, wisely pick SHIFT as 28 bits, which translates
|
|
* MULT into 2^31 (0x80000000). This is the same value around which
|
|
* the hardware PTPCLKRATE is centered, so the same ppb conversion
|
|
* arithmetic can be reused.
|
|
*/
|
|
#define SJA1105_CC_SHIFT 28
|
|
#define SJA1105_CC_MULT (8 << SJA1105_CC_SHIFT)
|
|
|
|
/* Having 33 bits of cycle counter left until a 64-bit overflow during delta
|
|
* conversion, we multiply this by the 8 ns counter resolution and arrive at
|
|
* a comfortable 68.71 second refresh interval until the delta would cause
|
|
* an integer overflow, in absence of any other readout.
|
|
* Approximate to 1 minute.
|
|
*/
|
|
#define SJA1105_REFRESH_INTERVAL (HZ * 60)
|
|
|
|
/* This range is actually +/- SJA1105_MAX_ADJ_PPB
|
|
* divided by 1000 (ppb -> ppm) and with a 16-bit
|
|
* "fractional" part (actually fixed point).
|
|
* |
|
|
* v
|
|
* Convert scaled_ppm from the +/- ((10^6) << 16) range
|
|
* into the +/- (1 << 31) range.
|
|
*
|
|
* This forgoes a "ppb" numeric representation (up to NSEC_PER_SEC)
|
|
* and defines the scaling factor between scaled_ppm and the actual
|
|
* frequency adjustments (both cycle counter and hardware).
|
|
*
|
|
* ptpclkrate = scaled_ppm * 2^31 / (10^6 * 2^16)
|
|
* simplifies to
|
|
* ptpclkrate = scaled_ppm * 2^9 / 5^6
|
|
*/
|
|
#define SJA1105_CC_MULT_NUM (1 << 9)
|
|
#define SJA1105_CC_MULT_DEM 15625
|
|
|
|
#define ptp_to_sja1105(d) container_of((d), struct sja1105_private, ptp_caps)
|
|
#define cc_to_sja1105(d) container_of((d), struct sja1105_private, tstamp_cc)
|
|
#define dw_to_sja1105(d) container_of((d), struct sja1105_private, refresh_work)
|
|
|
|
struct sja1105_ptp_cmd {
|
|
u64 resptp; /* reset */
|
|
};
|
|
|
|
int sja1105_get_ts_info(struct dsa_switch *ds, int port,
|
|
struct ethtool_ts_info *info)
|
|
{
|
|
struct sja1105_private *priv = ds->priv;
|
|
|
|
/* Called during cleanup */
|
|
if (!priv->clock)
|
|
return -ENODEV;
|
|
|
|
info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE |
|
|
SOF_TIMESTAMPING_RX_HARDWARE |
|
|
SOF_TIMESTAMPING_RAW_HARDWARE;
|
|
info->tx_types = (1 << HWTSTAMP_TX_OFF) |
|
|
(1 << HWTSTAMP_TX_ON);
|
|
info->rx_filters = (1 << HWTSTAMP_FILTER_NONE) |
|
|
(1 << HWTSTAMP_FILTER_PTP_V2_L2_EVENT);
|
|
info->phc_index = ptp_clock_index(priv->clock);
|
|
return 0;
|
|
}
|
|
|
|
int sja1105et_ptp_cmd(const void *ctx, const void *data)
|
|
{
|
|
const struct sja1105_ptp_cmd *cmd = data;
|
|
const struct sja1105_private *priv = ctx;
|
|
const struct sja1105_regs *regs = priv->info->regs;
|
|
const int size = SJA1105_SIZE_PTP_CMD;
|
|
u8 buf[SJA1105_SIZE_PTP_CMD] = {0};
|
|
/* No need to keep this as part of the structure */
|
|
u64 valid = 1;
|
|
|
|
sja1105_pack(buf, &valid, 31, 31, size);
|
|
sja1105_pack(buf, &cmd->resptp, 2, 2, size);
|
|
|
|
return sja1105_spi_send_packed_buf(priv, SPI_WRITE, regs->ptp_control,
|
|
buf, SJA1105_SIZE_PTP_CMD);
|
|
}
|
|
|
|
int sja1105pqrs_ptp_cmd(const void *ctx, const void *data)
|
|
{
|
|
const struct sja1105_ptp_cmd *cmd = data;
|
|
const struct sja1105_private *priv = ctx;
|
|
const struct sja1105_regs *regs = priv->info->regs;
|
|
const int size = SJA1105_SIZE_PTP_CMD;
|
|
u8 buf[SJA1105_SIZE_PTP_CMD] = {0};
|
|
/* No need to keep this as part of the structure */
|
|
u64 valid = 1;
|
|
|
|
sja1105_pack(buf, &valid, 31, 31, size);
|
|
sja1105_pack(buf, &cmd->resptp, 3, 3, size);
|
|
|
|
return sja1105_spi_send_packed_buf(priv, SPI_WRITE, regs->ptp_control,
|
|
buf, SJA1105_SIZE_PTP_CMD);
|
|
}
|
|
|
|
/* The switch returns partial timestamps (24 bits for SJA1105 E/T, which wrap
|
|
* around in 0.135 seconds, and 32 bits for P/Q/R/S, wrapping around in 34.35
|
|
* seconds).
|
|
*
|
|
* This receives the RX or TX MAC timestamps, provided by hardware as
|
|
* the lower bits of the cycle counter, sampled at the time the timestamp was
|
|
* collected.
|
|
*
|
|
* To reconstruct into a full 64-bit-wide timestamp, the cycle counter is
|
|
* read and the high-order bits are filled in.
|
|
*
|
|
* Must be called within one wraparound period of the partial timestamp since
|
|
* it was generated by the MAC.
|
|
*/
|
|
u64 sja1105_tstamp_reconstruct(struct sja1105_private *priv, u64 now,
|
|
u64 ts_partial)
|
|
{
|
|
u64 partial_tstamp_mask = CYCLECOUNTER_MASK(priv->info->ptp_ts_bits);
|
|
u64 ts_reconstructed;
|
|
|
|
ts_reconstructed = (now & ~partial_tstamp_mask) | ts_partial;
|
|
|
|
/* Check lower bits of current cycle counter against the timestamp.
|
|
* If the current cycle counter is lower than the partial timestamp,
|
|
* then wraparound surely occurred and must be accounted for.
|
|
*/
|
|
if ((now & partial_tstamp_mask) <= ts_partial)
|
|
ts_reconstructed -= (partial_tstamp_mask + 1);
|
|
|
|
return ts_reconstructed;
|
|
}
|
|
|
|
/* Reads the SPI interface for an egress timestamp generated by the switch
|
|
* for frames sent using management routes.
|
|
*
|
|
* SJA1105 E/T layout of the 4-byte SPI payload:
|
|
*
|
|
* 31 23 15 7 0
|
|
* | | | | |
|
|
* +-----+-----+-----+ ^
|
|
* ^ |
|
|
* | |
|
|
* 24-bit timestamp Update bit
|
|
*
|
|
*
|
|
* SJA1105 P/Q/R/S layout of the 8-byte SPI payload:
|
|
*
|
|
* 31 23 15 7 0 63 55 47 39 32
|
|
* | | | | | | | | | |
|
|
* ^ +-----+-----+-----+-----+
|
|
* | ^
|
|
* | |
|
|
* Update bit 32-bit timestamp
|
|
*
|
|
* Notice that the update bit is in the same place.
|
|
* To have common code for E/T and P/Q/R/S for reading the timestamp,
|
|
* we need to juggle with the offset and the bit indices.
|
|
*/
|
|
int sja1105_ptpegr_ts_poll(struct sja1105_private *priv, int port, u64 *ts)
|
|
{
|
|
const struct sja1105_regs *regs = priv->info->regs;
|
|
int tstamp_bit_start, tstamp_bit_end;
|
|
int timeout = 10;
|
|
u8 packed_buf[8];
|
|
u64 update;
|
|
int rc;
|
|
|
|
do {
|
|
rc = sja1105_spi_send_packed_buf(priv, SPI_READ,
|
|
regs->ptpegr_ts[port],
|
|
packed_buf,
|
|
priv->info->ptpegr_ts_bytes);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
sja1105_unpack(packed_buf, &update, 0, 0,
|
|
priv->info->ptpegr_ts_bytes);
|
|
if (update)
|
|
break;
|
|
|
|
usleep_range(10, 50);
|
|
} while (--timeout);
|
|
|
|
if (!timeout)
|
|
return -ETIMEDOUT;
|
|
|
|
/* Point the end bit to the second 32-bit word on P/Q/R/S,
|
|
* no-op on E/T.
|
|
*/
|
|
tstamp_bit_end = (priv->info->ptpegr_ts_bytes - 4) * 8;
|
|
/* Shift the 24-bit timestamp on E/T to be collected from 31:8.
|
|
* No-op on P/Q/R/S.
|
|
*/
|
|
tstamp_bit_end += 32 - priv->info->ptp_ts_bits;
|
|
tstamp_bit_start = tstamp_bit_end + priv->info->ptp_ts_bits - 1;
|
|
|
|
*ts = 0;
|
|
|
|
sja1105_unpack(packed_buf, ts, tstamp_bit_start, tstamp_bit_end,
|
|
priv->info->ptpegr_ts_bytes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sja1105_ptp_reset(struct sja1105_private *priv)
|
|
{
|
|
struct dsa_switch *ds = priv->ds;
|
|
struct sja1105_ptp_cmd cmd = {0};
|
|
int rc;
|
|
|
|
mutex_lock(&priv->ptp_lock);
|
|
|
|
cmd.resptp = 1;
|
|
dev_dbg(ds->dev, "Resetting PTP clock\n");
|
|
rc = priv->info->ptp_cmd(priv, &cmd);
|
|
|
|
timecounter_init(&priv->tstamp_tc, &priv->tstamp_cc,
|
|
ktime_to_ns(ktime_get_real()));
|
|
|
|
mutex_unlock(&priv->ptp_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int sja1105_ptp_gettime(struct ptp_clock_info *ptp,
|
|
struct timespec64 *ts)
|
|
{
|
|
struct sja1105_private *priv = ptp_to_sja1105(ptp);
|
|
u64 ns;
|
|
|
|
mutex_lock(&priv->ptp_lock);
|
|
ns = timecounter_read(&priv->tstamp_tc);
|
|
mutex_unlock(&priv->ptp_lock);
|
|
|
|
*ts = ns_to_timespec64(ns);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sja1105_ptp_settime(struct ptp_clock_info *ptp,
|
|
const struct timespec64 *ts)
|
|
{
|
|
struct sja1105_private *priv = ptp_to_sja1105(ptp);
|
|
u64 ns = timespec64_to_ns(ts);
|
|
|
|
mutex_lock(&priv->ptp_lock);
|
|
timecounter_init(&priv->tstamp_tc, &priv->tstamp_cc, ns);
|
|
mutex_unlock(&priv->ptp_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sja1105_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
|
|
{
|
|
struct sja1105_private *priv = ptp_to_sja1105(ptp);
|
|
s64 clkrate;
|
|
|
|
clkrate = (s64)scaled_ppm * SJA1105_CC_MULT_NUM;
|
|
clkrate = div_s64(clkrate, SJA1105_CC_MULT_DEM);
|
|
|
|
mutex_lock(&priv->ptp_lock);
|
|
|
|
/* Force a readout to update the timer *before* changing its frequency.
|
|
*
|
|
* This way, its corrected time curve can at all times be modeled
|
|
* as a linear "A * x + B" function, where:
|
|
*
|
|
* - B are past frequency adjustments and offset shifts, all
|
|
* accumulated into the cycle_last variable.
|
|
*
|
|
* - A is the new frequency adjustments we're just about to set.
|
|
*
|
|
* Reading now makes B accumulate the correct amount of time,
|
|
* corrected at the old rate, before changing it.
|
|
*
|
|
* Hardware timestamps then become simple points on the curve and
|
|
* are approximated using the above function. This is still better
|
|
* than letting the switch take the timestamps using the hardware
|
|
* rate-corrected clock (PTPCLKVAL) - the comparison in this case would
|
|
* be that we're shifting the ruler at the same time as we're taking
|
|
* measurements with it.
|
|
*
|
|
* The disadvantage is that it's possible to receive timestamps when
|
|
* a frequency adjustment took place in the near past.
|
|
* In this case they will be approximated using the new ppb value
|
|
* instead of a compound function made of two segments (one at the old
|
|
* and the other at the new rate) - introducing some inaccuracy.
|
|
*/
|
|
timecounter_read(&priv->tstamp_tc);
|
|
|
|
priv->tstamp_cc.mult = SJA1105_CC_MULT + clkrate;
|
|
|
|
mutex_unlock(&priv->ptp_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sja1105_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
|
|
{
|
|
struct sja1105_private *priv = ptp_to_sja1105(ptp);
|
|
|
|
mutex_lock(&priv->ptp_lock);
|
|
timecounter_adjtime(&priv->tstamp_tc, delta);
|
|
mutex_unlock(&priv->ptp_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static u64 sja1105_ptptsclk_read(const struct cyclecounter *cc)
|
|
{
|
|
struct sja1105_private *priv = cc_to_sja1105(cc);
|
|
const struct sja1105_regs *regs = priv->info->regs;
|
|
u64 ptptsclk = 0;
|
|
int rc;
|
|
|
|
rc = sja1105_spi_send_int(priv, SPI_READ, regs->ptptsclk,
|
|
&ptptsclk, 8);
|
|
if (rc < 0)
|
|
dev_err_ratelimited(priv->ds->dev,
|
|
"failed to read ptp cycle counter: %d\n",
|
|
rc);
|
|
return ptptsclk;
|
|
}
|
|
|
|
static void sja1105_ptp_overflow_check(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dw = to_delayed_work(work);
|
|
struct sja1105_private *priv = dw_to_sja1105(dw);
|
|
struct timespec64 ts;
|
|
|
|
sja1105_ptp_gettime(&priv->ptp_caps, &ts);
|
|
|
|
schedule_delayed_work(&priv->refresh_work, SJA1105_REFRESH_INTERVAL);
|
|
}
|
|
|
|
static const struct ptp_clock_info sja1105_ptp_caps = {
|
|
.owner = THIS_MODULE,
|
|
.name = "SJA1105 PHC",
|
|
.adjfine = sja1105_ptp_adjfine,
|
|
.adjtime = sja1105_ptp_adjtime,
|
|
.gettime64 = sja1105_ptp_gettime,
|
|
.settime64 = sja1105_ptp_settime,
|
|
.max_adj = SJA1105_MAX_ADJ_PPB,
|
|
};
|
|
|
|
int sja1105_ptp_clock_register(struct sja1105_private *priv)
|
|
{
|
|
struct dsa_switch *ds = priv->ds;
|
|
|
|
/* Set up the cycle counter */
|
|
priv->tstamp_cc = (struct cyclecounter) {
|
|
.read = sja1105_ptptsclk_read,
|
|
.mask = CYCLECOUNTER_MASK(64),
|
|
.shift = SJA1105_CC_SHIFT,
|
|
.mult = SJA1105_CC_MULT,
|
|
};
|
|
mutex_init(&priv->ptp_lock);
|
|
INIT_DELAYED_WORK(&priv->refresh_work, sja1105_ptp_overflow_check);
|
|
|
|
schedule_delayed_work(&priv->refresh_work, SJA1105_REFRESH_INTERVAL);
|
|
|
|
priv->ptp_caps = sja1105_ptp_caps;
|
|
|
|
priv->clock = ptp_clock_register(&priv->ptp_caps, ds->dev);
|
|
if (IS_ERR_OR_NULL(priv->clock))
|
|
return PTR_ERR(priv->clock);
|
|
|
|
return sja1105_ptp_reset(priv);
|
|
}
|
|
|
|
void sja1105_ptp_clock_unregister(struct sja1105_private *priv)
|
|
{
|
|
if (IS_ERR_OR_NULL(priv->clock))
|
|
return;
|
|
|
|
cancel_delayed_work_sync(&priv->refresh_work);
|
|
ptp_clock_unregister(priv->clock);
|
|
priv->clock = NULL;
|
|
}
|