/** @file
SpiBus driver
Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#include
#include
#include "SpiBus.h"
/**
Checks if two device paths are the same.
@param[in] DevicePath1 First device path to compare
@param[in] DevicePath2 Second device path to compare
@retval TRUE The device paths share the same nodes and values
@retval FALSE The device paths differ
**/
BOOLEAN
EFIAPI
DevicePathsAreEqual (
IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath1,
IN CONST EFI_DEVICE_PATH_PROTOCOL *DevicePath2
)
{
UINTN Size1;
UINTN Size2;
Size1 = GetDevicePathSize (DevicePath1);
Size2 = GetDevicePathSize (DevicePath2);
if (Size1 != Size2) {
return FALSE;
}
if (CompareMem (DevicePath1, DevicePath2, Size1) != 0) {
return FALSE;
}
return TRUE;
}
/**
Calls the SpiPeripherals ChipSelect if it is not null, otherwise
calls the Host Controllers ChipSelect function.
@param[in] SpiChip The SpiChip to place on the bus via asserting its chip select
@param[in] PinValue Value to place on the chip select pin
@retval EFI_SUCCESS Chip select pin was placed at requested level
@retval EFI_INVALID_PARAMETER Invalid parameters passed into ChipSelect function
**/
EFI_STATUS
EFIAPI
SpiChipSelect (
IN CONST SPI_IO_CHIP *SpiChip,
IN BOOLEAN PinValue
)
{
EFI_STATUS Status;
// Check which chip select function to use
if (SpiChip->Protocol.SpiPeripheral->ChipSelect != NULL) {
Status = SpiChip->Protocol.SpiPeripheral->ChipSelect (
SpiChip->BusTransaction.SpiPeripheral,
PinValue
);
} else {
Status = SpiChip->SpiHc->ChipSelect (
SpiChip->SpiHc,
SpiChip->BusTransaction.SpiPeripheral,
PinValue
);
}
return Status;
}
/**
Checks the SpiChip's BusTransaction attributes to ensure its a valid SPI transaction.
@param[in] SpiChip The SpiChip where a bus transaction is requested
@retval EFI_SUCCESS This is a valid SPI bus transaction
@retval EFI_BAD_BUFFER_SIZE The WriteBytes value was invalid
@retval EFI_BAD_BUFFER_SIZE The ReadBytes value was invalid
@retval EFI_INVALID_PARAMETER TransactionType is not valid,
or BusWidth not supported by SPI peripheral or
SPI host controller,
or WriteBytes non-zero and WriteBuffer is
NULL,
or ReadBytes non-zero and ReadBuffer is NULL,
or ReadBuffer != WriteBuffer for full-duplex
type,
or WriteBuffer was NULL,
or TPL is too high
@retval EFI_OUT_OF_RESOURCES Insufficient memory for SPI transaction
@retval EFI_UNSUPPORTED The FrameSize is not supported by the SPI bus
layer or the SPI host controller
@retval EFI_UNSUPPORTED The SPI controller was not able to support
**/
EFI_STATUS
EFIAPI
IsValidSpiTransaction (
IN SPI_IO_CHIP *SpiChip
)
{
// Error checking
if (SpiChip->BusTransaction.TransactionType > SPI_TRANSACTION_WRITE_THEN_READ) {
return EFI_INVALID_PARAMETER;
}
if (((SpiChip->BusTransaction.BusWidth != 1) && (SpiChip->BusTransaction.BusWidth != 2) && (SpiChip->BusTransaction.BusWidth != 4) &&
(SpiChip->BusTransaction.BusWidth != 8)) || (SpiChip->BusTransaction.FrameSize == 0))
{
return EFI_INVALID_PARAMETER;
}
if ((SpiChip->BusTransaction.BusWidth == 8) && (((SpiChip->Protocol.Attributes & SPI_IO_SUPPORTS_8_BIT_DATA_BUS_WIDTH) != SPI_IO_SUPPORTS_8_BIT_DATA_BUS_WIDTH) ||
((SpiChip->BusTransaction.SpiPeripheral->Attributes & SPI_PART_SUPPORTS_8_BIT_DATA_BUS_WIDTH) != SPI_PART_SUPPORTS_8_BIT_DATA_BUS_WIDTH)))
{
return EFI_INVALID_PARAMETER;
} else if ((SpiChip->BusTransaction.BusWidth == 4) && (((SpiChip->Protocol.Attributes & SPI_IO_SUPPORTS_4_BIT_DATA_BUS_WIDTH) != SPI_IO_SUPPORTS_4_BIT_DATA_BUS_WIDTH) ||
((SpiChip->BusTransaction.SpiPeripheral->Attributes & SPI_PART_SUPPORTS_4_BIT_DATA_BUS_WIDTH) != SPI_PART_SUPPORTS_4_BIT_DATA_BUS_WIDTH)))
{
return EFI_INVALID_PARAMETER;
} else if ((SpiChip->BusTransaction.BusWidth == 2) && (((SpiChip->Protocol.Attributes & SPI_IO_SUPPORTS_4_BIT_DATA_BUS_WIDTH) != SPI_IO_SUPPORTS_4_BIT_DATA_BUS_WIDTH) ||
((SpiChip->BusTransaction.SpiPeripheral->Attributes & SPI_PART_SUPPORTS_2_BIT_DATA_BUS_WIDTH) != SPI_PART_SUPPORTS_2_BIT_DATA_BUS_WIDTH)))
{
return EFI_INVALID_PARAMETER;
}
if (((SpiChip->BusTransaction.WriteBytes > 0) && (SpiChip->BusTransaction.WriteBuffer == NULL)) || ((SpiChip->BusTransaction.ReadBytes > 0) && (SpiChip->BusTransaction.ReadBuffer == NULL))) {
return EFI_INVALID_PARAMETER;
}
if ((SpiChip->BusTransaction.TransactionType == SPI_TRANSACTION_FULL_DUPLEX) && (SpiChip->BusTransaction.ReadBytes != SpiChip->BusTransaction.WriteBytes)) {
return EFI_INVALID_PARAMETER;
}
// Check frame size, passed parameter is in bits
if ((SpiChip->Protocol.FrameSizeSupportMask & (1<<(SpiChip->BusTransaction.FrameSize-1))) == 0) {
return EFI_UNSUPPORTED;
}
return EFI_SUCCESS;
}
/**
Initiate a SPI transaction between the host and a SPI peripheral.
This routine must be called at or below TPL_NOTIFY.
This routine works with the SPI bus layer to pass the SPI transaction to the
SPI controller for execution on the SPI bus. There are four types of
supported transactions supported by this routine:
* Full Duplex: WriteBuffer and ReadBuffer are the same size.
* Write Only: WriteBuffer contains data for SPI peripheral, ReadBytes = 0
* Read Only: ReadBuffer to receive data from SPI peripheral, WriteBytes = 0
* Write Then Read: WriteBuffer contains control data to write to SPI
peripheral before data is placed into the ReadBuffer.
Both WriteBytes and ReadBytes must be non-zero.
@param[in] This Pointer to an EFI_SPI_IO_PROTOCOL structure.
@param[in] TransactionType Type of SPI transaction.
@param[in] DebugTransaction Set TRUE only when debugging is desired.
Debugging may be turned on for a single SPI
transaction. Only this transaction will display
debugging messages. All other transactions with
this value set to FALSE will not display any
debugging messages.
@param[in] ClockHz Specify the ClockHz value as zero (0) to use
the maximum clock frequency supported by the
SPI controller and part. Specify a non-zero
value only when a specific SPI transaction
requires a reduced clock rate.
@param[in] BusWidth Width of the SPI bus in bits: 1, 2, 4
@param[in] FrameSize Frame size in bits, range: 1 - 32
@param[in] WriteBytes The length of the WriteBuffer in bytes.
Specify zero for read-only operations.
@param[in] WriteBuffer The buffer containing data to be sent from the
host to the SPI chip. Specify NULL for read
only operations.
* Frame sizes 1-8 bits: UINT8 (one byte) per
frame
* Frame sizes 7-16 bits: UINT16 (two bytes) per
frame
* Frame sizes 17-32 bits: UINT32 (four bytes)
per frame The transmit frame is in the least
significant N bits.
@param[in] ReadBytes The length of the ReadBuffer in bytes.
Specify zero for write-only operations.
@param[out] ReadBuffer The buffer to receeive data from the SPI chip
during the transaction. Specify NULL for write
only operations.
* Frame sizes 1-8 bits: UINT8 (one byte) per
frame
* Frame sizes 7-16 bits: UINT16 (two bytes) per
frame
* Frame sizes 17-32 bits: UINT32 (four bytes)
per frame The received frame is in the least
significant N bits.
@retval EFI_SUCCESS The SPI transaction completed successfully
@retval EFI_BAD_BUFFER_SIZE The WriteBytes value was invalid
@retval EFI_BAD_BUFFER_SIZE The ReadBytes value was invalid
@retval EFI_INVALID_PARAMETER TransactionType is not valid,
or BusWidth not supported by SPI peripheral or
SPI host controller,
or WriteBytes non-zero and WriteBuffer is
NULL,
or ReadBytes non-zero and ReadBuffer is NULL,
or ReadBuffer != WriteBuffer for full-duplex
type,
or WriteBuffer was NULL,
or TPL is too high
@retval EFI_OUT_OF_RESOURCES Insufficient memory for SPI transaction
@retval EFI_UNSUPPORTED The FrameSize is not supported by the SPI bus
layer or the SPI host controller
@retval EFI_UNSUPPORTED The SPI controller was not able to support
**/
EFI_STATUS
EFIAPI
Transaction (
IN CONST EFI_SPI_IO_PROTOCOL *This,
IN EFI_SPI_TRANSACTION_TYPE TransactionType,
IN BOOLEAN DebugTransaction,
IN UINT32 ClockHz OPTIONAL,
IN UINT32 BusWidth,
IN UINT32 FrameSize,
IN UINT32 WriteBytes,
IN UINT8 *WriteBuffer,
IN UINT32 ReadBytes,
OUT UINT8 *ReadBuffer
)
{
EFI_STATUS Status;
SPI_IO_CHIP *SpiChip;
UINT32 MaxClockHz;
UINT8 *DummyReadBuffer;
UINT8 *DummyWriteBuffer;
SpiChip = SPI_IO_CHIP_FROM_THIS (This);
SpiChip->BusTransaction.SpiPeripheral =
(EFI_SPI_PERIPHERAL *)SpiChip->Protocol.SpiPeripheral;
SpiChip->BusTransaction.TransactionType = TransactionType;
SpiChip->BusTransaction.DebugTransaction = DebugTransaction;
SpiChip->BusTransaction.BusWidth = BusWidth;
SpiChip->BusTransaction.FrameSize = FrameSize;
SpiChip->BusTransaction.WriteBytes = WriteBytes;
SpiChip->BusTransaction.WriteBuffer = WriteBuffer;
SpiChip->BusTransaction.ReadBytes = ReadBytes;
SpiChip->BusTransaction.ReadBuffer = ReadBuffer;
// Ensure valid spi transaction parameters
Status = IsValidSpiTransaction (SpiChip);
if (EFI_ERROR (Status)) {
return Status;
}
// Setup the proper clock frequency
if (SpiChip->BusTransaction.SpiPeripheral->MaxClockHz != 0) {
MaxClockHz = SpiChip->BusTransaction.SpiPeripheral->MaxClockHz;
} else {
MaxClockHz = SpiChip->BusTransaction.SpiPeripheral->SpiPart->MaxClockHz;
}
// Call proper clock function
if (SpiChip->Protocol.SpiPeripheral->SpiBus->Clock != NULL) {
Status = SpiChip->Protocol.SpiPeripheral->SpiBus->Clock (
SpiChip->BusTransaction.SpiPeripheral,
&MaxClockHz
);
} else {
Status = SpiChip->SpiHc->Clock (
SpiChip->SpiHc,
SpiChip->BusTransaction.SpiPeripheral,
&MaxClockHz
);
}
if (EFI_ERROR (Status)) {
return Status;
}
Status = SpiChipSelect (SpiChip, SpiChip->BusTransaction.SpiPeripheral->SpiPart->ChipSelectPolarity);
if (EFI_ERROR (Status)) {
return Status;
}
// Check transaction types and match to HC capabilities
if ((TransactionType == SPI_TRANSACTION_WRITE_ONLY) &&
((SpiChip->SpiHc->Attributes & HC_SUPPORTS_WRITE_ONLY_OPERATIONS) != HC_SUPPORTS_WRITE_ONLY_OPERATIONS))
{
// Convert to full duplex transaction
SpiChip->BusTransaction.ReadBytes = SpiChip->BusTransaction.WriteBytes;
SpiChip->BusTransaction.ReadBuffer = AllocateZeroPool (SpiChip->BusTransaction.ReadBytes);
Status = SpiChip->SpiHc->Transaction (
SpiChip->SpiHc,
&SpiChip->BusTransaction
);
SpiChip->BusTransaction.ReadBytes = ReadBytes; // assign to passed parameter
FreePool (SpiChip->BusTransaction.ReadBuffer); // Free temporary buffer
} else if ((TransactionType == SPI_TRANSACTION_READ_ONLY) &&
((SpiChip->SpiHc->Attributes & HC_SUPPORTS_READ_ONLY_OPERATIONS) != HC_SUPPORTS_READ_ONLY_OPERATIONS))
{
// Convert to full duplex transaction
SpiChip->BusTransaction.WriteBytes = SpiChip->BusTransaction.WriteBytes;
SpiChip->BusTransaction.WriteBuffer = AllocateZeroPool (SpiChip->BusTransaction.WriteBytes);
Status = SpiChip->SpiHc->Transaction (
SpiChip->SpiHc,
&SpiChip->BusTransaction
);
SpiChip->BusTransaction.WriteBytes = WriteBytes;
FreePool (SpiChip->BusTransaction.WriteBuffer);
} else if ((TransactionType == SPI_TRANSACTION_WRITE_THEN_READ) &&
((SpiChip->SpiHc->Attributes & HC_SUPPORTS_WRITE_THEN_READ_OPERATIONS) != HC_SUPPORTS_WRITE_THEN_READ_OPERATIONS))
{
// Convert to full duplex transaction
DummyReadBuffer = AllocateZeroPool (WriteBytes);
DummyWriteBuffer = AllocateZeroPool (ReadBytes);
SpiChip->BusTransaction.ReadBuffer = DummyReadBuffer;
SpiChip->BusTransaction.ReadBytes = WriteBytes;
Status = SpiChip->SpiHc->Transaction (
SpiChip->SpiHc,
&SpiChip->BusTransaction
);
if (EFI_ERROR (Status)) {
return Status;
}
// Write is done, now need to read, restore passed in read buffer info
SpiChip->BusTransaction.ReadBuffer = ReadBuffer;
SpiChip->BusTransaction.ReadBytes = ReadBytes;
SpiChip->BusTransaction.WriteBuffer = DummyWriteBuffer;
SpiChip->BusTransaction.WriteBytes = ReadBytes;
Status = SpiChip->SpiHc->Transaction (
SpiChip->SpiHc,
&SpiChip->BusTransaction
);
// Restore write data
SpiChip->BusTransaction.WriteBuffer = WriteBuffer;
SpiChip->BusTransaction.WriteBytes = WriteBytes;
FreePool (DummyReadBuffer);
FreePool (DummyWriteBuffer);
} else {
// Supported transaction type, just pass info the SPI HC Protocol Transaction
Status = SpiChip->SpiHc->Transaction (
SpiChip->SpiHc,
&SpiChip->BusTransaction
);
}
if (EFI_ERROR (Status)) {
return Status;
}
Status = SpiChipSelect (SpiChip, !SpiChip->BusTransaction.SpiPeripheral->SpiPart->ChipSelectPolarity);
return Status;
}
/**
Update the SPI peripheral associated with this SPI 10 SpiChip.
Support socketed SPI parts by allowing the SPI peripheral driver to replace
the SPI peripheral after the connection is made. An example use is socketed
SPI NOR flash parts, where the size and parameters change depending upon
device is in the socket.
@param[in] This Pointer to an EFI_SPI_IO_PROTOCOL structure.
@param[in] SpiPeripheral Pointer to an EFI_SPI_PERIPHERAL structure.
@retval EFI_SUCCESS The SPI peripheral was updated successfully
@retval EFI_INVALID_PARAMETER The SpiPeripheral value is NULL,
or the SpiPeripheral->SpiBus is NULL,
or the SpiPeripheral->SpiBus pointing at
wrong bus, or the SpiPeripheral->SpiPart is NULL
**/
EFI_STATUS
EFIAPI
UpdateSpiPeripheral (
IN CONST EFI_SPI_IO_PROTOCOL *This,
IN CONST EFI_SPI_PERIPHERAL *SpiPeripheral
)
{
EFI_STATUS Status;
SPI_IO_CHIP *SpiChip;
DEBUG ((DEBUG_VERBOSE, "%a: SPI Bus - Entry\n", __func__));
SpiChip = SPI_IO_CHIP_FROM_THIS (This);
if ((SpiPeripheral == NULL) || (SpiPeripheral->SpiBus == NULL) ||
(SpiPeripheral->SpiPart == NULL))
{
return EFI_INVALID_PARAMETER;
}
// EFI_INVALID_PARAMETER if SpiPeripheral->SpiBus is pointing at wrong bus
if (!DevicePathsAreEqual (SpiPeripheral->SpiBus->ControllerPath, SpiChip->SpiBus->ControllerPath)) {
return EFI_INVALID_PARAMETER;
}
SpiChip->Protocol.OriginalSpiPeripheral = SpiChip->Protocol.SpiPeripheral;
SpiChip->Protocol.SpiPeripheral = SpiPeripheral;
Status = EFI_SUCCESS;
DEBUG ((
DEBUG_VERBOSE,
"%a: SPI Bus - Exit Status=%r\n",
__func__,
Status
));
return Status;
}