/*
**	Prolific: PL2302 USB-USB (12Mbps) Controller
**
**	Copyright (c) 2001 Alex Tsao (alex@prolific.com.tw)
**	
**
**	ChangeLog:
**		....	Most of the time spend reading sources & docs.
*/

/*
 * 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/sched.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/inetdevice.h>
#include <linux/usb.h>
#include <linux/module.h>

#define	BULK_IN_NUM		3
#define	BULK_OUT_NUM		2
#define INT_NUM			1
#define	CTRL_NUM		0

#define	PL2302_MTU		1500
#define	PL2302_MAX_MTU		1520	//1514+4(Len Header)

#define PEER_EXIST	0x01
#define TX_REQUEST  	0x02
#define RESET_IN	0x08
#define RESET_OUT	0x10

#define	PL2302_TX_TIMEOUT	(HZ*5)

#define	ALIGN(x)		x __attribute__((aligned(L1_CACHE_BYTES)))

typedef	unsigned char UCHAR;
typedef	__u16	USHORT;
typedef	__u32	ULONG;

typedef struct pl2302 {
	struct usb_device	*usb;
	struct net_device	*net;
	struct net_device_stats	stats;
	struct urb		ctrl_urb, int_urb, rx_urb, tx_urb;
	devrequest		dr;
	wait_queue_head_t	ctrl_wait, int_wait;
	unsigned char		ALIGN(rx_buff[PL2302_MAX_MTU]);
	unsigned char		ALIGN(tx_buff[PL2302_MAX_MTU]);
	int		int_status;	//Interrupt
	int		pid;	//Thread ID
} pl2302_t;

static const char *version = __FILE__ ": v0.0.03 2001/05/02 (C) 2001 Alex Tsao (alex@prolific.com.tw)";

MODULE_AUTHOR("Alex Tsao <alex@prolific.com.tw>");
MODULE_DESCRIPTION("PL2302 Prolific USB-USB driver");

static void int_callback( struct urb *urb )
{
	pl2302_t *pl2302 = urb->context;
	if( urb->status )
	printk( "<1>Status:%x Int:%x\n", urb->status, pl2302->int_status );
}

static void write_bulk_callback( struct urb *urb )
{
	pl2302_t *pl2302 = urb->context;

	if ( urb->status )
		printk("<1>%s: TX status 0x%x", pl2302->net->name, urb->status);

	pl2302->net->trans_start = jiffies;
	netif_wake_queue( pl2302->net );
}

static int rcv_routine( pl2302_t *pl2302 )
{
	__u32	iLen;
	struct sk_buff	*skb;

	iLen = *((__u32*)pl2302->rx_buff);

	if( iLen > PL2302_MAX_MTU ) return 3;

	if( memcmp( &pl2302->rx_buff[10], pl2302->net->dev_addr, 6 ) == 0 )
	{
		printk( "<1>Circular Packet\n" );
		return 2;
	}

	if ( !(skb = dev_alloc_skb(iLen)) )
		return 1;

	skb->dev = pl2302->net;
	eth_copy_and_sum(skb, &pl2302->rx_buff[4], iLen, 0);
	skb_put(skb, iLen);

	skb->protocol = eth_type_trans(skb, pl2302->net);
	netif_rx(skb);
	pl2302->stats.rx_packets++;
	pl2302->stats.rx_bytes += iLen;

	return 0;
}

static void read_bulk_callback( struct urb *urb )
{
	pl2302_t *pl2302 = urb->context;
	int count = urb->actual_length, res;

	if( !count ) return;

	switch ( urb->status ) {
		case USB_ST_NOERROR:
			if( count >= 42 ) rcv_routine( pl2302 );
			break;
		default:
			printk( "<1>RX status 0x%x", urb->status );
			return;
	}

	FILL_BULK_URB( &pl2302->rx_urb, pl2302->usb,
			usb_rcvbulkpipe(pl2302->usb, BULK_IN_NUM),
			pl2302->rx_buff, PL2302_MAX_MTU, 
			read_bulk_callback, pl2302 );
	if ( (res = usb_submit_urb(&pl2302->rx_urb)) )
		printk( "<1>BulkInError:%d\n", res );
}

static void ctrl_callback( urb_t *urb )
{
	pl2302_t	*pl2302 = urb->context;

	if ( !pl2302 )
		return;

	switch ( urb->status ) {
		case USB_ST_NOERROR:
			break;
		default:
			printk( "<1>Ctrl CallBack status 0x%x\n", urb->status);
			break;
	}
	wake_up_interruptible( &pl2302->ctrl_wait );
}

static int set_ctrl_command( pl2302_t *pl2302, __u8 type, __u8 request,
	__u16 value, __u16 indx, __u16 len, __u8 *data )
{
	int	ret;
	
	pl2302->dr.requesttype = type;
	pl2302->dr.request = request;
	pl2302->dr.value = cpu_to_le16(value);
	pl2302->dr.index = cpu_to_le16p(&indx);
	pl2302->dr.length = cpu_to_le16p(&len);
	pl2302->ctrl_urb.transfer_buffer_length = len;

	if( type & 0x80 )	//Rcv
		FILL_CONTROL_URB( &pl2302->ctrl_urb, pl2302->usb,
			  usb_rcvctrlpipe(pl2302->usb,CTRL_NUM),
			  (char *)&pl2302->dr,
			  data, len, ctrl_callback, pl2302 );
	else				//Snd
		FILL_CONTROL_URB( &pl2302->ctrl_urb, pl2302->usb,
			  usb_sndctrlpipe(pl2302->usb,CTRL_NUM),
			  (char *)&pl2302->dr,
			  data, len, ctrl_callback, pl2302 );

	if ( (ret = usb_submit_urb( &pl2302->ctrl_urb )) ) {
		printk( "<1> BAD CTRL %d", ret);
		return	ret;
	}

	interruptible_sleep_on( &pl2302->ctrl_wait );

	return	ret;
}

static int int_thread( void *context )
{
	pl2302_t *pl2302 = context;

	printk( "<1>Enter int_thread\n" );
	while( pl2302->pid )
	{
		FILL_INT_URB( &pl2302->int_urb, pl2302->usb,
			usb_rcvintpipe(pl2302->usb,INT_NUM),
			&pl2302->int_status, 1, 
			int_callback, pl2302, 0 );
		if( usb_submit_urb( &pl2302->int_urb ) != 0 )
			printk( "<1>issue interrupt error\n" );
		interruptible_sleep_on_timeout( &pl2302->int_wait, 2*HZ );
		if( pl2302->int_status & TX_REQUEST )
			set_ctrl_command( pl2302, 0x41, 0x01, 
			TX_REQUEST, 0, 0, NULL ); 
		if( pl2302->int_status & RESET_IN )
		{
			set_ctrl_command( pl2302, 0x41, 0x01,
			RESET_IN, 0, 0, NULL );
			usb_clear_halt( pl2302->usb, usb_rcvbulkpipe(pl2302->usb, BULK_IN_NUM) );
			usb_unlink_urb( &pl2302->rx_urb );
			FILL_BULK_URB( &pl2302->rx_urb, pl2302->usb,
				usb_rcvbulkpipe(pl2302->usb, BULK_IN_NUM),
				pl2302->rx_buff, PL2302_MAX_MTU, 
				read_bulk_callback, pl2302 );
			usb_submit_urb(&pl2302->rx_urb);
		}
		if( pl2302->int_status & RESET_OUT )
		{
			set_ctrl_command( pl2302, 0x41, 0x01,
			RESET_OUT, 0, 0, NULL );
			usb_clear_halt( pl2302->usb, usb_sndbulkpipe(pl2302->usb, BULK_OUT_NUM) );
			usb_unlink_urb( &pl2302->tx_urb );
		}
	}
	printk( "<1>Exit int_thread\n" );
	return 0;
}

static int init_card( pl2302_t *pl2302 )
{
	__u8	mac[6]={0x00,0x50,0x77,0x01,0x00,0x00};

	printk( "<1>init_card OK\n" );
//	Do Reset
	set_ctrl_command( pl2302, 0x41, 0x03, TX_REQUEST, 0, 0, NULL );
	set_ctrl_command( pl2302, 0x41, 0x03, RESET_IN, 0, 0, NULL );
	set_ctrl_command( pl2302, 0x41, 0x03, RESET_OUT, 0, 0, NULL );

	*((__u16*)&mac[4]) = jiffies;
	memcpy( pl2302->net->dev_addr, mac, 6 ); 

//	Create Thread
	pl2302->pid = kernel_thread( int_thread, pl2302,
			CLONE_FS|CLONE_FILES|CLONE_SIGHAND);
	printk( "<1>int_thread:%d\n", pl2302->pid );

	mdelay( 50 );
	set_ctrl_command( pl2302, 0x41, 0x03, PEER_EXIST, 0, 0, NULL );

	return	0;
}

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,48)
static void pl2302_tx_timeout( struct net_device *net )
{
	pl2302_t *pl2302 = net->priv;

	if ( !pl2302 )
		return;
		
	printk("<1>%s: Tx timed out.", net->name);
	pl2302->tx_urb.transfer_flags |= USB_ASYNC_UNLINK;
	usb_unlink_urb( &pl2302->tx_urb );
	pl2302->stats.tx_errors++;
}
#endif

static int write_queue( pl2302_t *pl2302, __u8 *buf, __u32 length )
{
	if( (length+4)%64 < 12 ) length += (12-(length+4)%64);	// %64==0 padding
	*((__u32*)pl2302->tx_buff) = length;
	memcpy( &pl2302->tx_buff[4], buf, length );
	FILL_BULK_URB( &pl2302->tx_urb, pl2302->usb,
			usb_sndbulkpipe(pl2302->usb, BULK_OUT_NUM),
			pl2302->tx_buff, PL2302_MAX_MTU, 
			write_bulk_callback, pl2302 );
	pl2302->tx_urb.transfer_buffer_length = 4+length;
	return usb_submit_urb(&pl2302->tx_urb);
}

static int pl2302_start_xmit( struct sk_buff *skb, struct net_device *net )
{
	pl2302_t	*pl2302 = net->priv;
	int 	res;

	if( pl2302->int_status & PEER_EXIST ) 
	{
		netif_stop_queue( net );
		if( (res = write_queue( pl2302, skb->data, skb->len )) )
		{
			printk("<1>failed tx_urb %d", res);
			pl2302->stats.tx_errors++;
			netif_start_queue( net );
		} else {
			pl2302->stats.tx_packets++;
			pl2302->stats.tx_bytes += skb->len;
			net->trans_start = jiffies;
		}
	}
	dev_kfree_skb(skb);
	return 0;
}

static struct net_device_stats *pl2302_netdev_stats( struct net_device *dev )
{
	printk( "<1>Statistics\n" );
	return &((pl2302_t *)dev->priv)->stats;
}

static int pl2302_open(struct net_device *net)
{
	pl2302_t *pl2302 = (pl2302_t *)net->priv;
	__u8	arp[42]={
0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x50,0x77,0x01,0x00,0x00,0x08,0x06,
0x00,0x01,0x08,0x00,0x06,0x04,0x00,0x01,0x00,0x50,0x77,0x01,0x00,0x00,
0x64,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
	__u32	addr;	//IP address

	printk( "<1>Open\n" );
	MOD_INC_USE_COUNT;
//	First	Bulk	In
	FILL_BULK_URB( &pl2302->rx_urb, pl2302->usb,
			usb_rcvbulkpipe(pl2302->usb, BULK_IN_NUM),
			pl2302->rx_buff, PL2302_MAX_MTU, 
			read_bulk_callback, pl2302 );
	usb_submit_urb(&pl2302->rx_urb);

	netif_start_queue( net );

	addr = ((struct in_device*)net->ip_ptr)->ifa_list->ifa_address;
	printk( "<1>IP:0x%x\n", addr );
	memcpy( &arp[6], net->dev_addr, 6 );
	memcpy( &arp[22], net->dev_addr, 6 ); 
	memcpy( &arp[28], &addr, 4 );
	write_queue( pl2302, arp, sizeof(arp) );

	return 0;
}

static int pl2302_close( struct net_device *net )
{
	pl2302_t	*pl2302 = net->priv;

	printk( "<1>Close\n" );

	netif_stop_queue( net );
	usb_unlink_urb( &pl2302->rx_urb );
	usb_unlink_urb( &pl2302->tx_urb );
	usb_unlink_urb( &pl2302->int_urb );
	usb_unlink_urb( &pl2302->ctrl_urb );
	MOD_DEC_USE_COUNT;

	return 0;
}

static void pl2302_set_multicast( struct net_device *net )
{
	printk( "<1>Multicast Count:%d Flags:0x%x\n", net->mc_count, net->flags );
}

static void* pl2302_probe( struct usb_device *dev, unsigned int ifnum )
{
	struct net_device	*net;
	pl2302_t		*pl2302;

	printk( "<1>Enter Probe\n" );

	if (dev->descriptor.idVendor != 0x067b || dev->descriptor.idProduct != 0x0001)
		return NULL;

	if (usb_set_configuration(dev, dev->config[0].bConfigurationValue)) {
		printk("<1>usb_set_configuration() failed");
		return NULL;
	}

	if(!(pl2302 = kmalloc(sizeof(struct pl2302), GFP_KERNEL))) {
		printk("<1>out of memory allocating device structure");
		return NULL;
	}

	usb_inc_dev_use( dev );
	memset(pl2302, 0, sizeof(struct pl2302));
	init_waitqueue_head( &pl2302->ctrl_wait );
	init_waitqueue_head( &pl2302->int_wait );

	net = init_etherdev( NULL, 0 );
	if ( !net ) {
		kfree( pl2302 );
		return	NULL;
	}
	
	pl2302->usb = dev;
	pl2302->net = net;
	net->priv = pl2302;
	net->open = pl2302_open;
	net->stop = pl2302_close;
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,48)
	net->watchdog_timeo = PL2302_TX_TIMEOUT;
	net->tx_timeout = pl2302_tx_timeout;
#endif
	net->hard_start_xmit = pl2302_start_xmit;
	net->set_multicast_list = pl2302_set_multicast;
	net->get_stats = pl2302_netdev_stats;
	net->mtu = PL2302_MTU;

	init_card( pl2302 );

	printk( "<1>Exit:Probe:%s\n", net->name );
	return pl2302;
}

static void pl2302_disconnect( struct usb_device *dev, void *ptr )
{
	struct pl2302 *pl2302 = ptr;

	printk( "<1>Disconnect\n" );
	if ( !pl2302 ) {
		printk("<1>unregistering non-existant device");
		return;
	}
	pl2302->pid = 0;
	
	unregister_netdev( pl2302->net );
	usb_dec_dev_use( dev );
	kfree( pl2302 );
	pl2302 = NULL;
}

static struct usb_driver pl2302_driver = {
	name:		"pl2302",
	probe:		pl2302_probe,
	disconnect:	pl2302_disconnect,
};

int __init pl2302_init(void)
{
	printk( "<1>Init:%s\n", version );
	return usb_register( &pl2302_driver );
}

void __exit pl2302_exit(void)
{
	printk( "<1>Exit\n" );
	usb_deregister( &pl2302_driver );
}

module_init( pl2302_init );
module_exit( pl2302_exit );