From patchwork Thu Jun 23 22:09:34 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Benjamin Collins X-Patchwork-Id: 164273 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from chlorine.canonical.com (chlorine.canonical.com [91.189.94.204]) by ozlabs.org (Postfix) with ESMTP id 66854B6FE0 for ; Tue, 12 Jun 2012 09:02:41 +1000 (EST) Received: from localhost ([127.0.0.1] helo=chlorine.canonical.com) by chlorine.canonical.com with esmtp (Exim 4.71) (envelope-from ) id 1SeDd1-00040m-Sl; Mon, 11 Jun 2012 23:02:31 +0000 Received: from mail-pb0-f49.google.com ([209.85.160.49]) by chlorine.canonical.com with esmtp (Exim 4.71) (envelope-from ) id 1SeDci-0003iy-PP for kernel-team@lists.ubuntu.com; Mon, 11 Jun 2012 23:02:13 +0000 Received: by pbbrq13 with SMTP id rq13so6562271pbb.8 for ; Mon, 11 Jun 2012 16:02:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:message-id:in-reply-to:references:from:date:subject:to; bh=bIF6teLu3wJnCMl8g75no4byW4q3V2nxpQzUo/miTOY=; b=cfMjt69KI/RfuNPhRG2sBhs0mCF8gjJqcW+ulxNhbhqTx4rlTLByj8B/pbVulK2Rkk f2RWQoQIYCPqRzUZ174L4QOet2/cNdAVqVdtJsOSlAmVIjw9XAyVxRLCGY0PwcNEbuKP EJmMEPC1QSDQbdKznybnp5YJsWt880F27Jztji87ydx4oOuLSky6Ei3YWDvY8Uy+M4qo 7bBCKRb/+y1L7LDrGiJq7Xn5RZaWSgxy1xwEQVnFSiH5JpW6LFYapSbryfZk0BSjmX5n 73j6r6xorGA9fGd4J8WkG+itsFlt0a7sJ4f/1X29RnkcnL5bjkiB5npBaJkH/m7R3jh6 2P6g== Received: by 10.68.231.40 with SMTP id td8mr31911700pbc.150.1339455731703; Mon, 11 Jun 2012 16:02:11 -0700 (PDT) Received: from localhost (ip68-13-200-36.hr.hr.cox.net. [68.13.200.36]) by mx.google.com with ESMTPS id oy8sm19927614pbc.52.2012.06.11.16.02.08 (version=TLSv1/SSLv3 cipher=OTHER); Mon, 11 Jun 2012 16:02:10 -0700 (PDT) Received: by localhost (sSMTP sendmail emulation); Mon, 11 Jun 2012 19:02:06 -0400 Message-Id: In-Reply-To: References: From: Andy Fleming Date: Thu, 23 Jun 2011 17:09:34 -0500 Subject: [PATCH 14/27] UBUNTU: SAUCE: Convert MDIO and PHY Lib drivers to support 10G To: kernel-team@lists.ubuntu.com X-BeenThere: kernel-team@lists.ubuntu.com X-Mailman-Version: 2.1.13 Precedence: list List-Id: Kernel team discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: kernel-team-bounces@lists.ubuntu.com Errors-To: kernel-team-bounces@lists.ubuntu.com 10G MDIO is a totally different protocol (clause 45 of 802.3). Supporting this new protocol requires a couple of changes: * Add a new parameter to the mdiobus_read functions to specify the "device address" inside the PHY. * Add a phy45_read command which takes advantage of that new parameter * Add a generic PHY driver for 10G PHYs * Convert fsl_pq_mdio.c to the new API This patch is being maintained and will eventually be merged upstream by Freescale directly. The powerpc-e500mc flavour uses this. Signed-off-by: Andy Fleming Signed-off-by: Ben Collins --- drivers/net/ethernet/freescale/fsl_pq_mdio.c | 11 +- drivers/net/ethernet/freescale/fsl_pq_mdio.h | 11 +- drivers/net/phy/mdio_bus.c | 8 +- drivers/net/phy/phy.c | 4 +- drivers/net/phy/phy_device.c | 170 ++++++++++++++++++++++---- include/linux/phy.h | 53 ++++++-- 6 files changed, 214 insertions(+), 43 deletions(-) diff --git a/drivers/net/ethernet/freescale/fsl_pq_mdio.c b/drivers/net/ethernet/freescale/fsl_pq_mdio.c index f7f0bf5..935a1b6 100644 --- a/drivers/net/ethernet/freescale/fsl_pq_mdio.c +++ b/drivers/net/ethernet/freescale/fsl_pq_mdio.c @@ -62,7 +62,7 @@ struct fsl_pq_mdio_priv { * controlling the external PHYs, for example. */ int fsl_pq_local_mdio_write(struct fsl_pq_mdio __iomem *regs, int mii_id, - int regnum, u16 value) + int regnum, u16 value) { /* Set the PHY address and the register address we want to write */ out_be32(®s->miimadd, (mii_id << 8) | regnum); @@ -87,8 +87,8 @@ int fsl_pq_local_mdio_write(struct fsl_pq_mdio __iomem *regs, int mii_id, * and are always tied to the local mdio pins, which may not be the * same as system mdio bus, used for controlling the external PHYs, for eg. */ -int fsl_pq_local_mdio_read(struct fsl_pq_mdio __iomem *regs, - int mii_id, int regnum) +int fsl_pq_local_mdio_read(struct fsl_pq_mdio __iomem *regs, int mii_id, + int regnum) { u16 value; @@ -120,7 +120,8 @@ static struct fsl_pq_mdio __iomem *fsl_pq_mdio_get_regs(struct mii_bus *bus) * Write value to the PHY at mii_id at register regnum, * on the bus, waiting until the write is done before returning. */ -int fsl_pq_mdio_write(struct mii_bus *bus, int mii_id, int regnum, u16 value) +int fsl_pq_mdio_write(struct mii_bus *bus, int mii_id, int devad, int regnum, + u16 value) { struct fsl_pq_mdio __iomem *regs = fsl_pq_mdio_get_regs(bus); @@ -132,7 +133,7 @@ int fsl_pq_mdio_write(struct mii_bus *bus, int mii_id, int regnum, u16 value) * Read the bus for PHY at addr mii_id, register regnum, and * return the value. Clears miimcom first. */ -int fsl_pq_mdio_read(struct mii_bus *bus, int mii_id, int regnum) +int fsl_pq_mdio_read(struct mii_bus *bus, int mii_id, int devad, int regnum) { struct fsl_pq_mdio __iomem *regs = fsl_pq_mdio_get_regs(bus); diff --git a/drivers/net/ethernet/freescale/fsl_pq_mdio.h b/drivers/net/ethernet/freescale/fsl_pq_mdio.h index bd17a2a..4b5254c 100644 --- a/drivers/net/ethernet/freescale/fsl_pq_mdio.h +++ b/drivers/net/ethernet/freescale/fsl_pq_mdio.h @@ -41,11 +41,14 @@ struct fsl_pq_mdio { u8 res4[2728]; } __packed; -int fsl_pq_mdio_read(struct mii_bus *bus, int mii_id, int regnum); -int fsl_pq_mdio_write(struct mii_bus *bus, int mii_id, int regnum, u16 value); + +int fsl_pq_mdio_read(struct mii_bus *bus, int mii_id, int devad, int regnum); +int fsl_pq_mdio_write(struct mii_bus *bus, int mii_id, int devad, int regnum, + u16 value); int fsl_pq_local_mdio_write(struct fsl_pq_mdio __iomem *regs, int mii_id, - int regnum, u16 value); -int fsl_pq_local_mdio_read(struct fsl_pq_mdio __iomem *regs, int mii_id, int regnum); + int regnum, u16 value); +int fsl_pq_local_mdio_read(struct fsl_pq_mdio __iomem *regs, int mii_id, + int regnum); int __init fsl_pq_mdio_init(void); void fsl_pq_mdio_exit(void); void fsl_pq_mdio_bus_name(char *name, struct device_node *np); diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 683ef1c..83f810d 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -253,14 +253,14 @@ EXPORT_SYMBOL(mdiobus_scan); * because the bus read/write functions may wait for an interrupt * to conclude the operation. */ -int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum) +int mdiobus_read(struct mii_bus *bus, int addr, int devad, u16 regnum) { int retval; BUG_ON(in_interrupt()); mutex_lock(&bus->mdio_lock); - retval = bus->read(bus, addr, regnum); + retval = bus->read(bus, addr, devad, regnum); mutex_unlock(&bus->mdio_lock); return retval; @@ -278,14 +278,14 @@ EXPORT_SYMBOL(mdiobus_read); * because the bus read/write functions may wait for an interrupt * to conclude the operation. */ -int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val) +int mdiobus_write(struct mii_bus *bus, int addr, int devad, u16 regnum, u16 val) { int err; BUG_ON(in_interrupt()); mutex_lock(&bus->mdio_lock); - err = bus->write(bus, addr, regnum, val); + err = bus->write(bus, addr, devad, regnum, val); mutex_unlock(&bus->mdio_lock); return err; diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index 3cbda08..2c16be0 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -322,7 +322,7 @@ int phy_mii_ioctl(struct phy_device *phydev, case SIOCGMIIREG: mii_data->val_out = mdiobus_read(phydev->bus, mii_data->phy_id, - mii_data->reg_num); + 0, mii_data->reg_num); break; case SIOCSMIIREG: @@ -353,7 +353,7 @@ int phy_mii_ioctl(struct phy_device *phydev, } } - mdiobus_write(phydev->bus, mii_data->phy_id, + mdiobus_write(phydev->bus, mii_data->phy_id, 0, mii_data->reg_num, val); if (mii_data->reg_num == MII_BMCR && diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index de86a55..f53bc32 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -6,7 +6,7 @@ * * Author: Andy Fleming * - * Copyright (c) 2004 Freescale Semiconductor, Inc. + * Copyright (c) 2004-2006, 2008-2010 Freescale Semiconductor, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -51,15 +52,13 @@ static void phy_device_release(struct device *dev) } static struct phy_driver genphy_driver; +static struct phy_driver gen10g_driver; extern int mdio_bus_init(void); extern void mdio_bus_exit(void); static LIST_HEAD(phy_fixup_list); static DEFINE_MUTEX(phy_fixup_lock); -static int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, - u32 flags, phy_interface_t interface); - /* * Creates a new phy_fixup and adds it to the list * @bus_id: A string which matches phydev->dev.bus_id (or PHY_ANY_ID) @@ -210,23 +209,29 @@ static struct phy_device* phy_device_create(struct mii_bus *bus, static int get_phy_id(struct mii_bus *bus, int addr, u32 *phy_id) { int phy_reg; + int i; + + for (i = 1; i < 5; i++) { + /* Grab the bits from PHYIR1, and put them + * in the upper half */ + phy_reg = mdiobus_read(bus, addr, i, MII_PHYSID1); - /* Grab the bits from PHYIR1, and put them - * in the upper half */ - phy_reg = mdiobus_read(bus, addr, MII_PHYSID1); + if (phy_reg < 0) + return -EIO; - if (phy_reg < 0) - return -EIO; + *phy_id = (phy_reg & 0xffff) << 16; - *phy_id = (phy_reg & 0xffff) << 16; + /* Grab the bits from PHYIR2, and put them in the lower half */ + phy_reg = mdiobus_read(bus, addr, i, MII_PHYSID2); - /* Grab the bits from PHYIR2, and put them in the lower half */ - phy_reg = mdiobus_read(bus, addr, MII_PHYSID2); + if (phy_reg < 0) + return -EIO; - if (phy_reg < 0) - return -EIO; + *phy_id |= (phy_reg & 0xffff); - *phy_id |= (phy_reg & 0xffff); + if (*phy_id != 0xffffffff) + break; + } return 0; } @@ -432,12 +437,12 @@ int phy_init_hw(struct phy_device *phydev) * * Description: Called by drivers to attach to a particular PHY * device. The phy_device is found, and properly hooked up - * to the phy_driver. If no driver is attached, then the - * genphy_driver is used. The phy_device is given a ptr to + * to the phy_driver. If no driver is attached, then a + * generic driver is used. The phy_device is given a ptr to * the attaching device, and given a callback for link status * change. The phy_device is returned to the attaching driver. */ -static int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, +int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, u32 flags, phy_interface_t interface) { struct device *d = &phydev->dev; @@ -446,7 +451,11 @@ static int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, /* Assume that if there is no driver, that it doesn't * exist, and we should use the genphy driver. */ if (NULL == d->driver) { - d->driver = &genphy_driver.driver; + int err; + if (interface == PHY_INTERFACE_MODE_XGMII) + d->driver = &gen10g_driver.driver; + else + d->driver = &genphy_driver.driver; err = d->driver->probe(d); if (err >= 0) @@ -530,6 +539,8 @@ void phy_detach(struct phy_device *phydev) * real driver could be loaded */ if (phydev->dev.driver == &genphy_driver.driver) device_release_driver(&phydev->dev); + else if (phydev->dev.driver == &gen10g_driver.driver) + device_release_driver(&phydev->dev); } EXPORT_SYMBOL(phy_detach); @@ -597,6 +608,12 @@ static int genphy_config_advert(struct phy_device *phydev) return changed; } +int gen10g_config_advert(struct phy_device *dev) +{ + return 0; +} +EXPORT_SYMBOL(gen10g_config_advert); + /** * genphy_setup_forced - configures/forces speed/duplex from @phydev * @phydev: target phy_device struct @@ -625,6 +642,10 @@ static int genphy_setup_forced(struct phy_device *phydev) return err; } +int gen10g_setup_forced(struct phy_device *phydev) +{ + return 0; +} /** * genphy_restart_aneg - Enable and Restart Autonegotiation @@ -650,6 +671,12 @@ int genphy_restart_aneg(struct phy_device *phydev) } EXPORT_SYMBOL(genphy_restart_aneg); +int gen10g_restart_aneg(struct phy_device *phydev) +{ + return 0; +} +EXPORT_SYMBOL(gen10g_restart_aneg); + /** * genphy_config_aneg - restart auto-negotiation or write BMCR @@ -692,6 +719,12 @@ int genphy_config_aneg(struct phy_device *phydev) } EXPORT_SYMBOL(genphy_config_aneg); +int gen10g_config_aneg(struct phy_device *phydev) +{ + return 0; +} +EXPORT_SYMBOL(gen10g_config_aneg); + /** * genphy_update_link - update link status in @phydev * @phydev: target phy_device struct @@ -821,6 +854,33 @@ int genphy_read_status(struct phy_device *phydev) } EXPORT_SYMBOL(genphy_read_status); +int gen10g_read_status(struct phy_device *phydev) +{ + int devad, reg; + u32 mmd_mask = phydev->mmds; + + phydev->link = 1; + + /* For now just lie and say it's 10G all the time */ + phydev->speed = 10000; + phydev->duplex = DUPLEX_FULL; + + for (devad = 0; mmd_mask; devad++, mmd_mask = mmd_mask >> 1) { + if (!mmd_mask & 1) + continue; + + /* Read twice because link state is latched and a + * read moves the current state into the register */ + phy45_read(phydev, devad, MDIO_STAT1); + reg = phy45_read(phydev, devad, MDIO_STAT1); + if (reg < 0 || !(reg & MDIO_STAT1_LSTATUS)) + phydev->link = 0; + } + + return 0; +} +EXPORT_SYMBOL(gen10g_read_status); + static int genphy_config_init(struct phy_device *phydev) { int val; @@ -867,6 +927,36 @@ static int genphy_config_init(struct phy_device *phydev) return 0; } + +/* Replicate mdio45_probe */ +int gen10g_config_init(struct phy_device *phydev) +{ + int mmd, stat2, devs1, devs2; + + phydev->supported = phydev->advertising = SUPPORTED_10000baseT_Full; + + /* Assume PHY must have at least one of PMA/PMD, WIS, PCS, PHY + * XS or DTE XS; give up if none is present. */ + for (mmd = 1; mmd <= 5; mmd++) { + /* Is this MMD present? */ + stat2 = phy45_read(phydev, mmd, MDIO_STAT2); + if (stat2 < 0 || + (stat2 & MDIO_STAT2_DEVPRST) != MDIO_STAT2_DEVPRST_VAL) + continue; + + /* It should tell us about all the other MMDs */ + devs1 = phy45_read(phydev, mmd, MDIO_DEVS1); + devs2 = phy45_read(phydev, mmd, MDIO_DEVS2); + if (devs1 < 0 || devs2 < 0) + continue; + + phydev->mmds = devs1 | (devs2 << 16); + return 0; + } + + return -ENODEV; +} + int genphy_suspend(struct phy_device *phydev) { int value; @@ -882,6 +972,12 @@ int genphy_suspend(struct phy_device *phydev) } EXPORT_SYMBOL(genphy_suspend); +int gen10g_suspend(struct phy_device *phydev) +{ + return 0; +} +EXPORT_SYMBOL(gen10g_suspend); + int genphy_resume(struct phy_device *phydev) { int value; @@ -897,6 +993,13 @@ int genphy_resume(struct phy_device *phydev) } EXPORT_SYMBOL(genphy_resume); +int gen10g_resume(struct phy_device *phydev) +{ + return 0; +} +EXPORT_SYMBOL(gen10g_resume); + + /** * phy_probe - probe and init a PHY device * @dev: device to probe and init @@ -1003,7 +1106,20 @@ static struct phy_driver genphy_driver = { .read_status = genphy_read_status, .suspend = genphy_suspend, .resume = genphy_resume, - .driver = {.owner= THIS_MODULE, }, + .driver = {.owner = THIS_MODULE, }, +}; + +static struct phy_driver gen10g_driver = { + .phy_id = 0xffffffff, + .phy_id_mask = 0xffffffff, + .name = "Generic 10G PHY", + .config_init = gen10g_config_init, + .features = 0, + .config_aneg = gen10g_config_aneg, + .read_status = gen10g_read_status, + .suspend = gen10g_suspend, + .resume = gen10g_resume, + .driver = {.owner = THIS_MODULE, }, }; static int __init phy_init(void) @@ -1016,13 +1132,25 @@ static int __init phy_init(void) rc = phy_driver_register(&genphy_driver); if (rc) - mdio_bus_exit(); + goto genphy_register_failed; + + rc = phy_driver_register(&gen10g_driver); + if (rc) + goto gen10g_register_failed; + + return rc; + +gen10g_register_failed: + phy_driver_unregister(&genphy_driver); +genphy_register_failed: + mdio_bus_exit(); return rc; } static void __exit phy_exit(void) { + phy_driver_unregister(&gen10g_driver); phy_driver_unregister(&genphy_driver); mdio_bus_exit(); } diff --git a/include/linux/phy.h b/include/linux/phy.h index c291cae..e44e8ec 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -6,7 +6,7 @@ * * Author: Andy Fleming * - * Copyright (c) 2004 Freescale Semiconductor, Inc. + * Copyright (c) 2004-2010 Freescale Semiconductor, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -64,6 +64,7 @@ typedef enum { PHY_INTERFACE_MODE_RGMII_TXID, PHY_INTERFACE_MODE_RTBI, PHY_INTERFACE_MODE_SMII, + PHY_INTERFACE_MODE_XGMII } phy_interface_t; @@ -98,8 +99,10 @@ struct mii_bus { const char *name; char id[MII_BUS_ID_SIZE]; void *priv; - int (*read)(struct mii_bus *bus, int phy_id, int regnum); - int (*write)(struct mii_bus *bus, int phy_id, int regnum, u16 val); + int (*read)(struct mii_bus *bus, int port_addr, int dev_addr, + int regnum); + int (*write)(struct mii_bus *bus, int port_addr, int dev_addr, + int regnum, u16 val); int (*reset)(struct mii_bus *bus); /* @@ -141,8 +144,9 @@ int mdiobus_register(struct mii_bus *bus); void mdiobus_unregister(struct mii_bus *bus); void mdiobus_free(struct mii_bus *bus); struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr); -int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum); -int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val); +int mdiobus_read(struct mii_bus *bus, int addr, int devad, u16 regnum); +int mdiobus_write(struct mii_bus *bus, int addr, int devad, + u16 regnum, u16 val); #define PHY_INTERRUPT_DISABLED 0x0 @@ -313,6 +317,7 @@ struct phy_device { /* See mii.h for more info */ u32 supported; u32 advertising; + u32 mmds; int autoneg; @@ -462,7 +467,22 @@ struct phy_fixup { */ static inline int phy_read(struct phy_device *phydev, u32 regnum) { - return mdiobus_read(phydev->bus, phydev->addr, regnum); + return mdiobus_read(phydev->bus, phydev->addr, 0, regnum); +} + +/** + * phy45_read - Convenience function for reading a given port/dev/reg address + * @phydev: The phy_device struct + * @devad: The device address to read + * @regnum: The register number to read + * + * NOTE: MUST NOT be called from interrupt context, + * because the bus read/write functions may wait for an interrupt + * to conclude the operation. + */ +static inline int phy45_read(struct phy_device *phydev, int devad, u16 regnum) +{ + return mdiobus_read(phydev->bus, phydev->addr, devad, regnum); } /** @@ -477,11 +497,30 @@ static inline int phy_read(struct phy_device *phydev, u32 regnum) */ static inline int phy_write(struct phy_device *phydev, u32 regnum, u16 val) { - return mdiobus_write(phydev->bus, phydev->addr, regnum, val); + return mdiobus_write(phydev->bus, phydev->addr, 0, regnum, val); +} + +/** + * phy45_write - Convenience function for writing a given port/dev/reg + * @phydev: the phy_device struct + * @devad: the device addr + * @regnum: register number to write + * @val: value to write to @regnum + * + * NOTE: MUST NOT be called from interrupt context, + * because the bus read/write functions may wait for an interrupt + * to conclude the operation. + */ +static inline int phy45_write(struct phy_device *phydev, u16 regnum, + int devad, u16 val) +{ + return mdiobus_write(phydev->bus, phydev->addr, devad, regnum, val); } struct phy_device* get_phy_device(struct mii_bus *bus, int addr); int phy_device_register(struct phy_device *phy); +int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, + u32 flags, phy_interface_t interface); int phy_init_hw(struct phy_device *phydev); struct phy_device * phy_attach(struct net_device *dev, const char *bus_id, u32 flags, phy_interface_t interface);