mirror of
https://github.com/torvalds/linux.git
synced 2026-05-24 23:22:31 +02:00
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: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
23462e036e
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 (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;
|
||||
|
||||
ret = ksz_rmw16(dev, REG_VPHY_SPECIAL_CTRL__2,
|
||||
VPHY_SPI_INDIRECT_ENABLE | VPHY_MDIO_INTERNAL_ENABLE,
|
||||
data16);
|
||||
|
||||
print_error:
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
dev_err(dev->dev, "failed to preinit the MDIO bus\n");
|
||||
|
||||
/* Allow SPI access */
|
||||
data16 |= VPHY_SPI_INDIRECT_ENABLE;
|
||||
|
||||
return ksz_write16(dev, REG_VPHY_SPECIAL_CTRL__2, data16);
|
||||
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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@
|
|||
#define SW_CLK125_ENB BIT(1)
|
||||
#define SW_CLK25_ENB BIT(0)
|
||||
|
||||
#define REG_SW_CFG_STRAP_VAL 0x0200
|
||||
#define SW_CASCADE_ID_CFG BIT(15)
|
||||
#define SW_VPHY_ADD_CFG BIT(0)
|
||||
|
||||
/* 2 - PHY Control */
|
||||
#define REG_SW_CFG_STRAP_OVR 0x0214
|
||||
#define SW_VPHY_DISABLE BIT(31)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user