On 12/13/2024, Dmitry Baryshkov wrote: > On Fri, 13 Dec 2024 at 08:06, Liu Ying <victor.liu@xxxxxxx> wrote: >> >> On 12/12/2024, Dmitry Baryshkov wrote: >>> On Wed, Dec 11, 2024 at 03:43:20PM +0800, Liu Ying wrote: >>>> On 12/10/2024, Dmitry Baryshkov wrote: >>>>> On Mon, Dec 09, 2024 at 11:39:16AM +0800, Liu Ying wrote: >>>>>> i.MX8qxp Display Controller(DC) is comprised of three main components that >>>>>> include a blit engine for 2D graphics accelerations, display controller for >>>>>> display output processing, as well as a command sequencer. Add kernel >>>>>> mode setting support for the display controller part with two CRTCs and >>>>>> two primary planes(backed by FetchLayer and FetchWarp respectively). The >>>>>> registers of the display controller are accessed without command sequencer >>>>>> involved, instead just by using CPU. The command sequencer is supposed to >>>>>> be used by the blit engine. >>>>>> >>>>>> Signed-off-by: Liu Ying <victor.liu@xxxxxxx> >>>>>> --- >>>>>> v6: >>>>>> * No change. >>>>>> >>>>>> v5: >>>>>> * Replace .remove_new with .remove in dc-drv.c. (Uwe) >>>>>> >>>>>> v4: >>>>>> * Move dc_fg_displaymode(), dc_fg_panic_displaymode() and dc_lb_blendcontrol() >>>>>> function calls from KMS routine to initialization stage. (Dmitry) >>>>>> * Drop dc-crtc.h and dc-plane.h header files and move relevant defines to >>>>>> appropriate .h header files or .c source files. (Dmitry) >>>>>> * Drop futile "else" clause from dc_crtc_common_irq_handler(). (Dmitry) >>>>>> * Drop dc_drm->pe_rpm_count. (Dmitry) >>>>>> * Drop DC_{CRTCS,ENCODERS,PRIMARYS} macros and only use DC_DISPLAYS. (Dmitry) >>>>>> * Drop drmm_kcalloc() function call to allocate an array for storing IRQs. >>>>>> Instead, put it in struct dc_crtc. (Dmitry) >>>>>> * Call devm_request_irq() to request IRQs, instead of using drmm action. >>>>>> (Dmitry) >>>>>> * Call devm_drm_of_get_bridge() to find the next bridge. (Dmitry) >>>>>> * Select DRM_CLIENT_SELECTION due to rebase. >>>>>> * Select the missing DRM_DISPLAY_HELPER and DRM_BRIDGE_CONNECTOR. >>>>>> * Use DRM_FBDEV_DMA_DRIVER_OPS due to rebase. >>>>>> * Replace drm_fbdev_dma_setup() with drm_client_setup_with_fourcc() due to >>>>>> rebase. >>>>>> * Replace drmm_add_action_or_reset() with devm_add_action_or_reset() to >>>>>> register dc_drm_component_unbind_all() action. >>>>>> * Request interrupts in dc_crtc_post_init() after encoder initialization to >>>>>> make sure next bridge is found first. >>>>>> >>>>>> v3: >>>>>> * No change. >>>>>> >>>>>> v2: >>>>>> * Find next bridge from TCon's port. >>>>>> * Drop drm/drm_module.h include from dc-drv.c. >>>>>> >>>>>> drivers/gpu/drm/imx/dc/Kconfig | 5 + >>>>>> drivers/gpu/drm/imx/dc/Makefile | 5 +- >>>>>> drivers/gpu/drm/imx/dc/dc-crtc.c | 558 ++++++++++++++++++++++++++++++ >>>>>> drivers/gpu/drm/imx/dc/dc-de.h | 3 + >>>>>> drivers/gpu/drm/imx/dc/dc-drv.c | 244 +++++++++++++ >>>>>> drivers/gpu/drm/imx/dc/dc-drv.h | 19 + >>>>>> drivers/gpu/drm/imx/dc/dc-kms.c | 143 ++++++++ >>>>>> drivers/gpu/drm/imx/dc/dc-kms.h | 58 ++++ >>>>>> drivers/gpu/drm/imx/dc/dc-plane.c | 241 +++++++++++++ >>>>>> 9 files changed, 1274 insertions(+), 2 deletions(-) >>>>>> create mode 100644 drivers/gpu/drm/imx/dc/dc-crtc.c >>>>>> create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.c >>>>>> create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.h >>>>>> create mode 100644 drivers/gpu/drm/imx/dc/dc-plane.c >>>>>> >>>>>> diff --git a/drivers/gpu/drm/imx/dc/Kconfig b/drivers/gpu/drm/imx/dc/Kconfig >>>>>> index 1fc84c7475de..415993207f2e 100644 >>>>>> --- a/drivers/gpu/drm/imx/dc/Kconfig >>>>>> +++ b/drivers/gpu/drm/imx/dc/Kconfig >>>>>> @@ -1,6 +1,11 @@ >>>>>> config DRM_IMX8_DC >>>>>> tristate "Freescale i.MX8 Display Controller Graphics" >>>>>> depends on DRM && COMMON_CLK && OF && (ARCH_MXC || COMPILE_TEST) >>>>>> + select DRM_CLIENT_SELECTION >>>>>> + select DRM_GEM_DMA_HELPER >>>>>> + select DRM_KMS_HELPER >>>>>> + select DRM_DISPLAY_HELPER >>>>>> + select DRM_BRIDGE_CONNECTOR >>>>>> select GENERIC_IRQ_CHIP >>>>>> select REGMAP >>>>>> select REGMAP_MMIO >>>>>> diff --git a/drivers/gpu/drm/imx/dc/Makefile b/drivers/gpu/drm/imx/dc/Makefile >>>>>> index 1ce3e8a8db22..b9d33c074984 100644 >>>>>> --- a/drivers/gpu/drm/imx/dc/Makefile >>>>>> +++ b/drivers/gpu/drm/imx/dc/Makefile >>>>>> @@ -1,6 +1,7 @@ >>>>>> # SPDX-License-Identifier: GPL-2.0 >>>>>> >>>>>> -imx8-dc-drm-objs := dc-cf.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o dc-fu.o \ >>>>>> - dc-fw.o dc-ic.o dc-lb.o dc-pe.o dc-tc.o >>>>>> +imx8-dc-drm-objs := dc-cf.o dc-crtc.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o \ >>>>>> + dc-fu.o dc-fw.o dc-ic.o dc-kms.o dc-lb.o dc-pe.o \ >>>>>> + dc-plane.o dc-tc.o >>>>>> >>>>>> obj-$(CONFIG_DRM_IMX8_DC) += imx8-dc-drm.o >>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-crtc.c b/drivers/gpu/drm/imx/dc/dc-crtc.c >>>>>> new file mode 100644 >>>>>> index 000000000000..fd6daa1807d8 >>>>>> --- /dev/null >>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-crtc.c >>>>>> @@ -0,0 +1,558 @@ >>>>>> +// SPDX-License-Identifier: GPL-2.0+ >>>>>> +/* >>>>>> + * Copyright 2024 NXP >>>>>> + */ >>>>>> + >>>>>> +#include <linux/completion.h> >>>>>> +#include <linux/container_of.h> >>>>>> +#include <linux/interrupt.h> >>>>>> +#include <linux/irqreturn.h> >>>>>> +#include <linux/pm_runtime.h> >>>>>> +#include <linux/spinlock.h> >>>>>> + >>>>>> +#include <drm/drm_atomic.h> >>>>>> +#include <drm/drm_atomic_helper.h> >>>>>> +#include <drm/drm_atomic_state_helper.h> >>>>>> +#include <drm/drm_crtc.h> >>>>>> +#include <drm/drm_device.h> >>>>>> +#include <drm/drm_drv.h> >>>>>> +#include <drm/drm_modes.h> >>>>>> +#include <drm/drm_modeset_helper_vtables.h> >>>>>> +#include <drm/drm_plane.h> >>>>>> +#include <drm/drm_print.h> >>>>>> +#include <drm/drm_vblank.h> >>>>>> + >>>>>> +#include "dc-de.h" >>>>>> +#include "dc-drv.h" >>>>>> +#include "dc-kms.h" >>>>>> +#include "dc-pe.h" >>>>>> + >>>>>> +#define dc_crtc_dbg(crtc, fmt, ...) \ >>>>>> +do { \ >>>>>> + typeof(crtc) _crtc = (crtc); \ >>>>> >>>>> Use exact type instead of typeof. >>>> >>>> Will do. >>>> >>>>> >>>>>> + drm_dbg_kms(_crtc->dev, "[CRTC:%d:%s] " fmt, \ >>>>>> + _crtc->base.id, _crtc->name, ##__VA_ARGS__); \ >>>>>> +} while (0) >>>>>> + >>>>>> +#define dc_crtc_err(crtc, fmt, ...) \ >>>>>> +do { \ >>>>>> + typeof(crtc) _crtc = (crtc); \ >>>>>> + drm_err(_crtc->dev, "[CRTC:%d:%s] " fmt, \ >>>>>> + _crtc->base.id, _crtc->name, ##__VA_ARGS__); \ >>>>>> +} while (0) >>>>>> + >>>>>> +#define DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(c) \ >>>>>> +do { \ >>>>>> + unsigned long ret; \ >>>>>> + ret = wait_for_completion_timeout(&dc_crtc->c, HZ); \ >>>>>> + if (ret == 0) \ >>>>>> + dc_crtc_err(crtc, "%s: wait for " #c " timeout\n", \ >>>>>> + __func__); \ >>>>>> +} while (0) >>>>>> + >>>>>> +#define DC_CRTC_CHECK_FRAMEGEN_FIFO(fg) \ >>>>>> +do { \ >>>>>> + typeof(fg) _fg = (fg); \ >>>>>> + if (dc_fg_secondary_requests_to_read_empty_fifo(_fg)) { \ >>>>>> + dc_fg_secondary_clear_channel_status(_fg); \ >>>>>> + dc_crtc_err(crtc, "%s: FrameGen FIFO empty\n", \ >>>>>> + __func__); \ >>>>>> + } \ >>>>>> +} while (0) >>>>>> + >>>>>> +#define DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(fg) \ >>>>>> +do { \ >>>>>> + if (dc_fg_wait_for_secondary_syncup(fg)) \ >>>>>> + dc_crtc_err(crtc, \ >>>>>> + "%s: FrameGen secondary channel isn't syncup\n",\ >>>>>> + __func__); \ >>>>>> +} while (0) >>>>>> + >>>>>> +static inline struct dc_crtc *to_dc_crtc(struct drm_crtc *crtc) >>>>>> +{ >>>>>> + return container_of(crtc, struct dc_crtc, base); >>>>>> +} >>>>>> + >>>>>> +static u32 dc_crtc_get_vblank_counter(struct drm_crtc *crtc) >>>>>> +{ >>>>>> + struct dc_crtc *dc_crtc = to_dc_crtc(crtc); >>>>>> + >>>>>> + return dc_fg_get_frame_index(dc_crtc->fg); >>>>>> +} >>>>>> + >>>>>> +static int dc_crtc_enable_vblank(struct drm_crtc *crtc) >>>>>> +{ >>>>>> + struct dc_crtc *dc_crtc = to_dc_crtc(crtc); >>>>>> + >>>>>> + enable_irq(dc_crtc->irq_dec_framecomplete); >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +static void dc_crtc_disable_vblank(struct drm_crtc *crtc) >>>>>> +{ >>>>>> + struct dc_crtc *dc_crtc = to_dc_crtc(crtc); >>>>>> + >>>>>> + disable_irq_nosync(dc_crtc->irq_dec_framecomplete); >>>>> >>>>> Why is it _nosync? >>>> >>>> Because disable_irq() can only be called from preemptible code according to >>>> it's kerneldoc. If I use disable_irq() here, I get this with >>>> CONFIG_DEBUG_ATOMIC_SLEEP enabled: >>> >>> Please add a one-line comment, like "nosync because of the atomic >>> context" >> >> Will do. >> >>> >>>> >>>> [ 50.607503] BUG: sleeping function called from invalid context at kernel/irq/manage.c:738 >>>> [ 50.615691] in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 0, name: swapper/0 >>>> [ 50.623527] preempt_count: 10003, expected: 0 >>>> [ 50.627888] RCU nest depth: 0, expected: 0 >>>> [ 50.631993] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 6.13.0-rc2-next-20241209-00026-g800cb5b7df74 #1407 >>>> [ 50.642092] Hardware name: Freescale i.MX8QXP MEK (DT) >>>> [ 50.647237] Call trace: >>>> [ 50.649687] show_stack+0x18/0x24 (C) >>>> [ 50.653369] dump_stack_lvl+0x80/0xb4 >>>> [ 50.657043] dump_stack+0x18/0x24 >>>> [ 50.660370] __might_resched+0x114/0x170 >>>> [ 50.664306] __might_sleep+0x48/0x98 >>>> [ 50.667894] disable_irq+0x24/0x60 >>>> [ 50.671308] dc_crtc_disable_vblank+0x14/0x20 [imx8_dc_drm] >>>> [ 50.676912] drm_vblank_disable_and_save+0xc0/0x108 [drm] >>>> [ 50.682533] vblank_disable_fn+0x78/0x9c [drm] >>>> [ 50.687146] drm_handle_vblank+0x238/0x2e8 [drm] >>>> [ 50.691932] drm_crtc_handle_vblank+0x1c/0x28 [drm] >>>> [ 50.696980] dc_crtc_irq_handler_dec_framecomplete+0x1c/0x6c [imx8_dc_drm] >>>> [ 50.703886] __handle_irq_event_percpu+0x60/0x14c >>>> [ 50.708604] handle_irq_event+0x4c/0xac >>>> [ 50.712443] handle_level_irq+0xc0/0x1b0 >>>> [ 50.716379] generic_handle_irq+0x34/0x4c >>>> [ 50.720392] dc_ic_irq_handler+0x128/0x160 [imx8_dc_drm] >>>> [ 50.725727] generic_handle_domain_irq+0x2c/0x44 >>>> [ 50.730357] imx_irqsteer_irq_handler+0xc0/0x1a0 >>>> [ 50.734987] generic_handle_domain_irq+0x2c/0x44 >>>> [ 50.739609] gic_handle_irq+0x4c/0x114 >>>> [ 50.743362] call_on_irq_stack+0x24/0x4c >>>> [ 50.747298] do_interrupt_handler+0x80/0x84 >>>> [ 50.751494] el1_interrupt+0x34/0x68 >>>> [ 50.755082] el1h_64_irq_handler+0x18/0x24 >>>> [ 50.759191] el1h_64_irq+0x6c/0x70 >>>> [ 50.762597] default_idle_call+0x28/0x3c (P) >>>> [ 50.766879] default_idle_call+0x24/0x3c (L) >>>> [ 50.771163] do_idle+0x200/0x25c >>>> [ 50.774403] cpu_startup_entry+0x34/0x3c >>>> [ 50.778338] kernel_init+0x0/0x1d8 >>>> [ 50.781752] start_kernel+0x5c4/0x70c >>>> [ 50.785427] __primary_switched+0x88/0x90 >>>> >>>>> >>>>>> +} >>>>>> + >>>>>> +static irqreturn_t >>>>>> +dc_crtc_dec_framecomplete_irq_handler(int irq, void *dev_id) >>>>>> +{ >>>>>> + struct dc_crtc *dc_crtc = dev_id; >>>>>> + struct drm_crtc *crtc = &dc_crtc->base; >>>>>> + unsigned long flags; >>>>>> + >>>>>> + drm_crtc_handle_vblank(crtc); >>>>>> + >>>>>> + spin_lock_irqsave(&crtc->dev->event_lock, flags); >>>>>> + if (dc_crtc->event) { >>>>>> + drm_crtc_send_vblank_event(crtc, dc_crtc->event); >>>>>> + dc_crtc->event = NULL; >>>>>> + drm_crtc_vblank_put(crtc); >>>>>> + } >>>>>> + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); >>>>>> + >>>>>> + return IRQ_HANDLED; >>>>>> +} >>>>>> + >>>>>> +static irqreturn_t dc_crtc_common_irq_handler(int irq, void *dev_id) >>>>>> +{ >>>>>> + struct dc_crtc *dc_crtc = dev_id; >>>>>> + >>>>>> + if (irq == dc_crtc->irq_dec_seqcomplete) >>>>>> + complete(&dc_crtc->dec_seqcomplete_done); >>>>>> + else if (irq == dc_crtc->irq_dec_shdld) >>>>>> + complete(&dc_crtc->dec_shdld_done); >>>>>> + else if (irq == dc_crtc->irq_ed_cont_shdld) >>>>>> + complete(&dc_crtc->ed_cont_shdld_done); >>>>>> + else if (irq == dc_crtc->irq_ed_safe_shdld) >>>>>> + complete(&dc_crtc->ed_safe_shdld_done); >>>>> >>>>> Is there any reason to have a single multiplex handler instead of having >>>>> 4 separate handlers, each doing one simple thing? >>>> >>>> Just thought that one irq handler is feasible to implement the common >>>> handling logic. It's also ok to use 4 separate handlers with a little >>>> performance improvement. I may change to use 4 separate handlers by >>>> introducing a DEFINE_DC_CRTC_IRQ_HANDLER() marco. >>> >>> Just define them one by one, there is no need for a macro. >> >> Will do. >> >>> >>>> >>>>> >>>>>> + >>>>>> + return IRQ_HANDLED; >>>>>> +} >>>>>> + >>>>>> +static const struct drm_crtc_funcs dc_crtc_funcs = { >>>>>> + .reset = drm_atomic_helper_crtc_reset, >>>>>> + .destroy = drm_crtc_cleanup, >>>>>> + .set_config = drm_atomic_helper_set_config, >>>>>> + .page_flip = drm_atomic_helper_page_flip, >>>>>> + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, >>>>>> + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, >>>>>> + .get_vblank_counter = dc_crtc_get_vblank_counter, >>>>>> + .enable_vblank = dc_crtc_enable_vblank, >>>>>> + .disable_vblank = dc_crtc_disable_vblank, >>>>>> + .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, >>>>>> +}; >>>>>> + >>>>>> +static void dc_crtc_queue_state_event(struct drm_crtc_state *crtc_state) >>>>>> +{ >>>>>> + struct drm_crtc *crtc = crtc_state->crtc; >>>>>> + struct dc_crtc *dc_crtc = to_dc_crtc(crtc); >>>>>> + >>>>>> + spin_lock_irq(&crtc->dev->event_lock); >>>>>> + if (crtc_state->event) { >>>>>> + WARN_ON(drm_crtc_vblank_get(crtc)); >>>>>> + WARN_ON(dc_crtc->event); >>>>>> + dc_crtc->event = crtc_state->event; >>>>>> + crtc_state->event = NULL; >>>>>> + } >>>>>> + spin_unlock_irq(&crtc->dev->event_lock); >>>>>> +} >>>>>> + >>>>>> +static enum drm_mode_status >>>>>> +dc_crtc_check_clock(struct dc_crtc *dc_crtc, int clk_khz) >>>>>> +{ >>>>>> + return dc_fg_check_clock(dc_crtc->fg, clk_khz); >>>>>> +} >>>>> >>>>> inline >>>> >>>> Will do. >>>> >>>>> >>>>>> + >>>>>> +static enum drm_mode_status >>>>>> +dc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode) >>>>>> +{ >>>>>> + struct dc_crtc *dc_crtc = to_dc_crtc(crtc); >>>>>> + enum drm_mode_status status; >>>>>> + >>>>>> + status = dc_crtc_check_clock(dc_crtc, mode->clock); >>>>>> + if (status != MODE_OK) >>>>>> + return status; >>>>>> + >>>>>> + if (mode->crtc_clock > DC_FRAMEGEN_MAX_CLOCK_KHZ) >>>>>> + return MODE_CLOCK_HIGH; >>>>>> + >>>>>> + return MODE_OK; >>>>>> +} >>>>>> + >>>>>> +static int >>>>>> +dc_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) >>>>>> +{ >>>>>> + struct drm_crtc_state *new_crtc_state = >>>>>> + drm_atomic_get_new_crtc_state(state, crtc); >>>>>> + struct drm_display_mode *adj = &new_crtc_state->adjusted_mode; >>>>>> + struct dc_crtc *dc_crtc = to_dc_crtc(crtc); >>>>>> + enum drm_mode_status status; >>>>>> + >>>>>> + status = dc_crtc_check_clock(dc_crtc, adj->clock); >>>>>> + if (status != MODE_OK) >>>>>> + return -EINVAL; >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +static void >>>>>> +dc_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state) >>>>>> +{ >>>>>> + struct drm_crtc_state *new_crtc_state = >>>>>> + drm_atomic_get_new_crtc_state(state, crtc); >>>>>> + struct dc_crtc *dc_crtc = to_dc_crtc(crtc); >>>>>> + int idx, ret; >>>>>> + >>>>>> + if (!drm_atomic_crtc_needs_modeset(new_crtc_state) || >>>>>> + !new_crtc_state->active) >>>>>> + return; >>>>>> + >>>>>> + if (!drm_dev_enter(crtc->dev, &idx)) >>>>>> + return; >>>>>> + >>>>>> + /* request pixel engine power-on when CRTC starts to be active */ >>>>>> + ret = pm_runtime_resume_and_get(dc_crtc->pe->dev); >>>>>> + if (ret) >>>>>> + dc_crtc_err(crtc, "failed to get DC pixel engine RPM: %d\n", >>>>>> + ret); >>>>>> + >>>>>> + drm_dev_exit(idx); >>>>>> +} >>>>>> + >>>>>> +static void >>>>>> +dc_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) >>>>>> +{ >>>>>> + struct drm_crtc_state *old_crtc_state = >>>>>> + drm_atomic_get_old_crtc_state(state, crtc); >>>>>> + struct drm_crtc_state *new_crtc_state = >>>>>> + drm_atomic_get_new_crtc_state(state, crtc); >>>>>> + struct dc_crtc *dc_crtc = to_dc_crtc(crtc); >>>>>> + int idx; >>>>>> + >>>>>> + if (drm_atomic_crtc_needs_modeset(new_crtc_state) || >>>>>> + (!old_crtc_state->active && !new_crtc_state->active)) >>>>>> + return; >>>>>> + >>>>>> + if (!drm_dev_enter(crtc->dev, &idx)) >>>>>> + goto out; >>>>>> + >>>>>> + enable_irq(dc_crtc->irq_ed_cont_shdld); >>>>>> + >>>>>> + /* flush plane update out to display */ >>>>>> + dc_ed_pec_sync_trigger(dc_crtc->ed_cont); >>>>>> + >>>>>> + DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done); >>>>>> + >>>>>> + disable_irq(dc_crtc->irq_ed_cont_shdld); >>>>>> + >>>>>> + DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg); >>>>>> + >>>>>> + drm_dev_exit(idx); >>>>>> + >>>>>> +out: >>>>>> + dc_crtc_queue_state_event(new_crtc_state); >>>>>> +} >>>>>> + >>>>>> +static void >>>>>> +dc_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state) >>>>>> +{ >>>>>> + struct drm_crtc_state *new_crtc_state = >>>>>> + drm_atomic_get_new_crtc_state(state, crtc); >>>>>> + struct drm_display_mode *adj = &new_crtc_state->adjusted_mode; >>>>>> + struct dc_crtc *dc_crtc = to_dc_crtc(crtc); >>>>>> + enum dc_link_id cf_link; >>>>>> + int idx, ret; >>>>>> + >>>>>> + dc_crtc_dbg(crtc, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj)); >>>>>> + >>>>>> + drm_crtc_vblank_on(crtc); >>>>>> + >>>>>> + if (!drm_dev_enter(crtc->dev, &idx)) >>>>>> + goto out; >>>>>> + >>>>>> + /* request display engine power-on when CRTC is enabled */ >>>>>> + ret = pm_runtime_resume_and_get(dc_crtc->de->dev); >>>>>> + if (ret < 0) >>>>>> + dc_crtc_err(crtc, "failed to get DC display engine RPM: %d\n", >>>>>> + ret); >>>>>> + >>>>>> + enable_irq(dc_crtc->irq_dec_shdld); >>>>>> + enable_irq(dc_crtc->irq_ed_cont_shdld); >>>>>> + enable_irq(dc_crtc->irq_ed_safe_shdld); >>>>>> + >>>>>> + dc_fg_cfg_videomode(dc_crtc->fg, adj); >>>>>> + >>>>>> + dc_cf_framedimensions(dc_crtc->cf_cont, >>>>>> + adj->crtc_hdisplay, adj->crtc_vdisplay); >>>>>> + dc_cf_framedimensions(dc_crtc->cf_safe, >>>>>> + adj->crtc_hdisplay, adj->crtc_vdisplay); >>>>>> + >>>>>> + /* constframe in safety stream shows blue frame */ >>>>>> + dc_cf_constantcolor_blue(dc_crtc->cf_safe); >>>>>> + cf_link = dc_cf_get_link_id(dc_crtc->cf_safe); >>>>>> + dc_ed_pec_src_sel(dc_crtc->ed_safe, cf_link); >>>>>> + >>>>>> + /* show CRTC background if no plane is enabled */ >>>>>> + if (new_crtc_state->plane_mask == 0) { >>>>>> + /* constframe in content stream shows black frame */ >>>>>> + dc_cf_constantcolor_black(dc_crtc->cf_cont); >>>>>> + >>>>>> + cf_link = dc_cf_get_link_id(dc_crtc->cf_cont); >>>>>> + dc_ed_pec_src_sel(dc_crtc->ed_cont, cf_link); >>>>>> + } >>>>>> + >>>>>> + dc_fg_enable_clock(dc_crtc->fg); >>>>>> + dc_ed_pec_sync_trigger(dc_crtc->ed_cont); >>>>>> + dc_ed_pec_sync_trigger(dc_crtc->ed_safe); >>>>>> + dc_fg_shdtokgen(dc_crtc->fg); >>>>>> + dc_fg_enable(dc_crtc->fg); >>>>>> + >>>>>> + DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_safe_shdld_done); >>>>>> + DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done); >>>>>> + DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_shdld_done); >>>>>> + >>>>>> + disable_irq(dc_crtc->irq_ed_safe_shdld); >>>>>> + disable_irq(dc_crtc->irq_ed_cont_shdld); >>>>>> + disable_irq(dc_crtc->irq_dec_shdld); >>>>>> + >>>>>> + DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(dc_crtc->fg); >>>>>> + >>>>>> + DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg); >>>>>> + >>>>>> + drm_dev_exit(idx); >>>>>> + >>>>>> +out: >>>>>> + dc_crtc_queue_state_event(new_crtc_state); >>>>>> +} >>>>>> + >>>>>> +static void >>>>>> +dc_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state) >>>>>> +{ >>>>>> + struct drm_crtc_state *new_crtc_state = >>>>>> + drm_atomic_get_new_crtc_state(state, crtc); >>>>>> + struct dc_crtc *dc_crtc = to_dc_crtc(crtc); >>>>>> + int idx, ret; >>>>>> + >>>>>> + if (!drm_dev_enter(crtc->dev, &idx)) >>>>>> + goto out; >>>>>> + >>>>>> + enable_irq(dc_crtc->irq_dec_seqcomplete); >>>>>> + dc_fg_disable(dc_crtc->fg); >>>>>> + DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_seqcomplete_done); >>>>>> + disable_irq(dc_crtc->irq_dec_seqcomplete); >>>>>> + >>>>>> + dc_fg_disable_clock(dc_crtc->fg); >>>>>> + >>>>>> + /* request pixel engine power-off as plane is off too */ >>>>>> + ret = pm_runtime_put(dc_crtc->pe->dev); >>>>>> + if (ret) >>>>>> + dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n", >>>>>> + ret); >>>>>> + >>>>>> + /* request display engine power-off when CRTC is disabled */ >>>>>> + ret = pm_runtime_put(dc_crtc->de->dev); >>>>> >>>>> Can this be expressed as a devlink between PE and DE? >>>> >>>> Looking at struct dc_{pe,de}, PE and DE are not dependent with each other, >>>> i.e., no consumer/supplier relationship(note that blit engine in PE can work >>>> by itself without DE) between them. So, it doesn't look right to link the two >>>> devices. >>> >>> Ack >>> >>>> >>>>> >>>>>> + if (ret < 0) >>>>>> + dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n", >>>>>> + ret); >>>>>> + >>>>>> + drm_dev_exit(idx); >>>>>> + >>>>>> +out: >>>>>> + drm_crtc_vblank_off(crtc); >>>>>> + >>>>>> + spin_lock_irq(&crtc->dev->event_lock); >>>>>> + if (new_crtc_state->event && !new_crtc_state->active) { >>>>>> + drm_crtc_send_vblank_event(crtc, new_crtc_state->event); >>>>>> + new_crtc_state->event = NULL; >>>>>> + } >>>>>> + spin_unlock_irq(&crtc->dev->event_lock); >>>>>> +} >>>>>> + >>>>>> +void dc_crtc_disable_at_unbind(struct drm_crtc *crtc) >>>>>> +{ >>>>>> + struct dc_crtc *dc_crtc = to_dc_crtc(crtc); >>>>>> + int ret; >>>>>> + >>>>>> + if (!dc_fg_wait_for_frame_index_moving(dc_crtc->fg)) >>>>>> + return; >>>>>> + >>>>>> + dc_fg_disable_clock(dc_crtc->fg); >>>>>> + >>>>>> + if (pm_runtime_active(dc_crtc->pe->dev)) { >>>>>> + ret = pm_runtime_put_sync(dc_crtc->pe->dev); >>>>>> + if (ret) >>>>>> + dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n", >>>>>> + ret); >>>>>> + } >>>>>> + >>>>>> + ret = pm_runtime_put_sync(dc_crtc->de->dev); >>>>>> + if (ret < 0) >>>>>> + dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n", >>>>>> + ret); >>>>>> +} >>>>>> + >>>>>> +static bool dc_crtc_get_scanout_position(struct drm_crtc *crtc, >>>>>> + bool in_vblank_irq, >>>>>> + int *vpos, int *hpos, >>>>>> + ktime_t *stime, ktime_t *etime, >>>>>> + const struct drm_display_mode *mode) >>>>>> +{ >>>>>> + struct dc_crtc *dc_crtc = to_dc_crtc(crtc); >>>>>> + int vdisplay = mode->crtc_vdisplay; >>>>>> + int vtotal = mode->crtc_vtotal; >>>>>> + bool reliable; >>>>>> + int line; >>>>>> + int idx; >>>>>> + >>>>>> + if (stime) >>>>>> + *stime = ktime_get(); >>>>>> + >>>>>> + if (!drm_dev_enter(crtc->dev, &idx)) { >>>>>> + reliable = false; >>>>>> + *vpos = 0; >>>>>> + *hpos = 0; >>>>>> + goto out; >>>>>> + } >>>>>> + >>>>>> + /* line index starts with 0 for the first active output line */ >>>>>> + line = dc_fg_get_line_index(dc_crtc->fg); >>>>>> + >>>>>> + if (line < vdisplay) >>>>>> + /* active scanout area - positive */ >>>>>> + *vpos = line + 1; >>>>>> + else >>>>>> + /* inside vblank - negative */ >>>>>> + *vpos = line - (vtotal - 1); >>>>>> + >>>>>> + *hpos = 0; >>>>>> + >>>>>> + reliable = true; >>>>>> + >>>>>> + drm_dev_exit(idx); >>>>>> +out: >>>>>> + if (etime) >>>>>> + *etime = ktime_get(); >>>>>> + >>>>>> + return reliable; >>>>>> +} >>>>>> + >>>>>> +static const struct drm_crtc_helper_funcs dc_helper_funcs = { >>>>>> + .mode_valid = dc_crtc_mode_valid, >>>>>> + .atomic_check = dc_crtc_atomic_check, >>>>>> + .atomic_begin = dc_crtc_atomic_begin, >>>>>> + .atomic_flush = dc_crtc_atomic_flush, >>>>>> + .atomic_enable = dc_crtc_atomic_enable, >>>>>> + .atomic_disable = dc_crtc_atomic_disable, >>>>>> + .get_scanout_position = dc_crtc_get_scanout_position, >>>>>> +}; >>>>>> + >>>>>> +static int dc_crtc_request_irqs(struct drm_device *drm, struct dc_crtc *dc_crtc) >>>>>> +{ >>>>>> + struct { >>>>>> + struct device *dev; >>>>>> + unsigned int irq; >>>>>> + irqreturn_t (*irq_handler)(int irq, void *dev_id); >>>>>> + } irqs[DC_CRTC_IRQS] = { >>>>>> + { >>>>>> + dc_crtc->de->dev, >>>>>> + dc_crtc->irq_dec_framecomplete, >>>>>> + dc_crtc_dec_framecomplete_irq_handler, >>>>>> + }, { >>>>>> + dc_crtc->de->dev, >>>>>> + dc_crtc->irq_dec_seqcomplete, >>>>>> + dc_crtc_common_irq_handler, >>>>>> + }, { >>>>>> + dc_crtc->de->dev, >>>>>> + dc_crtc->irq_dec_shdld, >>>>>> + dc_crtc_common_irq_handler, >>>>>> + }, { >>>>>> + dc_crtc->ed_cont->dev, >>>>>> + dc_crtc->irq_ed_cont_shdld, >>>>>> + dc_crtc_common_irq_handler, >>>>>> + }, { >>>>>> + dc_crtc->ed_safe->dev, >>>>>> + dc_crtc->irq_ed_safe_shdld, >>>>>> + dc_crtc_common_irq_handler, >>>>>> + }, >>>>>> + }; >>>>>> + int i, ret; >>>>>> + >>>>>> + for (i = 0; i < DC_CRTC_IRQS; i++) { >>>>>> + struct dc_crtc_irq *irq = &dc_crtc->irqs[i]; >>>>>> + >>>>>> + ret = devm_request_irq(irqs[i].dev, irqs[i].irq, >>>>>> + irqs[i].irq_handler, IRQF_NO_AUTOEN, >>>>>> + dev_name(irqs[i].dev), dc_crtc); >>>>>> + if (ret) { >>>>>> + dev_err(irqs[i].dev, "failed to request irq(%u): %d\n", >>>>>> + irqs[i].irq, ret); >>>>>> + return ret; >>>>>> + } >>>>>> + >>>>>> + irq->dc_crtc = dc_crtc; >>>>>> + irq->irq = irqs[i].irq; >>>>>> + } >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index) >>>>>> +{ >>>>>> + struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index]; >>>>>> + struct drm_device *drm = &dc_drm->base; >>>>>> + struct dc_de *de = dc_drm->de[crtc_index]; >>>>>> + struct dc_pe *pe = dc_drm->pe; >>>>>> + struct dc_plane *dc_primary; >>>>>> + int ret; >>>>>> + >>>>>> + dc_crtc->de = de; >>>>>> + dc_crtc->pe = pe; >>>>>> + >>>>>> + init_completion(&dc_crtc->dec_seqcomplete_done); >>>>>> + init_completion(&dc_crtc->dec_shdld_done); >>>>>> + init_completion(&dc_crtc->ed_cont_shdld_done); >>>>>> + init_completion(&dc_crtc->ed_safe_shdld_done); >>>>>> + >>>>>> + dc_crtc->cf_cont = pe->cf_cont[crtc_index]; >>>>>> + dc_crtc->cf_safe = pe->cf_safe[crtc_index]; >>>>>> + dc_crtc->ed_cont = pe->ed_cont[crtc_index]; >>>>>> + dc_crtc->ed_safe = pe->ed_safe[crtc_index]; >>>>>> + dc_crtc->fg = de->fg; >>>>>> + >>>>>> + dc_crtc->irq_dec_framecomplete = de->irq_framecomplete; >>>>>> + dc_crtc->irq_dec_seqcomplete = de->irq_seqcomplete; >>>>>> + dc_crtc->irq_dec_shdld = de->irq_shdld; >>>>>> + dc_crtc->irq_ed_safe_shdld = dc_crtc->ed_safe->irq_shdld; >>>>>> + dc_crtc->irq_ed_cont_shdld = dc_crtc->ed_cont->irq_shdld; >>>>>> + >>>>>> + dc_primary = &dc_drm->dc_primary[crtc_index]; >>>>>> + ret = dc_plane_init(dc_drm, dc_primary); >>>>>> + if (ret) { >>>>>> + dev_err(drm->dev, >>>>>> + "failed to init primary plane for display engine%u: %d\n", >>>>>> + de->id, ret); >>>>>> + return ret; >>>>>> + } >>>>>> + >>>>>> + drm_crtc_helper_add(&dc_crtc->base, &dc_helper_funcs); >>>>>> + >>>>>> + ret = drm_crtc_init_with_planes(drm, &dc_crtc->base, &dc_primary->base, >>>>>> + NULL, &dc_crtc_funcs, NULL); >>>>>> + if (ret) >>>>>> + dev_err(drm->dev, >>>>>> + "failed to add CRTC for display engine%u: %d\n", >>>>>> + de->id, ret); >>>>>> + >>>>>> + return ret; >>>>>> +} >>>>>> + >>>>>> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index) >>>>>> +{ >>>>>> + struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index]; >>>>>> + struct drm_device *drm = &dc_drm->base; >>>>>> + >>>>>> + return dc_crtc_request_irqs(drm, dc_crtc); >>>>>> +} >>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-de.h b/drivers/gpu/drm/imx/dc/dc-de.h >>>>>> index 17a44362118e..8a7b6c03a222 100644 >>>>>> --- a/drivers/gpu/drm/imx/dc/dc-de.h >>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-de.h >>>>>> @@ -13,6 +13,9 @@ >>>>>> >>>>>> #define DC_DISPLAYS 2 >>>>>> >>>>>> +#define DC_FRAMEGEN_MAX_FRAME_INDEX 0x3ffff >>>>>> +#define DC_FRAMEGEN_MAX_CLOCK_KHZ 300000 >>>>>> + >>>>>> struct dc_fg { >>>>>> struct device *dev; >>>>>> struct regmap *reg; >>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.c b/drivers/gpu/drm/imx/dc/dc-drv.c >>>>>> index fd68861f770a..1e4b8afa3eec 100644 >>>>>> --- a/drivers/gpu/drm/imx/dc/dc-drv.c >>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-drv.c >>>>>> @@ -3,11 +3,254 @@ >>>>>> * Copyright 2024 NXP >>>>>> */ >>>>>> >>>>>> +#include <linux/clk.h> >>>>>> +#include <linux/component.h> >>>>>> +#include <linux/device.h> >>>>>> +#include <linux/dma-mapping.h> >>>>>> +#include <linux/mod_devicetable.h> >>>>>> #include <linux/module.h> >>>>>> +#include <linux/of.h> >>>>>> +#include <linux/of_platform.h> >>>>>> #include <linux/platform_device.h> >>>>>> +#include <linux/pm.h> >>>>>> +#include <linux/pm_runtime.h> >>>>>> >>>>>> +#include <drm/drm_atomic_helper.h> >>>>>> +#include <drm/drm_client_setup.h> >>>>>> +#include <drm/drm_crtc.h> >>>>>> +#include <drm/drm_drv.h> >>>>>> +#include <drm/drm_fbdev_dma.h> >>>>>> +#include <drm/drm_fourcc.h> >>>>>> +#include <drm/drm_gem_dma_helper.h> >>>>>> +#include <drm/drm_managed.h> >>>>>> +#include <drm/drm_modeset_helper.h> >>>>>> +#include <drm/drm_of.h> >>>>>> + >>>>>> +#include "dc-de.h" >>>>>> #include "dc-drv.h" >>>>>> >>>>>> +struct dc_priv { >>>>>> + struct drm_device *drm; >>>>>> + struct clk *clk_cfg; >>>>>> +}; >>>>>> + >>>>>> +DEFINE_DRM_GEM_DMA_FOPS(dc_drm_driver_fops); >>>>>> + >>>>>> +static struct drm_driver dc_drm_driver = { >>>>>> + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, >>>>>> + DRM_GEM_DMA_DRIVER_OPS, >>>>>> + DRM_FBDEV_DMA_DRIVER_OPS, >>>>>> + .fops = &dc_drm_driver_fops, >>>>>> + .name = "imx8-dc", >>>>>> + .desc = "i.MX8 DC DRM graphics", >>>>>> + .date = "20240530", >>>>>> + .major = 1, >>>>>> + .minor = 0, >>>>>> + .patchlevel = 0, >>>>>> +}; >>>>>> + >>>>>> +static void >>>>>> +dc_add_components(struct device *dev, struct component_match **matchptr) >>>>>> +{ >>>>>> + struct device_node *child, *grandchild; >>>>>> + >>>>>> + for_each_available_child_of_node(dev->of_node, child) { >>>>>> + /* The interrupt controller is not a component. */ >>>>>> + if (of_device_is_compatible(child, "fsl,imx8qxp-dc-intc")) >>>>>> + continue; >>>>>> + >>>>>> + drm_of_component_match_add(dev, matchptr, component_compare_of, >>>>>> + child); >>>>>> + >>>>>> + for_each_available_child_of_node(child, grandchild) >>>>>> + drm_of_component_match_add(dev, matchptr, >>>>>> + component_compare_of, >>>>>> + grandchild); >>>>>> + } >>>>>> +} >>>>>> + >>>>>> +static void dc_drm_component_unbind_all(void *ptr) >>>>>> +{ >>>>>> + struct dc_drm_device *dc_drm = ptr; >>>>>> + struct drm_device *drm = &dc_drm->base; >>>>>> + >>>>>> + component_unbind_all(drm->dev, dc_drm); >>>>>> +} >>>>>> + >>>>>> +static int dc_drm_bind(struct device *dev) >>>>>> +{ >>>>>> + struct dc_priv *priv = dev_get_drvdata(dev); >>>>>> + struct dc_drm_device *dc_drm; >>>>>> + struct drm_device *drm; >>>>>> + int ret; >>>>>> + >>>>>> + dc_drm = devm_drm_dev_alloc(dev, &dc_drm_driver, struct dc_drm_device, >>>>>> + base); >>>>>> + if (IS_ERR(dc_drm)) >>>>>> + return PTR_ERR(dc_drm); >>>>>> + >>>>>> + drm = &dc_drm->base; >>>>>> + >>>>>> + ret = component_bind_all(dev, dc_drm); >>>>>> + if (ret) >>>>>> + return ret; >>>>>> + >>>>>> + ret = devm_add_action_or_reset(dev, dc_drm_component_unbind_all, >>>>>> + dc_drm); >>>>>> + if (ret) >>>>>> + return ret; >>>>>> + >>>>>> + ret = dc_kms_init(dc_drm); >>>>>> + if (ret) >>>>>> + return ret; >>>>>> + >>>>>> + ret = drm_dev_register(drm, 0); >>>>>> + if (ret) { >>>>>> + dev_err(dev, "failed to register drm device: %d\n", ret); >>>>>> + goto err; >>>>>> + } >>>>>> + >>>>>> + drm_client_setup_with_fourcc(drm, DRM_FORMAT_XRGB8888); >>>>>> + >>>>>> + priv->drm = drm; >>>>>> + >>>>>> + return 0; >>>>>> + >>>>>> +err: >>>>>> + dc_kms_uninit(dc_drm); >>>>>> + >>>>>> + return ret; >>>>>> +} >>>>>> + >>>>>> +static void dc_drm_unbind(struct device *dev) >>>>>> +{ >>>>>> + struct dc_priv *priv = dev_get_drvdata(dev); >>>>>> + struct dc_drm_device *dc_drm = to_dc_drm_device(priv->drm); >>>>>> + struct drm_device *drm = &dc_drm->base; >>>>>> + struct drm_crtc *crtc; >>>>>> + >>>>>> + priv->drm = NULL; >>>>>> + drm_dev_unplug(drm); >>>>>> + dc_kms_uninit(dc_drm); >>>>>> + drm_atomic_helper_shutdown(drm); >>>>>> + >>>>>> + drm_for_each_crtc(crtc, drm) >>>>>> + dc_crtc_disable_at_unbind(crtc); >>>>> >>>>> There should be no need for that. drm_atomic_helper_shutdown() should >>>>> disable all the CRTCs. >>>> >>>> In case DRM device is unplugged, drm_atomic_helper_shutdown does not >>>> effectively disable the CRTCs due to the bypassed logics wrapped by >>>> drm_dev_{enter,exit}. That's why dc_crtc_disable_at_unbind() is called >>>> here to effectively disable the CRTCs. >>> >>> I see. I haven't faced drm_dev_unplug() earlier. I checked, the "not >>> disabled" behaviour is documented and all other drivers don't perform >>> any kind of cleanup afterwards. Thus I think it's safe to drop the >>> dc_crtc_disable_at_unbind() unless it causes any kind of HW issues. >> >> IMHO, it's a bit safer to set the HWs backing the CRTCs to a known state >> at the unbind stage by properly disabling them, in case there is any >> unknown SW or HW issue. Plus, i.MX8qm/qxp SoCs don't provide reset >> signals to the Display Controllers(the resets DT property is optional), >> otherwise, this disablement function can be dropped(at least for now). > > I'd say, a rule of thumb is "Behave similarly" to the existing > drivers. We've had a long-standing TODO item expressed in the DRM > docs: the drm_dev_unplug() vs drm_atomic_helper_shutdown(). I think > your issue should be solved in a generic way. One random idea would be > to have a sepecial drm_dev_unplug() version which is intended for the > normal shutdown path, it should mark the device as unavailable after > disabling the outputs and unregistering it from userspace. I'll drop dc_crtc_disable_at_unbind() in next version to "behave similarly" to the existing drivers. > >> >>> >>>> >>>>> >>>>> Also, who is going to do drm_dev_unregister()? I don't see it in the >>>>> code. >>>> >>>> drm_dev_unplug() right above calls drm_dev_unregister(). >>>> >>>>> >>>>>> +} >>>>>> + >>>>>> +static const struct component_master_ops dc_drm_ops = { >>>>>> + .bind = dc_drm_bind, >>>>>> + .unbind = dc_drm_unbind, >>>>>> +}; >>>>>> + >>>>>> +static int dc_probe(struct platform_device *pdev) >>>>>> +{ >>>>>> + struct component_match *match = NULL; >>>>>> + struct dc_priv *priv; >>>>>> + int ret; >>>>>> + >>>>>> + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); >>>>>> + if (!priv) >>>>>> + return -ENOMEM; >>>>>> + >>>>>> + priv->clk_cfg = devm_clk_get(&pdev->dev, NULL); >>>>>> + if (IS_ERR(priv->clk_cfg)) >>>>>> + return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk_cfg), >>>>>> + "failed to get cfg clock\n"); >>>>>> + >>>>>> + dev_set_drvdata(&pdev->dev, priv); >>>>>> + >>>>>> + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); >>>>>> + if (ret) >>>>>> + return ret; >>>>>> + >>>>>> + ret = devm_pm_runtime_enable(&pdev->dev); >>>>>> + if (ret) >>>>>> + return ret; >>>>>> + >>>>>> + ret = devm_of_platform_populate(&pdev->dev); >>>>>> + if (ret) >>>>>> + return ret; >>>>>> + >>>>>> + dc_add_components(&pdev->dev, &match); >>>>>> + >>>>>> + ret = component_master_add_with_match(&pdev->dev, &dc_drm_ops, match); >>>>>> + if (ret) >>>>>> + return dev_err_probe(&pdev->dev, ret, >>>>>> + "failed to add component master\n"); >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +static void dc_remove(struct platform_device *pdev) >>>>>> +{ >>>>>> + component_master_del(&pdev->dev, &dc_drm_ops); >>>>>> +} >>>>>> + >>>>>> +static int dc_runtime_suspend(struct device *dev) >>>>>> +{ >>>>>> + struct dc_priv *priv = dev_get_drvdata(dev); >>>>>> + >>>>>> + clk_disable_unprepare(priv->clk_cfg); >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +static int dc_runtime_resume(struct device *dev) >>>>>> +{ >>>>>> + struct dc_priv *priv = dev_get_drvdata(dev); >>>>>> + int ret; >>>>>> + >>>>>> + ret = clk_prepare_enable(priv->clk_cfg); >>>>>> + if (ret) >>>>>> + dev_err(dev, "failed to enable cfg clock: %d\n", ret); >>>>>> + >>>>>> + return ret; >>>>>> +} >>>>>> + >>>>>> +static int dc_suspend(struct device *dev) >>>>>> +{ >>>>>> + struct dc_priv *priv = dev_get_drvdata(dev); >>>>>> + >>>>>> + return drm_mode_config_helper_suspend(priv->drm); >>>>>> +} >>>>>> + >>>>>> +static int dc_resume(struct device *dev) >>>>>> +{ >>>>>> + struct dc_priv *priv = dev_get_drvdata(dev); >>>>>> + >>>>>> + return drm_mode_config_helper_resume(priv->drm); >>>>>> +} >>>>>> + >>>>>> +static void dc_shutdown(struct platform_device *pdev) >>>>>> +{ >>>>>> + struct dc_priv *priv = dev_get_drvdata(&pdev->dev); >>>>>> + >>>>>> + drm_atomic_helper_shutdown(priv->drm); >>>>>> +} >>>>>> + >>>>>> +static const struct dev_pm_ops dc_pm_ops = { >>>>>> + RUNTIME_PM_OPS(dc_runtime_suspend, dc_runtime_resume, NULL) >>>>>> + SYSTEM_SLEEP_PM_OPS(dc_suspend, dc_resume) >>>>>> +}; >>>>>> + >>>>>> +static const struct of_device_id dc_dt_ids[] = { >>>>>> + { .compatible = "fsl,imx8qxp-dc", }, >>>>>> + { /* sentinel */ } >>>>>> +}; >>>>>> +MODULE_DEVICE_TABLE(of, dc_dt_ids); >>>>>> + >>>>>> +static struct platform_driver dc_driver = { >>>>>> + .probe = dc_probe, >>>>>> + .remove = dc_remove, >>>>>> + .shutdown = dc_shutdown, >>>>>> + .driver = { >>>>>> + .name = "imx8-dc", >>>>>> + .of_match_table = dc_dt_ids, >>>>>> + .pm = pm_sleep_ptr(&dc_pm_ops), >>>>>> + }, >>>>>> +}; >>>>>> + >>>>>> static struct platform_driver * const dc_drivers[] = { >>>>>> &dc_cf_driver, >>>>>> &dc_de_driver, >>>>>> @@ -19,6 +262,7 @@ static struct platform_driver * const dc_drivers[] = { >>>>>> &dc_lb_driver, >>>>>> &dc_pe_driver, >>>>>> &dc_tc_driver, >>>>>> + &dc_driver, >>>>>> }; >>>>>> >>>>>> static int __init dc_drm_init(void) >>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.h b/drivers/gpu/drm/imx/dc/dc-drv.h >>>>>> index 3b11f4862c6c..39a771a13933 100644 >>>>>> --- a/drivers/gpu/drm/imx/dc/dc-drv.h >>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-drv.h >>>>>> @@ -6,19 +6,38 @@ >>>>>> #ifndef __DC_DRV_H__ >>>>>> #define __DC_DRV_H__ >>>>>> >>>>>> +#include <linux/container_of.h> >>>>>> #include <linux/platform_device.h> >>>>>> >>>>>> #include <drm/drm_device.h> >>>>>> +#include <drm/drm_encoder.h> >>>>>> >>>>>> #include "dc-de.h" >>>>>> +#include "dc-kms.h" >>>>>> #include "dc-pe.h" >>>>>> >>>>>> struct dc_drm_device { >>>>>> struct drm_device base; >>>>>> + struct dc_crtc dc_crtc[DC_DISPLAYS]; >>>>>> + struct dc_plane dc_primary[DC_DISPLAYS]; >>>>>> + struct drm_encoder encoder[DC_DISPLAYS]; >>>>>> struct dc_de *de[DC_DISPLAYS]; >>>>>> struct dc_pe *pe; >>>>>> }; >>>>>> >>>>>> +static inline struct dc_drm_device *to_dc_drm_device(struct drm_device *drm) >>>>>> +{ >>>>>> + return container_of(drm, struct dc_drm_device, base); >>>>>> +} >>>>>> + >>>>>> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index); >>>>>> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index); >>>>>> + >>>>>> +int dc_kms_init(struct dc_drm_device *dc_drm); >>>>>> +void dc_kms_uninit(struct dc_drm_device *dc_drm); >>>>>> + >>>>>> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane); >>>>>> + >>>>>> extern struct platform_driver dc_cf_driver; >>>>>> extern struct platform_driver dc_ed_driver; >>>>>> extern struct platform_driver dc_de_driver; >>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.c b/drivers/gpu/drm/imx/dc/dc-kms.c >>>>>> new file mode 100644 >>>>>> index 000000000000..2b18aa37a4a8 >>>>>> --- /dev/null >>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-kms.c >>>>>> @@ -0,0 +1,143 @@ >>>>>> +// SPDX-License-Identifier: GPL-2.0+ >>>>>> +/* >>>>>> + * Copyright 2024 NXP >>>>>> + */ >>>>>> + >>>>>> +#include <linux/of.h> >>>>>> +#include <linux/of_graph.h> >>>>>> + >>>>>> +#include <drm/drm_atomic_helper.h> >>>>>> +#include <drm/drm_bridge.h> >>>>>> +#include <drm/drm_bridge_connector.h> >>>>>> +#include <drm/drm_connector.h> >>>>>> +#include <drm/drm_crtc.h> >>>>>> +#include <drm/drm_device.h> >>>>>> +#include <drm/drm_encoder.h> >>>>>> +#include <drm/drm_gem_framebuffer_helper.h> >>>>>> +#include <drm/drm_mode_config.h> >>>>>> +#include <drm/drm_print.h> >>>>>> +#include <drm/drm_probe_helper.h> >>>>>> +#include <drm/drm_simple_kms_helper.h> >>>>>> +#include <drm/drm_vblank.h> >>>>>> + >>>>>> +#include "dc-de.h" >>>>>> +#include "dc-drv.h" >>>>>> +#include "dc-kms.h" >>>>>> + >>>>>> +static const struct drm_mode_config_funcs dc_drm_mode_config_funcs = { >>>>>> + .fb_create = drm_gem_fb_create, >>>>>> + .atomic_check = drm_atomic_helper_check, >>>>>> + .atomic_commit = drm_atomic_helper_commit, >>>>>> +}; >>>>>> + >>>>>> +static int dc_kms_init_encoder_per_crtc(struct dc_drm_device *dc_drm, >>>>>> + int crtc_index) >>>>>> +{ >>>>>> + struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index]; >>>>>> + struct drm_device *drm = &dc_drm->base; >>>>>> + struct drm_crtc *crtc = &dc_crtc->base; >>>>>> + struct drm_connector *connector; >>>>>> + struct device *dev = drm->dev; >>>>>> + struct drm_encoder *encoder; >>>>>> + struct drm_bridge *bridge; >>>>>> + int ret; >>>>>> + >>>>>> + bridge = devm_drm_of_get_bridge(dev, dc_crtc->de->tc->dev->of_node, >>>>>> + 0, 0); >>>>>> + if (IS_ERR(bridge)) { >>>>>> + ret = PTR_ERR(bridge); >>>>>> + if (ret == -ENODEV) >>>>>> + return 0; >>>>>> + >>>>>> + return dev_err_probe(dev, ret, >>>>>> + "failed to find bridge for CRTC%u\n", >>>>>> + crtc->index); >>>>>> + } >>>>>> + >>>>>> + encoder = &dc_drm->encoder[crtc_index]; >>>>>> + ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE); >>>>>> + if (ret) { >>>>>> + dev_err(dev, "failed to initialize encoder for CRTC%u: %d\n", >>>>>> + crtc->index, ret); >>>>>> + return ret; >>>>>> + } >>>>>> + >>>>>> + encoder->possible_crtcs = drm_crtc_mask(crtc); >>>>>> + >>>>>> + ret = drm_bridge_attach(encoder, bridge, NULL, >>>>>> + DRM_BRIDGE_ATTACH_NO_CONNECTOR); >>>>>> + if (ret) { >>>>>> + dev_err(dev, >>>>>> + "failed to attach bridge to encoder for CRTC%u: %d\n", >>>>>> + crtc->index, ret); >>>>>> + return ret; >>>>>> + } >>>>>> + >>>>>> + connector = drm_bridge_connector_init(drm, encoder); >>>>>> + if (IS_ERR(connector)) { >>>>>> + ret = PTR_ERR(connector); >>>>>> + dev_err(dev, "failed to init bridge connector for CRTC%u: %d\n", >>>>>> + crtc->index, ret); >>>>>> + return ret; >>>>>> + } >>>>>> + >>>>>> + ret = drm_connector_attach_encoder(connector, encoder); >>>>>> + if (ret) >>>>>> + dev_err(dev, >>>>>> + "failed to attach encoder to connector for CRTC%u: %d\n", >>>>>> + crtc->index, ret); >>>>>> + >>>>>> + return ret; >>>>>> +} >>>>>> + >>>>>> +int dc_kms_init(struct dc_drm_device *dc_drm) >>>>>> +{ >>>>>> + struct drm_device *drm = &dc_drm->base; >>>>>> + int ret, i; >>>>>> + >>>>>> + ret = drmm_mode_config_init(drm); >>>>>> + if (ret) >>>>>> + return ret; >>>>>> + >>>>>> + drm->mode_config.min_width = 60; >>>>>> + drm->mode_config.min_height = 60; >>>>>> + drm->mode_config.max_width = 8192; >>>>>> + drm->mode_config.max_height = 8192; >>>>>> + drm->mode_config.funcs = &dc_drm_mode_config_funcs; >>>>>> + >>>>>> + drm->vblank_disable_immediate = true; >>>>>> + drm->max_vblank_count = DC_FRAMEGEN_MAX_FRAME_INDEX; >>>>>> + >>>>>> + for (i = 0; i < DC_DISPLAYS; i++) { >>>>>> + ret = dc_crtc_init(dc_drm, i); >>>>>> + if (ret) >>>>>> + return ret; >>>>>> + >>>>>> + ret = dc_kms_init_encoder_per_crtc(dc_drm, i); >>>>>> + if (ret) >>>>>> + return ret; >>>>>> + } >>>>>> + >>>>>> + for (i = 0; i < DC_DISPLAYS; i++) { >>>>>> + ret = dc_crtc_post_init(dc_drm, i); >>>>> >>>>> Can you use .late_register for this? >>>> >>>> Kerneldoc of struct drm_crtc_funcs::late_register says it's used to register >>>> additional userspace interfaces like debugfs interfaces. And, it seems that >>>> everyone implementing this uses it to add debugfs interfaces. So, it will >>>> kind of abuse it to do CRTC post initialization. >>> >>> Why can't they be requested earlier then? >> >> If I request them earlier in dc_crtc_init(), then they cannot be freed by >> devm_irq_release() when devm_drm_of_get_bridge() called by >> dc_kms_init_encoder_per_crtc() returns -EPROBE_DEFER(which means failing >> to find the first DRM bridge for the CRTC). Why can't they be freed by >> devm_irq_release()? Because they are requested by the devices of ExtDsts >> and Display Engines and their drivers are not removed during the probe >> deferral dance. Furthermore, -EPROBE_DEFER won't be returned after >> dc_crtc_post_init() since the later called drm_vblank_init() doesn't >> return -EPROBE_DEFER anyway, so it's fine to call dc_crtc_post_init() here. >> >> I met the irq free issue on my i.MX8qxp MEK board before, i.e., -EBUSY is >> returned when requesting them again, so it's tested. > > A typical solution is to request all resources before binding the > device as a component. Don't tell me that your interrupt controller is > another component of the DRM device :-) The IRQ handlers are _highly_ related to the CRTC driver(especially the dc_crtc_dec_framecomplete_irq_handler() where vblank is handled), so maybe it's more appropriate to request the IRQs and implement the IRQ handlers in dc-crtc.c instead of doing them in dc-{de,ed}.c. No? > >> >>> >>>> >>>>> >>>>>> + if (ret) >>>>>> + return ret; >>>>>> + } >>>>>> + >>>>>> + ret = drm_vblank_init(drm, DC_DISPLAYS); >>>>>> + if (ret) { >>>>>> + dev_err(drm->dev, "failed to init vblank support: %d\n", ret); >>>>>> + return ret; >>>>>> + } >>>>>> + >>>>>> + drm_mode_config_reset(drm); >>>>>> + >>>>>> + drm_kms_helper_poll_init(drm); >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +void dc_kms_uninit(struct dc_drm_device *dc_drm) >>>>>> +{ >>>>>> + drm_kms_helper_poll_fini(&dc_drm->base); >>>>>> +} >>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.h b/drivers/gpu/drm/imx/dc/dc-kms.h >>>>>> new file mode 100644 >>>>>> index 000000000000..57f6e0c15f57 >>>>>> --- /dev/null >>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-kms.h >>>>>> @@ -0,0 +1,58 @@ >>>>>> +/* SPDX-License-Identifier: GPL-2.0+ */ >>>>>> +/* >>>>>> + * Copyright 2024 NXP >>>>>> + */ >>>>>> + >>>>>> +#ifndef __DC_KMS_H__ >>>>>> +#define __DC_KMS_H__ >>>>>> + >>>>>> +#include <linux/completion.h> >>>>>> + >>>>>> +#include <drm/drm_crtc.h> >>>>>> +#include <drm/drm_plane.h> >>>>>> +#include <drm/drm_vblank.h> >>>>>> + >>>>>> +#include "dc-de.h" >>>>>> +#include "dc-fu.h" >>>>>> +#include "dc-pe.h" >>>>>> + >>>>>> +#define DC_CRTC_IRQS 5 >>>>>> + >>>>>> +struct dc_crtc_irq { >>>>>> + struct dc_crtc *dc_crtc; >>>>>> + unsigned int irq; >>>>>> +}; >>>>>> + >>>>> >>>>> Please provide some documentation for the structure in the form of the >>>>> kerneldoc. E.g. what is the difference between ed_cont and ed_safe? >>>> >>>> Will add kerneldoc for struct dc_{crtc,plane,drm_device} and tell the >>>> difference between ed_cont(content stream) and ed_safe(safety stream). >>>> >>>>> The de and fg pointers are global, please don't cache them >>>>> unnecessarily. >>>> >>>> Global? I don't catch your meaning, sorry. To me, it's handy to access de >>>> and fg via the two pointers in struct dc_crtc. >>> >>> I had to spend some time understanding if they are per-CRTC or if there >>> are a single instances of those subdevices. Thus I suggest to access >> >> They are per-CRTC. One Display Controller contains two Display Engines, >> hence two CRTCs. > > I'm sorry, I probably meant PE here. DE (and consequently FG) are > per-CRTC indeed. Alright, I'll drop PE from struct dc_crtc and access it via struct dc_drm_device in next version. > >> >>> them through dc_drm_device. You can ignore this suggestion though. >> >> I'll ignore this. Thanks anyway. >> >>> >>>> >>>>> >>>>>> +struct dc_crtc { >>>>>> + struct drm_crtc base; >>>>>> + struct dc_de *de; >>>>>> + struct dc_pe *pe; >>>>>> + struct dc_cf *cf_cont; >>>>>> + struct dc_cf *cf_safe; >>>>>> + struct dc_ed *ed_cont; >>>>>> + struct dc_ed *ed_safe; >>>>>> + struct dc_fg *fg; >>>>>> + unsigned int irq_dec_framecomplete; >>>>>> + unsigned int irq_dec_seqcomplete; >>>>>> + unsigned int irq_dec_shdld; >>>>>> + unsigned int irq_ed_cont_shdld; >>>>>> + unsigned int irq_ed_safe_shdld; >>>>>> + struct completion dec_seqcomplete_done; >>>>>> + struct completion dec_shdld_done; >>>>>> + struct completion ed_safe_shdld_done; >>>>>> + struct completion ed_cont_shdld_done; >>>>>> + struct drm_pending_vblank_event *event; >>>>>> + struct dc_crtc_irq irqs[DC_CRTC_IRQS]; >>>>>> +}; >>>>>> + >>>>>> +struct dc_plane { >>>>>> + struct drm_plane base; >>>>>> + struct dc_fu *fu; >>>>>> + struct dc_cf *cf; >>>>>> + struct dc_lb *lb; >>>>>> + struct dc_ed *ed; >>>>>> +}; >>>>>> + >>>>>> +void dc_crtc_disable_at_unbind(struct drm_crtc *crtc); >>>>>> + >>>>>> +#endif /* __DC_KMS_H__ */ >>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-plane.c b/drivers/gpu/drm/imx/dc/dc-plane.c >>>>>> new file mode 100644 >>>>>> index 000000000000..78d0d2cd3451 >>>>>> --- /dev/null >>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-plane.c >>>>>> @@ -0,0 +1,241 @@ >>>>>> +// SPDX-License-Identifier: GPL-2.0+ >>>>>> +/* >>>>>> + * Copyright 2024 NXP >>>>>> + */ >>>>>> + >>>>>> +#include <linux/container_of.h> >>>>>> + >>>>>> +#include <drm/drm_atomic.h> >>>>>> +#include <drm/drm_atomic_helper.h> >>>>>> +#include <drm/drm_atomic_state_helper.h> >>>>>> +#include <drm/drm_crtc.h> >>>>>> +#include <drm/drm_drv.h> >>>>>> +#include <drm/drm_fb_dma_helper.h> >>>>>> +#include <drm/drm_fourcc.h> >>>>>> +#include <drm/drm_framebuffer.h> >>>>>> +#include <drm/drm_gem_atomic_helper.h> >>>>>> +#include <drm/drm_plane_helper.h> >>>>>> +#include <drm/drm_print.h> >>>>>> + >>>>>> +#include "dc-drv.h" >>>>>> +#include "dc-fu.h" >>>>>> +#include "dc-kms.h" >>>>>> + >>>>>> +#define DC_PLANE_MAX_PITCH 0x10000 >>>>>> +#define DC_PLANE_MAX_PIX_CNT 8192 >>>>>> + >>>>>> +#define dc_plane_dbg(plane, fmt, ...) \ >>>>>> +do { \ >>>>>> + typeof(plane) _plane = (plane); \ >>>>>> + drm_dbg_kms(_plane->dev, "[PLANE:%d:%s] " fmt, \ >>>>>> + _plane->base.id, _plane->name, ##__VA_ARGS__); \ >>>>>> +} while (0) >>>>>> + >>>>>> +static const uint32_t dc_plane_formats[] = { >>>>>> + DRM_FORMAT_XRGB8888, >>>>>> +}; >>>>>> + >>>>>> +static const struct drm_plane_funcs dc_plane_funcs = { >>>>>> + .update_plane = drm_atomic_helper_update_plane, >>>>>> + .disable_plane = drm_atomic_helper_disable_plane, >>>>>> + .destroy = drm_plane_cleanup, >>>>>> + .reset = drm_atomic_helper_plane_reset, >>>>>> + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, >>>>>> + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, >>>>>> +}; >>>>>> + >>>>>> +static inline struct dc_plane *to_dc_plane(struct drm_plane *plane) >>>>>> +{ >>>>>> + return container_of(plane, struct dc_plane, base); >>>>>> +} >>>>>> + >>>>>> +static int dc_plane_check_no_off_screen(struct drm_plane_state *state, >>>>>> + struct drm_crtc_state *crtc_state) >>>>>> +{ >>>>>> + if (state->dst.x1 < 0 || state->dst.y1 < 0 || >>>>>> + state->dst.x2 > crtc_state->adjusted_mode.hdisplay || >>>>>> + state->dst.y2 > crtc_state->adjusted_mode.vdisplay) { >>>>>> + dc_plane_dbg(state->plane, "no off screen\n"); >>>>>> + return -EINVAL; >>>>>> + } >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +static int dc_plane_check_max_source_resolution(struct drm_plane_state *state) >>>>>> +{ >>>>>> + int src_h = drm_rect_height(&state->src) >> 16; >>>>>> + int src_w = drm_rect_width(&state->src) >> 16; >>>>>> + >>>>>> + if (src_w > DC_PLANE_MAX_PIX_CNT || src_h > DC_PLANE_MAX_PIX_CNT) { >>>>>> + dc_plane_dbg(state->plane, "invalid source resolution\n"); >>>>>> + return -EINVAL; >>>>>> + } >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +static int dc_plane_check_fb(struct drm_plane_state *state) >>>>>> +{ >>>>>> + struct drm_framebuffer *fb = state->fb; >>>>>> + dma_addr_t baseaddr = drm_fb_dma_get_gem_addr(fb, state, 0); >>>>>> + >>>>>> + /* base address alignment */ >>>>>> + if (baseaddr & 0x3) { >>>>>> + dc_plane_dbg(state->plane, "fb bad baddr alignment\n"); >>>>>> + return -EINVAL; >>>>>> + } >>>>>> + >>>>>> + /* pitches[0] range */ >>>>>> + if (fb->pitches[0] > DC_PLANE_MAX_PITCH) { >>>>>> + dc_plane_dbg(state->plane, "fb pitches[0] is out of range\n"); >>>>>> + return -EINVAL; >>>>>> + } >>>>>> + >>>>>> + /* pitches[0] alignment */ >>>>>> + if (fb->pitches[0] & 0x3) { >>>>>> + dc_plane_dbg(state->plane, "fb bad pitches[0] alignment\n"); >>>>>> + return -EINVAL; >>>>>> + } >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> + >>>>>> +static int >>>>>> +dc_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state) >>>>>> +{ >>>>>> + struct drm_plane_state *plane_state = >>>>>> + drm_atomic_get_new_plane_state(state, plane); >>>>>> + struct drm_crtc_state *crtc_state; >>>>>> + int ret; >>>>>> + >>>>>> + /* ok to disable */ >>>>>> + if (!plane_state->fb) >>>>>> + return 0; >>>>>> + >>>>>> + if (!plane_state->crtc) { >>>>>> + dc_plane_dbg(plane, "no CRTC in plane state\n"); >>>>>> + return -EINVAL; >>>>>> + } >>>>>> + >>>>>> + crtc_state = >>>>>> + drm_atomic_get_existing_crtc_state(state, plane_state->crtc); >>>>>> + if (WARN_ON(!crtc_state)) >>>>>> + return -EINVAL; >>>>>> + >>>>>> + ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state, >>>>>> + DRM_PLANE_NO_SCALING, >>>>>> + DRM_PLANE_NO_SCALING, >>>>>> + true, false); >>>>>> + if (ret) { >>>>>> + dc_plane_dbg(plane, "failed to check plane state: %d\n", ret); >>>>>> + return ret; >>>>>> + } >>>>>> + >>>>>> + ret = dc_plane_check_no_off_screen(plane_state, crtc_state); >>>>>> + if (ret) >>>>>> + return ret; >>>>>> + >>>>>> + ret = dc_plane_check_max_source_resolution(plane_state); >>>>>> + if (ret) >>>>>> + return ret; >>>>>> + >>>>>> + return dc_plane_check_fb(plane_state); >>>>>> +} >>>>>> + >>>>>> +static void >>>>>> +dc_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state) >>>>>> +{ >>>>>> + struct drm_plane_state *new_state = >>>>>> + drm_atomic_get_new_plane_state(state, plane); >>>>>> + struct dc_plane *dplane = to_dc_plane(plane); >>>>>> + struct drm_framebuffer *fb = new_state->fb; >>>>>> + const struct dc_fu_ops *fu_ops; >>>>>> + struct dc_lb *lb = dplane->lb; >>>>>> + struct dc_fu *fu = dplane->fu; >>>>>> + dma_addr_t baseaddr; >>>>>> + int src_w, src_h; >>>>>> + int idx; >>>>>> + >>>>>> + if (!drm_dev_enter(plane->dev, &idx)) >>>>>> + return; >>>>>> + >>>>>> + src_w = drm_rect_width(&new_state->src) >> 16; >>>>>> + src_h = drm_rect_height(&new_state->src) >> 16; >>>>>> + >>>>>> + baseaddr = drm_fb_dma_get_gem_addr(fb, new_state, 0); >>>>>> + >>>>>> + fu_ops = dc_fu_get_ops(dplane->fu); >>>>>> + >>>>>> + fu_ops->set_layerblend(fu, lb); >>>>>> + fu_ops->set_burstlength(fu, baseaddr); >>>>>> + fu_ops->set_src_stride(fu, DC_FETCHUNIT_FRAC0, fb->pitches[0]); >>>>>> + fu_ops->set_src_buf_dimensions(fu, DC_FETCHUNIT_FRAC0, src_w, src_h); >>>>>> + fu_ops->set_fmt(fu, DC_FETCHUNIT_FRAC0, fb->format); >>>>>> + fu_ops->set_framedimensions(fu, src_w, src_h); >>>>>> + fu_ops->set_baseaddress(fu, DC_FETCHUNIT_FRAC0, baseaddr); >>>>>> + fu_ops->enable_src_buf(fu, DC_FETCHUNIT_FRAC0); >>>>>> + >>>>>> + dc_plane_dbg(plane, "uses %s\n", fu_ops->get_name(fu)); >>>>>> + >>>>>> + dc_lb_pec_dynamic_prim_sel(lb, dc_cf_get_link_id(dplane->cf)); >>>>>> + dc_lb_pec_dynamic_sec_sel(lb, fu_ops->get_link_id(fu)); >>>>>> + dc_lb_mode(lb, LB_BLEND); >>>>>> + dc_lb_position(lb, new_state->dst.x1, new_state->dst.y1); >>>>>> + dc_lb_pec_clken(lb, CLKEN_AUTOMATIC); >>>>>> + >>>>>> + dc_plane_dbg(plane, "uses LayerBlend%u\n", dc_lb_get_id(lb)); >>>>>> + >>>>>> + /* set ExtDst's source to LayerBlend */ >>>>>> + dc_ed_pec_src_sel(dplane->ed, dc_lb_get_link_id(lb)); >>>>>> + >>>>>> + drm_dev_exit(idx); >>>>>> +} >>>>>> + >>>>>> +static void dc_plane_atomic_disable(struct drm_plane *plane, >>>>>> + struct drm_atomic_state *state) >>>>>> +{ >>>>>> + struct dc_plane *dplane = to_dc_plane(plane); >>>>>> + const struct dc_fu_ops *fu_ops; >>>>>> + int idx; >>>>>> + >>>>>> + if (!drm_dev_enter(plane->dev, &idx)) >>>>>> + return; >>>>>> + >>>>>> + /* disable fetchunit in shadow */ >>>>>> + fu_ops = dc_fu_get_ops(dplane->fu); >>>>>> + fu_ops->disable_src_buf(dplane->fu, DC_FETCHUNIT_FRAC0); >>>>>> + >>>>>> + /* set ExtDst's source to ConstFrame */ >>>>>> + dc_ed_pec_src_sel(dplane->ed, dc_cf_get_link_id(dplane->cf)); >>>>>> + >>>>>> + drm_dev_exit(idx); >>>>>> +} >>>>>> + >>>>>> +static const struct drm_plane_helper_funcs dc_plane_helper_funcs = { >>>>>> + .atomic_check = dc_plane_atomic_check, >>>>>> + .atomic_update = dc_plane_atomic_update, >>>>>> + .atomic_disable = dc_plane_atomic_disable, >>>>>> +}; >>>>>> + >>>>>> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane) >>>>>> +{ >>>>>> + struct drm_plane *plane = &dc_plane->base; >>>>>> + int ret; >>>>>> + >>>>>> + ret = drm_universal_plane_init(&dc_drm->base, plane, 0, &dc_plane_funcs, >>>>>> + dc_plane_formats, >>>>>> + ARRAY_SIZE(dc_plane_formats), >>>>>> + NULL, DRM_PLANE_TYPE_PRIMARY, NULL); >>>>>> + if (ret) >>>>>> + return ret; >>>>>> + >>>>>> + drm_plane_helper_add(plane, &dc_plane_helper_funcs); >>>>>> + >>>>>> + dc_plane->fu = dc_drm->pe->fu_disp[plane->index]; >>>>>> + dc_plane->cf = dc_drm->pe->cf_cont[plane->index]; >>>>>> + dc_plane->lb = dc_drm->pe->lb[plane->index]; >>>>>> + dc_plane->ed = dc_drm->pe->ed_cont[plane->index]; >>>>>> + >>>>>> + return 0; >>>>>> +} >>>>>> -- >>>>>> 2.34.1 >>>>>> >>>>> >>>> >>>> -- >>>> Regards, >>>> Liu Ying >>> >> >> -- >> Regards, >> Liu Ying > > > -- Regards, Liu Ying