/
multi.s
1850 lines (1766 loc) · 49 KB
/
multi.s
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
; Durango-X devcart SD multi-boot loader
; now with sidecar/fast SPI support
; v2.1.4 with volume-into-FAT32 and Pocket support!
; (c) 2023-2024 Carlos J. Santisteban
; based on code from http://www.rjhcoding.com/avrc-sd-interface-1.php and https://en.wikipedia.org/wiki/Serial_Peripheral_Interface
; last modified 20240130-1650
; assemble from here with xa multi.s -I ../../OS/firmware
; add -DSCREEN for screenshots display capability
; add -DTALLY for LED access indicator
; add -DDEBUG if desired
#echo DevCart @ $DFC0
#echo FastSPI @ $DF96-7, SPI ID=0...3
#echo volume-into-FAT32 & pX support!
; SD interface definitions
#define SD_CLK %00000001
#define SD_MOSI %00000010
#define SD_CS %00000100
#define SD_MISO %10000000
#define CMD0 0
#define CMD0_CRC $94
#define CMD8 8
#define CMD8_ARG $01AA
#define CMD8_CRC $86
#define CMD16 16
#define CMD16_ARG $0200
#define ACMD41 41
#define ACMD41_ARG $40
#define CMD55 55
#define CMD58 58
#define CMD17 17
#define SD_MAX_READ_ATTEMPTS 203
; error code messages
#define IDLE_ERR 0
#define SDIF_ERR 1
#define ECHO_ERR 2
#define INIT_ERR 3
#define READY_ERR 4
#define OK_MSG 5
#define FAIL_MSG 6
#define INVALID_SD 7
#define SEL_MSG 8
#define LOAD_MSG 9
#define PAGE_MSG 10
#define SPCR_MSG 11
#define OLD_SD 12
#define HC_XC 13
#define SPLASH 14
#define NX_DEVICE 15
#define ABORT 16
#define MNT_RAW 17
#define MNT_FAT 18
; *** hardware definitions ***
IO8attr = $DF80
IO8blk = $DF88
IO9kbd = $DF9B ; should be into firmware
IO9nes0 = $DF9C
IO9nlat = IO9nes0
IO9nes1 = $DF9D
IO9nclk = IO9nes1
IO9sp_d = $DF96 ; new, Fast SPI data transfer *** NO LONGER $DF9E
IO9sp_c = $DF97 ; new, Fast SPI control *** NO LONGER $DF9F
IOAie = $DFA0
IOBeep = $DFB0
IOCart = $DFC0
IO_PSG = $DFDB ; PSG riser port
#define KBDMAT
; *** memory usage ***
dev_id = $EB ; $EB 1...4=SPI, 241=devCart, 0=RasPi; will turn into '0...3' and ' ' (and '/') for display
crc = dev_id+ 1 ; $EC
en_ix = crc + 1 ; $ED ### directory storage ###
sd_ver = en_ix + 1 ; $EE ### not so temporary ###
arg = sd_ver+ 1 ; $F0-$F3
res = arg + 4 ; $F4-$F8
mosi = res + 5 ; $F9
miso = mosi + 1 ; $FA
token = miso + 1 ; $FB
ptr = token + 1 ; $FC-$FD
cnt = ptr + 2 ; $FE
tmpba = cnt - 1 ; actually $FE-$FF, as $FD will NOT be used
; *** sector buffer and header pointers ***
buffer = $400
magic1 = buffer+0 ; must contain zero
magic2 = buffer+7 ; must contain CR (13)
magic3 = buffer+255 ; must contain zero (assume filesize < 16 MiB)
bootsig = buffer+1 ; contains 'dX' for bootable ROM images, 'pX' for Pocket format
ld_addr = buffer+3 ; load address for Pocket format, '**' otherwise
ex_addr = buffer+5 ; execution address for Pocket format, '**' otherwise
fname = buffer+8
ftime = buffer+248 ; time in MS-DOS format
fdate = buffer+250 ; date in MS-DOS format
fsize = buffer+252 ; file size INCLUDING 256-byte header
end_pg = buffer+255 ; originally zero, this is a somewhat ugly hack...
; *** directory storage *** ($2F0-$2FF +36 bytes after $300, might be compacted)
en_tab = $300
;ex_ptr = $2FE ; execution pointer for Pocket files NEW
sig_tab = $2F0 ; NEW, store signature ('X'=executable, 'S'=16-colour screen, 'R'=HIRES screen)
; *** interface vectors *** NEW for extra SD interfaces ($2EA-$2EF)
v_spi_tr = $2EA
v_cs_enable = v_spi_tr+2 ; $2EC
v_cs_disable = v_cs_enable+2 ; $2EE
; *** mount point *** NEW ($2E1-$2E9)
sec_clus = $2E1 ; number of sectors per cluster $2E1
dir_clus = sec_clus+1 ; first cluster of directory (needed for subtract) $2E2
mnt_point = dir_clus+4 ; mount point (BIG endian like arg) $2E6
; *****************************************************
; *** firmware & hardware definitions for Durango-X ***
; *****************************************************
fw_irq = $0200 ; ### usual minimOS interrupt vectors ###
fw_nmi = $0202
ticks = $0206 ; jiffy counter EEEEK
; make room for keyboard driver ($020A-$020F)
; CONIO specific variables
fw_cbin = $0210 ; $210 integrated picoVDU/Durango-X specifics
fw_fnt = fw_cbin +1 ; $211 (new, pointer to relocatable 2KB font file)
fw_mask = fw_fnt +2 ; $213 (for inverse/emphasis mode)
fw_chalf = fw_mask +1 ; $214 (remaining pages to write)
fw_sind = fw_chalf +1 ; $215
fw_ccol = fw_sind +3 ; $218 (no longer SPARSE array of two-pixel combos, will store ink & paper)
fw_ctmp = fw_ccol +4 ; $21C
fw_cbyt = fw_ctmp ; (temporary glyph storage) other tmp
fw_ccnt = fw_cbyt ; (bytes per raster counter, no longer X) actually the same tmp
fw_ciop = fw_ccnt +1 ; $21D cursor position
fw_vbot = fw_ciop +2 ; $21F page start of screen at current hardware setting (updated upon FF)
fw_vtop = fw_vbot +1 ; $220 first non-VRAM page (new)
fw_io9 = fw_vtop +1 ; $221 received keypress
fw_scur = fw_io9 +1 ; $222 NEW, cursor control
fw_knes = fw_scur +1 ; $223 NEW, NES-pad alternative keyboard *** different use here
GAMEPAD_MASK1 = fw_knes +1 ; $224 EEEEEEEEK
GAMEPAD_MASK2 = GAMEPAD_MASK1 +1 ; $225 needed for standard gamepad support
gamepad1 = GAMEPAD_MASK2 +1 ; "standard" read value at $226
gamepad2 = gamepad1 +1 ; "standard" read value at $227
; CONIO zeropage usage ($E4-$E7)
cio_pt = $E6
cio_src = $E4
* = $E000 ; 8 kiB ROM image, non-downloadable
; ***********************
; *** standard header *** to be found before ANY ROM image
; ***********************
rom_start:
; header ID
.byt 0 ; [0]=NUL, first magic number
.asc "dX" ; bootable ROM for Durango-X devCart
.asc "****" ; reserved
.byt 13 ; [7]=NEWLINE, second magic number
; filename
.asc "devCart/FastSPI multiboot" ; C-string with filename @ [8], max 220 chars
#ifdef SCREEN
.asc " & image browser"
#endif
#ifdef DEBUG
.asc " (D", "EBUG version)"
#endif
; note terminator below
; optional C-string with comment after filename, filename+comment up to 220 chars
.asc 0, 0
; advance to end of header *** NEW format
.dsb rom_start + $E6 - *, $FF
; NEW library commit (user field 2)
.asc "$$$$$$$$"
; NEW main commit (user field 1)
.asc "$$$$$$$$"
; NEW coded version number
.word $21C4 ; 2.1f4 %vvvvrrrrsshhbbbb, where revision = %hhrrrr, ss = %00 (alpha), %01 (beta), %10 (RC), %11 (final)
; date & time in MS-DOS format at byte 248 ($F8)
.word $7440 ; time, 14.34 %0111 0-100 010-0 0000
.word $5837 ; date, 2024/1/30 %0101 100-0 001-1 1110
; filesize in top 32 bits (@ $FC) now including header ** must be EVEN number of pages because of 512-byte sectors
.word $10000-rom_start ; filesize (rom_end is actually $10000)
.word 0 ; 64K space does not use upper 16 bits, [255]=NUL may be third magic number
; **********************
; *** SD-card module ***
; **********************
sd_main:
.(
LDX #SPLASH
JSR disp_code ; display splash screen
; select first SD device (devCart) and try
LDA #241 ; devCart ID
STA dev_id
LDX #vec_dc_sd-vec_sd ; set vectors for devCart device
vecload:
LDA dev_id ; get present device ID
CLC
ADC #'0'-1 ; convert into ASCII (' ' for devCart)
TAY
PHX
JSR conio ; print ID
PLX ; retrieve vector index and continue
LDY #0 ; worth going upwards
vl_loop:
LDA vec_sd, X
STA v_spi_tr, Y ; assume it's first vector!
PHY ; *** print device name ***
PHX
LDY sd_name, X ; get character
JSR conio
PLX
PLY ; restore regs and go for next byte
INX
INY
CPY #6 ; number of bytes per entry
BNE vl_loop
; SD device driver has been installed, proceed to check for SD card as usual
sd_selected:
JSR sd_init ; check SD card
LDY #13
JSR conio ; newline
; *** list SD contents ***
; first sector *in volume* is already read!
STZ en_ix ; reset index
BRA header_see ; because it's already loaded! perhaps checked too, but check it anyway
ls_disp:
; * load current sector *
; ...unless it's the very first
LDX #>buffer ; temporary load address
STX ptr+1
; STZ ptr ; assume buffer is page-aligned
JSR ssec_rd ; read one 512-byte sector
; might do some error check here...
header_see:
JSR chk_head ; look for a valid header
BCC header_ok
JMP end_vol ; if not, that's the end of the volume
header_ok:
; header is valid, check whether bootable or not
LDA bootsig ; check Durango-X bootable ROM image signature
CMP #'d'
BEQ chk_exec ; *
CMP #'p' ; * might be Pocket as well
BNE next_file
chk_exec:
LDA bootsig+1
CMP #'X'
#ifdef SCREEN
BEQ ls_page ; it is bootable, but check for screenshots too
CMP #'S' ; 16-colour mode
BEQ ls_page
CMP #'R' ; HIRES mode
#endif
BNE next_file ; ignore generic files otherwise
; * bootable ROM image detected, register sector and display entry *
ls_page:
LDX en_ix ; last registered entry
CPX #9 ; already full?
BCS skip_hd
#ifdef SCREEN
STA sig_tab, X ; store accepted type NEW ** not used?
#endif
TXA
ASL
ASL ; 4-byte entries
TAX
LDY #3 ; max sector offset
en_loop:
LDA arg, Y ; current sector (big endian)
STA en_tab, X ; store locally (little endian)
INX
DEY
BPL en_loop ; complete four bytes
INC en_ix ; one entry has been detected
; print entry number before filename
LDY #14
JSR conio ; set inverse mode
LDA en_ix
CLC
ADC #'0' ; get entry (1-based now) as ASCII number
TAY
JSR conio
LDY #15
JSR conio ; standard video
#ifdef SCREEN
LDA bootsig+1
CMP #'S' ; is it a screenshot? (non-executable)
BEQ is_ss
CMP #'R'
BNE not_ss
is_ss:
LDY #16 ; DLE
JSR conio
LDY #12 ; paper glyph
JSR conio
BRA dispname ; put this together?
not_ss:
#endif
LDY #' ' ; default separator is SPACE
LDA bootsig ; * check executable signature
CMP #'p' ; * Pocket?
BNE disptype ; *
LDY #'.' ; * show dot instead
disptype: ; *
JSR conio ; space between number and filename
; now print filename
dispname:
LDX #0 ; string index
name_loop:
LDY fname, X ; get char
BEQ name_end ; until terminator
PHX
JSR conio ; print char
PLX
INX
; *** might check for maximum screen length...
BNE name_loop ; no need for BRA
name_end:
; *** might display some metadata here...
LDY #13
JSR conio ; next line
#ifdef DEBUG
JSR show_sector
#endif
BRA next_file
; * in any case, jump and read next header *
next_file:
; compute next header sector
JSR next_sector
JMP ls_disp ; no need for BRA
end_vol:
LDA en_ix ; check if volume ended with no entries listed
BEQ skip_err
CMP #9
BNE last_pg
skip_hd:
LDX #PAGE_MSG
JSR disp_code ; add next page option
last_pg:
LDX #NX_DEVICE
JSR disp_code ; and next device (new, also displayed after last page)
;last_pg:
JSR sel_en ; wait for a valid entry...
STZ en_ix ; ...but if arrived here, skip to new page
LDX #SPCR_MSG
JSR disp_code ; clean up for next page
JMP ls_page ; avoid re-reading the sector
skip_err:
LDX #INVALID_SD ; invalid contents error
JMP sd_fail
; *******************************************
; *** image is selected, now boot from it ***
; *******************************************
do_boot:
SEI ; no more interaction needed, slight performance improvement.
; reload sector of selected entry into buffer
LDX #>buffer ; temporary load address
STX ptr+1
; STZ ptr ; assume buffer is page-aligned
JSR ssec_rd ; read first 512-byte sector of the file (will be read again)
; print loading message with filename
LDX #LOAD_MSG
JSR disp_code
LDX #0
pr_load:
LDY fname, X ; get filename from buffered first sector
BEQ prl_ok
PHX
JSR conio ; print it
PLX
INX
BNE pr_load ; no need for BRA
prl_ok:
LDY #10 ; cursor down...
JSR conio
LDY #11 ; ...and cursor back up...
JSR conio ; ...leave some space for the progress indicator
; check size, determine ptr towards end of 64K space (or select screen address right now)
; but if pX format, begin from specified address AND end as required
#ifdef SCREEN
LDA bootsig+1
CMP #'X' ; if not executable, it's a screenshot
BEQ rom_siz
LDX #$80
STX end_pg ; tweak this indicator with end page!
LDA #$5E ; will load header sector off-screen!
BRA set_ptr ; always screen address
rom_siz:
#endif
LDA bootsig ; * ROM image or Pocket?
CMP #'p' ; *
BNE set_image ; * if Pocket...
LDA ld_addr+1 ; * get load address from header
TAX ; * and save for later
CLC ; *
ADC fsize+1 ; * add number of pages
LDY fsize ; * but check LSB (size not necessarily page-aligned)
BEQ all_pg ; * if zero, already OK
INC ; * otherwise fill last page
all_pg: ; *
STA end_pg ; * store end page
TXA ; * EEEEEEEEEEEEEEEEEEEEEEKKKKKKKKKKKKKKKKK
BRA set_ptr ; *
; ROM images go towards the end of 64K space
set_image:
LDA #0
SEC
SBC fsize+1 ; subtract number of pages
set_ptr:
STA ptr+1
STZ ptr ; definitive pointer is ready, proceed with load!
boot:
JSR ssec_rd ; read one 512-byte sector
; might do some error check here...
JSR chk_brk ; ** check for BREAK key
BCC cont_load ; ** if pressed...
JMP vecload ; ** ...restart with same _interface_
cont_load: ; ** or continue as usual
JSR progress
INC arg+3 ; only 64 sectors, no need to check MSB... EEEEEEEEK endianness!
BNE no_wrap
INC arg+2 ; now could have several images, may wrap...
BNE no_wrap
INC arg+1 ; now could have several images, may wrap...
BNE no_wrap
INC arg ; now could have several images, may wrap...
no_wrap:
LDA ptr+1 ; check current page
CMP end_pg ; this is usually zero... unless it's a screenshot!
BNE boot ; until completion
; ** after image is loaded... **
#ifdef SCREEN
LDA bootsig+1 ; signature of selected file
CMP #'X' ; if executable, go run it!
BEQ launch_rom ; otherwise it's a screenshot, S clears HIRES bit, R sets it
CMP #'S' ; C set if ='S', clear if 'R', just the opposite we need
LDA #%01110000 ; shifted left 1, non-inverse, screen3, RGB, Emilio's LED off
ROR ; now it's %C0111000, C clear
EOR #%10000000 ; invert operation, as desired
STA IO8attr ; set proper video mode
BRK
launch_rom:
#endif
LDA bootsig ; * Pocket or ROM image?
CMP #'d' ; * standard Durango-X ROM image?
BEQ launch_image ; * otherwise must be 'pX'
JMP (ex_addr) ; * run from specified address (from preloaded header)
; Interrupts already shut off for performance, good for reset anyway
launch_image:
JMP switch ; start code loaded into cartidge RAM!
; *** some high-level routines ***
chk_head:
; * look for a valid header * assume sector loaded at buffer
LDA magic1 ; check magic number one
BNE not_magic
LDA magic2 ; check magic number two
CMP #13 ; must be NEWL instead of zero
BNE not_magic
LDA magic3 ; check magic number three
BNE not_magic
CLC ; report no error, header is valid
RTS
not_magic:
SEC ; otherwise header is not valid
RTS
chk_brk:
; * check for BREAK key *
LDA #1 ; first column has both SPACE & SHIFT
STA IO9kbd
LDA IO9kbd ; get active rows
STZ IO9kbd ; just for good measure
AND #%10100000 ; mask relevant keys
CMP #%10100000 ; both SHIFT & SPACE?
BNE no_break
LDX #0 ; *** last line, where progress indicator was
brk_clean:
STZ $7F00, X ; *** clear last line, thus progress remains
INX ; ***
BNE brk_clean ; *** until the end of screen
LDX #ABORT
JSR disp_code ; show abort message EEEEK
LDX #vec_dc_sd-vec_sd ; set vectors for devCart device
LDA dev_id ; really DevCart?
BMI do_repeat ; seem so
LDX #vec_sp_sd-vec_sd ; FastSPI otherwise
do_repeat:
CLI ; EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEKKKKKKKKKKKKKKKKKKKK
; JMP vecload ; init card again, same device
SEC
RTS ; notify BREAK and return
no_break:
CLC
RTS ; no break, continue as usual
; ************************
; *** support routines ***
; ************************
; *** *** vectored hardware calls *** *** NEW
spi_tr:
JMP (v_spi_tr)
cs_enable:
JMP (v_cs_enable)
cs_disable:
JMP (v_cs_disable)
; *** *** hardware interface for devCart *** ***
; *** send data in A, return received data in A *** nominally ~4.4 kiB/s
dc_spi_tr:
STA mosi
LDY #8 ; x = 8;
LDA #SD_CLK
TRB IOCart ; digitalWrite(SCK, 0); (13t)
tr_l: ; while (x)
ASL mosi
LDA IOCart
AND #SD_MOSI^$FF
BCC mosi_set
ORA #SD_MOSI
mosi_set:
STA IOCart ; digitalWrite(MOSI, data & 128); data <<= 1;
INC IOCart ; digitalWrite(SCK, 1); ** assume SD_CLK is 1 **
ASL ; in <<= 1; ** assume SD_MISO is $80 **
ROL miso ; if(digitalRead(MISO)) in++;
DEC IOCart ; digitalWrite(SCK, 0); ** assume SD_CLK is 1 **
DEY ; x--;
BNE tr_l ; (worst case, 8*43 = 344t)
LDA miso ; return in; (total including call overhead = 372t, ~242 µs)
RTS
; *** enable card transfer ***
dc_cs_enable:
LDA #$FF
JSR spi_tr ; SPI_transfer(0xFF);
LDA #SD_CS
TRB IOCart ; CS_ENABLE();
LDA #$FF
#ifdef TALLY
STZ IOAie ; *** this will turn LED on ***
#endif
JMP dc_spi_tr ; SPI_transfer(0xFF); ...and return
; *** disable card transfer ***
dc_cs_disable:
LDA #$FF
JSR spi_tr ; SPI_transfer(0xFF);
LDA #SD_CS
TSB IOCart ; CS_DISABLE();
LDA #$FF
#ifdef TALLY
STA IOAie ; *** this will turn LED off ***
#endif
JMP dc_spi_tr ; SPI_transfer(0xFF); ...and return
; *** *** hardware interface for Fast SPI *** ***
; *** send data in A, return received data in A ***
sp_spi_tr:
STA IO9sp_d ; store outgoing byte
LDA IO9sp_c ; send 8 clock pulses (quickly)
; LDA IO9sp_c ; actually seven, as Parallel load will make first bit available!
LDA IO9sp_c
LDA IO9sp_c
LDA IO9sp_c
LDA IO9sp_c
LDA IO9sp_c
LDA IO9sp_c
LDA IO9sp_d ; retrieve incoming data
RTS
; *** enable card transfer ***
sp_cs_enable:
LDA #$FF
JSR spi_tr ; SPI_transfer(0xFF);
LDY dev_id ; must preserve X! eeek!
LDA sid_en, Y ; get enable mask for selected ID
STA IO9sp_c ; CS_ENABLE();
#ifdef TALLY
STA IOAie ; *** this will turn LED on ***
#endif
LDA #$FF
JMP sp_spi_tr ; SPI_transfer(0xFF); ...and return
; *** disable card transfer ***
sp_cs_disable:
LDA #$FF
JSR spi_tr ; SPI_transfer(0xFF);
LDA #%11111111 ; all SPI devices disabled
STA IO9sp_c ; CS_DISABLE();
#ifdef TALLY
STA IOAie ; *** this will turn LED off ***
#endif
; LDA #$FF
JMP sp_spi_tr ; SPI_transfer(0xFF); ...and return
; *** *** standard SD card support *** ***
; *** send command in A to card *** arg.l, crc.b
sd_cmd:
; send command header
ORA #$40
JSR spi_tr ; SPI_transfer(cmd|0x40);
; send argument
LDA arg
JSR spi_tr ; SPI_transfer((u_int8_t)(arg >> 24));
LDA arg+1
JSR spi_tr ; SPI_transfer((u_int8_t)(arg >> 16));
LDA arg+2
JSR spi_tr ; SPI_transfer((u_int8_t)(arg >> 8));
LDA arg+3
JSR spi_tr ; SPI_transfer((u_int8_t)(arg));
; send CRC
LDA crc
ORA #1
JMP spi_tr ; SPI_transfer(crc|0x01); ...and return
; *** *** special version of the above, in case SDSC is byte-addressed, CMD17 and CMD24 only *** ***
ba_cmd:
; send command header
ORA #$40
JSR spi_tr ; SPI_transfer(cmd|0x40);
; precompute byte-addressed sector
LDA arg+3
ASL
STA tmpba+2 ; only two bytes actually used (+1...+2)
LDA arg+2
ROL
STA tmpba+1
LDA arg+1
ROL
; STA tmpba
; send argument
; LDA tmpba
JSR spi_tr
LDA tmpba+1
JSR spi_tr
LDA tmpba+2
JSR spi_tr
LDA #0 ; always zero as 512 bytes/sector
JSR spi_tr
; send CRC
LDA crc
ORA #1
JMP spi_tr ; SPI_transfer(crc|0x01); ...and return
; *** read R1 response *** return result in res and A
rd_r1:
PHX ; eeeeeeeek
LDX #8 ; u_int8_t i = 0, res1;
; keep polling until actual data received
r1_l:
LDA #$FF
JSR spi_tr
CMP #$FF
BNE r1_got ; while((res1 = SPI_transfer(0xFF)) == 0xFF)
DEX ; i++;
BNE r1_l ; if(i > 8) break;
r1_got:
STA res ; return res1; (also in A)
PLX
RTS
; *** read R7 response *** return result in res[]
rd_r7:
JSR rd_r1 ; res[0] = SD_readRes1();
CMP #2
BCS r7end ; if(res[0] > 1) return; {in case of error}
; read remaining bytes
LDX #1
r7loop:
LDA #$FF
JSR spi_tr
STA res, X ; res[ X ] = SPI_transfer(0xFF);
INX
CPX #5
BNE r7loop
r7end:
RTS
; *** init SD card in SPI mode ***
sd_init:
; ** SD_powerUpSeq is inlined here **
JSR cs_disable ; EEEEEEEEEEEEEEEKKKKKKKKKKKKK
LDX #220 ; ** may substitute SD logo load for this delay **
sdpu_dl:
NOP
DEX
BNE sdpu_dl ; delayMicroseconds(1000);
; continue with powerup sequence
LDX #9 ; for (u_int8_t i = 0; i < 10; i++) ; one less as cs_disable sends another byte
sd80c:
LDA #$FF
JSR spi_tr ; SPI_transfer(0xFF);
DEX
BNE sd80c ; this sends 80 clocks to synchronise
JSR cs_disable
; command card to idle
LDX #10
set_idle:
; ** SD_goIdleState is inlined here **
JSR cs_enable ; assert chip select
; send CMD0
STZ arg
STZ arg+1
STZ arg+2
STZ arg+3 ; ** assume CMD0_ARG is 0 *
LDA #CMD0_CRC
STA crc
LDA #CMD0
JSR sd_cmd ; SD_command(CMD0, CMD0_ARG, CMD0_CRC);
; read response
JSR rd_r1 ; u_int8_t res1 = SD_readRes1();
JSR cs_disable ; deassert chip select
; continue set_idle loop
LDA res
CMP #1
BEQ is_idle ; while((res[0] = SD_goIdleState()) != 0x01)
DEX ; cmdAttempts++;
BNE set_idle
LDX #IDLE_ERR ; *** ERROR 0 in red ***
JMP sd_fail ; if(cmdAttempts > 10) return SD_ERROR;
is_idle:
LDX #IDLE_ERR ; eeeek
JSR pass_x ; *** PASS 0 in white ***
; ** SD_sendIfCond is inlined here **
JSR cs_enable ; assert chip select
; send CMD8
STZ arg
STZ arg+1 ; CMD8_ARG upper 16 bits are zero
LDA #>CMD8_ARG
STA arg+2
LDA #<CMD8_ARG
STA arg+3
LDA #CMD8_CRC
STA crc
LDA #CMD8
JSR sd_cmd ; SD_command(CMD8, CMD8_ARG, CMD8_CRC);
; read response
JSR rd_r7 ; SD_readRes7(res);
JSR cs_disable ; deassert chip select
STZ sd_ver ; ### default (0) is modern SD card ###
LDA res
LDX #SDIF_ERR ; moved here
CMP #1 ; check valid response
BEQ sdic_ok
; ### if error, might be 1.x card, notify and skip to CMD58 or ACMD41 ###
LDX #OLD_SD ; ### message for older cards ###
STX sd_ver ; ### store as flag ###
JSR disp_code
LDY #13
JSR conio
BRA not_cmd8
sdptec:
JMP sd_fail ; if(res[0] != 0x01) return SD_ERROR;
sdic_ok:
JSR pass_x ; *** PASS 1 in white ***
; check pattern echo
LDX #ECHO_ERR ; *** ERROR 2 in red ***
LDA res+4
CMP #$AA
BNE sdptec ; if(res[4] != 0xAA) return SD_ERROR;
JSR pass_x ; *** PASS 2 in white ***
; ### jump here for 1.x cards ###
not_cmd8:
; attempt to initialize card *** could add CMD58 for voltage check
LDX #101 ; cmdAttempts = 0;
sd_ia:
; send app cmd
; ** res[0] = SD_sendApp() inlined here **
JSR cs_enable ; assert chip select
; send CMD55
STZ arg
STZ arg+1
STZ arg+2
STZ arg+3
STZ crc ; ** assume CMD55_ARG and CMD55_CRC are 0 **
LDA #CMD55
JSR sd_cmd ; SD_command(CMD55, CMD55_ARG, CMD55_CRC);
; read response
JSR rd_r1 ; u_int8_t res1 = SD_readRes1();
JSR cs_disable ; deassert chip select
LDA res ; return res1;
; if no error in response
CMP #2
BCS sa_err ; if(res[0] < 2) eeeeeeeeek
; ** res[0] = SD_sendOpCond() inlined here **
JSR cs_enable ; assert chip select
; send CMD55
LDA #ACMD41_ARG ; only MSB is not zero
STA arg
STZ arg+1
STZ arg+2
STZ arg+3
STZ crc ; ** assume rest of ACMD41_ARG and ACMD41_CRC are 0 **
LDA #ACMD41
JSR sd_cmd ; SD_command(ACMD41, ACMD41_ARG, ACMD41_CRC);
; read response
JSR rd_r1 ; u_int8_t res1 = SD_readRes1();
JSR cs_disable ; deassert chip select
sa_err:
LDA res ; return res1; (needed here in case of error)
BEQ apc_rdy ; while(res[0] != SD_READY);
; wait 10 ms
LDA #12
d10m:
INY
BNE d10m
DEC
BNE d10m ; delayMicroseconds(10000);
DEX ; cmdAttempts++;
BNE sd_ia
LDX #INIT_ERR ; *** ERROR 3 in red ***
JMP sd_fail ; if(cmdAttempts > 100) return SD_ERROR;
apc_rdy:
LDX #INIT_ERR
JSR pass_x ; *** PASS 3 in white ***
; ### old SD cards are always SC ###
LDA sd_ver
BNE sd_sc
; read OCR
; ** SD_readOCR(res) is inlined here **
JSR cs_enable ; assert chip select
; send CMD58
STZ arg
STZ arg+1
STZ arg+2
STZ arg+3
STZ crc ; ** assume CMD58_ARG and CMD58_CRC are 0 **
LDA #CMD58
JSR sd_cmd ; SD_command(CMD58, CMD58_ARG, CMD58_CRC);
; read response
JSR rd_r7 ; SD_readRes7(res); actually R3
JSR cs_disable ; deassert chip select
; check whether card is ready
LDX #READY_ERR ; *** ERROR 4 in red ***
BIT res+1 ; eeeeeeeeek ### will check CCS as well ###
BMI card_rdy ; eeeeeeeeek
JMP sd_fail ; if(!(res[1] & 0x80)) return SD_ERROR;
card_rdy: ; * SD_init OK! *
; ### but check whether standard or HC/XC, as the former needs asserting 512-byte block size ###
; ### if V is set then notify and skip CMD16 ###
BVS hcxc
; ### set 512-byte block size ###
sd_sc:
SEC
ROR sd_ver ; *** attempt of marking D7 for SDSC cards, byte-addressed!
JSR cs_enable ; assert chip select ### eeeeeeeeek
STZ arg
STZ arg+1 ; assume CMD16_ARG upper 16 bits are zero
LDA #>CMD16_ARG ; actually 2 for 512 bytes per sector
STA arg+2
; LDA #<CMD16_ARG
; STA arg+3
STZ arg+3 ; assume CMD16_ARG LSB is zero (512 mod 256)
STZ crc ; assume CMD16_CRC is zero
LDA #CMD16
JSR sd_cmd
; should I check errors?
JSR rd_r1
JSR cs_disable ; deassert chip select ###
LDX #READY_ERR ; ### display standard capacity message and finish ###
BRA card_ok
hcxc:
LDX #HC_XC ; ### notify this instead ###
card_ok:
JSR pass_x ; *** PASS 4 in white ***
; JMP vol_find ; find and mount volume * NEW
; ******************************************************
; *** mount volume, either from raw sectors or FAT32 *** NEW
; ******************************************************
vol_find:
LDX #MNT_RAW
JSR disp_code ; let's mount the volume
STZ arg ; first of all, try raw card format
STZ arg+1
STZ arg+2
STZ arg+3
LDX #>buffer ; temporary load address EEEEEEEEEEEEK
STX ptr+1
STZ ptr ; assume buffer is page-aligned (this is needed)
JSR ssec_rd ; read very first sector on device
JSR chk_head ; and look for a valid header
BCS try_fat ; not valid? try FAT32
JMP vol_ok ; if RAW formatted, this will be a valid header
try_fat:
LDX #MNT_FAT ; otherwise assume it's FAT32
JSR disp_code
; this should be the MBR, check it out *** some cards have no MBR, thus non-fatal
LDA buffer+$1C2 ; first partition type
CMP #$0C ; is it FAT32LBA?
BEQ mbr_ptype_ok
CMP #$0B ; or FAT32CHS?
BEQ mbr_ptype_ok
LDX #mbr_ptype-fat_msg
JSR fat_err
BRA vbr_chk ; try with VBR as this is non-fatal
mbr_ptype_ok:
LDA #$55
CMP buffer+$1FE ; boot signature
BNE mbr_bsig_bad
ASL
CMP buffer+$1FF ; second byte
BEQ mbr_bsig_ok
mbr_bsig_bad:
LDX #mbr_bsig-fat_msg
JSR fat_err
BRA vbr_chk ; try with VBR as this is non-fatal
mbr_bsig_ok:
; let's get the partition's first sector
LDY #0
LDX #3 ; four bytes to copy
vbr_sector:
LDA buffer+$1C6, Y ; first sector on first partition (little endian)
STA arg, X ; SD card expects block number BIG endian
INY
DEX
BPL vbr_sector
; read VBR
LDX #>buffer ; temporary load address
STX ptr+1
; STZ ptr ; assume buffer is page-aligned
JSR ssec_rd ; read first sector on partition
; check out VBR
vbr_chk:
LDA buffer ; first byte on VBR...
CMP #$E9 ; ...could be jump long...
BEQ vbr_jump_ok
CMP #$EB ; ...or short...
BNE vbr_jump_bad
LDA buffer+2
CMP #$90 ; ...followed by NOP
BEQ vbr_jump_ok
vbr_jump_bad:
LDX #vbr_jump-fat_msg
JMP fatal ; non-MBR errors ARE fatal
vbr_jump_ok:
LDA buffer+$15 ; media descriptor
CMP #$F0 ; 3.5" or other media
BEQ bpb_media_ok
CMP #$F8 ; also hard disk?
BEQ bpb_media_ok
LDX #bpb_media-fat_msg
JMP fatal
bpb_media_ok:
LDA buffer+$42 ; extended boot signature
AND #%11111110 ; ignore LSB
CMP #$28 ; $28 or $29
BEQ bpb_extbs_ok
LDX #bpb_extbs-fat_msg
JMP fatal
bpb_extbs_ok:
LDA buffer+$B ; bytes per sector
BNE bpb_bps_bad ; LSB must be zero for 512
LDA buffer+$C ; check MSB
CMP #2 ; 512 bytes/sector is the standard
BEQ bpb_bps_ok
bpb_bps_bad:
LDX #bpb_bps-fat_msg
JMP fatal
bpb_bps_ok:
; VBR appears compatible, let's compute directory sector
LDA buffer+$10 ; number of FATs (may assume it's one or two, at least a power of two)
fat_shift:
LSR
BEQ all_fats ; all shifts done (none if 1, once if 2)
ASL buffer+$24 ; multiply sectors per FAT
ROL buffer+$25
ROL buffer+$26
ROL buffer+$27
BRA fat_shift
all_fats:
LDA buffer+$24
CLC
ADC buffer+$E ; add reserved sectors
STA buffer+$24
LDA buffer+$25
ADC buffer+$F
STA buffer+$25
BCC fat_total
INC buffer+$26
BNE fat_total
INC buffer+$27
fat_total:
; we've computed the offset from VBR where the directory begins, let's compute absolute sector
LDY #0
LDX #3 ; four bytes to copy
CLC ; eeeeek
dir_sector: