brintos

brintos / linux-shallow public Read only

0
0
Text · 53.3 KiB · beda1e9 Raw
2120 lines · c
1// SPDX-License-Identifier: (GPL-2.0 OR MIT)2/*3 * DSA driver for:4 * Hirschmann Hellcreek TSN switch.5 *6 * Copyright (C) 2019-2021 Linutronix GmbH7 * Author Kurt Kanzenbach <kurt@linutronix.de>8 */9 10#include <linux/kernel.h>11#include <linux/module.h>12#include <linux/device.h>13#include <linux/of.h>14#include <linux/of_mdio.h>15#include <linux/platform_device.h>16#include <linux/bitops.h>17#include <linux/if_bridge.h>18#include <linux/if_vlan.h>19#include <linux/etherdevice.h>20#include <linux/random.h>21#include <linux/iopoll.h>22#include <linux/mutex.h>23#include <linux/delay.h>24#include <net/dsa.h>25 26#include "hellcreek.h"27#include "hellcreek_ptp.h"28#include "hellcreek_hwtstamp.h"29 30static const struct hellcreek_counter hellcreek_counter[] = {31	{ 0x00, "RxFiltered", },32	{ 0x01, "RxOctets1k", },33	{ 0x02, "RxVTAG", },34	{ 0x03, "RxL2BAD", },35	{ 0x04, "RxOverloadDrop", },36	{ 0x05, "RxUC", },37	{ 0x06, "RxMC", },38	{ 0x07, "RxBC", },39	{ 0x08, "RxRS<64", },40	{ 0x09, "RxRS64", },41	{ 0x0a, "RxRS65_127", },42	{ 0x0b, "RxRS128_255", },43	{ 0x0c, "RxRS256_511", },44	{ 0x0d, "RxRS512_1023", },45	{ 0x0e, "RxRS1024_1518", },46	{ 0x0f, "RxRS>1518", },47	{ 0x10, "TxTailDropQueue0", },48	{ 0x11, "TxTailDropQueue1", },49	{ 0x12, "TxTailDropQueue2", },50	{ 0x13, "TxTailDropQueue3", },51	{ 0x14, "TxTailDropQueue4", },52	{ 0x15, "TxTailDropQueue5", },53	{ 0x16, "TxTailDropQueue6", },54	{ 0x17, "TxTailDropQueue7", },55	{ 0x18, "RxTrafficClass0", },56	{ 0x19, "RxTrafficClass1", },57	{ 0x1a, "RxTrafficClass2", },58	{ 0x1b, "RxTrafficClass3", },59	{ 0x1c, "RxTrafficClass4", },60	{ 0x1d, "RxTrafficClass5", },61	{ 0x1e, "RxTrafficClass6", },62	{ 0x1f, "RxTrafficClass7", },63	{ 0x21, "TxOctets1k", },64	{ 0x22, "TxVTAG", },65	{ 0x23, "TxL2BAD", },66	{ 0x25, "TxUC", },67	{ 0x26, "TxMC", },68	{ 0x27, "TxBC", },69	{ 0x28, "TxTS<64", },70	{ 0x29, "TxTS64", },71	{ 0x2a, "TxTS65_127", },72	{ 0x2b, "TxTS128_255", },73	{ 0x2c, "TxTS256_511", },74	{ 0x2d, "TxTS512_1023", },75	{ 0x2e, "TxTS1024_1518", },76	{ 0x2f, "TxTS>1518", },77	{ 0x30, "TxTrafficClassOverrun0", },78	{ 0x31, "TxTrafficClassOverrun1", },79	{ 0x32, "TxTrafficClassOverrun2", },80	{ 0x33, "TxTrafficClassOverrun3", },81	{ 0x34, "TxTrafficClassOverrun4", },82	{ 0x35, "TxTrafficClassOverrun5", },83	{ 0x36, "TxTrafficClassOverrun6", },84	{ 0x37, "TxTrafficClassOverrun7", },85	{ 0x38, "TxTrafficClass0", },86	{ 0x39, "TxTrafficClass1", },87	{ 0x3a, "TxTrafficClass2", },88	{ 0x3b, "TxTrafficClass3", },89	{ 0x3c, "TxTrafficClass4", },90	{ 0x3d, "TxTrafficClass5", },91	{ 0x3e, "TxTrafficClass6", },92	{ 0x3f, "TxTrafficClass7", },93};94 95static u16 hellcreek_read(struct hellcreek *hellcreek, unsigned int offset)96{97	return readw(hellcreek->base + offset);98}99 100static u16 hellcreek_read_ctrl(struct hellcreek *hellcreek)101{102	return readw(hellcreek->base + HR_CTRL_C);103}104 105static u16 hellcreek_read_stat(struct hellcreek *hellcreek)106{107	return readw(hellcreek->base + HR_SWSTAT);108}109 110static void hellcreek_write(struct hellcreek *hellcreek, u16 data,111			    unsigned int offset)112{113	writew(data, hellcreek->base + offset);114}115 116static void hellcreek_select_port(struct hellcreek *hellcreek, int port)117{118	u16 val = port << HR_PSEL_PTWSEL_SHIFT;119 120	hellcreek_write(hellcreek, val, HR_PSEL);121}122 123static void hellcreek_select_prio(struct hellcreek *hellcreek, int prio)124{125	u16 val = prio << HR_PSEL_PRTCWSEL_SHIFT;126 127	hellcreek_write(hellcreek, val, HR_PSEL);128}129 130static void hellcreek_select_port_prio(struct hellcreek *hellcreek, int port,131				       int prio)132{133	u16 val = port << HR_PSEL_PTWSEL_SHIFT;134 135	val |= prio << HR_PSEL_PRTCWSEL_SHIFT;136 137	hellcreek_write(hellcreek, val, HR_PSEL);138}139 140static void hellcreek_select_counter(struct hellcreek *hellcreek, int counter)141{142	u16 val = counter << HR_CSEL_SHIFT;143 144	hellcreek_write(hellcreek, val, HR_CSEL);145 146	/* Data sheet states to wait at least 20 internal clock cycles */147	ndelay(200);148}149 150static void hellcreek_select_vlan(struct hellcreek *hellcreek, int vid,151				  bool pvid)152{153	u16 val = 0;154 155	/* Set pvid bit first */156	if (pvid)157		val |= HR_VIDCFG_PVID;158	hellcreek_write(hellcreek, val, HR_VIDCFG);159 160	/* Set vlan */161	val |= vid << HR_VIDCFG_VID_SHIFT;162	hellcreek_write(hellcreek, val, HR_VIDCFG);163}164 165static void hellcreek_select_tgd(struct hellcreek *hellcreek, int port)166{167	u16 val = port << TR_TGDSEL_TDGSEL_SHIFT;168 169	hellcreek_write(hellcreek, val, TR_TGDSEL);170}171 172static int hellcreek_wait_until_ready(struct hellcreek *hellcreek)173{174	u16 val;175 176	/* Wait up to 1ms, although 3 us should be enough */177	return readx_poll_timeout(hellcreek_read_ctrl, hellcreek,178				  val, val & HR_CTRL_C_READY,179				  3, 1000);180}181 182static int hellcreek_wait_until_transitioned(struct hellcreek *hellcreek)183{184	u16 val;185 186	return readx_poll_timeout_atomic(hellcreek_read_ctrl, hellcreek,187					 val, !(val & HR_CTRL_C_TRANSITION),188					 1, 1000);189}190 191static int hellcreek_wait_fdb_ready(struct hellcreek *hellcreek)192{193	u16 val;194 195	return readx_poll_timeout_atomic(hellcreek_read_stat, hellcreek,196					 val, !(val & HR_SWSTAT_BUSY),197					 1, 1000);198}199 200static int hellcreek_detect(struct hellcreek *hellcreek)201{202	u16 id, rel_low, rel_high, date_low, date_high, tgd_ver;203	u8 tgd_maj, tgd_min;204	u32 rel, date;205 206	id	  = hellcreek_read(hellcreek, HR_MODID_C);207	rel_low	  = hellcreek_read(hellcreek, HR_REL_L_C);208	rel_high  = hellcreek_read(hellcreek, HR_REL_H_C);209	date_low  = hellcreek_read(hellcreek, HR_BLD_L_C);210	date_high = hellcreek_read(hellcreek, HR_BLD_H_C);211	tgd_ver   = hellcreek_read(hellcreek, TR_TGDVER);212 213	if (id != hellcreek->pdata->module_id)214		return -ENODEV;215 216	rel	= rel_low | (rel_high << 16);217	date	= date_low | (date_high << 16);218	tgd_maj = (tgd_ver & TR_TGDVER_REV_MAJ_MASK) >> TR_TGDVER_REV_MAJ_SHIFT;219	tgd_min = (tgd_ver & TR_TGDVER_REV_MIN_MASK) >> TR_TGDVER_REV_MIN_SHIFT;220 221	dev_info(hellcreek->dev, "Module ID=%02x Release=%04x Date=%04x TGD Version=%02x.%02x\n",222		 id, rel, date, tgd_maj, tgd_min);223 224	return 0;225}226 227static void hellcreek_feature_detect(struct hellcreek *hellcreek)228{229	u16 features;230 231	features = hellcreek_read(hellcreek, HR_FEABITS0);232 233	/* Only detect the size of the FDB table. The size and current234	 * utilization can be queried via devlink.235	 */236	hellcreek->fdb_entries = ((features & HR_FEABITS0_FDBBINS_MASK) >>237			       HR_FEABITS0_FDBBINS_SHIFT) * 32;238}239 240static enum dsa_tag_protocol hellcreek_get_tag_protocol(struct dsa_switch *ds,241							int port,242							enum dsa_tag_protocol mp)243{244	return DSA_TAG_PROTO_HELLCREEK;245}246 247static int hellcreek_port_enable(struct dsa_switch *ds, int port,248				 struct phy_device *phy)249{250	struct hellcreek *hellcreek = ds->priv;251	struct hellcreek_port *hellcreek_port;252	u16 val;253 254	hellcreek_port = &hellcreek->ports[port];255 256	dev_dbg(hellcreek->dev, "Enable port %d\n", port);257 258	mutex_lock(&hellcreek->reg_lock);259 260	hellcreek_select_port(hellcreek, port);261	val = hellcreek_port->ptcfg;262	val |= HR_PTCFG_ADMIN_EN;263	hellcreek_write(hellcreek, val, HR_PTCFG);264	hellcreek_port->ptcfg = val;265 266	mutex_unlock(&hellcreek->reg_lock);267 268	return 0;269}270 271static void hellcreek_port_disable(struct dsa_switch *ds, int port)272{273	struct hellcreek *hellcreek = ds->priv;274	struct hellcreek_port *hellcreek_port;275	u16 val;276 277	hellcreek_port = &hellcreek->ports[port];278 279	dev_dbg(hellcreek->dev, "Disable port %d\n", port);280 281	mutex_lock(&hellcreek->reg_lock);282 283	hellcreek_select_port(hellcreek, port);284	val = hellcreek_port->ptcfg;285	val &= ~HR_PTCFG_ADMIN_EN;286	hellcreek_write(hellcreek, val, HR_PTCFG);287	hellcreek_port->ptcfg = val;288 289	mutex_unlock(&hellcreek->reg_lock);290}291 292static void hellcreek_get_strings(struct dsa_switch *ds, int port,293				  u32 stringset, uint8_t *data)294{295	int i;296 297	for (i = 0; i < ARRAY_SIZE(hellcreek_counter); ++i) {298		const struct hellcreek_counter *counter = &hellcreek_counter[i];299 300		strscpy(data + i * ETH_GSTRING_LEN,301			counter->name, ETH_GSTRING_LEN);302	}303}304 305static int hellcreek_get_sset_count(struct dsa_switch *ds, int port, int sset)306{307	if (sset != ETH_SS_STATS)308		return 0;309 310	return ARRAY_SIZE(hellcreek_counter);311}312 313static void hellcreek_get_ethtool_stats(struct dsa_switch *ds, int port,314					uint64_t *data)315{316	struct hellcreek *hellcreek = ds->priv;317	struct hellcreek_port *hellcreek_port;318	int i;319 320	hellcreek_port = &hellcreek->ports[port];321 322	for (i = 0; i < ARRAY_SIZE(hellcreek_counter); ++i) {323		const struct hellcreek_counter *counter = &hellcreek_counter[i];324		u8 offset = counter->offset + port * 64;325		u16 high, low;326		u64 value;327 328		mutex_lock(&hellcreek->reg_lock);329 330		hellcreek_select_counter(hellcreek, offset);331 332		/* The registers are locked internally by selecting the333		 * counter. So low and high can be read without reading high334		 * again.335		 */336		high  = hellcreek_read(hellcreek, HR_CRDH);337		low   = hellcreek_read(hellcreek, HR_CRDL);338		value = ((u64)high << 16) | low;339 340		hellcreek_port->counter_values[i] += value;341		data[i] = hellcreek_port->counter_values[i];342 343		mutex_unlock(&hellcreek->reg_lock);344	}345}346 347static u16 hellcreek_private_vid(int port)348{349	return VLAN_N_VID - port + 1;350}351 352static int hellcreek_vlan_prepare(struct dsa_switch *ds, int port,353				  const struct switchdev_obj_port_vlan *vlan,354				  struct netlink_ext_ack *extack)355{356	struct hellcreek *hellcreek = ds->priv;357	int i;358 359	dev_dbg(hellcreek->dev, "VLAN prepare for port %d\n", port);360 361	/* Restriction: Make sure that nobody uses the "private" VLANs. These362	 * VLANs are internally used by the driver to ensure port363	 * separation. Thus, they cannot be used by someone else.364	 */365	for (i = 0; i < hellcreek->pdata->num_ports; ++i) {366		const u16 restricted_vid = hellcreek_private_vid(i);367 368		if (!dsa_is_user_port(ds, i))369			continue;370 371		if (vlan->vid == restricted_vid) {372			NL_SET_ERR_MSG_MOD(extack, "VID restricted by driver");373			return -EBUSY;374		}375	}376 377	return 0;378}379 380static void hellcreek_select_vlan_params(struct hellcreek *hellcreek, int port,381					 int *shift, int *mask)382{383	switch (port) {384	case 0:385		*shift = HR_VIDMBRCFG_P0MBR_SHIFT;386		*mask  = HR_VIDMBRCFG_P0MBR_MASK;387		break;388	case 1:389		*shift = HR_VIDMBRCFG_P1MBR_SHIFT;390		*mask  = HR_VIDMBRCFG_P1MBR_MASK;391		break;392	case 2:393		*shift = HR_VIDMBRCFG_P2MBR_SHIFT;394		*mask  = HR_VIDMBRCFG_P2MBR_MASK;395		break;396	case 3:397		*shift = HR_VIDMBRCFG_P3MBR_SHIFT;398		*mask  = HR_VIDMBRCFG_P3MBR_MASK;399		break;400	default:401		*shift = *mask = 0;402		dev_err(hellcreek->dev, "Unknown port %d selected!\n", port);403	}404}405 406static void hellcreek_apply_vlan(struct hellcreek *hellcreek, int port, u16 vid,407				 bool pvid, bool untagged)408{409	int shift, mask;410	u16 val;411 412	dev_dbg(hellcreek->dev, "Apply VLAN: port=%d vid=%u pvid=%d untagged=%d",413		port, vid, pvid, untagged);414 415	mutex_lock(&hellcreek->reg_lock);416 417	hellcreek_select_port(hellcreek, port);418	hellcreek_select_vlan(hellcreek, vid, pvid);419 420	/* Setup port vlan membership */421	hellcreek_select_vlan_params(hellcreek, port, &shift, &mask);422	val = hellcreek->vidmbrcfg[vid];423	val &= ~mask;424	if (untagged)425		val |= HELLCREEK_VLAN_UNTAGGED_MEMBER << shift;426	else427		val |= HELLCREEK_VLAN_TAGGED_MEMBER << shift;428 429	hellcreek_write(hellcreek, val, HR_VIDMBRCFG);430	hellcreek->vidmbrcfg[vid] = val;431 432	mutex_unlock(&hellcreek->reg_lock);433}434 435static void hellcreek_unapply_vlan(struct hellcreek *hellcreek, int port,436				   u16 vid)437{438	int shift, mask;439	u16 val;440 441	dev_dbg(hellcreek->dev, "Unapply VLAN: port=%d vid=%u\n", port, vid);442 443	mutex_lock(&hellcreek->reg_lock);444 445	hellcreek_select_vlan(hellcreek, vid, false);446 447	/* Setup port vlan membership */448	hellcreek_select_vlan_params(hellcreek, port, &shift, &mask);449	val = hellcreek->vidmbrcfg[vid];450	val &= ~mask;451	val |= HELLCREEK_VLAN_NO_MEMBER << shift;452 453	hellcreek_write(hellcreek, val, HR_VIDMBRCFG);454	hellcreek->vidmbrcfg[vid] = val;455 456	mutex_unlock(&hellcreek->reg_lock);457}458 459static int hellcreek_vlan_add(struct dsa_switch *ds, int port,460			      const struct switchdev_obj_port_vlan *vlan,461			      struct netlink_ext_ack *extack)462{463	bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;464	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;465	struct hellcreek *hellcreek = ds->priv;466	int err;467 468	err = hellcreek_vlan_prepare(ds, port, vlan, extack);469	if (err)470		return err;471 472	dev_dbg(hellcreek->dev, "Add VLAN %d on port %d, %s, %s\n",473		vlan->vid, port, untagged ? "untagged" : "tagged",474		pvid ? "PVID" : "no PVID");475 476	hellcreek_apply_vlan(hellcreek, port, vlan->vid, pvid, untagged);477 478	return 0;479}480 481static int hellcreek_vlan_del(struct dsa_switch *ds, int port,482			      const struct switchdev_obj_port_vlan *vlan)483{484	struct hellcreek *hellcreek = ds->priv;485 486	dev_dbg(hellcreek->dev, "Remove VLAN %d on port %d\n", vlan->vid, port);487 488	hellcreek_unapply_vlan(hellcreek, port, vlan->vid);489 490	return 0;491}492 493static void hellcreek_port_stp_state_set(struct dsa_switch *ds, int port,494					 u8 state)495{496	struct hellcreek *hellcreek = ds->priv;497	struct hellcreek_port *hellcreek_port;498	const char *new_state;499	u16 val;500 501	mutex_lock(&hellcreek->reg_lock);502 503	hellcreek_port = &hellcreek->ports[port];504	val = hellcreek_port->ptcfg;505 506	switch (state) {507	case BR_STATE_DISABLED:508		new_state = "DISABLED";509		val |= HR_PTCFG_BLOCKED;510		val &= ~HR_PTCFG_LEARNING_EN;511		break;512	case BR_STATE_BLOCKING:513		new_state = "BLOCKING";514		val |= HR_PTCFG_BLOCKED;515		val &= ~HR_PTCFG_LEARNING_EN;516		break;517	case BR_STATE_LISTENING:518		new_state = "LISTENING";519		val |= HR_PTCFG_BLOCKED;520		val &= ~HR_PTCFG_LEARNING_EN;521		break;522	case BR_STATE_LEARNING:523		new_state = "LEARNING";524		val |= HR_PTCFG_BLOCKED;525		val |= HR_PTCFG_LEARNING_EN;526		break;527	case BR_STATE_FORWARDING:528		new_state = "FORWARDING";529		val &= ~HR_PTCFG_BLOCKED;530		val |= HR_PTCFG_LEARNING_EN;531		break;532	default:533		new_state = "UNKNOWN";534	}535 536	hellcreek_select_port(hellcreek, port);537	hellcreek_write(hellcreek, val, HR_PTCFG);538	hellcreek_port->ptcfg = val;539 540	mutex_unlock(&hellcreek->reg_lock);541 542	dev_dbg(hellcreek->dev, "Configured STP state for port %d: %s\n",543		port, new_state);544}545 546static void hellcreek_setup_ingressflt(struct hellcreek *hellcreek, int port,547				       bool enable)548{549	struct hellcreek_port *hellcreek_port = &hellcreek->ports[port];550	u16 ptcfg;551 552	mutex_lock(&hellcreek->reg_lock);553 554	ptcfg = hellcreek_port->ptcfg;555 556	if (enable)557		ptcfg |= HR_PTCFG_INGRESSFLT;558	else559		ptcfg &= ~HR_PTCFG_INGRESSFLT;560 561	hellcreek_select_port(hellcreek, port);562	hellcreek_write(hellcreek, ptcfg, HR_PTCFG);563	hellcreek_port->ptcfg = ptcfg;564 565	mutex_unlock(&hellcreek->reg_lock);566}567 568static void hellcreek_setup_vlan_awareness(struct hellcreek *hellcreek,569					   bool enable)570{571	u16 swcfg;572 573	mutex_lock(&hellcreek->reg_lock);574 575	swcfg = hellcreek->swcfg;576 577	if (enable)578		swcfg |= HR_SWCFG_VLAN_UNAWARE;579	else580		swcfg &= ~HR_SWCFG_VLAN_UNAWARE;581 582	hellcreek_write(hellcreek, swcfg, HR_SWCFG);583 584	mutex_unlock(&hellcreek->reg_lock);585}586 587/* Default setup for DSA: VLAN <X>: CPU and Port <X> egress untagged. */588static void hellcreek_setup_vlan_membership(struct dsa_switch *ds, int port,589					    bool enabled)590{591	const u16 vid = hellcreek_private_vid(port);592	int upstream = dsa_upstream_port(ds, port);593	struct hellcreek *hellcreek = ds->priv;594 595	/* Apply vid to port as egress untagged and port vlan id */596	if (enabled)597		hellcreek_apply_vlan(hellcreek, port, vid, true, true);598	else599		hellcreek_unapply_vlan(hellcreek, port, vid);600 601	/* Apply vid to cpu port as well */602	if (enabled)603		hellcreek_apply_vlan(hellcreek, upstream, vid, false, true);604	else605		hellcreek_unapply_vlan(hellcreek, upstream, vid);606}607 608static void hellcreek_port_set_ucast_flood(struct hellcreek *hellcreek,609					   int port, bool enable)610{611	struct hellcreek_port *hellcreek_port;612	u16 val;613 614	hellcreek_port = &hellcreek->ports[port];615 616	dev_dbg(hellcreek->dev, "%s unicast flooding on port %d\n",617		enable ? "Enable" : "Disable", port);618 619	mutex_lock(&hellcreek->reg_lock);620 621	hellcreek_select_port(hellcreek, port);622	val = hellcreek_port->ptcfg;623	if (enable)624		val &= ~HR_PTCFG_UUC_FLT;625	else626		val |= HR_PTCFG_UUC_FLT;627	hellcreek_write(hellcreek, val, HR_PTCFG);628	hellcreek_port->ptcfg = val;629 630	mutex_unlock(&hellcreek->reg_lock);631}632 633static void hellcreek_port_set_mcast_flood(struct hellcreek *hellcreek,634					   int port, bool enable)635{636	struct hellcreek_port *hellcreek_port;637	u16 val;638 639	hellcreek_port = &hellcreek->ports[port];640 641	dev_dbg(hellcreek->dev, "%s multicast flooding on port %d\n",642		enable ? "Enable" : "Disable", port);643 644	mutex_lock(&hellcreek->reg_lock);645 646	hellcreek_select_port(hellcreek, port);647	val = hellcreek_port->ptcfg;648	if (enable)649		val &= ~HR_PTCFG_UMC_FLT;650	else651		val |= HR_PTCFG_UMC_FLT;652	hellcreek_write(hellcreek, val, HR_PTCFG);653	hellcreek_port->ptcfg = val;654 655	mutex_unlock(&hellcreek->reg_lock);656}657 658static int hellcreek_pre_bridge_flags(struct dsa_switch *ds, int port,659				      struct switchdev_brport_flags flags,660				      struct netlink_ext_ack *extack)661{662	if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD))663		return -EINVAL;664 665	return 0;666}667 668static int hellcreek_bridge_flags(struct dsa_switch *ds, int port,669				  struct switchdev_brport_flags flags,670				  struct netlink_ext_ack *extack)671{672	struct hellcreek *hellcreek = ds->priv;673 674	if (flags.mask & BR_FLOOD)675		hellcreek_port_set_ucast_flood(hellcreek, port,676					       !!(flags.val & BR_FLOOD));677 678	if (flags.mask & BR_MCAST_FLOOD)679		hellcreek_port_set_mcast_flood(hellcreek, port,680					       !!(flags.val & BR_MCAST_FLOOD));681 682	return 0;683}684 685static int hellcreek_port_bridge_join(struct dsa_switch *ds, int port,686				      struct dsa_bridge bridge,687				      bool *tx_fwd_offload,688				      struct netlink_ext_ack *extack)689{690	struct hellcreek *hellcreek = ds->priv;691 692	dev_dbg(hellcreek->dev, "Port %d joins a bridge\n", port);693 694	/* When joining a vlan_filtering bridge, keep the switch VLAN aware */695	if (!ds->vlan_filtering)696		hellcreek_setup_vlan_awareness(hellcreek, false);697 698	/* Drop private vlans */699	hellcreek_setup_vlan_membership(ds, port, false);700 701	return 0;702}703 704static void hellcreek_port_bridge_leave(struct dsa_switch *ds, int port,705					struct dsa_bridge bridge)706{707	struct hellcreek *hellcreek = ds->priv;708 709	dev_dbg(hellcreek->dev, "Port %d leaves a bridge\n", port);710 711	/* Enable VLAN awareness */712	hellcreek_setup_vlan_awareness(hellcreek, true);713 714	/* Enable private vlans */715	hellcreek_setup_vlan_membership(ds, port, true);716}717 718static int __hellcreek_fdb_add(struct hellcreek *hellcreek,719			       const struct hellcreek_fdb_entry *entry)720{721	u16 meta = 0;722 723	dev_dbg(hellcreek->dev, "Add static FDB entry: MAC=%pM, MASK=0x%02x, "724		"OBT=%d, PASS_BLOCKED=%d, REPRIO_EN=%d, PRIO=%d\n", entry->mac,725		entry->portmask, entry->is_obt, entry->pass_blocked,726		entry->reprio_en, entry->reprio_tc);727 728	/* Add mac address */729	hellcreek_write(hellcreek, entry->mac[1] | (entry->mac[0] << 8), HR_FDBWDH);730	hellcreek_write(hellcreek, entry->mac[3] | (entry->mac[2] << 8), HR_FDBWDM);731	hellcreek_write(hellcreek, entry->mac[5] | (entry->mac[4] << 8), HR_FDBWDL);732 733	/* Meta data */734	meta |= entry->portmask << HR_FDBWRM0_PORTMASK_SHIFT;735	if (entry->is_obt)736		meta |= HR_FDBWRM0_OBT;737	if (entry->pass_blocked)738		meta |= HR_FDBWRM0_PASS_BLOCKED;739	if (entry->reprio_en) {740		meta |= HR_FDBWRM0_REPRIO_EN;741		meta |= entry->reprio_tc << HR_FDBWRM0_REPRIO_TC_SHIFT;742	}743	hellcreek_write(hellcreek, meta, HR_FDBWRM0);744 745	/* Commit */746	hellcreek_write(hellcreek, 0x00, HR_FDBWRCMD);747 748	/* Wait until done */749	return hellcreek_wait_fdb_ready(hellcreek);750}751 752static int __hellcreek_fdb_del(struct hellcreek *hellcreek,753			       const struct hellcreek_fdb_entry *entry)754{755	dev_dbg(hellcreek->dev, "Delete FDB entry: MAC=%pM!\n", entry->mac);756 757	/* Delete by matching idx */758	hellcreek_write(hellcreek, entry->idx | HR_FDBWRCMD_FDBDEL, HR_FDBWRCMD);759 760	/* Wait until done */761	return hellcreek_wait_fdb_ready(hellcreek);762}763 764static void hellcreek_populate_fdb_entry(struct hellcreek *hellcreek,765					 struct hellcreek_fdb_entry *entry,766					 size_t idx)767{768	unsigned char addr[ETH_ALEN];769	u16 meta, mac;770 771	/* Read values */772	meta	= hellcreek_read(hellcreek, HR_FDBMDRD);773	mac	= hellcreek_read(hellcreek, HR_FDBRDL);774	addr[5] = mac & 0xff;775	addr[4] = (mac & 0xff00) >> 8;776	mac	= hellcreek_read(hellcreek, HR_FDBRDM);777	addr[3] = mac & 0xff;778	addr[2] = (mac & 0xff00) >> 8;779	mac	= hellcreek_read(hellcreek, HR_FDBRDH);780	addr[1] = mac & 0xff;781	addr[0] = (mac & 0xff00) >> 8;782 783	/* Populate @entry */784	memcpy(entry->mac, addr, sizeof(addr));785	entry->idx	    = idx;786	entry->portmask	    = (meta & HR_FDBMDRD_PORTMASK_MASK) >>787		HR_FDBMDRD_PORTMASK_SHIFT;788	entry->age	    = (meta & HR_FDBMDRD_AGE_MASK) >>789		HR_FDBMDRD_AGE_SHIFT;790	entry->is_obt	    = !!(meta & HR_FDBMDRD_OBT);791	entry->pass_blocked = !!(meta & HR_FDBMDRD_PASS_BLOCKED);792	entry->is_static    = !!(meta & HR_FDBMDRD_STATIC);793	entry->reprio_tc    = (meta & HR_FDBMDRD_REPRIO_TC_MASK) >>794		HR_FDBMDRD_REPRIO_TC_SHIFT;795	entry->reprio_en    = !!(meta & HR_FDBMDRD_REPRIO_EN);796}797 798/* Retrieve the index of a FDB entry by mac address. Currently we search through799 * the complete table in hardware. If that's too slow, we might have to cache800 * the complete FDB table in software.801 */802static int hellcreek_fdb_get(struct hellcreek *hellcreek,803			     const unsigned char *dest,804			     struct hellcreek_fdb_entry *entry)805{806	size_t i;807 808	/* Set read pointer to zero: The read of HR_FDBMAX (read-only register)809	 * should reset the internal pointer. But, that doesn't work. The vendor810	 * suggested a subsequent write as workaround. Same for HR_FDBRDH below.811	 */812	hellcreek_read(hellcreek, HR_FDBMAX);813	hellcreek_write(hellcreek, 0x00, HR_FDBMAX);814 815	/* We have to read the complete table, because the switch/driver might816	 * enter new entries anywhere.817	 */818	for (i = 0; i < hellcreek->fdb_entries; ++i) {819		struct hellcreek_fdb_entry tmp = { 0 };820 821		/* Read entry */822		hellcreek_populate_fdb_entry(hellcreek, &tmp, i);823 824		/* Force next entry */825		hellcreek_write(hellcreek, 0x00, HR_FDBRDH);826 827		if (memcmp(tmp.mac, dest, ETH_ALEN))828			continue;829 830		/* Match found */831		memcpy(entry, &tmp, sizeof(*entry));832 833		return 0;834	}835 836	return -ENOENT;837}838 839static int hellcreek_fdb_add(struct dsa_switch *ds, int port,840			     const unsigned char *addr, u16 vid,841			     struct dsa_db db)842{843	struct hellcreek_fdb_entry entry = { 0 };844	struct hellcreek *hellcreek = ds->priv;845	int ret;846 847	dev_dbg(hellcreek->dev, "Add FDB entry for MAC=%pM\n", addr);848 849	mutex_lock(&hellcreek->reg_lock);850 851	ret = hellcreek_fdb_get(hellcreek, addr, &entry);852	if (ret) {853		/* Not found */854		memcpy(entry.mac, addr, sizeof(entry.mac));855		entry.portmask = BIT(port);856 857		ret = __hellcreek_fdb_add(hellcreek, &entry);858		if (ret) {859			dev_err(hellcreek->dev, "Failed to add FDB entry!\n");860			goto out;861		}862	} else {863		/* Found */864		ret = __hellcreek_fdb_del(hellcreek, &entry);865		if (ret) {866			dev_err(hellcreek->dev, "Failed to delete FDB entry!\n");867			goto out;868		}869 870		entry.portmask |= BIT(port);871 872		ret = __hellcreek_fdb_add(hellcreek, &entry);873		if (ret) {874			dev_err(hellcreek->dev, "Failed to add FDB entry!\n");875			goto out;876		}877	}878 879out:880	mutex_unlock(&hellcreek->reg_lock);881 882	return ret;883}884 885static int hellcreek_fdb_del(struct dsa_switch *ds, int port,886			     const unsigned char *addr, u16 vid,887			     struct dsa_db db)888{889	struct hellcreek_fdb_entry entry = { 0 };890	struct hellcreek *hellcreek = ds->priv;891	int ret;892 893	dev_dbg(hellcreek->dev, "Delete FDB entry for MAC=%pM\n", addr);894 895	mutex_lock(&hellcreek->reg_lock);896 897	ret = hellcreek_fdb_get(hellcreek, addr, &entry);898	if (ret) {899		/* Not found */900		dev_err(hellcreek->dev, "FDB entry for deletion not found!\n");901	} else {902		/* Found */903		ret = __hellcreek_fdb_del(hellcreek, &entry);904		if (ret) {905			dev_err(hellcreek->dev, "Failed to delete FDB entry!\n");906			goto out;907		}908 909		entry.portmask &= ~BIT(port);910 911		if (entry.portmask != 0x00) {912			ret = __hellcreek_fdb_add(hellcreek, &entry);913			if (ret) {914				dev_err(hellcreek->dev, "Failed to add FDB entry!\n");915				goto out;916			}917		}918	}919 920out:921	mutex_unlock(&hellcreek->reg_lock);922 923	return ret;924}925 926static int hellcreek_fdb_dump(struct dsa_switch *ds, int port,927			      dsa_fdb_dump_cb_t *cb, void *data)928{929	struct hellcreek *hellcreek = ds->priv;930	u16 entries;931	int ret = 0;932	size_t i;933 934	mutex_lock(&hellcreek->reg_lock);935 936	/* Set read pointer to zero: The read of HR_FDBMAX (read-only register)937	 * should reset the internal pointer. But, that doesn't work. The vendor938	 * suggested a subsequent write as workaround. Same for HR_FDBRDH below.939	 */940	entries = hellcreek_read(hellcreek, HR_FDBMAX);941	hellcreek_write(hellcreek, 0x00, HR_FDBMAX);942 943	dev_dbg(hellcreek->dev, "FDB dump for port %d, entries=%d!\n", port, entries);944 945	/* Read table */946	for (i = 0; i < hellcreek->fdb_entries; ++i) {947		struct hellcreek_fdb_entry entry = { 0 };948 949		/* Read entry */950		hellcreek_populate_fdb_entry(hellcreek, &entry, i);951 952		/* Force next entry */953		hellcreek_write(hellcreek, 0x00, HR_FDBRDH);954 955		/* Check valid */956		if (is_zero_ether_addr(entry.mac))957			continue;958 959		/* Check port mask */960		if (!(entry.portmask & BIT(port)))961			continue;962 963		ret = cb(entry.mac, 0, entry.is_static, data);964		if (ret)965			break;966	}967 968	mutex_unlock(&hellcreek->reg_lock);969 970	return ret;971}972 973static int hellcreek_vlan_filtering(struct dsa_switch *ds, int port,974				    bool vlan_filtering,975				    struct netlink_ext_ack *extack)976{977	struct hellcreek *hellcreek = ds->priv;978 979	dev_dbg(hellcreek->dev, "%s VLAN filtering on port %d\n",980		vlan_filtering ? "Enable" : "Disable", port);981 982	/* Configure port to drop packages with not known vids */983	hellcreek_setup_ingressflt(hellcreek, port, vlan_filtering);984 985	/* Enable VLAN awareness on the switch. This save due to986	 * ds->vlan_filtering_is_global.987	 */988	hellcreek_setup_vlan_awareness(hellcreek, vlan_filtering);989 990	return 0;991}992 993static int hellcreek_enable_ip_core(struct hellcreek *hellcreek)994{995	int ret;996	u16 val;997 998	mutex_lock(&hellcreek->reg_lock);999 1000	val = hellcreek_read(hellcreek, HR_CTRL_C);1001	val |= HR_CTRL_C_ENABLE;1002	hellcreek_write(hellcreek, val, HR_CTRL_C);1003	ret = hellcreek_wait_until_transitioned(hellcreek);1004 1005	mutex_unlock(&hellcreek->reg_lock);1006 1007	return ret;1008}1009 1010static void hellcreek_setup_cpu_and_tunnel_port(struct hellcreek *hellcreek)1011{1012	struct hellcreek_port *tunnel_port = &hellcreek->ports[TUNNEL_PORT];1013	struct hellcreek_port *cpu_port = &hellcreek->ports[CPU_PORT];1014	u16 ptcfg = 0;1015 1016	ptcfg |= HR_PTCFG_LEARNING_EN | HR_PTCFG_ADMIN_EN;1017 1018	mutex_lock(&hellcreek->reg_lock);1019 1020	hellcreek_select_port(hellcreek, CPU_PORT);1021	hellcreek_write(hellcreek, ptcfg, HR_PTCFG);1022 1023	hellcreek_select_port(hellcreek, TUNNEL_PORT);1024	hellcreek_write(hellcreek, ptcfg, HR_PTCFG);1025 1026	cpu_port->ptcfg	   = ptcfg;1027	tunnel_port->ptcfg = ptcfg;1028 1029	mutex_unlock(&hellcreek->reg_lock);1030}1031 1032static void hellcreek_setup_tc_identity_mapping(struct hellcreek *hellcreek)1033{1034	int i;1035 1036	/* The switch has multiple egress queues per port. The queue is selected1037	 * via the PCP field in the VLAN header. The switch internally deals1038	 * with traffic classes instead of PCP values and this mapping is1039	 * configurable.1040	 *1041	 * The default mapping is (PCP - TC):1042	 *  7 - 71043	 *  6 - 61044	 *  5 - 51045	 *  4 - 41046	 *  3 - 31047	 *  2 - 11048	 *  1 - 01049	 *  0 - 21050	 *1051	 * The default should be an identity mapping.1052	 */1053 1054	for (i = 0; i < 8; ++i) {1055		mutex_lock(&hellcreek->reg_lock);1056 1057		hellcreek_select_prio(hellcreek, i);1058		hellcreek_write(hellcreek,1059				i << HR_PRTCCFG_PCP_TC_MAP_SHIFT,1060				HR_PRTCCFG);1061 1062		mutex_unlock(&hellcreek->reg_lock);1063	}1064}1065 1066static int hellcreek_setup_fdb(struct hellcreek *hellcreek)1067{1068	static struct hellcreek_fdb_entry l2_ptp = {1069		/* MAC: 01-1B-19-00-00-00 */1070		.mac	      = { 0x01, 0x1b, 0x19, 0x00, 0x00, 0x00 },1071		.portmask     = 0x03,	/* Management ports */1072		.age	      = 0,1073		.is_obt	      = 0,1074		.pass_blocked = 0,1075		.is_static    = 1,1076		.reprio_tc    = 6,	/* TC: 6 as per IEEE 802.1AS */1077		.reprio_en    = 1,1078	};1079	static struct hellcreek_fdb_entry udp4_ptp = {1080		/* MAC: 01-00-5E-00-01-81 */1081		.mac	      = { 0x01, 0x00, 0x5e, 0x00, 0x01, 0x81 },1082		.portmask     = 0x03,	/* Management ports */1083		.age	      = 0,1084		.is_obt	      = 0,1085		.pass_blocked = 0,1086		.is_static    = 1,1087		.reprio_tc    = 6,1088		.reprio_en    = 1,1089	};1090	static struct hellcreek_fdb_entry udp6_ptp = {1091		/* MAC: 33-33-00-00-01-81 */1092		.mac	      = { 0x33, 0x33, 0x00, 0x00, 0x01, 0x81 },1093		.portmask     = 0x03,	/* Management ports */1094		.age	      = 0,1095		.is_obt	      = 0,1096		.pass_blocked = 0,1097		.is_static    = 1,1098		.reprio_tc    = 6,1099		.reprio_en    = 1,1100	};1101	static struct hellcreek_fdb_entry l2_p2p = {1102		/* MAC: 01-80-C2-00-00-0E */1103		.mac	      = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e },1104		.portmask     = 0x03,	/* Management ports */1105		.age	      = 0,1106		.is_obt	      = 0,1107		.pass_blocked = 1,1108		.is_static    = 1,1109		.reprio_tc    = 6,	/* TC: 6 as per IEEE 802.1AS */1110		.reprio_en    = 1,1111	};1112	static struct hellcreek_fdb_entry udp4_p2p = {1113		/* MAC: 01-00-5E-00-00-6B */1114		.mac	      = { 0x01, 0x00, 0x5e, 0x00, 0x00, 0x6b },1115		.portmask     = 0x03,	/* Management ports */1116		.age	      = 0,1117		.is_obt	      = 0,1118		.pass_blocked = 1,1119		.is_static    = 1,1120		.reprio_tc    = 6,1121		.reprio_en    = 1,1122	};1123	static struct hellcreek_fdb_entry udp6_p2p = {1124		/* MAC: 33-33-00-00-00-6B */1125		.mac	      = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x6b },1126		.portmask     = 0x03,	/* Management ports */1127		.age	      = 0,1128		.is_obt	      = 0,1129		.pass_blocked = 1,1130		.is_static    = 1,1131		.reprio_tc    = 6,1132		.reprio_en    = 1,1133	};1134	static struct hellcreek_fdb_entry stp = {1135		/* MAC: 01-80-C2-00-00-00 */1136		.mac	      = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 },1137		.portmask     = 0x03,	/* Management ports */1138		.age	      = 0,1139		.is_obt	      = 0,1140		.pass_blocked = 1,1141		.is_static    = 1,1142		.reprio_tc    = 6,1143		.reprio_en    = 1,1144	};1145	int ret;1146 1147	mutex_lock(&hellcreek->reg_lock);1148	ret = __hellcreek_fdb_add(hellcreek, &l2_ptp);1149	if (ret)1150		goto out;1151	ret = __hellcreek_fdb_add(hellcreek, &udp4_ptp);1152	if (ret)1153		goto out;1154	ret = __hellcreek_fdb_add(hellcreek, &udp6_ptp);1155	if (ret)1156		goto out;1157	ret = __hellcreek_fdb_add(hellcreek, &l2_p2p);1158	if (ret)1159		goto out;1160	ret = __hellcreek_fdb_add(hellcreek, &udp4_p2p);1161	if (ret)1162		goto out;1163	ret = __hellcreek_fdb_add(hellcreek, &udp6_p2p);1164	if (ret)1165		goto out;1166	ret = __hellcreek_fdb_add(hellcreek, &stp);1167out:1168	mutex_unlock(&hellcreek->reg_lock);1169 1170	return ret;1171}1172 1173static int hellcreek_devlink_info_get(struct dsa_switch *ds,1174				      struct devlink_info_req *req,1175				      struct netlink_ext_ack *extack)1176{1177	struct hellcreek *hellcreek = ds->priv;1178 1179	return devlink_info_version_fixed_put(req,1180					      DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,1181					      hellcreek->pdata->name);1182}1183 1184static u64 hellcreek_devlink_vlan_table_get(void *priv)1185{1186	struct hellcreek *hellcreek = priv;1187	u64 count = 0;1188	int i;1189 1190	mutex_lock(&hellcreek->reg_lock);1191	for (i = 0; i < VLAN_N_VID; ++i)1192		if (hellcreek->vidmbrcfg[i])1193			count++;1194	mutex_unlock(&hellcreek->reg_lock);1195 1196	return count;1197}1198 1199static u64 hellcreek_devlink_fdb_table_get(void *priv)1200{1201	struct hellcreek *hellcreek = priv;1202	u64 count = 0;1203 1204	/* Reading this register has side effects. Synchronize against the other1205	 * FDB operations.1206	 */1207	mutex_lock(&hellcreek->reg_lock);1208	count = hellcreek_read(hellcreek, HR_FDBMAX);1209	mutex_unlock(&hellcreek->reg_lock);1210 1211	return count;1212}1213 1214static int hellcreek_setup_devlink_resources(struct dsa_switch *ds)1215{1216	struct devlink_resource_size_params size_vlan_params;1217	struct devlink_resource_size_params size_fdb_params;1218	struct hellcreek *hellcreek = ds->priv;1219	int err;1220 1221	devlink_resource_size_params_init(&size_vlan_params, VLAN_N_VID,1222					  VLAN_N_VID,1223					  1, DEVLINK_RESOURCE_UNIT_ENTRY);1224 1225	devlink_resource_size_params_init(&size_fdb_params,1226					  hellcreek->fdb_entries,1227					  hellcreek->fdb_entries,1228					  1, DEVLINK_RESOURCE_UNIT_ENTRY);1229 1230	err = dsa_devlink_resource_register(ds, "VLAN", VLAN_N_VID,1231					    HELLCREEK_DEVLINK_PARAM_ID_VLAN_TABLE,1232					    DEVLINK_RESOURCE_ID_PARENT_TOP,1233					    &size_vlan_params);1234	if (err)1235		goto out;1236 1237	err = dsa_devlink_resource_register(ds, "FDB", hellcreek->fdb_entries,1238					    HELLCREEK_DEVLINK_PARAM_ID_FDB_TABLE,1239					    DEVLINK_RESOURCE_ID_PARENT_TOP,1240					    &size_fdb_params);1241	if (err)1242		goto out;1243 1244	dsa_devlink_resource_occ_get_register(ds,1245					      HELLCREEK_DEVLINK_PARAM_ID_VLAN_TABLE,1246					      hellcreek_devlink_vlan_table_get,1247					      hellcreek);1248 1249	dsa_devlink_resource_occ_get_register(ds,1250					      HELLCREEK_DEVLINK_PARAM_ID_FDB_TABLE,1251					      hellcreek_devlink_fdb_table_get,1252					      hellcreek);1253 1254	return 0;1255 1256out:1257	dsa_devlink_resources_unregister(ds);1258 1259	return err;1260}1261 1262static int hellcreek_devlink_region_vlan_snapshot(struct devlink *dl,1263						  const struct devlink_region_ops *ops,1264						  struct netlink_ext_ack *extack,1265						  u8 **data)1266{1267	struct hellcreek_devlink_vlan_entry *table, *entry;1268	struct dsa_switch *ds = dsa_devlink_to_ds(dl);1269	struct hellcreek *hellcreek = ds->priv;1270	int i;1271 1272	table = kcalloc(VLAN_N_VID, sizeof(*entry), GFP_KERNEL);1273	if (!table)1274		return -ENOMEM;1275 1276	entry = table;1277 1278	mutex_lock(&hellcreek->reg_lock);1279	for (i = 0; i < VLAN_N_VID; ++i, ++entry) {1280		entry->member = hellcreek->vidmbrcfg[i];1281		entry->vid    = i;1282	}1283	mutex_unlock(&hellcreek->reg_lock);1284 1285	*data = (u8 *)table;1286 1287	return 0;1288}1289 1290static int hellcreek_devlink_region_fdb_snapshot(struct devlink *dl,1291						 const struct devlink_region_ops *ops,1292						 struct netlink_ext_ack *extack,1293						 u8 **data)1294{1295	struct dsa_switch *ds = dsa_devlink_to_ds(dl);1296	struct hellcreek_fdb_entry *table, *entry;1297	struct hellcreek *hellcreek = ds->priv;1298	size_t i;1299 1300	table = kcalloc(hellcreek->fdb_entries, sizeof(*entry), GFP_KERNEL);1301	if (!table)1302		return -ENOMEM;1303 1304	entry = table;1305 1306	mutex_lock(&hellcreek->reg_lock);1307 1308	/* Start table read */1309	hellcreek_read(hellcreek, HR_FDBMAX);1310	hellcreek_write(hellcreek, 0x00, HR_FDBMAX);1311 1312	for (i = 0; i < hellcreek->fdb_entries; ++i, ++entry) {1313		/* Read current entry */1314		hellcreek_populate_fdb_entry(hellcreek, entry, i);1315 1316		/* Advance read pointer */1317		hellcreek_write(hellcreek, 0x00, HR_FDBRDH);1318	}1319 1320	mutex_unlock(&hellcreek->reg_lock);1321 1322	*data = (u8 *)table;1323 1324	return 0;1325}1326 1327static struct devlink_region_ops hellcreek_region_vlan_ops = {1328	.name	    = "vlan",1329	.snapshot   = hellcreek_devlink_region_vlan_snapshot,1330	.destructor = kfree,1331};1332 1333static struct devlink_region_ops hellcreek_region_fdb_ops = {1334	.name	    = "fdb",1335	.snapshot   = hellcreek_devlink_region_fdb_snapshot,1336	.destructor = kfree,1337};1338 1339static int hellcreek_setup_devlink_regions(struct dsa_switch *ds)1340{1341	struct hellcreek *hellcreek = ds->priv;1342	struct devlink_region_ops *ops;1343	struct devlink_region *region;1344	u64 size;1345	int ret;1346 1347	/* VLAN table */1348	size = VLAN_N_VID * sizeof(struct hellcreek_devlink_vlan_entry);1349	ops  = &hellcreek_region_vlan_ops;1350 1351	region = dsa_devlink_region_create(ds, ops, 1, size);1352	if (IS_ERR(region))1353		return PTR_ERR(region);1354 1355	hellcreek->vlan_region = region;1356 1357	/* FDB table */1358	size = hellcreek->fdb_entries * sizeof(struct hellcreek_fdb_entry);1359	ops  = &hellcreek_region_fdb_ops;1360 1361	region = dsa_devlink_region_create(ds, ops, 1, size);1362	if (IS_ERR(region)) {1363		ret = PTR_ERR(region);1364		goto err_fdb;1365	}1366 1367	hellcreek->fdb_region = region;1368 1369	return 0;1370 1371err_fdb:1372	dsa_devlink_region_destroy(hellcreek->vlan_region);1373 1374	return ret;1375}1376 1377static void hellcreek_teardown_devlink_regions(struct dsa_switch *ds)1378{1379	struct hellcreek *hellcreek = ds->priv;1380 1381	dsa_devlink_region_destroy(hellcreek->fdb_region);1382	dsa_devlink_region_destroy(hellcreek->vlan_region);1383}1384 1385static int hellcreek_setup(struct dsa_switch *ds)1386{1387	struct hellcreek *hellcreek = ds->priv;1388	u16 swcfg = 0;1389	int ret, i;1390 1391	dev_dbg(hellcreek->dev, "Set up the switch\n");1392 1393	/* Let's go */1394	ret = hellcreek_enable_ip_core(hellcreek);1395	if (ret) {1396		dev_err(hellcreek->dev, "Failed to enable IP core!\n");1397		return ret;1398	}1399 1400	/* Enable CPU/Tunnel ports */1401	hellcreek_setup_cpu_and_tunnel_port(hellcreek);1402 1403	/* Switch config: Keep defaults, enable FDB aging and learning and tag1404	 * each frame from/to cpu port for DSA tagging.  Also enable the length1405	 * aware shaping mode. This eliminates the need for Qbv guard bands.1406	 */1407	swcfg |= HR_SWCFG_FDBAGE_EN |1408		HR_SWCFG_FDBLRN_EN  |1409		HR_SWCFG_ALWAYS_OBT |1410		(HR_SWCFG_LAS_ON << HR_SWCFG_LAS_MODE_SHIFT);1411	hellcreek->swcfg = swcfg;1412	hellcreek_write(hellcreek, swcfg, HR_SWCFG);1413 1414	/* Initial vlan membership to reflect port separation */1415	for (i = 0; i < ds->num_ports; ++i) {1416		if (!dsa_is_user_port(ds, i))1417			continue;1418 1419		hellcreek_setup_vlan_membership(ds, i, true);1420	}1421 1422	/* Configure PCP <-> TC mapping */1423	hellcreek_setup_tc_identity_mapping(hellcreek);1424 1425	/* The VLAN awareness is a global switch setting. Therefore, mixed vlan1426	 * filtering setups are not supported.1427	 */1428	ds->vlan_filtering_is_global = true;1429	ds->needs_standalone_vlan_filtering = true;1430 1431	/* Intercept _all_ PTP multicast traffic */1432	ret = hellcreek_setup_fdb(hellcreek);1433	if (ret) {1434		dev_err(hellcreek->dev,1435			"Failed to insert static PTP FDB entries\n");1436		return ret;1437	}1438 1439	/* Register devlink resources with DSA */1440	ret = hellcreek_setup_devlink_resources(ds);1441	if (ret) {1442		dev_err(hellcreek->dev,1443			"Failed to setup devlink resources!\n");1444		return ret;1445	}1446 1447	ret = hellcreek_setup_devlink_regions(ds);1448	if (ret) {1449		dev_err(hellcreek->dev,1450			"Failed to setup devlink regions!\n");1451		goto err_regions;1452	}1453 1454	return 0;1455 1456err_regions:1457	dsa_devlink_resources_unregister(ds);1458 1459	return ret;1460}1461 1462static void hellcreek_teardown(struct dsa_switch *ds)1463{1464	hellcreek_teardown_devlink_regions(ds);1465	dsa_devlink_resources_unregister(ds);1466}1467 1468static void hellcreek_phylink_get_caps(struct dsa_switch *ds, int port,1469				       struct phylink_config *config)1470{1471	struct hellcreek *hellcreek = ds->priv;1472 1473	__set_bit(PHY_INTERFACE_MODE_MII, config->supported_interfaces);1474	__set_bit(PHY_INTERFACE_MODE_RGMII, config->supported_interfaces);1475 1476	/* Include GMII - the hardware does not support this interface1477	 * mode, but it's the default interface mode for phylib, so we1478	 * need it for compatibility with existing DT.1479	 */1480	__set_bit(PHY_INTERFACE_MODE_GMII, config->supported_interfaces);1481 1482	/* The MAC settings are a hardware configuration option and cannot be1483	 * changed at run time or by strapping. Therefore the attached PHYs1484	 * should be programmed to only advertise settings which are supported1485	 * by the hardware.1486	 */1487	if (hellcreek->pdata->is_100_mbits)1488		config->mac_capabilities = MAC_100FD;1489	else1490		config->mac_capabilities = MAC_1000FD;1491}1492 1493static int1494hellcreek_port_prechangeupper(struct dsa_switch *ds, int port,1495			      struct netdev_notifier_changeupper_info *info)1496{1497	struct hellcreek *hellcreek = ds->priv;1498	bool used = true;1499	int ret = -EBUSY;1500	u16 vid;1501	int i;1502 1503	dev_dbg(hellcreek->dev, "Pre change upper for port %d\n", port);1504 1505	/*1506	 * Deny VLAN devices on top of lan ports with the same VLAN ids, because1507	 * it breaks the port separation due to the private VLANs. Example:1508	 *1509	 * lan0.100 *and* lan1.100 cannot be used in parallel. However, lan0.991510	 * and lan1.100 works.1511	 */1512 1513	if (!is_vlan_dev(info->upper_dev))1514		return 0;1515 1516	vid = vlan_dev_vlan_id(info->upper_dev);1517 1518	/* For all ports, check bitmaps */1519	mutex_lock(&hellcreek->vlan_lock);1520	for (i = 0; i < hellcreek->pdata->num_ports; ++i) {1521		if (!dsa_is_user_port(ds, i))1522			continue;1523 1524		if (port == i)1525			continue;1526 1527		used = used && test_bit(vid, hellcreek->ports[i].vlan_dev_bitmap);1528	}1529 1530	if (used)1531		goto out;1532 1533	/* Update bitmap */1534	set_bit(vid, hellcreek->ports[port].vlan_dev_bitmap);1535 1536	ret = 0;1537 1538out:1539	mutex_unlock(&hellcreek->vlan_lock);1540 1541	return ret;1542}1543 1544static void hellcreek_setup_maxsdu(struct hellcreek *hellcreek, int port,1545				   const struct tc_taprio_qopt_offload *schedule)1546{1547	int tc;1548 1549	for (tc = 0; tc < 8; ++tc) {1550		u32 max_sdu = schedule->max_sdu[tc] + VLAN_ETH_HLEN - ETH_FCS_LEN;1551		u16 val;1552 1553		if (!schedule->max_sdu[tc])1554			continue;1555 1556		dev_dbg(hellcreek->dev, "Configure max-sdu %u for tc %d on port %d\n",1557			max_sdu, tc, port);1558 1559		hellcreek_select_port_prio(hellcreek, port, tc);1560 1561		val = (max_sdu & HR_PTPRTCCFG_MAXSDU_MASK) << HR_PTPRTCCFG_MAXSDU_SHIFT;1562 1563		hellcreek_write(hellcreek, val, HR_PTPRTCCFG);1564	}1565}1566 1567static void hellcreek_reset_maxsdu(struct hellcreek *hellcreek, int port)1568{1569	int tc;1570 1571	for (tc = 0; tc < 8; ++tc) {1572		u16 val;1573 1574		hellcreek_select_port_prio(hellcreek, port, tc);1575 1576		val = (HELLCREEK_DEFAULT_MAX_SDU & HR_PTPRTCCFG_MAXSDU_MASK)1577			<< HR_PTPRTCCFG_MAXSDU_SHIFT;1578 1579		hellcreek_write(hellcreek, val, HR_PTPRTCCFG);1580	}1581}1582 1583static void hellcreek_setup_gcl(struct hellcreek *hellcreek, int port,1584				const struct tc_taprio_qopt_offload *schedule)1585{1586	const struct tc_taprio_sched_entry *cur, *initial, *next;1587	size_t i;1588 1589	cur = initial = &schedule->entries[0];1590	next = cur + 1;1591 1592	for (i = 1; i <= schedule->num_entries; ++i) {1593		u16 data;1594		u8 gates;1595 1596		if (i == schedule->num_entries)1597			gates = initial->gate_mask ^1598				cur->gate_mask;1599		else1600			gates = next->gate_mask ^1601				cur->gate_mask;1602 1603		data = gates;1604 1605		if (i == schedule->num_entries)1606			data |= TR_GCLDAT_GCLWRLAST;1607 1608		/* Gates states */1609		hellcreek_write(hellcreek, data, TR_GCLDAT);1610 1611		/* Time interval */1612		hellcreek_write(hellcreek,1613				cur->interval & 0x0000ffff,1614				TR_GCLTIL);1615		hellcreek_write(hellcreek,1616				(cur->interval & 0xffff0000) >> 16,1617				TR_GCLTIH);1618 1619		/* Commit entry */1620		data = ((i - 1) << TR_GCLCMD_GCLWRADR_SHIFT) |1621			(initial->gate_mask <<1622			 TR_GCLCMD_INIT_GATE_STATES_SHIFT);1623		hellcreek_write(hellcreek, data, TR_GCLCMD);1624 1625		cur++;1626		next++;1627	}1628}1629 1630static void hellcreek_set_cycle_time(struct hellcreek *hellcreek,1631				     const struct tc_taprio_qopt_offload *schedule)1632{1633	u32 cycle_time = schedule->cycle_time;1634 1635	hellcreek_write(hellcreek, cycle_time & 0x0000ffff, TR_CTWRL);1636	hellcreek_write(hellcreek, (cycle_time & 0xffff0000) >> 16, TR_CTWRH);1637}1638 1639static void hellcreek_switch_schedule(struct hellcreek *hellcreek,1640				      ktime_t start_time)1641{1642	struct timespec64 ts = ktime_to_timespec64(start_time);1643 1644	/* Start schedule at this point of time */1645	hellcreek_write(hellcreek, ts.tv_nsec & 0x0000ffff, TR_ESTWRL);1646	hellcreek_write(hellcreek, (ts.tv_nsec & 0xffff0000) >> 16, TR_ESTWRH);1647 1648	/* Arm timer, set seconds and switch schedule */1649	hellcreek_write(hellcreek, TR_ESTCMD_ESTARM | TR_ESTCMD_ESTSWCFG |1650			((ts.tv_sec & TR_ESTCMD_ESTSEC_MASK) <<1651			 TR_ESTCMD_ESTSEC_SHIFT), TR_ESTCMD);1652}1653 1654static bool hellcreek_schedule_startable(struct hellcreek *hellcreek, int port)1655{1656	struct hellcreek_port *hellcreek_port = &hellcreek->ports[port];1657	s64 base_time_ns, current_ns;1658 1659	/* The switch allows a schedule to be started only eight seconds within1660	 * the future. Therefore, check the current PTP time if the schedule is1661	 * startable or not.1662	 */1663 1664	/* Use the "cached" time. That should be alright, as it's updated quite1665	 * frequently in the PTP code.1666	 */1667	mutex_lock(&hellcreek->ptp_lock);1668	current_ns = hellcreek->seconds * NSEC_PER_SEC + hellcreek->last_ts;1669	mutex_unlock(&hellcreek->ptp_lock);1670 1671	/* Calculate difference to admin base time */1672	base_time_ns = ktime_to_ns(hellcreek_port->current_schedule->base_time);1673 1674	return base_time_ns - current_ns < (s64)4 * NSEC_PER_SEC;1675}1676 1677static void hellcreek_start_schedule(struct hellcreek *hellcreek, int port)1678{1679	struct hellcreek_port *hellcreek_port = &hellcreek->ports[port];1680	ktime_t base_time, current_time;1681	s64 current_ns;1682	u32 cycle_time;1683 1684	/* First select port */1685	hellcreek_select_tgd(hellcreek, port);1686 1687	/* Forward base time into the future if needed */1688	mutex_lock(&hellcreek->ptp_lock);1689	current_ns = hellcreek->seconds * NSEC_PER_SEC + hellcreek->last_ts;1690	mutex_unlock(&hellcreek->ptp_lock);1691 1692	current_time = ns_to_ktime(current_ns);1693	base_time    = hellcreek_port->current_schedule->base_time;1694	cycle_time   = hellcreek_port->current_schedule->cycle_time;1695 1696	if (ktime_compare(current_time, base_time) > 0) {1697		s64 n;1698 1699		n = div64_s64(ktime_sub_ns(current_time, base_time),1700			      cycle_time);1701		base_time = ktime_add_ns(base_time, (n + 1) * cycle_time);1702	}1703 1704	/* Set admin base time and switch schedule */1705	hellcreek_switch_schedule(hellcreek, base_time);1706 1707	taprio_offload_free(hellcreek_port->current_schedule);1708	hellcreek_port->current_schedule = NULL;1709 1710	dev_dbg(hellcreek->dev, "Armed EST timer for port %d\n",1711		hellcreek_port->port);1712}1713 1714static void hellcreek_check_schedule(struct work_struct *work)1715{1716	struct delayed_work *dw = to_delayed_work(work);1717	struct hellcreek_port *hellcreek_port;1718	struct hellcreek *hellcreek;1719	bool startable;1720 1721	hellcreek_port = dw_to_hellcreek_port(dw);1722	hellcreek = hellcreek_port->hellcreek;1723 1724	mutex_lock(&hellcreek->reg_lock);1725 1726	/* Check starting time */1727	startable = hellcreek_schedule_startable(hellcreek,1728						 hellcreek_port->port);1729	if (startable) {1730		hellcreek_start_schedule(hellcreek, hellcreek_port->port);1731		mutex_unlock(&hellcreek->reg_lock);1732		return;1733	}1734 1735	mutex_unlock(&hellcreek->reg_lock);1736 1737	/* Reschedule */1738	schedule_delayed_work(&hellcreek_port->schedule_work,1739			      HELLCREEK_SCHEDULE_PERIOD);1740}1741 1742static int hellcreek_port_set_schedule(struct dsa_switch *ds, int port,1743				       struct tc_taprio_qopt_offload *taprio)1744{1745	struct hellcreek *hellcreek = ds->priv;1746	struct hellcreek_port *hellcreek_port;1747	bool startable;1748	u16 ctrl;1749 1750	hellcreek_port = &hellcreek->ports[port];1751 1752	dev_dbg(hellcreek->dev, "Configure traffic schedule on port %d\n",1753		port);1754 1755	/* First cancel delayed work */1756	cancel_delayed_work_sync(&hellcreek_port->schedule_work);1757 1758	mutex_lock(&hellcreek->reg_lock);1759 1760	if (hellcreek_port->current_schedule) {1761		taprio_offload_free(hellcreek_port->current_schedule);1762		hellcreek_port->current_schedule = NULL;1763	}1764	hellcreek_port->current_schedule = taprio_offload_get(taprio);1765 1766	/* Configure max sdu */1767	hellcreek_setup_maxsdu(hellcreek, port, hellcreek_port->current_schedule);1768 1769	/* Select tdg */1770	hellcreek_select_tgd(hellcreek, port);1771 1772	/* Enable gating and keep defaults */1773	ctrl = (0xff << TR_TGDCTRL_ADMINGATESTATES_SHIFT) | TR_TGDCTRL_GATE_EN;1774	hellcreek_write(hellcreek, ctrl, TR_TGDCTRL);1775 1776	/* Cancel pending schedule */1777	hellcreek_write(hellcreek, 0x00, TR_ESTCMD);1778 1779	/* Setup a new schedule */1780	hellcreek_setup_gcl(hellcreek, port, hellcreek_port->current_schedule);1781 1782	/* Configure cycle time */1783	hellcreek_set_cycle_time(hellcreek, hellcreek_port->current_schedule);1784 1785	/* Check starting time */1786	startable = hellcreek_schedule_startable(hellcreek, port);1787	if (startable) {1788		hellcreek_start_schedule(hellcreek, port);1789		mutex_unlock(&hellcreek->reg_lock);1790		return 0;1791	}1792 1793	mutex_unlock(&hellcreek->reg_lock);1794 1795	/* Schedule periodic schedule check */1796	schedule_delayed_work(&hellcreek_port->schedule_work,1797			      HELLCREEK_SCHEDULE_PERIOD);1798 1799	return 0;1800}1801 1802static int hellcreek_port_del_schedule(struct dsa_switch *ds, int port)1803{1804	struct hellcreek *hellcreek = ds->priv;1805	struct hellcreek_port *hellcreek_port;1806 1807	hellcreek_port = &hellcreek->ports[port];1808 1809	dev_dbg(hellcreek->dev, "Remove traffic schedule on port %d\n", port);1810 1811	/* First cancel delayed work */1812	cancel_delayed_work_sync(&hellcreek_port->schedule_work);1813 1814	mutex_lock(&hellcreek->reg_lock);1815 1816	if (hellcreek_port->current_schedule) {1817		taprio_offload_free(hellcreek_port->current_schedule);1818		hellcreek_port->current_schedule = NULL;1819	}1820 1821	/* Reset max sdu */1822	hellcreek_reset_maxsdu(hellcreek, port);1823 1824	/* Select tgd */1825	hellcreek_select_tgd(hellcreek, port);1826 1827	/* Disable gating and return to regular switching flow */1828	hellcreek_write(hellcreek, 0xff << TR_TGDCTRL_ADMINGATESTATES_SHIFT,1829			TR_TGDCTRL);1830 1831	mutex_unlock(&hellcreek->reg_lock);1832 1833	return 0;1834}1835 1836static bool hellcreek_validate_schedule(struct hellcreek *hellcreek,1837					struct tc_taprio_qopt_offload *schedule)1838{1839	size_t i;1840 1841	/* Does this hellcreek version support Qbv in hardware? */1842	if (!hellcreek->pdata->qbv_support)1843		return false;1844 1845	/* cycle time can only be 32bit */1846	if (schedule->cycle_time > (u32)-1)1847		return false;1848 1849	/* cycle time extension is not supported */1850	if (schedule->cycle_time_extension)1851		return false;1852 1853	/* Only set command is supported */1854	for (i = 0; i < schedule->num_entries; ++i)1855		if (schedule->entries[i].command != TC_TAPRIO_CMD_SET_GATES)1856			return false;1857 1858	return true;1859}1860 1861static int hellcreek_tc_query_caps(struct tc_query_caps_base *base)1862{1863	switch (base->type) {1864	case TC_SETUP_QDISC_TAPRIO: {1865		struct tc_taprio_caps *caps = base->caps;1866 1867		caps->supports_queue_max_sdu = true;1868 1869		return 0;1870	}1871	default:1872		return -EOPNOTSUPP;1873	}1874}1875 1876static int hellcreek_port_setup_tc(struct dsa_switch *ds, int port,1877				   enum tc_setup_type type, void *type_data)1878{1879	struct hellcreek *hellcreek = ds->priv;1880 1881	switch (type) {1882	case TC_QUERY_CAPS:1883		return hellcreek_tc_query_caps(type_data);1884	case TC_SETUP_QDISC_TAPRIO: {1885		struct tc_taprio_qopt_offload *taprio = type_data;1886 1887		switch (taprio->cmd) {1888		case TAPRIO_CMD_REPLACE:1889			if (!hellcreek_validate_schedule(hellcreek, taprio))1890				return -EOPNOTSUPP;1891 1892			return hellcreek_port_set_schedule(ds, port, taprio);1893		case TAPRIO_CMD_DESTROY:1894			return hellcreek_port_del_schedule(ds, port);1895		default:1896			return -EOPNOTSUPP;1897		}1898	}1899	default:1900		return -EOPNOTSUPP;1901	}1902}1903 1904static const struct dsa_switch_ops hellcreek_ds_ops = {1905	.devlink_info_get      = hellcreek_devlink_info_get,1906	.get_ethtool_stats     = hellcreek_get_ethtool_stats,1907	.get_sset_count	       = hellcreek_get_sset_count,1908	.get_strings	       = hellcreek_get_strings,1909	.get_tag_protocol      = hellcreek_get_tag_protocol,1910	.get_ts_info	       = hellcreek_get_ts_info,1911	.phylink_get_caps      = hellcreek_phylink_get_caps,1912	.port_bridge_flags     = hellcreek_bridge_flags,1913	.port_bridge_join      = hellcreek_port_bridge_join,1914	.port_bridge_leave     = hellcreek_port_bridge_leave,1915	.port_disable	       = hellcreek_port_disable,1916	.port_enable	       = hellcreek_port_enable,1917	.port_fdb_add	       = hellcreek_fdb_add,1918	.port_fdb_del	       = hellcreek_fdb_del,1919	.port_fdb_dump	       = hellcreek_fdb_dump,1920	.port_hwtstamp_set     = hellcreek_port_hwtstamp_set,1921	.port_hwtstamp_get     = hellcreek_port_hwtstamp_get,1922	.port_pre_bridge_flags = hellcreek_pre_bridge_flags,1923	.port_prechangeupper   = hellcreek_port_prechangeupper,1924	.port_rxtstamp	       = hellcreek_port_rxtstamp,1925	.port_setup_tc	       = hellcreek_port_setup_tc,1926	.port_stp_state_set    = hellcreek_port_stp_state_set,1927	.port_txtstamp	       = hellcreek_port_txtstamp,1928	.port_vlan_add	       = hellcreek_vlan_add,1929	.port_vlan_del	       = hellcreek_vlan_del,1930	.port_vlan_filtering   = hellcreek_vlan_filtering,1931	.setup		       = hellcreek_setup,1932	.teardown	       = hellcreek_teardown,1933};1934 1935static int hellcreek_probe(struct platform_device *pdev)1936{1937	struct device *dev = &pdev->dev;1938	struct hellcreek *hellcreek;1939	struct resource *res;1940	int ret, i;1941 1942	hellcreek = devm_kzalloc(dev, sizeof(*hellcreek), GFP_KERNEL);1943	if (!hellcreek)1944		return -ENOMEM;1945 1946	hellcreek->vidmbrcfg = devm_kcalloc(dev, VLAN_N_VID,1947					    sizeof(*hellcreek->vidmbrcfg),1948					    GFP_KERNEL);1949	if (!hellcreek->vidmbrcfg)1950		return -ENOMEM;1951 1952	hellcreek->pdata = of_device_get_match_data(dev);1953 1954	hellcreek->ports = devm_kcalloc(dev, hellcreek->pdata->num_ports,1955					sizeof(*hellcreek->ports),1956					GFP_KERNEL);1957	if (!hellcreek->ports)1958		return -ENOMEM;1959 1960	for (i = 0; i < hellcreek->pdata->num_ports; ++i) {1961		struct hellcreek_port *port = &hellcreek->ports[i];1962 1963		port->counter_values =1964			devm_kcalloc(dev,1965				     ARRAY_SIZE(hellcreek_counter),1966				     sizeof(*port->counter_values),1967				     GFP_KERNEL);1968		if (!port->counter_values)1969			return -ENOMEM;1970 1971		port->vlan_dev_bitmap = devm_bitmap_zalloc(dev, VLAN_N_VID,1972							   GFP_KERNEL);1973		if (!port->vlan_dev_bitmap)1974			return -ENOMEM;1975 1976		port->hellcreek	= hellcreek;1977		port->port	= i;1978 1979		INIT_DELAYED_WORK(&port->schedule_work,1980				  hellcreek_check_schedule);1981	}1982 1983	mutex_init(&hellcreek->reg_lock);1984	mutex_init(&hellcreek->vlan_lock);1985	mutex_init(&hellcreek->ptp_lock);1986 1987	hellcreek->dev = dev;1988 1989	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tsn");1990	if (!res) {1991		dev_err(dev, "No memory region provided!\n");1992		return -ENODEV;1993	}1994 1995	hellcreek->base = devm_ioremap_resource(dev, res);1996	if (IS_ERR(hellcreek->base))1997		return PTR_ERR(hellcreek->base);1998 1999	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ptp");2000	if (!res) {2001		dev_err(dev, "No PTP memory region provided!\n");2002		return -ENODEV;2003	}2004 2005	hellcreek->ptp_base = devm_ioremap_resource(dev, res);2006	if (IS_ERR(hellcreek->ptp_base))2007		return PTR_ERR(hellcreek->ptp_base);2008 2009	ret = hellcreek_detect(hellcreek);2010	if (ret) {2011		dev_err(dev, "No (known) chip found!\n");2012		return ret;2013	}2014 2015	ret = hellcreek_wait_until_ready(hellcreek);2016	if (ret) {2017		dev_err(dev, "Switch didn't become ready!\n");2018		return ret;2019	}2020 2021	hellcreek_feature_detect(hellcreek);2022 2023	hellcreek->ds = devm_kzalloc(dev, sizeof(*hellcreek->ds), GFP_KERNEL);2024	if (!hellcreek->ds)2025		return -ENOMEM;2026 2027	hellcreek->ds->dev	     = dev;2028	hellcreek->ds->priv	     = hellcreek;2029	hellcreek->ds->ops	     = &hellcreek_ds_ops;2030	hellcreek->ds->num_ports     = hellcreek->pdata->num_ports;2031	hellcreek->ds->num_tx_queues = HELLCREEK_NUM_EGRESS_QUEUES;2032 2033	ret = dsa_register_switch(hellcreek->ds);2034	if (ret) {2035		dev_err_probe(dev, ret, "Unable to register switch\n");2036		return ret;2037	}2038 2039	ret = hellcreek_ptp_setup(hellcreek);2040	if (ret) {2041		dev_err(dev, "Failed to setup PTP!\n");2042		goto err_ptp_setup;2043	}2044 2045	ret = hellcreek_hwtstamp_setup(hellcreek);2046	if (ret) {2047		dev_err(dev, "Failed to setup hardware timestamping!\n");2048		goto err_tstamp_setup;2049	}2050 2051	platform_set_drvdata(pdev, hellcreek);2052 2053	return 0;2054 2055err_tstamp_setup:2056	hellcreek_ptp_free(hellcreek);2057err_ptp_setup:2058	dsa_unregister_switch(hellcreek->ds);2059 2060	return ret;2061}2062 2063static void hellcreek_remove(struct platform_device *pdev)2064{2065	struct hellcreek *hellcreek = platform_get_drvdata(pdev);2066 2067	if (!hellcreek)2068		return;2069 2070	hellcreek_hwtstamp_free(hellcreek);2071	hellcreek_ptp_free(hellcreek);2072	dsa_unregister_switch(hellcreek->ds);2073}2074 2075static void hellcreek_shutdown(struct platform_device *pdev)2076{2077	struct hellcreek *hellcreek = platform_get_drvdata(pdev);2078 2079	if (!hellcreek)2080		return;2081 2082	dsa_switch_shutdown(hellcreek->ds);2083 2084	platform_set_drvdata(pdev, NULL);2085}2086 2087static const struct hellcreek_platform_data de1soc_r1_pdata = {2088	.name		 = "r4c30",2089	.num_ports	 = 4,2090	.is_100_mbits	 = 1,2091	.qbv_support	 = 1,2092	.qbv_on_cpu_port = 1,2093	.qbu_support	 = 0,2094	.module_id	 = 0x4c30,2095};2096 2097static const struct of_device_id hellcreek_of_match[] = {2098	{2099		.compatible = "hirschmann,hellcreek-de1soc-r1",2100		.data	    = &de1soc_r1_pdata,2101	},2102	{ /* sentinel */ },2103};2104MODULE_DEVICE_TABLE(of, hellcreek_of_match);2105 2106static struct platform_driver hellcreek_driver = {2107	.probe	= hellcreek_probe,2108	.remove_new = hellcreek_remove,2109	.shutdown = hellcreek_shutdown,2110	.driver = {2111		.name = "hellcreek",2112		.of_match_table = hellcreek_of_match,2113	},2114};2115module_platform_driver(hellcreek_driver);2116 2117MODULE_AUTHOR("Kurt Kanzenbach <kurt@linutronix.de>");2118MODULE_DESCRIPTION("Hirschmann Hellcreek driver");2119MODULE_LICENSE("Dual MIT/GPL");2120