162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0 OR MIT)
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * DSA driver for:
462306a36Sopenharmony_ci * Hirschmann Hellcreek TSN switch.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2019-2021 Linutronix GmbH
762306a36Sopenharmony_ci * Author Kurt Kanzenbach <kurt@linutronix.de>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/device.h>
1362306a36Sopenharmony_ci#include <linux/of.h>
1462306a36Sopenharmony_ci#include <linux/of_mdio.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/bitops.h>
1762306a36Sopenharmony_ci#include <linux/if_bridge.h>
1862306a36Sopenharmony_ci#include <linux/if_vlan.h>
1962306a36Sopenharmony_ci#include <linux/etherdevice.h>
2062306a36Sopenharmony_ci#include <linux/random.h>
2162306a36Sopenharmony_ci#include <linux/iopoll.h>
2262306a36Sopenharmony_ci#include <linux/mutex.h>
2362306a36Sopenharmony_ci#include <linux/delay.h>
2462306a36Sopenharmony_ci#include <net/dsa.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#include "hellcreek.h"
2762306a36Sopenharmony_ci#include "hellcreek_ptp.h"
2862306a36Sopenharmony_ci#include "hellcreek_hwtstamp.h"
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic const struct hellcreek_counter hellcreek_counter[] = {
3162306a36Sopenharmony_ci	{ 0x00, "RxFiltered", },
3262306a36Sopenharmony_ci	{ 0x01, "RxOctets1k", },
3362306a36Sopenharmony_ci	{ 0x02, "RxVTAG", },
3462306a36Sopenharmony_ci	{ 0x03, "RxL2BAD", },
3562306a36Sopenharmony_ci	{ 0x04, "RxOverloadDrop", },
3662306a36Sopenharmony_ci	{ 0x05, "RxUC", },
3762306a36Sopenharmony_ci	{ 0x06, "RxMC", },
3862306a36Sopenharmony_ci	{ 0x07, "RxBC", },
3962306a36Sopenharmony_ci	{ 0x08, "RxRS<64", },
4062306a36Sopenharmony_ci	{ 0x09, "RxRS64", },
4162306a36Sopenharmony_ci	{ 0x0a, "RxRS65_127", },
4262306a36Sopenharmony_ci	{ 0x0b, "RxRS128_255", },
4362306a36Sopenharmony_ci	{ 0x0c, "RxRS256_511", },
4462306a36Sopenharmony_ci	{ 0x0d, "RxRS512_1023", },
4562306a36Sopenharmony_ci	{ 0x0e, "RxRS1024_1518", },
4662306a36Sopenharmony_ci	{ 0x0f, "RxRS>1518", },
4762306a36Sopenharmony_ci	{ 0x10, "TxTailDropQueue0", },
4862306a36Sopenharmony_ci	{ 0x11, "TxTailDropQueue1", },
4962306a36Sopenharmony_ci	{ 0x12, "TxTailDropQueue2", },
5062306a36Sopenharmony_ci	{ 0x13, "TxTailDropQueue3", },
5162306a36Sopenharmony_ci	{ 0x14, "TxTailDropQueue4", },
5262306a36Sopenharmony_ci	{ 0x15, "TxTailDropQueue5", },
5362306a36Sopenharmony_ci	{ 0x16, "TxTailDropQueue6", },
5462306a36Sopenharmony_ci	{ 0x17, "TxTailDropQueue7", },
5562306a36Sopenharmony_ci	{ 0x18, "RxTrafficClass0", },
5662306a36Sopenharmony_ci	{ 0x19, "RxTrafficClass1", },
5762306a36Sopenharmony_ci	{ 0x1a, "RxTrafficClass2", },
5862306a36Sopenharmony_ci	{ 0x1b, "RxTrafficClass3", },
5962306a36Sopenharmony_ci	{ 0x1c, "RxTrafficClass4", },
6062306a36Sopenharmony_ci	{ 0x1d, "RxTrafficClass5", },
6162306a36Sopenharmony_ci	{ 0x1e, "RxTrafficClass6", },
6262306a36Sopenharmony_ci	{ 0x1f, "RxTrafficClass7", },
6362306a36Sopenharmony_ci	{ 0x21, "TxOctets1k", },
6462306a36Sopenharmony_ci	{ 0x22, "TxVTAG", },
6562306a36Sopenharmony_ci	{ 0x23, "TxL2BAD", },
6662306a36Sopenharmony_ci	{ 0x25, "TxUC", },
6762306a36Sopenharmony_ci	{ 0x26, "TxMC", },
6862306a36Sopenharmony_ci	{ 0x27, "TxBC", },
6962306a36Sopenharmony_ci	{ 0x28, "TxTS<64", },
7062306a36Sopenharmony_ci	{ 0x29, "TxTS64", },
7162306a36Sopenharmony_ci	{ 0x2a, "TxTS65_127", },
7262306a36Sopenharmony_ci	{ 0x2b, "TxTS128_255", },
7362306a36Sopenharmony_ci	{ 0x2c, "TxTS256_511", },
7462306a36Sopenharmony_ci	{ 0x2d, "TxTS512_1023", },
7562306a36Sopenharmony_ci	{ 0x2e, "TxTS1024_1518", },
7662306a36Sopenharmony_ci	{ 0x2f, "TxTS>1518", },
7762306a36Sopenharmony_ci	{ 0x30, "TxTrafficClassOverrun0", },
7862306a36Sopenharmony_ci	{ 0x31, "TxTrafficClassOverrun1", },
7962306a36Sopenharmony_ci	{ 0x32, "TxTrafficClassOverrun2", },
8062306a36Sopenharmony_ci	{ 0x33, "TxTrafficClassOverrun3", },
8162306a36Sopenharmony_ci	{ 0x34, "TxTrafficClassOverrun4", },
8262306a36Sopenharmony_ci	{ 0x35, "TxTrafficClassOverrun5", },
8362306a36Sopenharmony_ci	{ 0x36, "TxTrafficClassOverrun6", },
8462306a36Sopenharmony_ci	{ 0x37, "TxTrafficClassOverrun7", },
8562306a36Sopenharmony_ci	{ 0x38, "TxTrafficClass0", },
8662306a36Sopenharmony_ci	{ 0x39, "TxTrafficClass1", },
8762306a36Sopenharmony_ci	{ 0x3a, "TxTrafficClass2", },
8862306a36Sopenharmony_ci	{ 0x3b, "TxTrafficClass3", },
8962306a36Sopenharmony_ci	{ 0x3c, "TxTrafficClass4", },
9062306a36Sopenharmony_ci	{ 0x3d, "TxTrafficClass5", },
9162306a36Sopenharmony_ci	{ 0x3e, "TxTrafficClass6", },
9262306a36Sopenharmony_ci	{ 0x3f, "TxTrafficClass7", },
9362306a36Sopenharmony_ci};
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic u16 hellcreek_read(struct hellcreek *hellcreek, unsigned int offset)
9662306a36Sopenharmony_ci{
9762306a36Sopenharmony_ci	return readw(hellcreek->base + offset);
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic u16 hellcreek_read_ctrl(struct hellcreek *hellcreek)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	return readw(hellcreek->base + HR_CTRL_C);
10362306a36Sopenharmony_ci}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic u16 hellcreek_read_stat(struct hellcreek *hellcreek)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	return readw(hellcreek->base + HR_SWSTAT);
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic void hellcreek_write(struct hellcreek *hellcreek, u16 data,
11162306a36Sopenharmony_ci			    unsigned int offset)
11262306a36Sopenharmony_ci{
11362306a36Sopenharmony_ci	writew(data, hellcreek->base + offset);
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic void hellcreek_select_port(struct hellcreek *hellcreek, int port)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	u16 val = port << HR_PSEL_PTWSEL_SHIFT;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, HR_PSEL);
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_cistatic void hellcreek_select_prio(struct hellcreek *hellcreek, int prio)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	u16 val = prio << HR_PSEL_PRTCWSEL_SHIFT;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, HR_PSEL);
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistatic void hellcreek_select_port_prio(struct hellcreek *hellcreek, int port,
13162306a36Sopenharmony_ci				       int prio)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	u16 val = port << HR_PSEL_PTWSEL_SHIFT;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	val |= prio << HR_PSEL_PRTCWSEL_SHIFT;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, HR_PSEL);
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistatic void hellcreek_select_counter(struct hellcreek *hellcreek, int counter)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	u16 val = counter << HR_CSEL_SHIFT;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, HR_CSEL);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	/* Data sheet states to wait at least 20 internal clock cycles */
14762306a36Sopenharmony_ci	ndelay(200);
14862306a36Sopenharmony_ci}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic void hellcreek_select_vlan(struct hellcreek *hellcreek, int vid,
15162306a36Sopenharmony_ci				  bool pvid)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	u16 val = 0;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	/* Set pvid bit first */
15662306a36Sopenharmony_ci	if (pvid)
15762306a36Sopenharmony_ci		val |= HR_VIDCFG_PVID;
15862306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, HR_VIDCFG);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	/* Set vlan */
16162306a36Sopenharmony_ci	val |= vid << HR_VIDCFG_VID_SHIFT;
16262306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, HR_VIDCFG);
16362306a36Sopenharmony_ci}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_cistatic void hellcreek_select_tgd(struct hellcreek *hellcreek, int port)
16662306a36Sopenharmony_ci{
16762306a36Sopenharmony_ci	u16 val = port << TR_TGDSEL_TDGSEL_SHIFT;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, TR_TGDSEL);
17062306a36Sopenharmony_ci}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_cistatic int hellcreek_wait_until_ready(struct hellcreek *hellcreek)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	u16 val;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	/* Wait up to 1ms, although 3 us should be enough */
17762306a36Sopenharmony_ci	return readx_poll_timeout(hellcreek_read_ctrl, hellcreek,
17862306a36Sopenharmony_ci				  val, val & HR_CTRL_C_READY,
17962306a36Sopenharmony_ci				  3, 1000);
18062306a36Sopenharmony_ci}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_cistatic int hellcreek_wait_until_transitioned(struct hellcreek *hellcreek)
18362306a36Sopenharmony_ci{
18462306a36Sopenharmony_ci	u16 val;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	return readx_poll_timeout_atomic(hellcreek_read_ctrl, hellcreek,
18762306a36Sopenharmony_ci					 val, !(val & HR_CTRL_C_TRANSITION),
18862306a36Sopenharmony_ci					 1, 1000);
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_cistatic int hellcreek_wait_fdb_ready(struct hellcreek *hellcreek)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	u16 val;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	return readx_poll_timeout_atomic(hellcreek_read_stat, hellcreek,
19662306a36Sopenharmony_ci					 val, !(val & HR_SWSTAT_BUSY),
19762306a36Sopenharmony_ci					 1, 1000);
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic int hellcreek_detect(struct hellcreek *hellcreek)
20162306a36Sopenharmony_ci{
20262306a36Sopenharmony_ci	u16 id, rel_low, rel_high, date_low, date_high, tgd_ver;
20362306a36Sopenharmony_ci	u8 tgd_maj, tgd_min;
20462306a36Sopenharmony_ci	u32 rel, date;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	id	  = hellcreek_read(hellcreek, HR_MODID_C);
20762306a36Sopenharmony_ci	rel_low	  = hellcreek_read(hellcreek, HR_REL_L_C);
20862306a36Sopenharmony_ci	rel_high  = hellcreek_read(hellcreek, HR_REL_H_C);
20962306a36Sopenharmony_ci	date_low  = hellcreek_read(hellcreek, HR_BLD_L_C);
21062306a36Sopenharmony_ci	date_high = hellcreek_read(hellcreek, HR_BLD_H_C);
21162306a36Sopenharmony_ci	tgd_ver   = hellcreek_read(hellcreek, TR_TGDVER);
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	if (id != hellcreek->pdata->module_id)
21462306a36Sopenharmony_ci		return -ENODEV;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	rel	= rel_low | (rel_high << 16);
21762306a36Sopenharmony_ci	date	= date_low | (date_high << 16);
21862306a36Sopenharmony_ci	tgd_maj = (tgd_ver & TR_TGDVER_REV_MAJ_MASK) >> TR_TGDVER_REV_MAJ_SHIFT;
21962306a36Sopenharmony_ci	tgd_min = (tgd_ver & TR_TGDVER_REV_MIN_MASK) >> TR_TGDVER_REV_MIN_SHIFT;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	dev_info(hellcreek->dev, "Module ID=%02x Release=%04x Date=%04x TGD Version=%02x.%02x\n",
22262306a36Sopenharmony_ci		 id, rel, date, tgd_maj, tgd_min);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	return 0;
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_cistatic void hellcreek_feature_detect(struct hellcreek *hellcreek)
22862306a36Sopenharmony_ci{
22962306a36Sopenharmony_ci	u16 features;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	features = hellcreek_read(hellcreek, HR_FEABITS0);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	/* Only detect the size of the FDB table. The size and current
23462306a36Sopenharmony_ci	 * utilization can be queried via devlink.
23562306a36Sopenharmony_ci	 */
23662306a36Sopenharmony_ci	hellcreek->fdb_entries = ((features & HR_FEABITS0_FDBBINS_MASK) >>
23762306a36Sopenharmony_ci			       HR_FEABITS0_FDBBINS_SHIFT) * 32;
23862306a36Sopenharmony_ci}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_cistatic enum dsa_tag_protocol hellcreek_get_tag_protocol(struct dsa_switch *ds,
24162306a36Sopenharmony_ci							int port,
24262306a36Sopenharmony_ci							enum dsa_tag_protocol mp)
24362306a36Sopenharmony_ci{
24462306a36Sopenharmony_ci	return DSA_TAG_PROTO_HELLCREEK;
24562306a36Sopenharmony_ci}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_cistatic int hellcreek_port_enable(struct dsa_switch *ds, int port,
24862306a36Sopenharmony_ci				 struct phy_device *phy)
24962306a36Sopenharmony_ci{
25062306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
25162306a36Sopenharmony_ci	struct hellcreek_port *hellcreek_port;
25262306a36Sopenharmony_ci	u16 val;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	hellcreek_port = &hellcreek->ports[port];
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Enable port %d\n", port);
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	hellcreek_select_port(hellcreek, port);
26162306a36Sopenharmony_ci	val = hellcreek_port->ptcfg;
26262306a36Sopenharmony_ci	val |= HR_PTCFG_ADMIN_EN;
26362306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, HR_PTCFG);
26462306a36Sopenharmony_ci	hellcreek_port->ptcfg = val;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	return 0;
26962306a36Sopenharmony_ci}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_cistatic void hellcreek_port_disable(struct dsa_switch *ds, int port)
27262306a36Sopenharmony_ci{
27362306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
27462306a36Sopenharmony_ci	struct hellcreek_port *hellcreek_port;
27562306a36Sopenharmony_ci	u16 val;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	hellcreek_port = &hellcreek->ports[port];
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Disable port %d\n", port);
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	hellcreek_select_port(hellcreek, port);
28462306a36Sopenharmony_ci	val = hellcreek_port->ptcfg;
28562306a36Sopenharmony_ci	val &= ~HR_PTCFG_ADMIN_EN;
28662306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, HR_PTCFG);
28762306a36Sopenharmony_ci	hellcreek_port->ptcfg = val;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
29062306a36Sopenharmony_ci}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_cistatic void hellcreek_get_strings(struct dsa_switch *ds, int port,
29362306a36Sopenharmony_ci				  u32 stringset, uint8_t *data)
29462306a36Sopenharmony_ci{
29562306a36Sopenharmony_ci	int i;
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(hellcreek_counter); ++i) {
29862306a36Sopenharmony_ci		const struct hellcreek_counter *counter = &hellcreek_counter[i];
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci		strscpy(data + i * ETH_GSTRING_LEN,
30162306a36Sopenharmony_ci			counter->name, ETH_GSTRING_LEN);
30262306a36Sopenharmony_ci	}
30362306a36Sopenharmony_ci}
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_cistatic int hellcreek_get_sset_count(struct dsa_switch *ds, int port, int sset)
30662306a36Sopenharmony_ci{
30762306a36Sopenharmony_ci	if (sset != ETH_SS_STATS)
30862306a36Sopenharmony_ci		return 0;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	return ARRAY_SIZE(hellcreek_counter);
31162306a36Sopenharmony_ci}
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_cistatic void hellcreek_get_ethtool_stats(struct dsa_switch *ds, int port,
31462306a36Sopenharmony_ci					uint64_t *data)
31562306a36Sopenharmony_ci{
31662306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
31762306a36Sopenharmony_ci	struct hellcreek_port *hellcreek_port;
31862306a36Sopenharmony_ci	int i;
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	hellcreek_port = &hellcreek->ports[port];
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(hellcreek_counter); ++i) {
32362306a36Sopenharmony_ci		const struct hellcreek_counter *counter = &hellcreek_counter[i];
32462306a36Sopenharmony_ci		u8 offset = counter->offset + port * 64;
32562306a36Sopenharmony_ci		u16 high, low;
32662306a36Sopenharmony_ci		u64 value;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci		mutex_lock(&hellcreek->reg_lock);
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci		hellcreek_select_counter(hellcreek, offset);
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci		/* The registers are locked internally by selecting the
33362306a36Sopenharmony_ci		 * counter. So low and high can be read without reading high
33462306a36Sopenharmony_ci		 * again.
33562306a36Sopenharmony_ci		 */
33662306a36Sopenharmony_ci		high  = hellcreek_read(hellcreek, HR_CRDH);
33762306a36Sopenharmony_ci		low   = hellcreek_read(hellcreek, HR_CRDL);
33862306a36Sopenharmony_ci		value = ((u64)high << 16) | low;
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci		hellcreek_port->counter_values[i] += value;
34162306a36Sopenharmony_ci		data[i] = hellcreek_port->counter_values[i];
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci		mutex_unlock(&hellcreek->reg_lock);
34462306a36Sopenharmony_ci	}
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic u16 hellcreek_private_vid(int port)
34862306a36Sopenharmony_ci{
34962306a36Sopenharmony_ci	return VLAN_N_VID - port + 1;
35062306a36Sopenharmony_ci}
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_cistatic int hellcreek_vlan_prepare(struct dsa_switch *ds, int port,
35362306a36Sopenharmony_ci				  const struct switchdev_obj_port_vlan *vlan,
35462306a36Sopenharmony_ci				  struct netlink_ext_ack *extack)
35562306a36Sopenharmony_ci{
35662306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
35762306a36Sopenharmony_ci	int i;
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "VLAN prepare for port %d\n", port);
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	/* Restriction: Make sure that nobody uses the "private" VLANs. These
36262306a36Sopenharmony_ci	 * VLANs are internally used by the driver to ensure port
36362306a36Sopenharmony_ci	 * separation. Thus, they cannot be used by someone else.
36462306a36Sopenharmony_ci	 */
36562306a36Sopenharmony_ci	for (i = 0; i < hellcreek->pdata->num_ports; ++i) {
36662306a36Sopenharmony_ci		const u16 restricted_vid = hellcreek_private_vid(i);
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci		if (!dsa_is_user_port(ds, i))
36962306a36Sopenharmony_ci			continue;
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci		if (vlan->vid == restricted_vid) {
37262306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "VID restricted by driver");
37362306a36Sopenharmony_ci			return -EBUSY;
37462306a36Sopenharmony_ci		}
37562306a36Sopenharmony_ci	}
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	return 0;
37862306a36Sopenharmony_ci}
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_cistatic void hellcreek_select_vlan_params(struct hellcreek *hellcreek, int port,
38162306a36Sopenharmony_ci					 int *shift, int *mask)
38262306a36Sopenharmony_ci{
38362306a36Sopenharmony_ci	switch (port) {
38462306a36Sopenharmony_ci	case 0:
38562306a36Sopenharmony_ci		*shift = HR_VIDMBRCFG_P0MBR_SHIFT;
38662306a36Sopenharmony_ci		*mask  = HR_VIDMBRCFG_P0MBR_MASK;
38762306a36Sopenharmony_ci		break;
38862306a36Sopenharmony_ci	case 1:
38962306a36Sopenharmony_ci		*shift = HR_VIDMBRCFG_P1MBR_SHIFT;
39062306a36Sopenharmony_ci		*mask  = HR_VIDMBRCFG_P1MBR_MASK;
39162306a36Sopenharmony_ci		break;
39262306a36Sopenharmony_ci	case 2:
39362306a36Sopenharmony_ci		*shift = HR_VIDMBRCFG_P2MBR_SHIFT;
39462306a36Sopenharmony_ci		*mask  = HR_VIDMBRCFG_P2MBR_MASK;
39562306a36Sopenharmony_ci		break;
39662306a36Sopenharmony_ci	case 3:
39762306a36Sopenharmony_ci		*shift = HR_VIDMBRCFG_P3MBR_SHIFT;
39862306a36Sopenharmony_ci		*mask  = HR_VIDMBRCFG_P3MBR_MASK;
39962306a36Sopenharmony_ci		break;
40062306a36Sopenharmony_ci	default:
40162306a36Sopenharmony_ci		*shift = *mask = 0;
40262306a36Sopenharmony_ci		dev_err(hellcreek->dev, "Unknown port %d selected!\n", port);
40362306a36Sopenharmony_ci	}
40462306a36Sopenharmony_ci}
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_cistatic void hellcreek_apply_vlan(struct hellcreek *hellcreek, int port, u16 vid,
40762306a36Sopenharmony_ci				 bool pvid, bool untagged)
40862306a36Sopenharmony_ci{
40962306a36Sopenharmony_ci	int shift, mask;
41062306a36Sopenharmony_ci	u16 val;
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Apply VLAN: port=%d vid=%u pvid=%d untagged=%d",
41362306a36Sopenharmony_ci		port, vid, pvid, untagged);
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	hellcreek_select_port(hellcreek, port);
41862306a36Sopenharmony_ci	hellcreek_select_vlan(hellcreek, vid, pvid);
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	/* Setup port vlan membership */
42162306a36Sopenharmony_ci	hellcreek_select_vlan_params(hellcreek, port, &shift, &mask);
42262306a36Sopenharmony_ci	val = hellcreek->vidmbrcfg[vid];
42362306a36Sopenharmony_ci	val &= ~mask;
42462306a36Sopenharmony_ci	if (untagged)
42562306a36Sopenharmony_ci		val |= HELLCREEK_VLAN_UNTAGGED_MEMBER << shift;
42662306a36Sopenharmony_ci	else
42762306a36Sopenharmony_ci		val |= HELLCREEK_VLAN_TAGGED_MEMBER << shift;
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, HR_VIDMBRCFG);
43062306a36Sopenharmony_ci	hellcreek->vidmbrcfg[vid] = val;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
43362306a36Sopenharmony_ci}
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_cistatic void hellcreek_unapply_vlan(struct hellcreek *hellcreek, int port,
43662306a36Sopenharmony_ci				   u16 vid)
43762306a36Sopenharmony_ci{
43862306a36Sopenharmony_ci	int shift, mask;
43962306a36Sopenharmony_ci	u16 val;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Unapply VLAN: port=%d vid=%u\n", port, vid);
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	hellcreek_select_vlan(hellcreek, vid, false);
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	/* Setup port vlan membership */
44862306a36Sopenharmony_ci	hellcreek_select_vlan_params(hellcreek, port, &shift, &mask);
44962306a36Sopenharmony_ci	val = hellcreek->vidmbrcfg[vid];
45062306a36Sopenharmony_ci	val &= ~mask;
45162306a36Sopenharmony_ci	val |= HELLCREEK_VLAN_NO_MEMBER << shift;
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, HR_VIDMBRCFG);
45462306a36Sopenharmony_ci	hellcreek->vidmbrcfg[vid] = val;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
45762306a36Sopenharmony_ci}
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_cistatic int hellcreek_vlan_add(struct dsa_switch *ds, int port,
46062306a36Sopenharmony_ci			      const struct switchdev_obj_port_vlan *vlan,
46162306a36Sopenharmony_ci			      struct netlink_ext_ack *extack)
46262306a36Sopenharmony_ci{
46362306a36Sopenharmony_ci	bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
46462306a36Sopenharmony_ci	bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
46562306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
46662306a36Sopenharmony_ci	int err;
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	err = hellcreek_vlan_prepare(ds, port, vlan, extack);
46962306a36Sopenharmony_ci	if (err)
47062306a36Sopenharmony_ci		return err;
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Add VLAN %d on port %d, %s, %s\n",
47362306a36Sopenharmony_ci		vlan->vid, port, untagged ? "untagged" : "tagged",
47462306a36Sopenharmony_ci		pvid ? "PVID" : "no PVID");
47562306a36Sopenharmony_ci
47662306a36Sopenharmony_ci	hellcreek_apply_vlan(hellcreek, port, vlan->vid, pvid, untagged);
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	return 0;
47962306a36Sopenharmony_ci}
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_cistatic int hellcreek_vlan_del(struct dsa_switch *ds, int port,
48262306a36Sopenharmony_ci			      const struct switchdev_obj_port_vlan *vlan)
48362306a36Sopenharmony_ci{
48462306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Remove VLAN %d on port %d\n", vlan->vid, port);
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	hellcreek_unapply_vlan(hellcreek, port, vlan->vid);
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci	return 0;
49162306a36Sopenharmony_ci}
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_cistatic void hellcreek_port_stp_state_set(struct dsa_switch *ds, int port,
49462306a36Sopenharmony_ci					 u8 state)
49562306a36Sopenharmony_ci{
49662306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
49762306a36Sopenharmony_ci	struct hellcreek_port *hellcreek_port;
49862306a36Sopenharmony_ci	const char *new_state;
49962306a36Sopenharmony_ci	u16 val;
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_ci	hellcreek_port = &hellcreek->ports[port];
50462306a36Sopenharmony_ci	val = hellcreek_port->ptcfg;
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci	switch (state) {
50762306a36Sopenharmony_ci	case BR_STATE_DISABLED:
50862306a36Sopenharmony_ci		new_state = "DISABLED";
50962306a36Sopenharmony_ci		val |= HR_PTCFG_BLOCKED;
51062306a36Sopenharmony_ci		val &= ~HR_PTCFG_LEARNING_EN;
51162306a36Sopenharmony_ci		break;
51262306a36Sopenharmony_ci	case BR_STATE_BLOCKING:
51362306a36Sopenharmony_ci		new_state = "BLOCKING";
51462306a36Sopenharmony_ci		val |= HR_PTCFG_BLOCKED;
51562306a36Sopenharmony_ci		val &= ~HR_PTCFG_LEARNING_EN;
51662306a36Sopenharmony_ci		break;
51762306a36Sopenharmony_ci	case BR_STATE_LISTENING:
51862306a36Sopenharmony_ci		new_state = "LISTENING";
51962306a36Sopenharmony_ci		val |= HR_PTCFG_BLOCKED;
52062306a36Sopenharmony_ci		val &= ~HR_PTCFG_LEARNING_EN;
52162306a36Sopenharmony_ci		break;
52262306a36Sopenharmony_ci	case BR_STATE_LEARNING:
52362306a36Sopenharmony_ci		new_state = "LEARNING";
52462306a36Sopenharmony_ci		val |= HR_PTCFG_BLOCKED;
52562306a36Sopenharmony_ci		val |= HR_PTCFG_LEARNING_EN;
52662306a36Sopenharmony_ci		break;
52762306a36Sopenharmony_ci	case BR_STATE_FORWARDING:
52862306a36Sopenharmony_ci		new_state = "FORWARDING";
52962306a36Sopenharmony_ci		val &= ~HR_PTCFG_BLOCKED;
53062306a36Sopenharmony_ci		val |= HR_PTCFG_LEARNING_EN;
53162306a36Sopenharmony_ci		break;
53262306a36Sopenharmony_ci	default:
53362306a36Sopenharmony_ci		new_state = "UNKNOWN";
53462306a36Sopenharmony_ci	}
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci	hellcreek_select_port(hellcreek, port);
53762306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, HR_PTCFG);
53862306a36Sopenharmony_ci	hellcreek_port->ptcfg = val;
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Configured STP state for port %d: %s\n",
54362306a36Sopenharmony_ci		port, new_state);
54462306a36Sopenharmony_ci}
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_cistatic void hellcreek_setup_ingressflt(struct hellcreek *hellcreek, int port,
54762306a36Sopenharmony_ci				       bool enable)
54862306a36Sopenharmony_ci{
54962306a36Sopenharmony_ci	struct hellcreek_port *hellcreek_port = &hellcreek->ports[port];
55062306a36Sopenharmony_ci	u16 ptcfg;
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
55362306a36Sopenharmony_ci
55462306a36Sopenharmony_ci	ptcfg = hellcreek_port->ptcfg;
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	if (enable)
55762306a36Sopenharmony_ci		ptcfg |= HR_PTCFG_INGRESSFLT;
55862306a36Sopenharmony_ci	else
55962306a36Sopenharmony_ci		ptcfg &= ~HR_PTCFG_INGRESSFLT;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	hellcreek_select_port(hellcreek, port);
56262306a36Sopenharmony_ci	hellcreek_write(hellcreek, ptcfg, HR_PTCFG);
56362306a36Sopenharmony_ci	hellcreek_port->ptcfg = ptcfg;
56462306a36Sopenharmony_ci
56562306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
56662306a36Sopenharmony_ci}
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_cistatic void hellcreek_setup_vlan_awareness(struct hellcreek *hellcreek,
56962306a36Sopenharmony_ci					   bool enable)
57062306a36Sopenharmony_ci{
57162306a36Sopenharmony_ci	u16 swcfg;
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ci	swcfg = hellcreek->swcfg;
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	if (enable)
57862306a36Sopenharmony_ci		swcfg |= HR_SWCFG_VLAN_UNAWARE;
57962306a36Sopenharmony_ci	else
58062306a36Sopenharmony_ci		swcfg &= ~HR_SWCFG_VLAN_UNAWARE;
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_ci	hellcreek_write(hellcreek, swcfg, HR_SWCFG);
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
58562306a36Sopenharmony_ci}
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ci/* Default setup for DSA: VLAN <X>: CPU and Port <X> egress untagged. */
58862306a36Sopenharmony_cistatic void hellcreek_setup_vlan_membership(struct dsa_switch *ds, int port,
58962306a36Sopenharmony_ci					    bool enabled)
59062306a36Sopenharmony_ci{
59162306a36Sopenharmony_ci	const u16 vid = hellcreek_private_vid(port);
59262306a36Sopenharmony_ci	int upstream = dsa_upstream_port(ds, port);
59362306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci	/* Apply vid to port as egress untagged and port vlan id */
59662306a36Sopenharmony_ci	if (enabled)
59762306a36Sopenharmony_ci		hellcreek_apply_vlan(hellcreek, port, vid, true, true);
59862306a36Sopenharmony_ci	else
59962306a36Sopenharmony_ci		hellcreek_unapply_vlan(hellcreek, port, vid);
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci	/* Apply vid to cpu port as well */
60262306a36Sopenharmony_ci	if (enabled)
60362306a36Sopenharmony_ci		hellcreek_apply_vlan(hellcreek, upstream, vid, false, true);
60462306a36Sopenharmony_ci	else
60562306a36Sopenharmony_ci		hellcreek_unapply_vlan(hellcreek, upstream, vid);
60662306a36Sopenharmony_ci}
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_cistatic void hellcreek_port_set_ucast_flood(struct hellcreek *hellcreek,
60962306a36Sopenharmony_ci					   int port, bool enable)
61062306a36Sopenharmony_ci{
61162306a36Sopenharmony_ci	struct hellcreek_port *hellcreek_port;
61262306a36Sopenharmony_ci	u16 val;
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ci	hellcreek_port = &hellcreek->ports[port];
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "%s unicast flooding on port %d\n",
61762306a36Sopenharmony_ci		enable ? "Enable" : "Disable", port);
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_ci	hellcreek_select_port(hellcreek, port);
62262306a36Sopenharmony_ci	val = hellcreek_port->ptcfg;
62362306a36Sopenharmony_ci	if (enable)
62462306a36Sopenharmony_ci		val &= ~HR_PTCFG_UUC_FLT;
62562306a36Sopenharmony_ci	else
62662306a36Sopenharmony_ci		val |= HR_PTCFG_UUC_FLT;
62762306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, HR_PTCFG);
62862306a36Sopenharmony_ci	hellcreek_port->ptcfg = val;
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
63162306a36Sopenharmony_ci}
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_cistatic void hellcreek_port_set_mcast_flood(struct hellcreek *hellcreek,
63462306a36Sopenharmony_ci					   int port, bool enable)
63562306a36Sopenharmony_ci{
63662306a36Sopenharmony_ci	struct hellcreek_port *hellcreek_port;
63762306a36Sopenharmony_ci	u16 val;
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	hellcreek_port = &hellcreek->ports[port];
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "%s multicast flooding on port %d\n",
64262306a36Sopenharmony_ci		enable ? "Enable" : "Disable", port);
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	hellcreek_select_port(hellcreek, port);
64762306a36Sopenharmony_ci	val = hellcreek_port->ptcfg;
64862306a36Sopenharmony_ci	if (enable)
64962306a36Sopenharmony_ci		val &= ~HR_PTCFG_UMC_FLT;
65062306a36Sopenharmony_ci	else
65162306a36Sopenharmony_ci		val |= HR_PTCFG_UMC_FLT;
65262306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, HR_PTCFG);
65362306a36Sopenharmony_ci	hellcreek_port->ptcfg = val;
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
65662306a36Sopenharmony_ci}
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_cistatic int hellcreek_pre_bridge_flags(struct dsa_switch *ds, int port,
65962306a36Sopenharmony_ci				      struct switchdev_brport_flags flags,
66062306a36Sopenharmony_ci				      struct netlink_ext_ack *extack)
66162306a36Sopenharmony_ci{
66262306a36Sopenharmony_ci	if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD))
66362306a36Sopenharmony_ci		return -EINVAL;
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_ci	return 0;
66662306a36Sopenharmony_ci}
66762306a36Sopenharmony_ci
66862306a36Sopenharmony_cistatic int hellcreek_bridge_flags(struct dsa_switch *ds, int port,
66962306a36Sopenharmony_ci				  struct switchdev_brport_flags flags,
67062306a36Sopenharmony_ci				  struct netlink_ext_ack *extack)
67162306a36Sopenharmony_ci{
67262306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_ci	if (flags.mask & BR_FLOOD)
67562306a36Sopenharmony_ci		hellcreek_port_set_ucast_flood(hellcreek, port,
67662306a36Sopenharmony_ci					       !!(flags.val & BR_FLOOD));
67762306a36Sopenharmony_ci
67862306a36Sopenharmony_ci	if (flags.mask & BR_MCAST_FLOOD)
67962306a36Sopenharmony_ci		hellcreek_port_set_mcast_flood(hellcreek, port,
68062306a36Sopenharmony_ci					       !!(flags.val & BR_MCAST_FLOOD));
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_ci	return 0;
68362306a36Sopenharmony_ci}
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_cistatic int hellcreek_port_bridge_join(struct dsa_switch *ds, int port,
68662306a36Sopenharmony_ci				      struct dsa_bridge bridge,
68762306a36Sopenharmony_ci				      bool *tx_fwd_offload,
68862306a36Sopenharmony_ci				      struct netlink_ext_ack *extack)
68962306a36Sopenharmony_ci{
69062306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
69162306a36Sopenharmony_ci
69262306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Port %d joins a bridge\n", port);
69362306a36Sopenharmony_ci
69462306a36Sopenharmony_ci	/* When joining a vlan_filtering bridge, keep the switch VLAN aware */
69562306a36Sopenharmony_ci	if (!ds->vlan_filtering)
69662306a36Sopenharmony_ci		hellcreek_setup_vlan_awareness(hellcreek, false);
69762306a36Sopenharmony_ci
69862306a36Sopenharmony_ci	/* Drop private vlans */
69962306a36Sopenharmony_ci	hellcreek_setup_vlan_membership(ds, port, false);
70062306a36Sopenharmony_ci
70162306a36Sopenharmony_ci	return 0;
70262306a36Sopenharmony_ci}
70362306a36Sopenharmony_ci
70462306a36Sopenharmony_cistatic void hellcreek_port_bridge_leave(struct dsa_switch *ds, int port,
70562306a36Sopenharmony_ci					struct dsa_bridge bridge)
70662306a36Sopenharmony_ci{
70762306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
70862306a36Sopenharmony_ci
70962306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Port %d leaves a bridge\n", port);
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ci	/* Enable VLAN awareness */
71262306a36Sopenharmony_ci	hellcreek_setup_vlan_awareness(hellcreek, true);
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_ci	/* Enable private vlans */
71562306a36Sopenharmony_ci	hellcreek_setup_vlan_membership(ds, port, true);
71662306a36Sopenharmony_ci}
71762306a36Sopenharmony_ci
71862306a36Sopenharmony_cistatic int __hellcreek_fdb_add(struct hellcreek *hellcreek,
71962306a36Sopenharmony_ci			       const struct hellcreek_fdb_entry *entry)
72062306a36Sopenharmony_ci{
72162306a36Sopenharmony_ci	u16 meta = 0;
72262306a36Sopenharmony_ci
72362306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Add static FDB entry: MAC=%pM, MASK=0x%02x, "
72462306a36Sopenharmony_ci		"OBT=%d, PASS_BLOCKED=%d, REPRIO_EN=%d, PRIO=%d\n", entry->mac,
72562306a36Sopenharmony_ci		entry->portmask, entry->is_obt, entry->pass_blocked,
72662306a36Sopenharmony_ci		entry->reprio_en, entry->reprio_tc);
72762306a36Sopenharmony_ci
72862306a36Sopenharmony_ci	/* Add mac address */
72962306a36Sopenharmony_ci	hellcreek_write(hellcreek, entry->mac[1] | (entry->mac[0] << 8), HR_FDBWDH);
73062306a36Sopenharmony_ci	hellcreek_write(hellcreek, entry->mac[3] | (entry->mac[2] << 8), HR_FDBWDM);
73162306a36Sopenharmony_ci	hellcreek_write(hellcreek, entry->mac[5] | (entry->mac[4] << 8), HR_FDBWDL);
73262306a36Sopenharmony_ci
73362306a36Sopenharmony_ci	/* Meta data */
73462306a36Sopenharmony_ci	meta |= entry->portmask << HR_FDBWRM0_PORTMASK_SHIFT;
73562306a36Sopenharmony_ci	if (entry->is_obt)
73662306a36Sopenharmony_ci		meta |= HR_FDBWRM0_OBT;
73762306a36Sopenharmony_ci	if (entry->pass_blocked)
73862306a36Sopenharmony_ci		meta |= HR_FDBWRM0_PASS_BLOCKED;
73962306a36Sopenharmony_ci	if (entry->reprio_en) {
74062306a36Sopenharmony_ci		meta |= HR_FDBWRM0_REPRIO_EN;
74162306a36Sopenharmony_ci		meta |= entry->reprio_tc << HR_FDBWRM0_REPRIO_TC_SHIFT;
74262306a36Sopenharmony_ci	}
74362306a36Sopenharmony_ci	hellcreek_write(hellcreek, meta, HR_FDBWRM0);
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_ci	/* Commit */
74662306a36Sopenharmony_ci	hellcreek_write(hellcreek, 0x00, HR_FDBWRCMD);
74762306a36Sopenharmony_ci
74862306a36Sopenharmony_ci	/* Wait until done */
74962306a36Sopenharmony_ci	return hellcreek_wait_fdb_ready(hellcreek);
75062306a36Sopenharmony_ci}
75162306a36Sopenharmony_ci
75262306a36Sopenharmony_cistatic int __hellcreek_fdb_del(struct hellcreek *hellcreek,
75362306a36Sopenharmony_ci			       const struct hellcreek_fdb_entry *entry)
75462306a36Sopenharmony_ci{
75562306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Delete FDB entry: MAC=%pM!\n", entry->mac);
75662306a36Sopenharmony_ci
75762306a36Sopenharmony_ci	/* Delete by matching idx */
75862306a36Sopenharmony_ci	hellcreek_write(hellcreek, entry->idx | HR_FDBWRCMD_FDBDEL, HR_FDBWRCMD);
75962306a36Sopenharmony_ci
76062306a36Sopenharmony_ci	/* Wait until done */
76162306a36Sopenharmony_ci	return hellcreek_wait_fdb_ready(hellcreek);
76262306a36Sopenharmony_ci}
76362306a36Sopenharmony_ci
76462306a36Sopenharmony_cistatic void hellcreek_populate_fdb_entry(struct hellcreek *hellcreek,
76562306a36Sopenharmony_ci					 struct hellcreek_fdb_entry *entry,
76662306a36Sopenharmony_ci					 size_t idx)
76762306a36Sopenharmony_ci{
76862306a36Sopenharmony_ci	unsigned char addr[ETH_ALEN];
76962306a36Sopenharmony_ci	u16 meta, mac;
77062306a36Sopenharmony_ci
77162306a36Sopenharmony_ci	/* Read values */
77262306a36Sopenharmony_ci	meta	= hellcreek_read(hellcreek, HR_FDBMDRD);
77362306a36Sopenharmony_ci	mac	= hellcreek_read(hellcreek, HR_FDBRDL);
77462306a36Sopenharmony_ci	addr[5] = mac & 0xff;
77562306a36Sopenharmony_ci	addr[4] = (mac & 0xff00) >> 8;
77662306a36Sopenharmony_ci	mac	= hellcreek_read(hellcreek, HR_FDBRDM);
77762306a36Sopenharmony_ci	addr[3] = mac & 0xff;
77862306a36Sopenharmony_ci	addr[2] = (mac & 0xff00) >> 8;
77962306a36Sopenharmony_ci	mac	= hellcreek_read(hellcreek, HR_FDBRDH);
78062306a36Sopenharmony_ci	addr[1] = mac & 0xff;
78162306a36Sopenharmony_ci	addr[0] = (mac & 0xff00) >> 8;
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_ci	/* Populate @entry */
78462306a36Sopenharmony_ci	memcpy(entry->mac, addr, sizeof(addr));
78562306a36Sopenharmony_ci	entry->idx	    = idx;
78662306a36Sopenharmony_ci	entry->portmask	    = (meta & HR_FDBMDRD_PORTMASK_MASK) >>
78762306a36Sopenharmony_ci		HR_FDBMDRD_PORTMASK_SHIFT;
78862306a36Sopenharmony_ci	entry->age	    = (meta & HR_FDBMDRD_AGE_MASK) >>
78962306a36Sopenharmony_ci		HR_FDBMDRD_AGE_SHIFT;
79062306a36Sopenharmony_ci	entry->is_obt	    = !!(meta & HR_FDBMDRD_OBT);
79162306a36Sopenharmony_ci	entry->pass_blocked = !!(meta & HR_FDBMDRD_PASS_BLOCKED);
79262306a36Sopenharmony_ci	entry->is_static    = !!(meta & HR_FDBMDRD_STATIC);
79362306a36Sopenharmony_ci	entry->reprio_tc    = (meta & HR_FDBMDRD_REPRIO_TC_MASK) >>
79462306a36Sopenharmony_ci		HR_FDBMDRD_REPRIO_TC_SHIFT;
79562306a36Sopenharmony_ci	entry->reprio_en    = !!(meta & HR_FDBMDRD_REPRIO_EN);
79662306a36Sopenharmony_ci}
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_ci/* Retrieve the index of a FDB entry by mac address. Currently we search through
79962306a36Sopenharmony_ci * the complete table in hardware. If that's too slow, we might have to cache
80062306a36Sopenharmony_ci * the complete FDB table in software.
80162306a36Sopenharmony_ci */
80262306a36Sopenharmony_cistatic int hellcreek_fdb_get(struct hellcreek *hellcreek,
80362306a36Sopenharmony_ci			     const unsigned char *dest,
80462306a36Sopenharmony_ci			     struct hellcreek_fdb_entry *entry)
80562306a36Sopenharmony_ci{
80662306a36Sopenharmony_ci	size_t i;
80762306a36Sopenharmony_ci
80862306a36Sopenharmony_ci	/* Set read pointer to zero: The read of HR_FDBMAX (read-only register)
80962306a36Sopenharmony_ci	 * should reset the internal pointer. But, that doesn't work. The vendor
81062306a36Sopenharmony_ci	 * suggested a subsequent write as workaround. Same for HR_FDBRDH below.
81162306a36Sopenharmony_ci	 */
81262306a36Sopenharmony_ci	hellcreek_read(hellcreek, HR_FDBMAX);
81362306a36Sopenharmony_ci	hellcreek_write(hellcreek, 0x00, HR_FDBMAX);
81462306a36Sopenharmony_ci
81562306a36Sopenharmony_ci	/* We have to read the complete table, because the switch/driver might
81662306a36Sopenharmony_ci	 * enter new entries anywhere.
81762306a36Sopenharmony_ci	 */
81862306a36Sopenharmony_ci	for (i = 0; i < hellcreek->fdb_entries; ++i) {
81962306a36Sopenharmony_ci		struct hellcreek_fdb_entry tmp = { 0 };
82062306a36Sopenharmony_ci
82162306a36Sopenharmony_ci		/* Read entry */
82262306a36Sopenharmony_ci		hellcreek_populate_fdb_entry(hellcreek, &tmp, i);
82362306a36Sopenharmony_ci
82462306a36Sopenharmony_ci		/* Force next entry */
82562306a36Sopenharmony_ci		hellcreek_write(hellcreek, 0x00, HR_FDBRDH);
82662306a36Sopenharmony_ci
82762306a36Sopenharmony_ci		if (memcmp(tmp.mac, dest, ETH_ALEN))
82862306a36Sopenharmony_ci			continue;
82962306a36Sopenharmony_ci
83062306a36Sopenharmony_ci		/* Match found */
83162306a36Sopenharmony_ci		memcpy(entry, &tmp, sizeof(*entry));
83262306a36Sopenharmony_ci
83362306a36Sopenharmony_ci		return 0;
83462306a36Sopenharmony_ci	}
83562306a36Sopenharmony_ci
83662306a36Sopenharmony_ci	return -ENOENT;
83762306a36Sopenharmony_ci}
83862306a36Sopenharmony_ci
83962306a36Sopenharmony_cistatic int hellcreek_fdb_add(struct dsa_switch *ds, int port,
84062306a36Sopenharmony_ci			     const unsigned char *addr, u16 vid,
84162306a36Sopenharmony_ci			     struct dsa_db db)
84262306a36Sopenharmony_ci{
84362306a36Sopenharmony_ci	struct hellcreek_fdb_entry entry = { 0 };
84462306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
84562306a36Sopenharmony_ci	int ret;
84662306a36Sopenharmony_ci
84762306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Add FDB entry for MAC=%pM\n", addr);
84862306a36Sopenharmony_ci
84962306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
85062306a36Sopenharmony_ci
85162306a36Sopenharmony_ci	ret = hellcreek_fdb_get(hellcreek, addr, &entry);
85262306a36Sopenharmony_ci	if (ret) {
85362306a36Sopenharmony_ci		/* Not found */
85462306a36Sopenharmony_ci		memcpy(entry.mac, addr, sizeof(entry.mac));
85562306a36Sopenharmony_ci		entry.portmask = BIT(port);
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_ci		ret = __hellcreek_fdb_add(hellcreek, &entry);
85862306a36Sopenharmony_ci		if (ret) {
85962306a36Sopenharmony_ci			dev_err(hellcreek->dev, "Failed to add FDB entry!\n");
86062306a36Sopenharmony_ci			goto out;
86162306a36Sopenharmony_ci		}
86262306a36Sopenharmony_ci	} else {
86362306a36Sopenharmony_ci		/* Found */
86462306a36Sopenharmony_ci		ret = __hellcreek_fdb_del(hellcreek, &entry);
86562306a36Sopenharmony_ci		if (ret) {
86662306a36Sopenharmony_ci			dev_err(hellcreek->dev, "Failed to delete FDB entry!\n");
86762306a36Sopenharmony_ci			goto out;
86862306a36Sopenharmony_ci		}
86962306a36Sopenharmony_ci
87062306a36Sopenharmony_ci		entry.portmask |= BIT(port);
87162306a36Sopenharmony_ci
87262306a36Sopenharmony_ci		ret = __hellcreek_fdb_add(hellcreek, &entry);
87362306a36Sopenharmony_ci		if (ret) {
87462306a36Sopenharmony_ci			dev_err(hellcreek->dev, "Failed to add FDB entry!\n");
87562306a36Sopenharmony_ci			goto out;
87662306a36Sopenharmony_ci		}
87762306a36Sopenharmony_ci	}
87862306a36Sopenharmony_ci
87962306a36Sopenharmony_ciout:
88062306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
88162306a36Sopenharmony_ci
88262306a36Sopenharmony_ci	return ret;
88362306a36Sopenharmony_ci}
88462306a36Sopenharmony_ci
88562306a36Sopenharmony_cistatic int hellcreek_fdb_del(struct dsa_switch *ds, int port,
88662306a36Sopenharmony_ci			     const unsigned char *addr, u16 vid,
88762306a36Sopenharmony_ci			     struct dsa_db db)
88862306a36Sopenharmony_ci{
88962306a36Sopenharmony_ci	struct hellcreek_fdb_entry entry = { 0 };
89062306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
89162306a36Sopenharmony_ci	int ret;
89262306a36Sopenharmony_ci
89362306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Delete FDB entry for MAC=%pM\n", addr);
89462306a36Sopenharmony_ci
89562306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
89662306a36Sopenharmony_ci
89762306a36Sopenharmony_ci	ret = hellcreek_fdb_get(hellcreek, addr, &entry);
89862306a36Sopenharmony_ci	if (ret) {
89962306a36Sopenharmony_ci		/* Not found */
90062306a36Sopenharmony_ci		dev_err(hellcreek->dev, "FDB entry for deletion not found!\n");
90162306a36Sopenharmony_ci	} else {
90262306a36Sopenharmony_ci		/* Found */
90362306a36Sopenharmony_ci		ret = __hellcreek_fdb_del(hellcreek, &entry);
90462306a36Sopenharmony_ci		if (ret) {
90562306a36Sopenharmony_ci			dev_err(hellcreek->dev, "Failed to delete FDB entry!\n");
90662306a36Sopenharmony_ci			goto out;
90762306a36Sopenharmony_ci		}
90862306a36Sopenharmony_ci
90962306a36Sopenharmony_ci		entry.portmask &= ~BIT(port);
91062306a36Sopenharmony_ci
91162306a36Sopenharmony_ci		if (entry.portmask != 0x00) {
91262306a36Sopenharmony_ci			ret = __hellcreek_fdb_add(hellcreek, &entry);
91362306a36Sopenharmony_ci			if (ret) {
91462306a36Sopenharmony_ci				dev_err(hellcreek->dev, "Failed to add FDB entry!\n");
91562306a36Sopenharmony_ci				goto out;
91662306a36Sopenharmony_ci			}
91762306a36Sopenharmony_ci		}
91862306a36Sopenharmony_ci	}
91962306a36Sopenharmony_ci
92062306a36Sopenharmony_ciout:
92162306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
92262306a36Sopenharmony_ci
92362306a36Sopenharmony_ci	return ret;
92462306a36Sopenharmony_ci}
92562306a36Sopenharmony_ci
92662306a36Sopenharmony_cistatic int hellcreek_fdb_dump(struct dsa_switch *ds, int port,
92762306a36Sopenharmony_ci			      dsa_fdb_dump_cb_t *cb, void *data)
92862306a36Sopenharmony_ci{
92962306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
93062306a36Sopenharmony_ci	u16 entries;
93162306a36Sopenharmony_ci	int ret = 0;
93262306a36Sopenharmony_ci	size_t i;
93362306a36Sopenharmony_ci
93462306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
93562306a36Sopenharmony_ci
93662306a36Sopenharmony_ci	/* Set read pointer to zero: The read of HR_FDBMAX (read-only register)
93762306a36Sopenharmony_ci	 * should reset the internal pointer. But, that doesn't work. The vendor
93862306a36Sopenharmony_ci	 * suggested a subsequent write as workaround. Same for HR_FDBRDH below.
93962306a36Sopenharmony_ci	 */
94062306a36Sopenharmony_ci	entries = hellcreek_read(hellcreek, HR_FDBMAX);
94162306a36Sopenharmony_ci	hellcreek_write(hellcreek, 0x00, HR_FDBMAX);
94262306a36Sopenharmony_ci
94362306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "FDB dump for port %d, entries=%d!\n", port, entries);
94462306a36Sopenharmony_ci
94562306a36Sopenharmony_ci	/* Read table */
94662306a36Sopenharmony_ci	for (i = 0; i < hellcreek->fdb_entries; ++i) {
94762306a36Sopenharmony_ci		struct hellcreek_fdb_entry entry = { 0 };
94862306a36Sopenharmony_ci
94962306a36Sopenharmony_ci		/* Read entry */
95062306a36Sopenharmony_ci		hellcreek_populate_fdb_entry(hellcreek, &entry, i);
95162306a36Sopenharmony_ci
95262306a36Sopenharmony_ci		/* Force next entry */
95362306a36Sopenharmony_ci		hellcreek_write(hellcreek, 0x00, HR_FDBRDH);
95462306a36Sopenharmony_ci
95562306a36Sopenharmony_ci		/* Check valid */
95662306a36Sopenharmony_ci		if (is_zero_ether_addr(entry.mac))
95762306a36Sopenharmony_ci			continue;
95862306a36Sopenharmony_ci
95962306a36Sopenharmony_ci		/* Check port mask */
96062306a36Sopenharmony_ci		if (!(entry.portmask & BIT(port)))
96162306a36Sopenharmony_ci			continue;
96262306a36Sopenharmony_ci
96362306a36Sopenharmony_ci		ret = cb(entry.mac, 0, entry.is_static, data);
96462306a36Sopenharmony_ci		if (ret)
96562306a36Sopenharmony_ci			break;
96662306a36Sopenharmony_ci	}
96762306a36Sopenharmony_ci
96862306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
96962306a36Sopenharmony_ci
97062306a36Sopenharmony_ci	return ret;
97162306a36Sopenharmony_ci}
97262306a36Sopenharmony_ci
97362306a36Sopenharmony_cistatic int hellcreek_vlan_filtering(struct dsa_switch *ds, int port,
97462306a36Sopenharmony_ci				    bool vlan_filtering,
97562306a36Sopenharmony_ci				    struct netlink_ext_ack *extack)
97662306a36Sopenharmony_ci{
97762306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
97862306a36Sopenharmony_ci
97962306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "%s VLAN filtering on port %d\n",
98062306a36Sopenharmony_ci		vlan_filtering ? "Enable" : "Disable", port);
98162306a36Sopenharmony_ci
98262306a36Sopenharmony_ci	/* Configure port to drop packages with not known vids */
98362306a36Sopenharmony_ci	hellcreek_setup_ingressflt(hellcreek, port, vlan_filtering);
98462306a36Sopenharmony_ci
98562306a36Sopenharmony_ci	/* Enable VLAN awareness on the switch. This save due to
98662306a36Sopenharmony_ci	 * ds->vlan_filtering_is_global.
98762306a36Sopenharmony_ci	 */
98862306a36Sopenharmony_ci	hellcreek_setup_vlan_awareness(hellcreek, vlan_filtering);
98962306a36Sopenharmony_ci
99062306a36Sopenharmony_ci	return 0;
99162306a36Sopenharmony_ci}
99262306a36Sopenharmony_ci
99362306a36Sopenharmony_cistatic int hellcreek_enable_ip_core(struct hellcreek *hellcreek)
99462306a36Sopenharmony_ci{
99562306a36Sopenharmony_ci	int ret;
99662306a36Sopenharmony_ci	u16 val;
99762306a36Sopenharmony_ci
99862306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
99962306a36Sopenharmony_ci
100062306a36Sopenharmony_ci	val = hellcreek_read(hellcreek, HR_CTRL_C);
100162306a36Sopenharmony_ci	val |= HR_CTRL_C_ENABLE;
100262306a36Sopenharmony_ci	hellcreek_write(hellcreek, val, HR_CTRL_C);
100362306a36Sopenharmony_ci	ret = hellcreek_wait_until_transitioned(hellcreek);
100462306a36Sopenharmony_ci
100562306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
100662306a36Sopenharmony_ci
100762306a36Sopenharmony_ci	return ret;
100862306a36Sopenharmony_ci}
100962306a36Sopenharmony_ci
101062306a36Sopenharmony_cistatic void hellcreek_setup_cpu_and_tunnel_port(struct hellcreek *hellcreek)
101162306a36Sopenharmony_ci{
101262306a36Sopenharmony_ci	struct hellcreek_port *tunnel_port = &hellcreek->ports[TUNNEL_PORT];
101362306a36Sopenharmony_ci	struct hellcreek_port *cpu_port = &hellcreek->ports[CPU_PORT];
101462306a36Sopenharmony_ci	u16 ptcfg = 0;
101562306a36Sopenharmony_ci
101662306a36Sopenharmony_ci	ptcfg |= HR_PTCFG_LEARNING_EN | HR_PTCFG_ADMIN_EN;
101762306a36Sopenharmony_ci
101862306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
101962306a36Sopenharmony_ci
102062306a36Sopenharmony_ci	hellcreek_select_port(hellcreek, CPU_PORT);
102162306a36Sopenharmony_ci	hellcreek_write(hellcreek, ptcfg, HR_PTCFG);
102262306a36Sopenharmony_ci
102362306a36Sopenharmony_ci	hellcreek_select_port(hellcreek, TUNNEL_PORT);
102462306a36Sopenharmony_ci	hellcreek_write(hellcreek, ptcfg, HR_PTCFG);
102562306a36Sopenharmony_ci
102662306a36Sopenharmony_ci	cpu_port->ptcfg	   = ptcfg;
102762306a36Sopenharmony_ci	tunnel_port->ptcfg = ptcfg;
102862306a36Sopenharmony_ci
102962306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
103062306a36Sopenharmony_ci}
103162306a36Sopenharmony_ci
103262306a36Sopenharmony_cistatic void hellcreek_setup_tc_identity_mapping(struct hellcreek *hellcreek)
103362306a36Sopenharmony_ci{
103462306a36Sopenharmony_ci	int i;
103562306a36Sopenharmony_ci
103662306a36Sopenharmony_ci	/* The switch has multiple egress queues per port. The queue is selected
103762306a36Sopenharmony_ci	 * via the PCP field in the VLAN header. The switch internally deals
103862306a36Sopenharmony_ci	 * with traffic classes instead of PCP values and this mapping is
103962306a36Sopenharmony_ci	 * configurable.
104062306a36Sopenharmony_ci	 *
104162306a36Sopenharmony_ci	 * The default mapping is (PCP - TC):
104262306a36Sopenharmony_ci	 *  7 - 7
104362306a36Sopenharmony_ci	 *  6 - 6
104462306a36Sopenharmony_ci	 *  5 - 5
104562306a36Sopenharmony_ci	 *  4 - 4
104662306a36Sopenharmony_ci	 *  3 - 3
104762306a36Sopenharmony_ci	 *  2 - 1
104862306a36Sopenharmony_ci	 *  1 - 0
104962306a36Sopenharmony_ci	 *  0 - 2
105062306a36Sopenharmony_ci	 *
105162306a36Sopenharmony_ci	 * The default should be an identity mapping.
105262306a36Sopenharmony_ci	 */
105362306a36Sopenharmony_ci
105462306a36Sopenharmony_ci	for (i = 0; i < 8; ++i) {
105562306a36Sopenharmony_ci		mutex_lock(&hellcreek->reg_lock);
105662306a36Sopenharmony_ci
105762306a36Sopenharmony_ci		hellcreek_select_prio(hellcreek, i);
105862306a36Sopenharmony_ci		hellcreek_write(hellcreek,
105962306a36Sopenharmony_ci				i << HR_PRTCCFG_PCP_TC_MAP_SHIFT,
106062306a36Sopenharmony_ci				HR_PRTCCFG);
106162306a36Sopenharmony_ci
106262306a36Sopenharmony_ci		mutex_unlock(&hellcreek->reg_lock);
106362306a36Sopenharmony_ci	}
106462306a36Sopenharmony_ci}
106562306a36Sopenharmony_ci
106662306a36Sopenharmony_cistatic int hellcreek_setup_fdb(struct hellcreek *hellcreek)
106762306a36Sopenharmony_ci{
106862306a36Sopenharmony_ci	static struct hellcreek_fdb_entry l2_ptp = {
106962306a36Sopenharmony_ci		/* MAC: 01-1B-19-00-00-00 */
107062306a36Sopenharmony_ci		.mac	      = { 0x01, 0x1b, 0x19, 0x00, 0x00, 0x00 },
107162306a36Sopenharmony_ci		.portmask     = 0x03,	/* Management ports */
107262306a36Sopenharmony_ci		.age	      = 0,
107362306a36Sopenharmony_ci		.is_obt	      = 0,
107462306a36Sopenharmony_ci		.pass_blocked = 0,
107562306a36Sopenharmony_ci		.is_static    = 1,
107662306a36Sopenharmony_ci		.reprio_tc    = 6,	/* TC: 6 as per IEEE 802.1AS */
107762306a36Sopenharmony_ci		.reprio_en    = 1,
107862306a36Sopenharmony_ci	};
107962306a36Sopenharmony_ci	static struct hellcreek_fdb_entry udp4_ptp = {
108062306a36Sopenharmony_ci		/* MAC: 01-00-5E-00-01-81 */
108162306a36Sopenharmony_ci		.mac	      = { 0x01, 0x00, 0x5e, 0x00, 0x01, 0x81 },
108262306a36Sopenharmony_ci		.portmask     = 0x03,	/* Management ports */
108362306a36Sopenharmony_ci		.age	      = 0,
108462306a36Sopenharmony_ci		.is_obt	      = 0,
108562306a36Sopenharmony_ci		.pass_blocked = 0,
108662306a36Sopenharmony_ci		.is_static    = 1,
108762306a36Sopenharmony_ci		.reprio_tc    = 6,
108862306a36Sopenharmony_ci		.reprio_en    = 1,
108962306a36Sopenharmony_ci	};
109062306a36Sopenharmony_ci	static struct hellcreek_fdb_entry udp6_ptp = {
109162306a36Sopenharmony_ci		/* MAC: 33-33-00-00-01-81 */
109262306a36Sopenharmony_ci		.mac	      = { 0x33, 0x33, 0x00, 0x00, 0x01, 0x81 },
109362306a36Sopenharmony_ci		.portmask     = 0x03,	/* Management ports */
109462306a36Sopenharmony_ci		.age	      = 0,
109562306a36Sopenharmony_ci		.is_obt	      = 0,
109662306a36Sopenharmony_ci		.pass_blocked = 0,
109762306a36Sopenharmony_ci		.is_static    = 1,
109862306a36Sopenharmony_ci		.reprio_tc    = 6,
109962306a36Sopenharmony_ci		.reprio_en    = 1,
110062306a36Sopenharmony_ci	};
110162306a36Sopenharmony_ci	static struct hellcreek_fdb_entry l2_p2p = {
110262306a36Sopenharmony_ci		/* MAC: 01-80-C2-00-00-0E */
110362306a36Sopenharmony_ci		.mac	      = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e },
110462306a36Sopenharmony_ci		.portmask     = 0x03,	/* Management ports */
110562306a36Sopenharmony_ci		.age	      = 0,
110662306a36Sopenharmony_ci		.is_obt	      = 0,
110762306a36Sopenharmony_ci		.pass_blocked = 1,
110862306a36Sopenharmony_ci		.is_static    = 1,
110962306a36Sopenharmony_ci		.reprio_tc    = 6,	/* TC: 6 as per IEEE 802.1AS */
111062306a36Sopenharmony_ci		.reprio_en    = 1,
111162306a36Sopenharmony_ci	};
111262306a36Sopenharmony_ci	static struct hellcreek_fdb_entry udp4_p2p = {
111362306a36Sopenharmony_ci		/* MAC: 01-00-5E-00-00-6B */
111462306a36Sopenharmony_ci		.mac	      = { 0x01, 0x00, 0x5e, 0x00, 0x00, 0x6b },
111562306a36Sopenharmony_ci		.portmask     = 0x03,	/* Management ports */
111662306a36Sopenharmony_ci		.age	      = 0,
111762306a36Sopenharmony_ci		.is_obt	      = 0,
111862306a36Sopenharmony_ci		.pass_blocked = 1,
111962306a36Sopenharmony_ci		.is_static    = 1,
112062306a36Sopenharmony_ci		.reprio_tc    = 6,
112162306a36Sopenharmony_ci		.reprio_en    = 1,
112262306a36Sopenharmony_ci	};
112362306a36Sopenharmony_ci	static struct hellcreek_fdb_entry udp6_p2p = {
112462306a36Sopenharmony_ci		/* MAC: 33-33-00-00-00-6B */
112562306a36Sopenharmony_ci		.mac	      = { 0x33, 0x33, 0x00, 0x00, 0x00, 0x6b },
112662306a36Sopenharmony_ci		.portmask     = 0x03,	/* Management ports */
112762306a36Sopenharmony_ci		.age	      = 0,
112862306a36Sopenharmony_ci		.is_obt	      = 0,
112962306a36Sopenharmony_ci		.pass_blocked = 1,
113062306a36Sopenharmony_ci		.is_static    = 1,
113162306a36Sopenharmony_ci		.reprio_tc    = 6,
113262306a36Sopenharmony_ci		.reprio_en    = 1,
113362306a36Sopenharmony_ci	};
113462306a36Sopenharmony_ci	static struct hellcreek_fdb_entry stp = {
113562306a36Sopenharmony_ci		/* MAC: 01-80-C2-00-00-00 */
113662306a36Sopenharmony_ci		.mac	      = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 },
113762306a36Sopenharmony_ci		.portmask     = 0x03,	/* Management ports */
113862306a36Sopenharmony_ci		.age	      = 0,
113962306a36Sopenharmony_ci		.is_obt	      = 0,
114062306a36Sopenharmony_ci		.pass_blocked = 1,
114162306a36Sopenharmony_ci		.is_static    = 1,
114262306a36Sopenharmony_ci		.reprio_tc    = 6,
114362306a36Sopenharmony_ci		.reprio_en    = 1,
114462306a36Sopenharmony_ci	};
114562306a36Sopenharmony_ci	int ret;
114662306a36Sopenharmony_ci
114762306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
114862306a36Sopenharmony_ci	ret = __hellcreek_fdb_add(hellcreek, &l2_ptp);
114962306a36Sopenharmony_ci	if (ret)
115062306a36Sopenharmony_ci		goto out;
115162306a36Sopenharmony_ci	ret = __hellcreek_fdb_add(hellcreek, &udp4_ptp);
115262306a36Sopenharmony_ci	if (ret)
115362306a36Sopenharmony_ci		goto out;
115462306a36Sopenharmony_ci	ret = __hellcreek_fdb_add(hellcreek, &udp6_ptp);
115562306a36Sopenharmony_ci	if (ret)
115662306a36Sopenharmony_ci		goto out;
115762306a36Sopenharmony_ci	ret = __hellcreek_fdb_add(hellcreek, &l2_p2p);
115862306a36Sopenharmony_ci	if (ret)
115962306a36Sopenharmony_ci		goto out;
116062306a36Sopenharmony_ci	ret = __hellcreek_fdb_add(hellcreek, &udp4_p2p);
116162306a36Sopenharmony_ci	if (ret)
116262306a36Sopenharmony_ci		goto out;
116362306a36Sopenharmony_ci	ret = __hellcreek_fdb_add(hellcreek, &udp6_p2p);
116462306a36Sopenharmony_ci	if (ret)
116562306a36Sopenharmony_ci		goto out;
116662306a36Sopenharmony_ci	ret = __hellcreek_fdb_add(hellcreek, &stp);
116762306a36Sopenharmony_ciout:
116862306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
116962306a36Sopenharmony_ci
117062306a36Sopenharmony_ci	return ret;
117162306a36Sopenharmony_ci}
117262306a36Sopenharmony_ci
117362306a36Sopenharmony_cistatic int hellcreek_devlink_info_get(struct dsa_switch *ds,
117462306a36Sopenharmony_ci				      struct devlink_info_req *req,
117562306a36Sopenharmony_ci				      struct netlink_ext_ack *extack)
117662306a36Sopenharmony_ci{
117762306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
117862306a36Sopenharmony_ci
117962306a36Sopenharmony_ci	return devlink_info_version_fixed_put(req,
118062306a36Sopenharmony_ci					      DEVLINK_INFO_VERSION_GENERIC_ASIC_ID,
118162306a36Sopenharmony_ci					      hellcreek->pdata->name);
118262306a36Sopenharmony_ci}
118362306a36Sopenharmony_ci
118462306a36Sopenharmony_cistatic u64 hellcreek_devlink_vlan_table_get(void *priv)
118562306a36Sopenharmony_ci{
118662306a36Sopenharmony_ci	struct hellcreek *hellcreek = priv;
118762306a36Sopenharmony_ci	u64 count = 0;
118862306a36Sopenharmony_ci	int i;
118962306a36Sopenharmony_ci
119062306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
119162306a36Sopenharmony_ci	for (i = 0; i < VLAN_N_VID; ++i)
119262306a36Sopenharmony_ci		if (hellcreek->vidmbrcfg[i])
119362306a36Sopenharmony_ci			count++;
119462306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
119562306a36Sopenharmony_ci
119662306a36Sopenharmony_ci	return count;
119762306a36Sopenharmony_ci}
119862306a36Sopenharmony_ci
119962306a36Sopenharmony_cistatic u64 hellcreek_devlink_fdb_table_get(void *priv)
120062306a36Sopenharmony_ci{
120162306a36Sopenharmony_ci	struct hellcreek *hellcreek = priv;
120262306a36Sopenharmony_ci	u64 count = 0;
120362306a36Sopenharmony_ci
120462306a36Sopenharmony_ci	/* Reading this register has side effects. Synchronize against the other
120562306a36Sopenharmony_ci	 * FDB operations.
120662306a36Sopenharmony_ci	 */
120762306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
120862306a36Sopenharmony_ci	count = hellcreek_read(hellcreek, HR_FDBMAX);
120962306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
121062306a36Sopenharmony_ci
121162306a36Sopenharmony_ci	return count;
121262306a36Sopenharmony_ci}
121362306a36Sopenharmony_ci
121462306a36Sopenharmony_cistatic int hellcreek_setup_devlink_resources(struct dsa_switch *ds)
121562306a36Sopenharmony_ci{
121662306a36Sopenharmony_ci	struct devlink_resource_size_params size_vlan_params;
121762306a36Sopenharmony_ci	struct devlink_resource_size_params size_fdb_params;
121862306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
121962306a36Sopenharmony_ci	int err;
122062306a36Sopenharmony_ci
122162306a36Sopenharmony_ci	devlink_resource_size_params_init(&size_vlan_params, VLAN_N_VID,
122262306a36Sopenharmony_ci					  VLAN_N_VID,
122362306a36Sopenharmony_ci					  1, DEVLINK_RESOURCE_UNIT_ENTRY);
122462306a36Sopenharmony_ci
122562306a36Sopenharmony_ci	devlink_resource_size_params_init(&size_fdb_params,
122662306a36Sopenharmony_ci					  hellcreek->fdb_entries,
122762306a36Sopenharmony_ci					  hellcreek->fdb_entries,
122862306a36Sopenharmony_ci					  1, DEVLINK_RESOURCE_UNIT_ENTRY);
122962306a36Sopenharmony_ci
123062306a36Sopenharmony_ci	err = dsa_devlink_resource_register(ds, "VLAN", VLAN_N_VID,
123162306a36Sopenharmony_ci					    HELLCREEK_DEVLINK_PARAM_ID_VLAN_TABLE,
123262306a36Sopenharmony_ci					    DEVLINK_RESOURCE_ID_PARENT_TOP,
123362306a36Sopenharmony_ci					    &size_vlan_params);
123462306a36Sopenharmony_ci	if (err)
123562306a36Sopenharmony_ci		goto out;
123662306a36Sopenharmony_ci
123762306a36Sopenharmony_ci	err = dsa_devlink_resource_register(ds, "FDB", hellcreek->fdb_entries,
123862306a36Sopenharmony_ci					    HELLCREEK_DEVLINK_PARAM_ID_FDB_TABLE,
123962306a36Sopenharmony_ci					    DEVLINK_RESOURCE_ID_PARENT_TOP,
124062306a36Sopenharmony_ci					    &size_fdb_params);
124162306a36Sopenharmony_ci	if (err)
124262306a36Sopenharmony_ci		goto out;
124362306a36Sopenharmony_ci
124462306a36Sopenharmony_ci	dsa_devlink_resource_occ_get_register(ds,
124562306a36Sopenharmony_ci					      HELLCREEK_DEVLINK_PARAM_ID_VLAN_TABLE,
124662306a36Sopenharmony_ci					      hellcreek_devlink_vlan_table_get,
124762306a36Sopenharmony_ci					      hellcreek);
124862306a36Sopenharmony_ci
124962306a36Sopenharmony_ci	dsa_devlink_resource_occ_get_register(ds,
125062306a36Sopenharmony_ci					      HELLCREEK_DEVLINK_PARAM_ID_FDB_TABLE,
125162306a36Sopenharmony_ci					      hellcreek_devlink_fdb_table_get,
125262306a36Sopenharmony_ci					      hellcreek);
125362306a36Sopenharmony_ci
125462306a36Sopenharmony_ci	return 0;
125562306a36Sopenharmony_ci
125662306a36Sopenharmony_ciout:
125762306a36Sopenharmony_ci	dsa_devlink_resources_unregister(ds);
125862306a36Sopenharmony_ci
125962306a36Sopenharmony_ci	return err;
126062306a36Sopenharmony_ci}
126162306a36Sopenharmony_ci
126262306a36Sopenharmony_cistatic int hellcreek_devlink_region_vlan_snapshot(struct devlink *dl,
126362306a36Sopenharmony_ci						  const struct devlink_region_ops *ops,
126462306a36Sopenharmony_ci						  struct netlink_ext_ack *extack,
126562306a36Sopenharmony_ci						  u8 **data)
126662306a36Sopenharmony_ci{
126762306a36Sopenharmony_ci	struct hellcreek_devlink_vlan_entry *table, *entry;
126862306a36Sopenharmony_ci	struct dsa_switch *ds = dsa_devlink_to_ds(dl);
126962306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
127062306a36Sopenharmony_ci	int i;
127162306a36Sopenharmony_ci
127262306a36Sopenharmony_ci	table = kcalloc(VLAN_N_VID, sizeof(*entry), GFP_KERNEL);
127362306a36Sopenharmony_ci	if (!table)
127462306a36Sopenharmony_ci		return -ENOMEM;
127562306a36Sopenharmony_ci
127662306a36Sopenharmony_ci	entry = table;
127762306a36Sopenharmony_ci
127862306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
127962306a36Sopenharmony_ci	for (i = 0; i < VLAN_N_VID; ++i, ++entry) {
128062306a36Sopenharmony_ci		entry->member = hellcreek->vidmbrcfg[i];
128162306a36Sopenharmony_ci		entry->vid    = i;
128262306a36Sopenharmony_ci	}
128362306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
128462306a36Sopenharmony_ci
128562306a36Sopenharmony_ci	*data = (u8 *)table;
128662306a36Sopenharmony_ci
128762306a36Sopenharmony_ci	return 0;
128862306a36Sopenharmony_ci}
128962306a36Sopenharmony_ci
129062306a36Sopenharmony_cistatic int hellcreek_devlink_region_fdb_snapshot(struct devlink *dl,
129162306a36Sopenharmony_ci						 const struct devlink_region_ops *ops,
129262306a36Sopenharmony_ci						 struct netlink_ext_ack *extack,
129362306a36Sopenharmony_ci						 u8 **data)
129462306a36Sopenharmony_ci{
129562306a36Sopenharmony_ci	struct dsa_switch *ds = dsa_devlink_to_ds(dl);
129662306a36Sopenharmony_ci	struct hellcreek_fdb_entry *table, *entry;
129762306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
129862306a36Sopenharmony_ci	size_t i;
129962306a36Sopenharmony_ci
130062306a36Sopenharmony_ci	table = kcalloc(hellcreek->fdb_entries, sizeof(*entry), GFP_KERNEL);
130162306a36Sopenharmony_ci	if (!table)
130262306a36Sopenharmony_ci		return -ENOMEM;
130362306a36Sopenharmony_ci
130462306a36Sopenharmony_ci	entry = table;
130562306a36Sopenharmony_ci
130662306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
130762306a36Sopenharmony_ci
130862306a36Sopenharmony_ci	/* Start table read */
130962306a36Sopenharmony_ci	hellcreek_read(hellcreek, HR_FDBMAX);
131062306a36Sopenharmony_ci	hellcreek_write(hellcreek, 0x00, HR_FDBMAX);
131162306a36Sopenharmony_ci
131262306a36Sopenharmony_ci	for (i = 0; i < hellcreek->fdb_entries; ++i, ++entry) {
131362306a36Sopenharmony_ci		/* Read current entry */
131462306a36Sopenharmony_ci		hellcreek_populate_fdb_entry(hellcreek, entry, i);
131562306a36Sopenharmony_ci
131662306a36Sopenharmony_ci		/* Advance read pointer */
131762306a36Sopenharmony_ci		hellcreek_write(hellcreek, 0x00, HR_FDBRDH);
131862306a36Sopenharmony_ci	}
131962306a36Sopenharmony_ci
132062306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
132162306a36Sopenharmony_ci
132262306a36Sopenharmony_ci	*data = (u8 *)table;
132362306a36Sopenharmony_ci
132462306a36Sopenharmony_ci	return 0;
132562306a36Sopenharmony_ci}
132662306a36Sopenharmony_ci
132762306a36Sopenharmony_cistatic struct devlink_region_ops hellcreek_region_vlan_ops = {
132862306a36Sopenharmony_ci	.name	    = "vlan",
132962306a36Sopenharmony_ci	.snapshot   = hellcreek_devlink_region_vlan_snapshot,
133062306a36Sopenharmony_ci	.destructor = kfree,
133162306a36Sopenharmony_ci};
133262306a36Sopenharmony_ci
133362306a36Sopenharmony_cistatic struct devlink_region_ops hellcreek_region_fdb_ops = {
133462306a36Sopenharmony_ci	.name	    = "fdb",
133562306a36Sopenharmony_ci	.snapshot   = hellcreek_devlink_region_fdb_snapshot,
133662306a36Sopenharmony_ci	.destructor = kfree,
133762306a36Sopenharmony_ci};
133862306a36Sopenharmony_ci
133962306a36Sopenharmony_cistatic int hellcreek_setup_devlink_regions(struct dsa_switch *ds)
134062306a36Sopenharmony_ci{
134162306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
134262306a36Sopenharmony_ci	struct devlink_region_ops *ops;
134362306a36Sopenharmony_ci	struct devlink_region *region;
134462306a36Sopenharmony_ci	u64 size;
134562306a36Sopenharmony_ci	int ret;
134662306a36Sopenharmony_ci
134762306a36Sopenharmony_ci	/* VLAN table */
134862306a36Sopenharmony_ci	size = VLAN_N_VID * sizeof(struct hellcreek_devlink_vlan_entry);
134962306a36Sopenharmony_ci	ops  = &hellcreek_region_vlan_ops;
135062306a36Sopenharmony_ci
135162306a36Sopenharmony_ci	region = dsa_devlink_region_create(ds, ops, 1, size);
135262306a36Sopenharmony_ci	if (IS_ERR(region))
135362306a36Sopenharmony_ci		return PTR_ERR(region);
135462306a36Sopenharmony_ci
135562306a36Sopenharmony_ci	hellcreek->vlan_region = region;
135662306a36Sopenharmony_ci
135762306a36Sopenharmony_ci	/* FDB table */
135862306a36Sopenharmony_ci	size = hellcreek->fdb_entries * sizeof(struct hellcreek_fdb_entry);
135962306a36Sopenharmony_ci	ops  = &hellcreek_region_fdb_ops;
136062306a36Sopenharmony_ci
136162306a36Sopenharmony_ci	region = dsa_devlink_region_create(ds, ops, 1, size);
136262306a36Sopenharmony_ci	if (IS_ERR(region)) {
136362306a36Sopenharmony_ci		ret = PTR_ERR(region);
136462306a36Sopenharmony_ci		goto err_fdb;
136562306a36Sopenharmony_ci	}
136662306a36Sopenharmony_ci
136762306a36Sopenharmony_ci	hellcreek->fdb_region = region;
136862306a36Sopenharmony_ci
136962306a36Sopenharmony_ci	return 0;
137062306a36Sopenharmony_ci
137162306a36Sopenharmony_cierr_fdb:
137262306a36Sopenharmony_ci	dsa_devlink_region_destroy(hellcreek->vlan_region);
137362306a36Sopenharmony_ci
137462306a36Sopenharmony_ci	return ret;
137562306a36Sopenharmony_ci}
137662306a36Sopenharmony_ci
137762306a36Sopenharmony_cistatic void hellcreek_teardown_devlink_regions(struct dsa_switch *ds)
137862306a36Sopenharmony_ci{
137962306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
138062306a36Sopenharmony_ci
138162306a36Sopenharmony_ci	dsa_devlink_region_destroy(hellcreek->fdb_region);
138262306a36Sopenharmony_ci	dsa_devlink_region_destroy(hellcreek->vlan_region);
138362306a36Sopenharmony_ci}
138462306a36Sopenharmony_ci
138562306a36Sopenharmony_cistatic int hellcreek_setup(struct dsa_switch *ds)
138662306a36Sopenharmony_ci{
138762306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
138862306a36Sopenharmony_ci	u16 swcfg = 0;
138962306a36Sopenharmony_ci	int ret, i;
139062306a36Sopenharmony_ci
139162306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Set up the switch\n");
139262306a36Sopenharmony_ci
139362306a36Sopenharmony_ci	/* Let's go */
139462306a36Sopenharmony_ci	ret = hellcreek_enable_ip_core(hellcreek);
139562306a36Sopenharmony_ci	if (ret) {
139662306a36Sopenharmony_ci		dev_err(hellcreek->dev, "Failed to enable IP core!\n");
139762306a36Sopenharmony_ci		return ret;
139862306a36Sopenharmony_ci	}
139962306a36Sopenharmony_ci
140062306a36Sopenharmony_ci	/* Enable CPU/Tunnel ports */
140162306a36Sopenharmony_ci	hellcreek_setup_cpu_and_tunnel_port(hellcreek);
140262306a36Sopenharmony_ci
140362306a36Sopenharmony_ci	/* Switch config: Keep defaults, enable FDB aging and learning and tag
140462306a36Sopenharmony_ci	 * each frame from/to cpu port for DSA tagging.  Also enable the length
140562306a36Sopenharmony_ci	 * aware shaping mode. This eliminates the need for Qbv guard bands.
140662306a36Sopenharmony_ci	 */
140762306a36Sopenharmony_ci	swcfg |= HR_SWCFG_FDBAGE_EN |
140862306a36Sopenharmony_ci		HR_SWCFG_FDBLRN_EN  |
140962306a36Sopenharmony_ci		HR_SWCFG_ALWAYS_OBT |
141062306a36Sopenharmony_ci		(HR_SWCFG_LAS_ON << HR_SWCFG_LAS_MODE_SHIFT);
141162306a36Sopenharmony_ci	hellcreek->swcfg = swcfg;
141262306a36Sopenharmony_ci	hellcreek_write(hellcreek, swcfg, HR_SWCFG);
141362306a36Sopenharmony_ci
141462306a36Sopenharmony_ci	/* Initial vlan membership to reflect port separation */
141562306a36Sopenharmony_ci	for (i = 0; i < ds->num_ports; ++i) {
141662306a36Sopenharmony_ci		if (!dsa_is_user_port(ds, i))
141762306a36Sopenharmony_ci			continue;
141862306a36Sopenharmony_ci
141962306a36Sopenharmony_ci		hellcreek_setup_vlan_membership(ds, i, true);
142062306a36Sopenharmony_ci	}
142162306a36Sopenharmony_ci
142262306a36Sopenharmony_ci	/* Configure PCP <-> TC mapping */
142362306a36Sopenharmony_ci	hellcreek_setup_tc_identity_mapping(hellcreek);
142462306a36Sopenharmony_ci
142562306a36Sopenharmony_ci	/* The VLAN awareness is a global switch setting. Therefore, mixed vlan
142662306a36Sopenharmony_ci	 * filtering setups are not supported.
142762306a36Sopenharmony_ci	 */
142862306a36Sopenharmony_ci	ds->vlan_filtering_is_global = true;
142962306a36Sopenharmony_ci	ds->needs_standalone_vlan_filtering = true;
143062306a36Sopenharmony_ci
143162306a36Sopenharmony_ci	/* Intercept _all_ PTP multicast traffic */
143262306a36Sopenharmony_ci	ret = hellcreek_setup_fdb(hellcreek);
143362306a36Sopenharmony_ci	if (ret) {
143462306a36Sopenharmony_ci		dev_err(hellcreek->dev,
143562306a36Sopenharmony_ci			"Failed to insert static PTP FDB entries\n");
143662306a36Sopenharmony_ci		return ret;
143762306a36Sopenharmony_ci	}
143862306a36Sopenharmony_ci
143962306a36Sopenharmony_ci	/* Register devlink resources with DSA */
144062306a36Sopenharmony_ci	ret = hellcreek_setup_devlink_resources(ds);
144162306a36Sopenharmony_ci	if (ret) {
144262306a36Sopenharmony_ci		dev_err(hellcreek->dev,
144362306a36Sopenharmony_ci			"Failed to setup devlink resources!\n");
144462306a36Sopenharmony_ci		return ret;
144562306a36Sopenharmony_ci	}
144662306a36Sopenharmony_ci
144762306a36Sopenharmony_ci	ret = hellcreek_setup_devlink_regions(ds);
144862306a36Sopenharmony_ci	if (ret) {
144962306a36Sopenharmony_ci		dev_err(hellcreek->dev,
145062306a36Sopenharmony_ci			"Failed to setup devlink regions!\n");
145162306a36Sopenharmony_ci		goto err_regions;
145262306a36Sopenharmony_ci	}
145362306a36Sopenharmony_ci
145462306a36Sopenharmony_ci	return 0;
145562306a36Sopenharmony_ci
145662306a36Sopenharmony_cierr_regions:
145762306a36Sopenharmony_ci	dsa_devlink_resources_unregister(ds);
145862306a36Sopenharmony_ci
145962306a36Sopenharmony_ci	return ret;
146062306a36Sopenharmony_ci}
146162306a36Sopenharmony_ci
146262306a36Sopenharmony_cistatic void hellcreek_teardown(struct dsa_switch *ds)
146362306a36Sopenharmony_ci{
146462306a36Sopenharmony_ci	hellcreek_teardown_devlink_regions(ds);
146562306a36Sopenharmony_ci	dsa_devlink_resources_unregister(ds);
146662306a36Sopenharmony_ci}
146762306a36Sopenharmony_ci
146862306a36Sopenharmony_cistatic void hellcreek_phylink_get_caps(struct dsa_switch *ds, int port,
146962306a36Sopenharmony_ci				       struct phylink_config *config)
147062306a36Sopenharmony_ci{
147162306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
147262306a36Sopenharmony_ci
147362306a36Sopenharmony_ci	__set_bit(PHY_INTERFACE_MODE_MII, config->supported_interfaces);
147462306a36Sopenharmony_ci	__set_bit(PHY_INTERFACE_MODE_RGMII, config->supported_interfaces);
147562306a36Sopenharmony_ci
147662306a36Sopenharmony_ci	/* Include GMII - the hardware does not support this interface
147762306a36Sopenharmony_ci	 * mode, but it's the default interface mode for phylib, so we
147862306a36Sopenharmony_ci	 * need it for compatibility with existing DT.
147962306a36Sopenharmony_ci	 */
148062306a36Sopenharmony_ci	__set_bit(PHY_INTERFACE_MODE_GMII, config->supported_interfaces);
148162306a36Sopenharmony_ci
148262306a36Sopenharmony_ci	/* The MAC settings are a hardware configuration option and cannot be
148362306a36Sopenharmony_ci	 * changed at run time or by strapping. Therefore the attached PHYs
148462306a36Sopenharmony_ci	 * should be programmed to only advertise settings which are supported
148562306a36Sopenharmony_ci	 * by the hardware.
148662306a36Sopenharmony_ci	 */
148762306a36Sopenharmony_ci	if (hellcreek->pdata->is_100_mbits)
148862306a36Sopenharmony_ci		config->mac_capabilities = MAC_100FD;
148962306a36Sopenharmony_ci	else
149062306a36Sopenharmony_ci		config->mac_capabilities = MAC_1000FD;
149162306a36Sopenharmony_ci}
149262306a36Sopenharmony_ci
149362306a36Sopenharmony_cistatic int
149462306a36Sopenharmony_cihellcreek_port_prechangeupper(struct dsa_switch *ds, int port,
149562306a36Sopenharmony_ci			      struct netdev_notifier_changeupper_info *info)
149662306a36Sopenharmony_ci{
149762306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
149862306a36Sopenharmony_ci	bool used = true;
149962306a36Sopenharmony_ci	int ret = -EBUSY;
150062306a36Sopenharmony_ci	u16 vid;
150162306a36Sopenharmony_ci	int i;
150262306a36Sopenharmony_ci
150362306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Pre change upper for port %d\n", port);
150462306a36Sopenharmony_ci
150562306a36Sopenharmony_ci	/*
150662306a36Sopenharmony_ci	 * Deny VLAN devices on top of lan ports with the same VLAN ids, because
150762306a36Sopenharmony_ci	 * it breaks the port separation due to the private VLANs. Example:
150862306a36Sopenharmony_ci	 *
150962306a36Sopenharmony_ci	 * lan0.100 *and* lan1.100 cannot be used in parallel. However, lan0.99
151062306a36Sopenharmony_ci	 * and lan1.100 works.
151162306a36Sopenharmony_ci	 */
151262306a36Sopenharmony_ci
151362306a36Sopenharmony_ci	if (!is_vlan_dev(info->upper_dev))
151462306a36Sopenharmony_ci		return 0;
151562306a36Sopenharmony_ci
151662306a36Sopenharmony_ci	vid = vlan_dev_vlan_id(info->upper_dev);
151762306a36Sopenharmony_ci
151862306a36Sopenharmony_ci	/* For all ports, check bitmaps */
151962306a36Sopenharmony_ci	mutex_lock(&hellcreek->vlan_lock);
152062306a36Sopenharmony_ci	for (i = 0; i < hellcreek->pdata->num_ports; ++i) {
152162306a36Sopenharmony_ci		if (!dsa_is_user_port(ds, i))
152262306a36Sopenharmony_ci			continue;
152362306a36Sopenharmony_ci
152462306a36Sopenharmony_ci		if (port == i)
152562306a36Sopenharmony_ci			continue;
152662306a36Sopenharmony_ci
152762306a36Sopenharmony_ci		used = used && test_bit(vid, hellcreek->ports[i].vlan_dev_bitmap);
152862306a36Sopenharmony_ci	}
152962306a36Sopenharmony_ci
153062306a36Sopenharmony_ci	if (used)
153162306a36Sopenharmony_ci		goto out;
153262306a36Sopenharmony_ci
153362306a36Sopenharmony_ci	/* Update bitmap */
153462306a36Sopenharmony_ci	set_bit(vid, hellcreek->ports[port].vlan_dev_bitmap);
153562306a36Sopenharmony_ci
153662306a36Sopenharmony_ci	ret = 0;
153762306a36Sopenharmony_ci
153862306a36Sopenharmony_ciout:
153962306a36Sopenharmony_ci	mutex_unlock(&hellcreek->vlan_lock);
154062306a36Sopenharmony_ci
154162306a36Sopenharmony_ci	return ret;
154262306a36Sopenharmony_ci}
154362306a36Sopenharmony_ci
154462306a36Sopenharmony_cistatic void hellcreek_setup_maxsdu(struct hellcreek *hellcreek, int port,
154562306a36Sopenharmony_ci				   const struct tc_taprio_qopt_offload *schedule)
154662306a36Sopenharmony_ci{
154762306a36Sopenharmony_ci	int tc;
154862306a36Sopenharmony_ci
154962306a36Sopenharmony_ci	for (tc = 0; tc < 8; ++tc) {
155062306a36Sopenharmony_ci		u32 max_sdu = schedule->max_sdu[tc] + VLAN_ETH_HLEN - ETH_FCS_LEN;
155162306a36Sopenharmony_ci		u16 val;
155262306a36Sopenharmony_ci
155362306a36Sopenharmony_ci		if (!schedule->max_sdu[tc])
155462306a36Sopenharmony_ci			continue;
155562306a36Sopenharmony_ci
155662306a36Sopenharmony_ci		dev_dbg(hellcreek->dev, "Configure max-sdu %u for tc %d on port %d\n",
155762306a36Sopenharmony_ci			max_sdu, tc, port);
155862306a36Sopenharmony_ci
155962306a36Sopenharmony_ci		hellcreek_select_port_prio(hellcreek, port, tc);
156062306a36Sopenharmony_ci
156162306a36Sopenharmony_ci		val = (max_sdu & HR_PTPRTCCFG_MAXSDU_MASK) << HR_PTPRTCCFG_MAXSDU_SHIFT;
156262306a36Sopenharmony_ci
156362306a36Sopenharmony_ci		hellcreek_write(hellcreek, val, HR_PTPRTCCFG);
156462306a36Sopenharmony_ci	}
156562306a36Sopenharmony_ci}
156662306a36Sopenharmony_ci
156762306a36Sopenharmony_cistatic void hellcreek_reset_maxsdu(struct hellcreek *hellcreek, int port)
156862306a36Sopenharmony_ci{
156962306a36Sopenharmony_ci	int tc;
157062306a36Sopenharmony_ci
157162306a36Sopenharmony_ci	for (tc = 0; tc < 8; ++tc) {
157262306a36Sopenharmony_ci		u16 val;
157362306a36Sopenharmony_ci
157462306a36Sopenharmony_ci		hellcreek_select_port_prio(hellcreek, port, tc);
157562306a36Sopenharmony_ci
157662306a36Sopenharmony_ci		val = (HELLCREEK_DEFAULT_MAX_SDU & HR_PTPRTCCFG_MAXSDU_MASK)
157762306a36Sopenharmony_ci			<< HR_PTPRTCCFG_MAXSDU_SHIFT;
157862306a36Sopenharmony_ci
157962306a36Sopenharmony_ci		hellcreek_write(hellcreek, val, HR_PTPRTCCFG);
158062306a36Sopenharmony_ci	}
158162306a36Sopenharmony_ci}
158262306a36Sopenharmony_ci
158362306a36Sopenharmony_cistatic void hellcreek_setup_gcl(struct hellcreek *hellcreek, int port,
158462306a36Sopenharmony_ci				const struct tc_taprio_qopt_offload *schedule)
158562306a36Sopenharmony_ci{
158662306a36Sopenharmony_ci	const struct tc_taprio_sched_entry *cur, *initial, *next;
158762306a36Sopenharmony_ci	size_t i;
158862306a36Sopenharmony_ci
158962306a36Sopenharmony_ci	cur = initial = &schedule->entries[0];
159062306a36Sopenharmony_ci	next = cur + 1;
159162306a36Sopenharmony_ci
159262306a36Sopenharmony_ci	for (i = 1; i <= schedule->num_entries; ++i) {
159362306a36Sopenharmony_ci		u16 data;
159462306a36Sopenharmony_ci		u8 gates;
159562306a36Sopenharmony_ci
159662306a36Sopenharmony_ci		if (i == schedule->num_entries)
159762306a36Sopenharmony_ci			gates = initial->gate_mask ^
159862306a36Sopenharmony_ci				cur->gate_mask;
159962306a36Sopenharmony_ci		else
160062306a36Sopenharmony_ci			gates = next->gate_mask ^
160162306a36Sopenharmony_ci				cur->gate_mask;
160262306a36Sopenharmony_ci
160362306a36Sopenharmony_ci		data = gates;
160462306a36Sopenharmony_ci
160562306a36Sopenharmony_ci		if (i == schedule->num_entries)
160662306a36Sopenharmony_ci			data |= TR_GCLDAT_GCLWRLAST;
160762306a36Sopenharmony_ci
160862306a36Sopenharmony_ci		/* Gates states */
160962306a36Sopenharmony_ci		hellcreek_write(hellcreek, data, TR_GCLDAT);
161062306a36Sopenharmony_ci
161162306a36Sopenharmony_ci		/* Time interval */
161262306a36Sopenharmony_ci		hellcreek_write(hellcreek,
161362306a36Sopenharmony_ci				cur->interval & 0x0000ffff,
161462306a36Sopenharmony_ci				TR_GCLTIL);
161562306a36Sopenharmony_ci		hellcreek_write(hellcreek,
161662306a36Sopenharmony_ci				(cur->interval & 0xffff0000) >> 16,
161762306a36Sopenharmony_ci				TR_GCLTIH);
161862306a36Sopenharmony_ci
161962306a36Sopenharmony_ci		/* Commit entry */
162062306a36Sopenharmony_ci		data = ((i - 1) << TR_GCLCMD_GCLWRADR_SHIFT) |
162162306a36Sopenharmony_ci			(initial->gate_mask <<
162262306a36Sopenharmony_ci			 TR_GCLCMD_INIT_GATE_STATES_SHIFT);
162362306a36Sopenharmony_ci		hellcreek_write(hellcreek, data, TR_GCLCMD);
162462306a36Sopenharmony_ci
162562306a36Sopenharmony_ci		cur++;
162662306a36Sopenharmony_ci		next++;
162762306a36Sopenharmony_ci	}
162862306a36Sopenharmony_ci}
162962306a36Sopenharmony_ci
163062306a36Sopenharmony_cistatic void hellcreek_set_cycle_time(struct hellcreek *hellcreek,
163162306a36Sopenharmony_ci				     const struct tc_taprio_qopt_offload *schedule)
163262306a36Sopenharmony_ci{
163362306a36Sopenharmony_ci	u32 cycle_time = schedule->cycle_time;
163462306a36Sopenharmony_ci
163562306a36Sopenharmony_ci	hellcreek_write(hellcreek, cycle_time & 0x0000ffff, TR_CTWRL);
163662306a36Sopenharmony_ci	hellcreek_write(hellcreek, (cycle_time & 0xffff0000) >> 16, TR_CTWRH);
163762306a36Sopenharmony_ci}
163862306a36Sopenharmony_ci
163962306a36Sopenharmony_cistatic void hellcreek_switch_schedule(struct hellcreek *hellcreek,
164062306a36Sopenharmony_ci				      ktime_t start_time)
164162306a36Sopenharmony_ci{
164262306a36Sopenharmony_ci	struct timespec64 ts = ktime_to_timespec64(start_time);
164362306a36Sopenharmony_ci
164462306a36Sopenharmony_ci	/* Start schedule at this point of time */
164562306a36Sopenharmony_ci	hellcreek_write(hellcreek, ts.tv_nsec & 0x0000ffff, TR_ESTWRL);
164662306a36Sopenharmony_ci	hellcreek_write(hellcreek, (ts.tv_nsec & 0xffff0000) >> 16, TR_ESTWRH);
164762306a36Sopenharmony_ci
164862306a36Sopenharmony_ci	/* Arm timer, set seconds and switch schedule */
164962306a36Sopenharmony_ci	hellcreek_write(hellcreek, TR_ESTCMD_ESTARM | TR_ESTCMD_ESTSWCFG |
165062306a36Sopenharmony_ci			((ts.tv_sec & TR_ESTCMD_ESTSEC_MASK) <<
165162306a36Sopenharmony_ci			 TR_ESTCMD_ESTSEC_SHIFT), TR_ESTCMD);
165262306a36Sopenharmony_ci}
165362306a36Sopenharmony_ci
165462306a36Sopenharmony_cistatic bool hellcreek_schedule_startable(struct hellcreek *hellcreek, int port)
165562306a36Sopenharmony_ci{
165662306a36Sopenharmony_ci	struct hellcreek_port *hellcreek_port = &hellcreek->ports[port];
165762306a36Sopenharmony_ci	s64 base_time_ns, current_ns;
165862306a36Sopenharmony_ci
165962306a36Sopenharmony_ci	/* The switch allows a schedule to be started only eight seconds within
166062306a36Sopenharmony_ci	 * the future. Therefore, check the current PTP time if the schedule is
166162306a36Sopenharmony_ci	 * startable or not.
166262306a36Sopenharmony_ci	 */
166362306a36Sopenharmony_ci
166462306a36Sopenharmony_ci	/* Use the "cached" time. That should be alright, as it's updated quite
166562306a36Sopenharmony_ci	 * frequently in the PTP code.
166662306a36Sopenharmony_ci	 */
166762306a36Sopenharmony_ci	mutex_lock(&hellcreek->ptp_lock);
166862306a36Sopenharmony_ci	current_ns = hellcreek->seconds * NSEC_PER_SEC + hellcreek->last_ts;
166962306a36Sopenharmony_ci	mutex_unlock(&hellcreek->ptp_lock);
167062306a36Sopenharmony_ci
167162306a36Sopenharmony_ci	/* Calculate difference to admin base time */
167262306a36Sopenharmony_ci	base_time_ns = ktime_to_ns(hellcreek_port->current_schedule->base_time);
167362306a36Sopenharmony_ci
167462306a36Sopenharmony_ci	return base_time_ns - current_ns < (s64)4 * NSEC_PER_SEC;
167562306a36Sopenharmony_ci}
167662306a36Sopenharmony_ci
167762306a36Sopenharmony_cistatic void hellcreek_start_schedule(struct hellcreek *hellcreek, int port)
167862306a36Sopenharmony_ci{
167962306a36Sopenharmony_ci	struct hellcreek_port *hellcreek_port = &hellcreek->ports[port];
168062306a36Sopenharmony_ci	ktime_t base_time, current_time;
168162306a36Sopenharmony_ci	s64 current_ns;
168262306a36Sopenharmony_ci	u32 cycle_time;
168362306a36Sopenharmony_ci
168462306a36Sopenharmony_ci	/* First select port */
168562306a36Sopenharmony_ci	hellcreek_select_tgd(hellcreek, port);
168662306a36Sopenharmony_ci
168762306a36Sopenharmony_ci	/* Forward base time into the future if needed */
168862306a36Sopenharmony_ci	mutex_lock(&hellcreek->ptp_lock);
168962306a36Sopenharmony_ci	current_ns = hellcreek->seconds * NSEC_PER_SEC + hellcreek->last_ts;
169062306a36Sopenharmony_ci	mutex_unlock(&hellcreek->ptp_lock);
169162306a36Sopenharmony_ci
169262306a36Sopenharmony_ci	current_time = ns_to_ktime(current_ns);
169362306a36Sopenharmony_ci	base_time    = hellcreek_port->current_schedule->base_time;
169462306a36Sopenharmony_ci	cycle_time   = hellcreek_port->current_schedule->cycle_time;
169562306a36Sopenharmony_ci
169662306a36Sopenharmony_ci	if (ktime_compare(current_time, base_time) > 0) {
169762306a36Sopenharmony_ci		s64 n;
169862306a36Sopenharmony_ci
169962306a36Sopenharmony_ci		n = div64_s64(ktime_sub_ns(current_time, base_time),
170062306a36Sopenharmony_ci			      cycle_time);
170162306a36Sopenharmony_ci		base_time = ktime_add_ns(base_time, (n + 1) * cycle_time);
170262306a36Sopenharmony_ci	}
170362306a36Sopenharmony_ci
170462306a36Sopenharmony_ci	/* Set admin base time and switch schedule */
170562306a36Sopenharmony_ci	hellcreek_switch_schedule(hellcreek, base_time);
170662306a36Sopenharmony_ci
170762306a36Sopenharmony_ci	taprio_offload_free(hellcreek_port->current_schedule);
170862306a36Sopenharmony_ci	hellcreek_port->current_schedule = NULL;
170962306a36Sopenharmony_ci
171062306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Armed EST timer for port %d\n",
171162306a36Sopenharmony_ci		hellcreek_port->port);
171262306a36Sopenharmony_ci}
171362306a36Sopenharmony_ci
171462306a36Sopenharmony_cistatic void hellcreek_check_schedule(struct work_struct *work)
171562306a36Sopenharmony_ci{
171662306a36Sopenharmony_ci	struct delayed_work *dw = to_delayed_work(work);
171762306a36Sopenharmony_ci	struct hellcreek_port *hellcreek_port;
171862306a36Sopenharmony_ci	struct hellcreek *hellcreek;
171962306a36Sopenharmony_ci	bool startable;
172062306a36Sopenharmony_ci
172162306a36Sopenharmony_ci	hellcreek_port = dw_to_hellcreek_port(dw);
172262306a36Sopenharmony_ci	hellcreek = hellcreek_port->hellcreek;
172362306a36Sopenharmony_ci
172462306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
172562306a36Sopenharmony_ci
172662306a36Sopenharmony_ci	/* Check starting time */
172762306a36Sopenharmony_ci	startable = hellcreek_schedule_startable(hellcreek,
172862306a36Sopenharmony_ci						 hellcreek_port->port);
172962306a36Sopenharmony_ci	if (startable) {
173062306a36Sopenharmony_ci		hellcreek_start_schedule(hellcreek, hellcreek_port->port);
173162306a36Sopenharmony_ci		mutex_unlock(&hellcreek->reg_lock);
173262306a36Sopenharmony_ci		return;
173362306a36Sopenharmony_ci	}
173462306a36Sopenharmony_ci
173562306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
173662306a36Sopenharmony_ci
173762306a36Sopenharmony_ci	/* Reschedule */
173862306a36Sopenharmony_ci	schedule_delayed_work(&hellcreek_port->schedule_work,
173962306a36Sopenharmony_ci			      HELLCREEK_SCHEDULE_PERIOD);
174062306a36Sopenharmony_ci}
174162306a36Sopenharmony_ci
174262306a36Sopenharmony_cistatic int hellcreek_port_set_schedule(struct dsa_switch *ds, int port,
174362306a36Sopenharmony_ci				       struct tc_taprio_qopt_offload *taprio)
174462306a36Sopenharmony_ci{
174562306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
174662306a36Sopenharmony_ci	struct hellcreek_port *hellcreek_port;
174762306a36Sopenharmony_ci	bool startable;
174862306a36Sopenharmony_ci	u16 ctrl;
174962306a36Sopenharmony_ci
175062306a36Sopenharmony_ci	hellcreek_port = &hellcreek->ports[port];
175162306a36Sopenharmony_ci
175262306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Configure traffic schedule on port %d\n",
175362306a36Sopenharmony_ci		port);
175462306a36Sopenharmony_ci
175562306a36Sopenharmony_ci	/* First cancel delayed work */
175662306a36Sopenharmony_ci	cancel_delayed_work_sync(&hellcreek_port->schedule_work);
175762306a36Sopenharmony_ci
175862306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
175962306a36Sopenharmony_ci
176062306a36Sopenharmony_ci	if (hellcreek_port->current_schedule) {
176162306a36Sopenharmony_ci		taprio_offload_free(hellcreek_port->current_schedule);
176262306a36Sopenharmony_ci		hellcreek_port->current_schedule = NULL;
176362306a36Sopenharmony_ci	}
176462306a36Sopenharmony_ci	hellcreek_port->current_schedule = taprio_offload_get(taprio);
176562306a36Sopenharmony_ci
176662306a36Sopenharmony_ci	/* Configure max sdu */
176762306a36Sopenharmony_ci	hellcreek_setup_maxsdu(hellcreek, port, hellcreek_port->current_schedule);
176862306a36Sopenharmony_ci
176962306a36Sopenharmony_ci	/* Select tdg */
177062306a36Sopenharmony_ci	hellcreek_select_tgd(hellcreek, port);
177162306a36Sopenharmony_ci
177262306a36Sopenharmony_ci	/* Enable gating and keep defaults */
177362306a36Sopenharmony_ci	ctrl = (0xff << TR_TGDCTRL_ADMINGATESTATES_SHIFT) | TR_TGDCTRL_GATE_EN;
177462306a36Sopenharmony_ci	hellcreek_write(hellcreek, ctrl, TR_TGDCTRL);
177562306a36Sopenharmony_ci
177662306a36Sopenharmony_ci	/* Cancel pending schedule */
177762306a36Sopenharmony_ci	hellcreek_write(hellcreek, 0x00, TR_ESTCMD);
177862306a36Sopenharmony_ci
177962306a36Sopenharmony_ci	/* Setup a new schedule */
178062306a36Sopenharmony_ci	hellcreek_setup_gcl(hellcreek, port, hellcreek_port->current_schedule);
178162306a36Sopenharmony_ci
178262306a36Sopenharmony_ci	/* Configure cycle time */
178362306a36Sopenharmony_ci	hellcreek_set_cycle_time(hellcreek, hellcreek_port->current_schedule);
178462306a36Sopenharmony_ci
178562306a36Sopenharmony_ci	/* Check starting time */
178662306a36Sopenharmony_ci	startable = hellcreek_schedule_startable(hellcreek, port);
178762306a36Sopenharmony_ci	if (startable) {
178862306a36Sopenharmony_ci		hellcreek_start_schedule(hellcreek, port);
178962306a36Sopenharmony_ci		mutex_unlock(&hellcreek->reg_lock);
179062306a36Sopenharmony_ci		return 0;
179162306a36Sopenharmony_ci	}
179262306a36Sopenharmony_ci
179362306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
179462306a36Sopenharmony_ci
179562306a36Sopenharmony_ci	/* Schedule periodic schedule check */
179662306a36Sopenharmony_ci	schedule_delayed_work(&hellcreek_port->schedule_work,
179762306a36Sopenharmony_ci			      HELLCREEK_SCHEDULE_PERIOD);
179862306a36Sopenharmony_ci
179962306a36Sopenharmony_ci	return 0;
180062306a36Sopenharmony_ci}
180162306a36Sopenharmony_ci
180262306a36Sopenharmony_cistatic int hellcreek_port_del_schedule(struct dsa_switch *ds, int port)
180362306a36Sopenharmony_ci{
180462306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
180562306a36Sopenharmony_ci	struct hellcreek_port *hellcreek_port;
180662306a36Sopenharmony_ci
180762306a36Sopenharmony_ci	hellcreek_port = &hellcreek->ports[port];
180862306a36Sopenharmony_ci
180962306a36Sopenharmony_ci	dev_dbg(hellcreek->dev, "Remove traffic schedule on port %d\n", port);
181062306a36Sopenharmony_ci
181162306a36Sopenharmony_ci	/* First cancel delayed work */
181262306a36Sopenharmony_ci	cancel_delayed_work_sync(&hellcreek_port->schedule_work);
181362306a36Sopenharmony_ci
181462306a36Sopenharmony_ci	mutex_lock(&hellcreek->reg_lock);
181562306a36Sopenharmony_ci
181662306a36Sopenharmony_ci	if (hellcreek_port->current_schedule) {
181762306a36Sopenharmony_ci		taprio_offload_free(hellcreek_port->current_schedule);
181862306a36Sopenharmony_ci		hellcreek_port->current_schedule = NULL;
181962306a36Sopenharmony_ci	}
182062306a36Sopenharmony_ci
182162306a36Sopenharmony_ci	/* Reset max sdu */
182262306a36Sopenharmony_ci	hellcreek_reset_maxsdu(hellcreek, port);
182362306a36Sopenharmony_ci
182462306a36Sopenharmony_ci	/* Select tgd */
182562306a36Sopenharmony_ci	hellcreek_select_tgd(hellcreek, port);
182662306a36Sopenharmony_ci
182762306a36Sopenharmony_ci	/* Disable gating and return to regular switching flow */
182862306a36Sopenharmony_ci	hellcreek_write(hellcreek, 0xff << TR_TGDCTRL_ADMINGATESTATES_SHIFT,
182962306a36Sopenharmony_ci			TR_TGDCTRL);
183062306a36Sopenharmony_ci
183162306a36Sopenharmony_ci	mutex_unlock(&hellcreek->reg_lock);
183262306a36Sopenharmony_ci
183362306a36Sopenharmony_ci	return 0;
183462306a36Sopenharmony_ci}
183562306a36Sopenharmony_ci
183662306a36Sopenharmony_cistatic bool hellcreek_validate_schedule(struct hellcreek *hellcreek,
183762306a36Sopenharmony_ci					struct tc_taprio_qopt_offload *schedule)
183862306a36Sopenharmony_ci{
183962306a36Sopenharmony_ci	size_t i;
184062306a36Sopenharmony_ci
184162306a36Sopenharmony_ci	/* Does this hellcreek version support Qbv in hardware? */
184262306a36Sopenharmony_ci	if (!hellcreek->pdata->qbv_support)
184362306a36Sopenharmony_ci		return false;
184462306a36Sopenharmony_ci
184562306a36Sopenharmony_ci	/* cycle time can only be 32bit */
184662306a36Sopenharmony_ci	if (schedule->cycle_time > (u32)-1)
184762306a36Sopenharmony_ci		return false;
184862306a36Sopenharmony_ci
184962306a36Sopenharmony_ci	/* cycle time extension is not supported */
185062306a36Sopenharmony_ci	if (schedule->cycle_time_extension)
185162306a36Sopenharmony_ci		return false;
185262306a36Sopenharmony_ci
185362306a36Sopenharmony_ci	/* Only set command is supported */
185462306a36Sopenharmony_ci	for (i = 0; i < schedule->num_entries; ++i)
185562306a36Sopenharmony_ci		if (schedule->entries[i].command != TC_TAPRIO_CMD_SET_GATES)
185662306a36Sopenharmony_ci			return false;
185762306a36Sopenharmony_ci
185862306a36Sopenharmony_ci	return true;
185962306a36Sopenharmony_ci}
186062306a36Sopenharmony_ci
186162306a36Sopenharmony_cistatic int hellcreek_tc_query_caps(struct tc_query_caps_base *base)
186262306a36Sopenharmony_ci{
186362306a36Sopenharmony_ci	switch (base->type) {
186462306a36Sopenharmony_ci	case TC_SETUP_QDISC_TAPRIO: {
186562306a36Sopenharmony_ci		struct tc_taprio_caps *caps = base->caps;
186662306a36Sopenharmony_ci
186762306a36Sopenharmony_ci		caps->supports_queue_max_sdu = true;
186862306a36Sopenharmony_ci
186962306a36Sopenharmony_ci		return 0;
187062306a36Sopenharmony_ci	}
187162306a36Sopenharmony_ci	default:
187262306a36Sopenharmony_ci		return -EOPNOTSUPP;
187362306a36Sopenharmony_ci	}
187462306a36Sopenharmony_ci}
187562306a36Sopenharmony_ci
187662306a36Sopenharmony_cistatic int hellcreek_port_setup_tc(struct dsa_switch *ds, int port,
187762306a36Sopenharmony_ci				   enum tc_setup_type type, void *type_data)
187862306a36Sopenharmony_ci{
187962306a36Sopenharmony_ci	struct hellcreek *hellcreek = ds->priv;
188062306a36Sopenharmony_ci
188162306a36Sopenharmony_ci	switch (type) {
188262306a36Sopenharmony_ci	case TC_QUERY_CAPS:
188362306a36Sopenharmony_ci		return hellcreek_tc_query_caps(type_data);
188462306a36Sopenharmony_ci	case TC_SETUP_QDISC_TAPRIO: {
188562306a36Sopenharmony_ci		struct tc_taprio_qopt_offload *taprio = type_data;
188662306a36Sopenharmony_ci
188762306a36Sopenharmony_ci		switch (taprio->cmd) {
188862306a36Sopenharmony_ci		case TAPRIO_CMD_REPLACE:
188962306a36Sopenharmony_ci			if (!hellcreek_validate_schedule(hellcreek, taprio))
189062306a36Sopenharmony_ci				return -EOPNOTSUPP;
189162306a36Sopenharmony_ci
189262306a36Sopenharmony_ci			return hellcreek_port_set_schedule(ds, port, taprio);
189362306a36Sopenharmony_ci		case TAPRIO_CMD_DESTROY:
189462306a36Sopenharmony_ci			return hellcreek_port_del_schedule(ds, port);
189562306a36Sopenharmony_ci		default:
189662306a36Sopenharmony_ci			return -EOPNOTSUPP;
189762306a36Sopenharmony_ci		}
189862306a36Sopenharmony_ci	}
189962306a36Sopenharmony_ci	default:
190062306a36Sopenharmony_ci		return -EOPNOTSUPP;
190162306a36Sopenharmony_ci	}
190262306a36Sopenharmony_ci}
190362306a36Sopenharmony_ci
190462306a36Sopenharmony_cistatic const struct dsa_switch_ops hellcreek_ds_ops = {
190562306a36Sopenharmony_ci	.devlink_info_get      = hellcreek_devlink_info_get,
190662306a36Sopenharmony_ci	.get_ethtool_stats     = hellcreek_get_ethtool_stats,
190762306a36Sopenharmony_ci	.get_sset_count	       = hellcreek_get_sset_count,
190862306a36Sopenharmony_ci	.get_strings	       = hellcreek_get_strings,
190962306a36Sopenharmony_ci	.get_tag_protocol      = hellcreek_get_tag_protocol,
191062306a36Sopenharmony_ci	.get_ts_info	       = hellcreek_get_ts_info,
191162306a36Sopenharmony_ci	.phylink_get_caps      = hellcreek_phylink_get_caps,
191262306a36Sopenharmony_ci	.port_bridge_flags     = hellcreek_bridge_flags,
191362306a36Sopenharmony_ci	.port_bridge_join      = hellcreek_port_bridge_join,
191462306a36Sopenharmony_ci	.port_bridge_leave     = hellcreek_port_bridge_leave,
191562306a36Sopenharmony_ci	.port_disable	       = hellcreek_port_disable,
191662306a36Sopenharmony_ci	.port_enable	       = hellcreek_port_enable,
191762306a36Sopenharmony_ci	.port_fdb_add	       = hellcreek_fdb_add,
191862306a36Sopenharmony_ci	.port_fdb_del	       = hellcreek_fdb_del,
191962306a36Sopenharmony_ci	.port_fdb_dump	       = hellcreek_fdb_dump,
192062306a36Sopenharmony_ci	.port_hwtstamp_set     = hellcreek_port_hwtstamp_set,
192162306a36Sopenharmony_ci	.port_hwtstamp_get     = hellcreek_port_hwtstamp_get,
192262306a36Sopenharmony_ci	.port_pre_bridge_flags = hellcreek_pre_bridge_flags,
192362306a36Sopenharmony_ci	.port_prechangeupper   = hellcreek_port_prechangeupper,
192462306a36Sopenharmony_ci	.port_rxtstamp	       = hellcreek_port_rxtstamp,
192562306a36Sopenharmony_ci	.port_setup_tc	       = hellcreek_port_setup_tc,
192662306a36Sopenharmony_ci	.port_stp_state_set    = hellcreek_port_stp_state_set,
192762306a36Sopenharmony_ci	.port_txtstamp	       = hellcreek_port_txtstamp,
192862306a36Sopenharmony_ci	.port_vlan_add	       = hellcreek_vlan_add,
192962306a36Sopenharmony_ci	.port_vlan_del	       = hellcreek_vlan_del,
193062306a36Sopenharmony_ci	.port_vlan_filtering   = hellcreek_vlan_filtering,
193162306a36Sopenharmony_ci	.setup		       = hellcreek_setup,
193262306a36Sopenharmony_ci	.teardown	       = hellcreek_teardown,
193362306a36Sopenharmony_ci};
193462306a36Sopenharmony_ci
193562306a36Sopenharmony_cistatic int hellcreek_probe(struct platform_device *pdev)
193662306a36Sopenharmony_ci{
193762306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
193862306a36Sopenharmony_ci	struct hellcreek *hellcreek;
193962306a36Sopenharmony_ci	struct resource *res;
194062306a36Sopenharmony_ci	int ret, i;
194162306a36Sopenharmony_ci
194262306a36Sopenharmony_ci	hellcreek = devm_kzalloc(dev, sizeof(*hellcreek), GFP_KERNEL);
194362306a36Sopenharmony_ci	if (!hellcreek)
194462306a36Sopenharmony_ci		return -ENOMEM;
194562306a36Sopenharmony_ci
194662306a36Sopenharmony_ci	hellcreek->vidmbrcfg = devm_kcalloc(dev, VLAN_N_VID,
194762306a36Sopenharmony_ci					    sizeof(*hellcreek->vidmbrcfg),
194862306a36Sopenharmony_ci					    GFP_KERNEL);
194962306a36Sopenharmony_ci	if (!hellcreek->vidmbrcfg)
195062306a36Sopenharmony_ci		return -ENOMEM;
195162306a36Sopenharmony_ci
195262306a36Sopenharmony_ci	hellcreek->pdata = of_device_get_match_data(dev);
195362306a36Sopenharmony_ci
195462306a36Sopenharmony_ci	hellcreek->ports = devm_kcalloc(dev, hellcreek->pdata->num_ports,
195562306a36Sopenharmony_ci					sizeof(*hellcreek->ports),
195662306a36Sopenharmony_ci					GFP_KERNEL);
195762306a36Sopenharmony_ci	if (!hellcreek->ports)
195862306a36Sopenharmony_ci		return -ENOMEM;
195962306a36Sopenharmony_ci
196062306a36Sopenharmony_ci	for (i = 0; i < hellcreek->pdata->num_ports; ++i) {
196162306a36Sopenharmony_ci		struct hellcreek_port *port = &hellcreek->ports[i];
196262306a36Sopenharmony_ci
196362306a36Sopenharmony_ci		port->counter_values =
196462306a36Sopenharmony_ci			devm_kcalloc(dev,
196562306a36Sopenharmony_ci				     ARRAY_SIZE(hellcreek_counter),
196662306a36Sopenharmony_ci				     sizeof(*port->counter_values),
196762306a36Sopenharmony_ci				     GFP_KERNEL);
196862306a36Sopenharmony_ci		if (!port->counter_values)
196962306a36Sopenharmony_ci			return -ENOMEM;
197062306a36Sopenharmony_ci
197162306a36Sopenharmony_ci		port->vlan_dev_bitmap = devm_bitmap_zalloc(dev, VLAN_N_VID,
197262306a36Sopenharmony_ci							   GFP_KERNEL);
197362306a36Sopenharmony_ci		if (!port->vlan_dev_bitmap)
197462306a36Sopenharmony_ci			return -ENOMEM;
197562306a36Sopenharmony_ci
197662306a36Sopenharmony_ci		port->hellcreek	= hellcreek;
197762306a36Sopenharmony_ci		port->port	= i;
197862306a36Sopenharmony_ci
197962306a36Sopenharmony_ci		INIT_DELAYED_WORK(&port->schedule_work,
198062306a36Sopenharmony_ci				  hellcreek_check_schedule);
198162306a36Sopenharmony_ci	}
198262306a36Sopenharmony_ci
198362306a36Sopenharmony_ci	mutex_init(&hellcreek->reg_lock);
198462306a36Sopenharmony_ci	mutex_init(&hellcreek->vlan_lock);
198562306a36Sopenharmony_ci	mutex_init(&hellcreek->ptp_lock);
198662306a36Sopenharmony_ci
198762306a36Sopenharmony_ci	hellcreek->dev = dev;
198862306a36Sopenharmony_ci
198962306a36Sopenharmony_ci	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tsn");
199062306a36Sopenharmony_ci	if (!res) {
199162306a36Sopenharmony_ci		dev_err(dev, "No memory region provided!\n");
199262306a36Sopenharmony_ci		return -ENODEV;
199362306a36Sopenharmony_ci	}
199462306a36Sopenharmony_ci
199562306a36Sopenharmony_ci	hellcreek->base = devm_ioremap_resource(dev, res);
199662306a36Sopenharmony_ci	if (IS_ERR(hellcreek->base))
199762306a36Sopenharmony_ci		return PTR_ERR(hellcreek->base);
199862306a36Sopenharmony_ci
199962306a36Sopenharmony_ci	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ptp");
200062306a36Sopenharmony_ci	if (!res) {
200162306a36Sopenharmony_ci		dev_err(dev, "No PTP memory region provided!\n");
200262306a36Sopenharmony_ci		return -ENODEV;
200362306a36Sopenharmony_ci	}
200462306a36Sopenharmony_ci
200562306a36Sopenharmony_ci	hellcreek->ptp_base = devm_ioremap_resource(dev, res);
200662306a36Sopenharmony_ci	if (IS_ERR(hellcreek->ptp_base))
200762306a36Sopenharmony_ci		return PTR_ERR(hellcreek->ptp_base);
200862306a36Sopenharmony_ci
200962306a36Sopenharmony_ci	ret = hellcreek_detect(hellcreek);
201062306a36Sopenharmony_ci	if (ret) {
201162306a36Sopenharmony_ci		dev_err(dev, "No (known) chip found!\n");
201262306a36Sopenharmony_ci		return ret;
201362306a36Sopenharmony_ci	}
201462306a36Sopenharmony_ci
201562306a36Sopenharmony_ci	ret = hellcreek_wait_until_ready(hellcreek);
201662306a36Sopenharmony_ci	if (ret) {
201762306a36Sopenharmony_ci		dev_err(dev, "Switch didn't become ready!\n");
201862306a36Sopenharmony_ci		return ret;
201962306a36Sopenharmony_ci	}
202062306a36Sopenharmony_ci
202162306a36Sopenharmony_ci	hellcreek_feature_detect(hellcreek);
202262306a36Sopenharmony_ci
202362306a36Sopenharmony_ci	hellcreek->ds = devm_kzalloc(dev, sizeof(*hellcreek->ds), GFP_KERNEL);
202462306a36Sopenharmony_ci	if (!hellcreek->ds)
202562306a36Sopenharmony_ci		return -ENOMEM;
202662306a36Sopenharmony_ci
202762306a36Sopenharmony_ci	hellcreek->ds->dev	     = dev;
202862306a36Sopenharmony_ci	hellcreek->ds->priv	     = hellcreek;
202962306a36Sopenharmony_ci	hellcreek->ds->ops	     = &hellcreek_ds_ops;
203062306a36Sopenharmony_ci	hellcreek->ds->num_ports     = hellcreek->pdata->num_ports;
203162306a36Sopenharmony_ci	hellcreek->ds->num_tx_queues = HELLCREEK_NUM_EGRESS_QUEUES;
203262306a36Sopenharmony_ci
203362306a36Sopenharmony_ci	ret = dsa_register_switch(hellcreek->ds);
203462306a36Sopenharmony_ci	if (ret) {
203562306a36Sopenharmony_ci		dev_err_probe(dev, ret, "Unable to register switch\n");
203662306a36Sopenharmony_ci		return ret;
203762306a36Sopenharmony_ci	}
203862306a36Sopenharmony_ci
203962306a36Sopenharmony_ci	ret = hellcreek_ptp_setup(hellcreek);
204062306a36Sopenharmony_ci	if (ret) {
204162306a36Sopenharmony_ci		dev_err(dev, "Failed to setup PTP!\n");
204262306a36Sopenharmony_ci		goto err_ptp_setup;
204362306a36Sopenharmony_ci	}
204462306a36Sopenharmony_ci
204562306a36Sopenharmony_ci	ret = hellcreek_hwtstamp_setup(hellcreek);
204662306a36Sopenharmony_ci	if (ret) {
204762306a36Sopenharmony_ci		dev_err(dev, "Failed to setup hardware timestamping!\n");
204862306a36Sopenharmony_ci		goto err_tstamp_setup;
204962306a36Sopenharmony_ci	}
205062306a36Sopenharmony_ci
205162306a36Sopenharmony_ci	platform_set_drvdata(pdev, hellcreek);
205262306a36Sopenharmony_ci
205362306a36Sopenharmony_ci	return 0;
205462306a36Sopenharmony_ci
205562306a36Sopenharmony_cierr_tstamp_setup:
205662306a36Sopenharmony_ci	hellcreek_ptp_free(hellcreek);
205762306a36Sopenharmony_cierr_ptp_setup:
205862306a36Sopenharmony_ci	dsa_unregister_switch(hellcreek->ds);
205962306a36Sopenharmony_ci
206062306a36Sopenharmony_ci	return ret;
206162306a36Sopenharmony_ci}
206262306a36Sopenharmony_ci
206362306a36Sopenharmony_cistatic int hellcreek_remove(struct platform_device *pdev)
206462306a36Sopenharmony_ci{
206562306a36Sopenharmony_ci	struct hellcreek *hellcreek = platform_get_drvdata(pdev);
206662306a36Sopenharmony_ci
206762306a36Sopenharmony_ci	if (!hellcreek)
206862306a36Sopenharmony_ci		return 0;
206962306a36Sopenharmony_ci
207062306a36Sopenharmony_ci	hellcreek_hwtstamp_free(hellcreek);
207162306a36Sopenharmony_ci	hellcreek_ptp_free(hellcreek);
207262306a36Sopenharmony_ci	dsa_unregister_switch(hellcreek->ds);
207362306a36Sopenharmony_ci
207462306a36Sopenharmony_ci	return 0;
207562306a36Sopenharmony_ci}
207662306a36Sopenharmony_ci
207762306a36Sopenharmony_cistatic void hellcreek_shutdown(struct platform_device *pdev)
207862306a36Sopenharmony_ci{
207962306a36Sopenharmony_ci	struct hellcreek *hellcreek = platform_get_drvdata(pdev);
208062306a36Sopenharmony_ci
208162306a36Sopenharmony_ci	if (!hellcreek)
208262306a36Sopenharmony_ci		return;
208362306a36Sopenharmony_ci
208462306a36Sopenharmony_ci	dsa_switch_shutdown(hellcreek->ds);
208562306a36Sopenharmony_ci
208662306a36Sopenharmony_ci	platform_set_drvdata(pdev, NULL);
208762306a36Sopenharmony_ci}
208862306a36Sopenharmony_ci
208962306a36Sopenharmony_cistatic const struct hellcreek_platform_data de1soc_r1_pdata = {
209062306a36Sopenharmony_ci	.name		 = "r4c30",
209162306a36Sopenharmony_ci	.num_ports	 = 4,
209262306a36Sopenharmony_ci	.is_100_mbits	 = 1,
209362306a36Sopenharmony_ci	.qbv_support	 = 1,
209462306a36Sopenharmony_ci	.qbv_on_cpu_port = 1,
209562306a36Sopenharmony_ci	.qbu_support	 = 0,
209662306a36Sopenharmony_ci	.module_id	 = 0x4c30,
209762306a36Sopenharmony_ci};
209862306a36Sopenharmony_ci
209962306a36Sopenharmony_cistatic const struct of_device_id hellcreek_of_match[] = {
210062306a36Sopenharmony_ci	{
210162306a36Sopenharmony_ci		.compatible = "hirschmann,hellcreek-de1soc-r1",
210262306a36Sopenharmony_ci		.data	    = &de1soc_r1_pdata,
210362306a36Sopenharmony_ci	},
210462306a36Sopenharmony_ci	{ /* sentinel */ },
210562306a36Sopenharmony_ci};
210662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, hellcreek_of_match);
210762306a36Sopenharmony_ci
210862306a36Sopenharmony_cistatic struct platform_driver hellcreek_driver = {
210962306a36Sopenharmony_ci	.probe	= hellcreek_probe,
211062306a36Sopenharmony_ci	.remove = hellcreek_remove,
211162306a36Sopenharmony_ci	.shutdown = hellcreek_shutdown,
211262306a36Sopenharmony_ci	.driver = {
211362306a36Sopenharmony_ci		.name = "hellcreek",
211462306a36Sopenharmony_ci		.of_match_table = hellcreek_of_match,
211562306a36Sopenharmony_ci	},
211662306a36Sopenharmony_ci};
211762306a36Sopenharmony_cimodule_platform_driver(hellcreek_driver);
211862306a36Sopenharmony_ci
211962306a36Sopenharmony_ciMODULE_AUTHOR("Kurt Kanzenbach <kurt@linutronix.de>");
212062306a36Sopenharmony_ciMODULE_DESCRIPTION("Hirschmann Hellcreek driver");
212162306a36Sopenharmony_ciMODULE_LICENSE("Dual MIT/GPL");
2122