From: Bartosz Szczepanek bsz@semihalf.com
MvI2cDxe driver was adapted to generic UEFI I2C stack. Connection with following interfaces was required: - EFI_I2C_MASTER_PROTOCOL - EFI_I2C_BUS_CONFIGURATION_MANAGEMENT_PROTOCOL - EFI_I2C_ENUMERATE_PROTOCOL
Driver exports configuration options via PCDs. Configurable options include: - PcdI2cSlaveAddresses - should contain a list of valid I2C devices' addresses on bus - PcdI2cBaseAddresses - physical address of I2C controller registers - PcdI2cClockFrequency - I2c clock frequency on platform
Drivers of devices on I2C bus should never use EFI_I2C_MASTER_PROTOCOL directly. Instead, these ought to utilise EFI_I2C_IO_PROTOCOL produced by generic UEFI stack.
Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Bartosz Szczepanek bsz@semihalf.com Signed-off-by: Marcin Wojtas mw@semihalf.com --- Documentation/Marvell/Drivers/I2cDriver.txt | 64 +++ Documentation/Marvell/PortingGuide/I2c.txt | 20 + Drivers/I2c/MvI2cDxe/MvI2cDxe.c | 743 ++++++++++++++++++++++++++++ Drivers/I2c/MvI2cDxe/MvI2cDxe.h | 263 ++++++++++ Drivers/I2c/MvI2cDxe/MvI2cDxe.inf | 75 +++ Platforms/Marvell/Marvell.dec | 8 + 6 files changed, 1173 insertions(+) create mode 100644 Documentation/Marvell/Drivers/I2cDriver.txt create mode 100644 Documentation/Marvell/PortingGuide/I2c.txt create mode 100755 Drivers/I2c/MvI2cDxe/MvI2cDxe.c create mode 100644 Drivers/I2c/MvI2cDxe/MvI2cDxe.h create mode 100755 Drivers/I2c/MvI2cDxe/MvI2cDxe.inf
diff --git a/Documentation/Marvell/Drivers/I2cDriver.txt b/Documentation/Marvell/Drivers/I2cDriver.txt new file mode 100644 index 0000000..2f890de --- /dev/null +++ b/Documentation/Marvell/Drivers/I2cDriver.txt @@ -0,0 +1,64 @@ +1. Introduction +--------------- +**MvI2cDxe** is a driver supporting I2C controller on Marvell SOCs boards. +It is connected through protocols to generic UEFI I2C stack, which exposes +IO functionality to drivers of specific devices on I2C bus. + +2. MvI2cDxe driver design +-------------------------- +MvI2cDxe produces several protocols from generic I2C stack: + - EFI_I2C_MASTER_PROTOCOL, + - EFI_I2C_ENUMERATE_PROTOCOL, + - EFI_I2C_BUS_CONFIGURATION_MANAGEMENT_PROTOCOL + - general-purpose EFI_DRIVER_BINDING_PROTOCOL. + + 2.1 EFI_I2C_MASTER_PROTOCOL + --------------------------- + This is the most important protocol produced by MvI2cDxe. Following functions + are implemented: + + /// + /// Reset the I2C host controller. + /// + EFI_I2C_MASTER_PROTOCOL_RESET Reset; + + /// + /// Start an I2C transaction in master mode on the host controller. + /// + EFI_I2C_MASTER_PROTOCOL_START_REQUEST StartRequest; + + StartRequest and Reset functions are used by I2cHost. + These should **not** be used by I2C device drivers - required + synchronization is not provided. Instead, members of EFI_I2C_IO_PROTOCOL + should be used. + + 2.2 EFI_I2C_BUS_CONFIGURATION_MANAGEMENT_PROTOCOL + ------------------------------------------------- + The only function exposed via this protocol is MvI2cEnableConf. It is + required by I2C stack in order to allow changing I2C bus configuration from + device drivers. + + 2.3 EFI_I2C_ENUMERATE_PROTOCOL + ------------------------------ + Provides Enumerate function, which is used by I2cBus code as an iterator over + devices on I2C bus. + + typedef + EFI_STATUS + (EFIAPI *EFI_I2C_ENUMERATE_PROTOCOL_ENUMERATE) ( + IN CONST EFI_I2C_ENUMERATE_PROTOCOL *This, + IN OUT CONST EFI_I2C_DEVICE **Device + ); + + /// + /// Traverse the set of I2C devices on an I2C bus. This routine + /// returns the next I2C device on an I2C bus. + /// + EFI_I2C_ENUMERATE_PROTOCOL_ENUMERATE Enumerate; + + MvI2cDevice creates EFI_I2C_DEVICE structure for every device on the bus. + Due to the fact that hardware-based I2C enumeration isn't safe, information + about attached devices should be provided through PCDs. After EFI_I2C_DEVICE + structure is created and filled properly, it is returned to I2cBus. It is + followed by attachment of I2C device driver. + diff --git a/Documentation/Marvell/PortingGuide/I2c.txt b/Documentation/Marvell/PortingGuide/I2c.txt new file mode 100644 index 0000000..28ae1ef --- /dev/null +++ b/Documentation/Marvell/PortingGuide/I2c.txt @@ -0,0 +1,20 @@ +1. Porting I2C driver to a new SOC +---------------------------------- +In order to enable driver on a new platform, following steps need to be taken: + - add following line to .dsc file: + OpenPlatformPkg/Drivers/I2c/MvI2cDxe/MvI2cDxe.inf + - add following line to .fdf file: + INF OpenPlatformPkg/Drivers/I2c/MvI2cDxe/MvI2cDxe.inf + - add PCDs with relevant values to .dsc file: + gMarvellTokenSpaceGuid.PcdI2cSlaveAddresses|{ 0x50, 0x57 } + (addresses of I2C slave devices on bus) + gMarvellTokenSpaceGuid.PcdI2cSlaveBuses|{ 0x0, 0x0 } + (buses to which accoring slaves are attached) + gMarvellTokenSpaceGuid.PcdI2cBusCount|2 + (number of SoC's I2C buses) + gMarvellTokenSpaceGuid.PcdI2cBaseAddresses|L"0xF2701000;0xF2701100" + (base addresses of I2C controller buses) + gMarvellTokenSpaceGuid.PcdI2cClockFrequency|200000000 + (I2C host controller clock frequency) + gMarvellTokenSpaceGuid.PcdI2cBaudRate|100000 + (baud rate used in I2C transmission) diff --git a/Drivers/I2c/MvI2cDxe/MvI2cDxe.c b/Drivers/I2c/MvI2cDxe/MvI2cDxe.c new file mode 100755 index 0000000..02c5e8d --- /dev/null +++ b/Drivers/I2c/MvI2cDxe/MvI2cDxe.c @@ -0,0 +1,743 @@ +/******************************************************************************** +Copyright (C) 2016 Marvell International Ltd. + +Marvell BSD License Option + +If you received this File from Marvell, you may opt to use, redistribute and/or +modify this File under the following licensing terms. +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 Marvell 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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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 <Protocol/I2cMaster.h> +#include <Protocol/I2cEnumerate.h> +#include <Protocol/I2cBusConfigurationManagement.h> +#include <Protocol/DevicePath.h> + +#include <Library/BaseLib.h> +#include <Library/IoLib.h> +#include <Library/DebugLib.h> +#include <Library/PcdLib.h> +#include <Library/UefiLib.h> +#include <Library/ParsePcdLib.h> +#include <Library/MemoryAllocationLib.h> +#include <Library/UefiBootServicesTableLib.h> + +#include "MvI2cDxe.h" + +STATIC MV_I2C_BAUD_RATE baud_rate; + +MV_I2C_DEVICE_PATH MvI2cDevicePathProtocol = { + { + { + HARDWARE_DEVICE_PATH, + HW_VENDOR_DP, + { + (UINT8) (sizeof(VENDOR_DEVICE_PATH)), + (UINT8) (sizeof(VENDOR_DEVICE_PATH) >> 8), + }, + }, + EFI_CALLER_ID_GUID + }, + { + END_DEVICE_PATH_TYPE, + END_ENTIRE_DEVICE_PATH_SUBTYPE, + { + sizeof(EFI_DEVICE_PATH_PROTOCOL), + 0 + } + } +}; + +STATIC +UINT32 +I2C_READ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN UINTN off) +{ + ASSERT (I2cMasterContext != NULL); + return MmioRead32 (I2cMasterContext->BaseAddress + off); +} + +STATIC +EFI_STATUS +I2C_WRITE ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN UINTN off, + IN UINT32 Value) +{ + ASSERT (I2cMasterContext != NULL); + return MmioWrite32 (I2cMasterContext->BaseAddress + off, Value); +} + +EFI_STATUS +EFIAPI +MvI2cInitialiseController ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable, + IN EFI_PHYSICAL_ADDRESS BaseAddress + ) +{ + EFI_STATUS Status; + I2C_MASTER_CONTEXT *I2cMasterContext; + STATIC INTN Bus = 0; + MV_I2C_DEVICE_PATH *DevicePath; + + DevicePath = AllocateCopyPool (sizeof(MvI2cDevicePathProtocol), + &MvI2cDevicePathProtocol); + if (DevicePath == NULL) { + DEBUG((DEBUG_ERROR, "MvI2cDxe: I2C device path allocation failed\n")); + return EFI_OUT_OF_RESOURCES; + } + DevicePath->Guid.Guid.Data4[0] = Bus; + + /* if attachment succeeds, this gets freed at ExitBootServices */ + I2cMasterContext = AllocateZeroPool (sizeof (I2C_MASTER_CONTEXT)); + if (I2cMasterContext == NULL) { + DEBUG((DEBUG_ERROR, "MvI2cDxe: I2C master context allocation failed\n")); + return EFI_OUT_OF_RESOURCES; + } + I2cMasterContext->Signature = I2C_MASTER_SIGNATURE; + I2cMasterContext->I2cMaster.Reset = MvI2cReset; + I2cMasterContext->I2cMaster.StartRequest = MvI2cStartRequest; + I2cMasterContext->I2cEnumerate.Enumerate = MvI2cEnumerate; + I2cMasterContext->I2cBusConf.EnableI2cBusConfiguration = MvI2cEnableConf; + I2cMasterContext->TclkFrequency = PcdGet32 (PcdI2cClockFrequency); + I2cMasterContext->BaseAddress = BaseAddress; + I2cMasterContext->Bus = Bus; + /* I2cMasterContext->Lock is responsible for serializing I2C operations */ + EfiInitializeLock(&I2cMasterContext->Lock, TPL_NOTIFY); + + MvI2cCalBaudRate( I2cMasterContext, + PcdGet32 (PcdI2cBaudRate), + &baud_rate, + I2cMasterContext->TclkFrequency + ); + + Status = gBS->InstallMultipleProtocolInterfaces( + &I2cMasterContext->Controller, + &gEfiI2cMasterProtocolGuid, + &I2cMasterContext->I2cMaster, + &gEfiI2cEnumerateProtocolGuid, + &I2cMasterContext->I2cEnumerate, + &gEfiI2cBusConfigurationManagementProtocolGuid, + &I2cMasterContext->I2cBusConf, + &gEfiDevicePathProtocolGuid, + (EFI_DEVICE_PATH_PROTOCOL *) DevicePath, + NULL); + + if (EFI_ERROR(Status)) { + DEBUG((DEBUG_ERROR, "MvI2cDxe: Installing protocol interfaces failed!\n")); + goto fail; + } + DEBUG((DEBUG_ERROR, "Succesfully installed controller %d at 0x%llx\n", Bus, + I2cMasterContext->BaseAddress)); + + Bus++; + + return EFI_SUCCESS; + +fail: + FreePool(I2cMasterContext); + return Status; +} + +EFI_STATUS +EFIAPI +MvI2cInitialise ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + UINT32 BusCount; + EFI_PHYSICAL_ADDRESS I2cBaseAddresses[PcdGet32 (PcdI2cBusCount)]; + INTN i; + + BusCount = PcdGet32 (PcdI2cBusCount); + if (BusCount == 0) + return EFI_SUCCESS; + + Status = ParsePcdString ( + (CHAR16 *) PcdGetPtr (PcdI2cBaseAddresses), + BusCount, + I2cBaseAddresses, + NULL + ); + if (EFI_ERROR(Status)) + return Status; + + for (i = 0; i < BusCount; i++) { + Status = MvI2cInitialiseController( + ImageHandle, + SystemTable, + I2cBaseAddresses[i] + ); + if (EFI_ERROR(Status)) + return Status; + } + + return Status; +} + +STATIC +VOID +MvI2cControlClear ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN UINT32 Mask) +{ + UINT32 Value; + + /* clears given bits in I2C_CONTROL register */ + Value = I2C_READ(I2cMasterContext, I2C_CONTROL); + Value &= ~Mask; + I2C_WRITE(I2cMasterContext, I2C_CONTROL, Value); +} + +STATIC +VOID +MvI2cControlSet ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN UINT32 Mask) +{ + UINT32 Value; + + /* sets given bits in I2C_CONTROL register */ + Value = I2C_READ(I2cMasterContext, I2C_CONTROL); + Value |= Mask; + I2C_WRITE(I2cMasterContext, I2C_CONTROL, Value); +} + +STATIC +VOID +MvI2cClearIflg ( + IN I2C_MASTER_CONTEXT *I2cMasterContext + ) +{ + gBS->Stall(I2C_OPERATION_TIMEOUT); + MvI2cControlClear(I2cMasterContext, I2C_CONTROL_IFLG); + gBS->Stall(I2C_OPERATION_TIMEOUT); +} + +/* Timeout is given in us */ +STATIC +UINTN +MvI2cPollCtrl ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN UINTN Timeout, + IN UINT32 Mask) +{ + Timeout /= 10; + while (!(I2C_READ(I2cMasterContext, I2C_CONTROL) & Mask)) { + gBS->Stall(10); + if (--Timeout == 0) + return (Timeout); + } + return (0); +} + +/* + * 'Timeout' is given in us. Note also that Timeout handling is not exact -- + * MvI2cLockedStart() total wait can be more than 2 x Timeout + * (MvI2cPollCtrl() is called twice). 'Mask' can be either I2C_STATUS_START + * or I2C_STATUS_RPTD_START + */ +STATIC +EFI_STATUS +MvI2cLockedStart ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN INT32 Mask, + IN UINT8 Slave, + IN UINTN Timeout + ) +{ + UINTN ReadAccess, IflgSet = 0; + UINT32 I2cStatus; + + if (Mask == I2C_STATUS_RPTD_START) { + /* read IFLG to know if it should be cleared later */ + IflgSet = I2C_READ(I2cMasterContext, I2C_CONTROL) & I2C_CONTROL_IFLG; + } + + MvI2cControlSet(I2cMasterContext, I2C_CONTROL_START); + + if (Mask == I2C_STATUS_RPTD_START && IflgSet) { + DEBUG((DEBUG_INFO, "MvI2cDxe: IFLG set, clearing\n")); + MvI2cClearIflg(I2cMasterContext); + } + + /* Without this delay we Timeout checking IFLG if the Timeout is 0 */ + gBS->Stall(I2C_OPERATION_TIMEOUT); + + if (MvI2cPollCtrl(I2cMasterContext, Timeout, I2C_CONTROL_IFLG)) { + DEBUG((DEBUG_ERROR, "MvI2cDxe: Timeout sending %sSTART condition\n", + Mask == I2C_STATUS_START ? "" : "repeated ")); + return EFI_NO_RESPONSE; + } + + I2cStatus = I2C_READ(I2cMasterContext, I2C_STATUS); + if (I2cStatus != Mask) { + DEBUG((DEBUG_ERROR, "MvI2cDxe: wrong I2cStatus (%02x) after sending %sSTART condition\n", + I2cStatus, Mask == I2C_STATUS_START ? "" : "repeated ")); + return EFI_DEVICE_ERROR; + } + + I2C_WRITE(I2cMasterContext, I2C_DATA, Slave); + gBS->Stall(I2C_OPERATION_TIMEOUT); + MvI2cClearIflg(I2cMasterContext); + + if (MvI2cPollCtrl(I2cMasterContext, Timeout, I2C_CONTROL_IFLG)) { + DEBUG((DEBUG_ERROR, "MvI2cDxe: Timeout sending Slave address\n")); + return EFI_NO_RESPONSE; + } + + ReadAccess = (Slave & 0x1) ? 1 : 0; + I2cStatus = I2C_READ(I2cMasterContext, I2C_STATUS); + if (I2cStatus != (ReadAccess ? + I2C_STATUS_ADDR_R_ACK : I2C_STATUS_ADDR_W_ACK)) { + DEBUG((DEBUG_ERROR, "MvI2cDxe: no ACK (I2cStatus: %02x) after sending Slave address\n", + I2cStatus)); + return EFI_NO_RESPONSE; + } + + return EFI_SUCCESS; +} + +#define ABSSUB(a,b) (((a) > (b)) ? (a) - (b) : (b) - (a)) +STATIC +VOID +MvI2cCalBaudRate ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN CONST UINT32 target, + IN OUT MV_I2C_BAUD_RATE *rate, + UINT32 clk + ) +{ + UINT32 cur, diff, diff0, baud; + UINTN m, n, m0, n0; + + /* Read initial m0, n0 values from register */ + baud = I2C_READ(I2cMasterContext, I2C_BAUD_RATE); + m0 = I2C_M_FROM_BAUD(baud); + n0 = I2C_N_FROM_BAUD(baud); + /* Calculate baud rate. */ + diff0 = 0xffffffff; + + for (n = 0; n < 8; n++) { + for (m = 0; m < 16; m++) { + cur = I2C_BAUD_RATE_RAW(clk,m,n); + diff = ABSSUB(target, cur); + if (diff < diff0) { + m0 = m; + n0 = n; + diff0 = diff; + } + } + } + rate->raw = I2C_BAUD_RATE_RAW(clk, m0, n0); + rate->param = I2C_BAUD_RATE_PARAM(m0, n0); + rate->m = m0; + rate->n = n0; +} + +EFI_STATUS +EFIAPI +MvI2cReset ( + IN CONST EFI_I2C_MASTER_PROTOCOL *This + ) +{ + UINT32 param; + I2C_MASTER_CONTEXT *I2cMasterContext = I2C_SC_FROM_MASTER(This); + + param = baud_rate.param; + + EfiAcquireLock (&I2cMasterContext->Lock); + I2C_WRITE(I2cMasterContext, I2C_SOFT_RESET, 0x0); + gBS->Stall(2 * I2C_OPERATION_TIMEOUT); + I2C_WRITE(I2cMasterContext, I2C_BAUD_RATE, param); + I2C_WRITE(I2cMasterContext, I2C_CONTROL, I2C_CONTROL_I2CEN | I2C_CONTROL_ACK); + gBS->Stall(I2C_OPERATION_TIMEOUT); + EfiReleaseLock (&I2cMasterContext->Lock); + + return EFI_SUCCESS; +} + +/* + * Timeout is given in us + */ +STATIC +EFI_STATUS +MvI2cRepeatedStart ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN UINT8 Slave, + IN UINTN Timeout + ) +{ + EFI_STATUS Status; + + EfiAcquireLock (&I2cMasterContext->Lock); + Status = MvI2cLockedStart(I2cMasterContext, I2C_STATUS_RPTD_START, Slave, + Timeout); + EfiReleaseLock (&I2cMasterContext->Lock); + + if (EFI_ERROR(Status)) { + MvI2cStop(I2cMasterContext); + } + return Status; +} + +/* + * Timeout is given in us + */ +STATIC +EFI_STATUS +MvI2cStart ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN UINT8 Slave, + IN UINTN Timeout + ) +{ + EFI_STATUS Status; + + EfiAcquireLock (&I2cMasterContext->Lock); + Status = MvI2cLockedStart(I2cMasterContext, I2C_STATUS_START, Slave, Timeout); + EfiReleaseLock (&I2cMasterContext->Lock); + + if (EFI_ERROR(Status)) { + MvI2cStop(I2cMasterContext); + } + return Status; +} + +STATIC +EFI_STATUS +MvI2cStop ( + IN I2C_MASTER_CONTEXT *I2cMasterContext + ) +{ + EfiAcquireLock (&I2cMasterContext->Lock); + MvI2cControlSet(I2cMasterContext, I2C_CONTROL_STOP); + gBS->Stall(I2C_OPERATION_TIMEOUT); + MvI2cClearIflg(I2cMasterContext); + EfiReleaseLock (&I2cMasterContext->Lock); + + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +MvI2cRead ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN OUT UINT8 *Buf, + IN UINTN Length, + IN OUT UINTN *read, + IN UINTN last, + IN UINTN delay + ) +{ + UINT32 I2cStatus; + UINTN LastByte; + EFI_STATUS Status; + + EfiAcquireLock (&I2cMasterContext->Lock); + *read = 0; + while (*read < Length) { + /* + * Check if we are reading last byte of the last Buffer, + * do not send ACK then, per I2C specs + */ + LastByte = ((*read == Length - 1) && last) ? 1 : 0; + if (LastByte) + MvI2cControlClear(I2cMasterContext, I2C_CONTROL_ACK); + else + MvI2cControlSet(I2cMasterContext, I2C_CONTROL_ACK); + + gBS->Stall (I2C_OPERATION_TIMEOUT); + MvI2cClearIflg(I2cMasterContext); + + if (MvI2cPollCtrl(I2cMasterContext, delay, I2C_CONTROL_IFLG)) { + DEBUG((DEBUG_ERROR, "MvI2cDxe: Timeout reading data\n")); + Status = EFI_NO_RESPONSE; + goto out; + } + + I2cStatus = I2C_READ(I2cMasterContext, I2C_STATUS); + if (I2cStatus != (LastByte ? + I2C_STATUS_DATA_RD_NOACK : I2C_STATUS_DATA_RD_ACK)) { + DEBUG((DEBUG_ERROR, "MvI2cDxe: wrong I2cStatus (%02x) while reading\n", I2cStatus)); + Status = EFI_DEVICE_ERROR; + goto out; + } + + *Buf++ = I2C_READ(I2cMasterContext, I2C_DATA); + (*read)++; + } + Status = EFI_SUCCESS; +out: + EfiReleaseLock (&I2cMasterContext->Lock); + return (Status); +} + +STATIC +EFI_STATUS +MvI2cWrite ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN OUT CONST UINT8 *Buf, + IN UINTN Length, + IN OUT UINTN *Sent, + IN UINTN Timeout + ) +{ + UINT32 status; + EFI_STATUS Status; + + EfiAcquireLock (&I2cMasterContext->Lock); + *Sent = 0; + while (*Sent < Length) { + I2C_WRITE(I2cMasterContext, I2C_DATA, *Buf++); + + MvI2cClearIflg(I2cMasterContext); + if (MvI2cPollCtrl(I2cMasterContext, Timeout, I2C_CONTROL_IFLG)) { + DEBUG((DEBUG_ERROR, "MvI2cDxe: Timeout writing data\n")); + Status = EFI_NO_RESPONSE; + goto out; + } + + status = I2C_READ(I2cMasterContext, I2C_STATUS); + if (status != I2C_STATUS_DATA_WR_ACK) { + DEBUG((DEBUG_ERROR, "MvI2cDxe: wrong status (%02x) while writing\n", status)); + Status = EFI_DEVICE_ERROR; + goto out; + } + (*Sent)++; + } + Status = EFI_SUCCESS; +out: + EfiReleaseLock (&I2cMasterContext->Lock); + return (Status); +} + +/* + * MvI2cStartRequest should be called only by I2cHost. + * I2C device drivers ought to use EFI_I2C_IO_PROTOCOL instead. + */ +STATIC +EFI_STATUS +MvI2cStartRequest ( + IN CONST EFI_I2C_MASTER_PROTOCOL *This, + IN UINTN SlaveAddress, + IN EFI_I2C_REQUEST_PACKET *RequestPacket, + IN EFI_EVENT Event OPTIONAL, + OUT EFI_STATUS *I2cStatus OPTIONAL + ) +{ + UINTN Count; + UINTN ReadMode; + UINTN Transmitted; + I2C_MASTER_CONTEXT *I2cMasterContext = I2C_SC_FROM_MASTER(This); + EFI_I2C_OPERATION *Operation; + + ASSERT (RequestPacket != NULL); + ASSERT (I2cMasterContext != NULL); + + for (Count = 0; Count < RequestPacket->OperationCount; Count++) { + Operation = &RequestPacket->Operation[Count]; + ReadMode = Operation->Flags & I2C_FLAG_READ; + + if (Count == 0) { + MvI2cStart ( I2cMasterContext, + (SlaveAddress << 1) | ReadMode, + I2C_TRANSFER_TIMEOUT + ); + } else if (!(Operation->Flags & I2C_FLAG_NORESTART)) { + MvI2cRepeatedStart ( I2cMasterContext, + (SlaveAddress << 1) | ReadMode, + I2C_TRANSFER_TIMEOUT + ); + } + + if (ReadMode) { + MvI2cRead ( I2cMasterContext, + Operation->Buffer, + Operation->LengthInBytes, + &Transmitted, + Count == 1, + I2C_TRANSFER_TIMEOUT + ); + } else { + MvI2cWrite ( I2cMasterContext, + Operation->Buffer, + Operation->LengthInBytes, + &Transmitted, + I2C_TRANSFER_TIMEOUT + ); + } + if (Count == RequestPacket->OperationCount - 1) { + MvI2cStop ( I2cMasterContext ); + } + } + + if (I2cStatus != NULL) + I2cStatus = EFI_SUCCESS; + if (Event != NULL) + gBS->SignalEvent(Event); + return EFI_SUCCESS; +} + +#define I2C_GUID \ + { \ + 0x391fc679, 0x6cb0, 0x4f01, { 0x9a, 0xc7, 0x8e, 0x1b, 0x78, 0x6b, 0x7a, 0x00 } \ + } + +STATIC CONST EFI_GUID DevGuid = I2C_GUID; + +#define I2C_DEVICE_INDEX(bus, address) (((address) & 0xffff) | (bus) << 16) +#define I2C_DEVICE_ADDRESS(index) ((index) & 0xffff) +STATIC +EFI_STATUS +MvI2cAllocDevice ( + IN UINT8 SlaveAddress, + IN UINT8 Bus, + IN OUT CONST EFI_I2C_DEVICE **Device + ) +{ + EFI_STATUS Status; + EFI_I2C_DEVICE *Dev; + UINT32 *TmpSlaveArray; + EFI_GUID *TmpGuidP; + + Status = gBS->AllocatePool ( EfiBootServicesData, + sizeof(EFI_I2C_DEVICE), + (VOID **) &Dev ); + if (EFI_ERROR(Status)) { + DEBUG((DEBUG_ERROR, "MvI2cDxe: I2C device memory allocation failed\n")); + return Status; + } + *Device = Dev; + Dev->DeviceIndex = SlaveAddress; + Dev->DeviceIndex = I2C_DEVICE_INDEX(Bus, SlaveAddress); + Dev->SlaveAddressCount = 1; + Dev->I2cBusConfiguration = 0; + Status = gBS->AllocatePool ( EfiBootServicesData, + sizeof(UINT32), + (VOID **) &TmpSlaveArray); + if (EFI_ERROR(Status)) { + goto fail1; + } + TmpSlaveArray[0] = SlaveAddress; + Dev->SlaveAddressArray = TmpSlaveArray; + + Status = gBS->AllocatePool ( EfiBootServicesData, + sizeof(EFI_GUID), + (VOID **) &TmpGuidP); + if (EFI_ERROR(Status)) { + goto fail2; + } + *TmpGuidP = DevGuid; + Dev->DeviceGuid = TmpGuidP; + + DEBUG((DEBUG_INFO, "MvI2c: allocated device with address %x\n", (UINTN)SlaveAddress)); + return EFI_SUCCESS; + +fail2: + FreePool(TmpSlaveArray); +fail1: + FreePool(Dev); + + return Status; +} + +/* + * It is called by I2cBus to enumerate devices on I2C bus. In this case, + * enumeration is based on PCD configuration - all Slave addresses specified + * in PCD get their corresponding EFI_I2C_DEVICE structures here. + * + * After enumeration succeeds, Supported() function of drivers that installed + * DriverBinding protocol is called. + */ +STATIC +EFI_STATUS +EFIAPI +MvI2cEnumerate ( + IN CONST EFI_I2C_ENUMERATE_PROTOCOL *This, + IN OUT CONST EFI_I2C_DEVICE **Device + ) +{ + UINT8 *DevicesPcd; + UINT8 *DeviceBusPcd; + UINTN Index, NextIndex; + UINT8 NextDeviceAddress; + I2C_MASTER_CONTEXT *I2cMasterContext = I2C_SC_FROM_ENUMERATE(This); + + DevicesPcd = PcdGetPtr (PcdI2cSlaveAddresses); + DeviceBusPcd = PcdGetPtr (PcdI2cSlaveBuses); + if (*Device == NULL) { + for (Index = 0; DevicesPcd[Index] != '\0'; Index++) { + if (DeviceBusPcd[Index] != I2cMasterContext->Bus) + continue; + if (DevicesPcd[Index] != '\0') + MvI2cAllocDevice (DevicesPcd[Index], I2cMasterContext->Bus, Device); + return EFI_SUCCESS; + } + } else { + /* Device is not NULL, so something was already allocated */ + for (Index = 0; DevicesPcd[Index] != '\0'; Index++) { + if (DeviceBusPcd[Index] != I2cMasterContext->Bus) + continue; + if (DevicesPcd[Index] == I2C_DEVICE_ADDRESS((*Device)->DeviceIndex)) { + for (NextIndex = Index + 1; DevicesPcd[NextIndex] != '\0'; NextIndex++) { + if (DeviceBusPcd[NextIndex] != I2cMasterContext->Bus) + continue; + NextDeviceAddress = DevicesPcd[NextIndex]; + if (NextDeviceAddress != '\0') + MvI2cAllocDevice(NextDeviceAddress, I2cMasterContext->Bus, Device); + return EFI_SUCCESS; + } + } + } + *Device = NULL; + return EFI_SUCCESS; + } + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +EFIAPI +MvI2cEnableConf ( + IN CONST EFI_I2C_BUS_CONFIGURATION_MANAGEMENT_PROTOCOL *This, + IN UINTN I2cBusConfiguration, + IN EFI_EVENT Event OPTIONAL, + IN EFI_STATUS *I2cStatus OPTIONAL + ) +{ + /* do nothing */ + if (I2cStatus != NULL) + I2cStatus = EFI_SUCCESS; + if (Event != NULL) + gBS->SignalEvent(Event); + return EFI_SUCCESS; +} diff --git a/Drivers/I2c/MvI2cDxe/MvI2cDxe.h b/Drivers/I2c/MvI2cDxe/MvI2cDxe.h new file mode 100644 index 0000000..a459c1c --- /dev/null +++ b/Drivers/I2c/MvI2cDxe/MvI2cDxe.h @@ -0,0 +1,263 @@ +/******************************************************************************** +Copyright (C) 2016 Marvell International Ltd. + +Marvell BSD License Option + +If you received this File from Marvell, you may opt to use, redistribute and/or +modify this File under the following licensing terms. +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 Marvell 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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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. + +*******************************************************************************/ + +#ifndef __MV_I2C_H__ +#define __MV_I2C_H__ + +#include <Uefi.h> + +#define I2C_BASE_ADDRESS 0xf0511000 + +#define I2C_SLAVE_ADDR 0x00 +#define I2C_EXT_SLAVE_ADDR 0x10 +#define I2C_DATA 0x04 + +#define I2C_CONTROL 0x08 +#define I2C_CONTROL_ACK (1 << 2) +#define I2C_CONTROL_IFLG (1 << 3) +#define I2C_CONTROL_STOP (1 << 4) +#define I2C_CONTROL_START (1 << 5) +#define I2C_CONTROL_I2CEN (1 << 6) +#define I2C_CONTROL_INTEN (1 << 7) + +#define I2C_STATUS 0x0c +#define I2C_STATUS_START 0x08 +#define I2C_STATUS_RPTD_START 0x10 +#define I2C_STATUS_ADDR_W_ACK 0x18 +#define I2C_STATUS_DATA_WR_ACK 0x28 +#define I2C_STATUS_ADDR_R_ACK 0x40 +#define I2C_STATUS_DATA_RD_ACK 0x50 +#define I2C_STATUS_DATA_RD_NOACK 0x58 + +#define I2C_BAUD_RATE 0x0c +#define I2C_BAUD_RATE_PARAM(M,N) ((((M) << 3) | ((N) & 0x7)) & 0x7f) +#define I2C_BAUD_RATE_RAW(C,M,N) ((C)/((10*(M+1))<<(N+1))) +#define I2C_M_FROM_BAUD(baud) (((baud) >> 3) & 0xf) +#define I2C_N_FROM_BAUD(baud) ((baud) & 0x7) + +#define I2C_SOFT_RESET 0x1c +#define I2C_TRANSFER_TIMEOUT 10000 +#define I2C_OPERATION_TIMEOUT 1000 + +#define I2C_UNKNOWN 0x0 +#define I2C_SLOW 0x1 +#define I2C_FAST 0x2 +#define I2C_FASTEST 0x3 + +/* + * I2C_FLAG_NORESTART is not part of PI spec, it allows to continue + * transmission without repeated start operation. + * FIXME: This flag is also defined in + * Platforms/Marvell/Include/Protocol/Eeprom.h and it's important to have both + * version synced. This solution is temporary and shared flag should be used by + * both files. + */ +#define I2C_FLAG_NORESTART 0x00000002 + +#define I2C_MASTER_SIGNATURE SIGNATURE_32 ('I', '2', 'C', 'M') + +typedef struct { + UINT32 Signature; + EFI_HANDLE Controller; + EFI_LOCK Lock; + UINTN TclkFrequency; + UINTN BaseAddress; + INTN Bus; + EFI_I2C_MASTER_PROTOCOL I2cMaster; + EFI_I2C_ENUMERATE_PROTOCOL I2cEnumerate; + EFI_I2C_BUS_CONFIGURATION_MANAGEMENT_PROTOCOL I2cBusConf; +} I2C_MASTER_CONTEXT; + +#define I2C_SC_FROM_MASTER(a) CR (a, I2C_MASTER_CONTEXT, I2cMaster, I2C_MASTER_SIGNATURE) +#define I2C_SC_FROM_ENUMERATE(a) CR (a, I2C_MASTER_CONTEXT, I2cEnumerate, I2C_MASTER_SIGNATURE) +#define I2C_SC_FROM_BUSCONF(a) CR (a, I2C_MASTER_CONTEXT, I2cBusConf, I2C_MASTER_SIGNATURE) + +typedef struct { + UINT32 raw; + UINTN param; + UINTN m; + UINTN n; +} MV_I2C_BAUD_RATE; + +typedef struct { + VENDOR_DEVICE_PATH Guid; + EFI_DEVICE_PATH_PROTOCOL End; +} MV_I2C_DEVICE_PATH; + +STATIC +UINT32 +I2C_READ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN UINTN off + ); + +STATIC +EFI_STATUS +I2C_WRITE ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN UINTN off, + IN UINT32 val + ); + +EFI_STATUS +EFIAPI +MvI2cInitialise ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ); + +STATIC +VOID +MvI2cControlClear ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN UINT32 mask + ); + +STATIC +VOID +MvI2cControlSet ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN UINT32 mask + ); + +STATIC +VOID +MvI2cClearIflg ( + IN I2C_MASTER_CONTEXT *I2cMasterContext + ); +STATIC +UINTN +MvI2cPollCtrl ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN UINTN timeout, + IN UINT32 mask + ); + +STATIC +EFI_STATUS +MvI2cLockedStart ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN INT32 mask, + IN UINT8 slave, + IN UINTN timeout + ); + +STATIC +VOID +MvI2cCalBaudRate ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN CONST UINT32 target, + IN OUT MV_I2C_BAUD_RATE *rate, + UINT32 clk + ); + +EFI_STATUS +EFIAPI +MvI2cReset ( + IN CONST EFI_I2C_MASTER_PROTOCOL *This + ); + +STATIC +EFI_STATUS +MvI2cRepeatedStart ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN UINT8 slave, + IN UINTN timeout + ); + +STATIC +EFI_STATUS +MvI2cStart ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN UINT8 slave, + IN UINTN timeout + ); + +STATIC +EFI_STATUS +MvI2cStop ( + IN I2C_MASTER_CONTEXT *I2cMasterContext + ); + +STATIC +EFI_STATUS +MvI2cRead ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN OUT UINT8 *buf, + IN UINTN len, + IN OUT UINTN *read, + IN UINTN last, + IN UINTN delay + ); + +STATIC +EFI_STATUS +MvI2cWrite ( + IN I2C_MASTER_CONTEXT *I2cMasterContext, + IN OUT CONST UINT8 *buf, + IN UINTN len, + IN OUT UINTN *sent, + IN UINTN timeout + ); + +STATIC +EFI_STATUS +EFIAPI +MvI2cStartRequest ( + IN CONST EFI_I2C_MASTER_PROTOCOL *This, + IN UINTN SlaveAddress, + IN EFI_I2C_REQUEST_PACKET *RequestPacket, + IN EFI_EVENT Event OPTIONAL, + OUT EFI_STATUS *I2cStatus OPTIONAL + ); + +STATIC +EFI_STATUS +EFIAPI +MvI2cEnumerate ( + IN CONST EFI_I2C_ENUMERATE_PROTOCOL *This, + IN OUT CONST EFI_I2C_DEVICE **Device + ); + +STATIC +EFI_STATUS +EFIAPI +MvI2cEnableConf ( + IN CONST EFI_I2C_BUS_CONFIGURATION_MANAGEMENT_PROTOCOL *This, + IN UINTN I2cBusConfiguration, + IN EFI_EVENT Event OPTIONAL, + IN EFI_STATUS *I2cStatus OPTIONAL + ); + +#endif // __MV_I2C_H__ diff --git a/Drivers/I2c/MvI2cDxe/MvI2cDxe.inf b/Drivers/I2c/MvI2cDxe/MvI2cDxe.inf new file mode 100755 index 0000000..c7c084c --- /dev/null +++ b/Drivers/I2c/MvI2cDxe/MvI2cDxe.inf @@ -0,0 +1,75 @@ +# +# Marvell BSD License Option +# +# If you received this File from Marvell, you may opt to use, redistribute +# and/or modify this File under the following licensing terms. +# 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 Marvell 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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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. +# + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = MvI2cDxe + FILE_GUID = 59fc3843-d8d4-40aa-ae07-38967138509b + MODULE_TYPE = DXE_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = MvI2cInitialise + +[Sources.common] + MvI2cDxe.c + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + ArmPlatformPkg/ArmPlatformPkg.dec + ArmPkg/ArmPkg.dec + OpenPlatformPkg/Platforms/Marvell/Marvell.dec + +[LibraryClasses] + IoLib + PcdLib + BaseLib + DebugLib + UefiLib + UefiDriverEntryPoint + UefiBootServicesTableLib + ParsePcdLib + +[Protocols] + gEfiI2cMasterProtocolGuid + gEfiDevicePathProtocolGuid + gEfiI2cEnumerateProtocolGuid + gEfiI2cBusConfigurationManagementProtocolGuid + +[Pcd] + gMarvellTokenSpaceGuid.PcdI2cSlaveAddresses + gMarvellTokenSpaceGuid.PcdI2cSlaveBuses + gMarvellTokenSpaceGuid.PcdI2cBaseAddresses + gMarvellTokenSpaceGuid.PcdI2cClockFrequency + gMarvellTokenSpaceGuid.PcdI2cBaudRate + gMarvellTokenSpaceGuid.PcdI2cBusCount + +[Depex] + TRUE diff --git a/Platforms/Marvell/Marvell.dec b/Platforms/Marvell/Marvell.dec index fee8025..6738116 100644 --- a/Platforms/Marvell/Marvell.dec +++ b/Platforms/Marvell/Marvell.dec @@ -104,3 +104,11 @@ gMarvellTokenSpaceGuid.PcdChip3MppSel6|{ 0 }|VOID*|0x30000044 gMarvellTokenSpaceGuid.PcdChip3MppSel7|{ 0 }|VOID*|0x30000045
+#I2C + gMarvellTokenSpaceGuid.PcdI2cSlaveAddresses|{ 0 }|VOID*|0x3000046 + gMarvellTokenSpaceGuid.PcdI2cSlaveBuses|{ 0 }|VOID*|0x3000184 + gMarvellTokenSpaceGuid.PcdI2cBaseAddresses|{ 0 }|VOID*|0x3000047 + gMarvellTokenSpaceGuid.PcdI2cClockFrequency|0|UINT32|0x3000048 + gMarvellTokenSpaceGuid.PcdI2cBaudRate|0|UINT32|0x3000049 + gMarvellTokenSpaceGuid.PcdI2cBusCount|0|UINT32|0x3000183 +