Commit f0d4ba9e authored by Kamil Alkhouri's avatar Kamil Alkhouri Committed by Jakub Kicinski

net: dsa: hellcreek: Add support for hardware timestamping

The switch has the ability to take hardware generated time stamps per port for
PTPv2 event messages in Rx and Tx direction. That is useful for achieving needed
time synchronization precision for TSN devices/switches. So add support for it.

There are two directions:

 * RX

   The switch has a single register per port to capture a timestamp. That
   mechanism is not used due to correlation problems. If the software processing
   is too slow and a PTPv2 event message is received before the previous one has
   been processed, false timestamps will be captured. Therefore, the switch can
   do "inline" timestamping which means it can insert the nanoseconds part of
   the timestamp directly into the PTPv2 event message. The reserved field (4
   bytes) is leveraged for that. This might not be in accordance with (older)
   PTP standards, but is the only way to get reliable results.

 * TX

   In Tx direction there is no correlation problem, because the software and the
   driver has to ensure that only one event message is "on the fly". However,
   the switch provides also a mechanism to check whether a timestamp is
   lost. That can only happen when a timestamp is read and at this point another
   message is timestamped. So, that lost bit is checked just in case to indicate
   to the user that the driver or the software is somewhat buggy.
Signed-off-by: default avatarKamil Alkhouri <kamil.alkhouri@hs-offenburg.de>
Signed-off-by: Kurt Kanzenbach's avatarKurt Kanzenbach <kurt@linutronix.de>
Acked-by: default avatarRichard Cochran <richardcochran@gmail.com>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent ddd56dfe
......@@ -2,3 +2,4 @@
obj-$(CONFIG_NET_DSA_HIRSCHMANN_HELLCREEK) += hellcreek_sw.o
hellcreek_sw-objs := hellcreek.o
hellcreek_sw-objs += hellcreek_ptp.o
hellcreek_sw-objs += hellcreek_hwtstamp.o
......@@ -26,6 +26,7 @@
#include "hellcreek.h"
#include "hellcreek_ptp.h"
#include "hellcreek_hwtstamp.h"
static const struct hellcreek_counter hellcreek_counter[] = {
{ 0x00, "RxFiltered", },
......@@ -1139,6 +1140,7 @@ static const struct dsa_switch_ops hellcreek_ds_ops = {
.get_sset_count = hellcreek_get_sset_count,
.get_strings = hellcreek_get_strings,
.get_tag_protocol = hellcreek_get_tag_protocol,
.get_ts_info = hellcreek_get_ts_info,
.phylink_validate = hellcreek_phylink_validate,
.port_bridge_join = hellcreek_port_bridge_join,
.port_bridge_leave = hellcreek_port_bridge_leave,
......@@ -1147,8 +1149,12 @@ static const struct dsa_switch_ops hellcreek_ds_ops = {
.port_fdb_add = hellcreek_fdb_add,
.port_fdb_del = hellcreek_fdb_del,
.port_fdb_dump = hellcreek_fdb_dump,
.port_hwtstamp_set = hellcreek_port_hwtstamp_set,
.port_hwtstamp_get = hellcreek_port_hwtstamp_get,
.port_prechangeupper = hellcreek_port_prechangeupper,
.port_rxtstamp = hellcreek_port_rxtstamp,
.port_stp_state_set = hellcreek_port_stp_state_set,
.port_txtstamp = hellcreek_port_txtstamp,
.port_vlan_add = hellcreek_vlan_add,
.port_vlan_del = hellcreek_vlan_del,
.port_vlan_filtering = hellcreek_vlan_filtering,
......@@ -1270,10 +1276,18 @@ static int hellcreek_probe(struct platform_device *pdev)
goto err_ptp_setup;
}
ret = hellcreek_hwtstamp_setup(hellcreek);
if (ret) {
dev_err(dev, "Failed to setup hardware timestamping!\n");
goto err_tstamp_setup;
}
platform_set_drvdata(pdev, hellcreek);
return 0;
err_tstamp_setup:
hellcreek_ptp_free(hellcreek);
err_ptp_setup:
dsa_unregister_switch(hellcreek->ds);
......@@ -1284,6 +1298,7 @@ static int hellcreek_remove(struct platform_device *pdev)
{
struct hellcreek *hellcreek = platform_get_drvdata(pdev);
hellcreek_hwtstamp_free(hellcreek);
hellcreek_ptp_free(hellcreek);
dsa_unregister_switch(hellcreek->ds);
platform_set_drvdata(pdev, NULL);
......
......@@ -214,12 +214,37 @@ struct hellcreek_counter {
struct hellcreek;
/* State flags for hellcreek_port_hwtstamp::state */
enum {
HELLCREEK_HWTSTAMP_ENABLED,
HELLCREEK_HWTSTAMP_TX_IN_PROGRESS,
};
/* A structure to hold hardware timestamping information per port */
struct hellcreek_port_hwtstamp {
/* Timestamping state */
unsigned long state;
/* Resources for receive timestamping */
struct sk_buff_head rx_queue; /* For synchronization messages */
/* Resources for transmit timestamping */
unsigned long tx_tstamp_start;
struct sk_buff *tx_skb;
/* Current timestamp configuration */
struct hwtstamp_config tstamp_config;
};
struct hellcreek_port {
struct hellcreek *hellcreek;
unsigned long *vlan_dev_bitmap;
int port;
u16 ptcfg; /* ptcfg shadow */
u64 *counter_values;
/* Per-port timestamping resources */
struct hellcreek_port_hwtstamp port_hwtstamp;
};
struct hellcreek_fdb_entry {
......
This diff is collapsed.
/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
/*
* DSA driver for:
* Hirschmann Hellcreek TSN switch.
*
* Copyright (C) 2019,2020 Hochschule Offenburg
* Copyright (C) 2019,2020 Linutronix GmbH
* Authors: Kurt Kanzenbach <kurt@linutronix.de>
* Kamil Alkhouri <kamil.alkhouri@hs-offenburg.de>
*/
#ifndef _HELLCREEK_HWTSTAMP_H_
#define _HELLCREEK_HWTSTAMP_H_
#include <net/dsa.h>
#include "hellcreek.h"
/* Timestamp Register */
#define PR_TS_RX_P1_STATUS_C (0x1d * 2)
#define PR_TS_RX_P1_DATA_C (0x1e * 2)
#define PR_TS_TX_P1_STATUS_C (0x1f * 2)
#define PR_TS_TX_P1_DATA_C (0x20 * 2)
#define PR_TS_RX_P2_STATUS_C (0x25 * 2)
#define PR_TS_RX_P2_DATA_C (0x26 * 2)
#define PR_TS_TX_P2_STATUS_C (0x27 * 2)
#define PR_TS_TX_P2_DATA_C (0x28 * 2)
#define PR_TS_STATUS_TS_AVAIL BIT(2)
#define PR_TS_STATUS_TS_LOST BIT(3)
#define SKB_PTP_TYPE(__skb) (*(unsigned int *)((__skb)->cb))
/* TX_TSTAMP_TIMEOUT: This limits the time spent polling for a TX
* timestamp. When working properly, hardware will produce a timestamp
* within 1ms. Software may enounter delays, so the timeout is set
* accordingly.
*/
#define TX_TSTAMP_TIMEOUT msecs_to_jiffies(40)
int hellcreek_port_hwtstamp_set(struct dsa_switch *ds, int port,
struct ifreq *ifr);
int hellcreek_port_hwtstamp_get(struct dsa_switch *ds, int port,
struct ifreq *ifr);
bool hellcreek_port_rxtstamp(struct dsa_switch *ds, int port,
struct sk_buff *clone, unsigned int type);
bool hellcreek_port_txtstamp(struct dsa_switch *ds, int port,
struct sk_buff *clone, unsigned int type);
int hellcreek_get_ts_info(struct dsa_switch *ds, int port,
struct ethtool_ts_info *info);
long hellcreek_hwtstamp_work(struct ptp_clock_info *ptp);
int hellcreek_hwtstamp_setup(struct hellcreek *chip);
void hellcreek_hwtstamp_free(struct hellcreek *chip);
#endif /* _HELLCREEK_HWTSTAMP_H_ */
......@@ -12,14 +12,15 @@
#include <linux/ptp_clock_kernel.h>
#include "hellcreek.h"
#include "hellcreek_ptp.h"
#include "hellcreek_hwtstamp.h"
static u16 hellcreek_ptp_read(struct hellcreek *hellcreek, unsigned int offset)
u16 hellcreek_ptp_read(struct hellcreek *hellcreek, unsigned int offset)
{
return readw(hellcreek->ptp_base + offset);
}
static void hellcreek_ptp_write(struct hellcreek *hellcreek, u16 data,
unsigned int offset)
void hellcreek_ptp_write(struct hellcreek *hellcreek, u16 data,
unsigned int offset)
{
writew(data, hellcreek->ptp_base + offset);
}
......@@ -61,6 +62,24 @@ static u64 __hellcreek_ptp_gettime(struct hellcreek *hellcreek)
return ns;
}
/* Retrieve the seconds parts in nanoseconds for a packet timestamped with @ns.
* There has to be a check whether an overflow occurred between the packet
* arrival and now. If so use the correct seconds (-1) for calculating the
* packet arrival time.
*/
u64 hellcreek_ptp_gettime_seconds(struct hellcreek *hellcreek, u64 ns)
{
u64 s;
__hellcreek_ptp_gettime(hellcreek);
if (hellcreek->last_ts > ns)
s = hellcreek->seconds * NSEC_PER_SEC;
else
s = (hellcreek->seconds - 1) * NSEC_PER_SEC;
return s;
}
static int hellcreek_ptp_gettime(struct ptp_clock_info *ptp,
struct timespec64 *ts)
{
......@@ -238,17 +257,18 @@ int hellcreek_ptp_setup(struct hellcreek *hellcreek)
* accumulator_overflow_rate shall not exceed 62.5 MHz (which adjusts
* the nominal frequency by 6.25%)
*/
hellcreek->ptp_clock_info.max_adj = 62500000;
hellcreek->ptp_clock_info.n_alarm = 0;
hellcreek->ptp_clock_info.n_pins = 0;
hellcreek->ptp_clock_info.n_ext_ts = 0;
hellcreek->ptp_clock_info.n_per_out = 0;
hellcreek->ptp_clock_info.pps = 0;
hellcreek->ptp_clock_info.adjfine = hellcreek_ptp_adjfine;
hellcreek->ptp_clock_info.adjtime = hellcreek_ptp_adjtime;
hellcreek->ptp_clock_info.gettime64 = hellcreek_ptp_gettime;
hellcreek->ptp_clock_info.settime64 = hellcreek_ptp_settime;
hellcreek->ptp_clock_info.enable = hellcreek_ptp_enable;
hellcreek->ptp_clock_info.max_adj = 62500000;
hellcreek->ptp_clock_info.n_alarm = 0;
hellcreek->ptp_clock_info.n_pins = 0;
hellcreek->ptp_clock_info.n_ext_ts = 0;
hellcreek->ptp_clock_info.n_per_out = 0;
hellcreek->ptp_clock_info.pps = 0;
hellcreek->ptp_clock_info.adjfine = hellcreek_ptp_adjfine;
hellcreek->ptp_clock_info.adjtime = hellcreek_ptp_adjtime;
hellcreek->ptp_clock_info.gettime64 = hellcreek_ptp_gettime;
hellcreek->ptp_clock_info.settime64 = hellcreek_ptp_settime;
hellcreek->ptp_clock_info.enable = hellcreek_ptp_enable;
hellcreek->ptp_clock_info.do_aux_work = hellcreek_hwtstamp_work;
hellcreek->ptp_clock = ptp_clock_register(&hellcreek->ptp_clock_info,
hellcreek->dev);
......
......@@ -59,6 +59,10 @@
int hellcreek_ptp_setup(struct hellcreek *hellcreek);
void hellcreek_ptp_free(struct hellcreek *hellcreek);
u16 hellcreek_ptp_read(struct hellcreek *hellcreek, unsigned int offset);
void hellcreek_ptp_write(struct hellcreek *hellcreek, u16 data,
unsigned int offset);
u64 hellcreek_ptp_gettime_seconds(struct hellcreek *hellcreek, u64 ns);
#define ptp_to_hellcreek(ptp) \
container_of(ptp, struct hellcreek, ptp_clock_info)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment