162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2011, 2012 Cavium, Inc.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/device.h>
762306a36Sopenharmony_ci#include <linux/mdio-mux.h>
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/of_mdio.h>
1062306a36Sopenharmony_ci#include <linux/phy.h>
1162306a36Sopenharmony_ci#include <linux/platform_device.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#define DRV_DESCRIPTION "MDIO bus multiplexer driver"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistruct mdio_mux_child_bus;
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_cistruct mdio_mux_parent_bus {
1862306a36Sopenharmony_ci	struct mii_bus *mii_bus;
1962306a36Sopenharmony_ci	int current_child;
2062306a36Sopenharmony_ci	int parent_id;
2162306a36Sopenharmony_ci	void *switch_data;
2262306a36Sopenharmony_ci	int (*switch_fn)(int current_child, int desired_child, void *data);
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci	/* List of our children linked through their next fields. */
2562306a36Sopenharmony_ci	struct mdio_mux_child_bus *children;
2662306a36Sopenharmony_ci};
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistruct mdio_mux_child_bus {
2962306a36Sopenharmony_ci	struct mii_bus *mii_bus;
3062306a36Sopenharmony_ci	struct mdio_mux_parent_bus *parent;
3162306a36Sopenharmony_ci	struct mdio_mux_child_bus *next;
3262306a36Sopenharmony_ci	int bus_number;
3362306a36Sopenharmony_ci};
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci/*
3662306a36Sopenharmony_ci * The parent bus' lock is used to order access to the switch_fn.
3762306a36Sopenharmony_ci */
3862306a36Sopenharmony_cistatic int mdio_mux_read(struct mii_bus *bus, int phy_id, int regnum)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	struct mdio_mux_child_bus *cb = bus->priv;
4162306a36Sopenharmony_ci	struct mdio_mux_parent_bus *pb = cb->parent;
4262306a36Sopenharmony_ci	int r;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	mutex_lock_nested(&pb->mii_bus->mdio_lock, MDIO_MUTEX_MUX);
4562306a36Sopenharmony_ci	r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
4662306a36Sopenharmony_ci	if (r)
4762306a36Sopenharmony_ci		goto out;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	pb->current_child = cb->bus_number;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	r = pb->mii_bus->read(pb->mii_bus, phy_id, regnum);
5262306a36Sopenharmony_ciout:
5362306a36Sopenharmony_ci	mutex_unlock(&pb->mii_bus->mdio_lock);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	return r;
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistatic int mdio_mux_read_c45(struct mii_bus *bus, int phy_id, int dev_addr,
5962306a36Sopenharmony_ci			     int regnum)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	struct mdio_mux_child_bus *cb = bus->priv;
6262306a36Sopenharmony_ci	struct mdio_mux_parent_bus *pb = cb->parent;
6362306a36Sopenharmony_ci	int r;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	mutex_lock_nested(&pb->mii_bus->mdio_lock, MDIO_MUTEX_MUX);
6662306a36Sopenharmony_ci	r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
6762306a36Sopenharmony_ci	if (r)
6862306a36Sopenharmony_ci		goto out;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	pb->current_child = cb->bus_number;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	r = pb->mii_bus->read_c45(pb->mii_bus, phy_id, dev_addr, regnum);
7362306a36Sopenharmony_ciout:
7462306a36Sopenharmony_ci	mutex_unlock(&pb->mii_bus->mdio_lock);
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	return r;
7762306a36Sopenharmony_ci}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci/*
8062306a36Sopenharmony_ci * The parent bus' lock is used to order access to the switch_fn.
8162306a36Sopenharmony_ci */
8262306a36Sopenharmony_cistatic int mdio_mux_write(struct mii_bus *bus, int phy_id,
8362306a36Sopenharmony_ci			  int regnum, u16 val)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	struct mdio_mux_child_bus *cb = bus->priv;
8662306a36Sopenharmony_ci	struct mdio_mux_parent_bus *pb = cb->parent;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	int r;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	mutex_lock_nested(&pb->mii_bus->mdio_lock, MDIO_MUTEX_MUX);
9162306a36Sopenharmony_ci	r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
9262306a36Sopenharmony_ci	if (r)
9362306a36Sopenharmony_ci		goto out;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	pb->current_child = cb->bus_number;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	r = pb->mii_bus->write(pb->mii_bus, phy_id, regnum, val);
9862306a36Sopenharmony_ciout:
9962306a36Sopenharmony_ci	mutex_unlock(&pb->mii_bus->mdio_lock);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	return r;
10262306a36Sopenharmony_ci}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_cistatic int mdio_mux_write_c45(struct mii_bus *bus, int phy_id, int dev_addr,
10562306a36Sopenharmony_ci			      int regnum, u16 val)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	struct mdio_mux_child_bus *cb = bus->priv;
10862306a36Sopenharmony_ci	struct mdio_mux_parent_bus *pb = cb->parent;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	int r;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	mutex_lock_nested(&pb->mii_bus->mdio_lock, MDIO_MUTEX_MUX);
11362306a36Sopenharmony_ci	r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
11462306a36Sopenharmony_ci	if (r)
11562306a36Sopenharmony_ci		goto out;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	pb->current_child = cb->bus_number;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	r = pb->mii_bus->write_c45(pb->mii_bus, phy_id, dev_addr, regnum, val);
12062306a36Sopenharmony_ciout:
12162306a36Sopenharmony_ci	mutex_unlock(&pb->mii_bus->mdio_lock);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	return r;
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic int parent_count;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cistatic void mdio_mux_uninit_children(struct mdio_mux_parent_bus *pb)
12962306a36Sopenharmony_ci{
13062306a36Sopenharmony_ci	struct mdio_mux_child_bus *cb = pb->children;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	while (cb) {
13362306a36Sopenharmony_ci		mdiobus_unregister(cb->mii_bus);
13462306a36Sopenharmony_ci		mdiobus_free(cb->mii_bus);
13562306a36Sopenharmony_ci		cb = cb->next;
13662306a36Sopenharmony_ci	}
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ciint mdio_mux_init(struct device *dev,
14062306a36Sopenharmony_ci		  struct device_node *mux_node,
14162306a36Sopenharmony_ci		  int (*switch_fn)(int cur, int desired, void *data),
14262306a36Sopenharmony_ci		  void **mux_handle,
14362306a36Sopenharmony_ci		  void *data,
14462306a36Sopenharmony_ci		  struct mii_bus *mux_bus)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	struct device_node *parent_bus_node;
14762306a36Sopenharmony_ci	struct device_node *child_bus_node;
14862306a36Sopenharmony_ci	int r, ret_val;
14962306a36Sopenharmony_ci	struct mii_bus *parent_bus;
15062306a36Sopenharmony_ci	struct mdio_mux_parent_bus *pb;
15162306a36Sopenharmony_ci	struct mdio_mux_child_bus *cb;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	if (!mux_node)
15462306a36Sopenharmony_ci		return -ENODEV;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	if (!mux_bus) {
15762306a36Sopenharmony_ci		parent_bus_node = of_parse_phandle(mux_node,
15862306a36Sopenharmony_ci						   "mdio-parent-bus", 0);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci		if (!parent_bus_node)
16162306a36Sopenharmony_ci			return -ENODEV;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci		parent_bus = of_mdio_find_bus(parent_bus_node);
16462306a36Sopenharmony_ci		if (!parent_bus) {
16562306a36Sopenharmony_ci			ret_val = -EPROBE_DEFER;
16662306a36Sopenharmony_ci			goto err_parent_bus;
16762306a36Sopenharmony_ci		}
16862306a36Sopenharmony_ci	} else {
16962306a36Sopenharmony_ci		parent_bus_node = NULL;
17062306a36Sopenharmony_ci		parent_bus = mux_bus;
17162306a36Sopenharmony_ci		get_device(&parent_bus->dev);
17262306a36Sopenharmony_ci	}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	pb = devm_kzalloc(dev, sizeof(*pb), GFP_KERNEL);
17562306a36Sopenharmony_ci	if (!pb) {
17662306a36Sopenharmony_ci		ret_val = -ENOMEM;
17762306a36Sopenharmony_ci		goto err_pb_kz;
17862306a36Sopenharmony_ci	}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	pb->switch_data = data;
18162306a36Sopenharmony_ci	pb->switch_fn = switch_fn;
18262306a36Sopenharmony_ci	pb->current_child = -1;
18362306a36Sopenharmony_ci	pb->parent_id = parent_count++;
18462306a36Sopenharmony_ci	pb->mii_bus = parent_bus;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	ret_val = -ENODEV;
18762306a36Sopenharmony_ci	for_each_available_child_of_node(mux_node, child_bus_node) {
18862306a36Sopenharmony_ci		int v;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci		r = of_property_read_u32(child_bus_node, "reg", &v);
19162306a36Sopenharmony_ci		if (r) {
19262306a36Sopenharmony_ci			dev_err(dev,
19362306a36Sopenharmony_ci				"Error: Failed to find reg for child %pOF\n",
19462306a36Sopenharmony_ci				child_bus_node);
19562306a36Sopenharmony_ci			continue;
19662306a36Sopenharmony_ci		}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci		cb = devm_kzalloc(dev, sizeof(*cb), GFP_KERNEL);
19962306a36Sopenharmony_ci		if (!cb) {
20062306a36Sopenharmony_ci			ret_val = -ENOMEM;
20162306a36Sopenharmony_ci			goto err_loop;
20262306a36Sopenharmony_ci		}
20362306a36Sopenharmony_ci		cb->bus_number = v;
20462306a36Sopenharmony_ci		cb->parent = pb;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci		cb->mii_bus = mdiobus_alloc();
20762306a36Sopenharmony_ci		if (!cb->mii_bus) {
20862306a36Sopenharmony_ci			ret_val = -ENOMEM;
20962306a36Sopenharmony_ci			goto err_loop;
21062306a36Sopenharmony_ci		}
21162306a36Sopenharmony_ci		cb->mii_bus->priv = cb;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci		cb->mii_bus->name = "mdio_mux";
21462306a36Sopenharmony_ci		snprintf(cb->mii_bus->id, MII_BUS_ID_SIZE, "%s-%x.%x",
21562306a36Sopenharmony_ci			 cb->mii_bus->name, pb->parent_id, v);
21662306a36Sopenharmony_ci		cb->mii_bus->parent = dev;
21762306a36Sopenharmony_ci		cb->mii_bus->read = mdio_mux_read;
21862306a36Sopenharmony_ci		cb->mii_bus->write = mdio_mux_write;
21962306a36Sopenharmony_ci		if (parent_bus->read_c45)
22062306a36Sopenharmony_ci			cb->mii_bus->read_c45 = mdio_mux_read_c45;
22162306a36Sopenharmony_ci		if (parent_bus->write_c45)
22262306a36Sopenharmony_ci			cb->mii_bus->write_c45 = mdio_mux_write_c45;
22362306a36Sopenharmony_ci		r = of_mdiobus_register(cb->mii_bus, child_bus_node);
22462306a36Sopenharmony_ci		if (r) {
22562306a36Sopenharmony_ci			mdiobus_free(cb->mii_bus);
22662306a36Sopenharmony_ci			if (r == -EPROBE_DEFER) {
22762306a36Sopenharmony_ci				ret_val = r;
22862306a36Sopenharmony_ci				goto err_loop;
22962306a36Sopenharmony_ci			}
23062306a36Sopenharmony_ci			devm_kfree(dev, cb);
23162306a36Sopenharmony_ci			dev_err(dev,
23262306a36Sopenharmony_ci				"Error: Failed to register MDIO bus for child %pOF\n",
23362306a36Sopenharmony_ci				child_bus_node);
23462306a36Sopenharmony_ci		} else {
23562306a36Sopenharmony_ci			cb->next = pb->children;
23662306a36Sopenharmony_ci			pb->children = cb;
23762306a36Sopenharmony_ci		}
23862306a36Sopenharmony_ci	}
23962306a36Sopenharmony_ci	if (pb->children) {
24062306a36Sopenharmony_ci		*mux_handle = pb;
24162306a36Sopenharmony_ci		return 0;
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	dev_err(dev, "Error: No acceptable child buses found\n");
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_cierr_loop:
24762306a36Sopenharmony_ci	mdio_mux_uninit_children(pb);
24862306a36Sopenharmony_ci	of_node_put(child_bus_node);
24962306a36Sopenharmony_cierr_pb_kz:
25062306a36Sopenharmony_ci	put_device(&parent_bus->dev);
25162306a36Sopenharmony_cierr_parent_bus:
25262306a36Sopenharmony_ci	of_node_put(parent_bus_node);
25362306a36Sopenharmony_ci	return ret_val;
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(mdio_mux_init);
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_civoid mdio_mux_uninit(void *mux_handle)
25862306a36Sopenharmony_ci{
25962306a36Sopenharmony_ci	struct mdio_mux_parent_bus *pb = mux_handle;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	mdio_mux_uninit_children(pb);
26262306a36Sopenharmony_ci	put_device(&pb->mii_bus->dev);
26362306a36Sopenharmony_ci}
26462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(mdio_mux_uninit);
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ciMODULE_DESCRIPTION(DRV_DESCRIPTION);
26762306a36Sopenharmony_ciMODULE_AUTHOR("David Daney");
26862306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
269