blob: 30e74dfc9f953d548a46f5af0e11cc0ff4b67198 [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 <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 <soc/aml-s905d2/s905d2-hw.h>
namespace amlogic_ram {
// There are 4 monitoring channels and each one can agregate up to 64
// hardware memory ports. NOTE: the word channel and port in this file
// refer to hardware, not to zircon objects.
constexpr size_t MEMBW_MAX_CHANNELS = 4u;
// Controls start,stop and if polling or interrupt mode.
constexpr uint32_t MEMBW_PORTS_CTRL = (0x0020 << 2);
constexpr uint32_t DMC_QOS_ENABLE_CTRL = (0x01 << 31);
constexpr uint32_t DMC_QOS_CLEAR_CTRL = (0x01 << 30);
// Returns the granted cycles counters total.
constexpr uint32_t MEMBW_ALL_GRANT_CNT = (0x002a << 2);
// Returns the granted cycles per channel.
constexpr uint32_t MEMBW_C0_GRANT_CNT = (0x2b << 2);
constexpr uint32_t MEMBW_C1_GRANT_CNT = (0x2c << 2);
constexpr uint32_t MEMBW_C2_GRANT_CNT = (0x2d << 2);
constexpr uint32_t MEMBW_C3_GRANT_CNT = (0x2e << 2);
// Controls how long to measure cycles for.
constexpr uint32_t MEMBW_TIMER = (0x002f << 2);
// Controls which ports are assigned to each channel.
constexpr uint32_t MEMBW_RP[MEMBW_MAX_CHANNELS] = {(0x0021 << 2), (0x0023 << 2), (0x0025 << 2),
(0x0027 << 2)};
// Controls wich subports are assinged to each channel.
constexpr uint32_t MEMBW_SP[MEMBW_MAX_CHANNELS] = {(0x0022 << 2), (0x0024 << 2), (0x0026 << 2),
(0x0028 << 2)};
constexpr double kMemCyclePerSecond = (912.0 / 2.0);
constexpr uint32_t kMemCycleCount = 1024 * 1024 * 57u;
// Ports constants for Astro and Sherlock.
// TODO(cpu) move this constants out of the driver and into the client.
#define PortID_ARM_AE (0x01u << 0)
#define PortID_MALI (0x01u << 1)
#define PortID_PCIE (0x01u << 2)
#define PortID_HDCP (0x01u << 3)
#define PortID_HEVC_FRONT (0x01u << 4)
#define PortID_TEST (0x01u << 5)
#define PortID_USB30 (0x01u << 6)
#define PortID_HEVC_BACK (0x01u << 8)
#define PortID_H265_ENC (0x01u << 9)
#define PortID_VPU_R1 (0x01u << 16)
#define PortID_VPU_R2 (0x01u << 17)
#define PortID_VPU_R3 (0x01u << 18)
#define PortID_VPU_W1 (0x01u << 19)
#define PortID_VPU_W2 (0x01u << 20)
#define PortID_VDEC (0x01u << 21)
#define PortID_HCODEC (0x01u << 22)
#define PortID_GE2D (0x01u << 23)
// Sherlock-only ports.
#define PortID_NNA (0x01u << 10)
#define PortID_GDC (0x01u << 11)
#define PortID_MIPI_ISP (0x01u << 12)
#define PortID_ARM_AF (0x01u << 13)
struct BwPorts {
// Each port is a single bit per channel.
uint64_t chn[MEMBW_MAX_CHANNELS];
};
constexpr double CalcBandwidth(uint32_t counter) {
return (static_cast<double>(counter) * 16.0 * kMemCyclePerSecond) / kMemCycleCount;
}
void InitBandwithTimer(ddk::MmioBuffer& mmio) {
// The only thing to init is how many cycles to sample for
// bandwith calculations.
mmio.Write32(kMemCycleCount, MEMBW_TIMER);
}
// Read the counters and output the bandwith computation via zxlog.
// This only happens if the driver has driver.aml_ram.log=+trace.
zx_status_t ReadBandwithCounters(ddk::MmioBuffer& mmio, const BwPorts& ports, zx::event& event) {
uint32_t channels_enabled = 0u;
for (size_t ix = 0; ix != MEMBW_MAX_CHANNELS; ++ix) {
if (ports.chn[ix] > 0xffffffff) {
// We don't support sub-ports (bits above 31) yet.
return ZX_ERR_NOT_SUPPORTED;
}
channels_enabled |= (ports.chn[ix] != 0) ? (1u << ix) : 0;
mmio.Write32(static_cast<uint32_t>(ports.chn[ix]), MEMBW_RP[ix]);
mmio.Write32(0xffff, MEMBW_SP[ix]);
}
if (channels_enabled == 0x0) {
// Nothing to monitor.
return ZX_OK;
}
mmio.Write32(channels_enabled | DMC_QOS_ENABLE_CTRL, MEMBW_PORTS_CTRL);
uint32_t value = mmio.Read32(MEMBW_PORTS_CTRL);
uint32_t count = 0;
// Polling loop for the bandwith cycle counters.
// TODO(cpu): tune wait interval to minimize polling.
while (value & DMC_QOS_ENABLE_CTRL) {
if (count++ > 20) {
return ZX_ERR_TIMED_OUT;
}
auto status = event.wait_one(ZX_EVENT_SIGNALED, zx::deadline_after(zx::msec(50)), nullptr);
if (status != ZX_ERR_TIMED_OUT) {
// Likely shutdown. The caller handles this.
return ZX_OK;
}
value = mmio.Read32(MEMBW_PORTS_CTRL);
}
auto counter_all = mmio.Read32(MEMBW_ALL_GRANT_CNT);
auto counter_0 = mmio.Read32(MEMBW_C0_GRANT_CNT);
auto counter_1 = mmio.Read32(MEMBW_C1_GRANT_CNT);
auto counter_2 = mmio.Read32(MEMBW_C2_GRANT_CNT);
auto counter_3 = mmio.Read32(MEMBW_C3_GRANT_CNT);
zxlogf(TRACE, "aml-ram: bw:%g %g %g %g %g cz:%d", CalcBandwidth(counter_all),
CalcBandwidth(counter_0), CalcBandwidth(counter_1), CalcBandwidth(counter_2),
CalcBandwidth(counter_3), count);
mmio.Write32(channels_enabled | DMC_QOS_CLEAR_CTRL, MEMBW_PORTS_CTRL);
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;
}
fbl::AllocChecker ac;
auto device = fbl::make_unique_checked<AmlRam>(&ac, parent, *std::move(mmio));
if (!ac.check()) {
zxlogf(ERROR, "aml-ram: Failed to allocate device memory");
return ZX_ERR_NO_MEMORY;
}
status = device->DdkAdd("ram", DEVICE_ADD_NON_BINDABLE);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-ram: Failed to add ram device, st = %d", status);
return status;
}
__UNUSED auto* dummy = device.release();
return ZX_OK;
}
AmlRam::AmlRam(zx_device_t* parent, ddk::MmioBuffer mmio)
: DeviceType(parent), mmio_(std::move(mmio)) {
if (zxlog_level_enabled(TRACE)) {
auto status = zx::event::create(0u, &shutdown_);
ZX_ASSERT(status == ZX_OK);
thread_ = std::thread([this] { ReadLoop(); });
}
}
void AmlRam::DdkSuspend(ddk::SuspendTxn txn) {
Shutdown();
txn.Reply(ZX_OK, txn.requested_state());
}
void AmlRam::DdkRelease() {
Shutdown();
delete this;
}
zx_status_t AmlRam::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
// TODO(cpu): Implement dispatch of the fuchsia.hardware.ram.metrics
// FIDL interface. This controls which ports to sample rather than
// hardcoding them in ReadLoop().
return transaction.Status();
}
// TODO(cpu): Remove this function once the FIDL protocol is implemented.
void AmlRam::ReadLoop() {
constexpr uint32_t kDecoder = PortID_HEVC_FRONT | PortID_HEVC_BACK | PortID_VDEC | PortID_HCODEC;
constexpr uint32_t kVPU =
PortID_VPU_R1 | PortID_VPU_R2 | PortID_VPU_R3 | PortID_VPU_W1 | PortID_VPU_W2;
// Sample the following 4 channels for the time being.
const BwPorts kPorts = {{PortID_ARM_AE, PortID_MALI, kDecoder, kVPU}};
InitBandwithTimer(mmio_);
for (;;) {
auto status = ReadBandwithCounters(mmio_, kPorts, shutdown_);
if (status != ZX_OK) {
zxlogf(ERROR, "aml-ram: error reading counters, st =%d", status);
return;
}
zx_signals_t observed = 0u;
status = shutdown_.wait_one(ZX_EVENT_SIGNALED, zx::deadline_after(zx::sec(3)), &observed);
if (status == ZX_ERR_TIMED_OUT) {
continue;
} else if (status != ZX_OK) {
zxlogf(ERROR, "aml-ram: error in wait_one, st =%d", status);
return;
} else {
// Normal shutdown.
zxlogf(TRACE, "aml-ram: loop shutdown");
return;
}
};
}
void AmlRam::Shutdown() {
if (shutdown_) {
shutdown_.signal(0u, ZX_EVENT_SIGNALED);
thread_.join();
shutdown_.reset();
}
}
} // 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