'------------------------------------------------------------------------------
' Copyright (c) 2020 Garry Jordan ("garryj")
' See below for terms of use
' 09/17/2020, v0.1.4
' TABs as space

'Modified by RJA to automatically update mouse position

'------------------------------------------------------------------------------
' P2-ES USB Host (Master) x 2 Accessory Board I/O group base pin. The object
' that starts the USB cog must provide the base pin# of the eight
'------------------------------------------------------------------------------
' Smart pin configuration for "long repository" mode with output enabled,
' regardless of DIR. This allows one pin to provide a long event code via IN
' and also to control the USB port activity LED on/off state vio OUT.
CON
    SP_REPO1_MODE = %01_00001_0 | 1 << 16    ' %TT_MMMMM_0, P[12:10] != %101
' #region (Shared constants)
'------------------------------------------------------------------------------
' A simple event/cmd system using a smart pin configured in "long repository"
' mode. The USB_EVENT_REPO pin defined above is used to allow the USB cog to
' signal the client that a particular event has occurred. The client must poll
' this smart pin (testp #USB_EVENT_REPO wc) as often as possible in its "main"
' loop and process the event IDs you're interested in.
'------------------------------------------------------------------------------
    #0, NO_EVENT, USB_ERROR, DEV_UNKNOWN, KB_READY, M_READY, KBM_READY
    DEV_DISCONNECT, DBG_DATA, M_DATA, CMD_SUSPEND, CMD_RESUME
    CMD_RESET
' The CMD_* tokens are asynchronous USB commands available to the user, through
' the Spin2 usbCommand method of this object. This method will post the cmd
' token to the host and when the cmd is complete the host will post the CMD_*
' token to byte[0] of the event long. Byte[1] is typically set to one of the
' below protocol error codes, with bytes[2..3] available for additional cmd data.
'------------------------------------------------------------------------------
' Protocol error codes:
    #0, ERR_NONE, ERR_CMD, ERR_URX, ERR_SE1, ERR_PACKET, ERR_TAT, ERR_TXN_RETRY
    ERR_XFER_RETRY, ERR_NAK, ERR_DATAX_SYNC, ERR_CONFIG_FAIL, ERR_TIMEOUT
'------------------------------------------------------------------------------
' Reference: "Device Class Definition for Human Interace Devices (HID), v1.11,
' Appendix B: Boot Interface".
'------------------------------------------------------------------------------
' Boot protocol mouse constants:
'------------------------------------------------------------------------------
' Mouse button bit assignments (left, right, center):
    #0, MOUSE_LBTNB, MOUSE_RBTNB, MOUSE_CBTNB       ' Button bits 3..7 device specific
' Mouse button bitflags:
    MOUSE_LBTNF = decod(MOUSE_LBTNB)
    MOUSE_RBTNF = decod(MOUSE_RBTNB)
    MOUSE_CBTNF = decod(MOUSE_CBTNB)
'------------------------------------------------------------------------------
' USB HID Keyboard/Keypad Page (0x07). The scancodes below are indexes into
' the scancode->character/function look-up table.
'------------------------------------------------------------------------------
' USB boot protocol key scancode constants. IMPORTANT: these constants are the
' keyboard scan codes per USB HID specification 1.11. The keypress value that
' the USB cog writes to the kbd_keypress location will be a 7-bit ASCII value
' if the key scancode represents an ASCII character e.g. TAB->$09,
' BACKSPACE->$08, SPACE->$20, DELETE->$7f, etc. If there is NOT an ASCII match,
' the keypress value will be the same as the key's HID scan code. In those
' cases, it is up to the client program to implement the action required for
' that key.
'------------------------------------------------------------------------------
' The first four entries in the key lookup table are info/error indicators:
    #0,   KEY_NO_KEY, KEY_ERR_ROLLOVER, KEY_POST_FAIL, KEY_ERR_UNDEF
' Alpha scancodes are contiguous in the lookup table and if the caps lock key
' is toggled on, these are the only scancodes affected by it:
    KEY_A_a     = $04
    KEY_B_b     = $05
    KEY_C_c     = $06
    KEY_D_d     = $07
    KEY_E_e     = $08
    KEY_F_f     = $09
    KEY_G_g     = $0a
    KEY_H_h     = $0b
    KEY_I_i     = $0c
    KEY_J_j     = $0d
    KEY_K_k     = $0e
    KEY_L_l     = $0f
    KEY_M_m     = $10
    KEY_N_n     = $11
    KEY_O_o     = $12
    KEY_P_p     = $13
    KEY_Q_q     = $14
    KEY_R_r     = $15
    KEY_S_s     = $16
    KEY_T_t     = $17
    KEY_U_u     = $18
    KEY_V_v     = $19
    KEY_W_w     = $1a
    KEY_X_x     = $1b
    KEY_Y_y     = $1c
    KEY_Z_z     = $1d
' Digit scancodes and their shifted characters:
    KEY_1       = $1e   ' 1 and !
    KEY_2       = $1f   ' 2 and @
    KEY_3       = $20   ' 3 and #
    KEY_4       = $21   ' 4 and $
    KEY_5       = $22   ' 5 and %
    KEY_6       = $23   ' 6 and ^
    KEY_7       = $24   ' 7 and &
    KEY_8       = $25   ' 8 and *
    KEY_9       = $26   ' 9 and (
    KEY_0       = $27   ' 0 and )
' Keyboard return (ENTER), tab, space, etc.:
    KEY_ENTER   = $28
    KEY_ESC     = $29
    KEY_BKSPACE = $2a   ' Delete (backspace)
    KEY_TAB     = $2b
    KEY_SPACE   = $2c
    KEY_MINUS   = $2d   ' - and _
    KEY_EQUAL   = $2e   ' = and +
    KEY_LBRACE  = $2f   ' [ and {
    KEY_RBRACE  = $30   ' ] and }
    KEY_BSLASH  = $31   ' \ and |
    KEY_HASHTIL = $32   ' None-US # and ~
    KEY_SEMICOL = $33   ' ; and :
    KEY_APOST   = $34   ' ' and "
    KEY_GRAVE   = $35   ' ` and ~
    KEY_COMMA   = $36   ' , and <
    KEY_DOT     = $37   ' . and >
    KEY_SLASH   = $38   ' / and ?
    KEY_CAPSLK  = $39
' Function key scancodes:
    KEY_F1      = $3a
    KEY_F2      = $3b
    KEY_F3      = $3c
    KEY_F4      = $3d
    KEY_F5      = $3e
    KEY_F6      = $3f
    KEY_F7      = $40
    KEY_F8      = $41
    KEY_F9      = $42
    KEY_F10     = $43
    KEY_F11     = $44
    KEY_F12     = $45
' Special, toggle and movement keys:
    KEY_PRTSCN  = $46
    KEY_SCRLK   = $47
    KEY_PAUSE   = $48
    KEY_INSERT  = $49
    KEY_HOME    = $4a
    KEY_PAGEUP  = $4b
    KEY_DELETE  = $4c
    KEY_END     = $4d
    KEY_PAGEDWN = $4e
    KEY_RIGHT   = $4f
    KEY_LEFT    = $50
    KEY_DOWN    = $51
    KEY_UP      = $52
' Keypad keys:
    KEY_KPNUMLK = $53
    KEY_KPSLASH = $54
    KEY_KPASTER = $55
    KEY_KPMINUS = $56
    KEY_KPPLUS  = $57
    KEY_KPENTER = $58
    KEY_KP1END  = $59
    KEY_KP2DWN  = $5a
    KEY_KP3PGD  = $5b
    KEY_KP4LFT  = $5c
    KEY_KP5     = $5d
    KEY_KP6RT   = $5e
    KEY_KP7HOME = $5f
    KEY_KP8UP   = $60
    KEY_KP9PGU  = $61
    KEY_KP0INS  = $62
    KEY_DOT_DEL = $63
    KEY_NOUS_SP = $64   ' Non-US \ and |
    KEY_APP     = $65   ' Application key
'------------------------------------------------------------------------------
' Keyboard LED output report toggle key bit postions:
    #0, LED_NUMLKB, LED_CAPSLKB, LED_SCRLKB, LED_COMPOSEB
    LED_KANAB, LED_CONST0B, LED_CONST1B, LED_CONST2B
' Keyboard LED output report toggle key bitflags:
    LED_NUMLKF   = decod(LED_NUMLKB)
    LED_CAPSLKF  = decod(LED_CAPSLKB)
    LED_SCRLKF   = decod(LED_SCRLKB)
    LED_COMPOSEF = decod(LED_COMPOSEB)
    LED_KANAF    = decod(LED_KANAB)
    LED_CONST0F  = decod(LED_CONST0B)
    LED_CONST1F  = decod(LED_CONST1B)
    LED_CONST2F  = decod(LED_CONST2B)
' Keyboard modifier key bit positions:
    #0, LEFT_CTRLB, LEFT_SHIFTB, LEFT_ALTB, LEFT_GUIB
    RIGHT_CTRLB, RIGHT_SHIFTB, RIGHT_ALTB, RIGHT_GUIB
' Keyboard modifier bitflags
    LEFT_CTRLF   = decod(LEFT_CTRLB)
    LEFT_SHIFTF  = decod(LEFT_SHIFTB)
    LEFT_ALTF    = decod(LEFT_ALTB)
    LEFT_GUIF    = decod(LEFT_GUIB)
    RIGHT_CTRLF  = decod(RIGHT_CTRLB)
    RIGHT_SHIFTF = decod(RIGHT_SHIFTB)
    RIGHT_ALTF   = decod(RIGHT_ALTB)
    RIGHT_GUIF   = decod(RIGHT_GUIB)
' Consolidated left/right modkeys. Add the modifier keys you want to trap
' together, then add the key scan code e.g. CTRL+ALT+"X" would be: $600 + KEY_X_x.
' The key() Spin2 method does this and the rawKey() method returns the key data
' as packed by the USB keyboard driver.
'   SHIFT = $100
'   CTRL  = $200
'   ALT   = $400
'   APP   = $800
' L|R key modifier flag combinations:
    KEYS_APP   = LEFT_GUIF + RIGHT_GUIF
    KEYS_ALT   = LEFT_ALTF + RIGHT_ALTF
    KEYS_CTRL  = LEFT_CTRLF + RIGHT_CTRLF
    KEYS_SHIFT = LEFT_SHIFTF + RIGHT_SHIFTF
'------------------------------------------------------------------------------
' #endregion (Shared constants)
' #region (P2 USB Smart Pins)
'------------------------------------------------------------------------------
' Low/full speed one-cog USB host with integrated mouse/keyboard "boot
' protocol" support.
'------------------------------------------------------------------------------
' P2-ES Evaluation Board:
'   The USB D-/D+ data lines must be an adjacent even/odd pin pair, with the
'   lower (even) pin# assigned to D- constant and the upper (odd) pin assigned
'   to the DP constant.
'   Mouse/keyboard activity is shown on the P56 LED.
'   Host status changes are posted to the mouse/keyboard data area in hub RAM.
'------------------------------------------------------------------------------
{
  USB references:
  Universal Serial Bus Specification, Revision 2.0
    www.usb.org/developers/docs/usb20_docs/
  Device Class Definition for Human Interface Devices (HID), Version 1.11
    www.usb.org/developers/hidpage/

Smart pin configuration bits:
  D/# = %AAAA_BBBB_FFF_PPPPPPPPPPPPP_TT_MMMMM_0

USB smart pin modes (FPGA, P2 RevA):
  %11000 = USB host, low-speed
  %11001 = USB host, full-speed
  %11010 = USB device, low-speed
  %11011 = USB device, full-speed

USB smart pin modes (P2 RevB):
  All USB smart pin modes have been consolidated to %11011.
  WXPIN is now used to set up the sub-modes and the NCO:
  bit 15 = 0 for device mode, 1 for host mode
  bit 14 = 0 for low-speed mode, 1 for full-speed mode
  bits 13..0 = NCO frequency

This mode requires that two adjacent pins be configured together to form a USB pair, whose OUTs
will be overridden to control their output states. These pins must be an even/odd pair, having only
the LSb of their pin numbers different. For example: pins 0 and 1, pins 2 and 3, and pins 4 and 5
can form USB pairs. They can be configured via WRPIN with identical D data of %1_11011_0. Using D
data of %0_11011_0 will disable output drive and effectively create a USB 'sniffer'. A new WRPIN
can be done to effect such a change without resetting the smart pin. NOTE: In Propeller 2 emulation
on an FPGA, there are no built-in 1.5k and 15k resistors, like the ASIC smart pins have, so it is
up to you to install these yourself on the D+ and D- lines.

WXPIN is used on the lower pin to establish the specific USB mode and set the baud rate. D[15] must
be 1 for 'host' or 0 for 'device'. D[14] must be 1 for 'full-speed' or 0 for 'low-speed'. D[13:0]
sets the baud rate, which is a 16-bit fraction of the system clock, whose two MSBs must be 0,
necessitating that the baud rate be less than 1/4th of the system clock frequency. For example, if
the main clock is 80MHz and you want a 12MHz baud rate (full-speed), use 12,000,000 / 80,000,000 *
$10000 = 9830, or $2666. To use this baud rate and select 'host' mode and 'full-speed', you could
do 'WXPIN ##$E666,lowerpin'.


The upper (odd) pin is the D+ pin. This pin's IN is raised whenever the output buffer empties,
signaling that a new output byte can be written via WYPIN to the lower (even) pin. No WXPIN/WYPIN
instructions are used for this pin.

The lower (even) pin is the D- pin. This pin's IN is raised whenever a change of status occurs in
the receiver, at which point a RDPIN/RQPIN can be used on this pin to read the 16-bit status word.
WXPIN is used on this pin to set the NCO baud rate.

These D+/D- electrical designations can actually be switched by swapping low-speed and full-speed
modes, due to USB's mirrored line signaling.

To start USB, clear the DIR bits of the intended two pins and configure them each via WRPIN. Use
WXPIN on the lower pin to set the baud rate. Then, set the pins' DIR bits. You are now ready to
read the receiver status via RDPIN/RQPIN and set output states and send packets via WYPIN, both on
the lower pin.

To affect the line states or send a packet, use WYPIN on the lower pin. Here are its D values:
 0 = output IDLE        - default state, float pins, except possible resistor(s) to 3.3V or GND
 1 = output SE0         - drive both D+ and D- low
 2 = output K           - drive K state onto D+ and D- (opposite)
 3 = output J           - drive J state onto D+ and D- (opposite), like IDLE, but driven
 4 = output EOP         - output end-of-packet: SE0, SE0, J, then IDLE
 $80 = SOP              - output start-of-packet, then bytes, automatic EOP when buffer runs out

To send a packet, first do a 'WYPIN #$80, lowerpin'. Then, after each IN rise on the upper pin,
do a 'AKPIN upperpin', followed by a 'WYPIN byte, lowerpin' to buffer the next byte. The
transmitter will automatically send an EOP when you stop giving it bytes. To keep the output buffer
from overflowing, you should always verify that the upper pin's IN was raised after each WYPIN,
before issuing another WYPIN, even if you are just setting a state. The reason for this is that all
output activity is timed to the baud generator and even state changes must wait for the next bit
period before being implemented, at which time the output buffer empties.

There are separate state machines for transmitting and receiving. Only the baud generator is common
between them. The transmitter was just described above. Below, the receiver is detailed. Note that
the receiver receives not just input from another host/device, but all local output, as well.

At any time, a RDPIN/RQPIN can be executed on the lower pin to read the current 16-bit status of the
receiver, with the error flag going into C. The lower pinā¤s IN will be raised whenever a change
occurs in the receiverā¤s status. This will necessitate A WRPIN/WXPIN/WYPIN/RDPIN/AKPIN before IN can
be raised again, to alert of the next change in status.

NOTE that after the pin is acknowledged, it will take at least two clocks for IN to drop, before it
can be polled again:
    AKPIN    pin                   ' Acknowledge smart pin, releases IN from high
    NOP                            ' Elapse two clocks (or more)
    TESTP    pin         WC        ' IN  can now be polled again

The receiver's status bits are as follows:
 [31:16] <unused>                - $0000
 [15:8]  byte                    - last byte received
 [7]     byte toggle             - cleared on SOP, toggled on each byte received
 [6]     error                   - cleared on SOP, set on bit-unstuff error, EOP SE0 > 3 bits, or SE1
 [5]     EOP in                  - cleared on SOP or 7+ bits of J or K, set on EOP
 [4]     SOP in                  - cleared on EOP or 7+ bits of J or K, set on SOP
 [3]     steady-state indicator  - cleared on line change, set on 7+ bits of no line change
 [2]     SE0 in         (RESET)  - cleared on !SE0, set on 1+ bits of SE0
 [1]     K in           (RESUME) - cleared on !K, set on 1+ bits of K
 [0]     J in           (IDLE)   - cleared on !J, set on 1+ bits of J

The result of a RDPIN/RQPIN can be bit-tested for events of interest. It can also be shifted right
by 8 bits to LSB-justify the last byte received and get the byte toggle bit into C, in order to
determine if you have a new byte. Assume that 'flag' is initially zero:
         SHR     D,#8    WC      'get byte into D, get toggle bit into C
         CMPX    flag,#1 WZ      'compare toggle bit to flag, new byte if Z
  IF_Z   XOR     flag,#1         'if new byte, toggle flag
  IF_Z   <use byte>              'if new byte, do something with it
}
'------------------------------------------------------------------------------
' USB References:
' Universal Serial Bus Specification, Revision 2.0
'   www.usb.org/developers/docs/usb20_docs/
' Device Class Definition for Human Interface Devices (HID), Version 1.11
'   www.usb.org/developers/hidpage/
' Universal Serial Bus (USB) HID Usage Tables, Version 1.12
'   www.usb.org/developers/hidpage/Hut1_12v2.pdf
'------------------------------------------------------------------------------
' #endregion (Basics of P2 USB Smart Pins)
' #region USB host constants)
'------------------------------------------------------------------------------
    _1thou         = 1_000
    _1m            = 1_000_000
    _1b            = 1_000_000_000
    _12m           = 12_000_000
    LSBTns         = 672.0                  ' Low-Speed bit period, in nanoseconds
    FSBTns         = 83.54                  ' Full-Speed bit period, in nanoseconds
    LSBTns4        = round(LSBTns * 4.0)    ' Low-Speed inter-packet delay, in nanoseconds
    LSBTns22       = round(LSBTns * 22.0)   ' Low-Speed turnaround wait time, in nanoseconds
    FSBTns4        = round(FSBTns * 4.0)    ' Full-Speed inter-packet delay, in nanoseconds
    FSBTns28       = round(FSBTns * 28.0)   ' Full-Speed turnaround wait time, in nanoseconds
' NCO baud calculations for low-speed/full-speed:
'        _1_5Mbps  = round(1_500_000.0 / _FCLKFREQ * 65536.0)  ' = NCO 492 @200MHz, 614 @160
'        _12Mbps   = round(12_000_000.0 / _FCLKFREQ * 65536.0) ' = NCO 3932 @200MHz, 4915 @160
' To configure RevA silicon LS/FS NCO baud: (WXPIN _1_5Mbps, D-) or (WXPIN _12Mbps, D-)
' To configure RevA LS/FS host/device: WRPIN appropriate LS/FS mode to D- (lower) and D+ (upper) pins:
    USB_V1HMODE_LS = %1_11000_0 + 1 << 16   ' Host mode
    USB_V1DMODE_LS = %1_11010_0 + 1 << 16   ' Device mode
    USB_V1HMODE_FS = %1_11001_0 + 1 << 16
    USB_V1DMODE_FS = %1_11011_0 + 1 << 16
' On RevB+ the USB smart pin mode is set to the D- and D+ pins via (WRPIN USBMode, pin#):
    USB_V2_DRVOUT  = %1_11011_0 + 1 << 16
    USB_V2_SNIFF   = %0_11011_0 + 1 << 16   ' Disable output to create a USB "sniffer"
' To configure NCO baud, host/device for LS/FS, set the appropriate mode to the D- pin# (WXPIN NCOMode, D-)
'   USB_H_LS_NCO   = %10 << 14 + _1_5Mbps   ' Host mode at NCO baud
'   USB_D_LS_NCO   = %00 << 14 + _1_5Mbps   ' Device mode at NCO baud
'   USB_H_FS_NCO   = %11 << 14 + _12Mbps
'   USB_D_FS_NCO   = %01 << 14 + _12Mbps
'------------------------------------------------------------------------------
' Time delays and intervals
' Useful USB constants and wait intervals:
    XFER_RETRIES    = 12                    ' Maximum retries before retiring a transfer
    TXN_RETRIES     = 12                    ' Maximum retries before retiring a transaction
    NAK_NOLIMIT     = 0                     ' Unlimited NAK retries
    IN_NAK_RETRIES  = 50000                 ' Control transfer IN-NAK retry limit (0 = unlimited)
    OUT_NAK_RETRIES = 50000                 ' Control transfer OUT-NAK retry limit (0 = unlimited)
' Standard Device request maximum timeout periods (reference):
{
    TO_STANDARD  = _1ms * 5000              ' Non-specific maximum timout period
    TO_DATA      = _1ms * 500               ' Standard Device requests with a data stage
    TO_NODATA    = _1ms * 50                ' Standard Device requests without a data stage
    TO_SETADDR   = _1ms * 50                ' Device SetAddress() command processing maximum
    TO_CHGADDR   = _1ms * 2                 ' Device SetAddress() period allowed to change its address before next request sent
}
'------------------------------------------------------------------------------
' Token packet format.
'------------------------------------------------------------------------------
'                   CRC5  ENDP ADDRESS PID
    CRC_MASK     = %11111_0000_0000000_00000000
    EP_MASK      = %00000_1111_0000000_00000000
    ADDR_MASK    = %00000_0000_1111111_00000000
    EP_ADDR_MASK = %00000_1111_1111111_11111111
    EP_ADDR_ZERO = %00010_0000_0000000_00000000     ' CRC5 = $02 for addr zero, ep zero
'------------------------------------------------------------------------------
' Packet Identifier Bytes (PID). Notice that the first two LSBits are
' identical for each group.
'------------------------------------------------------------------------------
' Token:
    PID_OUT   = %1110_0001                  ' $e1
    PID_IN    = %0110_1001                  ' $69
    PID_SOF   = %1010_0101                  ' $a5
    PID_SETUP = %0010_1101                  ' $2d

' Data:
    PID_DATA0 = %1100_0011                  ' $c3
    PID_DATA1 = %0100_1011                  ' $4b
    PID_DATA2 = %1000_0111                  ' $87
    PID_MDATA = %0000_1111                  ' $0f

' Handshake:
    PID_ACK   = %1101_0010                  ' $d2
    PID_NAK   = %0101_1010                  ' $5a
    PID_STALL = %0001_1110                  ' $1e
    PID_NYET  = %1001_0110                  ' $96
' Special:
    PID_PRE   = %0011_1100                  ' $3c
    PID_ERR   = %0011_1100                  ' $3c
    PID_SPLIT = %0111_1000                  ' $78
    PID_PING  = %1011_0100                  ' $b4
    PID_RESVD = %1111_0000                  ' $f0
' Tx, rx and host related constants
' USB transmitter WYPIN D line state options:
    OUT_IDLE = 0
    OUT_SE0  = 1
    OUT_K    = 2
    OUT_J    = 3
    OUT_EOP  = 4
    OUT_SOP  = $80
' USB receiver RDPIN status bit positions:
    #0, J_IDLEB, K_RESUMEB, SE0_RESETB, SE1_BADB, SOPB, EOPB, BUS_ERRB, BYTE_TGLB
' USB receiver RDPIN status bitflags:
    J_IDLEF    = decod(J_IDLEB)
    K_RESUMEF  = decod(K_RESUMEB)
    SE0_RESETF = decod(SE0_RESETB)
    SE1_BADF   = decod(SE1_BADB)
    SOPF       = decod(SOPB)
    EOPF       = decod(EOPB)
    BUS_ERRF   = decod(BUS_ERRB)
    BYTE_TGLF  = decod(BYTE_TGLB)
' USB CRC constants:
    USB5_POLY      = %0_0101 rev (5 - 1)    ' USB5 polynomial is reflected when calculating CRC
    USB5_RESIDUAL  = %0_1100 rev (5 - 1)    ' Expected CRC5 residual value when checking received data
    USB16_POLY     = $8005 rev (16 - 1)     ' USB16 polynomial is reflected when calculating CRC
    USB16_RESIDUAL = $800d rev (16 - 1)     ' Expected CRC16 residual value when checking received data
' Host->class driver USB connect speed:
    #0, USB_SPEED_UNKNOWN, USB_SPEED_LOW, USB_SPEED_FULL
' Debug stuff:
    DBG_DEADBEEF = $efbeadde                ' Handy byte sequence for hex search in .obj files
    DBG_C0DEBEEF = $efbedec0                ' Code block marker
    DBG_DADABEEF = $efbedada                ' Data marker
' Host status bit positions. Bit4 and bit5 use the receiver status constants for SOP and EOP:
    #0, IDLEB, CONNECTEDB, LOW_SPEEDB, DATAx_TGLB[4], DWNSTRM_HUBB, SUSPENDB
' Host status bitflags. Unless otherwise noted, bit states are active high:
    IDLEF        = decod(IDLEB)             ' Set when USB in idle state
    CONNECTEDF   = decod(CONNECTEDB)        ' USB device connected
    LOW_SPEEDF   = decod(LOW_SPEEDB)        ' Low-speed device connected, clear if full-speed
    DATAx_TGLF   = decod(DATAx_TGLB)        ' Cleared if sending DATA0 packet, set if sending DATA1 packet
'   EOPF         = decod(EOPB)              ' Same bit position as the USB rx RDPIN status constants defined above
'   BUS_ERRF     = decod(BUS_ERRB)          ' Same bit position as the USB rx RDPIN status constants defined above
'   BYTE_TGLF    = decod(BYTE_TGLB)         ' Same bit position as the USB rx RDPIN status constants defined above
    DWNSTRM_HUBF = decod(DWNSTRM_HUBB)      ' NYI: downstream hub(s) connected
    SUSPENDF     = decod(SUSPENDB)          ' NYI: command the host to signal a global suspend
'------------------------------------------------------------------------------
' Keyboard and mouse constants:
'------------------------------------------------------------------------------
' Keyboard interrupt endpoint poll interval and auto-repeat timing. Since the
' interrupt IN transactions are executed on a timed basis, use that to
' calculate auto-repeat initial delay and repeat rate.
'------------------------------------------------------------------------------
'    KBD_POLL_INTERVAL = _1ms * 8            ' Interrupt IN txn timespan
    KBD_REPEAT_DELAY  = 62                  ' 62 * 8ms = 496ms initial delay
    KBD_REPEAT_RATE   = 5                   ' 5 * 8ms = 40ms repeat rate
'------------------------------------------------------------------------------
' Mouse interrupt endpoint poll interval:
'------------------------------------------------------------------------------
'    MOUSE_POLL_INTERVAL = _1ms * 8          ' Interrupt IN txn timespan
    MOUSE_NAK_DELAY     = 62                ' USER_LED feedback blink delay uses the NAK count
'------------------------------------------------------------------------------
' P2 silicon revision detection:
'------------------------------------------------------------------------------
    #1, P2RevA, P2RevB
    #0, EVENT_P, ACTIVE_LED, DM_P, ERR_LED
'------------------------------------------------------------------------------
' VAR parameter constants and data offsets:
    KBD_BUFFMASK = $0f    ' Keyboard key data buffer size
    PAR_KBDTAIL  = 0      ' USB host VAR base offset
    PAR_KBDHEAD  = 4
    PAR_KBDBUFF  = 8
    PAR_PORTCHAR = PAR_KBDBUFF + (KBD_BUFFMASK + 1 * 4)
'------------------------------------------------------------------------------
' Startup parameter count:
    PAR_COUNT = 24
'------------------------------------------------------------------------------
' #endregion (USB host constants)
' #region (Spin DAT VAR data and PUB/PRI methods)
DAT
'------------------------------------------------------------------------------
' The table below is used to initialize the host pasm cog's "PAR" registers
' with the BYTE/WORD/LONG data type and length of the spin object's VAR section
' variables for this object instance. This table must match the first PAR_COUNT
' VAR declarations in (type BYTE/WORD/LONG * length) format.
'------------------------------------------------------------------------------
par_block_init  byte $04, $04, $04, $04, $04, 4 * (KBD_BUFFMASK + 1), $01
                byte $01, $01, $01, $01, $01, $01, $01, KBD_IN_RPT_LEN, KBD_IN_RPT_LEN
                byte $01, $01, $01, $01, $01, MOUSE_RPT_LEN, $01, $01
VAR
' Parameter block start address passed to the USB cog via COGNEW parameter:    MSB                              LSB
  long dbgData        ' Can go away if/when FastSpin supports PNut's debug program
  long initData       ' Byte packed run-time values defined by the client: |err_led_pin| dm_pin |tgl_led_pin|event_pin|
  long cmdData
  long frameBias
' Class driver vars for boot protocol keyboard and mouse.
  long mouseData
  long kbBuff[KBD_BUFFMASK + 1]    ' Keyboard FIFO buffer
  byte kbTail
  byte kbHead
' Keyboard/mouse run-time vars.
  byte kbIntfNum
  byte kbInterval
  byte kbInMaxPkt
  byte kbNextDatax
  byte kbMaxIndex
  byte kbLedStates
  byte kbCurReport[KBD_IN_RPT_LEN]
  byte kbPreReport[KBD_IN_RPT_LEN]
  byte kbLastKey
  byte msIntfNum
  byte msInterval
  byte msInMaxPkt
  byte msNextDatax
  byte msCurReport[MOUSE_RPT_LEN]
  byte usbErrCode
  byte parend
' Parameter block end
  byte cogNum

'' Caller must define all I/O pins required by the USB host. This implementation
'' uses the Parallax P2 Eval "Serial Host" add-on board, which requires a
'' contiguous four-pin block starting at an even pin number. All host pin
'' assignments are configured in the "usb_host_init" code block.
'' Returns cog number, or -1 if cog not available.
PUB start(base_pin, enable_pin, dm_pin, err_led_pin, pMouseData) : cog
'RJA:  Adding handling of mouse data to this driver
'RJA:  Adding explicit enable pin that can be set to -1 to not use
'Setting pointers in assembly code before starting cog
    pMouseX:=pMouseData
    pMouseY:=pMouseData+4
    pMouseMaxX:=pMouseData+8
    pMouseMaxY:=pMouseData+12
    pMouseButtons:=pMouseData+16
    pKeyReport:= pMouseData+20

'Enable USB power, if enable_pin>0
  if (enable_pin>0)
    pinh(enable_pin)


  frameBias := -14
  initData.byte[EVENT_P] := base_pin            ' Basepin is event mailbox smartpin
  initData.byte[ACTIVE_LED] := base_pin         ' and also the Host activity LED (active high)
  initData.byte[DM_P] := dm_pin                 ' USB D- pin (must be even pin#), D+ pin must follow
  initData.byte[ERR_LED] := err_led_pin         ' LED to light if fatal USB error (active low)

' Start up the USB host cog. Checkout a lock semaphore
  dbgData := @par_block_init                      ' Pass the USB cog the address of the PAR offset table
  cog := coginit(newcog, @usb_host_start, @dbgData)
    waitms(500)
  cogNum := cog

'' Get mouse data from the driver buffer (never waits).
'-+----------+-----------+-----------+--------------+
' |  Byte3   |   Byte2   |   Byte1   |    Byte0     |
'-+----------+-----------+-----------+--------------+
' | Reserved | Y Dir/Vel | X Dir/Vel | Button Flags |
'-+----------+-----------+-----------+--------------+

PUB Stop()
    cogstop(cogNum)

PUB mouse(nil) : data
  data := mouseData

'' Get new key from the buffer, zero if no key (never waits).
'' NOTE: This method returns key data in the same format as the
'-+-------------------+--------------------+----------+-------------+
' |  Byte3, bits 7..0 |  Byte2, bits 3..0  |   Byte1  |    Byte0    |
'-+-------------------+--------------------+----------+-------------+
' | Toggle Key States | APP|ALT|CTRL|SHIFT | Scancode | ASCII Value |
'-+-------------------+--------------------+----------+-------------+
PUB rawKey() : data
  if kbTail <> kbHead
    data := kbBuff[kbTail]
    kbTail := ++kbTail & KBD_BUFFMASK

'' Get a new key from the buffer, zero if no key (never waits). Combine the
'' left/right CTRL, ALT, SHIFT keys and the "Application" key scan codes to
'' make it easier to set up a CASE block using [modifier(s)|scancode] via
'' CASE data.WORD[0].
'-+-------------------+-------------+--------------------+----------+
' |  Byte3, bits 7..0 |    Byte2    |  Byte1, bits 3..0  |   Byte0  |
'-+-------------------+-------------+--------------------+----------+
' | Toggle Key States | ASCII Value | APP|ALT|CTRL|SHIFT | Scancode |
'-+-------------------+-------------+--------------------+----------+
PUB key(nil) : data | tmp
  if kbTail <> kbHead
    tmp := kbBuff[kbTail]
    kbTail := ++kbTail & KBD_BUFFMASK
    data.byte[3] := tmp.byte[3]
    data.byte[2] := tmp.byte[0]
    if (tmp.byte[2] & KEYS_APP)
      data += 1 << 11
    if (tmp.byte[2] & KEYS_ALT)
      data += 1 << 10
    if (tmp.byte[2] & KEYS_CTRL)
      data += 1 << 9
    if (tmp.byte[2] & KEYS_SHIFT)
      data += 1 << 8
    data += tmp.byte[1]

'' Poll the state of the USB event smart pin long respository.
'' If data is available, returns the event ID, otherwise zero (NO_EVENT).
PUB eventCheck(nil) : eventid
  if pinr(initData.byte[EVENT_P])
    eventid := rdpin(initData.byte[EVENT_P])

'' Post a synchronous user command to the USB host. An event with the cmd
'' name will be posted by the host when the cmd has completed. The cmd ID is
'' in bits 7..0 and bits 31..8 may contain additional data, depending on the
'' cmd that was posted.
'' Zero on success else ERR_* code.
PUB postCmd(cmd) : result
  case cmd
    CMD_RESET:
      result := execCmd(cmd)
    CMD_RESUME:
' After resume, a bus reset will ensure that the host and device get fully
' synchronized again.
      ifnot (result := execCmd(cmd))
        result := execCmd(CMD_RESET)
    CMD_SUSPEND:
      result := execCmd(cmd)
    other:
      result := ERR_CMD         ' Unknown cmd
  usbErrCode := result

'' Post a command to the USB cog and wait for the result.
'' Returns zero if the host cog recognizes the command. If it fails, the error
'' will be reported to the client via the event repository smart pin, otherwise
'' ERR_TIMEOUT.
PUB execCmd(cmd) : result | timeout
  cmdData := cmd
  timeout := getsec() + 3     ' Give the cmd up to five seconds to complete
  repeat
    if getsec() > timeout
      result := ERR_TIMEOUT
      return
  until cmdData == ERR_NONE   ' Wait until cmd acknowledged by host

'' Start a USB suspend->resume sequence. If the sequence is complete the
'' NO_EVENT token is returned.
PUB tglSuspendResume(curState) : newstate
  case curState
    NO_EVENT:    ' Initiate a suspend->resume sequence
      ifnot (newstate := execCmd(CMD_SUSPEND))
        newstate := CMD_SUSPEND
    CMD_SUSPEND:
      if (newstate := execCmd(CMD_RESUME)) == CMD_RESUME
        newstate := NO_EVENT    ' Sequence complete

PUB flushKeys() | idx
  repeat while kbTail <> kbHead
    kbBuff[kbTail] := 0
    kbTail := ++kbTail & KBD_BUFFMASK
' Clear the key report buffers
  repeat idx from 0 to KBD_IN_RPT_LEN
    kbCurReport[idx] := 0
    kbPreReport[idx + KBD_IN_RPT_LEN] := 0
{
PUB flushMouse() | idx
  repeat idx from 0 to MOUSE_RPT_LEN
    msCurReport[idx] := 0
}
'' Read the current USB error code. Returns the error code and a pointer to the
'' error description text.
PUB getError(nil) : code, szptr | len, idx
  code := usbErrCode
  szptr := @sz_none
  repeat idx from 0 to code
    ifnot idx == code
      szptr += strsize(szptr) + 1

'' Get the current version of the USB keyboard/mouse object.
PUB getVersion(nil) : data
  data := @sz_usb_kbm_ver                               ' ASCIIZ version string pointer

'' FastSpin supports P2RevA/B+, PNut supports B+.
PUB getP2Rev(nil) : szRev
  szRev.byte[0] := byte[@p2rev_char]
  if szRev == "B"
    szRev.byte[1] := "+"    ' RevB or greater

'' Get object cog number, or -1 if no cog running.
PUB getCogId() : id
  id := (cogNum >= 0) ? cogNum : -1

'' Tweak the USB 1ms frame timer sysclocks by +/- N clocks.
'PUB tweakFrameClks(val)
'  frameBias += val

'' Arbitrary long debug info value. This long can be written to in the USB code
'' and the client can be notified by triggering the DBG_DATA event.
{
PUB getDebugData() : data
  data := dbgData
}
' #endregion (Spin VAR and methods)
' #region (USB host cog)
DAT
                org
' /* usb_host_start
'------------------------------------------------------------------------------
' The USB host cog. PTRA has a PAR value, PTRB the base address of this cog.
'------------------------------------------------------------------------------
usb_host_start
                mov     hcog_base_addr, ptrb
                rdlong  pb, ptra      ' Get the address of the VAR pointer data type and length table
' Initialize the startup cog parameter block to access the object VAR data
                mov     htmp, #dbg_data_p
                rep     @.end, #PAR_COUNT
                altd    htmp
                mov     0-0, ptra
                add     htmp, #1
                rdbyte  htmp1, pb     ' Read the byte count required by this VAR definition
                add     ptra, htmp1
                add     pb, #1
.end
' Copy lut execution code from hub to lut
                mov     htmp, ##@hlut_end - 4 - @hlut_start ' Dealing with hub addresses
                shr     htmp, #2      ' so byte->long for the lut cell count
                loc     pb, #@hlut_start - @usb_host_start
                add     pb, hcog_base_addr
                setq2   htmp
                rdlong  0, pb         ' Do the hub->lut copy
                loc     pb, #@usb_host_init - @usb_host_start
                add     pb, hcog_base_addr
                jmp     pb            ' Initialize host and enter main processing loop
' */
' /* txn_setup
'------------------------------------------------------------------------------
' SETUP transaction. The mechanics of SETUP are identical to OUT, but it's
' special because the receiving function must not respond with either STALL or
' NAK, and must accept the DATAx packet that follows the SETUP token. If a
' non-control endpoint receives a SETUP token, or the function receives a
' corrupt packet, it must ignore the transaction
'------------------------------------------------------------------------------
' On entry:
'   PTRA - start address of the SETUP data struct.
' On exit:
'   retval - PID_ACK on success, otherwise error code.
'------------------------------------------------------------------------------
txn_setup
                setbyte ep_addr_pid, #PID_SETUP, #0
                mov     pkt_data, #SETUP_TXN_LEN        ' SETUP is single fixed size DATAx packet
                bitl    hstatus, #DATAx_TGLB            ' And always uses DATA0 packet
                mov     retry, #TXN_RETRIES             ' Retries possible as function will ignore a corrupt packet
                mov     pa, ptra                        ' Save SETUP struct pointer in case of retry
.setup
                call    #txn_out                        ' SETUP/OUT are the same transaction type, just different PIDs
                cmp     retval, #PID_ACK        wz
        if_z    ret
                call    #retry_wait
                cmp     retval, #ERR_TXN_RETRY  wz
        if_z    ret
                mov     ptra, pa                        ' Restore SETUP's DATAx pointer
                jmp     #.setup
' */
' /* txn_in
'------------------------------------------------------------------------------
' IN/INTERRUPT transaction.
' Possible function response: STALL or NAK handshake, or DATAx packet.
'------------------------------------------------------------------------------
' On entry:
'   ep_addr_pid - PID_IN(b0..7), address(b8..b14), endpoint(b15..18) and
'     CRC(b19..23).
' On exit:
'------------------------------------------------------------------------------
txn_in
                call    #wait_txn_ok                    ' ISR: ensure txn doesn't cross frame boundary
                setbyte ep_addr_pid, #PID_IN, #0
                call    #utx_token                      ' Put IN request on the bus
' Fall through to urx_packet
' vvvvvvvvvvvvvvvvvvvvvvvvvv
' */
' /* urx_packet
'------------------------------------------------------------------------------
' Wait for a packet from a device/function. As host, the only two packet types
' received are handshakes and IN DATAx.
'------------------------------------------------------------------------------
' On entry:
' On exit:
'   retval - the ID of the packet. If a PID fails validation, ERR_PACKET is
'     returned.
'------------------------------------------------------------------------------
urx_packet
                rqpin   urx, dm                         ' Wait until start-of-packet signal appears on the USB.
                testb   urx, #SOPB              wc
        if_c    jmp     #urx_packet
                getct   hct2
                addct2  hct2, tat_wait                  ' Start the response turn-around timer
                bitl    hstatus, #EOPB                  ' Make sure sticky EOP flag is clear
                mov     newb_flg, #0                    ' Initialize for multi-byte read
.wait_sop
                rdpin   urx, dm
                testb   urx, #SOPB              wc
        if_c    jmp     #.get_pid
                jnct2   #.wait_sop
        _ret_   mov     retval, #ERR_TAT
.get_pid
                call    #urx_next
                testb   urx, #BUS_ERRB          wc
        if_nc   jmp     #.chk_pid
        _ret_   mov     retval, #ERR_URX
.chk_pid
                cmp     retval, #PID_ACK        wz
        if_nz   cmp     retval, #PID_NAK        wz
        if_nz   cmp     retval, #PID_STALL      wz
        if_z    jmp     #.chk_eop                       ' Handshake, so check that packet is single byte
                testb   hstatus, #DATAx_TGLB    wc      ' Get low/full speed even/odd DATAx sequence to look for
                cmp     retval, #PID_DATA0      wz
   if_z_and_nc  jmp     #urx_data                       ' DATA0 and sequence match
   if_z_and_c   jmp     #.ack_resend                    ' Sequence error. Ignore data, resend the ACK that the device must have missed
                cmp     retval, #PID_DATA1      wz
   if_z_and_c   jmp     #urx_data                       ' DATA1 and sequence match
   if_z_and_nc  jmp     #.ack_resend
        _ret_   mov     retval, #ERR_PACKET             ' Some other bus error...
.ack_resend
                rqpin   urx, dm
                testb   urx, #EOPB              wc
        if_nc   jmp     #.ack_resend
                mov     retval, #PID_ACK
                call    #utx_handshake                  ' Send handshake PID and return to caller
        _ret_   mov     retval, #ERR_DATAX_SYNC
.chk_eop
                testb   hstatus, #LOW_SPEEDB    wc
        if_nc   jmp     #.idle                          ' Full-speed doesn't need an additional read to get EOP status
                call    #urx_next                       ' Low-speed requires an additional read to get EOP status
                testb   hstatus, #EOPB          wc
        if_c    jmp     #.idle                          ' Low-speed EOP seen
                testb   urx, #BUS_ERRB          wz
        if_nc   mov     retval, #ERR_PACKET             ' No EOP where one was expected
        if_z    mov     retval, #ERR_URX                ' Bit unstuff error, EOP SE0 > 3 bits or SE1, so we're hosed
                ret
.idle
                rqpin   urx, dm
                testb   urx, #J_IDLEB           wc
        if_nc   jmp     #.idle                          ' Wait for bus IDLE before returning handshake result
                ret
' */
' /* utx_token
'------------------------------------------------------------------------------
' Send a token packet with CRC5 checksum of address and endpoint. It is the
' responsibility of the caller to append the appropriate inter-packet delay,
' if one is required.
'------------------------------------------------------------------------------
' On entry:
'   ep_addr_pid - packed with the PID, address and endpoint.
' On exit:
'------------------------------------------------------------------------------
utx_token
                rqpin   urx, dm
                testb   urx, #J_IDLEB           wc
        if_nc   jmp     #utx_token
                mov     utx, #OUT_SOP
                call    #utx_byte                       ' Send sync byte
                mov     htmp, ep_addr_pid               ' Preserve the PID and destination
                mov     pkt_cnt, #3
.next_byte
                getbyte utx, htmp, #0                   ' Bytes on the bus LSB->MSB
                shr     htmp, #8                        ' Shift to next byte to send
.wait
                testp   dp                      wc
        if_nc   jmp     #.wait
                akpin   dp
                wypin   utx, dm
        _ret_   djnz    pkt_cnt, #.next_byte
' */
' /* txn_out
'------------------------------------------------------------------------------
' SETUP/OUT/INTERRUPT transaction.
' Possible function response in order of precedence: STALL, ACK, NAK.
'------------------------------------------------------------------------------
' On entry:
'   ep_addr_pid - PID_OUT(b0..7), address(b8..b14), endpoint(b15..18) and
'     CRC(b19..23).
'   PTRA - start address of the data buff/struct that has the bytes to send.
'   pkt_data - count of DATAx payload bytes to send.
' On exit:
'------------------------------------------------------------------------------
txn_out
                call    #wait_txn_ok                    ' ISR: ensure txn doesn't cross frame boundary
                call    #utx_token                      ' Put SETUP/OUT token on the bus
                rdfast  ##$80000000, ptra               ' Use hub RAM FIFO interface to read the tx buffer
                mov     pkt_cnt, pkt_data
' Fall through to utx_data
' vvvvvvvvvvvvvvvvvvvvvvvv
' */
' /* utx_data
'------------------------------------------------------------------------------
' Transmit a DATAx packet with USB-16 checksum of payload. The payload CRC is
' calculated while the data byte is being shifted out. Since data stage
' success/fail is not determined until the status stage of the transaction,
' this routine is only concerned about the current DATAx packet.
'------------------------------------------------------------------------------
' On entry:
'   PTRA - hub start address of the data to read.
'   pkt_cnt - data payload size.
' On exit:
'------------------------------------------------------------------------------
utx_data
                rqpin   urx, dm
                testb   urx, #SOPB              wc
        if_c    jmp     #utx_data
                mov     hctwait, ip_delay
                call    #poll_waitx                     ' SETUP/OUT token always precedes tx DATAx so insert IP delay
                mov     utx, #OUT_SOP
                call    #utx_byte                       ' Send sync
                bmask   crc, #15                        ' Prime the CRC16 pump
                testb   hstatus, #DATAx_TGLB    wc      ' Set the requested DATAx PID
        if_nc   mov     utx, #PID_DATA0
        if_c    mov     utx, #PID_DATA1
                call    #utx_byte                       ' No CRC calc done on PID
                cmp     pkt_cnt, #0             wz      ' Check if sending a zero length payload
        if_z    jmp     #.send_crc                      ' If so, only the CRC goes out
.read_byte
                rfbyte  utx                             ' Fetch data byte
                call    #utx_byte
                rev     utx                             ' Calculate CRC while the data is shifting out
                setq    utx                             ' SETQ left-justifies the reflected data byte
                crcnib  crc, ##USB16_POLY               ' Run CRC calc on the data nibs
                crcnib  crc, ##USB16_POLY
                djnz    pkt_cnt, #.read_byte
.send_crc
                xor     crc, ##$ffff                    ' Final XOR, and send the calculated CRC16
                getbyte utx, crc, #0
                call    #utx_byte
                getbyte utx, crc, #1
                call    #utx_byte                       ' Last CRC byte out
                jmp     #urx_packet                     ' Handle function response/error and back to caller
' */
' /* urx_data
'------------------------------------------------------------------------------
' Receive a DATAx_ payload with USB-16 checksum. The CRC is calculated as the
' payload bytes are received. The routine reads bytes until EOP is detected and
' expects that the packet includes at least the CRC word.
'
' In control transfers, it's possible to recieve fewer data bytes than what
' was requested, which makes it difficult to determine where the data stops
' and the CRC word begins. So the CRC calculation is done on every byte of the
' packet, including the CRC word. The CRC value should then be equal to the
' USB-16 expected residual value of 0xB001.
'
' The routine writes the IN packet data to a static max_packet_size buffer
' so the caller can verify IN success before writing the data to its final
' destination.
'------------------------------------------------------------------------------
' On entry:
'   pkt_data - max byte count expected to be in the packet.
'   newb_flg - signals new byte ready when toggled.
' On exit:
'   pkt_cnt - actual number of bytes read.
'------------------------------------------------------------------------------
urx_data
                mov     htmp2, pb
                loc     pb, #@urx_buff - @usb_host_start
                add     pb, hcog_base_addr
                wrfast  ##$80000000, pb                 ' Use hub RAM FIFO interface to buffer bytes received
                mov     pb, htmp2
                bmask   crc, #15                        ' Prime the CRC16 pump
                mov     pkt_cnt, #0                     ' Keep track of payload bytes received
                mov     pkt_tmp, pkt_data
                add     pkt_tmp, #2                     ' Tweak payload byte count to include CRC word
.wait_byte
' In-line rx for max speed
                rqpin   urx, dm
                mov     utx, #BYTE_TGLF                 ' Reg utx free in this context
                and     utx, urx
                cmp     newb_flg, utx           wz      ' Fetch a byte whenever the flags differ
        if_nz   xor     newb_flg, #BYTE_TGLF            ' Synchronize flags
        if_nz   jmp     #.get_byte                      ' New byte!
                testb   urx, #EOPB              wc
        if_c    jmp     #.chk_crc                       ' At end-of-packet
                jmp     #.wait_byte
.get_byte
                getbyte retval, urx, #1                 ' New byte from smart pins
                wfbyte  retval                          ' Add it to the data buffer
                rev     retval                          ' Calculate CRC while next byte is shifting in
                setq    retval                          ' SETQ left-justifies the reflected data byte
                crcnib  crc, ##USB16_POLY               ' Run CRC calc on the data nibs
                crcnib  crc, ##USB16_POLY
.end_crc
                add     pkt_cnt, #1
                cmp     pkt_cnt, pkt_tmp        wcz
        if_a    mov     retval, #ERR_PACKET             ' Error if payload > expected size
        if_a    ret
' For full-speed at 80MHz, the time it takes to do the final byte write and
' CRC verify has likely put us into the EOP zone. The P2 smart pins keep the
' EOP flag "sticky" for 7-bits of J, but at 80MHz, it still could be possible
' to miss it, so cheat a bit and look for SOP clear here.
                rqpin   urx, dm
                testb   urx, #EOPB              wc      ' FIXME: checking for EOP set should work when > 80MHz
        if_nc   jmp     #.wait_byte                     ' Next read will catch EOP at low-speed
' CRC OK = Payload CRC calc ^ packet's CRC bytes = $B001 (the USB-16 expected residual)
.chk_crc
                sub     pkt_cnt, #2                     ' Adjust payload count to exclude the CRC bytes read
                xor     crc, ##USB16_RESIDUAL   wz      ' CRC of (data + transmitted CRC) XOR residual should equal zero
        if_nz   jmp     #urx_packet                     ' CRC fail; discard data and wait until data re-sent or transfer timeout
                mov     retval, #PID_ACK
                mov     hctwait, ip_delay
                call    #poll_waitx
' Fall through to utx_handshake
' vvvvvvvvvvvvvvvvvvvvvvvvvvvvv
' */
' /* utx_handshake
'------------------------------------------------------------------------------
' Transmit a handshake PID. The routine assumes that the bus is IDLE and
' the appropriate IP delay has been inserted.
'------------------------------------------------------------------------------
' On entry:
'   retval - handshake PID to send.
' On exit:
'   retval unchanged.
'------------------------------------------------------------------------------
utx_handshake
                mov     utx, #OUT_SOP
                call    #utx_byte                       ' Send sync
                mov     utx, retval
                call    #utx_byte                       ' Send handshake PID
.idle
                rqpin   urx, dm
                testb   urx, #J_IDLEB           wc
        if_nc   jmp     #.idle                          ' Wait for IDLE to ensure the PID tx is complete
                mov     hctwait, tat_wait               ' Ensure one turn-around time before next transaction
                jmp     #poll_waitx
' */
' /* utx_byte
'------------------------------------------------------------------------------
' Wait for the USB tx buffer to empty and feed it a new byte.
'------------------------------------------------------------------------------
' On entry:
'   utx - byte to transmit.
' On exit:
'------------------------------------------------------------------------------
utx_byte
                testp   dp                      wc
        if_nc   jmp     #utx_byte
                akpin   dp
                waitx   utx_tweak       ' Wait #0 '#3 if < 180MHz, wait #3 '#20 if 180MHz+
        _ret_   wypin   utx, dm
' */
' /* urx_next
'------------------------------------------------------------------------------
' Fetch the next data byte of a packet. Always check receiver status for EOP.
'------------------------------------------------------------------------------
' On entry:
' On exit:
'   retval - the byte read.
'   urx - the receiver status. The caller must check the hstatus reg EOP flag
'     on return. If EOP is set, the byte in reg retval remains as the last byte
'     received.
'------------------------------------------------------------------------------
urx_next
                rdpin   urx, dm
                mov     utx, #BYTE_TGLF                 ' Reg utx free in this context
                and     utx, urx
                cmp     newb_flg, utx           wz      ' Fetch a byte whenever the flags differ
        if_nz   xor     newb_flg, #BYTE_TGLF            ' Synchronize flags
        if_nz   getbyte retval, urx, #1                 ' Fetch the new byte
        if_nz   ret                                     ' New byte is priority, so return now
                testb   urx, #SOPB              wc
                testb   urx, #BUS_ERRB          wz
   if_c_and_nz  jmp     #urx_next                       ' If SOP still raised and !BUS_ERRB a new byte should be coming
        if_nc   bith    hstatus, #EOPB                  ' If EOP make it sticky, otherwise it's a bus error
                ret
' */
' /* calc_crc5
'------------------------------------------------------------------------------
' Calculate USB-5 CRC. The upper word of the CRC pre-calc table in LUT contains
' the data used for the USB-5 CRC lookups. The token packet is three bytes in
' length, and the PID is not included in the CRC calculation:
'  CRC5  FRAME_NUMBER SOF (full-speed)
'  CRC5  ENDP ADDRESS PID
' %00000_1111_1111111_xxxxxxxx
'------------------------------------------------------------------------------
' On entry:
'   ep_addr_pid - stuffed with the function endpoint, address and
'     SETUP/IN/OUT/SOF PID according to the USB standard.
' On exit:
'   ep_addr_pid - CRC value appended to the packet.
'------------------------------------------------------------------------------
calc_crc5
                and     ep_addr_pid, ##EP_ADDR_MASK     ' Clear existing CRC, if any
                mov     htmp, ep_addr_pid
                shr     htmp, #8                        ' PID not included in CRC calc
                mov     crc, #$1f                       ' Initial CRC5 value
                rev     htmp                            ' Input data reflected
                setq    htmp                            ' CRCNIB setup for data bits 0..7
                crcnib  crc, #USB5_POLY
                crcnib  crc, #USB5_POLY                 ' Data bits 0..7 calculated
                shl     htmp, #9                wc      ' Shift out processed bits + 1 to set up CRC of remaining bits 8..10
                crcbit  crc, #USB5_POLY                 ' Inline instead of REP as we're in hubexec
                shl     htmp, #1                wc
                crcbit  crc, #USB5_POLY
                shl     htmp, #1                wc
                crcbit  crc, #USB5_POLY
                xor     crc, #$1f                       ' Final XOR value
                shl     crc, #8 + 11                    ' CRC to bits 23..19 of the token packet
        _ret_   or      ep_addr_pid, crc                ' Put the CRC in its new home
' */
' /* isr1_frame
'------------------------------------------------------------------------------
' Full-speed/low-speed frame timing interrupt service routine.
'------------------------------------------------------------------------------
isr1_fsframe
                getct   iframe_ct_base
                mov     iframe_ct_new, iframe_ct_base
                addct1  iframe_ct_new, _frame1ms_clks_
.wait
                testp   dp                      wc
        if_nc   jmp     #.wait
                akpin   dp
                mov     utx, #PID_SOF
                wypin   #OUT_SOP, dm                    ' Put start-of-packet SYNC field on the USB
                call    #utx_byte                       ' Send token PID byte
                mov     icrc, #$1f                      ' Prime the CRC5 pump
                mov     sof_pkt, frame                  ' CRC5 calculation done on the 11-bit frame number value
                rev     sof_pkt                         ' Input data reflected
                setq    sof_pkt                         ' CRCNIB setup for data bits 0..7
                crcnib  icrc, #USB5_POLY
                crcnib  icrc, #USB5_POLY                ' Data bits 0..7 calculated
                getbyte utx, frame, #0                  ' Send the low byte of the frame number
                call    #utx_byte
                shl     sof_pkt, #8                     ' Shift out processed bits to set up CRCBIT * 3
                rep     #2, #3                          ' Three data bits left to process
                shl     sof_pkt, #1             wc
                crcbit  icrc, #USB5_POLY                ' Data bits 8..10 calculated
                xor     icrc, #$1f                      ' Final XOR value
                getbyte utx, frame, #1                  ' Send remaining frame number bits
                shl     icrc, #3                        ' Merge CRC to bits 7..3 of the final token byte
                or      utx, icrc
                call    #utx_byte                       ' Last start-of-frame byte is on the wire
                mov     isrtmp1, _ip_delay_fs_          ' Use normal inter-packet delay when full-speed
                jmp     #isr1_wait
isr1_lsframe
                getct   iframe_ct_base
                mov     iframe_ct_new, iframe_ct_base
                addct1  iframe_ct_new, _frame1ms_clks_
.wait
                testp   dp                      wc
        if_nc   jmp     #.wait
                akpin   dp
                wypin   #OUT_EOP, dm                    ' EOP is the low-speed keep-alive strobe
                mov     isrtmp1, _ip_delay_ls_          ' Normal inter-packet delay works when low-speed
isr1_wait
                rqpin   utx, dm
                testb   utx, #SOPB                 wc
        if_c    jmp     #isr1_wait
                add     frame, #1                       ' Next frame# and check for wrap around
                and     frame, ##$7ff
                waitx   isrtmp1                         ' Make sure bus is idle
                reti1
' */
' /* wait_txn_ok
'------------------------------------------------------------------------------
' Wait for a window within the 1ms frame boundary that will ensure that a
' transaction will complete before the next frame is triggered.
'------------------------------------------------------------------------------
' On entry:
' On exit:
'------------------------------------------------------------------------------
wait_txn_ok
                getct   htmp2
                sub     htmp2, iframe_ct_base
                testb   hstatus, #LOW_SPEEDB    wc
        if_c    cmp     htmp2, _txn_ok_ls_      wcz
        if_nc   cmp     htmp2, _txn_ok_fs_      wcz
        if_a    jmp     #wait_txn_ok                    ' Not enough time, so wait until next frame
                ret
' */
' /* dev_reset
'------------------------------------------------------------------------------
' A device connection was detected, or a bus reset was requested by the USB
' client. Set the appropriate smart pin FS/LS speed mode to match the device
' and perform a reset sequence prior to device enumeration.
'------------------------------------------------------------------------------
dev_reset
                rqpin   urx, dm
                testb   urx, #K_RESUMEB         wc      ' K differential "1" in FS mode signals low-speed
        if_c    call    #set_speed_low                  ' The speed config subroutines must restore the caller C flag
        if_nc   call    #set_speed_full                 ' state on return if it writes the C flag.
reset
                setint1 #0                              ' Don't want frame interrupt while in reset
                wypin   #OUT_SE0, dm                    ' Assert bus reset
                waitx   _reset_hold_                    ' Spec is >= 10ms
                wypin   #OUT_IDLE, dm
                mov     frame, #0                       ' Reset the frame timespan count
                getct   iframe_ct_base
                mov     iframe_ct_new, iframe_ct_base
                addct1  iframe_ct_new, _frame1ms_clks_
                mov     htmp, frame                     ' Allow reset recovery time (Section 9.2.6.2)
                add     htmp, #36
                setint1 #1                              ' Set ISR event trigger to CT-passed-CT1
.framewait
                cmp     frame, htmp             wcz
        if_b    jmp     #.framewait
                ret
' */
' /* hmemcpy
'------------------------------------------------------------------------------
' Bulk hub<->hub byte copy. Does not check for src/dest buffer overlap.
'------------------------------------------------------------------------------
' On entry:
'   PTRA - source address.
'   PB - destination address.
'   hr0 - length of copy, in bytes.
' On exit:
'------------------------------------------------------------------------------
hmemcpy
                rdbyte  htmp, ptra++
                wrbyte  htmp, pb
                add     pb, #1
        _ret_   djnz    hr0, #hmemcpy
' */
' /* host_error
'------------------------------------------------------------------------------
' A fatal USB error has occured. Notify the client and spin in a pseudo-idle
' loop until the errant device is disconnected.
'------------------------------------------------------------------------------
' On entry:
' On exit:
'------------------------------------------------------------------------------
host_error
                wrbyte  retval, usb_err_code_p          ' Save the error code for the client interface
                wxpin   #USB_ERROR, usb_event_pin       ' Signal the client an error has occurred
                mov     hrep, #5
.spin
                rdlong  htmp, cmd_data_p
                cmp     htmp, #CMD_RESET        wz
        if_z    wrlong  #ERR_NONE, cmd_data_p           ' Acknowledge client reset cmd received
        if_z    flth    host_error_led
        if_z    jmp     #host_reset                     ' See if it works...
                drvnot  host_error_led
                mov     hctwait, _100ms_                ' Blink the error LED
                call    #poll_waitx
                rqpin   urx, dm
                testb   urx, #SE0_RESETB        wc
        if_nc   jmp     #.spin
                djnz    hrep, #.spin
        if_c    flth    host_error_led                  ' Clear the error LED
        if_c    ret                             wc      ' Handle disconnect?
                mov     hrep, #5
                jmp     #.spin
' */
' /* poll_kbd, poll_mouse
'------------------------------------------------------------------------------
' Post interrupt IN transactions at configured intervals.
'------------------------------------------------------------------------------
' On entry:
' On exit:
'------------------------------------------------------------------------------
poll_kbd
                getct   hct3
                cmp     hmouse_ep_addr, #0      wz
        if_z    addct3  hct3, _8ms_                     ' Set the timer for next poll interval
        if_nz   addct3  hct3, _4ms_                     ' Keep a 4ms timespan between kbd/mouse
        if_nz   mov     poll_target, #poll_mouse
                loc     pa, #@hget_kbd_in_report - @usb_host_start
                add     pa, hcog_base_addr
                jmp     pa                              ' Post IN txn and return to caller
poll_mouse
                getct   hct3
                cmp     hkbd_ep_addr, #0         wz
        if_z    addct3  hct3, _8ms_                     ' Mouse is the only connected device
        if_nz   addct3  hct3, _4ms_
        if_nz   mov     poll_target, #poll_kbd
                loc     pa, #@hget_mouse_in_report - @usb_host_start
                add     pa, hcog_base_addr
                jmp     pa                              ' Post IN txn and return to caller
' */
' /* Host registers
' Pointers to this spin object's VAR block data (assigned at cog startup)
dbg_data_p      long    0
init_data_p     long    0         ' Packed pin assignments for USB port (4) and event mailbox
cmd_data_p      long    0
frame_bias_p    long    0
mouse_data_p    long    0         ' Packed mouse button states and direction/velocity data
kb_buff_p       long    0
kb_tail_p       long    0
kb_head_p       long    0
kb_intf_num_p   long    0
kb_interval_p   long    0
kb_in_max_pkt_p long    0
kb_next_datax_p long    0
kb_max_index_p  long    0
kb_led_states_p long    0
kb_cur_report_p long    0
kb_pre_report_p long    0
kb_last_key_p   long    0
ms_intf_num_p   long    0
ms_interval_p   long    0
ms_in_max_pkt_p long    0
ms_next_datax_p long    0
ms_cur_report_p long    0
usb_err_code_p  long    0         ' Contains "Protocol error codes" constant enumeration value.
parend_p        long    0
' Initialized at cog startup:
save_sysclk     long    0         ' Save the current sysclock as the client may change it
hcog_base_addr  long    0         ' This object's start address in hub, read from PTRB at cog creation
dm              long    0         ' Client defines the basepin for four consecutive USB port pins
dp              long    0
usb_event_pin   long    0         ' Host event reporting uses a long repository smart pin
host_active_led long    0         ' Client defines the LED pin# for host bus activity
host_error_led  long    0         ' Client defines the LED pin# to light on error
iframe_ct_new   long    0
iframe_ct_base  long    0
p2rev_val       long    P2RevB
utx_tweak       long    0         ' Sysclock speeds above ~120MHz need some fairy dust for USB tx
' This register block is reset to zero when a USB device connects
hreg_init_start
hstatus         long    0         ' Host status flags
hctwait         long    0         ' Poll-based wait clocks
ip_delay        long    0         ' Inter-packet delay in bit periods for connected device speed
tat_wait        long    0         ' Maximum bus turn-around time in bit periods for connected device speed
nak_retry       long    0         ' NAK retry count, unlimited retries if zero
xfer_retry      long    0         ' Control transfer retry count
retry           long    0         ' Transaction retry count
utx             long    0         ' Byte to transmit on USB
urx             long    0         ' LSByte receiver status flags, MSByte received data
newb_flg        long    0         ' Receive "new byte" bit toggle detector
poll_target     long    0         ' Address of a subroutine that polls an interrupt IN endpoint
max_pkt_size    long    0         ' Maximum payload bytes allowed, likely to change on device connect.
total_data      long    0         ' Total bytes to tx/rx in a transfer data stage
stage_data      long    0         ' Count of bytes sent/received so far during a data stage.
pkt_data        long    0         ' Payload size of an OUT packet or bytes received on IN
frame           long    0         ' USB 1ms frame counter value
sof_pkt         long    0         ' ISR frame# packet and CRC5
icrc            long    0         ' Only used by the 1ms frame output ISR routine
pkt_cnt         long    0         ' Count of DATAx packet payload bytes
crc             long    0         ' Used for CRC16 calculation
ep_addr_pid     long    0         ' Endpoint and device addresses for connected device
retval          long    0         ' Global success/fail return parameter
context_retval  long    0         ' Operation contextual return parameter
' Keyboard/mouse stuff
hctrl_ep_addr   long    0
hctrl_max_pkt   long    0
hconfig_base    long    0
hcon_tot_len    long    0         ' Size of the complete config descriptor chain
hhid_intf_idx   long    0         ' Used during verbose descriptor terminal output
hsearch_key     long    0         ' Descriptor type to search for in the config chain
hnext_desc      long    0         ' Offset from the config descriptor start address to the next descriptor in the chain
hmouse_ep_addr  long    0
hmouse_poll_cnt long    0
hkbd_ep_addr    long    0         ' Keyboard interface endpoint address
hkbd_poll_cnt   long    0         ' Poll interval counter used for key auto-repeat
hkbd_repeat     long    0         ' Key auto-repeat delay threshold
hkbd_scancode   long    0         ' Key scancode
hkbd_modkeys    long    0         ' Keyboard modkeys
hkbd_keypress   long    0
hkbd_ledstates  long    0         ' Off/on state of keyboard LEDs
hreg_init_end

DAT 'RJA Adding pointers to mouse data in main cog
'Will be set before cog is started
pMouseX         long    0
pMouseY         long    0
pMouseMaxX      long    0
pMouseMaxY      long    0
pMouseButtons   long    0
mtemp1          long    0 'temp long for mouse math
mtemp2          long    0 'temp long for mouse math
pKeyReport      long    0

' Variables dependent on the system freqency
_var_64_lower_  res     1
_var_64_upper_  res     1
_12Mbps_        res     1
_1_5Mbps_       res     1
_1ns16fp_       res     1         ' 1ns as 32,16 fixed point
_1us_           res     1         ' 1us
_10us_          res     1         ' 10us
_33us_          res     1         ' 33us
_txn_err_       res     1         ' 250us
_500us_         res     1         ' 500us
_txn_ok_ls_     res     1         ' 666us timespan for LS transaction OK window
_txn_ok_fs_     res     1         ' 850us timespan for FS transaction OK window
_ip_delay_ls_   res     1         ' Low-Speed inter-packet 4 bit-time delay
_ip_delay_fs_   res     1         ' Full-Speed inter-packet 4 bit-time delay
_tat_wait_ls_   res     1         ' Low-Speed turnaround 22 bit-time wait
_tat_wait_fs_   res     1         ' Full-Speed turnaround 28 bit-time wait
_1ms_           res     1         ' 1ms
_2ms_           res     1         ' 2ms
_suspend_wait_  res     1         ' 3ms
_4ms_           res     1         ' 4ms
_xfer_wait_     res     1         ' 5ms
_8ms_           res     1         ' 8ms timespan for keyboard/mouse interrupt IN transactions
_reset_hold_    res     1         ' 15ms
_resume_hold_   res     1         ' Hold K-state for 20ms to signal device(s) to resume
_21ms_          res     1         ' 21ms
_100ms_         res     1         ' 100ms
_500ms_         res     1         ' 500ms
_pulse_time_    res     1         ' Activity LED toggle interval, one sec connect wait, _500ms_ when connected
_frame1ms_clks_ res     1         '_1ms +/- n clocks: calculated based on the current sysclock
'------------------------------------------------------------------------------
_usb_h_ls_nco_  res     1         ' USB smart pin modes dependent on sysclock
_usb_d_ls_nco_  res     1
_usb_h_fs_nco_  res     1
_usb_d_fs_nco_  res     1
'------------------------------------------------------------------------------
' Scratch registers
htmp            res     1         ' Scratch registers whose context remains within the same code block
htmp1           res     1
htmp2           res     1
hrep            res     1         ' Repeat count
hsave0          res     1         ' Subroutine parameter saves
hsave1          res     1
hsave2          res     1
isrtmp1         res     1
pkt_tmp         res     1         ' Tmp storage for routines that deal with datax packets
hr0             res     1         ' Multi-purpose registers
hr1             res     1
hr2             res     1
hr3             res     1
hpar1           res     1         ' Routine entry/exit parameters
hpar2           res     1
hpar3           res     1
hct2            res     1         ' Function response bus turn-around timer
hct3            res     1         ' Keyboard/mouse poll timer
mod_cnt         res     1         ' Used in idle loops




                fit     $1d8      ' PR0($1d8)..PR7($1df) may be used for Spin2<->PASM communication
' */
' #endregion (USB host cog)
' #region (Host LUT execution)
DAT             org     $200
hlut_start
' /* set_speed_full
'------------------------------------------------------------------------------
' Full-speed is the host's native speed, so all that is needed is to set the FS
' settings to startup defaults.
'------------------------------------------------------------------------------
' On entry:
' On exit: Save/restore caller C flag state if C is changed in this routine!
'------------------------------------------------------------------------------
set_speed_full
                mov     ijmp1, #isr1_fsframe            ' Set the USB 1ms frame handler ISR routine
                mov     max_pkt_size, #64               ' Set FS control read/write DATAx packet size
                mov     tat_wait, _tat_wait_fs_         ' Bus turn-around time in full-speed bit periods
        _ret_   mov     ip_delay, _ip_delay_fs_         ' Inter-packet delay in full-speed bit periods
'                ret                             wcz     ' Restore caller flags on exit
' */
' /* set_speed_low
'------------------------------------------------------------------------------
' When a low-speed device connects, the D-/D+ signaling is inverted. If there
' is a downstream hub connected (not yet implemented), the baud generator
' remains set at the full-speed rate, but signaling is switched to low-speed,
' which reverses the D-/D+ polarity. The polarity can be changed without
' putting the smart pins into reset.
'------------------------------------------------------------------------------
' On entry:
' On exit: CZ flags restored to caller states
'------------------------------------------------------------------------------
set_speed_low
                test    hstatus, #DWNSTRM_HUBF  wz      ' If no downstream hub connected, set low-speed baud
        if_z    mov     ijmp1, #isr1_lsframe            ' Set the USB 1ms frame handler ISR routine
                testb   p2rev_val, #0           wc      ' P2 Revision is either %0001 (A) or %0010 (B+)
        if_c    dirl    dm                              ' P2RevA needs to be completely reconfigured
        if_c    dirl    dp
        if_c    wrpin   ##USB_V1HMODE_LS, dm            ' Low-speed signaling is always used
        if_c    wrpin   ##USB_V1HMODE_LS, dp
        if_c    wxpin   _1_5Mbps_, dm                   ' Set 1.5Mbs baud if no downstream hub
        if_c    dirh    dm
        if_c    dirh    dp
        if_nc   wxpin   _usb_h_ls_nco_, dm              ' Host mode and 1.5Mbs baud if no downstream hub
                mov     max_pkt_size, #8                ' Set LS control read/write DATAx packet size
                mov     tat_wait, _tat_wait_ls_         ' Bus turn-around time in low-speed bit periods
                mov     ip_delay, _ip_delay_ls_         ' Inter-packet delay in low-speed bit periods
                bith    hstatus, #LOW_SPEEDB            ' D- pulled high, so it's a Low-Speed device
                ret                             wcz     ' Restore caller flags on exit
' */
' /* on_connect
'------------------------------------------------------------------------------
' Perform configuration stuff required when a device intitially connects.
'------------------------------------------------------------------------------
' On entry:
' On exit:
'------------------------------------------------------------------------------
on_connect
                mov     hr0, #2                         ' FIXME: need to determine a reasonable limit for reset & retry
                call    #dev_reset                      ' Reset device prior to Get Device Descriptor request
.retry
                testb   hstatus, #LOW_SPEEDB    wc
        if_c    mov     hpar1, #USB_SPEED_LOW           ' Also the connect speed
        if_nc   mov     hpar1, #USB_SPEED_FULL
                mov     ep_addr_pid, ##EP_ADDR_ZERO     ' New connect, use pre-calc CRC for ep/addr zero
                loc     ptra, #@get_dev_desc - @usb_host_start ' Hub start address of GetDeviceDescriptor SETUP struct
                add     ptra, hcog_base_addr
                wrword  #$40, ptra[wLength]             ' Request IN data stage max of 64 bytes will test actual < requested logic
                loc     pb, #@dev_desc_buff - @usb_host_start ' Start address of DeviceDescriptor struct for IN data
                add     pb, hcog_base_addr
                call    #control_read                   ' Execute GetDeviceDescriptor()
                cmp     retval, #PID_ACK        wz
        if_z    jmp     #.get_dev_desc
                mov     hctwait, _500ms_                ' If the first GetDescriptor() fails, reset and try again
                call    #poll_waitx
                sub     hr0, #1         wz              ' FIXME: need to determine a reasonable limit for reset & retry
        if_z    jmp     #host_error                     ' Post error and spin until the errant device is disconnected
                call    #reset                          ' Try another reset to see if the device responds
                jmp     #.retry
.get_dev_desc
                loc     pa, #@dev_desc_buff - @usb_host_start
                add     pa, hcog_base_addr              ' Fetch the max packet size for control transactions from the
                add     pa, #DEV_bMaxPktSize0           ' appropriate Device Descriptor struct member offset
                rdbyte  max_pkt_size, pa
                mov     hctwait, _1ms_
                call    #poll_waitx                     ' Do a reset before SetAddress(), but wait a bit first
                call    #reset
                loc     ptra, #@set_address - @usb_host_start ' Hub start address of SetAddress SETUP struct
                add     ptra, hcog_base_addr
                wrword  #1, ptra[wValue]                ' Only support one device port at this time
                call    #control_write                  ' Execute SetAddress()
                cmp     retval, #PID_ACK        wz
        if_nz   ret                                     ' Back to idle if not ACK
                mov     hctwait, _8ms_
                call    #poll_waitx                     ' Allow SetAddress() a minimum 2ms recovery interval
                mov     ep_addr_pid, #1 << 8            ' Device ep/addr now #1 and endpoint zero
                call    #calc_crc5
                loc     ptra, #@get_dev_desc - @usb_host_start ' Repeat SETUP for GetDeviceDescriptor()
                add     ptra, hcog_base_addr
                loc     pb, #@dev_desc_buff - @usb_host_start ' Start address of DeviceDescriptor struct has exact descriptor length
                add     pb, hcog_base_addr
                rdbyte  total_data, pb
                wrword  total_data, ptra[wLength]       ' Assign it to the SETUP wLength struct member
                call    #control_read                   ' Execute GetDeviceDescriptor() again, but with updated data length
                cmp     retval, #PID_ACK        wz
        if_nz   ret                                     ' Back to idle if not ACK
                mov     hctrl_ep_addr, ep_addr_pid      ' Make the device control address and endpoint official
                loc     ptra, #@dev_desc_buff - @usb_host_start ' Do the same with the control max packet size
                add     ptra, hcog_base_addr
                rdbyte  hctrl_max_pkt, ptra[DEV_bMaxPktSize0]
                mov     hctwait, _500us_
                call    #poll_waitx
                loc     ptra, #@get_config_desc - @usb_host_start ' Hub start address of GetConfigurationDescriptor SETUP struct
                add     ptra, hcog_base_addr
                wrword  #$ff, ptra[wLength]             ' Maximum DATAx bytes for receive to the SETUP struct
                loc     pb, #@con_desc_buff - @usb_host_start ' Hub start address of ConfigurationDescriptor structure
                add     pb, hcog_base_addr
                call    #control_read                   ' Execute GetConfigurationDescriptor()
                cmp     retval, #PID_ACK        wz
        if_nz   ret
                loc     ptra, #@con_desc_buff - @usb_host_start ' Check the config descriptor struct for expected data
                add     ptra, hcog_base_addr
                mov     hconfig_base, ptra              ' Will need this for configuration
                rdbyte  hr0, ptra++                     ' Config.bLength is at offset zero, expect >= CON_DESC_LEN
                rdbyte  hr1, ptra++                     ' Config.bDescType is next member, expect = TYPE_CONFIG constant
                rdword  htmp, ptra                      ' Config.wTotalLen is next member, expect >= bytes actually received
                cmp     hr0, #CON_DESC_LEN      wcz
        if_ae   cmp     hr1, #TYPE_CONFIG       wcz
        if_z    cmp     htmp, total_data        wcz
        if_b    mov     retval, #ERR_CONFIG_FAIL
        if_b    jmp     #host_error
                loc     pa, #@hparse_con_desc - @usb_host_start
                add     pa, hcog_base_addr
                jmp     pa
' */
' /* control_read
'------------------------------------------------------------------------------
' Perform a control read transaction (Section 8.5.3, Figure 8-37).
' Status reporting is always in the function-to-host direction.
'------------------------------------------------------------------------------
' On entry:
'   PTRA - start address of the SETUP data in hub.
'   PB - start address of the buffer/struct to be written to during the IN data
'     stage.
'   ep_addr_pid - device address, endpoint and CRC5.
' On exit:
'   retval - PID_ACK on success, otherwise error. If successful, reg total_data
'     contains the count of data stage bytes actually received, which must
'     always be <= the count requested.
'   context_retval - ERR_NONE if the overall transfer succeeds, otherwise a
'     more specific USB operation error code.
'------------------------------------------------------------------------------
control_read
                mov     hpar1, ep_addr_pid
                mov     hpar2, ptra
                mov     hpar3, pb                       ' Save dest buffer pointer
                mov     xfer_retry, #XFER_RETRIES
.xfer_start
                rdword  total_data, ptra[wLength]       ' Get the size of the data stage from the SETUP struct
                call    #txn_setup                      ' SETUP logic is the same for both control reads and writes
                cmp     retval, #PID_ACK        wz
        if_nz   ret                                     ' Back to caller to handle error
                cmp     total_data, #0          wz
        if_z    jmp     #pre_status_in                  ' No data, so directly to status stage
                mov     stage_data, #0                  ' Prepare for data stage
                mov     nak_retry, ##IN_NAK_RETRIES
                bith    hstatus, #DATAx_TGLB            ' Data stage starts with DATA1 PID
.data
                mov     pkt_data, total_data
                sub     pkt_data, stage_data
                cmp     pkt_data, max_pkt_size  wcz
        if_a    mov     pkt_data, max_pkt_size          ' Have a full packet with more data left
.nak_retry
                mov     retry, #TXN_RETRIES             ' Reset bus error retry limit
.in_retry
                call    #txn_in
                cmp     retval, #PID_ACK        wz      ' Commit on ACK
        if_z    jmp     #.commit
                cmp     retval, #PID_STALL      wz
        if_z    jmp     #.xfer_retry                    ' STALL triggers a transfer retry
                call    #retry_wait                     ' Wait a bit before retry
                cmp     retval, #PID_NAK        wz
        if_z    jmp     #.nak_retry                     ' Function not ready to send data
                cmp     retval, #ERR_NAK        wz
        if_z    jmp     #.xfer_retry                    ' NAK limit exceeded triggers a transfer retry
                cmp     retval, #ERR_TXN_RETRY  wz
        if_nz   jmp     #.in_retry                      ' Bus error retry
                ret                                     ' The transfer has failed
.commit
                cmp     pkt_cnt, #0             wz      ' Empty pkt means previous pkt was max_pkt_len
        if_z    jmp     #.pre_status                    ' and also end-of-data
                loc     ptra, #@urx_buff - @usb_host_start ' Copy DATAx in rx buffer to dest struct
                add     ptra, hcog_base_addr
                mov     hr0, pkt_cnt
                call    #hmemcpy                        ' hmemcpy(PTRA, PB, hr0)
                add     stage_data, pkt_cnt             ' Update bytes received on commit
                cmp     stage_data, total_data  wz      ' Have all asked-for bytes?
        if_z    jmp     #.pre_status                    ' Have all the data that's coming, so done
                cmp     pkt_cnt, pkt_data       wcz     ' Check for short packet
        if_b    jmp     #.pre_status                    ' Actual payload < expected means end of data stage
        if_a    mov     retval, #ERR_PACKET
        if_a    mov     context_retval, retval          ' In this case overall and context are the same
        if_a    ret                                     ' Caller must handle ERR_PACKET
                bitnot  hstatus, #DATAx_TGLB            ' Toggle DATAx sync bit
                jmp     #.data                          ' Start next IN transaction
.pre_status
                mov     total_data, stage_data          ' Replace the asked-for byte count with the bytes actually received
                setbyte ep_addr_pid, #PID_OUT, #0
                mov     pkt_data, #0
                bith    hstatus, #DATAx_TGLB            ' Status stage starts with DATA1 PID
                mov     retry, #TXN_RETRIES             ' Reset txn retry limit
                mov     nak_retry, ##OUT_NAK_RETRIES
.out_retry
                call    #txn_out                        ' Send empty OUT DATAx packet to confirm IN data received OK
                cmp     retval, #PID_ACK        wz
        if_z    ret                                     ' All is good when ACK
                cmp     retval, #PID_STALL      wz
        if_z    jmp     #.xfer_retry                    ' STALL triggers a transfer retry
                call    #retry_wait                     ' Wait a bit before retry
                cmp     retval, #ERR_NAK        wz
        if_z    jmp     #.xfer_retry                    ' NAK limit exceeded triggers a transfer retry
                cmp     retval, #ERR_TXN_RETRY  wz
        if_nz   jmp     #.out_retry                     ' Retry due to bus error or OUT-NAK retry limit not reached
                ret                                     ' Caller must handle transfer retirement
' I've encountered transfer STALL, even though the data looks correct, and
' instances of getting stuck in an endless OUT-NAK loop. Repeating the entire
' ControlRead() transfer gets things unstuck most of the time...
.xfer_retry
                mov     hctwait, _xfer_wait_
                call    #poll_waitx
                call    #wait_txn_ok
                mov     ep_addr_pid, hpar1
                mov     ptra, hpar2
                mov     pb, hpar3
                djnz    xfer_retry, #.xfer_start
                mov     context_retval, retval          ' Preserve the USB error code
        _ret_   mov     retval, #ERR_XFER_RETRY
' */
' /* control_write
'------------------------------------------------------------------------------
' Perform a control write transaction (Section 8.5.3, Figure 8-37). Status
' reporting is always in the function-to-host direction. It is assumed that
' the SETUP data struct is filled with the required values.
'------------------------------------------------------------------------------
' On entry:
'   PTRA - points to the start of the struct for the SETUP data.
'   PB - the start address of the struct/buffer to be read for the OUT data
'     stage.
'   ep_addr_pid - the proper CRC'd address and endpoint to use.
' On exit:
'   retval - used to convey the success/failure of each stage.
'   context_retval - ERR_NONE if the overall transfer succeeds, otherwise a
'     more specific USB operation error code.
'------------------------------------------------------------------------------
control_write
                mov     hpar1, ep_addr_pid
                mov     hpar2, ptra
                mov     hpar3, pb
                mov     xfer_retry, #XFER_RETRIES
.xfer_start
                mov     nak_retry, #NAK_NOLIMIT         ' Unlimited NAK retries the default
                rdword  total_data, ptra[wLength]       ' Get the size of the data stage from the SETUP struct
                call    #txn_setup                      ' SETUP logic is the same for both control reads and writes
                cmp     retval, #PID_ACK        wz
        if_nz   ret                                     ' Back to caller to handle error
                cmp     total_data, #0          wz
        if_z    jmp     #pre_status_in                  ' No data, so directly to status stage
                mov     stage_data, #0                  ' Prepare for data stage
                setbyte ep_addr_pid, #PID_OUT, #0       ' PID isn't part of the CRC calc
                bith    hstatus, #DATAx_TGLB            ' Data stage starts with DATA1 PID
                mov     retry, #TXN_RETRIES             ' Reset txn retry limit
.data
                mov     pkt_data, total_data
                sub     pkt_data, stage_data
                cmp     pkt_data, max_pkt_size  wcz
        if_a    mov     pkt_data, max_pkt_size          ' Data remaining is > max_pkt, so cap at max_pkt
.out_retry
                mov     ptra, pb                        ' Set current location in the OUT data buffer/struct
                call    #txn_out
                cmp     retval, #PID_ACK        wz
        if_z    jmp     #.commit                        ' Function got the data
                call    #retry_wait                     ' Wait a bit before retry
                cmp     retval, #ERR_TXN_RETRY  wz      ' Out of !NAK retries?
        if_nz   jmp     #.out_retry
                ret                                     ' Caller must handle transfer retirement
.commit
                mov     pb, ptra                        ' Save the current buffer/struct location
                add     stage_data, pkt_data
                cmp     stage_data, total_data  wz
        if_nz   bitnot  hstatus, #DATAX_TGLB            ' Toggle DATAx sync bit
        if_nz   jmp     #.data                          ' More data to send
pre_status_in
                bith    hstatus, #DATAx_TGLB            ' Status stage expects IN to be an empty DATA1 packet
                mov     retry, #TXN_RETRIES             ' Reset txn retry limit
.status_retry
                mov     pkt_data, #0
                call    #txn_in
                cmp     retval, #PID_ACK        wz      ' ACK says a DATA1 packet was received
        if_z    cmp     pkt_data, #0            wz      ' DEBUG: should never fail if the function is USB compliant?
        if_z    ret                                     ' Control Write finished
                cmp     retval, #PID_STALL      wz      ' STALL needs to go to the caller for resolution
        if_z    ret
                call    #retry_wait                     ' NAK or bus error, so delay a bit
                cmp     retval, #ERR_TXN_RETRY  wz
        if_nz   jmp     #.status_retry
                ret                                     ' Caller must handle transfer retirement
' */
' /* do_int_in
'------------------------------------------------------------------------------
' Execute an IN interrupt transaction.
'------------------------------------------------------------------------------
' On entry:
'   ep_addr_pid - The function address and endpoint for the IN request.
'   hpar2 - Address of the IN data buffer
'   hpar3 - Word1 has max data packet size, word0 has the DATAx to expect.
' On exit:
'   retval - the result of the operation.
'   hpar3 - the count of IN data bytes actually received.
'------------------------------------------------------------------------------
do_int_in
                getword htmp, hpar3, #0
                cmp     htmp, #PID_DATA0        wz
                bitnz   hstatus, #DATAx_TGLB            ' Set/reset flag for DATAx to expect
                mov     retry, #TXN_RETRIES
.retry
                getword pkt_data, hpar3, #1             ' IN max packet length
                call    #txn_in
                cmp     retval, #PID_ACK        wz      ' ACK if data received
        if_z    jmp     #.commit
                cmp     retval, #PID_NAK        wz      ' NAK if no data available (common)
        if_nz   cmp     retval, #PID_STALL      wz      ' STALL if the endpoint has a transfer issue and must be reset (rare)
        if_z    jmp     #.post_ret                      ' The caller must handle either
                call    #retry_wait
                cmp     retval, #ERR_TXN_RETRY  wz
        if_z    jmp     #.post_ret
                jmp     #.retry
.commit
                cmp     pkt_cnt, #0             wz
        if_z    jmp     #.post_ret                      ' Skip copy if it's an empty packet
                loc     ptra, #@urx_buff - @usb_host_start ' Copy the rx buffer
                add     ptra, hcog_base_addr
                mov     pb, hpar2                       ' to the destination buffer
                mov     hr0, pkt_cnt
                call    #hmemcpy                        ' hmemcpy(PTRA, PB, hr0)
.post_ret
                mov     hpar3, pkt_cnt                  ' IN bytes actually received
                ret
' */
' /* poll_waitx
'------------------------------------------------------------------------------
' The one millisecond frame timer is implemented as an interrupt service
' routine. Since this timing is critical, care must be taken to avoid any
' instructions that can delay the interrupt branch, which will likely upset
' the timer. WAITX is among those instructions, so any time you're inside
' an IN/OUT/SETUP transaction, use this routine instead of WAITX.
'------------------------------------------------------------------------------
' On entry:
'   hctwait - wait interval in sysclocks.
' On exit:
'------------------------------------------------------------------------------
poll_waitx
                getct   hct2
                addct2  hct2, hctwait
.wait
                jnct2   #.wait
                ret
' */
' /* retry_wait
'------------------------------------------------------------------------------
' Transaction retry handling for NAK/STALL or bus error.
'------------------------------------------------------------------------------
' On entry:
'   retval - transaction response PID or error code.
' On exit:
'------------------------------------------------------------------------------
retry_wait
                cmp     retval, #PID_STALL      wz
        if_z    ret                                     ' STALL is special case
                cmp     retval, #PID_NAK        wz
        if_z    jmp     #.nak
                mov     hctwait, _txn_err_              ' Transaction error wait...
                call    #poll_waitx
.dec
                sub     retry, #1               wz
        if_z    mov     retval, #ERR_TXN_RETRY          ' Only set error code if no retries left
                ret                                     ' Retry result to caller
.nak
                mov     hctwait, _33us_                 ' Seems to be a reasonable NAK delay
                call    #poll_waitx
                cmp     nak_retry, #NAK_NOLIMIT wz
        if_z    ret                                     ' Indefinite NAK retries
                sub     nak_retry, #1           wz
        if_z    mov     retval, #ERR_NAK
                ret
' */
' /* host_reset
host_reset
                setint1 #0                              ' Ensure 1ms frame strobe interrupt is off
                rdlong  htmp, #@CLKFREQ                 ' Get current sysclock setting
' FIXME: kludge to set a USB tx byte write ACKPIN<->WRPIN delay.
                cmp     htmp, ##168_000_000     wcz
        if_be   mov     utx_tweak, #0
        if_a    mov     utx_tweak, #3   '#20
' Check to see if the system clock has been changed.
                cmp     htmp, save_sysclk       wz
        if_nz   loc     pb, #@hinit_usb_timings - @usb_host_start
        if_nz   add     pb, hcog_base_addr
        if_nz   call    pb                              ' Recalculate sysclk dependent timing values
                dirl    dm                              ' Put smart pins into reset
                dirl    dp
                cmp     p2rev_val, #P2RevB      wz
        if_z    jmp     #.not_reva
                wrpin   ##USB_V1HMODE_FS, dm            ' The host is also the root hub, so full-speed is its native speed
                wrpin   ##USB_V1HMODE_FS, dp
                wxpin   _12Mbps_, dm                    ' Default to Full-Speed
                jmp     #.enable
.not_reva
                wrpin   ##USB_V2_DRVOUT, dm             ' The same USB smart pin mode for D- and D+ pins
                wrpin   ##USB_V2_DRVOUT, dp
                wxpin   _usb_h_fs_nco_, dm              ' Set host mode and full-speed NCO
.enable
                dirh    dm                              ' Crank them smart pins up
                dirh    dp
                waitx   _1us_
                wypin   #OUT_IDLE, dm
                waitx   _21ms_                          ' Hold to let the idle state get settled
                mov     pa, #hreg_init_start            ' Reset all host common registers to startup values
.regloop
                altd    pa
                mov     0-0, #0
                add     pa, #1
                cmp     pa, #hreg_init_end      wz
        if_nz   jmp     #.regloop
                mov     ptra, kb_buff_p                 ' Reset keyboard head/tail and key buffer
                mov     hrep, #KBD_BUFFMASK + 1
.kbdloop
                wrlong  #0, ptra++
                djnz    hrep, #.kbdloop
                wrbyte  #0, kb_tail_p
                wrbyte  #0, kb_head_p
discon_entry
                mov     mod_cnt, #3                     ' Make the first heartbeat pulse a short one
' Fall through to disconnected loop
' vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
' */
' /* disconnected
'-----------------------------------------------------------------------------------------------------------------
' Device connect handling (Section 7.1.7.3).
'-----------------------------------------------------------------------------------------------------------------
' The 15K pull-down resistors on D+ and D- allow the host to passively monitor the bus lines while waiting for a
' device to connect (Section 7.1.7.3).
'-----------------------------------------------------------------------------------------------------------------
disconnected
                rdlong  hr1, #@CLKFREQ                  ' Check to see if the system frequency has changed
                cmp     hr1, save_sysclk        wz
        if_nz   jmp     #host_reset                     ' host_reset will apply the new USB bus NCO calculation
                shr     hr1, #3                         ' Pulse the activity LED every two seconds but break
                waitx   hr1                             ' it down to smaller wait chunks
                incmod  mod_cnt, #8             wc
        if_c    drvnot  host_active_led
                rqpin   urx, dm
                and     urx, #J_IDLEF | K_RESUMEF wcz   ' Wait for rise of J or K, mutually exclusive
        if_z    jmp     #disconnected                   ' J and K still low, so keep waiting for connect
        if_ne   jmp     #.connect_test                  ' J or K is high, so test for connect
.se1_test
                mov     hctwait, _100ms_                ' J and K high is illegal SE1 state, so wait and retest
                call    #poll_waitx
                rqpin   urx, dm
                and     urx, #J_IDLEF | K_RESUMEF wcz
        if_ne   mov     retval, #ERR_NONE               ' SE0 or idle state resets any previous error
        if_ne   jmp     #discon_entry                   ' Back to connect detection loop
.se1            'SE1 is a fatal error condition
                mov     hctwait, _100ms_
                call    #poll_waitx
                mov     retval, #ERR_SE1
                call    #host_error                     ' Seeing SE1 for any length of time is not good...
.connect_test                                           ' Test lines until stable J/K state seen
                waitx   _100ms_                         ' Total of 100ms debounce interval (Section 7.1.7.3)
                rqpin   urx, dm
                and     urx, #J_IDLEF | K_RESUMEF wcz
        if_z    jmp     #discon_entry                   ' D+ and D- low
        if_e    jmp     #.se1_test                      ' D+ and D- high
connected
                bith    hstatus, #CONNECTEDB            ' Device plugged in
                call    #on_connect                     ' Initial device configuration
                cmp     retval, #PID_ACK        wz      ' Anything other than ACK means the device is unusable
        if_nz   jmp     #discon_entry
set_poll_target
                cmp     hkbd_ep_addr, #0        wz
        if_nz   mov     poll_target, #poll_kbd
        if_nz   jmp     #.set_pulse
                cmp     hmouse_ep_addr, #0      wz
        if_z    mov     poll_target, #0                 ' Unknown device, so no interrupt targets
        if_nz   mov     poll_target, #poll_mouse
.set_pulse
                getct   _pulse_time_
                add     _pulse_time_, _500ms_           ' Set activity LED pulse to the bus idle toggle rate
' Sample the USB 1ms frame delta register to see how close it is to the ideal frame interval
' of 1ms and make a correction, if necessary.
'                mov     htmp, _1ms_
'                subs    htmp, iframe_delta
'                adds    _frame1ms_clks_, htmp
'        debug(udec(_frame1ms_clks_))
' Fall through to idle/processing loop
' vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
hidle
                rqpin   urx, dm
                testb   urx, #SE0_RESETB        wc
        if_c    jmp     #.se0_test
                cmp     poll_target, #0         wz
        if_z    jmp     #.nopoll
                pollct3                         wc
        if_c    call    poll_target                     ' Call the current poll/wait subroutine
.nopoll
                rdlong  htmp, cmd_data_p
                cmp     htmp, #CMD_SUSPEND      wz
        if_z    jmp     #hsuspend
                rdlong  htmp, cmd_data_p
                cmp     htmp, #CMD_RESET        wz
        if_z    wrlong  #ERR_NONE, cmd_data_p           ' Acknowledge client reset cmd received
        if_z    jmp     #host_reset                     ' See if it works...
                getct   hr0
                cmp     hr0, _pulse_time_       wcz     ' Connected "heartbeat"
        if_ae   drvnot  host_active_led
        if_ae   getct   _pulse_time_
        if_ae   add     _pulse_time_, _500ms_
                jmp     #hidle
' Check for extended SE0 state on the bus
.se0_test
                mov     hctwait, _1ms_
                call    #poll_waitx                     ' Wait a bit and test for SE0 again
                rqpin   urx, dm
                testb   urx, #SE0_RESETB        wc
        if_nc   jmp     #hidle                          ' Bus still IDLE
                call    #wait_txn_ok
                wypin   ##OUT_IDLE, dm                  ' Float USB
                wxpin   #DEV_DISCONNECT, usb_event_pin  ' Notify client of disconnect
                jmp     #host_reset                     ' Device disconnected
' */
' /* hsuspend
hsuspend
                call    #wait_txn_ok                    ' Avoid a potential collision with an active frame isr
                setint1 #0                              ' Stopping the 1ms frame packets signals suspend
                waitx   _suspend_wait_                  ' The device enters suspend state when the bus is idle and
                wypin   #OUT_IDLE, dm                   ' no frame SOPs are received for three consecutive frames
                wrlong  #ERR_NONE, cmd_data_p           ' Clear the cmd from the event queue
                mov     mod_cnt, #0
' */
' Fall through to resume wait loop
' vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
' /* hwait_resume
hwait_resume
                rdlong  hr1, #@CLKFREQ                  ' Use the CLKFREQ value, as the sysclock may change
                shr     hr1, #2
                waitx   hr1                             ' Pulse the activity LED every two seconds but break
                incmod  mod_cnt, #8             wc      ' it down to smaller wait chunks
        if_c    drvnot  host_active_led
                rdlong  htmp, cmd_data_p
                cmp     htmp, #CMD_RESUME       wz
        if_nz   jmp     #hwait_resume
                rdlong  htmp, #@CLKFREQ                 ' Check to see if the system frequency has changed
                cmp     htmp, save_sysclk       wz
        if_z    jmp     #.resume
                loc     pb, #@hinit_usb_timings - @usb_host_start
                add     pb, hcog_base_addr
                call    pb                              ' Recalculate sysclk dependent timing values
                testb   hstatus, #LOW_SPEEDB    wc
        if_nc   wxpin   _usb_h_fs_nco_, dm              ' Write the new NCO calculation for the current bus speed
        if_c    wxpin   _usb_h_ls_nco_, dm
.resume
                wypin   #OUT_K, dm
                waitx   _resume_hold_                   ' Hold K-state for 20ms to signal device(s) to resume
                wypin   #OUT_SE0, dm
                mov     htmp, _ip_delay_ls_
                shr     htmp, #1                        ' Delay two LS bit times for K to J (idle) transition
                waitx   htmp
                wypin   #OUT_J, dm
                shr     htmp, #1
                waitx   htmp
                wypin   #OUT_IDLE, dm
                getct   iframe_ct_base
                mov     iframe_ct_new, iframe_ct_base
                addct1  iframe_ct_new, _frame1ms_clks_
                setint1 #1                              ' Enable the 1ms frame ISR
                mov     hctwait, _4ms_
                call    #poll_waitx                     ' Delay until at least three 1ms frames transmitted
                wrlong  #ERR_NONE, cmd_data_p           ' Acknowledge resume cmd complete
                jmp     #set_poll_target                ' This addr configs a USB poll transaction and falls thru to hidle
' */ hwait_resume
hlut_end
                fit     $400
' #endregion (Host LUT execution)
' #region (USB hub execution)
DAT             orgh
'------------------------------------------------------------------------------
' Routines called from cog space.
'------------------------------------------------------------------------------
' usb_host_init, hget_kbd_in_report, hget_mouse_in_report, hparse_con_desc
'------------------------------------------------------------------------------
' /* usb_host_init
'------------------------------------------------------------------------------
' USB host cog initialization.
'------------------------------------------------------------------------------
usb_host_init
                rdlong  htmp, init_data_p               ' Get the byte-packed startup data long
                getbyte usb_event_pin, htmp, #0         ' I/O basepin + 0 is USB event code mailbox
                getbyte host_active_led, htmp, #1       ' On the Serial Host board the I/O + 0 event/active pin is shared
                getbyte dm, htmp, #2                    ' I/O + 2 or I/O + 4 pins MUST be assigned to D-, next must be D+
                mov     dp, dm
                add     dp, #1
                getbyte host_error_led, htmp, #3        ' Pin# of LED to light on USB fatal error (active low)
                flth    host_error_led                  ' Ensure fatal error LED is inactive
                mov     p2rev_val, ptrb                 ' Running on P2RevA or P2RevB+ silicon?
                setq    #1
                rdlong  $1e0, ptrb++
                subr    p2rev_val, ptrb
                shr     p2rev_val, #2           wz      ' RevA if shift result is zero
        if_z    mov     p2rev_val, #P2RevA              ' 1 == RevA, 2 == RevB+
                loc     ptrb, #p2rev_char               ' Make a revision "A" or "B" char accessible to Spin2
                mov     htmp, p2rev_val
                add     htmp, #"@"
                wrbyte  htmp, ptrb
                dirl    usb_event_pin                   ' Configure the USB event mailbox smart pin
                wrpin   ##SP_REPO1_MODE, usb_event_pin  ' Mailbox smart pin output is enabled, so this pin# will raise
                dirh    usb_event_pin                   ' IN at event post and OUT drives the Serial Host activity LED.
' Configure and enable the Serial Host USB port.
'RJA disabling this here and moving to Spin2
                'mov     htmp, usb_event_pin             ' I/O + 1 pin is the Serial Host USB Protection enable/disable
                'add     htmp, #1                        ' Protection enable is a one-time operation
                'drvh    htmp                            ' Enable the port
                jmp     #host_reset                     ' Initialize host and enter main processing loop
' */
' /* hinit_usb_timings
'------------------------------------------------------------------------------
' Timing calculations happen before any interrupt(s) are enabled.
'------------------------------------------------------------------------------
' On entry:
'   htmp - current CLKFREQ value.
'------------------------------------------------------------------------------
hinit_usb_timings
'                getct   htmp2
                mov     save_sysclk, htmp
                qfrac   ##_12m, save_sysclk             ' CORDIC calculation for FS & LS baud
                getqx   _12Mbps_
                shr     _12Mbps_, #16           wc
                addx    _12Mbps_, #0                    ' _12Mbps = round(12_000_000 / CLKFREQ * 2^16)
                mov     _usb_h_fs_nco_, #%11            ' Host mode at FS NCO baud
                shl     _usb_h_fs_nco_, #14
                add     _usb_h_fs_nco_, _12Mbps_
                mov     _usb_d_fs_nco_, #%01            ' Device mode at FS NCO baud
                shl     _usb_d_fs_nco_, #14
                add     _usb_d_fs_nco_, _12Mbps_
                mov     _1_5Mbps_, _12Mbps_
                shr     _1_5Mbps_, #3                   ' _1_5Mbps_ = _12Mbps / 8
                mov     _usb_h_ls_nco_, #%10            ' Host mode at LS NCO baud
                shl     _usb_h_ls_nco_, #14
                add     _usb_h_ls_nco_, _1_5Mbps_
                mov     _usb_d_ls_nco_, _1_5Mbps_       ' Device mode at FS NCO baud
                qmul    save_sysclk, ##1 << 16          ' CORDIC calc for 1ns as 32,16 fixed point
                getqx   _var_64_lower_
                getqy   _var_64_upper_
                setq    _var_64_lower_
                qfrac   _var_64_upper_, ##_1b
                getqx   _1ns16fp_                       ' 1ns as 32,16 fixed point
                qmul    save_sysclk, ##1 << 9           ' CORDIC calc for 1us as 32,9 fixed point
                getqx   _var_64_lower_
                getqy   _var_64_upper_
                setq    _var_64_lower_
                qfrac   _var_64_upper_, ##_1m
                getqx   _1us_                           ' 1us as 32,9 fixed point
                mov     hsave0, _1us_                   ' Save it to compute other us values
                shr     _1us_, #9               wc
                addx    _1us_, #0                       ' Round to final value
                qmul    hsave0, #10                     ' Calc 10us
                getqx   _10us_
                shr     _10us_, #9              wc
                addx    _10us_, #0                      ' 10us
                qmul    hsave0, #33                     ' Calc 33us: 'Seems to be a reasonable NAK delay'
                getqx   _33us_
                shr     _33us_, #9              wc
                addx    _33us_, #0                      ' 33us
                qmul    hsave0, #250                    ' Calc 250us
                getqx   _txn_err_
                shr     _txn_err_, #9           wc
                addx    _txn_err_, #0                   ' 250us
                qmul    hsave0, #500                    ' Calc 500us
                getqx   _500us_
                shr     _500us_, #9             wc
                addx    _500us_, #0                     ' 500us
                qmul    hsave0, ##666                   ' 666us timespan for LS transaction OK window
                getqx   _txn_ok_ls_
                shr     _txn_ok_ls_, #9         wc
                addx    _txn_ok_ls_, #0                 ' 666us
                qmul    hsave0, ##850                   ' 850us timespan for FS transaction OK window
                getqx   _txn_ok_fs_
                shr     _txn_ok_fs_, #9         wc
                addx    _txn_ok_fs_, #0                 ' 850us
                mov     _ip_delay_ls_, _1ns16fp_
                mul     _ip_delay_ls_, ##LSBTns4        ' Low-Speed inter-packet 4 bit-time delay
                shr     _ip_delay_ls_, #16      wc
                addx    _ip_delay_ls_, #0
                mov     _tat_wait_ls_, _1ns16fp_
                mul     _tat_wait_ls_, ##LSBTns22       ' Low-Speed turnaround 22 bit-time wait
                shr     _tat_wait_ls_, #16      wc
                addx    _tat_wait_ls_, #0
                mov     _ip_delay_fs_, _1ns16fp_
                mul     _ip_delay_fs_, ##FSBTns4        ' Full-Speed inter-packet 4 bit-time delay
                shr     _ip_delay_fs_, #16      wc
                addx    _ip_delay_fs_, #0
                mov     _tat_wait_fs_, _1ns16fp_
                mul     _tat_wait_fs_, ##FSBTns28       ' Full-Speed turnaround 28 bit-time wait
                shr     _tat_wait_fs_, #16      wc
                addx    _tat_wait_fs_, #0
                qmul    save_sysclk, ##1 << 9           ' CORDIC calc for 1ms as 32,9 fixed point
                getqx   _var_64_lower_
                getqy   _var_64_upper_
                setq    _var_64_lower_
                qfrac   _var_64_upper_, ##_1thou
                getqx   _1ms_                           ' 1ms as 32,9 fixed point
                shr     _1ms_, #9               wc
                addx    _1ms_, #0                       ' 1ms
                rdlong  _frame1ms_clks_, frame_bias_p
                adds    _frame1ms_clks_, _1ms_
                mov     _2ms_, _1ms_
                shl     _2ms_, #1                       ' 2ms
                mov     _suspend_wait_, _1ms_
                add     _suspend_wait_, _1ms_           ' 3ms delay to signal connected devices to enter suspended mode
                mov     _4ms_, _1ms_
                shl     _4ms_, #2                       ' 4ms
                mov     _xfer_wait_, _4ms_
                add     _xfer_wait_, _1ms_              ' 5ms
                mov     _reset_hold_, _xfer_wait_       ' 5ms
                mov     _resume_hold_, _reset_hold_
                shl     _resume_hold_, #2               ' 20ms timespan to hold the K-state that signals devices to resume
                mov     _100ms_, _resume_hold_          ' 20ms
                shl     _100ms_, #1                     ' 40ms
                mov     _8ms_, _1ms_
                shl     _8ms_, #3                       ' 8ms
                shl     _reset_hold_, #1                ' 10ms
                add     _100ms_, _reset_hold_           ' 50ms
                add     _reset_hold_, _xfer_wait_       ' 15ms
                mov     _21ms_, _xfer_wait_             ' 5ms
                shl     _21ms_, #2                      ' 20ms
                add     _21ms_, _1ms_                   ' 21ms
                mov     _500ms_, _100ms_                ' 50ms
                shl     _100ms_, #1                     ' 100ms
                shl     _500ms_, #3                     ' 400ms
        _ret_   add     _500ms_, _100ms_                ' 500ms
'        _ret_   mov     _1sec_, save_sysclk
'        debug(udec(_1sec_), udec(_500ms_), udec(_100ms_))
'                ret
{
                getct   htmp
                sub     htmp, htmp2
        debug("USB timing calc sysclocks: ", udec( htmp))
                ret
}
' */ hinit_usb_timings
' /* hparse_con_desc
'------------------------------------------------------------------------------
' Parse a configuration descriptor chain to see if the device is a recognized
' one. If it is, start the task progression that will configure the device for
' use.
'------------------------------------------------------------------------------
' On entry:
'   hconfig_base - start address of the cached config descriptor chain.
' On exit:
'------------------------------------------------------------------------------
hparse_con_desc
                call    #init_kbdm_data                 ' Reset keyboard/mouse data area to start-up values
                mov     pa, #CON_wTotalLen
                add     pa, hconfig_base
                rdword  hcon_tot_len, pa                ' Keep config chain size handy
' Search the configuration descriptor for the Class/Subclass/Protocol "triad"
' that defines a keyboard and/or mouse.
                rdbyte  hnext_desc, hconfig_base        ' Config desc size is offset to first desc in chain
.next_intf
                mov     hsearch_key, #TYPE_INTERFACE
                call    #hsearch_desc_type
                cmp     ptrb, #0                wz
        if_z    jmp     #hset_config                    ' No more interface descs
                rdbyte  htmp, ptrb
                add     hnext_desc, htmp                ' Get offset to next desc, if any
                rdbyte  hhid_intf_idx, ptrb[INTF_bIntfNum]
                rdbyte  htmp, ptrb[INTF_bIntfClass]
                cmp     htmp, #CLASS_HID        wz      ' Only interested in the HID class interface descriptors
        if_nz   jmp     #.next_intf                     ' Search next interface in chain, if any
                rdbyte  htmp, ptrb[INTF_bSubClass]      ' Look for a boot interface sub-class
                cmp     htmp, #SUBCLASS_INTF_BOOT wz
        if_nz   jmp     #.next_intf
                rdbyte  hr3, ptrb[INTF_bProtocol] wz    ' Protocol must be non-zero
        if_z    jmp     #.next_intf
.endp
                mov     hsave1, ptrb
                mov     hsave2, hnext_desc
                mov     hsearch_key, #TYPE_ENDPOINT
                call    #hsearch_desc_type              ' Endpoint descs always follow interface descs
                cmp     ptrb, #0                wz
        if_nz   jmp     #.get_ep
.bad_ep
                mov     hnext_desc, hsave2
                mov     ptrb, hsave1
                jmp     #.next_intf
.get_ep
                rdbyte  hr1, ptrb[ENDP_bAddress]
                testb   hr1, #7                 wc      ' FIXME: define constant for endpoint IN/OUT bit
        if_nc   jmp     #.bad_ep                        ' Not an IN endpoint
                shl     hr1, #8 + 7
                add     ptrb, #ENDP_wMaxPktSize
                rdword  hr2, ptrb++
                and     hr2, ##$7ff                     ' Bits 10..0 define the max packet size
                rdbyte  hr0, ptrb                       ' Fetch the bInterval member (min poll interval, in milliseconds)
                mov     hnext_desc, hsave2
                mov     ptrb, hsave1
                cmp     hr3, #INTF_PROTO_KBD    wz
        if_nz   jmp     #.mouse
.keyboard
                mov     ptra, kb_intf_num_p
                wrbyte  hhid_intf_idx, ptra++           ' Save interface index and poll interval values
                wrbyte  hr0, ptra++
                mov     hkbd_ep_addr, hctrl_ep_addr
                and     hkbd_ep_addr, ##ADDR_MASK
                or      hkbd_ep_addr, hr1               ' IN endpoint address
                wrbyte  hr2, ptra
                jmp     #.next_intf                     ' See if there's a mouse subclass
.mouse
                cmp     hr3, #INTF_PROTO_MOUSE  wz
        if_nz   jmp     #.next_intf
                mov     ptra, ms_intf_num_p
                wrbyte  hhid_intf_idx, ptra++           ' Save interface index and poll interval values
                wrbyte  hr0, ptra++
                mov     hmouse_ep_addr, hctrl_ep_addr
                and     hmouse_ep_addr, ##ADDR_MASK
                or      hmouse_ep_addr, hr1             ' IN endpoint address
                wrbyte  hr2, ptra
                jmp     #.next_intf                     ' See if there's a keyboard protocol
' */
' /* hsearch_desc_type
'------------------------------------------------------------------------------
' Search the configuration descriptor chain for a specific descriptor type.
'------------------------------------------------------------------------------
' On entry:
'   con_tot_len - total length of the config descriptor chain.
'   next_desc - offset from the configuration descriptor to start the search.
'     It is assumed that the offset will point to the start of a USB standard
'     descriptor.
'   search_key - descriptor type to match.
' On exit:
'   PTRB - if a match is found, the descriptor start address, otherwise zero.
'   next_desc - descriptor offset if found, otherwise unchanged.
'------------------------------------------------------------------------------
hsearch_desc_type
                mov     hsave0, hnext_desc
.next
                cmp     hnext_desc, hcon_tot_len  wcz
        if_ae   mov     ptrb, #0
        if_ae   mov     hnext_desc, hsave0
        if_ae   ret
                mov     ptrb, hconfig_base
                add     ptrb, hnext_desc
                rdbyte  htmp, ptrb[DESC_bDescType]
                cmp     htmp, hsearch_key        wz
        if_z    ret
                rdbyte  htmp, ptrb
                add     hnext_desc, htmp                ' Get offset of next desc to check
                jmp     #.next
' */
' /* hset_config
'------------------------------------------------------------------------------
' If a newly-connected device is recognized, do whatever is needed to configure
' it according to its function, or functions. In the case of this boot protocol
' keyboard/mouse class driver:
' - SetConfiguration(config_num)
' - SetProtocol(boot)
' - SetIdle(indefinite)
' - Enter the device interrupt IN polling task stage.
'------------------------------------------------------------------------------
' On entry:
' On exit:
'------------------------------------------------------------------------------
hset_config
'                mov     hkbd_ep_addr, #0                ' DEBUG
'                mov     hmouse_ep_addr, #0              ' DEBUG
                mov     htmp2, #DEV_UNKNOWN
                cmp     hkbd_ep_addr, #0        wz
        if_z    cmp     hmouse_ep_addr, #0      wz
        if_z    jmp     #.notify_client                 ' No boot keyboard or mouse interface
.set_config
                loc     ptra, #set_config
                mov     pa, #CON_bConfigVal             ' Get configuration value to set (always the default config in our case)
                add     pa, hconfig_base
                rdbyte  hpar1, pa
                wrword  hpar1, ptra[wValue]             ' Write the config value to the config SETUP struct
                mov     ep_addr_pid, hctrl_ep_addr      ' All configuration transactions use the control endpoint
                mov     pb, #0                          ' SetConfiguration() has no data stage
                call    #control_write
                cmp     retval, #PID_ACK        wz
        if_nz   ret
.kbd_config
                cmp     hkbd_ep_addr, #0        wz
        if_z    jmp     #.mouse_config                  ' No keyboard, so setup the mouse, if detected
                rdbyte  htmp, kb_intf_num_p
                loc     ptra, #set_protocol
                wrword  #BOOT_PROTOCOL, ptra[wValue]
                wrword  htmp, ptra[wIndex]
                mov     pb, #0                          ' SetProtocol() has no data stage
                call    #control_write
                cmp     retval, #PID_ACK        wz
        if_nz   mov     hkbd_ep_addr, #0
        if_nz   jmp     #.mouse_config
                mov     hpar1, #0                       ' SetIdle() duration 0 = indefinite
                rdbyte  hpar2, kb_intf_num_p
                call    #hset_idle
                cmp     retval, #PID_ACK        wz
        if_nz   mov     hkbd_ep_addr, #0
        if_nz   jmp     #.mouse_config
                mov     hctwait, _2ms_
                call    #poll_waitx
                call    #hset_kbdled_report
                cmp     retval, #PID_ACK        wz
        if_nz   mov     hkbd_ep_addr, #0
        if_nz   jmp     #.mouse_config
                wrbyte  #KEY_NO_KEY, kb_last_key_p
                mov     ep_addr_pid, hkbd_ep_addr
                call    #calc_crc5                      ' One-time calculation of the ep/addr/pid crc
                mov     hkbd_ep_addr, ep_addr_pid
                mov     ep_addr_pid, hctrl_ep_addr      ' Restore the control ep addr for mouse configuration
                mov     hkbd_poll_cnt, #0               ' Initialize key auto-repeat counters
                mov     hkbd_repeat, #KBD_REPEAT_DELAY
                mov     htmp2, #KB_READY                ' Keyboard interface configured
.mouse_config
                cmp     hmouse_ep_addr, #0       wz
        if_z    jmp     #.notify_client                 ' Notify the client that a keyboard interface was configured
                rdbyte  htmp, ms_intf_num_p
                loc     ptra, #set_protocol
                wrword  #BOOT_PROTOCOL, ptra[wValue]
                wrword  htmp, ptra[wIndex]
                mov     pb, #0                          ' SetProtocol() has no data stage
                call    #control_write
                cmp     retval, #PID_ACK        wz
        if_nz   mov     hmouse_ep_addr, #0
        if_nz   ret
                cmp     hkbd_ep_addr, #0        wz      ' A SetIdle() duration of indefinite covers both keyboard and mouse
        if_nz   jmp     #.mouse_timer                   ' So skip if it has already been done
                mov     hpar1, #0                       ' SetIdle() duration 0 = indefinite
                rdbyte  hpar2, ms_intf_num_p
                call    #hset_idle
                cmp     retval, #PID_ACK        wz
        if_nz   mov     hmouse_ep_addr, #0
        if_nz   ret
.mouse_timer
                mov     ep_addr_pid, hmouse_ep_addr
                call    #calc_crc5                      ' One-time calculation of the ep/addr/pid crc
                mov     hmouse_ep_addr, ep_addr_pid
                cmp     hkbd_ep_addr, #0         wz
        if_nz   mov     htmp2, #KBM_READY               ' Both keyboard and mouse interfaces were configured
        if_z    mov     htmp2, #M_READY                 ' Only the mouse interface configured
'        if_z    mov     poll_target, #poll_mouse
.notify_client
        _ret_   wxpin   htmp2, usb_event_pin            ' Notify the client keyboard and/or mouse configured
' */
' /* init_kbdm_data
'------------------------------------------------------------------------------
' Initialize the keyboard/mouse data area to start-up values.
'------------------------------------------------------------------------------
' On entry:
' On exit:
'------------------------------------------------------------------------------
init_kbdm_data
                mov     hkbd_ep_addr, #0
                mov     hmouse_ep_addr, #0
                mov     ptra, kb_intf_num_p
                mov     pa, ms_cur_report_p
                add     pa, #MOUSE_RPT_LEN
.loop
                wrbyte  #0, ptra++
                cmp     pa, ptra                wcz
        if_b    jmp     #.loop
                wrbyte  #PID_DATA0, kb_next_datax_p     ' Reset interrupt IN datax sequence PIDs
        _ret_   wrbyte  #PID_DATA0, ms_next_datax_p
' */
' /* hset_idle
'------------------------------------------------------------------------------
' Execute a ControlWrite() that will perform the HID-specific HID_SET_IDLE
' function.
'------------------------------------------------------------------------------
' On entry:
'   hpar1 - Byte1 duration, byte0 reportID (HID 1.11, section 7.2.4).
'   hpar2 - index number of the target interface.
' On exit:
'------------------------------------------------------------------------------
hset_idle
                mov     hctwait, _2ms_
                call    #poll_waitx
                loc     ptra, #set_idle
                wrword  hpar1, ptra[wValue]
                wrword  hpar2, ptra[wIndex]
                jmp     #control_write
' */
' /* hset_kbdled_report
'------------------------------------------------------------------------------
' Execute a ControlWrite() that will perform the HID-specific HID_SET_REPORT
' function to set keyboard CapsLk, ScrLk and NumLk indicators.
'------------------------------------------------------------------------------
' On entry:
'   ep_addr_pid - device address and enpoint for the request.
' On exit:
'   retval - transaction result.
'------------------------------------------------------------------------------
hset_kbdled_report
                rdbyte  htmp, kb_intf_num_p
                loc     ptra, #set_report
                wrword  ##(TYPE_OUTPUT << 8), ptra[wValue] ' Byte1 report type, byte0 reportID (0)
                wrword  htmp, ptra[wIndex]
                wrword  #KBD_OUT_RPT_LEN, ptra[wLength]
                mov     pb, kb_led_states_p             ' Start address of OUT data
                jmp     #control_write                  ' Execute ControlWrite(SET_REPORT) and back to caller
' */
' /* hget_kbd_in_report
'------------------------------------------------------------------------------
' Execute an IN interrupt transaction to poll for keyboard activity.
'------------------------------------------------------------------------------
' On entry:
' On exit:
'------------------------------------------------------------------------------
hget_kbd_in_report
                mov     ep_addr_pid, hkbd_ep_addr
                mov     ptra, kb_in_max_pkt_p
                rdbyte  htmp, ptra++                    ' Always ask for max report size
                rdbyte  hpar3, ptra
                setword hpar3, htmp, #1                 ' Max IN packet size to expect
                mov     hpar2, kb_cur_report_p
                call    #do_int_in
                cmp     retval, #PID_ACK        wz
        if_z    jmp     #.data
                cmp     retval, #PID_NAK        wz
        if_nz   jmp     #host_error                     ' Something other than ACK/NAK, so likely fatal
' The NAK count is used to determine when key auto-repeat kicks in.
                add     hkbd_poll_cnt, #1
                cmp     hkbd_poll_cnt, hkbd_repeat wcz
        if_b    ret                                     ' No auto-repeat action
                cmp     hkbd_poll_cnt, #KBD_REPEAT_DELAY wz
        if_z    drvnot  host_active_led                 ' Show keypress activity on the feedback LED
                rdbyte  hpar1, kb_last_key_p            ' Peek at the last key-down scancode
                cmp     hpar1, #KEY_CAPSLK      wz
        if_z    mov     hpar1, #KEY_NO_KEY
                cmp     hpar1, #KEY_NO_KEY      wz
        if_z    mov     hkbd_repeat, #KBD_REPEAT_DELAY  ' Key repeat delay reset on KEY_NO_KEY
        if_z    mov     hkbd_poll_cnt, #0
        if_z    ret
                add     hkbd_repeat, #KBD_REPEAT_RATE   ' Set the next repeat interval
                jmp     #repeat_key                     ' Repeat the key being held down and return to caller
.data
                drvnot  host_active_led                 ' Show keypress activity on the feedback LED
                cmp     hpar3, #0               wz
        if_z    ret                                     ' Ignore an empty DATAx packet
                mov     ptra, kb_next_datax_p
                rdbyte  hpar1, ptra
                cmp     hpar1, #PID_DATA0       wz
        if_z    mov     hpar1, #PID_DATA1               ' Txn success, so toggle DATAx
        if_nz   mov     hpar1, #PID_DATA0
                wrbyte  hpar1, ptra++
                wrbyte  hpar3, ptra                     ' Save actual bytes read
                call    #hkbd_compare                   ' Check keypress activity since last IN
.led_check
                rdbyte  htmp, kb_led_states_p
                cmp     hkbd_ledstates, htmp    wz
        if_z    ret                                     ' No toggle key indicator changes, so we're done
                wrbyte  hkbd_ledstates, kb_led_states_p ' Update toggle key indicator states
                mov     ep_addr_pid, hctrl_ep_addr
                call    #hset_kbdled_report
                cmp     retval, #PID_ACK        wz
        if_nz   jmp     #host_error                     ' FIXME: on !ACK try to recover instead of fatal error
                ret
' */
' /* hkbd_compare
'------------------------------------------------------------------------------
' Compare current and previous keyboard data buffers for keypress changes.
'------------------------------------------------------------------------------
' On entry:
' On exit:
'   hpar1 - scancode of the last key in the down state, otherwise zero
'     (KEY_NO_KEY) if no keys were in the down state.
'   hkbd_modkeys - the current (possibly changed) modifier key states.
'------------------------------------------------------------------------------
hkbd_compare
                mov     hpar3, #KEY_NO_KEY
                mov     ptrb, kb_cur_report_p           ' Start of scancode array


DAT                'RJA inserting scancode array transport here
                mov     mtemp1,pKeyReport
                rdlong  mtemp2,ptrb
                wrlong  mtemp2,mtemp1
                add     mtemp1,#4
                rdlong  mtemp2,ptrb[1]
                wrlong  mtemp2,mtemp1

'                rep     @.endReport,#7
'                wrbyte  pKeyReport,ptrb++
'.endReport
'                sub     ptrb,#7
DAT
                add     ptrb, #2
                rdbyte  hpar1, ptrb++                   ' First array byte tells us if no keys down or keyboard error
                cmp     hpar1, #KEY_ERR_UNDEF   wcz     ' First four key values in lookup table are "special" scancodes
        if_a    jmp     #.get_keys                      ' Process normal scancode
                cmp     hpar1, #KEY_NO_KEY      wz
        if_z    jmp     #.end_keys                      ' No key activity or key up event, so commit current to previous
                mov     ptra, kb_pre_report_p           ' Keyboard rollover overflow or other error
                mov     pb, kb_cur_report_p
                mov     hr0, #KBD_IN_RPT_LEN            ' This data can't be used for compare purposes in the future
                jmp     #hmemcpy                        ' So make the previous data the current data and return to caller
' Walk the scancode array looking for pressed keys
.get_keys
                rdbyte  hr0, kb_max_index_p
                mov     hr3, hr0
                add     hr3, kb_cur_report_p            ' Last scancode array element
.next_key
                cmp     hpar1, #KEY_NO_KEY      wz
        if_z    jmp     #.end_keys                      ' No more pressed keys in the current report
                call    #check_key                      ' Process this scancode
                cmp     ptrb, hr3               wz      ' Check for array end
        if_nz   rdbyte  hpar1, ptrb++
        if_nz   jmp     #.next_key
.end_keys
                rdbyte  htmp, kb_last_key_p
                cmp     hpar3, htmp             wz
        if_nz   mov     hkbd_repeat, #KBD_REPEAT_DELAY  ' Scancode was different than last,
        if_nz   mov     hkbd_poll_cnt, #0               ' so reset auto-repeat
        if_nz   wrbyte  hpar3, kb_last_key_p
.end
                mov     ptra, kb_cur_report_p           ' Done processing current IN report
                mov     pb, kb_pre_report_p
                mov     hr0, #KBD_IN_RPT_LEN
                jmp     #hmemcpy                        ' Copy current->previous and back to caller
' */
' /* check_key (boot protocol specific)
'------------------------------------------------------------------------------
' See if the given scancode exists in the previously read keyboard data. If
' found it means the key is still pressed and can be ignored. If not found it's
' a new keypress to process.
'------------------------------------------------------------------------------
' On entry:
'   hr0 - Count of total keyboard data bytes.
'   hpar1 - scancode to check.
'   hkbd_ledstates - current CapsLock, NumLock and ScrollLock bitflags.
' On exit:
'   hpar1 - input scancode if matched (still in the down state), otherwise the
'     scancode of the newly pressed key.
'   hkbd_ledstates - possibly modified toggle key state.
'   hpar3 - scancode of the last key found in the down state.
'------------------------------------------------------------------------------
check_key
                mov     ptra, kb_pre_report_p
                add     ptra, #2
                mov     pb, kb_pre_report_p
                add     pb, hr0                         ' Last scancode array element
.loop
                rdbyte  htmp, ptra++
                cmp     htmp, hpar1             wz
        if_z    mov     hpar3, hpar1
        if_z    ret                                     ' Key still in down state, so done with this key
                cmp     ptra, pb                wz
        if_nz   jmp     #.loop
' The previous key report has been searched and no match found, so this is a
' newly-pressed key. Now look for toggle key changes.
                mov     hkbd_scancode, hpar1            ' Save new scancode
                mov     htmp, hkbd_ledstates
                cmp     hpar1, #KEY_CAPSLK      wz
        if_z    bitnot  htmp, #LED_CAPSLKB
                cmp     hpar1, #KEY_SCRLK       wz
        if_z    bitnot  htmp, #LED_SCRLKB
                cmp     hpar1, #KEY_KPNUMLK     wz
        if_z    bitnot  htmp, #LED_NUMLKB
                cmp     htmp, hkbd_ledstates    wz
        if_nz   mov     hkbd_ledstates, htmp
'        debug(uhex_long_array(kb_buff_p, #KBD_BUFFMASK + 1))
' Fall through with new keypress scancode
' vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
' */
' /* buffer_key
'------------------------------------------------------------------------------
' Process a newly-pressed key using the scancode table in hub. The keypress
' data is packed into a single long and written to a FIFO circular buffer.
' There is no check for buffer overflow.
'-+-------------------+---------------+----------+-------------+
' |       Byte3       |     Byte2     |   Byte1  |    Byte0    |
'-+-------------------+---------------+----------+-------------+
' | Toggle Key States | Modkey States | Scancode | ASCII Value |
'-+-------------------+---------------+----------+-------------+
' On entry:
'   hpar1 - the scancode of the key to process.
' On exit:
'   hpar3 - the scancode of the last-pressed key.
'------------------------------------------------------------------------------
buffer_key
                mov     hpar3, hpar1                    ' Save scancode for auto-repeat evaluation
                loc     ptra, #kbd_table
                shl     hpar1, #1                       ' Scancode table elements are word
                add     ptra, hpar1
                rdword  hpar2, ptra                     ' Lookup ASCII character pair (byte1 shifted, byte0 un-shifted)
                shr     hpar1, #1                       ' Restore scancode
                rdbyte  hkbd_modkeys, kb_cur_report_p   ' Key array zero is shift/alt/ctrl/gui modifier bits
                testb   hkbd_ledstates, #LED_CAPSLKB wc
        if_nc   jmp     #.chk_shift                     ' CapsLock not active, so we can evaluate shift now
' Active CapsLock key only affects the alpha keys
' All modkeys other than SHIFT are left up to the client to handle
                cmp     hpar1, #KEY_Z_z         wcz     ' The first valid scancode is KEY_A_a
        if_a    jmp     #.chk_shift                     ' So if it's above KEY_Z_z it's not an alpha key
                test    hkbd_modkeys, #KEYS_SHIFT wz
        if_nz   getbyte hkbd_keypress, hpar2, #0        ' CapsLock AND Shift is lowercase alpha
        if_z    getbyte hkbd_keypress, hpar2, #1
                jmp     #.out_key
.chk_shift
                test    hkbd_modkeys, #KEYS_SHIFT wz  ' Determine upper/lower case ASCII character to send
        if_z    getbyte hkbd_keypress, hpar2, #0
        if_nz   getbyte hkbd_keypress, hpar2, #1
.out_key
' Add the newly pressed key data to the FIFO circular buffer
                setbyte hkbd_keypress, hkbd_ledstates, #3
                setbyte hkbd_keypress, hkbd_modkeys, #2
                setbyte hkbd_keypress, hpar1, #1
repeat_key
                mov     pa, kb_buff_p                   ' Base address of the buffer
                mov     pb, kb_head_p
                rdbyte  htmp, pb                        ' Current head buffer offset 0..KBD_BUFFMASK
                shl     htmp, #2                        ' Head buffer offset in longs
                add     pa, htmp                        ' Buffer address for the new data
                wrlong  hkbd_keypress, pa
                shr     htmp, #2                        ' Back to 0..KBD_BUFFMASK format
                incmod  htmp, #KBD_BUFFMASK             ' Advance or wrap new head and save it
                wrbyte  htmp, pb
        _ret_   wrbyte  htmp, pb
' */
' /* hget_mouse_in_report
'------------------------------------------------------------------------------
' Execute an IN interrupt transaction to poll for mouse activity. If new mouse
' data is present, the button flags and X/Y direction/velocity values are
' packed into a single long and written to a FIFO circular buffer. There is no
' check for buffer overflow.
'-+----------+-----------+-----------+--------------+
' |  Byte3   |   Byte2   |   Byte1   |    Byte0     |
'-+----------+-----------+-----------+--------------+
' | Reserved | Y Dir/Vel | X Dir/Vel | Button Flags |
'-+----------+-----------+-----------+--------------+--------------------------
' On entry:
' On exit:
'------------------------------------------------------------------------------
hget_mouse_in_report
                mov     ep_addr_pid, hmouse_ep_addr
                mov     ptra, ms_in_max_pkt_p
                rdbyte  htmp, ptra++                    ' Always ask for max report size
                rdbyte  hpar3, ptra++                   ' Word0 DATAx PID to expect
                setword hpar3, htmp, #1                 ' Word1 max IN packet size to expect
                mov     hpar2, ptra                     ' IN data to mouse_cur_report buffer
                call    #do_int_in
                cmp     retval, #PID_ACK        wz
        if_z    jmp     #.data
                cmp     retval, #PID_NAK        wz
        if_nz   jmp     #host_error                     ' Something other than ACK/NAK, so likely fatal
.nak
                add     hmouse_poll_cnt, #1             ' Use the NAK results as a timer for LED feedback
                cmp     hmouse_poll_cnt, ##MOUSE_NAK_DELAY  wcz
        if_ae   drvnot  host_active_led
        if_ae   mov     hmouse_poll_cnt, #0
                ret
.data
' Some devices may return a zero-length packet with ACK, which I'm not sure
' is "legal" or not. The easiest response is to just treat it as a NAK...
                cmp     hpar3, #0               wz      ' hpar3 has actual byte count received
        if_z    jmp     #.nak
                mov     hmouse_poll_cnt, #0
                drvnot  host_active_led                 ' Show mouse activity on the feedback LED
                rdbyte  hpar1, ms_next_datax_p
                cmp     hpar1, #PID_DATA0       wz
        if_z    mov     hpar1, #PID_DATA1               ' ACK, so toggle DATAx
        if_nz   mov     hpar1, #PID_DATA0
                wrbyte  hpar1, ms_next_datax_p
.data_out
                mov     ptra, ms_cur_report_p           ' Pack mouse data into a single long for the client
                rdbyte  hr0, ptra++                     ' Button flags
                rdbyte  htmp, ptra++                    ' X direction and velocity is 8-bit signed
                setbyte hr0, htmp, #1
                rdbyte  htmp, ptra++                    ' Y direction and velocity is 8-bit signed
                setbyte hr0, htmp, #2
                cmp     hpar3, #4               wz      ' Very few mice send +/- scroll wheel data in boot protocol
        if_z    rdbyte  htmp, ptra
        if_z    setbyte hr0, htmp, #3                   ' If it's there, use it
                wrlong  hr0, mouse_data_p


                'RJA: Update mouse data in main Spin cog
                'buttons
                rdlong  mtemp1,pMouseButtons  'RJA:  update buttons with any new flags
                getbyte mtemp2,hr0,#0
                mov     mtemp1,mtemp2
                wrlong  mtemp1,pMouseButtons 'RJA: update main cog
                'X
                getbyte mtemp1,hr0,#1
                shl     mtemp1,#24
                sar     mtemp1,#24
                rdlong  mtemp2,pMouseX
                adds    mtemp2,mtemp1
                cmps    mtemp2,#0 wcz
        if_b    mov     mtemp2,#0
                rdlong  mtemp1,pMouseMaxX
                cmp     mtemp2,mtemp1 wcz
        if_a    mov     mtemp2,mtemp1
                wrlong  mtemp2,pMouseX
                 'Y
                getbyte mtemp1,hr0,#2
                shl     mtemp1,#24
                sar     mtemp1,#24
                rdlong  mtemp2,pMouseY
                adds    mtemp2,mtemp1
                cmps    mtemp2,#0 wcz
        if_b    mov     mtemp2,#0
                rdlong  mtemp1,pMouseMaxY
                cmp     mtemp2,mtemp1 wcz
        if_a    mov     mtemp2,mtemp1
                wrlong  mtemp2,pMouseY


        _ret_   wxpin   #M_DATA, usb_event_pin
' */
' #endregion (USB hub execution)
' #region Partially populated SETUP packets
'------------------------------------------------------------------------------
get_dev_desc            byte    (DIR_DEV_TO_HOST | TYPE_STANDARD | RECIP_DEVICE)
                        byte    REQ_GET_DESC
                        word    TYPE_DEVICE << 8
                        word    0       ' Zero or Language ID (Section 9.6.7)
                        word    0       ' Number of bytes to transfer if there is a data stage
get_config_desc         byte    (DIR_DEV_TO_HOST | TYPE_STANDARD | RECIP_DEVICE)
                        byte    REQ_GET_DESC
                        word    TYPE_CONFIG << 8
                        word    0       ' Zero or Language ID (Section 9.6.7)
                        word    0       ' Number of bytes to transfer if there is a data stage
set_config              byte    (DIR_HOST_TO_DEV | TYPE_STANDARD | RECIP_DEVICE)
                        byte    REQ_SET_CONFIG
                        word    0       ' Configuration value
                        word    0       ' Zero
                        word    0       ' Zero, as REQ_SET_CONFIG has no data stage
set_address             byte    (DIR_HOST_TO_DEV | TYPE_STANDARD | RECIP_DEVICE)
                        byte    REQ_SET_ADDR
                        word    0       ' Zero
                        word    0       ' Zero
                        word    0       ' Zero, as REQ_SET_ADDR has no data stage
'------------------------------------------------------------------------------
' The SET_PROTOCOL request is supported by devices in the "Boot" subclass. The
' wValue field dictates which protocol should be used.
'
' When initialized, all devices default to report protocol. However the host
' should not make any assumptions about the device state and should set the
' desired protocol whenever initializing a device.
'------------------------------------------------------------------------------
set_protocol            byte    (DIR_HOST_TO_DEV | TYPE_CLASS | RECIP_INTERFACE)
                        byte    HID_SET_PROTO
                        word    BOOT_PROTOCOL   ' 0 = Boot Protocol, 1 = Report Protocol
                                                ' (HID 1.11 Section 7.2.6).
                        word    0               ' Interface index number.
                        word    0               ' Zero, as HID_SET_PROTO has no data stage.
'------------------------------------------------------------------------------
set_idle                byte    (DIR_HOST_TO_DEV | TYPE_CLASS | RECIP_INTERFACE)
                        byte    HID_SET_IDLE
                        word    0       ' Byte1 = duration, byte0 = ReportID. A duration of zero inhibits
                                        ' reporting until a change is detected in the report data

                                        ' (HID 1.11 Section 7.2.4).
                        word    0       ' Interface index number.
                        word    0       ' Zero, as HID_SET_IDLE has no data stage.
set_report              byte    (DIR_HOST_TO_DEV | TYPE_CLASS | RECIP_INTERFACE)
                        byte    HID_SET_REPORT
                        word    0       ' Byte1 = report type, byte0 = ReportID.
                                        ' (HID 1.11 Section 7.2.2).
                        word    0       ' Interface index number.
                        word    0       ' Size of the report, in bytes.
' #endregion Partially populated SETUP packets
' #region Data buffers and structures
'------------------------------------------------------------------------------
' The USB data cache area gets zero-filled at every device disconnect
'------------------------------------------------------------------------------
usb_cache_start
urx_buff        byte    0[URX_BUFF_LEN]         ' USB IN DATAx scratch buffer
dev_desc_buff   byte    0[DEV_DESC_LEN]         ' Device descriptor
con_desc_buff   byte    0[CON_BUFF_LEN]         ' Configuration descriptor chain
usb_cache_end
'------------------------------------------------------------------------------
' #endregion (Data buffers and structures)
' #region (USB Boot Protocol Mouse/Keyboard Hub Interface)
'------------------------------------------------------------------------------
' Version info:
sz_usb_kbm_ver  byte    "v0.1.4", 0
p2rev_char      byte    "B"
' USB error description table:
sz_none         byte "no error", 0
sz_bad_cmd      byte "bad command", 0
sz_urx          byte "bus rx fail", 0
sz_se1          byte "SE1 state", 0
sz_packet       byte "packet rx fail", 0
sz_tat          byte "turn-around time exceeded", 0
sz_txn_retry    byte "txn retries exceeded", 0
sz_xfer_retry   byte "xfer retries exceeded", 0
sz_nak          byte "NAK fail", 0
sz_datax_sync   byte "DATAX sync fail", 0
sz_config_fail  byte "configuration failed", 0
sz_timeout      byte "operation timeout", 0
'debug_data_val  long DBG_DEADBEEF
'------------------------------------------------------------------------------
kbd_table ' Index is the key scan code
{$00} word KEY_NO_KEY, KEY_ERR_ROLLOVER, KEY_POST_FAIL, KEY_ERR_UNDEF ' $03
{$04} word $4161, $4262, $4363, $4464                                 ' $07: Aa Bb Cc Dd
{$08} word $4565, $4666, $4767, $4868, $4969, $4A6A, $4B6B, $4C6C     ' $0f: Ee Ff Gg Hh Ii Jj Kk Ll
{$10} word $4D6D, $4E6E, $4F6F, $5070, $5171, $5272, $5373, $5474     ' $17: Mm Nn Oo Pp Qq Rr Ss Tt
{$18} word $5575, $5676, $5777, $5878, $5979, $5A7A, $2131, $4032     ' $1f: Uu Vv Ww Xx Yy Zz !1 @2
{$20} word $2333, $2434, $2535, $5E36, $2637, $2A38, $2839, $2930     ' $27: #3 $4 %5 ^6 &7 *8 (9 )0
{$28} word $0D0D, $1B1B, $0808, $0909, $2020, $5F2D, $2B3D, $7B5B     ' $2f: Enter Esc BkSpc Tab Spc _- += {[
{$30} word $7D5D, $7C5C, $7E23, $3A3B, $2227, $7E60, $3C2C, $3E2E     ' $37: }] |\ ~# :; "' ~` <, >.
{$38} word $3F2F, $3939, $3A3A, $3B3B, $3C3C, $3D3D, $3E3E, $3F3F     ' $3f: ?/ CapsLock F1 F2 F3 F4 F5 F6
{$40} word $4040, $4141, $4242, $4343, $4444, $4545, $4646, $4747     ' $47: F7 F8 F9 F10 F11 F12 PrtSc, ScrLk
{$48} word $4848, $4949, $4A4A, $4B4B, $7F7F, $4D4D, $4E4E, $4F4F     ' $4f: Pause, Ins, Home PgUp BkSpc_Del End PgDn Right
{$50} word $5050, $5151, $5252, $5353, $2F2F, $2A2A, $2D2D, $2B2B     ' $57: Left Down Up KpdNumLck Kp/ Kp* Kp- Kp+
{$58} word $0D0D, $3131, $3232, $3333, $5034, $3535, $3636, $3737     ' $5f: KpEnter Kp1_End Kp2_Down Kp3_PgDn Kp4_Left Kp5 Kp6_Right Kp7_Home
{$60} word $3838, $3939, $3030, $7F2E, $5C7C, $6565                   ' $65: Kp8_Up Kp9_PgUp Kp0_Ins Kp._Del Kp\_| App
' #endregion (USB Boot Protocol Mouse/Keyboard Hub Interface)
' #region (USB Descriptor Definitions)
CON
'------------------------------------------------------------------------------
' USB References:
' Universal Serial Bus Specification, Revision 2.0
'   www.usb.org/developers/docs/usb20_docs/
' Device Class Definition for Human Interface Devices (HID), Version 1.11
'   www.usb.org/developers/hidpage/
'------------------------------------------------------------------------------
' SETUP packet bmRequestType bit groups (Section 9.3.1, Table 9-2).
' Use TYPE_STANDARD for all USB Standard Device Request codes.
'------------------------------------------------------------------------------
' D7 Data direction  | D6:5 Type    | D4:0 Recipient
'------------------------------------------------------------------------------
' 0 - Host-to-device | 0 = Standard | 0 = Device
' 1 - Device-to-host | 1 = Class    | 1 = Interface
'                    | 2 = Vendor   | 2 = Endpoint
'                    | 3 = Reserved | 3 = Other
'                    |              | 4 -31 = Reserved
'------------------------------------------------------------------------------
    DIR_HOST_TO_DEV = 0 << 7
    DIR_DEV_TO_HOST = 1 << 7                ' D7 Data direction
    TYPE_STANDARD   = %00 << 5              ' D6:D5 Type (use Standard for all USB Standard Device Requests
    TYPE_CLASS      = %01 << 5
    TYPE_VENDOR     = %10 << 5
    TYPE_RESERVED   = %11 << 5
    RECIP_DEVICE    = %0_0000               ' D4..D0 Recipient
    RECIP_INTERFACE = %0_0001
    RECIP_ENDPOINT  = %0_0010
    RECIP_OTHER     = %0_0011
'    RECIP 4 - 31 = Reserved
'------------------------------------------------------------------------------
' Standard Device Request codes (Section 9.4, Table 9-4):
'------------------------------------------------------------------------------
    #$00, REQ_GET_STATUS, REQ_CLEAR_FEATURE, REQ_RESERVED_1, REQ_SET_FEATURE
    REQ_RESERVED2, REQ_SET_ADDR, REQ_GET_DESC, REQ_SET_DESC, REQ_GET_CONFIG
    REQ_SET_CONFIG, REQ_GET_INTF, REQ_SET_INTF, REQ_SYNC_FRAME
'------------------------------------------------------------------------------
' Standard descriptor types (Section 9.4, Table 9-5):
'------------------------------------------------------------------------------
    #$01, TYPE_DEVICE, TYPE_CONFIG, TYPE_STRING, TYPE_INTERFACE, TYPE_ENDPOINT
    TYPE_QUALIFIER, TYPE_OTHER_SPEED, TYPE_INTERFACE_PWR, TYPE_OTG
'------------------------------------------------------------------------------
' Device/Interface Class Codes (full list at www.usb.org/developers/defined_class):
'------------------------------------------------------------------------------
    #$00, CLASS_INFO_INTF, CLASS_AUDIO, CLASS_COMM, CLASS_HID, CLASS_UNDEF0
    CLASS_PHYSICAL, CLASS_IMAGE, CLASS_PRINTER, CLASS_MASS_STORAGE, CLASS_HUB
    CLASS_CDC_DATA, CLASS_SMARTCARD, CLASS_UNDEF1, CLASS_CONT_SECURITY, CLASS_VIDEO
    CLASS_HEALTH, CLASS_AUDIO_VIDEO, CLASS_BILLBOARD, CLASS_TYPE_C_BRIDGE
    CLASS_DIAGNOSTIC_DEV  = $dc
    CLASS_WIRELESS_CTRL   = $e0
    CLASS_MISCELLANEOUS   = $ef
    CLASS_APP_SPECIFIC    = $fe
    CLASS_VENDOR_SPECIFIC = $ff
'------------------------------------------------------------------------------
' HID Class Requests (v1.11 HID Device Class Definition, Section 7.2):
'------------------------------------------------------------------------------
    #$01, HID_GET_REPORT, HID_GET_IDLE, HID_GET_PROTO[6] ' $04 - $08 reserved
    HID_SET_REPORT, HID_SET_IDLE, HID_SET_PROTO
' HID Descriptor types:
    #$21, TYPE_HID, TYPE_REPORT, TYPE_PHYSICAL ' HID types $24 - $2f are reserved
'------------------------------------------------------------------------------
' HID report types (v1.11 HID Device Class Definition, Section 7.2.1):
'------------------------------------------------------------------------------
    #$01, TYPE_INPUT, TYPE_OUTPUT, TYPE_FEATURE ' $04 - $ff are reserved
'------------------------------------------------------------------------------
' HID Interface SubClasses:
'------------------------------------------------------------------------------
    #$00, SUBCLASS_INTF_NONE, SUBCLASS_INTF_BOOT
'------------------------------------------------------------------------------
' HID Protocol codes:
'------------------------------------------------------------------------------
    #$00, INTF_PROTO_NONE, INTF_PROTO_KBD, INTF_PROTO_MOUSE
    #$00, BOOT_PROTOCOL, REPORT_PROTOCOL
'------------------------------------------------------------------------------
' Other HID buffer lengths:
'------------------------------------------------------------------------------
    MAX_HID_REPTS   = 4             ' We have this many HID report buffers
    REPT_BUFF_LEN   = 1024          ' HID reports can be quite large
    REPT_STRUCT_LEN = REPT_BUFF_LEN + 2 ' Struct is wLength, bData[REPT_BUFF_LEN]
    LANG_BUFF_LEN   = 128           ' LangID array buffer (in bytes)
    USTR_BUFF_LEN   = 128           ' Unicode string buffer (in bytes)
'------------------------------------------------------------------------------
' USB-IF defined language IDs (http://www.usb.org/developers/docs.html)
'------------------------------------------------------------------------------
    LANG_ENG_US  = $0409            ' English (United States)
    LOCAL_LANGID = LANG_ENG_US      ' Set your default langID here
'------------------------------------------------------------------------------
' SETUP structure member offsets.
' NOTE: These offsets are defined in terms of the structure member's data type,
'       to take advantage of PTRA/B scaled indexing, e.g.:
'         RDBYTE D,   PTRA[bRequest]
'         WRWORD D/#, PTRA[wLength]
'------------------------------------------------------------------------------
    bmRequestType = 0
    bRequest      = 1
    wValue        = 1
    wIndex        = 2
    wLength       = 3
' SETUP bmRequestType combinations:
{
    HTD_STD_DEV = (DIR_HOST_TO_DEV | TYPE_STANDARD | RECIP_DEVICE)
    DTH_STD_DEV = (DIR_DEV_TO_HOST | TYPE_STANDARD | RECIP_DEVICE)
    HTD_STD_INT = (DIR_HOST_TO_DEV | TYPE_STANDARD | RECIP_INTERFACE)
    HTD_STD_EP  = (DIR_HOST_TO_DEV | TYPE_STANDARD | RECIP_ENDPOINT)
}
'------------------------------------------------------------------------------
' Standard USB descriptor structure sizes in bytes. The values defined are the
' minimum size of the descriptor:
'------------------------------------------------------------------------------
    SETUP_TXN_LEN = 8
    DEV_DESC_LEN  = 18
    CON_DESC_LEN  = 9
    INTF_DESC_LEN = 9
    ENDP_DESC_LEN = 7
    STR0_DESC_LEN = 4
    USTR_DESC_LEN = 4
'------------------------------------------------------------------------------
' Other USB-related buffer sizes:
'------------------------------------------------------------------------------
    URX_BUFF_LEN = 128      ' USB receiver scratch buffer
    CON_BUFF_LEN = 256      ' Entire configuration descriptor chain
'------------------------------------------------------------------------------
' CON_bmAttrs member bit positions:
'------------------------------------------------------------------------------
    ATTR_RESVB     = 7      ' Reserved (should always be one)
    ATTR_SELF_PWRB = 6      ' Device Self-Powered
    ATTR_RMT_WAKEB = 5      ' Supports remote wakeup
'    bmAttributes bits 4..0 reserved and reset to zero
'------------------------------------------------------------------------------
' Standard USB descriptor struct member offset and member size, in bytes. Note
' that the DESC_bLength and DESC_bDescType members are defined in all of the
' descriptors (including HID) at the offsets shown.
'------------------+
' !!! IMPORTANT !!!|
'------------------+
' All standard descriptor member offsets are defined in bytes, so if you want
' to use PTRA/B indexing for WORD or LONG data, the unscaled PTRx[##index20]
' syntax should be used and the compiler will invoke AUGS with RDxxxx/WRxxxx,
' e.g.: RDBYTE D,   PTRA[DEV_bMaxPktSize0] is OK when it's a byte member
'       WRWORD D/#, PTRA[##DEV_bIdProduct] use unscaled if member is word/long.
' If PTRA/B unscaled indexing is used with pre/post increment/decrement, one
' must be very careful...
'------------------------------------------------------------------------------
'       Member          Offset  Size    Value           Description
'------------------------------------------------------------------------------
    DESC_bLength     = 0    ' 1     Number          Minimum size of this descriptor in bytes
    DESC_bDescType   = 1    ' 1     Constant        TYPE_DEVICE
' Device Descriptor (Section 9.6.1, Table 9-8):
    DEV_bcdUSB       = 2    ' 2     BCD             e.g., 2.10 is $0210
    DEV_bDevClass    = 4    ' 1     Class           Class code assigned by USB-IF.
    DEV_bDevSubClass = 5    ' 1     SubClass        SubClass Code assigned by USB-IF.
    DEV_bProtocol    = 6    ' 1     Protocol        Protocol Code assigned by USB-IF.
    DEV_bMaxPktSize0 = 7    ' 1     Number          Max packet size for endpoint 0. Must be 8 for LS, 16, 32 or 64 for FS.
    DEV_idVendor     = 8    ' 2     ID              Vendor ID - must be obtained from USB-IF.
    DEV_idProduct    = 10   ' 2     ID              PRoduct ID - must be obtained from USB-IF.
    DEV_bcdDevice    = 12   ' 2     BCD             Device release number in BCD.
    DEV_iMfg         = 14   ' 1     Index           Index of string descriptor describing manufacturer - set to zero if no string.
    DEV_iProduct     = 15   ' 1     Index           Index of string descriptor describing product - set to zero if no string.
    DEV_iSerialNum   = 16   ' 1     Index           Index of string descriptor describing device serial number - set to zero if no string.
    DEV_iNumConfigs  = 17   ' 1     Number          Number of possible configurations.
  ' Configuration Descriptor (Section 9.6.3, Table 9-10):
'    DESC_bLength     = 0    ' 1     Number          Minimum size of the descriptor, in bytes
'    DESC_bDescType   = 1    ' 1     Constant        TYPE_CONFIG
    CON_wTotalLen    = 2    ' 2     Number          See Section 9.6.3, Table 9-10.
    CON_bNumIntf     = 4    ' 1     Number          Number of interfaces supported by this configuration.
    CON_bConfigVal   = 5    ' 1     Number          Value to use as an argument to the SetConfiguration() request to select this configuration.
    CON_iConfig      = 6    ' 1     Index           Index of string descriptor describing this configuration.
    CON_bmAttrs      = 7    ' 1     Bitmap          See Table 9-10.
    CON_bMaxPower    = 8    ' 1     mA              Expressed in 2MA units (i.e. 50 = 100 mA)
' Interface Descriptor (Section 9.6.5, Table 9-12):
'    DESC_bLength     = 0    ' 1     Number          Minimum size of the descriptor, in bytes
'    DESC_bDescType   = 1    ' 1     Constant        TYPE_INTERFACE
    INTF_bIntfNum    = 2    ' 1     Number          See Table 9-12.
    INTF_bAltSetting = 3    ' 1     Number          Value used to select this alternate setting for the interface identified in the prior field.
    INTF_bNumEndpts  = 4    ' 1     Number          See Table 9-12.
    INTF_bIntfClass  = 5    ' 1     Class           Class code (assigned by USB-IF). If this field is 0xFF, the interface class is vendor-specific.
    INTF_bSubClass   = 6    ' 1     SubClass        Subclass code (assigned by USB-IF). These codes are qualified by the value of the
                            '                       bIntfClass field. If the bInterfaceClass field is not set to 0xFF, all values are
                            '                       reserved for assignment by the USB-IF.
    INTF_bProtocol   = 7    ' 1     Protocol        Protocol code (assigned by the USB). These codes are qualified by the value of the
                            '                       bIntfClass and the bSubClass fields. If this field is set to 0xFF, the device uses a
                            '                       vendor-specific protocol for this interface.
    INTF_iInterface  = 8    ' 1     Index           Index of string descriptor describing this interface.
' Endpoint Descriptor (Section 9.6.6, Table 9-13):
'    DESC_bLength     = 0    ' 1     Number          Minimum size of the descriptor, in bytes
'    DESC_bDescType   = 1    ' 1     Constant        TYPE_ENDPOINT
    ENDP_bAddress    = 2    ' 1     Endpoint        See table 9-13.
    ENDP_bmAttrs     = 3    ' 1     Bitmap
    ENDP_wMaxPktSize = 4    ' 2     Number
    ENDP_bInterval   = 6    ' 1     Number
' String Descriptor Zero (Section 9.6.7, Table 9-15):
'    DESC_bLength     = 0    ' 1     N + 2
'    DESC_bDescType   = 1    ' 1     Constant
    STR0_wLangID     = 2    ' N     LangID[(N - 2) / 2]
' Unicode String Descriptor (Section 9.6.7, Table 9-16):
'    DESC_bLength     = 0    ' 1     N + 2
'    DESC_bDescType   = 1    ' 1     Constant
    USTR_wString     = 2    ' N     wString[(N - 2) / 2]
' #region Descriptors required by this device class
'------------------------------------------------------------------------------
' HID Descriptor (Section 6.2.1)
'------------------------------------------------------------------------------
'    DESC_bLength     = 0    ' 1     Number          Minimum size of the descriptor, in bytes
'    DESC_bDescType   = 1    ' 1     Constant        TYPE_HID
    HID_bcdHID       = 2    ' 2     BCD             e.g., 1.10 is $0110
    HID_bCountryCode = 4    ' 1     Number          Hardware target country
    HID_bNumDesc     = 5    ' 1     Number          Number of HID class descriptors to follow, always at least one.
    HID_bDescType    = 6    ' 1     Constant        Type of HID class descriptor e.g. TYPE_REPORT ($22).
    HID_wDescLength  = 7    ' 2     Number          Total length of descriptor(s).
' Keyboard and mouse report data buffer lengths (boot protocol):
    KBD_OUT_RPT_LEN = 1     ' CapsLock, NumLock and ScrollLock status bits
    KBD_IN_RPT_LEN  = 8     ' Maximum keyboard boot protocol IN data packet size
    MOUSE_RPT_LEN   = 8     ' Maximum mouse boot protocol IN data packet size
'------------------------------------------------------------------------------
' #endregion Descriptors required by this device class
' #endregion Con (USB Descriptor Definitions)
{{
MIT License
+----------------------------------------------------------------------------------------------------------------------+
|                                             TERMS OF USE: MIT License                                                |
+----------------------------------------------------------------------------------------------------------------------+
|Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated          |
|documentation files (the "Software"), to deal in the Software without restriction, including without limitation the   |
|rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit|
|persons to whom the Software is furnished to do so, subject to the following conditions:                              |
|                                                                                                                      |
|The above copyright notice and this permission notice shall be included in all copies or substantial portions of the  |
|Software.                                                                                                             |
|                                                                                                                      |
|THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE  |
|WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
|COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR      |
|OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.      |
+----------------------------------------------------------------------------------------------------------------------+
}}