1  // SPDX-License-Identifier: GPL-2.0
2  /* Copyright (c) 2022 Baylibre, SAS.
3   * Author: Jerome Brunet <jbrunet@baylibre.com>
4   */
5  
6  #include <linux/bitfield.h>
7  #include <linux/delay.h>
8  #include <linux/clk.h>
9  #include <linux/io.h>
10  #include <linux/mdio-mux.h>
11  #include <linux/module.h>
12  #include <linux/platform_device.h>
13  
14  #define ETH_REG2		0x0
15  #define  REG2_PHYID		GENMASK(21, 0)
16  #define   EPHY_GXL_ID		0x110181
17  #define  REG2_LEDACT		GENMASK(23, 22)
18  #define  REG2_LEDLINK		GENMASK(25, 24)
19  #define  REG2_DIV4SEL		BIT(27)
20  #define  REG2_ADCBYPASS		BIT(30)
21  #define  REG2_CLKINSEL		BIT(31)
22  #define ETH_REG3		0x4
23  #define  REG3_ENH		BIT(3)
24  #define  REG3_CFGMODE		GENMASK(6, 4)
25  #define  REG3_AUTOMDIX		BIT(7)
26  #define  REG3_PHYADDR		GENMASK(12, 8)
27  #define  REG3_PWRUPRST		BIT(21)
28  #define  REG3_PWRDOWN		BIT(22)
29  #define  REG3_LEDPOL		BIT(23)
30  #define  REG3_PHYMDI		BIT(26)
31  #define  REG3_CLKINEN		BIT(29)
32  #define  REG3_PHYIP		BIT(30)
33  #define  REG3_PHYEN		BIT(31)
34  #define ETH_REG4		0x8
35  #define  REG4_PWRUPRSTSIG	BIT(0)
36  
37  #define MESON_GXL_MDIO_EXTERNAL_ID 0
38  #define MESON_GXL_MDIO_INTERNAL_ID 1
39  
40  struct gxl_mdio_mux {
41  	void __iomem *regs;
42  	void *mux_handle;
43  };
44  
gxl_enable_internal_mdio(struct gxl_mdio_mux * priv)45  static void gxl_enable_internal_mdio(struct gxl_mdio_mux *priv)
46  {
47  	u32 val;
48  
49  	/* Setup the internal phy */
50  	val = (REG3_ENH |
51  	       FIELD_PREP(REG3_CFGMODE, 0x7) |
52  	       REG3_AUTOMDIX |
53  	       FIELD_PREP(REG3_PHYADDR, 8) |
54  	       REG3_LEDPOL |
55  	       REG3_PHYMDI |
56  	       REG3_CLKINEN |
57  	       REG3_PHYIP);
58  
59  	writel(REG4_PWRUPRSTSIG, priv->regs + ETH_REG4);
60  	writel(val, priv->regs + ETH_REG3);
61  	mdelay(10);
62  
63  	/* NOTE: The HW kept the phy id configurable at runtime.
64  	 * The id below is arbitrary. It is the one used in the vendor code.
65  	 * The only constraint is that it must match the one in
66  	 * drivers/net/phy/meson-gxl.c to properly match the PHY.
67  	 */
68  	writel(FIELD_PREP(REG2_PHYID, EPHY_GXL_ID),
69  	       priv->regs + ETH_REG2);
70  
71  	/* Enable the internal phy */
72  	val |= REG3_PHYEN;
73  	writel(val, priv->regs + ETH_REG3);
74  	writel(0, priv->regs + ETH_REG4);
75  
76  	/* The phy needs a bit of time to power up */
77  	mdelay(10);
78  }
79  
gxl_enable_external_mdio(struct gxl_mdio_mux * priv)80  static void gxl_enable_external_mdio(struct gxl_mdio_mux *priv)
81  {
82  	/* Reset the mdio bus mux to the external phy */
83  	writel(0, priv->regs + ETH_REG3);
84  }
85  
gxl_mdio_switch_fn(int current_child,int desired_child,void * data)86  static int gxl_mdio_switch_fn(int current_child, int desired_child,
87  			      void *data)
88  {
89  	struct gxl_mdio_mux *priv = dev_get_drvdata(data);
90  
91  	if (current_child == desired_child)
92  		return 0;
93  
94  	switch (desired_child) {
95  	case MESON_GXL_MDIO_EXTERNAL_ID:
96  		gxl_enable_external_mdio(priv);
97  		break;
98  	case MESON_GXL_MDIO_INTERNAL_ID:
99  		gxl_enable_internal_mdio(priv);
100  		break;
101  	default:
102  		return -EINVAL;
103  	}
104  
105  	return 0;
106  }
107  
108  static const struct of_device_id gxl_mdio_mux_match[] = {
109  	{ .compatible = "amlogic,gxl-mdio-mux", },
110  	{},
111  };
112  MODULE_DEVICE_TABLE(of, gxl_mdio_mux_match);
113  
gxl_mdio_mux_probe(struct platform_device * pdev)114  static int gxl_mdio_mux_probe(struct platform_device *pdev)
115  {
116  	struct device *dev = &pdev->dev;
117  	struct gxl_mdio_mux *priv;
118  	struct clk *rclk;
119  	int ret;
120  
121  	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
122  	if (!priv)
123  		return -ENOMEM;
124  	platform_set_drvdata(pdev, priv);
125  
126  	priv->regs = devm_platform_ioremap_resource(pdev, 0);
127  	if (IS_ERR(priv->regs))
128  		return PTR_ERR(priv->regs);
129  
130  	rclk = devm_clk_get_enabled(dev, "ref");
131  	if (IS_ERR(rclk))
132  		return dev_err_probe(dev, PTR_ERR(rclk),
133  				     "failed to get reference clock\n");
134  
135  	ret = mdio_mux_init(dev, dev->of_node, gxl_mdio_switch_fn,
136  			    &priv->mux_handle, dev, NULL);
137  	if (ret)
138  		dev_err_probe(dev, ret, "mdio multiplexer init failed\n");
139  
140  	return ret;
141  }
142  
gxl_mdio_mux_remove(struct platform_device * pdev)143  static void gxl_mdio_mux_remove(struct platform_device *pdev)
144  {
145  	struct gxl_mdio_mux *priv = platform_get_drvdata(pdev);
146  
147  	mdio_mux_uninit(priv->mux_handle);
148  }
149  
150  static struct platform_driver gxl_mdio_mux_driver = {
151  	.probe		= gxl_mdio_mux_probe,
152  	.remove_new	= gxl_mdio_mux_remove,
153  	.driver		= {
154  		.name	= "gxl-mdio-mux",
155  		.of_match_table = gxl_mdio_mux_match,
156  	},
157  };
158  module_platform_driver(gxl_mdio_mux_driver);
159  
160  MODULE_DESCRIPTION("Amlogic GXL MDIO multiplexer driver");
161  MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
162  MODULE_LICENSE("GPL");
163