blob: a62a9bcee7d22ea7f9e367c23bf4a6b99eb5303b [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.
#include "aml-ram.h"
#include <lib/device-protocol/pdev.h>
#include <lib/zx/clock.h>
#include <zircon/assert.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <ddk/platform-defs.h>
#include <ddktl/fidl.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <soc/aml-s905d2/s905d2-hw.h>
namespace amlogic_ram {
constexpr size_t kMaxPendingRequests = 64u;
constexpr uint64_t kPortKeyIrqMsg = 0x0;
constexpr uint64_t kPortKeyCancelMsg = 0x1;
constexpr uint64_t kPortKeyWorkPendingMsg = 0x2;
// TODO(reveman): Understand why this is 16. Configurable and a product
// decision, or simply the way these counters are wired?
constexpr uint64_t kBytesPerCycle = 16ul;
zx_status_t ValidateRequest(const ram_metrics::BandwidthMeasurementConfig& config) {
// Restrict timer to reasonable values.
if ((config.cycles_to_measure < kMinimumCycleCount) ||
(config.cycles_to_measure > kMaximumCycleCount)) {
return ZX_ERR_INVALID_ARGS;
}
int enabled_count = 0;
for (size_t ix = 0; ix != ram_metrics::MAX_COUNT_CHANNELS; ++ix) {
auto& channel = config.channels[ix];
if ((ix >= MEMBW_MAX_CHANNELS) && (channel != 0)) {
// We only support the first four channels.
return ZX_ERR_INVALID_ARGS;
}
if (channel > 0xffffffff) {
// We don't support sub-ports (bits above 31) yet.
return ZX_ERR_NOT_SUPPORTED;
}
if (channel != 0) {
++enabled_count;
}
}
// At least one channel had at least one port.
if (enabled_count == 0) {
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
zx_status_t AmlRam::Create(void* context, zx_device_t* parent) {
zx_status_t status;
ddk::PDev pdev(parent);
std::optional<ddk::MmioBuffer> mmio;
if ((status = pdev.MapMmio(0, &mmio)) != ZX_OK) {
zxlogf(ERROR, "aml-ram: Failed to map mmio, st = %d", status);
return status;
}
zx::interrupt irq;
status = pdev.GetInterrupt(0, &irq);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-ram: Failed to map interrupt, st = %d", status);
return status;
}
zx::port port;
status = zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-ram: Failed to create port, st = %d", status);
return status;
}
status = irq.bind(port, kPortKeyIrqMsg, 0 /*options*/);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-ram: Failed to bind interrupt, st = %d", status);
return status;
}
pdev_device_info_t info;
status = pdev.GetDeviceInfo(&info);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-ram: Failed to get device info, st = %d", status);
return status;
}
fbl::AllocChecker ac;
auto device = fbl::make_unique_checked<AmlRam>(&ac, parent, *std::move(mmio), std::move(irq),
std::move(port), info.pid);
if (!ac.check()) {
zxlogf(ERROR, "aml-ram: Failed to allocate device memory");
return ZX_ERR_NO_MEMORY;
}
status = device->DdkAdd(ddk::DeviceAddArgs("ram")
.set_flags(DEVICE_ADD_NON_BINDABLE)
.set_proto_id(ZX_PROTOCOL_AMLOGIC_RAM));
if (status != ZX_OK) {
zxlogf(ERROR, "aml-ram: Failed to add ram device, st = %d", status);
return status;
}
// It's now the responsibility of |DdkRelease| to free this object.
__UNUSED auto* dummy = device.release();
return ZX_OK;
}
AmlRam::AmlRam(zx_device_t* parent, ddk::MmioBuffer mmio, zx::interrupt irq, zx::port port,
uint32_t device_pid)
: DeviceType(parent), mmio_(std::move(mmio)), irq_(std::move(irq)), port_(std::move(port)) {
// TODO(fxbug.dev/53325): ALL_GRANT counter is broken on S905D2.
all_grant_broken_ = device_pid == PDEV_PID_AMLOGIC_S905D2;
// Read windowing data:
// The S905D2 and the T931 both support the DMC_STICKY_1 register, which is where the
// DDR Windowing tool writes its results.
if (device_pid == PDEV_PID_AMLOGIC_S905D2 || device_pid == PDEV_PID_AMLOGIC_T931) {
windowing_data_supported_ = true;
} else {
windowing_data_supported_ = false;
}
}
AmlRam::~AmlRam() {
// Verify we drained all requests.
ZX_ASSERT(requests_.empty());
}
void AmlRam::DdkSuspend(ddk::SuspendTxn txn) {
// TODO(cpu): First put the device into txn.requested_state().
if (txn.suspend_reason() & (DEVICE_SUSPEND_REASON_POWEROFF | DEVICE_SUSPEND_REASON_MEXEC |
DEVICE_SUSPEND_REASON_REBOOT)) {
// Do any additional cleanup that is needed while shutting down the driver.
Shutdown();
}
txn.Reply(ZX_OK, txn.requested_state());
}
void AmlRam::DdkRelease() {
Shutdown();
delete this;
}
zx_status_t AmlRam::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
ram_metrics::Device::Dispatch(this, msg, &transaction);
return transaction.Status();
}
void AmlRam::MeasureBandwidth(ram_metrics::BandwidthMeasurementConfig config,
MeasureBandwidthCompleter::Sync& completer) {
zx_status_t st = ValidateRequest(config);
if (st != ZX_OK) {
zxlogf(ERROR, "aml-ram: bad request\n");
completer.ReplyError(st);
return;
}
if (!thread_.joinable()) {
thread_ = std::thread([this] { ReadLoop(); });
}
{
fbl::AutoLock lock(&lock_);
if (requests_.size() > kMaxPendingRequests) {
// Once the queue is shorter the request would likely succeed.
completer.ReplyError(ZX_ERR_SHOULD_WAIT);
return;
}
// Enqueue task and signal worker thread as needed.
requests_.emplace_back(std::move(config), completer.ToAsync());
if (requests_.size() == 1u) {
zx_port_packet_t packet = {
.key = kPortKeyWorkPendingMsg, .type = ZX_PKT_TYPE_USER, .status = ZX_OK};
ZX_ASSERT(port_.queue(&packet) == ZX_OK);
}
}
}
void AmlRam::GetDdrWindowingResults(GetDdrWindowingResultsCompleter::Sync& completer) {
if (windowing_data_supported_) {
completer.ReplySuccess(mmio_.Read32(DMC_STICKY_1));
} else {
zxlogf(ERROR, "aml-ram: windowing data is not supported\n");
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
}
void AmlRam::StartReadBandwithCounters(Job* job) {
uint32_t channels_enabled = 0u;
for (size_t ix = 0; ix != MEMBW_MAX_CHANNELS; ++ix) {
channels_enabled |= (job->config.channels[ix] != 0) ? (1u << ix) : 0;
mmio_.Write32(static_cast<uint32_t>(job->config.channels[ix]), MEMBW_RP[ix]);
mmio_.Write32(0xffff, MEMBW_SP[ix]);
}
job->start_time = zx_clock_get_monotonic();
mmio_.Write32(static_cast<uint32_t>(job->config.cycles_to_measure), MEMBW_TIMER);
mmio_.Write32(channels_enabled | DMC_QOS_ENABLE_CTRL, MEMBW_PORTS_CTRL);
}
void AmlRam::FinishReadBandwithCounters(ram_metrics::BandwidthInfo* bpi, zx_time_t start_time) {
ZX_ASSERT(irq_.ack() == ZX_OK);
bpi->timestamp = start_time;
bpi->frequency = ReadFrequency();
bpi->bytes_per_cycle = kBytesPerCycle;
uint32_t value = mmio_.Read32(MEMBW_PORTS_CTRL);
ZX_ASSERT((value & DMC_QOS_ENABLE_CTRL) == 0);
bpi->channels[0].readwrite_cycles = mmio_.Read32(MEMBW_C0_GRANT_CNT);
bpi->channels[1].readwrite_cycles = mmio_.Read32(MEMBW_C1_GRANT_CNT);
bpi->channels[2].readwrite_cycles = mmio_.Read32(MEMBW_C2_GRANT_CNT);
bpi->channels[3].readwrite_cycles = mmio_.Read32(MEMBW_C3_GRANT_CNT);
bpi->total.readwrite_cycles = all_grant_broken_ ? 0 : mmio_.Read32(MEMBW_ALL_GRANT_CNT);
mmio_.Write32(0x0f | DMC_QOS_CLEAR_CTRL, MEMBW_PORTS_CTRL);
}
void AmlRam::CancelReadBandwithCounters() {
mmio_.Write32(0x0f | DMC_QOS_CLEAR_CTRL, MEMBW_PORTS_CTRL);
// Here there might be a pending interrupt packet. The caller
// is going to exit so it is immaterial if we drain it or
// not.
}
void AmlRam::ReadLoop() {
std::deque<Job> jobs;
for (;;) {
zx_port_packet_t packet;
auto status = port_.wait(zx::time::infinite(), &packet);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-ram: error in wait, st =%d\n", status);
return;
}
switch (packet.key) {
case kPortKeyWorkPendingMsg: {
AcceptJobs(&jobs);
StartReadBandwithCounters(&jobs.front());
break;
}
case kPortKeyIrqMsg: {
ZX_ASSERT(!jobs.empty());
ram_metrics::BandwidthInfo bpi;
FinishReadBandwithCounters(&bpi, jobs.front().start_time);
Job job = std::move(jobs.front());
jobs.pop_front();
// Start new measurement before we reply the current one.
if (!jobs.empty()) {
StartReadBandwithCounters(&jobs.front());
}
job.completer.ReplySuccess(bpi);
break;
}
case kPortKeyCancelMsg: {
if (!jobs.empty()) {
CancelReadBandwithCounters();
RevertJobs(&jobs);
}
return;
}
default: {
ZX_ASSERT(false);
}
}
}
}
// Merge back the request jobs from the local jobs in |source| preserving
// the order of arrival: the last job in |source| is ahead of the
// first job in |request_|.
void AmlRam::RevertJobs(std::deque<AmlRam::Job>* source) {
fbl::AutoLock lock(&lock_);
requests_.insert(requests_.begin(), std::make_move_iterator(source->begin()),
std::make_move_iterator(source->end()));
source->clear();
}
// Merge requests from |request_| into local jobs while preserving order
// of arrival.
void AmlRam::AcceptJobs(std::deque<AmlRam::Job>* dest) {
fbl::AutoLock lock(&lock_);
dest->insert(dest->end(), std::make_move_iterator(requests_.begin()),
std::make_move_iterator(requests_.end()));
requests_.clear();
}
void AmlRam::Shutdown() {
if (thread_.joinable()) {
{
fbl::AutoLock lock(&lock_);
shutdown_ = true;
zx_port_packet_t packet = {
.key = kPortKeyCancelMsg, .type = ZX_PKT_TYPE_USER, .status = ZX_OK};
ZX_ASSERT(port_.queue(&packet) == ZX_OK);
}
thread_.join();
// Cancel all pending requests. There are no more threads
// but we still take the lock to keep lock checker happy.
{
fbl::AutoLock lock(&lock_);
for (auto& request : requests_) {
request.completer.Close(ZX_ERR_CANCELED);
}
requests_.clear();
}
}
}
uint64_t AmlRam::ReadFrequency() const {
uint32_t value = mmio_.Read32(MEMBW_PLL_CNTL);
uint64_t dpll_int_num = value & 0x1ff;
uint64_t dpll_ref_div_n = (value >> 10) & 0x1f;
uint64_t od = (value >> 16) & 0x7;
uint64_t od1 = (value >> 19) & 0x1;
ZX_ASSERT(dpll_ref_div_n);
uint64_t od_div = 1;
switch (od) {
case 0:
od_div = 2; // 000:/2
break;
case 1:
od_div = 3; // 001:/3
break;
case 2:
od_div = 4; // 010:/4
break;
case 3:
od_div = 6; // 011:/6
break;
case 4:
od_div = 8; // 100:/8
break;
}
uint64_t od1_shift = od1 == 0 ? 1 : 2; // 0:/2, 1:/4
// Frequency is calculated with the following equation:
//
// f = fREF * (M + frac) / N
//
constexpr uint64_t kFreqRef = 24000000;
return (((kFreqRef * dpll_int_num) / dpll_ref_div_n) >> od1_shift) / od_div;
}
} // namespace amlogic_ram
static constexpr zx_driver_ops_t aml_ram_driver_ops = []() {
zx_driver_ops_t result = {};
result.version = DRIVER_OPS_VERSION;
result.bind = amlogic_ram::AmlRam::Create;
return result;
}();
// clang-format off
ZIRCON_DRIVER_BEGIN(aml_ram, aml_ram_driver_ops, "zircon", "0.1", 5)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PDEV),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_AMLOGIC),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_DID, PDEV_DID_AMLOGIC_RAM_CTL),
// This driver can likely support S905D3 in the future.
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_PID, PDEV_PID_AMLOGIC_S905D2),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_PID, PDEV_PID_AMLOGIC_T931),
ZIRCON_DRIVER_END(aml_ram)
// clang-format on