From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- drivers/gpu/drm/arm/Kconfig | 45 + drivers/gpu/drm/arm/Makefile | 7 + drivers/gpu/drm/arm/display/Kbuild | 3 + drivers/gpu/drm/arm/display/Kconfig | 14 + drivers/gpu/drm/arm/display/include/malidp_io.h | 49 + .../gpu/drm/arm/display/include/malidp_product.h | 37 + drivers/gpu/drm/arm/display/include/malidp_utils.h | 43 + drivers/gpu/drm/arm/display/komeda/Makefile | 26 + .../gpu/drm/arm/display/komeda/d71/d71_component.c | 1444 ++++++++++++++++++++ drivers/gpu/drm/arm/display/komeda/d71/d71_dev.c | 644 +++++++++ drivers/gpu/drm/arm/display/komeda/d71/d71_dev.h | 54 + drivers/gpu/drm/arm/display/komeda/d71/d71_regs.h | 541 ++++++++ .../gpu/drm/arm/display/komeda/komeda_color_mgmt.c | 128 ++ .../gpu/drm/arm/display/komeda/komeda_color_mgmt.h | 25 + drivers/gpu/drm/arm/display/komeda/komeda_crtc.c | 689 ++++++++++ drivers/gpu/drm/arm/display/komeda/komeda_dev.c | 342 +++++ drivers/gpu/drm/arm/display/komeda/komeda_dev.h | 243 ++++ drivers/gpu/drm/arm/display/komeda/komeda_drv.c | 174 +++ drivers/gpu/drm/arm/display/komeda/komeda_event.c | 158 +++ .../drm/arm/display/komeda/komeda_format_caps.c | 152 +++ .../drm/arm/display/komeda/komeda_format_caps.h | 101 ++ .../drm/arm/display/komeda/komeda_framebuffer.c | 289 ++++ .../drm/arm/display/komeda/komeda_framebuffer.h | 49 + drivers/gpu/drm/arm/display/komeda/komeda_kms.c | 354 +++++ drivers/gpu/drm/arm/display/komeda/komeda_kms.h | 196 +++ .../gpu/drm/arm/display/komeda/komeda_pipeline.c | 380 ++++++ .../gpu/drm/arm/display/komeda/komeda_pipeline.h | 568 ++++++++ .../drm/arm/display/komeda/komeda_pipeline_state.c | 1356 ++++++++++++++++++ drivers/gpu/drm/arm/display/komeda/komeda_plane.c | 335 +++++ .../drm/arm/display/komeda/komeda_private_obj.c | 512 +++++++ .../drm/arm/display/komeda/komeda_wb_connector.c | 202 +++ drivers/gpu/drm/arm/hdlcd_crtc.c | 344 +++++ drivers/gpu/drm/arm/hdlcd_drv.c | 421 ++++++ drivers/gpu/drm/arm/hdlcd_drv.h | 41 + drivers/gpu/drm/arm/hdlcd_regs.h | 87 ++ drivers/gpu/drm/arm/malidp_crtc.c | 558 ++++++++ drivers/gpu/drm/arm/malidp_drv.c | 1015 ++++++++++++++ drivers/gpu/drm/arm/malidp_drv.h | 107 ++ drivers/gpu/drm/arm/malidp_hw.c | 1392 +++++++++++++++++++ drivers/gpu/drm/arm/malidp_hw.h | 412 ++++++ drivers/gpu/drm/arm/malidp_mw.c | 274 ++++ drivers/gpu/drm/arm/malidp_mw.h | 14 + drivers/gpu/drm/arm/malidp_planes.c | 1033 ++++++++++++++ drivers/gpu/drm/arm/malidp_regs.h | 301 ++++ 44 files changed, 15159 insertions(+) create mode 100644 drivers/gpu/drm/arm/Kconfig create mode 100644 drivers/gpu/drm/arm/Makefile create mode 100644 drivers/gpu/drm/arm/display/Kbuild create mode 100644 drivers/gpu/drm/arm/display/Kconfig create mode 100644 drivers/gpu/drm/arm/display/include/malidp_io.h create mode 100644 drivers/gpu/drm/arm/display/include/malidp_product.h create mode 100644 drivers/gpu/drm/arm/display/include/malidp_utils.h create mode 100644 drivers/gpu/drm/arm/display/komeda/Makefile create mode 100644 drivers/gpu/drm/arm/display/komeda/d71/d71_component.c create mode 100644 drivers/gpu/drm/arm/display/komeda/d71/d71_dev.c create mode 100644 drivers/gpu/drm/arm/display/komeda/d71/d71_dev.h create mode 100644 drivers/gpu/drm/arm/display/komeda/d71/d71_regs.h create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_color_mgmt.c create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_color_mgmt.h create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_crtc.c create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_dev.c create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_dev.h create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_drv.c create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_event.c create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_format_caps.c create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_format_caps.h create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.c create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.h create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_kms.c create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_kms.h create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_pipeline.c create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_plane.c create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c create mode 100644 drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c create mode 100644 drivers/gpu/drm/arm/hdlcd_crtc.c create mode 100644 drivers/gpu/drm/arm/hdlcd_drv.c create mode 100644 drivers/gpu/drm/arm/hdlcd_drv.h create mode 100644 drivers/gpu/drm/arm/hdlcd_regs.h create mode 100644 drivers/gpu/drm/arm/malidp_crtc.c create mode 100644 drivers/gpu/drm/arm/malidp_drv.c create mode 100644 drivers/gpu/drm/arm/malidp_drv.h create mode 100644 drivers/gpu/drm/arm/malidp_hw.c create mode 100644 drivers/gpu/drm/arm/malidp_hw.h create mode 100644 drivers/gpu/drm/arm/malidp_mw.c create mode 100644 drivers/gpu/drm/arm/malidp_mw.h create mode 100644 drivers/gpu/drm/arm/malidp_planes.c create mode 100644 drivers/gpu/drm/arm/malidp_regs.h (limited to 'drivers/gpu/drm/arm') diff --git a/drivers/gpu/drm/arm/Kconfig b/drivers/gpu/drm/arm/Kconfig new file mode 100644 index 000000000..ed3ed617c --- /dev/null +++ b/drivers/gpu/drm/arm/Kconfig @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: GPL-2.0 +menu "ARM devices" + depends on DRM + +config DRM_HDLCD + tristate "ARM HDLCD" + depends on DRM && OF && (ARM || ARM64 || COMPILE_TEST) + depends on COMMON_CLK + select DRM_CLIENT_SELECTION + select DRM_KMS_HELPER + select DRM_GEM_DMA_HELPER + select DRM_BRIDGE # for TDA998x + help + Choose this option if you have an ARM High Definition Colour LCD + controller. + + If M is selected the module will be called hdlcd. + +config DRM_HDLCD_SHOW_UNDERRUN + bool "Show underrun conditions" + depends on DRM_HDLCD + default n + help + Enable this option to show in red colour the pixels that the + HDLCD device did not fetch from framebuffer due to underrun + conditions. + +config DRM_MALI_DISPLAY + tristate "ARM Mali Display Processor" + depends on DRM && OF && (ARM || ARM64 || COMPILE_TEST) + depends on COMMON_CLK + select DRM_CLIENT_SELECTION + select DRM_KMS_HELPER + select DRM_GEM_DMA_HELPER + select VIDEOMODE_HELPERS + help + Choose this option if you want to compile the ARM Mali Display + Processor driver. It supports the DP500, DP550 and DP650 variants + of the hardware. + + If compiled as a module it will be called mali-dp. + +source "drivers/gpu/drm/arm/display/Kconfig" + +endmenu diff --git a/drivers/gpu/drm/arm/Makefile b/drivers/gpu/drm/arm/Makefile new file mode 100644 index 000000000..3ced6fc9e --- /dev/null +++ b/drivers/gpu/drm/arm/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +hdlcd-y := hdlcd_drv.o hdlcd_crtc.o +obj-$(CONFIG_DRM_HDLCD) += hdlcd.o +mali-dp-y := malidp_drv.o malidp_hw.o malidp_planes.o malidp_crtc.o +mali-dp-y += malidp_mw.o +obj-$(CONFIG_DRM_MALI_DISPLAY) += mali-dp.o +obj-$(CONFIG_DRM_KOMEDA) += display/ diff --git a/drivers/gpu/drm/arm/display/Kbuild b/drivers/gpu/drm/arm/display/Kbuild new file mode 100644 index 000000000..382f1ca83 --- /dev/null +++ b/drivers/gpu/drm/arm/display/Kbuild @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_DRM_KOMEDA) += komeda/ diff --git a/drivers/gpu/drm/arm/display/Kconfig b/drivers/gpu/drm/arm/display/Kconfig new file mode 100644 index 000000000..415c10a63 --- /dev/null +++ b/drivers/gpu/drm/arm/display/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +config DRM_KOMEDA + tristate "ARM Komeda display driver" + depends on DRM && OF + depends on COMMON_CLK + select DRM_CLIENT_SELECTION + select DRM_KMS_HELPER + select DRM_GEM_DMA_HELPER + select VIDEOMODE_HELPERS + help + Choose this option if you want to compile the ARM Komeda display + Processor driver. It supports the D71 variants of the hardware. + + If compiled as a module it will be called komeda. diff --git a/drivers/gpu/drm/arm/display/include/malidp_io.h b/drivers/gpu/drm/arm/display/include/malidp_io.h new file mode 100644 index 000000000..9440dff94 --- /dev/null +++ b/drivers/gpu/drm/arm/display/include/malidp_io.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#ifndef _MALIDP_IO_H_ +#define _MALIDP_IO_H_ + +#include + +static inline u32 +malidp_read32(u32 __iomem *base, u32 offset) +{ + return readl((base + (offset >> 2))); +} + +static inline void +malidp_write32(u32 __iomem *base, u32 offset, u32 v) +{ + writel(v, (base + (offset >> 2))); +} + +static inline void +malidp_write64(u32 __iomem *base, u32 offset, u64 v) +{ + writel(lower_32_bits(v), (base + (offset >> 2))); + writel(upper_32_bits(v), (base + (offset >> 2) + 1)); +} + +static inline void +malidp_write32_mask(u32 __iomem *base, u32 offset, u32 m, u32 v) +{ + u32 tmp = malidp_read32(base, offset); + + tmp &= (~m); + malidp_write32(base, offset, v | tmp); +} + +static inline void +malidp_write_group(u32 __iomem *base, u32 offset, int num, const u32 *values) +{ + int i; + + for (i = 0; i < num; i++) + malidp_write32(base, offset + i * 4, values[i]); +} + +#endif /*_MALIDP_IO_H_*/ diff --git a/drivers/gpu/drm/arm/display/include/malidp_product.h b/drivers/gpu/drm/arm/display/include/malidp_product.h new file mode 100644 index 000000000..6f954bcdf --- /dev/null +++ b/drivers/gpu/drm/arm/display/include/malidp_product.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#ifndef _MALIDP_PRODUCT_H_ +#define _MALIDP_PRODUCT_H_ + +/* Product identification */ +#define MALIDP_CORE_ID(__product, __major, __minor, __status) \ + ((((__product) & 0xFFFF) << 16) | (((__major) & 0xF) << 12) | \ + (((__minor) & 0xF) << 8) | ((__status) & 0xFF)) + +#define MALIDP_CORE_ID_PRODUCT_ID(__core_id) ((__u32)(__core_id) >> 16) +#define MALIDP_CORE_ID_MAJOR(__core_id) (((__u32)(__core_id) >> 12) & 0xF) +#define MALIDP_CORE_ID_MINOR(__core_id) (((__u32)(__core_id) >> 8) & 0xF) +#define MALIDP_CORE_ID_STATUS(__core_id) (((__u32)(__core_id)) & 0xFF) + +/* Mali-display product IDs */ +#define MALIDP_D71_PRODUCT_ID 0x0071 +#define MALIDP_D32_PRODUCT_ID 0x0032 +#define LINLONDP_D6_PRODUCT_ID 0x0060 + +union komeda_config_id { + struct { + __u32 max_line_sz:16, + n_pipelines:2, + n_scalers:2, /* number of scalers per pipeline */ + n_layers:3, /* number of layers per pipeline */ + n_richs:3, /* number of rich layers per pipeline */ + reserved_bits:6; + }; + __u32 value; +}; + +#endif /* _MALIDP_PRODUCT_H_ */ diff --git a/drivers/gpu/drm/arm/display/include/malidp_utils.h b/drivers/gpu/drm/arm/display/include/malidp_utils.h new file mode 100644 index 000000000..9f83baac6 --- /dev/null +++ b/drivers/gpu/drm/arm/display/include/malidp_utils.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#ifndef _MALIDP_UTILS_ +#define _MALIDP_UTILS_ + +#include +#include + +#define has_bit(nr, mask) (BIT(nr) & (mask)) +#define has_bits(bits, mask) (((bits) & (mask)) == (bits)) + +#define dp_wait_cond(__cond, __tries, __min_range, __max_range) \ +({ \ + int num_tries = __tries; \ + while (!__cond && (num_tries > 0)) { \ + usleep_range(__min_range, __max_range); \ + num_tries--; \ + } \ + (__cond) ? 0 : -ETIMEDOUT; \ +}) + +/* the restriction of range is [start, end] */ +struct malidp_range { + u32 start; + u32 end; +}; + +static inline void set_range(struct malidp_range *rg, u32 start, u32 end) +{ + rg->start = start; + rg->end = end; +} + +static inline bool malidp_in_range(struct malidp_range *rg, u32 v) +{ + return (v >= rg->start) && (v <= rg->end); +} + +#endif /* _MALIDP_UTILS_ */ diff --git a/drivers/gpu/drm/arm/display/komeda/Makefile b/drivers/gpu/drm/arm/display/komeda/Makefile new file mode 100644 index 000000000..cf5287fcb --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/Makefile @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0 + +ccflags-y := \ + -I $(src)/../include \ + -I $(src) + +komeda-y := \ + komeda_drv.o \ + komeda_dev.o \ + komeda_format_caps.o \ + komeda_color_mgmt.o \ + komeda_pipeline.o \ + komeda_pipeline_state.o \ + komeda_framebuffer.o \ + komeda_kms.o \ + komeda_crtc.o \ + komeda_plane.o \ + komeda_wb_connector.o \ + komeda_private_obj.o \ + komeda_event.o + +komeda-y += \ + d71/d71_dev.o \ + d71/d71_component.o + +obj-$(CONFIG_DRM_KOMEDA) += komeda.o diff --git a/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c b/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c new file mode 100644 index 000000000..27ca2930c --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/d71/d71_component.c @@ -0,0 +1,1444 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ + +#include +#include "d71_dev.h" +#include "komeda_kms.h" +#include "malidp_io.h" +#include "komeda_framebuffer.h" +#include "komeda_color_mgmt.h" + +static void get_resources_id(u32 hw_id, u32 *pipe_id, u32 *comp_id) +{ + u32 id = BLOCK_INFO_BLK_ID(hw_id); + u32 pipe = id; + + switch (BLOCK_INFO_BLK_TYPE(hw_id)) { + case D71_BLK_TYPE_LPU_WB_LAYER: + id = KOMEDA_COMPONENT_WB_LAYER; + break; + case D71_BLK_TYPE_CU_SPLITTER: + id = KOMEDA_COMPONENT_SPLITTER; + break; + case D71_BLK_TYPE_CU_SCALER: + pipe = id / D71_PIPELINE_MAX_SCALERS; + id %= D71_PIPELINE_MAX_SCALERS; + id += KOMEDA_COMPONENT_SCALER0; + break; + case D71_BLK_TYPE_CU: + id += KOMEDA_COMPONENT_COMPIZ0; + break; + case D71_BLK_TYPE_LPU_LAYER: + pipe = id / D71_PIPELINE_MAX_LAYERS; + id %= D71_PIPELINE_MAX_LAYERS; + id += KOMEDA_COMPONENT_LAYER0; + break; + case D71_BLK_TYPE_DOU_IPS: + id += KOMEDA_COMPONENT_IPS0; + break; + case D71_BLK_TYPE_CU_MERGER: + id = KOMEDA_COMPONENT_MERGER; + break; + case D71_BLK_TYPE_DOU: + id = KOMEDA_COMPONENT_TIMING_CTRLR; + break; + default: + id = 0xFFFFFFFF; + } + + if (comp_id) + *comp_id = id; + + if (pipe_id) + *pipe_id = pipe; +} + +static u32 get_valid_inputs(struct block_header *blk) +{ + u32 valid_inputs = 0, comp_id; + int i; + + for (i = 0; i < PIPELINE_INFO_N_VALID_INPUTS(blk->pipeline_info); i++) { + get_resources_id(blk->input_ids[i], NULL, &comp_id); + if (comp_id == 0xFFFFFFFF) + continue; + valid_inputs |= BIT(comp_id); + } + + return valid_inputs; +} + +static void get_values_from_reg(void __iomem *reg, u32 offset, + u32 count, u32 *val) +{ + u32 i, addr; + + for (i = 0; i < count; i++) { + addr = offset + (i << 2); + /* 0xA4 is WO register */ + if (addr != 0xA4) + val[i] = malidp_read32(reg, addr); + else + val[i] = 0xDEADDEAD; + } +} + +static void dump_block_header(struct seq_file *sf, void __iomem *reg) +{ + struct block_header hdr; + u32 i, n_input, n_output; + + d71_read_block_header(reg, &hdr); + seq_printf(sf, "BLOCK_INFO:\t\t0x%X\n", hdr.block_info); + seq_printf(sf, "PIPELINE_INFO:\t\t0x%X\n", hdr.pipeline_info); + + n_output = PIPELINE_INFO_N_OUTPUTS(hdr.pipeline_info); + n_input = PIPELINE_INFO_N_VALID_INPUTS(hdr.pipeline_info); + + for (i = 0; i < n_input; i++) + seq_printf(sf, "VALID_INPUT_ID%u:\t0x%X\n", + i, hdr.input_ids[i]); + + for (i = 0; i < n_output; i++) + seq_printf(sf, "OUTPUT_ID%u:\t\t0x%X\n", + i, hdr.output_ids[i]); +} + +/* On D71, we are using the global line size. From D32, every component have + * a line size register to indicate the fifo size. + */ +static u32 __get_blk_line_size(struct d71_dev *d71, u32 __iomem *reg, + u32 max_default) +{ + if (!d71->periph_addr) + max_default = malidp_read32(reg, BLK_MAX_LINE_SIZE); + + return max_default; +} + +static u32 get_blk_line_size(struct d71_dev *d71, u32 __iomem *reg) +{ + return __get_blk_line_size(d71, reg, d71->max_line_size); +} + +static u32 to_rot_ctrl(u32 rot) +{ + u32 lr_ctrl = 0; + + switch (rot & DRM_MODE_ROTATE_MASK) { + case DRM_MODE_ROTATE_0: + lr_ctrl |= L_ROT(L_ROT_R0); + break; + case DRM_MODE_ROTATE_90: + lr_ctrl |= L_ROT(L_ROT_R90); + break; + case DRM_MODE_ROTATE_180: + lr_ctrl |= L_ROT(L_ROT_R180); + break; + case DRM_MODE_ROTATE_270: + lr_ctrl |= L_ROT(L_ROT_R270); + break; + } + + if (rot & DRM_MODE_REFLECT_X) + lr_ctrl |= L_HFLIP; + if (rot & DRM_MODE_REFLECT_Y) + lr_ctrl |= L_VFLIP; + + return lr_ctrl; +} + +static u32 to_ad_ctrl(u64 modifier) +{ + u32 afbc_ctrl = AD_AEN; + + if (!modifier) + return 0; + + if ((modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK) == + AFBC_FORMAT_MOD_BLOCK_SIZE_32x8) + afbc_ctrl |= AD_WB; + + if (modifier & AFBC_FORMAT_MOD_YTR) + afbc_ctrl |= AD_YT; + if (modifier & AFBC_FORMAT_MOD_SPLIT) + afbc_ctrl |= AD_BS; + if (modifier & AFBC_FORMAT_MOD_TILED) + afbc_ctrl |= AD_TH; + + return afbc_ctrl; +} + +static inline u32 to_d71_input_id(struct komeda_component_state *st, int idx) +{ + struct komeda_component_output *input = &st->inputs[idx]; + + /* if input is not active, set hw input_id(0) to disable it */ + if (has_bit(idx, st->active_inputs)) + return input->component->hw_id + input->output_port; + else + return 0; +} + +static void d71_layer_update_fb(struct komeda_component *c, + struct komeda_fb *kfb, + dma_addr_t *addr) +{ + struct drm_framebuffer *fb = &kfb->base; + const struct drm_format_info *info = fb->format; + u32 __iomem *reg = c->reg; + int block_h; + + if (info->num_planes > 2) + malidp_write64(reg, BLK_P2_PTR_LOW, addr[2]); + + if (info->num_planes > 1) { + block_h = drm_format_info_block_height(info, 1); + malidp_write32(reg, BLK_P1_STRIDE, fb->pitches[1] * block_h); + malidp_write64(reg, BLK_P1_PTR_LOW, addr[1]); + } + + block_h = drm_format_info_block_height(info, 0); + malidp_write32(reg, BLK_P0_STRIDE, fb->pitches[0] * block_h); + malidp_write64(reg, BLK_P0_PTR_LOW, addr[0]); + malidp_write32(reg, LAYER_FMT, kfb->format_caps->hw_id); +} + +static void d71_layer_disable(struct komeda_component *c) +{ + malidp_write32_mask(c->reg, BLK_CONTROL, L_EN, 0); +} + +static void d71_layer_update(struct komeda_component *c, + struct komeda_component_state *state) +{ + struct komeda_layer_state *st = to_layer_st(state); + struct drm_plane_state *plane_st = state->plane->state; + struct drm_framebuffer *fb = plane_st->fb; + struct komeda_fb *kfb = to_kfb(fb); + u32 __iomem *reg = c->reg; + u32 ctrl_mask = L_EN | L_ROT(L_ROT_R270) | L_HFLIP | L_VFLIP | L_TBU_EN; + u32 ctrl = L_EN | to_rot_ctrl(st->rot); + + d71_layer_update_fb(c, kfb, st->addr); + + malidp_write32(reg, AD_CONTROL, to_ad_ctrl(fb->modifier)); + if (fb->modifier) { + u64 addr; + + malidp_write32(reg, LAYER_AD_H_CROP, HV_CROP(st->afbc_crop_l, + st->afbc_crop_r)); + malidp_write32(reg, LAYER_AD_V_CROP, HV_CROP(st->afbc_crop_t, + st->afbc_crop_b)); + /* afbc 1.2 wants payload, afbc 1.0/1.1 wants end_addr */ + if (fb->modifier & AFBC_FORMAT_MOD_TILED) + addr = st->addr[0] + kfb->offset_payload; + else + addr = st->addr[0] + kfb->afbc_size - 1; + + malidp_write32(reg, BLK_P1_PTR_LOW, lower_32_bits(addr)); + malidp_write32(reg, BLK_P1_PTR_HIGH, upper_32_bits(addr)); + } + + if (fb->format->is_yuv) { + u32 upsampling = 0; + + switch (kfb->format_caps->fourcc) { + case DRM_FORMAT_YUYV: + upsampling = fb->modifier ? LR_CHI422_BILINEAR : + LR_CHI422_REPLICATION; + break; + case DRM_FORMAT_UYVY: + upsampling = LR_CHI422_REPLICATION; + break; + case DRM_FORMAT_NV12: + case DRM_FORMAT_YUV420_8BIT: + case DRM_FORMAT_YUV420_10BIT: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_P010: + /* these fmt support MPGE/JPEG both, here perfer JPEG*/ + upsampling = LR_CHI420_JPEG; + break; + case DRM_FORMAT_X0L2: + upsampling = LR_CHI420_JPEG; + break; + default: + break; + } + + malidp_write32(reg, LAYER_R_CONTROL, upsampling); + malidp_write_group(reg, LAYER_YUV_RGB_COEFF0, + KOMEDA_N_YUV2RGB_COEFFS, + komeda_select_yuv2rgb_coeffs( + plane_st->color_encoding, + plane_st->color_range)); + } + + malidp_write32(reg, BLK_IN_SIZE, HV_SIZE(st->hsize, st->vsize)); + + if (kfb->is_va) + ctrl |= L_TBU_EN; + malidp_write32_mask(reg, BLK_CONTROL, ctrl_mask, ctrl); +} + +static void d71_layer_dump(struct komeda_component *c, struct seq_file *sf) +{ + u32 v[15], i; + bool rich, rgb2rgb; + char *prefix; + + get_values_from_reg(c->reg, LAYER_INFO, 1, &v[14]); + if (v[14] & 0x1) { + rich = true; + prefix = "LR_"; + } else { + rich = false; + prefix = "LS_"; + } + + rgb2rgb = !!(v[14] & L_INFO_CM); + + dump_block_header(sf, c->reg); + + seq_printf(sf, "%sLAYER_INFO:\t\t0x%X\n", prefix, v[14]); + + get_values_from_reg(c->reg, 0xD0, 1, v); + seq_printf(sf, "%sCONTROL:\t\t0x%X\n", prefix, v[0]); + if (rich) { + get_values_from_reg(c->reg, 0xD4, 1, v); + seq_printf(sf, "LR_RICH_CONTROL:\t0x%X\n", v[0]); + } + get_values_from_reg(c->reg, 0xD8, 4, v); + seq_printf(sf, "%sFORMAT:\t\t0x%X\n", prefix, v[0]); + seq_printf(sf, "%sIT_COEFFTAB:\t\t0x%X\n", prefix, v[1]); + seq_printf(sf, "%sIN_SIZE:\t\t0x%X\n", prefix, v[2]); + seq_printf(sf, "%sPALPHA:\t\t0x%X\n", prefix, v[3]); + + get_values_from_reg(c->reg, 0x100, 3, v); + seq_printf(sf, "%sP0_PTR_LOW:\t\t0x%X\n", prefix, v[0]); + seq_printf(sf, "%sP0_PTR_HIGH:\t\t0x%X\n", prefix, v[1]); + seq_printf(sf, "%sP0_STRIDE:\t\t0x%X\n", prefix, v[2]); + + get_values_from_reg(c->reg, 0x110, 2, v); + seq_printf(sf, "%sP1_PTR_LOW:\t\t0x%X\n", prefix, v[0]); + seq_printf(sf, "%sP1_PTR_HIGH:\t\t0x%X\n", prefix, v[1]); + if (rich) { + get_values_from_reg(c->reg, 0x118, 1, v); + seq_printf(sf, "LR_P1_STRIDE:\t\t0x%X\n", v[0]); + + get_values_from_reg(c->reg, 0x120, 2, v); + seq_printf(sf, "LR_P2_PTR_LOW:\t\t0x%X\n", v[0]); + seq_printf(sf, "LR_P2_PTR_HIGH:\t\t0x%X\n", v[1]); + + get_values_from_reg(c->reg, 0x130, 12, v); + for (i = 0; i < 12; i++) + seq_printf(sf, "LR_YUV_RGB_COEFF%u:\t0x%X\n", i, v[i]); + } + + if (rgb2rgb) { + get_values_from_reg(c->reg, LAYER_RGB_RGB_COEFF0, 12, v); + for (i = 0; i < 12; i++) + seq_printf(sf, "LS_RGB_RGB_COEFF%u:\t0x%X\n", i, v[i]); + } + + get_values_from_reg(c->reg, 0x160, 3, v); + seq_printf(sf, "%sAD_CONTROL:\t\t0x%X\n", prefix, v[0]); + seq_printf(sf, "%sAD_H_CROP:\t\t0x%X\n", prefix, v[1]); + seq_printf(sf, "%sAD_V_CROP:\t\t0x%X\n", prefix, v[2]); +} + +static int d71_layer_validate(struct komeda_component *c, + struct komeda_component_state *state) +{ + struct komeda_layer_state *st = to_layer_st(state); + struct komeda_layer *layer = to_layer(c); + struct drm_plane_state *plane_st; + struct drm_framebuffer *fb; + u32 fourcc, line_sz, max_line_sz; + + plane_st = drm_atomic_get_new_plane_state(state->obj.state, + state->plane); + fb = plane_st->fb; + fourcc = fb->format->format; + + if (drm_rotation_90_or_270(st->rot)) + line_sz = st->vsize - st->afbc_crop_t - st->afbc_crop_b; + else + line_sz = st->hsize - st->afbc_crop_l - st->afbc_crop_r; + + if (fb->modifier) { + if ((fb->modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK) == + AFBC_FORMAT_MOD_BLOCK_SIZE_32x8) + max_line_sz = layer->line_sz; + else + max_line_sz = layer->line_sz / 2; + + if (line_sz > max_line_sz) { + DRM_DEBUG_ATOMIC("afbc request line_sz: %d exceed the max afbc line_sz: %d.\n", + line_sz, max_line_sz); + return -EINVAL; + } + } + + if (fourcc == DRM_FORMAT_YUV420_10BIT && line_sz > 2046 && (st->afbc_crop_l % 4)) { + DRM_DEBUG_ATOMIC("YUV420_10BIT input_hsize: %d exceed the max size 2046.\n", + line_sz); + return -EINVAL; + } + + if (fourcc == DRM_FORMAT_X0L2 && line_sz > 2046 && (st->addr[0] % 16)) { + DRM_DEBUG_ATOMIC("X0L2 input_hsize: %d exceed the max size 2046.\n", + line_sz); + return -EINVAL; + } + + return 0; +} + +static const struct komeda_component_funcs d71_layer_funcs = { + .validate = d71_layer_validate, + .update = d71_layer_update, + .disable = d71_layer_disable, + .dump_register = d71_layer_dump, +}; + +static int d71_layer_init(struct d71_dev *d71, + struct block_header *blk, u32 __iomem *reg) +{ + struct komeda_component *c; + struct komeda_layer *layer; + u32 pipe_id, layer_id, layer_info; + + get_resources_id(blk->block_info, &pipe_id, &layer_id); + c = komeda_component_add(&d71->pipes[pipe_id]->base, sizeof(*layer), + layer_id, + BLOCK_INFO_INPUT_ID(blk->block_info), + &d71_layer_funcs, 0, + get_valid_inputs(blk), + 1, reg, "LPU%d_LAYER%d", pipe_id, layer_id); + if (IS_ERR(c)) { + DRM_ERROR("Failed to add layer component\n"); + return PTR_ERR(c); + } + + layer = to_layer(c); + layer_info = malidp_read32(reg, LAYER_INFO); + + if (layer_info & L_INFO_RF) + layer->layer_type = KOMEDA_FMT_RICH_LAYER; + else + layer->layer_type = KOMEDA_FMT_SIMPLE_LAYER; + + if (!d71->periph_addr) { + /* D32 or newer product */ + layer->line_sz = malidp_read32(reg, BLK_MAX_LINE_SIZE); + layer->yuv_line_sz = L_INFO_YUV_MAX_LINESZ(layer_info); + } else if (d71->max_line_size > 2048) { + /* D71 4K */ + layer->line_sz = d71->max_line_size; + layer->yuv_line_sz = layer->line_sz / 2; + } else { + /* D71 2K */ + if (layer->layer_type == KOMEDA_FMT_RICH_LAYER) { + /* rich layer is 4K configuration */ + layer->line_sz = d71->max_line_size * 2; + layer->yuv_line_sz = layer->line_sz / 2; + } else { + layer->line_sz = d71->max_line_size; + layer->yuv_line_sz = 0; + } + } + + set_range(&layer->hsize_in, 4, layer->line_sz); + + set_range(&layer->vsize_in, 4, d71->max_vsize); + + malidp_write32(reg, LAYER_PALPHA, D71_PALPHA_DEF_MAP); + + layer->supported_rots = DRM_MODE_ROTATE_MASK | DRM_MODE_REFLECT_MASK; + + return 0; +} + +static void d71_wb_layer_update(struct komeda_component *c, + struct komeda_component_state *state) +{ + struct komeda_layer_state *st = to_layer_st(state); + struct drm_connector_state *conn_st = state->wb_conn->state; + struct komeda_fb *kfb = to_kfb(conn_st->writeback_job->fb); + u32 ctrl = L_EN | LW_OFM, mask = L_EN | LW_OFM | LW_TBU_EN; + u32 __iomem *reg = c->reg; + + d71_layer_update_fb(c, kfb, st->addr); + + if (kfb->is_va) + ctrl |= LW_TBU_EN; + + malidp_write32(reg, BLK_IN_SIZE, HV_SIZE(st->hsize, st->vsize)); + malidp_write32(reg, BLK_INPUT_ID0, to_d71_input_id(state, 0)); + malidp_write32_mask(reg, BLK_CONTROL, mask, ctrl); +} + +static void d71_wb_layer_dump(struct komeda_component *c, struct seq_file *sf) +{ + u32 v[12], i; + + dump_block_header(sf, c->reg); + + get_values_from_reg(c->reg, 0x80, 1, v); + seq_printf(sf, "LW_INPUT_ID0:\t\t0x%X\n", v[0]); + + get_values_from_reg(c->reg, 0xD0, 3, v); + seq_printf(sf, "LW_CONTROL:\t\t0x%X\n", v[0]); + seq_printf(sf, "LW_PROG_LINE:\t\t0x%X\n", v[1]); + seq_printf(sf, "LW_FORMAT:\t\t0x%X\n", v[2]); + + get_values_from_reg(c->reg, 0xE0, 1, v); + seq_printf(sf, "LW_IN_SIZE:\t\t0x%X\n", v[0]); + + for (i = 0; i < 2; i++) { + get_values_from_reg(c->reg, 0x100 + i * 0x10, 3, v); + seq_printf(sf, "LW_P%u_PTR_LOW:\t\t0x%X\n", i, v[0]); + seq_printf(sf, "LW_P%u_PTR_HIGH:\t\t0x%X\n", i, v[1]); + seq_printf(sf, "LW_P%u_STRIDE:\t\t0x%X\n", i, v[2]); + } + + get_values_from_reg(c->reg, 0x130, 12, v); + for (i = 0; i < 12; i++) + seq_printf(sf, "LW_RGB_YUV_COEFF%u:\t0x%X\n", i, v[i]); +} + +static void d71_wb_layer_disable(struct komeda_component *c) +{ + malidp_write32(c->reg, BLK_INPUT_ID0, 0); + malidp_write32_mask(c->reg, BLK_CONTROL, L_EN, 0); +} + +static const struct komeda_component_funcs d71_wb_layer_funcs = { + .update = d71_wb_layer_update, + .disable = d71_wb_layer_disable, + .dump_register = d71_wb_layer_dump, +}; + +static int d71_wb_layer_init(struct d71_dev *d71, + struct block_header *blk, u32 __iomem *reg) +{ + struct komeda_component *c; + struct komeda_layer *wb_layer; + u32 pipe_id, layer_id; + + get_resources_id(blk->block_info, &pipe_id, &layer_id); + + c = komeda_component_add(&d71->pipes[pipe_id]->base, sizeof(*wb_layer), + layer_id, BLOCK_INFO_INPUT_ID(blk->block_info), + &d71_wb_layer_funcs, + 1, get_valid_inputs(blk), 0, reg, + "LPU%d_LAYER_WR", pipe_id); + if (IS_ERR(c)) { + DRM_ERROR("Failed to add wb_layer component\n"); + return PTR_ERR(c); + } + + wb_layer = to_layer(c); + wb_layer->layer_type = KOMEDA_FMT_WB_LAYER; + wb_layer->line_sz = get_blk_line_size(d71, reg); + wb_layer->yuv_line_sz = wb_layer->line_sz; + + set_range(&wb_layer->hsize_in, 64, wb_layer->line_sz); + set_range(&wb_layer->vsize_in, 64, d71->max_vsize); + + return 0; +} + +static void d71_component_disable(struct komeda_component *c) +{ + u32 __iomem *reg = c->reg; + u32 i; + + malidp_write32(reg, BLK_CONTROL, 0); + + for (i = 0; i < c->max_active_inputs; i++) { + malidp_write32(reg, BLK_INPUT_ID0 + (i << 2), 0); + + /* Besides clearing the input ID to zero, D71 compiz also has + * input enable bit in CU_INPUTx_CONTROL which need to be + * cleared. + */ + if (has_bit(c->id, KOMEDA_PIPELINE_COMPIZS)) + malidp_write32(reg, CU_INPUT0_CONTROL + + i * CU_PER_INPUT_REGS * 4, + CU_INPUT_CTRL_ALPHA(0xFF)); + } +} + +static void compiz_enable_input(u32 __iomem *id_reg, + u32 __iomem *cfg_reg, + u32 input_hw_id, + struct komeda_compiz_input_cfg *cin) +{ + u32 ctrl = CU_INPUT_CTRL_EN; + u8 blend = cin->pixel_blend_mode; + + if (blend == DRM_MODE_BLEND_PIXEL_NONE) + ctrl |= CU_INPUT_CTRL_PAD; + else if (blend == DRM_MODE_BLEND_PREMULTI) + ctrl |= CU_INPUT_CTRL_PMUL; + + ctrl |= CU_INPUT_CTRL_ALPHA(cin->layer_alpha); + + malidp_write32(id_reg, BLK_INPUT_ID0, input_hw_id); + + malidp_write32(cfg_reg, CU_INPUT0_SIZE, + HV_SIZE(cin->hsize, cin->vsize)); + malidp_write32(cfg_reg, CU_INPUT0_OFFSET, + HV_OFFSET(cin->hoffset, cin->voffset)); + malidp_write32(cfg_reg, CU_INPUT0_CONTROL, ctrl); +} + +static void d71_compiz_update(struct komeda_component *c, + struct komeda_component_state *state) +{ + struct komeda_compiz_state *st = to_compiz_st(state); + u32 __iomem *reg = c->reg; + u32 __iomem *id_reg, *cfg_reg; + u32 index; + + for_each_changed_input(state, index) { + id_reg = reg + index; + cfg_reg = reg + index * CU_PER_INPUT_REGS; + if (state->active_inputs & BIT(index)) { + compiz_enable_input(id_reg, cfg_reg, + to_d71_input_id(state, index), + &st->cins[index]); + } else { + malidp_write32(id_reg, BLK_INPUT_ID0, 0); + malidp_write32(cfg_reg, CU_INPUT0_CONTROL, 0); + } + } + + malidp_write32(reg, BLK_SIZE, HV_SIZE(st->hsize, st->vsize)); +} + +static void d71_compiz_dump(struct komeda_component *c, struct seq_file *sf) +{ + u32 v[8], i; + + dump_block_header(sf, c->reg); + + get_values_from_reg(c->reg, 0x80, 5, v); + for (i = 0; i < 5; i++) + seq_printf(sf, "CU_INPUT_ID%u:\t\t0x%X\n", i, v[i]); + + get_values_from_reg(c->reg, 0xA0, 5, v); + seq_printf(sf, "CU_IRQ_RAW_STATUS:\t0x%X\n", v[0]); + seq_printf(sf, "CU_IRQ_CLEAR:\t\t0x%X\n", v[1]); + seq_printf(sf, "CU_IRQ_MASK:\t\t0x%X\n", v[2]); + seq_printf(sf, "CU_IRQ_STATUS:\t\t0x%X\n", v[3]); + seq_printf(sf, "CU_STATUS:\t\t0x%X\n", v[4]); + + get_values_from_reg(c->reg, 0xD0, 2, v); + seq_printf(sf, "CU_CONTROL:\t\t0x%X\n", v[0]); + seq_printf(sf, "CU_SIZE:\t\t0x%X\n", v[1]); + + get_values_from_reg(c->reg, 0xDC, 1, v); + seq_printf(sf, "CU_BG_COLOR:\t\t0x%X\n", v[0]); + + for (i = 0, v[4] = 0xE0; i < 5; i++, v[4] += 0x10) { + get_values_from_reg(c->reg, v[4], 3, v); + seq_printf(sf, "CU_INPUT%u_SIZE:\t\t0x%X\n", i, v[0]); + seq_printf(sf, "CU_INPUT%u_OFFSET:\t0x%X\n", i, v[1]); + seq_printf(sf, "CU_INPUT%u_CONTROL:\t0x%X\n", i, v[2]); + } + + get_values_from_reg(c->reg, 0x130, 2, v); + seq_printf(sf, "CU_USER_LOW:\t\t0x%X\n", v[0]); + seq_printf(sf, "CU_USER_HIGH:\t\t0x%X\n", v[1]); +} + +static const struct komeda_component_funcs d71_compiz_funcs = { + .update = d71_compiz_update, + .disable = d71_component_disable, + .dump_register = d71_compiz_dump, +}; + +static int d71_compiz_init(struct d71_dev *d71, + struct block_header *blk, u32 __iomem *reg) +{ + struct komeda_component *c; + struct komeda_compiz *compiz; + 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(*compiz), + comp_id, + BLOCK_INFO_INPUT_ID(blk->block_info), + &d71_compiz_funcs, + CU_NUM_INPUT_IDS, get_valid_inputs(blk), + CU_NUM_OUTPUT_IDS, reg, + "CU%d", pipe_id); + if (IS_ERR(c)) + return PTR_ERR(c); + + compiz = to_compiz(c); + + set_range(&compiz->hsize, 64, get_blk_line_size(d71, reg)); + set_range(&compiz->vsize, 64, d71->max_vsize); + + return 0; +} + +static void d71_scaler_update_filter_lut(u32 __iomem *reg, u32 hsize_in, + u32 vsize_in, u32 hsize_out, + u32 vsize_out) +{ + u32 val = 0; + + if (hsize_in <= hsize_out) + val |= 0x62; + else if (hsize_in <= (hsize_out + hsize_out / 2)) + val |= 0x63; + else if (hsize_in <= hsize_out * 2) + val |= 0x64; + else if (hsize_in <= hsize_out * 2 + (hsize_out * 3) / 4) + val |= 0x65; + else + val |= 0x66; + + if (vsize_in <= vsize_out) + val |= SC_VTSEL(0x6A); + else if (vsize_in <= (vsize_out + vsize_out / 2)) + val |= SC_VTSEL(0x6B); + else if (vsize_in <= vsize_out * 2) + val |= SC_VTSEL(0x6C); + else if (vsize_in <= vsize_out * 2 + vsize_out * 3 / 4) + val |= SC_VTSEL(0x6D); + else + val |= SC_VTSEL(0x6E); + + malidp_write32(reg, SC_COEFFTAB, val); +} + +static void d71_scaler_update(struct komeda_component *c, + struct komeda_component_state *state) +{ + struct komeda_scaler_state *st = to_scaler_st(state); + u32 __iomem *reg = c->reg; + u32 init_ph, delta_ph, ctrl; + + d71_scaler_update_filter_lut(reg, st->hsize_in, st->vsize_in, + st->hsize_out, st->vsize_out); + + 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; + } + + malidp_write32(reg, SC_H_INIT_PH, init_ph); + + delta_ph = (st->total_hsize_in << 16) / st->total_hsize_out; + malidp_write32(reg, SC_H_DELTA_PH, delta_ph); + + init_ph = (st->total_vsize_in << 15) / st->vsize_out; + malidp_write32(reg, SC_V_INIT_PH, init_ph); + + 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, 0)); +} + +static void d71_scaler_dump(struct komeda_component *c, struct seq_file *sf) +{ + u32 v[10]; + + dump_block_header(sf, c->reg); + + get_values_from_reg(c->reg, 0x80, 1, v); + seq_printf(sf, "SC_INPUT_ID0:\t\t0x%X\n", v[0]); + + get_values_from_reg(c->reg, 0xD0, 1, v); + seq_printf(sf, "SC_CONTROL:\t\t0x%X\n", v[0]); + + get_values_from_reg(c->reg, 0xDC, 9, v); + seq_printf(sf, "SC_COEFFTAB:\t\t0x%X\n", v[0]); + seq_printf(sf, "SC_IN_SIZE:\t\t0x%X\n", v[1]); + seq_printf(sf, "SC_OUT_SIZE:\t\t0x%X\n", v[2]); + seq_printf(sf, "SC_H_CROP:\t\t0x%X\n", v[3]); + seq_printf(sf, "SC_V_CROP:\t\t0x%X\n", v[4]); + seq_printf(sf, "SC_H_INIT_PH:\t\t0x%X\n", v[5]); + seq_printf(sf, "SC_H_DELTA_PH:\t\t0x%X\n", v[6]); + seq_printf(sf, "SC_V_INIT_PH:\t\t0x%X\n", v[7]); + seq_printf(sf, "SC_V_DELTA_PH:\t\t0x%X\n", v[8]); + + get_values_from_reg(c->reg, 0x130, 10, v); + seq_printf(sf, "SC_ENH_LIMITS:\t\t0x%X\n", v[0]); + seq_printf(sf, "SC_ENH_COEFF0:\t\t0x%X\n", v[1]); + seq_printf(sf, "SC_ENH_COEFF1:\t\t0x%X\n", v[2]); + seq_printf(sf, "SC_ENH_COEFF2:\t\t0x%X\n", v[3]); + seq_printf(sf, "SC_ENH_COEFF3:\t\t0x%X\n", v[4]); + seq_printf(sf, "SC_ENH_COEFF4:\t\t0x%X\n", v[5]); + seq_printf(sf, "SC_ENH_COEFF5:\t\t0x%X\n", v[6]); + seq_printf(sf, "SC_ENH_COEFF6:\t\t0x%X\n", v[7]); + seq_printf(sf, "SC_ENH_COEFF7:\t\t0x%X\n", v[8]); + seq_printf(sf, "SC_ENH_COEFF8:\t\t0x%X\n", v[9]); +} + +static const struct komeda_component_funcs d71_scaler_funcs = { + .update = d71_scaler_update, + .disable = d71_component_disable, + .dump_register = d71_scaler_dump, +}; + +static int d71_scaler_init(struct d71_dev *d71, + struct block_header *blk, u32 __iomem *reg) +{ + struct komeda_component *c; + struct komeda_scaler *scaler; + 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(*scaler), + comp_id, BLOCK_INFO_INPUT_ID(blk->block_info), + &d71_scaler_funcs, + 1, get_valid_inputs(blk), 1, reg, + "CU%d_SCALER%d", + pipe_id, BLOCK_INFO_BLK_ID(blk->block_info)); + + if (IS_ERR(c)) { + DRM_ERROR("Failed to initialize scaler"); + return PTR_ERR(c); + } + + scaler = to_scaler(c); + set_range(&scaler->hsize, 4, __get_blk_line_size(d71, reg, 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); + + return 0; +} + +static int d71_downscaling_clk_check(struct komeda_pipeline *pipe, + struct drm_display_mode *mode, + unsigned long aclk_rate, + struct komeda_data_flow_cfg *dflow) +{ + u32 h_in = dflow->in_w; + u32 v_in = dflow->in_h; + u32 v_out = dflow->out_h; + u64 fraction, denominator; + + /* D71 downscaling must satisfy the following equation + * + * ACLK h_in * v_in + * ------- >= --------------------------------------------- + * PXLCLK (h_total - (1 + 2 * v_in / v_out)) * v_out + * + * In only horizontal downscaling situation, the right side should be + * multiplied by (h_total - 3) / (h_active - 3), then equation becomes + * + * ACLK h_in + * ------- >= ---------------- + * PXLCLK (h_active - 3) + * + * To avoid precision lost the equation 1 will be convert to: + * + * ACLK h_in * v_in + * ------- >= ----------------------------------- + * PXLCLK (h_total -1 ) * v_out - 2 * v_in + */ + if (v_in == v_out) { + fraction = h_in; + denominator = mode->hdisplay - 3; + } else { + fraction = h_in * v_in; + denominator = (mode->htotal - 1) * v_out - 2 * v_in; + } + + return aclk_rate * denominator >= mode->crtc_clock * 1000 * fraction ? + 0 : -EINVAL; +} + +static void d71_splitter_update(struct komeda_component *c, + struct komeda_component_state *state) +{ + struct komeda_splitter_state *st = to_splitter_st(state); + u32 __iomem *reg = c->reg; + + malidp_write32(reg, BLK_INPUT_ID0, to_d71_input_id(state, 0)); + malidp_write32(reg, BLK_SIZE, HV_SIZE(st->hsize, st->vsize)); + malidp_write32(reg, SP_OVERLAP_SIZE, st->overlap & 0x1FFF); + malidp_write32(reg, BLK_CONTROL, BLK_CTRL_EN); +} + +static void d71_splitter_dump(struct komeda_component *c, struct seq_file *sf) +{ + u32 v[3]; + + dump_block_header(sf, c->reg); + + get_values_from_reg(c->reg, BLK_INPUT_ID0, 1, v); + seq_printf(sf, "SP_INPUT_ID0:\t\t0x%X\n", v[0]); + + get_values_from_reg(c->reg, BLK_CONTROL, 3, v); + seq_printf(sf, "SP_CONTROL:\t\t0x%X\n", v[0]); + seq_printf(sf, "SP_SIZE:\t\t0x%X\n", v[1]); + seq_printf(sf, "SP_OVERLAP_SIZE:\t0x%X\n", v[2]); +} + +static const struct komeda_component_funcs d71_splitter_funcs = { + .update = d71_splitter_update, + .disable = d71_component_disable, + .dump_register = d71_splitter_dump, +}; + +static int d71_splitter_init(struct d71_dev *d71, + struct block_header *blk, u32 __iomem *reg) +{ + struct komeda_component *c; + struct komeda_splitter *splitter; + 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(*splitter), + comp_id, + BLOCK_INFO_INPUT_ID(blk->block_info), + &d71_splitter_funcs, + 1, get_valid_inputs(blk), 2, reg, + "CU%d_SPLITTER", pipe_id); + + if (IS_ERR(c)) { + DRM_ERROR("Failed to initialize splitter"); + return -1; + } + + splitter = to_splitter(c); + + set_range(&splitter->hsize, 4, get_blk_line_size(d71, reg)); + set_range(&splitter->vsize, 4, d71->max_vsize); + + 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, 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 (IS_ERR(c)) { + DRM_ERROR("Failed to initialize merger.\n"); + return PTR_ERR(c); + } + + merger = to_merger(c); + + set_range(&merger->hsize_merged, 4, + __get_blk_line_size(d71, reg, 4032)); + set_range(&merger->vsize_merged, 4, 4096); + + return 0; +} + +static void d71_improc_update(struct komeda_component *c, + struct komeda_component_state *state) +{ + struct drm_crtc_state *crtc_st = state->crtc->state; + struct komeda_improc_state *st = to_improc_st(state); + struct d71_pipeline *pipe = to_d71_pipeline(c->pipeline); + u32 __iomem *reg = c->reg; + u32 index, mask = 0, ctrl = 0; + + for_each_changed_input(state, index) + malidp_write32(reg, BLK_INPUT_ID0 + index * 4, + to_d71_input_id(state, index)); + + malidp_write32(reg, BLK_SIZE, HV_SIZE(st->hsize, st->vsize)); + malidp_write32(reg, IPS_DEPTH, st->color_depth); + + if (crtc_st->color_mgmt_changed) { + mask |= IPS_CTRL_FT | IPS_CTRL_RGB; + + if (crtc_st->gamma_lut) { + malidp_write_group(pipe->dou_ft_coeff_addr, FT_COEFF0, + KOMEDA_N_GAMMA_COEFFS, + st->fgamma_coeffs); + ctrl |= IPS_CTRL_FT; /* enable gamma */ + } + + if (crtc_st->ctm) { + malidp_write_group(reg, IPS_RGB_RGB_COEFF0, + KOMEDA_N_CTM_COEFFS, + st->ctm_coeffs); + ctrl |= IPS_CTRL_RGB; /* enable gamut */ + } + } + + mask |= IPS_CTRL_YUV | IPS_CTRL_CHD422 | IPS_CTRL_CHD420; + + /* config color format */ + if (st->color_format == DRM_OUTPUT_COLOR_FORMAT_YCBCR420) + ctrl |= IPS_CTRL_YUV | IPS_CTRL_CHD422 | IPS_CTRL_CHD420; + else if (st->color_format == DRM_OUTPUT_COLOR_FORMAT_YCBCR422) + ctrl |= IPS_CTRL_YUV | IPS_CTRL_CHD422; + else if (st->color_format == DRM_OUTPUT_COLOR_FORMAT_YCBCR444) + ctrl |= IPS_CTRL_YUV; + + malidp_write32_mask(reg, BLK_CONTROL, mask, ctrl); +} + +static void d71_improc_dump(struct komeda_component *c, struct seq_file *sf) +{ + u32 v[12], i; + + dump_block_header(sf, c->reg); + + get_values_from_reg(c->reg, 0x80, 2, v); + seq_printf(sf, "IPS_INPUT_ID0:\t\t0x%X\n", v[0]); + seq_printf(sf, "IPS_INPUT_ID1:\t\t0x%X\n", v[1]); + + get_values_from_reg(c->reg, 0xC0, 1, v); + seq_printf(sf, "IPS_INFO:\t\t0x%X\n", v[0]); + + get_values_from_reg(c->reg, 0xD0, 3, v); + seq_printf(sf, "IPS_CONTROL:\t\t0x%X\n", v[0]); + seq_printf(sf, "IPS_SIZE:\t\t0x%X\n", v[1]); + seq_printf(sf, "IPS_DEPTH:\t\t0x%X\n", v[2]); + + get_values_from_reg(c->reg, 0x130, 12, v); + for (i = 0; i < 12; i++) + seq_printf(sf, "IPS_RGB_RGB_COEFF%u:\t0x%X\n", i, v[i]); + + get_values_from_reg(c->reg, 0x170, 12, v); + for (i = 0; i < 12; i++) + seq_printf(sf, "IPS_RGB_YUV_COEFF%u:\t0x%X\n", i, v[i]); +} + +static const struct komeda_component_funcs d71_improc_funcs = { + .update = d71_improc_update, + .disable = d71_component_disable, + .dump_register = d71_improc_dump, +}; + +static int d71_improc_init(struct d71_dev *d71, + struct block_header *blk, u32 __iomem *reg) +{ + struct komeda_component *c; + struct komeda_improc *improc; + u32 pipe_id, comp_id, value; + + get_resources_id(blk->block_info, &pipe_id, &comp_id); + + c = komeda_component_add(&d71->pipes[pipe_id]->base, sizeof(*improc), + comp_id, + BLOCK_INFO_INPUT_ID(blk->block_info), + &d71_improc_funcs, IPS_NUM_INPUT_IDS, + get_valid_inputs(blk), + IPS_NUM_OUTPUT_IDS, reg, "DOU%d_IPS", pipe_id); + if (IS_ERR(c)) { + DRM_ERROR("Failed to add improc component\n"); + return PTR_ERR(c); + } + + improc = to_improc(c); + improc->supported_color_depths = BIT(8) | BIT(10); + improc->supported_color_formats = BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) | + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422); + value = malidp_read32(reg, BLK_INFO); + if (value & IPS_INFO_CHD420) + improc->supported_color_formats |= BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420); + + improc->supports_csc = true; + improc->supports_gamma = true; + + return 0; +} + +static void d71_timing_ctrlr_disable(struct komeda_component *c) +{ + malidp_write32_mask(c->reg, BLK_CONTROL, BS_CTRL_EN, 0); +} + +static void d71_timing_ctrlr_update(struct komeda_component *c, + struct komeda_component_state *state) +{ + struct drm_crtc_state *crtc_st = state->crtc->state; + struct drm_display_mode *mode = &crtc_st->adjusted_mode; + u32 __iomem *reg = c->reg; + u32 hactive, hfront_porch, hback_porch, hsync_len; + u32 vactive, vfront_porch, vback_porch, vsync_len; + u32 value; + + hactive = mode->crtc_hdisplay; + hfront_porch = mode->crtc_hsync_start - mode->crtc_hdisplay; + hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; + hback_porch = mode->crtc_htotal - mode->crtc_hsync_end; + + vactive = mode->crtc_vdisplay; + vfront_porch = mode->crtc_vsync_start - mode->crtc_vdisplay; + vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start; + vback_porch = mode->crtc_vtotal - mode->crtc_vsync_end; + + malidp_write32(reg, BS_ACTIVESIZE, HV_SIZE(hactive, vactive)); + malidp_write32(reg, BS_HINTERVALS, BS_H_INTVALS(hfront_porch, + hback_porch)); + malidp_write32(reg, BS_VINTERVALS, BS_V_INTVALS(vfront_porch, + vback_porch)); + + value = BS_SYNC_VSW(vsync_len) | BS_SYNC_HSW(hsync_len); + value |= mode->flags & DRM_MODE_FLAG_PVSYNC ? BS_SYNC_VSP : 0; + value |= mode->flags & DRM_MODE_FLAG_PHSYNC ? BS_SYNC_HSP : 0; + malidp_write32(reg, BS_SYNC, value); + + malidp_write32(reg, BS_PROG_LINE, D71_DEFAULT_PREPRETCH_LINE - 1); + malidp_write32(reg, BS_PREFETCH_LINE, D71_DEFAULT_PREPRETCH_LINE); + + /* configure bs control register */ + value = BS_CTRL_EN | BS_CTRL_VM; + if (c->pipeline->dual_link) { + malidp_write32(reg, BS_DRIFT_TO, hfront_porch + 16); + value |= BS_CTRL_DL; + } + + malidp_write32(reg, BLK_CONTROL, value); +} + +static void d71_timing_ctrlr_dump(struct komeda_component *c, + struct seq_file *sf) +{ + u32 v[8], i; + + dump_block_header(sf, c->reg); + + get_values_from_reg(c->reg, 0xC0, 1, v); + seq_printf(sf, "BS_INFO:\t\t0x%X\n", v[0]); + + get_values_from_reg(c->reg, 0xD0, 8, v); + seq_printf(sf, "BS_CONTROL:\t\t0x%X\n", v[0]); + seq_printf(sf, "BS_PROG_LINE:\t\t0x%X\n", v[1]); + seq_printf(sf, "BS_PREFETCH_LINE:\t0x%X\n", v[2]); + seq_printf(sf, "BS_BG_COLOR:\t\t0x%X\n", v[3]); + seq_printf(sf, "BS_ACTIVESIZE:\t\t0x%X\n", v[4]); + seq_printf(sf, "BS_HINTERVALS:\t\t0x%X\n", v[5]); + seq_printf(sf, "BS_VINTERVALS:\t\t0x%X\n", v[6]); + seq_printf(sf, "BS_SYNC:\t\t0x%X\n", v[7]); + + get_values_from_reg(c->reg, 0x100, 3, v); + seq_printf(sf, "BS_DRIFT_TO:\t\t0x%X\n", v[0]); + seq_printf(sf, "BS_FRAME_TO:\t\t0x%X\n", v[1]); + seq_printf(sf, "BS_TE_TO:\t\t0x%X\n", v[2]); + + get_values_from_reg(c->reg, 0x110, 3, v); + for (i = 0; i < 3; i++) + seq_printf(sf, "BS_T%u_INTERVAL:\t\t0x%X\n", i, v[i]); + + get_values_from_reg(c->reg, 0x120, 5, v); + for (i = 0; i < 2; i++) { + seq_printf(sf, "BS_CRC%u_LOW:\t\t0x%X\n", i, v[i << 1]); + seq_printf(sf, "BS_CRC%u_HIGH:\t\t0x%X\n", i, v[(i << 1) + 1]); + } + seq_printf(sf, "BS_USER:\t\t0x%X\n", v[4]); +} + +static const struct komeda_component_funcs d71_timing_ctrlr_funcs = { + .update = d71_timing_ctrlr_update, + .disable = d71_timing_ctrlr_disable, + .dump_register = d71_timing_ctrlr_dump, +}; + +static int d71_timing_ctrlr_init(struct d71_dev *d71, + struct block_header *blk, u32 __iomem *reg) +{ + struct komeda_component *c; + struct komeda_timing_ctrlr *ctrlr; + 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(*ctrlr), + KOMEDA_COMPONENT_TIMING_CTRLR, + BLOCK_INFO_INPUT_ID(blk->block_info), + &d71_timing_ctrlr_funcs, + 1, BIT(KOMEDA_COMPONENT_IPS0 + pipe_id), + BS_NUM_OUTPUT_IDS, reg, "DOU%d_BS", pipe_id); + if (IS_ERR(c)) { + DRM_ERROR("Failed to add display_ctrl component\n"); + return PTR_ERR(c); + } + + ctrlr = to_ctrlr(c); + + ctrlr->supports_dual_link = d71->supports_dual_link; + + return 0; +} + +int d71_probe_block(struct d71_dev *d71, + struct block_header *blk, u32 __iomem *reg) +{ + struct d71_pipeline *pipe; + int blk_id = BLOCK_INFO_BLK_ID(blk->block_info); + + int err = 0; + + switch (BLOCK_INFO_BLK_TYPE(blk->block_info)) { + case D71_BLK_TYPE_GCU: + break; + + case D71_BLK_TYPE_LPU: + pipe = d71->pipes[blk_id]; + pipe->lpu_addr = reg; + break; + + case D71_BLK_TYPE_LPU_LAYER: + err = d71_layer_init(d71, blk, reg); + break; + + case D71_BLK_TYPE_LPU_WB_LAYER: + err = d71_wb_layer_init(d71, blk, reg); + break; + + case D71_BLK_TYPE_CU: + pipe = d71->pipes[blk_id]; + pipe->cu_addr = reg; + err = d71_compiz_init(d71, blk, reg); + break; + + case D71_BLK_TYPE_CU_SCALER: + err = d71_scaler_init(d71, blk, reg); + break; + + case D71_BLK_TYPE_CU_SPLITTER: + err = d71_splitter_init(d71, blk, reg); + break; + + case D71_BLK_TYPE_CU_MERGER: + err = d71_merger_init(d71, blk, reg); + break; + + case D71_BLK_TYPE_DOU: + pipe = d71->pipes[blk_id]; + pipe->dou_addr = reg; + break; + + case D71_BLK_TYPE_DOU_IPS: + err = d71_improc_init(d71, blk, reg); + break; + + case D71_BLK_TYPE_DOU_FT_COEFF: + pipe = d71->pipes[blk_id]; + pipe->dou_ft_coeff_addr = reg; + break; + + case D71_BLK_TYPE_DOU_BS: + err = d71_timing_ctrlr_init(d71, blk, reg); + break; + + case D71_BLK_TYPE_GLB_LT_COEFF: + break; + + case D71_BLK_TYPE_GLB_SCL_COEFF: + d71->glb_scl_coeff_addr[blk_id] = reg; + break; + + default: + DRM_ERROR("Unknown block (block_info: 0x%x) is found\n", + blk->block_info); + err = -EINVAL; + break; + } + + return err; +} + +static void d71_gcu_dump(struct d71_dev *d71, struct seq_file *sf) +{ + u32 v[5]; + + seq_puts(sf, "\n------ GCU ------\n"); + + get_values_from_reg(d71->gcu_addr, 0, 3, v); + seq_printf(sf, "GLB_ARCH_ID:\t\t0x%X\n", v[0]); + seq_printf(sf, "GLB_CORE_ID:\t\t0x%X\n", v[1]); + seq_printf(sf, "GLB_CORE_INFO:\t\t0x%X\n", v[2]); + + get_values_from_reg(d71->gcu_addr, 0x10, 1, v); + seq_printf(sf, "GLB_IRQ_STATUS:\t\t0x%X\n", v[0]); + + get_values_from_reg(d71->gcu_addr, 0xA0, 5, v); + seq_printf(sf, "GCU_IRQ_RAW_STATUS:\t0x%X\n", v[0]); + seq_printf(sf, "GCU_IRQ_CLEAR:\t\t0x%X\n", v[1]); + seq_printf(sf, "GCU_IRQ_MASK:\t\t0x%X\n", v[2]); + seq_printf(sf, "GCU_IRQ_STATUS:\t\t0x%X\n", v[3]); + seq_printf(sf, "GCU_STATUS:\t\t0x%X\n", v[4]); + + get_values_from_reg(d71->gcu_addr, 0xD0, 3, v); + seq_printf(sf, "GCU_CONTROL:\t\t0x%X\n", v[0]); + seq_printf(sf, "GCU_CONFIG_VALID0:\t0x%X\n", v[1]); + seq_printf(sf, "GCU_CONFIG_VALID1:\t0x%X\n", v[2]); +} + +static void d71_lpu_dump(struct d71_pipeline *pipe, struct seq_file *sf) +{ + u32 v[6]; + + seq_printf(sf, "\n------ LPU%d ------\n", pipe->base.id); + + dump_block_header(sf, pipe->lpu_addr); + + get_values_from_reg(pipe->lpu_addr, 0xA0, 6, v); + seq_printf(sf, "LPU_IRQ_RAW_STATUS:\t0x%X\n", v[0]); + seq_printf(sf, "LPU_IRQ_CLEAR:\t\t0x%X\n", v[1]); + seq_printf(sf, "LPU_IRQ_MASK:\t\t0x%X\n", v[2]); + seq_printf(sf, "LPU_IRQ_STATUS:\t\t0x%X\n", v[3]); + seq_printf(sf, "LPU_STATUS:\t\t0x%X\n", v[4]); + seq_printf(sf, "LPU_TBU_STATUS:\t\t0x%X\n", v[5]); + + get_values_from_reg(pipe->lpu_addr, 0xC0, 1, v); + seq_printf(sf, "LPU_INFO:\t\t0x%X\n", v[0]); + + get_values_from_reg(pipe->lpu_addr, 0xD0, 3, v); + seq_printf(sf, "LPU_RAXI_CONTROL:\t0x%X\n", v[0]); + seq_printf(sf, "LPU_WAXI_CONTROL:\t0x%X\n", v[1]); + seq_printf(sf, "LPU_TBU_CONTROL:\t0x%X\n", v[2]); +} + +static void d71_dou_dump(struct d71_pipeline *pipe, struct seq_file *sf) +{ + u32 v[5]; + + seq_printf(sf, "\n------ DOU%d ------\n", pipe->base.id); + + dump_block_header(sf, pipe->dou_addr); + + get_values_from_reg(pipe->dou_addr, 0xA0, 5, v); + seq_printf(sf, "DOU_IRQ_RAW_STATUS:\t0x%X\n", v[0]); + seq_printf(sf, "DOU_IRQ_CLEAR:\t\t0x%X\n", v[1]); + seq_printf(sf, "DOU_IRQ_MASK:\t\t0x%X\n", v[2]); + seq_printf(sf, "DOU_IRQ_STATUS:\t\t0x%X\n", v[3]); + seq_printf(sf, "DOU_STATUS:\t\t0x%X\n", v[4]); +} + +static void d71_pipeline_dump(struct komeda_pipeline *pipe, struct seq_file *sf) +{ + struct d71_pipeline *d71_pipe = to_d71_pipeline(pipe); + + d71_lpu_dump(d71_pipe, sf); + d71_dou_dump(d71_pipe, sf); +} + +const struct komeda_pipeline_funcs d71_pipeline_funcs = { + .downscaling_clk_check = d71_downscaling_clk_check, + .dump_register = d71_pipeline_dump, +}; + +void d71_dump(struct komeda_dev *mdev, struct seq_file *sf) +{ + struct d71_dev *d71 = mdev->chip_data; + + d71_gcu_dump(d71, sf); +} diff --git a/drivers/gpu/drm/arm/display/komeda/d71/d71_dev.c b/drivers/gpu/drm/arm/display/komeda/d71/d71_dev.c new file mode 100644 index 000000000..f105e3a2d --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/d71/d71_dev.c @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ + +#include +#include +#include "d71_dev.h" +#include "malidp_io.h" + +static u64 get_lpu_event(struct d71_pipeline *d71_pipeline) +{ + u32 __iomem *reg = d71_pipeline->lpu_addr; + u32 status, raw_status; + u64 evts = 0ULL; + + raw_status = malidp_read32(reg, BLK_IRQ_RAW_STATUS); + if (raw_status & LPU_IRQ_IBSY) + evts |= KOMEDA_EVENT_IBSY; + if (raw_status & LPU_IRQ_EOW) + evts |= KOMEDA_EVENT_EOW; + if (raw_status & LPU_IRQ_OVR) + evts |= KOMEDA_EVENT_OVR; + + if (raw_status & (LPU_IRQ_ERR | LPU_IRQ_IBSY | LPU_IRQ_OVR)) { + u32 restore = 0, tbu_status; + /* Check error of LPU status */ + status = malidp_read32(reg, BLK_STATUS); + if (status & LPU_STATUS_AXIE) { + restore |= LPU_STATUS_AXIE; + evts |= KOMEDA_ERR_AXIE; + } + if (status & LPU_STATUS_ACE0) { + restore |= LPU_STATUS_ACE0; + evts |= KOMEDA_ERR_ACE0; + } + if (status & LPU_STATUS_ACE1) { + restore |= LPU_STATUS_ACE1; + evts |= KOMEDA_ERR_ACE1; + } + if (status & LPU_STATUS_ACE2) { + restore |= LPU_STATUS_ACE2; + evts |= KOMEDA_ERR_ACE2; + } + if (status & LPU_STATUS_ACE3) { + restore |= LPU_STATUS_ACE3; + evts |= KOMEDA_ERR_ACE3; + } + if (status & LPU_STATUS_FEMPTY) { + restore |= LPU_STATUS_FEMPTY; + evts |= KOMEDA_EVENT_EMPTY; + } + if (status & LPU_STATUS_FFULL) { + restore |= LPU_STATUS_FFULL; + evts |= KOMEDA_EVENT_FULL; + } + + if (restore != 0) + malidp_write32_mask(reg, BLK_STATUS, restore, 0); + + restore = 0; + /* Check errors of TBU status */ + tbu_status = malidp_read32(reg, LPU_TBU_STATUS); + if (tbu_status & LPU_TBU_STATUS_TCF) { + restore |= LPU_TBU_STATUS_TCF; + evts |= KOMEDA_ERR_TCF; + } + if (tbu_status & LPU_TBU_STATUS_TTNG) { + restore |= LPU_TBU_STATUS_TTNG; + evts |= KOMEDA_ERR_TTNG; + } + if (tbu_status & LPU_TBU_STATUS_TITR) { + restore |= LPU_TBU_STATUS_TITR; + evts |= KOMEDA_ERR_TITR; + } + if (tbu_status & LPU_TBU_STATUS_TEMR) { + restore |= LPU_TBU_STATUS_TEMR; + evts |= KOMEDA_ERR_TEMR; + } + if (tbu_status & LPU_TBU_STATUS_TTF) { + restore |= LPU_TBU_STATUS_TTF; + evts |= KOMEDA_ERR_TTF; + } + if (restore != 0) + malidp_write32_mask(reg, LPU_TBU_STATUS, restore, 0); + } + + malidp_write32(reg, BLK_IRQ_CLEAR, raw_status); + return evts; +} + +static u64 get_cu_event(struct d71_pipeline *d71_pipeline) +{ + u32 __iomem *reg = d71_pipeline->cu_addr; + u32 status, raw_status; + u64 evts = 0ULL; + + raw_status = malidp_read32(reg, BLK_IRQ_RAW_STATUS); + if (raw_status & CU_IRQ_OVR) + evts |= KOMEDA_EVENT_OVR; + + if (raw_status & (CU_IRQ_ERR | CU_IRQ_OVR)) { + status = malidp_read32(reg, BLK_STATUS) & 0x7FFFFFFF; + if (status & CU_STATUS_CPE) + evts |= KOMEDA_ERR_CPE; + if (status & CU_STATUS_ZME) + evts |= KOMEDA_ERR_ZME; + if (status & CU_STATUS_CFGE) + evts |= KOMEDA_ERR_CFGE; + if (status) + malidp_write32_mask(reg, BLK_STATUS, status, 0); + } + + malidp_write32(reg, BLK_IRQ_CLEAR, raw_status); + + return evts; +} + +static u64 get_dou_event(struct d71_pipeline *d71_pipeline) +{ + u32 __iomem *reg = d71_pipeline->dou_addr; + u32 status, raw_status; + u64 evts = 0ULL; + + raw_status = malidp_read32(reg, BLK_IRQ_RAW_STATUS); + if (raw_status & DOU_IRQ_PL0) + evts |= KOMEDA_EVENT_VSYNC; + if (raw_status & DOU_IRQ_UND) + evts |= KOMEDA_EVENT_URUN; + + if (raw_status & (DOU_IRQ_ERR | DOU_IRQ_UND)) { + u32 restore = 0; + + status = malidp_read32(reg, BLK_STATUS); + if (status & DOU_STATUS_DRIFTTO) { + restore |= DOU_STATUS_DRIFTTO; + evts |= KOMEDA_ERR_DRIFTTO; + } + if (status & DOU_STATUS_FRAMETO) { + restore |= DOU_STATUS_FRAMETO; + evts |= KOMEDA_ERR_FRAMETO; + } + if (status & DOU_STATUS_TETO) { + restore |= DOU_STATUS_TETO; + evts |= KOMEDA_ERR_TETO; + } + if (status & DOU_STATUS_CSCE) { + restore |= DOU_STATUS_CSCE; + evts |= KOMEDA_ERR_CSCE; + } + + if (restore != 0) + malidp_write32_mask(reg, BLK_STATUS, restore, 0); + } + + malidp_write32(reg, BLK_IRQ_CLEAR, raw_status); + return evts; +} + +static u64 get_pipeline_event(struct d71_pipeline *d71_pipeline, u32 gcu_status) +{ + u32 evts = 0ULL; + + if (gcu_status & (GLB_IRQ_STATUS_LPU0 | GLB_IRQ_STATUS_LPU1)) + evts |= get_lpu_event(d71_pipeline); + + if (gcu_status & (GLB_IRQ_STATUS_CU0 | GLB_IRQ_STATUS_CU1)) + evts |= get_cu_event(d71_pipeline); + + if (gcu_status & (GLB_IRQ_STATUS_DOU0 | GLB_IRQ_STATUS_DOU1)) + evts |= get_dou_event(d71_pipeline); + + return evts; +} + +static irqreturn_t +d71_irq_handler(struct komeda_dev *mdev, struct komeda_events *evts) +{ + struct d71_dev *d71 = mdev->chip_data; + u32 status, gcu_status, raw_status; + + gcu_status = malidp_read32(d71->gcu_addr, GLB_IRQ_STATUS); + + if (gcu_status & GLB_IRQ_STATUS_GCU) { + raw_status = malidp_read32(d71->gcu_addr, BLK_IRQ_RAW_STATUS); + if (raw_status & GCU_IRQ_CVAL0) + evts->pipes[0] |= KOMEDA_EVENT_FLIP; + if (raw_status & GCU_IRQ_CVAL1) + evts->pipes[1] |= KOMEDA_EVENT_FLIP; + if (raw_status & GCU_IRQ_ERR) { + status = malidp_read32(d71->gcu_addr, BLK_STATUS); + if (status & GCU_STATUS_MERR) { + evts->global |= KOMEDA_ERR_MERR; + malidp_write32_mask(d71->gcu_addr, BLK_STATUS, + GCU_STATUS_MERR, 0); + } + } + + malidp_write32(d71->gcu_addr, BLK_IRQ_CLEAR, raw_status); + } + + if (gcu_status & GLB_IRQ_STATUS_PIPE0) + evts->pipes[0] |= get_pipeline_event(d71->pipes[0], gcu_status); + + if (gcu_status & GLB_IRQ_STATUS_PIPE1) + evts->pipes[1] |= get_pipeline_event(d71->pipes[1], gcu_status); + + return IRQ_RETVAL(gcu_status); +} + +#define ENABLED_GCU_IRQS (GCU_IRQ_CVAL0 | GCU_IRQ_CVAL1 | \ + GCU_IRQ_MODE | GCU_IRQ_ERR) +#define ENABLED_LPU_IRQS (LPU_IRQ_IBSY | LPU_IRQ_ERR | LPU_IRQ_EOW) +#define ENABLED_CU_IRQS (CU_IRQ_OVR | CU_IRQ_ERR) +#define ENABLED_DOU_IRQS (DOU_IRQ_UND | DOU_IRQ_ERR) + +static int d71_enable_irq(struct komeda_dev *mdev) +{ + struct d71_dev *d71 = mdev->chip_data; + struct d71_pipeline *pipe; + u32 i; + + malidp_write32_mask(d71->gcu_addr, BLK_IRQ_MASK, + ENABLED_GCU_IRQS, ENABLED_GCU_IRQS); + for (i = 0; i < d71->num_pipelines; i++) { + pipe = d71->pipes[i]; + malidp_write32_mask(pipe->cu_addr, BLK_IRQ_MASK, + ENABLED_CU_IRQS, ENABLED_CU_IRQS); + malidp_write32_mask(pipe->lpu_addr, BLK_IRQ_MASK, + ENABLED_LPU_IRQS, ENABLED_LPU_IRQS); + malidp_write32_mask(pipe->dou_addr, BLK_IRQ_MASK, + ENABLED_DOU_IRQS, ENABLED_DOU_IRQS); + } + return 0; +} + +static int d71_disable_irq(struct komeda_dev *mdev) +{ + struct d71_dev *d71 = mdev->chip_data; + struct d71_pipeline *pipe; + u32 i; + + malidp_write32_mask(d71->gcu_addr, BLK_IRQ_MASK, ENABLED_GCU_IRQS, 0); + for (i = 0; i < d71->num_pipelines; i++) { + pipe = d71->pipes[i]; + malidp_write32_mask(pipe->cu_addr, BLK_IRQ_MASK, + ENABLED_CU_IRQS, 0); + malidp_write32_mask(pipe->lpu_addr, BLK_IRQ_MASK, + ENABLED_LPU_IRQS, 0); + malidp_write32_mask(pipe->dou_addr, BLK_IRQ_MASK, + ENABLED_DOU_IRQS, 0); + } + return 0; +} + +static void d71_on_off_vblank(struct komeda_dev *mdev, int master_pipe, bool on) +{ + struct d71_dev *d71 = mdev->chip_data; + struct d71_pipeline *pipe = d71->pipes[master_pipe]; + + malidp_write32_mask(pipe->dou_addr, BLK_IRQ_MASK, + DOU_IRQ_PL0, on ? DOU_IRQ_PL0 : 0); +} + +static int to_d71_opmode(int core_mode) +{ + switch (core_mode) { + case KOMEDA_MODE_DISP0: + return DO0_ACTIVE_MODE; + case KOMEDA_MODE_DISP1: + return DO1_ACTIVE_MODE; + case KOMEDA_MODE_DUAL_DISP: + return DO01_ACTIVE_MODE; + case KOMEDA_MODE_INACTIVE: + return INACTIVE_MODE; + default: + WARN(1, "Unknown operation mode"); + return INACTIVE_MODE; + } +} + +static int d71_change_opmode(struct komeda_dev *mdev, int new_mode) +{ + struct d71_dev *d71 = mdev->chip_data; + u32 opmode = to_d71_opmode(new_mode); + int ret; + + malidp_write32_mask(d71->gcu_addr, BLK_CONTROL, 0x7, opmode); + + ret = dp_wait_cond(((malidp_read32(d71->gcu_addr, BLK_CONTROL) & 0x7) == opmode), + 100, 1000, 10000); + + return ret; +} + +static void d71_flush(struct komeda_dev *mdev, + int master_pipe, u32 active_pipes) +{ + struct d71_dev *d71 = mdev->chip_data; + u32 reg_offset = (master_pipe == 0) ? + GCU_CONFIG_VALID0 : GCU_CONFIG_VALID1; + + malidp_write32(d71->gcu_addr, reg_offset, GCU_CONFIG_CVAL); +} + +static int d71_reset(struct d71_dev *d71) +{ + u32 __iomem *gcu = d71->gcu_addr; + int ret; + + malidp_write32(gcu, BLK_CONTROL, GCU_CONTROL_SRST); + + ret = dp_wait_cond(!(malidp_read32(gcu, BLK_CONTROL) & GCU_CONTROL_SRST), + 100, 1000, 10000); + + return ret; +} + +void d71_read_block_header(u32 __iomem *reg, struct block_header *blk) +{ + int i; + + blk->block_info = malidp_read32(reg, BLK_BLOCK_INFO); + if (BLOCK_INFO_BLK_TYPE(blk->block_info) == D71_BLK_TYPE_RESERVED) + return; + + blk->pipeline_info = malidp_read32(reg, BLK_PIPELINE_INFO); + + /* get valid input and output ids */ + for (i = 0; i < PIPELINE_INFO_N_VALID_INPUTS(blk->pipeline_info); i++) + blk->input_ids[i] = malidp_read32(reg + i, BLK_VALID_INPUT_ID0); + for (i = 0; i < PIPELINE_INFO_N_OUTPUTS(blk->pipeline_info); i++) + blk->output_ids[i] = malidp_read32(reg + i, BLK_OUTPUT_ID0); +} + +static void d71_cleanup(struct komeda_dev *mdev) +{ + struct d71_dev *d71 = mdev->chip_data; + + if (!d71) + return; + + devm_kfree(mdev->dev, d71); + mdev->chip_data = NULL; +} + +static int d71_enum_resources(struct komeda_dev *mdev) +{ + struct d71_dev *d71; + struct komeda_pipeline *pipe; + struct block_header blk; + u32 __iomem *blk_base; + u32 i, value, offset; + int err; + + d71 = devm_kzalloc(mdev->dev, sizeof(*d71), GFP_KERNEL); + if (!d71) + return -ENOMEM; + + mdev->chip_data = d71; + d71->mdev = mdev; + d71->gcu_addr = mdev->reg_base; + d71->periph_addr = mdev->reg_base + (D71_BLOCK_OFFSET_PERIPH >> 2); + + err = d71_reset(d71); + if (err) { + DRM_ERROR("Fail to reset d71 device.\n"); + goto err_cleanup; + } + + /* probe GCU */ + value = malidp_read32(d71->gcu_addr, GLB_CORE_INFO); + d71->num_blocks = value & 0xFF; + d71->num_pipelines = (value >> 8) & 0x7; + + if (d71->num_pipelines > D71_MAX_PIPELINE) { + DRM_ERROR("d71 supports %d pipelines, but got: %d.\n", + D71_MAX_PIPELINE, d71->num_pipelines); + err = -EINVAL; + goto err_cleanup; + } + + /* Only the legacy HW has the periph block, the newer merges the periph + * into GCU + */ + value = malidp_read32(d71->periph_addr, BLK_BLOCK_INFO); + if (BLOCK_INFO_BLK_TYPE(value) != D71_BLK_TYPE_PERIPH) + d71->periph_addr = NULL; + + if (d71->periph_addr) { + /* probe PERIPHERAL in legacy HW */ + value = malidp_read32(d71->periph_addr, PERIPH_CONFIGURATION_ID); + + d71->max_line_size = value & PERIPH_MAX_LINE_SIZE ? 4096 : 2048; + d71->max_vsize = 4096; + d71->num_rich_layers = value & PERIPH_NUM_RICH_LAYERS ? 2 : 1; + d71->supports_dual_link = !!(value & PERIPH_SPLIT_EN); + d71->integrates_tbu = !!(value & PERIPH_TBU_EN); + } else { + value = malidp_read32(d71->gcu_addr, GCU_CONFIGURATION_ID0); + d71->max_line_size = GCU_MAX_LINE_SIZE(value); + d71->max_vsize = GCU_MAX_NUM_LINES(value); + + value = malidp_read32(d71->gcu_addr, GCU_CONFIGURATION_ID1); + d71->num_rich_layers = GCU_NUM_RICH_LAYERS(value); + d71->supports_dual_link = GCU_DISPLAY_SPLIT_EN(value); + d71->integrates_tbu = GCU_DISPLAY_TBU_EN(value); + } + + for (i = 0; i < d71->num_pipelines; i++) { + pipe = komeda_pipeline_add(mdev, sizeof(struct d71_pipeline), + &d71_pipeline_funcs); + if (IS_ERR(pipe)) { + err = PTR_ERR(pipe); + goto err_cleanup; + } + + /* D71 HW doesn't update shadow registers when display output + * is turning off, so when we disable all pipeline components + * together with display output disable by one flush or one + * operation, the disable operation updated registers will not + * be flush to or valid in HW, which may leads problem. + * To workaround this problem, introduce a two phase disable. + * Phase1: Disabling components with display is on to make sure + * the disable can be flushed to HW. + * Phase2: Only turn-off display output. + */ + value = KOMEDA_PIPELINE_IMPROCS | + BIT(KOMEDA_COMPONENT_TIMING_CTRLR); + + pipe->standalone_disabled_comps = value; + + d71->pipes[i] = to_d71_pipeline(pipe); + } + + /* loop the register blks and probe. + * NOTE: d71->num_blocks includes reserved blocks. + * d71->num_blocks = GCU + valid blocks + reserved blocks + */ + i = 1; /* exclude GCU */ + offset = D71_BLOCK_SIZE; /* skip GCU */ + while (i < d71->num_blocks) { + blk_base = mdev->reg_base + (offset >> 2); + + d71_read_block_header(blk_base, &blk); + if (BLOCK_INFO_BLK_TYPE(blk.block_info) != D71_BLK_TYPE_RESERVED) { + err = d71_probe_block(d71, &blk, blk_base); + if (err) + goto err_cleanup; + } + + i++; + offset += D71_BLOCK_SIZE; + } + + DRM_DEBUG("total %d (out of %d) blocks are found.\n", + i, d71->num_blocks); + + return 0; + +err_cleanup: + d71_cleanup(mdev); + return err; +} + +#define __HW_ID(__group, __format) \ + ((((__group) & 0x7) << 3) | ((__format) & 0x7)) + +#define RICH KOMEDA_FMT_RICH_LAYER +#define SIMPLE KOMEDA_FMT_SIMPLE_LAYER +#define RICH_SIMPLE (KOMEDA_FMT_RICH_LAYER | KOMEDA_FMT_SIMPLE_LAYER) +#define RICH_WB (KOMEDA_FMT_RICH_LAYER | KOMEDA_FMT_WB_LAYER) +#define RICH_SIMPLE_WB (RICH_SIMPLE | KOMEDA_FMT_WB_LAYER) + +#define Rot_0 DRM_MODE_ROTATE_0 +#define Flip_H_V (DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y | Rot_0) +#define Rot_ALL_H_V (DRM_MODE_ROTATE_MASK | Flip_H_V) + +#define LYT_NM BIT(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16) +#define LYT_WB BIT(AFBC_FORMAT_MOD_BLOCK_SIZE_32x8) +#define LYT_NM_WB (LYT_NM | LYT_WB) + +#define AFB_TH AFBC(_TILED | _SPARSE) +#define AFB_TH_SC_YTR AFBC(_TILED | _SC | _SPARSE | _YTR) +#define AFB_TH_SC_YTR_BS AFBC(_TILED | _SC | _SPARSE | _YTR | _SPLIT) + +static struct komeda_format_caps d71_format_caps_table[] = { + /* HW_ID | fourcc | layer_types | rots | afbc_layouts | afbc_features */ + /* ABGR_2101010*/ + {__HW_ID(0, 0), DRM_FORMAT_ARGB2101010, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(0, 1), DRM_FORMAT_ABGR2101010, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(0, 1), DRM_FORMAT_ABGR2101010, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR_BS}, /* afbc */ + {__HW_ID(0, 2), DRM_FORMAT_RGBA1010102, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(0, 3), DRM_FORMAT_BGRA1010102, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + /* ABGR_8888*/ + {__HW_ID(1, 0), DRM_FORMAT_ARGB8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(1, 1), DRM_FORMAT_ABGR8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(1, 1), DRM_FORMAT_ABGR8888, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR_BS}, /* afbc */ + {__HW_ID(1, 2), DRM_FORMAT_RGBA8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(1, 3), DRM_FORMAT_BGRA8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + /* XBGB_8888 */ + {__HW_ID(2, 0), DRM_FORMAT_XRGB8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(2, 1), DRM_FORMAT_XBGR8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(2, 2), DRM_FORMAT_RGBX8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + {__HW_ID(2, 3), DRM_FORMAT_BGRX8888, RICH_SIMPLE_WB, Flip_H_V, 0, 0}, + /* BGR_888 */ /* none-afbc RGB888 doesn't support rotation and flip */ + {__HW_ID(3, 0), DRM_FORMAT_RGB888, RICH_SIMPLE_WB, Rot_0, 0, 0}, + {__HW_ID(3, 1), DRM_FORMAT_BGR888, RICH_SIMPLE_WB, Rot_0, 0, 0}, + {__HW_ID(3, 1), DRM_FORMAT_BGR888, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR_BS}, /* afbc */ + /* BGR 16bpp */ + {__HW_ID(4, 0), DRM_FORMAT_RGBA5551, RICH_SIMPLE, Flip_H_V, 0, 0}, + {__HW_ID(4, 1), DRM_FORMAT_ABGR1555, RICH_SIMPLE, Flip_H_V, 0, 0}, + {__HW_ID(4, 1), DRM_FORMAT_ABGR1555, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR}, /* afbc */ + {__HW_ID(4, 2), DRM_FORMAT_RGB565, RICH_SIMPLE, Flip_H_V, 0, 0}, + {__HW_ID(4, 3), DRM_FORMAT_BGR565, RICH_SIMPLE, Flip_H_V, 0, 0}, + {__HW_ID(4, 3), DRM_FORMAT_BGR565, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR}, /* afbc */ + {__HW_ID(4, 4), DRM_FORMAT_R8, SIMPLE, Rot_0, 0, 0}, + /* YUV 444/422/420 8bit */ + {__HW_ID(5, 1), DRM_FORMAT_YUYV, RICH, Rot_ALL_H_V, LYT_NM, AFB_TH}, /* afbc */ + {__HW_ID(5, 2), DRM_FORMAT_YUYV, RICH, Flip_H_V, 0, 0}, + {__HW_ID(5, 3), DRM_FORMAT_UYVY, RICH, Flip_H_V, 0, 0}, + {__HW_ID(5, 6), DRM_FORMAT_NV12, RICH_WB, Flip_H_V, 0, 0}, + {__HW_ID(5, 6), DRM_FORMAT_YUV420_8BIT, RICH, Rot_ALL_H_V, LYT_NM, AFB_TH}, /* afbc */ + {__HW_ID(5, 7), DRM_FORMAT_YUV420, RICH, Flip_H_V, 0, 0}, + /* YUV 10bit*/ + {__HW_ID(6, 6), DRM_FORMAT_X0L2, RICH, Flip_H_V, 0, 0}, + {__HW_ID(6, 7), DRM_FORMAT_P010, RICH, Flip_H_V, 0, 0}, + {__HW_ID(6, 7), DRM_FORMAT_YUV420_10BIT, RICH, Rot_ALL_H_V, LYT_NM, AFB_TH}, +}; + +static bool d71_format_mod_supported(const struct komeda_format_caps *caps, + u32 layer_type, u64 modifier, u32 rot) +{ + uint64_t layout = modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK; + + if ((layout == AFBC_FORMAT_MOD_BLOCK_SIZE_32x8) && + drm_rotation_90_or_270(rot)) { + DRM_DEBUG_ATOMIC("D71 doesn't support ROT90 for WB-AFBC.\n"); + return false; + } + + return true; +} + +static void d71_init_fmt_tbl(struct komeda_dev *mdev) +{ + struct komeda_format_caps_table *table = &mdev->fmt_tbl; + + table->format_caps = d71_format_caps_table; + table->format_mod_supported = d71_format_mod_supported; + table->n_formats = ARRAY_SIZE(d71_format_caps_table); +} + +static int d71_connect_iommu(struct komeda_dev *mdev) +{ + struct d71_dev *d71 = mdev->chip_data; + u32 __iomem *reg = d71->gcu_addr; + u32 check_bits = (d71->num_pipelines == 2) ? + GCU_STATUS_TCS0 | GCU_STATUS_TCS1 : GCU_STATUS_TCS0; + int i, ret; + + if (!d71->integrates_tbu) + return -1; + + malidp_write32_mask(reg, BLK_CONTROL, 0x7, TBU_CONNECT_MODE); + + ret = dp_wait_cond(has_bits(check_bits, malidp_read32(reg, BLK_STATUS)), + 100, 1000, 1000); + if (ret < 0) { + DRM_ERROR("timed out connecting to TCU!\n"); + malidp_write32_mask(reg, BLK_CONTROL, 0x7, INACTIVE_MODE); + return ret; + } + + for (i = 0; i < d71->num_pipelines; i++) + malidp_write32_mask(d71->pipes[i]->lpu_addr, LPU_TBU_CONTROL, + LPU_TBU_CTRL_TLBPEN, LPU_TBU_CTRL_TLBPEN); + return 0; +} + +static int d71_disconnect_iommu(struct komeda_dev *mdev) +{ + struct d71_dev *d71 = mdev->chip_data; + u32 __iomem *reg = d71->gcu_addr; + u32 check_bits = (d71->num_pipelines == 2) ? + GCU_STATUS_TCS0 | GCU_STATUS_TCS1 : GCU_STATUS_TCS0; + int ret; + + malidp_write32_mask(reg, BLK_CONTROL, 0x7, TBU_DISCONNECT_MODE); + + ret = dp_wait_cond(((malidp_read32(reg, BLK_STATUS) & check_bits) == 0), + 100, 1000, 1000); + if (ret < 0) { + DRM_ERROR("timed out disconnecting from TCU!\n"); + malidp_write32_mask(reg, BLK_CONTROL, 0x7, INACTIVE_MODE); + } + + return ret; +} + +static const struct komeda_dev_funcs d71_chip_funcs = { + .init_format_table = d71_init_fmt_tbl, + .enum_resources = d71_enum_resources, + .cleanup = d71_cleanup, + .irq_handler = d71_irq_handler, + .enable_irq = d71_enable_irq, + .disable_irq = d71_disable_irq, + .on_off_vblank = d71_on_off_vblank, + .change_opmode = d71_change_opmode, + .flush = d71_flush, + .connect_iommu = d71_connect_iommu, + .disconnect_iommu = d71_disconnect_iommu, + .dump_register = d71_dump, +}; + +const struct komeda_dev_funcs * +d71_identify(u32 __iomem *reg_base, struct komeda_chip_info *chip) +{ + const struct komeda_dev_funcs *funcs; + u32 product_id; + + chip->core_id = malidp_read32(reg_base, GLB_CORE_ID); + + product_id = MALIDP_CORE_ID_PRODUCT_ID(chip->core_id); + + switch (product_id) { + case MALIDP_D71_PRODUCT_ID: + case MALIDP_D32_PRODUCT_ID: + case LINLONDP_D6_PRODUCT_ID: + funcs = &d71_chip_funcs; + break; + default: + DRM_ERROR("Unsupported product: 0x%x\n", product_id); + return NULL; + } + + chip->arch_id = malidp_read32(reg_base, GLB_ARCH_ID); + chip->core_info = malidp_read32(reg_base, GLB_CORE_INFO); + chip->bus_width = D71_BUS_WIDTH_16_BYTES; + + return funcs; +} diff --git a/drivers/gpu/drm/arm/display/komeda/d71/d71_dev.h b/drivers/gpu/drm/arm/display/komeda/d71/d71_dev.h new file mode 100644 index 000000000..c7357c2b9 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/d71/d71_dev.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#ifndef _D71_DEV_H_ +#define _D71_DEV_H_ + +#include "komeda_dev.h" +#include "komeda_pipeline.h" +#include "d71_regs.h" + +struct d71_pipeline { + struct komeda_pipeline base; + + /* d71 private pipeline blocks */ + u32 __iomem *lpu_addr; + u32 __iomem *cu_addr; + u32 __iomem *dou_addr; + u32 __iomem *dou_ft_coeff_addr; /* forward transform coeffs table */ +}; + +struct d71_dev { + struct komeda_dev *mdev; + + int num_blocks; + int num_pipelines; + int num_rich_layers; + u32 max_line_size; + u32 max_vsize; + u32 supports_dual_link : 1; + u32 integrates_tbu : 1; + + /* global register blocks */ + u32 __iomem *gcu_addr; + /* scaling coeffs table */ + u32 __iomem *glb_scl_coeff_addr[D71_MAX_GLB_SCL_COEFF]; + u32 __iomem *periph_addr; + + struct d71_pipeline *pipes[D71_MAX_PIPELINE]; +}; + +#define to_d71_pipeline(x) container_of(x, struct d71_pipeline, base) + +extern const struct komeda_pipeline_funcs d71_pipeline_funcs; + +int d71_probe_block(struct d71_dev *d71, + struct block_header *blk, u32 __iomem *reg); +void d71_read_block_header(u32 __iomem *reg, struct block_header *blk); + +void d71_dump(struct komeda_dev *mdev, struct seq_file *sf); + +#endif /* !_D71_DEV_H_ */ diff --git a/drivers/gpu/drm/arm/display/komeda/d71/d71_regs.h b/drivers/gpu/drm/arm/display/komeda/d71/d71_regs.h new file mode 100644 index 000000000..e80172a0b --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/d71/d71_regs.h @@ -0,0 +1,541 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#ifndef _D71_REG_H_ +#define _D71_REG_H_ + +/* Common block registers offset */ +#define BLK_BLOCK_INFO 0x000 +#define BLK_PIPELINE_INFO 0x004 +#define BLK_MAX_LINE_SIZE 0x008 +#define BLK_VALID_INPUT_ID0 0x020 +#define BLK_OUTPUT_ID0 0x060 +#define BLK_INPUT_ID0 0x080 +#define BLK_IRQ_RAW_STATUS 0x0A0 +#define BLK_IRQ_CLEAR 0x0A4 +#define BLK_IRQ_MASK 0x0A8 +#define BLK_IRQ_STATUS 0x0AC +#define BLK_STATUS 0x0B0 +#define BLK_INFO 0x0C0 +#define BLK_CONTROL 0x0D0 +#define BLK_SIZE 0x0D4 +#define BLK_IN_SIZE 0x0E0 + +#define BLK_P0_PTR_LOW 0x100 +#define BLK_P0_PTR_HIGH 0x104 +#define BLK_P0_STRIDE 0x108 +#define BLK_P1_PTR_LOW 0x110 +#define BLK_P1_PTR_HIGH 0x114 +#define BLK_P1_STRIDE 0x118 +#define BLK_P2_PTR_LOW 0x120 +#define BLK_P2_PTR_HIGH 0x124 + +#define BLOCK_INFO_N_SUBBLKS(x) ((x) & 0x000F) +#define BLOCK_INFO_BLK_ID(x) (((x) & 0x00F0) >> 4) +#define BLOCK_INFO_BLK_TYPE(x) (((x) & 0xFF00) >> 8) +#define BLOCK_INFO_INPUT_ID(x) ((x) & 0xFFF0) +#define BLOCK_INFO_TYPE_ID(x) (((x) & 0x0FF0) >> 4) + +#define PIPELINE_INFO_N_OUTPUTS(x) ((x) & 0x000F) +#define PIPELINE_INFO_N_VALID_INPUTS(x) (((x) & 0x0F00) >> 8) + +/* Common block control register bits */ +#define BLK_CTRL_EN BIT(0) +/* Common size macro */ +#define HV_SIZE(h, v) (((h) & 0x1FFF) + (((v) & 0x1FFF) << 16)) +#define HV_OFFSET(h, v) (((h) & 0xFFF) + (((v) & 0xFFF) << 16)) +#define HV_CROP(h, v) (((h) & 0xFFF) + (((v) & 0xFFF) << 16)) + +/* AD_CONTROL register */ +#define AD_CONTROL 0x160 + +/* AD_CONTROL register bits */ +#define AD_AEN BIT(0) +#define AD_YT BIT(1) +#define AD_BS BIT(2) +#define AD_WB BIT(3) +#define AD_TH BIT(4) + +/* Global Control Unit */ +#define GLB_ARCH_ID 0x000 +#define GLB_CORE_ID 0x004 +#define GLB_CORE_INFO 0x008 +#define GLB_IRQ_STATUS 0x010 + +#define GCU_CONFIG_VALID0 0x0D4 +#define GCU_CONFIG_VALID1 0x0D8 + +/* GCU_CONTROL_BITS */ +#define GCU_CONTROL_MODE(x) ((x) & 0x7) +#define GCU_CONTROL_SRST BIT(16) + +/* GCU_CONFIGURATION registers */ +#define GCU_CONFIGURATION_ID0 0x100 +#define GCU_CONFIGURATION_ID1 0x104 + +/* GCU configuration */ +#define GCU_MAX_LINE_SIZE(x) ((x) & 0xFFFF) +#define GCU_MAX_NUM_LINES(x) ((x) >> 16) +#define GCU_NUM_RICH_LAYERS(x) ((x) & 0x7) +#define GCU_NUM_PIPELINES(x) (((x) >> 3) & 0x7) +#define GCU_NUM_SCALERS(x) (((x) >> 6) & 0x7) +#define GCU_DISPLAY_SPLIT_EN(x) (((x) >> 16) & 0x1) +#define GCU_DISPLAY_TBU_EN(x) (((x) >> 17) & 0x1) + +/* GCU opmode */ +#define INACTIVE_MODE 0 +#define TBU_CONNECT_MODE 1 +#define TBU_DISCONNECT_MODE 2 +#define DO0_ACTIVE_MODE 3 +#define DO1_ACTIVE_MODE 4 +#define DO01_ACTIVE_MODE 5 + +/* GLB_IRQ_STATUS bits */ +#define GLB_IRQ_STATUS_GCU BIT(0) +#define GLB_IRQ_STATUS_LPU0 BIT(8) +#define GLB_IRQ_STATUS_LPU1 BIT(9) +#define GLB_IRQ_STATUS_ATU0 BIT(10) +#define GLB_IRQ_STATUS_ATU1 BIT(11) +#define GLB_IRQ_STATUS_ATU2 BIT(12) +#define GLB_IRQ_STATUS_ATU3 BIT(13) +#define GLB_IRQ_STATUS_CU0 BIT(16) +#define GLB_IRQ_STATUS_CU1 BIT(17) +#define GLB_IRQ_STATUS_DOU0 BIT(24) +#define GLB_IRQ_STATUS_DOU1 BIT(25) + +#define GLB_IRQ_STATUS_PIPE0 (GLB_IRQ_STATUS_LPU0 |\ + GLB_IRQ_STATUS_ATU0 |\ + GLB_IRQ_STATUS_ATU1 |\ + GLB_IRQ_STATUS_CU0 |\ + GLB_IRQ_STATUS_DOU0) + +#define GLB_IRQ_STATUS_PIPE1 (GLB_IRQ_STATUS_LPU1 |\ + GLB_IRQ_STATUS_ATU2 |\ + GLB_IRQ_STATUS_ATU3 |\ + GLB_IRQ_STATUS_CU1 |\ + GLB_IRQ_STATUS_DOU1) + +#define GLB_IRQ_STATUS_ATU (GLB_IRQ_STATUS_ATU0 |\ + GLB_IRQ_STATUS_ATU1 |\ + GLB_IRQ_STATUS_ATU2 |\ + GLB_IRQ_STATUS_ATU3) + +/* GCU_IRQ_BITS */ +#define GCU_IRQ_CVAL0 BIT(0) +#define GCU_IRQ_CVAL1 BIT(1) +#define GCU_IRQ_MODE BIT(4) +#define GCU_IRQ_ERR BIT(11) + +/* GCU_STATUS_BITS */ +#define GCU_STATUS_MODE(x) ((x) & 0x7) +#define GCU_STATUS_MERR BIT(4) +#define GCU_STATUS_TCS0 BIT(8) +#define GCU_STATUS_TCS1 BIT(9) +#define GCU_STATUS_ACTIVE BIT(31) + +/* GCU_CONFIG_VALIDx BITS */ +#define GCU_CONFIG_CVAL BIT(0) + +/* PERIPHERAL registers */ +#define PERIPH_MAX_LINE_SIZE BIT(0) +#define PERIPH_NUM_RICH_LAYERS BIT(4) +#define PERIPH_SPLIT_EN BIT(8) +#define PERIPH_TBU_EN BIT(12) +#define PERIPH_AFBC_DMA_EN BIT(16) +#define PERIPH_CONFIGURATION_ID 0x1D4 + +/* LPU register */ +#define LPU_TBU_STATUS 0x0B4 +#define LPU_RAXI_CONTROL 0x0D0 +#define LPU_WAXI_CONTROL 0x0D4 +#define LPU_TBU_CONTROL 0x0D8 + +/* LPU_xAXI_CONTROL_BITS */ +#define TO_RAXI_AOUTSTDCAPB(x) (x) +#define TO_RAXI_BOUTSTDCAPB(x) ((x) << 8) +#define TO_RAXI_BEN(x) ((x) << 15) +#define TO_xAXI_BURSTLEN(x) ((x) << 16) +#define TO_xAXI_AxQOS(x) ((x) << 24) +#define TO_xAXI_ORD(x) ((x) << 31) +#define TO_WAXI_OUTSTDCAPB(x) (x) + +#define RAXI_AOUTSTDCAPB_MASK 0x7F +#define RAXI_BOUTSTDCAPB_MASK 0x7F00 +#define RAXI_BEN_MASK BIT(15) +#define xAXI_BURSTLEN_MASK 0x3F0000 +#define xAXI_AxQOS_MASK 0xF000000 +#define xAXI_ORD_MASK BIT(31) +#define WAXI_OUTSTDCAPB_MASK 0x3F + +/* LPU_TBU_CONTROL BITS */ +#define TO_TBU_DOUTSTDCAPB(x) (x) +#define TBU_DOUTSTDCAPB_MASK 0x3F + +/* LPU_IRQ_BITS */ +#define LPU_IRQ_OVR BIT(9) +#define LPU_IRQ_IBSY BIT(10) +#define LPU_IRQ_ERR BIT(11) +#define LPU_IRQ_EOW BIT(12) +#define LPU_IRQ_PL0 BIT(13) + +/* LPU_STATUS_BITS */ +#define LPU_STATUS_AXIED(x) ((x) & 0xF) +#define LPU_STATUS_AXIE BIT(4) +#define LPU_STATUS_AXIRP BIT(5) +#define LPU_STATUS_AXIWP BIT(6) +#define LPU_STATUS_FEMPTY BIT(11) +#define LPU_STATUS_FFULL BIT(14) +#define LPU_STATUS_ACE0 BIT(16) +#define LPU_STATUS_ACE1 BIT(17) +#define LPU_STATUS_ACE2 BIT(18) +#define LPU_STATUS_ACE3 BIT(19) +#define LPU_STATUS_ACTIVE BIT(31) + +#define AXIEID_MASK 0xF +#define AXIE_MASK LPU_STATUS_AXIE +#define AXIRP_MASK LPU_STATUS_AXIRP +#define AXIWP_MASK LPU_STATUS_AXIWP + +#define FROM_AXIEID(reg) ((reg) & AXIEID_MASK) +#define TO_AXIE(x) ((x) << 4) +#define FROM_AXIRP(reg) (((reg) & AXIRP_MASK) >> 5) +#define FROM_AXIWP(reg) (((reg) & AXIWP_MASK) >> 6) + +/* LPU_TBU_STATUS_BITS */ +#define LPU_TBU_STATUS_TCF BIT(1) +#define LPU_TBU_STATUS_TTNG BIT(2) +#define LPU_TBU_STATUS_TITR BIT(8) +#define LPU_TBU_STATUS_TEMR BIT(16) +#define LPU_TBU_STATUS_TTF BIT(31) + +/* LPU_TBU_CONTROL BITS */ +#define LPU_TBU_CTRL_TLBPEN BIT(16) + +/* CROSSBAR CONTROL BITS */ +#define CBU_INPUT_CTRL_EN BIT(0) +#define CBU_NUM_INPUT_IDS 5 +#define CBU_NUM_OUTPUT_IDS 5 + +/* CU register */ +#define CU_BG_COLOR 0x0DC +#define CU_INPUT0_SIZE 0x0E0 +#define CU_INPUT0_OFFSET 0x0E4 +#define CU_INPUT0_CONTROL 0x0E8 +#define CU_INPUT1_SIZE 0x0F0 +#define CU_INPUT1_OFFSET 0x0F4 +#define CU_INPUT1_CONTROL 0x0F8 +#define CU_INPUT2_SIZE 0x100 +#define CU_INPUT2_OFFSET 0x104 +#define CU_INPUT2_CONTROL 0x108 +#define CU_INPUT3_SIZE 0x110 +#define CU_INPUT3_OFFSET 0x114 +#define CU_INPUT3_CONTROL 0x118 +#define CU_INPUT4_SIZE 0x120 +#define CU_INPUT4_OFFSET 0x124 +#define CU_INPUT4_CONTROL 0x128 + +#define CU_PER_INPUT_REGS 4 + +#define CU_NUM_INPUT_IDS 5 +#define CU_NUM_OUTPUT_IDS 1 + +/* CU control register bits */ +#define CU_CTRL_COPROC BIT(0) + +/* CU_IRQ_BITS */ +#define CU_IRQ_OVR BIT(9) +#define CU_IRQ_ERR BIT(11) + +/* CU_STATUS_BITS */ +#define CU_STATUS_CPE BIT(0) +#define CU_STATUS_ZME BIT(1) +#define CU_STATUS_CFGE BIT(2) +#define CU_STATUS_ACTIVE BIT(31) + +/* CU input control register bits */ +#define CU_INPUT_CTRL_EN BIT(0) +#define CU_INPUT_CTRL_PAD BIT(1) +#define CU_INPUT_CTRL_PMUL BIT(2) +#define CU_INPUT_CTRL_ALPHA(x) (((x) & 0xFF) << 8) + +/* DOU register */ + +/* DOU_IRQ_BITS */ +#define DOU_IRQ_UND BIT(8) +#define DOU_IRQ_ERR BIT(11) +#define DOU_IRQ_PL0 BIT(13) +#define DOU_IRQ_PL1 BIT(14) + +/* DOU_STATUS_BITS */ +#define DOU_STATUS_DRIFTTO BIT(0) +#define DOU_STATUS_FRAMETO BIT(1) +#define DOU_STATUS_TETO BIT(2) +#define DOU_STATUS_CSCE BIT(8) +#define DOU_STATUS_ACTIVE BIT(31) + +/* Layer registers */ +#define LAYER_INFO 0x0C0 +#define LAYER_R_CONTROL 0x0D4 +#define LAYER_FMT 0x0D8 +#define LAYER_LT_COEFFTAB 0x0DC +#define LAYER_PALPHA 0x0E4 + +#define LAYER_YUV_RGB_COEFF0 0x130 + +#define LAYER_AD_H_CROP 0x164 +#define LAYER_AD_V_CROP 0x168 + +#define LAYER_RGB_RGB_COEFF0 0x170 + +/* L_CONTROL_BITS */ +#define L_EN BIT(0) +#define L_IT BIT(4) +#define L_R2R BIT(5) +#define L_FT BIT(6) +#define L_ROT(x) (((x) & 3) << 8) +#define L_HFLIP BIT(10) +#define L_VFLIP BIT(11) +#define L_TBU_EN BIT(16) +#define L_A_RCACHE(x) (((x) & 0xF) << 28) +#define L_ROT_R0 0 +#define L_ROT_R90 1 +#define L_ROT_R180 2 +#define L_ROT_R270 3 + +/* LAYER_R_CONTROL BITS */ +#define LR_CHI422_BILINEAR 0 +#define LR_CHI422_REPLICATION 1 +#define LR_CHI420_JPEG (0 << 2) +#define LR_CHI420_MPEG (1 << 2) + +#define L_ITSEL(x) ((x) & 0xFFF) +#define L_FTSEL(x) (((x) & 0xFFF) << 16) + +#define LAYER_PER_PLANE_REGS 4 + +/* Layer_WR registers */ +#define LAYER_WR_PROG_LINE 0x0D4 +#define LAYER_WR_FORMAT 0x0D8 + +/* Layer_WR control bits */ +#define LW_OFM BIT(4) +#define LW_LALPHA(x) (((x) & 0xFF) << 8) +#define LW_A_WCACHE(x) (((x) & 0xF) << 28) +#define LW_TBU_EN BIT(16) + +#define AxCACHE_MASK 0xF0000000 + +/* Layer AXI R/W cache setting */ +#define AxCACHE_B BIT(0) /* Bufferable */ +#define AxCACHE_M BIT(1) /* Modifiable */ +#define AxCACHE_RA BIT(2) /* Read-Allocate */ +#define AxCACHE_WA BIT(3) /* Write-Allocate */ + +/* Layer info bits */ +#define L_INFO_RF BIT(0) +#define L_INFO_CM BIT(1) +#define L_INFO_ABUF_SIZE(x) (((x) >> 4) & 0x7) +#define L_INFO_YUV_MAX_LINESZ(x) (((x) >> 16) & 0xFFFF) + +/* Scaler registers */ +#define SC_COEFFTAB 0x0DC +#define SC_OUT_SIZE 0x0E4 +#define SC_H_CROP 0x0E8 +#define SC_V_CROP 0x0EC +#define SC_H_INIT_PH 0x0F0 +#define SC_H_DELTA_PH 0x0F4 +#define SC_V_INIT_PH 0x0F8 +#define SC_V_DELTA_PH 0x0FC +#define SC_ENH_LIMITS 0x130 +#define SC_ENH_COEFF0 0x134 + +#define SC_MAX_ENH_COEFF 9 + +/* SC_CTRL_BITS */ +#define SC_CTRL_SCL BIT(0) +#define SC_CTRL_LS BIT(1) +#define SC_CTRL_AP BIT(4) +#define SC_CTRL_IENH BIT(8) +#define SC_CTRL_RGBSM BIT(16) +#define SC_CTRL_ASM BIT(17) + +#define SC_VTSEL(vtal) ((vtal) << 16) + +#define SC_NUM_INPUTS_IDS 1 +#define SC_NUM_OUTPUTS_IDS 1 + +#define MG_NUM_INPUTS_IDS 2 +#define MG_NUM_OUTPUTS_IDS 1 + +/* Merger registers */ +#define MG_INPUT_ID0 BLK_INPUT_ID0 +#define MG_INPUT_ID1 (MG_INPUT_ID0 + 4) +#define MG_SIZE BLK_SIZE + +/* Splitter registers */ +#define SP_OVERLAP_SIZE 0xD8 + +/* Backend registers */ +#define BS_INFO 0x0C0 +#define BS_PROG_LINE 0x0D4 +#define BS_PREFETCH_LINE 0x0D8 +#define BS_BG_COLOR 0x0DC +#define BS_ACTIVESIZE 0x0E0 +#define BS_HINTERVALS 0x0E4 +#define BS_VINTERVALS 0x0E8 +#define BS_SYNC 0x0EC +#define BS_DRIFT_TO 0x100 +#define BS_FRAME_TO 0x104 +#define BS_TE_TO 0x108 +#define BS_T0_INTERVAL 0x110 +#define BS_T1_INTERVAL 0x114 +#define BS_T2_INTERVAL 0x118 +#define BS_CRC0_LOW 0x120 +#define BS_CRC0_HIGH 0x124 +#define BS_CRC1_LOW 0x128 +#define BS_CRC1_HIGH 0x12C +#define BS_USER 0x130 + +/* BS control register bits */ +#define BS_CTRL_EN BIT(0) +#define BS_CTRL_VM BIT(1) +#define BS_CTRL_BM BIT(2) +#define BS_CTRL_HMASK BIT(4) +#define BS_CTRL_VD BIT(5) +#define BS_CTRL_TE BIT(8) +#define BS_CTRL_TS BIT(9) +#define BS_CTRL_TM BIT(12) +#define BS_CTRL_DL BIT(16) +#define BS_CTRL_SBS BIT(17) +#define BS_CTRL_CRC BIT(18) +#define BS_CTRL_PM BIT(20) + +/* BS active size/intervals */ +#define BS_H_INTVALS(hfp, hbp) (((hfp) & 0xFFF) + (((hbp) & 0x3FF) << 16)) +#define BS_V_INTVALS(vfp, vbp) (((vfp) & 0x3FFF) + (((vbp) & 0xFF) << 16)) + +/* BS_SYNC bits */ +#define BS_SYNC_HSW(x) ((x) & 0x3FF) +#define BS_SYNC_HSP BIT(12) +#define BS_SYNC_VSW(x) (((x) & 0xFF) << 16) +#define BS_SYNC_VSP BIT(28) + +#define BS_NUM_INPUT_IDS 0 +#define BS_NUM_OUTPUT_IDS 0 + +/* Image process registers */ +#define IPS_DEPTH 0x0D8 +#define IPS_RGB_RGB_COEFF0 0x130 +#define IPS_RGB_YUV_COEFF0 0x170 + +#define IPS_DEPTH_MARK 0xF + +/* IPS control register bits */ +#define IPS_CTRL_RGB BIT(0) +#define IPS_CTRL_FT BIT(4) +#define IPS_CTRL_YUV BIT(8) +#define IPS_CTRL_CHD422 BIT(9) +#define IPS_CTRL_CHD420 BIT(10) +#define IPS_CTRL_LPF BIT(11) +#define IPS_CTRL_DITH BIT(12) +#define IPS_CTRL_CLAMP BIT(16) +#define IPS_CTRL_SBS BIT(17) + +/* IPS info register bits */ +#define IPS_INFO_CHD420 BIT(10) + +#define IPS_NUM_INPUT_IDS 2 +#define IPS_NUM_OUTPUT_IDS 1 + +/* FT_COEFF block registers */ +#define FT_COEFF0 0x80 +#define GLB_IT_COEFF 0x80 + +/* GLB_SC_COEFF registers */ +#define GLB_SC_COEFF_ADDR 0x0080 +#define GLB_SC_COEFF_DATA 0x0084 +#define GLB_LT_COEFF_DATA 0x0080 + +#define GLB_SC_COEFF_MAX_NUM 1024 +#define GLB_LT_COEFF_NUM 65 +/* GLB_SC_ADDR */ +#define SC_COEFF_R_ADDR BIT(18) +#define SC_COEFF_G_ADDR BIT(17) +#define SC_COEFF_B_ADDR BIT(16) + +#define SC_COEFF_DATA(x, y) (((y) & 0xFFFF) | (((x) & 0xFFFF) << 16)) + +enum d71_blk_type { + D71_BLK_TYPE_GCU = 0x00, + D71_BLK_TYPE_LPU = 0x01, + D71_BLK_TYPE_CU = 0x02, + D71_BLK_TYPE_DOU = 0x03, + D71_BLK_TYPE_AEU = 0x04, + D71_BLK_TYPE_GLB_LT_COEFF = 0x05, + D71_BLK_TYPE_GLB_SCL_COEFF = 0x06, /* SH/SV scaler coeff */ + D71_BLK_TYPE_GLB_SC_COEFF = 0x07, + D71_BLK_TYPE_PERIPH = 0x08, + D71_BLK_TYPE_LPU_TRUSTED = 0x09, + D71_BLK_TYPE_AEU_TRUSTED = 0x0A, + D71_BLK_TYPE_LPU_LAYER = 0x10, + D71_BLK_TYPE_LPU_WB_LAYER = 0x11, + D71_BLK_TYPE_CU_SPLITTER = 0x20, + D71_BLK_TYPE_CU_SCALER = 0x21, + D71_BLK_TYPE_CU_MERGER = 0x22, + D71_BLK_TYPE_DOU_IPS = 0x30, + D71_BLK_TYPE_DOU_BS = 0x31, + D71_BLK_TYPE_DOU_FT_COEFF = 0x32, + D71_BLK_TYPE_AEU_DS = 0x40, + D71_BLK_TYPE_AEU_AES = 0x41, + D71_BLK_TYPE_RESERVED = 0xFF +}; + +/* Constant of components */ +#define D71_MAX_PIPELINE 2 +#define D71_PIPELINE_MAX_SCALERS 2 +#define D71_PIPELINE_MAX_LAYERS 4 + +#define D71_MAX_GLB_IT_COEFF 3 +#define D71_MAX_GLB_SCL_COEFF 4 + +#define D71_MAX_LAYERS_PER_LPU 4 +#define D71_BLOCK_MAX_INPUT 9 +#define D71_BLOCK_MAX_OUTPUT 5 +#define D71_MAX_SC_PER_CU 2 + +#define D71_BLOCK_OFFSET_PERIPH 0xFE00 +#define D71_BLOCK_SIZE 0x0200 + +#define D71_DEFAULT_PREPRETCH_LINE 5 +#define D71_BUS_WIDTH_16_BYTES 16 + +#define D71_SC_MAX_UPSCALING 64 +#define D71_SC_MAX_DOWNSCALING 6 +#define D71_SC_SPLIT_OVERLAP 8 +#define D71_SC_ENH_SPLIT_OVERLAP 1 + +#define D71_MG_MIN_MERGED_SIZE 4 +#define D71_MG_MAX_MERGED_HSIZE 4032 +#define D71_MG_MAX_MERGED_VSIZE 4096 + +#define D71_PALPHA_DEF_MAP 0xFFAA5500 +#define D71_LAYER_CONTROL_DEFAULT 0x30000000 +#define D71_WB_LAYER_CONTROL_DEFAULT 0x3000FF00 +#define D71_BS_CONTROL_DEFAULT 0x00000002 + +struct block_header { + u32 block_info; + u32 pipeline_info; + u32 input_ids[D71_BLOCK_MAX_INPUT]; + u32 output_ids[D71_BLOCK_MAX_OUTPUT]; +}; + +static inline u32 get_block_type(struct block_header *blk) +{ + return BLOCK_INFO_BLK_TYPE(blk->block_info); +} + +#endif /* !_D71_REG_H_ */ diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_color_mgmt.c b/drivers/gpu/drm/arm/display/komeda/komeda_color_mgmt.c new file mode 100644 index 000000000..50cb8f7ee --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_color_mgmt.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2019 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ + +#include "komeda_color_mgmt.h" + +/* 10bit precision YUV2RGB matrix */ +static const s32 yuv2rgb_bt601_narrow[KOMEDA_N_YUV2RGB_COEFFS] = { + 1192, 0, 1634, + 1192, -401, -832, + 1192, 2066, 0, + 64, 512, 512 +}; + +static const s32 yuv2rgb_bt601_wide[KOMEDA_N_YUV2RGB_COEFFS] = { + 1024, 0, 1436, + 1024, -352, -731, + 1024, 1815, 0, + 0, 512, 512 +}; + +static const s32 yuv2rgb_bt709_narrow[KOMEDA_N_YUV2RGB_COEFFS] = { + 1192, 0, 1836, + 1192, -218, -546, + 1192, 2163, 0, + 64, 512, 512 +}; + +static const s32 yuv2rgb_bt709_wide[KOMEDA_N_YUV2RGB_COEFFS] = { + 1024, 0, 1613, + 1024, -192, -479, + 1024, 1900, 0, + 0, 512, 512 +}; + +static const s32 yuv2rgb_bt2020[KOMEDA_N_YUV2RGB_COEFFS] = { + 1024, 0, 1476, + 1024, -165, -572, + 1024, 1884, 0, + 0, 512, 512 +}; + +const s32 *komeda_select_yuv2rgb_coeffs(u32 color_encoding, u32 color_range) +{ + bool narrow = color_range == DRM_COLOR_YCBCR_LIMITED_RANGE; + const s32 *coeffs; + + switch (color_encoding) { + case DRM_COLOR_YCBCR_BT709: + coeffs = narrow ? yuv2rgb_bt709_narrow : yuv2rgb_bt709_wide; + break; + case DRM_COLOR_YCBCR_BT601: + coeffs = narrow ? yuv2rgb_bt601_narrow : yuv2rgb_bt601_wide; + break; + case DRM_COLOR_YCBCR_BT2020: + coeffs = yuv2rgb_bt2020; + break; + default: + coeffs = NULL; + break; + } + + return coeffs; +} + +struct gamma_curve_sector { + u32 boundary_start; + u32 num_of_segments; + u32 segment_width; +}; + +static struct gamma_curve_sector sector_tbl[] = { + { 0, 4, 4 }, + { 16, 4, 4 }, + { 32, 4, 8 }, + { 64, 4, 16 }, + { 128, 4, 32 }, + { 256, 4, 64 }, + { 512, 16, 32 }, + { 1024, 24, 128 }, +}; + +static void +drm_lut_to_coeffs(struct drm_property_blob *lut_blob, u32 *coeffs, + struct gamma_curve_sector *sector_tbl, u32 num_sectors) +{ + struct drm_color_lut *lut; + u32 i, j, in, num = 0; + + if (!lut_blob) + return; + + lut = lut_blob->data; + + for (i = 0; i < num_sectors; i++) { + for (j = 0; j < sector_tbl[i].num_of_segments; j++) { + in = sector_tbl[i].boundary_start + + j * sector_tbl[i].segment_width; + + coeffs[num++] = drm_color_lut_extract(lut[in].red, + KOMEDA_COLOR_PRECISION); + } + } + + coeffs[num] = BIT(KOMEDA_COLOR_PRECISION); +} + +void drm_lut_to_fgamma_coeffs(struct drm_property_blob *lut_blob, u32 *coeffs) +{ + drm_lut_to_coeffs(lut_blob, coeffs, sector_tbl, ARRAY_SIZE(sector_tbl)); +} + +void drm_ctm_to_coeffs(struct drm_property_blob *ctm_blob, u32 *coeffs) +{ + struct drm_color_ctm *ctm; + u32 i; + + if (!ctm_blob) + return; + + ctm = ctm_blob->data; + + for (i = 0; i < KOMEDA_N_CTM_COEFFS; i++) + coeffs[i] = drm_color_ctm_s31_32_to_qm_n(ctm->matrix[i], 3, 12); +} diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_color_mgmt.h b/drivers/gpu/drm/arm/display/komeda/komeda_color_mgmt.h new file mode 100644 index 000000000..2f4668466 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_color_mgmt.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * (C) COPYRIGHT 2019 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ + +#ifndef _KOMEDA_COLOR_MGMT_H_ +#define _KOMEDA_COLOR_MGMT_H_ + +#include + +#define KOMEDA_N_YUV2RGB_COEFFS 12 +#define KOMEDA_N_RGB2YUV_COEFFS 12 +#define KOMEDA_COLOR_PRECISION 12 +#define KOMEDA_N_GAMMA_COEFFS 65 +#define KOMEDA_COLOR_LUT_SIZE BIT(KOMEDA_COLOR_PRECISION) +#define KOMEDA_N_CTM_COEFFS 9 + +void drm_lut_to_fgamma_coeffs(struct drm_property_blob *lut_blob, u32 *coeffs); +void drm_ctm_to_coeffs(struct drm_property_blob *ctm_blob, u32 *coeffs); + +const s32 *komeda_select_yuv2rgb_coeffs(u32 color_encoding, u32 color_range); + +#endif /*_KOMEDA_COLOR_MGMT_H_*/ diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_crtc.c b/drivers/gpu/drm/arm/display/komeda/komeda_crtc.c new file mode 100644 index 000000000..e8cb782a6 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_crtc.c @@ -0,0 +1,689 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "komeda_dev.h" +#include "komeda_kms.h" + +void komeda_crtc_get_color_config(struct drm_crtc_state *crtc_st, + u32 *color_depths, u32 *color_formats) +{ + struct drm_connector *conn; + struct drm_connector_state *conn_st; + u32 conn_color_formats = ~0u; + int i, min_bpc = 31, conn_bpc = 0; + + for_each_new_connector_in_state(crtc_st->state, conn, conn_st, i) { + if (conn_st->crtc != crtc_st->crtc) + continue; + + conn_bpc = conn->display_info.bpc ? conn->display_info.bpc : 8; + conn_color_formats &= conn->display_info.color_formats; + + if (conn_bpc < min_bpc) + min_bpc = conn_bpc; + } + + /* connector doesn't config any color_format, use RGB444 as default */ + if (!conn_color_formats) + conn_color_formats = BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444); + + *color_depths = GENMASK(min_bpc, 0); + *color_formats = conn_color_formats; +} + +static void komeda_crtc_update_clock_ratio(struct komeda_crtc_state *kcrtc_st) +{ + u64 pxlclk, aclk; + + if (!kcrtc_st->base.active) { + kcrtc_st->clock_ratio = 0; + return; + } + + pxlclk = kcrtc_st->base.adjusted_mode.crtc_clock * 1000ULL; + aclk = komeda_crtc_get_aclk(kcrtc_st); + + kcrtc_st->clock_ratio = div64_u64(aclk << 32, pxlclk); +} + +/** + * komeda_crtc_atomic_check - build display output data flow + * @crtc: DRM crtc + * @state: the crtc state object + * + * crtc_atomic_check is the final check stage, so beside build a display data + * pipeline according to the crtc_state, but still needs to release or disable + * the unclaimed pipeline resources. + * + * RETURNS: + * Zero for success or -errno + */ +static int +komeda_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); + struct komeda_crtc *kcrtc = to_kcrtc(crtc); + struct komeda_crtc_state *kcrtc_st = to_kcrtc_st(crtc_state); + int err; + + if (drm_atomic_crtc_needs_modeset(crtc_state)) + komeda_crtc_update_clock_ratio(kcrtc_st); + + if (crtc_state->active) { + err = komeda_build_display_data_flow(kcrtc, kcrtc_st); + if (err) + return err; + } + + /* release unclaimed pipeline resources */ + err = komeda_release_unclaimed_resources(kcrtc->slave, kcrtc_st); + if (err) + return err; + + err = komeda_release_unclaimed_resources(kcrtc->master, kcrtc_st); + if (err) + return err; + + return 0; +} + +/* For active a crtc, mainly need two parts of preparation + * 1. adjust display operation mode. + * 2. enable needed clk + */ +static int +komeda_crtc_prepare(struct komeda_crtc *kcrtc) +{ + struct drm_device *drm = kcrtc->base.dev; + struct komeda_dev *mdev = kcrtc->base.dev->dev_private; + struct komeda_pipeline *master = kcrtc->master; + struct komeda_crtc_state *kcrtc_st = to_kcrtc_st(kcrtc->base.state); + struct drm_display_mode *mode = &kcrtc_st->base.adjusted_mode; + u32 new_mode; + int err; + + mutex_lock(&mdev->lock); + + new_mode = mdev->dpmode | BIT(master->id); + if (WARN_ON(new_mode == mdev->dpmode)) { + err = 0; + goto unlock; + } + + err = mdev->funcs->change_opmode(mdev, new_mode); + if (err) { + drm_err(drm, "failed to change opmode: 0x%x -> 0x%x.\n,", + mdev->dpmode, new_mode); + goto unlock; + } + + mdev->dpmode = new_mode; + /* Only need to enable aclk on single display mode, but no need to + * enable aclk it on dual display mode, since the dual mode always + * switch from single display mode, the aclk already enabled, no need + * to enable it again. + */ + if (new_mode != KOMEDA_MODE_DUAL_DISP) { + err = clk_set_rate(mdev->aclk, komeda_crtc_get_aclk(kcrtc_st)); + if (err) + drm_err(drm, "failed to set aclk.\n"); + err = clk_prepare_enable(mdev->aclk); + if (err) + drm_err(drm, "failed to enable aclk.\n"); + } + + err = clk_set_rate(master->pxlclk, mode->crtc_clock * 1000); + if (err) + drm_err(drm, "failed to set pxlclk for pipe%d\n", master->id); + err = clk_prepare_enable(master->pxlclk); + if (err) + drm_err(drm, "failed to enable pxl clk for pipe%d.\n", master->id); + +unlock: + mutex_unlock(&mdev->lock); + + return err; +} + +static int +komeda_crtc_unprepare(struct komeda_crtc *kcrtc) +{ + struct drm_device *drm = kcrtc->base.dev; + struct komeda_dev *mdev = kcrtc->base.dev->dev_private; + struct komeda_pipeline *master = kcrtc->master; + u32 new_mode; + int err; + + mutex_lock(&mdev->lock); + + new_mode = mdev->dpmode & (~BIT(master->id)); + + if (WARN_ON(new_mode == mdev->dpmode)) { + err = 0; + goto unlock; + } + + err = mdev->funcs->change_opmode(mdev, new_mode); + if (err) { + drm_err(drm, "failed to change opmode: 0x%x -> 0x%x.\n,", + mdev->dpmode, new_mode); + goto unlock; + } + + mdev->dpmode = new_mode; + + clk_disable_unprepare(master->pxlclk); + if (new_mode == KOMEDA_MODE_INACTIVE) + clk_disable_unprepare(mdev->aclk); + +unlock: + mutex_unlock(&mdev->lock); + + return err; +} + +void komeda_crtc_handle_event(struct komeda_crtc *kcrtc, + struct komeda_events *evts) +{ + struct drm_device *drm = kcrtc->base.dev; + struct drm_crtc *crtc = &kcrtc->base; + u32 events = evts->pipes[kcrtc->master->id]; + + if (events & KOMEDA_EVENT_VSYNC) + drm_crtc_handle_vblank(crtc); + + if (events & KOMEDA_EVENT_EOW) { + struct komeda_wb_connector *wb_conn = kcrtc->wb_conn; + + if (wb_conn) + drm_writeback_signal_completion(&wb_conn->base, 0); + else + drm_warn(drm, "CRTC[%d]: EOW happen but no wb_connector.\n", + drm_crtc_index(&kcrtc->base)); + } + /* will handle it together with the write back support */ + if (events & KOMEDA_EVENT_EOW) + DRM_DEBUG("EOW.\n"); + + if (events & KOMEDA_EVENT_FLIP) { + unsigned long flags; + struct drm_pending_vblank_event *event; + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + if (kcrtc->disable_done) { + complete_all(kcrtc->disable_done); + kcrtc->disable_done = NULL; + } else if (crtc->state->event) { + event = crtc->state->event; + /* + * Consume event before notifying drm core that flip + * happened. + */ + crtc->state->event = NULL; + drm_crtc_send_vblank_event(crtc, event); + } else { + drm_warn(drm, "CRTC[%d]: FLIP happened but no pending commit.\n", + drm_crtc_index(&kcrtc->base)); + } + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + } +} + +static void +komeda_crtc_do_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old) +{ + struct komeda_crtc *kcrtc = to_kcrtc(crtc); + struct komeda_crtc_state *kcrtc_st = to_kcrtc_st(crtc->state); + struct komeda_dev *mdev = kcrtc->base.dev->dev_private; + struct komeda_pipeline *master = kcrtc->master; + struct komeda_pipeline *slave = kcrtc->slave; + struct komeda_wb_connector *wb_conn = kcrtc->wb_conn; + struct drm_connector_state *conn_st; + + DRM_DEBUG_ATOMIC("CRTC%d_FLUSH: active_pipes: 0x%x, affected: 0x%x.\n", + drm_crtc_index(crtc), + kcrtc_st->active_pipes, kcrtc_st->affected_pipes); + + /* step 1: update the pipeline/component state to HW */ + if (has_bit(master->id, kcrtc_st->affected_pipes)) + komeda_pipeline_update(master, old->state); + + if (slave && has_bit(slave->id, kcrtc_st->affected_pipes)) + komeda_pipeline_update(slave, old->state); + + conn_st = wb_conn ? wb_conn->base.base.state : NULL; + if (conn_st && conn_st->writeback_job) + drm_writeback_queue_job(&wb_conn->base, conn_st); + + /* step 2: notify the HW to kickoff the update */ + mdev->funcs->flush(mdev, master->id, kcrtc_st->active_pipes); +} + +static void +komeda_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct drm_crtc_state *old = drm_atomic_get_old_crtc_state(state, + crtc); + pm_runtime_get_sync(crtc->dev->dev); + komeda_crtc_prepare(to_kcrtc(crtc)); + drm_crtc_vblank_on(crtc); + WARN_ON(drm_crtc_vblank_get(crtc)); + komeda_crtc_do_flush(crtc, old); +} + +void +komeda_crtc_flush_and_wait_for_flip_done(struct komeda_crtc *kcrtc, + struct completion *input_flip_done) +{ + struct drm_device *drm = kcrtc->base.dev; + struct komeda_dev *mdev = kcrtc->master->mdev; + struct completion *flip_done; + struct completion temp; + + /* if caller doesn't send a flip_done, use a private flip_done */ + if (input_flip_done) { + flip_done = input_flip_done; + } else { + init_completion(&temp); + kcrtc->disable_done = &temp; + flip_done = &temp; + } + + mdev->funcs->flush(mdev, kcrtc->master->id, 0); + + /* wait the flip take affect.*/ + if (wait_for_completion_timeout(flip_done, HZ) == 0) { + drm_err(drm, "wait pipe%d flip done timeout\n", kcrtc->master->id); + if (!input_flip_done) { + unsigned long flags; + + spin_lock_irqsave(&drm->event_lock, flags); + kcrtc->disable_done = NULL; + spin_unlock_irqrestore(&drm->event_lock, flags); + } + } +} + +static void +komeda_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct drm_crtc_state *old = drm_atomic_get_old_crtc_state(state, + crtc); + struct komeda_crtc *kcrtc = to_kcrtc(crtc); + struct komeda_crtc_state *old_st = to_kcrtc_st(old); + struct komeda_pipeline *master = kcrtc->master; + struct komeda_pipeline *slave = kcrtc->slave; + struct completion *disable_done; + bool needs_phase2 = false; + + DRM_DEBUG_ATOMIC("CRTC%d_DISABLE: active_pipes: 0x%x, affected: 0x%x\n", + drm_crtc_index(crtc), + old_st->active_pipes, old_st->affected_pipes); + + if (slave && has_bit(slave->id, old_st->active_pipes)) + komeda_pipeline_disable(slave, old->state); + + if (has_bit(master->id, old_st->active_pipes)) + needs_phase2 = komeda_pipeline_disable(master, old->state); + + /* crtc_disable has two scenarios according to the state->active switch. + * 1. active -> inactive + * this commit is a disable commit. and the commit will be finished + * or done after the disable operation. on this case we can directly + * use the crtc->state->event to tracking the HW disable operation. + * 2. active -> active + * the crtc->commit is not for disable, but a modeset operation when + * crtc is active, such commit actually has been completed by 3 + * DRM operations: + * crtc_disable, update_planes(crtc_flush), crtc_enable + * so on this case the crtc->commit is for the whole process. + * we can not use it for tracing the disable, we need a temporary + * flip_done for tracing the disable. and crtc->state->event for + * the crtc_enable operation. + * That's also the reason why skip modeset commit in + * komeda_crtc_atomic_flush() + */ + disable_done = (needs_phase2 || crtc->state->active) ? + NULL : &crtc->state->commit->flip_done; + + /* wait phase 1 disable done */ + komeda_crtc_flush_and_wait_for_flip_done(kcrtc, disable_done); + + /* phase 2 */ + if (needs_phase2) { + komeda_pipeline_disable(kcrtc->master, old->state); + + disable_done = crtc->state->active ? + NULL : &crtc->state->commit->flip_done; + + komeda_crtc_flush_and_wait_for_flip_done(kcrtc, disable_done); + } + + drm_crtc_vblank_put(crtc); + drm_crtc_vblank_off(crtc); + komeda_crtc_unprepare(kcrtc); + pm_runtime_put(crtc->dev->dev); +} + +static void +komeda_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_commit *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, + crtc); + struct drm_crtc_state *old = drm_atomic_get_old_crtc_state(state, + crtc); + /* commit with modeset will be handled in enable/disable */ + if (drm_atomic_crtc_needs_modeset(crtc_state)) + return; + + komeda_crtc_do_flush(crtc, old); +} + +/* Returns the minimum frequency of the aclk rate (main engine clock) in Hz */ +static unsigned long +komeda_calc_min_aclk_rate(struct komeda_crtc *kcrtc, + unsigned long pxlclk) +{ + /* Once dual-link one display pipeline drives two display outputs, + * the aclk needs run on the double rate of pxlclk + */ + if (kcrtc->master->dual_link) + return pxlclk * 2; + else + return pxlclk; +} + +/* Get current aclk rate that specified by state */ +unsigned long komeda_crtc_get_aclk(struct komeda_crtc_state *kcrtc_st) +{ + struct drm_crtc *crtc = kcrtc_st->base.crtc; + struct komeda_dev *mdev = crtc->dev->dev_private; + unsigned long pxlclk = kcrtc_st->base.adjusted_mode.crtc_clock * 1000; + unsigned long min_aclk; + + min_aclk = komeda_calc_min_aclk_rate(to_kcrtc(crtc), pxlclk); + + return clk_round_rate(mdev->aclk, min_aclk); +} + +static enum drm_mode_status +komeda_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *m) +{ + struct komeda_dev *mdev = crtc->dev->dev_private; + struct komeda_crtc *kcrtc = to_kcrtc(crtc); + struct komeda_pipeline *master = kcrtc->master; + unsigned long min_pxlclk, min_aclk; + + if (m->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_NO_INTERLACE; + + min_pxlclk = m->clock * 1000; + if (master->dual_link) + min_pxlclk /= 2; + + if (min_pxlclk != clk_round_rate(master->pxlclk, min_pxlclk)) { + DRM_DEBUG_ATOMIC("pxlclk doesn't support %lu Hz\n", min_pxlclk); + + return MODE_NOCLOCK; + } + + min_aclk = komeda_calc_min_aclk_rate(to_kcrtc(crtc), min_pxlclk); + if (clk_round_rate(mdev->aclk, min_aclk) < min_aclk) { + DRM_DEBUG_ATOMIC("engine clk can't satisfy the requirement of %s-clk: %lu.\n", + m->name, min_pxlclk); + + return MODE_CLOCK_HIGH; + } + + return MODE_OK; +} + +static bool komeda_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *m, + struct drm_display_mode *adjusted_mode) +{ + struct komeda_crtc *kcrtc = to_kcrtc(crtc); + unsigned long clk_rate; + + drm_mode_set_crtcinfo(adjusted_mode, 0); + /* In dual link half the horizontal settings */ + if (kcrtc->master->dual_link) { + adjusted_mode->crtc_clock /= 2; + adjusted_mode->crtc_hdisplay /= 2; + adjusted_mode->crtc_hsync_start /= 2; + adjusted_mode->crtc_hsync_end /= 2; + adjusted_mode->crtc_htotal /= 2; + } + + clk_rate = adjusted_mode->crtc_clock * 1000; + /* crtc_clock will be used as the komeda output pixel clock */ + adjusted_mode->crtc_clock = clk_round_rate(kcrtc->master->pxlclk, + clk_rate) / 1000; + + return true; +} + +static const struct drm_crtc_helper_funcs komeda_crtc_helper_funcs = { + .atomic_check = komeda_crtc_atomic_check, + .atomic_flush = komeda_crtc_atomic_flush, + .atomic_enable = komeda_crtc_atomic_enable, + .atomic_disable = komeda_crtc_atomic_disable, + .mode_valid = komeda_crtc_mode_valid, + .mode_fixup = komeda_crtc_mode_fixup, +}; + +static void komeda_crtc_reset(struct drm_crtc *crtc) +{ + struct komeda_crtc_state *state; + + if (crtc->state) + __drm_atomic_helper_crtc_destroy_state(crtc->state); + + kfree(to_kcrtc_st(crtc->state)); + crtc->state = NULL; + + state = kzalloc_obj(*state); + if (state) + __drm_atomic_helper_crtc_reset(crtc, &state->base); +} + +static struct drm_crtc_state * +komeda_crtc_atomic_duplicate_state(struct drm_crtc *crtc) +{ + struct komeda_crtc_state *old = to_kcrtc_st(crtc->state); + struct komeda_crtc_state *new; + + new = kzalloc_obj(*new); + if (!new) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &new->base); + + new->affected_pipes = old->active_pipes; + new->clock_ratio = old->clock_ratio; + new->max_slave_zorder = old->max_slave_zorder; + + return &new->base; +} + +static void komeda_crtc_atomic_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + __drm_atomic_helper_crtc_destroy_state(state); + kfree(to_kcrtc_st(state)); +} + +static int komeda_crtc_vblank_enable(struct drm_crtc *crtc) +{ + struct komeda_dev *mdev = crtc->dev->dev_private; + struct komeda_crtc *kcrtc = to_kcrtc(crtc); + + mdev->funcs->on_off_vblank(mdev, kcrtc->master->id, true); + return 0; +} + +static void komeda_crtc_vblank_disable(struct drm_crtc *crtc) +{ + struct komeda_dev *mdev = crtc->dev->dev_private; + struct komeda_crtc *kcrtc = to_kcrtc(crtc); + + mdev->funcs->on_off_vblank(mdev, kcrtc->master->id, false); +} + +static const struct drm_crtc_funcs komeda_crtc_funcs = { + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .reset = komeda_crtc_reset, + .atomic_duplicate_state = komeda_crtc_atomic_duplicate_state, + .atomic_destroy_state = komeda_crtc_atomic_destroy_state, + .enable_vblank = komeda_crtc_vblank_enable, + .disable_vblank = komeda_crtc_vblank_disable, +}; + +int komeda_kms_setup_crtcs(struct komeda_kms_dev *kms, + struct komeda_dev *mdev) +{ + struct drm_device *drm = &kms->base; + struct komeda_crtc *crtc; + struct komeda_pipeline *master; + char str[16]; + int i; + + kms->n_crtcs = 0; + + for (i = 0; i < mdev->n_pipelines; i++) { + crtc = &kms->crtcs[kms->n_crtcs]; + master = mdev->pipelines[i]; + + crtc->master = master; + crtc->slave = komeda_pipeline_get_slave(master); + + if (crtc->slave) + sprintf(str, "pipe-%d", crtc->slave->id); + else + sprintf(str, "None"); + + drm_info(drm, "CRTC-%d: master(pipe-%d) slave(%s).\n", + kms->n_crtcs, master->id, str); + + kms->n_crtcs++; + } + + return 0; +} + +static struct drm_plane * +get_crtc_primary(struct komeda_kms_dev *kms, struct komeda_crtc *crtc) +{ + struct komeda_plane *kplane; + struct drm_plane *plane; + + drm_for_each_plane(plane, &kms->base) { + if (plane->type != DRM_PLANE_TYPE_PRIMARY) + continue; + + kplane = to_kplane(plane); + /* only master can be primary */ + if (kplane->layer->base.pipeline == crtc->master) + return plane; + } + + return NULL; +} + +static int komeda_attach_bridge(struct device *dev, + struct komeda_pipeline *pipe, + struct drm_encoder *encoder) +{ + struct drm_device *drm = encoder->dev; + struct drm_bridge *bridge; + int err; + + bridge = devm_drm_of_get_bridge(dev, pipe->of_node, + KOMEDA_OF_PORT_OUTPUT, 0); + if (IS_ERR(bridge)) + return dev_err_probe(dev, PTR_ERR(bridge), "remote bridge not found for pipe: %s\n", + of_node_full_name(pipe->of_node)); + + err = drm_bridge_attach(encoder, bridge, NULL, 0); + if (err) + drm_err(drm, "bridge_attach() failed for pipe: %s\n", + of_node_full_name(pipe->of_node)); + + return err; +} + +static int komeda_crtc_add(struct komeda_kms_dev *kms, + struct komeda_crtc *kcrtc) +{ + struct drm_crtc *crtc = &kcrtc->base; + struct drm_device *base = &kms->base; + struct komeda_pipeline *pipe = kcrtc->master; + struct drm_encoder *encoder = &kcrtc->encoder; + int err; + + err = drm_crtc_init_with_planes(base, crtc, + get_crtc_primary(kms, kcrtc), NULL, + &komeda_crtc_funcs, NULL); + if (err) + return err; + + drm_crtc_helper_add(crtc, &komeda_crtc_helper_funcs); + + crtc->port = pipe->of_output_port; + + /* Construct an encoder for each pipeline and attach it to the remote + * bridge + */ + kcrtc->encoder.possible_crtcs = drm_crtc_mask(crtc); + err = drm_simple_encoder_init(base, encoder, DRM_MODE_ENCODER_TMDS); + if (err) + return err; + + if (pipe->of_output_links[0]) { + err = komeda_attach_bridge(base->dev, pipe, encoder); + if (err) + return err; + } + + drm_crtc_enable_color_mgmt(crtc, 0, true, KOMEDA_COLOR_LUT_SIZE); + + komeda_pipeline_dump(pipe); + + return 0; +} + +int komeda_kms_add_crtcs(struct komeda_kms_dev *kms, struct komeda_dev *mdev) +{ + int i, err; + + for (i = 0; i < kms->n_crtcs; i++) { + err = komeda_crtc_add(kms, &kms->crtcs[i]); + if (err) + return err; + } + + return 0; +} diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_dev.c b/drivers/gpu/drm/arm/display/komeda/komeda_dev.c new file mode 100644 index 000000000..9aad1d1d2 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_dev.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "komeda_dev.h" + +static int komeda_register_show(struct seq_file *sf, void *x) +{ + struct komeda_dev *mdev = sf->private; + int i; + + seq_puts(sf, "\n====== Komeda register dump =========\n"); + + pm_runtime_get_sync(mdev->dev); + + if (mdev->funcs->dump_register) + mdev->funcs->dump_register(mdev, sf); + + for (i = 0; i < mdev->n_pipelines; i++) + komeda_pipeline_dump_register(mdev->pipelines[i], sf); + + pm_runtime_put(mdev->dev); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(komeda_register); + +static void komeda_debugfs_init(struct komeda_dev *mdev) +{ + if (!debugfs_initialized()) + return; + + mdev->debugfs_root = debugfs_create_dir("komeda", NULL); + debugfs_create_file("register", 0444, mdev->debugfs_root, + mdev, &komeda_register_fops); + debugfs_create_x16("err_verbosity", 0664, mdev->debugfs_root, + &mdev->err_verbosity); +} + +static ssize_t +core_id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct komeda_dev *mdev = dev_to_mdev(dev); + + return sysfs_emit(buf, "0x%08x\n", mdev->chip.core_id); +} +static DEVICE_ATTR_RO(core_id); + +static ssize_t +config_id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct komeda_dev *mdev = dev_to_mdev(dev); + struct komeda_pipeline *pipe = mdev->pipelines[0]; + union komeda_config_id config_id; + int i; + + memset(&config_id, 0, sizeof(config_id)); + + config_id.max_line_sz = pipe->layers[0]->hsize_in.end; + config_id.n_pipelines = mdev->n_pipelines; + config_id.n_scalers = pipe->n_scalers; + config_id.n_layers = pipe->n_layers; + config_id.n_richs = 0; + for (i = 0; i < pipe->n_layers; i++) { + if (pipe->layers[i]->layer_type == KOMEDA_FMT_RICH_LAYER) + config_id.n_richs++; + } + return sysfs_emit(buf, "0x%08x\n", config_id.value); +} +static DEVICE_ATTR_RO(config_id); + +static ssize_t +aclk_hz_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct komeda_dev *mdev = dev_to_mdev(dev); + + return sysfs_emit(buf, "%lu\n", clk_get_rate(mdev->aclk)); +} +static DEVICE_ATTR_RO(aclk_hz); + +static struct attribute *komeda_sysfs_entries[] = { + &dev_attr_core_id.attr, + &dev_attr_config_id.attr, + &dev_attr_aclk_hz.attr, + NULL, +}; + +static struct attribute_group komeda_sysfs_attr_group = { + .attrs = komeda_sysfs_entries, +}; + +static int komeda_parse_pipe_dt(struct komeda_pipeline *pipe) +{ + struct device_node *np = pipe->of_node; + struct clk *clk; + + clk = of_clk_get_by_name(np, "pxclk"); + if (IS_ERR(clk)) { + DRM_ERROR("get pxclk for pipeline %d failed!\n", pipe->id); + return PTR_ERR(clk); + } + pipe->pxlclk = clk; + + /* enum ports */ + pipe->of_output_links[0] = + of_graph_get_remote_node(np, KOMEDA_OF_PORT_OUTPUT, 0); + pipe->of_output_links[1] = + of_graph_get_remote_node(np, KOMEDA_OF_PORT_OUTPUT, 1); + pipe->of_output_port = + of_graph_get_port_by_id(np, KOMEDA_OF_PORT_OUTPUT); + + pipe->dual_link = pipe->of_output_links[0] && pipe->of_output_links[1]; + + return 0; +} + +static int komeda_parse_dt(struct device *dev, struct komeda_dev *mdev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct device_node *child, *np = dev->of_node; + struct komeda_pipeline *pipe; + u32 pipe_id = U32_MAX; + int ret = -1; + + mdev->irq = platform_get_irq(pdev, 0); + if (mdev->irq < 0) { + DRM_ERROR("could not get IRQ number.\n"); + return mdev->irq; + } + + /* Get the optional framebuffer memory resource */ + ret = of_reserved_mem_device_init(dev); + if (ret && ret != -ENODEV) + return ret; + + for_each_available_child_of_node(np, child) { + if (of_node_name_eq(child, "pipeline")) { + of_property_read_u32(child, "reg", &pipe_id); + if (pipe_id >= mdev->n_pipelines) { + DRM_WARN("Skip the redundant DT node: pipeline-%u.\n", + pipe_id); + continue; + } + mdev->pipelines[pipe_id]->of_node = of_node_get(child); + } + } + + for (pipe_id = 0; pipe_id < mdev->n_pipelines; pipe_id++) { + pipe = mdev->pipelines[pipe_id]; + + if (!pipe->of_node) { + DRM_ERROR("Pipeline-%d doesn't have a DT node.\n", + pipe->id); + return -EINVAL; + } + ret = komeda_parse_pipe_dt(pipe); + if (ret) + return ret; + } + + return 0; +} + +struct komeda_dev *komeda_dev_create(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + komeda_identify_func komeda_identify; + struct komeda_dev *mdev; + int err = 0; + + komeda_identify = of_device_get_match_data(dev); + if (!komeda_identify) + return ERR_PTR(-ENODEV); + + mdev = devm_kzalloc(dev, sizeof(*mdev), GFP_KERNEL); + if (!mdev) + return ERR_PTR(-ENOMEM); + + mutex_init(&mdev->lock); + + mdev->dev = dev; + mdev->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mdev->reg_base)) { + DRM_ERROR("Map register space failed.\n"); + err = PTR_ERR(mdev->reg_base); + mdev->reg_base = NULL; + goto err_cleanup; + } + + mdev->aclk = devm_clk_get(dev, "aclk"); + if (IS_ERR(mdev->aclk)) { + DRM_ERROR("Get engine clk failed.\n"); + err = PTR_ERR(mdev->aclk); + mdev->aclk = NULL; + goto err_cleanup; + } + + clk_prepare_enable(mdev->aclk); + + mdev->funcs = komeda_identify(mdev->reg_base, &mdev->chip); + if (!mdev->funcs) { + DRM_ERROR("Failed to identify the HW.\n"); + err = -ENODEV; + goto disable_clk; + } + + DRM_INFO("Found ARM Mali-D%x version r%dp%d\n", + MALIDP_CORE_ID_PRODUCT_ID(mdev->chip.core_id), + MALIDP_CORE_ID_MAJOR(mdev->chip.core_id), + MALIDP_CORE_ID_MINOR(mdev->chip.core_id)); + + mdev->funcs->init_format_table(mdev); + + err = mdev->funcs->enum_resources(mdev); + if (err) { + DRM_ERROR("enumerate display resource failed.\n"); + goto disable_clk; + } + + err = komeda_parse_dt(dev, mdev); + if (err) { + DRM_ERROR("parse device tree failed.\n"); + goto disable_clk; + } + + err = komeda_assemble_pipelines(mdev); + if (err) { + DRM_ERROR("assemble display pipelines failed.\n"); + goto disable_clk; + } + + dma_set_max_seg_size(dev, U32_MAX); + + mdev->iommu = iommu_get_domain_for_dev(mdev->dev); + if (!mdev->iommu) + DRM_INFO("continue without IOMMU support!\n"); + + clk_disable_unprepare(mdev->aclk); + + err = sysfs_create_group(&dev->kobj, &komeda_sysfs_attr_group); + if (err) { + DRM_ERROR("create sysfs group failed.\n"); + goto err_cleanup; + } + + mdev->err_verbosity = KOMEDA_DEV_PRINT_ERR_EVENTS; + + komeda_debugfs_init(mdev); + + return mdev; + +disable_clk: + clk_disable_unprepare(mdev->aclk); +err_cleanup: + komeda_dev_destroy(mdev); + return ERR_PTR(err); +} + +void komeda_dev_destroy(struct komeda_dev *mdev) +{ + struct device *dev = mdev->dev; + const struct komeda_dev_funcs *funcs = mdev->funcs; + int i; + + sysfs_remove_group(&dev->kobj, &komeda_sysfs_attr_group); + + debugfs_remove_recursive(mdev->debugfs_root); + + if (mdev->aclk) + clk_prepare_enable(mdev->aclk); + + for (i = 0; i < mdev->n_pipelines; i++) { + komeda_pipeline_destroy(mdev, mdev->pipelines[i]); + mdev->pipelines[i] = NULL; + } + + mdev->n_pipelines = 0; + + of_reserved_mem_device_release(dev); + + if (funcs && funcs->cleanup) + funcs->cleanup(mdev); + + if (mdev->reg_base) { + devm_iounmap(dev, mdev->reg_base); + mdev->reg_base = NULL; + } + + if (mdev->aclk) { + clk_disable_unprepare(mdev->aclk); + devm_clk_put(dev, mdev->aclk); + mdev->aclk = NULL; + } + + devm_kfree(dev, mdev); +} + +int komeda_dev_resume(struct komeda_dev *mdev) +{ + int err; + + err = clk_prepare_enable(mdev->aclk); + if (err) + return err; + + mdev->funcs->enable_irq(mdev); + + if (mdev->iommu && mdev->funcs->connect_iommu) + if (mdev->funcs->connect_iommu(mdev)) + DRM_ERROR("connect iommu failed.\n"); + + return 0; +} + +int komeda_dev_suspend(struct komeda_dev *mdev) +{ + if (mdev->iommu && mdev->funcs->disconnect_iommu) + if (mdev->funcs->disconnect_iommu(mdev)) + DRM_ERROR("disconnect iommu failed.\n"); + + mdev->funcs->disable_irq(mdev); + + clk_disable_unprepare(mdev->aclk); + + return 0; +} diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_dev.h b/drivers/gpu/drm/arm/display/komeda/komeda_dev.h new file mode 100644 index 000000000..5b536f0cb --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_dev.h @@ -0,0 +1,243 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#ifndef _KOMEDA_DEV_H_ +#define _KOMEDA_DEV_H_ + +#include +#include +#include "komeda_pipeline.h" +#include "malidp_product.h" +#include "komeda_format_caps.h" + +#define KOMEDA_EVENT_VSYNC BIT_ULL(0) +#define KOMEDA_EVENT_FLIP BIT_ULL(1) +#define KOMEDA_EVENT_URUN BIT_ULL(2) +#define KOMEDA_EVENT_IBSY BIT_ULL(3) +#define KOMEDA_EVENT_OVR BIT_ULL(4) +#define KOMEDA_EVENT_EOW BIT_ULL(5) +#define KOMEDA_EVENT_MODE BIT_ULL(6) +#define KOMEDA_EVENT_FULL BIT_ULL(7) +#define KOMEDA_EVENT_EMPTY BIT_ULL(8) + +#define KOMEDA_ERR_TETO BIT_ULL(14) +#define KOMEDA_ERR_TEMR BIT_ULL(15) +#define KOMEDA_ERR_TITR BIT_ULL(16) +#define KOMEDA_ERR_CPE BIT_ULL(17) +#define KOMEDA_ERR_CFGE BIT_ULL(18) +#define KOMEDA_ERR_AXIE BIT_ULL(19) +#define KOMEDA_ERR_ACE0 BIT_ULL(20) +#define KOMEDA_ERR_ACE1 BIT_ULL(21) +#define KOMEDA_ERR_ACE2 BIT_ULL(22) +#define KOMEDA_ERR_ACE3 BIT_ULL(23) +#define KOMEDA_ERR_DRIFTTO BIT_ULL(24) +#define KOMEDA_ERR_FRAMETO BIT_ULL(25) +#define KOMEDA_ERR_CSCE BIT_ULL(26) +#define KOMEDA_ERR_ZME BIT_ULL(27) +#define KOMEDA_ERR_MERR BIT_ULL(28) +#define KOMEDA_ERR_TCF BIT_ULL(29) +#define KOMEDA_ERR_TTNG BIT_ULL(30) +#define KOMEDA_ERR_TTF BIT_ULL(31) + +#define KOMEDA_ERR_EVENTS \ + (KOMEDA_EVENT_URUN | KOMEDA_EVENT_IBSY | KOMEDA_EVENT_OVR |\ + KOMEDA_ERR_TETO | KOMEDA_ERR_TEMR | KOMEDA_ERR_TITR |\ + KOMEDA_ERR_CPE | KOMEDA_ERR_CFGE | KOMEDA_ERR_AXIE |\ + KOMEDA_ERR_ACE0 | KOMEDA_ERR_ACE1 | KOMEDA_ERR_ACE2 |\ + KOMEDA_ERR_ACE3 | KOMEDA_ERR_DRIFTTO | KOMEDA_ERR_FRAMETO |\ + KOMEDA_ERR_ZME | KOMEDA_ERR_MERR | KOMEDA_ERR_TCF |\ + KOMEDA_ERR_TTNG | KOMEDA_ERR_TTF) + +#define KOMEDA_WARN_EVENTS \ + (KOMEDA_ERR_CSCE | KOMEDA_EVENT_FULL | KOMEDA_EVENT_EMPTY) + +#define KOMEDA_INFO_EVENTS (0 \ + | KOMEDA_EVENT_VSYNC \ + | KOMEDA_EVENT_FLIP \ + | KOMEDA_EVENT_EOW \ + | KOMEDA_EVENT_MODE \ + ) + +/* pipeline DT ports */ +enum { + KOMEDA_OF_PORT_OUTPUT = 0, + KOMEDA_OF_PORT_COPROC = 1, +}; + +struct komeda_chip_info { + u32 arch_id; + u32 core_id; + u32 core_info; + u32 bus_width; +}; + +struct komeda_dev; + +struct komeda_events { + u64 global; + u64 pipes[KOMEDA_MAX_PIPELINES]; +}; + +/** + * struct komeda_dev_funcs + * + * Supplied by chip level and returned by the chip entry function xxx_identify, + */ +struct komeda_dev_funcs { + /** + * @init_format_table: + * + * initialize &komeda_dev->format_table, this function should be called + * before the &enum_resource + */ + void (*init_format_table)(struct komeda_dev *mdev); + /** + * @enum_resources: + * + * for CHIP to report or add pipeline and component resources to CORE + */ + int (*enum_resources)(struct komeda_dev *mdev); + /** @cleanup: call to chip to cleanup komeda_dev->chip data */ + void (*cleanup)(struct komeda_dev *mdev); + /** @connect_iommu: Optional, connect to external iommu */ + int (*connect_iommu)(struct komeda_dev *mdev); + /** @disconnect_iommu: Optional, disconnect to external iommu */ + int (*disconnect_iommu)(struct komeda_dev *mdev); + /** + * @irq_handler: + * + * for CORE to get the HW event from the CHIP when interrupt happened. + */ + irqreturn_t (*irq_handler)(struct komeda_dev *mdev, + struct komeda_events *events); + /** @enable_irq: enable irq */ + int (*enable_irq)(struct komeda_dev *mdev); + /** @disable_irq: disable irq */ + int (*disable_irq)(struct komeda_dev *mdev); + /** @on_off_vblank: notify HW to on/off vblank */ + void (*on_off_vblank)(struct komeda_dev *mdev, + int master_pipe, bool on); + + /** @dump_register: Optional, dump registers to seq_file */ + void (*dump_register)(struct komeda_dev *mdev, struct seq_file *seq); + /** + * @change_opmode: + * + * Notify HW to switch to a new display operation mode. + */ + int (*change_opmode)(struct komeda_dev *mdev, int new_mode); + /** @flush: Notify the HW to flush or kickoff the update */ + void (*flush)(struct komeda_dev *mdev, + int master_pipe, u32 active_pipes); +}; + +/* + * DISPLAY_MODE describes how many display been enabled, and which will be + * passed to CHIP by &komeda_dev_funcs->change_opmode(), then CHIP can do the + * pipeline resources assignment according to this usage hint. + * - KOMEDA_MODE_DISP0: Only one display enabled, pipeline-0 work as master. + * - KOMEDA_MODE_DISP1: Only one display enabled, pipeline-0 work as master. + * - KOMEDA_MODE_DUAL_DISP: Dual display mode, both display has been enabled. + * And D71 supports assign two pipelines to one single display on mode + * KOMEDA_MODE_DISP0/DISP1 + */ +enum { + KOMEDA_MODE_INACTIVE = 0, + KOMEDA_MODE_DISP0 = BIT(0), + KOMEDA_MODE_DISP1 = BIT(1), + KOMEDA_MODE_DUAL_DISP = KOMEDA_MODE_DISP0 | KOMEDA_MODE_DISP1, +}; + +/** + * struct komeda_dev + * + * Pipeline and component are used to describe how to handle the pixel data. + * komeda_device is for describing the whole view of the device, and the + * control-abilites of device. + */ +struct komeda_dev { + /** @dev: the base device structure */ + struct device *dev; + /** @reg_base: the base address of komeda io space */ + u32 __iomem *reg_base; + + /** @chip: the basic chip information */ + struct komeda_chip_info chip; + /** @fmt_tbl: initialized by &komeda_dev_funcs->init_format_table */ + struct komeda_format_caps_table fmt_tbl; + /** @aclk: HW main engine clk */ + struct clk *aclk; + + /** @irq: irq number */ + int irq; + + /** @lock: used to protect dpmode */ + struct mutex lock; + /** @dpmode: current display mode */ + u32 dpmode; + + /** @n_pipelines: the number of pipe in @pipelines */ + int n_pipelines; + /** @pipelines: the komeda pipelines */ + struct komeda_pipeline *pipelines[KOMEDA_MAX_PIPELINES]; + + /** @funcs: chip funcs to access to HW */ + const struct komeda_dev_funcs *funcs; + /** + * @chip_data: + * + * chip data will be added by &komeda_dev_funcs.enum_resources() and + * destroyed by &komeda_dev_funcs.cleanup() + */ + void *chip_data; + + /** @iommu: iommu domain */ + struct iommu_domain *iommu; + + /** @debugfs_root: root directory of komeda debugfs */ + struct dentry *debugfs_root; + /** + * @err_verbosity: bitmask for how much extra info to print on error + * + * See KOMEDA_DEV_* macros for details. Low byte contains the debug + * level categories, the high byte contains extra debug options. + */ + u16 err_verbosity; + /* Print a single line per error per frame with error events. */ +#define KOMEDA_DEV_PRINT_ERR_EVENTS BIT(0) + /* Print a single line per warning per frame with error events. */ +#define KOMEDA_DEV_PRINT_WARN_EVENTS BIT(1) + /* Print a single line per info event per frame with error events. */ +#define KOMEDA_DEV_PRINT_INFO_EVENTS BIT(2) + /* Dump DRM state on an error or warning event. */ +#define KOMEDA_DEV_PRINT_DUMP_STATE_ON_EVENT BIT(8) + /* Disable rate limiting of event prints (normally one per commit) */ +#define KOMEDA_DEV_PRINT_DISABLE_RATELIMIT BIT(12) +}; + +static inline bool +komeda_product_match(struct komeda_dev *mdev, u32 target) +{ + return MALIDP_CORE_ID_PRODUCT_ID(mdev->chip.core_id) == target; +} + +typedef const struct komeda_dev_funcs * +(*komeda_identify_func)(u32 __iomem *reg, struct komeda_chip_info *chip); + +const struct komeda_dev_funcs * +d71_identify(u32 __iomem *reg, struct komeda_chip_info *chip); + +struct komeda_dev *komeda_dev_create(struct device *dev); +void komeda_dev_destroy(struct komeda_dev *mdev); + +struct komeda_dev *dev_to_mdev(struct device *dev); + +void komeda_print_events(struct komeda_events *evts, struct drm_device *dev); + +int komeda_dev_resume(struct komeda_dev *mdev); +int komeda_dev_suspend(struct komeda_dev *mdev); + +#endif /*_KOMEDA_DEV_H_*/ diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_drv.c b/drivers/gpu/drm/arm/display/komeda/komeda_drv.c new file mode 100644 index 000000000..67fffab01 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_drv.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "komeda_dev.h" +#include "komeda_kms.h" + +struct komeda_drv { + struct komeda_dev *mdev; + struct komeda_kms_dev *kms; +}; + +struct komeda_dev *dev_to_mdev(struct device *dev) +{ + struct komeda_drv *mdrv = dev_get_drvdata(dev); + + return mdrv ? mdrv->mdev : NULL; +} + +static void komeda_platform_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct komeda_drv *mdrv = dev_get_drvdata(dev); + + komeda_kms_detach(mdrv->kms); + + if (pm_runtime_enabled(dev)) + pm_runtime_disable(dev); + else + komeda_dev_suspend(mdrv->mdev); + + komeda_dev_destroy(mdrv->mdev); + + dev_set_drvdata(dev, NULL); + devm_kfree(dev, mdrv); +} + +static void komeda_platform_shutdown(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct komeda_drv *mdrv = dev_get_drvdata(dev); + + komeda_kms_shutdown(mdrv->kms); +} + +static int komeda_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct komeda_drv *mdrv; + int err; + + err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(40)); + if (err) + return dev_err_probe(dev, err, "DMA mask error\n"); + + mdrv = devm_kzalloc(dev, sizeof(*mdrv), GFP_KERNEL); + if (!mdrv) + return -ENOMEM; + + mdrv->mdev = komeda_dev_create(dev); + if (IS_ERR(mdrv->mdev)) { + err = PTR_ERR(mdrv->mdev); + goto free_mdrv; + } + + pm_runtime_enable(dev); + if (!pm_runtime_enabled(dev)) { + err = komeda_dev_resume(mdrv->mdev); + if (err) + goto err_destroy_mdev; + } + + mdrv->kms = komeda_kms_attach(mdrv->mdev); + if (IS_ERR(mdrv->kms)) { + err = PTR_ERR(mdrv->kms); + goto destroy_mdev; + } + + dev_set_drvdata(dev, mdrv); + drm_client_setup(&mdrv->kms->base, NULL); + + return 0; + +destroy_mdev: + if (pm_runtime_enabled(dev)) + pm_runtime_disable(dev); + else + komeda_dev_suspend(mdrv->mdev); +err_destroy_mdev: + komeda_dev_destroy(mdrv->mdev); + +free_mdrv: + devm_kfree(dev, mdrv); + return err; +} + +static const struct of_device_id komeda_of_match[] = { + { .compatible = "arm,mali-d71", .data = d71_identify, }, + { .compatible = "arm,mali-d32", .data = d71_identify, }, + { .compatible = "armchina,linlon-d6", .data = d71_identify, }, + {}, +}; + +MODULE_DEVICE_TABLE(of, komeda_of_match); + +static int __maybe_unused komeda_rt_pm_suspend(struct device *dev) +{ + struct komeda_drv *mdrv = dev_get_drvdata(dev); + + return komeda_dev_suspend(mdrv->mdev); +} + +static int __maybe_unused komeda_rt_pm_resume(struct device *dev) +{ + struct komeda_drv *mdrv = dev_get_drvdata(dev); + + return komeda_dev_resume(mdrv->mdev); +} + +static int __maybe_unused komeda_pm_suspend(struct device *dev) +{ + struct komeda_drv *mdrv = dev_get_drvdata(dev); + int res; + + res = drm_mode_config_helper_suspend(&mdrv->kms->base); + + if (!pm_runtime_status_suspended(dev)) + komeda_dev_suspend(mdrv->mdev); + + return res; +} + +static int __maybe_unused komeda_pm_resume(struct device *dev) +{ + struct komeda_drv *mdrv = dev_get_drvdata(dev); + int err = 0; + + if (!pm_runtime_status_suspended(dev)) + err = komeda_dev_resume(mdrv->mdev); + + return err ? err : drm_mode_config_helper_resume(&mdrv->kms->base); +} + +static const struct dev_pm_ops komeda_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(komeda_pm_suspend, komeda_pm_resume) + SET_RUNTIME_PM_OPS(komeda_rt_pm_suspend, komeda_rt_pm_resume, NULL) +}; + +static struct platform_driver komeda_platform_driver = { + .probe = komeda_platform_probe, + .remove = komeda_platform_remove, + .shutdown = komeda_platform_shutdown, + .driver = { + .name = "komeda", + .of_match_table = komeda_of_match, + .pm = &komeda_pm_ops, + }, +}; + +drm_module_platform_driver(komeda_platform_driver); + +MODULE_AUTHOR("James.Qian.Wang "); +MODULE_DESCRIPTION("Komeda KMS driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_event.c b/drivers/gpu/drm/arm/display/komeda/komeda_event.c new file mode 100644 index 000000000..53f944e66 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_event.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2019 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#include +#include + +#include "komeda_dev.h" + +struct komeda_str { + char *str; + u32 sz; + u32 len; +}; + +/* return 0 on success, < 0 on no space. + */ +__printf(2, 3) +static int komeda_sprintf(struct komeda_str *str, const char *fmt, ...) +{ + va_list args; + int num, free_sz; + int err; + + free_sz = str->sz - str->len - 1; + if (free_sz <= 0) + return -ENOSPC; + + va_start(args, fmt); + + num = vsnprintf(str->str + str->len, free_sz, fmt, args); + + va_end(args); + + if (num < free_sz) { + str->len += num; + err = 0; + } else { + str->len = str->sz - 1; + err = -ENOSPC; + } + + return err; +} + +static void evt_sprintf(struct komeda_str *str, u64 evt, const char *msg) +{ + if (evt) + komeda_sprintf(str, msg); +} + +static void evt_str(struct komeda_str *str, u64 events) +{ + if (events == 0ULL) { + komeda_sprintf(str, "None"); + return; + } + + evt_sprintf(str, events & KOMEDA_EVENT_VSYNC, "VSYNC|"); + evt_sprintf(str, events & KOMEDA_EVENT_FLIP, "FLIP|"); + evt_sprintf(str, events & KOMEDA_EVENT_EOW, "EOW|"); + evt_sprintf(str, events & KOMEDA_EVENT_MODE, "OP-MODE|"); + + evt_sprintf(str, events & KOMEDA_EVENT_URUN, "UNDERRUN|"); + evt_sprintf(str, events & KOMEDA_EVENT_OVR, "OVERRUN|"); + + /* GLB error */ + evt_sprintf(str, events & KOMEDA_ERR_MERR, "MERR|"); + evt_sprintf(str, events & KOMEDA_ERR_FRAMETO, "FRAMETO|"); + + /* DOU error */ + evt_sprintf(str, events & KOMEDA_ERR_DRIFTTO, "DRIFTTO|"); + evt_sprintf(str, events & KOMEDA_ERR_FRAMETO, "FRAMETO|"); + evt_sprintf(str, events & KOMEDA_ERR_TETO, "TETO|"); + evt_sprintf(str, events & KOMEDA_ERR_CSCE, "CSCE|"); + + /* LPU errors or events */ + evt_sprintf(str, events & KOMEDA_EVENT_IBSY, "IBSY|"); + evt_sprintf(str, events & KOMEDA_EVENT_EMPTY, "EMPTY|"); + evt_sprintf(str, events & KOMEDA_EVENT_FULL, "FULL|"); + evt_sprintf(str, events & KOMEDA_ERR_AXIE, "AXIE|"); + evt_sprintf(str, events & KOMEDA_ERR_ACE0, "ACE0|"); + evt_sprintf(str, events & KOMEDA_ERR_ACE1, "ACE1|"); + evt_sprintf(str, events & KOMEDA_ERR_ACE2, "ACE2|"); + evt_sprintf(str, events & KOMEDA_ERR_ACE3, "ACE3|"); + + /* LPU TBU errors*/ + evt_sprintf(str, events & KOMEDA_ERR_TCF, "TCF|"); + evt_sprintf(str, events & KOMEDA_ERR_TTNG, "TTNG|"); + evt_sprintf(str, events & KOMEDA_ERR_TITR, "TITR|"); + evt_sprintf(str, events & KOMEDA_ERR_TEMR, "TEMR|"); + evt_sprintf(str, events & KOMEDA_ERR_TTF, "TTF|"); + + /* CU errors*/ + evt_sprintf(str, events & KOMEDA_ERR_CPE, "COPROC|"); + evt_sprintf(str, events & KOMEDA_ERR_ZME, "ZME|"); + evt_sprintf(str, events & KOMEDA_ERR_CFGE, "CFGE|"); + evt_sprintf(str, events & KOMEDA_ERR_TEMR, "TEMR|"); + + if (str->len > 0 && (str->str[str->len - 1] == '|')) { + str->str[str->len - 1] = 0; + str->len--; + } +} + +static bool is_new_frame(struct komeda_events *a) +{ + return (a->pipes[0] | a->pipes[1]) & + (KOMEDA_EVENT_FLIP | KOMEDA_EVENT_EOW); +} + +void komeda_print_events(struct komeda_events *evts, struct drm_device *dev) +{ + u64 print_evts = 0; + static bool en_print = true; + struct komeda_dev *mdev = dev->dev_private; + u16 const err_verbosity = mdev->err_verbosity; + u64 evts_mask = evts->global | evts->pipes[0] | evts->pipes[1]; + + /* reduce the same msg print, only print the first evt for one frame */ + if (evts->global || is_new_frame(evts)) + en_print = true; + if (!(err_verbosity & KOMEDA_DEV_PRINT_DISABLE_RATELIMIT) && !en_print) + return; + + if (err_verbosity & KOMEDA_DEV_PRINT_ERR_EVENTS) + print_evts |= KOMEDA_ERR_EVENTS; + if (err_verbosity & KOMEDA_DEV_PRINT_WARN_EVENTS) + print_evts |= KOMEDA_WARN_EVENTS; + if (err_verbosity & KOMEDA_DEV_PRINT_INFO_EVENTS) + print_evts |= KOMEDA_INFO_EVENTS; + + if (evts_mask & print_evts) { + char msg[256]; + struct komeda_str str; + struct drm_printer p = drm_info_printer(dev->dev); + + str.str = msg; + str.sz = sizeof(msg); + str.len = 0; + + komeda_sprintf(&str, "gcu: "); + evt_str(&str, evts->global); + komeda_sprintf(&str, ", pipes[0]: "); + evt_str(&str, evts->pipes[0]); + komeda_sprintf(&str, ", pipes[1]: "); + evt_str(&str, evts->pipes[1]); + + DRM_ERROR("err detect: %s\n", msg); + if ((err_verbosity & KOMEDA_DEV_PRINT_DUMP_STATE_ON_EVENT) && + (evts_mask & (KOMEDA_ERR_EVENTS | KOMEDA_WARN_EVENTS))) + drm_state_dump(dev, &p); + + en_print = false; + } +} diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_format_caps.c b/drivers/gpu/drm/arm/display/komeda/komeda_format_caps.c new file mode 100644 index 000000000..c9a1edb9a --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_format_caps.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ + +#include +#include "komeda_format_caps.h" +#include "malidp_utils.h" + +const struct komeda_format_caps * +komeda_get_format_caps(struct komeda_format_caps_table *table, + u32 fourcc, u64 modifier) +{ + const struct komeda_format_caps *caps; + u64 afbc_features = modifier & ~(AFBC_FORMAT_MOD_BLOCK_SIZE_MASK); + u32 afbc_layout = modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK; + int id; + + for (id = 0; id < table->n_formats; id++) { + caps = &table->format_caps[id]; + + if (fourcc != caps->fourcc) + continue; + + if ((modifier == 0ULL) && (caps->supported_afbc_layouts == 0)) + return caps; + + if (has_bits(afbc_features, caps->supported_afbc_features) && + has_bit(afbc_layout, caps->supported_afbc_layouts)) + return caps; + } + + return NULL; +} + +u32 komeda_get_afbc_format_bpp(const struct drm_format_info *info, u64 modifier) +{ + u32 bpp; + + switch (info->format) { + case DRM_FORMAT_YUV420_8BIT: + bpp = 12; + break; + case DRM_FORMAT_YUV420_10BIT: + bpp = 15; + break; + default: + bpp = info->cpp[0] * 8; + break; + } + + return bpp; +} + +/* Two assumptions + * 1. RGB always has YTR + * 2. Tiled RGB always has SC + */ +u64 komeda_supported_modifiers[] = { + /* AFBC_16x16 + features: YUV+RGB both */ + AFBC_16x16(0), + /* SPARSE */ + AFBC_16x16(_SPARSE), + /* YTR + (SPARSE) */ + AFBC_16x16(_YTR | _SPARSE), + AFBC_16x16(_YTR), + /* SPLIT + SPARSE + YTR RGB only */ + /* split mode is only allowed for sparse mode */ + AFBC_16x16(_SPLIT | _SPARSE | _YTR), + /* TILED + (SPARSE) */ + /* TILED YUV format only */ + AFBC_16x16(_TILED | _SPARSE), + AFBC_16x16(_TILED), + /* TILED + SC + (SPLIT+SPARSE | SPARSE) + (YTR) */ + AFBC_16x16(_TILED | _SC | _SPLIT | _SPARSE | _YTR), + AFBC_16x16(_TILED | _SC | _SPARSE | _YTR), + AFBC_16x16(_TILED | _SC | _YTR), + /* AFBC_32x8 + features: which are RGB formats only */ + /* YTR + (SPARSE) */ + AFBC_32x8(_YTR | _SPARSE), + AFBC_32x8(_YTR), + /* SPLIT + SPARSE + (YTR) */ + /* split mode is only allowed for sparse mode */ + AFBC_32x8(_SPLIT | _SPARSE | _YTR), + /* TILED + SC + (SPLIT+SPARSE | SPARSE) + YTR */ + AFBC_32x8(_TILED | _SC | _SPLIT | _SPARSE | _YTR), + AFBC_32x8(_TILED | _SC | _SPARSE | _YTR), + AFBC_32x8(_TILED | _SC | _YTR), + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +bool komeda_format_mod_supported(struct komeda_format_caps_table *table, + u32 layer_type, u32 fourcc, u64 modifier, + u32 rot) +{ + const struct komeda_format_caps *caps; + + caps = komeda_get_format_caps(table, fourcc, modifier); + if (!caps) + return false; + + if (!(caps->supported_layer_types & layer_type)) + return false; + + if (table->format_mod_supported) + return table->format_mod_supported(caps, layer_type, modifier, + rot); + + return true; +} + +u32 *komeda_get_layer_fourcc_list(struct komeda_format_caps_table *table, + u32 layer_type, u32 *n_fmts) +{ + const struct komeda_format_caps *cap; + u32 *fmts; + int i, j, n = 0; + + fmts = kcalloc(table->n_formats, sizeof(u32), GFP_KERNEL); + if (!fmts) + return NULL; + + for (i = 0; i < table->n_formats; i++) { + cap = &table->format_caps[i]; + if (!(layer_type & cap->supported_layer_types) || + (cap->fourcc == 0)) + continue; + + /* one fourcc may has two caps items in table (afbc/none-afbc), + * so check the existing list to avoid adding a duplicated one. + */ + for (j = n - 1; j >= 0; j--) + if (fmts[j] == cap->fourcc) + break; + + if (j < 0) + fmts[n++] = cap->fourcc; + } + + if (n_fmts) + *n_fmts = n; + + return fmts; +} + +void komeda_put_fourcc_list(u32 *fourcc_list) +{ + kfree(fourcc_list); +} diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_format_caps.h b/drivers/gpu/drm/arm/display/komeda/komeda_format_caps.h new file mode 100644 index 000000000..cf7a183f7 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_format_caps.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ + +#ifndef _KOMEDA_FORMAT_CAPS_H_ +#define _KOMEDA_FORMAT_CAPS_H_ + +#include +#include +#include + +#define AFBC(x) DRM_FORMAT_MOD_ARM_AFBC(x) + +/* afbc layerout */ +#define AFBC_16x16(x) AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | (x)) +#define AFBC_32x8(x) AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_32x8 | (x)) + +/* afbc features */ +#define _YTR AFBC_FORMAT_MOD_YTR +#define _SPLIT AFBC_FORMAT_MOD_SPLIT +#define _SPARSE AFBC_FORMAT_MOD_SPARSE +#define _CBR AFBC_FORMAT_MOD_CBR +#define _TILED AFBC_FORMAT_MOD_TILED +#define _SC AFBC_FORMAT_MOD_SC + +/* layer_type */ +#define KOMEDA_FMT_RICH_LAYER BIT(0) +#define KOMEDA_FMT_SIMPLE_LAYER BIT(1) +#define KOMEDA_FMT_WB_LAYER BIT(2) + +#define AFBC_TH_LAYOUT_ALIGNMENT 8 +#define AFBC_HEADER_SIZE 16 +#define AFBC_SUPERBLK_ALIGNMENT 128 +#define AFBC_SUPERBLK_PIXELS 256 +#define AFBC_BODY_START_ALIGNMENT 1024 +#define AFBC_TH_BODY_START_ALIGNMENT 4096 + +/** + * struct komeda_format_caps + * + * komeda_format_caps is for describing ARM display specific features and + * limitations for a specific format, and format_caps will be linked into + * &komeda_framebuffer like a extension of &drm_format_info. + * + * NOTE: one fourcc may has two different format_caps items for fourcc and + * fourcc+modifier + * + * @hw_id: hw format id, hw specific value. + * @fourcc: drm fourcc format. + * @supported_layer_types: indicate which layer supports this format + * @supported_rots: allowed rotations for this format + * @supported_afbc_layouts: supported afbc layerout + * @supported_afbc_features: supported afbc features + */ +struct komeda_format_caps { + u32 hw_id; + u32 fourcc; + u32 supported_layer_types; + u32 supported_rots; + u32 supported_afbc_layouts; + u64 supported_afbc_features; +}; + +/** + * struct komeda_format_caps_table - format_caps mananger + * + * @n_formats: the size of format_caps list. + * @format_caps: format_caps list. + * @format_mod_supported: Optional. Some HW may have special requirements or + * limitations which can not be described by format_caps, this func supply HW + * the ability to do the further HW specific check. + */ +struct komeda_format_caps_table { + u32 n_formats; + const struct komeda_format_caps *format_caps; + bool (*format_mod_supported)(const struct komeda_format_caps *caps, + u32 layer_type, u64 modifier, u32 rot); +}; + +extern u64 komeda_supported_modifiers[]; + +const struct komeda_format_caps * +komeda_get_format_caps(struct komeda_format_caps_table *table, + u32 fourcc, u64 modifier); + +u32 komeda_get_afbc_format_bpp(const struct drm_format_info *info, + u64 modifier); + +u32 *komeda_get_layer_fourcc_list(struct komeda_format_caps_table *table, + u32 layer_type, u32 *n_fmts); + +void komeda_put_fourcc_list(u32 *fourcc_list); + +bool komeda_format_mod_supported(struct komeda_format_caps_table *table, + u32 layer_type, u32 fourcc, u64 modifier, + u32 rot); + +#endif diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.c b/drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.c new file mode 100644 index 000000000..50e86f352 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#include + +#include +#include +#include +#include +#include +#include + +#include "komeda_framebuffer.h" +#include "komeda_dev.h" + +static void komeda_fb_destroy(struct drm_framebuffer *fb) +{ + struct komeda_fb *kfb = to_kfb(fb); + u32 i; + + for (i = 0; i < fb->format->num_planes; i++) + drm_gem_object_put(fb->obj[i]); + + drm_framebuffer_cleanup(fb); + kfree(kfb); +} + +static int komeda_fb_create_handle(struct drm_framebuffer *fb, + struct drm_file *file, u32 *handle) +{ + return drm_gem_handle_create(file, fb->obj[0], handle); +} + +static const struct drm_framebuffer_funcs komeda_fb_funcs = { + .destroy = komeda_fb_destroy, + .create_handle = komeda_fb_create_handle, +}; + +static int +komeda_fb_afbc_size_check(struct komeda_fb *kfb, struct drm_file *file, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_framebuffer *fb = &kfb->base; + const struct drm_format_info *info = fb->format; + struct drm_gem_object *obj; + u32 alignment_w = 0, alignment_h = 0, alignment_header, n_blocks, bpp; + u64 min_size; + + obj = drm_gem_object_lookup(file, mode_cmd->handles[0]); + if (!obj) { + DRM_DEBUG_KMS("Failed to lookup GEM object\n"); + return -ENOENT; + } + + switch (fb->modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK) { + case AFBC_FORMAT_MOD_BLOCK_SIZE_32x8: + alignment_w = 32; + alignment_h = 8; + break; + case AFBC_FORMAT_MOD_BLOCK_SIZE_16x16: + alignment_w = 16; + alignment_h = 16; + break; + default: + WARN(1, "Invalid AFBC_FORMAT_MOD_BLOCK_SIZE: %lld.\n", + fb->modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK); + break; + } + + /* tiled header afbc */ + if (fb->modifier & AFBC_FORMAT_MOD_TILED) { + alignment_w *= AFBC_TH_LAYOUT_ALIGNMENT; + alignment_h *= AFBC_TH_LAYOUT_ALIGNMENT; + alignment_header = AFBC_TH_BODY_START_ALIGNMENT; + } else { + alignment_header = AFBC_BODY_START_ALIGNMENT; + } + + kfb->aligned_w = ALIGN(fb->width, alignment_w); + kfb->aligned_h = ALIGN(fb->height, alignment_h); + + if (fb->offsets[0] % alignment_header) { + DRM_DEBUG_KMS("afbc offset alignment check failed.\n"); + goto check_failed; + } + + n_blocks = (kfb->aligned_w * kfb->aligned_h) / AFBC_SUPERBLK_PIXELS; + kfb->offset_payload = ALIGN(n_blocks * AFBC_HEADER_SIZE, + alignment_header); + + bpp = komeda_get_afbc_format_bpp(info, fb->modifier); + kfb->afbc_size = kfb->offset_payload + n_blocks * + ALIGN(bpp * AFBC_SUPERBLK_PIXELS / 8, + AFBC_SUPERBLK_ALIGNMENT); + if (check_add_overflow(kfb->afbc_size, fb->offsets[0], &min_size)) { + goto check_failed; + } + if (min_size > obj->size) { + DRM_DEBUG_KMS("afbc size check failed, obj_size: 0x%zx. min_size 0x%llx.\n", + obj->size, min_size); + goto check_failed; + } + + fb->obj[0] = obj; + return 0; + +check_failed: + drm_gem_object_put(obj); + return -EINVAL; +} + +static int +komeda_fb_none_afbc_size_check(struct komeda_dev *mdev, struct komeda_fb *kfb, + struct drm_file *file, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_framebuffer *fb = &kfb->base; + const struct drm_format_info *info = fb->format; + struct drm_gem_object *obj; + u32 i, block_h; + u64 min_size; + + if (komeda_fb_check_src_coords(kfb, 0, 0, fb->width, fb->height)) + return -EINVAL; + + for (i = 0; i < info->num_planes; i++) { + obj = drm_gem_object_lookup(file, mode_cmd->handles[i]); + if (!obj) { + DRM_DEBUG_KMS("Failed to lookup GEM object\n"); + return -ENOENT; + } + fb->obj[i] = obj; + + block_h = drm_format_info_block_height(info, i); + if ((fb->pitches[i] * block_h) % mdev->chip.bus_width) { + DRM_DEBUG_KMS("Pitch[%d]: 0x%x doesn't align to 0x%x\n", + i, fb->pitches[i], mdev->chip.bus_width); + return -EINVAL; + } + + min_size = komeda_fb_get_pixel_addr(kfb, 0, fb->height, i) + - to_drm_gem_dma_obj(obj)->dma_addr; + if (obj->size < min_size) { + DRM_DEBUG_KMS("The fb->obj[%d] size: 0x%zx lower than the minimum requirement: 0x%llx.\n", + i, obj->size, min_size); + return -EINVAL; + } + } + + if (fb->format->num_planes == 3) { + if (fb->pitches[1] != fb->pitches[2]) { + DRM_DEBUG_KMS("The pitch[1] and [2] are not same\n"); + return -EINVAL; + } + } + + return 0; +} + +struct drm_framebuffer * +komeda_fb_create(struct drm_device *dev, struct drm_file *file, + const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct komeda_dev *mdev = dev->dev_private; + struct komeda_fb *kfb; + int ret = 0, i; + + kfb = kzalloc_obj(*kfb); + if (!kfb) + return ERR_PTR(-ENOMEM); + + kfb->format_caps = komeda_get_format_caps(&mdev->fmt_tbl, + mode_cmd->pixel_format, + mode_cmd->modifier[0]); + if (!kfb->format_caps) { + DRM_DEBUG_KMS("FMT %x is not supported.\n", + mode_cmd->pixel_format); + kfree(kfb); + return ERR_PTR(-EINVAL); + } + + drm_helper_mode_fill_fb_struct(dev, &kfb->base, info, mode_cmd); + + if (kfb->base.modifier) + ret = komeda_fb_afbc_size_check(kfb, file, mode_cmd); + else + ret = komeda_fb_none_afbc_size_check(mdev, kfb, file, mode_cmd); + if (ret < 0) + goto err_cleanup; + + ret = drm_framebuffer_init(dev, &kfb->base, &komeda_fb_funcs); + if (ret < 0) { + DRM_DEBUG_KMS("failed to initialize fb\n"); + + goto err_cleanup; + } + + kfb->is_va = mdev->iommu ? true : false; + + return &kfb->base; + +err_cleanup: + for (i = 0; i < kfb->base.format->num_planes; i++) + drm_gem_object_put(kfb->base.obj[i]); + + kfree(kfb); + return ERR_PTR(ret); +} + +int komeda_fb_check_src_coords(const struct komeda_fb *kfb, + u32 src_x, u32 src_y, u32 src_w, u32 src_h) +{ + const struct drm_framebuffer *fb = &kfb->base; + const struct drm_format_info *info = fb->format; + u32 block_w = drm_format_info_block_width(fb->format, 0); + u32 block_h = drm_format_info_block_height(fb->format, 0); + + if ((src_x + src_w > fb->width) || (src_y + src_h > fb->height)) { + DRM_DEBUG_ATOMIC("Invalid source coordinate.\n"); + return -EINVAL; + } + + if ((src_x % info->hsub) || (src_w % info->hsub) || + (src_y % info->vsub) || (src_h % info->vsub)) { + DRM_DEBUG_ATOMIC("Wrong subsampling dimension x:%d, y:%d, w:%d, h:%d for format: %x.\n", + src_x, src_y, src_w, src_h, info->format); + return -EINVAL; + } + + if ((src_x % block_w) || (src_w % block_w) || + (src_y % block_h) || (src_h % block_h)) { + DRM_DEBUG_ATOMIC("x:%d, y:%d, w:%d, h:%d should be multiple of block_w/h for format: %x.\n", + src_x, src_y, src_w, src_h, info->format); + return -EINVAL; + } + + return 0; +} + +dma_addr_t +komeda_fb_get_pixel_addr(struct komeda_fb *kfb, int x, int y, int plane) +{ + struct drm_framebuffer *fb = &kfb->base; + const struct drm_gem_dma_object *obj; + u32 offset, plane_x, plane_y, block_w, block_sz; + + if (plane >= fb->format->num_planes) { + DRM_DEBUG_KMS("Out of max plane num.\n"); + return -EINVAL; + } + + obj = drm_fb_dma_get_gem_obj(fb, plane); + + offset = fb->offsets[plane]; + if (!fb->modifier) { + block_w = drm_format_info_block_width(fb->format, plane); + block_sz = fb->format->char_per_block[plane]; + plane_x = x / (plane ? fb->format->hsub : 1); + plane_y = y / (plane ? fb->format->vsub : 1); + + offset += (plane_x / block_w) * block_sz + + plane_y * fb->pitches[plane]; + } + + return obj->dma_addr + offset; +} + +/* if the fb can be supported by a specific layer */ +bool komeda_fb_is_layer_supported(struct komeda_fb *kfb, u32 layer_type, + u32 rot) +{ + struct drm_framebuffer *fb = &kfb->base; + struct komeda_dev *mdev = fb->dev->dev_private; + u32 fourcc = fb->format->format; + u64 modifier = fb->modifier; + bool supported; + + supported = komeda_format_mod_supported(&mdev->fmt_tbl, layer_type, + fourcc, modifier, rot); + if (!supported) + DRM_DEBUG_ATOMIC("Layer TYPE: %d doesn't support fb FMT: %p4cc with modifier: 0x%llx.\n", + layer_type, &fourcc, modifier); + + return supported; +} diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.h b/drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.h new file mode 100644 index 000000000..02b2b8ae4 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#ifndef _KOMEDA_FRAMEBUFFER_H_ +#define _KOMEDA_FRAMEBUFFER_H_ + +#include +#include "komeda_format_caps.h" + +/** + * struct komeda_fb - Entending drm_framebuffer with komeda attribute + */ +struct komeda_fb { + /** @base: &drm_framebuffer */ + struct drm_framebuffer base; + /** + * @format_caps: + * extends drm_format_info for komeda specific information + */ + const struct komeda_format_caps *format_caps; + /** @is_va: if smmu is enabled, it will be true */ + bool is_va; + /** @aligned_w: aligned frame buffer width */ + u32 aligned_w; + /** @aligned_h: aligned frame buffer height */ + u32 aligned_h; + /** @afbc_size: minimum size of afbc */ + u32 afbc_size; + /** @offset_payload: start of afbc body buffer */ + u32 offset_payload; +}; + +#define to_kfb(dfb) container_of(dfb, struct komeda_fb, base) + +struct drm_framebuffer * +komeda_fb_create(struct drm_device *dev, struct drm_file *file, + const struct drm_format_info *info, + const struct drm_mode_fb_cmd2 *mode_cmd); +int komeda_fb_check_src_coords(const struct komeda_fb *kfb, + u32 src_x, u32 src_y, u32 src_w, u32 src_h); +dma_addr_t +komeda_fb_get_pixel_addr(struct komeda_fb *kfb, int x, int y, int plane); +bool komeda_fb_is_layer_supported(struct komeda_fb *kfb, u32 layer_type, + u32 rot); + +#endif diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_kms.c b/drivers/gpu/drm/arm/display/komeda/komeda_kms.c new file mode 100644 index 000000000..faa8b0413 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_kms.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "komeda_dev.h" +#include "komeda_framebuffer.h" +#include "komeda_kms.h" + +DEFINE_DRM_GEM_DMA_FOPS(komeda_cma_fops); + +static int komeda_gem_dma_dumb_create(struct drm_file *file, + struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + struct komeda_dev *mdev = dev->dev_private; + u32 pitch = DIV_ROUND_UP(args->width * args->bpp, 8); + + args->pitch = ALIGN(pitch, mdev->chip.bus_width); + + return drm_gem_dma_dumb_create_internal(file, dev, args); +} + +static irqreturn_t komeda_kms_irq_handler(int irq, void *data) +{ + struct drm_device *drm = data; + struct komeda_dev *mdev = drm->dev_private; + struct komeda_kms_dev *kms = to_kdev(drm); + struct komeda_events evts; + irqreturn_t status; + u32 i; + + /* Call into the CHIP to recognize events */ + memset(&evts, 0, sizeof(evts)); + status = mdev->funcs->irq_handler(mdev, &evts); + + komeda_print_events(&evts, drm); + + /* Notify the crtc to handle the events */ + for (i = 0; i < kms->n_crtcs; i++) + komeda_crtc_handle_event(&kms->crtcs[i], &evts); + + return status; +} + +static const struct drm_driver komeda_kms_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, + DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(komeda_gem_dma_dumb_create), + DRM_FBDEV_DMA_DRIVER_OPS, + .fops = &komeda_cma_fops, + .name = "komeda", + .desc = "Arm Komeda Display Processor driver", + .major = 0, + .minor = 1, +}; + +static void komeda_kms_atomic_commit_hw_done(struct drm_atomic_commit *state) +{ + struct drm_device *dev = state->dev; + struct komeda_kms_dev *kms = to_kdev(dev); + int i; + + for (i = 0; i < kms->n_crtcs; i++) { + struct komeda_crtc *kcrtc = &kms->crtcs[i]; + + if (kcrtc->base.state->active) { + struct completion *flip_done = NULL; + if (kcrtc->base.state->event) + flip_done = kcrtc->base.state->event->base.completion; + komeda_crtc_flush_and_wait_for_flip_done(kcrtc, flip_done); + } + } + drm_atomic_helper_commit_hw_done(state); +} + +static void komeda_kms_commit_tail(struct drm_atomic_commit *old_state) +{ + struct drm_device *dev = old_state->dev; + bool fence_cookie = dma_fence_begin_signalling(); + + drm_atomic_helper_commit_modeset_disables(dev, old_state); + + drm_atomic_helper_commit_planes(dev, old_state, + DRM_PLANE_COMMIT_ACTIVE_ONLY); + + drm_atomic_helper_commit_modeset_enables(dev, old_state); + + komeda_kms_atomic_commit_hw_done(old_state); + + drm_atomic_helper_wait_for_flip_done(dev, old_state); + + dma_fence_end_signalling(fence_cookie); + + drm_atomic_helper_cleanup_planes(dev, old_state); +} + +static const struct drm_mode_config_helper_funcs komeda_mode_config_helpers = { + .atomic_commit_tail = komeda_kms_commit_tail, +}; + +static int komeda_plane_state_list_add(struct drm_plane_state *plane_st, + struct list_head *zorder_list) +{ + struct komeda_plane_state *new = to_kplane_st(plane_st); + struct komeda_plane_state *node, *last; + + last = list_empty(zorder_list) ? + NULL : list_last_entry(zorder_list, typeof(*last), zlist_node); + + /* Considering the list sequence is zpos increasing, so if list is empty + * or the zpos of new node bigger than the last node in list, no need + * loop and just insert the new one to the tail of the list. + */ + if (!last || (new->base.zpos > last->base.zpos)) { + list_add_tail(&new->zlist_node, zorder_list); + return 0; + } + + /* Build the list by zpos increasing */ + list_for_each_entry(node, zorder_list, zlist_node) { + if (new->base.zpos < node->base.zpos) { + list_add_tail(&new->zlist_node, &node->zlist_node); + break; + } else if (node->base.zpos == new->base.zpos) { + struct drm_plane *a = node->base.plane; + struct drm_plane *b = new->base.plane; + + /* Komeda doesn't support setting a same zpos for + * different planes. + */ + DRM_DEBUG_ATOMIC("PLANE: %s and PLANE: %s are configured same zpos: %d.\n", + a->name, b->name, node->base.zpos); + return -EINVAL; + } + } + + return 0; +} + +static int komeda_crtc_normalize_zpos(struct drm_crtc *crtc, + struct drm_crtc_state *crtc_st) +{ + struct drm_atomic_commit *state = crtc_st->state; + struct komeda_crtc *kcrtc = to_kcrtc(crtc); + struct komeda_crtc_state *kcrtc_st = to_kcrtc_st(crtc_st); + struct komeda_plane_state *kplane_st; + struct drm_plane_state *plane_st; + struct drm_plane *plane; + struct list_head zorder_list; + int order = 0, err; + u32 slave_zpos = 0; + + DRM_DEBUG_ATOMIC("[CRTC:%d:%s] calculating normalized zpos values\n", + crtc->base.id, crtc->name); + + INIT_LIST_HEAD(&zorder_list); + + /* This loop also added all effected planes into the new state */ + drm_for_each_plane_mask(plane, crtc->dev, crtc_st->plane_mask) { + plane_st = drm_atomic_get_plane_state(state, plane); + if (IS_ERR(plane_st)) + return PTR_ERR(plane_st); + + /* Build a list by zpos increasing */ + err = komeda_plane_state_list_add(plane_st, &zorder_list); + if (err) + return err; + } + + kcrtc_st->max_slave_zorder = 0; + + list_for_each_entry(kplane_st, &zorder_list, zlist_node) { + plane_st = &kplane_st->base; + 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, + plane_st->zpos, plane_st->normalized_zpos); + + /* calculate max slave zorder */ + if (has_bit(drm_plane_index(plane), kcrtc->slave_planes)) { + slave_zpos = plane_st->normalized_zpos; + if (to_kplane_st(plane_st)->layer_split) + slave_zpos++; + kcrtc_st->max_slave_zorder = + max(slave_zpos, kcrtc_st->max_slave_zorder); + } + } + + crtc_st->zpos_changed = true; + + return 0; +} + +static int komeda_kms_check(struct drm_device *dev, + struct drm_atomic_commit *state) +{ + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_st; + int i, err; + + err = drm_atomic_helper_check_modeset(dev, state); + if (err) + return err; + + /* Komeda need to re-calculate resource assumption in every commit + * so need to add all affected_planes (even unchanged) to + * drm_atomic_commit. + */ + for_each_new_crtc_in_state(state, crtc, new_crtc_st, i) { + err = drm_atomic_add_affected_planes(state, crtc); + if (err) + return err; + + err = komeda_crtc_normalize_zpos(crtc, new_crtc_st); + if (err) + return err; + } + + err = drm_atomic_helper_check_planes(dev, state); + if (err) + return err; + + return 0; +} + +static const struct drm_mode_config_funcs komeda_mode_config_funcs = { + .fb_create = komeda_fb_create, + .atomic_check = komeda_kms_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static void komeda_kms_mode_config_init(struct komeda_kms_dev *kms, + struct komeda_dev *mdev) +{ + struct drm_mode_config *config = &kms->base.mode_config; + + drm_mode_config_init(&kms->base); + + komeda_kms_setup_crtcs(kms, mdev); + + /* Get value from dev */ + config->min_width = 0; + config->min_height = 0; + config->max_width = 4096; + config->max_height = 4096; + + config->funcs = &komeda_mode_config_funcs; + config->helper_private = &komeda_mode_config_helpers; +} + +struct komeda_kms_dev *komeda_kms_attach(struct komeda_dev *mdev) +{ + struct komeda_kms_dev *kms; + struct drm_device *drm; + int err; + + kms = devm_drm_dev_alloc(mdev->dev, &komeda_kms_driver, + struct komeda_kms_dev, base); + if (IS_ERR(kms)) + return kms; + + drm = &kms->base; + + drm->dev_private = mdev; + + komeda_kms_mode_config_init(kms, mdev); + + err = komeda_kms_add_private_objs(kms, mdev); + if (err) + goto cleanup_mode_config; + + err = komeda_kms_add_planes(kms, mdev); + if (err) + goto cleanup_mode_config; + + err = drm_vblank_init(drm, kms->n_crtcs); + if (err) + goto cleanup_mode_config; + + err = komeda_kms_add_crtcs(kms, mdev); + if (err) + goto cleanup_mode_config; + + err = komeda_kms_add_wb_connectors(kms, mdev); + if (err) + goto cleanup_mode_config; + + drm_mode_config_reset(drm); + + err = devm_request_irq(drm->dev, mdev->irq, + komeda_kms_irq_handler, IRQF_SHARED, + drm->driver->name, drm); + if (err) + goto cleanup_mode_config; + + drm_kms_helper_poll_init(drm); + + err = drm_dev_register(drm, 0); + if (err) + goto free_interrupts; + + return kms; + +free_interrupts: + drm_kms_helper_poll_fini(drm); +cleanup_mode_config: + drm_mode_config_cleanup(drm); + komeda_kms_cleanup_private_objs(kms); + drm->dev_private = NULL; + return ERR_PTR(err); +} + +void komeda_kms_detach(struct komeda_kms_dev *kms) +{ + struct drm_device *drm = &kms->base; + + drm_dev_unregister(drm); + drm_kms_helper_poll_fini(drm); + drm_atomic_helper_shutdown(drm); + drm_mode_config_cleanup(drm); + komeda_kms_cleanup_private_objs(kms); + drm->dev_private = NULL; +} + +void komeda_kms_shutdown(struct komeda_kms_dev *kms) +{ + struct drm_device *drm = &kms->base; + + drm_atomic_helper_shutdown(drm); +} diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_kms.h b/drivers/gpu/drm/arm/display/komeda/komeda_kms.h new file mode 100644 index 000000000..83e61c408 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_kms.h @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#ifndef _KOMEDA_KMS_H_ +#define _KOMEDA_KMS_H_ + +#include +#include +#include +#include +#include +#include +#include + +/** + * struct komeda_plane - komeda instance of drm_plane + */ +struct komeda_plane { + /** @base: &drm_plane */ + struct drm_plane base; + /** + * @layer: + * + * represents available layer input pipelines for this plane. + * + * NOTE: + * the layer is not for a specific Layer, but indicate a group of + * Layers with same capabilities. + */ + struct komeda_layer *layer; +}; + +/** + * struct komeda_plane_state + * + * The plane_state can be split into two data flow (left/right) and handled + * by two layers &komeda_plane.layer and &komeda_plane.layer.right + */ +struct komeda_plane_state { + /** @base: &drm_plane_state */ + struct drm_plane_state base; + /** @zlist_node: zorder list node */ + struct list_head zlist_node; + + /** @layer_split: on/off layer_split */ + u8 layer_split : 1; +}; + +/** + * struct komeda_wb_connector + */ +struct komeda_wb_connector { + /** @base: &drm_writeback_connector */ + struct drm_writeback_connector base; + + /** @wb_layer: represents associated writeback pipeline of komeda */ + struct komeda_layer *wb_layer; +}; + +/** + * struct komeda_crtc + */ +struct komeda_crtc { + /** @base: &drm_crtc */ + struct drm_crtc base; + /** @master: only master has display output */ + struct komeda_pipeline *master; + /** + * @slave: optional + * + * Doesn't have its own display output, the handled data flow will + * merge into the master. + */ + struct komeda_pipeline *slave; + + /** @slave_planes: komeda slave planes mask */ + u32 slave_planes; + + /** @wb_conn: komeda write back connector */ + struct komeda_wb_connector *wb_conn; + + /** @disable_done: this flip_done is for tracing the disable */ + struct completion *disable_done; + + /** @encoder: encoder at the end of the pipeline */ + struct drm_encoder encoder; +}; + +/** + * struct komeda_crtc_state + */ +struct komeda_crtc_state { + /** @base: &drm_crtc_state */ + struct drm_crtc_state base; + + /* private properties */ + + /* computed state which are used by validate/check */ + /** + * @affected_pipes: + * the affected pipelines in once display instance + */ + u32 affected_pipes; + /** + * @active_pipes: + * the active pipelines in once display instance + */ + u32 active_pipes; + + /** @clock_ratio: ratio of (aclk << 32)/pxlclk */ + u64 clock_ratio; + + /** @max_slave_zorder: the maximum of slave zorder */ + u32 max_slave_zorder; +}; + +/** struct komeda_kms_dev - for gather KMS related things */ +struct komeda_kms_dev { + /** @base: &drm_device */ + struct drm_device base; + + /** @n_crtcs: valid numbers of crtcs in &komeda_kms_dev.crtcs */ + int n_crtcs; + /** @crtcs: crtcs list */ + struct komeda_crtc crtcs[KOMEDA_MAX_PIPELINES]; +}; + +#define to_kplane(p) container_of(p, struct komeda_plane, base) +#define to_kplane_st(p) container_of(p, struct komeda_plane_state, base) +#define to_kconn(p) container_of(p, struct komeda_wb_connector, base) +#define to_kcrtc(p) container_of(p, struct komeda_crtc, base) +#define to_kcrtc_st(p) container_of(p, struct komeda_crtc_state, base) +#define to_kdev(p) container_of(p, struct komeda_kms_dev, base) +#define to_wb_conn(x) container_of(x, struct drm_writeback_connector, base) + +static inline bool is_writeback_only(struct drm_crtc_state *st) +{ + struct komeda_wb_connector *wb_conn = to_kcrtc(st->crtc)->wb_conn; + struct drm_connector *conn = wb_conn ? &wb_conn->base.base : NULL; + + return conn && (st->connector_mask == BIT(drm_connector_index(conn))); +} + +static inline bool +is_only_changed_connector(struct drm_crtc_state *st, struct drm_connector *conn) +{ + struct drm_crtc_state *old_st; + u32 changed_connectors; + + old_st = drm_atomic_get_old_crtc_state(st->state, st->crtc); + changed_connectors = st->connector_mask ^ old_st->connector_mask; + + 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); +} + +void komeda_crtc_get_color_config(struct drm_crtc_state *crtc_st, + u32 *color_depths, u32 *color_formats); +unsigned long komeda_crtc_get_aclk(struct komeda_crtc_state *kcrtc_st); + +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); +int komeda_kms_add_planes(struct komeda_kms_dev *kms, struct komeda_dev *mdev); +int komeda_kms_add_private_objs(struct komeda_kms_dev *kms, + struct komeda_dev *mdev); +int komeda_kms_add_wb_connectors(struct komeda_kms_dev *kms, + struct komeda_dev *mdev); +void komeda_kms_cleanup_private_objs(struct komeda_kms_dev *kms); + +void komeda_crtc_handle_event(struct komeda_crtc *kcrtc, + struct komeda_events *evts); +void komeda_crtc_flush_and_wait_for_flip_done(struct komeda_crtc *kcrtc, + struct completion *input_flip_done); + +struct komeda_kms_dev *komeda_kms_attach(struct komeda_dev *mdev); +void komeda_kms_detach(struct komeda_kms_dev *kms); +void komeda_kms_shutdown(struct komeda_kms_dev *kms); +void komeda_pipeline_dump(struct komeda_pipeline *pipe); + +#endif /*_KOMEDA_KMS_H_*/ diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.c b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.c new file mode 100644 index 000000000..81e244f0c --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#include +#include + +#include + +#include "komeda_dev.h" +#include "komeda_kms.h" +#include "komeda_pipeline.h" + +/** komeda_pipeline_add - Add a pipeline to &komeda_dev */ +struct komeda_pipeline * +komeda_pipeline_add(struct komeda_dev *mdev, size_t size, + const struct komeda_pipeline_funcs *funcs) +{ + struct komeda_pipeline *pipe; + + if (mdev->n_pipelines + 1 > KOMEDA_MAX_PIPELINES) { + DRM_ERROR("Exceed max support %d pipelines.\n", + KOMEDA_MAX_PIPELINES); + return ERR_PTR(-ENOSPC); + } + + if (size < sizeof(*pipe)) { + DRM_ERROR("Request pipeline size too small.\n"); + return ERR_PTR(-EINVAL); + } + + pipe = devm_kzalloc(mdev->dev, size, GFP_KERNEL); + if (!pipe) + return ERR_PTR(-ENOMEM); + + pipe->mdev = mdev; + pipe->id = mdev->n_pipelines; + pipe->funcs = funcs; + + mdev->pipelines[mdev->n_pipelines] = pipe; + mdev->n_pipelines++; + + return pipe; +} + +void komeda_pipeline_destroy(struct komeda_dev *mdev, + struct komeda_pipeline *pipe) +{ + struct komeda_component *c; + int i; + unsigned long avail_comps = pipe->avail_comps; + + for_each_set_bit(i, &avail_comps, 32) { + c = komeda_pipeline_get_component(pipe, i); + komeda_component_destroy(mdev, c); + } + + clk_put(pipe->pxlclk); + + of_node_put(pipe->of_output_links[0]); + of_node_put(pipe->of_output_links[1]); + of_node_put(pipe->of_output_port); + of_node_put(pipe->of_node); + + devm_kfree(mdev->dev, pipe); +} + +static struct komeda_component ** +komeda_pipeline_get_component_pos(struct komeda_pipeline *pipe, int id) +{ + struct komeda_dev *mdev = pipe->mdev; + struct komeda_pipeline *temp = NULL; + struct komeda_component **pos = NULL; + + switch (id) { + case KOMEDA_COMPONENT_LAYER0: + case KOMEDA_COMPONENT_LAYER1: + case KOMEDA_COMPONENT_LAYER2: + case KOMEDA_COMPONENT_LAYER3: + pos = to_cpos(pipe->layers[id - KOMEDA_COMPONENT_LAYER0]); + break; + case KOMEDA_COMPONENT_WB_LAYER: + pos = to_cpos(pipe->wb_layer); + break; + case KOMEDA_COMPONENT_COMPIZ0: + case KOMEDA_COMPONENT_COMPIZ1: + temp = mdev->pipelines[id - KOMEDA_COMPONENT_COMPIZ0]; + if (!temp) { + DRM_ERROR("compiz-%d doesn't exist.\n", id); + return NULL; + } + pos = to_cpos(temp->compiz); + break; + case KOMEDA_COMPONENT_SCALER0: + case KOMEDA_COMPONENT_SCALER1: + pos = to_cpos(pipe->scalers[id - KOMEDA_COMPONENT_SCALER0]); + break; + case KOMEDA_COMPONENT_SPLITTER: + pos = to_cpos(pipe->splitter); + 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]; + if (!temp) { + DRM_ERROR("ips-%d doesn't exist.\n", id); + return NULL; + } + pos = to_cpos(temp->improc); + break; + case KOMEDA_COMPONENT_TIMING_CTRLR: + pos = to_cpos(pipe->ctrlr); + break; + default: + pos = NULL; + DRM_ERROR("Unknown pipeline resource ID: %d.\n", id); + break; + } + + return pos; +} + +struct komeda_component * +komeda_pipeline_get_component(struct komeda_pipeline *pipe, int id) +{ + struct komeda_component **pos = NULL; + struct komeda_component *c = NULL; + + pos = komeda_pipeline_get_component_pos(pipe, id); + if (pos) + c = *pos; + + return c; +} + +struct komeda_component * +komeda_pipeline_get_first_component(struct komeda_pipeline *pipe, + u32 comp_mask) +{ + struct komeda_component *c = NULL; + unsigned long comp_mask_local = (unsigned long)comp_mask; + int id; + + id = find_first_bit(&comp_mask_local, 32); + if (id < 32) + c = komeda_pipeline_get_component(pipe, id); + + return c; +} + +static struct komeda_component * +komeda_component_pickup_input(struct komeda_component *c, u32 avail_comps) +{ + u32 avail_inputs = c->supported_inputs & (avail_comps); + + return komeda_pipeline_get_first_component(c->pipeline, avail_inputs); +} + +/** komeda_component_add - Add a component to &komeda_pipeline */ +struct komeda_component * +komeda_component_add(struct komeda_pipeline *pipe, + size_t comp_sz, u32 id, u32 hw_id, + const struct komeda_component_funcs *funcs, + u8 max_active_inputs, u32 supported_inputs, + u8 max_active_outputs, u32 __iomem *reg, + const char *name_fmt, ...) +{ + struct komeda_component **pos; + struct komeda_component *c; + int idx, *num = NULL; + + if (max_active_inputs > KOMEDA_COMPONENT_N_INPUTS) { + WARN(1, "please large KOMEDA_COMPONENT_N_INPUTS to %d.\n", + max_active_inputs); + return ERR_PTR(-ENOSPC); + } + + pos = komeda_pipeline_get_component_pos(pipe, id); + if (!pos || (*pos)) + return ERR_PTR(-EINVAL); + + if (has_bit(id, KOMEDA_PIPELINE_LAYERS)) { + idx = id - KOMEDA_COMPONENT_LAYER0; + num = &pipe->n_layers; + if (idx != pipe->n_layers) { + DRM_ERROR("please add Layer by id sequence.\n"); + return ERR_PTR(-EINVAL); + } + } else if (has_bit(id, KOMEDA_PIPELINE_SCALERS)) { + idx = id - KOMEDA_COMPONENT_SCALER0; + num = &pipe->n_scalers; + if (idx != pipe->n_scalers) { + DRM_ERROR("please add Scaler by id sequence.\n"); + return ERR_PTR(-EINVAL); + } + } + + c = devm_kzalloc(pipe->mdev->dev, comp_sz, GFP_KERNEL); + if (!c) + return ERR_PTR(-ENOMEM); + + c->id = id; + c->hw_id = hw_id; + c->reg = reg; + c->pipeline = pipe; + c->max_active_inputs = max_active_inputs; + c->max_active_outputs = max_active_outputs; + c->supported_inputs = supported_inputs; + c->funcs = funcs; + + if (name_fmt) { + va_list args; + + va_start(args, name_fmt); + vsnprintf(c->name, sizeof(c->name), name_fmt, args); + va_end(args); + } + + if (num) + *num = *num + 1; + + pipe->avail_comps |= BIT(c->id); + *pos = c; + + return c; +} + +void komeda_component_destroy(struct komeda_dev *mdev, + struct komeda_component *c) +{ + devm_kfree(mdev->dev, c); +} + +static void komeda_component_dump(struct komeda_component *c) +{ + if (!c) + return; + + DRM_DEBUG(" %s: ID %d-0x%08lx.\n", + c->name, c->id, BIT(c->id)); + DRM_DEBUG(" max_active_inputs:%d, supported_inputs: 0x%08x.\n", + c->max_active_inputs, c->supported_inputs); + DRM_DEBUG(" max_active_outputs:%d, supported_outputs: 0x%08x.\n", + c->max_active_outputs, c->supported_outputs); +} + +void komeda_pipeline_dump(struct komeda_pipeline *pipe) +{ + struct komeda_component *c; + int id; + unsigned long avail_comps = pipe->avail_comps; + + DRM_INFO("Pipeline-%d: n_layers: %d, n_scalers: %d, output: %s.\n", + pipe->id, pipe->n_layers, pipe->n_scalers, + pipe->dual_link ? "dual-link" : "single-link"); + DRM_INFO(" output_link[0]: %s.\n", + pipe->of_output_links[0] ? + pipe->of_output_links[0]->full_name : "none"); + DRM_INFO(" output_link[1]: %s.\n", + pipe->of_output_links[1] ? + pipe->of_output_links[1]->full_name : "none"); + + for_each_set_bit(id, &avail_comps, 32) { + c = komeda_pipeline_get_component(pipe, id); + + komeda_component_dump(c); + } +} + +static void komeda_component_verify_inputs(struct komeda_component *c) +{ + struct komeda_pipeline *pipe = c->pipeline; + struct komeda_component *input; + int id; + unsigned long supported_inputs = c->supported_inputs; + + for_each_set_bit(id, &supported_inputs, 32) { + input = komeda_pipeline_get_component(pipe, id); + if (!input) { + c->supported_inputs &= ~(BIT(id)); + DRM_WARN("Can not find input(ID-%d) for component: %s.\n", + id, c->name); + continue; + } + + input->supported_outputs |= BIT(c->id); + } +} + +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; + struct komeda_layer *layer; + int i, id; + unsigned long avail_comps = pipe->avail_comps; + + for_each_set_bit(id, &avail_comps, 32) { + 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); + } + + if (pipe->dual_link && !pipe->ctrlr->supports_dual_link) { + pipe->dual_link = false; + DRM_WARN("PIPE-%d doesn't support dual-link, ignore DT dual-link configuration.\n", + pipe->id); + } +} + +/* if pipeline_A accept another pipeline_B's component as input, treat + * pipeline_B as slave of pipeline_A. + */ +struct komeda_pipeline * +komeda_pipeline_get_slave(struct komeda_pipeline *master) +{ + struct komeda_component *slave; + + slave = komeda_component_pickup_input(&master->compiz->base, + KOMEDA_PIPELINE_COMPIZS); + + return slave ? slave->pipeline : NULL; +} + +int komeda_assemble_pipelines(struct komeda_dev *mdev) +{ + struct komeda_pipeline *pipe; + int i; + + for (i = 0; i < mdev->n_pipelines; i++) { + pipe = mdev->pipelines[i]; + + komeda_pipeline_assemble(pipe); + } + + return 0; +} + +void komeda_pipeline_dump_register(struct komeda_pipeline *pipe, + struct seq_file *sf) +{ + struct komeda_component *c; + u32 id; + unsigned long avail_comps; + + seq_printf(sf, "\n======== Pipeline-%d ==========\n", pipe->id); + + if (pipe->funcs && pipe->funcs->dump_register) + pipe->funcs->dump_register(pipe, sf); + + avail_comps = pipe->avail_comps; + for_each_set_bit(id, &avail_comps, 32) { + c = komeda_pipeline_get_component(pipe, id); + + seq_printf(sf, "\n------%s------\n", c->name); + if (c->funcs->dump_register) + c->funcs->dump_register(c, sf); + } +} diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h new file mode 100644 index 000000000..955f9f683 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h @@ -0,0 +1,568 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#ifndef _KOMEDA_PIPELINE_H_ +#define _KOMEDA_PIPELINE_H_ + +#include +#include +#include +#include "malidp_utils.h" +#include "komeda_color_mgmt.h" + +#define KOMEDA_MAX_PIPELINES 2 +#define KOMEDA_PIPELINE_MAX_LAYERS 4 +#define KOMEDA_PIPELINE_MAX_SCALERS 2 +#define KOMEDA_COMPONENT_N_INPUTS 5 + +/* pipeline component IDs */ +enum { + KOMEDA_COMPONENT_LAYER0 = 0, + KOMEDA_COMPONENT_LAYER1 = 1, + KOMEDA_COMPONENT_LAYER2 = 2, + KOMEDA_COMPONENT_LAYER3 = 3, + KOMEDA_COMPONENT_WB_LAYER = 7, /* write back layer */ + KOMEDA_COMPONENT_SCALER0 = 8, + KOMEDA_COMPONENT_SCALER1 = 9, + KOMEDA_COMPONENT_SPLITTER = 12, + KOMEDA_COMPONENT_MERGER = 14, + KOMEDA_COMPONENT_COMPIZ0 = 16, /* compositor */ + KOMEDA_COMPONENT_COMPIZ1 = 17, + KOMEDA_COMPONENT_IPS0 = 20, /* post image processor */ + KOMEDA_COMPONENT_IPS1 = 21, + KOMEDA_COMPONENT_TIMING_CTRLR = 22, /* timing controller */ +}; + +#define KOMEDA_PIPELINE_LAYERS (BIT(KOMEDA_COMPONENT_LAYER0) |\ + BIT(KOMEDA_COMPONENT_LAYER1) |\ + BIT(KOMEDA_COMPONENT_LAYER2) |\ + BIT(KOMEDA_COMPONENT_LAYER3)) + +#define KOMEDA_PIPELINE_SCALERS (BIT(KOMEDA_COMPONENT_SCALER0) |\ + BIT(KOMEDA_COMPONENT_SCALER1)) + +#define KOMEDA_PIPELINE_COMPIZS (BIT(KOMEDA_COMPONENT_COMPIZ0) |\ + BIT(KOMEDA_COMPONENT_COMPIZ1)) + +#define KOMEDA_PIPELINE_IMPROCS (BIT(KOMEDA_COMPONENT_IPS0) |\ + BIT(KOMEDA_COMPONENT_IPS1)) +struct komeda_component; +struct komeda_component_state; + +/** komeda_component_funcs - component control functions */ +struct komeda_component_funcs { + /** @validate: optional, + * component may has special requirements or limitations, this function + * supply HW the ability to do the further HW specific check. + */ + int (*validate)(struct komeda_component *c, + struct komeda_component_state *state); + /** @update: update is a active update */ + void (*update)(struct komeda_component *c, + struct komeda_component_state *state); + /** @disable: disable component */ + void (*disable)(struct komeda_component *c); + /** @dump_register: Optional, dump registers to seq_file */ + void (*dump_register)(struct komeda_component *c, struct seq_file *seq); +}; + +/** + * struct komeda_component + * + * struct komeda_component describe the data flow capabilities for how to link a + * component into the display pipeline. + * all specified components are subclass of this structure. + */ +struct komeda_component { + /** @obj: treat component as private obj */ + struct drm_private_obj obj; + /** @pipeline: the komeda pipeline this component belongs to */ + struct komeda_pipeline *pipeline; + /** @name: component name */ + char name[32]; + /** + * @reg: + * component register base, + * which is initialized by chip and used by chip only + */ + u32 __iomem *reg; + /** @id: component id */ + u32 id; + /** + * @hw_id: component hw id, + * which is initialized by chip and used by chip only + */ + u32 hw_id; + + /** + * @max_active_inputs: + * @max_active_outputs: + * + * maximum number of inputs/outputs that can be active at the same time + * Note: + * the number isn't the bit number of @supported_inputs or + * @supported_outputs, but may be less than it, since component may not + * support enabling all @supported_inputs/outputs at the same time. + */ + u8 max_active_inputs; + /** @max_active_outputs: maximum number of outputs */ + u8 max_active_outputs; + /** + * @supported_inputs: + * @supported_outputs: + * + * bitmask of BIT(component->id) for the supported inputs/outputs, + * describes the possibilities of how a component is linked into a + * pipeline. + */ + u32 supported_inputs; + /** @supported_outputs: bitmask of supported output componenet ids */ + u32 supported_outputs; + + /** + * @funcs: chip functions to access HW + */ + const struct komeda_component_funcs *funcs; +}; + +#define to_component(o) container_of(o, struct komeda_component, obj) + +/** + * struct komeda_component_output + * + * a component has multiple outputs, if want to know where the data + * comes from, only know the component is not enough, we still need to know + * its output port + */ +struct komeda_component_output { + /** @component: indicate which component the data comes from */ + struct komeda_component *component; + /** + * @output_port: + * the output port of the &komeda_component_output.component + */ + u8 output_port; +}; + +/** + * struct komeda_component_state + * + * component_state is the data flow configuration of the component, and it's + * the superclass of all specific component_state like @komeda_layer_state, + * @komeda_scaler_state + */ +struct komeda_component_state { + /** @obj: tracking component_state by drm_atomic_commit */ + struct drm_private_state obj; + /** @component: backpointer to the component */ + struct komeda_component *component; + /** + * @binding_user: + * currently bound user, the user can be @crtc, @plane or @wb_conn, + * which is valid decided by @component and @inputs + * + * - Layer: its user always is plane. + * - compiz/improc/timing_ctrlr: the user is crtc. + * - wb_layer: wb_conn; + * - scaler: plane when input is layer, wb_conn if input is compiz. + */ + union { + /** @crtc: backpointer for user crtc */ + struct drm_crtc *crtc; + /** @plane: backpointer for user plane */ + struct drm_plane *plane; + /** @wb_conn: backpointer for user wb_connector */ + struct drm_connector *wb_conn; + void *binding_user; + }; + + /** + * @active_inputs: + * + * active_inputs is bitmask of @inputs index + * + * - active_inputs = changed_active_inputs | unchanged_active_inputs + * - affected_inputs = old->active_inputs | new->active_inputs; + * - disabling_inputs = affected_inputs ^ active_inputs; + * - changed_inputs = disabling_inputs | changed_active_inputs; + * + * NOTE: + * changed_inputs doesn't include all active_input but only + * @changed_active_inputs, and this bitmask can be used in chip + * level for dirty update. + */ + u16 active_inputs; + /** @changed_active_inputs: bitmask of the changed @active_inputs */ + u16 changed_active_inputs; + /** @affected_inputs: bitmask for affected @inputs */ + u16 affected_inputs; + /** + * @inputs: + * + * the specific inputs[i] only valid on BIT(i) has been set in + * @active_inputs, if not the inputs[i] is undefined. + */ + struct komeda_component_output inputs[KOMEDA_COMPONENT_N_INPUTS]; +}; + +static inline u16 component_disabling_inputs(struct komeda_component_state *st) +{ + return st->affected_inputs ^ st->active_inputs; +} + +static inline u16 component_changed_inputs(struct komeda_component_state *st) +{ + return component_disabling_inputs(st) | st->changed_active_inputs; +} + +#define for_each_changed_input(st, i) \ + for ((i) = 0; (i) < (st)->component->max_active_inputs; (i)++) \ + if (has_bit((i), component_changed_inputs(st))) + +#define to_comp(__c) (((__c) == NULL) ? NULL : &((__c)->base)) +#define to_cpos(__c) ((struct komeda_component **)&(__c)) + +struct komeda_layer { + struct komeda_component base; + /* accepted h/v input range before rotation */ + struct malidp_range hsize_in, vsize_in; + u32 layer_type; /* RICH, SIMPLE or WB */ + u32 line_sz; + u32 yuv_line_sz; /* maximum line size for YUV422 and YUV420 */ + 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 { + struct komeda_component_state base; + /* layer specific configuration state */ + u16 hsize, vsize; + u32 rot; + u16 afbc_crop_l; + u16 afbc_crop_r; + u16 afbc_crop_t; + u16 afbc_crop_b; + dma_addr_t addr[3]; +}; + +struct komeda_scaler { + struct komeda_component base; + 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_split : 1, + right_part : 1; /* right part of split image */ +}; + +struct komeda_compiz { + struct komeda_component base; + struct malidp_range hsize, vsize; +}; + +struct komeda_compiz_input_cfg { + u16 hsize, vsize; + u16 hoffset, voffset; + u8 pixel_blend_mode, layer_alpha; +}; + +struct komeda_compiz_state { + struct komeda_component_state base; + /* composition size */ + u16 hsize, vsize; + 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_splitter { + struct komeda_component base; + struct malidp_range hsize, vsize; +}; + +struct komeda_splitter_state { + struct komeda_component_state base; + u16 hsize, vsize; + u16 overlap; +}; + +struct komeda_improc { + struct komeda_component base; + u32 supported_color_formats; /* BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444/YUV444/YUV420) */ + u32 supported_color_depths; /* BIT(8) | BIT(10)*/ + u8 supports_degamma : 1; + u8 supports_csc : 1; + u8 supports_gamma : 1; +}; + +struct komeda_improc_state { + struct komeda_component_state base; + enum drm_output_color_format color_format; + u8 color_depth; + u16 hsize, vsize; + u32 fgamma_coeffs[KOMEDA_N_GAMMA_COEFFS]; + u32 ctm_coeffs[KOMEDA_N_CTM_COEFFS]; +}; + +/* display timing controller */ +struct komeda_timing_ctrlr { + struct komeda_component base; + u8 supports_dual_link : 1; +}; + +struct komeda_timing_ctrlr_state { + struct komeda_component_state base; +}; + +/* Why define A separated structure but not use plane_state directly ? + * 1. Komeda supports layer_split which means a plane_state can be split and + * handled by two layers, one layer only handle half of plane image. + * 2. Fix up the user properties according to HW's capabilities, like user + * set rotation to R180, but HW only supports REFLECT_X+Y. the rot here is + * after drm_rotation_simplify() + */ +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 en_scaling : 1, + en_img_enhancement : 1, + en_split : 1, + is_yuv : 1, + right_part : 1; /* right part of display image if split enabled */ +}; + +struct komeda_pipeline_funcs { + /* check if the aclk (main engine clock) can satisfy the clock + * requirements of the downscaling that specified by dflow + */ + int (*downscaling_clk_check)(struct komeda_pipeline *pipe, + struct drm_display_mode *mode, + unsigned long aclk_rate, + struct komeda_data_flow_cfg *dflow); + /* dump_register: Optional, dump registers to seq_file */ + void (*dump_register)(struct komeda_pipeline *pipe, + struct seq_file *sf); +}; + +/** + * struct komeda_pipeline + * + * Represent a complete display pipeline and hold all functional components. + */ +struct komeda_pipeline { + /** @obj: link pipeline as private obj of drm_atomic_commit */ + struct drm_private_obj obj; + /** @mdev: the parent komeda_dev */ + struct komeda_dev *mdev; + /** @pxlclk: pixel clock */ + struct clk *pxlclk; + /** @id: pipeline id */ + int id; + /** @avail_comps: available components mask of pipeline */ + u32 avail_comps; + /** + * @standalone_disabled_comps: + * + * When disable the pipeline, some components can not be disabled + * together with others, but need a sparated and standalone disable. + * The standalone_disabled_comps are the components which need to be + * disabled standalone, and this concept also introduce concept of + * two phase. + * phase 1: for disabling the common components. + * phase 2: for disabling the standalong_disabled_comps. + */ + u32 standalone_disabled_comps; + /** @n_layers: the number of layer on @layers */ + int n_layers; + /** @layers: the pipeline layers */ + struct komeda_layer *layers[KOMEDA_PIPELINE_MAX_LAYERS]; + /** @n_scalers: the number of scaler on @scalers */ + int n_scalers; + /** @scalers: the pipeline scalers */ + struct komeda_scaler *scalers[KOMEDA_PIPELINE_MAX_SCALERS]; + /** @compiz: compositor */ + struct komeda_compiz *compiz; + /** @splitter: for split the compiz output to two half data flows */ + struct komeda_splitter *splitter; + /** @merger: merger */ + struct komeda_merger *merger; + /** @wb_layer: writeback layer */ + struct komeda_layer *wb_layer; + /** @improc: post image processor */ + struct komeda_improc *improc; + /** @ctrlr: timing controller */ + struct komeda_timing_ctrlr *ctrlr; + /** @funcs: chip private pipeline functions */ + const struct komeda_pipeline_funcs *funcs; + + /** @of_node: pipeline dt node */ + struct device_node *of_node; + /** @of_output_port: pipeline output port */ + struct device_node *of_output_port; + /** @of_output_links: output connector device nodes */ + struct device_node *of_output_links[2]; + /** @dual_link: true if of_output_links[0] and [1] are both valid */ + bool dual_link; +}; + +/** + * struct komeda_pipeline_state + * + * NOTE: + * Unlike the pipeline, pipeline_state doesn’t gather any component_state + * into it. It because all component will be managed by drm_atomic_commit. + */ +struct komeda_pipeline_state { + /** @obj: tracking pipeline_state by drm_atomic_commit */ + struct drm_private_state obj; + /** @pipe: backpointer to the pipeline */ + struct komeda_pipeline *pipe; + /** @crtc: currently bound crtc */ + struct drm_crtc *crtc; + /** + * @active_comps: + * + * bitmask - BIT(component->id) of active components + */ + u32 active_comps; +}; + +#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_splitter(c) container_of(c, struct komeda_splitter, 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_splitter_st(c) container_of(c, struct komeda_splitter_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) + +/* pipeline APIs */ +struct komeda_pipeline * +komeda_pipeline_add(struct komeda_dev *mdev, size_t size, + const struct komeda_pipeline_funcs *funcs); +void komeda_pipeline_destroy(struct komeda_dev *mdev, + struct komeda_pipeline *pipe); +struct komeda_pipeline * +komeda_pipeline_get_slave(struct komeda_pipeline *master); +int komeda_assemble_pipelines(struct komeda_dev *mdev); +struct komeda_component * +komeda_pipeline_get_component(struct komeda_pipeline *pipe, int id); +struct komeda_component * +komeda_pipeline_get_first_component(struct komeda_pipeline *pipe, + u32 comp_mask); + +void komeda_pipeline_dump_register(struct komeda_pipeline *pipe, + struct seq_file *sf); + +/* component APIs */ +extern __printf(10, 11) +struct komeda_component * +komeda_component_add(struct komeda_pipeline *pipe, + size_t comp_sz, u32 id, u32 hw_id, + const struct komeda_component_funcs *funcs, + u8 max_active_inputs, u32 supported_inputs, + u8 max_active_outputs, u32 __iomem *reg, + const char *name_fmt, ...); + +void komeda_component_destroy(struct komeda_dev *mdev, + struct komeda_component *c); + +static inline struct komeda_component * +komeda_component_pickup_output(struct komeda_component *c, u32 avail_comps) +{ + u32 avail_inputs = c->supported_outputs & (avail_comps); + + return komeda_pipeline_get_first_component(c->pipeline, avail_inputs); +} + +struct komeda_plane_state; +struct komeda_crtc_state; +struct komeda_crtc; + +void pipeline_composition_size(struct komeda_crtc_state *kcrtc_st, + u16 *hsize, u16 *vsize); + +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); +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); +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_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); + +int komeda_release_unclaimed_resources(struct komeda_pipeline *pipe, + struct komeda_crtc_state *kcrtc_st); + +struct komeda_pipeline_state * +komeda_pipeline_get_old_state(struct komeda_pipeline *pipe, + struct drm_atomic_commit *state); +bool komeda_pipeline_disable(struct komeda_pipeline *pipe, + struct drm_atomic_commit *old_state); +void komeda_pipeline_update(struct komeda_pipeline *pipe, + struct drm_atomic_commit *old_state); + +void komeda_complete_data_flow_cfg(struct komeda_layer *layer, + 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 new file mode 100644 index 000000000..457b21397 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_pipeline_state.c @@ -0,0 +1,1356 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ + +#include +#include +#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_commit *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_commit *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_commit *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_commit *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_commit *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_commit *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_commit *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_commit *state) +{ + struct komeda_pipeline_state *pipe_st; + u32 avail_scalers; + + pipe_st = komeda_pipeline_get_state(c->pipeline, state); + if (IS_ERR_OR_NULL(pipe_st)) + return NULL; + + avail_scalers = (pipe_st->active_comps & KOMEDA_PIPELINE_SCALERS) ^ + KOMEDA_PIPELINE_SCALERS; + + c = komeda_component_pickup_output(c, avail_scalers); + + return to_scaler(c); +} + +static void +komeda_rotate_data_flow(struct komeda_data_flow_cfg *dflow, u32 rot) +{ + if (drm_rotation_90_or_270(rot)) { + swap(dflow->in_h, dflow->in_w); + swap(dflow->total_in_h, dflow->total_in_w); + } +} + +static int +komeda_layer_check_cfg(struct komeda_layer *layer, + struct komeda_fb *kfb, + struct komeda_data_flow_cfg *dflow) +{ + u32 src_x, src_y, src_w, src_h; + u32 line_sz, max_line_sz; + + if (!komeda_fb_is_layer_supported(kfb, layer->layer_type, dflow->rot)) + return -EINVAL; + + if (layer->base.id == KOMEDA_COMPONENT_WB_LAYER) { + src_x = dflow->out_x; + src_y = dflow->out_y; + src_w = dflow->out_w; + src_h = dflow->out_h; + } else { + src_x = dflow->in_x; + src_y = dflow->in_y; + src_w = dflow->in_w; + src_h = dflow->in_h; + } + + if (komeda_fb_check_src_coords(kfb, src_x, src_y, src_w, src_h)) + return -EINVAL; + + if (!malidp_in_range(&layer->hsize_in, src_w)) { + DRM_DEBUG_ATOMIC("invalidate src_w %d.\n", src_w); + return -EINVAL; + } + + if (!malidp_in_range(&layer->vsize_in, src_h)) { + DRM_DEBUG_ATOMIC("invalidate src_h %d.\n", src_h); + return -EINVAL; + } + + if (drm_rotation_90_or_270(dflow->rot)) + line_sz = dflow->in_h; + else + line_sz = dflow->in_w; + + if (kfb->base.format->hsub > 1) + max_line_sz = layer->yuv_line_sz; + else + max_line_sz = layer->line_sz; + + if (line_sz > max_line_sz) { + DRM_DEBUG_ATOMIC("Required line_sz: %d exceeds the max size %d\n", + line_sz, max_line_sz); + return -EINVAL; + } + + return 0; +} + +static int +komeda_layer_validate(struct komeda_layer *layer, + struct komeda_plane_state *kplane_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_plane_state *plane_st = &kplane_st->base; + struct drm_framebuffer *fb = plane_st->fb; + struct komeda_fb *kfb = to_kfb(fb); + struct komeda_component_state *c_st; + struct komeda_layer_state *st; + int i, err; + + err = komeda_layer_check_cfg(layer, kfb, dflow); + if (err) + return err; + + c_st = komeda_component_get_state_and_set_user(&layer->base, + plane_st->state, plane_st->plane, plane_st->crtc); + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + st = to_layer_st(c_st); + + st->rot = dflow->rot; + + if (fb->modifier) { + st->hsize = kfb->aligned_w; + st->vsize = kfb->aligned_h; + st->afbc_crop_l = dflow->in_x; + st->afbc_crop_r = kfb->aligned_w - dflow->in_x - dflow->in_w; + st->afbc_crop_t = dflow->in_y; + st->afbc_crop_b = kfb->aligned_h - dflow->in_y - dflow->in_h; + } else { + st->hsize = dflow->in_w; + st->vsize = dflow->in_h; + st->afbc_crop_l = 0; + st->afbc_crop_r = 0; + st->afbc_crop_t = 0; + st->afbc_crop_b = 0; + } + + for (i = 0; i < fb->format->num_planes; i++) + st->addr[i] = komeda_fb_get_pixel_addr(kfb, dflow->in_x, + dflow->in_y, i); + + err = komeda_component_validate_private(&layer->base, c_st); + if (err) + return err; + + /* update the data flow for the next stage */ + komeda_component_set_output(&dflow->input, &layer->base, 0); + + /* + * The rotation has been handled by layer, so adjusted the data flow for + * the next stage. + */ + komeda_rotate_data_flow(dflow, st->rot); + + return 0; +} + +static int +komeda_wb_layer_validate(struct komeda_layer *wb_layer, + struct drm_connector_state *conn_st, + struct komeda_data_flow_cfg *dflow) +{ + struct komeda_fb *kfb = to_kfb(conn_st->writeback_job->fb); + struct komeda_component_state *c_st; + struct komeda_layer_state *st; + int i, err; + + err = komeda_layer_check_cfg(wb_layer, kfb, dflow); + if (err) + return err; + + c_st = komeda_component_get_state_and_set_user(&wb_layer->base, + conn_st->state, conn_st->connector, conn_st->crtc); + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + st = to_layer_st(c_st); + + st->hsize = dflow->out_w; + st->vsize = dflow->out_h; + + for (i = 0; i < kfb->base.format->num_planes; i++) + st->addr[i] = komeda_fb_get_pixel_addr(kfb, dflow->out_x, + dflow->out_y, i); + + komeda_component_add_input(&st->base, &dflow->input, 0); + komeda_component_set_output(&dflow->input, &wb_layer->base, 0); + + return 0; +} + +static bool scaling_ratio_valid(u32 size_in, u32 size_out, + u32 max_upscaling, u32 max_downscaling) +{ + if (size_out > size_in * max_upscaling) + return false; + else if (size_in > size_out * max_downscaling) + return false; + return true; +} + +static int +komeda_scaler_check_cfg(struct komeda_scaler *scaler, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + u32 hsize_in, vsize_in, hsize_out, vsize_out; + u32 max_upscaling; + + hsize_in = dflow->in_w; + vsize_in = dflow->in_h; + hsize_out = dflow->out_w; + vsize_out = dflow->out_h; + + if (!malidp_in_range(&scaler->hsize, hsize_in) || + !malidp_in_range(&scaler->hsize, hsize_out)) { + DRM_DEBUG_ATOMIC("Invalid horizontal sizes"); + return -EINVAL; + } + + if (!malidp_in_range(&scaler->vsize, vsize_in) || + !malidp_in_range(&scaler->vsize, vsize_out)) { + DRM_DEBUG_ATOMIC("Invalid vertical sizes"); + return -EINVAL; + } + + /* If input comes from compiz that means the scaling is for writeback + * and scaler can not do upscaling for writeback + */ + if (has_bit(dflow->input.component->id, KOMEDA_PIPELINE_COMPIZS)) + max_upscaling = 1; + else + max_upscaling = scaler->max_upscaling; + + if (!scaling_ratio_valid(hsize_in, hsize_out, max_upscaling, + scaler->max_downscaling)) { + DRM_DEBUG_ATOMIC("Invalid horizontal scaling ratio"); + return -EINVAL; + } + + if (!scaling_ratio_valid(vsize_in, vsize_out, max_upscaling, + scaler->max_downscaling)) { + DRM_DEBUG_ATOMIC("Invalid vertical scaling ratio"); + return -EINVAL; + } + + if (hsize_in > hsize_out || vsize_in > vsize_out) { + struct komeda_pipeline *pipe = scaler->base.pipeline; + int err; + + err = pipe->funcs->downscaling_clk_check(pipe, + &kcrtc_st->base.adjusted_mode, + komeda_crtc_get_aclk(kcrtc_st), dflow); + if (err) { + DRM_DEBUG_ATOMIC("aclk can't satisfy the clock requirement of the downscaling\n"); + return err; + } + } + + return 0; +} + +static int +komeda_scaler_validate(void *user, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_atomic_commit *drm_st = kcrtc_st->base.state; + struct komeda_component_state *c_st; + struct komeda_scaler_state *st; + struct komeda_scaler *scaler; + int err = 0; + + if (!(dflow->en_scaling || dflow->en_img_enhancement)) + return 0; + + scaler = komeda_component_get_avail_scaler(dflow->input.component, + drm_st); + if (!scaler) { + DRM_DEBUG_ATOMIC("No scaler available"); + return -EINVAL; + } + + err = komeda_scaler_check_cfg(scaler, kcrtc_st, dflow); + if (err) + return err; + + c_st = komeda_component_get_state_and_set_user(&scaler->base, + drm_st, user, kcrtc_st->base.crtc); + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + st = to_scaler_st(c_st); + + st->hsize_in = dflow->in_w; + st->vsize_in = dflow->in_h; + st->hsize_out = dflow->out_w; + st->vsize_out = dflow->out_h; + st->right_crop = dflow->right_crop; + st->left_crop = dflow->left_crop; + st->total_vsize_in = dflow->total_in_h; + st->total_hsize_in = dflow->total_in_w; + st->total_hsize_out = dflow->total_out_w; + + /* Enable alpha processing if the next stage needs the pixel alpha */ + st->en_alpha = dflow->pixel_blend_mode != DRM_MODE_BLEND_PIXEL_NONE; + st->en_scaling = dflow->en_scaling; + st->en_img_enhancement = dflow->en_img_enhancement; + st->en_split = dflow->en_split; + st->right_part = dflow->right_part; + + komeda_component_add_input(&st->base, &dflow->input, 0); + komeda_component_set_output(&dflow->input, &scaler->base, 0); + return err; +} + +static void komeda_split_data_flow(struct komeda_scaler *scaler, + struct komeda_data_flow_cfg *dflow, + struct komeda_data_flow_cfg *l_dflow, + struct komeda_data_flow_cfg *r_dflow); + +static int +komeda_splitter_validate(struct komeda_splitter *splitter, + struct drm_connector_state *conn_st, + struct komeda_data_flow_cfg *dflow, + struct komeda_data_flow_cfg *l_output, + struct komeda_data_flow_cfg *r_output) +{ + struct komeda_component_state *c_st; + struct komeda_splitter_state *st; + + if (!splitter) { + DRM_DEBUG_ATOMIC("Current HW doesn't support splitter.\n"); + return -EINVAL; + } + + if (!malidp_in_range(&splitter->hsize, dflow->in_w)) { + DRM_DEBUG_ATOMIC("split in_w:%d is out of the acceptable range.\n", + dflow->in_w); + return -EINVAL; + } + + if (!malidp_in_range(&splitter->vsize, dflow->in_h)) { + DRM_DEBUG_ATOMIC("split in_h: %d exceeds the acceptable range.\n", + dflow->in_h); + return -EINVAL; + } + + c_st = komeda_component_get_state_and_set_user(&splitter->base, + conn_st->state, conn_st->connector, conn_st->crtc); + + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + komeda_split_data_flow(splitter->base.pipeline->scalers[0], + dflow, l_output, r_output); + + st = to_splitter_st(c_st); + st->hsize = dflow->in_w; + st->vsize = dflow->in_h; + st->overlap = dflow->overlap; + + komeda_component_add_input(&st->base, &dflow->input, 0); + komeda_component_set_output(&l_output->input, &splitter->base, 0); + komeda_component_set_output(&r_output->input, &splitter->base, 1); + + return 0; +} + +static int +komeda_merger_validate(struct komeda_merger *merger, + void *user, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *left_input, + struct komeda_data_flow_cfg *right_input, + struct komeda_data_flow_cfg *output) +{ + struct komeda_component_state *c_st; + struct komeda_merger_state *st; + int err = 0; + + if (!merger) { + DRM_DEBUG_ATOMIC("No merger is available"); + return -EINVAL; + } + + if (!malidp_in_range(&merger->hsize_merged, output->out_w)) { + DRM_DEBUG_ATOMIC("merged_w: %d is out of the accepted range.\n", + output->out_w); + return -EINVAL; + } + + if (!malidp_in_range(&merger->vsize_merged, output->out_h)) { + DRM_DEBUG_ATOMIC("merged_h: %d is out of the accepted range.\n", + output->out_h); + return -EINVAL; + } + + c_st = komeda_component_get_state_and_set_user(&merger->base, + kcrtc_st->base.state, kcrtc_st->base.crtc, kcrtc_st->base.crtc); + + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + st = to_merger_st(c_st); + st->hsize_merged = output->out_w; + st->vsize_merged = output->out_h; + + komeda_component_add_input(c_st, &left_input->input, 0); + komeda_component_add_input(c_st, &right_input->input, 1); + komeda_component_set_output(&output->input, &merger->base, 0); + + return err; +} + +void pipeline_composition_size(struct komeda_crtc_state *kcrtc_st, + u16 *hsize, u16 *vsize) +{ + struct drm_display_mode *m = &kcrtc_st->base.adjusted_mode; + + if (hsize) + *hsize = m->hdisplay; + if (vsize) + *vsize = m->vdisplay; +} + +static int +komeda_compiz_set_input(struct komeda_compiz *compiz, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_atomic_commit *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 = __ffs(avail_formats); + } + + if (kcrtc_st->base.color_mgmt_changed) { + drm_lut_to_fgamma_coeffs(kcrtc_st->base.gamma_lut, + st->fgamma_coeffs); + drm_ctm_to_coeffs(kcrtc_st->base.ctm, st->ctm_coeffs); + } + + komeda_component_add_input(&st->base, &dflow->input, 0); + komeda_component_set_output(&dflow->input, &improc->base, 0); + + return 0; +} + +static int +komeda_timing_ctrlr_validate(struct komeda_timing_ctrlr *ctrlr, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_crtc *crtc = kcrtc_st->base.crtc; + struct komeda_timing_ctrlr_state *st; + struct komeda_component_state *c_st; + + c_st = komeda_component_get_state_and_set_user(&ctrlr->base, + kcrtc_st->base.state, crtc, crtc); + if (IS_ERR(c_st)) + return PTR_ERR(c_st); + + st = to_ctrlr_st(c_st); + + komeda_component_add_input(&st->base, &dflow->input, 0); + komeda_component_set_output(&dflow->input, &ctrlr->base, 0); + + return 0; +} + +void komeda_complete_data_flow_cfg(struct komeda_layer *layer, + struct komeda_data_flow_cfg *dflow, + struct drm_framebuffer *fb) +{ + struct komeda_scaler *scaler = layer->base.pipeline->scalers[0]; + u32 w = dflow->in_w; + u32 h = dflow->in_h; + + dflow->total_in_w = dflow->in_w; + dflow->total_in_h = dflow->in_h; + dflow->total_out_w = dflow->out_w; + + /* if format doesn't have alpha, fix blend mode to PIXEL_NONE */ + if (!fb->format->has_alpha) + dflow->pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; + + if (drm_rotation_90_or_270(dflow->rot)) + swap(w, h); + + dflow->en_scaling = (w != dflow->out_w) || (h != dflow->out_h); + dflow->is_yuv = fb->format->is_yuv; + + /* try to enable image enhancer if data flow is a 2x+ upscaling */ + dflow->en_img_enhancement = dflow->out_w >= 2 * w || + dflow->out_h >= 2 * h; + + /* try to enable split if scaling exceed the scaler's acceptable + * input/output range. + */ + if (dflow->en_scaling && scaler) + dflow->en_split = !malidp_in_range(&scaler->hsize, dflow->in_w) || + !malidp_in_range(&scaler->hsize, dflow->out_w); +} + +static bool merger_is_available(struct komeda_pipeline *pipe, + struct komeda_data_flow_cfg *dflow) +{ + u32 avail_inputs = pipe->merger ? + pipe->merger->base.supported_inputs : 0; + + return has_bit(dflow->input.component->id, avail_inputs); +} + +int komeda_build_layer_data_flow(struct komeda_layer *layer, + struct komeda_plane_state *kplane_st, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_plane *plane = kplane_st->base.plane; + struct komeda_pipeline *pipe = layer->base.pipeline; + int err; + + DRM_DEBUG_ATOMIC("%s handling [PLANE:%d:%s]: src[x/y:%d/%d, w/h:%d/%d] disp[x/y:%d/%d, w/h:%d/%d]", + layer->base.name, plane->base.id, plane->name, + dflow->in_x, dflow->in_y, dflow->in_w, dflow->in_h, + dflow->out_x, dflow->out_y, dflow->out_w, dflow->out_h); + + err = komeda_layer_validate(layer, kplane_st, dflow); + if (err) + return err; + + err = komeda_scaler_validate(plane, kcrtc_st, dflow); + if (err) + return err; + + /* if split, check if can put the data flow into merger */ + if (dflow->en_split && merger_is_available(pipe, dflow)) + return 0; + + err = komeda_compiz_set_input(pipe->compiz, kcrtc_st, dflow); + + return err; +} + +/* + * Split is introduced for workaround scaler's input/output size limitation. + * The idea is simple, if one scaler can not fit the requirement, use two. + * So split splits the big source image to two half parts (left/right) and do + * the scaling by two scaler separately and independently. + * But split also imports an edge problem in the middle of the image when + * scaling, to avoid it, split isn't a simple half-and-half, but add an extra + * pixels (overlap) to both side, after split the left/right will be: + * - left: [0, src_length/2 + overlap] + * - right: [src_length/2 - overlap, src_length] + * The extra overlap do eliminate the edge problem, but which may also generates + * unnecessary pixels when scaling, we need to crop them before scaler output + * the result to the next stage. and for the how to crop, it depends on the + * unneeded pixels, another words the position where overlay has been added. + * - left: crop the right + * - right: crop the left + * + * The diagram for how to do the split + * + * <---------------------left->out_w ----------------> + * |--------------------------------|---right_crop-----| <- left after split + * \ \ / + * \ \<--overlap--->/ + * |-----------------|-------------|(Middle)------|-----------------| <- src + * /<---overlap--->\ \ + * / \ \ + * right after split->|-----left_crop---|--------------------------------| + * ^<------------------- right->out_w --------------->^ + * + * NOTE: To consistent with HW the output_w always contains the crop size. + */ + +static void komeda_split_data_flow(struct komeda_scaler *scaler, + struct komeda_data_flow_cfg *dflow, + struct komeda_data_flow_cfg *l_dflow, + struct komeda_data_flow_cfg *r_dflow) +{ + bool r90 = drm_rotation_90_or_270(dflow->rot); + bool flip_h = has_flip_h(dflow->rot); + u32 l_out, r_out, overlap; + + memcpy(l_dflow, dflow, sizeof(*dflow)); + memcpy(r_dflow, dflow, sizeof(*dflow)); + + l_dflow->right_part = false; + r_dflow->right_part = true; + r_dflow->blending_zorder = dflow->blending_zorder + 1; + + overlap = 0; + if (dflow->en_scaling && scaler) + overlap += scaler->scaling_split_overlap; + + /* original dflow may fed into splitter, and which doesn't need + * enhancement overlap + */ + dflow->overlap = overlap; + + if (dflow->en_img_enhancement && scaler) + overlap += scaler->enh_split_overlap; + + l_dflow->overlap = overlap; + r_dflow->overlap = overlap; + + /* split the origin content */ + /* left/right here always means the left/right part of display image, + * not the source Image + */ + /* DRM rotation is anti-clockwise */ + if (r90) { + if (dflow->en_scaling) { + l_dflow->in_h = ALIGN(dflow->in_h, 2) / 2 + l_dflow->overlap; + r_dflow->in_h = l_dflow->in_h; + } else if (dflow->en_img_enhancement) { + /* enhancer only */ + l_dflow->in_h = ALIGN(dflow->in_h, 2) / 2 + l_dflow->overlap; + r_dflow->in_h = dflow->in_h / 2 + r_dflow->overlap; + } else { + /* split without scaler, no overlap */ + l_dflow->in_h = ALIGN(((dflow->in_h + 1) >> 1), 2); + r_dflow->in_h = dflow->in_h - l_dflow->in_h; + } + + /* Consider YUV format, after split, the split source w/h + * may not aligned to 2. we have two choices for such case. + * 1. scaler is enabled (overlap != 0), we can do a alignment + * both left/right and crop the extra data by scaler. + * 2. scaler is not enabled, only align the split left + * src/disp, and the rest part assign to right + */ + if ((overlap != 0) && dflow->is_yuv) { + l_dflow->in_h = ALIGN(l_dflow->in_h, 2); + r_dflow->in_h = ALIGN(r_dflow->in_h, 2); + } + + if (flip_h) + l_dflow->in_y = dflow->in_y + dflow->in_h - l_dflow->in_h; + else + r_dflow->in_y = dflow->in_y + dflow->in_h - r_dflow->in_h; + } else { + if (dflow->en_scaling) { + l_dflow->in_w = ALIGN(dflow->in_w, 2) / 2 + l_dflow->overlap; + r_dflow->in_w = l_dflow->in_w; + } else if (dflow->en_img_enhancement) { + l_dflow->in_w = ALIGN(dflow->in_w, 2) / 2 + l_dflow->overlap; + r_dflow->in_w = dflow->in_w / 2 + r_dflow->overlap; + } else { + l_dflow->in_w = ALIGN(((dflow->in_w + 1) >> 1), 2); + r_dflow->in_w = dflow->in_w - l_dflow->in_w; + } + + /* do YUV alignment when scaler enabled */ + if ((overlap != 0) && dflow->is_yuv) { + l_dflow->in_w = ALIGN(l_dflow->in_w, 2); + r_dflow->in_w = ALIGN(r_dflow->in_w, 2); + } + + /* on flip_h, the left display content from the right-source */ + if (flip_h) + l_dflow->in_x = dflow->in_w + dflow->in_x - l_dflow->in_w; + else + r_dflow->in_x = dflow->in_w + dflow->in_x - r_dflow->in_w; + } + + /* split the disp_rect */ + if (dflow->en_scaling || dflow->en_img_enhancement) + l_dflow->out_w = ((dflow->out_w + 1) >> 1); + else + l_dflow->out_w = ALIGN(((dflow->out_w + 1) >> 1), 2); + + r_dflow->out_w = dflow->out_w - l_dflow->out_w; + + l_dflow->out_x = dflow->out_x; + r_dflow->out_x = l_dflow->out_w + l_dflow->out_x; + + /* calculate the scaling crop */ + /* left scaler output more data and do crop */ + if (r90) { + l_out = (dflow->out_w * l_dflow->in_h) / dflow->in_h; + r_out = (dflow->out_w * r_dflow->in_h) / dflow->in_h; + } else { + l_out = (dflow->out_w * l_dflow->in_w) / dflow->in_w; + r_out = (dflow->out_w * r_dflow->in_w) / dflow->in_w; + } + + l_dflow->left_crop = 0; + l_dflow->right_crop = l_out - l_dflow->out_w; + r_dflow->left_crop = r_out - r_dflow->out_w; + r_dflow->right_crop = 0; + + /* out_w includes the crop length */ + l_dflow->out_w += l_dflow->right_crop + l_dflow->left_crop; + r_dflow->out_w += r_dflow->right_crop + r_dflow->left_crop; +} + +/* For layer split, a plane state will be split to two data flows and handled + * by two separated komeda layer input pipelines. komeda supports two types of + * layer split: + * - none-scaling split: + * / layer-left -> \ + * plane_state compiz-> ... + * \ layer-right-> / + * + * - scaling split: + * / layer-left -> scaler->\ + * plane_state merger -> compiz-> ... + * \ layer-right-> scaler->/ + * + * Since merger only supports scaler as input, so for none-scaling split, two + * layer data flows will be output to compiz directly. for scaling_split, two + * data flow will be merged by merger firstly, then merger outputs one merged + * data flow to compiz. + */ +int komeda_build_layer_split_data_flow(struct komeda_layer *left, + struct komeda_plane_state *kplane_st, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_plane *plane = kplane_st->base.plane; + struct komeda_pipeline *pipe = left->base.pipeline; + struct komeda_layer *right = left->right; + struct komeda_data_flow_cfg l_dflow, r_dflow; + int err; + + komeda_split_data_flow(pipe->scalers[0], dflow, &l_dflow, &r_dflow); + + DRM_DEBUG_ATOMIC("Assign %s + %s to [PLANE:%d:%s]: " + "src[x/y:%d/%d, w/h:%d/%d] disp[x/y:%d/%d, w/h:%d/%d]", + left->base.name, right->base.name, + plane->base.id, plane->name, + dflow->in_x, dflow->in_y, dflow->in_w, dflow->in_h, + dflow->out_x, dflow->out_y, dflow->out_w, dflow->out_h); + + err = komeda_build_layer_data_flow(left, kplane_st, kcrtc_st, &l_dflow); + if (err) + return err; + + err = komeda_build_layer_data_flow(right, kplane_st, kcrtc_st, &r_dflow); + if (err) + return err; + + /* The rotation has been handled by layer, so adjusted the data flow */ + komeda_rotate_data_flow(dflow, dflow->rot); + + /* left and right dflow has been merged to compiz already, + * no need merger to merge them anymore. + */ + if (r_dflow.input.component == l_dflow.input.component) + return 0; + + /* line merger path */ + err = komeda_merger_validate(pipe->merger, plane, kcrtc_st, + &l_dflow, &r_dflow, dflow); + if (err) + return err; + + err = komeda_compiz_set_input(pipe->compiz, kcrtc_st, dflow); + + return err; +} + +/* writeback data path: compiz -> scaler -> wb_layer -> memory */ +int komeda_build_wb_data_flow(struct komeda_layer *wb_layer, + struct drm_connector_state *conn_st, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct drm_connector *conn = conn_st->connector; + int err; + + err = komeda_scaler_validate(conn, kcrtc_st, dflow); + if (err) + return err; + + return komeda_wb_layer_validate(wb_layer, conn_st, dflow); +} + +/* writeback scaling split data path: + * /-> scaler ->\ + * compiz -> splitter merger -> wb_layer -> memory + * \-> scaler ->/ + */ +int komeda_build_wb_split_data_flow(struct komeda_layer *wb_layer, + struct drm_connector_state *conn_st, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct komeda_pipeline *pipe = wb_layer->base.pipeline; + struct drm_connector *conn = conn_st->connector; + struct komeda_data_flow_cfg l_dflow, r_dflow; + int err; + + err = komeda_splitter_validate(pipe->splitter, conn_st, + dflow, &l_dflow, &r_dflow); + if (err) + return err; + err = komeda_scaler_validate(conn, kcrtc_st, &l_dflow); + if (err) + return err; + + err = komeda_scaler_validate(conn, kcrtc_st, &r_dflow); + if (err) + return err; + + err = komeda_merger_validate(pipe->merger, conn_st, kcrtc_st, + &l_dflow, &r_dflow, dflow); + if (err) + return err; + + return komeda_wb_layer_validate(wb_layer, conn_st, dflow); +} + +/* build display output data flow, the data path is: + * compiz -> improc -> timing_ctrlr + */ +int komeda_build_display_data_flow(struct komeda_crtc *kcrtc, + struct komeda_crtc_state *kcrtc_st) +{ + struct komeda_pipeline *master = kcrtc->master; + struct komeda_pipeline *slave = kcrtc->slave; + struct komeda_data_flow_cfg m_dflow; /* master data flow */ + struct komeda_data_flow_cfg s_dflow; /* slave data flow */ + int err; + + memset(&m_dflow, 0, sizeof(m_dflow)); + memset(&s_dflow, 0, sizeof(s_dflow)); + + if (slave && has_bit(slave->id, kcrtc_st->active_pipes)) { + err = komeda_compiz_validate(slave->compiz, kcrtc_st, &s_dflow); + if (err) + return err; + + /* merge the slave dflow into master pipeline */ + err = komeda_compiz_set_input(master->compiz, kcrtc_st, + &s_dflow); + if (err) + return err; + } + + err = komeda_compiz_validate(master->compiz, kcrtc_st, &m_dflow); + if (err) + return err; + + err = komeda_improc_validate(master->improc, kcrtc_st, &m_dflow); + if (err) + return err; + + err = komeda_timing_ctrlr_validate(master->ctrlr, kcrtc_st, &m_dflow); + if (err) + return err; + + return 0; +} + +static int +komeda_pipeline_unbound_components(struct komeda_pipeline *pipe, + struct komeda_pipeline_state *new) +{ + struct drm_atomic_commit *drm_st = new->obj.state; + struct komeda_pipeline_state *old = priv_to_pipe_st(pipe->obj.state); + struct komeda_component_state *c_st; + struct komeda_component *c; + u32 id; + unsigned long disabling_comps; + + WARN_ON(!old); + + disabling_comps = (~new->active_comps) & old->active_comps; + + /* unbound all disabling component */ + for_each_set_bit(id, &disabling_comps, 32) { + c = komeda_pipeline_get_component(pipe, id); + c_st = komeda_component_get_state_and_set_user(c, + drm_st, NULL, new->crtc); + if (PTR_ERR(c_st) == -EDEADLK) + return -EDEADLK; + WARN_ON(IS_ERR(c_st)); + } + + return 0; +} + +/* release unclaimed pipeline resource */ +int komeda_release_unclaimed_resources(struct komeda_pipeline *pipe, + struct komeda_crtc_state *kcrtc_st) +{ + struct drm_atomic_commit *drm_st = kcrtc_st->base.state; + struct komeda_pipeline_state *st; + + /* ignore the pipeline which is not affected */ + if (!pipe || !has_bit(pipe->id, kcrtc_st->affected_pipes)) + return 0; + + if (has_bit(pipe->id, kcrtc_st->active_pipes)) + st = komeda_pipeline_get_new_state(pipe, drm_st); + else + st = komeda_pipeline_get_state_and_set_crtc(pipe, drm_st, NULL); + + if (WARN_ON(IS_ERR_OR_NULL(st))) + return -EINVAL; + + return komeda_pipeline_unbound_components(pipe, st); + +} + +/* Since standalone disabled components must be disabled separately and in the + * last, So a complete disable operation may needs to call pipeline_disable + * twice (two phase disabling). + * Phase 1: disable the common components, flush it. + * Phase 2: disable the standalone disabled components, flush it. + * + * RETURNS: + * true: disable is not complete, needs a phase 2 disable. + * false: disable is complete. + */ +bool komeda_pipeline_disable(struct komeda_pipeline *pipe, + struct drm_atomic_commit *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_commit *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); + } +} diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_plane.c b/drivers/gpu/drm/arm/display/komeda/komeda_plane.c new file mode 100644 index 000000000..57a4a0c7d --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_plane.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#include +#include +#include +#include +#include "komeda_dev.h" +#include "komeda_kms.h" +#include "komeda_framebuffer.h" + +static int +komeda_plane_init_data_flow(struct drm_plane_state *st, + struct komeda_crtc_state *kcrtc_st, + struct komeda_data_flow_cfg *dflow) +{ + struct komeda_plane *kplane = to_kplane(st->plane); + struct drm_framebuffer *fb = st->fb; + const struct komeda_format_caps *caps = to_kfb(fb)->format_caps; + struct komeda_pipeline *pipe = kplane->layer->base.pipeline; + + memset(dflow, 0, sizeof(*dflow)); + + dflow->blending_zorder = st->normalized_zpos; + if (pipe == to_kcrtc(st->crtc)->master) + dflow->blending_zorder -= kcrtc_st->max_slave_zorder; + if (dflow->blending_zorder < 0) { + DRM_DEBUG_ATOMIC("%s zorder:%d < max_slave_zorder: %d.\n", + st->plane->name, st->normalized_zpos, + kcrtc_st->max_slave_zorder); + return -EINVAL; + } + + dflow->pixel_blend_mode = st->pixel_blend_mode; + dflow->layer_alpha = st->alpha >> 8; + + dflow->out_x = st->crtc_x; + dflow->out_y = st->crtc_y; + dflow->out_w = st->crtc_w; + dflow->out_h = st->crtc_h; + + dflow->in_x = st->src_x >> 16; + dflow->in_y = st->src_y >> 16; + dflow->in_w = st->src_w >> 16; + dflow->in_h = st->src_h >> 16; + + dflow->rot = drm_rotation_simplify(st->rotation, caps->supported_rots); + if (!has_bits(dflow->rot, caps->supported_rots)) { + DRM_DEBUG_ATOMIC("rotation(0x%x) isn't supported by %p4cc with modifier: 0x%llx.\n", + dflow->rot, &caps->fourcc, fb->modifier); + return -EINVAL; + } + + komeda_complete_data_flow_cfg(kplane->layer, dflow, fb); + + return 0; +} + +/** + * komeda_plane_atomic_check - build input data flow + * @plane: DRM plane + * @state: the plane state object + * + * RETURNS: + * Zero for success or -errno + */ +static int +komeda_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, + plane); + struct komeda_plane *kplane = to_kplane(plane); + struct komeda_plane_state *kplane_st = to_kplane_st(new_plane_state); + struct komeda_layer *layer = kplane->layer; + struct drm_crtc_state *crtc_st; + struct komeda_crtc_state *kcrtc_st; + struct komeda_data_flow_cfg dflow; + int err; + + if (!new_plane_state->crtc || !new_plane_state->fb) + return 0; + + crtc_st = drm_atomic_get_crtc_state(state, + new_plane_state->crtc); + if (IS_ERR(crtc_st) || !crtc_st->enable) { + DRM_DEBUG_ATOMIC("Cannot update plane on a disabled CRTC.\n"); + return -EINVAL; + } + + /* crtc is inactive, skip the resource assignment */ + if (!crtc_st->active) + return 0; + + kcrtc_st = to_kcrtc_st(crtc_st); + + err = komeda_plane_init_data_flow(new_plane_state, kcrtc_st, &dflow); + if (err) + return err; + + if (dflow.en_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; +} + +/* plane doesn't represent a real HW, so there is no HW update for plane. + * komeda handles all the HW update in crtc->atomic_flush + */ +static void +komeda_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_commit *state) +{ +} + +static const struct drm_plane_helper_funcs komeda_plane_helper_funcs = { + .atomic_check = komeda_plane_atomic_check, + .atomic_update = komeda_plane_atomic_update, +}; + +static void komeda_plane_destroy(struct drm_plane *plane) +{ + drm_plane_cleanup(plane); + + kfree(to_kplane(plane)); +} + +static void komeda_plane_reset(struct drm_plane *plane) +{ + struct komeda_plane_state *state; + + if (plane->state) + __drm_atomic_helper_plane_destroy_state(plane->state); + + kfree(plane->state); + plane->state = NULL; + + state = kzalloc_obj(*state); + if (state) + __drm_atomic_helper_plane_reset(plane, &state->base); +} + +static struct drm_plane_state * +komeda_plane_atomic_duplicate_state(struct drm_plane *plane) +{ + struct komeda_plane_state *new; + + if (WARN_ON(!plane->state)) + return NULL; + + new = kzalloc_obj(*new); + if (!new) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, &new->base); + + return &new->base; +} + +static void +komeda_plane_atomic_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + __drm_atomic_helper_plane_destroy_state(state); + kfree(to_kplane_st(state)); +} + +static bool +komeda_plane_format_mod_supported(struct drm_plane *plane, + u32 format, u64 modifier) +{ + struct komeda_dev *mdev = plane->dev->dev_private; + struct komeda_plane *kplane = to_kplane(plane); + u32 layer_type = kplane->layer->layer_type; + + return komeda_format_mod_supported(&mdev->fmt_tbl, layer_type, + format, modifier, 0); +} + +static const struct drm_plane_funcs komeda_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = komeda_plane_destroy, + .reset = komeda_plane_reset, + .atomic_duplicate_state = komeda_plane_atomic_duplicate_state, + .atomic_destroy_state = komeda_plane_atomic_destroy_state, + .format_mod_supported = komeda_plane_format_mod_supported, +}; + +/* for komeda, which is pipeline can be share between crtcs */ +static u32 get_possible_crtcs(struct komeda_kms_dev *kms, + struct komeda_pipeline *pipe) +{ + struct komeda_crtc *crtc; + u32 possible_crtcs = 0; + int i; + + for (i = 0; i < kms->n_crtcs; i++) { + crtc = &kms->crtcs[i]; + + if ((pipe == crtc->master) || (pipe == crtc->slave)) + possible_crtcs |= BIT(i); + } + + return possible_crtcs; +} + +static void +komeda_set_crtc_plane_mask(struct komeda_kms_dev *kms, + struct komeda_pipeline *pipe, + struct drm_plane *plane) +{ + struct komeda_crtc *kcrtc; + int i; + + for (i = 0; i < kms->n_crtcs; i++) { + kcrtc = &kms->crtcs[i]; + + if (pipe == kcrtc->slave) + kcrtc->slave_planes |= BIT(drm_plane_index(plane)); + } +} + +/* use Layer0 as primary */ +static u32 get_plane_type(struct komeda_kms_dev *kms, + struct komeda_component *c) +{ + bool is_primary = (c->id == KOMEDA_COMPONENT_LAYER0); + + return is_primary ? DRM_PLANE_TYPE_PRIMARY : DRM_PLANE_TYPE_OVERLAY; +} + +static int komeda_plane_add(struct komeda_kms_dev *kms, + struct komeda_layer *layer) +{ + struct komeda_dev *mdev = kms->base.dev_private; + struct komeda_component *c = &layer->base; + struct komeda_plane *kplane; + struct drm_plane *plane; + u32 *formats, n_formats = 0; + int err; + + kplane = kzalloc_obj(*kplane); + if (!kplane) + return -ENOMEM; + + plane = &kplane->base; + kplane->layer = layer; + + formats = komeda_get_layer_fourcc_list(&mdev->fmt_tbl, + layer->layer_type, &n_formats); + if (!formats) { + kfree(kplane); + return -ENOMEM; + } + + err = drm_universal_plane_init(&kms->base, plane, + get_possible_crtcs(kms, c->pipeline), + &komeda_plane_funcs, + formats, n_formats, komeda_supported_modifiers, + get_plane_type(kms, c), + "%s", c->name); + + komeda_put_fourcc_list(formats); + + if (err) { + kfree(kplane); + return err; + } + + drm_plane_helper_add(plane, &komeda_plane_helper_funcs); + + err = drm_plane_create_rotation_property(plane, DRM_MODE_ROTATE_0, + layer->supported_rots); + if (err) + goto cleanup; + + err = drm_plane_create_alpha_property(plane); + if (err) + goto cleanup; + + err = drm_plane_create_blend_mode_property(plane, + BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI) | + BIT(DRM_MODE_BLEND_COVERAGE)); + if (err) + goto cleanup; + + err = drm_plane_create_color_properties(plane, + BIT(DRM_COLOR_YCBCR_BT601) | + BIT(DRM_COLOR_YCBCR_BT709) | + BIT(DRM_COLOR_YCBCR_BT2020), + BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) | + BIT(DRM_COLOR_YCBCR_FULL_RANGE), + DRM_COLOR_YCBCR_BT601, + DRM_COLOR_YCBCR_LIMITED_RANGE); + if (err) + goto cleanup; + + err = drm_plane_create_zpos_property(plane, layer->base.id, 0, 8); + if (err) + goto cleanup; + + komeda_set_crtc_plane_mask(kms, c->pipeline, plane); + + return 0; +cleanup: + komeda_plane_destroy(plane); + return err; +} + +int komeda_kms_add_planes(struct komeda_kms_dev *kms, struct komeda_dev *mdev) +{ + struct komeda_pipeline *pipe; + int i, j, err; + + for (i = 0; i < mdev->n_pipelines; i++) { + pipe = mdev->pipelines[i]; + + for (j = 0; j < pipe->n_layers; j++) { + err = komeda_plane_add(kms, pipe->layers[j]); + if (err) + return err; + } + } + + return 0; +} diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c b/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c new file mode 100644 index 000000000..77b3f3610 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_private_obj.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#include "komeda_dev.h" +#include "komeda_kms.h" + +static void +komeda_component_state_reset(struct komeda_component_state *st) +{ + st->binding_user = NULL; + st->affected_inputs = st->active_inputs; + st->active_inputs = 0; + st->changed_active_inputs = 0; +} + +static struct drm_private_state * +komeda_layer_atomic_duplicate_state(struct drm_private_obj *obj) +{ + struct komeda_layer_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_layer_atomic_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) +{ + struct komeda_layer_state *st = to_layer_st(priv_to_comp_st(state)); + + kfree(st); +} + +static struct drm_private_state * +komeda_layer_atomic_create_state(struct drm_private_obj *obj) +{ + struct komeda_layer_state *st; + + st = kzalloc_obj(*st); + if (!st) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_private_obj_create_state(obj, &st->base.obj); + komeda_component_state_reset(&st->base); + st->base.component = to_component(obj); + + return &st->base.obj; +} + +static const struct drm_private_state_funcs komeda_layer_obj_funcs = { + .atomic_create_state = komeda_layer_atomic_create_state, + .atomic_duplicate_state = komeda_layer_atomic_duplicate_state, + .atomic_destroy_state = komeda_layer_atomic_destroy_state, +}; + +static int komeda_layer_obj_add(struct komeda_kms_dev *kms, + struct komeda_layer *layer) +{ + drm_atomic_private_obj_init(&kms->base, &layer->base.obj, + &komeda_layer_obj_funcs); + return 0; +} + +static struct drm_private_state * +komeda_scaler_atomic_duplicate_state(struct drm_private_obj *obj) +{ + struct komeda_scaler_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_scaler_atomic_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) +{ + kfree(to_scaler_st(priv_to_comp_st(state))); +} + +static struct drm_private_state * +komeda_scaler_atomic_create_state(struct drm_private_obj *obj) +{ + struct komeda_scaler_state *st; + + st = kzalloc_obj(*st); + if (!st) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_private_obj_create_state(obj, &st->base.obj); + komeda_component_state_reset(&st->base); + st->base.component = to_component(obj); + + return &st->base.obj; +} + +static const struct drm_private_state_funcs komeda_scaler_obj_funcs = { + .atomic_create_state = komeda_scaler_atomic_create_state, + .atomic_duplicate_state = komeda_scaler_atomic_duplicate_state, + .atomic_destroy_state = komeda_scaler_atomic_destroy_state, +}; + +static int komeda_scaler_obj_add(struct komeda_kms_dev *kms, + struct komeda_scaler *scaler) +{ + drm_atomic_private_obj_init(&kms->base, + &scaler->base.obj, + &komeda_scaler_obj_funcs); + return 0; +} + +static struct drm_private_state * +komeda_compiz_atomic_duplicate_state(struct drm_private_obj *obj) +{ + struct komeda_compiz_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_compiz_atomic_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) +{ + kfree(to_compiz_st(priv_to_comp_st(state))); +} + +static struct drm_private_state * +komeda_compiz_atomic_create_state(struct drm_private_obj *obj) +{ + struct komeda_compiz_state *st; + + st = kzalloc_obj(*st); + if (!st) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_private_obj_create_state(obj, &st->base.obj); + komeda_component_state_reset(&st->base); + st->base.component = to_component(obj); + + return &st->base.obj; +} + +static const struct drm_private_state_funcs komeda_compiz_obj_funcs = { + .atomic_create_state = komeda_compiz_atomic_create_state, + .atomic_duplicate_state = komeda_compiz_atomic_duplicate_state, + .atomic_destroy_state = komeda_compiz_atomic_destroy_state, +}; + +static int komeda_compiz_obj_add(struct komeda_kms_dev *kms, + struct komeda_compiz *compiz) +{ + drm_atomic_private_obj_init(&kms->base, &compiz->base.obj, + &komeda_compiz_obj_funcs); + + return 0; +} + +static struct drm_private_state * +komeda_splitter_atomic_duplicate_state(struct drm_private_obj *obj) +{ + struct komeda_splitter_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_splitter_atomic_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) +{ + kfree(to_splitter_st(priv_to_comp_st(state))); +} + +static struct drm_private_state * +komeda_splitter_atomic_create_state(struct drm_private_obj *obj) +{ + struct komeda_splitter_state *st; + + st = kzalloc_obj(*st); + if (!st) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_private_obj_create_state(obj, &st->base.obj); + komeda_component_state_reset(&st->base); + st->base.component = to_component(obj); + + return &st->base.obj; +} + +static const struct drm_private_state_funcs komeda_splitter_obj_funcs = { + .atomic_create_state = komeda_splitter_atomic_create_state, + .atomic_duplicate_state = komeda_splitter_atomic_duplicate_state, + .atomic_destroy_state = komeda_splitter_atomic_destroy_state, +}; + +static int komeda_splitter_obj_add(struct komeda_kms_dev *kms, + struct komeda_splitter *splitter) +{ + drm_atomic_private_obj_init(&kms->base, + &splitter->base.obj, + &komeda_splitter_obj_funcs); + + 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 struct drm_private_state * +komeda_merger_atomic_create_state(struct drm_private_obj *obj) +{ + struct komeda_merger_state *st; + + st = kzalloc_obj(*st); + if (!st) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_private_obj_create_state(obj, &st->base.obj); + komeda_component_state_reset(&st->base); + st->base.component = to_component(obj); + + return &st->base.obj; +} + +static const struct drm_private_state_funcs komeda_merger_obj_funcs = { + .atomic_create_state = komeda_merger_atomic_create_state, + .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) +{ + drm_atomic_private_obj_init(&kms->base, + &merger->base.obj, + &komeda_merger_obj_funcs); + + return 0; +} + +static struct drm_private_state * +komeda_improc_atomic_duplicate_state(struct drm_private_obj *obj) +{ + struct komeda_improc_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_improc_atomic_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) +{ + kfree(to_improc_st(priv_to_comp_st(state))); +} + +static struct drm_private_state * +komeda_improc_atomic_create_state(struct drm_private_obj *obj) +{ + struct komeda_improc_state *st; + + st = kzalloc_obj(*st); + if (!st) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_private_obj_create_state(obj, &st->base.obj); + komeda_component_state_reset(&st->base); + st->base.component = to_component(obj); + + return &st->base.obj; +} + +static const struct drm_private_state_funcs komeda_improc_obj_funcs = { + .atomic_create_state = komeda_improc_atomic_create_state, + .atomic_duplicate_state = komeda_improc_atomic_duplicate_state, + .atomic_destroy_state = komeda_improc_atomic_destroy_state, +}; + +static int komeda_improc_obj_add(struct komeda_kms_dev *kms, + struct komeda_improc *improc) +{ + drm_atomic_private_obj_init(&kms->base, &improc->base.obj, + &komeda_improc_obj_funcs); + + return 0; +} + +static struct drm_private_state * +komeda_timing_ctrlr_atomic_duplicate_state(struct drm_private_obj *obj) +{ + struct komeda_timing_ctrlr_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_timing_ctrlr_atomic_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) +{ + kfree(to_ctrlr_st(priv_to_comp_st(state))); +} + +static struct drm_private_state * +komeda_timing_ctrlr_atomic_create_state(struct drm_private_obj *obj) +{ + struct komeda_timing_ctrlr_state *st; + + st = kzalloc_obj(*st); + if (!st) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_private_obj_create_state(obj, &st->base.obj); + komeda_component_state_reset(&st->base); + st->base.component = to_component(obj); + + return &st->base.obj; +} + +static const struct drm_private_state_funcs komeda_timing_ctrlr_obj_funcs = { + .atomic_create_state = komeda_timing_ctrlr_atomic_create_state, + .atomic_duplicate_state = komeda_timing_ctrlr_atomic_duplicate_state, + .atomic_destroy_state = komeda_timing_ctrlr_atomic_destroy_state, +}; + +static int komeda_timing_ctrlr_obj_add(struct komeda_kms_dev *kms, + struct komeda_timing_ctrlr *ctrlr) +{ + drm_atomic_private_obj_init(&kms->base, &ctrlr->base.obj, + &komeda_timing_ctrlr_obj_funcs); + + return 0; +} + +static struct drm_private_state * +komeda_pipeline_atomic_duplicate_state(struct drm_private_obj *obj) +{ + struct komeda_pipeline_state *st; + + st = kmemdup(obj->state, sizeof(*st), GFP_KERNEL); + if (!st) + return NULL; + + st->active_comps = 0; + + __drm_atomic_helper_private_obj_duplicate_state(obj, &st->obj); + + return &st->obj; +} + +static void +komeda_pipeline_atomic_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) +{ + kfree(priv_to_pipe_st(state)); +} + +static struct drm_private_state * +komeda_pipeline_atomic_create_state(struct drm_private_obj *obj) +{ + struct komeda_pipeline_state *st; + + st = kzalloc_obj(*st); + if (!st) + return ERR_PTR(-ENOMEM); + + __drm_atomic_helper_private_obj_create_state(obj, &st->obj); + st->active_comps = 0; + st->pipe = container_of(obj, struct komeda_pipeline, obj); + + return &st->obj; +} + +static const struct drm_private_state_funcs komeda_pipeline_obj_funcs = { + .atomic_create_state = komeda_pipeline_atomic_create_state, + .atomic_duplicate_state = komeda_pipeline_atomic_duplicate_state, + .atomic_destroy_state = komeda_pipeline_atomic_destroy_state, +}; + +static int komeda_pipeline_obj_add(struct komeda_kms_dev *kms, + struct komeda_pipeline *pipe) +{ + drm_atomic_private_obj_init(&kms->base, &pipe->obj, + &komeda_pipeline_obj_funcs); + + return 0; +} + +int komeda_kms_add_private_objs(struct komeda_kms_dev *kms, + struct komeda_dev *mdev) +{ + struct komeda_pipeline *pipe; + int i, j, err; + + for (i = 0; i < mdev->n_pipelines; i++) { + pipe = mdev->pipelines[i]; + + err = komeda_pipeline_obj_add(kms, pipe); + if (err) + return err; + + for (j = 0; j < pipe->n_layers; j++) { + err = komeda_layer_obj_add(kms, pipe->layers[j]); + if (err) + return err; + } + + if (pipe->wb_layer) { + err = komeda_layer_obj_add(kms, pipe->wb_layer); + if (err) + return err; + } + + for (j = 0; j < pipe->n_scalers; j++) { + err = komeda_scaler_obj_add(kms, pipe->scalers[j]); + if (err) + return err; + } + + err = komeda_compiz_obj_add(kms, pipe->compiz); + if (err) + return err; + + if (pipe->splitter) { + err = komeda_splitter_obj_add(kms, pipe->splitter); + 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; + + err = komeda_timing_ctrlr_obj_add(kms, pipe->ctrlr); + if (err) + return err; + } + + return 0; +} + +void komeda_kms_cleanup_private_objs(struct komeda_kms_dev *kms) +{ + struct drm_mode_config *config = &kms->base.mode_config; + struct drm_private_obj *obj, *next; + + list_for_each_entry_safe(obj, next, &config->privobj_list, head) + drm_atomic_private_obj_fini(obj); +} diff --git a/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c b/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c new file mode 100644 index 000000000..41cc3e080 --- /dev/null +++ b/drivers/gpu/drm/arm/display/komeda/komeda_wb_connector.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) COPYRIGHT 2018 ARM Limited. All rights reserved. + * Author: James.Qian.Wang + * + */ +#include +#include "komeda_dev.h" +#include "komeda_kms.h" + +static int +komeda_wb_init_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_framebuffer *fb = conn_st->writeback_job->fb; + + memset(dflow, 0, sizeof(*dflow)); + + dflow->out_w = fb->width; + dflow->out_h = fb->height; + + /* the write back data comes from the compiz */ + pipeline_composition_size(kcrtc_st, &dflow->in_w, &dflow->in_h); + dflow->input.component = &wb_layer->base.pipeline->compiz->base; + /* compiz doesn't output alpha */ + dflow->pixel_blend_mode = DRM_MODE_BLEND_PIXEL_NONE; + dflow->rot = DRM_MODE_ROTATE_0; + + komeda_complete_data_flow_cfg(wb_layer, dflow, fb); + + return 0; +} + +static int +komeda_wb_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_st, + struct drm_connector_state *conn_st) +{ + struct komeda_crtc_state *kcrtc_st = to_kcrtc_st(crtc_st); + struct drm_writeback_job *writeback_job = conn_st->writeback_job; + struct komeda_layer *wb_layer; + struct komeda_data_flow_cfg dflow; + int err; + + if (!writeback_job) + return 0; + + if (!crtc_st->active) { + DRM_DEBUG_ATOMIC("Cannot write the composition result out on a inactive CRTC.\n"); + return -EINVAL; + } + + wb_layer = to_kconn(to_wb_conn(conn_st->connector))->wb_layer; + + /* + * No need for a full modested when the only connector changed is the + * writeback connector. + */ + if (crtc_st->connectors_changed && + is_only_changed_connector(crtc_st, conn_st->connector)) + crtc_st->connectors_changed = false; + + err = komeda_wb_init_data_flow(wb_layer, conn_st, kcrtc_st, &dflow); + if (err) + return err; + + if (dflow.en_split) + err = komeda_build_wb_split_data_flow(wb_layer, + conn_st, kcrtc_st, &dflow); + else + err = komeda_build_wb_data_flow(wb_layer, + conn_st, kcrtc_st, &dflow); + + return err; +} + +static const struct drm_encoder_helper_funcs komeda_wb_encoder_helper_funcs = { + .atomic_check = komeda_wb_encoder_atomic_check, +}; + +static int +komeda_wb_connector_get_modes(struct drm_connector *connector) +{ + return 0; +} + +static enum drm_mode_status +komeda_wb_connector_mode_valid(struct drm_connector *connector, + const struct drm_display_mode *mode) +{ + struct drm_device *dev = connector->dev; + struct drm_mode_config *mode_config = &dev->mode_config; + int w = mode->hdisplay, h = mode->vdisplay; + + if ((w < mode_config->min_width) || (w > mode_config->max_width)) + return MODE_BAD_HVALUE; + + if ((h < mode_config->min_height) || (h > mode_config->max_height)) + return MODE_BAD_VVALUE; + + return MODE_OK; +} + +static const struct drm_connector_helper_funcs komeda_wb_conn_helper_funcs = { + .get_modes = komeda_wb_connector_get_modes, + .mode_valid = komeda_wb_connector_mode_valid, +}; + +static enum drm_connector_status +komeda_wb_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static int +komeda_wb_connector_fill_modes(struct drm_connector *connector, + uint32_t maxX, uint32_t maxY) +{ + return 0; +} + +static void komeda_wb_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); + kfree(to_kconn(to_wb_conn(connector))); +} + +static const struct drm_connector_funcs komeda_wb_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .detect = komeda_wb_connector_detect, + .fill_modes = komeda_wb_connector_fill_modes, + .destroy = komeda_wb_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int komeda_wb_connector_add(struct komeda_kms_dev *kms, + struct komeda_crtc *kcrtc) +{ + struct komeda_dev *mdev = kms->base.dev_private; + struct komeda_wb_connector *kwb_conn; + struct drm_writeback_connector *wb_conn; + struct drm_display_info *info; + u32 *formats, n_formats = 0; + int err; + + if (!kcrtc->master->wb_layer) + return 0; + + kwb_conn = kzalloc_obj(*kwb_conn); + if (!kwb_conn) + return -ENOMEM; + + kwb_conn->wb_layer = kcrtc->master->wb_layer; + + wb_conn = &kwb_conn->base; + + formats = komeda_get_layer_fourcc_list(&mdev->fmt_tbl, + kwb_conn->wb_layer->layer_type, + &n_formats); + if (!formats) { + kfree(kwb_conn); + return -ENOMEM; + } + + err = drm_writeback_connector_init(&kms->base, wb_conn, + &komeda_wb_connector_funcs, + &komeda_wb_encoder_helper_funcs, + formats, n_formats, + BIT(drm_crtc_index(&kcrtc->base))); + komeda_put_fourcc_list(formats); + if (err) { + kfree(kwb_conn); + return err; + } + + drm_connector_helper_add(&wb_conn->base, &komeda_wb_conn_helper_funcs); + + info = &kwb_conn->base.base.display_info; + info->bpc = __fls(kcrtc->master->improc->supported_color_depths); + info->color_formats = kcrtc->master->improc->supported_color_formats; + + kcrtc->wb_conn = kwb_conn; + + return 0; +} + +int komeda_kms_add_wb_connectors(struct komeda_kms_dev *kms, + struct komeda_dev *mdev) +{ + int i, err; + + for (i = 0; i < kms->n_crtcs; i++) { + err = komeda_wb_connector_add(kms, &kms->crtcs[i]); + if (err) + return err; + } + + return 0; +} diff --git a/drivers/gpu/drm/arm/hdlcd_crtc.c b/drivers/gpu/drm/arm/hdlcd_crtc.c new file mode 100644 index 000000000..8565749a3 --- /dev/null +++ b/drivers/gpu/drm/arm/hdlcd_crtc.c @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2013-2015 ARM Limited + * Author: Liviu Dudau + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * Implementation of a CRTC class for the HDLCD driver. + */ + +#include +#include + +#include