[dev][intel-i915] Convert driver to c++/ddktl
Change-Id: I927ef64122402fee3e661c8a473b24423dca63d9
diff --git a/system/dev/display/intel-i915/bind.c b/system/dev/display/intel-i915/bind.c
new file mode 100644
index 0000000..9486e2d
--- /dev/null
+++ b/system/dev/display/intel-i915/bind.c
@@ -0,0 +1,22 @@
+// Copyright 2017 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 <ddk/binding.h>
+#include <ddk/driver.h>
+
+#include "intel-i915.h"
+
+#define INTEL_I915_VID (0x8086)
+
+static zx_driver_ops_t intel_i915_driver_ops = {
+ .version = DRIVER_OPS_VERSION,
+ .bind = intel_i915_bind,
+};
+
+// clang-format off
+ZIRCON_DRIVER_BEGIN(intel_i915, intel_i915_driver_ops, "zircon", "*0.1", 3)
+ BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PCI),
+ BI_ABORT_IF(NE, BIND_PCI_VID, INTEL_I915_VID),
+ BI_MATCH_IF(EQ, BIND_PCI_CLASS, 0x3), // Display class
+ZIRCON_DRIVER_END(intel_i915)
diff --git a/system/dev/display/intel-i915/intel-i915.c b/system/dev/display/intel-i915/intel-i915.c
deleted file mode 100644
index a7242ea..0000000
--- a/system/dev/display/intel-i915/intel-i915.c
+++ /dev/null
@@ -1,223 +0,0 @@
-// Copyright 2016 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 <ddk/binding.h>
-#include <ddk/debug.h>
-#include <ddk/device.h>
-#include <ddk/driver.h>
-#include <ddk/protocol/display.h>
-#include <ddk/protocol/pci.h>
-#include <hw/pci.h>
-
-#include <assert.h>
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <zircon/syscalls.h>
-#include <zircon/types.h>
-
-#define INTEL_I915_VID (0x8086)
-#define INTEL_I915_BROADWELL_DID (0x1616)
-
-#define INTEL_I915_REG_WINDOW_SIZE (0x1000000u)
-#define INTEL_I915_FB_WINDOW_SIZE (0x10000000u)
-
-#define BACKLIGHT_CTRL_OFFSET (0xc8250)
-#define BACKLIGHT_CTRL_BIT ((uint32_t)(1u << 31))
-
-typedef struct intel_i915_device {
- void* regs;
- uint64_t regs_size;
- zx_handle_t regs_handle;
-
- void* framebuffer;
- uint64_t framebuffer_size;
- zx_handle_t framebuffer_handle;
-
- zx_display_info_t info;
- uint32_t flags;
-} intel_i915_device_t;
-
-#define FLAGS_BACKLIGHT 1
-
-static void intel_i915_enable_backlight(intel_i915_device_t* dev, bool enable) {
- if (dev->flags & FLAGS_BACKLIGHT) {
- void* backlight_ctrl = (uint8_t*)dev->regs + BACKLIGHT_CTRL_OFFSET;
- uint32_t tmp = pcie_read32(backlight_ctrl);
-
- if (enable)
- tmp |= BACKLIGHT_CTRL_BIT;
- else
- tmp &= ~BACKLIGHT_CTRL_BIT;
-
- pcie_write32(backlight_ctrl, tmp);
- }
-}
-
-// implement display protocol
-
-static zx_status_t intel_i915_set_mode(void* ctx, zx_display_info_t* info) {
- return ZX_ERR_NOT_SUPPORTED;
-}
-
-static zx_status_t intel_i915_get_mode(void* ctx, zx_display_info_t* info) {
- assert(info);
- intel_i915_device_t* device = ctx;
- memcpy(info, &device->info, sizeof(zx_display_info_t));
- return ZX_OK;
-}
-
-static zx_status_t intel_i915_get_framebuffer(void* ctx, void** framebuffer) {
- assert(framebuffer);
- intel_i915_device_t* device = ctx;
- (*framebuffer) = device->framebuffer;
- return ZX_OK;
-}
-
-static display_protocol_ops_t intel_i915_display_proto = {
- .set_mode = intel_i915_set_mode,
- .get_mode = intel_i915_get_mode,
- .get_framebuffer = intel_i915_get_framebuffer,
-};
-
-// implement device protocol
-
-static zx_status_t intel_i915_open(void* ctx, zx_device_t** out, uint32_t flags) {
- intel_i915_device_t* device = ctx;
- intel_i915_enable_backlight(device, true);
- return ZX_OK;
-}
-
-static zx_status_t intel_i915_close(void* ctx, uint32_t flags) {
- return ZX_OK;
-}
-
-static void intel_i915_release(void* ctx) {
- intel_i915_device_t* device = ctx;
- intel_i915_enable_backlight(device, false);
-
- if (device->regs) {
- zx_handle_close(device->regs_handle);
- device->regs_handle = -1;
- }
-
- if (device->framebuffer) {
- zx_handle_close(device->framebuffer_handle);
- device->framebuffer_handle = -1;
- }
-
- free(device);
-}
-
-static zx_protocol_device_t intel_i915_device_proto = {
- .version = DEVICE_OPS_VERSION,
- .open = intel_i915_open,
- .close = intel_i915_close,
- .release = intel_i915_release,
-};
-
-// implement driver object:
-
-static zx_status_t intel_i915_bind(void* ctx, zx_device_t* dev) {
- pci_protocol_t pci;
- if (device_get_protocol(dev, ZX_PROTOCOL_PCI, &pci))
- return ZX_ERR_NOT_SUPPORTED;
-
- // map resources and initialize the device
- intel_i915_device_t* device = calloc(1, sizeof(intel_i915_device_t));
- if (!device)
- return ZX_ERR_NO_MEMORY;
-
- const pci_config_t* pci_config;
- size_t config_size;
- zx_handle_t cfg_handle = ZX_HANDLE_INVALID;
- zx_status_t status = pci_map_resource(&pci, PCI_RESOURCE_CONFIG,
- ZX_CACHE_POLICY_UNCACHED_DEVICE,
- (void**)&pci_config,
- &config_size, &cfg_handle);
- if (status == ZX_OK) {
- if (pci_config->device_id == INTEL_I915_BROADWELL_DID) {
- // TODO: this should be based on the specific target
- device->flags |= FLAGS_BACKLIGHT;
- }
- zx_handle_close(cfg_handle);
- }
-
- // map register window
- status = pci_map_resource(&pci, PCI_RESOURCE_BAR_0, ZX_CACHE_POLICY_UNCACHED_DEVICE,
- &device->regs, &device->regs_size, &device->regs_handle);
- if (status != ZX_OK) {
- zxlogf(ERROR, "i915: failed to map bar 0: %d\n", status);
- goto fail;
- }
-
- // map framebuffer window
- status = pci_map_resource(&pci, PCI_RESOURCE_BAR_2, ZX_CACHE_POLICY_WRITE_COMBINING,
- &device->framebuffer,
- &device->framebuffer_size,
- &device->framebuffer_handle);
- if (status != ZX_OK) {
- zxlogf(ERROR, "i915: failed to map bar 2: %d\n", status);
- goto fail;
- }
-
- zx_display_info_t* di = &device->info;
- uint32_t format, width, height, stride;
- status = zx_bootloader_fb_get_info(&format, &width, &height, &stride);
- if (status == ZX_OK) {
- di->format = format;
- di->width = width;
- di->height = height;
- di->stride = stride;
- } else {
- di->format = ZX_PIXEL_FORMAT_RGB_565;
- di->width = 2560 / 2;
- di->height = 1700 / 2;
- di->stride = 2560 / 2;
- }
- di->flags = ZX_DISPLAY_FLAG_HW_FRAMEBUFFER;
-
- // TODO remove when the gfxconsole moves to user space
- intel_i915_enable_backlight(device, true);
- zx_set_framebuffer(get_root_resource(), device->framebuffer, device->framebuffer_size,
- format, width, height, stride);
-
- // create and add the display (char) device
- device_add_args_t args = {
- .version = DEVICE_ADD_ARGS_VERSION,
- .name = "intel_i915_disp",
- .ctx = device,
- .ops = &intel_i915_device_proto,
- .proto_id = ZX_PROTOCOL_DISPLAY,
- .proto_ops = &intel_i915_display_proto,
- };
-
- status = device_add(dev, &args, NULL);
- if (status != ZX_OK) {
- goto fail;
- }
-
- zxlogf(SPEW, "i915: reg=%p regsize=0x%" PRIx64 " fb=%p fbsize=0x%" PRIx64 "\n",
- device->regs, device->regs_size, device->framebuffer, device->framebuffer_size);
-
- return ZX_OK;
-
-fail:
- free(device);
- return status;
-}
-
-static zx_driver_ops_t intel_i915_driver_ops = {
- .version = DRIVER_OPS_VERSION,
- .bind = intel_i915_bind,
-};
-
-// clang-format off
-ZIRCON_DRIVER_BEGIN(intel_i915, intel_i915_driver_ops, "zircon", "*0.1", 3)
- BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PCI),
- BI_ABORT_IF(NE, BIND_PCI_VID, INTEL_I915_VID),
- BI_MATCH_IF(EQ, BIND_PCI_CLASS, 0x3), // Display class
-ZIRCON_DRIVER_END(intel_i915)
diff --git a/system/dev/display/intel-i915/intel-i915.cpp b/system/dev/display/intel-i915/intel-i915.cpp
new file mode 100644
index 0000000..a289846
--- /dev/null
+++ b/system/dev/display/intel-i915/intel-i915.cpp
@@ -0,0 +1,200 @@
+// Copyright 2016 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 <ddk/binding.h>
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/driver.h>
+#include <ddk/protocol/display.h>
+#include <ddk/protocol/pci.h>
+#include <hw/pci.h>
+
+#include <assert.h>
+#include <fbl/unique_ptr.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <zircon/syscalls.h>
+#include <zircon/types.h>
+
+#include "intel-i915.h"
+
+#define INTEL_I915_BROADWELL_DID (0x1616)
+
+#define INTEL_I915_REG_WINDOW_SIZE (0x1000000u)
+#define INTEL_I915_FB_WINDOW_SIZE (0x10000000u)
+
+#define BACKLIGHT_CTRL_OFFSET (0xc8250)
+#define BACKLIGHT_CTRL_BIT ((uint32_t)(1u << 31))
+
+#define FLAGS_BACKLIGHT 1
+
+namespace i915 {
+
+void Device::EnableBacklight(bool enable) {
+ if (flags_ & FLAGS_BACKLIGHT) {
+ auto* backlight_ctrl = reinterpret_cast<volatile uint32_t*>(regs_ + BACKLIGHT_CTRL_OFFSET);
+ uint32_t tmp = pcie_read32(backlight_ctrl);
+
+ if (enable) {
+ tmp |= BACKLIGHT_CTRL_BIT;
+ } else {
+ tmp &= ~BACKLIGHT_CTRL_BIT;
+ }
+
+ pcie_write32(backlight_ctrl, tmp);
+ }
+}
+
+// implement display protocol
+
+zx_status_t Device::SetMode(zx_display_info_t* info) {
+ return ZX_ERR_NOT_SUPPORTED;
+}
+
+zx_status_t Device::GetMode(zx_display_info_t* info) {
+ assert(info);
+ memcpy(info, &info_, sizeof(zx_display_info_t));
+ return ZX_OK;
+}
+
+zx_status_t Device::GetFramebuffer(void** framebuffer) {
+ assert(framebuffer);
+ *framebuffer = framebuffer_;
+ return ZX_OK;
+}
+
+void Device::Flush() {
+ // no-op
+}
+
+void Device::AcquireOrReleaseDisplay(bool acquire) {
+ // no-op
+}
+
+void Device::SetOwnershipChangeCallback(zx_display_cb_t callback, void* cookie) {
+ // no-op
+}
+
+// implement device protocol
+
+zx_status_t Device::DdkOpen(zx_device_t** dev_out, uint32_t flags) {
+ EnableBacklight(true);
+ return ZX_OK;
+}
+
+zx_status_t Device::DdkClose(uint32_t flags) {
+ return ZX_OK;
+}
+
+void Device::DdkRelease() {
+ delete this;
+}
+
+zx_status_t Device::Bind() {
+ pci_protocol_t pci;
+ if (device_get_protocol(parent_, ZX_PROTOCOL_PCI, &pci))
+ return ZX_ERR_NOT_SUPPORTED;
+
+ const pci_config_t* pci_config;
+ size_t config_size;
+ zx_handle_t cfg_handle = ZX_HANDLE_INVALID;
+ zx_status_t status = pci_map_resource(&pci, PCI_RESOURCE_CONFIG,
+ ZX_CACHE_POLICY_UNCACHED_DEVICE,
+ (void**)&pci_config,
+ &config_size, &cfg_handle);
+ uint32_t flags = 0;
+ if (status == ZX_OK) {
+ if (pci_config->device_id == INTEL_I915_BROADWELL_DID) {
+ // TODO: this should be based on the specific target
+ flags |= FLAGS_BACKLIGHT;
+ }
+ zx_handle_close(cfg_handle);
+ }
+
+ // map register window
+ void** addr = reinterpret_cast<void**>(®s_);
+ status = pci_map_resource(&pci, PCI_RESOURCE_BAR_0, ZX_CACHE_POLICY_UNCACHED_DEVICE,
+ addr, ®s_size_, ®s_handle_);
+ if (status != ZX_OK) {
+ zxlogf(ERROR, "i915: failed to map bar 0: %d\n", status);
+ return status;
+ }
+
+ // map framebuffer window
+ status = pci_map_resource(&pci, PCI_RESOURCE_BAR_2, ZX_CACHE_POLICY_WRITE_COMBINING,
+ &framebuffer_,
+ &framebuffer_size_,
+ &framebuffer_handle_);
+ if (status != ZX_OK) {
+ zxlogf(ERROR, "i915: failed to map bar 2: %d\n", status);
+ return status;
+ }
+
+ zx_display_info_t* di = &info_;
+ uint32_t format, width, height, stride;
+ status = zx_bootloader_fb_get_info(&format, &width, &height, &stride);
+ if (status == ZX_OK) {
+ di->format = format;
+ di->width = width;
+ di->height = height;
+ di->stride = stride;
+ } else {
+ di->format = ZX_PIXEL_FORMAT_RGB_565;
+ di->width = 2560 / 2;
+ di->height = 1700 / 2;
+ di->stride = 2560 / 2;
+ }
+ di->flags = ZX_DISPLAY_FLAG_HW_FRAMEBUFFER;
+
+ // TODO remove when the gfxconsole moves to user space
+ EnableBacklight(true);
+ zx_set_framebuffer(get_root_resource(), framebuffer_,
+ (uint32_t) framebuffer_size_,
+ format, width, height, stride);
+
+ status = DdkAdd("intel_i915_disp", flags);
+ if (status != ZX_OK) {
+ return status;
+ }
+
+ zxlogf(SPEW, "i915: reg=%08lx regsize=0x%" PRIx64 " fb=%p fbsize=0x%" PRIx64 "\n",
+ regs_, regs_size_, framebuffer_, framebuffer_size_);
+
+ return ZX_OK;
+}
+
+Device::Device(zx_device_t* parent) : DeviceType(parent) { }
+
+Device::~Device() {
+ if (regs_) {
+ EnableBacklight(false);
+
+ zx_handle_close(regs_handle_);
+ regs_handle_ = ZX_HANDLE_INVALID;
+ }
+
+ if (framebuffer_) {
+ zx_handle_close(framebuffer_handle_);
+ framebuffer_handle_ = ZX_HANDLE_INVALID;
+ }
+}
+
+} // namespace i915
+
+zx_status_t intel_i915_bind(void* ctx, zx_device_t* parent) {
+ fbl::AllocChecker ac;
+ fbl::unique_ptr<i915::Device> device(new (&ac) i915::Device(parent));
+ if (!ac.check()) {
+ return ZX_ERR_NO_MEMORY;
+ }
+
+ zx_status_t status = device->Bind();
+ if (status == ZX_OK) {
+ device.release();
+ }
+ return status;
+}
diff --git a/system/dev/display/intel-i915/intel-i915.h b/system/dev/display/intel-i915/intel-i915.h
new file mode 100644
index 0000000..229c79f
--- /dev/null
+++ b/system/dev/display/intel-i915/intel-i915.h
@@ -0,0 +1,55 @@
+// Copyright 2017 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
+
+#if __cplusplus
+
+#include <ddktl/device.h>
+#include <ddktl/protocol/display.h>
+
+namespace i915 {
+
+class Device;
+using DeviceType = ddk::Device<Device, ddk::Openable, ddk::Closable>;
+
+class Device: public DeviceType, public ddk::DisplayProtocol<Device> {
+public:
+ Device(zx_device_t* parent);
+ ~Device();
+
+ zx_status_t DdkOpen(zx_device_t** dev_out, uint32_t flags);
+ zx_status_t DdkClose(uint32_t flags);
+ void DdkRelease();
+ zx_status_t Bind();
+
+ zx_status_t SetMode(zx_display_info_t* info);
+ zx_status_t GetMode(zx_display_info_t* info);
+ zx_status_t GetFramebuffer(void** framebuffer);
+ void Flush();
+ void AcquireOrReleaseDisplay(bool acquire);
+ void SetOwnershipChangeCallback(zx_display_cb_t callback, void* cookie);
+
+private:
+ void EnableBacklight(bool enable);
+
+ uintptr_t regs_;
+ uint64_t regs_size_;
+ zx_handle_t regs_handle_;
+
+ void* framebuffer_;
+ uint64_t framebuffer_size_;
+ zx_handle_t framebuffer_handle_;
+
+ zx_display_info_t info_;
+ uint32_t flags_;
+};
+
+}
+
+#endif // __cplusplus
+
+__BEGIN_CDECLS
+zx_status_t intel_i915_bind(void* ctx, zx_device_t* parent);
+__END_CDECLS
diff --git a/system/dev/display/intel-i915/rules.mk b/system/dev/display/intel-i915/rules.mk
index ce40d58..7dd1de3 100644
--- a/system/dev/display/intel-i915/rules.mk
+++ b/system/dev/display/intel-i915/rules.mk
@@ -10,12 +10,18 @@
MODULE_TYPE := driver
-MODULE_SRCS := $(LOCAL_DIR)/intel-i915.c
+MODULE_SRCS := \
+ $(LOCAL_DIR)/bind.c \
+ $(LOCAL_DIR)/intel-i915.cpp
-MODULE_STATIC_LIBS := system/ulib/ddk
+MODULE_STATIC_LIBS := \
+ system/ulib/ddk \
+ system/ulib/ddktl \
+ system/ulib/zxcpp \
+ system/ulib/fbl
MODULE_LIBS := system/ulib/driver system/ulib/zircon system/ulib/c
include make/module.mk
-endif
\ No newline at end of file
+endif
diff --git a/system/ulib/ddktl/BUILD.gn b/system/ulib/ddktl/BUILD.gn
index 1f6c8c7..4ff01e8 100644
--- a/system/ulib/ddktl/BUILD.gn
+++ b/system/ulib/ddktl/BUILD.gn
@@ -15,6 +15,8 @@
"include/ddktl/protocol/block.h",
"include/ddktl/protocol/ethernet-internal.h",
"include/ddktl/protocol/ethernet.h",
+ "include/ddktl/protocol/display.h",
+ "include/ddktl/protocol/display-internal.h",
"include/ddktl/protocol/pci.h",
"include/ddktl/protocol/test.h",
"include/ddktl/protocol/tpm.h",
diff --git a/system/ulib/ddktl/include/ddktl/protocol/display-internal.h b/system/ulib/ddktl/include/ddktl/protocol/display-internal.h
new file mode 100644
index 0000000..f523f62
--- /dev/null
+++ b/system/ulib/ddktl/include/ddktl/protocol/display-internal.h
@@ -0,0 +1,77 @@
+// Copyright 2017 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 <ddktl/device-internal.h>
+#include <zircon/types.h>
+#include <fbl/type_support.h>
+#include <fbl/unique_ptr.h>
+
+#include <stdint.h>
+
+namespace ddk {
+namespace internal {
+
+DECLARE_HAS_MEMBER_FN(has_set_mode, SetMode);
+DECLARE_HAS_MEMBER_FN(has_get_mode, GetMode);
+DECLARE_HAS_MEMBER_FN(has_get_framebuffer, GetFramebuffer);
+DECLARE_HAS_MEMBER_FN(has_flush, Flush);
+DECLARE_HAS_MEMBER_FN(has_acquire_or_release_display, AcquireOrReleaseDisplay);
+DECLARE_HAS_MEMBER_FN(has_set_ownership_change_callback, SetOwnershipChangeCallback);
+
+template <typename D>
+constexpr void CheckDisplayProtocolSubclass() {
+ static_assert(internal::has_set_mode<D>::value,
+ "DisplayProtocol subclasses must implement SetMode");
+ static_assert(fbl::is_same<decltype(&D::SetMode),
+ zx_status_t (D::*)(zx_display_info_t*)>::value,
+ "SetMode must be a non-static member function with signature "
+ "'zx_status_t SetMode(zx_display_info_t* info)', and be visible to "
+ "ddk::DisplayProtocol<D> (either because they are public, or because of "
+ "friendship).");
+ static_assert(internal::has_get_mode<D>::value,
+ "DisplayProtocol subclasses must implement GetMode");
+ static_assert(fbl::is_same<decltype(&D::GetMode),
+ zx_status_t (D::*)(zx_display_info_t*)>::value,
+ "GetMode must be a non-static member function with signature "
+ "'zx_status_t GetMode(zx_display_info_t* info)', and be visible to "
+ "ddk::DisplayProtocol<D> (either because they are public, or because of "
+ "friendship).");
+ static_assert(internal::has_get_framebuffer<D>::value,
+ "DisplayProtocol subclasses must implement GetFramebuffer");
+ static_assert(fbl::is_same<decltype(&D::GetFramebuffer),
+ zx_status_t (D::*)(void**)>::value,
+ "GetFramebuffer must be a non-static member function with signature "
+ "'zx_status_t GetFramebuffer(void** framebuffer)', and be visible to "
+ "ddk::DisplayProtocol<D> (either because they are public, or because of "
+ "friendship).");
+ static_assert(internal::has_flush<D>::value,
+ "DisplayProtocol subclasses must implement Flush");
+ static_assert(fbl::is_same<decltype(&D::Flush),
+ void (D::*)()>::value,
+ "Flush must be a non-static member function with signature "
+ "'void Flush()', and be visible to "
+ "ddk::DisplayProtocol<D> (either because they are public, or because of "
+ "friendship).");
+ static_assert(internal::has_acquire_or_release_display<D>::value,
+ "DisplayProtocol subclasses must implement AcquireOrReleaseDisplay");
+ static_assert(fbl::is_same<decltype(&D::AcquireOrReleaseDisplay),
+ void (D::*)(bool)>::value,
+ "AcquireOrReleaseDisplay must be a non-static member function with signature "
+ "'void AcquireOrReleaseDisplay(bool acquire)', and be visible to "
+ "ddk::DisplayProtocol<D> (either because they are public, or because of "
+ "friendship).");
+ static_assert(internal::has_set_ownership_change_callback<D>::value,
+ "DisplayProtocol subclasses must implement SetOwnershipChangeCallback");
+ static_assert(fbl::is_same<decltype(&D::SetOwnershipChangeCallback),
+ void (D::*)(zx_display_cb_t, void*)>::value,
+ "SetOwnershipChangeCallback must be a non-static member function with signature "
+ "'void SetOwnershipChangeCallback(zx_display_cb_t cb, void* cookie)', and be "
+ "visible to ddk::DisplayProtocol<D> (either because they are public, or because "
+ "of friendship).");
+}
+
+} // namespace internal
+} // namespace ddk
diff --git a/system/ulib/ddktl/include/ddktl/protocol/display.h b/system/ulib/ddktl/include/ddktl/protocol/display.h
new file mode 100644
index 0000000..32d725a
--- /dev/null
+++ b/system/ulib/ddktl/include/ddktl/protocol/display.h
@@ -0,0 +1,60 @@
+// Copyright 2017 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 "ddktl/protocol/display-internal.h"
+
+#include <ddk/protocol/display.h>
+#include <zircon/assert.h>
+
+namespace ddk {
+
+template <typename D>
+class DisplayProtocol : public internal::base_protocol {
+ public:
+ DisplayProtocol() {
+ internal::CheckDisplayProtocolSubclass<D>();
+ ops_.set_mode = SetMode;
+ ops_.get_mode = GetMode;
+ ops_.get_framebuffer = GetFramebuffer;
+ ops_.flush = FlushThunk;
+ ops_.acquire_or_release_display = AcquireOrReleaseDisplay;
+ ops_.set_ownership_change_callback = SetOwnershipChangeCallback;
+
+ // Can only inherit from one base_protocol implemenation
+ ZX_ASSERT(ddk_proto_ops_ == nullptr);
+ ddk_proto_id_ = ZX_PROTOCOL_DISPLAY;
+ ddk_proto_ops_ = &ops_;
+ }
+
+ private:
+ static zx_status_t SetMode(void* ctx, zx_display_info_t* info) {
+ return static_cast<D*>(ctx)->SetMode(info);
+ }
+
+ static zx_status_t GetMode(void* ctx, zx_display_info_t* info) {
+ return static_cast<D*>(ctx)->GetMode(info);
+ }
+
+ static zx_status_t GetFramebuffer(void* ctx, void** framebuffer) {
+ return static_cast<D*>(ctx)->GetFramebuffer(framebuffer);
+ }
+
+ static void FlushThunk(void* ctx) {
+ static_cast<D*>(ctx)->Flush();
+ }
+
+ static void AcquireOrReleaseDisplay(void* ctx, bool acquire) {
+ static_cast<D*>(ctx)->AcquireOrReleaseDisplay(acquire);
+ }
+
+ static void SetOwnershipChangeCallback(void* ctx, zx_display_cb_t callback, void* cookie) {
+ static_cast<D*>(ctx)->SetOwnershipChangeCallback(callback, cookie);
+ }
+
+ display_protocol_ops_t ops_ = {};
+};
+
+} // namespace ddk