blob: 97cac10bd0d59cf8bac41a72958afb9029303c69 [file] [log] [blame]
Florian Fainelli2ba1b162014-08-27 17:04:47 -07001/*
2 * Broadcom UniMAC MDIO bus controller driver
3 *
Doug Berger42138082017-03-13 17:41:42 -07004 * Copyright (C) 2014-2017 Broadcom
Florian Fainelli2ba1b162014-08-27 17:04:47 -07005 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 */
11
12#include <linux/kernel.h>
13#include <linux/phy.h>
14#include <linux/platform_device.h>
15#include <linux/sched.h>
16#include <linux/module.h>
17#include <linux/io.h>
18#include <linux/delay.h>
19
20#include <linux/of.h>
21#include <linux/of_platform.h>
22#include <linux/of_mdio.h>
23
24#define MDIO_CMD 0x00
25#define MDIO_START_BUSY (1 << 29)
26#define MDIO_READ_FAIL (1 << 28)
27#define MDIO_RD (2 << 26)
28#define MDIO_WR (1 << 26)
29#define MDIO_PMD_SHIFT 21
30#define MDIO_PMD_MASK 0x1F
31#define MDIO_REG_SHIFT 16
32#define MDIO_REG_MASK 0x1F
33
34#define MDIO_CFG 0x04
35#define MDIO_C22 (1 << 0)
36#define MDIO_C45 0
37#define MDIO_CLK_DIV_SHIFT 4
38#define MDIO_CLK_DIV_MASK 0x3F
39#define MDIO_SUPP_PREAMBLE (1 << 12)
40
41struct unimac_mdio_priv {
42 struct mii_bus *mii_bus;
43 void __iomem *base;
44};
45
46static inline void unimac_mdio_start(struct unimac_mdio_priv *priv)
47{
48 u32 reg;
49
50 reg = __raw_readl(priv->base + MDIO_CMD);
51 reg |= MDIO_START_BUSY;
52 __raw_writel(reg, priv->base + MDIO_CMD);
53}
54
55static inline unsigned int unimac_mdio_busy(struct unimac_mdio_priv *priv)
56{
57 return __raw_readl(priv->base + MDIO_CMD) & MDIO_START_BUSY;
58}
59
Florian Fainelli69a60b02017-07-31 12:04:22 -070060static int unimac_mdio_poll(struct unimac_mdio_priv *priv)
61{
62 unsigned int timeout = 1000;
63
64 do {
65 if (!unimac_mdio_busy(priv))
66 return 0;
67
68 usleep_range(1000, 2000);
69 } while (timeout--);
70
71 if (!timeout)
72 return -ETIMEDOUT;
73
74 return 0;
75}
76
Florian Fainelli2ba1b162014-08-27 17:04:47 -070077static int unimac_mdio_read(struct mii_bus *bus, int phy_id, int reg)
78{
79 struct unimac_mdio_priv *priv = bus->priv;
Florian Fainelli2ba1b162014-08-27 17:04:47 -070080 u32 cmd;
81
82 /* Prepare the read operation */
83 cmd = MDIO_RD | (phy_id << MDIO_PMD_SHIFT) | (reg << MDIO_REG_SHIFT);
84 __raw_writel(cmd, priv->base + MDIO_CMD);
85
86 /* Start MDIO transaction */
87 unimac_mdio_start(priv);
88
Florian Fainelli69a60b02017-07-31 12:04:22 -070089 ret = unimac_mdio_poll(priv);
90 if (ret)
91 return ret;
Florian Fainelli2ba1b162014-08-27 17:04:47 -070092
93 cmd = __raw_readl(priv->base + MDIO_CMD);
Florian Fainelli1a3f4e82015-06-10 12:24:11 -070094
95 /* Some broken devices are known not to release the line during
96 * turn-around, e.g: Broadcom BCM53125 external switches, so check for
97 * that condition here and ignore the MDIO controller read failure
98 * indication.
99 */
100 if (!(bus->phy_ignore_ta_mask & 1 << phy_id) && (cmd & MDIO_READ_FAIL))
Florian Fainelli2ba1b162014-08-27 17:04:47 -0700101 return -EIO;
102
103 return cmd & 0xffff;
104}
105
106static int unimac_mdio_write(struct mii_bus *bus, int phy_id,
107 int reg, u16 val)
108{
109 struct unimac_mdio_priv *priv = bus->priv;
Florian Fainelli2ba1b162014-08-27 17:04:47 -0700110 u32 cmd;
111
112 /* Prepare the write operation */
113 cmd = MDIO_WR | (phy_id << MDIO_PMD_SHIFT) |
114 (reg << MDIO_REG_SHIFT) | (0xffff & val);
115 __raw_writel(cmd, priv->base + MDIO_CMD);
116
117 unimac_mdio_start(priv);
118
Florian Fainelli69a60b02017-07-31 12:04:22 -0700119 return unimac_mdio_poll(priv);
Florian Fainelli2ba1b162014-08-27 17:04:47 -0700120}
121
Florian Fainellid8e704e2015-06-26 10:39:06 -0700122/* Workaround for integrated BCM7xxx Gigabit PHYs which have a problem with
123 * their internal MDIO management controller making them fail to successfully
124 * be read from or written to for the first transaction. We insert a dummy
125 * BMSR read here to make sure that phy_get_device() and get_phy_id() can
126 * correctly read the PHY MII_PHYSID1/2 registers and successfully register a
127 * PHY device for this peripheral.
128 *
129 * Once the PHY driver is registered, we can workaround subsequent reads from
130 * there (e.g: during system-wide power management).
131 *
132 * bus->reset is invoked before mdiobus_scan during mdiobus_register and is
133 * therefore the right location to stick that workaround. Since we do not want
134 * to read from non-existing PHYs, we either use bus->phy_mask or do a manual
135 * Device Tree scan to limit the search area.
136 */
137static int unimac_mdio_reset(struct mii_bus *bus)
138{
139 struct device_node *np = bus->dev.of_node;
140 struct device_node *child;
141 u32 read_mask = 0;
142 int addr;
143
144 if (!np) {
145 read_mask = ~bus->phy_mask;
146 } else {
147 for_each_available_child_of_node(np, child) {
148 addr = of_mdio_parse_addr(&bus->dev, child);
149 if (addr < 0)
150 continue;
151
152 read_mask |= 1 << addr;
153 }
154 }
155
156 for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
157 if (read_mask & 1 << addr)
158 mdiobus_read(bus, addr, MII_BMSR);
159 }
160
161 return 0;
162}
163
Florian Fainelli2ba1b162014-08-27 17:04:47 -0700164static int unimac_mdio_probe(struct platform_device *pdev)
165{
166 struct unimac_mdio_priv *priv;
167 struct device_node *np;
168 struct mii_bus *bus;
169 struct resource *r;
170 int ret;
171
172 np = pdev->dev.of_node;
173
174 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
175 if (!priv)
176 return -ENOMEM;
177
178 r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
179
180 /* Just ioremap, as this MDIO block is usually integrated into an
181 * Ethernet MAC controller register range
182 */
183 priv->base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
184 if (!priv->base) {
185 dev_err(&pdev->dev, "failed to remap register\n");
186 return -ENOMEM;
187 }
188
189 priv->mii_bus = mdiobus_alloc();
190 if (!priv->mii_bus)
191 return -ENOMEM;
192
193 bus = priv->mii_bus;
194 bus->priv = priv;
195 bus->name = "unimac MII bus";
196 bus->parent = &pdev->dev;
197 bus->read = unimac_mdio_read;
198 bus->write = unimac_mdio_write;
Florian Fainellid8e704e2015-06-26 10:39:06 -0700199 bus->reset = unimac_mdio_reset;
Florian Fainellid782f7c2017-07-31 12:04:23 -0700200 snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id);
Florian Fainelli2ba1b162014-08-27 17:04:47 -0700201
Florian Fainelli2ba1b162014-08-27 17:04:47 -0700202 ret = of_mdiobus_register(bus, np);
203 if (ret) {
204 dev_err(&pdev->dev, "MDIO bus registration failed\n");
Andrew Lunne7f4dc32016-01-06 20:11:15 +0100205 goto out_mdio_free;
Florian Fainelli2ba1b162014-08-27 17:04:47 -0700206 }
207
208 platform_set_drvdata(pdev, priv);
209
210 dev_info(&pdev->dev, "Broadcom UniMAC MDIO bus at 0x%p\n", priv->base);
211
212 return 0;
213
Florian Fainelli2ba1b162014-08-27 17:04:47 -0700214out_mdio_free:
215 mdiobus_free(bus);
216 return ret;
217}
218
219static int unimac_mdio_remove(struct platform_device *pdev)
220{
221 struct unimac_mdio_priv *priv = platform_get_drvdata(pdev);
222
223 mdiobus_unregister(priv->mii_bus);
Florian Fainelli2ba1b162014-08-27 17:04:47 -0700224 mdiobus_free(priv->mii_bus);
225
226 return 0;
227}
228
Fabian Frederickd8a7dad2015-03-17 19:40:23 +0100229static const struct of_device_id unimac_mdio_ids[] = {
Doug Berger42138082017-03-13 17:41:42 -0700230 { .compatible = "brcm,genet-mdio-v5", },
Florian Fainelli2ba1b162014-08-27 17:04:47 -0700231 { .compatible = "brcm,genet-mdio-v4", },
232 { .compatible = "brcm,genet-mdio-v3", },
233 { .compatible = "brcm,genet-mdio-v2", },
234 { .compatible = "brcm,genet-mdio-v1", },
235 { .compatible = "brcm,unimac-mdio", },
Florian Fainelli45591542014-08-29 12:43:56 -0700236 { /* sentinel */ },
Florian Fainelli2ba1b162014-08-27 17:04:47 -0700237};
Luis de Bethencourt2f90a302015-09-18 18:16:12 +0200238MODULE_DEVICE_TABLE(of, unimac_mdio_ids);
Florian Fainelli2ba1b162014-08-27 17:04:47 -0700239
240static struct platform_driver unimac_mdio_driver = {
241 .driver = {
242 .name = "unimac-mdio",
Florian Fainelli2ba1b162014-08-27 17:04:47 -0700243 .of_match_table = unimac_mdio_ids,
244 },
245 .probe = unimac_mdio_probe,
246 .remove = unimac_mdio_remove,
247};
248module_platform_driver(unimac_mdio_driver);
249
250MODULE_AUTHOR("Broadcom Corporation");
251MODULE_DESCRIPTION("Broadcom UniMAC MDIO bus controller");
252MODULE_LICENSE("GPL");
253MODULE_ALIAS("platform:unimac-mdio");