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> 2269fc58a5SFlorian Fainelli #include <linux/idr.h> 23b3e5464eSJoakim Tjernlund #include <linux/netdevice.h> 240f3b1cf2SHeiner Kallweit #include <linux/linkmode.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; 366539c44dSDavid S. Miller struct fixed_phy_status status; 37b3e5464eSJoakim Tjernlund bool no_carrier; 386539c44dSDavid S. Miller int (*link_update)(struct net_device *, struct fixed_phy_status *); 396539c44dSDavid S. Miller struct list_head node; 405468e82fSLinus Walleij struct gpio_desc *link_gpiod; 416539c44dSDavid S. Miller }; 426539c44dSDavid S. Miller 436539c44dSDavid S. Miller static struct platform_device *pdev; 446539c44dSDavid S. Miller static struct fixed_mdio_bus platform_fmb = { 456539c44dSDavid S. Miller .phys = LIST_HEAD_INIT(platform_fmb.phys), 466539c44dSDavid S. Miller }; 476539c44dSDavid S. Miller 48b3e5464eSJoakim Tjernlund int fixed_phy_change_carrier(struct net_device *dev, bool new_carrier) 49b3e5464eSJoakim Tjernlund { 50b3e5464eSJoakim Tjernlund struct fixed_mdio_bus *fmb = &platform_fmb; 51b3e5464eSJoakim Tjernlund struct phy_device *phydev = dev->phydev; 52b3e5464eSJoakim Tjernlund struct fixed_phy *fp; 53b3e5464eSJoakim Tjernlund 54b3e5464eSJoakim Tjernlund if (!phydev || !phydev->mdio.bus) 55b3e5464eSJoakim Tjernlund return -EINVAL; 56b3e5464eSJoakim Tjernlund 57b3e5464eSJoakim Tjernlund list_for_each_entry(fp, &fmb->phys, node) { 58b3e5464eSJoakim Tjernlund if (fp->addr == phydev->mdio.addr) { 59b3e5464eSJoakim Tjernlund fp->no_carrier = !new_carrier; 60b3e5464eSJoakim Tjernlund return 0; 61b3e5464eSJoakim Tjernlund } 62b3e5464eSJoakim Tjernlund } 63b3e5464eSJoakim Tjernlund return -EINVAL; 64b3e5464eSJoakim Tjernlund } 65b3e5464eSJoakim Tjernlund EXPORT_SYMBOL_GPL(fixed_phy_change_carrier); 66b3e5464eSJoakim Tjernlund 6737688e3fSRussell King static void fixed_phy_update(struct fixed_phy *fp) 686539c44dSDavid S. Miller { 695468e82fSLinus Walleij if (!fp->no_carrier && fp->link_gpiod) 705468e82fSLinus Walleij fp->status.link = !!gpiod_get_value_cansleep(fp->link_gpiod); 716539c44dSDavid S. Miller } 726539c44dSDavid S. Miller 736539c44dSDavid S. Miller static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) 746539c44dSDavid S. Miller { 756539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = bus->priv; 766539c44dSDavid S. Miller struct fixed_phy *fp; 776539c44dSDavid S. Miller 786539c44dSDavid S. Miller list_for_each_entry(fp, &fmb->phys, node) { 796539c44dSDavid S. Miller if (fp->addr == phy_addr) { 80bf7afb29SRussell King struct fixed_phy_status state; 81bf7afb29SRussell King 82b3e5464eSJoakim Tjernlund fp->status.link = !fp->no_carrier; 8379cbb6bcSAhmed S. Darwish 846539c44dSDavid S. Miller /* Issue callback if user registered it. */ 858f289805SMoritz Fischer if (fp->link_update) 866539c44dSDavid S. Miller fp->link_update(fp->phydev->attached_dev, 876539c44dSDavid S. Miller &fp->status); 8879cbb6bcSAhmed S. Darwish 898f289805SMoritz Fischer /* Check the GPIO for change in status */ 9037688e3fSRussell King fixed_phy_update(fp); 91bf7afb29SRussell King state = fp->status; 92bf7afb29SRussell King 93bf7afb29SRussell King return swphy_read_reg(reg_num, &state); 946539c44dSDavid S. Miller } 956539c44dSDavid S. Miller } 966539c44dSDavid S. Miller 976539c44dSDavid S. Miller return 0xFFFF; 986539c44dSDavid S. Miller } 996539c44dSDavid S. Miller 1006539c44dSDavid S. Miller static int fixed_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num, 1016539c44dSDavid S. Miller u16 val) 1026539c44dSDavid S. Miller { 1036539c44dSDavid S. Miller return 0; 1046539c44dSDavid S. Miller } 1056539c44dSDavid S. Miller 1066539c44dSDavid S. Miller /* 1076539c44dSDavid S. Miller * If something weird is required to be done with link/speed, 1086539c44dSDavid S. Miller * network driver is able to assign a function to implement this. 1096539c44dSDavid S. Miller * May be useful for PHY's that need to be software-driven. 1106539c44dSDavid S. Miller */ 1116539c44dSDavid S. Miller int fixed_phy_set_link_update(struct phy_device *phydev, 1126539c44dSDavid S. Miller int (*link_update)(struct net_device *, 1136539c44dSDavid S. Miller struct fixed_phy_status *)) 1146539c44dSDavid S. Miller { 1156539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 1166539c44dSDavid S. Miller struct fixed_phy *fp; 1176539c44dSDavid S. Miller 118e5a03bfdSAndrew Lunn if (!phydev || !phydev->mdio.bus) 1196539c44dSDavid S. Miller return -EINVAL; 1206539c44dSDavid S. Miller 1216539c44dSDavid S. Miller list_for_each_entry(fp, &fmb->phys, node) { 122e5a03bfdSAndrew Lunn if (fp->addr == phydev->mdio.addr) { 1236539c44dSDavid S. Miller fp->link_update = link_update; 1246539c44dSDavid S. Miller fp->phydev = phydev; 1256539c44dSDavid S. Miller return 0; 1266539c44dSDavid S. Miller } 1276539c44dSDavid S. Miller } 1286539c44dSDavid S. Miller 1296539c44dSDavid S. Miller return -ENOENT; 1306539c44dSDavid S. Miller } 1316539c44dSDavid S. Miller EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); 1326539c44dSDavid S. Miller 1335468e82fSLinus Walleij static int fixed_phy_add_gpiod(unsigned int irq, int phy_addr, 134a5597008SAndrew Lunn struct fixed_phy_status *status, 1355468e82fSLinus Walleij struct gpio_desc *gpiod) 1366539c44dSDavid S. Miller { 1376539c44dSDavid S. Miller int ret; 1386539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 1396539c44dSDavid S. Miller struct fixed_phy *fp; 1406539c44dSDavid S. Miller 14168888ce0SRussell King ret = swphy_validate_state(status); 14268888ce0SRussell King if (ret < 0) 14368888ce0SRussell King return ret; 14468888ce0SRussell King 1456539c44dSDavid S. Miller fp = kzalloc(sizeof(*fp), GFP_KERNEL); 1466539c44dSDavid S. Miller if (!fp) 1476539c44dSDavid S. Miller return -ENOMEM; 1486539c44dSDavid S. Miller 149185be5aeSRabin Vincent if (irq != PHY_POLL) 150e7f4dc35SAndrew Lunn fmb->mii_bus->irq[phy_addr] = irq; 1516539c44dSDavid S. Miller 1526539c44dSDavid S. Miller fp->addr = phy_addr; 1536539c44dSDavid S. Miller fp->status = *status; 1545468e82fSLinus Walleij fp->link_gpiod = gpiod; 1556539c44dSDavid S. Miller 15637688e3fSRussell King fixed_phy_update(fp); 1576539c44dSDavid S. Miller 1586539c44dSDavid S. Miller list_add_tail(&fp->node, &fmb->phys); 1596539c44dSDavid S. Miller 1606539c44dSDavid S. Miller return 0; 1615468e82fSLinus Walleij } 1626539c44dSDavid S. Miller 1635468e82fSLinus Walleij int fixed_phy_add(unsigned int irq, int phy_addr, 1645468e82fSLinus Walleij struct fixed_phy_status *status) { 1655468e82fSLinus Walleij 1665468e82fSLinus Walleij return fixed_phy_add_gpiod(irq, phy_addr, status, NULL); 1676539c44dSDavid S. Miller } 1686539c44dSDavid S. Miller EXPORT_SYMBOL_GPL(fixed_phy_add); 1696539c44dSDavid S. Miller 17069fc58a5SFlorian Fainelli static DEFINE_IDA(phy_fixed_ida); 17169fc58a5SFlorian Fainelli 1725bcbe0f3SAndrew Lunn static void fixed_phy_del(int phy_addr) 1736539c44dSDavid S. Miller { 1746539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 1756539c44dSDavid S. Miller struct fixed_phy *fp, *tmp; 1766539c44dSDavid S. Miller 1776539c44dSDavid S. Miller list_for_each_entry_safe(fp, tmp, &fmb->phys, node) { 1786539c44dSDavid S. Miller if (fp->addr == phy_addr) { 1796539c44dSDavid S. Miller list_del(&fp->node); 1805468e82fSLinus Walleij if (fp->link_gpiod) 1815468e82fSLinus Walleij gpiod_put(fp->link_gpiod); 1826539c44dSDavid S. Miller kfree(fp); 18369fc58a5SFlorian Fainelli ida_simple_remove(&phy_fixed_ida, phy_addr); 1846539c44dSDavid S. Miller return; 1856539c44dSDavid S. Miller } 1866539c44dSDavid S. Miller } 1876539c44dSDavid S. Miller } 1886539c44dSDavid S. Miller 1895468e82fSLinus Walleij #ifdef CONFIG_OF_GPIO 1905468e82fSLinus Walleij static struct gpio_desc *fixed_phy_get_gpiod(struct device_node *np) 1915468e82fSLinus Walleij { 1925468e82fSLinus Walleij struct device_node *fixed_link_node; 1935468e82fSLinus Walleij struct gpio_desc *gpiod; 1945468e82fSLinus Walleij 1955468e82fSLinus Walleij if (!np) 1965468e82fSLinus Walleij return NULL; 1975468e82fSLinus Walleij 1985468e82fSLinus Walleij fixed_link_node = of_get_child_by_name(np, "fixed-link"); 1995468e82fSLinus Walleij if (!fixed_link_node) 2005468e82fSLinus Walleij return NULL; 2015468e82fSLinus Walleij 2025468e82fSLinus Walleij /* 2035468e82fSLinus Walleij * As the fixed link is just a device tree node without any 2045468e82fSLinus Walleij * Linux device associated with it, we simply have obtain 2055468e82fSLinus Walleij * the GPIO descriptor from the device tree like this. 2065468e82fSLinus Walleij */ 2075ffcc858SDmitry Torokhov gpiod = fwnode_gpiod_get_index(of_fwnode_handle(fixed_link_node), 2085ffcc858SDmitry Torokhov "link", 0, GPIOD_IN, "mdio"); 209d266f19fSDmitry Torokhov if (IS_ERR(gpiod) && PTR_ERR(gpiod) != -EPROBE_DEFER) { 210ab98c008SHubert Feurstein if (PTR_ERR(gpiod) != -ENOENT) 2115468e82fSLinus Walleij pr_err("error getting GPIO for fixed link %pOF, proceed without\n", 2125468e82fSLinus Walleij fixed_link_node); 2135468e82fSLinus Walleij gpiod = NULL; 2145468e82fSLinus Walleij } 215d266f19fSDmitry Torokhov of_node_put(fixed_link_node); 2165468e82fSLinus Walleij 2175468e82fSLinus Walleij return gpiod; 2185468e82fSLinus Walleij } 2195468e82fSLinus Walleij #else 2205468e82fSLinus Walleij static struct gpio_desc *fixed_phy_get_gpiod(struct device_node *np) 2215468e82fSLinus Walleij { 2225468e82fSLinus Walleij return NULL; 2235468e82fSLinus Walleij } 2245468e82fSLinus Walleij #endif 2255468e82fSLinus Walleij 22671bd106dSMoritz Fischer static struct phy_device *__fixed_phy_register(unsigned int irq, 2276539c44dSDavid S. Miller struct fixed_phy_status *status, 22871bd106dSMoritz Fischer struct device_node *np, 22971bd106dSMoritz Fischer struct gpio_desc *gpiod) 2306539c44dSDavid S. Miller { 2316539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 2326539c44dSDavid S. Miller struct phy_device *phy; 2336539c44dSDavid S. Miller int phy_addr; 2346539c44dSDavid S. Miller int ret; 2356539c44dSDavid S. Miller 236185be5aeSRabin Vincent if (!fmb->mii_bus || fmb->mii_bus->state != MDIOBUS_REGISTERED) 237185be5aeSRabin Vincent return ERR_PTR(-EPROBE_DEFER); 238185be5aeSRabin Vincent 2395468e82fSLinus Walleij /* Check if we have a GPIO associated with this fixed phy */ 24071bd106dSMoritz Fischer if (!gpiod) { 2415468e82fSLinus Walleij gpiod = fixed_phy_get_gpiod(np); 2425468e82fSLinus Walleij if (IS_ERR(gpiod)) 2435468e82fSLinus Walleij return ERR_CAST(gpiod); 24471bd106dSMoritz Fischer } 2455468e82fSLinus Walleij 2466539c44dSDavid S. Miller /* Get the next available PHY address, up to PHY_MAX_ADDR */ 24769fc58a5SFlorian Fainelli phy_addr = ida_simple_get(&phy_fixed_ida, 0, PHY_MAX_ADDR, GFP_KERNEL); 24869fc58a5SFlorian Fainelli if (phy_addr < 0) 24969fc58a5SFlorian Fainelli return ERR_PTR(phy_addr); 2506539c44dSDavid S. Miller 2515468e82fSLinus Walleij ret = fixed_phy_add_gpiod(irq, phy_addr, status, gpiod); 25269fc58a5SFlorian Fainelli if (ret < 0) { 25369fc58a5SFlorian Fainelli ida_simple_remove(&phy_fixed_ida, phy_addr); 2546539c44dSDavid S. Miller return ERR_PTR(ret); 25569fc58a5SFlorian Fainelli } 2566539c44dSDavid S. Miller 2576539c44dSDavid S. Miller phy = get_phy_device(fmb->mii_bus, phy_addr, false); 2584914a584SSergei Shtylyov if (IS_ERR(phy)) { 2596539c44dSDavid S. Miller fixed_phy_del(phy_addr); 2606539c44dSDavid S. Miller return ERR_PTR(-EINVAL); 2616539c44dSDavid S. Miller } 2626539c44dSDavid S. Miller 2634b195360SMadalin Bucur /* propagate the fixed link values to struct phy_device */ 2644b195360SMadalin Bucur phy->link = status->link; 2654b195360SMadalin Bucur if (status->link) { 2664b195360SMadalin Bucur phy->speed = status->speed; 2674b195360SMadalin Bucur phy->duplex = status->duplex; 2684b195360SMadalin Bucur phy->pause = status->pause; 2694b195360SMadalin Bucur phy->asym_pause = status->asym_pause; 2704b195360SMadalin Bucur } 2714b195360SMadalin Bucur 2726539c44dSDavid S. Miller of_node_get(np); 273e5a03bfdSAndrew Lunn phy->mdio.dev.of_node = np; 2745a11dd7dSFlorian Fainelli phy->is_pseudo_fixed_link = true; 2756539c44dSDavid S. Miller 27634b31da4SAndrew Lunn switch (status->speed) { 27734b31da4SAndrew Lunn case SPEED_1000: 2783c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, 2793c1bcc86SAndrew Lunn phy->supported); 2803c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, 2813c1bcc86SAndrew Lunn phy->supported); 2823c1bcc86SAndrew Lunn /* fall through */ 28334b31da4SAndrew Lunn case SPEED_100: 2843c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, 2853c1bcc86SAndrew Lunn phy->supported); 2863c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, 2873c1bcc86SAndrew Lunn phy->supported); 2883c1bcc86SAndrew Lunn /* fall through */ 28934b31da4SAndrew Lunn case SPEED_10: 29034b31da4SAndrew Lunn default: 2913c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, 2923c1bcc86SAndrew Lunn phy->supported); 2933c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, 2943c1bcc86SAndrew Lunn phy->supported); 29534b31da4SAndrew Lunn } 29634b31da4SAndrew Lunn 29722c0ef6bSHeiner Kallweit phy_advertise_supported(phy); 2980f3b1cf2SHeiner Kallweit 2996539c44dSDavid S. Miller ret = phy_device_register(phy); 3006539c44dSDavid S. Miller if (ret) { 3016539c44dSDavid S. Miller phy_device_free(phy); 3026539c44dSDavid S. Miller of_node_put(np); 3036539c44dSDavid S. Miller fixed_phy_del(phy_addr); 3046539c44dSDavid S. Miller return ERR_PTR(ret); 3056539c44dSDavid S. Miller } 3066539c44dSDavid S. Miller 3076539c44dSDavid S. Miller return phy; 3086539c44dSDavid S. Miller } 30971bd106dSMoritz Fischer 31071bd106dSMoritz Fischer struct phy_device *fixed_phy_register(unsigned int irq, 31171bd106dSMoritz Fischer struct fixed_phy_status *status, 31271bd106dSMoritz Fischer struct device_node *np) 31371bd106dSMoritz Fischer { 31471bd106dSMoritz Fischer return __fixed_phy_register(irq, status, np, NULL); 31571bd106dSMoritz Fischer } 3166539c44dSDavid S. Miller EXPORT_SYMBOL_GPL(fixed_phy_register); 3176539c44dSDavid S. Miller 31871bd106dSMoritz Fischer struct phy_device * 31971bd106dSMoritz Fischer fixed_phy_register_with_gpiod(unsigned int irq, 32071bd106dSMoritz Fischer struct fixed_phy_status *status, 32171bd106dSMoritz Fischer struct gpio_desc *gpiod) 32271bd106dSMoritz Fischer { 32371bd106dSMoritz Fischer return __fixed_phy_register(irq, status, NULL, gpiod); 32471bd106dSMoritz Fischer } 32571bd106dSMoritz Fischer EXPORT_SYMBOL_GPL(fixed_phy_register_with_gpiod); 32671bd106dSMoritz Fischer 3275bcbe0f3SAndrew Lunn void fixed_phy_unregister(struct phy_device *phy) 3285bcbe0f3SAndrew Lunn { 3295bcbe0f3SAndrew Lunn phy_device_remove(phy); 33013c9d934SJohan Hovold of_node_put(phy->mdio.dev.of_node); 3315bcbe0f3SAndrew Lunn fixed_phy_del(phy->mdio.addr); 3325bcbe0f3SAndrew Lunn } 3335bcbe0f3SAndrew Lunn EXPORT_SYMBOL_GPL(fixed_phy_unregister); 3345bcbe0f3SAndrew Lunn 3356539c44dSDavid S. Miller static int __init fixed_mdio_bus_init(void) 3366539c44dSDavid S. Miller { 3376539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 3386539c44dSDavid S. Miller int ret; 3396539c44dSDavid S. Miller 3406539c44dSDavid S. Miller pdev = platform_device_register_simple("Fixed MDIO bus", 0, NULL, 0); 341b0c1638fSFabio Estevam if (IS_ERR(pdev)) 342b0c1638fSFabio Estevam return PTR_ERR(pdev); 3436539c44dSDavid S. Miller 3446539c44dSDavid S. Miller fmb->mii_bus = mdiobus_alloc(); 3456539c44dSDavid S. Miller if (fmb->mii_bus == NULL) { 3466539c44dSDavid S. Miller ret = -ENOMEM; 3476539c44dSDavid S. Miller goto err_mdiobus_reg; 3486539c44dSDavid S. Miller } 3496539c44dSDavid S. Miller 3506539c44dSDavid S. Miller snprintf(fmb->mii_bus->id, MII_BUS_ID_SIZE, "fixed-0"); 3516539c44dSDavid S. Miller fmb->mii_bus->name = "Fixed MDIO Bus"; 3526539c44dSDavid S. Miller fmb->mii_bus->priv = fmb; 3536539c44dSDavid S. Miller fmb->mii_bus->parent = &pdev->dev; 3546539c44dSDavid S. Miller fmb->mii_bus->read = &fixed_mdio_read; 3556539c44dSDavid S. Miller fmb->mii_bus->write = &fixed_mdio_write; 3566539c44dSDavid S. Miller 3576539c44dSDavid S. Miller ret = mdiobus_register(fmb->mii_bus); 3586539c44dSDavid S. Miller if (ret) 3596539c44dSDavid S. Miller goto err_mdiobus_alloc; 3606539c44dSDavid S. Miller 3616539c44dSDavid S. Miller return 0; 3626539c44dSDavid S. Miller 3636539c44dSDavid S. Miller err_mdiobus_alloc: 3646539c44dSDavid S. Miller mdiobus_free(fmb->mii_bus); 3656539c44dSDavid S. Miller err_mdiobus_reg: 3666539c44dSDavid S. Miller platform_device_unregister(pdev); 3676539c44dSDavid S. Miller return ret; 3686539c44dSDavid S. Miller } 3696539c44dSDavid S. Miller module_init(fixed_mdio_bus_init); 3706539c44dSDavid S. Miller 3716539c44dSDavid S. Miller static void __exit fixed_mdio_bus_exit(void) 3726539c44dSDavid S. Miller { 3736539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 3746539c44dSDavid S. Miller struct fixed_phy *fp, *tmp; 3756539c44dSDavid S. Miller 3766539c44dSDavid S. Miller mdiobus_unregister(fmb->mii_bus); 3776539c44dSDavid S. Miller mdiobus_free(fmb->mii_bus); 3786539c44dSDavid S. Miller platform_device_unregister(pdev); 3796539c44dSDavid S. Miller 3806539c44dSDavid S. Miller list_for_each_entry_safe(fp, tmp, &fmb->phys, node) { 3816539c44dSDavid S. Miller list_del(&fp->node); 3826539c44dSDavid S. Miller kfree(fp); 3836539c44dSDavid S. Miller } 38469fc58a5SFlorian Fainelli ida_destroy(&phy_fixed_ida); 3856539c44dSDavid S. Miller } 3866539c44dSDavid S. Miller module_exit(fixed_mdio_bus_exit); 3876539c44dSDavid S. Miller 3886539c44dSDavid S. Miller MODULE_DESCRIPTION("Fixed MDIO bus (MDIO bus emulation with fixed PHYs)"); 3896539c44dSDavid S. Miller MODULE_AUTHOR("Vitaly Bordug"); 3906539c44dSDavid S. Miller MODULE_LICENSE("GPL"); 391