staticvoid ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits, bool set)
{
ksz_rmw8(dev, dev->dev_ops->get_port_addr(port, offset), bits,
set ? bits : 0);
}
/** * ksz8_ind_write8 - EEE/ACL/PME indirect register write * @dev: The device structure. * @table: Function & table select, register 110. * @addr: Indirect access control, register 111. * @data: The data to be written. * * This function performs an indirect register write for EEE, ACL or * PME switch functionalities. Both 8-bit registers 110 and 111 are * written at once with ksz_write16, using the serial multiple write * functionality. * * Return: 0 on success, or an error code on failure.
*/ staticint ksz8_ind_write8(struct ksz_device *dev, u8 table, u16 addr, u8 data)
{ const u16 *regs;
u16 ctrl_addr; int ret = 0;
regs = dev->info->regs;
mutex_lock(&dev->alu_mutex);
ctrl_addr = IND_ACC_TABLE(table) | addr;
ret = ksz_write16(dev, regs[REG_IND_CTRL_0], ctrl_addr); if (!ret)
ret = ksz_write8(dev, regs[REG_IND_BYTE], data);
mutex_unlock(&dev->alu_mutex);
return ret;
}
/** * ksz8_ind_read8 - EEE/ACL/PME indirect register read * @dev: The device structure. * @table: Function & table select, register 110. * @addr: Indirect access control, register 111. * @val: The value read. * * This function performs an indirect register read for EEE, ACL or * PME switch functionalities. Both 8-bit registers 110 and 111 are * written at once with ksz_write16, using the serial multiple write * functionality. * * Return: 0 on success, or an error code on failure.
*/ staticint ksz8_ind_read8(struct ksz_device *dev, u8 table, u16 addr, u8 *val)
{ const u16 *regs;
u16 ctrl_addr; int ret = 0;
regs = dev->info->regs;
mutex_lock(&dev->alu_mutex);
ctrl_addr = IND_ACC_TABLE(table | TABLE_READ) | addr;
ret = ksz_write16(dev, regs[REG_IND_CTRL_0], ctrl_addr); if (!ret)
ret = ksz_read8(dev, regs[REG_IND_BYTE], val);
int ksz8_change_mtu(struct ksz_device *dev, int port, int mtu)
{
u16 frame_size;
if (!dsa_is_cpu_port(dev->ds, port)) return 0;
frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN;
switch (dev->chip_id) { case KSZ8795_CHIP_ID: case KSZ8794_CHIP_ID: case KSZ8765_CHIP_ID: return ksz8795_change_mtu(dev, frame_size); case KSZ8463_CHIP_ID: case KSZ88X3_CHIP_ID: case KSZ8864_CHIP_ID: case KSZ8895_CHIP_ID: return ksz8863_change_mtu(dev, frame_size);
}
return -EOPNOTSUPP;
}
staticint ksz8_port_queue_split(struct ksz_device *dev, int port, int queues)
{
u8 mask_4q, mask_2q;
u8 reg_4q, reg_2q;
u8 data_4q = 0;
u8 data_2q = 0; int ret;
/* KSZ8795 family switches have Weighted Fair Queueing (WFQ) * enabled by default. Enable it for KSZ8873 family switches * too. Default value for KSZ8873 family is strict priority, * which should be enabled by using TC_SETUP_QDISC_ETS, not * by default.
*/
ret = ksz_rmw8(dev, REG_SW_CTRL_3, WEIGHTED_FAIR_QUEUE_ENABLE,
WEIGHTED_FAIR_QUEUE_ENABLE); if (ret) return ret;
} elseif (ksz_is_ksz8463(dev)) {
mask_4q = KSZ8873_PORT_4QUEUE_SPLIT_EN;
mask_2q = KSZ8873_PORT_2QUEUE_SPLIT_EN;
reg_4q = P1CR1;
reg_2q = P1CR1 + 1;
} else {
mask_4q = KSZ8795_PORT_4QUEUE_SPLIT_EN;
mask_2q = KSZ8795_PORT_2QUEUE_SPLIT_EN;
reg_4q = REG_PORT_CTRL_13;
reg_2q = REG_PORT_CTRL_0;
/* TODO: this is legacy from initial KSZ8795 driver, should be * moved to appropriate place in the future.
*/
ret = ksz_rmw8(dev, REG_SW_CTRL_19,
SW_OUT_RATE_LIMIT_QUEUE_BASED,
SW_OUT_RATE_LIMIT_QUEUE_BASED); if (ret) return ret;
}
/* It is almost guaranteed to always read the valid bit because of * slow SPI speed.
*/ for (loop = 2; loop > 0; loop--) {
ksz_read8(dev, regs[REG_IND_MIB_CHECK], &check);
if (check & masks[MIB_COUNTER_VALID]) {
ksz_read32(dev, regs[REG_IND_DATA_LO], &data); if (check & masks[MIB_COUNTER_OVERFLOW])
*cnt += MIB_COUNTER_VALUE + 1;
*cnt += data & MIB_COUNTER_VALUE; break;
}
}
mutex_unlock(&dev->alu_mutex);
}
/* It is almost guaranteed to always read the valid bit because of * slow SPI speed.
*/ for (loop = 2; loop > 0; loop--) {
ksz_read8(dev, regs[REG_IND_MIB_CHECK], &check);
if (check & masks[MIB_COUNTER_VALID]) {
ksz_read32(dev, regs[REG_IND_DATA_LO], &data); if (addr < 2) {
u64 total;
total = check & MIB_TOTAL_BYTES_H;
total <<= 32;
*cnt += total;
*cnt += data; if (check & masks[MIB_COUNTER_OVERFLOW]) {
total = MIB_TOTAL_BYTES_H + 1;
total <<= 32;
*cnt += total;
}
} else { if (check & masks[MIB_COUNTER_OVERFLOW])
*cnt += MIB_PACKET_DROPPED + 1;
*cnt += data & MIB_PACKET_DROPPED;
} break;
}
}
mutex_unlock(&dev->alu_mutex);
}
void ksz8_freeze_mib(struct ksz_device *dev, int port, bool freeze)
{ if (is_ksz88xx(dev)) return;
/* enable the port for flush/freeze function */ if (freeze)
ksz_cfg(dev, REG_SW_CTRL_6, BIT(port), true);
ksz_cfg(dev, REG_SW_CTRL_6, SW_MIB_COUNTER_FREEZE, freeze);
/* disable the port after freeze is done */ if (!freeze)
ksz_cfg(dev, REG_SW_CTRL_6, BIT(port), false);
}
/* For KSZ8795 family. */ if (ksz_is_ksz87xx(dev)) { /* flush all enabled port MIB counters */
ksz_cfg(dev, REG_SW_CTRL_6, BIT(port), true);
ksz_cfg(dev, REG_SW_CTRL_6, SW_MIB_COUNTER_FLUSH, true);
ksz_cfg(dev, REG_SW_CTRL_6, BIT(port), false);
}
mib->cnt_ptr = 0;
/* Some ports may not have MIB counters before SWITCH_COUNTER_NUM. */ while (mib->cnt_ptr < dev->info->reg_mib_cnt) {
dev->dev_ops->r_mib_cnt(dev, port, mib->cnt_ptr,
&mib->counters[mib->cnt_ptr]);
++mib->cnt_ptr;
}
/* last one in storage */
dropped = &mib->counters[dev->info->mib_cnt];
/* Some ports may not have MIB counters after SWITCH_COUNTER_NUM. */ while (mib->cnt_ptr < dev->info->mib_cnt) {
dev->dev_ops->r_mib_pkt(dev, port, mib->cnt_ptr,
dropped, &mib->counters[mib->cnt_ptr]);
++mib->cnt_ptr;
}
}
staticint ksz8_r_table(struct ksz_device *dev, int table, u16 addr, u64 *data)
{ const u16 *regs;
u16 ctrl_addr; int ret;
/* KSZ8795/KSZ8895 family switches have STATIC_MAC_TABLE_USE_FID and * STATIC_MAC_TABLE_FID definitions off by 1 when doing read on the * static MAC table compared to doing write.
*/ if (ksz_is_ksz87xx(dev) || ksz_is_8895_family(dev))
data_hi >>= 1;
alu->is_static = true;
alu->is_use_fid = (data_hi & masks[STATIC_MAC_TABLE_USE_FID]) ? 1 : 0;
alu->fid = (data_hi & masks[STATIC_MAC_TABLE_FID]) >>
shifts[STATIC_MAC_FID];
data = (u16 *)&buf;
addr = vid / 4;
index = vid & 3;
ksz8_r_table(dev, TABLE_VLAN, addr, &buf);
data[index] = vlan;
dev->vlan_cache[vid].table[0] = vlan;
ksz8_w_table(dev, TABLE_VLAN, addr, buf);
}
/** * ksz879x_get_loopback - KSZ879x specific function to get loopback * configuration status for a specific port * @dev: Pointer to the device structure * @port: Port number to query * @val: Pointer to store the result * * This function reads the SMI registers to determine whether loopback mode * is enabled for a specific port. * * Return: 0 on success, error code on failure.
*/ staticint ksz879x_get_loopback(struct ksz_device *dev, u16 port,
u16 *val)
{
u8 stat3; int ret;
ret = ksz_pread8(dev, port, REG_PORT_STATUS_3, &stat3); if (ret) return ret;
if (stat3 & PORT_PHY_LOOPBACK)
*val |= BMCR_LOOPBACK;
return 0;
}
/** * ksz879x_set_loopback - KSZ879x specific function to set loopback mode for * a specific port * @dev: Pointer to the device structure. * @port: Port number to modify. * @val: Value indicating whether to enable or disable loopback mode. * * This function translates loopback bit of the BMCR register into the * corresponding hardware register bit value and writes it to the SMI interface. * * Return: 0 on success, error code on failure.
*/ staticint ksz879x_set_loopback(struct ksz_device *dev, u16 port, u16 val)
{
u8 stat3 = 0;
if (val & BMCR_LOOPBACK)
stat3 |= PORT_PHY_LOOPBACK;
/** * ksz8_r_phy_ctrl - Translates and reads from the SMI interface to a MIIM PHY * Control register (Reg. 31). * @dev: The KSZ device instance. * @port: The port number to be read. * @val: The value read from the SMI interface. * * This function reads the SMI interface and translates the hardware register * bit values into their corresponding control settings for a MIIM PHY Control * register. * * Return: 0 on success, error code on failure.
*/ staticint ksz8_r_phy_ctrl(struct ksz_device *dev, int port, u16 *val)
{ const u16 *regs = dev->info->regs;
u8 reg_val; int ret;
*val = 0;
ret = ksz_pread8(dev, port, regs[P_LINK_STATUS], ®_val); if (ret < 0) return ret;
if (reg_val & PORT_MDIX_STATUS)
*val |= KSZ886X_CTRL_MDIX_STAT;
ret = ksz_pread8(dev, port, REG_PORT_LINK_MD_CTRL, ®_val); if (ret < 0) return ret;
if (reg_val & PORT_FORCE_LINK)
*val |= KSZ886X_CTRL_FORCE_LINK;
if (reg_val & PORT_POWER_SAVING)
*val |= KSZ886X_CTRL_PWRSAVE;
if (reg_val & PORT_PHY_REMOTE_LOOPBACK)
*val |= KSZ886X_CTRL_REMOTE_LOOPBACK;
return 0;
}
/** * ksz8_r_phy_bmcr - Translates and reads from the SMI interface to a MIIM PHY * Basic mode control register (Reg. 0). * @dev: The KSZ device instance. * @port: The port number to be read. * @val: The value read from the SMI interface. * * This function reads the SMI interface and translates the hardware register * bit values into their corresponding control settings for a MIIM PHY Basic * mode control register. * * MIIM Bit Mapping Comparison between KSZ8794 and KSZ8873 * ------------------------------------------------------------------- * MIIM Bit | KSZ8794 Reg/Bit | KSZ8873 Reg/Bit * ----------------------------+-----------------------------+---------------- * Bit 15 - Soft Reset | 0xF/4 | Not supported * Bit 14 - Loopback | 0xD/0 (MAC), 0xF/7 (PHY) ~ 0xD/0 (PHY) * Bit 13 - Force 100 | 0xC/6 = 0xC/6 * Bit 12 - AN Enable | 0xC/7 (reverse logic) ~ 0xC/7 * Bit 11 - Power Down | 0xD/3 = 0xD/3 * Bit 10 - PHY Isolate | 0xF/5 | Not supported * Bit 9 - Restart AN | 0xD/5 = 0xD/5 * Bit 8 - Force Full-Duplex | 0xC/5 = 0xC/5 * Bit 7 - Collision Test/Res. | Not supported | Not supported * Bit 6 - Reserved | Not supported | Not supported * Bit 5 - Hp_mdix | 0x9/7 ~ 0xF/7 * Bit 4 - Force MDI | 0xD/1 = 0xD/1 * Bit 3 - Disable MDIX | 0xD/2 = 0xD/2 * Bit 2 - Disable Far-End F. | ???? | 0xD/4 * Bit 1 - Disable Transmit | 0xD/6 = 0xD/6 * Bit 0 - Disable LED | 0xD/7 = 0xD/7 * ------------------------------------------------------------------- * * Return: 0 on success, error code on failure.
*/ staticint ksz8_r_phy_bmcr(struct ksz_device *dev, u16 port, u16 *val)
{ const u16 *regs = dev->info->regs;
u8 restart, speed, ctrl; int ret;
*val = 0;
ret = ksz_pread8(dev, port, regs[P_NEG_RESTART_CTRL], &restart); if (ret) return ret;
ret = ksz_pread8(dev, port, regs[P_SPEED_STATUS], &speed); if (ret) return ret;
ret = ksz_pread8(dev, port, regs[P_FORCE_CTRL], &ctrl); if (ret) return ret;
if (ctrl & PORT_FORCE_100_MBIT)
*val |= BMCR_SPEED100;
if (ksz_is_ksz88x3(dev)) { if (restart & KSZ8873_PORT_PHY_LOOPBACK)
*val |= BMCR_LOOPBACK;
if ((ctrl & PORT_AUTO_NEG_ENABLE))
*val |= BMCR_ANENABLE;
} else {
ret = ksz879x_get_loopback(dev, port, val); if (ret) return ret;
if (!(ctrl & PORT_AUTO_NEG_DISABLE))
*val |= BMCR_ANENABLE;
}
if (restart & PORT_POWER_DOWN)
*val |= BMCR_PDOWN;
if (restart & PORT_AUTO_NEG_RESTART)
*val |= BMCR_ANRESTART;
if (ctrl & PORT_FORCE_FULL_DUPLEX)
*val |= BMCR_FULLDPLX;
if (speed & PORT_HP_MDIX)
*val |= KSZ886X_BMCR_HP_MDIX;
if (restart & PORT_FORCE_MDIX)
*val |= KSZ886X_BMCR_FORCE_MDI;
if (restart & PORT_AUTO_MDIX_DISABLE)
*val |= KSZ886X_BMCR_DISABLE_AUTO_MDIX;
if (restart & PORT_TX_DISABLE)
*val |= KSZ886X_BMCR_DISABLE_TRANSMIT;
if (restart & PORT_LED_OFF)
*val |= KSZ886X_BMCR_DISABLE_LED;
return 0;
}
int ksz8_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val)
{
u8 ctrl, link, val1, val2; int processed = true; const u16 *regs;
u16 data = 0;
u16 p = phy; int ret;
regs = dev->info->regs;
switch (reg) { case MII_BMCR:
ret = ksz8_r_phy_bmcr(dev, p, &data); if (ret) return ret; break; case MII_BMSR:
ret = ksz_pread8(dev, p, regs[P_LINK_STATUS], &link); if (ret) return ret;
data = BMSR_100FULL |
BMSR_100HALF |
BMSR_10FULL |
BMSR_10HALF |
BMSR_ANEGCAPABLE; if (link & PORT_AUTO_NEG_COMPLETE)
data |= BMSR_ANEGCOMPLETE; if (link & PORT_STAT_LINK_GOOD)
data |= BMSR_LSTATUS; break; case MII_PHYSID1:
data = KSZ8795_ID_HI; break; case MII_PHYSID2: if (ksz_is_ksz88x3(dev))
data = KSZ8863_ID_LO; else
data = KSZ8795_ID_LO; break; case MII_ADVERTISE:
ret = ksz_pread8(dev, p, regs[P_LOCAL_CTRL], &ctrl); if (ret) return ret;
data = ADVERTISE_CSMA; if (ctrl & PORT_AUTO_NEG_SYM_PAUSE)
data |= ADVERTISE_PAUSE_CAP; if (ctrl & PORT_AUTO_NEG_100BTX_FD)
data |= ADVERTISE_100FULL; if (ctrl & PORT_AUTO_NEG_100BTX)
data |= ADVERTISE_100HALF; if (ctrl & PORT_AUTO_NEG_10BT_FD)
data |= ADVERTISE_10FULL; if (ctrl & PORT_AUTO_NEG_10BT)
data |= ADVERTISE_10HALF; break; case MII_LPA:
ret = ksz_pread8(dev, p, regs[P_REMOTE_STATUS], &link); if (ret) return ret;
data = LPA_SLCT; if (link & PORT_REMOTE_SYM_PAUSE)
data |= LPA_PAUSE_CAP; if (link & PORT_REMOTE_100BTX_FD)
data |= LPA_100FULL; if (link & PORT_REMOTE_100BTX)
data |= LPA_100HALF; if (link & PORT_REMOTE_10BT_FD)
data |= LPA_10FULL; if (link & PORT_REMOTE_10BT)
data |= LPA_10HALF; if (data & ~LPA_SLCT)
data |= LPA_LPACK; break; case PHY_REG_LINK_MD:
ret = ksz_pread8(dev, p, REG_PORT_LINK_MD_CTRL, &val1); if (ret) return ret;
ret = ksz_pread8(dev, p, REG_PORT_LINK_MD_RESULT, &val2); if (ret) return ret;
if (val1 & PORT_START_CABLE_DIAG)
data |= PHY_START_CABLE_DIAG;
if (val1 & PORT_CABLE_10M_SHORT)
data |= PHY_CABLE_10M_SHORT;
data |= FIELD_PREP(PHY_CABLE_DIAG_RESULT_M,
FIELD_GET(PORT_CABLE_DIAG_RESULT_M, val1));
data |= FIELD_PREP(PHY_CABLE_FAULT_COUNTER_M,
(FIELD_GET(PORT_CABLE_FAULT_COUNTER_H, val1) << 8) |
FIELD_GET(PORT_CABLE_FAULT_COUNTER_L, val2)); break; case PHY_REG_PHY_CTRL:
ret = ksz8_r_phy_ctrl(dev, p, &data); if (ret) return ret;
/** * ksz8_w_phy_ctrl - Translates and writes to the SMI interface from a MIIM PHY * Control register (Reg. 31). * @dev: The KSZ device instance. * @port: The port number to be configured. * @val: The register value to be written. * * This function translates control settings from a MIIM PHY Control register * into their corresponding hardware register bit values for the SMI * interface. * * Return: 0 on success, error code on failure.
*/ staticint ksz8_w_phy_ctrl(struct ksz_device *dev, int port, u16 val)
{
u8 reg_val = 0; int ret;
if (val & KSZ886X_CTRL_FORCE_LINK)
reg_val |= PORT_FORCE_LINK;
if (val & KSZ886X_CTRL_PWRSAVE)
reg_val |= PORT_POWER_SAVING;
if (val & KSZ886X_CTRL_REMOTE_LOOPBACK)
reg_val |= PORT_PHY_REMOTE_LOOPBACK;
/** * ksz8_w_phy_bmcr - Translates and writes to the SMI interface from a MIIM PHY * Basic mode control register (Reg. 0). * @dev: The KSZ device instance. * @port: The port number to be configured. * @val: The register value to be written. * * This function translates control settings from a MIIM PHY Basic mode control * register into their corresponding hardware register bit values for the SMI * interface. * * MIIM Bit Mapping Comparison between KSZ8794 and KSZ8873 * ------------------------------------------------------------------- * MIIM Bit | KSZ8794 Reg/Bit | KSZ8873 Reg/Bit * ----------------------------+-----------------------------+---------------- * Bit 15 - Soft Reset | 0xF/4 | Not supported * Bit 14 - Loopback | 0xD/0 (MAC), 0xF/7 (PHY) ~ 0xD/0 (PHY) * Bit 13 - Force 100 | 0xC/6 = 0xC/6 * Bit 12 - AN Enable | 0xC/7 (reverse logic) ~ 0xC/7 * Bit 11 - Power Down | 0xD/3 = 0xD/3 * Bit 10 - PHY Isolate | 0xF/5 | Not supported * Bit 9 - Restart AN | 0xD/5 = 0xD/5 * Bit 8 - Force Full-Duplex | 0xC/5 = 0xC/5 * Bit 7 - Collision Test/Res. | Not supported | Not supported * Bit 6 - Reserved | Not supported | Not supported * Bit 5 - Hp_mdix | 0x9/7 ~ 0xF/7 * Bit 4 - Force MDI | 0xD/1 = 0xD/1 * Bit 3 - Disable MDIX | 0xD/2 = 0xD/2 * Bit 2 - Disable Far-End F. | ???? | 0xD/4 * Bit 1 - Disable Transmit | 0xD/6 = 0xD/6 * Bit 0 - Disable LED | 0xD/7 = 0xD/7 * ------------------------------------------------------------------- * * Return: 0 on success, error code on failure.
*/ staticint ksz8_w_phy_bmcr(struct ksz_device *dev, u16 port, u16 val)
{
u8 restart, speed, ctrl, restart_mask; const u16 *regs = dev->info->regs; int ret;
/* Do not support PHY reset function. */ if (val & BMCR_RESET) return 0;
speed = 0; if (val & KSZ886X_BMCR_HP_MDIX)
speed |= PORT_HP_MDIX;
ret = ksz_prmw8(dev, port, regs[P_SPEED_STATUS], PORT_HP_MDIX, speed); if (ret) return ret;
ctrl = 0; if (ksz_is_ksz88x3(dev)) { if ((val & BMCR_ANENABLE))
ctrl |= PORT_AUTO_NEG_ENABLE;
} else { if (!(val & BMCR_ANENABLE))
ctrl |= PORT_AUTO_NEG_DISABLE;
/* Fiber port does not support auto-negotiation. */ if (dev->ports[port].fiber)
ctrl |= PORT_AUTO_NEG_DISABLE;
}
if (val & BMCR_SPEED100)
ctrl |= PORT_FORCE_100_MBIT;
if (val & BMCR_FULLDPLX)
ctrl |= PORT_FORCE_FULL_DUPLEX;
ret = ksz_prmw8(dev, port, regs[P_FORCE_CTRL], PORT_FORCE_100_MBIT | /* PORT_AUTO_NEG_ENABLE and PORT_AUTO_NEG_DISABLE are the same * bits
*/
PORT_FORCE_FULL_DUPLEX | PORT_AUTO_NEG_ENABLE, ctrl); if (ret) return ret;
int ksz8_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
{ const u16 *regs;
u8 ctrl, data;
u16 p = phy; int ret;
regs = dev->info->regs;
switch (reg) { case MII_BMCR:
ret = ksz8_w_phy_bmcr(dev, p, val); if (ret) return ret; break; case MII_ADVERTISE:
ret = ksz_pread8(dev, p, regs[P_LOCAL_CTRL], &ctrl); if (ret) return ret;
data = ctrl;
data &= ~(PORT_AUTO_NEG_SYM_PAUSE |
PORT_AUTO_NEG_100BTX_FD |
PORT_AUTO_NEG_100BTX |
PORT_AUTO_NEG_10BT_FD |
PORT_AUTO_NEG_10BT); if (val & ADVERTISE_PAUSE_CAP)
data |= PORT_AUTO_NEG_SYM_PAUSE; if (val & ADVERTISE_100FULL)
data |= PORT_AUTO_NEG_100BTX_FD; if (val & ADVERTISE_100HALF)
data |= PORT_AUTO_NEG_100BTX; if (val & ADVERTISE_10FULL)
data |= PORT_AUTO_NEG_10BT_FD; if (val & ADVERTISE_10HALF)
data |= PORT_AUTO_NEG_10BT;
if (data != ctrl) {
ret = ksz_pwrite8(dev, p, regs[P_LOCAL_CTRL], data); if (ret) return ret;
} break; case PHY_REG_LINK_MD: if (val & PHY_START_CABLE_DIAG)
ksz_port_cfg(dev, p, REG_PORT_LINK_MD_CTRL, PORT_START_CABLE_DIAG, true); break;
case PHY_REG_PHY_CTRL:
ret = ksz8_w_phy_ctrl(dev, p, val); if (ret) return ret; break; default: break;
}
return 0;
}
void ksz8_cfg_port_member(struct ksz_device *dev, int port, u8 member)
{ int offset = P_MIRROR_CTRL;
u8 data;
if (ksz_is_ksz8463(dev))
offset = P1CR2;
ksz_pread8(dev, port, offset, &data);
data &= ~dev->port_mask;
data |= (member & dev->port_mask);
ksz_pwrite8(dev, port, offset, data);
}
void ksz8_flush_dyn_mac_table(struct ksz_device *dev, int port)
{
u8 learn[DSA_MAX_PORTS]; int first, index, cnt; const u16 *regs; int reg = S_FLUSH_TABLE_CTRL; int mask = SW_FLUSH_DYN_MAC_TABLE;
regs = dev->info->regs;
if ((uint)port < dev->info->port_cnt) {
first = port;
cnt = port + 1;
} else { /* Flush all ports. */
first = 0;
cnt = dev->info->port_cnt;
} for (index = first; index < cnt; index++) {
ksz_pread8(dev, index, regs[P_STP_CTRL], &learn[index]); if (!(learn[index] & PORT_LEARN_DISABLE))
ksz_pwrite8(dev, index, regs[P_STP_CTRL],
learn[index] | PORT_LEARN_DISABLE);
} if (ksz_is_ksz8463(dev)) {
reg = KSZ8463_FLUSH_TABLE_CTRL;
mask = KSZ8463_FLUSH_DYN_MAC_TABLE;
}
ksz_cfg(dev, reg, mask, true); for (index = first; index < cnt; index++) { if (!(learn[index] & PORT_LEARN_DISABLE))
ksz_pwrite8(dev, index, regs[P_STP_CTRL], learn[index]);
}
}
int ksz8_fdb_dump(struct ksz_device *dev, int port,
dsa_fdb_dump_cb_t *cb, void *data)
{
u8 mac[ETH_ALEN];
u8 src_port, fid;
u16 entries = 0; int ret, i;
for (i = 0; i < KSZ8_DYN_MAC_ENTRIES; i++) {
ret = ksz8_r_dyn_mac_table(dev, i, mac, &fid, &src_port,
&entries); if (ret) return ret;
if (i >= entries) return 0;
if (port == src_port) {
ret = cb(mac, fid, false, data); if (ret) return ret;
}
}
return 0;
}
staticint ksz8_add_sta_mac(struct ksz_device *dev, int port, constunsignedchar *addr, u16 vid)
{ struct alu_struct alu; int index, ret; int empty = 0;
alu.port_forward = 0; for (index = 0; index < dev->info->num_statics; index++) { bool valid;
ret = ksz8_r_sta_mac_table(dev, index, &alu, &valid); if (ret) return ret; if (!valid) { /* Remember the first empty entry. */ if (!empty)
empty = index + 1; continue;
}
if (!memcmp(alu.mac, addr, ETH_ALEN) && alu.fid == vid) break;
}
/* no available entry */ if (index == dev->info->num_statics && !empty) return -ENOSPC;
/* Need a way to map VID to FID. */
alu.fid = vid;
}
return ksz8_w_sta_mac_table(dev, index, &alu);
}
staticint ksz8_del_sta_mac(struct ksz_device *dev, int port, constunsignedchar *addr, u16 vid)
{ struct alu_struct alu; int index, ret;
for (index = 0; index < dev->info->num_statics; index++) { bool valid;
ret = ksz8_r_sta_mac_table(dev, index, &alu, &valid); if (ret) return ret; if (!valid) continue;
if (!memcmp(alu.mac, addr, ETH_ALEN) && alu.fid == vid) break;
}
/* no available entry */ if (index == dev->info->num_statics) return 0;
/* clear port */
alu.port_forward &= ~BIT(port); if (!alu.port_forward)
alu.is_static = false;
return ksz8_w_sta_mac_table(dev, index, &alu);
}
int ksz8_mdb_add(struct ksz_device *dev, int port, conststruct switchdev_obj_port_mdb *mdb, struct dsa_db db)
{ return ksz8_add_sta_mac(dev, port, mdb->addr, mdb->vid);
}
int ksz8_mdb_del(struct ksz_device *dev, int port, conststruct switchdev_obj_port_mdb *mdb, struct dsa_db db)
{ return ksz8_del_sta_mac(dev, port, mdb->addr, mdb->vid);
}
int ksz8_fdb_add(struct ksz_device *dev, int port, constunsignedchar *addr,
u16 vid, struct dsa_db db)
{ return ksz8_add_sta_mac(dev, port, addr, vid);
}
int ksz8_fdb_del(struct ksz_device *dev, int port, constunsignedchar *addr,
u16 vid, struct dsa_db db)
{ return ksz8_del_sta_mac(dev, port, addr, vid);
}
int ksz8_port_vlan_filtering(struct ksz_device *dev, int port, bool flag, struct netlink_ext_ack *extack)
{ if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) return -ENOTSUPP;
/* Discard packets with VID not enabled on the switch */
ksz_cfg(dev, S_MIRROR_CTRL, SW_VLAN_ENABLE, flag);
/* Discard packets with VID not enabled on the ingress port */ for (port = 0; port < dev->phy_port_cnt; ++port)
ksz_port_cfg(dev, port, REG_PORT_CTRL_2, PORT_INGRESS_FILTER,
flag);
return 0;
}
staticvoid ksz8_port_enable_pvid(struct ksz_device *dev, int port, bool state)
{ if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) { int reg = REG_SW_INSERT_SRC_PVID;
if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) return -ENOTSUPP;
/* If a VLAN is added with untagged flag different from the * port's Remove Tag flag, we need to change the latter. * Ignore VID 0, which is always untagged. * Ignore CPU port, which will always be tagged.
*/ if (untagged != p->remove_tag && vlan->vid != 0 &&
port != dev->cpu_port) { unsignedint vid;
/* Reject attempts to add a VLAN that requires the * Remove Tag flag to be changed, unless there are no * other VLANs currently configured.
*/ for (vid = 1; vid < dev->info->num_vlans; ++vid) { /* Skip the VID we are going to add or reconfigure */ if (vid == vlan->vid) continue;
if (!p->interface && 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",
port);
p->interface = dev->compat_interface;
}
}
if (cpu_port)
member = dsa_user_ports(ds); else
member = BIT(dsa_upstream_port(ds, port));
ksz8_cfg_port_member(dev, port, member);
/* Disable all WoL options by default. Otherwise * ksz_switch_macaddr_get/put logic will not work properly. * CPU port 4 has no WoL functionality.
*/ if (ksz_is_ksz87xx(dev) && !cpu_port)
ksz8_pme_pwrite8(dev, port, regs[REG_PORT_PME_CTRL], 0);
}
for (i = 0; i < dev->phy_port_cnt; i++) {
ksz_port_stp_state_set(ds, i, BR_STATE_DISABLED);
} for (i = 0; i < dev->phy_port_cnt; i++) {
p = &dev->ports[i];
/* For KSZ8795 family. */ if (ksz_is_ksz87xx(dev)) {
ksz_pread8(dev, i, regs[P_REMOTE_STATUS], &remote); if (remote & KSZ8_PORT_FIBER_MODE)
p->fiber = 1;
} if (p->fiber)
ksz_port_cfg(dev, i, regs[P_STP_CTRL],
PORT_FORCE_FLOW_CTRL, true); else
ksz_port_cfg(dev, i, regs[P_STP_CTRL],
PORT_FORCE_FLOW_CTRL, false); if (p->fiber)
fiber_ports |= (1 << i);
} if (ksz_is_ksz8463(dev)) { /* Setup fiber ports. */ if (fiber_ports) {
fiber_ports &= 3;
regmap_update_bits(ksz_regmap_16(dev),
KSZ8463_REG_CFG_CTRL,
fiber_ports << PORT_COPPER_MODE_S,
0);
regmap_update_bits(ksz_regmap_16(dev),
KSZ8463_REG_DSP_CTRL_6,
COPPER_RECEIVE_ADJUSTMENT, 0);
}
/* Turn off PTP function as the switch's proprietary way of * handling timestamp is not supported in current Linux PTP * stack implementation.
*/
regmap_update_bits(ksz_regmap_16(dev),
KSZ8463_PTP_MSG_CONF1,
PTP_ENABLE, 0);
regmap_update_bits(ksz_regmap_16(dev),
KSZ8463_PTP_CLK_CTRL,
PTP_CLK_ENABLE, 0);
}
}
/** * ksz8_phy_port_link_up - Configures ports with integrated PHYs * @dev: The KSZ device instance. * @port: The port number to configure. * @duplex: The desired duplex mode. * @tx_pause: If true, enables transmit pause. * @rx_pause: If true, enables receive pause. * * Description: * The function configures flow control settings for a given port based on the * desired settings and current duplex mode. * * According to the KSZ8873 datasheet, the PORT_FORCE_FLOW_CTRL bit in the * Port Control 2 register (0x1A for Port 1, 0x22 for Port 2, 0x32 for Port 3) * determines how flow control is handled on the port: * "1 = will always enable full-duplex flow control on the port, regardless * of AN result. * 0 = full-duplex flow control is enabled based on AN result." * * This means that the flow control behavior depends on the state of this bit: * - If PORT_FORCE_FLOW_CTRL is set to 1, the switch will ignore AN results and * force flow control on the port. * - If PORT_FORCE_FLOW_CTRL is set to 0, the switch will enable or disable * flow control based on the AN results. * * However, there is a potential limitation in this configuration. It is * currently not possible to force disable flow control on a port if we still * advertise pause support. While such a configuration is not currently * supported by Linux, and may not make practical sense, it's important to be * aware of this limitation when working with the KSZ8873 and similar devices.
*/ staticvoid ksz8_phy_port_link_up(struct ksz_device *dev, int port, int duplex, bool tx_pause, bool rx_pause)
{ const u16 *regs = dev->info->regs;
u8 sctrl = 0;
/* The KSZ8795 switch differs from the KSZ8873 by supporting * asymmetric pause control. However, since a single bit is used to * control both RX and TX pause, we can't enforce asymmetric pause * control - both TX and RX pause will be either enabled or disabled * together. * * If auto-negotiation is enabled, we usually allow the flow control to * be determined by the auto-negotiation process based on the * capabilities of both link partners. However, for KSZ8873, the * PORT_FORCE_FLOW_CTRL bit may be set by the hardware bootstrap, * ignoring the auto-negotiation result. Thus, even in auto-negotiation * mode, we need to ensure that the PORT_FORCE_FLOW_CTRL bit is * properly cleared. * * In the absence of pause auto-negotiation, we will enforce symmetric * pause control for both variants of switches - KSZ8873 and KSZ8795. * * Autoneg Pause Autoneg rx,tx PORT_FORCE_FLOW_CTRL * 1 1 x 0 * 0 1 x 0 (flow control probably disabled) * x 0 1 1 (flow control force enabled) * 1 0 0 0 (flow control still depends on * aneg result due to hardware) * 0 0 0 0 (flow control probably disabled)
*/ if (dev->ports[port].manual_flow && tx_pause)
sctrl |= PORT_FORCE_FLOW_CTRL;
/** * ksz8_cpu_port_link_up - Configures the CPU port of the switch. * @dev: The KSZ device instance. * @speed: The desired link speed. * @duplex: The desired duplex mode. * @tx_pause: If true, enables transmit pause. * @rx_pause: If true, enables receive pause. * * Description: * The function configures flow control and speed settings for the CPU * port of the switch based on the desired settings, current duplex mode, and * speed.
*/ staticvoid ksz8_cpu_port_link_up(struct ksz_device *dev, int speed, int duplex, bool tx_pause, bool rx_pause)
{ const u16 *regs = dev->info->regs;
u8 ctrl = 0;
/* SW_FLOW_CTRL, SW_HALF_DUPLEX, and SW_10_MBIT bits are bootstrappable * at least on KSZ8873. They can have different values depending on your * board setup.
*/ if (tx_pause || rx_pause)
ctrl |= SW_FLOW_CTRL;
if (duplex == DUPLEX_HALF)
ctrl |= SW_HALF_DUPLEX;
/* This hardware only supports SPEED_10 and SPEED_100. For SPEED_10 * we need to set the SW_10_MBIT bit. Otherwise, we can leave it 0.
*/ if (speed == SPEED_10)
ctrl |= SW_10_MBIT;
void ksz8_phylink_mac_link_up(struct phylink_config *config, struct phy_device *phydev, unsignedint mode,
phy_interface_t interface, int speed, int duplex, bool tx_pause, bool rx_pause)
{ struct dsa_port *dp = dsa_phylink_to_port(config); struct ksz_device *dev = dp->ds->priv; int port = dp->index;
/* If the port is the CPU port, apply special handling. Only the CPU * port is configured via global registers.
*/ if (dev->cpu_port == port)
ksz8_cpu_port_link_up(dev, speed, duplex, tx_pause, rx_pause); elseif (dev->info->internal_phy[port])
ksz8_phy_port_link_up(dev, port, duplex, tx_pause, rx_pause);
}
staticint ksz8_handle_global_errata(struct dsa_switch *ds)
{ struct ksz_device *dev = ds->priv; int ret = 0;
/* KSZ87xx Errata DS80000687C. * Module 2: Link drops with some EEE link partners. * An issue with the EEE next page exchange between the * KSZ879x/KSZ877x/KSZ876x and some EEE link partners may result in * the link dropping.
*/ if (dev->info->ksz87xx_eee_link_erratum)
ret = ksz8_ind_write8(dev, TABLE_EEE, REG_IND_EEE_GLOB2_HI, 0);
return ret;
}
int ksz8_enable_stp_addr(struct ksz_device *dev)
{ struct alu_struct alu;
int ksz8_setup(struct dsa_switch *ds)
{ struct ksz_device *dev = ds->priv; const u16 *regs = dev->info->regs; int i, ret = 0;
ds->mtu_enforcement_ingress = true;
/* We rely on software untagging on the CPU port, so that we * can support both tagged and untagged VLANs
*/
ds->untag_bridge_pvid = true;
/* VLAN filtering is partly controlled by the global VLAN * Enable flag
*/
ds->vlan_filtering_is_global = true;
/* Enable automatic fast aging when link changed detected. */
ksz_cfg(dev, S_LINK_AGING_CTRL, SW_LINK_AUTO_AGING, true);
/* Enable aggressive back off algorithm in half duplex mode. */
ret = ksz_rmw8(dev, REG_SW_CTRL_1, SW_AGGR_BACKOFF, SW_AGGR_BACKOFF); if (ret) return ret;
/* * Make sure unicast VLAN boundary is set as default and * enable no excessive collision drop.
*/
ret = ksz_rmw8(dev, REG_SW_CTRL_2,
UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP,
UNICAST_VLAN_BOUNDARY | NO_EXC_COLLISION_DROP); if (ret) return ret;
if (!ksz_is_ksz88x3(dev) && !ksz_is_ksz8463(dev))
ksz_cfg(dev, REG_SW_CTRL_19, SW_INS_TAG_ENABLE, true);
for (i = 0; i < (dev->info->num_vlans / 4); i++)
ksz8_r_vlan_entries(dev, i);
/* Make sure PME (WoL) is not enabled. If requested, it will * be enabled by ksz_wol_pre_shutdown(). Otherwise, some PMICs * do not like PME events changes before shutdown. PME only * available on KSZ87xx family.
*/ if (ksz_is_ksz87xx(dev)) {
ret = ksz8_pme_write8(dev, regs[REG_SW_PME_CTRL], 0); if (!ret)
ret = ksz_rmw8(dev, REG_INT_ENABLE, INT_PME, 0);
}
if (!ret) return ksz8_handle_global_errata(ds); else return ret;
}
/* Silicon Errata Sheet (DS80000830A): * "Port 1 does not respond to received flow control PAUSE frames" * So, disable Pause support on "Port 1" (port == 0) for all ksz88x3 * switches.
*/ if (!ksz_is_ksz88x3(dev) || port)
config->mac_capabilities |= MAC_SYM_PAUSE;
/* Asym pause is not supported on KSZ8863 and KSZ8873 */ if (!ksz_is_ksz88x3(dev))
config->mac_capabilities |= MAC_ASYM_PAUSE;
}
u32 ksz8_get_port_addr(int port, int offset)
{ return PORT_CTRL_ADDR(port, offset);
}
int ksz8463_r_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 *val)
{
u16 sw_reg = 0;
u16 data = 0; int ret;
if (phy > 1) return -ENOSPC; switch (reg) { case MII_PHYSID1:
sw_reg = ksz8463_get_phy_addr(phy, 0, PHY1IHR); break; case MII_PHYSID2:
sw_reg = ksz8463_get_phy_addr(phy, 0, PHY1ILR); break; case MII_BMCR: case MII_BMSR: case MII_ADVERTISE: case MII_LPA:
sw_reg = ksz8463_get_phy_addr(phy, reg, P1MBCR); break; case MII_TPISTATUS: /* This register holds the PHY interrupt status for simulated * Micrel KSZ PHY.
*/
data = 0x0505; break; default: break;
} if (sw_reg) {
ret = ksz_read16(dev, sw_reg, &data); if (ret) return ret;
}
*val = data;
return 0;
}
int ksz8463_w_phy(struct ksz_device *dev, u16 phy, u16 reg, u16 val)
{
u16 sw_reg = 0; int ret;
if (phy > 1) return -ENOSPC;
/* No write to fiber port. */ if (dev->ports[phy].fiber) return 0; switch (reg) { case MII_BMCR: case MII_ADVERTISE:
sw_reg = ksz8463_get_phy_addr(phy, reg, P1MBCR); break; default: break;
} if (sw_reg) {
ret = ksz_write16(dev, sw_reg, val); if (ret) return ret;
}
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.