[drivers][msd-img-rgx][mtk] Add driver that enables power

This driver binds to the device and enables power so that the GPU ID
registers can be read. In the future this can either export a device
that the msd-img-rgx driver binds to (similar to aml-gpu and
msd-arm-mali) or link directly to the IMG driver and call into it.

Test: boot on cleo.

Change-Id: I7f933f736e8e91e1f053ebd18618a4d16483e6e7
diff --git a/garnet/drivers/gpu/msd-img-rgx/mtk/BUILD.gn b/garnet/drivers/gpu/msd-img-rgx/mtk/BUILD.gn
new file mode 100644
index 0000000..5264d8f
--- /dev/null
+++ b/garnet/drivers/gpu/msd-img-rgx/mtk/BUILD.gn
@@ -0,0 +1,24 @@
+# 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.
+
+import("//build/config/fuchsia/rules.gni")
+
+driver_module("msd_img_rgx_mtk") {
+  output_name = "libmsd_img_rgx_mtk"
+
+  sources = [
+    "mt8167s-gpu.cpp",
+  ]
+
+  deps = [
+    "//zircon/public/banjo/ddk-protocol-platform-bus",
+    "//zircon/public/banjo/ddk-protocol-platform-device",
+    "//zircon/public/lib/ddk",
+    "//zircon/public/lib/ddktl",
+    "//zircon/public/lib/driver",
+  ]
+
+  configs -= [ "//build/config/fuchsia:no_cpp_standard_library" ]
+  configs += [ "//build/config/fuchsia:static_cpp_standard_library" ]
+}
diff --git a/garnet/drivers/gpu/msd-img-rgx/mtk/mt8167s-gpu.cpp b/garnet/drivers/gpu/msd-img-rgx/mtk/mt8167s-gpu.cpp
new file mode 100644
index 0000000..4a48375
--- /dev/null
+++ b/garnet/drivers/gpu/msd-img-rgx/mtk/mt8167s-gpu.cpp
@@ -0,0 +1,297 @@
+// 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 <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/bus.h>
+#include <ddk/protocol/platform/device.h>
+
+#include <ddktl/device.h>
+#include <ddktl/pdev.h>
+#include <ddktl/protocol/clk.h>
+#include <hw/reg.h>
+
+#define GPU_ERROR(fmt, ...) zxlogf(ERROR, "[%s %d]" fmt, __func__, __LINE__, ##__VA_ARGS__)
+
+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>;
+
+class Mt8167sGpu : public DeviceType {
+public:
+    Mt8167sGpu(zx_device_t* parent) : DeviceType(parent) {}
+
+    zx_status_t Bind();
+    void DdkRelease();
+private:
+    // MFG is Mediatek's name for their graphics subsystem.
+    zx_status_t PowerOnMfgAsync();
+    zx_status_t PowerOnMfg2d();
+    zx_status_t PowerOnMfg();
+
+    void EnableMfgHwApm();
+
+    clk_protocol_t clk_proto_ = {};
+    std::optional<ddk::ClkProtocolClient> clk_;
+    // 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
+};
+
+void Mt8167sGpu::DdkRelease() { delete this; }
+
+enum {
+    // Indices into clocks provided by the board file.
+    kClkSlowMfgIndex = 0,
+    kClkAxiMfgIndex = 1,
+    kClkMfgMmIndex = 2,
+
+    // 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,
+};
+
+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);
+    clk_->Enable(kClkSlowMfgIndex);
+    clk_->Enable(kClkAxiMfgIndex);
+    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()
+{
+    clk_->Enable(kClkMfgMmIndex);
+    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);
+
+    status = device_get_protocol(parent(), ZX_PROTOCOL_CLK, &clk_proto_);
+    if (status != ZX_OK) {
+        GPU_ERROR("ZX_PROTOCOL_CLK not available: %d", status);
+        return status;
+    }
+
+    clk_ = ddk::ClkProtocolClient(&clk_proto_);
+
+    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));
+
+    return DdkAdd("mt8167s-gpu");
+}
+
+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)
+// clang-format on
diff --git a/garnet/lib/magma/gnbuild/magma-img-rgx/BUILD.gn b/garnet/lib/magma/gnbuild/magma-img-rgx/BUILD.gn
new file mode 100644
index 0000000..aa055be
--- /dev/null
+++ b/garnet/lib/magma/gnbuild/magma-img-rgx/BUILD.gn
@@ -0,0 +1,18 @@
+# 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.
+
+import("//build/package.gni")
+import("//garnet/lib/magma/gnbuild/magma.gni")
+
+package("magma-img-rgx-mtk") {
+  deprecated_system_image = true
+  deps = [
+    "//garnet/drivers/gpu/msd-img-rgx/mtk:msd_img_rgx_mtk",
+  ]
+  drivers = [
+    {
+      name = "libmsd_img_rgx_mtk.so"
+    },
+  ]
+}
diff --git a/garnet/packages/prod/all b/garnet/packages/prod/all
index 876b43bd..91b1650 100644
--- a/garnet/packages/prod/all
+++ b/garnet/packages/prod/all
@@ -44,6 +44,7 @@
         "garnet/packages/prod/log_listener",
         "garnet/packages/prod/logger",
         "garnet/packages/prod/magma",
+        "garnet/packages/prod/magma-img-rgx-mtk",
         "garnet/packages/prod/magma-vsl-gc",
         "garnet/packages/prod/magma_tools",
         "garnet/packages/prod/mdns",
diff --git a/garnet/packages/prod/magma-img-rgx-mtk b/garnet/packages/prod/magma-img-rgx-mtk
new file mode 100644
index 0000000..ecdc572
--- /dev/null
+++ b/garnet/packages/prod/magma-img-rgx-mtk
@@ -0,0 +1,6 @@
+{
+    "packages": [
+        "//garnet/lib/magma/gnbuild/magma-img-rgx:magma-img-rgx-mtk"
+    ]
+}
+
diff --git a/zircon/system/dev/board/mt8167s_ref/mt8167-gpu.cpp b/zircon/system/dev/board/mt8167s_ref/mt8167-gpu.cpp
index 2d5ba91..d5e15d6 100644
--- a/zircon/system/dev/board/mt8167s_ref/mt8167-gpu.cpp
+++ b/zircon/system/dev/board/mt8167s_ref/mt8167-gpu.cpp
@@ -55,7 +55,7 @@
             .clk = kClkMfgMm,
         }};
     pbus_dev_t gpu_dev = {};
-    gpu_dev.name = "gpio";
+    gpu_dev.name = "mt8167s_gpu";
     gpu_dev.vid = PDEV_VID_MEDIATEK;
     gpu_dev.did = PDEV_DID_MEDIATEK_GPU;
     gpu_dev.mmio_list = gpu_mmios;
@@ -67,7 +67,7 @@
 
     zx_status_t status = pbus_.DeviceAdd(&gpu_dev);
     if (status != ZX_OK) {
-        zxlogf(ERROR, "%s: ProtocolDeviceAdd failed %d\n", __FUNCTION__, status);
+        zxlogf(ERROR, "%s: DeviceAdd failed %d\n", __FUNCTION__, status);
         return status;
     }