/* 	Copyright 2000 Be Incorporated. All Rights Reserved.
**	This file may be used under the terms of the Be Sample Code 
**	License.
*/

#include <OS.h>
#include <Drivers.h>
#include <KernelExport.h>
#include <stdlib.h>
#include <stdio.h>

#include <USB.h>
#include <USB_spec.h>

#define DEBUG_DRIVER 1

#if DEBUG_DRIVER
#define DPRINTF(x) dprintf x
#else
#define DPRINTF(x) ((void)0)
#endif

int32 api_version = B_CUR_DRIVER_API_VERSION;

///* ~44 kHz 16bit stereo */
//#define SAMPLE_SIZE	4  
//#define QS_OUT	(44*SAMPLE_SIZE*TS)
//
//#define TS	4	/* 4 ms buffers */
//#define NB	2
//
//int qs_out = QS_OUT;
//
typedef struct scannerdev scannerdev;
//typedef struct iso_channel iso_channel;	
//typedef struct iso_packet iso_packet;
//
//struct iso_packet
//{
//	struct iso_packet*		next;
//	struct iso_channel*		channel;	
//	void*					buffer;
//	uint32					status;
//	size_t					buffer_size;
//	rlea*					rle_array;
//};
//
//struct iso_channel
//{
//	usb_pipe*	ep;
//	
//	iso_packet*	next_to_queue;
//	iso_packet*	current_rw;
//	size_t		remain;
//	char*		buf_ptr;
//	sem_id		num_available_packets;
//	area_id		buffer_area;
//	iso_packet	iso_packets[NB];
//	
//	int active;
//};

struct scannerdev 
{
	scannerdev *next;
	
	int open;
	int number;
	int fake_fd;
	const usb_device *dev;
	usb_pipe *in, *out;
	// used to implement sync I/O
	sem_id lock;
	sem_id notify;
	area_id buffer_id;
	void *buffer;

	size_t actual;
	status_t status;
	
//	iso_channel	out_channel;
};

/* handy strings for referring to ourself */
#define ID "usb_scanner: "
static const char *drivername = "usb_scanner";
static const char *basename = "scanner/usb";

/* list of device instances and names for publishing */
static scannerdev *device_list = NULL;
static sem_id dev_list_lock = -1;
static int device_count = 0;

static char **device_names = NULL;

/* handles for the USB bus manager */
static char *usb_name = B_USB_MODULE_NAME;
static usb_module_info *usb;


/* Device addition and removal ---------------------------------------
**
** add_device() and remove_device() are used to create and tear down
** device instances.  They are driver by the callbacks device_added()
** and device_removed() which are invoked by the USB bus manager.
*/

static scannerdev * 
add_device(const usb_device *dev, const usb_configuration_info *conf)
{
	scannerdev *ad = NULL;
	int num,i,ifc,alt;
	const usb_interface_info *ii;
	status_t st;
	usb_pipe *in,*out;
	
	DPRINTF((ID "add_device(%p, %p)\n", dev, conf));

	num=0;	
//	if((num = get_number()) < 0) {
//		return NULL;
//	}
	
	for(ifc = 0; ifc < conf->interface_count; ifc++)
	{
		for(alt = 0; alt < conf->interface[ifc].alt_count; alt++) 
		{
			ii = &conf->interface[ifc].alt[alt];

//			/* does it have an AudioStreaming interface? */
//			if(ii->descr->interface_class != AC_AUDIO) continue;
//			if(ii->descr->interface_subclass != AC_AUDIOSTREAMING) continue;
//			if(ii->endpoint_count != 1) continue;
//			if(ii->endpoint_count!=2 || ii->endpoint_count!=3) continue;			
			/* ignore input endpoints */
//			if(ii->endpoint[0].descr->endpoint_address & 0x80) continue;
			
			/* does it support 2 channel, 16 bit audio? */
//			for(i = 0; i < ii->generic_count; i++){
//				usb_format_type_descr *ft = (usb_format_type_descr*) ii->generic[i];
//				if(ft->type != AC_CS_INTERFACE) continue;
//				if(ft->subtype != AC_FORMAT_TYPE) continue;
//				if(ft->length < sizeof(usb_format_type_descr)) continue;
//				if(ft->num_channels != 2) continue;
//				if(ft->subframe_size != 2) continue;
//				if(ft->sample_freq_type != 0) continue;
//				
//				dprintf(ID "found a 2ch, 16bit isoch output (ifc=%d, alt=%d, mp=%d)\n",
//						ifc, alt, ii->endpoint[0].descr->max_packet_size);
				
				goto got_one;
//			}
		}
	}
	
fail:
//	put_number(num);
	if(ad) free(ad);
	return NULL;

got_one:			
	if((ad = (scannerdev *) malloc(sizeof(scannerdev))) == NULL) goto fail;
	
	ad->dev = dev;
	ad->number = num;
	ad->open = 0;
	ad->fake_fd=-2;
	
	if((st = usb->set_alt_interface(dev, ii)) != B_OK) {
		dprintf(ID "set_alt_interface(0) returns %d\n", st);
		goto fail;
	}

	if((st = usb->set_configuration(dev,conf)) != B_OK) {
		dprintf(ID "set_configuration() returns %d\n", st);
		goto fail;
	}

	for(i=0; i<ii->endpoint_count;i++)
	{
		dprintf(ID "endpoint %ld, attr %ld, addr %ld", i,ii->endpoint[i].descr->attributes,ii->endpoint[i].descr->endpoint_address);
		if((ii->endpoint[i].descr->attributes==2) && (ii->endpoint[i].descr->endpoint_address & 0x80)) in = ii->endpoint[i].handle;
		if((ii->endpoint[i].descr->attributes==2) && ((ii->endpoint[i].descr->endpoint_address & 0x80)==0)) out = ii->endpoint[i].handle;		
	}
	ad->in=in;
	ad->out=out;	

	if((ad->lock = create_sem(1,"usb rawdev lock")) < 0) goto fail;
	
	if((ad->notify = create_sem(0,"usb rawdev notify")) < 0) goto fail;
	
	if((ad->buffer_id = create_area("usb rawdev area", &(ad->buffer),
								   B_ANY_KERNEL_ADDRESS, 65536, B_CONTIGUOUS,
								   B_READ_AREA | B_WRITE_AREA)) < 0) goto fail;
								   
	DPRINTF((ID "added %p (out=%p) (/dev/%s%d)\n", ad, out, basename, num));
	
//	if((st = usb->set_pipe_policy(out, NB, TS, SAMPLE_SIZE)) != B_OK){
//		dprintf(ID "set_pipe_policy(out) returns %d\n", st);
//		goto fail;
//	}
//
//	init_iso_channel(&ad->out_channel, out, qs_out, FALSE);
//	start_iso_channel_out(&ad->out_channel);

	/* add it to the list of devices so it will be published, etc */	
	acquire_sem(dev_list_lock);
	ad->next = device_list;
	device_list = ad;
	device_count++;
	release_sem(dev_list_lock);
	
	return ad;
}

static void 
remove_device(scannerdev *ad)
{
//	if(ad->out_channel.active) stop_iso_channel(&ad->out_channel);
//	uninit_iso_channel(&ad->out_channel);
//	put_number(ad->number);

//	usb->cancel_queued_transfers(ad->in); 
//	usb->cancel_queued_transfers(ad->out); 

	delete_sem(ad->lock);
	delete_sem(ad->notify);
	delete_area(ad->buffer_id);

	free(ad);
}

static status_t 
device_added(const usb_device *dev, void **cookie)
{
	const usb_configuration_info *conf;
	scannerdev *ad;
	int i;

	DPRINTF((ID "device_added(%p,...)\n", dev));

	if(conf = usb->get_nth_configuration(dev, 0)) 
	{
		if((ad = add_device(dev, conf)) != NULL)
		{
			*cookie = (void*) ad;
			return B_OK;
		}
	}
	return B_ERROR;
}


static status_t 
device_removed(void *cookie)
{
	scannerdev *ad = (scannerdev *) cookie;
	int i;

	DPRINTF((ID "device_removed(%p)\n",ad));
	
	acquire_sem(dev_list_lock);

	/* mark it as inactive and encourage IO to finish */	
//	ad->out_channel.active = 0;
//	delete_sem(ad->out_channel.num_available_packets);

	/* remove it from the list of devices */	
	if(ad == device_list){
		device_list = ad->next;
	} else {
		scannerdev *n;
		for(n = device_list; n; n = n->next){
			if(n->next == ad){
				n->next = ad->next;
				break;
			}
		}
	}
	device_count--;
	
	/* tear it down if it's not open -- 
	   otherwise the last device_free() will handle it */
		
	// remove the fake fd, to reach a real 0 count
	if(ad->fake_fd>=0) 
	{
		int fd=ad->fake_fd;
		ad->fake_fd=-1;
		close(fd);
	}
	
	if(ad->open == 0){
		remove_device(ad);
	} else {
		DPRINTF((ID "device /dev/%s%d still open -- marked for removal\n",
				 basename,ad->number));
	}
	
	release_sem(dev_list_lock);
	
	return B_OK;
}

/* Device Hooks -----------------------------------------------------------
**
** Here we implement the posixy driver hooks (open/close/read/write/ioctl)
*/

static status_t
device_open(const char *dname, uint32 flags, void **cookie)
{	
	scannerdev *ad=device_list;
	int n;
	
	n = atoi(dname + strlen(basename)); 

	DPRINTF((ID "device_open(\"%s\",%d,...)\n",dname,flags));
	
	acquire_sem(dev_list_lock);
	for(ad = device_list; ad; ad = ad->next)
	{
//		if(ad->number == n)
		{
//			if(ad->out_channel.active) 
			{
				ad->open++;
				*cookie = ad;	
				release_sem(dev_list_lock);
				return B_OK;
			} 
//			else 
//			{
//				dprintf(ID "device is going offline. cannot open\n");
//			}
		}
	}
	release_sem(dev_list_lock);
	return B_ERROR;
}
 
static status_t
device_close (void *cookie)
{
	scannerdev *ad = (scannerdev*) cookie;
	DPRINTF((ID "device_close() name = \"%s%d\"\n",basename,ad->number));
	return B_OK;
}

static status_t
device_free(void *cookie)
{
	scannerdev *ad = (scannerdev *) cookie;
	
	DPRINTF((ID "device_free() name = \"%s%d\"\n",basename,ad->number));
	
	acquire_sem(dev_list_lock);
	ad->open--;
	if((ad->open == 0) && (ad->fake_fd == -1)) 
	{
		remove_device(ad);
	}
	release_sem(dev_list_lock);
	
	return B_OK;
}

// I/O is async, this one releases the lock after the op has completed

void cb_control(void *cookie, uint32 status, void *data, uint32 actual_len)
{
	scannerdev *d = (scannerdev *) cookie;
	d->actual = actual_len;
	d->status = (status>0) ? -1 : 0;
	release_sem(d->notify);
}
				
static status_t
device_read(void *cookie, off_t pos, void *buf, size_t *count)
{
	scannerdev* ad = (scannerdev*) cookie; 
	status_t status;
	
	if(*count >65536) return(B_ERROR);
	
	acquire_sem(ad->lock);
//	memcpy(ad->buffer,buf,*count);
	
	DPRINTF((ID "device_read(%p,%Ld,0x%x,%d) name = \"%s%d\"\n",
			 cookie, pos, buf, *count, basename, ad->number));
//	snooze(50000);
	status=usb->queue_bulk(ad->in,ad->buffer,*count,cb_control,cookie);

	if(!status)
	{
		if(acquire_sem_etc(ad->notify,1,B_CAN_INTERRUPT|B_RELATIVE_TIMEOUT,100000000))
		{
			/* timeout or abort */
			usb->cancel_queued_transfers(ad->in);
			DPRINTF((ID "INTERRUPTED OR TIMEOUT\n"));
			acquire_sem(ad->notify);
			ad->actual=0;
		}
		DPRINTF((ID " data read(%ld)\n",ad->actual ));
		memcpy(buf,ad->buffer,ad->actual);
		*count = ad->actual;
	}
		release_sem(ad->lock);

	return ((ad->status>0) ? -1 : 0);
}

static status_t
device_write(void *cookie, off_t pos, const void *buf, size_t *count)
{
	scannerdev* ad = (scannerdev*) cookie; 
//	iso_channel* channel = &ad->out_channel;
	status_t status;

	if(*count >65536) return(B_ERROR);
	
	acquire_sem(ad->lock);
	memcpy(ad->buffer,buf,*count);
	
	DPRINTF((ID "device_write(%p,%Ld,0x%x,%d) name = \"%s%d\"\n",
			 cookie, pos, buf, *count, basename, ad->number));
//	snooze(50000);
	status=usb->queue_bulk(ad->out,ad->buffer,*count,cb_control,cookie);

	if(!status)
	{
		if(acquire_sem_etc(ad->notify,1,B_CAN_INTERRUPT|B_RELATIVE_TIMEOUT,100000000))
		{
			/* timeout or abort */
			usb->cancel_queued_transfers(ad->out);
			DPRINTF((ID "INTERRUPTED OR TIMEOUT\n"));
			acquire_sem(ad->notify);
			ad->actual=0;
		}
		DPRINTF((ID " data written(%ld)\n",ad->actual ));
//		memcpy((void *)buf,ad->buffer,ad->actual);
		*count = ad->actual;
	}
		release_sem(ad->lock);

	return ad->status;

//#if 0
//#endif
//		
//	if(channel->remain == 0) {
//		st = acquire_sem_etc(channel->num_available_packets, 1, B_RELATIVE_TIMEOUT, 4*1000*1000);
//		if(st) {
//			if(st == B_TIMED_OUT) {
//				dprintf("st = B_TIMED_OUT\n");
//				*count = 0;
//			}
//			return st;
//		}
//	
//		if(channel->current_rw == channel->next_to_queue) dprintf("e");
//		
//		channel->remain = channel->current_rw->buffer_size;
//		channel->buf_ptr = channel->current_rw->buffer;
//	}
//	
//	if(channel->remain >= *count) {
//		memcpy(channel->buf_ptr, buf, *count);
//		channel->remain -= *count;
//		channel->buf_ptr += *count;
//	} else {
//		memcpy(channel->buf_ptr, buf, channel->remain);
//		*count = channel->remain;
//		channel->remain = 0;
//		channel->buf_ptr = NULL;
//	}
//	
//	return B_OK;
}

static status_t
device_control (void *cookie, uint32 msg, void *arg1, size_t len)
{
	scannerdev* ad = (scannerdev*) cookie; 

	// if it's the first time through, open the fake fd used to avoid a 0
	// open count and so driver uniinitialization
	
	if(ad->fake_fd==-2) 
	{
		ad->fake_fd=open("/dev/scanner/usb/0",0);
	}

	if(msg==(B_DEVICE_OP_CODES_END+1))
	{
		// get vendor id
		const usb_device_descriptor *dd;
		
		dd= usb->get_device_descriptor(ad->dev);
		DPRINTF((ID "got vendor id %d\n",dd->vendor_id));
		memcpy(arg1,&(dd->vendor_id),2);
		
		return(B_OK);
	}
	
	return B_ERROR;
}


/* Driver Hooks ---------------------------------------------------------
**
** These functions provide the glue used by DevFS to load/unload
** the driver and also handle registering with the USB bus manager
** to receive device added and removed events
*/

static usb_notify_hooks notify_hooks = 
{
	&device_added,
	&device_removed
};

usb_support_descriptor supported_devices[] =
{
	/* Acer */
	{ 0, 0, 0, 0x04a5, 0x2060 },	/* Prisa Acerscan 620U & 640U (!)*/
	{ 0, 0, 0, 0x04a5, 0x2040 },	/* Prisa AcerScan 620U (!) */
	{ 0, 0, 0, 0x04a5, 0x2022 },	/* Vuego Scan Brisa 340U */
	/* Agfa */
	{ 0, 0, 0, 0x06bd, 0x0001 },	/* SnapScan 1212U */
	{ 0, 0, 0, 0x06bd, 0x0002 },	/* SnapScan 1236U */
	{ 0, 0, 0, 0x06bd, 0x2061 },	/* Another SnapScan 1212U (?)*/
	{ 0, 0, 0, 0x06bd, 0x0100 },	/* SnapScan Touch */
	/* Colorado -- See Primax/Colorado below */
	/* Epson -- See Seiko/Epson below */
	/* Genius */
	{ 0, 0, 0, 0x0458, 0x2001 },	/* ColorPage-Vivid Pro */
	/* Hewlett Packard */
	{ 0, 0, 0, 0x03f0, 0x0205 },	/* 3300C */
	{ 0, 0, 0, 0x03f0, 0x0101 },	/* 4100C */
	{ 0, 0, 0, 0x03f0, 0x0105 },	/* 4200C */
	{ 0, 0, 0, 0x03f0, 0x0102 },	/* PhotoSmart S20 */
	{ 0, 0, 0, 0x03f0, 0x0401 },	/* 5200C */
	{ 0, 0, 0, 0x03f0, 0x0701 },	/* 5300C */
	{ 0, 0, 0, 0x03f0, 0x0201 },	/* 6200C */
	{ 0, 0, 0, 0x03f0, 0x0601 },	/* 6300C */
	/* iVina */
	{ 0, 0, 0, 0x0638, 0x0268 },     /* 1200U */
	/* Microtek */
	{ 0, 0, 0, 0x05da, 0x0099 },	/* ScanMaker X6 - X6U */
	{ 0, 0, 0, 0x05da, 0x0094 },	/* Phantom 336CX - C3 */
	{ 0, 0, 0, 0x05da, 0x00a0 },	/* Phantom 336CX - C3 #2 */
	{ 0, 0, 0, 0x05da, 0x009a },	/* Phantom C6 */
	{ 0, 0, 0, 0x05da, 0x00a3 },	/* ScanMaker V6USL */
	{ 0, 0, 0, 0x05da, 0x80a3 },	/* ScanMaker V6USL #2 */
	{ 0, 0, 0, 0x05da, 0x80ac },	/* ScanMaker V6UL - SpicyU */
	/* Mustek */
	{ 0, 0, 0, 0x055f, 0x0001 },	/* 1200 CU */
	{ 0, 0, 0, 0x0400, 0x1000 },	/* BearPaw 1200 */
	{ 0, 0, 0, 0x055f, 0x0002 },	/* 600 CU */
	{ 0, 0, 0, 0x055f, 0x0003 },	/* 1200 USB */
	{ 0, 0, 0, 0x055f, 0x0006 },	/* 1200 UB */
	/* Primax/Colorado */
	{ 0, 0, 0, 0x0461, 0x0300 },	/* G2-300 #1 */
	{ 0, 0, 0, 0x0461, 0x0380 },	/* G2-600 #1 */
	{ 0, 0, 0, 0x0461, 0x0301 },	/* G2E-300 #1 */
	{ 0, 0, 0, 0x0461, 0x0381 },	/* ReadyScan 636i */
	{ 0, 0, 0, 0x0461, 0x0302 },	/* G2-300 #2 */
	{ 0, 0, 0, 0x0461, 0x0382 },	/* G2-600 #2 */
	{ 0, 0, 0, 0x0461, 0x0303 },	/* G2E-300 #2 */
	{ 0, 0, 0, 0x0461, 0x0383 },	/* G2E-600 */
	{ 0, 0, 0, 0x0461, 0x0340 },	/* Colorado USB 9600 */
	{ 0, 0, 0, 0x0461, 0x0360 },	/* Colorado USB 19200 */
	{ 0, 0, 0, 0x0461, 0x0341 },	/* Colorado 600u */
	{ 0, 0, 0, 0x0461, 0x0361 },	/* Colorado 1200u */
	/* Seiko/Epson Corp. */
	{ 0, 0, 0, 0x04b8, 0x0101 },	/* Perfection 636U and 636Photo */
	{ 0, 0, 0, 0x04b8, 0x0103 },	/* Perfection 610 */
	{ 0, 0, 0, 0x04b8, 0x0104 },	/* Perfection 1200U and 1200Photo*/
	{ 0, 0, 0, 0x04b8, 0x0106 },	/* Stylus Scan 2500 */
	{ 0, 0, 0, 0x04b8, 0x0107 },	/* Expression 1600 */
	/* Umax */
	{ 0, 0, 0, 0x1606, 0x0010 },	/* Astra 1220U */
	{ 0, 0, 0, 0x1606, 0x0030 },	/* Astra 2000U */
	{ 0, 0, 0, 0x1606, 0x0230 },	/* Astra 2200U */
	/* Visioneer */
	{ 0, 0, 0, 0x04a7, 0x0221 },	/* OneTouch 5300 USB */
	{ 0, 0, 0, 0x04a7, 0x0211 },	/* OneTouch 7600 USB */
	{ 0, 0, 0, 0x04a7, 0x0231 },	/* 6100 USB */
	{ 0, 0, 0, 0x04a7, 0x0311 },	/* 6200 EPP/USB */
	{ 0, 0, 0, 0x04a7, 0x0321 },	/* OneTouch 8100 EPP/USB */
	{ 0, 0, 0, 0x04a7, 0x0331 }		/* OneTouch 8600 EPP/USB */
};

_EXPORT status_t 
init_hardware(void)
{
	return B_OK;
}

_EXPORT status_t
init_driver(void)
{
	int i;
	DPRINTF((ID "init_driver(), built %s %s\n", __DATE__, __TIME__));
	
#if DEBUG_DRIVER
	if(load_driver_symbols(drivername) == B_OK) {
		DPRINTF((ID "loaded symbols\n"));
	} else {
		DPRINTF((ID "no symbols for you!\n"));
	}
#endif
			
	if(get_module(usb_name,(module_info**) &usb) != B_OK){
		dprintf(ID "cannot get module \"%s\"\n",usb_name);
		return B_ERROR;
	} 
	
	if((dev_list_lock = create_sem(1,"dev_list_lock")) < 0){
		put_module(usb_name);
		return dev_list_lock;
	}
	
	usb->register_driver(drivername, supported_devices, 55, NULL);
	usb->install_notify(drivername, &notify_hooks);
	
	return B_OK;
}

_EXPORT void
uninit_driver(void)
{
	int i;
	
	DPRINTF((ID "uninit_driver()\n"));
	
	usb->uninstall_notify(drivername);
	
	delete_sem(dev_list_lock);

	put_module(usb_name);

	if(device_names){
		for(i=0;device_names[i];i++) free(device_names[i]);
		free(device_names);
	}
	
}

_EXPORT const char**
publish_devices()
{
	scannerdev *ad;
	int i;
	
	DPRINTF((ID "publish_devices()\n"));
	
	if(device_names){
		for(i=0;device_names[i];i++) free((char *) device_names[i]);
		free(device_names);
	}
	
	acquire_sem(dev_list_lock);	
	device_names = (char **) malloc(sizeof(char*) * (device_count + 1));	
	if(device_names){
		for(i = 0, ad = device_list; ad; ad = ad->next){
//			if(ad->out_channel.active)
			{
				if(device_names[i] = (char *) malloc(strlen(basename) + 4)){
//					sprintf(device_names[i],"%s/%d",basename,ad->number);
					sprintf(device_names[i],"%s/0",basename);
					DPRINTF((ID "publishing: \"/dev/%s\"\n",device_names[i]));
					i++;
				}
			}
		}
		device_names[i] = NULL;
	}
	release_sem(dev_list_lock);
	
	return (const char **) device_names;
}

static device_hooks DeviceHooks = {
	device_open, 			
	device_close, 			
	device_free,			
	device_control, 		
	device_read,			
	device_write			 
};

_EXPORT device_hooks*
find_device(const char* name)
{
	return &DeviceHooks;
}
