; Copyright 2000 Pace Micro Technology plc
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
;     http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;
; > Copro15ops

;macros for Coprocessor #15 operations (configuration), which run-time detect
;and cater for ARM 6,7,8,A (A=StrongARM).
;Routines detect which ARM directly by reading ARM ID register (avoids memory reads).

; 24-01-96 MJS Created
; 07-10-96 MJS Updated for proper ARM 810 support (not needed for RO 3.70)
; 10-03-97 MJS A few additions for chocolate flavour screen handling (possible
;              Domain and FSR register use) in Phoebe OS
; 05-02-09 JL  Disable ARM_flush_* when compiling OS for HAL. OS now supports
;              too many ARM versions for cache/TLB flushing to be implemented in
;              simple macros.

        GET     Hdr:CPU.Arch

ARM_config_cp        CP 15  ;coprocessor number for configuration control

ARM_ID_reg           CN  0  ;processor ID
ARM_control_reg      CN  1  ;control
ARM_tbase_reg        CN  2  ;translation base (MMU)
ARM_domain_reg       CN  3  ;domain access control (MMU)
ARM_FSR_reg          CN  5  ;Fault status reg (MMU, read only on ARM 6/7)
ARM_FAR_reg          CN  6  ;Fault address reg (MMU, read only on ARM 6/7)

ARM67_TLBflush_reg   CN  5  ;TLB flush, ARMs 6 or 7
ARM67_TLBpurge_reg   CN  6  ;TLB purge entry, ARMs 6 or 7
ARM67_cacheflush_reg CN  7  ;cache flush, ARMs 6 or 7
ARMv3_TLBflush_reg   CN  5  ;TLB flush, ARMs 6 or 7
ARMv3_TLBpurge_reg   CN  6  ;TLB purge entry, ARMs 6 or 7
ARMv3_cacheflush_reg CN  7  ;cache flush, ARMs 6 or 7

ARM8A_cache_reg      CN  7  ;cache operations, ARMs 8 or StrongARM
ARM8A_TLB_reg        CN  8  ;TLB operations, ARMs 8 or StrongARM
ARMv4_cache_reg      CN  7  ;cache operations, ARMs 8 or StrongARM
ARMv4_TLB_reg        CN  8  ;TLB operations, ARMs 8 or StrongARM

ARM8_cacheLD_reg     CN  9  ;cache lock-down, ARM 8
ARM8_TLBLD_reg       CN 10  ;TLB lock-down, ARM 8

ARM8_CTC_reg         CN 15  ;Clock and test configuration

ARMA_TCI_reg         CN 15  ;Test,Clock and Idle control

;so that bleedin' AASM will accept the general value for MCR CRm field
C0  CN  0
C1  CN  1
C2  CN  2
C3  CN  3
C4  CN  4
C5  CN  5
C6  CN  6
C7  CN  7
C8  CN  8
C9  CN  9
C10 CN 10
C11 CN 11
C12 CN 12
C13 CN 13
C14 CN 14
C15 CN 15


;
; ----------------- all ARMs ---------------------------------------------
;

;set MMU translation base
        MACRO
        ARM_MMU_transbase $reg,$cond
        MCR$cond ARM_config_cp,0,$reg,ARM_tbase_reg,C0,0
        MEND

;set MMU domain access register
        MACRO
        ARM_MMU_domain $reg,$cond
        MCR$cond ARM_config_cp,0,$reg,ARM_domain_reg,C0,0
        MEND

;read control register
        MACRO
        ARM_read_control $reg,$cond
        MRC$cond ARM_config_cp,0,$reg,ARM_control_reg,C0,0
        MEND

;set control register
        MACRO
        ARM_write_control $reg,$cond
        MCR$cond ARM_config_cp,0,$reg,ARM_control_reg,C0,0
        MEND

;read MMU/external fault status
        MACRO
        ARM_read_FSR $reg,$cond
        MRC$cond ARM_config_cp,0,$reg,ARM_FSR_reg,C0,0
        MEND

;set MMU/external fault status
        MACRO
        ARM_write_FSR $reg,$cond
        MCR$cond ARM_config_cp,0,$reg,ARM_FSR_reg,C0,0
        MEND

;read MMU/external fault address
        MACRO
        ARM_read_FAR $reg,$cond
        MRC$cond ARM_config_cp,0,$reg,ARM_FAR_reg,C0,0
        MEND

; set MMU/external fault address
        MACRO
        ARM_write_FAR $reg,$cond
        MCR$cond ARM_config_cp,0,$reg,ARM_FAR_reg,C0,0
        MEND

;read ID register to register $id
;bits 15:12 of returned ID will be 0,7,8,10 for ARM 6,7,8,A
        MACRO
        ARM_read_ID $id,$cond
        MRC$cond ARM_config_cp,0,$id,ARM_ID_reg,C0,0
        MEND

;read cache type register to register $type
        MACRO
        ARM_read_cachetype $type,$cond
        MRC$cond ARM_config_cp,0,$type,ARM_ID_reg,C0,1
        MEND

;read ARM 'number' (6,7,8,&A currently) into $num
        MACRO
        ARM_number $num
        ARM_read_ID $num
        ANDS     $num,$num,#&F000
        MOVEQ    $num,#&6000       ;catch and correct daft ARM 6 ID layout
        MOV      $num,$num,LSR #12
        MEND

; check if we're on an ARM 6 - EQ if so
        MACRO
        ARM_6       $tmp, $cond
        ARM_read_ID $tmp, $cond
        TST$cond    $tmp, #&F000
        MEND

;check whether running on emulator - this is subject to change. ARMs before
;ARM 920 ignore op2, and will definitely return something other than "1".
;ARM 920 onwards use op2 0 and 1 - behaviour with other op2 values is as yet
;unknown...
        MACRO
        ARM_on_emulator $tmp,$cond
        MRC$cond ARM_config_cp,0,$tmp,ARM_ID_reg,C0,7
        TEQ$cond $tmp,#1
        MEND

 [ :LNOT: HAL
;flush whole TLB (both data and instruction for StrongARM)
;trashes $temp
        MACRO
        ARM_flush_TLB $temp
        ARM_read_ID $temp
        AND      $temp,$temp,#&F000
        CMP      $temp,#&8000   ;ARM 8?
        CMPNE    $temp,#&A000   ;or StrongARM?
        MCRNE    ARM_config_cp,0,R0,ARM67_TLBflush_reg,C0,0
        MCREQ    ARM_config_cp,0,R0,ARM8A_TLB_reg,C7,0
        MEND

;flush whole cache (both data and instruction for StrongARM),
;without worrying about any cache cleaning
;trashes $temp
        MACRO
        ARM_flush_cache $temp
        ARM_read_ID $temp
        AND      $temp,$temp,#&F000
        CMP      $temp,#&8000   ;ARM 8?
        CMPNE    $temp,#&A000   ;or StrongARM?
        MCRNE    ARM_config_cp,0,R0,ARM67_cacheflush_reg,C0,0
        MCREQ    ARM_config_cp,0,R0,ARM8A_cache_reg,C7,0
        MEND

;flush whole TLB and cache (both data and instruction for StrongARM),
;without worrying about any cache cleaning
;trashes $temp
        MACRO
        ARM_flush_cacheandTLB $temp
        ARM_read_ID $temp
        AND      $temp,$temp,#&F000
        CMP      $temp,#&8000   ;ARM 8?
        CMPNE    $temp,#&A000   ;or StrongARM?
        MCRNE    ARM_config_cp,0,R0,ARM67_cacheflush_reg,C0,0
        MCRNE    ARM_config_cp,0,R0,ARM67_TLBflush_reg,C0,0
        MCREQ    ARM_config_cp,0,R0,ARM8A_cache_reg,C7,0
        MCREQ    ARM_config_cp,0,R0,ARM8A_TLB_reg,C7,0
        MEND
 ]

;
; -------------- ARM 6,7 only --------------------------------------------
;

;flush cache
        MACRO
        ARM67_flush_cache $cond
        MCR$cond ARM_config_cp,0,R0,ARM67_cacheflush_reg,C0,0
        MEND

;flush TLB
        MACRO
        ARM67_flush_TLB $cond
        MCR$cond ARM_config_cp,0,R0,ARM67_TLBflush_reg,C0,0
        MEND

;flush TLB entry, virtual address in $reg
        MACRO
        ARM67_flush_TLBentry $reg,$cond
        MCR$cond ARM_config_cp,0,$reg,ARM67_TLBpurge_reg,C0,0
        MEND

;
; -------------- ARM 810 only ----------------------------------------------
;

 [ {FALSE}

;turn off branch prediction
; - the forced mispredicted branch ensures that the predictor is trapped in
;   this code segment when turned off
; - corrupts $temp and status flags
;
        MACRO
        ARM8_branchpredict_off $temp
01
        ARM_read_control $temp
        BIC $temp,$temp,#&800        ;z bit (branch prediction)
        ARM_write_control $temp
        SEC                          ;set carry flag
        BCC %BT01
        MEND

;turn on branch prediction
        MACRO
        ARM8_branchpredict_on $temp
        ARM_read_control $temp
        ORR $temp,$temp,#&800        ;z bit (branch prediction)
        ARM_write_control $temp
        MEND

;flush branch prediction, which is sufficient for an IMB (instruction memory
;barrier) on ARM 810, BUT...
; - intended for in line use only, where efficiency matters, or SWI call is
;   awkward
; - general code should use SWI OS_SynchroniseCodeAreas to implement
;   an IMB (instruction memory barrier) in future proof, ARM independent way
; - kernel code may use this without regard to which ARM running - ie. assumed
;   harmless on other ARMs
;
        MACRO
        ARM8_branchpredict_flush
        SUB PC,PC,#4        ;flush, because PC is written by data op
        MEND

;clean cache entry
; - segment,index spec in $reg
; - bits 4..6   = segment (0..7)
; - bits 26..31 = index   (0..63)
; - all other bits zero
        MACRO
        ARM8_clean_IDCentry $reg,$cond
        MCR$cond ARM_config_cp,0,$reg,ARM8A_cache_reg,C11,1
        MEND

;flush cache entry -  segment,index spec in $reg, as for ARM8_clean_IDCentry
        MACRO
        ARM8_flush_IDCentry $reg,$cond
        MCR$cond ARM_config_cp,0,$reg,ARM8A_cache_reg,C7,1
        MEND

;clean and flush cache entry -  segment,index spec in $reg, as for ARM8_clean_IDCentry
;
;if ARM810cleanflushbroken is TRUE, interrupts *must* be currently diabled (see below)
;
        MACRO
        ARM8_cleanflush_IDCentry $reg,$cond
  [ ARM810cleanflushbroken
        ARM8_clean_IDCentry $reg,$cond
        ARM8_flush_IDCentry $reg,$cond
  |
        MCR$cond ARM_config_cp,0,$reg,ARM8A_cache_reg,C15,1
  ]
        MEND

;fully clean and flush cache (assumes no locked-down entries to preserve)
;
;if ARM810cleanflushbroken is TRUE, then we have to make sure interrupts are disabled during
;the sequence of 2 MCRs that make up ARM8_cleanflush_IDCentry, to avoid an interrupt hole.
;The hole occurs if an interrupt fills and dirties the particular cache entry after the clean
;but before the flush. We don't have this problem with StrongARM, because the entry is
;specified by virtual address, and RISC OS only cleans/flushes address space not currently
;involved in interrupts.
;
  [ ARM810cleanflushbroken

        MACRO
        ARM8_cleanflush_IDC $temp,$temp2
        ;for simplicity, disable interrupts during entire operation
        mrs ,$temp2,CPSR
        ORR  $temp,$temp2,#I32_bit
        msr ,CPSR_c,$temp                        ;disable I
        MOV  $temp,#0                            ;initial segment and index
01
        ARM8_cleanflush_IDCentry $temp
        ADD $temp,$temp,#1 :SHL: 26              ;next index
        CMP $temp,#1 :SHL: 26                    ;last index done if index field wrapped to 0
        BHS %BT01
        ADD $temp,$temp,#1 :SHL: 4               ;next segment
        CMP $temp,#8 :SHL: 4                     ;8 segments done?
        BLO %BT01
        msr ,CPSR_c,$temp2                       ;restore I
        MEND

  |

        MACRO
        ARM8_cleanflush_IDC $temp
        MOV $temp,#0                             ;initial segment and index
01
        ARM8_cleanflush_IDCentry $temp
        ADD $temp,$temp,#1 :SHL: 26              ;next index
        CMP $temp,#1 :SHL: 26                    ;last index done if index field wrapped to 0
        BHS %BT01
        ADD $temp,$temp,#1 :SHL: 4               ;next segment
        CMP $temp,#8 :SHL: 4                     ;8 segments done?
        BLO %BT01
        MEND

  ]

;flush whole TLB (actually, same as ARMA_flush_TLBs)
        MACRO
        ARM8_flush_TLB $cond
        MCR$cond ARM_config_cp,0,R0,ARM8A_TLB_reg,C7,0
        MEND

;flush TLB entry, virtual address in $reg
        MACRO
        ARM8_flush_TLBentry $reg,$cond
        MCR$cond ARM_config_cp,0,$reg,ARM8A_TLB_reg,C7,1
        MEND

;select external Refclk pin as fast clock (dynamic switching, asynchronous)
        MACRO
        ARM8_refclk_fclk $temp
        MRC ARM_config_cp,0,$temp,ARM8_CTC_reg,C0,0
        BIC $temp, $temp,#&1                        ;turn off dynamic bus switching (bit0)
        MCR ARM_config_cp,0,$temp,ARM8_CTC_reg,C0,0
        BIC $temp,$temp,#&2                         ;select asynchronous mode (default) (bit1)
        ORR $temp,$temp,#&4                         ;select REFCLK as the FCLK source (bits3:2)
        BIC $temp,$temp,#&10                        ;ensure L=0 when writing (PLL locked) (bit4)
        MCR ARM_config_cp,0,$temp,ARM8_CTC_reg,C0,0
        NOP
        NOP
        NOP
        NOP
        ORR $temp,$temp,#&1                         ;select dynamic clock switching (bit0)
        MCR ARM_config_cp,0,$temp,ARM8_CTC_reg,C0,0
        MEND

;select PLL output as fast clock (dynamic switching, asynchronous)
        MACRO
        ARM8_pll_fclk $temp
        MRC ARM_config_cp,0,$temp,ARM8_CTC_reg,C0,0
        BIC $temp,$temp,#&1                         ;turn off dynamic bus switching (bit0)
        MCR ARM_config_cp,0,$temp,ARM8_CTC_reg,C0,0
        BIC $temp,$temp,#&2                         ;select asynchronous mode (default) (bit1)
        ORR $temp,$temp,#&C                         ;select PLLClkOut as the FCLK source (bits3:2)
        BIC $temp,$temp,#&10                        ;ensure L=0 when writing (PLL locked) (bit4)
        MCR ARM_config_cp,0,$temp,ARM8_CTC_reg,C0,0
        NOP
        NOP
        NOP
        NOP
        ORR $temp,$temp,#&1                         ;select dynamic clock switching (bit0)
        MCR ARM_config_cp,0,$temp,ARM8_CTC_reg,C0,0
        MEND

 ] ;ARM810support

;
; -------------- StrongARM only ------------------------------------------
;

;clean whole data cache, using 16k private cleaner area at address in
;$cleanaddr
;trashes $cleanaddr,$temp1,$temp2
;
;method:
;clean whole (16k) data cache by reading 16k private cleaner area in 8-word
;(one cache line) steps
;
;note: this routine should NOT be used as is without care - remember
;      1) interrupts should be off (to guarantee this clean is effective)
;      2) DC should be flushed afterwards (to guarantee next clean using
;         private area is effective, ie. all private area flushed out now)
;      see ARMA_fullycleanflush_DC for 'packaged routine'
;
        MACRO
        ARMA_clean_DC $cleanaddr,$temp1,$temp2
        ADD     $temp1,$cleanaddr,#16*1024
10
        LDR     $temp2,[$cleanaddr],#32
        TEQ     $temp1,$cleanaddr
        BNE     %BT10
        MEND

;flush whole data cache
        MACRO
        ARMA_flush_DC $cond
        MCR$cond ARM_config_cp,0,R0,ARM8A_cache_reg,C6,0
        MEND

;clean data cache entry, virtual addr in $reg
        MACRO
        ARMA_clean_DCentry $reg,$cond
        MCR$cond ARM_config_cp,0,$reg,ARM8A_cache_reg,C10,1
        MEND

;flush data cache entry, virtual addr in $reg
        MACRO
        ARMA_flush_DCentry $reg,$cond
        MCR$cond ARM_config_cp,0,$reg,ARM8A_cache_reg,C6,1
        MEND

;clean and flush data cache entry, virtual addr in $reg
        MACRO
        ARMA_cleanflush_DCentry $reg,$cond
        MCR$cond ARM_config_cp,0,$reg,ARM8A_cache_reg,C14,1
        MEND

;clean data cache for virtual address range from $lo (inclusive) to $hi (exclusive)
;corrupts $lo,$hi
        MACRO
        ARMA_clean_DCrange $lo,$hi
        BIC     $lo,$lo,#31    ;align down to 8-word (1 cache line) boundary
        ADD     $hi,$hi,#31
        BIC     $hi,$hi,#31    ;align up to 8-word boundary
01
        ARMA_clean_DCentry $lo ;clean entry for virtual address $lo
        ADD     $lo,$lo,#32    ;next line
        ARMA_clean_DCentry $lo
        ADD     $lo,$lo,#32
        ARMA_clean_DCentry $lo
        ADD     $lo,$lo,#32
        ARMA_clean_DCentry $lo
        ADD     $lo,$lo,#32
        CMP     $lo,$hi
        BLO     %BT01
        MEND

;drain write buffer
        MACRO
        ARMA_drain_WB $cond
        MCR$cond ARM_config_cp,0,R0,ARM8A_cache_reg,C10,4
        MEND

        MACRO
        ARM_drain_WB $cond
        MCR$cond ARM_config_cp,0,R0,ARM8A_cache_reg,C10,4
        MEND

;flush whole instruction cache
        MACRO
        ARMA_flush_IC $WithoutNOPs,$cond
        MCR$cond ARM_config_cp,0,R0,ARM8A_cache_reg,C5,0
      [ "$WithoutNOPs" = ""
        MOV     R0,R0 ; 4 NOPS - up to 4 further instructions may come from IC before flush
        MOV     R0,R0
        MOV     R0,R0
        MOV     R0,R0
      ]
        MEND

;flush whole instruction cache and whole data cache
        MACRO
        ARMA_flush_ICandDC $WithoutNOPs,$cond
        MCR$cond ARM_config_cp,0,R0,ARM8A_cache_reg,C7,0
      [ "$WithoutNOPs" = ""
        MOV     R0,R0 ; 4 NOPS - up to 4 further instructions may come from IC before flush
        MOV     R0,R0
        MOV     R0,R0
        MOV     R0,R0
      ]
        MEND

;flush whole instruction TLB
        MACRO
        ARMA_flush_ITLB $cond
        MCR$cond ARM_config_cp,0,R0,ARM8A_TLB_reg,C5,0
        MEND

;flush whole data TLB
        MACRO
        ARMA_flush_DTLB $cond
        MCR$cond ARM_config_cp,0,R0,ARM8A_TLB_reg,C6,0
        MEND

;flush whole instruction and data TLBs
        MACRO
        ARMA_flush_TLBs $cond
        MCR$cond ARM_config_cp,0,R0,ARM8A_TLB_reg,C7,0
        MEND

;flush data TLB entry, virtual address in $reg
        MACRO
        ARMA_flush_DTLBentry $reg,$cond
        MCR$cond ARM_config_cp,0,$reg,ARM8A_TLB_reg,C6,1
        MEND

;fully clean and flush DC - see ARMA_clean_DC for more info
        MACRO
        ARMA_fullycleanflush_DC $cleanaddr,$temp1,$temp2,$temp3
        mrs    ,$temp3,CPSR
        ORR     $temp1,$temp3,#I32_bit
        msr    ,CPSR_c,$temp1         ;disable IRQs
        ARMA_clean_DC $cleanaddr,$temp1,$temp2
        ARMA_flush_DC
        msr    ,CPSR_c,$temp3         ;restore IRQ state
        MEND

;enable core clock switching (fast core clock allowed)
        MACRO
        ARMA_fastcoreclock $cond
        MCR$cond ARM_config_cp,0,R0,ARMA_TCI_reg,C1,2
        MEND

;disable core clock switching (core clock is memory clock)
        MACRO
        ARMA_slowcoreclock $cond
        MCR$cond ARM_config_cp,0,R0,ARMA_TCI_reg,C2,2
        MEND

;
; -------------- Additional ARMv7 stuff -----------------------------------
;

; Provided here are ISB, DSB and DMB macros suitable for ARMv6+
; Although ARMv4 & v5 do provide CP15 ops that are compatible with the ARMv6 ops, it's implementation defined whether each processor implements the ops or not (and the ops are unpredictable if unimplemented)
; So to play it safe these macros will complain if used on pre-ARMv6
; For all the macros, set the $quick to something if the value of $temp is
; already zero (this will cut out a pointless MOV)

; Instruction Synchronisation Barrier - required on ARMv6+ to ensure the effects of the following are visible to following instructions:
; * Completed cache, TLB & branch predictor maintenance operations
; * CP14/CP15 writes
        MACRO
        myISB $cond,$temp,$option,$quick
     [ NoARMv6
        ! 1, "Don't know what to do on pre-ARMv6!"
     |
      [ NoARMv7
        ; ARMv6, use legacy MCR op
       [ "$quick"=""
        MOV$cond $temp,#0
       ]
        MCR$cond p15,0,$temp,c7,c5,4
      |
        ; ARMv7+, use ISB instruction (saves on temp register, but instruction is unconditional)
        ; Shouldn't hurt too much if we just ignore the condition code
        DCI &F57FF06F ; ISB SY
      ]
     ]
        MEND

; Data Synchronisation Barrier - aka drain write buffer/data write barrier. Stalls pipeline until all preceeding memory accesses (including cache/TLB/BTC ops complete.
        MACRO
        myDSB $cond,$temp,$option,$quick
     [ NoARMv6
        ! 1, "Don't know what to do on pre-ARMv6!"
     |
      [ NoARMv7
        ; pre-ARMv7, use legacy MCR op
       [ "$quick"=""
        MOV$cond $temp,#0
       ]
        MCR$cond p15,0,$temp,c7,c10,4
      |
        ; ARMv7+, use DSB instruction
       [ "$option"="SY" :LOR: "$option"=""
        DCI &F57FF04F ; DSB SY
       |
       [ "$option"="ST" :LOR: "$option"="SYST"
        DCI &F57FF04E ; DSB ST
       |
       [ "$option"="ISH"
        DCI &F57FF04B ; DSB ISH
       |
       [ "$option"="ISHST"
        DCI &F57FF04A ; DSB ISHST
       |
       [ "$option"="NSH"
        DCI &F57FF047 ; DSB NSH
       |
       [ "$option"="NSHST"
        DCI &F57FF046 ; DSB NSHST
       |
       [ "$option"="OSH"
        DCI &F57FF043 ; DSB OSH
       |
       [ "$option"="OSHST"
        DCI &F57FF042 ; DSB OSHST
       |
        ! 1, "Unrecognised DSB option"
       ]
       ]
       ]
       ]
       ]
       ]
       ]
       ]
      ]
     ]
        MEND

; Data Memory Barrier - More lightweight DSB, ensures memory accesses behave correctly without stalling the pipeline to wait for preceeding accesses to complete. I.e. it's only good for synchronising load/store instructions.
        MACRO
        myDMB $cond,$temp,$option,$quick
     [ NoARMv6
        ! 1, "Don't know what to do on pre-ARMv6!"
     |
      [ NoARMv7
        ; ARMv6, use legacy MCR op
       [ "$quick"=""
        MOV$cond $temp,#0
       ]
        MCR$cond p15,0,$temp,c7,c10,5
      |
        ; ARMv7+, use DMB instruction
       [ "$option"="SY" :LOR: "$option"=""
        DCI &F57FF05F ; DMB SY
       |
       [ "$option"="ST" :LOR: "$option"="SYST"
        DCI &F57FF05E ; DMB ST
       |
       [ "$option"="ISH"
        DCI &F57FF05B ; DMB ISH
       |
       [ "$option"="ISHST"
        DCI &F57FF05A ; DMB ISHST
       |
       [ "$option"="NSH"
        DCI &F57FF057 ; DMB NSH
       |
       [ "$option"="NSHST"
        DCI &F57FF056 ; DMB NSHST
       |
       [ "$option"="OSH"
        DCI &F57FF053 ; DMB OSH
       |
       [ "$option"="OSHST"
        DCI &F57FF052 ; DMB OSHST
       |
        ! 1, "Unrecognised DMB option"
       ]
       ]
       ]
       ]
       ]
       ]
       ]
       ]
      ]
     ]
        MEND


    END