;                            Software License Agreement
;
; The software supplied herewith by Microchip Technology Incorporated (the "Company")
; for its PICmicro(r) 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
;
; Implements the chapter 9 enumeration commands for Microchip's
; PIC16C7x5 parts.
;
; ###############################################################################
;
;   Author(s):          Dan Butler and Reston Condit
;   Company:            Microchip Technology Inc
;
;   Revision:           1.21
;   Date:               08 August 2001
;   Assembled using     MPASM 2.61
;   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
;   20 March 2001       DZB Reduced use of common RAM
;   20 March 2001       DZB Put and Get use their own temp variable (GPtemp) to
;                           avoid collisions with the ISR's use of temp.
;   29 March 2001       DZB Fixed saving of bank bits in GetUSB
;   02 May 2001         DZB Implemented SHOW_ENUM_STATUS to show enumeration
;                           status on the PORTB LEDs: 0- Powered, 1- Default,
;                           2- addressed, 3- configured, 4- sleep, 
;                           5- EP0 Activity, 6- EP1 Activity, 7- EP2 Activity
;   03 August 2001      RAC Made distinct GetEP and PutEP macros for endpoints 1 
;                           and 2.  These functions are GetEP1, GetEP2, PutEP1, and 
;                           PutEP2.  Instances of the these macros are created in
;                           usb_ch9.asm.
;   08 August 2001      RAC Corrected various banking and paging issues.
;   15 August           RAC Added Report_desc_index function in descript.asm.
;                           This function allows more than one report descriptor
;                           to be used. 
;   06 September 2001	JMS Changed to work with PBP compiler.
;
;################################################################################
;
;    include files:
;       P16C7x5.inc     Rev 1.00
;       usb_defs.inc    Rev 1.21
;
;################################################################################

    errorlevel -302 ; supress "register not in bank0, check page bits" message

;#define SHOW_ENUM_STATUS

; **********************************************************************
; This section contains the functions to interface with the main 
; application.
; **********************************************************************

; **********************************************************************
; GETEP1 and GETEP2
;
; Note:  These functions are, in reality, macros defined in usb_defs.inc.
;        To save ROM, delete the instances below that you will not need.
;
; Enter with 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.
;
; 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.
; 
; The number of bytes moved is returned in W reg.
; **********************************************************************

	GETEP1		; create instance of GETEP1
	GETEP2		; create instance of GETEP2

; **********************************************************************
; PUTEP1 and PUTEP2
;
; Note:  These functions are, in reality, macros defined in usb_defs.inc.
;        To save ROM, delete the instances below that you will not need.
;
; Enter with bytecount in W and buffer pointer in IRP+FSR.
; the bytecount is encoded in the lower nybble of W.
;
; 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)
; **********************************************************************
	PUTEP1      ; create instance of PUTEP1
	PUTEP2      ; create instance of PUTEP2

; *********************************************************************
; 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
    banksel IS_IDLE
    btfss   IS_IDLE,0       ; test the bus idle bit
    goto    nosleeptoday

 ifdef SHOW_ENUM_STATUS
    banksel PORTB
    bsf     PORTB,4         ; turn on LED 4 to indicate we've gone to sleep
    banksel UIR
 endif
    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
    bcf     UIE,ACTIVITY
 ifdef SHOW_ENUM_STATUS
    banksel PORTB
    bcf     PORTB,4         ; turn off LED 4 to indicate we're back.
 endif

nosleeptoday
    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
    banksel USB_status_device    
    btfss   USB_status_device, 1
    goto    RemoteEnd

    clrf    inner
    movlw   0xB0
    movwf   outer

    bsf     STATUS, RP0     ; BANK 3
    bsf     UCTRL, 2        ; RESUME SIGNALING
    bcf     STATUS, RP0     ; BANK 2

    incfsz  inner,f         ; DELAY over 10mS
    goto    $-1
    incfsz  outer,f
    goto    $-3

    bsf     STATUS,RP0
    bcf     UCTRL, 2        ; Clear Resume bit
    bcf     STATUS,RP0
    bcf     USB_status_device, 1    ; Disable Remote Wakeup
    
RemoteEnd
    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   ; clear attach bit

    bcf     STATUS, RP0     ; bank 2
    
    clrf    outer
    clrf    inner

    incfsz  inner,f
    goto    $-1
    incfsz  outer,f
    goto    $-3

    pagesel InitUSB
    call    InitUSB         ; reinitialize the USB peripheral
    return


; ******************************************************************
; Init USB
; Initializes the USB peripheral, sets up the interrupts
; ******************************************************************
InitUSB
    banksel USWSTAT
    clrf    USWSTAT         ; default to powered state
    movlw   0x01            ; mask all USB interrupts except reset
    movwf   UIE
    clrf    UIR             ; clear all USB Interrupt flags
    movlw   0x08            ; Device attached
    movwf   UCTRL

    bcf     STATUS, RP0     ; bank 2
    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 PIR1            ; bank 0
    bcf     PIR1,USBIF      ; clear the USB flag
    bsf     STATUS,RP0      ; bank 1
    bsf     PIE1,USBIE      ; enable usb interrupt
    bsf     INTCON, 6       ; enable global and peripheral interrupts
    bsf     INTCON, 7
 ifdef SHOW_ENUM_STATUS
    bcf     STATUS,RP0      ; select bank 0
    bsf     PORTB,0         ; set bit zero to indicate Powered status
 endif
    return


; ******************************************************************
; DeInit USB
; Shuts down the USB peripheral, clears the interrupt enable.
; ******************************************************************
DeInitUSB
    banksel UCTRL
    bcf     UCTRL,DEV_ATT   ; D+/D- go high Z
    bsf     UCTRL,SUSPND    ; Place USB module in low power mode.

    clrf    USWSTAT         ; set device state to powered.

    bcf     STATUS,RP1      ; select bank 1
    bcf     PIE1,USBIE      ; clear USB interrupt enable
 ifdef SHOW_ENUM_STATUS
    bcf     STATUS,RP0
    movlw   0x01
    movwf   PORTB           ; clear all lights except powered
    bsf     STATUS,RP0
 endif

    return


; The functions below are the core functions
; ******************************************************************
; USB interrupt triggered, Why?
; Poll the USB interrupt flags to find the cause.
; ******************************************************************
ServiceUSBInt
    banksel UIR
    movf    UIR,w           ; get the USB interrupt register
    andwf   UIE,w           ; mask off the disabled interrupts
    bcf     STATUS, RP0     ; BANK 2
    btfsc   STATUS,Z        ; is there any unmasked interrupts?
    goto    ExitServiceUSBInt ; no, bail out.

    pagesel TokenDone
    movwf   USBMaskedInterrupts
    btfsc   USBMaskedInterrupts,TOK_DNE    ; was it a token done?
    call    TokenDone
    banksel USBMaskedInterrupts
    btfsc   USBMaskedInterrupts,USB_RST
    call    USBReset
    btfsc   USBMaskedInterrupts,STALL
    call    USBStall
    btfsc   USBMaskedInterrupts,UERR
    call    USBError
    btfsc   USBMaskedInterrupts,UIDLE
    call    USBSleep
    btfsc   USBMaskedInterrupts,ACTIVITY
    call    USBActivity
    pagesel ServiceUSBInt
    goto    ServiceUSBInt
ExitServiceUSBInt
    banksel PIR1
    bcf     PIR1,USBIF
    return

; ******************************************************************
; USB Reset interrupt triggered (SE0)
; initialize the Buffer Descriptor Table,
; Transition to the DEFAULT state,
; Set address to 0
; enable the USB
; ******************************************************************
USBReset    ; START IN BANK2

    clrf    USB_Curr_Config
    clrf    IS_IDLE
    bsf     STATUS, RP0     ; 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   0x8
    movwf   BD0OBC

    movlw   0x08            ; Clear owns bit (PIC can write)
    movwf   BD0IST
    movlw   USB_Buffer+8    ; Endpoint 0 IN gets a buffer
    movwf   BD0IAL          ; set up buffer address

    clrf    UADDR           ; set USB Address to 0
    clrf    UIR             ; clear all the USB interrupt flags
    banksel PIR1            ; switch to bank 0
    bcf     PIR1,USBIF

; 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

    banksel UEP0
    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   DEFAULT_STATE
    movwf   USWSTAT

    bcf     STATUS,RP0      ; select bank 2
    movlw   0x00
    movwf   USB_status_device ; Self powered, remote wakeup disabled
    bcf     STATUS,RP1      ; bank 0
 ifdef SHOW_ENUM_STATUS
    bsf     PORTB,1         ; set bit one to indicate Reset status
 endif
    bsf     STATUS,RP1
    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    ; starts from bank2
    bsf     STATUS, RP0     ; up to bank 3
    bcf     UIE,UIDLE
    bcf     UIR,UIDLE
    bcf     UIE,ACTIVITY
    bcf     UIR,ACTIVITY
    banksel PIR1            ; switch to bank 0
    bcf     PIR1,USBIF

    banksel IS_IDLE         ; switch to bank 2
    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 
; see that any action is required.  Clear the bit and move on.
; ******************************************************************
USBStall    ; starts in bank 2
    bsf     STATUS, RP0     ; bank 3
    bcf     UIR, STALL      ; clear STALL

    banksel PIR1            ; switch to bank 0
    bcf     PIR1,USBIF
    bsf     STATUS,RP1      ; bank 2    
    return

 
; ******************************************************************
; The SIE detected an error.  This code increments the appropriate 
; error counter and clears the flag.
; ******************************************************************
USBError    ; starts in bank 2
    bsf     STATUS, RP0     ; bank 3
    bcf     UIR,UERR
    banksel PIR1            ; switch to bank 0
    bcf     PIR1,USBIF      ; clear the USB interrupt flag.
    bsf     STATUS,RP1      ; switch to bank 2
    
 ifdef COUNTERRORS
    banksel UEIR
    movf    UEIR,w          ; get the error register
    andwf   UEIE,w          ; mask with the enables
    clrf    UEIR
    bcf     STATUS, RP0     ; Bank 2
    movwf   USBMaskedErrors ; save the masked errors

    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
    banksel USBMaskedInterrupts 
    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     ; starts in bank 2
    bsf     STATUS, RP0     ; Bank 3
    bcf     UIE,ACTIVITY    ; clear the Activity and Idle bits
    bcf     UIR,ACTIVITY
    bcf     UIE,UIDLE
    bcf     UIR,UIDLE

    banksel PIR1            ; switch to bank 0
    bcf     PIR1,USBIF      ; clear the USB interrupt flag.
    bsf     STATUS,RP1      ; switch to bank 2
    
    clrf    IS_IDLE

    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       ; starts in bank 2
    COPYBUFFERDESCRIPTOR    ; copy BD from dual port to unbanked RAM
    banksel USTAT
    movf    USTAT,w         ; copy USTAT register before...
    bcf     UIR,TOK_DNE     ; clearing the token done interrupt.

    banksel PIR1            ; switch to bank 0
    bcf     PIR1,USBIF      ; clear the USB interrupt flag.
    bsf     STATUS,RP1      ; switch to bank 2

    movwf   USB_USTAT       ; Save USTAT in bank 2

 ifdef SHOW_ENUM_STATUS
; This toggles the activity bits on portB  (EP0 -> Bit 5; EP1 -> bit 6; EP2 -> bit 7)
    bcf     STATUS,RP1      ; bank 0
    andlw   0x18            ; save endpoint bits
    btfss   STATUS,Z        ; is it EP0?
    goto    tryEP1activity
    movlw   0x20
    goto    maskport
tryEP1activity
    xorlw   0x08            ; is it bit one?
    btfss   STATUS,Z
    movlw   0x80            ; No, It's not EP0, nor 1 so it must be EP2.  toggle bit 7
    btfsc   STATUS,Z    
    movlw   0x40            ; Yes, toggle bit 6 to Show EP1 activity
maskport
    xorwf   PORTB,f
    bsf     STATUS,RP1      ; bank 2
 endif

; 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    ; STARTS IN BANK2
    movf    USB_USTAT,w     ; get the status register
    btfss   STATUS,Z        ; was it EP0?
    goto    tryEP1          ; no, try EP1

    movf    USB_dev_req,w
    xorlw   HID_SET_REPORT
    btfss   STATUS,Z
    goto    ResetEP0OutBuffer

HIDSetReport
;   Copy buffer to EP1 Out so it looks like it was received from an EP1 OUT
    movlw   0xFF
    movwf   USB_dev_req     ; clear the request type
    banksel BD1IST
    movf    BD0OST,w
    movwf   BD1OST          ; Copy status register to EP1 Out
    movf    BD0OAL,w        ; get EP0 Out buffer address
    bcf     STATUS,RP0      ; bank 2
    movwf   hid_source_ptr
    bsf     STATUS,RP0      ; bank 3
    movf    BD1OAL,w        ; get EP1 Out Buffer Address
    bcf     STATUS,RP0      ; bank 2
    movwf   hid_dest_ptr
    bsf     STATUS,RP0      ; bank 3
    movf    BD0OBC,w        ; Get byte count
    movwf   BD1OBC          ; copy to EP1 Byte count
    bcf     STATUS,RP0      ; bank 2
    movwf   counter

    bankisel BD1IST         ; indirectly to bank 3
HIDSRCopyLoop
    movf    hid_source_ptr,w
    movwf   FSR
    movf    INDF,w
    movwf   temp
    movf    hid_dest_ptr,w
    movwf   FSR
    movf    temp,w
    movwf   INDF
    incf    hid_source_ptr,f
    incf    hid_dest_ptr,f
    decfsz  counter,f
    goto    HIDSRCopyLoop

    bsf     STATUS,RP0      ; bank 3
    movlw   0x08
    movwf   BD0OST          ; REset EP0 Status back to SIE

ResetEP0OutBuffer
    bsf     STATUS,RP0      ; no, just reset buffer and move on.

    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
    goto    Send_0Len_pkt
    return

tryEP1  ; bank 3
    xorlw   0x08            ; was it EP1?
    btfss   STATUS,Z    
    goto    tryEP2

; **** Add Callout here to service EP1 in transactions.  ****

    return

tryEP2  ; bank 3
    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      ; starts in bank2
; 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
    pagesel copy_descriptor_to_EP0
    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_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
    goto    exitEP0in

check_CF
    movf    USB_dev_req,w
    xorlw   CLEAR_FEATURE
    btfss   STATUS,Z
    goto    Class_Specific
    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        ; starts in bank 2
    xorlw   0x08            ; was it EP1?
    btfss   STATUS,Z    
    goto    tryEP2in
; **** Add Callout here to service EP1 in transactions.  ****
    return

tryEP2in        ; starts in bank 2
; **** Add Callout here to service EP2 in transactions.  ****
    return
; ******************************************************************
; Return a zero length packet on EP0 In
; ******************************************************************
Send_0Len_pkt
    banksel BD0IBC
    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    ; starts in bank 2
    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

    bsf     STATUS, RP0
    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
    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 ClassSpecificRequest
    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    ClassSpecificRequest    ; 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
    pagesel wrongstate	; MOVED
    btfss   STATUS,Z        ; was it a standard request?
    goto    wrongstate
    pagesel CheckVendor
    goto    CheckVendor     ; nope, see if it was a vendor specific
    return
                
; 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    ; starts in bank 2
    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_Code

    movf    BufferData+bRequest,w   ; was our request Set Configuration
    xorlw   SET_CONFIGURATION
    btfsc   STATUS,Z
    goto    Set_Configuration_Code

    movf    BufferData+bRequest,w   ; was our request Set Feature
    xorlw   SET_FEATURE
    btfsc   STATUS,Z
    goto    Set_Device_Feature
    goto    wrongstate

HostToInterface ; starts in bank 2
    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_Code

    movf    BufferData+bRequest,w   ; was our request Set Feature
    xorlw   SET_FEATURE
    btfsc   STATUS,Z
    goto    Set_Interface_Feature

HostToEndpoint  ; starts in bank2
    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    ; starts in bank2
    movf    BufferData+bRequest,w   ; what was our request
    xorlw   GET_CONFIGURATION
    btfsc   STATUS,Z
    goto    Get_Configuration_Code

    movf    BufferData+bRequest,w   ; was our request Get Decriptor?
    xorlw   GET_DESCRIPTOR
    btfsc   STATUS,Z
    goto    Get_Descriptor_Code

    movf    BufferData+bRequest,w   ; was our request Get Status?
    xorlw   GET_STATUS
    btfsc   STATUS,Z
    goto    Get_Device_Status

InterfaceToHost ; starts in bank2
    movf    BufferData+bRequest,w   ; was our request Get Interface?
    xorlw   GET_INTERFACE
    btfsc   STATUS,Z
    goto    Get_Interface_Code

    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_Code

EndpointToHost  ; starts in bank2
    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_Code  ; starts in bank2
    pagesel Get_Report_Descriptor
    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

    movf    BufferData+(wValue+1),w
    xorlw   0x21
    btfsc   STATUS,Z
    goto    Get_HID_Descriptor
    pagesel GetCh9Descriptor

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                    ; 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_Code	; 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           ; starts in bank 2
    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

    movlw   low DeviceDescriptor
    movwf   EP0_start
    movlw   high DeviceDescriptor
    movwf   EP0_start+1
    call    Descriptions        ; get length of device descriptor
    movwf   EP0_end             ; save length
    pagesel DeviceEndPtr

    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
    movf    BufferData+wLength,w
    btfss   STATUS,C
    movwf   EP0_end

DeviceEndPtr
    incf    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   ; starts in bank2
    pagesel Descriptions        ; set up PCLATH for call below
    movlw   GET_DESCRIPTOR
    movwf   USB_dev_req         ; currently processing a get descriptor request

    bcf     STATUS,C
    rlf     BufferData+wValue,w
    call    Config_desc_index   ; translate index to offset into descriptor table
    movwf   EP0_start
    bcf     STATUS,C
    rlf     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 2 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    BufferData+(wLength+1),f ; test for 0
    pagesel CmpLowerByte	; MOVED
    btfsc   STATUS,Z
    goto    CmpLowerByte
    goto    ConfigEndPtr        ; if not, no need to compare.  EP0_end is shorter than request length

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.
ConfigEndPtr

    movlw   8
    movwf   EP0_maxLength
    incf    EP0_end,f

    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_Code    ; starts in bank2
    movlw   GET_STRING_DESCRIPTOR
    movwf   USB_dev_req         ; currently processing a get descriptor request

    movf    BufferData+wIndex,w
    btfss   STATUS,Z
    goto    not_string0
    movf    BufferData+(wIndex+1),w
    btfss   STATUS,Z
    goto    not_string0
    movlw   low String0
    movwf   EP0_start
    movlw   high String0
    movwf   EP0_start+1
    goto    found_string

not_string0
    movlw   high (String0+2)
    movwf   EP0_start+1
    movlw   low (String0+2)
    movwf   EP0_start
    clrf    inner

check_langid
    pagesel StringDescriptions
    call    StringDescriptions
    pagesel wrong_langid 
    incf    EP0_start,f
    subwf   BufferData+wIndex, w
    btfss   STATUS, Z
    goto    wrong_langid
    pagesel StringDescriptions
    call    StringDescriptions
    pagesel right_langid 
    subwf   BufferData+(wIndex+1), w
    btfsc   STATUS, Z
    goto    right_langid
    
wrong_langid
    incf    EP0_start,f
    incf    inner,f
    movlw   low String0_end     ; compare EP0_start to the addr of
    subwf   EP0_start,w         ; the last langid
    btfss   STATUS,C            ; if EP0_start is equal or lager,
    goto    check_langid        ; we've checked all langid and didn't find it
    clrf    USB_dev_req         ; clear USB_dev_req, since GET_descriptor is over
    pagesel wrongstate
    goto    wrongstate

right_langid
    movlw   6                   ; number of strings we have per language + 1
    subwf   BufferData+wValue,w
    btfss   STATUS,C
    goto    right_string
    clrf    USB_dev_req
    pagesel   wrongstate
    goto    wrongstate

right_string
    rlf     BufferData+wValue,w
    movwf   EP0_start+1
    movf    inner,w
    pagesel string_index
    call    string_index
    movwf   EP0_start
    incf    EP0_start+1,f
    movf    inner,w
    call    string_index
    movwf   EP0_start+1

found_string
    pagesel StringDescriptions
    call    StringDescriptions  ; get length of the string 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

    incf    EP0_end,f
    pagesel copy_descriptor_to_EP0
    call    copy_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
    banksel UEP0
    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           ; starts in bank2
    bsf     STATUS,RP0
    movf    BD0IAL,w        ; get buffer pointer
    movwf   FSR
    bcf     STATUS,RP0      ; 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

    bsf     STATUS,RP0      ; bank 3
    movlw   0x02
    movwf   BD0IBC          ; set byte count to 2
    movlw   0xC8
    movwf   BD0IST          ; Data 1 packet, set owns bit
    return

; ******************************************************************
; A do nothing response.  Always returns a two byte record, with all
; bits zero.
; ******************************************************************
Get_Interface_Status        ; starts in bank 2
    bsf     STATUS, RP0     ; bank 3
    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
    return

; ******************************************************************
; Returns the Endpoint stall bit via a 2 byte in buffer
; ******************************************************************
Get_Endpoint_Status         ; starts in bank 2
    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
    bsf     STATUS,RP0
    btfsc   UEP2,EP_STALL
    bsf     STATUS,C
    goto    build_status_buffer

get_EP1_status
    bcf     STATUS,C
    bsf     STATUS,RP0
    btfsc   UEP1,EP_STALL
    bsf     STATUS,C
         
build_status_buffer
    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
    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_Code     ; starts in bank 2
    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

    bsf     STATUS, RP0
    clrf    BD0IBC
    movlw   0xc8
    movwf   BD0IST          ; write the whole mess back
    return

finish_set_address          ; starts in bank 2
    clrf    USB_dev_req     ; no request pending
    clrf    USB_Curr_Config ; make sure current configuration is 0
    movf    USB_address_pending,w
    bsf     STATUS, RP0
    movwf   UADDR           ; set the device address
    btfsc   STATUS,Z        ; was address 0?
    goto    endfinishsetaddr    ; yes: don't change state

    movlw   ADDRESS_STATE   ; non-zero: transition to addressed state
    movwf   USWSTAT         ; transition to addressed state
 ifdef SHOW_ENUM_STATUS
    banksel PORTB
    bsf     PORTB,2         ; set bit 2 to indicate Addressed state
    banksel USWSTAT         ; not necessary, Send_0LenPkt resets bank bits
 endif

endfinishsetaddr
    return

; ******************************************************************
; only feature valid for device feature is Device Remote wakeup
; ******************************************************************
Clear_Device_Feature        ; starts in bank2
    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
    bcf     USB_status_device,1 ; set device remote wakeup
    call    Send_0Len_pkt
    return

; ******************************************************************
; Only endpoint feature is Endpoint halt.
; ******************************************************************
Clear_Endpoint_Feature  ; starts in bank 2
    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
    bsf     STATUS, RP0
    bcf     UEP2,EP_STALL       ; set the stall bit
    goto    clear_endpoint_end  ; skip the second stall
ClearEP1
    bsf     STATUS, RP0
    bcf     UEP1,EP_STALL   ; set the stall bit

clear_endpoint_end
    call    Send_0Len_pkt
    return

Clear_Interface_Feature     ; starts in bank2
    return

; ******************************************************************
; only feature valid for device feature is Device Remote wakeup
; ******************************************************************
Set_Device_Feature  ; starts in bank 2
    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
    bsf     USB_status_device,1 ; set device remote wakeup
    call    Send_0Len_pkt
    return

; ******************************************************************
; Only endpoint feature is Endpoint halt.
; ******************************************************************
Set_Endpoint_Feature            ; starts in bank 2
    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     STATUS, RP0
    bsf     UEP2,EP_STALL   ; set the stall bit
    goto    StallEnd        ; skip the second stall

StallEP1
    bsf     STATUS, RP0
    bsf     UEP1,EP_STALL   ; set the stall bit
StallEnd
    call    Send_0Len_pkt
    return

Set_Interface_Feature       ; starts in bank 2
    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_Code           ; starts in bank 2
    bsf     STATUS, RP0
    movf    low BD0IAL,w    ; get address of buffer
    movwf   FSR
    bcf     STATUS, RP0
    bsf     STATUS,IRP      ; indirectly to banks 2-3
    movf    USB_Curr_Config,w
    movwf   INDF            ; write byte to buffer
    bsf     STATUS, RP0
    movlw   0x01
    movwf   BD0IBC          ; set byte count to 1
    movlw   0xc8            ; DATA1 packet, DTS enabled
    movwf   BD0IST          ; give buffer back to SIE
    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_Code           ; starts in bank 2
; All we do is set a meaningless number.  This'll 
; need more code here to actually give meaning to each configuration
; we choose.
    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

    btfsc   STATUS,Z        ; was the configuration zero?
    goto    AckSetConfigCmd ; yes: stay in the addressed state

    bsf     STATUS, RP0     ; bank 3
    movlw   CONFIG_STATE    ; No: transition to configured
    movwf   USWSTAT         ; save new state.
 ifdef SHOW_ENUM_STATUS
    banksel PORTB
    bsf     PORTB,3         ; set bit 3 to show configured
;    banksel USWSTAT         ; not necessary, Send_0LenPkt resets bank bits
 endif

AckSetConfigCmd
    call    Send_0Len_pkt

; These configure the EP1 and EP2  endpoints.  Change these as necessary
; for your application.
    banksel BD1OST
    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   0x48            ; 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   0x48            ; 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

;    pagesel SetConfiguration    ; call SetConfiguration etc. after configuration changed
;    movf    USB_Curr_Config,w   ; if you have multiple configurations
;    call    SetConfiguration
;    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_Code   ; STARTS IN BANK 2
    bsf     STATUS, RP0
    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
    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_Code   ; start bank 2
    bsf     STATUS, RP0
    movf    USWSTAT,w       ; test to make sure we're configured
    bcf     STATUS,RP0      ; bank2
    andlw   0x03
    xorlw   CONFIG_STATE
    btfss   STATUS,Z
    goto    wrongstate

    bcf     STATUS, RP0
    movf    BufferData+wIndex,w ; get interface
    addlw   low 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
    banksel BD0IAL
    bankisel BD0IAL
    movf    BD0IAL,w        ; get buffer address
    movwf   FSR 
    banksel bufindex
    clrf    bufindex        ; bufindex = 0
gdd_loop
    movf    bufindex,w      ; while (bufindex < EP0_maxLength)
    subwf   EP0_maxLength,w ;    && (EP0_start < EP0_end)
    btfsc   STATUS,Z
    goto    end_gdd_loop

    decfsz  EP0_end, f
    goto    gdd_copy_loop
    goto    end_gdd_loop_short_packet

gdd_copy_loop
    pagesel Descriptions
    call    Descriptions
    pagesel gdd_copy_loop
    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      ; Bank 3
    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
    pagesel copy_descriptor_to_EP0
    return
; *********************************************************************
; SetConfiguration
; 
; This function is called when the host issues a Set Configuration 
; command.  The housekeeping within USB is handled within the CH9 commands
; This function should be filled in to give meaning to the command within
; the application.
;
; SetConfiguration is called from within the ISR so this function should
; be kept as short as possible.
; *********************************************************************
SetConfiguration
    return

; *********************************************************************
; Vendor Specific calls
; control is transferred here when bmRequestType bits 5 & 6 = 10 indicating
; the request is a vendor specific request.  This function then would 
; interpret the bRequest field to determine what action is required.
; The end of each vendor specific command should be terminated with a 
; return.  
; *********************************************************************
CheckVendor    
    return  ; *** remove this line and uncomment out the remainder    

