summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/imx/dpu/dpu-kms.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/imx/dpu/dpu-kms.c')
-rw-r--r--drivers/gpu/drm/imx/dpu/dpu-kms.c585
1 files changed, 585 insertions, 0 deletions
diff --git a/drivers/gpu/drm/imx/dpu/dpu-kms.c b/drivers/gpu/drm/imx/dpu/dpu-kms.c
new file mode 100644
index 000000000000..dbd86650097c
--- /dev/null
+++ b/drivers/gpu/drm/imx/dpu/dpu-kms.c
@@ -0,0 +1,585 @@
+/*
+ * Copyright 2017-2018 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <linux/dma-buf.h>
+#include <linux/reservation.h>
+#include <linux/sort.h>
+#include <video/dpu.h>
+#include "dpu-crtc.h"
+#include "dpu-plane.h"
+#include "imx-drm.h"
+
+static void dpu_drm_output_poll_changed(struct drm_device *dev)
+{
+ struct imx_drm_device *imxdrm = dev->dev_private;
+
+ drm_fbdev_cma_hotplug_event(imxdrm->fbhelper);
+}
+
+static struct drm_plane_state **
+dpu_atomic_alloc_tmp_planes_per_crtc(struct drm_device *dev)
+{
+ int total_planes = dev->mode_config.num_total_plane;
+ struct drm_plane_state **states;
+
+ states = kmalloc_array(total_planes, sizeof(*states), GFP_TEMPORARY);
+ if (!states)
+ return ERR_PTR(-ENOMEM);
+
+ return states;
+}
+
+static int zpos_cmp(const void *a, const void *b)
+{
+ const struct drm_plane_state *sa = *(struct drm_plane_state **)a;
+ const struct drm_plane_state *sb = *(struct drm_plane_state **)b;
+
+ return sa->normalized_zpos - sb->normalized_zpos;
+}
+
+static int dpu_atomic_sort_planes_per_crtc(struct drm_crtc_state *crtc_state,
+ struct drm_plane_state **states)
+{
+ struct drm_atomic_state *state = crtc_state->state;
+ struct drm_device *dev = state->dev;
+ struct drm_plane *plane;
+ int n = 0;
+
+ drm_for_each_plane_mask(plane, dev, crtc_state->plane_mask) {
+ struct drm_plane_state *plane_state =
+ drm_atomic_get_plane_state(state, plane);
+ if (IS_ERR(plane_state))
+ return PTR_ERR(plane_state);
+ states[n++] = plane_state;
+ }
+
+ sort(states, n, sizeof(*states), zpos_cmp, NULL);
+
+ return n;
+}
+
+static int
+dpu_atomic_compute_plane_base_per_crtc(struct drm_plane_state **states, int n)
+{
+ struct dpu_plane_state *dpstate;
+ int i, left, right, top, bottom, tmp;
+
+ /* compute the plane base */
+ left = states[0]->crtc_x;
+ top = states[0]->crtc_y;
+ right = states[0]->crtc_x + states[0]->crtc_w;
+ bottom = states[0]->crtc_y + states[0]->crtc_h;
+
+ for (i = 1; i < n; i++) {
+ left = min(states[i]->crtc_x, left);
+ top = min(states[i]->crtc_y, top);
+
+ tmp = states[i]->crtc_x + states[i]->crtc_w;
+ right = max(tmp, right);
+
+ tmp = states[i]->crtc_y + states[i]->crtc_h;
+ bottom = max(tmp, bottom);
+ }
+
+ /* BTW, be smart to compute the layer offset */
+ for (i = 0; i < n; i++) {
+ dpstate = to_dpu_plane_state(states[i]);
+ dpstate->layer_x = states[i]->crtc_x - left;
+ dpstate->layer_y = states[i]->crtc_y - top;
+ }
+
+ /* finally, store the base in plane state */
+ dpstate = to_dpu_plane_state(states[0]);
+ dpstate->base_x = left;
+ dpstate->base_y = top;
+ dpstate->base_w = right - left;
+ dpstate->base_h = bottom - top;
+
+ return 0;
+}
+
+static void
+dpu_atomic_set_top_plane_per_crtc(struct drm_plane_state **states, int n)
+{
+ struct dpu_plane_state *dpstate;
+ int i;
+
+ for (i = 0; i < n; i++) {
+ dpstate = to_dpu_plane_state(states[i]);
+ dpstate->is_top = (i == (n - 1)) ? true : false;
+ }
+}
+
+static int
+dpu_atomic_assign_plane_source_per_crtc(struct drm_plane_state **states, int n)
+{
+ struct dpu_plane_state *dpstate;
+ struct dpu_plane *dplane;
+ struct dpu_plane_grp *grp;
+ struct drm_framebuffer *fb;
+ struct dpu_fetchunit *fu;
+ struct dpu_fetchunit *fe;
+ struct dpu_hscaler *hs;
+ struct dpu_vscaler *vs;
+ unsigned int sid, src_sid;
+ unsigned int num_planes;
+ int i, j, k, l, m;
+ int total_asrc_num;
+ u32 src_a_mask, cap_mask, fe_mask, hs_mask, vs_mask;
+ bool need_fetcheco, need_hscaler, need_vscaler;
+ bool fmt_is_yuv;
+
+ /* for active planes only */
+ for (i = 0; i < n; i++) {
+ dpstate = to_dpu_plane_state(states[i]);
+ dplane = to_dpu_plane(states[i]->plane);
+ fb = states[i]->fb;
+ num_planes = drm_format_num_planes(fb->pixel_format);
+ fmt_is_yuv = drm_format_is_yuv(fb->pixel_format);
+ grp = dplane->grp;
+ sid = dplane->stream_id;
+
+ need_fetcheco = (num_planes > 1);
+ need_hscaler = (states[i]->src_w >> 16 != states[i]->crtc_w);
+ need_vscaler = (states[i]->src_h >> 16 != states[i]->crtc_h);
+
+ total_asrc_num = 0;
+ src_a_mask = grp->src_a_mask;
+ fe_mask = 0;
+ hs_mask = 0;
+ vs_mask = 0;
+
+ for (l = 0; l < (sizeof(grp->src_a_mask) * 8); l++) {
+ if (grp->src_a_mask & BIT(l))
+ total_asrc_num++;
+ }
+
+ /* assign source */
+ mutex_lock(&grp->mutex);
+ for (k = 0; k < total_asrc_num; k++) {
+ m = ffs(src_a_mask) - 1;
+
+ fu = source_to_fu(&grp->res, sources[m]);
+ if (!fu)
+ return -EINVAL;
+
+ /* avoid on-the-fly/hot migration */
+ src_sid = fu->ops->get_stream_id(fu);
+ if (src_sid && src_sid != BIT(sid))
+ goto next;
+
+ if (fetchunit_is_fetchdecode(fu)) {
+ cap_mask = fetchdecode_get_vproc_mask(fu);
+
+ if (need_fetcheco) {
+ fe = fetchdecode_get_fetcheco(fu);
+
+ /* avoid on-the-fly/hot migration */
+ src_sid = fu->ops->get_stream_id(fe);
+ if (src_sid && src_sid != BIT(sid))
+ goto next;
+
+ /* fetch unit has the fetcheco cap? */
+ if (!dpu_vproc_has_fetcheco_cap(cap_mask))
+ goto next;
+
+ fe_mask =
+ dpu_vproc_get_fetcheco_cap(cap_mask);
+
+ /* fetcheco available? */
+ if (grp->src_use_vproc_mask & fe_mask)
+ goto next;
+ }
+
+ if (need_hscaler) {
+ hs = fetchdecode_get_hscaler(fu);
+
+ /* avoid on-the-fly/hot migration */
+ src_sid = hscaler_get_stream_id(hs);
+ if (src_sid && src_sid != BIT(sid))
+ goto next;
+
+ /* fetch unit has the hscale cap */
+ if (!dpu_vproc_has_hscale_cap(cap_mask))
+ goto next;
+
+ hs_mask =
+ dpu_vproc_get_hscale_cap(cap_mask);
+
+ /* hscaler available? */
+ if (grp->src_use_vproc_mask & hs_mask)
+ goto next;
+ }
+
+ if (need_vscaler) {
+ vs = fetchdecode_get_vscaler(fu);
+
+ /* avoid on-the-fly/hot migration */
+ src_sid = vscaler_get_stream_id(vs);
+ if (src_sid && src_sid != BIT(sid))
+ goto next;
+
+ /* fetch unit has the vscale cap? */
+ if (!dpu_vproc_has_vscale_cap(cap_mask))
+ goto next;
+
+ vs_mask =
+ dpu_vproc_get_vscale_cap(cap_mask);
+
+ /* vscaler available? */
+ if (grp->src_use_vproc_mask & vs_mask)
+ goto next;
+ }
+ } else {
+ if (fmt_is_yuv || need_fetcheco ||
+ need_hscaler || need_vscaler)
+ goto next;
+ }
+
+ grp->src_a_mask &= ~BIT(m);
+ grp->src_use_vproc_mask |= fe_mask | hs_mask | vs_mask;
+ break;
+next:
+ src_a_mask &= ~BIT(m);
+ fe_mask = 0;
+ hs_mask = 0;
+ vs_mask = 0;
+ }
+ mutex_unlock(&grp->mutex);
+
+ if (k == total_asrc_num)
+ return -EINVAL;
+
+ dpstate->source = sources[m];
+
+ /* assign stage and blend */
+ if (sid) {
+ j = grp->hw_plane_num - (n - i);
+ dpstate->stage = i ? stages[j - 1] : cf_stages[sid];
+ dpstate->blend = blends[j];
+ } else {
+ dpstate->stage = i ? stages[i - 1] : cf_stages[sid];
+ dpstate->blend = blends[i];
+ }
+ }
+
+ return 0;
+}
+
+static void
+dpu_atomic_mark_pipe_states_prone_to_put_per_crtc(struct drm_crtc *crtc,
+ u32 crtc_mask,
+ struct drm_atomic_state *state,
+ bool *puts)
+{
+ struct drm_plane *plane;
+ struct drm_plane_state *plane_state;
+ bool found_pstate = false;
+ int i;
+
+ if ((crtc_mask & drm_crtc_mask(crtc)) == 0) {
+ for_each_plane_in_state(state, plane, plane_state, i) {
+ if (plane->possible_crtcs &
+ drm_crtc_mask(crtc)) {
+ found_pstate = true;
+ break;
+ }
+ }
+
+ if (!found_pstate)
+ puts[drm_crtc_index(crtc)] = true;
+ }
+}
+
+static void
+dpu_atomic_put_plane_state(struct drm_atomic_state *state,
+ struct drm_plane *plane)
+{
+ int index = drm_plane_index(plane);
+
+ plane->funcs->atomic_destroy_state(plane, state->planes[index].state);
+ state->planes[index].ptr = NULL;
+ state->planes[index].state = NULL;
+
+ drm_modeset_unlock(&plane->mutex);
+}
+
+static void
+dpu_atomic_put_crtc_state(struct drm_atomic_state *state,
+ struct drm_crtc *crtc)
+{
+ int index = drm_crtc_index(crtc);
+
+ crtc->funcs->atomic_destroy_state(crtc, state->crtcs[index].state);
+ state->crtcs[index].ptr = NULL;
+ state->crtcs[index].state = NULL;
+
+ drm_modeset_unlock(&crtc->mutex);
+}
+
+static void
+dpu_atomic_put_possible_states_per_crtc(struct drm_crtc_state *crtc_state)
+{
+ struct drm_atomic_state *state = crtc_state->state;
+ struct drm_crtc *crtc = crtc_state->crtc;
+ struct drm_crtc_state *old_crtc_state = crtc->state;
+ struct drm_plane *plane;
+ struct drm_plane_state *plane_state;
+ struct dpu_plane *dplane = to_dpu_plane(crtc->primary);
+ struct dpu_plane_state **old_dpstates;
+ struct dpu_plane_state *old_dpstate, *new_dpstate;
+ u32 active_mask = 0;
+ int i;
+
+ old_dpstates = crtc_state_get_dpu_plane_states(old_crtc_state);
+ if (WARN_ON(!old_dpstates))
+ return;
+
+ for (i = 0; i < dplane->grp->hw_plane_num; i++) {
+ old_dpstate = old_dpstates[i];
+ if (!old_dpstate)
+ continue;
+
+ active_mask |= BIT(i);
+
+ drm_atomic_crtc_state_for_each_plane(plane, crtc_state) {
+ if (drm_plane_index(plane) !=
+ drm_plane_index(old_dpstate->base.plane))
+ continue;
+
+ plane_state =
+ drm_atomic_get_existing_plane_state(state,
+ plane);
+ WARN_ON(!plane_state);
+
+ new_dpstate = to_dpu_plane_state(plane_state);
+
+ active_mask &= ~BIT(i);
+
+ /*
+ * Should be enough to check the below real HW plane
+ * resources only.
+ * Vproc resources and things like layer_x/y should
+ * be fine.
+ */
+ if (old_dpstate->stage != new_dpstate->stage ||
+ old_dpstate->source != new_dpstate->source ||
+ old_dpstate->blend != new_dpstate->blend)
+ return;
+ }
+ }
+
+ /* pure software check */
+ if (WARN_ON(active_mask))
+ return;
+
+ drm_atomic_crtc_state_for_each_plane(plane, crtc_state)
+ dpu_atomic_put_plane_state(state, plane);
+
+ dpu_atomic_put_crtc_state(state, crtc);
+}
+
+static int dpu_drm_atomic_check(struct drm_device *dev,
+ struct drm_atomic_state *state)
+{
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *crtc_state;
+ struct drm_plane *plane;
+ struct dpu_plane *dpu_plane;
+ const struct drm_plane_state *plane_state;
+ struct dpu_plane_grp *grp[MAX_DPU_PLANE_GRP];
+ int ret, i, grp_id;
+ int active_plane[MAX_DPU_PLANE_GRP];
+ int active_plane_fetcheco[MAX_DPU_PLANE_GRP];
+ int active_plane_hscale[MAX_DPU_PLANE_GRP];
+ int active_plane_vscale[MAX_DPU_PLANE_GRP];
+ bool pipe_states_prone_to_put[MAX_CRTC];
+ u32 crtc_mask_in_state = 0;
+
+ ret = drm_atomic_helper_check_modeset(dev, state);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < MAX_CRTC; i++)
+ pipe_states_prone_to_put[i] = false;
+
+ for (i = 0; i < MAX_DPU_PLANE_GRP; i++) {
+ active_plane[i] = 0;
+ active_plane_fetcheco[i] = 0;
+ active_plane_hscale[i] = 0;
+ active_plane_vscale[i] = 0;
+ grp[i] = NULL;
+ }
+
+ for_each_crtc_in_state(state, crtc, crtc_state, i)
+ crtc_mask_in_state |= drm_crtc_mask(crtc);
+
+ drm_for_each_crtc(crtc, dev) {
+ dpu_atomic_mark_pipe_states_prone_to_put_per_crtc(crtc,
+ crtc_mask_in_state, state,
+ pipe_states_prone_to_put);
+
+ crtc_state = drm_atomic_get_crtc_state(state, crtc);
+ if (IS_ERR(crtc_state))
+ return PTR_ERR(crtc_state);
+
+ drm_atomic_crtc_state_for_each_plane(plane, crtc_state)
+ plane_state = drm_atomic_get_plane_state(state, plane);
+
+ drm_atomic_crtc_state_for_each_plane_state(plane, plane_state,
+ crtc_state) {
+ struct drm_framebuffer *fb = plane_state->fb;
+ dpu_plane = to_dpu_plane(plane);
+ grp_id = dpu_plane->grp->id;
+ active_plane[grp_id]++;
+
+ if (drm_format_num_planes(fb->pixel_format) > 1)
+ active_plane_fetcheco[grp_id]++;
+
+ if (plane_state->src_w >> 16 != plane_state->crtc_w)
+ active_plane_hscale[grp_id]++;
+
+ if (plane_state->src_h >> 16 != plane_state->crtc_h)
+ active_plane_vscale[grp_id]++;
+
+ if (grp[grp_id] == NULL)
+ grp[grp_id] = dpu_plane->grp;
+ }
+ }
+
+ /* enough resources? */
+ for (i = 0; i < MAX_DPU_PLANE_GRP; i++) {
+ if (grp[i]) {
+ if (active_plane[i] > grp[i]->hw_plane_num)
+ return -EINVAL;
+
+ if (active_plane_fetcheco[i] >
+ grp[i]->hw_plane_fetcheco_num)
+ return -EINVAL;
+
+ if (active_plane_hscale[i] >
+ grp[i]->hw_plane_hscaler_num)
+ return -EINVAL;
+
+ if (active_plane_vscale[i] >
+ grp[i]->hw_plane_vscaler_num)
+ return -EINVAL;
+ }
+ }
+
+ /* clear resource mask */
+ for (i = 0; i < MAX_DPU_PLANE_GRP; i++) {
+ if (grp[i]) {
+ mutex_lock(&grp[i]->mutex);
+ grp[i]->src_a_mask = ~grp[i]->src_na_mask;
+ grp[i]->src_use_vproc_mask = 0;
+ mutex_unlock(&grp[i]->mutex);
+ }
+ }
+
+ ret = drm_atomic_normalize_zpos(dev, state);
+ if (ret)
+ return ret;
+
+ for_each_crtc_in_state(state, crtc, crtc_state, i) {
+ struct drm_plane_state **states;
+ int n;
+
+ states = dpu_atomic_alloc_tmp_planes_per_crtc(dev);
+ if (IS_ERR(states))
+ return PTR_ERR(states);
+
+ n = dpu_atomic_sort_planes_per_crtc(crtc_state, states);
+ if (n < 0) {
+ kfree(states);
+ return n;
+ }
+
+ /* no active planes? */
+ if (n == 0) {
+ kfree(states);
+ continue;
+ }
+
+ /* 'zpos = 0' means primary plane */
+ if (states[0]->plane->type != DRM_PLANE_TYPE_PRIMARY) {
+ kfree(states);
+ return -EINVAL;
+ }
+
+ ret = dpu_atomic_compute_plane_base_per_crtc(states, n);
+ if (ret) {
+ kfree(states);
+ return ret;
+ }
+
+ dpu_atomic_set_top_plane_per_crtc(states, n);
+
+ ret = dpu_atomic_assign_plane_source_per_crtc(states, n);
+ if (ret) {
+ kfree(states);
+ return ret;
+ }
+
+ kfree(states);
+
+ if (pipe_states_prone_to_put[drm_crtc_index(crtc)])
+ dpu_atomic_put_possible_states_per_crtc(crtc_state);
+ }
+
+ ret = drm_atomic_helper_check_planes(dev, state);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+static int dpu_drm_atomic_commit(struct drm_device *dev,
+ struct drm_atomic_state *state,
+ bool nonblock)
+{
+ struct drm_plane_state *plane_state;
+ struct drm_plane *plane;
+ struct dma_buf *dma_buf;
+ int i;
+
+ /*
+ * If the plane fb has an dma-buf attached, fish out the exclusive
+ * fence for the atomic helper to wait on.
+ */
+ for_each_plane_in_state(state, plane, plane_state, i) {
+ if ((plane->state->fb != plane_state->fb) && plane_state->fb) {
+ dma_buf = drm_fb_cma_get_gem_obj(plane_state->fb,
+ 0)->base.dma_buf;
+ if (!dma_buf)
+ continue;
+ plane_state->fence =
+ reservation_object_get_excl_rcu(dma_buf->resv);
+ }
+ }
+
+ return drm_atomic_helper_commit(dev, state, nonblock);
+}
+
+const struct drm_mode_config_funcs dpu_drm_mode_config_funcs = {
+ .fb_create = drm_fb_cma_create,
+ .output_poll_changed = dpu_drm_output_poll_changed,
+ .atomic_check = dpu_drm_atomic_check,
+ .atomic_commit = dpu_drm_atomic_commit,
+};