blob: 147979e8d9b7b8c12f8f4e918f124665bc847a30 [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 <zircon/boot/driver-config.h>
#include <zircon/boot/image.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); }
};
struct Driver : public DriverBase<Driver, KDRV_AMLOGIC_UART, dcfg_simple_t> {
template <typename... Args>
explicit Driver(Args&&... args)
: DriverBase<Driver, KDRV_AMLOGIC_UART, dcfg_simple_t>(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)
.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().FromValue(0);
cr.set_tx_interrupt(enable).WriteTo(io.io());
}
template <class IoProvider>
void EnableRxInterrupt(IoProvider& io, bool enable = true) {
auto cr = ControlRegister::Get().FromValue(0);
cr.set_rx_interrupt(enable).WriteTo(io.io());
}
template <class IoProvider>
void InitInterrupt(IoProvider& io) {
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);
}
template <class IoProvider, typename Tx, typename Rx>
void Interrupt(IoProvider& io, Tx&& tx, Rx&& rx) {
auto sr = StatusRegister::Get().ReadFrom(io.io());
bool tx_done = false;
auto check_tx = [&]() {
if (!tx_done && !sr.tx_fifo_full()) {
tx();
EnableTxInterrupt(io, false);
tx_done = true;
}
};
check_tx();
bool full = false;
while (!full && !sr.rx_fifo_empty()) {
// Read the character if there's a place to put it.
rx([&]() { return ReadFifoRegister::Get().ReadFrom(io.io()).data(); },
[&]() {
// If the buffer is full, disable the receive interrupt instead
// and stop checking.
EnableRxInterrupt(io, false);
full = true;
});
// Fetch fresh status for next iteration to check.
sr.ReadFrom(io.io());
// Re-check for transmit since we have fresh status handy.
check_tx();
}
}
};
} // namespace uart::amlogic
#endif // LIB_UART_AMLOGIC_H_