#define UwsIsa_c


// **********************************************************************
// * uwsisa.c                                                           *
// *                                                                    *
// * Written by Matthew DeLoera (deloera@us.ibm.com)                    *
// * (C) Copyright IBM Corporation, 1998-2001                           *
// *                                                                    *
// *  This software may be used and distributed according to the terms  *
// *  of the GNU Public License, incorporated herein by reference.      *
// **********************************************************************

// **********************************************************************
// * uwsisa.c - This source file provides all ISA-specific functionality*
// *            such as hardware detection, interrupt handling, and     *
// *            device I/O.                                             *
// *                                                                    *
// * Exported Functions                                                 *
// * ------------------                                                 *
// * sysSpGetIsaInfo        - ISA hardware detection/probing            *
// * sysSpProcessIsaRequest - preparation and sending of commands to ASM*
// * sysSp_isaIntrHandler   - ISA interrupt handler                     *
// *                                                                    *
// * Changes                                                            *
// * ------------------------------------------------------------------ *
// * 01/29/2001 - Cleaned up for open source release.                   *
// * ------------------------------------------------------------------ *
// * 06/04/2001 - PHR_162848 - Removed uresolved reference to           *
// *                           current->signal                          *
// * ------------------------------------------------------------------ *
// * 06/27/2002 - PHR_MULTI  - Added multi node support   PHR_MULTI_4   *
// **********************************************************************


// ************
// * INCLUDES *
// ************

#include "uwscmd.h"


// ***********************
// * EXTERNAL PROTOTYPES *
// ***********************
extern void sysSpCmdTimeout(PDRIVER_INFO pDriverNode);
extern void sysSpRunSpCmd(PDRIVER_INFO pDriverNode);
extern void sysSpRunEvent(PDRIVER_INFO pDriverNode);

extern PWORK_BLOCK sysSpPeekQueue(void *pHead);
extern PWORK_BLOCK sysSpEnqueue(PDRIVER_INFO pDriverNode, void *pHead, PWORK_BLOCK pBlock);
extern PWORK_BLOCK sysSpDequeue(PDRIVER_INFO pDriverNode, void *pHead);


// ********************
// * LOCAL PROTOTYPES *
// ********************
void  sysSpGetIsaInfo(void);
void  sysSpProcessIsaRequest(PCMD_BLOCK pCmdBlk, unsigned long ultimeoutValue);
void  sysSpProcessIncomingISA(void);
short GetSLIMBuffer(SLIM_STATUS *pSlim, unsigned char spcmd);
void  sysSp_isaIntrHandler(int iRq, void * pDev_id, struct pt_regs *regs);


// ************************************************************************
// * sysSpGetIsaInfo() - This function determines information for Ranger, *
// *                     such as port number and interrupt level.         *
// *                                                                      *
// * Parameters:                                                          *
// ************************************************************************
void sysSpGetIsaInfo(void)
{
   UCHAR temp;

   // READ CMOS TO DETERMINE WHICH INTERRUPT VECTOR
   // WAS SELECTED BY THE USER IN POST BIOS SETUP
   WRITE_PORT_UCHAR(0x70, 0x0A);
   temp = READ_PORT_UCHAR(0x71);

   // IF BOTH BANK BITS ARE NON-ZERO, WE MUST SWITCH TO BANK 0
   if ((temp & 0x05) != 0)
   {
       WRITE_PORT_UCHAR(0x71, 0x26);
   }

   // CMOS BANK 0 NOW SELECTED - READ BYTE AT OFFSET 0x33
   WRITE_PORT_UCHAR(0x70, 0x33);
   pSpPrimaryNode->intVector = READ_PORT_UCHAR(0x71);

   if (DBG_ISADEBUG)
   {
      printk("sysSpGetIsaInfo: CMOS data at offset 0x33 = 0x%x", pSpPrimaryNode->intVector);
   }

   // THE SP INTERRUPT VECTOR IS IN THE LOWER 4 BITS OF THIS BYTE
   pSpPrimaryNode->intVector = pSpPrimaryNode->intVector & 0x0f;

   // BIOS FIX - IF THE IRQ VECTOR IS 14, USE 6 INSTEAD
   if (pSpPrimaryNode->intVector == 14)
   {
      pSpPrimaryNode->intVector = 6;
   }

   if (DBG_ISADEBUG)
   {
      printk("sysSpGetIsaInfo: Chosen IRQ vector = 0x%x", pSpPrimaryNode->intVector);
   }

   // IF WE SWITCHED CMOS BANKS, SWITCH BACK TO PREVIOUS SETTING
   if ((temp & 0x05) != 0)
   {
      WRITE_PORT_UCHAR(0x70, 0x0A);
      WRITE_PORT_UCHAR(0x71, temp);
   }

   // FINISH SETTING OTHER CONFIGURATION PARMS IN DRIVER EXTENSION
   pSpPrimaryNode->ioAddr   = (PUCHAR)0xE8;
   pSpPrimaryNode->baseAddr = 0;
   pSpPrimaryNode->memSize  = 0;

   return;
}


// **********************************************************************
// * sysSpProcessIsaRequest() - This function sends a message to the SP *
// *                            via I2C, using the SLIM TX machine.     *
// *                                                                    *
// * NOTE: The command queue is currently locked!                       *
// *                                                                    *
// * Parameters:                                                        *
// *      pCmdBlk        - Pointer to command block to be sent          *
// *      ultimeoutValue - Comnand timeout value in ms.                 *
// **********************************************************************
void sysSpProcessIsaRequest(PCMD_BLOCK pCmdBlk, unsigned long ultimeoutValue)
{
   UCHAR uc;
   PCMD_BUFFER pCmdBuffer = (PCMD_BUFFER)pCmdBlk->pBuffer;
   
   // INITIALIZE CONTROL STRUCTURE FOR TIMEOUT CALLBACK ROUTINE
   pSpPrimaryNode->pActiveCmdBlk = pCmdBlk;   // PHR_MULTI

   // INTIIALIZE SLIM TX MACHINE AND BUILD A SLIM PACKET WITH THIS SP COMMAND
   InitializeSlimSend(&pSpPrimaryNode->sendSlim, (PUCHAR)pCmdBlk->pBuffer,
       (USHORT)(sizeof(spCmd) + pCmdBuffer->cmdBlock.CommandLgth + pCmdBuffer->cmdBlock.DataLgth));
   
   // MAKE SURE THE I2C INTERRUPT IS CLEARED
   if ((GET_I2C_STATUS & I2C_INTERRUPT_MASK) != 0) GET_I2C_DATA;

   // MAKE SURE THE I2C OUTPUT PORT IS FREE
   if (GET_I2C_STATUS & OUTPUT_PORT_FREE)
   {
      // FETCH THE FIRST BYTE OF THE SLIM PACKET TO BE SENT (CHECK THAT IT'S VALID)
      if ( GetNextSlimByte(&pSpPrimaryNode->sendSlim, &uc) )
      {
         // MARK THIS COMMAND BLOCK'S COMPLETION CODE AS PENDING
         pCmdBlk->completionCode = DDERR_CMD_PENDING;

         // SET UP THE COMMAND TIMER
         pSpPrimaryNode->CommandTimer.function = (void *)sysSpCmdTimeout;
         pSpPrimaryNode->CommandTimer.data     = (unsigned long)pSpPrimaryNode; // PHR_MULTI
         pSpPrimaryNode->CommandTimer.expires  = jiffies + ultimeoutValue * HZ;

         add_timer(&(pSpPrimaryNode->CommandTimer));

         // TRANSMIT THIS FIRST BYTE TO THE SP, THEREBY STARTING THE INTERRUPT-DRIVEN TX PROCESS
         PUT_I2C_DATA(uc);
      }
      else
      {
         printk(KERN_CRIT "ibmasm ERROR: First byte of slim packet was NULL. Returning failure\n");

         // MARK THIS COMMAND BLOCK'S COMPLETION CODE AS FAILED
         pCmdBlk->completionCode = DDERR_SEND_BYTE_FAILED;
      }
   }

   return;
}

// **************************************************************************************
// * sysSpProcessIncomingISA() - This function gets called by sysSp_isaIntrHandler, the *
// *                             device drivers main interrupt handler. It checks to    *
// *                             see if this interrupt is valid and determines what type*
// *                             of interrupt it is - Command Response, Heart Beat, ACK *
// *                             or Event and processes it accordingly.                 *
// * Parameters:                                                                        *
// **************************************************************************************
void sysSpProcessIncomingISA()
{
   UCHAR dataByte;
   ULONG dataLength =0;
   PCMD_BLOCK pCmdBlk = NULL;
   unsigned long flags=0;

   // READ NEWLY RECEIVED MESSAGE BYTE FROM SP'S I/O PORT
   dataByte = GET_I2C_DATA;

   // DETERMINE WHETHER THIS IS AN ACK
   if (ACK == dataByte)
   {
        // RECEIVING AN ACK INDICATES THAT WE'RE CURRENTLY SENDING A COMMAND TO THE SP.
        // SEND THE NEXT BYTE.
        if (GetNextSlimByte(&pSpPrimaryNode->sendSlim, &dataByte))
        {
             PUT_I2C_DATA(dataByte);

        }
   }
   else
   {
      // NOT AN ACK - USE THIS BYTE TO BUILD THE INCOMING MESSAGE.
      dataLength = SetNextSlimByte(&pSpPrimaryNode->receiveSlim, &dataByte);

      if (dataLength != 0)
      {
         // THE SLIM RX MACHINE INDICATES THAT THIS MESSAGE IS COMPLETE
         switch (pSpPrimaryNode->receiveSlim.buffer[0])
         {
            case spCmdResp:   // COMMAND RESPONSE 
            {
               // LOCK COMMAND QUEUE
               spin_lock_irqsave( &(pSpPrimaryNode->CmdQueueLock), flags );

               if (pSpPrimaryNode->CmdQueue == NULL)
               {
                   printk(KERN_CRIT "ibmasm: ERROR - Received a command response without an associated command.\n");

                   // UNLOCK THE COMMAND QUEUE
                   spin_unlock_irqrestore( &(pSpPrimaryNode->CmdQueueLock), flags );

                   break;
               }

               // GET THE COMMAND FROM THE COMMAND QUEUE THATS BEEN WAITING FOR A COMMAND RESPONSE
               // PREVIOUSLY GetNextSlimByte() CALLED GetSLIMBuffer() WHICH DETERMINED THAT
               // A COMMAND RESPONSE WAS BEING RECEIVED SO IT STORED IT IN THE GLOBAL COMMAND 
               // RESPONSE BUFFER - pSpPrimaryNode->pSpResponseBuffer

               pCmdBlk = sysSpPeekQueue(pSpPrimaryNode->CmdQueue);

               // UNLOCK THE COMMAND QUEUE
               spin_unlock_irqrestore( &(pSpPrimaryNode->CmdQueueLock), flags );

               // THERE SHOULD BE A PENDING COMMAND ON THE COMMAND QUEUE. IF NOT THERE'S A PROBLEM 
               if ( (pCmdBlk != NULL) && ((pCmdBlk->completionCode == DDERR_CMD_PENDING) ||
                                          (pCmdBlk->completionCode == DDERR_PENDING_CMD_INTERRUPTED)))
               {
                   // DEACTIVATE THE COMMAND TIMER AND RESET THE TIMER FLAG
                   del_timer(&(pSpPrimaryNode->CommandTimer));

                   if (pCmdBlk->PID == PID_HEART_BEAT)
                   {
                      if (DBG_HB)
                      {
                         printk(KERN_CRIT "DBG_HB: Received a command response for a heart beat.\n");
                         printk(KERN_CRIT "DBG_HB: Dequeueing Heart Beat command block.\n");
                      }

                      // MARK THE COMPLETION CODE AS SUCCESSFUL 
                      pCmdBlk->completionCode = DDERR_SUCCESS;
                      //pHB_Block->completionCode = DDERR_SUCCESS;  // PHR_MULTI

                      // LOCK THE COMMAND QUEUE AND DEQUEUE THE HEART BEAT FROM THE COMMAND QUEUE
                      spin_lock_irqsave( &(pSpPrimaryNode->CmdQueueLock), flags );

                      sysSpDequeue(pSpPrimaryNode, pSpPrimaryNode->CmdQueue);

                      // UNLOCK THE COMMAND QUEUE
                      spin_unlock_irqrestore( &(pSpPrimaryNode->CmdQueueLock), flags );
                   }
                   else  // IT'S FOR A COMMAND ISSUED TO THE H/W              // PHR_174969
                   {
                       if (pCmdBlk->completionCode == DDERR_PENDING_CMD_INTERRUPTED)
                       {
                           if (DBG_PHR)
                           {
                              printk(KERN_CRIT "DBG_PHR: Received a command response for an interrupted pending command.\n");
                           }

                           // MARK THE COMPLETION CODE AS SUCCESSFUL 
                           pCmdBlk->completionCode = DDERR_SUCCESS;

                           // SET THE BUFFER LENGTH
                           pCmdBlk->bufferLength = dataLength;
                       }
                       else
                       {
                           // MARK THE COMPLETION CODE AS SUCCESSFUL 
                           pCmdBlk->completionCode = DDERR_SUCCESS;

                           // SET THE BUFFER LENGTH
                           pCmdBlk->bufferLength = dataLength;

                           // RELEASE THE BLOCKED THREAD WAITING FOR A COMMAND RESPONSE
                           //wake_up_interruptible(&(pCmdBlk->pWaitQueueRun));    // PHR_187769
                           wake_up(&(pCmdBlk->pWaitQueueRun));                    // PHR_187769 
                       }
                   }
               }
               else   // SHOULD NOT BE HERE, IF WE ARE, THERE'S A PROBLEM  
               {
                  pCmdBlk->completionCode = DDERR_UNEXPECTED_CMD_RESPONSE;
                  printk(KERN_CRIT "ibmasm: ERROR - Received a command response with out a command pending.\n");
               }

               sysSpRunSpCmd(pSpPrimaryNode);

               break;
            }
            case spEvent:     // EVENT RESPONSE 
            {
              if (DBG_IRQ) printk("sysSpProcessIncomingISA: Received event.\n");

              // LOCK EVENT QUEUE
              spin_lock_irqsave( &(pSpPrimaryNode->EventQueueLock), flags );

              // GENERATE NEW INDEX FOR THIS EVENT
              if (pSpPrimaryNode->MagicNumber < (EV_MAXNUMBER-1)) pSpPrimaryNode->MagicNumber++;
              else pSpPrimaryNode->MagicNumber = 1;

              // STORE THIS EVENT ON THE CIRCULAR EVENT BUFFER
              pSpPrimaryNode->EvRcvd[pSpPrimaryNode->EvBufIndex].EvLength = dataLength;
              pSpPrimaryNode->EvRcvd[pSpPrimaryNode->EvBufIndex].EvNumber = pSpPrimaryNode->MagicNumber;
              if (pSpPrimaryNode->EvBufIndex < (EV_BUFFERS-1)) pSpPrimaryNode->EvBufIndex++;
              else                                       pSpPrimaryNode->EvBufIndex = 0;

              // RELEASE ALL BLOCKED REGISTRATION THREADS
              sysSpRunEvent(pSpPrimaryNode);

              // UNLOCK EVENT QUEUE
              spin_unlock_irqrestore( &(pSpPrimaryNode->EventQueueLock), flags );

              break;
            }
            case spHeartBeat:               // HEARTBEAT // PHR_MULTI_2
            {
               // PREVIOUSLY GetNextSlimByte() CALLED GetSLIMBuffer() WHICH DETERMINED THAT
               // A HEART BEAT WAS BEING RECEIVED SO IT STORED IT IN THE GLOBAL HEART BEAT
               // BUFFER - pSpPrimaryNode->pHB_Block->pBuffer

               if (DBG_HB)
               {
                  printk(KERN_CRIT "DBG_HB: Received a heart beat from the SP\n");
               }

               // PREPARE THE COMMAND BLOCK TO BE SENT TO THE SP
               pSpPrimaryNode->pHB_Block->pBuffer[0]     = spWrite; 
               pSpPrimaryNode->pHB_Block->PID            = PID_HEART_BEAT;
               pSpPrimaryNode->pHB_Block->bufferLength   = dataLength; 
               pSpPrimaryNode->pHB_Block->completionCode = DDERR_CMD_INITIATED;
             
               // ADD THIS HEART BEAT TO THE COMMAND QUEUE - IF THE QUEUE WAS NOT EMPTY (NULL)
               // WAIT FOR IT'S TURN TO RUN BY WAITING FOR sysSpRunCmd() TO WAKE THIS TASK UP.
               // OTHERWISE, THE HEART BEAT RESPONSE CAN BE SENT TO THE SP IMMEDIATELY
               spin_lock_irqsave( &(pSpPrimaryNode->CmdQueueLock), flags );

               if (DBG_HB)
               {
                  printk(KERN_CRIT "DBG_HB: Enqueueing heart beat.\n");
               }

               if ( (sysSpEnqueue(pSpPrimaryNode, pSpPrimaryNode->CmdQueue, pSpPrimaryNode->pHB_Block)) == NULL)
               { 
                  if (DBG_HB)
                  {
                     printk(KERN_CRIT "DBG_HB: Sending heart beat to the SP (ISA H/W).\n");
                  }

                  // RETRIEVE THE HEART BEAT FROM THE COMMAND QUEUE
                  pCmdBlk = sysSpPeekQueue(pSpPrimaryNode->CmdQueue);

                  // UNLOCK THE COMMAND QUEUE
                  spin_unlock_irqrestore( &(pSpPrimaryNode->CmdQueueLock), flags );

                  // SEND THE HEART BEAT RESPONSE TO THE SP
                  sysSpProcessIsaRequest(pCmdBlk, CMD_TIMEOUT_240);
               }
               else // THERE WAS A COMMAND ON THE QUEUE SO WAIT FOR THIS TASKS TURN TO RUN
               {
                  // UNLOCK THE COMMAND QUEUE
                  spin_unlock_irqrestore( &(pSpPrimaryNode->CmdQueueLock), flags );

                  break;
               }

               sysSpRunSpCmd(pSpPrimaryNode);

               break;
            }  // end - case HeartBeat


            default:          // UNEXPECTED COMMAND 
              printk(KERN_CRIT "ibmasm: ERROR - Received an unknown command from the Service Processor adapter.\n");
              break;

         }  // end switch

         // RESET SLIM RECEIVE VARIABLES IN PREPARATION FOR THE NEXT INCOMING PACKET FROM THE SP
         InitializeSlimReceive(&pSpPrimaryNode->receiveSlim, NULL, 0);
      }     // end if  -datalength = 0
   }        // end else - not an ACK

   return;
}           // end - sysSpProcessIncomingISA()


// ****************************************************************************
// * GetSLIMBuffer() - This function gets called by the cross-platform SLIM   *
// *                   functions. It allocates a data buffer for the incoming *
// *                   data bytes, if necessary, and points the SLIM RX       *
// *                   machine's buffer pointer to the appropriate buffer.    *
// *                                                                          *
// * Parameters:                                                              *
// *      pSlim - Pointer to the SLIM RX machine                              *
// *      spcmd - The message type byte for this message                      *
// ****************************************************************************
short GetSLIMBuffer(SLIM_STATUS *pSlim, unsigned char spcmd)
{
     PCMD_BLOCK pCmdBlk = NULL;
     ULONG      flags   = 0;

     // BRANCH ON MESSAGE TYPE
     if (spcmd == spCmdResp)
     {
          // COMMAND RESPONSE - POINT TO CURRENT COMMAND BLOCK IN COMMAND QUEUE
          spin_lock_irqsave( &(pSpPrimaryNode->CmdQueueLock), flags );

          if (pSpPrimaryNode->CmdQueue == NULL)
          {
              // UNLOCK THE COMMAND QUEUE
              spin_unlock_irqrestore( &(pSpPrimaryNode->CmdQueueLock), flags );

              printk("ibmasm: ERROR - GetSLIMBuffer There are no pending commands to sync with this command response.\n");

              return 1;
          }

          pCmdBlk = sysSpPeekQueue(pSpPrimaryNode->CmdQueue);

          // UNLOCK THE COMMAND QUEUE
          spin_unlock_irqrestore( &(pSpPrimaryNode->CmdQueueLock), flags );
          
          // PHR_174969
          if ( (pCmdBlk->completionCode == DDERR_CMD_PENDING) ||
               (pCmdBlk->completionCode == DDERR_PENDING_CMD_INTERRUPTED) )
          {
            // THERE IS A CURRENT COMMAND BLOCK - POINT THE SLIM RX MACHINE TO IT'S BUFFER
            pSlim->buffer     = pSpPrimaryNode->pSpResponseBuffer;
            pSlim->bufferSize = RESPONSE_BUFFER_SIZE;
            return 1;
          }
          else
          {
             printk("ibmasm: ERROR - GetSLIMBuffer There are no pending commands to sync with this command response.\n");
          }
     }
     else if (spcmd == spEvent)
     {
          if (DBG_DEBUG) printk("GetSLIMBuffer: Event.\n");

          // EVENT - POINT THE SLIM RX MACHINE TO THE CURRENT EVENT BUFFER'S DATA BUFFER
          pSlim->buffer     = pSpPrimaryNode->EvRcvd[pSpPrimaryNode->EvBufIndex].pEvBuffer;
          pSlim->bufferSize = EV_BUFFER_SIZE;
          return 1;
     }
     else if (spcmd == spHeartBeat)
     {
        if (DBG_HB)
        {
            printk("DBG_HB: GetSLIMBuffer assigning HB buffer as Slim buffer.\n");
        }

        // HEARTBEAT - POINT THE SLIM RX MACHINE TO THE GLOBAL HEARTBEAT BUFFER
        pSlim->buffer     = pSpPrimaryNode->pHB_Block->pBuffer;      // PHR_MULTI_2 
        pSlim->bufferSize = HB_BUFFER_SIZE;
        return 1;
     }

     return 0;
}


// ******************************************************************************
// * sysSp_isaIntrHandler - This is the driver's interrupt handler entry point. *
// *                        The kernel calls this function when an interrupt    *
// *                        occurs on the ISA host adapter.                     *
// *                                                                            *
// * Parameters:                                                                *
// *      iRq     - # of the IRQ                                                *
// *      pDev_id - Pointer to the driver extension                             *
// *      regs    - Pointer to the processor registers at the time of IRQ       *
// ******************************************************************************

void sysSp_isaIntrHandler(int iRq, void * pDev_id, struct pt_regs *regs)
{
     // CHECK THE SP IRQ REGISTER TO DETERMINE WHETHER THIS INTERRUPT IS FOR THIS DRIVER
     if (inb((unsigned int)I2C_CTL_PORT) & I2C_INTERRUPT_MASK) sysSpProcessIncomingISA();
     return;
}


#undef UwsIsa_c


