mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-11-15 09:36:35 +00:00
thermosphere: tegra uart driver rewrite
This commit is contained in:
parent
cefd66e7af
commit
3a79a7a961
2 changed files with 341 additions and 0 deletions
131
thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.cpp
Normal file
131
thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
210
thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.hpp
Normal file
210
thermosphere/src/drivers/tegra/hvisor_drivers_tegra_uart.hpp
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue