Newer
Older
minerva / Kernel / Arch / aarch64 / Serial / PL011.cpp
@minerva minerva on 13 Jul 4 KB Initial commit
/*
 * Copyright (c) 2021, Nico Weber <thakis@chromium.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/Singleton.h>
#include <Kernel/Arch/aarch64/Serial/PL011.h>

namespace Kernel {

// 3.2 Summary of registers
struct PL011Registers {
    u32 data;
    u32 receive_status_or_error_clear;
    u32 unused[4];
    u32 flag;
    u32 unused2;

    u32 irda_low_power_counter;
    u32 integer_baud_rate_divisor;    // Only the lowest 16 bits are used.
    u32 fractional_baud_rate_divisor; // Only the lowest 6 bits are used.
    u32 line_control;

    u32 control;
    u32 interrupt_fifo_level_select;
    u32 interrupt_mask_set_clear;
    u32 raw_interrupt_status;

    u32 masked_interrupt_status;
    u32 interrupt_clear;
    u32 dma_control;
};
static_assert(AssertSize<PL011Registers, 0x4c>());

// Bits of the `flag` register.
// 3.3.3 Flag Register, UARTFR
enum FlagBits {
    ClearToSend = 1 << 0,
    DataSetReady = 1 << 1,
    DataCarrierDetect = 1 << 2,
    UARTBusy = 1 << 3,
    ReceiveFifoEmpty = 1 << 4,
    TransmitFifoFull = 1 << 5,
    ReceiveFifoFull = 1 << 6,
    TransmitFifoEmpty = 1 << 7,
    RingIndicator = 1 << 8,
};

// Bits for the `line_control` register.
// 3.3.7 Line Control Register, UARTLCR_H
enum LineControlBits {
    SendBreak = 1 << 0,
    EnableParityCheckingAndGeneration = 1 << 1,
    EvenParity = 1 << 2,
    TransmitTwoStopBits = 1 << 3,
    EnableFIFOs = 1 << 4,

    WordLength5Bits = 0b00 << 5,
    WordLength6Bits = 0b01 << 5,
    WordLength7Bits = 0b10 << 5,
    WordLength8Bits = 0b11 << 5,

    StickParity = 1 << 7,
};

// Bits for the `control` register.
// 3.3.8 Control Register, UARTCR
//     NOTE: 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 setting the FEN bit to 0 in the Line Control Register, UARTLCR_H.
//     4. Reprogram the Control Register, UARTCR.
//     5. Enable the UART
enum ControlBits {
    UARTEnable = 1 << 0,
    SIREnable = 1 << 1,
    SIRLowPowerIrDAModeEnable = 1 << 2,
    // Bits 3-6 are reserved.
    LoopbackEnable = 1 << 7,
    TransmitEnable = 1 << 8,
    ReceiveEnable = 1 << 9,
    DataTransmitReady = 1 << 10,
    RequestToSend = 1 << 11,
    Out1 = 1 << 12,
    Out2 = 1 << 13,
    RTSHardwareFlowControlEnable = 1 << 14,
    CTSHardwareFlowControlEnable = 1 << 15,
};

PL011::PL011(Memory::TypedMapping<PL011Registers volatile> registers_mapping)
    : m_registers(move(registers_mapping))
{
    // Disable UART while changing configuration.
    m_registers->control = 0;

    // FIXME: Should wait for current transmission to end and should flush FIFO.

    m_registers->line_control = EnableFIFOs | WordLength8Bits;

    m_registers->control = UARTEnable | TransmitEnable | ReceiveEnable;
}

ErrorOr<NonnullOwnPtr<PL011>> PL011::initialize(PhysicalAddress physical_address)
{
    auto registers_mapping = TRY(Memory::map_typed_writable<PL011Registers volatile>(physical_address));
    return TRY(adopt_nonnull_own_or_enomem(new (nothrow) PL011(move(registers_mapping))));
}

void PL011::send(u32 c)
{
    wait_until_we_can_send();
    m_registers->data = c;
}

void PL011::print_str(char const* s, size_t length)
{
    for (size_t i = 0; i < length; ++i) {
        char character = *s++;
        if (character == '\n')
            send('\r');
        send(character);
    }
}

u32 PL011::receive()
{
    wait_until_we_can_receive();

    // Mask out error bits.
    return m_registers->data & 0xFF;
}

void PL011::set_baud_rate(int baud_rate, int uart_frequency_in_hz)
{
    // 3.3.6 Fractional Baud Rate Register, UARTFBRD: """Baud rate divisor BAUDDIV = (FUARTCLK/(16 * Baud rate))""".
    // BAUDDIV is stored as a 16.6 fixed point value, so do computation scaled by (1 << 6) == 64.
    // 64*(FUARTCLK/(16 * Baud rate)) == 4*FUARTCLK/(Baud rate). For rounding, add 0.5 == (Baud rate/2)/(Baud rate).
    u32 baud_rate_divisor_fixed_point = (4 * uart_frequency_in_hz + baud_rate / 2) / baud_rate;

    m_registers->integer_baud_rate_divisor = baud_rate_divisor_fixed_point / 64;
    m_registers->fractional_baud_rate_divisor = baud_rate_divisor_fixed_point % 64;
}

void PL011::wait_until_we_can_send()
{
    while (m_registers->flag & TransmitFifoFull)
        Processor::wait_check();
}

void PL011::wait_until_we_can_receive()
{
    while (m_registers->flag & ReceiveFifoEmpty)
        Processor::wait_check();
}

}