/** @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.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #include #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; }