embeddedreverse-engineeringx86-16disassemblylcd

Reverse engineer LCD Protocol used in MPC2000XL


I'm trying to know the protocol used for that device. LCD is : 248*60 Binary Size is 512Kb.

The CPU that is used : IC10, UPD70236: V53A is an NEC branch of the Intel 8086 (via their famous V20, V30, etc. series), with various extensions including system peripherals (like DMA), expanded memory (for a total 24-bit memory space), and faster clock and instruction rates.

I'm looking for the functions that are responsible for LCD Write Text, and how are the pins are toggled, specifically, the LCD commands that are sent.

ROM can be find here: https://www.mediafire.com/file/bdepkvyz7dsr9pj/MPC2KXL.BIN/file

Also in the website has everything, including schematic:

http://zine.r-massive.com/akai-mpc-2000xl-archive/

Here is the LCD Socket with the signals:

enter image description here


Solution

  • TL;DR:

    Re-engineering leads to these insights, reduced to relevant details for a replacement.

    The LCD has a width of 256 pixels and a height of 60 pixels.

    There are two driver ICs of the same type. They use the addresses 0x60/0x62 and 0x100/0x102, respectively. The driver ICs receive the address reduced to the single bit A1. The chip select lines nCS1 and nCS2 are active mutually exclusive for each driver IC. The prefix "n" denotes the active-low nature of a line.

    nRST nCS1 nCS2 A1 nIORD nIOWR Meaning
    0 x x x x x Asynchronous reset
    1 1 1 x x x No access
    1 0 0 x x x Irregular, not generated by MCU
    1 x x x 1 1 No access
    1 x x x 0 0 Illegal, not generated by MCU
    1 0 1 0 0 1 Read status of LCD1
    1 0 1 1 0 1 Read data of LCD1, not used by firmware
    1 0 1 0 1 0 Write command of LCD1
    1 0 1 1 1 0 Read command of LCD1
    1 1 0 0 0 1 Read status of LCD2
    1 1 0 1 0 1 Read data of LCD2, not used by firmware
    1 1 0 0 1 0 Write command of LCD2
    1 1 0 1 1 0 Read command of LCD2

    Reading 0x60 and 0x100, respectively, returns status. Bit 7 of the status signals "busy" when set, other bits seem to signal reasons for being busy. A replacement of sufficing speed can simply return 0x00.

    Writing 0x60 and 0x100, respectively, sets a command. Reducing the effort to the minimum, only commands 0x23 to 0x20 are relevant for a replacement specific for the use case:

    Writing 0x62 and 0x102, respectively, adds one or more parameter bytes after a command:


    Details

    A web recherche for the LCD revealed no usable results, so I went the hard way to re-engineer the firmware.

    This image of the LCD was posted on the OP's thread on a forum, it shows that the LCD has two most probably equal driver ICs. Unfortunately there is no IC marking.

    LCD image

    The following excerpts of the schematic (nicely linked by Clifford to a source without paywall) show relevant details:

    LCD connector

    Decoder for LCD1N and LCD2N

    The schematic details reveal the IO addresses.

    The two chip select lines back up the theory that there are two driver ICs. Such configuration is often used in a main-sub relation, where the main driver synchronizes the sub driver. This way LCDs can be driven that are larger than a single driver can control.

    The address line A1 suggests that each driver IC has the common organization of a command address and a data address. Later findings support that idea.

    The V53 CPU has its reset vector at 0xFFFF:0x0000. The linked binary has a size of 512 kiB = 0x80000, which needs an offset of 0x80000 to provide its end at that location.

    Using Ghidra I set up the loaded binary with said offset and added a RAM of same size at offset 0. Then I searched for words of 0x0060, 0x0062, 0x0100, and 0x0102. The findings look quite promising. Note: the following edited excerpts already show edited function definitions.

    The following are clearly the base functions to access the LCD.

                         **************************************************************
                         *                          FUNCTION                          *
                         **************************************************************
                         void __cdecl16near outLcd1Control(uint8_t value)
         void              <VOID>         <RETURN>
         uint8_t           AL:1           value
    9000:ab9b ba 60 00        MOV        DX,0x60
    9000:ab9e ee              OUT        DX,value
    9000:ab9f c3              RET
                         **************************************************************
                         *                          FUNCTION                          *
                         **************************************************************
                         void __cdecl16near outLcd1Data(uint8_t value)
         void              <VOID>         <RETURN>
         uint8_t           AL:1           value
    9000:aba0 ba 62 00        MOV        DX,0x62
    9000:aba3 ee              OUT        DX,value
    9000:aba4 c3              RET
                         **************************************************************
                         *                          FUNCTION                          *
                         **************************************************************
                         void __cdecl16near waitForLcd1Ready(void)
         void              <VOID>         <RETURN>
         uint16_t          CX:2           iVar2
    9000:aba5 50              PUSH       AX
    9000:aba6 51              PUSH       CX
    9000:aba7 b9 ff ff        MOV        iVar2,0xffff
                         LAB_9000_abaa
    9000:abaa ba 60 00        MOV        DX,0x60
    9000:abad ec              IN         AL,DX
    9000:abae a8 80           TEST       AL,0x80
    9000:abb0 74 02           JZ         LAB_9000_abb4
    9000:abb2 e2 f6           LOOP       LAB_9000_abaa
                         LAB_9000_abb4
    9000:abb4 59              POP        iVar2
    9000:abb5 58              POP        AX
    9000:abb6 c3              RET
                         **************************************************************
                         *                          FUNCTION                          *
                         **************************************************************
                         void __cdecl16near outLcd2Control(uint8_t value)
         void              <VOID>         <RETURN>
         uint8_t           AL:1           value
    9000:abb7 ba 00 01        MOV        DX,0x100
    9000:abba ee              OUT        DX,value
    9000:abbb c3              RET
                         **************************************************************
                         *                          FUNCTION                          *
                         **************************************************************
                         void __cdecl16near outLcd2Data(uint8_t value)
         void              <VOID>         <RETURN>
         uint8_t           AL:1           value
    9000:abbc ba 02 01        MOV        DX,0x102
    9000:abbf ee              OUT        DX,value
    9000:abc0 c3              RET
                         **************************************************************
                         *                          FUNCTION                          *
                         **************************************************************
                         void __cdecl16near waitForLcd2Ready(void)
         void              <VOID>         <RETURN>
         uint16_t          CX:2           iVar2
    9000:abc1 50              PUSH       AX
    9000:abc2 51              PUSH       CX
    9000:abc3 b9 ff ff        MOV        iVar2,0xffff
                         LAB_9000_abc6
    9000:abc6 ba 00 01        MOV        DX,0x100
    9000:abc9 ec              IN         AL,DX
    9000:abca a8 80           TEST       AL,0x80
    9000:abcc 74 02           JZ         LAB_9000_abd0
    9000:abce e2 f6           LOOP       LAB_9000_abc6
                         LAB_9000_abd0
    9000:abd0 59              POP        iVar2
    9000:abd1 58              POP        AX
    9000:abd2 c3              RET
    

    One important finding is that reading 0x60 and 0x100, respectively, returns status. Bit 7 of the status apparently signals "busy" when set. A counter avoids an endless loop in case of any error.

    After disassembling more of the neighborhood, more interesting functions show up.

    The following looks like the initializing of the LCD.

                         **************************************************************
                         *                          FUNCTION                          *
                         **************************************************************
                         void __cdecl16near initializeLcd(void)
         void              <VOID>         <RETURN>
    9000:aa74 b0 23           MOV        AL,0x23
    9000:aa76 e8 22 01        CALL       outLcd1Control
    9000:aa79 b0 85           MOV        AL,0x85
    9000:aa7b e8 27 01        CALL       waitForLcd1Ready
    9000:aa7e e8 1f 01        CALL       outLcd1Data
    9000:aa81 b0 24           MOV        AL,0x24
    9000:aa83 e8 15 01        CALL       outLcd1Control
    9000:aa86 b0 01           MOV        AL,0x1
    9000:aa88 e8 1a 01        CALL       waitForLcd1Ready
    9000:aa8b e8 12 01        CALL       outLcd1Data
    9000:aa8e b0 23           MOV        AL,0x23
    9000:aa90 e8 24 01        CALL       outLcd2Control
    9000:aa93 b0 8d           MOV        AL,0x8d
    9000:aa95 e8 29 01        CALL       waitForLcd2Ready
    9000:aa98 e8 21 01        CALL       outLcd2Data
    9000:aa9b b0 24           MOV        AL,0x24
    9000:aa9d e8 17 01        CALL       outLcd2Control
    9000:aaa0 b0 01           MOV        AL,0x1
    9000:aaa2 e8 1c 01        CALL       waitForLcd2Ready
    9000:aaa5 e8 14 01        CALL       outLcd2Data
    9000:aaa8 e8 28 01        CALL       delayXxxUs
    9000:aaab b3 00           MOV        BL,0x0
                         LAB_9000_aaad
    9000:aaad b0 22           MOV        AL,0x22
    9000:aaaf e8 e9 00        CALL       outLcd1Control
    9000:aab2 8a c3           MOV        AL,BL
    9000:aab4 e8 ee 00        CALL       waitForLcd1Ready
    9000:aab7 e8 e6 00        CALL       outLcd1Data
    9000:aaba b0 21           MOV        AL,0x21
    9000:aabc e8 dc 00        CALL       outLcd1Control
    9000:aabf b0 00           MOV        AL,0x0
    9000:aac1 e8 e1 00        CALL       waitForLcd1Ready
    9000:aac4 e8 d9 00        CALL       outLcd1Data
    9000:aac7 b0 20           MOV        AL,0x20
    9000:aac9 e8 cf 00        CALL       outLcd1Control
    9000:aacc b9 14 00        MOV        CX,0x14
                         LAB_9000_aacf
    9000:aacf b0 00           MOV        AL,0x0
    9000:aad1 e8 d1 00        CALL       waitForLcd1Ready
    9000:aad4 e8 c9 00        CALL       outLcd1Data
    9000:aad7 e2 f6           LOOP       LAB_9000_aacf
    9000:aad9 fe c3           INC        BL
    9000:aadb 80 fb 41        CMP        BL,0x41
    9000:aade 75 cd           JNZ        LAB_9000_aaad
    9000:aae0 e8 f0 00        CALL       delayXxxUs
    9000:aae3 b3 00           MOV        BL,0x0
                         LAB_9000_aae5
    9000:aae5 b0 22           MOV        AL,0x22
    9000:aae7 e8 cd 00        CALL       outLcd2Control
    9000:aaea 8a c3           MOV        AL,BL
    9000:aaec e8 d2 00        CALL       waitForLcd2Ready
    9000:aaef e8 ca 00        CALL       outLcd2Data
    9000:aaf2 b0 21           MOV        AL,0x21
    9000:aaf4 e8 c0 00        CALL       outLcd2Control
    9000:aaf7 b0 00           MOV        AL,0x0
    9000:aaf9 e8 c5 00        CALL       waitForLcd2Ready
    9000:aafc e8 bd 00        CALL       outLcd2Data
    9000:aaff b0 20           MOV        AL,0x20
    9000:ab01 e8 b3 00        CALL       outLcd2Control
    9000:ab04 b9 14 00        MOV        CX,0x14
                         LAB_9000_ab07
    9000:ab07 b0 00           MOV        AL,0x0
    9000:ab09 e8 b5 00        CALL       waitForLcd2Ready
    9000:ab0c e8 ad 00        CALL       outLcd2Data
    9000:ab0f e2 f6           LOOP       LAB_9000_ab07
    9000:ab11 fe c3           INC        BL
    9000:ab13 80 fb 41        CMP        BL,0x41
    9000:ab16 75 cd           JNZ        LAB_9000_aae5
    9000:ab18 e8 b8 00        CALL       delayXxxUs
    9000:ab1b b9 00 00        MOV        CX,0x0
    9000:ab1e b3 64           MOV        BL,0x64
                         LAB_9000_ab20
    9000:ab20 51              PUSH       CX
    9000:ab21 53              PUSH       BX
    9000:ab22 b0 22           MOV        AL,0x22
    9000:ab24 e8 90 00        CALL       outLcd2Control
    9000:ab27 8a c1           MOV        AL,CL
    9000:ab29 e8 95 00        CALL       waitForLcd2Ready
    9000:ab2c e8 8d 00        CALL       outLcd2Data
    9000:ab2f b0 21           MOV        AL,0x21
    9000:ab31 e8 83 00        CALL       outLcd2Control
    9000:ab34 8a c3           MOV        AL,BL
    9000:ab36 2a e4           SUB        AH,AH
    9000:ab38 b1 08           MOV        CL,0x8
    9000:ab3a f6 f1           DIV        CL
    9000:ab3c 8a dc           MOV        BL,AH
    9000:ab3e 2a ff           SUB        BH,BH
    9000:ab40 e8 7e 00        CALL       waitForLcd2Ready
    9000:ab43 e8 76 00        CALL       outLcd2Data
    9000:ab46 b0 20           MOV        AL,0x20
    9000:ab48 e8 6c 00        CALL       outLcd2Control
    9000:ab4b 8a 87 b7 75     MOV        AL,byte ptr [BX + 0x75b7]
    9000:ab4f e8 6f 00        CALL       waitForLcd2Ready
    9000:ab52 e8 67 00        CALL       outLcd2Data
    9000:ab55 5b              POP        BX
    9000:ab56 59              POP        CX
    9000:ab57 fe c3           INC        BL
    9000:ab59 fe c1           INC        CL
    9000:ab5b 80 f9 3c        CMP        CL,0x3c
    9000:ab5e 75 c0           JNZ        LAB_9000_ab20
    9000:ab60 b0 23           MOV        AL,0x23
    9000:ab62 e8 36 00        CALL       outLcd1Control
    9000:ab65 b0 05           MOV        AL,0x5
    9000:ab67 e8 3b 00        CALL       waitForLcd1Ready
    9000:ab6a e8 33 00        CALL       outLcd1Data
    9000:ab6d b0 24           MOV        AL,0x24
    9000:ab6f e8 29 00        CALL       outLcd1Control
    9000:ab72 b0 01           MOV        AL,0x1
    9000:ab74 e8 2e 00        CALL       waitForLcd1Ready
    9000:ab77 e8 26 00        CALL       outLcd1Data
    9000:ab7a b0 23           MOV        AL,0x23
    9000:ab7c e8 38 00        CALL       outLcd2Control
    9000:ab7f b0 2d           MOV        AL,0x2d
    9000:ab81 e8 3d 00        CALL       waitForLcd2Ready
    9000:ab84 e8 35 00        CALL       outLcd2Data
    9000:ab87 b0 24           MOV        AL,0x24
    9000:ab89 e8 2b 00        CALL       outLcd2Control
    9000:ab8c b0 01           MOV        AL,0x1
    9000:ab8e e8 30 00        CALL       waitForLcd2Ready
    9000:ab91 e8 28 00        CALL       outLcd2Data
    9000:ab94 e8 3c 00        CALL       delayXxxUs
    9000:ab97 e8 92 01        CALL       clearBuffer
    9000:ab9a c3              RET
    

    As a simplified C function it looks like this.

    void initializeLcd(void) {
      outLcd1Control(0x23);
      waitForLcd1Ready();
      outLcd1Data(0x85);
    
      outLcd1Control(0x24);
      waitForLcd1Ready();
      outLcd1Data(1);
    
      outLcd2Control(0x23);
      waitForLcd2Ready();
      outLcd2Data(0x8d);
    
      outLcd2Control(0x24);
      waitForLcd2Ready();
      outLcd2Data(1);
    
      delayXxxUs();
    
      for (uint8_t y = 0; y != 65; y++) {
        outLcd1Control(0x22);
        waitForLcd1Ready();
        outLcd1Data(y);
    
        outLcd1Control(0x21);
        waitForLcd1Ready();
        outLcd1Data(0);
    
        outLcd1Control(0x20);
        for (int i = 0; i != 20; i++) {
          waitForLcd1Ready();
          outLcd1Data(0);
        }
      }
    
      delayXxxUs();
    
      for (uint8_t y = 0; y != 65; y++) {
        outLcd2Control(0x22);
        waitForLcd2Ready();
        outLcd2Data(y);
    
        outLcd2Control(0x21);
        waitForLcd2Ready();
        outLcd2Data(0);
    
        outLcd2Control(0x20);
        for (int i = 0; i != 20; i++) {
          waitForLcd2Ready();
          outLcd2Data(0);
        }
      }
    
      delayXxxUs();
    
      uint8_t x = 100;
      for (uint8_t y = 0; y != 60; y++) {
        outLcd2Control(0x22);
        waitForLcd2Ready();
        outLcd2Data(y);
    
        outLcd2Control(0x21);
        waitForLcd2Ready();
        outLcd2Data(x / 8);
    
        outLcd2Control(0x20);
        waitForLcd2Ready();
        outLcd2Data(0x75b7[x % 8]);
        x++;
      }
    
      outLcd1Control(0x23);
      waitForLcd1Ready();
      outLcd1Data(0x05);
    
      outLcd1Control(0x24);
      waitForLcd1Ready();
      outLcd1Data(1);
    
      outLcd2Control(0x23);
      waitForLcd2Ready();
      outLcd2Data(0x2d);
    
      outLcd2Control(0x24);
      waitForLcd2Ready();
      outLcd2Data(1);
    
      delayXxxUs();
    
      clearBuffer();
    }
    

    This function sets multiple control values of unknown meaning. Some of them can be deduced from their parameters.

    For example, command 0x23 has the parameter 0x85 and 0x8d, respectively, for LCD1 and LCD2. We can assume the bit 3 of the parameter selects main/sub. Later the command has the parameter 0x05 and 0x2d, respectively, for LCD1 and LCD2.

    There is a function delayxxxUs() to delay for an interval I did not calculate. It is not relevant for the analysis.

    There is a function clearBuffer() that clears a buffer in RAM. This is also not relevant for the LCD, as this function does not access the drivers.

    After more searching, there is this huge part that apparently copies some buffers in different ways to the LCD. The specific differences are not relevant for the LCD. In the end, its drivers receive and store pixel data.

                         **************************************************************
                         *                          FUNCTION                          *
                         **************************************************************
                         void __cdecl16near copy(void)
         void              <VOID>         <RETURN>
    9000:abdc 81 3e a2        CMP        word ptr [0x75a2],0x6e22
             75 22 6e
    9000:abe2 74 0a           JZ         LAB_9000_abee
    9000:abe4 80 3e 0e        CMP        byte ptr [0x760e],0x0
             76 00
    9000:abe9 75 03           JNZ        LAB_9000_abee
    9000:abeb e9 97 00        JMP        copyAllMasked
                         LAB_9000_abee
    9000:abee 8b 36 a2 75     MOV        SI,word ptr [0x75a2]
    9000:abf2 2b db           SUB        BX,BX
    9000:abf4 b9 3c 00        MOV        CX,0x3c
                         LAB_9000_abf7
    9000:abf7 e8 08 00        CALL       copyUnmasked
    9000:abfa fe c3           INC        BL
    9000:abfc 80 fb 20        CMP        BL,0x20
    9000:abff 75 f6           JNZ        LAB_9000_abf7
    9000:ac01 c3              RET
                         **************************************************************
                         *                          FUNCTION                          *
                         **************************************************************
                         void __cdecl16near copyUnmasked(uint8_t *buffer, uint8_t x, uint16_t height)
         void              <VOID>         <RETURN>
         uint8_t *         SI:2           buffer
         uint8_t           BL:1           x
         uint16_t          CX:2           height
    9000:ac02 80 fb 14        CMP        x,0x14
    9000:ac05 73 3e           JNC        LAB_9000_ac45
    9000:ac07 53              PUSH       x
    9000:ac08 51              PUSH       height
    9000:ac09 b0 22           MOV        AL,0x22
    9000:ac0b e8 8d ff        CALL       outLcd1Control
    9000:ac0e b0 00           MOV        AL,0x0
    9000:ac10 e8 92 ff        CALL       waitForLcd1Ready
    9000:ac13 e8 8a ff        CALL       outLcd1Data
    9000:ac16 b0 21           MOV        AL,0x21
    9000:ac18 e8 80 ff        CALL       outLcd1Control
    9000:ac1b 8a c3           MOV        AL,x
    9000:ac1d e8 85 ff        CALL       waitForLcd1Ready
    9000:ac20 e8 7d ff        CALL       outLcd1Data
    9000:ac23 b0 20           MOV        AL,0x20
    9000:ac25 e8 73 ff        CALL       outLcd1Control
    9000:ac28 2a ff           SUB        BH,BH
                         LAB_9000_ac2a
    9000:ac2a 8a 20           MOV        AH,byte ptr [x + buffer]
    9000:ac2c ba 60 00        MOV        DX,0x60
    9000:ac2f ec              IN         AL,DX
    9000:ac30 d0 e0           SHL        AL,0x1
    9000:ac32 74 03           JZ         LAB_9000_ac37
    9000:ac34 e8 6e ff        CALL       waitForLcd1Ready
                         LAB_9000_ac37
    9000:ac37 8a c4           MOV        AL,AH
    9000:ac39 ba 62 00        MOV        DX,0x62
    9000:ac3c ee              OUT        DX,AL
    9000:ac3d 83 c3 20        ADD        x,0x20
    9000:ac40 e2 e8           LOOP       LAB_9000_ac2a
    9000:ac42 59              POP        height
    9000:ac43 5b              POP        x
    9000:ac44 c3              RET
                         LAB_9000_ac45
    9000:ac45 53              PUSH       BX
    9000:ac46 51              PUSH       CX
    9000:ac47 b0 22           MOV        AL,0x22
    9000:ac49 e8 6b ff        CALL       outLcd2Control
    9000:ac4c b0 00           MOV        AL,0x0
    9000:ac4e e8 70 ff        CALL       waitForLcd2Ready
    9000:ac51 e8 68 ff        CALL       outLcd2Data
    9000:ac54 b0 21           MOV        AL,0x21
    9000:ac56 e8 5e ff        CALL       outLcd2Control
    9000:ac59 8a c3           MOV        AL,BL
    9000:ac5b 2c 14           SUB        AL,0x14
    9000:ac5d e8 61 ff        CALL       waitForLcd2Ready
    9000:ac60 e8 59 ff        CALL       outLcd2Data
    9000:ac63 b0 20           MOV        AL,0x20
    9000:ac65 e8 4f ff        CALL       outLcd2Control
    9000:ac68 2a ff           SUB        BH,BH
                         LAB_9000_ac6a
    9000:ac6a 8a 20           MOV        AH,byte ptr [BX + SI]
    9000:ac6c ba 00 01        MOV        DX,0x100
    9000:ac6f ec              IN         AL,DX
    9000:ac70 d0 e0           SHL        AL,0x1
    9000:ac72 74 03           JZ         LAB_9000_ac77
    9000:ac74 e8 4a ff        CALL       waitForLcd2Ready
                         LAB_9000_ac77
    9000:ac77 8a c4           MOV        AL,AH
    9000:ac79 ba 02 01        MOV        DX,0x102
    9000:ac7c ee              OUT        DX,AL
    9000:ac7d 83 c3 20        ADD        BX,0x20
    9000:ac80 e2 e8           LOOP       LAB_9000_ac6a
    9000:ac82 59              POP        CX
    9000:ac83 5b              POP        BX
    9000:ac84 c3              RET
                         **************************************************************
                         *                          FUNCTION                          *
                         **************************************************************
                         void __cdecl16near copyAllMasked(void)
         void              <VOID>         <RETURN>
    9000:ac85 2b db           SUB        BX,BX
    9000:ac87 b9 3c 00        MOV        CX,0x3c
                         LAB_9000_ac8a
    9000:ac8a e8 08 00        CALL       copyMasked
    9000:ac8d fe c3           INC        BL
    9000:ac8f 80 fb 20        CMP        BL,0x20
    9000:ac92 75 f6           JNZ        LAB_9000_ac8a
    9000:ac94 c3              RET
                         **************************************************************
                         *                          FUNCTION                          *
                         **************************************************************
                         void __cdecl16near copyMasked(uint8_t x, uint16_t height)
         void              <VOID>         <RETURN>
         uint8_t           BL:1           x
         uint16_t          CX:2           height
    9000:ac95 80 fb 14        CMP        x,0x14
    9000:ac98 73 48           JNC        LAB_9000_ace2
    9000:ac9a 53              PUSH       x
    9000:ac9b 51              PUSH       height
    9000:ac9c b0 22           MOV        AL,0x22
    9000:ac9e e8 fa fe        CALL       outLcd1Control
    9000:aca1 b0 00           MOV        AL,0x0
    9000:aca3 e8 ff fe        CALL       waitForLcd1Ready
    9000:aca6 e8 f7 fe        CALL       outLcd1Data
    9000:aca9 b0 21           MOV        AL,0x21
    9000:acab e8 ed fe        CALL       outLcd1Control
    9000:acae 8a c3           MOV        AL,x
    9000:acb0 e8 f2 fe        CALL       waitForLcd1Ready
    9000:acb3 e8 ea fe        CALL       outLcd1Data
    9000:acb6 b0 20           MOV        AL,0x20
    9000:acb8 e8 e0 fe        CALL       outLcd1Control
    9000:acbb 2a ff           SUB        BH,BH
                         LAB_9000_acbd
    9000:acbd 8a a7 a2 57     MOV        AH,byte ptr [x + 0x57a2]
    9000:acc1 0a a7 22 5f     OR         AH,byte ptr [x + 0x5f22]
    9000:acc5 32 a7 a2 66     XOR        AH,byte ptr [x + 0x66a2]
    9000:acc9 ba 60 00        MOV        DX,0x60
    9000:accc ec              IN         AL,DX
    9000:accd d0 e0           SHL        AL,0x1
    9000:accf 74 03           JZ         LAB_9000_acd4
    9000:acd1 e8 d1 fe        CALL       waitForLcd1Ready
                         LAB_9000_acd4
    9000:acd4 8a c4           MOV        AL,AH
    9000:acd6 ba 62 00        MOV        DX,0x62
    9000:acd9 ee              OUT        DX,AL
    9000:acda 83 c3 20        ADD        x,0x20
    9000:acdd e2 de           LOOP       LAB_9000_acbd
    9000:acdf 59              POP        height
    9000:ace0 5b              POP        x
    9000:ace1 c3              RET
                         LAB_9000_ace2
    9000:ace2 53              PUSH       x
    9000:ace3 51              PUSH       height
    9000:ace4 b0 22           MOV        AL,0x22
    9000:ace6 e8 ce fe        CALL       outLcd2Control
    9000:ace9 b0 00           MOV        AL,0x0
    9000:aceb e8 d3 fe        CALL       waitForLcd2Ready
    9000:acee e8 cb fe        CALL       outLcd2Data
    9000:acf1 b0 21           MOV        AL,0x21
    9000:acf3 e8 c1 fe        CALL       outLcd2Control
    9000:acf6 8a c3           MOV        AL,x
    9000:acf8 2c 14           SUB        AL,0x14
    9000:acfa e8 c4 fe        CALL       waitForLcd2Ready
    9000:acfd e8 bc fe        CALL       outLcd2Data
    9000:ad00 b0 20           MOV        AL,0x20
    9000:ad02 e8 b2 fe        CALL       outLcd2Control
    9000:ad05 2a ff           SUB        BH,BH
                         LAB_9000_ad07
    9000:ad07 8a a7 a2 57     MOV        AH,byte ptr [x + 0x57a2]
    9000:ad0b 0a a7 22 5f     OR         AH,byte ptr [x + 0x5f22]
    9000:ad0f 32 a7 a2 66     XOR        AH,byte ptr [x + 0x66a2]
    9000:ad13 ba 00 01        MOV        DX,0x100
    9000:ad16 ec              IN         AL,DX
    9000:ad17 d0 e0           SHL        AL,0x1
    9000:ad19 74 03           JZ         LAB_9000_ad1e
    9000:ad1b e8 a3 fe        CALL       waitForLcd2Ready
                         LAB_9000_ad1e
    9000:ad1e 8a c4           MOV        AL,AH
    9000:ad20 ba 02 01        MOV        DX,0x102
    9000:ad23 ee              OUT        DX,AL
    9000:ad24 83 c3 20        ADD        x,0x20
    9000:ad27 e2 de           LOOP       LAB_9000_ad07
    9000:ad29 59              POP        height
    9000:ad2a 5b              POP        x
    9000:ad2b c3              RET
    

    Reduced to a simplified C function that shows just the transfer of a buffer to the LCD:

    void copy(void) {
      for (uint8_t x = 0; x != 32; x++) {
        copyUnmasked(*(uint8_t **)0x75a2, x, 60);
      }
    }
    
    void copyUnmasked(uint8_t *buffer, uint8_t x, uint16_t height) {
      if (x < 20) {
        outLcd1Control(0x22);
        waitForLcd1Ready();
        outLcd1Data(0);
    
        outLcd1Control(0x21);
        waitForLcd1Ready();
        outLcd1Data(x);
    
        outLcd1Control(0x20);
        do {
          if ((in(0x60) & 0x7f) != 0) {
            waitForLcd1Ready();
          }
          out(0x62, buffer[x]);
    
          x += 32;
          height--;
        } while (height != 0);
    
      } else {
        outLcd2Control(0x22);
        waitForLcd2Ready();
        outLcd2Data(0);
    
        outLcd2Control(0x21);
        waitForLcd2Ready();
        outLcd2Data(x - 20);
    
        outLcd2Control(0x20);
        do {
          if ((in(0x100) & 0x7f) != 0) {
            waitForLcd2Ready();
          }
          out(0x102, buffer[x]);
    
          x += 32;
          height--;
        } while (height != 0);
      }
    }
    

    The buffer is organized as 60 lines of 32 bytes.

    There are two stacked loops: