diff options
author | Florian Fainelli <f.fainelli@gmail.com> | 2014-02-13 19:08:48 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2014-02-14 00:27:58 -0500 |
commit | aa09677cba423b894bde434e523991eab83895ec (patch) | |
tree | edca6ebb02b5e1c5f1948de6ee53abefe7a06ddb | |
parent | 1c1008c793fa46703a2fee469f4235e1c7984333 (diff) |
net: bcmgenet: add MDIO routines
This patch adds support for configuring the port multiplexer hardware
which resides in front of the GENET Ethernet MAC controller. This allows
us to support:
- internal PHYs (using drivers/net/phy/bcm7xxx.c)
- MoCA PHYs which are an entirely separate hardware block not covered
here
- external PHYs and switches
Note that MoCA and switches are currently supported using the emulated
"fixed PHY" driver.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | drivers/net/ethernet/broadcom/genet/bcmmii.c | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/drivers/net/ethernet/broadcom/genet/bcmmii.c b/drivers/net/ethernet/broadcom/genet/bcmmii.c new file mode 100644 index 000000000000..4608673beaff --- /dev/null +++ b/drivers/net/ethernet/broadcom/genet/bcmmii.c | |||
@@ -0,0 +1,464 @@ | |||
1 | /* | ||
2 | * Broadcom GENET MDIO routines | ||
3 | * | ||
4 | * Copyright (c) 2014 Broadcom Corporation | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License | ||
16 | * along with this program; if not, write to the Free Software | ||
17 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | ||
18 | */ | ||
19 | |||
20 | |||
21 | #include <linux/types.h> | ||
22 | #include <linux/delay.h> | ||
23 | #include <linux/wait.h> | ||
24 | #include <linux/mii.h> | ||
25 | #include <linux/ethtool.h> | ||
26 | #include <linux/bitops.h> | ||
27 | #include <linux/netdevice.h> | ||
28 | #include <linux/platform_device.h> | ||
29 | #include <linux/phy.h> | ||
30 | #include <linux/phy_fixed.h> | ||
31 | #include <linux/brcmphy.h> | ||
32 | #include <linux/of.h> | ||
33 | #include <linux/of_net.h> | ||
34 | #include <linux/of_mdio.h> | ||
35 | |||
36 | #include "bcmgenet.h" | ||
37 | |||
38 | /* read a value from the MII */ | ||
39 | static int bcmgenet_mii_read(struct mii_bus *bus, int phy_id, int location) | ||
40 | { | ||
41 | int ret; | ||
42 | struct net_device *dev = bus->priv; | ||
43 | struct bcmgenet_priv *priv = netdev_priv(dev); | ||
44 | u32 reg; | ||
45 | |||
46 | bcmgenet_umac_writel(priv, (MDIO_RD | (phy_id << MDIO_PMD_SHIFT) | | ||
47 | (location << MDIO_REG_SHIFT)), UMAC_MDIO_CMD); | ||
48 | /* Start MDIO transaction*/ | ||
49 | reg = bcmgenet_umac_readl(priv, UMAC_MDIO_CMD); | ||
50 | reg |= MDIO_START_BUSY; | ||
51 | bcmgenet_umac_writel(priv, reg, UMAC_MDIO_CMD); | ||
52 | wait_event_timeout(priv->wq, | ||
53 | !(bcmgenet_umac_readl(priv, UMAC_MDIO_CMD) | ||
54 | & MDIO_START_BUSY), | ||
55 | HZ / 100); | ||
56 | ret = bcmgenet_umac_readl(priv, UMAC_MDIO_CMD); | ||
57 | |||
58 | if (ret & MDIO_READ_FAIL) | ||
59 | return -EIO; | ||
60 | |||
61 | return ret & 0xffff; | ||
62 | } | ||
63 | |||
64 | /* write a value to the MII */ | ||
65 | static int bcmgenet_mii_write(struct mii_bus *bus, int phy_id, | ||
66 | int location, u16 val) | ||
67 | { | ||
68 | struct net_device *dev = bus->priv; | ||
69 | struct bcmgenet_priv *priv = netdev_priv(dev); | ||
70 | u32 reg; | ||
71 | |||
72 | bcmgenet_umac_writel(priv, (MDIO_WR | (phy_id << MDIO_PMD_SHIFT) | | ||
73 | (location << MDIO_REG_SHIFT) | (0xffff & val)), | ||
74 | UMAC_MDIO_CMD); | ||
75 | reg = bcmgenet_umac_readl(priv, UMAC_MDIO_CMD); | ||
76 | reg |= MDIO_START_BUSY; | ||
77 | bcmgenet_umac_writel(priv, reg, UMAC_MDIO_CMD); | ||
78 | wait_event_timeout(priv->wq, | ||
79 | !(bcmgenet_umac_readl(priv, UMAC_MDIO_CMD) & | ||
80 | MDIO_START_BUSY), | ||
81 | HZ / 100); | ||
82 | |||
83 | return 0; | ||
84 | } | ||
85 | |||
86 | /* setup netdev link state when PHY link status change and | ||
87 | * update UMAC and RGMII block when link up | ||
88 | */ | ||
89 | static void bcmgenet_mii_setup(struct net_device *dev) | ||
90 | { | ||
91 | struct bcmgenet_priv *priv = netdev_priv(dev); | ||
92 | struct phy_device *phydev = priv->phydev; | ||
93 | u32 reg, cmd_bits = 0; | ||
94 | unsigned int status_changed = 0; | ||
95 | |||
96 | if (priv->old_link != phydev->link) { | ||
97 | status_changed = 1; | ||
98 | priv->old_link = phydev->link; | ||
99 | } | ||
100 | |||
101 | if (phydev->link) { | ||
102 | /* program UMAC and RGMII block based on established link | ||
103 | * speed, pause, and duplex. | ||
104 | * the speed set in umac->cmd tell RGMII block which clock | ||
105 | * 25MHz(100Mbps)/125MHz(1Gbps) to use for transmit. | ||
106 | * receive clock is provided by PHY. | ||
107 | */ | ||
108 | reg = bcmgenet_ext_readl(priv, EXT_RGMII_OOB_CTRL); | ||
109 | reg &= ~OOB_DISABLE; | ||
110 | reg |= RGMII_LINK; | ||
111 | bcmgenet_ext_writel(priv, reg, EXT_RGMII_OOB_CTRL); | ||
112 | |||
113 | /* speed */ | ||
114 | if (phydev->speed == SPEED_1000) | ||
115 | cmd_bits = UMAC_SPEED_1000; | ||
116 | else if (phydev->speed == SPEED_100) | ||
117 | cmd_bits = UMAC_SPEED_100; | ||
118 | else | ||
119 | cmd_bits = UMAC_SPEED_10; | ||
120 | cmd_bits <<= CMD_SPEED_SHIFT; | ||
121 | |||
122 | if (priv->old_duplex != phydev->duplex) { | ||
123 | status_changed = 1; | ||
124 | priv->old_duplex = phydev->duplex; | ||
125 | } | ||
126 | |||
127 | /* duplex */ | ||
128 | if (phydev->duplex != DUPLEX_FULL) | ||
129 | cmd_bits |= CMD_HD_EN; | ||
130 | |||
131 | if (priv->old_pause != phydev->pause) { | ||
132 | status_changed = 1; | ||
133 | priv->old_pause = phydev->pause; | ||
134 | } | ||
135 | |||
136 | /* pause capability */ | ||
137 | if (!phydev->pause) | ||
138 | cmd_bits |= CMD_RX_PAUSE_IGNORE | CMD_TX_PAUSE_IGNORE; | ||
139 | |||
140 | reg = bcmgenet_umac_readl(priv, UMAC_CMD); | ||
141 | reg &= ~((CMD_SPEED_MASK << CMD_SPEED_SHIFT) | | ||
142 | CMD_HD_EN | | ||
143 | CMD_RX_PAUSE_IGNORE | CMD_TX_PAUSE_IGNORE); | ||
144 | reg |= cmd_bits; | ||
145 | bcmgenet_umac_writel(priv, reg, UMAC_CMD); | ||
146 | } | ||
147 | |||
148 | if (status_changed) | ||
149 | phy_print_status(phydev); | ||
150 | } | ||
151 | |||
152 | void bcmgenet_mii_reset(struct net_device *dev) | ||
153 | { | ||
154 | struct bcmgenet_priv *priv = netdev_priv(dev); | ||
155 | |||
156 | if (priv->phydev) { | ||
157 | phy_init_hw(priv->phydev); | ||
158 | phy_start_aneg(priv->phydev); | ||
159 | } | ||
160 | } | ||
161 | |||
162 | static void bcmgenet_ephy_power_up(struct net_device *dev) | ||
163 | { | ||
164 | struct bcmgenet_priv *priv = netdev_priv(dev); | ||
165 | u32 reg = 0; | ||
166 | |||
167 | /* EXT_GPHY_CTRL is only valid for GENETv4 and onward */ | ||
168 | if (!GENET_IS_V4(priv)) | ||
169 | return; | ||
170 | |||
171 | reg = bcmgenet_ext_readl(priv, EXT_GPHY_CTRL); | ||
172 | reg &= ~(EXT_CFG_IDDQ_BIAS | EXT_CFG_PWR_DOWN); | ||
173 | reg |= EXT_GPHY_RESET; | ||
174 | bcmgenet_ext_writel(priv, reg, EXT_GPHY_CTRL); | ||
175 | mdelay(2); | ||
176 | |||
177 | reg &= ~EXT_GPHY_RESET; | ||
178 | bcmgenet_ext_writel(priv, reg, EXT_GPHY_CTRL); | ||
179 | udelay(20); | ||
180 | } | ||
181 | |||
182 | static void bcmgenet_internal_phy_setup(struct net_device *dev) | ||
183 | { | ||
184 | struct bcmgenet_priv *priv = netdev_priv(dev); | ||
185 | u32 reg; | ||
186 | |||
187 | /* Power up EPHY */ | ||
188 | bcmgenet_ephy_power_up(dev); | ||
189 | /* enable APD */ | ||
190 | reg = bcmgenet_ext_readl(priv, EXT_EXT_PWR_MGMT); | ||
191 | reg |= EXT_PWR_DN_EN_LD; | ||
192 | bcmgenet_ext_writel(priv, reg, EXT_EXT_PWR_MGMT); | ||
193 | bcmgenet_mii_reset(dev); | ||
194 | } | ||
195 | |||
196 | static void bcmgenet_moca_phy_setup(struct bcmgenet_priv *priv) | ||
197 | { | ||
198 | u32 reg; | ||
199 | |||
200 | /* Speed settings are set in bcmgenet_mii_setup() */ | ||
201 | reg = bcmgenet_sys_readl(priv, SYS_PORT_CTRL); | ||
202 | reg |= LED_ACT_SOURCE_MAC; | ||
203 | bcmgenet_sys_writel(priv, reg, SYS_PORT_CTRL); | ||
204 | } | ||
205 | |||
206 | int bcmgenet_mii_config(struct net_device *dev) | ||
207 | { | ||
208 | struct bcmgenet_priv *priv = netdev_priv(dev); | ||
209 | struct phy_device *phydev = priv->phydev; | ||
210 | struct device *kdev = &priv->pdev->dev; | ||
211 | const char *phy_name = NULL; | ||
212 | u32 id_mode_dis = 0; | ||
213 | u32 port_ctrl; | ||
214 | u32 reg; | ||
215 | |||
216 | priv->ext_phy = !phy_is_internal(priv->phydev) && | ||
217 | (priv->phy_interface != PHY_INTERFACE_MODE_MOCA); | ||
218 | |||
219 | if (phy_is_internal(priv->phydev)) | ||
220 | priv->phy_interface = PHY_INTERFACE_MODE_NA; | ||
221 | |||
222 | switch (priv->phy_interface) { | ||
223 | case PHY_INTERFACE_MODE_NA: | ||
224 | case PHY_INTERFACE_MODE_MOCA: | ||
225 | /* Irrespective of the actually configured PHY speed (100 or | ||
226 | * 1000) GENETv4 only has an internal GPHY so we will just end | ||
227 | * up masking the Gigabit features from what we support, not | ||
228 | * switching to the EPHY | ||
229 | */ | ||
230 | if (GENET_IS_V4(priv)) | ||
231 | port_ctrl = PORT_MODE_INT_GPHY; | ||
232 | else | ||
233 | port_ctrl = PORT_MODE_INT_EPHY; | ||
234 | |||
235 | bcmgenet_sys_writel(priv, port_ctrl, SYS_PORT_CTRL); | ||
236 | |||
237 | if (phy_is_internal(priv->phydev)) { | ||
238 | phy_name = "internal PHY"; | ||
239 | bcmgenet_internal_phy_setup(dev); | ||
240 | } else if (priv->phy_interface == PHY_INTERFACE_MODE_MOCA) { | ||
241 | phy_name = "MoCA"; | ||
242 | bcmgenet_moca_phy_setup(priv); | ||
243 | } | ||
244 | break; | ||
245 | |||
246 | case PHY_INTERFACE_MODE_MII: | ||
247 | phy_name = "external MII"; | ||
248 | phydev->supported &= PHY_BASIC_FEATURES; | ||
249 | bcmgenet_sys_writel(priv, | ||
250 | PORT_MODE_EXT_EPHY, SYS_PORT_CTRL); | ||
251 | break; | ||
252 | |||
253 | case PHY_INTERFACE_MODE_REVMII: | ||
254 | phy_name = "external RvMII"; | ||
255 | /* of_mdiobus_register took care of reading the 'max-speed' | ||
256 | * PHY property for us, effectively limiting the PHY supported | ||
257 | * capabilities, use that knowledge to also configure the | ||
258 | * Reverse MII interface correctly. | ||
259 | */ | ||
260 | if ((priv->phydev->supported & PHY_BASIC_FEATURES) == | ||
261 | PHY_BASIC_FEATURES) | ||
262 | port_ctrl = PORT_MODE_EXT_RVMII_25; | ||
263 | else | ||
264 | port_ctrl = PORT_MODE_EXT_RVMII_50; | ||
265 | bcmgenet_sys_writel(priv, port_ctrl, SYS_PORT_CTRL); | ||
266 | break; | ||
267 | |||
268 | case PHY_INTERFACE_MODE_RGMII: | ||
269 | /* RGMII_NO_ID: TXC transitions at the same time as TXD | ||
270 | * (requires PCB or receiver-side delay) | ||
271 | * RGMII: Add 2ns delay on TXC (90 degree shift) | ||
272 | * | ||
273 | * ID is implicitly disabled for 100Mbps (RG)MII operation. | ||
274 | */ | ||
275 | id_mode_dis = BIT(16); | ||
276 | /* fall through */ | ||
277 | case PHY_INTERFACE_MODE_RGMII_TXID: | ||
278 | if (id_mode_dis) | ||
279 | phy_name = "external RGMII (no delay)"; | ||
280 | else | ||
281 | phy_name = "external RGMII (TX delay)"; | ||
282 | reg = bcmgenet_ext_readl(priv, EXT_RGMII_OOB_CTRL); | ||
283 | reg |= RGMII_MODE_EN | id_mode_dis; | ||
284 | bcmgenet_ext_writel(priv, reg, EXT_RGMII_OOB_CTRL); | ||
285 | bcmgenet_sys_writel(priv, | ||
286 | PORT_MODE_EXT_GPHY, SYS_PORT_CTRL); | ||
287 | break; | ||
288 | default: | ||
289 | dev_err(kdev, "unknown phy mode: %d\n", priv->phy_interface); | ||
290 | return -EINVAL; | ||
291 | } | ||
292 | |||
293 | dev_info(kdev, "configuring instance for %s\n", phy_name); | ||
294 | |||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | static int bcmgenet_mii_probe(struct net_device *dev) | ||
299 | { | ||
300 | struct bcmgenet_priv *priv = netdev_priv(dev); | ||
301 | struct phy_device *phydev; | ||
302 | unsigned int phy_flags; | ||
303 | int ret; | ||
304 | |||
305 | if (priv->phydev) { | ||
306 | pr_info("PHY already attached\n"); | ||
307 | return 0; | ||
308 | } | ||
309 | |||
310 | if (priv->phy_dn) | ||
311 | phydev = of_phy_connect(dev, priv->phy_dn, | ||
312 | bcmgenet_mii_setup, 0, | ||
313 | priv->phy_interface); | ||
314 | else | ||
315 | phydev = of_phy_connect_fixed_link(dev, | ||
316 | bcmgenet_mii_setup, | ||
317 | priv->phy_interface); | ||
318 | |||
319 | if (!phydev) { | ||
320 | pr_err("could not attach to PHY\n"); | ||
321 | return -ENODEV; | ||
322 | } | ||
323 | |||
324 | priv->old_link = -1; | ||
325 | priv->old_duplex = -1; | ||
326 | priv->old_pause = -1; | ||
327 | priv->phydev = phydev; | ||
328 | |||
329 | /* Configure port multiplexer based on what the probed PHY device since | ||
330 | * reading the 'max-speed' property determines the maximum supported | ||
331 | * PHY speed which is needed for bcmgenet_mii_config() to configure | ||
332 | * things appropriately. | ||
333 | */ | ||
334 | ret = bcmgenet_mii_config(dev); | ||
335 | if (ret) { | ||
336 | phy_disconnect(priv->phydev); | ||
337 | return ret; | ||
338 | } | ||
339 | |||
340 | phy_flags = PHY_BRCM_100MBPS_WAR; | ||
341 | |||
342 | /* workarounds are only needed for 100Mpbs PHYs, and | ||
343 | * never on GENET V1 hardware | ||
344 | */ | ||
345 | if ((phydev->supported & PHY_GBIT_FEATURES) || GENET_IS_V1(priv)) | ||
346 | phy_flags = 0; | ||
347 | |||
348 | phydev->dev_flags |= phy_flags; | ||
349 | phydev->advertising = phydev->supported; | ||
350 | |||
351 | /* The internal PHY has its link interrupts routed to the | ||
352 | * Ethernet MAC ISRs | ||
353 | */ | ||
354 | if (phy_is_internal(priv->phydev)) | ||
355 | priv->mii_bus->irq[phydev->addr] = PHY_IGNORE_INTERRUPT; | ||
356 | else | ||
357 | priv->mii_bus->irq[phydev->addr] = PHY_POLL; | ||
358 | |||
359 | pr_info("attached PHY at address %d [%s]\n", | ||
360 | phydev->addr, phydev->drv->name); | ||
361 | |||
362 | return 0; | ||
363 | } | ||
364 | |||
365 | static int bcmgenet_mii_alloc(struct bcmgenet_priv *priv) | ||
366 | { | ||
367 | struct mii_bus *bus; | ||
368 | |||
369 | if (priv->mii_bus) | ||
370 | return 0; | ||
371 | |||
372 | priv->mii_bus = mdiobus_alloc(); | ||
373 | if (!priv->mii_bus) { | ||
374 | pr_err("failed to allocate\n"); | ||
375 | return -ENOMEM; | ||
376 | } | ||
377 | |||
378 | bus = priv->mii_bus; | ||
379 | bus->priv = priv->dev; | ||
380 | bus->name = "bcmgenet MII bus"; | ||
381 | bus->parent = &priv->pdev->dev; | ||
382 | bus->read = bcmgenet_mii_read; | ||
383 | bus->write = bcmgenet_mii_write; | ||
384 | snprintf(bus->id, MII_BUS_ID_SIZE, "%s-%d", | ||
385 | priv->pdev->name, priv->pdev->id); | ||
386 | |||
387 | bus->irq = kzalloc(sizeof(int) * PHY_MAX_ADDR, GFP_KERNEL); | ||
388 | if (!bus->irq) { | ||
389 | mdiobus_free(priv->mii_bus); | ||
390 | return -ENOMEM; | ||
391 | } | ||
392 | |||
393 | return 0; | ||
394 | } | ||
395 | |||
396 | static int bcmgenet_mii_of_init(struct bcmgenet_priv *priv) | ||
397 | { | ||
398 | struct device_node *dn = priv->pdev->dev.of_node; | ||
399 | struct device *kdev = &priv->pdev->dev; | ||
400 | struct device_node *mdio_dn; | ||
401 | char *compat; | ||
402 | int ret; | ||
403 | |||
404 | compat = kasprintf(GFP_KERNEL, "brcm,genet-mdio-v%d", priv->version); | ||
405 | if (!compat) | ||
406 | return -ENOMEM; | ||
407 | |||
408 | mdio_dn = of_find_compatible_node(dn, NULL, compat); | ||
409 | kfree(compat); | ||
410 | if (!mdio_dn) { | ||
411 | dev_err(kdev, "unable to find MDIO bus node\n"); | ||
412 | return -ENODEV; | ||
413 | } | ||
414 | |||
415 | ret = of_mdiobus_register(priv->mii_bus, mdio_dn); | ||
416 | if (ret) { | ||
417 | dev_err(kdev, "failed to register MDIO bus\n"); | ||
418 | return ret; | ||
419 | } | ||
420 | |||
421 | /* Fetch the PHY phandle */ | ||
422 | priv->phy_dn = of_parse_phandle(dn, "phy-handle", 0); | ||
423 | |||
424 | /* Get the link mode */ | ||
425 | priv->phy_interface = of_get_phy_mode(dn); | ||
426 | |||
427 | return 0; | ||
428 | } | ||
429 | |||
430 | int bcmgenet_mii_init(struct net_device *dev) | ||
431 | { | ||
432 | struct bcmgenet_priv *priv = netdev_priv(dev); | ||
433 | int ret; | ||
434 | |||
435 | ret = bcmgenet_mii_alloc(priv); | ||
436 | if (ret) | ||
437 | return ret; | ||
438 | |||
439 | ret = bcmgenet_mii_of_init(priv); | ||
440 | if (ret) | ||
441 | goto out_free; | ||
442 | |||
443 | ret = bcmgenet_mii_probe(dev); | ||
444 | if (ret) | ||
445 | goto out; | ||
446 | |||
447 | return 0; | ||
448 | |||
449 | out: | ||
450 | mdiobus_unregister(priv->mii_bus); | ||
451 | out_free: | ||
452 | kfree(priv->mii_bus->irq); | ||
453 | mdiobus_free(priv->mii_bus); | ||
454 | return ret; | ||
455 | } | ||
456 | |||
457 | void bcmgenet_mii_exit(struct net_device *dev) | ||
458 | { | ||
459 | struct bcmgenet_priv *priv = netdev_priv(dev); | ||
460 | |||
461 | mdiobus_unregister(priv->mii_bus); | ||
462 | kfree(priv->mii_bus->irq); | ||
463 | mdiobus_free(priv->mii_bus); | ||
464 | } | ||