xref: /linux/drivers/net/mdio/mdio-mux.c (revision 10ad63da)
1a9770eacSAndrew Lunn // SPDX-License-Identifier: GPL-2.0
2a9770eacSAndrew Lunn /*
3a9770eacSAndrew Lunn  * Copyright (C) 2011, 2012 Cavium, Inc.
4a9770eacSAndrew Lunn  */
5a9770eacSAndrew Lunn 
6a9770eacSAndrew Lunn #include <linux/device.h>
71bf34366SCalvin Johnson #include <linux/mdio-mux.h>
8a9770eacSAndrew Lunn #include <linux/module.h>
91bf34366SCalvin Johnson #include <linux/of_mdio.h>
10a9770eacSAndrew Lunn #include <linux/phy.h>
111bf34366SCalvin Johnson #include <linux/platform_device.h>
12a9770eacSAndrew Lunn 
13a9770eacSAndrew Lunn #define DRV_DESCRIPTION "MDIO bus multiplexer driver"
14a9770eacSAndrew Lunn 
15a9770eacSAndrew Lunn struct mdio_mux_child_bus;
16a9770eacSAndrew Lunn 
17a9770eacSAndrew Lunn struct mdio_mux_parent_bus {
18a9770eacSAndrew Lunn 	struct mii_bus *mii_bus;
19a9770eacSAndrew Lunn 	int current_child;
20a9770eacSAndrew Lunn 	int parent_id;
21a9770eacSAndrew Lunn 	void *switch_data;
22a9770eacSAndrew Lunn 	int (*switch_fn)(int current_child, int desired_child, void *data);
23a9770eacSAndrew Lunn 
24a9770eacSAndrew Lunn 	/* List of our children linked through their next fields. */
25a9770eacSAndrew Lunn 	struct mdio_mux_child_bus *children;
26a9770eacSAndrew Lunn };
27a9770eacSAndrew Lunn 
28a9770eacSAndrew Lunn struct mdio_mux_child_bus {
29a9770eacSAndrew Lunn 	struct mii_bus *mii_bus;
30a9770eacSAndrew Lunn 	struct mdio_mux_parent_bus *parent;
31a9770eacSAndrew Lunn 	struct mdio_mux_child_bus *next;
32a9770eacSAndrew Lunn 	int bus_number;
33a9770eacSAndrew Lunn };
34a9770eacSAndrew Lunn 
35a9770eacSAndrew Lunn /*
36a9770eacSAndrew Lunn  * The parent bus' lock is used to order access to the switch_fn.
37a9770eacSAndrew Lunn  */
mdio_mux_read(struct mii_bus * bus,int phy_id,int regnum)38a9770eacSAndrew Lunn static int mdio_mux_read(struct mii_bus *bus, int phy_id, int regnum)
39a9770eacSAndrew Lunn {
40a9770eacSAndrew Lunn 	struct mdio_mux_child_bus *cb = bus->priv;
41a9770eacSAndrew Lunn 	struct mdio_mux_parent_bus *pb = cb->parent;
42a9770eacSAndrew Lunn 	int r;
43a9770eacSAndrew Lunn 
44a9770eacSAndrew Lunn 	mutex_lock_nested(&pb->mii_bus->mdio_lock, MDIO_MUTEX_MUX);
45a9770eacSAndrew Lunn 	r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
46a9770eacSAndrew Lunn 	if (r)
47a9770eacSAndrew Lunn 		goto out;
48a9770eacSAndrew Lunn 
49a9770eacSAndrew Lunn 	pb->current_child = cb->bus_number;
50a9770eacSAndrew Lunn 
51a9770eacSAndrew Lunn 	r = pb->mii_bus->read(pb->mii_bus, phy_id, regnum);
52a9770eacSAndrew Lunn out:
53a9770eacSAndrew Lunn 	mutex_unlock(&pb->mii_bus->mdio_lock);
54a9770eacSAndrew Lunn 
55a9770eacSAndrew Lunn 	return r;
56a9770eacSAndrew Lunn }
57a9770eacSAndrew Lunn 
mdio_mux_read_c45(struct mii_bus * bus,int phy_id,int dev_addr,int regnum)581f9f2143SVladimir Oltean static int mdio_mux_read_c45(struct mii_bus *bus, int phy_id, int dev_addr,
591f9f2143SVladimir Oltean 			     int regnum)
601f9f2143SVladimir Oltean {
611f9f2143SVladimir Oltean 	struct mdio_mux_child_bus *cb = bus->priv;
621f9f2143SVladimir Oltean 	struct mdio_mux_parent_bus *pb = cb->parent;
631f9f2143SVladimir Oltean 	int r;
641f9f2143SVladimir Oltean 
651f9f2143SVladimir Oltean 	mutex_lock_nested(&pb->mii_bus->mdio_lock, MDIO_MUTEX_MUX);
661f9f2143SVladimir Oltean 	r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
671f9f2143SVladimir Oltean 	if (r)
681f9f2143SVladimir Oltean 		goto out;
691f9f2143SVladimir Oltean 
701f9f2143SVladimir Oltean 	pb->current_child = cb->bus_number;
711f9f2143SVladimir Oltean 
721f9f2143SVladimir Oltean 	r = pb->mii_bus->read_c45(pb->mii_bus, phy_id, dev_addr, regnum);
731f9f2143SVladimir Oltean out:
741f9f2143SVladimir Oltean 	mutex_unlock(&pb->mii_bus->mdio_lock);
751f9f2143SVladimir Oltean 
761f9f2143SVladimir Oltean 	return r;
771f9f2143SVladimir Oltean }
781f9f2143SVladimir Oltean 
79a9770eacSAndrew Lunn /*
80a9770eacSAndrew Lunn  * The parent bus' lock is used to order access to the switch_fn.
81a9770eacSAndrew Lunn  */
mdio_mux_write(struct mii_bus * bus,int phy_id,int regnum,u16 val)82a9770eacSAndrew Lunn static int mdio_mux_write(struct mii_bus *bus, int phy_id,
83a9770eacSAndrew Lunn 			  int regnum, u16 val)
84a9770eacSAndrew Lunn {
85a9770eacSAndrew Lunn 	struct mdio_mux_child_bus *cb = bus->priv;
86a9770eacSAndrew Lunn 	struct mdio_mux_parent_bus *pb = cb->parent;
87a9770eacSAndrew Lunn 
88a9770eacSAndrew Lunn 	int r;
89a9770eacSAndrew Lunn 
90a9770eacSAndrew Lunn 	mutex_lock_nested(&pb->mii_bus->mdio_lock, MDIO_MUTEX_MUX);
91a9770eacSAndrew Lunn 	r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
92a9770eacSAndrew Lunn 	if (r)
93a9770eacSAndrew Lunn 		goto out;
94a9770eacSAndrew Lunn 
95a9770eacSAndrew Lunn 	pb->current_child = cb->bus_number;
96a9770eacSAndrew Lunn 
97a9770eacSAndrew Lunn 	r = pb->mii_bus->write(pb->mii_bus, phy_id, regnum, val);
98a9770eacSAndrew Lunn out:
99a9770eacSAndrew Lunn 	mutex_unlock(&pb->mii_bus->mdio_lock);
100a9770eacSAndrew Lunn 
101a9770eacSAndrew Lunn 	return r;
102a9770eacSAndrew Lunn }
103a9770eacSAndrew Lunn 
mdio_mux_write_c45(struct mii_bus * bus,int phy_id,int dev_addr,int regnum,u16 val)1041f9f2143SVladimir Oltean static int mdio_mux_write_c45(struct mii_bus *bus, int phy_id, int dev_addr,
1051f9f2143SVladimir Oltean 			      int regnum, u16 val)
1061f9f2143SVladimir Oltean {
1071f9f2143SVladimir Oltean 	struct mdio_mux_child_bus *cb = bus->priv;
1081f9f2143SVladimir Oltean 	struct mdio_mux_parent_bus *pb = cb->parent;
1091f9f2143SVladimir Oltean 
1101f9f2143SVladimir Oltean 	int r;
1111f9f2143SVladimir Oltean 
1121f9f2143SVladimir Oltean 	mutex_lock_nested(&pb->mii_bus->mdio_lock, MDIO_MUTEX_MUX);
1131f9f2143SVladimir Oltean 	r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
1141f9f2143SVladimir Oltean 	if (r)
1151f9f2143SVladimir Oltean 		goto out;
1161f9f2143SVladimir Oltean 
1171f9f2143SVladimir Oltean 	pb->current_child = cb->bus_number;
1181f9f2143SVladimir Oltean 
1191f9f2143SVladimir Oltean 	r = pb->mii_bus->write_c45(pb->mii_bus, phy_id, dev_addr, regnum, val);
1201f9f2143SVladimir Oltean out:
1211f9f2143SVladimir Oltean 	mutex_unlock(&pb->mii_bus->mdio_lock);
1221f9f2143SVladimir Oltean 
1231f9f2143SVladimir Oltean 	return r;
1241f9f2143SVladimir Oltean }
1251f9f2143SVladimir Oltean 
126a9770eacSAndrew Lunn static int parent_count;
127a9770eacSAndrew Lunn 
mdio_mux_uninit_children(struct mdio_mux_parent_bus * pb)12899d81e94SSaravana Kannan static void mdio_mux_uninit_children(struct mdio_mux_parent_bus *pb)
12999d81e94SSaravana Kannan {
13099d81e94SSaravana Kannan 	struct mdio_mux_child_bus *cb = pb->children;
13199d81e94SSaravana Kannan 
13299d81e94SSaravana Kannan 	while (cb) {
13399d81e94SSaravana Kannan 		mdiobus_unregister(cb->mii_bus);
13499d81e94SSaravana Kannan 		mdiobus_free(cb->mii_bus);
13599d81e94SSaravana Kannan 		cb = cb->next;
13699d81e94SSaravana Kannan 	}
13799d81e94SSaravana Kannan }
13899d81e94SSaravana Kannan 
mdio_mux_init(struct device * dev,struct device_node * mux_node,int (* switch_fn)(int cur,int desired,void * data),void ** mux_handle,void * data,struct mii_bus * mux_bus)139a9770eacSAndrew Lunn int mdio_mux_init(struct device *dev,
140a9770eacSAndrew Lunn 		  struct device_node *mux_node,
141a9770eacSAndrew Lunn 		  int (*switch_fn)(int cur, int desired, void *data),
142a9770eacSAndrew Lunn 		  void **mux_handle,
143a9770eacSAndrew Lunn 		  void *data,
144a9770eacSAndrew Lunn 		  struct mii_bus *mux_bus)
145a9770eacSAndrew Lunn {
146a9770eacSAndrew Lunn 	struct device_node *parent_bus_node;
147a9770eacSAndrew Lunn 	struct device_node *child_bus_node;
148a9770eacSAndrew Lunn 	int r, ret_val;
149a9770eacSAndrew Lunn 	struct mii_bus *parent_bus;
150a9770eacSAndrew Lunn 	struct mdio_mux_parent_bus *pb;
151a9770eacSAndrew Lunn 	struct mdio_mux_child_bus *cb;
152a9770eacSAndrew Lunn 
153a9770eacSAndrew Lunn 	if (!mux_node)
154a9770eacSAndrew Lunn 		return -ENODEV;
155a9770eacSAndrew Lunn 
156a9770eacSAndrew Lunn 	if (!mux_bus) {
157a9770eacSAndrew Lunn 		parent_bus_node = of_parse_phandle(mux_node,
158a9770eacSAndrew Lunn 						   "mdio-parent-bus", 0);
159a9770eacSAndrew Lunn 
160a9770eacSAndrew Lunn 		if (!parent_bus_node)
161a9770eacSAndrew Lunn 			return -ENODEV;
162a9770eacSAndrew Lunn 
163a9770eacSAndrew Lunn 		parent_bus = of_mdio_find_bus(parent_bus_node);
164a9770eacSAndrew Lunn 		if (!parent_bus) {
165a9770eacSAndrew Lunn 			ret_val = -EPROBE_DEFER;
166a9770eacSAndrew Lunn 			goto err_parent_bus;
167a9770eacSAndrew Lunn 		}
168a9770eacSAndrew Lunn 	} else {
169a9770eacSAndrew Lunn 		parent_bus_node = NULL;
170a9770eacSAndrew Lunn 		parent_bus = mux_bus;
171a9770eacSAndrew Lunn 		get_device(&parent_bus->dev);
172a9770eacSAndrew Lunn 	}
173a9770eacSAndrew Lunn 
174a9770eacSAndrew Lunn 	pb = devm_kzalloc(dev, sizeof(*pb), GFP_KERNEL);
175a9770eacSAndrew Lunn 	if (!pb) {
176a9770eacSAndrew Lunn 		ret_val = -ENOMEM;
177a9770eacSAndrew Lunn 		goto err_pb_kz;
178a9770eacSAndrew Lunn 	}
179a9770eacSAndrew Lunn 
180a9770eacSAndrew Lunn 	pb->switch_data = data;
181a9770eacSAndrew Lunn 	pb->switch_fn = switch_fn;
182a9770eacSAndrew Lunn 	pb->current_child = -1;
183a9770eacSAndrew Lunn 	pb->parent_id = parent_count++;
184a9770eacSAndrew Lunn 	pb->mii_bus = parent_bus;
185a9770eacSAndrew Lunn 
186a9770eacSAndrew Lunn 	ret_val = -ENODEV;
187a9770eacSAndrew Lunn 	for_each_available_child_of_node(mux_node, child_bus_node) {
188a9770eacSAndrew Lunn 		int v;
189a9770eacSAndrew Lunn 
190a9770eacSAndrew Lunn 		r = of_property_read_u32(child_bus_node, "reg", &v);
191a9770eacSAndrew Lunn 		if (r) {
192a9770eacSAndrew Lunn 			dev_err(dev,
193d215ab4dSVladimir Oltean 				"Error: Failed to find reg for child %pOF: %pe\n",
194d215ab4dSVladimir Oltean 				child_bus_node, ERR_PTR(r));
195a9770eacSAndrew Lunn 			continue;
196a9770eacSAndrew Lunn 		}
197a9770eacSAndrew Lunn 
198a9770eacSAndrew Lunn 		cb = devm_kzalloc(dev, sizeof(*cb), GFP_KERNEL);
199a9770eacSAndrew Lunn 		if (!cb) {
200a9770eacSAndrew Lunn 			ret_val = -ENOMEM;
20199d81e94SSaravana Kannan 			goto err_loop;
202a9770eacSAndrew Lunn 		}
203a9770eacSAndrew Lunn 		cb->bus_number = v;
204a9770eacSAndrew Lunn 		cb->parent = pb;
205a9770eacSAndrew Lunn 
206a9770eacSAndrew Lunn 		cb->mii_bus = mdiobus_alloc();
207a9770eacSAndrew Lunn 		if (!cb->mii_bus) {
208a9770eacSAndrew Lunn 			ret_val = -ENOMEM;
20999d81e94SSaravana Kannan 			goto err_loop;
210a9770eacSAndrew Lunn 		}
211a9770eacSAndrew Lunn 		cb->mii_bus->priv = cb;
212a9770eacSAndrew Lunn 
213a9770eacSAndrew Lunn 		cb->mii_bus->name = "mdio_mux";
2141416ea0dSHeiner Kallweit 		snprintf(cb->mii_bus->id, MII_BUS_ID_SIZE, "%s-%x.%x",
2151416ea0dSHeiner Kallweit 			 cb->mii_bus->name, pb->parent_id, v);
216a9770eacSAndrew Lunn 		cb->mii_bus->parent = dev;
217*10ad63daSVladimir Oltean 		if (parent_bus->read)
218a9770eacSAndrew Lunn 			cb->mii_bus->read = mdio_mux_read;
219*10ad63daSVladimir Oltean 		if (parent_bus->write)
220a9770eacSAndrew Lunn 			cb->mii_bus->write = mdio_mux_write;
2211f9f2143SVladimir Oltean 		if (parent_bus->read_c45)
2221f9f2143SVladimir Oltean 			cb->mii_bus->read_c45 = mdio_mux_read_c45;
2231f9f2143SVladimir Oltean 		if (parent_bus->write_c45)
2241f9f2143SVladimir Oltean 			cb->mii_bus->write_c45 = mdio_mux_write_c45;
225a9770eacSAndrew Lunn 		r = of_mdiobus_register(cb->mii_bus, child_bus_node);
226a9770eacSAndrew Lunn 		if (r) {
2277bd0cef5SSaravana Kannan 			mdiobus_free(cb->mii_bus);
2287bd0cef5SSaravana Kannan 			if (r == -EPROBE_DEFER) {
2297bd0cef5SSaravana Kannan 				ret_val = r;
2307bd0cef5SSaravana Kannan 				goto err_loop;
2317bd0cef5SSaravana Kannan 			}
2327bd0cef5SSaravana Kannan 			devm_kfree(dev, cb);
233a9770eacSAndrew Lunn 			dev_err(dev,
234d215ab4dSVladimir Oltean 				"Error: Failed to register MDIO bus for child %pOF: %pe\n",
235d215ab4dSVladimir Oltean 				child_bus_node, ERR_PTR(r));
236a9770eacSAndrew Lunn 		} else {
237a9770eacSAndrew Lunn 			cb->next = pb->children;
238a9770eacSAndrew Lunn 			pb->children = cb;
239a9770eacSAndrew Lunn 		}
240a9770eacSAndrew Lunn 	}
241a9770eacSAndrew Lunn 	if (pb->children) {
242a9770eacSAndrew Lunn 		*mux_handle = pb;
243a9770eacSAndrew Lunn 		return 0;
244a9770eacSAndrew Lunn 	}
245a9770eacSAndrew Lunn 
246a9770eacSAndrew Lunn 	dev_err(dev, "Error: No acceptable child buses found\n");
24799d81e94SSaravana Kannan 
24899d81e94SSaravana Kannan err_loop:
24999d81e94SSaravana Kannan 	mdio_mux_uninit_children(pb);
25099d81e94SSaravana Kannan 	of_node_put(child_bus_node);
251a9770eacSAndrew Lunn err_pb_kz:
252a9770eacSAndrew Lunn 	put_device(&parent_bus->dev);
253a9770eacSAndrew Lunn err_parent_bus:
254a9770eacSAndrew Lunn 	of_node_put(parent_bus_node);
255a9770eacSAndrew Lunn 	return ret_val;
256a9770eacSAndrew Lunn }
257a9770eacSAndrew Lunn EXPORT_SYMBOL_GPL(mdio_mux_init);
258a9770eacSAndrew Lunn 
mdio_mux_uninit(void * mux_handle)259a9770eacSAndrew Lunn void mdio_mux_uninit(void *mux_handle)
260a9770eacSAndrew Lunn {
261a9770eacSAndrew Lunn 	struct mdio_mux_parent_bus *pb = mux_handle;
262a9770eacSAndrew Lunn 
26399d81e94SSaravana Kannan 	mdio_mux_uninit_children(pb);
264a9770eacSAndrew Lunn 	put_device(&pb->mii_bus->dev);
265a9770eacSAndrew Lunn }
266a9770eacSAndrew Lunn EXPORT_SYMBOL_GPL(mdio_mux_uninit);
267a9770eacSAndrew Lunn 
268a9770eacSAndrew Lunn MODULE_DESCRIPTION(DRV_DESCRIPTION);
269a9770eacSAndrew Lunn MODULE_AUTHOR("David Daney");
270a9770eacSAndrew Lunn MODULE_LICENSE("GPL v2");
271