1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Fixed MDIO bus (MDIO bus emulation with fixed PHYs) 4 * 5 * Author: Vitaly Bordug <vbordug@ru.mvista.com> 6 * Anton Vorontsov <avorontsov@ru.mvista.com> 7 * 8 * Copyright (c) 2006-2007 MontaVista Software, Inc. 9 */ 10 11 #include <linux/kernel.h> 12 #include <linux/module.h> 13 #include <linux/list.h> 14 #include <linux/mii.h> 15 #include <linux/phy.h> 16 #include <linux/phy_fixed.h> 17 #include <linux/err.h> 18 #include <linux/slab.h> 19 #include <linux/of.h> 20 #include <linux/idr.h> 21 #include <linux/netdevice.h> 22 23 #include "swphy.h" 24 25 struct fixed_phy { 26 int addr; 27 struct phy_device *phydev; 28 struct fixed_phy_status status; 29 int (*link_update)(struct net_device *, struct fixed_phy_status *); 30 struct list_head node; 31 }; 32 33 static struct mii_bus *fmb_mii_bus; 34 static LIST_HEAD(fmb_phys); 35 36 static struct fixed_phy *fixed_phy_find(int addr) 37 { 38 struct fixed_phy *fp; 39 40 list_for_each_entry(fp, &fmb_phys, node) { 41 if (fp->addr == addr) 42 return fp; 43 } 44 45 return NULL; 46 } 47 48 int fixed_phy_change_carrier(struct net_device *dev, bool new_carrier) 49 { 50 struct phy_device *phydev = dev->phydev; 51 struct fixed_phy *fp; 52 53 if (!phydev || !phydev->mdio.bus) 54 return -EINVAL; 55 56 fp = fixed_phy_find(phydev->mdio.addr); 57 if (!fp) 58 return -EINVAL; 59 60 fp->status.link = new_carrier; 61 62 return 0; 63 } 64 EXPORT_SYMBOL_GPL(fixed_phy_change_carrier); 65 66 static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) 67 { 68 struct fixed_phy *fp; 69 70 fp = fixed_phy_find(phy_addr); 71 if (!fp) 72 return 0xffff; 73 74 if (fp->link_update) 75 fp->link_update(fp->phydev->attached_dev, &fp->status); 76 77 return swphy_read_reg(reg_num, &fp->status); 78 } 79 80 static int fixed_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num, 81 u16 val) 82 { 83 return 0; 84 } 85 86 /* 87 * If something weird is required to be done with link/speed, 88 * network driver is able to assign a function to implement this. 89 * May be useful for PHY's that need to be software-driven. 90 */ 91 int fixed_phy_set_link_update(struct phy_device *phydev, 92 int (*link_update)(struct net_device *, 93 struct fixed_phy_status *)) 94 { 95 struct fixed_phy *fp; 96 97 if (!phydev || !phydev->mdio.bus) 98 return -EINVAL; 99 100 fp = fixed_phy_find(phydev->mdio.addr); 101 if (!fp) 102 return -ENOENT; 103 104 fp->link_update = link_update; 105 fp->phydev = phydev; 106 107 return 0; 108 } 109 EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); 110 111 static int __fixed_phy_add(int phy_addr, 112 const struct fixed_phy_status *status) 113 { 114 struct fixed_phy *fp; 115 int ret; 116 117 ret = swphy_validate_state(status); 118 if (ret < 0) 119 return ret; 120 121 fp = kzalloc(sizeof(*fp), GFP_KERNEL); 122 if (!fp) 123 return -ENOMEM; 124 125 fp->addr = phy_addr; 126 fp->status = *status; 127 128 list_add_tail(&fp->node, &fmb_phys); 129 130 return 0; 131 } 132 133 static DEFINE_IDA(phy_fixed_ida); 134 135 static void fixed_phy_del(int phy_addr) 136 { 137 struct fixed_phy *fp; 138 139 fp = fixed_phy_find(phy_addr); 140 if (!fp) 141 return; 142 143 list_del(&fp->node); 144 kfree(fp); 145 ida_free(&phy_fixed_ida, phy_addr); 146 } 147 148 struct phy_device *fixed_phy_register(const struct fixed_phy_status *status, 149 struct device_node *np) 150 { 151 struct phy_device *phy; 152 int phy_addr; 153 int ret; 154 155 if (!fmb_mii_bus || fmb_mii_bus->state != MDIOBUS_REGISTERED) 156 return ERR_PTR(-EPROBE_DEFER); 157 158 /* Get the next available PHY address, up to PHY_MAX_ADDR */ 159 phy_addr = ida_alloc_max(&phy_fixed_ida, PHY_MAX_ADDR - 1, GFP_KERNEL); 160 if (phy_addr < 0) 161 return ERR_PTR(phy_addr); 162 163 ret = __fixed_phy_add(phy_addr, status); 164 if (ret < 0) { 165 ida_free(&phy_fixed_ida, phy_addr); 166 return ERR_PTR(ret); 167 } 168 169 phy = get_phy_device(fmb_mii_bus, phy_addr, false); 170 if (IS_ERR(phy)) { 171 fixed_phy_del(phy_addr); 172 return ERR_PTR(-EINVAL); 173 } 174 175 /* propagate the fixed link values to struct phy_device */ 176 phy->link = 1; 177 phy->speed = status->speed; 178 phy->duplex = status->duplex; 179 phy->pause = status->pause; 180 phy->asym_pause = status->asym_pause; 181 182 of_node_get(np); 183 phy->mdio.dev.of_node = np; 184 phy->is_pseudo_fixed_link = true; 185 186 ret = phy_device_register(phy); 187 if (ret) { 188 phy_device_free(phy); 189 of_node_put(np); 190 fixed_phy_del(phy_addr); 191 return ERR_PTR(ret); 192 } 193 194 return phy; 195 } 196 EXPORT_SYMBOL_GPL(fixed_phy_register); 197 198 struct phy_device *fixed_phy_register_100fd(void) 199 { 200 static const struct fixed_phy_status status = { 201 .speed = SPEED_100, 202 .duplex = DUPLEX_FULL, 203 }; 204 205 return fixed_phy_register(&status, NULL); 206 } 207 EXPORT_SYMBOL_GPL(fixed_phy_register_100fd); 208 209 void fixed_phy_unregister(struct phy_device *phy) 210 { 211 phy_device_remove(phy); 212 of_node_put(phy->mdio.dev.of_node); 213 fixed_phy_del(phy->mdio.addr); 214 phy_device_free(phy); 215 } 216 EXPORT_SYMBOL_GPL(fixed_phy_unregister); 217 218 static int __init fixed_mdio_bus_init(void) 219 { 220 int ret; 221 222 fmb_mii_bus = mdiobus_alloc(); 223 if (!fmb_mii_bus) 224 return -ENOMEM; 225 226 snprintf(fmb_mii_bus->id, MII_BUS_ID_SIZE, "fixed-0"); 227 fmb_mii_bus->name = "Fixed MDIO Bus"; 228 fmb_mii_bus->read = &fixed_mdio_read; 229 fmb_mii_bus->write = &fixed_mdio_write; 230 fmb_mii_bus->phy_mask = ~0; 231 232 ret = mdiobus_register(fmb_mii_bus); 233 if (ret) 234 goto err_mdiobus_alloc; 235 236 return 0; 237 238 err_mdiobus_alloc: 239 mdiobus_free(fmb_mii_bus); 240 return ret; 241 } 242 module_init(fixed_mdio_bus_init); 243 244 static void __exit fixed_mdio_bus_exit(void) 245 { 246 struct fixed_phy *fp, *tmp; 247 248 mdiobus_unregister(fmb_mii_bus); 249 mdiobus_free(fmb_mii_bus); 250 251 list_for_each_entry_safe(fp, tmp, &fmb_phys, node) { 252 list_del(&fp->node); 253 kfree(fp); 254 } 255 ida_destroy(&phy_fixed_ida); 256 } 257 module_exit(fixed_mdio_bus_exit); 258 259 MODULE_DESCRIPTION("Fixed MDIO bus (MDIO bus emulation with fixed PHYs)"); 260 MODULE_AUTHOR("Vitaly Bordug"); 261 MODULE_LICENSE("GPL"); 262