1354 lines
38 KiB
C
1354 lines
38 KiB
C
|
// 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 (!in_range(&layer->hsize_in, src_w)) {
|
||
|
DRM_DEBUG_ATOMIC("invalidate src_w %d.\n", src_w);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!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 (!in_range(&scaler->hsize, hsize_in) ||
|
||
|
!in_range(&scaler->hsize, hsize_out)) {
|
||
|
DRM_DEBUG_ATOMIC("Invalid horizontal sizes");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!in_range(&scaler->vsize, vsize_in) ||
|
||
|
!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 (!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 (!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 (!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 (!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 = !in_range(&scaler->hsize, dflow->in_w) ||
|
||
|
!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 void
|
||
|
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);
|
||
|
WARN_ON(IS_ERR(c_st));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* 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;
|
||
|
|
||
|
komeda_pipeline_unbound_components(pipe, st);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* 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);
|
||
|
}
|
||
|
}
|