diff --git a/drivers/net/dsa/sja1105/Kconfig b/drivers/net/dsa/sja1105/Kconfig index 1144fc5f61a8..049cea8240e4 100644 --- a/drivers/net/dsa/sja1105/Kconfig +++ b/drivers/net/dsa/sja1105/Kconfig @@ -16,3 +16,10 @@ tristate "NXP SJA1105 Ethernet switch family support" - SJA1105Q (Gen. 2, No SGMII, TT-Ethernet) - SJA1105R (Gen. 2, SGMII, No TT-Ethernet) - SJA1105S (Gen. 2, SGMII, TT-Ethernet) + +config NET_DSA_SJA1105_PTP +tristate "Support for the PTP clock on the NXP SJA1105 Ethernet switch" + depends on NET_DSA_SJA1105 + help + This enables support for timestamping and PTP clock manipulations in + the SJA1105 DSA driver. diff --git a/drivers/net/dsa/sja1105/Makefile b/drivers/net/dsa/sja1105/Makefile index 941848de8b46..946eea7d8480 100644 --- a/drivers/net/dsa/sja1105/Makefile +++ b/drivers/net/dsa/sja1105/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_NET_DSA_SJA1105) += sja1105.o +obj-$(CONFIG_NET_DSA_SJA1105_PTP) += sja1105_ptp.o sja1105-objs := \ sja1105_spi.o \ diff --git a/drivers/net/dsa/sja1105/sja1105.h b/drivers/net/dsa/sja1105/sja1105.h index 61d00682de60..3c6296203c21 100644 --- a/drivers/net/dsa/sja1105/sja1105.h +++ b/drivers/net/dsa/sja1105/sja1105.h @@ -5,6 +5,8 @@ #ifndef _SJA1105_H #define _SJA1105_H +#include +#include #include #include #include @@ -27,6 +29,10 @@ struct sja1105_regs { u64 rgu; u64 config; u64 rmii_pll1; + u64 ptp_control; + u64 ptpclk; + u64 ptpclkrate; + u64 ptptsclk; u64 pad_mii_tx[SJA1105_NUM_PORTS]; u64 cgu_idiv[SJA1105_NUM_PORTS]; u64 rgmii_pad_mii_tx[SJA1105_NUM_PORTS]; @@ -53,6 +59,7 @@ struct sja1105_info { const struct sja1105_dynamic_table_ops *dyn_ops; const struct sja1105_table_ops *static_ops; const struct sja1105_regs *regs; + int (*ptp_cmd)(const void *ctx, const void *data); int (*reset_cmd)(const void *ctx, const void *data); int (*setup_rgmii_delay)(const void *ctx, int port); /* Prototypes from include/net/dsa.h */ @@ -72,6 +79,16 @@ struct sja1105_private { struct spi_device *spidev; struct dsa_switch *ds; struct sja1105_port ports[SJA1105_NUM_PORTS]; + struct ptp_clock_info ptp_caps; + struct ptp_clock *clock; + /* The cycle counter translates the PTP timestamps (based on + * a free-running counter) into a software time domain. + */ + struct cyclecounter tstamp_cc; + struct timecounter tstamp_tc; + struct delayed_work refresh_work; + /* Serializes all operations on the cycle counter */ + struct mutex ptp_lock; /* Serializes transmission of management frames so that * the switch doesn't confuse them with one another. */ @@ -79,6 +96,7 @@ struct sja1105_private { }; #include "sja1105_dynamic_config.h" +#include "sja1105_ptp.h" struct sja1105_spi_message { u64 access; diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c index ea854ea903d1..f897fdb12930 100644 --- a/drivers/net/dsa/sja1105/sja1105_main.c +++ b/drivers/net/dsa/sja1105/sja1105_main.c @@ -1530,6 +1530,11 @@ static int sja1105_setup(struct dsa_switch *ds) return rc; } + rc = sja1105_ptp_clock_register(priv); + if (rc < 0) { + dev_err(ds->dev, "Failed to register PTP clock: %d\n", rc); + return rc; + } /* Create and send configuration down to device */ rc = sja1105_static_config_load(priv, ports); if (rc < 0) { @@ -1681,6 +1686,7 @@ static const struct dsa_switch_ops sja1105_switch_ops = { .get_strings = sja1105_get_strings, .get_ethtool_stats = sja1105_get_ethtool_stats, .get_sset_count = sja1105_get_sset_count, + .get_ts_info = sja1105_get_ts_info, .port_fdb_dump = sja1105_fdb_dump, .port_fdb_add = sja1105_fdb_add, .port_fdb_del = sja1105_fdb_del, @@ -1805,6 +1811,7 @@ static int sja1105_remove(struct spi_device *spi) { struct sja1105_private *priv = spi_get_drvdata(spi); + sja1105_ptp_clock_unregister(priv); dsa_unregister_switch(priv->ds); sja1105_static_config_free(&priv->static_config); return 0; diff --git a/drivers/net/dsa/sja1105/sja1105_ptp.c b/drivers/net/dsa/sja1105/sja1105_ptp.c new file mode 100644 index 000000000000..47313a6ec932 --- /dev/null +++ b/drivers/net/dsa/sja1105/sja1105_ptp.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2019, Vladimir Oltean + */ +#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); + info->rx_filters = (1 << HWTSTAMP_FILTER_NONE); + info->phc_index = ptp_clock_index(priv->clock); + return 0; +} +EXPORT_SYMBOL_GPL(sja1105_get_ts_info); + +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); +} +EXPORT_SYMBOL_GPL(sja1105et_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); +} +EXPORT_SYMBOL_GPL(sja1105pqrs_ptp_cmd); + +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; +} +EXPORT_SYMBOL_GPL(sja1105_ptp_reset); + +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); +} +EXPORT_SYMBOL_GPL(sja1105_ptp_clock_register); + +void sja1105_ptp_clock_unregister(struct sja1105_private *priv) +{ + if (IS_ERR_OR_NULL(priv->clock)) + return; + + ptp_clock_unregister(priv->clock); + priv->clock = NULL; +} +EXPORT_SYMBOL_GPL(sja1105_ptp_clock_unregister); + +MODULE_AUTHOR("Vladimir Oltean "); +MODULE_DESCRIPTION("SJA1105 PHC Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/dsa/sja1105/sja1105_ptp.h b/drivers/net/dsa/sja1105/sja1105_ptp.h new file mode 100644 index 000000000000..137ffbb0a233 --- /dev/null +++ b/drivers/net/dsa/sja1105/sja1105_ptp.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Copyright (c) 2019, Vladimir Oltean + */ +#ifndef _SJA1105_PTP_H +#define _SJA1105_PTP_H + +#if IS_ENABLED(CONFIG_NET_DSA_SJA1105_PTP) + +int sja1105_ptp_clock_register(struct sja1105_private *priv); + +void sja1105_ptp_clock_unregister(struct sja1105_private *priv); + +int sja1105et_ptp_cmd(const void *ctx, const void *data); + +int sja1105pqrs_ptp_cmd(const void *ctx, const void *data); + +int sja1105_get_ts_info(struct dsa_switch *ds, int port, + struct ethtool_ts_info *ts); + +int sja1105_ptp_reset(struct sja1105_private *priv); + +#else + +static inline int sja1105_ptp_clock_register(struct sja1105_private *priv) +{ + return 0; +} + +static inline void sja1105_ptp_clock_unregister(struct sja1105_private *priv) +{ + return; +} + +static inline int sja1105_ptp_reset(struct sja1105_private *priv) +{ + return 0; +} + +#define sja1105et_ptp_cmd NULL + +#define sja1105pqrs_ptp_cmd NULL + +#define sja1105_get_ts_info NULL + +#endif /* IS_ENABLED(CONFIG_NET_DSA_SJA1105_PTP) */ + +#endif /* _SJA1105_PTP_H */ diff --git a/drivers/net/dsa/sja1105/sja1105_spi.c b/drivers/net/dsa/sja1105/sja1105_spi.c index 422894068c8b..a0d08e6c22ff 100644 --- a/drivers/net/dsa/sja1105/sja1105_spi.c +++ b/drivers/net/dsa/sja1105/sja1105_spi.c @@ -480,7 +480,12 @@ int sja1105_static_config_upload(struct sja1105_private *priv) dev_info(dev, "Succeeded after %d tried\n", RETRIES - retries); } + rc = sja1105_ptp_reset(priv); + if (rc < 0) + dev_err(dev, "Failed to reset PTP clock: %d\n", rc); + dev_info(dev, "Reset switch and programmed static config\n"); + out: kfree(config_buf); return rc; @@ -509,6 +514,10 @@ static struct sja1105_regs sja1105et_regs = { .rgmii_tx_clk = {0x100016, 0x10001D, 0x100024, 0x10002B, 0x100032}, .rmii_ref_clk = {0x100015, 0x10001C, 0x100023, 0x10002A, 0x100031}, .rmii_ext_tx_clk = {0x100018, 0x10001F, 0x100026, 0x10002D, 0x100034}, + .ptp_control = 0x17, + .ptpclk = 0x18, /* Spans 0x18 to 0x19 */ + .ptpclkrate = 0x1A, + .ptptsclk = 0x1B, /* Spans 0x1B to 0x1C */ }; static struct sja1105_regs sja1105pqrs_regs = { @@ -535,6 +544,10 @@ static struct sja1105_regs sja1105pqrs_regs = { .rmii_ref_clk = {0x100015, 0x10001B, 0x100021, 0x100027, 0x10002D}, .rmii_ext_tx_clk = {0x100017, 0x10001D, 0x100023, 0x100029, 0x10002F}, .qlevel = {0x604, 0x614, 0x624, 0x634, 0x644}, + .ptp_control = 0x18, + .ptpclk = 0x19, + .ptpclkrate = 0x1B, + .ptptsclk = 0x1C, }; struct sja1105_info sja1105e_info = { @@ -545,6 +558,7 @@ struct sja1105_info sja1105e_info = { .reset_cmd = sja1105et_reset_cmd, .fdb_add_cmd = sja1105et_fdb_add, .fdb_del_cmd = sja1105et_fdb_del, + .ptp_cmd = sja1105et_ptp_cmd, .regs = &sja1105et_regs, .name = "SJA1105E", }; @@ -556,6 +570,7 @@ struct sja1105_info sja1105t_info = { .reset_cmd = sja1105et_reset_cmd, .fdb_add_cmd = sja1105et_fdb_add, .fdb_del_cmd = sja1105et_fdb_del, + .ptp_cmd = sja1105et_ptp_cmd, .regs = &sja1105et_regs, .name = "SJA1105T", }; @@ -567,6 +582,7 @@ struct sja1105_info sja1105p_info = { .reset_cmd = sja1105pqrs_reset_cmd, .fdb_add_cmd = sja1105pqrs_fdb_add, .fdb_del_cmd = sja1105pqrs_fdb_del, + .ptp_cmd = sja1105pqrs_ptp_cmd, .regs = &sja1105pqrs_regs, .name = "SJA1105P", }; @@ -578,6 +594,7 @@ struct sja1105_info sja1105q_info = { .reset_cmd = sja1105pqrs_reset_cmd, .fdb_add_cmd = sja1105pqrs_fdb_add, .fdb_del_cmd = sja1105pqrs_fdb_del, + .ptp_cmd = sja1105pqrs_ptp_cmd, .regs = &sja1105pqrs_regs, .name = "SJA1105Q", }; @@ -589,6 +606,7 @@ struct sja1105_info sja1105r_info = { .reset_cmd = sja1105pqrs_reset_cmd, .fdb_add_cmd = sja1105pqrs_fdb_add, .fdb_del_cmd = sja1105pqrs_fdb_del, + .ptp_cmd = sja1105pqrs_ptp_cmd, .regs = &sja1105pqrs_regs, .name = "SJA1105R", }; @@ -601,5 +619,6 @@ struct sja1105_info sja1105s_info = { .reset_cmd = sja1105pqrs_reset_cmd, .fdb_add_cmd = sja1105pqrs_fdb_add, .fdb_del_cmd = sja1105pqrs_fdb_del, + .ptp_cmd = sja1105pqrs_ptp_cmd, .name = "SJA1105S", };