diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c index aa66abb57b1c..2f9af13905da 100644 --- a/drivers/gpu/drm/tegra/dc.c +++ b/drivers/gpu/drm/tegra/dc.c @@ -820,99 +820,6 @@ static int tegra_dc_add_planes(struct drm_device *drm, struct tegra_dc *dc) return 0; } -static int tegra_dc_set_base(struct tegra_dc *dc, int x, int y, - struct drm_framebuffer *fb) -{ - struct tegra_bo *bo = tegra_fb_get_plane(fb, 0); - unsigned int h_offset = 0, v_offset = 0; - struct tegra_bo_tiling tiling; - unsigned long value, flags; - unsigned int format, swap; - int err; - - err = tegra_fb_get_tiling(fb, &tiling); - if (err < 0) - return err; - - spin_lock_irqsave(&dc->lock, flags); - - tegra_dc_writel(dc, WINDOW_A_SELECT, DC_CMD_DISPLAY_WINDOW_HEADER); - - value = fb->offsets[0] + y * fb->pitches[0] + - x * fb->bits_per_pixel / 8; - - tegra_dc_writel(dc, bo->paddr + value, DC_WINBUF_START_ADDR); - tegra_dc_writel(dc, fb->pitches[0], DC_WIN_LINE_STRIDE); - - format = tegra_dc_format(fb->pixel_format, &swap); - tegra_dc_writel(dc, format, DC_WIN_COLOR_DEPTH); - tegra_dc_writel(dc, swap, DC_WIN_BYTE_SWAP); - - if (dc->soc->supports_block_linear) { - unsigned long height = tiling.value; - - switch (tiling.mode) { - case TEGRA_BO_TILING_MODE_PITCH: - value = DC_WINBUF_SURFACE_KIND_PITCH; - break; - - 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_dc_writel(dc, value, DC_WINBUF_SURFACE_KIND); - } else { - switch (tiling.mode) { - case TEGRA_BO_TILING_MODE_PITCH: - value = DC_WIN_BUFFER_ADDR_MODE_LINEAR_UV | - DC_WIN_BUFFER_ADDR_MODE_LINEAR; - break; - - case TEGRA_BO_TILING_MODE_TILED: - value = DC_WIN_BUFFER_ADDR_MODE_TILE_UV | - DC_WIN_BUFFER_ADDR_MODE_TILE; - break; - - case TEGRA_BO_TILING_MODE_BLOCK: - DRM_ERROR("hardware doesn't support block linear mode\n"); - spin_unlock_irqrestore(&dc->lock, flags); - return -EINVAL; - } - - tegra_dc_writel(dc, value, DC_WIN_BUFFER_ADDR_MODE); - } - - /* make sure bottom-up buffers are properly displayed */ - if (tegra_fb_is_bottom_up(fb)) { - value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); - value |= V_DIRECTION; - tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); - - v_offset += fb->height - 1; - } else { - value = tegra_dc_readl(dc, DC_WIN_WIN_OPTIONS); - value &= ~V_DIRECTION; - tegra_dc_writel(dc, value, DC_WIN_WIN_OPTIONS); - } - - tegra_dc_writel(dc, h_offset, DC_WINBUF_ADDR_H_OFFSET); - tegra_dc_writel(dc, v_offset, DC_WINBUF_ADDR_V_OFFSET); - - value = GENERAL_ACT_REQ | WIN_A_ACT_REQ; - tegra_dc_writel(dc, value << 8, DC_CMD_STATE_CONTROL); - tegra_dc_writel(dc, value, DC_CMD_STATE_CONTROL); - - spin_unlock_irqrestore(&dc->lock, flags); - - return 0; -} - void tegra_dc_enable_vblank(struct tegra_dc *dc) { unsigned long value, flags; @@ -991,30 +898,6 @@ void tegra_dc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file) spin_unlock_irqrestore(&drm->event_lock, flags); } -static int tegra_dc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, - struct drm_pending_vblank_event *event, uint32_t page_flip_flags) -{ - unsigned int pipe = drm_crtc_index(crtc); - struct tegra_dc *dc = to_tegra_dc(crtc); - - if (dc->event) - return -EBUSY; - - if (event) { - event->pipe = pipe; - dc->event = event; - drm_crtc_vblank_get(crtc); - } - - if (crtc->primary->state) - drm_atomic_set_fb_for_plane(crtc->primary->state, fb); - - tegra_dc_set_base(dc, 0, 0, fb); - crtc->primary->fb = fb; - - return 0; -} - static void tegra_dc_destroy(struct drm_crtc *crtc) { drm_crtc_cleanup(crtc); @@ -1056,7 +939,7 @@ static void tegra_crtc_atomic_destroy_state(struct drm_crtc *crtc, } static const struct drm_crtc_funcs tegra_crtc_funcs = { - .page_flip = tegra_dc_page_flip, + .page_flip = drm_atomic_helper_page_flip, .set_config = drm_atomic_helper_set_config, .destroy = tegra_dc_destroy, .reset = tegra_crtc_reset, @@ -1326,6 +1209,16 @@ static int tegra_crtc_atomic_check(struct drm_crtc *crtc, static void tegra_crtc_atomic_begin(struct drm_crtc *crtc) { + 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) diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index d01484cf3432..0edfb5e0b374 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -10,6 +10,7 @@ #include #include +#include #include #include "drm.h" @@ -26,13 +27,90 @@ struct tegra_drm_file { struct list_head contexts; }; +static void tegra_atomic_schedule(struct tegra_drm *tegra, + struct drm_atomic_state *state) +{ + tegra->commit.state = state; + schedule_work(&tegra->commit.work); +} + +static void tegra_atomic_complete(struct tegra_drm *tegra, + struct drm_atomic_state *state) +{ + struct drm_device *drm = tegra->drm; + + /* + * Everything below can be run asynchronously without the need to grab + * any modeset locks at all under one condition: It must be guaranteed + * that the asynchronous work has either been cancelled (if the driver + * supports it, which at least requires that the framebuffers get + * cleaned up with drm_atomic_helper_cleanup_planes()) or completed + * before the new state gets committed on the software side with + * drm_atomic_helper_swap_state(). + * + * This scheme allows new atomic state updates to be prepared and + * checked in parallel to the asynchronous completion of the previous + * update. Which is important since compositors need to figure out the + * composition of the next frame right after having submitted the + * current layout. + */ + + drm_atomic_helper_commit_pre_planes(drm, state); + drm_atomic_helper_commit_planes(drm, state); + drm_atomic_helper_commit_post_planes(drm, state); + + drm_atomic_helper_wait_for_vblanks(drm, state); + + drm_atomic_helper_cleanup_planes(drm, state); + drm_atomic_state_free(state); +} + +static void tegra_atomic_work(struct work_struct *work) +{ + struct tegra_drm *tegra = container_of(work, struct tegra_drm, + commit.work); + + tegra_atomic_complete(tegra, tegra->commit.state); +} + +static int tegra_atomic_commit(struct drm_device *drm, + struct drm_atomic_state *state, bool async) +{ + struct tegra_drm *tegra = drm->dev_private; + int err; + + err = drm_atomic_helper_prepare_planes(drm, state); + if (err) + return err; + + /* serialize outstanding asynchronous commits */ + mutex_lock(&tegra->commit.lock); + flush_work(&tegra->commit.work); + + /* + * This is the point of no return - everything below never fails except + * when the hw goes bonghits. Which means we can commit the new state on + * the software side now. + */ + + drm_atomic_helper_swap_state(drm, state); + + if (async) + tegra_atomic_schedule(tegra, state); + else + tegra_atomic_complete(tegra, state); + + mutex_unlock(&tegra->commit.lock); + return 0; +} + static const struct drm_mode_config_funcs tegra_drm_mode_funcs = { .fb_create = tegra_fb_create, #ifdef CONFIG_DRM_TEGRA_FBDEV .output_poll_changed = tegra_fb_output_poll_changed, #endif .atomic_check = drm_atomic_helper_check, - .atomic_commit = drm_atomic_helper_commit, + .atomic_commit = tegra_atomic_commit, }; static int tegra_drm_load(struct drm_device *drm, unsigned long flags) @@ -58,6 +136,10 @@ static int tegra_drm_load(struct drm_device *drm, unsigned long flags) mutex_init(&tegra->clients_lock); INIT_LIST_HEAD(&tegra->clients); + + mutex_init(&tegra->commit.lock); + INIT_WORK(&tegra->commit.work, tegra_atomic_work); + drm->dev_private = tegra; tegra->drm = drm; diff --git a/drivers/gpu/drm/tegra/drm.h b/drivers/gpu/drm/tegra/drm.h index 3db719de312f..b1c7027b26e7 100644 --- a/drivers/gpu/drm/tegra/drm.h +++ b/drivers/gpu/drm/tegra/drm.h @@ -50,6 +50,12 @@ struct tegra_drm { #endif unsigned int pitch_align; + + struct { + struct drm_atomic_state *state; + struct work_struct work; + struct mutex lock; + } commit; }; struct tegra_drm_client;