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