;USB low-level controller routines include "ti83plus.inc" include "equates.inc" PUBLIC DirectUSB_SendControlData,DirectUSB_PeripheralInit,DirectUSB_WaitForStableLines,DirectUSB_HostInit PUBLIC DirectUSB_SendControlDataOut,DirectUSB_SetupEndpoint,DirectUSB_Suspend,DirectUSB_EnablePower PUBLIC DecrementCounter,DirectUSB_EnableHostMode,DirectUSB_Init,DirectUSB_Kill,DirectUSB_ReceiveControlRequest PUBLIC IsPort86hError,DirectUSB_SendControlRequest,IsControlNAKOrStall EXTERN IPutC,DispHexA,DispHexHL EXTERN DirectUSB_GetFreePipe,DirectUSB_SetPipeEndpoint,WaitTimer20ms,WaitTimer40ms,WaitTimerB10ms DirectUSB_SetupEndpoint: ;Set up endpoint B, type H, max packet size DE, interval C ;IX => device information structure ;Disable USB interrupts ld a,9 call IPutC call DirectUSB_GetFreePipe ret c xor a out (5Bh),a ld a,l call DispHexA out (8Eh),a ;Set the pipe number in our endpoint array inc ix ; inc ix ld a,b res 7,a call DirectUSB_SetPipeEndpoint push af $$: inc ix dec a jr nz,$B pop af ld (ix+0),l srl d rr e srl d rr e srl d rr e sla h sla h sla h sla h or h call DispHexA bit 7,b jr z,setupOutgoing out (9Ah),a ld a,e call DispHexA out (93h),a ld a,90h call DispHexA out (94h),a xor a out (95h),a ld a,c call DispHexA out (9Bh),a ld a,0Eh out (89h),a ld a,1 out (5Bh),a ret setupOutgoing: out (98h),a ld a,e call DispHexA out (90h),a ld a,0FFh out (87h),a xor a out (92h),a ld a,1 out (5Bh),a ret ;Receives an incoming control request, if in fact we have one to receive. DirectUSB_ReceiveControlRequest: xor a out (8Eh),a in a,(91h) ld b,a and 4 jr z,$F ld a,b and 0FBh jr outPort91h $$: ld a,b and 10h jr z,$F ld a,b or 80h out (91h),a $$: ld a,b and 1 jr nz,$F ld a,40h outPort91h: out (91h),a ret $$: ld b,8 ld hl,controlRequestBuffer call DirectUSB_ReceiveControlBytes xor a ret ;This receives an incoming control request, and either finishes it or starts data output. ;The handler for the specific request should finish outputting if necessary. DirectUSB_ReceiveControlBytes: in a,(0A0h) ld (hl),a inc hl djnz DirectUSB_ReceiveControlBytes ld a,(controlRequestBuffer) and 80h jr z,$F xor a out (8Eh),a ld a,40h out (91h),a ret $$: xor a out (8Eh),a in a,(91h) ld a,48h out (91h),a in a,(91h) ret ;This sends an 8-byte control request at HL and receives BC bytes back in response to DE. DirectUSB_SendControlData: DISAVE call DirectUSB_SendControlDataSafe EIRESTORE ret DirectUSB_SendControlDataSafe: ld a,b or c jr nz,$F push bc call DirectUSB_Send8ControlBytes pop bc ld a,0Ah call DirectUSB_SendControlCmd jr c,DirectUSB_SendControlDataDone ld a,60h call DirectUSB_SendControlCmd jr DirectUSB_SendControlDataDone $$: push bc call DirectUSB_Send8ControlBytes pop bc ld a,0Ah call DirectUSB_SendControlCmd jr c,DirectUSB_SendControlDataDone ex de,hl ld de,(bMaxPacketSize0) DirectUSB_SendControlDataLoop: push bc ld a,b or a jr nz,dusb_sd1 ld d,c ld a,e cp c jr nc,$F dusb_sd1: ld d,e $$: pop bc ld a,20h call DirectUSB_SendControlCmd jr c,DirectUSB_SendControlDataDone $$: in a,(0A0h) ld (hl),a inc hl dec bc dec d jr nz,$B ld a,b or c jr nz,DirectUSB_SendControlDataLoop ld a,42h call DirectUSB_SendControlCmd jr DirectUSB_SendControlDataDone ;This sends an 8-byte control request at HL with BC bytes of data at DE. DirectUSB_SendControlDataOut: DISAVE call DirectUSB_SendControlDataOutSafe EIRESTORE ret DirectUSB_SendControlDataOutSafe: push bc call DirectUSB_Send8ControlBytes ld a,0Ah call DirectUSB_SendControlCmd pop bc jr c,DirectUSB_SendControlDataDone ex de,hl ld de,(bMaxPacketSize0) ;Send BC bytes, E bytes at a time DirectUSB_SendControlDataOutLoop: push bc ld a,b or a jr nz,dusb_sddol1 ;over 256 bytes, definitely send bMaxPacketSize0 ld d,c ld a,e cp c jr nc,$F dusb_sddol1: ld d,e $$: pop bc $$: ld a,(hl) out (0A0h),a inc hl dec bc dec d jr nz,$B ld a,02h call DirectUSB_SendControlCmd jr c,DirectUSB_SendControlDataDone ld a,b or c jr nz,DirectUSB_SendControlDataOutLoop ld a,60h call DirectUSB_SendControlCmd DirectUSB_SendControlDataDone: ret DirectUSB_SendControlCmd: push af xor a out (8Eh),a pop af out (91h),a call DirectUSB_WaitControlSuccess ret c CIfNAKOrStall: call IsControlNAKOrStall scf ret nz or a ret DirectUSB_WaitControlSuccess: push de ld de,0FFFFh $$: ld a,d or e scf jr z,$F dec de in a,(82h) bit 0,a jr z,$B or a $$: pop de ret DirectUSB_Send8ControlBytes: ld a,8 DirectUSB_SendAControlBytes: ld b,a DirectUSB_SendBControlBytes: xor a out (8Eh),a $$: ld a,(hl) out (0A0h),a inc hl djnz $B ret ;This waits for port 4Dh (line state) to be consistent and make sense, and sets the interrupt mask appropriately. DirectUSB_WaitForStableLines: in a,(4Dh) bit 5,a jr nz,miniBorNoCableConnected ;An A cable is connected, so wait ld de,0FFFFh $$: call DecrementCounter jp z,DirectUSB_Suspend ;error in a,(4Dh) bit 7,a jr z,$B in a,(4Dh) bit 0,a jr z,$B ld a,22h out (57h),a ;enable A cable unplugged event (and mysterious bit 1 event (OTG?)) scf ret miniBorNoCableConnected: ;We either have a mini-B cable connected or not, which is it? in a,(4Dh) bit 6,a ld a,93h jr nz,SetInterruptMask ;B cable is connected ;No cable is connected, disable the upper ports and wait for a cable to be connected xor a out (4Ch),a ld a,50h ;enable A and B plugged-in events SetInterruptMask: out (57h),a scf ret ;This initializes USB, figuring out whether we're host or peripheral (apparently) -- falls through to DirectUSB_HostInit. DirectUSB_Init: in a,(4Dh) bit 5,a jr nz,DirectUSB_PeripheralInit ;This initializes the connected peripheral. DirectUSB_HostInit: in a,(4Ch) cp 5Ah ret z cp 1Ah ret z in a,(4Dh) bit 5,a jr z,$F ;We're initializing as host even though a mini-B cable is plugged in (OTG), so enable the A plug-in event (I guess...) ld a,10h out (57h),a $$: xor a out (4Ch),a ;NOTE: Do we really want to do this? ld a,1 out (5Bh),a xor a in a,(4Ch) ;Enable USB port power ld a,2 out (54h),a ld a,20h out (4Ah),a call DirectUSB_EnablePower ;Wait on the controller to start up ld de,0FFFFh dusb_hi1: call DecrementCounter jp z,DirectUSB_Suspend in a,(4Ch) bit 0,a scf ccf ret nz ;apparently this means we're already set up cp 12h jr z,$F cp 52h jr nz,dusb_hi1 $$: call DirectUSB_EnableHostMode ;Enable all pipes for output ld a,0FFh out (87h),a xor a out (92h),a in a,(87h) ;Enable most pipes for input (control pipe is special) ld a,0Eh out (89h),a ;I have no idea what port 8Bh does ld a,0A1h ;0F7h ;0A1h out (8Bh),a in a,(86h) ;this theoretically clears out any miscellaneous errors ;Wait on bit 6, port 0x81 to set (no idea what it is) ld de,0FFFFh $$: call DecrementCounter jp z,DirectUSB_Suspend in a,(81h) and 40h jr z,$B ;If the "host bit" isn't set, then do it all over again in a,(8Fh) bit 2,a jr z,dusb_hi1 call DirectUSB_WaitForFrameCounterStart ret c xor a out (5Bh),a jr DirectUSB_ClearAddress DirectUSB_WaitForFrameCounterStart: ld b,5 dusb_waitframe_loop: ld de,0FFFFh $$: call DecrementCounter jr z,$F in a,(8Ch) or a jr z,$B or a ret $$: djnz dusb_waitframe_loop jr DirectUSB_Suspend ;This initializes the calculator as a USB peripheral connected to a host. DirectUSB_PeripheralInit: ld a,80h out (57h),a ;enable B cable unplugged event xor a out (4Ch),a xor a in a,(4Ch) ld a,2 out (54h),a ld a,20h out (4Ah),a call DirectUSB_EnablePower ld a,8 out (4Ch),a ld de,0FFFFh dusb_pi1: call DecrementCounter scf ret z in a,(4Ch) cp 1Ah jr z,$F cp 5Ah jr nz,dusb_pi1 $$: ld a,0FFh out (87h),a xor a out (92h),a in a,(87h) ld a,0Eh out (89h),a ld a,21h out (8Bh),a WaitForFrameCounterStart: ld b,5 waitFrameCounterLoop; ld de,0FFFFh $$: call DecrementCounter jr z,waitFrameError in a,(8Ch) or a jr z,$B or a ret waitFrameError: djnz waitFrameCounterLoop call DirectUSB_Suspend scf ret ;This sets the device's address to the default (0x00). DirectUSB_ClearAddress: ld a,8 out (81h),a in a,(81h) call WaitTimer20ms xor a out (81h),a call WaitTimer40ms xor a out (80h),a ret ;This checks for a NAK or Stall condition on the control endpoint. IsControlNAKOrStall: xor a out (8Eh),a in a,(91h) bit 4,a ret nz bit 2,a ret ;This sends an 8-byte control request at controlRequestBuffer. DirectUSB_SendControlRequest: ld hl,9C29h ld b,8 $$: ld a,(hl) out (0A0h),a inc hl djnz $B xor a out (8Eh),a ld a,0Ah out (91h),a or a ret ;I think this does some magic to set the "host bit", as TI calls it (bit 2 of port 8Fh), regardless of what cable is connected. DirectUSB_EnableHostMode: in a,(4Dh) bit 5,a jr nz,DirectUSB_EnableHostModeFromPeripheral call DirectUSB_IsNewController jr z,DirectUSB_Enable1 ;Apparently we should do it differently on this hardware ;Wait for Vbus to not be high ld de,0FFFFh $$: call DecrementCounter jr z,DirectUSB_Suspend in a,(4Dh) bit 6,a ; jr nz,$B call WaitTimer40ms ;Enable power to the upper I/O ports, I suppose in a,(3Ah) and 0FDh out (3Ah),a in a,(39h) or 2 out (39h),a ld a,8 out (4Ch),a ld a,1 out (8Fh),a ;Now wait for Vbus to be high ld de,0FFFFh $$: call DecrementCounter jr z,DirectUSB_Suspend in a,(4Dh) bit 6,a jr z,$B ;Who knows... in a,(39h) and 0FDh out (39h),a ret DirectUSB_Enable1: ;Enable power to the upper I/O ports, I suppose ld a,8 out (4Ch),a ;Wait for bit 5 of port 0x81 to go low, for whatever reason ld de,0FFFFh $$: call DecrementCounter jp z,DirectUSB_Suspend in a,(81h) bit 5,a jr nz,$B ld a,1 out (8Fh),a jr WaitTimer20ms DirectUSB_EnableHostModeFromPeripheral: call DirectUSB_EnableHostModeFromPeripheral_Do ret c ;Enable B-disconnect event ld a,90h out (57h),a ret DirectUSB_EnableHostModeFromPeripheral_Do: in a,(4Dh) bit 5,a jr z,DirectUSB_EnableHostModeFromPeripheral_Done call DirectUSB_EnablePowerFromPeripheral_Do jr c,DirectUSB_Suspend ;Wait a while for some reason xor a out (5Bh),a out (8Bh),a ld b,50 call WaitTimerB10ms ld a,3 out (8Fh),a in a,(8Fh) in a,(4Ch) ld a,0F7h out (8Bh),a ;Wait for the other device to reconnect ld de,0FFFFh $$: call DecrementCounter jr z,DirectUSB_Suspend in a,(86h) bit 4,a jr z,$B call WaitTimer20ms in a,(81h) and 0F7h out (81h),a call DirectUSB_ClearAddress DirectUSB_EnableHostModeFromPeripheral_Done: in a,(8Fh) xor a ret DirectUSB_EnablePowerFromPeripheral_Do: call DirectUSB_IsNewController jr z,DirectUSB_Enable1 ;Wait for Vbus to not be high ld de,0FFFFh $$: call DecrementCounter jr z,DirectUSB_Suspend in a,(4Dh) bit 6,a jr nz,$B ;Uh...I have absolutely no freaking idea... ld a,27h out (50h),a in a,(3Ah) and 0FCh or 4 out (3Ah),a in a,(39h) or 7 out (39h),a in a,(4Fh) and 0BFh or 88h out (4Fh),a call WaitTimer20ms in a,(4Fh) and 37h out (4Fh),a in a,(3Ah) and 0F8h out (3Ah),a in a,(39h) or 7 out (39h),a ;Enable power to the upper I/O ports, I suppose ld a,8 out (4Ch),a ld a,1 out (8Fh),a ld b,2 dusb_enablepowerp_loop: ld de,0FFFFh $$: call DecrementCounter jr z,$F in a,(4Dh) bit 6,a jr z,$B jr dusb_enablepowerp_done $$: djnz dusb_enablepowerp_loop scf ret dusb_enablepowerp_done: in a,(39h) and 0F8h out (39h),a or a ret ;This appears to actually supply power to the port (and/or the upper I/O ports), used in both host and peripheral mode. DirectUSB_EnablePower: xor a out (4Bh),a call DirectUSB_IsNewController jr z,$F ld a,20h out (4Bh),a $$: xor a out (54h),a ld b,1 call WaitTimerB10ms call DirectUSB_IsNewController jr z,$F ld a,44h out (54h),a $$: ld a,0C4h out (54h),a ret ;We have no idea what this signifies. DirectUSB_IsNewController: in a,(3Ah) bit 3,a ret ;The only difference between this and suspending is the write of zero to port 4Ch. Probably relevant somehow. DirectUSB_Kill: xor a out (5Bh),a call DirectUSB_DoSuspend xor a out (4Ch),a jp DirectUSB_WaitForStableLines ;Suspending is only a best guess at what this actually does. DirectUSB_Suspend: xor a out (5Bh),a call DirectUSB_DoSuspend jp DirectUSB_WaitForStableLines DirectUSB_DoSuspend: in a,(4Dh) bit 5,a jr nz,suspend_miniBorNoCableConnected xor a out (4Ch),a jr suspend_continue suspend_miniBorNoCableConnected: ld b,8 in a,(4Dh) bit 6,a jr nz,$F ld b,0 $$: ld a,b out (4Ch),a suspend_continue: ld a,2 out (54h),a in a,(39h) and 0F8h out (39h),a scf ret DecrementCounter: dec de ld a,d or e ret ;I'm not exactly sure what this indicates (likely device connected), but suspending appears to be the proper response to it. IsPort86hError: in a,(86h) bit 5,a ret