forked from totalspectrum/spin2cpp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhypermemory.spin2
More file actions
2794 lines (2371 loc) · 117 KB
/
Copy pathhypermemory.spin2
File metadata and controls
2794 lines (2371 loc) · 117 KB
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
'--------------------------------------------------------------------------------------------------
{
Propeller 2 Memory Driver
=========================
This software contains a SPIN2 based interface to access HyperRAM/HyperFlash external memory
devices from Propeller 2 systems, as found on the Parallax P2-EVAL HyperRAM expansion board.
It works in conjuction with a lower level PASM2 based COG that provides access to the memory.
The driver manages multiple memory buses with multiple devices on each bus, shared by multiple
COGs. Different COGs can be accessing different buses simultaneously without blocking, and a
each COG can also launch multiple requests to memories on different buses in a non-blocking manner.
It supports singular reads & writes of bytes/words/longs as well as larger bursts plus fills and
copies. It includes methods for bitmap image transfers to/from and within external memories.
The HyperFlash devices can also be erased and re-programmed with the SPIN2 API provided.
Potentially, in the future, other external memory devices could be mapped into the same address
space to extend the capabilities further.
Some of the HyperRAM transfer code was inspired by some earlier original work by ozpropdev.
Revision history:
----------------
0.7b 22 SEP 2020 rogloh -initial BETA pre-release for Ahle2-
0.8b 27 SEP 2020 rogloh -initial BETA release-
}
'--------------------------------------------------------------------------------------------------
' MEMORY DRIVER - SPIN2 & Fastspin API
'--------------------------------------------------------------------------------------------------
CON
MAX_INSTANCES = 1 ' set this to the maximum number of HyperRAM buses used in your system
LAST_INSTANCE = MAX_INSTANCES - 1
'mailbox request types passed through to SPIN2 clients needing direct mailbox access
R_READBYTE = driver.R_READBYTE
R_READWORD = driver.R_READWORD
R_READLONG = driver.R_READLONG
R_READBURST = driver.R_READBURST
R_WRITEBYTE = driver.R_WRITEBYTE
R_WRITEWORD = driver.R_WRITEWORD
R_WRITELONG = driver.R_WRITELONG
R_WRITEBURST= driver.R_WRITEBURST
R_SETLATENCY= driver.R_SETLATENCY
R_GETLATENCY= driver.R_GETLATENCY
R_SETPARAMS = driver.R_SETPARAMS
R_GETPARAMS = driver.R_GETPARAMS
R_SETREG = driver.R_SETREG
R_GETREG = driver.R_GETREG
R_CONFIG = driver.R_CONFIG
R_DUMPSTATE = driver.R_DUMPSTATE
'memory device types
#0, T_HYPERFLASH, T_HYPERRAM_1, T_INVALID ' keep T_INVALID last, as more types get added
'memory device sizes (for 8MB or smaller devices, you can use 16MB with foldover)
#23, S_16MB, S_32MB, S_64MB, S_128MB, S_256MB
'flag bit masks and their values for COG
F_ATN = 1 ' flag mask included when COGATN notification is desired
F_LOCKED = 2 ' flag mask included when locked transfers are desired
F_PRIORITY = 4 ' flag mask included when priority servicing is desired
F_STALL = 8 ' flag mask included when RR COGs are to stall if accessing locked flash
'flag bit masks and their values for driver startup
F_FASTREAD = 1 << driver.FASTREAD_BIT ' flag mask used to enable SYSCLK/1 read speeds
F_FASTWRITE = 1 << driver.FASTWRITE_BIT ' (future SYSCLK/1 writes if/when supported?)
F_UNREGCLK = 1 << driver.UNREGCLK_BIT ' flag mask used to disable registering the clock pins
F_EXPANSION = 1 << driver.EXPANSION_BIT ' flag mask used to enable graphics expansion
'flag bit masks per bank
F_PROTFLAG = 1 << driver.PROT_BIT
F_FLASHFLAG = 1 << driver.FLASH_BIT
'error codes (including those passed back from the underlying driver)
ERR_INVALID_BANK = driver.ERR_INVALID_BANK ' bank requested is not mapped to a device
ERR_UNSUPPORTED = driver.ERR_UNSUPPORTED ' request is unsupported by the driver
ERR_INVALID_LIST = driver.ERR_INVALID_LIST ' invalid request format in list
ERR_ALIGNMENT = driver.ERR_ALIGNMENT ' word address and word aligned writes only in HyperFlash
ERR_BUSY = driver.ERR_BUSY ' flash bank is currently busy (COG access protected)
ERR_INVALID = -6 ' invalid arguments
ERR_NO_FREE_BUS = -7 ' ran out of driver resources (increase MAX_INSTANCES)
ERR_BAD_ADDR_MAP = -8 ' address alignment mapping error
ERR_NOT_CREATED = -9 ' bus not created
ERR_NO_COGS = -10 ' ran out of COGs
ERR_NO_DEVICES = -11 ' no devices mapped to the bus
ERR_ACTIVE = -12 ' driver already running for this bus, can't map new devices
ERR_STARTUP = -13 ' driver COG failed to respond after starting (driver crashed?)
ERR_TOO_SLOW = -14 ' can't send sufficient data at this P2 clock speed
ERR_INACTIVE = -15 ' driver is not running
ERR_ABORTED = -16 ' driver was shutdown
ERR_UNMAPPED = -17 ' address is unmapped or driver is not running
ERR_FLASH_ERASE = -18 ' flash erase failure
ERR_FLASH_PROGRAM= -19 ' flash programming failure
ERR_FLASH_TIMEOUT= -20 ' flash ready response timed out
ERR_FLASH_LOCKED = -21 ' flash access sector protected/locked
ERR_NOT_FLASH = -22 ' not a flash device address
ERR_NOT_RAM = -23 ' not a RAM device address
ERR_WOULD_BLOCK = -24 ' cancelled to avoid blocking during non-blocking request
ERR_NOT_SAME_BUS = -25 ' copy request not within same bus
ERR_NO_LOCK = -26 ' no LOCK available for driver
ERR_ADDR_IN_USE = -27 ' address already mapped and in use
ERR_MAILBOX_BUSY = -28 ' mailbox still has operation pending
ERR_UNLOCKED = -29 ' flash not currently exclusively protected
ERR_CANCELLED = -30 ' flash programming was cancelled
'driver operating frequency range in Hz
MINFREQ = 50_000_000
MAXFREQ = 400_000_000 ' good luck!
'startup latency of devices after reset
DEFAULT_HYPERRAM1_LATENCY = 6 ' default initial latency of HyperRAM (v1) after reset
DEFAULT_HYPERRAM2_LATENCY = 7 ' default initial latency of HyperRAM (v2) after reset
DEFAULT_HYPERFLASH_LATENCY = 16 ' default initial latency of HyperFLASH after reset
'misc
NUMCOGS = 8 ' keep this fixed
NUMBANKS = 16 ' number of banks per bus
ALLCOGS = $ff ' mask used when this setting applies to all COGs
ITEMSIZE = 8 ' size of list item in longs
MAILBOXSIZE = 24 ' mailboxes per instance counted in longs
'field identifiers
#0, FIELD_BURST, FIELD_DELAY, FIELD_PROTECTION, FIELD_FLAGS
'flash erase sizes / flags
ERASE_SECTOR_256K = $00040000 ' single sector erase, size=256kB
ERASE_ENTIRE_FLASH = $10000000 ' entire device erase
ERASE_NO_WAIT = $20000000 ' non-blocking erase, status MUST be polled by client
ERASE_SHOW_PROGRESS = $40000000 ' calls send(".") each second in polling loop during erase
'flash status register bits
FLASH_STATUS_DRB = 7 ' device ready bit
FLASH_STATUS_ESB = 5 ' erase status bit
FLASH_STATUS_PSB = 4 ' program status bit
FLASH_STATUS_SLSB = 1 ' sector locked bit
FLASH_STATUS_ESTAT = 0 ' sector erase status bit
FLASH_PROG_TIMEOUT = 2 ' in milliseconds
'.................................................................................................
OBJ
driver : "hyperdrv" ' HyperRAM/Flash driver
{{
.................................................................................................
initHyperDriver(basePin, ramAddr, flashAddr, flags, freq, cog)
The simplest way to initialize a driver for the P2-EVAL HyperRAM/HyperFlash breakout board. Once
invoked, all COGs can share both Hyper memories using basic round-robin request scheduling and the
largest burst size. Service parameters can be adjusted after this if other settings are desired,
unnecessary COGs can be removed from polling, default delays/latencies modified, etc.
Generic initialization for other implementations can instead be achieved by calling these methods:
mapHyperRam(...) and/or mapHyperFlash(...) ' must call either at least once before start
start(...) ' call only after mapping some device
setupQoS(...) ' can call anytime after mapping or after start
removeCogs(...) ' can call after mapping and start
etc
Arguments:
basePin - base pin number where HyperRAM/Flash module is fitted (0,16,32,48)
ramAddr - HyperRAM start address (or -1 if not desired), this gets aligned on a 16MB boundary
flashAddr - HyperFlash start address (or -1 if not desired), gets aligned on a 32MB boundary
flags - 0 (default), or can optionally include these driver COG configuration flags:
F_FASTREAD mask bit to enable sysclk/1 transfer rate reads instead of sysclk/2 on all banks
F_UNREGCLK mask bit to not register the clock output pin (experimental only at this stage)
freq - P2 clock frequency at which this driver will ultimately run, or 0 for current P2 frequency.
This value is used for determining the burst sizes applicable at this bus speed.
cog - either a COG ID for the driver to use, or -1 to allocate a new COG
Returns: driver's bus ID on success, or a negative error code
.................................................................................................
}}
PUB initHyperDriver(basePin, ramAddr, flashAddr, flags, freq, cog) : bus | profile
bus := ERR_NO_DEVICES
' map the HyperRAM and HyperFlash devices, and align 32MB HyperFlash to a 32MB block
if ramAddr <> -1
profile := getDefaultProfile(T_HYPERRAM_1, flags)
if (bus := mapHyperRam(ramAddr, S_16MB, basePin, basePin+12, basePin+8, {
} basePin+10, basePin+15, 0, profile)) < 0
return bus
if flashAddr <> -1
profile := getDefaultProfile(T_HYPERFLASH, flags)
if (bus := mapHyperFlash(flashAddr & $FE000000, S_32MB, basePin, basePin+13, basePin+9, {
} basePin+11, basePin+15, 0, profile)) < 0
return bus
' test for neither device mapped
if bus < 0
return bus
' start the driver, check for error
cog := start(bus, flags, freq, cog)
if cog +> NUMCOGS-1
return cog
{{
.................................................................................................
mapHyperRam(addr, size, datapin, cspin, clkpin, rwdspin, resetpin, burst, delayProfile)
or
mapHyperFlash(addr, size, datapin, cspin, clkpin, rwdspin, resetpin, burst, delayProfile)
Maps a Hyper memory device to a sequence of 16MB blocks starting at the provided 32 bit address.
Mapping can only be done before the driver is started. Up to 16 memory blocks can be mapped to
different devices on the same bus spanning a block range from $x0000000-$xF000000 where x is some
hexadecimal digit from $0-$F. All devices on the same bus must have unique values in address
bits 24-27 and the start address should be aligned to the size of the device (eg. a 32MB device
only starts on a 32MB boundary in the 4GB address space, etc).
Arguments:
addr - 32 bit start address of a range of external memory to map to a Hyper device.
size - size of device being mapped (S_16MB, S_32MB, ..., S_256MB)
datapin - P2 pin number of lowest data pin of Hyper bus (0, 8, 16, ..., 56)
cspin - P2 pin number of device's chip select signal
clkpin - P2 pin number of device's clock signal
rwdspin - P2 pin number of device's RWDS signal
resetpin - P2 pin number of device's reset signal (-1 if not used)
burst - maximum number of bytes that can be transferred to/from the device in one CS transaction
delayProfile - points to delay timing profile used by bank at the operating frequency
Note: The burst value is specified assuming a sysclk/1 transfer rate. With sysclk/2 transfers
(e.g all writes) the actual device burst will be half of this value. Transfers longer
than this burst size will be fragmented into multiple smaller bursts up to this size.
You should typically set this to 0 to to automatically let the driver assign it a default
burst size based on the device capablities, otherwise the default device burst size can
be overridden for tweaking performance if required.
Returns: Hyper bus ID on success, negative error values on failure
In the future with some more development and drivers, other buses could also be mapped such as
mapSpiRAM(addr, size, type, miso, mosi, cs, clk)
mapSpiFlash(addr, size, type, miso, mosi, cs, clk)
mapPSRAM(addr, size, type, ...)
..etc
This may allow software flexibility using different memories and types within the same infrastructure.
.................................................................................................
}}
PUB mapHyperRam(addr, size, datapin, cspin, clkpin, rwdspin, resetpin, burst, delayProfile) : bus
bus := mapHyperDevice(addr, T_HYPERRAM_1, size, datapin, cspin, clkpin, rwdspin, resetpin, burst, delayProfile)
PUB mapHyperFlash(addr, size, datapin, cspin, clkpin, rwdspin, resetpin, burst, delayProfile) : bus
bus := mapHyperDevice(addr, T_HYPERFLASH, size, datapin, cspin, clkpin, rwdspin, resetpin, burst, delayProfile)
' internally used mapping method
PRI mapHyperDevice(addr, memType, size, datapin, cspin, clkpin, rwdspin, resetpin, burst, delayProfile) : bus | device, pinInfo, i, latency, bank
' check for invalid arguments
if size < S_16MB or size > S_256MB or memType +> T_INVALID - 1
return ERR_INVALID
' check for unaligned address size mapping
bank := (addr >> 24) & $f
if size == S_32MB and (bank & 1)
return ERR_BAD_ADDR_MAP
elseif size == S_64MB and (bank & 3)
return ERR_BAD_ADDR_MAP
elseif size == S_128MB and (bank & 7)
return ERR_BAD_ADDR_MAP
elseif size == S_256MB and (bank & 15)
return ERR_BAD_ADDR_MAP
' create or return the existing bus ID for these pins
bus := create(datapin)
if bus < 0
return bus
' check if driver is already running, if so it's too late to map now
if driverCogs[bus] <> -1
return ERR_ACTIVE
' constrain the burst to remain within the 16 bit streamer limit and align to a flash page boundary
if burst +> $FFF0
burst := $FFF0
' always allow at least 4 byte transfers so all individual long read/write accesses work atomically
' we then double this to 8 to account for the slower sysclk/2 write which halves the burst
if burst and burst < 8
burst := 8
burst &= !7 ' also keep lower 3 bits free for flash protection use
' determine device information
device := (burst << 16) | size ' input delay is determine later at startup
' assign a default latency
if memType == T_HYPERFLASH
device |= F_FLASHFLAG
latency := DEFAULT_HYPERFLASH_LATENCY
else
' assume HyperRAM v1 device for now, until V2 devices supported
latency := DEFAULT_HYPERRAM1_LATENCY
' prepare control pins and latency for this device
pinInfo := (latency << 25) | (rwdspin & $3f) << 16 | (clkpin & $3f) << 8 | (cspin & $3f)
' check if any other devices on this bus conflict with the address chosen
repeat i from bank to bank + ((1<<(size - S_16MB)) - 1)
if devices[bus * 2 * NUMBANKS + i]
return ERR_ADDR_IN_USE
' replicate device information into all driver banks that span this device's size
repeat i from bank to bank + ((1<<(size - S_16MB)) - 1)
devices[bus * 2 * NUMBANKS + i] := device
devices[bus * 2 * NUMBANKS + NUMBANKS + i] := pinInfo
' retain delay timing profile pointer for later use during startup
profiles[bus*NUMBANKS + i] := delayProfile
' set bit7 of addrMap entry to keep this new mapping invalid until driver is enabled
' which prevents premature memory access by other COGs and a potential hang otherwise
addrMap[addr >> 28] := 128 + bus
' maintain all new device reset pins on this bus
if resetpin > -1 AND resetpin < 32
maskA[bus] |= 1 << resetpin
if resetpin > 31 AND resetpin < 64
maskB[bus] |= 1 << (resetpin - 32)
return bus+1
{{
................................................................................................
start(bus, flags, freq, cog)
Starts the driver COG for a Hyper memory bus. At least one device needs to be mapped to the bus
before this driver will be started.
NOTE: Some setup parameters are global so only one COG should spawn each driver at any time.
If multiple drivers are being spawned by different COGs this needs to be managed with some
type of co-ordination or locks during critical provisioning steps that share common data.
Arguments:
bus - Hyper bus ID for the bus to be started (1-based)
flags - optional flags can be enabled:
F_FASTREAD mask bit to enable sysclk/1 transfer rate reads instead of sysclk/2
F_FASTWRITE mask bit to enable sysclk/1 transfer rate writes instead of sysclk/2
F_UNREGCLK mask bit to unregister the clock output pin
freq - operating frequency if it will be different to current P2 frequency, 0 otherwise
cog - COG ID to use for the driver or -1 to allocate a new COG
Returns new driver COG number or negative error values
................................................................................................
}}
PUB start(bus, flags, freq, cog) : driverCog | timeout, i, j, latency, burst, device, delay, timing, startAddr, id
' externally exposed bus IDs are 1 based
bus--
' check for invalid parameters
if bus +> LAST_INSTANCE
return ERR_INVALID
' check the bus was created, if not created we don't have any devices mapped and can't start yet
if freeDrivers & (1 << bus)
return ERR_NO_DEVICES
' check for the driver already running on this bus, if so don't start again just return its COG id
if driverCogs[bus] <> -1
return driverCogs[bus]
' determine system frequency we will operate at and check it falls within an allowed range
if freq == 0
freq := clkfreq
if freq +< MINFREQ or freq +> MAXFREQ
return ERR_INVALID
' find and configure all devices on bus
repeat i from 0 to NUMBANKS - 1
' skip unprovisioned banks
if (device := devices[bus * 2 * NUMBANKS + i]) == 0
next
' compute the default burst size if it was unspecified
burst := device >> 16
latency:= devices[bus * 2 * NUMBANKS + i + NUMBANKS] >> 25
if burst == 0 ' automatic max burst size selected
if device & F_FLASHFLAG
burst := $FFF0 ' HyperFlash burst read size is only limited by streamer and page boundary
else ' but for HyperRAM we need to compute a max size for the 4us interval
burst := getMaxBurst(freq, 4, latency)
' check if there is at least enough time to send a 32 bit long at the lower transfer rate
if burst < 8
return ERR_TOO_SLOW
' determine the read input delay for the operating frequency of this device using its profile
delay := lookupInputDelay(freq, profiles[bus * NUMBANKS + i])
' update the device information in each bank
devices[bus * 2 * NUMBANKS + i] := (burst << 16) | ((delay & $f) << 12) | (device & $fff)
' setup the startup parameters to pass to driver COG
i := (bus * NUMCOGS + cogid()) * ITEMSIZE
startupParams[i+0]:= clkfreq 'operating P2 frequency
startupParams[i+1]:= flags 'optional startup flags
startupParams[i+2]:= maskA[bus] 'port A (lower 32 pins) reset mask
startupParams[i+3]:= maskB[bus] 'port B (upper 32 pins) reset mask
startupParams[i+4]:= busBasePin[bus] 'data bus pin number
startupParams[i+5]:= @devices[bus * 2 * NUMBANKS]'ptr to all per bank params and pin settings
startupParams[i+6]:= @cogList[bus * NUMCOGS] 'ptr to all per COG settings
startupParams[i+7]:= mailboxAddr[bus] 'mailbox base address for this driver
' if no COGs are setup yet then just use a default with all COGs enabled and accessing the
' memory via round robin polling, using the maximum default burst size for the devices
repeat i from 0 to NUMCOGS
if i == NUMCOGS
setupQoS(ALLCOGS, bus+1, -1, 0, 0)
elseif cogList[bus * NUMCOGS + i] <> 0 'exit if something was setup prior to starting
quit
' auto assign the COG if desired
if cog +> NUMCOGS -1
cog := 16
' launch the PASM2 driver COG and return an error if no COGs were free
startAddr := driver.getDriverAddr()
driverCog := coginit(cog, startAddr, @startupParams + (bus * NUMCOGS + cogid()) * ITEMSIZE * 4)
if driverCog +> NUMCOGS - 1
return ERR_NO_COGS
' wait until COG has completely started, or timeout if driver unresponsive after 50ms
timeout := getct()
repeat
if getct() - timeout +> freq / 20
cogstop(driverCog)
return ERR_STARTUP ' should not occur, someone/something killed it?
until startupParams[(bus * NUMCOGS + cogid()) * ITEMSIZE] == 0
' cache the latest setup mailbox addresses per COG to optimize for single driver instance
repeat i from 0 to NUMCOGS - 1
mailboxAddrCog[i] := @mailboxes[0] + i * 12
' enable the address mapping for this bus now the driver is running
repeat i from 0 to 15
if addrMap[i] == bus + 128
addrMap[i] -= 128 ' clear top bit to enable mapping
' save the COG ID of driver for this bus
driverCogs[bus] := driverCog
'Loop through bus devices and setup a default device latency in case it had been changed
'prior to this driver restarting, and if its reset pin was not enabled. An obscure case.
repeat i from 0 to NUMBANKS-1
device := devices[bus * 2 * NUMBANKS + i]
if device
if device & F_FLASHFLAG
repeat j from 0 to 15
if addrMap[j] == bus ' find any address mapped to this bus
setFlashLatency((j<<28)+(i<<24), DEFAULT_HYPERFLASH_LATENCY)
quit
else
repeat j from 0 to 15
if addrMap[j] == bus ' find any address mapped to this bus
' assume Version 1 HyperRAM for now
latency := DEFAULT_HYPERRAM1_LATENCY
' check for Version 2 HyperRAM
if (readRamIR((j<<28)+(i<<24), 1, 0) & $ff) == 1 ' read IR1 version byte
' if V2 HyperRAM, check operating frequency
if ((flags & (F_FASTREAD|F_FASTWRITE)) && freq > 200000000)
latency := DEFAULT_HYPERRAM2_LATENCY
setRamLatency((j<<28)+(i<<24), latency)
quit
{{
................................................................................................
setupQoS(cogmask, bus, cogburst, flags, priority)
Configures one or more COGs to access the device(s) on a bus, with the given operating settings.
This API is used to control the quality of service (QoS) for all the COGs setup to be serviced.
This operation can occur before or after the driver is started, but after the first device mapping.
If called before starting the driver, it will override the default and only allow the configured COGs
to access the memory when the driver starts. If only called after the driver starts up then all COGs
will default to being enabled once the driver starts, and the previous COG settings will remain intact
for any COGs not listed in the cogmask.
Arguments:
cogMask - 8 bit mask of all COGIDs to modify (eg. %11111111 for all COGs, or %00000001 = COG0 only)
bus - Hyper bus ID of driver being configured (1-based)
cogBurst - maximum burst size in bytes allowed by COG (applied in addition to any device burst limit)
NOTE: if this COG's burst size is set to less than 4 the COG will be removed from polling!
Pass in -1 if the COG transfer is only to be limited by the accessed device's own burst limit
flags - These flags indicates how to service the COG and whether it is priority or round-robin polled.
- set F_ATN flag for an additional notification with COGATN on completion of request
- set F_PRIORITY flag to enable priority polling, otherwise round-robin polling is used
- set F_LOCKED flag to complete full transactions before any other COGs are serviced
- set F_STALL flag to stall round robin COGs accessing flash that is locked by another COG
priority - priority of COG
The lowest 3 bits of this value indicate a polling priority level from 0-7 which are used
when F_PRIORITY is optionally set. Priority 0 is the lowest, 7 is the highest.
If multiple COGs share the same priority, the COG with the lowest ID is serviced first.
For example a video COG would typically want to make itself the highest priority polled
COG and enable the F_LOCKED flag for example to maximize its performance while keeping
all other COGs at lower priority with their F_LOCKED flags cleared and limiting the bursts
for other COGs to keep the service latency under control.
Returns: 0 on success, or negative error values on failure
................................................................................................
}}
PUB setupQoS(cogMask, bus, cogBurst, flags, priority) : r | cog, setflags, m
' externally exposed bus IDs are 1 based
bus--
' check for invalid arguments
if bus +> LAST_INSTANCE or cogMask == 0
return ERR_INVALID
' check for bus not created
if freeDrivers & (1 << bus)
return ERR_NOT_CREATED
' construct flags
setflags := 1 ' set any bit in the low byte to indicate that something has now been provisioned
if flags & F_ATN
setflags |= 1 << driver.NOTIFY_BIT
if flags & F_LOCKED
setflags |= 1 << driver.LOCKED_BIT
if flags & F_PRIORITY
setflags |= 1 << driver.PRIORITY_BIT ' setup this COG as a priority polled COG in driver
else ' round robin polled COG
if flags & F_STALL ' test whether to stall or return an error if flash gets locked out
priority := 0 ' zero priority for a round-robin polled COG that stalls
else
priority := 1 ' non-zero priority for a round-robin polled COG that returns busy
' limit burst sizes to fit 16 bit streamer limit and also align to flash page boundary
if cogBurst +> $FFF0
cogBurst := $FFF0
elseif cogBurst +< 4 ' we need to send at least 4 bytes for supporting long accesses
cogBurst := 0 ' removes COG from polling
' update the COGs affected
repeat cog from 0 to NUMCOGS - 1
if cogMask & (1 << cog)
cogList[bus * NUMCOGS + cog] := (cogBurst << 16) + ((priority & $7) << 12) + setflags
' if driver is already running, also trigger it to update with these new COG settings
if driverCogs[bus] <> -1
m := mailboxAddr[bus] + driverCogs[bus]*12
repeat until LOCKTRY(driverlock)
long[m] := R_CONFIG + cogid()
repeat while long[m] < 0
LOCKREL(driverlock)
return 0
{{
................................................................................................
removeCogs(cogMask, bus)
Removes one or more COGs from accessing a Hyper memory bus if they are known to not require
access to Hyper memory. This will reduce polling overhead and provide better request access
fairness to other RR COGs if their load is equal.
Arguments:
cogMask - 8 bit mask of all COGs IDs which will be prevented from being polled/serviced.
bus - ID of Hyper bus being configured (1-based)
Returns: 0 on success, or negative error values on failure
................................................................................................
}}
PUB removeCogs(cogMask, bus) : r
r := setupQoS(cogMask, bus, 0, 0, 0)
' internally used bus creation method returns new bus ID for new data pin(s) or existing bus ID
PRI create(baseDataPin) : id
if driverlock < 0
driverlock := LOCKNEW()
if driverlock < 0
return ERR_NO_LOCK
' check for invalid arguments
if baseDataPin & !$3f
return ERR_INVALID
' check if another bus already exists on the same data pins, if so return that one
if busMap[baseDataPin >> 3] +< MAX_INSTANCES
return busMap[baseDataPin >> 3]
' start of critical section, if a single COG is doing the entire setup, this is not an issue
repeat until LOCKTRY(driverlock)
' find a free driver instance
repeat id from 0 to MAX_INSTANCES
if id == MAX_INSTANCES ' no free driver instances
LOCKREL(driverlock)
return ERR_NO_FREE_BUS
if freeDrivers & (1 << id)
quit
' assign new driver instance
freeDrivers &= !(1 << id)
' end of critical section
LOCKREL(driverlock)
' init data structures for new instance
busMap[baseDataPin >> 3] := id
mailboxAddr[id] := @mailboxes[id * MAILBOXSIZE]
driverCogs[id] := -1
busBasePin[id] := baseDataPin
maskA[id] := 0
maskB[id] := 0
longfill(@devices[id * 2 * NUMBANKS], 0, NUMBANKS) 'clear out device params for all banks
longfill(@devices[id * 2 * NUMBANKS+NUMBANKS], -1, NUMBANKS)'invalidate pin infomation for all banks
longfill(@cogList[id * NUMCOGS], 0, NUMCOGS) 'clear out COG service parameters
longfill(@mailboxes[id * MAILBOXSIZE], 0, MAILBOXSIZE) 'clear out mailboxes for this bus
{{
................................................................................................
shutdown(bus, waitUntilIdle)
Free up an already created/started Hyper bus, killing any driver COG and removes all address to
device mappings for this bus.
Arguments:
bus - Hyper bus ID to be shut down and released (1-based)
waitUntilIdle - set true to first wait for all existing requests on this bus to complete first
set false to shutdown immediately regardless of pending requests
Returns: 0 on success, or a negative error code
................................................................................................
}}
PUB shutdown(bus, waitUntilIdle) : i | m
' externally exposed bus IDs are 1 based
bus--
' check for invalid arguments
if bus +> LAST_INSTANCE
return ERR_INVALID
' check for bus not created
if freeDrivers & (1<<bus)
return 0 ' nothing to do
' search through the addrMap and remove all references to this bus to stop any new accesses
repeat i from 0 to 15
if addrMap[i] == bus
addrMap[i] := 255
' clear out single instance addresses to unmap
if MAX_INSTANCES == 1
longfill(@mailboxAddrCog[0], 0, NUMCOGS)
' unbind data bus group from the driver
repeat i from 0 to 7
if busMap[i] == bus
busMap[i] := 255
waitms(2) ' small delay to settle down any last request being prepared
' check if driver COG is already spawned and if so then kill it
if driverCogs[bus] <> -1
if waitUntilIdle 'wait until all mailbox results are fully idle before shutdown
repeat i from 0 to NUMCOGS - 1
m := getMailboxAddr(bus+1, i)
repeat while long[m][0] < 0
' kill the driver COG
cogstop(driverCogs[bus])
driverCogs[bus] := -1
' clear out any pending mailbox entries by aborting
repeat i from 0 to NUMCOGS - 1
m := getMailboxAddr(bus+1, i)
if long[m][0] < 0
long[m][0] := -ERR_ABORTED
' free up this driver instance, TODO also return driverlock if no drivers left?
freeDrivers |= (1 << bus)
return 0
{{
................................................................................................
getMailboxAddr(bus, cog)
Obtains the address of the first mailbox slot for the given COG to access devices on the given bus.
Arguments:
bus - ID of Hyper bus to access (1-based)
cog - ID of cog whose mailbox slot address is returned
Returns: mailbox address in HUB RAM or negative error code
................................................................................................
}}
PUB getMailboxAddr(bus, cog) : addr
bus--
if bus +> LAST_INSTANCE OR cog +> NUMCOGS - 1
return ERR_INVALID
addr := mailboxAddr[bus] + cog*12
{{
................................................................................................
getDriverCogID(bus)
Obtains the COG ID of the driver managing a given Hyper bus.
Arguments:
bus - Hyper bus ID of driver whose cog ID is desired (1-based)
Returns:
ID of cog or negative error code
................................................................................................
}}
PUB getDriverCogID(bus) : cog
cog := (--bus +> LAST_INSTANCE) ? ERR_INVALID : driverCogs[bus]
{{
................................................................................................
getDriverLockID()
Obtains the HUB lock being used by the memory driver, other PASM2 COGs could share this same lock.
Arguments:
bus - not used at the moment, as driver shares a single lock for all Hyper buses but this
argument is included for future potential use with other bus types
Returns:
ID of driver's HUB lock or negative error code
................................................................................................
}}
PUB getDriverLockID(bus) : lock
lock := (driverlock < 0) ? ERR_INACTIVE : driverlock
{{
................................................................................................
getMaxBurst(frequency, cs_interval, latency)
Computes a maximum number of bytes that can be transferred using HyperRAM at sysclk/1 operation
within the maximum Chip Select low time interval at the given frequency and device latency.
Note: When sysclk/2 transfers are done, the burst size will be halved automatically by the driver
operating at the reduced rate. The size returned here should be used for all provisioning.
Arguments:
frequency - P2 operating frequency in Hz
cs_interval - maximum allowed CS low interval time (in microseconds)
latency - maximum number of latency clocks in use by the device
Returns: maximum burst size or 0 if not enough clocks to send any data in the interval provided.
................................................................................................
}}
PUB getMaxBurst(frequency, cs_interval, latency) : clocks
' compute total clock cycles available in CS low interval allowing for overhead and latency
clocks := (frequency / 1000000) * cs_interval - driver.OVERHEAD_CYCLES - (((latency<<1) + 2) << 2)
' assume sysclk/1 operation and round down to nearest 2*long (for sysclk/2)
return (clocks < 0) ? 0 : (clocks & !7)
{{
................................................................................................
setDelayFrequency(addr, freq, tempK)
Sets the frequency of operation and updates the input delay parameter for a given device's bank(s)
Uses the input timing/temperature profile for the device that operates at the address.
Arguments:
addr - (any) address of device to setup input delay
freq - current operating frequency in Hz
tempK - (future use) temperature in Kelvin. Pass 0 for now to ignore.
Returns: delay to use or negative error code
................................................................................................
}}
PUB setDelayFrequency(addr, freq, tempK) : r | bus, bank, delay
bus := addrMap[addr >> 28]
bank := (addr >> 24) & $f
if bus +> LAST_INSTANCE
return ERR_INVALID
delay := lookupInputDelay(freq, profiles[bus * NUMBANKS + bank])
return setDelay(addr, delay)
{{
................................................................................................
setDelayProfile(addr, profile)
Sets up the frequency-delay timing profile for a given device on a bus.
Arguments:
addr - (any) address of device to setup input delay timing profile
profile - pointer to address of profile stored in HUB RAM
Returns: 0 for success or negative error code
Note : this is just to associate a custom delay profile for a device, with no change to the
input delay actually used in the driver until setDelayFrequency is called.
................................................................................................
}}
PUB setDelayProfile(addr, profile) : r | bus, bank, size, i
bus := addrMap[addr >> 28]
if bus +> LAST_INSTANCE
return ERR_INVALID
bank := getStartBank(bus, addr)
if bank < 0
return ERR_INVALID
size := devices[bus * 2 * NUMBANKS + bank] & $ff
' configure parameter over all spanned banks and update local storage
repeat i from bank to bank + size - S_16MB
profiles[bus * NUMBANKS + i] := profile
return 0
' internal method to find input delay from frequency using given profile
PRI lookupInputDelay(freq, profile) : delay
delay := long[profile][0]
repeat while long[profile][1]
if freq +< long[profile][1]
quit
profile += 4
delay++
{{
................................................................................................
getResult(bus, nonBlocking)
Gets the status/result of the last (or current) operation for the calling COG's mailbox.
Arguments:
bus - which bus mailbox is being checked (1-based)
nonBlocking - flag to set true if we don't want to wait for a result if it is still pending
Returns: status of last mailbox operation by this COG on the given bus or a negative error code.
It will return ERR_WOULD_BLOCK if mailbox is still running when non-blocking flag is true
................................................................................................
}}
PUB getResult(bus, nonBlocking) : m
if --bus +> LAST_INSTANCE
return ERR_INVALID
m := getMailboxAddr(bus+1, cogid())
' wait for result in case a list is running
repeat while long[m] < 0
if nonBlocking ' exit if we don't want to block
return ERR_WOULD_BLOCK
return -long[m] ' return whether it was an error case or not
{{
................................................................................................
readByte(addr)
readWord(addr)
readLong(addr)
Read access methods (coded separately for speed for each data access size)
Arguments:
addr - the external address to read from
Returns: data value at that external address or a negative error code
Reads using readLong cannot differentiate error cases vs real data without also checking for
the last error code explicity using getResult (if desired), while readByte/readWord can still be
tested for errors if they are negative (bit 31 is 1).
This API was designed for faster speed assuming read address errors are avoided, and this approach
conveniently allows the returned result to remain assignable in expressions like this:
x := mem.readLong(addr) + 120
If you do wish to check for errors on the long sized reads you can always use read() instead.
................................................................................................
}}
PUB readByte(addr) : r | m
if MAX_INSTANCES == 1 ' optimization for single instance, everything mapped to one bus
m := mailboxAddrCog[cogid()] ' get mailbox base address for this COG
if m == 0 ' prevent hang if driver is not running
return ERR_UNMAPPED
else ' multiple buses, need to lookup address to find mailbox for bus
m := addrMap[addr>>28]
if m +> LAST_INSTANCE ' if address not mapped, exit
return ERR_UNMAPPED
m := mailboxAddr[m] + cogid()*12
if long[m] < 0
return ERR_MAILBOX_BUSY
long[m][2] := 0 ' just read only - no RMW mask
long[m] := R_READBYTE + (addr & $fffffff) ' trigger a read request in the mailbox
repeat
if not r := long[m] ' test mailbox for zero
return long[m][1] ' and return data result
while r < 0 ' or loop until error
return -r ' return negated value as error code
PUB readWord(addr) : r | m
if MAX_INSTANCES == 1
m := mailboxAddrCog[cogid()]
if m == 0
return ERR_UNMAPPED
else
m := addrMap[addr>>28]
if m +> LAST_INSTANCE
return ERR_UNMAPPED
m := mailboxAddr[m] + cogid()*12
if long[m] < 0
return ERR_MAILBOX_BUSY
long[m][2] := 0
long[m] := R_READWORD + (addr & $fffffff)
repeat
if not r := long[m]
return long[m][1]
while r < 0
return -r
PUB readLong(addr) : r | m
if MAX_INSTANCES == 1
m := mailboxAddrCog[cogid()]
if m == 0
return ERR_UNMAPPED
else
m := addrMap[addr>>28]
if m +> LAST_INSTANCE
return ERR_UNMAPPED
m := mailboxAddr[m] + cogid()*12
if long[m] < 0
return ERR_MAILBOX_BUSY
long[m][2] := 0
long[m] := R_READLONG + (addr & $fffffff)
repeat
if not r := long[m]
return long[m][1]
while r < 0
return -r
{{
................................................................................................
read(dstHubAddr, srcAddr, count)
readList(dstHubAddr, srcAddr, count, listPtr)
Read burst method to read a range of external memory bytes into HUB RAM.
Arguments:
dstHubAddr - HUB address where data is to be read
srcAddr - source address in external memory to read from
count - number of bytes to read
listPtr - (readList only) pointer to listItem to populate in HUB RAM
Returns: 0 for success or a negative error code
for readList only - returns address of next link pointer field in list item
Note: transfer length is not validated, you can fill the entire HUB RAM multiple times with this
Address wrapping will occur in the accessed device if the count plus the srcAddr exceeds
the last address in the device.
................................................................................................
}}
PUB read(dstHubAddr, srcAddr, count) : r | m
if count == 0 ' don't even bother reading if count == 0
return 0
if MAX_INSTANCES == 1 ' optimization for single instance, everything mapped to single bus
m := mailboxAddrCog[cogid()]
if m == 0
return ERR_UNMAPPED
else ' multiple buses, need to lookup address to find mailbox for bus
m := addrMap[dstHubAddr>>28]
if m +> LAST_INSTANCE ' if address not mapped, exit
return ERR_UNMAPPED
m := mailboxAddr[m] + cogid()*12 ' compute COG's mailbox for this bus
if long[m] < 0
return ERR_MAILBOX_BUSY
long[m][2] := count
long[m][1] := dstHubAddr
long[m] := R_READBURST + (srcAddr & $fffffff) ' trigger burst read operation
repeat
r := long[m]
while r < 0
return -r 'return success or error
' A request list capable form of read for preparing read bursts in list items
PUB readList(dstHubAddr, srcAddr, count, listPtr) : r | m
if listPtr == 0 ' call immediately without a list
return read(dstHubAddr, srcAddr, count)
'otherwise build the list
long[listPtr][0] := R_READBURST + (srcAddr & $fffffff)
long[listPtr][1] := dstHubAddr
long[listPtr][2] := count
long[listPtr][3] := 0
return listPtr + 12
{{
................................................................................................
readReg(addr, regAddr)
Read a register from the external memory device
Arguments:
addr - (any) memory address of the device to access
regAddr - word address of the device register to read
Returns: 16 bit register data or a negative error code
Note: regAddr is a 16 bit address in the device, not a byte address
................................................................................................
}}
PUB readReg(addr, regAddr) : r
return readRaw(addr, $E000 + (regAddr >> 19), (regAddr & 7) + ((regAddr>>3)<<16))
' internally used register read method