// *********************************************************************
//    Copyright (c) 2023-2024  Thomas Fänge
//    Copyright (c) 2023-2024  Christoph Giesselink
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// *********************************************************************

// *********************************************************************
// HP41Infrared.cpp : implementation file
// *********************************************************************

#include"StdAfx.h"
#include"V41.h"
#include"Udp.h"

#define NPIC_IR_PRT_WRITE   0x00
#define NPIC_IR_PRT_READ    0x01
#define NPIC_IR_PRT_CMD     0x02

#define BUSY_CNT            78      // busy counter sending frame (78 = Delay of about 12.4 ms)
#define TIMER_FAST          75      // fast timer (~85Hz)
#define TIMER_SLOW          825     // slow timer (7.6Hz) @ 6300 instructions/s

// bits register (8)
#define BLINKY_FREQ         (1<<6)  // 0 = 85Hz, 1 = 7.6Hz

// bits status register (14)
#define BLINKY_CLK_ENABLE   (1<<4)
#define BLINKY_RAM_ENABLE   (1<<6)
#define BLINKY_ENABLE       (1<<7)

// bits FI register
#define FI_TFAIL            (1<<11) // FI_PRT_BUSY
#define FI_ALM              (1<<12) // FI_PRT_TIMER

/*
There are 16 internal registers that can be written and read using NPIC commands.

The pattern for the NPIC commands (10 bits) used in Blinky are as follows:

rrrr 111 cc x

- rrrr is the register in use (0-15)
  - reg 15 is not used
  - reg 14 is just read only
- cc is the command:
  - 00 is WRITE REG
  - 01 is READ REG
  - 10 is special function
    - I have seen FUNC 1-5, 7 and 8 being used in the code
  - 11 is not used at all
- x is RTNCPU (0 - continue in NPIC mode, 1 - return control to NUT)

Register 0-7 are full 56 bits registers.
Register 8-14 are only 8 bits registers.

There is also 16 register memory mapped at address 0x20 (0x20-0x2F).
Of these, only 14 memory positions are used (0-13) in the ROM.
And this RAM area are identical to the registers, i.e. 0x20-0x27 maps to the
56 bits registers 0-7, and 0x28-0x2D maps to the 8 bits registers 8-13.
Still I have not found any code that uses this memory except the TESTP function.

Writing to the NPIC register:
 6EC7 264 SELPF 9
 6EC8 039 #039 WRITE (0) - Write status (Reg 0)

Writing to 0x20 using the DADD=C (0x2F0) instruction is treated special, and maps to status.
The MSB of C (nibble 0+1) are written to the status, as follows (0x80 is written)

 6583 130208 LDI 208   # C = ....208
 6585 33C    RCR 1     # C = 8...........20
 6586 270    DADD=C    # RAM select 0x20
 6587 2F0    DATA=C    # Write 0x80 to status (flag 4 is cleared)
 6588 228    REGN=C 8  # Writing to RAM (0x28) is disabled (flag 4)

As indicated above, flag 4 of status (reg 14) enables/disables writing to RAM memory 0x2X.

And the following command (FUNC (4)):
 6589 264    SELPF 9
 658A 13D    #13D      # FUNC (4) - sets flag 4 (i.e. bit 4 (...1....) in reg 14)
                       # Note that FUNC #4 doesn't mean flag #4 - it is just a coincident

seems to enable writing again (ie setting bit 4 in reg 14).

The registers I have found to contain the following information.

Register 0, 1, 2 and 3 contains the output buffer
- Starting at register 3 MSB and fills downwards.
- Current pointer is stored in reg 6

Register 5 delay register
- Code (i.e. at 61C8) clears digit 0 (LSB)
- Or write nibble (i.e. at 621E) to digit 13 (MSB)
- As I have come to understand, this register keeps track of the number of lines(?) printed,
  and is used to keep track of the correct delay towards the printer.

Register 6 contains output buffer pointer and delay
- vvuuaannnDXYyy
  - X is 3, 2, 1 or 0 depending on which reg is used
  - Y is position in register (6->0):
    - 66554433221100
  - D is the delay = ~D/8 seconds
  - nnn is used as a loop counter (500) @ 6775 (second bank)
- Last byte (yy) is used at e.g. 6146 and 68BB (second bank)
- Nibble 13-12 (vv) is used at 6909 (second bank)
- Nibble 11-10 (uu) is used at 6160 (second bank)
- Nibble 11-8 (uuaa) is used as return address (eg at 62B9 (second bank))

Register 7 contains 16 flags (Reg7[3-0]).
- ..ssss...BFFFF
  - 16 bits are used as flags
    - 15 (7) Indicates mapping: 1:MAPON / 0:MAPOFF
    - 14 (6) used at e.g. 6DD2, 6771
    - 13 (5) used at e.g. 6B09?
    - 12 (4) used at e.g. 68E3
    - 11 (3) Used by FMT
    - 10 (2) Used by FMT
    -  9 (1) Used at e.g. 6DD7
    -  8 (0) Used at e.g. 6D80
    -  7 Used at e.g. 6DD1
    -  6 Used at e.g. 6DE0, 60ED
    -  3 Used at e.g. 6EE8 - printer enabled?
    -  2 Used at e.g. 60EF
    -  1 Used at e.g. 6ADC
  - One nibble (4) is used (e.g. 62B7 where it is exchanged with B[S])
    - Also used at bank 0 @ 6DCF
  - Nibble 8-11 are used as an extra return stack (e.g. @ 62B9)

Register 8 contains at least 8 flags (Reg8[1-0]).
- Bit 7 checked at start of any function (@6F1D), should be set
    - Same as bit 7 in Reg 14?
- At least some of bit 4-6 should also be set (@6F1F)
- It is only read once in "real" code:
  - Bank 0 at 6CC7 it is read just before issuing an error message
- During startup (Bank 0 @ 6C87) 0x80 (1000 0000)is written
- At Bank 0 @ 6F2B 0xD0 (1101 0000) is written
- Also at Bank 0 @ 6F4E 0xD0 (1101 0000) is written (Reset and init)
    - Here the complete register is initialized to 0x4E0000000000D0

Register 9 is unclear for me
- Sometimes is reflects the HW status (reg 14) and sometimes reg 8

Register 14 is read during shutdown (bank 0 @ 6C95)
- Bit 6 is checked (timer active)
    - If active - update timervalue in reg 10
- Bit 4 enables the RAM (0x20-0x2D)
    - If active - write is allowed

All initializations of the memories are done by the Blinky code during startup.

To get the emulation running (I have so far only made the TESTP function to work),
I have done the following assumptions:

FI flag 11 (FI_TFAIL) indicates that the hw (IR-led) is busy, i.e. it it set if we
write to output while the output is not yet ready.
It seems as if the HW has a one byte buffer, and the flag is set when a second
character is sent (before the first one is done).
After each write to output reg, the code waits until the flag is cleared (with
a timeout of about 16*6 cycles).

FI flag 12 (FI_ALM)

Writing to register 10 sets the timer value.
  - Writing to this register immediately clears FI_TFAIL and FI_ALM
  - If the clock is enabled, the value is decremented immediately at write,
    then accoring to the clock speed (hi or low).
  - When reaching 0 the FI_ALM (timer rerady?) is set.
  - The value is not wrapped, ie after FI_ALM, the value is still 0.

Register 11 is the output register, send one byte over IR.
  - It takes about 10 cycles to complete a write
  - If not done when a second byte appears, the flag FI_TFAIL (printer busy?) is set
  - Flag is cleared when done with sending

Writing to any other register is just saved in the register array.
Reading of any register just returns the current content of the register.

Executing FUNC (2) results in the following:
  - Enables the timer clock in reg 10
  - Bit 6 in register 14 is set

Executing FUNC (3) results in the following:
  - Disables the timer clock in reg 10
  - Bit 6 in register 14 is clear

Executing FUNC (4) results in the following:
  - Enables the writing to RAM (0x2X)
  - Bit 4 in register 14 is set

Executing FUNC (5) results in the following:
  - Disables the writing to RAM (0x2X)
  - Bit 4 in register 14 is clear
  - FI_TFAIL and FI_ALM is deactivated

Executing FUNC (7) results in the following (Reset?):
  - Register 14 is cleared (clock and RAM disabled)
  - FI_TFAIL and FI_ALM is cleared

Executing FUNC (8) results in the following (Reset?):
  - FI_TFAIL and FI_ALM is deactivated

Function 1 is still unknown (it is executed as the last command in TESTP if the command is ok).
*/

/****************************/
// executes intelligent infrared printer instructions
/****************************/
void HP41::exec_perph_infrared()
{
  CARRY=0;
  // rrrr 111 cc x
  // *rrrr* is the register in use (0-15)
  // - reg 15 is not used
  // - reg 14 is just read only
  // *cc* is the command:
  // - 00 is WRITE REG
  // - 01 is READ REG
  // - 10 is special function
  // - I have seen FUNC 1-5, 7 and 8 being used in the code
  // - 11 is not used at all
  // *x* is RTNCPU (0 - continue in NPIC mode, 1 - return control to NUT)
  const word r   = (Tyte1 >> 6) & 0xF;      // register
  const word cmd = (Tyte1 >> 1) & 0x3;      // cmd

  switch (cmd)
  {
  case NPIC_IR_PRT_WRITE:  // Write ...
    if (r < 8)
    {
      // copy C[W] content to infrared register
      byte *pReg=&pRAM[0x20+r].Reg[0];
      for (uint i = 0; i < 14; i += 2)
      {
        *pReg++ = (C_REG[i+1] << 4) | C_REG[i];
      }
    }
    else
    {
      // fill uppper bytes with 0
      memset(&pRAM[0x20+r].Reg[1],0,sizeof(pRAM[0x20+r].Reg)-sizeof(pRAM[0x20+r].Reg[0]));
      if (r != 14)   // register 14 is read only
      {
        // set only last byte
        pRAM[0x20+r].Reg[0] = (C_REG[1] << 4) | C_REG[0];
      }
    }
    switch (r)
    {
    case  8:
      if ((pRAM[0x28].Reg[0] & 0x80))
        pRAM[0x2E].Reg[0] |= BLINKY_ENABLE;
      pRAM[0x29].Reg[0] = pRAM[0x28].Reg[0]; // writing to reg 8 affects reg 9 as well
      break;
    case 10:  // 2B9 - 1010 111 00 1 r = 10
      if (pRAM[0x2A].Reg[0] != 0 && (pRAM[0x2E].Reg[0] & BLINKY_CLK_ENABLE))
        --pRAM[0x2A].Reg[0];                // decr. timer
      // Writing results in clearing of FI[12]
      m_wIrFI_REG &= ~FI_ALM;
      m_nIrAlm = InfraredTimerCnt();        // start timer
      break;
    case 11:  // 2F9 - 1011 111 00 1 r = 11
      if ((pRAM[0x2E].Reg[0] & BLINKY_CLK_ENABLE))  // timer enabled
      {
        // pRAM[0x2E].Reg[0] is the transmit buffer
        m_wIrFI_REG |= FI_TFAIL;            // transmit buffer full
      }
      break;
    }
    fRamWritten=TRUE;
    break;
  case NPIC_IR_PRT_READ:  // Read ...
    {
      // copy infrared register content to C[W]
      const RAM_REG &regAddr = pRAM[0x20+r];
      for (uint i = 0; i < 7; ++i)
      {
        const byte &byReg = regAddr.Reg[i];
        C_REG[i<<1]     = byReg & 0x0f;
        C_REG[(i<<1)+1] = (byReg >> 4) & 0x0f;
      }
    }
    break;
  case NPIC_IR_PRT_CMD:  // Func ...
    switch (r)
    {
    case 1:
      pRAM[0x2E].Reg[0] = BLINKY_CLK_ENABLE | (1<<5);
      break;
    case 2: // Enable timer clock
      pRAM[0x2E].Reg[0] |= BLINKY_CLK_ENABLE;
      pRAM[0x29].Reg[0] = pRAM[0x2E].Reg[0]; // affects reg 9 also
      break;
    case 3: // Disable timer clock
      pRAM[0x2E].Reg[0] &= ~BLINKY_CLK_ENABLE;
      break;
    case 4: // Enable RAM access
      pRAM[0x2E].Reg[0] |= BLINKY_RAM_ENABLE;
      break;
    case 5: // Disable RAM access and deselct the printer
      pRAM[0x2E].Reg[0] &= ~BLINKY_RAM_ENABLE;
      InfraredSelectFI(false);
      break;
    case 7: // Reset
      pRAM[0x2E].Reg[0] = 0;
      m_wIrFI_REG &= ~(FI_TFAIL | FI_ALM);
      InfraredUpdateFI();
      break;
    case 8: // de-select the printer
      InfraredSelectFI(false);
      break;
    }
    fRamWritten=TRUE;
  }
}

/****************************/
// get Blinky timer frequency
/****************************/
inline int HP41::InfraredTimerCnt() const
{
  // timer frequency depends on Bit 6 in register 8
  return (pRAM[0x28].Reg[0] & BLINKY_FREQ) ? TIMER_SLOW : TIMER_FAST;
}

/****************************/
// check if Blinky timer running
/****************************/
bool HP41::InfraredTimerRunning() const
{
  // use m_nIrAlm instead of counting value pRAM[0x2A].Reg[0] for timer running
  return fInfrared && m_nIrAlm && (pRAM[0x2E].Reg[0] & BLINKY_CLK_ENABLE) != 0;
}

/****************************/
// check if Blinky RAM enabled
/****************************/
bool HP41::InfraredRamEnabled() const
{
  return fInfrared && (pRAM[0x2E].Reg[0] & BLINKY_RAM_ENABLE) != 0;
}

/****************************/
// select FI register
/****************************/
void HP41::InfraredSelectFI(bool bSelect)
{
  if (fInfrared)                            // module equipped
  {
    if (bSelect == false)
    {
      EnterCriticalSection(&csFI);
      {
        FI_REG &= ~(FI_TFAIL | FI_ALM);
      }
      LeaveCriticalSection(&csFI);
    }
    m_bIrSelected = bSelect;
  }
}

/****************************/
// update FI register
/****************************/
void HP41::InfraredUpdateFI()
{
  if (fInfrared && m_bIrSelected)
  {
    EnterCriticalSection(&csFI);
    {
      FI_REG &= ~(FI_TFAIL | FI_ALM);
      FI_REG |= m_wIrFI_REG;
    }
    LeaveCriticalSection(&csFI);
  }
}

/****************************/
// update Blinky timer and simulate frame busy counter
/****************************/
void HP41::InfraredUpdateTimer()
{
  if (fInfrared)                            // module equipped
  {
    // check blinky timer counter
    if (m_nIrAlm && (pRAM[0x2E].Reg[0] & BLINKY_CLK_ENABLE))
    {
      --m_nIrAlm;
      if (m_nIrAlm == 0)
      {
        if (pRAM[0x2A].Reg[0] == 0)
        {
          if (InfraredRamEnabled())         // check if module RAM avilable
          {
            // Set FI flag when counter reaches zero ...
            m_wIrFI_REG |= FI_ALM;
            Wakeup();
          }
        }
        else
        {
          // Decrement and continue counting ...
          --pRAM[0x2A].Reg[0];              // decr. timer
          fRamWritten=TRUE;
          m_nIrAlm = InfraredTimerCnt();    // restart timer
        }
      }
    }

    if (m_nIrbusyCnt > 0)                   // transmitter busy 
    {
      --m_nIrbusyCnt;

      if (m_nIrbusyCnt == 0)                // transmitter run empty
      {
        // send transmitter data to printer simulation
        TRACE("Print82242A(%02X): %c\n",m_byIrTransmitter,(m_byIrTransmitter>=' ')?m_byIrTransmitter:'.');
        Udp::getInstance()->SendByte(m_byIrTransmitter);
      }
    }

    // transmit buffer full and transmitter empty
    if ((m_wIrFI_REG & FI_TFAIL) != 0 && m_nIrbusyCnt == 0)
    {
      m_byIrTransmitter=pRAM[0x2B].Reg[0];  // copy data from transmit buffer to transmitter
      m_wIrFI_REG &= ~FI_TFAIL;             // clear busy flag for transmit buffer empty
      m_nIrbusyCnt = BUSY_CNT;              // transmitter busy
    }
  }
}
