; Copyright 2009 Castle Technology Ltd
;
; 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.
;

        GET     Hdr:ListOpts
        GET     Hdr:Macros
        GET     Hdr:System
        GET     Hdr:Machine.<Machine>
        GET     Hdr:ImageSize.<ImageSize>
        $GetIO
        GET     Hdr:Proc

        GET     Hdr:OSEntries
        GET     Hdr:HALEntries

        GET     hdr.omap3530
        GET     hdr.StaticWS
        GET     hdr.PRCM
        GET     hdr.GPIO
        GET     hdr.CoPro15ops
        GET     hdr.Timers

        AREA    |Asm$$Code|, CODE, READONLY, PIC

        EXPORT  Video_init
        EXPORT  HAL_VideoFlybackDevice
        EXPORT  HAL_Video_SetMode
        EXPORT  HAL_Video_WritePaletteEntry
        EXPORT  HAL_Video_WritePaletteEntries
        EXPORT  HAL_Video_ReadPaletteEntry
        EXPORT  HAL_Video_SetInterlace
        EXPORT  HAL_Video_SetBlank
        EXPORT  HAL_Video_SetPowerSave
        EXPORT  HAL_Video_UpdatePointer
        EXPORT  HAL_Video_SetDAG
        EXPORT  HAL_Video_VetMode
        EXPORT  HAL_Video_PixelFormats
        EXPORT  HAL_Video_Features
        EXPORT  HAL_Video_BufferAlignment
        EXPORT  HAL_Video_OutputFormat

 [ :LNOT: HALDoesVideo
        EXPORT   VideoDevice_Init
        EXPORT   Video_Power_VBC_DVI
        EXPORT   Video_Power_VBC_Pandora
        EXPORT   Video_Power_VBC_TouchBook
 ]

        IMPORT  vtophys
        IMPORT  |__rt_udiv|
        IMPORT  DebugHALPrint
        IMPORT  DebugHALPrintReg
        IMPORT  PRCM_GetFreqSel
        IMPORT  HAL_CounterDelay
        IMPORT  DebugMemDump
        IMPORT  memcpy
        IMPORT  GPIOx_SetAsOutput
        IMPORT  GPIOx_SetOutput

        MACRO
        CallOS  $entry
        ASSERT  $entry <= HighestOSEntry
        MOV     lr, pc
        LDR     pc, OSentries + 4*$entry
        MEND

; A brief rundown of OMAP3530 video output modes:
; RFBI - Parallel interface using MIPI DBI protocol. Supports two RFBI-compliant LCD panels
; RFBI bypass - Parallel interface using MIPI DPI protocol
; SDI - Serial interface for TI FlatLink 3G panels (and for those alone?)
; DSI - Serial interface for other types of LCD panel. Supports multiple panels via mix of video & command modes.
; TV out - Supports composite & S-Video, PAL & NTSC

; Currently this code only supports the RFBI bypass mode, as used to interface with the TFP410 DVI framer used on the beagleboard. Luckily this is also the simplest interface of the lot.

; A note on pixel clock sources:
; There are two pixel clock sources - one from DPLL4 in the PRCM, and the DSI PLL. DPLL4 provides the source for multiple clocks, so changing its frequency at runtime would cause many problems. So we use the DSI PLL instead, as its clock output only affects the pixel clock in the display controller (which we do use) and the DSI protocol engine (which we don't use for RFBI bypass mode)

 [ HALDoesVideo
DSI_BPP     *   24 ; BPP of the data sent out by DSI
DSI_LANES   *   3  ; Number of data lanes used by DSI
 ]

; TFP410 power is controlled via GPIO 170
;TFP410_GPIO * 170 -> gone to board config

; Max pixel rate:
; - DSI1_PLL_FCLK is limited to 173MHz (spruf98b p2113). Combine with minimum LCD & PCD values and you get a max pixel rate of 86.5MHz (173/2)
; - However spruf98b p2008 says the max pixel rate is 75MHz
; - Beagleboard ref. manual says 65MHz (but TFP410 datasheet says 165MHz?)

; For the moment, limit the pixel rate to 75MHz. This could probably be increased to 86.5 if some of the FIFO fixes were brought over from the OMAPVideo module.
 [ HALDoesVideo
MAX_PIXEL_RATE * 75000
 ]

; Clock flow:
; sys_clk -> DSS2_ALWON_FCLK -> DSI PLL -> DSI1_PLL_FCLK, DSI2_PLL_FCLK (173MHz max)
; DSI1_PLL_FCLK -> DISPC_FCLK -> LCD -> PCD (75MHz max)
; DSI2_PLL_FCLK -> DSI_FCLK. Must be > byte clock(?), L4 interface clock, video port clock. But we don't care for RFBI bypass.
; -----------------------------------------------------------------------------------

 [ HALDoesVideo

        MACRO
        SetRegBits $r,$bits
        LDR     a2, [a1, #$r]
        ORR     a2, a2, #$bits
        STR     a2, [a1, #$r]
        MEND

        MACRO
        DumpReg $r
        DebugTX "$r"
        LDR     a1, =$r
        LDR     a1, [a3, a1]
        DebugReg a1
        MEND

                GBLL    VideoDebug
VideoDebug      SETL    {TRUE}

                GBLL    ExtraVideoDebug
ExtraVideoDebug SETL    {FALSE}

                GBLL    VetModeDebug
VetModeDebug    SETL    {FALSE}

                ; Set to TRUE to disable all changes to video output settings
                GBLL    PassiveVideo
PassiveVideo    SETL    {FALSE}

        MACRO
        SetGoBits $base,$temp,$temp2,$str
;        MOV     $temp2, #1024*1024 ; How long is typical wait time? Do we need to wait for vsync or what?
;5
;        LDR     $temp, [$base, #DISPC_CONTROL]
;        TST     $temp, #&1:SHL:5 ; Only test GOLCD, since EVSYNC doesn't happen often enough on beagleboard?
;        BEQ     %FT10
;        SUBS    $temp2, $temp2, #1
;        BNE     %BT5
; [ VideoDebug
;        Push "lr" ; Just in case
;        DebugTX $str
;        DebugReg $temp
;        Pull "lr"
; ]
;10
;        ORR     $temp, $temp, #&3:SHL:5
;        STR     $temp, [$base, #DISPC_CONTROL]
        ; New SetGoBits: Rather than blocking on the update and clashing with UpdatePointer, we just set a flag which UpdatePointer will monitor
        ; Only downside is that if interrupts are disabled for a long time, nothing will happen. But long stretches without interrupts is generally a bad thing anyway.
        MOV     $temp, #3:SHL:5
        STR     $temp, video_gobits
        MEND


 [ ExtraVideoDebug
        LTORG

DumpDispcRegs
      Push    "a1-a3,lr"
      LDR     a3, L4_Display_Log
      DumpReg DSS_SYSCONFIG
      DumpReg DSS_SYSSTATUS
      DumpReg DSS_IRQSTATUS
      DumpReg DSS_CONTROL
      DumpReg DSS_SDI_CONTROL
      DumpReg DSS_PLL_CONTROL
      DumpReg DSS_SDI_STATUS
      DumpReg DISPC_SYSCONFIG
      DumpReg DISPC_SYSTATUS
      DumpReg DISPC_IRQSTATUS
      DumpReg DISPC_IRQENABLE
      DumpReg DISPC_CONTROL
      DumpReg DISPC_CONFIG
      DumpReg DISPC_DEFAULT_COLOR0
      DumpReg DISPC_DEFAULT_COLOR1
      DumpReg DISPC_TRANS_COLOR0
      DumpReg DISPC_TRANS_COLOR1
      DumpReg DISPC_LINE_STATUS
      DumpReg DISPC_LINE_NUMBER
      DumpReg DISPC_TIMING_H
      DumpReg DISPC_TIMING_V
      DumpReg DISPC_POL_FREQ
      DumpReg DISPC_DIVISOR
      DumpReg DISPC_GLOBAL_ALPHA
      DumpReg DISPC_SIZE_DIG
      DumpReg DISPC_SIZE_LCD
      DumpReg DISPC_GFX_BA0
      DumpReg DISPC_GFX_BA1
      DumpReg DISPC_GFX_POSITION
      DumpReg DISPC_GFX_SIZE
      DumpReg DISPC_GFX_ATTRIBUTES
      DumpReg DISPC_GFX_FIFO_THRESHOLD
      DumpReg DISPC_GFX_FIFO_SIZE_STATUS
      DumpReg DISPC_GFX_ROW_INC
      DumpReg DISPC_GFX_PIXEL_INC
      DumpReg DISPC_GFX_WINDOW_SKIP
      DumpReg DISPC_GFX_TABLE_BA
      DumpReg DISPC_VID1+DISPC_VIDn_BA0
      DumpReg DISPC_VID1+DISPC_VIDn_BA1
      DumpReg DISPC_VID1+DISPC_VIDn_POSITION
      DumpReg DISPC_VID1+DISPC_VIDn_SIZE
      DumpReg DISPC_VID1+DISPC_VIDn_ATTRIBUTES
      DumpReg DISPC_VID1+DISPC_VIDn_FIFO_THRESHOLD
      DumpReg DISPC_VID1+DISPC_VIDn_FIFO_SIZE_STATUS
      DumpReg DISPC_VID1+DISPC_VIDn_ROW_INC
      DumpReg DISPC_VID1+DISPC_VIDn_PIXEL_INC
      DumpReg DISPC_VID1+DISPC_VIDn_FIR
      DumpReg DISPC_VID1+DISPC_VIDn_PICTURE_SIZE
      DumpReg DISPC_VID1+DISPC_VIDn_ACCU0
      DumpReg DISPC_VID1+DISPC_VIDn_ACCU1
      DumpReg DISPC_VID1+DISPC_VIDn_FIR_COEF_H0
      DumpReg DISPC_VID1+DISPC_VIDn_FIR_COEF_HV0
      DumpReg DISPC_VID1+DISPC_VIDn_FIR_COEF_H1
      DumpReg DISPC_VID1+DISPC_VIDn_FIR_COEF_HV1
      DumpReg DISPC_VID1+DISPC_VIDn_FIR_COEF_H2
      DumpReg DISPC_VID1+DISPC_VIDn_FIR_COEF_HV2
      DumpReg DISPC_VID1+DISPC_VIDn_FIR_COEF_H3
      DumpReg DISPC_VID1+DISPC_VIDn_FIR_COEF_HV3
      DumpReg DISPC_VID1+DISPC_VIDn_CONV_COEF0
      DumpReg DISPC_VID1+DISPC_VIDn_CONV_COEF1
      DumpReg DISPC_VID1+DISPC_VIDn_CONV_COEF2
      DumpReg DISPC_VID1+DISPC_VIDn_CONV_COEF3
      DumpReg DISPC_VID1+DISPC_VIDn_CONV_COEF4
      B %FT10
      LTORG
10
      DumpReg DISPC_VID2+DISPC_VIDn_BA0
      DumpReg DISPC_VID2+DISPC_VIDn_BA1
      DumpReg DISPC_VID2+DISPC_VIDn_POSITION
      DumpReg DISPC_VID2+DISPC_VIDn_SIZE
      DumpReg DISPC_VID2+DISPC_VIDn_ATTRIBUTES
      DumpReg DISPC_VID2+DISPC_VIDn_FIFO_THRESHOLD
      DumpReg DISPC_VID2+DISPC_VIDn_FIFO_SIZE_STATUS
      DumpReg DISPC_VID2+DISPC_VIDn_ROW_INC
      DumpReg DISPC_VID2+DISPC_VIDn_PIXEL_INC
      DumpReg DISPC_VID2+DISPC_VIDn_FIR
      DumpReg DISPC_VID2+DISPC_VIDn_PICTURE_SIZE
      DumpReg DISPC_VID2+DISPC_VIDn_ACCU0
      DumpReg DISPC_VID2+DISPC_VIDn_ACCU1
      DumpReg DISPC_VID2+DISPC_VIDn_FIR_COEF_H0
      DumpReg DISPC_VID2+DISPC_VIDn_FIR_COEF_HV0
      DumpReg DISPC_VID2+DISPC_VIDn_FIR_COEF_H1
      DumpReg DISPC_VID2+DISPC_VIDn_FIR_COEF_HV1
      DumpReg DISPC_VID2+DISPC_VIDn_FIR_COEF_H2
      DumpReg DISPC_VID2+DISPC_VIDn_FIR_COEF_HV2
      DumpReg DISPC_VID2+DISPC_VIDn_FIR_COEF_H3
      DumpReg DISPC_VID2+DISPC_VIDn_FIR_COEF_HV3
      DumpReg DISPC_VID2+DISPC_VIDn_CONV_COEF0
      DumpReg DISPC_VID2+DISPC_VIDn_CONV_COEF1
      DumpReg DISPC_VID2+DISPC_VIDn_CONV_COEF2
      DumpReg DISPC_VID2+DISPC_VIDn_CONV_COEF3
      DumpReg DISPC_VID2+DISPC_VIDn_CONV_COEF4
      DumpReg DISPC_DATA_CYCLE0
      DumpReg DISPC_VID0_FIR_COEF_V
      DumpReg DISPC_VID1_FIR_COEF_V
      DumpReg DISPC_CPR_COEF_R
      DumpReg DISPC_CPR_COEF_G
      DumpReg DISPC_CPR_COEF_B
      DumpReg DISPC_GFX_PRELOAD
      DumpReg DISPC_VID0_PRELOAD
      DumpReg DISPC_VID1_PRELOAD
      Pull    "a1-a3,pc"

      LTORG
 ]

Video_init
        Push    "v1,lr"
 [ :LNOT: PassiveVideo
        ; omap_dss_probe()
        ; - dss_clk_enable_all_no_ctx()
        LDR     a1, L4_ClockMan_Log
        LDR     a2, [a1, #CM_ICLKEN_DSS]
        ORR     a2, a2, #1 ; EN_DSS (correct to enable iclk before fclk?)
        STR     a2, [a1, #CM_ICLKEN_DSS]
        LDR     a2, [a1, #CM_FCLKEN_DSS]
        ORR     a2, a2, #7 ; EN_DSS1, EN_DSS2, EN_TV (required for reset)
        STR     a2, [a1, #CM_FCLKEN_DSS]
 [ ExtraVideoDebug
        DebugTX "Original regs"
        BL DumpDispcRegs
 ]
        ; - dss_init()
        LDR     v1, L4_Display_Log
        LDR     a2, [v1, #DISPC_CONTROL]
        TST     a2, #1 ; LCD already enabled?
        BNE     %FT10 ; Skip init
        ; Apparently, bad things happen if you reset the DSS immediately after enabling the clocks
        DebugTX "Performing DSS reset"
        MOV     a1, #50*1024 ; 50msec ish
        BL      HAL_CounterDelay ; Wait (timer should already running)
        ; -- omap_dss_reset()
        LDR     a1, [v1, #DSS_SYSCONFIG]
        ORR     a1, a1, #2 ; SOFTRESET
        STR     a1, [v1, #DSS_SYSCONFIG]
        ; Wait for completion
5
        LDR     a1, [v1, #DSS_SYSSTATUS]
        TST     a1, #1
        BEQ     %BT5
10
        DebugTX "DSS init"
        ; Enable autoidle mode
        LDR     a1, [v1, #DSS_SYSCONFIG]
        ORR     a1, a1, #1
        STR     a1, [v1, #DSS_SYSCONFIG]
        ; Select DSS1_ALWON_FCLK as DISPC fclk
        LDR     a1, [v1, #DSS_CONTROL]
        BIC     a1, a1, #1
        STR     a1, [v1, #DSS_CONTROL]
        ; - dpi_init()
        ;   (nothing)
        ; - dispc_init()
        ; -- _omap_dispc_initial_config()
        LDR     a1, =&2015 ; AUTOIDLE, ENWAKEUP, SIDLEMODE=2, MIDLEMODE=2 (smart idle modes)
        STR     a1, [v1, #DISPC_SYSCONFIG]
        LDR     a1, [v1, #DISPC_CONFIG]
        ORR     a1, a1, #1:SHL:9 ; Gate functional clocks
        STR     a1, [v1, #DISPC_CONFIG]
        ; - dss_init_displays()
        ; -- dpi_init_display()
        ;    (nothing)
        ; - dss_init_overlay_managers()
        ;   (nothing)
        ; - dss_init_overlays()
        ;   (nothing)
        ; Other old bits:
        ; Configure GPIO pins so we can turn the DVI framer on/off
        LDRB    a1, [sb, #BoardConfig_VideoGPIO]
        CMP     a1, #255
        MOV     a2, #0
        BLNE    GPIOx_SetAsOutput ; Turn DVI framer off
 |
 [ ExtraVideoDebug
        DebugTX "Original regs"
        BL DumpDispcRegs
 ]
        LDR     v1, L4_Display_Log
 ] ; PassiveVideo
        ; Configure FIFO thresholds. these don't match u-boot, but they do match some linux code, so I'll stick with this for now.
        LDR     a3, [v1, #DISPC_GFX_FIFO_SIZE_STATUS]
        MOV     a2, a3, LSR #2 ; low threshold = 1/4 size
        SUB     a3, a3, a2 ; High threshold = 3/4 size
        ORR     a3, a2, a3, LSL #16
        STR     a3, [v1, #DISPC_GFX_FIFO_THRESHOLD]
        ADD     a4, v1, #DISPC_VID1:AND:&FF
        ADD     a4, a4, #DISPC_VID1:AND:&FF00
        LDR     a3, [a4, #DISPC_VIDn_FIFO_SIZE_STATUS]
        MOV     a2, a3, LSR #2 ; low threshold = 1/4 size
        SUB     a3, a3, a2 ; High threshold = 3/4 size
        ORR     a3, a2, a3, LSL #16
        STR     a3, [a4, #DISPC_VIDn_FIFO_THRESHOLD]
        ADD     a4, v1, #DISPC_VID2:AND:&FF
        ADD     a4, a4, #DISPC_VID2:AND:&FF00
        LDR     a3, [a4, #DISPC_VIDn_FIFO_SIZE_STATUS]
        MOV     a2, a3, LSR #2 ; low threshold = 1/4 size
        SUB     a3, a3, a2 ; High threshold = 3/4 size
        ORR     a3, a2, a3, LSL #16
        STR     a3, [a4, #DISPC_VIDn_FIFO_THRESHOLD]
        ; Allocate palette space
        LDR     a1, NCNBAllocNext
        STR     a1, video_palette
        ADD     a1, a1, #4*256
        ; Allocate hardware cursor space
        STR     a1, cursor_image
        ADD     a1, a1, #HW_CURSOR_WIDTH*HW_CURSOR_HEIGHT*4
        STR     a1, NCNBAllocNext
        MOV     a1, #0 ; Pixel rate isn't set yet
        STR     a1, pixel_rate
        STR     a1, video_gobits
        BL      Determine_PorchSync_Limits
        STR     a1, video_maxporch
        STR     a2, video_maxsync
        Pull    "v1,pc"

; -----------------------------------------------------------------------------------

HAL_VideoFlybackDevice
        MOV     a1, #VIDEO_IRQ ; DSS IRQ
        MOV     pc, lr

; -------------------------------------------------------------------------

;
;  void HAL_Video_SetMode(const void *VIDCList3)
;
;  program DSS registers from VIDCList3 specification
;
;  in: VIDClist -> video mode list (in VIDCList type 3 format)
;

HAL_Video_SetMode ROUT
        Push    "v1-v4,lr"
 [ VideoDebug
        DebugTX "HAL_Video_SetMode"
 ]
        ; Most of these registers are programmed with (VIDC value)-1
        LDR     v1, L4_Display_Log
 [ :LNOT: PassiveVideo
        ; Start off by programming pixel clock, so we can exit cleanly if we've been asked to change to a mode with a bad clock rate
        ; Program CM_CLKSEL2_PLL
        LDR     a4, [a1, #VIDCList3_PixelRate] ; Desired pixel rate, kHz
 [ VideoDebug
        DebugTX "Pixel rate:"
        DebugReg a4
        LDR a2, sys_clk
        DebugTX "sys_clk"
        DebugReg a2
 ]
        LDR     a2, =MAX_PIXEL_RATE
        CMP     a4, a2
        BLS     %FT10
        DebugTX "HAL_Video_SetMode: Pixel rate too high"
        Pull    "v1-v4,pc"
10
        ; Check if we're already programmed to this pixel rate
        LDR     a2, pixel_rate
        CMP     a2, a4
        BEQ     %FT20
        ; Else we need to reprogram the DSI PLL
        ; Use the sequence indicated in the OMAP TRM (spruf98b, fig 15-135), tweaked a bit to avoid writing values until we know the desired pixel rate is possible
        ; We also follow figures 15-142 and 15-143, to ensure video output is disabled & re-enabled in the correct manner before we go tweaking the clock frequencies (although those two figures reference the SDI PLL, not the DSI PLL) although this is wrong now that we're using RFBI? just need to follow dpi_set_mode()
        ; todo - investigate what the role of the sys_clk divider is (i.e. whether it really does need to be on for some frequencies)

        ; Calculate REGM, REGN
        MOV     a2, #1000
        Push    "a1"
        MUL     a1,a2,a4 ; Pixel rate, Hz
        BL      calculate_DSS_clock_divider
        CMP     a1, #2048
        CMPLO   a2, #128
        BLO     %FT10
        DebugTX "HAL_Video_SetMode: Bad DSS clock divider"
        DebugReg a4
        DebugReg a1
        DebugReg a2
        Pull    "a1,v1-v4,pc"
10
        ; Calculate FREQSEL
        MOV     v2, a1 ; REGM
        MOV     v3, a2 ; REGN
        ; First, calculate Fint
        ADD     a1, a2, #1
        LDR     a2, sys_clk
        BL      |__rt_udiv|
        BL      PRCM_GetFreqSel
        SUB     a2, a1, #3
        CMP     a2, #4
        BLS     %FT10
        DebugTX "HAL_Video_SetMode: Bad Fint"
        DebugReg a4
        DebugReg v2
        DebugReg v3
        Pull    "a1,v1-v4,pc"
10
        MOV     v4, a1 ; FREQSEL

        ; Disable Vsync IRQ to avoid pointer corruption issues
        MOV     a1, #0
        STR     a1, [v1, #DISPC_IRQENABLE]

        ; Data synchronisation barrier
        myDSB

        ; New pixel rate is good, shutdown DISPC while we update the DSI PLL
        LDR     a1, pixel_rate
        CMP     a1, #0
        BEQ     %FT30 ; Skip DISPC/PLL shutdown sequence
 [ VideoDebug
        DebugTX "Performing DISPC shutdown"
 ]
        ; Should we turn off the DVI framer here?

        ; Wait for any pending GOLCD/GODIGITAL to complete
10
        LDR     a1, [v1, #DISPC_CONTROL]
        TST     a1, #3:SHL:5
        BNE     %BT10

        ; Now we turn off the DISPC outputs
        ; Just gating the functional clocks should work?
        LDR     a1, [v1, #DISPC_CONFIG]
        ORR     a1, a1, #1:SHL:9
        STR     a1, [v1, #DISPC_CONFIG]
30
        ; DISPC is turned off, now we can program the new DSI PLL value
        ; v2 = REGM
        ; v3 = REGN
        ; v4 = FREQSEL
        ; dpi_display_enable()
        ; - dpi_basic_init()
        ; -- dispc_set_parallel_interface_mode(OMAP_DSS_PARALLELMODE_BYPASS)
        LDR     a1, [v1, #DISPC_CONTROL]
        BIC     a1, a1, #1:SHL:11 ; STALLMODE=0
        ORR     a1, a1, #3:SHL:15 ; GPOUT0=1,GPOUT1=1
        ; -- dispc_set_lcd_display_type(OMAP_DSS_LCD_DISPLAY_TFT)
        ORR     a1, a1, #1:SHL:3
        ; -- dispc_set_tft_data_lines(DSI_LINES)
        ORR     a1, a1, #3:SHL:8
        STR     a1, [v1, #DISPC_CONTROL]
        ; - dsi_pll_init(0,1)
        ; -- dss_dsi_power_up()
        ;    (nothing?)
        ; -- dispc_pck_free_enable(1)
        LDR     a1, [v1, #DISPC_CONTROL]
        ORR     a1, a1, #1:SHL:27 ; PCKFREEENABLE - PLL doesn't come out of reset without this, according to some linux code
        STR     a1, [v1, #DISPC_CONTROL]
 [ :LNOT: QEMU
        ; -- wait_for_bit_up(DSI_PLL_STATUS,0)
  [ VideoDebug
        DebugTX "Wait for DSI_PLLCTRL_RESET_DONE"
  ]
10
        LDR     a1, [v1, #DSI_PLL_STATUS]
        TST     a1, #1 ; DSI_PLLCTRL_RESET_DONE? (surely this assumes that it's just been reset?)
        BEQ     %BT10
        ; -- dispc_pck_free_enable(0)
        LDR     a1, [v1, #DISPC_CONTROL]
        BIC     a1, a1, #1:SHL:27 ; Disable PCKFREEENABLE because it causes problems with video planes? (more linux stuff)
        STR     a1, [v1, #DISPC_CONTROL]
        ; -- dsi_pll_power(DSI_PLL_POWER_ON_DIV)
        LDR     a1, [v1, #DSI_CLK_CTRL]
        ORR     a1, a1, #3:SHL:30 ; PLL on, HSDIVIDER on, but no output to DSI complex I/O
        STR     a1, [v1, #DSI_CLK_CTRL]
  [ VideoDebug
        DebugTX "Wait for DSI_PLL_POWER_ON_HSCLK"
  ]
10
        LDR     a1, [v1, #DSI_CLK_CTRL]
        AND     a1, a1, #3:SHL:28
        CMP     a1, #3:SHL:28
        BNE     %BT10 ; Wait for requested state to be reached

        ; - dpi_set_mode()
        ; -- dispc_set_pol_freq() - ok to delay until later?
        ; -- dpi_set_dsi_clk()
  [ VideoDebug
        DebugTX "Programming DSI PLL"
  ]
        ; --- dsi_pll_program() - what we do here:
        LDR     a1, [v1, #DSI_PLL_CONTROL]
        BIC     a1, a1, #1 ; DSI_PLL_AUTOMODE = manual (apparently)
        STR     a1, [v1, #DSI_PLL_CONTROL]
        ; Calculate DSI_PLL_CONFIGURATION1
        LDR     a1, [v1, #DSI_PLL_CONFIGURATION1]
        BIC     a1, a1, #&FE
        ORR     a1, a1, v3, LSL #1 ; REGN
        BIC     a1, a1, #&70000
        BIC     a1, a1, #&0FF00
        ORR     a1, a1, v2, LSL #8 ; REGM
        ORR     a1, a1, #1 ; STOPMODE selected (why?)
        ; REGM3, REGM4
        LDR     a2, =&7F80000
        BIC     a1, a1, a2 ; Clear old values
        ; We leave REGM4 as 0, since we don't use it
        ; But REGM3+1 = frequency on data lane/(pixel rate*2)
        ; REMG3+1 = (2*pixel rate*bpp)/(pixel rate*2*lanes) = DSI_BPP/DSI_LANES
        ASSERT  (DSI_BPP :MOD: DSI_LANES)=0
        ORR     a1, a1, #((DSI_BPP/DSI_LANES)-1):SHL:19
        ORR     a1, a1, #7:SHL:23 ; Set REGM4?
        STR     a1, [v1, #DSI_PLL_CONFIGURATION1]
        ; Calculate DSI_PLL_CONFIGURATION2
        LDR     a2, [v1, #DSI_PLL_CONFIGURATION2]
        BIC     a2, a2, #3:SHL:11 ; Select sys_clk, clear HIGHFREQ
        BIC     a2, a2, #&1E
        ORR     a2, a2, v4, LSL #1 ; FREQSEL (angstrom always uses 7?)
        ORR     a2, a2, #(1:SHL:13)+(1:SHL:8) ; Enable reference clock, drift guard
        BIC     a2, a2, #1:SHL:14 ; Disable DSIPHY clock
        ORR     a2, a2, #1:SHL:20 ; HSDIVIDER in bypass mode
        STR     a2, [v1, #DSI_PLL_CONFIGURATION2]
        ; Set DSI_PLL_GO
        MOV     a1, #1
        STR     a1, [v1, #DSI_PLL_GO]
        ; Wait for completion
  [ VideoDebug
        DebugTX "Wait for DSI_PLL_GO"
  ]
10
        LDR     a1, [v1, #DSI_PLL_GO]
        TST     a1, #1 ; GO bit still set?
        BNE     %BT10
  [ VideoDebug
        DebugTX "Wait for DSI PLL lock"
  ]
        MOV     a3, #2*1024*1024
10
        LDR     a1, [v1, #DSI_PLL_STATUS]
        TST     a1, #2 ; DSI PLL locked?
;        BEQ     %BT10
        BNE     %FT5
        SUBS    a3, a3, #1
        BNE     %BT10
  [ VideoDebug
        DebugTX "DSI PLL lock timeout"
        LDR     a1, [v1, #DSI_PLL_CONTROL] ; 00000000
        DebugReg a1
        LDR     a1, [v1, #DSI_PLL_STATUS] ; 00000041 - DSI_PLL_BYPASS, RESETDONE
        DebugReg a1
        LDR     a1, [v1, #DSI_PLL_GO] ; 00000000
        DebugReg a1
        LDR     a1, [v1, #DSI_PLL_CONFIGURATION1] ; 00387c0f - STOPMODE selected (bad?), REGN = 7, REGM = 7c, M3REG = 7, M4REG = 0
        DebugReg a1
        LDR     a1, [v1, #DSI_PLL_CONFIGURATION2] ; 0010200c - FREQSEL 1.5-1.75, DSI_PLL_REFEN, DSI_HSDIVBYPASS
        DebugReg a1
        LDR     a1, [v1, #DSI_CLK_CTRL] ; 50000001 - LP_CLK_DIVISOR 1, power=HSDIVIDER off, PLL on
        DebugReg a1
  ]
;6
;        B       %BT6
5
        ; Modify CONFIGURATION2 again to do stuff
        ; Some of this can be skipped, matches reset values?
        BIC     a2, a2, #1 ; DSI_PLL_IDLE = 0
        BIC     a2, a2, #&6E0 ; DSI_PLL_PLLLPMODE, LOWCURRSTBY, TIGHTPHASELOCK, LOCKSEL = 0
        ORR     a2, a2, #1:SHL:16 ; DSS_CLOCK_EN
        BIC     a2, a2, #1:SHL:17 ; DSS_CLOCK_PWDN
        BIC     a2, a2, #1:SHL:20 ; HSDIVIDER in normal mode
        STR     a2, [v1, #DSI_PLL_CONFIGURATION2]
 ]
        ; Make sure DISPC uses DSI1_PLL_FCLK
        LDR     a1, [v1, #DSS_CONTROL]
        ORR     a1, a1, #1
        STR     a1, [v1, #DSS_CONTROL]

        ; Enable functional clock again
        LDR     a1, [v1, #DISPC_CONFIG]
        BIC     a1, a1, #1:SHL:9
        ; Also make sure colour phase rotation is enabled
        ORR     a1, a1, #1:SHL:15
        ; Also make sure transparency colour key is enabled for the mouse cursor
        ORR     a1, a1, #&F:SHL:10 ; Video source transparency colour key for both LCD & digital output
        BIC     a1, a1, #3:SHL:1 ; load palette+frame data!
        STR     a1, [v1, #DISPC_CONFIG]

        Pull    "a1"
        ; Remember the new pixel rate!
        LDR     a3, [a1, #VIDCList3_PixelRate]
        STR     a3, pixel_rate
20
 [ VideoDebug
        DebugTX "Programming VIDC timings"
 ]
        ; Now we can program the rest of the VIDC list
        ; Program DISPC_TIMING_H
        LDR     a2, [a1, #VIDCList3_HorizSyncWidth]
        LDR     a4, video_maxsync
        SUBS    a2, a2, #1
        MOVLT   a2, #0
        CMP     a2, a4
        SUBGE   a2, a4, #1 ; a4 is actually max value+1
        LDR     a3, [a1, #VIDCList3_HorizFrontPorch]
        LDR     a4, video_maxporch
        SUBS    a3, a3, #1
        MOVLT   a3, #0
        CMP     a3, a4
        SUBGE   a3, a4, #1
        ORR     a2, a2, a3, LSL #8
        LDR     a3, [a1, #VIDCList3_HorizBackPorch]
        SUBS    a3, a3, #1
        MOVLT   a3, #0
        CMP     a3, a4
        SUBGE   a3, a4, #1
        ORR     a2, a2, a3, LSL #20
        STR     a2, [v1, #DISPC_TIMING_H]
        ; Program DISPC_TIMING_V
        LDR     a2, [a1, #VIDCList3_VertiSyncWidth]
        LDR     a4, video_maxsync
        SUBS    a2, a2, #1
        MOVLT   a2, #0
        CMP     a2, a4
        SUBGE   a2, a4, #1
        LDR     a3, [a1, #VIDCList3_VertiFrontPorch]
        LDR     a4, video_maxporch
        CMP     a3, #0
        MOVLT   a3, #0
        CMP     a3, a4
        SUBGE   a3, a4, #1
        ORR     a2, a2, a3, LSL #8
        LDR     a3, [a1, #VIDCList3_VertiBackPorch]
        CMP     a3, #0
        MOVLT   a3, #0
        CMP     a3, a4
        SUBGE   a3, a4, #1
        ORR     a2, a2, a3, LSL #20
        STR     a2, [v1, #DISPC_TIMING_V]
        ; Program DISPC_POL_FREQ
        MOV     a2, #&28 ; AC bias pin frequency - from u-boot
        LDR     a3, [a1, #VIDCList3_SyncPol]
        TST     a3, #SyncPol_InvertHSync
        ORRNE   a2, a2, #&2000
        TST     a3, #SyncPol_InvertVSync
        ORRNE   a2, a2, #&1000
        STR     a2, [v1, #DISPC_POL_FREQ]
        ; DISPC_SIZE_DIG, DISPC_SIZE_LCD, DISPC_GFX_SIZE, DISPC_PICTURE_SIZE
        LDR     a2, [a1, #VIDCList3_HorizDisplaySize]
        CMP     a2, #2048
        MOVGT   a2, #2048
        CMP     a2, #0
        MOVLE   a2, #1
        SUB     a2, a2, #1
        LDR     a3, [a1, #VIDCList3_VertiDisplaySize]
        CMP     a3, #2048
        MOVGT   a3, #2048
        CMP     a3, #0
        MOVLE   a3, #1
        SUB     a3, a3, #1
        ORR     a2, a2, a3, LSL #16
        STR     a2, [v1, #DISPC_SIZE_DIG]
        STR     a2, [v1, #DISPC_SIZE_LCD]
        STR     a2, [v1, #DISPC_GFX_SIZE]
 |
        ; Disable Vsync IRQ to avoid pointer corruption issues
        MOV     a2, #0
        STR     a2, [v1, #DISPC_IRQENABLE]

        ; Data synchronisation barrier
        myDSB
        ; Remember the new pixel rate!
        LDR     a3, [a1, #VIDCList3_PixelRate]
        STR     a3, pixel_rate
        ; Update DISPC_CONFIG to make sure colour phase rotation & transparency colour key is in use
        LDR     a2, [v1, #DISPC_CONFIG]
        BIC     a2, a2, #1:SHL:3 ; LUT used as palette
        BIC     a2, a2, #3:SHL:18 ; alpha blending disabled
        ORR     a2, a2, #1:SHL:15 ; colour phase rotation enabled
        ORR     a2, a2, #&F:SHL:10 ; Video source transparency colour key for both LCD & digital output
        BIC     a2, a2, #3:SHL:1 ; Load palette+frame data!
        STR     a2, [v1, #DISPC_CONFIG]
        ; Crop graphics overlay to fit LCD size
        LDR     a2, [v1, #DISPC_SIZE_LCD]
        MOV     a3, a2, LSR #16 ; height-1
        BIC     a2, a2, a3, LSL #16
        ADD     a2, a2, #1 ; width
        LDR     a4, [a1, #VIDCList3_HorizDisplaySize]
        CMP     a4, a2
        SUBGT   v2, a4, a2
        MOVGT   a4, a2
        MOVLE   v2, #0
        SUB     a4, a4, #1
        LDR     a2, [a1, #VIDCList3_VertiDisplaySize]
        SUB     a2, a2, #1
        CMP     a2, a3
        ORRGT   a4, a4, a3, LSL #16
        ORRLE   a4, a4, a2, LSL #16
        STR     a4, [v1, #DISPC_GFX_SIZE]
        LDR     a2, [a1, #VIDCList3_PixelDepth]
        SUBS    a2, a2, #3
        MOVGT   v2, v2, LSL a2 ; Convert pixel overlap to bytes
        RSBLT   a2, a2, #0
        MOVLT   v2, v2, LSR a2
        ADD     v2, v2, #1
        STR     v2, [v1, #DISPC_GFX_ROW_INC]
        MOV     v2, #1
        STR     v2, [v1, #DISPC_GFX_PIXEL_INC] ; Just in case
 ] ; PassiveVideo
        ; DISPC_GFX_ATTRIBUTES
        LDR     a2, [a1, #VIDCList3_PixelDepth]
        ADR     a3, gfxenable_table
        LDR     a2, [a3, a2, LSL #2]
        STR     a2, [v1, #DISPC_GFX_ATTRIBUTES]
        ; DISPC_GFX_TABLE_BA
        Push    "a1"
        LDR     a1, video_palette
        BL      vtophys
        STR     a1, [v1, #DISPC_GFX_TABLE_BA]
        Pull    "a1"
        ; DISPC_CPR_COEF_*
        ; For now, swap R&B channels regardless of whether we're in a palettised mode or not
        MOV     a2,#256
        STR     a2, [v1, #DISPC_CPR_COEF_R]
        MOV     a2, a2, LSL #11
        STR     a2, [v1, #DISPC_CPR_COEF_G]
        MOV     a2, a2, LSL #11
        STR     a2, [v1, #DISPC_CPR_COEF_B]
        ; DISPC_GLOBAL_ALPHA
        LDR     a2, =&FF00FF
        STR     a2, [v1, #DISPC_GLOBAL_ALPHA]
        ; DISPC_CONTROL
 [ :LNOT: PassiveVideo
        LDR     a2, =&1836b ; Same value as u-boot: GPOUT0, GPOUT1, 24-bit TFT data lines, GODIGITAL, GOLCD, active matrix display, DIGITALENABLE, LCDENABLE
 |
        LDR     a2, [v1, #DISPC_CONTROL]
        ORR     a2, a2, #3:SHL:5
 ]
        STR     a2, [v1, #DISPC_CONTROL]
 [ VideoDebug
        DebugTX "Video mode programmed, waiting for GOLCD/GODIGITAL to clear"
 ]
10
        LDR     a1, [v1, #DISPC_CONTROL]
        TST     a1, #3:SHL:5
        BNE     %BT10
        ; Re-enable VSync IRQ
        MOV     a1, #2
        STR     a1, [v1, #DISPC_IRQENABLE]
 [ :LNOT: PassiveVideo
        ; Now enable the DVI framer
        LDRB    a1, [sb, #BoardConfig_VideoGPIO]
        CMP     a1, #255
        MOV     a2, #1
        BLNE    GPIOx_SetOutput
 ]
        ; Done?
 [ VideoDebug
        DebugTX "HAL_Video_SetMode complete"
 ]
;        MOV     a1, #1 ; NTSC 601 s-video
;        BL venc_setmode
 [ ExtraVideoDebug
        DebugTX "Final regs"
        BL DumpDispcRegs
 ]
        Pull    "v1-v4,pc"

gfxenable_table ; GFXENABLE values for different BPP values
            ; FEDCBA9876543210
        DCD 2_0100100010000001 ; 1BPP
        DCD 2_0100100010000011 ; 2BPP
        DCD 2_0100100010000101 ; 4BPP
        DCD 2_0100100010000111 ; 8BPP
        DCD 2_0100100010001101 ; 16BPP
        DCD 2_0100100010010001 ; 32BPP

; -------------------------------------------------------------------------

;
; void HAL_Video_WritePaletteEntry(uint type, uint pcolour, uint index)
;
; write palette entry to video controller
;
;  type     = 0 for normal palette entry
;             1 for border colour
;             2 for pointer colour
;          >= 3 reserved
;  pcolour  = palette entry colour in BBGGRRSS format (Blue,Green,Red,Supremacy)
;  index    = index of entry  (0..255 for normal, 0 for border, 0..3 for pointer)
;             note that RISC OS only uses 1..3 for pointer (0 is assumed to be transparent)
;
HAL_Video_WritePaletteEntry ROUT
        CMP     a1, #3
        MOVHS   pc, lr
        CMP     a1, #1
        BEQ     %FT10 ; Border colour
        BGT     %FT20 ; Pointer colour
        CMP     a3, #255
        MOVHI   pc, lr ; Out of range
        LDR     a4, video_palette
        MOV     a2, a2, ROR #8
        STR     a2, [a4, a3, LSL #2]
        MOV     pc, lr
10      ; Update border colour
        ; We don't really support this properly. for now just set the default background colour.
        MOV     a2, a2, ROR #8
        LDR     a1, L4_Display_Log
        STR     a2, [a1, #DISPC_DEFAULT_COLOR0]
        STR     a2, [a1, #DISPC_DEFAULT_COLOR1]
        ; GODIGITAL+GOLCD to ensure it's updated
        SetGoBits a1,a2,a3,"HAL_Video_WritePaletteEntry GOLCD timeout"
        MOV     pc, lr
20      ; Update cursor colour
        CMP     a3, #4
        MOVHS   pc, lr ; Out of range
        MOV     a2, a2, LSR #8 ; 00bbggrr
        ADR     a1, cursor_palette
        STR     a2, [a1, a3, LSL #2]
        STR     a1, cursor_update ; Store nonzero value to update flag
        MOV     pc, lr

; -------------------------------------------------------------------------

;
; void HAL_Video_WritePaletteEntries(uint type, const uint *pcolours, uint index, uint Nentries)
;
; write block of palette entries to video controller
;
;  type     = 0 for normal palette entry
;             1 for border colour
;             2 for pointer colour
;          >= 3 reserved
;  pcolours = pointer to block of palette entry colours in BBGGRRSS format (Blue,Green,Red,Supremacy)
;  index    = start index in palette (for first entry in block)
;             note that RISC OS only uses 1..3 for pointer (0 is assumed to be transparent)
;  Nentries = number of entries in block (must be >= 1)
;
HAL_Video_WritePaletteEntries ROUT
        CMP     a4, #0
        MOVEQ   pc, lr ; Nothing to store
        CMP     a1, #3
        MOVHS   pc, lr
        CMP     a1, #1
        BEQ     %FT10 ; Border colour
        BGT     %FT20 ; Pointer colour
        CMP     a3, #255
        CMPLS   a4, #256
        MOVHI   pc, lr ; Out of range
        ADD     ip, a3, a4
        CMP     ip, #256
        MOVHI   pc, lr ; Out of range
        LDR     a1, video_palette
        ADD     a1, a1, a3, LSL #2
5
        LDR     a3, [a2], #4
        MOV     a3, a3, ROR #8
        STR     a3, [a1], #4
        SUBS    a4, a4, #1
        BGT     %BT5
        MOV     pc, lr
10      ; Border update
        ; We don't really support this properly. for now just set the default background colour.
        CMP     a4, #1
        MOVNE   pc, lr
        LDR     a2, [a2]
        MOV     a2, a2, ROR #8
        LDR     a1, L4_Display_Log
        STR     a2, [a1, #DISPC_DEFAULT_COLOR0]
        STR     a2, [a1, #DISPC_DEFAULT_COLOR1]
        ; GODIGITAL+GOLCD to ensure it's updated
        SetGoBits a1,a2,a3,"HAL_Video_WritePaletteEntries GOLCD timeout"
        MOV     pc, lr
20      ; Cursor update
        CMP     a3, #3
        CMPLS   a4, #4
        MOVHI   pc, lr ; Out of range
        ADD     ip, a3, a4
        CMP     ip, #4
        MOVHI   pc, lr ; Out of range
        ADR     a1, cursor_palette
        ADD     a1, a1, a3, LSL #2
5
        LDR     ip, [a2], #4
        MOV     ip, ip, LSR #8 ; 00bbggrr
        ADD     a3, a3, #1
        STR     ip, [a1], #4
        SUBS    a4, a4, #1
        BGT     %BT5
        STR     a1, cursor_update ; Store nonzero value to update flag
        MOV     pc, lr

        LTORG

; -------------------------------------------------------------------------

;
; uint HAL_Video_ReadPaletteEntry(uint type, uint pcolour, uint index)
;
; return the effective palette entry after taking into account any hardware
; restrictions in the video controller, assuming it was programmed with pcolour
;
;  type     = 0 for normal palette entry
;             1 for border colour
;             2 for pointer colour
;          >= 3 reserved
;  pcolour  = palette entry colour in BBGGRRSS format (Blue,Green,Red,Supremacy)
;  index    = index of entry  (0..255 for normal, 0 for border, 0..3 for pointer)
;             note that RISC OS only uses 1..3 for pointer (0 is assumed to be transparent)
;  returns  : effective BBGGRRSS
;
;  mjs: depending on h/w capabilities, specific HALs may have to
;       remember current settings (eg. bits per pixel), keep soft copy
;       of entries or whatever, in their workspace. Because the HAL API
;       supplies a pcolour, the need to keep a full palette soft copy
;       in the HAL is minimised

HAL_Video_ReadPaletteEntry ROUT
        MOV     a1, a2
        MOV     pc, lr

; -------------------------------------------------------------------------

; void HAL_Video_SetInterlace(uint interlace)
;
;  interlace = 0/1 for interlace off/on

HAL_Video_SetInterlace ROUT
        MOV     pc, lr

; -------------------------------------------------------------------------

; void HAL_Video_SetBlank(uint blank, uint DPMS)
;
; blank = 0/1 for unblank/blank
; DMPS  = 0..3 as specified by monitor DPMSState (from mode file)
;         0 for no DPMS power saving

; HAL is expected to attempt to turn syncs off according to DPMS, and
; to turn video DMA off for blank (and therefore on for unblank) if possible.
; HAL is not expected to do anything else, eg. blank all palette entries.
; Such things are the responsibility of the OS, and also this call is expected
; to be fast. May be called with interrupts off.

HAL_Video_SetBlank ROUT
        MOV     pc, lr

; -------------------------------------------------------------------------

; void HAL_Video_SetPowerSave(uint powersave)
;
; powersave = 0/1 for power save off/on

HAL_Video_SetPowerSave ROUT
        MOV     pc, lr

; -------------------------------------------------------------------------

; void HAL_Video_UpdatePointer(uint flags, int x, int y, const shape_t *shape)
;
; Update the displayed position of the current pointer shape (or turn
; shape off)
;
; HAL code may need to take note of shape updated flag, and make its
; own new copies if true. This is to handle cases like dual scan LCD
; pointer, which typically needs two or more shapes buffers for the
; hardware. This work should _only_ be done when the updated flag
; is true, or possibly because provoked by clipping requirements.
; A simple HAL, using the kernel shape buffer directly, may be able to
; ignore the updated flag.
;
; flags:
;   bit 0  = pointer display enable (0=off, 1=on)
;   bit 1  = pointer shape update (0=no change, 1=updated)
;   bits 2..31 reserved (0)
; xpos = x position of top left of pointer (xpos = 0 for left of display)
; ypos = y position of top left of pointer (ypos = 0 for top of display)
; shape points to shape_t descriptor block:
;   typedef struct shape_t
;   {
;     uint8   width;      /* unpadded width in bytes (see notes) */
;     uint8   height;     /* in pixels */
;     uint8   padding[2]; /* 2 bytes of padding for field alignment */
;     void   *buffLA;     /* logical address of buffer holding pixel data */
;     void   *buffPA;     /* corresponding physical address of buffer */
;   }
;
; Notes:
; 1) if flags bit 0 is 0 (pointer off), x, y, shape are undefined
; 2) the shape data from RISC OS is always padded with transparent pixels
;    on the rhs, to a width of 32 pixels (8 bytes)
; 3) pointer clipping is the responsibility of the HAL (eg. may be able to
;    allow display of pointer in border region on some h/w)
; 4) buffer for pixel data is aligned to a multiple of 256 bytes or better
;
; This call is made by the OS at a time to allow smoothly displayed changes
; (on a VSync)

HAL_Video_UpdatePointer ROUT
        Push     "v1-v5,lr"
        TST     a1, #1
        BEQ     %FT10 ; Cursor off
        ; Update image?
        TST      a1, #2
        LDREQ    a1, cursor_update ; Any palette changes that need implementing?
        CMPEQ    a1, #0
        BEQ      %FT20
        ; The current pointer implementation assumes that palette index 0 is
        ; the only transparent entry (which is currently true for RISC OS).
        ; And since we're using the DSS transparency colour key feature, we
        ; need to make sure the (24bit RGB) value for entry 0 is unique.
        ; So this piece of code, just before the image update, ensures that
        ; the palette entry is unique
        ADR      a1, cursor_palette
        LDMIA    a1, {v1-v4} ; Grab the 4 entries. We assume that the top bit is already clear (the palette set functions should have done that for us)
5
        CMP      v1, v2
        CMPNE    v1, v3
        CMPNE    v1, v4
        ADDEQ    v1, v1, #1
        BICEQ    v1, v1, #&FF000000 ; Avoid overflowing into top byte
        BEQ      %BT5 ; Loop until we find a uinque colour
        STR      v1, [a1] ; Store it back
        ; Here we also update the colour key registers with the right value
        LDR      v2, L4_Display_Log
        STR      v1, [v2, #DISPC_TRANS_COLOR0]
        STR      v1, [v2, #DISPC_TRANS_COLOR1]
        ; Now we can update the image
        LDRB     v1, [a4] ; width
        LDRB     v2, [a4,#1] ; height
        MOV      v1, v1, LSL #2 ; Byte width -> pixel width
        ; Clamp size to sensible values
        CMP      v1, #0
        CMPNE    v2, #0
        BEQ      %FT20 ; No width/height... ignore
        CMP      v2, #HW_CURSOR_HEIGHT
        MOVGT    v2, #HW_CURSOR_HEIGHT
        ; No upper bound on width; we just skip the extra pixels when copying to our buffer
        LDR      lr, cursor_image
        ; Good - now copy the data over
        ASSERT   HW_CURSOR_WIDTH = 32 ; RISC OS pads each row to be 32 pixels wide, make sure our buffer is the same width
        MOV      v4, #0 ; Current y
        LDR      a4, [a4,#4] ; Get source buffer
5
        MOV      v1, #16
        CMP      v4,v2 ; If the cursor is smaller than our buffer, don't load. No need to set v3 & v5 to 0 either; they will be 0 from the previous row
        LDMLTIA  a4!, {v3,v5} ; Get all 32 pixels for this row
6
        AND      ip, v3, #&3
        LDR      ip, [a1, ip, LSL #2]
        MOV      v3, v3, LSR #2
        SUBS     v1, v1, #1
        STR      ip, [lr], #4
        BGT      %BT6
        MOV      v1, #16
7
        AND      ip, v5, #&3
        LDR      ip, [a1, ip, LSL #2]
        MOV      v5, v5, LSR #2
        SUBS     v1, v1, #1
        STR      ip, [lr], #4
        BGT      %BT7
        ADD      v4,v4,#1
        CMP      v4,#HW_CURSOR_HEIGHT
        BLT      %BT5
        ; Image updated!
        STR      v3, cursor_update ; Clear update flag (v3 will be 0 at this point)
20
        ; Update position
        ; Even though the source image is likely to be smaller than our buffer, we always treat it as being the full buffer size
        LDR     a1, cursor_image
        MOV     v1, a2
        MOV     v2, a3
        BL      vtophys ; Get physical addr for programming VID2 base address
        MOV     v3, #HW_CURSOR_WIDTH
        MOV     v4, #HW_CURSOR_HEIGHT
        CMP     v1, #0
        SUBLT   a1, a1, v1, LSL #2 ; Skip pixels that are off the left of the screen
        ADDLT   v3, v3, v1
        MOVLT   v1, #0
        CMP     v2, #0
        SUBLT   a1, a1, v2, LSL #HW_CURSOR_WIDTH_POW2+2 ; Skip rows that are off the top of the screen
        ADDLT   v4, v4, v2
        MOVLT   v2, #0
        ; This may just be an issue with how the graphics windows are set up, but it looks like we need to crop the pixels that are off the right/bottom of the screen
        LDR     a3, L4_Display_Log
        LDR     a4, [a3, #DISPC_SIZE_LCD] ; Use SIZE_LCD, since we program VID2 for LCD output? (Not that it matters, since the sizes are identical ATM)
        MOV     v5, a4, LSR #16 ; Height-1
        BIC     a4, a4, v5, LSL #16 ; Width-1
        ADD     v5, v5, #1
        ADD     a4, a4, #1
        SUB     a4, a4, v1 ; a4 = max width
        CMP     a4, v3
        MOVLT   v3, a4
        SUB     v5, v5, v2 ; v5 = max height
        CMP     v5, v4
        MOVLT   v4, v5
        CMP     v1, #2048
        CMPLS   v2, #2048
        BHI     %FT10 ; Outside programmable video window position
        CMP     v3, #0
        CMPGT   v4, #0
        BLE     %FT10 ; Cropped image has no or negative size
        ; Else program attributes
        ADD     a4, a3, #DISPC_VID2:AND:&FF
        ADD     a4, a4, #DISPC_VID2:AND:&FF00
        LDR     a2, [a4, #DISPC_VIDn_BA0]
        CMP     a1, a2 ; Update required?
        STR     a1, [a4, #DISPC_VIDn_BA0]
        LDR     a2, [a4, #DISPC_VIDn_BA1]
        CMPEQ   a1, a2
        STR     a1, [a4, #DISPC_VIDn_BA1]
        ORR     a1, v1, v2, LSL #16
        LDR     a2, [a4, #DISPC_VIDn_POSITION]
        CMPEQ   a1, a2
        STR     a1, [a4, #DISPC_VIDn_POSITION]
        MOV     a1, v3, LSL #2 ; Width in bytes
        RSB     a1, a1, #HW_CURSOR_WIDTH*4 ; ROW_INC in bytes
        ADD     a1, a1, #1 ; Add 1 to get correct ROW_INC value
        LDR     a2, [a4, #DISPC_VIDn_ROW_INC]
        CMPEQ   a1, a2
        STR     a1, [a4, #DISPC_VIDn_ROW_INC]
        SUB     v3, v3, #1
        SUB     v4, v4, #1
        ORR     a1, v3, v4, LSL #16
        LDR     a2, [a4, #DISPC_VIDn_SIZE]
        CMPEQ   a1, a2
        STR     a1, [a4, #DISPC_VIDn_SIZE]
        LDR     a2, [a4, #DISPC_VIDn_PICTURE_SIZE]
        CMPEQ   a1, a2
        STR     a1, [a4, #DISPC_VIDn_PICTURE_SIZE] ; Required?
        LDR     a1, =2_0100010000000000000011001 ; high priority pipeline, use FIFO threshold, LCD output, 4x32bit DMA burst, ARGB 32, enabled
        LDR     a2, [a4, #DISPC_VIDn_ATTRIBUTES]
        CMPEQ   a1, a2
        STR     a1, [a4, #DISPC_VIDn_ATTRIBUTES]
        BEQ     %FT30 ; Will only be EQ if none of the registers changed
        B       %FT20 ; Else at least one reg changed and we need to set the go bits
10
        ; Cursor off
        LDR     a3, L4_Display_Log
        ADD     a1, a3, #DISPC_VID2:AND:&FF
        ADD     a1, a1, #DISPC_VID2:AND:&FF00
        MOV     a2, #0 ; Disabled
        LDR     a4, [a1, #DISPC_VIDn_ATTRIBUTES]
        CMP     a4, a2
        BEQ     %FT30 ; Skip update
        STR     a2, [a1, #DISPC_VIDn_ATTRIBUTES]
20
        ; Set GODIGITAL+GOLCD to ensure the register updates are applied
        SetGoBits a3,a1,a2,"HAL_Video_UpdatePointer GOLCD timeout"
30
        ; If pixel_rate is zero or DISPC_CONFIG bit 9 is set, we're in the middle of a mode change
        ; in which case setting the go bits is probably a bad idea
        LDR     a1, pixel_rate
        CMP     a1, #0
        Pull    "v1-v5,pc",EQ
        LDR     a1, [a3, #DISPC_CONFIG]
        TST     a1, #1:SHL:9
        Pull    "v1-v5,pc",NE
        ; Do we have a pending gobits from elsewhere?
        LDR     a1, video_gobits
        LDR     a2, [a3, #DISPC_CONTROL]
        BICS    a4, a1, a2 ; a4 = bits we can safely set
        ORRNE   a2, a2, a4
        STRNE   a2, [a3, #DISPC_CONTROL] ; Set the bits
        BICNE   a1, a1, a4 ; Clear them from our update mask
        STRNE   a1, video_gobits
        Pull     "v1-v5,pc"


; -------------------------------------------------------------------------

; void HAL_Video_SetDAG(uint DAG, uint paddr)
;
; set Video DMA address generator value to given physical address
;
; DAG   = 0 set start address of current video display
;         1 set start address of total video buffer
;         2 set end address (exclusive) of total video buffer
;         all other values reserved
; paddr = physical address for given DAG
;
; Notes:
; The OS has a video buffer which is >= total display size, and may be using
; bank switching (several display buffers) or hardware scroll within the
; total video buffer.
;
; DAG=1 will be start address of current total video buffer
; DAG=2 will be end address (exclusive) of current total video buffer
; DAG=0 will be start address in buffer for current display
;
; HALs should respond as follows:
; 1) If they have no hardware scroll support, only DAG=0 is significant,
;    and the end address of the current display is implied by the size
;    of the current mode. Calls with DAG=1,2 should be ignored.
; 2) If they support hardware scroll, DAG=0 again defines display start.
;    DAG=2 defines the last address (exclusive) that should be displayed
;    before wrapping back (if reached within display size), and DAG=1
;    defines the address to which accesses should wrap back.

HAL_Video_SetDAG ROUT
        CMP     a1, #0
        MOVNE   pc, lr
        Push    "lr"
        LDR     a3, L4_Display_Log
        STR     a2, [a3, #DISPC_GFX_BA0]
        STR     a2, [a3, #DISPC_GFX_BA1]
 [ VideoDebug
        DebugTX "SetDAG"
        DebugReg a2
 ]
        ; GODIGITAL+GOLCD to ensure it's updated
        SetGoBits a3,a1,a2,"HAL_Video_SetDAG GOLCD timeout"
        Pull    "pc"
        MOV     pc, lr


; -------------------------------------------------------------------------

;
; uint HAL_Video_Features(void)
;
; returns a flags word:
;    bit 0     h/w scroll is supported
;    bit 1     h/w pointer is supported
;    bit 2     interlace is supported with progressive framestore
;    other bits reserved (returned as 0)

HAL_Video_Features ROUT
        MOV     a1, #2_010   ; no hw scroll, h/w pointer, no interlace
        MOV     pc, lr

; -------------------------------------------------------------------------

;
; uint HAL_Video_PixelFormats(void)
;
;
; returns flags word:
;    bit 0     1 bpp is supported
;    bit 1     2 bpp is supported
;    bit 2     4 bpp is supported
;    bit 3     8 bpp is supported
;    bit 4    16 bpp is supported
;    bit 5    32 bpp is supported
;    other bits reserved (returned as 0)
;    bits 0-5 refer to support with standard RO pixel layout (little endian
;    packing for 1,2,4 bpp, 5-5-5 RGB for 16 bpp, etc.)
;    other formats may be introduced when/if RO supports them

HAL_Video_PixelFormats ROUT
        MOV     a1, #2_111111   ; 1,2,4,8,16,32 bpp
        MOV     pc, lr

; -------------------------------------------------------------------------

;
; uint HAL_Video_BufferAlignment(void)
;
; returns the required alignment for the framestore buffer, in bytes
; (expected to be a power of 2)

HAL_Video_BufferAlignment ROUT
        MOV     a1, #32          ; align to 32 bytes
        MOV     pc, lr

; -------------------------------------------------------------------------

;
; uint HAL_Video_OutputFormat(void)
;
; returns current video output format
;      0 = analogue (or 'normal', unspecified)
;      1 = 16-bit non-multiplexed RGB 5-6-5 (Chrontel 7003 format 0)
;     ...
;     10 = Chrontel 7003 format 9
;  >= 11 reserved
;

HAL_Video_OutputFormat
        MOV     a1, #0     ;normal/unspecified
        MOV     pc, lr

; -------------------------------------------------------------------------

;;;mjsHAL - is the mode workspace really generic enough to pass to HAL?
;;;

;
; int HAL_Video_VetMode(const void *VIDClist, const void *workspace)
;
; VIDClist  -> generic video controller list (VIDC list type 3)
; workspace -> mode workspace (if mode number), or 0
; returns 0 if OK (may be minor adjusts to VIDClist and/or workspace values)
;         non-zero if not OK
;
HAL_Video_VetMode ROUT
 [ :LNOT: PassiveVideo
 [ VetModeDebug
        Push "lr"
        DebugTX "HAL_Video_VetMode"
        LDR     a3, [a1, #VIDCList3_PixelRate]
        DebugTX "Pixel rate"
        DebugReg a3
        LDR     a3, [a1, #VIDCList3_PixelRate]
        MOV     a2,#1000
        Push    "a1"
        MUL     a1,a2,a3
        BL      calculate_DSS_clock_divider
        DebugTX "DSI PLL divider"
        DebugReg a1
        DebugReg a2
        ADD     a1, a2, #1
        LDR     a2, sys_clk
        BL      |__rt_udiv|
        DebugTX "Fint"
        DebugReg a1
        BL      PRCM_GetFreqSel
        DebugTX "FreqSel"
        DebugReg a1
        Pull    "a1"
        LDR     a2, [a1, #VIDCList3_HorizSyncWidth]
        DebugTX "HSW"
        DebugReg a2
        LDR     a2, [a1, #VIDCList3_HorizFrontPorch]
        DebugTX "HFP"
        DebugReg a2
        LDR     a2, [a1, #VIDCList3_HorizBackPorch]
        DebugTX "HBP"
        DebugReg a2
        LDR     a2, [a1, #VIDCList3_VertiSyncWidth]
        DebugTX "VSW"
        DebugReg a2
        LDR     a2, [a1, #VIDCList3_VertiFrontPorch]
        DebugTX "VFP"
        DebugReg a2
        LDR     a2, [a1, #VIDCList3_VertiBackPorch]
        DebugTX "VBP"
        DebugReg a2
        LDR     a2, [a1, #VIDCList3_HorizDisplaySize]
        DebugTX "Width"
        DebugReg a2
        LDR     a2, [a1, #VIDCList3_VertiDisplaySize]
        DebugTX "Height"
        DebugReg a2
        LDR     a2, [a1, #VIDCList3_PixelDepth]
        DebugTX "Colours"
        DebugReg a2
        Pull "lr"
 ]
        ; Check pixel rate
        LDR     a3, [a1, #VIDCList3_PixelRate]
        LDR     a2, =MAX_PIXEL_RATE
        CMP     a3, a2
        MOVGT   pc,lr
        CMP     a3,#0
        MOVEQ   pc,lr
        MOV     a2,#1000
        Push    "a1,lr"
        MUL     a1,a2,a3
        BL      calculate_DSS_clock_divider
        CMP     a1, #2048
        CMPLO   a2, #128
        Pull    "a1,pc",HS
        ; Check Fint is between 0.75MHz and 2.1MHz
        ADD     a1, a2, #1
        LDR     a2, sys_clk
        BL      |__rt_udiv|
        BL      PRCM_GetFreqSel
        SUB     a1, a1, #3
        CMP     a1, #4
        Pull    "a1,lr"
        MOVHI   pc,lr ; Accept FreqSel values of 3-7 (0.75MHz->2.1MHz)
        ; Check H, V timing values
        ; Although SetMode does attempt to adjust these itself, for the time being let's try and avoid any modes with out of range values altogether.
        LDR     a2, [a1, #VIDCList3_HorizSyncWidth]
        LDR     a3, video_maxsync
        SUB     a2, a2, #1
        CMP     a2, a3
        MOVHS   pc,lr
        LDR     a2, [a1, #VIDCList3_VertiSyncWidth]
        SUB     a2, a2, #1
        CMP     a2, a3
        MOVHS   pc,lr
        LDR     a2, [a1, #VIDCList3_HorizFrontPorch]
        LDR     a3, video_maxporch
        SUB     a2, a2, #1
        CMP     a2, a3
        MOVHS   pc,lr
        LDR     a2, [a1, #VIDCList3_HorizBackPorch]
        SUB     a2, a2, #1
        CMP     a2, a3
        MOVHS   pc,lr
        LDR     a2, [a1, #VIDCList3_VertiFrontPorch]
        SUB     a2, a2, #1
        CMP     a2, a3
        MOVHS   pc,lr
        LDR     a2, [a1, #VIDCList3_VertiBackPorch]
        SUB     a2, a2, #1
        CMP     a2, a3
        MOVHS   pc,lr
        ; Check (HBP+HSW+HFP)*PCD > 8
        LDR     a2, [a1, #VIDCList3_HorizBackPorch]
        LDR     a3, [a1, #VIDCList3_HorizSyncWidth]
        LDR     a4, [a1, #VIDCList3_HorizFrontPorch]
        ADD     a2, a2, a3
        ADD     a2, a2, a4
        CMP     a2, #4 ; We always use PCD of 2
        MOVLE   pc,lr
        ; Check screen size
        LDR     a2, [a1, #VIDCList3_HorizDisplaySize]
        SUB     a2, a2, #1
        CMP     a2, #2048
        MOVHS   pc,lr
        LDR     a2, [a1, #VIDCList3_VertiDisplaySize]
        SUB     a2, a2, #1
        CMP     a2, #2048
        ; Check colour depth
        LDR     a2, [a1, #VIDCList3_PixelDepth]
        CMP     a2, #5
        MOVHI   pc,lr
 ] ; PassiveVideo
        ; Else we're good
        MOV     a1,#0
        MOV     pc,lr

; -------------------------------------------------------------------------

; Function for calculating DSI PLL clock dividers
; in:  a1 = desired pixel rate (Hz)
; out: a1 = DSI_PLL_REGM (values not guaranteed to be in range)
;      a2 = DSI_PLL_REGN
; all other regs preserved
;
; The full clock flow is as follows:
;   DSI PLL input = DSI_PLL_REFCLK (= DSS2_ALWON_FCLK = sys_clk)
;   Data lane freq = DSIPHY = 2*pixel rate*DSI_BPP/DSI_LANES
;   DSIPHY = 2*DSI_PLL_REGM/(DSI_PLL_REGN+1)*DSI_PLL_REFCLK/(HIGHFREQ+1)
;   HIGHFREQ = (DSI_PLL_REFCLK>32MHz?1:0) (=0 for all current sys_clk values)
; Also, 0.75MHz <= Fint <= 2.1MHz where Fint = DSI_PLL_REFCLK/(DSI_PLL_REGN+1)
; DSIPHY is then used as the source for the HSDIVIDER, which does the following:
;   DSI1_PLL_FCLK = DSIPHY/REGM3
;   DSI2_PLL_FCLK = DSIPHY/REGM4
; Both must be <= 173MHz
; DSI2_PLL_FCLK can be used to drive the DSI module, except we don't care much for that
; DSI1_PLL_FCLK is what we use to drive the DISPC pixel clock:
;   pixel rate = (DSI1_PLL_FCLK/LCD)/PCD
;   1 <= LCD <= 255
;   2 <= PCD <= 255
; Even though we don't use the DSI output module, we still calculate DSIPHY as if we are using it (so that DSIPHY = data lane freq)
; This then simplifies REGM3, REGM4, LCD and PCD programming so that it can use fixed values independent of the pixel rate:
;   REGM3 = DSIPHY/(pixel rate*2) = DSI_LANES/DSI_BPP (so that DSI1_PLL_FCLK = 2*pixel rate)
;   LCD=1, PCD=2 (so that DSI1_PLL_FCLK is divided by two to get the target pixel rate)
;
; An algorithm that aims for 100% accurate clock rates was found to be incapable of calculating divider values for most RISC OS screen modes (in particular the 25.175MHz mode used at boot). Instead, an algorithm that aims to find the closest possible clock rate is used. Most clock rate errors appear to be tiny (within 1kHz of the desired data lane frequency), so this shouldn't pose any problems.
;
calculate_DSS_clock_divider
        CMP     a1, #0
        MVNEQ   a2, #0
        MOVEQ   pc, lr
        Push    "a3-v5,lr"
        ; data lane freq = 2*pixel rate*bpp/lanes
        ASSERT  ((2*DSI_BPP) :MOD: DSI_LANES)=0
        MOV     a3, #(2*DSI_BPP)/DSI_LANES
        MUL     v3, a3, a1 ; data lane freq
        MOV     v1, v3 ; Best error. This can easily be modified to limit the clock to a maximum error value, either fixed or to within a percentage of the pixel rate.
        MVN     v4, #0 ; Best REGM
        MVN     v5, #0 ; Best REGN
        ; Search for satisfactory REGN:
        ; - Provides Fint that's within range
        ; - sys_clk MOD REGN = 0
        MOV     v2, #1 ; Current N(+1)
;        DebugTX "calculate_DSS_clock_divider"
        LDR     a1, sys_clk
;        DebugReg a1
10
        MOV     a1, v2
        LDR     a2, sys_clk
        BL      |__rt_udiv| ; a1 = Fint, a2 = remainder
        CMP     a2, #0
        BNE     %FT20
        LDR     a3, =750000
        LDR     a4, =2100000
        CMP     a1, a3
        CMPHI   a4, a1
        BLO     %FT20
;        DebugTX "REGN -> Fint"
;        DebugReg v2
;        DebugReg a1
        ; REGN is good; use it to calculate nearest REGM
        MOV     a4, a1 ; Fint
        MOV     a1, a4, LSL #1 ; Fint*2
        MOV     a2, v3
;        DebugTX "REGM calc"
;        DebugReg a1
;        DebugReg a2
        BL      |__rt_udiv| ; a1 = REGM, a2 = error
;        DebugTX "REGM result"
;        DebugReg a1
;        DebugReg a2
        ; Round to nearest, maintaining a2 as the error
        CMP     a2, a4
        ADDGE   a1, a1, #1
        RSBGE   a2, a2, a4, LSL #1
        ; Is a2 better than our current error?
        CMP     a2, v1
        MOVLT   v1, a2
        MOVLT   v4, a1
        MOVLT   v5, v2
        CMP     a2, #0 ; Is it perfect?
        BEQ     %FT30
20
        ; Try another N
        ADD     v2, v2, #1
        CMP     v2, #129
        BLT     %BT10
30
        ; Use v4 & v5, whether they're accurate or not
        MOV     a1, v4 ; DSI_PLL_REGM
        SUB     a2, v5, #1 ; DSI_PLL_REGN
        Pull    "a3-v5,pc"

  | ; HALDoesVideo

Video_init
        ; Configure GPIO pins so we can turn the DVI framer on/off
        LDRB    a1, [sb, #BoardConfig_VideoGPIO]
        CMP     a1, #255
        MOV     a2, #0
        BNE     GPIOx_SetAsOutput ; Turn DVI framer off
        MOV     pc, lr

; Stubs for unused HAL functions

HAL_Video_OutputFormat
HAL_Video_VetMode
        MOV     r0, #0
HAL_Video_SetMode
HAL_Video_WritePaletteEntry
HAL_Video_WritePaletteEntries
HAL_Video_SetInterlace
HAL_Video_SetBlank
HAL_Video_SetPowerSave
HAL_Video_UpdatePointer
HAL_Video_SetDAG
HAL_Video_ReadPaletteEntry
        MOV     pc, lr

HAL_VideoFlybackDevice
        MOV     a1, #-1
        MOV     pc, lr

HAL_Video_PixelFormats
        MOV     a1, #2_111111
        MOV     pc, lr

HAL_Video_Features
        MOV     a1, #2_10010            ; hw pointer, no VSyncs
        MOV     pc, lr

HAL_Video_BufferAlignment
        MOV     a1, #4
        MOV     pc, lr

VideoDevice_Init
        ; Not much to do here - just register our HAL device
        Push    "v1,lr"
        ADRL    v1, VideoDevice
        MOV     a1, v1
        ADR     a2, VideoDeviceTemplate
        MOV     a3, #Video_DeviceSize
        BL      memcpy
        LDR     a1, L4_Display_Log
        STR     a1, [v1, #HALDevice_Address]
        ADRL    a3, VideoBoardConfig
        STR     a3, [v1, #HALDevice_VDUDeviceSpecificField]
        STR     sb, [v1, #:INDEX:VideoWorkspace]
        ; Fill in the board config
        LDR     a2, sys_clk
        STR     a2, [a3, #VideoBoardConfig_sys_clk]
        BL      Determine_PorchSync_Limits
        STRH    a1, [a3, #VideoBoardConfig_Max_Porch]
        STRH    a2, [a3, #VideoBoardConfig_Max_Sync]
        LDR     a2, L4_sDMA_Log
        STR     a2, [a3, #VideoBoardConfig_DMA_Ptr]
        MOV     a2, #SDMA_IRQ_1
        STR     a2, [a3, #VideoBoardConfig_DMA_Device]
        ASSERT  SDMA_NumDevices = 31
        MOV     a2, #&80000000
        STR     a2, [a3, #VideoBoardConfig_DMA_Chans]
        LDRB    a2, [sb, #BoardConfig_VBC_Flags]
        STRB    a2, [a3, #VideoBoardConfig_Flags]
        LDRB    a2, [sb, #BoardConfig_VBC_LCDNum]
        STRB    a2, [a3, #VideoBoardConfig_Num_LCDs]
        LDR     a2, [sb, #BoardConfig_VBC_LCDPtr]
        STR     a2, [a3, #VideoBoardConfig_LCD_Configs]
        MOV     a1, #0
        MOV     a2, v1
        CallOS  OS_AddDevice
        Pull    "v1,pc"

VideoDeviceTemplate
        DCW     HALDeviceType_Video + HALDeviceVideo_VDU
        DCW     HALDeviceID_VDU_OMAP3
        DCD     HALDeviceBus_Interconnect + HALDeviceInterconnectBus_L3
        DCD     0               ; API version 0
        DCD     VideoDevice_Desc
        DCD     0               ; Address - filled in later
        %       12              ; Reserved
        DCD     VideoDevice_Activate
        DCD     VideoDevice_Deactivate
        DCD     VideoDevice_Reset
        DCD     VideoDevice_Sleep
        DCD     VIDEO_IRQ ; Device.
        DCD     0               ; TestIRQ cannot be called
        %       8
        DCD     0          ; Pointer to board config stuff - filled in later
        ASSERT (.-VideoDeviceTemplate) = HALDevice_VDU_Size
        DCD     0          ; HAL workspace pointer - filled in later
        ASSERT (.-VideoDeviceTemplate) = Video_DeviceSize

VideoDevice_Desc
        =       "OMAP3 video controller", 0
        ALIGN

VideoDevice_Activate
        Entry   "sb"
        LDR     sb, VideoWorkspace
        ; Enable DSS power
        ; TODO - should disable interrupts for this bit!
        LDR     a1, L4_ClockMan_Log
        LDR     a2, [a1, #CM_ICLKEN_DSS]
        ORR     a2, a2, #1 ; EN_DSS (correct to enable iclk before fclk?)
        STR     a2, [a1, #CM_ICLKEN_DSS]
        LDR     a2, [a1, #CM_FCLKEN_DSS]
        ORR     a2, a2, #7 ; EN_DSS1, EN_DSS2, EN_TV (required for reset)
        STR     a2, [a1, #CM_FCLKEN_DSS]
        MOV     a1, #1
        EXIT

VideoDevice_Deactivate ; TODO!
VideoDevice_Reset
        MOV     pc, lr

VideoDevice_Sleep
        MOV     a1, #0
        MOV     pc, lr

Video_Power_VBC_Pandora
Video_Power_VBC_DVI
        ; a1 = HAL device
        ; a2 = brightness 0-65536, which we treat as a simple on/off flag
        Entry   "sb"
        LDR     sb, VideoWorkspace
        ; Just set the GPIO to the right value
        LDRB    a1, [sb, #BoardConfig_VideoGPIO]
        CMP     a1, #255
        BLNE    GPIOx_SetOutput
        EXIT

Video_Power_VBC_TouchBook
        ; a1 = HAL device
        ; a2 = brightness 0-65536
        Entry   "sb"
        LDR     sb, VideoWorkspace
        ; TODO - Proper brightness controls
        ; For now, just toggle on/off, via the SCPWM bit of GPTIMER9.TCLR
        LDR     a3, Timers_Log
        ADD     a3, a3, #L4_GPTIMER9-TIMER_BASE
        CMP     a2, #0
        LDR     a4, [a3, #TCLR]
        BICEQ   a4, a4, #&80
        ORRNE   a4, a4, #&80
        STR     a4, [a3, #TCLR]
        ; Also toggle the GPIO that controls the DVI framer
        LDRB    a1, [sb, #BoardConfig_VideoGPIO]
        CMP     a1, #255
        BLNE    GPIOx_SetOutput
        EXIT

  ]

Determine_PorchSync_Limits
        ; Returns:
        ; a1 = max porch value
        ; a2 = max sync value
        ; Get the OMAP revision so we can find out how large the timing registers are. OMAP35x errata shows that in ES 3.1+ the registers were increased from 8/6 bits to 12/8 bits.
        LDR     a2, L4_Wakeup_Log
        ADD     a2, a2, #(L4_CONTROL_IDCODE-L4_Wakeup) :AND: &FF00
        LDR     a2, [a2, #(L4_CONTROL_IDCODE-L4_Wakeup) :AND: &00FF]
        ; Check revision first
        CMP     a2, #REVISION_ES31 :SHL: 28
        MOVHS   a1, #1:SHL:12
        MOVHS   a2, #1:SHL:8
        MOVHS   pc, lr
        ; Revision check failed - check if this is OMAP35x
        ; If not, assume it's something newer which supports the larger timings
        LDR     a1, =HAWKEYE_OMAP35x_ES10
        MOV     a2, a2, LSL #4
        CMP     a1, a2, LSR #16
        LDRNE   a1, =HAWKEYE_OMAP35x
        CMPNE   a1, a2, LSR #16
        MOVNE   a1, #1:SHL:12
        MOVNE   a2, #1:SHL:8
        MOVEQ   a1, #1:SHL:8
        MOVEQ   a2, #1:SHL:6
        MOV     pc, lr

        END