aboutsummaryrefslogtreecommitdiffstats
path: root/arch/powerpc/sysdev
diff options
context:
space:
mode:
authorTrent Piepho <tpiepho@freescale.com>2008-10-30 21:17:06 -0400
committerJeff Garzik <jgarzik@redhat.com>2008-10-31 00:59:46 -0400
commitc132419e560a2ecd3c8cf77f9c37e103e74b3754 (patch)
tree09f6753d9eb9b4fd06b0f7651414d6555ee2cccb /arch/powerpc/sysdev
parent71527ef484426f2a4fb868da379b46f4408e80d6 (diff)
gianfar: Fix race in TBI/SerDes configuration
The init_phy() function attaches to the PHY, then configures the SerDes<->TBI link (in SGMII mode). The TBI is on the MDIO bus with the PHY (sort of) and is accessed via the gianfar's MDIO registers, using the functions gfar_local_mdio_read/write(), which don't do any locking. The previously attached PHY will start a work-queue on a timer, and probably an irq handler as well, which will talk to the PHY and thus use the MDIO bus. This uses phy_read/write(), which have locking, but not against the gfar_local_mdio versions. The result is that PHY code will try to use the MDIO bus at the same time as the SerDes setup code, corrupting the transfers. Setting up the SerDes before attaching to the PHY will insure that there is no race between the SerDes code and *our* PHY, but doesn't fix everything. Typically the PHYs for all gianfar devices are on the same MDIO bus, which is associated with the first gianfar device. This means that the first gianfar's SerDes code could corrupt the MDIO transfers for a different gianfar's PHY. The lock used by phy_read/write() is contained in the mii_bus structure, which is pointed to by the PHY. This is difficult to access from the gianfar drivers, as there is no link between a gianfar device and the mii_bus which shares the same MDIO registers. As far as the device layer and drivers are concerned they are two unrelated devices (which happen to share registers). Generally all gianfar devices' PHYs will be on the bus associated with the first gianfar. But this might not be the case, so simply locking the gianfar's PHY's mii bus might not lock the mii bus that the SerDes setup code is going to use. We solve this by having the code that creates the gianfar platform device look in the device tree for an mdio device that shares the gianfar's registers. If one is found the ID of its platform device is saved in the gianfar's platform data. A new function in the gianfar mii code, gfar_get_miibus(), can use the bus ID to search through the platform devices for a gianfar_mdio device with the right ID. The platform device's driver data is the mii_bus structure, which the SerDes setup code can use to lock the current bus. Signed-off-by: Trent Piepho <tpiepho@freescale.com> CC: Andy Fleming <afleming@freescale.com> Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
Diffstat (limited to 'arch/powerpc/sysdev')
-rw-r--r--arch/powerpc/sysdev/fsl_soc.c26
1 files changed, 26 insertions, 0 deletions
diff --git a/arch/powerpc/sysdev/fsl_soc.c b/arch/powerpc/sysdev/fsl_soc.c
index 01b884b25696..26ecb96f9731 100644
--- a/arch/powerpc/sysdev/fsl_soc.c
+++ b/arch/powerpc/sysdev/fsl_soc.c
@@ -223,6 +223,8 @@ static int gfar_mdio_of_init_one(struct device_node *np)
223 if (ret) 223 if (ret)
224 return ret; 224 return ret;
225 225
226 /* The gianfar device will try to use the same ID created below to find
227 * this bus, to coordinate register access (since they share). */
226 mdio_dev = platform_device_register_simple("fsl-gianfar_mdio", 228 mdio_dev = platform_device_register_simple("fsl-gianfar_mdio",
227 res.start&0xfffff, &res, 1); 229 res.start&0xfffff, &res, 1);
228 if (IS_ERR(mdio_dev)) 230 if (IS_ERR(mdio_dev))
@@ -394,6 +396,30 @@ static int __init gfar_of_init(void)
394 of_node_put(mdio); 396 of_node_put(mdio);
395 } 397 }
396 398
399 /* Get MDIO bus controlled by this eTSEC, if any. Normally only
400 * eTSEC 1 will control an MDIO bus, not necessarily the same
401 * bus that its PHY is on ('mdio' above), so we can't just use
402 * that. What we do is look for a gianfar mdio device that has
403 * overlapping registers with this device. That's really the
404 * whole point, to find the device sharing our registers to
405 * coordinate access with it.
406 */
407 for_each_compatible_node(mdio, NULL, "fsl,gianfar-mdio") {
408 if (of_address_to_resource(mdio, 0, &res))
409 continue;
410
411 if (res.start >= r[0].start && res.end <= r[0].end) {
412 /* Get the ID the mdio bus platform device was
413 * registered with. gfar_data.bus_id is
414 * different because it's for finding a PHY,
415 * while this is for finding a MII bus.
416 */
417 gfar_data.mdio_bus = res.start&0xfffff;
418 of_node_put(mdio);
419 break;
420 }
421 }
422
397 ret = 423 ret =
398 platform_device_add_data(gfar_dev, &gfar_data, 424 platform_device_add_data(gfar_dev, &gfar_data,
399 sizeof(struct 425 sizeof(struct