diff --git a/thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.cpp b/thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.cpp new file mode 100644 index 000000000..64ad9b015 --- /dev/null +++ b/thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2019-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "hvisor_drivers_arm_pl011.hpp" + +namespace ams::hvisor::drivers::arm { + + void PL011::Initialize(u32 baudRate, u32 clkRate) const + { + /* The TRM (DDI0183) reads: + Program the control registers as follows: + 1. Disable the UART. + 2. Wait for the end of transmission or reception of the current character. + 3. Flush the transmit FIFO by disabling bit 4 (FEN) in the line control register + (UARTCLR_H). + 4. Reprogram the control register. + 5. Enable the UART. + */ + // First, disable the UART. Flush the receive FIFO, wait for tx to complete, and disable both FIFOs. + m_regs->cr &= ~UARTCR_UARTEN; + while (!(m_regs->fr & UARTFR_RXFE)) { + m_regs->dr; + } + while (m_regs->fr & UARTFR_BUSY); + // This flushes the transmit FIFO: + m_regs->lcr_h &= ~UARTLCR_H_FEN; + + // Set baudrate, Divisor = (Uart clock * 4) / baudrate; stored in IBRD|FBRD + u32 divisor = (4 * clkRate) / baudRate; + m_regs->ibrd = divisor >> 6; + m_regs->fbrd = divisor & 0x3F; + + // Select FIFO fill levels for interrupts + m_regs->ifls = IFLS_RX4_8 | IFLS_TX4_8; + + // FIFO Enabled / No Parity / 8 Data bit / One Stop Bit + m_regs->lcr_h = UARTLCR_H_FEN | UARTLCR_H_WLEN_8; + + // Select the interrupts we want to have + // RX timeout and TX/RX fill interrupts + m_regs->imsc = RTI | RXI | RXI; + + // Clear any pending errors + m_regs->ecr = 0; + + // Clear all interrupts + m_regs->icr = ALL_INTERRUPTS; + + // Enable tx, rx, and uart overall + m_regs->cr = UARTCR_RXE | UARTCR_TXE | UARTCR_UARTEN; + } + + void PL011::WriteData(const void *buffer, size_t size) const + { + const u8 *buf8 = reinterpret_cast(buffer); + for (size_t i = 0; i < size; i++) { + while (m_regs->fr & UARTFR_TXFF); // while TX FIFO full + m_regs->dr = buf8[i]; + } + + } + + void PL011::ReadData(void *buffer, size_t size) const + { + u8 *buf8 = reinterpret_cast(buffer); + size_t i; + + for (i = 0; i < size; i++) { + while (m_regs->fr & UARTFR_RXFE); + buf8[i] = m_regs->dr; + } + + } + + size_t PL011::ReadDataMax(void *buffer, size_t maxSize) const + { + u8 *buf8 = reinterpret_cast(buffer); + size_t count = 0; + + for (size_t i = 0; i < maxSize && !(m_regs->fr & UARTFR_RXFE); i++) { + buf8[i] = m_regs->dr; + ++count; + } + + return count; + + } + + size_t PL011::ReadDataUntil(char *buffer, size_t maxSize, char delimiter) const + { + size_t count = 0; + + for (size_t i = 0; i < maxSize; i++) { + while (m_regs->fr & UARTFR_RXFE); + buffer[i] = m_regs->dr; + ++count; + if (buffer[i] == delimiter) { + break; + } + } + + return count; + } + + void PL011::SetRxInterruptEnabled(bool enabled) const + { + constexpr u32 mask = RTI | RXI; + + if (enabled) { + m_regs->imsc |= mask; + } else { + m_regs->imsc &= ~mask; + } + } + +} diff --git a/thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.hpp b/thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.hpp new file mode 100644 index 000000000..bf11c7109 --- /dev/null +++ b/thermosphere/src/drivers/arm/hvisor_drivers_arm_pl011.hpp @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2019-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "../../defines.hpp" + +// AMBA PL011 driver +// Originally from +/* + * Copyright (c) 2013-2018, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +namespace ams::hvisor::drivers::arm { + + class PL011 final { + private: + struct Registers { + u32 dr; + union { + u32 sr; + u32 ecr; + }; + u32 _0x08, _0x0C, _0x10, _0x14; + u32 fr; + u32 _0x1C; + u32 ilpr; + u32 ibrd; + u32 fbrd; + u32 lcr_h; + u32 cr; + u32 ifls; + u32 imsc; + u32 ris; + u32 mis; + u32 icr; + u32 dmacr; + }; + + enum Mask : u32 { + DATA_ERROR_MASK = 0x0F00, // Data status bits + PL011_STATUS_ERROR_MASK = 0x0F, // Status reg bits + }; + + enum Error : u32 { + OE = BIT(3), // Overrun error + BE = BIT(2), // Break error + PE = BIT(1), // Parity error + FE = BIT(0), // Framing error + }; + + enum Interrupt : u32 { + OEI = BIT(10), // Overrun error interrupt + BEI = BIT(9), // Break error interrupt + PEI = BIT(8), // Parity error interrupt + FEI = BIT(7), // Framing error interrupt + RTI = BIT(6), // Receive timeout interrupt + TXI = BIT(5), // Transmit interrupt + RXI = BIT(4), // Receive interrupt + DSRMI = BIT(3), // DSR modem interrupt + DCDMI = BIT(2), // DCD modem interrupt + CTSMI = BIT(1), // CTS modem interrupt + RIMI = BIT(0), // RI modem interrupt + ALL_INTERRUPTS = MASK(11), + }; + + // Flag reg bits + enum FrFlags : u32 { + UARTFR_RI = BIT(8), // Ring indicator + UARTFR_TXFE = BIT(7), // Transmit FIFO empty + UARTFR_RXFF = BIT(6), // Receive FIFO full + UARTFR_TXFF = BIT(5), // Transmit FIFO full + UARTFR_RXFE = BIT(4), // Receive FIFO empty + UARTFR_BUSY = BIT(3), // UART busy + UARTFR_DCD = BIT(2), // Data carrier detect + UARTFR_DSR = BIT(1), // Data set ready + UARTFR_CTS = BIT(0), // Clear to send + }; + + // Control reg bits + enum CrFlags : u32 { + UARTCR_CTSEN = BIT(15), // CTS hardware flow control enable + UARTCR_RTSEN = BIT(14), // RTS hardware flow control enable + UARTCR_RTS = BIT(11), // Request to send + UARTCR_DTR = BIT(10), // Data transmit ready. + UARTCR_RXE = BIT(9), // Receive enable + UARTCR_TXE = BIT(8), // Transmit enable + UARTCR_LBE = BIT(7), // Loopback enable + UARTCR_UARTEN = BIT(0), // UART Enable + }; + + // Line Control Register Bits + enum LcrFlags : u32 { + UARTLCR_H_SPS = BIT(7), // Stick parity select + UARTLCR_H_WLEN_8 = (3 << 5), + UARTLCR_H_WLEN_7 = (2 << 5), + UARTLCR_H_WLEN_6 = BIT(5), + UARTLCR_H_WLEN_5 = (0 << 5), + UARTLCR_H_FEN = BIT(4), // FIFOs Enable + UARTLCR_H_STP2 = BIT(3), // Two stop bits select + UARTLCR_H_EPS = BIT(2), // Even parity select + UARTLCR_H_PEN = BIT(1), // Parity Enable + UARTLCR_H_BRK = BIT(0), // Send break + }; + + // FIFO level select register + enum IflsLevels : u32 { + IFLS_RX1_8 = (0 << 3), + IFLS_RX2_8 = (1 << 3), + IFLS_RX4_8 = (2 << 3), + IFLS_RX6_8 = (3 << 3), + IFLS_RX7_8 = (4 << 3), + IFLS_TX1_8 = (0 << 0), + IFLS_TX2_8 = (1 << 0), + IFLS_TX4_8 = (2 << 0), + IFLS_TX6_8 = (3 << 0), + IFLS_TX7_8 = (4 << 0), + }; + + private: + volatile Registers *m_regs = nullptr; + void Initialize(u32 baudRate, u32 clkRate = 1) const; + + // TODO friend + public: + + void WriteData(const void *buffer, size_t size) const; + void ReadData(void *buffer, size_t size) const; + size_t ReadDataMax(void *buffer, size_t maxSize) const; + size_t ReadDataUntil(char *buffer, size_t maxSize, char delimiter) const; + + void SetRxInterruptEnabled(bool enabled) const; + }; +}