[display][loopback] DO NOT SUMBIT

Change-Id: Iaec440485c8df4006bad1d6a32f583a76c663350
diff --git a/zircon/system/banjo/ddk.protocol.display.controller/display-controller.banjo b/zircon/system/banjo/ddk.protocol.display.controller/display-controller.banjo
index 77f1872..24aab00 100644
--- a/zircon/system/banjo/ddk.protocol.display.controller/display-controller.banjo
+++ b/zircon/system/banjo/ddk.protocol.display.controller/display-controller.banjo
@@ -412,4 +412,7 @@
     // If the system only supports single-buffered mode with a single framebuffer, then this returns
     // that buffer and its stride. Otherwise it can return ZX_ERR_NOT_SUPPORTED.
     GetSingleBufferFramebuffer() -> (zx.status res, handle<vmo>? vmo, uint32 stride);
+
+    //
+    CaptureDisplayOutput() -> (zx.status s, handle<vmo> vmo);
 };
diff --git a/zircon/system/dev/display/astro-display/astro-display.cpp b/zircon/system/dev/display/astro-display/astro-display.cpp
index 8cf9db5..49f2292 100644
--- a/zircon/system/dev/display/astro-display/astro-display.cpp
+++ b/zircon/system/dev/display/astro-display/astro-display.cpp
@@ -14,7 +14,6 @@
 namespace astro_display {
 
 namespace {
-
 // List of supported pixel formats
 zx_pixel_format_t kSupportedPixelFormats[] = { ZX_PIXEL_FORMAT_RGB_x888 };
 
@@ -291,6 +290,10 @@
     }
 
     import_info->canvas_idx = local_canvas_idx;
+    import_info->image_height = image->height;
+    import_info->image_width = image->width;
+    import_info->image_stride = stride * ZX_PIXEL_FORMAT_BYTES(image->pixel_format);
+
     image->handle = reinterpret_cast<uint64_t>(import_info.get());
     imported_images_.push_back(std::move(import_info));
     return status;
@@ -362,6 +365,8 @@
     }
     fbl::AutoLock lock(&image_lock_);
     import_info->canvas_idx = local_canvas_idx;
+    import_info->image_height = image->height;
+    import_info->image_stride = minimum_row_bytes;
     image->handle = reinterpret_cast<uint64_t>(import_info.get());
     imported_images_.push_back(std::move(import_info));
     return status;
@@ -381,7 +386,6 @@
 uint32_t AstroDisplay::DisplayControllerImplCheckConfiguration(
     const display_config_t** display_configs, size_t display_count, uint32_t** layer_cfg_results,
     size_t* layer_cfg_result_count) {
-
     if (display_count != 1) {
         ZX_DEBUG_ASSERT(display_count == 0);
         return CONFIG_DISPLAY_OK;
@@ -440,7 +444,6 @@
 void AstroDisplay::DisplayControllerImplApplyConfiguration(const display_config_t** display_configs,
                                                         size_t display_count) {
     ZX_DEBUG_ASSERT(display_configs);
-
     fbl::AutoLock lock(&display_lock_);
 
     if (display_count == 1 && display_configs[0]->layer_count) {
@@ -608,6 +611,66 @@
     return ZX_OK;
 }
 
+zx_status_t AstroDisplay::DisplayControllerImplCaptureDisplayOutput(zx::vmo* out_vmo) {
+    // Make sure this lock is enough to ensure images are not discarded during capture
+    fbl::AutoLock lock(&display_lock_);
+
+    // First obtain the information regarding the current image being display
+    if (!current_image_valid_) {
+        DISP_ERROR("Not valid image being displayed\n");
+        return ZX_ERR_UNAVAILABLE;
+    }
+
+    auto image_info = reinterpret_cast<ImageInfo*>(current_image_);
+
+    //FIXME: make sure it's valid
+
+    //Create VMO
+    uint64_t size = image_info->image_height * image_info->image_stride;
+    zx_status_t status = zx::vmo::create_contiguous(bti_, size, 0, out_vmo);
+    if (status != ZX_OK) {
+        DISP_ERROR("Could not allocate vmo %d\n", status);
+        return status;
+    }
+
+    // duplicate the vmo. One for the canvas driver. the other goes back to client
+    zx_handle_t vmo_dup;
+    if ((status = zx_handle_duplicate(out_vmo->get(), ZX_RIGHT_SAME_RIGHTS, &vmo_dup)) != ZX_OK) {
+        DISP_ERROR("Couldn't duplicate vmo %d\n", status);
+        return status;
+    }
+
+    canvas_info_t canvas_info;
+    canvas_info.height = image_info->image_height;
+    // canvas_info.stride_bytes = image_info->image_stride;
+    canvas_info.stride_bytes = (ALIGN(600 * 3, 64));  //image_info->image_stride;
+    canvas_info.wrap = 0;
+    canvas_info.blkmode = 0;
+    canvas_info.endianness = 7; // little endian 64bit
+    canvas_info.flags = CANVAS_FLAGS_READ | CANVAS_FLAGS_WRITE;
+
+    uint8_t canvas_idx;
+    status = amlogic_canvas_config(&canvas_, vmo_dup, 0, &canvas_info, &canvas_idx);
+    if (status != ZX_OK) {
+        DISP_ERROR("Could not configure canvas %d\n", status);
+        status = ZX_ERR_NO_RESOURCES;
+        return status;
+    }
+
+    // At this point, we have a canvas configured with the same settings as the canvas used by
+    // the currently displayed image. Now, we can start to capture a frame of image
+    DISP_INFO("canvas: index = %d, height = %d, stride = %d, width = %d\n",
+              canvas_idx,
+              image_info->image_height,
+              image_info->image_stride,
+              image_info->image_width);
+
+
+    // Start the capture
+    return vpu_->Capture(canvas_idx, image_info->image_height, 600);
+}
+
+
 int AstroDisplay::VSyncThread() {
     zx_status_t status;
     while (1) {
diff --git a/zircon/system/dev/display/astro-display/astro-display.h b/zircon/system/dev/display/astro-display/astro-display.h
index 87ef305..920e4d6 100644
--- a/zircon/system/dev/display/astro-display/astro-display.h
+++ b/zircon/system/dev/display/astro-display/astro-display.h
@@ -39,6 +39,9 @@
 
 struct ImageInfo : public fbl::DoublyLinkedListable<std::unique_ptr<ImageInfo>> {
     uint8_t canvas_idx;
+    uint32_t image_height;
+    uint32_t image_width;
+    uint32_t image_stride;
 };
 
 class AstroDisplay;
@@ -77,6 +80,7 @@
                                                                 uint32_t* out_stride) {
         return ZX_ERR_NOT_SUPPORTED;
     }
+    zx_status_t DisplayControllerImplCaptureDisplayOutput(zx::vmo* out_vmo);
 
     // Required functions for DeviceType
     void DdkUnbind();
diff --git a/zircon/system/dev/display/astro-display/vpu-regs.h b/zircon/system/dev/display/astro-display/vpu-regs.h
index b5e652b..3a14a1a 100644
--- a/zircon/system/dev/display/astro-display/vpu-regs.h
+++ b/zircon/system/dev/display/astro-display/vpu-regs.h
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 #pragma once
+#include <hwreg/bitfields.h>
+#include <hwreg/mmio.h>
 
 #define VPU_VIU_OSD1_CTRL_STAT                              (0x1a10 << 2)
 #define VPU_VIU_OSD1_CTRL_STAT2                             (0x1a2d << 2)
@@ -69,3 +71,220 @@
 #define VPU_VPP_POST_MATRIX_PRE_OFFSET0_1                   (0x32bb << 2)
 #define VPU_VPP_POST_MATRIX_PRE_OFFSET2                     (0x32bc << 2)
 #define VPU_VPP_POST_MATRIX_EN_CTRL                         (0x32bd << 2)
+
+// Registers needed for Video Loopback mode
+#define VPU_VDIN1_COM_CTRL0                                 (0x1302 << 2)
+#define VPU_VDIN1_COM_STATUS0                               (0x1305 << 2)
+#define VPU_VDIN1_MATRIX_CTRL                               (0x1310 << 2)
+#define VPU_VDIN1_COEF00_01                                 (0x1311 << 2)
+#define VPU_VDIN1_COEF02_10                                 (0x1312 << 2)
+#define VPU_VDIN1_COEF11_12                                 (0x1313 << 2)
+#define VPU_VDIN1_COEF20_21                                 (0x1314 << 2)
+#define VPU_VDIN1_COEF22                                    (0x1315 << 2)
+#define VPU_VDIN1_OFFSET0_1                                 (0x1316 << 2)
+#define VPU_VDIN1_OFFSET2                                   (0x1317 << 2)
+#define VPU_VDIN1_PRE_OFFSET0_1                             (0x1318 << 2)
+#define VPU_VDIN1_PRE_OFFSET2                               (0x1319 << 2)
+#define VPU_VDIN1_LFIFO_CTRL                                (0x131a << 2)
+#define VPU_VDIN1_INTF_WIDTHM1                              (0x131c << 2)
+#define VPU_VDIN1_WR_CTRL2                                  (0x131f << 2)
+#define VPU_VDIN1_WR_CTRL                                   (0x1320 << 2)
+#define VPU_VDIN1_WR_H_START_END                            (0x1321 << 2)
+#define VPU_VDIN1_WR_V_START_END                            (0x1322 << 2)
+#define VPU_VDIN1_ASFIFO_CTRL3                              (0x136f << 2)
+#define VPU_VDIN1_MISC_CTRL                                 (0x2782 << 2)
+#define VPU_VIU_VDIN_IF_MUX_CTRL                            (0x2783 << 2) // undocumented
+
+namespace astro_display {
+
+class VdInComCtrl0Reg : public hwreg::RegisterBase<VdInComCtrl0Reg, uint32_t> {
+public:
+    DEF_FIELD(26, 20, hold_lines);
+    DEF_BIT(4, enable_vdin);
+    DEF_FIELD(3, 0, vdin_selection);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdInComCtrl0Reg>(VPU_VDIN1_COM_CTRL0);
+    }
+};
+
+class VdInComStatus0Reg : public hwreg::RegisterBase<VdInComStatus0Reg, uint32_t> {
+public:
+    DEF_BIT(2, done);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdInComStatus0Reg>(VPU_VDIN1_COM_STATUS0);
+    }
+};
+
+class VdInMatrixCtrlReg : public hwreg::RegisterBase<VdInMatrixCtrlReg, uint32_t> {
+public:
+    DEF_FIELD(3, 2, select);
+    DEF_BIT(1, enable);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdInMatrixCtrlReg>(VPU_VDIN1_MATRIX_CTRL);
+    }
+};
+
+class VdinCoef00_01Reg : public hwreg::RegisterBase<VdinCoef00_01Reg, uint32_t> {
+public:
+    DEF_FIELD(28, 16, coef00);
+    DEF_FIELD(12, 0, coef01);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdinCoef00_01Reg>(VPU_VDIN1_COEF00_01);
+    }
+};
+
+class VdinCoef02_10Reg : public hwreg::RegisterBase<VdinCoef02_10Reg, uint32_t> {
+public:
+    DEF_FIELD(28, 16, coef02);
+    DEF_FIELD(12, 0, coef10);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdinCoef02_10Reg>(VPU_VDIN1_COEF02_10);
+    }
+};
+
+class VdinCoef11_12Reg : public hwreg::RegisterBase<VdinCoef11_12Reg, uint32_t> {
+public:
+    DEF_FIELD(28, 16, coef11);
+    DEF_FIELD(12, 0, coef12);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdinCoef11_12Reg>(VPU_VDIN1_COEF11_12);
+    }
+};
+
+class VdinCoef20_21Reg : public hwreg::RegisterBase<VdinCoef20_21Reg, uint32_t> {
+public:
+    DEF_FIELD(28, 16, coef20);
+    DEF_FIELD(12, 0, coef21);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdinCoef20_21Reg>(VPU_VDIN1_COEF20_21);
+    }
+};
+
+class VdinCoef22Reg : public hwreg::RegisterBase<VdinCoef22Reg, uint32_t> {
+public:
+    DEF_FIELD(12, 0, coef22);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdinCoef22Reg>(VPU_VDIN1_COEF22);
+    }
+};
+
+class VdinOffset0_1Reg : public hwreg::RegisterBase<VdinOffset0_1Reg, uint32_t> {
+public:
+    DEF_FIELD(28, 16, offset0);
+    DEF_FIELD(12, 0, offset1);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdinOffset0_1Reg>(VPU_VDIN1_OFFSET0_1);
+    }
+};
+
+class VdinOffset2Reg : public hwreg::RegisterBase<VdinOffset2Reg, uint32_t> {
+public:
+    DEF_FIELD(12, 0, offset2);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdinOffset2Reg>(VPU_VDIN1_OFFSET2);
+    }
+};
+
+class VdinPreOffset0_1Reg : public hwreg::RegisterBase<VdinPreOffset0_1Reg, uint32_t> {
+public:
+    DEF_FIELD(28, 16, preoffset0);
+    DEF_FIELD(12, 0, preoffset1);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdinPreOffset0_1Reg>(VPU_VDIN1_PRE_OFFSET0_1);
+    }
+};
+
+class VdinPreOffset2Reg : public hwreg::RegisterBase<VdinPreOffset2Reg, uint32_t> {
+public:
+    DEF_FIELD(12, 0, preoffset2);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdinPreOffset2Reg>(VPU_VDIN1_PRE_OFFSET2);
+    }
+};
+
+class VdinLFifoCtrlReg : public hwreg::RegisterBase<VdinLFifoCtrlReg, uint32_t> {
+public:
+    DEF_FIELD(11, 0, fifo_buf_size);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdinLFifoCtrlReg>(VPU_VDIN1_LFIFO_CTRL);
+    }
+};
+
+class VdinIntfWidthM1Reg : public hwreg::RegisterBase<VdinIntfWidthM1Reg, uint32_t> {
+public:
+    static auto Get() {
+        return hwreg::RegisterAddr<VdinIntfWidthM1Reg>(VPU_VDIN1_INTF_WIDTHM1);
+    }
+};
+
+class VdInWrCtrlReg : public hwreg::RegisterBase<VdInWrCtrlReg, uint32_t> {
+public:
+    DEF_BIT(27, eol_sel);
+    DEF_BIT(21, done_status_clear_bit);
+    DEF_BIT(19, word_swap);
+    DEF_FIELD(13, 12, memory_format);
+    DEF_BIT(10, write_ctrl);
+    DEF_BIT(9, write_req_urgent);
+    DEF_BIT(8, write_mem_enable);
+    DEF_FIELD(7, 0, canvas_idx);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdInWrCtrlReg>(VPU_VDIN1_WR_CTRL);
+    }
+};
+
+class VdInWrHStartEndReg : public hwreg::RegisterBase<VdInWrHStartEndReg, uint32_t> {
+public:
+    DEF_BIT(29, reverse_enable);
+    DEF_FIELD(28, 16, start);
+    DEF_FIELD(12, 0, end);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdInWrHStartEndReg>(VPU_VDIN1_WR_H_START_END);
+    }
+};
+
+class VdInWrVStartEndReg : public hwreg::RegisterBase<VdInWrVStartEndReg, uint32_t> {
+public:
+    DEF_BIT(29, reverse_enable);
+    DEF_FIELD(28, 16, start);
+    DEF_FIELD(12, 0, end);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdInWrVStartEndReg>(VPU_VDIN1_WR_V_START_END);
+    }
+};
+
+
+class VdInAFifoCtrl3Reg : public hwreg::RegisterBase<VdInAFifoCtrl3Reg, uint32_t> {
+public:
+    DEF_BIT(7, data_valid_en);
+    DEF_BIT(6, go_field_en);
+    DEF_BIT(5, go_line_en);
+    DEF_BIT(4, vsync_pol_set);
+    DEF_BIT(3, hsync_pol_set);
+    DEF_BIT(2, vsync_sync_reset_en);
+    DEF_BIT(1, fifo_overflow_clr);
+    DEF_BIT(0, soft_reset_en);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdInAFifoCtrl3Reg>(VPU_VDIN1_ASFIFO_CTRL3);
+    }
+};
+
+class VdInMiscCtrlReg : public hwreg::RegisterBase<VdInMiscCtrlReg, uint32_t> {
+public:
+    DEF_BIT(4, mif_reset);
+    static auto Get() {
+        return hwreg::RegisterAddr<VdInMiscCtrlReg>(VPU_VDIN1_MISC_CTRL);
+    }
+};
+
+class VdInIfMuxCtrlReg : public hwreg::RegisterBase<VdInIfMuxCtrlReg, uint32_t> {
+public:
+    DEF_FIELD(12, 8, vpu_path_1); // bit defs are not documented.
+    DEF_FIELD(4, 0, vpu_path_0); // bit defs are not documented.
+    static auto Get() {
+        return hwreg::RegisterAddr<VdInIfMuxCtrlReg>(VPU_VIU_VDIN_IF_MUX_CTRL);
+    }
+};
+
+} // namespace astro_display
+
+
diff --git a/zircon/system/dev/display/astro-display/vpu.cpp b/zircon/system/dev/display/astro-display/vpu.cpp
index cb9fc59..891cae2 100644
--- a/zircon/system/dev/display/astro-display/vpu.cpp
+++ b/zircon/system/dev/display/astro-display/vpu.cpp
@@ -348,4 +348,180 @@
     SET_BIT32(HHI, HHI_VAPBCLK_CNTL, 0, 8, 1);
     SET_BIT32(HHI, HHI_VPU_CLK_CNTL, 0, 8, 1);
 }
+
+void Vpu::PrintCaptureRegisters() {
+    DISP_INFO("** Display Loopback Register Dump **\n\n");
+    DISP_INFO("VdInIfMuxCtrlReg = 0x%x\n",
+              VdInIfMuxCtrlReg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdInComCtrl0Reg = 0x%x\n",
+              VdInComCtrl0Reg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdInComStatus0Reg = 0x%x\n",
+              VdInComStatus0Reg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdInAFifoCtrl3Reg = 0x%x\n",
+              VdInAFifoCtrl3Reg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdInMatrixCtrlReg = 0x%x\n",
+              VdInMatrixCtrlReg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdInWrCtrlReg = 0x%x\n",
+              VdInWrCtrlReg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdInWrHStartEndReg = 0x%x\n",
+              VdInWrHStartEndReg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdInWrVStartEndReg = 0x%x\n",
+              VdInWrVStartEndReg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdinCoef00_01Reg = 0x%x\n",
+              VdinCoef00_01Reg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdinCoef02_10Reg = 0x%x\n",
+              VdinCoef02_10Reg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdinCoef11_12Reg = 0x%x\n",
+              VdinCoef11_12Reg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdinCoef20_21Reg = 0x%x\n",
+              VdinCoef20_21Reg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdinCoef22Reg = 0x%x\n",
+              VdinCoef22Reg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdinOffset0_1Reg = 0x%x\n",
+              VdinOffset0_1Reg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdinOffset2Reg = 0x%x\n",
+              VdinOffset2Reg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdinPreOffset0_1Reg = 0x%x\n",
+              VdinPreOffset0_1Reg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+    DISP_INFO("VdinPreOffset2Reg = 0x%x\n",
+              VdinPreOffset2Reg::Get().ReadFrom(&(*vpu_mmio_)).reg_value());
+}
+
+zx_status_t Vpu::Capture(uint8_t canvas_idx, uint32_t height, uint32_t stride) {
+    ZX_DEBUG_ASSERT(initialized_);
+
+    // setup VPU path
+    VdInIfMuxCtrlReg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_vpu_path_1(4)
+        .set_vpu_path_0(4)
+        .WriteTo(&(*vpu_mmio_));
+
+    // setup hold lines and vdin selection to internal loopback
+    VdInComCtrl0Reg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_hold_lines(0)
+        .set_vdin_selection(7)
+        .WriteTo(&(*vpu_mmio_));
+
+    VdinLFifoCtrlReg::Get()
+        .FromValue(0)
+        .set_fifo_buf_size(0xf00)
+        .WriteTo(&(*vpu_mmio_));
+
+    // Setup Async Fifo
+    VdInAFifoCtrl3Reg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_data_valid_en(1)
+        .set_go_field_en(1)
+        .set_go_line_en(1)
+        .set_vsync_pol_set(1)
+        .set_hsync_pol_set(0)
+        .set_vsync_sync_reset_en(1)
+        .set_fifo_overflow_clr(0)
+        .set_soft_reset_en(0)
+        .WriteTo(&(*vpu_mmio_));
+
+    // setup vdin input dimensions
+    VdinIntfWidthM1Reg::Get()
+        .FromValue(stride - 1)
+        .WriteTo(&(*vpu_mmio_));
+
+    // Configure memory size
+    VdInWrHStartEndReg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_start(0)
+        .set_end(stride - 1)
+        .WriteTo(&(*vpu_mmio_));
+    VdInWrVStartEndReg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_start(0)
+        .set_end(height - 1)
+        .WriteTo(&(*vpu_mmio_));
+
+    // Write output canvas index, 128 bit endian, eol with width, enable 4:4:4 RGB888 mode
+    VdInWrCtrlReg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_eol_sel(0)
+        .set_word_swap(1)
+        .set_memory_format(1)
+        .set_canvas_idx(canvas_idx)
+        .WriteTo(&(*vpu_mmio_));
+
+    // enable vdin memory power
+    SET_BIT32(HHI, HHI_VPU_MEM_PD_REG0, 0, 18, 2);
+
+    // Now that loopback mode is configured, start capture
+    // pause write output
+    VdInWrCtrlReg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_write_ctrl(0)
+        .WriteTo(&(*vpu_mmio_));
+
+    // disable vdin path
+    VdInComCtrl0Reg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_enable_vdin(0)
+        .WriteTo(&(*vpu_mmio_));
+
+    // reset mif
+    VdInMiscCtrlReg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_mif_reset(1)
+        .WriteTo(&(*vpu_mmio_));
+    zx_nanosleep(zx_deadline_after(ZX_USEC(1)));
+    VdInMiscCtrlReg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_mif_reset(0)
+        .WriteTo(&(*vpu_mmio_));
+
+    // resume write output
+    VdInWrCtrlReg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_write_ctrl(1)
+        .WriteTo(&(*vpu_mmio_));
+
+    // wait until resets finishes
+    zx_nanosleep(zx_deadline_after(ZX_MSEC(20)));
+
+    // Clear status bit
+    VdInWrCtrlReg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_done_status_clear_bit(1)
+        .WriteTo(&(*vpu_mmio_));
+
+    // Set as urgent
+    VdInWrCtrlReg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_write_req_urgent(1)
+        .WriteTo(&(*vpu_mmio_));
+
+    // Enable loopback
+    VdInWrCtrlReg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_write_mem_enable(1)
+        .WriteTo(&(*vpu_mmio_));
+
+    // enable vdin path
+    VdInComCtrl0Reg::Get()
+        .ReadFrom(&(*vpu_mmio_))
+        .set_enable_vdin(1)
+        .WriteTo(&(*vpu_mmio_));
+
+    // Wait for done
+    int timeout = 1000;
+    while (VdInComStatus0Reg::Get().ReadFrom(&(*vpu_mmio_)).done() == 0 && timeout--) {
+        zx_nanosleep(zx_deadline_after(ZX_MSEC(8)));
+    }
+
+    if (timeout <= 0) {
+        DISP_ERROR("Time out! Loopback did not succeed\n");
+        PrintCaptureRegisters();
+        return ZX_ERR_TIMED_OUT;
+
+    }
+
+    return ZX_OK;
+}
+
 } // namespace astro_display
diff --git a/zircon/system/dev/display/astro-display/vpu.h b/zircon/system/dev/display/astro-display/vpu.h
index 90ad234..3afe771 100644
--- a/zircon/system/dev/display/astro-display/vpu.h
+++ b/zircon/system/dev/display/astro-display/vpu.h
@@ -29,6 +29,9 @@
     // This function sets up default video post processing unit. It contains undocumented
     // registers and/or initialization sequences
     void VppInit();
+
+    zx_status_t Capture(uint8_t canvas_idx, uint32_t height, uint32_t stride);
+    void PrintCaptureRegisters();
 private:
     // This function configures the VPU-related clocks. It contains undocumented registers
     // and/or clock initialization sequences
diff --git a/zircon/system/dev/display/display/client.cpp b/zircon/system/dev/display/display/client.cpp
index 0a1e7f3..bdc9c1f 100644
--- a/zircon/system/dev/display/display/client.cpp
+++ b/zircon/system/dev/display/display/client.cpp
@@ -61,6 +61,7 @@
         SELECT_TABLE_CASE(fuchsia_hardware_display_ControllerSetBufferCollectionConstraints);
         SELECT_TABLE_CASE(fuchsia_hardware_display_ControllerReleaseBufferCollection);
         SELECT_TABLE_CASE(fuchsia_hardware_display_ControllerGetSingleBufferFramebuffer);
+        SELECT_TABLE_CASE(fuchsia_hardware_display_ControllerCaptureDisplayOutput);
     }
     if (table != nullptr) {
         const char* err;
@@ -206,6 +207,13 @@
         HandleGetSingleBufferFramebuffer(r, &builder, &out_handle, &has_out_handle, &out_type);
         break;
     }
+    case fuchsia_hardware_display_ControllerCaptureDisplayOutputOrdinal: {
+        auto r = reinterpret_cast<
+            const fuchsia_hardware_display_ControllerCaptureDisplayOutputRequest*>(
+            msg.bytes().data());
+        HandleCaptureDisplayOutput(r, &builder, &out_handle, &has_out_handle, &out_type);
+        break;
+    }
     default:
         zxlogf(INFO, "Unknown ordinal %d\n", msg.ordinal());
     }
@@ -1154,6 +1162,20 @@
     resp->stride = stride;
 }
 
+void Client::HandleCaptureDisplayOutput(
+    const fuchsia_hardware_display_ControllerCaptureDisplayOutputRequest* req,
+    fidl::Builder* resp_builder, zx_handle_t* handle_out, bool* has_handle_out,
+    const fidl_type_t** resp_table) {
+    auto resp = resp_builder->New<fuchsia_hardware_display_ControllerCaptureDisplayOutputResponse>();
+    *resp_table = &fuchsia_hardware_display_ControllerCaptureDisplayOutputResponseTable;
+
+    zx::vmo vmo;
+    resp->res = controller_->dc()->CaptureDisplayOutput(&vmo);
+    *has_handle_out = resp->res == ZX_OK;
+    *handle_out = vmo.release();
+    resp->vmo = *has_handle_out ? FIDL_HANDLE_PRESENT : FIDL_HANDLE_ABSENT;
+}
+
 bool Client::CheckConfig(fidl::Builder* resp_builder) {
     const display_config_t* configs[configs_.size()];
     layer_t* layers[layers_.size()];
diff --git a/zircon/system/dev/display/display/client.h b/zircon/system/dev/display/display/client.h
index ffcd548..5496d63 100644
--- a/zircon/system/dev/display/display/client.h
+++ b/zircon/system/dev/display/display/client.h
@@ -221,6 +221,11 @@
         const fuchsia_hardware_display_ControllerReleaseBufferCollectionRequest* req,
         fidl::Builder* resp_builder, const fidl_type_t** resp_table);
 
+    void HandleCaptureDisplayOutput(
+    const fuchsia_hardware_display_ControllerCaptureDisplayOutputRequest* req,
+    fidl::Builder* resp_builder, zx_handle_t* handle_out, bool* has_handle_out,
+    const fidl_type_t** resp_table);
+
     // Cleans up layer state associated with an image. If image == nullptr, then
     // cleans up all image state. Return true if a current layer was modified.
     bool CleanUpImage(Image* image);
diff --git a/zircon/system/dev/display/dummy/dummy-display.h b/zircon/system/dev/display/dummy/dummy-display.h
index 614f528..a01b2a0 100644
--- a/zircon/system/dev/display/dummy/dummy-display.h
+++ b/zircon/system/dev/display/dummy/dummy-display.h
@@ -60,6 +60,9 @@
                                                                 uint32_t* out_stride) {
         return ZX_ERR_NOT_SUPPORTED;
     }
+    zx_status_t DisplayControllerImplCaptureDisplayOutput(zx::vmo* out_vmo) {
+        return ZX_ERR_NOT_SUPPORTED;
+    }
 
     // Required functions for DeviceType
     void DdkUnbind();
diff --git a/zircon/system/dev/display/hikey-display/ddk-interface.h b/zircon/system/dev/display/hikey-display/ddk-interface.h
index bbd7975..a481ee0 100644
--- a/zircon/system/dev/display/hikey-display/ddk-interface.h
+++ b/zircon/system/dev/display/hikey-display/ddk-interface.h
@@ -53,6 +53,9 @@
                                                                 uint32_t* out_stride) {
         return ZX_ERR_NOT_SUPPORTED;
     }
+    zx_status_t DisplayControllerImplCaptureDisplayOutput(zx::vmo* out_vmo) {
+        return ZX_ERR_NOT_SUPPORTED;
+    }
 
     void DdkUnbind();
     void DdkRelease();
diff --git a/zircon/system/dev/display/intel-i915/intel-i915.h b/zircon/system/dev/display/intel-i915/intel-i915.h
index 4cac4e6..341e170 100644
--- a/zircon/system/dev/display/intel-i915/intel-i915.h
+++ b/zircon/system/dev/display/intel-i915/intel-i915.h
@@ -101,6 +101,9 @@
                                                                 uint32_t* out_stride) {
         return ZX_ERR_NOT_SUPPORTED;
     }
+    zx_status_t DisplayControllerImplCaptureDisplayOutput(zx::vmo* out_vmo) {
+        return ZX_ERR_NOT_SUPPORTED;
+    }
 
     // gpu core ops
     zx_status_t ReadPciConfig16(uint16_t addr, uint16_t* value_out);
diff --git a/zircon/system/dev/display/intel-i915/macros.h b/zircon/system/dev/display/intel-i915/macros.h
index d171342..a4a812f 100644
--- a/zircon/system/dev/display/intel-i915/macros.h
+++ b/zircon/system/dev/display/intel-i915/macros.h
@@ -18,7 +18,7 @@
 #define WAIT_ON_MS(COND, N) WAIT_ON(COND, N, M)
 
 #define LOG_ERROR(fmt, ...) zxlogf(ERROR, "i915: " fmt, ##__VA_ARGS__)
-#define LOG_WARN(fmt, ...) zxlogf(WARN, "i915: " fmt, ##__VA_ARGS__)
+#define LOG_WARN(fmt, ...) zxlogf(INFO, "i915: " fmt, ##__VA_ARGS__)
 #define LOG_INFO(fmt, ...) zxlogf(INFO, "i915: " fmt, ##__VA_ARGS__)
-#define LOG_TRACE(fmt, ...) zxlogf(TRACE, "i915: " fmt, ##__VA_ARGS__)
-#define LOG_SPEW(fmt, ...) zxlogf(SPEW, "i915: " fmt, ##__VA_ARGS__)
+#define LOG_TRACE(fmt, ...) zxlogf(INFO, "i915: " fmt, ##__VA_ARGS__)
+#define LOG_SPEW(fmt, ...) zxlogf(INFO, "i915: " fmt, ##__VA_ARGS__)
diff --git a/zircon/system/dev/display/mt8167s-display/mt8167s-display.h b/zircon/system/dev/display/mt8167s-display/mt8167s-display.h
index 8960ddf..da8171d 100644
--- a/zircon/system/dev/display/mt8167s-display/mt8167s-display.h
+++ b/zircon/system/dev/display/mt8167s-display/mt8167s-display.h
@@ -84,6 +84,10 @@
                                                                 uint32_t* out_stride) {
         return ZX_ERR_NOT_SUPPORTED;
     }
+    zx_status_t DisplayControllerImplCaptureDisplayOutput(zx::vmo* out_vmo) {
+        return ZX_ERR_NOT_SUPPORTED;
+    }
+
     int VSyncThread();
 
     // Required functions for DeviceType
diff --git a/zircon/system/dev/display/simple/simple-display.h b/zircon/system/dev/display/simple/simple-display.h
index ff30f08..795049e 100644
--- a/zircon/system/dev/display/simple/simple-display.h
+++ b/zircon/system/dev/display/simple/simple-display.h
@@ -56,6 +56,10 @@
     zx_status_t DisplayControllerImplGetSingleBufferFramebuffer(zx::vmo* out_vmo,
                                                                 uint32_t* out_stride);
 
+    zx_status_t DisplayControllerImplCaptureDisplayOutput(zx::vmo* out_vmo) {
+        return ZX_ERR_NOT_SUPPORTED;
+    }
+
 private:
     ddk::MmioBuffer framebuffer_mmio_;
     zx_koid_t framebuffer_koid_;
diff --git a/zircon/system/dev/display/vim-display/vim-display.cpp b/zircon/system/dev/display/vim-display/vim-display.cpp
index 96f45dd..5e596d5 100644
--- a/zircon/system/dev/display/vim-display/vim-display.cpp
+++ b/zircon/system/dev/display/vim-display/vim-display.cpp
@@ -549,6 +549,10 @@
     return ZX_ERR_NOT_SUPPORTED;
 }
 
+static zx_status_t capture_display_output(void* ctx, zx_handle_t* out_vmo) {
+    return ZX_ERR_NOT_SUPPORTED;
+}
+
 static display_controller_impl_protocol_ops_t display_controller_ops = {
     .set_display_controller_interface = vim_set_display_controller_interface,
     .import_vmo_image = vim_import_vmo_image,
@@ -561,6 +565,7 @@
     .get_sysmem_connection = get_sysmem_connection,
     .set_buffer_collection_constraints = set_buffer_collection_constraints,
     .get_single_buffer_framebuffer = get_single_buffer_framebuffer,
+    .capture_display_output = capture_display_output
 };
 
 static uint32_t get_bus_count(void* ctx) {
diff --git a/zircon/system/fidl/fuchsia-hardware-display/display-controller.fidl b/zircon/system/fidl/fuchsia-hardware-display/display-controller.fidl
index 003ed33..7fb8603 100644
--- a/zircon/system/fidl/fuchsia-hardware-display/display-controller.fidl
+++ b/zircon/system/fidl/fuchsia-hardware-display/display-controller.fidl
@@ -464,4 +464,6 @@
     // If the system only supports single-buffered mode with a single framebuffer, then this returns
     // that buffer and its stride. Otherwise it can return ZX_ERR_NOT_SUPPORTED.
     GetSingleBufferFramebuffer() -> (zx.status res, handle<vmo>? vmo, uint32 stride);
+
+    CaptureDisplayOutput() -> (zx.status res, handle<vmo>? vmo);
 };
diff --git a/zircon/system/uapp/display-test/main.cpp b/zircon/system/uapp/display-test/main.cpp
index 79bc0c6..74f1815 100644
--- a/zircon/system/uapp/display-test/main.cpp
+++ b/zircon/system/uapp/display-test/main.cpp
@@ -21,6 +21,8 @@
 #include <lib/fidl/cpp/string_view.h>
 #include <lib/fidl/cpp/vector_view.h>
 #include <lib/fzl/fdio.h>
+#include <lib/zx/vmar.h>
+#include <lib/zx/vmo.h>
 
 #include <zircon/pixelformat.h>
 #include <zircon/status.h>
@@ -310,6 +312,61 @@
     return ZX_OK;
 }
 
+static bool capture_display() {
+    zx_status_t status;
+    zx::vmo local_vmo;
+    fuchsia_hardware_display_ControllerCaptureDisplayOutputRequest msg;
+    msg.hdr.ordinal = fuchsia_hardware_display_ControllerCaptureDisplayOutputOrdinal;
+
+    fuchsia_hardware_display_ControllerCaptureDisplayOutputResponse rsp;
+    zx_channel_call_args_t calls_args = {};
+    calls_args.wr_bytes = &msg;
+    calls_args.rd_bytes = &rsp;
+    calls_args.rd_handles = local_vmo.reset_and_get_address();
+    calls_args.wr_num_bytes = sizeof(msg);
+    calls_args.rd_num_bytes = sizeof(rsp);
+    calls_args.rd_num_handles = 1;
+    uint32_t actual_bytes, actual_handles;
+    if ((status = zx_channel_call(dc_handle, 0, ZX_TIME_INFINITE, &calls_args,
+                                  &actual_bytes, &actual_handles)) != ZX_OK) {
+        printf("%s: channel call failed %d\n", __FUNCTION__, status);
+        return false;
+    }
+
+    if (rsp.res != ZX_OK) {
+        printf("%s: capture failed %d\n",__FUNCTION__, rsp.res);
+        return false;
+    }
+
+    size_t size = 0;
+    status = local_vmo.get_size(&size);
+    printf("local_vmo size = %zu\n", size);
+
+    // let's print it
+    uintptr_t addr;
+    if (zx::vmar::root_self()->map(0, local_vmo, 0,
+                                   size,
+                                   ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, &addr) != ZX_OK) {
+        printf("Could not map vmar\n");
+        return false;
+    }
+
+    uint8_t* ptr = reinterpret_cast<uint8_t*>(addr);
+    zx_cache_flush(ptr, size, ZX_CACHE_FLUSH_INVALIDATE);
+
+    // printf("dumping\n");
+    // for (size_t i = 0; i < 1024; i++) {
+    //     if (i % 64 == 0) printf("\n");
+    //     printf("<0x%x> ", ptr[i]);
+    // }
+    // printf("\n\n");
+
+    FILE* file = fopen("payam", "a");
+    fwrite()
+
+
+    return true;
+}
 int main(int argc, const char* argv[]) {
     printf("Running display test\n");
 
@@ -317,6 +374,7 @@
     fbl::Vector<fbl::Vector<uint64_t>> display_layers;
     fbl::Vector<fbl::unique_ptr<VirtualLayer>> layers;
     int32_t num_frames = 120; // default to 120 frames
+    bool capture = false;
     enum Platform {
         SIMPLE,
         INTEL,
@@ -389,6 +447,10 @@
             platform = SIMPLE;
             argv += 1;
             argc -= 1;
+        } else if (strcmp(argv[0], "--capture") == 0) {
+            capture = true;
+            argv += 1;
+            argc -= 1;
         } else {
             printf("Unrecognized argument \"%s\"\n", argv[0]);
             return -1;
@@ -573,6 +635,14 @@
         while ((status = wait_for_vsync(layers)) == ZX_ERR_NEXT) {
             // wait again
         }
+
+        if (capture) {
+            if (!capture_display()) {
+                break;
+            } else {
+                break;
+            }
+        }
         ZX_ASSERT(status == ZX_OK);
     }
 
diff --git a/zircon/system/uapp/display-test/virtual-layer.cpp b/zircon/system/uapp/display-test/virtual-layer.cpp
index 305f9e4..351be45 100644
--- a/zircon/system/uapp/display-test/virtual-layer.cpp
+++ b/zircon/system/uapp/display-test/virtual-layer.cpp
@@ -107,7 +107,7 @@
         return false;
     }
     uint32_t fg_color = get_fg_color();
-    uint32_t bg_color = alpha_enable_ ? 0x3fffffff : 0xffffffff;
+    uint32_t bg_color = alpha_enable_ ? 0x3f00ff00 : 0xff00ff00;
 
     images_[0] = Image::Create(
         dc_handle, image_width_, image_height_, image_format_, fg_color, bg_color,