diff options
Diffstat (limited to 'drivers/net/ibm_newemac/rgmii.c')
| -rw-r--r-- | drivers/net/ibm_newemac/rgmii.c | 323 | 
1 files changed, 323 insertions, 0 deletions
diff --git a/drivers/net/ibm_newemac/rgmii.c b/drivers/net/ibm_newemac/rgmii.c new file mode 100644 index 00000000000..bcd7fc639c4 --- /dev/null +++ b/drivers/net/ibm_newemac/rgmii.c @@ -0,0 +1,323 @@ +/* + * drivers/net/ibm_newemac/rgmii.c + * + * Driver for PowerPC 4xx on-chip ethernet controller, RGMII bridge support. + * + * Copyright (c) 2004, 2005 Zultys Technologies. + * Eugene Surovegin <eugene.surovegin@zultys.com> or <ebs@ebshome.net> + * + * Based on original work by + * 	Matt Porter <mporter@kernel.crashing.org> + * 	Copyright 2004 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/ethtool.h> +#include <asm/io.h> + +#include "emac.h" +#include "debug.h" + +// XXX FIXME: Axon seems to support a subset of the RGMII, we +// thus need to take that into account and possibly change some +// of the bit settings below that don't seem to quite match the +// AXON spec + +/* RGMIIx_FER */ +#define RGMII_FER_MASK(idx)	(0x7 << ((idx) * 4)) +#define RGMII_FER_RTBI(idx)	(0x4 << ((idx) * 4)) +#define RGMII_FER_RGMII(idx)	(0x5 << ((idx) * 4)) +#define RGMII_FER_TBI(idx)	(0x6 << ((idx) * 4)) +#define RGMII_FER_GMII(idx)	(0x7 << ((idx) * 4)) + +/* RGMIIx_SSR */ +#define RGMII_SSR_MASK(idx)	(0x7 << ((idx) * 8)) +#define RGMII_SSR_100(idx)	(0x2 << ((idx) * 8)) +#define RGMII_SSR_1000(idx)	(0x4 << ((idx) * 8)) + +/* RGMII bridge supports only GMII/TBI and RGMII/RTBI PHYs */ +static inline int rgmii_valid_mode(int phy_mode) +{ +	return  phy_mode == PHY_MODE_GMII || +		phy_mode == PHY_MODE_RGMII || +		phy_mode == PHY_MODE_TBI || +		phy_mode == PHY_MODE_RTBI; +} + +static inline const char *rgmii_mode_name(int mode) +{ +	switch (mode) { +	case PHY_MODE_RGMII: +		return "RGMII"; +	case PHY_MODE_TBI: +		return "TBI"; +	case PHY_MODE_GMII: +		return "GMII"; +	case PHY_MODE_RTBI: +		return "RTBI"; +	default: +		BUG(); +	} +} + +static inline u32 rgmii_mode_mask(int mode, int input) +{ +	switch (mode) { +	case PHY_MODE_RGMII: +		return RGMII_FER_RGMII(input); +	case PHY_MODE_TBI: +		return RGMII_FER_TBI(input); +	case PHY_MODE_GMII: +		return RGMII_FER_GMII(input); +	case PHY_MODE_RTBI: +		return RGMII_FER_RTBI(input); +	default: +		BUG(); +	} +} + +int __devinit rgmii_attach(struct of_device *ofdev, int input, int mode) +{ +	struct rgmii_instance *dev = dev_get_drvdata(&ofdev->dev); +	struct rgmii_regs *p = dev->base; + +	RGMII_DBG(dev, "attach(%d)" NL, input); + +	/* Check if we need to attach to a RGMII */ +	if (input < 0 || !rgmii_valid_mode(mode)) { +		printk(KERN_ERR "%s: unsupported settings !\n", +		       ofdev->node->full_name); +		return -ENODEV; +	} + +	mutex_lock(&dev->lock); + +	/* Enable this input */ +	out_be32(&p->fer, in_be32(&p->fer) | rgmii_mode_mask(mode, input)); + +	printk(KERN_NOTICE "%s: input %d in %s mode\n", +	       ofdev->node->full_name, input, rgmii_mode_name(mode)); + +	++dev->users; + +	mutex_unlock(&dev->lock); + +	return 0; +} + +void rgmii_set_speed(struct of_device *ofdev, int input, int speed) +{ +	struct rgmii_instance *dev = dev_get_drvdata(&ofdev->dev); +	struct rgmii_regs *p = dev->base; +	u32 ssr; + +	mutex_lock(&dev->lock); + +	ssr = in_be32(&p->ssr) & ~RGMII_SSR_MASK(input); + +	RGMII_DBG(dev, "speed(%d, %d)" NL, input, speed); + +	if (speed == SPEED_1000) +		ssr |= RGMII_SSR_1000(input); +	else if (speed == SPEED_100) +		ssr |= RGMII_SSR_100(input); + +	out_be32(&p->ssr, ssr); + +	mutex_unlock(&dev->lock); +} + +void rgmii_get_mdio(struct of_device *ofdev, int input) +{ +	struct rgmii_instance *dev = dev_get_drvdata(&ofdev->dev); +	struct rgmii_regs *p = dev->base; +	u32 fer; + +	RGMII_DBG2(dev, "get_mdio(%d)" NL, input); + +	if (dev->type != RGMII_AXON) +		return; + +	mutex_lock(&dev->lock); + +	fer = in_be32(&p->fer); +	fer |= 0x00080000u >> input; +	out_be32(&p->fer, fer); +	(void)in_be32(&p->fer); + +	DBG2(dev, " fer = 0x%08x\n", fer); +} + +void rgmii_put_mdio(struct of_device *ofdev, int input) +{ +	struct rgmii_instance *dev = dev_get_drvdata(&ofdev->dev); +	struct rgmii_regs *p = dev->base; +	u32 fer; + +	RGMII_DBG2(dev, "put_mdio(%d)" NL, input); + +	if (dev->type != RGMII_AXON) +		return; + +	fer = in_be32(&p->fer); +	fer &= ~(0x00080000u >> input); +	out_be32(&p->fer, fer); +	(void)in_be32(&p->fer); + +	DBG2(dev, " fer = 0x%08x\n", fer); + +	mutex_unlock(&dev->lock); +} + +void __devexit rgmii_detach(struct of_device *ofdev, int input) +{ +	struct rgmii_instance *dev = dev_get_drvdata(&ofdev->dev); +	struct rgmii_regs *p = dev->base; + +	mutex_lock(&dev->lock); + +	BUG_ON(!dev || dev->users == 0); + +	RGMII_DBG(dev, "detach(%d)" NL, input); + +	/* Disable this input */ +	out_be32(&p->fer, in_be32(&p->fer) & ~RGMII_FER_MASK(input)); + +	--dev->users; + +	mutex_unlock(&dev->lock); +} + +int rgmii_get_regs_len(struct of_device *ofdev) +{ +	return sizeof(struct emac_ethtool_regs_subhdr) + +		sizeof(struct rgmii_regs); +} + +void *rgmii_dump_regs(struct of_device *ofdev, void *buf) +{ +	struct rgmii_instance *dev = dev_get_drvdata(&ofdev->dev); +	struct emac_ethtool_regs_subhdr *hdr = buf; +	struct rgmii_regs *regs = (struct rgmii_regs *)(hdr + 1); + +	hdr->version = 0; +	hdr->index = 0; /* for now, are there chips with more than one +			 * rgmii ? if yes, then we'll add a cell_index +			 * like we do for emac +			 */ +	memcpy_fromio(regs, dev->base, sizeof(struct rgmii_regs)); +	return regs + 1; +} + + +static int __devinit rgmii_probe(struct of_device *ofdev, +				 const struct of_device_id *match) +{ +	struct device_node *np = ofdev->node; +	struct rgmii_instance *dev; +	struct resource regs; +	int rc; + +	rc = -ENOMEM; +	dev = kzalloc(sizeof(struct rgmii_instance), GFP_KERNEL); +	if (dev == NULL) { +		printk(KERN_ERR "%s: could not allocate RGMII device!\n", +		       np->full_name); +		goto err_gone; +	} + +	mutex_init(&dev->lock); +	dev->ofdev = ofdev; + +	rc = -ENXIO; +	if (of_address_to_resource(np, 0, ®s)) { +		printk(KERN_ERR "%s: Can't get registers address\n", +		       np->full_name); +		goto err_free; +	} + +	rc = -ENOMEM; +	dev->base = (struct rgmii_regs *)ioremap(regs.start, +						 sizeof(struct rgmii_regs)); +	if (dev->base == NULL) { +		printk(KERN_ERR "%s: Can't map device registers!\n", +		       np->full_name); +		goto err_free; +	} + +	/* Check for RGMII type */ +	if (device_is_compatible(ofdev->node, "ibm,rgmii-axon")) +		dev->type = RGMII_AXON; +	else +		dev->type = RGMII_STANDARD; + +	DBG2(dev, " Boot FER = 0x%08x, SSR = 0x%08x\n", +	     in_be32(&dev->base->fer), in_be32(&dev->base->ssr)); + +	/* Disable all inputs by default */ +	out_be32(&dev->base->fer, 0); + +	printk(KERN_INFO +	       "RGMII %s %s initialized\n", +	       dev->type == RGMII_STANDARD ? "standard" : "axon", +	       ofdev->node->full_name); + +	wmb(); +	dev_set_drvdata(&ofdev->dev, dev); + +	return 0; + + err_free: +	kfree(dev); + err_gone: +	return rc; +} + +static int __devexit rgmii_remove(struct of_device *ofdev) +{ +	struct rgmii_instance *dev = dev_get_drvdata(&ofdev->dev); + +	dev_set_drvdata(&ofdev->dev, NULL); + +	WARN_ON(dev->users != 0); + +	iounmap(dev->base); +	kfree(dev); + +	return 0; +} + +static struct of_device_id rgmii_match[] = +{ +	{ +		.type		= "rgmii-interface", +		.compatible	= "ibm,rgmii", +	}, +	{ +		.type		= "emac-rgmii", +	}, +	{}, +}; + +static struct of_platform_driver rgmii_driver = { +	.name = "emac-rgmii", +	.match_table = rgmii_match, + +	.probe = rgmii_probe, +	.remove = rgmii_remove, +}; + +int __init rgmii_init(void) +{ +	return of_register_platform_driver(&rgmii_driver); +} + +void rgmii_exit(void) +{ +	of_unregister_platform_driver(&rgmii_driver); +}  |