From 3a79a7a96177d80393bd735d4030ad28d467c0b5 Mon Sep 17 00:00:00 2001 From: TuxSH <1922548+TuxSH@users.noreply.github.com> Date: Fri, 6 Mar 2020 18:54:38 +0000 Subject: [PATCH] thermosphere: tegra uart driver rewrite --- .../tegra/hvisor_drivers_tegra_uart.cpp | 131 +++++++++++ .../tegra/hvisor_drivers_tegra_uart.hpp | 210 ++++++++++++++++++ 2 files changed, 341 insertions(+) create mode 100644 thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.cpp create mode 100644 thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.hpp diff --git a/thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.cpp b/thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.cpp new file mode 100644 index 000000000..68c85cfb1 --- /dev/null +++ b/thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.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_tegra_uart.hpp" +#include "../../hvisor_generic_timer.hpp" + +#include + +using namespace ams::hvisor; + +namespace { + + inline void WaitCycles(u32 baudRate, u32 num) + { + u32 t = (num * 1000000 + 16 * baudRate - 1) / (16 * baudRate); + GenericTimer::GetInstance().Wait(std::chrono::microseconds{t}); + } + + inline void WaitSyms(u32 baudRate, u32 num) + { + u32 t = (num * 1000000 + baudRate - 1) / baudRate; + GenericTimer::GetInstance().Wait(std::chrono::microseconds{t}); + } + +} + +namespace ams::hvisor::drivers::tegra { + + void Uart::Initialize(u32 baudRate, u32 clkRate, bool invertTx) const + { + // Calculate baud rate, round to nearest (clkRate / (16 * baudRate)) + u32 divisor = (8 * baudRate + clkRate) / (16 * baudRate); + + m_regs->lcr &= ~LCR_DLAB; // Disable DLAB. + m_regs->ier = 0; // Disable all interrupts. + m_regs->mcr = 0; + + // Setup UART in FIFO mode + m_regs->lcr = LCR_DLAB | LCR_WD_LENGTH_8; // Enable DLAB and set word length 8. + m_regs->dll = divisor & 0xFF; // Divisor latch LSB. + m_regs->dlh = (divisor >> 8) & 0xFF; // Divisor latch MSB. + m_regs->lcr &= ~LCR_DLAB; // Disable DLAB. + m_regs->spr; // Dummy read. + WaitSyms(baudRate, 3); // Wait for 3 symbols at the new baudrate. + + // Enable FIFO with default settings. + m_regs->fcr = FCR_FCR_EN_FIFO; + m_regs->irda_csr = invertTx ? IRDA_CSR_INVERT_TXD : 0; // Invert TX if needed + m_regs->spr; // Dummy read as mandated by TRM. + WaitCycles(baudRate, 3); // Wait for 3 baud cycles, as mandated by TRM (erratum). + + // Flush FIFO. + WaitIdle(STATUS_TX_IDLE); // Make sure there's no data being written in TX FIFO (TRM). + m_regs->fcr |= FCR_RX_CLR | FCR_TX_CLR; // Clear TX and RX FIFOs. + WaitCycles(baudRate, 32); // Wait for 32 baud cycles (TRM, erratum). + + // Wait for idle state (TRM). + WaitIdle(STATUS_TX_IDLE | STATUS_RX_IDLE); + } + + void Uart::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->lsr & LSR_THRE)); // Wait until it's possible to send data. + m_regs->thr = buf8[i]; + } + } + + void Uart::ReadData(void *buffer, size_t size) const + { + u8 *buf8 = reinterpret_cast(buffer); + for (size_t i = 0; i < size; i++) { + while (!(m_regs->lsr & LSR_RDR)) // Wait until it's possible to receive data. + buf8[i] = m_regs->rbr; + } + } + + size_t Uart::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->lsr & LSR_RDR); i++) { + buf8[i] = m_regs->rbr; + ++count; + } + + return count; + } + + size_t Uart::ReadDataUntil(char *buffer, size_t maxSize, char delimiter) const + { + size_t count = 0; + + for (size_t i = 0; i < maxSize && (m_regs->lsr & LSR_RDR); i++) { + while (!(m_regs->lsr & LSR_RDR)) // Wait until it's possible to receive data. + + buffer[i] = m_regs->rbr; + ++count; + if (buffer[i] == delimiter) { + break; + } + } + + return count; + } + + void Uart::SetRxInterruptEnabled(bool enabled) const + { + constexpr u32 mask = IER_IE_RX_TIMEOUT | IER_IE_RHR; + + // We don't support any other interrupt here. + m_regs->ier = enabled ? mask : 0; + } +} \ No newline at end of file diff --git a/thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.hpp b/thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.hpp new file mode 100644 index 000000000..eb8e8c63f --- /dev/null +++ b/thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.hpp @@ -0,0 +1,210 @@ +/* + * 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" + +namespace ams::hvisor::drivers::tegra { + + class Uart final { + private: + struct Registers { + union { + // UART_THR_DLAB_0 + u32 thr; + u32 rbr; + u32 dll; + }; + union { + // UART_IER_DLAB_0 + u32 ier; + u32 dlh; + }; + union { + // UART_IIR_FCR_0 + u32 iir; + u32 fcr; + }; + u32 lcr; + u32 mcr; + u32 lsr; + u32 msr; + u32 spr; + u32 irda_csr; + u32 rx_fifo_cfg; + u32 mie; + u32 vendor_status; + u8 _0x30[0x0C]; + u32 asr; + }; + static_assert(std::is_standard_layout_v); + static_assert(std::is_trivial_v); + + // 36.3.12 UART_VENDOR_STATUS_0_0 + enum VendorStatusFlags : u32 { + STATUS_TX_FIFO_COUNTER = 0b111111 << 24, // reflects number of current entries in TX FIFO + STATUS_RX_FIFO_COUNTER = 0b111111 << 16, // reflects number of current entries in RX FIFO + + /* + This bit is set to 1 when write data is issued to the TX FIFO when + it is already full and gets cleared on register read (sticky bit until read): + 0 = NO_OVERRUN + 1 = OVERRUN + */ + STATUS_TX_OVERRUN = BIT(3), + + /* + This bit is set to 1 when a read is issued to an empty FIFO and + gets cleared on register read (sticky bit until read): + 0 = NO_UNDERRUN + 1 = UNDERRUN + */ + STATUS_RX_UNDERRUN = BIT(2), + STATUS_RX_IDLE = BIT(1), + STATUS_TX_IDLE = BIT(0), + }; + + // 36.3.6 UART_LSR_0 + enum LsrFlags : u32 { + LSR_RX_FIFO_EMPTY = BIT(9), // Receiver FIFO empty status + LSR_TX_FIFO_FULL = BIT(8), // Transmitter FIFO full status + LSR_FIFOE = BIT(7), // Receive FIFO Error + LSR_TMTY = BIT(6), // Transmit Shift Register empty status + LSR_THRE = BIT(5), // Transmit Holding Register is Empty -- OK to write data + LSR_BRK = BIT(4), // BREAK condition detected on line + LSR_FERR = BIT(3), // Framing Error + LSR_PERR = BIT(2), // Parity Error + LSR_OVRF = BIT(1), // Receiver Overrun Error + LSR_RDR = BIT(0), // Receiver Data Ready + }; + + // 36.3.4 UART_LCR_0 + enum LcrFlags : u32 { + /* + STOP: + 0 = Transmit 1 stop bit + 1 = Transmit 2 stop bits (receiver always checks for 1 stop bit) + */ + LCR_DLAB = BIT(7), // Divisor Latch Access Bit (set to allow programming of the DLH, DLM Divisors) + LCR_SET_B = BIT(6), // Set BREAK condition -- Transmitter sends all zeroes to indicate BREAK + LCR_SET_P = BIT(5), // Set (force) parity to value in LCR[4] + LCR_EVEN = BIT(4), // Even parity format. There will always be an even number of 1s in the binary representation (PAR = 1) + LCR_PAR = BIT(3), // Parity enabled + LCR_STOP = BIT(2), + + LCR_WD_LENGTH_5 = 0 << 0, // word length 5 + LCR_WD_LENGTH_6 = 1 << 0, // word length 6 + LCR_WD_LENGTH_7 = 2 << 0, // word length 7 + LCR_WD_LENGTH_8 = 3 << 0, // word length 8 + }; + + // 36.3.3 UART_IIR_FCR_0 + enum FcrFlags : u32{ + // RX_TRIG + FCR_RX_TRIG_MASK = 3 << 6, + FCR_RX_TRIG_FIFO_COUNT_GREATER_1 = 0 << 6, + FCR_RX_TRIG_FIFO_COUNT_GREATER_4 = 1 << 6, + FCR_RX_TRIG_FIFO_COUNT_GREATER_8 = 2 << 6, + FCR_RX_TRIG_FIFO_COUNT_GREATER_16 = 3 << 6, + + // TX_TRIG + FCR_TX_TRIG_MASK = 3 << 4, + FCR_TX_TRIG_FIFO_COUNT_GREATER_16 = 0 << 4, + FCR_TX_TRIG_FIFO_COUNT_GREATER_8 = 1 << 4, + FCR_TX_TRIG_FIFO_COUNT_GREATER_4 = 2 << 4, + FCR_TX_TRIG_FIFO_COUNT_GREATER_1 = 3 << 4, + + /* + DMA: + 0 = DMA_MODE_0 + 1 = DMA_MODE_1 + */ + FCR_DMA = BIT(3), + + /* + RX/TX_CLR: + Clears the contents of the receive (resp. transmit) FIFO and resets + its counter logic to 0. + The receive (resp. transmit) shift register is not cleared or altered. + This bit returns to 0 after clearing the FIFOs. + */ + FCR_TX_CLR = BIT(2), // See above + FCR_RX_CLR = BIT(1), // See above + FCR_FCR_EN_FIFO = BIT(0), // Enable the transmit and receive FIFOs. This bit should be enabled + }; + + // 36.3.2 UART_IER_DLAB_0_0 + enum IerFlags : u32 { + IER_IE_EORD = BIT(5), // Interrupt enable for Interrupt Enable for End of Received Data + IER_IE_RX_TIMEOUT = BIT(4), // Interrupt enable for RX FIFO timeout + IER_IE_MSI = BIT(3), // Interrupt enable for Modem Status Interrupt + IER_IE_RXS = BIT(2), // Interrupt enable for Receiver Line Status Interrupt + IER_IE_THR = BIT(1), // Interrupt enable for Transmitter Holding Register Empty interrupt + IER_IE_RHR = BIT(0), // Interrupt enable for Received Data Interrupt + }; + + // 6.3.3 UART_IIR_FCR_0 + enum IirFlags : u32 { + IIR_EN_FIFO_MASK = 3 << 6, + IIR_MODE_16450 = 0 << 6, + IIR_MODE_16550 = 1 << 6, + + IIR_IS_PRI2 = BIT(3), // Encoded Interrupt ID Refer to IIR[3:0] table + IIR_IS_PRI1 = BIT(2), // Encoded Interrupt ID Refer to IIR[3:0] table + IIR_IS_PRI0 = BIT(1), // Encoded Interrupt ID Refer to IIR[3:0] table [36.3.3] + IIR_IS_STA = BIT(0), // Interrupt Pending if ZERO + }; + + // 36.3.9 UART_IRDA_CSR_0 + enum IrdaCsrFlags : u32{ + IRDA_CSR_SIR_A = BIT(7), + + IRDA_CSR_PWT_A_BAUD_PULSE_3_14 = 0 << 6, + IRDA_CSR_PWT_A_BAUD_PULSE_4_14 = 1 << 6, + + IRDA_CSR_INVERT_RTS = BIT(3), + IRDA_CSR_INVERT_CTS = BIT(2), + IRDA_CSR_INVERT_TXD = BIT(1), + IRDA_CSR_INVERT_RXD = BIT(0), + }; + + private: + // TODO friend + volatile Registers *m_regs = nullptr; + + private: + void Initialize(u32 baudRate, u32 clkRate, bool invertTx) const; + void WaitIdle(u32 status) const + { + if (status & STATUS_TX_IDLE) { + while (!(m_regs->lsr & LSR_TMTY)); + } + + if (status & STATUS_RX_IDLE) { + while (m_regs->lsr & LSR_RDR); + } + } + + 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; + }; +} \ No newline at end of file