15b79c72eSTristram Ha // SPDX-License-Identifier: GPL-2.0
2b987e98eSWoojung Huh /*
3b987e98eSWoojung Huh  * Microchip switch driver main logic
4b987e98eSWoojung Huh  *
542fc6a4cSTristram Ha  * Copyright (C) 2017-2019 Microchip Technology Inc.
6b987e98eSWoojung Huh  */
7b987e98eSWoojung Huh 
8b987e98eSWoojung Huh #include <linux/delay.h>
9b987e98eSWoojung Huh #include <linux/export.h>
10924352c3SMarek Vasut #include <linux/gpio/consumer.h>
11b987e98eSWoojung Huh #include <linux/kernel.h>
12b987e98eSWoojung Huh #include <linux/module.h>
13b987e98eSWoojung Huh #include <linux/platform_data/microchip-ksz.h>
14b987e98eSWoojung Huh #include <linux/phy.h>
15b987e98eSWoojung Huh #include <linux/etherdevice.h>
16b987e98eSWoojung Huh #include <linux/if_bridge.h>
17c2e86691STristram Ha #include <linux/of_net.h>
18b987e98eSWoojung Huh #include <net/dsa.h>
19b987e98eSWoojung Huh #include <net/switchdev.h>
20b987e98eSWoojung Huh 
21ffc60b55SMarek Vasut #include "ksz_common.h"
227049f9b5STristram Ha 
23c2e86691STristram Ha void ksz_update_port_member(struct ksz_device *dev, int port)
24b987e98eSWoojung Huh {
25*b3612ccdSOleksij Rempel 	struct ksz_port *p = &dev->ports[port];
26*b3612ccdSOleksij Rempel 	struct dsa_switch *ds = dev->ds;
27*b3612ccdSOleksij Rempel 	u8 port_member = 0, cpu_port;
28*b3612ccdSOleksij Rempel 	const struct dsa_port *dp;
29b987e98eSWoojung Huh 	int i;
30b987e98eSWoojung Huh 
31*b3612ccdSOleksij Rempel 	if (!dsa_is_user_port(ds, port))
32*b3612ccdSOleksij Rempel 		return;
33*b3612ccdSOleksij Rempel 
34*b3612ccdSOleksij Rempel 	dp = dsa_to_port(ds, port);
35*b3612ccdSOleksij Rempel 	cpu_port = BIT(dsa_upstream_port(ds, port));
36*b3612ccdSOleksij Rempel 
37*b3612ccdSOleksij Rempel 	for (i = 0; i < ds->num_ports; i++) {
38*b3612ccdSOleksij Rempel 		const struct dsa_port *other_dp = dsa_to_port(ds, i);
39*b3612ccdSOleksij Rempel 		struct ksz_port *other_p = &dev->ports[i];
40*b3612ccdSOleksij Rempel 		u8 val = 0;
41*b3612ccdSOleksij Rempel 
42*b3612ccdSOleksij Rempel 		if (!dsa_is_user_port(ds, i))
43c2e86691STristram Ha 			continue;
44*b3612ccdSOleksij Rempel 		if (port == i)
45*b3612ccdSOleksij Rempel 			continue;
46*b3612ccdSOleksij Rempel 		if (!dp->bridge_dev || dp->bridge_dev != other_dp->bridge_dev)
47c2e86691STristram Ha 			continue;
48b987e98eSWoojung Huh 
49*b3612ccdSOleksij Rempel 		if (other_p->stp_state == BR_STATE_FORWARDING &&
50*b3612ccdSOleksij Rempel 		    p->stp_state == BR_STATE_FORWARDING) {
51*b3612ccdSOleksij Rempel 			val |= BIT(port);
52*b3612ccdSOleksij Rempel 			port_member |= BIT(i);
53b987e98eSWoojung Huh 		}
54*b3612ccdSOleksij Rempel 
55*b3612ccdSOleksij Rempel 		dev->dev_ops->cfg_port_member(dev, i, val | cpu_port);
56*b3612ccdSOleksij Rempel 	}
57*b3612ccdSOleksij Rempel 
58*b3612ccdSOleksij Rempel 	dev->dev_ops->cfg_port_member(dev, port, port_member | cpu_port);
59b987e98eSWoojung Huh }
60c2e86691STristram Ha EXPORT_SYMBOL_GPL(ksz_update_port_member);
61b987e98eSWoojung Huh 
627c6ff470STristram Ha static void port_r_cnt(struct ksz_device *dev, int port)
637c6ff470STristram Ha {
647c6ff470STristram Ha 	struct ksz_port_mib *mib = &dev->ports[port].mib;
657c6ff470STristram Ha 	u64 *dropped;
667c6ff470STristram Ha 
677c6ff470STristram Ha 	/* Some ports may not have MIB counters before SWITCH_COUNTER_NUM. */
687c6ff470STristram Ha 	while (mib->cnt_ptr < dev->reg_mib_cnt) {
697c6ff470STristram Ha 		dev->dev_ops->r_mib_cnt(dev, port, mib->cnt_ptr,
707c6ff470STristram Ha 					&mib->counters[mib->cnt_ptr]);
717c6ff470STristram Ha 		++mib->cnt_ptr;
727c6ff470STristram Ha 	}
737c6ff470STristram Ha 
747c6ff470STristram Ha 	/* last one in storage */
757c6ff470STristram Ha 	dropped = &mib->counters[dev->mib_cnt];
767c6ff470STristram Ha 
777c6ff470STristram Ha 	/* Some ports may not have MIB counters after SWITCH_COUNTER_NUM. */
787c6ff470STristram Ha 	while (mib->cnt_ptr < dev->mib_cnt) {
797c6ff470STristram Ha 		dev->dev_ops->r_mib_pkt(dev, port, mib->cnt_ptr,
807c6ff470STristram Ha 					dropped, &mib->counters[mib->cnt_ptr]);
817c6ff470STristram Ha 		++mib->cnt_ptr;
827c6ff470STristram Ha 	}
837c6ff470STristram Ha 	mib->cnt_ptr = 0;
847c6ff470STristram Ha }
857c6ff470STristram Ha 
867c6ff470STristram Ha static void ksz_mib_read_work(struct work_struct *work)
877c6ff470STristram Ha {
887c6ff470STristram Ha 	struct ksz_device *dev = container_of(work, struct ksz_device,
89469b390eSGeorge McCollister 					      mib_read.work);
907c6ff470STristram Ha 	struct ksz_port_mib *mib;
917c6ff470STristram Ha 	struct ksz_port *p;
927c6ff470STristram Ha 	int i;
937c6ff470STristram Ha 
94c9f4633bSMichael Grzeschik 	for (i = 0; i < dev->port_cnt; i++) {
956bb9e376SRobert Hancock 		if (dsa_is_unused_port(dev->ds, i))
966bb9e376SRobert Hancock 			continue;
976bb9e376SRobert Hancock 
987c6ff470STristram Ha 		p = &dev->ports[i];
997c6ff470STristram Ha 		mib = &p->mib;
1007c6ff470STristram Ha 		mutex_lock(&mib->cnt_mutex);
1017c6ff470STristram Ha 
1027c6ff470STristram Ha 		/* Only read MIB counters when the port is told to do.
1037c6ff470STristram Ha 		 * If not, read only dropped counters when link is not up.
1047c6ff470STristram Ha 		 */
1057c6ff470STristram Ha 		if (!p->read) {
1067c6ff470STristram Ha 			const struct dsa_port *dp = dsa_to_port(dev->ds, i);
1077c6ff470STristram Ha 
1087c6ff470STristram Ha 			if (!netif_carrier_ok(dp->slave))
1097c6ff470STristram Ha 				mib->cnt_ptr = dev->reg_mib_cnt;
1107c6ff470STristram Ha 		}
1117c6ff470STristram Ha 		port_r_cnt(dev, i);
1127c6ff470STristram Ha 		p->read = false;
1137c6ff470STristram Ha 		mutex_unlock(&mib->cnt_mutex);
1147c6ff470STristram Ha 	}
1157c6ff470STristram Ha 
116469b390eSGeorge McCollister 	schedule_delayed_work(&dev->mib_read, dev->mib_read_interval);
1177c6ff470STristram Ha }
1187c6ff470STristram Ha 
1197c6ff470STristram Ha void ksz_init_mib_timer(struct ksz_device *dev)
1207c6ff470STristram Ha {
1217c6ff470STristram Ha 	int i;
1227c6ff470STristram Ha 
123469b390eSGeorge McCollister 	INIT_DELAYED_WORK(&dev->mib_read, ksz_mib_read_work);
124469b390eSGeorge McCollister 
125c9f4633bSMichael Grzeschik 	for (i = 0; i < dev->port_cnt; i++)
1267c6ff470STristram Ha 		dev->dev_ops->port_init_cnt(dev, i);
1277c6ff470STristram Ha }
1287c6ff470STristram Ha EXPORT_SYMBOL_GPL(ksz_init_mib_timer);
1297c6ff470STristram Ha 
130c2e86691STristram Ha int ksz_phy_read16(struct dsa_switch *ds, int addr, int reg)
131b987e98eSWoojung Huh {
132b987e98eSWoojung Huh 	struct ksz_device *dev = ds->priv;
133c2e86691STristram Ha 	u16 val = 0xffff;
134b987e98eSWoojung Huh 
135c2e86691STristram Ha 	dev->dev_ops->r_phy(dev, addr, reg, &val);
136b987e98eSWoojung Huh 
137b987e98eSWoojung Huh 	return val;
138b987e98eSWoojung Huh }
139c2e86691STristram Ha EXPORT_SYMBOL_GPL(ksz_phy_read16);
140b987e98eSWoojung Huh 
141c2e86691STristram Ha int ksz_phy_write16(struct dsa_switch *ds, int addr, int reg, u16 val)
142b987e98eSWoojung Huh {
143b987e98eSWoojung Huh 	struct ksz_device *dev = ds->priv;
144b987e98eSWoojung Huh 
145c2e86691STristram Ha 	dev->dev_ops->w_phy(dev, addr, reg, val);
146b987e98eSWoojung Huh 
147b987e98eSWoojung Huh 	return 0;
148b987e98eSWoojung Huh }
149c2e86691STristram Ha EXPORT_SYMBOL_GPL(ksz_phy_write16);
150b987e98eSWoojung Huh 
151143a102eSCodrin Ciubotariu void ksz_mac_link_down(struct dsa_switch *ds, int port, unsigned int mode,
152143a102eSCodrin Ciubotariu 		       phy_interface_t interface)
153c30d894bSTristram Ha {
154c30d894bSTristram Ha 	struct ksz_device *dev = ds->priv;
155c30d894bSTristram Ha 	struct ksz_port *p = &dev->ports[port];
156c30d894bSTristram Ha 
157c30d894bSTristram Ha 	/* Read all MIB counters when the link is going down. */
158c30d894bSTristram Ha 	p->read = true;
1598098bd69SChristian Eggers 	/* timer started */
1608098bd69SChristian Eggers 	if (dev->mib_read_interval)
161469b390eSGeorge McCollister 		schedule_delayed_work(&dev->mib_read, 0);
162143a102eSCodrin Ciubotariu }
163143a102eSCodrin Ciubotariu EXPORT_SYMBOL_GPL(ksz_mac_link_down);
164143a102eSCodrin Ciubotariu 
165c2e86691STristram Ha int ksz_sset_count(struct dsa_switch *ds, int port, int sset)
166b987e98eSWoojung Huh {
167b987e98eSWoojung Huh 	struct ksz_device *dev = ds->priv;
168b987e98eSWoojung Huh 
16989f09048SFlorian Fainelli 	if (sset != ETH_SS_STATS)
17089f09048SFlorian Fainelli 		return 0;
17189f09048SFlorian Fainelli 
172c2e86691STristram Ha 	return dev->mib_cnt;
173b987e98eSWoojung Huh }
174c2e86691STristram Ha EXPORT_SYMBOL_GPL(ksz_sset_count);
175b987e98eSWoojung Huh 
1767c6ff470STristram Ha void ksz_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *buf)
1777c6ff470STristram Ha {
1787c6ff470STristram Ha 	const struct dsa_port *dp = dsa_to_port(ds, port);
1797c6ff470STristram Ha 	struct ksz_device *dev = ds->priv;
1807c6ff470STristram Ha 	struct ksz_port_mib *mib;
1817c6ff470STristram Ha 
1827c6ff470STristram Ha 	mib = &dev->ports[port].mib;
1837c6ff470STristram Ha 	mutex_lock(&mib->cnt_mutex);
1847c6ff470STristram Ha 
1857c6ff470STristram Ha 	/* Only read dropped counters if no link. */
1867c6ff470STristram Ha 	if (!netif_carrier_ok(dp->slave))
1877c6ff470STristram Ha 		mib->cnt_ptr = dev->reg_mib_cnt;
1887c6ff470STristram Ha 	port_r_cnt(dev, port);
1897c6ff470STristram Ha 	memcpy(buf, mib->counters, dev->mib_cnt * sizeof(u64));
1907c6ff470STristram Ha 	mutex_unlock(&mib->cnt_mutex);
1917c6ff470STristram Ha }
1927c6ff470STristram Ha EXPORT_SYMBOL_GPL(ksz_get_ethtool_stats);
1937c6ff470STristram Ha 
194c2e86691STristram Ha int ksz_port_bridge_join(struct dsa_switch *ds, int port,
195c2e86691STristram Ha 			 struct net_device *br)
196b987e98eSWoojung Huh {
197c2e86691STristram Ha 	/* port_stp_state_set() will be called after to put the port in
198c2e86691STristram Ha 	 * appropriate state so there is no need to do anything.
199c2e86691STristram Ha 	 */
200b987e98eSWoojung Huh 
201b987e98eSWoojung Huh 	return 0;
202b987e98eSWoojung Huh }
203c2e86691STristram Ha EXPORT_SYMBOL_GPL(ksz_port_bridge_join);
204b987e98eSWoojung Huh 
205c2e86691STristram Ha void ksz_port_bridge_leave(struct dsa_switch *ds, int port,
206c2e86691STristram Ha 			   struct net_device *br)
207c2e86691STristram Ha {
208c2e86691STristram Ha 	/* port_stp_state_set() will be called after to put the port in
209c2e86691STristram Ha 	 * forwarding state so there is no need to do anything.
210c2e86691STristram Ha 	 */
211c2e86691STristram Ha }
212c2e86691STristram Ha EXPORT_SYMBOL_GPL(ksz_port_bridge_leave);
213c2e86691STristram Ha 
214c2e86691STristram Ha void ksz_port_fast_age(struct dsa_switch *ds, int port)
215c2e86691STristram Ha {
216c2e86691STristram Ha 	struct ksz_device *dev = ds->priv;
217c2e86691STristram Ha 
218c2e86691STristram Ha 	dev->dev_ops->flush_dyn_mac_table(dev, port);
219c2e86691STristram Ha }
220c2e86691STristram Ha EXPORT_SYMBOL_GPL(ksz_port_fast_age);
221c2e86691STristram Ha 
222c2e86691STristram Ha int ksz_port_fdb_dump(struct dsa_switch *ds, int port, dsa_fdb_dump_cb_t *cb,
223c2e86691STristram Ha 		      void *data)
224b987e98eSWoojung Huh {
225b987e98eSWoojung Huh 	struct ksz_device *dev = ds->priv;
226b987e98eSWoojung Huh 	int ret = 0;
227c2e86691STristram Ha 	u16 i = 0;
228c2e86691STristram Ha 	u16 entries = 0;
229c2e86691STristram Ha 	u8 timestamp = 0;
230c2e86691STristram Ha 	u8 fid;
231c2e86691STristram Ha 	u8 member;
232b987e98eSWoojung Huh 	struct alu_struct alu;
233b987e98eSWoojung Huh 
234b987e98eSWoojung Huh 	do {
235c2e86691STristram Ha 		alu.is_static = false;
236c2e86691STristram Ha 		ret = dev->dev_ops->r_dyn_mac_table(dev, i, alu.mac, &fid,
237c2e86691STristram Ha 						    &member, &timestamp,
238c2e86691STristram Ha 						    &entries);
239c2e86691STristram Ha 		if (!ret && (member & BIT(port))) {
2402bedde1aSArkadi Sharshevsky 			ret = cb(alu.mac, alu.fid, alu.is_static, data);
241b987e98eSWoojung Huh 			if (ret)
242c2e86691STristram Ha 				break;
243b987e98eSWoojung Huh 		}
244c2e86691STristram Ha 		i++;
245c2e86691STristram Ha 	} while (i < entries);
246c2e86691STristram Ha 	if (i >= entries)
247c2e86691STristram Ha 		ret = 0;
248b987e98eSWoojung Huh 
249b987e98eSWoojung Huh 	return ret;
250b987e98eSWoojung Huh }
251c2e86691STristram Ha EXPORT_SYMBOL_GPL(ksz_port_fdb_dump);
252b987e98eSWoojung Huh 
253a52b2da7SVladimir Oltean int ksz_port_mdb_add(struct dsa_switch *ds, int port,
2543709aadcSVivien Didelot 		     const struct switchdev_obj_port_mdb *mdb)
255b987e98eSWoojung Huh {
256b987e98eSWoojung Huh 	struct ksz_device *dev = ds->priv;
257c2e86691STristram Ha 	struct alu_struct alu;
258b987e98eSWoojung Huh 	int index;
259c2e86691STristram Ha 	int empty = 0;
260b987e98eSWoojung Huh 
261c2e86691STristram Ha 	alu.port_forward = 0;
262c2e86691STristram Ha 	for (index = 0; index < dev->num_statics; index++) {
263c2e86691STristram Ha 		if (!dev->dev_ops->r_sta_mac_table(dev, index, &alu)) {
264c2e86691STristram Ha 			/* Found one already in static MAC table. */
265c2e86691STristram Ha 			if (!memcmp(alu.mac, mdb->addr, ETH_ALEN) &&
266c2e86691STristram Ha 			    alu.fid == mdb->vid)
267c2e86691STristram Ha 				break;
268c2e86691STristram Ha 		/* Remember the first empty entry. */
269c2e86691STristram Ha 		} else if (!empty) {
270c2e86691STristram Ha 			empty = index + 1;
271c2e86691STristram Ha 		}
272c2e86691STristram Ha 	}
273b987e98eSWoojung Huh 
274c2e86691STristram Ha 	/* no available entry */
275c2e86691STristram Ha 	if (index == dev->num_statics && !empty)
276a52b2da7SVladimir Oltean 		return -ENOSPC;
277c2e86691STristram Ha 
278c2e86691STristram Ha 	/* add entry */
279c2e86691STristram Ha 	if (index == dev->num_statics) {
280c2e86691STristram Ha 		index = empty - 1;
281c2e86691STristram Ha 		memset(&alu, 0, sizeof(alu));
282c2e86691STristram Ha 		memcpy(alu.mac, mdb->addr, ETH_ALEN);
283c2e86691STristram Ha 		alu.is_static = true;
284c2e86691STristram Ha 	}
285c2e86691STristram Ha 	alu.port_forward |= BIT(port);
286c2e86691STristram Ha 	if (mdb->vid) {
287c2e86691STristram Ha 		alu.is_use_fid = true;
288c2e86691STristram Ha 
289c2e86691STristram Ha 		/* Need a way to map VID to FID. */
290c2e86691STristram Ha 		alu.fid = mdb->vid;
291c2e86691STristram Ha 	}
292c2e86691STristram Ha 	dev->dev_ops->w_sta_mac_table(dev, index, &alu);
293a52b2da7SVladimir Oltean 
294a52b2da7SVladimir Oltean 	return 0;
295c2e86691STristram Ha }
296c2e86691STristram Ha EXPORT_SYMBOL_GPL(ksz_port_mdb_add);
297c2e86691STristram Ha 
298c2e86691STristram Ha int ksz_port_mdb_del(struct dsa_switch *ds, int port,
299c2e86691STristram Ha 		     const struct switchdev_obj_port_mdb *mdb)
300c2e86691STristram Ha {
301c2e86691STristram Ha 	struct ksz_device *dev = ds->priv;
302c2e86691STristram Ha 	struct alu_struct alu;
303c2e86691STristram Ha 	int index;
304c2e86691STristram Ha 	int ret = 0;
305b987e98eSWoojung Huh 
306b987e98eSWoojung Huh 	for (index = 0; index < dev->num_statics; index++) {
307c2e86691STristram Ha 		if (!dev->dev_ops->r_sta_mac_table(dev, index, &alu)) {
308c2e86691STristram Ha 			/* Found one already in static MAC table. */
309c2e86691STristram Ha 			if (!memcmp(alu.mac, mdb->addr, ETH_ALEN) &&
310c2e86691STristram Ha 			    alu.fid == mdb->vid)
311b987e98eSWoojung Huh 				break;
312b987e98eSWoojung Huh 		}
313b987e98eSWoojung Huh 	}
314b987e98eSWoojung Huh 
315b987e98eSWoojung Huh 	/* no available entry */
316b987e98eSWoojung Huh 	if (index == dev->num_statics)
317b987e98eSWoojung Huh 		goto exit;
318b987e98eSWoojung Huh 
319b987e98eSWoojung Huh 	/* clear port */
320c2e86691STristram Ha 	alu.port_forward &= ~BIT(port);
321c2e86691STristram Ha 	if (!alu.port_forward)
322c2e86691STristram Ha 		alu.is_static = false;
323c2e86691STristram Ha 	dev->dev_ops->w_sta_mac_table(dev, index, &alu);
324b987e98eSWoojung Huh 
325b987e98eSWoojung Huh exit:
326b987e98eSWoojung Huh 	return ret;
327b987e98eSWoojung Huh }
328c2e86691STristram Ha EXPORT_SYMBOL_GPL(ksz_port_mdb_del);
329b987e98eSWoojung Huh 
330c2e86691STristram Ha int ksz_enable_port(struct dsa_switch *ds, int port, struct phy_device *phy)
331b987e98eSWoojung Huh {
332b987e98eSWoojung Huh 	struct ksz_device *dev = ds->priv;
333b987e98eSWoojung Huh 
33474be4babSVivien Didelot 	if (!dsa_is_user_port(ds, port))
33574be4babSVivien Didelot 		return 0;
33674be4babSVivien Didelot 
337c2e86691STristram Ha 	/* setup slave port */
338c2e86691STristram Ha 	dev->dev_ops->port_setup(dev, port, false);
339b987e98eSWoojung Huh 
340c2e86691STristram Ha 	/* port_stp_state_set() will be called after to enable the port so
341c2e86691STristram Ha 	 * there is no need to do anything.
342c2e86691STristram Ha 	 */
343b987e98eSWoojung Huh 
344b987e98eSWoojung Huh 	return 0;
345b987e98eSWoojung Huh }
346c2e86691STristram Ha EXPORT_SYMBOL_GPL(ksz_enable_port);
347b987e98eSWoojung Huh 
348ee394feaSMarek Vasut struct ksz_device *ksz_switch_alloc(struct device *base, void *priv)
349b987e98eSWoojung Huh {
350b987e98eSWoojung Huh 	struct dsa_switch *ds;
351b987e98eSWoojung Huh 	struct ksz_device *swdev;
352b987e98eSWoojung Huh 
3537e99e347SVivien Didelot 	ds = devm_kzalloc(base, sizeof(*ds), GFP_KERNEL);
354b987e98eSWoojung Huh 	if (!ds)
355b987e98eSWoojung Huh 		return NULL;
356b987e98eSWoojung Huh 
3577e99e347SVivien Didelot 	ds->dev = base;
3587e99e347SVivien Didelot 	ds->num_ports = DSA_MAX_PORTS;
3597e99e347SVivien Didelot 
360b987e98eSWoojung Huh 	swdev = devm_kzalloc(base, sizeof(*swdev), GFP_KERNEL);
361b987e98eSWoojung Huh 	if (!swdev)
362b987e98eSWoojung Huh 		return NULL;
363b987e98eSWoojung Huh 
364b987e98eSWoojung Huh 	ds->priv = swdev;
365b987e98eSWoojung Huh 	swdev->dev = base;
366b987e98eSWoojung Huh 
367b987e98eSWoojung Huh 	swdev->ds = ds;
368b987e98eSWoojung Huh 	swdev->priv = priv;
369b987e98eSWoojung Huh 
370b987e98eSWoojung Huh 	return swdev;
371b987e98eSWoojung Huh }
372b987e98eSWoojung Huh EXPORT_SYMBOL(ksz_switch_alloc);
373b987e98eSWoojung Huh 
374c2e86691STristram Ha int ksz_switch_register(struct ksz_device *dev,
375c2e86691STristram Ha 			const struct ksz_dev_ops *ops)
376b987e98eSWoojung Huh {
377912aae27SHelmut Grohne 	struct device_node *port, *ports;
3780c65b2b9SAndrew Lunn 	phy_interface_t interface;
379edecfa98SHelmut Grohne 	unsigned int port_num;
380b987e98eSWoojung Huh 	int ret;
381b987e98eSWoojung Huh 
382b987e98eSWoojung Huh 	if (dev->pdata)
383b987e98eSWoojung Huh 		dev->chip_id = dev->pdata->chip_id;
384b987e98eSWoojung Huh 
385924352c3SMarek Vasut 	dev->reset_gpio = devm_gpiod_get_optional(dev->dev, "reset",
386924352c3SMarek Vasut 						  GPIOD_OUT_LOW);
387924352c3SMarek Vasut 	if (IS_ERR(dev->reset_gpio))
388924352c3SMarek Vasut 		return PTR_ERR(dev->reset_gpio);
389924352c3SMarek Vasut 
390924352c3SMarek Vasut 	if (dev->reset_gpio) {
39122e72b5eSMarek Vasut 		gpiod_set_value_cansleep(dev->reset_gpio, 1);
3925b797980SPaul Barker 		usleep_range(10000, 12000);
39322e72b5eSMarek Vasut 		gpiod_set_value_cansleep(dev->reset_gpio, 0);
3941c45ba93SMarek Vasut 		msleep(100);
395924352c3SMarek Vasut 	}
396924352c3SMarek Vasut 
3977049f9b5STristram Ha 	mutex_init(&dev->dev_mutex);
398013572a2SMarek Vasut 	mutex_init(&dev->regmap_mutex);
399284fb78eSTristram Ha 	mutex_init(&dev->alu_mutex);
400284fb78eSTristram Ha 	mutex_init(&dev->vlan_mutex);
401284fb78eSTristram Ha 
402c2e86691STristram Ha 	dev->dev_ops = ops;
403c2e86691STristram Ha 
404c2e86691STristram Ha 	if (dev->dev_ops->detect(dev))
405b987e98eSWoojung Huh 		return -EINVAL;
406b987e98eSWoojung Huh 
407c2e86691STristram Ha 	ret = dev->dev_ops->init(dev);
408b987e98eSWoojung Huh 	if (ret)
409b987e98eSWoojung Huh 		return ret;
410b987e98eSWoojung Huh 
4118c29bebbSTristram Ha 	/* Host port interface will be self detected, or specifically set in
4128c29bebbSTristram Ha 	 * device tree.
4138c29bebbSTristram Ha 	 */
414edecfa98SHelmut Grohne 	for (port_num = 0; port_num < dev->port_cnt; ++port_num)
415edecfa98SHelmut Grohne 		dev->ports[port_num].interface = PHY_INTERFACE_MODE_NA;
416c2e86691STristram Ha 	if (dev->dev->of_node) {
4170c65b2b9SAndrew Lunn 		ret = of_get_phy_mode(dev->dev->of_node, &interface);
4180c65b2b9SAndrew Lunn 		if (ret == 0)
419edecfa98SHelmut Grohne 			dev->compat_interface = interface;
42044e53c88SChristian Eggers 		ports = of_get_child_by_name(dev->dev->of_node, "ethernet-ports");
42144e53c88SChristian Eggers 		if (!ports)
422912aae27SHelmut Grohne 			ports = of_get_child_by_name(dev->dev->of_node, "ports");
423912aae27SHelmut Grohne 		if (ports)
424912aae27SHelmut Grohne 			for_each_available_child_of_node(ports, port) {
425912aae27SHelmut Grohne 				if (of_property_read_u32(port, "reg",
426912aae27SHelmut Grohne 							 &port_num))
427edecfa98SHelmut Grohne 					continue;
42884f7e0bbSkernel test robot 				if (!(dev->port_mask & BIT(port_num))) {
42984f7e0bbSkernel test robot 					of_node_put(port);
430edecfa98SHelmut Grohne 					return -EINVAL;
43184f7e0bbSkernel test robot 				}
432912aae27SHelmut Grohne 				of_get_phy_mode(port,
433912aae27SHelmut Grohne 						&dev->ports[port_num].interface);
434edecfa98SHelmut Grohne 			}
43579c8bd15SRobert Hancock 		dev->synclko_125 = of_property_read_bool(dev->dev->of_node,
43679c8bd15SRobert Hancock 							 "microchip,synclko-125");
437c2e86691STristram Ha 	}
438c2e86691STristram Ha 
439c2e86691STristram Ha 	ret = dsa_register_switch(dev->ds);
440c2e86691STristram Ha 	if (ret) {
441c2e86691STristram Ha 		dev->dev_ops->exit(dev);
442c2e86691STristram Ha 		return ret;
443c2e86691STristram Ha 	}
444c2e86691STristram Ha 
4458098bd69SChristian Eggers 	/* Read MIB counters every 30 seconds to avoid overflow. */
4468098bd69SChristian Eggers 	dev->mib_read_interval = msecs_to_jiffies(30000);
4478098bd69SChristian Eggers 
4488098bd69SChristian Eggers 	/* Start the MIB timer. */
4498098bd69SChristian Eggers 	schedule_delayed_work(&dev->mib_read, 0);
4508098bd69SChristian Eggers 
451c2e86691STristram Ha 	return 0;
452b987e98eSWoojung Huh }
453b987e98eSWoojung Huh EXPORT_SYMBOL(ksz_switch_register);
454b987e98eSWoojung Huh 
455b987e98eSWoojung Huh void ksz_switch_remove(struct ksz_device *dev)
456b987e98eSWoojung Huh {
4577c6ff470STristram Ha 	/* timer started */
458ef1100efSArun Ramadoss 	if (dev->mib_read_interval) {
459ef1100efSArun Ramadoss 		dev->mib_read_interval = 0;
460469b390eSGeorge McCollister 		cancel_delayed_work_sync(&dev->mib_read);
461ef1100efSArun Ramadoss 	}
4627c6ff470STristram Ha 
463c2e86691STristram Ha 	dev->dev_ops->exit(dev);
464b987e98eSWoojung Huh 	dsa_unregister_switch(dev->ds);
465924352c3SMarek Vasut 
466924352c3SMarek Vasut 	if (dev->reset_gpio)
46722e72b5eSMarek Vasut 		gpiod_set_value_cansleep(dev->reset_gpio, 1);
468924352c3SMarek Vasut 
469b987e98eSWoojung Huh }
470b987e98eSWoojung Huh EXPORT_SYMBOL(ksz_switch_remove);
471b987e98eSWoojung Huh 
472b987e98eSWoojung Huh MODULE_AUTHOR("Woojung Huh <Woojung.Huh@microchip.com>");
473b987e98eSWoojung Huh MODULE_DESCRIPTION("Microchip KSZ Series Switch DSA Driver");
474b987e98eSWoojung Huh MODULE_LICENSE("GPL");
475