[07/12] drm/tegra: Add Tegra186 display hub support

Message ID 20171127100952.22465-8-thierry.reding@gmail.com
State New
Headers show
Series
  • drm/tegra: Basic Tegra186 support
Related show

Commit Message

Thierry Reding Nov. 27, 2017, 10:09 a.m.
From: Thierry Reding <treding@nvidia.com>

The display architecture has changed in several significant ways with
the new Tegra186 SoC. Shared between all display controllers is a set
of common resources referred to as the display hub. The hub generates
accesses to memory and feeds them into various composition pipelines,
each of which being a window that can be assigned to arbitrary heads.

Atomic state is subclassed in order to track the global bandwidth
requirements and select and adjust the hub clocks appropriately. The
plane code is shared to a large degree with earlier SoC generations,
except where the programming differs.

Signed-off-by: Thierry Reding <treding@nvidia.com>
---
 drivers/gpu/drm/tegra/Makefile |   1 +
 drivers/gpu/drm/tegra/dc.c     |  31 ++
 drivers/gpu/drm/tegra/dc.h     | 114 ++++++
 drivers/gpu/drm/tegra/drm.c    |  67 +++-
 drivers/gpu/drm/tegra/drm.h    |  19 +
 drivers/gpu/drm/tegra/hub.c    | 808 +++++++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tegra/hub.h    |  82 +++++
 drivers/gpu/drm/tegra/plane.h  |   2 +
 8 files changed, 1121 insertions(+), 3 deletions(-)
 create mode 100644 drivers/gpu/drm/tegra/hub.c
 create mode 100644 drivers/gpu/drm/tegra/hub.h

Patch

diff --git a/drivers/gpu/drm/tegra/Makefile b/drivers/gpu/drm/tegra/Makefile
index a47784765217..2e0d6213f6bc 100644
--- a/drivers/gpu/drm/tegra/Makefile
+++ b/drivers/gpu/drm/tegra/Makefile
@@ -5,6 +5,7 @@  tegra-drm-y := \
 	drm.o \
 	gem.o \
 	fb.o \
+	hub.o \
 	plane.o \
 	dc.o \
 	output.o \
diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
index d2c7a3481022..0ba1bf7f7615 100644
--- a/drivers/gpu/drm/tegra/dc.c
+++ b/drivers/gpu/drm/tegra/dc.c
@@ -465,6 +465,15 @@  static struct drm_plane *tegra_dc_primary_plane_create(struct drm_device *drm,
 	num_formats = ARRAY_SIZE(tegra_primary_plane_formats);
 	formats = tegra_primary_plane_formats;
 
+	/*
+	 * XXX compute offset so that we can directly access windows.
+	 *
+	 * Always use window A as primary window.
+	 */
+	plane->offset = 0;
+	plane->index = 0;
+	plane->depth = 255;
+
 	err = drm_universal_plane_init(drm, &plane->base, possible_crtcs,
 				       &tegra_plane_funcs, formats,
 				       num_formats, NULL,
@@ -660,7 +669,10 @@  static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm,
 	if (!plane)
 		return ERR_PTR(-ENOMEM);
 
+	/* XXX compute offset so that we can directly access windows */
+	plane->offset = 0;
 	plane->index = index;
+	plane->depth = 0;
 
 	num_formats = ARRAY_SIZE(tegra_overlay_plane_formats);
 	formats = tegra_overlay_plane_formats;
@@ -1401,6 +1413,25 @@  static void tegra_crtc_atomic_enable(struct drm_crtc *crtc,
 static int tegra_crtc_atomic_check(struct drm_crtc *crtc,
 				   struct drm_crtc_state *state)
 {
+	struct tegra_atomic_state *s = to_tegra_atomic_state(state->state);
+	struct tegra_dc_state *tegra = to_dc_state(state);
+
+	/*
+	 * The display hub display clock needs to be fed by the display clock
+	 * with the highest frequency to ensure proper functioning of all the
+	 * displays.
+	 *
+	 * Note that this isn't used before Tegra186, but it doesn't hurt and
+	 * conditionalizing it would make the code less clean.
+	 */
+	if (state->active) {
+		if (!s->clk_disp || tegra->pclk > s->rate) {
+			s->dc = to_tegra_dc(crtc);
+			s->clk_disp = s->dc->clk;
+			s->rate = tegra->pclk;
+		}
+	}
+
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h
index b65dfbb0af89..22c5091006bc 100644
--- a/drivers/gpu/drm/tegra/dc.h
+++ b/drivers/gpu/drm/tegra/dc.h
@@ -209,6 +209,8 @@  int tegra_dc_rgb_exit(struct tegra_dc *dc);
 #define WIN_B_UPDATE    (1 << 10)
 #define WIN_C_UPDATE    (1 << 11)
 #define CURSOR_UPDATE   (1 << 15)
+#define COMMON_ACTREQ   (1 << 16)
+#define COMMON_UPDATE   (1 << 17)
 #define NC_HOST_TRIG    (1 << 24)
 
 #define DC_CMD_DISPLAY_WINDOW_HEADER		0x042
@@ -486,6 +488,35 @@  int tegra_dc_rgb_exit(struct tegra_dc *dc);
 #define CURSOR_SRC_BLEND_MASK			(3 << 8)
 #define CURSOR_ALPHA				0xff
 
+#define DC_WIN_CORE_ACT_CONTROL 0x50e
+#define  VCOUNTER (0 << 0)
+#define  HCOUNTER (1 << 0)
+
+#define DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLA 0x543
+#define  LATENCY_CTL_MODE_ENABLE (1 << 2)
+
+#define DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLB 0x544
+#define  WATERMARK_MASK 0x1fffffff
+
+#define DC_WIN_CORE_PRECOMP_WGRP_PIPE_METER 0x560
+#define  PIPE_METER_INT(x)  (((x) & 0xff) << 8)
+#define  PIPE_METER_FRAC(x) (((x) & 0xff) << 0)
+
+#define DC_WIN_CORE_IHUB_WGRP_POOL_CONFIG 0x561
+#define  MEMPOOL_ENTRIES(x) (((x) & 0xffff) << 0)
+
+#define DC_WIN_CORE_IHUB_WGRP_FETCH_METER 0x562
+#define  SLOTS(x) (((x) & 0xff) << 0)
+
+#define DC_WIN_CORE_IHUB_LINEBUF_CONFIG 0x563
+#define  MODE_TWO_LINES  (0 << 14)
+#define  MODE_FOUR_LINES (1 << 14)
+
+#define DC_WIN_CORE_IHUB_THREAD_GROUP 0x568
+#define  THREAD_NUM_MASK (0x1f << 1)
+#define  THREAD_NUM(x) (((x) & 0x1f) << 1)
+#define  THREAD_GROUP_ENABLE (1 << 0)
+
 #define DC_WIN_CSC_YOF				0x611
 #define DC_WIN_CSC_KYRGB			0x612
 #define DC_WIN_CSC_KUR				0x613
@@ -596,8 +627,91 @@  int tegra_dc_rgb_exit(struct tegra_dc *dc);
 
 #define DC_WINBUF_START_ADDR_HI			0x80d
 
+#define DC_WINBUF_CDE_CONTROL			0x82f
+#define  ENABLE_SURFACE (1 << 0)
+
 #define DC_WINBUF_AD_UFLOW_STATUS		0xbca
 #define DC_WINBUF_BD_UFLOW_STATUS		0xdca
 #define DC_WINBUF_CD_UFLOW_STATUS		0xfca
 
+/* Tegra186 and later */
+#define DC_WIN_CORE_WINDOWGROUP_SET_CONTROL	0x702
+#define OWNER_MASK (0xf << 0)
+#define OWNER(x) (((x) & 0xf) << 0)
+
+#define DC_WIN_CROPPED_SIZE			0x706
+
+#define DC_WIN_PLANAR_STORAGE			0x709
+#define PITCH(x) (((x) >> 6) & 0x1fff)
+
+#define DC_WIN_SET_PARAMS			0x70d
+#define  CLAMP_BEFORE_BLEND (1 << 15)
+#define  DEGAMMA_NONE (0 << 13)
+#define  DEGAMMA_SRGB (1 << 13)
+#define  DEGAMMA_YUV8_10 (2 << 13)
+#define  DEGAMMA_YUV12 (3 << 13)
+#define  INPUT_RANGE_BYPASS (0 << 10)
+#define  INPUT_RANGE_LIMITED (1 << 10)
+#define  INPUT_RANGE_FULL (2 << 10)
+#define  COLOR_SPACE_RGB (0 << 8)
+#define  COLOR_SPACE_YUV_601 (1 << 8)
+#define  COLOR_SPACE_YUV_709 (2 << 8)
+#define  COLOR_SPACE_YUV_2020 (3 << 8)
+
+#define DC_WIN_WINDOWGROUP_SET_CONTROL_INPUT_SCALER	0x70e
+#define  HORIZONTAL_TAPS_2 (1 << 3)
+#define  HORIZONTAL_TAPS_5 (4 << 3)
+#define  VERTICAL_TAPS_2 (1 << 0)
+#define  VERTICAL_TAPS_5 (4 << 0)
+
+#define DC_WIN_WINDOWGROUP_SET_INPUT_SCALER_USAGE	0x711
+#define  INPUT_SCALER_USE422  (1 << 2)
+#define  INPUT_SCALER_VBYPASS (1 << 1)
+#define  INPUT_SCALER_HBYPASS (1 << 0)
+
+#define DC_WIN_BLEND_LAYER_CONTROL		0x716
+#define  COLOR_KEY_NONE (0 << 25)
+#define  COLOR_KEY_SRC (1 << 25)
+#define  COLOR_KEY_DST (2 << 25)
+#define  BLEND_BYPASS (1 << 24)
+#define  K2(x) (((x) & 0xff) << 16)
+#define  K1(x) (((x) & 0xff) << 8)
+#define  WINDOW_LAYER_DEPTH(x) (((x) & 0xff) << 0)
+
+#define DC_WIN_BLEND_MATCH_SELECT		0x717
+#define  BLEND_FACTOR_DST_ALPHA_ZERO			(0 << 12)
+#define  BLEND_FACTOR_DST_ALPHA_ONE			(1 << 12)
+#define  BLEND_FACTOR_DST_ALPHA_NEG_K1_TIMES_SRC	(2 << 12)
+#define  BLEND_FACTOR_DST_ALPHA_K2			(3 << 12)
+#define  BLEND_FACTOR_SRC_ALPHA_ZERO			(0 << 8)
+#define  BLEND_FACTOR_SRC_ALPHA_K1			(1 << 8)
+#define  BLEND_FACTOR_SRC_ALPHA_K2			(2 << 8)
+#define  BLEND_FACTOR_SRC_ALPHA_NEG_K1_TIMES_DST	(3 << 8)
+#define  BLEND_FACTOR_DST_COLOR_ZERO			(0 << 4)
+#define  BLEND_FACTOR_DST_COLOR_ONE			(1 << 4)
+#define  BLEND_FACTOR_DST_COLOR_K1			(2 << 4)
+#define  BLEND_FACTOR_DST_COLOR_K2			(3 << 4)
+#define  BLEND_FACTOR_DST_COLOR_K1_TIMES_DST		(4 << 4)
+#define  BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_DST	(5 << 4)
+#define  BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_SRC	(6 << 4)
+#define  BLEND_FACTOR_DST_COLOR_NEG_K1			(7 << 4)
+#define  BLEND_FACTOR_SRC_COLOR_ZERO			(0 << 0)
+#define  BLEND_FACTOR_SRC_COLOR_ONE			(1 << 0)
+#define  BLEND_FACTOR_SRC_COLOR_K1			(2 << 0)
+#define  BLEND_FACTOR_SRC_COLOR_K1_TIMES_DST		(3 << 0)
+#define  BLEND_FACTOR_SRC_COLOR_NEG_K1_TIMES_DST	(4 << 0)
+#define  BLEND_FACTOR_SRC_COLOR_K1_TIMES_SRC		(5 << 0)
+
+#define DC_WIN_BLEND_NOMATCH_SELECT		0x718
+
+#define DC_WIN_PRECOMP_WGRP_PARAMS		0x724
+#define  SWAP_UV (1 << 0)
+
+#define DC_WIN_WINDOW_SET_CONTROL		0x730
+#define  CONTROL_CSC_ENABLE (1 << 5)
+
+#define DC_WINBUF_CROPPED_POINT			0x806
+#define OFFSET_Y(x) (((x) & 0xffff) << 16)
+#define OFFSET_X(x) (((x) & 0xffff) << 0)
+
 #endif /* TEGRA_DC_H */
diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c
index bff04ca82b8b..8d5da4d583dd 100644
--- a/drivers/gpu/drm/tegra/drm.c
+++ b/drivers/gpu/drm/tegra/drm.c
@@ -34,6 +34,35 @@  struct tegra_drm_file {
 	struct mutex lock;
 };
 
+static struct drm_atomic_state *
+tegra_atomic_state_alloc(struct drm_device *drm)
+{
+	struct tegra_atomic_state *state = kzalloc(sizeof(*state), GFP_KERNEL);
+
+	if (!state || drm_atomic_state_init(drm, &state->base) < 0) {
+		kfree(state);
+		return NULL;
+	}
+
+	return &state->base;
+}
+
+static void tegra_atomic_state_clear(struct drm_atomic_state *state)
+{
+	struct tegra_atomic_state *tegra = to_tegra_atomic_state(state);
+
+	drm_atomic_state_default_clear(state);
+	tegra->clk_disp = NULL;
+	tegra->dc = NULL;
+	tegra->rate = 0;
+}
+
+static void tegra_atomic_state_free(struct drm_atomic_state *state)
+{
+	drm_atomic_state_default_release(state);
+	kfree(state);
+}
+
 static const struct drm_mode_config_funcs tegra_drm_mode_config_funcs = {
 	.fb_create = tegra_fb_create,
 #ifdef CONFIG_DRM_FBDEV_EMULATION
@@ -41,11 +70,32 @@  static const struct drm_mode_config_funcs tegra_drm_mode_config_funcs = {
 #endif
 	.atomic_check = drm_atomic_helper_check,
 	.atomic_commit = drm_atomic_helper_commit,
+	.atomic_state_alloc = tegra_atomic_state_alloc,
+	.atomic_state_clear = tegra_atomic_state_clear,
+	.atomic_state_free = tegra_atomic_state_free,
 };
 
+static void tegra_atomic_commit_tail(struct drm_atomic_state *old_state)
+{
+	struct drm_device *drm = old_state->dev;
+	struct tegra_drm *tegra = drm->dev_private;
+
+	if (tegra->hub) {
+		drm_atomic_helper_commit_modeset_disables(drm, old_state);
+		tegra_display_hub_atomic_commit(drm, old_state);
+		drm_atomic_helper_commit_planes(drm, old_state, 0);
+		drm_atomic_helper_commit_modeset_enables(drm, old_state);
+		drm_atomic_helper_commit_hw_done(old_state);
+		drm_atomic_helper_wait_for_vblanks(drm, old_state);
+		drm_atomic_helper_cleanup_planes(drm, old_state);
+	} else {
+		drm_atomic_helper_commit_tail_rpm(old_state);
+	}
+}
+
 static const struct drm_mode_config_helper_funcs
 tegra_drm_mode_config_helpers = {
-	.atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
+	.atomic_commit_tail = tegra_atomic_commit_tail,
 };
 
 static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
@@ -120,6 +170,12 @@  static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
 	if (err < 0)
 		goto fbdev;
 
+	if (tegra->hub) {
+		err = tegra_display_hub_prepare(tegra->hub);
+		if (err < 0)
+			goto device;
+	}
+
 	/*
 	 * We don't use the drm_irq_install() helpers provided by the DRM
 	 * core, so we need to set this manually in order to allow the
@@ -132,16 +188,19 @@  static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
 
 	err = drm_vblank_init(drm, drm->mode_config.num_crtc);
 	if (err < 0)
-		goto device;
+		goto hub;
 
 	drm_mode_config_reset(drm);
 
 	err = tegra_drm_fb_init(drm);
 	if (err < 0)
-		goto device;
+		goto hub;
 
 	return 0;
 
+hub:
+	if (tegra->hub)
+		tegra_display_hub_cleanup(tegra->hub);
 device:
 	host1x_device_exit(device);
 fbdev:
@@ -1286,6 +1345,7 @@  static const struct of_device_id host1x_drm_subdevs[] = {
 	{ .compatible = "nvidia,tegra210-sor", },
 	{ .compatible = "nvidia,tegra210-sor1", },
 	{ .compatible = "nvidia,tegra210-vic", },
+	{ .compatible = "nvidia,tegra186-display", },
 	{ .compatible = "nvidia,tegra186-vic", },
 	{ /* sentinel */ }
 };
@@ -1301,6 +1361,7 @@  static struct host1x_driver host1x_drm_driver = {
 };
 
 static struct platform_driver * const drivers[] = {
+	&tegra_display_hub_driver,
 	&tegra_dc_driver,
 	&tegra_hdmi_driver,
 	&tegra_dsi_driver,
diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h
index 0da21cc207d2..4073bad48f14 100644
--- a/drivers/gpu/drm/tegra/drm.h
+++ b/drivers/gpu/drm/tegra/drm.h
@@ -16,6 +16,7 @@ 
 #include <linux/of_gpio.h>
 
 #include <drm/drmP.h>
+#include <drm/drm_atomic.h>
 #include <drm/drm_crtc_helper.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_encoder.h>
@@ -23,6 +24,7 @@ 
 #include <drm/drm_fixed.h>
 
 #include "gem.h"
+#include "hub.h"
 #include "trace.h"
 
 struct reset_control;
@@ -40,6 +42,20 @@  struct tegra_fbdev {
 };
 #endif
 
+struct tegra_atomic_state {
+	struct drm_atomic_state base;
+
+	struct clk *clk_disp;
+	struct tegra_dc *dc;
+	unsigned long rate;
+};
+
+static inline struct tegra_atomic_state *
+to_tegra_atomic_state(struct drm_atomic_state *state)
+{
+	return container_of(state, struct tegra_atomic_state, base);
+}
+
 struct tegra_drm {
 	struct drm_device *drm;
 
@@ -63,6 +79,8 @@  struct tegra_drm {
 
 	unsigned int pitch_align;
 
+	struct tegra_display_hub *hub;
+
 	struct drm_atomic_state *state;
 };
 
@@ -188,6 +206,7 @@  void tegra_fbdev_restore_mode(struct tegra_fbdev *fbdev);
 void tegra_fb_output_poll_changed(struct drm_device *drm);
 #endif
 
+extern struct platform_driver tegra_display_hub_driver;
 extern struct platform_driver tegra_dc_driver;
 extern struct platform_driver tegra_hdmi_driver;
 extern struct platform_driver tegra_dsi_driver;
diff --git a/drivers/gpu/drm/tegra/hub.c b/drivers/gpu/drm/tegra/hub.c
new file mode 100644
index 000000000000..dae54acd3c5b
--- /dev/null
+++ b/drivers/gpu/drm/tegra/hub.c
@@ -0,0 +1,808 @@ 
+/*
+ * Copyright (C) 2017 NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/host1x.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "drm.h"
+#include "dc.h"
+#include "plane.h"
+
+static const u32 tegra_shared_plane_formats[] = {
+	DRM_FORMAT_XBGR8888,
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_RGB565,
+};
+
+static inline unsigned int tegra_plane_offset(struct tegra_shared_plane *plane,
+					      unsigned int offset)
+{
+	struct tegra_plane *p = &plane->base;
+
+	if (offset >= 0x500 && offset <= 0x581) {
+		offset = 0x000 + (offset - 0x500);
+		return p->offset + offset;
+	}
+
+	if (offset >= 0x700 && offset <= 0x73c) {
+		offset = 0x180 + (offset - 0x700);
+		return p->offset + offset;
+	}
+
+	if (offset >= 0x800 && offset <= 0x83e) {
+		offset = 0x1c0 + (offset - 0x800);
+		return p->offset + offset;
+	}
+
+	dev_WARN(plane->dc->dev, "invalid offset: %x\n", offset);
+
+	return p->offset + offset;
+}
+
+static inline u32 tegra_plane_readl(struct tegra_shared_plane *plane,
+				    unsigned int offset)
+{
+	return tegra_dc_readl(plane->dc, tegra_plane_offset(plane, offset));
+}
+
+static inline void tegra_plane_writel(struct tegra_shared_plane *plane,
+				      u32 value, unsigned int offset)
+{
+	tegra_dc_writel(plane->dc, value, tegra_plane_offset(plane, offset));
+}
+
+static int tegra_windowgroup_enable(struct tegra_windowgroup *wgrp)
+{
+	mutex_lock(&wgrp->lock);
+
+	if (wgrp->usecount == 0) {
+		pm_runtime_get_sync(wgrp->parent);
+		reset_control_deassert(wgrp->rst);
+	}
+
+	wgrp->usecount++;
+	mutex_unlock(&wgrp->lock);
+
+	return 0;
+}
+
+static void tegra_windowgroup_disable(struct tegra_windowgroup *wgrp)
+{
+	int err;
+
+	mutex_lock(&wgrp->lock);
+
+	if (wgrp->usecount == 1) {
+		err = reset_control_assert(wgrp->rst);
+		if (err < 0) {
+			pr_err("failed to assert reset for window group %u\n",
+			       wgrp->index);
+		}
+
+		pm_runtime_put(wgrp->parent);
+	}
+
+	wgrp->usecount--;
+	mutex_unlock(&wgrp->lock);
+}
+
+int tegra_display_hub_prepare(struct tegra_display_hub *hub)
+{
+	unsigned int i;
+
+	/*
+	 * XXX Enabling/disabling windowgroups needs to happen when the owner
+	 * display controller is disabled. There's currently no good point at
+	 * which this could be executed, so unconditionally enable all window
+	 * groups for now.
+	 */
+	for (i = 0; i < hub->soc->num_wgrps; i++) {
+		struct tegra_windowgroup *wgrp = &hub->wgrps[i];
+
+		tegra_windowgroup_enable(wgrp);
+	}
+
+	return 0;
+}
+
+void tegra_display_hub_cleanup(struct tegra_display_hub *hub)
+{
+	unsigned int i;
+
+	/*
+	 * XXX Remove this once window groups can be more fine-grainedly
+	 * enabled and disabled.
+	 */
+	for (i = 0; i < hub->soc->num_wgrps; i++) {
+		struct tegra_windowgroup *wgrp = &hub->wgrps[i];
+
+		tegra_windowgroup_disable(wgrp);
+	}
+}
+
+static void tegra_shared_plane_update(struct tegra_shared_plane *plane)
+{
+	struct tegra_dc *dc = plane->dc;
+	unsigned long timeout;
+	u32 mask, value;
+
+	mask = COMMON_UPDATE | WIN_A_UPDATE << plane->base.index;
+	tegra_dc_writel(dc, mask, DC_CMD_STATE_CONTROL);
+
+	timeout = jiffies + msecs_to_jiffies(1000);
+
+	while (time_before(jiffies, timeout)) {
+		value = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
+		if ((value & mask) == 0)
+			break;
+
+		usleep_range(100, 400);
+	}
+}
+
+static void tegra_shared_plane_activate(struct tegra_shared_plane *plane)
+{
+	struct tegra_dc *dc = plane->dc;
+	unsigned long timeout;
+	u32 mask, value;
+
+	mask = COMMON_ACTREQ | WIN_A_ACT_REQ << plane->base.index;
+	tegra_dc_writel(dc, mask, DC_CMD_STATE_CONTROL);
+
+	timeout = jiffies + msecs_to_jiffies(1000);
+
+	while (time_before(jiffies, timeout)) {
+		value = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
+		if ((value & mask) == 0)
+			break;
+
+		usleep_range(100, 400);
+	}
+}
+
+static unsigned int
+tegra_shared_plane_get_owner(struct tegra_shared_plane *plane,
+			     struct tegra_dc *dc)
+{
+	unsigned int offset =
+		tegra_plane_offset(plane, DC_WIN_CORE_WINDOWGROUP_SET_CONTROL);
+
+	return tegra_dc_readl(dc, offset) & OWNER_MASK;
+}
+
+static bool tegra_dc_owns_shared_plane(struct tegra_dc *dc,
+				       struct tegra_shared_plane *plane)
+{
+	struct device *dev = dc->dev;
+
+	if (tegra_shared_plane_get_owner(plane, dc) == dc->pipe) {
+		if (plane->dc == dc)
+			return true;
+
+		dev_WARN(dev, "head %u owns window %u but is not attached\n",
+			 dc->pipe, plane->base.index);
+	}
+
+	return false;
+}
+
+static int tegra_shared_plane_set_owner(struct tegra_shared_plane *plane,
+					struct tegra_dc *new)
+{
+	unsigned int offset =
+		tegra_plane_offset(plane, DC_WIN_CORE_WINDOWGROUP_SET_CONTROL);
+	struct tegra_dc *old = plane->dc, *dc = new ? new : old;
+	struct device *dev = new ? new->dev : old->dev;
+	unsigned int owner, index = plane->base.index;
+	u32 value;
+
+	value = tegra_dc_readl(dc, offset);
+	owner = value & OWNER_MASK;
+
+	if (new && (owner != OWNER_MASK && owner != new->pipe)) {
+		dev_WARN(dev, "window %u owned by head %u\n", index, owner);
+		return -EBUSY;
+	}
+
+	/*
+	 * This seems to happen whenever the head has been disabled with one
+	 * or more windows being active. This is harmless because we'll just
+	 * reassign the window to the new head anyway.
+	 */
+	if (old && owner == OWNER_MASK)
+		dev_dbg(dev, "window %u not owned by head %u but %u\n", index,
+			 old->pipe, owner);
+
+	value &= ~OWNER_MASK;
+
+	if (new)
+		value |= OWNER(new->pipe);
+	else
+		value |= OWNER_MASK;
+
+	tegra_dc_writel(dc, value, offset);
+
+	plane->dc = new;
+
+	return 0;
+}
+
+static void tegra_dc_assign_shared_plane(struct tegra_dc *dc,
+					 struct tegra_shared_plane *plane)
+{
+	u32 value;
+	int err;
+
+	if (!tegra_dc_owns_shared_plane(dc, plane)) {
+		err = tegra_shared_plane_set_owner(plane, dc);
+		if (err < 0)
+			return;
+	}
+
+	value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_LINEBUF_CONFIG);
+	value |= MODE_FOUR_LINES;
+	tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_LINEBUF_CONFIG);
+
+	value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_FETCH_METER);
+	value = SLOTS(1);
+	tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_FETCH_METER);
+
+	/* disable watermark */
+	value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLA);
+	value &= ~LATENCY_CTL_MODE_ENABLE;
+	tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLA);
+
+	value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLB);
+	value |= WATERMARK_MASK;
+	tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_LATENCY_CTLB);
+
+	/* pipe meter */
+	value = tegra_plane_readl(plane, DC_WIN_CORE_PRECOMP_WGRP_PIPE_METER);
+	value = PIPE_METER_INT(0) | PIPE_METER_FRAC(0);
+	tegra_plane_writel(plane, value, DC_WIN_CORE_PRECOMP_WGRP_PIPE_METER);
+
+	/* mempool entries */
+	value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_WGRP_POOL_CONFIG);
+	value = MEMPOOL_ENTRIES(0x331);
+	tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_WGRP_POOL_CONFIG);
+
+	value = tegra_plane_readl(plane, DC_WIN_CORE_IHUB_THREAD_GROUP);
+	value &= ~THREAD_NUM_MASK;
+	value |= THREAD_NUM(plane->base.index);
+	value |= THREAD_GROUP_ENABLE;
+	tegra_plane_writel(plane, value, DC_WIN_CORE_IHUB_THREAD_GROUP);
+
+	tegra_shared_plane_update(plane);
+	tegra_shared_plane_activate(plane);
+}
+
+static void tegra_dc_remove_shared_plane(struct tegra_dc *dc,
+					 struct tegra_shared_plane *plane)
+{
+	tegra_shared_plane_set_owner(plane, NULL);
+}
+
+static int tegra_shared_plane_atomic_check(struct drm_plane *plane,
+					   struct drm_plane_state *state)
+{
+	struct tegra_plane_state *plane_state = to_tegra_plane_state(state);
+	struct tegra_shared_plane *tegra = to_tegra_shared_plane(plane);
+	struct tegra_bo_tiling *tiling = &plane_state->tiling;
+	struct tegra_dc *dc = to_tegra_dc(state->crtc);
+	int err;
+
+	/* no need for further checks if the plane is being disabled */
+	if (!state->crtc || !state->fb)
+		return 0;
+
+	err = tegra_plane_format(state->fb->format->format,
+				 &plane_state->format,
+				 &plane_state->swap);
+	if (err < 0)
+		return err;
+
+	err = tegra_fb_get_tiling(state->fb, tiling);
+	if (err < 0)
+		return err;
+
+	if (tiling->mode == TEGRA_BO_TILING_MODE_BLOCK &&
+	    !dc->soc->supports_block_linear) {
+		DRM_ERROR("hardware doesn't support block linear mode\n");
+		return -EINVAL;
+	}
+
+	/*
+	 * Tegra doesn't support different strides for U and V planes so we
+	 * error out if the user tries to display a framebuffer with such a
+	 * configuration.
+	 */
+	if (state->fb->format->num_planes > 2) {
+		if (state->fb->pitches[2] != state->fb->pitches[1]) {
+			DRM_ERROR("unsupported UV-plane configuration\n");
+			return -EINVAL;
+		}
+	}
+
+	/* XXX scaling is not yet supported, add a check here */
+
+	err = tegra_plane_state_add(&tegra->base, state);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static void tegra_shared_plane_atomic_disable(struct drm_plane *plane,
+					      struct drm_plane_state *old_state)
+{
+	struct tegra_shared_plane *p = to_tegra_shared_plane(plane);
+	struct tegra_dc *dc = to_tegra_dc(old_state->crtc);
+	u32 value;
+
+	/* rien ne va plus */
+	if (!old_state || !old_state->crtc)
+		return;
+
+	/*
+	 * XXX Legacy helpers seem to sometimes call ->atomic_disable() even
+	 * on planes that are already disabled. Make sure we fallback to the
+	 * head for this particular state instead of crashing.
+	 */
+	if (WARN_ON(p->dc == NULL))
+		p->dc = dc;
+
+	pm_runtime_get_sync(dc->dev);
+
+	value = tegra_plane_readl(p, DC_WIN_WIN_OPTIONS);
+	value &= ~WIN_ENABLE;
+	tegra_plane_writel(p, value, DC_WIN_WIN_OPTIONS);
+
+	tegra_dc_remove_shared_plane(dc, p);
+
+	pm_runtime_put(dc->dev);
+}
+
+static void tegra_shared_plane_atomic_update(struct drm_plane *plane,
+					     struct drm_plane_state *old_state)
+{
+	struct tegra_plane_state *state = to_tegra_plane_state(plane->state);
+	struct tegra_shared_plane *p = to_tegra_shared_plane(plane);
+	struct tegra_dc *dc = to_tegra_dc(plane->state->crtc);
+	struct drm_framebuffer *fb = plane->state->fb;
+	struct tegra_bo *bo;
+	dma_addr_t base;
+	u32 value;
+
+	/* rien ne va plus */
+	if (!plane->state->crtc || !plane->state->fb)
+		return;
+
+	if (!plane->state->visible) {
+		tegra_shared_plane_atomic_disable(plane, old_state);
+		return;
+	}
+
+	pm_runtime_get_sync(dc->dev);
+
+	tegra_dc_assign_shared_plane(dc, p);
+
+	tegra_plane_writel(p, VCOUNTER, DC_WIN_CORE_ACT_CONTROL);
+
+	/* blending */
+	value = BLEND_FACTOR_DST_ALPHA_ZERO | BLEND_FACTOR_SRC_ALPHA_K2 |
+		BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_SRC |
+		BLEND_FACTOR_SRC_COLOR_K1_TIMES_SRC;
+	tegra_plane_writel(p, value, DC_WIN_BLEND_MATCH_SELECT);
+
+	value = BLEND_FACTOR_DST_ALPHA_ZERO | BLEND_FACTOR_SRC_ALPHA_K2 |
+		BLEND_FACTOR_DST_COLOR_NEG_K1_TIMES_SRC |
+		BLEND_FACTOR_SRC_COLOR_K1_TIMES_SRC;
+	tegra_plane_writel(p, value, DC_WIN_BLEND_NOMATCH_SELECT);
+
+	value = K2(255) | K1(255) | WINDOW_LAYER_DEPTH(p->base.depth);
+	tegra_plane_writel(p, value, DC_WIN_BLEND_LAYER_CONTROL);
+
+	/* bypass scaling */
+	value = HORIZONTAL_TAPS_5 | VERTICAL_TAPS_5;
+	tegra_plane_writel(p, value, DC_WIN_WINDOWGROUP_SET_CONTROL_INPUT_SCALER);
+
+	value = INPUT_SCALER_VBYPASS | INPUT_SCALER_HBYPASS;
+	tegra_plane_writel(p, value, DC_WIN_WINDOWGROUP_SET_INPUT_SCALER_USAGE);
+
+	/* disable compression */
+	tegra_plane_writel(p, 0, DC_WINBUF_CDE_CONTROL);
+
+	bo = tegra_fb_get_plane(fb, 0);
+	base = bo->paddr;
+
+	tegra_plane_writel(p, state->format, DC_WIN_COLOR_DEPTH);
+	tegra_plane_writel(p, 0, DC_WIN_PRECOMP_WGRP_PARAMS);
+
+	value = V_POSITION(plane->state->crtc_y) |
+		H_POSITION(plane->state->crtc_x);
+	tegra_plane_writel(p, value, DC_WIN_POSITION);
+
+	value = V_SIZE(plane->state->crtc_h) | H_SIZE(plane->state->crtc_w);
+	tegra_plane_writel(p, value, DC_WIN_SIZE);
+
+	value = WIN_ENABLE | COLOR_EXPAND;
+	tegra_plane_writel(p, value, DC_WIN_WIN_OPTIONS);
+
+	value = V_SIZE(plane->state->crtc_h) | H_SIZE(plane->state->crtc_w);
+	tegra_plane_writel(p, value, DC_WIN_CROPPED_SIZE);
+
+	tegra_plane_writel(p, upper_32_bits(base), DC_WINBUF_START_ADDR_HI);
+	tegra_plane_writel(p, lower_32_bits(base), DC_WINBUF_START_ADDR);
+
+	value = PITCH(fb->pitches[0]);
+	tegra_plane_writel(p, value, DC_WIN_PLANAR_STORAGE);
+
+	value = CLAMP_BEFORE_BLEND | DEGAMMA_SRGB | INPUT_RANGE_FULL;
+	tegra_plane_writel(p, value, DC_WIN_SET_PARAMS);
+
+	value = OFFSET_X(plane->state->src_y >> 16) |
+		OFFSET_Y(plane->state->src_x >> 16);
+	tegra_plane_writel(p, value, DC_WINBUF_CROPPED_POINT);
+
+	if (dc->soc->supports_block_linear) {
+		unsigned long height = state->tiling.value;
+
+		/* XXX */
+		switch (state->tiling.mode) {
+		case TEGRA_BO_TILING_MODE_PITCH:
+			value = DC_WINBUF_SURFACE_KIND_BLOCK_HEIGHT(0) |
+				DC_WINBUF_SURFACE_KIND_PITCH;
+			break;
+
+		/* XXX not supported on Tegra186 and later */
+		case TEGRA_BO_TILING_MODE_TILED:
+			value = DC_WINBUF_SURFACE_KIND_TILED;
+			break;
+
+		case TEGRA_BO_TILING_MODE_BLOCK:
+			value = DC_WINBUF_SURFACE_KIND_BLOCK_HEIGHT(height) |
+				DC_WINBUF_SURFACE_KIND_BLOCK;
+			break;
+		}
+
+		tegra_plane_writel(p, value, DC_WINBUF_SURFACE_KIND);
+	}
+
+	/* disable gamut CSC */
+	value = tegra_plane_readl(p, DC_WIN_WINDOW_SET_CONTROL);
+	value &= ~CONTROL_CSC_ENABLE;
+	tegra_plane_writel(p, value, DC_WIN_WINDOW_SET_CONTROL);
+
+	pm_runtime_put(dc->dev);
+}
+
+static int tegra_shared_plane_prepare(struct drm_plane *plane,
+				      struct drm_plane_state *state)
+{
+	struct dma_fence *fence;
+	struct tegra_bo *bo;
+
+	if ((plane->state->fb == state->fb) || !state->fb)
+		return 0;
+
+	/* XXX handle multi-planar formats */
+	bo = tegra_fb_get_plane(state->fb, 0);
+	fence = reservation_object_get_excl_rcu(bo->resv);
+	drm_atomic_set_fence_for_plane(state, fence);
+
+	return 0;
+}
+
+static const struct drm_plane_helper_funcs tegra_shared_plane_helper_funcs = {
+	.atomic_check = tegra_shared_plane_atomic_check,
+	.atomic_update = tegra_shared_plane_atomic_update,
+	.atomic_disable = tegra_shared_plane_atomic_disable,
+	.prepare_fb = tegra_shared_plane_prepare,
+};
+
+struct drm_plane *tegra_shared_plane_create(struct drm_device *drm,
+					    struct tegra_dc *dc,
+					    unsigned int wgrp,
+					    unsigned int index)
+{
+	enum drm_plane_type type = DRM_PLANE_TYPE_OVERLAY;
+	struct tegra_drm *tegra = drm->dev_private;
+	struct tegra_display_hub *hub = tegra->hub;
+	/* planes can be assigned to arbitrary CRTCs */
+	unsigned int possible_crtcs = 0x7;
+	struct tegra_shared_plane *plane;
+	unsigned int num_formats;
+	struct drm_plane *p;
+	const u32 *formats;
+	int err;
+
+	plane = kzalloc(sizeof(*plane), GFP_KERNEL);
+	if (!plane)
+		return ERR_PTR(-ENOMEM);
+
+	plane->base.offset = 0x0a00 + 0x0300 * index;
+	plane->base.index = index;
+	plane->base.depth = 0;
+
+	plane->wgrp = &hub->wgrps[wgrp];
+	plane->wgrp->parent = dc->dev;
+
+	p = &plane->base.base;
+
+	num_formats = ARRAY_SIZE(tegra_shared_plane_formats);
+	formats = tegra_shared_plane_formats;
+
+	err = drm_universal_plane_init(drm, p, possible_crtcs,
+				       &tegra_plane_funcs, formats,
+				       num_formats, NULL, type, NULL);
+	if (err < 0) {
+		kfree(plane);
+		return ERR_PTR(err);
+	}
+
+	drm_plane_helper_add(p, &tegra_shared_plane_helper_funcs);
+
+	return p;
+}
+
+static void tegra_display_hub_update(struct tegra_dc *dc)
+{
+	u32 value;
+
+	pm_runtime_get_sync(dc->dev);
+
+	value = tegra_dc_readl(dc, DC_CMD_IHUB_COMMON_MISC_CTL);
+	value &= ~LATENCY_EVENT;
+	tegra_dc_writel(dc, value, DC_CMD_IHUB_COMMON_MISC_CTL);
+
+	value = tegra_dc_readl(dc, DC_DISP_IHUB_COMMON_DISPLAY_FETCH_METER);
+	value = CURS_SLOTS(1) | WGRP_SLOTS(1);
+	tegra_dc_writel(dc, value, DC_DISP_IHUB_COMMON_DISPLAY_FETCH_METER);
+
+	tegra_dc_writel(dc, COMMON_UPDATE, DC_CMD_STATE_CONTROL);
+	tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
+	tegra_dc_writel(dc, COMMON_ACTREQ, DC_CMD_STATE_CONTROL);
+	tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
+
+	pm_runtime_put(dc->dev);
+}
+
+void tegra_display_hub_atomic_commit(struct drm_device *drm,
+				     struct drm_atomic_state *state)
+{
+	struct tegra_atomic_state *s = to_tegra_atomic_state(state);
+	struct tegra_drm *tegra = drm->dev_private;
+	struct tegra_display_hub *hub = tegra->hub;
+	struct device *dev = hub->client.dev;
+	int err;
+
+	if (s->clk_disp) {
+		err = clk_set_rate(s->clk_disp, s->rate);
+		if (err < 0)
+			dev_err(dev, "failed to set rate of %pC to %lu Hz\n",
+				s->clk_disp, s->rate);
+
+		err = clk_set_parent(hub->clk_disp, s->clk_disp);
+		if (err < 0)
+			dev_err(dev, "failed to set parent of %pC to %pC: %d\n",
+				hub->clk_disp, s->clk_disp, err);
+	}
+
+	if (s->dc)
+		tegra_display_hub_update(s->dc);
+}
+
+static int tegra_display_hub_init(struct host1x_client *client)
+{
+	struct tegra_display_hub *hub = to_tegra_display_hub(client);
+	struct drm_device *drm = dev_get_drvdata(client->parent);
+	struct tegra_drm *tegra = drm->dev_private;
+
+	tegra->hub = hub;
+
+	return 0;
+}
+
+static int tegra_display_hub_exit(struct host1x_client *client)
+{
+	struct drm_device *drm = dev_get_drvdata(client->parent);
+	struct tegra_drm *tegra = drm->dev_private;
+
+	tegra->hub = NULL;
+
+	return 0;
+}
+
+static const struct host1x_client_ops tegra_display_hub_ops = {
+	.init = tegra_display_hub_init,
+	.exit = tegra_display_hub_exit,
+};
+
+static int tegra_display_hub_probe(struct platform_device *pdev)
+{
+	struct tegra_display_hub *hub;
+	unsigned int i;
+	int err;
+
+	hub = devm_kzalloc(&pdev->dev, sizeof(*hub), GFP_KERNEL);
+	if (!hub)
+		return -ENOMEM;
+
+	hub->soc = of_device_get_match_data(&pdev->dev);
+
+	hub->clk_disp = devm_clk_get(&pdev->dev, "disp");
+	if (IS_ERR(hub->clk_disp)) {
+		err = PTR_ERR(hub->clk_disp);
+		return err;
+	}
+
+	hub->clk_dsc = devm_clk_get(&pdev->dev, "dsc");
+	if (IS_ERR(hub->clk_dsc)) {
+		err = PTR_ERR(hub->clk_dsc);
+		return err;
+	}
+
+	hub->clk_hub = devm_clk_get(&pdev->dev, "hub");
+	if (IS_ERR(hub->clk_hub)) {
+		err = PTR_ERR(hub->clk_hub);
+		return err;
+	}
+
+	hub->rst = devm_reset_control_get(&pdev->dev, "misc");
+	if (IS_ERR(hub->rst)) {
+		err = PTR_ERR(hub->rst);
+		return err;
+	}
+
+	hub->wgrps = devm_kcalloc(&pdev->dev, hub->soc->num_wgrps,
+				  sizeof(*hub->wgrps), GFP_KERNEL);
+	if (!hub->wgrps)
+		return -ENOMEM;
+
+	for (i = 0; i < hub->soc->num_wgrps; i++) {
+		struct tegra_windowgroup *wgrp = &hub->wgrps[i];
+		char id[8];
+
+		snprintf(id, sizeof(id), "wgrp%u", i);
+		mutex_init(&wgrp->lock);
+		wgrp->usecount = 0;
+		wgrp->index = i;
+
+		wgrp->rst = devm_reset_control_get(&pdev->dev, id);
+		if (IS_ERR(wgrp->rst))
+			return PTR_ERR(wgrp->rst);
+
+		err = reset_control_assert(wgrp->rst);
+		if (err < 0)
+			return err;
+	}
+
+	/* XXX: enable clock across reset? */
+	err = reset_control_assert(hub->rst);
+	if (err < 0)
+		return err;
+
+	platform_set_drvdata(pdev, hub);
+	pm_runtime_enable(&pdev->dev);
+
+	INIT_LIST_HEAD(&hub->client.list);
+	hub->client.ops = &tegra_display_hub_ops;
+	hub->client.dev = &pdev->dev;
+
+	err = host1x_client_register(&hub->client);
+	if (err < 0)
+		dev_err(&pdev->dev, "failed to register host1x client: %d\n",
+			err);
+
+	return err;
+}
+
+static int tegra_display_hub_remove(struct platform_device *pdev)
+{
+	struct tegra_display_hub *hub = platform_get_drvdata(pdev);
+	int err;
+
+	err = host1x_client_unregister(&hub->client);
+	if (err < 0) {
+		dev_err(&pdev->dev, "failed to unregister host1x client: %d\n",
+			err);
+	}
+
+	pm_runtime_disable(&pdev->dev);
+
+	return err;
+}
+
+static int tegra_display_hub_suspend(struct device *dev)
+{
+	struct tegra_display_hub *hub = dev_get_drvdata(dev);
+	int err;
+
+	err = reset_control_assert(hub->rst);
+	if (err < 0)
+		return err;
+
+	clk_disable_unprepare(hub->clk_hub);
+	clk_disable_unprepare(hub->clk_dsc);
+	clk_disable_unprepare(hub->clk_disp);
+
+	return 0;
+}
+
+static int tegra_display_hub_resume(struct device *dev)
+{
+	struct tegra_display_hub *hub = dev_get_drvdata(dev);
+	int err;
+
+	err = clk_prepare_enable(hub->clk_disp);
+	if (err < 0)
+		return err;
+
+	err = clk_prepare_enable(hub->clk_dsc);
+	if (err < 0)
+		goto disable_disp;
+
+	err = clk_prepare_enable(hub->clk_hub);
+	if (err < 0)
+		goto disable_dsc;
+
+	err = reset_control_deassert(hub->rst);
+	if (err < 0)
+		goto disable_hub;
+
+	return 0;
+
+disable_hub:
+	clk_disable_unprepare(hub->clk_hub);
+disable_dsc:
+	clk_disable_unprepare(hub->clk_dsc);
+disable_disp:
+	clk_disable_unprepare(hub->clk_disp);
+	return err;
+}
+
+static const struct dev_pm_ops tegra_display_hub_pm_ops = {
+	SET_RUNTIME_PM_OPS(tegra_display_hub_suspend,
+			   tegra_display_hub_resume, NULL)
+};
+
+static const struct tegra_display_hub_soc tegra186_display_hub = {
+	.num_wgrps = 6,
+};
+
+static const struct of_device_id tegra_display_hub_of_match[] = {
+	{
+		.compatible = "nvidia,tegra186-display",
+		.data = &tegra186_display_hub
+	}, {
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(of, tegra_display_hub_of_match);
+
+struct platform_driver tegra_display_hub_driver = {
+	.driver = {
+		.name = "tegra-display-hub",
+		.of_match_table = tegra_display_hub_of_match,
+		.pm = &tegra_display_hub_pm_ops,
+	},
+	.probe = tegra_display_hub_probe,
+	.remove = tegra_display_hub_remove,
+};
diff --git a/drivers/gpu/drm/tegra/hub.h b/drivers/gpu/drm/tegra/hub.h
new file mode 100644
index 000000000000..965c333736b0
--- /dev/null
+++ b/drivers/gpu/drm/tegra/hub.h
@@ -0,0 +1,82 @@ 
+/*
+ * Copyright (C) 2017 NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef TEGRA_HUB_H
+#define TEGRA_HUB_H 1
+
+#include <drm/drmP.h>
+#include <drm/drm_plane.h>
+
+#include "plane.h"
+
+struct tegra_dc;
+
+struct tegra_windowgroup {
+	unsigned int usecount;
+	struct mutex lock;
+
+	unsigned int index;
+	struct device *parent;
+	struct reset_control *rst;
+};
+
+struct tegra_shared_plane {
+	struct tegra_plane base;
+	struct tegra_windowgroup *wgrp;
+	struct tegra_dc *dc;
+};
+
+static inline struct tegra_shared_plane *
+to_tegra_shared_plane(struct drm_plane *plane)
+{
+	return container_of(plane, struct tegra_shared_plane, base.base);
+}
+
+struct tegra_display_hub_soc {
+	unsigned int num_wgrps;
+};
+
+struct tegra_display_hub {
+	struct host1x_client client;
+	struct clk *clk_disp;
+	struct clk *clk_dsc;
+	struct clk *clk_hub;
+	struct reset_control *rst;
+
+	const struct tegra_display_hub_soc *soc;
+	struct tegra_windowgroup *wgrps;
+};
+
+static inline struct tegra_display_hub *
+to_tegra_display_hub(struct host1x_client *client)
+{
+	return container_of(client, struct tegra_display_hub, client);
+}
+
+struct tegra_dc;
+struct tegra_plane;
+
+int tegra_display_hub_prepare(struct tegra_display_hub *hub);
+void tegra_display_hub_cleanup(struct tegra_display_hub *hub);
+
+struct drm_plane *tegra_shared_plane_create(struct drm_device *drm,
+					    struct tegra_dc *dc,
+					    unsigned int wgrp,
+					    unsigned int index);
+
+void tegra_display_hub_atomic_commit(struct drm_device *drm,
+				     struct drm_atomic_state *state);
+
+#define DC_CMD_IHUB_COMMON_MISC_CTL 0x068
+#define  LATENCY_EVENT (1 << 3)
+
+#define DC_DISP_IHUB_COMMON_DISPLAY_FETCH_METER 0x451
+#define  CURS_SLOTS(x) (((x) & 0xff) << 8)
+#define  WGRP_SLOTS(x) (((x) & 0xff) << 0)
+
+#endif /* TEGRA_HUB_H */
diff --git a/drivers/gpu/drm/tegra/plane.h b/drivers/gpu/drm/tegra/plane.h
index 8237b885acd7..fc7566f630fa 100644
--- a/drivers/gpu/drm/tegra/plane.h
+++ b/drivers/gpu/drm/tegra/plane.h
@@ -15,7 +15,9 @@  struct tegra_bo;
 
 struct tegra_plane {
 	struct drm_plane base;
+	unsigned int offset;
 	unsigned int index;
+	unsigned int depth;
 };
 
 struct tegra_cursor {