blob: c3649cc7f13fef5d6e7def0157f1448d21a58b9f [file] [log] [blame]
// Copyright 2018 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 "src/devices/serial/drivers/aml-uart/aml-uart.h"
#ifdef DFV1
#include <lib/ddk/debug.h> // nogncheck
#else
#include <lib/driver/compat/cpp/logging.h> // nogncheck
#endif
#include <lib/zx/clock.h>
#include <threads.h>
#include <zircon/syscalls-next.h>
#include <zircon/threads.h>
#include <fbl/auto_lock.h>
#include "src/devices/serial/drivers/aml-uart/registers.h"
namespace fhsi = fuchsia_hardware_serialimpl;
namespace serial {
namespace internal {
fit::closure DriverTransportReadOperation::MakeCallback(zx_status_t status, void* buf, size_t len) {
return fit::closure(
[arena = std::move(arena_), completer = std::move(completer_), status, buf, len]() mutable {
if (status == ZX_OK) {
completer.buffer(arena).ReplySuccess(
fidl::VectorView<uint8_t>::FromExternal(static_cast<uint8_t*>(buf), len));
} else {
completer.buffer(arena).ReplyError(status);
}
});
}
fit::closure DriverTransportWriteOperation::MakeCallback(zx_status_t status) {
return fit::closure(
[arena = std::move(arena_), completer = std::move(completer_), status]() mutable {
if (status == ZX_OK) {
completer.buffer(arena).ReplySuccess();
} else {
completer.buffer(arena).ReplyError(status);
}
});
}
} // namespace internal
AmlUart::AmlUart(
ddk::PDevFidl pdev, const fuchsia_hardware_serial::wire::SerialPortInfo& serial_port_info,
fdf::MmioBuffer mmio, fdf::UnownedSynchronizedDispatcher irq_dispatcher,
std::optional<fdf::UnownedSynchronizedDispatcher> timer_dispatcher,
std::optional<fidl::ClientEnd<fuchsia_power_broker::Lessor>> lessor_client_end,
std::optional<fidl::ClientEnd<fuchsia_power_broker::CurrentLevel>> current_level_client_end,
std::optional<fidl::ClientEnd<fuchsia_power_broker::RequiredLevel>> required_level_client_end,
std::optional<fidl::ClientEnd<fuchsia_power_broker::ElementControl>> element_control_client_end)
: pdev_(std::move(pdev)),
serial_port_info_(serial_port_info),
mmio_(std::move(mmio)),
irq_dispatcher_(std::move(irq_dispatcher)) {
if (timer_dispatcher != std::nullopt) {
timer_dispatcher_ = std::move(*timer_dispatcher);
// Use ZX_TIMER_SLACK_LATE so that the timer will never fire earlier than the deadline.
zx::timer::create(ZX_TIMER_SLACK_LATE, ZX_CLOCK_MONOTONIC, &lease_timer_);
timer_waiter_.set_object(lease_timer_.get());
timer_waiter_.set_trigger(ZX_TIMER_SIGNALED);
}
if (lessor_client_end != std::nullopt) {
lessor_client_.Bind(std::move(*lessor_client_end));
} else {
zxlogf(INFO, "No lessor client end gets passed into AmlUart during initialization.");
}
if (required_level_client_end != std::nullopt) {
required_level_client_.Bind(std::move(*required_level_client_end),
fdf::Dispatcher::GetCurrent()->async_dispatcher());
} else {
zxlogf(INFO, "No required level client end gets passed into AmlUart during initialization.");
}
if (current_level_client_end != std::nullopt) {
current_level_client_.Bind(std::move(*current_level_client_end));
} else {
zxlogf(INFO, "No current level client end gets passed into AmlUart during initialization.");
}
if (element_control_client_end != std::nullopt) {
element_control_client_end_ = std::move(element_control_client_end);
WatchRequiredLevel();
} else {
zxlogf(INFO, "No element control client end gets passed into AmlUart during initialization.");
}
}
// Keep watching the required level, since the driver doesn't toggle the hardware power state
// according to power element levels, report the current level to power broker immediately with the
// required level value received from power broker.
void AmlUart::WatchRequiredLevel() {
if (!required_level_client_.is_valid()) {
zxlogf(ERROR, "RequiredLevel not valid, can't monitor it");
element_control_client_end_.reset();
return;
}
required_level_client_->Watch().Then(
[this](fidl::Result<fuchsia_power_broker::RequiredLevel::Watch>& result) {
if (result.is_error()) {
if (result.error_value().is_framework_error() &&
result.error_value().framework_error().status() != ZX_ERR_CANCELED) {
zxlogf(ERROR, "Power level required call failed: %s. Stop monitoring required level",
result.error_value().FormatDescription().c_str());
}
element_control_client_end_.reset();
return;
}
zxlogf(DEBUG, "RequiredLevel : %u", static_cast<uint8_t>(result->required_level()));
if (!current_level_client_.is_valid()) {
zxlogf(ERROR, "CurrentLevel not valid. Stop monitoring required level");
element_control_client_end_.reset();
return;
}
auto update_result = current_level_client_->Update(result->required_level());
if (update_result.is_error()) {
zxlogf(ERROR, "CurrentLevel call failed: %s. Stop monitoring required level",
update_result.error_value().FormatDescription().c_str());
element_control_client_end_.reset();
return;
}
WatchRequiredLevel();
});
}
constexpr auto kMinBaudRate = 2;
bool AmlUart::Readable() { return !Status::Get().ReadFrom(&mmio_).rx_empty(); }
bool AmlUart::Writable() { return !Status::Get().ReadFrom(&mmio_).tx_full(); }
void AmlUart::GetInfo(fdf::Arena& arena, GetInfoCompleter::Sync& completer) {
completer.buffer(arena).ReplySuccess(serial_port_info_);
}
zx_status_t AmlUart::Config(uint32_t baud_rate, uint32_t flags) {
// Control register is determined completely by this logic, so start with a clean slate.
if (baud_rate < kMinBaudRate) {
return ZX_ERR_INVALID_ARGS;
}
auto ctrl = Control::Get().FromValue(0);
if ((flags & fhsi::kSerialSetBaudRateOnly) == 0) {
switch (flags & fhsi::kSerialDataBitsMask) {
case fhsi::kSerialDataBits5:
ctrl.set_xmit_len(Control::kXmitLength5);
break;
case fhsi::kSerialDataBits6:
ctrl.set_xmit_len(Control::kXmitLength6);
break;
case fhsi::kSerialDataBits7:
ctrl.set_xmit_len(Control::kXmitLength7);
break;
case fhsi::kSerialDataBits8:
ctrl.set_xmit_len(Control::kXmitLength8);
break;
default:
return ZX_ERR_INVALID_ARGS;
}
switch (flags & fhsi::kSerialStopBitsMask) {
case fhsi::kSerialStopBits1:
ctrl.set_stop_len(Control::kStopLen1);
break;
case fhsi::kSerialStopBits2:
ctrl.set_stop_len(Control::kStopLen2);
break;
default:
return ZX_ERR_INVALID_ARGS;
}
switch (flags & fhsi::kSerialParityMask) {
case fhsi::kSerialParityNone:
ctrl.set_parity(Control::kParityNone);
break;
case fhsi::kSerialParityEven:
ctrl.set_parity(Control::kParityEven);
break;
case fhsi::kSerialParityOdd:
ctrl.set_parity(Control::kParityOdd);
break;
default:
return ZX_ERR_INVALID_ARGS;
}
switch (flags & fhsi::kSerialFlowCtrlMask) {
case fhsi::kSerialFlowCtrlNone:
ctrl.set_two_wire(1);
break;
case fhsi::kSerialFlowCtrlCtsRts:
// CTS/RTS is on by default
break;
default:
return ZX_ERR_INVALID_ARGS;
}
}
// Configure baud rate based on crystal clock speed.
// See meson_uart_change_speed() in drivers/amlogic/uart/uart/meson_uart.c.
constexpr uint32_t kCrystalClockSpeed = 24000000;
uint32_t baud_bits = (kCrystalClockSpeed / 3) / baud_rate - 1;
if (baud_bits & (~AML_UART_REG5_NEW_BAUD_RATE_MASK)) {
zxlogf(ERROR, "%s: baud rate %u too large", __func__, baud_rate);
return ZX_ERR_OUT_OF_RANGE;
}
auto baud = Reg5::Get()
.FromValue(0)
.set_new_baud_rate(baud_bits)
.set_use_xtal_clk(1)
.set_use_new_baud_rate(1);
fbl::AutoLock al(&enable_lock_);
if ((flags & fhsi::kSerialSetBaudRateOnly) == 0) {
// Invert our RTS if we are we are not enabled and configured for flow control.
if (!enabled_ && (ctrl.two_wire() == 0)) {
ctrl.set_inv_rts(1);
}
ctrl.WriteTo(&mmio_);
}
baud.WriteTo(&mmio_);
return ZX_OK;
}
void AmlUart::EnableLocked(bool enable) {
auto ctrl = Control::Get().ReadFrom(&mmio_);
if (enable) {
// Reset the port.
ctrl.set_rst_rx(1).set_rst_tx(1).set_clear_error(1).WriteTo(&mmio_);
ctrl.set_rst_rx(0).set_rst_tx(0).set_clear_error(0).WriteTo(&mmio_);
// Enable rx and tx.
ctrl.set_tx_enable(1)
.set_rx_enable(1)
.set_tx_interrupt_enable(1)
.set_rx_interrupt_enable(1)
// Clear our RTS.
.set_inv_rts(0)
.WriteTo(&mmio_);
// Set interrupt thresholds.
// Generate interrupt if TX buffer drops below half full.
constexpr uint32_t kTransmitIrqCount = 32;
// Generate interrupt as soon as we receive any data.
constexpr uint32_t kRecieveIrqCount = 1;
Misc::Get()
.FromValue(0)
.set_xmit_irq_count(kTransmitIrqCount)
.set_recv_irq_count(kRecieveIrqCount)
.WriteTo(&mmio_);
} else {
ctrl.set_tx_enable(0)
.set_rx_enable(0)
// Invert our RTS if we are configured for flow control.
.set_inv_rts(!ctrl.two_wire())
.WriteTo(&mmio_);
}
}
fidl::ClientEnd<fuchsia_power_broker::ElementControl>& AmlUart::element_control_for_testing() {
return *element_control_client_end_;
}
void AmlUart::HandleTXRaceForTest() {
{
fbl::AutoLock al(&enable_lock_);
EnableLocked(true);
}
Writable();
HandleTX();
HandleTX();
}
void AmlUart::HandleRXRaceForTest() {
{
fbl::AutoLock al(&enable_lock_);
EnableLocked(true);
}
Readable();
HandleRX();
HandleRX();
}
void AmlUart::InjectTimerForTest(zx_handle_t handle) {
lease_timer_.reset(handle);
timer_waiter_.set_object(lease_timer_.get());
timer_waiter_.set_trigger(ZX_TIMER_SIGNALED);
}
zx_status_t AmlUart::Enable(bool enable) {
fbl::AutoLock al(&enable_lock_);
if (enable && !enabled_) {
zx_status_t status = pdev_.GetInterrupt(0, ZX_INTERRUPT_WAKE_VECTOR, &irq_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s: pdev_get_interrupt failed %d", __func__, status);
return status;
}
EnableLocked(true);
irq_handler_.set_object(irq_.get());
irq_handler_.Begin(irq_dispatcher_->async_dispatcher());
} else if (!enable && enabled_) {
irq_handler_.Cancel();
EnableLocked(false);
}
enabled_ = enable;
return ZX_OK;
}
void AmlUart::CancelAll(fdf::Arena& arena, CancelAllCompleter::Sync& completer) {
{
fbl::AutoLock read_lock(&read_lock_);
if (read_operation_) {
auto cb = MakeReadCallbackLocked(ZX_ERR_CANCELED, nullptr, 0);
read_lock.release();
cb();
}
}
{
fbl::AutoLock write_lock(&write_lock_);
if (write_operation_) {
auto cb = MakeWriteCallbackLocked(ZX_ERR_CANCELED);
write_lock.release();
cb();
}
}
completer.buffer(arena).Reply();
}
// Handles receiviung data into the buffer and calling the read callback when complete.
// Does nothing if read_pending_ is false.
void AmlUart::HandleRX() {
fbl::AutoLock lock(&read_lock_);
if (!read_operation_) {
return;
}
unsigned char buf[128];
size_t length = 128;
auto* bufptr = static_cast<uint8_t*>(buf);
const uint8_t* const end = bufptr + length;
while (bufptr < end && Readable()) {
uint32_t val = mmio_.Read32(AML_UART_RFIFO);
*bufptr++ = static_cast<uint8_t>(val);
}
const size_t read = reinterpret_cast<uintptr_t>(bufptr) - reinterpret_cast<uintptr_t>(buf);
if (read == 0) {
return;
}
// Some bytes were read. The client must queue another read to get any data.
auto cb = MakeReadCallbackLocked(ZX_OK, buf, read);
lock.release();
cb();
}
// Handles transmitting the data in write_buffer_ until it is completely written.
// Does nothing if write_pending_ is not true.
void AmlUart::HandleTX() {
fbl::AutoLock lock(&write_lock_);
if (!write_operation_) {
return;
}
const auto* bufptr = static_cast<const uint8_t*>(write_buffer_);
const uint8_t* const end = bufptr + write_size_;
while (bufptr < end && Writable()) {
mmio_.Write32(*bufptr++, AML_UART_WFIFO);
}
const size_t written =
reinterpret_cast<uintptr_t>(bufptr) - reinterpret_cast<uintptr_t>(write_buffer_);
write_size_ -= written;
write_buffer_ += written;
if (!write_size_) {
// The write has completed, notify the client.
auto cb = MakeWriteCallbackLocked(ZX_OK);
lock.release();
cb();
}
}
fit::closure AmlUart::MakeReadCallbackLocked(zx_status_t status, void* buf, size_t len) {
if (read_operation_) {
auto callback = read_operation_->MakeCallback(status, buf, len);
read_operation_.reset();
return callback;
}
ZX_PANIC("AmlUart::MakeReadCallbackLocked invalid state. No active Read operation.");
}
fit::closure AmlUart::MakeWriteCallbackLocked(zx_status_t status) {
if (write_operation_) {
auto callback = write_operation_->MakeCallback(status);
write_operation_.reset();
return callback;
}
ZX_PANIC("AmlUart::MakeWriteCallbackLocked invalid state. No active Write operation.");
}
void AmlUart::Config(ConfigRequestView request, fdf::Arena& arena,
ConfigCompleter::Sync& completer) {
zx_status_t status = Config(request->baud_rate, request->flags);
if (status == ZX_OK) {
completer.buffer(arena).ReplySuccess();
} else {
completer.buffer(arena).ReplyError(status);
}
}
void AmlUart::Enable(EnableRequestView request, fdf::Arena& arena,
EnableCompleter::Sync& completer) {
zx_status_t status = Enable(request->enable);
if (status == ZX_OK) {
completer.buffer(arena).ReplySuccess();
} else {
completer.buffer(arena).ReplyError(status);
}
}
void AmlUart::Read(fdf::Arena& arena, ReadCompleter::Sync& completer) {
fbl::AutoLock lock(&read_lock_);
if (read_operation_) {
lock.release();
completer.buffer(arena).ReplyError(ZX_ERR_NOT_SUPPORTED);
return;
}
read_operation_.emplace(std::move(arena), completer.ToAsync());
lock.release();
HandleRX();
}
void AmlUart::Write(WriteRequestView request, fdf::Arena& arena, WriteCompleter::Sync& completer) {
fbl::AutoLock lock(&write_lock_);
if (write_operation_) {
lock.release();
completer.buffer(arena).ReplyError(ZX_ERR_NOT_SUPPORTED);
return;
}
write_buffer_ = request->data.data();
write_size_ = request->data.count();
write_operation_.emplace(std::move(arena), completer.ToAsync());
lock.release();
HandleTX();
}
void AmlUart::handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_hardware_serialimpl::Device> metadata,
fidl::UnknownMethodCompleter::Sync& completer) {
zxlogf(WARNING, "handle_unknown_method in fuchsia_hardware_serialimpl::Device server.");
}
zx::result<fidl::ClientEnd<fuchsia_power_broker::LeaseControl>> AmlUart::AcquireLease() {
auto lease_result = lessor_client_->Lease(kPowerLevelHandling);
if (lease_result.is_error()) {
zxlogf(ERROR, "Failed to aquire lease: %s",
lease_result.error_value().FormatDescription().c_str());
return zx::error(ZX_ERR_BAD_STATE);
}
if (!lease_result->lease_control().is_valid()) {
zxlogf(ERROR, "Lease returned invalid lease control client end.");
return zx::error(ZX_ERR_BAD_STATE);
}
// Wait until the lease is actually granted.
fidl::SyncClient<fuchsia_power_broker::LeaseControl> lease_control_client(
std::move(lease_result->lease_control()));
fuchsia_power_broker::LeaseStatus current_lease_status =
fuchsia_power_broker::LeaseStatus::kUnknown;
do {
auto watch_result = lease_control_client->WatchStatus(current_lease_status);
if (watch_result.is_error()) {
zxlogf(ERROR, "WatchStatus returned error: %s",
watch_result.error_value().FormatDescription().c_str());
return zx::error(ZX_ERR_BAD_STATE);
}
current_lease_status = watch_result->status();
} while (current_lease_status != fuchsia_power_broker::LeaseStatus::kSatisfied);
return zx::ok(lease_control_client.TakeClientEnd());
}
void AmlUart::HandleIrq(async_dispatcher_t* dispatcher, async::IrqBase* irq, zx_status_t status,
const zx_packet_interrupt_t* interrupt) {
if (status != ZX_OK) {
return;
}
// If the element control client end is not available, it means that power management is not
// enabled for this driver, the driver will not take a wake lease and set timer in this case.
if (element_control_client_end_) {
fbl::AutoLock lock(&timer_lock_);
if (!lease_control_client_end_.is_valid()) {
// Take the wake lease and keep the lease control client.
auto lease_control_result = AcquireLease();
if (lease_control_result.is_error()) {
return;
}
lease_control_client_end_ = std::move(*lease_control_result);
}
timeout_ = zx::deadline_after(zx::msec(kPowerLeaseTimeoutMs));
// Must set the new timeout before the wait on dispatcher begins, otherwise if there was a
// previous timer that has been fired, the new wait on dispatcher will capture the asserted
// signal again. Also we don't need to worry about missing the timeout signal from the new timer
// set, because the timeout signal will persist after timer fires.
lease_timer_.set(timeout_, zx::duration());
timer_waiter_.Begin(timer_dispatcher_->async_dispatcher());
}
auto uart_status = Status::Get().ReadFrom(&mmio_);
if (!uart_status.rx_empty()) {
HandleRX();
}
if (!uart_status.tx_full()) {
HandleTX();
}
irq_.ack();
}
void AmlUart::HandleLeaseTimer(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
fbl::AutoLock lock(&timer_lock_);
if (status == ZX_ERR_CANCELED) {
// Do nothing if the handler is triggered by the destroy of the dispatcher.
return;
}
if (status != ZX_OK) {
zxlogf(ERROR, "Timer fired with an error: %s", zx_status_get_string(status));
return;
}
if (zx::clock::get_monotonic() < timeout_) {
// If the current time is earlier than timeout, it means that this handler is triggered after
// |HandleIrq| holds the lock, and when this handler get the lock, the timer has been reset, so
// don't drop the lease in this case.
zxlogf(INFO,
"Timer has been reset by a new interrupt, this handler is out-of-date, so do nothing.");
return;
}
lease_control_client_end_.reset();
}
} // namespace serial