1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (c) 2022 Baylibre, SAS.
4 * Author: Jerome Brunet <jbrunet@baylibre.com>
5 * Copyright (c) 2023 Neil Armstrong <neil.armstrong@linaro.org>
6 */
7
8#include <dm.h>
9#include <errno.h>
10#include <log.h>
11#include <miiphy.h>
12#include <asm/io.h>
13#include <linux/bitfield.h>
14#include <linux/delay.h>
15
16#define ETH_REG2		0x0
17#define  REG2_PHYID		GENMASK(21, 0)
18#define   EPHY_GXL_ID		0x110181
19#define  REG2_LEDACT		GENMASK(23, 22)
20#define  REG2_LEDLINK		GENMASK(25, 24)
21#define  REG2_DIV4SEL		BIT(27)
22#define  REG2_ADCBYPASS		BIT(30)
23#define  REG2_CLKINSEL		BIT(31)
24#define ETH_REG3		0x4
25#define  REG3_ENH		BIT(3)
26#define  REG3_CFGMODE		GENMASK(6, 4)
27#define  REG3_AUTOMDIX		BIT(7)
28#define  REG3_PHYADDR		GENMASK(12, 8)
29#define  REG3_PWRUPRST		BIT(21)
30#define  REG3_PWRDOWN		BIT(22)
31#define  REG3_LEDPOL		BIT(23)
32#define  REG3_PHYMDI		BIT(26)
33#define  REG3_CLKINEN		BIT(29)
34#define  REG3_PHYIP		BIT(30)
35#define  REG3_PHYEN		BIT(31)
36#define ETH_REG4		0x8
37#define  REG4_PWRUPRSTSIG	BIT(0)
38
39#define MESON_GXL_MDIO_EXTERNAL_ID 0
40#define MESON_GXL_MDIO_INTERNAL_ID 1
41
42struct mdio_mux_meson_gxl_priv {
43	phys_addr_t regs;
44};
45
46static int meson_gxl_enable_internal_mdio(struct mdio_mux_meson_gxl_priv *priv)
47{
48	u32 val;
49
50	/* Setup the internal phy */
51	val = (REG3_ENH |
52	       FIELD_PREP(REG3_CFGMODE, 0x7) |
53	       REG3_AUTOMDIX |
54	       FIELD_PREP(REG3_PHYADDR, 8) |
55	       REG3_LEDPOL |
56	       REG3_PHYMDI |
57	       REG3_CLKINEN |
58	       REG3_PHYIP);
59
60	writel(REG4_PWRUPRSTSIG, priv->regs + ETH_REG4);
61	writel(val, priv->regs + ETH_REG3);
62	mdelay(10);
63
64	/* NOTE: The HW kept the phy id configurable at runtime.
65	 * The id below is arbitrary. It is the one used in the vendor code.
66	 * The only constraint is that it must match the one in
67	 * drivers/net/phy/meson-gxl.c to properly match the PHY.
68	 */
69	writel(FIELD_PREP(REG2_PHYID, EPHY_GXL_ID),
70	       priv->regs + ETH_REG2);
71
72	/* Enable the internal phy */
73	val |= REG3_PHYEN;
74	writel(val, priv->regs + ETH_REG3);
75	writel(0, priv->regs + ETH_REG4);
76
77	/* The phy needs a bit of time to power up */
78	mdelay(10);
79
80	return 0;
81}
82
83static int meson_gxl_enable_external_mdio(struct mdio_mux_meson_gxl_priv *priv)
84{
85	/* Reset the mdio bus mux to the external phy */
86	writel(0, priv->regs + ETH_REG3);
87
88	return 0;
89}
90
91static int mdio_mux_meson_gxl_select(struct udevice *mux, int cur, int sel)
92{
93	struct mdio_mux_meson_gxl_priv *priv = dev_get_priv(mux);
94
95	debug("%s: %x -> %x\n", __func__, (u32)cur, (u32)sel);
96
97	/* if last selection didn't change we're good to go */
98	if (cur == sel)
99		return 0;
100
101	switch (sel) {
102	case MESON_GXL_MDIO_EXTERNAL_ID:
103		return meson_gxl_enable_external_mdio(priv);
104	case MESON_GXL_MDIO_INTERNAL_ID:
105		return meson_gxl_enable_internal_mdio(priv);
106	default:
107		return -EINVAL;
108	}
109
110	return 0;
111}
112
113static const struct mdio_mux_ops mdio_mux_meson_gxl_ops = {
114	.select = mdio_mux_meson_gxl_select,
115};
116
117static int mdio_mux_meson_gxl_probe(struct udevice *dev)
118{
119	struct mdio_mux_meson_gxl_priv *priv = dev_get_priv(dev);
120
121	priv->regs = dev_read_addr(dev);
122
123	return 0;
124}
125
126static const struct udevice_id mdio_mux_meson_gxl_ids[] = {
127	{ .compatible = "amlogic,gxl-mdio-mux" },
128	{ }
129};
130
131U_BOOT_DRIVER(mdio_mux_meson_gxl) = {
132	.name		= "mdio_mux_meson_gxl",
133	.id		= UCLASS_MDIO_MUX,
134	.of_match	= mdio_mux_meson_gxl_ids,
135	.probe		= mdio_mux_meson_gxl_probe,
136	.ops		= &mdio_mux_meson_gxl_ops,
137	.priv_auto	= sizeof(struct mdio_mux_meson_gxl_priv),
138};
139