Commit 914b32f6 authored by Vivien Didelot's avatar Vivien Didelot Committed by David S. Miller

net: dsa: mv88e6xxx: abstract switch registers accesses

When the SMI address of the switch chip is zero, the chip assumes to be
the only one on the SMI master bus and thus responds to all its known
SMI devices addresses (port registers, Global2, etc.)

When its SMI address is not zero, some chips (e.g. 88E6352) use an
indirect access through two SMI Command and Data registers.

Other models (e.g. 88E6060) using less than 16 internal SMI addresses
always use a direct access.

Add a capability flag to describe chips supporting the (indirect)
Multi-chip Addressing Mode, and a low-level API to access the registers
via SMI.

Other accesses (like Ethernet management frames) may be added later.
Signed-off-by: default avatarVivien Didelot <vivien.didelot@savoirfairelinux.com>
Reviewed-by: default avatarAndrew Lunn <andrew@lunn.ch>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 9dddd478
...@@ -38,21 +38,74 @@ static void assert_reg_lock(struct mv88e6xxx_priv_state *ps) ...@@ -38,21 +38,74 @@ static void assert_reg_lock(struct mv88e6xxx_priv_state *ps)
} }
} }
/* If the switch's ADDR[4:0] strap pins are strapped to zero, it will /* The switch ADDR[4:1] configuration pins define the chip SMI device address
* use all 32 SMI bus addresses on its SMI bus, and all switch registers * (ADDR[0] is always zero, thus only even SMI addresses can be strapped).
* will be directly accessible on some {device address,register address} *
* pair. If the ADDR[4:0] pins are not strapped to zero, the switch * When ADDR is all zero, the chip uses Single-chip Addressing Mode, assuming it
* will only respond to SMI transactions to that specific address, and * is the only device connected to the SMI master. In this mode it responds to
* an indirect addressing mechanism needs to be used to access its * all 32 possible SMI addresses, and thus maps directly the internal devices.
* registers. *
* When ADDR is non-zero, the chip uses Multi-chip Addressing Mode, allowing
* multiple devices to share the SMI interface. In this mode it responds to only
* 2 registers, used to indirectly access the internal SMI devices.
*/ */
static int mv88e6xxx_reg_wait_ready(struct mii_bus *bus, int sw_addr)
static int mv88e6xxx_smi_read(struct mv88e6xxx_priv_state *ps,
int addr, int reg, u16 *val)
{
if (!ps->smi_ops)
return -EOPNOTSUPP;
return ps->smi_ops->read(ps, addr, reg, val);
}
static int mv88e6xxx_smi_write(struct mv88e6xxx_priv_state *ps,
int addr, int reg, u16 val)
{
if (!ps->smi_ops)
return -EOPNOTSUPP;
return ps->smi_ops->write(ps, addr, reg, val);
}
static int mv88e6xxx_smi_single_chip_read(struct mv88e6xxx_priv_state *ps,
int addr, int reg, u16 *val)
{
int ret;
ret = mdiobus_read_nested(ps->bus, addr, reg);
if (ret < 0)
return ret;
*val = ret & 0xffff;
return 0;
}
static int mv88e6xxx_smi_single_chip_write(struct mv88e6xxx_priv_state *ps,
int addr, int reg, u16 val)
{
int ret;
ret = mdiobus_write_nested(ps->bus, addr, reg, val);
if (ret < 0)
return ret;
return 0;
}
static const struct mv88e6xxx_ops mv88e6xxx_smi_single_chip_ops = {
.read = mv88e6xxx_smi_single_chip_read,
.write = mv88e6xxx_smi_single_chip_write,
};
static int mv88e6xxx_smi_multi_chip_wait(struct mv88e6xxx_priv_state *ps)
{ {
int ret; int ret;
int i; int i;
for (i = 0; i < 16; i++) { for (i = 0; i < 16; i++) {
ret = mdiobus_read_nested(bus, sw_addr, SMI_CMD); ret = mdiobus_read_nested(ps->bus, ps->sw_addr, SMI_CMD);
if (ret < 0) if (ret < 0)
return ret; return ret;
...@@ -63,108 +116,134 @@ static int mv88e6xxx_reg_wait_ready(struct mii_bus *bus, int sw_addr) ...@@ -63,108 +116,134 @@ static int mv88e6xxx_reg_wait_ready(struct mii_bus *bus, int sw_addr)
return -ETIMEDOUT; return -ETIMEDOUT;
} }
static int __mv88e6xxx_reg_read(struct mii_bus *bus, int sw_addr, int addr, static int mv88e6xxx_smi_multi_chip_read(struct mv88e6xxx_priv_state *ps,
int reg) int addr, int reg, u16 *val)
{ {
int ret; int ret;
if (sw_addr == 0)
return mdiobus_read_nested(bus, addr, reg);
/* Wait for the bus to become free. */ /* Wait for the bus to become free. */
ret = mv88e6xxx_reg_wait_ready(bus, sw_addr); ret = mv88e6xxx_smi_multi_chip_wait(ps);
if (ret < 0) if (ret < 0)
return ret; return ret;
/* Transmit the read command. */ /* Transmit the read command. */
ret = mdiobus_write_nested(bus, sw_addr, SMI_CMD, ret = mdiobus_write_nested(ps->bus, ps->sw_addr, SMI_CMD,
SMI_CMD_OP_22_READ | (addr << 5) | reg); SMI_CMD_OP_22_READ | (addr << 5) | reg);
if (ret < 0) if (ret < 0)
return ret; return ret;
/* Wait for the read command to complete. */ /* Wait for the read command to complete. */
ret = mv88e6xxx_reg_wait_ready(bus, sw_addr); ret = mv88e6xxx_smi_multi_chip_wait(ps);
if (ret < 0) if (ret < 0)
return ret; return ret;
/* Read the data. */ /* Read the data. */
ret = mdiobus_read_nested(bus, sw_addr, SMI_DATA); ret = mdiobus_read_nested(ps->bus, ps->sw_addr, SMI_DATA);
if (ret < 0)
return ret;
return ret & 0xffff;
}
static int _mv88e6xxx_reg_read(struct mv88e6xxx_priv_state *ps,
int addr, int reg)
{
int ret;
assert_reg_lock(ps);
ret = __mv88e6xxx_reg_read(ps->bus, ps->sw_addr, addr, reg);
if (ret < 0) if (ret < 0)
return ret; return ret;
dev_dbg(ps->dev, "<- addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n", *val = ret & 0xffff;
addr, reg, ret);
return ret;
}
static int mv88e6xxx_reg_read(struct mv88e6xxx_priv_state *ps, int addr, return 0;
int reg)
{
int ret;
mutex_lock(&ps->reg_lock);
ret = _mv88e6xxx_reg_read(ps, addr, reg);
mutex_unlock(&ps->reg_lock);
return ret;
} }
static int __mv88e6xxx_reg_write(struct mii_bus *bus, int sw_addr, int addr, static int mv88e6xxx_smi_multi_chip_write(struct mv88e6xxx_priv_state *ps,
int reg, u16 val) int addr, int reg, u16 val)
{ {
int ret; int ret;
if (sw_addr == 0)
return mdiobus_write_nested(bus, addr, reg, val);
/* Wait for the bus to become free. */ /* Wait for the bus to become free. */
ret = mv88e6xxx_reg_wait_ready(bus, sw_addr); ret = mv88e6xxx_smi_multi_chip_wait(ps);
if (ret < 0) if (ret < 0)
return ret; return ret;
/* Transmit the data to write. */ /* Transmit the data to write. */
ret = mdiobus_write_nested(bus, sw_addr, SMI_DATA, val); ret = mdiobus_write_nested(ps->bus, ps->sw_addr, SMI_DATA, val);
if (ret < 0) if (ret < 0)
return ret; return ret;
/* Transmit the write command. */ /* Transmit the write command. */
ret = mdiobus_write_nested(bus, sw_addr, SMI_CMD, ret = mdiobus_write_nested(ps->bus, ps->sw_addr, SMI_CMD,
SMI_CMD_OP_22_WRITE | (addr << 5) | reg); SMI_CMD_OP_22_WRITE | (addr << 5) | reg);
if (ret < 0) if (ret < 0)
return ret; return ret;
/* Wait for the write command to complete. */ /* Wait for the write command to complete. */
ret = mv88e6xxx_reg_wait_ready(bus, sw_addr); ret = mv88e6xxx_smi_multi_chip_wait(ps);
if (ret < 0) if (ret < 0)
return ret; return ret;
return 0; return 0;
} }
static int _mv88e6xxx_reg_write(struct mv88e6xxx_priv_state *ps, int addr, static const struct mv88e6xxx_ops mv88e6xxx_smi_multi_chip_ops = {
int reg, u16 val) .read = mv88e6xxx_smi_multi_chip_read,
.write = mv88e6xxx_smi_multi_chip_write,
};
static int mv88e6xxx_read(struct mv88e6xxx_priv_state *ps,
int addr, int reg, u16 *val)
{
int err;
assert_reg_lock(ps);
err = mv88e6xxx_smi_read(ps, addr, reg, val);
if (err)
return err;
dev_dbg(ps->dev, "<- addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n",
addr, reg, *val);
return 0;
}
static int mv88e6xxx_write(struct mv88e6xxx_priv_state *ps,
int addr, int reg, u16 val)
{ {
int err;
assert_reg_lock(ps); assert_reg_lock(ps);
err = mv88e6xxx_smi_write(ps, addr, reg, val);
if (err)
return err;
dev_dbg(ps->dev, "-> addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n", dev_dbg(ps->dev, "-> addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n",
addr, reg, val); addr, reg, val);
return __mv88e6xxx_reg_write(ps->bus, ps->sw_addr, addr, reg, val); return 0;
}
static int _mv88e6xxx_reg_read(struct mv88e6xxx_priv_state *ps,
int addr, int reg)
{
u16 val;
int err;
err = mv88e6xxx_read(ps, addr, reg, &val);
if (err)
return err;
return val;
}
static int mv88e6xxx_reg_read(struct mv88e6xxx_priv_state *ps, int addr,
int reg)
{
int ret;
mutex_lock(&ps->reg_lock);
ret = _mv88e6xxx_reg_read(ps, addr, reg);
mutex_unlock(&ps->reg_lock);
return ret;
}
static int _mv88e6xxx_reg_write(struct mv88e6xxx_priv_state *ps, int addr,
int reg, u16 val)
{
return mv88e6xxx_write(ps, addr, reg, val);
} }
static int mv88e6xxx_reg_write(struct mv88e6xxx_priv_state *ps, int addr, static int mv88e6xxx_reg_write(struct mv88e6xxx_priv_state *ps, int addr,
...@@ -3666,6 +3745,13 @@ static int mv88e6xxx_smi_init(struct mv88e6xxx_priv_state *ps, ...@@ -3666,6 +3745,13 @@ static int mv88e6xxx_smi_init(struct mv88e6xxx_priv_state *ps,
if (sw_addr & 0x1) if (sw_addr & 0x1)
return -EINVAL; return -EINVAL;
if (sw_addr == 0)
ps->smi_ops = &mv88e6xxx_smi_single_chip_ops;
else if (mv88e6xxx_has(ps, MV88E6XXX_FLAG_MULTI_CHIP))
ps->smi_ops = &mv88e6xxx_smi_multi_chip_ops;
else
return -EINVAL;
ps->bus = bus; ps->bus = bus;
ps->sw_addr = sw_addr; ps->sw_addr = sw_addr;
......
...@@ -387,6 +387,12 @@ enum mv88e6xxx_cap { ...@@ -387,6 +387,12 @@ enum mv88e6xxx_cap {
*/ */
MV88E6XXX_CAP_EEPROM, MV88E6XXX_CAP_EEPROM,
/* Multi-chip Addressing Mode.
* Some chips require an indirect SMI access when their SMI device
* address is not zero. See SMI_CMD and SMI_DATA.
*/
MV88E6XXX_CAP_MULTI_CHIP,
/* Port State Filtering for 802.1D Spanning Tree. /* Port State Filtering for 802.1D Spanning Tree.
* See PORT_CONTROL_STATE_* values in the PORT_CONTROL register. * See PORT_CONTROL_STATE_* values in the PORT_CONTROL register.
*/ */
...@@ -439,6 +445,7 @@ enum mv88e6xxx_cap { ...@@ -439,6 +445,7 @@ enum mv88e6xxx_cap {
#define MV88E6XXX_FLAG_ATU BIT(MV88E6XXX_CAP_ATU) #define MV88E6XXX_FLAG_ATU BIT(MV88E6XXX_CAP_ATU)
#define MV88E6XXX_FLAG_EEE BIT(MV88E6XXX_CAP_EEE) #define MV88E6XXX_FLAG_EEE BIT(MV88E6XXX_CAP_EEE)
#define MV88E6XXX_FLAG_EEPROM BIT(MV88E6XXX_CAP_EEPROM) #define MV88E6XXX_FLAG_EEPROM BIT(MV88E6XXX_CAP_EEPROM)
#define MV88E6XXX_FLAG_MULTI_CHIP BIT(MV88E6XXX_CAP_MULTI_CHIP)
#define MV88E6XXX_FLAG_PORTSTATE BIT(MV88E6XXX_CAP_PORTSTATE) #define MV88E6XXX_FLAG_PORTSTATE BIT(MV88E6XXX_CAP_PORTSTATE)
#define MV88E6XXX_FLAG_PPU BIT(MV88E6XXX_CAP_PPU) #define MV88E6XXX_FLAG_PPU BIT(MV88E6XXX_CAP_PPU)
#define MV88E6XXX_FLAG_PPU_ACTIVE BIT(MV88E6XXX_CAP_PPU_ACTIVE) #define MV88E6XXX_FLAG_PPU_ACTIVE BIT(MV88E6XXX_CAP_PPU_ACTIVE)
...@@ -452,25 +459,29 @@ enum mv88e6xxx_cap { ...@@ -452,25 +459,29 @@ enum mv88e6xxx_cap {
#define MV88E6XXX_FLAGS_FAMILY_6095 \ #define MV88E6XXX_FLAGS_FAMILY_6095 \
(MV88E6XXX_FLAG_ATU | \ (MV88E6XXX_FLAG_ATU | \
MV88E6XXX_FLAG_MULTI_CHIP | \
MV88E6XXX_FLAG_PPU | \ MV88E6XXX_FLAG_PPU | \
MV88E6XXX_FLAG_VLANTABLE | \ MV88E6XXX_FLAG_VLANTABLE | \
MV88E6XXX_FLAG_VTU) MV88E6XXX_FLAG_VTU)
#define MV88E6XXX_FLAGS_FAMILY_6097 \ #define MV88E6XXX_FLAGS_FAMILY_6097 \
(MV88E6XXX_FLAG_ATU | \ (MV88E6XXX_FLAG_ATU | \
MV88E6XXX_FLAG_MULTI_CHIP | \
MV88E6XXX_FLAG_PPU | \ MV88E6XXX_FLAG_PPU | \
MV88E6XXX_FLAG_STU | \ MV88E6XXX_FLAG_STU | \
MV88E6XXX_FLAG_VLANTABLE | \ MV88E6XXX_FLAG_VLANTABLE | \
MV88E6XXX_FLAG_VTU) MV88E6XXX_FLAG_VTU)
#define MV88E6XXX_FLAGS_FAMILY_6165 \ #define MV88E6XXX_FLAGS_FAMILY_6165 \
(MV88E6XXX_FLAG_STU | \ (MV88E6XXX_FLAG_MULTI_CHIP | \
MV88E6XXX_FLAG_STU | \
MV88E6XXX_FLAG_SWITCH_MAC | \ MV88E6XXX_FLAG_SWITCH_MAC | \
MV88E6XXX_FLAG_TEMP | \ MV88E6XXX_FLAG_TEMP | \
MV88E6XXX_FLAG_VTU) MV88E6XXX_FLAG_VTU)
#define MV88E6XXX_FLAGS_FAMILY_6185 \ #define MV88E6XXX_FLAGS_FAMILY_6185 \
(MV88E6XXX_FLAG_ATU | \ (MV88E6XXX_FLAG_ATU | \
MV88E6XXX_FLAG_MULTI_CHIP | \
MV88E6XXX_FLAG_PPU | \ MV88E6XXX_FLAG_PPU | \
MV88E6XXX_FLAG_VLANTABLE | \ MV88E6XXX_FLAG_VLANTABLE | \
MV88E6XXX_FLAG_VTU) MV88E6XXX_FLAG_VTU)
...@@ -479,6 +490,7 @@ enum mv88e6xxx_cap { ...@@ -479,6 +490,7 @@ enum mv88e6xxx_cap {
(MV88E6XXX_FLAG_ATU | \ (MV88E6XXX_FLAG_ATU | \
MV88E6XXX_FLAG_EEE | \ MV88E6XXX_FLAG_EEE | \
MV88E6XXX_FLAG_EEPROM | \ MV88E6XXX_FLAG_EEPROM | \
MV88E6XXX_FLAG_MULTI_CHIP | \
MV88E6XXX_FLAG_PORTSTATE | \ MV88E6XXX_FLAG_PORTSTATE | \
MV88E6XXX_FLAG_PPU_ACTIVE | \ MV88E6XXX_FLAG_PPU_ACTIVE | \
MV88E6XXX_FLAG_SMI_PHY | \ MV88E6XXX_FLAG_SMI_PHY | \
...@@ -490,6 +502,7 @@ enum mv88e6xxx_cap { ...@@ -490,6 +502,7 @@ enum mv88e6xxx_cap {
#define MV88E6XXX_FLAGS_FAMILY_6351 \ #define MV88E6XXX_FLAGS_FAMILY_6351 \
(MV88E6XXX_FLAG_ATU | \ (MV88E6XXX_FLAG_ATU | \
MV88E6XXX_FLAG_MULTI_CHIP | \
MV88E6XXX_FLAG_PORTSTATE | \ MV88E6XXX_FLAG_PORTSTATE | \
MV88E6XXX_FLAG_PPU_ACTIVE | \ MV88E6XXX_FLAG_PPU_ACTIVE | \
MV88E6XXX_FLAG_SMI_PHY | \ MV88E6XXX_FLAG_SMI_PHY | \
...@@ -503,6 +516,7 @@ enum mv88e6xxx_cap { ...@@ -503,6 +516,7 @@ enum mv88e6xxx_cap {
(MV88E6XXX_FLAG_ATU | \ (MV88E6XXX_FLAG_ATU | \
MV88E6XXX_FLAG_EEE | \ MV88E6XXX_FLAG_EEE | \
MV88E6XXX_FLAG_EEPROM | \ MV88E6XXX_FLAG_EEPROM | \
MV88E6XXX_FLAG_MULTI_CHIP | \
MV88E6XXX_FLAG_PORTSTATE | \ MV88E6XXX_FLAG_PORTSTATE | \
MV88E6XXX_FLAG_PPU_ACTIVE | \ MV88E6XXX_FLAG_PPU_ACTIVE | \
MV88E6XXX_FLAG_SMI_PHY | \ MV88E6XXX_FLAG_SMI_PHY | \
...@@ -542,6 +556,8 @@ struct mv88e6xxx_vtu_stu_entry { ...@@ -542,6 +556,8 @@ struct mv88e6xxx_vtu_stu_entry {
u8 data[DSA_MAX_PORTS]; u8 data[DSA_MAX_PORTS];
}; };
struct mv88e6xxx_ops;
struct mv88e6xxx_priv_port { struct mv88e6xxx_priv_port {
struct net_device *bridge_dev; struct net_device *bridge_dev;
}; };
...@@ -561,6 +577,7 @@ struct mv88e6xxx_priv_state { ...@@ -561,6 +577,7 @@ struct mv88e6xxx_priv_state {
/* The MII bus and the address on the bus that is used to /* The MII bus and the address on the bus that is used to
* communication with the switch * communication with the switch
*/ */
const struct mv88e6xxx_ops *smi_ops;
struct mii_bus *bus; struct mii_bus *bus;
int sw_addr; int sw_addr;
...@@ -606,6 +623,13 @@ struct mv88e6xxx_priv_state { ...@@ -606,6 +623,13 @@ struct mv88e6xxx_priv_state {
struct mii_bus *mdio_bus; struct mii_bus *mdio_bus;
}; };
struct mv88e6xxx_ops {
int (*read)(struct mv88e6xxx_priv_state *ps,
int addr, int reg, u16 *val);
int (*write)(struct mv88e6xxx_priv_state *ps,
int addr, int reg, u16 val);
};
enum stat_type { enum stat_type {
BANK0, BANK0,
BANK1, BANK1,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment