linux/drivers/net/phy/mdio-mux.c
<<
>>
Prefs
   1/*
   2 * This file is subject to the terms and conditions of the GNU General Public
   3 * License.  See the file "COPYING" in the main directory of this archive
   4 * for more details.
   5 *
   6 * Copyright (C) 2011, 2012 Cavium, Inc.
   7 */
   8
   9#include <linux/platform_device.h>
  10#include <linux/mdio-mux.h>
  11#include <linux/of_mdio.h>
  12#include <linux/device.h>
  13#include <linux/module.h>
  14#include <linux/phy.h>
  15
  16#define DRV_VERSION "1.0"
  17#define DRV_DESCRIPTION "MDIO bus multiplexer driver"
  18
  19struct mdio_mux_child_bus;
  20
  21struct mdio_mux_parent_bus {
  22        struct mii_bus *mii_bus;
  23        int current_child;
  24        int parent_id;
  25        void *switch_data;
  26        int (*switch_fn)(int current_child, int desired_child, void *data);
  27
  28        /* List of our children linked through their next fields. */
  29        struct mdio_mux_child_bus *children;
  30};
  31
  32struct mdio_mux_child_bus {
  33        struct mii_bus *mii_bus;
  34        struct mdio_mux_parent_bus *parent;
  35        struct mdio_mux_child_bus *next;
  36        int bus_number;
  37        int phy_irq[PHY_MAX_ADDR];
  38};
  39
  40/*
  41 * The parent bus' lock is used to order access to the switch_fn.
  42 */
  43static int mdio_mux_read(struct mii_bus *bus, int phy_id, int regnum)
  44{
  45        struct mdio_mux_child_bus *cb = bus->priv;
  46        struct mdio_mux_parent_bus *pb = cb->parent;
  47        int r;
  48
  49        /* In theory multiple mdio_mux could be stacked, thus creating
  50         * more than a single level of nesting.  But in practice,
  51         * SINGLE_DEPTH_NESTING will cover the vast majority of use
  52         * cases.  We use it, instead of trying to handle the general
  53         * case.
  54         */
  55        mutex_lock_nested(&pb->mii_bus->mdio_lock, SINGLE_DEPTH_NESTING);
  56        r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
  57        if (r)
  58                goto out;
  59
  60        pb->current_child = cb->bus_number;
  61
  62        r = pb->mii_bus->read(pb->mii_bus, phy_id, regnum);
  63out:
  64        mutex_unlock(&pb->mii_bus->mdio_lock);
  65
  66        return r;
  67}
  68
  69/*
  70 * The parent bus' lock is used to order access to the switch_fn.
  71 */
  72static int mdio_mux_write(struct mii_bus *bus, int phy_id,
  73                          int regnum, u16 val)
  74{
  75        struct mdio_mux_child_bus *cb = bus->priv;
  76        struct mdio_mux_parent_bus *pb = cb->parent;
  77
  78        int r;
  79
  80        mutex_lock_nested(&pb->mii_bus->mdio_lock, SINGLE_DEPTH_NESTING);
  81        r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
  82        if (r)
  83                goto out;
  84
  85        pb->current_child = cb->bus_number;
  86
  87        r = pb->mii_bus->write(pb->mii_bus, phy_id, regnum, val);
  88out:
  89        mutex_unlock(&pb->mii_bus->mdio_lock);
  90
  91        return r;
  92}
  93
  94static int parent_count;
  95
  96int mdio_mux_init(struct device *dev,
  97                  int (*switch_fn)(int cur, int desired, void *data),
  98                  void **mux_handle,
  99                  void *data)
 100{
 101        struct device_node *parent_bus_node;
 102        struct device_node *child_bus_node;
 103        int r, ret_val;
 104        struct mii_bus *parent_bus;
 105        struct mdio_mux_parent_bus *pb;
 106        struct mdio_mux_child_bus *cb;
 107
 108        if (!dev->of_node)
 109                return -ENODEV;
 110
 111        parent_bus_node = of_parse_phandle(dev->of_node, "mdio-parent-bus", 0);
 112
 113        if (!parent_bus_node)
 114                return -ENODEV;
 115
 116        parent_bus = of_mdio_find_bus(parent_bus_node);
 117        if (parent_bus == NULL) {
 118                ret_val = -EPROBE_DEFER;
 119                goto err_parent_bus;
 120        }
 121
 122        pb = devm_kzalloc(dev, sizeof(*pb), GFP_KERNEL);
 123        if (pb == NULL) {
 124                ret_val = -ENOMEM;
 125                goto err_parent_bus;
 126        }
 127
 128        pb->switch_data = data;
 129        pb->switch_fn = switch_fn;
 130        pb->current_child = -1;
 131        pb->parent_id = parent_count++;
 132        pb->mii_bus = parent_bus;
 133
 134        ret_val = -ENODEV;
 135        for_each_available_child_of_node(dev->of_node, child_bus_node) {
 136                u32 v;
 137
 138                r = of_property_read_u32(child_bus_node, "reg", &v);
 139                if (r)
 140                        continue;
 141
 142                cb = devm_kzalloc(dev, sizeof(*cb), GFP_KERNEL);
 143                if (cb == NULL) {
 144                        dev_err(dev,
 145                                "Error: Failed to allocate memory for child\n");
 146                        ret_val = -ENOMEM;
 147                        break;
 148                }
 149                cb->bus_number = v;
 150                cb->parent = pb;
 151                cb->mii_bus = mdiobus_alloc();
 152                cb->mii_bus->priv = cb;
 153
 154                cb->mii_bus->irq = cb->phy_irq;
 155                cb->mii_bus->name = "mdio_mux";
 156                snprintf(cb->mii_bus->id, MII_BUS_ID_SIZE, "%x.%x",
 157                         pb->parent_id, v);
 158                cb->mii_bus->parent = dev;
 159                cb->mii_bus->read = mdio_mux_read;
 160                cb->mii_bus->write = mdio_mux_write;
 161                r = of_mdiobus_register(cb->mii_bus, child_bus_node);
 162                if (r) {
 163                        mdiobus_free(cb->mii_bus);
 164                        devm_kfree(dev, cb);
 165                } else {
 166                        of_node_get(child_bus_node);
 167                        cb->next = pb->children;
 168                        pb->children = cb;
 169                }
 170        }
 171        if (pb->children) {
 172                *mux_handle = pb;
 173                dev_info(dev, "Version " DRV_VERSION "\n");
 174                return 0;
 175        }
 176err_parent_bus:
 177        of_node_put(parent_bus_node);
 178        return ret_val;
 179}
 180EXPORT_SYMBOL_GPL(mdio_mux_init);
 181
 182void mdio_mux_uninit(void *mux_handle)
 183{
 184        struct mdio_mux_parent_bus *pb = mux_handle;
 185        struct mdio_mux_child_bus *cb = pb->children;
 186
 187        while (cb) {
 188                mdiobus_unregister(cb->mii_bus);
 189                mdiobus_free(cb->mii_bus);
 190                cb = cb->next;
 191        }
 192}
 193EXPORT_SYMBOL_GPL(mdio_mux_uninit);
 194
 195MODULE_DESCRIPTION(DRV_DESCRIPTION);
 196MODULE_VERSION(DRV_VERSION);
 197MODULE_AUTHOR("David Daney");
 198MODULE_LICENSE("GPL");
 199