Add control routines required for TX hardware time stamping. The KSZ9563 only supports one step time stamping (HWTSTAMP_TX_ONESTEP_P2P), which requires linuxptp-2.0 or later. Currently, only P2P delay measurement is supported. See patchwork discussion and comments in ksz9477_ptp_init() for details: https://patchwork.ozlabs.org/project/netdev/patch/20201019172435.4416-8-ceggers@xxxxxxx/ Signed-off-by: Christian Eggers <ceggers@xxxxxxx> --- drivers/net/dsa/microchip/ksz9477_main.c | 6 + drivers/net/dsa/microchip/ksz9477_ptp.c | 187 +++++++++++++++++++++++ drivers/net/dsa/microchip/ksz9477_ptp.h | 22 +++ drivers/net/dsa/microchip/ksz_common.h | 4 + 4 files changed, 219 insertions(+) diff --git a/drivers/net/dsa/microchip/ksz9477_main.c b/drivers/net/dsa/microchip/ksz9477_main.c index d1a2ad4a34f1..830efdaef9dc 100644 --- a/drivers/net/dsa/microchip/ksz9477_main.c +++ b/drivers/net/dsa/microchip/ksz9477_main.c @@ -1389,6 +1389,7 @@ static const struct dsa_switch_ops ksz9477_switch_ops = { .phy_read = ksz9477_phy_read16, .phy_write = ksz9477_phy_write16, .phylink_mac_link_down = ksz_mac_link_down, + .get_ts_info = ksz9477_ptp_get_ts_info, .port_enable = ksz_enable_port, .get_strings = ksz9477_get_strings, .get_ethtool_stats = ksz_get_ethtool_stats, @@ -1409,6 +1410,11 @@ static const struct dsa_switch_ops ksz9477_switch_ops = { .port_mdb_del = ksz9477_port_mdb_del, .port_mirror_add = ksz9477_port_mirror_add, .port_mirror_del = ksz9477_port_mirror_del, + .port_hwtstamp_get = ksz9477_ptp_port_hwtstamp_get, + .port_hwtstamp_set = ksz9477_ptp_port_hwtstamp_set, + .port_txtstamp = NULL, + /* never defer rx delivery, tstamping is done via tail tagging */ + .port_rxtstamp = NULL, }; static u32 ksz9477_get_port_addr(int port, int offset) diff --git a/drivers/net/dsa/microchip/ksz9477_ptp.c b/drivers/net/dsa/microchip/ksz9477_ptp.c index 0ffc4504a290..f411e5cb88a5 100644 --- a/drivers/net/dsa/microchip/ksz9477_ptp.c +++ b/drivers/net/dsa/microchip/ksz9477_ptp.c @@ -218,6 +218,18 @@ static int ksz9477_ptp_enable(struct ptp_clock_info *ptp, return -EOPNOTSUPP; } +static long ksz9477_ptp_do_aux_work(struct ptp_clock_info *ptp) +{ + struct ksz_device *dev = container_of(ptp, struct ksz_device, ptp_caps); + struct timespec64 ts; + + mutex_lock(&dev->ptp_mutex); + _ksz9477_ptp_gettime(dev, &ts); + mutex_unlock(&dev->ptp_mutex); + + return HZ; /* reschedule in 1 second */ +} + static int ksz9477_ptp_start_clock(struct ksz_device *dev) { u16 data; @@ -257,6 +269,54 @@ static int ksz9477_ptp_stop_clock(struct ksz_device *dev) return ksz_write16(dev, REG_PTP_CLK_CTRL, data); } +/* device attributes */ + +enum ksz9477_ptp_tcmode { + KSZ9477_PTP_TCMODE_E2E, + KSZ9477_PTP_TCMODE_P2P, +}; + +static int ksz9477_ptp_tcmode_set(struct ksz_device *dev, + enum ksz9477_ptp_tcmode tcmode) +{ + u16 data; + int ret; + + ret = ksz_read16(dev, REG_PTP_MSG_CONF1, &data); + if (ret) + return ret; + + if (tcmode == KSZ9477_PTP_TCMODE_P2P) + data |= PTP_TC_P2P; + else + data &= ~PTP_TC_P2P; + + return ksz_write16(dev, REG_PTP_MSG_CONF1, data); +} + +enum ksz9477_ptp_ocmode { + KSZ9477_PTP_OCMODE_SLAVE, + KSZ9477_PTP_OCMODE_MASTER, +}; + +static int ksz9477_ptp_ocmode_set(struct ksz_device *dev, + enum ksz9477_ptp_ocmode ocmode) +{ + u16 data; + int ret; + + ret = ksz_read16(dev, REG_PTP_MSG_CONF1, &data); + if (ret) + return ret; + + if (ocmode == KSZ9477_PTP_OCMODE_MASTER) + data |= PTP_MASTER; + else + data &= ~PTP_MASTER; + + return ksz_write16(dev, REG_PTP_MSG_CONF1, data); +} + int ksz9477_ptp_init(struct ksz_device *dev) { int ret; @@ -282,6 +342,7 @@ int ksz9477_ptp_init(struct ksz_device *dev) dev->ptp_caps.gettime64 = ksz9477_ptp_gettime; dev->ptp_caps.settime64 = ksz9477_ptp_settime; dev->ptp_caps.enable = ksz9477_ptp_enable; + dev->ptp_caps.do_aux_work = ksz9477_ptp_do_aux_work; /* Start hardware counter (will overflow after 136 years) */ ret = ksz9477_ptp_start_clock(dev); @@ -294,8 +355,31 @@ int ksz9477_ptp_init(struct ksz_device *dev) goto error_stop_clock; } + /* Currently, only P2P delay measurement is supported. Setting ocmode + * to slave will work independently of actually being master or slave. + * For E2E delay measurement, switching between master and slave would + * be required, as the KSZ devices filters out PTP messages depending on + * the ocmode setting: + * - in slave mode, DelayReq messages are filtered out + * - in master mode, Sync messages are filtered out + * Currently (and probably also in future) there is no interface in the + * kernel which allows switching between master and slave mode. For + * this reason, E2E cannot be supported. See patchwork for full + * discussion: + * https://patchwork.ozlabs.org/project/netdev/patch/20201019172435.4416-8-ceggers@xxxxxxx/ + */ + ksz9477_ptp_tcmode_set(dev, KSZ9477_PTP_TCMODE_P2P); + ksz9477_ptp_ocmode_set(dev, KSZ9477_PTP_OCMODE_SLAVE); + + /* Schedule cyclic call of ksz_ptp_do_aux_work() */ + ret = ptp_schedule_worker(dev->ptp_clock, 0); + if (ret) + goto error_unregister_clock; + return 0; +error_unregister_clock: + ptp_clock_unregister(dev->ptp_clock); error_stop_clock: ksz9477_ptp_stop_clock(dev); return ret; @@ -306,3 +390,106 @@ void ksz9477_ptp_deinit(struct ksz_device *dev) ptp_clock_unregister(dev->ptp_clock); ksz9477_ptp_stop_clock(dev); } + +/* DSA PTP operations */ + +int ksz9477_ptp_get_ts_info(struct dsa_switch *ds, int port, + struct ethtool_ts_info *ts) +{ + struct ksz_device *dev = ds->priv; + + ts->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE | + SOF_TIMESTAMPING_RX_HARDWARE | + SOF_TIMESTAMPING_RAW_HARDWARE; + + ts->phc_index = ptp_clock_index(dev->ptp_clock); + + ts->tx_types = BIT(HWTSTAMP_TX_OFF) | + BIT(HWTSTAMP_TX_ONESTEP_P2P); + + ts->rx_filters = BIT(HWTSTAMP_FILTER_NONE) | + BIT(HWTSTAMP_FILTER_PTP_V2_L4_EVENT) | + BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT) | + BIT(HWTSTAMP_FILTER_PTP_V2_EVENT); + + return 0; +} + +static int ksz9477_set_hwtstamp_config(struct ksz_device *dev, int port, + struct hwtstamp_config *config) +{ + struct ksz_port *prt = &dev->ports[port]; + + /* reserved for future extensions */ + if (config->flags) + return -EINVAL; + + switch (config->tx_type) { + case HWTSTAMP_TX_OFF: + prt->hwts_tx_en = false; + break; + case HWTSTAMP_TX_ONESTEP_P2P: + prt->hwts_tx_en = true; + break; + default: + return -ERANGE; + } + + switch (config->rx_filter) { + case HWTSTAMP_FILTER_NONE: + break; + case HWTSTAMP_FILTER_PTP_V2_L4_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L4_SYNC: + config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L4_EVENT; + break; + case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_L2_SYNC: + config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; + break; + case HWTSTAMP_FILTER_PTP_V2_EVENT: + case HWTSTAMP_FILTER_PTP_V2_SYNC: + config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT; + break; + case HWTSTAMP_FILTER_ALL: + default: + config->rx_filter = HWTSTAMP_FILTER_NONE; + return -ERANGE; + } + + return 0; +} + +int ksz9477_ptp_port_hwtstamp_get(struct dsa_switch *ds, int port, + struct ifreq *ifr) +{ + struct ksz_device *dev = ds->priv; + unsigned long bytes_copied; + + bytes_copied = copy_to_user(ifr->ifr_data, + &dev->ports[port].tstamp_config, + sizeof(dev->ports[port].tstamp_config)); + + return bytes_copied ? -EFAULT : 0; +} + +int ksz9477_ptp_port_hwtstamp_set(struct dsa_switch *ds, int port, + struct ifreq *ifr) +{ + struct ksz_device *dev = ds->priv; + struct hwtstamp_config config; + unsigned long bytes_copied; + int err; + + if (copy_from_user(&config, ifr->ifr_data, sizeof(config))) + return -EFAULT; + + err = ksz9477_set_hwtstamp_config(dev, port, &config); + if (err) + return err; + + /* Save the chosen configuration to be returned later. */ + memcpy(&dev->ports[port].tstamp_config, &config, sizeof(config)); + bytes_copied = copy_to_user(ifr->ifr_data, &config, sizeof(config)); + + return bytes_copied ? -EFAULT : 0; +} diff --git a/drivers/net/dsa/microchip/ksz9477_ptp.h b/drivers/net/dsa/microchip/ksz9477_ptp.h index 0076538419fa..2fd58a981ec5 100644 --- a/drivers/net/dsa/microchip/ksz9477_ptp.h +++ b/drivers/net/dsa/microchip/ksz9477_ptp.h @@ -10,6 +10,8 @@ #ifndef DRIVERS_NET_DSA_MICROCHIP_KSZ9477_PTP_H_ #define DRIVERS_NET_DSA_MICROCHIP_KSZ9477_PTP_H_ +#include <linux/types.h> + #include "ksz_common.h" #if IS_ENABLED(CONFIG_NET_DSA_MICROCHIP_KSZ9477_PTP) @@ -17,11 +19,31 @@ int ksz9477_ptp_init(struct ksz_device *dev); void ksz9477_ptp_deinit(struct ksz_device *dev); +int ksz9477_ptp_get_ts_info(struct dsa_switch *ds, int port, + struct ethtool_ts_info *ts); +int ksz9477_ptp_port_hwtstamp_get(struct dsa_switch *ds, int port, + struct ifreq *ifr); +int ksz9477_ptp_port_hwtstamp_set(struct dsa_switch *ds, int port, + struct ifreq *ifr); + #else static inline int ksz9477_ptp_init(struct ksz_device *dev) { return 0; } static inline void ksz9477_ptp_deinit(struct ksz_device *dev) {} +static inline int ksz9477_ptp_get_ts_info(struct dsa_switch *ds, int port, + struct ethtool_ts_info *ts) +{ return -EOPNOTSUPP; } + +static inline int ksz9477_ptp_port_hwtstamp_get(struct dsa_switch *ds, int port, + struct ifreq *ifr) +{ return -EOPNOTSUPP; } + +static inline int ksz9477_ptp_port_hwtstamp_set(struct dsa_switch *ds, int port, + struct ifreq *ifr) +{ return -EOPNOTSUPP; } + + #endif #endif /* DRIVERS_NET_DSA_MICROCHIP_KSZ9477_PTP_H_ */ diff --git a/drivers/net/dsa/microchip/ksz_common.h b/drivers/net/dsa/microchip/ksz_common.h index 43dd66009482..139e9b84290b 100644 --- a/drivers/net/dsa/microchip/ksz_common.h +++ b/drivers/net/dsa/microchip/ksz_common.h @@ -41,6 +41,10 @@ struct ksz_port { struct ksz_port_mib mib; phy_interface_t interface; +#if IS_ENABLED(CONFIG_NET_DSA_MICROCHIP_KSZ9477_PTP) + struct hwtstamp_config tstamp_config; + bool hwts_tx_en; +#endif }; struct ksz_device { -- Christian Eggers Embedded software developer Arnold & Richter Cine Technik GmbH & Co. Betriebs KG Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRA 57918 Persoenlich haftender Gesellschafter: Arnold & Richter Cine Technik GmbH Sitz: Muenchen - Registergericht: Amtsgericht Muenchen - Handelsregisternummer: HRB 54477 Geschaeftsfuehrer: Dr. Michael Neuhaeuser; Stephan Schenk; Walter Trauninger; Markus Zeiler