/*++
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
Copyright (c) 1995-2000 Microsoft Corporation.  All rights reserved.
Copyright (c) 2001-2002 SAMSUNG Electronics Corporation.  All rights reserved.

Author	hmseo@sec.samsung.com (SAMSUNG Electronics)

Module Name:  

SER2440_SER.C

Abstract:  

Notes: 
--*/

#define EXAMINE_BOOTARGS
#define DEBUGMODE 0

#include <windows.h>
#include <types.h>
#include <ceddk.h>
#include <memory.h>
#include <serhw.h>
#include <ser16550.h>
#include <hw16550.h>
#include <nkintr.h>
#include <devload.h>
#include <windev.h>
#include <notify.h>
#include <pm.h>

#include "S2440.h"

#define USERDBG        1

#ifdef EXAMINE_BOOTARGS
#include <bootarg.h>
#include <oalintr.h>
#include <pc.h>
#endif

#include <ser2440.h>

#undef ZONE_INIT
#include <serdbg.h>

static BOOL IRDA;

#define BAUD_TABLE_SIZE 23
static const
PAIRS   BaudPairs[BAUD_TABLE_SIZE] =    {
	{50,        2307},
	{75,        1538},
	{110,       1049},
	{135,       858},
	{150,       769},
	{300,       384},
	{600,       192},
	{1200,      96},
	{1800,      64},
	{2000,      58},
	{2400,      48},
	{3600,      32},
	{4800,      24},
	{7200,      16},
	{9600,      12},
	{12800,     9},
	{14400,     8},
	{19200,     6},
	{23040,     5},
	{28800,     4},
	{38400,     3},
	{57600,     2},
	{115200,    1}
};

static const LOOKUP_TBL  BaudTable = {BAUD_TABLE_SIZE, (PAIRS *) BaudPairs};

// Miscellaneous internal routines.
PUCHAR
static
Ser_InternalMapRegisterAddresses(ULONG HWAddress, ULONG Size)
{
	PUCHAR              ioPortBase; 
	ULONG               inIoSpace      = 1;
	PHYSICAL_ADDRESS    ioPhysicalBase = { HWAddress, 0};

	RETAILMSG(DEBUGMODE, (TEXT("+ Ser_InternalMapRegisterAddresses : HalTranslateBusAddress\r\n")));
	if ( HalTranslateBusAddress(Isa, 0, ioPhysicalBase, &inIoSpace, &ioPhysicalBase))
	{
		DEBUGMSG(1, (TEXT("Ser_InternalMapRegisterAddresses : HalTranslateBusAddress - OK\r\n")));
		if ( !inIoSpace )
		{
			DEBUGMSG(1, (TEXT("Ser_InternalMapRegisterAddresses : ! IO Space\r\n")));
			if ( (ioPortBase = (PUCHAR)MmMapIoSpace(ioPhysicalBase, Size, FALSE)) == NULL )
			{
				// We may as well not continue
				DEBUGMSG(ZONE_INIT | ZONE_ERROR, (TEXT("Error mapping I/O Ports\r\n")));
				return (NULL);
			}
		}
		else
		{
			DEBUGMSG(1, (TEXT("Ser_InternalMapRegisterAddresses : IO Space\r\n")));
			ioPortBase = (PUCHAR)ioPhysicalBase.LowPart;
		}
	}
	else
	{
		DEBUGMSG(ZONE_INIT | ZONE_ERROR, (TEXT("Error translating I/O Ports.\r\n")));
		return (NULL);
	}

	RETAILMSG(DEBUGMODE, (TEXT("- Ser_InternalMapRegisterAddresses : %d\r\n"),ioPortBase ));
	return (ioPortBase);
}

static
BOOL
SerSetIRBaudRate(
			PSER_INFO   pHWHead,
			ULONG baud     // @parm     UINT16 what is the baud rate
					)
{
	PS2440_UART_INFO   pHWHead2   = (PS2440_UART_INFO)pHWHead;

	RETAILMSG(DEBUGMODE, (TEXT("IRDA : +SerSetIRBaudRate\r\n")));    
	DEBUGMSG (ZONE_INIT|1, (TEXT("Serial set IR Baud %d\r\n"), baud));

	if ( (pHWHead2->s2440SerReg->rUCON & CS_MASK) == CS_PCLK )
	{
		RETAILMSG (1, (TEXT("USE CS_PCLK\r\n")));    
		OUTREG(pHWHead2,rUBRDIV,( (int)(S2440PCLK/16.0/baud) -1 ));
	}
	else		// if  ( (pHWHead2->s2440SerReg->rUCON & CS_MASK) == CS_UCLK )
	{
		RETAILMSG (1, (TEXT("USE CS_UCLK\r\n")));    
		OUTREG(pHWHead2,rUBRDIV,( (int)(S2440UCLK/16.0/baud) -1 ));
	}
		
	RETAILMSG(DEBUGMODE, (TEXT("IRDA : -SerSetIRBaudRate\r\n")));
	return (TRUE);
}

/*
 *  NOTE : The caller should have set pHWHead->fIRMode.  It is not
 * set here, since power on/off may need to temporarily disable
 * the intefaces without actually overwriting the current recorded
 * mode.
 */
static
void
SerSetOutputMode(
                PSER_INFO   pHWHead,
                BOOL UseIR,     // @parm     BOOL Should we use IR interface
                BOOL Use9Pin    // @parm     BOOL Should we use Wire interface
                )
{
	// If you support IR, here you need to set the interface to either IR mode
	// or normal serial. Note that it is possible for both BOOls to
	// be false (i.e. power down), but never for both to be TRUE.

	PS2440_UART_INFO   pHWHead2   = (PS2440_UART_INFO)pHWHead;

	RETAILMSG (DEBUGMODE, (TEXT("+SerSetOutputMode\r\n")));

	if(UseIR)  
		SETREG(pHWHead2,rULCON,SER2440_IRMODE_MASK);   // Infra-red mode enable.
	else
		CLEARREG(pHWHead2,rULCON,SER2440_IRMODE_MASK);   // Infra-red mode enable.
	
	RETAILMSG(DEBUGMODE, (TEXT("-SerSetOutputMode\r\n")));
}



/*++
*******************************************************************************
Routine:

    Ser_GetRegistryData

Description:

    Take the registry path provided to COM_Init and use it to find this 
    requested comm port's DeviceArrayIndex, teh IOPort Base Address, and the
   Interrupt number.
   
Arguments:

    LPCTSTR regKeyPath	the registry path passed in to COM_Init.

Return Value:

    -1 if there is an error.

*******************************************************************************
--*/
BOOL
Ser_GetRegistryData(PSER_INFO pHWHead, LPCTSTR regKeyPath)
{
#define GCI_BUFFER_SIZE 256   

	LONG    regError;
	HKEY    hKey;
	DWORD   dwDataSize = GCI_BUFFER_SIZE;

	RETAILMSG(DEBUGMODE, (TEXT("Try to open %s\r\n"), regKeyPath));

	// We've been handed the name of a key in the registry that was generated
	// on the fly by device.exe.  We're going to open that key and pull from it
	// a value that is the name of this serial port's real key.  That key
	// will have the DeviceArrayIndex that we're trying to find.  
	hKey = OpenDeviceKey(regKeyPath);
	if ( hKey == NULL )
	{
		DEBUGMSG(ZONE_INIT | ZONE_ERROR,(TEXT("Failed to open device key\r\n")));
		return ( FALSE );        
	}

	// Okay, we're finally ready to try and load our registry data.
	dwDataSize = PC_REG_DEVINDEX_VAL_LEN;
	regError = RegQueryValueEx(
					hKey, 
					PC_REG_DEVINDEX_VAL_NAME, 
					NULL, 
					NULL,
					(LPBYTE)(&pHWHead->dwDevIndex), 
					&dwDataSize);

	if ( regError == ERROR_SUCCESS )
	{
		dwDataSize = PC_REG_IRQ_VAL_LEN;
		regError = RegQueryValueEx(
					hKey, 
					PC_REG_IRQ_VAL_NAME, 
					NULL, 
					NULL,
					(LPBYTE)(&pHWHead->dwIRQ), 
					&dwDataSize);
	}

	if ( regError == ERROR_SUCCESS )
	{
		dwDataSize = PC_REG_IOBASE_VAL_LEN;
		regError = RegQueryValueEx(
					hKey, 
					PC_REG_IOBASE_VAL_NAME, 
					NULL, 
					NULL,
					(LPBYTE)(&pHWHead->dwIOBase), 
					&dwDataSize);
	}

	if ( regError == ERROR_SUCCESS )
	{
		dwDataSize = PC_REG_IOLEN_VAL_LEN;
		regError = RegQueryValueEx(
					hKey, 
					PC_REG_IOLEN_VAL_NAME, 
					NULL, 
					NULL,
					(LPBYTE)(&pHWHead->dwIOLen), 
					&dwDataSize);
	}

	RegCloseKey (hKey);

	if ( regError != ERROR_SUCCESS )
	{
		RETAILMSG(DEBUGMODE, (TEXT("Failed to get serial registry values, Error 0x%X\r\n"),regError));
		return ( FALSE );
	}

	RETAILMSG (DEBUGMODE,(TEXT("SerInit - Devindex %d, IRQ %d, IOB %X, IOLen %X \r\n"),
		pHWHead->dwDevIndex, pHWHead->dwIRQ, pHWHead->dwIOBase, pHWHead->dwIOLen));

	return ( TRUE ); 
}



/*
 @doc OEM 
 @func PVOID | SerInit | Initializes device identified by argument.
 *  This routine sets information controlled by the user
 *  such as Line control and baud rate. It can also initialize events and
 *  interrupts, thereby indirectly managing initializing hardware buffers.
 *  Exported only to driver, called only once per process.
 *
 @rdesc The return value is a PVOID to be passed back into the HW
 dependent layer when HW functions are called.
 */
static
PVOID
SerInit(
	BOOL bIR,
       ULONG   Identifier, // @parm Device identifier.
       PVOID   pMddHead,   // @parm First argument to mdd callbacks.
       PHWOBJ  pHWObj      // @parm Pointer to our own HW OBJ for this device
       )
{
	PSER_INFO			pHWHead;

#ifdef EXAMINE_BOOTARGS    
	PVOID				*ppBootArgs     = NULL;  // Pointer to pointer to bootargs.
	PHYSICAL_ADDRESS	PhysicalAddress = {0,0};
#endif    

	// Note on defaults.  While the PC typcially considers COM1 to be at 
	// 3F8, irq4 and COM2 to be 2F8, irq3, NKPC uses COM1 internally for the
	// debugger.  So, when NK tells me to start "COM1" it really means the
	// first one that's available, which is what the PC calls COM2.  Confused?
	// The end result is that COM1 on NK is what the PC considers to be COM2.
	// But wait, there's more.  On a Puzzle, the debugger is on COM2 and the
	// COM1 for NK is ACTUALLY COM1.  So PCs need 2F8 for their port base
	// and Puzzles need 3F8.

	RETAILMSG(DEBUGMODE, (TEXT("SerInit - !!! \r\n")));

	// Allocate for our main data structure and one of it's fields.
	pHWHead = (PSER_INFO)LocalAlloc( LMEM_ZEROINIT|LMEM_FIXED,	sizeof(SER_INFO) );
	if ( !pHWHead )
		return( NULL );

	if ( ! Ser_GetRegistryData(pHWHead, (LPCTSTR)Identifier) )
	{
		DEBUGMSG (ZONE_INIT|ZONE_ERROR,	(TEXT("SerInit - Unable to read registry data.  Failing Init !!! \r\n")));
		goto ALLOCFAILED;
	}

	pHWHead->pBaseAddress = Ser_InternalMapRegisterAddresses(pHWHead->dwIOBase, pHWHead->dwIOLen);

#ifdef EXAMINE_BOOTARGS        
	// Allocate a pointer to our bootargs since they may indicate that we don't have
	// access to the hardware resource.

	// First, map the bootargs pointer itself.  Note that I'm reading/writing
	// directly on the physical address.  I can do this since I know this is CEPC and 
	// know the adress is not in IO space.  For OEM platforms you would want to do
	// HalTranslateBusAddress first.
	PhysicalAddress.LowPart = BOOT_ARG_PTR_LOCATION & ~0x80000000;

	if ( ppBootArgs = MmMapIoSpace(PhysicalAddress, sizeof( PVOID ), TRUE ) )
	{
		DEBUGMSG (ZONE_INIT, (TEXT("SerInit - ppBootArgs (%X) at %X\r\n"), PhysicalAddress.LowPart, ppBootArgs ));
	}
	else
	{
		DEBUGMSG (ZONE_INIT | ZONE_ERROR, (TEXT("SerInit - ppBootArgs failure at %X\r\n"), PhysicalAddress.LowPart ));
		goto ALLOCFAILED;
	}

	// Now map the bootargs structure itself
	PhysicalAddress.LowPart = (DWORD) *ppBootArgs;
	if ( pHWHead->pBootArgs = MmMapIoSpace(PhysicalAddress, sizeof(BOOT_ARGS), TRUE ) )
	{
		DEBUGMSG (ZONE_INIT, (TEXT("SerInit - pBootArgs (%X) at %X\r\n"), PhysicalAddress.LowPart, pHWHead->pBootArgs ));
	}
	else
	{
		DEBUGMSG (ZONE_INIT | ZONE_ERROR, (TEXT("SerInit - pBootArgs failure at %X\r\n"), (DWORD)PhysicalAddress.LowPart));
		goto ALLOCFAILED;
	}

	// We no longer need ppBootArgs
	MmUnmapIoSpace( ppBootArgs, sizeof(PVOID) );
#endif // EXAMINE_BOOTARGS

	pHWHead->pMddHead     = pMddHead;
	pHWHead->pHWObj = pHWObj;
	pHWHead->cOpenCount   = 0;

	RETAILMSG(DEBUGMODE,(TEXT("SerInit - IRQ %d = SYSINTR %d\r\n"),
					pHWHead->dwIRQ, pHWHead->pHWObj->dwIntID));

	// Set up our Comm Properties data    
	pHWHead->CommProp.wPacketLength       = 0xffff;
	pHWHead->CommProp.wPacketVersion     = 0xffff;
	pHWHead->CommProp.dwServiceMask      = SP_SERIALCOMM;
	pHWHead->CommProp.dwReserved1         = 0;
	pHWHead->CommProp.dwMaxTxQueue        = 16;
	pHWHead->CommProp.dwMaxRxQueue        = 16;
	pHWHead->CommProp.dwMaxBaud       = BAUD_115200;
	pHWHead->CommProp.dwProvSubType      = PST_RS232;
	pHWHead->CommProp.dwProvCapabilities =
		PCF_DTRDSR | PCF_RLSD | PCF_RTSCTS |
		PCF_SETXCHAR |
		PCF_INTTIMEOUTS |
		PCF_PARITY_CHECK |
		PCF_SPECIALCHARS |
		PCF_TOTALTIMEOUTS |
		PCF_XONXOFF;
	pHWHead->CommProp.dwSettableBaud      =
		BAUD_075 | BAUD_110 | BAUD_150 | BAUD_300 | BAUD_600 |
		BAUD_1200 | BAUD_1800 | BAUD_2400 | BAUD_4800 |
		BAUD_7200 | BAUD_9600 | BAUD_14400 |
		BAUD_19200 | BAUD_38400 | BAUD_56K | BAUD_128K |
		BAUD_115200 | BAUD_57600 | BAUD_USER;
	pHWHead->CommProp.dwSettableParams    =
		SP_BAUD | SP_DATABITS | SP_HANDSHAKING | SP_PARITY |
		SP_PARITY_CHECK | SP_RLSD | SP_STOPBITS;
	pHWHead->CommProp.wSettableData       =
		DATABITS_5 | DATABITS_6 | DATABITS_7 | DATABITS_8;
	pHWHead->CommProp.wSettableStopParity =
		STOPBITS_10 | STOPBITS_20 |
		PARITY_NONE | PARITY_ODD | PARITY_EVEN | PARITY_SPACE |
		PARITY_MARK;

	pHWHead->fIRMode = bIR;
	pHWHead->s2440COM.UseIrDA = bIR;

#ifdef EXAMINE_BOOTARGS
	// Don't actually init the hardware if it is being used for debugging
	if ( ((pHWHead->pBootArgs->ucComPort == 1) && (pHWHead->dwIOBase == COM1_BASE)) ||
		((pHWHead->pBootArgs->ucComPort == 2) && (pHWHead->dwIOBase == COM2_BASE)) || 
		((pHWHead->pBootArgs->ucComPort == 3) && (pHWHead->dwIOBase == COM3_BASE)) ||
		((pHWHead->pBootArgs->ucComPort == 4) && (pHWHead->dwIOBase == COM4_BASE)) ) {
		DEBUGMSG (ZONE_INIT|ZONE_ERROR, (TEXT("\r\nSerInit - Skipping hardware init of debug port\r\n\r\n")));        
	} else
#endif
	{
		DEBUGMSG (ZONE_INIT|USERDBG, (TEXT("SerInit - Init 16550 data\r\n")));
		SL_Init( pHWHead, pHWHead->pBaseAddress, 1,
				EvaluateEventFlag, pMddHead, (PLOOKUP_TBL)&BaudTable);
		DEBUGMSG (ZONE_INIT,
			(TEXT("SerInit - Disabling UART Power\r\n")));
		SerSetOutputMode(pHWHead, FALSE, FALSE );    
	}

	return (pHWHead);

	ALLOCFAILED:
	if ( pHWHead->pBaseAddress )
		VirtualFree(pHWHead->pBaseAddress, 0, MEM_RELEASE);
	if ( ppBootArgs )
		MmUnmapIoSpace( ppBootArgs, sizeof( PVOID ) );
	if ( pHWHead->pBootArgs )
		MmUnmapIoSpace( pHWHead->pBootArgs, sizeof(BOOT_ARGS) );

	LocalFree(pHWHead);
	return (NULL);
}


PVOID
SerInitSerial(
       ULONG   Identifier, // @parm Device identifier.
       PVOID   pMddHead,   // @parm First argument to mdd callbacks.
       PHWOBJ  pHWObj      // @parm Pointer to our own HW OBJ for this device
)
{
	return (SerInit(FALSE, Identifier, pMddHead, pHWObj));
}

PVOID		
SerInitSerial2(
       ULONG   Identifier, // @parm Device identifier.
       PVOID   pMddHead,   // @parm First argument to mdd callbacks.
       PHWOBJ  pHWObj      // @parm Pointer to our own HW OBJ for this device
)
{
	return (SerInit(FALSE, Identifier, pMddHead, pHWObj));
}

PVOID		
SerInitSerial3(
       ULONG   Identifier, // @parm Device identifier.
       PVOID   pMddHead,   // @parm First argument to mdd callbacks.
       PHWOBJ  pHWObj      // @parm Pointer to our own HW OBJ for this device
)
{
	return (SerInit(FALSE, Identifier, pMddHead, pHWObj));
}

PVOID
SerInitIR(
       ULONG   Identifier, // @parm Device identifier.
       PVOID   pMddHead,   // @parm First argument to mdd callbacks.
       PHWOBJ  pHWObj      // @parm Pointer to our own HW OBJ for this device
)
{
	return (SerInit(TRUE, Identifier, pMddHead, pHWObj));
}



/*
 @doc OEM
 @func ULONG | SerClose | This routine closes the device identified by the PVOID returned by SerInit.
 *  Not exported to users, only to driver.
 *
 @rdesc The return value is 0.
 */
static
ULONG
SerClose(
        PVOID   pHead   // @parm PVOID returned by SerInit.
        )
{
	PSER_INFO   pHWHead = (PSER_INFO)pHead;
	ULONG  uTries;

	RETAILMSG (DEBUGMODE,(TEXT("+SerClose\r\n")));
	if ( pHWHead->cOpenCount )
	{
		DEBUGMSG (ZONE_CLOSE, (TEXT("SerClose, closing device\r\n")));
		pHWHead->cOpenCount--;

		// while we are still transmitting, sleep.
		uTries = 0;
		while((pHWHead->s2440COM.s2440SerReg->rUFSTAT & SER2440_FIFOSTAT_MASK) & (uTries++ < 100))		// TxFifo not empty..
		{
			DEBUGMSG (ZONE_CLOSE, (TEXT("SerClose, TX in progress.\r\n")));            
			Sleep(10);
		}

		// When the device is closed, we power it down.
		DEBUGMSG (ZONE_CLOSE, (TEXT("SerClose - Powering down UART\r\n")));
		pHWHead->fIRMode  = FALSE;
		SerSetOutputMode(pHWHead, FALSE, FALSE );

		DEBUGMSG (ZONE_CLOSE, (TEXT("SerClose - Calling SL_Close\r\n")));
		SL_Close( pHWHead );
	}

	RETAILMSG(DEBUGMODE,(TEXT("-SerClose\r\n")));
	return (0);
}

/*
 @doc OEM 
 @func PVOID | SerDeinit | Deinitializes device identified by argument.
 *  This routine frees any memory allocated by SerInit.
 *
 */
static
BOOL
SerDeinit(PVOID   pHead)   // @parm PVOID returned by SerInit.
{
	PSER_INFO   pHWHead = (PSER_INFO)pHead;

	RETAILMSG(DEBUGMODE,(TEXT("SerDeinit \r\n")));
	if ( !pHWHead )
		return (FALSE);

	// Make sure device is closed before doing DeInit
	if ( pHWHead->cOpenCount )
		SerClose( pHead );

	SL_Deinit( pHead );

	if ( pHWHead->pBaseAddress )
		VirtualFree(pHWHead->pBaseAddress, 0, MEM_RELEASE);

	if ( pHWHead->pBootArgs )
		MmUnmapIoSpace( pHWHead->pBootArgs, sizeof(BOOT_ARGS) );

	// Free the HWObj
	LocalFree(pHWHead->pHWObj);

	// And now free the SER_INFO structure.
	LocalFree(pHWHead);

	return (TRUE);
}

/*
 @doc OEM
 @func	VOID | SerGetCommProperties | Retrieves Comm Properties.
 *
 @rdesc	None.
 */
static
VOID
SerGetCommProperties(
                    PVOID   pHead,      // @parm PVOID returned by SerInit. 
                    LPCOMMPROP  pCommProp   // @parm Pointer to receive COMMPROP structure. 
                    )
{
	PSER_INFO   pHWHead = (PSER_INFO)pHead;
	RETAILMSG(DEBUGMODE,(TEXT("+SerGetCommProperties\r\n")));
	*pCommProp = pHWHead->CommProp;
	return;
}


/*
 @doc OEM
 @func VOID | SerSetBaudRate |
 * This routine sets the baud rate of the device.
 *  Not exported to users, only to driver.
 *
 @rdesc None.
 */
static
BOOL
SerSetBaudRate(
              PVOID   pHead,  // @parm     PVOID returned by SerInit
              ULONG   BaudRate    // @parm     ULONG representing decimal baud rate.
              )
{
	PSER_INFO   pHWHead = (PSER_INFO)pHead;

	RETAILMSG(DEBUGMODE,(TEXT("IRDA : SerSetBaudRate \r\n")));
	// If we are running in IR mode, try to set the IR baud
	// first, since it supports a subset of the rates supported
	// by the UART.  If we fail setting the IR rate, then
	// return an error and leave the UART alone.
	if ( IRDA )
	{
		if ( ! SerSetIRBaudRate( pHWHead, BaudRate ) )
		{
			DEBUGMSG (ZONE_ERROR, 
				(TEXT("Unsupported IR BaudRate\r\n")));
			// We should return an error, but vtbl doesn't expect one
			return (FALSE); 
		}
	}

	// Now set buadrate on the UART
	return ( SL_SetBaudRate( pHead, BaudRate ) );    
}

/*
 @doc OEM
 @func BOOL | SerPowerOff |
 *  Called by driver to turn off power to serial port.
 *  Not exported to users, only to driver.
 *
 @rdesc This routine returns a status.
 */
BOOL
SerPowerOff(PVOID   pHead)       // @parm	PVOID returned by SerInit.
{
	PSER_INFO   pHWHead = (PSER_INFO)pHead;

	RETAILMSG(DEBUGMODE,(TEXT("SerPowerOff\r\n")));
	SerClose( pHWHead );
	// First, power down the UART
	SL_PowerOff( pHWHead );

	// And then disable our IR and 9 Pin interface
	SerSetOutputMode( pHWHead, FALSE, FALSE );

	return (TRUE);
}

/*
 @doc OEM
 @func BOOL | SerPowerOn |
 *  Called by driver to turn on power to serial port.
 *  Not exported to users, only to driver.
 *
 @rdesc This routine returns a status.
 */
BOOL
SerPowerOn(PVOID   pHead)       // @parm	PVOID returned by SerInit.
{
	PSER_INFO   pHWHead = (PSER_INFO)pHead;

	RETAILMSG(DEBUGMODE,(TEXT("SerPowerOn\r\n")));
	// First, power up the UART
	SL_PowerOn( pHWHead );

	// And then enable our IR interface (if needed)
	SerSetOutputMode( pHWHead, pHWHead->fIRMode, !pHWHead->fIRMode );
	return (TRUE);
}

/*
 @doc OEM
 @func BOOL | SerEnableIR | This routine enables ir.
 *  Not exported to users, only to driver.
 *
 @rdesc Returns TRUE if successful, FALSEotherwise.
 */
static
BOOL
SerEnableIR(
           PVOID   pHead, // @parm PVOID returned by Serinit.
           ULONG   BaudRate  // @parm PVOID returned by HWinit.
           )
{
	PSER_INFO   pHWHead = (PSER_INFO)pHead;

	RETAILMSG(DEBUGMODE,(TEXT("SerEnableIR\r\n")));
	pHWHead->fIRMode  = TRUE;
	SerSetOutputMode( pHWHead, pHWHead->fIRMode, !pHWHead->fIRMode );
	return (TRUE);
}

/*
 @doc OEM
 @func BOOL | SerDisableIR | This routine disable the ir.
 *  Not exported to users, only to driver.
 *
 @rdesc Returns TRUE if successful, FALSEotherwise.
 */
static
BOOL
SerDisableIR(PVOID   pHead)		// @parm PVOID returned by Serinit.
{
	PSER_INFO   pHWHead = (PSER_INFO)pHead;

	RETAILMSG(DEBUGMODE,(TEXT("SerDisableIR\r\n")));
	pHWHead->fIRMode  = FALSE;
	SerSetOutputMode( pHWHead, pHWHead->fIRMode, !pHWHead->fIRMode );
	return (TRUE);
}

/*
 @doc OEM
 @func BOOL | SerEnableIR | This routine enables ir.
 *  Not exported to users, only to driver.
 *
 @rdesc Returns TRUE if successful, FALSEotherwise.
 */
static
BOOL
SerEnableSerial(
           PVOID   pHead, // @parm PVOID returned by Serinit.
           ULONG   BaudRate  // @parm PVOID returned by HWinit.
           )
{
	PSER_INFO   pHWHead = (PSER_INFO)pHead;

	RETAILMSG(DEBUGMODE,(TEXT("SerEnableSerial\r\n")));
	pHWHead->fIRMode  = FALSE;
	SerSetOutputMode( pHWHead, pHWHead->fIRMode, !pHWHead->fIRMode );
	return (TRUE);
}

/*
 @doc OEM
 @func BOOL | SerDisableIR | This routine disable the ir.
 *  Not exported to users, only to driver.
 *
 @rdesc Returns TRUE if successful, FALSEotherwise.
 */
static
BOOL
SerDisableSerial(PVOID   pHead)		// @parm PVOID returned by Serinit.
{
	PSER_INFO   pHWHead = (PSER_INFO)pHead;

	RETAILMSG(DEBUGMODE,(TEXT("SerDisableSerial\r\n")));

	pHWHead->fIRMode  = TRUE;
	SerSetOutputMode( pHWHead, pHWHead->fIRMode, !pHWHead->fIRMode );
	return (TRUE);
}


/*
 @doc OEM
 @func BOOL | SerOpen | This routine is called when the port is opened.
 *  Not exported to users, only to driver.
 *
 @rdesc Returns TRUE if successful, FALSEotherwise.
 */
static
BOOL
SerOpen(PVOID   pHead)		// @parm PVOID returned by Serinit.
{
	PSER_INFO   pHWHead = (PSER_INFO)pHead;

	// Disallow multiple simultaneous opens
	if ( pHWHead->cOpenCount )
		return (FALSE);

#ifdef EXAMINE_BOOTARGS
	RETAILMSG(DEBUGMODE, (TEXT("SerOpen - Bootargs ComPort %x\r\n"), pHWHead->pBootArgs->ucComPort));
	// If the port is in use as a debugger port, don't allow opens.
	if ( ((pHWHead->pBootArgs->ucComPort == 1) && (pHWHead->dwIOBase == COM1_BASE)) ||
		((pHWHead->pBootArgs->ucComPort == 2) && (pHWHead->dwIOBase == COM2_BASE)) || 
		((pHWHead->pBootArgs->ucComPort == 3) && (pHWHead->dwIOBase == COM3_BASE)) ||
		((pHWHead->pBootArgs->ucComPort == 4) && (pHWHead->dwIOBase == COM4_BASE)) )
	{
		RETAILMSG (DEBUGMODE, (TEXT("SerOpen - Fail open of debug port\r\n")));        
		return (FALSE);        
	}
#endif

	pHWHead->cOpenCount++;

	if ( pHWHead->fIRMode == TRUE )
		RETAILMSG(DEBUGMODE,(TEXT("Use IrDA \r\n")));
	else
		RETAILMSG(DEBUGMODE,(TEXT("Use Serail \r\n")));
	SerSetOutputMode(pHWHead, pHWHead->fIRMode, !pHWHead->fIRMode );  

	// NOTE: - If we wanted to support 16450s, we'll could dynamically
	// identify them here.

	// Init SER2440 info
	RETAILMSG(DEBUGMODE, (TEXT("SerOpen - Calling SL_Open\r\n")));
	SL_Open( pHWHead );
	RETAILMSG(DEBUGMODE, (TEXT("SerOpen - Return TRUE\r\n")));

	return (TRUE);
}

const
HW_VTBL IoVTbl = {
	SerInitSerial,
	SL_PostInit,
	SerDeinit,
	SerOpen,
	SerClose,
	SL_GetInterruptType,
	SL_RxIntr,
	SL_TxIntrEx,
	SL_ModemIntr,
	SL_LineIntr,
	SL_GetRxBufferSize,
	SerPowerOff,
	SerPowerOn,
	SL_ClearDTR,
	SL_SetDTR,
	SL_ClearRTS,
	SL_SetRTS,
	SerEnableSerial,
	SerDisableSerial,
	SL_ClearBreak,
	SL_SetBreak,
	SL_XmitComChar,
	SL_GetStatus,
	SL_Reset,
	SL_GetModemStatus,
	SerGetCommProperties,
	SL_PurgeComm,
	SL_SetDCB,
	SL_SetCommTimeouts,
	};

const		//use of UART1		
HW_VTBL IoVTbl2 = {
	SerInitSerial2,
	SL_PostInit,
	SerDeinit,
	SerOpen,
	SerClose,
	SL_GetInterruptType,
	SL_RxIntr,
	SL_TxIntrEx,
	SL_ModemIntr,
	SL_LineIntr,
	SL_GetRxBufferSize,
	SerPowerOff,
	SerPowerOn,
	SL_ClearDTR,
	SL_SetDTR,
	SL_ClearRTS,
	SL_SetRTS,
	SerEnableSerial,
	SerDisableSerial,
	SL_ClearBreak,
	SL_SetBreak,
	SL_XmitComChar,
	SL_GetStatus,
	SL_Reset,
	SL_GetModemStatus,
	SerGetCommProperties,
	SL_PurgeComm,
	SL_SetDCB,
	SL_SetCommTimeouts,
	};

const		//use of UART2		
HW_VTBL IoVTbl3 = {
	SerInitSerial3,
	SL_PostInit,
	SerDeinit,
	SerOpen,
	SerClose,
	SL_GetInterruptType,
	SL_RxIntr,
	SL_TxIntrEx,
	SL_ModemIntr,
	SL_LineIntr,
	SL_GetRxBufferSize,
	SerPowerOff,
	SerPowerOn,
	SL_ClearDTR,
	SL_SetDTR,
	SL_ClearRTS,
	SL_SetRTS,
	SerEnableSerial,
	SerDisableSerial,
	SL_ClearBreak,
	SL_SetBreak,
	SL_XmitComChar,
	SL_GetStatus,
	SL_Reset,
	SL_GetModemStatus,
	SerGetCommProperties,
	SL_PurgeComm,
	SL_SetDCB,
	SL_SetCommTimeouts,
	};

extern const HW_VTBL SerCardIoVTbl;


const HWOBJ IoObj = {
	THREAD_AT_INIT,
	SYSINTR_SERIAL,
	(PHW_VTBL) &IoVTbl
};

const HWOBJ IoObj2 = {		
	THREAD_AT_INIT,
	SYSINTR_SERIAL2,
	(PHW_VTBL) &IoVTbl2
};


const HWOBJ IoObj3 = {	
	THREAD_AT_INIT,
	SYSINTR_SERIAL3,
	(PHW_VTBL) &IoVTbl3
};


typedef HWOBJ const *PCHWOBJ;


const PCHWOBJ HWObjects[] = {		
	&IoObj,
	&IoObj2,
	&IoObj3
};

// GetSerialObj : The purpose of this function is to allow multiple PDDs to be
// linked with a single MDD creating a multiport driver.  In such a driver, the
// MDD must be able to determine the correct vtbl and associated parameters for
// each PDD.  Immediately prior to calling HWInit, the MDD calls GetSerialObject
// to get the correct function pointers and parameters.
//
PHWOBJ
GetSerialObject(DWORD DeviceArrayIndex)
{
	PHWOBJ pSerObj;

	RETAILMSG(DEBUGMODE,(TEXT("IRDA : GetSerialObject\r\n")));

	IRDA = DeviceArrayIndex;

	// Now return this structure to the MDD.
	if ( DeviceArrayIndex == 0 )
		pSerObj = (PHWOBJ)(&IoObj);

	else if( DeviceArrayIndex == 1 )		
		pSerObj = (PHWOBJ)(&IoObj2);


	else if( DeviceArrayIndex == 2 )
		pSerObj = (PHWOBJ)(&IoObj3);

	return (pSerObj);
}