////////////////////////////////////////////////////////////////
// File - P9050_SERIAL.C
//
// This is a serial port driver for PLX 9050 RDK board.
//
// To use this driver, install the PLX 9050 RDK board, plug in
// to the piggyback ISA connector the serial port or modem ISA
// card. Run this driver.
// Your ISA serial port will be detected, and written into the
// registry. After rebooting, a new COM port will be added, that
// will allow you to access your legacy ISA card.
// 
////////////////////////////////////////////////////////////////

#include <windows.h>
#include <winioctl.h>
#include "../../../include/windrvr.h"
#include "../lib/p9050_lib.c"
#include <stdio.h>

CHAR *sApp = "PLX 9050 Serial Driver";

DWORD PLX_DetectInterrupt(P9050_HANDLE hPlx, DWORD dwVendorID, DWORD dwDeviceID, DWORD nCardNum)
{
    WD_PCI_SCAN_CARDS pciScan;
    WD_PCI_CARD_INFO pciCardInfo;
    DWORD i;

    BZERO(pciScan);
    pciScan.searchId.dwVendorId = dwVendorID;
    pciScan.searchId.dwDeviceId = dwDeviceID;
    WD_PciScanCards (hPlx->hWD, &pciScan);
    if (pciScan.dwCards<=nCardNum)
        return 0; // no cards found

    BZERO(pciCardInfo);
    pciCardInfo.pciSlot = pciScan.cardSlot[nCardNum];
    WD_PciGetCardInfo (hPlx->hWD, &pciCardInfo);

    // search for interrupt item
    for (i=0; i<pciCardInfo.Card.dwItems; i++)
    {
        if (pciCardInfo.Card.Item[i].item==ITEM_INTERRUPT)
            return pciCardInfo.Card.Item[i].I.Int.dwInterrupt;
    }

    return 0; // interrupt not found
}

// The main window loop.
// WinMain() opens a handle for speaker, and then creates the main menu window.
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow )
{
    // IO addresses of serial ports on the local bus attached to the PLX 9050 board
    DWORD dwSerialAddr[] = {0x3f8, 0x2f8, 0x3e8, 0x2e8, 0xffffffff};
    // start creating com ports from COM5
    DWORD dwComPort = 5; 

    P9050_HANDLE hPlx = NULL;
    DWORD dwVendorID = 0x10b5;
    DWORD dwDeviceID = 0x9050;
    DWORD nCardNum = 0; // use this if you have more than one plx 9050 boards installed.
    DWORD dwInterrupt;

    DWORD res;
    DWORD val;
    CHAR sTmp[256];
    DWORD i;
    OSVERSIONINFO lVerInfo;
    HKEY hKey;

    DWORD dwFound = 0;
    int retVal = FALSE;
    
    lVerInfo.dwOSVersionInfoSize = sizeof (lVerInfo);
    GetVersionEx (&lVerInfo);
    switch (lVerInfo.dwPlatformId)
    {
        case VER_PLATFORM_WIN32_NT:
            break;

        case VER_PLATFORM_WIN32_WINDOWS:
        default:
            MessageBox( NULL, "This driver will only work on Windows NT\n", sApp, MB_OK | MB_ICONERROR );
            goto Exit;
    }    
    
    // open PLX 9050 card WITHOUT interrupts.
    // sometimes there might be problems with interrupt sharing
    if (!P9050_Open( &hPlx, dwVendorID, dwDeviceID, nCardNum, 0))
    {
        MessageBox( NULL, P9050_ErrorString, sApp, MB_OK | MB_ICONERROR );
        goto Exit;
    }

    // since we opened the card without interrupts, then we have to fetch
    // the interrupt information ourselves
    dwInterrupt = PLX_DetectInterrupt(hPlx, dwVendorID, dwDeviceID, nCardNum);
    if (!dwInterrupt)
    {
        MessageBox( NULL, "No interrupt allocated for card\n", sApp, MB_OK | MB_ICONERROR );
        goto Exit;
    }

    // the standard serial port can only access IO mapped devices
    if (hPlx->addrDesc[P9050_ADDR_SPACE1].fIsMemory)
    {
        MessageBox( NULL, "Address space 1 (BAR3) must be IO mapped in order that the standard serial driver can access it\n", 
            sApp, MB_OK | MB_ICONERROR );
        goto Exit;
    }

    // Sanity check: is address space 1 mapped 0x0-0x3ff IO range.
    // If your plx device is configured differently, then you can adjust this test
    // to the size you programmed address space 1.
    if (hPlx->addrDesc[P9050_ADDR_SPACE1].dwBytes != 0x400)
    {
        MessageBox( NULL, "Expected address space 1 (BAR3) to be of size 0x400\n", sApp, MB_OK | MB_ICONERROR );
        goto Exit;
    }

    // test the PLX 9050 local bus for serial ports
    for (i=0; dwSerialAddr[i]!=0xffffffff; i++)
    {
        DWORD dwAddr = dwSerialAddr[i];
        DWORD dwVal0, dwVal1, dwVal2;

        // Validation: is there a serial port at this address?
        dwVal0 = P9050_ReadSpaceByte( hPlx, P9050_ADDR_SPACE1, dwAddr);
        dwVal1 = P9050_ReadSpaceByte( hPlx, P9050_ADDR_SPACE1, dwAddr+1);
        dwVal2 = P9050_ReadSpaceByte( hPlx, P9050_ADDR_SPACE1, dwAddr+2);
        if (dwVal0 == dwVal1 && dwVal1==dwVal2)
        {
            //printf ("all registers have the same values - this is probably not a serial port\n");
            continue;
        }

        sprintf (sTmp,"System\\CurrentControlSet\\Services\\Serial\\Parameters\\Serial%d", dwComPort+dwFound-1);
        RegCreateKeyEx (HKEY_LOCAL_MACHINE, sTmp, 0,"",0,KEY_ALL_ACCESS, NULL, &hKey, &res);
        sprintf (sTmp, "COM%d", dwComPort+dwFound);
        RegSetValueEx (hKey, "DosDevices", 0, REG_SZ, sTmp, strlen(sTmp)+1);
        val = 1;
        RegSetValueEx (hKey, "ForceFifoEnable", 0, REG_DWORD, (PVOID) &val, 4);
        val = dwInterrupt;
        RegSetValueEx (hKey, "Interrupt", 0, REG_DWORD, (PVOID) &val, 4);
        val = hPlx->addrDesc[P9050_ADDR_SPACE1].dwAddr + dwAddr;
        RegSetValueEx (hKey, "PortAddress", 0, REG_DWORD, (PVOID) &val, 4);
        RegCloseKey (hKey);
        dwFound ++;
    }
    
    if (dwFound) 
    {
        retVal = TRUE;
        sprintf (sTmp, "%d serial ports were found on the PLX 9050 board.\n"
            "You will need to reboot in order for the serial driver to be activated.", 
            dwFound);
        MessageBox( NULL, sTmp, sApp, MB_OK);
    }
    else
        MessageBox( NULL, "No serial ports found on the PLX 9050 board\n", sApp, MB_OK | MB_ICONERROR );

Exit:
    if (hPlx) 
        P9050_Close( hPlx);
    return retVal;
}