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_REVERSED BIT(28) 21 #define REG2_ADCBYPASS BIT(30) 22 #define REG2_CLKINSEL BIT(31) 23 #define ETH_REG3 0x4 24 #define REG3_ENH BIT(3) 25 #define REG3_CFGMODE GENMASK(6, 4) 26 #define REG3_AUTOMDIX BIT(7) 27 #define REG3_PHYADDR GENMASK(12, 8) 28 #define REG3_PWRUPRST BIT(21) 29 #define REG3_PWRDOWN BIT(22) 30 #define REG3_LEDPOL BIT(23) 31 #define REG3_PHYMDI BIT(26) 32 #define REG3_CLKINEN BIT(29) 33 #define REG3_PHYIP BIT(30) 34 #define REG3_PHYEN BIT(31) 35 #define ETH_REG4 0x8 36 #define REG4_PWRUPRSTSIG BIT(0) 37 38 #define MESON_GXL_MDIO_EXTERNAL_ID 0 39 #define MESON_GXL_MDIO_INTERNAL_ID 1 40 41 struct gxl_mdio_mux { 42 void __iomem *regs; 43 void *mux_handle; 44 }; 45 46 static void gxl_enable_internal_mdio(struct gxl_mdio_mux *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(REG2_REVERSED | 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 81 static void gxl_enable_external_mdio(struct gxl_mdio_mux *priv) 82 { 83 /* Reset the mdio bus mux to the external phy */ 84 writel(0, priv->regs + ETH_REG3); 85 } 86 87 static int gxl_mdio_switch_fn(int current_child, int desired_child, 88 void *data) 89 { 90 struct gxl_mdio_mux *priv = dev_get_drvdata(data); 91 92 if (current_child == desired_child) 93 return 0; 94 95 switch (desired_child) { 96 case MESON_GXL_MDIO_EXTERNAL_ID: 97 gxl_enable_external_mdio(priv); 98 break; 99 case MESON_GXL_MDIO_INTERNAL_ID: 100 gxl_enable_internal_mdio(priv); 101 break; 102 default: 103 return -EINVAL; 104 } 105 106 return 0; 107 } 108 109 static const struct of_device_id gxl_mdio_mux_match[] = { 110 { .compatible = "amlogic,gxl-mdio-mux", }, 111 {}, 112 }; 113 MODULE_DEVICE_TABLE(of, gxl_mdio_mux_match); 114 115 static int gxl_mdio_mux_probe(struct platform_device *pdev) 116 { 117 struct device *dev = &pdev->dev; 118 struct gxl_mdio_mux *priv; 119 struct clk *rclk; 120 int ret; 121 122 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 123 if (!priv) 124 return -ENOMEM; 125 platform_set_drvdata(pdev, priv); 126 127 priv->regs = devm_platform_ioremap_resource(pdev, 0); 128 if (IS_ERR(priv->regs)) 129 return PTR_ERR(priv->regs); 130 131 rclk = devm_clk_get_enabled(dev, "ref"); 132 if (IS_ERR(rclk)) 133 return dev_err_probe(dev, PTR_ERR(rclk), 134 "failed to get reference clock\n"); 135 136 ret = mdio_mux_init(dev, dev->of_node, gxl_mdio_switch_fn, 137 &priv->mux_handle, dev, NULL); 138 if (ret) 139 dev_err_probe(dev, ret, "mdio multiplexer init failed\n"); 140 141 return ret; 142 } 143 144 static void gxl_mdio_mux_remove(struct platform_device *pdev) 145 { 146 struct gxl_mdio_mux *priv = platform_get_drvdata(pdev); 147 148 mdio_mux_uninit(priv->mux_handle); 149 } 150 151 static struct platform_driver gxl_mdio_mux_driver = { 152 .probe = gxl_mdio_mux_probe, 153 .remove = gxl_mdio_mux_remove, 154 .driver = { 155 .name = "gxl-mdio-mux", 156 .of_match_table = gxl_mdio_mux_match, 157 }, 158 }; 159 module_platform_driver(gxl_mdio_mux_driver); 160 161 MODULE_DESCRIPTION("Amlogic GXL MDIO multiplexer driver"); 162 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); 163 MODULE_LICENSE("GPL"); 164