diff mbox

[v4,net-next,v4,14/14] net: dsa: mv88e6xxx: abstract switch registers accesses

Message ID 20160620160337.2934-15-vivien.didelot@savoirfairelinux.com
State Superseded, archived
Delegated to: David Miller
Headers show

Commit Message

Vivien Didelot June 20, 2016, 4:03 p.m. UTC
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: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
---
 drivers/net/dsa/mv88e6xxx.c | 204 +++++++++++++++++++++++++++++++-------------
 drivers/net/dsa/mv88e6xxx.h |  26 +++++-
 2 files changed, 170 insertions(+), 60 deletions(-)

Comments

Andrew Lunn June 20, 2016, 4:30 p.m. UTC | #1
On Mon, Jun 20, 2016 at 12:03:37PM -0400, Vivien Didelot wrote:
> 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: Vivien Didelot <vivien.didelot@savoirfairelinux.com>

Reviewed-by: Andrew Lunn <andrew@lunn.ch>

This series is now ready for merging.

Thanks
    Andrew
Vivien Didelot June 20, 2016, 5:03 p.m. UTC | #2
Hi Andrew, David,

Andrew Lunn <andrew@lunn.ch> writes:

> On Mon, Jun 20, 2016 at 12:03:37PM -0400, Vivien Didelot wrote:
>> 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: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
>
> Reviewed-by: Andrew Lunn <andrew@lunn.ch>
>
> This series is now ready for merging.

I introduced a warning in that patch by mistake, by printing 'val'
instead of '*val' in a dev_dbg() call...

I respin a v5 with Andrew's tag and the debug printing fixed.

Sorry for the noice...

      Vivien
kernel test robot June 20, 2016, 6:25 p.m. UTC | #3
Hi,

[auto build test WARNING on net-next/master]

url:    https://github.com/0day-ci/linux/commits/Vivien-Didelot/net-dsa-mv88e6xxx-probe-compatible/20160621-020115
config: tile-allyesconfig (attached as .config)
compiler: tilegx-linux-gcc (GCC) 4.6.2
reproduce:
        wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        make.cross ARCH=tile 

All warnings (new ones prefixed by >>):

   drivers/net/dsa/mv88e6xxx.c: In function 'mv88e6xxx_read':
>> drivers/net/dsa/mv88e6xxx.c:195:2: warning: format '%x' expects argument of type 'unsigned int', but argument 6 has type 'u16 *' [-Wformat]

vim +195 drivers/net/dsa/mv88e6xxx.c

   179	static const struct mv88e6xxx_ops mv88e6xxx_smi_multi_chip_ops = {
   180		.read = mv88e6xxx_smi_multi_chip_read,
   181		.write = mv88e6xxx_smi_multi_chip_write,
   182	};
   183	
   184	static int mv88e6xxx_read(struct mv88e6xxx_priv_state *ps,
   185				  int addr, int reg, u16 *val)
   186	{
   187		int err;
   188	
   189		assert_reg_lock(ps);
   190	
   191		err = mv88e6xxx_smi_read(ps, addr, reg, val);
   192		if (err)
   193			return err;
   194	
 > 195		dev_dbg(ps->dev, "<- addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n",
   196			addr, reg, val);
   197	
   198		return 0;
   199	}
   200	
   201	static int mv88e6xxx_write(struct mv88e6xxx_priv_state *ps,
   202				   int addr, int reg, u16 val)
   203	{

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
kernel test robot June 20, 2016, 6:32 p.m. UTC | #4
Hi,

[auto build test WARNING on net-next/master]

url:    https://github.com/0day-ci/linux/commits/Vivien-Didelot/net-dsa-mv88e6xxx-probe-compatible/20160621-020115
config: sparc64-allyesconfig (attached as .config)
compiler: sparc64-linux-gnu-gcc (Debian 5.3.1-8) 5.3.1 20160205
reproduce:
        wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        make.cross ARCH=sparc64 

All warnings (new ones prefixed by >>):

   In file included from include/linux/printk.h:289:0,
                    from include/linux/kernel.h:13,
                    from include/linux/delay.h:10,
                    from drivers/net/dsa/mv88e6xxx.c:16:
   drivers/net/dsa/mv88e6xxx.c: In function 'mv88e6xxx_read':
>> drivers/net/dsa/mv88e6xxx.c:195:19: warning: format '%x' expects argument of type 'unsigned int', but argument 6 has type 'u16 * {aka short unsigned int *}' [-Wformat=]
     dev_dbg(ps->dev, "<- addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n",
                      ^
   include/linux/dynamic_debug.h:86:39: note: in definition of macro 'dynamic_dev_dbg'
      __dynamic_dev_dbg(&descriptor, dev, fmt, \
                                          ^
>> drivers/net/dsa/mv88e6xxx.c:195:2: note: in expansion of macro 'dev_dbg'
     dev_dbg(ps->dev, "<- addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n",
     ^

vim +195 drivers/net/dsa/mv88e6xxx.c

    10	 * This program is free software; you can redistribute it and/or modify
    11	 * it under the terms of the GNU General Public License as published by
    12	 * the Free Software Foundation; either version 2 of the License, or
    13	 * (at your option) any later version.
    14	 */
    15	
  > 16	#include <linux/delay.h>
    17	#include <linux/etherdevice.h>
    18	#include <linux/ethtool.h>
    19	#include <linux/if_bridge.h>
    20	#include <linux/jiffies.h>
    21	#include <linux/list.h>
    22	#include <linux/mdio.h>
    23	#include <linux/module.h>
    24	#include <linux/of_device.h>
    25	#include <linux/of_mdio.h>
    26	#include <linux/netdevice.h>
    27	#include <linux/gpio/consumer.h>
    28	#include <linux/phy.h>
    29	#include <net/dsa.h>
    30	#include <net/switchdev.h>
    31	#include "mv88e6xxx.h"
    32	
    33	static void assert_reg_lock(struct mv88e6xxx_priv_state *ps)
    34	{
    35		if (unlikely(!mutex_is_locked(&ps->reg_lock))) {
    36			dev_err(ps->dev, "Switch registers lock not held!\n");
    37			dump_stack();
    38		}
    39	}
    40	
    41	/* The switch ADDR[4:1] configuration pins define the chip SMI device address
    42	 * (ADDR[0] is always zero, thus only even SMI addresses can be strapped).
    43	 *
    44	 * When ADDR is all zero, the chip uses Single-chip Addressing Mode, assuming it
    45	 * is the only device connected to the SMI master. In this mode it responds to
    46	 * all 32 possible SMI addresses, and thus maps directly the internal devices.
    47	 *
    48	 * When ADDR is non-zero, the chip uses Multi-chip Addressing Mode, allowing
    49	 * multiple devices to share the SMI interface. In this mode it responds to only
    50	 * 2 registers, used to indirectly access the internal SMI devices.
    51	 */
    52	
    53	static int mv88e6xxx_smi_read(struct mv88e6xxx_priv_state *ps,
    54				      int addr, int reg, u16 *val)
    55	{
    56		if (!ps->smi_ops)
    57			return -EOPNOTSUPP;
    58	
    59		return ps->smi_ops->read(ps, addr, reg, val);
    60	}
    61	
    62	static int mv88e6xxx_smi_write(struct mv88e6xxx_priv_state *ps,
    63				       int addr, int reg, u16 val)
    64	{
    65		if (!ps->smi_ops)
    66			return -EOPNOTSUPP;
    67	
    68		return ps->smi_ops->write(ps, addr, reg, val);
    69	}
    70	
    71	static int mv88e6xxx_smi_single_chip_read(struct mv88e6xxx_priv_state *ps,
    72						  int addr, int reg, u16 *val)
    73	{
    74		int ret;
    75	
    76		ret = mdiobus_read_nested(ps->bus, addr, reg);
    77		if (ret < 0)
    78			return ret;
    79	
    80		*val = ret & 0xffff;
    81	
    82		return 0;
    83	}
    84	
    85	static int mv88e6xxx_smi_single_chip_write(struct mv88e6xxx_priv_state *ps,
    86						   int addr, int reg, u16 val)
    87	{
    88		int ret;
    89	
    90		ret = mdiobus_write_nested(ps->bus, addr, reg, val);
    91		if (ret < 0)
    92			return ret;
    93	
    94		return 0;
    95	}
    96	
    97	static const struct mv88e6xxx_ops mv88e6xxx_smi_single_chip_ops = {
    98		.read = mv88e6xxx_smi_single_chip_read,
    99		.write = mv88e6xxx_smi_single_chip_write,
   100	};
   101	
   102	static int mv88e6xxx_smi_multi_chip_wait(struct mv88e6xxx_priv_state *ps)
   103	{
   104		int ret;
   105		int i;
   106	
   107		for (i = 0; i < 16; i++) {
   108			ret = mdiobus_read_nested(ps->bus, ps->sw_addr, SMI_CMD);
   109			if (ret < 0)
   110				return ret;
   111	
   112			if ((ret & SMI_CMD_BUSY) == 0)
   113				return 0;
   114		}
   115	
   116		return -ETIMEDOUT;
   117	}
   118	
   119	static int mv88e6xxx_smi_multi_chip_read(struct mv88e6xxx_priv_state *ps,
   120						 int addr, int reg, u16 *val)
   121	{
   122		int ret;
   123	
   124		/* Wait for the bus to become free. */
   125		ret = mv88e6xxx_smi_multi_chip_wait(ps);
   126		if (ret < 0)
   127			return ret;
   128	
   129		/* Transmit the read command. */
   130		ret = mdiobus_write_nested(ps->bus, ps->sw_addr, SMI_CMD,
   131					   SMI_CMD_OP_22_READ | (addr << 5) | reg);
   132		if (ret < 0)
   133			return ret;
   134	
   135		/* Wait for the read command to complete. */
   136		ret = mv88e6xxx_smi_multi_chip_wait(ps);
   137		if (ret < 0)
   138			return ret;
   139	
   140		/* Read the data. */
   141		ret = mdiobus_read_nested(ps->bus, ps->sw_addr, SMI_DATA);
   142		if (ret < 0)
   143			return ret;
   144	
   145		*val = ret & 0xffff;
   146	
   147		return 0;
   148	}
   149	
   150	static int mv88e6xxx_smi_multi_chip_write(struct mv88e6xxx_priv_state *ps,
   151						  int addr, int reg, u16 val)
   152	{
   153		int ret;
   154	
   155		/* Wait for the bus to become free. */
   156		ret = mv88e6xxx_smi_multi_chip_wait(ps);
   157		if (ret < 0)
   158			return ret;
   159	
   160		/* Transmit the data to write. */
   161		ret = mdiobus_write_nested(ps->bus, ps->sw_addr, SMI_DATA, val);
   162		if (ret < 0)
   163			return ret;
   164	
   165		/* Transmit the write command. */
   166		ret = mdiobus_write_nested(ps->bus, ps->sw_addr, SMI_CMD,
   167					   SMI_CMD_OP_22_WRITE | (addr << 5) | reg);
   168		if (ret < 0)
   169			return ret;
   170	
   171		/* Wait for the write command to complete. */
   172		ret = mv88e6xxx_smi_multi_chip_wait(ps);
   173		if (ret < 0)
   174			return ret;
   175	
   176		return 0;
   177	}
   178	
   179	static const struct mv88e6xxx_ops mv88e6xxx_smi_multi_chip_ops = {
   180		.read = mv88e6xxx_smi_multi_chip_read,
   181		.write = mv88e6xxx_smi_multi_chip_write,
   182	};
   183	
   184	static int mv88e6xxx_read(struct mv88e6xxx_priv_state *ps,
   185				  int addr, int reg, u16 *val)
   186	{
   187		int err;
   188	
   189		assert_reg_lock(ps);
   190	
   191		err = mv88e6xxx_smi_read(ps, addr, reg, val);
   192		if (err)
   193			return err;
   194	
 > 195		dev_dbg(ps->dev, "<- addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n",
   196			addr, reg, val);
   197	
   198		return 0;

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
kernel test robot June 20, 2016, 6:33 p.m. UTC | #5
Hi,

[auto build test WARNING on net-next/master]

url:    https://github.com/0day-ci/linux/commits/Vivien-Didelot/net-dsa-mv88e6xxx-probe-compatible/20160621-020115
config: m68k-allyesconfig (attached as .config)
compiler: m68k-linux-gcc (GCC) 4.9.0
reproduce:
        wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        make.cross ARCH=m68k 

All warnings (new ones prefixed by >>):

   In file included from include/linux/printk.h:289:0,
                    from include/linux/kernel.h:13,
                    from include/linux/delay.h:10,
                    from drivers/net/dsa/mv88e6xxx.c:16:
   drivers/net/dsa/mv88e6xxx.c: In function 'mv88e6xxx_read':
>> include/linux/dynamic_debug.h:64:16: warning: format '%x' expects argument of type 'unsigned int', but argument 6 has type 'u16 *' [-Wformat=]
     static struct _ddebug  __aligned(8)   \
                   ^
   include/linux/dynamic_debug.h:84:2: note: in expansion of macro 'DEFINE_DYNAMIC_DEBUG_METADATA'
     DEFINE_DYNAMIC_DEBUG_METADATA(descriptor, fmt);  \
     ^
   include/linux/device.h:1197:2: note: in expansion of macro 'dynamic_dev_dbg'
     dynamic_dev_dbg(dev, format, ##__VA_ARGS__); \
     ^
   drivers/net/dsa/mv88e6xxx.c:195:2: note: in expansion of macro 'dev_dbg'
     dev_dbg(ps->dev, "<- addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n",
     ^

vim +64 include/linux/dynamic_debug.h

b48420c1 Jim Cromie  2012-04-27  48  					const char *modname);
b48420c1 Jim Cromie  2012-04-27  49  
cbc46635 Joe Perches 2011-08-11  50  struct device;
cbc46635 Joe Perches 2011-08-11  51  
b9075fa9 Joe Perches 2011-10-31  52  extern __printf(3, 4)
906d2015 Joe Perches 2014-09-24  53  void __dynamic_dev_dbg(struct _ddebug *descriptor, const struct device *dev,
b9075fa9 Joe Perches 2011-10-31  54  		       const char *fmt, ...);
cbc46635 Joe Perches 2011-08-11  55  
ffa10cb4 Jason Baron 2011-08-11  56  struct net_device;
ffa10cb4 Jason Baron 2011-08-11  57  
b9075fa9 Joe Perches 2011-10-31  58  extern __printf(3, 4)
906d2015 Joe Perches 2014-09-24  59  void __dynamic_netdev_dbg(struct _ddebug *descriptor,
ffa10cb4 Jason Baron 2011-08-11  60  			  const struct net_device *dev,
b9075fa9 Joe Perches 2011-10-31  61  			  const char *fmt, ...);
ffa10cb4 Jason Baron 2011-08-11  62  
07613b0b Jason Baron 2011-10-04  63  #define DEFINE_DYNAMIC_DEBUG_METADATA(name, fmt)		\
c0d2af63 Joe Perches 2012-10-18 @64  	static struct _ddebug  __aligned(8)			\
07613b0b Jason Baron 2011-10-04  65  	__attribute__((section("__verbose"))) name = {		\
07613b0b Jason Baron 2011-10-04  66  		.modname = KBUILD_MODNAME,			\
07613b0b Jason Baron 2011-10-04  67  		.function = __func__,				\
07613b0b Jason Baron 2011-10-04  68  		.filename = __FILE__,				\
07613b0b Jason Baron 2011-10-04  69  		.format = (fmt),				\
07613b0b Jason Baron 2011-10-04  70  		.lineno = __LINE__,				\
07613b0b Jason Baron 2011-10-04  71  		.flags =  _DPRINTK_FLAGS_DEFAULT,		\
07613b0b Jason Baron 2011-10-04  72  	}

:::::: The code at line 64 was first introduced by commit
:::::: c0d2af637863940b1a4fb208224ca7acb905c39f dynamic_debug: Remove unnecessary __used

:::::: TO: Joe Perches <joe@perches.com>
:::::: CC: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation
diff mbox

Patch

diff --git a/drivers/net/dsa/mv88e6xxx.c b/drivers/net/dsa/mv88e6xxx.c
index 789f938a..8ef1e7c 100644
--- a/drivers/net/dsa/mv88e6xxx.c
+++ b/drivers/net/dsa/mv88e6xxx.c
@@ -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
- * use all 32 SMI bus addresses on its SMI bus, and all switch registers
- * will be directly accessible on some {device address,register address}
- * pair.  If the ADDR[4:0] pins are not strapped to zero, the switch
- * will only respond to SMI transactions to that specific address, and
- * an indirect addressing mechanism needs to be used to access its
- * registers.
+/* The switch ADDR[4:1] configuration pins define the chip SMI device address
+ * (ADDR[0] is always zero, thus only even SMI addresses can be strapped).
+ *
+ * When ADDR is all zero, the chip uses Single-chip Addressing Mode, assuming it
+ * is the only device connected to the SMI master. In this mode it responds to
+ * all 32 possible SMI addresses, and thus maps directly the internal devices.
+ *
+ * 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 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)
 			return ret;
 
@@ -63,108 +116,134 @@  static int mv88e6xxx_reg_wait_ready(struct mii_bus *bus, int sw_addr)
 	return -ETIMEDOUT;
 }
 
-static int __mv88e6xxx_reg_read(struct mii_bus *bus, int sw_addr, int addr,
-				int reg)
+static int mv88e6xxx_smi_multi_chip_read(struct mv88e6xxx_priv_state *ps,
+					 int addr, int reg, u16 *val)
 {
 	int ret;
 
-	if (sw_addr == 0)
-		return mdiobus_read_nested(bus, addr, reg);
-
 	/* 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)
 		return ret;
 
 	/* 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);
 	if (ret < 0)
 		return ret;
 
 	/* 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)
 		return ret;
 
 	/* Read the data. */
-	ret = mdiobus_read_nested(bus, 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);
+	ret = mdiobus_read_nested(ps->bus, ps->sw_addr, SMI_DATA);
 	if (ret < 0)
 		return ret;
 
-	dev_dbg(ps->dev, "<- addr: 0x%.2x reg: 0x%.2x val: 0x%.4x\n",
-		addr, reg, ret);
-
-	return ret;
-}
-
-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);
+	*val = ret & 0xffff;
 
-	return ret;
+	return 0;
 }
 
-static int __mv88e6xxx_reg_write(struct mii_bus *bus, int sw_addr, int addr,
-				 int reg, u16 val)
+static int mv88e6xxx_smi_multi_chip_write(struct mv88e6xxx_priv_state *ps,
+					  int addr, int reg, u16 val)
 {
 	int ret;
 
-	if (sw_addr == 0)
-		return mdiobus_write_nested(bus, addr, reg, val);
-
 	/* 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)
 		return ret;
 
 	/* 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)
 		return ret;
 
 	/* 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);
 	if (ret < 0)
 		return ret;
 
 	/* 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)
 		return ret;
 
 	return 0;
 }
 
-static int _mv88e6xxx_reg_write(struct mv88e6xxx_priv_state *ps, int addr,
-				int reg, u16 val)
+static const struct mv88e6xxx_ops mv88e6xxx_smi_multi_chip_ops = {
+	.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);
 
+	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",
 		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,
@@ -3666,6 +3745,13 @@  static int mv88e6xxx_smi_init(struct mv88e6xxx_priv_state *ps,
 	if (sw_addr & 0x1)
 		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->sw_addr = sw_addr;
 
diff --git a/drivers/net/dsa/mv88e6xxx.h b/drivers/net/dsa/mv88e6xxx.h
index 8e6fe6b..a94acd8 100644
--- a/drivers/net/dsa/mv88e6xxx.h
+++ b/drivers/net/dsa/mv88e6xxx.h
@@ -387,6 +387,12 @@  enum mv88e6xxx_cap {
 	 */
 	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.
 	 * See PORT_CONTROL_STATE_* values in the PORT_CONTROL register.
 	 */
@@ -439,6 +445,7 @@  enum mv88e6xxx_cap {
 #define MV88E6XXX_FLAG_ATU		BIT(MV88E6XXX_CAP_ATU)
 #define MV88E6XXX_FLAG_EEE		BIT(MV88E6XXX_CAP_EEE)
 #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_PPU		BIT(MV88E6XXX_CAP_PPU)
 #define MV88E6XXX_FLAG_PPU_ACTIVE	BIT(MV88E6XXX_CAP_PPU_ACTIVE)
@@ -452,25 +459,29 @@  enum mv88e6xxx_cap {
 
 #define MV88E6XXX_FLAGS_FAMILY_6095	\
 	(MV88E6XXX_FLAG_ATU |		\
+	 MV88E6XXX_FLAG_MULTI_CHIP |	\
 	 MV88E6XXX_FLAG_PPU |		\
 	 MV88E6XXX_FLAG_VLANTABLE |	\
 	 MV88E6XXX_FLAG_VTU)
 
 #define MV88E6XXX_FLAGS_FAMILY_6097	\
 	(MV88E6XXX_FLAG_ATU |		\
+	 MV88E6XXX_FLAG_MULTI_CHIP |	\
 	 MV88E6XXX_FLAG_PPU |		\
 	 MV88E6XXX_FLAG_STU |		\
 	 MV88E6XXX_FLAG_VLANTABLE |	\
 	 MV88E6XXX_FLAG_VTU)
 
 #define MV88E6XXX_FLAGS_FAMILY_6165	\
-	(MV88E6XXX_FLAG_STU |		\
+	(MV88E6XXX_FLAG_MULTI_CHIP |	\
+	 MV88E6XXX_FLAG_STU |		\
 	 MV88E6XXX_FLAG_SWITCH_MAC |	\
 	 MV88E6XXX_FLAG_TEMP |		\
 	 MV88E6XXX_FLAG_VTU)
 
 #define MV88E6XXX_FLAGS_FAMILY_6185	\
 	(MV88E6XXX_FLAG_ATU |		\
+	 MV88E6XXX_FLAG_MULTI_CHIP |	\
 	 MV88E6XXX_FLAG_PPU |		\
 	 MV88E6XXX_FLAG_VLANTABLE |	\
 	 MV88E6XXX_FLAG_VTU)
@@ -479,6 +490,7 @@  enum mv88e6xxx_cap {
 	(MV88E6XXX_FLAG_ATU |		\
 	 MV88E6XXX_FLAG_EEE |		\
 	 MV88E6XXX_FLAG_EEPROM |	\
+	 MV88E6XXX_FLAG_MULTI_CHIP |	\
 	 MV88E6XXX_FLAG_PORTSTATE |	\
 	 MV88E6XXX_FLAG_PPU_ACTIVE |	\
 	 MV88E6XXX_FLAG_SMI_PHY |	\
@@ -490,6 +502,7 @@  enum mv88e6xxx_cap {
 
 #define MV88E6XXX_FLAGS_FAMILY_6351	\
 	(MV88E6XXX_FLAG_ATU |		\
+	 MV88E6XXX_FLAG_MULTI_CHIP |	\
 	 MV88E6XXX_FLAG_PORTSTATE |	\
 	 MV88E6XXX_FLAG_PPU_ACTIVE |	\
 	 MV88E6XXX_FLAG_SMI_PHY |	\
@@ -503,6 +516,7 @@  enum mv88e6xxx_cap {
 	(MV88E6XXX_FLAG_ATU |		\
 	 MV88E6XXX_FLAG_EEE |		\
 	 MV88E6XXX_FLAG_EEPROM |	\
+	 MV88E6XXX_FLAG_MULTI_CHIP |	\
 	 MV88E6XXX_FLAG_PORTSTATE |	\
 	 MV88E6XXX_FLAG_PPU_ACTIVE |	\
 	 MV88E6XXX_FLAG_SMI_PHY |	\
@@ -542,6 +556,8 @@  struct mv88e6xxx_vtu_stu_entry {
 	u8	data[DSA_MAX_PORTS];
 };
 
+struct mv88e6xxx_ops;
+
 struct mv88e6xxx_priv_port {
 	struct net_device *bridge_dev;
 };
@@ -561,6 +577,7 @@  struct mv88e6xxx_priv_state {
 	/* The MII bus and the address on the bus that is used to
 	 * communication with the switch
 	 */
+	const struct mv88e6xxx_ops *smi_ops;
 	struct mii_bus *bus;
 	int sw_addr;
 
@@ -606,6 +623,13 @@  struct mv88e6xxx_priv_state {
 	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 {
 	BANK0,
 	BANK1,