thermosphere: tegra uart driver rewrite

This commit is contained in:
TuxSH 2020-03-06 18:54:38 +00:00
parent cefd66e7af
commit 3a79a7a961
2 changed files with 341 additions and 0 deletions

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "hvisor_drivers_tegra_uart.hpp"
#include "../../hvisor_generic_timer.hpp"
#include <chrono>
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<const u8 *>(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<u8 *>(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<u8 *>(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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<Registers>);
static_assert(std::is_trivial_v<Registers>);
// 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;
};
}