/**
 * @file
 *
 * Config Southbridge HD Audio Controller
 *
 *
 *
 * @xrefitem bom "File Content Label" "Release Content"
 * @e project:      CIMx-SB
 * @e sub-project:
 * @e \$Revision:$   @e \$Date:$
 *
 */

/*
 *****************************************************************************
 *
 * Copyright (c) 2011, Advanced Micro Devices, Inc.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Advanced Micro Devices, Inc. nor the names of 
 *       its contributors may be used to endorse or promote products derived 
 *       from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL ADVANCED MICRO DEVICES, INC. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * ***************************************************************************
 *
 */


#include "SBPLATFORM.h"
#include "cbtypes.h"

//
// Declaration of local functions
//

VOID configureAzaliaPinCmd (IN AMDSBCFG* pConfig, IN UINT32 ddBAR0, IN UINT8 dbChannelNum);
VOID configureAzaliaSetConfigD4Dword (IN CODECENTRY* tempAzaliaCodecEntryPtr, IN UINT32 ddChannelNum, IN UINT32 ddBAR0);

/**
 * Pin Config for ALC880, ALC882 and ALC883.
 *
 *
 *
 */
const static CODECENTRY AzaliaCodecAlc882Table[] =
{
  {0x14, 0x01014010},
  {0x15, 0x01011012},
  {0x16, 0x01016011},
  {0x17, 0x01012014},
  {0x18, 0x01A19030},
  {0x19, 0x411111F0},
  {0x1a, 0x01813080},
  {0x1b, 0x411111F0},
  {0x1C, 0x411111F0},
  {0x1d, 0x411111F0},
  {0x1e, 0x01441150},
  {0x1f, 0x01C46160},
  {0xff, 0xffffffff}
};

/**
 * Pin Config for ALC0262.
 *
 *
 *
 */
const static CODECENTRY AzaliaCodecAlc262Table[] =
{
  {0x14, 0x01014010},
  {0x15, 0x411111F0},
  {0x16, 0x411111F0},
  {0x18, 0x01A19830},
  {0x19, 0x02A19C40},
  {0x1a, 0x01813031},
  {0x1b, 0x02014C20},
  {0x1c, 0x411111F0},
  {0x1d, 0x411111F0},
  {0x1e, 0x0144111E},
  {0x1f, 0x01C46150},
  {0xff, 0xffffffff}
};

/**
 * Pin Config for ALC0269.
 *
 *
 *
 */
const static CODECENTRY AzaliaCodecAlc269Table[] =
{
  {0x12, 0x99A30960},
  {0x14, 0x99130110},
  {0x15, 0x0221401F},
  {0x16, 0x99130120},
  {0x18, 0x01A19850},
  {0x19, 0x02A15951},
  {0x1a, 0x01813052},
  {0x1b, 0x0181405F},
  {0x1d, 0x40134601},
  {0x1e, 0x01441130},
  {0x11, 0x18567140},
  {0x20, 0x0030FFFF},
  {0xff, 0xffffffff}
};

/**
 * Pin Config for ALC0861.
 *
 *
 *
 */
const static CODECENTRY AzaliaCodecAlc861Table[] =
{
  {0x01, 0x8086C601},
  {0x0B, 0x01014110},
  {0x0C, 0x01813140},
  {0x0D, 0x01A19941},
  {0x0E, 0x411111F0},
  {0x0F, 0x02214420},
  {0x10, 0x02A1994E},
  {0x11, 0x99330142},
  {0x12, 0x01451130},
  {0x1F, 0x411111F0},
  {0x20, 0x411111F0},
  {0x23, 0x411111F0},
  {0xff, 0xffffffff}
};

/**
 * Pin Config for ALC0889.
 *
 *
 *
 */
const static CODECENTRY AzaliaCodecAlc889Table[] =
{
  {0x11, 0x411111F0},
  {0x14, 0x01014010},
  {0x15, 0x01011012},
  {0x16, 0x01016011},
  {0x17, 0x01013014},
  {0x18, 0x01A19030},
  {0x19, 0x411111F0},
  {0x1a, 0x411111F0},
  {0x1b, 0x411111F0},
  {0x1C, 0x411111F0},
  {0x1d, 0x411111F0},
  {0x1e, 0x01442150},
  {0x1f, 0x01C42160},
  {0xff, 0xffffffff}
};

/**
 * Pin Config for ADI1984.
 *
 *
 *
 */
const static CODECENTRY AzaliaCodecAd1984Table[] =
{
  {0x11, 0x0221401F},
  {0x12, 0x90170110},
  {0x13, 0x511301F0},
  {0x14, 0x02A15020},
  {0x15, 0x50A301F0},
  {0x16, 0x593301F0},
  {0x17, 0x55A601F0},
  {0x18, 0x55A601F0},
  {0x1A, 0x91F311F0},
  {0x1B, 0x014511A0},
  {0x1C, 0x599301F0},
  {0xff, 0xffffffff}
};

/**
 * FrontPanel Config table list
 *
 *
 *
 */
const static CODECENTRY FrontPanelAzaliaCodecTableList[] =
{
  {0x19, 0x02A19040},
  {0x1b, 0x02214020},
  {0xff, 0xffffffff}
};

/**
 * Current HD Audio support codec list
 *
 *
 *
 */
const static CODECTBLLIST azaliaCodecTableList[] =
{
  {0x010ec0880, (CODECENTRY*)&AzaliaCodecAlc882Table[0]},
  {0x010ec0882, (CODECENTRY*)&AzaliaCodecAlc882Table[0]},
  {0x010ec0883, (CODECENTRY*)&AzaliaCodecAlc882Table[0]},
  {0x010ec0885, (CODECENTRY*)&AzaliaCodecAlc882Table[0]},
  {0x010ec0889, (CODECENTRY*)&AzaliaCodecAlc889Table[0]},
  {0x010ec0262, (CODECENTRY*)&AzaliaCodecAlc262Table[0]},
  {0x010ec0269, (CODECENTRY*)&AzaliaCodecAlc269Table[0]},
  {0x010ec0861, (CODECENTRY*)&AzaliaCodecAlc861Table[0]},
  {0x011d41984, (CODECENTRY*)&AzaliaCodecAd1984Table[0]},
  { (UINT32) 0x0FFFFFFFF, (CODECENTRY*) (UINTN)0x0FFFFFFFF}
};

/**
 * azaliaInitBeforePciEnum - Config HD Audio Before PCI emulation
 *
 *
 *
 * @param[in] pConfig Southbridge configuration structure pointer.
 *
 */
VOID
azaliaInitBeforePciEnum (
  IN       AMDSBCFG* pConfig
  )
{
  if ( pConfig->AzaliaController == 1 ) {
    RWMEM (ACPI_MMIO_BASE + PMIO_BASE + SB_PMIOA_REGEB, AccWidthUint8, ~BIT0, 0);
  } else {
    RWMEM (ACPI_MMIO_BASE + PMIO_BASE + SB_PMIOA_REGEB, AccWidthUint8, ~BIT0, BIT0);
    if ( pConfig->BuildParameters.HdAudioMsi) {
      RWPCI ((AZALIA_BUS_DEV_FUN << 16) + SB_AZ_REG44, AccWidthUint32 | S3_SAVE, ~BIT8, BIT8);
      RWPCI ((AZALIA_BUS_DEV_FUN << 16) + SB_AZ_REG60, AccWidthUint32 | S3_SAVE, ~BIT16, BIT16);
    }
  }
}

/**
 * azaliaInitAfterPciEnum - Config HD Audio after PCI emulation
 *
 *
 *
 * @param[in] pConfig Southbridge configuration structure pointer.
 *
 */
VOID
azaliaInitAfterPciEnum (
  IN       AMDSBCFG* pConfig
  )
{
  UINT8   Data;
  UINT8   i;
  UINT8   dbEnableAzalia;
  UINT8   dbPinRouting;
  UINT8   dbChannelNum;
  UINT8   dbTempVariable;
  UINT16   dwTempVariable;
  UINT32   ddBAR0;
  UINT32   ddTempVariable;
  dbEnableAzalia = 0;
  dbChannelNum = 0;
  dbTempVariable = 0;
  dwTempVariable = 0;
  ddBAR0 = 0;
  ddTempVariable = 0;

  if ( pConfig->AzaliaController == 1 ) {
    return;
  }

  if ( pConfig->AzaliaController != 1 ) {
    RWPCI ((AZALIA_BUS_DEV_FUN << 16) + SB_AZ_REG04, AccWidthUint8 | S3_SAVE, ~BIT1, BIT1);
    if ( pConfig->BuildParameters.AzaliaSsid != NULL ) {
      RWPCI ((AZALIA_BUS_DEV_FUN << 16) + SB_AZ_REG2C, AccWidthUint32 | S3_SAVE, 0x00, pConfig->BuildParameters.AzaliaSsid);
    } 
    ReadPCI ((AZALIA_BUS_DEV_FUN << 16) + SB_AZ_REG10, AccWidthUint32, &ddBAR0);
    if ( ddBAR0 != 0 ) {
      if ( ddBAR0 != 0xFFFFFFFF ) {
        ddBAR0 &=  ~(0x03FFF);
        dbEnableAzalia = 1;
      }
    }
  }

  if ( dbEnableAzalia ) {
    // Get SDIN Configuration
    if ( pConfig->AZALIACONFIG.AzaliaConfig.AzaliaSdin0 == 2 ) {
      RWMEM (ACPI_MMIO_BASE + GPIO_BASE + SB_GPIO_REG167, AccWidthUint8, 0, 0x3E);
      RWMEM (ACPI_MMIO_BASE + IOMUX_BASE + SB_GPIO_REG167, AccWidthUint8, 0, 0x00);
    } else {
      RWMEM (ACPI_MMIO_BASE + GPIO_BASE + SB_GPIO_REG167, AccWidthUint8, 0, 0x0);
      RWMEM (ACPI_MMIO_BASE + IOMUX_BASE + SB_GPIO_REG167, AccWidthUint8, 0, 0x01);
    }
    if ( pConfig->AZALIACONFIG.AzaliaConfig.AzaliaSdin1 == 2 ) {
      RWMEM (ACPI_MMIO_BASE + GPIO_BASE + SB_GPIO_REG168, AccWidthUint8, 0, 0x3E);
      RWMEM (ACPI_MMIO_BASE + IOMUX_BASE + SB_GPIO_REG168, AccWidthUint8, 0, 0x00);
    } else {
      RWMEM (ACPI_MMIO_BASE + GPIO_BASE + SB_GPIO_REG168, AccWidthUint8, 0, 0x0);
      RWMEM (ACPI_MMIO_BASE + IOMUX_BASE + SB_GPIO_REG168, AccWidthUint8, 0, 0x01);
    }
    if ( pConfig->AZALIACONFIG.AzaliaConfig.AzaliaSdin2 == 2 ) {
      RWMEM (ACPI_MMIO_BASE + GPIO_BASE + SB_GPIO_REG169, AccWidthUint8, 0, 0x3E);
      RWMEM (ACPI_MMIO_BASE + IOMUX_BASE + SB_GPIO_REG169, AccWidthUint8, 0, 0x00);
    } else {
      RWMEM (ACPI_MMIO_BASE + GPIO_BASE + SB_GPIO_REG169, AccWidthUint8, 0, 0x0);
      RWMEM (ACPI_MMIO_BASE + IOMUX_BASE + SB_GPIO_REG169, AccWidthUint8, 0, 0x01);
    }
    if ( pConfig->AZALIACONFIG.AzaliaConfig.AzaliaSdin3 == 2 ) {
      RWMEM (ACPI_MMIO_BASE + GPIO_BASE + SB_GPIO_REG170, AccWidthUint8, 0, 0x3E);
      RWMEM (ACPI_MMIO_BASE + IOMUX_BASE + SB_GPIO_REG170, AccWidthUint8, 0, 0x00);
    } else {
      RWMEM (ACPI_MMIO_BASE + GPIO_BASE + SB_GPIO_REG170, AccWidthUint8, 0, 0x0);
      RWMEM (ACPI_MMIO_BASE + IOMUX_BASE + SB_GPIO_REG170, AccWidthUint8, 0, 0x01);
    }
    // INT#A Azalia resource
    Data = 0x93; // Azalia APIC index
    WriteIO (SB_IOMAP_REGC00, AccWidthUint8, &Data);
    Data = 0x10; // IRQ16 (INTA#)
    WriteIO (SB_IOMAP_REGC01, AccWidthUint8, &Data);

    i = 11;
    do {
      ReadMEM ( ddBAR0 + SB_AZ_BAR_REG08, AccWidthUint8 | S3_SAVE, &dbTempVariable);
      dbTempVariable |= BIT0;
      WriteMEM (ddBAR0 + SB_AZ_BAR_REG08, AccWidthUint8 | S3_SAVE, &dbTempVariable);
      SbStall (1000);
      ReadMEM (ddBAR0 + SB_AZ_BAR_REG08, AccWidthUint8 | S3_SAVE, &dbTempVariable);
      i--;
    }  while ((! (dbTempVariable & BIT0)) && (i > 0) );

    if ( i == 0 ) {
      return;
    }

    SbStall (1000);
    ReadMEM ( ddBAR0 + SB_AZ_BAR_REG0E, AccWidthUint16, &dwTempVariable);
    if ( dwTempVariable & 0x0F ) {

      //atleast one azalia codec found
      // ?? E0 is not real register what we expect. we have change to GPIO/and program GPIO Mux
      //ReadMEM (ACPI_MMIO_BASE + PMIO_BASE + SB_PMIOA_REGE0, AccWidthUint8, &dbPinRouting);
      dbPinRouting = pConfig->AZALIACONFIG.AzaliaSdinPin;
      do {
        if ( ( ! (dbPinRouting & BIT0) ) && (dbPinRouting & BIT1) ) {
//          dbChannelNum = 3;
          configureAzaliaPinCmd (pConfig, ddBAR0, dbChannelNum);
        }
        dbPinRouting >>= 2;
        dbChannelNum++;
      }  while ( dbChannelNum != 4 );
    } else {
      //No Azalia codec found
      if ( pConfig->AzaliaController != 2 ) {
        dbEnableAzalia = 0;     //set flag to disable Azalia
      }
    }
  }

  if ( dbEnableAzalia ) {
    //redo clear reset
    do {
      dwTempVariable = 0;
      WriteMEM ( ddBAR0 + SB_AZ_BAR_REG0C, AccWidthUint16 | S3_SAVE, &dwTempVariable);
      ReadMEM (ddBAR0 + SB_AZ_BAR_REG08, AccWidthUint8 | S3_SAVE, &dbTempVariable);
      dbTempVariable &= ~(BIT0);
      WriteMEM (ddBAR0 + SB_AZ_BAR_REG08, AccWidthUint8 | S3_SAVE, &dbTempVariable);
      ReadMEM (ddBAR0 + SB_AZ_BAR_REG08, AccWidthUint8 | S3_SAVE, &dbTempVariable);
    }  while ( dbTempVariable & BIT0 );

    if ( pConfig->AzaliaSnoop == 1 ) {
      RWPCI ((AZALIA_BUS_DEV_FUN << 16) + SB_AZ_REG42, AccWidthUint8 | S3_SAVE, 0xFF, BIT1 + BIT0);
    }
  } else {
    //disable Azalia controller
    RWPCI ((AZALIA_BUS_DEV_FUN << 16) + SB_AZ_REG04, AccWidthUint16 | S3_SAVE, 0, 0);
    // RWPMIO (SB_PMIO_REG59, AccWidthUint8 | S3_SAVE, ~BIT3, 0);
    RWMEM (ACPI_MMIO_BASE + PMIO_BASE + SB_PMIOA_REGEB, AccWidthUint8, ~BIT0, 0);
    // RWPCI ((SMBUS_BUS_DEV_FUN << 16) + SB_SMBUS_REGFC, AccWidthUint8 | S3_SAVE, 0, 0x55);
    RWMEM (ACPI_MMIO_BASE + PMIO_BASE + SB_PMIOA_REGEB, AccWidthUint8, ~BIT0, 0);
  }
}

/**
 * configureAzaliaPinCmd - Configuration HD Audio PIN Command
 *
 *
 * @param[in] pConfig              Southbridge configuration structure pointer.
 * @param[in] ddBAR0               HD Audio BAR0 base address.
 * @param[in] dbChannelNum         Channel Number.
 *
 */
VOID
configureAzaliaPinCmd (
  IN       AMDSBCFG* pConfig,
  IN       UINT32 ddBAR0,
  IN       UINT8 dbChannelNum
  )
{
  UINT32   ddTempVariable;
  UINT32   ddChannelNum;
  CODECTBLLIST*  ptempAzaliaOemCodecTablePtr;
  CODECENTRY* tempAzaliaCodecEntryPtr;

  if ( (pConfig->AzaliaPinCfg) != 1 ) {
    return;
  }

  ddChannelNum = dbChannelNum << 28;
  ddTempVariable = 0xF0000;
  ddTempVariable |= ddChannelNum;

  WriteMEM (ddBAR0 + SB_AZ_BAR_REG60, AccWidthUint32 | S3_SAVE, &ddTempVariable);
  SbStall (600);
  ReadMEM (ddBAR0 + SB_AZ_BAR_REG64, AccWidthUint32 | S3_SAVE, &ddTempVariable);

  if ( ((pConfig->AZOEMTBL.pAzaliaOemCodecTablePtr) == NULL) || ((pConfig->AZOEMTBL.pAzaliaOemCodecTablePtr) == ((CODECTBLLIST*) (UINTN)0xFFFFFFFF))) {
    ptempAzaliaOemCodecTablePtr = (CODECTBLLIST*) FIXUP_PTR (&azaliaCodecTableList[0]);
  } else {
    ptempAzaliaOemCodecTablePtr = (CODECTBLLIST*) pConfig->AZOEMTBL.pAzaliaOemCodecTablePtr;
  }

  while ( ptempAzaliaOemCodecTablePtr->CodecID != 0xFFFFFFFF ) {
    if ( ptempAzaliaOemCodecTablePtr->CodecID == ddTempVariable ) {
      break;
    } else {
      ++ptempAzaliaOemCodecTablePtr;
    }
  }

  if ( ptempAzaliaOemCodecTablePtr->CodecID != 0xFFFFFFFF ) {
    tempAzaliaCodecEntryPtr = (CODECENTRY*) ptempAzaliaOemCodecTablePtr->CodecTablePtr;

    if ( ((pConfig->AZOEMTBL.pAzaliaOemCodecTablePtr) == NULL) || ((pConfig->AZOEMTBL.pAzaliaOemCodecTablePtr) == ((CODECTBLLIST*) (UINTN)0xFFFFFFFF)) ) {
      tempAzaliaCodecEntryPtr = (CODECENTRY*) FIXUP_PTR (tempAzaliaCodecEntryPtr);
    }
    configureAzaliaSetConfigD4Dword (tempAzaliaCodecEntryPtr, ddChannelNum, ddBAR0);
    if ( pConfig->AzaliaFrontPanel != 1 ) {
      if ( (pConfig->AzaliaFrontPanel == 2) || (pConfig->FrontPanelDetected == 1) ) {
        if ( ((pConfig->AZOEMFPTBL.pAzaliaOemFpCodecTablePtr) == NULL) || ((pConfig->AZOEMFPTBL.pAzaliaOemFpCodecTablePtr) == (VOID*) (UINTN)0xFFFFFFFF) ) {
          tempAzaliaCodecEntryPtr = (CODECENTRY*) FIXUP_PTR (&FrontPanelAzaliaCodecTableList[0]);
        } else {
          tempAzaliaCodecEntryPtr = (CODECENTRY*) pConfig->AZOEMFPTBL.pAzaliaOemFpCodecTablePtr;
        }
        configureAzaliaSetConfigD4Dword (tempAzaliaCodecEntryPtr, ddChannelNum, ddBAR0);
      }
    }
  }
}

/**
 * configureAzaliaSetConfigD4Dword - Configuration HD Audio Codec table
 *
 *
 * @param[in] tempAzaliaCodecEntryPtr    HD Audio Codec table structure pointer.
 * @param[in] ddChannelNum               HD Audio Channel Number.
 * @param[in] ddBAR0                     HD Audio BAR0 base address.
 *
 */
VOID
configureAzaliaSetConfigD4Dword (
  IN       CODECENTRY* tempAzaliaCodecEntryPtr,
  IN       UINT32 ddChannelNum,
  IN       UINT32 ddBAR0
  )
{
  UINT8 dbtemp1;
  UINT8 dbtemp2;
  UINT8 i;
  UINT32 ddtemp;
  UINT32 ddtemp2;
  ddtemp = 0;
  ddtemp2 = 0;
  while ( (tempAzaliaCodecEntryPtr->Nid) != 0xFF ) {
    dbtemp1 = 0x20;
    if ( (tempAzaliaCodecEntryPtr->Nid) == 0x1 ) {
      dbtemp1 = 0x24;
    }

    ddtemp =  tempAzaliaCodecEntryPtr->Nid;
    ddtemp &= 0xff;
    ddtemp <<= 20;
    ddtemp |= ddChannelNum;

    ddtemp |= (0x700 << 8);
    for ( i = 4; i > 0; i-- ) {
      do {
        ReadMEM (ddBAR0 + SB_AZ_BAR_REG68, AccWidthUint32, &ddtemp2);
      } while ( ddtemp2 & BIT0 );

      dbtemp2 = (UINT8) (( (tempAzaliaCodecEntryPtr->Byte40) >> ((4 - i) * 8 ) ) & 0xff);
      ddtemp =  (ddtemp & 0xFFFF0000) + ((dbtemp1 - i) << 8) + dbtemp2;
      WriteMEM (ddBAR0 + SB_AZ_BAR_REG60, AccWidthUint32 | S3_SAVE, &ddtemp);
      SbStall (60);
    }
    ++tempAzaliaCodecEntryPtr;
  }
}