-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
usb.spin2
3730 lines (3518 loc) · 186 KB
/
usb.spin2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{
USB Keyboard and Gamepad Driver
Copyright (c) 2022-23 by Marco Maccaferri <[email protected]>
Based on 1CogKbM code by Garry Jordan ("garryj")
with code portions by Ada Gottensträter
Usage : see hkbd_compare, hkbd_translate for keyboard
hid_decode and hpad_translate for controllers
TERMS OF USE: MIT License
}
CON
_CLKFREQ = 160_000_000
USB_BASE_PIN = 40
ACTIVITY_LED = 57
ERROR_LED = 56
DAT ' Startup
org $000
asmclk
drvl #ACTIVITY_LED
drvl #ERROR_LED
coginit #0, ##@usb_host_start
DAT ' USB Host driver
org $000
usb_host_start
mov hcog_base_addr, ptrb
mov htmp, ##@hlut_end - 4 - @hlut_start ' Dealing with hub addresses
shr htmp, #2 ' so byte->long for the lut cell count
loc pb, #@hlut_start - @usb_host_start
add pb, hcog_base_addr
setq2 htmp
rdlong 0, pb ' Do the hub->lut copy
loc pb, #@usb_host_init - @usb_host_start
add pb, hcog_base_addr
jmp pb ' Initialize host and enter main processing loop
'------------------------------------------------------------------------------
' 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
'------------------------------------------------------------------------------
' 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
'------------------------------------------------------------------------------
' 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
'------------------------------------------------------------------------------
' 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
testb hstatus, #DWNSTRM_HUBB wc
if_c call #utx_pre
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
'------------------------------------------------------------------------------
' SETUP/OUT/INTERRUPT transaction.
' Possible function response in order of precedence: STALL, ACK, NAK.
'------------------------------------------------------------------------------
' On entry:
' ep_addr_pid - PID_OUT(b0..7), address(b8..b14), endpoint(b15..18) and
' CRC(b19..23).
' PTRA - start address of the data buff/struct that has the bytes to send.
' pkt_data - count of DATAx payload bytes to send.
' On exit:
'------------------------------------------------------------------------------
txn_out
call #wait_txn_ok ' ISR: ensure txn doesn't cross frame boundary
call #utx_token ' Put SETUP/OUT token on the bus
rdfast ##$80000000, ptra ' Use hub RAM FIFO interface to read the tx buffer
mov pkt_cnt, pkt_data
' Fall through to utx_data
'------------------------------------------------------------------------------
' 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
testb hstatus, #DWNSTRM_HUBB wc
if_c call #utx_pre
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
bitnot crc,#0 addbits 15 ' 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
'------------------------------------------------------------------------------
' 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
mov pb, urx_buff_p
wrfast ##$80000000, pb ' Use hub RAM FIFO interface to buffer bytes received
mov pb, htmp2
bmask crc, #15 ' Prime the CRC16 pump
mov pkt_cnt, #0 ' Keep track of payload bytes received
mov pkt_tmp, pkt_data
add pkt_tmp, #2 ' Tweak payload byte count to include CRC word
.wait_byte
' In-line rx for max speed
rqpin urx, dm
mov utx, #BYTE_TGLF ' Reg utx free in this context
and utx, urx
cmp newb_flg, utx wz ' Fetch a byte whenever the flags differ
if_nz xor newb_flg, #BYTE_TGLF ' Synchronize flags
if_nz jmp #.get_byte ' New byte!
testb urx, #EOPB wc
if_c jmp #.chk_crc ' At end-of-packet
jmp #.wait_byte
.get_byte
getbyte retval, urx, #1 ' New byte from smart pins
wfbyte retval ' Add it to the data buffer
rev retval ' Calculate CRC while next byte is shifting in
setq retval ' SETQ left-justifies the reflected data byte
crcnib crc, ##USB16_POLY ' Run CRC calc on the data nibs
crcnib crc, ##USB16_POLY
.end_crc
add pkt_cnt, #1
cmp pkt_cnt, pkt_tmp wcz
if_a mov retval, #ERR_PACKET ' Error if payload > expected size
if_a ret
' For full-speed at 80MHz, the time it takes to do the final byte write and
' CRC verify has likely put us into the EOP zone. The P2 smart pins keep the
' EOP flag "sticky" for 7-bits of J, but at 80MHz, it still could be possible
' to miss it, so cheat a bit and look for SOP clear here.
rqpin urx, dm
testb urx, #EOPB wc ' FIXME: checking for EOP set should work when > 80MHz
if_nc jmp #.wait_byte ' Next read will catch EOP at low-speed
' CRC OK = Payload CRC calc ^ packet's CRC bytes = $B001 (the USB-16 expected residual)
.chk_crc
sub pkt_cnt, #2 ' Adjust payload count to exclude the CRC bytes read
xor crc, ##USB16_RESIDUAL wz ' CRC of (data + transmitted CRC) XOR residual should equal zero
if_nz jmp #urx_packet ' CRC fail; discard data and wait until data re-sent or transfer timeout
mov retval, #PID_ACK
mov hctwait, ip_delay
call #poll_waitx
' Fall through to utx_handshake
'------------------------------------------------------------------------------
' 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
testb hstatus, #DWNSTRM_HUBB wc
if_c call #utx_pre
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
'------------------------------------------------------------------------------
' Wait for the USB tx buffer to empty and feed it a new byte.
'------------------------------------------------------------------------------
' On entry:
' utx - byte to transmit.
' On exit:
'------------------------------------------------------------------------------
utx_byte
testp dp wc
if_nc jmp #utx_byte
akpin dp
waitx utx_tweak ' Wait #0 '#3 if < 180MHz, wait #3 '#20 if 180MHz+
_ret_ wypin utx, dm
'------------------------------------------------------------------------------
' 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
'------------------------------------------------------------------------------
' 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
'------------------------------------------------------------------------------
' Full-speed/low-speed frame timing interrupt service routine.
'------------------------------------------------------------------------------
isr1_fsframe
getct iframe_ct_base
mov iframe_ct_new, iframe_ct_base
addct1 iframe_ct_new, _frame1ms_clks_
wxpin _usb_h_fs_nco_, dm ' Restore host mode and 12Mbs baud
.wait
testp dp wc
if_nc jmp #.wait
akpin dp
mov utx, #PID_SOF
wypin #OUT_SOP, dm ' Put start-of-packet SYNC field on the USB
call #utx_byte ' Send token PID byte
mov icrc, #$1f ' Prime the CRC5 pump
mov sof_pkt, frame ' CRC5 calculation done on the 11-bit frame number value
rev sof_pkt ' Input data reflected
setq sof_pkt ' CRCNIB setup for data bits 0..7
crcnib icrc, #USB5_POLY
crcnib icrc, #USB5_POLY ' Data bits 0..7 calculated
getbyte utx, frame, #0 ' Send the low byte of the frame number
call #utx_byte
shl sof_pkt, #8 ' Shift out processed bits to set up CRCBIT * 3
rep #2, #3 ' Three data bits left to process
shl sof_pkt, #1 wc
crcbit icrc, #USB5_POLY ' Data bits 8..10 calculated
xor icrc, #$1f ' Final XOR value
getbyte utx, frame, #1 ' Send remaining frame number bits
shl icrc, #3 ' Merge CRC to bits 7..3 of the final token byte
or utx, icrc
call #utx_byte ' Last start-of-frame byte is on the wire
mov isrtmp1, _ip_delay_fs_ ' Use normal inter-packet delay when full-speed
jmp #isr1_wait
isr1_lsframe
getct iframe_ct_base
mov iframe_ct_new, iframe_ct_base
addct1 iframe_ct_new, _frame1ms_clks_
.wait
testp dp wc
if_nc jmp #.wait
akpin dp
wypin #OUT_EOP, dm ' EOP is the low-speed keep-alive strobe
mov isrtmp1, _ip_delay_ls_ ' Normal inter-packet delay works when low-speed
isr1_wait
rqpin utx, dm
testb utx, #SOPB wc
if_c jmp #isr1_wait
add frame, #1 ' Next frame# and check for wrap around
zerox frame, #10
waitx isrtmp1 ' Make sure bus is idle
reti1
'------------------------------------------------------------------------------
' Wait for a window within the 1ms frame boundary that will ensure that a
' transaction will complete before the next frame is triggered.
'------------------------------------------------------------------------------
' On entry:
' On exit:
'------------------------------------------------------------------------------
wait_txn_ok
getct htmp2
sub htmp2, iframe_ct_base
testb hstatus, #LOW_SPEEDB wc
if_c cmp htmp2, _txn_ok_ls_ wcz
if_nc cmp htmp2, _txn_ok_fs_ wcz
if_a jmp #wait_txn_ok ' Not enough time, so wait until next frame
ret
'------------------------------------------------------------------------------
' A device connection was detected, or a bus reset was requested by the USB
' client. Set the appropriate smart pin FS/LS speed mode to match the device
' and perform a reset sequence prior to device enumeration.
'------------------------------------------------------------------------------
dev_reset
rqpin urx, dm
testb urx, #K_RESUMEB wc ' K differential "1" in FS mode signals low-speed
if_c call #set_speed_low ' The speed config subroutines must restore the caller C flag
if_nc call #set_speed_full ' state on return if it writes the C flag.
reset
setint1 #0 ' Don't want frame interrupt while in reset
wypin #OUT_SE0, dm ' Assert bus reset
waitx _reset_hold_ ' Spec is >= 10ms
wypin #OUT_IDLE, dm
mov frame, #0 ' Reset the frame timespan count
getct iframe_ct_base
mov iframe_ct_new, iframe_ct_base
addct1 iframe_ct_new, _frame1ms_clks_
mov htmp, frame ' Allow reset recovery time (Section 9.2.6.2)
add htmp, #36
setint1 #1 ' Set ISR event trigger to CT-passed-CT1
.framewait
cmp frame, htmp wcz
if_b jmp #.framewait
ret
'------------------------------------------------------------------------------
' 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
'------------------------------------------------------------------------------
' 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
drvl host_error_led
jmp #host_reset ' See if it works...
' USB commands and error codes
cmd_data long 0
usb_err_code long ERR_NONE
' Parameters block assigned at cog startup
dm long USB_BASE_PIN + 2 ' Client defines the basepin for four consecutive USB port pins
dp long USB_BASE_PIN + 3
usb_event_pin long USB_BASE_PIN ' Host event reporting uses a long repository smart pin
host_active_led long ACTIVITY_LED ' Client defines the LED pin# for host bus activity
host_error_led long ERROR_LED ' Client defines the LED pin# to light on error
hid_descr_p long 0, 0[7]
hid_report_p long 0, 0[7]
hub_descr_p long 0
urx_buff_p long 0
dev_desc_buff_p long 0
con_desc_buff_p long 0
cache_start_p long 0
cache_end_p long 0
hdev_init_start
hdev_port long 0
hdev_next_datax long 0, 0
hub_intf_num long 0
hub_next_datax long 0
hub_status_chg long 0
hub_port_status long 0
ms_intf_num long 0
ms_in_max_pkt long 0
kb_intf_num long 0
kb_interval long 0
kb_in_max_pkt long 0
kb_max_index long 0
kb_led_states long 0
kbm_next_datax long 0
gp_intf_num long 0
gp_interval long 0
gp_max_index long 0
gp_descr_len long 0, 0[7]
hdev_init_end
hidr_start
hidr_id long 0
hidr_axis long 0[3]
hidr_buttons long 0
hidr_hats long 0
hidr_flags long 0
hidr_state long 0
hidr_size long 0
hidr_count long 0
hidr_offset long 0
hidr_usage long 0
hidr_usage_idx long 0
hidr_lminmax long 0
hidr_pminmax long 0
hidr_end
' Initialized at cog startup:
save_sysclk long 0 ' Save the current sysclock as the client may change it
hcog_base_addr long 0 ' This object's start address in hub, read from PTRB at cog creation
iframe_ct_new long 0
iframe_ct_base long 0
p2rev_val long P2RevB
utx_tweak long 0 ' Sysclock speeds above ~120MHz need some fairy dust for USB tx
' This register block is reset to zero when a USB device connects
hreg_init_start
hstatus long 0 ' Host status flags
hctwait long 0 ' Poll-based wait clocks
ip_delay long 0 ' Inter-packet delay in bit periods for connected device speed
tat_wait long 0 ' Maximum bus turn-around time in bit periods for connected device speed
nak_retry long 0 ' NAK retry count, unlimited retries if zero
xfer_retry long 0 ' Control transfer retry count
retry long 0 ' Transaction retry count
utx long 0 ' Byte to transmit on USB
urx long 0 ' LSByte receiver status flags, MSByte received data
newb_flg long 0 ' Receive "new byte" bit toggle detector
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
' Device stuff
hdev_id long 0
hdev_bcd long 0
hdev_type long 0, 0 ' Configured device indicator
' 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
hdev_intf_idx long 0 ' Used during verbose descriptor terminal output
hdev_class long 0
hdev_subclass long 0
hdev_protocol long 0
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
hhub_ctrl_ep long 0 ' Hub control endpoint address
hhub_ep_addr long 0 ' Hub interface endpoint address
hmouse_ep_addr long 0 ' Mouse interface endpoint address
hkbd_ctrl_ep long 0 ' Keyboard control endpoint address
hkbd_ep_addr long 0 ' Keyboard interface endpoint address
hkbd_poll_cnt long 0 ' Poll interval counter used for key auto-repeat
hkbd_ledstates long 0 ' Off/on state of keyboard LEDs
hdev_ep_addr long 0, 0[7]
hdev_out_addr long 0
hreg_init_end
' Variables dependent on the system freqency
_var_64_lower_ res 1
_var_64_upper_ res 1
_12Mbps_ res 1
_1_5Mbps_ res 1
_1ns16fp_ res 1 ' 1ns as 32,16 fixed point
_1us_ res 1 ' 1us
_10us_ res 1 ' 10us
_33us_ res 1 ' 33us
_txn_err_ res 1 ' 250us
_500us_ res 1 ' 500us
_txn_ok_ls_ res 1 ' 666us timespan for LS transaction OK window
_txn_ok_fs_ res 1 ' 850us timespan for FS transaction OK window
_ip_delay_ls_ res 1 ' Low-Speed inter-packet 4 bit-time delay
_ip_delay_fs_ res 1 ' Full-Speed inter-packet 4 bit-time delay
_tat_wait_ls_ res 1 ' Low-Speed turnaround 22 bit-time wait
_tat_wait_fs_ res 1 ' Full-Speed turnaround 28 bit-time wait
_preamble_wait_ res 1
_1ms_ res 1 ' 1ms
_2ms_ res 1 ' 2ms
_suspend_wait_ res 1 ' 3ms
_4ms_ res 1 ' 4ms
_xfer_wait_ res 1 ' 5ms
_8ms_ res 1 ' 8ms timespan for keyboard/mouse interrupt IN transactions
_reset_hold_ res 1 ' 15ms
_resume_hold_ res 1 ' Hold K-state for 20ms to signal device(s) to resume
_21ms_ res 1 ' 21ms
_100ms_ res 1 ' 100ms
_500ms_ res 1 ' 500ms
_pulse_time_ res 1 ' Activity LED toggle interval, one sec connect wait, _500ms_ when connected
_frame1ms_clks_ res 1 '_1ms +/- n clocks: calculated based on the current sysclock
'------------------------------------------------------------------------------
_usb_h_ls_nco_ res 1 ' USB smart pin modes dependent on sysclock
_usb_d_ls_nco_ res 1
_usb_h_fs_nco_ res 1
_usb_d_fs_nco_ res 1
'------------------------------------------------------------------------------
' Scratch registers
htmp res 1 ' Scratch registers whose context remains within the same code block
htmp1 res 1
htmp2 res 1
htmp3 res 1
hrep res 1 ' Repeat count
hsave0 res 1 ' Subroutine parameter saves
hsave1 res 1
hsave2 res 1
isrtmp1 res 1
pkt_tmp res 1 ' Tmp storage for routines that deal with datax packets
hr0 res 1 ' Multi-purpose registers
hr1 res 1
hr2 res 1
hr3 res 1
hpar1 res 1 ' Routine entry/exit parameters
hpar2 res 1
hpar3 res 1
hct2 res 1 ' Function response bus turn-around timer
hct3 res 1 ' Keyboard/mouse poll timer
mod_cnt res 1 ' Used in idle loops
fit $1F0
DAT ' USB Host LUT execution
org $200
hlut_start
'------------------------------------------------------------------------------
' Full-speed is the host's native speed, so all that is needed is to set the FS
' settings to startup defaults.
'------------------------------------------------------------------------------
' On entry:
' On exit: Save/restore caller C flag state if C is changed in this routine!
'------------------------------------------------------------------------------
set_speed_full
mov ijmp1, #isr1_fsframe ' Set the USB 1ms frame handler ISR routine
mov max_pkt_size, #64 ' Set FS control read/write DATAx packet size
mov tat_wait, _tat_wait_fs_ ' Bus turn-around time in full-speed bit periods
_ret_ mov ip_delay, _ip_delay_fs_ ' Inter-packet delay in full-speed bit periods
' ret wcz ' Restore caller flags on exit
'------------------------------------------------------------------------------
' When a low-speed device connects, the D-/D+ signaling is inverted. If there
' is a downstream hub connected (not yet implemented), the baud generator
' remains set at the full-speed rate, but signaling is switched to low-speed,
' which reverses the D-/D+ polarity. The polarity can be changed without
' putting the smart pins into reset.
'------------------------------------------------------------------------------
' On entry:
' On exit: CZ flags restored to caller states
'------------------------------------------------------------------------------
set_speed_low
test hstatus, #DWNSTRM_HUBF wz ' If no downstream hub connected, set low-speed baud
if_z mov ijmp1, #isr1_lsframe ' Set the USB 1ms frame handler ISR routine
testb p2rev_val, #0 wc ' P2 Revision is either %0001 (A) or %0010 (B+)
if_c dirl dm ' P2RevA needs to be completely reconfigured
if_c dirl dp
if_c wrpin ##USB_V1HMODE_LS, dm ' Low-speed signaling is always used
if_c wrpin ##USB_V1HMODE_LS, dp
if_c wxpin _1_5Mbps_, dm ' Set 1.5Mbs baud if no downstream hub
if_c dirh dm
if_c dirh dp
if_nc wxpin _usb_h_ls_nco_, dm ' Host mode and 1.5Mbs baud if no downstream hub
mov max_pkt_size, #8 ' Set LS control read/write DATAx packet size
mov tat_wait, _tat_wait_ls_ ' Bus turn-around time in low-speed bit periods
mov ip_delay, _ip_delay_ls_ ' Inter-packet delay in low-speed bit periods
bith hstatus, #LOW_SPEEDB ' D- pulled high, so it's a Low-Speed device
ret wcz ' Restore caller flags on exit
'------------------------------------------------------------------------------
' Perform configuration stuff required when a device intitially connects.
'------------------------------------------------------------------------------
' On entry:
' On exit:
'------------------------------------------------------------------------------
on_connect
mov hr0, #2 ' FIXME: need to determine a reasonable limit for reset & retry
call #dev_reset ' Reset device prior to Get Device Descriptor request
.retry
testb hstatus, #LOW_SPEEDB wc
if_c mov hpar1, #USB_SPEED_LOW ' Also the connect speed
if_nc mov hpar1, #USB_SPEED_FULL
mov ep_addr_pid, ##EP_ADDR_ZERO ' New connect, use pre-calc CRC for ep/addr zero
loc ptra, #@get_dev_desc - @usb_host_start ' Hub start address of GetDeviceDescriptor SETUP struct
add ptra, hcog_base_addr
wrword #8, ptra[wLength] ' Request IN data stage max of 8 bytes will test actual < requested logic
mov pb, dev_desc_buff_p ' 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, _500ms_ ' If the first GetDescriptor() fails, reset and try again
call #poll_waitx
sub hr0, #1 wz ' FIXME: need to determine a reasonable limit for reset & retry
if_z jmp #host_error ' Post error and spin until the errant device is disconnected
call #reset ' Try another reset to see if the device responds
jmp #.retry
.get_dev_desc
mov pa, dev_desc_buff_p ' Fetch the max packet size for control transactions from the
add pa, #DEV_bMaxPktSize0 ' appropriate Device Descriptor struct member offset
rdbyte max_pkt_size, pa
mov hctwait, _1ms_
call #poll_waitx ' Do a reset before SetAddress(), but wait a bit first
call #reset
loc ptra, #@set_address - @usb_host_start ' Hub start address of SetAddress SETUP struct
add ptra, hcog_base_addr
wrword #8, ptra[wValue] ' Only support one device port at this time
call #control_write ' Execute SetAddress()
cmp retval, #PID_ACK wz
if_nz ret ' Back to idle if not ACK
mov hctwait, _8ms_
call #poll_waitx ' Allow SetAddress() a minimum 2ms recovery interval
mov ep_addr_pid, ##8 << 8 ' Device ep/addr now #8 and endpoint zero
call #calc_crc5
loc ptra, #@get_dev_desc - @usb_host_start ' Repeat SETUP for GetDeviceDescriptor()
add ptra, hcog_base_addr
mov pb, dev_desc_buff_p ' 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
mov ptra, dev_desc_buff_p ' Do the same with the control max packet size
rdbyte hctrl_max_pkt, ptra[DEV_bMaxPktSize0]
mov hctwait, _500us_
call #poll_waitx
loc ptra, #@get_config_desc - @usb_host_start ' Hub start address of GetConfigurationDescriptor SETUP struct
add ptra, hcog_base_addr
wrword #$FF, ptra[wLength] ' Maximum DATAx bytes for receive to the SETUP struct
mov pb, con_desc_buff_p ' Hub start address of ConfigurationDescriptor structure
call #control_read ' Execute GetConfigurationDescriptor()
cmp retval, #PID_ACK wz
if_nz ret
mov ptra, con_desc_buff_p ' 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
loc pa, #@init_kbdm_data - @usb_host_start
add pa, hcog_base_addr
call pa
loc pa, #@hparse_con_desc - @usb_host_start
add pa, hcog_base_addr
jmp pa
'------------------------------------------------------------------------------
' 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
mov ptra, urx_buff_p ' Copy DATAx in rx buffer to dest struct
mov hr0, pkt_cnt
cmp ptra, pb wz
if_nz 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
'------------------------------------------------------------------------------
' 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
testb ep_addr_pid, #31 wc
if_c bith hstatus, #DWNSTRM_HUBB
if_c bith _usb_h_ls_nco_, #14
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 jmp #dwnstream_reset ' 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
jmp #dwnstream_reset ' 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 jmp #dwnstream_reset ' Control Write finished
cmp retval, #PID_STALL wz ' STALL needs to go to the caller for resolution