blob: da435d2e9b178bf61745c0e381b0c43fa161376a [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef LIB_UART_NS8250_H_
#define LIB_UART_NS8250_H_
#include <lib/acpi_lite/debug_port.h>
#include <lib/stdcompat/array.h>
#include <lib/zbi-format/driver-config.h>
#include <zircon/limits.h>
#include <array>
#include <optional>
#include <string_view>
#include <hwreg/bitfields.h>
#include "uart.h"
// 8250 and derivatives, including 16550.
namespace uart {
namespace ns8250 {
constexpr uint32_t kDefaultBaudRate = 115200;
constexpr uint32_t kMaxBaudRate = 115200;
constexpr uint8_t kFifoDepth16750 = 64;
constexpr uint8_t kFifoDepth16550A = 16;
constexpr uint8_t kFifoDepthDw8250Minimum = 16;
constexpr uint8_t kFifoDepthGeneric = 1;
// Traditional COM1 configuration.
constexpr zbi_dcfg_simple_pio_t kLegacyConfig{.base = 0x3f8, .irq = 4};
enum class InterruptType : uint8_t {
kModemStatus = 0b0000,
kNone = 0b0001,
kTxEmpty = 0b0010,
kRxDataAvailable = 0b0100,
kRxLineStatus = 0b0110,
kDw8250BusyDetect = 0b0111, // dw8250 only
kCharTimeout = 0b1100,
};
class RxBufferRegister : public hwreg::RegisterBase<RxBufferRegister, uint8_t> {
public:
DEF_FIELD(7, 0, data);
static auto Get() { return hwreg::RegisterAddr<RxBufferRegister>(0); }
};
class TxBufferRegister : public hwreg::RegisterBase<TxBufferRegister, uint8_t> {
public:
DEF_FIELD(7, 0, data);
static auto Get() { return hwreg::RegisterAddr<TxBufferRegister>(0); }
};
class InterruptEnableRegister : public hwreg::RegisterBase<InterruptEnableRegister, uint8_t> {
public:
DEF_RSVDZ_FIELD(7, 4);
DEF_BIT(3, modem_status);
DEF_BIT(2, line_status);
DEF_BIT(1, tx_empty);
DEF_BIT(0, rx_available);
static auto Get() { return hwreg::RegisterAddr<InterruptEnableRegister>(1); }
};
class InterruptIdentRegister : public hwreg::RegisterBase<InterruptIdentRegister, uint8_t> {
public:
DEF_FIELD(7, 6, fifos_enabled);
DEF_BIT(5, extended_fifo_enabled);
DEF_RSVDZ_BIT(4);
DEF_ENUM_FIELD(InterruptType, 3, 0, interrupt_id);
static auto Get() { return hwreg::RegisterAddr<InterruptIdentRegister>(2); }
};
class FifoControlRegister : public hwreg::RegisterBase<FifoControlRegister, uint8_t> {
public:
DEF_FIELD(7, 6, receiver_trigger);
// Note: dw8250 has a TX IRQ trigger field in 5...4.
DEF_BIT(5, extended_fifo_enable);
DEF_RSVDZ_BIT(4);
DEF_BIT(3, dma_mode);
DEF_BIT(2, tx_fifo_reset);
DEF_BIT(1, rx_fifo_reset);
DEF_BIT(0, fifo_enable);
static constexpr uint8_t kMaxTriggerLevel = 0b11;
static auto Get() { return hwreg::RegisterAddr<FifoControlRegister>(2); }
};
class LineControlRegister : public hwreg::RegisterBase<LineControlRegister, uint8_t> {
public:
DEF_BIT(7, divisor_latch_access);
DEF_BIT(6, break_control);
DEF_BIT(5, stick_parity);
DEF_BIT(4, even_parity);
DEF_BIT(3, parity_enable);
DEF_BIT(2, stop_bits);
DEF_FIELD(1, 0, word_length);
static constexpr uint8_t kWordLength5 = 0b00;
static constexpr uint8_t kWordLength6 = 0b01;
static constexpr uint8_t kWordLength7 = 0b10;
static constexpr uint8_t kWordLength8 = 0b11;
static constexpr uint8_t kStopBits1 = 0b0;
static constexpr uint8_t kStopBits2 = 0b1;
static auto Get() { return hwreg::RegisterAddr<LineControlRegister>(3); }
};
class ModemControlRegister : public hwreg::RegisterBase<ModemControlRegister, uint8_t> {
public:
DEF_RSVDZ_FIELD(7, 6);
DEF_BIT(5, automatic_flow_control_enable);
DEF_BIT(4, loop);
DEF_BIT(3, auxiliary_out_2);
DEF_BIT(2, auxiliary_out_1);
DEF_BIT(1, request_to_send);
DEF_BIT(0, data_terminal_ready);
static auto Get() { return hwreg::RegisterAddr<ModemControlRegister>(4); }
};
class LineStatusRegister : public hwreg::RegisterBase<LineStatusRegister, uint8_t> {
public:
DEF_BIT(7, error_in_rx_fifo);
DEF_BIT(6, tx_empty);
DEF_BIT(5, tx_register_empty);
DEF_BIT(4, break_interrupt);
DEF_BIT(3, framing_error);
DEF_BIT(2, parity_error);
DEF_BIT(1, overrun_error);
DEF_BIT(0, data_ready);
static auto Get() { return hwreg::RegisterAddr<LineStatusRegister>(5); }
};
class ModemStatusRegister : public hwreg::RegisterBase<ModemStatusRegister, uint8_t> {
public:
DEF_BIT(7, data_carrier_detect);
DEF_BIT(6, ring_indicator);
DEF_BIT(5, data_set_ready);
DEF_BIT(4, clear_to_send);
DEF_BIT(3, delta_data_carrier_detect);
DEF_BIT(2, trailing_edge_ring_indicator);
DEF_BIT(1, delta_data_set_ready);
DEF_BIT(0, delta_clear_to_send);
static auto Get() { return hwreg::RegisterAddr<ModemStatusRegister>(6); }
};
class ScratchRegister : public hwreg::RegisterBase<ScratchRegister, uint8_t> {
public:
DEF_FIELD(7, 0, data);
static auto Get() { return hwreg::RegisterAddr<ScratchRegister>(7); }
};
class DivisorLatchLowerRegister : public hwreg::RegisterBase<DivisorLatchLowerRegister, uint8_t> {
public:
DEF_FIELD(7, 0, data);
static auto Get() { return hwreg::RegisterAddr<DivisorLatchLowerRegister>(0); }
};
class DivisorLatchUpperRegister : public hwreg::RegisterBase<DivisorLatchUpperRegister, uint8_t> {
public:
DEF_FIELD(7, 0, data);
static auto Get() { return hwreg::RegisterAddr<DivisorLatchUpperRegister>(1); }
};
// dW8250 only
class UartStatusRegister : public hwreg::RegisterBase<UartStatusRegister, uint32_t> {
public:
DEF_RSVDZ_FIELD(31, 5);
// Bits 4...1 are optionally configured in the dw8250 core.
DEF_BIT(4, receive_fifo_full);
DEF_BIT(3, receive_fifo_not_empty);
DEF_BIT(2, transmit_fifo_empty);
DEF_BIT(1, transmit_fifo_not_full);
DEF_BIT(0, uart_busy);
static auto Get() { return hwreg::RegisterAddr<UartStatusRegister>(0x7c / 4); }
};
// The scaled number of `IoSlots` used by this driver, for PIO corresponds to the number of
// IO Ports used by the driver.
template <uint32_t KdrvExtra>
inline constexpr uint32_t kIoSlots = 8;
// Accomodates for the registers specific to this model that are used in the implementation.
// Specifically `UartStatusRegister`. For Scaled MMIO, this corresponds to the number of
// unscaled registers that need to be accessed by the implementation. The MMIO region size
// can be obtained by scaling the register by their access width(`sizeof(uint32_t)`).
template <>
inline constexpr uint32_t kIoSlots<ZBI_KERNEL_DRIVER_DW8250_UART> = (0x7c + sizeof(uint32_t)) / 4;
// This provides the actual driver logic common to MMIO and PIO variants.
template <uint32_t KdrvExtra, typename KdrvConfig, IoRegisterType IoRegType,
size_t IoSlots = kIoSlots<KdrvExtra>>
class DriverImpl : public DriverBase<DriverImpl<KdrvExtra, KdrvConfig, IoRegType, IoSlots>,
KdrvExtra, KdrvConfig, IoRegType, IoSlots> {
public:
using Base = DriverBase<DriverImpl<KdrvExtra, KdrvConfig, IoRegType, IoSlots>, KdrvExtra,
KdrvConfig, IoRegType, IoSlots>;
static constexpr auto kDevicetreeBindings = []() {
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_I8250_MMIO32_UART ||
KdrvExtra == ZBI_KERNEL_DRIVER_I8250_MMIO8_UART) {
return cpp20::to_array<std::string_view>(
{"ns8250", "ns16450", "ns16550a", "ns16550", "ns16750", "ns16850"});
} else if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_DW8250_UART) {
return cpp20::to_array<std::string_view>({"snps,dw-apb-uart"});
} else {
return std::array<std::string_view, 0>{};
}
}();
static constexpr std::string_view config_name() {
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_I8250_PIO_UART) {
return "ioport";
}
#if defined(__i386__) || defined(__x86_64__)
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_I8250_MMIO32_UART) {
return "mmio";
}
#endif
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_I8250_MMIO8_UART) {
return "ns8250-8bit";
}
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_DW8250_UART) {
return "dw8250";
}
return "ns8250";
}
template <typename... Args>
explicit DriverImpl(Args&&... args) : Base(std::forward<Args>(args)...) {}
using Base::MaybeCreate;
static std::optional<DriverImpl> MaybeCreate(
const acpi_lite::AcpiDebugPortDescriptor& debug_port) {
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_I8250_MMIO32_UART) {
if (debug_port.type == acpi_lite::AcpiDebugPortDescriptor::Type::kMmio) {
return DriverImpl(KdrvConfig{.mmio_phys = debug_port.address});
}
}
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_I8250_PIO_UART) {
if (debug_port.type == acpi_lite::AcpiDebugPortDescriptor::Type::kPio) {
return DriverImpl(KdrvConfig{.base = static_cast<uint16_t>(debug_port.address)});
}
}
return {};
}
static std::optional<DriverImpl> MaybeCreate(std::string_view string) {
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_I8250_PIO_UART) {
if (string == "legacy") {
return DriverImpl{kLegacyConfig};
}
}
return Base::MaybeCreate(string);
}
static bool MatchDevicetree(const devicetree::PropertyDecoder& decoder) {
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_I8250_PIO_UART) {
return false;
} else {
// Check that the compatible property contains a compatible devicetree binding.
if (!Base::MatchDevicetree(decoder)) {
return false;
}
auto [io_width_prop, reg_shift_prop] = decoder.FindProperties("reg-io-width", "reg-shift");
std::optional<uint32_t> io_width = io_width_prop ? io_width_prop->AsUint32() : std::nullopt;
std::optional<uint32_t> reg_shift =
reg_shift_prop ? reg_shift_prop->AsUint32() : std::nullopt;
// Must provide io-width and reg-shift of 32 bits.
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_I8250_MMIO32_UART ||
KdrvExtra == ZBI_KERNEL_DRIVER_DW8250_UART) {
return io_width == 4 && reg_shift == 2;
} else {
return io_width.value_or(1) == 1 && reg_shift.value_or(0) == 0;
}
}
}
template <class IoProvider>
void Init(IoProvider& io) {
// Get basic config done so that tx functions.
// Disable all interrupts.
InterruptEnableRegister::Get().FromValue(0).WriteTo(io.io());
// Extended FIFO mode must be enabled while the divisor latch is.
// Be sure to preserve the line controls, modulo divisor latch access,
// which should be disabled immediately after configuring the FIFO.
auto lcr = LineControlRegister::Get().ReadFrom(io.io());
lcr.set_divisor_latch_access(true).WriteTo(io.io());
auto fcr = FifoControlRegister::Get().FromValue(0);
fcr.set_fifo_enable(true).set_rx_fifo_reset(true).set_tx_fifo_reset(true).set_receiver_trigger(
FifoControlRegister::kMaxTriggerLevel);
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_DW8250_UART) {
// dw8250 does not have an extended fifo enable bit in bit 5, but
// instead has a TX fifo threshold field in bits 4-5. Leave these
// as zero by not setting it here.
} else {
fcr.set_extended_fifo_enable(true);
}
fcr.WriteTo(io.io());
// Commit divisor by clearing the latch.
lcr.set_divisor_latch_access(false).WriteTo(io.io());
// Drive flow control bits high since we don't actively manage them.
auto mcr = ModemControlRegister::Get().FromValue(0);
mcr.set_data_terminal_ready(true).set_request_to_send(true).WriteTo(io.io());
// Figure out the FIFO depth.
auto iir = InterruptIdentRegister::Get().ReadFrom(io.io());
if (iir.fifos_enabled()) {
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_DW8250_UART) {
// The fifo depth isn't easily known on the dw8250, but it
// must be at least 16 bytes if the fifo is enabled.
fifo_depth_ = kFifoDepthDw8250Minimum;
} else {
// This is a 16750 or a 16550A.
fifo_depth_ = iir.extended_fifo_enabled() ? kFifoDepth16750 : kFifoDepth16550A;
}
} else {
fifo_depth_ = kFifoDepthGeneric;
}
}
template <class IoProvider>
void SetLineControl(IoProvider& io, std::optional<DataBits> data_bits,
std::optional<Parity> parity, std::optional<StopBits> stop_bits) {
constexpr uint32_t kDivisor = kMaxBaudRate / kDefaultBaudRate;
LineControlRegister::Get().FromValue(0).set_divisor_latch_access(true).WriteTo(io.io());
DivisorLatchLowerRegister::Get()
.FromValue(0)
.set_data(static_cast<uint8_t>(kDivisor))
.WriteTo(io.io());
DivisorLatchUpperRegister::Get()
.FromValue(0)
.set_data(static_cast<uint8_t>(kDivisor >> 8))
.WriteTo(io.io());
auto lcr = LineControlRegister::Get().FromValue(0).set_divisor_latch_access(false);
if (data_bits) {
const uint8_t word_length = [bits = *data_bits]() {
switch (bits) {
case DataBits::k5:
return LineControlRegister::kWordLength5;
case DataBits::k6:
return LineControlRegister::kWordLength6;
case DataBits::k7:
return LineControlRegister::kWordLength7;
case DataBits::k8:
return LineControlRegister::kWordLength8;
}
ZX_PANIC("Unknown value for DataBits enum class (%u)", static_cast<unsigned int>(bits));
}();
lcr.set_word_length(word_length);
}
if (parity) {
lcr.set_parity_enable(*parity != Parity::kNone).set_even_parity(*parity == Parity::kEven);
}
if (stop_bits) {
const uint8_t num_stop_bits = [bits = *stop_bits]() {
switch (bits) {
case StopBits::k1:
return LineControlRegister::kStopBits1;
case StopBits::k2:
return LineControlRegister::kStopBits2;
}
ZX_PANIC("Unknown value for StopBits enum class (%u)", static_cast<unsigned int>(bits));
}();
lcr.set_stop_bits(num_stop_bits);
}
lcr.WriteTo(io.io());
}
template <class IoProvider>
bool TxReady(IoProvider& io) {
return LineStatusRegister::Get().ReadFrom(io.io()).tx_register_empty();
}
template <class IoProvider, typename It1, typename It2>
auto Write(IoProvider& io, bool, It1 it, const It2& end) {
// The FIFO is empty now and we know the size, so fill it completely.
auto tx = TxBufferRegister::Get().FromValue(0);
auto space = fifo_depth_;
do {
tx.set_data(*it).WriteTo(io.io());
} while (++it != end && --space > 0);
return it;
}
template <class IoProvider>
std::optional<uint8_t> Read(IoProvider& io) {
if (LineStatusRegister::Get().ReadFrom(io.io()).data_ready()) {
return RxBufferRegister::Get().ReadFrom(io.io()).data();
}
return {};
}
template <class IoProvider>
void EnableTxInterrupt(IoProvider& io, bool enable = true) {
auto ier = InterruptEnableRegister::Get().ReadFrom(io.io());
ier.set_tx_empty(enable).WriteTo(io.io());
}
template <class IoProvider>
void EnableRxInterrupt(IoProvider& io, bool enable = true) {
auto ier = InterruptEnableRegister::Get().ReadFrom(io.io());
ier.set_rx_available(enable).WriteTo(io.io());
}
template <class IoProvider, typename EnableInterruptCallback>
void InitInterrupt(IoProvider& io, EnableInterruptCallback&& enable_interrupt_callback) {
// In x86 drivers enabling the interrupt after setting up the hardware
// may cause the Rx Interrupt never to fire.
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_I8250_PIO_UART ||
KdrvExtra == ZBI_KERNEL_DRIVER_I8250_MMIO32_UART) {
enable_interrupt_callback();
}
// Enable receive interrupts.
EnableRxInterrupt(io);
// Modem Control Register: Auxiliary Output 2 is another IRQ enable bit.
auto mcr = ModemControlRegister::Get().FromValue(0);
mcr.set_auxiliary_out_2(true).WriteTo(io.io());
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_DW8250_UART ||
KdrvExtra == ZBI_KERNEL_DRIVER_I8250_MMIO8_UART) {
enable_interrupt_callback();
}
}
template <class IoProvider, typename Lock, typename Waiter, typename Tx, typename Rx>
void Interrupt(IoProvider& io, Lock& lock, Waiter& waiter, Tx&& tx, Rx&& rx) {
auto iir = InterruptIdentRegister::Get();
InterruptType id;
while ((id = iir.ReadFrom(io.io()).interrupt_id()) != InterruptType::kNone) {
if constexpr (KdrvExtra == ZBI_KERNEL_DRIVER_DW8250_UART) {
if (id == InterruptType::kDw8250BusyDetect) {
// dw8250 only. From the manual:
// "Master has tried to write to the Line Control Register while the DW_apb_uart is busy
// (USR[0] is set to one)." Read the UART Status Register to clear it.
UartStatusRegister::Get().ReadFrom(io.io());
}
}
// Reading LSR will clear kRxLineStatus signal.
auto lsr = LineStatusRegister::Get().ReadFrom(io.io());
// Notify TX.
if (lsr.tx_register_empty()) {
tx(lock, waiter, [&]() { EnableTxInterrupt(io, false); });
}
// Drain RX while the line status bit is ready.
bool should_drain_rx = true;
for (; should_drain_rx && lsr.data_ready();
lsr = LineStatusRegister::Get().ReadFrom(io.io())) {
rx(
lock, //
[&]() { return RxBufferRegister::Get().ReadFrom(io.io()).data(); },
[&]() {
// If the buffer is full, disable the receive interrupt instead and
// exit the loop.
EnableRxInterrupt(io, false);
should_drain_rx = false;
});
}
}
}
protected:
uint8_t fifo_depth_ = kFifoDepthGeneric;
};
// uart::KernelDriver UartDriver API for PIO via MMIO where offsets expressed in bytes are scaled
// by 4. Additionally all read or write operations are performed in 4 byte regions.
using Mmio32Driver =
DriverImpl<ZBI_KERNEL_DRIVER_I8250_MMIO32_UART, zbi_dcfg_simple_t, IoRegisterType::kMmio32>;
// uart::KernelDriver UartDriver API for PIO via MMIO where offsets are expressed in bytes.
using Mmio8Driver =
DriverImpl<ZBI_KERNEL_DRIVER_I8250_MMIO8_UART, zbi_dcfg_simple_t, IoRegisterType::kMmio8>;
// uart::KernelDriver UartDriver API for direct PIO.
using PioDriver =
DriverImpl<ZBI_KERNEL_DRIVER_I8250_PIO_UART, zbi_dcfg_simple_pio_t, IoRegisterType::kPio>;
// uart::KernelDriver UartDriver API for PIO via MMIO using legacy item type.
using Dw8250Driver =
DriverImpl<ZBI_KERNEL_DRIVER_DW8250_UART, zbi_dcfg_simple_t, IoRegisterType::kMmio32>;
} // namespace ns8250
} // namespace uart
#endif // LIB_UART_NS8250_H_