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 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 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 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 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 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