CloverBootloader/Drivers/AudioDxe/HdaController/HdaControllerHdaIo.c
2019-09-03 12:58:42 +03:00

501 lines
16 KiB
C

/*
* File: HdaControllerHdaIo.c
*
* Copyright (c) 2018 John Davis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "HdaController.h"
#include <Library/HdaRegisters.h>
// HDA I/O Device Path GUID.
EFI_GUID gEfiHdaIoDevicePathGuid = EFI_HDA_IO_DEVICE_PATH_GUID;
/**
Retrieves this codec's address.
@param[in] This A pointer to the HDA_IO_PROTOCOL instance.
@param[out] CodecAddress The codec's address.
@retval EFI_SUCCESS The codec's address was returned.
@retval EFI_INVALID_PARAMETER One or more parameters are invalid.
**/
EFI_STATUS
EFIAPI
HdaControllerHdaIoGetAddress(
IN EFI_HDA_IO_PROTOCOL *This,
OUT UINT8 *CodecAddress) {
HDA_IO_PRIVATE_DATA *HdaPrivateData;
// If parameters are NULL, return error.
if (This == NULL || CodecAddress == NULL)
return EFI_INVALID_PARAMETER;
// Get private data and codec address.
HdaPrivateData = HDA_IO_PRIVATE_DATA_FROM_THIS(This);
*CodecAddress = HdaPrivateData->HdaCodecAddress;
return EFI_SUCCESS;
}
/**
Sends a single command to the codec.
@param[in] This A pointer to the HDA_IO_PROTOCOL instance.
@param[in] Node The destination node.
@param[in] Verb The verb to send.
@param[out] Response The response received.
@retval EFI_SUCCESS The verb was sent successfully and a response received.
@retval EFI_INVALID_PARAMETER One or more parameters are invalid.
**/
EFI_STATUS
EFIAPI
HdaControllerHdaIoSendCommand(
IN EFI_HDA_IO_PROTOCOL *This,
IN UINT8 Node,
IN UINT32 Verb,
OUT UINT32 *Response)
{
UINT32 Verba = Verb;
// Create verb list with single item.
EFI_HDA_IO_VERB_LIST HdaCodecVerbList;
HdaCodecVerbList.Count = 1;
HdaCodecVerbList.Verbs = &Verba;
HdaCodecVerbList.Responses = Response;
// Call SendCommands().
return HdaControllerHdaIoSendCommands(This, Node, &HdaCodecVerbList);
}
/**
Sends a set of commands to the codec.
@param[in] This A pointer to the HDA_IO_PROTOCOL instance.
@param[in] Node The destination node.
@param[in] Verbs The verbs to send. Responses will be delievered in the same list.
@retval EFI_SUCCESS The verbs were sent successfully and all responses received.
@retval EFI_INVALID_PARAMETER One or more parameters are invalid.
**/
EFI_STATUS
EFIAPI
HdaControllerHdaIoSendCommands(
IN EFI_HDA_IO_PROTOCOL *This,
IN UINT8 Node,
IN EFI_HDA_IO_VERB_LIST *Verbs)
{
// Create variables.
HDA_IO_PRIVATE_DATA *HdaPrivateData;
// If parameters are NULL, return error.
if (This == NULL || Verbs == NULL)
return EFI_INVALID_PARAMETER;
// Get private data and send commands.
HdaPrivateData = HDA_IO_PRIVATE_DATA_FROM_THIS(This);
return HdaControllerSendCommands(HdaPrivateData->HdaControllerDev, HdaPrivateData->HdaCodecAddress, Node, Verbs);
}
EFI_STATUS
EFIAPI
HdaControllerHdaIoSetupStream(
IN EFI_HDA_IO_PROTOCOL *This,
IN EFI_HDA_IO_PROTOCOL_TYPE Type,
IN UINT16 Format,
OUT UINT8 *StreamId)
{
//DEBUG((DEBUG_INFO, "HdaControllerHdaIoSetupStream(): start\n"));
// Create variables.
EFI_STATUS Status;
HDA_IO_PRIVATE_DATA *HdaIoPrivateData;
HDA_CONTROLLER_DEV *HdaControllerDev;
EFI_PCI_IO_PROTOCOL *PciIo;
// Stream.
HDA_STREAM *HdaStream;
UINT16 HdaStreamFormat = 0;
UINT8 HdaStreamId = 0;
EFI_TPL OldTpl = 0;
UINT8 i;
// If a parameter is invalid, return error.
if ((This == NULL) || (Type >= EfiHdaIoTypeMaximum) || (StreamId == NULL))
return EFI_INVALID_PARAMETER;
// Get private data.
HdaIoPrivateData = HDA_IO_PRIVATE_DATA_FROM_THIS(This);
HdaControllerDev = HdaIoPrivateData->HdaControllerDev;
PciIo = HdaControllerDev->PciIo;
// Get stream.
if (Type == EfiHdaIoTypeOutput)
HdaStream = HdaIoPrivateData->HdaOutputStream;
else
HdaStream = HdaIoPrivateData->HdaInputStream;
// Get current stream ID.
Status = HdaControllerGetStreamId(HdaStream, &HdaStreamId);
if (EFI_ERROR(Status))
goto DONE;
// Is a stream ID allocated already? If so that means the stream is already
// set up and we'll need to tear it down first.
if (HdaStreamId > 0) {
Status = EFI_ALREADY_STARTED;
goto DONE;
}
// Raise TPL so we can't be messed with.
OldTpl = gBS->RaiseTPL(TPL_HIGH_LEVEL);
// Find and allocate stream ID.
for (i = HDA_STREAM_ID_MIN; i <= HDA_STREAM_ID_MAX; i++) {
if (!(HdaControllerDev->StreamIdMapping & (1 << i))) {
HdaControllerDev->StreamIdMapping |= (1 << i);
HdaStreamId = i;
break;
}
}
// If stream ID is still zero, fail.
if (HdaStreamId == 0) {
Status = EFI_OUT_OF_RESOURCES;
goto DONE;
}
// Get current format.
Status = PciIo->Mem.Read(PciIo, EfiPciIoWidthUint16, PCI_HDA_BAR,
HDA_REG_SDNFMT(HdaStream->Index), 1, &HdaStreamFormat);
if (EFI_ERROR(Status))
goto DONE;
// Reset stream if format has changed.
if (Format != HdaStreamFormat) {
// Reset stream.
// DEBUG((DEBUG_INFO, "HdaControllerHdaIoSetupStream(): format changed, resetting stream\n"));
HdaControllerDev->DmaPositions[HdaStream->Index].Position = 0;
Status = HdaControllerResetStream(HdaStream);
if (EFI_ERROR(Status))
goto DONE;
}
// Set stream ID.
Status = HdaControllerSetStreamId(HdaIoPrivateData->HdaOutputStream, HdaStreamId);
if (EFI_ERROR(Status))
goto DONE;
*StreamId = HdaStreamId;
// Set stream format.
//DEBUG((DEBUG_INFO, "HdaControllerHdaIoSetupStream(): setting format 0x%X\n", Format));
Status = PciIo->Mem.Write(PciIo, EfiPciIoWidthUint16, PCI_HDA_BAR,
HDA_REG_SDNFMT(HdaStream->Index), 1, &Format);
// if (EFI_ERROR(Status))
// goto DONE;
// Stream is ready.
// Status = EFI_SUCCESS;
DONE:
// Restore TPL if needed.
if (OldTpl)
gBS->RestoreTPL(OldTpl);
return Status;
}
EFI_STATUS
EFIAPI
HdaControllerHdaIoCloseStream(
IN EFI_HDA_IO_PROTOCOL *This,
IN EFI_HDA_IO_PROTOCOL_TYPE Type)
{
//DEBUG((DEBUG_INFO, "HdaControllerHdaIoCloseStream(): start\n"));
// Create variables.
EFI_STATUS Status;
HDA_IO_PRIVATE_DATA *HdaIoPrivateData;
HDA_CONTROLLER_DEV *HdaControllerDev;
// Stream.
HDA_STREAM *HdaStream;
UINT8 HdaStreamId = 0;
EFI_TPL OldTpl = 0;
// If a parameter is invalid, return error.
if ((This == NULL) || (Type >= EfiHdaIoTypeMaximum))
return EFI_INVALID_PARAMETER;
// Get private data.
HdaIoPrivateData = HDA_IO_PRIVATE_DATA_FROM_THIS(This);
HdaControllerDev = HdaIoPrivateData->HdaControllerDev;
// Get stream.
if (Type == EfiHdaIoTypeOutput)
HdaStream = HdaIoPrivateData->HdaOutputStream;
else
HdaStream = HdaIoPrivateData->HdaInputStream;
// Get current stream ID.
Status = HdaControllerGetStreamId(HdaStream, &HdaStreamId);
if (EFI_ERROR(Status))
goto DONE;
// Is a stream ID already at zero?
if (HdaStreamId == 0) {
Status = EFI_SUCCESS;
goto DONE;
}
// Raise TPL so we can't be messed with.
OldTpl = gBS->RaiseTPL(TPL_HIGH_LEVEL);
// Stop stream.
Status = HdaControllerHdaIoStopStream(This, Type);
if (EFI_ERROR(Status))
goto DONE;
// Set stream ID to zero.
Status = HdaControllerSetStreamId(HdaStream, 0);
if (EFI_ERROR(Status))
goto DONE;
// De-allocate stream ID from bitmap.
HdaControllerDev->StreamIdMapping &= ~(1 << HdaStreamId);
// Stream closed successfully.
// Status = EFI_SUCCESS; // already done
DONE:
// Restore TPL if needed.
if (OldTpl)
gBS->RestoreTPL(OldTpl);
return Status;
}
EFI_STATUS
EFIAPI
HdaControllerHdaIoGetStream(
IN EFI_HDA_IO_PROTOCOL *This,
IN EFI_HDA_IO_PROTOCOL_TYPE Type,
OUT BOOLEAN *State)
{
//DEBUG((DEBUG_INFO, "HdaControllerHdaIoGetStream(): start\n"));
// Create variables.
HDA_IO_PRIVATE_DATA *HdaIoPrivateData;
HDA_STREAM *HdaStream;
// If a parameter is invalid, return error.
if ((This == NULL) || (Type >= EfiHdaIoTypeMaximum) || (State == NULL))
return EFI_INVALID_PARAMETER;
// Get private data.
HdaIoPrivateData = HDA_IO_PRIVATE_DATA_FROM_THIS(This);
// Get stream.
if (Type == EfiHdaIoTypeOutput)
HdaStream = HdaIoPrivateData->HdaOutputStream;
else
HdaStream = HdaIoPrivateData->HdaInputStream;
// Get stream state.
return HdaControllerGetStream(HdaStream, State);
}
EFI_STATUS
EFIAPI
HdaControllerHdaIoStartStream(
IN EFI_HDA_IO_PROTOCOL *This,
IN EFI_HDA_IO_PROTOCOL_TYPE Type,
IN VOID *Buffer,
IN UINTN BufferLength,
IN UINTN BufferPosition OPTIONAL,
IN EFI_HDA_IO_STREAM_CALLBACK Callback OPTIONAL,
IN VOID *Context1 OPTIONAL,
IN VOID *Context2 OPTIONAL,
IN VOID *Context3 OPTIONAL)
{
//DEBUG((DEBUG_INFO, "HdaControllerHdaIoStartStream(): start\n"));
// Create variables.
EFI_STATUS Status;
HDA_IO_PRIVATE_DATA *HdaIoPrivateData;
HDA_CONTROLLER_DEV *HdaControllerDev;
EFI_PCI_IO_PROTOCOL *PciIo;
// Stream.
HDA_STREAM *HdaStream;
UINT8 HdaStreamId = 0;
UINT16 HdaStreamSts;
UINT32 HdaStreamDmaPos;
UINTN HdaStreamDmaRemainingLength;
UINTN HdaStreamCurrentBlock;
UINTN HdaStreamNextBlock;
// If a parameter is invalid, return error.
if ((This == NULL) || (Type >= EfiHdaIoTypeMaximum) ||
(Buffer == NULL) || (BufferLength == 0) || (BufferPosition >= BufferLength))
return EFI_INVALID_PARAMETER;
// Get private data.
HdaIoPrivateData = HDA_IO_PRIVATE_DATA_FROM_THIS(This);
HdaControllerDev = HdaIoPrivateData->HdaControllerDev;
PciIo = HdaControllerDev->PciIo;
// Get stream.
if (Type == EfiHdaIoTypeOutput)
HdaStream = HdaIoPrivateData->HdaOutputStream;
else
HdaStream = HdaIoPrivateData->HdaInputStream;
// Get current stream ID.
Status = HdaControllerGetStreamId(HdaStream, &HdaStreamId);
if (EFI_ERROR(Status))
return Status;
// Is a stream ID zero? If so that means the stream is not setup yet.
if (HdaStreamId == 0)
return EFI_NOT_READY;
// Reset completion bit.
HdaStreamSts = HDA_REG_SDNSTS_BCIS;
Status = PciIo->Mem.Write(PciIo, EfiPciIoWidthUint8, PCI_HDA_BAR, HDA_REG_SDNSTS(HdaStream->Index), 1, &HdaStreamSts);
if (EFI_ERROR(Status))
return Status;
// Get current DMA position.
HdaStreamDmaPos = HdaControllerDev->DmaPositions[HdaStream->Index].Position;
HdaStreamCurrentBlock = HdaStreamDmaPos / HDA_BDL_BLOCKSIZE;
HdaStreamNextBlock = HdaStreamCurrentBlock + 1;
HdaStreamNextBlock %= HDA_BDL_ENTRY_COUNT;
// DEBUG((DEBUG_INFO, "HdaControllerHdaIoStartStream(): stream %u DMA pos 0x%X\n",
// HdaStream->Index, HdaStreamDmaPos));
// Save pointer to buffer.
HdaStream->BufferSource = Buffer;
HdaStream->BufferSourceLength = BufferLength;
HdaStream->BufferSourcePosition = BufferPosition;
HdaStream->Callback = Callback;
HdaStream->CallbackContext1 = Context1;
HdaStream->CallbackContext2 = Context2;
HdaStream->CallbackContext3 = Context3;
// Zero out buffer.
gBS->SetMem(HdaStream->BufferData, HDA_STREAM_BUF_SIZE, 0);
// Fill rest of current block.
HdaStreamDmaRemainingLength = HDA_BDL_BLOCKSIZE - (HdaStreamDmaPos - (HdaStreamCurrentBlock * HDA_BDL_BLOCKSIZE));
if ((HdaStream->BufferSourcePosition + HdaStreamDmaRemainingLength) > BufferLength)
HdaStreamDmaRemainingLength = BufferLength;
gBS->CopyMem(HdaStream->BufferData + HdaStreamDmaPos, HdaStream->BufferSource + HdaStream->BufferSourcePosition, HdaStreamDmaRemainingLength);
HdaStream->BufferSourcePosition += HdaStreamDmaRemainingLength;
// DEBUG((DEBUG_INFO, "%u (0x%X) bytes written to 0x%X (block %u of %u)\n", HdaStreamDmaRemainingLength, HdaStreamDmaRemainingLength,
// HdaStream->BufferData + HdaStreamDmaPos, HdaStreamCurrentBlock, HDA_BDL_ENTRY_COUNT));
// Fill next block.
if (HdaStream->BufferSourcePosition < BufferLength) {
HdaStreamDmaRemainingLength = HDA_BDL_BLOCKSIZE;
if ((HdaStream->BufferSourcePosition + HdaStreamDmaRemainingLength) > BufferLength)
HdaStreamDmaRemainingLength = BufferLength;
gBS->CopyMem(HdaStream->BufferData + (HdaStreamNextBlock * HDA_BDL_BLOCKSIZE), HdaStream->BufferSource + HdaStream->BufferSourcePosition, HdaStreamDmaRemainingLength);
HdaStream->BufferSourcePosition += HdaStreamDmaRemainingLength;
// DEBUG((DEBUG_INFO, "%u (0x%X) bytes written to 0x%X (block %u of %u)\n", HdaStreamDmaRemainingLength, HdaStreamDmaRemainingLength,
// HdaStream->BufferData + (HdaStreamNextBlock * HDA_BDL_BLOCKSIZE), HdaStreamNextBlock, HDA_BDL_ENTRY_COUNT));
}
// Setup polling timer.
HdaStream->BufferSourceDone = FALSE;
Status = gBS->SetTimer(HdaStream->PollTimer, TimerPeriodic, HDA_STREAM_POLL_TIME);
if (EFI_ERROR(Status))
return Status;
// Change stream state.
Status = HdaControllerSetStream(HdaStream, TRUE);
if (EFI_ERROR(Status))
HdaControllerHdaIoStopStream(This, Type);
// return EFI_SUCCESS;
//STOP_STREAM:
// Stop stream.
return Status;
}
EFI_STATUS
EFIAPI
HdaControllerHdaIoStopStream(
IN EFI_HDA_IO_PROTOCOL *This,
IN EFI_HDA_IO_PROTOCOL_TYPE Type)
{
//DEBUG((DEBUG_INFO, "HdaControllerHdaIoStopStream(): start\n"));
// Create variables.
EFI_STATUS Status;
HDA_IO_PRIVATE_DATA *HdaIoPrivateData;
// Stream.
HDA_STREAM *HdaStream;
UINT8 HdaStreamId;
// If a parameter is invalid, return error.
if ((This == NULL) || (Type >= EfiHdaIoTypeMaximum))
return EFI_INVALID_PARAMETER;
// Get private data.
HdaIoPrivateData = HDA_IO_PRIVATE_DATA_FROM_THIS(This);
// Get stream.
if (Type == EfiHdaIoTypeOutput)
HdaStream = HdaIoPrivateData->HdaOutputStream;
else
HdaStream = HdaIoPrivateData->HdaInputStream;
// Get current stream ID.
Status = HdaControllerGetStreamId(HdaStream, &HdaStreamId);
if (EFI_ERROR(Status))
return Status;
// Is the stream ID zero? If so that means the stream is not setup yet.
if (HdaStreamId == 0)
return EFI_NOT_READY;
// Cancel polling timer.
Status = gBS->SetTimer(HdaStream->PollTimer, TimerCancel, 0);
if (EFI_ERROR(Status))
return Status;
// Stop stream.
Status = HdaControllerSetStream(HdaStream, FALSE);
if (EFI_ERROR(Status))
return Status;
// Remove source buffer pointer.
HdaStream->BufferSource = NULL;
HdaStream->BufferSourceLength = 0;
HdaStream->BufferSourcePosition = 0;
HdaStream->Callback = NULL;
HdaStream->CallbackContext1 = NULL;
HdaStream->CallbackContext2 = NULL;
HdaStream->CallbackContext3 = NULL;
return EFI_SUCCESS;
}