1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * (C) Copyright 2019
4 * Alex Marginean, NXP
5 */
6
7#include <common.h>
8#include <dm.h>
9#include <log.h>
10#include <miiphy.h>
11#include <dm/device-internal.h>
12#include <dm/uclass-internal.h>
13#include <dm/lists.h>
14
15#define MDIO_MUX_CHILD_DRV_NAME	"mdio-mux-bus-drv"
16
17/**
18 * struct mdio_mux_perdev_priv - Per-device class data for MDIO MUX DM
19 *
20 * @parent_mdio: Parent DM MDIO device, this is called for actual MDIO I/O after
21 *               setting up the mux.  Typically this is a real MDIO device,
22 *               unless there are cascaded muxes.
23 * @selected:    Current child bus selection.  Defaults to -1
24 */
25struct mdio_mux_perdev_priv {
26	struct udevice *mdio_parent;
27	int selected;
28};
29
30/*
31 * This source file uses three types of devices, as follows:
32 * - mux is the hardware MDIO MUX which selects between the existing child MDIO
33 * buses, this is the device relevant for MDIO MUX class of drivers.
34 * - ch is a child MDIO bus, this is just a representation of a mux selection,
35 * not a real piece of hardware.
36 * - mdio_parent is the actual MDIO bus called to perform reads/writes after
37 * the MUX is configured.  Typically this is a real MDIO device, unless there
38 * are cascaded muxes.
39 */
40
41/**
42 * struct mdio_mux_ch_data - Per-device data for child MDIOs
43 *
44 * @sel: Selection value used by the MDIO MUX to access this child MDIO bus
45 */
46struct mdio_mux_ch_data {
47	int sel;
48};
49
50static struct udevice *mmux_get_parent_mdio(struct udevice *mux)
51{
52	struct mdio_mux_perdev_priv *pdata = dev_get_uclass_priv(mux);
53
54	return pdata->mdio_parent;
55}
56
57/* call driver select function before performing MDIO r/w */
58static int mmux_change_sel(struct udevice *ch, bool sel)
59{
60	struct udevice *mux = ch->parent;
61	struct mdio_mux_perdev_priv *priv = dev_get_uclass_priv(mux);
62	struct mdio_mux_ops *ops = mdio_mux_get_ops(mux);
63	struct mdio_mux_ch_data *ch_data = dev_get_parent_plat(ch);
64	int err = 0;
65
66	if (sel) {
67		err = ops->select(mux, priv->selected, ch_data->sel);
68		if (err)
69			return err;
70
71		priv->selected = ch_data->sel;
72	} else {
73		if (ops->deselect) {
74			ops->deselect(mux, ch_data->sel);
75			priv->selected = MDIO_MUX_SELECT_NONE;
76		}
77	}
78
79	return 0;
80}
81
82/* Read wrapper, sets up the mux before issuing a read on parent MDIO bus */
83static int mmux_read(struct udevice *ch, int addr, int devad,
84		     int reg)
85{
86	struct udevice *mux = ch->parent;
87	struct udevice *parent_mdio = mmux_get_parent_mdio(mux);
88	int err;
89
90	err = mmux_change_sel(ch, true);
91	if (err)
92		return err;
93
94	err = dm_mdio_read(parent_mdio, addr, devad, reg);
95	mmux_change_sel(ch, false);
96
97	return err;
98}
99
100/* Write wrapper, sets up the mux before issuing a write on parent MDIO bus */
101static int mmux_write(struct udevice *ch, int addr, int devad,
102		      int reg, u16 val)
103{
104	struct udevice *mux = ch->parent;
105	struct udevice *parent_mdio = mmux_get_parent_mdio(mux);
106	int err;
107
108	err = mmux_change_sel(ch, true);
109	if (err)
110		return err;
111
112	err = dm_mdio_write(parent_mdio, addr, devad, reg, val);
113	mmux_change_sel(ch, false);
114
115	return err;
116}
117
118/* Reset wrapper, sets up the mux before issuing a reset on parent MDIO bus */
119static int mmux_reset(struct udevice *ch)
120{
121	struct udevice *mux = ch->parent;
122	struct udevice *parent_mdio = mmux_get_parent_mdio(mux);
123	int err;
124
125	/* reset is optional, if it's not implemented just exit */
126	if (!mdio_get_ops(parent_mdio)->reset)
127		return 0;
128
129	err = mmux_change_sel(ch, true);
130	if (err)
131		return err;
132
133	err = dm_mdio_reset(parent_mdio);
134	mmux_change_sel(ch, false);
135
136	return err;
137}
138
139/* Picks up the mux selection value for each child */
140static int dm_mdio_mux_child_post_bind(struct udevice *ch)
141{
142	struct mdio_mux_ch_data *ch_data = dev_get_parent_plat(ch);
143
144	ch_data->sel = dev_read_u32_default(ch, "reg", MDIO_MUX_SELECT_NONE);
145
146	if (ch_data->sel == MDIO_MUX_SELECT_NONE)
147		return -EINVAL;
148
149	return 0;
150}
151
152/* Explicitly bind child MDIOs after binding the mux */
153static int dm_mdio_mux_post_bind(struct udevice *mux)
154{
155	ofnode ch_node;
156	int err, first_err = 0;
157
158	if (!dev_has_ofnode(mux)) {
159		debug("%s: no mux node found, no child MDIO busses set up\n",
160		      __func__);
161		return 0;
162	}
163
164	/*
165	 * we're going by Linux bindings so the child nodes do not have
166	 * compatible strings.  We're going through them here and binding to
167	 * them.
168	 */
169	dev_for_each_subnode(ch_node, mux) {
170		struct udevice *ch_dev;
171		const char *ch_name;
172
173		ch_name = ofnode_get_name(ch_node);
174
175		err = device_bind_driver_to_node(mux, MDIO_MUX_CHILD_DRV_NAME,
176						 ch_name, ch_node, &ch_dev);
177		/* try to bind all, but keep 1st error */
178		if (err && !first_err)
179			first_err = err;
180	}
181
182	return first_err;
183}
184
185/* Get a reference to the parent MDIO bus, it should be bound by now */
186static int dm_mdio_mux_post_probe(struct udevice *mux)
187{
188	struct mdio_mux_perdev_priv *priv = dev_get_uclass_priv(mux);
189	int err;
190
191	priv->selected = MDIO_MUX_SELECT_NONE;
192
193	/* pick up mdio parent from device tree */
194	err = uclass_get_device_by_phandle(UCLASS_MDIO, mux, "mdio-parent-bus",
195					   &priv->mdio_parent);
196	if (err) {
197		debug("%s: didn't find mdio-parent-bus\n", __func__);
198		return err;
199	}
200
201	return 0;
202}
203
204const struct mdio_ops mmux_child_mdio_ops = {
205	.read = mmux_read,
206	.write = mmux_write,
207	.reset = mmux_reset,
208};
209
210/* MDIO class driver used for MUX child MDIO buses */
211U_BOOT_DRIVER(mdio_mux_child) = {
212	.name		= MDIO_MUX_CHILD_DRV_NAME,
213	.id		= UCLASS_MDIO,
214	.ops		= &mmux_child_mdio_ops,
215};
216
217UCLASS_DRIVER(mdio_mux) = {
218	.id = UCLASS_MDIO_MUX,
219	.name = "mdio-mux",
220	.child_post_bind = dm_mdio_mux_child_post_bind,
221	.post_bind  = dm_mdio_mux_post_bind,
222	.post_probe = dm_mdio_mux_post_probe,
223	.per_device_auto	= sizeof(struct mdio_mux_perdev_priv),
224	.per_child_plat_auto	= sizeof(struct mdio_mux_ch_data),
225};
226