//////////////////////////////////////////////////////////////////////////////
//
//  Microsoft Research Singularity
//
//  Copyright (c) Microsoft Corporation.   All rights reserved.
//
//  File:   halkduart.cpp: UART serial transport.
//
//  Note:   Kernel Only
//

#include "hal.h"
#include "halkd.h"

//
// Debugger Debugging
//
#define KDDBG if (0) kdprintf
#define KDDBG2 if (0) kdprintf

///////////////////////////////////////////////////////////////// Serial Port.
//
#define OMAP_CLOCK_RATE 2995200

// Define COM Port registers.
#define COM_DAT 0x00
#define COM_DLL 0x00            // Divisor Latch (LSB).
#define COM_IEN 0x01            // Interrupt enable register
#define COM_DLM 0x01            // Divisor Latch (MSB).
#define COM_FCR 0x02            // FIFO Control Register.
#define COM_LCR 0x03            // Line Control Register.
#define COM_MCR 0x04            // Modem Control Register.
#define COM_LSR 0x05            // Line Status Register.
#define COM_MSR 0x06            // Modem Status Register.
#define COM_SCR 0x07            // Scratch Register.

#define OMAP_UART_MDR1    0x8
#define OMAP_UART_IER     0x1
#define OMAP_UART_EFR     0x2

// Define bits in the FIFO Control Register (FCR).
#define FCR_ENABLE                0x01
#define FCR_CLEAR_RECEIVE         0x02
#define FCR_CLEAR_TRANSMIT        0x04

// Define bits in the Line Control Register (LCR).
#define LCR_DATA_SIZE             0x03
#define LCR_DLAB                  0x80

// Define bits in the Modem Control Register (MCR).
#define MCR_DATA_TERMINAL_READY   0x01
#define MCR_REQUEST_TO_SEND       0x02
#define MCR_OUT1                  0x04
#define MCR_OUT2                  0x08
#define MCR_LOOPBACK              0x10
#define MCR_INITIALIZE            (MCR_DATA_TERMINAL_READY | MCR_REQUEST_TO_SEND)

// Define bits in the Line Status Register (LSR).
#define LSR_DATA_AVAILABLE        0x01
#define LSR_OVERRUN_ERROR         0x02
#define LSR_PARITY_ERROR          0x04
#define LSR_FRAMING_ERROR         0x08
#define LSR_BREAK_SIGNAL          0x10
#define LSR_THR_EMPTY             0x20
#define LSR_THR_LINE_IDLE         0x40


// Defined bits in the Modem Status Register (MSR).
#define MSR_DELTA_CLEAR_TO_SEND   0x01
#define MSR_DELTA_DATA_SET_READY  0x02
#define MSR_DELTA_RING_INDICATOR  0x04
#define MSR_DELTA_CARRIER_DETECT  0x08
#define MSR_CLEAR_TO_SEND         0x10
#define MSR_DATA_SET_READY        0x20
#define MSR_RING_INDICATOR        0x40
#define MSR_CARRIER_DETECT        0x80

//
// Globals
//
static const uint32 BaudRate = 115200;
static uint32 * uartBase = 0;

////////////////////////////////////////////////// Serial Port Input & Output.
//
static inline void WriteReg8(volatile void * addr, uint8 value)
{
    ((volatile uint8 *)addr)[0] = value;
}

static inline uint8 ReadReg8(volatile void * addr)
{
    return ((volatile uint8 *)addr)[0];
}

static void UartSetBaudRate(uint32 * BaseAddress, uint32 BaudRate)
{
    uint32 Divisor = OMAP_CLOCK_RATE / BaudRate;
    uint8 Enhanced;

    // Disable UART
    WriteReg8(BaseAddress + OMAP_UART_MDR1, 0x7);

    // Set register configuration mode B
    WriteReg8(BaseAddress + COM_LCR, 0xBF);

    // Save enhanced mode
    Enhanced = ReadReg8(BaseAddress + OMAP_UART_EFR);
    WriteReg8(BaseAddress + OMAP_UART_EFR, Enhanced | (1 << 4));

    // switch to operational mode
    WriteReg8(BaseAddress + COM_LCR, 0);

    // clear sleep mode
    WriteReg8(BaseAddress + OMAP_UART_IER, 0);

    // Set register configuration mode B
    WriteReg8(BaseAddress + COM_LCR, 0xBF);

    // Write the divisor value to DLL and DLM.
    WriteReg8(BaseAddress + COM_DLM, (uint8)((Divisor >> 8) & 0xff));
    WriteReg8(BaseAddress + COM_DLL, (uint8)(Divisor & 0xff));

    // Restore enhanced mode
    WriteReg8(BaseAddress + OMAP_UART_EFR, Enhanced);

    // Reset the Line Control Register.
    WriteReg8(BaseAddress + COM_LCR, LCR_DATA_SIZE);

    // Enable UART
    WriteReg8(BaseAddress + OMAP_UART_MDR1, 0);
}

bool KdpSerialInit(Class_Microsoft_Singularity_Hal_Platform *nbi)
// Initializes the communication port (baud rate, parity etc.)
{
    if (nbi->DebugBasePort < 0x100) {
        return false;

    }
    uartBase = (uint32 *)nbi->DebugBasePort;

    // Set the default baudrate.
    UartSetBaudRate(uartBase, BaudRate);

    // Set DLAB to zero.  DLAB controls the meaning of the first two
    // registers.  When zero, the first register is used for all byte transfer
    // and the second register controls device interrupts.
    //
    WriteReg8(uartBase + COM_LCR,
              ReadReg8(uartBase + COM_LCR) & ~LCR_DLAB);

    // Disable device interrupts.  This implementation will handle state
    // transitions by request only.
    //
    WriteReg8(uartBase + COM_IEN, 0);

    // Reset and disable the FIFO queue.
    // N.B. FIFO will be reenabled before returning from this routine.
    //
    WriteReg8(uartBase + COM_FCR, FCR_CLEAR_TRANSMIT | FCR_CLEAR_RECEIVE);

    // Configure the Modem Control Register.  Disabled device interrupts,
    // turn off loopback.
    //
    WriteReg8(uartBase + COM_MCR,
              ReadReg8(uartBase + COM_MCR) & MCR_INITIALIZE);

    // Initialize the Modem Control Register.  Indicate to the device that
    // we are able to send and receive data.
    //
    WriteReg8(uartBase + COM_MCR, MCR_INITIALIZE);

    // Enable the FIFO queues.
    WriteReg8(uartBase + COM_FCR, FCR_ENABLE);

    return true;
}

//
// Define wait timeout value.
//
#define TIMEOUT_COUNT 1024 * 30
// #define TIMEOUT_COUNT 1024 * 200
//#define TIMEOUT_COUNT 15

KDP_STATUS KdpSerialGetByte(OUT PUCHAR Input, BOOL WaitForByte)
{
    UINT8 lsr;
    UINT8 value;
    UINT32 limitcount = (WaitForByte) ? TIMEOUT_COUNT : 1;

    UINT8 msr;
    msr = ReadReg8(uartBase + COM_MSR);
    KDDBG2("MSR %02x\n", msr);

    while (limitcount != 0) {
        limitcount--;

        lsr = ReadReg8(uartBase + COM_LSR);
        KDDBG2("LSR %02x\n", lsr);
        if (lsr & LSR_DATA_AVAILABLE) {
            value = ReadReg8(uartBase + COM_DAT);
            *Input = (UINT8)(value & 0xff);
            return KDP_PACKET_RECEIVED;
        }
    }
    return KDP_PACKET_TIMEOUT;
}


void KdpSerialPutByte(IN UCHAR Output)
{
    // Loop until the device is ready for output
    while ((ReadReg8(uartBase + COM_LSR) & LSR_THR_EMPTY) == 0) {
    }

    // The transmitter regiser is clear and can be written to.
    WriteReg8(uartBase + COM_DAT, Output);
}
//
///////////////////////////////////////////////////////////////// End of File.
