diff options
Diffstat (limited to 'drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c')
-rw-r--r-- | drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c | 1356 |
1 files changed, 1356 insertions, 0 deletions
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c new file mode 100644 index 0000000000..f3e7441726 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c @@ -0,0 +1,1356 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang <james.qian.wang@arm.com> + * + */ + +#include <drm/drm_print.h> +#include <linux/clk.h> +#include "komeda_dev.h" +#include "komeda_kms.h" +#include "komeda_pipeline.h" +#include "komeda_framebuffer.h" + +static inline bool is_switching_user(void *old, void *new) +{ + if (!old || !new) + return false; + + return old != new; +} + +static struct komeda_pipeline_state * +komeda_pipeline_get_state(struct komeda_pipeline *pipe, + struct drm_atomic_state *state) +{ + struct drm_private_state *priv_st; + + priv_st = drm_atomic_get_private_obj_state(state, &pipe->obj); + if (IS_ERR(priv_st)) + return ERR_CAST(priv_st); + + return priv_to_pipe_st(priv_st); +} + +struct komeda_pipeline_state * +komeda_pipeline_get_old_state(struct komeda_pipeline *pipe, + struct drm_atomic_state *state) +{ + struct drm_private_state *priv_st; + + priv_st = drm_atomic_get_old_private_obj_state(state, &pipe->obj); + if (priv_st) + return priv_to_pipe_st(priv_st); + return NULL; +} + +static struct komeda_pipeline_state * +komeda_pipeline_get_new_state(struct komeda_pipeline *pipe, + struct drm_atomic_state *state) +{ + struct drm_private_state *priv_st; + + priv_st = drm_atomic_get_new_private_obj_state(state, &pipe->obj); + if (priv_st) + return priv_to_pipe_st(priv_st); + return NULL; +} + +/* Assign pipeline for crtc */ +static struct komeda_pipeline_state * +komeda_pipeline_get_state_and_set_crtc(struct komeda_pipeline *pipe, + struct drm_atomic_state *state, + struct drm_crtc *crtc) +{ + struct komeda_pipeline_state *st; + + st = komeda_pipeline_get_state(pipe, state); + if (IS_ERR(st)) + return st; + + if (is_switching_user(crtc, st->crtc)) { + DRM_DEBUG_ATOMIC("CRTC%d required pipeline%d is busy.\n", + drm_crtc_index(crtc), pipe->id); + return ERR_PTR(-EBUSY); + } + + /* pipeline only can be disabled when the it is free or unused */ + if (!crtc && st->active_comps) { + DRM_DEBUG_ATOMIC("Disabling a busy pipeline:%d.\n", pipe->id); + return ERR_PTR(-EBUSY); + } + + st->crtc = crtc; + + if (crtc) { + struct komeda_crtc_state *kcrtc_st; + + kcrtc_st = to_kcrtc_st(drm_atomic_get_new_crtc_state(state, + crtc)); + + kcrtc_st->active_pipes |= BIT(pipe->id); + kcrtc_st->affected_pipes |= BIT(pipe->id); + } + return st; +} + +static struct komeda_component_state * +komeda_component_get_state(struct komeda_component *c, + struct drm_atomic_state *state) +{ + struct drm_private_state *priv_st; + + WARN_ON(!drm_modeset_is_locked(&c->pipeline->obj.lock)); + + priv_st = drm_atomic_get_private_obj_state(state, &c->obj); + if (IS_ERR(priv_st)) + return ERR_CAST(priv_st); + + return priv_to_comp_st(priv_st); +} + +static struct komeda_component_state * +komeda_component_get_old_state(struct komeda_component *c, + struct drm_atomic_state *state) +{ + struct drm_private_state *priv_st; + + priv_st = drm_atomic_get_old_private_obj_state(state, &c->obj); + if (priv_st) + return priv_to_comp_st(priv_st); + return NULL; +} + +/** + * komeda_component_get_state_and_set_user() + * + * @c: component to get state and set user + * @state: global atomic state + * @user: direct user, the binding user + * @crtc: the CRTC user, the big boss :) + * + * This function accepts two users: + * - The direct user: can be plane/crtc/wb_connector depends on component + * - The big boss (CRTC) + * CRTC is the big boss (the final user), because all component resources + * eventually will be assigned to CRTC, like the layer will be binding to + * kms_plane, but kms plane will be binding to a CRTC eventually. + * + * The big boss (CRTC) is for pipeline assignment, since &komeda_component isn't + * independent and can be assigned to CRTC freely, but belongs to a specific + * pipeline, only pipeline can be shared between crtc, and pipeline as a whole + * (include all the internal components) assigned to a specific CRTC. + * + * So when set a user to komeda_component, need first to check the status of + * component->pipeline to see if the pipeline is available on this specific + * CRTC. if the pipeline is busy (assigned to another CRTC), even the required + * component is free, the component still cannot be assigned to the direct user. + */ +static struct komeda_component_state * +komeda_component_get_state_and_set_user(struct komeda_component *c, + struct drm_atomic_state *state, + void *user, + struct drm_crtc *crtc) +{ + struct komeda_pipeline_state *pipe_st; + struct komeda_component_state *st; + + /* First check if the pipeline is available */ + pipe_st = komeda_pipeline_get_state_and_set_crtc(c->pipeline, + state, crtc); + if (IS_ERR(pipe_st)) + return ERR_CAST(pipe_st); + + st = komeda_component_get_state(c, state); + if (IS_ERR(st)) + return st; + + /* check if the component has been occupied */ + if (is_switching_user(user, st->binding_user)) { + DRM_DEBUG_ATOMIC("required %s is busy.\n", c->name); + return ERR_PTR(-EBUSY); + } + + st->binding_user = user; + /* mark the component as active if user is valid */ + if (st->binding_user) + pipe_st->active_comps |= BIT(c->id); + + return st; +} + +static void +komeda_component_add_input(struct komeda_component_state *state, + struct komeda_component_output *input, + int idx) +{ + struct komeda_component *c = state->component; + + WARN_ON((idx < 0 || idx >= c->max_active_inputs)); + + /* since the inputs[i] is only valid when it is active. So if a input[i] + * is a newly enabled input which switches from disable to enable, then + * the old inputs[i] is undefined (NOT zeroed), we can not rely on + * memcmp, but directly mark it changed + */ + if (!has_bit(idx, state->affected_inputs) || + memcmp(&state->inputs[idx], input, sizeof(*input))) { + memcpy(&state->inputs[idx], input, sizeof(*input)); + state->changed_active_inputs |= BIT(idx); + } + state->active_inputs |= BIT(idx); + state->affected_inputs |= BIT(idx); +} + +static int +komeda_component_check_input(struct komeda_component_state *state, + struct komeda_component_output *input, + int idx) +{ + struct komeda_component *c = state->component; + + if ((idx < 0) || (idx >= c->max_active_inputs)) { + DRM_DEBUG_ATOMIC("%s required an invalid %s-input[%d].\n", + input->component->name, c->name, idx); + return -EINVAL; + } + + if (has_bit(idx, state->active_inputs)) { + DRM_DEBUG_ATOMIC("%s required %s-input[%d] has been occupied already.\n", + input->component->name, c->name, idx); + return -EINVAL; + } + + return 0; +} + +static void +komeda_component_set_output(struct komeda_component_output *output, + struct komeda_component *comp, + u8 output_port) +{ + output->component = comp; + output->output_port = output_port; +} + +static int +komeda_component_validate_private(struct komeda_component *c, + struct komeda_component_state *st) +{ + int err; + + if (!c->funcs->validate) + return 0; + + err = c->funcs->validate(c, st); + if (err) + DRM_DEBUG_ATOMIC("%s validate private failed.\n", c->name); + + return err; +} + +/* Get current available scaler from the component->supported_outputs */ +static struct komeda_scaler * +komeda_component_get_avail_scaler(struct komeda_component *c, + struct drm_atomic_state *state) +{ + struct komeda_pipeline_state *pipe_st; + u32 avail_scalers; + + pipe_st = komeda_pipeline_get_state(c->pipeline, state); + if (!pipe_st) + return NULL; + + avail_scalers = (pipe_st->active_comps & KOMEDA_PIPELINE_SCALERS) ^ + KOMEDA_PIPELINE_SCALERS; + + c = komeda_component_pickup_output(c, avail_scalers); + + return to_scaler(c); +} + +static void +komeda_rotate_data_flow(struct komeda_data_flow_cfg *dflow, u32 rot) +{ + if (drm_rotation_90_or_270(rot)) { + swap(dflow->in_h, dflow->in_w); + swap(dflow->total_in_h, dflow->total_in_w); + } +} + +static int +komeda_layer_check_cfg(struct komeda_layer *layer, + struct komeda_fb *kfb, + struct komeda_data_flow_cfg *dflow) +{ + u32 src_x, src_y, src_w, src_h; + u32 line_sz, max_line_sz; + + if (!komeda_fb_is_layer_supported(kfb, layer->layer_type, dflow->rot)) + return -EINVAL; + + if (layer->base.id == KOMEDA_COMPONENT_WB_LAYER) { + src_x = dflow->out_x; + src_y = dflow->out_y; + src_w = dflow->out_w; + src_h = dflow->out_h; + } else { + src_x = dflow->in_x; + src_y = dflow->in_y; + src_w = dflow->in_w; + src_h = dflow->in_h; + } + + if (komeda_fb_check_src_coords(kfb, src_x, src_y, src_w, src_h)) + return -EINVAL; + + if (!malidp_in_range(&layer->hsize_in, src_w)) { + DRM_DEBUG_ATOMIC("invalidate src_w %d.\n", src_w); + return -EINVAL; + } + + if (!malidp_in_range(&layer->vsize_in, src_h)) { + DRM_DEBUG_ATOMIC("invalidate src_h %d.\n", src_h); + return -EINVAL; + } + + if (drm_rotation_90_or_270(dflow->rot)) + line_sz = dflow->in_h; + else + line_sz = dflow->in_w; + + if (kfb->base.format->hsub > 1) + max_line_sz = layer->yuv_line_sz; + else + max_line_sz = layer->line_sz; + + if (line_sz > max_line_sz) { + DRM_DEBUG_ATOMIC("Required line_sz: %d exceeds the max size %d\n", + line_sz, max_line_sz); + return -EINVAL; + } + + return 0; +} + +static int +komeda_layer_validate(struct komeda_layer *layer, + struct komeda_plane_state *kplane_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_plane_state *plane_st = &kplane_st->base; + struct drm_framebuffer *fb = plane_st->fb; + struct komeda_fb *kfb = to_kfb(fb); + struct komeda_component_state *c_st; + struct komeda_layer_state *st; + int i, err; + + err = komeda_layer_check_cfg(layer, kfb, dflow); + if (err) + return err; + + c_st = komeda_component_get_state_and_set_user(&layer->base, + plane_st->state, plane_st->plane, plane_st->crtc); + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + st = to_layer_st(c_st); + + st->rot = dflow->rot; + + if (fb->modifier) { + st->hsize = kfb->aligned_w; + st->vsize = kfb->aligned_h; + st->afbc_crop_l = dflow->in_x; + st->afbc_crop_r = kfb->aligned_w - dflow->in_x - dflow->in_w; + st->afbc_crop_t = dflow->in_y; + st->afbc_crop_b = kfb->aligned_h - dflow->in_y - dflow->in_h; + } else { + st->hsize = dflow->in_w; + st->vsize = dflow->in_h; + st->afbc_crop_l = 0; + st->afbc_crop_r = 0; + st->afbc_crop_t = 0; + st->afbc_crop_b = 0; + } + + for (i = 0; i < fb->format->num_planes; i++) + st->addr[i] = komeda_fb_get_pixel_addr(kfb, dflow->in_x, + dflow->in_y, i); + + err = komeda_component_validate_private(&layer->base, c_st); + if (err) + return err; + + /* update the data flow for the next stage */ + komeda_component_set_output(&dflow->input, &layer->base, 0); + + /* + * The rotation has been handled by layer, so adjusted the data flow for + * the next stage. + */ + komeda_rotate_data_flow(dflow, st->rot); + + return 0; +} + +static int +komeda_wb_layer_validate(struct komeda_layer *wb_layer, + struct drm_connector_state *conn_st, + struct komeda_data_flow_cfg *dflow) +{ + struct komeda_fb *kfb = to_kfb(conn_st->writeback_job->fb); + struct komeda_component_state *c_st; + struct komeda_layer_state *st; + int i, err; + + err = komeda_layer_check_cfg(wb_layer, kfb, dflow); + if (err) + return err; + + c_st = komeda_component_get_state_and_set_user(&wb_layer->base, + conn_st->state, conn_st->connector, conn_st->crtc); + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + st = to_layer_st(c_st); + + st->hsize = dflow->out_w; + st->vsize = dflow->out_h; + + for (i = 0; i < kfb->base.format->num_planes; i++) + st->addr[i] = komeda_fb_get_pixel_addr(kfb, dflow->out_x, + dflow->out_y, i); + + komeda_component_add_input(&st->base, &dflow->input, 0); + komeda_component_set_output(&dflow->input, &wb_layer->base, 0); + + return 0; +} + +static bool scaling_ratio_valid(u32 size_in, u32 size_out, + u32 max_upscaling, u32 max_downscaling) +{ + if (size_out > size_in * max_upscaling) + return false; + else if (size_in > size_out * max_downscaling) + return false; + return true; +} + +static int +komeda_scaler_check_cfg(struct komeda_scaler *scaler, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + u32 hsize_in, vsize_in, hsize_out, vsize_out; + u32 max_upscaling; + + hsize_in = dflow->in_w; + vsize_in = dflow->in_h; + hsize_out = dflow->out_w; + vsize_out = dflow->out_h; + + if (!malidp_in_range(&scaler->hsize, hsize_in) || + !malidp_in_range(&scaler->hsize, hsize_out)) { + DRM_DEBUG_ATOMIC("Invalid horizontal sizes"); + return -EINVAL; + } + + if (!malidp_in_range(&scaler->vsize, vsize_in) || + !malidp_in_range(&scaler->vsize, vsize_out)) { + DRM_DEBUG_ATOMIC("Invalid vertical sizes"); + return -EINVAL; + } + + /* If input comes from compiz that means the scaling is for writeback + * and scaler can not do upscaling for writeback + */ + if (has_bit(dflow->input.component->id, KOMEDA_PIPELINE_COMPIZS)) + max_upscaling = 1; + else + max_upscaling = scaler->max_upscaling; + + if (!scaling_ratio_valid(hsize_in, hsize_out, max_upscaling, + scaler->max_downscaling)) { + DRM_DEBUG_ATOMIC("Invalid horizontal scaling ratio"); + return -EINVAL; + } + + if (!scaling_ratio_valid(vsize_in, vsize_out, max_upscaling, + scaler->max_downscaling)) { + DRM_DEBUG_ATOMIC("Invalid vertical scaling ratio"); + return -EINVAL; + } + + if (hsize_in > hsize_out || vsize_in > vsize_out) { + struct komeda_pipeline *pipe = scaler->base.pipeline; + int err; + + err = pipe->funcs->downscaling_clk_check(pipe, + &kcrtc_st->base.adjusted_mode, + komeda_crtc_get_aclk(kcrtc_st), dflow); + if (err) { + DRM_DEBUG_ATOMIC("aclk can't satisfy the clock requirement of the downscaling\n"); + return err; + } + } + + return 0; +} + +static int +komeda_scaler_validate(void *user, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_atomic_state *drm_st = kcrtc_st->base.state; + struct komeda_component_state *c_st; + struct komeda_scaler_state *st; + struct komeda_scaler *scaler; + int err = 0; + + if (!(dflow->en_scaling || dflow->en_img_enhancement)) + return 0; + + scaler = komeda_component_get_avail_scaler(dflow->input.component, + drm_st); + if (!scaler) { + DRM_DEBUG_ATOMIC("No scaler available"); + return -EINVAL; + } + + err = komeda_scaler_check_cfg(scaler, kcrtc_st, dflow); + if (err) + return err; + + c_st = komeda_component_get_state_and_set_user(&scaler->base, + drm_st, user, kcrtc_st->base.crtc); + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + st = to_scaler_st(c_st); + + st->hsize_in = dflow->in_w; + st->vsize_in = dflow->in_h; + st->hsize_out = dflow->out_w; + st->vsize_out = dflow->out_h; + st->right_crop = dflow->right_crop; + st->left_crop = dflow->left_crop; + st->total_vsize_in = dflow->total_in_h; + st->total_hsize_in = dflow->total_in_w; + st->total_hsize_out = dflow->total_out_w; + + /* Enable alpha processing if the next stage needs the pixel alpha */ + st->en_alpha = dflow->pixel_blend_mode != DRM_MODE_BLEND_PIXEL_NONE; + st->en_scaling = dflow->en_scaling; + st->en_img_enhancement = dflow->en_img_enhancement; + st->en_split = dflow->en_split; + st->right_part = dflow->right_part; + + komeda_component_add_input(&st->base, &dflow->input, 0); + komeda_component_set_output(&dflow->input, &scaler->base, 0); + return err; +} + +static void komeda_split_data_flow(struct komeda_scaler *scaler, + struct komeda_data_flow_cfg *dflow, + struct komeda_data_flow_cfg *l_dflow, + struct komeda_data_flow_cfg *r_dflow); + +static int +komeda_splitter_validate(struct komeda_splitter *splitter, + struct drm_connector_state *conn_st, + struct komeda_data_flow_cfg *dflow, + struct komeda_data_flow_cfg *l_output, + struct komeda_data_flow_cfg *r_output) +{ + struct komeda_component_state *c_st; + struct komeda_splitter_state *st; + + if (!splitter) { + DRM_DEBUG_ATOMIC("Current HW doesn't support splitter.\n"); + return -EINVAL; + } + + if (!malidp_in_range(&splitter->hsize, dflow->in_w)) { + DRM_DEBUG_ATOMIC("split in_w:%d is out of the acceptable range.\n", + dflow->in_w); + return -EINVAL; + } + + if (!malidp_in_range(&splitter->vsize, dflow->in_h)) { + DRM_DEBUG_ATOMIC("split in_h: %d exceeds the acceptable range.\n", + dflow->in_h); + return -EINVAL; + } + + c_st = komeda_component_get_state_and_set_user(&splitter->base, + conn_st->state, conn_st->connector, conn_st->crtc); + + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + komeda_split_data_flow(splitter->base.pipeline->scalers[0], + dflow, l_output, r_output); + + st = to_splitter_st(c_st); + st->hsize = dflow->in_w; + st->vsize = dflow->in_h; + st->overlap = dflow->overlap; + + komeda_component_add_input(&st->base, &dflow->input, 0); + komeda_component_set_output(&l_output->input, &splitter->base, 0); + komeda_component_set_output(&r_output->input, &splitter->base, 1); + + return 0; +} + +static int +komeda_merger_validate(struct komeda_merger *merger, + void *user, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *left_input, + struct komeda_data_flow_cfg *right_input, + struct komeda_data_flow_cfg *output) +{ + struct komeda_component_state *c_st; + struct komeda_merger_state *st; + int err = 0; + + if (!merger) { + DRM_DEBUG_ATOMIC("No merger is available"); + return -EINVAL; + } + + if (!malidp_in_range(&merger->hsize_merged, output->out_w)) { + DRM_DEBUG_ATOMIC("merged_w: %d is out of the accepted range.\n", + output->out_w); + return -EINVAL; + } + + if (!malidp_in_range(&merger->vsize_merged, output->out_h)) { + DRM_DEBUG_ATOMIC("merged_h: %d is out of the accepted range.\n", + output->out_h); + return -EINVAL; + } + + c_st = komeda_component_get_state_and_set_user(&merger->base, + kcrtc_st->base.state, kcrtc_st->base.crtc, kcrtc_st->base.crtc); + + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + st = to_merger_st(c_st); + st->hsize_merged = output->out_w; + st->vsize_merged = output->out_h; + + komeda_component_add_input(c_st, &left_input->input, 0); + komeda_component_add_input(c_st, &right_input->input, 1); + komeda_component_set_output(&output->input, &merger->base, 0); + + return err; +} + +void pipeline_composition_size(struct komeda_crtc_state *kcrtc_st, + u16 *hsize, u16 *vsize) +{ + struct drm_display_mode *m = &kcrtc_st->base.adjusted_mode; + + if (hsize) + *hsize = m->hdisplay; + if (vsize) + *vsize = m->vdisplay; +} + +static int +komeda_compiz_set_input(struct komeda_compiz *compiz, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_atomic_state *drm_st = kcrtc_st->base.state; + struct komeda_component_state *c_st, *old_st; + struct komeda_compiz_input_cfg *cin; + u16 compiz_w, compiz_h; + int idx = dflow->blending_zorder; + + pipeline_composition_size(kcrtc_st, &compiz_w, &compiz_h); + /* check display rect */ + if ((dflow->out_x + dflow->out_w > compiz_w) || + (dflow->out_y + dflow->out_h > compiz_h) || + dflow->out_w == 0 || dflow->out_h == 0) { + DRM_DEBUG_ATOMIC("invalid disp rect [x=%d, y=%d, w=%d, h=%d]\n", + dflow->out_x, dflow->out_y, + dflow->out_w, dflow->out_h); + return -EINVAL; + } + + c_st = komeda_component_get_state_and_set_user(&compiz->base, drm_st, + kcrtc_st->base.crtc, kcrtc_st->base.crtc); + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + if (komeda_component_check_input(c_st, &dflow->input, idx)) + return -EINVAL; + + cin = &(to_compiz_st(c_st)->cins[idx]); + + cin->hsize = dflow->out_w; + cin->vsize = dflow->out_h; + cin->hoffset = dflow->out_x; + cin->voffset = dflow->out_y; + cin->pixel_blend_mode = dflow->pixel_blend_mode; + cin->layer_alpha = dflow->layer_alpha; + + old_st = komeda_component_get_old_state(&compiz->base, drm_st); + + /* compare with old to check if this input has been changed */ + if (WARN_ON(!old_st) || + memcmp(&(to_compiz_st(old_st)->cins[idx]), cin, sizeof(*cin))) + c_st->changed_active_inputs |= BIT(idx); + + komeda_component_add_input(c_st, &dflow->input, idx); + komeda_component_set_output(&dflow->input, &compiz->base, 0); + + return 0; +} + +static int +komeda_compiz_validate(struct komeda_compiz *compiz, + struct komeda_crtc_state *state, + struct komeda_data_flow_cfg *dflow) +{ + struct komeda_component_state *c_st; + struct komeda_compiz_state *st; + + c_st = komeda_component_get_state_and_set_user(&compiz->base, + state->base.state, state->base.crtc, state->base.crtc); + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + st = to_compiz_st(c_st); + + pipeline_composition_size(state, &st->hsize, &st->vsize); + + komeda_component_set_output(&dflow->input, &compiz->base, 0); + + /* compiz output dflow will be fed to the next pipeline stage, prepare + * the data flow configuration for the next stage + */ + if (dflow) { + dflow->in_w = st->hsize; + dflow->in_h = st->vsize; + dflow->out_w = dflow->in_w; + dflow->out_h = dflow->in_h; + /* the output data of compiz doesn't have alpha, it only can be + * used as bottom layer when blend it with master layers + */ + dflow->pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; + dflow->layer_alpha = 0xFF; + dflow->blending_zorder = 0; + } + + return 0; +} + +static int +komeda_improc_validate(struct komeda_improc *improc, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_crtc *crtc = kcrtc_st->base.crtc; + struct drm_crtc_state *crtc_st = &kcrtc_st->base; + struct komeda_component_state *c_st; + struct komeda_improc_state *st; + + c_st = komeda_component_get_state_and_set_user(&improc->base, + kcrtc_st->base.state, crtc, crtc); + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + st = to_improc_st(c_st); + + st->hsize = dflow->in_w; + st->vsize = dflow->in_h; + + if (drm_atomic_crtc_needs_modeset(crtc_st)) { + u32 output_depths, output_formats; + u32 avail_depths, avail_formats; + + komeda_crtc_get_color_config(crtc_st, &output_depths, + &output_formats); + + avail_depths = output_depths & improc->supported_color_depths; + if (avail_depths == 0) { + DRM_DEBUG_ATOMIC("No available color depths, conn depths: 0x%x & display: 0x%x\n", + output_depths, + improc->supported_color_depths); + return -EINVAL; + } + + avail_formats = output_formats & + improc->supported_color_formats; + if (!avail_formats) { + DRM_DEBUG_ATOMIC("No available color_formats, conn formats 0x%x & display: 0x%x\n", + output_formats, + improc->supported_color_formats); + return -EINVAL; + } + + st->color_depth = __fls(avail_depths); + st->color_format = BIT(__ffs(avail_formats)); + } + + if (kcrtc_st->base.color_mgmt_changed) { + drm_lut_to_fgamma_coeffs(kcrtc_st->base.gamma_lut, + st->fgamma_coeffs); + drm_ctm_to_coeffs(kcrtc_st->base.ctm, st->ctm_coeffs); + } + + komeda_component_add_input(&st->base, &dflow->input, 0); + komeda_component_set_output(&dflow->input, &improc->base, 0); + + return 0; +} + +static int +komeda_timing_ctrlr_validate(struct komeda_timing_ctrlr *ctrlr, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_crtc *crtc = kcrtc_st->base.crtc; + struct komeda_timing_ctrlr_state *st; + struct komeda_component_state *c_st; + + c_st = komeda_component_get_state_and_set_user(&ctrlr->base, + kcrtc_st->base.state, crtc, crtc); + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + st = to_ctrlr_st(c_st); + + komeda_component_add_input(&st->base, &dflow->input, 0); + komeda_component_set_output(&dflow->input, &ctrlr->base, 0); + + return 0; +} + +void komeda_complete_data_flow_cfg(struct komeda_layer *layer, + struct komeda_data_flow_cfg *dflow, + struct drm_framebuffer *fb) +{ + struct komeda_scaler *scaler = layer->base.pipeline->scalers[0]; + u32 w = dflow->in_w; + u32 h = dflow->in_h; + + dflow->total_in_w = dflow->in_w; + dflow->total_in_h = dflow->in_h; + dflow->total_out_w = dflow->out_w; + + /* if format doesn't have alpha, fix blend mode to PIXEL_NONE */ + if (!fb->format->has_alpha) + dflow->pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; + + if (drm_rotation_90_or_270(dflow->rot)) + swap(w, h); + + dflow->en_scaling = (w != dflow->out_w) || (h != dflow->out_h); + dflow->is_yuv = fb->format->is_yuv; + + /* try to enable image enhancer if data flow is a 2x+ upscaling */ + dflow->en_img_enhancement = dflow->out_w >= 2 * w || + dflow->out_h >= 2 * h; + + /* try to enable split if scaling exceed the scaler's acceptable + * input/output range. + */ + if (dflow->en_scaling && scaler) + dflow->en_split = !malidp_in_range(&scaler->hsize, dflow->in_w) || + !malidp_in_range(&scaler->hsize, dflow->out_w); +} + +static bool merger_is_available(struct komeda_pipeline *pipe, + struct komeda_data_flow_cfg *dflow) +{ + u32 avail_inputs = pipe->merger ? + pipe->merger->base.supported_inputs : 0; + + return has_bit(dflow->input.component->id, avail_inputs); +} + +int komeda_build_layer_data_flow(struct komeda_layer *layer, + struct komeda_plane_state *kplane_st, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_plane *plane = kplane_st->base.plane; + struct komeda_pipeline *pipe = layer->base.pipeline; + int err; + + DRM_DEBUG_ATOMIC("%s handling [PLANE:%d:%s]: src[x/y:%d/%d, w/h:%d/%d] disp[x/y:%d/%d, w/h:%d/%d]", + layer->base.name, plane->base.id, plane->name, + dflow->in_x, dflow->in_y, dflow->in_w, dflow->in_h, + dflow->out_x, dflow->out_y, dflow->out_w, dflow->out_h); + + err = komeda_layer_validate(layer, kplane_st, dflow); + if (err) + return err; + + err = komeda_scaler_validate(plane, kcrtc_st, dflow); + if (err) + return err; + + /* if split, check if can put the data flow into merger */ + if (dflow->en_split && merger_is_available(pipe, dflow)) + return 0; + + err = komeda_compiz_set_input(pipe->compiz, kcrtc_st, dflow); + + return err; +} + +/* + * Split is introduced for workaround scaler's input/output size limitation. + * The idea is simple, if one scaler can not fit the requirement, use two. + * So split splits the big source image to two half parts (left/right) and do + * the scaling by two scaler separately and independently. + * But split also imports an edge problem in the middle of the image when + * scaling, to avoid it, split isn't a simple half-and-half, but add an extra + * pixels (overlap) to both side, after split the left/right will be: + * - left: [0, src_length/2 + overlap] + * - right: [src_length/2 - overlap, src_length] + * The extra overlap do eliminate the edge problem, but which may also generates + * unnecessary pixels when scaling, we need to crop them before scaler output + * the result to the next stage. and for the how to crop, it depends on the + * unneeded pixels, another words the position where overlay has been added. + * - left: crop the right + * - right: crop the left + * + * The diagram for how to do the split + * + * <---------------------left->out_w ----------------> + * |--------------------------------|---right_crop-----| <- left after split + * \ \ / + * \ \<--overlap--->/ + * |-----------------|-------------|(Middle)------|-----------------| <- src + * /<---overlap--->\ \ + * / \ \ + * right after split->|-----left_crop---|--------------------------------| + * ^<------------------- right->out_w --------------->^ + * + * NOTE: To consistent with HW the output_w always contains the crop size. + */ + +static void komeda_split_data_flow(struct komeda_scaler *scaler, + struct komeda_data_flow_cfg *dflow, + struct komeda_data_flow_cfg *l_dflow, + struct komeda_data_flow_cfg *r_dflow) +{ + bool r90 = drm_rotation_90_or_270(dflow->rot); + bool flip_h = has_flip_h(dflow->rot); + u32 l_out, r_out, overlap; + + memcpy(l_dflow, dflow, sizeof(*dflow)); + memcpy(r_dflow, dflow, sizeof(*dflow)); + + l_dflow->right_part = false; + r_dflow->right_part = true; + r_dflow->blending_zorder = dflow->blending_zorder + 1; + + overlap = 0; + if (dflow->en_scaling && scaler) + overlap += scaler->scaling_split_overlap; + + /* original dflow may fed into splitter, and which doesn't need + * enhancement overlap + */ + dflow->overlap = overlap; + + if (dflow->en_img_enhancement && scaler) + overlap += scaler->enh_split_overlap; + + l_dflow->overlap = overlap; + r_dflow->overlap = overlap; + + /* split the origin content */ + /* left/right here always means the left/right part of display image, + * not the source Image + */ + /* DRM rotation is anti-clockwise */ + if (r90) { + if (dflow->en_scaling) { + l_dflow->in_h = ALIGN(dflow->in_h, 2) / 2 + l_dflow->overlap; + r_dflow->in_h = l_dflow->in_h; + } else if (dflow->en_img_enhancement) { + /* enhancer only */ + l_dflow->in_h = ALIGN(dflow->in_h, 2) / 2 + l_dflow->overlap; + r_dflow->in_h = dflow->in_h / 2 + r_dflow->overlap; + } else { + /* split without scaler, no overlap */ + l_dflow->in_h = ALIGN(((dflow->in_h + 1) >> 1), 2); + r_dflow->in_h = dflow->in_h - l_dflow->in_h; + } + + /* Consider YUV format, after split, the split source w/h + * may not aligned to 2. we have two choices for such case. + * 1. scaler is enabled (overlap != 0), we can do a alignment + * both left/right and crop the extra data by scaler. + * 2. scaler is not enabled, only align the split left + * src/disp, and the rest part assign to right + */ + if ((overlap != 0) && dflow->is_yuv) { + l_dflow->in_h = ALIGN(l_dflow->in_h, 2); + r_dflow->in_h = ALIGN(r_dflow->in_h, 2); + } + + if (flip_h) + l_dflow->in_y = dflow->in_y + dflow->in_h - l_dflow->in_h; + else + r_dflow->in_y = dflow->in_y + dflow->in_h - r_dflow->in_h; + } else { + if (dflow->en_scaling) { + l_dflow->in_w = ALIGN(dflow->in_w, 2) / 2 + l_dflow->overlap; + r_dflow->in_w = l_dflow->in_w; + } else if (dflow->en_img_enhancement) { + l_dflow->in_w = ALIGN(dflow->in_w, 2) / 2 + l_dflow->overlap; + r_dflow->in_w = dflow->in_w / 2 + r_dflow->overlap; + } else { + l_dflow->in_w = ALIGN(((dflow->in_w + 1) >> 1), 2); + r_dflow->in_w = dflow->in_w - l_dflow->in_w; + } + + /* do YUV alignment when scaler enabled */ + if ((overlap != 0) && dflow->is_yuv) { + l_dflow->in_w = ALIGN(l_dflow->in_w, 2); + r_dflow->in_w = ALIGN(r_dflow->in_w, 2); + } + + /* on flip_h, the left display content from the right-source */ + if (flip_h) + l_dflow->in_x = dflow->in_w + dflow->in_x - l_dflow->in_w; + else + r_dflow->in_x = dflow->in_w + dflow->in_x - r_dflow->in_w; + } + + /* split the disp_rect */ + if (dflow->en_scaling || dflow->en_img_enhancement) + l_dflow->out_w = ((dflow->out_w + 1) >> 1); + else + l_dflow->out_w = ALIGN(((dflow->out_w + 1) >> 1), 2); + + r_dflow->out_w = dflow->out_w - l_dflow->out_w; + + l_dflow->out_x = dflow->out_x; + r_dflow->out_x = l_dflow->out_w + l_dflow->out_x; + + /* calculate the scaling crop */ + /* left scaler output more data and do crop */ + if (r90) { + l_out = (dflow->out_w * l_dflow->in_h) / dflow->in_h; + r_out = (dflow->out_w * r_dflow->in_h) / dflow->in_h; + } else { + l_out = (dflow->out_w * l_dflow->in_w) / dflow->in_w; + r_out = (dflow->out_w * r_dflow->in_w) / dflow->in_w; + } + + l_dflow->left_crop = 0; + l_dflow->right_crop = l_out - l_dflow->out_w; + r_dflow->left_crop = r_out - r_dflow->out_w; + r_dflow->right_crop = 0; + + /* out_w includes the crop length */ + l_dflow->out_w += l_dflow->right_crop + l_dflow->left_crop; + r_dflow->out_w += r_dflow->right_crop + r_dflow->left_crop; +} + +/* For layer split, a plane state will be split to two data flows and handled + * by two separated komeda layer input pipelines. komeda supports two types of + * layer split: + * - none-scaling split: + * / layer-left -> \ + * plane_state compiz-> ... + * \ layer-right-> / + * + * - scaling split: + * / layer-left -> scaler->\ + * plane_state merger -> compiz-> ... + * \ layer-right-> scaler->/ + * + * Since merger only supports scaler as input, so for none-scaling split, two + * layer data flows will be output to compiz directly. for scaling_split, two + * data flow will be merged by merger firstly, then merger outputs one merged + * data flow to compiz. + */ +int komeda_build_layer_split_data_flow(struct komeda_layer *left, + struct komeda_plane_state *kplane_st, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_plane *plane = kplane_st->base.plane; + struct komeda_pipeline *pipe = left->base.pipeline; + struct komeda_layer *right = left->right; + struct komeda_data_flow_cfg l_dflow, r_dflow; + int err; + + komeda_split_data_flow(pipe->scalers[0], dflow, &l_dflow, &r_dflow); + + DRM_DEBUG_ATOMIC("Assign %s + %s to [PLANE:%d:%s]: " + "src[x/y:%d/%d, w/h:%d/%d] disp[x/y:%d/%d, w/h:%d/%d]", + left->base.name, right->base.name, + plane->base.id, plane->name, + dflow->in_x, dflow->in_y, dflow->in_w, dflow->in_h, + dflow->out_x, dflow->out_y, dflow->out_w, dflow->out_h); + + err = komeda_build_layer_data_flow(left, kplane_st, kcrtc_st, &l_dflow); + if (err) + return err; + + err = komeda_build_layer_data_flow(right, kplane_st, kcrtc_st, &r_dflow); + if (err) + return err; + + /* The rotation has been handled by layer, so adjusted the data flow */ + komeda_rotate_data_flow(dflow, dflow->rot); + + /* left and right dflow has been merged to compiz already, + * no need merger to merge them anymore. + */ + if (r_dflow.input.component == l_dflow.input.component) + return 0; + + /* line merger path */ + err = komeda_merger_validate(pipe->merger, plane, kcrtc_st, + &l_dflow, &r_dflow, dflow); + if (err) + return err; + + err = komeda_compiz_set_input(pipe->compiz, kcrtc_st, dflow); + + return err; +} + +/* writeback data path: compiz -> scaler -> wb_layer -> memory */ +int komeda_build_wb_data_flow(struct komeda_layer *wb_layer, + struct drm_connector_state *conn_st, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_connector *conn = conn_st->connector; + int err; + + err = komeda_scaler_validate(conn, kcrtc_st, dflow); + if (err) + return err; + + return komeda_wb_layer_validate(wb_layer, conn_st, dflow); +} + +/* writeback scaling split data path: + * /-> scaler ->\ + * compiz -> splitter merger -> wb_layer -> memory + * \-> scaler ->/ + */ +int komeda_build_wb_split_data_flow(struct komeda_layer *wb_layer, + struct drm_connector_state *conn_st, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct komeda_pipeline *pipe = wb_layer->base.pipeline; + struct drm_connector *conn = conn_st->connector; + struct komeda_data_flow_cfg l_dflow, r_dflow; + int err; + + err = komeda_splitter_validate(pipe->splitter, conn_st, + dflow, &l_dflow, &r_dflow); + if (err) + return err; + err = komeda_scaler_validate(conn, kcrtc_st, &l_dflow); + if (err) + return err; + + err = komeda_scaler_validate(conn, kcrtc_st, &r_dflow); + if (err) + return err; + + err = komeda_merger_validate(pipe->merger, conn_st, kcrtc_st, + &l_dflow, &r_dflow, dflow); + if (err) + return err; + + return komeda_wb_layer_validate(wb_layer, conn_st, dflow); +} + +/* build display output data flow, the data path is: + * compiz -> improc -> timing_ctrlr + */ +int komeda_build_display_data_flow(struct komeda_crtc *kcrtc, + struct komeda_crtc_state *kcrtc_st) +{ + struct komeda_pipeline *master = kcrtc->master; + struct komeda_pipeline *slave = kcrtc->slave; + struct komeda_data_flow_cfg m_dflow; /* master data flow */ + struct komeda_data_flow_cfg s_dflow; /* slave data flow */ + int err; + + memset(&m_dflow, 0, sizeof(m_dflow)); + memset(&s_dflow, 0, sizeof(s_dflow)); + + if (slave && has_bit(slave->id, kcrtc_st->active_pipes)) { + err = komeda_compiz_validate(slave->compiz, kcrtc_st, &s_dflow); + if (err) + return err; + + /* merge the slave dflow into master pipeline */ + err = komeda_compiz_set_input(master->compiz, kcrtc_st, + &s_dflow); + if (err) + return err; + } + + err = komeda_compiz_validate(master->compiz, kcrtc_st, &m_dflow); + if (err) + return err; + + err = komeda_improc_validate(master->improc, kcrtc_st, &m_dflow); + if (err) + return err; + + err = komeda_timing_ctrlr_validate(master->ctrlr, kcrtc_st, &m_dflow); + if (err) + return err; + + return 0; +} + +static int +komeda_pipeline_unbound_components(struct komeda_pipeline *pipe, + struct komeda_pipeline_state *new) +{ + struct drm_atomic_state *drm_st = new->obj.state; + struct komeda_pipeline_state *old = priv_to_pipe_st(pipe->obj.state); + struct komeda_component_state *c_st; + struct komeda_component *c; + u32 id; + unsigned long disabling_comps; + + WARN_ON(!old); + + disabling_comps = (~new->active_comps) & old->active_comps; + + /* unbound all disabling component */ + for_each_set_bit(id, &disabling_comps, 32) { + c = komeda_pipeline_get_component(pipe, id); + c_st = komeda_component_get_state_and_set_user(c, + drm_st, NULL, new->crtc); + if (PTR_ERR(c_st) == -EDEADLK) + return -EDEADLK; + WARN_ON(IS_ERR(c_st)); + } + + return 0; +} + +/* release unclaimed pipeline resource */ +int komeda_release_unclaimed_resources(struct komeda_pipeline *pipe, + struct komeda_crtc_state *kcrtc_st) +{ + struct drm_atomic_state *drm_st = kcrtc_st->base.state; + struct komeda_pipeline_state *st; + + /* ignore the pipeline which is not affected */ + if (!pipe || !has_bit(pipe->id, kcrtc_st->affected_pipes)) + return 0; + + if (has_bit(pipe->id, kcrtc_st->active_pipes)) + st = komeda_pipeline_get_new_state(pipe, drm_st); + else + st = komeda_pipeline_get_state_and_set_crtc(pipe, drm_st, NULL); + + if (WARN_ON(IS_ERR_OR_NULL(st))) + return -EINVAL; + + return komeda_pipeline_unbound_components(pipe, st); + +} + +/* Since standalone disabled components must be disabled separately and in the + * last, So a complete disable operation may needs to call pipeline_disable + * twice (two phase disabling). + * Phase 1: disable the common components, flush it. + * Phase 2: disable the standalone disabled components, flush it. + * + * RETURNS: + * true: disable is not complete, needs a phase 2 disable. + * false: disable is complete. + */ +bool komeda_pipeline_disable(struct komeda_pipeline *pipe, + struct drm_atomic_state *old_state) +{ + struct komeda_pipeline_state *old; + struct komeda_component *c; + struct komeda_component_state *c_st; + u32 id; + unsigned long disabling_comps; + + old = komeda_pipeline_get_old_state(pipe, old_state); + + disabling_comps = old->active_comps & + (~pipe->standalone_disabled_comps); + if (!disabling_comps) + disabling_comps = old->active_comps & + pipe->standalone_disabled_comps; + + DRM_DEBUG_ATOMIC("PIPE%d: active_comps: 0x%x, disabling_comps: 0x%lx.\n", + pipe->id, old->active_comps, disabling_comps); + + for_each_set_bit(id, &disabling_comps, 32) { + c = komeda_pipeline_get_component(pipe, id); + c_st = priv_to_comp_st(c->obj.state); + + /* + * If we disabled a component then all active_inputs should be + * put in the list of changed_active_inputs, so they get + * re-enabled. + * This usually happens during a modeset when the pipeline is + * first disabled and then the actual state gets committed + * again. + */ + c_st->changed_active_inputs |= c_st->active_inputs; + + c->funcs->disable(c); + } + + /* Update the pipeline state, if there are components that are still + * active, return true for calling the phase 2 disable. + */ + old->active_comps &= ~disabling_comps; + + return old->active_comps ? true : false; +} + +void komeda_pipeline_update(struct komeda_pipeline *pipe, + struct drm_atomic_state *old_state) +{ + struct komeda_pipeline_state *new = priv_to_pipe_st(pipe->obj.state); + struct komeda_pipeline_state *old; + struct komeda_component *c; + u32 id; + unsigned long changed_comps; + + old = komeda_pipeline_get_old_state(pipe, old_state); + + changed_comps = new->active_comps | old->active_comps; + + DRM_DEBUG_ATOMIC("PIPE%d: active_comps: 0x%x, changed: 0x%lx.\n", + pipe->id, new->active_comps, changed_comps); + + for_each_set_bit(id, &changed_comps, 32) { + c = komeda_pipeline_get_component(pipe, id); + + if (new->active_comps & BIT(c->id)) + c->funcs->update(c, priv_to_comp_st(c->obj.state)); + else + c->funcs->disable(c); + } +} |