'RJA Revised for Rev.B silicon based on what Eric Smith did for micropython '------------------------------------------------------------------------------ ' garryj 05/2019 ' TABs as space, width: 8. ' Serial terminal: Tera Term, Parallax Serial Terminal (set the cterm_type ' register to the appropriate TERM_* constant). ' #region (Shared constants) CON 'New stuff for Rev.B USB_V2MODE = %1_11011_0 + 1 << 16 ' In P2 silicon v2 all USB smart pin modes are consolidated to %11011. USB_H_FS_NCO = %11 << 14 + _12Mbps USB_H_LS_NCO = %10 << 14 + _1_5Mbps CON ' Don't forget to set this to match the top object's MHz rate! _FCLKFREQ = 160_000_000.0 _CLKFREQ = round(_FCLKFREQ) '------------------------------------------------------------------------------ ' P2-ES USB Host (Master) x 2 Accessory Board I/O group base pin: USB_BASEPIN = 0' 16 ' + offsets for ES-64006 Serial Host board '------------------------------------------------------------------------------ '#define USB_PORTB #ifndef USB_PORTB ' PortA is the default. ' USB "portA" connector pin assignments (lower port): HOST_ACTIVE_LED = USB_BASEPIN + 4 ' USB protection enable/disable USB_PROTECT_ON = USB_BASEPIN + 5 ' Smart pin pair used for the USB transceiver: DM = USB_BASEPIN + 6 ' DM is "The Brain" DP = USB_BASEPIN + 7 ' DP is passive ' Long repository pin#: USB_EVENT_REPO = USB_BASEPIN + 8 ' LED to blink at host/driver fatal error: HOST_ERROR_LED = LED56 #else ' USB "portB" connector pin assignments (upper port): HOST_ACTIVE_LED = USB_BASEPIN + 0 ' USB protection enable/disable USB_PROTECT_ON = USB_BASEPIN + 1 ' Smart pin pair used for the USB transceiver: DM = USB_BASEPIN + 2 ' DM is "The Brain" DP = USB_BASEPIN + 3 ' DP is passive ' Long repository pin#: USB_EVENT_REPO = USB_BASEPIN + 9 ' LED to blink at host/driver fatal error: HOST_ERROR_LED = LED57 #endif ' Smart pin configuration for "long repository" mode: SP_REPO1_MODE = %00001_0 | 1 << 16 ' %MMMMM_0, P[12:10] != %101 '------------------------------------------------------------------------------ ' LED to show USB activity: ' KBM_ACTIVE_LED = LED56 ' P2-ES board user LEDs tied to pins: #56, LED56, LED57, LED58, LED59, LED60, LED61, LED62, LED63 '------------------------------------------------------------------------------ ' A simple event 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 have a related spin function to handle the event. '------------------------------------------------------------------------------ #0, NO_EVENT, USB_ERROR, DEV_UNKNOWN, KB_READY, M_READY, KBM_READY DEV_DISCONNECT, USB_DBG_INFO, M_DATA '------------------------------------------------------------------------------ ' 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 = |< MOUSE_LBTNB MOUSE_RBTNF = |< MOUSE_RBTNB MOUSE_CBTNF = |< 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 ACSII 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. '------------------------------------------------------------------------------ ' 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_Z_z = $1d ' Digit scancodes and their shifted characters: KEY_1 = $1e ' 1 and ! KEY_0 = $27 ' 0 and ) ' Keyboard return (ENTER), tab, space, etc.: KEY_ENTER = $28 KEY_CAPSLK = $39 ' F1..F12 keys: KEY_F1 = $3a KEY_F12 = $45 ' More special keys and cursor movement: KEY_PRTSCN = $46 KEY_SCRLK = $47 KEY_INSERT = $49 KEY_DELETE = $4c KEY_UP = $52 ' Keypad keys: KEY_NUMLK = $53 KEY_DOT_DEL = $63 '------------------------------------------------------------------------------ ' 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 = |< LED_NUMLKB LED_CAPSLKF = |< LED_CAPSLKB LED_SCRLKF = |< LED_SCRLKB LED_COMPOSEF = |< LED_COMPOSEB LED_KANAF = |< LED_KANAB LED_CONST0F = |< LED_CONST0B LED_CONST1F = |< LED_CONST1B LED_CONST2F = |< 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 = |< LEFT_CTRLB LEFT_SHIFTF = |< LEFT_SHIFTB LEFT_ALTF = |< LEFT_ALTB LEFT_GUIF = |< LEFT_GUIB RIGHT_CTRLF = |< RIGHT_CTRLB RIGHT_SHIFTF = |< RIGHT_SHIFTB RIGHT_ALTF = |< RIGHT_ALTB RIGHT_GUIF = |< RIGHT_GUIB ' Keyboard modifier flag combinations: KEY_CTRLMOD = LEFT_CTRLF | RIGHT_CTRLF KEY_SHIFTMOD = LEFT_SHIFTF | RIGHT_SHIFTF KEY_ALTMOD = LEFT_ALTF | RIGHT_ALTF ' Keyboard buffer size: MOUSE_BUFFMASK = $0f ' Mouse data buffer KBD_BUFFMASK = $0f ' Keyboard key data buffer '------------------------------------------------------------------------------ ' #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 DM 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, test chip v1): %11000 = USB host, low-speed %11001 = USB host, full-speed %11010 = USB device, low-speed %11011 = USB device, full-speed USB smart pin modes (scheduled for test chip v2): 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 These modes require 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, pins 4 and 5, etc., can form USB pairs. They can be configured via WRPIN with identical D data of %1_110xx_0. Using D data of %0_110xx_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 the current FPGA, there are no built-in 1.5k and 15k resistors, which the final silicon smart pins will contain, so it is up to you to insert these yourself on the DP and DM lines. The upper (odd) pin is the DP 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 DM pin. This pin's IN is raised whenever a change of status occurs in the receiver, at which point a RDPIN 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 DP/DM 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, which is a 16-bit fraction of the system clock. 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. Then, set the pins' DIR bits. You are now ready to read the receiver status via RDPIN 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 DP and DM low 2 = output K - drive K state onto DP and DM (opposite) 3 = output J - drive J state onto DP and DM (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. Bits 31..8 of your data bytes are ignored, but by keeping those upper bits clear, WYPIN will be able to do the fastest transfer to the smart pin, which takes only 4 clocks. 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] - $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 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. } '------------------------------------------------------------------------------ ' 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) '------------------------------------------------------------------------------ _12Mbps = round((12_000_000.0 / _FCLKFREQ) * 65536.0) ' = 4369 NCO @180MHz, 9830 @80MHz _1_5Mbps = round((1_500_000.0 / _FCLKFREQ) * 65536.0) ' = 546 NCO @180MHz, 1229 @80Mhz USB_V1HMODE_FS = %1_11001_0 + 1 << 16 USB_V1DMODE_FS = %1_11011_0 + 1 << 16 USB_H_FS_NCO = %11 << 14 + _12Mbps USB_D_FS_NCO = %01 << 14 + _12Mbps USB_V1HMODE_LS = %1_11000_0 + 1 << 16 USB_V1DMODE_LS = %1_11010_0 + 1 << 16 USB_H_LS_NCO = %10 << 14 + _1_5Mbps USB_D_LS_NCO = %00 << 14 + _1_5Mbps USB_V2MODE = %1_11011_0 + 1 << 16 ' In P2 silicon v2 all USB smart pin modes are consolidated to %11011. '------------------------------------------------------------------------------ ' Time units converted to clock cycles for the given P2 Mhz clock speed _1ms = round(_FCLKFREQ / 1_000.0) _1us = round(float(_1ms) / 1_000.0) _100ns = round(float(_1us) / 1_000.0 * 100.0) LSBTns = 667.0 ' Low-Speed bit period, in nanoseconds FSBTns = 83.54 ' Full-Speed bit period, in nanoseconds _1LSBT = round(_FCLKFREQ / 1_000_000_000.0 * LSBTns)' = 53 clocks @ 80Mhz, 80 clocks @ 120Mhz _1FSBT = round(_FCLKFREQ / 1_000_000_000.0 * FSBTns)' = 7 clocks @ 80Mhz, 10 clocks @ 120Mhz ' Time delays and intervals ' Useful USB constants and wait intervals: CONNECT_WAIT = _1ms * 250 ' Connect/disconnect verification delay RESET_HOLD = _1ms * 15 ' Host reset state hold time 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) XFER_WAIT = _1ms * 5 ' Transfer retry wait timespan ' Standard Device request maximum timeout periods: 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 '------------------------------------------------------------------------------ ' Low-Speed inter-packet delay, in bit periods (Section 7.1.18). For the host, ' the range is a minimum of two bit periods and a maximum of 7.5 bit periods: '------------------------------------------------------------------------------ IP_DELAY_LS = round(4.0 * LSBTns * float(_1us) / 1000.0) ' Range @180MHz: 240 to 901 clocks (1.334us to 5.003us) IP_DELAY_FS = round(4.0 * FSBTns * float(_1us) / 1000.0) ' Range @180MHz: 30 to 112 clocks (0.166us to 0.623us) '------------------------------------------------------------------------------ ' End-to-end signal delay, in bit periods (Section 7.1.19), measured at the ' data pins of the device from the SE0-to-J transition at the end of the EOP. ' For example, when a device transmits the data for an IN transaction, it will ' wait at least 16 bit periods for the host handshake response, but no more ' than 18 bit periods: ' Low-Speed: 10.672us..12.006us (@80MHz: 853.8 to 960.5 clocks). ' Full-Speed: 1.238us..1.494us (@80MHz: 106.2 to 119.5 clocks). ' When the host transmits data e.g. an OUT transaction, it must wait at least ' 18 bit periods before it will timeout the response and start a new transaction. '------------------------------------------------------------------------------ TAT_WAIT_LS = round(22.0 * LSBTns * float(_1us) / 1000.0) TAT_WAIT_FS = round(28.0 * FSBTns * float(_1us) / 1000.0) '------------------------------------------------------------------------------ ' Interrupt service routine timespans for 1ms interval frame generation. ' Target resolution is 1.00ms +/- 0.0005ms. At full-speed, a numbered ' start-of-frame packet is issued. At low-speed, a single EOP is issued as a ' "keep-alive" strobe to prevent a device from entering suspend mode. '------------------------------------------------------------------------------ TXN_OK_LS = _1us * 667 - 6 ' All USB transactions must be started within this timespan NO_TXN_LS = _1us * 333 - 7 ' Low-speed needs ~33% bandwidth per max packet of 8 bytes TXN_OK_FS = _1us * 825 - 6 ' Full-speed needs ~10% bandwidth per max packet of 64 bytes NO_TXN_FS = _1us * 175 - 7 ' Non-USB related time intervals: PULSE_TIME = _1ms * 750 ' LED-blinking heartbeat interval ' PID tokens '------------------------------------------------------------------------------ ' 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 = |< J_IDLEB K_RESUMEF = |< K_RESUMEB SE0_RESETF = |< SE0_RESETB SE1_BADF = |< SE1_BADB SOPF = |< SOPB EOPF = |< EOPB BUS_ERRF = |< BUS_ERRB BYTE_TGLF = |< BYTE_TGLB ' USB CRC constants: USB5_POLY = %0_0101 >< 5 ' USB5 polynomial is reflected when calculating CRC USB5_RESIDUAL = %0_1100 >< 5 ' Expected CRC5 residual value when checking received data USB16_POLY = $8005 >< 16 ' USB16 polynomial is reflected when calculating CRC USB16_RESIDUAL = $800d >< 16 ' Expected CRC16 residual value when checking received data ' Host->class driver USB connect speed: #0, USB_SPEED_UNKNOWN, USB_SPEED_LOW, USB_SPEED_FULL ' Protocol error codes: #0, ERR_NONE, ERR_URX, ERR_SE1, ERR_PACKET, ERR_TAT, ERR_TXN_RETRY, ERR_XFER_RETRY ERR_NAK, ERR_ACK_RETRY, ERR_EMPTY_PKT, ERR_RX_CRC, ERR_DATAX_SYNC, ERR_CONFIG_FAIL, ERR_END ' Debug stuff: DBG_FEEDBEEF = $efbeedfe ' Handy byte sequence for hex search in .obj files DBG_C0DEDEAD = $addedec0 ' End of cog/lut code marker DBG_DADADEAD = $addedada ' End of 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, TXN_LIMITB[4], DWNSTRM_HUBB ' Host status bitflags. Unless otherwise noted, bit states are active high: IDLEF = |< IDLEB ' Set when USB in idle state CONNECTEDF = |< CONNECTEDB ' USB device connected LOW_SPEEDF = |< LOW_SPEEDB ' Low-speed device connected, clear if full-speed DATAx_TGLF = |< DATAx_TGLB ' Cleared if sending DATA0 packet, set if sending DATA1 packet TXN_LIMITF = |< TXN_LIMITB ' No-go area to prevent transactions from spanning frame boundary ' EOPF = |< EOPB ' Same bit position as the USB receiver RDPIN constant defined above ' BUS_ERRF = |< BUS_ERRB ' Same bit position as the USB receiver RDPIN constant defined above ' BYTE_TGLF = |< BYTE_TGLB ' Same bit position as the USB receiver RDPIN constant defined above DWNSTRM_HUBF = |< DWNSTRM_HUBB ' Not yet implemented ' LUT allocation: '------------------------------------------------------------------------------ HLUT_BASE = $068 HLUT_TOP = $1ff '------------------------------------------------------------------------------ ' 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 '------------------------------------------------------------------------------ ' Startup parameter count: START_PARM_COUNT = 4 '------------------------------------------------------------------------------ ' #endregion (USB host constants) ' #region (Spin VAR data and PUB/PRI functions) VAR long usbcog ' Paramter block start address passed to the USB cog via COGNEW parameter long mouseData long itemAddr long itemCount long dataVals[32] ' Contiguous cog/lut register/cell values ' Parameter block end '' PUB start(status) ' new silicon long[@usb_rpin_ls_val] := USB_V2MODE long[@usb_xpin_ls_val] := USB_H_LS_NCO long[@usb_rpin_fs_val] := USB_V2MODE long[@usb_xpin_fs_val] := USB_H_FS_NCO usbcog := cognew(@usb_host_start, @mouseData) + 1 if usbcog dirl_(USB_EVENT_REPO) ' DIR bit low puts smart pin in reset mode wrpin_(SP_REPO1_MODE, USB_EVENT_REPO) ' Set "long repository" mode to act as an event mailbox dirh_(USB_EVENT_REPO) ' Enable the event mailbox smart pin (will raise IN) byte[status][0] := usbcog ' Pack the USB port data into a single long byte[status][1] := USB_EVENT_REPO ' Long repository smart pin to poll for event notifications ' byte[status][2] := 0 ' Client must set <> 0 at kbd "ready" event, zero at disconnect event ' byte[status][3] := 0 ' Client must set <> 0 at mouse "ready" event, zero at disconnect event PUB mouse : data data := mouseData PUB key : data if kbd_tail <> kbd_head data := kbd_buffer[kbd_tail] kbd_tail := ++kbd_tail & KBD_BUFFMASK PUB getErrorCode return @usb_error_code ' Hub long with the error value PUB getVersion return @sz_usb_kbm_ver ' ASCIIZ version string pointer PUB getDebugInfo return itemAddr, dataVals ' #endregion (Spin VAR and functions) ' #region (USB host cog) DAT org 0 ' /* usb_host_start '------------------------------------------------------------------------------ ' The USB host cog. '------------------------------------------------------------------------------ usb_host_start mov htmp, #mouse_data_ptr ' Transfer the startup parameter values rep #4, #START_PARM_COUNT ' to their respective cog registers. altd htmp mov 0-0, ptra add htmp, #1 add ptra, #4 flth #HOST_ERROR_LED drvh #USB_PROTECT_ON ' Enable P2-ES 64006-ES Serial Host USB Protection ' FIXME: kludge to set a USB tx byte write ACKPIN<->WRPIN delay. mov htmp, ##_CLKFREQ cmp htmp, ##168_000_000 wcz if_be mov utx_tweak, #0 if_a mov utx_tweak, #20 mov htmp, ##@hlut_end - 4 ' Dealing with hub addresses, so convert byte sub htmp, ##@hlut_start ' addresses to the N - 1 longs required by SETQ2 shr htmp, #2 loc pb, #@hlut_start setq2 htmp rdlong 0, pb ' Copy hub code/data to lut call #hpulse_led mov ijmp1, #isr1_frame ' Set the USB 1ms frame handler ISR routine jmp #host_reset ' Initialize host and enter main processing loop ' */ ' */ usb_rpin_ls_val long USB_V1HMODE_LS ' USB_V2MODE for v2 silicon usb_xpin_ls_val long _1_5Mbps ' USB_H_LS_NCO for v2 silicon usb_rpin_fs_val long USB_V1HMODE_FS ' USB_V2MODE for v2 silicon usb_xpin_fs_val long _12Mbps ' USB_H_FS_NCO for v2 silicon ' /* 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 ##(|< 31), 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 wrfast ##(|< 31), 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, #SOPB wc ' FIXME: checking for EOP set should work when > 80MHz if_c 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 #3 if < 180MHz, wait #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 ' */ ' /* wait_txn_ok '------------------------------------------------------------------------------ ' Wait for a window within the 1ms frame boundary that will ensure that a ' transaction can complete without spanning the frame. '------------------------------------------------------------------------------ ' On entry: ' On exit: '------------------------------------------------------------------------------ wait_txn_ok mov htmp2, #0 .wait testb hstatus, #TXN_LIMITB wc if_c add htmp2, #1 if_c jmp #.wait ' In "no transaction" zone, so wait for the next frame cmp htmp2, #0 wz if_z ret mov hctwait, ip_delay jmp #poll_waitx ' In the "transaction OK" zone, so good to go ' */ ' /* 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, ##_1us * 250 ' 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, ##_1us * 33 ' 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 ' */ ' /* 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_frame testb hstatus, #TXN_LIMITB wc if_nc jmp #.no_txn_zone ' Timer "txn OK" expired, go set "no txn" timer addct1 hct1, txn_ok_zone ' Refresh the "txn OK zone" timespan testb hstatus, #LOW_SPEEDB wc if_nc jmp #.fullspeed mov utx, #OUT_EOP ' Timer "no txn" expired, so it's end-of-frame call #utx_byte ' EOP is the low-speed keep-alive strobe mov isr_tmp, ip_delay ' Normal inter-packet delay works when low-speed jmp #.wait .fullspeed mov utx, #OUT_SOP call #utx_byte ' Send sync 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 mov utx, #PID_SOF call #utx_byte ' Send token PID byte 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 add frame, #1 ' Calculate number for next frame and frame, ##$7ff mov isr_tmp, ##IP_DELAY_FS ' Use normal inter-packet delay when full-speed .wait rqpin utx, #DM testb utx, #SOPB wc if_c jmp #.wait waitx isr_tmp ' Make sure bus is idle before bitl hstatus, #TXN_LIMITB ' Bus ready for traffic reti1 .no_txn_zone addct1 hct1, no_txn_zone ' Refresh the "no txn zone" timespan bith hstatus, #TXN_LIMITB reti1 ' */ ' /* dev_reset '------------------------------------------------------------------------------ ' A device connection was detected. 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 ' These subroutines must restore the caller C flag if_nc call #set_speed_full ' state on return if C is used. reset setint1 #0 ' Don't want frame interrupt at reset wypin #OUT_SE0, #DM ' Assert bus reset waitx ##RESET_HOLD ' Spec is >= 10ms wypin #OUT_IDLE, #DM bitl hstatus, #TXN_LIMITB ' Reset the frame txn OK/!OK flag mov frame, #0 ' Reset the frame count getct hct1 ' Reset the frame timer addct1 hct1, ##TXN_OK_ZONE setint1 #1 ' Set ISR event trigger to CT1-equals-CT mov hctwait, ##_1ms * 21 call #poll_waitx ' Allow reset recovery time (Section 9.2.6.2) ret ' */ ' /* 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: Restore caller C flag state if C flag used! '------------------------------------------------------------------------------ set_speed_full mov txn_ok_zone, ##TXN_OK_FS ' Set FS bandwidth usage limits mov no_txn_zone, ##NO_TXN_FS 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 ' */ ' /* set_speed_low '------------------------------------------------------------------------------ ' When a low-speed device connects, the D-/D+ signalling is inverted. If there ' is a downstream hub connected (not yet implemented), the baud generator ' remains set at the full-speed rate, but signalling 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: Restore caller C flag state if C flag used! '------------------------------------------------------------------------------ set_speed_low test hstatus, #DWNSTRM_HUBF wz ' If no downstream hub connected, set low-speed baudrate if_z dirl #DP if_z dirl #DM wrpin usb_rpin_ls_val, #DP ' Low-speed signalling is always used wrpin usb_rpin_ls_val, #DM if_z wxpin usb_xpin_ls_val, #DM ' Set 1.5Mbs baudrate if no downstream hub { wrpin ##USB_V2MODE, #DP ' Low-speed signalling is always used wrpin ##USB_V2MODE, #DM if_z wxpin ##USB_H_LS_NCO, #DM ' Host mode and 1.5Mbs baudrate if no downstream hub } if_z dirh #DP if_z dirh #DM mov txn_ok_zone, ##TXN_OK_LS ' Set low-speed bandwidth usage limits mov no_txn_zone, ##NO_TXN_LS 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 _ret_ bith hstatus, #LOW_SPEEDB ' DM pulled high, so it's a Low-Speed device ' */ ' /* 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 loc ptra, #@usb_error_code wrlong retval, ptra ' Post the USB operation result value wxpin #USB_ERROR, #USB_EVENT_REPO ' Signal the client an error has occurred mov hrep, #5 .spin drvnot #HOST_ERROR_LED mov hctwait, ##PULSE_TIME >> 3 call #poll_waitx rqpin urx, #DM testb urx, #SE0_RESETB wc if_nc jmp #.spin djnz hrep, #.spin 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, ##KBD_POLL_INTERVAL ' Set the timer for next poll interval if_nz addct3 hct3, ##_1ms * 4 ' Try to keep 4ms timespan between them if_nz mov poll_target, #poll_mouse jmp #\@hget_kbd_in_report ' Post IN txn and return to caller poll_mouse getct hct3 cmp hkbd_ep_addr, #0 wz if_z addct3 hct3, ##MOUSE_POLL_INTERVAL ' Mouse is the only connected device if_nz addct3 hct3, ##_1ms * 4 if_nz mov poll_target, #poll_kbd jmp #\@hget_mouse_in_report ' Post IN txn and return to caller ' */ ' /* Host registers ' Pointers to this spin object's VAR block data (assigned at cog startup) mouse_data_ptr long 0 ' long mouseData item_addr_ptr long 0 ' long itemAddress item_count_ptr long 0 ' long itemCount data_vals_ptr long 0 ' long dataVals ' Scratch registers htmp long 0 ' Scratch registers whocse context remains within the same code block htmp1 long 0 htmp2 long 0 hrep long 0 ' Repeat count hsave0 long 0 ' Subroutine parameter saves hsave1 long 0 hsave2 long 0 utx_tweak long 0 ' When sysclock speed above ~120MHz, need some fairy dust for USB tx isr_tmp long 0 pkt_tmp long 0 ' Tmp storage for routines that deal with datax packets hr0 long 0 ' Multi-purpose registers hr1 long 0 hr2 long 0 hr3 long 0 hpar1 long 0 ' Routine entry/exit parameters hpar2 long 0 hpar3 long 0 hct1 long 0 ' Timer for the 1ms frame generator interrupt service routine hct2 long 0 ' Function response bus turn-around timer hct3 long 0 ' Keyboard/mouse poll timer ' 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 txn_ok_zone long 0 ' Timespan that packets can be put on the bus no_txn_zone long 0 ' Timespan in which packets must wait for bus to become available 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 ' Written by ISR only. 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 hevent long 0 ' Command/request eventID posted by the driver cog 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 last_mouse_data long 0 ' Last mouse data passed to the client hmouse_poll_cnt long 0 ' The following registers must match the layout of the "public" keyboard ' interface that is in hub RAM, starting at the kbd_count label. hkbd_head long 0 ' Keyboard data buffer head hkbd_ep_addr long 0 ' Keyboard interface endpoint address hkbd_report long 0 ' Address of report descriptor data for the keyboard interface 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 ' kbd_scancode hkbd_modkeys long 0 ' kbd_modkeys hkbd_keypress long 0 ' kbd_keypress hkbd_ledstates long 0 ' kbd_ledstates hreg_init_end fit $1f4 ' Not using standard registers $1f0..$1f3 ' */ ' #endregion (USB host cog) ' #region (Host LUT execution) org $200 hlut_start ' /* 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 ' Hub start address of GetDeviceDescriptor SETUP struct wrword #$40, ptra[wLength] ' Request IN data stage max of 64 bytes will test actual < requested logic loc pb, #@dev_desc_buff ' Start address of DeviceDescriptor struct for IN data call #control_read ' Execute GetDeviceDescriptor() cmp retval, #PID_ACK wz if_z jmp #.get_dev_desc mov hctwait, ##_1ms * 500 ' 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 ' 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 ' Hub start address of SetAddress SETUP struct 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, ##_1ms * 8 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 ' Repeat SETUP for GetDeviceDescriptor() loc pb, #@dev_desc_buff ' Start address of DeviceDescriptor struct has exact descriptor length 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 ' Do the same with the control max packet size rdbyte hctrl_max_pkt, ptra[DEV_bMaxPktSize0] mov hctwait, ##_1us * 500 call #poll_waitx loc ptra, #@get_config_desc ' Hub start address of GetConfigurationDescriptor SETUP struct wrword #$ff, ptra[wLength] ' Maximum DATAx bytes for receive to the SETUP struct loc pb, #@con_desc_buff ' Hub start address of ConfigurationDescriptor structure call #control_read ' Execute GetConfigurationDescriptor() cmp retval, #PID_ACK wz if_nz ret loc ptra, #\@con_desc_buff ' Check the config descriptor struct for expected data 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 jmp #\@hparse_con_desc ' */ ' /* 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 ' Copy DATAx in rx buffer to dest struct 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 ' Copy the rx buffer 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 ' */ ' /* host_reset host_reset ' drvh #KBM_ACTIVE_LED setint1 #0 dirl #DP ' Put smart pins into reset dirl #DM wrpin usb_rpin_fs_val, #DP ' The host is also the root hub, so full-speed is its native speed wrpin usb_rpin_fs_val, #DM wxpin usb_xpin_fs_val, #DM ' Default to Full-Speed { wrpin ##USB_V2MODE, #DP ' Low-speed signalling is always used wrpin ##USB_V2MODE, #DM wxpin ##USB_H_FS_NCO, #DM ' Host mode and default to 12Mbs baudrate } dirh #DM ' Crank them smart pins up dirh #DP waitx ##_1us * 10 wypin ##OUT_IDLE, #DM mov pa, #hreg_init_start ' Reset all host common registers .regloop altd pa mov 0-0, #0 add pa, #1 cmp pa, #hreg_init_end wz if_nz jmp #.regloop loc ptra, #\@kbd_tail ' Reset the keyboard tail/head and buffer in hub mov hrep, #KBD_BUFFMASK + 3 .kbdloop wrlong #0, ptra++ djnz hrep, #.kbdloop mov poll_target, ##hpulse_led call poll_target ' 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 pollct3 wc if_c call poll_target ' Toggle the host wait for connect USER_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, ##_1ms * 100 ' 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 #disconnected ' Back to connect detection loop .se1 'SE1 is a fatal error condition waitx ##_1ms * 100 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 mov hctwait, ##_1ms * 100 call #poll_waitx ' Total of 100ms debounce interval (Section 7.1.7.3) rqpin urx, #DM and urx, #J_IDLEF | K_RESUMEF wcz if_z jmp #disconnected ' DP and DM low if_e jmp #.se1_test ' DP and DM high connected bith hstatus, #CONNECTEDB ' Device plugged in drvh #HOST_ACTIVE_LED call #on_connect ' Initial device configuration cmp retval, #PID_ACK wz ' Anything other than ACK means the device is unusable if_nz jmp #disconnected ' Fall through to idle/processing loop ' vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv hidle rqpin urx, #DM testb urx, #SE0_RESETB wc if_c jmp #.se0_test pollct3 wc if_c call poll_target ' Call the current poll/wait subroutine 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 setint1 #0 ' Turn off frame strobe interrupt wypin ##OUT_IDLE, #DM ' Float USB drvh #HOST_ACTIVE_LED wxpin #DEV_DISCONNECT, #USB_EVENT_REPO ' Notify client of disconnect jmp #host_reset ' Device disconnected ' */ ' /* hpulse_led '------------------------------------------------------------------------------ ' Toggle a user LED when the host is the connect wait loop or the main USB ' processing loop. '------------------------------------------------------------------------------ hpulse_led getct hct3 addct3 hct3, ##PULSE_TIME _ret_ drvnot #HOST_ACTIVE_LED hlut_end fit $400 ' */ ' #endregion (Host LUT execution) ' #region (USB hub execution) orgh ' /* cog_lut_copy '------------------------------------------------------------------------------ ' Copy the contents of N cog registers or lut cells to a buffer in hub RAM. The ' caller must ensure that the destination buffer is sized appropriately. For a ' cog region copy the address range is $000..$1ff and for lut $200..$3ff. '------------------------------------------------------------------------------ ' On entry: ' hpar1 - address of the first register/cell to copy. ' hpar2 - address of the destination buffer/array. ' hrep - count of registers/cells to copy. ' On exit: ' hpar1, hrep - unchanged. ' htmp, htmp1, htmp2 - trashed. '------------------------------------------------------------------------------ cog_lut_copy mov htmp1, hpar1 ' Keep base address unchanged mov htmp2, hrep cmp hpar1, #$1ff wcz ' Cog space or lut? if_a sub htmp1, ##$200 .loop cmp hpar1, #$1ff wcz if_be alts htmp1 ' Set next S field to cog address pointer if_be mov htmp, 0-0 ' Get register content if_a rdlut htmp, htmp1 ' It's lut space, so read the cell content wrlong htmp, hpar2 ' Write register/cell content to the buffer/array add htmp1, #1 add hpar2, #4 djnz htmp2, #.loop wrlong hpar1, item_addr_ptr ' Write the buffer/array start address and _ret_ wrlong hrep, item_count_ptr ' count to their respective VAR locations ' */ ' /* 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 call #poll_waitx ' 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 loc ptra, #\@kbd_intf_num 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 loc ptra, #\@mouse_intf_num 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 loc ptra, #\@kbd_intf_num rdbyte htmp, ptra 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 loc ptra, #\@kbd_intf_num rdbyte hpar2, ptra call #hset_idle cmp retval, #PID_ACK wz if_nz mov hkbd_ep_addr, #0 if_nz jmp #.mouse_config mov hctwait, ##_1ms * 2 call #poll_waitx call #hset_kbdled_report cmp retval, #PID_ACK wz if_nz mov hkbd_ep_addr, #0 if_nz jmp #.mouse_config loc ptra, #\@kbd_last_key wrbyte #KEY_NO_KEY, ptra 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 poll_target, #poll_kbd mov htmp2, #KB_READY ' Keyboard interface configured getct hct3 addct3 hct3, ##KBD_POLL_INTERVAL ' Wait an interval before the first IN poll .mouse_config cmp hmouse_ep_addr, #0 wz if_z jmp #.notify_client ' Notify the client that a keyboard interface was configured loc ptra, #\@mouse_intf_num rdbyte htmp, ptra 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 loc ptra, #\@mouse_intf_num rdbyte hpar2, ptra 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 if_z getct hct3 if_z addct3 hct3, ##MOUSE_POLL_INTERVAL ' Set first poll interval .notify_client _ret_ wxpin htmp2, #USB_EVENT_REPO ' Notify the client keyboard and/or mouse configured ' */ ' /* init_kdbm_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 loc ptra, #\@kbd_mouse_start loc pa, #\@kbd_mouse_end .loop wrbyte #0, ptra++ cmp ptra, pa wz if_nz jmp #.loop loc ptra, #\@kbd_next_datax wrbyte #PID_DATA0, ptra ' Reset interrupt IN datax sequence PIDs loc ptra, #\@mouse_next_datax _ret_ wrbyte #PID_DATA0, ptra { wxpin #RD_KBM_DATA, #USB_EVENT_REPO mov hctwait, ##_1ms * 500 jmp #poll_waitx } ' */ ' /* 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, ##_1ms * 2 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 loc ptra, #\@kbd_intf_num rdbyte htmp, ptra 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] loc pb, #\@kbd_led_states ' 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 loc ptra, #\@kbd_in_max_pkt rdbyte htmp, ptra++ ' Always ask for max report size rdbyte hpar3, ptra setword hpar3, htmp, #1 ' Max IN packet size to expect loc ptra, #\@kbd_cur_report mov hpar2, ptra 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 loc ptra, #\@kbd_last_key rdbyte hpar1, ptra ' 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 cmp hpar3, #0 wz if_z ret ' Ignore an empty DATAx packet drvnot #HOST_ACTIVE_LED ' Show keypress activity on the feedback LED loc ptra, #\@kbd_next_datax 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 loc ptra, #\@kbd_led_states rdbyte htmp, ptra cmp hkbd_ledstates, htmp wz if_z ret ' No toggle key indicator changes, so we're done wrbyte hkbd_ledstates, ptra ' 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 loc ptrb, #\@kbd_cur_report + 2 ' Start of scancode array 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 loc ptra, #\@kbd_prev_report ' Keyboard rollover overflow or other error loc pb, #\@kbd_cur_report 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 loc ptra, #\@kbd_max_index rdbyte hr0, ptra mov hr3, hr0 loc ptra, #\@kbd_cur_report add hr3, ptra ' 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 loc ptra, #\@kbd_last_key rdbyte htmp, ptra 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, ptra .end loc ptra, #\@kbd_cur_report ' Done processing current IN report loc pb, #\@kbd_prev_report 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 loc ptra, #\@kbd_prev_report + 2 loc pb, #\@kbd_prev_report 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_NUMLK wz if_z bitnot htmp, #LED_NUMLKB cmp htmp, hkbd_ledstates wz if_nz mov hkbd_ledstates, htmp ' 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 loc ptra, #\@kbd_cur_report rdbyte hkbd_modkeys, ptra ' 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, #KEY_SHIFTMOD 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, #KEY_SHIFTMOD 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 loc pa, #\@kbd_buffer loc pb, #\@kbd_head rdlong htmp, pb shl htmp, #2 add pa, htmp wrlong hkbd_keypress, pa ' New data to buffer shr htmp, #2 incmod htmp, #KBD_BUFFMASK ' Advance head _ret_ wrlong htmp, pb ' and save it ' */ ' /* 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 loc ptra, #\@mouse_in_max_pkt 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_z jmp #.nak 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 if_z jmp #.nak mov hmouse_poll_cnt, #0 drvnot #HOST_ACTIVE_LED ' Show mouse activity on the feedback LED loc ptra, #\@mouse_next_datax rdbyte hpar1, ptra cmp hpar1, #PID_DATA0 wz if_z mov hpar1, #PID_DATA1 ' ACK, so toggle DATAx if_nz mov hpar1, #PID_DATA0 wrbyte hpar1, ptra .data_out loc ptra, #\@mouse_cur_report ' 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 last_mouse_data, hr0 wz ' Some devices will occasionally "leak" stale data if_z ret ' so skip notifying the client if data unchanged mov last_mouse_data, hr0 wrlong hr0, mouse_data_ptr _ret_ wxpin #M_DATA, #USB_EVENT_REPO ' */ ' #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 '------------------------------------------------------------------------------ ' Class driver data structure for boot protocol keyboard. ' FIXME: Device endpoint/address and DATAx toggle state probably should be ' maintained by the host and not the class driver. '------------------------------------------------------------------------------ kbd_mouse_start kbd_intf_num byte 0 ' Keyboard interface index (zero-based) kbd_interval byte 0 ' Minimum poll interval, in milliseconds kbd_in_max_pkt byte 0 ' Boot protocol IN report max is 8 bytes kbd_next_datax byte 0 ' DATAx PID expected on the next IN transaction kbd_max_index byte 0 ' Actual length of the current IN interrupt packet kbd_led_states byte 0 ' Output report for keyboard toggle key indicator LED states kbd_cur_report byte 0[KBD_IN_RPT_LEN] ' Compare buffers for key down/up detection kbd_prev_report byte 0[KBD_IN_RPT_LEN] kbd_last_key byte 0 ' Used for key auto-repeat ' Class driver data structure for boot protocol mouse: mouse_intf_num byte 0 ' Mouse interface index (zero based) mouse_interval byte 0 ' Minimum poll interval, in milliseconds mouse_in_max_pkt byte 0 ' Boot protocol IN report min of three bytes, max of 8 mouse_next_datax byte 0 ' DATAx PID expected on the next IN transaction mouse_cur_report byte 0[MOUSE_RPT_LEN] ' Max mouse IN data buffer mouse_prev_report byte 0[MOUSE_RPT_LEN] kbd_mouse_end ' End of keyboard/mouse data block '------------------------------------------------------------------------------ ' 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.05", 0 ' Current error state of the USB, posted by the USB_ERROR event. The error code ' will be one of the constants in the "Protocol error codes" enumeration. usb_error_code long ERR_NONE '------------------------------------------------------------------------------ ' Keyboard FIFO buffer. '------------------------------------------------------------------------------ kbd_tail long 0 ' Client read/write, USB read-only kbd_head long 0 ' USB read/write, client read-only kbd_buffer long -1[KBD_BUFFMASK + 1] '------------------------------------------------------------------------------ ' #region Data buffers and structures ' /* Keyboard scancode table kbd_table {$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, $087F, $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 ' */ ' #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. | +----------------------------------------------------------------------------------------------------------------------+ }