183 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Broadcom BCM6368 mdiomux bus controller driver4 *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 0x021#define MDIOC_EXT_MASK BIT(16)22#define MDIOC_REG_SHIFT 2023#define MDIOC_PHYID_SHIFT 2524#define MDIOC_RD_MASK BIT(30)25#define MDIOC_WR_MASK BIT(31)26 27#define MDIOD_REG 0x428 29struct 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 37static 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 58static 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 79static 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 89static 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 an107 * Ethernet MAC controller register range108 */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 151out_register:152 mdiobus_unregister(bus);153 return rc;154}155 156static void 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 164static const struct of_device_id bcm6368_mdiomux_ids[] = {165 { .compatible = "brcm,bcm6368-mdio-mux", },166 { /* sentinel */ }167};168MODULE_DEVICE_TABLE(of, bcm6368_mdiomux_ids);169 170static struct platform_driver bcm6368_mdiomux_driver = {171 .driver = {172 .name = "bcm6368-mdio-mux",173 .of_match_table = bcm6368_mdiomux_ids,174 },175 .probe = bcm6368_mdiomux_probe,176 .remove_new = bcm6368_mdiomux_remove,177};178module_platform_driver(bcm6368_mdiomux_driver);179 180MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");181MODULE_DESCRIPTION("BCM6368 mdiomux bus controller driver");182MODULE_LICENSE("GPL v2");183