diff options
Diffstat (limited to 'target/linux/generic-2.6/files')
| -rw-r--r-- | target/linux/generic-2.6/files/drivers/net/phy/adm6996.c | 170 | ||||
| -rw-r--r-- | target/linux/generic-2.6/files/drivers/net/phy/adm6996.h | 105 | 
2 files changed, 275 insertions, 0 deletions
| diff --git a/target/linux/generic-2.6/files/drivers/net/phy/adm6996.c b/target/linux/generic-2.6/files/drivers/net/phy/adm6996.c new file mode 100644 index 000000000..3033813ec --- /dev/null +++ b/target/linux/generic-2.6/files/drivers/net/phy/adm6996.c @@ -0,0 +1,170 @@ +/* + * ADM6996 switch driver + * + * Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org> + * + * This program is free software; you can redistribute  it and/or modify it + * under  the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ +#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 <asm/io.h> +#include <asm/irq.h> +#include <asm/uaccess.h> +#include "adm6996.h" + +MODULE_DESCRIPTION("Infineon ADM6996 Switch"); +MODULE_AUTHOR("Felix Fietkau"); +MODULE_LICENSE("GPL"); + +struct adm6996_priv { +	/* use abstraction for regops, we want to add gpio support in the future */ +	u16 (*read)(struct phy_device *phydev, enum admreg reg); +	void (*write)(struct phy_device *phydev, enum admreg reg, u16 val); +}; + +#define to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv) + + +static inline u16 +r16(struct phy_device *pdev, enum admreg reg) +{ +	return to_adm(pdev)->read(pdev, reg); +} + +static inline void +w16(struct phy_device *pdev, enum admreg reg, u16 val) +{ +	to_adm(pdev)->write(pdev, reg, val); +} + + +static u16 +adm6996_read_mii_reg(struct phy_device *phydev, enum admreg reg) +{ +	return phydev->bus->read(phydev->bus, PHYADDR(reg)); +} + +static void +adm6996_write_mii_reg(struct phy_device *phydev, enum admreg reg, u16 val) +{ +	phydev->bus->write(phydev->bus, PHYADDR(reg), val); +} + + +static int adm6996_config_init(struct phy_device *pdev) +{ +	int i; + +	printk("%s: ADM6996 PHY driver attached.\n", pdev->attached_dev->name); +	pdev->supported = ADVERTISED_100baseT_Full; +	pdev->advertising = ADVERTISED_100baseT_Full; + +	/* initialize port and vlan settings */ +	for (i = 0; i < ADM_PHY_PORTS; i++) { +		w16(pdev, adm_portcfg[i], ADM_PORTCFG_INIT | +			ADM_PORTCFG_PVID((i == ADM_WAN_PORT) ? 1 : 0)); +	} +	w16(pdev, adm_portcfg[5], ADM_PORTCFG_CPU); + +	/* reset all ports */ +	for (i = 0; i < ADM_PHY_PORTS; i++) { +		w16(pdev, ADM_PHY_PORT(i), ADM_PHYCFG_INIT); +	} + +	return 0; +} + +static int adm6996_read_status(struct phy_device *phydev) +{ +	phydev->speed = SPEED_100; +	phydev->duplex = DUPLEX_FULL; +	phydev->state = PHY_UP; +	return 0; +} + +static int adm6996_config_aneg(struct phy_device *phydev) +{ +	return 0; +} + +static int adm6996_probe(struct phy_device *pdev) +{ +	struct adm6996_priv *priv; + +	priv = kzalloc(sizeof(struct adm6996_priv), GFP_KERNEL); +	if (priv == NULL) +		return -ENOMEM; + +	priv->read = adm6996_read_mii_reg; +	priv->write = adm6996_write_mii_reg; +	pdev->priv = priv; +	return 0; +} + +static void adm6996_remove(struct phy_device *pdev) +{ +	kfree(pdev->priv); +} + +static bool adm6996_detect(struct mii_bus *bus, int addr) +{ +	u16 reg; + +	/* we only attach to phy id 0 */ +	if (addr != 0) +		return false; + +	/* look for the switch on the bus */ +	reg = bus->read(bus, PHYADDR(ADM_SIG0)) & ADM_SIG0_MASK; +	if (reg != ADM_SIG0_VAL) +		return false; + +	reg = bus->read(bus, PHYADDR(ADM_SIG1)) & ADM_SIG1_MASK; +	if (reg != ADM_SIG1_VAL) +		return false; + +	return true; +} + +static struct phy_driver adm6996_driver = { +	.name		= "Infineon ADM6996", +	.features	= PHY_BASIC_FEATURES, +	.detect		= adm6996_detect, +	.probe		= adm6996_probe, +	.remove		= adm6996_remove, +	.config_init	= &adm6996_config_init, +	.config_aneg	= &adm6996_config_aneg, +	.read_status	= &adm6996_read_status, +	.driver		= { .owner = THIS_MODULE,}, +}; + +static int __init adm6996_init(void) +{ +	return phy_driver_register(&adm6996_driver); +} + +static void __exit adm6996_exit(void) +{ +	phy_driver_unregister(&adm6996_driver); +} + +module_init(adm6996_init); +module_exit(adm6996_exit); diff --git a/target/linux/generic-2.6/files/drivers/net/phy/adm6996.h b/target/linux/generic-2.6/files/drivers/net/phy/adm6996.h new file mode 100644 index 000000000..e07490151 --- /dev/null +++ b/target/linux/generic-2.6/files/drivers/net/phy/adm6996.h @@ -0,0 +1,105 @@ +/* + * ADM6996 switch driver + * + * Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org> + * + * This program is free software; you can redistribute  it and/or modify it + * under  the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ +#ifndef __ADM6996_H +#define __ADM6996_H + +#define ADM_PHY_PORTS	5 +#define ADM_CPU_PORT	5 +#define ADM_WAN_PORT	0 /* FIXME: dynamic ? */ + +enum admreg { +	ADM_EEPROM_BASE		= 0x0, +		ADM_P0_CFG		= ADM_EEPROM_BASE + 1, +		ADM_P1_CFG		= ADM_EEPROM_BASE + 3, +		ADM_P2_CFG		= ADM_EEPROM_BASE + 5, +		ADM_P3_CFG		= ADM_EEPROM_BASE + 7, +		ADM_P4_CFG		= ADM_EEPROM_BASE + 8, +		ADM_P5_CFG		= ADM_EEPROM_BASE + 9, +	ADM_EEPROM_EXT_BASE	= 0x40, +	ADM_COUNTER_BASE	= 0xa0, +		ADM_SIG0		= ADM_COUNTER_BASE + 0, +		ADM_SIG1		= ADM_COUNTER_BASE + 1, +	ADM_PHY_BASE		= 0x200, +#define ADM_PHY_PORT(n) (ADM_PHY_BASE + (0x20 * n)) +}; + +/* Chip identification patterns */ +#define	ADM_SIG0_MASK	0xfff0 +#define ADM_SIG0_VAL	0x1020 +#define ADM_SIG1_MASK	0xffff +#define ADM_SIG1_VAL	0x0007 + +enum { +	ADM_PHYCFG_COLTST     = (1 << 7),	/* Enable collision test */ +	ADM_PHYCFG_DPLX       = (1 << 8),	/* Enable full duplex */ +	ADM_PHYCFG_ANEN_RST   = (1 << 9),	/* Restart auto negotiation (self clear) */ +	ADM_PHYCFG_ISO        = (1 << 10),	/* Isolate PHY */ +	ADM_PHYCFG_PDN        = (1 << 11),	/* Power down PHY */ +	ADM_PHYCFG_ANEN       = (1 << 12),	/* Enable auto negotiation */ +	ADM_PHYCFG_SPEED_100  = (1 << 13),	/* Enable 100 Mbit/s */ +	ADM_PHYCFG_LPBK       = (1 << 14),	/* Enable loopback operation */ +	ADM_PHYCFG_RST        = (1 << 15),	/* Reset the port (self clear) */ +	ADM_PHYCFG_INIT = ( +		ADM_PHYCFG_RST | +		ADM_PHYCFG_SPEED_100 | +		ADM_PHYCFG_ANEN | +		ADM_PHYCFG_ANEN_RST +	) +}; + +enum { +	ADM_PORTCFG_FC        = (1 << 0),	/* Enable 802.x flow control */ +	ADM_PORTCFG_AN        = (1 << 1),	/* Enable auto-negotiation */ +	ADM_PORTCFG_SPEED_100 = (1 << 2),	/* Enable 100 Mbit/s */ +	ADM_PORTCFG_DPLX      = (1 << 3),	/* Enable full duplex */ +	ADM_PORTCFG_OT        = (1 << 4),	/* Output tagged packets */ +	ADM_PORTCFG_PD        = (1 << 5),	/* Port disable */ +	ADM_PORTCFG_TV_PRIO   = (1 << 6),	/* 0 = VLAN based priority +	                                 	 * 1 = TOS based priority */ +	ADM_PORTCFG_PPE       = (1 << 7),	/* Port based priority enable */ +	ADM_PORTCFG_PP_S      = (1 << 8),	/* Port based priority, 2 bits */ +	ADM_PORTCFG_PVID_BASE = (1 << 10),	/* Primary VLAN id, 4 bits */ +	ADM_PORTCFG_FSE	      = (1 << 14),	/* Fx select enable */ +	ADM_PORTCFG_CAM       = (1 << 15),	/* Crossover Auto MDIX */ + +	ADM_PORTCFG_INIT = ( +		ADM_PORTCFG_FC | +		ADM_PORTCFG_AN | +		ADM_PORTCFG_SPEED_100 | +		ADM_PORTCFG_DPLX | +		ADM_PORTCFG_CAM +	), +	ADM_PORTCFG_CPU = ( +		ADM_PORTCFG_FC | +		ADM_PORTCFG_SPEED_100 | +		ADM_PORTCFG_OT | +		ADM_PORTCFG_DPLX +	), +}; + +#define ADM_PORTCFG_PPID(N) ((n & 0x3) << 8) +#define ADM_PORTCFG_PVID(n) ((n & 0xf) << 10) + +static const u8 adm_portcfg[] = { +	[0] = ADM_P0_CFG, +	[1] = ADM_P1_CFG, +	[2] = ADM_P2_CFG, +	[3] = ADM_P3_CFG, +	[4] = ADM_P4_CFG, +	[5] = ADM_P5_CFG, +}; + +/* + * Split the register address in phy id and register + * it will get combined again by the mdio bus op + */ +#define PHYADDR(_reg)	((_reg >> 5) & 0xff), (_reg & 0x1f) + +#endif | 
