summaryrefslogtreecommitdiffstats
path: root/drivers/net/phy/mdio-mux.c
blob: aa63cbde973cdcd9b03f3ef8502448ed3b6daf9a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2017 Zodiac Inflight Innovation
 * Author: Andrey Smirnov <andrew.smirnov@gmail.com>
 *
 * Based on analogous code from Linux kernel
 *
 * Copyright (C) 2011, 2012 Cavium, Inc.
 */
#include <common.h>
#include <linux/mdio-mux.h>
#include <linux/phy.h>

struct mdio_mux_parent_bus {
	struct mii_bus *mii_bus;
	int current_child;
	int parent_id;
	void *switch_data;
	int (*switch_fn)(int current_child, int desired_child, void *data);
};

struct mdio_mux_child_bus {
	struct mii_bus mii_bus;
	struct mdio_mux_parent_bus *parent;
	struct mdio_mux_child_bus *next;
	int bus_number;
};

static int mdio_mux_read_or_write(struct mii_bus *bus, int phy_id,
		       int regnum, u16 *val)
{
	struct mdio_mux_child_bus *cb = bus->priv;
	struct mdio_mux_parent_bus *pb = cb->parent;
	int r;

	r = pb->switch_fn(pb->current_child, cb->bus_number, pb->switch_data);
	if (!r) {
		pb->current_child = cb->bus_number;
		if (val)
			r = pb->mii_bus->write(pb->mii_bus, phy_id,
					       regnum, *val);
		else
			r = pb->mii_bus->read(pb->mii_bus, phy_id,
					      regnum);
	}
	return r;
}

static int mdio_mux_read(struct mii_bus *bus, int phy_id, int regnum)
{
	return mdio_mux_read_or_write(bus, phy_id, regnum, NULL);
}

static int mdio_mux_write(struct mii_bus *bus, int phy_id,
			  int regnum, u16 val)
{
	return mdio_mux_read_or_write(bus, phy_id, regnum, &val);
}

int mdio_mux_init(struct device_d *dev,
		  struct device_node *mux_node,
		  int (*switch_fn)(int cur, int desired, void *data),
		  void *data,
		  struct mii_bus *mux_bus)
{
	static int parent_count = 0;

	struct device_node *parent_bus_node;
	struct device_node *child_bus_node;
	struct mdio_mux_parent_bus *pb;
	struct mdio_mux_child_bus *cb;
	struct mii_bus *parent_bus;
	int r;

	if (!mux_node)
		return -ENODEV;

	if (!mux_bus) {
		parent_bus_node = of_parse_phandle(mux_node,
						   "mdio-parent-bus", 0);

		if (!parent_bus_node)
			return -ENODEV;

		parent_bus = of_mdio_find_bus(parent_bus_node);

		if (!parent_bus)
			return -EPROBE_DEFER;
	} else {
		parent_bus_node = NULL;
		parent_bus = mux_bus;
	}

	pb = xzalloc(sizeof(*pb));

	pb->switch_data   = data;
	pb->switch_fn     = switch_fn;
	pb->current_child = -1;
	pb->parent_id     = parent_count++;
	pb->mii_bus       = parent_bus;

	for_each_available_child_of_node(mux_node, child_bus_node) {
		int v;

		r = of_property_read_u32(child_bus_node, "reg", &v);
		if (r) {
			dev_err(dev,
				"Error: Failed to find reg for child %pOF\n",
				child_bus_node);
			continue;
		}

		cb = xzalloc(sizeof(*cb));
		cb->bus_number = v;
		cb->parent     = pb;

		cb->mii_bus.priv   = cb;
		cb->mii_bus.parent = dev;
		cb->mii_bus.read   = mdio_mux_read;
		cb->mii_bus.write  = mdio_mux_write;
		cb->mii_bus.dev.device_node = child_bus_node;

		r = mdiobus_register(&cb->mii_bus);
		if (r) {
			dev_err(dev,
				"Error: Failed to register MDIO bus for child %pOF\n",
				child_bus_node);
		}
	}

	parent_bus->is_multiplexed = true;
	return 0;
}
EXPORT_SYMBOL_GPL(mdio_mux_init);