; TTXHelper
;   Additional teletext-related facilities
;
;
; by Richard Murray
;   Version: 0.02
;   Date   : Tuesday, 17th February 2004
;   Started: Monday, 12th January 2004
;
;
; THIS MODULE IS *NOT* ROMABLE - it writes to itself in *ONE* location.
;
;


; Our inclusions:
        GET   ^.h.equx        ; EQUB, EQUD, EQUSZ, EQUSZA...
        GET   ^.h.SWINames    ; SWI definitions
        GET   ^.h.pushpull    ; Push, Pull, PushLR, PullLR, Return, PullRet...
        GET   ^.h.debug       ; DebugMsg (only used during actual debugging)


; DEFINITIONS:
;
; Version information
         GBLS CODENAME
CODENAME SETS "TTXHelper"
         GBLS CODEVERS
CODEVERS SETS "0.02"
         GBLS FULLVERS
FULLVERS SETS "0.02 "         ; must be EXACTLY five bytes...
         GBLS CODEDATE
CODEDATE SETS "17 Feb 2004"   ; Date format MUST be "DD Mmm CCYY" for RISC OS to recognise it
         GBLA CODEVERN
CODEVERN SETA 1

; SWI chunk
         GBLA SWICHUNK
SWICHUNK SETA &57180          ; **OFFICIAL** ALLOCATION
; Error chunk
         GBLA ERRCHUNK
ERRCHUNK SETA &81B510         ; **OFFICIAL** ALLOCATION (it uses PtrIIC's chunk + &10)

; Languages
_ENGLISH                 *    0
_FRENCH                  *    1
_SWEDISH                 *    2
; inval.                 *    3
_GERMAN                  *    4
_SPANISH                 *    5
_ITALIAN                 *    6
; inval.                 *    7

; IIC devices
_TTX_WRITE               *  34
_TTX_READ                *  35
_CITAC_WRITE             * 192       ; device 96
_CITAC_READ              * 193       ; device 96

_UTA1_CITAC              * 199       ; device 99 * READ *
_UTA3_CITAC              * 195       ; device 95 * READ *

; interface IDs
_DEV_SYSIIC              * 0         ; system IIC bus
_DEV_PTRIIC              * 1         ; PtrIIC bus emulation
_DEV_RTVIIC              * 2         ; RiscTV interface

; Additional SWIs
PtrIIC_Control           * &56DC0
XPtrIIC_Control          * &76DC0
RiscTV_IICControl        * &43902
XRiscTV_IICControl       * &63902

; Our own SWIs (!)
TTXHelper_AutoDetect     * (SWICHUNK +  0)
TTXHelper_ScanFlash      * (SWICHUNK +  1)
TTXHelper_ConvertToISO   * (SWICHUNK +  2)
TTXHelper_ConvertToANSI  * (SWICHUNK +  3)
TTXHelper_ExtractLine    * (SWICHUNK +  4)
TTXHelper_SetCharSet     * (SWICHUNK +  6)
TTXHelper_TransChar      * (SWICHUNK +  7)
TTXHelper_UseInterface   * (SWICHUNK +  9)
TTXHelper_ReadBytes      * (SWICHUNK + 10)
TTXHelper_WriteBytes     * (SWICHUNK + 11)
TTXHelper_ReadRegister   * (SWICHUNK + 12)
TTXHelper_WriteRegister  * (SWICHUNK + 13)
TTXHelper_ReadChapter    * (SWICHUNK + 14)
TTXHelper_ClearTSDP      * (SWICHUNK + 15)
XTTXHelper_AutoDetect    * (SWICHUNK +  0) + &20000
XTTXHelper_ScanFlash     * (SWICHUNK +  1) + &20000
XTTXHelper_ConvertToISO  * (SWICHUNK +  2) + &20000
XTTXHelper_ConvertToANSI * (SWICHUNK +  3) + &20000
XTTXHelper_ExtractLine   * (SWICHUNK +  4) + &20000
XTTXHelper_SetCharSet    * (SWICHUNK +  6) + &20000
XTTXHelper_TransChar     * (SWICHUNK +  7) + &20000
XTTXHelper_UseInterface  * (SWICHUNK +  9) + &20000
XTTXHelper_ReadBytes     * (SWICHUNK + 10) + &20000
XTTXHelper_WriteBytes    * (SWICHUNK + 11) + &20000
XTTXHelper_ReadRegister  * (SWICHUNK + 12) + &20000
XTTXHelper_WriteRegister * (SWICHUNK + 13) + &20000
XTTXHelper_ReadChapter   * (SWICHUNK + 14) + &20000
XTTXHelper_ClearTSDP     * (SWICHUNK + 15) + &20000



        ; *****************
        ;
        ; CODE BEGINS HERE!
        ;
        ; *****************

        AREA     |TTXHelper$Code|, CODE, A32bit  ; ** THIS CODE SELF-MODIFIES **

	ENTRY

entrypoint
        ; Standard RISC OS module header
        EQUD     0                            ; Start code
	EQUD	 0                            ; Initialise code
	EQUD	 0                            ; Finalise code
	EQUD	 0                            ; Service call
	EQUD	 (titlestring - entrypoint)   ; Module title string
	EQUD	 (helpstring - entrypoint)    ; Module help string
	EQUD	 (commandtable - entrypoint)  ; Help/command table
	EQUD	 SWICHUNK              	      ; SWI chunk
	EQUD	 (swicode - entrypoint)       ; SWI handler code
	EQUD	 (switable - entrypoint)      ; SWI decoding table
	EQUD	 0			      ; SWI decoding code
	EQUD	 0			      ; Messages file
	EQUD	 (thirtytwo - entrypoint)     ; 32bit flag word
        EQUD     0                            ; for future expansion



thirtytwo
       ; Bit zero set indicates this is a 32bit-compatible module...
       EQUD      1



titlestring
        =        CODENAME, 0
        ALIGN



helpstring
        =        CODENAME, 9, CODEVERS, " (", CODEDATE, ")  Rick Murray; http://www.heyrick.co.uk/software/ttx/"
        ALIGN



commandtable
        ; *TTXHelper_Licence
        EQUSZA   "TTXHelper_Licence"
        EQUD     (licencecode - entrypoint)
        EQUD     0
        EQUD     0
        EQUD     (licencehelp - entrypoint)

        ; *TTXHelper_Probe
        EQUSZA   "TTXHelper_Probe"
        EQUD     (probecode - entrypoint)
        EQUD     0
        EQUD     0
        EQUD     (probehelp - entrypoint)

        ; *TTXHelper_View
        EQUSZA   "TTXHelper_View"
        EQUD     (viewcode - entrypoint)
        EQUD     &00010001                    ; max 1 parameter, min 1 parameter, don't GSTrans
        EQUD     0
        EQUD     (viewhelp - entrypoint)

        EQUD     0                            ; end of list



switable
        =        CODENAME, 0
        EQUSZ    "AutoDetect"                 ; try to detect hardware in use
        EQUSZ    "ScanFlash"                  ; blank out 'blinked' text
        EQUSZ    "ConvertToISO"               ; convert SAA5050 -> ISO 8859/1
        EQUSZ    "ConvertToANSI"              ; (don't ask!)
        EQUSZ    "ExtractLine"                ; extract lines, nicified
        EQUSZ    "NOP"                        ; for future expansion  (&xxxx5)
        EQUSZ    "NOP"                        ; for future expansion  (&xxxx6); patches charset
        EQUSZ    "NOP"                        ; for future expansion  (&xxxx7); raw char convert
        EQUSZ    "NOP"                        ; for future expansion  (&xxxx8)
        EQUSZ    "UseInterface"               ; set which IIC interface to use
        EQUSZ    "ReadBytes"                  ; Read from receiver memory
        EQUSZ    "WriteBytes"                 ; Write to receiver memory
        EQUSZ    "ReadRegister"               ; Read from receiver register
        EQUSZ    "WriteRegister"              ; Write to receiver register
        EQUSZ    "ReadChapter"                ; Read a chapter from receiver memory
        EQUSZ    "ClearTSDP"                  ; Clear TSDP area in receiver memory
        EQUB     0                            ; (SWIs &xxxx0 to &xxxxF)
        ALIGN



licencecode
        ; This is because RISC OS doesn't recognise "TTXHelper_Licence" as a command if it
        ; only has help text... so we mimic the behaviour of the *Help routine...
        PushLR                                ; SVC mode, remember?
        ADRL     R0, licencehelp
        MOV      R1, #0
        MOV      R2, #0
        SWI      XOS_PrettyPrint
        PullRet



probecode
        ; NOTE - we only need to preserve R7-R11, which we don't use anyway...
        PushLR

        ; Call SWI
        SWI      XTTXHelper_AutoDetect

        ; Push the variables high to preserve them.
        MOV      R4, R0
        MOV      R5, R1
        MOV      R6, R2

        ; Now let's work out what we have.
        CMP      R0, #-1                      ; found nowt
        BEQ      probecode_foundnothing

        ; We found something...
        ADRL     R0, probe_found
        SWI      XOS_PrettyPrint              ; does 13=NewLine for us

        ; What is it?
        ADRL     R0, probe_compatible
        SWI      XOS_Write0

        CMP      R4, #1                       ; UTA-1
        BEQ      probe_founduta

        CMP      R4, #2                       ; UTA-2
        BEQ      probe_founduta

        CMP      R4, #3                       ; UTA-3
        BEQ      probe_founduta

        ; Otherwise, dunno what this is!
        BL       probecode_unrecognised

probe_founduta
        ; Write "Ground Control UTA-<model>."
        ADRL     R0, probe_groundcontrol
        SWI      XOS_Write0
        MOV      R0, R4
        ADRL     R1, extract_line_data
        MOV      R2, #16
        SWI      XOS_ConvertCardinal1
        SWI      XOS_Write0
        SWI      XOS_WriteI + '.'
        SWI      XOS_NewLine
        ; ** fall through **

probecode_ifoutput
        ; Now write what it is connected to.
        ADRL     R0, probe_interface
        SWI      XOS_Write0

        CMP      R5, #_DEV_SYSIIC
        ADREQL   R0, probe_sysiic
        BEQ      probecode_foundif

        CMP      R5, #_DEV_PTRIIC
        ADREQL   R0, probe_ptriic
        BEQ      probecode_foundif

        CMP      R5, #_DEV_RTVIIC
        ADREQL   R0, probe_rtviic
        BEQ      probecode_foundif

        ; Here? Then it is unknown...
        ; push type into R4 for unknown code to work, we don't need R4 any more...
        MOV      R4, R5
        BL       probecode_unrecognised
        B        probecode_memtest

probecode_foundif
        SWI      XOS_Write0
        SWI      XOS_NewLine
        ; ** fall through **

probecode_memtest
        CMP      R6, #0
        BEQ      probecode_nomemtest

        ADRL     R0, probe_memory
        SWI      XOS_Write0
        MOV      R0, R6
        ADRL     R1, extract_line_data
        MOV      R2, #16
        SWI      XOS_ConvertCardinal2
        SWI      XOS_Write0
        ADRL     R0, probe_memorytail
        SWI      XOS_Write0
        B        probecode_exit

probecode_nomemtest
        ADRL     R0, probe_memorynot
        SWI      XOS_Write0
        B        probecode_exit

probecode_foundnothing
        ADRL     R0, probe_unfound
        SWI      XOS_Write0
        ; ** fall through **

probecode_exit
        SWI      XOS_NewLine
        SWI      XOS_NewLine
        PullRet

probecode_unrecognised
        ; Write "unrecognised (<type>).".
        PushLR
        ADRL     R0, probe_unknown
        SWI      XOS_Write0
        AND      R0, R4, #255                 ; bring it into 0-255 range
        ADRL     R1, extract_line_data
        MOV      R2, #16
        SWI      XOS_ConvertCardinal1
        SWI      XOS_Write0
        ADRL     R0, probe_unknowntail
        SWI      XOS_Write0
        SWI      XOS_NewLine
        PullRet



viewcode
        PushLR

        ; Store command tail
        MOV      R5, R0

        ; Make sure we're in the command line
        ADRL     R0, view_wimpstate
        ADRL     R1, extract_line_data
        MOV      R2, #64
        MOV      R3, #0
        MOV      R4, #0
        SWI      XOS_ReadVarVal
        ADRL     R1, extract_line_data
        LDRB     R0, [R1, #0]
        CMP      R0, #'d'
        ADREQL   R0, view_wimperror
        BEQ      viewcode_dropexit

        ; Grab 1024 bytes for display
        MOV      R0, #6                        ; OS_Module 6 = Claim
        MOV      R3, #1024
        SWI      XOS_Module
        LDMVSFD  R13!, {R14}                   ; abort if allocation error
        MOVVS    PC, R14
        MOV      R6, R2                        ; store memory pointer in R6

        ; Open file
        MOV      R0, #(&40 + 15)               ; Open Read Only; no path; error if missing/dir
        MOV      R1, R5
        SWI      XOS_Find
        BVS      viewcode_freeandexit
        MOV      R5, R0                        ; no longer need command tail, store file handle

        ; Load 1000 bytes from file (24 rows * 40 columns = 1000)
        MOV      R3, #1000
        MOV      R2, R6
        MOV      R1, R0
        MOV      R0, #4                        ; OS_GBPB 4, Read Bytes from current file pointer
        SWI      XOS_GBPB
        BVS      viewcode_closeandfreeandexit

        ; Close file
        MOV      R0, #0
        MOV      R1, R5
        SWI      XOS_Find

        ; Let's select MODE 7
        SWI      XOS_WriteI + 22
        SWI      XOS_WriteI + 7

        ; And then draw the data to the screen
        ;   R0 = Byte
        ;   R2 = Scratch
        ;   R3 = Scratch
        ;   R4 = Row
        ;   R5 = Column
        ;   R6 = Buffer

        MOV      R4, #0                       ; First row
viewcode_rowloop
        MOV      R5, #0                       ; First column

viewcode_columnloop
        ; The byte is buffer ? ( ( row * 40 ) + column )
        MOV      R3, #40                      ; [this could be optimised]
        MUL      R2, R4, R3                   ; offset  = row * 40
        ADD      R2, R2, R5                   ; offset += column
        LDRB     R0, [R6, R2]                 ; byte = (buffer + offset)

        ; Deweird it
        ORR      R0, R0, #&80                 ; deweird it (part 1)
        SUB      R0, R0, #&80                 ; deweird it (part 2)

        ; Reweird it if it is a control code (!)
        CMP      R0, #32
        ORRLT    R0, R0, #&80

        ; Write it
        SWI      XOS_WriteC

        ADD      R5, R5, #1
        CMP      R5, #40
        BLT      viewcode_columnloop

        ; we're in the row (outer) loop
        ADD      R4, R4, #1
        CMP      R4, #24
        BLT      viewcode_rowloop

        B        viewcode_safefreeandexit


viewcode_closeandfreeandexit
        Push     "R0"
        MOV      R0, #0
        MOV      R1, R5
        SWI      XOS_Find
        Pull     "R0"
        ; ** fall through **

viewcode_freeandexit
        Push     "R0"
        MOV      R0, #7                        ; OS_Module 7 = Free
        MOV      R2, R6
        SWI      XOS_Module
        Pull     "R0"
viewcode_dropexit
        SetV                                   ; We get called on an error condition...
        PullRet

viewcode_safefreeandexit
        MOV      R0, #7                        ; OS_Module 7 = Free
        MOV      R2, R6
        SWI      XOS_Module
        PullRet                                ; No error, we're happy bunnies



swicode
        ; Standard RISC OS SWI entry considerations
        ;
        ; On entry:
        ;   R0 - R8 as for caller
        ;   R11 = SWI number AND 63
        ;   R12 = Private word
        ;   R13 = SVC stack
        ;   R14 = Return address
        ;
        ; ************************************
        ; *                                  *
        ; *  REMEMBER: We're in SVC mode...  *
        ; *                                  *
        ; ************************************

        CMP      R11, #((swicode_endoftable - swicode_table) / 4)
        ADDCC    PC, PC, R11, LSL #2
        B        swicode_invalid

swicode_table
        B        swicode_autodetect
        B        swicode_scanflash
        B        swicode_converttoiso
        B        swicode_converttoansi
        B        swicode_extractline
        B        swicode_unimplemented        ; was swicode_nop
        B        swicode_undocumented_patch_characterset
        B        swicode_undocumented_rawchar_lookup
        B        swicode_unimplemented        ; was swicode_nop
        B        swicode_useinterface
        B        swicode_readbytes
        B        swicode_writebytes
        B        swicode_readregister
        B        swicode_writeregister
        B        swicode_readchapter
        B        swicode_cleartsdp
swicode_endoftable



swicode_invalid
        ; return unknown SWI error
        ADRL     R0, errormsg_invalidswi
        SetV
        Return



swicode_nop                                   ; NOT CURRENTLY USED - we'll say "unimplemented"
        ; jump table needs valid NOP entries, so we'll direct 'em here, to send 'em home! :-)
        ; Return



swicode_autodetect
        ; SYS "TTXHelper_AutoDetect"                                        &57180
        ;
        ; Try to detect which teletext device we're actually using...
        ;
        ; On entry:
        ;   -
        ;
        ; On exit:
        ;   R0 = Hardware detected (-1 none; 0 unknown; 1 UTA-1; 2 UTA-2; 3 UTA-3)
        ;   R1 = If R0 is 0-2, then this is 0 IIC; 1 PtrIIC; if R0 = 3, this will be 2.
        ;   R2 = Bytes of onboard memory actually detected, or -1 if not tested
        ;   R3 = Reserved, returns module version.
        ;


        ; In case the code looks complex, here's a handy flowchart! :-)
        ;
        ;         Do we have system IIC? ->---.
        ;          |no                     yes|
        ;          |                         \|/
        ;          |   Is teletext decoder IC on system IIC? ->----.
        ;          |    |no                                     yes|
        ;         \|/  \|/                                         |
        ;         Do we have PtrIIC emulation? >.                  |
        ;          |no                      yes |                  |
        ;          |                           \|/                 |
        ;          |   Is teletext decoder IC on PtrIIC? ->--------|
        ;          |    |no                                     yes|
        ;         \|/  \|/                                         |
        ;         Do we have RiscTV's IIC? ->-.                    |
        ;          |no                     yes|                    |
        ;          |                         \|/                   |
        ;          |   Is teletext decoder IC on RiscTV's IIC? ->--|
        ;          |    |no                                     yes|
        ;         \|/  \|/                                         |
        ;         Give up!                                         |
        ;         [EXIT]                                           |
        ;                                                          |
        ;          .-<-------------------------------------------<-'
        ;          |
        ;         \|/
        ;         Do we have a CITAC? ->------.
        ;          |yes                     no|
        ;          |                         \|/
        ;          |                         Dunno what type this is...
        ;          |                         [EXIT]
        ;         \|/
        ;         Do we have a CITAC 99? ->---.
        ;          |no                     yes|
        ;          |                         \|/
        ;          |                         It is a UTA-1 (or compatible) ->-.
        ;         \|/                                                         |
        ;         Do we have a CITAC 97? ->---.                               |
        ;          |no                     yes|                               |
        ;          |                         \|/                              |
        ;          |                         It is a UTA-3 (or compatible) ->-|
        ;         \|/                                                         |
        ;         Who knows what this is!                                     |
        ;          |                                                          |
        ;          |-<------------------------------------------------------<-'
        ;         \|/
        ;         Probe for memory capacity
        ;          |
        ;          |
        ;         \|/
        ;         Write to chapter zero, did it read back? ->-------.
        ;          |yes                                           no|
        ;          |                                               \|/
        ;          |                                               it has 0 bytes! ->---.
        ;         \|/                                                                   |
        ;         Write to chapter four, did it read back? ->-------.                   |
        ;          |yes                                           no|                   |
        ;          |                                               \|/                  |
        ;          |                                               it has 1024 bytes! >-|
        ;         \|/                                                                   |
        ;         it has 2048 bytes                                                     |
        ;          |                                                                    |
        ;          |-<----------------------------------------------------------------<-'
        ;          |
        ;         \|/
        ;         [EXIT]


        Push     "R4-R6, R14"

        ; Keep current device setting
        LDR      R0, device_identity
        Push     "R0"

        ; Do we have system IIC?
        MOV      R0, #_DEV_SYSIIC
        SWI      XTTXHelper_UseInterface                 ; call our own SWI
        BVC      swicode_autodetect_sysiic_test          ; got System IIC, test it...
        ; ** otherwise fall through **

swicode_autodetect_ptriic_check
        ; Do we have PtrIIC IIC?
        MOV      R0, #_DEV_PTRIIC
        SWI      XTTXHelper_UseInterface                 ; don't call us, we'll call you!
        BVC      swicode_autodetect_ptriic_test          ; got PtrIIC, test it...
        ; ** otherwise fall through **

swicode_autodetect_rtviic_check
        ; Do we have RiscTV IIC?
        MOV      R0, #_DEV_RTVIIC
        SWI      XTTXHelper_UseInterface                 ; call for psychiatric help
        BVC      swicode_autodetect_rtviic_test          ; got RiscTV IIC, test it...
        ; ** otherwise fall through **

swicode_autodetect_iicdev_unfound
        ; if we are here, we didn't find any hardware
        ; restore previous device ID
        Pull     "R0"
        STR      R0, device_identity
        ; set up reply to user
        MOV      R0, #-1                      ; not found
        MOV      R1, #-1                      ; invalid
        MOV      R2, #-1                      ; invalid
        MOV      R3, #CODEVERN
        CMP      R0, #-1                      ; result is EQ, flags updated, V should be clear
        ; bye bye
        Pull     "R4-R6, PC"                  ; implicit RETURN

swicode_autodetect_iicdev_unknown
        ; if we are here, we didn't find any hardware
        ; restore previous device ID
        Pull     "R0"
        STR      R0, device_identity
        ; set up reply to user
        MOV      R0, #0                       ; unknown
        MOV      R1, R5
        MOV      R2, #-1                      ; invalid
        MOV      R3, #CODEVERN
        CMP      R0, #0                       ; result is EQ, flags updated, V should be clear
        ; au revoir
        Pull     "R4-R6, PC"                  ; implicit RETURN


swicode_autodetect_sysiic_test
        ; Look for device #17 on system IIC
        MOV      R0, #_TTX_READ
        ADRL     R1, iic_data
        MOV      R2, #1
        BL       iic_transfer
        BVS      swicode_autodetect_ptriic_check ; try PtrIIC if not found here
        MOV      R5, #0                       ; it is system IIC
        B        swicode_autodetect_probe

swicode_autodetect_ptriic_test
        ; Look for device #17 on PtrIIC
        MOV      R0, #_TTX_READ
        ADRL     R1, iic_data
        MOV      R2, #1
        BL       iic_transfer
        BVS      swicode_autodetect_rtviic_check ; try RiscTV if not found here
        MOV      R5, #1                       ; it is PtrIIC
        B        swicode_autodetect_probe

swicode_autodetect_rtviic_test
        ; Look for device #17 on RiscTV's IIC
        MOV      R0, #_TTX_READ
        ADRL     R1, iic_data
        MOV      R2, #1
        BL       iic_transfer
        BVS      swicode_autodetect_iicdev_unfound ; if not found, go away and sulk
        MOV      R5, #2                       ; it is RiscTV's IIC
        ; **fall through**

swicode_autodetect_probe
        ; We have the interface sorted out, so let's now probe what sort of device it is.
        ;
        ; If tuner device #96 does NOT respond, it is unknown hardware
        ; If tuner device #99 responds, it is a UTA-1 type.
        ; If tuner device #97 responds, it is a UTA-3 type.
        ; We'll hope for UTA-3 compatible, but... :-)

        ; Check for basic CITAC
        MOV      R0, #_CITAC_READ
        ADRL     R1, iic_data
        MOV      R2, #1
        BL       iic_transfer
        BVS      swicode_autodetect_iicdev_unknown

        ; Check for UTA-1 variant
        MOV      R0, #_UTA1_CITAC
        ADRL     R1, iic_data
        MOV      R2, #1
        BL       iic_transfer
        MOVVC    R4, #1                       ; flag it as a UTA-1
        BVC      swicode_autodetect_memprobe  ; skip UTA-3 test if was a UTA-1...

        ; Check for UTA-3 variant
        MOV      R0, #_UTA3_CITAC
        ADRL     R1, iic_data
        MOV      R2, #1
        BL       iic_transfer
        MOVVC    R4, #3                       ; flag it as a UTA-3
        MOVVS    R4, #0                       ; dunno what this is...
        ; **fall through**

swicode_autodetect_memprobe
        ; Memory test

        ; Set chapter 0 and write an ID to 0,0
        MOV      R0, #0
        BL       set_chapter
        BL       swicode_autodetect_memprobe_core
        LDR      R0, my_identity              ; load...
        LDR      R1, iic_data
        CMP      R0, R1                       ; ...and test
        MOVNE    R6, #0
        MOVEQ    R6, #1024

        ; Again, for chapter 4
        MOV      R0, #4
        BL       set_chapter
        BL       swicode_autodetect_memprobe_core
        LDR      R0, my_identity              ; wash...
        LDR      R1, iic_data
        CMP      R0, R1                       ; ...and go
        MOVEQ    R6, #2048

        ; Probe complete. :-)

        ; restore previous device ID
        Pull     "R0"
        STR      R0, device_identity
        ; we shouldn't have any errors to restore!
        ; set up reply to user
        MOV      R0, R4
        MOV      R1, R5
        MOV      R2, R6
        MOV      R3, #CODEVERN
        ; hasta la vista, baby
        Pull     "R4-R6, PC"                  ; implicit RETURN


swicode_autodetect_memprobe_core
        PushLR
        MOV      R0, #22
        MOV      R1, #36
        ADRL     R2, my_identity
        MOV      R3, #4
        SWI      XTTXHelper_WriteBytes
        ;Push     "R1-R5, PC"
        ;B        swicode_writebytes_internalentry
        ;NOP
        ;NOP

        ; now let's read it back
        MOV      R0, #22
        MOV      R1, #36
        ADRL     R2, iic_data
        MOV      R3, #4
        SWI      XTTXHelper_ReadBytes
        ;Push     "R1-R5, PC"
        ;B        swicode_readbytes_internalentry
        ;NOP
        ;NOP

        PullRet



swicode_scanflash
        ; SYS "TTXHelper_ScanFlash"                                         &57181
        ;
        ; Scan for flash commands, and build up a 'flash-off' frame
        ;
        ; On entry:
        ;   R0 = Pointer to 'source' frame.
        ;   R1 = Pointer to 'destination' frame.
        ;
        ; On exit:
        ;   R0 = 0 if no flashing content; else a counter of flash commands found.
        ;
        ; ASCII &08/ &88 = Flash on
        ; ASCII &09/ &89 = Flash off
        ; Newline        = Flash off (implicit)
        ;
        ; We use:
        ;   R0 - source
        ;   R1 - destination
        ;   R2 - row
        ;   R3 - column
        ;   R4 - byte
        ;   R5 - flash status
        ;   R6 - flash on count
        ;   R7 - scratch
        ;   R8 - scratch

        Push     "R1-R8, R14"

        MOV      R2, #0                       ; First row (column is set within row loop)
        MOV      R6, #0                       ; No flash codes counted yet

swicode_scanflash_rowloop
        MOV      R3, #0                       ; First column
        MOV      R5, #0                       ; Flash is off; newline implicitly does this

swicode_scanflash_columnloop
        ; the byte is  source ? ( ( row * 40 ) + column )
        MOV      R8, #40                      ; [this could be optimised]
        MUL      R7, R2, R8                   ; offset  = row * 40
        ADD      R7, R7, R3                   ; offset += column
        LDRB     R4, [R0, R7]                 ; byte = (source + offset)

        ; is it a flash on?
        CMP      R4, #&08
        CMPNE    R4, #&88
        MOVEQ    R5, #1                       ; flag flash on
        ADDEQ    R6, R6, #1                   ; increment flash count
        MOVEQ    R4, #32                      ; patch out the flash on code

        ; is it a flash off?
        CMP      R4, #&09
        CMPNE    R4, #&89
        MOVEQ    R5, #0                       ; flag flash off
        MOVEQ    R4, #32                      ; patch out the flash off code

        ; if flash is on...
        CMP      R5, #1
        BNE      swicode_scanflash_skipbyteprocess
        ; ...and it's NOT a control character, blank it
        ORR      R4, R4, #&80                 ; deweird it (part 1)
        SUB      R4, R4, #&80                 ; deweird it (part 2)
        CMP      R4, #32                      ; not a control character?
        MOVGT    R4, #32                      ; make it a space

        ; a casual reader might think - why toss away stuff that is NOT a control code?
        ; why not toss the control codes?
        ; remember we are creating the 'blanked' frame of a flash pair; so we want to
        ; blank any characters within the flash section; at the same time we want to
        ; preserve any control codes as these mean things to the teletext display

swicode_scanflash_skipbyteprocess
        ; we're in the column (inner) loop

        ; write back the (possibly updated) byte to the DESTINATION frame
        ; we know R7 (offset) is still valid
        STRB     R4, [R1, R7]                 ; (dest + offset) = byte

        ADD      R3, R3, #1
        CMP      R3, #40
        BLT      swicode_scanflash_columnloop

        ; we're in the row (outer) loop
        ADD      R2, R2, #1
        CMP      R2, #26
        BLT      swicode_scanflash_rowloop

        ; now copy the linked frames, language, etc...
        ; R2 is offset start
        ; R3 is offset end
                                              ;     0 = Frame/data start
        LDR      R2, offset_frameend          ;  1040 = Frame end
        LDR      R3, offset_dataend           ;  1080 = Data end

swicode_scanflash_tailcopy
        LDRB     R4, [R0, R2]                 ; read from src+offset
        STRB     R4, [R1, R2]                 ; and shove to dest+offset
        ADD      R2, R2, #1
        CMP      R2, R3
        BNE      swicode_scanflash_tailcopy

        ; finally, set the return values
        MOV      R0, R6

        ; we're done...
        Pull     "R1-R8, PC"                  ; implicit return



swicode_converttoiso
        ; SYS "TTXHelper_ConvertToISO"                                      &57182
        ;
        ; Convert from teletext 7-bit character set to ISO 8859/1; stripping mosaic graphics.
        ;
        ; On entry:
        ;   R0 = Pointer to 'source' frame.
        ;   R1 = Pointer to 'destination' frame.
        ;   R2 = Language (0 = GB; 1 = F; 2 = SWE; 4 = D; 5 = E; 6 = I) [3 & 7 assume GB]
        ;
        ; On exit:
        ;   -
        ;

        PushLR                                ; it's SVC mode, so we have to be real...
        BL       swicode_undocumented_patch_characterset
        PullLR                                ; ...paranoid about what happens to R14.

        ;   R0 = source
        ;   R1 = destination
        ;   R2 = row
        ;   R3 = column
        ;   R4 = byte
        ;   R5 = mosaic on/off flag
        ;   R6 = character set
        ;   R7 = scratch register
        ;   R8 = scratch register
        ;
        ; From here-on in, it'll have a STRONG similarity to 'swicode_scanflash'. It is,
        ; essentially, doing the same scan-through. Don't try to make a generic scan-through
        ; routine for these things; the intructions you save here will be lost in figuring
        ; out which thing we're actually doing (unless you do some heinous stack-screwage
        ; by putting your routine branch addresses on the stack!), if nothing else, it'll
        ; make the code a hell of a lot LESS readable. This, at least, makes sense.
        ; (well, it does to ME!)

        Push     "R0-R8, R14"

        MOV      R2, #0                       ; First row (column is set within row loop)
        ADRL     R6, character_set

swicode_converttoiso_rowloop
        MOV      R3, #0                       ; First column
        MOV      R5, #0                       ; mosaic is off; newline implicitly does this

swicode_converttoiso_columnloop
        ; the byte is  source ? ( ( row * 40 ) + column )
        MOV      R8, #40                      ; [this could be optimised]
        MUL      R7, R2, R8                   ; offset  = row * 40
        ADD      R7, R7, R3                   ; offset += column
        LDRB     R4, [R0, R7]                 ; byte = (source + offset)

        ; deweird the byte
        ORR      R4, R4, #&80
        SUB      R4, R4, #&80

        ; is it a mosaic off?
        ; we don't check for >0 for the OBVIOUS reasons! :-)
        CMP      R4, #8
        MOVLT    R5, #0
        MOVLT    R4, #32                      ; patch it out

        ; is it a mosaic on?
        CMP      R4, #24                      ; mosaic on is >15 and <24
        BGE      swicode_converttoiso_checkmosaic
        CMP      R4, #15
        MOVGT    R5, #1
        MOVGT    R4, #32                      ; patch out mosaic code

swicode_converttoiso_checkmosaic
        ; if mosaic is NOT on...
        CMP      R5, #1
        BNE      swicode_converttoiso_skipbyteprocess

        ; pass it here if it is a blast-through character
        ;
        ; These are okay:
        ;    @ABCDEFGHIJKLNMOPQRSTUVWXYZ[\]^_
        ; Anything else should be expunged with extreme prejudice.

        CMP      R4, #'@'                     ; lowest character allowed
        MOVLT    R4, #32                      ; too low, so blank it
        CMP      R4, #'_'                     ; highest character allowed
        MOVGT    R4, #32                      ; too high, so blank it

swicode_converttoiso_skipbyteprocess
        ; we're in the column (inner) loop

        CMP      R4, #127                     ; ensure it is in range 0-127
        SUBGT    R4, R4, #128
        CMP      R4, #32                      ; and strip out control codes
        MOVLT    R4, #32

        ; the game isn't over yet - we need to look up this byte...
        LDRB     R4, [R6, R4]
        ; That was *IT*? Here I am about to wet myself with the sheer excitement
        ; and anticipation and it's ONE lousy instruction? I guess I should be
        ; happy with the beauty and simplicity of it, but... It reminds me of
        ; that hyped-up Sky promotional pay-per-view boxing match that was over
        ; in a matter of seconds. [I don't watch boxing, it made the news!]

        ; By the way, if you're reading the above and scratching your head (don't
        ; do that - your cow-orkers will think you've got bugs); please be aware
        ; that I've had two 500ml beers now, so maybe my wetting myself isn't so
        ; far from the truth (#@&$ing diuretics), and...
        ; I don't know what the hell I'm talking about (yeah, I *so* know what I
        ; am talking about normally!), so if you see any weird comments, blame
        ; them on the beer. Well, it's a good excuse isn't it? The alternative is
        ; for me to admit I'm a fruitcake, but I can't do that since I don't even
        ; *like* fruitcake (or christmas pudding for that matter). I'm more of a
        ; Victoria sponge and doughnut ('donut' if you're American) person.
        ; Remember what I said about me talking crap? This is a good example.
        ; I'll shut up now.


        ; Except to say:
        ;   You know the *really* most depressing, more-depressing-than-every-
        ;   other-depressing-thing thing?
        ;     I think that I write better code when I have alcohol bubbling
        ;     around my braincell (note - singular).
        ; That's f***ed up.


        ; write back the byte to the DESTINATION frame
        ; we know R7 (offset) is still valid

        STRB     R4, [R1, R7]                 ; (dest + offset) = byte

        ADD      R3, R3, #1
        CMP      R3, #40
        BLT      swicode_converttoiso_columnloop

        ; we're in the row (outer) loop
        ADD      R2, R2, #1
        CMP      R2, #26
        BLT      swicode_converttoiso_rowloop

        ; now copy the linked frames, language, etc...
        ; R2 is offset start
        ; R3 is offset end
                                              ;     0 = Frame/data start
        LDR      R2, offset_frameend          ;  1040 = Frame end
        LDR      R3, offset_dataend           ;  1080 = Data end

swicode_converttoiso_tailcopy
        LDRB     R4, [R0, R2]                 ; read from src+offset
        STRB     R4, [R1, R2]                 ; and shove to dest+offset
        ADD      R2, R2, #1
        CMP      R2, R3
        BNE      swicode_converttoiso_tailcopy

        ; we're done...
        Pull     "R0-R8, PC"                  ; implicit return



swicode_converttoansi
        ; SYS "TTXHelper_ConvertToANSI"                                     &57183
        ;
        ; Convert from teletext 7-bit character set to PC-ANSI. Don't ask. :-)
        ;
        ; On entry:
        ;   R0 = Pointer to 'source' frame.
        ;   R1 = Pointer to 'destination' frame.
        ;   R2 = Language (0 = GB; 1 = F; 2 = SWE; 4 = D; 5 = E; 6 = I) [3 & 7 assume GB]
        ;
        ; On exit:
        ;   -
        ;

        B        swicode_unimplemented



swicode_extractline
        ; SYS "TTXHelper_ExtractLine"                                       &57184
        ;
        ; Extract line from data, and trim trailing spaces
        ;
        ; On entry:
        ;   R0 = Pointer to frame data.
        ;   R1 = Line.
        ;
        ; On exit:
        ;   R1 = Next line number, or -1 if no more lines
        ;   R2 = Pointer to line data.
        ;


        ; internally:
        ;   R0 = data
        ;   R1 = line data pointer
        ;   R2 = row     / offset
        ;   R3 = column
        ;   R4 = byte
        ;   R7 = scratch register
        ;   R8 = scratch register
        ;   R9 = Line data offset
        ;
        ; This, too, has a STRONG similarity to 'swicode_scanflash'...

        Push     "R0-R8, R14"

        ; validate line
        CMP      R1, #1                       ; must be in range 1-26
        MOVLT    R1, #1
        CMP      R1, #26
        MOVGT    R1, #26

        ; set up registers
        MOV      R3, #0                       ; set column
        SUB      R2, R1, #1                   ; row (internally we count 0-25, not 1-26)
        ADRL     R1, extract_line_data

swicode_extractline_columnloop
        ; the byte is  source ? ( ( row * 40 ) + column )
        MOV      R8, #40                      ; [this could be optimised]
        MUL      R7, R2, R8                   ; offset  = row * 40
        ADD      R7, R7, R3                   ; offset += column
        LDRB     R4, [R0, R7]                 ; byte = (source + offset)

        ; *********
        ; we SHOULD be working with ISO/ANSI converted data, so we won't alter the byte
        ; *********                          ^^^^^^^^^

        ; save it
        STRB     R4, [R1, R3]                 ; write to (dest + offset)

        ADD      R3, R3, #1
        CMP      R3, #40
        BLT      swicode_extractline_columnloop

        ; now let's get rid of the spaces
swicode_extractline_nukespaces
        SUB      R3, R3, #1
        LDRB     R4, [R1, R3]                 ; get byte
        CMP      R4, #32                      ; is it a space? loop around if so...
        BEQ      swicode_extractline_nukespaces

        ; this byte is NOT a space
        ADD      R3, R3, #1
        MOV      R4, #&0D                     ; write out the BASIC terminator byte
        STRB     R4, [R1, R3]
        ADD      R3, R3, #1
        MOV      R4, #&00                     ; followed by a C terminator byte
        STRB     R4, [R1, R3]

        ; restore registers
        Pull     "R0-R8, R14"

        ; set up registers for return
        CMP      R1, #26                      ; R1 is next line, or -1 if no more
        ADDLT    R1, R1, #1
        MOVEQ    R1, #-1
        ADRL     R2, extract_line_data        ; R2 is pointer to line data

        ; now we can leave...
        Return



        ; macro for the following function...
        MACRO
        swapchar $from, $to
        LDRB     R3, [R1, #$from]
        STRB     R3, [R0, #$to]
        MEND

swicode_undocumented_patch_characterset
        ; SYS <no SWI name>                                                 &57186
        ;
        ; Patch character set to the correct language.
        ;
        ; On entry:
        ;   R2 = Language (0-7 for ISO, 8-15 for ANSI)
        ;
        ; On exit:
        ;   -
        ;
        ; On entry R2 should be the 'language'.
        ; 0-7 for ISO; 8-15 for ANSI.
        ;
        ; Internally:
        ;    R0 = Character set pointer
        ;    R1 = Option set pointer
        ;    R3 = Byte
        ;

        Push     "R0-R3"

        ; which language?
        CMP      R2, #0                       ; charset must be >= 0
        MOVLT    R2, #0
        CMP      R2, #15                      ; charset must be <= 15
        MOVGT    R2, #15

        ADRL     R1, option_lookuptable       ; table base address
        ADD      R1, R1, R2, LSL #2           ; fudge it to point to correct entry
        LDR      R1, [R1]                     ; and load correct pointer
        ADRL     R0, option_lookuptable_base  ; and add it to the actual data base
        ADD      R1, R1, R0                   ;  address to form the proper address

        ; set up the other pointer
        ADRL     R0, character_set

        ; now patch the character table
        ; unrolled code is a hell of a lot easier than being smart with lookups :-)
        ; (but we CAN be smart with macros!)

        swapchar   0,  35                      ; opt[ 0] -> charset[ 35]
        swapchar   1,  36                      ; opt[ 1] -> charset[ 36]
        swapchar   2,  64                      ; opt[ 2] -> charset[ 64]
        swapchar   3,  91                      ; opt[ 3] -> charset[ 91]
        swapchar   4,  92                      ; opt[ 4] -> charset[ 92]
        swapchar   5,  93                      ; opt[ 5] -> charset[ 93]
        swapchar   6,  94                      ; opt[ 6] -> charset[ 94]
        swapchar   7,  95                      ; opt[ 7] -> charset[ 95]
        swapchar   8,  96                      ; opt[ 8] -> charset[ 96]
        swapchar   9, 123                      ; opt[ 9] -> charset[123]
        swapchar  10, 124                      ; opt[10] -> charset[124]
        swapchar  11, 125                      ; opt[11] -> charset[125]
        swapchar  12, 126                      ; opt[12] -> charset[126]

        ; that's all!
        Pull     "R0-R3"
        Return



swicode_undocumented_rawchar_lookup
        ; SYS <no SWI name>                                                 &57187
        ;
        ; Given a teletext byte, in R0, return the equivalent in ISO/ANSI terms.
        ;
        ; NOTE THAT THIS DOES NO PROCESSING TO THE BYTE, it only contrains it to 0-127.
        ; Control characters are converted to spaces.
        ; mosaic characters are NOT handled, this will return the alphanumeric version.
        ; You MUST have previously set up the character table.
        ;
        ; On entry:
        ;   R0 = Byte, in teletext set
        ;
        ; On exit:
        ;   R0 = Byte, in ISO/ANSI set
        ;

        Push     "R1"

        CMP      R0, #127                      ; bring byte into range
        SUBGT    R0, R0, #128

        CMP      R0, #32                       ; remove control codes
        MOVLT    R0, #32

        ADRL     R1, character_set
        LDRB     R0, [R1, R0]                  ; look it up

        Pull     "R1"
        Return



swicode_useinterface
        ; SYS "TTXHelper_UseInterface"                                      &57189
        ;
        ; Instruct TTXHelper to use a specific interface.
        ;
        ; On entry:
        ;   R0 = 0 system IIC; 1 PtrIIC; 2 RiscTV
        ;
        ; On exit:
        ;   R0 = 0, else pointer to error block
        ;

        Push     "R1, R2, R14"

swicode_useinterface_internalentry
        ; sanity check
        CMP      R0, #0                        ; device ID must be in the range 0-2
        ADRLTL   R0, errormsg_interfacerange
        BLT      swicode_useinterfaceerror
        CMP      R0, #2
        ADRGTL   R0, errormsg_interfacerange
        BGT      swicode_useinterfaceerror

        MOV      R2, R0                        ; preserve user's selection

        CMP      R2, #0                        ; user wants to set up system IIC bus
        ADREQL   R1, swiname_systemiic

        CMP      R2, #1                        ; user wants to set up PtrIIC
        ADREQL   R1, swiname_ptriic

        CMP      R2, #2                        ; user wants to set up RiscTV IIC
        ADREQL   R1, swiname_risctviic

        ; Now check this SWI actually exists...
        SWI      XOS_SWINumberFromString
        ADRVSL   R0, errormsg_interfacenotavail
        BVS      swicode_useinterfaceerror

        ; still here?
        ; then let's write the new device ID
        STR      R2, device_identity
        MOV      R0, #0                        ; allesgt

        Pull     "R1, R2, PC"                  ; implicit RETURN

        ; It makes sense to check for PtrIIC and RiscTV, but why bother to check that the
        ; system IIC bus exists 'toujours'? Actually, that isn't entirely correct.
        ; Take, for instance, the "Bush box". The only time we see "iic" in the entire ROM
        ; image is several instances of the string "asciicircum" in the font encodings.
        ; The Bush box has *no* IIC bus! [it has an almost-but-not-IIC NVRAM without an RTC]

swicode_useinterfaceerror
        Pull     "R1, R2, R14"
        SetV
        Return



swicode_readbytes
        ; SYS "TTXHelper_ReadBytes"                                         &5718A
        ;
        ; Read byte(s) from receiver memory.
        ;
        ; On entry:
        ;   R0 = Row position
        ;   R1 = Column position
        ;   R2 = Pointer to data buffer
        ;   R3 = Number of bytes to read
        ;
        ; On exit:
        ;   R0 = 0, or pointer to error block.
        ;

        ; Internally:
        ;   R0 = Row
        ;   R1 = Column
        ;   R2 = Data buffer
        ;   R3 = Byte counter

        Push     "R1-R5, R14"

        ; Validate entry parameters
        BL       validate_txrx_parameters

        ; Set chapter zero (use TTXHelper_ReadChapter if you want ghost row data)
        Push     "R0"
        MOV      R0, #0
        BL       set_chapter
        Pull     "R1", VS                      ; get back stacked R0 but discard it...
        BVS      swicode_readwritebyteserror
        Pull     "R0"

swicode_readbytes_internalentry
        ; Set up row and column
        BL       set_row_and_col
        BVS      swicode_readwritebyteserror

        ; Now read into the buffer
        MOV      R0, #_TTX_READ
        MOV      R1, R2
        MOV      R2, R3
        BL       iic_transfer
        BVS      swicode_readwritebyteserror

        MOV      R0, #0
        Pull     "R1-R5, PC"                   ; implicit RETURN



swicode_writebytes
        ; SYS "TTXHelper_WriteBytes"                                        &5718B
        ;
        ; Write byte(s) to receiver memory.
        ;
        ; On entry:
        ;   R0 = Row position
        ;   R1 = Column position
        ;   R2 = Pointer to data buffer
        ;   R3 = Number of bytes to write
        ;
        ; On exit:
        ;   R0 = 0, or pointer to error block.
        ;

        ; Internally:
        ;   R0 = Row / offset
        ;   R1 = Column
        ;   R2 = Data buffer
        ;   R3 = Byte counter

        Push     "R1-R5, R14"

        ; Validate entry parameters
        BL       validate_txrx_parameters

        ADD      R3, R3, #1                    ; so the offsets work...

        Push     "R0"
        MOV      R0, #0
        BL       set_chapter
        Pull     "R1", VS                      ; get back stacked R0 but discard it...
        BVS      swicode_readwritebyteserror
        Pull     "R0"

swicode_writebytes_internalentry
        ; now we cannot expect the user to offset their data block by 3 to accomodate
        ; us, so we have to handle this for ourselves...
        Push     "R0-R3"
        MOV      R0, #6                        ; OS_Module 6 = Claim
        ADD      R3, R3, #3
        SWI      XOS_Module
        ; we really should error check this :-)

        MOV      R4, R2                        ; remember our block
        Pull     "R0-R3"

         ; set up row/column
        STRB     R0, [R4, #1]                  ; row
        STRB     R1, [R4, #2]                  ; column
        ; (this frees up R0 and R1)
        MOV      R0, #9
        STRB     R0, [R4, #0]

        ; copy across the data
        MOV      R0, #0                        ; offset into user's block
        MOV      R1, #3                        ; offset into our block

swicode_writebytes_copyloop
        LDRB     R5, [R2, R0]
        STRB     R5, [R4, R1]
        ADD      R0, R0, #1
        ADD      R1, R1, #1
        SUBS     R3, R3, #1
        BNE      swicode_writebytes_copyloop

        ; now write it
        MOV      R2, R1                        ; we can use that offset! :-)
        MOV      R1, R4                        ; R4 is our block pointer
        MOV      R0, #_TTX_WRITE
        BL       iic_transfer
        ; we do NOT error check as we can assume that IIC missing would have failed at chapset.

        MOV      R0, #7                        ; OS_Module 7 = Free
        MOV      R2, R4
        SWI      XOS_Module

        MOV      R0, #0
        Pull     "R1-R5, PC"                   ; implicit RETURN



swicode_readregister
        ; SYS "TTXHelper_ReadRegister"                                      &5718C
        ;
        ; Read one (or more) SAA524x register(s).
        ;
        ; On entry:
        ;   R0 = Register
        ;   R1 = Pointer to data buffer
        ;   R2 = Number of bytes to read
        ;
        ; On exit:
        ;   R0 = 0, or pointer to error block.
        ;

        ; Internally:
        ;   R0 = Scratch
        ;   R1 = Scratch
        ;   R2 = Scratch
        ;   R3 = Scratch
        ;   R4 = Register to read from
        ;   R5 = User block
        ;   R6 = Number of bytes to copy

        Push     "R1-R8, R14"                  ; so we can use the same exit as writereg

        ; Set up registers
        MOV      R4, R0
        MOV      R5, R1
        MOV      R6, R2

        ; Set up the EuroCCT register we want to read...
        ADRL     R1, iic_data
        STRB     R4, [R1]

        MOV      R0, #_TTX_WRITE
                 ; R1 already set up
        MOV      R2, #1                        ; we're only sending ONE byte!
        BL       iic_transfer
        BVS      swicode_readwriteregerror

        ; Now read from it
        MOV      R0, #_TTX_READ
        MOV      R1, R5                        ; our buffer
        MOV      R2, R6                        ; bytes to read
        BL       iic_transfer
        BVS      swicode_readwriteregerror

        MOV      R0, #0                        ; flag it was okay
        Pull     "R1-R8, PC"                   ; implicit RETURN



swicode_writeregister
        ; SYS "TTXHelper_WriteRegister"                                     &5718D
        ;
        ; Write one (or more) SAA524x register(s).
        ;
        ; On entry:
        ;   R0 = Register
        ;   R1 = Pointer to data buffer
        ;   R2 = Number of bytes to write
        ;
        ; On exit:
        ;   R0 = 0, or pointer to error block.
        ;

        ; Internally:
        ;   R0 = Scratch
        ;   R1 = Scratch
        ;   R2 = Scratch
        ;   R3 = Scratch
        ;   R4 = User block
        ;   R5 = Our block
        ;   R6 = Pointer into user block
        ;   R7 = Pointer into our block / register to write
        ;   R8 = Number of bytes to copy

        Push     "R1-R8, R14"

        ; Set up initial registers
        MOV      R7, R0
        MOV      R4, R1
        MOV      R8, R2

        ; Get ourselves a block of memory
        MOV      R0, #6                        ; OS_Module 6 = Claim
        MOV      R3, R8                        ; includes our +1 :-)
        SWI      XOS_Module
        ; (we really should error check this)
        MOV      R5, R2

        ; Set up the initial register
        STRB     R7, [R5, #0]

        ; Copy across the data
        MOV      R6, #0                        ; offset into user's block
        MOV      R7, #1                        ; offset into our block

swicode_writeregister_copyloop
        LDRB     R0, [R4, R6]
        STRB     R0, [R5, R7]
        ADD      R6, R6, #1
        ADD      R7, R7, #1
        SUBS     R8, R8, #1
        BNE      swicode_writeregister_copyloop

        ; Write it
        MOV      R0, #_TTX_WRITE
        MOV      R1, R5
        MOV      R2, R7                        ; there's a crafty way to do it!
        BL       iic_transfer
        BVS      swicode_readwriteregerror

        MOV      R0, #7                        ; OS_Module 7 = Free
        MOV      R2, R5
        SWI      XOS_Module

        MOV      R0, #0                        ; flag it's all okay
        Pull     "R1-R8, PC"                   ; implicit RETURN



swicode_readchapter
        ; SYS "TTXHelper_ReadChapter"                                       &5718E
        ;
        ; Raw-read a chapter (1024 bytes) directly from the receiver memory.
        ;
        ; On entry:
        ;   R0 = Chapter to read, 0 or 4
        ;   R1 = Pointer to data buffer
        ;
        ; On exit:
        ;   R0 = 0, or pointer to error block passed on from IIC call.
        ;
        ; Standard page memory and control bits is on chapter 0.
        ; Extension 'ghost' rows is on chapter 4.

        Push     "R1-R3, R14"

        CMP      R0, #0                        ; chapters 0 and 4 only
        CMPNE    R0, #4
        BNE      swicode_readchaptererror

        ; Set chapter
        BL       set_chapter
        Pull     "R1-R3, PC", VS               ; implicit RETURN

        ; Set up for internal read-bytes operation
        MOV      R3, #1024                     ; length
        MOV      R2, R1                        ; buffer
        MOV      R1, #0                        ; column    ** INTERNALLY WE COUNT FROM 0
        MOV      R0, #0                        ; row       ** COUNTING FROM 1 IS ONLY THE UI

        ; Do it!
        ; We cannot call our own SWI as it'll force chapter zero.
        Push     "R1-R5, PC"                   ; mimic the stack condition expected by readbytes
        B        swicode_readbytes_internalentry
        NOP
        ; readbytes returns here...
        NOP
        ; maybe here on a StrongARM?

        ; Force us back to chapter 0 so we don't bugger up other teletexty things
        MOV      R0, #0
        BL       set_chapter

        MOVVC    R0, #0
        Pull     "R1-R3, PC"                   ; implicit RETURN

swicode_readchaptererror
        ADR      R0, errormsg_chapterinvalid
        SetV
        Pull     "R1-R3, PC", NE               ; implicit RETURN



swicode_cleartsdp
        ; SYS "TTXHelper_ClearTSDP"                                         &5718F
        ;
        ; Blank out the TSDP data bytes.
        ;
        ; On entry:
        ;   -
        ;
        ; On exit:
        ;   R0 = 0, or pointer to error block.
        ;

        Push     "R1-R3, R14"

        ; Set chapter
        MOV      R0, #4
        BL       set_chapter
        Pull     "R1-R3, PC", VS               ; implicit RETURN

        ; Set data to be a bunch of blanks
        MOV      R0, #' '                      ; so we remember it's a space! :-)
        ADRL     R1, extract_line_data
        MOV      R2, #0
swicode_cleartsdp_loop
        STRB     R0, [R1, R2]
        ADD      R2, R2, #1
        CMP      R2, #40
        BLT      swicode_cleartsdp_loop

        ; Set it up for writebytes, then do the call...
        MOV      R3, #40                       ; length to write
        MOV      R2, R1                        ; buffer
        MOV      R1, #0                        ; column    ** INTERNALLY WE COUNT FROM 0
        MOV      R0, #23                       ; row       ** COUNTING FROM 1 IS ONLY THE UI

        ; We cannot call our own SWI as it'll force chapter zero.
        Push     "R1-R5, PC"
        B        swicode_writebytes_internalentry
        NOP
        NOP

        ; Force us back to chapter 0 so we don't bugger up other teletexty things
        MOV      R0, #0
        BL       set_chapter

        MOVVC    R0, #0
        Pull     "R1-R3, PC"                   ; implicit RETURN



        ; **************************
        ;
        ; CENTRALISED ABORT HANDLERS
        ;
        ; **************************

swicode_readwritebyteserror
        Pull     "R1-R5, R14"
        SetV
        Return



swicode_readwriteregerror
        Pull     "R1-R8, R14"
        ADR      R0, errormsg_readwritereg     ; it's all the same error...
        SetV
        Return



swicode_unimplemented
        ; Handle 'unimplemented' messages
        Push     "R0-R2, R14"
        ; provide the user with some useful feedback!
        ; this code is a little peculiar, and yes, we write to ourself!
        ; pay attention! :-)
        LDR      R0, our_swichunk
        ADD      R0, R0, R11                  ; R11 is SWI number AND 63; given by RISC OS
        ADR      R1, errormsg_unimptail
        MOV      R2, #8
        SWI      XOS_ConvertHex6
        ; replace the terminator byte with a closing bracket
        MOV      R0, #')'
        STRB     R0, [R1]
        ; replace the initial zero (unrequired) with the '&' that we want
        MOV      R0, #'&'
        ADR      R1, errormsg_unimptail
        STRB     R0, [R1]
        ; force a resync (just in case...)
        MOV      R0, #1
        ADRL     R1, entrypoint
        ADRL     R2, the_end_of_the_world
        SWI      &2006E                       ; XOS_SynchroniseCodeAreas
        ; we're done!
        Pull     "R0-R2, R14"

        ; set it up as an error and get the hell out of here!
        ADR      R0, errormsg_unimplemented
        SetV
        Return



        ; **********************************
        ;
        ; MISCELLANEOUS INTERNAL ROUTINES...
        ;
        ; **********************************


validate_txrx_parameters
        ; Assumes:
        ;   R0 = Row
        ;   R1 = Column
        ;   R3 = Bytes

        ; Validate row
        CMP      R0, #1
        ADRLT    R0, errormsg_txrxrowinvalid
        BLT      swicode_readwritebyteserror
        CMP      R0, #24
        ADRGT    R0, errormsg_txrxrowinvalid
        BGT      swicode_readwritebyteserror

        SUB      R0, R0, #1                   ; hardware uses 0...23, not 1 to 24

        ; Validate column
        CMP      R1, #1
        ADRLT    R0, errormsg_txrxcolinvalid
        BLT      swicode_readwritebyteserror
        CMP      R1, #40
        ADRGT    R0, errormsg_txrxcolinvalid
        BGT      swicode_readwritebyteserror

        SUB      R1, R1, #1                   ; hardware uses 0...39, not 1 to 40

        ; Validate data bytes
        CMP      R3, #1
        ADRLT    R0, errormsg_txrxbytesinvalid
        BLT      swicode_readwritebyteserror
        CMP      R3, #1024
        ADRGT    R0, errormsg_txrxbytesinvalid
        BGT      swicode_readwritebyteserror

        Return



set_chapter
        ; R0 = chapter to set, 0 or 4
        ; WE DO NOT PRESERVE R0, SO WE CAN PASS ON ANY ERROR MESSAGES GENERATED.
        Push     "R1-R2, R14"

        ADRL     R1, iic_data
        MOV      R2, #8                       ; write register #8 to set chapter
        STRB     R2, [R1, #0]
        STRB     R0, [R1, #1]                 ; and the chapter number...

        MOV      R0, #_TTX_WRITE
        MOV      R2, #2
        BL       iic_transfer

        Pull     "R1-R2, PC"                  ; implicit RETURN



set_row_and_col
        ; R0 = Row
        ; R1 = Column
        ; WE DO NOT PRESERVE R0, SO WE CAN PASS ON ANY ERROR MESSAGES GENERATED.
        Push     "R1-R3, R14"

        ADRL     R3, iic_data
        MOV      R2, #9                       ; write register #9 to set row
        STRB     R2, [R3, #0]
        STRB     R0, [R3, #1]                 ; row
        STRB     R1, [R3, #2]                 ; column

        MOV      R0, #_TTX_WRITE
        MOV      R1, R3
        MOV      R2, #3
        BL       iic_transfer

        Pull     "R1-R3, PC"                  ; implicit RETURN



iic_transfer
        ; Send/receive something on the IIC bus
        ; On entry:
        ;   R0 = Device ID, corrected for read/write direction
        ;   R1 = Pointer to data
        ;   R2 = Bytes to transfer
        ; WE DO NOT PRESERVE R0, SO WE CAN PASS ON ANY ERROR MESSAGES GENERATED.

        Push     "R0"

        LDR      R0, device_identity

        CMP      R0, #_DEV_RTVIIC
        BEQ      iic_transfer_risctviic

        CMP      R0, #_DEV_PTRIIC
        BEQ      iic_transfer_ptriic


        ; assume if we come through to here that it is the system IIC bus
iic_transfer_sysiic
        Pull     "R0"
        Push     "R1-R2, R14"
        SWI      XIIC_Control
        Pull     "R1-R2, PC"                  ; implicit RETURN


iic_transfer_ptriic
        Pull     "R0"
        Push     "R1-R2, R14"
        SWI      XPtrIIC_Control
        Pull     "R1-R2, PC"                  ; implicit RETURN


iic_transfer_risctviic
        Pull     "R0"
        Push     "R1-R3, R14"
        MOV      R3, R2                       ; RiscTV's IIC is NOT the same as system IIC
        MOV      R2, R1
        MOV      R1, R0
        MOV      R0, #0
        SWI      XRiscTV_IICControl
        Pull     "R1-R3, PC"                  ; implicit RETURN



        ; ************
        ;
        ; DATA FOLLOWS
        ;
        ; ************

offset_framestart
offset_datastart                              ; for consistency
        &        0

offset_frameend
        &        1040                         ; this is +1 so use BLT...

offset_dataend
        &        1080                         ; this is +1 so use BLT...

offset_page_crc
        &        1068

offset_thisframe_number
        &        1072                         ; frame is 'entire' thing

offset_thispage_language
        &        1076                         ; page is rows 1-26 (i.e. not header)

offset_thisheader_language
        &        1077                         ; header is, well... the header...

my_identity
        =        "Rick"

our_swichunk
        &        SWICHUNK

device_identity
        &        _DEV_SYSIIC                  ; assume system IIC if otherwise unset

swiname_ptriic
        EQUS     "Ptr"                        ; look here for PtrIIC...    [use ADRL]
swiname_systemiic
        EQUSZ    "IIC_Control"                ; or here for plain IIC...   [use ARDL]

swiname_risctviic
        EQUSZA   "RiscTV_IICControl"



        ; ***************
        ;
        ; MESSAGES FOLLOW
        ;
        ; ***************

errormsg_invalidswi
        EQUD     (ERRCHUNK + 0)
        EQUSZA   "Unknown TTXHelper SWI"

errormsg_interfacerange
        EQUD     (ERRCHUNK + 1)
        EQUSZA   "Invalid device ID passed to TTXHelper_UseInterface"

errormsg_interfacenotavail
        EQUD     (ERRCHUNK + 2)
        EQUSZA   "No hardware/software support for device ID passed to TTXHelper_UseInterface"

errormsg_txrxbytesinvalid
        EQUD     (ERRCHUNK + 3)
        EQUSZA   "Invalid number of bytes to transfer (should be 1 - 1024)"

errormsg_txrxrowinvalid
        EQUD     (ERRCHUNK + 4)
        EQUSZA   "Invalid row number (should be 1 - 24)"

errormsg_txrxcolinvalid
        EQUD     (ERRCHUNK + 5)
        EQUSZA   "Invalid column number (should be 1 - 40)"

errormsg_readwritereg
        EQUD     (ERRCHUNK + 6)
        EQUSZA   "Error while reading/writing register"

errormsg_chapterinvalid
        EQUD     (ERRCHUNK + 7)
        EQUSZA   "Invalid chapter (should be 0 or 4)"

errormsg_unimplemented
        EQUD     (ERRCHUNK + 8)
        EQUS     "This TTXHelper SWI ("
errormsg_unimptail
        EQUSZA   "&.....) is not currently implemented."


RICKTTX * 0
licencehelp
        =        "This module is Copyright  2004 Rick Murray.", 13
        [ RICKTTX = 0
        =        "It may be used and redistributed according to the licence which should have "
        =        "been supplied alongside this module.", 13
        |
        =        "This module is intended for use within my !Teletext software.", 13
        =        9, "**** DO NOT (RE)DISTRIBUTE THIS COPY OF THE TTXHELPER MODULE ****", 13
        =        "If you wish to use this module in your OWN applications, please download a "
        =        "copy from my website (URL below) and read the 'Licence' file supplied.", 13
        =        "(it'd also be useful to read the documentation, too!)", 13
        ]
        =        "You can obtain the latest version of this module, plus my other Teletext-"
        =        "related software, at:", 13
        =        9, "http://www.heyrick.co.uk/software/ttx/", 13, 0
        ALIGN

probehelp
        =        "This command will probe for a connected teletext receiver.", 13
        =        "When it finds one, it will tell you some things about it.", 13, 0
        ALIGN

viewhelp
        =        "Syntax: TTXHelper_View <filename>", 13
        =        "Allows you to view saved teletext frames on-screen.", 13, 0
        ALIGN

view_wimpstate
        EQUSZA   "Wimp$State"

view_wimperror
        EQUD     (ERRCHUNK + 9)
        =        "Sorry, you cannot view teletext frames in the desktop environment. Please go into the single-tasking command line [F12] and try again.", 13, 13, 0
        ALIGN


        ; THESE STRINGS ARE NOT WORD ALIGNED, SO ACCESS *ONLY* USING ADRL...
probe_found
        =        "Found teletext receiver...", 13, 0
probe_unfound
        EQUSZ    "Unable to detect any teletext receiving hardware."
probe_compatible
        EQUSZ    "  Apparently compatible with "
probe_groundcontrol
        EQUSZ    "the Ground Control/Octopus UTA-"
probe_interface
        EQUSZ    "  Connected via "
probe_sysiic
        EQUSZ    "System IIC."
probe_ptriic
        EQUSZ    "PtrIIC (parallel)."
probe_rtviic
        EQUSZ    "RiscTV's IIC."
probe_memory
        EQUSZ    "  Receiver internal memory is "
probe_memorytail
        EQUSZ    " bytes."
probe_memorynot
        EQUSZ    "  Memory test not performed."
probe_unknown
        EQUSZ    "unrecognised ("
probe_unknowntail
        EQUSZA   ")."



        ; *********************
        ;
        ; CHARACTER SET FOLLOWS
        ;
        ; *********************

character_set                                 ; *BASE* character set
        ; All '' bytes are replaced by national option characters
        ; control characters (0 - 127)
        =        ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
        =        ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
        =        ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
        =        ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '
        ; plain ASCII (raw; lower block)
        =        ' ', '!', &22, '', '', '%', '&', &27 ; &22 = " and &27 = '
        =        '(', ')', '*', '+', ',', '-', '.', '/'
        =        '0', '1', '2', '3', '4', '5', '6', '7'
        =        '8', '9', ':', ';', '<', '=', '>', '?'
        =        '', 'A', 'B', 'C', 'D', 'E', 'F', 'G'
        =        'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O'
        =        'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W'
        =        'X', 'Y', 'Z', '', '', '', '', ''
        =        '', 'a', 'b', 'c', 'd', 'e', 'f', 'g'
        =        'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'
        =        'p', 'q', 'r', 's', 't', 'u', 'v', 'w'
        =        'x', 'y', 'z', '', '', '', '', ' '
        ; THERE IS NO UPPER BLOCK, as it is merely a copy
        ; of the lower block - so ensure ALL lookups are
        ; in the range 0-127.

lutb    ; lutb = option lookup table base
option_lookuptable_base

option_english_iso
        =        '', '$', '@', ' ', '', ' ', ' ', '#', '', '', '|', '', ''
        ALIGN

option_french_iso
        =        '', '', '', '', '', '', '', '#', '', '', '', '', ''
        ALIGN

option_swedish_iso
        =        '#', '', '', '', '', '', '', '_', '', '', '', '', ''
        ALIGN

option_german_iso
        =        '#', '$', '@', '', '', '', '^', '_', '', '', '', '', ''
        ALIGN

option_spanish_iso
        =        '', '$', '', '', '', '', '', '', '', '', '', '', ''
        ALIGN

option_italian_iso
        =        '', '$', '', '', '', ' ', ' ', '#', '', '', '', '', ''
        ALIGN

option_english_ansi
        =        '', '$', '@', '', '', '', ' ', '#', '', '', '', ' ', ''
        ALIGN

option_french_ansi
        =        '', '', '', '', '', '', '', '#', '', '', '', '', ''
        ALIGN

option_swedish_ansi
        =        '#', 'O', '', '', '', '', '', '_', '', '', '', '', ''
        ALIGN

option_german_ansi
        =        '#', '$', ' ', '', '', '', '^', '_', '', '', '', '', ''
        ALIGN

option_spanish_ansi
        =        '', '$', '', '', '', '', '', '', '', '', '', '', ''
        ALIGN

option_italian_ansi
        =        '', '$', '', '', '', ' ', ' ', '#', '', '', '', '', ''
        ALIGN

option_lookuptable
        ; IN THE ORIGINAL EUROPEAN / WESTERN-EUROPEAN VERSION:
        ;   The option sets 0, 1, 2, 4, 5, and 6 are officially defined.
        ;   Option set 3 is mentioned in some sources as being Czech, but nothing
        ;   that I've seen has supported this. Option set 7 is undefined.
        ;   Selection option sets 3 or 7 will revert to English.
        ;
        &        (option_english_iso - lutb)  ;  0 - english ISO
        &        (option_french_iso - lutb)   ;  1 - french ISO
        &        (option_swedish_iso - lutb)  ;  2 - swedish ISO
        &        (option_english_iso - lutb)  ;  3 - INVALID; assume english ISO
        &        (option_german_iso - lutb)   ;  4 - german ISO
        &        (option_spanish_iso - lutb)  ;  5 - spanish ISO
        &        (option_italian_iso - lutb)  ;  6 - italian ISO
        &        (option_english_iso - lutb)  ;  7 - INVALID; assume english ISO

        &        (option_english_ansi - lutb) ;  8 - english ANSI
        &        (option_french_ansi - lutb)  ;  9 - french ANSI
        &        (option_swedish_ansi - lutb) ; 10 - swedish ANSI
        &        (option_english_ansi - lutb) ; 11 - INVALID; assume english ANSI
        &        (option_german_ansi - lutb)  ; 12 - german ANSI
        &        (option_spanish_ansi - lutb) ; 13 - spanish ANSI
        &        (option_italian_ansi - lutb) ; 14 - italian ANSI
        &        (option_english_ansi - lutb) ; 15 - INVALID; assume english ANSI



        ; *******************************
        ;
        ; MISCELLANEOUS SCRATCH DATA AREA
        ;
        ; *******************************

iic_data
        =        0, 0, 0, 0, 0, 0, 0, 0

extract_line_data                             ; 64 bytes
        =        "DATADATADATADATADATADATADATADATADATADATADATADATADATADATADATADATA"

        =        10, 10, FULLVERS, 10

the_end_of_the_world
        END
