#pragma optimize("", off)
/*++
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.

Module Name:  

  ucbreg.c

Abstract:  

  This module contains routines for manipulating the touch/audio board
  registers.  The FPGA uses a serial interface to actually communicate
  with the board, and indicates completions either by an interrupt, or
  a polled bit.  
  
Functions:

  TchAudAndReg
  TchAudOrReg
  TchAudWriteReg
  TchAudReadReg
  
Notes:

Revision History:

  Glenn Davis 4/1/97

--*/
#include <windows.h>
#include <p2.h>
#include <p2debug.h>
#include <tchaud.h>
#include <drv_glob.h>

/*
 * Timeout value for poll loop waiting for completion of register operation.
 */
#define TCHAUD_REG_TIMEOUT	500 /* ms */

#define TCHAUD_SEM_TIMEOUT  (TCHAUD_REG_TIMEOUT*2)

/*
 * Macro to handle wrap when comparing timestamps
 */
#define ELAPSED_TICKS(start,end)  (((start) <= (end))? ((end) - (start)) : (0xffffffff - (start) + (end) + 1))

/*
 * Routines to provide synchronization for accessing the touch/audio asic
 * registers.  We use a mutex to prevent contention between the touch and
 * audio drivers, and a flag in driver globals to prevent contention with
 * the touch ISR.  If the touch ISR sees the flag set, it will not attempt
 * to access the UCB registers (it will wait until the 5ms pen timer goes off
 * and try again).
 */
void
TchAudLock(HANDLE hMutex, PULONG pSem)
{
    DWORD StartTime = GetTickCount(), CurTime;
    
    WaitForSingleObject(hMutex, TCHAUD_SEM_TIMEOUT);
    // Now, synchronize with ISR. Busy wait OK, since ISR will always preeempt.
    while (InterlockedTestExchange(pSem, 0, 1) != 0) {
        Sleep(0);   // Force reschedule
        CurTime = GetTickCount();
        if (ELAPSED_TICKS(StartTime,CurTime) > TCHAUD_SEM_TIMEOUT) {
            RETAILMSG(1,(TEXT("!TchAudLock: Timed out waiting for semaphore!!!\r\n")));
            break;
        }
    }
}

void
TchAudUnlock(HANDLE hMutex, PULONG pSem)
{
    *pSem = 0;
    ReleaseMutex(hMutex);
}


/*
 * Routines for accessing the Touch/audio ASIC registers.  To read or write
 * one of the ASIC registers, first specify the register and operation,
 * and then wait for completion, which is indicated by the regIntr bit in the
 * ucbStr register.  We poll for this event in WaitForRegCompletion.
 */
static BOOL 
WaitForRegCompletion(
    volatile PTCHAUD_ASIC_REGISTERS pTchAudRegs,
    BOOL bInPowerHandler)
{
   DWORD StartTime;

   if (!bInPowerHandler)
       StartTime = GetTickCount();
   
   while (1)
   {
       if (pTchAudRegs->ucbStr & intrMaskRegIntrMask) {
           // Clear regIntr interrupt.
           pTchAudRegs->ucbStr = intrMaskRegIntrMask;           
           return TRUE;
       }
       if (!bInPowerHandler) {
           DWORD CurTime = GetTickCount();
           if (ELAPSED_TICKS(StartTime,CurTime) > TCHAUD_REG_TIMEOUT) {
               RETAILMSG(1,(TEXT("!TchAud: Timed out waiting for UCB reg completion\n\r")));
               return FALSE;
           }
       }
   }
   return FALSE;
}

typedef enum {OP_READ, OP_WRITE,OP_AND,OP_OR} REGOP;

/*
 * Routine to perform read write and modify operations on ASIC registers.
 */
static BOOL
TchAudRegOp(
    USHORT Reg,       // UCB register number
    USHORT Val,       // Value for modify operations
    USHORT *pRetval,  // for OP_READ only 
    REGOP op,         
    volatile PTCHAUD_ASIC_REGISTERS pTchAudRegs,
    BOOL bInPowerHandler)
{
 
    BOOL bRet; 
    USHORT TchAudIntrMask;

    // Make sure regIntr is masked so we don't generate interrupts 
    // when we write to the ASIC.
    TchAudIntrMask = pTchAudRegs->intrMask;
    pTchAudRegs->intrMask = (USHORT)(TchAudIntrMask & ~intrMaskRegIntrMask); 

    // Clear regIntr interrupt just as a precaution.
    pTchAudRegs->ucbStr = intrMaskRegIntrMask;

    if (op != OP_WRITE) {
        // Read register
        pTchAudRegs->ucbCntr = (USHORT)(Reg & ~ucbCntrWriteBit);
        pTchAudRegs->ucbRegister = 0;
        if (WaitForRegCompletion(pTchAudRegs, bInPowerHandler) == FALSE) {
            bRet = FALSE;
            goto RegOpsDone;
        }

        if (op == OP_READ) {
            *pRetval = pTchAudRegs->ucbRegister;
            bRet = TRUE;
            goto RegOpsDone;
        }
        
        if (op == OP_AND)
            Val &= pTchAudRegs->ucbRegister;
        else // OR
            Val |= pTchAudRegs->ucbRegister;
    }
    
    // Write to register
//    RETAILMSG(1,(TEXT("TchAudRegOp: Writing val 0x%X to reg %u, thread: 0x%X\r\n"),Val,Reg,GetCurrentThread()));
    pTchAudRegs->ucbCntr     = (USHORT)(Reg | ucbCntrWriteBit);
    pTchAudRegs->ucbRegister = Val;

    bRet = WaitForRegCompletion(pTchAudRegs, bInPowerHandler);

RegOpsDone:
    
    //
    // Restore intrMask register to original value.
    //
    pTchAudRegs->intrMask = TchAudIntrMask;
    return bRet;   
}


BOOL
TchAudReadReg(
    USHORT Reg,
    USHORT *pVal,
    PTCHAUD_ASIC_REGISTERS pTchAudRegs,
    BOOL bInPowerHandler)
{
    return TchAudRegOp(Reg,0,pVal,OP_READ, pTchAudRegs, bInPowerHandler);
}

BOOL
TchAudWriteReg(
    USHORT Reg,
    USHORT Val,
    PTCHAUD_ASIC_REGISTERS pTchAudRegs,
    BOOL bInPowerHandler)
{
    return TchAudRegOp(Reg,Val,NULL,OP_WRITE, pTchAudRegs, bInPowerHandler);
}
BOOL
TchAudAndReg(
    USHORT Reg,
    USHORT Val,
    PTCHAUD_ASIC_REGISTERS pTchAudRegs,
    BOOL bInPowerHandler)
{
    return TchAudRegOp(Reg,Val,NULL,OP_AND, pTchAudRegs, bInPowerHandler);
}
BOOL
TchAudOrReg(
    USHORT Reg,
    USHORT Val,
    PTCHAUD_ASIC_REGISTERS pTchAudRegs,
    BOOL bInPowerHandler)
{
    return TchAudRegOp(Reg,Val,NULL,OP_OR, pTchAudRegs, bInPowerHandler);
}


