162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/* Copyright (c) 2022 Baylibre, SAS.
362306a36Sopenharmony_ci * Author: Jerome Brunet <jbrunet@baylibre.com>
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/bitfield.h>
762306a36Sopenharmony_ci#include <linux/delay.h>
862306a36Sopenharmony_ci#include <linux/clk.h>
962306a36Sopenharmony_ci#include <linux/io.h>
1062306a36Sopenharmony_ci#include <linux/mdio-mux.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/platform_device.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#define ETH_REG2		0x0
1562306a36Sopenharmony_ci#define  REG2_PHYID		GENMASK(21, 0)
1662306a36Sopenharmony_ci#define   EPHY_GXL_ID		0x110181
1762306a36Sopenharmony_ci#define  REG2_LEDACT		GENMASK(23, 22)
1862306a36Sopenharmony_ci#define  REG2_LEDLINK		GENMASK(25, 24)
1962306a36Sopenharmony_ci#define  REG2_DIV4SEL		BIT(27)
2062306a36Sopenharmony_ci#define  REG2_ADCBYPASS		BIT(30)
2162306a36Sopenharmony_ci#define  REG2_CLKINSEL		BIT(31)
2262306a36Sopenharmony_ci#define ETH_REG3		0x4
2362306a36Sopenharmony_ci#define  REG3_ENH		BIT(3)
2462306a36Sopenharmony_ci#define  REG3_CFGMODE		GENMASK(6, 4)
2562306a36Sopenharmony_ci#define  REG3_AUTOMDIX		BIT(7)
2662306a36Sopenharmony_ci#define  REG3_PHYADDR		GENMASK(12, 8)
2762306a36Sopenharmony_ci#define  REG3_PWRUPRST		BIT(21)
2862306a36Sopenharmony_ci#define  REG3_PWRDOWN		BIT(22)
2962306a36Sopenharmony_ci#define  REG3_LEDPOL		BIT(23)
3062306a36Sopenharmony_ci#define  REG3_PHYMDI		BIT(26)
3162306a36Sopenharmony_ci#define  REG3_CLKINEN		BIT(29)
3262306a36Sopenharmony_ci#define  REG3_PHYIP		BIT(30)
3362306a36Sopenharmony_ci#define  REG3_PHYEN		BIT(31)
3462306a36Sopenharmony_ci#define ETH_REG4		0x8
3562306a36Sopenharmony_ci#define  REG4_PWRUPRSTSIG	BIT(0)
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define MESON_GXL_MDIO_EXTERNAL_ID 0
3862306a36Sopenharmony_ci#define MESON_GXL_MDIO_INTERNAL_ID 1
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistruct gxl_mdio_mux {
4162306a36Sopenharmony_ci	void __iomem *regs;
4262306a36Sopenharmony_ci	void *mux_handle;
4362306a36Sopenharmony_ci};
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cistatic void gxl_enable_internal_mdio(struct gxl_mdio_mux *priv)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	u32 val;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	/* Setup the internal phy */
5062306a36Sopenharmony_ci	val = (REG3_ENH |
5162306a36Sopenharmony_ci	       FIELD_PREP(REG3_CFGMODE, 0x7) |
5262306a36Sopenharmony_ci	       REG3_AUTOMDIX |
5362306a36Sopenharmony_ci	       FIELD_PREP(REG3_PHYADDR, 8) |
5462306a36Sopenharmony_ci	       REG3_LEDPOL |
5562306a36Sopenharmony_ci	       REG3_PHYMDI |
5662306a36Sopenharmony_ci	       REG3_CLKINEN |
5762306a36Sopenharmony_ci	       REG3_PHYIP);
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	writel(REG4_PWRUPRSTSIG, priv->regs + ETH_REG4);
6062306a36Sopenharmony_ci	writel(val, priv->regs + ETH_REG3);
6162306a36Sopenharmony_ci	mdelay(10);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	/* NOTE: The HW kept the phy id configurable at runtime.
6462306a36Sopenharmony_ci	 * The id below is arbitrary. It is the one used in the vendor code.
6562306a36Sopenharmony_ci	 * The only constraint is that it must match the one in
6662306a36Sopenharmony_ci	 * drivers/net/phy/meson-gxl.c to properly match the PHY.
6762306a36Sopenharmony_ci	 */
6862306a36Sopenharmony_ci	writel(FIELD_PREP(REG2_PHYID, EPHY_GXL_ID),
6962306a36Sopenharmony_ci	       priv->regs + ETH_REG2);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	/* Enable the internal phy */
7262306a36Sopenharmony_ci	val |= REG3_PHYEN;
7362306a36Sopenharmony_ci	writel(val, priv->regs + ETH_REG3);
7462306a36Sopenharmony_ci	writel(0, priv->regs + ETH_REG4);
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	/* The phy needs a bit of time to power up */
7762306a36Sopenharmony_ci	mdelay(10);
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic void gxl_enable_external_mdio(struct gxl_mdio_mux *priv)
8162306a36Sopenharmony_ci{
8262306a36Sopenharmony_ci	/* Reset the mdio bus mux to the external phy */
8362306a36Sopenharmony_ci	writel(0, priv->regs + ETH_REG3);
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic int gxl_mdio_switch_fn(int current_child, int desired_child,
8762306a36Sopenharmony_ci			      void *data)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	struct gxl_mdio_mux *priv = dev_get_drvdata(data);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	if (current_child == desired_child)
9262306a36Sopenharmony_ci		return 0;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	switch (desired_child) {
9562306a36Sopenharmony_ci	case MESON_GXL_MDIO_EXTERNAL_ID:
9662306a36Sopenharmony_ci		gxl_enable_external_mdio(priv);
9762306a36Sopenharmony_ci		break;
9862306a36Sopenharmony_ci	case MESON_GXL_MDIO_INTERNAL_ID:
9962306a36Sopenharmony_ci		gxl_enable_internal_mdio(priv);
10062306a36Sopenharmony_ci		break;
10162306a36Sopenharmony_ci	default:
10262306a36Sopenharmony_ci		return -EINVAL;
10362306a36Sopenharmony_ci	}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	return 0;
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic const struct of_device_id gxl_mdio_mux_match[] = {
10962306a36Sopenharmony_ci	{ .compatible = "amlogic,gxl-mdio-mux", },
11062306a36Sopenharmony_ci	{},
11162306a36Sopenharmony_ci};
11262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, gxl_mdio_mux_match);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_cistatic int gxl_mdio_mux_probe(struct platform_device *pdev)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
11762306a36Sopenharmony_ci	struct gxl_mdio_mux *priv;
11862306a36Sopenharmony_ci	struct clk *rclk;
11962306a36Sopenharmony_ci	int ret;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
12262306a36Sopenharmony_ci	if (!priv)
12362306a36Sopenharmony_ci		return -ENOMEM;
12462306a36Sopenharmony_ci	platform_set_drvdata(pdev, priv);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	priv->regs = devm_platform_ioremap_resource(pdev, 0);
12762306a36Sopenharmony_ci	if (IS_ERR(priv->regs))
12862306a36Sopenharmony_ci		return PTR_ERR(priv->regs);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	rclk = devm_clk_get_enabled(dev, "ref");
13162306a36Sopenharmony_ci	if (IS_ERR(rclk))
13262306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(rclk),
13362306a36Sopenharmony_ci				     "failed to get reference clock\n");
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	ret = mdio_mux_init(dev, dev->of_node, gxl_mdio_switch_fn,
13662306a36Sopenharmony_ci			    &priv->mux_handle, dev, NULL);
13762306a36Sopenharmony_ci	if (ret)
13862306a36Sopenharmony_ci		dev_err_probe(dev, ret, "mdio multiplexer init failed\n");
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	return ret;
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistatic int gxl_mdio_mux_remove(struct platform_device *pdev)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	struct gxl_mdio_mux *priv = platform_get_drvdata(pdev);
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	mdio_mux_uninit(priv->mux_handle);
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	return 0;
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic struct platform_driver gxl_mdio_mux_driver = {
15362306a36Sopenharmony_ci	.probe		= gxl_mdio_mux_probe,
15462306a36Sopenharmony_ci	.remove		= gxl_mdio_mux_remove,
15562306a36Sopenharmony_ci	.driver		= {
15662306a36Sopenharmony_ci		.name	= "gxl-mdio-mux",
15762306a36Sopenharmony_ci		.of_match_table = gxl_mdio_mux_match,
15862306a36Sopenharmony_ci	},
15962306a36Sopenharmony_ci};
16062306a36Sopenharmony_cimodule_platform_driver(gxl_mdio_mux_driver);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ciMODULE_DESCRIPTION("Amlogic GXL MDIO multiplexer driver");
16362306a36Sopenharmony_ciMODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
16462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
165