/*
* Copyright (c) 2006-2008 Advanced Micro Devices,Inc. ("AMD").
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.

* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA 
*/

//*****************************************************************************
//*     This file contains code specific to CS5536 MFGPTs 
//*****************************************************************************


#include "VSA2.H"
#include "SYSMGR.H"
#include "PROTOS.H"
#include "MDD.H"
#include "MAPPER.H"
#include "TIMER.H"

// External variables:
extern ULONG MDD_Base;

// Local variables:
USHORT MFGPT_Base=0;
ULONG MFGPT_LBAR[2];
 


// NOTE: Timers that use the 32KHz clock interfere with LPC DMA (see PBZ 709)
  
                                                                      // Clock    Prescalar  Period
#define TIMER_SETUP (MFGPT_SCALE_1K | MFGPT_CMP1MODE | MFGPT_CLK_SEL) // 14 MHz     1024      71 us
#define STDBY_SETUP (MFGPT_SCALE_32 | MFGPT_CMP1MODE)                 // 32 KHz       32       1 ms
#define PWM_SETUP   (MFGPT_SCALE_32 | MFGPT_CMP1GE)                   // 32 KHz       32       1 ms
TIMERS TimerInfo[] = {
//  #       Z Mapper        Setup           
// Save for OS (Linux) use   { 0,   Z_IRQ_MFGPT_04,   PWM_SETUP},
  { 1,   Z_IRQ_MFGPT_15,   PWM_SETUP},
  { 2,   Z_IRQ_MFGPT_26,   PWM_SETUP},
  { 3,   Z_IRQ_MFGPT_37, TIMER_SETUP},
// Save for OS (Linux) use  { 4,   Z_IRQ_MFGPT_04, TIMER_SETUP},
  { 5,   Z_IRQ_MFGPT_15, TIMER_SETUP},
  { 6,   Z_IRQ_MFGPT_26, STDBY_SETUP},
  { 7,   Z_IRQ_MFGPT_37,   PWM_SETUP},
};
#define NUM_MFGPTS     (sizeof(TimerInfo)/sizeof(TIMERS))  // Number of MFGPTs available

//************************************************************************************
// Saves the MFGPT LBAR setting and enables the LBAR
//************************************************************************************
void Enable_MFGPT_LBAR()
{ ULONG Tmp;
  int i;

  // Get MFGPT LBAR
  Read_MSR(MDD_Base + MSR_LBAR_MFGPT, MFGPT_LBAR);

  // Has the MFGPT base address changed?
  if (MFGPT_Base != (USHORT)MFGPT_LBAR[0]) {
    // Yes, record the new base address of MFGPT
    MFGPT_Base = (USHORT)MFGPT_LBAR[0];

    // Update the individual timer base addresses
    for (i = 0; i < NUM_MFGPTS; i++) {
      TimerInfo[i].TimerBase = MFGPT_Base + TimerInfo[i].Timer*MFGPT_OFFSET;
    }
  }

  // Save the current LBAR enable
  Tmp = MFGPT_LBAR[1];

  // Enable the LBAR
  MFGPT_LBAR[1] |= LBAR_EN;
  Write_MSR(MDD_Base + MSR_LBAR_MFGPT, MFGPT_LBAR);
  MFGPT_LBAR[1] = Tmp;

}


//************************************************************************************
// Restores the MFGPT LBAR
//************************************************************************************
void Restore_MFGPT_LBAR()
{
  // Restore MFGPT LBAR
  Write_MSR(MDD_Base + MSR_LBAR_MFGPT, MFGPT_LBAR);
}


//************************************************************************************
// Checks for MFGPT events
// Returns a mask of which timer(s) have expired
//************************************************************************************
USHORT CS5536_MFGPT_Handler(void)
{ USHORT Setup, Status, Accum_Status=0, i;
  register TIMERS * TimerPtr;

  Enable_MFGPT_LBAR();

  for (i = 0; i < NUM_MFGPTS; i++) {

    TimerPtr = &TimerInfo[i];

    // Get timer status
    Setup = TimerPtr->TimerBase + MFGPT_SETUP;
    Status = in_16(Setup);
    if (Status & MFGPT_INITED) {
      out_16(Setup, Status);
    }

    // Check if MFGPT is reserved for EVENT_PWM
    if(TimerPtr->Setup == PWM_SETUP) {
      continue;
    }

    // If timer is enabled and expired...
    Status &= MFGPT_ENABLE | MFGPT_COMPARE1;
    if (Status == (MFGPT_ENABLE | MFGPT_COMPARE1)) {
      // disable the timer
      out_16(Setup, Status & (~MFGPT_ENABLE));
      // record timer event
      Accum_Status |= TimerPtr->Mask;
    }
  }

  // Restore the MFGPT LBAR
  Restore_MFGPT_LBAR();

  return Accum_Status;
}

//************************************************************************************
// Initializes a single MFGPT
//************************************************************************************
void Init_Timer(TIMERS * TimerPtr)
{ USHORT Setup, TimerKHz;
  ULONG MicrosPerCount;

  TimerPtr->TimerBase = MFGPT_Base + TimerPtr->Timer*MFGPT_OFFSET;

  TimerPtr->Mask = 1 << TimerPtr->Timer;

  Setup = TimerPtr->Setup;

  // Compute microseconds / tick
  MicrosPerCount = 1000L << (Setup & 0xF);
  TimerKHz = 32;
  if (Setup & MFGPT_CLK_SEL) {
    TimerKHz = 14318;
  }
  TimerPtr->Period = (USHORT)(MicrosPerCount / TimerKHz);

  // Initialize the timer
  out_16(TimerPtr->TimerBase + MFGPT_SETUP, Setup);

  // NOTE: Counter resets to 0 when Counter == Comparator2 register
  out_32(TimerPtr->TimerBase + MFGPT_CMP1,  0xFFFFFFFF);

}

//***********************************************************************
// Marks a MFGPT as available
//***********************************************************************
void pascal MarkTimerAvailable(USHORT Timer)
{ USHORT i;

  for (i = 0; i < NUM_MFGPTS; i++) {
    if (TimerInfo[i].Timer == Timer) {
      TimerInfo[i].Interval = 0x00000000;
      return;
    }
  }

  Log_Error("Invalid timer: %d.", Timer);
}


//************************************************************************************
// Initializes the MFGPT timers to be used for EVENT_TIMER
//************************************************************************************
void Init_MFGPT(void)
{ ULONG MsrAddr, IRQ_Enables;
  USHORT i;
  register TIMERS * TimerPtr;

  Enable_MFGPT_LBAR();

  // Get current MFGPT IRQ mask
  MsrAddr = MDD_Base;
  (USHORT)MsrAddr = MSR_MFGPT_IRQ;
  IRQ_Enables = Read_MSR_LO(MsrAddr);

  for (i = 0; i < NUM_MFGPTS; i++) {

    TimerPtr = &TimerInfo[i];

    // Initialize a MFGPT
    Init_Timer(TimerPtr);

    // Mark timer as available
    MarkTimerAvailable(TimerPtr->Timer);

    // Route Compare 1 events to SMI
    IRQZ_Mapper(TimerPtr->Mapper, 2);

    // Enable timer event
    IRQ_Enables |= TimerPtr->Mask;

  }


  // Restore the MFGPT LBAR
  Restore_MFGPT_LBAR();


  // Enable MFGPT SMIs
  Write_MSR_LO(MsrAddr, IRQ_Enables);

} 



//***********************************************************************
// Disables a millisecond timer
//***********************************************************************
void DisableMsTimer_5536(USHORT Timer)
{ USHORT Setup, i;

  Enable_MFGPT_LBAR();


  for (i = 0; i < NUM_MFGPTS; i++) {
    if (TimerInfo[i].Timer == Timer) {
      // Mark timer as available
      MarkTimerAvailable(Timer);

      // Clear timer event and disable timer
      Setup = TimerInfo[i].TimerBase + MFGPT_SETUP;
      out_16(Setup, MFGPT_COMPARE1 | MFGPT_COMPARE2);
      break;
    }
  }

  // Restore the MFGPT LBAR
  Restore_MFGPT_LBAR();

}



//***********************************************************************
// Allocates a h/w timer for the specified interval
// Returns the timer # allocated else 0xFFFF
//***********************************************************************
USHORT AllocateTimer_5536(ULONG Interval, UCHAR Attributes)
{ UCHAR i, Exact = 0, Unused=0, Compat=0, Mask;
  register TIMERS * TimerPtr;

  // Find the MFGPT whose timebase is the best-match
  for (i = 0; i < NUM_MFGPTS; i++) {
    TimerPtr = &TimerInfo[i];

    // Don't use timers reserved for EVENT_PWM
    if (TimerPtr->Setup == PWM_SETUP) {
      continue;
    }

    if (Attributes & (FOR_STANDBY >> 24)) {
      // Timers used for PM must be in the 32 KHz domain
      if (TimerPtr->Setup & MFGPT_CLK_SEL) {
        continue;
      }
    } else {
      if (TimerPtr->Timer >= 6) {
        // Timers 6 & 7 are reserved for PM
        continue;
      }
    }
    Mask = (UCHAR)TimerPtr->Mask;

    // Is timer unused?
    if (TimerPtr->Interval == 0x00000000) {
      Unused |= Mask;
	}

    // Is timebase a perfect match?
    if ((Interval*1000 % TimerPtr->Period) == 0) {
      Exact |= Mask;
    }

    // Is timebase compatible?
    if (Interval*1000 >= TimerPtr->Period) {
      Compat |= Mask;
    }
  }

  // Is there an UNUSED timer whose timebase is an exact match?
  if (!(Mask = (Unused & Exact))) {
    // No, is there an UNUSED timer that is compatible?
    if (!(Mask = (Unused & Compat))) {
      // No, is there ANY timer whose timebase is an exact match?
      if (!(Mask = Exact)) {
        // No, is there ANY timer that is compatible?
        if (!(Mask = Compat)) {
          // No timers are available
          return 0xFFFF;
        }
      }
    }
  }

  // Return the MFGPT index
  Mask = 1 << BitScanReverse(Mask);
  for (i = 0; i < NUM_MFGPTS; i++) {
    if (TimerInfo[i].Mask == Mask) {
      return i;
    }
  }
  return 0xFFFF;
}


//***********************************************************************
// Enables a millisecond timer
//***********************************************************************
UCHAR EnableMsTimer_5536(ULONG Interval, UCHAR Attributes)
{ USHORT i, Count, TimerBase;
  ULONG MsrAddr, IRQ_Enables, TotalCounts, Roundoff;
  register TIMERS * TimerPtr;

  // Get current MFGPT IRQ mask
  MsrAddr = MDD_Base;
  (USHORT)MsrAddr = MSR_MFGPT_IRQ;
  IRQ_Enables = Read_MSR_LO(MsrAddr);

  // Allocate a h/w timer
  i = AllocateTimer_5536(Interval, Attributes);
  if (i == 0xFFFF) {
    Log_Error("Attempt to enable %d ms timer interval failed", Interval);
    return 0;
  }

  TimerPtr = &TimerInfo[i];

  // A timer was allocated, is it already in use?
  if (TimerPtr->Interval) {
    // Yes, does it need to be reprogrammed?
    if (Interval > TimerPtr->Interval) {
      // No, but since the timer has already been running for an
      // indeterminate period, the 1st interval will be shortened.
      return (UCHAR)TimerPtr->Timer;
    }
  }


  if (Interval) {

    Enable_MFGPT_LBAR();

    // Get I/O address of this timer
    TimerBase = TimerPtr->TimerBase;

    // The MFGPT document specifies the following sequence in order
    // to prevent any spurious resets, interrupt, or output events:
    // - Set Counter Enable bit to 0.
    // - Clear SMI enable in MSR_MFGPT_IRQ
    // - Update Count as desired
    // - When updates are completed, clear event bits
    // - Set SMI enable in MSR_MFGPT_IRQ
    // - Set Counter Enable bit to 1

    // Does this timer need to be initialized again (e.g. after S3) ?
    if (!(in_16(TimerBase + MFGPT_SETUP) & MFGPT_INITED)) {
      // Initialize the timer
      Init_Timer(TimerPtr);
    }

    // Set Counter Enable to 0
    out_16(TimerBase + MFGPT_SETUP, 0);

    // Disable SMIs
    Write_MSR_LO(MsrAddr, 0x00000000);

    // Scale the interval appropriately
    Roundoff = 0;
    if (TimerPtr->Period < 1000) {
      Roundoff = TimerPtr->Period;
    }
    TotalCounts = (Interval*1000 + Roundoff)/TimerPtr->Period;
    Count = (USHORT)TotalCounts;
    if (TotalCounts & 0xFFFF0000) {
	  Count = 0xFFFF;
    }

    // Record the timer's interval
    TimerPtr->Interval = ((ULONG)Count * TimerPtr->Period)/1000;

    // Zero the counter
    out_16(TimerBase + MFGPT_COUNTER, 0x0000);

    // Set Comparator1 to Count and Comparator2 to 0xFFFF
    out_32(TimerBase + MFGPT_CMP1, 0xFFFF0000L | Count);

    // Clear timer event(s)
    out_16(TimerBase + MFGPT_SETUP, MFGPT_COMPARE1 | MFGPT_COMPARE2);

    // Re-enable SMIs
    Write_MSR_LO(MsrAddr, IRQ_Enables);

    // Set Counter Enable to 1
    out_16(TimerBase + MFGPT_SETUP, MFGPT_ENABLE);

    // Restore the MFGPT LBAR
    Restore_MFGPT_LBAR();
  }

  return (UCHAR)TimerPtr->Timer;
}


//***********************************************************************
// Configures a MFGPT as a PWM signal generator
//***********************************************************************
void pascal Program_PWM(UCHAR Gpio, UCHAR Duty, USHORT Rate, UCHAR EnableFlag)
{ USHORT i, Timer;
  register TIMERS * TimerPtr;

  // Determine the MFGPT associated with the specified GPIO
  switch (Gpio) {
    case 5:
      Timer = 0;
      break;
    case 6:
      Timer = 1;
      break;
    case 7:
      Timer = 2;
      break;
    case 27:
      Timer = 7;
      break;
    default:
      Log_Error("EVENT_PWM is not supported on GPIO%d", Gpio);
      return;
  }

  // Program the MFGPT to the specified rate and duty cycle
  for (i = 0; i < NUM_MFGPTS; i++) {

    TimerPtr = &TimerInfo[i];

    if (TimerPtr->Timer == Timer) {

      // Check if MFGPT is reserved for EVENT_PWM
      if(TimerPtr->Setup != PWM_SETUP) {
        continue;
      }

      // Enable the MFGPT LBAR
      Enable_MFGPT_LBAR();

      // Clear this timer's SETUP register in case it has been used previously as a timer
      // Write_MSR_LO(MDD_Base + MSR_MFGPT_CLR_SETUP, TimerPtr->Mask);
      // out_16(TimerPtr->TimerBase + MFGPT_SETUP, PWM_SETUP);


      // Clamp duty cycle to 100%
      if (Duty > 100) {
        Duty = 100;
      }

      // Program the Count registers
      out_16(TimerPtr->TimerBase + MFGPT_COUNTER, 0x0000);
      out_16(TimerPtr->TimerBase + MFGPT_CMP1, (USHORT)(((ULONG)Rate * Duty)/100));
      out_16(TimerPtr->TimerBase + MFGPT_CMP2,  Rate);

      // Enable the counter
      out_16(TimerPtr->TimerBase + MFGPT_SETUP, EnableFlag ? MFGPT_ENABLE: 0);

      Restore_MFGPT_LBAR();

      return;
	}
  }

  Log_Error("EVENT_PWM on GPIO%d failed because MFGPT%d is reserved.", Gpio, Timer);
}

