[intel-i915] Add support for rotation

This is a revert of the revert 455cc09565a4f9018cd9921e9184d857bc5b7133
with an additional fix in DdkSuspend to fix crashing during mexec.

Test: display-test, boot with zedboot image including change
Change-Id: I55dcbd21496155357c30d5ac6a83215c642f8d9a
diff --git a/system/dev/display/intel-i915/display-device.cpp b/system/dev/display/intel-i915/display-device.cpp
index 2161727..639b211 100644
--- a/system/dev/display/intel-i915/display-device.cpp
+++ b/system/dev/display/intel-i915/display-device.cpp
@@ -10,6 +10,7 @@
 #include "registers.h"
 #include "registers-dpll.h"
 #include "registers-transcoder.h"
+#include "tiling.h"
 
 namespace i915 {
 
@@ -136,10 +137,38 @@
             plane_surface.WriteTo(controller_->mmio_space());
             continue;
         }
+        image_t* image = &primary->image;
+
+        const fbl::unique_ptr<GttRegion>& region = controller_->GetGttRegion(image->handle);
+        region->SetRotation(primary->transform_mode, *image);
+
+        uint32_t plane_width;
+        uint32_t plane_height;
+        uint32_t stride;
+        uint32_t x_offset;
+        uint32_t y_offset;
+        if (primary->transform_mode == FRAME_TRANSFORM_IDENTITY
+                || primary->transform_mode == FRAME_TRANSFORM_ROT_180) {
+            plane_width = primary->src_frame.width;
+            plane_height = primary->src_frame.height;
+            stride = width_in_tiles(image->type, image->width, image->pixel_format);
+            x_offset = primary->src_frame.x_pos;
+            y_offset = primary->src_frame.y_pos;
+        } else {
+            uint32_t tile_height = height_in_tiles(image->type, image->height, image->pixel_format);
+            uint32_t tile_px_height = get_tile_px_height(image->type, image->pixel_format);
+            uint32_t total_height = tile_height * tile_px_height;
+
+            plane_width = primary->src_frame.height;
+            plane_height = primary->src_frame.width;
+            stride = tile_height;
+            x_offset = total_height - primary->src_frame.y_pos - primary->src_frame.height;
+            y_offset = primary->src_frame.x_pos;
+        }
 
         auto plane_size = pipe_regs.PlaneSurfaceSize(i).FromValue(0);
-        plane_size.set_width_minus_1(primary->dest_frame.width - 1);
-        plane_size.set_height_minus_1(primary->dest_frame.height - 1);
+        plane_size.set_width_minus_1(plane_width - 1);
+        plane_size.set_height_minus_1(plane_height - 1);
         plane_size.WriteTo(mmio_space());
 
         auto plane_pos = pipe_regs.PlanePosition(i).FromValue(0);
@@ -148,13 +177,12 @@
         plane_pos.WriteTo(mmio_space());
 
         auto plane_offset = pipe_regs.PlaneOffset(i).FromValue(0);
-        plane_offset.set_start_x(primary->src_frame.x_pos);
-        plane_offset.set_start_y(primary->src_frame.y_pos);
+        plane_offset.set_start_x(x_offset);
+        plane_offset.set_start_y(y_offset);
         plane_offset.WriteTo(mmio_space());
 
         auto stride_reg = pipe_regs.PlaneSurfaceStride(i).FromValue(0);
-        stride_reg.set_stride(primary->image.type,
-                              primary->image.width, primary->image.pixel_format);
+        stride_reg.set_stride(stride);
         stride_reg.WriteTo(controller_->mmio_space());
 
         auto plane_ctrl = pipe_regs.PlaneControl(i).ReadFrom(controller_->mmio_space());
@@ -170,10 +198,19 @@
             ZX_ASSERT(primary->image.type == IMAGE_TYPE_YF_TILED);
             plane_ctrl.set_tiled_surface(plane_ctrl.kTilingYF);
         }
+        if (primary->transform_mode == FRAME_TRANSFORM_IDENTITY) {
+            plane_ctrl.set_plane_rotation(plane_ctrl.kIdentity);
+        } else if (primary->transform_mode == FRAME_TRANSFORM_ROT_90) {
+            plane_ctrl.set_plane_rotation(plane_ctrl.k90deg);
+        } else if (primary->transform_mode == FRAME_TRANSFORM_ROT_180) {
+            plane_ctrl.set_plane_rotation(plane_ctrl.k180deg);
+        } else {
+            ZX_ASSERT(primary->transform_mode == FRAME_TRANSFORM_ROT_270);
+            plane_ctrl.set_plane_rotation(plane_ctrl.k270deg);
+        }
         plane_ctrl.WriteTo(controller_->mmio_space());
 
-        uint32_t base_address =
-                static_cast<uint32_t>(reinterpret_cast<uint64_t>(primary->image.handle));
+        uint32_t base_address = static_cast<uint32_t>(region->base());
 
         auto plane_surface = pipe_regs.PlaneSurface(i).ReadFrom(controller_->mmio_space());
         plane_surface.set_surface_base_addr(base_address >> plane_surface.kRShiftCount);
diff --git a/system/dev/display/intel-i915/gtt.cpp b/system/dev/display/intel-i915/gtt.cpp
index 4d6335c..5fcaad3 100644
--- a/system/dev/display/intel-i915/gtt.cpp
+++ b/system/dev/display/intel-i915/gtt.cpp
@@ -12,6 +12,7 @@
 #include "intel-i915.h"
 #include "gtt.h"
 #include "macros.h"
+#include "tiling.h"
 #include "registers.h"
 
 #define PAGE_PRESENT (1 << 0)
@@ -20,9 +21,10 @@
 
 constexpr size_t kEntriesPerPinTxn = PAGE_SIZE / sizeof(zx_paddr_t);
 
-inline uint64_t gen_pte_encode(uint64_t bus_addr, bool valid)
+inline uint64_t gen_pte_encode(uint64_t bus_addr)
 {
-    return bus_addr | (valid ? PAGE_PRESENT : 0);
+    // Make every page present so we don't have to deal with padding for framebuffers
+    return bus_addr | PAGE_PRESENT;
 }
 
 inline uint32_t get_pte_offset(uint32_t idx) {
@@ -85,7 +87,7 @@
     }
 
     // Populate the gtt with the scratch buffer.
-    uint64_t pte = gen_pte_encode(scratch_buffer_paddr_, false);
+    uint64_t pte = gen_pte_encode(scratch_buffer_paddr_);
     unsigned i;
     for (i = 0; i < gtt_size / sizeof(uint64_t); i++) {
         controller_->mmio_space()->Write<uint64_t>(get_pte_offset(i), pte);
@@ -97,8 +99,8 @@
 }
 
 zx_status_t Gtt::AllocRegion(uint32_t length, uint32_t align_pow2,
-                             uint32_t pte_padding, fbl::unique_ptr<GttRegion>* region_out) {
-    uint32_t region_length = ROUNDUP(length, PAGE_SIZE) + (pte_padding * PAGE_SIZE);
+                             fbl::unique_ptr<GttRegion>* region_out) {
+    uint32_t region_length = ROUNDUP(length, PAGE_SIZE);
     fbl::AllocChecker ac;
     auto r = fbl::make_unique_checked<GttRegion>(&ac, this);
     if (!ac.check()) {
@@ -107,26 +109,21 @@
     if (region_allocator_.GetRegion(region_length, align_pow2, r->region_) != ZX_OK) {
         return ZX_ERR_NO_RESOURCES;
     }
-    r->pte_padding_ = pte_padding;
     *region_out = fbl::move(r);
     return ZX_OK;
 }
 
-void Gtt::SetupForMexec(uintptr_t stolen_fb, uint32_t length, uint32_t pte_padding) {
+void Gtt::SetupForMexec(uintptr_t stolen_fb, uint32_t length) {
     // Just clobber everything to get the bootloader framebuffer to work.
     unsigned pte_idx = 0;
     for (unsigned i = 0; i < ROUNDUP(length, PAGE_SIZE) / PAGE_SIZE; i++, stolen_fb += PAGE_SIZE) {
-        uint64_t pte = gen_pte_encode(stolen_fb, true);
+        uint64_t pte = gen_pte_encode(stolen_fb);
         controller_->mmio_space()->Write<uint64_t>(get_pte_offset(pte_idx++), pte);
     }
-    uint64_t padding_pte = gen_pte_encode(scratch_buffer_paddr_, true);
-    for (unsigned i = 0; i < pte_padding; i++) {
-        controller_->mmio_space()->Write<uint64_t>(get_pte_offset(pte_idx++), padding_pte);
-    }
     controller_->mmio_space()->Read<uint32_t>(get_pte_offset(pte_idx - 1)); // Posting read
 }
 
-GttRegion::GttRegion(Gtt* gtt) : gtt_(gtt) {}
+GttRegion::GttRegion(Gtt* gtt) : gtt_(gtt), is_rotated_(false) {}
 
 GttRegion::~GttRegion() {
     ClearRegion(false);
@@ -134,7 +131,7 @@
 
 zx_status_t GttRegion::PopulateRegion(zx_handle_t vmo, uint64_t page_offset,
                                       uint64_t length, bool writable) {
-    if ((PAGE_SIZE * pte_padding_) + length > region_->size) {
+    if (length > region_->size) {
         return ZX_ERR_INVALID_ARGS;
     }
     if (mapped_end_ != 0) {
@@ -179,15 +176,11 @@
         for (unsigned i = 0; i < actual_entries; i++) {
             for (unsigned j = 0;
                     j < gtt_->min_contiguity_ / PAGE_SIZE && pte_idx < pte_idx_end; j++) {
-                uint64_t pte = gen_pte_encode(paddrs[i] + j * PAGE_SIZE, true);
+                uint64_t pte = gen_pte_encode(paddrs[i] + j * PAGE_SIZE);
                 gtt_->controller_->mmio_space()->Write<uint64_t>(get_pte_offset(pte_idx++), pte);
             }
         }
     }
-    uint64_t padding_pte = gen_pte_encode(gtt_->scratch_buffer_paddr_, true);
-    for (unsigned i = 0; i < pte_padding_; i++) {
-        gtt_->controller_->mmio_space()->Write<uint64_t>(get_pte_offset(pte_idx++), padding_pte);
-    }
 
     gtt_->controller_->mmio_space()->Read<uint32_t>(get_pte_offset(pte_idx - 1)); // Posting read
     return ZX_OK;
@@ -199,7 +192,7 @@
     }
 
     uint32_t pte_idx = static_cast<uint32_t>(region_->base / PAGE_SIZE);
-    uint64_t pte = gen_pte_encode(gtt_->scratch_buffer_paddr_, false);
+    uint64_t pte = gen_pte_encode(gtt_->scratch_buffer_paddr_);
     auto mmio_space = gtt_->controller_->mmio_space();
 
     for (unsigned i = 0; i < mapped_end_ / PAGE_SIZE; i++) {
@@ -223,4 +216,47 @@
     vmo_ = ZX_HANDLE_INVALID;
 }
 
+void GttRegion::SetRotation(uint32_t rotation, const image_t& image) {
+    bool rotated = (rotation == FRAME_TRANSFORM_ROT_90 || rotation == FRAME_TRANSFORM_ROT_270);
+    if (rotated == is_rotated_) {
+        return;
+    }
+    is_rotated_ = rotated;
+    // Displaying an image with 90/270 degree rotation requires rearranging the image's
+    // GTT mapping. Since permutations are composed of disjoint cycles and because we can
+    // calculate each page's location in the new mapping, we can remap the image by shifting
+    // the GTT entries around each cycle. We use one of the ignored bits in the global GTT
+    // PTEs to keep track of whether or not entries have been rotated.
+    constexpr uint32_t kRotatedFlag = (1 << 1);
+
+    uint64_t mask = is_rotated_ ? kRotatedFlag : 0;
+    uint32_t width = width_in_tiles(image.type, image.width, image.pixel_format);
+    uint32_t height = height_in_tiles(image.type, image.height, image.pixel_format);
+
+    auto mmio_space = gtt_->controller_->mmio_space();
+    uint32_t pte_offset = static_cast<uint32_t>(base() / PAGE_SIZE);
+    for (uint32_t i = 0; i < size() / PAGE_SIZE; i++) {
+        uint64_t entry = mmio_space->Read<uint64_t>(get_pte_offset(i + pte_offset));
+        uint32_t position = i;
+        // If the entry has already been cycled into the correct place, the
+        // loop check will immediately fail.
+        while ((entry & kRotatedFlag) != mask) {
+            if (mask) {
+                uint32_t x = position % width;
+                uint32_t y = position / width;
+                position = ((x + 1) * height) - y - 1;
+            } else {
+                uint32_t x = position % height;
+                uint32_t y = position / height;
+                position = ((height - x - 1) * width) + y;
+            }
+            uint32_t dest_offset = get_pte_offset(position + pte_offset);
+
+            uint64_t next_entry = mmio_space->Read<uint64_t>(dest_offset);
+            mmio_space->Write<uint64_t>(dest_offset, entry ^ kRotatedFlag);
+            entry = next_entry;
+        }
+    }
+}
+
 } // namespace i915
diff --git a/system/dev/display/intel-i915/gtt.h b/system/dev/display/intel-i915/gtt.h
index 8189a67..5c8b58d 100644
--- a/system/dev/display/intel-i915/gtt.h
+++ b/system/dev/display/intel-i915/gtt.h
@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include <ddk/protocol/display-controller.h>
 #include <fbl/unique_ptr.h>
 #include <fbl/vector.h>
 #include <hwreg/mmio.h>
@@ -21,6 +22,8 @@
     explicit GttRegion(Gtt* gtt);
     ~GttRegion();
 
+    void SetRotation(uint32_t rotation, const image_t& image);
+
     zx_status_t PopulateRegion(zx_handle_t vmo, uint64_t page_offset,
                                uint64_t length, bool writable = false);
     void ClearRegion(bool close_vmo);
@@ -33,12 +36,13 @@
 
     fbl::Vector<zx::pmt> pmts_;
     uint32_t mapped_end_ = 0;
-    uint32_t pte_padding_;
     // The region's current vmo. The region does not own the vmo handle; it
     // is up to the owner of the region to determine when the vmo should be
     // closed.
     zx_handle_t vmo_ = ZX_HANDLE_INVALID;
 
+    bool is_rotated_;
+
     friend class Gtt;
 };
 
@@ -48,9 +52,8 @@
     ~Gtt();
     zx_status_t Init(Controller* controller);
     zx_status_t AllocRegion(uint32_t length,
-                            uint32_t align_pow2, uint32_t pte_padding,
-                            fbl::unique_ptr<GttRegion>* region_out);
-    void SetupForMexec(uintptr_t stolen_fb, uint32_t length, uint32_t pte_padding);
+                            uint32_t align_pow2, fbl::unique_ptr<GttRegion>* region_out);
+    void SetupForMexec(uintptr_t stolen_fb, uint32_t length);
 
     uint64_t size() const { return gfx_mem_size_; }
 private:
diff --git a/system/dev/display/intel-i915/intel-i915.cpp b/system/dev/display/intel-i915/intel-i915.cpp
index a9b00e1..c677512 100644
--- a/system/dev/display/intel-i915/intel-i915.cpp
+++ b/system/dev/display/intel-i915/intel-i915.cpp
@@ -35,6 +35,7 @@
 #include "registers-pipe.h"
 #include "registers-transcoder.h"
 #include "registers.h"
+#include "tiling.h"
 
 #define INTEL_I915_BROADWELL_DID (0x1616)
 
@@ -639,14 +640,20 @@
         return ZX_ERR_NO_MEMORY;
     }
 
-    uint32_t length = image->height * ZX_PIXEL_FORMAT_BYTES(image->pixel_format) *
-            registers::PlaneSurfaceStride::compute_pixel_stride(image->type, image->width,
-                                                                image->pixel_format);
+    uint32_t length = width_in_tiles(image->type, image->width, image->pixel_format) *
+            height_in_tiles(image->type, image->height, image->pixel_format) *
+            get_tile_byte_size(image->type);
+
+    uint32_t align;
+    if (image->type == IMAGE_TYPE_SIMPLE) {
+        align = registers::PlaneSurface::kLinearAlignment;
+    } else if (image->type == IMAGE_TYPE_X_TILED) {
+        align = registers::PlaneSurface::kXTilingAlignment;
+    } else {
+        align = registers::PlaneSurface::kYTilingAlignment;
+    }
     fbl::unique_ptr<GttRegion> gtt_region;
-    zx_status_t status = gtt_.AllocRegion(length,
-                                          registers::PlaneSurface::kLinearAlignment,
-                                          registers::PlaneSurface::kTrailingPtePadding,
-                                          &gtt_region);
+    zx_status_t status = gtt_.AllocRegion(length, align, &gtt_region);
     if (status != ZX_OK) {
         return status;
     }
@@ -654,10 +661,7 @@
     // The vsync logic requires that images not have base == 0
     if (gtt_region->base() == 0) {
         fbl::unique_ptr<GttRegion> alt_gtt_region;
-        zx_status_t status = gtt_.AllocRegion(length,
-                                              registers::PlaneSurface::kLinearAlignment,
-                                              registers::PlaneSurface::kTrailingPtePadding,
-                                              &alt_gtt_region);
+        zx_status_t status = gtt_.AllocRegion(length, align, &alt_gtt_region);
         if (status != ZX_OK) {
             return status;
         }
@@ -684,6 +688,16 @@
     }
 }
 
+const fbl::unique_ptr<GttRegion>& Controller::GetGttRegion(void* handle) {
+    fbl::AutoLock lock(&gtt_lock_);
+    for (auto& region : imported_images_) {
+        if (region->base() == reinterpret_cast<uint64_t>(handle)) {
+            return region;
+        }
+    }
+    ZX_ASSERT(false);
+}
+
 bool Controller::GetLayer(registers::Pipe pipe, uint32_t plane,
                           const display_config_t** configs, uint32_t display_count,
                           const layer_t** layer_out) {
@@ -1058,11 +1072,34 @@
                 continue;
             }
             primary_layer_t* primary = &config->layers[j]->cfg.primary;
-            if (primary->transform_mode != FRAME_TRANSFORM_IDENTITY) {
+            if (primary->transform_mode == FRAME_TRANSFORM_ROT_90
+                    || primary->transform_mode == FRAME_TRANSFORM_ROT_270) {
+                // Linear and x tiled images don't support 90/270 rotation
+                if (primary->image.type == IMAGE_TYPE_SIMPLE
+                        || primary->image.type == IMAGE_TYPE_X_TILED) {
+                    layer_cfg_result[i][j] |= CLIENT_TRANSFORM;
+                }
+            } else if (primary->transform_mode != FRAME_TRANSFORM_IDENTITY
+                    && primary->transform_mode != FRAME_TRANSFORM_ROT_180) {
+                // Cover unsupported rotations
                 layer_cfg_result[i][j] |= CLIENT_TRANSFORM;
             }
-            if (primary->dest_frame.width != primary->src_frame.width
-                    || primary->dest_frame.height != primary->src_frame.height) {
+
+            uint32_t src_width;
+            uint32_t src_height;
+            if (primary->transform_mode == FRAME_TRANSFORM_IDENTITY
+                    || primary->transform_mode == FRAME_TRANSFORM_ROT_180
+                    || primary->transform_mode == FRAME_TRANSFORM_REFLECT_X
+                    || primary->transform_mode == FRAME_TRANSFORM_REFLECT_Y) {
+                src_width = primary->src_frame.width;
+                src_height = primary->src_frame.height;
+            } else {
+                src_width = primary->src_frame.height;
+                src_height = primary->src_frame.width;
+            }
+
+            if (primary->dest_frame.width != src_width
+                    || primary->dest_frame.height != src_height) {
                 layer_cfg_result[i][j] |= CLIENT_FRAME_SCALE;
             }
         }
@@ -1141,7 +1178,8 @@
 }
 
 uint32_t Controller::ComputeLinearStride(uint32_t width, zx_pixel_format_t format) {
-    return registers::PlaneSurfaceStride::compute_pixel_stride(IMAGE_TYPE_SIMPLE, width, format);
+    return fbl::round_up(width,
+            get_tile_byte_width(IMAGE_TYPE_SIMPLE, format) / ZX_PIXEL_FORMAT_BYTES(format));
 }
 
 zx_status_t Controller::AllocateVmo(uint64_t size, zx_handle_t* vmo_out) {
@@ -1218,7 +1256,7 @@
     }
     fbl::unique_ptr<GttRegion> region;
     zx_status_t status = gtt_.AllocRegion(static_cast<uint32_t>(page_count * PAGE_SIZE),
-                                          PAGE_SIZE, 0, &region);
+                                          PAGE_SIZE, &region);
     if (status != ZX_OK) {
         return status;
     }
@@ -1311,7 +1349,7 @@
 
         {
             fbl::AutoLock lock(&gtt_lock_);
-            gtt_.SetupForMexec(fb, fb_size, registers::PlaneSurface::kTrailingPtePadding);
+            gtt_.SetupForMexec(fb, fb_size);
         }
 
         // Try to map the framebuffer and clear it. If not, oh well.
@@ -1331,7 +1369,7 @@
                 registers::PipeRegs pipe_regs(display->pipe());
 
                 auto plane_stride = pipe_regs.PlaneSurfaceStride(0).ReadFrom(mmio_space_.get());
-                plane_stride.set_stride(IMAGE_TYPE_SIMPLE, stride, format);
+                plane_stride.set_stride(width_in_tiles(IMAGE_TYPE_SIMPLE, width, format));
                 plane_stride.WriteTo(mmio_space_.get());
 
                 auto plane_surface = pipe_regs.PlaneSurface(0).ReadFrom(mmio_space_.get());
diff --git a/system/dev/display/intel-i915/intel-i915.h b/system/dev/display/intel-i915/intel-i915.h
index 1b9aae5..4f46712 100644
--- a/system/dev/display/intel-i915/intel-i915.h
+++ b/system/dev/display/intel-i915/intel-i915.h
@@ -59,6 +59,8 @@
     uint32_t ComputeLinearStride(uint32_t width, zx_pixel_format_t format);
     zx_status_t AllocateVmo(uint64_t size, zx_handle_t* vmo_out);
 
+    const fbl::unique_ptr<GttRegion>& GetGttRegion(void* handle);
+
     zx_status_t ReadPciConfig16(uint16_t addr, uint16_t* value_out);
     zx_status_t MapPciMmio(uint32_t pci_bar, void** addr_out, uint64_t* size_out);
     zx_status_t UnmapPciMmio(uint32_t pci_bar);
diff --git a/system/dev/display/intel-i915/registers-pipe.h b/system/dev/display/intel-i915/registers-pipe.h
index d670908..f717244 100644
--- a/system/dev/display/intel-i915/registers-pipe.h
+++ b/system/dev/display/intel-i915/registers-pipe.h
@@ -46,8 +46,6 @@
     static constexpr uint32_t kLinearAlignment = 256 * 1024;
     static constexpr uint32_t kXTilingAlignment = 256 * 1024;
     static constexpr uint32_t kYTilingAlignment = 1024 * 1024;
-    static constexpr uint32_t kTrailingPtePadding = 136;
-    static constexpr uint32_t kHeaderPtePaddingFor180Or270 = 136;
 
     DEF_BIT(3, ring_flip_source);
 };
@@ -69,30 +67,6 @@
     static constexpr uint32_t kBaseAddr = 0x70188;
 
     DEF_FIELD(9, 0, stride);
-
-    void set_stride(uint32_t tiling, uint32_t width, zx_pixel_format_t format) {
-        uint32_t chunk_size = get_chunk_size(tiling, format);
-        set_stride(fbl::round_up(width * ZX_PIXEL_FORMAT_BYTES(format), chunk_size) / chunk_size);
-    }
-
-    static uint32_t compute_pixel_stride(uint32_t tiling, uint32_t width,
-                                         zx_pixel_format_t format) {
-        uint32_t chunk_size = get_chunk_size(tiling, format);
-        return fbl::round_up(width, chunk_size / ZX_PIXEL_FORMAT_BYTES(format));
-    }
-
-private:
-    static uint32_t get_chunk_size(uint32_t tiling, zx_pixel_format_t format) {
-        switch (tiling) {
-        case IMAGE_TYPE_SIMPLE: return 64;
-        case IMAGE_TYPE_X_TILED: return 512;
-        case IMAGE_TYPE_Y_LEGACY_TILED: return 128;
-        case IMAGE_TYPE_YF_TILED: return ZX_PIXEL_FORMAT_BYTES(format) == 1 ? 64 : 128;
-        default:
-            ZX_ASSERT(false);
-            return 0;
-        }
-    }
 };
 
 // PLANE_SIZE
@@ -138,6 +112,10 @@
     DEF_FIELD(5, 4, alpha_mode);
     DEF_BIT(3, allow_double_buffer_update_disable);
     DEF_FIELD(1, 0, plane_rotation);
+    static constexpr uint32_t kIdentity = 0;
+    static constexpr uint32_t k90deg = 1;
+    static constexpr uint32_t k180deg = 2;
+    static constexpr uint32_t k270deg = 3;
 };
 
 // PLANE_BUF_CFG
diff --git a/system/dev/display/intel-i915/tiling.h b/system/dev/display/intel-i915/tiling.h
new file mode 100644
index 0000000..1e31cbb
--- /dev/null
+++ b/system/dev/display/intel-i915/tiling.h
@@ -0,0 +1,48 @@
+// Copyright 2018 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.
+
+#pragma once
+
+#include <assert.h>
+#include <ddk/protocol/intel-gpu-core.h>
+#include <fbl/algorithm.h>
+#include <inttypes.h>
+#include <zircon/pixelformat.h>
+#include <zircon/device/display-controller.h>
+
+namespace i915 {
+
+static inline uint32_t get_tile_byte_width(uint32_t tiling, zx_pixel_format_t format) {
+    switch (tiling) {
+    case IMAGE_TYPE_SIMPLE: return 64;
+    case IMAGE_TYPE_X_TILED: return 512;
+    case IMAGE_TYPE_Y_LEGACY_TILED: return 128;
+    case IMAGE_TYPE_YF_TILED: return ZX_PIXEL_FORMAT_BYTES(format) == 1 ? 64 : 128;
+    default:
+        assert(false);
+        return 0;
+    }
+}
+
+static inline uint32_t get_tile_byte_size(uint32_t tiling) {
+    return tiling == IMAGE_TYPE_SIMPLE ? 64 : 4096;
+}
+
+static inline uint32_t get_tile_px_height(uint32_t tiling, zx_pixel_format_t format) {
+    return get_tile_byte_size(tiling) / get_tile_byte_width(tiling, format);
+}
+
+static inline uint32_t width_in_tiles(uint32_t tiling, uint32_t width,
+                                      zx_pixel_format_t format) {
+    uint32_t tile_width = get_tile_byte_width(tiling, format);
+    return ((width * ZX_PIXEL_FORMAT_BYTES(format))+ tile_width - 1) / tile_width;
+}
+
+static inline uint32_t height_in_tiles(uint32_t tiling, uint32_t height,
+                                       zx_pixel_format_t format) {
+    uint32_t tile_height = get_tile_px_height(tiling, format);
+    return (height + tile_height - 1) / tile_height;
+}
+
+} // namespace i915
diff --git a/system/fidl/display/display-controller.fidl b/system/fidl/display/display-controller.fidl
index dba28df..ebd432b 100644
--- a/system/fidl/display/display-controller.fidl
+++ b/system/fidl/display/display-controller.fidl
@@ -98,8 +98,9 @@
 };
 
 enum ConfigError : uint8 {
+  OK = 0;
   // The requested layer configuration is invalid.
-  INVALID_CONFIG = 0;
+  INVALID_CONFIG = 1;
 };
 
 struct ConfigResult {