RK3568 PHY driverフレームワークの解説

RK3568プラットフォームにおいて、PHY芯片每には struct phy_device 型のデバイスが生成され、対応する struct phy_driver 型のドライバが割り当てられる。これらは mdio_bus_type バスに接続されており、MACは struct net_device として登録される。

  • バス - struct mii_bus(MIIはメディア独立インターフェースを意味する)
  • デバイス - struct phy_device
  • _driver - struct phy_driver

コードパス:rk356x-linux/kernel/drivers/net/phy、RK内部EPHYドライバコードは drivers/net/phy/rockchip.c

GMACドライバコードの場所:drivers/net/ethernet/stmicro/stmmac/*

rk356x-linux/kernel/drivers/net/ethernet/stmicro/stmmac/stmmac_mdio.c

  1. phy_device構造体
struct phy_device {
    struct mdio_device mdio;
    struct phy_driver *drv;
    u32 phy_id;
    struct phy_c45_device_ids c45_ids;
    unsigned is_c45:1;
    unsigned is_internal:1;
    unsigned is_pseudo_fixed_link:1;
    unsigned is_gigabit_capable:1;
    unsigned has_fixups:1;
    unsigned suspended:1;
    unsigned suspended_by_mdio_bus:1;
    unsigned sysfs_links:1;
    unsigned loopback_enabled:1;
    unsigned downshifted_rate:1;
    unsigned autoneg:1;
    unsigned link:1;
    unsigned autoneg_complete:1;
    unsigned interrupts:1;
    enum phy_state state;
    u32 dev_flags;
    phy_interface_t interface;
    int speed;
    int duplex;
    int port;
    int pause;
    int asym_pause;
    u8 master_slave_get;
    u8 master_slave_set;
    u8 master_slave_state;
    __ETHTOOL_DECLARE_LINK_MODE_MASK(supported);
    __ETHTOOL_DECLARE_LINK_MODE_MASK(advertising);
    __ETHTOOL_DECLARE_LINK_MODE_MASK(lp_advertising);
    __ETHTOOL_DECLARE_LINK_MODE_MASK(adv_old);
    u32 eee_broken_modes;
#ifdef CONFIG_LED_TRIGGER_PHY
    struct phy_led_trigger *phy_led_triggers;
    unsigned int phy_num_led_triggers;
    struct phy_led_trigger *last_triggered;
    struct phy_led_trigger *led_link_trigger;
#endif
    int irq;
    void *priv;
    struct phy_package_shared *shared;
    struct sk_buff *skb;
    void *ehdr;
    struct nlattr *nest;
    struct delayed_work state_queue;
    struct mutex lock;
    bool sfp_bus_attached;
    struct sfp_bus *sfp_bus;
    struct phylink *phylink;
    struct net_device *attached_dev;
    struct mii_timestamper *mii_ts;
    u8 mdix;
    u8 mdix_ctrl;
    void (*phy_link_change)(struct phy_device *phydev, bool up);
    void (*adjust_link)(struct net_device *dev);
#if IS_ENABLED(CONFIG_MACSEC)
    const struct macsec_ops *macsec_ops;
#endif
    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
    ANDROID_KABI_RESERVE(3);
    ANDROID_KABI_RESERVE(4);
};
  1. phy_driver構造体
struct phy_driver {
    struct mdio_driver_common mdiodrv;
    u32 phy_id;
    char *name;
    u32 phy_id_mask;
    const unsigned long * const features;
    u32 flags;
    const void *driver_data;

    int (*soft_reset)(struct phy_device *phydev);
    int (*config_init)(struct phy_device *phydev);
    int (*probe)(struct phy_device *phydev);
    int (*get_features)(struct phy_device *phydev);
    int (*suspend)(struct phy_device *phydev);
    int (*resume)(struct phy_device *phydev);
    int (*config_aneg)(struct phy_device *phydev);
    int (*aneg_done)(struct phy_device *phydev);
    int (*read_status)(struct phy_device *phydev);
    int (*ack_interrupt)(struct phy_device *phydev);
    int (*config_intr)(struct phy_device *phydev);
    int (*did_interrupt)(struct phy_device *phydev);
    irqreturn_t (*handle_interrupt)(struct phy_device *phydev);
    void (*remove)(struct phy_device *phydev);
    int (*match_phy_device)(struct phy_device *phydev);
    int (*set_wol)(struct phy_device *dev, struct ethtool_wolinfo *wol);
    void (*get_wol)(struct phy_device *dev, struct ethtool_wolinfo *wol);
    void (*link_change_notify)(struct phy_device *dev);
    int (*read_mmd)(struct phy_device *dev, int devnum, u16);
    int (*write_mmd)(struct phy_device *dev, int devnum, u16, u16);
    int (*read_page)(struct phy_device *dev);
    int (*write_page)(struct phy_device *dev, int page);
    int (*module_info)(struct phy_device *dev, struct ethtool_modinfo *);
    int (*module_eeprom)(struct phy_device *dev, struct ethtool_eeprom *, u8 *);
    int (*cable_test_start)(struct phy_device *dev);
    int (*cable_test_tdr_start)(struct phy_device *dev, const struct phy_tdr_config *);
    int (*cable_test_get_status)(struct phy_device *dev, bool *);
    int (*get_sset_count)(struct phy_device *dev);
    void (*get_strings)(struct phy_device *dev, u8 *data);
    void (*get_stats)(struct phy_device *dev, struct ethtool_stats *, u64 *);
    int (*get_tunable)(struct phy_device *dev, struct ethtool_tunable *, void *);
    int (*set_tunable)(struct phy_device *dev, struct ethtool_tunable *, const void *);
    int (*set_loopback)(struct phy_device *dev, bool enable);
    int (*get_sqi)(struct phy_device *dev);
    int (*get_sqi_max)(struct phy_device *dev);
    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
};
  1. mii_bus関連構造体
struct mii_bus {
    struct module *owner;
    const char *name;
    char id[MII_BUS_ID_SIZE];
    void *priv;
    int (*read)(struct mii_bus *bus, int addr, int);
    int (*write)(struct mii_bus *bus, int addr, int, u16 val);
    int (*reset)(struct mii_bus *bus);
    struct mdio_bus_stats stats[PHY_MAX_ADDR];
    struct mutex mdio_lock;
    struct device *parent;
    enum {
        MDIOBUS_ALLOCATED = 1,
        MDIOBUS_REGISTERED,
        MDIOBUS_UNREGISTERED,
        MDIOBUS_RELEASED,
    } state;
    struct device dev;
    struct mdio_device *mdio_map[PHY_MAX_ADDR];
    u32 phy_mask;
    u32 phy_ignore_ta_mask;
    int irq[PHY_MAX_ADDR];
    int reset_delay_us;
    int reset_post_delay_us;
    struct gpio_desc *reset_gpiod;
    enum {
        MDIOBUS_NO_CAP = 0,
        MDIOBUS_C22,
        MDIOBUS_C45,
        MDIOBUS_C22_C45,
    } probe_capabilities;

    struct mutex shared_lock;
    struct phy_package_shared *shared[PHY_MAX_ADDR];
};
  1. phy驱动フレームワーク
#define phy_module_driver(__phy_drivers, __count)           \
static int __init phy_module_init(void)                     \
{                                                            \
    return phy_drivers_register(__phy_drivers, __count, THIS_MODULE); \
}                                                            \
module_init(phy_module_init);

(1). phy_driverの初期化 - phy_init

static int __init phy_init(void)
{
    int rc;
    rc = mdio_bus_init();
    if (rc)
        return rc;
    ethtool_set_ethtool_phy_ops(&phy_ethtool_phy_ops);
    features_init();

    rc = phy_driver_register(&genphy_c45_driver, THIS_MODULE);
    if (rc)
        goto err_c45;

    rc = phy_driver_register(&genphy_driver, THIS_MODULE);
    if (rc) {
        phy_driver_register(&genphy_c45_driver);
err_c45:
        mdio_bus_exit();
    }
    return rc;
}

//mdioバス驅動を登録
int __init mdio_bus_init(void)
{
    int ret;
    ret = class_register(&mdio_bus_class);
    if (!ret) {
        ret = bus_register(&mdio_bus_type);
        if (ret)
            class_unregister(&mdio_bus_class);
    }
    return ret;
}
int phy_driver_register(struct phy_driver *new_driver, struct module *owner)
{
    int retval;

    if (WARN_ON(new_driver->features && new_driver->get_features)) {
        pr_err("%s: features and get_features must not both be set\n",
               new_driver->name);
        return -EINVAL;
    }

    new_driver->mdiodrv.flags |= MDIO_DEVICE_IS_PHY;
    new_driver->mdiodrv.driver.name = new_driver->name;
    new_driver->mdiodrv.driver.bus = &mdio_bus_type;
    new_driver->mdiodrv.driver.probe = phy_probe;
    new_driver->mdiodrv.driver.remove = phy_remove;
    new_driver->mdiodrv.driver.owner = owner;
    new_driver->mdiodrv.driver.probe_type = PROBE_FORCE_SYNCHRONOUS;

    retval = driver_register(&new_driver->mdiodrv.driver);
    if (retval) {
        pr_err("%s: Error %d in registering driver\n",
               new_driver->name, retval);
        return retval;
    }
    pr_debug("%s: Registered new driver\n", new_driver->name);
    return 0;
}

rk356x-linux/kernel/drivers/net/phy/phy_device.c

struct phy_device *phy_device_create(struct mii_bus *bus, int addr, u32 phy_id,
                     bool is_c45,
                     struct phy_c45_device_ids *c45_ids)
{
    struct phy_device *dev;
    struct mdio_device *mdiodev;
    int ret = 0;

    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return ERR_PTR(-ENOMEM);

    mdiodev = &dev->mdio;
    mdiodev->dev.parent = &bus->dev;
    mdiodev->dev.bus = &mdio_bus_type;
    mdiodev->dev.type = &mdio_bus_phy_type;
    mdiodev->bus = bus;
    mdiodev->bus_match = phy_bus_match;
    mdiodev->addr = addr;
    mdiodev->flags = MDIO_DEVICE_FLAG_PHY;
    mdiodev->device_free = phy_mdio_device_free;
    mdiodev->device_remove = phy_mdio_device_remove;

    dev->speed = SPEED_UNKNOWN;
    dev->duplex = DUPLEX_UNKNOWN;
    dev->pause = 0;
    dev->asym_pause = 0;
    dev->link = 0;
    dev->port = PORT_TP;
    dev->interface = PHY_INTERFACE_MODE_GMII;
    dev->autoneg = AUTONEG_ENABLE;

    dev->is_c45 = is_c45;
    dev->phy_id = phy_id;
    if (c45_ids)
        dev->c45_ids = *c45_ids;
    dev->irq = bus->irq[addr];

    dev_set_name(&mdiodev->dev, PHY_ID_FMT, bus->id, addr);
    device_initialize(&mdiodev->dev);

    dev->state = PHY_DOWN;
    mutex_init(&dev->lock);
    INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);

    if (is_c45 && c45_ids) {
        const int num_ids = ARRAY_SIZE(c45_ids->device_ids);
        int i;

        for (i = 1; i < num_ids; i++) {
            if (c45_ids->device_ids[i] == 0xffffffff)
                continue;

            ret = phy_request_driver_module(dev, c45_ids->device_ids[i]);
            if (ret)
                break;
        }
    } else {
        ret = phy_request_driver_module(dev, phy_id);
    }

    if (ret) {
        put_device(&mdiodev->dev);
        dev = ERR_PTR(ret);
    }

    return dev;
}
EXPORT_SYMBOL(phy_device_create);

static int phy_bus_match(struct device *dev, struct device_driver *drv)
{
    struct phy_device *phydev = to_phy_device(dev);
    struct phy_driver *phydrv = to_phy_driver(drv);
    const int num_ids = ARRAY_SIZE(phydev->c45_ids.device_ids);
    int i;

    if (!(phydrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY))
        return 0;

    if (phydrv->match_phy_device)
        return phydrv->match_phy_device(phydev);

    if (phydev->is_c45) {
        for (i = 1; i < num_ids; i++) {
            if (phydev->c45_ids.device_ids[i] == 0xffffffff)
                continue;

            if ((phydrv->phy_id & phydrv->phy_id_mask) ==
                (phydev->c45_ids.device_ids[i] & phydrv->phy_id_mask))
                return 1;
        }
        return 0;
    } else {
        return (phydrv->phy_id & phydrv->phy_id_mask) ==
            (phydev->phy_id & phydrv->phy_id_mask);
    }
}

phydrv->match_phy_device関数の登録例:

.match_phy_device = rtlgen_match_phy_device,
static struct phy_driver genphy_driver = {
    .phy_id        = 0xffffffff,
    .phy_id_mask   = 0xffffffff,
    .name          = "Generic PHY",
    .get_features  = genphy_read_abilities,
    .suspend       = genphy_suspend,
    .resume        = genphy_resume,
    .set_loopback  = genphy_loopback,
};

(2)mdio初期化

static struct attribute *mdio_bus_device_statistics_attrs[] = {
    &dev_attr_mdio_bus_device_transfers.attr.attr,
    &dev_attr_mdio_bus_device_errors.attr.attr,
    &dev_attr_mdio_bus_device_writes.attr.attr,
    &dev_attr_mdio_bus_device_reads.attr.attr,
    NULL,
};

static const struct attribute_group mdio_bus_device_statistics_group = {
    .name   = "statistics",
    .attrs  = mdio_bus_device_statistics_attrs,
};

static const struct attribute_group *mdio_bus_dev_groups[] = {
    &mdio_bus_device_statistics_group,
    NULL,
};

static int mdio_bus_match(struct device *dev, struct device_driver *drv)
{
    struct mdio_device *mdio = to_mdio_device(dev);

    if (of_driver_match_device(dev, drv))
        return 1;

    if (mdio->bus_match)
        return mdio->bus_match(dev, drv);

    return 0;
}

struct bus_type mdio_bus_type = {
    .name       = "mdio_bus",
    .dev_groups = mdio_bus_dev_groups,
    .match      = mdio_bus_match,
    .uevent     = mdio_uevent,
};
EXPORT_SYMBOL(mdio_bus_type);

int __init mdio_bus_init(void)
{
    int ret;
    ret = class_register(&mdio_bus_class);
    if (!ret) {
        ret = bus_register(&mdio_bus_type);
        if (ret)
            class_unregister(&mdio_bus_class);
    }
    return ret;
}
  1. mdioマッチング関係:mdio_device.c / mdio_bus.c
struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr)
{
    struct mdio_device *mdiodev;

    mdiodev = kzalloc(sizeof(*mdiodev), GFP_KERNEL);
    if (!mdiodev)
        return ERR_PTR(-ENOMEM);

    mdiodev->dev.release = mdio_device_release;
    mdiodev->dev.parent = &bus->dev;
    mdiodev->dev.bus = &mdio_bus_type;
    mdiodev->device_free = mdio_device_free;
    mdiodev->device_remove = mdio_device_remove;
    mdiodev->bus = bus;
    mdiodev->addr = addr;

    dev_set_name(&mdiodev->dev, PHY_ID_FMT, bus->id, addr);
    device_initialize(&mdiodev->dev);

    return mdiodev;
}
EXPORT_SYMBOL(mdio_device_create);

struct bus_type mdio_bus_type = {
    .name       = "mdio_bus",
    .dev_groups = mdio_bus_dev_groups,
    .match      = mdio_bus_match,
    .uevent     = mdio_uevent,
};

static int mdio_bus_match(struct device *dev, struct device_driver *drv)
{
    struct mdio_device *mdio = to_mdio_device(dev);

    if (of_driver_match_device(dev, drv))
        return 1;

    if (mdio->bus_match)
        return mdio->bus_match(dev, drv);

    return 0;
}

呼び出し関係図

rk_gmac_probe
  └→ stmmac_dvr_probe
       └→ stmmac_mdio_register(stmmac_mdio_read)
            └→ of_mdiobus_register
                 └→ mdiobus_register
                      └→ __mdiobus_register
                           └→ mdiobus_scan
                                └→ get_phy_device
                                     └→ phy_device_create

stmmacマッチング規則(of_match_table関数)

static const struct of_device_id rk_gmac_dwmac_match[] = {
#ifdef CONFIG_CPU_PX30
    { .compatible = "rockchip,px30-gmac",   .data = &px30_ops   },
#endif
#ifdef CONFIG_CPU_RK1808
    { .compatible = "rockchip,rk1808-gmac", .data = &rk1808_ops },
#endif
#ifdef CONFIG_CPU_RK312X
    { .compatible = "rockchip,rk3128-gmac", .data = &rk3128_ops },
#endif
#ifdef CONFIG_CPU_RK322X
    { .compatible = "rockchip,rk3228-gmac", .data = &rk3228_ops },
#endif
#ifdef CONFIG_CPU_RK3288
    { .compatible = "rockchip,rk3288-gmac", .data = &rk3288_ops },
#endif
#ifdef CONFIG_CPU_RK3308
    { .compatible = "rockchip,rk3308-mac",  .data = &rk3308_ops },
#endif
#ifdef CONFIG_CPU_RK3328
    { .compatible = "rockchip,rk3328-gmac", .data = &rk3328_ops },
#endif
#ifdef CONFIG_CPU_RK3366
    { .compatible = "rockchip,rk3366-gmac", .data = &rk3366_ops },
#endif
#ifdef CONFIG_CPU_RK3368
    { .compatible = "rockchip,rk3368-gmac", .data = &rk3368_ops },
#endif
#ifdef CONFIG_CPU_RK3399
    { .compatible = "rockchip,rk3399-gmac", .data = &rk3399_ops },
#endif
#ifdef CONFIG_CPU_RK3528
    { .compatible = "rockchip,rk3528-gmac", .data = &rk3528_ops },
#endif
#ifdef CONFIG_CPU_RK3562
    { .compatible = "rockchip,rk3562-gmac", .data = &rk3562_ops },
#endif
#ifdef CONFIG_CPU_RK3568
    { .compatible = "rockchip,rk3568-gmac", .data = &rk3568_ops },
#endif
#ifdef CONFIG_CPU_RK3588
    { .compatible = "rockchip,rk3588-gmac", .data = &rk3588_ops },
#endif
#ifdef CONFIG_CPU_RV1106
    { .compatible = "rockchip,rv1106-gmac", .data = &rv1106_ops },
#endif
#ifdef CONFIG_CPU_RV1108
    { .compatible = "rockchip,rv1108-gmac", .data = &rv1108_ops },
#endif
#ifdef CONFIG_CPU_RV1126
    { .compatible = "rockchip,rv1126-gmac", .data = &rv1126_ops },
#endif
    { }
};
MODULE_DEVICE_TABLE(of, rk_gmac_dwmac_match);

static struct platform_driver rk_gmac_dwmac_driver = {
    .probe  = rk_gmac_probe,
    .remove = rk_gmac_remove,
    .driver = {
        .name           = "rk_gmac-dwmac",
        .pm             = &rk_gmac_pm_ops,
        .of_match_table = rk_gmac_dwmac_match,
    },
};

デバイストリー解析:rk356x-linux/kernel/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c

struct plat_stmmacenet_data *stmmac_probe_config_dt(struct platform_device *pdev, const char **mac)

mdioデバイストリーマッチング規則

int of_mdiobus_register(struct mii_bus *mdio, struct device_node *np)
{
    struct device_node *child;
    bool scanphys = false;
    int addr, rc;

    if (!np)
        return mdiobus_register(mdio);

    if (!of_device_is_available(np))
        return -ENODEV;

    mdio->phy_mask = ~0;
    mdio->dev.of_node = np;
    mdio->dev.fwnode = of_fwnode_handle(np);

    mdio->reset_delay_us = DEFAULT_GPIO_RESET_DELAY;
    of_property_read_u32(np, "reset-delay-us", &mdio->reset_delay_us);
    mdio->reset_post_delay_us = 0;
    of_property_read_u32(np, "reset-post-delay-us", &mdio->reset_post_delay_us);

    rc = mdiobus_register(mdio);
    if (rc)
        return rc;

    for_each_available_child_of_node(np, child) {
        addr = of_mdio_parse_addr(&mdio->dev, child);
        if (addr < 0) {
            scanphys = true;
            continue;
        }

        if (of_mdiobus_child_is_phy(child))
            rc = of_mdiobus_register_phy(mdio, child, addr);
        else
            rc = of_mdiobus_register_device(mdio, child, addr);

        if (rc == -ENODEV)
            dev_err(&mdio->dev,
                "MDIO device at address %d is missing.\n",
                addr);
        else if (rc)
            goto unregister;
    }

    if (!scanphys)
        return 0;

    for_each_available_child_of_node(np, child) {
        if (of_find_property(child, "reg", NULL))
            continue;

        for (addr = 0; addr < PHY_MAX_ADDR; addr++) {
            if (mdiobus_is_registered_device(mdio, addr))
                continue;

            dev_info(&mdio->dev, "scan phy %pOFn at address %i\n",
                 child, addr);

            if (of_mdiobus_child_is_phy(child)) {
                rc = of_mdiobus_register_phy(mdio, child, addr);
                if (!rc)
                    break;
                if (rc != -ENODEV)
                    goto unregister;
            }
        }
    }

    return 0;

unregister:
    of_node_put(child);
    mdiobus_unregister(mdio);
    return rc;
}
EXPORT_SYMBOL(of_mdiobus_register);

6月12日 23:32 投稿