/* * drivers/net/phy/fixed.c * * Driver for fixed PHYs, when transceiver is able to operate in one fixed mode. * * Author: Vitaly Bordug * * Copyright (c) 2006 MontaVista Software, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * */ #include <linux/kernel.h> #include <linux/string.h> #include <linux/errno.h> #include <linux/unistd.h> #include <linux/slab.h> #include <linux/interrupt.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/skbuff.h> #include <linux/spinlock.h> #include <linux/mm.h> #include <linux/module.h> #include <linux/mii.h> #include <linux/ethtool.h> #include <linux/phy.h> #include <linux/phy_fixed.h> #include <asm/io.h> #include <asm/irq.h> #include <asm/uaccess.h> /* we need to track the allocated pointers in order to free them on exit */ static struct fixed_info *fixed_phy_ptrs[CONFIG_FIXED_MII_AMNT*MAX_PHY_AMNT]; /*----------------------------------------------------------------------------- * If something weird is required to be done with link/speed, * network driver is able to assign a function to implement this. * May be useful for PHY's that need to be software-driven. *-----------------------------------------------------------------------------*/ int fixed_mdio_set_link_update(struct phy_device *phydev, int (*link_update) (struct net_device *, struct fixed_phy_status *)) { struct fixed_info *fixed; if (link_update == NULL) return -EINVAL; if (phydev) { if (phydev->bus) { fixed = phydev->bus->priv; fixed->link_update = link_update; return 0; } } return -EINVAL; } EXPORT_SYMBOL(fixed_mdio_set_link_update); struct fixed_info *fixed_mdio_get_phydev (int phydev_ind) { if (phydev_ind >= MAX_PHY_AMNT) return NULL; return fixed_phy_ptrs[phydev_ind]; } EXPORT_SYMBOL(fixed_mdio_get_phydev); /*----------------------------------------------------------------------------- * This is used for updating internal mii regs from the status *-----------------------------------------------------------------------------*/ #if defined(CONFIG_FIXED_MII_100_FDX) || defined(CONFIG_FIXED_MII_10_FDX) || defined(CONFIG_FIXED_MII_1000_FDX) static int fixed_mdio_update_regs(struct fixed_info *fixed) { u16 *regs = fixed->regs; u16 bmsr = 0; u16 bmcr = 0; if (!regs) { printk(KERN_ERR "%s: regs not set up", __FUNCTION__); return -EINVAL; } if (fixed->phy_status.link) bmsr |= BMSR_LSTATUS; if (fixed->phy_status.duplex) { bmcr |= BMCR_FULLDPLX; switch (fixed->phy_status.speed) { case 100: bmsr |= BMSR_100FULL; bmcr |= BMCR_SPEED100; break; case 10: bmsr |= BMSR_10FULL; break; } } else { switch (fixed->phy_status.speed) { case 100: bmsr |= BMSR_100HALF; bmcr |= BMCR_SPEED100; break; case 10: bmsr |= BMSR_100HALF; break; } } regs[MII_BMCR] = bmcr; regs[MII_BMSR] = bmsr | 0x800; /*we are always capable of 10 hdx */ return 0; } static int fixed_mii_read(struct mii_bus *bus, int phy_id, int location) { struct fixed_info *fixed = bus->priv; /* if user has registered link update callback, use it */ if (fixed->phydev) if (fixed->phydev->attached_dev) { if (fixed->link_update) { fixed->link_update(fixed->phydev->attached_dev, &fixed->phy_status); fixed_mdio_update_regs(fixed); } } if ((unsigned int)location >= fixed->regs_num) return -1; return fixed->regs[location]; } static int fixed_mii_write(struct mii_bus *bus, int phy_id, int location, u16 val) { /* do nothing for now */ return 0; } static int fixed_mii_reset(struct mii_bus *bus) { /*nothing here - no way/need to reset it */ return 0; } #endif static int fixed_config_aneg(struct phy_device *phydev) { /* :TODO:03/13/2006 09:45:37 PM:: The full autoneg funcionality can be emulated, but no need to have anything here for now */ return 0; } /*----------------------------------------------------------------------------- * the manual bind will do the magic - with phy_id_mask == 0 * match will never return true... *-----------------------------------------------------------------------------*/ static struct phy_driver fixed_mdio_driver = { .name = "Fixed PHY", #ifdef CONFIG_FIXED_MII_1000_FDX .features = PHY_GBIT_FEATURES, #else .features = PHY_BASIC_FEATURES, #endif .config_aneg = fixed_config_aneg, .read_status = genphy_read_status, .driver = { .owner = THIS_MODULE, }, }; static void fixed_mdio_release(struct device *dev) { struct phy_device *phydev = container_of(dev, struct phy_device, dev); struct mii_bus *bus = phydev->bus; struct fixed_info *fixed = bus->priv; kfree(phydev); kfree(bus->dev); kfree(bus); kfree(fixed->regs); kfree(fixed); } /*----------------------------------------------------------------------------- * This func is used to create all the necessary stuff, bind * the fixed phy driver and register all it on the mdio_bus_type. * speed is either 10 or 100 or 1000, duplex is boolean. * number is used to create multiple fixed PHYs, so that several devices can * utilize them simultaneously. * * The device on mdio bus will look like [bus_id]:[phy_id], * bus_id = number * phy_id = speed+duplex. *-----------------------------------------------------------------------------*/ #if defined(CONFIG_FIXED_MII_100_FDX) || defined(CONFIG_FIXED_MII_10_FDX) || defined(CONFIG_FIXED_MII_1000_FDX) struct fixed_info *fixed_mdio_register_device( int bus_id, int speed, int duplex, u8 phy_id) { struct mii_bus *new_bus; struct fixed_info *fixed; struct phy_device *phydev; int err; struct device *dev = kzalloc(sizeof(struct device), GFP_KERNEL); if (dev == NULL) goto err_dev_alloc; new_bus = kzalloc(sizeof(struct mii_bus), GFP_KERNEL); if (new_bus == NULL) goto err_bus_alloc; fixed = kzalloc(sizeof(struct fixed_info), GFP_KERNEL); if (fixed == NULL) goto err_fixed_alloc; fixed->regs = kzalloc(MII_REGS_NUM * sizeof(int), GFP_KERNEL); if (NULL == fixed->regs) goto err_fixed_regs_alloc; fixed->regs_num = MII_REGS_NUM; fixed->phy_status.speed = speed; fixed->phy_status.duplex = duplex; fixed->phy_status.link = 1; new_bus->name = "Fixed MII Bus"; new_bus->read = &fixed_mii_read; new_bus->write = &fixed_mii_write; new_bus->reset = &fixed_mii_reset; /*set up workspace */ fixed_mdio_update_regs(fixed); new_bus->priv = fixed; new_bus->dev = dev; dev_set_drvdata(dev, new_bus); /* create phy_device and register it on the mdio bus */ phydev = phy_device_create(new_bus, 0, 0); if (phydev == NULL) goto err_phy_dev_create; /* * Put the phydev pointer into the fixed pack so that bus read/write * code could be able to access for instance attached netdev. Well it * doesn't have to do so, only in case of utilizing user-specified * link-update... */ fixed->phydev = phydev; phydev->speed = speed; phydev->duplex = duplex; phydev->irq = PHY_IGNORE_INTERRUPT; phydev->dev.bus = &mdio_bus_type; snprintf(phydev->dev.bus_id, BUS_ID_SIZE, PHY_ID_FMT, bus_id, phy_id); phydev->bus = new_bus; phydev->dev.driver = &fixed_mdio_driver.driver; phydev->dev.release = fixed_mdio_release; err = phydev->dev.driver->probe(&phydev->dev); if (err < 0) { printk(KERN_ERR "Phy %s: problems with fixed driver\n", phydev->dev.bus_id); goto err_out; } err = device_register(&phydev->dev); if (err) { printk(KERN_ERR "Phy %s failed to register\n", phydev->dev.bus_id); goto err_out; } //phydev->state = PHY_RUNNING; /* make phy go up quick, but in 10Mbit/HDX return fixed; err_out: kfree(phydev); err_phy_dev_create: kfree(fixed->regs); err_fixed_regs_alloc: kfree(fixed); err_fixed_alloc: kfree(new_bus); err_bus_alloc: kfree(dev); err_dev_alloc: return NULL; } #endif MODULE_DESCRIPTION("Fixed PHY device & driver for PAL"); MODULE_AUTHOR("Vitaly Bordug"); MODULE_LICENSE("GPL"); static int __init fixed_init(void) { int cnt = 0; int i; /* register on the bus... Not expected to be matched * with anything there... * */ phy_driver_register(&fixed_mdio_driver); /* We will create several mdio devices here, and will bound the upper * driver to them. * * Then the external software can lookup the phy bus by searching * for 0:101, to be connected to the virtual 100M Fdx phy. * * In case several virtual PHYs required, the bus_id will be in form * [num]:[duplex]+[speed], which make it able even to define * driver-specific link control callback, if for instance PHY is * completely SW-driven. */ for (i=1; i <= CONFIG_FIXED_MII_AMNT; i++) { #ifdef CONFIG_FIXED_MII_1000_FDX fixed_phy_ptrs[cnt++] = fixed_mdio_register_device(0, 1000, 1, i); #endif #ifdef CONFIG_FIXED_MII_100_FDX fixed_phy_ptrs[cnt++] = fixed_mdio_register_device(1, 100, 1, i); #endif #ifdef CONFIG_FIXED_MII_10_FDX fixed_phy_ptrs[cnt++] = fixed_mdio_register_device(2, 10, 1, i); #endif } return 0; } static void __exit fixed_exit(void) { int i; phy_driver_unregister(&fixed_mdio_driver); for (i=0; i < MAX_PHY_AMNT; i++) if ( fixed_phy_ptrs[i] ) device_unregister(&fixed_phy_ptrs[i]->phydev->dev); } module_init(fixed_init); module_exit(fixed_exit);