1 // SPDX-License-Identifier: GPL-2.0+
3 * Broadcom BCM6368 mdiomux bus controller driver
5 * Copyright (C) 2021 Álvaro Fernández Rojas <noltari@gmail.com>
8 #include <linux/delay.h>
10 #include <linux/kernel.h>
11 #include <linux/mdio-mux.h>
12 #include <linux/module.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>
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)
29 struct bcm6368_mdiomux_desc
{
33 struct mii_bus
*mii_bus
;
37 static int bcm6368_mdiomux_read(struct mii_bus
*bus
, int phy_id
, int loc
)
39 struct bcm6368_mdiomux_desc
*md
= bus
->priv
;
43 __raw_writel(0, md
->base
+ MDIOC_REG
);
46 (phy_id
<< MDIOC_PHYID_SHIFT
) |
47 (loc
<< MDIOC_REG_SHIFT
);
49 reg
|= MDIOC_EXT_MASK
;
51 __raw_writel(reg
, md
->base
+ MDIOC_REG
);
53 ret
= __raw_readw(md
->base
+ MDIOD_REG
);
58 static int bcm6368_mdiomux_write(struct mii_bus
*bus
, int phy_id
, int loc
,
61 struct bcm6368_mdiomux_desc
*md
= bus
->priv
;
64 __raw_writel(0, md
->base
+ MDIOC_REG
);
67 (phy_id
<< MDIOC_PHYID_SHIFT
) |
68 (loc
<< MDIOC_REG_SHIFT
);
70 reg
|= MDIOC_EXT_MASK
;
73 __raw_writel(reg
, md
->base
+ MDIOC_REG
);
79 static int bcm6368_mdiomux_switch_fn(int current_child
, int desired_child
,
82 struct bcm6368_mdiomux_desc
*md
= data
;
84 md
->ext_phy
= desired_child
;
89 static int bcm6368_mdiomux_probe(struct platform_device
*pdev
)
91 struct bcm6368_mdiomux_desc
*md
;
96 md
= devm_kzalloc(&pdev
->dev
, sizeof(*md
), GFP_KERNEL
);
101 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
106 * Just ioremap, as this MDIO block is usually integrated into an
107 * Ethernet MAC controller register range
109 md
->base
= devm_ioremap(&pdev
->dev
, res
->start
, resource_size(res
));
111 dev_err(&pdev
->dev
, "failed to ioremap register\n");
115 md
->mii_bus
= devm_mdiobus_alloc(&pdev
->dev
);
117 dev_err(&pdev
->dev
, "mdiomux bus alloc failed\n");
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
;
131 rc
= mdiobus_register(bus
);
133 dev_err(&pdev
->dev
, "mdiomux registration failed\n");
137 platform_set_drvdata(pdev
, md
);
139 rc
= mdio_mux_init(md
->dev
, md
->dev
->of_node
,
140 bcm6368_mdiomux_switch_fn
, &md
->mux_handle
, md
,
143 dev_info(md
->dev
, "mdiomux initialization failed\n");
147 dev_info(&pdev
->dev
, "Broadcom BCM6368 MDIO mux bus\n");
152 mdiobus_unregister(bus
);
156 static void bcm6368_mdiomux_remove(struct platform_device
*pdev
)
158 struct bcm6368_mdiomux_desc
*md
= platform_get_drvdata(pdev
);
160 mdio_mux_uninit(md
->mux_handle
);
161 mdiobus_unregister(md
->mii_bus
);
164 static const struct of_device_id bcm6368_mdiomux_ids
[] = {
165 { .compatible
= "brcm,bcm6368-mdio-mux", },
168 MODULE_DEVICE_TABLE(of
, bcm6368_mdiomux_ids
);
170 static struct platform_driver bcm6368_mdiomux_driver
= {
172 .name
= "bcm6368-mdio-mux",
173 .of_match_table
= bcm6368_mdiomux_ids
,
175 .probe
= bcm6368_mdiomux_probe
,
176 .remove
= bcm6368_mdiomux_remove
,
178 module_platform_driver(bcm6368_mdiomux_driver
);
180 MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
181 MODULE_DESCRIPTION("BCM6368 mdiomux bus controller driver");
182 MODULE_LICENSE("GPL v2");