drm/tegra: dc: Register debugfs in ->late_register()
authorThierry Reding <treding@nvidia.com>
Wed, 8 Nov 2017 12:40:54 +0000 (13:40 +0100)
committerThierry Reding <treding@nvidia.com>
Wed, 13 Dec 2017 12:42:07 +0000 (13:42 +0100)
The ->late_register() and ->early_unregister() callbacks are called at
the right time to make sure userspace only accesses interfaces when it
should. Move debugfs registration and unregistration to these callback
functions to avoid potential races with userspace.

Signed-off-by: Thierry Reding <treding@nvidia.com>
drivers/gpu/drm/tegra/dc.c
drivers/gpu/drm/tegra/dc.h

index 27135748240184112e1517f5613b6d76abc47404..29777604df392032429126a6af6727b4ccf3ed65 100644 (file)
@@ -959,37 +959,370 @@ static void tegra_crtc_atomic_destroy_state(struct drm_crtc *crtc,
        kfree(state);
 }
 
-static u32 tegra_dc_get_vblank_counter(struct drm_crtc *crtc)
-{
-       struct tegra_dc *dc = to_tegra_dc(crtc);
-
-       if (dc->syncpt)
-               return host1x_syncpt_read(dc->syncpt);
+#define DEBUGFS_REG32(_name) { .name = #_name, .offset = _name }
 
-       /* fallback to software emulated VBLANK counter */
-       return drm_crtc_vblank_count(&dc->base);
-}
+static const struct debugfs_reg32 tegra_dc_regs[] = {
+       DEBUGFS_REG32(DC_CMD_GENERAL_INCR_SYNCPT),
+       DEBUGFS_REG32(DC_CMD_GENERAL_INCR_SYNCPT_CNTRL),
+       DEBUGFS_REG32(DC_CMD_GENERAL_INCR_SYNCPT_ERROR),
+       DEBUGFS_REG32(DC_CMD_WIN_A_INCR_SYNCPT),
+       DEBUGFS_REG32(DC_CMD_WIN_A_INCR_SYNCPT_CNTRL),
+       DEBUGFS_REG32(DC_CMD_WIN_A_INCR_SYNCPT_ERROR),
+       DEBUGFS_REG32(DC_CMD_WIN_B_INCR_SYNCPT),
+       DEBUGFS_REG32(DC_CMD_WIN_B_INCR_SYNCPT_CNTRL),
+       DEBUGFS_REG32(DC_CMD_WIN_B_INCR_SYNCPT_ERROR),
+       DEBUGFS_REG32(DC_CMD_WIN_C_INCR_SYNCPT),
+       DEBUGFS_REG32(DC_CMD_WIN_C_INCR_SYNCPT_CNTRL),
+       DEBUGFS_REG32(DC_CMD_WIN_C_INCR_SYNCPT_ERROR),
+       DEBUGFS_REG32(DC_CMD_CONT_SYNCPT_VSYNC),
+       DEBUGFS_REG32(DC_CMD_DISPLAY_COMMAND_OPTION0),
+       DEBUGFS_REG32(DC_CMD_DISPLAY_COMMAND),
+       DEBUGFS_REG32(DC_CMD_SIGNAL_RAISE),
+       DEBUGFS_REG32(DC_CMD_DISPLAY_POWER_CONTROL),
+       DEBUGFS_REG32(DC_CMD_INT_STATUS),
+       DEBUGFS_REG32(DC_CMD_INT_MASK),
+       DEBUGFS_REG32(DC_CMD_INT_ENABLE),
+       DEBUGFS_REG32(DC_CMD_INT_TYPE),
+       DEBUGFS_REG32(DC_CMD_INT_POLARITY),
+       DEBUGFS_REG32(DC_CMD_SIGNAL_RAISE1),
+       DEBUGFS_REG32(DC_CMD_SIGNAL_RAISE2),
+       DEBUGFS_REG32(DC_CMD_SIGNAL_RAISE3),
+       DEBUGFS_REG32(DC_CMD_STATE_ACCESS),
+       DEBUGFS_REG32(DC_CMD_STATE_CONTROL),
+       DEBUGFS_REG32(DC_CMD_DISPLAY_WINDOW_HEADER),
+       DEBUGFS_REG32(DC_CMD_REG_ACT_CONTROL),
+       DEBUGFS_REG32(DC_COM_CRC_CONTROL),
+       DEBUGFS_REG32(DC_COM_CRC_CHECKSUM),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_ENABLE(0)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_ENABLE(1)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_ENABLE(2)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_ENABLE(3)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_POLARITY(0)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_POLARITY(1)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_POLARITY(2)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_POLARITY(3)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_DATA(0)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_DATA(1)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_DATA(2)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_DATA(3)),
+       DEBUGFS_REG32(DC_COM_PIN_INPUT_ENABLE(0)),
+       DEBUGFS_REG32(DC_COM_PIN_INPUT_ENABLE(1)),
+       DEBUGFS_REG32(DC_COM_PIN_INPUT_ENABLE(2)),
+       DEBUGFS_REG32(DC_COM_PIN_INPUT_ENABLE(3)),
+       DEBUGFS_REG32(DC_COM_PIN_INPUT_DATA(0)),
+       DEBUGFS_REG32(DC_COM_PIN_INPUT_DATA(1)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(0)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(1)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(2)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(3)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(4)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(5)),
+       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(6)),
+       DEBUGFS_REG32(DC_COM_PIN_MISC_CONTROL),
+       DEBUGFS_REG32(DC_COM_PIN_PM0_CONTROL),
+       DEBUGFS_REG32(DC_COM_PIN_PM0_DUTY_CYCLE),
+       DEBUGFS_REG32(DC_COM_PIN_PM1_CONTROL),
+       DEBUGFS_REG32(DC_COM_PIN_PM1_DUTY_CYCLE),
+       DEBUGFS_REG32(DC_COM_SPI_CONTROL),
+       DEBUGFS_REG32(DC_COM_SPI_START_BYTE),
+       DEBUGFS_REG32(DC_COM_HSPI_WRITE_DATA_AB),
+       DEBUGFS_REG32(DC_COM_HSPI_WRITE_DATA_CD),
+       DEBUGFS_REG32(DC_COM_HSPI_CS_DC),
+       DEBUGFS_REG32(DC_COM_SCRATCH_REGISTER_A),
+       DEBUGFS_REG32(DC_COM_SCRATCH_REGISTER_B),
+       DEBUGFS_REG32(DC_COM_GPIO_CTRL),
+       DEBUGFS_REG32(DC_COM_GPIO_DEBOUNCE_COUNTER),
+       DEBUGFS_REG32(DC_COM_CRC_CHECKSUM_LATCHED),
+       DEBUGFS_REG32(DC_DISP_DISP_SIGNAL_OPTIONS0),
+       DEBUGFS_REG32(DC_DISP_DISP_SIGNAL_OPTIONS1),
+       DEBUGFS_REG32(DC_DISP_DISP_WIN_OPTIONS),
+       DEBUGFS_REG32(DC_DISP_DISP_MEM_HIGH_PRIORITY),
+       DEBUGFS_REG32(DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER),
+       DEBUGFS_REG32(DC_DISP_DISP_TIMING_OPTIONS),
+       DEBUGFS_REG32(DC_DISP_REF_TO_SYNC),
+       DEBUGFS_REG32(DC_DISP_SYNC_WIDTH),
+       DEBUGFS_REG32(DC_DISP_BACK_PORCH),
+       DEBUGFS_REG32(DC_DISP_ACTIVE),
+       DEBUGFS_REG32(DC_DISP_FRONT_PORCH),
+       DEBUGFS_REG32(DC_DISP_H_PULSE0_CONTROL),
+       DEBUGFS_REG32(DC_DISP_H_PULSE0_POSITION_A),
+       DEBUGFS_REG32(DC_DISP_H_PULSE0_POSITION_B),
+       DEBUGFS_REG32(DC_DISP_H_PULSE0_POSITION_C),
+       DEBUGFS_REG32(DC_DISP_H_PULSE0_POSITION_D),
+       DEBUGFS_REG32(DC_DISP_H_PULSE1_CONTROL),
+       DEBUGFS_REG32(DC_DISP_H_PULSE1_POSITION_A),
+       DEBUGFS_REG32(DC_DISP_H_PULSE1_POSITION_B),
+       DEBUGFS_REG32(DC_DISP_H_PULSE1_POSITION_C),
+       DEBUGFS_REG32(DC_DISP_H_PULSE1_POSITION_D),
+       DEBUGFS_REG32(DC_DISP_H_PULSE2_CONTROL),
+       DEBUGFS_REG32(DC_DISP_H_PULSE2_POSITION_A),
+       DEBUGFS_REG32(DC_DISP_H_PULSE2_POSITION_B),
+       DEBUGFS_REG32(DC_DISP_H_PULSE2_POSITION_C),
+       DEBUGFS_REG32(DC_DISP_H_PULSE2_POSITION_D),
+       DEBUGFS_REG32(DC_DISP_V_PULSE0_CONTROL),
+       DEBUGFS_REG32(DC_DISP_V_PULSE0_POSITION_A),
+       DEBUGFS_REG32(DC_DISP_V_PULSE0_POSITION_B),
+       DEBUGFS_REG32(DC_DISP_V_PULSE0_POSITION_C),
+       DEBUGFS_REG32(DC_DISP_V_PULSE1_CONTROL),
+       DEBUGFS_REG32(DC_DISP_V_PULSE1_POSITION_A),
+       DEBUGFS_REG32(DC_DISP_V_PULSE1_POSITION_B),
+       DEBUGFS_REG32(DC_DISP_V_PULSE1_POSITION_C),
+       DEBUGFS_REG32(DC_DISP_V_PULSE2_CONTROL),
+       DEBUGFS_REG32(DC_DISP_V_PULSE2_POSITION_A),
+       DEBUGFS_REG32(DC_DISP_V_PULSE3_CONTROL),
+       DEBUGFS_REG32(DC_DISP_V_PULSE3_POSITION_A),
+       DEBUGFS_REG32(DC_DISP_M0_CONTROL),
+       DEBUGFS_REG32(DC_DISP_M1_CONTROL),
+       DEBUGFS_REG32(DC_DISP_DI_CONTROL),
+       DEBUGFS_REG32(DC_DISP_PP_CONTROL),
+       DEBUGFS_REG32(DC_DISP_PP_SELECT_A),
+       DEBUGFS_REG32(DC_DISP_PP_SELECT_B),
+       DEBUGFS_REG32(DC_DISP_PP_SELECT_C),
+       DEBUGFS_REG32(DC_DISP_PP_SELECT_D),
+       DEBUGFS_REG32(DC_DISP_DISP_CLOCK_CONTROL),
+       DEBUGFS_REG32(DC_DISP_DISP_INTERFACE_CONTROL),
+       DEBUGFS_REG32(DC_DISP_DISP_COLOR_CONTROL),
+       DEBUGFS_REG32(DC_DISP_SHIFT_CLOCK_OPTIONS),
+       DEBUGFS_REG32(DC_DISP_DATA_ENABLE_OPTIONS),
+       DEBUGFS_REG32(DC_DISP_SERIAL_INTERFACE_OPTIONS),
+       DEBUGFS_REG32(DC_DISP_LCD_SPI_OPTIONS),
+       DEBUGFS_REG32(DC_DISP_BORDER_COLOR),
+       DEBUGFS_REG32(DC_DISP_COLOR_KEY0_LOWER),
+       DEBUGFS_REG32(DC_DISP_COLOR_KEY0_UPPER),
+       DEBUGFS_REG32(DC_DISP_COLOR_KEY1_LOWER),
+       DEBUGFS_REG32(DC_DISP_COLOR_KEY1_UPPER),
+       DEBUGFS_REG32(DC_DISP_CURSOR_FOREGROUND),
+       DEBUGFS_REG32(DC_DISP_CURSOR_BACKGROUND),
+       DEBUGFS_REG32(DC_DISP_CURSOR_START_ADDR),
+       DEBUGFS_REG32(DC_DISP_CURSOR_START_ADDR_NS),
+       DEBUGFS_REG32(DC_DISP_CURSOR_POSITION),
+       DEBUGFS_REG32(DC_DISP_CURSOR_POSITION_NS),
+       DEBUGFS_REG32(DC_DISP_INIT_SEQ_CONTROL),
+       DEBUGFS_REG32(DC_DISP_SPI_INIT_SEQ_DATA_A),
+       DEBUGFS_REG32(DC_DISP_SPI_INIT_SEQ_DATA_B),
+       DEBUGFS_REG32(DC_DISP_SPI_INIT_SEQ_DATA_C),
+       DEBUGFS_REG32(DC_DISP_SPI_INIT_SEQ_DATA_D),
+       DEBUGFS_REG32(DC_DISP_DC_MCCIF_FIFOCTRL),
+       DEBUGFS_REG32(DC_DISP_MCCIF_DISPLAY0A_HYST),
+       DEBUGFS_REG32(DC_DISP_MCCIF_DISPLAY0B_HYST),
+       DEBUGFS_REG32(DC_DISP_MCCIF_DISPLAY1A_HYST),
+       DEBUGFS_REG32(DC_DISP_MCCIF_DISPLAY1B_HYST),
+       DEBUGFS_REG32(DC_DISP_DAC_CRT_CTRL),
+       DEBUGFS_REG32(DC_DISP_DISP_MISC_CONTROL),
+       DEBUGFS_REG32(DC_DISP_SD_CONTROL),
+       DEBUGFS_REG32(DC_DISP_SD_CSC_COEFF),
+       DEBUGFS_REG32(DC_DISP_SD_LUT(0)),
+       DEBUGFS_REG32(DC_DISP_SD_LUT(1)),
+       DEBUGFS_REG32(DC_DISP_SD_LUT(2)),
+       DEBUGFS_REG32(DC_DISP_SD_LUT(3)),
+       DEBUGFS_REG32(DC_DISP_SD_LUT(4)),
+       DEBUGFS_REG32(DC_DISP_SD_LUT(5)),
+       DEBUGFS_REG32(DC_DISP_SD_LUT(6)),
+       DEBUGFS_REG32(DC_DISP_SD_LUT(7)),
+       DEBUGFS_REG32(DC_DISP_SD_LUT(8)),
+       DEBUGFS_REG32(DC_DISP_SD_FLICKER_CONTROL),
+       DEBUGFS_REG32(DC_DISP_DC_PIXEL_COUNT),
+       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(0)),
+       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(1)),
+       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(2)),
+       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(3)),
+       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(4)),
+       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(5)),
+       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(6)),
+       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(7)),
+       DEBUGFS_REG32(DC_DISP_SD_BL_TF(0)),
+       DEBUGFS_REG32(DC_DISP_SD_BL_TF(1)),
+       DEBUGFS_REG32(DC_DISP_SD_BL_TF(2)),
+       DEBUGFS_REG32(DC_DISP_SD_BL_TF(3)),
+       DEBUGFS_REG32(DC_DISP_SD_BL_CONTROL),
+       DEBUGFS_REG32(DC_DISP_SD_HW_K_VALUES),
+       DEBUGFS_REG32(DC_DISP_SD_MAN_K_VALUES),
+       DEBUGFS_REG32(DC_DISP_CURSOR_START_ADDR_HI),
+       DEBUGFS_REG32(DC_DISP_BLEND_CURSOR_CONTROL),
+       DEBUGFS_REG32(DC_WIN_WIN_OPTIONS),
+       DEBUGFS_REG32(DC_WIN_BYTE_SWAP),
+       DEBUGFS_REG32(DC_WIN_BUFFER_CONTROL),
+       DEBUGFS_REG32(DC_WIN_COLOR_DEPTH),
+       DEBUGFS_REG32(DC_WIN_POSITION),
+       DEBUGFS_REG32(DC_WIN_SIZE),
+       DEBUGFS_REG32(DC_WIN_PRESCALED_SIZE),
+       DEBUGFS_REG32(DC_WIN_H_INITIAL_DDA),
+       DEBUGFS_REG32(DC_WIN_V_INITIAL_DDA),
+       DEBUGFS_REG32(DC_WIN_DDA_INC),
+       DEBUGFS_REG32(DC_WIN_LINE_STRIDE),
+       DEBUGFS_REG32(DC_WIN_BUF_STRIDE),
+       DEBUGFS_REG32(DC_WIN_UV_BUF_STRIDE),
+       DEBUGFS_REG32(DC_WIN_BUFFER_ADDR_MODE),
+       DEBUGFS_REG32(DC_WIN_DV_CONTROL),
+       DEBUGFS_REG32(DC_WIN_BLEND_NOKEY),
+       DEBUGFS_REG32(DC_WIN_BLEND_1WIN),
+       DEBUGFS_REG32(DC_WIN_BLEND_2WIN_X),
+       DEBUGFS_REG32(DC_WIN_BLEND_2WIN_Y),
+       DEBUGFS_REG32(DC_WIN_BLEND_3WIN_XY),
+       DEBUGFS_REG32(DC_WIN_HP_FETCH_CONTROL),
+       DEBUGFS_REG32(DC_WINBUF_START_ADDR),
+       DEBUGFS_REG32(DC_WINBUF_START_ADDR_NS),
+       DEBUGFS_REG32(DC_WINBUF_START_ADDR_U),
+       DEBUGFS_REG32(DC_WINBUF_START_ADDR_U_NS),
+       DEBUGFS_REG32(DC_WINBUF_START_ADDR_V),
+       DEBUGFS_REG32(DC_WINBUF_START_ADDR_V_NS),
+       DEBUGFS_REG32(DC_WINBUF_ADDR_H_OFFSET),
+       DEBUGFS_REG32(DC_WINBUF_ADDR_H_OFFSET_NS),
+       DEBUGFS_REG32(DC_WINBUF_ADDR_V_OFFSET),
+       DEBUGFS_REG32(DC_WINBUF_ADDR_V_OFFSET_NS),
+       DEBUGFS_REG32(DC_WINBUF_UFLOW_STATUS),
+       DEBUGFS_REG32(DC_WINBUF_AD_UFLOW_STATUS),
+       DEBUGFS_REG32(DC_WINBUF_BD_UFLOW_STATUS),
+       DEBUGFS_REG32(DC_WINBUF_CD_UFLOW_STATUS),
+};
 
-static int tegra_dc_enable_vblank(struct drm_crtc *crtc)
+static int tegra_dc_show_regs(struct seq_file *s, void *data)
 {
-       struct tegra_dc *dc = to_tegra_dc(crtc);
-       unsigned long value, flags;
+       struct drm_info_node *node = s->private;
+       struct tegra_dc *dc = node->info_ent->data;
+       unsigned int i;
+       int err = 0;
 
-       spin_lock_irqsave(&dc->lock, flags);
+       drm_modeset_lock(&dc->base.mutex, NULL);
 
-       value = tegra_dc_readl(dc, DC_CMD_INT_MASK);
-       value |= VBLANK_INT;
-       tegra_dc_writel(dc, value, DC_CMD_INT_MASK);
+       if (!dc->base.state->active) {
+               err = -EBUSY;
+               goto unlock;
+       }
 
-       spin_unlock_irqrestore(&dc->lock, flags);
+       for (i = 0; i < ARRAY_SIZE(tegra_dc_regs); i++) {
+               unsigned int offset = tegra_dc_regs[i].offset;
 
-       return 0;
+               seq_printf(s, "%-40s %#05x %08x\n", tegra_dc_regs[i].name,
+                          offset, tegra_dc_readl(dc, offset));
+       }
+
+unlock:
+       drm_modeset_unlock(&dc->base.mutex);
+       return err;
 }
 
-static void tegra_dc_disable_vblank(struct drm_crtc *crtc)
+static int tegra_dc_show_crc(struct seq_file *s, void *data)
 {
-       struct tegra_dc *dc = to_tegra_dc(crtc);
-       unsigned long value, flags;
+       struct drm_info_node *node = s->private;
+       struct tegra_dc *dc = node->info_ent->data;
+       int err = 0;
+       u32 value;
+
+       drm_modeset_lock(&dc->base.mutex, NULL);
+
+       if (!dc->base.state->active) {
+               err = -EBUSY;
+               goto unlock;
+       }
+
+       value = DC_COM_CRC_CONTROL_ACTIVE_DATA | DC_COM_CRC_CONTROL_ENABLE;
+       tegra_dc_writel(dc, value, DC_COM_CRC_CONTROL);
+       tegra_dc_commit(dc);
+
+       drm_crtc_wait_one_vblank(&dc->base);
+       drm_crtc_wait_one_vblank(&dc->base);
+
+       value = tegra_dc_readl(dc, DC_COM_CRC_CHECKSUM);
+       seq_printf(s, "%08x\n", value);
+
+       tegra_dc_writel(dc, 0, DC_COM_CRC_CONTROL);
+
+unlock:
+       drm_modeset_unlock(&dc->base.mutex);
+       return err;
+}
+
+static int tegra_dc_show_stats(struct seq_file *s, void *data)
+{
+       struct drm_info_node *node = s->private;
+       struct tegra_dc *dc = node->info_ent->data;
+
+       seq_printf(s, "frames: %lu\n", dc->stats.frames);
+       seq_printf(s, "vblank: %lu\n", dc->stats.vblank);
+       seq_printf(s, "underflow: %lu\n", dc->stats.underflow);
+       seq_printf(s, "overflow: %lu\n", dc->stats.overflow);
+
+       return 0;
+}
+
+static struct drm_info_list debugfs_files[] = {
+       { "regs", tegra_dc_show_regs, 0, NULL },
+       { "crc", tegra_dc_show_crc, 0, NULL },
+       { "stats", tegra_dc_show_stats, 0, NULL },
+};
+
+static int tegra_dc_late_register(struct drm_crtc *crtc)
+{
+       unsigned int i, count = ARRAY_SIZE(debugfs_files);
+       struct drm_minor *minor = crtc->dev->primary;
+       struct dentry *root = crtc->debugfs_entry;
+       struct tegra_dc *dc = to_tegra_dc(crtc);
+       int err;
+
+       dc->debugfs_files = kmemdup(debugfs_files, sizeof(debugfs_files),
+                                   GFP_KERNEL);
+       if (!dc->debugfs_files)
+               return -ENOMEM;
+
+       for (i = 0; i < count; i++)
+               dc->debugfs_files[i].data = dc;
+
+       err = drm_debugfs_create_files(dc->debugfs_files, count, root, minor);
+       if (err < 0)
+               goto free;
+
+       return 0;
+
+free:
+       kfree(dc->debugfs_files);
+       dc->debugfs_files = NULL;
+
+       return err;
+}
+
+static void tegra_dc_early_unregister(struct drm_crtc *crtc)
+{
+       unsigned int count = ARRAY_SIZE(debugfs_files);
+       struct drm_minor *minor = crtc->dev->primary;
+       struct tegra_dc *dc = to_tegra_dc(crtc);
+
+       drm_debugfs_remove_files(dc->debugfs_files, count, minor);
+       kfree(dc->debugfs_files);
+       dc->debugfs_files = NULL;
+}
+
+static u32 tegra_dc_get_vblank_counter(struct drm_crtc *crtc)
+{
+       struct tegra_dc *dc = to_tegra_dc(crtc);
+
+       if (dc->syncpt)
+               return host1x_syncpt_read(dc->syncpt);
+
+       /* fallback to software emulated VBLANK counter */
+       return drm_crtc_vblank_count(&dc->base);
+}
+
+static int tegra_dc_enable_vblank(struct drm_crtc *crtc)
+{
+       struct tegra_dc *dc = to_tegra_dc(crtc);
+       unsigned long value, flags;
+
+       spin_lock_irqsave(&dc->lock, flags);
+
+       value = tegra_dc_readl(dc, DC_CMD_INT_MASK);
+       value |= VBLANK_INT;
+       tegra_dc_writel(dc, value, DC_CMD_INT_MASK);
+
+       spin_unlock_irqrestore(&dc->lock, flags);
+
+       return 0;
+}
+
+static void tegra_dc_disable_vblank(struct drm_crtc *crtc)
+{
+       struct tegra_dc *dc = to_tegra_dc(crtc);
+       unsigned long value, flags;
 
        spin_lock_irqsave(&dc->lock, flags);
 
@@ -1007,6 +1340,8 @@ static const struct drm_crtc_funcs tegra_crtc_funcs = {
        .reset = tegra_crtc_reset,
        .atomic_duplicate_state = tegra_crtc_atomic_duplicate_state,
        .atomic_destroy_state = tegra_crtc_atomic_destroy_state,
+       .late_register = tegra_dc_late_register,
+       .early_unregister = tegra_dc_early_unregister,
        .get_vblank_counter = tegra_dc_get_vblank_counter,
        .enable_vblank = tegra_dc_enable_vblank,
        .disable_vblank = tegra_dc_disable_vblank,
@@ -1148,590 +1483,239 @@ static int tegra_dc_wait_idle(struct tegra_dc *dc, unsigned long timeout)
 
 static void tegra_crtc_atomic_disable(struct drm_crtc *crtc,
                                      struct drm_crtc_state *old_state)
-{
-       struct tegra_dc *dc = to_tegra_dc(crtc);
-       u32 value;
-
-       if (!tegra_dc_idle(dc)) {
-               tegra_dc_stop(dc);
-
-               /*
-                * Ignore the return value, there isn't anything useful to do
-                * in case this fails.
-                */
-               tegra_dc_wait_idle(dc, 100);
-       }
-
-       /*
-        * This should really be part of the RGB encoder driver, but clearing
-        * these bits has the side-effect of stopping the display controller.
-        * When that happens no VBLANK interrupts will be raised. At the same
-        * time the encoder is disabled before the display controller, so the
-        * above code is always going to timeout waiting for the controller
-        * to go idle.
-        *
-        * Given the close coupling between the RGB encoder and the display
-        * controller doing it here is still kind of okay. None of the other
-        * encoder drivers require these bits to be cleared.
-        *
-        * XXX: Perhaps given that the display controller is switched off at
-        * this point anyway maybe clearing these bits isn't even useful for
-        * the RGB encoder?
-        */
-       if (dc->rgb) {
-               value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL);
-               value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
-                          PW4_ENABLE | PM0_ENABLE | PM1_ENABLE);
-               tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
-       }
-
-       tegra_dc_stats_reset(&dc->stats);
-       drm_crtc_vblank_off(crtc);
-
-       pm_runtime_put_sync(dc->dev);
-}
-
-static void tegra_crtc_atomic_enable(struct drm_crtc *crtc,
-                                    struct drm_crtc_state *old_state)
-{
-       struct drm_display_mode *mode = &crtc->state->adjusted_mode;
-       struct tegra_dc_state *state = to_dc_state(crtc->state);
-       struct tegra_dc *dc = to_tegra_dc(crtc);
-       u32 value;
-
-       pm_runtime_get_sync(dc->dev);
-
-       /* initialize display controller */
-       if (dc->syncpt) {
-               u32 syncpt = host1x_syncpt_id(dc->syncpt);
-
-               value = SYNCPT_CNTRL_NO_STALL;
-               tegra_dc_writel(dc, value, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL);
-
-               value = SYNCPT_VSYNC_ENABLE | syncpt;
-               tegra_dc_writel(dc, value, DC_CMD_CONT_SYNCPT_VSYNC);
-       }
-
-       value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
-               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
-       tegra_dc_writel(dc, value, DC_CMD_INT_TYPE);
-
-       value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
-               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
-       tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY);
-
-       /* initialize timer */
-       value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) |
-               WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20);
-       tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY);
-
-       value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(1) |
-               WINDOW_B_THRESHOLD(1) | WINDOW_C_THRESHOLD(1);
-       tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER);
-
-       value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
-               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
-       tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE);
-
-       value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
-               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
-       tegra_dc_writel(dc, value, DC_CMD_INT_MASK);
-
-       if (dc->soc->supports_border_color)
-               tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR);
-
-       /* apply PLL and pixel clock changes */
-       tegra_dc_commit_state(dc, state);
-
-       /* program display mode */
-       tegra_dc_set_timings(dc, mode);
-
-       /* interlacing isn't supported yet, so disable it */
-       if (dc->soc->supports_interlacing) {
-               value = tegra_dc_readl(dc, DC_DISP_INTERLACE_CONTROL);
-               value &= ~INTERLACE_ENABLE;
-               tegra_dc_writel(dc, value, DC_DISP_INTERLACE_CONTROL);
-       }
-
-       value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
-       value &= ~DISP_CTRL_MODE_MASK;
-       value |= DISP_CTRL_MODE_C_DISPLAY;
-       tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
-
-       value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL);
-       value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
-                PW4_ENABLE | PM0_ENABLE | PM1_ENABLE;
-       tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
-
-       tegra_dc_commit(dc);
-
-       drm_crtc_vblank_on(crtc);
-}
-
-static int tegra_crtc_atomic_check(struct drm_crtc *crtc,
-                                  struct drm_crtc_state *state)
-{
-       return 0;
-}
-
-static void tegra_crtc_atomic_begin(struct drm_crtc *crtc,
-                                   struct drm_crtc_state *old_crtc_state)
-{
-       struct tegra_dc *dc = to_tegra_dc(crtc);
-
-       if (crtc->state->event) {
-               crtc->state->event->pipe = drm_crtc_index(crtc);
-
-               WARN_ON(drm_crtc_vblank_get(crtc) != 0);
-
-               dc->event = crtc->state->event;
-               crtc->state->event = NULL;
-       }
-}
-
-static void tegra_crtc_atomic_flush(struct drm_crtc *crtc,
-                                   struct drm_crtc_state *old_crtc_state)
-{
-       struct tegra_dc_state *state = to_dc_state(crtc->state);
-       struct tegra_dc *dc = to_tegra_dc(crtc);
-
-       tegra_dc_writel(dc, state->planes << 8, DC_CMD_STATE_CONTROL);
-       tegra_dc_writel(dc, state->planes, DC_CMD_STATE_CONTROL);
-}
-
-static const struct drm_crtc_helper_funcs tegra_crtc_helper_funcs = {
-       .atomic_check = tegra_crtc_atomic_check,
-       .atomic_begin = tegra_crtc_atomic_begin,
-       .atomic_flush = tegra_crtc_atomic_flush,
-       .atomic_enable = tegra_crtc_atomic_enable,
-       .atomic_disable = tegra_crtc_atomic_disable,
-};
-
-static void tegra_dc_finish_page_flip(struct tegra_dc *dc)
-{
-       struct drm_device *drm = dc->base.dev;
-       struct drm_crtc *crtc = &dc->base;
-       unsigned long flags, base;
-       struct tegra_bo *bo;
-
-       spin_lock_irqsave(&drm->event_lock, flags);
-
-       if (!dc->event) {
-               spin_unlock_irqrestore(&drm->event_lock, flags);
-               return;
-       }
-
-       bo = tegra_fb_get_plane(crtc->primary->fb, 0);
-
-       spin_lock(&dc->lock);
-
-       /* check if new start address has been latched */
-       tegra_dc_writel(dc, WINDOW_A_SELECT, DC_CMD_DISPLAY_WINDOW_HEADER);
-       tegra_dc_writel(dc, READ_MUX, DC_CMD_STATE_ACCESS);
-       base = tegra_dc_readl(dc, DC_WINBUF_START_ADDR);
-       tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS);
-
-       spin_unlock(&dc->lock);
-
-       if (base == bo->paddr + crtc->primary->fb->offsets[0]) {
-               drm_crtc_send_vblank_event(crtc, dc->event);
-               drm_crtc_vblank_put(crtc);
-               dc->event = NULL;
-       }
-
-       spin_unlock_irqrestore(&drm->event_lock, flags);
-}
-
-static irqreturn_t tegra_dc_irq(int irq, void *data)
-{
-       struct tegra_dc *dc = data;
-       unsigned long status;
-
-       status = tegra_dc_readl(dc, DC_CMD_INT_STATUS);
-       tegra_dc_writel(dc, status, DC_CMD_INT_STATUS);
-
-       if (status & FRAME_END_INT) {
-               /*
-               dev_dbg(dc->dev, "%s(): frame end\n", __func__);
-               */
-               dc->stats.frames++;
-       }
-
-       if (status & VBLANK_INT) {
-               /*
-               dev_dbg(dc->dev, "%s(): vertical blank\n", __func__);
-               */
-               drm_crtc_handle_vblank(&dc->base);
-               tegra_dc_finish_page_flip(dc);
-               dc->stats.vblank++;
-       }
-
-       if (status & (WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT)) {
-               /*
-               dev_dbg(dc->dev, "%s(): underflow\n", __func__);
-               */
-               dc->stats.underflow++;
-       }
-
-       if (status & (WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT)) {
-               /*
-               dev_dbg(dc->dev, "%s(): overflow\n", __func__);
-               */
-               dc->stats.overflow++;
-       }
-
-       return IRQ_HANDLED;
-}
-
-#define DEBUGFS_REG32(_name) { .name = #_name, .offset = _name }
-
-static const struct debugfs_reg32 tegra_dc_regs[] = {
-       DEBUGFS_REG32(DC_CMD_GENERAL_INCR_SYNCPT),
-       DEBUGFS_REG32(DC_CMD_GENERAL_INCR_SYNCPT_CNTRL),
-       DEBUGFS_REG32(DC_CMD_GENERAL_INCR_SYNCPT_ERROR),
-       DEBUGFS_REG32(DC_CMD_WIN_A_INCR_SYNCPT),
-       DEBUGFS_REG32(DC_CMD_WIN_A_INCR_SYNCPT_CNTRL),
-       DEBUGFS_REG32(DC_CMD_WIN_A_INCR_SYNCPT_ERROR),
-       DEBUGFS_REG32(DC_CMD_WIN_B_INCR_SYNCPT),
-       DEBUGFS_REG32(DC_CMD_WIN_B_INCR_SYNCPT_CNTRL),
-       DEBUGFS_REG32(DC_CMD_WIN_B_INCR_SYNCPT_ERROR),
-       DEBUGFS_REG32(DC_CMD_WIN_C_INCR_SYNCPT),
-       DEBUGFS_REG32(DC_CMD_WIN_C_INCR_SYNCPT_CNTRL),
-       DEBUGFS_REG32(DC_CMD_WIN_C_INCR_SYNCPT_ERROR),
-       DEBUGFS_REG32(DC_CMD_CONT_SYNCPT_VSYNC),
-       DEBUGFS_REG32(DC_CMD_DISPLAY_COMMAND_OPTION0),
-       DEBUGFS_REG32(DC_CMD_DISPLAY_COMMAND),
-       DEBUGFS_REG32(DC_CMD_SIGNAL_RAISE),
-       DEBUGFS_REG32(DC_CMD_DISPLAY_POWER_CONTROL),
-       DEBUGFS_REG32(DC_CMD_INT_STATUS),
-       DEBUGFS_REG32(DC_CMD_INT_MASK),
-       DEBUGFS_REG32(DC_CMD_INT_ENABLE),
-       DEBUGFS_REG32(DC_CMD_INT_TYPE),
-       DEBUGFS_REG32(DC_CMD_INT_POLARITY),
-       DEBUGFS_REG32(DC_CMD_SIGNAL_RAISE1),
-       DEBUGFS_REG32(DC_CMD_SIGNAL_RAISE2),
-       DEBUGFS_REG32(DC_CMD_SIGNAL_RAISE3),
-       DEBUGFS_REG32(DC_CMD_STATE_ACCESS),
-       DEBUGFS_REG32(DC_CMD_STATE_CONTROL),
-       DEBUGFS_REG32(DC_CMD_DISPLAY_WINDOW_HEADER),
-       DEBUGFS_REG32(DC_CMD_REG_ACT_CONTROL),
-       DEBUGFS_REG32(DC_COM_CRC_CONTROL),
-       DEBUGFS_REG32(DC_COM_CRC_CHECKSUM),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_ENABLE(0)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_ENABLE(1)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_ENABLE(2)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_ENABLE(3)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_POLARITY(0)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_POLARITY(1)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_POLARITY(2)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_POLARITY(3)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_DATA(0)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_DATA(1)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_DATA(2)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_DATA(3)),
-       DEBUGFS_REG32(DC_COM_PIN_INPUT_ENABLE(0)),
-       DEBUGFS_REG32(DC_COM_PIN_INPUT_ENABLE(1)),
-       DEBUGFS_REG32(DC_COM_PIN_INPUT_ENABLE(2)),
-       DEBUGFS_REG32(DC_COM_PIN_INPUT_ENABLE(3)),
-       DEBUGFS_REG32(DC_COM_PIN_INPUT_DATA(0)),
-       DEBUGFS_REG32(DC_COM_PIN_INPUT_DATA(1)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(0)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(1)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(2)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(3)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(4)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(5)),
-       DEBUGFS_REG32(DC_COM_PIN_OUTPUT_SELECT(6)),
-       DEBUGFS_REG32(DC_COM_PIN_MISC_CONTROL),
-       DEBUGFS_REG32(DC_COM_PIN_PM0_CONTROL),
-       DEBUGFS_REG32(DC_COM_PIN_PM0_DUTY_CYCLE),
-       DEBUGFS_REG32(DC_COM_PIN_PM1_CONTROL),
-       DEBUGFS_REG32(DC_COM_PIN_PM1_DUTY_CYCLE),
-       DEBUGFS_REG32(DC_COM_SPI_CONTROL),
-       DEBUGFS_REG32(DC_COM_SPI_START_BYTE),
-       DEBUGFS_REG32(DC_COM_HSPI_WRITE_DATA_AB),
-       DEBUGFS_REG32(DC_COM_HSPI_WRITE_DATA_CD),
-       DEBUGFS_REG32(DC_COM_HSPI_CS_DC),
-       DEBUGFS_REG32(DC_COM_SCRATCH_REGISTER_A),
-       DEBUGFS_REG32(DC_COM_SCRATCH_REGISTER_B),
-       DEBUGFS_REG32(DC_COM_GPIO_CTRL),
-       DEBUGFS_REG32(DC_COM_GPIO_DEBOUNCE_COUNTER),
-       DEBUGFS_REG32(DC_COM_CRC_CHECKSUM_LATCHED),
-       DEBUGFS_REG32(DC_DISP_DISP_SIGNAL_OPTIONS0),
-       DEBUGFS_REG32(DC_DISP_DISP_SIGNAL_OPTIONS1),
-       DEBUGFS_REG32(DC_DISP_DISP_WIN_OPTIONS),
-       DEBUGFS_REG32(DC_DISP_DISP_MEM_HIGH_PRIORITY),
-       DEBUGFS_REG32(DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER),
-       DEBUGFS_REG32(DC_DISP_DISP_TIMING_OPTIONS),
-       DEBUGFS_REG32(DC_DISP_REF_TO_SYNC),
-       DEBUGFS_REG32(DC_DISP_SYNC_WIDTH),
-       DEBUGFS_REG32(DC_DISP_BACK_PORCH),
-       DEBUGFS_REG32(DC_DISP_ACTIVE),
-       DEBUGFS_REG32(DC_DISP_FRONT_PORCH),
-       DEBUGFS_REG32(DC_DISP_H_PULSE0_CONTROL),
-       DEBUGFS_REG32(DC_DISP_H_PULSE0_POSITION_A),
-       DEBUGFS_REG32(DC_DISP_H_PULSE0_POSITION_B),
-       DEBUGFS_REG32(DC_DISP_H_PULSE0_POSITION_C),
-       DEBUGFS_REG32(DC_DISP_H_PULSE0_POSITION_D),
-       DEBUGFS_REG32(DC_DISP_H_PULSE1_CONTROL),
-       DEBUGFS_REG32(DC_DISP_H_PULSE1_POSITION_A),
-       DEBUGFS_REG32(DC_DISP_H_PULSE1_POSITION_B),
-       DEBUGFS_REG32(DC_DISP_H_PULSE1_POSITION_C),
-       DEBUGFS_REG32(DC_DISP_H_PULSE1_POSITION_D),
-       DEBUGFS_REG32(DC_DISP_H_PULSE2_CONTROL),
-       DEBUGFS_REG32(DC_DISP_H_PULSE2_POSITION_A),
-       DEBUGFS_REG32(DC_DISP_H_PULSE2_POSITION_B),
-       DEBUGFS_REG32(DC_DISP_H_PULSE2_POSITION_C),
-       DEBUGFS_REG32(DC_DISP_H_PULSE2_POSITION_D),
-       DEBUGFS_REG32(DC_DISP_V_PULSE0_CONTROL),
-       DEBUGFS_REG32(DC_DISP_V_PULSE0_POSITION_A),
-       DEBUGFS_REG32(DC_DISP_V_PULSE0_POSITION_B),
-       DEBUGFS_REG32(DC_DISP_V_PULSE0_POSITION_C),
-       DEBUGFS_REG32(DC_DISP_V_PULSE1_CONTROL),
-       DEBUGFS_REG32(DC_DISP_V_PULSE1_POSITION_A),
-       DEBUGFS_REG32(DC_DISP_V_PULSE1_POSITION_B),
-       DEBUGFS_REG32(DC_DISP_V_PULSE1_POSITION_C),
-       DEBUGFS_REG32(DC_DISP_V_PULSE2_CONTROL),
-       DEBUGFS_REG32(DC_DISP_V_PULSE2_POSITION_A),
-       DEBUGFS_REG32(DC_DISP_V_PULSE3_CONTROL),
-       DEBUGFS_REG32(DC_DISP_V_PULSE3_POSITION_A),
-       DEBUGFS_REG32(DC_DISP_M0_CONTROL),
-       DEBUGFS_REG32(DC_DISP_M1_CONTROL),
-       DEBUGFS_REG32(DC_DISP_DI_CONTROL),
-       DEBUGFS_REG32(DC_DISP_PP_CONTROL),
-       DEBUGFS_REG32(DC_DISP_PP_SELECT_A),
-       DEBUGFS_REG32(DC_DISP_PP_SELECT_B),
-       DEBUGFS_REG32(DC_DISP_PP_SELECT_C),
-       DEBUGFS_REG32(DC_DISP_PP_SELECT_D),
-       DEBUGFS_REG32(DC_DISP_DISP_CLOCK_CONTROL),
-       DEBUGFS_REG32(DC_DISP_DISP_INTERFACE_CONTROL),
-       DEBUGFS_REG32(DC_DISP_DISP_COLOR_CONTROL),
-       DEBUGFS_REG32(DC_DISP_SHIFT_CLOCK_OPTIONS),
-       DEBUGFS_REG32(DC_DISP_DATA_ENABLE_OPTIONS),
-       DEBUGFS_REG32(DC_DISP_SERIAL_INTERFACE_OPTIONS),
-       DEBUGFS_REG32(DC_DISP_LCD_SPI_OPTIONS),
-       DEBUGFS_REG32(DC_DISP_BORDER_COLOR),
-       DEBUGFS_REG32(DC_DISP_COLOR_KEY0_LOWER),
-       DEBUGFS_REG32(DC_DISP_COLOR_KEY0_UPPER),
-       DEBUGFS_REG32(DC_DISP_COLOR_KEY1_LOWER),
-       DEBUGFS_REG32(DC_DISP_COLOR_KEY1_UPPER),
-       DEBUGFS_REG32(DC_DISP_CURSOR_FOREGROUND),
-       DEBUGFS_REG32(DC_DISP_CURSOR_BACKGROUND),
-       DEBUGFS_REG32(DC_DISP_CURSOR_START_ADDR),
-       DEBUGFS_REG32(DC_DISP_CURSOR_START_ADDR_NS),
-       DEBUGFS_REG32(DC_DISP_CURSOR_POSITION),
-       DEBUGFS_REG32(DC_DISP_CURSOR_POSITION_NS),
-       DEBUGFS_REG32(DC_DISP_INIT_SEQ_CONTROL),
-       DEBUGFS_REG32(DC_DISP_SPI_INIT_SEQ_DATA_A),
-       DEBUGFS_REG32(DC_DISP_SPI_INIT_SEQ_DATA_B),
-       DEBUGFS_REG32(DC_DISP_SPI_INIT_SEQ_DATA_C),
-       DEBUGFS_REG32(DC_DISP_SPI_INIT_SEQ_DATA_D),
-       DEBUGFS_REG32(DC_DISP_DC_MCCIF_FIFOCTRL),
-       DEBUGFS_REG32(DC_DISP_MCCIF_DISPLAY0A_HYST),
-       DEBUGFS_REG32(DC_DISP_MCCIF_DISPLAY0B_HYST),
-       DEBUGFS_REG32(DC_DISP_MCCIF_DISPLAY1A_HYST),
-       DEBUGFS_REG32(DC_DISP_MCCIF_DISPLAY1B_HYST),
-       DEBUGFS_REG32(DC_DISP_DAC_CRT_CTRL),
-       DEBUGFS_REG32(DC_DISP_DISP_MISC_CONTROL),
-       DEBUGFS_REG32(DC_DISP_SD_CONTROL),
-       DEBUGFS_REG32(DC_DISP_SD_CSC_COEFF),
-       DEBUGFS_REG32(DC_DISP_SD_LUT(0)),
-       DEBUGFS_REG32(DC_DISP_SD_LUT(1)),
-       DEBUGFS_REG32(DC_DISP_SD_LUT(2)),
-       DEBUGFS_REG32(DC_DISP_SD_LUT(3)),
-       DEBUGFS_REG32(DC_DISP_SD_LUT(4)),
-       DEBUGFS_REG32(DC_DISP_SD_LUT(5)),
-       DEBUGFS_REG32(DC_DISP_SD_LUT(6)),
-       DEBUGFS_REG32(DC_DISP_SD_LUT(7)),
-       DEBUGFS_REG32(DC_DISP_SD_LUT(8)),
-       DEBUGFS_REG32(DC_DISP_SD_FLICKER_CONTROL),
-       DEBUGFS_REG32(DC_DISP_DC_PIXEL_COUNT),
-       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(0)),
-       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(1)),
-       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(2)),
-       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(3)),
-       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(4)),
-       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(5)),
-       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(6)),
-       DEBUGFS_REG32(DC_DISP_SD_HISTOGRAM(7)),
-       DEBUGFS_REG32(DC_DISP_SD_BL_TF(0)),
-       DEBUGFS_REG32(DC_DISP_SD_BL_TF(1)),
-       DEBUGFS_REG32(DC_DISP_SD_BL_TF(2)),
-       DEBUGFS_REG32(DC_DISP_SD_BL_TF(3)),
-       DEBUGFS_REG32(DC_DISP_SD_BL_CONTROL),
-       DEBUGFS_REG32(DC_DISP_SD_HW_K_VALUES),
-       DEBUGFS_REG32(DC_DISP_SD_MAN_K_VALUES),
-       DEBUGFS_REG32(DC_DISP_CURSOR_START_ADDR_HI),
-       DEBUGFS_REG32(DC_DISP_BLEND_CURSOR_CONTROL),
-       DEBUGFS_REG32(DC_WIN_WIN_OPTIONS),
-       DEBUGFS_REG32(DC_WIN_BYTE_SWAP),
-       DEBUGFS_REG32(DC_WIN_BUFFER_CONTROL),
-       DEBUGFS_REG32(DC_WIN_COLOR_DEPTH),
-       DEBUGFS_REG32(DC_WIN_POSITION),
-       DEBUGFS_REG32(DC_WIN_SIZE),
-       DEBUGFS_REG32(DC_WIN_PRESCALED_SIZE),
-       DEBUGFS_REG32(DC_WIN_H_INITIAL_DDA),
-       DEBUGFS_REG32(DC_WIN_V_INITIAL_DDA),
-       DEBUGFS_REG32(DC_WIN_DDA_INC),
-       DEBUGFS_REG32(DC_WIN_LINE_STRIDE),
-       DEBUGFS_REG32(DC_WIN_BUF_STRIDE),
-       DEBUGFS_REG32(DC_WIN_UV_BUF_STRIDE),
-       DEBUGFS_REG32(DC_WIN_BUFFER_ADDR_MODE),
-       DEBUGFS_REG32(DC_WIN_DV_CONTROL),
-       DEBUGFS_REG32(DC_WIN_BLEND_NOKEY),
-       DEBUGFS_REG32(DC_WIN_BLEND_1WIN),
-       DEBUGFS_REG32(DC_WIN_BLEND_2WIN_X),
-       DEBUGFS_REG32(DC_WIN_BLEND_2WIN_Y),
-       DEBUGFS_REG32(DC_WIN_BLEND_3WIN_XY),
-       DEBUGFS_REG32(DC_WIN_HP_FETCH_CONTROL),
-       DEBUGFS_REG32(DC_WINBUF_START_ADDR),
-       DEBUGFS_REG32(DC_WINBUF_START_ADDR_NS),
-       DEBUGFS_REG32(DC_WINBUF_START_ADDR_U),
-       DEBUGFS_REG32(DC_WINBUF_START_ADDR_U_NS),
-       DEBUGFS_REG32(DC_WINBUF_START_ADDR_V),
-       DEBUGFS_REG32(DC_WINBUF_START_ADDR_V_NS),
-       DEBUGFS_REG32(DC_WINBUF_ADDR_H_OFFSET),
-       DEBUGFS_REG32(DC_WINBUF_ADDR_H_OFFSET_NS),
-       DEBUGFS_REG32(DC_WINBUF_ADDR_V_OFFSET),
-       DEBUGFS_REG32(DC_WINBUF_ADDR_V_OFFSET_NS),
-       DEBUGFS_REG32(DC_WINBUF_UFLOW_STATUS),
-       DEBUGFS_REG32(DC_WINBUF_AD_UFLOW_STATUS),
-       DEBUGFS_REG32(DC_WINBUF_BD_UFLOW_STATUS),
-       DEBUGFS_REG32(DC_WINBUF_CD_UFLOW_STATUS),
-};
-
-static int tegra_dc_show_regs(struct seq_file *s, void *data)
-{
-       struct drm_info_node *node = s->private;
-       struct tegra_dc *dc = node->info_ent->data;
-       unsigned int i;
-       int err = 0;
+{
+       struct tegra_dc *dc = to_tegra_dc(crtc);
+       u32 value;
 
-       drm_modeset_lock(&dc->base.mutex, NULL);
+       if (!tegra_dc_idle(dc)) {
+               tegra_dc_stop(dc);
 
-       if (!dc->base.state->active) {
-               err = -EBUSY;
-               goto unlock;
+               /*
+                * Ignore the return value, there isn't anything useful to do
+                * in case this fails.
+                */
+               tegra_dc_wait_idle(dc, 100);
        }
 
-       for (i = 0; i < ARRAY_SIZE(tegra_dc_regs); i++) {
-               unsigned int offset = tegra_dc_regs[i].offset;
-
-               seq_printf(s, "%-40s %#05x %08x\n", tegra_dc_regs[i].name,
-                          offset, tegra_dc_readl(dc, offset));
+       /*
+        * This should really be part of the RGB encoder driver, but clearing
+        * these bits has the side-effect of stopping the display controller.
+        * When that happens no VBLANK interrupts will be raised. At the same
+        * time the encoder is disabled before the display controller, so the
+        * above code is always going to timeout waiting for the controller
+        * to go idle.
+        *
+        * Given the close coupling between the RGB encoder and the display
+        * controller doing it here is still kind of okay. None of the other
+        * encoder drivers require these bits to be cleared.
+        *
+        * XXX: Perhaps given that the display controller is switched off at
+        * this point anyway maybe clearing these bits isn't even useful for
+        * the RGB encoder?
+        */
+       if (dc->rgb) {
+               value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL);
+               value &= ~(PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
+                          PW4_ENABLE | PM0_ENABLE | PM1_ENABLE);
+               tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
        }
 
-unlock:
-       drm_modeset_unlock(&dc->base.mutex);
-       return err;
+       tegra_dc_stats_reset(&dc->stats);
+       drm_crtc_vblank_off(crtc);
+
+       pm_runtime_put_sync(dc->dev);
 }
 
-static int tegra_dc_show_crc(struct seq_file *s, void *data)
+static void tegra_crtc_atomic_enable(struct drm_crtc *crtc,
+                                    struct drm_crtc_state *old_state)
 {
-       struct drm_info_node *node = s->private;
-       struct tegra_dc *dc = node->info_ent->data;
-       int err = 0;
+       struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+       struct tegra_dc_state *state = to_dc_state(crtc->state);
+       struct tegra_dc *dc = to_tegra_dc(crtc);
        u32 value;
 
-       drm_modeset_lock(&dc->base.mutex, NULL);
+       pm_runtime_get_sync(dc->dev);
 
-       if (!dc->base.state->active) {
-               err = -EBUSY;
-               goto unlock;
+       /* initialize display controller */
+       if (dc->syncpt) {
+               u32 syncpt = host1x_syncpt_id(dc->syncpt);
+
+               value = SYNCPT_CNTRL_NO_STALL;
+               tegra_dc_writel(dc, value, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL);
+
+               value = SYNCPT_VSYNC_ENABLE | syncpt;
+               tegra_dc_writel(dc, value, DC_CMD_CONT_SYNCPT_VSYNC);
        }
 
-       value = DC_COM_CRC_CONTROL_ACTIVE_DATA | DC_COM_CRC_CONTROL_ENABLE;
-       tegra_dc_writel(dc, value, DC_COM_CRC_CONTROL);
-       tegra_dc_commit(dc);
+       value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
+               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
+       tegra_dc_writel(dc, value, DC_CMD_INT_TYPE);
 
-       drm_crtc_wait_one_vblank(&dc->base);
-       drm_crtc_wait_one_vblank(&dc->base);
+       value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
+               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
+       tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY);
 
-       value = tegra_dc_readl(dc, DC_COM_CRC_CHECKSUM);
-       seq_printf(s, "%08x\n", value);
+       /* initialize timer */
+       value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) |
+               WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20);
+       tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY);
 
-       tegra_dc_writel(dc, 0, DC_COM_CRC_CONTROL);
+       value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(1) |
+               WINDOW_B_THRESHOLD(1) | WINDOW_C_THRESHOLD(1);
+       tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER);
 
-unlock:
-       drm_modeset_unlock(&dc->base.mutex);
-       return err;
+       value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
+               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
+       tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE);
+
+       value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
+               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
+       tegra_dc_writel(dc, value, DC_CMD_INT_MASK);
+
+       if (dc->soc->supports_border_color)
+               tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR);
+
+       /* apply PLL and pixel clock changes */
+       tegra_dc_commit_state(dc, state);
+
+       /* program display mode */
+       tegra_dc_set_timings(dc, mode);
+
+       /* interlacing isn't supported yet, so disable it */
+       if (dc->soc->supports_interlacing) {
+               value = tegra_dc_readl(dc, DC_DISP_INTERLACE_CONTROL);
+               value &= ~INTERLACE_ENABLE;
+               tegra_dc_writel(dc, value, DC_DISP_INTERLACE_CONTROL);
+       }
+
+       value = tegra_dc_readl(dc, DC_CMD_DISPLAY_COMMAND);
+       value &= ~DISP_CTRL_MODE_MASK;
+       value |= DISP_CTRL_MODE_C_DISPLAY;
+       tegra_dc_writel(dc, value, DC_CMD_DISPLAY_COMMAND);
+
+       value = tegra_dc_readl(dc, DC_CMD_DISPLAY_POWER_CONTROL);
+       value |= PW0_ENABLE | PW1_ENABLE | PW2_ENABLE | PW3_ENABLE |
+                PW4_ENABLE | PM0_ENABLE | PM1_ENABLE;
+       tegra_dc_writel(dc, value, DC_CMD_DISPLAY_POWER_CONTROL);
+
+       tegra_dc_commit(dc);
+
+       drm_crtc_vblank_on(crtc);
 }
 
-static int tegra_dc_show_stats(struct seq_file *s, void *data)
+static int tegra_crtc_atomic_check(struct drm_crtc *crtc,
+                                  struct drm_crtc_state *state)
 {
-       struct drm_info_node *node = s->private;
-       struct tegra_dc *dc = node->info_ent->data;
+       return 0;
+}
 
-       seq_printf(s, "frames: %lu\n", dc->stats.frames);
-       seq_printf(s, "vblank: %lu\n", dc->stats.vblank);
-       seq_printf(s, "underflow: %lu\n", dc->stats.underflow);
-       seq_printf(s, "overflow: %lu\n", dc->stats.overflow);
+static void tegra_crtc_atomic_begin(struct drm_crtc *crtc,
+                                   struct drm_crtc_state *old_crtc_state)
+{
+       struct tegra_dc *dc = to_tegra_dc(crtc);
 
-       return 0;
+       if (crtc->state->event) {
+               crtc->state->event->pipe = drm_crtc_index(crtc);
+
+               WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+
+               dc->event = crtc->state->event;
+               crtc->state->event = NULL;
+       }
 }
 
-static struct drm_info_list debugfs_files[] = {
-       { "regs", tegra_dc_show_regs, 0, NULL },
-       { "crc", tegra_dc_show_crc, 0, NULL },
-       { "stats", tegra_dc_show_stats, 0, NULL },
+static void tegra_crtc_atomic_flush(struct drm_crtc *crtc,
+                                   struct drm_crtc_state *old_crtc_state)
+{
+       struct tegra_dc_state *state = to_dc_state(crtc->state);
+       struct tegra_dc *dc = to_tegra_dc(crtc);
+
+       tegra_dc_writel(dc, state->planes << 8, DC_CMD_STATE_CONTROL);
+       tegra_dc_writel(dc, state->planes, DC_CMD_STATE_CONTROL);
+}
+
+static const struct drm_crtc_helper_funcs tegra_crtc_helper_funcs = {
+       .atomic_check = tegra_crtc_atomic_check,
+       .atomic_begin = tegra_crtc_atomic_begin,
+       .atomic_flush = tegra_crtc_atomic_flush,
+       .atomic_enable = tegra_crtc_atomic_enable,
+       .atomic_disable = tegra_crtc_atomic_disable,
 };
 
-static int tegra_dc_debugfs_init(struct tegra_dc *dc, struct drm_minor *minor)
+static void tegra_dc_finish_page_flip(struct tegra_dc *dc)
 {
-       unsigned int i;
-       char *name;
-       int err;
-
-       name = kasprintf(GFP_KERNEL, "dc.%d", dc->pipe);
-       dc->debugfs = debugfs_create_dir(name, minor->debugfs_root);
-       kfree(name);
+       struct drm_device *drm = dc->base.dev;
+       struct drm_crtc *crtc = &dc->base;
+       unsigned long flags, base;
+       struct tegra_bo *bo;
 
-       if (!dc->debugfs)
-               return -ENOMEM;
+       spin_lock_irqsave(&drm->event_lock, flags);
 
-       dc->debugfs_files = kmemdup(debugfs_files, sizeof(debugfs_files),
-                                   GFP_KERNEL);
-       if (!dc->debugfs_files) {
-               err = -ENOMEM;
-               goto remove;
+       if (!dc->event) {
+               spin_unlock_irqrestore(&drm->event_lock, flags);
+               return;
        }
 
-       for (i = 0; i < ARRAY_SIZE(debugfs_files); i++)
-               dc->debugfs_files[i].data = dc;
+       bo = tegra_fb_get_plane(crtc->primary->fb, 0);
 
-       err = drm_debugfs_create_files(dc->debugfs_files,
-                                      ARRAY_SIZE(debugfs_files),
-                                      dc->debugfs, minor);
-       if (err < 0)
-               goto free;
+       spin_lock(&dc->lock);
 
-       dc->minor = minor;
+       /* check if new start address has been latched */
+       tegra_dc_writel(dc, WINDOW_A_SELECT, DC_CMD_DISPLAY_WINDOW_HEADER);
+       tegra_dc_writel(dc, READ_MUX, DC_CMD_STATE_ACCESS);
+       base = tegra_dc_readl(dc, DC_WINBUF_START_ADDR);
+       tegra_dc_writel(dc, 0, DC_CMD_STATE_ACCESS);
 
-       return 0;
+       spin_unlock(&dc->lock);
 
-free:
-       kfree(dc->debugfs_files);
-       dc->debugfs_files = NULL;
-remove:
-       debugfs_remove(dc->debugfs);
-       dc->debugfs = NULL;
+       if (base == bo->paddr + crtc->primary->fb->offsets[0]) {
+               drm_crtc_send_vblank_event(crtc, dc->event);
+               drm_crtc_vblank_put(crtc);
+               dc->event = NULL;
+       }
 
-       return err;
+       spin_unlock_irqrestore(&drm->event_lock, flags);
 }
 
-static int tegra_dc_debugfs_exit(struct tegra_dc *dc)
+static irqreturn_t tegra_dc_irq(int irq, void *data)
 {
-       drm_debugfs_remove_files(dc->debugfs_files, ARRAY_SIZE(debugfs_files),
-                                dc->minor);
-       dc->minor = NULL;
+       struct tegra_dc *dc = data;
+       unsigned long status;
 
-       kfree(dc->debugfs_files);
-       dc->debugfs_files = NULL;
+       status = tegra_dc_readl(dc, DC_CMD_INT_STATUS);
+       tegra_dc_writel(dc, status, DC_CMD_INT_STATUS);
+
+       if (status & FRAME_END_INT) {
+               /*
+               dev_dbg(dc->dev, "%s(): frame end\n", __func__);
+               */
+               dc->stats.frames++;
+       }
 
-       debugfs_remove(dc->debugfs);
-       dc->debugfs = NULL;
+       if (status & VBLANK_INT) {
+               /*
+               dev_dbg(dc->dev, "%s(): vertical blank\n", __func__);
+               */
+               drm_crtc_handle_vblank(&dc->base);
+               tegra_dc_finish_page_flip(dc);
+               dc->stats.vblank++;
+       }
 
-       return 0;
+       if (status & (WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT)) {
+               /*
+               dev_dbg(dc->dev, "%s(): underflow\n", __func__);
+               */
+               dc->stats.underflow++;
+       }
+
+       if (status & (WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT)) {
+               /*
+               dev_dbg(dc->dev, "%s(): overflow\n", __func__);
+               */
+               dc->stats.overflow++;
+       }
+
+       return IRQ_HANDLED;
 }
 
 static int tegra_dc_init(struct host1x_client *client)
@@ -1797,12 +1781,6 @@ static int tegra_dc_init(struct host1x_client *client)
        if (err < 0)
                goto cleanup;
 
-       if (IS_ENABLED(CONFIG_DEBUG_FS)) {
-               err = tegra_dc_debugfs_init(dc, drm->primary);
-               if (err < 0)
-                       dev_err(dc->dev, "debugfs setup failed: %d\n", err);
-       }
-
        err = devm_request_irq(dc->dev, dc->irq, tegra_dc_irq, 0,
                               dev_name(dc->dev), dc);
        if (err < 0) {
@@ -1835,12 +1813,6 @@ static int tegra_dc_exit(struct host1x_client *client)
 
        devm_free_irq(dc->dev, dc->irq, dc);
 
-       if (IS_ENABLED(CONFIG_DEBUG_FS)) {
-               err = tegra_dc_debugfs_exit(dc);
-               if (err < 0)
-                       dev_err(dc->dev, "debugfs cleanup failed: %d\n", err);
-       }
-
        err = tegra_dc_rgb_exit(dc);
        if (err) {
                dev_err(dc->dev, "failed to shutdown RGB output: %d\n", err);
index cb100b6e32821efb5cf9d2242f4cb0021044b0ea..032c734dd4703dc5e4364177b4fb9be7a1a6e67d 100644 (file)
@@ -56,8 +56,6 @@ struct tegra_dc {
        struct list_head list;
 
        struct drm_info_list *debugfs_files;
-       struct drm_minor *minor;
-       struct dentry *debugfs;
 
        /* page-flip handling */
        struct drm_pending_vblank_event *event;