12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 298cd1552SFlorian Fainelli /* 398cd1552SFlorian Fainelli * Distributed Switch Architecture loopback driver 498cd1552SFlorian Fainelli * 598cd1552SFlorian Fainelli * Copyright (C) 2016, Florian Fainelli <f.fainelli@gmail.com> 698cd1552SFlorian Fainelli */ 798cd1552SFlorian Fainelli 898cd1552SFlorian Fainelli #include <linux/platform_device.h> 998cd1552SFlorian Fainelli #include <linux/netdevice.h> 1098cd1552SFlorian Fainelli #include <linux/phy.h> 1198cd1552SFlorian Fainelli #include <linux/phy_fixed.h> 1298cd1552SFlorian Fainelli #include <linux/export.h> 13484c0172SFlorian Fainelli #include <linux/ethtool.h> 1498cd1552SFlorian Fainelli #include <linux/workqueue.h> 1598cd1552SFlorian Fainelli #include <linux/module.h> 1698cd1552SFlorian Fainelli #include <linux/if_bridge.h> 1798cd1552SFlorian Fainelli #include <net/dsa.h> 1898cd1552SFlorian Fainelli 1998cd1552SFlorian Fainelli #include "dsa_loop.h" 2098cd1552SFlorian Fainelli 2198cd1552SFlorian Fainelli struct dsa_loop_vlan { 2298cd1552SFlorian Fainelli u16 members; 2398cd1552SFlorian Fainelli u16 untagged; 2498cd1552SFlorian Fainelli }; 2598cd1552SFlorian Fainelli 26484c0172SFlorian Fainelli struct dsa_loop_mib_entry { 27484c0172SFlorian Fainelli char name[ETH_GSTRING_LEN]; 28484c0172SFlorian Fainelli unsigned long val; 29484c0172SFlorian Fainelli }; 30484c0172SFlorian Fainelli 31484c0172SFlorian Fainelli enum dsa_loop_mib_counters { 32484c0172SFlorian Fainelli DSA_LOOP_PHY_READ_OK, 33484c0172SFlorian Fainelli DSA_LOOP_PHY_READ_ERR, 34484c0172SFlorian Fainelli DSA_LOOP_PHY_WRITE_OK, 35484c0172SFlorian Fainelli DSA_LOOP_PHY_WRITE_ERR, 36484c0172SFlorian Fainelli __DSA_LOOP_CNT_MAX, 37484c0172SFlorian Fainelli }; 38484c0172SFlorian Fainelli 39484c0172SFlorian Fainelli static struct dsa_loop_mib_entry dsa_loop_mibs[] = { 40484c0172SFlorian Fainelli [DSA_LOOP_PHY_READ_OK] = { "phy_read_ok", }, 41484c0172SFlorian Fainelli [DSA_LOOP_PHY_READ_ERR] = { "phy_read_err", }, 42484c0172SFlorian Fainelli [DSA_LOOP_PHY_WRITE_OK] = { "phy_write_ok", }, 43484c0172SFlorian Fainelli [DSA_LOOP_PHY_WRITE_ERR] = { "phy_write_err", }, 44484c0172SFlorian Fainelli }; 45484c0172SFlorian Fainelli 46484c0172SFlorian Fainelli struct dsa_loop_port { 47484c0172SFlorian Fainelli struct dsa_loop_mib_entry mib[__DSA_LOOP_CNT_MAX]; 48484c0172SFlorian Fainelli }; 49484c0172SFlorian Fainelli 5098cd1552SFlorian Fainelli #define DSA_LOOP_VLANS 5 5198cd1552SFlorian Fainelli 5298cd1552SFlorian Fainelli struct dsa_loop_priv { 5398cd1552SFlorian Fainelli struct mii_bus *bus; 5498cd1552SFlorian Fainelli unsigned int port_base; 5598cd1552SFlorian Fainelli struct dsa_loop_vlan vlans[DSA_LOOP_VLANS]; 5698cd1552SFlorian Fainelli struct net_device *netdev; 57484c0172SFlorian Fainelli struct dsa_loop_port ports[DSA_MAX_PORTS]; 5898cd1552SFlorian Fainelli u16 pvid; 5998cd1552SFlorian Fainelli }; 6098cd1552SFlorian Fainelli 6198cd1552SFlorian Fainelli static struct phy_device *phydevs[PHY_MAX_ADDR]; 6298cd1552SFlorian Fainelli 635ed4e3ebSFlorian Fainelli static enum dsa_tag_protocol dsa_loop_get_protocol(struct dsa_switch *ds, 644d776482SFlorian Fainelli int port, 654d776482SFlorian Fainelli enum dsa_tag_protocol mp) 6698cd1552SFlorian Fainelli { 67e52cde71SFlorian Fainelli dev_dbg(ds->dev, "%s: port: %d\n", __func__, port); 6898cd1552SFlorian Fainelli 6998cd1552SFlorian Fainelli return DSA_TAG_PROTO_NONE; 7098cd1552SFlorian Fainelli } 7198cd1552SFlorian Fainelli 7298cd1552SFlorian Fainelli static int dsa_loop_setup(struct dsa_switch *ds) 7398cd1552SFlorian Fainelli { 74484c0172SFlorian Fainelli struct dsa_loop_priv *ps = ds->priv; 75484c0172SFlorian Fainelli unsigned int i; 76484c0172SFlorian Fainelli 77484c0172SFlorian Fainelli for (i = 0; i < ds->num_ports; i++) 78484c0172SFlorian Fainelli memcpy(ps->ports[i].mib, dsa_loop_mibs, 79484c0172SFlorian Fainelli sizeof(dsa_loop_mibs)); 80484c0172SFlorian Fainelli 8198cd1552SFlorian Fainelli dev_dbg(ds->dev, "%s\n", __func__); 8298cd1552SFlorian Fainelli 8398cd1552SFlorian Fainelli return 0; 8498cd1552SFlorian Fainelli } 8598cd1552SFlorian Fainelli 8689f09048SFlorian Fainelli static int dsa_loop_get_sset_count(struct dsa_switch *ds, int port, int sset) 87484c0172SFlorian Fainelli { 8896cbddcdSFlorian Fainelli if (sset != ETH_SS_STATS && sset != ETH_SS_PHY_STATS) 8989f09048SFlorian Fainelli return 0; 9089f09048SFlorian Fainelli 91484c0172SFlorian Fainelli return __DSA_LOOP_CNT_MAX; 92484c0172SFlorian Fainelli } 93484c0172SFlorian Fainelli 9489f09048SFlorian Fainelli static void dsa_loop_get_strings(struct dsa_switch *ds, int port, 9589f09048SFlorian Fainelli u32 stringset, uint8_t *data) 96484c0172SFlorian Fainelli { 97484c0172SFlorian Fainelli struct dsa_loop_priv *ps = ds->priv; 98484c0172SFlorian Fainelli unsigned int i; 99484c0172SFlorian Fainelli 10096cbddcdSFlorian Fainelli if (stringset != ETH_SS_STATS && stringset != ETH_SS_PHY_STATS) 10189f09048SFlorian Fainelli return; 10289f09048SFlorian Fainelli 103484c0172SFlorian Fainelli for (i = 0; i < __DSA_LOOP_CNT_MAX; i++) 104484c0172SFlorian Fainelli memcpy(data + i * ETH_GSTRING_LEN, 105484c0172SFlorian Fainelli ps->ports[port].mib[i].name, ETH_GSTRING_LEN); 106484c0172SFlorian Fainelli } 107484c0172SFlorian Fainelli 108484c0172SFlorian Fainelli static void dsa_loop_get_ethtool_stats(struct dsa_switch *ds, int port, 109484c0172SFlorian Fainelli uint64_t *data) 110484c0172SFlorian Fainelli { 111484c0172SFlorian Fainelli struct dsa_loop_priv *ps = ds->priv; 112484c0172SFlorian Fainelli unsigned int i; 113484c0172SFlorian Fainelli 114484c0172SFlorian Fainelli for (i = 0; i < __DSA_LOOP_CNT_MAX; i++) 115484c0172SFlorian Fainelli data[i] = ps->ports[port].mib[i].val; 116484c0172SFlorian Fainelli } 117484c0172SFlorian Fainelli 11898cd1552SFlorian Fainelli static int dsa_loop_phy_read(struct dsa_switch *ds, int port, int regnum) 11998cd1552SFlorian Fainelli { 12098cd1552SFlorian Fainelli struct dsa_loop_priv *ps = ds->priv; 12198cd1552SFlorian Fainelli struct mii_bus *bus = ps->bus; 122484c0172SFlorian Fainelli int ret; 12398cd1552SFlorian Fainelli 124484c0172SFlorian Fainelli ret = mdiobus_read_nested(bus, ps->port_base + port, regnum); 125484c0172SFlorian Fainelli if (ret < 0) 126484c0172SFlorian Fainelli ps->ports[port].mib[DSA_LOOP_PHY_READ_ERR].val++; 127484c0172SFlorian Fainelli else 128484c0172SFlorian Fainelli ps->ports[port].mib[DSA_LOOP_PHY_READ_OK].val++; 129484c0172SFlorian Fainelli 130484c0172SFlorian Fainelli return ret; 13198cd1552SFlorian Fainelli } 13298cd1552SFlorian Fainelli 13398cd1552SFlorian Fainelli static int dsa_loop_phy_write(struct dsa_switch *ds, int port, 13498cd1552SFlorian Fainelli int regnum, u16 value) 13598cd1552SFlorian Fainelli { 13698cd1552SFlorian Fainelli struct dsa_loop_priv *ps = ds->priv; 13798cd1552SFlorian Fainelli struct mii_bus *bus = ps->bus; 138484c0172SFlorian Fainelli int ret; 13998cd1552SFlorian Fainelli 140484c0172SFlorian Fainelli ret = mdiobus_write_nested(bus, ps->port_base + port, regnum, value); 141484c0172SFlorian Fainelli if (ret < 0) 142484c0172SFlorian Fainelli ps->ports[port].mib[DSA_LOOP_PHY_WRITE_ERR].val++; 143484c0172SFlorian Fainelli else 144484c0172SFlorian Fainelli ps->ports[port].mib[DSA_LOOP_PHY_WRITE_OK].val++; 145484c0172SFlorian Fainelli 146484c0172SFlorian Fainelli return ret; 14798cd1552SFlorian Fainelli } 14898cd1552SFlorian Fainelli 14998cd1552SFlorian Fainelli static int dsa_loop_port_bridge_join(struct dsa_switch *ds, int port, 15098cd1552SFlorian Fainelli struct net_device *bridge) 15198cd1552SFlorian Fainelli { 152e52cde71SFlorian Fainelli dev_dbg(ds->dev, "%s: port: %d, bridge: %s\n", 153e52cde71SFlorian Fainelli __func__, port, bridge->name); 15498cd1552SFlorian Fainelli 15598cd1552SFlorian Fainelli return 0; 15698cd1552SFlorian Fainelli } 15798cd1552SFlorian Fainelli 15898cd1552SFlorian Fainelli static void dsa_loop_port_bridge_leave(struct dsa_switch *ds, int port, 15998cd1552SFlorian Fainelli struct net_device *bridge) 16098cd1552SFlorian Fainelli { 161e52cde71SFlorian Fainelli dev_dbg(ds->dev, "%s: port: %d, bridge: %s\n", 162e52cde71SFlorian Fainelli __func__, port, bridge->name); 16398cd1552SFlorian Fainelli } 16498cd1552SFlorian Fainelli 16598cd1552SFlorian Fainelli static void dsa_loop_port_stp_state_set(struct dsa_switch *ds, int port, 16698cd1552SFlorian Fainelli u8 state) 16798cd1552SFlorian Fainelli { 168e52cde71SFlorian Fainelli dev_dbg(ds->dev, "%s: port: %d, state: %d\n", 169e52cde71SFlorian Fainelli __func__, port, state); 17098cd1552SFlorian Fainelli } 17198cd1552SFlorian Fainelli 17298cd1552SFlorian Fainelli static int dsa_loop_port_vlan_filtering(struct dsa_switch *ds, int port, 17398cd1552SFlorian Fainelli bool vlan_filtering) 17498cd1552SFlorian Fainelli { 175e52cde71SFlorian Fainelli dev_dbg(ds->dev, "%s: port: %d, vlan_filtering: %d\n", 176e52cde71SFlorian Fainelli __func__, port, vlan_filtering); 17798cd1552SFlorian Fainelli 17898cd1552SFlorian Fainelli return 0; 17998cd1552SFlorian Fainelli } 18098cd1552SFlorian Fainelli 18180e02360SVivien Didelot static int 18280e02360SVivien Didelot dsa_loop_port_vlan_prepare(struct dsa_switch *ds, int port, 18380e02360SVivien Didelot const struct switchdev_obj_port_vlan *vlan) 18498cd1552SFlorian Fainelli { 18598cd1552SFlorian Fainelli struct dsa_loop_priv *ps = ds->priv; 18698cd1552SFlorian Fainelli struct mii_bus *bus = ps->bus; 18798cd1552SFlorian Fainelli 188e52cde71SFlorian Fainelli dev_dbg(ds->dev, "%s: port: %d, vlan: %d-%d", 189e52cde71SFlorian Fainelli __func__, port, vlan->vid_begin, vlan->vid_end); 19098cd1552SFlorian Fainelli 19198cd1552SFlorian Fainelli /* Just do a sleeping operation to make lockdep checks effective */ 19298cd1552SFlorian Fainelli mdiobus_read(bus, ps->port_base + port, MII_BMSR); 19398cd1552SFlorian Fainelli 19498cd1552SFlorian Fainelli if (vlan->vid_end > DSA_LOOP_VLANS) 19598cd1552SFlorian Fainelli return -ERANGE; 19698cd1552SFlorian Fainelli 19798cd1552SFlorian Fainelli return 0; 19898cd1552SFlorian Fainelli } 19998cd1552SFlorian Fainelli 20098cd1552SFlorian Fainelli static void dsa_loop_port_vlan_add(struct dsa_switch *ds, int port, 20180e02360SVivien Didelot const struct switchdev_obj_port_vlan *vlan) 20298cd1552SFlorian Fainelli { 20398cd1552SFlorian Fainelli bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; 20498cd1552SFlorian Fainelli bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; 20598cd1552SFlorian Fainelli struct dsa_loop_priv *ps = ds->priv; 20698cd1552SFlorian Fainelli struct mii_bus *bus = ps->bus; 20798cd1552SFlorian Fainelli struct dsa_loop_vlan *vl; 20898cd1552SFlorian Fainelli u16 vid; 20998cd1552SFlorian Fainelli 21098cd1552SFlorian Fainelli /* Just do a sleeping operation to make lockdep checks effective */ 21198cd1552SFlorian Fainelli mdiobus_read(bus, ps->port_base + port, MII_BMSR); 21298cd1552SFlorian Fainelli 21398cd1552SFlorian Fainelli for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { 21498cd1552SFlorian Fainelli vl = &ps->vlans[vid]; 21598cd1552SFlorian Fainelli 21698cd1552SFlorian Fainelli vl->members |= BIT(port); 21798cd1552SFlorian Fainelli if (untagged) 21898cd1552SFlorian Fainelli vl->untagged |= BIT(port); 21998cd1552SFlorian Fainelli else 22098cd1552SFlorian Fainelli vl->untagged &= ~BIT(port); 221e52cde71SFlorian Fainelli 222e52cde71SFlorian Fainelli dev_dbg(ds->dev, "%s: port: %d vlan: %d, %stagged, pvid: %d\n", 223e52cde71SFlorian Fainelli __func__, port, vid, untagged ? "un" : "", pvid); 22498cd1552SFlorian Fainelli } 22598cd1552SFlorian Fainelli 22698cd1552SFlorian Fainelli if (pvid) 22798cd1552SFlorian Fainelli ps->pvid = vid; 22898cd1552SFlorian Fainelli } 22998cd1552SFlorian Fainelli 23098cd1552SFlorian Fainelli static int dsa_loop_port_vlan_del(struct dsa_switch *ds, int port, 23198cd1552SFlorian Fainelli const struct switchdev_obj_port_vlan *vlan) 23298cd1552SFlorian Fainelli { 23398cd1552SFlorian Fainelli bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED; 23498cd1552SFlorian Fainelli struct dsa_loop_priv *ps = ds->priv; 23598cd1552SFlorian Fainelli struct mii_bus *bus = ps->bus; 23698cd1552SFlorian Fainelli struct dsa_loop_vlan *vl; 2375865ccceSFlorian Fainelli u16 vid, pvid = ps->pvid; 23898cd1552SFlorian Fainelli 23998cd1552SFlorian Fainelli /* Just do a sleeping operation to make lockdep checks effective */ 24098cd1552SFlorian Fainelli mdiobus_read(bus, ps->port_base + port, MII_BMSR); 24198cd1552SFlorian Fainelli 24298cd1552SFlorian Fainelli for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { 24398cd1552SFlorian Fainelli vl = &ps->vlans[vid]; 24498cd1552SFlorian Fainelli 24598cd1552SFlorian Fainelli vl->members &= ~BIT(port); 24698cd1552SFlorian Fainelli if (untagged) 24798cd1552SFlorian Fainelli vl->untagged &= ~BIT(port); 24898cd1552SFlorian Fainelli 24998cd1552SFlorian Fainelli if (pvid == vid) 25098cd1552SFlorian Fainelli pvid = 1; 251e52cde71SFlorian Fainelli 252e52cde71SFlorian Fainelli dev_dbg(ds->dev, "%s: port: %d vlan: %d, %stagged, pvid: %d\n", 253e52cde71SFlorian Fainelli __func__, port, vid, untagged ? "un" : "", pvid); 25498cd1552SFlorian Fainelli } 25598cd1552SFlorian Fainelli ps->pvid = pvid; 25698cd1552SFlorian Fainelli 25798cd1552SFlorian Fainelli return 0; 25898cd1552SFlorian Fainelli } 25998cd1552SFlorian Fainelli 260d78d6776SBhumika Goyal static const struct dsa_switch_ops dsa_loop_driver = { 26198cd1552SFlorian Fainelli .get_tag_protocol = dsa_loop_get_protocol, 26298cd1552SFlorian Fainelli .setup = dsa_loop_setup, 263484c0172SFlorian Fainelli .get_strings = dsa_loop_get_strings, 264484c0172SFlorian Fainelli .get_ethtool_stats = dsa_loop_get_ethtool_stats, 265484c0172SFlorian Fainelli .get_sset_count = dsa_loop_get_sset_count, 26696cbddcdSFlorian Fainelli .get_ethtool_phy_stats = dsa_loop_get_ethtool_stats, 26798cd1552SFlorian Fainelli .phy_read = dsa_loop_phy_read, 26898cd1552SFlorian Fainelli .phy_write = dsa_loop_phy_write, 26998cd1552SFlorian Fainelli .port_bridge_join = dsa_loop_port_bridge_join, 27098cd1552SFlorian Fainelli .port_bridge_leave = dsa_loop_port_bridge_leave, 27198cd1552SFlorian Fainelli .port_stp_state_set = dsa_loop_port_stp_state_set, 27298cd1552SFlorian Fainelli .port_vlan_filtering = dsa_loop_port_vlan_filtering, 27398cd1552SFlorian Fainelli .port_vlan_prepare = dsa_loop_port_vlan_prepare, 27498cd1552SFlorian Fainelli .port_vlan_add = dsa_loop_port_vlan_add, 27598cd1552SFlorian Fainelli .port_vlan_del = dsa_loop_port_vlan_del, 27698cd1552SFlorian Fainelli }; 27798cd1552SFlorian Fainelli 27898cd1552SFlorian Fainelli static int dsa_loop_drv_probe(struct mdio_device *mdiodev) 27998cd1552SFlorian Fainelli { 28098cd1552SFlorian Fainelli struct dsa_loop_pdata *pdata = mdiodev->dev.platform_data; 28198cd1552SFlorian Fainelli struct dsa_loop_priv *ps; 28298cd1552SFlorian Fainelli struct dsa_switch *ds; 28398cd1552SFlorian Fainelli 28498cd1552SFlorian Fainelli if (!pdata) 28598cd1552SFlorian Fainelli return -ENODEV; 28698cd1552SFlorian Fainelli 28798cd1552SFlorian Fainelli dev_info(&mdiodev->dev, "%s: 0x%0x\n", 28898cd1552SFlorian Fainelli pdata->name, pdata->enabled_ports); 28998cd1552SFlorian Fainelli 2907e99e347SVivien Didelot ds = devm_kzalloc(&mdiodev->dev, sizeof(*ds), GFP_KERNEL); 29198cd1552SFlorian Fainelli if (!ds) 29298cd1552SFlorian Fainelli return -ENOMEM; 29398cd1552SFlorian Fainelli 2947e99e347SVivien Didelot ds->dev = &mdiodev->dev; 2957e99e347SVivien Didelot ds->num_ports = DSA_MAX_PORTS; 2967e99e347SVivien Didelot 29798cd1552SFlorian Fainelli ps = devm_kzalloc(&mdiodev->dev, sizeof(*ps), GFP_KERNEL); 2988ce7aaaaSChristophe Jaillet if (!ps) 2998ce7aaaaSChristophe Jaillet return -ENOMEM; 3008ce7aaaaSChristophe Jaillet 30198cd1552SFlorian Fainelli ps->netdev = dev_get_by_name(&init_net, pdata->netdev); 30298cd1552SFlorian Fainelli if (!ps->netdev) 30398cd1552SFlorian Fainelli return -EPROBE_DEFER; 30498cd1552SFlorian Fainelli 30598cd1552SFlorian Fainelli pdata->cd.netdev[DSA_LOOP_CPU_PORT] = &ps->netdev->dev; 30698cd1552SFlorian Fainelli 30798cd1552SFlorian Fainelli ds->dev = &mdiodev->dev; 30898cd1552SFlorian Fainelli ds->ops = &dsa_loop_driver; 30998cd1552SFlorian Fainelli ds->priv = ps; 31098cd1552SFlorian Fainelli ps->bus = mdiodev->bus; 31198cd1552SFlorian Fainelli 31298cd1552SFlorian Fainelli dev_set_drvdata(&mdiodev->dev, ds); 31398cd1552SFlorian Fainelli 31423c9ee49SVivien Didelot return dsa_register_switch(ds); 31598cd1552SFlorian Fainelli } 31698cd1552SFlorian Fainelli 31798cd1552SFlorian Fainelli static void dsa_loop_drv_remove(struct mdio_device *mdiodev) 31898cd1552SFlorian Fainelli { 31998cd1552SFlorian Fainelli struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev); 32098cd1552SFlorian Fainelli struct dsa_loop_priv *ps = ds->priv; 32198cd1552SFlorian Fainelli 32298cd1552SFlorian Fainelli dsa_unregister_switch(ds); 32398cd1552SFlorian Fainelli dev_put(ps->netdev); 32498cd1552SFlorian Fainelli } 32598cd1552SFlorian Fainelli 32698cd1552SFlorian Fainelli static struct mdio_driver dsa_loop_drv = { 32798cd1552SFlorian Fainelli .mdiodrv.driver = { 32898cd1552SFlorian Fainelli .name = "dsa-loop", 32998cd1552SFlorian Fainelli }, 33098cd1552SFlorian Fainelli .probe = dsa_loop_drv_probe, 33198cd1552SFlorian Fainelli .remove = dsa_loop_drv_remove, 33298cd1552SFlorian Fainelli }; 33398cd1552SFlorian Fainelli 33498cd1552SFlorian Fainelli #define NUM_FIXED_PHYS (DSA_LOOP_NUM_PORTS - 2) 33598cd1552SFlorian Fainelli 33698cd1552SFlorian Fainelli static int __init dsa_loop_init(void) 33798cd1552SFlorian Fainelli { 33898cd1552SFlorian Fainelli struct fixed_phy_status status = { 33998cd1552SFlorian Fainelli .link = 1, 34098cd1552SFlorian Fainelli .speed = SPEED_100, 34198cd1552SFlorian Fainelli .duplex = DUPLEX_FULL, 34298cd1552SFlorian Fainelli }; 34398cd1552SFlorian Fainelli unsigned int i; 34498cd1552SFlorian Fainelli 34598cd1552SFlorian Fainelli for (i = 0; i < NUM_FIXED_PHYS; i++) 3465468e82fSLinus Walleij phydevs[i] = fixed_phy_register(PHY_POLL, &status, NULL); 34798cd1552SFlorian Fainelli 34898cd1552SFlorian Fainelli return mdio_driver_register(&dsa_loop_drv); 34998cd1552SFlorian Fainelli } 35098cd1552SFlorian Fainelli module_init(dsa_loop_init); 35198cd1552SFlorian Fainelli 35298cd1552SFlorian Fainelli static void __exit dsa_loop_exit(void) 35398cd1552SFlorian Fainelli { 3543407dc8eSFlorian Fainelli unsigned int i; 3553407dc8eSFlorian Fainelli 35698cd1552SFlorian Fainelli mdio_driver_unregister(&dsa_loop_drv); 3573407dc8eSFlorian Fainelli for (i = 0; i < NUM_FIXED_PHYS; i++) 3586d9c153aSFlorian Fainelli if (!IS_ERR(phydevs[i])) 3593407dc8eSFlorian Fainelli fixed_phy_unregister(phydevs[i]); 36098cd1552SFlorian Fainelli } 36198cd1552SFlorian Fainelli module_exit(dsa_loop_exit); 36298cd1552SFlorian Fainelli 3633047211cSFlorian Fainelli MODULE_SOFTDEP("pre: dsa_loop_bdinfo"); 36498cd1552SFlorian Fainelli MODULE_LICENSE("GPL"); 36598cd1552SFlorian Fainelli MODULE_AUTHOR("Florian Fainelli"); 36698cd1552SFlorian Fainelli MODULE_DESCRIPTION("DSA loopback driver"); 367