2020年2月28日 星期五

Linux Kernel(16.2)- PHY Abstraction Layer I


Purpose

多數的network devices都是透過MAC去存取PHY上的register,而network device driver就是利用這些register去決定如何配置該網路裝置, PHY的register都是遵守相同的標準,因而建立PHY Abstraction Layer的目的就是減少network device driver的loading,因為PHY的driver在此了, 三個目的如下:
  • 1) Increase code-reuse
  • 2) Increase overall code-maintainability
  • 3) Speed development time for new network drivers, and for new systems


The MDIO bus

多數的network devices都是透過所謂的management bus與PHY溝通,不同的network devices會使用不同的bus, 這些bus要被註冊到對應的network devices上,這些bus的interface必須遵守以下規則
  1. read and write function must be implemented.
    int write(struct mii_bus *bus, int mii_id, int regnum, u16 value);
    int read(struct mii_bus *bus, int mii_id, int regnum);
    mii_id是PHY在該bus上的ID
  2. reset function可有可無
  3. probe function是必要的,用以設定PHY driver所需的任何東西,
可以參考drivers/net/ethernet/freescale/fsl_pq_mdio.c為例的mdio bus driver,以下為我摘要的片段
static struct platform_driver fsl_pq_mdio_driver = {
    .driver = {
        .name = "fsl-pq_mdio",
        .of_match_table = fsl_pq_mdio_match,
    },
    .probe = fsl_pq_mdio_probe,
    .remove = fsl_pq_mdio_remove,
};

static int fsl_pq_mdio_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    struct mii_bus *new_bus;

    new_bus = mdiobus_alloc_size(sizeof(*priv));
    if (!new_bus)
        return -ENOMEM;

    priv = new_bus->priv;

    new_bus->name = "Freescale PowerQUICC MII Bus",
    new_bus->read = &fsl_pq_mdio_read;
    new_bus->write = &fsl_pq_mdio_write;
    new_bus->reset = &fsl_pq_mdio_reset;

    new_bus->parent = &pdev->dev;
    platform_set_drvdata(pdev, new_bus);
    err = of_mdiobus_register(new_bus, np);

    return 0;
}

/*
 * Read the bus for PHY at addr mii_id, register regnum, and return the value.
 * Clears miimcom first.
 *
 * All PHY operation done on the bus attached to the local interface, which
 * may be different from the generic mdio bus.  This is helpful in programming
 * interfaces like the TBI which, in turn, control interfaces like on-chip
 * SERDES 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.
 */
static int fsl_pq_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
{
    struct fsl_pq_mdio_priv *priv = bus->priv;
    struct fsl_pq_mii __iomem *regs = priv->regs;
    unsigned int timeout;
    u16 value;

    /* Set the PHY address and the register address we want to read */
    iowrite32be((mii_id << 8) | regnum, ®s->miimadd);

    /* Clear miimcom, and then initiate a read */
    iowrite32be(0, ®s->miimcom);
    iowrite32be(MII_READ_COMMAND, ®s->miimcom);

    /* Wait for the transaction to finish, normally less than 100us */
    timeout = MII_TIMEOUT;
    while ((ioread32be(®s->miimind) &
           (MIIMIND_NOTVALID | MIIMIND_BUSY)) && timeout) {
        cpu_relax();
        timeout--;
    }

    if (!timeout)
        return -ETIMEDOUT;


    /* Grab the value of the register from miimstat */
    value = ioread32be(®s->miimstat);

    dev_dbg(&bus->dev, "read %04x from address %x/%x\n", value, mii_id, regnum);
    return value;
}

/*
 * Write value to the PHY at mii_id at register regnum, on the bus attached
 * to the local interface, which may be different from the generic mdio bus
 * (tied to a single interface), waiting until the write is done before
 * returning. This is helpful in programming interfaces like the TBI which
 * control interfaces like onchip SERDES 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 example.
 */
static int fsl_pq_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
        u16 value)
{
    struct fsl_pq_mdio_priv *priv = bus->priv;
    struct fsl_pq_mii __iomem *regs = priv->regs;
    unsigned int timeout;

    /* Set the PHY address and the register address we want to write */
    iowrite32be((mii_id << 8) | regnum, ®s->miimadd);

    /* Write out the value we want */
    iowrite32be(value, ®s->miimcon);

    /* Wait for the transaction to finish */
    timeout = MII_TIMEOUT;
    while ((ioread32be(®s->miimind) & MIIMIND_BUSY) && timeout) {
        cpu_relax();
        timeout--;
    }

    return timeout ? 0 : -ETIMEDOUT;
}

/* Reset the MIIM registers, and wait for the bus to free */
static int fsl_pq_mdio_reset(struct mii_bus *bus)
{
    struct fsl_pq_mdio_priv *priv = bus->priv;
    struct fsl_pq_mii __iomem *regs = priv->regs;
    unsigned int timeout;

    mutex_lock(&bus->mdio_lock);

    /* Reset the management interface */
    iowrite32be(MIIMCFG_RESET, ®s->miimcfg);

    /* Setup the MII Mgmt clock speed */
    iowrite32be(MIIMCFG_INIT_VALUE, ®s->miimcfg);

    /* Wait until the bus is free */
    timeout = MII_TIMEOUT;
    while ((ioread32be(®s->miimind) & MIIMIND_BUSY) && timeout) {
        cpu_relax();
        timeout--;
    }

    mutex_unlock(&bus->mdio_lock);

    if (!timeout) {
        dev_err(&bus->dev, "timeout waiting for MII bus\n");
        return -EBUSY;
    }

    return 0;
}


(RG)MII/electrical interface considerations

Due to this design decision, a 1.5ns to 2ns delay must be added between the clock line (RXC or TXC) and the data lines to let the PHY (clock sink) have enough setup and hold times to sample the data lines correctly.



我也摘錄一段Qualcomm的EMAC probe部分,
https://github.com/mauronofrio/android_kernel_nubia_msm8953/blob/master/drivers/net/ethernet/qualcomm/emac/emac_phy.c
int emac_phy_config_external(struct platform_device *pdev,
                 struct emac_adapter *adpt)
{
    struct device_node *np = pdev->dev.of_node;
    struct mii_bus *mii_bus;
    int ret;

    /* Create the mii_bus object for talking to the MDIO bus */
    mii_bus = devm_mdiobus_alloc(&pdev->dev);
    adpt->mii_bus = mii_bus;

    if (!mii_bus)
        return -ENOMEM;

    mii_bus->name = "emac-mdio";
    snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s", pdev->name);
    mii_bus->read = emac_mdio_read;
    mii_bus->write = emac_mdio_write;
    mii_bus->parent = &pdev->dev;
    mii_bus->priv = adpt;

    if (ACPI_COMPANION(&pdev->dev)) {
        u32 phy_addr;

        ret = mdiobus_register(mii_bus);
        if (ret) {
            emac_err(adpt, "could not register mdio bus\n");
            return ret;
        }
        ret = device_property_read_u32(&pdev->dev, "phy-channel",
                           &phy_addr);
        if (ret)
            /* If we can't read a valid phy address, then assume
             * that there is only one phy on this mdio bus.
             */
            adpt->phydev = phy_find_first(mii_bus);
        else
            adpt->phydev = mii_bus->phy_map[phy_addr];
    } else {
        struct device_node *phy_np;

        ret = of_mdiobus_register(mii_bus, np);

        if (ret) {
            emac_err(adpt, "could not register mdio bus\n");
            return ret;
        }

        phy_np = of_parse_phandle(np, "phy-handle", 0);
        adpt->phydev = of_phy_find_device(phy_np);
        of_node_put(phy_np);
    }

    if (!adpt->phydev) {
        emac_err(adpt, "could not find external phy\n");
        mdiobus_unregister(mii_bus);
        return -ENODEV;
    }

    if (!adpt->phydev->phy_id) {
        emac_err(adpt, "External phy is not up\n");
        mdiobus_unregister(mii_bus);
        return -EPROBE_DEFER;
    }

    if (adpt->phydev->drv) {
        emac_dbg(adpt, probe, "attached PHY driver [%s] ",
             adpt->phydev->drv->name);
        emac_dbg(adpt, probe, "(mii_bus:phy_addr=%s, irq=%d)\n",
             dev_name(&adpt->phydev->dev), adpt->phydev->irq);
    }

    /* Set initial link status to false */
    adpt->phydev->link = 0;
    return 0;
}

/**
 * of_mdiobus_register - Register mii_bus and create PHYs from the device tree
 * @mdio: pointer to mii_bus structure
 * @np: pointer to device_node of MDIO bus.
 *
 * This function registers the mii_bus structure and registers a phy_device
 * for each child node of @np.
 */
int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
{
    struct device_node *child;
    const __be32 *paddr;
    bool scanphys = false;
    int addr, rc, i;

    /* Mask out all PHYs from auto probing.  Instead the PHYs listed in
     * the device tree are populated after the bus has been registered */
    mdio->phy_mask = ~0;

    /* Clear all the IRQ properties */
    if (mdio->irq)
        for (i=0; iirq[i] = PHY_POLL;

    mdio->dev.of_node = np;

    /* Register the MDIO bus */
    rc = mdiobus_register(mdio);
    if (rc)
        return rc;


    /* Loop over the child nodes and register a phy_device for each one */
    for_each_available_child_of_node(np, child) {
        addr = of_mdio_parse_addr(&mdio->dev, child);
        if (addr < 0) {
            scanphys = true;
            continue;
        }

        rc = of_mdiobus_register_phy(mdio, child, addr);
        if (rc)
            continue;
    }

    if (!scanphys)
        return 0;

    /* auto scan for PHYs with empty reg property */
    for_each_available_child_of_node(np, child) {
        /* Skip PHYs with reg property set */
        paddr = of_get_property(child, "reg", NULL);
        if (paddr)
            continue;

        for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
            /* skip already registered PHYs */
            if (mdio->phy_map[addr])
                continue;

            /* be noisy to encourage people to set reg property */
            dev_info(&mdio->dev, "scan phy %s at address %i\n",
                 child->name, addr);

            rc = of_mdiobus_register_phy(mdio, child, addr);
            if (rc)
                continue;
        }
    }

    return 0;
}



這兩個sample code都是簡略的顯示management bus的部分,主要展示mdio bus device driver的基本寫法,填寫name、read()、write()、reset()後使用of_mdiobus_register()註冊,

    參考資料:
  • kernel/msm-4.14/Documentation/networking/phy.txt



沒有留言:

張貼留言

熱門文章