/* ksz9477_drive_strengths - Drive strength mapping for KSZ9477 variants * * This values are not documented in KSZ9477 variants but confirmed by * Microchip that KSZ9477, KSZ9567, KSZ8567, KSZ9897, KSZ9896, KSZ9563, KSZ9893 * and KSZ8563 are using same register (drive strength) settings like KSZ8795. * * Documentation in KSZ8795CLX provides more information with some * recommendations: * - for high speed signals * 1. 4 mA or 8 mA is often used for MII, RMII, and SPI interface with using * 2.5V or 3.3V VDDIO. * 2. 12 mA or 16 mA is often used for MII, RMII, and SPI interface with * using 1.8V VDDIO. * 3. 20 mA or 24 mA is often used for GMII/RGMII interface with using 2.5V * or 3.3V VDDIO. * 4. 28 mA is often used for GMII/RGMII interface with using 1.8V VDDIO. * 5. In same interface, the heavy loading should use higher one of the * drive current strength. * - for low speed signals * 1. 3.3V VDDIO, use either 4 mA or 8 mA. * 2. 2.5V VDDIO, use either 8 mA or 12 mA. * 3. 1.8V VDDIO, use either 12 mA or 16 mA. * 4. If it is heavy loading, can use higher drive current strength.
*/ staticconststruct ksz_drive_strength ksz9477_drive_strengths[] = {
{ SW_DRIVE_STRENGTH_2MA, 2000 },
{ SW_DRIVE_STRENGTH_4MA, 4000 },
{ SW_DRIVE_STRENGTH_8MA, 8000 },
{ SW_DRIVE_STRENGTH_12MA, 12000 },
{ SW_DRIVE_STRENGTH_16MA, 16000 },
{ SW_DRIVE_STRENGTH_20MA, 20000 },
{ SW_DRIVE_STRENGTH_24MA, 24000 },
{ SW_DRIVE_STRENGTH_28MA, 28000 },
};
/* ksz88x3_drive_strengths - Drive strength mapping for KSZ8863, KSZ8873, .. * variants. * This values are documented in KSZ8873 and KSZ8863 datasheets.
*/ staticconststruct ksz_drive_strength ksz88x3_drive_strengths[] = {
{ 0, 8000 },
{ KSZ8873_DRIVE_STRENGTH_16MA, 16000 },
};
/** * ksz_phylink_mac_disable_tx_lpi() - Callback to signal LPI support (Dummy) * @config: phylink config structure * * This function is a dummy handler. See ksz_phylink_mac_enable_tx_lpi() for * a detailed explanation of EEE/LPI handling in KSZ switches.
*/ staticvoid ksz_phylink_mac_disable_tx_lpi(struct phylink_config *config)
{
}
/** * ksz_phylink_mac_enable_tx_lpi() - Callback to signal LPI support (Dummy) * @config: phylink config structure * @timer: timer value before entering LPI (unused) * @tx_clock_stop: whether to stop the TX clock in LPI mode (unused) * * This function signals to phylink that the driver architecture supports * LPI management, enabling phylink to control EEE advertisement during * negotiation according to IEEE Std 802.3 (Clause 78). * * Hardware Management of EEE/LPI State: * For KSZ switch ports with integrated PHYs (e.g., KSZ9893R ports 1-2), * observation and testing suggest that the actual EEE / Low Power Idle (LPI) * state transitions are managed autonomously by the hardware based on * the auto-negotiation results. (Note: While the datasheet describes EEE * operation based on negotiation, it doesn't explicitly detail the internal * MAC/PHY interaction, so autonomous hardware management of the MAC state * for LPI is inferred from observed behavior). * This hardware control, consistent with the switch's ability to operate * autonomously via strapping, means MAC-level software intervention is not * required or exposed for managing the LPI state once EEE is negotiated. * (Ref: KSZ9893R Data Sheet DS00002420D, primarily Section 4.7.5 explaining * EEE, also Sections 4.1.7 on Auto-Negotiation and 3.2.1 on Configuration * Straps). * * Additionally, ports configured as MAC interfaces (e.g., KSZ9893R port 3) * lack documented MAC-level LPI control. * * Therefore, this callback performs no action and serves primarily to inform * phylink of LPI awareness and to document the inferred hardware behavior. * * Returns: 0 (Always success)
*/ staticint ksz_phylink_mac_enable_tx_lpi(struct phylink_config *config,
u32 timer, bool tx_clock_stop)
{ return 0;
}
[KSZ8794] = { /* WARNING * ======= * KSZ8794 is similar to KSZ8795, except the port map * contains a gap between external and CPU ports, the * port map is NOT continuous. The per-port register * map is shifted accordingly too, i.e. registers at * offset 0x40 are NOT used on KSZ8794 and they ARE * used on KSZ8795 for external port 3. * external cpu * KSZ8794 0,1,2 4 * KSZ8795 0,1,2,3 4 * KSZ8765 0,1,2,3 4 * port_cnt is configured as 5, even though it is 4
*/
.chip_id = KSZ8794_CHIP_ID,
.dev_name = "KSZ8794",
.num_vlans = 4096,
.num_alus = 0,
.num_statics = 32,
.cpu_ports = 0x10, /* can be configured as cpu port */
.port_cnt = 5, /* total cpu and user ports */
.num_tx_queues = 4,
.num_ipms = 4,
.ops = &ksz87xx_dev_ops,
.phylink_mac_ops = &ksz8_phylink_mac_ops,
.ksz87xx_eee_link_erratum = true,
.mib_names = ksz9477_mib_names,
.mib_cnt = ARRAY_SIZE(ksz9477_mib_names),
.reg_mib_cnt = MIB_COUNTER_NUM,
.regs = ksz8795_regs,
.masks = ksz8795_masks,
.shifts = ksz8795_shifts,
.xmii_ctrl0 = ksz8795_xmii_ctrl0,
.xmii_ctrl1 = ksz8795_xmii_ctrl1,
.supports_mii = {false, false, false, false, true},
.supports_rmii = {false, false, false, false, true},
.supports_rgmii = {false, false, false, false, true},
.internal_phy = {true, true, true, false, false},
},
if (dev->info->supports_mii[port])
__set_bit(PHY_INTERFACE_MODE_MII, config->supported_interfaces);
if (dev->info->supports_rmii[port])
__set_bit(PHY_INTERFACE_MODE_RMII,
config->supported_interfaces);
if (dev->info->supports_rgmii[port])
phy_interface_set_rgmii(config->supported_interfaces);
if (dev->info->internal_phy[port]) {
__set_bit(PHY_INTERFACE_MODE_INTERNAL,
config->supported_interfaces); /* Compatibility for phylib's default interface type when the * phy-mode property is absent
*/
__set_bit(PHY_INTERFACE_MODE_GMII,
config->supported_interfaces);
}
if (dev->dev_ops->get_caps)
dev->dev_ops->get_caps(dev, port, config);
if (ds->ops->support_eee && ds->ops->support_eee(ds, port)) {
memcpy(config->lpi_interfaces, config->supported_interfaces, sizeof(config->lpi_interfaces));
config->lpi_capabilities = MAC_100FD; if (dev->info->gbit_capable[port])
config->lpi_capabilities |= MAC_1000FD;
staticvoid ksz_get_strings(struct dsa_switch *ds, int port,
u32 stringset, uint8_t *buf)
{ struct ksz_device *dev = ds->priv; int i;
if (stringset != ETH_SS_STATS) return;
for (i = 0; i < dev->info->mib_cnt; i++)
ethtool_puts(&buf, dev->info->mib_names[i].string);
}
/** * ksz_update_port_member - Adjust port forwarding rules based on STP state and * isolation settings. * @dev: A pointer to the struct ksz_device representing the device. * @port: The port number to adjust. * * This function dynamically adjusts the port membership configuration for a * specified port and other device ports, based on Spanning Tree Protocol (STP) * states and port isolation settings. Each port, including the CPU port, has a * membership register, represented as a bitfield, where each bit corresponds * to a port number. A set bit indicates permission to forward frames to that * port. This function iterates over all ports, updating the membership register * to reflect current forwarding permissions: * * 1. Forwards frames only to ports that are part of the same bridge group and * in the BR_STATE_FORWARDING state. * 2. Takes into account the isolation status of ports; ports in the * BR_STATE_FORWARDING state with BR_ISOLATED configuration will not forward * frames to each other, even if they are in the same bridge group. * 3. Ensures that the CPU port is included in the membership based on its * upstream port configuration, allowing for management and control traffic * to flow as required.
*/ staticvoid ksz_update_port_member(struct ksz_device *dev, int port)
{ struct ksz_port *p = &dev->ports[port]; struct dsa_switch *ds = dev->ds;
u8 port_member = 0, cpu_port; conststruct dsa_port *dp; int i, j;
for (i = 0; i < ds->num_ports; i++) { conststruct dsa_port *other_dp = dsa_to_port(ds, i); struct ksz_port *other_p = &dev->ports[i];
u8 val = 0;
if (!dsa_is_user_port(ds, i)) continue; if (port == i) continue; if (!dsa_port_bridge_same(dp, other_dp)) continue; if (other_p->stp_state != BR_STATE_FORWARDING) continue;
/* At this point we know that "port" and "other" port [i] are in * the same bridge group and that "other" port [i] is in * forwarding stp state. If "port" is also in forwarding stp * state, we can allow forwarding from port [port] to port [i]. * Except if both ports are isolated.
*/ if (p->stp_state == BR_STATE_FORWARDING &&
!(p->isolated && other_p->isolated)) {
val |= BIT(port);
port_member |= BIT(i);
}
/* Retain port [i]'s relationship to other ports than [port] */ for (j = 0; j < ds->num_ports; j++) { conststruct dsa_port *third_dp; struct ksz_port *third_p;
if (j == i) continue; if (j == port) continue; if (!dsa_is_user_port(ds, j)) continue;
third_p = &dev->ports[j]; if (third_p->stp_state != BR_STATE_FORWARDING) continue;
third_dp = dsa_to_port(ds, j);
/* Now we updating relation of the "other" port [i] to * the "third" port [j]. We already know that "other" * port [i] is in forwarding stp state and that "third" * port [j] is in forwarding stp state too. * We need to check if "other" port [i] and "third" port * [j] are in the same bridge group and not isolated * before allowing forwarding from port [i] to port [j].
*/ if (dsa_port_bridge_same(other_dp, third_dp) &&
!(other_p->isolated && third_p->isolated))
val |= BIT(j);
}
dev->dev_ops->cfg_port_member(dev, i, val | cpu_port);
}
/* HSR ports are setup once so need to use the assigned membership * when the port is enabled.
*/ if (!port_member && p->stp_state == BR_STATE_FORWARDING &&
(dev->hsr_ports & BIT(port)))
port_member = dev->hsr_ports;
dev->dev_ops->cfg_port_member(dev, port, port_member | cpu_port);
}
staticint ksz_sw_mdio_read(struct mii_bus *bus, int addr, int regnum)
{ struct ksz_device *dev = bus->priv;
u16 val; int ret;
ret = dev->dev_ops->r_phy(dev, addr, regnum, &val); if (ret < 0) return ret;
return val;
}
staticint ksz_sw_mdio_write(struct mii_bus *bus, int addr, int regnum,
u16 val)
{ struct ksz_device *dev = bus->priv;
/** * ksz_parent_mdio_read - Read data from a PHY register on the parent MDIO bus. * @bus: MDIO bus structure. * @addr: PHY address on the parent MDIO bus. * @regnum: Register number to read. * * This function provides a direct read operation on the parent MDIO bus for * accessing PHY registers. By bypassing SPI or I2C, it uses the parent MDIO bus * to retrieve data from the PHY registers at the specified address and register * number. * * Return: Value of the PHY register, or a negative error code on failure.
*/ staticint ksz_parent_mdio_read(struct mii_bus *bus, int addr, int regnum)
{ struct ksz_device *dev = bus->priv;
/** * ksz_parent_mdio_write - Write data to a PHY register on the parent MDIO bus. * @bus: MDIO bus structure. * @addr: PHY address on the parent MDIO bus. * @regnum: Register number to write to. * @val: Value to write to the PHY register. * * This function provides a direct write operation on the parent MDIO bus for * accessing PHY registers. Bypassing SPI or I2C, it uses the parent MDIO bus * to modify the PHY register values at the specified address. * * Return: 0 on success, or a negative error code on failure.
*/ staticint ksz_parent_mdio_write(struct mii_bus *bus, int addr, int regnum,
u16 val)
{ struct ksz_device *dev = bus->priv;
/** * ksz_phy_addr_to_port - Map a PHY address to the corresponding switch port. * @dev: Pointer to device structure. * @addr: PHY address to map to a port. * * This function finds the corresponding switch port for a given PHY address by * iterating over all user ports on the device. It checks if a port's PHY * address in `phy_addr_map` matches the specified address and if the port * contains an internal PHY. If a match is found, the index of the port is * returned. * * Return: Port index on success, or -EINVAL if no matching port is found.
*/ staticint ksz_phy_addr_to_port(struct ksz_device *dev, int addr)
{ struct dsa_switch *ds = dev->ds; struct dsa_port *dp;
/** * ksz_irq_phy_setup - Configure IRQs for PHYs in the KSZ device. * @dev: Pointer to the KSZ device structure. * * Sets up IRQs for each active PHY connected to the KSZ switch by mapping the * appropriate IRQs for each PHY and assigning them to the `user_mii_bus` in * the DSA switch structure. Each IRQ is mapped based on the port's IRQ domain. * * Return: 0 on success, or a negative error code on failure.
*/ staticint ksz_irq_phy_setup(struct ksz_device *dev)
{ struct dsa_switch *ds = dev->ds; int phy, port; int irq; int ret;
for (phy = 0; phy < PHY_MAX_ADDR; phy++) { if (BIT(phy) & ds->phys_mii_mask) {
port = ksz_phy_addr_to_port(dev, phy); if (port < 0) {
ret = port; goto out;
}
irq = irq_find_mapping(dev->ports[port].pirq.domain,
PORT_SRC_PHY_INT); if (irq < 0) {
ret = irq; goto out;
}
ds->user_mii_bus->irq[phy] = irq;
}
} return 0;
out: while (phy--) if (BIT(phy) & ds->phys_mii_mask)
irq_dispose_mapping(ds->user_mii_bus->irq[phy]);
return ret;
}
/** * ksz_irq_phy_free - Release IRQ mappings for PHYs in the KSZ device. * @dev: Pointer to the KSZ device structure. * * Releases any IRQ mappings previously assigned to active PHYs in the KSZ * switch by disposing of each mapped IRQ in the `user_mii_bus` structure.
*/ staticvoid ksz_irq_phy_free(struct ksz_device *dev)
{ struct dsa_switch *ds = dev->ds; int phy;
for (phy = 0; phy < PHY_MAX_ADDR; phy++) if (BIT(phy) & ds->phys_mii_mask)
irq_dispose_mapping(ds->user_mii_bus->irq[phy]);
}
/** * ksz_parse_dt_phy_config - Parse and validate PHY configuration from DT * @dev: pointer to the KSZ device structure * @bus: pointer to the MII bus structure * @mdio_np: pointer to the MDIO node in the device tree * * This function parses and validates PHY configurations for each user port * defined in the device tree for a KSZ switch device. It verifies that the * `phy-handle` properties are correctly set and that the internal PHYs match * expected addresses and parent nodes. Sets up the PHY mask in the MII bus if * all validations pass. Logs error messages for any mismatches or missing data. * * Return: 0 on success, or a negative error code on failure.
*/ staticint ksz_parse_dt_phy_config(struct ksz_device *dev, struct mii_bus *bus, struct device_node *mdio_np)
{ struct device_node *phy_node, *phy_parent_node; bool phys_are_valid = true; struct dsa_port *dp;
u32 phy_addr; int ret;
dsa_switch_for_each_user_port(dp, dev->ds) { if (!dev->info->internal_phy[dp->index]) continue;
phy_node = of_parse_phandle(dp->dn, "phy-handle", 0); if (!phy_node) {
dev_err(dev->dev, "failed to parse phy-handle for port %d.\n",
dp->index);
phys_are_valid = false; continue;
}
phy_parent_node = of_get_parent(phy_node); if (!phy_parent_node) {
dev_err(dev->dev, "failed to get PHY-parent node for port %d\n",
dp->index);
phys_are_valid = false;
} elseif (phy_parent_node != mdio_np) {
dev_err(dev->dev, "PHY-parent node mismatch for port %d, expected %pOF, got %pOF\n",
dp->index, mdio_np, phy_parent_node);
phys_are_valid = false;
} else {
ret = of_property_read_u32(phy_node, "reg", &phy_addr); if (ret < 0) {
dev_err(dev->dev, "failed to read PHY address for port %d. Error %d\n",
dp->index, ret);
phys_are_valid = false;
} elseif (phy_addr != dev->phy_addr_map[dp->index]) {
dev_err(dev->dev, "PHY address mismatch for port %d, expected 0x%x, got 0x%x\n",
dp->index, dev->phy_addr_map[dp->index],
phy_addr);
phys_are_valid = false;
} else {
bus->phy_mask |= BIT(phy_addr);
}
}
/** * ksz_mdio_register - Register and configure the MDIO bus for the KSZ device. * @dev: Pointer to the KSZ device structure. * * This function sets up and registers an MDIO bus for the KSZ switch device, * allowing access to its internal PHYs. If the device supports side MDIO, * the function will configure the external MDIO controller specified by the * "mdio-parent-bus" device tree property to directly manage internal PHYs. * Otherwise, SPI or I2C access is set up for PHY access. * * Return: 0 on success, or a negative error code on failure.
*/ staticint ksz_mdio_register(struct ksz_device *dev)
{ struct device_node *parent_bus_node; struct mii_bus *parent_bus = NULL; struct dsa_switch *ds = dev->ds; struct device_node *mdio_np; struct mii_bus *bus; int ret, i;
mdio_np = of_get_child_by_name(dev->dev->of_node, "mdio"); if (!mdio_np) return 0;
parent_bus_node = of_parse_phandle(mdio_np, "mdio-parent-bus", 0); if (parent_bus_node && !dev->info->phy_side_mdio_supported) {
dev_err(dev->dev, "Side MDIO bus is not supported for this HW, ignoring 'mdio-parent-bus' property.\n");
ret = -EINVAL;
goto put_mdio_node;
} elseif (parent_bus_node) {
parent_bus = of_mdio_find_bus(parent_bus_node); if (!parent_bus) {
ret = -EPROBE_DEFER;
goto put_mdio_node;
}
dev->parent_mdio_bus = parent_bus;
}
bus = devm_mdiobus_alloc(ds->dev); if (!bus) {
ret = -ENOMEM; goto put_mdio_node;
}
if (dev->dev_ops->mdio_bus_preinit) {
ret = dev->dev_ops->mdio_bus_preinit(dev, !!parent_bus); if (ret) goto put_mdio_node;
}
if (dev->dev_ops->create_phy_addr_map) {
ret = dev->dev_ops->create_phy_addr_map(dev, !!parent_bus); if (ret) goto put_mdio_node;
} else { for (i = 0; i < dev->info->port_cnt; i++)
dev->phy_addr_map[i] = i;
}
if (dev->irq > 0) {
ret = ksz_irq_phy_setup(dev); if (ret) goto put_mdio_node;
}
ret = devm_of_mdiobus_register(ds->dev, bus, mdio_np); if (ret) {
dev_err(ds->dev, "unable to register MDIO bus %s\n",
bus->id); if (dev->irq > 0)
ksz_irq_phy_free(dev);
}
if (dev->dev_ops->setup) {
ret = dev->dev_ops->setup(ds); if (ret) return ret;
}
/* Start with learning disabled on standalone user ports, and enabled * on the CPU port. In lack of other finer mechanisms, learning on the * CPU port will avoid flooding bridge local addresses on the network * in some cases.
*/
p = &dev->ports[dev->cpu_port];
p->learning = true;
if (dev->irq > 0) {
ret = ksz_girq_setup(dev); if (ret) return ret;
dsa_switch_for_each_user_port(dp, dev->ds) {
ret = ksz_pirq_setup(dev, dp->index); if (ret) goto out_girq;
if (dev->info->ptp_capable) {
ret = ksz_ptp_irq_setup(ds, dp->index); if (ret) goto out_pirq;
}
}
}
if (dev->info->ptp_capable) {
ret = ksz_ptp_clock_register(ds); if (ret) {
dev_err(dev->dev, "Failed to register PTP clock: %d\n",
ret); goto out_ptpirq;
}
}
ret = ksz_mdio_register(dev); if (ret < 0) {
dev_err(dev->dev, "failed to register the mdio"); goto out_ptp_clock_unregister;
}
ret = ksz_dcb_init(dev); if (ret) goto out_ptp_clock_unregister;
/* 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;
}
mib->cnt_ptr = 0;
}
for (i = 0; i < dev->info->port_cnt; i++) { if (dsa_is_unused_port(dev->ds, i)) continue;
p = &dev->ports[i];
mib = &p->mib;
mutex_lock(&mib->cnt_mutex);
/* Only read MIB counters when the port is told to do. * If not, read only dropped counters when link is not up.
*/ if (!p->read) { conststruct dsa_port *dp = dsa_to_port(dev->ds, i);
switch (dev->chip_id) { case KSZ88X3_CHIP_ID: /* Silicon Errata Sheet (DS80000830A): * Port 1 does not work with LinkMD Cable-Testing. * Port 1 does not respond to received PAUSE control frames.
*/ if (!port) return MICREL_KSZ8_P1_ERRATA; break;
}
/* Read all MIB counters when the link is going down. */
dev->ports[dp->index].read = true; /* timer started */ if (dev->mib_read_interval)
schedule_delayed_work(&dev->mib_read, 0);
}
staticint ksz_sset_count(struct dsa_switch *ds, int port, int sset)
{ struct ksz_device *dev = ds->priv;
/* Only read dropped counters if no link. */ if (!netif_carrier_ok(dp->user))
mib->cnt_ptr = dev->info->reg_mib_cnt;
port_r_cnt(dev, port);
memcpy(buf, mib->counters, dev->info->mib_cnt * sizeof(u64));
mutex_unlock(&mib->cnt_mutex);
}
staticint ksz_port_bridge_join(struct dsa_switch *ds, int port, struct dsa_bridge bridge, bool *tx_fwd_offload, struct netlink_ext_ack *extack)
{ /* port_stp_state_set() will be called after to put the port in * appropriate state so there is no need to do anything.
*/
return 0;
}
staticvoid ksz_port_bridge_leave(struct dsa_switch *ds, int port, struct dsa_bridge bridge)
{ /* port_stp_state_set() will be called after to put the port in * forwarding state so there is no need to do anything.
*/
}
staticint ksz9477_set_default_prio_queue_mapping(struct ksz_device *dev, int port)
{
u32 queue_map = 0; int ipm;
for (ipm = 0; ipm < dev->info->num_ipms; ipm++) { int queue;
/* Traffic Type (TT) is corresponding to the Internal Priority * Map (IPM) in the switch. Traffic Class (TC) is * corresponding to the queue in the switch.
*/
queue = ieee8021q_tt_to_tc(ipm, dev->info->num_tx_queues); if (queue < 0) return queue;
switch (state) { case BR_STATE_DISABLED:
data |= PORT_LEARN_DISABLE; break; case BR_STATE_LISTENING:
data |= (PORT_RX_ENABLE | PORT_LEARN_DISABLE); break; case BR_STATE_LEARNING:
data |= PORT_RX_ENABLE; if (!p->learning)
data |= PORT_LEARN_DISABLE; break; case BR_STATE_FORWARDING:
data |= (PORT_TX_ENABLE | PORT_RX_ENABLE); if (!p->learning)
data |= PORT_LEARN_DISABLE; break; case BR_STATE_BLOCKING:
data |= PORT_LEARN_DISABLE; break; default:
dev_err(ds->dev, "invalid STP state: %d\n", state); return;
}
switch (dev->chip_id) { case KSZ8563_CHIP_ID: case KSZ8567_CHIP_ID: case KSZ9477_CHIP_ID: case KSZ9563_CHIP_ID: case KSZ9567_CHIP_ID: case KSZ9893_CHIP_ID: case KSZ9896_CHIP_ID: case KSZ9897_CHIP_ID: case LAN9646_CHIP_ID: if (dsa_is_user_port(ds, port))
ksz9477_port_acl_free(dev, port);
}
}
switch (dev->chip_id) { case KSZ8795_CHIP_ID: case KSZ8794_CHIP_ID: case KSZ8765_CHIP_ID: return KSZ8795_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; case KSZ8463_CHIP_ID: case KSZ88X3_CHIP_ID: case KSZ8864_CHIP_ID: case KSZ8895_CHIP_ID: return KSZ8863_HUGE_PACKET_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN; case KSZ8563_CHIP_ID: case KSZ8567_CHIP_ID: case KSZ9477_CHIP_ID: case KSZ9563_CHIP_ID: case KSZ9567_CHIP_ID: case KSZ9893_CHIP_ID: case KSZ9896_CHIP_ID: case KSZ9897_CHIP_ID: case LAN9370_CHIP_ID: case LAN9371_CHIP_ID: case LAN9372_CHIP_ID: case LAN9373_CHIP_ID: case LAN9374_CHIP_ID: case LAN9646_CHIP_ID: return KSZ9477_MAX_FRAME_SIZE - VLAN_ETH_HLEN - ETH_FCS_LEN;
}
return -EOPNOTSUPP;
}
/** * ksz_support_eee - Determine Energy Efficient Ethernet (EEE) support for a * port * @ds: Pointer to the DSA switch structure * @port: Port number to check * * This function also documents devices where EEE was initially advertised but * later withdrawn due to reliability issues, as described in official errata * documents. These devices are explicitly listed to record known limitations, * even if there is no technical necessity for runtime checks. * * Returns: true if the internal PHY on the given port supports fully * operational EEE, false otherwise.
*/ staticbool ksz_support_eee(struct dsa_switch *ds, int port)
{ struct ksz_device *dev = ds->priv;
if (!dev->info->internal_phy[port]) returnfalse;
switch (dev->chip_id) { case KSZ8563_CHIP_ID: case KSZ9563_CHIP_ID: case KSZ9893_CHIP_ID: returntrue; case KSZ8567_CHIP_ID: /* KSZ8567R Errata DS80000752C Module 4 */ case KSZ8765_CHIP_ID: case KSZ8794_CHIP_ID: case KSZ8795_CHIP_ID: /* KSZ879x/KSZ877x/KSZ876x Errata DS80000687C Module 2 */ case KSZ9477_CHIP_ID: /* KSZ9477S Errata DS80000754A Module 4 */ case KSZ9567_CHIP_ID: /* KSZ9567S Errata DS80000756A Module 4 */ case KSZ9896_CHIP_ID: /* KSZ9896C Errata DS80000757A Module 3 */ case KSZ9897_CHIP_ID: case LAN9646_CHIP_ID: /* KSZ9897R Errata DS80000758C Module 4 */ /* Energy Efficient Ethernet (EEE) feature select must be * manually disabled * The EEE feature is enabled by default, but it is not fully * operational. It must be manually disabled through register * controls. If not disabled, the PHY ports can auto-negotiate * to enable EEE, and this feature can cause link drops when * linked to another device supporting EEE. * * The same item appears in the errata for all switches above.
*/ break;
}
if (duplex == DUPLEX_FULL)
val = FIELD_PREP(P_MII_DUPLEX_M, bitval[P_MII_FULL_DUPLEX]); else
val = FIELD_PREP(P_MII_DUPLEX_M, bitval[P_MII_HALF_DUPLEX]);
switch (id32) { case KSZ9477_CHIP_ID: case KSZ9896_CHIP_ID: case KSZ9897_CHIP_ID: case KSZ9567_CHIP_ID: case KSZ8567_CHIP_ID: case LAN9370_CHIP_ID: case LAN9371_CHIP_ID: case LAN9372_CHIP_ID: case LAN9373_CHIP_ID: case LAN9374_CHIP_ID:
/* LAN9646 does not have its own chip id. */ if (dev->chip_id != LAN9646_CHIP_ID)
dev->chip_id = id32; break; case KSZ9893_CHIP_ID:
ret = ksz_read8(dev, REG_CHIP_ID4,
&id4); if (ret) return ret;
switch (dev->chip_id) { case KSZ8563_CHIP_ID: case KSZ8567_CHIP_ID: case KSZ9477_CHIP_ID: case KSZ9563_CHIP_ID: case KSZ9567_CHIP_ID: case KSZ9893_CHIP_ID: case KSZ9896_CHIP_ID: case KSZ9897_CHIP_ID: case LAN9646_CHIP_ID: return ksz9477_cls_flower_add(ds, port, cls, ingress);
}
switch (dev->chip_id) { case KSZ8563_CHIP_ID: case KSZ8567_CHIP_ID: case KSZ9477_CHIP_ID: case KSZ9563_CHIP_ID: case KSZ9567_CHIP_ID: case KSZ9893_CHIP_ID: case KSZ9896_CHIP_ID: case KSZ9897_CHIP_ID: case LAN9646_CHIP_ID: return ksz9477_cls_flower_del(ds, port, cls, ingress);
}
return -EOPNOTSUPP;
}
/* Bandwidth is calculated by idle slope/transmission speed. Then the Bandwidth * is converted to Hex-decimal using the successive multiplication method. On * every step, integer part is taken and decimal part is carry forwarded.
*/ staticint cinc_cal(s32 idle_slope, s32 send_slope, u32 *bw)
{
u32 cinc = 0;
u32 txrate;
u32 rate;
u8 temp;
u8 i;
txrate = idle_slope - send_slope;
if (!txrate) return -EINVAL;
rate = idle_slope;
/* 24 bit register */ for (i = 0; i < 6; i++) {
rate = rate * 16;
staticint ksz_disable_egress_rate_limit(struct ksz_device *dev, int port)
{ int queue, ret;
/* Configuration will not take effect until the last Port Queue X * Egress Limit Control Register is written.
*/ for (queue = 0; queue < dev->info->num_tx_queues; queue++) {
ret = ksz_pwrite8(dev, port, KSZ9477_REG_PORT_OUT_RATE_0 + queue,
KSZ9477_OUT_RATE_NO_LIMIT); if (ret) return ret;
}
return 0;
}
staticint ksz_ets_band_to_queue(struct tc_ets_qopt_offload_replace_params *p, int band)
{ /* Compared to queues, bands prioritize packets differently. In strict * priority mode, the lowest priority is assigned to Queue 0 while the * highest priority is given to Band 0.
*/ return p->bands - 1 - band;
}
static u8 ksz8463_tc_ctrl(int port, int queue)
{
u8 reg;
/** * ksz88x3_tc_ets_add - Configure ETS (Enhanced Transmission Selection) * for a port on KSZ88x3 switch * @dev: Pointer to the KSZ switch device structure * @port: Port number to configure * @p: Pointer to offload replace parameters describing ETS bands and mapping * * The KSZ88x3 supports two scheduling modes: Strict Priority and * Weighted Fair Queuing (WFQ). Both modes have fixed behavior: * - No configurable queue-to-priority mapping * - No weight adjustment in WFQ mode * * This function configures the switch to use strict priority mode by * clearing the WFQ enable bit for all queues associated with ETS bands. * If strict priority is not explicitly requested, the switch will default * to WFQ mode. * * Return: 0 on success, or a negative error code on failure
*/ staticint ksz88x3_tc_ets_add(struct ksz_device *dev, int port, struct tc_ets_qopt_offload_replace_params *p)
{ int ret, band;
/* Only strict priority mode is supported for now. * WFQ is implicitly enabled when strict mode is disabled.
*/ for (band = 0; band < p->bands; band++) { int queue = ksz_ets_band_to_queue(p, band);
u8 reg;
/* Calculate TXQ Split Control register address for this * port/queue
*/
reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); if (ksz_is_ksz8463(dev))
reg = ksz8463_tc_ctrl(port, queue);
/* Clear WFQ enable bit to select strict priority scheduling */
ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE, 0); if (ret) return ret;
}
return 0;
}
/** * ksz88x3_tc_ets_del - Reset ETS (Enhanced Transmission Selection) config * for a port on KSZ88x3 switch * @dev: Pointer to the KSZ switch device structure * @port: Port number to reset * * The KSZ88x3 supports only fixed scheduling modes: Strict Priority or * Weighted Fair Queuing (WFQ), with no reconfiguration of weights or * queue mapping. This function resets the port’s scheduling mode to * the default, which is WFQ, by enabling the WFQ bit for all queues. * * Return: 0 on success, or a negative error code on failure
*/ staticint ksz88x3_tc_ets_del(struct ksz_device *dev, int port)
{ int ret, queue;
/* Iterate over all transmit queues for this port */ for (queue = 0; queue < dev->info->num_tx_queues; queue++) {
u8 reg;
/* Calculate TXQ Split Control register address for this * port/queue
*/
reg = KSZ8873_TXQ_SPLIT_CTRL_REG(port, queue); if (ksz_is_ksz8463(dev))
reg = ksz8463_tc_ctrl(port, queue);
/* Set WFQ enable bit to revert back to default scheduling * mode
*/
ret = ksz_rmw8(dev, reg, KSZ8873_TXQ_WFQ_ENABLE,
KSZ8873_TXQ_WFQ_ENABLE); if (ret) return ret;
}
return 0;
}
staticint ksz_queue_set_strict(struct ksz_device *dev, int port, int queue)
{ int ret;
ret = ksz_pwrite32(dev, port, REG_PORT_MTI_QUEUE_INDEX__4, queue); if (ret) return ret;
staticint ksz_tc_ets_add(struct ksz_device *dev, int port, struct tc_ets_qopt_offload_replace_params *p)
{ int ret, band, tc_prio;
u32 queue_map = 0;
/* In order to ensure proper prioritization, it is necessary to set the * rate limit for the related queue to zero. Otherwise strict priority * or WRR mode will not work. This is a hardware limitation.
*/
ret = ksz_disable_egress_rate_limit(dev, port); if (ret) return ret;
/* Configure queue scheduling mode for all bands. Currently only strict * prio mode is supported.
*/ for (band = 0; band < p->bands; band++) { int queue = ksz_ets_band_to_queue(p, band);
ret = ksz_queue_set_strict(dev, port, queue); if (ret) return ret;
}
/* Configure the mapping between traffic classes and queues. Note: * priomap variable support 16 traffic classes, but the chip can handle * only 8 classes.
*/ for (tc_prio = 0; tc_prio < ARRAY_SIZE(p->priomap); tc_prio++) { int queue;
staticint ksz_tc_ets_del(struct ksz_device *dev, int port)
{ int ret, queue;
/* To restore the default chip configuration, set all queues to use the * WRR scheduler with a weight of 1.
*/ for (queue = 0; queue < dev->info->num_tx_queues; queue++) {
ret = ksz_queue_set_wrr(dev, port, queue,
KSZ9477_DEFAULT_WRR_WEIGHT);
if (ret) return ret;
}
/* Revert the queue mapping for TC-priority to its default setting on * the chip.
*/ return ksz9477_set_default_prio_queue_mapping(dev, port);
}
staticint ksz_tc_ets_validate(struct ksz_device *dev, int port, struct tc_ets_qopt_offload_replace_params *p)
{ int band;
/* Since it is not feasible to share one port among multiple qdisc, * the user must configure all available queues appropriately.
*/ if (p->bands != dev->info->num_tx_queues) {
dev_err(dev->dev, "Not supported amount of bands. It should be %d\n",
dev->info->num_tx_queues); return -EOPNOTSUPP;
}
for (band = 0; band < p->bands; ++band) { /* The KSZ switches utilize a weighted round robin configuration * where a certain number of packets can be transmitted from a * queue before the next queue is serviced. For more information * on this, refer to section 5.2.8.4 of the KSZ8565R * documentation on the Port Transmit Queue Control 1 Register. * However, the current ETS Qdisc implementation (as of February * 2023) assigns a weight to each queue based on the number of * bytes or extrapolated bandwidth in percentages. Since this * differs from the KSZ switches' method and we don't want to * fake support by converting bytes to packets, it is better to * return an error instead.
*/ if (p->quanta[band]) {
dev_err(dev->dev, "Quanta/weights configuration is not supported.\n"); return -EOPNOTSUPP;
}
}
return 0;
}
staticint ksz_tc_setup_qdisc_ets(struct dsa_switch *ds, int port, struct tc_ets_qopt_offload *qopt)
{ struct ksz_device *dev = ds->priv; int ret;
if (is_ksz8(dev) && !(ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev))) return -EOPNOTSUPP;
if (qopt->parent != TC_H_ROOT) {
dev_err(dev->dev, "Parent should be \"root\"\n"); return -EOPNOTSUPP;
}
switch (qopt->command) { case TC_ETS_REPLACE:
ret = ksz_tc_ets_validate(dev, port, &qopt->replace_params); if (ret) return ret;
if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) return ksz88x3_tc_ets_add(dev, port,
&qopt->replace_params); else return ksz_tc_ets_add(dev, port, &qopt->replace_params); case TC_ETS_DESTROY: if (ksz_is_ksz88x3(dev) || ksz_is_ksz8463(dev)) return ksz88x3_tc_ets_del(dev, port); else return ksz_tc_ets_del(dev, port); case TC_ETS_STATS: case TC_ETS_GRAFT: return -EOPNOTSUPP;
}
/** * ksz_handle_wake_reason - Handle wake reason on a specified port. * @dev: The device structure. * @port: The port number. * * This function reads the PME (Power Management Event) status register of a * specified port to determine the wake reason. If there is no wake event, it * returns early. Otherwise, it logs the wake reason which could be due to a * "Magic Packet", "Link Up", or "Energy Detect" event. The PME status register * is then cleared to acknowledge the handling of the wake event. * * Return: 0 on success, or an error code on failure.
*/ int ksz_handle_wake_reason(struct ksz_device *dev, int port)
{ conststruct ksz_dev_ops *ops = dev->dev_ops; const u16 *regs = dev->info->regs;
u8 pme_status; int ret;
ret = ops->pme_pread8(dev, port, regs[REG_PORT_PME_STATUS],
&pme_status); if (ret) return ret;
/** * ksz_get_wol - Get Wake-on-LAN settings for a specified port. * @ds: The dsa_switch structure. * @port: The port number. * @wol: Pointer to ethtool Wake-on-LAN settings structure. * * This function checks the device PME wakeup_source flag and chip_id. * If enabled and supported, it sets the supported and active WoL * flags.
*/ staticvoid ksz_get_wol(struct dsa_switch *ds, int port, struct ethtool_wolinfo *wol)
{ struct ksz_device *dev = ds->priv; const u16 *regs = dev->info->regs;
u8 pme_ctrl; int ret;
if (!is_ksz9477(dev) && !ksz_is_ksz87xx(dev)) return;
if (!dev->wakeup_source) return;
wol->supported = WAKE_PHY;
/* Check if the current MAC address on this port can be set * as global for WAKE_MAGIC support. The result may vary * dynamically based on other ports configurations.
*/ if (ksz_is_port_mac_global_usable(dev->ds, port))
wol->supported |= WAKE_MAGIC;
ret = dev->dev_ops->pme_pread8(dev, port, regs[REG_PORT_PME_CTRL],
&pme_ctrl); if (ret) return;
if (pme_ctrl & PME_WOL_MAGICPKT)
wol->wolopts |= WAKE_MAGIC; if (pme_ctrl & (PME_WOL_LINKUP | PME_WOL_ENERGY))
wol->wolopts |= WAKE_PHY;
}
/** * ksz_set_wol - Set Wake-on-LAN settings for a specified port. * @ds: The dsa_switch structure. * @port: The port number. * @wol: Pointer to ethtool Wake-on-LAN settings structure. * * This function configures Wake-on-LAN (WoL) settings for a specified * port. It validates the provided WoL options, checks if PME is * enabled and supported, clears any previous wake reasons, and sets * the Magic Packet flag in the port's PME control register if * specified. * * Return: 0 on success, or other error codes on failure.
*/ staticint ksz_set_wol(struct dsa_switch *ds, int port, struct ethtool_wolinfo *wol)
{
u8 pme_ctrl = 0, pme_ctrl_old = 0; struct ksz_device *dev = ds->priv; const u16 *regs = dev->info->regs; bool magic_switched_off; bool magic_switched_on; int ret;
if (wol->wolopts & ~(WAKE_PHY | WAKE_MAGIC)) return -EINVAL;
if (!is_ksz9477(dev) && !ksz_is_ksz87xx(dev)) return -EOPNOTSUPP;
if (!dev->wakeup_source) return -EOPNOTSUPP;
ret = ksz_handle_wake_reason(dev, port); if (ret) return ret;
if (wol->wolopts & WAKE_MAGIC)
pme_ctrl |= PME_WOL_MAGICPKT; if (wol->wolopts & WAKE_PHY)
pme_ctrl |= PME_WOL_LINKUP | PME_WOL_ENERGY;
ret = dev->dev_ops->pme_pread8(dev, port, regs[REG_PORT_PME_CTRL],
&pme_ctrl_old); if (ret) return ret;
/* To keep reference count of MAC address, we should do this * operation only on change of WOL settings.
*/ if (magic_switched_on) {
ret = ksz_switch_macaddr_get(dev->ds, port, NULL); if (ret) return ret;
} elseif (magic_switched_off) {
ksz_switch_macaddr_put(dev->ds);
}
ret = dev->dev_ops->pme_pwrite8(dev, port, regs[REG_PORT_PME_CTRL],
pme_ctrl); if (ret) { if (magic_switched_on)
ksz_switch_macaddr_put(dev->ds); return ret;
}
return 0;
}
/** * ksz_wol_pre_shutdown - Prepares the switch device for shutdown while * considering Wake-on-LAN (WoL) settings. * @dev: The switch device structure. * @wol_enabled: Pointer to a boolean which will be set to true if WoL is * enabled on any port. * * This function prepares the switch device for a safe shutdown while taking * into account the Wake-on-LAN (WoL) settings on the user ports. It updates * the wol_enabled flag accordingly to reflect whether WoL is active on any * port.
*/ staticvoid ksz_wol_pre_shutdown(struct ksz_device *dev, bool *wol_enabled)
{ conststruct ksz_dev_ops *ops = dev->dev_ops; const u16 *regs = dev->info->regs;
u8 pme_pin_en = PME_ENABLE; struct dsa_port *dp; int ret;
*wol_enabled = false;
if (!is_ksz9477(dev) && !ksz_is_ksz87xx(dev)) return;
ret = ops->pme_pread8(dev, dp->index,
regs[REG_PORT_PME_CTRL], &pme_ctrl); if (!ret && pme_ctrl)
*wol_enabled = true;
/* make sure there are no pending wake events which would * prevent the device from going to sleep/shutdown.
*/
ksz_handle_wake_reason(dev, dp->index);
}
/* Now we are save to enable PME pin. */ if (*wol_enabled) { if (dev->pme_active_high)
pme_pin_en |= PME_POLARITY;
ops->pme_write8(dev, regs[REG_SW_PME_CTRL], pme_pin_en); if (ksz_is_ksz87xx(dev))
ksz_write8(dev, KSZ87XX_REG_INT_EN, KSZ87XX_INT_PME_MASK);
}
}
if (dp->hsr_dev) {
dev_err(ds->dev, "Cannot change MAC address on port %d with active HSR offload\n",
port); return -EBUSY;
}
/* Need to initialize variable as the code to fill in settings may * not be executed.
*/
wol.wolopts = 0;
ksz_get_wol(ds, dp->index, &wol); if (wol.wolopts & WAKE_MAGIC) {
dev_err(ds->dev, "Cannot change MAC address on port %d with active Wake on Magic Packet\n",
port); return -EBUSY;
}
return 0;
}
/** * ksz_is_port_mac_global_usable - Check if the MAC address on a given port * can be used as a global address. * @ds: Pointer to the DSA switch structure. * @port: The port number on which the MAC address is to be checked. * * This function examines the MAC address set on the specified port and * determines if it can be used as a global address for the switch. * * Return: true if the port's MAC address can be used as a global address, false * otherwise.
*/ bool ksz_is_port_mac_global_usable(struct dsa_switch *ds, int port)
{ struct net_device *user = dsa_to_port(ds, port)->user; constunsignedchar *addr = user->dev_addr; struct ksz_switch_macaddr *switch_macaddr; struct ksz_device *dev = ds->priv;
ASSERT_RTNL();
switch_macaddr = dev->switch_macaddr; if (switch_macaddr && !ether_addr_equal(switch_macaddr->addr, addr)) returnfalse;
returntrue;
}
/** * ksz_switch_macaddr_get - Program the switch's MAC address register. * @ds: DSA switch instance. * @port: Port number. * @extack: Netlink extended acknowledgment. * * This function programs the switch's MAC address register with the MAC address * of the requesting user port. This single address is used by the switch for * multiple features like HSR self-address filtering and WoL. Other user ports * can share ownership of this address as long as their MAC address is the same. * The MAC addresses of user ports must not change while they have ownership of * the switch MAC address. * * Return: 0 on success, or other error codes on failure.
*/ int ksz_switch_macaddr_get(struct dsa_switch *ds, int port, struct netlink_ext_ack *extack)
{ struct net_device *user = dsa_to_port(ds, port)->user; constunsignedchar *addr = user->dev_addr; struct ksz_switch_macaddr *switch_macaddr; struct ksz_device *dev = ds->priv; const u16 *regs = dev->info->regs; int i, ret;
/* Make sure concurrent MAC address changes are blocked */
ASSERT_RTNL();
switch_macaddr = dev->switch_macaddr; if (switch_macaddr) { if (!ether_addr_equal(switch_macaddr->addr, addr)) {
NL_SET_ERR_MSG_FMT_MOD(extack, "Switch already configured for MAC address %pM",
switch_macaddr->addr); return -EBUSY;
}
/* Program the switch MAC address to hardware */ for (i = 0; i < ETH_ALEN; i++) { if (ksz_is_ksz8463(dev)) {
u16 addr16 = ((u16)addr[i] << 8) | addr[i + 1];
ret = ksz_write16(dev, regs[REG_SW_MAC_ADDR] + i,
addr16);
i++;
} else {
ret = ksz_write8(dev, regs[REG_SW_MAC_ADDR] + i,
addr[i]);
} if (ret) goto macaddr_drop;
}
ret = hsr_get_version(hsr, &ver); if (ret) return ret;
if (dev->chip_id != KSZ9477_CHIP_ID) {
NL_SET_ERR_MSG_MOD(extack, "Chip does not support HSR offload"); return -EOPNOTSUPP;
}
/* KSZ9477 can support HW offloading of only 1 HSR device */ if (dev->hsr_dev && hsr != dev->hsr_dev) {
NL_SET_ERR_MSG_MOD(extack, "Offload supported for a single HSR"); return -EOPNOTSUPP;
}
/* KSZ9477 only supports HSR v0 and v1 */ if (!(ver == HSR_V0 || ver == HSR_V1)) {
NL_SET_ERR_MSG_MOD(extack, "Only HSR v0 and v1 supported"); return -EOPNOTSUPP;
}
/* KSZ9477 can only perform HSR offloading for up to two ports */ if (hweight8(dev->hsr_ports) >= 2) {
NL_SET_ERR_MSG_MOD(extack, "Cannot offload more than two ports - using software HSR"); return -EOPNOTSUPP;
}
/* Self MAC address filtering, to avoid frames traversing * the HSR ring more than once.
*/
ret = ksz_switch_macaddr_get(ds, port, extack); if (ret) return ret;
swdev = devm_kzalloc(base, sizeof(*swdev), GFP_KERNEL); if (!swdev) return NULL;
ds->priv = swdev;
swdev->dev = base;
swdev->ds = ds;
swdev->priv = priv;
return swdev;
}
EXPORT_SYMBOL(ksz_switch_alloc);
/** * ksz_switch_shutdown - Shutdown routine for the switch device. * @dev: The switch device structure. * * This function is responsible for initiating a shutdown sequence for the * switch device. It invokes the reset operation defined in the device * operations, if available, to reset the switch. Subsequently, it calls the * DSA framework's shutdown function to ensure a proper shutdown of the DSA * switch.
*/ void ksz_switch_shutdown(struct ksz_device *dev)
{ bool wol_enabled = false;
ksz_wol_pre_shutdown(dev, &wol_enabled);
if (dev->dev_ops->reset && !wol_enabled)
dev->dev_ops->reset(dev);
/** * ksz_drive_strength_to_reg() - Convert drive strength value to corresponding * register value. * @array: The array of drive strength values to search. * @array_size: The size of the array. * @microamp: The drive strength value in microamp to be converted. * * This function searches the array of drive strength values for the given * microamp value and returns the corresponding register value for that drive. * * Returns: If found, the corresponding register value for that drive strength * is returned. Otherwise, -EINVAL is returned indicating an invalid value.
*/ staticint ksz_drive_strength_to_reg(conststruct ksz_drive_strength *array,
size_t array_size, int microamp)
{ int i;
for (i = 0; i < array_size; i++) { if (array[i].microamp == microamp) return array[i].reg_val;
}
return -EINVAL;
}
/** * ksz_drive_strength_error() - Report invalid drive strength value * @dev: ksz device * @array: The array of drive strength values to search. * @array_size: The size of the array. * @microamp: Invalid drive strength value in microamp * * This function logs an error message when an unsupported drive strength value * is detected. It lists out all the supported drive strength values for * reference in the error message.
*/ staticvoid ksz_drive_strength_error(struct ksz_device *dev, conststruct ksz_drive_strength *array,
size_t array_size, int microamp)
{ char supported_values[100];
size_t remaining_size; int added_len; char *ptr; int i;
/** * ksz9477_drive_strength_write() - Set the drive strength for specific KSZ9477 * chip variants. * @dev: ksz device * @props: Array of drive strength properties to be applied * @num_props: Number of properties in the array * * This function configures the drive strength for various KSZ9477 chip variants * based on the provided properties. It handles chip-specific nuances and * ensures only valid drive strengths are written to the respective chip. * * Return: 0 on successful configuration, a negative error code on failure.
*/ staticint ksz9477_drive_strength_write(struct ksz_device *dev, struct ksz_driver_strength_prop *props, int num_props)
{
size_t array_size = ARRAY_SIZE(ksz9477_drive_strengths); int i, ret, reg;
u8 mask = 0;
u8 val = 0;
if (props[KSZ_DRIVER_STRENGTH_IO].value != -1)
dev_warn(dev->dev, "%s is not supported by this chip variant\n",
props[KSZ_DRIVER_STRENGTH_IO].name);
for (i = 0; i < num_props; i++) { if (props[i].value == -1) continue;
ret = ksz_drive_strength_to_reg(ksz9477_drive_strengths,
array_size, props[i].value); if (ret < 0) {
ksz_drive_strength_error(dev, ksz9477_drive_strengths,
array_size, props[i].value); return ret;
}
mask |= SW_DRIVE_STRENGTH_M << props[i].offset;
val |= ret << props[i].offset;
}
return ksz_rmw8(dev, reg, mask, val);
}
/** * ksz88x3_drive_strength_write() - Set the drive strength configuration for * KSZ8863 compatible chip variants. * @dev: ksz device * @props: Array of drive strength properties to be set * @num_props: Number of properties in the array * * This function applies the specified drive strength settings to KSZ88X3 chip * variants (KSZ8873, KSZ8863). * It ensures the configurations align with what the chip variant supports and * warns or errors out on unsupported settings. * * Return: 0 on success, error code otherwise
*/ staticint ksz88x3_drive_strength_write(struct ksz_device *dev, struct ksz_driver_strength_prop *props, int num_props)
{
size_t array_size = ARRAY_SIZE(ksz88x3_drive_strengths); int microamp; int i, ret;
for (i = 0; i < num_props; i++) { if (props[i].value == -1 || i == KSZ_DRIVER_STRENGTH_IO) continue;
dev_warn(dev->dev, "%s is not supported by this chip variant\n",
props[i].name);
}
microamp = props[KSZ_DRIVER_STRENGTH_IO].value;
ret = ksz_drive_strength_to_reg(ksz88x3_drive_strengths, array_size,
microamp); if (ret < 0) {
ksz_drive_strength_error(dev, ksz88x3_drive_strengths,
array_size, microamp); return ret;
}
/** * ksz_parse_drive_strength() - Extract and apply drive strength configurations * from device tree properties. * @dev: ksz device * * This function reads the specified drive strength properties from the * device tree, validates against the supported chip variants, and sets * them accordingly. An error should be critical here, as the drive strength * settings are crucial for EMI compliance. * * Return: 0 on success, error code otherwise
*/ staticint ksz_parse_drive_strength(struct ksz_device *dev)
{ struct ksz_driver_strength_prop of_props[] = {
[KSZ_DRIVER_STRENGTH_HI] = {
.name = "microchip,hi-drive-strength-microamp",
.offset = SW_HI_SPEED_DRIVE_STRENGTH_S,
.value = -1,
},
[KSZ_DRIVER_STRENGTH_LO] = {
.name = "microchip,lo-drive-strength-microamp",
.offset = SW_LO_SPEED_DRIVE_STRENGTH_S,
.value = -1,
},
[KSZ_DRIVER_STRENGTH_IO] = {
.name = "microchip,io-drive-strength-microamp",
.offset = 0, /* don't care */
.value = -1,
},
}; struct device_node *np = dev->dev->of_node; bool have_any_prop = false; int i, ret;
for (i = 0; i < ARRAY_SIZE(of_props); i++) {
ret = of_property_read_u32(np, of_props[i].name,
&of_props[i].value); if (ret && ret != -EINVAL)
dev_warn(dev->dev, "Failed to read %s\n",
of_props[i].name); if (ret) continue;
have_any_prop = true;
}
if (!have_any_prop) return 0;
switch (dev->chip_id) { case KSZ88X3_CHIP_ID: return ksz88x3_drive_strength_write(dev, of_props,
ARRAY_SIZE(of_props)); case KSZ8795_CHIP_ID: case KSZ8794_CHIP_ID: case KSZ8765_CHIP_ID: case KSZ8563_CHIP_ID: case KSZ8567_CHIP_ID: case KSZ9477_CHIP_ID: case KSZ9563_CHIP_ID: case KSZ9567_CHIP_ID: case KSZ9893_CHIP_ID: case KSZ9896_CHIP_ID: case KSZ9897_CHIP_ID: case LAN9646_CHIP_ID: return ksz9477_drive_strength_write(dev, of_props,
ARRAY_SIZE(of_props)); default: for (i = 0; i < ARRAY_SIZE(of_props); i++) { if (of_props[i].value == -1) continue;
dev_warn(dev->dev, "%s is not supported by this chip variant\n",
of_props[i].name);
}
}
int ksz_switch_register(struct ksz_device *dev)
{ conststruct ksz_chip_data *info; struct device_node *ports;
phy_interface_t interface; unsignedint port_num; int ret; int i;
dev->reset_gpio = devm_gpiod_get_optional(dev->dev, "reset",
GPIOD_OUT_LOW); if (IS_ERR(dev->reset_gpio)) return PTR_ERR(dev->reset_gpio);
if (dev->reset_gpio) { if (of_device_is_compatible(dev->dev->of_node, "microchip,ksz8463")) {
ret = ksz8463_configure_straps_spi(dev); 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.