aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/phy
diff options
context:
space:
mode:
authorDavid Daney <david.daney@cavium.com>2012-05-02 11:16:38 -0400
committerDavid S. Miller <davem@davemloft.net>2012-05-07 22:58:09 -0400
commit0ca2997d145268e6b4ef000692061849cdab8348 (patch)
treedeeca7985f029cae477945e988816889c986e263 /drivers/net/phy
parent251060220021283eef3652145a41f5b26db97ce5 (diff)
netdev/of/phy: Add MDIO bus multiplexer support.
This patch adds a somewhat generic framework for MDIO bus multiplexers. It is modeled on the I2C multiplexer. The multiplexer is needed if there are multiple PHYs with the same address connected to the same MDIO bus adepter, or if there is insufficient electrical drive capability for all the connected PHY devices. Conceptually it could look something like this: ------------------ | Control Signal | --------+--------- | --------------- --------+------ | MDIO MASTER |---| Multiplexer | --------------- --+-------+---- | | C C h h i i l l d d | | --------- A B --------- | | | | | | | PHY@1 +-------+ +---+ PHY@1 | | | | | | | --------- | | --------- --------- | | --------- | | | | | | | PHY@2 +-------+ +---+ PHY@2 | | | | | --------- --------- This framework configures the bus topology from device tree data. The mechanics of switching the multiplexer is left to device specific drivers. The follow-on patch contains a multiplexer driven by GPIO lines. Signed-off-by: David Daney <david.daney@cavium.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/phy')
-rw-r--r--drivers/net/phy/Kconfig9
-rw-r--r--drivers/net/phy/Makefile1
-rw-r--r--drivers/net/phy/mdio-mux.c192
3 files changed, 202 insertions, 0 deletions
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 0e01f4e5cd64..99c0674a5935 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -135,6 +135,15 @@ config MDIO_OCTEON
135 135
136 If in doubt, say Y. 136 If in doubt, say Y.
137 137
138config MDIO_BUS_MUX
139 tristate
140 depends on OF_MDIO
141 help
142 This module provides a driver framework for MDIO bus
143 multiplexers which connect one of several child MDIO busses
144 to a parent bus. Switching between child busses is done by
145 device specific drivers.
146
138endif # PHYLIB 147endif # PHYLIB
139 148
140config MICREL_KS8995MA 149config MICREL_KS8995MA
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index b7438b1b94b9..a6b50e7715d0 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -25,3 +25,4 @@ obj-$(CONFIG_MICREL_PHY) += micrel.o
25obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o 25obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
26obj-$(CONFIG_MICREL_KS8995MA) += spi_ks8995.o 26obj-$(CONFIG_MICREL_KS8995MA) += spi_ks8995.o
27obj-$(CONFIG_AMD_PHY) += amd.o 27obj-$(CONFIG_AMD_PHY) += amd.o
28obj-$(CONFIG_MDIO_BUS_MUX) += mdio-mux.o
diff --git a/drivers/net/phy/mdio-mux.c b/drivers/net/phy/mdio-mux.c
new file mode 100644
index 000000000000..39ea0674dcde
--- /dev/null
+++ b/drivers/net/phy/mdio-mux.c
@@ -0,0 +1,192 @@
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 mutex_lock(&pb->mii_bus->mdio_lock);
50 r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
51 if (r)
52 goto out;
53
54 pb->current_child = cb->bus_number;
55
56 r = pb->mii_bus->read(pb->mii_bus, phy_id, regnum);
57out:
58 mutex_unlock(&pb->mii_bus->mdio_lock);
59
60 return r;
61}
62
63/*
64 * The parent bus' lock is used to order access to the switch_fn.
65 */
66static int mdio_mux_write(struct mii_bus *bus, int phy_id,
67 int regnum, u16 val)
68{
69 struct mdio_mux_child_bus *cb = bus->priv;
70 struct mdio_mux_parent_bus *pb = cb->parent;
71
72 int r;
73
74 mutex_lock(&pb->mii_bus->mdio_lock);
75 r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
76 if (r)
77 goto out;
78
79 pb->current_child = cb->bus_number;
80
81 r = pb->mii_bus->write(pb->mii_bus, phy_id, regnum, val);
82out:
83 mutex_unlock(&pb->mii_bus->mdio_lock);
84
85 return r;
86}
87
88static int parent_count;
89
90int mdio_mux_init(struct device *dev,
91 int (*switch_fn)(int cur, int desired, void *data),
92 void **mux_handle,
93 void *data)
94{
95 struct device_node *parent_bus_node;
96 struct device_node *child_bus_node;
97 int r, ret_val;
98 struct mii_bus *parent_bus;
99 struct mdio_mux_parent_bus *pb;
100 struct mdio_mux_child_bus *cb;
101
102 if (!dev->of_node)
103 return -ENODEV;
104
105 parent_bus_node = of_parse_phandle(dev->of_node, "mdio-parent-bus", 0);
106
107 if (!parent_bus_node)
108 return -ENODEV;
109
110 parent_bus = of_mdio_find_bus(parent_bus_node);
111 if (parent_bus == NULL) {
112 ret_val = -EPROBE_DEFER;
113 goto err_parent_bus;
114 }
115
116 pb = devm_kzalloc(dev, sizeof(*pb), GFP_KERNEL);
117 if (pb == NULL) {
118 ret_val = -ENOMEM;
119 goto err_parent_bus;
120 }
121
122 pb->switch_data = data;
123 pb->switch_fn = switch_fn;
124 pb->current_child = -1;
125 pb->parent_id = parent_count++;
126 pb->mii_bus = parent_bus;
127
128 ret_val = -ENODEV;
129 for_each_child_of_node(dev->of_node, child_bus_node) {
130 u32 v;
131
132 r = of_property_read_u32(child_bus_node, "reg", &v);
133 if (r)
134 continue;
135
136 cb = devm_kzalloc(dev, sizeof(*cb), GFP_KERNEL);
137 if (cb == NULL) {
138 dev_err(dev,
139 "Error: Failed to allocate memory for child\n");
140 ret_val = -ENOMEM;
141 break;
142 }
143 cb->bus_number = v;
144 cb->parent = pb;
145 cb->mii_bus = mdiobus_alloc();
146 cb->mii_bus->priv = cb;
147
148 cb->mii_bus->irq = cb->phy_irq;
149 cb->mii_bus->name = "mdio_mux";
150 snprintf(cb->mii_bus->id, MII_BUS_ID_SIZE, "%x.%x",
151 pb->parent_id, v);
152 cb->mii_bus->parent = dev;
153 cb->mii_bus->read = mdio_mux_read;
154 cb->mii_bus->write = mdio_mux_write;
155 r = of_mdiobus_register(cb->mii_bus, child_bus_node);
156 if (r) {
157 mdiobus_free(cb->mii_bus);
158 devm_kfree(dev, cb);
159 } else {
160 of_node_get(child_bus_node);
161 cb->next = pb->children;
162 pb->children = cb;
163 }
164 }
165 if (pb->children) {
166 *mux_handle = pb;
167 dev_info(dev, "Version " DRV_VERSION "\n");
168 return 0;
169 }
170err_parent_bus:
171 of_node_put(parent_bus_node);
172 return ret_val;
173}
174EXPORT_SYMBOL_GPL(mdio_mux_init);
175
176void mdio_mux_uninit(void *mux_handle)
177{
178 struct mdio_mux_parent_bus *pb = mux_handle;
179 struct mdio_mux_child_bus *cb = pb->children;
180
181 while (cb) {
182 mdiobus_unregister(cb->mii_bus);
183 mdiobus_free(cb->mii_bus);
184 cb = cb->next;
185 }
186}
187EXPORT_SYMBOL_GPL(mdio_mux_uninit);
188
189MODULE_DESCRIPTION(DRV_DESCRIPTION);
190MODULE_VERSION(DRV_VERSION);
191MODULE_AUTHOR("David Daney");
192MODULE_LICENSE("GPL");