blob: 7ea2004e275ce9d71777bcfebc026a1d7957f608 [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_PL011_H_
#define LIB_UART_PL011_H_
#include <lib/stdcompat/array.h>
#include <lib/zbi-format/driver-config.h>
#include <lib/zbi-format/zbi.h>
#include <array>
#include <cstdint>
#include <string_view>
#include <hwreg/bitfields.h>
#include "uart.h"
// PrimeCell® UART (PL011) Technical Reference Manual
// Revision: r1p5
// URL: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0183g/index.html
namespace uart::pl011 {
// This is where QEMU puts its emulated PL011.
constexpr zbi_dcfg_simple_t kQemuConfig{.mmio_phys = 0x09000000, .irq = 33};
// We use expanded title (first clause in the Function column of the manual)
// rather than the acronym (Name column in the manual) for readability, except
// for the RS-232 standard acronyms and tx/rx for transmit/receive.
struct DataRegister : public hwreg::RegisterBase<DataRegister, uint16_t> {
// 15:12 Reserved.
DEF_BIT(11, overrun_error);
DEF_BIT(10, break_error);
DEF_BIT(9, parity_error);
DEF_BIT(8, framing_error);
DEF_FIELD(7, 0, data);
static auto Get() { return hwreg::RegisterAddr<DataRegister>(0); }
};
struct FlagRegister : public hwreg::RegisterBase<FlagRegister, uint16_t> {
// 15:9 Reserved, do not modify.
DEF_BIT(8, ri);
DEF_BIT(7, tx_fifo_empty);
DEF_BIT(6, rx_fifo_full);
DEF_BIT(5, tx_fifo_full);
DEF_BIT(4, rx_fifo_empty);
DEF_BIT(3, busy);
DEF_BIT(2, dcd);
DEF_BIT(1, dsr);
DEF_BIT(0, cts);
static auto Get() { return hwreg::RegisterAddr<FlagRegister>(0x18); }
};
struct ControlRegister : public hwreg::RegisterBase<ControlRegister, uint16_t> {
DEF_BIT(15, cts_enable);
DEF_BIT(14, rts_enable);
DEF_BIT(13, out2);
DEF_BIT(12, out1);
DEF_BIT(11, rts);
DEF_BIT(10, dtr);
DEF_BIT(9, rx_enable);
DEF_BIT(8, tx_enable);
DEF_BIT(7, loopback_enable);
// 6:3 Reserved, do not modify.
DEF_BIT(2, sir_low_power);
DEF_BIT(1, sir_enable);
DEF_BIT(0, uart_enable);
static auto Get() { return hwreg::RegisterAddr<ControlRegister>(0x30); }
};
struct InterruptFifoLevelSelectRegister
: public hwreg::RegisterBase<InterruptFifoLevelSelectRegister, uint16_t> {
// 15:6 Reserved, do not modify.
enum class Level : uint8_t {
ONE_EIGHTH = 0b000,
ONE_QUARTER = 0b001,
ONE_HALF = 0b010,
THREE_QUARTERS = 0b011,
SEVEN_EIGHTHS = 0b100,
};
DEF_ENUM_FIELD(Level, 5, 3, rx);
DEF_ENUM_FIELD(Level, 2, 0, tx);
static auto Get() { return hwreg::RegisterAddr<InterruptFifoLevelSelectRegister>(0x34); }
};
// The three interrupt-related registers have the same fields. Neither
// inheritance nor template tricks seem to work with hwreg types, so rather
// than repeating the same fields in three types, just use one type with
// three different Get functions.
struct InterruptRegister : public hwreg::RegisterBase<InterruptRegister, uint16_t> {
// 15:11 Reserved, do not modify.
DEF_BIT(10, overrun_error);
DEF_BIT(9, break_error);
DEF_BIT(8, parity_error);
DEF_BIT(7, framing_error);
DEF_BIT(6, rx_timeout);
DEF_BIT(5, tx);
DEF_BIT(4, rx);
DEF_BIT(3, dsr);
DEF_BIT(2, dcd);
DEF_BIT(1, cts);
DEF_BIT(0, ri);
static auto Get(uint32_t offset) { return hwreg::RegisterAddr<InterruptRegister>(offset); }
};
struct InterruptMaskSetClearRegister {
static auto Get() { return InterruptRegister::Get(0x38); }
};
struct InterruptMaskedStatusRegister {
static auto Get() { return InterruptRegister::Get(0x40); }
};
struct InterruptClearRegister {
static auto Get() { return InterruptRegister::Get(0x44); }
};
struct LineControlRegister : public hwreg::RegisterBase<LineControlRegister, uint16_t> {
DEF_FIELD(15, 8, reserved);
DEF_BIT(7, enable_sticky_parity);
DEF_FIELD(6, 5, word_length);
DEF_BIT(4, fifo_enable);
DEF_BIT(3, enable_two_stop_bits);
DEF_BIT(2, enable_even_parity);
DEF_BIT(1, enable_parity);
DEF_BIT(0, enable_enable_send_break);
static auto Get() { return hwreg::RegisterAddr<LineControlRegister>(0x2C); }
};
// The number of `IoSlots` used by this driver, determined by the last accessed register, see
// `LineControlRegister`. For unscaled MMIO, this corresponds to the size of the MMIO region
// from a provided base address.
static constexpr size_t kIoSlots = 0x2C + sizeof(uint16_t);
struct Driver : public DriverBase<Driver, ZBI_KERNEL_DRIVER_PL011_UART, zbi_dcfg_simple_t,
IoRegisterType::kMmio8, kIoSlots> {
using Base = DriverBase<Driver, ZBI_KERNEL_DRIVER_PL011_UART, zbi_dcfg_simple_t,
IoRegisterType::kMmio8, kIoSlots>;
static constexpr auto kDevicetreeBindings =
cpp20::to_array<std::string_view>({"arm,primecell", "arm,pl011"});
static constexpr std::string_view config_name() { return "pl011"; }
template <typename... Args>
explicit Driver(Args&&... args) : Base(std::forward<Args>(args)...) {}
using Base::MaybeCreate;
static std::optional<Driver> MaybeCreate(std::string_view string) {
if (string == "qemu") {
return Driver(kQemuConfig);
}
return Base::MaybeCreate(string);
}
template <class IoProvider>
void Init(IoProvider& io) {
// Other line control settings were initialized by the hardware or the boot
// loader and we just use them as they are.
auto lcr = LineControlRegister::Get().ReadFrom(io.io());
lcr.set_fifo_enable(true).WriteTo(io.io());
auto cr = ControlRegister::Get().FromValue(0);
cr.set_tx_enable(true).set_uart_enable(true).WriteTo(io.io());
}
template <class IoProvider>
bool TxReady(IoProvider& io) {
return FlagRegister::Get().ReadFrom(io.io()).tx_fifo_empty();
}
template <class IoProvider, typename It1, typename It2>
auto Write(IoProvider& io, bool, It1 it, const It2& end) {
DataRegister::Get().FromValue(0).set_data(*it).WriteTo(io.io());
return ++it;
}
template <class IoProvider>
std::optional<uint8_t> Read(IoProvider& io) {
if (FlagRegister::Get().ReadFrom(io.io()).rx_fifo_empty()) {
return {};
}
return DataRegister::Get().ReadFrom(io.io()).data();
}
template <class IoProvider>
void EnableTxInterrupt(IoProvider& io, bool enable = true) {
auto imscr = InterruptMaskSetClearRegister::Get().ReadFrom(io.io());
imscr.set_tx(enable).WriteTo(io.io());
}
template <class IoProvider>
void EnableRxInterrupt(IoProvider& io, bool enable = true) {
auto imscr = InterruptMaskSetClearRegister::Get().ReadFrom(io.io());
imscr.set_rx(enable).set_rx_timeout(enable).WriteTo(io.io());
}
template <class IoProvider, typename EnableInterruptCallback>
void InitInterrupt(IoProvider& io, EnableInterruptCallback&& enable_interrupt_callback) {
// Clear any pending interrupts.
auto icr = InterruptClearRegister::Get().FromValue(0x3ff);
icr.WriteTo(io.io());
// Set the FIFO trigger levels to fastest trigger (1/8 capacity).
auto fifo = InterruptFifoLevelSelectRegister::Get().FromValue(0);
fifo.set_rx(InterruptFifoLevelSelectRegister::Level::ONE_EIGHTH)
.set_tx(InterruptFifoLevelSelectRegister::Level::ONE_EIGHTH)
.WriteTo(io.io());
// Enable receive interrupts and then finally enable reception itself.
// Transmit interrupts are enabled only when there is a blocked writer.
EnableRxInterrupt(io);
auto cr = ControlRegister::Get().ReadFrom(io.io());
cr.set_rx_enable(true).WriteTo(io.io());
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 misr = InterruptMaskedStatusRegister::Get().ReadFrom(io.io());
if (misr.rx_timeout() || misr.rx()) {
bool full = false;
while (!full && !FlagRegister::Get().ReadFrom(io.io()).rx_fifo_empty()) {
// Read the character if there's a place to put it.
rx(
lock, //
[&]() { return DataRegister::Get().ReadFrom(io.io()).data(); },
[&]() {
// If the buffer is full, disable the receive interrupt instead
// and stop checking.
EnableRxInterrupt(io, false);
full = true;
});
}
}
if (misr.tx()) {
tx(lock, waiter, [&]() { EnableTxInterrupt(io, false); });
}
}
};
} // namespace uart::pl011
#endif // LIB_UART_PL011_H_