| /* |
| * Copyright 2017 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_fetchdecode *fd; |
| struct dpu_fetcheco *fe; |
| struct dpu_hscaler *hs; |
| struct dpu_vscaler *vs; |
| unsigned int sid, src_sid; |
| int i, j, k; |
| int fd_id; |
| u32 cap_mask, fe_mask, hs_mask, vs_mask; |
| |
| /* 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; |
| grp = dplane->grp; |
| sid = dplane->stream_id; |
| |
| /* assign source */ |
| mutex_lock(&grp->mutex); |
| for (k = 0; k < grp->hw_plane_num; k++) { |
| /* already used by others? */ |
| if (grp->src_mask & BIT(k)) |
| continue; |
| |
| fd_id = source_to_id(sources[k]); |
| |
| fd = grp->res.fd[fd_id]; |
| |
| /* avoid on-the-fly/hot migration */ |
| src_sid = fetchdecode_get_stream_id(fd); |
| if (src_sid && src_sid != BIT(sid)) |
| continue; |
| |
| cap_mask = fetchdecode_get_vproc_mask(fd); |
| |
| if (drm_format_num_planes(fb->pixel_format) > 1) { |
| fe = fetchdecode_get_fetcheco(fd); |
| |
| /* avoid on-the-fly/hot migration */ |
| src_sid = fetcheco_get_stream_id(fe); |
| if (src_sid && src_sid != BIT(sid)) |
| continue; |
| |
| /* fetch unit has the fetcheco capability? */ |
| if (!dpu_vproc_has_fetcheco_cap(cap_mask)) |
| continue; |
| |
| fe_mask = dpu_vproc_get_fetcheco_cap(cap_mask); |
| |
| /* fetcheco available? */ |
| if (grp->src_use_vproc_mask & fe_mask) |
| continue; |
| |
| grp->src_use_vproc_mask |= fe_mask; |
| } |
| |
| if (states[i]->src_w >> 16 != states[i]->crtc_w) { |
| hs = fetchdecode_get_hscaler(fd); |
| |
| /* avoid on-the-fly/hot migration */ |
| src_sid = hscaler_get_stream_id(hs); |
| if (src_sid && src_sid != BIT(sid)) |
| continue; |
| |
| /* fetch unit has the hscale capability? */ |
| if (!dpu_vproc_has_hscale_cap(cap_mask)) |
| continue; |
| |
| hs_mask = dpu_vproc_get_hscale_cap(cap_mask); |
| |
| /* hscaler available? */ |
| if (grp->src_use_vproc_mask & hs_mask) |
| continue; |
| |
| grp->src_use_vproc_mask |= hs_mask; |
| } |
| |
| if (states[i]->src_h >> 16 != states[i]->crtc_h) { |
| vs = fetchdecode_get_vscaler(fd); |
| |
| /* avoid on-the-fly/hot migration */ |
| src_sid = vscaler_get_stream_id(vs); |
| if (src_sid && src_sid != BIT(sid)) |
| continue; |
| |
| /* fetch unit has the vscale capability? */ |
| if (!dpu_vproc_has_vscale_cap(cap_mask)) |
| continue; |
| |
| vs_mask = dpu_vproc_get_vscale_cap(cap_mask); |
| |
| /* vscaler available? */ |
| if (grp->src_use_vproc_mask & vs_mask) |
| continue; |
| |
| grp->src_use_vproc_mask |= vs_mask; |
| } |
| |
| grp->src_mask |= BIT(k); |
| break; |
| } |
| mutex_unlock(&grp->mutex); |
| |
| if (k == grp->hw_plane_num) |
| return -EINVAL; |
| |
| dpstate->source = sources[k]; |
| |
| /* 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_mask = 0; |
| 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, |
| }; |