staticint ksz9477_pcs_read(struct mii_bus *bus, int phy, int mmd, int reg)
{ struct ksz_device *dev = bus->priv; int port = ksz_get_sgmii_port(dev);
u16 val;
port_sgmii_r(dev, port, mmd, reg, &val);
/* Simulate a value to activate special code in the XPCS driver if *supported.
*/ if (mmd == MDIO_MMD_PMAPMD) { if (reg == MDIO_DEVID1)
val = 0x9477; elseif (reg == MDIO_DEVID2)
val = 0x22 << 10;
} elseif (mmd == MDIO_MMD_VEND2) { struct ksz_port *p = &dev->ports[port];
/* Need to update MII_BMCR register with the exact speed and *duplexmodewhenrunninginSGMIImodeandthisregisteris *usedtodetectconnectedspeedinthatmode.
*/ if (reg == MMD_SR_MII_AUTO_NEG_STATUS) { int duplex, speed;
staticint ksz9477_pcs_write(struct mii_bus *bus, int phy, int mmd, int reg,
u16 val)
{ struct ksz_device *dev = bus->priv; int port = ksz_get_sgmii_port(dev);
if (mmd == MDIO_MMD_VEND2) { struct ksz_port *p = &dev->ports[port];
/* Need these bits for 1000BASE-X mode to work with *ANon.
*/ if (!(val & sgmii_mode))
val |= SR_MII_SGMII_LINK_UP |
SR_MII_TX_CFG_PHY_MASTER;
/* SGMII interrupt in the port cannot be masked, so *makesureinterruptisnotenabledasitisnot *handled.
*/
val &= ~SR_MII_AUTO_NEG_COMPLETE_INTR;
} elseif (reg == MII_BMCR) { /* The MII_ADVERTISE register needs to write once *beforedoingauto-negotiationforthecorrect *config_wordtobesentoutafterreset.
*/ if ((val & BMCR_ANENABLE) && !p->sgmii_adv_write) {
u16 adv;
/* The SGMII port cannot disable flow control *soitisbettertojustadvertisesymmetric *pause.
*/
port_sgmii_r(dev, port, mmd, MII_ADVERTISE,
&adv);
adv |= ADVERTISE_1000XPAUSE;
adv &= ~ADVERTISE_1000XPSE_ASYM;
port_sgmii_w(dev, port, mmd, MII_ADVERTISE,
adv);
p->sgmii_adv_write = 1;
} elseif (val & BMCR_RESET) {
p->sgmii_adv_write = 0;
}
} elseif (reg == MII_ADVERTISE) { /* XPCS driver writes to this register so there is no *needtoupdateitfortheerrata.
*/
p->sgmii_adv_write = 1;
}
}
port_sgmii_w(dev, port, mmd, reg, val);
return0;
}
int ksz9477_pcs_create(struct ksz_device *dev)
{ /* This chip has a SGMII port. */ if (ksz_has_sgmii_port(dev)) { int port = ksz_get_sgmii_port(dev); struct ksz_port *p = &dev->ports[port]; struct phylink_pcs *pcs; struct mii_bus *bus; int ret;
bus = devm_mdiobus_alloc(dev->dev); if (!bus) return -ENOMEM;
/* retain the flush/freeze bit */
data = p->freeze ? MIB_COUNTER_FLUSH_FREEZE : 0;
data |= MIB_COUNTER_READ;
data |= (addr << MIB_COUNTER_INDEX_S);
ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, data);
ret = regmap_read_poll_timeout(ksz_regmap_32(dev),
PORT_CTRL_ADDR(port, REG_PORT_MIB_CTRL_STAT__4),
val, !(val & MIB_COUNTER_READ), 10, 1000); /* failed to read MIB. get out of loop */ if (ret) {
dev_dbg(dev->dev, "Failed to get MIB\n"); return;
}
/* enable/disable the port for flush/freeze function */
mutex_lock(&p->mib.cnt_mutex);
ksz_pwrite32(dev, port, REG_PORT_MIB_CTRL_STAT__4, val);
/* used by MIB counter reading code to know freeze is enabled */
p->freeze = freeze;
mutex_unlock(&p->mib.cnt_mutex);
}
staticint ksz9477_half_duplex_monitor(struct ksz_device *dev, int port,
u64 tx_late_col)
{
u8 lue_ctrl;
u32 pmavbc;
u16 pqm; int ret;
/* Errata DS80000754 recommends monitoring potential faults in *half-duplexmode.Theswitchmightnotbeabletocommunicateanymore *inthesestates.Ifyouseethismessage,pleasereadthe *errata-sheetformoreinformation: *https://ww1.microchip.com/downloads/aemDocuments/documents/UNG/ProductDocuments/Errata/KSZ9477S-Errata-DS80000754.pdf *Toworkaroundthisissue,half-duplexmodeshouldbeavoided. *Asoftwareresetcouldbeimplementedtorecoverfromthisstate.
*/
dev_warn_once(dev->dev, "Half-duplex detected on port %d, transmission halt may occur\n",
port); if (tx_late_col != 0) { /* Transmission halt with late collisions */
dev_crit_once(dev->dev, "TX late collisions detected, transmission may be halted on port %d\n",
port);
}
ret = ksz_read8(dev, REG_SW_LUE_CTRL_0, &lue_ctrl); if (ret) return ret; if (lue_ctrl & SW_VLAN_ENABLE) {
ret = ksz_pread16(dev, port, REG_PORT_QM_TX_CNT_0__4, &pqm); if (ret) return ret;
ret = ksz_read32(dev, REG_PMAVBC, &pmavbc); if (ret) return ret;
if ((FIELD_GET(PMAVBC_MASK, pmavbc) <= PMAVBC_MIN) ||
(FIELD_GET(PORT_QM_TX_CNT_M, pqm) >= PORT_QM_TX_CNT_MAX)) { /* Transmission halt with Half-Duplex and VLAN */
dev_crit_once(dev->dev, "resources out of limits, transmission may be halted\n");
}
}
return ret;
}
int ksz9477_errata_monitor(struct ksz_device *dev, int port,
u64 tx_late_col)
{
u8 status; int ret;
ret = ksz_pread8(dev, port, REG_PORT_STATUS_0, &status); if (ret) return ret;
if (!(FIELD_GET(PORT_INTF_SPEED_MASK, status)
== PORT_INTF_SPEED_NONE) &&
!(status & PORT_INTF_FULL_DUPLEX)) {
ret = ksz9477_half_duplex_monitor(dev, port, tx_late_col);
}
staticvoid ksz9477_r_phy_quirks(struct ksz_device *dev, u16 addr, u16 reg,
u16 *data)
{ /* KSZ8563R do not have extended registers but BMSR_ESTATEN and *BMSR_ERCAPbitsareset.
*/ if (dev->chip_id == KSZ8563_CHIP_ID && reg == MII_BMSR)
*data &= ~(BMSR_ESTATEN | BMSR_ERCAP);
}
int ksz9477_r_phy(struct ksz_device *dev, u16 addr, u16 reg, u16 *data)
{
u16 val = 0xffff; int ret;
/* No real PHY after this. Simulate the PHY. *AfixedPHYcanbesetupinthedevicetree,butthisfunctionis *stillcalledforthatportduringinitialization. *ForRGMIIPHYthereisnowaytoaccessitsothefixedPHYshould *beused.ForSGMIIPHYthesupportingcodewillbeaddedlater.
*/ if (!dev->info->internal_phy[addr]) { struct ksz_port *p = &dev->ports[addr];
switch (reg) { case MII_BMCR:
val = 0x1140; break; case MII_BMSR:
val = 0x796d; break; case MII_PHYSID1:
val = 0x0022; break; case MII_PHYSID2:
val = 0x1631; break; case MII_ADVERTISE:
val = 0x05e1; break; case MII_LPA:
val = 0xc5e1; break; case MII_CTRL1000:
val = 0x0700; break; case MII_STAT1000: if (p->phydev.speed == SPEED_1000)
val = 0x3800; else
val = 0; break;
}
} else {
ret = ksz_pread16(dev, addr, 0x100 + (reg << 1), &val); if (ret) return ret;
int ksz9477_fdb_dump(struct ksz_device *dev, int port,
dsa_fdb_dump_cb_t *cb, void *data)
{ int ret = 0;
u32 ksz_data;
u32 alu_table[4]; struct alu_struct alu; int timeout;
mutex_lock(&dev->alu_mutex);
/* start ALU search */
ksz_write32(dev, REG_SW_ALU_CTRL__4, ALU_START | ALU_SEARCH);
do {
timeout = 1000; do {
ksz_read32(dev, REG_SW_ALU_CTRL__4, &ksz_data); if ((ksz_data & ALU_VALID) || !(ksz_data & ALU_START)) break;
usleep_range(1, 10);
} while (timeout-- > 0);
if (!timeout) {
dev_dbg(dev->dev, "Failed to search ALU\n");
ret = -ETIMEDOUT; gotoexit;
}
if (!(ksz_data & ALU_VALID)) continue;
/* read ALU table */
ksz9477_read_table(dev, alu_table);
ksz9477_convert_alu(&alu, alu_table);
if (alu.port_forward & BIT(port)) {
ret = cb(alu.mac, alu.fid, alu.is_static, data); if (ret) gotoexit;
}
} while (ksz_data & ALU_START);
exit:
/* stop ALU search */
ksz_write32(dev, REG_SW_ALU_CTRL__4, 0);
mutex_unlock(&dev->alu_mutex);
return ret;
}
int ksz9477_mdb_add(struct ksz_device *dev, int port, conststruct switchdev_obj_port_mdb *mdb, struct dsa_db db)
{
u32 static_table[4]; const u8 *shifts; const u32 *masks;
u32 data; int index;
u32 mac_hi, mac_lo; int err = 0;
for (index = 0; index < dev->info->num_statics; index++) { /* find empty slot first */
data = (index << shifts[ALU_STAT_INDEX]) |
masks[ALU_STAT_READ] | ALU_STAT_START;
ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);
/* wait to be finished */
err = ksz9477_wait_alu_sta_ready(dev); if (err) {
dev_dbg(dev->dev, "Failed to read ALU STATIC\n"); gotoexit;
}
/* read ALU static table */
ksz9477_read_table(dev, static_table);
if (static_table[0] & ALU_V_STATIC_VALID) { /* check this has same vid & mac address */ if (((static_table[2] >> ALU_V_FID_S) == mdb->vid) &&
((static_table[2] & ALU_V_MAC_ADDR_HI) == mac_hi) &&
static_table[3] == mac_lo) { /* found matching one */ break;
}
} else { /* found empty one */ break;
}
}
/* no available entry */ if (index == dev->info->num_statics) {
err = -ENOSPC; gotoexit;
}
data = (index << shifts[ALU_STAT_INDEX]) | ALU_STAT_START;
ksz_write32(dev, REG_SW_ALU_STAT_CTRL__4, data);
/* wait to be finished */
ret = ksz9477_wait_alu_sta_ready(dev); if (ret)
dev_dbg(dev->dev, "Failed to read ALU STATIC\n");
exit:
mutex_unlock(&dev->alu_mutex);
return ret;
}
int ksz9477_port_mirror_add(struct ksz_device *dev, int port, struct dsa_mall_mirror_tc_entry *mirror, bool ingress, struct netlink_ext_ack *extack)
{
u8 data; int p;
/* Limit to one sniffer port *Checkifanyoftheportisalreadysetforsniffing *Ifyes,instructtheusertoremovethepreviousentry&exit
*/ for (p = 0; p < dev->info->port_cnt; p++) { /* Skip the current sniffing port */ if (p == mirror->to_local_port) continue;
ksz_pread8(dev, p, P_MIRROR_CTRL, &data);
if (data & PORT_MIRROR_SNIFFER) {
NL_SET_ERR_MSG_MOD(extack, "Sniffer port is already configured, delete existing rules & retry"); return -EBUSY;
}
}
/* Check if any of the port is still referring to sniffer port */ for (p = 0; p < dev->info->port_cnt; p++) {
ksz_pread8(dev, p, P_MIRROR_CTRL, &data);
/* delete sniffing if there are no other mirroring rules */ if (!in_use)
ksz_port_cfg(dev, mirror->to_local_port, P_MIRROR_CTRL,
PORT_MIRROR_SNIFFER, false);
}
/* The aging timer comprises a 3-bit multiplier and an 8-bit second *value.Eitherofthemcannotbezero.Themaximumtimeristhen *7*255=1785seconds.
*/ if (!secs)
secs = 1;
/* Return error if too large. */ elseif (secs > 7 * MAX_TIMER_VAL) return -EINVAL;
ret = ksz_read8(dev, REG_SW_LUE_CTRL_0, &value); if (ret < 0) return ret;
/* Check whether there is need to update the multiplier. */
mult = FIELD_GET(SW_AGE_CNT_M, value);
max_val = MAX_TIMER_VAL; if (mult > 0) { /* Try to use the same multiplier already in the register as *thehardwaredefaultusesmultiplier4and75secondsfor *300seconds.
*/
max_val = DIV_ROUND_UP(secs, mult); if (max_val > MAX_TIMER_VAL || max_val * mult != secs)
max_val = MAX_TIMER_VAL;
}
data = DIV_ROUND_UP(secs, max_val); if (mult != data) {
value &= ~SW_AGE_CNT_M;
value |= FIELD_PREP(SW_AGE_CNT_M, data);
ret = ksz_write8(dev, REG_SW_LUE_CTRL_0, value); if (ret < 0) return ret;
}
value = DIV_ROUND_UP(secs, data); return ksz_write8(dev, REG_SW_LUE_CTRL_3, value);
}
void ksz9477_port_queue_split(struct ksz_device *dev, int port)
{
u8 data;
if (dev->info->num_tx_queues == 8)
data = PORT_EIGHT_QUEUE; elseif (dev->info->num_tx_queues == 4)
data = PORT_FOUR_QUEUE; elseif (dev->info->num_tx_queues == 2)
data = PORT_TWO_QUEUE; else
data = PORT_SINGLE_QUEUE;
/* force flow control for non-PHY ports only */
ksz_port_cfg(dev, port, REG_PORT_CTRL_0,
PORT_FORCE_TX_FLOW_CTRL | PORT_FORCE_RX_FLOW_CTRL,
!dev->info->internal_phy[port]);
if (cpu_port)
member = dsa_user_ports(ds); else
member = BIT(dsa_upstream_port(ds, port));
/* clear pending wake flags */
ksz_handle_wake_reason(dev, port);
/* Disable all WoL options by default. Otherwise *ksz_switch_macaddr_get/putlogicwillnotworkproperly.
*/
ksz_pwrite8(dev, port, regs[REG_PORT_PME_CTRL], 0);
}
for (i = 0; i < dev->info->port_cnt; i++) { if (dsa_is_cpu_port(ds, i) &&
(dev->info->cpu_ports & (1 << i))) {
phy_interface_t interface; constchar *prev_msg; constchar *prev_mode;
dev->cpu_port = i;
p = &dev->ports[i];
/* Read from XMII register to determine host port *interface.Ifsetspecificallyindevicetree *notethedifferencetohelpdebugging.
*/
interface = ksz9477_get_interface(dev, i); if (!p->interface) { if (dev->compat_interface) {
dev_warn(dev->dev, "Using legacy switch \"phy-mode\" property, because it is missing on port %d node. " "Please update your device tree.\n",
i);
p->interface = dev->compat_interface;
} else {
p->interface = interface;
}
} if (interface && interface != p->interface) {
prev_msg = " instead of ";
prev_mode = phy_modes(interface);
} else {
prev_msg = "";
prev_mode = "";
}
dev_info(dev->dev, "Port%d: using phy mode %s%s%s\n",
i,
phy_modes(p->interface),
prev_msg,
prev_mode);
/* enable cpu port */
ksz9477_port_setup(dev, i, true);
}
}
for (i = 0; i < dev->info->port_cnt; i++) { if (i == dev->cpu_port) continue;
ksz_port_stp_state_set(ds, i, BR_STATE_DISABLED);
/* Power down the internal PHY if port is unused. */ if (dsa_is_unused_port(ds, i) && dev->info->internal_phy[i])
ksz_pwrite16(dev, i, 0x100, BMCR_PDOWN);
}
}
/* Use collision based back pressure mode. */
ksz_cfg(dev, REG_SW_MAC_CTRL_1, SW_BACK_PRESSURE,
SW_BACK_PRESSURE_COLLISION);
/* Now we can configure default MTU value */
ret = regmap_update_bits(ksz_regmap_16(dev), REG_SW_MTU__2, REG_SW_MTU_MASK,
VLAN_ETH_FRAME_LEN + ETH_FCS_LEN); if (ret) return ret;
/* enable global MIB counter freeze function */
ksz_cfg(dev, REG_SW_MAC_CTRL_6, SW_MIB_COUNTER_FREEZE, true);
/* Make sure PME (WoL) is not enabled. If requested, it will *beenabledbyksz_wol_pre_shutdown().Otherwise,somePMICs *donotlikePMEeventschangesbeforeshutdown.
*/ return ksz_write8(dev, regs[REG_SW_PME_CTRL], 0);
}
u32 ksz9477_get_port_addr(int port, int offset)
{ return PORT_CTRL_ADDR(port, offset);
}
int ksz9477_tc_cbs_set_cinc(struct ksz_device *dev, int port, u32 val)
{
val = val >> 8;
if (!dev->hsr_ports) { /* Enable discarding of received HSR frames */
ksz_read8(dev, REG_HSR_ALU_CTRL_0__1, &data);
data |= HSR_DUPLICATE_DISCARD;
data &= ~HSR_NODE_UNICAST;
ksz_write8(dev, REG_HSR_ALU_CTRL_0__1, data);
}
/* Enable per port self-address filtering. *Theglobalself-addressfilteringhasalreadybeenenabledinthe *ksz9477_reset_switch()function.
*/
ksz_port_cfg(dev, port, REG_PORT_LUE_CTRL, PORT_SRC_ADDR_FILTER, true);
/* Setup HW supported features for lan HSR ports */
user = dsa_to_port(ds, port)->user;
user->features |= KSZ9477_SUPPORTED_HSR_FEATURES;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.