patch-2.3.49 linux/drivers/usb/pegasus.c

Next file: linux/drivers/usb/rio500.c
Previous file: linux/drivers/usb/joydev.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.48/linux/drivers/usb/pegasus.c linux/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 );

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)