blob: 6fc2749d57e28d6386d0daf3c9f2278b60c416c6 [file] [log] [blame]
// 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 <memory>
#include <mutex>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/platform-device-lib.h>
#include <ddk/protocol/platform/device.h>
#include <ddktl/device.h>
#include <ddktl/pdev.h>
#include <ddktl/protocol/clock.h>
#include <ddktl/protocol/empty-protocol.h>
#include <fuchsia/gpu/magma/c/fidl.h>
#include <hw/reg.h>
#include <lib/fidl-utils/bind.h>
#include "magma_util/macros.h"
#include "sys_driver/magma_driver.h"
#define GPU_ERROR(fmt, ...) zxlogf(ERROR, "[%s %d]" fmt, __func__, __LINE__, ##__VA_ARGS__)
enum {
// Indices into clocks provided by the board file.
kClkSlowMfgIndex = 0,
kClkAxiMfgIndex = 1,
kClkMfgMmIndex = 2,
kClockCount,
// Indices into mmio buffers provided by the board file.
kMfgMmioIndex = 0,
kMfgTopMmioIndex = 1,
kScpsysMmioIndex = 2,
kXoMmioIndex = 3,
kInfraTopAxiSi1Ctl = 0x1204,
kInfraTopAxiProtectEn = 0x1220,
kInfraTopAxiProtectSta1 = 0x1228,
kPwrStatus = 0x60c,
kPwrStatus2nd = 0x610,
};
struct ComponentDescription {
zx_status_t PowerOn(ddk::MmioBuffer* power_gpu_buffer);
bool IsPoweredOn(ddk::MmioBuffer* power_gpu_buffer, uint32_t bit);
// offset into power_gpu_buffer registers
uint32_t reg_offset;
// Index into the power status registers, used to determine when powered on.
uint32_t on_bit_offset;
// Bits in the register that need to be set to zero to power on the SRAM.
uint32_t sram_bits;
// Bits in the register that will be cleared once the SRAM is powered on.
uint32_t sram_ack_bits;
};
class Mt8167sGpu;
using DeviceType = ddk::Device<Mt8167sGpu, ddk::Messageable>;
class Mt8167sGpu : public DeviceType, public ddk::EmptyProtocol<ZX_PROTOCOL_GPU> {
public:
Mt8167sGpu(zx_device_t* parent) : DeviceType(parent) {}
~Mt8167sGpu();
zx_status_t Bind();
void DdkRelease();
zx_status_t DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn);
zx_status_t Query(uint64_t query_id, fidl_txn_t* transaction);
zx_status_t Connect(uint64_t client_id, fidl_txn_t* transaction);
zx_status_t DumpState(uint32_t dump_type);
zx_status_t Restart();
private:
bool StartMagma() MAGMA_REQUIRES(magma_mutex_);
void StopMagma() MAGMA_REQUIRES(magma_mutex_);
// MFG is Mediatek's name for their graphics subsystem.
zx_status_t PowerOnMfgAsync();
zx_status_t PowerOnMfg2d();
zx_status_t PowerOnMfg();
void EnableMfgHwApm();
ddk::ClockProtocolClient clks_[kClockCount];
// MFG TOP MMIO - Controls mediatek's gpu-related power- and
// clock-management hardware.
std::optional<ddk::MmioBuffer> gpu_buffer_;
// MFG MMIO (corresponds to the IMG GPU's registers)
std::optional<ddk::MmioBuffer> real_gpu_buffer_;
std::optional<ddk::MmioBuffer> power_gpu_buffer_; // SCPSYS MMIO
std::optional<ddk::MmioBuffer> clock_gpu_buffer_; // XO MMIO
std::mutex magma_mutex_;
std::unique_ptr<MagmaDriver> magma_driver_ MAGMA_GUARDED(magma_mutex_);
std::shared_ptr<MagmaSystemDevice> magma_system_device_ MAGMA_GUARDED(magma_mutex_);
};
Mt8167sGpu::~Mt8167sGpu()
{
std::lock_guard<std::mutex> lock(magma_mutex_);
StopMagma();
}
bool Mt8167sGpu::StartMagma()
{
magma_system_device_ = magma_driver_->CreateDevice(parent());
return !!magma_system_device_;
}
void Mt8167sGpu::StopMagma()
{
if (magma_system_device_) {
magma_system_device_->Shutdown();
magma_system_device_.reset();
}
}
void Mt8167sGpu::DdkRelease() { delete this; }
static fuchsia_gpu_magma_Device_ops_t device_fidl_ops = {
.Query = fidl::Binder<Mt8167sGpu>::BindMember<&Mt8167sGpu::Query>,
.Connect = fidl::Binder<Mt8167sGpu>::BindMember<&Mt8167sGpu::Connect>,
.DumpState = fidl::Binder<Mt8167sGpu>::BindMember<&Mt8167sGpu::DumpState>,
.TestRestart = fidl::Binder<Mt8167sGpu>::BindMember<&Mt8167sGpu::Restart>,
};
zx_status_t Mt8167sGpu::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn)
{
return fuchsia_gpu_magma_Device_dispatch(this, txn, msg, &device_fidl_ops);
}
zx_status_t ComponentDescription::PowerOn(ddk::MmioBuffer* power_gpu_buffer)
{
enum {
kPowerResetBBit = 0,
kPowerIsoBit = 1,
kPowerOnBit = 2,
kPowerOn2ndBit = 3,
kPowerOnClkDisBit = 4,
};
power_gpu_buffer->SetBit<uint32_t>(kPowerOnBit, reg_offset);
power_gpu_buffer->SetBit<uint32_t>(kPowerOn2ndBit, reg_offset);
zx::time timeout = zx::deadline_after(zx::msec(100)); // Arbitrary timeout
while (!IsPoweredOn(power_gpu_buffer, on_bit_offset)) {
if (zx::clock::get_monotonic() > timeout) {
GPU_ERROR("Timed out powering on component");
return ZX_ERR_TIMED_OUT;
}
}
power_gpu_buffer->ClearBit<uint32_t>(kPowerOnClkDisBit, reg_offset);
power_gpu_buffer->ClearBit<uint32_t>(kPowerIsoBit, reg_offset);
power_gpu_buffer->SetBit<uint32_t>(kPowerResetBBit, reg_offset);
if (sram_bits) {
power_gpu_buffer->ClearBits32(sram_bits, reg_offset);
zx::time timeout = zx::deadline_after(zx::msec(100)); // Arbitrary timeout
while (power_gpu_buffer->ReadMasked32(sram_ack_bits, reg_offset)) {
if (zx::clock::get_monotonic() > timeout) {
GPU_ERROR("Timed out powering on SRAM");
return ZX_ERR_TIMED_OUT;
}
}
}
return ZX_OK;
}
bool ComponentDescription::IsPoweredOn(ddk::MmioBuffer* power_gpu_buffer, uint32_t bit)
{
return power_gpu_buffer->GetBit<uint32_t>(bit, kPwrStatus) &&
power_gpu_buffer->GetBit<uint32_t>(bit, kPwrStatus2nd);
}
// Power on the asynchronous memory interface between the GPU and the DDR controller.
zx_status_t Mt8167sGpu::PowerOnMfgAsync()
{
// Set clock sources properly. Some of these are also used by the 3D and 2D
// cores.
clock_gpu_buffer_->ModifyBits<uint32_t>(0, 20, 2, 0x40); // slow mfg mux to 26MHz
// MFG AXI to mainpll_d11 (on version 2+ of chip)
clock_gpu_buffer_->ModifyBits<uint32_t>(1, 18, 2, 0x40);
clks_[kClkSlowMfgIndex].Enable();
clks_[kClkAxiMfgIndex].Enable();
constexpr uint32_t kAsyncPwrStatusBit = 25;
constexpr uint32_t kAsyncPwrRegOffset = 0x2c4;
ComponentDescription mfg_async = {kAsyncPwrRegOffset, kAsyncPwrStatusBit, 0, 0};
return mfg_async.PowerOn(&power_gpu_buffer_.value());
}
// Power on the 2D engine (it's unclear whether this is needed to access the 3D
// GPU, but power it on anyway).
zx_status_t Mt8167sGpu::PowerOnMfg2d()
{
// Enable access to AXI Bus
clock_gpu_buffer_->SetBits32((1 << 7), kInfraTopAxiSi1Ctl);
constexpr uint32_t k2dPwrStatusBit = 24;
constexpr uint32_t k2dPwrRegOffset = 0x2c0;
ComponentDescription mfg_2d = {k2dPwrRegOffset, k2dPwrStatusBit, 0xf << 8, 0xf << 12};
zx_status_t status = mfg_2d.PowerOn(&power_gpu_buffer_.value());
if (status != ZX_OK)
return status;
// Disable AXI protection after it's powered up.
clock_gpu_buffer_->ClearBits32((1 << 2) | (1 << 5), kInfraTopAxiProtectEn);
zx_nanosleep(zx_deadline_after(ZX_USEC(100)));
return ZX_OK;
}
// Power on the 3D engine (IMG GPU).
zx_status_t Mt8167sGpu::PowerOnMfg()
{
clks_[kClkMfgMmIndex].Enable();
static constexpr uint32_t kMfg3dPwrCon = 0x214;
ComponentDescription mfg = {kMfg3dPwrCon, 4, 0xf << 8, 0xf << 12};
zx_status_t status = mfg.PowerOn(&power_gpu_buffer_.value());
if (status != ZX_OK)
return status;
// Power on MFG (internal to TOP)
constexpr uint32_t kMfgCgClr = 0x8;
constexpr uint32_t kBAxiClr = (1 << 0);
constexpr uint32_t kBMemClr = (1 << 1);
constexpr uint32_t kBG3dClr = (1 << 2);
constexpr uint32_t kB26MClr = (1 << 3);
gpu_buffer_->SetBits32(kBAxiClr | kBMemClr | kBG3dClr | kB26MClr, kMfgCgClr);
EnableMfgHwApm();
return ZX_OK;
}
// Enable hardware-controlled power management.
void Mt8167sGpu::EnableMfgHwApm()
{
struct {
uint32_t value;
uint32_t offset;
} writes[] = {
{0x01a80000, 0x504}, {0x00080010, 0x508}, {0x00080010, 0x50c}, {0x00b800b8, 0x510},
{0x00b000b0, 0x514}, {0x00c000c8, 0x518}, {0x00c000c8, 0x51c}, {0x00d000d8, 0x520},
{0x00d800d8, 0x524}, {0x00d800d8, 0x528}, {0x9000001b, 0x24}, {0x8000001b, 0x24},
};
for (uint32_t i = 0; i < countof(writes); i++) {
gpu_buffer_->Write32(writes[i].value, writes[i].offset);
}
}
static uint64_t ReadHW64(const ddk::MmioBuffer* buffer, uint32_t offset)
{
// Read 2 registers to combine into a 64-bit register.
return (static_cast<uint64_t>(buffer->Read32(offset + 4)) << 32) | buffer->Read32(offset);
}
zx_status_t Mt8167sGpu::Bind()
{
pdev_protocol_t pdev_proto;
zx_status_t status;
if ((status = device_get_protocol(parent(), ZX_PROTOCOL_PDEV, &pdev_proto)) != ZX_OK) {
GPU_ERROR("ZX_PROTOCOL_PDEV not available\n");
return status;
}
ddk::PDev pdev(&pdev_proto);
for (unsigned i = 0; i < kClockCount; i++) {
clks_[i] = pdev.GetClk(i);
if (!clks_[i].is_valid()) {
zxlogf(ERROR, "%s GetClk failed %d\n", __func__, status);
return status;
}
}
status = pdev.MapMmio(kMfgMmioIndex, &real_gpu_buffer_);
if (status != ZX_OK) {
GPU_ERROR("pdev_map_mmio_buffer failed\n");
return status;
}
status = pdev.MapMmio(kMfgTopMmioIndex, &gpu_buffer_);
if (status != ZX_OK) {
GPU_ERROR("pdev_map_mmio_buffer failed\n");
return status;
}
status = pdev.MapMmio(kScpsysMmioIndex, &power_gpu_buffer_);
if (status != ZX_OK) {
GPU_ERROR("pdev_map_mmio_buffer failed\n");
return status;
}
status = pdev.MapMmio(kXoMmioIndex, &clock_gpu_buffer_);
if (status != ZX_OK) {
GPU_ERROR("pdev_map_mmio_buffer failed\n");
return status;
}
// Power on in order.
status = PowerOnMfgAsync();
if (status != ZX_OK) {
GPU_ERROR("Failed to power on MFG ASYNC\n");
return status;
}
status = PowerOnMfg2d();
if (status != ZX_OK) {
GPU_ERROR("Failed to power on MFG 2D\n");
return status;
}
status = PowerOnMfg();
if (status != ZX_OK) {
GPU_ERROR("Failed to power on MFG\n");
return status;
}
zxlogf(INFO, "[mt8167s-gpu] GPU ID: %lx\n", ReadHW64(&real_gpu_buffer_.value(), 0x18));
zxlogf(INFO, "[mt8167s-gpu] GPU core revision: %lx\n",
ReadHW64(&real_gpu_buffer_.value(), 0x20));
{
std::lock_guard<std::mutex> lock(magma_mutex_);
magma_driver_ = MagmaDriver::Create();
if (!magma_driver_) {
GPU_ERROR("Failed to create MagmaDriver\n");
return ZX_ERR_INTERNAL;
}
if (!StartMagma()) {
GPU_ERROR("Failed to start Magma system device\n");
return ZX_ERR_INTERNAL;
}
}
return DdkAdd("mt8167s-gpu");
}
zx_status_t Mt8167sGpu::Query(uint64_t query_id, fidl_txn_t* transaction)
{
DLOG("Mt8167sGpu::Query");
std::lock_guard<std::mutex> lock(magma_mutex_);
uint64_t result;
switch (query_id) {
case MAGMA_QUERY_DEVICE_ID:
result = magma_system_device_->GetDeviceId();
break;
case MAGMA_QUERY_IS_TEST_RESTART_SUPPORTED:
result = 0;
break;
default:
if (!magma_system_device_->Query(query_id, &result))
return DRET_MSG(ZX_ERR_INVALID_ARGS, "unhandled query param 0x%" PRIx64, result);
}
DLOG("query query_id 0x%" PRIx64 " returning 0x%" PRIx64, query_id, result);
zx_status_t status = fuchsia_gpu_magma_DeviceQuery_reply(transaction, result);
if (status != ZX_OK)
return DRET_MSG(ZX_ERR_INTERNAL, "magma_DeviceQuery_reply failed: %d", status);
return ZX_OK;
}
zx_status_t Mt8167sGpu::Connect(uint64_t client_id, fidl_txn_t* transaction)
{
DLOG("Mt8167sGpu::Connect");
std::lock_guard<std::mutex> lock(magma_mutex_);
auto connection = MagmaSystemDevice::Open(magma_system_device_, client_id);
if (!connection)
return DRET_MSG(ZX_ERR_INVALID_ARGS, "MagmaSystemDevice::Open failed");
zx_status_t status = fuchsia_gpu_magma_DeviceConnect_reply(
transaction, connection->GetClientEndpoint(), connection->GetClientNotificationEndpoint());
if (status != ZX_OK)
return DRET_MSG(ZX_ERR_INTERNAL, "magma_DeviceConnect_reply failed: %d", status);
magma_system_device_->StartConnectionThread(std::move(connection));
return ZX_OK;
}
zx_status_t Mt8167sGpu::DumpState(uint32_t dump_type)
{
DLOG("Mt8167sGpu::DumpState");
std::lock_guard<std::mutex> lock(magma_mutex_);
if (dump_type & ~(MAGMA_DUMP_TYPE_NORMAL | MAGMA_DUMP_TYPE_PERF_COUNTERS |
MAGMA_DUMP_TYPE_PERF_COUNTER_ENABLE))
return DRET_MSG(ZX_ERR_INVALID_ARGS, "Invalid dump type %x", dump_type);
if (magma_system_device_)
magma_system_device_->DumpStatus(dump_type);
return ZX_OK;
}
zx_status_t Mt8167sGpu::Restart() { return ZX_ERR_NOT_SUPPORTED; }
extern "C" zx_status_t mt8167s_gpu_bind(void* ctx, zx_device_t* parent)
{
auto dev = std::make_unique<Mt8167sGpu>(parent);
auto status = dev->Bind();
if (status == ZX_OK) {
// devmgr is now in charge of the memory for dev
dev.release();
}
return status;
}
static zx_driver_ops_t mt8167s_gpu_driver_ops = {
.version = DRIVER_OPS_VERSION,
.init = nullptr,
.bind = mt8167s_gpu_bind,
.create = nullptr,
.release = nullptr,
};
// clang-format off
ZIRCON_DRIVER_BEGIN(mt8167s_gpu, mt8167s_gpu_driver_ops, "zircon", "0.1", 3)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PDEV),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_MEDIATEK),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_MEDIATEK_GPU),
ZIRCON_DRIVER_END(mt8167s_gpu)