summaryrefslogtreecommitdiffstats
path: root/drivers/usb/pegasus.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/pegasus.c')
-rw-r--r--drivers/usb/pegasus.c579
1 files changed, 579 insertions, 0 deletions
diff --git a/drivers/usb/pegasus.c b/drivers/usb/pegasus.c
new file mode 100644
index 000000000..1e752dc75
--- /dev/null
+++ b/drivers/usb/pegasus.c
@@ -0,0 +1,579 @@
+/*
+**
+** Pegasus: USB 10/100Mbps/HomePNA (1Mbps) Controller
+**
+** Copyleft (L) 1999 Petko Manolov - Petkan (petkan@spct.net)
+**
+** Distribute under GPL version 2 or later.
+*/
+
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/malloc.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+
+#include "usb.h"
+
+#if LINUX_VERSION_CODE<0x2032d || !defined(__KERNEL__) || !defined(__OPTIMIZE__)
+#error You can not compile this driver on this kernel with this C options!
+#endif
+
+
+#define ADMTEK_VENDOR_ID 0x07a6
+#define ADMTEK_HPNA_PEGASUS 0x0986
+
+#define HPNA_MTU 1500
+#define MAX_MTU 1536
+
+#define TX_TIMEOUT (HZ*5)
+#define SOMETHING (jiffies + TX_TIMEOUT)
+
+
+static const char version[] = "pegasus.c: v0.2.27 2000/02/29 Written by Petko Manolov (petkan@spct.net)\n";
+
+
+typedef struct usb_hpna
+{
+ struct usb_device *usb_dev;
+ struct net_device *net_dev;
+ int present;
+ int active;
+ void *irq_handler;
+ struct list_head list;
+ struct net_device_stats stats;
+ spinlock_t hpna_lock;
+ struct timer_list timer;
+
+ unsigned int rx_pipe;
+ unsigned char * rx_buff;
+ urb_t rx_urb;
+
+ unsigned int tx_pipe;
+ unsigned char * tx_buff;
+ urb_t tx_urb;
+ struct sk_buff * tx_skbuff;
+
+ __u8 intr_ival;
+ unsigned int intr_pipe;
+ unsigned char intr_buff[8];
+ urb_t intr_urb;
+} usb_hpna_t;
+
+
+usb_hpna_t usb_dev_hpna;
+static int loopback = 0;
+int multicast_filter_limit = 32;
+static LIST_HEAD(hpna_list);
+
+
+MODULE_AUTHOR("Petko Manolov <petkan@spct.net>");
+MODULE_DESCRIPTION("ADMtek \"Pegasus\" USB Ethernet driver");
+MODULE_PARM(loopback, "i");
+
+
+
+/*** vendor specific commands ***/
+static __inline__ int hpna_get_registers( struct usb_device *dev, __u16 indx, __u16 size, void *data )
+{
+ return usb_control_msg(dev, usb_rcvctrlpipe(dev,0), 0xf0, 0xc0, 0,
+ indx, data, size, HZ);
+}
+
+
+static __inline__ int hpna_set_register( struct usb_device *dev, __u16 indx, __u8 value )
+{
+ __u8 data = value;
+ return usb_control_msg(dev, usb_sndctrlpipe(dev,0), 0xf1, 0x40,
+ data, indx, &data, 1, HZ);
+}
+
+
+static __inline__ int hpna_set_registers( struct usb_device *dev, __u16 indx, __u16 size, void *data )
+{
+ return usb_control_msg(dev, usb_sndctrlpipe(dev,0), 0xf1, 0x40, 0,
+ indx, data, size, HZ);
+}
+
+
+static int read_phy_word( struct usb_device *dev, __u8 index, __u16 *regdata )
+{
+ int i;
+ __u8 data[4];
+
+ data[0] = 1;
+ data[1] = 0;
+ data[2] = 0;
+ data[3] = 0x40 + index;
+ hpna_set_registers( dev, 0x25, 4, data );
+ for ( i=0; i<100; i++ ) {
+ hpna_get_registers( dev, 0x25, 4, data );
+ if ( data[3] & 0x80 ) {
+ *regdata = *(__u16 *)(data+1);
+ return 0;
+ }
+ udelay(100);
+ }
+ warn("read_phy_word() failed");
+ return 1;
+}
+
+
+static int write_phy_word( struct usb_device *dev, __u8 index, __u16 regdata )
+{
+ int i;
+ __u8 data[4];
+
+ data[0] = 1;
+ data[1] = regdata;
+ data[2] = regdata >> 8;
+ data[3] = 0x20 + index;
+ hpna_set_registers( dev, 0x25, 4, data );
+ for ( i=0; i<100; i++ ) {
+ hpna_get_registers( dev, 0x28, 1, data );
+ if ( data[0] & 0x80 ) {
+ return 0;
+ }
+ udelay(100);
+ }
+ warn("write_phy_word() failed");
+ return 1;
+}
+
+
+int read_srom_word( struct usb_device *dev, __u8 index, __u16 *retdata)
+{
+ int i;
+ __u8 data[4];
+
+ data[0] = index;
+ data[1] = data[2] = 0;
+ data[3] = 0x02;
+ hpna_set_registers(dev, 0x20, 4, data);
+ for ( i=0; i<100; i++ ) {
+ hpna_get_registers(dev, 0x23, 1, data);
+ if ( data[0] & 4 ) {
+ hpna_get_registers(dev, 0x21, 2, data);
+ *retdata = *(__u16 *)data;
+ return 0;
+ }
+ }
+ warn("read_srom_word() failed");
+ return 1;
+}
+/*** end ***/
+
+
+
+
+int get_node_id( struct usb_device *dev, __u8 *id )
+{
+ int i;
+
+ for ( i=0; i<3; i++ ) {
+ if ( read_srom_word(dev, i, (__u16 *)&id[i*2] ) )
+ return 1;
+ }
+ return 0;
+}
+
+
+static int reset_mac( struct usb_device *dev )
+{
+ __u8 data = 0x8;
+ int i;
+
+ hpna_set_register( dev, 1, 0x08 );
+ for ( i=0; i<100; i++ ) {
+ hpna_get_registers( dev, 1, 1, &data);
+ if ( !(data & 0x08) ) {
+ if ( loopback & 1 )
+ return 0;
+ else if ( loopback & 2 ) {
+ write_phy_word( dev, 0, 0x4000 );
+ /*return 0;*/
+ }
+ hpna_set_register( dev, 0x7e, 0x24 );
+ hpna_set_register( dev, 0x7e, 0x27 );
+ return 0;
+ }
+ }
+ return 1;
+}
+
+
+int start_net( struct net_device *dev, struct usb_device *usb_dev )
+{
+ __u16 partmedia, temp;
+ __u8 node_id[6];
+ __u8 data[4];
+
+ if ( get_node_id(usb_dev, node_id) )
+ return 1;
+ hpna_set_registers(usb_dev, 0x10, 6, node_id);
+ memcpy(dev->dev_addr, node_id, 6);
+ if ( read_phy_word(usb_dev, 1, &temp) )
+ return 2;
+ if ( !(temp & 4) ) {
+ if ( loopback )
+ goto ok;
+ err("link NOT established - %x", temp);
+ return 3;
+ }
+ok:
+ if ( read_phy_word(usb_dev, 5, &partmedia) )
+ return 4;
+ temp = partmedia;
+ partmedia &= 0x1f;
+ if ( partmedia != 1 ) {
+ err("party FAIL %x", temp);
+ return 5;
+ }
+ partmedia = temp;
+ if ( partmedia & 0x100 )
+ data[1] = 0x30;
+ else {
+ if ( partmedia & 0x80 )
+ data[1] = 0x10;
+ else
+ data[1] = 0;
+ }
+
+ data[0] = 0xc9;
+ data[2] = (loopback & 1) ? 0x08 : 0x00;
+
+ hpna_set_registers(usb_dev, 0, 3, data);
+
+ return 0;
+}
+
+
+static void hpna_read_irq( purb_t urb )
+{
+ struct net_device *net_dev = urb->context;
+ usb_hpna_t *hpna = net_dev->priv;
+ int count = urb->actual_length, res;
+ int rx_status = *(int *)(hpna->rx_buff + count - 4);
+
+
+ if ( urb->status ) {
+ info( "%s: RX status %d\n", net_dev->name, urb->status );
+ goto goon;
+ }
+
+ if ( !count )
+ goto goon;
+/* if ( rx_status & 0x00010000 )
+ goto goon;
+*/
+ if ( rx_status & 0x000e0000 ) {
+ dbg("%s: error receiving packet %x",
+ net_dev->name, rx_status & 0xe0000);
+ hpna->stats.rx_errors++;
+ if(rx_status & 0x060000) hpna->stats.rx_length_errors++;
+ if(rx_status & 0x080000) hpna->stats.rx_crc_errors++;
+ if(rx_status & 0x100000) hpna->stats.rx_frame_errors++;
+ } else {
+ struct sk_buff *skb;
+ __u16 pkt_len = (rx_status & 0xfff) - 8;
+
+
+ if((skb = dev_alloc_skb(pkt_len+2)) != NULL ) {
+ skb->dev = net_dev;
+ skb_reserve(skb, 2);
+ eth_copy_and_sum(skb, hpna->rx_buff, pkt_len, 0);
+ skb_put(skb, pkt_len);
+ } else
+ goto goon;
+ skb->protocol = eth_type_trans(skb, net_dev);
+ netif_rx(skb);
+ hpna->stats.rx_packets++;
+ hpna->stats.rx_bytes += pkt_len;
+ }
+goon:
+ if ( (res = usb_submit_urb( &hpna->rx_urb )) )
+ warn("failed rx_urb %d", res);
+}
+
+
+static void hpna_irq( urb_t *urb)
+{
+ if( urb->status ) {
+ __u8 *d = urb->transfer_buffer;
+ printk("txst0 %x, txst1 %x, rxst %x, rxlst0 %x, rxlst1 %x, wakest %x",
+ d[0], d[1], d[2], d[3], d[4], d[5] );
+ }
+}
+
+
+static void hpna_write_irq( purb_t urb )
+{
+ struct net_device *net_dev = urb->context;
+ usb_hpna_t *hpna = net_dev->priv;
+
+
+ spin_lock( &hpna->hpna_lock );
+
+ if ( urb->status )
+ info("%s: TX status %d\n", net_dev->name, urb->status);
+ netif_wake_queue( net_dev );
+
+ spin_unlock( &hpna->hpna_lock );
+}
+
+
+static void tx_timeout( struct net_device *dev )
+{
+ usb_hpna_t *hpna = dev->priv;
+
+ warn( "%s: Tx timed out. Reseting...", dev->name );
+ hpna->stats.tx_errors++;
+ dev->trans_start = jiffies;
+ netif_wake_queue( dev );
+}
+
+
+static int hpna_start_xmit( struct sk_buff *skb, struct net_device *net_dev )
+{
+ usb_hpna_t *hpna = (usb_hpna_t *)net_dev->priv;
+ int count = skb->len+2 % 64 ? skb->len+2 : skb->len+3;
+ int res;
+
+ spin_lock( &hpna->hpna_lock );
+
+ netif_stop_queue( net_dev );
+ ((__u16 *)hpna->tx_buff)[0] = skb->len;
+ memcpy(hpna->tx_buff+2, skb->data, skb->len);
+ (&hpna->tx_urb)->transfer_buffer_length = count;
+ if ( (res = usb_submit_urb( &hpna->tx_urb )) ) {
+ warn("failed tx_urb %d", res);
+ hpna->stats.tx_errors++;
+ netif_start_queue( net_dev );
+ } else {
+ hpna->stats.tx_packets++;
+ hpna->stats.tx_bytes += skb->len;
+ net_dev->trans_start = jiffies;
+ }
+ dev_kfree_skb( skb );
+ spin_unlock( &hpna->hpna_lock );
+ return 0;
+}
+
+
+static struct net_device_stats *hpna_netdev_stats( struct net_device *dev )
+{
+ return &((usb_hpna_t *)dev->priv)->stats;
+}
+
+static int hpna_open( struct net_device *net_dev )
+{
+ usb_hpna_t *hpna = (usb_hpna_t *)net_dev->priv;
+ int res;
+
+ if ( hpna->active )
+ return -EBUSY;
+ else
+ hpna->active = 1;
+
+ if ( start_net(net_dev, hpna->usb_dev) ) {
+ err("can't start_net()");
+ return -EIO;
+ }
+
+ if ( (res = usb_submit_urb( &hpna->rx_urb )) )
+ warn("failed rx_urb %d", res);
+
+/* usb_submit_urb( &hpna->intr_urb );*/
+ netif_start_queue( net_dev );
+
+ MOD_INC_USE_COUNT;
+
+ return 0;
+}
+
+
+static int hpna_close( struct net_device *net_dev )
+{
+ usb_hpna_t *hpna = net_dev->priv;
+
+
+ netif_stop_queue( net_dev );
+
+ usb_unlink_urb( &hpna->rx_urb );
+ usb_unlink_urb( &hpna->tx_urb );
+/* usb_unlink_urb( hpna->intr_urb );*/
+
+ hpna->active = 0;
+
+ MOD_DEC_USE_COUNT;
+
+ return 0;
+}
+
+
+static int hpna_ioctl( struct net_device *dev, struct ifreq *rq, int cmd )
+{
+ __u16 *data = (__u16 *)&rq->ifr_data;
+ usb_hpna_t *hpna = dev->priv;
+
+ switch( cmd ) {
+ case SIOCDEVPRIVATE:
+ data[0] = 1;
+ case SIOCDEVPRIVATE+1:
+ read_phy_word(hpna->usb_dev, data[1] & 0x1f, &data[3]);
+ return 0;
+ case SIOCDEVPRIVATE+2:
+ if ( !capable(CAP_NET_ADMIN) )
+ return -EPERM;
+ write_phy_word(hpna->usb_dev, data[1] & 0x1f, data[2]);
+ return 0;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+
+static void set_rx_mode( struct net_device *net_dev )
+{
+ usb_hpna_t *hpna=net_dev->priv;
+
+ netif_stop_queue( net_dev );
+
+ if ( net_dev->flags & IFF_PROMISC ) {
+ info("%s: Promiscuous mode enabled", net_dev->name);
+ hpna_set_register( hpna->usb_dev, 2, 0x04 );
+ } else if ((net_dev->mc_count > multicast_filter_limit) ||
+ (net_dev->flags & IFF_ALLMULTI)) {
+ hpna_set_register(hpna->usb_dev, 0, 0xfa);
+ hpna_set_register(hpna->usb_dev, 2, 0);
+ } else {
+ dbg("%s: set Rx mode", net_dev->name);
+ }
+
+ netif_wake_queue( net_dev );
+}
+
+
+static void * usb_hpna_probe( struct usb_device *dev, unsigned int ifnum )
+{
+ struct net_device *net_dev;
+ usb_hpna_t *hpna = &usb_dev_hpna;
+
+
+
+ if ( dev->descriptor.idVendor != ADMTEK_VENDOR_ID ||
+ dev->descriptor.idProduct != ADMTEK_HPNA_PEGASUS ) {
+ return NULL;
+ }
+
+ printk("USB HPNA Pegasus found\n");
+
+ if ( usb_set_configuration(dev, dev->config[0].bConfigurationValue)) {
+ err("usb_set_configuration() failed");
+ return NULL;
+ }
+
+ hpna->usb_dev = dev;
+
+ hpna->rx_pipe = usb_rcvbulkpipe(hpna->usb_dev, 1);
+ hpna->tx_pipe = usb_sndbulkpipe(hpna->usb_dev, 2);
+ hpna->intr_pipe = usb_rcvintpipe(hpna->usb_dev, 0);
+
+ if ( reset_mac(dev) ) {
+ err("can't reset MAC");
+ }
+
+ hpna->present = 1;
+
+ if(!(hpna->rx_buff=kmalloc(MAX_MTU, GFP_KERNEL))) {
+ err("not enough mem for out buff");
+ return NULL;
+ }
+ if(!(hpna->tx_buff=kmalloc(MAX_MTU, GFP_KERNEL))) {
+ kfree_s(hpna->rx_buff, MAX_MTU);
+ err("not enough mem for out buff");
+ return NULL;
+ }
+
+ net_dev = init_etherdev( 0, 0 );
+ hpna->net_dev = net_dev;
+ net_dev->priv = hpna;
+ net_dev->open = hpna_open;
+ net_dev->stop = hpna_close;
+ net_dev->watchdog_timeo = TX_TIMEOUT;
+ net_dev->tx_timeout = tx_timeout;
+ net_dev->do_ioctl = hpna_ioctl;
+ net_dev->hard_start_xmit = hpna_start_xmit;
+ net_dev->set_multicast_list = set_rx_mode;
+ net_dev->get_stats = hpna_netdev_stats;
+ net_dev->mtu = HPNA_MTU;
+ hpna->hpna_lock = SPIN_LOCK_UNLOCKED;
+
+ FILL_BULK_URB( &hpna->rx_urb, hpna->usb_dev, hpna->rx_pipe,
+ hpna->rx_buff, MAX_MTU, hpna_read_irq, net_dev );
+ FILL_BULK_URB( &hpna->tx_urb, hpna->usb_dev, hpna->tx_pipe,
+ hpna->tx_buff, MAX_MTU, hpna_write_irq, net_dev );
+ FILL_INT_URB( &hpna->intr_urb, hpna->usb_dev, hpna->intr_pipe,
+ hpna->intr_buff, 8, hpna_irq, net_dev, 250 );
+
+/* list_add( &hpna->list, &hpna_list );*/
+
+ return net_dev;
+}
+
+
+static void usb_hpna_disconnect( struct usb_device *dev, void *ptr )
+{
+ struct net_device *net_dev = ptr;
+ struct usb_hpna *hpna = net_dev->priv;
+
+
+ if ( net_dev->flags & IFF_UP )
+ dev_close(net_dev);
+
+ unregister_netdev( net_dev );
+
+ if ( !hpna ) /* should never happen */
+ return;
+
+ usb_unlink_urb( &hpna->rx_urb );
+ usb_unlink_urb( &hpna->tx_urb );
+/* usb_unlink_urb( &hpna->intr_urb );*/
+ kfree_s(hpna->rx_buff, MAX_MTU);
+ kfree_s(hpna->tx_buff, MAX_MTU);
+
+ hpna->usb_dev = NULL;
+ hpna->present = 0;
+
+ printk("USB HPNA disconnected\n");
+}
+
+
+static struct usb_driver usb_hpna_driver = {
+ "ADMtek \"Pegasus\" USB Ethernet",
+ usb_hpna_probe,
+ usb_hpna_disconnect,
+ {NULL, NULL}
+};
+
+
+
+static int __init start_hpna( void )
+{
+ printk( version );
+ return usb_register( &usb_hpna_driver );
+}
+
+
+static void __exit stop_hpna( void )
+{
+ usb_deregister( &usb_hpna_driver );
+}
+
+
+module_init( start_hpna );
+module_exit( stop_hpna );