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