-/* $Id: ethernet.c,v 1.17 2003/07/04 08:27:37 starvik Exp $
+/* $Id: ethernet.c,v 1.22 2004/05/14 07:58:03 starvik Exp $
*
* e100net.c: A network driver for the ETRAX 100LX network controller.
*
* The outline of this driver comes from skeleton.c.
*
* $Log: ethernet.c,v $
+ * Revision 1.22 2004/05/14 07:58:03 starvik
+ * Merge of changes from 2.4
+ *
+ * Revision 1.20 2004/03/11 11:38:40 starvik
+ * Merge of Linux 2.6.4
+ *
+ * Revision 1.18 2003/12/03 13:45:46 starvik
+ * Use hardware pad for short packets to prevent information leakage.
+ *
* Revision 1.17 2003/07/04 08:27:37 starvik
* Merge of Linux 2.5.74
*
struct sk_buff* skb;
} etrax_eth_descr;
+/* Some transceivers requires special handling */
+struct transceiver_ops
+{
+ unsigned int oui;
+ void (*check_speed)(void);
+ void (*check_duplex)(void);
+};
+
+struct transceiver_ops* transceiver;
+
/* Duplex settings */
enum duplex
{
*/
#define MDIO_BASE_STATUS_REG 0x1
#define MDIO_BASE_CONTROL_REG 0x0
+#define MDIO_PHY_ID_HIGH_REG 0x2
+#define MDIO_PHY_ID_LOW_REG 0x3
#define MDIO_BC_NEGOTIATE 0x0200
#define MDIO_BC_FULL_DUPLEX_MASK 0x0100
#define MDIO_BC_AUTO_NEG_MASK 0x1000
#define MDIO_BC_SPEED_SELECT_MASK 0x2000
+#define MDIO_STATUS_100_FD 0x4000
+#define MDIO_STATUS_100_HD 0x2000
+#define MDIO_STATUS_10_FD 0x1000
+#define MDIO_STATUS_10_HD 0x0800
+#define MDIO_STATUS_SPEED_DUPLEX_MASK 0x7800
#define MDIO_ADVERTISMENT_REG 0x4
#define MDIO_ADVERT_100_FD 0x100
#define MDIO_ADVERT_100_HD 0x080
/* Broadcom specific */
#define MDIO_AUX_CTRL_STATUS_REG 0x18
-#define MDIO_FULL_DUPLEX_IND 0x1
-#define MDIO_SPEED 0x2
-#define MDIO_PHYS_ADDR 0x0
+#define MDIO_BC_FULL_DUPLEX_IND 0x1
+#define MDIO_BC_SPEED 0x2
+
+/* TDK specific */
+#define MDIO_TDK_DIAGNOSTIC_REG 18
+#define MDIO_TDK_DIAGNOSTIC_RATE 0x400
+#define MDIO_TDK_DIAGNOSTIC_DPLX 0x800
/* Network flash constants */
#define NET_FLASH_TIME (HZ/50) /* 20 ms */
static etrax_eth_descr TxDescList[NBR_OF_TX_DESC] __attribute__ ((aligned(32)));
static unsigned int network_rec_config_shadow = 0;
+static unsigned int mdio_phy_addr; /* Transciever address */
+
+static unsigned int network_tr_ctrl_shadow = 0;
/* Network speed indication. */
static struct timer_list speed_timer = TIMER_INITIALIZER(NULL, 0, 0);
static void e100_hardware_send_packet(char *buf, int length);
static void update_rx_stats(struct net_device_stats *);
static void update_tx_stats(struct net_device_stats *);
+static int e100_probe_transceiver(void);
static void e100_check_speed(unsigned long dummy);
static void e100_set_speed(unsigned long speed);
static void e100_clear_network_leds(unsigned long dummy);
static void e100_set_network_leds(int active);
+static void broadcom_check_speed(void);
+static void broadcom_check_duplex(void);
+static void tdk_check_speed(void);
+static void tdk_check_duplex(void);
+static void generic_check_speed(void);
+static void generic_check_duplex(void);
+
+struct transceiver_ops transceivers[] =
+{
+ {0x1018, broadcom_check_speed, broadcom_check_duplex}, /* Broadcom */
+ {0xC039, tdk_check_speed, tdk_check_duplex}, /* TDK 2120 */
+ {0x039C, tdk_check_speed, tdk_check_duplex}, /* TDK 2120C */
+ {0x0000, generic_check_speed, generic_check_duplex} /* Generic, must be last */
+};
+
#define tx_done(dev) (*R_DMA_CH0_CMD == 0)
/*
struct net_device *dev;
int i, err;
- printk("ETRAX 100LX 10/100MBit ethernet v2.0 (c) 2000-2003 Axis Communications AB\n");
+ printk(KERN_INFO
+ "ETRAX 100LX 10/100MBit ethernet v2.0 (c) 2000-2003 Axis Communications AB\n");
dev = alloc_etherdev(sizeof(struct net_local));
if (!dev)
current_speed_selection = 0; /* Auto */
speed_timer.expires = jiffies + NET_LINK_UP_CHECK_INTERVAL;
speed_timer.function = e100_check_speed;
- add_timer(&speed_timer);
clear_led_timer.function = e100_clear_network_leds;
current_duplex = autoneg;
duplex_timer.expires = jiffies + NET_DUPLEX_CHECK_INTERVAL;
duplex_timer.function = e100_check_duplex;
- add_timer(&duplex_timer);
/* Initialize group address registers to make sure that no */
/* unwanted addresses are matched */
/* show it in the log as well */
- printk("%s: changed MAC to ", dev->name);
+ printk(KERN_INFO "%s: changed MAC to ", dev->name);
for (i = 0; i < 5; i++)
printk("%02X:", dev->dev_addr[i]);
{
unsigned long flags;
- /* disable the ethernet interface while we configure it */
-
- *R_NETWORK_GEN_CONFIG =
- IO_STATE(R_NETWORK_GEN_CONFIG, phy, mii_clk) |
- IO_STATE(R_NETWORK_GEN_CONFIG, enable, off);
-
/* enable the MDIO output pin */
*R_NETWORK_MGM_CTRL = IO_STATE(R_NETWORK_MGM_CTRL, mdoe, enable);
IO_STATE(R_NETWORK_GEN_CONFIG, phy, mii_clk) |
IO_STATE(R_NETWORK_GEN_CONFIG, enable, on);
- *R_NETWORK_TR_CTRL =
- IO_STATE(R_NETWORK_TR_CTRL, clr_error, clr) |
- IO_STATE(R_NETWORK_TR_CTRL, delay, none) |
- IO_STATE(R_NETWORK_TR_CTRL, cancel, dont) |
- IO_STATE(R_NETWORK_TR_CTRL, cd, enable) |
- IO_STATE(R_NETWORK_TR_CTRL, retry, enable) |
- IO_STATE(R_NETWORK_TR_CTRL, pad, enable) |
- IO_STATE(R_NETWORK_TR_CTRL, crc, enable);
+ SETS(network_tr_ctrl_shadow, R_NETWORK_TR_CTRL, clr_error, clr);
+ SETS(network_tr_ctrl_shadow, R_NETWORK_TR_CTRL, delay, none);
+ SETS(network_tr_ctrl_shadow, R_NETWORK_TR_CTRL, cancel, dont);
+ SETS(network_tr_ctrl_shadow, R_NETWORK_TR_CTRL, cd, enable);
+ SETS(network_tr_ctrl_shadow, R_NETWORK_TR_CTRL, retry, enable);
+ SETS(network_tr_ctrl_shadow, R_NETWORK_TR_CTRL, pad, enable);
+ SETS(network_tr_ctrl_shadow, R_NETWORK_TR_CTRL, crc, enable);
+ *R_NETWORK_TR_CTRL = network_tr_ctrl_shadow;
save_flags(flags);
cli();
/* enable the irq's for ethernet DMA */
*R_IRQ_MASK2_SET =
- IO_STATE(R_IRQ_MASK2_SET, dma1_eop, set);
+ IO_STATE(R_IRQ_MASK2_SET, dma0_eop, set) |
+ IO_STATE(R_IRQ_MASK2_SET, dma1_eop, set);
*R_IRQ_MASK0_SET =
IO_STATE(R_IRQ_MASK0_SET, overrun, set) |
restore_flags(flags);
+ /* Probe for transceiver */
+ if (e100_probe_transceiver())
+ goto grace_exit3;
+
+ /* Start duplex/speed timers */
+ add_timer(&speed_timer);
+ add_timer(&duplex_timer);
+
/* We are now ready to accept transmit requeusts from
* the queueing layer of the networking.
*/
return 0;
+grace_exit3:
+ free_irq(NETWORK_STATUS_IRQ_NBR, (void *)dev);
grace_exit2:
free_irq(NETWORK_DMA_TX_IRQ_NBR, (void *)dev);
grace_exit1:
}
+static void
+generic_check_speed(void)
+{
+ unsigned long data;
+ data = e100_get_mdio_reg(MDIO_ADVERTISMENT_REG);
+ if ((data & MDIO_ADVERT_100_FD) ||
+ (data & MDIO_ADVERT_100_HD))
+ current_speed = 100;
+ else
+ current_speed = 10;
+}
+
+static void
+tdk_check_speed(void)
+{
+ unsigned long data;
+ data = e100_get_mdio_reg(MDIO_TDK_DIAGNOSTIC_REG);
+ current_speed = (data & MDIO_TDK_DIAGNOSTIC_RATE ? 100 : 10);
+}
+
+static void
+broadcom_check_speed(void)
+{
+ unsigned long data;
+ data = e100_get_mdio_reg(MDIO_AUX_CTRL_STATUS_REG);
+ current_speed = (data & MDIO_BC_SPEED ? 100 : 10);
+}
+
static void
e100_check_speed(unsigned long dummy)
{
+ static int led_initiated = 0;
unsigned long data;
int old_speed = current_speed;
if (!(data & MDIO_LINK_UP_MASK)) {
current_speed = 0;
} else {
- data = e100_get_mdio_reg(MDIO_AUX_CTRL_STATUS_REG);
- current_speed = (data & MDIO_SPEED ? 100 : 10);
+ transceiver->check_speed();
}
- if (old_speed != current_speed)
+ if ((old_speed != current_speed) || !led_initiated) {
+ led_initiated = 1;
e100_set_network_leds(NO_NETWORK_ACTIVITY);
+ }
/* Reinitialize the timer. */
speed_timer.expires = jiffies + NET_LINK_UP_CHECK_INTERVAL;
static void
e100_set_speed(unsigned long speed)
{
- current_speed_selection = speed;
- e100_negotiate();
+ if (speed != current_speed_selection) {
+ current_speed_selection = speed;
+ e100_negotiate();
+ }
}
static void
e100_check_duplex(unsigned long dummy)
{
- unsigned long data;
-
- data = e100_get_mdio_reg(MDIO_AUX_CTRL_STATUS_REG);
-
- if (data & MDIO_FULL_DUPLEX_IND) {
- if (!full_duplex) { /* Duplex changed to full? */
- full_duplex = 1;
- SETF(network_rec_config_shadow, R_NETWORK_REC_CONFIG, duplex, full_duplex);
- *R_NETWORK_REC_CONFIG = network_rec_config_shadow;
- }
- } else { /* half */
- if (full_duplex) { /* Duplex changed to half? */
- full_duplex = 0;
- SETF(network_rec_config_shadow, R_NETWORK_REC_CONFIG, duplex, full_duplex);
- *R_NETWORK_REC_CONFIG = network_rec_config_shadow;
- }
+ int old_duplex = full_duplex;
+ transceiver->check_duplex();
+ if (old_duplex != full_duplex) {
+ /* Duplex changed */
+ SETF(network_rec_config_shadow, R_NETWORK_REC_CONFIG, duplex, full_duplex);
+ *R_NETWORK_REC_CONFIG = network_rec_config_shadow;
}
/* Reinitialize the timer. */
add_timer(&duplex_timer);
}
+static void
+generic_check_duplex(void)
+{
+ unsigned long data;
+ data = e100_get_mdio_reg(MDIO_ADVERTISMENT_REG);
+ if ((data & MDIO_ADVERT_100_FD) ||
+ (data & MDIO_ADVERT_10_FD))
+ full_duplex = 1;
+ else
+ full_duplex = 0;
+}
+
+static void
+tdk_check_duplex(void)
+{
+ unsigned long data;
+ data = e100_get_mdio_reg(MDIO_TDK_DIAGNOSTIC_REG);
+ full_duplex = (data & MDIO_TDK_DIAGNOSTIC_DPLX) ? 1 : 0;
+}
+
+static void
+broadcom_check_duplex(void)
+{
+ unsigned long data;
+ data = e100_get_mdio_reg(MDIO_AUX_CTRL_STATUS_REG);
+ full_duplex = (data & MDIO_BC_FULL_DUPLEX_IND) ? 1 : 0;
+}
+
static void
e100_set_duplex(enum duplex new_duplex)
{
- current_duplex = new_duplex;
- e100_negotiate();
+ if (new_duplex != current_duplex) {
+ current_duplex = new_duplex;
+ e100_negotiate();
+ }
}
+static int
+e100_probe_transceiver(void)
+{
+ unsigned int phyid_high;
+ unsigned int phyid_low;
+ unsigned int oui;
+ struct transceiver_ops* ops = NULL;
+
+ /* Probe MDIO physical address */
+ for (mdio_phy_addr = 0; mdio_phy_addr <= 31; mdio_phy_addr++) {
+ if (e100_get_mdio_reg(MDIO_BASE_STATUS_REG) != 0xffff)
+ break;
+ }
+ if (mdio_phy_addr == 32)
+ return -ENODEV;
+
+ /* Get manufacturer */
+ phyid_high = e100_get_mdio_reg(MDIO_PHY_ID_HIGH_REG);
+ phyid_low = e100_get_mdio_reg(MDIO_PHY_ID_LOW_REG);
+ oui = (phyid_high << 6) | (phyid_low >> 10);
+
+ for (ops = &transceivers[0]; ops->oui; ops++) {
+ if (ops->oui == oui)
+ break;
+ }
+ transceiver = ops;
+
+ return 0;
+}
static unsigned short
e100_get_mdio_reg(unsigned char reg_num)
int bitCounter;
/* Start of frame, OP Code, Physical Address, Register Address */
- cmd = (MDIO_START << 14) | (MDIO_READ << 12) | (MDIO_PHYS_ADDR << 7) |
+ cmd = (MDIO_START << 14) | (MDIO_READ << 12) | (mdio_phy_addr << 7) |
(reg_num << 2);
e100_send_mdio_cmd(cmd, 0);
int bitCounter;
unsigned short cmd;
- cmd = (MDIO_START << 14) | (MDIO_WRITE << 12) | (MDIO_PHYS_ADDR << 7) |
+ cmd = (MDIO_START << 14) | (MDIO_WRITE << 12) | (mdio_phy_addr << 7) |
(reg << 2);
e100_send_mdio_cmd(cmd, 1);
data = e100_get_mdio_reg(MDIO_BASE_CONTROL_REG);
- cmd = (MDIO_START << 14) | (MDIO_WRITE << 12) | (MDIO_PHYS_ADDR << 7) | (MDIO_BASE_CONTROL_REG << 2);
+ cmd = (MDIO_START << 14) | (MDIO_WRITE << 12) | (mdio_phy_addr << 7) | (MDIO_BASE_CONTROL_REG << 2);
e100_send_mdio_cmd(cmd, 1);
e100_send_packet(struct sk_buff *skb, struct net_device *dev)
{
struct net_local *np = (struct net_local *)dev->priv;
- int length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN;
unsigned char *buf = skb->data;
unsigned long flags;
dev->trans_start = jiffies;
- e100_hardware_send_packet(buf, length);
+ e100_hardware_send_packet(buf, skb->len);
myNextTxDesc = phys_to_virt(myNextTxDesc->descr.next);
/* Stop queue if full */
if (myNextTxDesc == myFirstTxDesc) {
- /* Enable transmit interrupt to wake up queue */
- *R_DMA_CH0_CLR_INTR = IO_STATE(R_DMA_CH0_CLR_INTR, clr_eop, do);
- *R_IRQ_MASK2_SET = IO_STATE(R_IRQ_MASK2_SET, dma0_eop, set);
netif_stop_queue(dev);
}
struct net_local *np = (struct net_local *)dev->priv;
unsigned long irqbits = *R_IRQ_MASK2_RD;
+ /* Disable RX/TX IRQs to avoid reentrancy */
+ *R_IRQ_MASK2_CLR =
+ IO_STATE(R_IRQ_MASK2_CLR, dma0_eop, clr) |
+ IO_STATE(R_IRQ_MASK2_CLR, dma1_eop, clr);
+
/* Handle received packets */
if (irqbits & IO_STATE(R_IRQ_MASK2_RD, dma1_eop, active)) {
/* acknowledge the eop interrupt */
if (irqbits & IO_STATE(R_IRQ_MASK2_RD, dma0_eop, active)) {
/* acknowledge the eop interrupt and wake up queue */
*R_DMA_CH0_CLR_INTR = IO_STATE(R_DMA_CH0_CLR_INTR, clr_eop, do);
- *R_IRQ_MASK2_CLR = IO_STATE(R_IRQ_MASK2_CLR, dma0_eop, clr);
netif_wake_queue(dev);
}
+
+ /* Enable RX/TX IRQs again */
+ *R_IRQ_MASK2_SET =
+ IO_STATE(R_IRQ_MASK2_SET, dma0_eop, set) |
+ IO_STATE(R_IRQ_MASK2_SET, dma1_eop, set);
+
return IRQ_HANDLED;
}
/* check for underrun irq */
if (irqbits & IO_STATE(R_IRQ_MASK0_RD, underrun, active)) {
- *R_NETWORK_TR_CTRL = IO_STATE(R_NETWORK_TR_CTRL, clr_error, clr);
+ SETS(network_tr_ctrl_shadow, R_NETWORK_TR_CTRL, clr_error, clr);
+ *R_NETWORK_TR_CTRL = network_tr_ctrl_shadow;
+ SETS(network_tr_ctrl_shadow, R_NETWORK_TR_CTRL, clr_error, nop);
np->stats.tx_errors++;
D(printk("ethernet receiver underrun!\n"));
}
}
/* check for excessive collision irq */
if (irqbits & IO_STATE(R_IRQ_MASK0_RD, excessive_col, active)) {
+ SETS(network_tr_ctrl_shadow, R_NETWORK_TR_CTRL, clr_error, clr);
+ *R_NETWORK_TR_CTRL = network_tr_ctrl_shadow;
+ SETS(network_tr_ctrl_shadow, R_NETWORK_TR_CTRL, clr_error, nop);
*R_NETWORK_TR_CTRL = IO_STATE(R_NETWORK_TR_CTRL, clr_error, clr);
np->stats.tx_errors++;
D(printk("ethernet excessive collisions!\n"));
{
struct net_local *np = (struct net_local *)dev->priv;
- printk("Closing %s.\n", dev->name);
+ printk(KERN_INFO "Closing %s.\n", dev->name);
netif_stop_queue(dev);
- *R_NETWORK_GEN_CONFIG =
- IO_STATE(R_NETWORK_GEN_CONFIG, phy, mii_clk) |
- IO_STATE(R_NETWORK_GEN_CONFIG, enable, off);
-
*R_IRQ_MASK0_CLR =
IO_STATE(R_IRQ_MASK0_CLR, overrun, clr) |
IO_STATE(R_IRQ_MASK0_CLR, underrun, clr) |
update_rx_stats(&np->stats);
update_tx_stats(&np->stats);
+ /* Stop speed/duplex timers */
+ del_timer(&speed_timer);
+ del_timer(&duplex_timer);
+
return 0;
}
static int
e100_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
- struct mii_ioctl_data *data = (struct mii_ioctl_data *)&ifr->ifr_data;
- struct net_local *np = (struct net_local *)dev->priv;
+ struct mii_ioctl_data *data = if_mii(ifr);
+ struct net_local *np = netdev_priv(dev);
spin_lock(&np->lock); /* Preempt protection */
switch (cmd) {
case SIOCETHTOOL:
return e100_ethtool_ioctl(dev,ifr);
case SIOCGMIIPHY: /* Get PHY address */
- data->phy_id = MDIO_PHYS_ADDR;
+ data->phy_id = mdio_phy_addr;
break;
case SIOCGMIIREG: /* Read MII register */
data->val_out = e100_get_mdio_reg(data->reg_num);
case SET_ETH_SPEED_AUTO: /* Auto negotiate speed */
e100_set_speed(0);
break;
- case SET_ETH_DUPLEX_HALF: /* Hhalf duplex. */
+ case SET_ETH_DUPLEX_HALF: /* Half duplex. */
e100_set_duplex(half);
break;
case SET_ETH_DUPLEX_FULL: /* Full duplex. */
SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full;
ecmd.port = PORT_TP;
ecmd.transceiver = XCVR_EXTERNAL;
- ecmd.phy_address = MDIO_PHYS_ADDR;
+ ecmd.phy_address = mdio_phy_addr;
ecmd.speed = current_speed;
ecmd.duplex = full_duplex ? DUPLEX_FULL : DUPLEX_HALF;
ecmd.advertising = ADVERTISED_TP;
if (current_duplex == autoneg && current_speed_selection == 0)
- ecmd.advertising = ADVERTISED_Autoneg;
+ ecmd.advertising |= ADVERTISED_Autoneg;
else {
ecmd.advertising |=
ADVERTISED_10baseT_Half | ADVERTISED_10baseT_Full |
struct ethtool_drvinfo info;
memset((void *) &info, 0, sizeof (info));
strncpy(info.driver, "ETRAX 100LX", sizeof(info.driver) - 1);
- strncpy(info.version, "$Revision: 1.17 $", sizeof(info.version) - 1);
+ strncpy(info.version, "$Revision: 1.22 $", sizeof(info.version) - 1);
strncpy(info.fw_version, "N/A", sizeof(info.fw_version) - 1);
strncpy(info.bus_info, "N/A", sizeof(info.bus_info) - 1);
info.regdump_len = 0;
if (!current_speed) {
/* Make LED red, link is down */
+#if defined(CONFIG_ETRAX_NETWORK_RED_ON_NO_CONNECTION)
+ LED_NETWORK_SET(LED_RED);
+#else
LED_NETWORK_SET(LED_OFF);
+#endif
}
else if (light_leds) {
if (current_speed == 10) {
return etrax_ethernet_init();
}
+static int __init
+e100_boot_setup(char* str)
+{
+ struct sockaddr sa = {0};
+ int i;
+
+ /* Parse the colon separated Ethernet station address */
+ for (i = 0; i < ETH_ALEN; i++) {
+ unsigned int tmp;
+ if (sscanf(str + 3*i, "%2x", &tmp) != 1) {
+ printk(KERN_WARNING "Malformed station address");
+ return 0;
+ }
+ sa.sa_data[i] = (char)tmp;
+ }
+
+ default_mac = sa;
+ return 1;
+}
+
+__setup("etrax100_eth=", e100_boot_setup);
+
module_init(etrax_init_module);