fixed.c 8.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
/*
 * 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/sched.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 <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>

#define MII_REGS_NUM	7

/*
    The idea is to emulate normal phy behavior by responding with
    pre-defined values to mii BMCR read, so that read_status hook could
    take all the needed info.
*/

struct fixed_phy_status {
	u8 	link;
	u16	speed;
	u8 	duplex;
};

/*-----------------------------------------------------------------------------
 *  Private information hoder for mii_bus
 *-----------------------------------------------------------------------------*/
struct fixed_info {
	u16 *regs;
	u8 regs_num;
	struct fixed_phy_status phy_status;
	struct phy_device *phydev; /* pointer to the container */
	/* link & speed cb */
	int(*link_update)(struct net_device*, struct fixed_phy_status*);

};

/*-----------------------------------------------------------------------------
 *  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);

/*-----------------------------------------------------------------------------
 *  This is used for updating internal mii regs from the status
 *-----------------------------------------------------------------------------*/
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;
}

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",
	.features	= PHY_BASIC_FEATURES,
	.config_aneg	= fixed_config_aneg,
	.read_status	= genphy_read_status,
	.driver 	= { .owner = THIS_MODULE,},
};

/*-----------------------------------------------------------------------------
 *  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, duplex is boolean.
 * number is used to create multiple fixed PHYs, so that several devices can
 * utilize them simultaneously.
 *-----------------------------------------------------------------------------*/
static int fixed_mdio_register_device(int number, int speed, int duplex)
{
	struct mii_bus *new_bus;
	struct fixed_info *fixed;
	struct phy_device *phydev;
	int err = 0;

	struct device* dev = kzalloc(sizeof(struct device), GFP_KERNEL);

	if (NULL == dev)
		return -ENOMEM;

	new_bus = kzalloc(sizeof(struct mii_bus), GFP_KERNEL);

	if (NULL == new_bus) {
		kfree(dev);
		return -ENOMEM;
	}
	fixed = kzalloc(sizeof(struct fixed_info), GFP_KERNEL);

	if (NULL == fixed) {
		kfree(dev);
		kfree(new_bus);
		return -ENOMEM;
	}

	fixed->regs = kzalloc(MII_REGS_NUM*sizeof(int), GFP_KERNEL);
	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);

	/*
	 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;

	if(NULL == phydev) {
		err = -ENOMEM;
		goto device_create_fail;
	}

257
	phydev->irq = PHY_IGNORE_INTERRUPT;
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
	phydev->dev.bus = &mdio_bus_type;

	if(number)
		snprintf(phydev->dev.bus_id, BUS_ID_SIZE,
				"fixed_%d@%d:%d", number, speed, duplex);
	else
		snprintf(phydev->dev.bus_id, BUS_ID_SIZE,
				"fixed@%d:%d", speed, duplex);
	phydev->bus = new_bus;

	err = device_register(&phydev->dev);
	if(err) {
		printk(KERN_ERR "Phy %s failed to register\n",
				phydev->dev.bus_id);
		goto bus_register_fail;
	}

	/*
	   the mdio bus has phy_id match... In order not to do it
	   artificially, we are binding the driver here by hand;
	   it will be the same for all the fixed phys anyway.
	 */
	down_write(&phydev->dev.bus->subsys.rwsem);

	phydev->dev.driver = &fixed_mdio_driver.driver;

	err = phydev->dev.driver->probe(&phydev->dev);
	if(err < 0) {
		printk(KERN_ERR "Phy %s: problems with fixed driver\n",phydev->dev.bus_id);
		up_write(&phydev->dev.bus->subsys.rwsem);
		goto probe_fail;
	}

291 292
	err = device_bind_driver(&phydev->dev);

293 294
	up_write(&phydev->dev.bus->subsys.rwsem);

295 296 297
	if (err)
		goto probe_fail;

298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
	return 0;

probe_fail:
	device_unregister(&phydev->dev);
bus_register_fail:
	kfree(phydev);
device_create_fail:
	kfree(dev);
	kfree(new_bus);
	kfree(fixed);

	return err;
}


MODULE_DESCRIPTION("Fixed PHY device & driver for PAL");
MODULE_AUTHOR("Vitaly Bordug");
MODULE_LICENSE("GPL");

static int __init fixed_init(void)
{
319
#if 0
320 321
	int ret;
	int duplex = 0;
322
#endif
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342

	/* register on the bus... Not expected to be matched with anything there... */
	phy_driver_register(&fixed_mdio_driver);

	/* So let the fun begin...
	   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
	   fixed@speed:duplex, e.g. fixed@100:1, to be connected to the
	   virtual 100M Fdx phy.

	   In case several virtual PHYs required, the bus_id will be in form
	   fixed_<num>@<speed>:<duplex>, which make it able even to define
	   driver-specific link control callback, if for instance PHY is completely
	   SW-driven.

	*/

#ifdef CONFIG_FIXED_MII_DUPLEX
343
#if 0
344 345
	duplex = 1;
#endif
346
#endif
347 348 349 350 351

#ifdef CONFIG_FIXED_MII_100_FDX
	fixed_mdio_register_device(0, 100, 1);
#endif

352
#ifdef CONFIG_FIXED_MII_10_FDX
353 354 355 356 357 358 359 360 361 362 363 364 365
	fixed_mdio_register_device(0, 10, 1);
#endif
	return 0;
}

static void __exit fixed_exit(void)
{
	phy_driver_unregister(&fixed_mdio_driver);
	/* :WARNING:02/18/2006 04:32:40 AM:: Cleanup all the created stuff */
}

module_init(fixed_init);
module_exit(fixed_exit);