This patch series add laye split support for komeda.
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 flows will be merged by merger firstly, then merger outputs one merged
data flow to compiz.
This patch series depends on:
- https://patchwork.freedesktop.org/series/60767/
- https://patchwork.freedesktop.org/series/60838/
James Qian Wang (Arm Technology China) (3):
drm/komeda: Add component komeda_merger
drm/komeda: Add split support for scaler
drm/komeda: Add layer split support
.../arm/display/komeda/d71/d71_component.c | 121 ++++++-
.../gpu/drm/arm/display/komeda/komeda_kms.c | 8 +
.../gpu/drm/arm/display/komeda/komeda_kms.h | 22 +-
.../drm/arm/display/komeda/komeda_pipeline.c | 26 +-
.../drm/arm/display/komeda/komeda_pipeline.h | 49 ++-
.../display/komeda/komeda_pipeline_state.c | 321 +++++++++++++++++-
.../gpu/drm/arm/display/komeda/komeda_plane.c | 32 +-
.../arm/display/komeda/komeda_private_obj.c | 49 +++
.../arm/display/komeda/komeda_wb_connector.c | 2 +-
9 files changed, 602 insertions(+), 28 deletions(-)
--
2.17.1
To achieve same caling effect compare with none split, the texel
calculation need to use the same scaling ratio before split, so add
"total_xxx" to pipeline to describe the hsize/vsize before split.
Update pipeline and d71_scaler_update accordingly.
Signed-off-by: James Qian Wang (Arm Technology China) <[email protected]>
---
.../arm/display/komeda/d71/d71_component.c | 47 +++++++++++++++++--
.../drm/arm/display/komeda/komeda_pipeline.h | 19 ++++++--
.../display/komeda/komeda_pipeline_state.c | 21 ++++++++-
.../gpu/drm/arm/display/komeda/komeda_plane.c | 8 ++--
.../arm/display/komeda/komeda_wb_connector.c | 2 +-
5 files changed, 81 insertions(+), 16 deletions(-)
diff --git a/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c b/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c
index 3266bd54c936..d101a5cc2766 100644
--- a/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c
+++ b/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c
@@ -642,23 +642,58 @@ static void d71_scaler_update(struct komeda_component *c,
malidp_write32(reg, BLK_IN_SIZE, HV_SIZE(st->hsize_in, st->vsize_in));
malidp_write32(reg, SC_OUT_SIZE, HV_SIZE(st->hsize_out, st->vsize_out));
+ malidp_write32(reg, SC_H_CROP, HV_CROP(st->left_crop, st->right_crop));
+
+ /* for right part, HW only sample the valid pixel which means the pixels
+ * in left_crop will be jumpped, and the first sample pixel is:
+ *
+ * dst_a = st->total_hsize_out - st->hsize_out + st->left_crop + 0.5;
+ *
+ * Then the corresponding texel in src is:
+ *
+ * h_delta_phase = st->total_hsize_in / st->total_hsize_out;
+ * src_a = dst_A * h_delta_phase;
+ *
+ * and h_init_phase is src_a deduct the real source start src_S;
+ *
+ * src_S = st->total_hsize_in - st->hsize_in;
+ * h_init_phase = src_a - src_S;
+ *
+ * And HW precision for the initial/delta_phase is 16:16 fixed point,
+ * the following is the simplified formula
+ */
+ if (st->right_part) {
+ u32 dst_a = st->total_hsize_out - st->hsize_out + st->left_crop;
+
+ if (st->en_img_enhancement)
+ dst_a -= 1;
+
+ init_ph = ((st->total_hsize_in * (2 * dst_a + 1) -
+ 2 * st->total_hsize_out * (st->total_hsize_in -
+ st->hsize_in)) << 15) / st->total_hsize_out;
+ } else {
+ init_ph = (st->total_hsize_in << 15) / st->total_hsize_out;
+ }
- init_ph = (st->hsize_in << 15) / st->hsize_out;
malidp_write32(reg, SC_H_INIT_PH, init_ph);
- delta_ph = (st->hsize_in << 16) / st->hsize_out;
+ delta_ph = (st->total_hsize_in << 16) / st->total_hsize_out;
malidp_write32(reg, SC_H_DELTA_PH, delta_ph);
- init_ph = (st->vsize_in << 15) / st->vsize_out;
+ init_ph = (st->total_vsize_in << 15) / st->vsize_out;
malidp_write32(reg, SC_V_INIT_PH, init_ph);
- delta_ph = (st->vsize_in << 16) / st->vsize_out;
+ delta_ph = (st->total_vsize_in << 16) / st->vsize_out;
malidp_write32(reg, SC_V_DELTA_PH, delta_ph);
ctrl = 0;
ctrl |= st->en_scaling ? SC_CTRL_SCL : 0;
ctrl |= st->en_alpha ? SC_CTRL_AP : 0;
ctrl |= st->en_img_enhancement ? SC_CTRL_IENH : 0;
+ /* If we use the hardware splitter we shouldn't set SC_CTRL_LS */
+ if (st->en_split &&
+ state->inputs[0].component->id != KOMEDA_COMPONENT_SPLITTER)
+ ctrl |= SC_CTRL_LS;
malidp_write32(reg, BLK_CONTROL, ctrl);
malidp_write32(reg, BLK_INPUT_ID0, to_d71_input_id(&state->inputs[0]));
@@ -716,10 +751,12 @@ static int d71_scaler_init(struct d71_dev *d71,
}
scaler = to_scaler(c);
- set_range(&scaler->hsize, 4, d71->max_line_size);
+ set_range(&scaler->hsize, 4, 2048);
set_range(&scaler->vsize, 4, 4096);
scaler->max_downscaling = 6;
scaler->max_upscaling = 64;
+ scaler->scaling_split_overlap = 8;
+ scaler->enh_split_overlap = 1;
malidp_write32(c->reg, BLK_CONTROL, 0);
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
index c92733736799..4e1cf8fd89bf 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
@@ -247,15 +247,22 @@ struct komeda_scaler {
struct malidp_range hsize, vsize;
u32 max_upscaling;
u32 max_downscaling;
+ u8 scaling_split_overlap; /* split overlap for scaling */
+ u8 enh_split_overlap; /* split overlap for image enhancement */
};
struct komeda_scaler_state {
struct komeda_component_state base;
u16 hsize_in, vsize_in;
u16 hsize_out, vsize_out;
+ u16 total_hsize_in, total_vsize_in;
+ u16 total_hsize_out; /* total_xxxx are size before split */
+ u16 left_crop, right_crop;
u8 en_scaling : 1,
en_alpha : 1, /* enable alpha processing */
- en_img_enhancement : 1;
+ en_img_enhancement : 1,
+ en_split : 1,
+ right_part; /* right part of split image */
};
struct komeda_compiz {
@@ -323,11 +330,16 @@ struct komeda_data_flow_cfg {
struct komeda_component_output input;
u16 in_x, in_y, in_w, in_h;
u32 out_x, out_y, out_w, out_h;
+ u16 total_in_h, total_in_w;
+ u16 total_out_w;
+ u16 left_crop, right_crop, overlap;
u32 rot;
int blending_zorder;
u8 pixel_blend_mode, layer_alpha;
u8 needs_scaling : 1,
- needs_img_enhancement : 1;
+ needs_img_enhancement : 1,
+ needs_split : 1,
+ right_part : 1; /* right part of display image if split enabled */
};
/** struct komeda_pipeline_funcs */
@@ -488,6 +500,7 @@ void komeda_pipeline_disable(struct komeda_pipeline *pipe,
void komeda_pipeline_update(struct komeda_pipeline *pipe,
struct drm_atomic_state *old_state);
-void komeda_complete_data_flow_cfg(struct komeda_data_flow_cfg *dflow);
+void komeda_complete_data_flow_cfg(struct komeda_data_flow_cfg *dflow,
+ struct drm_framebuffer *fb);
#endif /* _KOMEDA_PIPELINE_H_*/
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c
index fcd34164b3c2..9657dbfe0210 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c
@@ -489,11 +489,19 @@ komeda_scaler_validate(void *user,
st->hsize_in = dflow->in_w;
st->vsize_in = dflow->in_h;
st->hsize_out = dflow->out_w;
- st->vsize_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;
+
st->en_scaling = dflow->needs_scaling;
/* 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_img_enhancement = dflow->needs_img_enhancement;
+ st->en_split = dflow->needs_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);
@@ -647,14 +655,23 @@ komeda_timing_ctrlr_validate(struct komeda_timing_ctrlr *ctrlr,
return 0;
}
-void komeda_complete_data_flow_cfg(struct komeda_data_flow_cfg *dflow)
+void komeda_complete_data_flow_cfg(struct komeda_data_flow_cfg *dflow,
+ struct drm_framebuffer *fb)
{
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 (drm_rotation_90_or_270(dflow->rot))
swap(w, h);
+ /* 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;
+
dflow->needs_scaling = (w != dflow->out_w) || (h != dflow->out_h);
}
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_plane.c b/drivers/gpu/drm/arm/display/komeda/komeda_plane.c
index aad766365bbb..75ef0e6c5d98 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_plane.c
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_plane.c
@@ -22,10 +22,7 @@ komeda_plane_init_data_flow(struct drm_plane_state *st,
memset(dflow, 0, sizeof(*dflow));
dflow->blending_zorder = st->normalized_zpos;
-
- /* if format doesn't have alpha, fix blend mode to PIXEL_NONE */
- dflow->pixel_blend_mode = fb->format->has_alpha ?
- st->pixel_blend_mode : DRM_MODE_BLEND_PIXEL_NONE;
+ dflow->pixel_blend_mode = st->pixel_blend_mode;
dflow->layer_alpha = st->alpha >> 8;
dflow->out_x = st->crtc_x;
@@ -46,9 +43,10 @@ komeda_plane_init_data_flow(struct drm_plane_state *st,
fb->modifier));
return -EINVAL;
}
+
dflow->needs_img_enhancement = kplane_st->img_enhancement;
- komeda_complete_data_flow_cfg(dflow);
+ komeda_complete_data_flow_cfg(dflow, fb);
return 0;
}
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c b/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c
index eed521218ef3..20295291572f 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c
@@ -31,7 +31,7 @@ komeda_wb_init_data_flow(struct komeda_layer *wb_layer,
dflow->pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE;
dflow->rot = DRM_MODE_ROTATE_0;
- komeda_complete_data_flow_cfg(dflow);
+ komeda_complete_data_flow_cfg(dflow, fb);
return 0;
}
--
2.17.1
Introduce a new component komeda_merger, because D71 HW supports to split
a whole image to two half parts and does the scaling independently. Merger
merges two separate results to one, and output it to compositor or wb_layer
For this patch:
- Add the definition of komeda_merger/merger_state
- Report and initialize komeda_merger according to the D71 HW.
Signed-off-by: James Qian Wang (Arm Technology China) <[email protected]>
---
.../arm/display/komeda/d71/d71_component.c | 74 +++++++++++++++++++
.../drm/arm/display/komeda/komeda_pipeline.c | 3 +
.../drm/arm/display/komeda/komeda_pipeline.h | 18 ++++-
.../arm/display/komeda/komeda_private_obj.c | 49 ++++++++++++
4 files changed, 143 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c b/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c
index ca4b2f7a8106..3266bd54c936 100644
--- a/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c
+++ b/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c
@@ -726,6 +726,77 @@ static int d71_scaler_init(struct d71_dev *d71,
return 0;
}
+static void d71_merger_update(struct komeda_component *c,
+ struct komeda_component_state *state)
+{
+ struct komeda_merger_state *st = to_merger_st(state);
+ u32 __iomem *reg = c->reg;
+ u32 index;
+
+ for_each_changed_input(state, index)
+ malidp_write32(reg, MG_INPUT_ID0 + index * 4,
+ to_d71_input_id(&state->inputs[index]));
+
+ malidp_write32(reg, MG_SIZE, HV_SIZE(st->hsize_merged,
+ st->vsize_merged));
+ malidp_write32(reg, BLK_CONTROL, BLK_CTRL_EN);
+}
+
+static void d71_merger_dump(struct komeda_component *c, struct seq_file *sf)
+{
+ u32 v;
+
+ dump_block_header(sf, c->reg);
+
+ get_values_from_reg(c->reg, MG_INPUT_ID0, 1, &v);
+ seq_printf(sf, "MG_INPUT_ID0:\t\t0x%X\n", v);
+
+ get_values_from_reg(c->reg, MG_INPUT_ID1, 1, &v);
+ seq_printf(sf, "MG_INPUT_ID1:\t\t0x%X\n", v);
+
+ get_values_from_reg(c->reg, BLK_CONTROL, 1, &v);
+ seq_printf(sf, "MG_CONTROL:\t\t0x%X\n", v);
+
+ get_values_from_reg(c->reg, MG_SIZE, 1, &v);
+ seq_printf(sf, "MG_SIZE:\t\t0x%X\n", v);
+}
+
+static const struct komeda_component_funcs d71_merger_funcs = {
+ .update = d71_merger_update,
+ .disable = d71_component_disable,
+ .dump_register = d71_merger_dump,
+};
+
+static int d71_merger_init(struct d71_dev *d71,
+ struct block_header *blk, u32 __iomem *reg)
+{
+ struct komeda_component *c;
+ struct komeda_merger *merger;
+ u32 pipe_id, comp_id;
+
+ get_resources_id(blk->block_info, &pipe_id, &comp_id);
+
+ c = komeda_component_add(&d71->pipes[pipe_id]->base, sizeof(*merger),
+ comp_id,
+ BLOCK_INFO_INPUT_ID(blk->block_info),
+ &d71_merger_funcs,
+ MG_NUM_INPUTS_IDS, get_valid_inputs(blk),
+ MG_NUM_OUTPUTS_IDS, reg,
+ "CU%d_MERGER", pipe_id);
+
+ if (!c) {
+ DRM_ERROR("Failed to initialize merger.\n");
+ return -1;
+ }
+
+ merger = to_merger(c);
+
+ set_range(&merger->hsize_merged, 4, 4032);
+ set_range(&merger->vsize_merged, 4, 4096);
+
+ return 0;
+}
+
static void d71_improc_update(struct komeda_component *c,
struct komeda_component_state *state)
{
@@ -951,7 +1022,10 @@ int d71_probe_block(struct d71_dev *d71,
break;
case D71_BLK_TYPE_CU_SPLITTER:
+ break;
+
case D71_BLK_TYPE_CU_MERGER:
+ err = d71_merger_init(d71, blk, reg);
break;
case D71_BLK_TYPE_DOU:
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.c b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.c
index 96248586b4e8..f434cb526dbe 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.c
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.c
@@ -92,6 +92,9 @@ komeda_pipeline_get_component_pos(struct komeda_pipeline *pipe, int id)
case KOMEDA_COMPONENT_SCALER1:
pos = to_cpos(pipe->scalers[id - KOMEDA_COMPONENT_SCALER0]);
break;
+ case KOMEDA_COMPONENT_MERGER:
+ pos = to_cpos(pipe->merger);
+ break;
case KOMEDA_COMPONENT_IPS0:
case KOMEDA_COMPONENT_IPS1:
temp = mdev->pipelines[id - KOMEDA_COMPONENT_IPS0];
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
index 2c7e3f97a6f3..c92733736799 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
@@ -276,6 +276,18 @@ struct komeda_compiz_state {
struct komeda_compiz_input_cfg cins[KOMEDA_COMPONENT_N_INPUTS];
};
+struct komeda_merger {
+ struct komeda_component base;
+ struct malidp_range hsize_merged;
+ struct malidp_range vsize_merged;
+};
+
+struct komeda_merger_state {
+ struct komeda_component_state base;
+ u16 hsize_merged;
+ u16 vsize_merged;
+};
+
struct komeda_improc {
struct komeda_component base;
u32 supported_color_formats; /* DRM_RGB/YUV444/YUV420*/
@@ -353,6 +365,8 @@ struct komeda_pipeline {
struct komeda_scaler *scalers[KOMEDA_PIPELINE_MAX_SCALERS];
/** @compiz: compositor */
struct komeda_compiz *compiz;
+ /** @merger: merger */
+ struct komeda_merger *merger;
/** @wb_layer: writeback layer */
struct komeda_layer *wb_layer;
/** @improc: post image processor */
@@ -395,17 +409,19 @@ struct komeda_pipeline_state {
#define to_layer(c) container_of(c, struct komeda_layer, base)
#define to_compiz(c) container_of(c, struct komeda_compiz, base)
#define to_scaler(c) container_of(c, struct komeda_scaler, base)
+#define to_merger(c) container_of(c, struct komeda_merger, base)
#define to_improc(c) container_of(c, struct komeda_improc, base)
#define to_ctrlr(c) container_of(c, struct komeda_timing_ctrlr, base)
#define to_layer_st(c) container_of(c, struct komeda_layer_state, base)
#define to_compiz_st(c) container_of(c, struct komeda_compiz_state, base)
#define to_scaler_st(c) container_of(c, struct komeda_scaler_state, base)
+#define to_merger_st(c) container_of(c, struct komeda_merger_state, base)
#define to_improc_st(c) container_of(c, struct komeda_improc_state, base)
#define to_ctrlr_st(c) container_of(c, struct komeda_timing_ctrlr_state, base)
#define priv_to_comp_st(o) container_of(o, struct komeda_component_state, obj)
-#define priv_to_pipe_st(o) container_of(o, struct komeda_pipeline_state, obj)
+#define priv_to_pipe_st(o) container_of(o, struct komeda_pipeline_state, obj)
/* pipeline APIs */
struct komeda_pipeline *
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c b/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c
index bac90ab8fdc9..0f4e1f601ce0 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c
@@ -146,6 +146,49 @@ static int komeda_compiz_obj_add(struct komeda_kms_dev *kms,
return 0;
}
+static struct drm_private_state *
+komeda_merger_atomic_duplicate_state(struct drm_private_obj *obj)
+{
+ struct komeda_merger_state *st;
+
+ st = kmemdup(obj->state, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return NULL;
+
+ komeda_component_state_reset(&st->base);
+ __drm_atomic_helper_private_obj_duplicate_state(obj, &st->base.obj);
+
+ return &st->base.obj;
+}
+
+static void komeda_merger_atomic_destroy_state(struct drm_private_obj *obj,
+ struct drm_private_state *state)
+{
+ kfree(to_merger_st(priv_to_comp_st(state)));
+}
+
+static const struct drm_private_state_funcs komeda_merger_obj_funcs = {
+ .atomic_duplicate_state = komeda_merger_atomic_duplicate_state,
+ .atomic_destroy_state = komeda_merger_atomic_destroy_state,
+};
+
+static int komeda_merger_obj_add(struct komeda_kms_dev *kms,
+ struct komeda_merger *merger)
+{
+ struct komeda_merger_state *st;
+
+ st = kzalloc(sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ st->base.component = &merger->base;
+ drm_atomic_private_obj_init(&kms->base,
+ &merger->base.obj, &st->base.obj,
+ &komeda_merger_obj_funcs);
+
+ return 0;
+}
+
static struct drm_private_state *
komeda_improc_atomic_duplicate_state(struct drm_private_obj *obj)
{
@@ -311,6 +354,12 @@ int komeda_kms_add_private_objs(struct komeda_kms_dev *kms,
if (err)
return err;
+ if (pipe->merger) {
+ err = komeda_merger_obj_add(kms, pipe->merger);
+ if (err)
+ return err;
+ }
+
err = komeda_improc_obj_add(kms, pipe->improc);
if (err)
return err;
--
2.17.1
Komeda supports two types of layer split:
- none-scaling split
- scaling split
Since D71 merger only support scaler as input, so for none-scaling split,
the two layer dflow will be output to compiz directly. for scaling_split,
the data flow will be merged by merger firstly, then output the merged
data flow to compiz.
Komeda handles the split in kernel completely to hide the detailed and
complicated split calcualtion to user mode, for user only need to set the
layer_split property to enable/disable it.
Signed-off-by: James Qian Wang (Arm Technology China) <[email protected]>
---
.../gpu/drm/arm/display/komeda/komeda_kms.c | 8 +
.../gpu/drm/arm/display/komeda/komeda_kms.h | 22 +-
.../drm/arm/display/komeda/komeda_pipeline.c | 23 +-
.../drm/arm/display/komeda/komeda_pipeline.h | 12 +
.../display/komeda/komeda_pipeline_state.c | 300 +++++++++++++++++-
.../gpu/drm/arm/display/komeda/komeda_plane.c | 24 +-
6 files changed, 378 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_kms.c b/drivers/gpu/drm/arm/display/komeda/komeda_kms.c
index 0ec76656cd1f..5d10c55c9c40 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_kms.c
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_kms.c
@@ -173,6 +173,14 @@ static int komeda_crtc_normalize_zpos(struct drm_crtc *crtc,
plane = plane_st->plane;
plane_st->normalized_zpos = order++;
+ /* When layer_split has been enabled, one plane will be handled
+ * by two separated komeda layers (left/right), which may needs
+ * two zorders.
+ * - zorder: for left_layer for left display part.
+ * - zorder + 1: will be reserved for right layer.
+ */
+ if (to_kplane_st(plane_st)->layer_split)
+ order++;
DRM_DEBUG_ATOMIC("[PLANE:%d:%s] zpos:%d, normalized zpos: %d\n",
plane->base.id, plane->name,
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_kms.h b/drivers/gpu/drm/arm/display/komeda/komeda_kms.h
index d1cef46f7068..93a0cdea056f 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_kms.h
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_kms.h
@@ -36,6 +36,8 @@ struct komeda_plane {
/** @prop_img_enhancement: for on/off image enhancement */
struct drm_property *prop_img_enhancement;
+ /** @prop_layer_split: for on/off layer_split */
+ struct drm_property *prop_layer_split;
};
/**
@@ -50,8 +52,11 @@ struct komeda_plane_state {
/** @zlist_node: zorder list node */
struct list_head zlist_node;
- /* @img_enhancement: on/off image enhancement */
- u8 img_enhancement : 1;
+ /* @img_enhancement: on/off image enhancement
+ * @layer_split: on/off layer_split
+ */
+ u8 img_enhancement : 1,
+ layer_split : 1;
};
/**
@@ -149,6 +154,19 @@ is_only_changed_connector(struct drm_crtc_state *st, struct drm_connector *conn)
return BIT(drm_connector_index(conn)) == changed_connectors;
}
+static inline bool has_flip_h(u32 rot)
+{
+ u32 rotation = drm_rotation_simplify(rot,
+ DRM_MODE_ROTATE_0 |
+ DRM_MODE_ROTATE_90 |
+ DRM_MODE_REFLECT_MASK);
+
+ if (rotation & DRM_MODE_ROTATE_90)
+ return !!(rotation & DRM_MODE_REFLECT_Y);
+ else
+ return !!(rotation & DRM_MODE_REFLECT_X);
+}
+
int komeda_kms_setup_crtcs(struct komeda_kms_dev *kms, struct komeda_dev *mdev);
int komeda_kms_add_crtcs(struct komeda_kms_dev *kms, struct komeda_dev *mdev);
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.c b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.c
index f434cb526dbe..c7c3caad13bd 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.c
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.c
@@ -266,16 +266,35 @@ static void komeda_component_verify_inputs(struct komeda_component *c)
}
}
+static struct komeda_layer *
+komeda_get_layer_split_right_layer(struct komeda_pipeline *pipe,
+ struct komeda_layer *left)
+{
+ int index = left->base.id - KOMEDA_COMPONENT_LAYER0;
+ int i;
+
+ for (i = index + 1; i < pipe->n_layers; i++)
+ if (left->layer_type == pipe->layers[i]->layer_type)
+ return pipe->layers[i];
+ return NULL;
+}
+
static void komeda_pipeline_assemble(struct komeda_pipeline *pipe)
{
struct komeda_component *c;
- int id;
+ struct komeda_layer *layer;
+ int i, id;
dp_for_each_set_bit(id, pipe->avail_comps) {
c = komeda_pipeline_get_component(pipe, id);
-
komeda_component_verify_inputs(c);
}
+ /* calculate right layer for the layer split */
+ for (i = 0; i < pipe->n_layers; i++) {
+ layer = pipe->layers[i];
+
+ layer->right = komeda_get_layer_split_right_layer(pipe, layer);
+ }
}
int komeda_assemble_pipelines(struct komeda_dev *mdev)
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
index 4e1cf8fd89bf..2c3d3658110c 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
@@ -228,6 +228,12 @@ struct komeda_layer {
struct malidp_range hsize_in, vsize_in;
u32 layer_type; /* RICH, SIMPLE or WB */
u32 supported_rots;
+ /* komeda supports layer split which splits a whole image to two parts
+ * left and right and handle them by two individual layer processors
+ * Note: left/right are always according to the final display rect,
+ * not the source buffer.
+ */
+ struct komeda_layer *right;
};
struct komeda_layer_state {
@@ -339,6 +345,7 @@ struct komeda_data_flow_cfg {
u8 needs_scaling : 1,
needs_img_enhancement : 1,
needs_split : 1,
+ is_yuv : 1,
right_part : 1; /* right part of display image if split enabled */
};
@@ -489,6 +496,11 @@ int komeda_build_wb_data_flow(struct komeda_layer *wb_layer,
int komeda_build_display_data_flow(struct komeda_crtc *kcrtc,
struct komeda_crtc_state *kcrtc_st);
+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);
+
int komeda_release_unclaimed_resources(struct komeda_pipeline *pipe,
struct komeda_crtc_state *kcrtc_st);
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c
index 9657dbfe0210..0bcf316fe2c8 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c
@@ -209,13 +209,14 @@ komeda_component_check_input(struct komeda_component_state *state,
struct komeda_component *c = state->component;
if ((idx < 0) || (idx >= c->max_active_inputs)) {
- DRM_DEBUG_ATOMIC("%s invalid input id: %d.\n", c->name, idx);
+ 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 input_id: %d has been occupied already.\n",
- c->name, idx);
+ DRM_DEBUG_ATOMIC("%s required %s-input[%d] has been occupied already.\n",
+ input->component->name, c->name, idx);
return -EINVAL;
}
@@ -267,6 +268,15 @@ komeda_component_get_avail_scaler(struct komeda_component *c,
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,
@@ -358,8 +368,7 @@ komeda_layer_validate(struct komeda_layer *layer,
* The rotation has been handled by layer, so adjusted the data flow for
* the next stage.
*/
- if (drm_rotation_90_or_270(st->rot))
- swap(dflow->in_h, dflow->in_w);
+ komeda_rotate_data_flow(dflow, st->rot);
return 0;
}
@@ -508,6 +517,52 @@ komeda_scaler_validate(void *user,
return err;
}
+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)
{
@@ -566,6 +621,7 @@ komeda_compiz_set_input(struct komeda_compiz *compiz,
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;
}
@@ -673,6 +729,16 @@ void komeda_complete_data_flow_cfg(struct komeda_data_flow_cfg *dflow,
dflow->pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE;
dflow->needs_scaling = (w != dflow->out_w) || (h != dflow->out_h);
+ dflow->is_yuv = fb->format->is_yuv;
+}
+
+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,
@@ -697,6 +763,230 @@ int komeda_build_layer_data_flow(struct komeda_layer *layer,
if (err)
return err;
+ /* if split, check if can put the data flow into merger */
+ if (dflow->needs_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->needs_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->needs_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->needs_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->needs_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 splitted 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 splitted 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->needs_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->needs_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->needs_scaling || dflow->needs_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;
diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_plane.c b/drivers/gpu/drm/arm/display/komeda/komeda_plane.c
index 75ef0e6c5d98..9b213b51d27e 100644
--- a/drivers/gpu/drm/arm/display/komeda/komeda_plane.c
+++ b/drivers/gpu/drm/arm/display/komeda/komeda_plane.c
@@ -44,7 +44,8 @@ komeda_plane_init_data_flow(struct drm_plane_state *st,
return -EINVAL;
}
- dflow->needs_img_enhancement = kplane_st->img_enhancement;
+ dflow->needs_img_enhancement = !!kplane_st->img_enhancement;
+ dflow->needs_split = !!kplane_st->layer_split;
komeda_complete_data_flow_cfg(dflow, fb);
@@ -90,7 +91,12 @@ komeda_plane_atomic_check(struct drm_plane *plane,
if (err)
return err;
- err = komeda_build_layer_data_flow(layer, kplane_st, kcrtc_st, &dflow);
+ if (dflow.needs_split)
+ err = komeda_build_layer_split_data_flow(layer,
+ kplane_st, kcrtc_st, &dflow);
+ else
+ err = komeda_build_layer_data_flow(layer,
+ kplane_st, kcrtc_st, &dflow);
return err;
}
@@ -180,6 +186,8 @@ komeda_plane_atomic_get_property(struct drm_plane *plane,
if (property == kplane->prop_img_enhancement)
*val = st->img_enhancement;
+ else if (property == kplane->prop_layer_split)
+ *val = st->layer_split;
else
return -EINVAL;
@@ -197,6 +205,8 @@ komeda_plane_atomic_set_property(struct drm_plane *plane,
if (property == kplane->prop_img_enhancement)
st->img_enhancement = !!val;
+ else if (property == kplane->prop_layer_split)
+ st->layer_split = !!val;
else
return -EINVAL;
@@ -246,6 +256,16 @@ komeda_plane_create_layer_properties(struct komeda_plane *kplane,
kplane->prop_img_enhancement = prop;
}
+ /* property: layer split */
+ if (layer->right) {
+ prop = drm_property_create_bool(drm, DRM_MODE_PROP_ATOMIC,
+ "layer_split");
+ if (!prop)
+ return -ENOMEM;
+ kplane->prop_layer_split = prop;
+ drm_object_attach_property(&plane->base, prop, 0);
+ }
+
return 0;
}
--
2.17.1
Hi James,
On Mon, May 20, 2019 at 11:44:47AM +0100, james qian wang (Arm Technology China) wrote:
> To achieve same caling effect compare with none split, the texel
> calculation need to use the same scaling ratio before split, so add
> "total_xxx" to pipeline to describe the hsize/vsize before split.
> Update pipeline and d71_scaler_update accordingly.
>
> Signed-off-by: James Qian Wang (Arm Technology China) <[email protected]>
> ---
> .../arm/display/komeda/d71/d71_component.c | 47 +++++++++++++++++--
> .../drm/arm/display/komeda/komeda_pipeline.h | 19 ++++++--
> .../display/komeda/komeda_pipeline_state.c | 21 ++++++++-
> .../gpu/drm/arm/display/komeda/komeda_plane.c | 8 ++--
> .../arm/display/komeda/komeda_wb_connector.c | 2 +-
> 5 files changed, 81 insertions(+), 16 deletions(-)
>
> diff --git a/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c b/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c
> index 3266bd54c936..d101a5cc2766 100644
> --- a/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c
> +++ b/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c
> @@ -642,23 +642,58 @@ static void d71_scaler_update(struct komeda_component *c,
>
> malidp_write32(reg, BLK_IN_SIZE, HV_SIZE(st->hsize_in, st->vsize_in));
> malidp_write32(reg, SC_OUT_SIZE, HV_SIZE(st->hsize_out, st->vsize_out));
> + malidp_write32(reg, SC_H_CROP, HV_CROP(st->left_crop, st->right_crop));
> +
> + /* for right part, HW only sample the valid pixel which means the pixels
> + * in left_crop will be jumpped, and the first sample pixel is:
> + *
> + * dst_a = st->total_hsize_out - st->hsize_out + st->left_crop + 0.5;
> + *
> + * Then the corresponding texel in src is:
> + *
> + * h_delta_phase = st->total_hsize_in / st->total_hsize_out;
> + * src_a = dst_A * h_delta_phase;
> + *
> + * and h_init_phase is src_a deduct the real source start src_S;
> + *
> + * src_S = st->total_hsize_in - st->hsize_in;
> + * h_init_phase = src_a - src_S;
> + *
> + * And HW precision for the initial/delta_phase is 16:16 fixed point,
> + * the following is the simplified formula
> + */
> + if (st->right_part) {
> + u32 dst_a = st->total_hsize_out - st->hsize_out + st->left_crop;
> +
> + if (st->en_img_enhancement)
> + dst_a -= 1;
> +
> + init_ph = ((st->total_hsize_in * (2 * dst_a + 1) -
> + 2 * st->total_hsize_out * (st->total_hsize_in -
> + st->hsize_in)) << 15) / st->total_hsize_out;
> + } else {
> + init_ph = (st->total_hsize_in << 15) / st->total_hsize_out;
> + }
>
> - init_ph = (st->hsize_in << 15) / st->hsize_out;
> malidp_write32(reg, SC_H_INIT_PH, init_ph);
>
> - delta_ph = (st->hsize_in << 16) / st->hsize_out;
> + delta_ph = (st->total_hsize_in << 16) / st->total_hsize_out;
> malidp_write32(reg, SC_H_DELTA_PH, delta_ph);
>
> - init_ph = (st->vsize_in << 15) / st->vsize_out;
> + init_ph = (st->total_vsize_in << 15) / st->vsize_out;
> malidp_write32(reg, SC_V_INIT_PH, init_ph);
>
> - delta_ph = (st->vsize_in << 16) / st->vsize_out;
> + delta_ph = (st->total_vsize_in << 16) / st->vsize_out;
> malidp_write32(reg, SC_V_DELTA_PH, delta_ph);
>
> ctrl = 0;
> ctrl |= st->en_scaling ? SC_CTRL_SCL : 0;
> ctrl |= st->en_alpha ? SC_CTRL_AP : 0;
> ctrl |= st->en_img_enhancement ? SC_CTRL_IENH : 0;
> + /* If we use the hardware splitter we shouldn't set SC_CTRL_LS */
> + if (st->en_split &&
> + state->inputs[0].component->id != KOMEDA_COMPONENT_SPLITTER)
> + ctrl |= SC_CTRL_LS;
>
> malidp_write32(reg, BLK_CONTROL, ctrl);
> malidp_write32(reg, BLK_INPUT_ID0, to_d71_input_id(&state->inputs[0]));
> @@ -716,10 +751,12 @@ static int d71_scaler_init(struct d71_dev *d71,
> }
>
> scaler = to_scaler(c);
> - set_range(&scaler->hsize, 4, d71->max_line_size);
> + set_range(&scaler->hsize, 4, 2048);
> set_range(&scaler->vsize, 4, 4096);
> scaler->max_downscaling = 6;
> scaler->max_upscaling = 64;
> + scaler->scaling_split_overlap = 8;
> + scaler->enh_split_overlap = 1;
>
> malidp_write32(c->reg, BLK_CONTROL, 0);
>
> diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
> index c92733736799..4e1cf8fd89bf 100644
> --- a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
> +++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
> @@ -247,15 +247,22 @@ struct komeda_scaler {
> struct malidp_range hsize, vsize;
> u32 max_upscaling;
> u32 max_downscaling;
> + u8 scaling_split_overlap; /* split overlap for scaling */
> + u8 enh_split_overlap; /* split overlap for image enhancement */
> };
>
> struct komeda_scaler_state {
> struct komeda_component_state base;
> u16 hsize_in, vsize_in;
> u16 hsize_out, vsize_out;
> + u16 total_hsize_in, total_vsize_in;
> + u16 total_hsize_out; /* total_xxxx are size before split */
> + u16 left_crop, right_crop;
> u8 en_scaling : 1,
> en_alpha : 1, /* enable alpha processing */
> - en_img_enhancement : 1;
> + en_img_enhancement : 1,
> + en_split : 1,
> + right_part; /* right part of split image */
Should right_part be a 1 bit value here, same as in komeda_data_flow_cfg ?
> };
>
> struct komeda_compiz {
> @@ -323,11 +330,16 @@ struct komeda_data_flow_cfg {
> struct komeda_component_output input;
> u16 in_x, in_y, in_w, in_h;
> u32 out_x, out_y, out_w, out_h;
> + u16 total_in_h, total_in_w;
> + u16 total_out_w;
> + u16 left_crop, right_crop, overlap;
> u32 rot;
> int blending_zorder;
> u8 pixel_blend_mode, layer_alpha;
> u8 needs_scaling : 1,
> - needs_img_enhancement : 1;
> + needs_img_enhancement : 1,
> + needs_split : 1,
> + right_part : 1; /* right part of display image if split enabled */
> };
>
> /** struct komeda_pipeline_funcs */
> @@ -488,6 +500,7 @@ void komeda_pipeline_disable(struct komeda_pipeline *pipe,
> void komeda_pipeline_update(struct komeda_pipeline *pipe,
> struct drm_atomic_state *old_state);
>
> -void komeda_complete_data_flow_cfg(struct komeda_data_flow_cfg *dflow);
> +void komeda_complete_data_flow_cfg(struct komeda_data_flow_cfg *dflow,
> + struct drm_framebuffer *fb);
>
> #endif /* _KOMEDA_PIPELINE_H_*/
> diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c
> index fcd34164b3c2..9657dbfe0210 100644
> --- a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c
> +++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c
> @@ -489,11 +489,19 @@ komeda_scaler_validate(void *user,
> st->hsize_in = dflow->in_w;
> st->vsize_in = dflow->in_h;
> st->hsize_out = dflow->out_w;
> - st->vsize_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;
> +
> st->en_scaling = dflow->needs_scaling;
> /* 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_img_enhancement = dflow->needs_img_enhancement;
> + st->en_split = dflow->needs_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);
> @@ -647,14 +655,23 @@ komeda_timing_ctrlr_validate(struct komeda_timing_ctrlr *ctrlr,
> return 0;
> }
>
> -void komeda_complete_data_flow_cfg(struct komeda_data_flow_cfg *dflow)
> +void komeda_complete_data_flow_cfg(struct komeda_data_flow_cfg *dflow,
> + struct drm_framebuffer *fb)
> {
> 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 (drm_rotation_90_or_270(dflow->rot))
> swap(w, h);
>
> + /* 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;
> +
> dflow->needs_scaling = (w != dflow->out_w) || (h != dflow->out_h);
> }
>
> diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_plane.c b/drivers/gpu/drm/arm/display/komeda/komeda_plane.c
> index aad766365bbb..75ef0e6c5d98 100644
> --- a/drivers/gpu/drm/arm/display/komeda/komeda_plane.c
> +++ b/drivers/gpu/drm/arm/display/komeda/komeda_plane.c
> @@ -22,10 +22,7 @@ komeda_plane_init_data_flow(struct drm_plane_state *st,
> memset(dflow, 0, sizeof(*dflow));
>
> dflow->blending_zorder = st->normalized_zpos;
> -
> - /* if format doesn't have alpha, fix blend mode to PIXEL_NONE */
> - dflow->pixel_blend_mode = fb->format->has_alpha ?
> - st->pixel_blend_mode : DRM_MODE_BLEND_PIXEL_NONE;
> + dflow->pixel_blend_mode = st->pixel_blend_mode;
> dflow->layer_alpha = st->alpha >> 8;
>
> dflow->out_x = st->crtc_x;
> @@ -46,9 +43,10 @@ komeda_plane_init_data_flow(struct drm_plane_state *st,
> fb->modifier));
> return -EINVAL;
> }
> +
> dflow->needs_img_enhancement = kplane_st->img_enhancement;
>
> - komeda_complete_data_flow_cfg(dflow);
> + komeda_complete_data_flow_cfg(dflow, fb);
>
> return 0;
> }
> diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c b/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c
> index eed521218ef3..20295291572f 100644
> --- a/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c
> +++ b/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c
> @@ -31,7 +31,7 @@ komeda_wb_init_data_flow(struct komeda_layer *wb_layer,
> dflow->pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE;
> dflow->rot = DRM_MODE_ROTATE_0;
>
> - komeda_complete_data_flow_cfg(dflow);
> + komeda_complete_data_flow_cfg(dflow, fb);
>
> return 0;
> }
> --
> 2.17.1
>
Otherwise: Reviewed-by: Liviu Dudau <[email protected]>
Best regards,
Liviu
--
====================
| I would like to |
| fix the world, |
| but they're not |
| giving me the |
\ source code! /
---------------
¯\_(ツ)_/¯
On Fri, Jun 07, 2019 at 05:46:36PM +0800, Liviu Dudau wrote:
> Hi James,
>
> On Mon, May 20, 2019 at 11:44:47AM +0100, james qian wang (Arm Technology China) wrote:
> > To achieve same caling effect compare with none split, the texel
> > calculation need to use the same scaling ratio before split, so add
> > "total_xxx" to pipeline to describe the hsize/vsize before split.
> > Update pipeline and d71_scaler_update accordingly.
> >
> > Signed-off-by: James Qian Wang (Arm Technology China) <[email protected]>
> > ---
> > .../arm/display/komeda/d71/d71_component.c | 47 +++++++++++++++++--
> > .../drm/arm/display/komeda/komeda_pipeline.h | 19 ++++++--
> > .../display/komeda/komeda_pipeline_state.c | 21 ++++++++-
> > .../gpu/drm/arm/display/komeda/komeda_plane.c | 8 ++--
> > .../arm/display/komeda/komeda_wb_connector.c | 2 +-
> > 5 files changed, 81 insertions(+), 16 deletions(-)
> >
> > diff --git a/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c b/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c
> > index 3266bd54c936..d101a5cc2766 100644
> > --- a/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c
> > +++ b/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c
> > @@ -642,23 +642,58 @@ static void d71_scaler_update(struct komeda_component *c,
> >
> > malidp_write32(reg, BLK_IN_SIZE, HV_SIZE(st->hsize_in, st->vsize_in));
> > malidp_write32(reg, SC_OUT_SIZE, HV_SIZE(st->hsize_out, st->vsize_out));
> > + malidp_write32(reg, SC_H_CROP, HV_CROP(st->left_crop, st->right_crop));
> > +
> > + /* for right part, HW only sample the valid pixel which means the pixels
> > + * in left_crop will be jumpped, and the first sample pixel is:
> > + *
> > + * dst_a = st->total_hsize_out - st->hsize_out + st->left_crop + 0.5;
> > + *
> > + * Then the corresponding texel in src is:
> > + *
> > + * h_delta_phase = st->total_hsize_in / st->total_hsize_out;
> > + * src_a = dst_A * h_delta_phase;
> > + *
> > + * and h_init_phase is src_a deduct the real source start src_S;
> > + *
> > + * src_S = st->total_hsize_in - st->hsize_in;
> > + * h_init_phase = src_a - src_S;
> > + *
> > + * And HW precision for the initial/delta_phase is 16:16 fixed point,
> > + * the following is the simplified formula
> > + */
> > + if (st->right_part) {
> > + u32 dst_a = st->total_hsize_out - st->hsize_out + st->left_crop;
> > +
> > + if (st->en_img_enhancement)
> > + dst_a -= 1;
> > +
> > + init_ph = ((st->total_hsize_in * (2 * dst_a + 1) -
> > + 2 * st->total_hsize_out * (st->total_hsize_in -
> > + st->hsize_in)) << 15) / st->total_hsize_out;
> > + } else {
> > + init_ph = (st->total_hsize_in << 15) / st->total_hsize_out;
> > + }
> >
> > - init_ph = (st->hsize_in << 15) / st->hsize_out;
> > malidp_write32(reg, SC_H_INIT_PH, init_ph);
> >
> > - delta_ph = (st->hsize_in << 16) / st->hsize_out;
> > + delta_ph = (st->total_hsize_in << 16) / st->total_hsize_out;
> > malidp_write32(reg, SC_H_DELTA_PH, delta_ph);
> >
> > - init_ph = (st->vsize_in << 15) / st->vsize_out;
> > + init_ph = (st->total_vsize_in << 15) / st->vsize_out;
> > malidp_write32(reg, SC_V_INIT_PH, init_ph);
> >
> > - delta_ph = (st->vsize_in << 16) / st->vsize_out;
> > + delta_ph = (st->total_vsize_in << 16) / st->vsize_out;
> > malidp_write32(reg, SC_V_DELTA_PH, delta_ph);
> >
> > ctrl = 0;
> > ctrl |= st->en_scaling ? SC_CTRL_SCL : 0;
> > ctrl |= st->en_alpha ? SC_CTRL_AP : 0;
> > ctrl |= st->en_img_enhancement ? SC_CTRL_IENH : 0;
> > + /* If we use the hardware splitter we shouldn't set SC_CTRL_LS */
> > + if (st->en_split &&
> > + state->inputs[0].component->id != KOMEDA_COMPONENT_SPLITTER)
> > + ctrl |= SC_CTRL_LS;
> >
> > malidp_write32(reg, BLK_CONTROL, ctrl);
> > malidp_write32(reg, BLK_INPUT_ID0, to_d71_input_id(&state->inputs[0]));
> > @@ -716,10 +751,12 @@ static int d71_scaler_init(struct d71_dev *d71,
> > }
> >
> > scaler = to_scaler(c);
> > - set_range(&scaler->hsize, 4, d71->max_line_size);
> > + set_range(&scaler->hsize, 4, 2048);
> > set_range(&scaler->vsize, 4, 4096);
> > scaler->max_downscaling = 6;
> > scaler->max_upscaling = 64;
> > + scaler->scaling_split_overlap = 8;
> > + scaler->enh_split_overlap = 1;
> >
> > malidp_write32(c->reg, BLK_CONTROL, 0);
> >
> > diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
> > index c92733736799..4e1cf8fd89bf 100644
> > --- a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
> > +++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
> > @@ -247,15 +247,22 @@ struct komeda_scaler {
> > struct malidp_range hsize, vsize;
> > u32 max_upscaling;
> > u32 max_downscaling;
> > + u8 scaling_split_overlap; /* split overlap for scaling */
> > + u8 enh_split_overlap; /* split overlap for image enhancement */
> > };
> >
> > struct komeda_scaler_state {
> > struct komeda_component_state base;
> > u16 hsize_in, vsize_in;
> > u16 hsize_out, vsize_out;
> > + u16 total_hsize_in, total_vsize_in;
> > + u16 total_hsize_out; /* total_xxxx are size before split */
> > + u16 left_crop, right_crop;
> > u8 en_scaling : 1,
> > en_alpha : 1, /* enable alpha processing */
> > - en_img_enhancement : 1;
> > + en_img_enhancement : 1,
> > + en_split : 1,
> > + right_part; /* right part of split image */
>
> Should right_part be a 1 bit value here, same as in komeda_data_flow_cfg ?
>
Yes, it should be a 1 bit value, will correct it
James
> > };
> >
> > struct komeda_compiz {
> > @@ -323,11 +330,16 @@ struct komeda_data_flow_cfg {
> > struct komeda_component_output input;
> > u16 in_x, in_y, in_w, in_h;
> > u32 out_x, out_y, out_w, out_h;
> > + u16 total_in_h, total_in_w;
> > + u16 total_out_w;
> > + u16 left_crop, right_crop, overlap;
> > u32 rot;
> > int blending_zorder;
> > u8 pixel_blend_mode, layer_alpha;
> > u8 needs_scaling : 1,
> > - needs_img_enhancement : 1;
> > + needs_img_enhancement : 1,
> > + needs_split : 1,
> > + right_part : 1; /* right part of display image if split enabled */
> > };
> >
> > /** struct komeda_pipeline_funcs */
> > @@ -488,6 +500,7 @@ void komeda_pipeline_disable(struct komeda_pipeline *pipe,
> > void komeda_pipeline_update(struct komeda_pipeline *pipe,
> > struct drm_atomic_state *old_state);
> >
> > -void komeda_complete_data_flow_cfg(struct komeda_data_flow_cfg *dflow);
> > +void komeda_complete_data_flow_cfg(struct komeda_data_flow_cfg *dflow,
> > + struct drm_framebuffer *fb);
> >
> > #endif /* _KOMEDA_PIPELINE_H_*/
> > diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c
> > index fcd34164b3c2..9657dbfe0210 100644
> > --- a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c
> > +++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c
> > @@ -489,11 +489,19 @@ komeda_scaler_validate(void *user,
> > st->hsize_in = dflow->in_w;
> > st->vsize_in = dflow->in_h;
> > st->hsize_out = dflow->out_w;
> > - st->vsize_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;
> > +
> > st->en_scaling = dflow->needs_scaling;
> > /* 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_img_enhancement = dflow->needs_img_enhancement;
> > + st->en_split = dflow->needs_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);
> > @@ -647,14 +655,23 @@ komeda_timing_ctrlr_validate(struct komeda_timing_ctrlr *ctrlr,
> > return 0;
> > }
> >
> > -void komeda_complete_data_flow_cfg(struct komeda_data_flow_cfg *dflow)
> > +void komeda_complete_data_flow_cfg(struct komeda_data_flow_cfg *dflow,
> > + struct drm_framebuffer *fb)
> > {
> > 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 (drm_rotation_90_or_270(dflow->rot))
> > swap(w, h);
> >
> > + /* 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;
> > +
> > dflow->needs_scaling = (w != dflow->out_w) || (h != dflow->out_h);
> > }
> >
> > diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_plane.c b/drivers/gpu/drm/arm/display/komeda/komeda_plane.c
> > index aad766365bbb..75ef0e6c5d98 100644
> > --- a/drivers/gpu/drm/arm/display/komeda/komeda_plane.c
> > +++ b/drivers/gpu/drm/arm/display/komeda/komeda_plane.c
> > @@ -22,10 +22,7 @@ komeda_plane_init_data_flow(struct drm_plane_state *st,
> > memset(dflow, 0, sizeof(*dflow));
> >
> > dflow->blending_zorder = st->normalized_zpos;
> > -
> > - /* if format doesn't have alpha, fix blend mode to PIXEL_NONE */
> > - dflow->pixel_blend_mode = fb->format->has_alpha ?
> > - st->pixel_blend_mode : DRM_MODE_BLEND_PIXEL_NONE;
> > + dflow->pixel_blend_mode = st->pixel_blend_mode;
> > dflow->layer_alpha = st->alpha >> 8;
> >
> > dflow->out_x = st->crtc_x;
> > @@ -46,9 +43,10 @@ komeda_plane_init_data_flow(struct drm_plane_state *st,
> > fb->modifier));
> > return -EINVAL;
> > }
> > +
> > dflow->needs_img_enhancement = kplane_st->img_enhancement;
> >
> > - komeda_complete_data_flow_cfg(dflow);
> > + komeda_complete_data_flow_cfg(dflow, fb);
> >
> > return 0;
> > }
> > diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c b/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c
> > index eed521218ef3..20295291572f 100644
> > --- a/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c
> > +++ b/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c
> > @@ -31,7 +31,7 @@ komeda_wb_init_data_flow(struct komeda_layer *wb_layer,
> > dflow->pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE;
> > dflow->rot = DRM_MODE_ROTATE_0;
> >
> > - komeda_complete_data_flow_cfg(dflow);
> > + komeda_complete_data_flow_cfg(dflow, fb);
> >
> > return 0;
> > }
> > --
> > 2.17.1
> >
>
> Otherwise: Reviewed-by: Liviu Dudau <[email protected]>
>
> Best regards,
> Liviu
>
>
> --
> ====================
> | I would like to |
> | fix the world, |
> | but they're not |
> | giving me the |
> \ source code! /
> ---------------
> ¯\_(ツ)_/¯