Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8d94a873 authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'PTP-support-for-the-SJA1105-DSA-driver'

Vladimir Oltean says:

====================
PTP support for the SJA1105 DSA driver

This patchset adds the following:

 - A timecounter/cyclecounter based PHC for the free-running
   timestamping clock of this switch.

 - A state machine implemented in the DSA tagger for SJA1105, which
   keeps track of metadata follow-up Ethernet frames (the switch's way
   of transmitting RX timestamps).

Clock manipulations on the actual hardware PTP clock will have to be
implemented anyway, for the TTEthernet block and the time-based ingress
policer.

v3 patchset can be found at:
https://lkml.org/lkml/2019/6/4/954

Changes from v3:

- Made it compile with the SJA1105 DSA driver and PTP driver as modules.

- Reworked/simplified/fixed some issues in 03/17
  (dsa_8021q_remove_header) and added an ASCII image that
  illustrates the transformation that is taking place.

- Removed a useless check for sja1105_is_link_local from 16/17 (RX
  timestamping) which also made previous 08/17 patch ("Move
  sja1105_is_link_local to include/linux") useless and therefore dropped.

v2 patchset can be found at:
https://lkml.org/lkml/2019/6/2/146

Changes from v2:

- Broke previous 09/10 patch (timestamping) into multiple smaller
  patches.

- Every patch in the series compiles.

v1 patchset can be found at:
https://lkml.org/lkml/2019/5/28/1093



Changes from v1:

- Removed the addition of the DSA .can_timestamp callback.

- Waiting for meta frames is done completely inside the tagger, and all
  frames emitted on RX are already partially timestamped.

- Added a global data structure for the tagger common to all ports.

- Made PTP work with ports in standalone mode, by limiting use of the
  DMAC-mangling "incl_srcpt" mode only when ports are bridged, aka when
  the DSA master is already promiscuous and can receive anything.
  Also changed meta frames to be sent at the 01-80-C2-00-00-0E DMAC.

- Made some progress w.r.t. observed negative path delay.  Apparently it
  only appears when the delay mechanism is the delay request-response
  (end-to-end) one. If peer delay is used (-P), the path delay is
  positive and appears reasonable for an 1000Base-T link (485 ns in
  steady state).

  SJA1105 as PTP slave (OC) with E2E path delay:

ptp4l[55.600]: master offset          8 s2 freq  +83677 path delay     -2390
ptp4l[56.600]: master offset         17 s2 freq  +83688 path delay     -2391
ptp4l[57.601]: master offset          6 s2 freq  +83682 path delay     -2391
ptp4l[58.601]: master offset         -1 s2 freq  +83677 path delay     -2391

  SJA1105 as PTP slave (OC) with P2P path delay:

ptp4l[48.343]: master offset          5 s2 freq  +83715 path delay       484
ptp4l[48.468]: master offset         -3 s2 freq  +83705 path delay       485
ptp4l[48.593]: master offset          0 s2 freq  +83708 path delay       485
ptp4l[48.718]: master offset          1 s2 freq  +83710 path delay       485
ptp4l[48.844]: master offset          1 s2 freq  +83710 path delay       485
ptp4l[48.969]: master offset         -5 s2 freq  +83702 path delay       485
ptp4l[49.094]: master offset          3 s2 freq  +83712 path delay       485
ptp4l[49.219]: master offset          4 s2 freq  +83714 path delay       485
ptp4l[49.344]: master offset         -5 s2 freq  +83702 path delay       485
ptp4l[49.469]: master offset          3 s2 freq  +83713 path delay       487
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents a6cdeeb1 a602afd2
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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.
+1 −0
Original line number Diff line number Diff line
# 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 \
+29 −0
Original line number Diff line number Diff line
@@ -5,6 +5,8 @@
#ifndef _SJA1105_H
#define _SJA1105_H

#include <linux/ptp_clock_kernel.h>
#include <linux/timecounter.h>
#include <linux/dsa/sja1105.h>
#include <net/dsa.h>
#include <linux/mutex.h>
@@ -27,6 +29,11 @@ struct sja1105_regs {
	u64 rgu;
	u64 config;
	u64 rmii_pll1;
	u64 ptp_control;
	u64 ptpclk;
	u64 ptpclkrate;
	u64 ptptsclk;
	u64 ptpegr_ts[SJA1105_NUM_PORTS];
	u64 pad_mii_tx[SJA1105_NUM_PORTS];
	u64 cgu_idiv[SJA1105_NUM_PORTS];
	u64 rgmii_pad_mii_tx[SJA1105_NUM_PORTS];
@@ -50,9 +57,19 @@ struct sja1105_info {
	 * switch core and device_id)
	 */
	u64 part_no;
	/* E/T and P/Q/R/S have partial timestamps of different sizes.
	 * They must be reconstructed on both families anyway to get the full
	 * 64-bit values back.
	 */
	int ptp_ts_bits;
	/* Also SPI commands are of different sizes to retrieve
	 * the egress timestamps.
	 */
	int ptpegr_ts_bytes;
	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,13 +89,25 @@ 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.
	 */
	struct mutex mgmt_lock;
	struct sja1105_tagger_data tagger_data;
};

#include "sja1105_dynamic_config.h"
#include "sja1105_ptp.h"

struct sja1105_spi_message {
	u64 access;
+2 −0
Original line number Diff line number Diff line
@@ -378,6 +378,7 @@ struct sja1105_dynamic_table_ops sja1105et_dyn_ops[BLK_IDX_MAX_DYN] = {
		.addr = 0x38,
	},
	[BLK_IDX_L2_FORWARDING_PARAMS] = {0},
	[BLK_IDX_AVB_PARAMS] = {0},
	[BLK_IDX_GENERAL_PARAMS] = {
		.entry_packing = sja1105et_general_params_entry_packing,
		.cmd_packing = sja1105et_general_params_cmd_packing,
@@ -441,6 +442,7 @@ struct sja1105_dynamic_table_ops sja1105pqrs_dyn_ops[BLK_IDX_MAX_DYN] = {
		.addr = 0x38,
	},
	[BLK_IDX_L2_FORWARDING_PARAMS] = {0},
	[BLK_IDX_AVB_PARAMS] = {0},
	[BLK_IDX_GENERAL_PARAMS] = {
		.entry_packing = sja1105et_general_params_entry_packing,
		.cmd_packing = sja1105et_general_params_cmd_packing,
+290 −26
Original line number Diff line number Diff line
@@ -389,14 +389,14 @@ static int sja1105_init_general_params(struct sja1105_private *priv)
		.mirr_ptacu = 0,
		.switchid = priv->ds->index,
		/* Priority queue for link-local frames trapped to CPU */
		.hostprio = 0,
		.hostprio = 7,
		.mac_fltres1 = SJA1105_LINKLOCAL_FILTER_A,
		.mac_flt1    = SJA1105_LINKLOCAL_FILTER_A_MASK,
		.incl_srcpt1 = true,
		.incl_srcpt1 = false,
		.send_meta1  = false,
		.mac_fltres0 = SJA1105_LINKLOCAL_FILTER_B,
		.mac_flt0    = SJA1105_LINKLOCAL_FILTER_B_MASK,
		.incl_srcpt0 = true,
		.incl_srcpt0 = false,
		.send_meta0  = false,
		/* The destination for traffic matching mac_fltres1 and
		 * mac_fltres0 on all ports except host_port. Such traffic
@@ -508,6 +508,39 @@ static int sja1105_init_l2_policing(struct sja1105_private *priv)
	return 0;
}

static int sja1105_init_avb_params(struct sja1105_private *priv,
				   bool on)
{
	struct sja1105_avb_params_entry *avb;
	struct sja1105_table *table;

	table = &priv->static_config.tables[BLK_IDX_AVB_PARAMS];

	/* Discard previous AVB Parameters Table */
	if (table->entry_count) {
		kfree(table->entries);
		table->entry_count = 0;
	}

	/* Configure the reception of meta frames only if requested */
	if (!on)
		return 0;

	table->entries = kcalloc(SJA1105_MAX_AVB_PARAMS_COUNT,
				 table->ops->unpacked_entry_size, GFP_KERNEL);
	if (!table->entries)
		return -ENOMEM;

	table->entry_count = SJA1105_MAX_AVB_PARAMS_COUNT;

	avb = table->entries;

	avb->destmeta = SJA1105_META_DMAC;
	avb->srcmeta  = SJA1105_META_SMAC;

	return 0;
}

static int sja1105_static_config_load(struct sja1105_private *priv,
				      struct sja1105_dt_port *ports)
{
@@ -546,6 +579,9 @@ static int sja1105_static_config_load(struct sja1105_private *priv,
	if (rc < 0)
		return rc;
	rc = sja1105_init_general_params(priv);
	if (rc < 0)
		return rc;
	rc = sja1105_init_avb_params(priv, false);
	if (rc < 0)
		return rc;

@@ -1289,23 +1325,6 @@ static int sja1105_static_config_reload(struct sja1105_private *priv)
	return rc;
}

/* The TPID setting belongs to the General Parameters table,
 * which can only be partially reconfigured at runtime (and not the TPID).
 * So a switch reset is required.
 */
static int sja1105_change_tpid(struct sja1105_private *priv,
			       u16 tpid, u16 tpid2)
{
	struct sja1105_general_params_entry *general_params;
	struct sja1105_table *table;

	table = &priv->static_config.tables[BLK_IDX_GENERAL_PARAMS];
	general_params = table->entries;
	general_params->tpid = tpid;
	general_params->tpid2 = tpid2;
	return sja1105_static_config_reload(priv);
}

static int sja1105_pvid_apply(struct sja1105_private *priv, int port, u16 pvid)
{
	struct sja1105_mac_config_entry *mac;
@@ -1424,17 +1443,41 @@ static int sja1105_vlan_prepare(struct dsa_switch *ds, int port,
	return 0;
}

/* The TPID setting belongs to the General Parameters table,
 * which can only be partially reconfigured at runtime (and not the TPID).
 * So a switch reset is required.
 */
static int sja1105_vlan_filtering(struct dsa_switch *ds, int port, bool enabled)
{
	struct sja1105_general_params_entry *general_params;
	struct sja1105_private *priv = ds->priv;
	struct sja1105_table *table;
	u16 tpid, tpid2;
	int rc;

	if (enabled)
	if (enabled) {
		/* Enable VLAN filtering. */
		rc = sja1105_change_tpid(priv, ETH_P_8021Q, ETH_P_8021AD);
	else
		tpid  = ETH_P_8021AD;
		tpid2 = ETH_P_8021Q;
	} else {
		/* Disable VLAN filtering. */
		rc = sja1105_change_tpid(priv, ETH_P_SJA1105, ETH_P_SJA1105);
		tpid  = ETH_P_SJA1105;
		tpid2 = ETH_P_SJA1105;
	}

	table = &priv->static_config.tables[BLK_IDX_GENERAL_PARAMS];
	general_params = table->entries;
	/* EtherType used to identify outer tagged (S-tag) VLAN traffic */
	general_params->tpid = tpid;
	/* EtherType used to identify inner tagged (C-tag) VLAN traffic */
	general_params->tpid2 = tpid2;
	/* When VLAN filtering is on, we need to at least be able to
	 * decode management traffic through the "backup plan".
	 */
	general_params->incl_srcpt1 = enabled;
	general_params->incl_srcpt0 = enabled;

	rc = sja1105_static_config_reload(priv);
	if (rc)
		dev_err(ds->dev, "Failed to change VLAN Ethertype\n");

@@ -1523,6 +1566,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) {
@@ -1552,8 +1600,16 @@ static int sja1105_setup(struct dsa_switch *ds)
	return sja1105_setup_8021q_tagging(ds, true);
}

static void sja1105_teardown(struct dsa_switch *ds)
{
	struct sja1105_private *priv = ds->priv;

	cancel_work_sync(&priv->tagger_data.rxtstamp_work);
	skb_queue_purge(&priv->tagger_data.skb_rxtstamp_queue);
}

static int sja1105_mgmt_xmit(struct dsa_switch *ds, int port, int slot,
			     struct sk_buff *skb)
			     struct sk_buff *skb, bool takets)
{
	struct sja1105_mgmt_entry mgmt_route = {0};
	struct sja1105_private *priv = ds->priv;
@@ -1566,6 +1622,8 @@ static int sja1105_mgmt_xmit(struct dsa_switch *ds, int port, int slot,
	mgmt_route.macaddr = ether_addr_to_u64(hdr->h_dest);
	mgmt_route.destports = BIT(port);
	mgmt_route.enfport = 1;
	mgmt_route.tsreg = 0;
	mgmt_route.takets = takets;

	rc = sja1105_dynamic_config_write(priv, BLK_IDX_MGMT_ROUTE,
					  slot, &mgmt_route, true);
@@ -1617,7 +1675,11 @@ static netdev_tx_t sja1105_port_deferred_xmit(struct dsa_switch *ds, int port,
{
	struct sja1105_private *priv = ds->priv;
	struct sja1105_port *sp = &priv->ports[port];
	struct skb_shared_hwtstamps shwt = {0};
	int slot = sp->mgmt_slot;
	struct sk_buff *clone;
	u64 now, ts;
	int rc;

	/* The tragic fact about the switch having 4x2 slots for installing
	 * management routes is that all of them except one are actually
@@ -1635,8 +1697,36 @@ static netdev_tx_t sja1105_port_deferred_xmit(struct dsa_switch *ds, int port,
	 */
	mutex_lock(&priv->mgmt_lock);

	sja1105_mgmt_xmit(ds, port, slot, skb);
	/* The clone, if there, was made by dsa_skb_tx_timestamp */
	clone = DSA_SKB_CB(skb)->clone;

	sja1105_mgmt_xmit(ds, port, slot, skb, !!clone);

	if (!clone)
		goto out;

	skb_shinfo(clone)->tx_flags |= SKBTX_IN_PROGRESS;

	mutex_lock(&priv->ptp_lock);

	now = priv->tstamp_cc.read(&priv->tstamp_cc);

	rc = sja1105_ptpegr_ts_poll(priv, slot, &ts);
	if (rc < 0) {
		dev_err(ds->dev, "xmit: timed out polling for tstamp\n");
		kfree_skb(clone);
		goto out_unlock_ptp;
	}

	ts = sja1105_tstamp_reconstruct(priv, now, ts);
	ts = timecounter_cyc2time(&priv->tstamp_tc, ts);

	shwt.hwtstamp = ns_to_ktime(ts);
	skb_complete_tx_timestamp(clone, &shwt);

out_unlock_ptp:
	mutex_unlock(&priv->ptp_lock);
out:
	mutex_unlock(&priv->mgmt_lock);
	return NETDEV_TX_OK;
}
@@ -1665,15 +1755,178 @@ static int sja1105_set_ageing_time(struct dsa_switch *ds,
	return sja1105_static_config_reload(priv);
}

/* Caller must hold priv->tagger_data.meta_lock */
static int sja1105_change_rxtstamping(struct sja1105_private *priv,
				      bool on)
{
	struct sja1105_general_params_entry *general_params;
	struct sja1105_table *table;
	int rc;

	table = &priv->static_config.tables[BLK_IDX_GENERAL_PARAMS];
	general_params = table->entries;
	general_params->send_meta1 = on;
	general_params->send_meta0 = on;

	rc = sja1105_init_avb_params(priv, on);
	if (rc < 0)
		return rc;

	/* Initialize the meta state machine to a known state */
	if (priv->tagger_data.stampable_skb) {
		kfree_skb(priv->tagger_data.stampable_skb);
		priv->tagger_data.stampable_skb = NULL;
	}

	return sja1105_static_config_reload(priv);
}

static int sja1105_hwtstamp_set(struct dsa_switch *ds, int port,
				struct ifreq *ifr)
{
	struct sja1105_private *priv = ds->priv;
	struct hwtstamp_config config;
	bool rx_on;
	int rc;

	if (copy_from_user(&config, ifr->ifr_data, sizeof(config)))
		return -EFAULT;

	switch (config.tx_type) {
	case HWTSTAMP_TX_OFF:
		priv->ports[port].hwts_tx_en = false;
		break;
	case HWTSTAMP_TX_ON:
		priv->ports[port].hwts_tx_en = true;
		break;
	default:
		return -ERANGE;
	}

	switch (config.rx_filter) {
	case HWTSTAMP_FILTER_NONE:
		rx_on = false;
		break;
	default:
		rx_on = true;
		break;
	}

	if (rx_on != priv->tagger_data.hwts_rx_en) {
		spin_lock(&priv->tagger_data.meta_lock);
		rc = sja1105_change_rxtstamping(priv, rx_on);
		spin_unlock(&priv->tagger_data.meta_lock);
		if (rc < 0) {
			dev_err(ds->dev,
				"Failed to change RX timestamping: %d\n", rc);
			return -EFAULT;
		}
		priv->tagger_data.hwts_rx_en = rx_on;
	}

	if (copy_to_user(ifr->ifr_data, &config, sizeof(config)))
		return -EFAULT;
	return 0;
}

static int sja1105_hwtstamp_get(struct dsa_switch *ds, int port,
				struct ifreq *ifr)
{
	struct sja1105_private *priv = ds->priv;
	struct hwtstamp_config config;

	config.flags = 0;
	if (priv->ports[port].hwts_tx_en)
		config.tx_type = HWTSTAMP_TX_ON;
	else
		config.tx_type = HWTSTAMP_TX_OFF;
	if (priv->tagger_data.hwts_rx_en)
		config.rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT;
	else
		config.rx_filter = HWTSTAMP_FILTER_NONE;

	return copy_to_user(ifr->ifr_data, &config, sizeof(config)) ?
		-EFAULT : 0;
}

#define to_tagger(d) \
	container_of((d), struct sja1105_tagger_data, rxtstamp_work)
#define to_sja1105(d) \
	container_of((d), struct sja1105_private, tagger_data)

static void sja1105_rxtstamp_work(struct work_struct *work)
{
	struct sja1105_tagger_data *data = to_tagger(work);
	struct sja1105_private *priv = to_sja1105(data);
	struct sk_buff *skb;
	u64 now;

	mutex_lock(&priv->ptp_lock);

	now = priv->tstamp_cc.read(&priv->tstamp_cc);

	while ((skb = skb_dequeue(&data->skb_rxtstamp_queue)) != NULL) {
		struct skb_shared_hwtstamps *shwt = skb_hwtstamps(skb);
		u64 ts;

		*shwt = (struct skb_shared_hwtstamps) {0};

		ts = SJA1105_SKB_CB(skb)->meta_tstamp;
		ts = sja1105_tstamp_reconstruct(priv, now, ts);
		ts = timecounter_cyc2time(&priv->tstamp_tc, ts);

		shwt->hwtstamp = ns_to_ktime(ts);
		netif_rx_ni(skb);
	}

	mutex_unlock(&priv->ptp_lock);
}

/* Called from dsa_skb_defer_rx_timestamp */
bool sja1105_port_rxtstamp(struct dsa_switch *ds, int port,
			   struct sk_buff *skb, unsigned int type)
{
	struct sja1105_private *priv = ds->priv;
	struct sja1105_tagger_data *data = &priv->tagger_data;

	if (!data->hwts_rx_en)
		return false;

	/* We need to read the full PTP clock to reconstruct the Rx
	 * timestamp. For that we need a sleepable context.
	 */
	skb_queue_tail(&data->skb_rxtstamp_queue, skb);
	schedule_work(&data->rxtstamp_work);
	return true;
}

/* Called from dsa_skb_tx_timestamp. This callback is just to make DSA clone
 * the skb and have it available in DSA_SKB_CB in the .port_deferred_xmit
 * callback, where we will timestamp it synchronously.
 */
bool sja1105_port_txtstamp(struct dsa_switch *ds, int port,
			   struct sk_buff *skb, unsigned int type)
{
	struct sja1105_private *priv = ds->priv;
	struct sja1105_port *sp = &priv->ports[port];

	if (!sp->hwts_tx_en)
		return false;

	return true;
}

static const struct dsa_switch_ops sja1105_switch_ops = {
	.get_tag_protocol	= sja1105_get_tag_protocol,
	.setup			= sja1105_setup,
	.teardown		= sja1105_teardown,
	.set_ageing_time	= sja1105_set_ageing_time,
	.phylink_validate	= sja1105_phylink_validate,
	.phylink_mac_config	= sja1105_mac_config,
	.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,
@@ -1688,6 +1941,10 @@ static const struct dsa_switch_ops sja1105_switch_ops = {
	.port_mdb_add		= sja1105_mdb_add,
	.port_mdb_del		= sja1105_mdb_del,
	.port_deferred_xmit	= sja1105_port_deferred_xmit,
	.port_hwtstamp_get	= sja1105_hwtstamp_get,
	.port_hwtstamp_set	= sja1105_hwtstamp_set,
	.port_rxtstamp		= sja1105_port_rxtstamp,
	.port_txtstamp		= sja1105_port_txtstamp,
};

static int sja1105_check_device_id(struct sja1105_private *priv)
@@ -1728,6 +1985,7 @@ static int sja1105_check_device_id(struct sja1105_private *priv)

static int sja1105_probe(struct spi_device *spi)
{
	struct sja1105_tagger_data *tagger_data;
	struct device *dev = &spi->dev;
	struct sja1105_private *priv;
	struct dsa_switch *ds;
@@ -1782,12 +2040,17 @@ static int sja1105_probe(struct spi_device *spi)
	ds->priv = priv;
	priv->ds = ds;

	tagger_data = &priv->tagger_data;
	skb_queue_head_init(&tagger_data->skb_rxtstamp_queue);
	INIT_WORK(&tagger_data->rxtstamp_work, sja1105_rxtstamp_work);

	/* Connections between dsa_port and sja1105_port */
	for (i = 0; i < SJA1105_NUM_PORTS; i++) {
		struct sja1105_port *sp = &priv->ports[i];

		ds->ports[i].priv = sp;
		sp->dp = &ds->ports[i];
		sp->data = tagger_data;
	}
	mutex_init(&priv->mgmt_lock);

@@ -1798,6 +2061,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;
Loading