/**@file
  Provide the SMBUS ASL methods for BIOS usage.
  Note the code requested here illegally consumes PCI IO resources and
  collides with some runtime SMBUS driver.
  The only valid solution is to retire this code and provide native SMBUS
  driver with SMBUS operation region support implemented for ACPI usage.

Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

//
// Define various SMBus PCI Configuration Space Registers.
//
OperationRegion(SMBP, PCI_Config, 0x0, 0xC0)
Field(SMBP,DWordAcc,NoLock,Preserve)
{
  Offset(0x20),  // SMBus Base Address
      , 5,
  SBAR, 11,
  Offset(0x40), // Host Configuration
      , 2,
  I2CE, 1,      // I2C_EN
}

//
// Define various SMBus IO Mapped Registers.
//
OperationRegion(SMBI,SystemIO,ShiftLeft(SBAR,5),0x10)
Field(SMBI,ByteAcc,NoLock,Preserve)
{
  HSTS, 8,  // 0 - Host Status Register
  Offset(0x02),
  HCON, 8,  // 2 - Host Control
  HCOM, 8,  // 3 - Host Command
  TXSA, 8,  // 4 - Transmit Slave Address
  DAT0, 8,  // 5 - Host Data 0
  DAT1, 8,  // 6 - Host Data 1
  HBDR, 8,  // 7 - Host Block Data
  PECR, 8,  // 8 - Packer Error Check
  RXSA, 8,  // 9 - Receive Slave Address
  SDAT, 16, // A - Slave Data
}

// SMBus Send Byte - This function will write a single byte of
// data to a specific Slave Device per SMBus Send Byte Protocol.
//  Arg0 = Address
//  Arg1 = Data
//  Return: Success = 1
//          Failure = 0
Method(SSXB,2,Serialized)
{
  // Step 1:  Confirm the ICHx SMBus is ready to perform
  // communication.

  If(STRT())
  {
    Return(0)
  }

  // Step 2:  Initiate a Send Byte.
  Store(0,I2CE)       // Ensure SMbus Mode.
  Store(0xBF,HSTS)    // Clear all but INUSE_STS.
  Store(Arg0,TXSA)    // Write Address in TXSA.
  Store(Arg1,HCOM)    // Data in HCOM.

  // Set the SMBus Host control register to 0x48.
  //   Bit 7:    =  0  = reserved
  //   Bit 6:    =  1  = start
  //   Bit 5:    =  0  = disregard, I2C related bit
  //   Bits 4:2: = 001 = Byte Protocol
  //   Bit 1:    =  0  = Normal Function
  //   Bit 0:    =  0  = Disable interrupt generation
  Store(0x48,HCON)

  // Step 3:  Exit the Method correctly.
  If(COMP)
  {
    Or(HSTS,0xFF,HSTS)    // Clear INUSE_STS and others..
    Return(1)             // Return Success.
  }

  Return(0)
}

// SMBus Receive Byte - This function will write a single byte
// of data to a specific Slave Device per SMBus Receive Byte
// Protocol.
//  Arg0 = Address
//  Return: Success = Byte-Size Value
//          Failure = Word-Size Value = FFFFh.
Method(SRXB,1,Serialized)
{
  // Step 1:  Confirm the ICHx SMBus is ready to perform
  // communication.
  If(STRT())
  {
    Return(0xFFFF)
  }

  // Step 2:  Initiate a Receive Byte.
  Store(0,I2CE)             // Ensure SMbus Mode.
  Store(0xBF,HSTS)          // Clear all but INUSE_STS.
  Store(Or(Arg0,1),TXSA)    // Read Address in TXSA.

  // Set the SMBus Host control register to 0x48.
  //   Bit 7:    =  0  = reserved
  //   Bit 6:    =  1  = start
  //   Bit 5:    =  0  = disregard, I2C related bit
  //   Bits 4:2: = 001 = Byte Protocol
  //   Bit 1:    =  0  = Normal Function
  //   Bit 0:    =  0  = Disable interrupt generation
  Store(0x44,HCON)

  // Step 3:  Exit the Method correctly.
  If(COMP)
  {
    Or(HSTS,0xFF,HSTS)    // Clear INUSE_STS and others
    Return(DAT0)          // Return Success.
  }

  Return(0xFFFF)        // Return Failure.
}

// SMBus Write Byte - This function will write a single byte
// of data to a specific Slave Device per SMBus Write Byte
// Protocol.
//  Arg0 = Address
//  Arg1 = Command
//  Arg2 = Data
//  Return: Success = 1
//          Failure = 0
Method(SWRB,3,Serialized)
{
  // Step 1:  Confirm the ICHx SMBus is ready to perform communication.
  If(STRT())
  {
    Return(0)
  }

  // Step 2:  Initiate a Write Byte.
  Store(0,I2CE)       // Ensure SMbus Mode.
  Store(0xBF,HSTS)    // Clear all but INUSE_STS.
  Store(Arg0,TXSA)    // Write Address in TXSA.
  Store(Arg1,HCOM)    // Command in HCOM.
  Store(Arg2,DAT0)    // Data in DAT0.

  // Set the SMBus Host control register to 0x48.
  //   Bit 7:    =  0  = reserved
  //   Bit 6:    =  1  = start
  //   Bit 5:    =  0  = disregard, I2C related bit
  //   Bits 4:2: = 010 = Byte Data Protocol
  //   Bit 1:    =  0  = Normal Function
  //   Bit 0:    =  0  = Disable interrupt generation
  Store(0x48,HCON)

  // Step 3:  Exit the Method correctly.
  If(COMP)
  {
    Or(HSTS,0xFF,HSTS)    // Clear INUSE_STS and others..
    Return(1)             // Return Success.
  }

  Return(0)    // Return Failure.
}

// SMBus Read Byte - This function will read a single byte of data
// from a specific slave device per SMBus Read Byte Protocol.
//  Arg0 = Address
//  Arg1 = Command
//  Return: Success = Byte-Size Value
//          Failure = Word-Size Value
Method(SRDB,2,Serialized)
{
  // Step 1:  Confirm the ICHx SMBus is ready to perform communication.
  If(STRT())
  {
  Return(0xFFFF)
  }

  // Step 2:  Initiate a Read Byte.
  Store(0,I2CE)             // Ensure SMbus Mode.
  Store(0xBF,HSTS)          // Clear all but INUSE_STS.
  Store(Or(Arg0,1),TXSA)    // Read Address in TXSA.
  Store(Arg1,HCOM)          // Command in HCOM.

  // Set the SMBus Host control register to 0x48.
  //   Bit 7:    =  0  = reserved
  //   Bit 6:    =  1  = start
  //   Bit 5:    =  0  = disregard, I2C related bit
  //   Bits 4:2: = 010 = Byte Data Protocol
  //   Bit 1:    =  0  = Normal Function
  //   Bit 0:    =  0  = Disable interrupt generation
  Store(0x48,HCON)

  // Step 3:  Exit the Method correctly.
  If(COMP)
  {
    Or(HSTS,0xFF,HSTS)    // Clear INUSE_STS and others..
    Return(DAT0)          // Return Success.
  }

  Return(0xFFFF)    // Return Failure.
}

// SMBus Write Word - This function will write a single word
// of data to a specific Slave Device per SMBus Write Word
// Protocol.
//  Arg0 = Address
//  Arg1 = Command
//  Arg2 = Data (16 bits in size)
//  Return: Success = 1
//          Failure = 0
Method(SWRW,3,Serialized)
{
  // Step 1:  Confirm the ICHx SMBus is ready to perform communication.
  If(STRT())
  {
    Return(0)
  }

  // Step 2:  Initiate a Write Word.
  Store(0,I2CE)                        // Ensure SMbus Mode.
  Store(0xBF,HSTS)                     // Clear all but INUSE_STS.
  Store(Arg0,TXSA)                     // Write Address in TXSA.
  Store(Arg1,HCOM)                     // Command in HCOM.
  And(Arg2,0xFF,DAT1)                  // Low byte Data in DAT1.
  And(ShiftRight(Arg2,8),0xFF,DAT0)    // High byte Data in DAT0.

  // Set the SMBus Host control register to 0x4C.
  //   Bit 7:    =  0  = reserved
  //   Bit 6:    =  1  = start
  //   Bit 5:    =  0  = disregard, I2C related bit
  //   Bits 4:2: = 011 = Word Data Protocol
  //   Bit 1:    =  0  = Normal Function
  //   Bit 0:    =  0  = Disable interrupt generation
  Store(0x4C,HCON)

  // Step 3:  Exit the Method correctly.
  If(COMP())
  {
    Or(HSTS,0xFF,HSTS)    // Clear INUSE_STS and others.
    Return(1)             // Return Success.
  }

  Return(0)    // Return Failure.
}

// SMBus Read Word - This function will read a single byte of data
// from a specific slave device per SMBus Read Word Protocol.
//  Arg0 = Address
//  Arg1 = Command
//  Return: Success = Word-Size Value
//          Failure = Dword-Size Value
Method(SRDW,2,Serialized)
{
  // Step 1:  Confirm the ICHx SMBus is ready to perform communication.
  If(STRT())
  {
  Return(0xFFFF)
  }

  // Step 2:  Initiate a Read Word.
  Store(0,I2CE)             // Ensure SMbus Mode.
  Store(0xBF,HSTS)          // Clear all but INUSE_STS.
  Store(Or(Arg0,1),TXSA)    // Read Address in TXSA.
  Store(Arg1,HCOM)          // Command in HCOM.

  // Set the SMBus Host control register to 0x4C.
  //   Bit 7:    =  0  = reserved
  //   Bit 6:    =  1  = start
  //   Bit 5:    =  0  = disregard, I2C related bit
  //   Bits 4:2: = 011 = Word Data Protocol
  //   Bit 1:    =  0  = Normal Function
  //   Bit 0:    =  0  = Disable interrupt generation
  Store(0x4C,HCON)

  // Step 3:  Exit the Method correctly.
  If(COMP())
  {
    Or(HSTS,0xFF,HSTS)                    // Clear INUSE_STS and others.
    Return(Or(ShiftLeft(DAT0,8),DAT1))    // Return Success.
  }

  Return(0xFFFFFFFF)    // Return Failure.
}

// SMBus Block Write - This function will write an entire block of data
// to a  specific slave device per SMBus Block Write Protocol.
//  Arg0 = Address
//  Arg1 = Command
//  Arg2 = Buffer of Data to Write
//  Arg3 = 1 = I2C Block Write, 0 = SMBus Block Write
//  Return:  Success = 1
//    Failure = 0
Method(SBLW,4,Serialized)
{
  // Step 1:  Confirm the ICHx SMBus is ready to perform communication.
  If(STRT())
  {
    Return(0)
  }

  // Step 2:  Initiate a Block Write.
  Store(Arg3,I2CE)                      // Select the proper protocol.
  Store(0xBF,HSTS)                      // Clear all but INUSE_STS.
  Store(Arg0,TXSA)                      // Write Address in TXSA.
  Store(Arg1,HCOM)                      // Command in HCOM.
  Store(Sizeof(Arg2),DAT0)              // Count in DAT0.
  Store(0,Local1)                       // Init Pointer to Buffer.
  Store(DerefOf(Index(Arg2,0)),HBDR)    // First Byte in HBD Register.

  // Set the SMBus Host control register to 0x48.
  //   Bit 7:    =  0  = reserved
  //   Bit 6:    =  1  = start
  //   Bit 5:    =  0  = disregard, I2C related bit
  //   Bits 4:2: = 101 = Block Protocol
  //   Bit 1:    =  0  = Normal Function
  //   Bit 0:    =  0  = Disable interrupt generation
  Store(0x54,HCON)

  // Step 3:  Send the entire Block of Data.
  While(LGreater(Sizeof(Arg2),Local1))
  {
    // Wait up to 200ms for Host Status to get set.
    Store(4000,Local0)    // 4000 * 50us = 200ms.

    While(LAnd(LNot(And(HSTS,0x80)),Local0))
    {
      Decrement(Local0)  // Decrement Count.
      Stall(50)          // Delay = 50us.
    }

    If(LNot(Local0))    // Timeout?
    {
      KILL()       // Yes.  Kill Communication.
      Return(0)    // Return failure.
    }

    Store(0x80,HSTS)     // Clear Host Status.
    Increment(Local1)    // Point to Next Byte.

    // Place next byte in HBDR if last byte has not been sent.
    If(LGreater(Sizeof(Arg2),Local1))
    {
      Store(DerefOf(Index(Arg2,Local1)),HBDR)
    }
  }

  // Step 4:  Exit the Method correctly.
  If(COMP())
  {
    Or(HSTS,0xFF,HSTS)    // Clear all status bits.
    Return(1)             // Return Success.
  }

  Return(0)    // Return Failure.
}

// SMBus Block Read - This function will read a block of data from
// a specific slave device per SMBus Block Read Protocol.
//  Arg0 = Address
//  Arg1 = Command
//  Arg2 = 1 = I2C Block Write, 0 = SMBus Block Write
//  Return: Success = Data Buffer (First Byte = length)
//          Failure = 0
Method(SBLR,3,Serialized)
{
  Name(TBUF, Buffer(256) {})

  // Step 1:  Confirm the ICHx SMBus is ready to perform communication.
  If(STRT())
  {
    Return(0)
  }

  // Step 2:  Initiate a Block Read.
  Store(Arg2,I2CE)          // Select the proper protocol.
  Store(0xBF,HSTS)          // Clear all but INUSE_STS.
  Store(Or(Arg0,1),TXSA)    // Read Address in TXSA.
  Store(Arg1,HCOM)          // Command in HCOM.

  // Set the SMBus Host control register to 0x48.
  //   Bit 7:    =  0  = reserved
  //   Bit 6:    =  1  = start
  //   Bit 5:    =  0  = disregard, I2C related bit
  //   Bits 4:2: = 101 = Block Protocol
  //   Bit 1:    =  0  = Normal Function
  //   Bit 0:    =  0  = Disable interrupt generation
  Store(0x54,HCON)

  // Step 3:  Wait up to 200ms to get the Data Count.
  Store(4000,Local0)     // 4000 * 50us = 200ms.

  While(LAnd(LNot(And(HSTS,0x80)),Local0))
  {
    Decrement(Local0)    // Decrement Count.
    Stall(50)            // Delay = 50us.
  }

  If(LNot(Local0))      // Timeout?
  {
    KILL()       // Yes. Kill Communication.
    Return(0)    // Return failure.
  }

  Store(DAT0,Index(TBUF,0))    // Get the Data Count.
  Store(0x80,HSTS)             // Clear Host Status.
  Store(1,Local1)              // Local1 = Buffer Pointer.

  // Step 4:  Get the Block Data and store it.
  While(LLess(Local1,DerefOf(Index(TBUF,0))))
  {
    // Wait up to 200ms for Host Status to get set.
    Store(4000,Local0)    // 4000 * 50us = 200ms.

    While(LAnd(LNot(And(HSTS,0x80)),Local0))
    {
      Decrement(Local0)  // Decrement Count.
      Stall(50)          // Delay = 50us.
    }

    If(LNot(Local0))     // Timeout?
    {
      KILL()       // Yes. Kill Communication.
      Return(0)    // Return failure.
    }

    Store(HBDR,Index(TBUF,Local1))  // Place into Buffer.
    Store(0x80,HSTS)                // Clear Host Status.
    Increment(Local1)
  }

  // Step 5:  Exit the Method correctly.
  If(COMP())
  {
    Or(HSTS,0xFF,HSTS)    // Clear INUSE_STS and others.
    Return(TBUF)          // Return Success.
  }

  Return(0)    // Return Failure.
}


// SMBus Start Check
//  Return:  Success = 0
//    Failure = 1
Method(STRT,0,Serialized)
{
  // Wait up to 200ms to confirm the SMBus Semaphore has been
  // released (In Use Status = 0).  Note that the Sleep time may take
  // longer as the This function will yield the Processor such that it
  // may perform different tasks during the delay.
  Store(200,Local0)      // 200 * 1ms = 200ms.

  While(Local0)
  {
    If(And(HSTS,0x40))      // In Use Set?
    {
      Decrement(Local0)     // Yes.  Decrement Count.
      Sleep(1)              // Delay = 1ms.
      If(LEqual(Local0,0))  // Count = 0?
      {
        Return(1)  // Return failure.
      }
    }
    Else
    {
      Store(0,Local0)    // In Use Clear.  Continue.
    }
  }

  // In Use Status = 0 during last read, which will make subsequent
  // reads return In Use Status = 1 until software clears it.  All
  // software using ICHx SMBus should check this bit before initiating
  // any SMBus communication.

  // Wait up to 200ms to confirm the Host Interface is
  // not processing a command.
  Store(4000,Local0)      // 4000 * 50us = 200ms.

  While(Local0)
  {
    If(And(HSTS,0x01))    // Host Busy Set?
    {
      Decrement(Local0)       // Decrement Count.
      Stall(50)               // Delay = 50us.
      If(LEqual(Local0,0))    // Count = 0?
      {
        KILL()    // Yes. Kill Communication.
      }
    }
    Else
    {
      Return(0)
    }
  }

  Return(1)    // Timeout. Return failure.
}

// SMBus Completion Check
//  Return:  Success = 1
//    Failure = 0
Method(COMP,0,Serialized)
{
  // Wait for up to 200ms for the Completion Command
  // Status to get set.
  Store(4000,Local0)      // 4000 * 50us = 200ms.

  While(Local0)
  {
    If(And(HSTS,0x02))    // Completion Status Set?
    {
      Return(1)    // Yes. We are done.
    }
    Else
    {
      Decrement(Local0)       // Decrement Count.
      Stall(50)               // Delay 50us.
      If(LEqual(Local0,0))    // Count = 0?
      {
        KILL()    // Yes. Kill Communication.
      }
    }
  }

  Return(0)    // Timeout.  Return Failure.
}

// SMBus Kill Command
Method(KILL,0,Serialized)
{
  Or(HCON,0x02,HCON)    // Yes. Send Kill command.
  Or(HSTS,0xFF,HSTS)    // Clear all status.
}
