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