1a2443fd1SAndrew Lunn // SPDX-License-Identifier: GPL-2.0+ 26539c44dSDavid S. Miller /* 36539c44dSDavid S. Miller * Fixed MDIO bus (MDIO bus emulation with fixed PHYs) 46539c44dSDavid S. Miller * 56539c44dSDavid S. Miller * Author: Vitaly Bordug <vbordug@ru.mvista.com> 66539c44dSDavid S. Miller * Anton Vorontsov <avorontsov@ru.mvista.com> 76539c44dSDavid S. Miller * 86539c44dSDavid S. Miller * Copyright (c) 2006-2007 MontaVista Software, Inc. 96539c44dSDavid S. Miller */ 106539c44dSDavid S. Miller 116539c44dSDavid S. Miller #include <linux/kernel.h> 126539c44dSDavid S. Miller #include <linux/module.h> 136539c44dSDavid S. Miller #include <linux/platform_device.h> 146539c44dSDavid S. Miller #include <linux/list.h> 156539c44dSDavid S. Miller #include <linux/mii.h> 166539c44dSDavid S. Miller #include <linux/phy.h> 176539c44dSDavid S. Miller #include <linux/phy_fixed.h> 186539c44dSDavid S. Miller #include <linux/err.h> 196539c44dSDavid S. Miller #include <linux/slab.h> 206539c44dSDavid S. Miller #include <linux/of.h> 215468e82fSLinus Walleij #include <linux/gpio/consumer.h> 22bf7afb29SRussell King #include <linux/seqlock.h> 2369fc58a5SFlorian Fainelli #include <linux/idr.h> 24b3e5464eSJoakim Tjernlund #include <linux/netdevice.h> 256539c44dSDavid S. Miller 265ae68b0cSRussell King #include "swphy.h" 275ae68b0cSRussell King 286539c44dSDavid S. Miller struct fixed_mdio_bus { 296539c44dSDavid S. Miller struct mii_bus *mii_bus; 306539c44dSDavid S. Miller struct list_head phys; 316539c44dSDavid S. Miller }; 326539c44dSDavid S. Miller 336539c44dSDavid S. Miller struct fixed_phy { 346539c44dSDavid S. Miller int addr; 356539c44dSDavid S. Miller struct phy_device *phydev; 36bf7afb29SRussell King seqcount_t seqcount; 376539c44dSDavid S. Miller struct fixed_phy_status status; 38b3e5464eSJoakim Tjernlund bool no_carrier; 396539c44dSDavid S. Miller int (*link_update)(struct net_device *, struct fixed_phy_status *); 406539c44dSDavid S. Miller struct list_head node; 415468e82fSLinus Walleij struct gpio_desc *link_gpiod; 426539c44dSDavid S. Miller }; 436539c44dSDavid S. Miller 446539c44dSDavid S. Miller static struct platform_device *pdev; 456539c44dSDavid S. Miller static struct fixed_mdio_bus platform_fmb = { 466539c44dSDavid S. Miller .phys = LIST_HEAD_INIT(platform_fmb.phys), 476539c44dSDavid S. Miller }; 486539c44dSDavid S. Miller 49b3e5464eSJoakim Tjernlund int fixed_phy_change_carrier(struct net_device *dev, bool new_carrier) 50b3e5464eSJoakim Tjernlund { 51b3e5464eSJoakim Tjernlund struct fixed_mdio_bus *fmb = &platform_fmb; 52b3e5464eSJoakim Tjernlund struct phy_device *phydev = dev->phydev; 53b3e5464eSJoakim Tjernlund struct fixed_phy *fp; 54b3e5464eSJoakim Tjernlund 55b3e5464eSJoakim Tjernlund if (!phydev || !phydev->mdio.bus) 56b3e5464eSJoakim Tjernlund return -EINVAL; 57b3e5464eSJoakim Tjernlund 58b3e5464eSJoakim Tjernlund list_for_each_entry(fp, &fmb->phys, node) { 59b3e5464eSJoakim Tjernlund if (fp->addr == phydev->mdio.addr) { 60b3e5464eSJoakim Tjernlund fp->no_carrier = !new_carrier; 61b3e5464eSJoakim Tjernlund return 0; 62b3e5464eSJoakim Tjernlund } 63b3e5464eSJoakim Tjernlund } 64b3e5464eSJoakim Tjernlund return -EINVAL; 65b3e5464eSJoakim Tjernlund } 66b3e5464eSJoakim Tjernlund EXPORT_SYMBOL_GPL(fixed_phy_change_carrier); 67b3e5464eSJoakim Tjernlund 6837688e3fSRussell King static void fixed_phy_update(struct fixed_phy *fp) 696539c44dSDavid S. Miller { 705468e82fSLinus Walleij if (!fp->no_carrier && fp->link_gpiod) 715468e82fSLinus Walleij fp->status.link = !!gpiod_get_value_cansleep(fp->link_gpiod); 726539c44dSDavid S. Miller } 736539c44dSDavid S. Miller 746539c44dSDavid S. Miller static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) 756539c44dSDavid S. Miller { 766539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = bus->priv; 776539c44dSDavid S. Miller struct fixed_phy *fp; 786539c44dSDavid S. Miller 796539c44dSDavid S. Miller list_for_each_entry(fp, &fmb->phys, node) { 806539c44dSDavid S. Miller if (fp->addr == phy_addr) { 81bf7afb29SRussell King struct fixed_phy_status state; 82bf7afb29SRussell King int s; 83bf7afb29SRussell King 84bf7afb29SRussell King do { 85bf7afb29SRussell King s = read_seqcount_begin(&fp->seqcount); 86b3e5464eSJoakim Tjernlund fp->status.link = !fp->no_carrier; 876539c44dSDavid S. Miller /* Issue callback if user registered it. */ 888f289805SMoritz Fischer if (fp->link_update) 896539c44dSDavid S. Miller fp->link_update(fp->phydev->attached_dev, 906539c44dSDavid S. Miller &fp->status); 918f289805SMoritz Fischer /* Check the GPIO for change in status */ 9237688e3fSRussell King fixed_phy_update(fp); 93bf7afb29SRussell King state = fp->status; 94bf7afb29SRussell King } while (read_seqcount_retry(&fp->seqcount, s)); 95bf7afb29SRussell King 96bf7afb29SRussell King return swphy_read_reg(reg_num, &state); 976539c44dSDavid S. Miller } 986539c44dSDavid S. Miller } 996539c44dSDavid S. Miller 1006539c44dSDavid S. Miller return 0xFFFF; 1016539c44dSDavid S. Miller } 1026539c44dSDavid S. Miller 1036539c44dSDavid S. Miller static int fixed_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num, 1046539c44dSDavid S. Miller u16 val) 1056539c44dSDavid S. Miller { 1066539c44dSDavid S. Miller return 0; 1076539c44dSDavid S. Miller } 1086539c44dSDavid S. Miller 1096539c44dSDavid S. Miller /* 1106539c44dSDavid S. Miller * If something weird is required to be done with link/speed, 1116539c44dSDavid S. Miller * network driver is able to assign a function to implement this. 1126539c44dSDavid S. Miller * May be useful for PHY's that need to be software-driven. 1136539c44dSDavid S. Miller */ 1146539c44dSDavid S. Miller int fixed_phy_set_link_update(struct phy_device *phydev, 1156539c44dSDavid S. Miller int (*link_update)(struct net_device *, 1166539c44dSDavid S. Miller struct fixed_phy_status *)) 1176539c44dSDavid S. Miller { 1186539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 1196539c44dSDavid S. Miller struct fixed_phy *fp; 1206539c44dSDavid S. Miller 121e5a03bfdSAndrew Lunn if (!phydev || !phydev->mdio.bus) 1226539c44dSDavid S. Miller return -EINVAL; 1236539c44dSDavid S. Miller 1246539c44dSDavid S. Miller list_for_each_entry(fp, &fmb->phys, node) { 125e5a03bfdSAndrew Lunn if (fp->addr == phydev->mdio.addr) { 1266539c44dSDavid S. Miller fp->link_update = link_update; 1276539c44dSDavid S. Miller fp->phydev = phydev; 1286539c44dSDavid S. Miller return 0; 1296539c44dSDavid S. Miller } 1306539c44dSDavid S. Miller } 1316539c44dSDavid S. Miller 1326539c44dSDavid S. Miller return -ENOENT; 1336539c44dSDavid S. Miller } 1346539c44dSDavid S. Miller EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); 1356539c44dSDavid S. Miller 1365468e82fSLinus Walleij static int fixed_phy_add_gpiod(unsigned int irq, int phy_addr, 137a5597008SAndrew Lunn struct fixed_phy_status *status, 1385468e82fSLinus Walleij struct gpio_desc *gpiod) 1396539c44dSDavid S. Miller { 1406539c44dSDavid S. Miller int ret; 1416539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 1426539c44dSDavid S. Miller struct fixed_phy *fp; 1436539c44dSDavid S. Miller 14468888ce0SRussell King ret = swphy_validate_state(status); 14568888ce0SRussell King if (ret < 0) 14668888ce0SRussell King return ret; 14768888ce0SRussell King 1486539c44dSDavid S. Miller fp = kzalloc(sizeof(*fp), GFP_KERNEL); 1496539c44dSDavid S. Miller if (!fp) 1506539c44dSDavid S. Miller return -ENOMEM; 1516539c44dSDavid S. Miller 152bf7afb29SRussell King seqcount_init(&fp->seqcount); 153bf7afb29SRussell King 154185be5aeSRabin Vincent if (irq != PHY_POLL) 155e7f4dc35SAndrew Lunn fmb->mii_bus->irq[phy_addr] = irq; 1566539c44dSDavid S. Miller 1576539c44dSDavid S. Miller fp->addr = phy_addr; 1586539c44dSDavid S. Miller fp->status = *status; 1595468e82fSLinus Walleij fp->link_gpiod = gpiod; 1606539c44dSDavid S. Miller 16137688e3fSRussell King fixed_phy_update(fp); 1626539c44dSDavid S. Miller 1636539c44dSDavid S. Miller list_add_tail(&fp->node, &fmb->phys); 1646539c44dSDavid S. Miller 1656539c44dSDavid S. Miller return 0; 1665468e82fSLinus Walleij } 1676539c44dSDavid S. Miller 1685468e82fSLinus Walleij int fixed_phy_add(unsigned int irq, int phy_addr, 1695468e82fSLinus Walleij struct fixed_phy_status *status) { 1705468e82fSLinus Walleij 1715468e82fSLinus Walleij return fixed_phy_add_gpiod(irq, phy_addr, status, NULL); 1726539c44dSDavid S. Miller } 1736539c44dSDavid S. Miller EXPORT_SYMBOL_GPL(fixed_phy_add); 1746539c44dSDavid S. Miller 17569fc58a5SFlorian Fainelli static DEFINE_IDA(phy_fixed_ida); 17669fc58a5SFlorian Fainelli 1775bcbe0f3SAndrew Lunn static void fixed_phy_del(int phy_addr) 1786539c44dSDavid S. Miller { 1796539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 1806539c44dSDavid S. Miller struct fixed_phy *fp, *tmp; 1816539c44dSDavid S. Miller 1826539c44dSDavid S. Miller list_for_each_entry_safe(fp, tmp, &fmb->phys, node) { 1836539c44dSDavid S. Miller if (fp->addr == phy_addr) { 1846539c44dSDavid S. Miller list_del(&fp->node); 1855468e82fSLinus Walleij if (fp->link_gpiod) 1865468e82fSLinus Walleij gpiod_put(fp->link_gpiod); 1876539c44dSDavid S. Miller kfree(fp); 18869fc58a5SFlorian Fainelli ida_simple_remove(&phy_fixed_ida, phy_addr); 1896539c44dSDavid S. Miller return; 1906539c44dSDavid S. Miller } 1916539c44dSDavid S. Miller } 1926539c44dSDavid S. Miller } 1936539c44dSDavid S. Miller 1945468e82fSLinus Walleij #ifdef CONFIG_OF_GPIO 1955468e82fSLinus Walleij static struct gpio_desc *fixed_phy_get_gpiod(struct device_node *np) 1965468e82fSLinus Walleij { 1975468e82fSLinus Walleij struct device_node *fixed_link_node; 1985468e82fSLinus Walleij struct gpio_desc *gpiod; 1995468e82fSLinus Walleij 2005468e82fSLinus Walleij if (!np) 2015468e82fSLinus Walleij return NULL; 2025468e82fSLinus Walleij 2035468e82fSLinus Walleij fixed_link_node = of_get_child_by_name(np, "fixed-link"); 2045468e82fSLinus Walleij if (!fixed_link_node) 2055468e82fSLinus Walleij return NULL; 2065468e82fSLinus Walleij 2075468e82fSLinus Walleij /* 2085468e82fSLinus Walleij * As the fixed link is just a device tree node without any 2095468e82fSLinus Walleij * Linux device associated with it, we simply have obtain 2105468e82fSLinus Walleij * the GPIO descriptor from the device tree like this. 2115468e82fSLinus Walleij */ 2125468e82fSLinus Walleij gpiod = gpiod_get_from_of_node(fixed_link_node, "link-gpios", 0, 2135468e82fSLinus Walleij GPIOD_IN, "mdio"); 2145468e82fSLinus Walleij of_node_put(fixed_link_node); 2155468e82fSLinus Walleij if (IS_ERR(gpiod)) { 2165468e82fSLinus Walleij if (PTR_ERR(gpiod) == -EPROBE_DEFER) 2175468e82fSLinus Walleij return gpiod; 2185468e82fSLinus Walleij pr_err("error getting GPIO for fixed link %pOF, proceed without\n", 2195468e82fSLinus Walleij fixed_link_node); 2205468e82fSLinus Walleij gpiod = NULL; 2215468e82fSLinus Walleij } 2225468e82fSLinus Walleij 2235468e82fSLinus Walleij return gpiod; 2245468e82fSLinus Walleij } 2255468e82fSLinus Walleij #else 2265468e82fSLinus Walleij static struct gpio_desc *fixed_phy_get_gpiod(struct device_node *np) 2275468e82fSLinus Walleij { 2285468e82fSLinus Walleij return NULL; 2295468e82fSLinus Walleij } 2305468e82fSLinus Walleij #endif 2315468e82fSLinus Walleij 23271bd106dSMoritz Fischer static struct phy_device *__fixed_phy_register(unsigned int irq, 2336539c44dSDavid S. Miller struct fixed_phy_status *status, 23471bd106dSMoritz Fischer struct device_node *np, 23571bd106dSMoritz Fischer struct gpio_desc *gpiod) 2366539c44dSDavid S. Miller { 2376539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 2386539c44dSDavid S. Miller struct phy_device *phy; 2396539c44dSDavid S. Miller int phy_addr; 2406539c44dSDavid S. Miller int ret; 2416539c44dSDavid S. Miller 242185be5aeSRabin Vincent if (!fmb->mii_bus || fmb->mii_bus->state != MDIOBUS_REGISTERED) 243185be5aeSRabin Vincent return ERR_PTR(-EPROBE_DEFER); 244185be5aeSRabin Vincent 2455468e82fSLinus Walleij /* Check if we have a GPIO associated with this fixed phy */ 24671bd106dSMoritz Fischer if (!gpiod) { 2475468e82fSLinus Walleij gpiod = fixed_phy_get_gpiod(np); 2485468e82fSLinus Walleij if (IS_ERR(gpiod)) 2495468e82fSLinus Walleij return ERR_CAST(gpiod); 25071bd106dSMoritz Fischer } 2515468e82fSLinus Walleij 2526539c44dSDavid S. Miller /* Get the next available PHY address, up to PHY_MAX_ADDR */ 25369fc58a5SFlorian Fainelli phy_addr = ida_simple_get(&phy_fixed_ida, 0, PHY_MAX_ADDR, GFP_KERNEL); 25469fc58a5SFlorian Fainelli if (phy_addr < 0) 25569fc58a5SFlorian Fainelli return ERR_PTR(phy_addr); 2566539c44dSDavid S. Miller 2575468e82fSLinus Walleij ret = fixed_phy_add_gpiod(irq, phy_addr, status, gpiod); 25869fc58a5SFlorian Fainelli if (ret < 0) { 25969fc58a5SFlorian Fainelli ida_simple_remove(&phy_fixed_ida, phy_addr); 2606539c44dSDavid S. Miller return ERR_PTR(ret); 26169fc58a5SFlorian Fainelli } 2626539c44dSDavid S. Miller 2636539c44dSDavid S. Miller phy = get_phy_device(fmb->mii_bus, phy_addr, false); 2644914a584SSergei Shtylyov if (IS_ERR(phy)) { 2656539c44dSDavid S. Miller fixed_phy_del(phy_addr); 2666539c44dSDavid S. Miller return ERR_PTR(-EINVAL); 2676539c44dSDavid S. Miller } 2686539c44dSDavid S. Miller 2694b195360SMadalin Bucur /* propagate the fixed link values to struct phy_device */ 2704b195360SMadalin Bucur phy->link = status->link; 2714b195360SMadalin Bucur if (status->link) { 2724b195360SMadalin Bucur phy->speed = status->speed; 2734b195360SMadalin Bucur phy->duplex = status->duplex; 2744b195360SMadalin Bucur phy->pause = status->pause; 2754b195360SMadalin Bucur phy->asym_pause = status->asym_pause; 2764b195360SMadalin Bucur } 2774b195360SMadalin Bucur 2786539c44dSDavid S. Miller of_node_get(np); 279e5a03bfdSAndrew Lunn phy->mdio.dev.of_node = np; 2805a11dd7dSFlorian Fainelli phy->is_pseudo_fixed_link = true; 2816539c44dSDavid S. Miller 28234b31da4SAndrew Lunn switch (status->speed) { 28334b31da4SAndrew Lunn case SPEED_1000: 2843c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, 2853c1bcc86SAndrew Lunn phy->supported); 2863c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, 2873c1bcc86SAndrew Lunn phy->supported); 2883c1bcc86SAndrew Lunn /* fall through */ 28934b31da4SAndrew Lunn case SPEED_100: 2903c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, 2913c1bcc86SAndrew Lunn phy->supported); 2923c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, 2933c1bcc86SAndrew Lunn phy->supported); 2943c1bcc86SAndrew Lunn /* fall through */ 29534b31da4SAndrew Lunn case SPEED_10: 29634b31da4SAndrew Lunn default: 2973c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, 2983c1bcc86SAndrew Lunn phy->supported); 2993c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, 3003c1bcc86SAndrew Lunn phy->supported); 30134b31da4SAndrew Lunn } 30234b31da4SAndrew Lunn 3036539c44dSDavid S. Miller ret = phy_device_register(phy); 3046539c44dSDavid S. Miller if (ret) { 3056539c44dSDavid S. Miller phy_device_free(phy); 3066539c44dSDavid S. Miller of_node_put(np); 3076539c44dSDavid S. Miller fixed_phy_del(phy_addr); 3086539c44dSDavid S. Miller return ERR_PTR(ret); 3096539c44dSDavid S. Miller } 3106539c44dSDavid S. Miller 3116539c44dSDavid S. Miller return phy; 3126539c44dSDavid S. Miller } 31371bd106dSMoritz Fischer 31471bd106dSMoritz Fischer struct phy_device *fixed_phy_register(unsigned int irq, 31571bd106dSMoritz Fischer struct fixed_phy_status *status, 31671bd106dSMoritz Fischer struct device_node *np) 31771bd106dSMoritz Fischer { 31871bd106dSMoritz Fischer return __fixed_phy_register(irq, status, np, NULL); 31971bd106dSMoritz Fischer } 3206539c44dSDavid S. Miller EXPORT_SYMBOL_GPL(fixed_phy_register); 3216539c44dSDavid S. Miller 32271bd106dSMoritz Fischer struct phy_device * 32371bd106dSMoritz Fischer fixed_phy_register_with_gpiod(unsigned int irq, 32471bd106dSMoritz Fischer struct fixed_phy_status *status, 32571bd106dSMoritz Fischer struct gpio_desc *gpiod) 32671bd106dSMoritz Fischer { 32771bd106dSMoritz Fischer return __fixed_phy_register(irq, status, NULL, gpiod); 32871bd106dSMoritz Fischer } 32971bd106dSMoritz Fischer EXPORT_SYMBOL_GPL(fixed_phy_register_with_gpiod); 33071bd106dSMoritz Fischer 3315bcbe0f3SAndrew Lunn void fixed_phy_unregister(struct phy_device *phy) 3325bcbe0f3SAndrew Lunn { 3335bcbe0f3SAndrew Lunn phy_device_remove(phy); 33413c9d934SJohan Hovold of_node_put(phy->mdio.dev.of_node); 3355bcbe0f3SAndrew Lunn fixed_phy_del(phy->mdio.addr); 3365bcbe0f3SAndrew Lunn } 3375bcbe0f3SAndrew Lunn EXPORT_SYMBOL_GPL(fixed_phy_unregister); 3385bcbe0f3SAndrew Lunn 3396539c44dSDavid S. Miller static int __init fixed_mdio_bus_init(void) 3406539c44dSDavid S. Miller { 3416539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 3426539c44dSDavid S. Miller int ret; 3436539c44dSDavid S. Miller 3446539c44dSDavid S. Miller pdev = platform_device_register_simple("Fixed MDIO bus", 0, NULL, 0); 345b0c1638fSFabio Estevam if (IS_ERR(pdev)) 346b0c1638fSFabio Estevam return PTR_ERR(pdev); 3476539c44dSDavid S. Miller 3486539c44dSDavid S. Miller fmb->mii_bus = mdiobus_alloc(); 3496539c44dSDavid S. Miller if (fmb->mii_bus == NULL) { 3506539c44dSDavid S. Miller ret = -ENOMEM; 3516539c44dSDavid S. Miller goto err_mdiobus_reg; 3526539c44dSDavid S. Miller } 3536539c44dSDavid S. Miller 3546539c44dSDavid S. Miller snprintf(fmb->mii_bus->id, MII_BUS_ID_SIZE, "fixed-0"); 3556539c44dSDavid S. Miller fmb->mii_bus->name = "Fixed MDIO Bus"; 3566539c44dSDavid S. Miller fmb->mii_bus->priv = fmb; 3576539c44dSDavid S. Miller fmb->mii_bus->parent = &pdev->dev; 3586539c44dSDavid S. Miller fmb->mii_bus->read = &fixed_mdio_read; 3596539c44dSDavid S. Miller fmb->mii_bus->write = &fixed_mdio_write; 3606539c44dSDavid S. Miller 3616539c44dSDavid S. Miller ret = mdiobus_register(fmb->mii_bus); 3626539c44dSDavid S. Miller if (ret) 3636539c44dSDavid S. Miller goto err_mdiobus_alloc; 3646539c44dSDavid S. Miller 3656539c44dSDavid S. Miller return 0; 3666539c44dSDavid S. Miller 3676539c44dSDavid S. Miller err_mdiobus_alloc: 3686539c44dSDavid S. Miller mdiobus_free(fmb->mii_bus); 3696539c44dSDavid S. Miller err_mdiobus_reg: 3706539c44dSDavid S. Miller platform_device_unregister(pdev); 3716539c44dSDavid S. Miller return ret; 3726539c44dSDavid S. Miller } 3736539c44dSDavid S. Miller module_init(fixed_mdio_bus_init); 3746539c44dSDavid S. Miller 3756539c44dSDavid S. Miller static void __exit fixed_mdio_bus_exit(void) 3766539c44dSDavid S. Miller { 3776539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 3786539c44dSDavid S. Miller struct fixed_phy *fp, *tmp; 3796539c44dSDavid S. Miller 3806539c44dSDavid S. Miller mdiobus_unregister(fmb->mii_bus); 3816539c44dSDavid S. Miller mdiobus_free(fmb->mii_bus); 3826539c44dSDavid S. Miller platform_device_unregister(pdev); 3836539c44dSDavid S. Miller 3846539c44dSDavid S. Miller list_for_each_entry_safe(fp, tmp, &fmb->phys, node) { 3856539c44dSDavid S. Miller list_del(&fp->node); 3866539c44dSDavid S. Miller kfree(fp); 3876539c44dSDavid S. Miller } 38869fc58a5SFlorian Fainelli ida_destroy(&phy_fixed_ida); 3896539c44dSDavid S. Miller } 3906539c44dSDavid S. Miller module_exit(fixed_mdio_bus_exit); 3916539c44dSDavid S. Miller 3926539c44dSDavid S. Miller MODULE_DESCRIPTION("Fixed MDIO bus (MDIO bus emulation with fixed PHYs)"); 3936539c44dSDavid S. Miller MODULE_AUTHOR("Vitaly Bordug"); 3946539c44dSDavid S. Miller MODULE_LICENSE("GPL"); 395