| // Copyright 2019 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. |
| |
| #include "uart16550.h" |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/metadata.h> |
| #include <ddk/protocol/serial.h> |
| #include <ddk/protocol/serialimpl.h> |
| #include <fuchsia/hardware/serial/c/fidl.h> |
| #include <hw/inout.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| #include <algorithm> |
| |
| namespace { |
| |
| enum class InterruptType : uint8_t { |
| kNone = 0b0001, |
| kRxLineStatus = 0b0110, |
| kRxDataAvailable = 0b0100, |
| kCharTimeout = 0b1100, |
| kTxEmpty = 0b0010, |
| kModemStatus = 0b0000, |
| }; |
| |
| 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_FIELD(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, reciever_trigger); |
| 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); } |
| }; |
| |
| } // namespace |
| |
| namespace uart16550 { |
| |
| static constexpr int64_t kPioIndex = 0; |
| static constexpr int64_t kIrqIndex = 0; |
| |
| static constexpr uint32_t kMaxBaudRate = 115200; |
| static constexpr uint8_t kDefaultConfig = |
| SERIAL_DATA_BITS_8 | SERIAL_STOP_BITS_1 | SERIAL_PARITY_NONE; |
| |
| static constexpr uint32_t kPortCount = 8; |
| |
| static constexpr uint32_t kFifoDepth16750 = 64; |
| static constexpr uint32_t kFifoDepth16550A = 16; |
| static constexpr uint32_t kFifoDepthGeneric = 1; |
| |
| static constexpr serial_port_info_t kInfo = { |
| .serial_class = fuchsia_hardware_serial_Class_GENERIC, |
| .serial_vid = 0, |
| .serial_pid = 0, |
| }; |
| |
| Uart16550::Uart16550() : DeviceType(nullptr) {} |
| |
| Uart16550::Uart16550(zx_device_t* parent) : DeviceType(parent), acpi_(parent) {} |
| |
| zx_status_t Uart16550::Create(void* /*ctx*/, zx_device_t* parent) { |
| auto dev = std::make_unique<Uart16550>(parent); |
| |
| auto status = dev->Init(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Init failed\n", __func__); |
| return status; |
| } |
| |
| dev->DdkAdd("uart16550"); |
| |
| // Release because devmgr is now in charge of the device. |
| static_cast<void>(dev.release()); |
| return ZX_OK; |
| } |
| |
| size_t Uart16550::FifoDepth() const { return uart_fifo_len_; } |
| |
| bool Uart16550::Enabled() { |
| std::lock_guard<std::mutex> lock(device_mutex_); |
| return enabled_; |
| } |
| |
| bool Uart16550::NotifyCallbackSet() { |
| std::lock_guard<std::mutex> lock(device_mutex_); |
| return notify_cb_.callback != nullptr; |
| } |
| |
| // Create RX and TX FIFOs, obtain interrupt and port handles from the ACPI |
| // device, obtain port permissions, set up default configuration, and start the |
| // interrupt handler thread. |
| zx_status_t Uart16550::Init() { |
| zx::resource io_port; |
| auto status = acpi_.GetPio(kPioIndex, &io_port); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: acpi_.GetPio failed\n", __func__); |
| return status; |
| } |
| |
| status = acpi_.MapInterrupt(kIrqIndex, &interrupt_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: acpi_.MapInterrupt failed\n", __func__); |
| return status; |
| } |
| |
| zx_info_resource_t resource_info; |
| status = |
| io_port.get_info(ZX_INFO_RESOURCE, &resource_info, sizeof(resource_info), nullptr, nullptr); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: io_port.get_info failed\n", __func__); |
| return status; |
| } |
| |
| const auto port_base = static_cast<uint16_t>(resource_info.base); |
| const auto port_size = static_cast<uint32_t>(resource_info.size); |
| |
| if (port_base != resource_info.base) { |
| zxlogf(ERROR, "%s: overflowing UART port base\n", __func__); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (port_size != resource_info.size) { |
| zxlogf(ERROR, "%s: overflowing UART port size\n", __func__); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (port_size != kPortCount) { |
| zxlogf(ERROR, "%s: unsupported UART port count\n", __func__); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| status = zx_ioports_request(io_port.get(), port_base, port_size); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: zx_ioports_request failed\n", __func__); |
| return status; |
| } |
| |
| { |
| std::lock_guard<std::mutex> lock(device_mutex_); |
| auto port_read = [](uint16_t port) -> uint8_t { return inp(port); }; |
| |
| auto port_write = [](uint8_t data, uint16_t port) { outp(port, data); }; |
| |
| port_io_ = PortIo(port_read, port_write, port_base); |
| |
| InitFifosLocked(); |
| } |
| |
| status = SerialImplConfig(kMaxBaudRate, kDefaultConfig); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: SerialImplConfig failed\n", __func__); |
| return status; |
| } |
| |
| interrupt_thread_ = std::thread([&] { HandleInterrupts(); }); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Uart16550::Init(zx::interrupt interrupt, fbl::Function<uint8_t(uint16_t)> port_read, |
| fbl::Function<void(uint8_t, uint16_t)> port_write) { |
| interrupt_ = std::move(interrupt); |
| { |
| std::lock_guard<std::mutex> lock(device_mutex_); |
| port_io_ = PortIo(std::move(port_read), std::move(port_write), 0); |
| InitFifosLocked(); |
| } |
| |
| auto status = SerialImplConfig(kMaxBaudRate, kDefaultConfig); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: SerialImplConfig failed\n", __func__); |
| return status; |
| } |
| |
| interrupt_thread_ = std::thread([&] { HandleInterrupts(); }); |
| |
| return ZX_OK; |
| } |
| |
| zx::unowned_interrupt Uart16550::InterruptHandle() { return zx::unowned_interrupt(interrupt_); } |
| |
| zx_status_t Uart16550::SerialImplGetInfo(serial_port_info_t* info) { |
| *info = kInfo; |
| return ZX_OK; |
| } |
| |
| zx_status_t Uart16550::SerialImplConfig(uint32_t baud_rate, uint32_t flags) { |
| if (Enabled()) { |
| zxlogf(ERROR, "%s: attempted to configure when enabled\n", __func__); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (baud_rate == 0) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| const auto divisor = static_cast<uint16_t>(kMaxBaudRate / baud_rate); |
| if (divisor != kMaxBaudRate / baud_rate || divisor == 0) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if ((flags & SERIAL_FLOW_CTRL_MASK) != SERIAL_FLOW_CTRL_NONE && !SupportsAutomaticFlowControl()) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| const auto lower = static_cast<uint8_t>(divisor); |
| const auto upper = static_cast<uint8_t>(divisor >> 8); |
| |
| std::lock_guard<std::mutex> lock(device_mutex_); |
| |
| auto lcr = LineControlRegister::Get().ReadFrom(&port_io_); |
| |
| lcr.set_divisor_latch_access(true).WriteTo(&port_io_); |
| |
| DivisorLatchLowerRegister::Get().FromValue(0).set_data(lower).WriteTo(&port_io_); |
| DivisorLatchUpperRegister::Get().FromValue(0).set_data(upper).WriteTo(&port_io_); |
| |
| lcr.set_divisor_latch_access(false); |
| |
| if (flags & SERIAL_SET_BAUD_RATE_ONLY) { |
| lcr.WriteTo(&port_io_); |
| return ZX_OK; |
| } |
| |
| switch (flags & SERIAL_DATA_BITS_MASK) { |
| case SERIAL_DATA_BITS_5: |
| lcr.set_word_length(LineControlRegister::kWordLength5); |
| break; |
| case SERIAL_DATA_BITS_6: |
| lcr.set_word_length(LineControlRegister::kWordLength6); |
| break; |
| case SERIAL_DATA_BITS_7: |
| lcr.set_word_length(LineControlRegister::kWordLength7); |
| break; |
| case SERIAL_DATA_BITS_8: |
| lcr.set_word_length(LineControlRegister::kWordLength8); |
| break; |
| } |
| |
| switch (flags & SERIAL_STOP_BITS_MASK) { |
| case SERIAL_STOP_BITS_1: |
| lcr.set_stop_bits(LineControlRegister::kStopBits1); |
| break; |
| case SERIAL_STOP_BITS_2: |
| lcr.set_stop_bits(LineControlRegister::kStopBits2); |
| break; |
| } |
| |
| switch (flags & SERIAL_PARITY_MASK) { |
| case SERIAL_PARITY_NONE: |
| lcr.set_parity_enable(false); |
| lcr.set_even_parity(false); |
| break; |
| case SERIAL_PARITY_ODD: |
| lcr.set_parity_enable(true); |
| lcr.set_even_parity(false); |
| break; |
| case SERIAL_PARITY_EVEN: |
| lcr.set_parity_enable(true); |
| lcr.set_even_parity(true); |
| break; |
| } |
| |
| lcr.WriteTo(&port_io_); |
| |
| auto mcr = ModemControlRegister::Get().FromValue(0); |
| |
| // The below is necessary for interrupts on some devices. |
| mcr.set_auxiliary_out_2(true); |
| |
| switch (flags & SERIAL_FLOW_CTRL_MASK) { |
| case SERIAL_FLOW_CTRL_NONE: |
| mcr.set_automatic_flow_control_enable(false); |
| mcr.set_data_terminal_ready(true); |
| mcr.set_request_to_send(true); |
| break; |
| case SERIAL_FLOW_CTRL_CTS_RTS: |
| mcr.set_automatic_flow_control_enable(true); |
| mcr.set_data_terminal_ready(false); |
| mcr.set_request_to_send(false); |
| break; |
| } |
| |
| mcr.WriteTo(&port_io_); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Uart16550::SerialImplEnable(bool enable) { |
| std::lock_guard<std::mutex> lock(device_mutex_); |
| if (enabled_) { |
| if (!enable) { |
| // The device is enabled, and will be disabled. |
| InterruptEnableRegister::Get() |
| .FromValue(0) |
| .set_rx_available(false) |
| .set_line_status(false) |
| .set_modem_status(false) |
| .set_tx_empty(false) |
| .WriteTo(&port_io_); |
| } |
| } else { |
| if (enable) { |
| // The device is disabled, and will be enabled. |
| ResetFifosLocked(); |
| InterruptEnableRegister::Get() |
| .FromValue(0) |
| .set_rx_available(true) |
| .set_line_status(true) |
| .set_modem_status(true) |
| .set_tx_empty(false) |
| .WriteTo(&port_io_); |
| } |
| } |
| enabled_ = enable; |
| return ZX_OK; |
| } |
| |
| zx_status_t Uart16550::SerialImplRead(void* buf, size_t size, size_t* actual) { |
| std::lock_guard<std::mutex> lock(device_mutex_); |
| *actual = 0; |
| |
| if (!enabled_) { |
| zxlogf(ERROR, "%s: attempted to read when disabled\n", __func__); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| auto p = static_cast<uint8_t*>(buf); |
| |
| auto lcr = LineStatusRegister::Get(); |
| |
| auto data_ready_and_notify = [&]() __TA_REQUIRES(device_mutex_) { |
| auto ready = lcr.ReadFrom(&port_io_).data_ready(); |
| auto state = state_; |
| if (!ready) { |
| state &= ~SERIAL_STATE_READABLE; |
| } else { |
| state |= SERIAL_STATE_READABLE; |
| } |
| if (state_ != state) { |
| state_ = state; |
| NotifyLocked(); |
| } |
| return ready; |
| }; |
| |
| if (!data_ready_and_notify()) { |
| return ZX_ERR_SHOULD_WAIT; |
| } |
| |
| auto rbr = RxBufferRegister::Get(); |
| |
| while (data_ready_and_notify() && size != 0) { |
| *p++ = rbr.ReadFrom(&port_io_).data(); |
| *actual += 1; |
| --size; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Uart16550::SerialImplWrite(const void* buf, size_t size, size_t* actual) { |
| std::lock_guard<std::mutex> lock(device_mutex_); |
| *actual = 0; |
| |
| if (!enabled_) { |
| zxlogf(ERROR, "%s: attempted to write when disabled\n", __func__); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| auto p = static_cast<const uint8_t*>(buf); |
| size_t writable = std::min(size, uart_fifo_len_); |
| |
| auto lsr = LineStatusRegister::Get(); |
| auto ier = InterruptEnableRegister::Get(); |
| |
| if (!lsr.ReadFrom(&port_io_).tx_empty()) { |
| ier.ReadFrom(&port_io_).set_tx_empty(true).WriteTo(&port_io_); |
| return ZX_ERR_SHOULD_WAIT; |
| } |
| |
| auto tbr = TxBufferRegister::Get(); |
| |
| while (writable != 0) { |
| tbr.FromValue(0).set_data(*p++).WriteTo(&port_io_); |
| *actual += 1; |
| --writable; |
| } |
| |
| if (*actual != size) { |
| ier.ReadFrom(&port_io_).set_tx_empty(true).WriteTo(&port_io_); |
| } |
| |
| if (*actual != 0) { |
| auto state = state_; |
| state &= ~SERIAL_STATE_WRITABLE; |
| if (state_ != state) { |
| state_ = state; |
| NotifyLocked(); |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t Uart16550::SerialImplSetNotifyCallback(const serial_notify_t* cb) { |
| std::lock_guard<std::mutex> lock(device_mutex_); |
| if (enabled_) { |
| zxlogf(ERROR, "%s: attempted to set notify callback when enabled\n", __func__); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (!cb) { |
| notify_cb_.callback = nullptr; |
| notify_cb_.ctx = nullptr; |
| } else { |
| notify_cb_ = *cb; |
| } |
| |
| return ZX_OK; |
| } |
| |
| void Uart16550::DdkRelease() { |
| SerialImplEnable(false); |
| // End the interrupt loop by canceling waits. |
| interrupt_.destroy(); |
| interrupt_thread_.join(); |
| delete this; |
| } |
| |
| void Uart16550::DdkUnbind() { DdkRemove(); } |
| |
| bool Uart16550::SupportsAutomaticFlowControl() const { return uart_fifo_len_ == kFifoDepth16750; } |
| |
| void Uart16550::ResetFifosLocked() { |
| // 16750 requires we toggle extended fifo while divisor latch is enabled. |
| LineControlRegister::Get().FromValue(0).set_divisor_latch_access(true).WriteTo(&port_io_); |
| FifoControlRegister::Get() |
| .FromValue(0) |
| .set_fifo_enable(true) |
| .set_rx_fifo_reset(true) |
| .set_tx_fifo_reset(true) |
| .set_dma_mode(0) |
| .set_extended_fifo_enable(true) |
| .set_reciever_trigger(FifoControlRegister::kMaxTriggerLevel) |
| .WriteTo(&port_io_); |
| LineControlRegister::Get().FromValue(0).set_divisor_latch_access(false).WriteTo(&port_io_); |
| } |
| |
| void Uart16550::InitFifosLocked() { |
| ResetFifosLocked(); |
| const auto iir = InterruptIdentRegister::Get().ReadFrom(&port_io_); |
| if (iir.fifos_enabled()) { |
| if (iir.extended_fifo_enabled()) { |
| uart_fifo_len_ = kFifoDepth16750; |
| } else { |
| uart_fifo_len_ = kFifoDepth16550A; |
| } |
| } else { |
| uart_fifo_len_ = kFifoDepthGeneric; |
| } |
| } |
| |
| void Uart16550::NotifyLocked() { |
| if (notify_cb_.callback && enabled_) { |
| notify_cb_.callback(notify_cb_.ctx, state_); |
| } |
| } |
| |
| // Loop and wait on the interrupt handle. When an interrupt is detected, read the interrupt |
| // identifier. If there is data available in the hardware RX FIFO, notify readable. If the |
| // hardware TX FIFO is empty, notify writable. If there is a line status error, log it. If |
| // there is a modem status, log it. |
| void Uart16550::HandleInterrupts() { |
| // Ignore the timestamp. |
| while (interrupt_.wait(nullptr) == ZX_OK) { |
| std::lock_guard<std::mutex> lock(device_mutex_); |
| |
| if (!enabled_) { |
| // Interrupts should be disabled now and we shouldn't respond to them. |
| continue; |
| } |
| |
| const auto identifier = InterruptIdentRegister::Get().ReadFrom(&port_io_).interrupt_id(); |
| |
| switch (static_cast<InterruptType>(identifier)) { |
| case InterruptType::kNone: |
| break; |
| case InterruptType::kRxLineStatus: { |
| // Clear the interrupt. |
| const auto lsr = LineStatusRegister::Get().ReadFrom(&port_io_); |
| if (lsr.overrun_error()) { |
| zxlogf(ERROR, "%s: overrun error (OE) detected\n", __func__); |
| } |
| if (lsr.parity_error()) { |
| zxlogf(ERROR, "%s: parity error (PE) detected\n", __func__); |
| } |
| if (lsr.framing_error()) { |
| zxlogf(ERROR, "%s: framing error (FE) detected\n", __func__); |
| } |
| if (lsr.break_interrupt()) { |
| zxlogf(ERROR, "%s: break interrupt (BI) detected\n", __func__); |
| } |
| if (lsr.error_in_rx_fifo()) { |
| zxlogf(ERROR, "%s: error in rx fifo detected\n", __func__); |
| } |
| break; |
| } |
| case InterruptType::kRxDataAvailable: // In both cases, there is data ready in the rx fifo. |
| case InterruptType::kCharTimeout: { |
| auto state = state_; |
| state |= SERIAL_STATE_READABLE; |
| if (state_ != state) { |
| state_ = state; |
| NotifyLocked(); |
| } |
| break; |
| } |
| case InterruptType::kTxEmpty: { |
| InterruptEnableRegister::Get().ReadFrom(&port_io_).set_tx_empty(false).WriteTo(&port_io_); |
| auto state = state_; |
| state |= SERIAL_STATE_WRITABLE; |
| if (state_ != state) { |
| state_ = state; |
| NotifyLocked(); |
| } |
| break; |
| } |
| case InterruptType::kModemStatus: { |
| // Clear the interrupt. |
| const auto msr = ModemStatusRegister::Get().ReadFrom(&port_io_); |
| if (msr.clear_to_send()) { |
| zxlogf(INFO, "%s: clear to send (CTS) detected\n", __func__); |
| } |
| if (msr.data_set_ready()) { |
| zxlogf(INFO, "%s: data set ready (DSR) detected\n", __func__); |
| } |
| if (msr.ring_indicator()) { |
| zxlogf(INFO, "%s: ring indicator (RI) detected\n", __func__); |
| } |
| if (msr.data_carrier_detect()) { |
| zxlogf(INFO, "%s: data carrier (DCD) detected\n", __func__); |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| static constexpr zx_driver_ops_t driver_ops = [] { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = Uart16550::Create; |
| return ops; |
| }(); |
| |
| } // namespace uart16550 |
| |
| // clang-format off |
| ZIRCON_DRIVER_BEGIN(uart16550, uart16550::driver_ops, "zircon", "0.1", 3) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_ACPI), |
| BI_ABORT_IF(NE, BIND_ACPI_HID_0_3, 0x504e5030), // PNP0501\0 |
| BI_MATCH_IF(EQ, BIND_ACPI_HID_4_7, 0x35303100), |
| ZIRCON_DRIVER_END(uart16550) |