/** @file
|
PCH Serial IO UART Lib implementation.
|
All function in this library is available for PEI, DXE, and SMM,
|
But do not support UEFI RUNTIME environment call.
|
|
Copyright (c) 2019 Intel Corporation. All rights reserved. <BR>
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
**/
|
|
#include <Base.h>
|
#include <Uefi/UefiBaseType.h>
|
#include <Library/IoLib.h>
|
#include <Library/BaseLib.h>
|
#include <Library/PchPcrLib.h>
|
#include <Library/PchSerialIoLib.h>
|
#include <Library/PchSerialIoUartLib.h>
|
#include <IndustryStandard/Pci30.h>
|
|
#define MAX_BAUD_RATE 115200
|
|
#define R_PCH_SERIAL_IO_8BIT_UART_RXBUF 0x00
|
#define R_PCH_SERIAL_IO_8BIT_UART_TXBUF 0x00
|
#define R_PCH_SERIAL_IO_8BIT_UART_BAUD_LOW 0x00
|
#define R_PCH_SERIAL_IO_8BIT_UART_BAUD_HIGH 0x01
|
#define R_PCH_SERIAL_IO_8BIT_UART_FCR 0x02
|
#define R_PCH_SERIAL_IO_8BIT_UART_IIR 0x02
|
#define R_PCH_SERIAL_IO_8BIT_UART_LCR 0x03
|
#define R_PCH_SERIAL_IO_8BIT_UART_MCR 0x04
|
#define R_PCH_SERIAL_IO_8BIT_UART_LSR 0x05
|
#define R_PCH_SERIAL_IO_8BIT_UART_USR 0x1F
|
#define R_PCH_SERIAL_IO_32BIT_UART_CTR 0xFC //Component Type Register contains identification code
|
#define UART_COMPONENT_IDENTIFICATION_CODE 0x44570110
|
|
#define B_PCH_SERIAL_IO_UART_IIR_FIFOSE BIT7|BIT6
|
#define B_PCH_SERIAL_IO_UART_LSR_RXDA BIT0
|
#define B_PCH_SERIAL_IO_UART_LSR_BI BIT4
|
#define B_PCH_SERIAL_IO_UART_LSR_TXRDY BIT5
|
#define B_PCH_SERIAL_IO_UART_LCR_DLAB BIT7
|
#define B_PCH_SERIAL_IO_UART_FCR_FCR BIT0
|
#define B_PCH_SERIAL_IO_UART_MCR_RTS BIT1
|
#define B_PCH_SERIAL_IO_UART_MCR_AFCE BIT5
|
#define B_PCH_SERIAL_IO_UART_USR_TFNF BIT1
|
|
/**
|
Returns UART's currently active access mode, 8 or 32 bit
|
|
@param[in] MmioBase Base address of UART MMIO space
|
|
@retval AccessMode8bit
|
@retval AccessMode32bit
|
**/
|
UART_ACCESS_MODE
|
DetectAccessMode (
|
IN UINTN MmioBase
|
)
|
{
|
if (MmioRead32 (MmioBase + R_PCH_SERIAL_IO_32BIT_UART_CTR) == UART_COMPONENT_IDENTIFICATION_CODE) {
|
return AccessMode32bit;
|
} else {
|
return AccessMode8bit;
|
}
|
}
|
|
|
/**
|
Register access helper. Depending on SerialIO UART mode,
|
its registers are aligned to 1 or 4 bytes and have 8 or 32bit size
|
|
@param[in] AccessMode Selects between 8bit access to 1-byte aligned registers or 32bit access to 4-byte algined
|
@param[in] BaseAddress Base address of UART MMIO space
|
@param[in] Offset Register offset in 8bit mode
|
@param[in] Data Data to be written
|
**/
|
STATIC
|
VOID
|
WriteRegister (
|
IN UART_ACCESS_MODE AccessMode,
|
IN UINTN BaseAddress,
|
IN UINTN Offset,
|
IN UINT8 Data
|
)
|
{
|
if (AccessMode == AccessMode32bit) {
|
MmioWrite32 (BaseAddress + 4*Offset, Data);
|
} else {
|
MmioWrite8 (BaseAddress + Offset, Data);
|
}
|
}
|
|
/**
|
Register access helper. Depending on SerialIO UART mode,
|
its registers are aligned to 1 or 4 bytes and have 8 or 32bit size
|
|
@param[in] AccessMode Selects between 8bit access to 1-byte aligned registers or 32bit access to 4-byte algined
|
@param[in] BaseAddress Base address of UART MMIO space
|
@param[in] Offset Register offset in 8bit mode
|
@retval retrieved register value, always 8bit regardless of access mode
|
**/
|
STATIC
|
UINT8
|
ReadRegister (
|
IN UART_ACCESS_MODE AccessMode,
|
IN UINTN BaseAddress,
|
IN UINTN Offset
|
)
|
{
|
if (AccessMode == AccessMode32bit) {
|
return (UINT8) (0xFF & MmioRead32 (BaseAddress + 4*Offset));
|
} else {
|
return MmioRead8 (BaseAddress + Offset);
|
}
|
}
|
|
/**
|
SerialIo UART in PCI mode will become unavailable when PCI enumerator
|
disables its memory space. This function re-enables it
|
|
@param[in] UartNumber Selects Serial IO UART device (0-2)
|
**/
|
STATIC
|
VOID
|
EnablePciMse (
|
IN UINT8 UartNumber
|
)
|
{
|
UINTN CfgSpace;
|
|
CfgSpace = FindSerialIoBar (UartNumber + PchSerialIoIndexUart0, 1);
|
if (MmioRead16 (CfgSpace + PCI_VENDOR_ID_OFFSET) == 0xFFFF) {
|
return;
|
}
|
if ((MmioRead16 (CfgSpace + PCI_COMMAND_OFFSET) & EFI_PCI_COMMAND_MEMORY_SPACE) != EFI_PCI_COMMAND_MEMORY_SPACE) {
|
if ((MmioRead32 (CfgSpace + PCI_BASE_ADDRESSREG_OFFSET) & 0xFFFFF000) != 0x0 &&
|
(MmioRead32 (CfgSpace + PCI_BASE_ADDRESSREG_OFFSET) & 0xFFFFF000) != 0xFFFFF000 ) {
|
MmioOr8 (CfgSpace + PCI_COMMAND_OFFSET, EFI_PCI_COMMAND_MEMORY_SPACE);
|
}
|
}
|
}
|
|
/**
|
Initialize selected SerialIo UART.
|
This init function MUST be used prior any SerialIo UART functions to init serial io controller if platform is going use serialio UART as debug output.
|
|
@param UartNumber Selects Serial IO UART device (0-2)
|
@param FifoEnable When TRUE, enables 64-byte FIFOs.
|
@param BaudRate Baud rate.
|
@param LineControl Data length, parity, stop bits.
|
@param HardwareFlowControl Automated hardware flow control. If TRUE, hardware automatically checks CTS when sending data, and sets RTS when receiving data.
|
**/
|
VOID
|
EFIAPI
|
PchSerialIoUartInit (
|
UINT8 UartNumber,
|
BOOLEAN FifoEnable,
|
UINT32 BaudRate,
|
UINT8 LineControl,
|
BOOLEAN HardwareFlowControl
|
)
|
{
|
UINTN Base;
|
UINTN Divisor;
|
UART_ACCESS_MODE AccessMode;
|
|
Base = FindSerialIoBar (UartNumber + PchSerialIoIndexUart0, 0);
|
if ((Base & 0xFFFFFF00) == 0x0 || (Base & 0xFFFFF000) == 0xFFFFF000) {
|
//
|
// Base is not programmed, skip it.
|
//
|
return;
|
}
|
EnablePciMse (UartNumber);
|
AccessMode = DetectAccessMode (Base);
|
|
Divisor = MAX_BAUD_RATE / BaudRate;
|
|
//
|
// Configure baud rate
|
//
|
WriteRegister (AccessMode, Base, R_PCH_SERIAL_IO_8BIT_UART_LCR, B_PCH_SERIAL_IO_UART_LCR_DLAB);
|
WriteRegister (AccessMode, Base, R_PCH_SERIAL_IO_8BIT_UART_BAUD_HIGH, (UINT8) (Divisor >> 8));
|
WriteRegister (AccessMode, Base, R_PCH_SERIAL_IO_8BIT_UART_BAUD_LOW, (UINT8) (Divisor & 0xff));
|
//
|
// Configure Line control and switch back to bank 0
|
//
|
WriteRegister (AccessMode, Base, R_PCH_SERIAL_IO_8BIT_UART_LCR, LineControl & 0x1F);
|
//
|
// Enable and reset FIFOs
|
//
|
WriteRegister (AccessMode, Base, R_PCH_SERIAL_IO_8BIT_UART_FCR, FifoEnable?B_PCH_SERIAL_IO_UART_FCR_FCR:0 );
|
//
|
// Put Modem Control Register(MCR) into its reset state of 0x00.
|
//
|
WriteRegister (AccessMode, Base, R_PCH_SERIAL_IO_8BIT_UART_MCR, B_PCH_SERIAL_IO_UART_MCR_RTS | (HardwareFlowControl?B_PCH_SERIAL_IO_UART_MCR_AFCE:0));
|
}
|
|
/**
|
Write data to serial device.
|
|
If the buffer is NULL, then return 0;
|
if NumberOfBytes is zero, then return 0.
|
|
@param UartNumber Selects Serial IO UART device (0-2)
|
@param Buffer Point of data buffer which need to be writed.
|
@param NumberOfBytes Number of output bytes which are cached in Buffer.
|
|
@retval Actual number of bytes writed to serial device.
|
**/
|
UINTN
|
EFIAPI
|
PchSerialIoUartOut (
|
IN UINT8 UartNumber,
|
IN UINT8 *Buffer,
|
IN UINTN NumberOfBytes
|
)
|
{
|
UINTN BytesLeft;
|
UINTN Base;
|
UART_ACCESS_MODE AccessMode;
|
|
if (NULL == Buffer) {
|
return 0;
|
}
|
|
Base = FindSerialIoBar (UartNumber + PchSerialIoIndexUart0, 0);
|
//
|
// Sanity checks to avoid infinite loop when trying to print through uninitialized UART
|
//
|
if ((Base & 0xFFFFFF00) == 0x0 || (Base & 0xFFFFF000) == 0xFFFFF000) {
|
return 0;
|
}
|
EnablePciMse (UartNumber);
|
AccessMode = DetectAccessMode (Base);
|
|
if (ReadRegister (AccessMode, Base, R_PCH_SERIAL_IO_8BIT_UART_USR) == 0xFF) {
|
return 0;
|
}
|
|
BytesLeft = NumberOfBytes;
|
|
while (BytesLeft != 0) {
|
//
|
// Write data while there's room in TXFIFO. If HW Flow Control was enabled, it happens automatically on hardware level.
|
//
|
if (ReadRegister (AccessMode, Base, R_PCH_SERIAL_IO_8BIT_UART_USR) & B_PCH_SERIAL_IO_UART_USR_TFNF) {
|
WriteRegister (AccessMode, Base, R_PCH_SERIAL_IO_8BIT_UART_TXBUF, *Buffer);
|
Buffer++;
|
BytesLeft--;
|
}
|
}
|
|
return NumberOfBytes;
|
}
|
|
/*
|
Read data from serial device and save the datas in buffer.
|
|
If the buffer is NULL, then return 0;
|
if NumberOfBytes is zero, then return 0.
|
|
@param UartNumber Selects Serial IO UART device (0-2)
|
@param Buffer Point of data buffer which need to be writed.
|
@param NumberOfBytes Number of output bytes which are cached in Buffer.
|
@param WaitUntilBufferFull When TRUE, function waits until whole buffer is filled. When FALSE, function returns as soon as no new characters are available.
|
|
@retval Actual number of bytes raed to serial device.
|
|
**/
|
UINTN
|
EFIAPI
|
PchSerialIoUartIn (
|
IN UINT8 UartNumber,
|
OUT UINT8 *Buffer,
|
IN UINTN NumberOfBytes,
|
IN BOOLEAN WaitUntilBufferFull
|
)
|
{
|
UINTN BytesReceived;
|
UINTN Base;
|
UART_ACCESS_MODE AccessMode;
|
UINT8 Lsr;
|
UINT8 Byte;
|
|
if (NULL == Buffer) {
|
return 0;
|
}
|
|
Base = FindSerialIoBar (UartNumber + PchSerialIoIndexUart0, 0);
|
//
|
// Sanity checks to avoid infinite loop when trying to print through uninitialized UART
|
//
|
if ((Base & 0xFFFFFF00) == 0x0 || (Base & 0xFFFFF000) == 0xFFFFF000) {
|
return 0;
|
}
|
EnablePciMse (UartNumber);
|
AccessMode = DetectAccessMode (Base);
|
|
BytesReceived = 0;
|
|
while (BytesReceived != NumberOfBytes) {
|
//
|
// Read the line status register
|
//
|
Lsr = ReadRegister(AccessMode, Base, R_PCH_SERIAL_IO_8BIT_UART_LSR);
|
|
//
|
// If there is data in the RX buffer, read it.
|
//
|
if ((Lsr & B_PCH_SERIAL_IO_UART_LSR_RXDA) != 0) {
|
Byte = ReadRegister (AccessMode, Base, R_PCH_SERIAL_IO_8BIT_UART_RXBUF);
|
//
|
// Check if the break interrupt bit is set. If set, the byte read from the
|
// RX buffer is invalid and should be ignored. If not set, copy the byte into
|
// the receive buffer.
|
//
|
if ((Lsr & B_PCH_SERIAL_IO_UART_LSR_BI) == 0) {
|
*Buffer = Byte;
|
Buffer++;
|
BytesReceived++;
|
}
|
} else {
|
if (!WaitUntilBufferFull) {
|
//
|
// If there's no data and function shouldn't wait, exit early
|
//
|
return BytesReceived;
|
}
|
}
|
}
|
return BytesReceived;
|
}
|
|
/**
|
Polls a serial device to see if there is any data waiting to be read.
|
|
Polls a serial device to see if there is any data waiting to be read.
|
If there is data waiting to be read from the serial device, then TRUE is returned.
|
If there is no data waiting to be read from the serial device, then FALSE is returned.
|
|
@param UartNumber Selects Serial IO UART device (0-2)
|
|
@retval TRUE Data is waiting to be read from the serial device.
|
@retval FALSE There is no data waiting to be read from the serial device.
|
|
**/
|
BOOLEAN
|
EFIAPI
|
PchSerialIoUartPoll (
|
IN UINT8 UartNumber
|
)
|
{
|
UINTN Base;
|
UART_ACCESS_MODE AccessMode;
|
|
Base = FindSerialIoBar (UartNumber + PchSerialIoIndexUart0, 0);
|
//
|
// Sanity checks to avoid infinite loop when trying to print through uninitialized UART
|
//
|
if ((Base & 0xFFFFFF00) == 0x0 || (Base & 0xFFFFF000) == 0xFFFFF000) {
|
return 0;
|
}
|
EnablePciMse (UartNumber);
|
AccessMode = DetectAccessMode (Base);
|
|
//
|
// Read the serial port status
|
//
|
if ((ReadRegister (AccessMode, Base, R_PCH_SERIAL_IO_8BIT_UART_LSR) & B_PCH_SERIAL_IO_UART_LSR_RXDA) != 0) {
|
return TRUE;
|
}
|
return FALSE;
|
}
|