diff options
Diffstat (limited to 'drivers/usb/pegasus.c')
-rw-r--r-- | drivers/usb/pegasus.c | 269 |
1 files changed, 212 insertions, 57 deletions
diff --git a/drivers/usb/pegasus.c b/drivers/usb/pegasus.c index 80b82bba2..8c588f79e 100644 --- a/drivers/usb/pegasus.c +++ b/drivers/usb/pegasus.c @@ -1,11 +1,37 @@ /* ** Pegasus: USB 10/100Mbps/HomePNA (1Mbps) Controller ** -** Copyright (R) 1999,2000 Petko Manolov - Petkan (petkan@spct.net) +** Copyright (c) 1999,2000 Petko Manolov - Petkan (petkan@spct.net) +** ** -** Distribute under GPL version 2 or later. +** ChangeLog: +** .... Most of the time spend reading sources & docs. +** v0.2.x First official release for the Linux kernel. +** v0.3.0 Beutified and structured, some bugs fixed. +** v0.3.x URBifying bulk requests and bugfixing. First relatively +** stable release. Still can touch device's registers only +** from top-halves. +** v0.4.0 Control messages remained unurbified are now URBs. +** Now we can touch the HW at any time. */ +/* + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + #include <linux/module.h> #include <linux/sched.h> #include <linux/malloc.h> @@ -16,7 +42,7 @@ #include <linux/usb.h> -static const char *version = __FILE__ ": v0.3.14 2000/06/09 (C) 1999-2000 Petko Manolov (petkan@spct.net)\n"; +static const char *version = __FILE__ ": v0.4.0 2000/06/15 (C) 1999-2000 Petko Manolov (petkan@spct.net)\n"; #define PEGASUS_MTU 1500 @@ -24,17 +50,39 @@ static const char *version = __FILE__ ": v0.3.14 2000/06/09 (C) 1999-2000 Petko #define SROM_WRITE 0x01 #define SROM_READ 0x02 #define PEGASUS_TX_TIMEOUT (HZ*5) +#define PEGASUS_CTRL_TIMEOUT 1000 #define PEGASUS_RESET 1 #define ALIGN(x) x __attribute__((aligned(L1_CACHE_BYTES))) +enum pegasus_registers { + EthCtrl0 = 0, + EthCtrl1 = 1, + EthCtrl2 = 2, + EthID = 0x10, + EpromOffset = 0x20, + EpromData = 0x21, /* 0x21 low, 0x22 high byte */ + EpromCtrl = 0x23, + PhyAddr = 0x25, + PhyData = 0x26, /* 0x26 low, 0x27 high byte */ + PhyCtrl = 0x28, + UsbStst = 0x2a, + EthTxStat0 = 0x2b, + EthTxStat1 = 0x2c, + EthRxStat = 0x2d, + Gpio0 = 0x7e, + Gpio1 = 0x7f, +}; + + struct pegasus { struct usb_device *usb; struct net_device *net; struct net_device_stats stats; int flags; - spinlock_t pegasus_lock; - struct urb rx_urb, tx_urb, intr_urb; + spinlock_t pegasus_lock, ctrl_lock; + struct urb rx_urb, tx_urb, intr_urb, ctrl_urb; + devrequest dr; unsigned char ALIGN(rx_buff[PEGASUS_MAX_MTU]); unsigned char ALIGN(tx_buff[PEGASUS_MAX_MTU]); unsigned char ALIGN(intr_buff[8]); @@ -64,7 +112,7 @@ static struct usb_eth_dev usb_dev_id[] = { {"D-Link DSB-650TX", 0x2001, 0x4001, NULL}, {"D-Link DSB-650TX", 0x2001, 0x4002, NULL}, {"D-Link DSB-650TX(PNA)", 0x2001, 0x4003, NULL}, - {"D-Link DU-10", 0x07b8, 0xabc1, NULL}, + {"D-Link DU-E10", 0x07b8, 0xabc1, NULL}, {"D-Link DU-E100", 0x07b8, 0x4002, NULL}, {"Linksys USB100TX", 0x066b, 0x2203, NULL}, {"Linksys USB100TX", 0x066b, 0x2204, NULL}, @@ -78,23 +126,120 @@ static struct usb_eth_dev usb_dev_id[] = { }; -#define pegasus_get_registers(dev, indx, size, data)\ - usb_control_msg(dev, usb_rcvctrlpipe(dev,0), 0xf0, 0xc0, 0, indx, data, size, HZ); -#define pegasus_set_registers(dev, indx, size, data)\ - usb_control_msg(dev, usb_sndctrlpipe(dev,0), 0xf1, 0x40, 0, indx, data, size, HZ); -#define pegasus_set_register(dev, indx, value) \ - { __u8 __data = value; \ - usb_control_msg(dev, usb_sndctrlpipe(dev,0), 0xf1, 0x40, __data, indx, &__data, 1, HZ);} + +static void pegasus_ctrl_end( urb_t *urb ) +{ + if ( urb->status ) + warn("ctrl_urb end status %d", urb->status); +} -static int pegasus_read_phy_word(struct usb_device *dev, __u8 index, __u16 *regdata) +static int pegasus_ctrl_timeout( urb_t *ctrl_urb ) +{ + int timeout=0; + + while ( ctrl_urb->status == -EINPROGRESS ) { + if ( timeout++ < PEGASUS_CTRL_TIMEOUT ) { + udelay(100); + continue; + } + err("ctrl urb busy %d", ctrl_urb->status); + usb_unlink_urb( ctrl_urb ); + return ctrl_urb->status; + } + return 0; +} + + +static int pegasus_get_registers( struct pegasus *pegasus, __u16 indx, __u16 size, void *data ) +{ + int ret; + + + spin_lock( &pegasus->ctrl_lock ); + pegasus->dr.requesttype = 0xc0; + pegasus->dr.request = 0xf0; + pegasus->dr.value = 0x0; + pegasus->dr.index = indx; + pegasus->dr.length = pegasus->ctrl_urb.transfer_buffer_length = size; + + FILL_CONTROL_URB( &pegasus->ctrl_urb, pegasus->usb, + usb_rcvctrlpipe(pegasus->usb,0), (char *)&pegasus->dr, + data, size, pegasus_ctrl_end, pegasus ); + + if ( (ret = usb_submit_urb( &pegasus->ctrl_urb )) ) + err("BAD CTRLs %d", ret); + else + ret = pegasus_ctrl_timeout( &pegasus->ctrl_urb ); + + spin_unlock( &pegasus->ctrl_lock ); + + return ret; +} + + +static int pegasus_set_registers( struct pegasus *pegasus, __u16 indx, __u16 size, void *data ) +{ + int ret; + + + spin_lock( &pegasus->ctrl_lock ); + pegasus->dr.requesttype = 0x40; + pegasus->dr.request = 0xf1; + pegasus->dr.value = 0x0; + pegasus->dr.index = indx; + pegasus->dr.length = pegasus->ctrl_urb.transfer_buffer_length = size; + + FILL_CONTROL_URB( &pegasus->ctrl_urb, pegasus->usb, + usb_sndctrlpipe(pegasus->usb,0), (char *)&pegasus->dr, + data, size, pegasus_ctrl_end, pegasus ); + + if ( (ret = usb_submit_urb( &pegasus->ctrl_urb )) ) + err("BAD CTRL %d", ret); + else + ret = pegasus_ctrl_timeout( &pegasus->ctrl_urb ); + + spin_unlock( &pegasus->ctrl_lock ); + + return ret; +} + + +static int pegasus_set_register( struct pegasus *pegasus, __u16 indx,__u8 data ) +{ + int ret; + + + spin_lock( &pegasus->ctrl_lock ); + pegasus->dr.requesttype = 0x40; + pegasus->dr.request = 0xf1; + pegasus->dr.value = data; + pegasus->dr.index = indx; + pegasus->dr.length = pegasus->ctrl_urb.transfer_buffer_length = 1; + + FILL_CONTROL_URB( &pegasus->ctrl_urb, pegasus->usb, + usb_sndctrlpipe(pegasus->usb,0), (char *)&pegasus->dr, + &data, 1, pegasus_ctrl_end, pegasus ); + + if ( (ret = usb_submit_urb( &pegasus->ctrl_urb )) ) + err("BAD CTRL %d", ret); + else + ret = pegasus_ctrl_timeout( &pegasus->ctrl_urb ); + + spin_unlock( &pegasus->ctrl_lock ); + + return ret; +} + + +static int pegasus_read_phy_word(struct pegasus *pegasus, __u8 index, __u16 *regdata) { int i; __u8 data[4] = { 1, 0, 0, 0x40 + index }; - pegasus_set_registers(dev, 0x25, 4, data); + pegasus_set_registers(pegasus, PhyAddr, 4, data); for (i = 0; i < 100; i++) { - pegasus_get_registers(dev, 0x26, 3, data); + pegasus_get_registers(pegasus, PhyData, 3, data); if (data[2] & 0x80) { *regdata = *(__u16 *)(data); return 0; @@ -107,14 +252,14 @@ static int pegasus_read_phy_word(struct usb_device *dev, __u8 index, __u16 *regd } -static int pegasus_write_phy_word(struct usb_device *dev, __u8 index, __u16 regdata) +static int pegasus_write_phy_word(struct pegasus *pegasus, __u8 index, __u16 regdata) { int i; __u8 data[4] = { 1, regdata, regdata >> 8, 0x20 + index }; - pegasus_set_registers(dev, 0x25, 4, data); + pegasus_set_registers(pegasus, PhyAddr, 4, data); for (i = 0; i < 100; i++) { - pegasus_get_registers(dev, 0x28, 1, data); + pegasus_get_registers(pegasus, PhyCtrl, 1, data); if (data[0] & 0x80) return 0; udelay(100); @@ -125,51 +270,51 @@ static int pegasus_write_phy_word(struct usb_device *dev, __u8 index, __u16 regd } -static int pegasus_rw_srom_word(struct usb_device *dev, __u8 index, __u16 *retdata, __u8 direction) +static int pegasus_rw_eprom_word(struct pegasus *pegasus, __u8 index, __u16 *retdata, __u8 direction) { int i; __u8 data[4] = { index, 0, 0, direction }; - pegasus_set_registers(dev, 0x20, 4, data); + pegasus_set_registers(pegasus, EpromOffset, 4, data); for (i = 0; i < 100; i++) { - pegasus_get_registers(dev, 0x23, 1, data); + pegasus_get_registers(pegasus, EpromCtrl, 1, data); if (data[0] & 4) { - pegasus_get_registers(dev, 0x21, 2, data); + pegasus_get_registers(pegasus, EpromData, 2, data); *retdata = *(__u16 *)data; return 0; } } - warn("pegasus_rw_srom_word() failed"); + warn("pegasus_rw_eprom_word() failed"); return 1; } -static int pegasus_get_node_id(struct usb_device *dev, __u8 *id) +static int pegasus_get_node_id(struct pegasus *pegasus, __u8 *id) { int i; for (i = 0; i < 3; i++) - if (pegasus_rw_srom_word(dev,i,(__u16 *)&id[i * 2],SROM_READ)) + if (pegasus_rw_eprom_word(pegasus,i,(__u16 *)&id[i*2],SROM_READ)) return 1; return 0; } -static int pegasus_reset_mac(struct usb_device *dev) +static int pegasus_reset_mac(struct pegasus *pegasus) { __u8 data = 0x8; int i; - pegasus_set_register(dev, 1, data); + pegasus_set_register(pegasus, EthCtrl1, data); for (i = 0; i < 100; i++) { - pegasus_get_registers(dev, 1, 1, &data); + pegasus_get_registers(pegasus, EthCtrl1, 1, &data); if (~data & 0x08) { if (loopback & 1) return 0; if (loopback & 2) - pegasus_write_phy_word(dev, 0, 0x4000); - pegasus_set_register(dev, 0x7e, 0x24); - pegasus_set_register(dev, 0x7e, 0x27); + pegasus_write_phy_word(pegasus, 0, 0x4000); + pegasus_set_register(pegasus, Gpio0, 0x24); + pegasus_set_register(pegasus, Gpio0, 0x27); return 0; } } @@ -183,22 +328,22 @@ static int pegasus_start_net(struct net_device *dev, struct usb_device *usb) __u16 partmedia, temp; __u8 node_id[6]; __u8 data[4]; + struct pegasus *pegasus = dev->priv; - if (pegasus_get_node_id(usb, node_id)) + if (pegasus_get_node_id(pegasus, node_id)) return 1; - pegasus_set_registers(usb, 0x10, 6, node_id); + pegasus_set_registers(pegasus, EthID, 6, node_id); memcpy(dev->dev_addr, node_id, 6); - if (pegasus_read_phy_word(usb, 1, &temp)) + if (pegasus_read_phy_word(pegasus, 1, &temp)) return 2; if ((~temp & 4) && !loopback) { - warn("%s: link NOT established (0x%x), check the cable.", + warn("%s: link NOT established (0x%x) - check the cable.", dev->name, temp); - /* return 3; FIXME */ } - if (pegasus_read_phy_word(usb, 5, &partmedia)) + if (pegasus_read_phy_word(pegasus, 5, &partmedia)) return 4; if ((partmedia & 0x1f) != 1) { @@ -210,7 +355,7 @@ static int pegasus_start_net(struct net_device *dev, struct usb_device *usb) data[1] = (partmedia & 0x100) ? 0x30 : ((partmedia & 0x80) ? 0x10 : 0); data[2] = (loopback & 1) ? 0x09 : 0x01; - pegasus_set_registers(usb, 0, 3, data); + pegasus_set_registers(pegasus, EthCtrl0, 3, data); return 0; } @@ -281,24 +426,26 @@ static void pegasus_write_bulk(struct urb *urb) { struct pegasus *pegasus = urb->context; - spin_lock(&pegasus->pegasus_lock); if (urb->status) info("%s: TX status %d", pegasus->net->name, urb->status); +#if 1 /* Should be fixed */ + if (urb->status == -ETIMEDOUT) + pegasus_reset_mac(pegasus); +#endif netif_wake_queue(pegasus->net); - - spin_unlock(&pegasus->pegasus_lock); } static void pegasus_tx_timeout(struct net_device *net) { struct pegasus *pegasus = net->priv; - warn("%s: Tx timed out. Reseting...", net->name); + usb_unlink_urb(&pegasus->tx_urb); + warn("%s: Tx timed out. Reseting...", net->name); + pegasus_reset_mac( pegasus ); pegasus->stats.tx_errors++; net->trans_start = jiffies; - pegasus->flags |= PEGASUS_RESET; netif_wake_queue(net); } @@ -372,6 +519,8 @@ static int pegasus_close(struct net_device *net) netif_stop_queue(net); + if ( pegasus->ctrl_urb.status == -EINPROGRESS ) + usb_unlink_urb(&pegasus->ctrl_urb); if ( pegasus->rx_urb.status == -EINPROGRESS ) usb_unlink_urb(&pegasus->rx_urb); if ( pegasus->tx_urb.status == -EINPROGRESS ) @@ -394,12 +543,12 @@ static int pegasus_ioctl(struct net_device *net, struct ifreq *rq, int cmd) case SIOCDEVPRIVATE: data[0] = 1; case SIOCDEVPRIVATE+1: - pegasus_read_phy_word(pegasus->usb, data[1] & 0x1f, &data[3]); + pegasus_read_phy_word(pegasus, data[1] & 0x1f, &data[3]); return 0; case SIOCDEVPRIVATE+2: if (!capable(CAP_NET_ADMIN)) return -EPERM; - pegasus_write_phy_word(pegasus->usb, data[1] & 0x1f, data[2]); + pegasus_write_phy_word(pegasus, data[1] & 0x1f, data[2]); return 0; default: return -EOPNOTSUPP; @@ -410,19 +559,23 @@ static int pegasus_ioctl(struct net_device *net, struct ifreq *rq, int cmd) static void pegasus_set_rx_mode(struct net_device *net) { struct pegasus *pegasus = net->priv; + __u8 tmp; netif_stop_queue(net); if (net->flags & IFF_PROMISC) { info("%s: Promiscuous mode enabled", net->name); -/* pegasus_set_register(pegasus->usb, 2, 0x04); FIXME */ + pegasus_get_registers(pegasus, EthCtrl2, 1, &tmp); + pegasus_set_register(pegasus, EthCtrl2, tmp | 4); } else if ((net->mc_count > multicast_filter_limit) || (net->flags & IFF_ALLMULTI)) { - pegasus_set_register(pegasus->usb, 0, 0xfa); - pegasus_set_register(pegasus->usb, 2, 0); + pegasus_set_register(pegasus, EthCtrl0, 0xfa); + pegasus_set_register(pegasus, EthCtrl2, 0); info("%s set allmulti", net->name); } else { info("%s: set Rx mode", net->name); + pegasus_get_registers(pegasus, EthCtrl2, 1, &tmp); + pegasus_set_register(pegasus, EthCtrl2, tmp & ~4); } netif_wake_queue(net); @@ -464,12 +617,6 @@ static void * pegasus_probe(struct usb_device *dev, unsigned int ifnum) } memset(pegasus, 0, sizeof(struct pegasus)); - if (pegasus_reset_mac(dev)) { - err("can't reset MAC"); - kfree(pegasus); - return NULL; - } - net = init_etherdev(0, 0); net->priv = pegasus; net->open = pegasus_open; @@ -485,6 +632,7 @@ static void * pegasus_probe(struct usb_device *dev, unsigned int ifnum) pegasus->usb = dev; pegasus->net = net; pegasus->pegasus_lock = SPIN_LOCK_UNLOCKED; + pegasus->ctrl_lock = SPIN_LOCK_UNLOCKED; FILL_BULK_URB(&pegasus->rx_urb, dev, usb_rcvbulkpipe(dev, 1), pegasus->rx_buff, PEGASUS_MAX_MTU, pegasus_read_bulk, @@ -495,7 +643,12 @@ static void * pegasus_probe(struct usb_device *dev, unsigned int ifnum) FILL_INT_URB(&pegasus->intr_urb, dev, usb_rcvintpipe(dev, 3), pegasus->intr_buff, 8, pegasus_irq, pegasus, 500); - + if (pegasus_reset_mac(pegasus)) { + err("can't reset MAC"); + kfree(pegasus); + return NULL; + } + printk(KERN_INFO "%s: %s\n", net->name, usb_dev_id[dev_indx].name); return pegasus; @@ -515,7 +668,9 @@ static void pegasus_disconnect(struct usb_device *dev, void *ptr) dev_close(pegasus->net); unregister_netdev(pegasus->net); - + + if ( pegasus->ctrl_urb.status == -EINPROGRESS ) + usb_unlink_urb(&pegasus->ctrl_urb); if ( pegasus->rx_urb.status == -EINPROGRESS ) usb_unlink_urb(&pegasus->rx_urb); if ( pegasus->tx_urb.status == -EINPROGRESS ) |