| // 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_ |