Commit | Line | Data |
---|---|---|
a2443fd1 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
2ba1b163 FF |
2 | /* |
3 | * Broadcom UniMAC MDIO bus controller driver | |
4 | * | |
42138085 | 5 | * Copyright (C) 2014-2017 Broadcom |
2ba1b163 FF |
6 | */ |
7 | ||
8 | #include <linux/kernel.h> | |
9 | #include <linux/phy.h> | |
10 | #include <linux/platform_device.h> | |
11 | #include <linux/sched.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/io.h> | |
14 | #include <linux/delay.h> | |
b78ac6ec | 15 | #include <linux/clk.h> |
2ba1b163 FF |
16 | |
17 | #include <linux/of.h> | |
18 | #include <linux/of_platform.h> | |
19 | #include <linux/of_mdio.h> | |
20 | ||
f248aff8 FF |
21 | #include <linux/platform_data/mdio-bcm-unimac.h> |
22 | ||
2ba1b163 FF |
23 | #define MDIO_CMD 0x00 |
24 | #define MDIO_START_BUSY (1 << 29) | |
25 | #define MDIO_READ_FAIL (1 << 28) | |
26 | #define MDIO_RD (2 << 26) | |
27 | #define MDIO_WR (1 << 26) | |
28 | #define MDIO_PMD_SHIFT 21 | |
29 | #define MDIO_PMD_MASK 0x1F | |
30 | #define MDIO_REG_SHIFT 16 | |
31 | #define MDIO_REG_MASK 0x1F | |
32 | ||
33 | #define MDIO_CFG 0x04 | |
34 | #define MDIO_C22 (1 << 0) | |
35 | #define MDIO_C45 0 | |
36 | #define MDIO_CLK_DIV_SHIFT 4 | |
37 | #define MDIO_CLK_DIV_MASK 0x3F | |
38 | #define MDIO_SUPP_PREAMBLE (1 << 12) | |
39 | ||
40 | struct unimac_mdio_priv { | |
41 | struct mii_bus *mii_bus; | |
42 | void __iomem *base; | |
f248aff8 FF |
43 | int (*wait_func) (void *wait_func_data); |
44 | void *wait_func_data; | |
b78ac6ec FF |
45 | struct clk *clk; |
46 | u32 clk_freq; | |
2ba1b163 FF |
47 | }; |
48 | ||
cb51a091 FF |
49 | static inline u32 unimac_mdio_readl(struct unimac_mdio_priv *priv, u32 offset) |
50 | { | |
51 | /* MIPS chips strapped for BE will automagically configure the | |
52 | * peripheral registers for CPU-native byte order. | |
53 | */ | |
54 | if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) | |
55 | return __raw_readl(priv->base + offset); | |
56 | else | |
57 | return readl_relaxed(priv->base + offset); | |
58 | } | |
59 | ||
60 | static inline void unimac_mdio_writel(struct unimac_mdio_priv *priv, u32 val, | |
61 | u32 offset) | |
62 | { | |
63 | if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) | |
64 | __raw_writel(val, priv->base + offset); | |
65 | else | |
66 | writel_relaxed(val, priv->base + offset); | |
67 | } | |
68 | ||
2ba1b163 FF |
69 | static inline void unimac_mdio_start(struct unimac_mdio_priv *priv) |
70 | { | |
71 | u32 reg; | |
72 | ||
cb51a091 | 73 | reg = unimac_mdio_readl(priv, MDIO_CMD); |
2ba1b163 | 74 | reg |= MDIO_START_BUSY; |
cb51a091 | 75 | unimac_mdio_writel(priv, reg, MDIO_CMD); |
2ba1b163 FF |
76 | } |
77 | ||
78 | static inline unsigned int unimac_mdio_busy(struct unimac_mdio_priv *priv) | |
79 | { | |
cb51a091 | 80 | return unimac_mdio_readl(priv, MDIO_CMD) & MDIO_START_BUSY; |
2ba1b163 FF |
81 | } |
82 | ||
f248aff8 | 83 | static int unimac_mdio_poll(void *wait_func_data) |
69a60b05 | 84 | { |
f248aff8 | 85 | struct unimac_mdio_priv *priv = wait_func_data; |
69a60b05 FF |
86 | unsigned int timeout = 1000; |
87 | ||
88 | do { | |
89 | if (!unimac_mdio_busy(priv)) | |
90 | return 0; | |
91 | ||
92 | usleep_range(1000, 2000); | |
51ce3e21 | 93 | } while (--timeout); |
69a60b05 | 94 | |
180a8c3d | 95 | return -ETIMEDOUT; |
69a60b05 FF |
96 | } |
97 | ||
2ba1b163 FF |
98 | static int unimac_mdio_read(struct mii_bus *bus, int phy_id, int reg) |
99 | { | |
100 | struct unimac_mdio_priv *priv = bus->priv; | |
f248aff8 | 101 | int ret; |
2ba1b163 FF |
102 | u32 cmd; |
103 | ||
104 | /* Prepare the read operation */ | |
105 | cmd = MDIO_RD | (phy_id << MDIO_PMD_SHIFT) | (reg << MDIO_REG_SHIFT); | |
cb51a091 | 106 | unimac_mdio_writel(priv, cmd, MDIO_CMD); |
2ba1b163 FF |
107 | |
108 | /* Start MDIO transaction */ | |
109 | unimac_mdio_start(priv); | |
110 | ||
f248aff8 | 111 | ret = priv->wait_func(priv->wait_func_data); |
69a60b05 FF |
112 | if (ret) |
113 | return ret; | |
2ba1b163 | 114 | |
cb51a091 | 115 | cmd = unimac_mdio_readl(priv, MDIO_CMD); |
1a3f4e83 FF |
116 | |
117 | /* Some broken devices are known not to release the line during | |
118 | * turn-around, e.g: Broadcom BCM53125 external switches, so check for | |
119 | * that condition here and ignore the MDIO controller read failure | |
120 | * indication. | |
121 | */ | |
122 | if (!(bus->phy_ignore_ta_mask & 1 << phy_id) && (cmd & MDIO_READ_FAIL)) | |
2ba1b163 FF |
123 | return -EIO; |
124 | ||
125 | return cmd & 0xffff; | |
126 | } | |
127 | ||
128 | static int unimac_mdio_write(struct mii_bus *bus, int phy_id, | |
129 | int reg, u16 val) | |
130 | { | |
131 | struct unimac_mdio_priv *priv = bus->priv; | |
2ba1b163 FF |
132 | u32 cmd; |
133 | ||
134 | /* Prepare the write operation */ | |
135 | cmd = MDIO_WR | (phy_id << MDIO_PMD_SHIFT) | | |
136 | (reg << MDIO_REG_SHIFT) | (0xffff & val); | |
cb51a091 | 137 | unimac_mdio_writel(priv, cmd, MDIO_CMD); |
2ba1b163 FF |
138 | |
139 | unimac_mdio_start(priv); | |
140 | ||
f248aff8 | 141 | return priv->wait_func(priv->wait_func_data); |
2ba1b163 FF |
142 | } |
143 | ||
d8e704e4 FF |
144 | /* Workaround for integrated BCM7xxx Gigabit PHYs which have a problem with |
145 | * their internal MDIO management controller making them fail to successfully | |
146 | * be read from or written to for the first transaction. We insert a dummy | |
147 | * BMSR read here to make sure that phy_get_device() and get_phy_id() can | |
148 | * correctly read the PHY MII_PHYSID1/2 registers and successfully register a | |
149 | * PHY device for this peripheral. | |
150 | * | |
151 | * Once the PHY driver is registered, we can workaround subsequent reads from | |
152 | * there (e.g: during system-wide power management). | |
153 | * | |
154 | * bus->reset is invoked before mdiobus_scan during mdiobus_register and is | |
155 | * therefore the right location to stick that workaround. Since we do not want | |
156 | * to read from non-existing PHYs, we either use bus->phy_mask or do a manual | |
157 | * Device Tree scan to limit the search area. | |
158 | */ | |
159 | static int unimac_mdio_reset(struct mii_bus *bus) | |
160 | { | |
161 | struct device_node *np = bus->dev.of_node; | |
162 | struct device_node *child; | |
163 | u32 read_mask = 0; | |
164 | int addr; | |
165 | ||
166 | if (!np) { | |
167 | read_mask = ~bus->phy_mask; | |
168 | } else { | |
169 | for_each_available_child_of_node(np, child) { | |
170 | addr = of_mdio_parse_addr(&bus->dev, child); | |
171 | if (addr < 0) | |
172 | continue; | |
173 | ||
174 | read_mask |= 1 << addr; | |
175 | } | |
176 | } | |
177 | ||
178 | for (addr = 0; addr < PHY_MAX_ADDR; addr++) { | |
e23597f7 FF |
179 | if (read_mask & 1 << addr) { |
180 | dev_dbg(&bus->dev, "Workaround for PHY @ %d\n", addr); | |
d8e704e4 | 181 | mdiobus_read(bus, addr, MII_BMSR); |
e23597f7 | 182 | } |
d8e704e4 FF |
183 | } |
184 | ||
185 | return 0; | |
186 | } | |
187 | ||
b78ac6ec FF |
188 | static void unimac_mdio_clk_set(struct unimac_mdio_priv *priv) |
189 | { | |
190 | unsigned long rate; | |
191 | u32 reg, div; | |
192 | ||
193 | /* Keep the hardware default values */ | |
194 | if (!priv->clk_freq) | |
195 | return; | |
196 | ||
197 | if (!priv->clk) | |
198 | rate = 250000000; | |
199 | else | |
200 | rate = clk_get_rate(priv->clk); | |
201 | ||
202 | div = (rate / (2 * priv->clk_freq)) - 1; | |
203 | if (div & ~MDIO_CLK_DIV_MASK) { | |
204 | pr_warn("Incorrect MDIO clock frequency, ignoring\n"); | |
205 | return; | |
206 | } | |
207 | ||
208 | /* The MDIO clock is the reference clock (typicaly 250Mhz) divided by | |
209 | * 2 x (MDIO_CLK_DIV + 1) | |
210 | */ | |
211 | reg = unimac_mdio_readl(priv, MDIO_CFG); | |
212 | reg &= ~(MDIO_CLK_DIV_MASK << MDIO_CLK_DIV_SHIFT); | |
213 | reg |= div << MDIO_CLK_DIV_SHIFT; | |
214 | unimac_mdio_writel(priv, reg, MDIO_CFG); | |
215 | } | |
216 | ||
2ba1b163 FF |
217 | static int unimac_mdio_probe(struct platform_device *pdev) |
218 | { | |
f248aff8 | 219 | struct unimac_mdio_pdata *pdata = pdev->dev.platform_data; |
2ba1b163 FF |
220 | struct unimac_mdio_priv *priv; |
221 | struct device_node *np; | |
222 | struct mii_bus *bus; | |
223 | struct resource *r; | |
224 | int ret; | |
225 | ||
226 | np = pdev->dev.of_node; | |
227 | ||
228 | priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | |
229 | if (!priv) | |
230 | return -ENOMEM; | |
231 | ||
232 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
297a6961 WY |
233 | if (!r) |
234 | return -EINVAL; | |
2ba1b163 FF |
235 | |
236 | /* Just ioremap, as this MDIO block is usually integrated into an | |
237 | * Ethernet MAC controller register range | |
238 | */ | |
239 | priv->base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); | |
240 | if (!priv->base) { | |
241 | dev_err(&pdev->dev, "failed to remap register\n"); | |
242 | return -ENOMEM; | |
243 | } | |
244 | ||
c312c781 AP |
245 | priv->clk = devm_clk_get_optional(&pdev->dev, NULL); |
246 | if (IS_ERR(priv->clk)) | |
b78ac6ec | 247 | return PTR_ERR(priv->clk); |
b78ac6ec FF |
248 | |
249 | ret = clk_prepare_enable(priv->clk); | |
250 | if (ret) | |
251 | return ret; | |
252 | ||
253 | if (of_property_read_u32(np, "clock-frequency", &priv->clk_freq)) | |
254 | priv->clk_freq = 0; | |
255 | ||
256 | unimac_mdio_clk_set(priv); | |
257 | ||
2ba1b163 | 258 | priv->mii_bus = mdiobus_alloc(); |
b78ac6ec FF |
259 | if (!priv->mii_bus) { |
260 | ret = -ENOMEM; | |
261 | goto out_clk_disable; | |
262 | } | |
2ba1b163 FF |
263 | |
264 | bus = priv->mii_bus; | |
265 | bus->priv = priv; | |
f248aff8 FF |
266 | if (pdata) { |
267 | bus->name = pdata->bus_name; | |
268 | priv->wait_func = pdata->wait_func; | |
269 | priv->wait_func_data = pdata->wait_func_data; | |
270 | bus->phy_mask = ~pdata->phy_mask; | |
271 | } else { | |
272 | bus->name = "unimac MII bus"; | |
273 | priv->wait_func_data = priv; | |
274 | priv->wait_func = unimac_mdio_poll; | |
275 | } | |
2ba1b163 FF |
276 | bus->parent = &pdev->dev; |
277 | bus->read = unimac_mdio_read; | |
278 | bus->write = unimac_mdio_write; | |
d8e704e4 | 279 | bus->reset = unimac_mdio_reset; |
d782f7c9 | 280 | snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", pdev->name, pdev->id); |
2ba1b163 | 281 | |
2ba1b163 FF |
282 | ret = of_mdiobus_register(bus, np); |
283 | if (ret) { | |
284 | dev_err(&pdev->dev, "MDIO bus registration failed\n"); | |
e7f4dc35 | 285 | goto out_mdio_free; |
2ba1b163 FF |
286 | } |
287 | ||
288 | platform_set_drvdata(pdev, priv); | |
289 | ||
647aed23 | 290 | dev_info(&pdev->dev, "Broadcom UniMAC MDIO bus\n"); |
2ba1b163 FF |
291 | |
292 | return 0; | |
293 | ||
2ba1b163 FF |
294 | out_mdio_free: |
295 | mdiobus_free(bus); | |
b78ac6ec FF |
296 | out_clk_disable: |
297 | clk_disable_unprepare(priv->clk); | |
2ba1b163 FF |
298 | return ret; |
299 | } | |
300 | ||
301 | static int unimac_mdio_remove(struct platform_device *pdev) | |
302 | { | |
303 | struct unimac_mdio_priv *priv = platform_get_drvdata(pdev); | |
304 | ||
305 | mdiobus_unregister(priv->mii_bus); | |
2ba1b163 | 306 | mdiobus_free(priv->mii_bus); |
b78ac6ec FF |
307 | clk_disable_unprepare(priv->clk); |
308 | ||
309 | return 0; | |
310 | } | |
311 | ||
9b97123a | 312 | static int __maybe_unused unimac_mdio_suspend(struct device *d) |
b78ac6ec FF |
313 | { |
314 | struct unimac_mdio_priv *priv = dev_get_drvdata(d); | |
315 | ||
316 | clk_disable_unprepare(priv->clk); | |
317 | ||
318 | return 0; | |
319 | } | |
320 | ||
9b97123a | 321 | static int __maybe_unused unimac_mdio_resume(struct device *d) |
b78ac6ec FF |
322 | { |
323 | struct unimac_mdio_priv *priv = dev_get_drvdata(d); | |
324 | int ret; | |
325 | ||
326 | ret = clk_prepare_enable(priv->clk); | |
327 | if (ret) | |
328 | return ret; | |
329 | ||
330 | unimac_mdio_clk_set(priv); | |
2ba1b163 FF |
331 | |
332 | return 0; | |
333 | } | |
334 | ||
b78ac6ec FF |
335 | static SIMPLE_DEV_PM_OPS(unimac_mdio_pm_ops, |
336 | unimac_mdio_suspend, unimac_mdio_resume); | |
337 | ||
d8a7dadb | 338 | static const struct of_device_id unimac_mdio_ids[] = { |
42138085 | 339 | { .compatible = "brcm,genet-mdio-v5", }, |
2ba1b163 FF |
340 | { .compatible = "brcm,genet-mdio-v4", }, |
341 | { .compatible = "brcm,genet-mdio-v3", }, | |
342 | { .compatible = "brcm,genet-mdio-v2", }, | |
343 | { .compatible = "brcm,genet-mdio-v1", }, | |
344 | { .compatible = "brcm,unimac-mdio", }, | |
4559154a | 345 | { /* sentinel */ }, |
2ba1b163 | 346 | }; |
2f90a307 | 347 | MODULE_DEVICE_TABLE(of, unimac_mdio_ids); |
2ba1b163 FF |
348 | |
349 | static struct platform_driver unimac_mdio_driver = { | |
350 | .driver = { | |
f248aff8 | 351 | .name = UNIMAC_MDIO_DRV_NAME, |
2ba1b163 | 352 | .of_match_table = unimac_mdio_ids, |
b78ac6ec | 353 | .pm = &unimac_mdio_pm_ops, |
2ba1b163 FF |
354 | }, |
355 | .probe = unimac_mdio_probe, | |
356 | .remove = unimac_mdio_remove, | |
357 | }; | |
358 | module_platform_driver(unimac_mdio_driver); | |
359 | ||
360 | MODULE_AUTHOR("Broadcom Corporation"); | |
361 | MODULE_DESCRIPTION("Broadcom UniMAC MDIO bus controller"); | |
362 | MODULE_LICENSE("GPL"); | |
f248aff8 | 363 | MODULE_ALIAS("platform:" UNIMAC_MDIO_DRV_NAME); |