Commit 23462e03 authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'side-mdio-support-for-lan937x-switches'

Oleksij Rempel says:

====================
Side MDIO Support for LAN937x Switches

This patch set introduces support for an internal MDIO bus in LAN937x
switches, enabling the use of a side MDIO channel for PHY management
while keeping SPI as the main interface for switch configuration.

other changelogs are added to separate patches.
====================

Link: https://patch.msgid.link/20241106075942.1636998-1-o.rempel@pengutronix.de


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 2cd78740 34125ac8
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
@@ -81,6 +81,26 @@ properties:
  interrupts:
    maxItems: 1

  mdio:
    $ref: /schemas/net/mdio.yaml#
    unevaluatedProperties: false
    properties:
      mdio-parent-bus:
        $ref: /schemas/types.yaml#/definitions/phandle
        description:
          Phandle pointing to the MDIO bus controller connected to the
          secondary MDIO interface. This property should be used when
          the internal MDIO bus is accessed via a secondary MDIO
          interface rather than the primary management interface.

    patternProperties:
      "^ethernet-phy@[0-9a-f]$":
        type: object
        $ref: /schemas/net/ethernet-phy.yaml#
        unevaluatedProperties: false
        description:
          Integrated PHY node

required:
  - compatible
  - reg
+243 −16
Original line number Diff line number Diff line
@@ -411,6 +411,8 @@ static const struct ksz_dev_ops lan937x_dev_ops = {
	.flush_dyn_mac_table = ksz9477_flush_dyn_mac_table,
	.port_setup = lan937x_port_setup,
	.set_ageing_time = lan937x_set_ageing_time,
	.mdio_bus_preinit = lan937x_mdio_bus_preinit,
	.create_phy_addr_map = lan937x_create_phy_addr_map,
	.r_phy = lan937x_r_phy,
	.w_phy = lan937x_w_phy,
	.r_mib_cnt = ksz9477_r_mib_cnt,
@@ -1762,6 +1764,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
		.num_tx_queues = 8,
		.num_ipms = 8,
		.tc_cbs_supported = true,
		.phy_side_mdio_supported = true,
		.ops = &lan937x_dev_ops,
		.phylink_mac_ops = &lan937x_phylink_mac_ops,
		.mib_names = ksz9477_mib_names,
@@ -1790,6 +1793,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
		.num_tx_queues = 8,
		.num_ipms = 8,
		.tc_cbs_supported = true,
		.phy_side_mdio_supported = true,
		.ops = &lan937x_dev_ops,
		.phylink_mac_ops = &lan937x_phylink_mac_ops,
		.mib_names = ksz9477_mib_names,
@@ -1818,6 +1822,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
		.num_tx_queues = 8,
		.num_ipms = 8,
		.tc_cbs_supported = true,
		.phy_side_mdio_supported = true,
		.ops = &lan937x_dev_ops,
		.phylink_mac_ops = &lan937x_phylink_mac_ops,
		.mib_names = ksz9477_mib_names,
@@ -1850,6 +1855,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
		.num_tx_queues = 8,
		.num_ipms = 8,
		.tc_cbs_supported = true,
		.phy_side_mdio_supported = true,
		.ops = &lan937x_dev_ops,
		.phylink_mac_ops = &lan937x_phylink_mac_ops,
		.mib_names = ksz9477_mib_names,
@@ -1882,6 +1888,7 @@ const struct ksz_chip_data ksz_switch_chips[] = {
		.num_tx_queues = 8,
		.num_ipms = 8,
		.tc_cbs_supported = true,
		.phy_side_mdio_supported = true,
		.ops = &lan937x_dev_ops,
		.phylink_mac_ops = &lan937x_phylink_mac_ops,
		.mib_names = ksz9477_mib_names,
@@ -2236,16 +2243,100 @@ static int ksz_sw_mdio_write(struct mii_bus *bus, int addr, int regnum,
	return dev->dev_ops->w_phy(dev, addr, regnum, val);
}

/**
 * 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.
 */
static int ksz_parent_mdio_read(struct mii_bus *bus, int addr, int regnum)
{
	struct ksz_device *dev = bus->priv;

	return mdiobus_read_nested(dev->parent_mdio_bus, addr, regnum);
}

/**
 * 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.
 */
static int ksz_parent_mdio_write(struct mii_bus *bus, int addr, int regnum,
				 u16 val)
{
	struct ksz_device *dev = bus->priv;

	return mdiobus_write_nested(dev->parent_mdio_bus, addr, regnum, val);
}

/**
 * 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.
 */
static int ksz_phy_addr_to_port(struct ksz_device *dev, int addr)
{
	struct dsa_switch *ds = dev->ds;
	struct dsa_port *dp;

	dsa_switch_for_each_user_port(dp, ds) {
		if (dev->info->internal_phy[dp->index] &&
		    dev->phy_addr_map[dp->index] == addr)
			return dp->index;
	}

	return -EINVAL;
}

/**
 * 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.
 */
static int ksz_irq_phy_setup(struct ksz_device *dev)
{
	struct dsa_switch *ds = dev->ds;
	int phy;
	int phy, port;
	int irq;
	int ret;

	for (phy = 0; phy < KSZ_MAX_NUM_PORTS; phy++) {
	for (phy = 0; phy < PHY_MAX_ADDR; phy++) {
		if (BIT(phy) & ds->phys_mii_mask) {
			irq = irq_find_mapping(dev->ports[phy].pirq.domain,
			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;
@@ -2263,49 +2354,183 @@ static int ksz_irq_phy_setup(struct ksz_device *dev)
	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.
 */
static void ksz_irq_phy_free(struct ksz_device *dev)
{
	struct dsa_switch *ds = dev->ds;
	int phy;

	for (phy = 0; phy < KSZ_MAX_NUM_PORTS; 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.
 */
static int 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;
		} else if (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;
			} else if (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);
			}
		}

		of_node_put(phy_node);
		of_node_put(phy_parent_node);
	}

	if (!phys_are_valid)
		return -EINVAL;

	return 0;
}

/**
 * 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.
 */
static int 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;
	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;
	} else if (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) {
		of_node_put(mdio_np);
		return -ENOMEM;
		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;
	}

	bus->priv = dev;
	if (parent_bus) {
		bus->read = ksz_parent_mdio_read;
		bus->write = ksz_parent_mdio_write;
		bus->name = "KSZ side MDIO";
		snprintf(bus->id, MII_BUS_ID_SIZE, "ksz-side-mdio-%d",
			 ds->index);
	} else {
		bus->read = ksz_sw_mdio_read;
		bus->write = ksz_sw_mdio_write;
		bus->name = "ksz user smi";
		snprintf(bus->id, MII_BUS_ID_SIZE, "SMI-%d", ds->index);
	}

	ret = ksz_parse_dt_phy_config(dev, bus, mdio_np);
	if (ret)
		goto put_mdio_node;

	ds->phys_mii_mask = bus->phy_mask;
	bus->parent = ds->dev;
	bus->phy_mask = ~ds->phys_mii_mask;

	ds->user_mii_bus = bus;

	if (dev->irq > 0) {
		ret = ksz_irq_phy_setup(dev);
		if (ret) {
			of_node_put(mdio_np);
			return ret;
		}
		if (ret)
			goto put_mdio_node;
	}

	ret = devm_of_mdiobus_register(ds->dev, bus, mdio_np);
@@ -2316,7 +2541,9 @@ static int ksz_mdio_register(struct ksz_device *dev)
			ksz_irq_phy_free(dev);
	}

put_mdio_node:
	of_node_put(mdio_np);
	of_node_put(parent_bus_node);

	return ret;
}
+59 −0
Original line number Diff line number Diff line
@@ -65,6 +65,12 @@ struct ksz_chip_data {
	u8 num_tx_queues;
	u8 num_ipms; /* number of Internal Priority Maps */
	bool tc_cbs_supported;

	/**
	 * @phy_side_mdio_supported: Indicates if the chip supports an additional
	 * side MDIO channel for accessing integrated PHYs.
	 */
	bool phy_side_mdio_supported;
	const struct ksz_dev_ops *ops;
	const struct phylink_mac_ops *phylink_mac_ops;
	bool phy_errata_9477;
@@ -191,6 +197,22 @@ struct ksz_device {
	struct ksz_switch_macaddr *switch_macaddr;
	struct net_device *hsr_dev;     /* HSR */
	u8 hsr_ports;

	/**
	 * @phy_addr_map: Array mapping switch ports to their corresponding PHY
	 * addresses.
	 */
	u8 phy_addr_map[KSZ_MAX_NUM_PORTS];

	/**
	 * @parent_mdio_bus: Pointer to the external MDIO bus controller.
	 *
	 * This points to an external MDIO bus controller that is used to access
	 * the  PHYs integrated within the switch. Unlike an integrated MDIO
	 * bus, this external controller provides a direct path for managing
	 * the switch’s internal PHYs, bypassing the main SPI interface.
	 */
	struct mii_bus *parent_mdio_bus;
};

/* List of supported models */
@@ -326,6 +348,43 @@ struct ksz_dev_ops {
	void (*port_cleanup)(struct ksz_device *dev, int port);
	void (*port_setup)(struct ksz_device *dev, int port, bool cpu_port);
	int (*set_ageing_time)(struct ksz_device *dev, unsigned int msecs);

	/**
	 * @mdio_bus_preinit: Function pointer to pre-initialize the MDIO bus
	 *                    for accessing PHYs.
	 * @dev: Pointer to device structure.
	 * @side_mdio: Boolean indicating if the PHYs are accessed over a side
	 *             MDIO bus.
	 *
	 * This function pointer is used to configure the MDIO bus for PHY
	 * access before initiating regular PHY operations. It enables either
	 * SPI/I2C or side MDIO access modes by unlocking necessary registers
	 * and setting up access permissions for the selected mode.
	 *
	 * Return:
	 *  - 0 on success.
	 *  - Negative error code on failure.
	 */
	int (*mdio_bus_preinit)(struct ksz_device *dev, bool side_mdio);

	/**
	 * @create_phy_addr_map: Function pointer to create a port-to-PHY
	 *                       address map.
	 * @dev: Pointer to device structure.
	 * @side_mdio: Boolean indicating if the PHYs are accessed over a side
	 *             MDIO bus.
	 *
	 * This function pointer is responsible for mapping switch ports to PHY
	 * addresses according to the configured access mode (SPI or side MDIO)
	 * and the device’s strap configuration. The mapping setup may vary
	 * depending on the chip variant and configuration. Ensures the correct
	 * address mapping for PHY communication.
	 *
	 * Return:
	 *  - 0 on success.
	 *  - Negative error code on failure (e.g., invalid configuration).
	 */
	int (*create_phy_addr_map)(struct ksz_device *dev, bool side_mdio);
	int (*r_phy)(struct ksz_device *dev, u16 phy, u16 reg, u16 *val);
	int (*w_phy)(struct ksz_device *dev, u16 phy, u16 reg, u16 val);
	void (*r_mib_cnt)(struct ksz_device *dev, int port, u16 addr,
+2 −0
Original line number Diff line number Diff line
@@ -13,6 +13,8 @@ void lan937x_port_setup(struct ksz_device *dev, int port, bool cpu_port);
void lan937x_config_cpu_port(struct dsa_switch *ds);
int lan937x_switch_init(struct ksz_device *dev);
void lan937x_switch_exit(struct ksz_device *dev);
int lan937x_mdio_bus_preinit(struct ksz_device *dev, bool side_mdio);
int lan937x_create_phy_addr_map(struct ksz_device *dev, bool side_mdio);
int lan937x_r_phy(struct ksz_device *dev, u16 addr, u16 reg, u16 *data);
int lan937x_w_phy(struct ksz_device *dev, u16 addr, u16 reg, u16 val);
int lan937x_change_mtu(struct ksz_device *dev, int port, int new_mtu);
+210 −16
Original line number Diff line number Diff line
@@ -18,6 +18,87 @@
#include "ksz9477.h"
#include "lan937x.h"

/* marker for ports without built-in PHY */
#define LAN937X_NO_PHY U8_MAX

/*
 * lan9370_phy_addr - Mapping of LAN9370 switch ports to PHY addresses.
 *
 * Each entry corresponds to a specific port on the LAN9370 switch,
 * where ports 1-4 are connected to integrated 100BASE-T1 PHYs, and
 * Port 5 is connected to an RGMII interface without a PHY. The values
 * are based on the documentation (DS00003108E, section 3.3).
 */
static const u8 lan9370_phy_addr[] = {
	[0] = 2, /* Port 1, T1 AFE0 */
	[1] = 3, /* Port 2, T1 AFE1 */
	[2] = 5, /* Port 3, T1 AFE3 */
	[3] = 6, /* Port 4, T1 AFE4 */
	[4] = LAN937X_NO_PHY, /* Port 5, RGMII 2 */
};

/*
 * lan9371_phy_addr - Mapping of LAN9371 switch ports to PHY addresses.
 *
 * The values are based on the documentation (DS00003109E, section 3.3).
 */
static const u8 lan9371_phy_addr[] = {
	[0] = 2, /* Port 1, T1 AFE0 */
	[1] = 3, /* Port 2, T1 AFE1 */
	[2] = 5, /* Port 3, T1 AFE3 */
	[3] = 8, /* Port 4, TX PHY */
	[4] = LAN937X_NO_PHY, /* Port 5, RGMII 2 */
	[5] = LAN937X_NO_PHY, /* Port 6, RGMII 1 */
};

/*
 * lan9372_phy_addr - Mapping of LAN9372 switch ports to PHY addresses.
 *
 * The values are based on the documentation (DS00003110F, section 3.3).
 */
static const u8 lan9372_phy_addr[] = {
	[0] = 2, /* Port 1, T1 AFE0 */
	[1] = 3, /* Port 2, T1 AFE1 */
	[2] = 5, /* Port 3, T1 AFE3 */
	[3] = 8, /* Port 4, TX PHY */
	[4] = LAN937X_NO_PHY, /* Port 5, RGMII 2 */
	[5] = LAN937X_NO_PHY, /* Port 6, RGMII 1 */
	[6] = 6, /* Port 7, T1 AFE4 */
	[7] = 4, /* Port 8, T1 AFE2 */
};

/*
 * lan9373_phy_addr - Mapping of LAN9373 switch ports to PHY addresses.
 *
 * The values are based on the documentation (DS00003110F, section 3.3).
 */
static const u8 lan9373_phy_addr[] = {
	[0] = 2, /* Port 1, T1 AFE0 */
	[1] = 3, /* Port 2, T1 AFE1 */
	[2] = 5, /* Port 3, T1 AFE3 */
	[3] = LAN937X_NO_PHY, /* Port 4, SGMII */
	[4] = LAN937X_NO_PHY, /* Port 5, RGMII 2 */
	[5] = LAN937X_NO_PHY, /* Port 6, RGMII 1 */
	[6] = 6, /* Port 7, T1 AFE4 */
	[7] = 4, /* Port 8, T1 AFE2 */
};

/*
 * lan9374_phy_addr - Mapping of LAN9374 switch ports to PHY addresses.
 *
 * The values are based on the documentation (DS00003110F, section 3.3).
 */
static const u8 lan9374_phy_addr[] = {
	[0] = 2, /* Port 1, T1 AFE0 */
	[1] = 3, /* Port 2, T1 AFE1 */
	[2] = 5, /* Port 3, T1 AFE3 */
	[3] = 7, /* Port 4, T1 AFE5 */
	[4] = LAN937X_NO_PHY, /* Port 5, RGMII 2 */
	[5] = LAN937X_NO_PHY, /* Port 6, RGMII 1 */
	[6] = 6, /* Port 7, T1 AFE4 */
	[7] = 4, /* Port 8, T1 AFE2 */
};

static int lan937x_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set)
{
	return regmap_update_bits(ksz_regmap_8(dev), addr, bits, set ? bits : 0);
@@ -30,24 +111,144 @@ static int lan937x_port_cfg(struct ksz_device *dev, int port, int offset,
				  bits, set ? bits : 0);
}

static int lan937x_enable_spi_indirect_access(struct ksz_device *dev)
/**
 * lan937x_create_phy_addr_map - Create port-to-PHY address map for MDIO bus.
 * @dev: Pointer to device structure.
 * @side_mdio: Boolean indicating if the PHYs are accessed over a side MDIO bus.
 *
 * This function sets up the PHY address mapping for the LAN937x switches,
 * which support two access modes for internal PHYs:
 * 1. **SPI Access**: A straightforward one-to-one port-to-PHY address
 *    mapping is applied.
 * 2. **MDIO Access**: The PHY address mapping varies based on chip variant
 *    and strap configuration. An offset is calculated based on strap settings
 *    to ensure correct PHY addresses are assigned. The offset calculation logic
 *    is based on Microchip's Article Number 000015828, available at:
 *    https://microchip.my.site.com/s/article/LAN9374-Virtual-PHY-PHY-Address-Mapping
 *
 * The function first checks if side MDIO access is disabled, in which case a
 * simple direct mapping (port number = PHY address) is applied. If side MDIO
 * access is enabled, it reads the strap configuration to determine the correct
 * offset for PHY addresses.
 *
 * The appropriate mapping table is selected based on the chip ID, and the
 * `phy_addr_map` is populated with the correct addresses for each port. Any
 * port with no PHY is assigned a `LAN937X_NO_PHY` marker.
 *
 * Return: 0 on success, error code on failure.
 */
int lan937x_create_phy_addr_map(struct ksz_device *dev, bool side_mdio)
{
	static const u8 *phy_addr_map;
	u32 strap_val;
	u8 offset = 0;
	size_t size;
	int ret, i;

	if (!side_mdio) {
		/* simple direct mapping */
		for (i = 0; i < dev->info->port_cnt; i++)
			dev->phy_addr_map[i] = i;

		return 0;
	}

	ret = ksz_read32(dev, REG_SW_CFG_STRAP_VAL, &strap_val);
	if (ret < 0)
		return ret;

	if (!(strap_val & SW_CASCADE_ID_CFG) && !(strap_val & SW_VPHY_ADD_CFG))
		offset = 0;
	else if (!(strap_val & SW_CASCADE_ID_CFG) && (strap_val & SW_VPHY_ADD_CFG))
		offset = 7;
	else if ((strap_val & SW_CASCADE_ID_CFG) && !(strap_val & SW_VPHY_ADD_CFG))
		offset = 15;
	else
		offset = 22;

	switch (dev->info->chip_id) {
	case LAN9370_CHIP_ID:
		phy_addr_map = lan9370_phy_addr;
		size = ARRAY_SIZE(lan9370_phy_addr);
		break;
	case LAN9371_CHIP_ID:
		phy_addr_map = lan9371_phy_addr;
		size = ARRAY_SIZE(lan9371_phy_addr);
		break;
	case LAN9372_CHIP_ID:
		phy_addr_map = lan9372_phy_addr;
		size = ARRAY_SIZE(lan9372_phy_addr);
		break;
	case LAN9373_CHIP_ID:
		phy_addr_map = lan9373_phy_addr;
		size = ARRAY_SIZE(lan9373_phy_addr);
		break;
	case LAN9374_CHIP_ID:
		phy_addr_map = lan9374_phy_addr;
		size = ARRAY_SIZE(lan9374_phy_addr);
		break;
	default:
		return -EINVAL;
	}

	if (size < dev->info->port_cnt)
		return -EINVAL;

	for (i = 0; i < dev->info->port_cnt; i++) {
		if (phy_addr_map[i] == LAN937X_NO_PHY)
			dev->phy_addr_map[i] = phy_addr_map[i];
		else
			dev->phy_addr_map[i] = phy_addr_map[i] + offset;
	}

	return 0;
}

/**
 * lan937x_mdio_bus_preinit - Pre-initialize MDIO bus for accessing PHYs.
 * @dev: Pointer to device structure.
 * @side_mdio: Boolean indicating if the PHYs are accessed over a side MDIO bus.
 *
 * This function configures the LAN937x switch for PHY access either through
 * SPI or the side MDIO bus, unlocking the necessary registers for each access
 * mode.
 *
 * Operation Modes:
 * 1. **SPI Access**: Enables SPI indirect access to address clock domain
 *    crossing issues when SPI is used for PHY access.
 * 2. **MDIO Access**: Grants access to internal PHYs over the side MDIO bus,
 *    required when using the MDIO bus for PHY management.
 *
 * Return: 0 on success, error code on failure.
 */
int lan937x_mdio_bus_preinit(struct ksz_device *dev, bool side_mdio)
{
	u16 data16;
	int ret;

	/* Enable Phy access through SPI */
	/* Unlock access to the PHYs, needed for SPI and side MDIO access */
	ret = lan937x_cfg(dev, REG_GLOBAL_CTRL_0, SW_PHY_REG_BLOCK, false);
	if (ret < 0)
		return ret;
		goto print_error;

	ret = ksz_read16(dev, REG_VPHY_SPECIAL_CTRL__2, &data16);
	if (ret < 0)
		return ret;
	if (side_mdio)
		/* Allow access to internal PHYs over MDIO bus */
		data16 = VPHY_MDIO_INTERNAL_ENABLE;
	else
		/* Enable SPI indirect access to address clock domain crossing
		 * issue
		 */
		data16 = VPHY_SPI_INDIRECT_ENABLE;

	/* Allow SPI access */
	data16 |= VPHY_SPI_INDIRECT_ENABLE;
	ret = ksz_rmw16(dev, REG_VPHY_SPECIAL_CTRL__2,
			VPHY_SPI_INDIRECT_ENABLE | VPHY_MDIO_INTERNAL_ENABLE,
			data16);

	return ksz_write16(dev, REG_VPHY_SPECIAL_CTRL__2, data16);
print_error:
	if (ret < 0)
		dev_err(dev->dev, "failed to preinit the MDIO bus\n");

	return ret;
}

static int lan937x_vphy_ind_addr_wr(struct ksz_device *dev, int addr, int reg)
@@ -363,13 +564,6 @@ int lan937x_setup(struct dsa_switch *ds)
	struct ksz_device *dev = ds->priv;
	int ret;

	/* enable Indirect Access from SPI to the VPHY registers */
	ret = lan937x_enable_spi_indirect_access(dev);
	if (ret < 0) {
		dev_err(dev->dev, "failed to enable spi indirect access");
		return ret;
	}

	/* The VLAN aware is a global setting. Mixed vlan
	 * filterings are not supported.
	 */
Loading