blob: fabca265c2ea969b43016bc614009a212332c906 [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_AMLOGIC_H_
#define LIB_UART_AMLOGIC_H_
#include <lib/stdcompat/array.h>
#include <lib/zbi-format/driver-config.h>
#include <lib/zbi-format/zbi.h>
#include <algorithm>
#include <hwreg/bitfields.h>
#include "uart.h"
namespace uart::amlogic {
struct FifoRegister : public hwreg::RegisterBase<FifoRegister, uint32_t> {
DEF_RSVDZ_FIELD(31, 8);
DEF_FIELD(7, 0, data);
static auto Get(uint32_t offset) { return hwreg::RegisterAddr<FifoRegister>(offset); }
};
struct WriteFifoRegister {
static auto Get() { return FifoRegister::Get(0x0); }
};
struct ReadFifoRegister {
static auto Get() { return FifoRegister::Get(0x4); }
};
struct ControlRegister : public hwreg::RegisterBase<ControlRegister, uint32_t> {
enum class Bits {
k8 = 0b00,
k7 = 0b01,
k6 = 0b10,
k5 = 0b11,
};
enum class StopBits {
k1 = 0b00,
k2 = 0b01,
};
DEF_BIT(31, invert_rts);
DEF_BIT(30, mask_error);
DEF_BIT(29, invert_cts);
DEF_BIT(28, tx_interrupt);
DEF_BIT(27, rx_interrupt);
DEF_BIT(26, invert_tx);
DEF_BIT(25, invert_rx);
DEF_BIT(24, clear_error);
DEF_BIT(23, rx_reset);
DEF_BIT(22, tx_reset);
DEF_ENUM_FIELD(Bits, 21, 20, bits);
DEF_BIT(19, parity_enable);
DEF_BIT(18, parity_odd);
DEF_ENUM_FIELD(StopBits, 17, 16, stop_bits);
DEF_BIT(15, two_wire);
// Bit 14 is unused.
DEF_BIT(13, rx_enable);
DEF_BIT(12, tx_enable);
DEF_FIELD(11, 0, old_baud_rate);
static auto Get() { return hwreg::RegisterAddr<ControlRegister>(0x8); }
};
struct StatusRegister : public hwreg::RegisterBase<StatusRegister, uint32_t> {
// Bits [31:27] are unused.
DEF_BIT(26, rx_busy);
DEF_BIT(25, tx_busy);
DEF_BIT(24, rx_fifo_overflow);
DEF_BIT(23, cts);
DEF_BIT(22, tx_fifo_empty);
DEF_BIT(21, tx_fifo_full);
DEF_BIT(20, rx_fifo_empty);
DEF_BIT(19, rx_fifo_full);
DEF_BIT(18, fifo_written_when_full);
DEF_BIT(17, frame_error);
DEF_BIT(16, parity_error);
// Bit 15 is unused.
DEF_FIELD(14, 8, tx_fifo_count);
// Bit 7 is unused.
DEF_FIELD(6, 0, rx_fifo_count);
static auto Get() { return hwreg::RegisterAddr<StatusRegister>(0xc); }
};
struct IrqControlRegister : public hwreg::RegisterBase<IrqControlRegister, uint32_t> {
DEF_FIELD(15, 8, tx_irq_count);
DEF_FIELD(7, 0, rx_irq_count);
static auto Get() { return hwreg::RegisterAddr<IrqControlRegister>(0x10); }
};
// The number of `IoSlots` used by this driver, determined by the last accessed register, see
// `IrqControlRegister`. For unscaled MMIO, this corresponds to the size of the MMIO region
// from a provided base address.
static constexpr size_t kIoSlots = 0x10 + sizeof(uint32_t);
struct Driver : public DriverBase<Driver, ZBI_KERNEL_DRIVER_AMLOGIC_UART, zbi_dcfg_simple_t,
IoRegisterType::kMmio8, kIoSlots> {
static constexpr auto kDevicetreeBindings =
cpp20::to_array<std::string_view>({"amlogic,meson-gx-uart", "amlogic,meson-ao-uart"});
template <typename... Args>
explicit Driver(Args&&... args)
: DriverBase<Driver, ZBI_KERNEL_DRIVER_AMLOGIC_UART, zbi_dcfg_simple_t,
IoRegisterType::kMmio8, kIoSlots>(std::forward<Args>(args)...) {}
static constexpr std::string_view config_name() { return "amlogic"; }
static constexpr uint32_t kFifoDepth = 64;
template <class IoProvider>
void Init(IoProvider& io) {
// The line control settings were initialized by the hardware or the boot
// loader and we just use them as they are.
ControlRegister::Get()
.ReadFrom(io.io())
.set_rx_reset(true)
.set_tx_reset(true)
.set_clear_error(true)
.set_tx_enable(true)
.set_rx_enable(true)
.set_tx_interrupt(false)
.set_rx_interrupt(false)
.set_two_wire(true)
.WriteTo(io.io())
// Must change state of rx/tx reset back to non reset or IRQs might
// not work properly.
.set_clear_error(false)
.set_rx_reset(false)
.set_tx_reset(false)
.WriteTo(io.io());
}
template <class IoProvider>
uint32_t TxReady(IoProvider& io) {
auto sr = StatusRegister::Get().ReadFrom(io.io());
if (sr.tx_fifo_full()) {
return 0;
}
// Be careful about the assumed maximum it will report.
return kFifoDepth - std::min(sr.tx_fifo_count(), kFifoDepth);
}
template <class IoProvider, typename It1, typename It2>
auto Write(IoProvider& io, uint32_t ready_space, It1 it, const It2& end) {
auto tx = WriteFifoRegister::Get().FromValue(0);
do {
tx.set_data(*it).WriteTo(io.io());
} while (++it != end && --ready_space > 0);
return it;
}
template <class IoProvider>
std::optional<uint8_t> Read(IoProvider& io) {
if (StatusRegister::Get().ReadFrom(io.io()).rx_fifo_empty()) {
return {};
}
return ReadFifoRegister::Get().ReadFrom(io.io()).data();
}
template <class IoProvider>
void EnableTxInterrupt(IoProvider& io, bool enable = true) {
auto cr = ControlRegister::Get().ReadFrom(io.io());
cr.set_tx_interrupt(enable).WriteTo(io.io());
}
template <class IoProvider>
void EnableRxInterrupt(IoProvider& io, bool enable = true) {
auto cr = ControlRegister::Get().ReadFrom(io.io());
cr.set_rx_interrupt(enable).WriteTo(io.io());
}
template <class IoProvider, typename EnableInterruptCallback>
void InitInterrupt(IoProvider& io, EnableInterruptCallback&& enable_interrupt_callback) {
auto icr = IrqControlRegister::Get().ReadFrom(io.io());
icr.set_tx_irq_count(kFifoDepth / 8).set_rx_irq_count(1).WriteTo(io.io());
// Enable receive interrupts.
// Transmit interrupts are enabled only when there is a blocked writer.
EnableRxInterrupt(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) {
size_t drained_rx = 0;
while (drained_rx < kFifoDepth) {
auto sr = StatusRegister::Get().ReadFrom(io.io());
auto cr = ControlRegister::Get().ReadFrom(io.io());
// Drain characters in the fifo.
bool rx_disabled = false;
// Drain at most |kFifoDepth| characters per IRQ.
// If there were no characters in the fifo, then it was either an error
// or TX IRQ, will be handled in this pass.
drained_rx += sr.rx_fifo_count() == 0 ? kFifoDepth : sr.rx_fifo_count();
for (size_t i = 0; i < sr.rx_fifo_count() && !rx_disabled; ++i) {
rx(
lock, //
[&]() { return ReadFifoRegister::Get().ReadFrom(io.io()).data(); },
[&]() {
// If the buffer is full, disable the receive interrupt instead
// and stop checking.
EnableRxInterrupt(io, false);
rx_disabled = true;
});
}
// Clear any interrupt due to errors.
if (sr.frame_error() || sr.parity_error()) {
cr.set_clear_error(true).WriteTo(io.io()).set_clear_error(false).WriteTo(io.io());
}
if (cr.tx_interrupt() && !sr.tx_fifo_full()) {
tx(lock, waiter, [&]() { EnableTxInterrupt(io, false); });
}
}
}
};
} // namespace uart::amlogic
#endif // LIB_UART_AMLOGIC_H_