;                            Software License Agreement
;
; The software supplied herewith by Microchip Technology Incorporated (the "Company")
; for its PICmicro® Microcontroller is intended and supplied to you, the Company’s
; customer, for use solely and exclusively on Microchip PICmicro Microcontroller
; products.
;
; The software is owned by the Company and/or its supplier, and is protected under
; applicable copyright laws. All rights are reserved. Any use in violation of the
; foregoing restrictions may subject the user to criminal sanctions under applicable
; laws, as well as to civil liability for the breach of the terms and conditions of
; this license.
;
; THIS SOFTWARE IS PROVIDED IN AN "AS IS" CONDITION. NO WARRANTIES, WHETHER EXPRESS,
; IMPLIED OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF
; MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. THE
; COMPANY SHALL NOT, IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL OR
; CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER.
;
; ###############################################################################
;	filename:		USB_CH9.ASM
;				USB Human Interface Device (HID) class specific
;				commands
;
; Implements the chapter 9 enumeration commands for Microchip's
; PIC16C7x5 parts.  
;
; Control is routed here when a token done interrupt is received and
; the bit 5 of the RequestType byte is set (0x20).  Prior to this, the
; EP0 buffer has been copied to common RAM.  Data in the 
;
; ###############################################################################
;
;    	Author:			Dan Butler
;    	Company:		Microchip Technology Inc
;
;	Revision:		1.10
;	Date:			07-December-2000
;	Assembled using		MPASM 2.30.07
;	Revision History:
;	23-August-2000	DZB	Changed descriptor pointers to 16 bits.
;	24-August-2000  DZB	Moved EP1 & 2 configuration from USBReset
;				to Set_Configuration to implement requirement in
;				USB V1.1 spec paragraph 5.3.1.2
;	28-August-2000  DZB	Force data toggle on OUT packets in PutUSB
;
;################################################################################
;
;	include files:
;		P16C745.inc	Rev 1.00
;		usb_defs.inc	Rev 1.00
;
;################################################################################

#include <p16C745.inc>
#include "usb_defs.inc"
	errorlevel -302 ; supress "register not in bank0, check page bits" message


#define  COUNTERRORS	; comment this out to reclaim space occupied by error counters
;#define  FUNCTIONIDS	; comment this out to reclaim space occupied by maintaining the USWSTAT register

unbanked	udata_shr	; these will get assigned to unbanked RAM (0x70-0x7F)
BufferDescriptor	res	3
BufferData		res	8
temp			res	1	; short term temp register used in Get Interface

	global	BufferDescriptor
	global	BufferData
	global	temp
	global	EP0_maxLength
	global	EP0_start
	global	EP0_end
	global	copy_string_descriptor_to_EP0


bank2	udata
USBMaskedInterrupts	res	1
USB_Curr_Config		res	1
USB_status_device	res	1	; status of device
USB_dev_req		res	1
USB_address_pending	res	1
USBMaskedErrors		res	1
PIDs			res	1
EP0_start		res	2	; pointer to first byte of data to send
EP0_end			res	2	; pointer to last byt of data to send
EP0_maxLength		res	1
bufindex		res	1
USB_Interface		res	3	; allow 3 interfaces to have alternate endpoints
inner			res	1
outer			res	1
dest_ptr		res	1	; used in buffer copies for Get and
source_ptr		res	1	; put USB calls
counter			res	1
bytecounter		res	1	; saved copy that will be returned in W
RP_save			res	1	; save bank bits while copying buffers
;EP_save			res	1
IS_IDLE			res	1
USB_USTAT		res	1	; copy of the USTAT register before clearing TOK_DNE

	global	USB_Curr_Config	
	global	USB_status_device
	global	USB_dev_req
	global	USB_Interface

#ifdef	COUNTERRORS
USB_PID_ERR		res	2	; 16 bit counters for each error condition
USB_CRC5_ERR		res	2
USB_CRC16_ERR		res	2
USB_DFN8_ERR		res	2
USB_BTO_ERR		res	2
USB_WRT_ERR		res	2
USB_OWN_ERR		res	2
USB_BTS_ERR		res	2
#endif

	extern	Config_desc_index
	extern	Descriptions
	extern	string_index
	extern	StringDescriptors
	extern	CheckVendor
	extern	SetConfiguration
	;extern	ClassSpecificRequest
	extern	GetClassSpecificDescriptor
	extern	Check_Class_Specific_IN
	extern	Get_Report_Descriptor



; **********************************************************************
; This section contains the functions to interface with the main 
; application.
; **********************************************************************

interface	code
; **********************************************************************
; Enter with Endpoint number in W reg and buffer pointer in IRP+FSR.  
; Checks the semaphore for the OUT endpoint, and copies the buffer 
; if available.  Restores the bank bits as we found them.
;
; This function can be assembled for Endpoint 1 or 2 by changing the 
; OUTENDPOINT variable below.
;
; Returns the bytecount in the W register and return status in the carry
; bit as follows:
; 0 - no buffer available,
; 1 - Buffer copied and buffer made available for next transfer.
;     Also, number of bytes moved is returned in W reg
; **********************************************************************
OUTENDPOINT	set	1
INENDPOINT	set	1
GetEP#V(OUTENDPOINT)
	global	GetEP#V(OUTENDPOINT)
	
	movf	STATUS,w
	banksel	RP_save		; Bank 2
	movwf	RP_save		; Reg page stored on page 2

	movf	FSR,w
	movwf	dest_ptr	; save the buffer destination pointer

	bsf	STATUS,RP0	; bank 3
	btfsc	BD#V(OUTENDPOINT)OST,UOWN	; Has the buffer been filled?
	goto	nobuffer	; nope, OWN = 1 ==> SIE owns the buffer
				; Yep: OWN = 0 ==> PIC owns the buffer

	movf	BD#V(OUTENDPOINT)OBC,w	; get byte count
	bcf	STATUS,RP0	; bank 2
	movwf	counter
	movwf	bytecounter	; # of bytes that will be moved
	btfsc	STATUS,Z	; is it a zero length buffer?
	goto	exitgetloop	; yes, bail out now and avoid the rush
	bsf	STATUS,RP0	; bank 2
	movf	BD#V(OUTENDPOINT)OAL,w	; get address pointer
	bcf	STATUS,RP0	; bank 2
	movwf	source_ptr

; This loop operates with the direct bank bits set to bank 2, while IRP
; gets switched as needed to for the buffer copy
getEPloop
	bsf	STATUS,IRP	; select high banks on INDF
	movf	source_ptr,w	; get source pointer
	movwf	FSR
	movf	INDF,w
	movwf	temp		; in common RAM to avoid paging issues

	movf	dest_ptr,w	
	movwf	FSR
	btfss	RP_save,IRP	; should it be zero?
	bcf	STATUS,IRP	; yes: make it so.
	movf	temp,w		; no, get the byte we read.
	movwf	INDF		; store it
	incf	dest_ptr,f
	incf	source_ptr,f
	decfsz	counter,f
	goto	getEPloop

exitgetloop
	bsf	STATUS,RP0	; bank 3
	movf	BD#V(OUTENDPOINT)OST,w
	andlw	0x40		; save only the data 0/1 bit
	xorlw	0x40		; toggle the data o/1 bit
	iorlw	0x88		; set owns bit and DTS bit
	movwf	BD#V(OUTENDPOINT)OST

	movlw	0x08		; reset byte counter
	movwf	BD#V(OUTENDPOINT)OBC
	bcf	STATUS,RP0	; bank 2
	movf	bytecounter,w	; return # of bytes moved in W reg
	movwf	temp		; need in unbanked RAM
	movf	RP_save,w	; restore bank bits
	movwf	STATUS
	movf	temp,w		; get bytecount from unbanked RAM
	bsf	STATUS,C	; signal success
	return

nobuffer
	movf	RP_save,w	; restore bank bits
	movwf	STATUS
	bcf	STATUS,C
	return

; ******************************************************************
; Enter with bytecount in W and buffer pointer in IRP+FSR.
; the bytecount is encoded in the lower nybble of W.
;
; This function can be assembled for Endpoint 1 or 2 by changing the 
; INENDPOINT variable below.
;
; Tests the owns bit for the IN side of the specified Endpoint.
; If we own the buffer, the buffer pointed to by the FSR is copied
; to the EPn In buffer, then the owns bit is set so the data will be
; TX'd next time polled.  
;
; Returns the status in the carry bit as follows:
; 1 - buffer available and copied.
; 0 - buffer not available (try again later)
; ******************************************************************

PutEP#V(INENDPOINT)
	global	PutEP#V(INENDPOINT)

	movwf	temp		; save Bytecount temporarily in common RAM

	movf	STATUS,w	; save bank bits before we trash them
	banksel	RP_save		; switch to bank 2
	movwf	RP_save

	movf	temp,w
	andlw	0x0F		; extract byte count.
	movwf	counter

	movf	FSR,w
	movwf	source_ptr

	movf	counter,w	; prepare to copy the byte count
	banksel	BD1IST		; bank 3
	btfsc	BD#V(INENDPOINT)IST,UOWN	; is the buffer already full?
	goto	nobufferputep1	; yes - don't write over it

	movwf	BD#V(INENDPOINT)IBC		; set byte count in BD
	btfsc	STATUS,Z	; is it a zero length buffer?
	goto	exitputloop	; yes, bail out now and avoid the rush
	movf	BD#V(INENDPOINT)IAL,w	; get address pointer
	bcf	STATUS,RP0	; back to bank 2
	movwf	dest_ptr

; This loop operates with the direct bits set to bank 2, while IRP
; gets switched as needed to for the buffer copy
PutEPloop
	bsf	STATUS,IRP	; assume IRP is set
	btfss	RP_save,IRP	; should it be zero?
	bcf	STATUS,IRP	; yes: make it so.
	movf	source_ptr,w
	movwf	FSR
	movf	INDF,w
	movwf	temp

	bsf	STATUS,IRP	; select high banks on INDF
	movf	dest_ptr,w
	movwf	FSR
	movf	temp,w		; no, get the byte we read.
	movwf	INDF		; store it

	incf	dest_ptr,f
	incf	source_ptr,f
	decfsz	counter,f
	goto	PutEPloop

exitputloop
	bsf	STATUS,RP0	; back to bank 3
	movf	BD#V(INENDPOINT)IST,w
	andlw	0x40		; save only the data 0/1 bit
	xorlw	0x40		; toggle the data o/1 bit
	iorlw	0x88		; set owns bit and DTS bit
	movwf	BD#V(INENDPOINT)IST
	movf	RP_save,w	; restore bank bits the way we found them
	movwf	STATUS
	bsf	STATUS,C	; set carry to show success
	return

nobufferputep1
	bcf	STATUS,C
	return

; *********************************************************************
; Stall Endpoint.
; Sets the stall bit in the Endpoint Control Register.  For the control
; Endpoint, this implements a Protocol stall and is used when the request
; is invalid for the current device state.  For non-control Endpoints,
; this is a Functional Stall, meaning that the device needs outside 
; intervention and trying again later won't help until it's been serviced.
; enter with endpoint # to stall in Wreg.
; *********************************************************************
StallUSBEP
	bsf	STATUS,IRP	; select banks 2/3
	andlw	0x03		; try to keep things under control
	addlw	low UEP0	; add address of endpoint control reg
	movwf	FSR
	bsf	INDF,EP_STALL	; set stall bit
	return

; *********************************************************************
; Unstall Endpoint.
; Sets the stall bit in the Endpoint Control Register.  For the control
; Endpoint, this implements a Protocol stall and is used when the request
; is invalid for the current device state.  For non-control Endpoints,
; this is a Functional Stall, meaning that the device needs outside 
; intervention and trying again later won't help until it's been serviced.
; enter with endpoint # to stall in Wreg.
; *********************************************************************
UnstallUSBEP
	bsf	STATUS,IRP	; select banks 2/3
	andlw	0x03		; try to keep things under control
	addlw	low UEP0	; add address of endpoint control reg
	movwf	FSR
	bcf	INDF,EP_STALL	; clear stall bit
	return

; *********************************************************************
; CheckSleep
; Checks the USB Sleep bit.  If the bit is set, the
; Endpoint, this implements a Protocol stall and is used when the request
; is invalid for the current device state.  For non-control Endpoints,
; this is a Functional Stall, meaning that the device needs outside 
; intervention and trying again later won't help until it's been serviced.
; enter with endpoint # to stall in Wreg.
; *********************************************************************
CheckSleep
	global	CheckSleep
	movf	STATUS,w	; save the register bank bits
	movwf	RP_save

	banksel	IS_IDLE
	btfss	IS_IDLE,0	; test the bus idle bit
	goto	nosleeptoday
	bsf	STATUS,RP0	; point to bank 3
	bcf	UIR,ACTIVITY
	bsf	UIE,ACTIVITY	; enable the USB activity interrupt
	bsf	UCTRL,SUSPND	; put USB regulator and transciever in low power state
	sleep			; and go to sleep
	nop
	bcf	UCTRL,SUSPND
	bcf	UIR,UIDLE
	bsf	UIE,UIDLE
	bcf	UIR,ACTIVITY
	bsf	UIE,ACTIVITY
	comf	PORTA,f
nosleeptoday
	movf	RP_save,w	; restore bank bits
	movwf	STATUS
	return

; *********************************************************************
; Remote Wakeup
; Checks USB_status_device to see if the host enabled Remote Wakeup
; If so, perform Remote wakeup and disable remote wakeup feature
; It is called by PortBChange
; *********************************************************************
RemoteWakeup
	global	RemoteWakeup
	movf	STATUS,w	; save the register bank bits
	bsf	STATUS,RP1
	bcf	STATUS,RP0
	movwf	RP_save
	
	btfss	USB_status_device, 1
	goto	RemoteEnd
	clrf	inner
	movlw	0xB0
	movwf	outer

	bsf	STATUS, RP0
	bsf	UCTRL, 2
	bcf	STATUS, RP0

	incfsz	inner,f
	goto	$-1
	incfsz	outer,f
	goto	$-3

	bsf	STATUS,RP0
	bcf	UCTRL, 2
	bcf	STATUS,RP0
	bcf	USB_status_device, 1
	
RemoteEnd
	movf	RP_save,w	; restore bank bits
	movwf	STATUS
	return


; *********************************************************************
; USB Soft Detach
; Clears the DEV_ATT bit, electrically disconnecting the device to the bus.
; This removes the device from the bus, then reconnects so it can be
; re-enumerated by the host.  This is envisioned as a last ditch effort
; by the software.
; *********************************************************************
SoftDetachUSB
	banksel	UCTRL
	bcf	UCTRL,DEV_ATT	; set attach bit

	banksel	outer
	clrf	outer
OuterLoop
	clrwdt			; clear the watchdog timer
	clrf	inner
InnerLoop
	incfsz	inner,f
	goto	InnerLoop
	incfsz	outer,f
	goto	OuterLoop

	pagesel	InitUSB
	call	InitUSB		; reinitialize the USB peripheral
	return

core	code
; The functions below are the core functions
; ******************************************************************
; USB interrupt triggered, OK, why?
; OK, we've identified that we're servicing a USB interrupt.
; ******************************************************************
ServiceUSBInt
	global	ServiceUSBInt

	bcf	PIR1,USBIF	; clear USB interrupt flag
	banksel	UIR		; select bank 3
#ifdef FUNCTIONIDS
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	SVCUSBINT	;
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif

	movf	UIR,w		; get the USB interrupt register
	andwf	UIE,w		; mask off the disabled interrupts
	banksel	USBMaskedInterrupts	; select bank 2
	movwf	USBMaskedInterrupts

	btfsc	USBMaskedInterrupts,TOK_DNE	; was it a token done?
	goto	TokenDone
	btfsc	USBMaskedInterrupts,USB_RST
	goto	USBReset
	btfsc	USBMaskedInterrupts,STALL
	goto	USBStall
	btfsc	USBMaskedInterrupts,UERR
	goto	USBError
	btfsc	USBMaskedInterrupts,UIDLE
	goto	USBSleep
	btfsc	USBMaskedInterrupts,ACTIVITY
	goto	USBActivity
	return

; ******************************************************************
; USB Reset interrupt triggered (SE0)
; initialize the Buffer Descriptor Table,
; Transition to the DEFAULT state,
; Set address to 0
; enable the USB
; ******************************************************************
USBReset
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	SVCRESET	;
	iorwf	USWSTAT,f	; place in upper 6 bits of register
	banksel	USB_Curr_Config	; select bank 3
#endif
	clrf	USB_Curr_Config
	clrf	IS_IDLE
	banksel	UIR		; select bank 3

	bcf	UIR,TOK_DNE	; hit this 4 times to clear out the
	bcf	UIR,TOK_DNE	; USTAT FIFO
	bcf	UIR,TOK_DNE	
	bcf	UIR,TOK_DNE	

	movlw	0x88		; set owns bit (SIE can write)
	movwf	BD0OST
	movlw	USB_Buffer	; Endpoint 0 OUT gets a buffer
	movwf	BD0OAL		; set up buffer address
	movlw	8
	movwf	BD0OBC		; set byte count

	movlw	0x08		; Clear owns bit (PIC can write)
	movwf	BD0IST
	movlw	USB_Buffer+8	; Endpoint 0 OUT gets a buffer
	movwf	BD0IAL		; set up buffer address
;	movlw	8		; no need to do these here... Byte Count gets
;	movwf	BD0OBC		; set in when the buffer is filled.

	clrf	UADDR		; set USB Address to 0
	bcf	UIR,USB_RST	; clear reset flag

; Set up the Endpoint Control Registers.  The following patterns are defined
; ENDPT_DISABLED - endpoint not used
; ENDPT_IN_ONLY  - endpoint supports IN transactions only
; ENDPT_OUT_ONLY - endpoint supports OUT transactions only
; ENDPT_CONTROL	 - Supports IN, OUT and CONTROL transactions - Only use with EP0
; ENDPT_NON_CONTROL - Supports both IN and OUT transactions

	movlw	ENDPT_CONTROL
	movwf	UEP0		; endpoint 0 is a control pipe and requires an ACK

	movlw	0x3B		; enable all interrupts except activity
	movwf	UIE
	movlw	0xFF		; enable all error interrupts
	movwf	UEIE

	movlw	0xfc
	andwf	USWSTAT,f	; save high order 6 bits
	movlw	DEFAULT_STATE
	iorwf	USWSTAT,f

	bcf	STATUS,RP0	; select bank 2
	bcf	STATUS,RP1	; bank 0
	return			; to keep straight with host controller tests

; ******************************************************************
; Enable Wakeup on interupt and Activity interrupt then put the 
; device to sleep to save power.  Activity on the D+/D- lines will
; set the ACTIVITY interrupt, waking up the part.
; ******************************************************************
USBSleep
	banksel	UIR		; select bank 3
#ifdef FUNCTIONIDS
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	SVCSLEEP
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif
	bcf	UIR,UIDLE
	clrf	STATUS		; bank 0

	banksel	IS_IDLE
	bsf	IS_IDLE, 0

	return

; ******************************************************************
; This is activated by the STALL bit in the UIR register.  It really
; just tells us that the SIE sent a STALL handshake.  So far, Don't 
c; see that any action is required.  Clear the bit and move on.
; ******************************************************************
USBStall
	banksel	UIR		; select bank 3
#ifdef FUNCTIONIDS
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	SVCSTALL
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif
	bcf	UIR,STALL	; clear the stall bit
	clrf	STATUS		; bank 0
	return

 
; ******************************************************************
; The SIE detected an error.  This code increments the appropriate 
; error counter and clears the flag.
; ******************************************************************
USBError
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	SVCERROR
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif
#ifdef COUNTERRORS
	movf	UEIR,w		; get the error register
	andwf	UEIE,w		; mask with the enables
	clrf	UEIR
	bcf	STATUS,RP0	; bank 2
	movwf	USBMaskedErrors

	btfss	USBMaskedErrors,PID_ERR
	goto	CRC5Error
	INCREMENT16	USB_PID_ERR
CRC5Error
	btfss	USBMaskedErrors,CRC5
	goto	CRC16Error
	INCREMENT16	USB_CRC5_ERR
CRC16Error
	btfss	USBMaskedErrors,CRC16
	goto	DFN8Error
	INCREMENT16	USB_CRC16_ERR
DFN8Error
	btfss	USBMaskedErrors,DFN8
	goto	BTOError
	INCREMENT16	USB_DFN8_ERR
BTOError
	btfss	USBMaskedErrors,BTO_ERR
	goto	WRTError
	INCREMENT16	USB_BTO_ERR
WRTError
	btfss	USBMaskedErrors,WRT_ERR
	goto	OWNError
	INCREMENT16	USB_WRT_ERR
OWNError
	btfss	USBMaskedErrors,OWN_ERR
	goto	BTSError
	INCREMENT16	USB_OWN_ERR
BTSError
	btfss	USBMaskedErrors,BTS_ERR
	goto	EndError
	INCREMENT16	USB_BTS_ERR
EndError
#endif 
	bsf	STATUS,RP0	; bank 3
	bcf	UIR,UERR
	clrf	STATUS		; bank 0
	return

; ******************************************************************
; Service the Activity Interrupt.  This is only enabled when the
; device is put to sleep as a result of inactivity on the bus.  This
; code wakes up the part, disables the activity interrupt and reenables
; the idle interrupt.
; ******************************************************************
USBActivity
	banksel	UIR		; select bank 3
#ifdef FUNCTIONIDS
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	SVCACTIVITY
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif
	bcf	UIR,ACTIVITY
	bcf	UIE,ACTIVITY
	bcf	UIR,UIDLE
	bsf	UIE,UIDLE
	banksel	IS_IDLE
	clrf	IS_IDLE

	clrf	STATUS		; bank 0

	return

; ******************************************************************
; Process token done interrupt...  Most of the work gets done through
; this interrupt.  Token Done is signaled in response to an In, Out,
; or Setup transaction.
; ******************************************************************
TokenDone
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	SVCTOKENDONE
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif
	COPYBUFFERDESCRIPTOR	; copy BD from dual port to unbanked RAM

	banksel	USTAT		; select bank 3
	movf	USTAT,w		; copy USTAT register before...
	bcf	UIR,TOK_DNE	; clearing the token done interrupt.
	bcf	STATUS,RP0	; bank 2
	movwf	USB_USTAT	; Save USTAT in bank 2

; check UOWN bit here if desired
	movf	BufferDescriptor,w	; get the first byte of the BD
	andlw	0x3c		; save the PIDs
	movwf	PIDs

	xorlw	TOKEN_IN
	btfsc	STATUS,Z
	goto	TokenInPID	

	movf	PIDs,w
	xorlw	TOKEN_OUT
	btfsc	STATUS,Z	
	goto	TokenOutPID

	movf	PIDs,w
	xorlw	TOKEN_SETUP
	btfsc	STATUS,Z	
	goto	TokenSetupPID

	return			; should never get here...

; ******************************************************************
; Process out tokens
; For EP0, just turn the buffer around.  There should be no EP0 
; tokens to deal with.
; EP1 and EP2 have live data destined for the application
; ******************************************************************
TokenOutPID
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	TOKENOUT
	iorwf	USWSTAT,f	; place in upper 6 bits of register
	banksel	USB_USTAT	; select bank 3
#endif
	movf	USB_USTAT,w	; get the status register
	btfss	STATUS,Z	; was it EP0?
	goto	tryEP1		; no, try EP1

	movlw	0x08		; it's EP0.. buffer already copied,
	movwf	BD0OBC		; just reset the buffer
	movlw	0x88		
	movwf	BD0OST		; set OWN and DTS Bit
	bcf	STATUS,RP0	; bank 2

	movf	USB_dev_req,w
	xorlw	VEND_SET_MEMORY
	btfss	STATUS,Z
	return

tryEP1	bcf	PORTA,0
	xorlw	0x08		; was it EP1?
	btfss	STATUS,Z	
	goto	tryEP2

	bcf	STATUS,RP0	; back to bank2
; **** Add Callout here to service EP1 in transactions.  ****

	return

tryEP2
	movf	USB_USTAT,w
	xorlw	0x10		; was it EP2?
	btfsc	STATUS,Z	
	return			; unrecognized EP (Should never take this exit)
; **** Add Callout here to service EP2 in transactions.  ****
	return

; ******************************************************************
; Process in tokens
; ******************************************************************
TokenInPID	
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	TOKENIN
	iorwf	USWSTAT,f	; place in upper 6 bits of register
	banksel	USB_USTAT	; select bank 3
#endif
; Assumes EP0 vars are setup in a previous call to setup.  
EP0_in
	movf	USB_USTAT,w		; get the status register
	andlw	0x18		; save only EP bits (we already know it's an IN)
	btfss	STATUS,Z	; was it EP0?
	goto	tryEP1in	; no, try EP1

	movf	USB_dev_req,w
	xorlw	GET_DESCRIPTOR
	btfss	STATUS,Z
	goto	check_GSD
	call	copy_descriptor_to_EP0
	goto	exitEP0in

; Check for Get String Descriptor
check_GSD
	movf	USB_dev_req,w
	xorlw	GET_STRING_DESCRIPTOR
	btfss	STATUS,Z
	goto	check_SA
	call	copy_string_descriptor_to_EP0
	goto	exitEP0in

; Check for Set Address
check_SA
	movf	USB_dev_req,w
	xorlw	SET_ADDRESS
	btfss	STATUS,Z
	goto	check_SF
	call	finish_set_address
	goto	exitEP0in

check_SF
	movf	USB_dev_req,w
	xorlw	SET_FEATURE
	btfss	STATUS,Z
	goto	check_CF
	call	Send_0Len_pkt
	goto	exitEP0in

check_CF
	movf	USB_dev_req,w
	xorlw	CLEAR_FEATURE
	btfss	STATUS,Z
	goto	Class_Specific
	call	Send_0Len_pkt
	goto	exitEP0in

Class_Specific
	pagesel	Check_Class_Specific_IN
	goto	Check_Class_Specific_IN

exitEP0in
	return


; ******************************************************************
; though not required, it might be nice to have a callback function here
; that would take some action like setting up the next buffer when the
; previous one is complete.  Not necessary because the same functionality
; can be provided through the PutUSB call.  
; ******************************************************************
tryEP1in
	xorlw	0x08		; was it EP1?
	btfss	STATUS,Z	
	goto	tryEP2in
	bcf	STATUS,RP0	; back to bank2

	return

tryEP2in
	return
; ******************************************************************
; Return a zero length packet on EP0 In
; ******************************************************************
Send_0Len_pkt

	banksel	BD0IBC		; select bank 3
#ifdef FUNCTIONIDS
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	ZEROLENPACKET
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif
	global	Send_0Len_pkt
	clrf	BD0IBC		; set byte count to 0
	movlw	0xc8
	movwf	BD0IST		; set owns bit
	bcf	STATUS,RP0	; back to bank 2
	clrf	USB_dev_req
	return

; ********************************************************************
; process setup tokens
; ******************************************************************
TokenSetupPID
	banksel	USWSTAT		; select bank 3
#ifdef FUNCTIONIDS
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	TOKENSETUP
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif
	bsf	STATUS,IRP	; indirectly to pages 2/3
	movf	BufferDescriptor+ADDRESS,w		; get the status register
	movwf	FSR		; save in the FSR.
	movf	INDF,w
	movwf	BufferData	; in shared RAM
	incf	FSR,f
	movf	INDF,w
	movwf	BufferData+1
	incf	FSR,f
	movf	INDF,w
	movwf	BufferData+2
	incf	FSR,f
	movf	INDF,w
	movwf	BufferData+3
	incf	FSR,f
	movf	INDF,w
	movwf	BufferData+4
	incf	FSR,f
	movf	INDF,w
	movwf	BufferData+5
	incf	FSR,f
	movf	INDF,w
	movwf	BufferData+6
	incf	FSR,f
	movf	INDF,w
	movwf	BufferData+7

	movlw	0x88		; set UOWNs bit back to SIE
	movwf	BD0OST
	movlw	0x08
	movwf	BD0OBC		; reset the byte count too.
	movwf	BD0IST		; return the in buffer to us (dequeue any pending requests)

	bcf	UCTRL,PKT_DIS	; Assuming there is nothing to dequeue, clear the packet disable bit

	bcf	STATUS,RP0	; bank 2
	clrf	USB_dev_req	; clear the device request..

	movf	BufferData+bmRequestType,w
	xorlw	0x00		; test for host to device tokens
	btfsc	STATUS,Z
	goto	HostToDevice

	movf	BufferData+bmRequestType,w
	xorlw	0x01		; test for host to Interface tokens
	btfsc	STATUS,Z
	goto	HostToInterface

	movf	BufferData+bmRequestType,w
	xorlw	0x02		; test for host to Endpoint tokens
	btfsc	STATUS,Z
	goto	HostToEndpoint

	movf	BufferData+bmRequestType,w
	xorlw	0x80		; test for device to Host tokens
	btfsc	STATUS,Z
	goto	DeviceToHost

	movf	BufferData+bmRequestType,w
	xorlw	0x81		; test for device to Interface tokens
	btfsc	STATUS,Z
	goto	InterfaceToHost

	movf	BufferData+bmRequestType,w
	xorlw	0x82		; test for device to Endpoint tokens
	btfsc	STATUS,Z
	goto	EndpointToHost

	pagesel	GetClassSpecificDescriptor
	movf	BufferData+bmRequestType,w
	andlw	0x60		; mask off type bits
	xorlw	0x20		; test for class specific
	btfsc	STATUS,Z	; was it a standard request?
	goto	CheckForVendorRequest
	goto	GetClassSpecificDescriptor	; nope, see if it was a class specific request

CheckForVendorRequest
	movf	BufferData+bmRequestType,w
	andlw	0x60		; mask off type bits
	xorlw	0x40		; test for vendor specific
	btfss	STATUS,Z	; was it a standard request?
	goto	CheckForStandardRequest
	pagesel	CheckVendor
	goto	CheckVendor	; nope, see if it was a vendor specific
	return
				; see file USB_VEND.ASM for code.
; now test bRequest to see what the request was.

CheckForStandardRequest
; bmRequestType told us it was a Host to Device transfer.  Now look at
; the specifics to see what's up
HostToDevice
	movf	BufferData+bRequest,w	; what was our request
	xorlw	CLEAR_FEATURE
	btfsc	STATUS,Z
	goto	Clear_Device_Feature

	movf	BufferData+bRequest,w	; was our request Set Address
	xorlw	SET_ADDRESS
	btfsc	STATUS,Z
	goto	Set_Address

	movf	BufferData+bRequest,w	; was our request Set Configuration
	xorlw	SET_CONFIGURATION
	btfsc	STATUS,Z
	goto	Set_Configuration

	movf	BufferData+bRequest,w	; was our request Set Feature
	xorlw	SET_FEATURE
	btfsc	STATUS,Z
	goto	Set_Device_Feature
	goto	wrongstate

HostToInterface
	movf	BufferData+bRequest,w	; what was our request
	xorlw	CLEAR_FEATURE
	btfsc	STATUS,Z
	goto	Clear_Interface_Feature

	movf	BufferData+bRequest,w	; was our request Set Interface
	xorlw	SET_INTERFACE
	btfsc	STATUS,Z
	goto	Set_Interface

	movf	BufferData+bRequest,w	; was our request Set Feature
	xorlw	SET_FEATURE
	btfsc	STATUS,Z
	goto	Set_Interface_Feature

HostToEndpoint
	movf	BufferData+bRequest,w	; what was our request
	xorlw	CLEAR_FEATURE
	btfsc	STATUS,Z
	goto	Clear_Endpoint_Feature

	movf	BufferData+bRequest,w	; was our request Set Feature
	xorlw	SET_FEATURE
	btfsc	STATUS,Z
	goto	Set_Endpoint_Feature

DeviceToHost
	movf	BufferData+bRequest,w	; what was our request
	xorlw	GET_CONFIGURATION
	btfsc	STATUS,Z
	goto	Get_Configuration

	movf	BufferData+bRequest,w	; was our request Get Decriptor?
	xorlw	GET_DESCRIPTOR
	btfsc	STATUS,Z
	goto	Get_Descriptor

	movf	BufferData+bRequest,w	; was our request Get Status?
	xorlw	GET_STATUS
	btfsc	STATUS,Z
	goto	Get_Device_Status

InterfaceToHost
	movf	BufferData+bRequest,w	; was our request Get Interface?
	xorlw	GET_INTERFACE
	btfsc	STATUS,Z
	goto	Get_Interface

	movf	BufferData+bRequest,w	; was our request Get Status?
	xorlw	GET_STATUS
	btfsc	STATUS,Z
	goto	Get_Interface_Status

	movf	BufferData+bRequest,w	; was our request Get Decriptor?
	xorlw	GET_DESCRIPTOR
	btfsc	STATUS,Z
	goto	Get_Descriptor

EndpointToHost
	movf	BufferData+bRequest,w	; was our request Get Status?
	xorlw	GET_STATUS
	btfsc	STATUS,Z
	goto	Get_Endpoint_Status

	pagesel	wrongstate		; unrecognised token, stall EP0
	goto	wrongstate

	return

; ******************************************************************
; Get Descriptor
; Handles the three different Get Descriptor commands
; ******************************************************************
Get_Descriptor
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	GETDESCRIPTOR
	iorwf	USWSTAT,f	; place in upper 6 bits of register
	bcf	STATUS,RP0	; select bank 2
#endif
	btfss	BufferData+(wValue+1),5	; get descriptor type
	goto	GetCh9Descriptor
	pagesel	GetClassSpecificDescriptor
	goto	GetClassSpecificDescriptor

	pagesel	Get_Report_Descriptor	; this tests for an 81 06 00 22 
	movf	BufferData+(wValue+1),w	; request, which seems to be undefined,
	xorlw	0x22			; but it won't enumerate without it
	btfsc	STATUS,Z
	goto	Get_Report_Descriptor

GetCh9Descriptor
	movlw	high StartGDIndex ; set up PCLATH with the current address
	movwf	PCLATH		; set up pclath for the computed goto
	movf	BufferData+(wValue+1),w	; get descriptor type
	andlw	0x03		; minor attempt to keep things under control
	addlw	low  StartGDIndex
	btfsc	STATUS,C	; was there an overflow?
	incf	PCLATH,f	; yes, bump PCLATH
	movwf	PCL		; adjust PC
StartGDIndex
	goto	wrongstate		; 0
	goto	Get_Device_Descriptor	; 1
	goto	Get_Config_Descriptor	; 2
	goto	Get_String_Descriptor	; 3
;	goto	Get_Interface_Descriptor; 4 - not used
;	goto	Get_EndPoint_Descriptor	; 5 - not used
;	goto	wrongstate		; 6
;	goto	wrongstate		; 7


; *********************************************************************
; Looks up the offset of the device descriptor via the low order byte
; of wValue.  The pointers are set up and the data is copied to the 
; buffer, then the flags are set.
;
; EP0_start points to the first word to transfer
; EP0_end points to the last, limited to the least of the message length
;         or the number of bytes requested in the message (wLength).
; EP0_maxLength is the number of bytes to transfer at a time, 8 bytes
; ******************************************************************
Get_Device_Descriptor
	pagesel	Descriptions	; set up PCLATH for call below
	movlw	GET_DESCRIPTOR
	movwf	USB_dev_req		; currently processing a get descriptor request

	movlw	8
	movwf	EP0_maxLength

	movf	BufferData+wValue,w	; get descriptor index
	movwf	EP0_start
	clrf	EP0_start+1		; clear high order byte
	call	Descriptions 		; get length of device descriptor
	movwf	EP0_end			; save length
	clrf	EP0_end+1

	movf	BufferData+(wLength+1),f ; move it to itself, check for non zero.
	btfss	STATUS,Z		; if zero, we need to compare EP0_end to requested length.
	goto	DeviceEndPtr		; if not, no need to compare.  EP0_end is shorter than request length

	subwf	BufferData+wLength,w	; compare against requested length
	btfss	STATUS,C
	movwf	EP0_end			; save it.
DeviceEndPtr
	movf	EP0_start,w
	addwf	EP0_end,f

	pagesel	copy_descriptor_to_EP0
	call	copy_descriptor_to_EP0

	return
	    

; *********************************************************************
; Looks up the offset of the config descriptor via the low order byte
; of wValue.  The pointers are set up and the data is copied to the 
; buffer, then the flags are set.
;
; EP0_start points to the first word to transfer
; EP0_end points to the last, limited to the least of the message length
;         or the number of bytes requested in the message (wLength).
; EP0_maxLength is the number of bytes to transfer at a time, 8 bytes
; ******************************************************************
Get_Config_Descriptor
	pagesel	Descriptions		; set up PCLATH for call below
	movlw	GET_DESCRIPTOR
	movwf	USB_dev_req		; currently processing a get descriptor request

	movf	BufferData+wValue,w	; get descriptor index
	call	Config_desc_index	; translate index to offset into descriptor table
	movwf	EP0_start
	movf	BufferData+wValue,w
	addlw	1			; point to high order byte
	call	Config_desc_index	; translate index to offset into descriptor table
	movwf	EP0_start+1

	movlw	2			; bump pointer by to to get the complete descriptor 
	addwf	EP0_start,f		; length, not just config descriptor
	btfsc	STATUS,C
	incf	EP0_start+1,f
	call	Descriptions 		; get length of the config descriptor
	movwf	EP0_end			; Get message length

	movlw	2			; move EP0_start pointer back to beginning
	subwf	EP0_start,f
	btfss	STATUS,C
	decf	EP0_start+1,f

	movf	EP0_end+1,w
	subwf	BufferData+(wLength+1),w ; Compare high order bytes
	btfsc	STATUS,Z
	goto	CmpLowerByte		; Compare lower byte when equal
	btfsc	STATUS,C		; if zero, we need to compare EP0_end to requested length.
	goto	ConfigEndPtr		; if not, no need to compare.  EP0_end is shorter than request length
	goto	LimitSize		

CmpLowerByte
	movf	EP0_end,w
	subwf	BufferData+wLength,w	; compare against requested length
	btfsc	STATUS,C
	goto	ConfigEndPtr
LimitSize
	movf	BufferData+wLength,w	; if requested length is shorter..
	movwf	EP0_end			; save it.
	movf	BufferData+(wLength+1),w  ; if requested length is shorter..
	movwf	EP0_end+1		; save it.
ConfigEndPtr
	clrf	EP0_end+1		; clear high order byte
	movf	EP0_start,w		; add start address to convert length
	addwf	EP0_end,f		; to end pointer
	btfsc	STATUS,C		; did it overflow?
	incf	EP0_end,f
	movf	EP0_start+1,w
	addwf	EP0_end+1,f

	movlw	8
	movwf	EP0_maxLength

	pagesel	copy_descriptor_to_EP0
	call	copy_descriptor_to_EP0
	return

; ******************************************************************
; Set up to return String descriptors
; Looks up the offset of the string descriptor via the low order byte
; of wValue.  The pointers are set up and the data is copied to the 
; buffer, then the flags are set.
; ******************************************************************
Get_String_Descriptor
	pagesel	StringDescriptors
	movlw	GET_STRING_DESCRIPTOR
	movwf	USB_dev_req		; currently processing a get descriptor request

	movf	BufferData+wLength,w
	movwf	EP0_maxLength	

	rlf	BufferData+wValue,w	; get descriptor index * 2
	call	string_index		; translate index to offset into
	movwf	EP0_start		; descriptor table
	addlw	1			; point to high order byte
	call	string_index		; translate index to offset into
	movwf	EP0_start+1		; descriptor table

	call	StringDescriptors 	; get length of the config descriptor
	movwf	EP0_end			; save length
	subwf	BufferData+wLength,w	; compare against requested length
	movf	BufferData+wLength,w	; if requested length is shorter..
	btfss	STATUS,C
	movwf	EP0_end			; save it.

	movlw	8			; each transfer may be 8 bytes long
	movwf	EP0_maxLength

	clrf	EP0_end+1		; clear high order byte
	movf	EP0_start,w		; add low order bytes
	addwf	EP0_end,f
	btfsc	STATUS,C		; did it overflow?
	incf	EP0_end+1,f		; add high order bytes
	movf	EP0_start+1,w
	addwf	EP0_end+1,f

	pagesel	copy_string_descriptor_to_EP0
	call	copy_string_descriptor_to_EP0
	return

; ******************************************************************
; Stalls the EP0 endpoint to signal that the command was not recognised.
; This gets reset as the result of a Setup Transaction.
; ******************************************************************
wrongstate
	global	wrongstate

	banksel	BD0IST
;	bsf	BD0IST,BSTALL	; set endpoint stall bit
	bsf	UEP0,EP_STALL
	bcf	STATUS,RP0	; back to page 2

	return

; ******************************************************************
; Loads the device status byte into the EP0 In Buffer.
; ******************************************************************
Get_Device_Status
	banksel	BD0IAL		; select bank 3
#ifdef FUNCTIONIDS
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	GETSTATUS
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif
	movf	BD0IAL,w	; get buffer pointer
	movwf	FSR
	banksel	USB_status_device ; select bank 2
	bsf	STATUS,IRP	; select indirectly banks 2-3
	movf	USB_status_device,w	; get device status byte
	movwf	INDF
	incf	FSR,f
	clrf	INDF

	banksel	BD0IBC		; select bank 3
	movlw	0x02
	movwf	BD0IBC		; set byte count to 2
	movlw	0xC8
	movwf	BD0IST		; Data 1 packet, set owns bit
	bcf	STATUS,RP0	; select bank 2
	return

; ******************************************************************
; A do nothing response.  Always returns a two byte record, with all
; bits zero.
; ******************************************************************
Get_Interface_Status
	banksel	BD0IAL		; select bank 3
#ifdef FUNCTIONIDS
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	GETSTATUS
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif
	movf	BD0IAL,w	; get buffer pointer
	movwf	FSR
	clrf	INDF
	incf	FSR,f
	clrf	INDF

	movlw	0x02
	movwf	BD0IBC		; set byte count to 2
	movlw	0xC8
	movwf	BD0IST		; Data 1 packet, set owns bit
	bcf	STATUS,RP0	; select bank 2
	return

; ******************************************************************
; Returns the Endpoint stall bit via a 2 byte in buffer
; ******************************************************************
Get_Endpoint_Status
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	GETSTATUS
	iorwf	USWSTAT,f	; place in upper 6 bits of register
	bcf	STATUS,RP0
#endif
	banksel	UEP0		; select bank 3
	movlw	0x0f
	andwf	BufferData+wIndex,w	; get endpoint, strip off direction bit
	xorlw	0x01		; is it EP1?
	btfsc	STATUS,Z
	goto	get_EP1_status

	movlw	0x0f
	andwf	BufferData+wIndex,w	; get endpoint, strip off direction bit
	xorlw	0x02		; is it EP2?
	btfss	STATUS,Z
	goto	wrongstate

get_EP2_status
	bcf	STATUS,C
	btfsc	UEP2,EP_STALL
	bsf	STATUS,C
	goto	build_status_buffer

get_EP1_status
	bcf	STATUS,C
	btfsc	UEP1,EP_STALL
	bsf	STATUS,C
		; 
build_status_buffer
	bsf	STATUS,IRP	; point to bank 3
	movf	BD0IAL,w	; get address of buffer
	movwf	FSR
	clrf	INDF		; clear byte 0 in buffer
	rlf	INDF,f		; rotate in carry bit (EP_stall bit)
	incf	FSR,f		; bump pointer
	clrf	INDF		; clear byte

	movlw	0x02
	movwf	BD0IBC		; set byte count to 2
	movlw	0xC8
	movwf	BD0IST		; Data 1 packet, set owns bit
	bcf	STATUS,RP0	; select bank 2
	return


; *********************************************************************
; The low order byte of wValue now has the new device address as assigned
; from the host.  Save it in the UADDR, transition to the ADDRESSED state
; and clear the current configuration.
; This assumes the SIE has already sent the status stage of the transaction
; as implied by Figure 3-35 of the DOS (Rev A-7)
; ******************************************************************
Set_Address
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	SETADDRESS
	iorwf	USWSTAT,f	; place in upper 6 bits of register
	banksel	USB_dev_req	; select bank 3
#endif
	movlw	SET_ADDRESS
	movwf	USB_dev_req		; currently processing a get descriptor request
	movf	BufferData+wValue,w	; new address in low order byte of wValue
	movwf	USB_address_pending

	banksel	BD0IBC		; select bank 3
	movlw	0x00
	movwf	BD0IBC
	movlw	0xc8	
	movwf	BD0IST		; write the whole mess back
	bcf	STATUS,RP0	; back to page 2
	return

finish_set_address
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	FINISHSETADDRESS
	iorwf	USWSTAT,f	; place in upper 6 bits of register
	banksel	USB_dev_req	; select bank 2
#endif
	clrf	USB_dev_req	; no request pending
	clrf	USB_Curr_Config	; make sure current configuration is 0
	movf	USB_address_pending,w
	banksel	UADDR		; select bank 3
	movwf	UADDR		; set the device address
	btfsc	STATUS,Z	; was address 0?
	goto	endfinishsetaddr; yes: don't change state

	movlw	0xfc
	andwf	USWSTAT,f
	movlw	ADDRESS_STATE	; non-zero: transition to addressed state
	iorwf	USWSTAT,f	; transition to addressed state
endfinishsetaddr
	bcf	STATUS,RP0	; bank 2
	return

; ******************************************************************
; only feature valid for device feature is Device Remote wakeup
; ******************************************************************
Clear_Device_Feature
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	CLEARFEATURE
	iorwf	USWSTAT,f	; place in upper 6 bits of register
	bcf	STATUS,RP0	; select bank 2
#endif
	movf	BufferData+wValue,w
	xorlw	0x01		; was it a Device Remote wakeup? If not, return STALL,
	btfss	STATUS,Z	; since we only implement this feature on this device.
	goto	wrongstate

right_state_clear_feature
	banksel	USB_status_device
	bcf	USB_status_device,1	; set device remote wakeup
	call	Send_0Len_pkt
	return

; ******************************************************************
; Only endpoint feature is Endpoint halt.
; ******************************************************************
Clear_Endpoint_Feature
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	CLEARFEATURE
	iorwf	USWSTAT,f	; place in upper 6 bits of register
	bcf	STATUS,RP0
#endif
	banksel	UEP0
	movlw	0x0f
	andwf	BufferData+wIndex,w	; mask off the EP number bits
	xorlw	0x01	; is it EP1?
	btfsc	STATUS,Z
	goto	ClearEP1

	movlw	0x0f
	andwf	BufferData+wIndex,w	; mask off the EP number bits
	xorlw	0x02	; is it EP2?
	btfss	STATUS,Z
	goto	wrongstate

ClearEP2
	bcf	UEP2,EP_STALL	; set the stall bit
	goto	$+2		; skip the second stall
ClearEP1
	bcf	UEP1,EP_STALL	; set the stall bit

	call	Send_0Len_pkt
	return

Clear_Interface_Feature
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	CLEARFEATURE
	iorwf	USWSTAT,f	; place in upper 6 bits of register
	bcf	STATUS,RP0
#endif
	return

; ******************************************************************
; only feature valid for device feature is Device Remote wakeup
; ******************************************************************
Set_Device_Feature
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	SETFEATURE
	iorwf	USWSTAT,f	; place in upper 6 bits of register
	bcf	STATUS,RP0	; select bank 2
#endif
	movf	BufferData+wValue,w	; get high order byte of wValue	
	xorlw	0x01	; was it a Device Remote wakeup?
	btfss	STATUS,Z
	goto	wrongstate	; request error
	banksel	USB_status_device	; select appropriate bank
	bsf	USB_status_device,1	; set device remote wakeup
	call	Send_0Len_pkt
	return

; ******************************************************************
; Only endpoint feature is Endpoint halt.
; ******************************************************************
Set_Endpoint_Feature
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	SETFEATURE
	iorwf	USWSTAT,f	; place in upper 6 bits of register
	bcf	STATUS,RP0
#endif
	banksel	UEP0
	movlw	0x0f
	andwf	BufferData+wIndex,w	; mask off the EP number bits
	xorlw	0x01	; is it EP1?
	btfsc	STATUS,Z
	goto	StallEP1

	movlw	0x0f
	andwf	BufferData+wIndex,w	; mask off the EP number bits
	xorlw	0x02	; is it EP2?
	btfss	STATUS,Z
	goto	wrongstate

StallEP2
	bsf	UEP2,EP_STALL	; set the stall bit
	goto	$+2		; skip the second stall

StallEP1
	bsf	UEP1,EP_STALL	; set the stall bit

	call	Send_0Len_pkt
	return

Set_Interface_Feature
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	SETFEATURE
	iorwf	USWSTAT,f	; place in upper 6 bits of register
	bcf	STATUS,RP0	; select bank 2
#endif
	goto	wrongstate	; invalid request
	return

; ********************************************************************
; Get configuration returns a single byte Data1 packet indicating the 
; configuration in use.
; Default State    - undefined
; Addressed State  - returns 0
; Configured state - returns current configured state.
; ******************************************************************
Get_Configuration
	banksel	BD0IAL		; select bank 3
#ifdef FUNCTIONIDS
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	GETCONFIG
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif
	movf	low BD0IAL,w	; get address of buffer
	movwf	FSR
	banksel	USB_Curr_Config	; select bank 2
	bsf	STATUS,IRP	; indirectly to banks 2-3
	movf	USB_Curr_Config,w
	movwf	INDF		; write byte to buffer
	banksel	BD0IBC		; select bank 3
	movlw	0x01
	movwf	BD0IBC		; set byte count to 1
	movlw	0xc8		; DATA1 packet, DTS enabled
	movwf	BD0IST		; give buffer back to SIE
	bcf	STATUS,RP0	; back to bank 2
	return

; ******************************************************************
; Set configuration uses the configuration selected by the low order
; byte of wValue.  Sets up a zero length data1 packet as a reply.
; ******************************************************************
Set_Configuration
; All we do is set a meaningless number.  This'll 
; need more code here to actually give meaning to each configuration
; we choose.
#ifdef FUNCTIONIDS
	banksel	USWSTAT		; select bank 3
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	SETCONFIG
	iorwf	USWSTAT,f	; place in upper 6 bits of register
	bcf	STATUS,RP0
#endif
	movf	BufferData+wValue,w	; is it a valid configuration?
	sublw	NUM_CONFIGURATIONS	
	btfss	STATUS,C		; if config <= num configs, request appears valid
	goto	wrongstate

	movf	BufferData+wValue,w	
	movwf	USB_Curr_Config	; store new state in configuration

	banksel	USWSTAT		; select bank 3
	movlw	0xfc
	andwf	USWSTAT,f	; clear status bits

	movlw	CONFIG_STATE	; Assume configured state
	movf	BufferData+wValue,f	; run it through the ALU to test for 0
	btfsc	STATUS,Z	; was the configuration zero?
	movlw	ADDRESS_STATE	; yes: set addressed state

	iorwf	USWSTAT,f	; save new state.

; These configure the EP1 and EP2  endpoints.  Change these as necessary
; for your application.
	movlw	0x88		; set own bit of EP1 (SIE can write)
	movwf	BD1OST
	movlw	USB_Buffer+0x10	; Endpoint 1 OUT gets a buffer
	movwf	BD1OAL		; set up buffer address
	movlw	8
	movwf	BD1OBC		; set byte count

	movlw	0x08		; set own bit of EP1 (PIC can write)
	movwf	BD1IST		
	movlw	USB_Buffer+0x18	; Endpoint 1 IN gets a buffer
	movwf	BD1IAL		; set up buffer address

	movlw	0x88		; set own bit of EP2 (SIE can write)
	movwf	BD2OST
	movlw	USB_Buffer+0x20	; Endpoint 2 OUT gets a buffer
	movwf	BD2OAL		; set up buffer address
	movlw	8
	movwf	BD2OBC		; set byte count

	movlw	0x08		; set own bit of EP2 (PIC can write)
	movwf	BD2IST
	movlw	USB_Buffer+0x20	; EP1 In and EP2 In share a buffer
	movwf	BD2IAL		; set up buffer address

; Set up the Endpoint Control Registers.  The following patterns are defined
; ENDPT_DISABLED - endpoint not used
; ENDPT_IN_ONLY  - endpoint supports IN transactions only
; ENDPT_OUT_ONLY - endpoint supports OUT transactions only
; ENDPT_CONTROL	 - Supports IN, OUT and CONTROL transactions - Only use with EP0
; ENDPT_NON_CONTROL - Supports both IN and OUT transactions
	movlw	ENDPT_NON_CONTROL
	movwf	UEP1		; enable EP's 1 and 2 for In and Outs...
	movlw	ENDPT_NON_CONTROL
	movwf	UEP2

	bcf	STATUS,RP0	; bank 2
	call	Send_0Len_pkt

	pagesel	SetConfiguration
	movf	USB_Curr_Config,w	
	call	SetConfiguration	; callout to set the configuration
	pagesel	Set_Configuration
	return


; ********************************************************************
; Get interface returns a single byte Data1 packet indicating the 
; interface in use.
; Default State    - undefined
; Addressed State  - Not valid - returns stall
; Configured state - returns current configured state.
; ******************************************************************
Get_Interface
	banksel	USWSTAT		; select bank 3
#ifdef FUNCTIONIDS
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	GETINTERFACE
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif
	movf	USWSTAT,w	; Not valid in the addressed state
	bcf	STATUS,RP0	; bank 2
	andlw	0x03
	xorlw	ADDRESS_STATE
	btfsc	STATUS,Z
	goto	wrongstate

	movf	BufferData+wIndex,w	; get interface ID
	addlw	low USB_Interface
	movwf	FSR
	bsf	STATUS,IRP
	movf	INDF,w		
	movwf	temp		; store in temp register
	
	bsf	STATUS,RP0	; bank 3
	movf	BD0IAL,w	; get address of buffer
	movwf	FSR
	movf	temp,w		; load temp
	movwf	INDF		; write byte to buffer

	movlw	0x01
	movwf	BD0IBC		; set byte count to 1
	movlw	0xc8		; DATA1 packet, DTS enabled
	movwf	BD0IST		; give buffer back to SIE
	bcf	STATUS,RP0	; select bank 2
	return

; ******************************************************************
; Set configuration uses the configuration selected by the low order
; byte of wValue.  Sets up a zero length data1 packet as a reply.
; ******************************************************************
Set_Interface
	banksel	USWSTAT		; select bank 3
#ifdef FUNCTIONIDS
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	SETINTERFACE
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif
	movf	USWSTAT,w	; test to make sure we're configured
	bcf	STATUS,RP0	; bank2
	andlw	0x03
	xorlw	CONFIG_STATE
	btfss	STATUS,Z
	goto	wrongstate

	movf	BufferData+wIndex,w	; get interface
	addlw	USB_Interface		; add offset to array
	movwf	FSR
	bsf	STATUS,IRP	; indirectly to banks 2-3
	movf	BufferData+wValue,w	; get alternate interface
	movwf	INDF			; store in array
; All we do is set a meaningless number.  This'll 
; need more code here to actually give meaning to each configuration
; we choose.

	call	Send_0Len_pkt
	return

; *********************************************************************
; copies the next chunk of buffer descriptor over to the EP0 In buffer.
; Inputs:
;	EP0_start - points to first byte of configuration table to transfer
;	EP0_end - total number of bytes to transfer
;	EP0_maxLength - maximum number of bytes that can be sent during
;	a single transfer
;
; toggles the data0/1 bit before setting the UOWN bit over to SIE.
; ******************************************************************
copy_descriptor_to_EP0
	global	copy_descriptor_to_EP0
	banksel	BD0IAL		; select bank 3
#ifdef FUNCTIONIDS
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	COPYDESC2EP0
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif
	bsf	STATUS,IRP	; make sure the FSR will point to pages 2/3
	movf	BD0IAL,w	; get buffer address
	movwf	FSR
	bsf	STATUS,IRP	; indirectly to banks 2-3
	bcf	STATUS,RP0	; SELECTBANK2
	clrf	bufindex	; bufindex = 0
	pagesel	Descriptions
gdd_loop
	movf	bufindex,w	; while (bufindex < EP0_maxLength)
	subwf	EP0_maxLength,w	;    && (EP0_start < EP0_end)
	btfsc	STATUS,Z
	goto	end_gdd_loop

	movf	EP0_start+1,w	; second half of above comparison
	subwf	EP0_end+1,w	; (EP0_start < EP0_end)
	btfsc	STATUS,Z	; each is 16 bits
	goto	gdd_cmp_loworder  ; high order bytes equal, check low order byte
	btfsc	STATUS,C
	goto	gdd_copy_loop	
	goto	end_gdd_loop_short_packet

gdd_cmp_loworder
	movf	EP0_start,w
	subwf	EP0_end,w
	btfsc	STATUS,Z
	goto	end_gdd_loop_short_packet

gdd_copy_loop
	call	Descriptions
	movwf	INDF
	
	incf	bufindex,f	
	incf	FSR,f
	incfsz	EP0_start,f
	goto	gdd_loop
	incf	EP0_start+1,f
	goto	gdd_loop

end_gdd_loop_short_packet
	clrf	USB_dev_req	; we're sending a short packet, clear the device request
end_gdd_loop
	movf	bufindex,w	; write number of bytes to byte count
	bsf	STATUS,RP0	; SELECTBANK3
	movwf	BD0IBC
	movlw	(0x01<<DATA01)	; toggle data0/1 bit
	xorwf	BD0IST,w
	andlw	(0x01<<DATA01)	; clear PID bits
	iorlw	0x88		; set OWN and DTS bits
	movwf	BD0IST		; write the whole mess back
	bcf	STATUS,RP0	; back to page 2
	pagesel copy_descriptor_to_EP0
	return

; **********************************************************************
; copies the next chunk of string descriptor over to the EP0 In buffer.
; Inputs:
;	EP0_start - points to first byte of configuration table to transfer
;	EP0_end - total number of bytes to transfer
;	EP0_maxLength - maximum number of bytes that can be sent during
;	a single transfer
;
; toggles the data0/1 bit before setting the UOWN bit over to SIE.
; ******************************************************************
copy_string_descriptor_to_EP0
	banksel	BD0IAL		; select bank 3
#ifdef FUNCTIONIDS
	movlw	0x03		; save state bits
	andwf	USWSTAT,f
	movlw	COPYSTRINGDESC2EP0
	iorwf	USWSTAT,f	; place in upper 6 bits of register
#endif
	pagesel	StringDescriptors
	bsf	STATUS,IRP	; make sure the FSR will point to pages 2/3
	movf	BD0IAL,w	; get buffer address
	movwf	FSR
	bsf	STATUS,IRP	; indirectly to banks 2-3
	banksel	bufindex	; SELECTBANK2
	clrf	bufindex	; bufindex = 0
gsd_loop
	movf	bufindex,w	; while (bufindex < EP0_maxLength)
	subwf	EP0_maxLength,w	;    && (EP0_start < EP0_end)
	btfsc	STATUS,Z
	goto	end_gsd_loop

	movf	bufindex,w	; while (bufindex < EP0_maxLength)
	subwf	EP0_maxLength,w	;    && (EP0_start < EP0_end)
	btfsc	STATUS,Z
	goto	end_gsd_loop

	movf	EP0_start+1,w	; second half of above comparison
	subwf	EP0_end+1,w	; (EP0_start < EP0_end)
	btfsc	STATUS,Z	; each is 16 bits
	goto	gsd_cmp_loworder  ; high order bytes equal, check low order byte
	btfsc	STATUS,C
	goto	gsd_copy_loop	
	goto	end_gsd_loop_short_packet

gsd_cmp_loworder
	movf	EP0_start,w
	subwf	EP0_end,w
	btfss	STATUS,C
	goto	end_gsd_loop_short_packet

gsd_copy_loop
	call	StringDescriptors
	movwf	INDF

	incf	bufindex,f	
	incf	FSR,f
	incfsz	EP0_start,f
	goto	gsd_loop
	incf	EP0_start+1,f
	goto	gsd_loop

end_gsd_loop_short_packet
	clrf	USB_dev_req
end_gsd_loop
	movf	bufindex,w	; write number of bytes to byte count
	bsf	STATUS,RP0	; SELECTBANK3
	movwf	BD0IBC
	movlw	(0x01<<DATA01)	; toggle data0/1 bit
	xorwf	BD0IST,w
	andlw	(0x01<<DATA01)	; clear PID bits
	iorlw	0x88		; set OWN and DTS bits
	movwf	BD0IST		; write the whole mess back
	bcf	STATUS,RP0	; back to page 2
	pagesel	copy_string_descriptor_to_EP0
	return

; ******************************************************************
; Init USB
; Initializes the USB peripheral, sets up the interrupts
; ******************************************************************
InitUSB
	global	InitUSB

	banksel	USWSTAT
	clrf	USWSTAT		; default to powered state
	movlw	0x01	; mask all interrupts except reset
	movwf	UIE
	movlw	0x08	; Device attached
	movwf	UCTRL

	banksel	USB_Curr_Config
	clrf	USB_Curr_Config
	movlw	1
	movwf	USB_status_device
	clrf	USB_Interface
	clrf	USB_Interface+1
	clrf	USB_Interface+2
	movlw	0xFF
	movwf	USB_dev_req	; no device requests in process
#ifdef COUNTERRORS
	clrf	USB_PID_ERR
	clrf	USB_PID_ERR+1
	clrf	USB_CRC5_ERR
	clrf	USB_CRC5_ERR+1
	clrf	USB_CRC16_ERR
	clrf	USB_CRC16_ERR+1
	clrf	USB_DFN8_ERR
	clrf	USB_DFN8_ERR+1
	clrf	USB_BTO_ERR
	clrf	USB_BTO_ERR+1
	clrf	USB_WRT_ERR
	clrf	USB_WRT_ERR+1
	clrf	USB_OWN_ERR
	clrf	USB_OWN_ERR+1
	clrf	USB_BTS_ERR
	clrf	USB_BTS_ERR+1
#endif
	banksel	PIE1
	bsf	PIE1,USBIE	; enable usb interrupt
	movlw	0xc8		; enable peripheral and global interrupts
	movwf	INTCON
	return


; ******************************************************************
; DeInit USB
; Shuts down the USB peripheral, clears the interrupt enable.
; ******************************************************************
DeInitUSB
	global	DeInitUSB

	banksel	UCTRL		; select bank 3
	bcf	UCTRL,DEV_ATT	; D+/D- go high Z
	bsf	UCTRL,SUSPND	; Place USB module in low power mode.
	movlw	0xfc		; set device state to powered.
	andwf	USWSTAT,f

	bcf	STATUS,RP1	; select bank 1
	bcf	PIE1,USBIE	; clear USB interrupt enable
	bcf	PIE1,USBIF	; Clear USB interrupt flag
	return

	end
