1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Broadcom BCM6368 mdiomux bus controller driver 4 * 5 * Copyright (C) 2021 Álvaro Fernández Rojas <noltari@gmail.com> 6 */ 7 8 #include <linux/delay.h> 9 #include <linux/io.h> 10 #include <linux/kernel.h> 11 #include <linux/mdio-mux.h> 12 #include <linux/module.h> 13 #include <linux/of.h> 14 #include <linux/of_platform.h> 15 #include <linux/of_mdio.h> 16 #include <linux/phy.h> 17 #include <linux/platform_device.h> 18 #include <linux/sched.h> 19 20 #define MDIOC_REG 0x0 21 #define MDIOC_EXT_MASK BIT(16) 22 #define MDIOC_REG_SHIFT 20 23 #define MDIOC_PHYID_SHIFT 25 24 #define MDIOC_RD_MASK BIT(30) 25 #define MDIOC_WR_MASK BIT(31) 26 27 #define MDIOD_REG 0x4 28 29 struct bcm6368_mdiomux_desc { 30 void *mux_handle; 31 void __iomem *base; 32 struct device *dev; 33 struct mii_bus *mii_bus; 34 int ext_phy; 35 }; 36 37 static int bcm6368_mdiomux_read(struct mii_bus *bus, int phy_id, int loc) 38 { 39 struct bcm6368_mdiomux_desc *md = bus->priv; 40 uint32_t reg; 41 int ret; 42 43 __raw_writel(0, md->base + MDIOC_REG); 44 45 reg = MDIOC_RD_MASK | 46 (phy_id << MDIOC_PHYID_SHIFT) | 47 (loc << MDIOC_REG_SHIFT); 48 if (md->ext_phy) 49 reg |= MDIOC_EXT_MASK; 50 51 __raw_writel(reg, md->base + MDIOC_REG); 52 udelay(50); 53 ret = __raw_readw(md->base + MDIOD_REG); 54 55 return ret; 56 } 57 58 static int bcm6368_mdiomux_write(struct mii_bus *bus, int phy_id, int loc, 59 uint16_t val) 60 { 61 struct bcm6368_mdiomux_desc *md = bus->priv; 62 uint32_t reg; 63 64 __raw_writel(0, md->base + MDIOC_REG); 65 66 reg = MDIOC_WR_MASK | 67 (phy_id << MDIOC_PHYID_SHIFT) | 68 (loc << MDIOC_REG_SHIFT); 69 if (md->ext_phy) 70 reg |= MDIOC_EXT_MASK; 71 reg |= val; 72 73 __raw_writel(reg, md->base + MDIOC_REG); 74 udelay(50); 75 76 return 0; 77 } 78 79 static int bcm6368_mdiomux_switch_fn(int current_child, int desired_child, 80 void *data) 81 { 82 struct bcm6368_mdiomux_desc *md = data; 83 84 md->ext_phy = desired_child; 85 86 return 0; 87 } 88 89 static int bcm6368_mdiomux_probe(struct platform_device *pdev) 90 { 91 struct bcm6368_mdiomux_desc *md; 92 struct mii_bus *bus; 93 struct resource *res; 94 int rc; 95 96 md = devm_kzalloc(&pdev->dev, sizeof(*md), GFP_KERNEL); 97 if (!md) 98 return -ENOMEM; 99 md->dev = &pdev->dev; 100 101 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 102 if (!res) 103 return -EINVAL; 104 105 /* 106 * Just ioremap, as this MDIO block is usually integrated into an 107 * Ethernet MAC controller register range 108 */ 109 md->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); 110 if (!md->base) { 111 dev_err(&pdev->dev, "failed to ioremap register\n"); 112 return -ENOMEM; 113 } 114 115 md->mii_bus = devm_mdiobus_alloc(&pdev->dev); 116 if (!md->mii_bus) { 117 dev_err(&pdev->dev, "mdiomux bus alloc failed\n"); 118 return -ENOMEM; 119 } 120 121 bus = md->mii_bus; 122 bus->priv = md; 123 bus->name = "BCM6368 MDIO mux bus"; 124 snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id); 125 bus->parent = &pdev->dev; 126 bus->read = bcm6368_mdiomux_read; 127 bus->write = bcm6368_mdiomux_write; 128 bus->phy_mask = 0x3f; 129 bus->dev.of_node = pdev->dev.of_node; 130 131 rc = mdiobus_register(bus); 132 if (rc) { 133 dev_err(&pdev->dev, "mdiomux registration failed\n"); 134 return rc; 135 } 136 137 platform_set_drvdata(pdev, md); 138 139 rc = mdio_mux_init(md->dev, md->dev->of_node, 140 bcm6368_mdiomux_switch_fn, &md->mux_handle, md, 141 md->mii_bus); 142 if (rc) { 143 dev_info(md->dev, "mdiomux initialization failed\n"); 144 goto out_register; 145 } 146 147 dev_info(&pdev->dev, "Broadcom BCM6368 MDIO mux bus\n"); 148 149 return 0; 150 151 out_register: 152 mdiobus_unregister(bus); 153 return rc; 154 } 155 156 static int bcm6368_mdiomux_remove(struct platform_device *pdev) 157 { 158 struct bcm6368_mdiomux_desc *md = platform_get_drvdata(pdev); 159 160 mdio_mux_uninit(md->mux_handle); 161 mdiobus_unregister(md->mii_bus); 162 163 return 0; 164 } 165 166 static const struct of_device_id bcm6368_mdiomux_ids[] = { 167 { .compatible = "brcm,bcm6368-mdio-mux", }, 168 { /* sentinel */ } 169 }; 170 MODULE_DEVICE_TABLE(of, bcm6368_mdiomux_ids); 171 172 static struct platform_driver bcm6368_mdiomux_driver = { 173 .driver = { 174 .name = "bcm6368-mdio-mux", 175 .of_match_table = bcm6368_mdiomux_ids, 176 }, 177 .probe = bcm6368_mdiomux_probe, 178 .remove = bcm6368_mdiomux_remove, 179 }; 180 module_platform_driver(bcm6368_mdiomux_driver); 181 182 MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>"); 183 MODULE_DESCRIPTION("BCM6368 mdiomux bus controller driver"); 184 MODULE_LICENSE("GPL v2"); 185