| /* |
| * Copyright 2017-2019 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_KERNEL); |
| 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_crtc_state *crtc_state, |
| struct drm_plane_state **states, int n, |
| bool use_pc) |
| { |
| struct dpu_plane_state *dpstate; |
| int i, left, right, top, bottom, tmp; |
| int base_x, base_y, base_w, base_h; |
| int half_hdisplay = crtc_state->adjusted_mode.hdisplay >> 1; |
| bool lo, ro, bo; |
| |
| /* 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; |
| } |
| |
| /* store the base in plane state */ |
| dpstate = to_dpu_plane_state(states[0]); |
| base_x = left; |
| base_y = top; |
| base_w = right - left; |
| base_h = bottom - top; |
| dpstate->base_x = base_x; |
| dpstate->base_y = base_y; |
| dpstate->base_w = base_w; |
| dpstate->base_h = base_h; |
| |
| if (!use_pc) |
| return 0; |
| |
| /* compute left/right_layer/base_x/w if pixel combiner is needed */ |
| for (i = 0; i < n; i++) { |
| dpstate = to_dpu_plane_state(states[i]); |
| |
| lo = dpstate->left_src_w && !dpstate->right_src_w; |
| ro = !dpstate->left_src_w && dpstate->right_src_w; |
| bo = dpstate->left_src_w && dpstate->right_src_w; |
| |
| if (lo || bo) { |
| dpstate->left_layer_x = dpstate->layer_x; |
| dpstate->right_layer_x = 0; |
| } else if (ro) { |
| dpstate->left_layer_x = 0; |
| dpstate->right_layer_x = |
| states[i]->crtc_x - half_hdisplay; |
| } |
| |
| if (i) |
| continue; |
| |
| if (base_x < half_hdisplay) { |
| dpstate->left_base_x = base_x; |
| dpstate->right_base_x = 0; |
| |
| if ((base_x + base_w) < half_hdisplay) { |
| dpstate->left_base_w = base_w; |
| dpstate->right_base_w = 0; |
| } else { |
| dpstate->left_base_w = half_hdisplay - base_x; |
| dpstate->right_base_w = |
| base_x + base_w - half_hdisplay; |
| } |
| } else { |
| dpstate->left_base_x = 0; |
| dpstate->right_base_x = base_x - half_hdisplay; |
| |
| dpstate->left_base_w = 0; |
| dpstate->right_base_w = base_w; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void |
| dpu_atomic_set_top_plane_per_crtc(struct drm_plane_state **states, int n, |
| bool use_pc) |
| { |
| struct dpu_plane_state *dpstate; |
| bool found_l_top = false, found_r_top = false; |
| int i; |
| |
| for (i = n - 1; i >= 0; i--) { |
| dpstate = to_dpu_plane_state(states[i]); |
| if (use_pc) { |
| if (dpstate->left_src_w && !found_l_top) { |
| dpstate->is_left_top = true; |
| found_l_top = true; |
| } else { |
| dpstate->is_left_top = false; |
| } |
| |
| if (dpstate->right_src_w && !found_r_top) { |
| dpstate->is_right_top = true; |
| found_r_top = true; |
| } else { |
| dpstate->is_right_top = false; |
| } |
| } else { |
| dpstate->is_top = (i == (n - 1)) ? true : false; |
| } |
| } |
| } |
| |
| static int |
| dpu_atomic_assign_plane_source_per_crtc(struct drm_plane_state **states, |
| int n, bool use_pc) |
| { |
| 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; |
| lb_prim_sel_t stage; |
| dpu_block_id_t blend; |
| unsigned int sid, src_sid; |
| unsigned int num_planes; |
| int i, j, k, l, m; |
| int total_asrc_num; |
| int s0_layer_cnt = 0, s1_layer_cnt = 0; |
| int s0_n = 0, s1_n = 0; |
| u32 src_a_mask, cap_mask, fe_mask, hs_mask, vs_mask; |
| bool need_fetcheco, need_hscaler, need_vscaler; |
| bool fmt_is_yuv; |
| bool alloc_aux_source; |
| |
| if (use_pc) { |
| for (i = 0; i < n; i++) { |
| dpstate = to_dpu_plane_state(states[i]); |
| |
| if (dpstate->left_src_w) |
| s0_n++; |
| |
| if (dpstate->right_src_w) |
| s1_n++; |
| } |
| } else { |
| s0_n = n; |
| s1_n = n; |
| } |
| |
| /* 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->format->format); |
| fmt_is_yuv = drm_format_is_yuv(fb->format->format); |
| grp = dplane->grp; |
| alloc_aux_source = false; |
| |
| if (use_pc) |
| sid = dpstate->left_src_w ? 0 : 1; |
| else |
| sid = dplane->stream_id; |
| |
| again: |
| if (alloc_aux_source) |
| sid ^= 1; |
| |
| 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; |
| |
| if (alloc_aux_source) |
| dpstate->aux_source = sources[m]; |
| else |
| dpstate->source = sources[m]; |
| |
| /* assign stage and blend */ |
| if (sid) { |
| j = grp->hw_plane_num - (s1_n - s1_layer_cnt); |
| stage = s1_layer_cnt ? stages[j - 1] : cf_stages[sid]; |
| blend = blends[j]; |
| |
| s1_layer_cnt++; |
| } else { |
| stage = s0_layer_cnt ? |
| stages[s0_layer_cnt - 1] : cf_stages[sid]; |
| blend = blends[s0_layer_cnt]; |
| |
| s0_layer_cnt++; |
| } |
| |
| if (alloc_aux_source) { |
| dpstate->aux_stage = stage; |
| dpstate->aux_blend = blend; |
| } else { |
| dpstate->stage = stage; |
| dpstate->blend = blend; |
| } |
| |
| if (dpstate->need_aux_source && !alloc_aux_source) { |
| alloc_aux_source = true; |
| goto again; |
| } |
| } |
| |
| 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 || |
| old_dpstate->aux_stage != new_dpstate->aux_stage || |
| old_dpstate->aux_source != new_dpstate->aux_source || |
| old_dpstate->aux_blend != new_dpstate->aux_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; |
| struct drm_plane_state *plane_state; |
| struct dpu_plane_state *dpstate; |
| struct drm_framebuffer *fb; |
| 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]; |
| int half_hdisplay = 0; |
| bool pipe_states_prone_to_put[MAX_CRTC]; |
| bool use_pc[MAX_DPU_PLANE_GRP]; |
| 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; |
| use_pc[i] = false; |
| 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) { |
| struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); |
| struct imx_crtc_state *imx_crtc_state; |
| struct dpu_crtc_state *dcstate; |
| bool need_left, need_right, need_aux_source, use_pc_per_crtc; |
| |
| use_pc_per_crtc = false; |
| |
| 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); |
| |
| imx_crtc_state = to_imx_crtc_state(crtc_state); |
| dcstate = to_dpu_crtc_state(imx_crtc_state); |
| |
| if (crtc_state->enable) { |
| if (use_pc[dpu_crtc->crtc_grp_id]) |
| return -EINVAL; |
| |
| if (crtc_state->adjusted_mode.clock > |
| dpu_crtc->syncmode_min_prate || |
| crtc_state->adjusted_mode.hdisplay > |
| dpu_crtc->singlemode_max_width) { |
| if (!dpu_crtc->has_pc) |
| return -EINVAL; |
| |
| use_pc_per_crtc = true; |
| } |
| } |
| |
| if (use_pc_per_crtc) { |
| use_pc[dpu_crtc->crtc_grp_id] = true; |
| half_hdisplay = crtc_state->adjusted_mode.hdisplay >> 1; |
| } |
| |
| dcstate->use_pc = use_pc_per_crtc; |
| |
| drm_for_each_plane_mask(plane, dev, crtc_state->plane_mask) { |
| plane_state = drm_atomic_get_plane_state(state, plane); |
| dpstate = to_dpu_plane_state(plane_state); |
| fb = plane_state->fb; |
| dpu_plane = to_dpu_plane(plane); |
| grp_id = dpu_plane->grp->id; |
| active_plane[grp_id]++; |
| |
| need_left = false; |
| need_right = false; |
| need_aux_source = false; |
| |
| if (use_pc_per_crtc) { |
| if (plane_state->crtc_x < half_hdisplay) |
| need_left = true; |
| |
| if ((plane_state->crtc_w + |
| plane_state->crtc_x) > half_hdisplay) |
| need_right = true; |
| |
| if (need_left && need_right) { |
| need_aux_source = true; |
| active_plane[grp_id]++; |
| } |
| } |
| |
| if (need_left && need_right) { |
| dpstate->left_crtc_w = half_hdisplay; |
| dpstate->left_crtc_w -= plane_state->crtc_x; |
| |
| dpstate->left_src_w = dpstate->left_crtc_w; |
| } else if (need_left) { |
| dpstate->left_crtc_w = plane_state->crtc_w; |
| dpstate->left_src_w = plane_state->src_w >> 16; |
| } else { |
| dpstate->left_crtc_w = 0; |
| dpstate->left_src_w = 0; |
| } |
| |
| if (need_right && need_left) { |
| dpstate->right_crtc_w = plane_state->crtc_x + |
| plane_state->crtc_w; |
| dpstate->right_crtc_w -= half_hdisplay; |
| |
| dpstate->right_src_w = dpstate->right_crtc_w; |
| } else if (need_right) { |
| dpstate->right_crtc_w = plane_state->crtc_w; |
| dpstate->right_src_w = plane_state->src_w >> 16; |
| } else { |
| dpstate->right_crtc_w = 0; |
| dpstate->right_src_w = 0; |
| } |
| |
| if (drm_format_num_planes(fb->format->format) > 1) { |
| active_plane_fetcheco[grp_id]++; |
| if (need_aux_source) |
| active_plane_fetcheco[grp_id]++; |
| } |
| |
| if (plane_state->src_w >> 16 != plane_state->crtc_w) { |
| if (use_pc_per_crtc) |
| return -EINVAL; |
| |
| active_plane_hscale[grp_id]++; |
| } |
| |
| if (plane_state->src_h >> 16 != plane_state->crtc_h) { |
| if (use_pc_per_crtc) |
| return -EINVAL; |
| |
| active_plane_vscale[grp_id]++; |
| } |
| |
| if (grp[grp_id] == NULL) |
| grp[grp_id] = dpu_plane->grp; |
| |
| dpstate->need_aux_source = need_aux_source; |
| } |
| } |
| |
| /* 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 dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); |
| 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(crtc_state, states, |
| n, use_pc[dpu_crtc->crtc_grp_id]); |
| if (ret) { |
| kfree(states); |
| return ret; |
| } |
| |
| dpu_atomic_set_top_plane_per_crtc(states, n, |
| use_pc[dpu_crtc->crtc_grp_id]); |
| |
| ret = dpu_atomic_assign_plane_source_per_crtc(states, n, |
| use_pc[dpu_crtc->crtc_grp_id]); |
| 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 void dpu_drm_commit_tail(struct drm_atomic_state *old_state) |
| { |
| struct drm_device *dev = old_state->dev; |
| |
| drm_atomic_helper_wait_for_fences(dev, old_state, false); |
| |
| drm_atomic_helper_wait_for_dependencies(old_state); |
| |
| drm_atomic_helper_commit_tail(old_state); |
| |
| drm_atomic_helper_commit_cleanup_done(old_state); |
| |
| drm_atomic_state_put(old_state); |
| } |
| |
| static void dpu_drm_commit_work(struct work_struct *work) |
| { |
| struct drm_atomic_state *state = container_of(work, |
| struct drm_atomic_state, |
| commit_work); |
| dpu_drm_commit_tail(state); |
| } |
| |
| /* |
| * This is almost a copy of drm_atomic_helper_commit(). |
| * For nonblock commits, we queue the work on a freezable and unbound work queue |
| * of our own instead of system_unbound_wq to make sure work items on the work |
| * queue are drained in the freeze phase of the system suspend operations. |
| */ |
| static int dpu_drm_atomic_commit(struct drm_device *dev, |
| struct drm_atomic_state *state, |
| bool nonblock) |
| { |
| struct imx_drm_device *imxdrm = dev->dev_private; |
| int ret; |
| |
| if (state->async_update) { |
| ret = drm_atomic_helper_prepare_planes(dev, state); |
| if (ret) |
| return ret; |
| |
| drm_atomic_helper_async_commit(dev, state); |
| drm_atomic_helper_cleanup_planes(dev, state); |
| |
| return 0; |
| } |
| |
| ret = drm_atomic_helper_setup_commit(state, nonblock); |
| if (ret) |
| return ret; |
| |
| INIT_WORK(&state->commit_work, dpu_drm_commit_work); |
| |
| ret = drm_atomic_helper_prepare_planes(dev, state); |
| if (ret) |
| return ret; |
| |
| if (!nonblock) { |
| ret = drm_atomic_helper_wait_for_fences(dev, state, true); |
| if (ret) |
| goto err; |
| } |
| |
| ret = drm_atomic_helper_swap_state(state, true); |
| if (ret) |
| goto err; |
| |
| drm_atomic_state_get(state); |
| if (nonblock) |
| queue_work(imxdrm->dpu_nonblock_commit_wq, &state->commit_work); |
| else |
| dpu_drm_commit_tail(state); |
| |
| return 0; |
| |
| err: |
| drm_atomic_helper_cleanup_planes(dev, state); |
| return ret; |
| } |
| |
| 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, |
| }; |