xref: /openbmc/linux/drivers/net/dsa/mv88e6060.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
1406a4362SAndrew Lunn // SPDX-License-Identifier: GPL-2.0+
23b158859SBen Hutchings /*
33b158859SBen Hutchings  * net/dsa/mv88e6060.c - Driver for Marvell 88e6060 switch chips
43b158859SBen Hutchings  * Copyright (c) 2008-2009 Marvell Semiconductor
53b158859SBen Hutchings  */
63b158859SBen Hutchings 
719b2f97eSBarry Grussling #include <linux/delay.h>
856c3ff9bSVivien Didelot #include <linux/etherdevice.h>
919b2f97eSBarry Grussling #include <linux/jiffies.h>
103b158859SBen Hutchings #include <linux/list.h>
112bbba277SPaul Gortmaker #include <linux/module.h>
123b158859SBen Hutchings #include <linux/netdevice.h>
133b158859SBen Hutchings #include <linux/phy.h>
143b158859SBen Hutchings #include <net/dsa.h>
156a4b2980SNeil Armstrong #include "mv88e6060.h"
163b158859SBen Hutchings 
reg_read(struct mv88e6060_priv * priv,int addr,int reg)173e8bc1b8SAndrew Lunn static int reg_read(struct mv88e6060_priv *priv, int addr, int reg)
183b158859SBen Hutchings {
19a77d43f1SAndrew Lunn 	return mdiobus_read_nested(priv->bus, priv->sw_addr + addr, reg);
203b158859SBen Hutchings }
213b158859SBen Hutchings 
reg_write(struct mv88e6060_priv * priv,int addr,int reg,u16 val)223e8bc1b8SAndrew Lunn static int reg_write(struct mv88e6060_priv *priv, int addr, int reg, u16 val)
233b158859SBen Hutchings {
24a77d43f1SAndrew Lunn 	return mdiobus_write_nested(priv->bus, priv->sw_addr + addr, reg, val);
253b158859SBen Hutchings }
263b158859SBen Hutchings 
mv88e6060_get_name(struct mii_bus * bus,int sw_addr)270209d144SVivien Didelot static const char *mv88e6060_get_name(struct mii_bus *bus, int sw_addr)
283b158859SBen Hutchings {
293b158859SBen Hutchings 	int ret;
303b158859SBen Hutchings 
316a4b2980SNeil Armstrong 	ret = mdiobus_read(bus, sw_addr + REG_PORT(0), PORT_SWITCH_ID);
323b158859SBen Hutchings 	if (ret >= 0) {
336a4b2980SNeil Armstrong 		if (ret == PORT_SWITCH_ID_6060)
343de6aa4cSGuenter Roeck 			return "Marvell 88E6060 (A0)";
356a4b2980SNeil Armstrong 		if (ret == PORT_SWITCH_ID_6060_R1 ||
366a4b2980SNeil Armstrong 		    ret == PORT_SWITCH_ID_6060_R2)
373de6aa4cSGuenter Roeck 			return "Marvell 88E6060 (B0)";
386a4b2980SNeil Armstrong 		if ((ret & PORT_SWITCH_ID_6060_MASK) == PORT_SWITCH_ID_6060)
393b158859SBen Hutchings 			return "Marvell 88E6060";
403b158859SBen Hutchings 	}
413b158859SBen Hutchings 
423b158859SBen Hutchings 	return NULL;
433b158859SBen Hutchings }
443b158859SBen Hutchings 
mv88e6060_get_tag_protocol(struct dsa_switch * ds,int port,enum dsa_tag_protocol m)455ed4e3ebSFlorian Fainelli static enum dsa_tag_protocol mv88e6060_get_tag_protocol(struct dsa_switch *ds,
464d776482SFlorian Fainelli 							int port,
474d776482SFlorian Fainelli 							enum dsa_tag_protocol m)
487b314362SAndrew Lunn {
497b314362SAndrew Lunn 	return DSA_TAG_PROTO_TRAILER;
507b314362SAndrew Lunn }
517b314362SAndrew Lunn 
mv88e6060_switch_reset(struct mv88e6060_priv * priv)523e8bc1b8SAndrew Lunn static int mv88e6060_switch_reset(struct mv88e6060_priv *priv)
533b158859SBen Hutchings {
543b158859SBen Hutchings 	int i;
553b158859SBen Hutchings 	int ret;
5619b2f97eSBarry Grussling 	unsigned long timeout;
573b158859SBen Hutchings 
583675c8d7SBarry Grussling 	/* Set all ports to the disabled state. */
596a4b2980SNeil Armstrong 	for (i = 0; i < MV88E6060_PORTS; i++) {
601ba22bf5SAndrew Lunn 		ret = reg_read(priv, REG_PORT(i), PORT_CONTROL);
611ba22bf5SAndrew Lunn 		if (ret < 0)
621ba22bf5SAndrew Lunn 			return ret;
63c4362c37SAndrew Lunn 		ret = reg_write(priv, REG_PORT(i), PORT_CONTROL,
646a4b2980SNeil Armstrong 				ret & ~PORT_CONTROL_STATE_MASK);
65c4362c37SAndrew Lunn 		if (ret)
66c4362c37SAndrew Lunn 			return ret;
673b158859SBen Hutchings 	}
683b158859SBen Hutchings 
693675c8d7SBarry Grussling 	/* Wait for transmit queues to drain. */
7019b2f97eSBarry Grussling 	usleep_range(2000, 4000);
713b158859SBen Hutchings 
723675c8d7SBarry Grussling 	/* Reset the switch. */
73c4362c37SAndrew Lunn 	ret = reg_write(priv, REG_GLOBAL, GLOBAL_ATU_CONTROL,
746a4b2980SNeil Armstrong 			GLOBAL_ATU_CONTROL_SWRESET |
75a7451560SAnderson Luiz Alves 			GLOBAL_ATU_CONTROL_LEARNDIS);
76c4362c37SAndrew Lunn 	if (ret)
77c4362c37SAndrew Lunn 		return ret;
783b158859SBen Hutchings 
793675c8d7SBarry Grussling 	/* Wait up to one second for reset to complete. */
8019b2f97eSBarry Grussling 	timeout = jiffies + 1 * HZ;
8119b2f97eSBarry Grussling 	while (time_before(jiffies, timeout)) {
821ba22bf5SAndrew Lunn 		ret = reg_read(priv, REG_GLOBAL, GLOBAL_STATUS);
831ba22bf5SAndrew Lunn 		if (ret < 0)
841ba22bf5SAndrew Lunn 			return ret;
851ba22bf5SAndrew Lunn 
866a4b2980SNeil Armstrong 		if (ret & GLOBAL_STATUS_INIT_READY)
873b158859SBen Hutchings 			break;
883b158859SBen Hutchings 
8919b2f97eSBarry Grussling 		usleep_range(1000, 2000);
903b158859SBen Hutchings 	}
9119b2f97eSBarry Grussling 	if (time_after(jiffies, timeout))
923b158859SBen Hutchings 		return -ETIMEDOUT;
933b158859SBen Hutchings 
943b158859SBen Hutchings 	return 0;
953b158859SBen Hutchings }
963b158859SBen Hutchings 
mv88e6060_setup_global(struct mv88e6060_priv * priv)973e8bc1b8SAndrew Lunn static int mv88e6060_setup_global(struct mv88e6060_priv *priv)
983b158859SBen Hutchings {
99c4362c37SAndrew Lunn 	int ret;
100c4362c37SAndrew Lunn 
1013675c8d7SBarry Grussling 	/* Disable discarding of frames with excessive collisions,
1023b158859SBen Hutchings 	 * set the maximum frame size to 1536 bytes, and mask all
1033b158859SBen Hutchings 	 * interrupt sources.
1043b158859SBen Hutchings 	 */
105c4362c37SAndrew Lunn 	ret = reg_write(priv, REG_GLOBAL, GLOBAL_CONTROL,
106c4362c37SAndrew Lunn 			GLOBAL_CONTROL_MAX_FRAME_1536);
107c4362c37SAndrew Lunn 	if (ret)
108c4362c37SAndrew Lunn 		return ret;
1093b158859SBen Hutchings 
110a7451560SAnderson Luiz Alves 	/* Disable automatic address learning.
1113b158859SBen Hutchings 	 */
112c4362c37SAndrew Lunn 	return reg_write(priv, REG_GLOBAL, GLOBAL_ATU_CONTROL,
113a7451560SAnderson Luiz Alves 			 GLOBAL_ATU_CONTROL_LEARNDIS);
1143b158859SBen Hutchings }
1153b158859SBen Hutchings 
mv88e6060_setup_port(struct mv88e6060_priv * priv,int p)1163e8bc1b8SAndrew Lunn static int mv88e6060_setup_port(struct mv88e6060_priv *priv, int p)
1173b158859SBen Hutchings {
1183b158859SBen Hutchings 	int addr = REG_PORT(p);
119c4362c37SAndrew Lunn 	int ret;
1203b158859SBen Hutchings 
121246bbf2fSSergei Antonov 	if (dsa_is_unused_port(priv->ds, p))
122246bbf2fSSergei Antonov 		return 0;
123246bbf2fSSergei Antonov 
1243675c8d7SBarry Grussling 	/* Do not force flow control, disable Ingress and Egress
1253b158859SBen Hutchings 	 * Header tagging, disable VLAN tunneling, and set the port
1263b158859SBen Hutchings 	 * state to Forwarding.  Additionally, if this is the CPU
1273b158859SBen Hutchings 	 * port, enable Ingress and Egress Trailer tagging mode.
1283b158859SBen Hutchings 	 */
129c4362c37SAndrew Lunn 	ret = reg_write(priv, addr, PORT_CONTROL,
1303e8bc1b8SAndrew Lunn 			dsa_is_cpu_port(priv->ds, p) ?
1316a4b2980SNeil Armstrong 			PORT_CONTROL_TRAILER |
1326a4b2980SNeil Armstrong 			PORT_CONTROL_INGRESS_MODE |
1336a4b2980SNeil Armstrong 			PORT_CONTROL_STATE_FORWARDING :
1346a4b2980SNeil Armstrong 			PORT_CONTROL_STATE_FORWARDING);
135c4362c37SAndrew Lunn 	if (ret)
136c4362c37SAndrew Lunn 		return ret;
1373b158859SBen Hutchings 
1383675c8d7SBarry Grussling 	/* Port based VLAN map: give each port its own address
1393b158859SBen Hutchings 	 * database, allow the CPU port to talk to each of the 'real'
1403b158859SBen Hutchings 	 * ports, and allow each of the 'real' ports to only talk to
1413b158859SBen Hutchings 	 * the CPU port.
1423b158859SBen Hutchings 	 */
143c4362c37SAndrew Lunn 	ret = reg_write(priv, addr, PORT_VLAN_MAP,
1446a4b2980SNeil Armstrong 			((p & 0xf) << PORT_VLAN_MAP_DBNUM_SHIFT) |
145c4362c37SAndrew Lunn 			(dsa_is_cpu_port(priv->ds, p) ?
146c4362c37SAndrew Lunn 			 dsa_user_ports(priv->ds) :
1473e8bc1b8SAndrew Lunn 			 BIT(dsa_to_port(priv->ds, p)->cpu_dp->index)));
148c4362c37SAndrew Lunn 	if (ret)
149c4362c37SAndrew Lunn 		return ret;
1503b158859SBen Hutchings 
1513675c8d7SBarry Grussling 	/* Port Association Vector: when learning source addresses
1523b158859SBen Hutchings 	 * of packets, add the address to the address database using
1533b158859SBen Hutchings 	 * a port bitmap that has only the bit for this port set and
1543b158859SBen Hutchings 	 * the other bits clear.
1553b158859SBen Hutchings 	 */
156c4362c37SAndrew Lunn 	return reg_write(priv, addr, PORT_ASSOC_VECTOR, BIT(p));
1573b158859SBen Hutchings }
1583b158859SBen Hutchings 
mv88e6060_setup_addr(struct mv88e6060_priv * priv)1593e8bc1b8SAndrew Lunn static int mv88e6060_setup_addr(struct mv88e6060_priv *priv)
16056c3ff9bSVivien Didelot {
16156c3ff9bSVivien Didelot 	u8 addr[ETH_ALEN];
162c4362c37SAndrew Lunn 	int ret;
16356c3ff9bSVivien Didelot 	u16 val;
16456c3ff9bSVivien Didelot 
16556c3ff9bSVivien Didelot 	eth_random_addr(addr);
16656c3ff9bSVivien Didelot 
16756c3ff9bSVivien Didelot 	val = addr[0] << 8 | addr[1];
16856c3ff9bSVivien Didelot 
16956c3ff9bSVivien Didelot 	/* The multicast bit is always transmitted as a zero, so the switch uses
17056c3ff9bSVivien Didelot 	 * bit 8 for "DiffAddr", where 0 means all ports transmit the same SA.
17156c3ff9bSVivien Didelot 	 */
17256c3ff9bSVivien Didelot 	val &= 0xfeff;
17356c3ff9bSVivien Didelot 
174c4362c37SAndrew Lunn 	ret = reg_write(priv, REG_GLOBAL, GLOBAL_MAC_01, val);
175c4362c37SAndrew Lunn 	if (ret)
176c4362c37SAndrew Lunn 		return ret;
17756c3ff9bSVivien Didelot 
178c4362c37SAndrew Lunn 	ret = reg_write(priv, REG_GLOBAL, GLOBAL_MAC_23,
179c4362c37SAndrew Lunn 			(addr[2] << 8) | addr[3]);
180c4362c37SAndrew Lunn 	if (ret)
181c4362c37SAndrew Lunn 		return ret;
182c4362c37SAndrew Lunn 
183c4362c37SAndrew Lunn 	return reg_write(priv, REG_GLOBAL, GLOBAL_MAC_45,
184c4362c37SAndrew Lunn 			 (addr[4] << 8) | addr[5]);
18556c3ff9bSVivien Didelot }
18656c3ff9bSVivien Didelot 
mv88e6060_setup(struct dsa_switch * ds)1873b158859SBen Hutchings static int mv88e6060_setup(struct dsa_switch *ds)
1883b158859SBen Hutchings {
1893e8bc1b8SAndrew Lunn 	struct mv88e6060_priv *priv = ds->priv;
1903b158859SBen Hutchings 	int ret;
191a77d43f1SAndrew Lunn 	int i;
1923b158859SBen Hutchings 
1933e8bc1b8SAndrew Lunn 	priv->ds = ds;
1943e8bc1b8SAndrew Lunn 
1953e8bc1b8SAndrew Lunn 	ret = mv88e6060_switch_reset(priv);
1963b158859SBen Hutchings 	if (ret < 0)
1973b158859SBen Hutchings 		return ret;
1983b158859SBen Hutchings 
1993b158859SBen Hutchings 	/* @@@ initialise atu */
2003b158859SBen Hutchings 
2013e8bc1b8SAndrew Lunn 	ret = mv88e6060_setup_global(priv);
2023b158859SBen Hutchings 	if (ret < 0)
2033b158859SBen Hutchings 		return ret;
2043b158859SBen Hutchings 
2053e8bc1b8SAndrew Lunn 	ret = mv88e6060_setup_addr(priv);
20656c3ff9bSVivien Didelot 	if (ret < 0)
20756c3ff9bSVivien Didelot 		return ret;
20856c3ff9bSVivien Didelot 
2096a4b2980SNeil Armstrong 	for (i = 0; i < MV88E6060_PORTS; i++) {
2103e8bc1b8SAndrew Lunn 		ret = mv88e6060_setup_port(priv, i);
2113b158859SBen Hutchings 		if (ret < 0)
2123b158859SBen Hutchings 			return ret;
2133b158859SBen Hutchings 	}
2143b158859SBen Hutchings 
2153b158859SBen Hutchings 	return 0;
2163b158859SBen Hutchings }
2173b158859SBen Hutchings 
mv88e6060_port_to_phy_addr(int port)2183b158859SBen Hutchings static int mv88e6060_port_to_phy_addr(int port)
2193b158859SBen Hutchings {
2206a4b2980SNeil Armstrong 	if (port >= 0 && port < MV88E6060_PORTS)
2213b158859SBen Hutchings 		return port;
2223b158859SBen Hutchings 	return -1;
2233b158859SBen Hutchings }
2243b158859SBen Hutchings 
mv88e6060_phy_read(struct dsa_switch * ds,int port,int regnum)2253b158859SBen Hutchings static int mv88e6060_phy_read(struct dsa_switch *ds, int port, int regnum)
2263b158859SBen Hutchings {
2273e8bc1b8SAndrew Lunn 	struct mv88e6060_priv *priv = ds->priv;
2283b158859SBen Hutchings 	int addr;
2293b158859SBen Hutchings 
2303b158859SBen Hutchings 	addr = mv88e6060_port_to_phy_addr(port);
2313b158859SBen Hutchings 	if (addr == -1)
2323b158859SBen Hutchings 		return 0xffff;
2333b158859SBen Hutchings 
2343e8bc1b8SAndrew Lunn 	return reg_read(priv, addr, regnum);
2353b158859SBen Hutchings }
2363b158859SBen Hutchings 
2373b158859SBen Hutchings static int
mv88e6060_phy_write(struct dsa_switch * ds,int port,int regnum,u16 val)2383b158859SBen Hutchings mv88e6060_phy_write(struct dsa_switch *ds, int port, int regnum, u16 val)
2393b158859SBen Hutchings {
2403e8bc1b8SAndrew Lunn 	struct mv88e6060_priv *priv = ds->priv;
2413b158859SBen Hutchings 	int addr;
2423b158859SBen Hutchings 
2433b158859SBen Hutchings 	addr = mv88e6060_port_to_phy_addr(port);
2443b158859SBen Hutchings 	if (addr == -1)
2453b158859SBen Hutchings 		return 0xffff;
2463b158859SBen Hutchings 
2473e8bc1b8SAndrew Lunn 	return reg_write(priv, addr, regnum, val);
2483b158859SBen Hutchings }
2493b158859SBen Hutchings 
mv88e6060_phylink_get_caps(struct dsa_switch * ds,int port,struct phylink_config * config)250*479b322eSRussell King (Oracle) static void mv88e6060_phylink_get_caps(struct dsa_switch *ds, int port,
251*479b322eSRussell King (Oracle) 				       struct phylink_config *config)
252*479b322eSRussell King (Oracle) {
253*479b322eSRussell King (Oracle) 	unsigned long *interfaces = config->supported_interfaces;
254*479b322eSRussell King (Oracle) 	struct mv88e6060_priv *priv = ds->priv;
255*479b322eSRussell King (Oracle) 	int addr = REG_PORT(port);
256*479b322eSRussell King (Oracle) 	int ret;
257*479b322eSRussell King (Oracle) 
258*479b322eSRussell King (Oracle) 	ret = reg_read(priv, addr, PORT_STATUS);
259*479b322eSRussell King (Oracle) 	if (ret < 0) {
260*479b322eSRussell King (Oracle) 		dev_err(ds->dev,
261*479b322eSRussell King (Oracle) 			"port %d: unable to read status register: %pe\n",
262*479b322eSRussell King (Oracle) 			port, ERR_PTR(ret));
263*479b322eSRussell King (Oracle) 		return;
264*479b322eSRussell King (Oracle) 	}
265*479b322eSRussell King (Oracle) 
266*479b322eSRussell King (Oracle) 	/* If the port is configured in SNI mode (acts as a 10Mbps PHY),
267*479b322eSRussell King (Oracle) 	 * it should have phy-mode = "sni", but that doesn't yet exist, so
268*479b322eSRussell King (Oracle) 	 * forcibly fail validation until the need arises to introduce it.
269*479b322eSRussell King (Oracle) 	 */
270*479b322eSRussell King (Oracle) 	if (!(ret & PORT_STATUS_PORTMODE)) {
271*479b322eSRussell King (Oracle) 		dev_warn(ds->dev, "port %d: SNI mode not supported\n", port);
272*479b322eSRussell King (Oracle) 		return;
273*479b322eSRussell King (Oracle) 	}
274*479b322eSRussell King (Oracle) 
275*479b322eSRussell King (Oracle) 	config->mac_capabilities = MAC_100 | MAC_10 | MAC_SYM_PAUSE;
276*479b322eSRussell King (Oracle) 
277*479b322eSRussell King (Oracle) 	if (port >= 4) {
278*479b322eSRussell King (Oracle) 		/* Ports 4 and 5 can support MII, REVMII and REVRMII modes */
279*479b322eSRussell King (Oracle) 		__set_bit(PHY_INTERFACE_MODE_MII, interfaces);
280*479b322eSRussell King (Oracle) 		__set_bit(PHY_INTERFACE_MODE_REVMII, interfaces);
281*479b322eSRussell King (Oracle) 		__set_bit(PHY_INTERFACE_MODE_REVRMII, interfaces);
282*479b322eSRussell King (Oracle) 	}
283*479b322eSRussell King (Oracle) 	if (port <= 4) {
284*479b322eSRussell King (Oracle) 		/* Ports 0 to 3 have internal PHYs, and port 4 can optionally
285*479b322eSRussell King (Oracle) 		 * use an internal PHY.
286*479b322eSRussell King (Oracle) 		 */
287*479b322eSRussell King (Oracle) 		/* Internal PHY */
288*479b322eSRussell King (Oracle) 		__set_bit(PHY_INTERFACE_MODE_INTERNAL, interfaces);
289*479b322eSRussell King (Oracle) 		/* Default phylib interface mode */
290*479b322eSRussell King (Oracle) 		__set_bit(PHY_INTERFACE_MODE_GMII, interfaces);
291*479b322eSRussell King (Oracle) 	}
292*479b322eSRussell King (Oracle) }
293*479b322eSRussell King (Oracle) 
294a82f67afSFlorian Fainelli static const struct dsa_switch_ops mv88e6060_switch_ops = {
2957b314362SAndrew Lunn 	.get_tag_protocol = mv88e6060_get_tag_protocol,
2963b158859SBen Hutchings 	.setup		= mv88e6060_setup,
2973b158859SBen Hutchings 	.phy_read	= mv88e6060_phy_read,
2983b158859SBen Hutchings 	.phy_write	= mv88e6060_phy_write,
299*479b322eSRussell King (Oracle) 	.phylink_get_caps = mv88e6060_phylink_get_caps,
3003b158859SBen Hutchings };
3013b158859SBen Hutchings 
mv88e6060_probe(struct mdio_device * mdiodev)30227761760SAndrew Lunn static int mv88e6060_probe(struct mdio_device *mdiodev)
30327761760SAndrew Lunn {
30427761760SAndrew Lunn 	struct device *dev = &mdiodev->dev;
30527761760SAndrew Lunn 	struct mv88e6060_priv *priv;
30627761760SAndrew Lunn 	struct dsa_switch *ds;
30727761760SAndrew Lunn 	const char *name;
30827761760SAndrew Lunn 
30927761760SAndrew Lunn 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
31027761760SAndrew Lunn 	if (!priv)
31127761760SAndrew Lunn 		return -ENOMEM;
31227761760SAndrew Lunn 
31327761760SAndrew Lunn 	priv->bus = mdiodev->bus;
31427761760SAndrew Lunn 	priv->sw_addr = mdiodev->addr;
31527761760SAndrew Lunn 
31627761760SAndrew Lunn 	name = mv88e6060_get_name(priv->bus, priv->sw_addr);
31727761760SAndrew Lunn 	if (!name)
31827761760SAndrew Lunn 		return -ENODEV;
31927761760SAndrew Lunn 
32027761760SAndrew Lunn 	dev_info(dev, "switch %s detected\n", name);
32127761760SAndrew Lunn 
3227e99e347SVivien Didelot 	ds = devm_kzalloc(dev, sizeof(*ds), GFP_KERNEL);
32327761760SAndrew Lunn 	if (!ds)
32427761760SAndrew Lunn 		return -ENOMEM;
32527761760SAndrew Lunn 
3267e99e347SVivien Didelot 	ds->dev = dev;
3277e99e347SVivien Didelot 	ds->num_ports = MV88E6060_PORTS;
32827761760SAndrew Lunn 	ds->priv = priv;
32927761760SAndrew Lunn 	ds->dev = dev;
33027761760SAndrew Lunn 	ds->ops = &mv88e6060_switch_ops;
33127761760SAndrew Lunn 
33227761760SAndrew Lunn 	dev_set_drvdata(dev, ds);
33327761760SAndrew Lunn 
33427761760SAndrew Lunn 	return dsa_register_switch(ds);
33527761760SAndrew Lunn }
33627761760SAndrew Lunn 
mv88e6060_remove(struct mdio_device * mdiodev)33727761760SAndrew Lunn static void mv88e6060_remove(struct mdio_device *mdiodev)
33827761760SAndrew Lunn {
33927761760SAndrew Lunn 	struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
34027761760SAndrew Lunn 
3410650bf52SVladimir Oltean 	if (!ds)
3420650bf52SVladimir Oltean 		return;
3430650bf52SVladimir Oltean 
34427761760SAndrew Lunn 	dsa_unregister_switch(ds);
3450650bf52SVladimir Oltean }
3460650bf52SVladimir Oltean 
mv88e6060_shutdown(struct mdio_device * mdiodev)3470650bf52SVladimir Oltean static void mv88e6060_shutdown(struct mdio_device *mdiodev)
3480650bf52SVladimir Oltean {
3490650bf52SVladimir Oltean 	struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
3500650bf52SVladimir Oltean 
3510650bf52SVladimir Oltean 	if (!ds)
3520650bf52SVladimir Oltean 		return;
3530650bf52SVladimir Oltean 
3540650bf52SVladimir Oltean 	dsa_switch_shutdown(ds);
3550650bf52SVladimir Oltean 
3560650bf52SVladimir Oltean 	dev_set_drvdata(&mdiodev->dev, NULL);
35727761760SAndrew Lunn }
35827761760SAndrew Lunn 
35927761760SAndrew Lunn static const struct of_device_id mv88e6060_of_match[] = {
36027761760SAndrew Lunn 	{
36127761760SAndrew Lunn 		.compatible = "marvell,mv88e6060",
36227761760SAndrew Lunn 	},
36327761760SAndrew Lunn 	{ /* sentinel */ },
36427761760SAndrew Lunn };
36527761760SAndrew Lunn 
36627761760SAndrew Lunn static struct mdio_driver mv88e6060_driver = {
36727761760SAndrew Lunn 	.probe	= mv88e6060_probe,
36827761760SAndrew Lunn 	.remove = mv88e6060_remove,
3690650bf52SVladimir Oltean 	.shutdown = mv88e6060_shutdown,
37027761760SAndrew Lunn 	.mdiodrv.driver = {
37127761760SAndrew Lunn 		.name = "mv88e6060",
37227761760SAndrew Lunn 		.of_match_table = mv88e6060_of_match,
37327761760SAndrew Lunn 	},
37427761760SAndrew Lunn };
37527761760SAndrew Lunn 
3762f8e7eceSAndrew Lunn mdio_module_driver(mv88e6060_driver);
3773b158859SBen Hutchings 
3783b158859SBen Hutchings MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>");
3793b158859SBen Hutchings MODULE_DESCRIPTION("Driver for Marvell 88E6060 ethernet switch chip");
3803b158859SBen Hutchings MODULE_LICENSE("GPL");
3813b158859SBen Hutchings MODULE_ALIAS("platform:mv88e6060");
382