16539c44dSDavid S. Miller /* 26539c44dSDavid S. Miller * Fixed MDIO bus (MDIO bus emulation with fixed PHYs) 36539c44dSDavid S. Miller * 46539c44dSDavid S. Miller * Author: Vitaly Bordug <vbordug@ru.mvista.com> 56539c44dSDavid S. Miller * Anton Vorontsov <avorontsov@ru.mvista.com> 66539c44dSDavid S. Miller * 76539c44dSDavid S. Miller * Copyright (c) 2006-2007 MontaVista Software, Inc. 86539c44dSDavid S. Miller * 96539c44dSDavid S. Miller * This program is free software; you can redistribute it and/or modify it 106539c44dSDavid S. Miller * under the terms of the GNU General Public License as published by the 116539c44dSDavid S. Miller * Free Software Foundation; either version 2 of the License, or (at your 126539c44dSDavid S. Miller * option) any later version. 136539c44dSDavid S. Miller */ 146539c44dSDavid S. Miller 156539c44dSDavid S. Miller #include <linux/kernel.h> 166539c44dSDavid S. Miller #include <linux/module.h> 176539c44dSDavid S. Miller #include <linux/platform_device.h> 186539c44dSDavid S. Miller #include <linux/list.h> 196539c44dSDavid S. Miller #include <linux/mii.h> 206539c44dSDavid S. Miller #include <linux/phy.h> 216539c44dSDavid S. Miller #include <linux/phy_fixed.h> 226539c44dSDavid S. Miller #include <linux/err.h> 236539c44dSDavid S. Miller #include <linux/slab.h> 246539c44dSDavid S. Miller #include <linux/of.h> 25a5597008SAndrew Lunn #include <linux/gpio.h> 26bf7afb29SRussell King #include <linux/seqlock.h> 2769fc58a5SFlorian Fainelli #include <linux/idr.h> 286539c44dSDavid S. Miller 295ae68b0cSRussell King #include "swphy.h" 305ae68b0cSRussell King 316539c44dSDavid S. Miller struct fixed_mdio_bus { 326539c44dSDavid S. Miller struct mii_bus *mii_bus; 336539c44dSDavid S. Miller struct list_head phys; 346539c44dSDavid S. Miller }; 356539c44dSDavid S. Miller 366539c44dSDavid S. Miller struct fixed_phy { 376539c44dSDavid S. Miller int addr; 386539c44dSDavid S. Miller struct phy_device *phydev; 39bf7afb29SRussell King seqcount_t seqcount; 406539c44dSDavid S. Miller struct fixed_phy_status status; 416539c44dSDavid S. Miller int (*link_update)(struct net_device *, struct fixed_phy_status *); 426539c44dSDavid S. Miller struct list_head node; 43a5597008SAndrew Lunn int link_gpio; 446539c44dSDavid S. Miller }; 456539c44dSDavid S. Miller 466539c44dSDavid S. Miller static struct platform_device *pdev; 476539c44dSDavid S. Miller static struct fixed_mdio_bus platform_fmb = { 486539c44dSDavid S. Miller .phys = LIST_HEAD_INIT(platform_fmb.phys), 496539c44dSDavid S. Miller }; 506539c44dSDavid S. Miller 5137688e3fSRussell King static void fixed_phy_update(struct fixed_phy *fp) 526539c44dSDavid S. Miller { 53a5597008SAndrew Lunn if (gpio_is_valid(fp->link_gpio)) 54a5597008SAndrew Lunn fp->status.link = !!gpio_get_value_cansleep(fp->link_gpio); 556539c44dSDavid S. Miller } 566539c44dSDavid S. Miller 576539c44dSDavid S. Miller static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) 586539c44dSDavid S. Miller { 596539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = bus->priv; 606539c44dSDavid S. Miller struct fixed_phy *fp; 616539c44dSDavid S. Miller 626539c44dSDavid S. Miller list_for_each_entry(fp, &fmb->phys, node) { 636539c44dSDavid S. Miller if (fp->addr == phy_addr) { 64bf7afb29SRussell King struct fixed_phy_status state; 65bf7afb29SRussell King int s; 66bf7afb29SRussell King 67bf7afb29SRussell King do { 68bf7afb29SRussell King s = read_seqcount_begin(&fp->seqcount); 696539c44dSDavid S. Miller /* Issue callback if user registered it. */ 706539c44dSDavid S. Miller if (fp->link_update) { 716539c44dSDavid S. Miller fp->link_update(fp->phydev->attached_dev, 726539c44dSDavid S. Miller &fp->status); 7337688e3fSRussell King fixed_phy_update(fp); 746539c44dSDavid S. Miller } 75bf7afb29SRussell King state = fp->status; 76bf7afb29SRussell King } while (read_seqcount_retry(&fp->seqcount, s)); 77bf7afb29SRussell King 78bf7afb29SRussell King return swphy_read_reg(reg_num, &state); 796539c44dSDavid S. Miller } 806539c44dSDavid S. Miller } 816539c44dSDavid S. Miller 826539c44dSDavid S. Miller return 0xFFFF; 836539c44dSDavid S. Miller } 846539c44dSDavid S. Miller 856539c44dSDavid S. Miller static int fixed_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num, 866539c44dSDavid S. Miller u16 val) 876539c44dSDavid S. Miller { 886539c44dSDavid S. Miller return 0; 896539c44dSDavid S. Miller } 906539c44dSDavid S. Miller 916539c44dSDavid S. Miller /* 926539c44dSDavid S. Miller * If something weird is required to be done with link/speed, 936539c44dSDavid S. Miller * network driver is able to assign a function to implement this. 946539c44dSDavid S. Miller * May be useful for PHY's that need to be software-driven. 956539c44dSDavid S. Miller */ 966539c44dSDavid S. Miller int fixed_phy_set_link_update(struct phy_device *phydev, 976539c44dSDavid S. Miller int (*link_update)(struct net_device *, 986539c44dSDavid S. Miller struct fixed_phy_status *)) 996539c44dSDavid S. Miller { 1006539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 1016539c44dSDavid S. Miller struct fixed_phy *fp; 1026539c44dSDavid S. Miller 103e5a03bfdSAndrew Lunn if (!phydev || !phydev->mdio.bus) 1046539c44dSDavid S. Miller return -EINVAL; 1056539c44dSDavid S. Miller 1066539c44dSDavid S. Miller list_for_each_entry(fp, &fmb->phys, node) { 107e5a03bfdSAndrew Lunn if (fp->addr == phydev->mdio.addr) { 1086539c44dSDavid S. Miller fp->link_update = link_update; 1096539c44dSDavid S. Miller fp->phydev = phydev; 1106539c44dSDavid S. Miller return 0; 1116539c44dSDavid S. Miller } 1126539c44dSDavid S. Miller } 1136539c44dSDavid S. Miller 1146539c44dSDavid S. Miller return -ENOENT; 1156539c44dSDavid S. Miller } 1166539c44dSDavid S. Miller EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); 1176539c44dSDavid S. Miller 1186539c44dSDavid S. Miller int fixed_phy_add(unsigned int irq, int phy_addr, 119a5597008SAndrew Lunn struct fixed_phy_status *status, 120a5597008SAndrew Lunn int link_gpio) 1216539c44dSDavid S. Miller { 1226539c44dSDavid S. Miller int ret; 1236539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 1246539c44dSDavid S. Miller struct fixed_phy *fp; 1256539c44dSDavid S. Miller 12668888ce0SRussell King ret = swphy_validate_state(status); 12768888ce0SRussell King if (ret < 0) 12868888ce0SRussell King return ret; 12968888ce0SRussell King 1306539c44dSDavid S. Miller fp = kzalloc(sizeof(*fp), GFP_KERNEL); 1316539c44dSDavid S. Miller if (!fp) 1326539c44dSDavid S. Miller return -ENOMEM; 1336539c44dSDavid S. Miller 134bf7afb29SRussell King seqcount_init(&fp->seqcount); 135bf7afb29SRussell King 136185be5aeSRabin Vincent if (irq != PHY_POLL) 137e7f4dc35SAndrew Lunn fmb->mii_bus->irq[phy_addr] = irq; 1386539c44dSDavid S. Miller 1396539c44dSDavid S. Miller fp->addr = phy_addr; 1406539c44dSDavid S. Miller fp->status = *status; 141a5597008SAndrew Lunn fp->link_gpio = link_gpio; 142a5597008SAndrew Lunn 143a5597008SAndrew Lunn if (gpio_is_valid(fp->link_gpio)) { 144a5597008SAndrew Lunn ret = gpio_request_one(fp->link_gpio, GPIOF_DIR_IN, 145a5597008SAndrew Lunn "fixed-link-gpio-link"); 146a5597008SAndrew Lunn if (ret) 147a5597008SAndrew Lunn goto err_regs; 148a5597008SAndrew Lunn } 1496539c44dSDavid S. Miller 15037688e3fSRussell King fixed_phy_update(fp); 1516539c44dSDavid S. Miller 1526539c44dSDavid S. Miller list_add_tail(&fp->node, &fmb->phys); 1536539c44dSDavid S. Miller 1546539c44dSDavid S. Miller return 0; 1556539c44dSDavid S. Miller 1566539c44dSDavid S. Miller err_regs: 1576539c44dSDavid S. Miller kfree(fp); 1586539c44dSDavid S. Miller return ret; 1596539c44dSDavid S. Miller } 1606539c44dSDavid S. Miller EXPORT_SYMBOL_GPL(fixed_phy_add); 1616539c44dSDavid S. Miller 16269fc58a5SFlorian Fainelli static DEFINE_IDA(phy_fixed_ida); 16369fc58a5SFlorian Fainelli 1645bcbe0f3SAndrew Lunn static void fixed_phy_del(int phy_addr) 1656539c44dSDavid S. Miller { 1666539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 1676539c44dSDavid S. Miller struct fixed_phy *fp, *tmp; 1686539c44dSDavid S. Miller 1696539c44dSDavid S. Miller list_for_each_entry_safe(fp, tmp, &fmb->phys, node) { 1706539c44dSDavid S. Miller if (fp->addr == phy_addr) { 1716539c44dSDavid S. Miller list_del(&fp->node); 172a5597008SAndrew Lunn if (gpio_is_valid(fp->link_gpio)) 173a5597008SAndrew Lunn gpio_free(fp->link_gpio); 1746539c44dSDavid S. Miller kfree(fp); 17569fc58a5SFlorian Fainelli ida_simple_remove(&phy_fixed_ida, phy_addr); 1766539c44dSDavid S. Miller return; 1776539c44dSDavid S. Miller } 1786539c44dSDavid S. Miller } 1796539c44dSDavid S. Miller } 1806539c44dSDavid S. Miller 1816539c44dSDavid S. Miller struct phy_device *fixed_phy_register(unsigned int irq, 1826539c44dSDavid S. Miller struct fixed_phy_status *status, 183a5597008SAndrew Lunn int link_gpio, 1846539c44dSDavid S. Miller struct device_node *np) 1856539c44dSDavid S. Miller { 1866539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 1876539c44dSDavid S. Miller struct phy_device *phy; 1886539c44dSDavid S. Miller int phy_addr; 1896539c44dSDavid S. Miller int ret; 1906539c44dSDavid S. Miller 191185be5aeSRabin Vincent if (!fmb->mii_bus || fmb->mii_bus->state != MDIOBUS_REGISTERED) 192185be5aeSRabin Vincent return ERR_PTR(-EPROBE_DEFER); 193185be5aeSRabin Vincent 1946539c44dSDavid S. Miller /* Get the next available PHY address, up to PHY_MAX_ADDR */ 19569fc58a5SFlorian Fainelli phy_addr = ida_simple_get(&phy_fixed_ida, 0, PHY_MAX_ADDR, GFP_KERNEL); 19669fc58a5SFlorian Fainelli if (phy_addr < 0) 19769fc58a5SFlorian Fainelli return ERR_PTR(phy_addr); 1986539c44dSDavid S. Miller 199bd1a05eeSSergei Shtylyov ret = fixed_phy_add(irq, phy_addr, status, link_gpio); 20069fc58a5SFlorian Fainelli if (ret < 0) { 20169fc58a5SFlorian Fainelli ida_simple_remove(&phy_fixed_ida, phy_addr); 2026539c44dSDavid S. Miller return ERR_PTR(ret); 20369fc58a5SFlorian Fainelli } 2046539c44dSDavid S. Miller 2056539c44dSDavid S. Miller phy = get_phy_device(fmb->mii_bus, phy_addr, false); 2064914a584SSergei Shtylyov if (IS_ERR(phy)) { 2076539c44dSDavid S. Miller fixed_phy_del(phy_addr); 2086539c44dSDavid S. Miller return ERR_PTR(-EINVAL); 2096539c44dSDavid S. Miller } 2106539c44dSDavid S. Miller 2114b195360SMadalin Bucur /* propagate the fixed link values to struct phy_device */ 2124b195360SMadalin Bucur phy->link = status->link; 2134b195360SMadalin Bucur if (status->link) { 2144b195360SMadalin Bucur phy->speed = status->speed; 2154b195360SMadalin Bucur phy->duplex = status->duplex; 2164b195360SMadalin Bucur phy->pause = status->pause; 2174b195360SMadalin Bucur phy->asym_pause = status->asym_pause; 2184b195360SMadalin Bucur } 2194b195360SMadalin Bucur 2206539c44dSDavid S. Miller of_node_get(np); 221e5a03bfdSAndrew Lunn phy->mdio.dev.of_node = np; 2225a11dd7dSFlorian Fainelli phy->is_pseudo_fixed_link = true; 2236539c44dSDavid S. Miller 22434b31da4SAndrew Lunn switch (status->speed) { 22534b31da4SAndrew Lunn case SPEED_1000: 2263c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, 2273c1bcc86SAndrew Lunn phy->supported); 2283c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, 2293c1bcc86SAndrew Lunn phy->supported); 2303c1bcc86SAndrew Lunn /* fall through */ 23134b31da4SAndrew Lunn case SPEED_100: 2323c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, 2333c1bcc86SAndrew Lunn phy->supported); 2343c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, 2353c1bcc86SAndrew Lunn phy->supported); 2363c1bcc86SAndrew Lunn /* fall through */ 23734b31da4SAndrew Lunn case SPEED_10: 23834b31da4SAndrew Lunn default: 2393c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, 2403c1bcc86SAndrew Lunn phy->supported); 2413c1bcc86SAndrew Lunn linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, 2423c1bcc86SAndrew Lunn phy->supported); 24334b31da4SAndrew Lunn } 24434b31da4SAndrew Lunn 2456539c44dSDavid S. Miller ret = phy_device_register(phy); 2466539c44dSDavid S. Miller if (ret) { 2476539c44dSDavid S. Miller phy_device_free(phy); 2486539c44dSDavid S. Miller of_node_put(np); 2496539c44dSDavid S. Miller fixed_phy_del(phy_addr); 2506539c44dSDavid S. Miller return ERR_PTR(ret); 2516539c44dSDavid S. Miller } 2526539c44dSDavid S. Miller 2536539c44dSDavid S. Miller return phy; 2546539c44dSDavid S. Miller } 2556539c44dSDavid S. Miller EXPORT_SYMBOL_GPL(fixed_phy_register); 2566539c44dSDavid S. Miller 2575bcbe0f3SAndrew Lunn void fixed_phy_unregister(struct phy_device *phy) 2585bcbe0f3SAndrew Lunn { 2595bcbe0f3SAndrew Lunn phy_device_remove(phy); 26013c9d934SJohan Hovold of_node_put(phy->mdio.dev.of_node); 2615bcbe0f3SAndrew Lunn fixed_phy_del(phy->mdio.addr); 2625bcbe0f3SAndrew Lunn } 2635bcbe0f3SAndrew Lunn EXPORT_SYMBOL_GPL(fixed_phy_unregister); 2645bcbe0f3SAndrew Lunn 2656539c44dSDavid S. Miller static int __init fixed_mdio_bus_init(void) 2666539c44dSDavid S. Miller { 2676539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 2686539c44dSDavid S. Miller int ret; 2696539c44dSDavid S. Miller 2706539c44dSDavid S. Miller pdev = platform_device_register_simple("Fixed MDIO bus", 0, NULL, 0); 271b0c1638fSFabio Estevam if (IS_ERR(pdev)) 272b0c1638fSFabio Estevam return PTR_ERR(pdev); 2736539c44dSDavid S. Miller 2746539c44dSDavid S. Miller fmb->mii_bus = mdiobus_alloc(); 2756539c44dSDavid S. Miller if (fmb->mii_bus == NULL) { 2766539c44dSDavid S. Miller ret = -ENOMEM; 2776539c44dSDavid S. Miller goto err_mdiobus_reg; 2786539c44dSDavid S. Miller } 2796539c44dSDavid S. Miller 2806539c44dSDavid S. Miller snprintf(fmb->mii_bus->id, MII_BUS_ID_SIZE, "fixed-0"); 2816539c44dSDavid S. Miller fmb->mii_bus->name = "Fixed MDIO Bus"; 2826539c44dSDavid S. Miller fmb->mii_bus->priv = fmb; 2836539c44dSDavid S. Miller fmb->mii_bus->parent = &pdev->dev; 2846539c44dSDavid S. Miller fmb->mii_bus->read = &fixed_mdio_read; 2856539c44dSDavid S. Miller fmb->mii_bus->write = &fixed_mdio_write; 2866539c44dSDavid S. Miller 2876539c44dSDavid S. Miller ret = mdiobus_register(fmb->mii_bus); 2886539c44dSDavid S. Miller if (ret) 2896539c44dSDavid S. Miller goto err_mdiobus_alloc; 2906539c44dSDavid S. Miller 2916539c44dSDavid S. Miller return 0; 2926539c44dSDavid S. Miller 2936539c44dSDavid S. Miller err_mdiobus_alloc: 2946539c44dSDavid S. Miller mdiobus_free(fmb->mii_bus); 2956539c44dSDavid S. Miller err_mdiobus_reg: 2966539c44dSDavid S. Miller platform_device_unregister(pdev); 2976539c44dSDavid S. Miller return ret; 2986539c44dSDavid S. Miller } 2996539c44dSDavid S. Miller module_init(fixed_mdio_bus_init); 3006539c44dSDavid S. Miller 3016539c44dSDavid S. Miller static void __exit fixed_mdio_bus_exit(void) 3026539c44dSDavid S. Miller { 3036539c44dSDavid S. Miller struct fixed_mdio_bus *fmb = &platform_fmb; 3046539c44dSDavid S. Miller struct fixed_phy *fp, *tmp; 3056539c44dSDavid S. Miller 3066539c44dSDavid S. Miller mdiobus_unregister(fmb->mii_bus); 3076539c44dSDavid S. Miller mdiobus_free(fmb->mii_bus); 3086539c44dSDavid S. Miller platform_device_unregister(pdev); 3096539c44dSDavid S. Miller 3106539c44dSDavid S. Miller list_for_each_entry_safe(fp, tmp, &fmb->phys, node) { 3116539c44dSDavid S. Miller list_del(&fp->node); 3126539c44dSDavid S. Miller kfree(fp); 3136539c44dSDavid S. Miller } 31469fc58a5SFlorian Fainelli ida_destroy(&phy_fixed_ida); 3156539c44dSDavid S. Miller } 3166539c44dSDavid S. Miller module_exit(fixed_mdio_bus_exit); 3176539c44dSDavid S. Miller 3186539c44dSDavid S. Miller MODULE_DESCRIPTION("Fixed MDIO bus (MDIO bus emulation with fixed PHYs)"); 3196539c44dSDavid S. Miller MODULE_AUTHOR("Vitaly Bordug"); 3206539c44dSDavid S. Miller MODULE_LICENSE("GPL"); 321