From 15342f930ebebcfe36f2415049736a77d7d2e045 Mon Sep 17 00:00:00 2001 From: Miaoqian Lin Date: Mon, 13 Dec 2021 07:21:15 +0000 Subject: malidp: Fix NULL vs IS_ERR() checking The get_sg_table() function does not return NULL. It returns error pointers. Signed-off-by: Miaoqian Lin Signed-off-by: Liviu Dudau Link: https://lore.kernel.org/dri-devel/20211213072115.18098-1-linmq006@gmail.com/ --- drivers/gpu/drm/arm/malidp_planes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/arm/malidp_planes.c b/drivers/gpu/drm/arm/malidp_planes.c index 81d9f5004025..338cec4a3fff 100644 --- a/drivers/gpu/drm/arm/malidp_planes.c +++ b/drivers/gpu/drm/arm/malidp_planes.c @@ -344,7 +344,7 @@ static bool malidp_check_pages_threshold(struct malidp_plane_state *ms, else sgt = obj->funcs->get_sg_table(obj); - if (!sgt) + if (IS_ERR(sgt)) return false; sgl = sgt->sgl; -- cgit v1.2.3 From 4b674dd69701c2e22e8e7770c1706a69f3b17269 Mon Sep 17 00:00:00 2001 From: Steven Price Date: Fri, 3 Dec 2021 10:28:15 +0000 Subject: drm/plane: Move range check for format_count earlier While the check for format_count > 64 in __drm_universal_plane_init() shouldn't be hit (it's a WARN_ON), in its current position it will then leak the plane->format_types array and fail to call drm_mode_object_unregister() leaking the modeset identifier. Move it to the start of the function to avoid allocating those resources in the first place. Signed-off-by: Steven Price Signed-off-by: Liviu Dudau Link: https://lore.kernel.org/dri-devel/20211203102815.38624-1-steven.price@arm.com/ --- drivers/gpu/drm/drm_plane.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c index bf0daa8d9bbd..726f2f163c26 100644 --- a/drivers/gpu/drm/drm_plane.c +++ b/drivers/gpu/drm/drm_plane.c @@ -247,6 +247,13 @@ static int __drm_universal_plane_init(struct drm_device *dev, if (WARN_ON(config->num_total_plane >= 32)) return -EINVAL; + /* + * First driver to need more than 64 formats needs to fix this. Each + * format is encoded as a bit and the current code only supports a u64. + */ + if (WARN_ON(format_count > 64)) + return -EINVAL; + WARN_ON(drm_drv_uses_atomic_modeset(dev) && (!funcs->atomic_destroy_state || !funcs->atomic_duplicate_state)); @@ -268,13 +275,6 @@ static int __drm_universal_plane_init(struct drm_device *dev, return -ENOMEM; } - /* - * First driver to need more than 64 formats needs to fix this. Each - * format is encoded as a bit and the current code only supports a u64. - */ - if (WARN_ON(format_count > 64)) - return -EINVAL; - if (format_modifiers) { const uint64_t *temp_modifiers = format_modifiers; -- cgit v1.2.3 From 1e6b8d5df017aa7a0cbff4555d1803ea3e8b3d8e Mon Sep 17 00:00:00 2001 From: Wayne Lin Date: Thu, 28 Apr 2022 20:49:44 +0800 Subject: drm/dp_mst: Lower down debug info level when receive NAK [Why] It's reasonable that we receive NAK while doing DP_REMOTE_DPCD_READ. Downstream device might reply NAK with the reason and source should react accordingly. e.g. 1. When downstream device can't handle corresponding message in time, it then replies NAK as reason been set as DEFER. 2. When multi-function branch-sink device doesn't enumerate virtual DP peer devices for those multi-function down facing ports. Without virtual DPCD, branch device might reply NAK with reason as BAD_PARAM indicating this port can't do aux DPCD read. It's expected result. Not an error. [How] Use drm_dbg_kms() to replace drm_err() when receive NAK. Changes since v1: * drm_dp_mst_topology.c file path changed. Folder was rename from 'dp' to 'display' Signed-off-by: Wayne Lin Reviewed-by: Harry Wentland Reviewed-by: Lyude Paul [fixed some indenting issues] Signed-off-by: Lyude Paul Link: https://patchwork.freedesktop.org/patch/msgid/20220428124944.1683680-1-Wayne.Lin@amd.com --- drivers/gpu/drm/display/drm_dp_mst_topology.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/display/drm_dp_mst_topology.c b/drivers/gpu/drm/display/drm_dp_mst_topology.c index 8526aae75c6d..9aa2c20904e3 100644 --- a/drivers/gpu/drm/display/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/display/drm_dp_mst_topology.c @@ -3557,10 +3557,9 @@ static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr, if (ret < 0) goto fail_free; - /* DPCD read should never be NACKed */ if (txmsg->reply.reply_type == 1) { - drm_err(mgr->dev, "mstb %p port %d: DPCD read on addr 0x%x for %d bytes NAKed\n", - mstb, port->port_num, offset, size); + drm_dbg_kms(mgr->dev, "mstb %p port %d: DPCD read on addr 0x%x for %d bytes NAKed\n", + mstb, port->port_num, offset, size); ret = -EIO; goto fail_free; } -- cgit v1.2.3 From b2aa405e89a4025642ba53897bb7b9ef5de210a9 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Thu, 28 Apr 2022 10:22:44 +0200 Subject: drm/display: Select DP helper for DRM_DP_AUX_CHARDEV and DRM_DP_CEC The DRM_DP_AUX_CHARDEV and DRM_DP_CEC Kconfig symbols enable code that use DP helper functions, that are only present if CONFIG_DRM_DISPLAY_DP_HELPER is also enabled. But these don't select the DRM_DISPLAY_DP_HELPER symbol, meaning that it is possible to enable any of them without CONFIG_DRM_DISPLAY_DP_HELPER. That will lead to the following linking errors with the mentioned config: LD vmlinux.o MODPOST vmlinux.symvers MODINFO modules.builtin.modinfo GEN modules.builtin LD .tmp_vmlinux.kallsyms1 KSYMS .tmp_vmlinux.kallsyms1.S AS .tmp_vmlinux.kallsyms1.S LD .tmp_vmlinux.kallsyms2 KSYMS .tmp_vmlinux.kallsyms2.S AS .tmp_vmlinux.kallsyms2.S LD vmlinux SYSMAP System.map SORTTAB vmlinux OBJCOPY arch/arm64/boot/Image MODPOST modules-only.symvers ERROR: modpost: "drm_dp_dpcd_write" [drivers/gpu/drm/display/drm_display_helper.ko] undefined! ERROR: modpost: "drm_dp_read_desc" [drivers/gpu/drm/display/drm_display_helper.ko] undefined! ERROR: modpost: "drm_dp_dpcd_read" [drivers/gpu/drm/display/drm_display_helper.ko] undefined! make[1]: *** [scripts/Makefile.modpost:134: modules-only.symvers] Error 1 make[1]: *** Deleting file 'modules-only.symvers' make: *** [Makefile:1749: modules] Error 2 Besides making these symbols to select CONFIG_DRM_DISPLAY_DP_HELPER, make them to depend on DRM_DISPLAY_HELPER, since can't be enabled without it. Note: It seems this has been an issue for a long time but was made easier to reproduce after the commit 1e0f66420b13 ("drm/display: Introduce a DRM display-helper module"). Adding a Fixes: tag just to make sure that this fix will be picked for stable once the mentioned change also lands there. Fixes: 1e0f66420b13 ("drm/display: Introduce a DRM display-helper module") Signed-off-by: Javier Martinez Canillas Acked-by: Thomas Zimmermann Reviewed-by: Lyude Paul Link: https://patchwork.freedesktop.org/patch/msgid/20220428082244.390859-1-javierm@redhat.com --- drivers/gpu/drm/display/Kconfig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/display/Kconfig b/drivers/gpu/drm/display/Kconfig index f84f1b0cd23f..1b6e6af37546 100644 --- a/drivers/gpu/drm/display/Kconfig +++ b/drivers/gpu/drm/display/Kconfig @@ -31,7 +31,8 @@ config DRM_DISPLAY_HDMI_HELPER config DRM_DP_AUX_CHARDEV bool "DRM DP AUX Interface" - depends on DRM + depends on DRM && DRM_DISPLAY_HELPER + select DRM_DISPLAY_DP_HELPER help Choose this option to enable a /dev/drm_dp_auxN node that allows to read and write values to arbitrary DPCD registers on the DP aux @@ -39,7 +40,8 @@ config DRM_DP_AUX_CHARDEV config DRM_DP_CEC bool "Enable DisplayPort CEC-Tunneling-over-AUX HDMI support" - depends on DRM + depends on DRM && DRM_DISPLAY_HELPER + select DRM_DISPLAY_DP_HELPER select CEC_CORE help Choose this option if you want to enable HDMI CEC support for -- cgit v1.2.3 From 5d96fc9c9e2ac8a4c78959286e1ec890ad340fc4 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Tue, 26 Apr 2022 12:19:12 +0300 Subject: drm/edid: fix kernel-doc parameter name mismatches Fix the below drm/edid kernel-doc warnings: drivers/gpu/drm/drm_edid.c:1589: warning: Function parameter or member '_edid' not described in 'drm_edid_header_is_valid' drivers/gpu/drm/drm_edid.c:1589: warning: Excess function parameter 'raw_edid' description in 'drm_edid_header_is_valid' drivers/gpu/drm/drm_edid.c:1737: warning: Function parameter or member '_block' not described in 'drm_edid_block_valid' drivers/gpu/drm/drm_edid.c:1737: warning: Excess function parameter 'raw_edid' description in 'drm_edid_block_valid' drivers/gpu/drm/drm_edid.c:2136: warning: Function parameter or member 'read_block' not described in 'drm_do_get_edid' drivers/gpu/drm/drm_edid.c:2136: warning: Function parameter or member 'context' not described in 'drm_do_get_edid' drivers/gpu/drm/drm_edid.c:2136: warning: Excess function parameter 'get_edid_block' description in 'drm_do_get_edid' drivers/gpu/drm/drm_edid.c:2136: warning: Excess function parameter 'data' description in 'drm_do_get_edid' Reported-by: Stephen Rothwell References: https://lore.kernel.org/r/20220406154431.567414c3@canb.auug.org.au References: https://lore.kernel.org/r/20220420162431.2b28ddea@canb.auug.org.au Signed-off-by: Jani Nikula Reviewed-by: Simon Ser Link: https://patchwork.freedesktop.org/patch/msgid/20220426091913.1339941-1-jani.nikula@intel.com --- drivers/gpu/drm/drm_edid.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 7a8482b75071..6446f5d3944b 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -1610,7 +1610,7 @@ static void edid_header_fix(void *edid) /** * drm_edid_header_is_valid - sanity check the header of the base EDID block - * @raw_edid: pointer to raw base EDID block + * @_edid: pointer to raw base EDID block * * Sanity check the header of the base EDID block. * @@ -1827,7 +1827,7 @@ static void edid_block_dump(const char *level, const void *block, int block_num) /** * drm_edid_block_valid - Sanity check the EDID block (base or extension) - * @raw_edid: pointer to raw EDID block + * @_block: pointer to raw EDID block * @block_num: type of block to validate (0 for base, extension otherwise) * @print_bad_edid: if true, dump bad EDID blocks to the console * @edid_corrupt: if true, the header or checksum is invalid @@ -2112,8 +2112,8 @@ static enum edid_block_status edid_block_read(void *block, unsigned int block_nu /** * drm_do_get_edid - get EDID data using a custom EDID block read function * @connector: connector we're probing - * @get_edid_block: EDID block read function - * @data: private data passed to the block read function + * @read_block: EDID block read function + * @context: private data passed to the block read function * * When the I2C adapter connected to the DDC bus is hidden behind a device that * exposes a different interface to read EDID blocks this function can be used -- cgit v1.2.3 From 17edb8e1e325cbd10ec41f1524869870953ed1a8 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Tue, 26 Apr 2022 12:19:13 +0300 Subject: drm/edid: drop kernel-doc for static functions Drop the kernel-doc for static functions, it's excessive, but retain the info in plain comments. Signed-off-by: Jani Nikula Reviewed-by: Simon Ser Link: https://patchwork.freedesktop.org/patch/msgid/20220426091913.1339941-2-jani.nikula@intel.com --- drivers/gpu/drm/drm_edid.c | 57 ++++++++++++++-------------------------------- 1 file changed, 17 insertions(+), 40 deletions(-) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 6446f5d3944b..bc43e1b32092 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -2384,13 +2384,9 @@ static u32 edid_get_quirks(const struct edid *edid) #define MODE_SIZE(m) ((m)->hdisplay * (m)->vdisplay) #define MODE_REFRESH_DIFF(c,t) (abs((c) - (t))) -/** - * edid_fixup_preferred - set preferred modes based on quirk list - * @connector: has mode list to fix up - * @quirks: quirks list - * - * Walk the mode list for @connector, clearing the preferred status - * on existing modes and setting it anew for the right mode ala @quirks. +/* + * Walk the mode list for connector, clearing the preferred status on existing + * modes and setting it anew for the right mode ala quirks. */ static void edid_fixup_preferred(struct drm_connector *connector, u32 quirks) @@ -2659,10 +2655,7 @@ drm_gtf2_2j(const struct edid *edid) return descriptor ? descriptor->data.other_data.data.range.formula.gtf2.j : 0; } -/** - * standard_timing_level - get std. timing level(CVT/GTF/DMT) - * @edid: EDID block to scan - */ +/* Get standard timing level (CVT/GTF/DMT). */ static int standard_timing_level(const struct edid *edid) { if (edid->revision >= 2) { @@ -2696,12 +2689,7 @@ static int drm_mode_hsync(const struct drm_display_mode *mode) return DIV_ROUND_CLOSEST(mode->clock, mode->htotal); } -/** - * drm_mode_std - convert standard mode info (width, height, refresh) into mode - * @connector: connector of for the EDID block - * @edid: EDID block to scan - * @t: standard timing params - * +/* * Take the standard timing params (in this case width, aspect, and refresh) * and convert them into a real mode using CVT/GTF/DMT. */ @@ -2857,15 +2845,10 @@ drm_mode_do_interlace_quirk(struct drm_display_mode *mode, mode->flags |= DRM_MODE_FLAG_INTERLACE; } -/** - * drm_mode_detailed - create a new mode from an EDID detailed timing section - * @dev: DRM device (needed to create new mode) - * @edid: EDID block - * @timing: EDID detailed timing info - * @quirks: quirks to apply - * - * An EDID detailed timing block contains enough info for us to create and - * return a new struct drm_display_mode. +/* + * Create a new mode from an EDID detailed timing section. An EDID detailed + * timing block contains enough info for us to create and return a new struct + * drm_display_mode. */ static struct drm_display_mode *drm_mode_detailed(struct drm_device *dev, const struct edid *edid, @@ -3247,13 +3230,10 @@ do_established_modes(const struct detailed_timing *timing, void *c) closure->modes += drm_est3_modes(closure->connector, timing); } -/** - * add_established_modes - get est. modes from EDID and add them - * @connector: connector to add mode(s) to - * @edid: EDID block to scan - * - * Each EDID block contains a bitmap of the supported "established modes" list - * (defined above). Tease them out and add them to the global modes list. +/* + * Get established modes from EDID and add them. Each EDID block contains a + * bitmap of the supported "established modes" list (defined above). Tease them + * out and add them to the global modes list. */ static int add_established_modes(struct drm_connector *connector, const struct edid *edid) @@ -3311,13 +3291,10 @@ do_standard_modes(const struct detailed_timing *timing, void *c) } } -/** - * add_standard_modes - get std. modes from EDID and add them - * @connector: connector to add mode(s) to - * @edid: EDID block to scan - * - * Standard modes can be calculated using the appropriate standard (DMT, - * GTF or CVT. Grab them from @edid and add them to the list. +/* + * Get standard modes from EDID and add them. Standard modes can be calculated + * using the appropriate standard (DMT, GTF, or CVT). Grab them from EDID and + * add them to the list. */ static int add_standard_modes(struct drm_connector *connector, const struct edid *edid) -- cgit v1.2.3 From 9030a9e571b3ba250d3d450a98310e3c74ecaff4 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 28 Apr 2022 23:31:32 +0200 Subject: drm/bridge: tc358767: Fix (e)DP bridge endpoint parsing in dedicated function Per toshiba,tc358767.yaml DT binding document, port@2 the output (e)DP port is optional. In case this port is not described in DT, the bridge driver operates in DPI-to-DP mode. The drm_of_find_panel_or_bridge() call in tc_probe_edp_bridge_endpoint() returns -ENODEV in case port@2 is not present in DT and this specific return value is incorrectly propagated outside of tc_probe_edp_bridge_endpoint() function. All other error values must be propagated and are propagated correctly. Return 0 in case the port@2 is missing instead, that reinstates the original behavior before the commit this patch fixes. Fixes: 8478095a8c4b ("drm/bridge: tc358767: Move (e)DP bridge endpoint parsing into dedicated function") Signed-off-by: Marek Vasut Cc: Jonas Karlman Cc: Laurent Pinchart Cc: Lucas Stach Cc: Marek Vasut Cc: Maxime Ripard Cc: Neil Armstrong Cc: Robert Foss Cc: Sam Ravnborg Reviewed-by: Lucas Stach Link: https://patchwork.freedesktop.org/patch/msgid/20220428213132.447890-1-marex@denx.de --- drivers/gpu/drm/bridge/tc358767.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c index b20b38661002..7dde71313b84 100644 --- a/drivers/gpu/drm/bridge/tc358767.c +++ b/drivers/gpu/drm/bridge/tc358767.c @@ -1955,7 +1955,7 @@ static int tc_probe_edp_bridge_endpoint(struct tc_data *tc) tc->bridge.ops |= DRM_BRIDGE_OP_DETECT; tc->bridge.ops |= DRM_BRIDGE_OP_EDID; - return ret; + return 0; } static int tc_probe_bridge_endpoint(struct tc_data *tc) -- cgit v1.2.3 From 37f67d390d160f4bf6bde76e5113acfc004ee96a Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 28 Apr 2022 15:26:23 +0300 Subject: drm/sun4i: dsi: delete unnecessary IS_ERR() checks The "dsi->bus_clk" pointer cannot be an error pointer at this point. The check is confusing and unnecessary. Delete it. Signed-off-by: Dan Carpenter Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/YmqH71MtoGn2AXUg@kili --- drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c index 527c7b2474da..b4dfa166eccd 100644 --- a/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c +++ b/drivers/gpu/drm/sun4i/sun6i_mipi_dsi.c @@ -1193,8 +1193,8 @@ err_remove_dsi_host: err_unprotect_clk: clk_rate_exclusive_put(dsi->mod_clk); err_attach_clk: - if (!IS_ERR(dsi->bus_clk)) - regmap_mmio_detach_clk(dsi->regs); + regmap_mmio_detach_clk(dsi->regs); + return ret; } @@ -1207,8 +1207,7 @@ static int sun6i_dsi_remove(struct platform_device *pdev) mipi_dsi_host_unregister(&dsi->host); clk_rate_exclusive_put(dsi->mod_clk); - if (!IS_ERR(dsi->bus_clk)) - regmap_mmio_detach_clk(dsi->regs); + regmap_mmio_detach_clk(dsi->regs); return 0; } -- cgit v1.2.3 From f2a28a83f6c42f716f44b33e8a8acb718187b09f Mon Sep 17 00:00:00 2001 From: Minghao Chi Date: Fri, 29 Apr 2022 05:49:45 +0000 Subject: drm/vc4: simplify the return expression of vc4_prepare_fb() Simplify the return expression. Reported-by: Zeal Robot Signed-off-by: Minghao Chi Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20220429054945.3852039-1-chi.minghao@zte.com.cn --- drivers/gpu/drm/vc4/vc4_plane.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_plane.c b/drivers/gpu/drm/vc4/vc4_plane.c index 920a9eefe426..b3438f4a81ce 100644 --- a/drivers/gpu/drm/vc4/vc4_plane.c +++ b/drivers/gpu/drm/vc4/vc4_plane.c @@ -1350,7 +1350,6 @@ static int vc4_prepare_fb(struct drm_plane *plane, struct drm_plane_state *state) { struct vc4_bo *bo; - int ret; if (!state->fb) return 0; @@ -1362,11 +1361,7 @@ static int vc4_prepare_fb(struct drm_plane *plane, if (plane->state->fb == state->fb) return 0; - ret = vc4_bo_inc_usecnt(bo); - if (ret) - return ret; - - return 0; + return vc4_bo_inc_usecnt(bo); } static void vc4_cleanup_fb(struct drm_plane *plane, -- cgit v1.2.3 From a425e9802d029838660edd898667f8da6c64b030 Mon Sep 17 00:00:00 2001 From: Minghao Chi Date: Fri, 29 Apr 2022 09:02:08 +0000 Subject: drm/vc4: simplify the return expression of vc4_grab_bin_bo() Simplify the return expression. Reported-by: Zeal Robot Signed-off-by: Minghao Chi Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20220429090208.3852879-1-chi.minghao@zte.com.cn --- drivers/gpu/drm/vc4/vc4_bo.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c index e451cc5bcfac..49c0f2ac868b 100644 --- a/drivers/gpu/drm/vc4/vc4_bo.c +++ b/drivers/gpu/drm/vc4/vc4_bo.c @@ -738,19 +738,13 @@ static const struct drm_gem_object_funcs vc4_gem_object_funcs = { static int vc4_grab_bin_bo(struct vc4_dev *vc4, struct vc4_file *vc4file) { - int ret; - if (!vc4->v3d) return -ENODEV; if (vc4file->bin_bo_used) return 0; - ret = vc4_v3d_bin_bo_get(vc4, &vc4file->bin_bo_used); - if (ret) - return ret; - - return 0; + return vc4_v3d_bin_bo_get(vc4, &vc4file->bin_bo_used); } int vc4_create_bo_ioctl(struct drm_device *dev, void *data, -- cgit v1.2.3 From ab7671282b7ca750b5de99b2a59e51edab34ecc7 Mon Sep 17 00:00:00 2001 From: Minghao Chi Date: Fri, 29 Apr 2022 09:03:09 +0000 Subject: drm/nouveau: simplify the return expression of nouveau_debugfs_init() Simplify the return expression. Reported-by: Zeal Robot Signed-off-by: Minghao Chi Reviewed-by: Lyude Paul [fixed an indenting error before pushing] Signed-off-by: Lyude Paul Link: https://patchwork.freedesktop.org/patch/msgid/20220429090309.3853003-1-chi.minghao@zte.com.cn --- drivers/gpu/drm/nouveau/nouveau_debugfs.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_debugfs.c b/drivers/gpu/drm/nouveau/nouveau_debugfs.c index 1cbe01048b93..2a36d1ca8fda 100644 --- a/drivers/gpu/drm/nouveau/nouveau_debugfs.c +++ b/drivers/gpu/drm/nouveau/nouveau_debugfs.c @@ -255,19 +255,13 @@ nouveau_drm_debugfs_init(struct drm_minor *minor) int nouveau_debugfs_init(struct nouveau_drm *drm) { - int ret; - drm->debugfs = kzalloc(sizeof(*drm->debugfs), GFP_KERNEL); if (!drm->debugfs) return -ENOMEM; - ret = nvif_object_ctor(&drm->client.device.object, "debugfsCtrl", 0, - NVIF_CLASS_CONTROL, NULL, 0, - &drm->debugfs->ctrl); - if (ret) - return ret; - - return 0; + return nvif_object_ctor(&drm->client.device.object, "debugfsCtrl", 0, + NVIF_CLASS_CONTROL, NULL, 0, + &drm->debugfs->ctrl); } void -- cgit v1.2.3 From 1e69a83a5e1aae7a1e6079cf306fc72087a059d9 Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Fri, 29 Apr 2022 14:20:55 -0300 Subject: dt-bindings: display: simple: Add Startek KD070WVFPA043-C069A panel Add Startek KD070WVFPA043-C069A 7" TFT LCD panel compatible string. Signed-off-by: Fabio Estevam Acked-by: Sam Ravnborg Reviewed-by: Marek Vasut Signed-off-by: Marek Vasut Link: https://patchwork.freedesktop.org/patch/msgid/20220429172056.3499563-1-festevam@gmail.com --- Documentation/devicetree/bindings/display/panel/panel-simple.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml index cfe7bb9f89de..21ba90c9fe33 100644 --- a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml +++ b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml @@ -296,6 +296,8 @@ properties: - starry,kr070pe2t # Starry 12.2" (1920x1200 pixels) TFT LCD panel - starry,kr122ea0sra + # Startek KD070WVFPA043-C069A 7" TFT LCD panel + - startek,kd070wvfpa # Team Source Display Technology TST043015CMHX 4.3" WQVGA TFT LCD panel - team-source-display,tst043015cmhx # Tianma Micro-electronics TM070JDHG30 7.0" WXGA TFT LCD panel -- cgit v1.2.3 From 9ff9236394dd8a4fc7d1d3f3dc9d00cda3dd58be Mon Sep 17 00:00:00 2001 From: Heiko Schocher Date: Fri, 29 Apr 2022 14:20:56 -0300 Subject: drm/panel: simple: Add Startek KD070WVFPA043-C069A panel support Add Startek KD070WVFPA043-C069A 7" TFT LCD panel support. Signed-off-by: Heiko Schocher [fabio: passed .flags and .bus_flags] Signed-off-by: Fabio Estevam Acked-by: Sam Ravnborg Reviewed-by: Marek Vasut Signed-off-by: Marek Vasut Link: https://patchwork.freedesktop.org/patch/msgid/20220429172056.3499563-2-festevam@gmail.com --- drivers/gpu/drm/panel/panel-simple.c | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 595396f57632..8c9fae298d9b 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -3310,6 +3310,41 @@ static const struct panel_desc starry_kr070pe2t = { .connector_type = DRM_MODE_CONNECTOR_DPI, }; +static const struct display_timing startek_kd070wvfpa_mode = { + .pixelclock = { 25200000, 27200000, 30500000 }, + .hactive = { 800, 800, 800 }, + .hfront_porch = { 19, 44, 115 }, + .hback_porch = { 5, 16, 101 }, + .hsync_len = { 1, 2, 100 }, + .vactive = { 480, 480, 480 }, + .vfront_porch = { 5, 43, 67 }, + .vback_porch = { 5, 5, 67 }, + .vsync_len = { 1, 2, 66 }, + .flags = DISPLAY_FLAGS_HSYNC_LOW | DISPLAY_FLAGS_VSYNC_LOW | + DISPLAY_FLAGS_DE_HIGH | DISPLAY_FLAGS_PIXDATA_POSEDGE | + DISPLAY_FLAGS_SYNC_POSEDGE, +}; + +static const struct panel_desc startek_kd070wvfpa = { + .timings = &startek_kd070wvfpa_mode, + .num_timings = 1, + .bpc = 8, + .size = { + .width = 152, + .height = 91, + }, + .delay = { + .prepare = 20, + .enable = 200, + .disable = 200, + }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X24, + .connector_type = DRM_MODE_CONNECTOR_DPI, + .bus_flags = DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE, +}; + static const struct display_timing tsd_tst043015cmhx_timing = { .pixelclock = { 5000000, 9000000, 12000000 }, .hactive = { 480, 480, 480 }, @@ -4019,6 +4054,9 @@ static const struct of_device_id platform_of_match[] = { }, { .compatible = "starry,kr070pe2t", .data = &starry_kr070pe2t, + }, { + .compatible = "startek,kd070wvfpa", + .data = &startek_kd070wvfpa, }, { .compatible = "team-source-display,tst043015cmhx", .data = &tsd_tst043015cmhx, -- cgit v1.2.3 From 1ea28bc5542d607ff7c806e409a72862c5af8f5e Mon Sep 17 00:00:00 2001 From: Christian König Date: Thu, 21 Apr 2022 20:20:49 +0200 Subject: drm: handle kernel fences in drm_gem_plane_helper_prepare_fb v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit drm_gem_plane_helper_prepare_fb() was using drm_atomic_set_fence_for_plane() which ignores all implicit fences when an explicit fence is already set. That's rather unfortunate when the fb still has a kernel fence we need to wait for to avoid presenting garbage on the screen. So instead update the fence in the plane state directly. While at it also take care of all potential GEM objects and not just the first one. Also remove the now unused drm_atomic_set_fence_for_plane() function, new drivers should probably use the atomic helpers directly. v2: improve kerneldoc, use local variable and num_planes, WARN_ON_ONCE on missing planes. Signed-off-by: Christian König Reviewed-by: Daniel Vetter (v1) Acked-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20220429134230.24334-1-christian.koenig@amd.com --- drivers/gpu/drm/drm_atomic_uapi.c | 47 +++------------------ drivers/gpu/drm/drm_gem_atomic_helper.c | 73 +++++++++++++++++++++++++-------- include/drm/drm_atomic_uapi.h | 2 - include/drm/drm_plane.h | 4 +- 4 files changed, 62 insertions(+), 64 deletions(-) diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c index c6394ba13b24..434f3d4cb8a2 100644 --- a/drivers/gpu/drm/drm_atomic_uapi.c +++ b/drivers/gpu/drm/drm_atomic_uapi.c @@ -254,43 +254,6 @@ drm_atomic_set_fb_for_plane(struct drm_plane_state *plane_state, } EXPORT_SYMBOL(drm_atomic_set_fb_for_plane); -/** - * drm_atomic_set_fence_for_plane - set fence for plane - * @plane_state: atomic state object for the plane - * @fence: dma_fence to use for the plane - * - * Helper to setup the plane_state fence in case it is not set yet. - * By using this drivers doesn't need to worry if the user choose - * implicit or explicit fencing. - * - * This function will not set the fence to the state if it was set - * via explicit fencing interfaces on the atomic ioctl. In that case it will - * drop the reference to the fence as we are not storing it anywhere. - * Otherwise, if &drm_plane_state.fence is not set this function we just set it - * with the received implicit fence. In both cases this function consumes a - * reference for @fence. - * - * This way explicit fencing can be used to overrule implicit fencing, which is - * important to make explicit fencing use-cases work: One example is using one - * buffer for 2 screens with different refresh rates. Implicit fencing will - * clamp rendering to the refresh rate of the slower screen, whereas explicit - * fence allows 2 independent render and display loops on a single buffer. If a - * driver allows obeys both implicit and explicit fences for plane updates, then - * it will break all the benefits of explicit fencing. - */ -void -drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state, - struct dma_fence *fence) -{ - if (plane_state->fence) { - dma_fence_put(fence); - return; - } - - plane_state->fence = fence; -} -EXPORT_SYMBOL(drm_atomic_set_fence_for_plane); - /** * drm_atomic_set_crtc_for_connector - set CRTC for connector * @conn_state: atomic state object for the connector @@ -1077,10 +1040,10 @@ int drm_atomic_set_property(struct drm_atomic_state *state, * * As a contrast, with implicit fencing the kernel keeps track of any * ongoing rendering, and automatically ensures that the atomic update waits - * for any pending rendering to complete. For shared buffers represented with - * a &struct dma_buf this is tracked in &struct dma_resv. - * Implicit syncing is how Linux traditionally worked (e.g. DRI2/3 on X.org), - * whereas explicit fencing is what Android wants. + * for any pending rendering to complete. This is usually tracked in &struct + * dma_resv which can also contain mandatory kernel fences. Implicit syncing + * is how Linux traditionally worked (e.g. DRI2/3 on X.org), whereas explicit + * fencing is what Android wants. * * "IN_FENCE_FD”: * Use this property to pass a fence that DRM should wait on before @@ -1095,7 +1058,7 @@ int drm_atomic_set_property(struct drm_atomic_state *state, * * On the driver side the fence is stored on the @fence parameter of * &struct drm_plane_state. Drivers which also support implicit fencing - * should set the implicit fence using drm_atomic_set_fence_for_plane(), + * should extract the implicit fence using drm_gem_plane_helper_prepare_fb(), * to make sure there's consistent behaviour between drivers in precedence * of implicit vs. explicit fencing. * diff --git a/drivers/gpu/drm/drm_gem_atomic_helper.c b/drivers/gpu/drm/drm_gem_atomic_helper.c index a6d89aed0bda..a5026f617739 100644 --- a/drivers/gpu/drm/drm_gem_atomic_helper.c +++ b/drivers/gpu/drm/drm_gem_atomic_helper.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include @@ -137,29 +138,67 @@ * * This function is the default implementation for GEM drivers of * &drm_plane_helper_funcs.prepare_fb if no callback is provided. - * - * See drm_atomic_set_fence_for_plane() for a discussion of implicit and - * explicit fencing in atomic modeset updates. */ -int drm_gem_plane_helper_prepare_fb(struct drm_plane *plane, struct drm_plane_state *state) +int drm_gem_plane_helper_prepare_fb(struct drm_plane *plane, + struct drm_plane_state *state) { - struct drm_gem_object *obj; - struct dma_fence *fence; + struct dma_fence *fence = dma_fence_get(state->fence); + enum dma_resv_usage usage; + size_t i; int ret; if (!state->fb) return 0; - obj = drm_gem_fb_get_obj(state->fb, 0); - ret = dma_resv_get_singleton(obj->resv, DMA_RESV_USAGE_WRITE, &fence); - if (ret) - return ret; - - /* TODO: drm_atomic_set_fence_for_plane() should be changed to be able - * to handle more fences in general for multiple BOs per fb. + /* + * Only add the kernel fences here if there is already a fence set via + * explicit fencing interfaces on the atomic ioctl. + * + * This way explicit fencing can be used to overrule implicit fencing, + * which is important to make explicit fencing use-cases work: One + * example is using one buffer for 2 screens with different refresh + * rates. Implicit fencing will clamp rendering to the refresh rate of + * the slower screen, whereas explicit fence allows 2 independent + * render and display loops on a single buffer. If a driver allows + * obeys both implicit and explicit fences for plane updates, then it + * will break all the benefits of explicit fencing. */ - drm_atomic_set_fence_for_plane(state, fence); + usage = fence ? DMA_RESV_USAGE_KERNEL : DMA_RESV_USAGE_WRITE; + + for (i = 0; i < state->fb->format->num_planes; ++i) { + struct drm_gem_object *obj = drm_gem_fb_get_obj(state->fb, i); + struct dma_fence *new; + + if (WARN_ON_ONCE(!obj)) + continue; + + ret = dma_resv_get_singleton(obj->resv, usage, &new); + if (ret) + goto error; + + if (new && fence) { + struct dma_fence_chain *chain = dma_fence_chain_alloc(); + + if (!chain) { + ret = -ENOMEM; + goto error; + } + + dma_fence_chain_init(chain, fence, new, 1); + fence = &chain->base; + + } else if (new) { + fence = new; + } + } + + dma_fence_put(state->fence); + state->fence = fence; return 0; + +error: + dma_fence_put(fence); + return ret; } EXPORT_SYMBOL_GPL(drm_gem_plane_helper_prepare_fb); @@ -168,13 +207,13 @@ EXPORT_SYMBOL_GPL(drm_gem_plane_helper_prepare_fb); * @pipe: Simple display pipe * @plane_state: Plane state * - * This function uses drm_gem_plane_helper_prepare_fb() to extract the exclusive fence - * from &drm_gem_object.resv and attaches it to plane state for the atomic + * This function uses drm_gem_plane_helper_prepare_fb() to extract the fences + * from &drm_gem_object.resv and attaches them to the plane state for the atomic * helper to wait on. This is necessary to correctly implement implicit * synchronization for any buffers shared as a struct &dma_buf. Drivers can use * this as their &drm_simple_display_pipe_funcs.prepare_fb callback. * - * See drm_atomic_set_fence_for_plane() for a discussion of implicit and + * See drm_gem_plane_helper_prepare_fb() for a discussion of implicit and * explicit fencing in atomic modeset updates. */ int drm_gem_simple_display_pipe_prepare_fb(struct drm_simple_display_pipe *pipe, diff --git a/include/drm/drm_atomic_uapi.h b/include/drm/drm_atomic_uapi.h index 8cec52ad1277..4c6d39d7bdb2 100644 --- a/include/drm/drm_atomic_uapi.h +++ b/include/drm/drm_atomic_uapi.h @@ -49,8 +49,6 @@ drm_atomic_set_crtc_for_plane(struct drm_plane_state *plane_state, struct drm_crtc *crtc); void drm_atomic_set_fb_for_plane(struct drm_plane_state *plane_state, struct drm_framebuffer *fb); -void drm_atomic_set_fence_for_plane(struct drm_plane_state *plane_state, - struct dma_fence *fence); int __must_check drm_atomic_set_crtc_for_connector(struct drm_connector_state *conn_state, struct drm_crtc *crtc); diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h index 2628c7cde2da..89ea54652e87 100644 --- a/include/drm/drm_plane.h +++ b/include/drm/drm_plane.h @@ -74,9 +74,7 @@ struct drm_plane_state { * * Optional fence to wait for before scanning out @fb. The core atomic * code will set this when userspace is using explicit fencing. Do not - * write this field directly for a driver's implicit fence, use - * drm_atomic_set_fence_for_plane() to ensure that an explicit fence is - * preserved. + * write this field directly for a driver's implicit fence. * * Drivers should store any implicit fence in this from their * &drm_plane_helper_funcs.prepare_fb callback. See drm_gem_plane_helper_prepare_fb() -- cgit v1.2.3 From ab7c37ec11d2f3c88e1d49c5fb4fe4ed568d88e3 Mon Sep 17 00:00:00 2001 From: Christian König Date: Fri, 29 Apr 2022 13:36:16 +0200 Subject: drm/nouveau: use drm_gem_plane_helper_prepare_fb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of manually adjusting the plane state. Signed-off-by: Christian König Reviewed-by: Lyude Paul Link: https://patchwork.freedesktop.org/patch/msgid/20220429134230.24334-3-christian.koenig@amd.com --- drivers/gpu/drm/nouveau/dispnv50/wndw.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/nouveau/dispnv50/wndw.c b/drivers/gpu/drm/nouveau/dispnv50/wndw.c index 8642b84ea20c..bb8a4601e0d9 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/wndw.c +++ b/drivers/gpu/drm/nouveau/dispnv50/wndw.c @@ -32,6 +32,7 @@ #include #include +#include #include #include "nouveau_bo.h" @@ -558,9 +559,7 @@ nv50_wndw_prepare_fb(struct drm_plane *plane, struct drm_plane_state *state) asyw->image.handle[0] = ctxdma->object.handle; } - ret = dma_resv_get_singleton(nvbo->bo.base.resv, - DMA_RESV_USAGE_WRITE, - &asyw->state.fence); + ret = drm_gem_plane_helper_prepare_fb(plane, state); if (ret) return ret; -- cgit v1.2.3 From 7eafbecd2288c542ea15ea20cf1a7e64a25c21bc Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Thu, 11 Nov 2021 15:11:03 +0530 Subject: drm/panel: panel-simple: Fix proper bpc for AM-1280800N3TZQW-T00H AM-1280800N3TZQW-T00H panel support 8 bpc not 6 bpc as per recent testing in i.MX8MM platform. Fix it. Fixes: bca684e69c4c ("drm/panel: simple: Add AM-1280800N3TZQW-T00H") Signed-off-by: Jagan Teki Reviewed-by: Robert Foss Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20211111094103.494831-1-jagan@amarulasolutions.com --- drivers/gpu/drm/panel/panel-simple.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 8c9fae298d9b..4a2e580a2f7b 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -720,7 +720,7 @@ static const struct drm_display_mode ampire_am_1280800n3tzqw_t00h_mode = { static const struct panel_desc ampire_am_1280800n3tzqw_t00h = { .modes = &ire_am_1280800n3tzqw_t00h_mode, .num_modes = 1, - .bpc = 6, + .bpc = 8, .size = { .width = 217, .height = 136, -- cgit v1.2.3 From 46ca7da7f1e8592af6059419176dd58c10dcdb5b Mon Sep 17 00:00:00 2001 From: Pin-Yen Lin Date: Mon, 25 Apr 2022 21:44:24 +0800 Subject: drm/bridge: it6505: Send DPCD SET_POWER to downstream Send DPCD SET_POWER command to downstream in .atomic_disable to make the downstream monitor enter the power down mode, so the device suspend won't be affected. Fixes: b5c84a9edcd418 ("drm/bridge: add it6505 driver") Signed-off-by: Pin-Yen Lin Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220425134424.1150965-1-treapking@chromium.org Reviewed-by: Robert Foss --- drivers/gpu/drm/bridge/ite-it6505.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/bridge/ite-it6505.c b/drivers/gpu/drm/bridge/ite-it6505.c index 8fed30df08b0..4b673c4792d7 100644 --- a/drivers/gpu/drm/bridge/ite-it6505.c +++ b/drivers/gpu/drm/bridge/ite-it6505.c @@ -737,8 +737,9 @@ static int it6505_drm_dp_link_probe(struct drm_dp_aux *aux, return 0; } -static int it6505_drm_dp_link_power_up(struct drm_dp_aux *aux, - struct it6505_drm_dp_link *link) +static int it6505_drm_dp_link_set_power(struct drm_dp_aux *aux, + struct it6505_drm_dp_link *link, + u8 mode) { u8 value; int err; @@ -752,18 +753,20 @@ static int it6505_drm_dp_link_power_up(struct drm_dp_aux *aux, return err; value &= ~DP_SET_POWER_MASK; - value |= DP_SET_POWER_D0; + value |= mode; err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value); if (err < 0) return err; - /* - * According to the DP 1.1 specification, a "Sink Device must exit the - * power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink - * Control Field" (register 0x600). - */ - usleep_range(1000, 2000); + if (mode == DP_SET_POWER_D0) { + /* + * According to the DP 1.1 specification, a "Sink Device must + * exit the power saving state within 1 ms" (Section 2.5.3.1, + * Table 5-52, "Sink Control Field" (register 0x600). + */ + usleep_range(1000, 2000); + } return 0; } @@ -2624,7 +2627,8 @@ static enum drm_connector_status it6505_detect(struct it6505 *it6505) if (it6505_get_sink_hpd_status(it6505)) { it6505_aux_on(it6505); it6505_drm_dp_link_probe(&it6505->aux, &it6505->link); - it6505_drm_dp_link_power_up(&it6505->aux, &it6505->link); + it6505_drm_dp_link_set_power(&it6505->aux, &it6505->link, + DP_SET_POWER_D0); it6505->auto_train_retry = AUTO_TRAIN_RETRY; if (it6505->dpcd[0] == 0) { @@ -2960,8 +2964,11 @@ static void it6505_bridge_atomic_disable(struct drm_bridge *bridge, DRM_DEV_DEBUG_DRIVER(dev, "start"); - if (it6505->powered) + if (it6505->powered) { it6505_video_disable(it6505); + it6505_drm_dp_link_set_power(&it6505->aux, &it6505->link, + DP_SET_POWER_D3); + } } static enum drm_connector_status -- cgit v1.2.3 From 0aae7623b4952212781cd48fa411ead52608f6eb Mon Sep 17 00:00:00 2001 From: Alvin Šipraga Date: Sat, 23 Apr 2022 14:08:52 +0200 Subject: drm: bridge: adv7511: enable CEC support for ADV7535 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Like the ADV7533, the ADV7535 has an offset for the CEC register map, and it is the same value (ADV7533_REG_CEC_OFFSET = 0x70). Rather than testing for numerous chip types in the offset calculations throughout the driver, just compute it during driver probe and put it in the private adv7511 data structure. Signed-off-by: Alvin Šipraga Reviewed-by: Robert Foss Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220423120854.1503163-2-alvin@pqrs.dk --- drivers/gpu/drm/bridge/adv7511/adv7511.h | 1 + drivers/gpu/drm/bridge/adv7511/adv7511_cec.c | 18 ++++++------------ drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 5 +++-- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h index 6a882891d91c..da6d8ee2cd84 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511.h +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -335,6 +335,7 @@ struct adv7511 { struct regmap *regmap; struct regmap *regmap_cec; + unsigned int reg_cec_offset; enum drm_connector_status status; bool powered; diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c index 28d9becc939c..1f619389e201 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c @@ -21,8 +21,7 @@ static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status) { - unsigned int offset = adv7511->type == ADV7533 ? - ADV7533_REG_CEC_OFFSET : 0; + unsigned int offset = adv7511->reg_cec_offset; unsigned int val; if (regmap_read(adv7511->regmap_cec, @@ -73,8 +72,7 @@ static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status) void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1) { - unsigned int offset = adv7511->type == ADV7533 ? - ADV7533_REG_CEC_OFFSET : 0; + unsigned int offset = adv7511->reg_cec_offset; const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY | ADV7511_INT1_CEC_TX_ARBIT_LOST | ADV7511_INT1_CEC_TX_RETRY_TIMEOUT; @@ -118,8 +116,7 @@ void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1) static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable) { struct adv7511 *adv7511 = cec_get_drvdata(adap); - unsigned int offset = adv7511->type == ADV7533 ? - ADV7533_REG_CEC_OFFSET : 0; + unsigned int offset = adv7511->reg_cec_offset; if (adv7511->i2c_cec == NULL) return -EIO; @@ -165,8 +162,7 @@ static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable) static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) { struct adv7511 *adv7511 = cec_get_drvdata(adap); - unsigned int offset = adv7511->type == ADV7533 ? - ADV7533_REG_CEC_OFFSET : 0; + unsigned int offset = adv7511->reg_cec_offset; unsigned int i, free_idx = ADV7511_MAX_ADDRS; if (!adv7511->cec_enabled_adap) @@ -235,8 +231,7 @@ static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, u32 signal_free_time, struct cec_msg *msg) { struct adv7511 *adv7511 = cec_get_drvdata(adap); - unsigned int offset = adv7511->type == ADV7533 ? - ADV7533_REG_CEC_OFFSET : 0; + unsigned int offset = adv7511->reg_cec_offset; u8 len = msg->len; unsigned int i; @@ -289,8 +284,7 @@ static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511) int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511) { - unsigned int offset = adv7511->type == ADV7533 ? - ADV7533_REG_CEC_OFFSET : 0; + unsigned int offset = adv7511->reg_cec_offset; int ret = adv7511_cec_parse_dt(dev, adv7511); if (ret) diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index b3f10c54e064..556ba1b447ba 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -1027,8 +1027,7 @@ static bool adv7511_cec_register_volatile(struct device *dev, unsigned int reg) struct i2c_client *i2c = to_i2c_client(dev); struct adv7511 *adv7511 = i2c_get_clientdata(i2c); - if (adv7511->type == ADV7533 || adv7511->type == ADV7535) - reg -= ADV7533_REG_CEC_OFFSET; + reg -= adv7511->reg_cec_offset; switch (reg) { case ADV7511_REG_CEC_RX_FRAME_HDR: @@ -1073,6 +1072,8 @@ static int adv7511_init_cec_regmap(struct adv7511 *adv) ret = adv7533_patch_cec_registers(adv); if (ret) goto err; + + adv->reg_cec_offset = ADV7533_REG_CEC_OFFSET; } return 0; -- cgit v1.2.3 From ab0af093bf905a7616eaf247469bc8ee3c117e22 Mon Sep 17 00:00:00 2001 From: Alvin Šipraga Date: Sat, 23 Apr 2022 14:08:53 +0200 Subject: drm: bridge: adv7511: use non-legacy mode for CEC RX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ADV7511 family of bridges supports two modes for CEC RX: legacy and non-legacy mode. The only difference is whether the chip uses a single CEC RX buffer, or uses all three available RX buffers. Currently the adv7511 driver uses legacy mode. While debugging a stall in CEC RX on an ADV7535, we reached out to Analog Devices, who suggested to use non-legacy mode instead. According to the programming guide for the ADV7511 [1], and the register control manual of the ADV7535 [2], this is the default behaviour on reset. As previously stated, the adv7511 driver currently overrides this to legacy mode. This patch updates the adv7511 driver to instead use non-legacy mode with all three CEC RX buffers. As a result of this change, we no longer experience any stalling of CEC RX with the ADV7535. It is not known why non-legacy mode solves this particular issue, but besides this, no functional change is to be expected by this patch. Please note that this has only been tested on an ADV7535. What follows is a brief description of the non-legacy mode interrupt handling behaviour. The programming guide in [1] gives a more detailed explanation. With three RX buffers, the interrupt handler checks the CEC_RX_STATUS register (renamed from CEC_RX_ENABLE in this patch), which contains 2-bit psuedo-timestamps for each of the RX buffers. The RX timestamps for each buffer represent the time of arrival for the CEC frame held in a given buffer, with lower timestamp values indicating chronologically older frames. A special value of 0 indicates that the given RX buffer is inactive and should be skipped. The interrupt handler parses these timestamps and then reads the active RX buffers in the prescribed order using the same logic as before. Changes have been made to ensure that the correct RX buffer is cleared after processing. This clearing procesure also sets the timestamp of the given RX buffer to 0 to mark it as inactive. [1] https://www.analog.com/media/en/technical-documentation/user-guides/ADV7511_Programming_Guide.pdf cf. CEC Map, register 0x4A, bit 3, default value 1: 0 = Use only buffer 0 to store CEC frames (Legacy mode) 1 = Use all 3 buffers to stores the CEC frames (Non-legacy mode) [2] The ADV7535 register control manual is under NDA, but trust me when I say that non-legacy CEC RX mode is the default here too. Here the register is offset by 0x70 and has an address of 0xBA in the DSI_CEC regiser map. Signed-off-by: Alvin Šipraga Reviewed-by: Robert Foss Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220423120854.1503163-3-alvin@pqrs.dk --- drivers/gpu/drm/bridge/adv7511/adv7511.h | 26 ++++++-- drivers/gpu/drm/bridge/adv7511/adv7511_cec.c | 98 +++++++++++++++++++++------- drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 14 ++-- 3 files changed, 106 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h index da6d8ee2cd84..9e3bb8a8ee40 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511.h +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -209,10 +209,16 @@ #define ADV7511_REG_CEC_TX_ENABLE 0x11 #define ADV7511_REG_CEC_TX_RETRY 0x12 #define ADV7511_REG_CEC_TX_LOW_DRV_CNT 0x14 -#define ADV7511_REG_CEC_RX_FRAME_HDR 0x15 -#define ADV7511_REG_CEC_RX_FRAME_DATA0 0x16 -#define ADV7511_REG_CEC_RX_FRAME_LEN 0x25 -#define ADV7511_REG_CEC_RX_ENABLE 0x26 +#define ADV7511_REG_CEC_RX1_FRAME_HDR 0x15 +#define ADV7511_REG_CEC_RX1_FRAME_DATA0 0x16 +#define ADV7511_REG_CEC_RX1_FRAME_LEN 0x25 +#define ADV7511_REG_CEC_RX_STATUS 0x26 +#define ADV7511_REG_CEC_RX2_FRAME_HDR 0x27 +#define ADV7511_REG_CEC_RX2_FRAME_DATA0 0x28 +#define ADV7511_REG_CEC_RX2_FRAME_LEN 0x37 +#define ADV7511_REG_CEC_RX3_FRAME_HDR 0x38 +#define ADV7511_REG_CEC_RX3_FRAME_DATA0 0x39 +#define ADV7511_REG_CEC_RX3_FRAME_LEN 0x48 #define ADV7511_REG_CEC_RX_BUFFERS 0x4a #define ADV7511_REG_CEC_LOG_ADDR_MASK 0x4b #define ADV7511_REG_CEC_LOG_ADDR_0_1 0x4c @@ -220,6 +226,18 @@ #define ADV7511_REG_CEC_CLK_DIV 0x4e #define ADV7511_REG_CEC_SOFT_RESET 0x50 +static const u8 ADV7511_REG_CEC_RX_FRAME_HDR[] = { + ADV7511_REG_CEC_RX1_FRAME_HDR, + ADV7511_REG_CEC_RX2_FRAME_HDR, + ADV7511_REG_CEC_RX3_FRAME_HDR, +}; + +static const u8 ADV7511_REG_CEC_RX_FRAME_LEN[] = { + ADV7511_REG_CEC_RX1_FRAME_LEN, + ADV7511_REG_CEC_RX2_FRAME_LEN, + ADV7511_REG_CEC_RX3_FRAME_LEN, +}; + #define ADV7533_REG_CEC_OFFSET 0x70 enum adv7511_input_clock { diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c index 1f619389e201..399f625a50c8 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c @@ -17,7 +17,8 @@ #define ADV7511_INT1_CEC_MASK \ (ADV7511_INT1_CEC_TX_READY | ADV7511_INT1_CEC_TX_ARBIT_LOST | \ - ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1) + ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1 | \ + ADV7511_INT1_CEC_RX_READY2 | ADV7511_INT1_CEC_RX_READY3) static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status) { @@ -70,25 +71,16 @@ static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status) } } -void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1) +static void adv7511_cec_rx(struct adv7511 *adv7511, int rx_buf) { unsigned int offset = adv7511->reg_cec_offset; - const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY | - ADV7511_INT1_CEC_TX_ARBIT_LOST | - ADV7511_INT1_CEC_TX_RETRY_TIMEOUT; struct cec_msg msg = {}; unsigned int len; unsigned int val; u8 i; - if (irq1 & irq_tx_mask) - adv_cec_tx_raw_status(adv7511, irq1); - - if (!(irq1 & ADV7511_INT1_CEC_RX_READY1)) - return; - if (regmap_read(adv7511->regmap_cec, - ADV7511_REG_CEC_RX_FRAME_LEN + offset, &len)) + ADV7511_REG_CEC_RX_FRAME_LEN[rx_buf] + offset, &len)) return; msg.len = len & 0x1f; @@ -101,18 +93,76 @@ void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1) for (i = 0; i < msg.len; i++) { regmap_read(adv7511->regmap_cec, - i + ADV7511_REG_CEC_RX_FRAME_HDR + offset, &val); + i + ADV7511_REG_CEC_RX_FRAME_HDR[rx_buf] + offset, + &val); msg.msg[i] = val; } - /* toggle to re-enable rx 1 */ - regmap_write(adv7511->regmap_cec, - ADV7511_REG_CEC_RX_BUFFERS + offset, 1); - regmap_write(adv7511->regmap_cec, - ADV7511_REG_CEC_RX_BUFFERS + offset, 0); + /* Toggle RX Ready Clear bit to re-enable this RX buffer */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, BIT(rx_buf), + BIT(rx_buf)); + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, BIT(rx_buf), 0); + cec_received_msg(adv7511->cec_adap, &msg); } +void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1) +{ + unsigned int offset = adv7511->reg_cec_offset; + const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY | + ADV7511_INT1_CEC_TX_ARBIT_LOST | + ADV7511_INT1_CEC_TX_RETRY_TIMEOUT; + const u32 irq_rx_mask = ADV7511_INT1_CEC_RX_READY1 | + ADV7511_INT1_CEC_RX_READY2 | + ADV7511_INT1_CEC_RX_READY3; + unsigned int rx_status; + int rx_order[3] = { -1, -1, -1 }; + int i; + + if (irq1 & irq_tx_mask) + adv_cec_tx_raw_status(adv7511, irq1); + + if (!(irq1 & irq_rx_mask)) + return; + + if (regmap_read(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_STATUS + offset, &rx_status)) + return; + + /* + * ADV7511_REG_CEC_RX_STATUS[5:0] contains the reception order of RX + * buffers 0, 1, and 2 in bits [1:0], [3:2], and [5:4] respectively. + * The values are to be interpreted as follows: + * + * 0 = buffer unused + * 1 = buffer contains oldest received frame (if applicable) + * 2 = buffer contains second oldest received frame (if applicable) + * 3 = buffer contains third oldest received frame (if applicable) + * + * Fill rx_order with the sequence of RX buffer indices to + * read from in order, where -1 indicates that there are no + * more buffers to process. + */ + for (i = 0; i < 3; i++) { + unsigned int timestamp = (rx_status >> (2 * i)) & 0x3; + + if (timestamp) + rx_order[timestamp - 1] = i; + } + + /* Read CEC RX buffers in the appropriate order as prescribed above */ + for (i = 0; i < 3; i++) { + int rx_buf = rx_order[i]; + + if (rx_buf < 0) + break; + + adv7511_cec_rx(adv7511, rx_buf); + } +} + static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable) { struct adv7511 *adv7511 = cec_get_drvdata(adap); @@ -126,11 +176,11 @@ static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable) regmap_update_bits(adv7511->regmap_cec, ADV7511_REG_CEC_CLK_DIV + offset, 0x03, 0x01); - /* legacy mode and clear all rx buffers */ + /* non-legacy mode and clear all rx buffers */ regmap_write(adv7511->regmap_cec, - ADV7511_REG_CEC_RX_BUFFERS + offset, 0x07); + ADV7511_REG_CEC_RX_BUFFERS + offset, 0x0f); regmap_write(adv7511->regmap_cec, - ADV7511_REG_CEC_RX_BUFFERS + offset, 0); + ADV7511_REG_CEC_RX_BUFFERS + offset, 0x08); /* initially disable tx */ regmap_update_bits(adv7511->regmap_cec, ADV7511_REG_CEC_TX_ENABLE + offset, 1, 0); @@ -138,7 +188,7 @@ static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable) /* tx: ready */ /* tx: arbitration lost */ /* tx: retry timeout */ - /* rx: ready 1 */ + /* rx: ready 1-3 */ regmap_update_bits(adv7511->regmap, ADV7511_REG_INT_ENABLE(1), 0x3f, ADV7511_INT1_CEC_MASK); @@ -304,9 +354,9 @@ int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511) regmap_write(adv7511->regmap_cec, ADV7511_REG_CEC_SOFT_RESET + offset, 0x00); - /* legacy mode */ + /* non-legacy mode - use all three RX buffers */ regmap_write(adv7511->regmap_cec, - ADV7511_REG_CEC_RX_BUFFERS + offset, 0x00); + ADV7511_REG_CEC_RX_BUFFERS + offset, 0x08); regmap_write(adv7511->regmap_cec, ADV7511_REG_CEC_CLK_DIV + offset, diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index 556ba1b447ba..5bb9300040dd 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -1030,10 +1030,16 @@ static bool adv7511_cec_register_volatile(struct device *dev, unsigned int reg) reg -= adv7511->reg_cec_offset; switch (reg) { - case ADV7511_REG_CEC_RX_FRAME_HDR: - case ADV7511_REG_CEC_RX_FRAME_DATA0... - ADV7511_REG_CEC_RX_FRAME_DATA0 + 14: - case ADV7511_REG_CEC_RX_FRAME_LEN: + case ADV7511_REG_CEC_RX1_FRAME_HDR: + case ADV7511_REG_CEC_RX1_FRAME_DATA0 ... ADV7511_REG_CEC_RX1_FRAME_DATA0 + 14: + case ADV7511_REG_CEC_RX1_FRAME_LEN: + case ADV7511_REG_CEC_RX2_FRAME_HDR: + case ADV7511_REG_CEC_RX2_FRAME_DATA0 ... ADV7511_REG_CEC_RX2_FRAME_DATA0 + 14: + case ADV7511_REG_CEC_RX2_FRAME_LEN: + case ADV7511_REG_CEC_RX3_FRAME_HDR: + case ADV7511_REG_CEC_RX3_FRAME_DATA0 ... ADV7511_REG_CEC_RX3_FRAME_DATA0 + 14: + case ADV7511_REG_CEC_RX3_FRAME_LEN: + case ADV7511_REG_CEC_RX_STATUS: case ADV7511_REG_CEC_RX_BUFFERS: case ADV7511_REG_CEC_TX_LOW_DRV_CNT: return true; -- cgit v1.2.3 From 666518676d4fea1517043d6008df4f67ea19b5dd Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Tue, 26 Apr 2022 21:36:44 +0200 Subject: dt-bindings: display: bridge: ldb: Implement simple Freescale i.MX8MP LDB bridge The i.MX8MP contains two syscon registers which are responsible for configuring the on-SoC DPI-to-LVDS serializer. Add DT binding which represents this serializer as a bridge. Acked-by: Sam Ravnborg Signed-off-by: Marek Vasut Cc: Laurent Pinchart Cc: Lucas Stach Cc: Maxime Ripard Cc: Peng Fan Cc: Rob Herring Cc: Robby Cai Cc: Robert Foss Cc: Sam Ravnborg Cc: Thomas Zimmermann Cc: devicetree@vger.kernel.org To: dri-devel@lists.freedesktop.org Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220426193645.244792-1-marex@denx.de --- .../bindings/display/bridge/fsl,ldb.yaml | 92 ++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/bridge/fsl,ldb.yaml diff --git a/Documentation/devicetree/bindings/display/bridge/fsl,ldb.yaml b/Documentation/devicetree/bindings/display/bridge/fsl,ldb.yaml new file mode 100644 index 000000000000..77f174eee424 --- /dev/null +++ b/Documentation/devicetree/bindings/display/bridge/fsl,ldb.yaml @@ -0,0 +1,92 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/bridge/fsl,ldb.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Freescale i.MX8MP DPI to LVDS bridge chip + +maintainers: + - Marek Vasut + +description: | + The i.MX8MP mediamix contains two registers which are responsible + for configuring the on-SoC DPI-to-LVDS serializer. This describes + those registers as bridge within the DT. + +properties: + compatible: + const: fsl,imx8mp-ldb + + clocks: + maxItems: 1 + + clock-names: + const: ldb + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: Video port for DPI input. + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: Video port for LVDS Channel-A output (panel or bridge). + + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: Video port for LVDS Channel-B output (panel or bridge). + + required: + - port@0 + - port@1 + +required: + - compatible + - clocks + - ports + +additionalProperties: false + +examples: + - | + #include + + blk-ctrl { + bridge { + compatible = "fsl,imx8mp-ldb"; + clocks = <&clk IMX8MP_CLK_MEDIA_LDB>; + clock-names = "ldb"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + ldb_from_lcdif2: endpoint { + remote-endpoint = <&lcdif2_to_ldb>; + }; + }; + + port@1 { + reg = <1>; + + ldb_lvds_ch0: endpoint { + remote-endpoint = <&ldb_to_lvdsx4panel>; + }; + }; + + port@2 { + reg = <2>; + + ldb_lvds_ch1: endpoint { + }; + }; + }; + }; + }; -- cgit v1.2.3 From 463db5c2ed4aed015323bf0677fdb126e77d01a8 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Tue, 26 Apr 2022 21:36:45 +0200 Subject: drm: bridge: ldb: Implement simple Freescale i.MX8MP LDB bridge The i.MX8MP contains two syscon registers which are responsible for configuring the on-SoC DPI-to-LVDS serializer. Implement a simple bridge driver for this serializer. -- - Add sentinel of_device_table - Add RB from Sam - Rename to fsl-ldb altogether Reviewed-by: Sam Ravnborg Signed-off-by: Marek Vasut Cc: Laurent Pinchart Cc: Lucas Stach Cc: Maxime Ripard Cc: Peng Fan Cc: Robby Cai Cc: Robert Foss Cc: Sam Ravnborg Cc: Thomas Zimmermann To: dri-devel@lists.freedesktop.org V2: - Rename syscon to fsl,syscon V3: - Consistently use MX8MP V4: - Fix MODULE_DESCRIPTION to also use MX8MP Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220426193645.244792-2-marex@denx.de --- drivers/gpu/drm/bridge/Kconfig | 8 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/fsl-ldb.c | 342 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 351 insertions(+) create mode 100644 drivers/gpu/drm/bridge/fsl-ldb.c diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index ef9f1b0d91bf..da3441830d46 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -75,6 +75,14 @@ config DRM_DISPLAY_CONNECTOR on ARM-based platforms. Saying Y here when this driver is not needed will not cause any issue. +config DRM_FSL_LDB + tristate "Freescale i.MX8MP LDB bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL_BRIDGE + help + Support for i.MX8MP DPI-to-LVDS on-SoC encoder. + config DRM_ITE_IT6505 tristate "ITE IT6505 DisplayPort bridge" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index b0edf2022fa0..f6c0a95de549 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_DRM_CHIPONE_ICN6211) += chipone-icn6211.o obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o +obj-$(CONFIG_DRM_FSL_LDB) += fsl-ldb.o obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o diff --git a/drivers/gpu/drm/bridge/fsl-ldb.c b/drivers/gpu/drm/bridge/fsl-ldb.c new file mode 100644 index 000000000000..b2675c769a55 --- /dev/null +++ b/drivers/gpu/drm/bridge/fsl-ldb.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2022 Marek Vasut + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define LDB_CTRL 0x5c +#define LDB_CTRL_CH0_ENABLE BIT(0) +#define LDB_CTRL_CH0_DI_SELECT BIT(1) +#define LDB_CTRL_CH1_ENABLE BIT(2) +#define LDB_CTRL_CH1_DI_SELECT BIT(3) +#define LDB_CTRL_SPLIT_MODE BIT(4) +#define LDB_CTRL_CH0_DATA_WIDTH BIT(5) +#define LDB_CTRL_CH0_BIT_MAPPING BIT(6) +#define LDB_CTRL_CH1_DATA_WIDTH BIT(7) +#define LDB_CTRL_CH1_BIT_MAPPING BIT(8) +#define LDB_CTRL_DI0_VSYNC_POLARITY BIT(9) +#define LDB_CTRL_DI1_VSYNC_POLARITY BIT(10) +#define LDB_CTRL_REG_CH0_FIFO_RESET BIT(11) +#define LDB_CTRL_REG_CH1_FIFO_RESET BIT(12) +#define LDB_CTRL_ASYNC_FIFO_ENABLE BIT(24) +#define LDB_CTRL_ASYNC_FIFO_THRESHOLD_MASK GENMASK(27, 25) + +#define LVDS_CTRL 0x128 +#define LVDS_CTRL_CH0_EN BIT(0) +#define LVDS_CTRL_CH1_EN BIT(1) +#define LVDS_CTRL_VBG_EN BIT(2) +#define LVDS_CTRL_HS_EN BIT(3) +#define LVDS_CTRL_PRE_EMPH_EN BIT(4) +#define LVDS_CTRL_PRE_EMPH_ADJ(n) (((n) & 0x7) << 5) +#define LVDS_CTRL_PRE_EMPH_ADJ_MASK GENMASK(7, 5) +#define LVDS_CTRL_CM_ADJ(n) (((n) & 0x7) << 8) +#define LVDS_CTRL_CM_ADJ_MASK GENMASK(10, 8) +#define LVDS_CTRL_CC_ADJ(n) (((n) & 0x7) << 11) +#define LVDS_CTRL_CC_ADJ_MASK GENMASK(13, 11) +#define LVDS_CTRL_SLEW_ADJ(n) (((n) & 0x7) << 14) +#define LVDS_CTRL_SLEW_ADJ_MASK GENMASK(16, 14) +#define LVDS_CTRL_VBG_ADJ(n) (((n) & 0x7) << 17) +#define LVDS_CTRL_VBG_ADJ_MASK GENMASK(19, 17) + +struct fsl_ldb { + struct device *dev; + struct drm_bridge bridge; + struct drm_bridge *panel_bridge; + struct clk *clk; + struct regmap *regmap; + bool lvds_dual_link; +}; + +static inline struct fsl_ldb *to_fsl_ldb(struct drm_bridge *bridge) +{ + return container_of(bridge, struct fsl_ldb, bridge); +} + +static int fsl_ldb_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge); + + return drm_bridge_attach(bridge->encoder, fsl_ldb->panel_bridge, + bridge, flags); +} + +static int fsl_ldb_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + /* Invert DE signal polarity. */ + bridge_state->input_bus_cfg.flags &= ~(DRM_BUS_FLAG_DE_LOW | + DRM_BUS_FLAG_DE_HIGH); + if (bridge_state->output_bus_cfg.flags & DRM_BUS_FLAG_DE_LOW) + bridge_state->input_bus_cfg.flags |= DRM_BUS_FLAG_DE_HIGH; + else if (bridge_state->output_bus_cfg.flags & DRM_BUS_FLAG_DE_HIGH) + bridge_state->input_bus_cfg.flags |= DRM_BUS_FLAG_DE_LOW; + + return 0; +} + +static void fsl_ldb_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge); + struct drm_atomic_state *state = old_bridge_state->base.state; + const struct drm_bridge_state *bridge_state; + const struct drm_crtc_state *crtc_state; + const struct drm_display_mode *mode; + struct drm_connector *connector; + struct drm_crtc *crtc; + bool lvds_format_24bpp; + bool lvds_format_jeida; + u32 reg; + + /* Get the LVDS format from the bridge state. */ + bridge_state = drm_atomic_get_new_bridge_state(state, bridge); + + switch (bridge_state->output_bus_cfg.format) { + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + lvds_format_24bpp = false; + lvds_format_jeida = true; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA: + lvds_format_24bpp = true; + lvds_format_jeida = true; + break; + case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG: + lvds_format_24bpp = true; + lvds_format_jeida = false; + break; + default: + /* + * Some bridges still don't set the correct LVDS bus pixel + * format, use SPWG24 default format until those are fixed. + */ + lvds_format_24bpp = true; + lvds_format_jeida = false; + dev_warn(fsl_ldb->dev, + "Unsupported LVDS bus format 0x%04x, please check output bridge driver. Falling back to SPWG24.\n", + bridge_state->output_bus_cfg.format); + break; + } + + /* + * Retrieve the CRTC adjusted mode. This requires a little dance to go + * from the bridge to the encoder, to the connector and to the CRTC. + */ + connector = drm_atomic_get_new_connector_for_encoder(state, + bridge->encoder); + crtc = drm_atomic_get_new_connector_state(state, connector)->crtc; + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + mode = &crtc_state->adjusted_mode; + + if (fsl_ldb->lvds_dual_link) + clk_set_rate(fsl_ldb->clk, mode->clock * 3500); + else + clk_set_rate(fsl_ldb->clk, mode->clock * 7000); + clk_prepare_enable(fsl_ldb->clk); + + /* Program LDB_CTRL */ + reg = LDB_CTRL_CH0_ENABLE; + + if (fsl_ldb->lvds_dual_link) + reg |= LDB_CTRL_CH1_ENABLE; + + if (lvds_format_24bpp) { + reg |= LDB_CTRL_CH0_DATA_WIDTH; + if (fsl_ldb->lvds_dual_link) + reg |= LDB_CTRL_CH1_DATA_WIDTH; + } + + if (lvds_format_jeida) { + reg |= LDB_CTRL_CH0_BIT_MAPPING; + if (fsl_ldb->lvds_dual_link) + reg |= LDB_CTRL_CH1_BIT_MAPPING; + } + + if (mode->flags & DRM_MODE_FLAG_PVSYNC) { + reg |= LDB_CTRL_DI0_VSYNC_POLARITY; + if (fsl_ldb->lvds_dual_link) + reg |= LDB_CTRL_DI1_VSYNC_POLARITY; + } + + regmap_write(fsl_ldb->regmap, LDB_CTRL, reg); + + /* Program LVDS_CTRL */ + reg = LVDS_CTRL_CC_ADJ(2) | LVDS_CTRL_PRE_EMPH_EN | + LVDS_CTRL_PRE_EMPH_ADJ(3) | LVDS_CTRL_VBG_EN; + regmap_write(fsl_ldb->regmap, LVDS_CTRL, reg); + + /* Wait for VBG to stabilize. */ + usleep_range(15, 20); + + reg |= LVDS_CTRL_CH0_EN; + if (fsl_ldb->lvds_dual_link) + reg |= LVDS_CTRL_CH1_EN; + + regmap_write(fsl_ldb->regmap, LVDS_CTRL, reg); +} + +static void fsl_ldb_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge); + + /* Stop both channels. */ + regmap_write(fsl_ldb->regmap, LVDS_CTRL, 0); + regmap_write(fsl_ldb->regmap, LDB_CTRL, 0); + + clk_disable_unprepare(fsl_ldb->clk); +} + +#define MAX_INPUT_SEL_FORMATS 1 +static u32 * +fsl_ldb_atomic_get_input_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + u32 output_fmt, + unsigned int *num_input_fmts) +{ + u32 *input_fmts; + + *num_input_fmts = 0; + + input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof(*input_fmts), + GFP_KERNEL); + if (!input_fmts) + return NULL; + + input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24; + *num_input_fmts = MAX_INPUT_SEL_FORMATS; + + return input_fmts; +} + +static enum drm_mode_status +fsl_ldb_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge); + + if (mode->clock > (fsl_ldb->lvds_dual_link ? 80000 : 160000)) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static const struct drm_bridge_funcs funcs = { + .attach = fsl_ldb_attach, + .atomic_check = fsl_ldb_atomic_check, + .atomic_enable = fsl_ldb_atomic_enable, + .atomic_disable = fsl_ldb_atomic_disable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_get_input_bus_fmts = fsl_ldb_atomic_get_input_bus_fmts, + .atomic_reset = drm_atomic_helper_bridge_reset, + .mode_valid = fsl_ldb_mode_valid, +}; + +static int fsl_ldb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *panel_node; + struct device_node *port1, *port2; + struct drm_panel *panel; + struct fsl_ldb *fsl_ldb; + int dual_link; + + fsl_ldb = devm_kzalloc(dev, sizeof(*fsl_ldb), GFP_KERNEL); + if (!fsl_ldb) + return -ENOMEM; + + fsl_ldb->dev = &pdev->dev; + fsl_ldb->bridge.funcs = &funcs; + fsl_ldb->bridge.of_node = dev->of_node; + + fsl_ldb->clk = devm_clk_get(dev, "ldb"); + if (IS_ERR(fsl_ldb->clk)) + return PTR_ERR(fsl_ldb->clk); + + fsl_ldb->regmap = syscon_node_to_regmap(dev->of_node->parent); + if (IS_ERR(fsl_ldb->regmap)) + return PTR_ERR(fsl_ldb->regmap); + + /* Locate the panel DT node. */ + panel_node = of_graph_get_remote_node(dev->of_node, 1, 0); + if (!panel_node) + return -ENXIO; + + panel = of_drm_find_panel(panel_node); + of_node_put(panel_node); + if (IS_ERR(panel)) + return PTR_ERR(panel); + + fsl_ldb->panel_bridge = devm_drm_panel_bridge_add(dev, panel); + if (IS_ERR(fsl_ldb->panel_bridge)) + return PTR_ERR(fsl_ldb->panel_bridge); + + /* Determine whether this is dual-link configuration */ + port1 = of_graph_get_port_by_id(dev->of_node, 1); + port2 = of_graph_get_port_by_id(dev->of_node, 2); + dual_link = drm_of_lvds_get_dual_link_pixel_order(port1, port2); + of_node_put(port1); + of_node_put(port2); + + if (dual_link == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) { + dev_err(dev, "LVDS channel pixel swap not supported.\n"); + return -EINVAL; + } + + if (dual_link == DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS) + fsl_ldb->lvds_dual_link = true; + + platform_set_drvdata(pdev, fsl_ldb); + + drm_bridge_add(&fsl_ldb->bridge); + + return 0; +} + +static int fsl_ldb_remove(struct platform_device *pdev) +{ + struct fsl_ldb *fsl_ldb = platform_get_drvdata(pdev); + + drm_bridge_remove(&fsl_ldb->bridge); + + return 0; +} + +static const struct of_device_id fsl_ldb_match[] = { + { .compatible = "fsl,imx8mp-ldb", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, fsl_ldb_match); + +static struct platform_driver fsl_ldb_driver = { + .probe = fsl_ldb_probe, + .remove = fsl_ldb_remove, + .driver = { + .name = "fsl-ldb", + .of_match_table = fsl_ldb_match, + }, +}; +module_platform_driver(fsl_ldb_driver); + +MODULE_AUTHOR("Marek Vasut "); +MODULE_DESCRIPTION("Freescale i.MX8MP LDB"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 6a47a16dcef3fdda79a95452964d001a620db473 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Thu, 28 Apr 2022 18:28:03 +0200 Subject: drm/bridge: tfp410: Make tfp410_fini() return void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tfp410_fini() always returns zero. Make it return no value which makes it easier to see in the callers that there is no error to handle. Also the return value of i2c and platform driver remove callbacks is ignored anyway. This prepares making i2c and platform remove callbacks return void, too. Signed-off-by: Uwe Kleine-König Reviewed-by: Laurent Pinchart Signed-off-by: Robert Foss Link: https://patchwork.freedesktop.org/patch/msgid/20220428162803.185275-1-u.kleine-koenig@pengutronix.de --- drivers/gpu/drm/bridge/ti-tfp410.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c index ba3fa2a9b8a4..756b3e6e776b 100644 --- a/drivers/gpu/drm/bridge/ti-tfp410.c +++ b/drivers/gpu/drm/bridge/ti-tfp410.c @@ -341,13 +341,11 @@ static int tfp410_init(struct device *dev, bool i2c) return 0; } -static int tfp410_fini(struct device *dev) +static void tfp410_fini(struct device *dev) { struct tfp410 *dvi = dev_get_drvdata(dev); drm_bridge_remove(&dvi->bridge); - - return 0; } static int tfp410_probe(struct platform_device *pdev) @@ -357,7 +355,9 @@ static int tfp410_probe(struct platform_device *pdev) static int tfp410_remove(struct platform_device *pdev) { - return tfp410_fini(&pdev->dev); + tfp410_fini(&pdev->dev); + + return 0; } static const struct of_device_id tfp410_match[] = { @@ -394,7 +394,9 @@ static int tfp410_i2c_probe(struct i2c_client *client, static int tfp410_i2c_remove(struct i2c_client *client) { - return tfp410_fini(&client->dev); + tfp410_fini(&client->dev); + + return 0; } static const struct i2c_device_id tfp410_i2c_ids[] = { -- cgit v1.2.3 From f8c242908ad15bbd604d3bcb54961b7d454c43f8 Mon Sep 17 00:00:00 2001 From: Yang Yingliang Date: Fri, 22 Apr 2022 11:28:54 +0800 Subject: drm/rockchip: vop: fix possible null-ptr-deref in vop_bind() It will cause null-ptr-deref in resource_size(), if platform_get_resource() returns NULL, move calling resource_size() after devm_ioremap_resource() that will check 'res' to avoid null-ptr-deref. Fixes: 2048e3286f34 ("drm: rockchip: Add basic drm driver") Signed-off-by: Yang Yingliang Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220422032854.2995175-1-yangyingliang@huawei.com --- drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index 3e8d9e2d1b67..d53037531f40 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -2118,10 +2118,10 @@ static int vop_bind(struct device *dev, struct device *master, void *data) vop_win_init(vop); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - vop->len = resource_size(res); vop->regs = devm_ioremap_resource(dev, res); if (IS_ERR(vop->regs)) return PTR_ERR(vop->regs); + vop->len = resource_size(res); res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (res) { -- cgit v1.2.3 From 421be3ee36a497949a4b564cd1e4f7f9fe755f57 Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Tue, 5 Apr 2022 15:32:50 +0100 Subject: drm/rockchip: Refactor IOMMU initialisation Defer the IOMMU domain setup until after successfully binding components, so we can figure out IOMMU support directly from the VOP devices themselves, rather than manually inferring it from the DT (which also fails to account for whether the IOMMU driver is actually loaded). Although this is somewhat of a logical cleanup, the main motivation is to prepare for a change in the iommu_domain_alloc() interface. Signed-off-by: Robin Murphy Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/94eee7ab434fe11eb0787f691e9f1ab03a2e91be.1649168685.git.robin.murphy@arm.com --- drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 60 +++++++++++++---------------- drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 3 ++ drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 2 + 3 files changed, 31 insertions(+), 34 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index 4eaeb430c83a..7efd12312354 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -7,7 +7,6 @@ */ #include -#include #include #include #include @@ -34,7 +33,6 @@ #define DRIVER_MAJOR 1 #define DRIVER_MINOR 0 -static bool is_support_iommu = true; static const struct drm_driver rockchip_drm_driver; /* @@ -48,7 +46,7 @@ int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, struct rockchip_drm_private *private = drm_dev->dev_private; int ret; - if (!is_support_iommu) + if (!private->domain) return 0; ret = iommu_attach_device(private->domain, dev); @@ -64,12 +62,22 @@ void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, struct device *dev) { struct rockchip_drm_private *private = drm_dev->dev_private; - struct iommu_domain *domain = private->domain; - if (!is_support_iommu) + if (!private->domain) return; - iommu_detach_device(domain, dev); + iommu_detach_device(private->domain, dev); +} + +void rockchip_drm_dma_init_device(struct drm_device *drm_dev, + struct device *dev) +{ + struct rockchip_drm_private *private = drm_dev->dev_private; + + if (!device_iommu_mapped(dev)) + private->iommu_dev = ERR_PTR(-ENODEV); + else if (!private->iommu_dev) + private->iommu_dev = dev; } static int rockchip_drm_init_iommu(struct drm_device *drm_dev) @@ -78,10 +86,10 @@ static int rockchip_drm_init_iommu(struct drm_device *drm_dev) struct iommu_domain_geometry *geometry; u64 start, end; - if (!is_support_iommu) + if (IS_ERR_OR_NULL(private->iommu_dev)) return 0; - private->domain = iommu_domain_alloc(&platform_bus_type); + private->domain = iommu_domain_alloc(private->iommu_dev->bus); if (!private->domain) return -ENOMEM; @@ -101,7 +109,7 @@ static void rockchip_iommu_cleanup(struct drm_device *drm_dev) { struct rockchip_drm_private *private = drm_dev->dev_private; - if (!is_support_iommu) + if (!private->domain) return; drm_mm_takedown(&private->mm); @@ -137,25 +145,25 @@ static int rockchip_drm_bind(struct device *dev) drm_dev->dev_private = private; - ret = rockchip_drm_init_iommu(drm_dev); - if (ret) - goto err_free; - ret = drmm_mode_config_init(drm_dev); if (ret) - goto err_iommu_cleanup; + goto err_free; rockchip_drm_mode_config_init(drm_dev); /* Try to bind all sub drivers. */ ret = component_bind_all(dev, drm_dev); if (ret) - goto err_iommu_cleanup; + goto err_free; - ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc); + ret = rockchip_drm_init_iommu(drm_dev); if (ret) goto err_unbind_all; + ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc); + if (ret) + goto err_iommu_cleanup; + drm_mode_config_reset(drm_dev); /* init kms poll for handling hpd */ @@ -170,10 +178,10 @@ static int rockchip_drm_bind(struct device *dev) return 0; err_kms_helper_poll_fini: drm_kms_helper_poll_fini(drm_dev); -err_unbind_all: - component_unbind_all(dev, drm_dev); err_iommu_cleanup: rockchip_iommu_cleanup(drm_dev); +err_unbind_all: + component_unbind_all(dev, drm_dev); err_free: drm_dev_put(drm_dev); return ret; @@ -342,8 +350,6 @@ static int rockchip_drm_platform_of_probe(struct device *dev) return -ENODEV; for (i = 0;; i++) { - struct device_node *iommu; - port = of_parse_phandle(np, "ports", i); if (!port) break; @@ -353,21 +359,7 @@ static int rockchip_drm_platform_of_probe(struct device *dev) continue; } - iommu = of_parse_phandle(port->parent, "iommus", 0); - if (!iommu || !of_device_is_available(iommu)) { - DRM_DEV_DEBUG(dev, - "no iommu attached for %pOF, using non-iommu buffers\n", - port->parent); - /* - * if there is a crtc not support iommu, force set all - * crtc use non-iommu buffer. - */ - is_support_iommu = false; - } - found = true; - - of_node_put(iommu); of_node_put(port); } diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index 143a48330f84..008c44aef400 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -44,6 +44,7 @@ struct rockchip_crtc_state { */ struct rockchip_drm_private { struct iommu_domain *domain; + struct device *iommu_dev; struct mutex mm_lock; struct drm_mm mm; }; @@ -52,6 +53,8 @@ int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, struct device *dev); void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, struct device *dev); +void rockchip_drm_dma_init_device(struct drm_device *drm_dev, + struct device *dev); int rockchip_drm_wait_vact_end(struct drm_crtc *crtc, unsigned int mstimeout); int rockchip_drm_endpoint_is_subdriver(struct device_node *ep); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index d53037531f40..ff6fff085e0d 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -2175,6 +2175,8 @@ static int vop_bind(struct device *dev, struct device *master, void *data) } } + rockchip_drm_dma_init_device(drm_dev, dev); + return 0; err_disable_pm_runtime: -- cgit v1.2.3 From 7494b1ed1df414e43dc8429f0c016b2054b1b5da Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Thu, 21 Apr 2022 10:43:04 -0400 Subject: drm/rockchip: cdn-dp: change rk3399_cdn_dp from global to static Smatch reports this issue cdn-dp-core.c:51:20: warning: symbol 'rk3399_cdn_dp' was not declared. Should it be static? rk3399_cdn_dp is only used in cdn-dp-core.c so change its storge-class specifier to static. Signed-off-by: Tom Rix Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220421144304.586396-1-trix@redhat.com --- drivers/gpu/drm/rockchip/cdn-dp-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c index 2857d3f68730..0d027b10fbb3 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c @@ -48,7 +48,7 @@ struct cdn_dp_data { u8 max_phy; }; -struct cdn_dp_data rk3399_cdn_dp = { +static struct cdn_dp_data rk3399_cdn_dp = { .max_phy = 2, }; -- cgit v1.2.3 From d449222dd533ca83a3a2f88aafe06fdd8d589280 Mon Sep 17 00:00:00 2001 From: José Expósito Date: Thu, 21 Apr 2022 19:07:22 +0200 Subject: drm/rockchip: inno_hdmi: Replace drm_detect_hdmi_monitor() with is_hdmi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Once EDID is parsed, the monitor HDMI support information is available through drm_display_info.is_hdmi. This driver calls drm_detect_hdmi_monitor() to receive the same information and stores its own cached value, which is less efficient. Avoid calling drm_detect_hdmi_monitor() and use drm_display_info.is_hdmi instead and also remove hdmi_data_info.sink_is_hdmi as it is no longer necessary. Signed-off-by: José Expósito Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220421170725.903361-3-jose.exposito89@gmail.com --- drivers/gpu/drm/rockchip/inno_hdmi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/rockchip/inno_hdmi.c b/drivers/gpu/drm/rockchip/inno_hdmi.c index 046e8ec2a71c..aa8b704d44a2 100644 --- a/drivers/gpu/drm/rockchip/inno_hdmi.c +++ b/drivers/gpu/drm/rockchip/inno_hdmi.c @@ -30,7 +30,6 @@ struct hdmi_data_info { int vic; - bool sink_is_hdmi; bool sink_has_audio; unsigned int enc_in_format; unsigned int enc_out_format; @@ -433,6 +432,8 @@ static int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi, static int inno_hdmi_setup(struct inno_hdmi *hdmi, struct drm_display_mode *mode) { + struct drm_display_info *display = &hdmi->connector.display_info; + hdmi->hdmi_data.vic = drm_match_cea_mode(mode); hdmi->hdmi_data.enc_in_format = HDMI_COLORSPACE_RGB; @@ -452,13 +453,13 @@ static int inno_hdmi_setup(struct inno_hdmi *hdmi, /* Set HDMI Mode */ hdmi_writeb(hdmi, HDMI_HDCP_CTRL, - v_HDMI_DVI(hdmi->hdmi_data.sink_is_hdmi)); + v_HDMI_DVI(display->is_hdmi)); inno_hdmi_config_video_timing(hdmi, mode); inno_hdmi_config_video_csc(hdmi); - if (hdmi->hdmi_data.sink_is_hdmi) { + if (display->is_hdmi) { inno_hdmi_config_video_avi(hdmi, mode); inno_hdmi_config_video_vsi(hdmi, mode); } @@ -553,7 +554,6 @@ static int inno_hdmi_connector_get_modes(struct drm_connector *connector) edid = drm_get_edid(connector, hdmi->ddc); if (edid) { - hdmi->hdmi_data.sink_is_hdmi = drm_detect_hdmi_monitor(edid); hdmi->hdmi_data.sink_has_audio = drm_detect_monitor_audio(edid); drm_connector_update_edid_property(connector, edid); ret = drm_add_edid_modes(connector, edid); -- cgit v1.2.3 From d2eabdb64474c2101953859601794f1ea08ec1d9 Mon Sep 17 00:00:00 2001 From: José Expósito Date: Thu, 21 Apr 2022 19:07:23 +0200 Subject: drm/rockchip: rk3066_hdmi: Replace drm_detect_hdmi_monitor() with is_hdmi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Once EDID is parsed, the monitor HDMI support information is available through drm_display_info.is_hdmi. This driver calls drm_detect_hdmi_monitor() to receive the same information and stores its own cached value, which is less efficient. Avoid calling drm_detect_hdmi_monitor() and use drm_display_info.is_hdmi instead and also remove hdmi_data_info.sink_is_hdmi as it is no longer necessary. Signed-off-by: José Expósito Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220421170725.903361-4-jose.exposito89@gmail.com --- drivers/gpu/drm/rockchip/rk3066_hdmi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/rockchip/rk3066_hdmi.c b/drivers/gpu/drm/rockchip/rk3066_hdmi.c index 1c546c3a8998..a09dbb7d446a 100644 --- a/drivers/gpu/drm/rockchip/rk3066_hdmi.c +++ b/drivers/gpu/drm/rockchip/rk3066_hdmi.c @@ -22,7 +22,6 @@ struct hdmi_data_info { int vic; /* The CEA Video ID (VIC) of the current drm display mode. */ - bool sink_is_hdmi; unsigned int enc_out_format; unsigned int colorimetry; }; @@ -317,6 +316,8 @@ static void rk3066_hdmi_config_phy(struct rk3066_hdmi *hdmi) static int rk3066_hdmi_setup(struct rk3066_hdmi *hdmi, struct drm_display_mode *mode) { + struct drm_display_info *display = &hdmi->connector.display_info; + hdmi->hdmi_data.vic = drm_match_cea_mode(mode); hdmi->hdmi_data.enc_out_format = HDMI_COLORSPACE_RGB; @@ -349,7 +350,7 @@ static int rk3066_hdmi_setup(struct rk3066_hdmi *hdmi, rk3066_hdmi_config_video_timing(hdmi, mode); - if (hdmi->hdmi_data.sink_is_hdmi) { + if (display->is_hdmi) { hdmi_modb(hdmi, HDMI_HDCP_CTRL, HDMI_VIDEO_MODE_MASK, HDMI_VIDEO_MODE_HDMI); rk3066_hdmi_config_avi(hdmi, mode); @@ -472,7 +473,6 @@ static int rk3066_hdmi_connector_get_modes(struct drm_connector *connector) edid = drm_get_edid(connector, hdmi->ddc); if (edid) { - hdmi->hdmi_data.sink_is_hdmi = drm_detect_hdmi_monitor(edid); drm_connector_update_edid_property(connector, edid); ret = drm_add_edid_modes(connector, edid); kfree(edid); -- cgit v1.2.3 From 3fa50896c35982afb59ad5bcbe04ec2e91bb54a5 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Fri, 14 Jan 2022 15:40:38 +0800 Subject: drm/rockchip: Support YUV formats with U/V swapped The VOP in Rockchip SoCs that support YUV planes also support swapping of the U and V elements. Supporting the swapped variants, especially NV21, would be beneficial for multimedia applications, as the hardware video decoders only output NV21, and supporting this pixel format in the display pipeline would allow the decoded video frames to be output directly. Add support for this to support the various formats that have U/V swapped. Signed-off-by: Chen-Yu Tsai Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220114074038.2633848-1-wenst@chromium.org --- drivers/gpu/drm/rockchip/rockchip_drm_vop.c | 20 +++++++++++++++++++- drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 1 + drivers/gpu/drm/rockchip/rockchip_vop_reg.c | 11 +++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c index ff6fff085e0d..74562d40f639 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.c @@ -262,6 +262,18 @@ static bool has_rb_swapped(uint32_t format) } } +static bool has_uv_swapped(uint32_t format) +{ + switch (format) { + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV61: + case DRM_FORMAT_NV42: + return true; + default: + return false; + } +} + static enum vop_data_format vop_convert_format(uint32_t format) { switch (format) { @@ -277,10 +289,13 @@ static enum vop_data_format vop_convert_format(uint32_t format) case DRM_FORMAT_BGR565: return VOP_FMT_RGB565; case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: return VOP_FMT_YUV420SP; case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: return VOP_FMT_YUV422SP; case DRM_FORMAT_NV24: + case DRM_FORMAT_NV42: return VOP_FMT_YUV444SP; default: DRM_ERROR("unsupported format[%08x]\n", format); @@ -899,7 +914,7 @@ static void vop_plane_atomic_update(struct drm_plane *plane, unsigned long offset; dma_addr_t dma_addr; uint32_t val; - bool rb_swap; + bool rb_swap, uv_swap; int win_index = VOP_WIN_TO_INDEX(vop_win); int format; int is_yuv = fb->format->is_yuv; @@ -988,6 +1003,9 @@ static void vop_plane_atomic_update(struct drm_plane *plane, y2r_coefficients[i], bt601_yuv2rgb[i]); } + + uv_swap = has_uv_swapped(fb->format->format); + VOP_WIN_SET(vop, win, uv_swap, uv_swap); } if (win->phy->scl) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h index 857d97cdc67c..3aa95fdd427d 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h @@ -166,6 +166,7 @@ struct vop_win_phy { struct vop_reg gate; struct vop_reg format; struct vop_reg rb_swap; + struct vop_reg uv_swap; struct vop_reg act_info; struct vop_reg dsp_info; struct vop_reg dsp_st; diff --git a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c index 798b542e5916..d03dd0402923 100644 --- a/drivers/gpu/drm/rockchip/rockchip_vop_reg.c +++ b/drivers/gpu/drm/rockchip/rockchip_vop_reg.c @@ -46,8 +46,11 @@ static const uint32_t formats_win_full[] = { DRM_FORMAT_RGB565, DRM_FORMAT_BGR565, DRM_FORMAT_NV12, + DRM_FORMAT_NV21, DRM_FORMAT_NV16, + DRM_FORMAT_NV61, DRM_FORMAT_NV24, + DRM_FORMAT_NV42, }; static const uint64_t format_modifiers_win_full[] = { @@ -272,6 +275,7 @@ static const struct vop_win_phy px30_win0_data = { .enable = VOP_REG(PX30_WIN0_CTRL0, 0x1, 0), .format = VOP_REG(PX30_WIN0_CTRL0, 0x7, 1), .rb_swap = VOP_REG(PX30_WIN0_CTRL0, 0x1, 12), + .uv_swap = VOP_REG(PX30_WIN0_CTRL0, 0x1, 15), .act_info = VOP_REG(PX30_WIN0_ACT_INFO, 0xffffffff, 0), .dsp_info = VOP_REG(PX30_WIN0_DSP_INFO, 0xffffffff, 0), .dsp_st = VOP_REG(PX30_WIN0_DSP_ST, 0xffffffff, 0), @@ -291,6 +295,7 @@ static const struct vop_win_phy px30_win1_data = { .enable = VOP_REG(PX30_WIN1_CTRL0, 0x1, 0), .format = VOP_REG(PX30_WIN1_CTRL0, 0x7, 4), .rb_swap = VOP_REG(PX30_WIN1_CTRL0, 0x1, 12), + .uv_swap = VOP_REG(PX30_WIN1_CTRL0, 0x1, 15), .dsp_info = VOP_REG(PX30_WIN1_DSP_INFO, 0xffffffff, 0), .dsp_st = VOP_REG(PX30_WIN1_DSP_ST, 0xffffffff, 0), .yrgb_mst = VOP_REG(PX30_WIN1_MST, 0xffffffff, 0), @@ -368,6 +373,7 @@ static const struct vop_win_phy rk3066_win0_data = { .enable = VOP_REG(RK3066_SYS_CTRL1, 0x1, 0), .format = VOP_REG(RK3066_SYS_CTRL1, 0x7, 4), .rb_swap = VOP_REG(RK3066_SYS_CTRL1, 0x1, 19), + .uv_swap = VOP_REG(RK3066_SYS_CTRL1, 0x1, 22), .act_info = VOP_REG(RK3066_WIN0_ACT_INFO, 0x1fff1fff, 0), .dsp_info = VOP_REG(RK3066_WIN0_DSP_INFO, 0x0fff0fff, 0), .dsp_st = VOP_REG(RK3066_WIN0_DSP_ST, 0x1fff1fff, 0), @@ -386,6 +392,7 @@ static const struct vop_win_phy rk3066_win1_data = { .enable = VOP_REG(RK3066_SYS_CTRL1, 0x1, 1), .format = VOP_REG(RK3066_SYS_CTRL1, 0x7, 7), .rb_swap = VOP_REG(RK3066_SYS_CTRL1, 0x1, 23), + .uv_swap = VOP_REG(RK3066_SYS_CTRL1, 0x1, 26), .act_info = VOP_REG(RK3066_WIN1_ACT_INFO, 0x1fff1fff, 0), .dsp_info = VOP_REG(RK3066_WIN1_DSP_INFO, 0x0fff0fff, 0), .dsp_st = VOP_REG(RK3066_WIN1_DSP_ST, 0x1fff1fff, 0), @@ -489,6 +496,7 @@ static const struct vop_win_phy rk3188_win0_data = { .enable = VOP_REG(RK3188_SYS_CTRL, 0x1, 0), .format = VOP_REG(RK3188_SYS_CTRL, 0x7, 3), .rb_swap = VOP_REG(RK3188_SYS_CTRL, 0x1, 15), + .uv_swap = VOP_REG(RK3188_SYS_CTRL, 0x1, 18), .act_info = VOP_REG(RK3188_WIN0_ACT_INFO, 0x1fff1fff, 0), .dsp_info = VOP_REG(RK3188_WIN0_DSP_INFO, 0x0fff0fff, 0), .dsp_st = VOP_REG(RK3188_WIN0_DSP_ST, 0x1fff1fff, 0), @@ -619,6 +627,7 @@ static const struct vop_win_phy rk3288_win01_data = { .enable = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 0), .format = VOP_REG(RK3288_WIN0_CTRL0, 0x7, 1), .rb_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 12), + .uv_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 15), .act_info = VOP_REG(RK3288_WIN0_ACT_INFO, 0x1fff1fff, 0), .dsp_info = VOP_REG(RK3288_WIN0_DSP_INFO, 0x0fff0fff, 0), .dsp_st = VOP_REG(RK3288_WIN0_DSP_ST, 0x1fff1fff, 0), @@ -753,6 +762,7 @@ static const struct vop_win_phy rk3368_win01_data = { .enable = VOP_REG(RK3368_WIN0_CTRL0, 0x1, 0), .format = VOP_REG(RK3368_WIN0_CTRL0, 0x7, 1), .rb_swap = VOP_REG(RK3368_WIN0_CTRL0, 0x1, 12), + .uv_swap = VOP_REG(RK3368_WIN0_CTRL0, 0x1, 15), .x_mir_en = VOP_REG(RK3368_WIN0_CTRL0, 0x1, 21), .y_mir_en = VOP_REG(RK3368_WIN0_CTRL0, 0x1, 22), .act_info = VOP_REG(RK3368_WIN0_ACT_INFO, 0x1fff1fff, 0), @@ -902,6 +912,7 @@ static const struct vop_win_phy rk3399_win01_data = { .enable = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 0), .format = VOP_REG(RK3288_WIN0_CTRL0, 0x7, 1), .rb_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 12), + .uv_swap = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 15), .x_mir_en = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 21), .y_mir_en = VOP_REG(RK3288_WIN0_CTRL0, 0x1, 22), .act_info = VOP_REG(RK3288_WIN0_ACT_INFO, 0x1fff1fff, 0), -- cgit v1.2.3 From 540b8f271e53362a308f6bf288d38b630cf3fbd2 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 22 Apr 2022 09:28:19 +0200 Subject: drm/rockchip: Embed drm_encoder into rockchip_decoder The VOP2 driver needs rockchip specific information for a drm_encoder. This patch creates a struct rockchip_encoder with a struct drm_encoder embedded in it. This is used throughout the rockchip driver instead of struct drm_encoder directly. The information the VOP2 drivers needs is the of_graph endpoint node of the encoder. To ease bisectability this is added here. While at it convert the different encoder-to-driverdata macros to static inline functions in order to gain type safety and readability. Signed-off-by: Sascha Hauer Tested-by: Michael Riesch Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220422072841.2206452-3-s.hauer@pengutronix.de --- drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 32 +++++++++++++++-------- drivers/gpu/drm/rockchip/cdn-dp-core.c | 18 ++++++++----- drivers/gpu/drm/rockchip/cdn-dp-core.h | 2 +- drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c | 17 ++++++++----- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 11 +++++--- drivers/gpu/drm/rockchip/inno_hdmi.c | 32 +++++++++++++++-------- drivers/gpu/drm/rockchip/rk3066_hdmi.c | 34 ++++++++++++++++--------- drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 10 ++++++++ drivers/gpu/drm/rockchip/rockchip_lvds.c | 26 +++++++++++-------- 9 files changed, 122 insertions(+), 60 deletions(-) diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c index f7e3fb94ed04..70be64ca0a00 100644 --- a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -40,8 +40,6 @@ #define PSR_WAIT_LINE_FLAG_TIMEOUT_MS 100 -#define to_dp(nm) container_of(nm, struct rockchip_dp_device, nm) - /** * struct rockchip_dp_chip_data - splite the grf setting of kind of chips * @lcdsel_grf_reg: grf register offset of lcdc select @@ -59,7 +57,7 @@ struct rockchip_dp_chip_data { struct rockchip_dp_device { struct drm_device *drm_dev; struct device *dev; - struct drm_encoder encoder; + struct rockchip_encoder encoder; struct drm_display_mode mode; struct clk *pclk; @@ -73,6 +71,18 @@ struct rockchip_dp_device { struct analogix_dp_plat_data plat_data; }; +static struct rockchip_dp_device *encoder_to_dp(struct drm_encoder *encoder) +{ + struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); + + return container_of(rkencoder, struct rockchip_dp_device, encoder); +} + +static struct rockchip_dp_device *pdata_encoder_to_dp(struct analogix_dp_plat_data *plat_data) +{ + return container_of(plat_data, struct rockchip_dp_device, plat_data); +} + static int rockchip_dp_pre_init(struct rockchip_dp_device *dp) { reset_control_assert(dp->rst); @@ -84,7 +94,7 @@ static int rockchip_dp_pre_init(struct rockchip_dp_device *dp) static int rockchip_dp_poweron_start(struct analogix_dp_plat_data *plat_data) { - struct rockchip_dp_device *dp = to_dp(plat_data); + struct rockchip_dp_device *dp = pdata_encoder_to_dp(plat_data); int ret; ret = clk_prepare_enable(dp->pclk); @@ -105,7 +115,7 @@ static int rockchip_dp_poweron_start(struct analogix_dp_plat_data *plat_data) static int rockchip_dp_powerdown(struct analogix_dp_plat_data *plat_data) { - struct rockchip_dp_device *dp = to_dp(plat_data); + struct rockchip_dp_device *dp = pdata_encoder_to_dp(plat_data); clk_disable_unprepare(dp->pclk); @@ -166,7 +176,7 @@ struct drm_crtc *rockchip_dp_drm_get_new_crtc(struct drm_encoder *encoder, static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder, struct drm_atomic_state *state) { - struct rockchip_dp_device *dp = to_dp(encoder); + struct rockchip_dp_device *dp = encoder_to_dp(encoder); struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state; int ret; @@ -208,7 +218,7 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder, static void rockchip_dp_drm_encoder_disable(struct drm_encoder *encoder, struct drm_atomic_state *state) { - struct rockchip_dp_device *dp = to_dp(encoder); + struct rockchip_dp_device *dp = encoder_to_dp(encoder); struct drm_crtc *crtc; struct drm_crtc_state *new_crtc_state = NULL; int ret; @@ -297,7 +307,7 @@ static int rockchip_dp_of_probe(struct rockchip_dp_device *dp) static int rockchip_dp_drm_create_encoder(struct rockchip_dp_device *dp) { - struct drm_encoder *encoder = &dp->encoder; + struct drm_encoder *encoder = &dp->encoder.encoder; struct drm_device *drm_dev = dp->drm_dev; struct device *dev = dp->dev; int ret; @@ -333,7 +343,7 @@ static int rockchip_dp_bind(struct device *dev, struct device *master, return ret; } - dp->plat_data.encoder = &dp->encoder; + dp->plat_data.encoder = &dp->encoder.encoder; ret = analogix_dp_bind(dp->adp, drm_dev); if (ret) @@ -341,7 +351,7 @@ static int rockchip_dp_bind(struct device *dev, struct device *master, return 0; err_cleanup_encoder: - dp->encoder.funcs->destroy(&dp->encoder); + dp->encoder.encoder.funcs->destroy(&dp->encoder.encoder); return ret; } @@ -351,7 +361,7 @@ static void rockchip_dp_unbind(struct device *dev, struct device *master, struct rockchip_dp_device *dp = dev_get_drvdata(dev); analogix_dp_unbind(dp->adp); - dp->encoder.funcs->destroy(&dp->encoder); + dp->encoder.encoder.funcs->destroy(&dp->encoder.encoder); } static const struct component_ops rockchip_dp_component_ops = { diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c index 0d027b10fbb3..c204e9b95c1f 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.c +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c @@ -26,11 +26,17 @@ #include "cdn-dp-reg.h" #include "rockchip_drm_vop.h" -#define connector_to_dp(c) \ - container_of(c, struct cdn_dp_device, connector) +static inline struct cdn_dp_device *connector_to_dp(struct drm_connector *connector) +{ + return container_of(connector, struct cdn_dp_device, connector); +} -#define encoder_to_dp(c) \ - container_of(c, struct cdn_dp_device, encoder) +static inline struct cdn_dp_device *encoder_to_dp(struct drm_encoder *encoder) +{ + struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); + + return container_of(rkencoder, struct cdn_dp_device, encoder); +} #define GRF_SOC_CON9 0x6224 #define DP_SEL_VOP_LIT BIT(12) @@ -1050,7 +1056,7 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data) INIT_WORK(&dp->event_work, cdn_dp_pd_event_work); - encoder = &dp->encoder; + encoder = &dp->encoder.encoder; encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node); @@ -1115,7 +1121,7 @@ err_free_encoder: static void cdn_dp_unbind(struct device *dev, struct device *master, void *data) { struct cdn_dp_device *dp = dev_get_drvdata(dev); - struct drm_encoder *encoder = &dp->encoder; + struct drm_encoder *encoder = &dp->encoder.encoder; struct drm_connector *connector = &dp->connector; cancel_work_sync(&dp->event_work); diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.h b/drivers/gpu/drm/rockchip/cdn-dp-core.h index cd6c3089e35c..5b2fed1f5f55 100644 --- a/drivers/gpu/drm/rockchip/cdn-dp-core.h +++ b/drivers/gpu/drm/rockchip/cdn-dp-core.h @@ -66,7 +66,7 @@ struct cdn_dp_device { struct device *dev; struct drm_device *drm_dev; struct drm_connector connector; - struct drm_encoder encoder; + struct rockchip_encoder encoder; struct drm_display_mode mode; struct platform_device *audio_pdev; struct work_struct event_work; diff --git a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c index 4ed7a6868197..110e83aad9bb 100644 --- a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c @@ -181,8 +181,6 @@ #define HIWORD_UPDATE(val, mask) (val | (mask) << 16) -#define to_dsi(nm) container_of(nm, struct dw_mipi_dsi_rockchip, nm) - enum { DW_DSI_USAGE_IDLE, DW_DSI_USAGE_DSI, @@ -236,7 +234,7 @@ struct rockchip_dw_dsi_chip_data { struct dw_mipi_dsi_rockchip { struct device *dev; - struct drm_encoder encoder; + struct rockchip_encoder encoder; void __iomem *base; struct regmap *grf_regmap; @@ -271,6 +269,13 @@ struct dw_mipi_dsi_rockchip { bool dsi_bound; }; +static struct dw_mipi_dsi_rockchip *to_dsi(struct drm_encoder *encoder) +{ + struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); + + return container_of(rkencoder, struct dw_mipi_dsi_rockchip, encoder); +} + struct dphy_pll_parameter_map { unsigned int max_mbps; u8 hsfreqrange; @@ -770,7 +775,7 @@ static void dw_mipi_dsi_encoder_enable(struct drm_encoder *encoder) int ret, mux; mux = drm_of_encoder_active_endpoint_id(dsi->dev->of_node, - &dsi->encoder); + &dsi->encoder.encoder); if (mux < 0) return; @@ -801,7 +806,7 @@ dw_mipi_dsi_encoder_helper_funcs = { static int rockchip_dsi_drm_create_encoder(struct dw_mipi_dsi_rockchip *dsi, struct drm_device *drm_dev) { - struct drm_encoder *encoder = &dsi->encoder; + struct drm_encoder *encoder = &dsi->encoder.encoder; int ret; encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, @@ -959,7 +964,7 @@ static int dw_mipi_dsi_rockchip_bind(struct device *dev, goto out_pll_clk; } - ret = dw_mipi_dsi_bind(dsi->dmd, &dsi->encoder); + ret = dw_mipi_dsi_bind(dsi->dmd, &dsi->encoder.encoder); if (ret) { DRM_DEV_ERROR(dev, "Failed to bind: %d\n", ret); goto out_pll_clk; diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 8677c8271678..06c9ddef6f36 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -67,7 +67,7 @@ struct rockchip_hdmi_chip_data { struct rockchip_hdmi { struct device *dev; struct regmap *regmap; - struct drm_encoder encoder; + struct rockchip_encoder encoder; const struct rockchip_hdmi_chip_data *chip_data; struct clk *vpll_clk; struct clk *grf_clk; @@ -75,7 +75,12 @@ struct rockchip_hdmi { struct phy *phy; }; -#define to_rockchip_hdmi(x) container_of(x, struct rockchip_hdmi, x) +static struct rockchip_hdmi *to_rockchip_hdmi(struct drm_encoder *encoder) +{ + struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); + + return container_of(rkencoder, struct rockchip_hdmi, encoder); +} static const struct dw_hdmi_mpll_config rockchip_mpll_cfg[] = { { @@ -511,7 +516,7 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, hdmi->dev = &pdev->dev; hdmi->chip_data = plat_data->phy_data; plat_data->phy_data = hdmi; - encoder = &hdmi->encoder; + encoder = &hdmi->encoder.encoder; encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); /* diff --git a/drivers/gpu/drm/rockchip/inno_hdmi.c b/drivers/gpu/drm/rockchip/inno_hdmi.c index aa8b704d44a2..87b2243ea23e 100644 --- a/drivers/gpu/drm/rockchip/inno_hdmi.c +++ b/drivers/gpu/drm/rockchip/inno_hdmi.c @@ -26,8 +26,6 @@ #include "inno_hdmi.h" -#define to_inno_hdmi(x) container_of(x, struct inno_hdmi, x) - struct hdmi_data_info { int vic; bool sink_has_audio; @@ -55,7 +53,7 @@ struct inno_hdmi { void __iomem *regs; struct drm_connector connector; - struct drm_encoder encoder; + struct rockchip_encoder encoder; struct inno_hdmi_i2c *i2c; struct i2c_adapter *ddc; @@ -66,6 +64,18 @@ struct inno_hdmi { struct drm_display_mode previous_mode; }; +static struct inno_hdmi *encoder_to_inno_hdmi(struct drm_encoder *encoder) +{ + struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); + + return container_of(rkencoder, struct inno_hdmi, encoder); +} + +static struct inno_hdmi *connector_to_inno_hdmi(struct drm_connector *connector) +{ + return container_of(connector, struct inno_hdmi, connector); +} + enum { CSC_ITU601_16_235_TO_RGB_0_255_8BIT, CSC_ITU601_0_255_TO_RGB_0_255_8BIT, @@ -484,7 +494,7 @@ static void inno_hdmi_encoder_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, struct drm_display_mode *adj_mode) { - struct inno_hdmi *hdmi = to_inno_hdmi(encoder); + struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder); inno_hdmi_setup(hdmi, adj_mode); @@ -494,14 +504,14 @@ static void inno_hdmi_encoder_mode_set(struct drm_encoder *encoder, static void inno_hdmi_encoder_enable(struct drm_encoder *encoder) { - struct inno_hdmi *hdmi = to_inno_hdmi(encoder); + struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder); inno_hdmi_set_pwr_mode(hdmi, NORMAL); } static void inno_hdmi_encoder_disable(struct drm_encoder *encoder) { - struct inno_hdmi *hdmi = to_inno_hdmi(encoder); + struct inno_hdmi *hdmi = encoder_to_inno_hdmi(encoder); inno_hdmi_set_pwr_mode(hdmi, LOWER_PWR); } @@ -537,7 +547,7 @@ static struct drm_encoder_helper_funcs inno_hdmi_encoder_helper_funcs = { static enum drm_connector_status inno_hdmi_connector_detect(struct drm_connector *connector, bool force) { - struct inno_hdmi *hdmi = to_inno_hdmi(connector); + struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); return (hdmi_readb(hdmi, HDMI_STATUS) & m_HOTPLUG) ? connector_status_connected : connector_status_disconnected; @@ -545,7 +555,7 @@ inno_hdmi_connector_detect(struct drm_connector *connector, bool force) static int inno_hdmi_connector_get_modes(struct drm_connector *connector) { - struct inno_hdmi *hdmi = to_inno_hdmi(connector); + struct inno_hdmi *hdmi = connector_to_inno_hdmi(connector); struct edid *edid; int ret = 0; @@ -599,7 +609,7 @@ static struct drm_connector_helper_funcs inno_hdmi_connector_helper_funcs = { static int inno_hdmi_register(struct drm_device *drm, struct inno_hdmi *hdmi) { - struct drm_encoder *encoder = &hdmi->encoder; + struct drm_encoder *encoder = &hdmi->encoder.encoder; struct device *dev = hdmi->dev; encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); @@ -879,7 +889,7 @@ static int inno_hdmi_bind(struct device *dev, struct device *master, return 0; err_cleanup_hdmi: hdmi->connector.funcs->destroy(&hdmi->connector); - hdmi->encoder.funcs->destroy(&hdmi->encoder); + hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder); err_put_adapter: i2c_put_adapter(hdmi->ddc); err_disable_clk: @@ -893,7 +903,7 @@ static void inno_hdmi_unbind(struct device *dev, struct device *master, struct inno_hdmi *hdmi = dev_get_drvdata(dev); hdmi->connector.funcs->destroy(&hdmi->connector); - hdmi->encoder.funcs->destroy(&hdmi->encoder); + hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder); i2c_put_adapter(hdmi->ddc); clk_disable_unprepare(hdmi->pclk); diff --git a/drivers/gpu/drm/rockchip/rk3066_hdmi.c b/drivers/gpu/drm/rockchip/rk3066_hdmi.c index a09dbb7d446a..c8c3612a4fe2 100644 --- a/drivers/gpu/drm/rockchip/rk3066_hdmi.c +++ b/drivers/gpu/drm/rockchip/rk3066_hdmi.c @@ -46,7 +46,7 @@ struct rk3066_hdmi { void __iomem *regs; struct drm_connector connector; - struct drm_encoder encoder; + struct rockchip_encoder encoder; struct rk3066_hdmi_i2c *i2c; struct i2c_adapter *ddc; @@ -57,7 +57,17 @@ struct rk3066_hdmi { struct drm_display_mode previous_mode; }; -#define to_rk3066_hdmi(x) container_of(x, struct rk3066_hdmi, x) +static struct rk3066_hdmi *encoder_to_rk3066_hdmi(struct drm_encoder *encoder) +{ + struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); + + return container_of(rkencoder, struct rk3066_hdmi, encoder); +} + +static struct rk3066_hdmi *connector_to_rk3066_hdmi(struct drm_connector *connector) +{ + return container_of(connector, struct rk3066_hdmi, connector); +} static inline u8 hdmi_readb(struct rk3066_hdmi *hdmi, u16 offset) { @@ -381,7 +391,7 @@ rk3066_hdmi_encoder_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, struct drm_display_mode *adj_mode) { - struct rk3066_hdmi *hdmi = to_rk3066_hdmi(encoder); + struct rk3066_hdmi *hdmi = encoder_to_rk3066_hdmi(encoder); /* Store the display mode for plugin/DPMS poweron events. */ memcpy(&hdmi->previous_mode, adj_mode, sizeof(hdmi->previous_mode)); @@ -389,7 +399,7 @@ rk3066_hdmi_encoder_mode_set(struct drm_encoder *encoder, static void rk3066_hdmi_encoder_enable(struct drm_encoder *encoder) { - struct rk3066_hdmi *hdmi = to_rk3066_hdmi(encoder); + struct rk3066_hdmi *hdmi = encoder_to_rk3066_hdmi(encoder); int mux, val; mux = drm_of_encoder_active_endpoint_id(hdmi->dev->of_node, encoder); @@ -408,7 +418,7 @@ static void rk3066_hdmi_encoder_enable(struct drm_encoder *encoder) static void rk3066_hdmi_encoder_disable(struct drm_encoder *encoder) { - struct rk3066_hdmi *hdmi = to_rk3066_hdmi(encoder); + struct rk3066_hdmi *hdmi = encoder_to_rk3066_hdmi(encoder); DRM_DEV_DEBUG(hdmi->dev, "hdmi encoder disable\n"); @@ -456,7 +466,7 @@ struct drm_encoder_helper_funcs rk3066_hdmi_encoder_helper_funcs = { static enum drm_connector_status rk3066_hdmi_connector_detect(struct drm_connector *connector, bool force) { - struct rk3066_hdmi *hdmi = to_rk3066_hdmi(connector); + struct rk3066_hdmi *hdmi = connector_to_rk3066_hdmi(connector); return (hdmi_readb(hdmi, HDMI_HPG_MENS_STA) & HDMI_HPG_IN_STATUS_HIGH) ? connector_status_connected : connector_status_disconnected; @@ -464,7 +474,7 @@ rk3066_hdmi_connector_detect(struct drm_connector *connector, bool force) static int rk3066_hdmi_connector_get_modes(struct drm_connector *connector) { - struct rk3066_hdmi *hdmi = to_rk3066_hdmi(connector); + struct rk3066_hdmi *hdmi = connector_to_rk3066_hdmi(connector); struct edid *edid; int ret = 0; @@ -496,9 +506,9 @@ rk3066_hdmi_connector_mode_valid(struct drm_connector *connector, static struct drm_encoder * rk3066_hdmi_connector_best_encoder(struct drm_connector *connector) { - struct rk3066_hdmi *hdmi = to_rk3066_hdmi(connector); + struct rk3066_hdmi *hdmi = connector_to_rk3066_hdmi(connector); - return &hdmi->encoder; + return &hdmi->encoder.encoder; } static int @@ -538,7 +548,7 @@ struct drm_connector_helper_funcs rk3066_hdmi_connector_helper_funcs = { static int rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi) { - struct drm_encoder *encoder = &hdmi->encoder; + struct drm_encoder *encoder = &hdmi->encoder.encoder; struct device *dev = hdmi->dev; encoder->possible_crtcs = @@ -816,7 +826,7 @@ static int rk3066_hdmi_bind(struct device *dev, struct device *master, err_cleanup_hdmi: hdmi->connector.funcs->destroy(&hdmi->connector); - hdmi->encoder.funcs->destroy(&hdmi->encoder); + hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder); err_disable_i2c: i2c_put_adapter(hdmi->ddc); err_disable_hclk: @@ -831,7 +841,7 @@ static void rk3066_hdmi_unbind(struct device *dev, struct device *master, struct rk3066_hdmi *hdmi = dev_get_drvdata(dev); hdmi->connector.funcs->destroy(&hdmi->connector); - hdmi->encoder.funcs->destroy(&hdmi->encoder); + hdmi->encoder.encoder.funcs->destroy(&hdmi->encoder.encoder); i2c_put_adapter(hdmi->ddc); clk_disable_unprepare(hdmi->hclk); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index 008c44aef400..048ab277de65 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -49,6 +49,10 @@ struct rockchip_drm_private { struct drm_mm mm; }; +struct rockchip_encoder { + struct drm_encoder encoder; +}; + int rockchip_drm_dma_attach_device(struct drm_device *drm_dev, struct device *dev); void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, @@ -66,4 +70,10 @@ extern struct platform_driver rockchip_dp_driver; extern struct platform_driver rockchip_lvds_driver; extern struct platform_driver vop_platform_driver; extern struct platform_driver rk3066_hdmi_driver; + +static inline struct rockchip_encoder *to_rockchip_encoder(struct drm_encoder *encoder) +{ + return container_of(encoder, struct rockchip_encoder, encoder); +} + #endif /* _ROCKCHIP_DRM_DRV_H_ */ diff --git a/drivers/gpu/drm/rockchip/rockchip_lvds.c b/drivers/gpu/drm/rockchip/rockchip_lvds.c index 997b7d46a2d1..5a284332ec49 100644 --- a/drivers/gpu/drm/rockchip/rockchip_lvds.c +++ b/drivers/gpu/drm/rockchip/rockchip_lvds.c @@ -36,12 +36,6 @@ struct rockchip_lvds; -#define connector_to_lvds(c) \ - container_of(c, struct rockchip_lvds, connector) - -#define encoder_to_lvds(c) \ - container_of(c, struct rockchip_lvds, encoder) - /** * struct rockchip_lvds_soc_data - rockchip lvds Soc private data * @probe: LVDS platform probe function @@ -65,10 +59,22 @@ struct rockchip_lvds { struct drm_panel *panel; struct drm_bridge *bridge; struct drm_connector connector; - struct drm_encoder encoder; + struct rockchip_encoder encoder; struct dev_pin_info *pins; }; +static inline struct rockchip_lvds *connector_to_lvds(struct drm_connector *connector) +{ + return container_of(connector, struct rockchip_lvds, connector); +} + +static inline struct rockchip_lvds *encoder_to_lvds(struct drm_encoder *encoder) +{ + struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); + + return container_of(rkencoder, struct rockchip_lvds, encoder); +} + static inline void rk3288_writel(struct rockchip_lvds *lvds, u32 offset, u32 val) { @@ -599,7 +605,7 @@ static int rockchip_lvds_bind(struct device *dev, struct device *master, goto err_put_remote; } - encoder = &lvds->encoder; + encoder = &lvds->encoder.encoder; encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node); @@ -674,10 +680,10 @@ static void rockchip_lvds_unbind(struct device *dev, struct device *master, const struct drm_encoder_helper_funcs *encoder_funcs; encoder_funcs = lvds->soc_data->helper_funcs; - encoder_funcs->disable(&lvds->encoder); + encoder_funcs->disable(&lvds->encoder.encoder); pm_runtime_disable(dev); drm_connector_cleanup(&lvds->connector); - drm_encoder_cleanup(&lvds->encoder); + drm_encoder_cleanup(&lvds->encoder.encoder); } static const struct component_ops rockchip_lvds_component_ops = { -- cgit v1.2.3 From cf544c6a885c52d79e4d8bf139fb8cb63a878512 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 22 Apr 2022 09:28:20 +0200 Subject: drm/rockchip: Add crtc_endpoint_id to rockchip_encoder The VOP2 has an interface mux which decides to which encoder(s) a CRTC is routed to. The encoders and CRTCs are connected via of_graphs in the device tree. When given an encoder the VOP2 driver needs to know to which internal register setting this encoder matches. For this the VOP2 binding offers different endpoints, one for each possible encoder. The endpoint ids of these endpoints are used as a key from an encoders device tree description to the internal register setting. This patch adds the key aka endpoint id to struct rockchip_encoder plus a function to read the endpoint id starting from the encoders device node. Signed-off-by: Sascha Hauer Tested-by: Michael Riesch Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220422072841.2206452-4-s.hauer@pengutronix.de --- drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 33 +++++++++++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 4 +++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index 7efd12312354..0dc09d92d92d 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -244,6 +244,39 @@ static const struct dev_pm_ops rockchip_drm_pm_ops = { static struct platform_driver *rockchip_sub_drivers[MAX_ROCKCHIP_SUB_DRIVERS]; static int num_rockchip_sub_drivers; +/* + * Get the endpoint id of the remote endpoint of the given encoder. This + * information is used by the VOP2 driver to identify the encoder. + * + * @rkencoder: The encoder to get the remote endpoint id from + * @np: The encoder device node + * @port: The number of the port leading to the VOP2 + * @reg: The endpoint number leading to the VOP2 + */ +int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rkencoder, + struct device_node *np, int port, int reg) +{ + struct of_endpoint ep; + struct device_node *en, *ren; + int ret; + + en = of_graph_get_endpoint_by_regs(np, port, reg); + if (!en) + return -ENOENT; + + ren = of_graph_get_remote_endpoint(en); + if (!ren) + return -ENOENT; + + ret = of_graph_parse_endpoint(ren, &ep); + if (ret) + return ret; + + rkencoder->crtc_endpoint_id = ep.id; + + return 0; +} + /* * Check if a vop endpoint is leading to a rockchip subdriver or bridge. * Should be called from the component bind stage of the drivers diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index 048ab277de65..a27ab928e1d2 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -50,6 +50,7 @@ struct rockchip_drm_private { }; struct rockchip_encoder { + int crtc_endpoint_id; struct drm_encoder encoder; }; @@ -60,7 +61,8 @@ void rockchip_drm_dma_detach_device(struct drm_device *drm_dev, void rockchip_drm_dma_init_device(struct drm_device *drm_dev, struct device *dev); int rockchip_drm_wait_vact_end(struct drm_crtc *crtc, unsigned int mstimeout); - +int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rencoder, + struct device_node *np, int port, int reg); int rockchip_drm_endpoint_is_subdriver(struct device_node *ep); extern struct platform_driver cdn_dp_driver; extern struct platform_driver dw_hdmi_rockchip_pltfm_driver; -- cgit v1.2.3 From a9d37e684492ab5db1cce28b655e20c01191873f Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 22 Apr 2022 09:28:21 +0200 Subject: drm/rockchip: dw_hdmi: rename vpll clock to reference clock "vpll" is a misnomer. A clock input to a device should be named after the usage in the device, not after the clock that drives it. On the rk3568 the same clock is driven by the HPLL. To fix that, this patch renames the vpll clock to ref clock. The clock name "vpll" is left for compatibility to old device trees. Signed-off-by: Sascha Hauer Reviewed-by: Dmitry Osipenko Tested-by: Michael Riesch Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220422072841.2206452-5-s.hauer@pengutronix.de --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 06c9ddef6f36..912181429880 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -69,7 +69,7 @@ struct rockchip_hdmi { struct regmap *regmap; struct rockchip_encoder encoder; const struct rockchip_hdmi_chip_data *chip_data; - struct clk *vpll_clk; + struct clk *ref_clk; struct clk *grf_clk; struct dw_hdmi *hdmi; struct phy *phy; @@ -201,14 +201,15 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi) return PTR_ERR(hdmi->regmap); } - hdmi->vpll_clk = devm_clk_get(hdmi->dev, "vpll"); - if (PTR_ERR(hdmi->vpll_clk) == -ENOENT) { - hdmi->vpll_clk = NULL; - } else if (PTR_ERR(hdmi->vpll_clk) == -EPROBE_DEFER) { + hdmi->ref_clk = devm_clk_get_optional(hdmi->dev, "ref"); + if (!hdmi->ref_clk) + hdmi->ref_clk = devm_clk_get_optional(hdmi->dev, "vpll"); + + if (PTR_ERR(hdmi->ref_clk) == -EPROBE_DEFER) { return -EPROBE_DEFER; - } else if (IS_ERR(hdmi->vpll_clk)) { - DRM_DEV_ERROR(hdmi->dev, "failed to get vpll clock\n"); - return PTR_ERR(hdmi->vpll_clk); + } else if (IS_ERR(hdmi->ref_clk)) { + DRM_DEV_ERROR(hdmi->dev, "failed to get reference clock\n"); + return PTR_ERR(hdmi->ref_clk); } hdmi->grf_clk = devm_clk_get(hdmi->dev, "grf"); @@ -262,7 +263,7 @@ static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder, { struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder); - clk_set_rate(hdmi->vpll_clk, adj_mode->clock * 1000); + clk_set_rate(hdmi->ref_clk, adj_mode->clock * 1000); } static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder) @@ -542,9 +543,9 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, return ret; } - ret = clk_prepare_enable(hdmi->vpll_clk); + ret = clk_prepare_enable(hdmi->ref_clk); if (ret) { - DRM_DEV_ERROR(hdmi->dev, "Failed to enable HDMI vpll: %d\n", + DRM_DEV_ERROR(hdmi->dev, "Failed to enable HDMI reference clock: %d\n", ret); return ret; } @@ -563,7 +564,7 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, if (IS_ERR(hdmi->hdmi)) { ret = PTR_ERR(hdmi->hdmi); drm_encoder_cleanup(encoder); - clk_disable_unprepare(hdmi->vpll_clk); + clk_disable_unprepare(hdmi->ref_clk); } return ret; @@ -575,7 +576,7 @@ static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master, struct rockchip_hdmi *hdmi = dev_get_drvdata(dev); dw_hdmi_unbind(hdmi->hdmi); - clk_disable_unprepare(hdmi->vpll_clk); + clk_disable_unprepare(hdmi->ref_clk); } static const struct component_ops dw_hdmi_rockchip_ops = { -- cgit v1.2.3 From a5fc012e6ee75a899173398573e77207542f588a Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 22 Apr 2022 09:28:22 +0200 Subject: dt-bindings: display: rockchip: dw-hdmi: use "ref" as clock name "vpll" is a misnomer. A clock input to a device should be named after the usage in the device, not after the clock that drives it. On the rk3568 the same clock is driven by the HPLL. This patch adds "ref" as a new alternative clock name for "vpll" Signed-off-by: Sascha Hauer Acked-by: Rob Herring Tested-by: Michael Riesch Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220422072841.2206452-6-s.hauer@pengutronix.de --- .../devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml index da3b889ad8fc..0400f67e5f2c 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml @@ -36,7 +36,8 @@ properties: # order when present. - description: The HDMI CEC controller main clock - description: Power for GRF IO - - description: External clock for some HDMI PHY + - description: External clock for some HDMI PHY (old clock name, deprecated) + - description: External clock for some HDMI PHY (new name) clock-names: minItems: 2 @@ -47,10 +48,14 @@ properties: - cec - grf - vpll + - ref - enum: - grf - vpll - - const: vpll + - ref + - enum: + - vpll + - ref ddc-i2c-bus: $ref: /schemas/types.yaml#/definitions/phandle -- cgit v1.2.3 From 77b0693f875d4d916d0518ac0de68d4d0de3934f Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Fri, 22 Apr 2022 09:28:25 +0200 Subject: dt-bindings: display: rockchip: dw-hdmi: Add compatible for rk3568 HDMI Define a new compatible for rk3568 HDMI. This version of HDMI hardware block needs two new clocks hclk_vio and hclk to provide phy reference clocks. Signed-off-by: Benjamin Gaignard Reviewed-by: Rob Herring Signed-off-by: Sascha Hauer Tested-by: Michael Riesch Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220422072841.2206452-9-s.hauer@pengutronix.de --- Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml index 0400f67e5f2c..e6b8437a1e2d 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml @@ -23,6 +23,7 @@ properties: - rockchip,rk3288-dw-hdmi - rockchip,rk3328-dw-hdmi - rockchip,rk3399-dw-hdmi + - rockchip,rk3568-dw-hdmi reg-io-width: const: 4 -- cgit v1.2.3 From 28bbb5ffbe32741e65d798070986d212cc11e1bb Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 22 Apr 2022 09:28:24 +0200 Subject: drm/rockchip: dw_hdmi: add rk3568 support Add a new dw_hdmi_plat_data struct and new compatible for rk3568. Signed-off-by: Benjamin Gaignard Signed-off-by: Sascha Hauer Tested-by: Michael Riesch Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220422072841.2206452-8-s.hauer@pengutronix.de --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 912181429880..b64cc62c7b5a 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -50,6 +50,10 @@ #define RK3399_GRF_SOC_CON20 0x6250 #define RK3399_HDMI_LCDC_SEL BIT(6) +#define RK3568_GRF_VO_CON1 0x0364 +#define RK3568_HDMI_SDAIN_MSK BIT(15) +#define RK3568_HDMI_SCLIN_MSK BIT(14) + #define HIWORD_UPDATE(val, mask) (val | (mask) << 16) /** @@ -473,6 +477,19 @@ static const struct dw_hdmi_plat_data rk3399_hdmi_drv_data = { .use_drm_infoframe = true, }; +static struct rockchip_hdmi_chip_data rk3568_chip_data = { + .lcdsel_grf_reg = -1, +}; + +static const struct dw_hdmi_plat_data rk3568_hdmi_drv_data = { + .mode_valid = dw_hdmi_rockchip_mode_valid, + .mpll_cfg = rockchip_mpll_cfg, + .cur_ctr = rockchip_cur_ctr, + .phy_config = rockchip_phy_config, + .phy_data = &rk3568_chip_data, + .use_drm_infoframe = true, +}; + static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = { { .compatible = "rockchip,rk3228-dw-hdmi", .data = &rk3228_hdmi_drv_data @@ -486,6 +503,9 @@ static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = { { .compatible = "rockchip,rk3399-dw-hdmi", .data = &rk3399_hdmi_drv_data }, + { .compatible = "rockchip,rk3568-dw-hdmi", + .data = &rk3568_hdmi_drv_data + }, {}, }; MODULE_DEVICE_TABLE(of, dw_hdmi_rockchip_dt_ids); @@ -520,6 +540,9 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, encoder = &hdmi->encoder.encoder; encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + rockchip_drm_encoder_set_crtc_endpoint_id(&hdmi->encoder, + dev->of_node, 0, 0); + /* * If we failed to find the CRTC(s) which this encoder is * supposed to be connected to, it's because the CRTC has @@ -550,6 +573,14 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, return ret; } + if (hdmi->chip_data == &rk3568_chip_data) { + regmap_write(hdmi->regmap, RK3568_GRF_VO_CON1, + HIWORD_UPDATE(RK3568_HDMI_SDAIN_MSK | + RK3568_HDMI_SCLIN_MSK, + RK3568_HDMI_SDAIN_MSK | + RK3568_HDMI_SCLIN_MSK)); + } + drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs); drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS); -- cgit v1.2.3 From 80266ccb864b6443fd0bf54bb17444d2632fad21 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 22 Apr 2022 09:28:27 +0200 Subject: dt-bindings: display: rockchip: dw-hdmi: Add regulator support The RK3568 has HDMI_TX_AVDD0V9 and HDMI_TX_AVDD_1V8 supply inputs needed for the HDMI port. Add the binding for these supplies. Signed-off-by: Sascha Hauer Acked-by: Rob Herring Tested-by: Michael Riesch Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220422072841.2206452-11-s.hauer@pengutronix.de --- .../bindings/display/rockchip/rockchip,dw-hdmi.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml index e6b8437a1e2d..38ebb6983028 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml @@ -28,6 +28,17 @@ properties: reg-io-width: const: 4 + avdd-0v9-supply: + description: + A 0.9V supply that powers up the SoC internal circuitry. The actual pin name + varies between the different SoCs and is usually HDMI_TX_AVDD_0V9 or sometimes + HDMI_AVDD_1V0. + + avdd-1v8-supply: + description: + A 1.8V supply that powers up the SoC internal circuitry. The pin name on the + SoC usually is HDMI_TX_AVDD_1V8. + clocks: minItems: 2 items: -- cgit v1.2.3 From ca80c4eb4b01a7f1c2f333d0a329937ef9c7f03a Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 22 Apr 2022 09:28:26 +0200 Subject: drm/rockchip: dw_hdmi: add regulator support The RK3568 has HDMI_TX_AVDD0V9 and HDMI_TX_AVDD_1V8 supply inputs needed for the HDMI port. add support for these to the driver for boards which have them supplied by switchable regulators. Signed-off-by: Sascha Hauer Reviewed-by: Dmitry Osipenko Tested-by: Michael Riesch Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220422072841.2206452-10-s.hauer@pengutronix.de --- drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c | 41 ++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index b64cc62c7b5a..c14f88893868 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -76,6 +77,8 @@ struct rockchip_hdmi { struct clk *ref_clk; struct clk *grf_clk; struct dw_hdmi *hdmi; + struct regulator *avdd_0v9; + struct regulator *avdd_1v8; struct phy *phy; }; @@ -226,6 +229,14 @@ static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi) return PTR_ERR(hdmi->grf_clk); } + hdmi->avdd_0v9 = devm_regulator_get(hdmi->dev, "avdd-0v9"); + if (IS_ERR(hdmi->avdd_0v9)) + return PTR_ERR(hdmi->avdd_0v9); + + hdmi->avdd_1v8 = devm_regulator_get(hdmi->dev, "avdd-1v8"); + if (IS_ERR(hdmi->avdd_1v8)) + return PTR_ERR(hdmi->avdd_1v8); + return 0; } @@ -566,11 +577,23 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, return ret; } + ret = regulator_enable(hdmi->avdd_0v9); + if (ret) { + DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd0v9: %d\n", ret); + goto err_avdd_0v9; + } + + ret = regulator_enable(hdmi->avdd_1v8); + if (ret) { + DRM_DEV_ERROR(hdmi->dev, "failed to enable avdd1v8: %d\n", ret); + goto err_avdd_1v8; + } + ret = clk_prepare_enable(hdmi->ref_clk); if (ret) { DRM_DEV_ERROR(hdmi->dev, "Failed to enable HDMI reference clock: %d\n", ret); - return ret; + goto err_clk; } if (hdmi->chip_data == &rk3568_chip_data) { @@ -594,10 +617,19 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, */ if (IS_ERR(hdmi->hdmi)) { ret = PTR_ERR(hdmi->hdmi); - drm_encoder_cleanup(encoder); - clk_disable_unprepare(hdmi->ref_clk); + goto err_bind; } + return 0; + +err_bind: + drm_encoder_cleanup(encoder); + clk_disable_unprepare(hdmi->ref_clk); +err_clk: + regulator_disable(hdmi->avdd_1v8); +err_avdd_1v8: + regulator_disable(hdmi->avdd_0v9); +err_avdd_0v9: return ret; } @@ -608,6 +640,9 @@ static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master, dw_hdmi_unbind(hdmi->hdmi); clk_disable_unprepare(hdmi->ref_clk); + + regulator_disable(hdmi->avdd_1v8); + regulator_disable(hdmi->avdd_0v9); } static const struct component_ops dw_hdmi_rockchip_ops = { -- cgit v1.2.3 From 6e944f52a225484b87bb343d0ba28165edf04b19 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 22 Apr 2022 09:28:32 +0200 Subject: dt-bindings: display: rockchip: dw-hdmi: Make unwedge pinctrl optional None of the upstream device tree files has a "unwedge" pinctrl specified. Make it optional. Signed-off-by: Sascha Hauer Acked-by: Rob Herring Tested-by: Michael Riesch Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220422072841.2206452-16-s.hauer@pengutronix.de --- Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml index 38ebb6983028..d7cb2b2be60e 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml @@ -89,6 +89,7 @@ properties: The unwedge pinctrl entry shall drive the DDC SDA line low. This is intended to work around a hardware errata that can cause the DDC I2C bus to be wedged. + minItems: 1 items: - const: default - const: unwedge -- cgit v1.2.3 From 594e9c04b5864b4b8b151ef4ba9521c59e0f5c54 Mon Sep 17 00:00:00 2001 From: KuoHsiang Chou Date: Thu, 28 Apr 2022 15:56:03 +0800 Subject: drm/ast: Create the driver for ASPEED proprietory Display-Port V1: 1. The MCU FW controling ASPEED DP is loaded by BMC boot loader. 2. Driver starts after CR[3:1] == 111b that indicates Tx is ASTDP, and CRD1[5] has been asserted by BMVC boot loader. 3. EDID is prioritized by DP monitor. 4. DP's EDID has high priority to decide resolution supporting. V2: Modules description: 1. ASTDP (ASPEED DisplayPort) is controlled by dedicated AST-MCU (ASPEED propriatary MCU). 2. MCU is looping in charged of HPD, Read EDID, Link Training with DP sink. 3. ASTDP and AST-MUC reside in BMC (Baseboard Management controller) addressing-space. 4. ASPEED DRM driver requests MCU to get HPD and EDID by CR-scratched register. Booting sequence: 1. Check if TX is ASTDP // ast_dp_launch() 2. Check if DP-MCU FW has loaded // ast_dp_launch() 3. Read EDID // ast_dp_read_edid() 4. Resolution switch // ast_dp_SetOutput() V3: 1. Remove unneeded semicolon. 2. Apply to git://anongit.freedesktop.org/drm/drm, instead of git://anongit.freedesktop.org/drm/drm-misc 3. Resolve auto build test WARNINGs on V1 patch. V4: 1. Sync code-base with kernel 5.17_rc6 2. Remove the define of DPControlPower, because DP chips need to be powered on to be used. 3. Remove the switches of PHY and Display from EDID procedure. 4. Revise increaing delay to fixed delay, because this version kernel doesn't detect minitor consistenntly. 5. Create clean-up code used for reset of power state on errors with -EIO manner. 6. Revise the DP detection by TX type and its DP-FW status during booting and resume. 7. Correct the CamelCase Style. 8. Use register reading while needing, and remove to hold full register. 9. Instead of 'u8', revise to 'bool' on swwitch of PHY and video. 10.Correct typo 11.Remove the duplicated copy of TX definition. 12.Use EDID_LENGTH as the constant of 128. Signed-off-by: KuoHsiang Chou Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20220428075603.20904-1-kuohsiang_chou@aspeedtech.com --- drivers/gpu/drm/ast/Makefile | 2 +- drivers/gpu/drm/ast/ast_dp.c | 282 +++++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/ast/ast_drv.h | 115 +++++++++++++++++ drivers/gpu/drm/ast/ast_main.c | 5 +- drivers/gpu/drm/ast/ast_mode.c | 124 +++++++++++++++++- drivers/gpu/drm/ast/ast_post.c | 4 +- 6 files changed, 524 insertions(+), 8 deletions(-) create mode 100644 drivers/gpu/drm/ast/ast_dp.c diff --git a/drivers/gpu/drm/ast/Makefile b/drivers/gpu/drm/ast/Makefile index 21f71160bc3e..5a53ce51fb24 100644 --- a/drivers/gpu/drm/ast/Makefile +++ b/drivers/gpu/drm/ast/Makefile @@ -3,6 +3,6 @@ # Makefile for the drm device driver. This driver provides support for the # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. -ast-y := ast_drv.o ast_i2c.o ast_main.o ast_mm.o ast_mode.o ast_post.o ast_dp501.o +ast-y := ast_drv.o ast_i2c.o ast_main.o ast_mm.o ast_mode.o ast_post.o ast_dp501.o ast_dp.o obj-$(CONFIG_DRM_AST) := ast.o diff --git a/drivers/gpu/drm/ast/ast_dp.c b/drivers/gpu/drm/ast/ast_dp.c new file mode 100644 index 000000000000..4551bc8a3ecf --- /dev/null +++ b/drivers/gpu/drm/ast/ast_dp.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2021, ASPEED Technology Inc. +// Authors: KuoHsiang Chou + +#include +#include +#include +#include "ast_drv.h" + +int ast_astdp_read_edid(struct drm_device *dev, u8 *ediddata) +{ + struct ast_private *ast = to_ast_private(dev); + u8 i = 0, j = 0; + + /* + * CRD1[b5]: DP MCU FW is executing + * CRDC[b0]: DP link success + * CRDF[b0]: DP HPD + * CRE5[b0]: Host reading EDID process is done + */ + if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD1, ASTDP_MCU_FW_EXECUTING) && + ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDC, ASTDP_LINK_SUCCESS) && + ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDF, ASTDP_HPD) && + ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, + ASTDP_HOST_EDID_READ_DONE_MASK))) { + goto err_astdp_edid_not_ready; + } + + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, (u8) ~ASTDP_HOST_EDID_READ_DONE_MASK, + 0x00); + + for (i = 0; i < 32; i++) { + /* + * CRE4[7:0]: Read-Pointer for EDID (Unit: 4bytes); valid range: 0~64 + */ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE4, + (u8) ~ASTDP_EDID_READ_POINTER_MASK, (u8) i); + j = 0; + + /* + * CRD7[b0]: valid flag for EDID + * CRD6[b0]: mirror read pointer for EDID + */ + while ((ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD7, + ASTDP_EDID_VALID_FLAG_MASK) != 0x01) || + (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD6, + ASTDP_EDID_READ_POINTER_MASK) != i)) { + /* + * Delay are getting longer with each retry. + * 1. The Delays are often 2 loops when users request "Display Settings" + * of right-click of mouse. + * 2. The Delays are often longer a lot when system resume from S3/S4. + */ + mdelay(j+1); + + if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD1, + ASTDP_MCU_FW_EXECUTING) && + ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDC, + ASTDP_LINK_SUCCESS) && + ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDF, ASTDP_HPD))) { + goto err_astdp_jump_out_loop_of_edid; + } + + j++; + if (j > 200) + goto err_astdp_jump_out_loop_of_edid; + } + + *(ediddata) = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, + 0xD8, ASTDP_EDID_READ_DATA_MASK); + *(ediddata + 1) = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD9, + ASTDP_EDID_READ_DATA_MASK); + *(ediddata + 2) = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDA, + ASTDP_EDID_READ_DATA_MASK); + *(ediddata + 3) = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDB, + ASTDP_EDID_READ_DATA_MASK); + + if (i == 31) { + /* + * For 128-bytes EDID_1.3, + * 1. Add the value of Bytes-126 to Bytes-127. + * The Bytes-127 is Checksum. Sum of all 128bytes should + * equal 0 (mod 256). + * 2. Modify Bytes-126 to be 0. + * The Bytes-126 indicates the Number of extensions to + * follow. 0 represents noextensions. + */ + *(ediddata + 3) = *(ediddata + 3) + *(ediddata + 2); + *(ediddata + 2) = 0; + } + + ediddata += 4; + } + + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, (u8) ~ASTDP_HOST_EDID_READ_DONE_MASK, + ASTDP_HOST_EDID_READ_DONE); + + return 0; + +err_astdp_jump_out_loop_of_edid: + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, + (u8) ~ASTDP_HOST_EDID_READ_DONE_MASK, + ASTDP_HOST_EDID_READ_DONE); + return (~(j+256) + 1); + +err_astdp_edid_not_ready: + if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD1, ASTDP_MCU_FW_EXECUTING))) + return (~0xD1 + 1); + if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDC, ASTDP_LINK_SUCCESS))) + return (~0xDC + 1); + if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDF, ASTDP_HPD))) + return (~0xDF + 1); + if (!(ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, ASTDP_HOST_EDID_READ_DONE_MASK))) + return (~0xE5 + 1); + + return 0; +} + +/* + * Launch Aspeed DP + */ +void ast_dp_launch(struct drm_device *dev, u8 bPower) +{ + u32 i = 0, j = 0, WaitCount = 1; + u8 bDPTX = 0; + u8 bDPExecute = 1; + + struct ast_private *ast = to_ast_private(dev); + // S3 come back, need more time to wait BMC ready. + if (bPower) + WaitCount = 300; + + + // Wait total count by different condition. + for (j = 0; j < WaitCount; j++) { + bDPTX = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD1, TX_TYPE_MASK); + + if (bDPTX) + break; + + msleep(100); + } + + // 0xE : ASTDP with DPMCU FW handling + if (bDPTX == ASTDP_DPMCU_TX) { + // Wait one second then timeout. + i = 0; + + while (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xD1, COPROCESSOR_LAUNCH) != + COPROCESSOR_LAUNCH) { + i++; + // wait 100 ms + msleep(100); + + if (i >= 10) { + // DP would not be ready. + bDPExecute = 0; + break; + } + } + + if (bDPExecute) + ast->tx_chip_type = AST_TX_ASTDP; + + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE5, + (u8) ~ASTDP_HOST_EDID_READ_DONE_MASK, + ASTDP_HOST_EDID_READ_DONE); + } else + ast->tx_chip_type = AST_TX_NONE; +} + + + +void ast_dp_power_on_off(struct drm_device *dev, bool on) +{ + struct ast_private *ast = to_ast_private(dev); + // Read and Turn off DP PHY sleep + u8 bE3 = ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE3, AST_DP_VIDEO_ENABLE); + + // Turn on DP PHY sleep + if (!on) + bE3 |= AST_DP_PHY_SLEEP; + + // DP Power on/off + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE3, (u8) ~AST_DP_PHY_SLEEP, bE3); +} + + + +void ast_dp_set_on_off(struct drm_device *dev, bool on) +{ + struct ast_private *ast = to_ast_private(dev); + u8 video_on_off = on; + + // Video On/Off + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE3, (u8) ~AST_DP_VIDEO_ENABLE, on); + + // If DP plug in and link successful then check video on / off status + if (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDC, ASTDP_LINK_SUCCESS) && + ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDF, ASTDP_HPD)) { + video_on_off <<= 4; + while (ast_get_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xDF, + ASTDP_MIRROR_VIDEO_ENABLE) != video_on_off) { + // wait 1 ms + mdelay(1); + } + } +} + +void ast_dp_set_mode(struct drm_crtc *crtc, struct ast_vbios_mode_info *vbios_mode) +{ + struct ast_private *ast = to_ast_private(crtc->dev); + + u32 ulRefreshRateIndex; + u8 ModeIdx; + + ulRefreshRateIndex = vbios_mode->enh_table->refresh_rate_index - 1; + + switch (crtc->mode.crtc_hdisplay) { + case 320: + ModeIdx = ASTDP_320x240_60; + break; + case 400: + ModeIdx = ASTDP_400x300_60; + break; + case 512: + ModeIdx = ASTDP_512x384_60; + break; + case 640: + ModeIdx = (ASTDP_640x480_60 + (u8) ulRefreshRateIndex); + break; + case 800: + ModeIdx = (ASTDP_800x600_56 + (u8) ulRefreshRateIndex); + break; + case 1024: + ModeIdx = (ASTDP_1024x768_60 + (u8) ulRefreshRateIndex); + break; + case 1152: + ModeIdx = ASTDP_1152x864_75; + break; + case 1280: + if (crtc->mode.crtc_vdisplay == 800) + ModeIdx = (ASTDP_1280x800_60_RB - (u8) ulRefreshRateIndex); + else // 1024 + ModeIdx = (ASTDP_1280x1024_60 + (u8) ulRefreshRateIndex); + break; + case 1360: + case 1366: + ModeIdx = ASTDP_1366x768_60; + break; + case 1440: + ModeIdx = (ASTDP_1440x900_60_RB - (u8) ulRefreshRateIndex); + break; + case 1600: + if (crtc->mode.crtc_vdisplay == 900) + ModeIdx = (ASTDP_1600x900_60_RB - (u8) ulRefreshRateIndex); + else //1200 + ModeIdx = ASTDP_1600x1200_60; + break; + case 1680: + ModeIdx = (ASTDP_1680x1050_60_RB - (u8) ulRefreshRateIndex); + break; + case 1920: + if (crtc->mode.crtc_vdisplay == 1080) + ModeIdx = ASTDP_1920x1080_60; + else //1200 + ModeIdx = ASTDP_1920x1200_60; + break; + default: + return; + } + + /* + * CRE0[7:0]: MISC0 ((0x00: 18-bpp) or (0x20: 24-bpp) + * CRE1[7:0]: MISC1 (default: 0x00) + * CRE2[7:0]: video format index (0x00 ~ 0x20 or 0x40 ~ 0x50) + */ + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE0, (u8) ~ASTDP_CLEAR_MASK, + ASTDP_MISC0_24bpp); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE1, (u8) ~ASTDP_CLEAR_MASK, ASTDP_MISC1); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xE2, (u8) ~ASTDP_CLEAR_MASK, ModeIdx); +} diff --git a/drivers/gpu/drm/ast/ast_drv.h b/drivers/gpu/drm/ast/ast_drv.h index a19315b2f7e5..ff1ae314bd0d 100644 --- a/drivers/gpu/drm/ast/ast_drv.h +++ b/drivers/gpu/drm/ast/ast_drv.h @@ -70,6 +70,7 @@ enum ast_tx_chip { AST_TX_NONE, AST_TX_SIL164, AST_TX_DP501, + AST_TX_ASTDP, }; #define AST_DRAM_512Mx16 0 @@ -184,6 +185,10 @@ struct ast_private { struct drm_encoder encoder; struct drm_connector connector; } dp501; + struct { + struct drm_encoder encoder; + struct drm_connector connector; + } astdp; } output; bool support_wide_screen; @@ -357,10 +362,113 @@ int ast_mode_config_init(struct ast_private *ast); #define AST_DP501_EDID_DATA 0xf020 /* Define for Soc scratched reg */ +#define COPROCESSOR_LAUNCH BIT(5) + +/* + * Display Transmitter Type: + */ +#define TX_TYPE_MASK GENMASK(3, 1) +#define NO_TX (0 << 1) +#define ITE66121_VBIOS_TX (1 << 1) +#define SI164_VBIOS_TX (2 << 1) +#define CH7003_VBIOS_TX (3 << 1) +#define DP501_VBIOS_TX (4 << 1) +#define ANX9807_VBIOS_TX (5 << 1) +#define TX_FW_EMBEDDED_FW_TX (6 << 1) +#define ASTDP_DPMCU_TX (7 << 1) + #define AST_VRAM_INIT_STATUS_MASK GENMASK(7, 6) //#define AST_VRAM_INIT_BY_BMC BIT(7) //#define AST_VRAM_INIT_READY BIT(6) +/* Define for Soc scratched reg used on ASTDP */ +#define AST_DP_PHY_SLEEP BIT(4) +#define AST_DP_VIDEO_ENABLE BIT(0) + +#define AST_DP_POWER_ON true +#define AST_DP_POWER_OFF false + +/* + * CRD1[b5]: DP MCU FW is executing + * CRDC[b0]: DP link success + * CRDF[b0]: DP HPD + * CRE5[b0]: Host reading EDID process is done + */ +#define ASTDP_MCU_FW_EXECUTING BIT(5) +#define ASTDP_LINK_SUCCESS BIT(0) +#define ASTDP_HPD BIT(0) +#define ASTDP_HOST_EDID_READ_DONE BIT(0) +#define ASTDP_HOST_EDID_READ_DONE_MASK GENMASK(0, 0) + +/* + * CRB8[b1]: Enable VSYNC off + * CRB8[b0]: Enable HSYNC off + */ +#define AST_DPMS_VSYNC_OFF BIT(1) +#define AST_DPMS_HSYNC_OFF BIT(0) + +/* + * CRDF[b4]: Mirror of AST_DP_VIDEO_ENABLE + * Precondition: A. ~AST_DP_PHY_SLEEP && + * B. DP_HPD && + * C. DP_LINK_SUCCESS + */ +#define ASTDP_MIRROR_VIDEO_ENABLE BIT(4) + +#define ASTDP_EDID_READ_POINTER_MASK GENMASK(7, 0) +#define ASTDP_EDID_VALID_FLAG_MASK GENMASK(0, 0) +#define ASTDP_EDID_READ_DATA_MASK GENMASK(7, 0) + +/* + * ASTDP setmode registers: + * CRE0[7:0]: MISC0 ((0x00: 18-bpp) or (0x20: 24-bpp) + * CRE1[7:0]: MISC1 (default: 0x00) + * CRE2[7:0]: video format index (0x00 ~ 0x20 or 0x40 ~ 0x50) + */ +#define ASTDP_MISC0_24bpp BIT(5) +#define ASTDP_MISC1 0 +#define ASTDP_CLEAR_MASK GENMASK(7, 0) + +/* + * ASTDP resoultion table: + * EX: ASTDP_A_B_C: + * A: Resolution + * B: Refresh Rate + * C: Misc information, such as CVT, Reduce Blanked + */ +#define ASTDP_640x480_60 0x00 +#define ASTDP_640x480_72 0x01 +#define ASTDP_640x480_75 0x02 +#define ASTDP_640x480_85 0x03 +#define ASTDP_800x600_56 0x04 +#define ASTDP_800x600_60 0x05 +#define ASTDP_800x600_72 0x06 +#define ASTDP_800x600_75 0x07 +#define ASTDP_800x600_85 0x08 +#define ASTDP_1024x768_60 0x09 +#define ASTDP_1024x768_70 0x0A +#define ASTDP_1024x768_75 0x0B +#define ASTDP_1024x768_85 0x0C +#define ASTDP_1280x1024_60 0x0D +#define ASTDP_1280x1024_75 0x0E +#define ASTDP_1280x1024_85 0x0F +#define ASTDP_1600x1200_60 0x10 +#define ASTDP_320x240_60 0x11 +#define ASTDP_400x300_60 0x12 +#define ASTDP_512x384_60 0x13 +#define ASTDP_1920x1200_60 0x14 +#define ASTDP_1920x1080_60 0x15 +#define ASTDP_1280x800_60 0x16 +#define ASTDP_1280x800_60_RB 0x17 +#define ASTDP_1440x900_60 0x18 +#define ASTDP_1440x900_60_RB 0x19 +#define ASTDP_1680x1050_60 0x1A +#define ASTDP_1680x1050_60_RB 0x1B +#define ASTDP_1600x900_60 0x1C +#define ASTDP_1600x900_60_RB 0x1D +#define ASTDP_1366x768_60 0x1E +#define ASTDP_1152x864_75 0x1F + int ast_mm_init(struct ast_private *ast); /* ast post */ @@ -381,4 +489,11 @@ void ast_init_3rdtx(struct drm_device *dev); /* ast_i2c.c */ struct ast_i2c_chan *ast_i2c_create(struct drm_device *dev); +/* aspeed DP */ +int ast_astdp_read_edid(struct drm_device *dev, u8 *ediddata); +void ast_dp_launch(struct drm_device *dev, u8 bPower); +void ast_dp_power_on_off(struct drm_device *dev, bool no); +void ast_dp_set_on_off(struct drm_device *dev, bool no); +void ast_dp_set_mode(struct drm_crtc *crtc, struct ast_vbios_mode_info *vbios_mode); + #endif diff --git a/drivers/gpu/drm/ast/ast_main.c b/drivers/gpu/drm/ast/ast_main.c index 22e9e2d3c71a..1113ee1cb7d4 100644 --- a/drivers/gpu/drm/ast/ast_main.c +++ b/drivers/gpu/drm/ast/ast_main.c @@ -232,7 +232,7 @@ static int ast_detect_chip(struct drm_device *dev, bool *need_post) ast->tx_chip_type = AST_TX_SIL164; } - if ((ast->chip == AST2300) || (ast->chip == AST2400)) { + if ((ast->chip == AST2300) || (ast->chip == AST2400) || (ast->chip == AST2500)) { /* * On AST2300 and 2400, look the configuration set by the SoC in * the SOC scratch register #1 bits 11:8 (interestingly marked @@ -256,7 +256,8 @@ static int ast_detect_chip(struct drm_device *dev, bool *need_post) case 0x0c: ast->tx_chip_type = AST_TX_DP501; } - } + } else if (ast->chip == AST2600) + ast_dp_launch(&ast->base, 0); /* Print stuff for diagnostic purposes */ switch(ast->tx_chip_type) { diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c index 45b56b39ad47..4728825b7b67 100644 --- a/drivers/gpu/drm/ast/ast_mode.c +++ b/drivers/gpu/drm/ast/ast_mode.c @@ -988,21 +988,41 @@ err_drm_gem_vram_put: static void ast_crtc_dpms(struct drm_crtc *crtc, int mode) { struct ast_private *ast = to_ast_private(crtc->dev); + u8 ch = AST_DPMS_VSYNC_OFF | AST_DPMS_HSYNC_OFF; /* TODO: Maybe control display signal generation with * Sync Enable (bit CR17.7). */ switch (mode) { case DRM_MODE_DPMS_ON: - case DRM_MODE_DPMS_STANDBY: - case DRM_MODE_DPMS_SUSPEND: + ast_set_index_reg_mask(ast, AST_IO_SEQ_PORT, 0x01, 0xdf, 0); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb6, 0xfc, 0); if (ast->tx_chip_type == AST_TX_DP501) ast_set_dp501_video_output(crtc->dev, 1); + + if (ast->tx_chip_type == AST_TX_ASTDP) { + ast_dp_power_on_off(crtc->dev, AST_DP_POWER_ON); + ast_wait_for_vretrace(ast); + ast_dp_set_on_off(crtc->dev, 1); + } + + ast_crtc_load_lut(ast, crtc); break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: case DRM_MODE_DPMS_OFF: + ch = mode; if (ast->tx_chip_type == AST_TX_DP501) ast_set_dp501_video_output(crtc->dev, 0); break; + + if (ast->tx_chip_type == AST_TX_ASTDP) { + ast_dp_set_on_off(crtc->dev, 0); + ast_dp_power_on_off(crtc->dev, AST_DP_POWER_OFF); + } + + ast_set_index_reg_mask(ast, AST_IO_SEQ_PORT, 0x01, 0xdf, 0x20); + ast_set_index_reg_mask(ast, AST_IO_CRTC_PORT, 0xb6, 0xfc, ch); } } @@ -1027,7 +1047,7 @@ ast_crtc_helper_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode if ((ast->chip == AST2100) || (ast->chip == AST2200) || (ast->chip == AST2300) || (ast->chip == AST2400) || - (ast->chip == AST2500)) { + (ast->chip == AST2500) || (ast->chip == AST2600)) { if ((mode->hdisplay == 1920) && (mode->vdisplay == 1080)) return MODE_OK; @@ -1110,6 +1130,7 @@ ast_crtc_helper_atomic_flush(struct drm_crtc *crtc, struct ast_private *ast = to_ast_private(crtc->dev); struct ast_crtc_state *ast_crtc_state = to_ast_crtc_state(crtc_state); struct ast_crtc_state *old_ast_crtc_state = to_ast_crtc_state(old_crtc_state); + struct ast_vbios_mode_info *vbios_mode_info = &ast_crtc_state->vbios_mode_info; /* * The gamma LUT has to be reloaded after changing the primary @@ -1117,6 +1138,10 @@ ast_crtc_helper_atomic_flush(struct drm_crtc *crtc, */ if (old_ast_crtc_state->format != ast_crtc_state->format) ast_crtc_load_lut(ast, crtc); + + //Set Aspeed Display-Port + if (ast->tx_chip_type == AST_TX_ASTDP) + ast_dp_set_mode(crtc, vbios_mode_info); } static void @@ -1527,6 +1552,93 @@ static int ast_dp501_output_init(struct ast_private *ast) return 0; } +/* + * ASPEED Display-Port Connector + */ + +static int ast_astdp_connector_helper_get_modes(struct drm_connector *connector) +{ + void *edid; + + int succ; + int count; + + edid = kmalloc(EDID_LENGTH, GFP_KERNEL); + if (!edid) + goto err_drm_connector_update_edid_property; + + succ = ast_astdp_read_edid(connector->dev, edid); + if (succ < 0) + goto err_kfree; + + drm_connector_update_edid_property(connector, edid); + count = drm_add_edid_modes(connector, edid); + kfree(edid); + + return count; + +err_kfree: + kfree(edid); +err_drm_connector_update_edid_property: + drm_connector_update_edid_property(connector, NULL); + return 0; +} + +static const struct drm_connector_helper_funcs ast_astdp_connector_helper_funcs = { + .get_modes = ast_astdp_connector_helper_get_modes, +}; + +static const struct drm_connector_funcs ast_astdp_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int ast_astdp_connector_init(struct drm_device *dev, struct drm_connector *connector) +{ + int ret; + + ret = drm_connector_init(dev, connector, &ast_astdp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (ret) + return ret; + + drm_connector_helper_add(connector, &ast_astdp_connector_helper_funcs); + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + connector->polled = DRM_CONNECTOR_POLL_CONNECT; + + return 0; +} + +static int ast_astdp_output_init(struct ast_private *ast) +{ + struct drm_device *dev = &ast->base; + struct drm_crtc *crtc = &ast->crtc; + struct drm_encoder *encoder = &ast->output.astdp.encoder; + struct drm_connector *connector = &ast->output.astdp.connector; + int ret; + + ret = drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_TMDS); + if (ret) + return ret; + encoder->possible_crtcs = drm_crtc_mask(crtc); + + ret = ast_astdp_connector_init(dev, connector); + if (ret) + return ret; + + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + return ret; + + return 0; +} + /* * Mode config */ @@ -1563,7 +1675,8 @@ int ast_mode_config_init(struct ast_private *ast) ast->chip == AST2200 || ast->chip == AST2300 || ast->chip == AST2400 || - ast->chip == AST2500) { + ast->chip == AST2500 || + ast->chip == AST2600) { dev->mode_config.max_width = 1920; dev->mode_config.max_height = 2048; } else { @@ -1594,6 +1707,9 @@ int ast_mode_config_init(struct ast_private *ast) case AST_TX_DP501: ret = ast_dp501_output_init(ast); break; + case AST_TX_ASTDP: + ret = ast_astdp_output_init(ast); + break; } if (ret) return ret; diff --git a/drivers/gpu/drm/ast/ast_post.c b/drivers/gpu/drm/ast/ast_post.c index b5d92f652fd8..0aa9cf0fb5c3 100644 --- a/drivers/gpu/drm/ast/ast_post.c +++ b/drivers/gpu/drm/ast/ast_post.c @@ -379,7 +379,9 @@ void ast_post_gpu(struct drm_device *dev) ast_enable_mmio(dev); ast_set_def_ext_reg(dev); - if (ast->config_mode == ast_use_p2a) { + if (ast->chip == AST2600) { + ast_dp_launch(dev, 1); + } else if (ast->config_mode == ast_use_p2a) { if (ast->chip == AST2500) ast_post_chip_2500(dev); else if (ast->chip == AST2300 || ast->chip == AST2400) -- cgit v1.2.3 From 5905585103276b7c14bb9a7de4b575216cb6dac4 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 29 Apr 2022 12:08:30 +0200 Subject: fbdev: Put mmap for deferred I/O into drivers The fbdev mmap function fb_mmap() unconditionally overrides the driver's implementation if deferred I/O has been activated. This makes it hard to implement mmap with anything but a vmalloc()'ed software buffer. That is specifically a problem for DRM, where video memory is maintained by a memory manager. Leave the mmap handling to drivers and expect them to call the helper for deferred I/O by thmeselves. v4: * unlock mm_lock in fb_mmap() error path (Dan) v3: * fix warning if fb_mmap is missing (kernel test robot) v2: * print a helpful error message if the defio setup is incorrect (Javier) Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20220429100834.18898-2-tzimmermann@suse.de --- drivers/gpu/drm/drm_fb_helper.c | 4 +++- drivers/gpu/drm/vmwgfx/vmwgfx_fb.c | 1 + drivers/hid/hid-picolcd_fb.c | 1 + drivers/staging/fbtft/fbtft-core.c | 1 + drivers/video/fbdev/broadsheetfb.c | 1 + drivers/video/fbdev/core/fb_defio.c | 1 + drivers/video/fbdev/core/fbmem.c | 22 ++++++++++++---------- drivers/video/fbdev/hecubafb.c | 1 + drivers/video/fbdev/hyperv_fb.c | 1 + drivers/video/fbdev/metronomefb.c | 1 + drivers/video/fbdev/sh_mobile_lcdcfb.c | 6 ++++++ drivers/video/fbdev/smscufx.c | 3 +++ drivers/video/fbdev/ssd1307fb.c | 1 + drivers/video/fbdev/udlfb.c | 3 +++ drivers/video/fbdev/xen-fbfront.c | 1 + 15 files changed, 37 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index d265a73313c9..d06ce0e92d66 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -2118,7 +2118,9 @@ static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) { struct drm_fb_helper *fb_helper = info->par; - if (fb_helper->dev->driver->gem_prime_mmap) + if (drm_fbdev_use_shadow_fb(fb_helper)) + return fb_deferred_io_mmap(info, vma); + else if (fb_helper->dev->driver->gem_prime_mmap) return fb_helper->dev->driver->gem_prime_mmap(fb_helper->buffer->gem, vma); else return -ENODEV; diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c b/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c index adf17c740656..02a4a4fa3da3 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c @@ -619,6 +619,7 @@ static const struct fb_ops vmw_fb_ops = { .fb_imageblit = vmw_fb_imageblit, .fb_pan_display = vmw_fb_pan_display, .fb_blank = vmw_fb_blank, + .fb_mmap = fb_deferred_io_mmap, }; int vmw_fb_init(struct vmw_private *vmw_priv) diff --git a/drivers/hid/hid-picolcd_fb.c b/drivers/hid/hid-picolcd_fb.c index 33c102a60992..78515e6bacf0 100644 --- a/drivers/hid/hid-picolcd_fb.c +++ b/drivers/hid/hid-picolcd_fb.c @@ -428,6 +428,7 @@ static const struct fb_ops picolcdfb_ops = { .fb_imageblit = picolcd_fb_imageblit, .fb_check_var = picolcd_fb_check_var, .fb_set_par = picolcd_set_par, + .fb_mmap = fb_deferred_io_mmap, }; diff --git a/drivers/staging/fbtft/fbtft-core.c b/drivers/staging/fbtft/fbtft-core.c index 9c4d797e7ae4..d4e14f7c9d57 100644 --- a/drivers/staging/fbtft/fbtft-core.c +++ b/drivers/staging/fbtft/fbtft-core.c @@ -652,6 +652,7 @@ struct fb_info *fbtft_framebuffer_alloc(struct fbtft_display *display, fbops->fb_imageblit = fbtft_fb_imageblit; fbops->fb_setcolreg = fbtft_fb_setcolreg; fbops->fb_blank = fbtft_fb_blank; + fbops->fb_mmap = fb_deferred_io_mmap; fbdefio->delay = HZ / fps; fbdefio->sort_pagelist = true; diff --git a/drivers/video/fbdev/broadsheetfb.c b/drivers/video/fbdev/broadsheetfb.c index b9054f658838..528bc0902338 100644 --- a/drivers/video/fbdev/broadsheetfb.c +++ b/drivers/video/fbdev/broadsheetfb.c @@ -1055,6 +1055,7 @@ static const struct fb_ops broadsheetfb_ops = { .fb_fillrect = broadsheetfb_fillrect, .fb_copyarea = broadsheetfb_copyarea, .fb_imageblit = broadsheetfb_imageblit, + .fb_mmap = fb_deferred_io_mmap, }; static struct fb_deferred_io broadsheetfb_defio = { diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c index 6aaf6d0abf39..6924d489a289 100644 --- a/drivers/video/fbdev/core/fb_defio.c +++ b/drivers/video/fbdev/core/fb_defio.c @@ -181,6 +181,7 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma) vma->vm_private_data = info; return 0; } +EXPORT_SYMBOL_GPL(fb_deferred_io_mmap); /* workqueue callback */ static void fb_deferred_io_work(struct work_struct *work) diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c index 84427470367b..b445a7a00def 100644 --- a/drivers/video/fbdev/core/fbmem.c +++ b/drivers/video/fbdev/core/fbmem.c @@ -1334,7 +1334,6 @@ static int fb_mmap(struct file *file, struct vm_area_struct * vma) { struct fb_info *info = file_fb_info(file); - int (*fb_mmap_fn)(struct fb_info *info, struct vm_area_struct *vma); unsigned long mmio_pgoff; unsigned long start; u32 len; @@ -1343,14 +1342,7 @@ fb_mmap(struct file *file, struct vm_area_struct * vma) return -ENODEV; mutex_lock(&info->mm_lock); - fb_mmap_fn = info->fbops->fb_mmap; - -#if IS_ENABLED(CONFIG_FB_DEFERRED_IO) - if (info->fbdefio) - fb_mmap_fn = fb_deferred_io_mmap; -#endif - - if (fb_mmap_fn) { + if (info->fbops->fb_mmap) { int res; /* @@ -1358,9 +1350,19 @@ fb_mmap(struct file *file, struct vm_area_struct * vma) * SME protection is removed ahead of the call */ vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot); - res = fb_mmap_fn(info, vma); + res = info->fbops->fb_mmap(info, vma); mutex_unlock(&info->mm_lock); return res; +#if IS_ENABLED(CONFIG_FB_DEFERRED_IO) + } else if (info->fbdefio) { + /* + * FB deferred I/O wants you to handle mmap in your drivers. At a + * minimum, point struct fb_ops.fb_mmap to fb_deferred_io_mmap(). + */ + dev_warn_once(info->dev, "fbdev mmap not set up for deferred I/O.\n"); + mutex_unlock(&info->mm_lock); + return -ENODEV; +#endif } /* diff --git a/drivers/video/fbdev/hecubafb.c b/drivers/video/fbdev/hecubafb.c index 00d77105161a..2c483e2cf9ec 100644 --- a/drivers/video/fbdev/hecubafb.c +++ b/drivers/video/fbdev/hecubafb.c @@ -204,6 +204,7 @@ static const struct fb_ops hecubafb_ops = { .fb_fillrect = hecubafb_fillrect, .fb_copyarea = hecubafb_copyarea, .fb_imageblit = hecubafb_imageblit, + .fb_mmap = fb_deferred_io_mmap, }; static struct fb_deferred_io hecubafb_defio = { diff --git a/drivers/video/fbdev/hyperv_fb.c b/drivers/video/fbdev/hyperv_fb.c index c8e0ea27caf1..d7f6abf827b9 100644 --- a/drivers/video/fbdev/hyperv_fb.c +++ b/drivers/video/fbdev/hyperv_fb.c @@ -909,6 +909,7 @@ static const struct fb_ops hvfb_ops = { .fb_copyarea = hvfb_cfb_copyarea, .fb_imageblit = hvfb_cfb_imageblit, .fb_blank = hvfb_blank, + .fb_mmap = fb_deferred_io_mmap, }; diff --git a/drivers/video/fbdev/metronomefb.c b/drivers/video/fbdev/metronomefb.c index af858dd23ea6..2541f2fe065b 100644 --- a/drivers/video/fbdev/metronomefb.c +++ b/drivers/video/fbdev/metronomefb.c @@ -564,6 +564,7 @@ static const struct fb_ops metronomefb_ops = { .fb_fillrect = metronomefb_fillrect, .fb_copyarea = metronomefb_copyarea, .fb_imageblit = metronomefb_imageblit, + .fb_mmap = fb_deferred_io_mmap, }; static struct fb_deferred_io metronomefb_defio = { diff --git a/drivers/video/fbdev/sh_mobile_lcdcfb.c b/drivers/video/fbdev/sh_mobile_lcdcfb.c index 9a4417430b4e..ad034eb0e7a0 100644 --- a/drivers/video/fbdev/sh_mobile_lcdcfb.c +++ b/drivers/video/fbdev/sh_mobile_lcdcfb.c @@ -1480,6 +1480,9 @@ sh_mobile_lcdc_overlay_mmap(struct fb_info *info, struct vm_area_struct *vma) { struct sh_mobile_lcdc_overlay *ovl = info->par; + if (info->fbdefio) + return fb_deferred_io_mmap(info, vma); + return dma_mmap_coherent(ovl->channel->lcdc->dev, vma, ovl->fb_mem, ovl->dma_handle, ovl->fb_size); } @@ -1954,6 +1957,9 @@ sh_mobile_lcdc_mmap(struct fb_info *info, struct vm_area_struct *vma) { struct sh_mobile_lcdc_chan *ch = info->par; + if (info->fbdefio) + return fb_deferred_io_mmap(info, vma); + return dma_mmap_coherent(ch->lcdc->dev, vma, ch->fb_mem, ch->dma_handle, ch->fb_size); } diff --git a/drivers/video/fbdev/smscufx.c b/drivers/video/fbdev/smscufx.c index 28768c272b73..9383f8d0914c 100644 --- a/drivers/video/fbdev/smscufx.c +++ b/drivers/video/fbdev/smscufx.c @@ -779,6 +779,9 @@ static int ufx_ops_mmap(struct fb_info *info, struct vm_area_struct *vma) unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; unsigned long page, pos; + if (info->fbdefio) + return fb_deferred_io_mmap(info, vma); + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) return -EINVAL; if (size > info->fix.smem_len) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index c6d5df31111d..7547b4628afc 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -368,6 +368,7 @@ static const struct fb_ops ssd1307fb_ops = { .fb_fillrect = ssd1307fb_fillrect, .fb_copyarea = ssd1307fb_copyarea, .fb_imageblit = ssd1307fb_imageblit, + .fb_mmap = fb_deferred_io_mmap, }; static void ssd1307fb_deferred_io(struct fb_info *info, diff --git a/drivers/video/fbdev/udlfb.c b/drivers/video/fbdev/udlfb.c index d280733f283b..179b30ba09f5 100644 --- a/drivers/video/fbdev/udlfb.c +++ b/drivers/video/fbdev/udlfb.c @@ -326,6 +326,9 @@ static int dlfb_ops_mmap(struct fb_info *info, struct vm_area_struct *vma) unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; unsigned long page, pos; + if (info->fbdefio) + return fb_deferred_io_mmap(info, vma); + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) return -EINVAL; if (size > info->fix.smem_len) diff --git a/drivers/video/fbdev/xen-fbfront.c b/drivers/video/fbdev/xen-fbfront.c index 6826f986da43..6c8846eba2fb 100644 --- a/drivers/video/fbdev/xen-fbfront.c +++ b/drivers/video/fbdev/xen-fbfront.c @@ -338,6 +338,7 @@ static const struct fb_ops xenfb_fb_ops = { .fb_imageblit = xenfb_imageblit, .fb_check_var = xenfb_check_var, .fb_set_par = xenfb_set_par, + .fb_mmap = fb_deferred_io_mmap, }; static irqreturn_t xenfb_event_handler(int rq, void *dev_id) -- cgit v1.2.3 From 56c134f7f1b58be08bdb0ca8372474a4a5165f31 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 29 Apr 2022 12:08:31 +0200 Subject: fbdev: Track deferred-I/O pages in pageref struct Store the per-page state for fbdev's deferred I/O in struct fb_deferred_io_pageref. Maintain a list of pagerefs for the pages that have to be written back to video memory. Update all affected drivers. As with pages before, fbdev acquires a pageref when an mmaped page of the framebuffer is being written to. It holds the pageref in a list of all currently written pagerefs until it flushes the written pages to video memory. Writeback occurs periodically. After writeback fbdev releases all pagerefs and builds up a new dirty list until the next writeback occurs. Using pagerefs has a number of benefits. For pages of the framebuffer, the deferred I/O code used struct page.lru as an entry into the list of dirty pages. The lru field is owned by the page cache, which makes deferred I/O incompatible with some memory pages (e.g., most notably DRM's GEM SHMEM allocator). struct fb_deferred_io_pageref now provides an entry into a list of dirty framebuffer pages, freeing lru for use with the page cache. Drivers also assumed that struct page.index is the page offset into the framebuffer. This is not true for DRM buffers, which are located at various offset within a mapped area. struct fb_deferred_io_pageref explicitly stores an offset into the framebuffer. struct page.index is now only the page offset into the mapped area. These changes will allow DRM to use fbdev deferred I/O without an intermediate shadow buffer. v3: * use pageref->offset for sorting * fix grammar in comment v2: * minor fixes in commit message Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20220429100834.18898-3-tzimmermann@suse.de --- drivers/gpu/drm/drm_fb_helper.c | 6 +- drivers/gpu/drm/vmwgfx/vmwgfx_fb.c | 5 +- drivers/staging/fbtft/fbtft-core.c | 5 +- drivers/video/fbdev/broadsheetfb.c | 5 +- drivers/video/fbdev/core/fb_defio.c | 156 ++++++++++++++++++++++----------- drivers/video/fbdev/hyperv_fb.c | 5 +- drivers/video/fbdev/metronomefb.c | 5 +- drivers/video/fbdev/sh_mobile_lcdcfb.c | 6 +- drivers/video/fbdev/smscufx.c | 5 +- drivers/video/fbdev/udlfb.c | 5 +- drivers/video/fbdev/xen-fbfront.c | 5 +- include/linux/fb.h | 11 ++- 12 files changed, 145 insertions(+), 74 deletions(-) diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index d06ce0e92d66..dd1d72d58b35 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -717,13 +717,13 @@ void drm_fb_helper_deferred_io(struct fb_info *info, struct list_head *pagelist) { unsigned long start, end, min, max; - struct page *page; + struct fb_deferred_io_pageref *pageref; struct drm_rect damage_area; min = ULONG_MAX; max = 0; - list_for_each_entry(page, pagelist, lru) { - start = page->index << PAGE_SHIFT; + list_for_each_entry(pageref, pagelist, list) { + start = pageref->offset; end = start + PAGE_SIZE; min = min(min, start); max = max(max, end); diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c b/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c index 02a4a4fa3da3..db02e17e12b6 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c @@ -322,12 +322,13 @@ static void vmw_deferred_io(struct fb_info *info, struct vmw_fb_par *par = info->par; unsigned long start, end, min, max; unsigned long flags; - struct page *page; + struct fb_deferred_io_pageref *pageref; int y1, y2; min = ULONG_MAX; max = 0; - list_for_each_entry(page, pagelist, lru) { + list_for_each_entry(pageref, pagelist, list) { + struct page *page = pageref->page; start = page->index << PAGE_SHIFT; end = start + PAGE_SIZE - 1; min = min(min, start); diff --git a/drivers/staging/fbtft/fbtft-core.c b/drivers/staging/fbtft/fbtft-core.c index d4e14f7c9d57..e9662822e1ef 100644 --- a/drivers/staging/fbtft/fbtft-core.c +++ b/drivers/staging/fbtft/fbtft-core.c @@ -326,7 +326,7 @@ static void fbtft_deferred_io(struct fb_info *info, struct list_head *pagelist) { struct fbtft_par *par = info->par; unsigned int dirty_lines_start, dirty_lines_end; - struct page *page; + struct fb_deferred_io_pageref *pageref; unsigned long index; unsigned int y_low = 0, y_high = 0; int count = 0; @@ -340,7 +340,8 @@ static void fbtft_deferred_io(struct fb_info *info, struct list_head *pagelist) spin_unlock(&par->dirty_lock); /* Mark display lines as dirty */ - list_for_each_entry(page, pagelist, lru) { + list_for_each_entry(pageref, pagelist, list) { + struct page *page = pageref->page; count++; index = page->index << PAGE_SHIFT; y_low = index / info->fix.line_length; diff --git a/drivers/video/fbdev/broadsheetfb.c b/drivers/video/fbdev/broadsheetfb.c index 528bc0902338..6afc6ef4cb5e 100644 --- a/drivers/video/fbdev/broadsheetfb.c +++ b/drivers/video/fbdev/broadsheetfb.c @@ -934,7 +934,7 @@ static void broadsheetfb_dpy_deferred_io(struct fb_info *info, { u16 y1 = 0, h = 0; int prev_index = -1; - struct page *cur; + struct fb_deferred_io_pageref *pageref; struct fb_deferred_io *fbdefio = info->fbdefio; int h_inc; u16 yres = info->var.yres; @@ -944,7 +944,8 @@ static void broadsheetfb_dpy_deferred_io(struct fb_info *info, h_inc = DIV_ROUND_UP(PAGE_SIZE , xres); /* walk the written page list and swizzle the data */ - list_for_each_entry(cur, &fbdefio->pagelist, lru) { + list_for_each_entry(pageref, &fbdefio->pagelist, list) { + struct page *cur = pageref->page; if (prev_index < 0) { /* just starting so assign first page */ y1 = (cur->index << PAGE_SHIFT) / xres; diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c index 6924d489a289..1bdef5c241f7 100644 --- a/drivers/video/fbdev/core/fb_defio.c +++ b/drivers/video/fbdev/core/fb_defio.c @@ -36,6 +36,60 @@ static struct page *fb_deferred_io_page(struct fb_info *info, unsigned long offs return page; } +static struct fb_deferred_io_pageref *fb_deferred_io_pageref_get(struct fb_info *info, + unsigned long offset, + struct page *page) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + struct list_head *pos = &fbdefio->pagelist; + unsigned long pgoff = offset >> PAGE_SHIFT; + struct fb_deferred_io_pageref *pageref, *cur; + + if (WARN_ON_ONCE(pgoff >= info->npagerefs)) + return NULL; /* incorrect allocation size */ + + /* 1:1 mapping between pageref and page offset */ + pageref = &info->pagerefs[pgoff]; + + /* + * This check is to catch the case where a new process could start + * writing to the same page through a new PTE. This new access + * can cause a call to .page_mkwrite even if the original process' + * PTE is marked writable. + */ + if (!list_empty(&pageref->list)) + goto pageref_already_added; + + pageref->page = page; + pageref->offset = pgoff << PAGE_SHIFT; + + if (unlikely(fbdefio->sort_pagelist)) { + /* + * We loop through the list of pagerefs before adding in + * order to keep the pagerefs sorted. This has significant + * overhead of O(n^2) with n being the number of written + * pages. If possible, drivers should try to work with + * unsorted page lists instead. + */ + list_for_each_entry(cur, &info->fbdefio->pagelist, list) { + if (cur->offset > pageref->offset) + break; + } + pos = &cur->list; + } + + list_add_tail(&pageref->list, pos); + +pageref_already_added: + return pageref; +} + +static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref, + struct fb_info *info) +{ + list_del_init(&pageref->list); +} + /* this is to find and return the vmalloc-ed fb pages */ static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf) { @@ -59,7 +113,7 @@ static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf) printk(KERN_ERR "no mapping available\n"); BUG_ON(!page->mapping); - page->index = vmf->pgoff; + page->index = vmf->pgoff; /* for page_mkclean() */ vmf->page = page; return 0; @@ -95,7 +149,11 @@ static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf) struct page *page = vmf->page; struct fb_info *info = vmf->vma->vm_private_data; struct fb_deferred_io *fbdefio = info->fbdefio; - struct list_head *pos = &fbdefio->pagelist; + struct fb_deferred_io_pageref *pageref; + unsigned long offset; + vm_fault_t ret; + + offset = (vmf->address - vmf->vma->vm_start); /* this is a callback we get when userspace first tries to write to the page. we schedule a workqueue. that workqueue @@ -112,6 +170,12 @@ static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf) if (fbdefio->first_io && list_empty(&fbdefio->pagelist)) fbdefio->first_io(info); + pageref = fb_deferred_io_pageref_get(info, offset, page); + if (WARN_ON_ONCE(!pageref)) { + ret = VM_FAULT_OOM; + goto err_mutex_unlock; + } + /* * We want the page to remain locked from ->page_mkwrite until * the PTE is marked dirty to avoid page_mkclean() being called @@ -120,47 +184,17 @@ static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf) * Do this by locking the page here and informing the caller * about it with VM_FAULT_LOCKED. */ - lock_page(page); - - /* - * This check is to catch the case where a new process could start - * writing to the same page through a new PTE. This new access - * can cause a call to .page_mkwrite even if the original process' - * PTE is marked writable. - * - * TODO: The lru field is owned by the page cache; hence the name. - * We dequeue in fb_deferred_io_work() after flushing the - * page's content into video memory. Instead of lru, fbdefio - * should have it's own field. - */ - if (!list_empty(&page->lru)) - goto page_already_added; - - if (unlikely(fbdefio->sort_pagelist)) { - /* - * We loop through the pagelist before adding in order to - * keep the pagelist sorted. This has significant overhead - * of O(n^2) with n being the number of written pages. If - * possible, drivers should try to work with unsorted page - * lists instead. - */ - struct page *cur; - - list_for_each_entry(cur, &fbdefio->pagelist, lru) { - if (cur->index > page->index) - break; - } - pos = &cur->lru; - } - - list_add_tail(&page->lru, pos); + lock_page(pageref->page); -page_already_added: mutex_unlock(&fbdefio->lock); /* come back after delay to process the deferred IO */ schedule_delayed_work(&info->deferred_work, fbdefio->delay); return VM_FAULT_LOCKED; + +err_mutex_unlock: + mutex_unlock(&fbdefio->lock); + return ret; } static const struct vm_operations_struct fb_deferred_io_vm_ops = { @@ -186,15 +220,14 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_mmap); /* workqueue callback */ static void fb_deferred_io_work(struct work_struct *work) { - struct fb_info *info = container_of(work, struct fb_info, - deferred_work.work); - struct list_head *node, *next; - struct page *cur; + struct fb_info *info = container_of(work, struct fb_info, deferred_work.work); + struct fb_deferred_io_pageref *pageref, *next; struct fb_deferred_io *fbdefio = info->fbdefio; /* here we mkclean the pages, then do all deferred IO */ mutex_lock(&fbdefio->lock); - list_for_each_entry(cur, &fbdefio->pagelist, lru) { + list_for_each_entry(pageref, &fbdefio->pagelist, list) { + struct page *cur = pageref->page; lock_page(cur); page_mkclean(cur); unlock_page(cur); @@ -204,30 +237,48 @@ static void fb_deferred_io_work(struct work_struct *work) fbdefio->deferred_io(info, &fbdefio->pagelist); /* clear the list */ - list_for_each_safe(node, next, &fbdefio->pagelist) { - list_del_init(node); - } + list_for_each_entry_safe(pageref, next, &fbdefio->pagelist, list) + fb_deferred_io_pageref_put(pageref, info); + mutex_unlock(&fbdefio->lock); } -void fb_deferred_io_init(struct fb_info *info) +int fb_deferred_io_init(struct fb_info *info) { struct fb_deferred_io *fbdefio = info->fbdefio; - struct page *page; - unsigned int i; + struct fb_deferred_io_pageref *pagerefs; + unsigned long npagerefs, i; + int ret; BUG_ON(!fbdefio); + + if (WARN_ON(!info->fix.smem_len)) + return -EINVAL; + mutex_init(&fbdefio->lock); INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work); INIT_LIST_HEAD(&fbdefio->pagelist); if (fbdefio->delay == 0) /* set a default of 1 s */ fbdefio->delay = HZ; - /* initialize all the page lists one time */ - for (i = 0; i < info->fix.smem_len; i += PAGE_SIZE) { - page = fb_deferred_io_page(info, i); - INIT_LIST_HEAD(&page->lru); + npagerefs = DIV_ROUND_UP(info->fix.smem_len, PAGE_SIZE); + + /* alloc a page ref for each page of the display memory */ + pagerefs = kvcalloc(npagerefs, sizeof(*pagerefs), GFP_KERNEL); + if (!pagerefs) { + ret = -ENOMEM; + goto err; } + for (i = 0; i < npagerefs; ++i) + INIT_LIST_HEAD(&pagerefs[i].list); + info->npagerefs = npagerefs; + info->pagerefs = pagerefs; + + return 0; + +err: + mutex_destroy(&fbdefio->lock); + return ret; } EXPORT_SYMBOL_GPL(fb_deferred_io_init); @@ -254,6 +305,7 @@ void fb_deferred_io_cleanup(struct fb_info *info) page->mapping = NULL; } + kvfree(info->pagerefs); mutex_destroy(&fbdefio->lock); } EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup); diff --git a/drivers/video/fbdev/hyperv_fb.c b/drivers/video/fbdev/hyperv_fb.c index d7f6abf827b9..51eb57ea68f7 100644 --- a/drivers/video/fbdev/hyperv_fb.c +++ b/drivers/video/fbdev/hyperv_fb.c @@ -424,7 +424,7 @@ static void synthvid_deferred_io(struct fb_info *p, struct list_head *pagelist) { struct hvfb_par *par = p->par; - struct page *page; + struct fb_deferred_io_pageref *pageref; unsigned long start, end; int y1, y2, miny, maxy; @@ -437,7 +437,8 @@ static void synthvid_deferred_io(struct fb_info *p, * in synthvid_update function by clamping the y2 * value to yres. */ - list_for_each_entry(page, pagelist, lru) { + list_for_each_entry(pageref, pagelist, list) { + struct page *page = pageref->page; start = page->index << PAGE_SHIFT; end = start + PAGE_SIZE - 1; y1 = start / p->fix.line_length; diff --git a/drivers/video/fbdev/metronomefb.c b/drivers/video/fbdev/metronomefb.c index 2541f2fe065b..8352fa3f4cef 100644 --- a/drivers/video/fbdev/metronomefb.c +++ b/drivers/video/fbdev/metronomefb.c @@ -469,12 +469,13 @@ static void metronomefb_dpy_deferred_io(struct fb_info *info, struct list_head *pagelist) { u16 cksum; - struct page *cur; + struct fb_deferred_io_pageref *pageref; struct fb_deferred_io *fbdefio = info->fbdefio; struct metronomefb_par *par = info->par; /* walk the written page list and swizzle the data */ - list_for_each_entry(cur, &fbdefio->pagelist, lru) { + list_for_each_entry(pageref, &fbdefio->pagelist, list) { + struct page *cur = pageref->page; cksum = metronomefb_dpy_update_page(par, (cur->index << PAGE_SHIFT)); par->metromem_img_csum -= par->csum_table[cur->index]; diff --git a/drivers/video/fbdev/sh_mobile_lcdcfb.c b/drivers/video/fbdev/sh_mobile_lcdcfb.c index ad034eb0e7a0..aea0660ba0c3 100644 --- a/drivers/video/fbdev/sh_mobile_lcdcfb.c +++ b/drivers/video/fbdev/sh_mobile_lcdcfb.c @@ -440,13 +440,15 @@ static int sh_mobile_lcdc_sginit(struct fb_info *info, { struct sh_mobile_lcdc_chan *ch = info->par; unsigned int nr_pages_max = ch->fb_size >> PAGE_SHIFT; - struct page *page; + struct fb_deferred_io_pageref *pageref; int nr_pages = 0; sg_init_table(ch->sglist, nr_pages_max); - list_for_each_entry(page, pagelist, lru) + list_for_each_entry(pageref, pagelist, list) { + struct page *page = pageref->page; sg_set_page(&ch->sglist[nr_pages++], page, PAGE_SIZE, 0); + } return nr_pages; } diff --git a/drivers/video/fbdev/smscufx.c b/drivers/video/fbdev/smscufx.c index 9383f8d0914c..eb108f56ea04 100644 --- a/drivers/video/fbdev/smscufx.c +++ b/drivers/video/fbdev/smscufx.c @@ -958,7 +958,7 @@ static void ufx_ops_fillrect(struct fb_info *info, static void ufx_dpy_deferred_io(struct fb_info *info, struct list_head *pagelist) { - struct page *cur; + struct fb_deferred_io_pageref *pageref; struct fb_deferred_io *fbdefio = info->fbdefio; struct ufx_data *dev = info->par; @@ -969,9 +969,10 @@ static void ufx_dpy_deferred_io(struct fb_info *info, return; /* walk the written page list and render each to device */ - list_for_each_entry(cur, &fbdefio->pagelist, lru) { + list_for_each_entry(pageref, &fbdefio->pagelist, list) { /* create a rectangle of full screen width that encloses the * entire dirty framebuffer page */ + struct page *cur = pageref->page; const int x = 0; const int width = dev->info->var.xres; const int y = (cur->index << PAGE_SHIFT) / (width * 2); diff --git a/drivers/video/fbdev/udlfb.c b/drivers/video/fbdev/udlfb.c index 179b30ba09f5..f70b9cc8e855 100644 --- a/drivers/video/fbdev/udlfb.c +++ b/drivers/video/fbdev/udlfb.c @@ -784,7 +784,7 @@ static void dlfb_ops_fillrect(struct fb_info *info, static void dlfb_dpy_deferred_io(struct fb_info *info, struct list_head *pagelist) { - struct page *cur; + struct fb_deferred_io_pageref *pageref; struct fb_deferred_io *fbdefio = info->fbdefio; struct dlfb_data *dlfb = info->par; struct urb *urb; @@ -811,7 +811,8 @@ static void dlfb_dpy_deferred_io(struct fb_info *info, cmd = urb->transfer_buffer; /* walk the written page list and render each to device */ - list_for_each_entry(cur, &fbdefio->pagelist, lru) { + list_for_each_entry(pageref, &fbdefio->pagelist, list) { + struct page *cur = pageref->page; if (dlfb_render_hline(dlfb, &urb, (char *) info->fix.smem_start, &cmd, cur->index << PAGE_SHIFT, diff --git a/drivers/video/fbdev/xen-fbfront.c b/drivers/video/fbdev/xen-fbfront.c index 6c8846eba2fb..608fcde767d3 100644 --- a/drivers/video/fbdev/xen-fbfront.c +++ b/drivers/video/fbdev/xen-fbfront.c @@ -185,13 +185,14 @@ static void xenfb_deferred_io(struct fb_info *fb_info, struct list_head *pagelist) { struct xenfb_info *info = fb_info->par; - struct page *page; + struct fb_deferred_io_pageref *pageref; unsigned long beg, end; int y1, y2, miny, maxy; miny = INT_MAX; maxy = 0; - list_for_each_entry(page, pagelist, lru) { + list_for_each_entry(pageref, pagelist, list) { + struct page *page = pageref->page; beg = page->index << PAGE_SHIFT; end = beg + PAGE_SIZE - 1; y1 = beg / fb_info->fix.line_length; diff --git a/include/linux/fb.h b/include/linux/fb.h index f95da1af9ff6..a332590c0fae 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -201,6 +201,13 @@ struct fb_pixmap { }; #ifdef CONFIG_FB_DEFERRED_IO +struct fb_deferred_io_pageref { + struct page *page; + unsigned long offset; + /* private */ + struct list_head list; +}; + struct fb_deferred_io { /* delay between mkwrite and deferred handler */ unsigned long delay; @@ -468,6 +475,8 @@ struct fb_info { #endif #ifdef CONFIG_FB_DEFERRED_IO struct delayed_work deferred_work; + unsigned long npagerefs; + struct fb_deferred_io_pageref *pagerefs; struct fb_deferred_io *fbdefio; #endif @@ -661,7 +670,7 @@ static inline void __fb_pad_aligned_buffer(u8 *dst, u32 d_pitch, /* drivers/video/fb_defio.c */ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma); -extern void fb_deferred_io_init(struct fb_info *info); +extern int fb_deferred_io_init(struct fb_info *info); extern void fb_deferred_io_open(struct fb_info *info, struct inode *inode, struct file *file); -- cgit v1.2.3 From 3ed3811283ddbc959db564efce6f0988c63bc03c Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 29 Apr 2022 12:08:32 +0200 Subject: fbdev: Refactor implementation of page_mkwrite Refactor the page-write handler for deferred I/O. Drivers use the function to let fbdev track written pages of mmap'ed framebuffer memory. v3: * keep locking within track-pages function for readability (Sam) v2: * don't export the helper until we have an external caller Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20220429100834.18898-4-tzimmermann@suse.de --- drivers/video/fbdev/core/fb_defio.c | 53 ++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c index 1bdef5c241f7..3f9bb4725119 100644 --- a/drivers/video/fbdev/core/fb_defio.c +++ b/drivers/video/fbdev/core/fb_defio.c @@ -143,26 +143,17 @@ int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasy } EXPORT_SYMBOL_GPL(fb_deferred_io_fsync); -/* vm_ops->page_mkwrite handler */ -static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf) +/* + * Adds a page to the dirty list. Call this from struct + * vm_operations_struct.page_mkwrite. + */ +static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset, + struct page *page) { - struct page *page = vmf->page; - struct fb_info *info = vmf->vma->vm_private_data; struct fb_deferred_io *fbdefio = info->fbdefio; struct fb_deferred_io_pageref *pageref; - unsigned long offset; vm_fault_t ret; - offset = (vmf->address - vmf->vma->vm_start); - - /* this is a callback we get when userspace first tries to - write to the page. we schedule a workqueue. that workqueue - will eventually mkclean the touched pages and execute the - deferred framebuffer IO. then if userspace touches a page - again, we repeat the same scheme */ - - file_update_time(vmf->vma->vm_file); - /* protect against the workqueue changing the page list */ mutex_lock(&fbdefio->lock); @@ -197,6 +188,38 @@ err_mutex_unlock: return ret; } +/* + * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O + * @fb_info: The fbdev info structure + * @vmf: The VM fault + * + * This is a callback we get when userspace first tries to + * write to the page. We schedule a workqueue. That workqueue + * will eventually mkclean the touched pages and execute the + * deferred framebuffer IO. Then if userspace touches a page + * again, we repeat the same scheme. + * + * Returns: + * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise. + */ +static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf) +{ + unsigned long offset = vmf->address - vmf->vma->vm_start; + struct page *page = vmf->page; + + file_update_time(vmf->vma->vm_file); + + return fb_deferred_io_track_page(info, offset, page); +} + +/* vm_ops->page_mkwrite handler */ +static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf) +{ + struct fb_info *info = vmf->vma->vm_private_data; + + return fb_deferred_io_page_mkwrite(info, vmf); +} + static const struct vm_operations_struct fb_deferred_io_vm_ops = { .fault = fb_deferred_io_fault, .page_mkwrite = fb_deferred_io_mkwrite, -- cgit v1.2.3 From e80eec1b871a2acb8f5c92db4c237e9ae6dd322b Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 29 Apr 2022 12:08:33 +0200 Subject: fbdev: Rename pagelist to pagereflist for deferred I/O Rename various instances of pagelist to pagereflist. The list now stores pageref structures, so the new name is more appropriate. In their write-back helpers, several fbdev drivers refer to the pageref list in struct fb_deferred_io instead of using the one supplied as argument to the function. Convert them over to the supplied one. It's the same instance, so no change of behavior occurs. v4: * fix commit message (Javier) Suggested-by: Sam Ravnborg Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20220429100834.18898-5-tzimmermann@suse.de --- drivers/gpu/drm/drm_fb_helper.c | 7 +++---- drivers/gpu/drm/vmwgfx/vmwgfx_fb.c | 5 ++--- drivers/hid/hid-picolcd_fb.c | 2 +- drivers/staging/fbtft/fbtft-core.c | 10 +++++----- drivers/video/fbdev/broadsheetfb.c | 12 +++++------- drivers/video/fbdev/core/fb_defio.c | 18 +++++++++--------- drivers/video/fbdev/hecubafb.c | 3 +-- drivers/video/fbdev/hyperv_fb.c | 5 ++--- drivers/video/fbdev/metronomefb.c | 12 +++++------- drivers/video/fbdev/sh_mobile_lcdcfb.c | 16 +++++++--------- drivers/video/fbdev/smscufx.c | 8 +++----- drivers/video/fbdev/ssd1307fb.c | 3 +-- drivers/video/fbdev/udlfb.c | 8 +++----- drivers/video/fbdev/xen-fbfront.c | 5 ++--- include/drm/drm_fb_helper.h | 3 +-- include/linux/fb.h | 6 +++--- 16 files changed, 53 insertions(+), 70 deletions(-) diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index dd1d72d58b35..5ad2b6a2778c 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -708,13 +708,12 @@ static void drm_fb_helper_memory_range_to_clip(struct fb_info *info, off_t off, /** * drm_fb_helper_deferred_io() - fbdev deferred_io callback function * @info: fb_info struct pointer - * @pagelist: list of mmap framebuffer pages that have to be flushed + * @pagereflist: list of mmap framebuffer pages that have to be flushed * * This function is used as the &fb_deferred_io.deferred_io * callback function for flushing the fbdev mmap writes. */ -void drm_fb_helper_deferred_io(struct fb_info *info, - struct list_head *pagelist) +void drm_fb_helper_deferred_io(struct fb_info *info, struct list_head *pagereflist) { unsigned long start, end, min, max; struct fb_deferred_io_pageref *pageref; @@ -722,7 +721,7 @@ void drm_fb_helper_deferred_io(struct fb_info *info, min = ULONG_MAX; max = 0; - list_for_each_entry(pageref, pagelist, list) { + list_for_each_entry(pageref, pagereflist, list) { start = pageref->offset; end = start + PAGE_SIZE; min = min(min, start); diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c b/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c index db02e17e12b6..9a3dc5c4eec8 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c @@ -316,8 +316,7 @@ static int vmw_fb_pan_display(struct fb_var_screeninfo *var, return 0; } -static void vmw_deferred_io(struct fb_info *info, - struct list_head *pagelist) +static void vmw_deferred_io(struct fb_info *info, struct list_head *pagereflist) { struct vmw_fb_par *par = info->par; unsigned long start, end, min, max; @@ -327,7 +326,7 @@ static void vmw_deferred_io(struct fb_info *info, min = ULONG_MAX; max = 0; - list_for_each_entry(pageref, pagelist, list) { + list_for_each_entry(pageref, pagereflist, list) { struct page *page = pageref->page; start = page->index << PAGE_SHIFT; end = start + PAGE_SIZE - 1; diff --git a/drivers/hid/hid-picolcd_fb.c b/drivers/hid/hid-picolcd_fb.c index 78515e6bacf0..de61c08fabea 100644 --- a/drivers/hid/hid-picolcd_fb.c +++ b/drivers/hid/hid-picolcd_fb.c @@ -433,7 +433,7 @@ static const struct fb_ops picolcdfb_ops = { /* Callback from deferred IO workqueue */ -static void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagelist) +static void picolcd_fb_deferred_io(struct fb_info *info, struct list_head *pagereflist) { picolcd_fb_update(info); } diff --git a/drivers/staging/fbtft/fbtft-core.c b/drivers/staging/fbtft/fbtft-core.c index e9662822e1ef..8774bffe94cc 100644 --- a/drivers/staging/fbtft/fbtft-core.c +++ b/drivers/staging/fbtft/fbtft-core.c @@ -322,7 +322,7 @@ static void fbtft_mkdirty(struct fb_info *info, int y, int height) schedule_delayed_work(&info->deferred_work, fbdefio->delay); } -static void fbtft_deferred_io(struct fb_info *info, struct list_head *pagelist) +static void fbtft_deferred_io(struct fb_info *info, struct list_head *pagereflist) { struct fbtft_par *par = info->par; unsigned int dirty_lines_start, dirty_lines_end; @@ -340,7 +340,7 @@ static void fbtft_deferred_io(struct fb_info *info, struct list_head *pagelist) spin_unlock(&par->dirty_lock); /* Mark display lines as dirty */ - list_for_each_entry(pageref, pagelist, list) { + list_for_each_entry(pageref, pagereflist, list) { struct page *page = pageref->page; count++; index = page->index << PAGE_SHIFT; @@ -655,9 +655,9 @@ struct fb_info *fbtft_framebuffer_alloc(struct fbtft_display *display, fbops->fb_blank = fbtft_fb_blank; fbops->fb_mmap = fb_deferred_io_mmap; - fbdefio->delay = HZ / fps; - fbdefio->sort_pagelist = true; - fbdefio->deferred_io = fbtft_deferred_io; + fbdefio->delay = HZ / fps; + fbdefio->sort_pagereflist = true; + fbdefio->deferred_io = fbtft_deferred_io; fb_deferred_io_init(info); snprintf(info->fix.id, sizeof(info->fix.id), "%s", dev->driver->name); diff --git a/drivers/video/fbdev/broadsheetfb.c b/drivers/video/fbdev/broadsheetfb.c index 6afc6ef4cb5e..883a3ac03189 100644 --- a/drivers/video/fbdev/broadsheetfb.c +++ b/drivers/video/fbdev/broadsheetfb.c @@ -929,13 +929,11 @@ static void broadsheetfb_dpy_update(struct broadsheetfb_par *par) } /* this is called back from the deferred io workqueue */ -static void broadsheetfb_dpy_deferred_io(struct fb_info *info, - struct list_head *pagelist) +static void broadsheetfb_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist) { u16 y1 = 0, h = 0; int prev_index = -1; struct fb_deferred_io_pageref *pageref; - struct fb_deferred_io *fbdefio = info->fbdefio; int h_inc; u16 yres = info->var.yres; u16 xres = info->var.xres; @@ -944,7 +942,7 @@ static void broadsheetfb_dpy_deferred_io(struct fb_info *info, h_inc = DIV_ROUND_UP(PAGE_SIZE , xres); /* walk the written page list and swizzle the data */ - list_for_each_entry(pageref, &fbdefio->pagelist, list) { + list_for_each_entry(pageref, pagereflist, list) { struct page *cur = pageref->page; if (prev_index < 0) { /* just starting so assign first page */ @@ -1060,9 +1058,9 @@ static const struct fb_ops broadsheetfb_ops = { }; static struct fb_deferred_io broadsheetfb_defio = { - .delay = HZ/4, - .sort_pagelist = true, - .deferred_io = broadsheetfb_dpy_deferred_io, + .delay = HZ/4, + .sort_pagereflist = true, + .deferred_io = broadsheetfb_dpy_deferred_io, }; static int broadsheetfb_probe(struct platform_device *dev) diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c index 3f9bb4725119..c730253ab85c 100644 --- a/drivers/video/fbdev/core/fb_defio.c +++ b/drivers/video/fbdev/core/fb_defio.c @@ -41,7 +41,7 @@ static struct fb_deferred_io_pageref *fb_deferred_io_pageref_get(struct fb_info struct page *page) { struct fb_deferred_io *fbdefio = info->fbdefio; - struct list_head *pos = &fbdefio->pagelist; + struct list_head *pos = &fbdefio->pagereflist; unsigned long pgoff = offset >> PAGE_SHIFT; struct fb_deferred_io_pageref *pageref, *cur; @@ -63,7 +63,7 @@ static struct fb_deferred_io_pageref *fb_deferred_io_pageref_get(struct fb_info pageref->page = page; pageref->offset = pgoff << PAGE_SHIFT; - if (unlikely(fbdefio->sort_pagelist)) { + if (unlikely(fbdefio->sort_pagereflist)) { /* * We loop through the list of pagerefs before adding in * order to keep the pagerefs sorted. This has significant @@ -71,7 +71,7 @@ static struct fb_deferred_io_pageref *fb_deferred_io_pageref_get(struct fb_info * pages. If possible, drivers should try to work with * unsorted page lists instead. */ - list_for_each_entry(cur, &info->fbdefio->pagelist, list) { + list_for_each_entry(cur, &fbdefio->pagereflist, list) { if (cur->offset > pageref->offset) break; } @@ -158,7 +158,7 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long mutex_lock(&fbdefio->lock); /* first write in this cycle, notify the driver */ - if (fbdefio->first_io && list_empty(&fbdefio->pagelist)) + if (fbdefio->first_io && list_empty(&fbdefio->pagereflist)) fbdefio->first_io(info); pageref = fb_deferred_io_pageref_get(info, offset, page); @@ -249,18 +249,18 @@ static void fb_deferred_io_work(struct work_struct *work) /* here we mkclean the pages, then do all deferred IO */ mutex_lock(&fbdefio->lock); - list_for_each_entry(pageref, &fbdefio->pagelist, list) { + list_for_each_entry(pageref, &fbdefio->pagereflist, list) { struct page *cur = pageref->page; lock_page(cur); page_mkclean(cur); unlock_page(cur); } - /* driver's callback with pagelist */ - fbdefio->deferred_io(info, &fbdefio->pagelist); + /* driver's callback with pagereflist */ + fbdefio->deferred_io(info, &fbdefio->pagereflist); /* clear the list */ - list_for_each_entry_safe(pageref, next, &fbdefio->pagelist, list) + list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list) fb_deferred_io_pageref_put(pageref, info); mutex_unlock(&fbdefio->lock); @@ -280,7 +280,7 @@ int fb_deferred_io_init(struct fb_info *info) mutex_init(&fbdefio->lock); INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work); - INIT_LIST_HEAD(&fbdefio->pagelist); + INIT_LIST_HEAD(&fbdefio->pagereflist); if (fbdefio->delay == 0) /* set a default of 1 s */ fbdefio->delay = HZ; diff --git a/drivers/video/fbdev/hecubafb.c b/drivers/video/fbdev/hecubafb.c index 2c483e2cf9ec..eb1eaadc1bbb 100644 --- a/drivers/video/fbdev/hecubafb.c +++ b/drivers/video/fbdev/hecubafb.c @@ -115,8 +115,7 @@ static void hecubafb_dpy_update(struct hecubafb_par *par) } /* this is called back from the deferred io workqueue */ -static void hecubafb_dpy_deferred_io(struct fb_info *info, - struct list_head *pagelist) +static void hecubafb_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist) { hecubafb_dpy_update(info->par); } diff --git a/drivers/video/fbdev/hyperv_fb.c b/drivers/video/fbdev/hyperv_fb.c index 51eb57ea68f7..550cf0990070 100644 --- a/drivers/video/fbdev/hyperv_fb.c +++ b/drivers/video/fbdev/hyperv_fb.c @@ -420,8 +420,7 @@ static void hvfb_docopy(struct hvfb_par *par, } /* Deferred IO callback */ -static void synthvid_deferred_io(struct fb_info *p, - struct list_head *pagelist) +static void synthvid_deferred_io(struct fb_info *p, struct list_head *pagereflist) { struct hvfb_par *par = p->par; struct fb_deferred_io_pageref *pageref; @@ -437,7 +436,7 @@ static void synthvid_deferred_io(struct fb_info *p, * in synthvid_update function by clamping the y2 * value to yres. */ - list_for_each_entry(pageref, pagelist, list) { + list_for_each_entry(pageref, pagereflist, list) { struct page *page = pageref->page; start = page->index << PAGE_SHIFT; end = start + PAGE_SIZE - 1; diff --git a/drivers/video/fbdev/metronomefb.c b/drivers/video/fbdev/metronomefb.c index 8352fa3f4cef..f581c73d39df 100644 --- a/drivers/video/fbdev/metronomefb.c +++ b/drivers/video/fbdev/metronomefb.c @@ -465,16 +465,14 @@ static u16 metronomefb_dpy_update_page(struct metronomefb_par *par, int index) } /* this is called back from the deferred io workqueue */ -static void metronomefb_dpy_deferred_io(struct fb_info *info, - struct list_head *pagelist) +static void metronomefb_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist) { u16 cksum; struct fb_deferred_io_pageref *pageref; - struct fb_deferred_io *fbdefio = info->fbdefio; struct metronomefb_par *par = info->par; /* walk the written page list and swizzle the data */ - list_for_each_entry(pageref, &fbdefio->pagelist, list) { + list_for_each_entry(pageref, pagereflist, list) { struct page *cur = pageref->page; cksum = metronomefb_dpy_update_page(par, (cur->index << PAGE_SHIFT)); @@ -569,9 +567,9 @@ static const struct fb_ops metronomefb_ops = { }; static struct fb_deferred_io metronomefb_defio = { - .delay = HZ, - .sort_pagelist = true, - .deferred_io = metronomefb_dpy_deferred_io, + .delay = HZ, + .sort_pagereflist = true, + .deferred_io = metronomefb_dpy_deferred_io, }; static int metronomefb_probe(struct platform_device *dev) diff --git a/drivers/video/fbdev/sh_mobile_lcdcfb.c b/drivers/video/fbdev/sh_mobile_lcdcfb.c index aea0660ba0c3..2b060eca4360 100644 --- a/drivers/video/fbdev/sh_mobile_lcdcfb.c +++ b/drivers/video/fbdev/sh_mobile_lcdcfb.c @@ -435,8 +435,7 @@ static struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = { .read_data = lcdc_sys_read_data, }; -static int sh_mobile_lcdc_sginit(struct fb_info *info, - struct list_head *pagelist) +static int sh_mobile_lcdc_sginit(struct fb_info *info, struct list_head *pagereflist) { struct sh_mobile_lcdc_chan *ch = info->par; unsigned int nr_pages_max = ch->fb_size >> PAGE_SHIFT; @@ -445,7 +444,7 @@ static int sh_mobile_lcdc_sginit(struct fb_info *info, sg_init_table(ch->sglist, nr_pages_max); - list_for_each_entry(pageref, pagelist, list) { + list_for_each_entry(pageref, pagereflist, list) { struct page *page = pageref->page; sg_set_page(&ch->sglist[nr_pages++], page, PAGE_SIZE, 0); } @@ -453,8 +452,7 @@ static int sh_mobile_lcdc_sginit(struct fb_info *info, return nr_pages; } -static void sh_mobile_lcdc_deferred_io(struct fb_info *info, - struct list_head *pagelist) +static void sh_mobile_lcdc_deferred_io(struct fb_info *info, struct list_head *pagereflist) { struct sh_mobile_lcdc_chan *ch = info->par; const struct sh_mobile_lcdc_panel_cfg *panel = &ch->cfg->panel_cfg; @@ -463,7 +461,7 @@ static void sh_mobile_lcdc_deferred_io(struct fb_info *info, sh_mobile_lcdc_clk_on(ch->lcdc); /* - * It's possible to get here without anything on the pagelist via + * It's possible to get here without anything on the pagereflist via * sh_mobile_lcdc_deferred_io_touch() or via a userspace fsync() * invocation. In the former case, the acceleration routines are * stepped in to when using the framebuffer console causing the @@ -473,12 +471,12 @@ static void sh_mobile_lcdc_deferred_io(struct fb_info *info, * acceleration routines have their own methods for writing in * that still need to be updated. * - * The fsync() and empty pagelist case could be optimized for, + * The fsync() and empty pagereflist case could be optimized for, * but we don't bother, as any application exhibiting such * behaviour is fundamentally broken anyways. */ - if (!list_empty(pagelist)) { - unsigned int nr_pages = sh_mobile_lcdc_sginit(info, pagelist); + if (!list_empty(pagereflist)) { + unsigned int nr_pages = sh_mobile_lcdc_sginit(info, pagereflist); /* trigger panel update */ dma_map_sg(ch->lcdc->dev, ch->sglist, nr_pages, DMA_TO_DEVICE); diff --git a/drivers/video/fbdev/smscufx.c b/drivers/video/fbdev/smscufx.c index eb108f56ea04..7f00605bc0d1 100644 --- a/drivers/video/fbdev/smscufx.c +++ b/drivers/video/fbdev/smscufx.c @@ -955,12 +955,10 @@ static void ufx_ops_fillrect(struct fb_info *info, * Touching ANY framebuffer memory that triggers a page fault * in fb_defio will cause a deadlock, when it also tries to * grab the same mutex. */ -static void ufx_dpy_deferred_io(struct fb_info *info, - struct list_head *pagelist) +static void ufx_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist) { - struct fb_deferred_io_pageref *pageref; - struct fb_deferred_io *fbdefio = info->fbdefio; struct ufx_data *dev = info->par; + struct fb_deferred_io_pageref *pageref; if (!fb_defio) return; @@ -969,7 +967,7 @@ static void ufx_dpy_deferred_io(struct fb_info *info, return; /* walk the written page list and render each to device */ - list_for_each_entry(pageref, &fbdefio->pagelist, list) { + list_for_each_entry(pageref, pagereflist, list) { /* create a rectangle of full screen width that encloses the * entire dirty framebuffer page */ struct page *cur = pageref->page; diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 7547b4628afc..5c765655d000 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -371,8 +371,7 @@ static const struct fb_ops ssd1307fb_ops = { .fb_mmap = fb_deferred_io_mmap, }; -static void ssd1307fb_deferred_io(struct fb_info *info, - struct list_head *pagelist) +static void ssd1307fb_deferred_io(struct fb_info *info, struct list_head *pagereflist) { ssd1307fb_update_display(info->par); } diff --git a/drivers/video/fbdev/udlfb.c b/drivers/video/fbdev/udlfb.c index f70b9cc8e855..66eefd317431 100644 --- a/drivers/video/fbdev/udlfb.c +++ b/drivers/video/fbdev/udlfb.c @@ -781,11 +781,9 @@ static void dlfb_ops_fillrect(struct fb_info *info, * in fb_defio will cause a deadlock, when it also tries to * grab the same mutex. */ -static void dlfb_dpy_deferred_io(struct fb_info *info, - struct list_head *pagelist) +static void dlfb_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist) { struct fb_deferred_io_pageref *pageref; - struct fb_deferred_io *fbdefio = info->fbdefio; struct dlfb_data *dlfb = info->par; struct urb *urb; char *cmd; @@ -811,7 +809,7 @@ static void dlfb_dpy_deferred_io(struct fb_info *info, cmd = urb->transfer_buffer; /* walk the written page list and render each to device */ - list_for_each_entry(pageref, &fbdefio->pagelist, list) { + list_for_each_entry(pageref, pagereflist, list) { struct page *cur = pageref->page; if (dlfb_render_hline(dlfb, &urb, (char *) info->fix.smem_start, @@ -984,7 +982,7 @@ static int dlfb_ops_open(struct fb_info *info, int user) if (fbdefio) { fbdefio->delay = DL_DEFIO_WRITE_DELAY; - fbdefio->sort_pagelist = true; + fbdefio->sort_pagereflist = true; fbdefio->deferred_io = dlfb_dpy_deferred_io; } diff --git a/drivers/video/fbdev/xen-fbfront.c b/drivers/video/fbdev/xen-fbfront.c index 608fcde767d3..bc8244b9ce03 100644 --- a/drivers/video/fbdev/xen-fbfront.c +++ b/drivers/video/fbdev/xen-fbfront.c @@ -181,8 +181,7 @@ static void xenfb_refresh(struct xenfb_info *info, xenfb_do_update(info, x1, y1, x2 - x1 + 1, y2 - y1 + 1); } -static void xenfb_deferred_io(struct fb_info *fb_info, - struct list_head *pagelist) +static void xenfb_deferred_io(struct fb_info *fb_info, struct list_head *pagereflist) { struct xenfb_info *info = fb_info->par; struct fb_deferred_io_pageref *pageref; @@ -191,7 +190,7 @@ static void xenfb_deferred_io(struct fb_info *fb_info, miny = INT_MAX; maxy = 0; - list_for_each_entry(pageref, pagelist, list) { + list_for_each_entry(pageref, pagereflist, list) { struct page *page = pageref->page; beg = page->index << PAGE_SHIFT; end = beg + PAGE_SIZE - 1; diff --git a/include/drm/drm_fb_helper.h b/include/drm/drm_fb_helper.h index 3af4624368d8..329607ca65c0 100644 --- a/include/drm/drm_fb_helper.h +++ b/include/drm/drm_fb_helper.h @@ -229,8 +229,7 @@ void drm_fb_helper_fill_info(struct fb_info *info, struct drm_fb_helper *fb_helper, struct drm_fb_helper_surface_size *sizes); -void drm_fb_helper_deferred_io(struct fb_info *info, - struct list_head *pagelist); +void drm_fb_helper_deferred_io(struct fb_info *info, struct list_head *pagereflist); ssize_t drm_fb_helper_sys_read(struct fb_info *info, char __user *buf, size_t count, loff_t *ppos); diff --git a/include/linux/fb.h b/include/linux/fb.h index a332590c0fae..69c67c70fa78 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -211,9 +211,9 @@ struct fb_deferred_io_pageref { struct fb_deferred_io { /* delay between mkwrite and deferred handler */ unsigned long delay; - bool sort_pagelist; /* sort pagelist by offset */ - struct mutex lock; /* mutex that protects the page list */ - struct list_head pagelist; /* list of touched pages */ + bool sort_pagereflist; /* sort pagelist by offset */ + struct mutex lock; /* mutex that protects the pageref list */ + struct list_head pagereflist; /* list of pagerefs for touched pages */ /* callback */ void (*first_io)(struct fb_info *info); void (*deferred_io)(struct fb_info *info, struct list_head *pagelist); -- cgit v1.2.3 From e2d8b4289c937447ab710052f15a18f686db73dc Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 29 Apr 2022 12:08:34 +0200 Subject: fbdev: Use pageref offset for deferred-I/O writeback Use pageref->offset instead of page->index for deferred-I/O writeback where appropriate. Distinguishes between file-mapping offset and video- memory offset. While at it, also remove unnecessary references to struct page. Fbdev's deferred-I/O code uses the two related page->index and pageref->offset. The former is the page offset in the mapped file, the latter is the byte offset in the video memory (or fbdev screen buffer). It's the same value for fbdev drivers, but for DRM the values can be different. Because GEM buffer objects are mapped at an offset in the DRM device file, page->index has this offset added to it as well. We currently don't hit this case in DRM, because all affected mappings of GEM memory are performed with an internal, intermediate shadow buffer. The value of page->index is required by page_mkclean(), which we call to reset the mappings during the writeback phase of the deferred I/O. The value of pageref->offset is for conveniently getting an offset into video memory in fb helpers. v4: * fix commit message (Javier) Suggested-by: Javier Martinez Canillas Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20220429100834.18898-6-tzimmermann@suse.de --- drivers/gpu/drm/vmwgfx/vmwgfx_fb.c | 3 +-- drivers/staging/fbtft/fbtft-core.c | 9 +++------ drivers/video/fbdev/broadsheetfb.c | 13 ++++++------- drivers/video/fbdev/hyperv_fb.c | 3 +-- drivers/video/fbdev/metronomefb.c | 9 ++++----- drivers/video/fbdev/sh_mobile_lcdcfb.c | 3 +-- drivers/video/fbdev/smscufx.c | 3 +-- drivers/video/fbdev/udlfb.c | 6 ++---- drivers/video/fbdev/xen-fbfront.c | 3 +-- 9 files changed, 20 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c b/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c index 9a3dc5c4eec8..0ba9739f406d 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_fb.c @@ -327,8 +327,7 @@ static void vmw_deferred_io(struct fb_info *info, struct list_head *pagereflist) min = ULONG_MAX; max = 0; list_for_each_entry(pageref, pagereflist, list) { - struct page *page = pageref->page; - start = page->index << PAGE_SHIFT; + start = pageref->offset; end = start + PAGE_SIZE - 1; min = min(min, start); max = max(max, end); diff --git a/drivers/staging/fbtft/fbtft-core.c b/drivers/staging/fbtft/fbtft-core.c index 8774bffe94cc..60b2278d8b16 100644 --- a/drivers/staging/fbtft/fbtft-core.c +++ b/drivers/staging/fbtft/fbtft-core.c @@ -327,7 +327,6 @@ static void fbtft_deferred_io(struct fb_info *info, struct list_head *pagereflis struct fbtft_par *par = info->par; unsigned int dirty_lines_start, dirty_lines_end; struct fb_deferred_io_pageref *pageref; - unsigned long index; unsigned int y_low = 0, y_high = 0; int count = 0; @@ -341,14 +340,12 @@ static void fbtft_deferred_io(struct fb_info *info, struct list_head *pagereflis /* Mark display lines as dirty */ list_for_each_entry(pageref, pagereflist, list) { - struct page *page = pageref->page; count++; - index = page->index << PAGE_SHIFT; - y_low = index / info->fix.line_length; - y_high = (index + PAGE_SIZE - 1) / info->fix.line_length; + y_low = pageref->offset / info->fix.line_length; + y_high = (pageref->offset + PAGE_SIZE - 1) / info->fix.line_length; dev_dbg(info->device, "page->index=%lu y_low=%d y_high=%d\n", - page->index, y_low, y_high); + pageref->page->index, y_low, y_high); if (y_high > info->var.yres - 1) y_high = info->var.yres - 1; if (y_low < dirty_lines_start) diff --git a/drivers/video/fbdev/broadsheetfb.c b/drivers/video/fbdev/broadsheetfb.c index 883a3ac03189..55e62dd96f9b 100644 --- a/drivers/video/fbdev/broadsheetfb.c +++ b/drivers/video/fbdev/broadsheetfb.c @@ -932,7 +932,7 @@ static void broadsheetfb_dpy_update(struct broadsheetfb_par *par) static void broadsheetfb_dpy_deferred_io(struct fb_info *info, struct list_head *pagereflist) { u16 y1 = 0, h = 0; - int prev_index = -1; + unsigned long prev_offset = ULONG_MAX; struct fb_deferred_io_pageref *pageref; int h_inc; u16 yres = info->var.yres; @@ -943,22 +943,21 @@ static void broadsheetfb_dpy_deferred_io(struct fb_info *info, struct list_head /* walk the written page list and swizzle the data */ list_for_each_entry(pageref, pagereflist, list) { - struct page *cur = pageref->page; - if (prev_index < 0) { + if (prev_offset == ULONG_MAX) { /* just starting so assign first page */ - y1 = (cur->index << PAGE_SHIFT) / xres; + y1 = pageref->offset / xres; h = h_inc; - } else if ((prev_index + 1) == cur->index) { + } else if ((prev_offset + PAGE_SIZE) == pageref->offset) { /* this page is consecutive so increase our height */ h += h_inc; } else { /* page not consecutive, issue previous update first */ broadsheetfb_dpy_update_pages(info->par, y1, y1 + h); /* start over with our non consecutive page */ - y1 = (cur->index << PAGE_SHIFT) / xres; + y1 = pageref->offset / xres; h = h_inc; } - prev_index = cur->index; + prev_offset = pageref->offset; } /* if we still have any pages to update we do so now */ diff --git a/drivers/video/fbdev/hyperv_fb.c b/drivers/video/fbdev/hyperv_fb.c index 550cf0990070..8359a513b600 100644 --- a/drivers/video/fbdev/hyperv_fb.c +++ b/drivers/video/fbdev/hyperv_fb.c @@ -437,8 +437,7 @@ static void synthvid_deferred_io(struct fb_info *p, struct list_head *pagereflis * value to yres. */ list_for_each_entry(pageref, pagereflist, list) { - struct page *page = pageref->page; - start = page->index << PAGE_SHIFT; + start = pageref->offset; end = start + PAGE_SIZE - 1; y1 = start / p->fix.line_length; y2 = end / p->fix.line_length; diff --git a/drivers/video/fbdev/metronomefb.c b/drivers/video/fbdev/metronomefb.c index f581c73d39df..9fd4bb85d735 100644 --- a/drivers/video/fbdev/metronomefb.c +++ b/drivers/video/fbdev/metronomefb.c @@ -473,11 +473,10 @@ static void metronomefb_dpy_deferred_io(struct fb_info *info, struct list_head * /* walk the written page list and swizzle the data */ list_for_each_entry(pageref, pagereflist, list) { - struct page *cur = pageref->page; - cksum = metronomefb_dpy_update_page(par, - (cur->index << PAGE_SHIFT)); - par->metromem_img_csum -= par->csum_table[cur->index]; - par->csum_table[cur->index] = cksum; + unsigned long pgoffset = pageref->offset >> PAGE_SHIFT; + cksum = metronomefb_dpy_update_page(par, pageref->offset); + par->metromem_img_csum -= par->csum_table[pgoffset]; + par->csum_table[pgoffset] = cksum; par->metromem_img_csum += cksum; } diff --git a/drivers/video/fbdev/sh_mobile_lcdcfb.c b/drivers/video/fbdev/sh_mobile_lcdcfb.c index 2b060eca4360..6d00893d41f4 100644 --- a/drivers/video/fbdev/sh_mobile_lcdcfb.c +++ b/drivers/video/fbdev/sh_mobile_lcdcfb.c @@ -445,8 +445,7 @@ static int sh_mobile_lcdc_sginit(struct fb_info *info, struct list_head *pageref sg_init_table(ch->sglist, nr_pages_max); list_for_each_entry(pageref, pagereflist, list) { - struct page *page = pageref->page; - sg_set_page(&ch->sglist[nr_pages++], page, PAGE_SIZE, 0); + sg_set_page(&ch->sglist[nr_pages++], pageref->page, PAGE_SIZE, 0); } return nr_pages; diff --git a/drivers/video/fbdev/smscufx.c b/drivers/video/fbdev/smscufx.c index 7f00605bc0d1..d7aa5511c361 100644 --- a/drivers/video/fbdev/smscufx.c +++ b/drivers/video/fbdev/smscufx.c @@ -970,10 +970,9 @@ static void ufx_dpy_deferred_io(struct fb_info *info, struct list_head *pagerefl list_for_each_entry(pageref, pagereflist, list) { /* create a rectangle of full screen width that encloses the * entire dirty framebuffer page */ - struct page *cur = pageref->page; const int x = 0; const int width = dev->info->var.xres; - const int y = (cur->index << PAGE_SHIFT) / (width * 2); + const int y = pageref->offset / (width * 2); int height = (PAGE_SIZE / (width * 2)) + 1; height = min(height, (int)(dev->info->var.yres - y)); diff --git a/drivers/video/fbdev/udlfb.c b/drivers/video/fbdev/udlfb.c index 66eefd317431..c863244ef12c 100644 --- a/drivers/video/fbdev/udlfb.c +++ b/drivers/video/fbdev/udlfb.c @@ -810,11 +810,9 @@ static void dlfb_dpy_deferred_io(struct fb_info *info, struct list_head *pageref /* walk the written page list and render each to device */ list_for_each_entry(pageref, pagereflist, list) { - struct page *cur = pageref->page; - if (dlfb_render_hline(dlfb, &urb, (char *) info->fix.smem_start, - &cmd, cur->index << PAGE_SHIFT, - PAGE_SIZE, &bytes_identical, &bytes_sent)) + &cmd, pageref->offset, PAGE_SIZE, + &bytes_identical, &bytes_sent)) goto error; bytes_rendered += PAGE_SIZE; } diff --git a/drivers/video/fbdev/xen-fbfront.c b/drivers/video/fbdev/xen-fbfront.c index bc8244b9ce03..3bed357a9870 100644 --- a/drivers/video/fbdev/xen-fbfront.c +++ b/drivers/video/fbdev/xen-fbfront.c @@ -191,8 +191,7 @@ static void xenfb_deferred_io(struct fb_info *fb_info, struct list_head *pageref miny = INT_MAX; maxy = 0; list_for_each_entry(pageref, pagereflist, list) { - struct page *page = pageref->page; - beg = page->index << PAGE_SHIFT; + beg = pageref->offset; end = beg + PAGE_SIZE - 1; y1 = beg / fb_info->fix.line_length; y2 = end / fb_info->fix.line_length; -- cgit v1.2.3 From d7442505de9259f8b975232470378d399c25b2fa Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 3 Mar 2022 21:58:31 +0100 Subject: drm/simpledrm: Use fbdev defaults for shadow buffering Don't select shadow buffering for the fbdev console explicitly. The fbdev emulation's heuristic will enable it for any framebuffer with .dirty callback. Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20220303205839.28484-2-tzimmermann@suse.de --- drivers/gpu/drm/tiny/simpledrm.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/tiny/simpledrm.c b/drivers/gpu/drm/tiny/simpledrm.c index f5b8e864a5cd..768242a78e2b 100644 --- a/drivers/gpu/drm/tiny/simpledrm.c +++ b/drivers/gpu/drm/tiny/simpledrm.c @@ -801,7 +801,6 @@ static int simpledrm_device_init_modeset(struct simpledrm_device *sdev) dev->mode_config.max_width = max_width; dev->mode_config.min_height = mode->vdisplay; dev->mode_config.max_height = max_height; - dev->mode_config.prefer_shadow_fbdev = true; dev->mode_config.preferred_depth = sdev->format->cpp[0] * 8; dev->mode_config.funcs = &simpledrm_mode_config_funcs; -- cgit v1.2.3 From f778f405faa2cda2342ac290b24c2c17c25d7c16 Mon Sep 17 00:00:00 2001 From: Christian König Date: Fri, 22 Apr 2022 14:38:47 +0200 Subject: dma-buf/sync_file: cleanup fence merging a bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit krealloc_array() ignores attempts to reduce the array size, so the attempt to save memory is completely pointless here. Also move testing for the no fence case into sync_file_set_fence(), this way we don't even touch the fence array when we don't have any fences. Signed-off-by: Christian König Reviewed-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220426124637.329764-1-christian.koenig@amd.com --- drivers/dma-buf/sync_file.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/drivers/dma-buf/sync_file.c b/drivers/dma-buf/sync_file.c index 514d213261df..0fe564539166 100644 --- a/drivers/dma-buf/sync_file.c +++ b/drivers/dma-buf/sync_file.c @@ -157,9 +157,15 @@ static int sync_file_set_fence(struct sync_file *sync_file, * we already own a new reference to the fence. For num_fence > 1 * we own the reference of the dma_fence_array creation. */ - if (num_fences == 1) { + + if (num_fences == 0) { + sync_file->fence = dma_fence_get_stub(); + kfree(fences); + + } else if (num_fences == 1) { sync_file->fence = fences[0]; kfree(fences); + } else { array = dma_fence_array_create(num_fences, fences, dma_fence_context_alloc(1), @@ -261,19 +267,6 @@ static struct sync_file *sync_file_merge(const char *name, struct sync_file *a, } } - if (index == 0) - fences[index++] = dma_fence_get_stub(); - - if (num_fences > index) { - struct dma_fence **tmp; - - /* Keep going even when reducing the size failed */ - tmp = krealloc_array(fences, index, sizeof(*fences), - GFP_KERNEL); - if (tmp) - fences = tmp; - } - if (sync_file_set_fence(sync_file, fences, index) < 0) goto err_put_fences; -- cgit v1.2.3 From 74015e2650bad641a02100cdf0ac23a96c1a2553 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 22 Apr 2022 09:28:40 +0200 Subject: dt-bindings: display: rockchip: Add binding for VOP2 The VOP2 is found on newer Rockchip SoCs like the rk3568 or the rk3566. The binding differs slightly from the existing VOP binding, so add a new binding file for it. Signed-off-by: Sascha Hauer Reviewed-by: Rob Herring Tested-by: Michael Riesch Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220422072841.2206452-24-s.hauer@pengutronix.de --- .../bindings/display/rockchip/rockchip-vop2.yaml | 140 +++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml new file mode 100644 index 000000000000..655d9b327f7d --- /dev/null +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip-vop2.yaml @@ -0,0 +1,140 @@ +# SPDX-License-Identifier: GPL-2.0-only or BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/rockchip/rockchip-vop2.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip SoC display controller (VOP2) + +description: + VOP2 (Video Output Processor v2) is the display controller for the Rockchip + series of SoCs which transfers the image data from a video memory + buffer to an external LCD interface. + +maintainers: + - Sandy Huang + - Heiko Stuebner + +properties: + compatible: + enum: + - rockchip,rk3566-vop + - rockchip,rk3568-vop + + reg: + minItems: 1 + items: + - description: + Must contain one entry corresponding to the base address and length + of the register space. + - description: + Can optionally contain a second entry corresponding to + the CRTC gamma LUT address. + + interrupts: + maxItems: 1 + description: + The VOP interrupt is shared by several interrupt sources, such as + frame start (VSYNC), line flag and other status interrupts. + + clocks: + items: + - description: Clock for ddr buffer transfer. + - description: Clock for the ahb bus to R/W the phy regs. + - description: Pixel clock for video port 0. + - description: Pixel clock for video port 1. + - description: Pixel clock for video port 2. + + clock-names: + items: + - const: aclk + - const: hclk + - const: dclk_vp0 + - const: dclk_vp1 + - const: dclk_vp2 + + rockchip,grf: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Phandle to GRF regs used for misc control + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: + Output endpoint of VP0 + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: + Output endpoint of VP1 + + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: + Output endpoint of VP2 + + iommus: + maxItems: 1 + + power-domains: + maxItems: 1 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - ports + +additionalProperties: false + +examples: + - | + #include + #include + #include + bus { + #address-cells = <2>; + #size-cells = <2>; + vop: vop@fe040000 { + compatible = "rockchip,rk3568-vop"; + reg = <0x0 0xfe040000 0x0 0x3000>, <0x0 0xfe044000 0x0 0x1000>; + interrupts = ; + clocks = <&cru ACLK_VOP>, + <&cru HCLK_VOP>, + <&cru DCLK_VOP0>, + <&cru DCLK_VOP1>, + <&cru DCLK_VOP2>; + clock-names = "aclk", + "hclk", + "dclk_vp0", + "dclk_vp1", + "dclk_vp2"; + power-domains = <&power RK3568_PD_VO>; + iommus = <&vop_mmu>; + vop_out: ports { + #address-cells = <1>; + #size-cells = <0>; + vp0: port@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + }; + vp1: port@1 { + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + }; + vp2: port@2 { + reg = <2>; + #address-cells = <1>; + #size-cells = <0>; + }; + }; + }; + }; -- cgit v1.2.3 From 4073e42028de0f6ebbd86a9b30aaa5501bcdff59 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 22 Apr 2022 09:28:41 +0200 Subject: dt-bindings: display: rockchip: dw-hdmi: fix ports description Current port description doesn't cover all possible cases. It currently expects one single port with two endpoints. When the HDMI connector is described in the device tree there can be two ports, first one going to the VOP and the second one going to the connector. Also on SoCs which only have a single VOP there will be only one endpoint instead of two. This patch addresses both issues. With this there can either be a single port ("port") , or two of them ("port@0", "port@1") when the connector is also in the device tree. Also the first or only port can either have one endpoint ("endpoint") for single VOP SoCs or two ("endpoint@0", "endpoint@1") for dual VOP SoCs. Signed-off-by: Sascha Hauer Reviewed-by: Rob Herring Tested-by: Michael Riesch Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220422072841.2206452-25-s.hauer@pengutronix.de --- .../display/rockchip/rockchip,dw-hdmi.yaml | 24 ++++++++-------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml index d7cb2b2be60e..7e59dee15a5f 100644 --- a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-hdmi.yaml @@ -97,27 +97,21 @@ properties: ports: $ref: /schemas/graph.yaml#/properties/ports - properties: - port: - $ref: /schemas/graph.yaml#/$defs/port-base - unevaluatedProperties: false + patternProperties: + "^port(@0)?$": + $ref: /schemas/graph.yaml#/properties/port description: Input of the DWC HDMI TX - properties: + endpoint: + description: Connection to the VOP endpoint@0: - $ref: /schemas/graph.yaml#/properties/endpoint description: Connection to the VOPB - endpoint@1: - $ref: /schemas/graph.yaml#/properties/endpoint description: Connection to the VOPL - - required: - - endpoint@0 - - endpoint@1 - - required: - - port + properties: + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: Output of the DWC HDMI TX rockchip,grf: $ref: /schemas/types.yaml#/definitions/phandle -- cgit v1.2.3 From b382406a2cf4afaa7320a7ad4b298ed6e2675437 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 22 Apr 2022 09:28:38 +0200 Subject: drm/rockchip: Make VOP driver optional With upcoming VOP2 support VOP won't be the only choice anymore, so make the VOP driver optional. This also adds a dependency from ROCKCHIP_ANALOGIX_DP to ROCKCHIP_VOP, because that driver currently only links and works with the VOP driver. Signed-off-by: Sascha Hauer Tested-by: Michael Riesch Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220422072841.2206452-22-s.hauer@pengutronix.de --- drivers/gpu/drm/rockchip/Kconfig | 8 ++++++++ drivers/gpu/drm/rockchip/Makefile | 3 ++- drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index fb81040a733d..4233b4ce65d3 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -22,8 +22,16 @@ config DRM_ROCKCHIP if DRM_ROCKCHIP +config ROCKCHIP_VOP + bool "Rockchip VOP driver" + default y + help + This selects support for the VOP driver. You should enable it + on older SoCs. + config ROCKCHIP_ANALOGIX_DP bool "Rockchip specific extensions for Analogix DP driver" + depends on ROCKCHIP_VOP select DRM_DISPLAY_DP_HELPER help This selects support for Rockchip SoC specific extensions diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index 1a56f696558c..dfc5512fdb9f 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -4,8 +4,9 @@ # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \ - rockchip_drm_gem.o rockchip_drm_vop.o rockchip_vop_reg.o + rockchip_drm_gem.o +rockchipdrm-$(CONFIG_ROCKCHIP_VOP) += rockchip_drm_vop.o rockchip_vop_reg.o rockchipdrm-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index 0dc09d92d92d..6ab046081946 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -481,7 +481,7 @@ static int __init rockchip_drm_init(void) return -ENODEV; num_rockchip_sub_drivers = 0; - ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_DRM_ROCKCHIP); + ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_ROCKCHIP_VOP); ADD_ROCKCHIP_SUB_DRIVER(rockchip_lvds_driver, CONFIG_ROCKCHIP_LVDS); ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver, -- cgit v1.2.3 From 604be85547ce4d61b89292d2f9a78c721b778c16 Mon Sep 17 00:00:00 2001 From: Andy Yan Date: Fri, 22 Apr 2022 09:28:39 +0200 Subject: drm/rockchip: Add VOP2 driver The VOP2 unit is found on Rockchip SoCs beginning with rk3566/rk3568. It replaces the VOP unit found in the older Rockchip SoCs. This driver has been derived from the downstream Rockchip Kernel and heavily modified: - All nonstandard DRM properties have been removed - dropped struct vop2_plane_state and pass around less data between functions - Dropped all DRM_FORMAT_* not known on upstream - rework register access to get rid of excessively used macros - Drop all waiting for framesyncs The driver is tested with HDMI and MIPI-DSI display on a RK3568-EVB board. Overlay support is tested with the modetest utility. AFBC support on the cluster windows is tested with weston-simple-dmabuf-egl on weston using the (yet to be upstreamed) panfrost driver support. Signed-off-by: Andy Yan Co-Developed-by: Sascha Hauer Signed-off-by: Sascha Hauer Tested-by: Michael Riesch [dt-binding-header:] Acked-by: Rob Herring [moved dt-binding header from dt-nodes patch to here and made checkpatch --strict happier] Signed-off-by: Heiko Stuebner Link: https://patchwork.freedesktop.org/patch/msgid/20220422072841.2206452-23-s.hauer@pengutronix.de --- drivers/gpu/drm/rockchip/Kconfig | 6 + drivers/gpu/drm/rockchip/Makefile | 1 + drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 1 + drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 6 +- drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 2 + drivers/gpu/drm/rockchip/rockchip_drm_vop.h | 14 + drivers/gpu/drm/rockchip/rockchip_drm_vop2.c | 2706 ++++++++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_vop2.h | 477 +++++ drivers/gpu/drm/rockchip/rockchip_vop2_reg.c | 281 +++ include/dt-bindings/soc/rockchip,vop2.h | 14 + 10 files changed, 3507 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_vop2.c create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_vop2.h create mode 100644 drivers/gpu/drm/rockchip/rockchip_vop2_reg.c create mode 100644 include/dt-bindings/soc/rockchip,vop2.h diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index 4233b4ce65d3..5afab49dc4f2 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -29,6 +29,12 @@ config ROCKCHIP_VOP This selects support for the VOP driver. You should enable it on older SoCs. +config ROCKCHIP_VOP2 + bool "Rockchip VOP2 driver" + help + This selects support for the VOP2 driver. The VOP2 hardware is + first found on the RK3568. + config ROCKCHIP_ANALOGIX_DP bool "Rockchip specific extensions for Analogix DP driver" depends on ROCKCHIP_VOP diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index dfc5512fdb9f..3ff7b21c0414 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -6,6 +6,7 @@ rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \ rockchip_drm_gem.o +rockchipdrm-$(CONFIG_ROCKCHIP_VOP2) += rockchip_drm_vop2.o rockchip_vop2_reg.o rockchipdrm-$(CONFIG_ROCKCHIP_VOP) += rockchip_drm_vop.o rockchip_vop_reg.o rockchipdrm-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_CDN_DP) += cdn-dp-core.o cdn-dp-reg.o diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index 6ab046081946..67d38f53d3e5 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -482,6 +482,7 @@ static int __init rockchip_drm_init(void) num_rockchip_sub_drivers = 0; ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_ROCKCHIP_VOP); + ADD_ROCKCHIP_SUB_DRIVER(vop2_platform_driver, CONFIG_ROCKCHIP_VOP2); ADD_ROCKCHIP_SUB_DRIVER(rockchip_lvds_driver, CONFIG_ROCKCHIP_LVDS); ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver, diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index a27ab928e1d2..1641440837af 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -18,7 +18,7 @@ #define ROCKCHIP_MAX_FB_BUFFER 3 #define ROCKCHIP_MAX_CONNECTOR 2 -#define ROCKCHIP_MAX_CRTC 2 +#define ROCKCHIP_MAX_CRTC 4 struct drm_device; struct drm_connector; @@ -31,6 +31,9 @@ struct rockchip_crtc_state { int output_bpc; int output_flags; bool enable_afbc; + u32 bus_format; + u32 bus_flags; + int color_space; }; #define to_rockchip_crtc_state(s) \ container_of(s, struct rockchip_crtc_state, base) @@ -72,6 +75,7 @@ extern struct platform_driver rockchip_dp_driver; extern struct platform_driver rockchip_lvds_driver; extern struct platform_driver vop_platform_driver; extern struct platform_driver rk3066_hdmi_driver; +extern struct platform_driver vop2_platform_driver; static inline struct rockchip_encoder *to_rockchip_encoder(struct drm_encoder *encoder) { diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c index 3aa37e177667..0d2cb4f3922b 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c @@ -134,4 +134,6 @@ void rockchip_drm_mode_config_init(struct drm_device *dev) dev->mode_config.funcs = &rockchip_drm_mode_config_funcs; dev->mode_config.helper_private = &rockchip_mode_config_helpers; + + dev->mode_config.normalize_zpos = true; } diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h index 3aa95fdd427d..ba88addc1a75 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_vop.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop.h @@ -54,9 +54,23 @@ struct vop_afbc { struct vop_reg enable; struct vop_reg win_sel; struct vop_reg format; + struct vop_reg rb_swap; + struct vop_reg uv_swap; + struct vop_reg auto_gating_en; + struct vop_reg block_split_en; + struct vop_reg pic_vir_width; + struct vop_reg tile_num; struct vop_reg hreg_block_split; + struct vop_reg pic_offset; struct vop_reg pic_size; + struct vop_reg dsp_offset; + struct vop_reg transform_offset; struct vop_reg hdr_ptr; + struct vop_reg half_block_en; + struct vop_reg xmirror; + struct vop_reg ymirror; + struct vop_reg rotate_270; + struct vop_reg rotate_90; struct vop_reg rstn; }; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c new file mode 100644 index 000000000000..0b49fed16535 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.c @@ -0,0 +1,2706 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2020 Rockchip Electronics Co., Ltd. + * Author: Andy Yan + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "rockchip_drm_drv.h" +#include "rockchip_drm_gem.h" +#include "rockchip_drm_fb.h" +#include "rockchip_drm_vop2.h" + +/* + * VOP2 architecture + * + +----------+ +-------------+ +-----------+ + | Cluster | | Sel 1 from 6| | 1 from 3 | + | window0 | | Layer0 | | RGB | + +----------+ +-------------+ +---------------+ +-------------+ +-----------+ + +----------+ +-------------+ |N from 6 layers| | | + | Cluster | | Sel 1 from 6| | Overlay0 +--->| Video Port0 | +-----------+ + | window1 | | Layer1 | | | | | | 1 from 3 | + +----------+ +-------------+ +---------------+ +-------------+ | LVDS | + +----------+ +-------------+ +-----------+ + | Esmart | | Sel 1 from 6| + | window0 | | Layer2 | +---------------+ +-------------+ +-----------+ + +----------+ +-------------+ |N from 6 Layers| | | +--> | 1 from 3 | + +----------+ +-------------+ --------> | Overlay1 +--->| Video Port1 | | MIPI | + | Esmart | | Sel 1 from 6| --------> | | | | +-----------+ + | Window1 | | Layer3 | +---------------+ +-------------+ + +----------+ +-------------+ +-----------+ + +----------+ +-------------+ | 1 from 3 | + | Smart | | Sel 1 from 6| +---------------+ +-------------+ | HDMI | + | Window0 | | Layer4 | |N from 6 Layers| | | +-----------+ + +----------+ +-------------+ | Overlay2 +--->| Video Port2 | + +----------+ +-------------+ | | | | +-----------+ + | Smart | | Sel 1 from 6| +---------------+ +-------------+ | 1 from 3 | + | Window1 | | Layer5 | | eDP | + +----------+ +-------------+ +-----------+ + * + */ + +enum vop2_data_format { + VOP2_FMT_ARGB8888 = 0, + VOP2_FMT_RGB888, + VOP2_FMT_RGB565, + VOP2_FMT_XRGB101010, + VOP2_FMT_YUV420SP, + VOP2_FMT_YUV422SP, + VOP2_FMT_YUV444SP, + VOP2_FMT_YUYV422 = 8, + VOP2_FMT_YUYV420, + VOP2_FMT_VYUY422, + VOP2_FMT_VYUY420, + VOP2_FMT_YUV420SP_TILE_8x4 = 0x10, + VOP2_FMT_YUV420SP_TILE_16x2, + VOP2_FMT_YUV422SP_TILE_8x4, + VOP2_FMT_YUV422SP_TILE_16x2, + VOP2_FMT_YUV420SP_10, + VOP2_FMT_YUV422SP_10, + VOP2_FMT_YUV444SP_10, +}; + +enum vop2_afbc_format { + VOP2_AFBC_FMT_RGB565, + VOP2_AFBC_FMT_ARGB2101010 = 2, + VOP2_AFBC_FMT_YUV420_10BIT, + VOP2_AFBC_FMT_RGB888, + VOP2_AFBC_FMT_ARGB8888, + VOP2_AFBC_FMT_YUV420 = 9, + VOP2_AFBC_FMT_YUV422 = 0xb, + VOP2_AFBC_FMT_YUV422_10BIT = 0xe, + VOP2_AFBC_FMT_INVALID = -1, +}; + +union vop2_alpha_ctrl { + u32 val; + struct { + /* [0:1] */ + u32 color_mode:1; + u32 alpha_mode:1; + /* [2:3] */ + u32 blend_mode:2; + u32 alpha_cal_mode:1; + /* [5:7] */ + u32 factor_mode:3; + /* [8:9] */ + u32 alpha_en:1; + u32 src_dst_swap:1; + u32 reserved:6; + /* [16:23] */ + u32 glb_alpha:8; + } bits; +}; + +struct vop2_alpha { + union vop2_alpha_ctrl src_color_ctrl; + union vop2_alpha_ctrl dst_color_ctrl; + union vop2_alpha_ctrl src_alpha_ctrl; + union vop2_alpha_ctrl dst_alpha_ctrl; +}; + +struct vop2_alpha_config { + bool src_premulti_en; + bool dst_premulti_en; + bool src_pixel_alpha_en; + bool dst_pixel_alpha_en; + u16 src_glb_alpha_value; + u16 dst_glb_alpha_value; +}; + +struct vop2_win { + struct vop2 *vop2; + struct drm_plane base; + const struct vop2_win_data *data; + struct regmap_field *reg[VOP2_WIN_MAX_REG]; + + /** + * @win_id: graphic window id, a cluster may be split into two + * graphics windows. + */ + u8 win_id; + u8 delay; + u32 offset; + + enum drm_plane_type type; +}; + +struct vop2_video_port { + struct drm_crtc crtc; + struct vop2 *vop2; + struct clk *dclk; + unsigned int id; + const struct vop2_video_port_regs *regs; + const struct vop2_video_port_data *data; + + struct completion dsp_hold_completion; + + /** + * @win_mask: Bitmask of windows attached to the video port; + */ + u32 win_mask; + + struct vop2_win *primary_plane; + struct drm_pending_vblank_event *event; + + unsigned int nlayers; +}; + +struct vop2 { + struct device *dev; + struct drm_device *drm; + struct vop2_video_port vps[ROCKCHIP_MAX_CRTC]; + + const struct vop2_data *data; + /* + * Number of windows that are registered as plane, may be less than the + * total number of hardware windows. + */ + u32 registered_num_wins; + + void __iomem *regs; + struct regmap *map; + + struct regmap *grf; + + /* physical map length of vop2 register */ + u32 len; + + void __iomem *lut_regs; + + /* protects crtc enable/disable */ + struct mutex vop2_lock; + + int irq; + + /* + * Some global resources are shared between all video ports(crtcs), so + * we need a ref counter here. + */ + unsigned int enable_count; + struct clk *hclk; + struct clk *aclk; + + /* must be put at the end of the struct */ + struct vop2_win win[]; +}; + +static struct vop2_video_port *to_vop2_video_port(struct drm_crtc *crtc) +{ + return container_of(crtc, struct vop2_video_port, crtc); +} + +static struct vop2_win *to_vop2_win(struct drm_plane *p) +{ + return container_of(p, struct vop2_win, base); +} + +static void vop2_lock(struct vop2 *vop2) +{ + mutex_lock(&vop2->vop2_lock); +} + +static void vop2_unlock(struct vop2 *vop2) +{ + mutex_unlock(&vop2->vop2_lock); +} + +static void vop2_writel(struct vop2 *vop2, u32 offset, u32 v) +{ + regmap_write(vop2->map, offset, v); +} + +static void vop2_vp_write(struct vop2_video_port *vp, u32 offset, u32 v) +{ + regmap_write(vp->vop2->map, vp->data->offset + offset, v); +} + +static u32 vop2_readl(struct vop2 *vop2, u32 offset) +{ + u32 val; + + regmap_read(vop2->map, offset, &val); + + return val; +} + +static void vop2_win_write(const struct vop2_win *win, unsigned int reg, u32 v) +{ + regmap_field_write(win->reg[reg], v); +} + +static bool vop2_cluster_window(const struct vop2_win *win) +{ + return win->data->feature & WIN_FEATURE_CLUSTER; +} + +static void vop2_cfg_done(struct vop2_video_port *vp) +{ + struct vop2 *vop2 = vp->vop2; + + regmap_set_bits(vop2->map, RK3568_REG_CFG_DONE, + BIT(vp->id) | RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN); +} + +static void vop2_win_disable(struct vop2_win *win) +{ + vop2_win_write(win, VOP2_WIN_ENABLE, 0); + + if (vop2_cluster_window(win)) + vop2_win_write(win, VOP2_WIN_CLUSTER_ENABLE, 0); +} + +static enum vop2_data_format vop2_convert_format(u32 format) +{ + switch (format) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return VOP2_FMT_ARGB8888; + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + return VOP2_FMT_RGB888; + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + return VOP2_FMT_RGB565; + case DRM_FORMAT_NV12: + return VOP2_FMT_YUV420SP; + case DRM_FORMAT_NV16: + return VOP2_FMT_YUV422SP; + case DRM_FORMAT_NV24: + return VOP2_FMT_YUV444SP; + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + return VOP2_FMT_VYUY422; + case DRM_FORMAT_VYUY: + case DRM_FORMAT_UYVY: + return VOP2_FMT_YUYV422; + default: + DRM_ERROR("unsupported format[%08x]\n", format); + return -EINVAL; + } +} + +static enum vop2_afbc_format vop2_convert_afbc_format(u32 format) +{ + switch (format) { + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + return VOP2_AFBC_FMT_ARGB8888; + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + return VOP2_AFBC_FMT_RGB888; + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + return VOP2_AFBC_FMT_RGB565; + case DRM_FORMAT_NV12: + return VOP2_AFBC_FMT_YUV420; + case DRM_FORMAT_NV16: + return VOP2_AFBC_FMT_YUV422; + default: + return VOP2_AFBC_FMT_INVALID; + } + + return VOP2_AFBC_FMT_INVALID; +} + +static bool vop2_win_rb_swap(u32 format) +{ + switch (format) { + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_BGR888: + case DRM_FORMAT_BGR565: + return true; + default: + return false; + } +} + +static bool vop2_afbc_rb_swap(u32 format) +{ + switch (format) { + case DRM_FORMAT_NV24: + return true; + default: + return false; + } +} + +static bool vop2_afbc_uv_swap(u32 format) +{ + switch (format) { + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV16: + return true; + default: + return false; + } +} + +static bool vop2_win_uv_swap(u32 format) +{ + switch (format) { + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV24: + return true; + default: + return false; + } +} + +static bool vop2_win_dither_up(u32 format) +{ + switch (format) { + case DRM_FORMAT_BGR565: + case DRM_FORMAT_RGB565: + return true; + default: + return false; + } +} + +static bool vop2_output_uv_swap(u32 bus_format, u32 output_mode) +{ + /* + * FIXME: + * + * There is no media type for YUV444 output, + * so when out_mode is AAAA or P888, assume output is YUV444 on + * yuv format. + * + * From H/W testing, YUV444 mode need a rb swap. + */ + if (bus_format == MEDIA_BUS_FMT_YVYU8_1X16 || + bus_format == MEDIA_BUS_FMT_VYUY8_1X16 || + bus_format == MEDIA_BUS_FMT_YVYU8_2X8 || + bus_format == MEDIA_BUS_FMT_VYUY8_2X8 || + ((bus_format == MEDIA_BUS_FMT_YUV8_1X24 || + bus_format == MEDIA_BUS_FMT_YUV10_1X30) && + (output_mode == ROCKCHIP_OUT_MODE_AAAA || + output_mode == ROCKCHIP_OUT_MODE_P888))) + return true; + else + return false; +} + +static bool is_yuv_output(u32 bus_format) +{ + switch (bus_format) { + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_YVYU8_2X8: + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_VYUY8_2X8: + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_YVYU8_1X16: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_VYUY8_1X16: + return true; + default: + return false; + } +} + +static bool rockchip_afbc(struct drm_plane *plane, u64 modifier) +{ + int i; + + if (modifier == DRM_FORMAT_MOD_LINEAR) + return false; + + for (i = 0 ; i < plane->modifier_count; i++) + if (plane->modifiers[i] == modifier) + return true; + + return false; +} + +static bool rockchip_vop2_mod_supported(struct drm_plane *plane, u32 format, + u64 modifier) +{ + struct vop2_win *win = to_vop2_win(plane); + struct vop2 *vop2 = win->vop2; + + if (modifier == DRM_FORMAT_MOD_INVALID) + return false; + + if (modifier == DRM_FORMAT_MOD_LINEAR) + return true; + + if (!rockchip_afbc(plane, modifier)) { + drm_err(vop2->drm, "Unsupported format modifier 0x%llx\n", + modifier); + + return false; + } + + return vop2_convert_afbc_format(format) >= 0; +} + +static u32 vop2_afbc_transform_offset(struct drm_plane_state *pstate, + bool afbc_half_block_en) +{ + struct drm_rect *src = &pstate->src; + struct drm_framebuffer *fb = pstate->fb; + u32 bpp = fb->format->cpp[0] * 8; + u32 vir_width = (fb->pitches[0] << 3) / bpp; + u32 width = drm_rect_width(src) >> 16; + u32 height = drm_rect_height(src) >> 16; + u32 act_xoffset = src->x1 >> 16; + u32 act_yoffset = src->y1 >> 16; + u32 align16_crop = 0; + u32 align64_crop = 0; + u32 height_tmp; + u8 tx, ty; + u8 bottom_crop_line_num = 0; + + /* 16 pixel align */ + if (height & 0xf) + align16_crop = 16 - (height & 0xf); + + height_tmp = height + align16_crop; + + /* 64 pixel align */ + if (height_tmp & 0x3f) + align64_crop = 64 - (height_tmp & 0x3f); + + bottom_crop_line_num = align16_crop + align64_crop; + + switch (pstate->rotation & + (DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y | + DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270)) { + case DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y: + tx = 16 - ((act_xoffset + width) & 0xf); + ty = bottom_crop_line_num - act_yoffset; + break; + case DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_90: + tx = bottom_crop_line_num - act_yoffset; + ty = vir_width - width - act_xoffset; + break; + case DRM_MODE_REFLECT_X | DRM_MODE_ROTATE_270: + tx = act_yoffset; + ty = act_xoffset; + break; + case DRM_MODE_REFLECT_X: + tx = 16 - ((act_xoffset + width) & 0xf); + ty = act_yoffset; + break; + case DRM_MODE_REFLECT_Y: + tx = act_xoffset; + ty = bottom_crop_line_num - act_yoffset; + break; + case DRM_MODE_ROTATE_90: + tx = bottom_crop_line_num - act_yoffset; + ty = act_xoffset; + break; + case DRM_MODE_ROTATE_270: + tx = act_yoffset; + ty = vir_width - width - act_xoffset; + break; + case 0: + tx = act_xoffset; + ty = act_yoffset; + break; + } + + if (afbc_half_block_en) + ty &= 0x7f; + +#define TRANSFORM_XOFFSET GENMASK(7, 0) +#define TRANSFORM_YOFFSET GENMASK(23, 16) + return FIELD_PREP(TRANSFORM_XOFFSET, tx) | + FIELD_PREP(TRANSFORM_YOFFSET, ty); +} + +/* + * A Cluster window has 2048 x 16 line buffer, which can + * works at 2048 x 16(Full) or 4096 x 8 (Half) mode. + * for Cluster_lb_mode register: + * 0: half mode, for plane input width range 2048 ~ 4096 + * 1: half mode, for cluster work at 2 * 2048 plane mode + * 2: half mode, for rotate_90/270 mode + * + */ +static int vop2_get_cluster_lb_mode(struct vop2_win *win, + struct drm_plane_state *pstate) +{ + if ((pstate->rotation & DRM_MODE_ROTATE_270) || + (pstate->rotation & DRM_MODE_ROTATE_90)) + return 2; + else + return 0; +} + +static u16 vop2_scale_factor(u32 src, u32 dst) +{ + u32 fac; + int shift; + + if (src == dst) + return 0; + + if (dst < 2) + return U16_MAX; + + if (src < 2) + return 0; + + if (src > dst) + shift = 12; + else + shift = 16; + + src--; + dst--; + + fac = DIV_ROUND_UP(src << shift, dst) - 1; + + if (fac > U16_MAX) + return U16_MAX; + + return fac; +} + +static void vop2_setup_scale(struct vop2 *vop2, const struct vop2_win *win, + u32 src_w, u32 src_h, u32 dst_w, + u32 dst_h, u32 pixel_format) +{ + const struct drm_format_info *info; + u16 hor_scl_mode, ver_scl_mode; + u16 hscl_filter_mode, vscl_filter_mode; + u8 gt2 = 0; + u8 gt4 = 0; + u32 val; + + info = drm_format_info(pixel_format); + + if (src_h >= (4 * dst_h)) { + gt4 = 1; + src_h >>= 2; + } else if (src_h >= (2 * dst_h)) { + gt2 = 1; + src_h >>= 1; + } + + hor_scl_mode = scl_get_scl_mode(src_w, dst_w); + ver_scl_mode = scl_get_scl_mode(src_h, dst_h); + + if (hor_scl_mode == SCALE_UP) + hscl_filter_mode = VOP2_SCALE_UP_BIC; + else + hscl_filter_mode = VOP2_SCALE_DOWN_BIL; + + if (ver_scl_mode == SCALE_UP) + vscl_filter_mode = VOP2_SCALE_UP_BIL; + else + vscl_filter_mode = VOP2_SCALE_DOWN_BIL; + + /* + * RK3568 VOP Esmart/Smart dsp_w should be even pixel + * at scale down mode + */ + if (!(win->data->feature & WIN_FEATURE_AFBDC)) { + if ((hor_scl_mode == SCALE_DOWN) && (dst_w & 0x1)) { + drm_dbg(vop2->drm, "%s dst_w[%d] should align as 2 pixel\n", + win->data->name, dst_w); + dst_w++; + } + } + + val = vop2_scale_factor(src_w, dst_w); + vop2_win_write(win, VOP2_WIN_SCALE_YRGB_X, val); + val = vop2_scale_factor(src_h, dst_h); + vop2_win_write(win, VOP2_WIN_SCALE_YRGB_Y, val); + + vop2_win_write(win, VOP2_WIN_VSD_YRGB_GT4, gt4); + vop2_win_write(win, VOP2_WIN_VSD_YRGB_GT2, gt2); + + vop2_win_write(win, VOP2_WIN_YRGB_HOR_SCL_MODE, hor_scl_mode); + vop2_win_write(win, VOP2_WIN_YRGB_VER_SCL_MODE, ver_scl_mode); + + if (vop2_cluster_window(win)) + return; + + vop2_win_write(win, VOP2_WIN_YRGB_HSCL_FILTER_MODE, hscl_filter_mode); + vop2_win_write(win, VOP2_WIN_YRGB_VSCL_FILTER_MODE, vscl_filter_mode); + + if (info->is_yuv) { + src_w /= info->hsub; + src_h /= info->vsub; + + gt4 = 0; + gt2 = 0; + + if (src_h >= (4 * dst_h)) { + gt4 = 1; + src_h >>= 2; + } else if (src_h >= (2 * dst_h)) { + gt2 = 1; + src_h >>= 1; + } + + hor_scl_mode = scl_get_scl_mode(src_w, dst_w); + ver_scl_mode = scl_get_scl_mode(src_h, dst_h); + + val = vop2_scale_factor(src_w, dst_w); + vop2_win_write(win, VOP2_WIN_SCALE_CBCR_X, val); + + val = vop2_scale_factor(src_h, dst_h); + vop2_win_write(win, VOP2_WIN_SCALE_CBCR_Y, val); + + vop2_win_write(win, VOP2_WIN_VSD_CBCR_GT4, gt4); + vop2_win_write(win, VOP2_WIN_VSD_CBCR_GT2, gt2); + vop2_win_write(win, VOP2_WIN_CBCR_HOR_SCL_MODE, hor_scl_mode); + vop2_win_write(win, VOP2_WIN_CBCR_VER_SCL_MODE, ver_scl_mode); + vop2_win_write(win, VOP2_WIN_CBCR_HSCL_FILTER_MODE, hscl_filter_mode); + vop2_win_write(win, VOP2_WIN_CBCR_VSCL_FILTER_MODE, vscl_filter_mode); + } +} + +static int vop2_convert_csc_mode(int csc_mode) +{ + switch (csc_mode) { + case V4L2_COLORSPACE_SMPTE170M: + case V4L2_COLORSPACE_470_SYSTEM_M: + case V4L2_COLORSPACE_470_SYSTEM_BG: + return CSC_BT601L; + case V4L2_COLORSPACE_REC709: + case V4L2_COLORSPACE_SMPTE240M: + case V4L2_COLORSPACE_DEFAULT: + return CSC_BT709L; + case V4L2_COLORSPACE_JPEG: + return CSC_BT601F; + case V4L2_COLORSPACE_BT2020: + return CSC_BT2020; + default: + return CSC_BT709L; + } +} + +/* + * colorspace path: + * Input Win csc Output + * 1. YUV(2020) --> Y2R->2020To709->R2Y --> YUV_OUTPUT(601/709) + * RGB --> R2Y __/ + * + * 2. YUV(2020) --> bypasss --> YUV_OUTPUT(2020) + * RGB --> 709To2020->R2Y __/ + * + * 3. YUV(2020) --> Y2R->2020To709 --> RGB_OUTPUT(709) + * RGB --> R2Y __/ + * + * 4. YUV(601/709)-> Y2R->709To2020->R2Y --> YUV_OUTPUT(2020) + * RGB --> 709To2020->R2Y __/ + * + * 5. YUV(601/709)-> bypass --> YUV_OUTPUT(709) + * RGB --> R2Y __/ + * + * 6. YUV(601/709)-> bypass --> YUV_OUTPUT(601) + * RGB --> R2Y(601) __/ + * + * 7. YUV --> Y2R(709) --> RGB_OUTPUT(709) + * RGB --> bypass __/ + * + * 8. RGB --> 709To2020->R2Y --> YUV_OUTPUT(2020) + * + * 9. RGB --> R2Y(709) --> YUV_OUTPUT(709) + * + * 10. RGB --> R2Y(601) --> YUV_OUTPUT(601) + * + * 11. RGB --> bypass --> RGB_OUTPUT(709) + */ + +static void vop2_setup_csc_mode(struct vop2_video_port *vp, + struct vop2_win *win, + struct drm_plane_state *pstate) +{ + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(vp->crtc.state); + int is_input_yuv = pstate->fb->format->is_yuv; + int is_output_yuv = is_yuv_output(vcstate->bus_format); + int input_csc = V4L2_COLORSPACE_DEFAULT; + int output_csc = vcstate->color_space; + bool r2y_en, y2r_en; + int csc_mode; + + if (is_input_yuv && !is_output_yuv) { + y2r_en = true; + r2y_en = false; + csc_mode = vop2_convert_csc_mode(input_csc); + } else if (!is_input_yuv && is_output_yuv) { + y2r_en = false; + r2y_en = true; + csc_mode = vop2_convert_csc_mode(output_csc); + } else { + y2r_en = false; + r2y_en = false; + csc_mode = false; + } + + vop2_win_write(win, VOP2_WIN_Y2R_EN, y2r_en); + vop2_win_write(win, VOP2_WIN_R2Y_EN, r2y_en); + vop2_win_write(win, VOP2_WIN_CSC_MODE, csc_mode); +} + +static void vop2_crtc_enable_irq(struct vop2_video_port *vp, u32 irq) +{ + struct vop2 *vop2 = vp->vop2; + + vop2_writel(vop2, RK3568_VP_INT_CLR(vp->id), irq << 16 | irq); + vop2_writel(vop2, RK3568_VP_INT_EN(vp->id), irq << 16 | irq); +} + +static void vop2_crtc_disable_irq(struct vop2_video_port *vp, u32 irq) +{ + struct vop2 *vop2 = vp->vop2; + + vop2_writel(vop2, RK3568_VP_INT_EN(vp->id), irq << 16); +} + +static int vop2_core_clks_prepare_enable(struct vop2 *vop2) +{ + int ret; + + ret = clk_prepare_enable(vop2->hclk); + if (ret < 0) { + drm_err(vop2->drm, "failed to enable hclk - %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(vop2->aclk); + if (ret < 0) { + drm_err(vop2->drm, "failed to enable aclk - %d\n", ret); + goto err; + } + + return 0; +err: + clk_disable_unprepare(vop2->hclk); + + return ret; +} + +static void vop2_enable(struct vop2 *vop2) +{ + int ret; + + ret = pm_runtime_get_sync(vop2->dev); + if (ret < 0) { + drm_err(vop2->drm, "failed to get pm runtime: %d\n", ret); + return; + } + + ret = vop2_core_clks_prepare_enable(vop2); + if (ret) { + pm_runtime_put_sync(vop2->dev); + return; + } + + ret = rockchip_drm_dma_attach_device(vop2->drm, vop2->dev); + if (ret) { + drm_err(vop2->drm, "failed to attach dma mapping, %d\n", ret); + return; + } + + if (vop2->data->soc_id == 3566) + vop2_writel(vop2, RK3568_OTP_WIN_EN, 1); + + vop2_writel(vop2, RK3568_REG_CFG_DONE, RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN); + + /* + * Disable auto gating, this is a workaround to + * avoid display image shift when a window enabled. + */ + regmap_clear_bits(vop2->map, RK3568_SYS_AUTO_GATING_CTRL, + RK3568_SYS_AUTO_GATING_CTRL__AUTO_GATING_EN); + + vop2_writel(vop2, RK3568_SYS0_INT_CLR, + VOP2_INT_BUS_ERRPR << 16 | VOP2_INT_BUS_ERRPR); + vop2_writel(vop2, RK3568_SYS0_INT_EN, + VOP2_INT_BUS_ERRPR << 16 | VOP2_INT_BUS_ERRPR); + vop2_writel(vop2, RK3568_SYS1_INT_CLR, + VOP2_INT_BUS_ERRPR << 16 | VOP2_INT_BUS_ERRPR); + vop2_writel(vop2, RK3568_SYS1_INT_EN, + VOP2_INT_BUS_ERRPR << 16 | VOP2_INT_BUS_ERRPR); +} + +static void vop2_disable(struct vop2 *vop2) +{ + rockchip_drm_dma_detach_device(vop2->drm, vop2->dev); + + pm_runtime_put_sync(vop2->dev); + + clk_disable_unprepare(vop2->aclk); + clk_disable_unprepare(vop2->hclk); +} + +static void vop2_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct vop2 *vop2 = vp->vop2; + int ret; + + vop2_lock(vop2); + + drm_crtc_vblank_off(crtc); + + /* + * Vop standby will take effect at end of current frame, + * if dsp hold valid irq happen, it means standby complete. + * + * we must wait standby complete when we want to disable aclk, + * if not, memory bus maybe dead. + */ + reinit_completion(&vp->dsp_hold_completion); + + vop2_crtc_enable_irq(vp, VP_INT_DSP_HOLD_VALID); + + vop2_vp_write(vp, RK3568_VP_DSP_CTRL, RK3568_VP_DSP_CTRL__STANDBY); + + ret = wait_for_completion_timeout(&vp->dsp_hold_completion, + msecs_to_jiffies(50)); + if (!ret) + drm_info(vop2->drm, "wait for vp%d dsp_hold timeout\n", vp->id); + + vop2_crtc_disable_irq(vp, VP_INT_DSP_HOLD_VALID); + + clk_disable_unprepare(vp->dclk); + + vop2->enable_count--; + + if (!vop2->enable_count) + vop2_disable(vop2); + + vop2_unlock(vop2); + + if (crtc->state->event && !crtc->state->active) { + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + spin_unlock_irq(&crtc->dev->event_lock); + + crtc->state->event = NULL; + } +} + +static int vop2_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *astate) +{ + struct drm_plane_state *pstate = drm_atomic_get_new_plane_state(astate, plane); + struct drm_framebuffer *fb = pstate->fb; + struct drm_crtc *crtc = pstate->crtc; + struct drm_crtc_state *cstate; + struct vop2_video_port *vp; + struct vop2 *vop2; + const struct vop2_data *vop2_data; + struct drm_rect *dest = &pstate->dst; + struct drm_rect *src = &pstate->src; + int min_scale = FRAC_16_16(1, 8); + int max_scale = FRAC_16_16(8, 1); + int format; + int ret; + + if (!crtc) + return 0; + + vp = to_vop2_video_port(crtc); + vop2 = vp->vop2; + vop2_data = vop2->data; + + cstate = drm_atomic_get_existing_crtc_state(pstate->state, crtc); + if (WARN_ON(!cstate)) + return -EINVAL; + + ret = drm_atomic_helper_check_plane_state(pstate, cstate, + min_scale, max_scale, + true, true); + if (ret) + return ret; + + if (!pstate->visible) + return 0; + + format = vop2_convert_format(fb->format->format); + if (format < 0) + return format; + + if (drm_rect_width(src) >> 16 < 4 || drm_rect_height(src) >> 16 < 4 || + drm_rect_width(dest) < 4 || drm_rect_width(dest) < 4) { + drm_err(vop2->drm, "Invalid size: %dx%d->%dx%d, min size is 4x4\n", + drm_rect_width(src) >> 16, drm_rect_height(src) >> 16, + drm_rect_width(dest), drm_rect_height(dest)); + pstate->visible = false; + return 0; + } + + if (drm_rect_width(src) >> 16 > vop2_data->max_input.width || + drm_rect_height(src) >> 16 > vop2_data->max_input.height) { + drm_err(vop2->drm, "Invalid source: %dx%d. max input: %dx%d\n", + drm_rect_width(src) >> 16, + drm_rect_height(src) >> 16, + vop2_data->max_input.width, + vop2_data->max_input.height); + return -EINVAL; + } + + /* + * Src.x1 can be odd when do clip, but yuv plane start point + * need align with 2 pixel. + */ + if (fb->format->is_yuv && ((pstate->src.x1 >> 16) % 2)) { + drm_err(vop2->drm, "Invalid Source: Yuv format not support odd xpos\n"); + return -EINVAL; + } + + return 0; +} + +static void vop2_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *old_pstate = drm_atomic_get_old_plane_state(state, plane); + struct vop2_win *win = to_vop2_win(plane); + struct vop2 *vop2 = win->vop2; + + drm_dbg(vop2->drm, "%s disable\n", win->data->name); + + if (!old_pstate->crtc) + return; + + vop2_win_disable(win); + vop2_win_write(win, VOP2_WIN_YUV_CLIP, 0); +} + +/* + * The color key is 10 bit, so all format should + * convert to 10 bit here. + */ +static void vop2_plane_setup_color_key(struct drm_plane *plane, u32 color_key) +{ + struct drm_plane_state *pstate = plane->state; + struct drm_framebuffer *fb = pstate->fb; + struct vop2_win *win = to_vop2_win(plane); + u32 color_key_en = 0; + u32 r = 0; + u32 g = 0; + u32 b = 0; + + if (!(color_key & VOP2_COLOR_KEY_MASK) || fb->format->is_yuv) { + vop2_win_write(win, VOP2_WIN_COLOR_KEY_EN, 0); + return; + } + + switch (fb->format->format) { + case DRM_FORMAT_RGB565: + case DRM_FORMAT_BGR565: + r = (color_key & 0xf800) >> 11; + g = (color_key & 0x7e0) >> 5; + b = (color_key & 0x1f); + r <<= 5; + g <<= 4; + b <<= 5; + color_key_en = 1; + break; + case DRM_FORMAT_XRGB8888: + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XBGR8888: + case DRM_FORMAT_ABGR8888: + case DRM_FORMAT_RGB888: + case DRM_FORMAT_BGR888: + r = (color_key & 0xff0000) >> 16; + g = (color_key & 0xff00) >> 8; + b = (color_key & 0xff); + r <<= 2; + g <<= 2; + b <<= 2; + color_key_en = 1; + break; + } + + vop2_win_write(win, VOP2_WIN_COLOR_KEY_EN, color_key_en); + vop2_win_write(win, VOP2_WIN_COLOR_KEY, (r << 20) | (g << 10) | b); +} + +static void vop2_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *pstate = plane->state; + struct drm_crtc *crtc = pstate->crtc; + struct vop2_win *win = to_vop2_win(plane); + struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode; + struct vop2 *vop2 = win->vop2; + struct drm_framebuffer *fb = pstate->fb; + u32 bpp = fb->format->cpp[0] * 8; + u32 actual_w, actual_h, dsp_w, dsp_h; + u32 act_info, dsp_info; + u32 format; + u32 afbc_format; + u32 rb_swap; + u32 uv_swap; + struct drm_rect *src = &pstate->src; + struct drm_rect *dest = &pstate->dst; + u32 afbc_tile_num; + u32 transform_offset; + bool dither_up; + bool xmirror = pstate->rotation & DRM_MODE_REFLECT_X ? true : false; + bool ymirror = pstate->rotation & DRM_MODE_REFLECT_Y ? true : false; + bool rotate_270 = pstate->rotation & DRM_MODE_ROTATE_270; + bool rotate_90 = pstate->rotation & DRM_MODE_ROTATE_90; + struct rockchip_gem_object *rk_obj; + unsigned long offset; + bool afbc_en; + dma_addr_t yrgb_mst; + dma_addr_t uv_mst; + + /* + * can't update plane when vop2 is disabled. + */ + if (WARN_ON(!crtc)) + return; + + if (!pstate->visible) { + vop2_plane_atomic_disable(plane, state); + return; + } + + afbc_en = rockchip_afbc(plane, fb->modifier); + + offset = (src->x1 >> 16) * fb->format->cpp[0]; + + /* + * AFBC HDR_PTR must set to the zero offset of the framebuffer. + */ + if (afbc_en) + offset = 0; + else if (pstate->rotation & DRM_MODE_REFLECT_Y) + offset += ((src->y2 >> 16) - 1) * fb->pitches[0]; + else + offset += (src->y1 >> 16) * fb->pitches[0]; + + rk_obj = to_rockchip_obj(fb->obj[0]); + + yrgb_mst = rk_obj->dma_addr + offset + fb->offsets[0]; + if (fb->format->is_yuv) { + int hsub = fb->format->hsub; + int vsub = fb->format->vsub; + + offset = (src->x1 >> 16) * fb->format->cpp[1] / hsub; + offset += (src->y1 >> 16) * fb->pitches[1] / vsub; + + if ((pstate->rotation & DRM_MODE_REFLECT_Y) && !afbc_en) + offset += fb->pitches[1] * ((pstate->src_h >> 16) - 2) / vsub; + + rk_obj = to_rockchip_obj(fb->obj[0]); + uv_mst = rk_obj->dma_addr + offset + fb->offsets[1]; + } + + actual_w = drm_rect_width(src) >> 16; + actual_h = drm_rect_height(src) >> 16; + dsp_w = drm_rect_width(dest); + + if (dest->x1 + dsp_w > adjusted_mode->hdisplay) { + drm_err(vop2->drm, "vp%d %s dest->x1[%d] + dsp_w[%d] exceed mode hdisplay[%d]\n", + vp->id, win->data->name, dest->x1, dsp_w, adjusted_mode->hdisplay); + dsp_w = adjusted_mode->hdisplay - dest->x1; + if (dsp_w < 4) + dsp_w = 4; + actual_w = dsp_w * actual_w / drm_rect_width(dest); + } + + dsp_h = drm_rect_height(dest); + + if (dest->y1 + dsp_h > adjusted_mode->vdisplay) { + drm_err(vop2->drm, "vp%d %s dest->y1[%d] + dsp_h[%d] exceed mode vdisplay[%d]\n", + vp->id, win->data->name, dest->y1, dsp_h, adjusted_mode->vdisplay); + dsp_h = adjusted_mode->vdisplay - dest->y1; + if (dsp_h < 4) + dsp_h = 4; + actual_h = dsp_h * actual_h / drm_rect_height(dest); + } + + /* + * This is workaround solution for IC design: + * esmart can't support scale down when actual_w % 16 == 1. + */ + if (!(win->data->feature & WIN_FEATURE_AFBDC)) { + if (actual_w > dsp_w && (actual_w & 0xf) == 1) { + drm_err(vop2->drm, "vp%d %s act_w[%d] MODE 16 == 1\n", + vp->id, win->data->name, actual_w); + actual_w -= 1; + } + } + + if (afbc_en && actual_w % 4) { + drm_err(vop2->drm, "vp%d %s actual_w[%d] not 4 pixel aligned\n", + vp->id, win->data->name, actual_w); + actual_w = ALIGN_DOWN(actual_w, 4); + } + + act_info = (actual_h - 1) << 16 | ((actual_w - 1) & 0xffff); + dsp_info = (dsp_h - 1) << 16 | ((dsp_w - 1) & 0xffff); + + format = vop2_convert_format(fb->format->format); + + drm_dbg(vop2->drm, "vp%d update %s[%dx%d->%dx%d@%dx%d] fmt[%p4cc_%s] addr[%pad]\n", + vp->id, win->data->name, actual_w, actual_h, dsp_w, dsp_h, + dest->x1, dest->y1, + &fb->format->format, + afbc_en ? "AFBC" : "", &yrgb_mst); + + if (afbc_en) { + u32 stride; + + /* the afbc superblock is 16 x 16 */ + afbc_format = vop2_convert_afbc_format(fb->format->format); + + /* Enable color transform for YTR */ + if (fb->modifier & AFBC_FORMAT_MOD_YTR) + afbc_format |= (1 << 4); + + afbc_tile_num = ALIGN(actual_w, 16) >> 4; + + /* + * AFBC pic_vir_width is count by pixel, this is different + * with WIN_VIR_STRIDE. + */ + stride = (fb->pitches[0] << 3) / bpp; + if ((stride & 0x3f) && (xmirror || rotate_90 || rotate_270)) + drm_err(vop2->drm, "vp%d %s stride[%d] not 64 pixel aligened\n", + vp->id, win->data->name, stride); + + rb_swap = vop2_afbc_rb_swap(fb->format->format); + uv_swap = vop2_afbc_uv_swap(fb->format->format); + /* + * This is a workaround for crazy IC design, Cluster + * and Esmart/Smart use different format configuration map: + * YUV420_10BIT: 0x10 for Cluster, 0x14 for Esmart/Smart. + * + * This is one thing we can make the convert simple: + * AFBCD decode all the YUV data to YUV444. So we just + * set all the yuv 10 bit to YUV444_10. + */ + if (fb->format->is_yuv && bpp == 10) + format = VOP2_CLUSTER_YUV444_10; + + if (vop2_cluster_window(win)) + vop2_win_write(win, VOP2_WIN_AFBC_ENABLE, 1); + vop2_win_write(win, VOP2_WIN_AFBC_FORMAT, afbc_format); + vop2_win_write(win, VOP2_WIN_AFBC_RB_SWAP, rb_swap); + vop2_win_write(win, VOP2_WIN_AFBC_UV_SWAP, uv_swap); + vop2_win_write(win, VOP2_WIN_AFBC_AUTO_GATING_EN, 0); + vop2_win_write(win, VOP2_WIN_AFBC_BLOCK_SPLIT_EN, 0); + if (pstate->rotation & (DRM_MODE_ROTATE_270 | DRM_MODE_ROTATE_90)) { + vop2_win_write(win, VOP2_WIN_AFBC_HALF_BLOCK_EN, 0); + transform_offset = vop2_afbc_transform_offset(pstate, false); + } else { + vop2_win_write(win, VOP2_WIN_AFBC_HALF_BLOCK_EN, 1); + transform_offset = vop2_afbc_transform_offset(pstate, true); + } + vop2_win_write(win, VOP2_WIN_AFBC_HDR_PTR, yrgb_mst); + vop2_win_write(win, VOP2_WIN_AFBC_PIC_SIZE, act_info); + vop2_win_write(win, VOP2_WIN_AFBC_TRANSFORM_OFFSET, transform_offset); + vop2_win_write(win, VOP2_WIN_AFBC_PIC_OFFSET, ((src->x1 >> 16) | src->y1)); + vop2_win_write(win, VOP2_WIN_AFBC_DSP_OFFSET, (dest->x1 | (dest->y1 << 16))); + vop2_win_write(win, VOP2_WIN_AFBC_PIC_VIR_WIDTH, stride); + vop2_win_write(win, VOP2_WIN_AFBC_TILE_NUM, afbc_tile_num); + vop2_win_write(win, VOP2_WIN_XMIRROR, xmirror); + vop2_win_write(win, VOP2_WIN_AFBC_ROTATE_270, rotate_270); + vop2_win_write(win, VOP2_WIN_AFBC_ROTATE_90, rotate_90); + } else { + vop2_win_write(win, VOP2_WIN_YRGB_VIR, DIV_ROUND_UP(fb->pitches[0], 4)); + } + + vop2_win_write(win, VOP2_WIN_YMIRROR, ymirror); + + if (rotate_90 || rotate_270) { + act_info = swahw32(act_info); + actual_w = drm_rect_height(src) >> 16; + actual_h = drm_rect_width(src) >> 16; + } + + vop2_win_write(win, VOP2_WIN_FORMAT, format); + vop2_win_write(win, VOP2_WIN_YRGB_MST, yrgb_mst); + + rb_swap = vop2_win_rb_swap(fb->format->format); + vop2_win_write(win, VOP2_WIN_RB_SWAP, rb_swap); + if (!vop2_cluster_window(win)) { + uv_swap = vop2_win_uv_swap(fb->format->format); + vop2_win_write(win, VOP2_WIN_UV_SWAP, uv_swap); + } + + if (fb->format->is_yuv) { + vop2_win_write(win, VOP2_WIN_UV_VIR, DIV_ROUND_UP(fb->pitches[1], 4)); + vop2_win_write(win, VOP2_WIN_UV_MST, uv_mst); + } + + vop2_setup_scale(vop2, win, actual_w, actual_h, dsp_w, dsp_h, fb->format->format); + if (!vop2_cluster_window(win)) + vop2_plane_setup_color_key(plane, 0); + vop2_win_write(win, VOP2_WIN_ACT_INFO, act_info); + vop2_win_write(win, VOP2_WIN_DSP_INFO, dsp_info); + vop2_win_write(win, VOP2_WIN_DSP_ST, dest->y1 << 16 | (dest->x1 & 0xffff)); + + vop2_setup_csc_mode(vp, win, pstate); + + dither_up = vop2_win_dither_up(fb->format->format); + vop2_win_write(win, VOP2_WIN_DITHER_UP, dither_up); + + vop2_win_write(win, VOP2_WIN_ENABLE, 1); + + if (vop2_cluster_window(win)) { + int lb_mode = vop2_get_cluster_lb_mode(win, pstate); + + vop2_win_write(win, VOP2_WIN_CLUSTER_LB_MODE, lb_mode); + vop2_win_write(win, VOP2_WIN_CLUSTER_ENABLE, 1); + } +} + +static const struct drm_plane_helper_funcs vop2_plane_helper_funcs = { + .atomic_check = vop2_plane_atomic_check, + .atomic_update = vop2_plane_atomic_update, + .atomic_disable = vop2_plane_atomic_disable, +}; + +static const struct drm_plane_funcs vop2_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, + .format_mod_supported = rockchip_vop2_mod_supported, +}; + +static int vop2_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct vop2_video_port *vp = to_vop2_video_port(crtc); + + vop2_crtc_enable_irq(vp, VP_INT_FS_FIELD); + + return 0; +} + +static void vop2_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct vop2_video_port *vp = to_vop2_video_port(crtc); + + vop2_crtc_disable_irq(vp, VP_INT_FS_FIELD); +} + +static bool vop2_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + drm_mode_set_crtcinfo(adj_mode, CRTC_INTERLACE_HALVE_V | + CRTC_STEREO_DOUBLE); + + return true; +} + +static void vop2_dither_setup(struct drm_crtc *crtc, u32 *dsp_ctrl) +{ + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state); + + switch (vcstate->bus_format) { + case MEDIA_BUS_FMT_RGB565_1X16: + *dsp_ctrl |= RK3568_VP_DSP_CTRL__DITHER_DOWN_EN; + break; + case MEDIA_BUS_FMT_RGB666_1X18: + case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: + case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG: + *dsp_ctrl |= RK3568_VP_DSP_CTRL__DITHER_DOWN_EN; + *dsp_ctrl |= RGB888_TO_RGB666; + break; + case MEDIA_BUS_FMT_YUV8_1X24: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + *dsp_ctrl |= RK3568_VP_DSP_CTRL__PRE_DITHER_DOWN_EN; + break; + default: + break; + } + + if (vcstate->output_mode != ROCKCHIP_OUT_MODE_AAAA) + *dsp_ctrl |= RK3568_VP_DSP_CTRL__PRE_DITHER_DOWN_EN; + + *dsp_ctrl |= FIELD_PREP(RK3568_VP_DSP_CTRL__DITHER_DOWN_SEL, + DITHER_DOWN_ALLEGRO); +} + +static void vop2_post_config(struct drm_crtc *crtc) +{ + struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + u16 vtotal = mode->crtc_vtotal; + u16 hdisplay = mode->crtc_hdisplay; + u16 hact_st = mode->crtc_htotal - mode->crtc_hsync_start; + u16 vdisplay = mode->crtc_vdisplay; + u16 vact_st = mode->crtc_vtotal - mode->crtc_vsync_start; + u32 left_margin = 100, right_margin = 100; + u32 top_margin = 100, bottom_margin = 100; + u16 hsize = hdisplay * (left_margin + right_margin) / 200; + u16 vsize = vdisplay * (top_margin + bottom_margin) / 200; + u16 hact_end, vact_end; + u32 val; + + vsize = rounddown(vsize, 2); + hsize = rounddown(hsize, 2); + hact_st += hdisplay * (100 - left_margin) / 200; + hact_end = hact_st + hsize; + val = hact_st << 16; + val |= hact_end; + vop2_vp_write(vp, RK3568_VP_POST_DSP_HACT_INFO, val); + vact_st += vdisplay * (100 - top_margin) / 200; + vact_end = vact_st + vsize; + val = vact_st << 16; + val |= vact_end; + vop2_vp_write(vp, RK3568_VP_POST_DSP_VACT_INFO, val); + val = scl_cal_scale2(vdisplay, vsize) << 16; + val |= scl_cal_scale2(hdisplay, hsize); + vop2_vp_write(vp, RK3568_VP_POST_SCL_FACTOR_YRGB, val); + + val = 0; + if (hdisplay != hsize) + val |= RK3568_VP_POST_SCL_CTRL__HSCALEDOWN; + if (vdisplay != vsize) + val |= RK3568_VP_POST_SCL_CTRL__VSCALEDOWN; + vop2_vp_write(vp, RK3568_VP_POST_SCL_CTRL, val); + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + u16 vact_st_f1 = vtotal + vact_st + 1; + u16 vact_end_f1 = vact_st_f1 + vsize; + + val = vact_st_f1 << 16 | vact_end_f1; + vop2_vp_write(vp, RK3568_VP_POST_DSP_VACT_INFO_F1, val); + } + + vop2_vp_write(vp, RK3568_VP_DSP_BG, 0); +} + +static void rk3568_set_intf_mux(struct vop2_video_port *vp, int id, + u32 polflags) +{ + struct vop2 *vop2 = vp->vop2; + u32 die, dip; + + die = vop2_readl(vop2, RK3568_DSP_IF_EN); + dip = vop2_readl(vop2, RK3568_DSP_IF_POL); + + switch (id) { + case ROCKCHIP_VOP2_EP_RGB0: + die &= ~RK3568_SYS_DSP_INFACE_EN_RGB_MUX; + die |= RK3568_SYS_DSP_INFACE_EN_RGB | + FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_RGB_MUX, vp->id); + if (polflags & POLFLAG_DCLK_INV) + regmap_write(vop2->grf, RK3568_GRF_VO_CON1, BIT(3 + 16) | BIT(3)); + else + regmap_write(vop2->grf, RK3568_GRF_VO_CON1, BIT(3 + 16)); + break; + case ROCKCHIP_VOP2_EP_HDMI0: + die &= ~RK3568_SYS_DSP_INFACE_EN_HDMI_MUX; + die |= RK3568_SYS_DSP_INFACE_EN_HDMI | + FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_HDMI_MUX, vp->id); + break; + case ROCKCHIP_VOP2_EP_EDP0: + die &= ~RK3568_SYS_DSP_INFACE_EN_EDP_MUX; + die |= RK3568_SYS_DSP_INFACE_EN_EDP | + FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_EDP_MUX, vp->id); + break; + case ROCKCHIP_VOP2_EP_MIPI0: + die &= ~RK3568_SYS_DSP_INFACE_EN_MIPI0_MUX; + die |= RK3568_SYS_DSP_INFACE_EN_MIPI0 | + FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_MIPI0_MUX, vp->id); + dip &= ~RK3568_DSP_IF_POL__MIPI_PIN_POL; + dip |= FIELD_PREP(RK3568_DSP_IF_POL__MIPI_PIN_POL, polflags); + break; + case ROCKCHIP_VOP2_EP_MIPI1: + die &= ~RK3568_SYS_DSP_INFACE_EN_MIPI1_MUX; + die |= RK3568_SYS_DSP_INFACE_EN_MIPI1 | + FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_MIPI1_MUX, vp->id); + dip &= ~RK3568_DSP_IF_POL__MIPI_PIN_POL; + dip |= FIELD_PREP(RK3568_DSP_IF_POL__MIPI_PIN_POL, polflags); + break; + case ROCKCHIP_VOP2_EP_LVDS0: + die &= ~RK3568_SYS_DSP_INFACE_EN_LVDS0_MUX; + die |= RK3568_SYS_DSP_INFACE_EN_LVDS0 | + FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_LVDS0_MUX, vp->id); + dip &= ~RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL; + dip |= FIELD_PREP(RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL, polflags); + break; + case ROCKCHIP_VOP2_EP_LVDS1: + die &= ~RK3568_SYS_DSP_INFACE_EN_LVDS1_MUX; + die |= RK3568_SYS_DSP_INFACE_EN_LVDS1 | + FIELD_PREP(RK3568_SYS_DSP_INFACE_EN_LVDS1_MUX, vp->id); + dip &= ~RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL; + dip |= FIELD_PREP(RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL, polflags); + break; + default: + drm_err(vop2->drm, "Invalid interface id %d on vp%d\n", id, vp->id); + return; + }; + + dip |= RK3568_DSP_IF_POL__CFG_DONE_IMD; + + vop2_writel(vop2, RK3568_DSP_IF_EN, die); + vop2_writel(vop2, RK3568_DSP_IF_POL, dip); +} + +static int us_to_vertical_line(struct drm_display_mode *mode, int us) +{ + return us * mode->clock / mode->htotal / 1000; +} + +static void vop2_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct vop2 *vop2 = vp->vop2; + const struct vop2_data *vop2_data = vop2->data; + const struct vop2_video_port_data *vp_data = &vop2_data->vp[vp->id]; + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state); + struct drm_display_mode *mode = &crtc->state->adjusted_mode; + unsigned long clock = mode->crtc_clock * 1000; + u16 hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; + u16 hdisplay = mode->crtc_hdisplay; + u16 htotal = mode->crtc_htotal; + u16 hact_st = mode->crtc_htotal - mode->crtc_hsync_start; + u16 hact_end = hact_st + hdisplay; + u16 vdisplay = mode->crtc_vdisplay; + u16 vtotal = mode->crtc_vtotal; + u16 vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start; + u16 vact_st = mode->crtc_vtotal - mode->crtc_vsync_start; + u16 vact_end = vact_st + vdisplay; + u8 out_mode; + u32 dsp_ctrl = 0; + int act_end; + u32 val, polflags; + int ret; + struct drm_encoder *encoder; + + drm_dbg(vop2->drm, "Update mode to %dx%d%s%d, type: %d for vp%d\n", + hdisplay, vdisplay, mode->flags & DRM_MODE_FLAG_INTERLACE ? "i" : "p", + drm_mode_vrefresh(mode), vcstate->output_type, vp->id); + + vop2_lock(vop2); + + ret = clk_prepare_enable(vp->dclk); + if (ret < 0) { + drm_err(vop2->drm, "failed to enable dclk for video port%d - %d\n", + vp->id, ret); + return; + } + + if (!vop2->enable_count) + vop2_enable(vop2); + + vop2->enable_count++; + + vop2_crtc_enable_irq(vp, VP_INT_POST_BUF_EMPTY); + + polflags = 0; + if (vcstate->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE) + polflags |= POLFLAG_DCLK_INV; + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + polflags |= BIT(HSYNC_POSITIVE); + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + polflags |= BIT(VSYNC_POSITIVE); + + drm_for_each_encoder_mask(encoder, crtc->dev, crtc_state->encoder_mask) { + struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); + + rk3568_set_intf_mux(vp, rkencoder->crtc_endpoint_id, polflags); + } + + if (vcstate->output_mode == ROCKCHIP_OUT_MODE_AAAA && + !(vp_data->feature & VOP_FEATURE_OUTPUT_10BIT)) + out_mode = ROCKCHIP_OUT_MODE_P888; + else + out_mode = vcstate->output_mode; + + dsp_ctrl |= FIELD_PREP(RK3568_VP_DSP_CTRL__OUT_MODE, out_mode); + + if (vop2_output_uv_swap(vcstate->bus_format, vcstate->output_mode)) + dsp_ctrl |= RK3568_VP_DSP_CTRL__DSP_RB_SWAP; + + if (is_yuv_output(vcstate->bus_format)) + dsp_ctrl |= RK3568_VP_DSP_CTRL__POST_DSP_OUT_R2Y; + + vop2_dither_setup(crtc, &dsp_ctrl); + + vop2_vp_write(vp, RK3568_VP_DSP_HTOTAL_HS_END, (htotal << 16) | hsync_len); + val = hact_st << 16; + val |= hact_end; + vop2_vp_write(vp, RK3568_VP_DSP_HACT_ST_END, val); + + val = vact_st << 16; + val |= vact_end; + vop2_vp_write(vp, RK3568_VP_DSP_VACT_ST_END, val); + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + u16 vact_st_f1 = vtotal + vact_st + 1; + u16 vact_end_f1 = vact_st_f1 + vdisplay; + + val = vact_st_f1 << 16 | vact_end_f1; + vop2_vp_write(vp, RK3568_VP_DSP_VACT_ST_END_F1, val); + + val = vtotal << 16 | (vtotal + vsync_len); + vop2_vp_write(vp, RK3568_VP_DSP_VS_ST_END_F1, val); + dsp_ctrl |= RK3568_VP_DSP_CTRL__DSP_INTERLACE; + dsp_ctrl |= RK3568_VP_DSP_CTRL__DSP_FILED_POL; + dsp_ctrl |= RK3568_VP_DSP_CTRL__P2I_EN; + vtotal += vtotal + 1; + act_end = vact_end_f1; + } else { + act_end = vact_end; + } + + vop2_writel(vop2, RK3568_VP_LINE_FLAG(vp->id), + (act_end - us_to_vertical_line(mode, 0)) << 16 | act_end); + + vop2_vp_write(vp, RK3568_VP_DSP_VTOTAL_VS_END, vtotal << 16 | vsync_len); + + if (mode->flags & DRM_MODE_FLAG_DBLCLK) { + dsp_ctrl |= RK3568_VP_DSP_CTRL__CORE_DCLK_DIV; + clock *= 2; + } + + vop2_vp_write(vp, RK3568_VP_MIPI_CTRL, 0); + + clk_set_rate(vp->dclk, clock); + + vop2_post_config(crtc); + + vop2_cfg_done(vp); + + vop2_vp_write(vp, RK3568_VP_DSP_CTRL, dsp_ctrl); + + drm_crtc_vblank_on(crtc); + + vop2_unlock(vop2); +} + +static int vop2_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct drm_plane *plane; + int nplanes = 0; + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + drm_atomic_crtc_state_for_each_plane(plane, crtc_state) + nplanes++; + + if (nplanes > vp->nlayers) + return -EINVAL; + + return 0; +} + +static bool is_opaque(u16 alpha) +{ + return (alpha >> 8) == 0xff; +} + +static void vop2_parse_alpha(struct vop2_alpha_config *alpha_config, + struct vop2_alpha *alpha) +{ + int src_glb_alpha_en = is_opaque(alpha_config->src_glb_alpha_value) ? 0 : 1; + int dst_glb_alpha_en = is_opaque(alpha_config->dst_glb_alpha_value) ? 0 : 1; + int src_color_mode = alpha_config->src_premulti_en ? + ALPHA_SRC_PRE_MUL : ALPHA_SRC_NO_PRE_MUL; + int dst_color_mode = alpha_config->dst_premulti_en ? + ALPHA_SRC_PRE_MUL : ALPHA_SRC_NO_PRE_MUL; + + alpha->src_color_ctrl.val = 0; + alpha->dst_color_ctrl.val = 0; + alpha->src_alpha_ctrl.val = 0; + alpha->dst_alpha_ctrl.val = 0; + + if (!alpha_config->src_pixel_alpha_en) + alpha->src_color_ctrl.bits.blend_mode = ALPHA_GLOBAL; + else if (alpha_config->src_pixel_alpha_en && !src_glb_alpha_en) + alpha->src_color_ctrl.bits.blend_mode = ALPHA_PER_PIX; + else + alpha->src_color_ctrl.bits.blend_mode = ALPHA_PER_PIX_GLOBAL; + + alpha->src_color_ctrl.bits.alpha_en = 1; + + if (alpha->src_color_ctrl.bits.blend_mode == ALPHA_GLOBAL) { + alpha->src_color_ctrl.bits.color_mode = src_color_mode; + alpha->src_color_ctrl.bits.factor_mode = SRC_FAC_ALPHA_SRC_GLOBAL; + } else if (alpha->src_color_ctrl.bits.blend_mode == ALPHA_PER_PIX) { + alpha->src_color_ctrl.bits.color_mode = src_color_mode; + alpha->src_color_ctrl.bits.factor_mode = SRC_FAC_ALPHA_ONE; + } else { + alpha->src_color_ctrl.bits.color_mode = ALPHA_SRC_PRE_MUL; + alpha->src_color_ctrl.bits.factor_mode = SRC_FAC_ALPHA_SRC_GLOBAL; + } + alpha->src_color_ctrl.bits.glb_alpha = alpha_config->src_glb_alpha_value >> 8; + alpha->src_color_ctrl.bits.alpha_mode = ALPHA_STRAIGHT; + alpha->src_color_ctrl.bits.alpha_cal_mode = ALPHA_SATURATION; + + alpha->dst_color_ctrl.bits.alpha_mode = ALPHA_STRAIGHT; + alpha->dst_color_ctrl.bits.alpha_cal_mode = ALPHA_SATURATION; + alpha->dst_color_ctrl.bits.blend_mode = ALPHA_GLOBAL; + alpha->dst_color_ctrl.bits.glb_alpha = alpha_config->dst_glb_alpha_value >> 8; + alpha->dst_color_ctrl.bits.color_mode = dst_color_mode; + alpha->dst_color_ctrl.bits.factor_mode = ALPHA_SRC_INVERSE; + + alpha->src_alpha_ctrl.bits.alpha_mode = ALPHA_STRAIGHT; + alpha->src_alpha_ctrl.bits.blend_mode = alpha->src_color_ctrl.bits.blend_mode; + alpha->src_alpha_ctrl.bits.alpha_cal_mode = ALPHA_SATURATION; + alpha->src_alpha_ctrl.bits.factor_mode = ALPHA_ONE; + + alpha->dst_alpha_ctrl.bits.alpha_mode = ALPHA_STRAIGHT; + if (alpha_config->dst_pixel_alpha_en && !dst_glb_alpha_en) + alpha->dst_alpha_ctrl.bits.blend_mode = ALPHA_PER_PIX; + else + alpha->dst_alpha_ctrl.bits.blend_mode = ALPHA_PER_PIX_GLOBAL; + alpha->dst_alpha_ctrl.bits.alpha_cal_mode = ALPHA_NO_SATURATION; + alpha->dst_alpha_ctrl.bits.factor_mode = ALPHA_SRC_INVERSE; +} + +static int vop2_find_start_mixer_id_for_vp(struct vop2 *vop2, u8 port_id) +{ + struct vop2_video_port *vp; + int used_layer = 0; + int i; + + for (i = 0; i < port_id; i++) { + vp = &vop2->vps[i]; + used_layer += hweight32(vp->win_mask); + } + + return used_layer; +} + +static void vop2_setup_cluster_alpha(struct vop2 *vop2, struct vop2_win *main_win) +{ + u32 offset = (main_win->data->phys_id * 0x10); + struct vop2_alpha_config alpha_config; + struct vop2_alpha alpha; + struct drm_plane_state *bottom_win_pstate; + bool src_pixel_alpha_en = false; + u16 src_glb_alpha_val, dst_glb_alpha_val; + bool premulti_en = false; + bool swap = false; + + /* At one win mode, win0 is dst/bottom win, and win1 is a all zero src/top win */ + bottom_win_pstate = main_win->base.state; + src_glb_alpha_val = 0; + dst_glb_alpha_val = main_win->base.state->alpha; + + if (!bottom_win_pstate->fb) + return; + + alpha_config.src_premulti_en = premulti_en; + alpha_config.dst_premulti_en = false; + alpha_config.src_pixel_alpha_en = src_pixel_alpha_en; + alpha_config.dst_pixel_alpha_en = true; /* alpha value need transfer to next mix */ + alpha_config.src_glb_alpha_value = src_glb_alpha_val; + alpha_config.dst_glb_alpha_value = dst_glb_alpha_val; + vop2_parse_alpha(&alpha_config, &alpha); + + alpha.src_color_ctrl.bits.src_dst_swap = swap; + vop2_writel(vop2, RK3568_CLUSTER0_MIX_SRC_COLOR_CTRL + offset, + alpha.src_color_ctrl.val); + vop2_writel(vop2, RK3568_CLUSTER0_MIX_DST_COLOR_CTRL + offset, + alpha.dst_color_ctrl.val); + vop2_writel(vop2, RK3568_CLUSTER0_MIX_SRC_ALPHA_CTRL + offset, + alpha.src_alpha_ctrl.val); + vop2_writel(vop2, RK3568_CLUSTER0_MIX_DST_ALPHA_CTRL + offset, + alpha.dst_alpha_ctrl.val); +} + +static void vop2_setup_alpha(struct vop2_video_port *vp) +{ + struct vop2 *vop2 = vp->vop2; + struct drm_framebuffer *fb; + struct vop2_alpha_config alpha_config; + struct vop2_alpha alpha; + struct drm_plane *plane; + int pixel_alpha_en; + int premulti_en, gpremulti_en = 0; + int mixer_id; + u32 offset; + bool bottom_layer_alpha_en = false; + u32 dst_global_alpha = DRM_BLEND_ALPHA_OPAQUE; + + mixer_id = vop2_find_start_mixer_id_for_vp(vop2, vp->id); + alpha_config.dst_pixel_alpha_en = true; /* alpha value need transfer to next mix */ + + drm_atomic_crtc_for_each_plane(plane, &vp->crtc) { + struct vop2_win *win = to_vop2_win(plane); + + if (plane->state->normalized_zpos == 0 && + !is_opaque(plane->state->alpha) && + !vop2_cluster_window(win)) { + /* + * If bottom layer have global alpha effect [except cluster layer, + * because cluster have deal with bottom layer global alpha value + * at cluster mix], bottom layer mix need deal with global alpha. + */ + bottom_layer_alpha_en = true; + dst_global_alpha = plane->state->alpha; + } + } + + drm_atomic_crtc_for_each_plane(plane, &vp->crtc) { + struct vop2_win *win = to_vop2_win(plane); + int zpos = plane->state->normalized_zpos; + + if (plane->state->pixel_blend_mode == DRM_MODE_BLEND_PREMULTI) + premulti_en = 1; + else + premulti_en = 0; + + plane = &win->base; + fb = plane->state->fb; + + pixel_alpha_en = fb->format->has_alpha; + + alpha_config.src_premulti_en = premulti_en; + + if (bottom_layer_alpha_en && zpos == 1) { + gpremulti_en = premulti_en; + /* Cd = Cs + (1 - As) * Cd * Agd */ + alpha_config.dst_premulti_en = false; + alpha_config.src_pixel_alpha_en = pixel_alpha_en; + alpha_config.src_glb_alpha_value = plane->state->alpha; + alpha_config.dst_glb_alpha_value = dst_global_alpha; + } else if (vop2_cluster_window(win)) { + /* Mix output data only have pixel alpha */ + alpha_config.dst_premulti_en = true; + alpha_config.src_pixel_alpha_en = true; + alpha_config.src_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE; + alpha_config.dst_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE; + } else { + /* Cd = Cs + (1 - As) * Cd */ + alpha_config.dst_premulti_en = true; + alpha_config.src_pixel_alpha_en = pixel_alpha_en; + alpha_config.src_glb_alpha_value = plane->state->alpha; + alpha_config.dst_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE; + } + + vop2_parse_alpha(&alpha_config, &alpha); + + offset = (mixer_id + zpos - 1) * 0x10; + vop2_writel(vop2, RK3568_MIX0_SRC_COLOR_CTRL + offset, + alpha.src_color_ctrl.val); + vop2_writel(vop2, RK3568_MIX0_DST_COLOR_CTRL + offset, + alpha.dst_color_ctrl.val); + vop2_writel(vop2, RK3568_MIX0_SRC_ALPHA_CTRL + offset, + alpha.src_alpha_ctrl.val); + vop2_writel(vop2, RK3568_MIX0_DST_ALPHA_CTRL + offset, + alpha.dst_alpha_ctrl.val); + } + + if (vp->id == 0) { + if (bottom_layer_alpha_en) { + /* Transfer pixel alpha to hdr mix */ + alpha_config.src_premulti_en = gpremulti_en; + alpha_config.dst_premulti_en = true; + alpha_config.src_pixel_alpha_en = true; + alpha_config.src_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE; + alpha_config.dst_glb_alpha_value = DRM_BLEND_ALPHA_OPAQUE; + vop2_parse_alpha(&alpha_config, &alpha); + + vop2_writel(vop2, RK3568_HDR0_SRC_COLOR_CTRL, + alpha.src_color_ctrl.val); + vop2_writel(vop2, RK3568_HDR0_DST_COLOR_CTRL, + alpha.dst_color_ctrl.val); + vop2_writel(vop2, RK3568_HDR0_SRC_ALPHA_CTRL, + alpha.src_alpha_ctrl.val); + vop2_writel(vop2, RK3568_HDR0_DST_ALPHA_CTRL, + alpha.dst_alpha_ctrl.val); + } else { + vop2_writel(vop2, RK3568_HDR0_SRC_COLOR_CTRL, 0); + } + } +} + +static void vop2_setup_layer_mixer(struct vop2_video_port *vp) +{ + struct vop2 *vop2 = vp->vop2; + struct drm_plane *plane; + u32 layer_sel = 0; + u32 port_sel; + unsigned int nlayer, ofs; + struct drm_display_mode *adjusted_mode; + u16 hsync_len; + u16 hdisplay; + u32 bg_dly; + u32 pre_scan_dly; + int i; + struct vop2_video_port *vp0 = &vop2->vps[0]; + struct vop2_video_port *vp1 = &vop2->vps[1]; + struct vop2_video_port *vp2 = &vop2->vps[2]; + + adjusted_mode = &vp->crtc.state->adjusted_mode; + hsync_len = adjusted_mode->crtc_hsync_end - adjusted_mode->crtc_hsync_start; + hdisplay = adjusted_mode->crtc_hdisplay; + + bg_dly = vp->data->pre_scan_max_dly[3]; + vop2_writel(vop2, RK3568_VP_BG_MIX_CTRL(vp->id), + FIELD_PREP(RK3568_VP_BG_MIX_CTRL__BG_DLY, bg_dly)); + + pre_scan_dly = ((bg_dly + (hdisplay >> 1) - 1) << 16) | hsync_len; + vop2_vp_write(vp, RK3568_VP_PRE_SCAN_HTIMING, pre_scan_dly); + + vop2_writel(vop2, RK3568_OVL_CTRL, 0); + port_sel = vop2_readl(vop2, RK3568_OVL_PORT_SEL); + port_sel &= RK3568_OVL_PORT_SEL__SEL_PORT; + + if (vp0->nlayers) + port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT0_MUX, + vp0->nlayers - 1); + else + port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT0_MUX, 8); + + if (vp1->nlayers) + port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT1_MUX, + (vp0->nlayers + vp1->nlayers - 1)); + else + port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT1_MUX, 8); + + if (vp2->nlayers) + port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT2_MUX, + (vp2->nlayers + vp1->nlayers + vp0->nlayers - 1)); + else + port_sel |= FIELD_PREP(RK3568_OVL_PORT_SET__PORT1_MUX, 8); + + layer_sel = vop2_readl(vop2, RK3568_OVL_LAYER_SEL); + + ofs = 0; + for (i = 0; i < vp->id; i++) + ofs += vop2->vps[i].nlayers; + + nlayer = 0; + drm_atomic_crtc_for_each_plane(plane, &vp->crtc) { + struct vop2_win *win = to_vop2_win(plane); + + switch (win->data->phys_id) { + case ROCKCHIP_VOP2_CLUSTER0: + port_sel &= ~RK3568_OVL_PORT_SEL__CLUSTER0; + port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__CLUSTER0, vp->id); + break; + case ROCKCHIP_VOP2_CLUSTER1: + port_sel &= ~RK3568_OVL_PORT_SEL__CLUSTER1; + port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__CLUSTER1, vp->id); + break; + case ROCKCHIP_VOP2_ESMART0: + port_sel &= ~RK3568_OVL_PORT_SEL__ESMART0; + port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__ESMART0, vp->id); + break; + case ROCKCHIP_VOP2_ESMART1: + port_sel &= ~RK3568_OVL_PORT_SEL__ESMART1; + port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__ESMART1, vp->id); + break; + case ROCKCHIP_VOP2_SMART0: + port_sel &= ~RK3568_OVL_PORT_SEL__SMART0; + port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__SMART0, vp->id); + break; + case ROCKCHIP_VOP2_SMART1: + port_sel &= ~RK3568_OVL_PORT_SEL__SMART1; + port_sel |= FIELD_PREP(RK3568_OVL_PORT_SEL__SMART1, vp->id); + break; + } + + layer_sel &= ~RK3568_OVL_LAYER_SEL__LAYER(plane->state->normalized_zpos + ofs, + 0x7); + layer_sel |= RK3568_OVL_LAYER_SEL__LAYER(plane->state->normalized_zpos + ofs, + win->data->layer_sel_id); + nlayer++; + } + + /* configure unused layers to 0x5 (reserved) */ + for (; nlayer < vp->nlayers; nlayer++) { + layer_sel &= ~RK3568_OVL_LAYER_SEL__LAYER(nlayer + ofs, 0x7); + layer_sel |= RK3568_OVL_LAYER_SEL__LAYER(nlayer + ofs, 5); + } + + vop2_writel(vop2, RK3568_OVL_LAYER_SEL, layer_sel); + vop2_writel(vop2, RK3568_OVL_PORT_SEL, port_sel); + vop2_writel(vop2, RK3568_OVL_CTRL, RK3568_OVL_CTRL__LAYERSEL_REGDONE_IMD); +} + +static void vop2_setup_dly_for_windows(struct vop2 *vop2) +{ + struct vop2_win *win; + int i = 0; + u32 cdly = 0, sdly = 0; + + for (i = 0; i < vop2->data->win_size; i++) { + u32 dly; + + win = &vop2->win[i]; + dly = win->delay; + + switch (win->data->phys_id) { + case ROCKCHIP_VOP2_CLUSTER0: + cdly |= FIELD_PREP(RK3568_CLUSTER_DLY_NUM__CLUSTER0_0, dly); + cdly |= FIELD_PREP(RK3568_CLUSTER_DLY_NUM__CLUSTER0_1, dly); + break; + case ROCKCHIP_VOP2_CLUSTER1: + cdly |= FIELD_PREP(RK3568_CLUSTER_DLY_NUM__CLUSTER1_0, dly); + cdly |= FIELD_PREP(RK3568_CLUSTER_DLY_NUM__CLUSTER1_1, dly); + break; + case ROCKCHIP_VOP2_ESMART0: + sdly |= FIELD_PREP(RK3568_SMART_DLY_NUM__ESMART0, dly); + break; + case ROCKCHIP_VOP2_ESMART1: + sdly |= FIELD_PREP(RK3568_SMART_DLY_NUM__ESMART1, dly); + break; + case ROCKCHIP_VOP2_SMART0: + sdly |= FIELD_PREP(RK3568_SMART_DLY_NUM__SMART0, dly); + break; + case ROCKCHIP_VOP2_SMART1: + sdly |= FIELD_PREP(RK3568_SMART_DLY_NUM__SMART1, dly); + break; + } + } + + vop2_writel(vop2, RK3568_CLUSTER_DLY_NUM, cdly); + vop2_writel(vop2, RK3568_SMART_DLY_NUM, sdly); +} + +static void vop2_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct vop2_video_port *vp = to_vop2_video_port(crtc); + struct vop2 *vop2 = vp->vop2; + struct drm_plane *plane; + + vp->win_mask = 0; + + drm_atomic_crtc_for_each_plane(plane, crtc) { + struct vop2_win *win = to_vop2_win(plane); + + win->delay = win->data->dly[VOP2_DLY_MODE_DEFAULT]; + + vp->win_mask |= BIT(win->data->phys_id); + + if (vop2_cluster_window(win)) + vop2_setup_cluster_alpha(vop2, win); + } + + if (!vp->win_mask) + return; + + vop2_setup_layer_mixer(vp); + vop2_setup_alpha(vp); + vop2_setup_dly_for_windows(vop2); +} + +static void vop2_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct vop2_video_port *vp = to_vop2_video_port(crtc); + + vop2_post_config(crtc); + + vop2_cfg_done(vp); + + spin_lock_irq(&crtc->dev->event_lock); + + if (crtc->state->event) { + WARN_ON(drm_crtc_vblank_get(crtc)); + vp->event = crtc->state->event; + crtc->state->event = NULL; + } + + spin_unlock_irq(&crtc->dev->event_lock); +} + +static const struct drm_crtc_helper_funcs vop2_crtc_helper_funcs = { + .mode_fixup = vop2_crtc_mode_fixup, + .atomic_check = vop2_crtc_atomic_check, + .atomic_begin = vop2_crtc_atomic_begin, + .atomic_flush = vop2_crtc_atomic_flush, + .atomic_enable = vop2_crtc_atomic_enable, + .atomic_disable = vop2_crtc_atomic_disable, +}; + +static void vop2_crtc_reset(struct drm_crtc *crtc) +{ + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state); + + if (crtc->state) { + __drm_atomic_helper_crtc_destroy_state(crtc->state); + kfree(vcstate); + } + + vcstate = kzalloc(sizeof(*vcstate), GFP_KERNEL); + if (!vcstate) + return; + + crtc->state = &vcstate->base; + crtc->state->crtc = crtc; +} + +static struct drm_crtc_state *vop2_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct rockchip_crtc_state *vcstate, *old_vcstate; + + old_vcstate = to_rockchip_crtc_state(crtc->state); + + vcstate = kmemdup(old_vcstate, sizeof(*old_vcstate), GFP_KERNEL); + if (!vcstate) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &vcstate->base); + + return &vcstate->base; +} + +static void vop2_crtc_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(state); + + __drm_atomic_helper_crtc_destroy_state(&vcstate->base); + kfree(vcstate); +} + +static const struct drm_crtc_funcs vop2_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .destroy = drm_crtc_cleanup, + .reset = vop2_crtc_reset, + .atomic_duplicate_state = vop2_crtc_duplicate_state, + .atomic_destroy_state = vop2_crtc_destroy_state, + .enable_vblank = vop2_crtc_enable_vblank, + .disable_vblank = vop2_crtc_disable_vblank, +}; + +static irqreturn_t vop2_isr(int irq, void *data) +{ + struct vop2 *vop2 = data; + const struct vop2_data *vop2_data = vop2->data; + u32 axi_irqs[VOP2_SYS_AXI_BUS_NUM]; + int ret = IRQ_NONE; + int i; + + /* + * The irq is shared with the iommu. If the runtime-pm state of the + * vop2-device is disabled the irq has to be targeted at the iommu. + */ + if (!pm_runtime_get_if_in_use(vop2->dev)) + return IRQ_NONE; + + for (i = 0; i < vop2_data->nr_vps; i++) { + struct vop2_video_port *vp = &vop2->vps[i]; + struct drm_crtc *crtc = &vp->crtc; + u32 irqs; + + irqs = vop2_readl(vop2, RK3568_VP_INT_STATUS(vp->id)); + vop2_writel(vop2, RK3568_VP_INT_CLR(vp->id), irqs << 16 | irqs); + + if (irqs & VP_INT_DSP_HOLD_VALID) { + complete(&vp->dsp_hold_completion); + ret = IRQ_HANDLED; + } + + if (irqs & VP_INT_FS_FIELD) { + drm_crtc_handle_vblank(crtc); + spin_lock(&crtc->dev->event_lock); + if (vp->event) { + u32 val = vop2_readl(vop2, RK3568_REG_CFG_DONE); + + if (!(val & BIT(vp->id))) { + drm_crtc_send_vblank_event(crtc, vp->event); + vp->event = NULL; + drm_crtc_vblank_put(crtc); + } + } + spin_unlock(&crtc->dev->event_lock); + + ret = IRQ_HANDLED; + } + + if (irqs & VP_INT_POST_BUF_EMPTY) { + drm_err_ratelimited(vop2->drm, + "POST_BUF_EMPTY irq err at vp%d\n", + vp->id); + ret = IRQ_HANDLED; + } + } + + axi_irqs[0] = vop2_readl(vop2, RK3568_SYS0_INT_STATUS); + vop2_writel(vop2, RK3568_SYS0_INT_CLR, axi_irqs[0] << 16 | axi_irqs[0]); + axi_irqs[1] = vop2_readl(vop2, RK3568_SYS1_INT_STATUS); + vop2_writel(vop2, RK3568_SYS1_INT_CLR, axi_irqs[1] << 16 | axi_irqs[1]); + + for (i = 0; i < ARRAY_SIZE(axi_irqs); i++) { + if (axi_irqs[i] & VOP2_INT_BUS_ERRPR) { + drm_err_ratelimited(vop2->drm, "BUS_ERROR irq err\n"); + ret = IRQ_HANDLED; + } + } + + pm_runtime_put(vop2->dev); + + return ret; +} + +static int vop2_plane_init(struct vop2 *vop2, struct vop2_win *win, + unsigned long possible_crtcs) +{ + const struct vop2_win_data *win_data = win->data; + unsigned int blend_caps = BIT(DRM_MODE_BLEND_PIXEL_NONE) | + BIT(DRM_MODE_BLEND_PREMULTI) | + BIT(DRM_MODE_BLEND_COVERAGE); + int ret; + + ret = drm_universal_plane_init(vop2->drm, &win->base, possible_crtcs, + &vop2_plane_funcs, win_data->formats, + win_data->nformats, + win_data->format_modifiers, + win->type, win_data->name); + if (ret) { + drm_err(vop2->drm, "failed to initialize plane %d\n", ret); + return ret; + } + + drm_plane_helper_add(&win->base, &vop2_plane_helper_funcs); + + if (win->data->supported_rotations) + drm_plane_create_rotation_property(&win->base, DRM_MODE_ROTATE_0, + DRM_MODE_ROTATE_0 | + win->data->supported_rotations); + drm_plane_create_alpha_property(&win->base); + drm_plane_create_blend_mode_property(&win->base, blend_caps); + drm_plane_create_zpos_property(&win->base, win->win_id, 0, + vop2->registered_num_wins - 1); + + return 0; +} + +static struct vop2_video_port *find_vp_without_primary(struct vop2 *vop2) +{ + int i; + + for (i = 0; i < vop2->data->nr_vps; i++) { + struct vop2_video_port *vp = &vop2->vps[i]; + + if (!vp->crtc.port) + continue; + if (vp->primary_plane) + continue; + + return vp; + } + + return NULL; +} + +#define NR_LAYERS 6 + +static int vop2_create_crtc(struct vop2 *vop2) +{ + const struct vop2_data *vop2_data = vop2->data; + struct drm_device *drm = vop2->drm; + struct device *dev = vop2->dev; + struct drm_plane *plane; + struct device_node *port; + struct vop2_video_port *vp; + int i, nvp, nvps = 0; + int ret; + + for (i = 0; i < vop2_data->nr_vps; i++) { + const struct vop2_video_port_data *vp_data; + struct device_node *np; + char dclk_name[9]; + + vp_data = &vop2_data->vp[i]; + vp = &vop2->vps[i]; + vp->vop2 = vop2; + vp->id = vp_data->id; + vp->regs = vp_data->regs; + vp->data = vp_data; + + snprintf(dclk_name, sizeof(dclk_name), "dclk_vp%d", vp->id); + vp->dclk = devm_clk_get(vop2->dev, dclk_name); + if (IS_ERR(vp->dclk)) { + drm_err(vop2->drm, "failed to get %s\n", dclk_name); + return PTR_ERR(vp->dclk); + } + + np = of_graph_get_remote_node(dev->of_node, i, -1); + if (!np) { + drm_dbg(vop2->drm, "%s: No remote for vp%d\n", __func__, i); + continue; + } + of_node_put(np); + + port = of_graph_get_port_by_id(dev->of_node, i); + if (!port) { + drm_err(vop2->drm, "no port node found for video_port%d\n", i); + return -ENOENT; + } + + vp->crtc.port = port; + nvps++; + } + + nvp = 0; + for (i = 0; i < vop2->registered_num_wins; i++) { + struct vop2_win *win = &vop2->win[i]; + u32 possible_crtcs; + + if (vop2->data->soc_id == 3566) { + /* + * On RK3566 these windows don't have an independent + * framebuffer. They share the framebuffer with smart0, + * esmart0 and cluster0 respectively. + */ + switch (win->data->phys_id) { + case ROCKCHIP_VOP2_SMART1: + case ROCKCHIP_VOP2_ESMART1: + case ROCKCHIP_VOP2_CLUSTER1: + continue; + } + } + + if (win->type == DRM_PLANE_TYPE_PRIMARY) { + vp = find_vp_without_primary(vop2); + if (vp) { + possible_crtcs = BIT(nvp); + vp->primary_plane = win; + nvp++; + } else { + /* change the unused primary window to overlay window */ + win->type = DRM_PLANE_TYPE_OVERLAY; + } + } + + if (win->type == DRM_PLANE_TYPE_OVERLAY) + possible_crtcs = (1 << nvps) - 1; + + ret = vop2_plane_init(vop2, win, possible_crtcs); + if (ret) { + drm_err(vop2->drm, "failed to init plane %s: %d\n", + win->data->name, ret); + return ret; + } + } + + for (i = 0; i < vop2_data->nr_vps; i++) { + vp = &vop2->vps[i]; + + if (!vp->crtc.port) + continue; + + plane = &vp->primary_plane->base; + + ret = drm_crtc_init_with_planes(drm, &vp->crtc, plane, NULL, + &vop2_crtc_funcs, + "video_port%d", vp->id); + if (ret) { + drm_err(vop2->drm, "crtc init for video_port%d failed\n", i); + return ret; + } + + drm_crtc_helper_add(&vp->crtc, &vop2_crtc_helper_funcs); + + init_completion(&vp->dsp_hold_completion); + } + + /* + * On the VOP2 it's very hard to change the number of layers on a VP + * during runtime, so we distribute the layers equally over the used + * VPs + */ + for (i = 0; i < vop2->data->nr_vps; i++) { + struct vop2_video_port *vp = &vop2->vps[i]; + + if (vp->crtc.port) + vp->nlayers = NR_LAYERS / nvps; + } + + return 0; +} + +static void vop2_destroy_crtc(struct drm_crtc *crtc) +{ + of_node_put(crtc->port); + + /* + * Destroy CRTC after vop2_plane_destroy() since vop2_disable_plane() + * references the CRTC. + */ + drm_crtc_cleanup(crtc); +} + +static struct reg_field vop2_cluster_regs[VOP2_WIN_MAX_REG] = { + [VOP2_WIN_ENABLE] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 0, 0), + [VOP2_WIN_FORMAT] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 1, 5), + [VOP2_WIN_RB_SWAP] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 14, 14), + [VOP2_WIN_DITHER_UP] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 18, 18), + [VOP2_WIN_ACT_INFO] = REG_FIELD(RK3568_CLUSTER_WIN_ACT_INFO, 0, 31), + [VOP2_WIN_DSP_INFO] = REG_FIELD(RK3568_CLUSTER_WIN_DSP_INFO, 0, 31), + [VOP2_WIN_DSP_ST] = REG_FIELD(RK3568_CLUSTER_WIN_DSP_ST, 0, 31), + [VOP2_WIN_YRGB_MST] = REG_FIELD(RK3568_CLUSTER_WIN_YRGB_MST, 0, 31), + [VOP2_WIN_UV_MST] = REG_FIELD(RK3568_CLUSTER_WIN_CBR_MST, 0, 31), + [VOP2_WIN_YUV_CLIP] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 19, 19), + [VOP2_WIN_YRGB_VIR] = REG_FIELD(RK3568_CLUSTER_WIN_VIR, 0, 15), + [VOP2_WIN_UV_VIR] = REG_FIELD(RK3568_CLUSTER_WIN_VIR, 16, 31), + [VOP2_WIN_Y2R_EN] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 8, 8), + [VOP2_WIN_R2Y_EN] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 9, 9), + [VOP2_WIN_CSC_MODE] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL0, 10, 11), + + /* Scale */ + [VOP2_WIN_SCALE_YRGB_X] = REG_FIELD(RK3568_CLUSTER_WIN_SCL_FACTOR_YRGB, 0, 15), + [VOP2_WIN_SCALE_YRGB_Y] = REG_FIELD(RK3568_CLUSTER_WIN_SCL_FACTOR_YRGB, 16, 31), + [VOP2_WIN_YRGB_VER_SCL_MODE] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 14, 15), + [VOP2_WIN_YRGB_HOR_SCL_MODE] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 12, 13), + [VOP2_WIN_BIC_COE_SEL] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 2, 3), + [VOP2_WIN_VSD_YRGB_GT2] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 28, 28), + [VOP2_WIN_VSD_YRGB_GT4] = REG_FIELD(RK3568_CLUSTER_WIN_CTRL1, 29, 29), + + /* cluster regs */ + [VOP2_WIN_AFBC_ENABLE] = REG_FIELD(RK3568_CLUSTER_CTRL, 1, 1), + [VOP2_WIN_CLUSTER_ENABLE] = REG_FIELD(RK3568_CLUSTER_CTRL, 0, 0), + [VOP2_WIN_CLUSTER_LB_MODE] = REG_FIELD(RK3568_CLUSTER_CTRL, 4, 7), + + /* afbc regs */ + [VOP2_WIN_AFBC_FORMAT] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 2, 6), + [VOP2_WIN_AFBC_RB_SWAP] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 9, 9), + [VOP2_WIN_AFBC_UV_SWAP] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 10, 10), + [VOP2_WIN_AFBC_AUTO_GATING_EN] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_OUTPUT_CTRL, 4, 4), + [VOP2_WIN_AFBC_HALF_BLOCK_EN] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 7, 7), + [VOP2_WIN_AFBC_BLOCK_SPLIT_EN] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_CTRL, 8, 8), + [VOP2_WIN_AFBC_HDR_PTR] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_HDR_PTR, 0, 31), + [VOP2_WIN_AFBC_PIC_SIZE] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_PIC_SIZE, 0, 31), + [VOP2_WIN_AFBC_PIC_VIR_WIDTH] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_VIR_WIDTH, 0, 15), + [VOP2_WIN_AFBC_TILE_NUM] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_VIR_WIDTH, 16, 31), + [VOP2_WIN_AFBC_PIC_OFFSET] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_PIC_OFFSET, 0, 31), + [VOP2_WIN_AFBC_DSP_OFFSET] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_DSP_OFFSET, 0, 31), + [VOP2_WIN_AFBC_TRANSFORM_OFFSET] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_TRANSFORM_OFFSET, 0, 31), + [VOP2_WIN_AFBC_ROTATE_90] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE, 0, 0), + [VOP2_WIN_AFBC_ROTATE_270] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE, 1, 1), + [VOP2_WIN_XMIRROR] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE, 2, 2), + [VOP2_WIN_YMIRROR] = REG_FIELD(RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE, 3, 3), + [VOP2_WIN_UV_SWAP] = { .reg = 0xffffffff }, + [VOP2_WIN_COLOR_KEY] = { .reg = 0xffffffff }, + [VOP2_WIN_COLOR_KEY_EN] = { .reg = 0xffffffff }, + [VOP2_WIN_SCALE_CBCR_X] = { .reg = 0xffffffff }, + [VOP2_WIN_SCALE_CBCR_Y] = { .reg = 0xffffffff }, + [VOP2_WIN_YRGB_HSCL_FILTER_MODE] = { .reg = 0xffffffff }, + [VOP2_WIN_YRGB_VSCL_FILTER_MODE] = { .reg = 0xffffffff }, + [VOP2_WIN_CBCR_VER_SCL_MODE] = { .reg = 0xffffffff }, + [VOP2_WIN_CBCR_HSCL_FILTER_MODE] = { .reg = 0xffffffff }, + [VOP2_WIN_CBCR_HOR_SCL_MODE] = { .reg = 0xffffffff }, + [VOP2_WIN_CBCR_VSCL_FILTER_MODE] = { .reg = 0xffffffff }, + [VOP2_WIN_VSD_CBCR_GT2] = { .reg = 0xffffffff }, + [VOP2_WIN_VSD_CBCR_GT4] = { .reg = 0xffffffff }, +}; + +static int vop2_cluster_init(struct vop2_win *win) +{ + struct vop2 *vop2 = win->vop2; + struct reg_field *cluster_regs; + int ret, i; + + cluster_regs = kmemdup(vop2_cluster_regs, sizeof(vop2_cluster_regs), + GFP_KERNEL); + if (!cluster_regs) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(vop2_cluster_regs); i++) + if (cluster_regs[i].reg != 0xffffffff) + cluster_regs[i].reg += win->offset; + + ret = devm_regmap_field_bulk_alloc(vop2->dev, vop2->map, win->reg, + cluster_regs, + ARRAY_SIZE(vop2_cluster_regs)); + + kfree(cluster_regs); + + return ret; +}; + +static struct reg_field vop2_esmart_regs[VOP2_WIN_MAX_REG] = { + [VOP2_WIN_ENABLE] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 0, 0), + [VOP2_WIN_FORMAT] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 1, 5), + [VOP2_WIN_DITHER_UP] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 12, 12), + [VOP2_WIN_RB_SWAP] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 14, 14), + [VOP2_WIN_UV_SWAP] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 16, 16), + [VOP2_WIN_ACT_INFO] = REG_FIELD(RK3568_SMART_REGION0_ACT_INFO, 0, 31), + [VOP2_WIN_DSP_INFO] = REG_FIELD(RK3568_SMART_REGION0_DSP_INFO, 0, 31), + [VOP2_WIN_DSP_ST] = REG_FIELD(RK3568_SMART_REGION0_DSP_ST, 0, 28), + [VOP2_WIN_YRGB_MST] = REG_FIELD(RK3568_SMART_REGION0_YRGB_MST, 0, 31), + [VOP2_WIN_UV_MST] = REG_FIELD(RK3568_SMART_REGION0_CBR_MST, 0, 31), + [VOP2_WIN_YUV_CLIP] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 17, 17), + [VOP2_WIN_YRGB_VIR] = REG_FIELD(RK3568_SMART_REGION0_VIR, 0, 15), + [VOP2_WIN_UV_VIR] = REG_FIELD(RK3568_SMART_REGION0_VIR, 16, 31), + [VOP2_WIN_Y2R_EN] = REG_FIELD(RK3568_SMART_CTRL0, 0, 0), + [VOP2_WIN_R2Y_EN] = REG_FIELD(RK3568_SMART_CTRL0, 1, 1), + [VOP2_WIN_CSC_MODE] = REG_FIELD(RK3568_SMART_CTRL0, 2, 3), + [VOP2_WIN_YMIRROR] = REG_FIELD(RK3568_SMART_CTRL1, 31, 31), + [VOP2_WIN_COLOR_KEY] = REG_FIELD(RK3568_SMART_COLOR_KEY_CTRL, 0, 29), + [VOP2_WIN_COLOR_KEY_EN] = REG_FIELD(RK3568_SMART_COLOR_KEY_CTRL, 31, 31), + + /* Scale */ + [VOP2_WIN_SCALE_YRGB_X] = REG_FIELD(RK3568_SMART_REGION0_SCL_FACTOR_YRGB, 0, 15), + [VOP2_WIN_SCALE_YRGB_Y] = REG_FIELD(RK3568_SMART_REGION0_SCL_FACTOR_YRGB, 16, 31), + [VOP2_WIN_SCALE_CBCR_X] = REG_FIELD(RK3568_SMART_REGION0_SCL_FACTOR_CBR, 0, 15), + [VOP2_WIN_SCALE_CBCR_Y] = REG_FIELD(RK3568_SMART_REGION0_SCL_FACTOR_CBR, 16, 31), + [VOP2_WIN_YRGB_HOR_SCL_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 0, 1), + [VOP2_WIN_YRGB_HSCL_FILTER_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 2, 3), + [VOP2_WIN_YRGB_VER_SCL_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 4, 5), + [VOP2_WIN_YRGB_VSCL_FILTER_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 6, 7), + [VOP2_WIN_CBCR_HOR_SCL_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 8, 9), + [VOP2_WIN_CBCR_HSCL_FILTER_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 10, 11), + [VOP2_WIN_CBCR_VER_SCL_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 12, 13), + [VOP2_WIN_CBCR_VSCL_FILTER_MODE] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 14, 15), + [VOP2_WIN_BIC_COE_SEL] = REG_FIELD(RK3568_SMART_REGION0_SCL_CTRL, 16, 17), + [VOP2_WIN_VSD_YRGB_GT2] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 8, 8), + [VOP2_WIN_VSD_YRGB_GT4] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 9, 9), + [VOP2_WIN_VSD_CBCR_GT2] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 10, 10), + [VOP2_WIN_VSD_CBCR_GT4] = REG_FIELD(RK3568_SMART_REGION0_CTRL, 11, 11), + [VOP2_WIN_XMIRROR] = { .reg = 0xffffffff }, + [VOP2_WIN_CLUSTER_ENABLE] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_ENABLE] = { .reg = 0xffffffff }, + [VOP2_WIN_CLUSTER_LB_MODE] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_FORMAT] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_RB_SWAP] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_UV_SWAP] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_AUTO_GATING_EN] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_BLOCK_SPLIT_EN] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_PIC_VIR_WIDTH] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_TILE_NUM] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_PIC_OFFSET] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_PIC_SIZE] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_DSP_OFFSET] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_TRANSFORM_OFFSET] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_HDR_PTR] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_HALF_BLOCK_EN] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_ROTATE_270] = { .reg = 0xffffffff }, + [VOP2_WIN_AFBC_ROTATE_90] = { .reg = 0xffffffff }, +}; + +static int vop2_esmart_init(struct vop2_win *win) +{ + struct vop2 *vop2 = win->vop2; + struct reg_field *esmart_regs; + int ret, i; + + esmart_regs = kmemdup(vop2_esmart_regs, sizeof(vop2_esmart_regs), + GFP_KERNEL); + if (!esmart_regs) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(vop2_esmart_regs); i++) + if (esmart_regs[i].reg != 0xffffffff) + esmart_regs[i].reg += win->offset; + + ret = devm_regmap_field_bulk_alloc(vop2->dev, vop2->map, win->reg, + esmart_regs, + ARRAY_SIZE(vop2_esmart_regs)); + + kfree(esmart_regs); + + return ret; +}; + +static int vop2_win_init(struct vop2 *vop2) +{ + const struct vop2_data *vop2_data = vop2->data; + struct vop2_win *win; + int i, ret; + + for (i = 0; i < vop2_data->win_size; i++) { + const struct vop2_win_data *win_data = &vop2_data->win[i]; + + win = &vop2->win[i]; + win->data = win_data; + win->type = win_data->type; + win->offset = win_data->base; + win->win_id = i; + win->vop2 = vop2; + if (vop2_cluster_window(win)) + ret = vop2_cluster_init(win); + else + ret = vop2_esmart_init(win); + if (ret) + return ret; + } + + vop2->registered_num_wins = vop2_data->win_size; + + return 0; +} + +/* + * The window registers are only updated when config done is written. + * Until that they read back the old value. As we read-modify-write + * these registers mark them as non-volatile. This makes sure we read + * the new values from the regmap register cache. + */ +static const struct regmap_range vop2_nonvolatile_range[] = { + regmap_reg_range(0x1000, 0x23ff), +}; + +static const struct regmap_access_table vop2_volatile_table = { + .no_ranges = vop2_nonvolatile_range, + .n_no_ranges = ARRAY_SIZE(vop2_nonvolatile_range), +}; + +static const struct regmap_config vop2_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x3000, + .name = "vop2", + .volatile_table = &vop2_volatile_table, + .cache_type = REGCACHE_RBTREE, +}; + +static int vop2_bind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + const struct vop2_data *vop2_data; + struct drm_device *drm = data; + struct vop2 *vop2; + struct resource *res; + size_t alloc_size; + int ret; + + vop2_data = of_device_get_match_data(dev); + if (!vop2_data) + return -ENODEV; + + /* Allocate vop2 struct and its vop2_win array */ + alloc_size = sizeof(*vop2) + sizeof(*vop2->win) * vop2_data->win_size; + vop2 = devm_kzalloc(dev, alloc_size, GFP_KERNEL); + if (!vop2) + return -ENOMEM; + + vop2->dev = dev; + vop2->data = vop2_data; + vop2->drm = drm; + + dev_set_drvdata(dev, vop2); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); + if (!res) { + drm_err(vop2->drm, "failed to get vop2 register byname\n"); + return -EINVAL; + } + + vop2->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(vop2->regs)) + return PTR_ERR(vop2->regs); + vop2->len = resource_size(res); + + vop2->map = devm_regmap_init_mmio(dev, vop2->regs, &vop2_regmap_config); + + ret = vop2_win_init(vop2); + if (ret) + return ret; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gamma_lut"); + if (res) { + vop2->lut_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(vop2->lut_regs)) + return PTR_ERR(vop2->lut_regs); + } + + vop2->grf = syscon_regmap_lookup_by_phandle(dev->of_node, "rockchip,grf"); + + vop2->hclk = devm_clk_get(vop2->dev, "hclk"); + if (IS_ERR(vop2->hclk)) { + drm_err(vop2->drm, "failed to get hclk source\n"); + return PTR_ERR(vop2->hclk); + } + + vop2->aclk = devm_clk_get(vop2->dev, "aclk"); + if (IS_ERR(vop2->aclk)) { + drm_err(vop2->drm, "failed to get aclk source\n"); + return PTR_ERR(vop2->aclk); + } + + vop2->irq = platform_get_irq(pdev, 0); + if (vop2->irq < 0) { + drm_err(vop2->drm, "cannot find irq for vop2\n"); + return vop2->irq; + } + + mutex_init(&vop2->vop2_lock); + + ret = devm_request_irq(dev, vop2->irq, vop2_isr, IRQF_SHARED, dev_name(dev), vop2); + if (ret) + return ret; + + ret = vop2_create_crtc(vop2); + if (ret) + return ret; + + rockchip_drm_dma_init_device(vop2->drm, vop2->dev); + + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static void vop2_unbind(struct device *dev, struct device *master, void *data) +{ + struct vop2 *vop2 = dev_get_drvdata(dev); + struct drm_device *drm = vop2->drm; + struct list_head *plane_list = &drm->mode_config.plane_list; + struct list_head *crtc_list = &drm->mode_config.crtc_list; + struct drm_crtc *crtc, *tmpc; + struct drm_plane *plane, *tmpp; + + pm_runtime_disable(dev); + + list_for_each_entry_safe(plane, tmpp, plane_list, head) + drm_plane_cleanup(plane); + + list_for_each_entry_safe(crtc, tmpc, crtc_list, head) + vop2_destroy_crtc(crtc); +} + +const struct component_ops vop2_component_ops = { + .bind = vop2_bind, + .unbind = vop2_unbind, +}; +EXPORT_SYMBOL_GPL(vop2_component_ops); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h new file mode 100644 index 000000000000..c727093a06d6 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_vop2.h @@ -0,0 +1,477 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:Mark Yao + */ + +#ifndef _ROCKCHIP_DRM_VOP2_H +#define _ROCKCHIP_DRM_VOP2_H + +#include "rockchip_drm_vop.h" + +#include +#include + +#define VOP_FEATURE_OUTPUT_10BIT BIT(0) + +#define WIN_FEATURE_AFBDC BIT(0) +#define WIN_FEATURE_CLUSTER BIT(1) + +/* + * the delay number of a window in different mode. + */ +enum win_dly_mode { + VOP2_DLY_MODE_DEFAULT, /**< default mode */ + VOP2_DLY_MODE_HISO_S, /** HDR in SDR out mode, as a SDR window */ + VOP2_DLY_MODE_HIHO_H, /** HDR in HDR out mode, as a HDR window */ + VOP2_DLY_MODE_MAX, +}; + +struct vop_rect { + int width; + int height; +}; + +enum vop2_scale_up_mode { + VOP2_SCALE_UP_NRST_NBOR, + VOP2_SCALE_UP_BIL, + VOP2_SCALE_UP_BIC, +}; + +enum vop2_scale_down_mode { + VOP2_SCALE_DOWN_NRST_NBOR, + VOP2_SCALE_DOWN_BIL, + VOP2_SCALE_DOWN_AVG, +}; + +enum vop2_win_regs { + VOP2_WIN_ENABLE, + VOP2_WIN_FORMAT, + VOP2_WIN_CSC_MODE, + VOP2_WIN_XMIRROR, + VOP2_WIN_YMIRROR, + VOP2_WIN_RB_SWAP, + VOP2_WIN_UV_SWAP, + VOP2_WIN_ACT_INFO, + VOP2_WIN_DSP_INFO, + VOP2_WIN_DSP_ST, + VOP2_WIN_YRGB_MST, + VOP2_WIN_UV_MST, + VOP2_WIN_YRGB_VIR, + VOP2_WIN_UV_VIR, + VOP2_WIN_YUV_CLIP, + VOP2_WIN_Y2R_EN, + VOP2_WIN_R2Y_EN, + VOP2_WIN_COLOR_KEY, + VOP2_WIN_COLOR_KEY_EN, + VOP2_WIN_DITHER_UP, + + /* scale regs */ + VOP2_WIN_SCALE_YRGB_X, + VOP2_WIN_SCALE_YRGB_Y, + VOP2_WIN_SCALE_CBCR_X, + VOP2_WIN_SCALE_CBCR_Y, + VOP2_WIN_YRGB_HOR_SCL_MODE, + VOP2_WIN_YRGB_HSCL_FILTER_MODE, + VOP2_WIN_YRGB_VER_SCL_MODE, + VOP2_WIN_YRGB_VSCL_FILTER_MODE, + VOP2_WIN_CBCR_VER_SCL_MODE, + VOP2_WIN_CBCR_HSCL_FILTER_MODE, + VOP2_WIN_CBCR_HOR_SCL_MODE, + VOP2_WIN_CBCR_VSCL_FILTER_MODE, + VOP2_WIN_VSD_CBCR_GT2, + VOP2_WIN_VSD_CBCR_GT4, + VOP2_WIN_VSD_YRGB_GT2, + VOP2_WIN_VSD_YRGB_GT4, + VOP2_WIN_BIC_COE_SEL, + + /* cluster regs */ + VOP2_WIN_CLUSTER_ENABLE, + VOP2_WIN_AFBC_ENABLE, + VOP2_WIN_CLUSTER_LB_MODE, + + /* afbc regs */ + VOP2_WIN_AFBC_FORMAT, + VOP2_WIN_AFBC_RB_SWAP, + VOP2_WIN_AFBC_UV_SWAP, + VOP2_WIN_AFBC_AUTO_GATING_EN, + VOP2_WIN_AFBC_BLOCK_SPLIT_EN, + VOP2_WIN_AFBC_PIC_VIR_WIDTH, + VOP2_WIN_AFBC_TILE_NUM, + VOP2_WIN_AFBC_PIC_OFFSET, + VOP2_WIN_AFBC_PIC_SIZE, + VOP2_WIN_AFBC_DSP_OFFSET, + VOP2_WIN_AFBC_TRANSFORM_OFFSET, + VOP2_WIN_AFBC_HDR_PTR, + VOP2_WIN_AFBC_HALF_BLOCK_EN, + VOP2_WIN_AFBC_ROTATE_270, + VOP2_WIN_AFBC_ROTATE_90, + VOP2_WIN_MAX_REG, +}; + +struct vop2_win_data { + const char *name; + unsigned int phys_id; + + u32 base; + enum drm_plane_type type; + + u32 nformats; + const u32 *formats; + const uint64_t *format_modifiers; + const unsigned int supported_rotations; + + /** + * @layer_sel_id: defined by register OVERLAY_LAYER_SEL of VOP2 + */ + unsigned int layer_sel_id; + uint64_t feature; + + unsigned int max_upscale_factor; + unsigned int max_downscale_factor; + const u8 dly[VOP2_DLY_MODE_MAX]; +}; + +struct vop2_video_port_data { + unsigned int id; + u32 feature; + u16 gamma_lut_len; + u16 cubic_lut_len; + struct vop_rect max_output; + const u8 pre_scan_max_dly[4]; + const struct vop2_video_port_regs *regs; + unsigned int offset; +}; + +struct vop2_data { + u8 nr_vps; + const struct vop2_ctrl *ctrl; + const struct vop2_win_data *win; + const struct vop2_video_port_data *vp; + const struct vop_csc_table *csc_table; + struct vop_rect max_input; + struct vop_rect max_output; + + unsigned int win_size; + unsigned int soc_id; +}; + +/* interrupt define */ +#define FS_NEW_INTR BIT(4) +#define ADDR_SAME_INTR BIT(5) +#define LINE_FLAG1_INTR BIT(6) +#define WIN0_EMPTY_INTR BIT(7) +#define WIN1_EMPTY_INTR BIT(8) +#define WIN2_EMPTY_INTR BIT(9) +#define WIN3_EMPTY_INTR BIT(10) +#define HWC_EMPTY_INTR BIT(11) +#define POST_BUF_EMPTY_INTR BIT(12) +#define PWM_GEN_INTR BIT(13) +#define DMA_FINISH_INTR BIT(14) +#define FS_FIELD_INTR BIT(15) +#define FE_INTR BIT(16) +#define WB_UV_FIFO_FULL_INTR BIT(17) +#define WB_YRGB_FIFO_FULL_INTR BIT(18) +#define WB_COMPLETE_INTR BIT(19) + +/* + * display output interface supported by rockchip lcdc + */ +#define ROCKCHIP_OUT_MODE_P888 0 +#define ROCKCHIP_OUT_MODE_BT1120 0 +#define ROCKCHIP_OUT_MODE_P666 1 +#define ROCKCHIP_OUT_MODE_P565 2 +#define ROCKCHIP_OUT_MODE_BT656 5 +#define ROCKCHIP_OUT_MODE_S888 8 +#define ROCKCHIP_OUT_MODE_S888_DUMMY 12 +#define ROCKCHIP_OUT_MODE_YUV420 14 +/* for use special outface */ +#define ROCKCHIP_OUT_MODE_AAAA 15 + +enum vop_csc_format { + CSC_BT601L, + CSC_BT709L, + CSC_BT601F, + CSC_BT2020, +}; + +enum src_factor_mode { + SRC_FAC_ALPHA_ZERO, + SRC_FAC_ALPHA_ONE, + SRC_FAC_ALPHA_DST, + SRC_FAC_ALPHA_DST_INVERSE, + SRC_FAC_ALPHA_SRC, + SRC_FAC_ALPHA_SRC_GLOBAL, +}; + +enum dst_factor_mode { + DST_FAC_ALPHA_ZERO, + DST_FAC_ALPHA_ONE, + DST_FAC_ALPHA_SRC, + DST_FAC_ALPHA_SRC_INVERSE, + DST_FAC_ALPHA_DST, + DST_FAC_ALPHA_DST_GLOBAL, +}; + +#define RK3568_GRF_VO_CON1 0x0364 +/* System registers definition */ +#define RK3568_REG_CFG_DONE 0x000 +#define RK3568_VERSION_INFO 0x004 +#define RK3568_SYS_AUTO_GATING_CTRL 0x008 +#define RK3568_SYS_AXI_LUT_CTRL 0x024 +#define RK3568_DSP_IF_EN 0x028 +#define RK3568_DSP_IF_CTRL 0x02c +#define RK3568_DSP_IF_POL 0x030 +#define RK3568_WB_CTRL 0x40 +#define RK3568_WB_XSCAL_FACTOR 0x44 +#define RK3568_WB_YRGB_MST 0x48 +#define RK3568_WB_CBR_MST 0x4C +#define RK3568_OTP_WIN_EN 0x050 +#define RK3568_LUT_PORT_SEL 0x058 +#define RK3568_SYS_STATUS0 0x060 +#define RK3568_VP_LINE_FLAG(vp) (0x70 + (vp) * 0x4) +#define RK3568_SYS0_INT_EN 0x80 +#define RK3568_SYS0_INT_CLR 0x84 +#define RK3568_SYS0_INT_STATUS 0x88 +#define RK3568_SYS1_INT_EN 0x90 +#define RK3568_SYS1_INT_CLR 0x94 +#define RK3568_SYS1_INT_STATUS 0x98 +#define RK3568_VP_INT_EN(vp) (0xA0 + (vp) * 0x10) +#define RK3568_VP_INT_CLR(vp) (0xA4 + (vp) * 0x10) +#define RK3568_VP_INT_STATUS(vp) (0xA8 + (vp) * 0x10) +#define RK3568_VP_INT_RAW_STATUS(vp) (0xAC + (vp) * 0x10) + +/* Video Port registers definition */ +#define RK3568_VP_DSP_CTRL 0x00 +#define RK3568_VP_MIPI_CTRL 0x04 +#define RK3568_VP_COLOR_BAR_CTRL 0x08 +#define RK3568_VP_3D_LUT_CTRL 0x10 +#define RK3568_VP_3D_LUT_MST 0x20 +#define RK3568_VP_DSP_BG 0x2C +#define RK3568_VP_PRE_SCAN_HTIMING 0x30 +#define RK3568_VP_POST_DSP_HACT_INFO 0x34 +#define RK3568_VP_POST_DSP_VACT_INFO 0x38 +#define RK3568_VP_POST_SCL_FACTOR_YRGB 0x3C +#define RK3568_VP_POST_SCL_CTRL 0x40 +#define RK3568_VP_POST_DSP_VACT_INFO_F1 0x44 +#define RK3568_VP_DSP_HTOTAL_HS_END 0x48 +#define RK3568_VP_DSP_HACT_ST_END 0x4C +#define RK3568_VP_DSP_VTOTAL_VS_END 0x50 +#define RK3568_VP_DSP_VACT_ST_END 0x54 +#define RK3568_VP_DSP_VS_ST_END_F1 0x58 +#define RK3568_VP_DSP_VACT_ST_END_F1 0x5C +#define RK3568_VP_BCSH_CTRL 0x60 +#define RK3568_VP_BCSH_BCS 0x64 +#define RK3568_VP_BCSH_H 0x68 +#define RK3568_VP_BCSH_COLOR_BAR 0x6C + +/* Overlay registers definition */ +#define RK3568_OVL_CTRL 0x600 +#define RK3568_OVL_LAYER_SEL 0x604 +#define RK3568_OVL_PORT_SEL 0x608 +#define RK3568_CLUSTER0_MIX_SRC_COLOR_CTRL 0x610 +#define RK3568_CLUSTER0_MIX_DST_COLOR_CTRL 0x614 +#define RK3568_CLUSTER0_MIX_SRC_ALPHA_CTRL 0x618 +#define RK3568_CLUSTER0_MIX_DST_ALPHA_CTRL 0x61C +#define RK3568_MIX0_SRC_COLOR_CTRL 0x650 +#define RK3568_MIX0_DST_COLOR_CTRL 0x654 +#define RK3568_MIX0_SRC_ALPHA_CTRL 0x658 +#define RK3568_MIX0_DST_ALPHA_CTRL 0x65C +#define RK3568_HDR0_SRC_COLOR_CTRL 0x6C0 +#define RK3568_HDR0_DST_COLOR_CTRL 0x6C4 +#define RK3568_HDR0_SRC_ALPHA_CTRL 0x6C8 +#define RK3568_HDR0_DST_ALPHA_CTRL 0x6CC +#define RK3568_VP_BG_MIX_CTRL(vp) (0x6E0 + (vp) * 4) +#define RK3568_CLUSTER_DLY_NUM 0x6F0 +#define RK3568_SMART_DLY_NUM 0x6F8 + +/* Cluster register definition, offset relative to window base */ +#define RK3568_CLUSTER_WIN_CTRL0 0x00 +#define RK3568_CLUSTER_WIN_CTRL1 0x04 +#define RK3568_CLUSTER_WIN_YRGB_MST 0x10 +#define RK3568_CLUSTER_WIN_CBR_MST 0x14 +#define RK3568_CLUSTER_WIN_VIR 0x18 +#define RK3568_CLUSTER_WIN_ACT_INFO 0x20 +#define RK3568_CLUSTER_WIN_DSP_INFO 0x24 +#define RK3568_CLUSTER_WIN_DSP_ST 0x28 +#define RK3568_CLUSTER_WIN_SCL_FACTOR_YRGB 0x30 +#define RK3568_CLUSTER_WIN_AFBCD_TRANSFORM_OFFSET 0x3C +#define RK3568_CLUSTER_WIN_AFBCD_OUTPUT_CTRL 0x50 +#define RK3568_CLUSTER_WIN_AFBCD_ROTATE_MODE 0x54 +#define RK3568_CLUSTER_WIN_AFBCD_HDR_PTR 0x58 +#define RK3568_CLUSTER_WIN_AFBCD_VIR_WIDTH 0x5C +#define RK3568_CLUSTER_WIN_AFBCD_PIC_SIZE 0x60 +#define RK3568_CLUSTER_WIN_AFBCD_PIC_OFFSET 0x64 +#define RK3568_CLUSTER_WIN_AFBCD_DSP_OFFSET 0x68 +#define RK3568_CLUSTER_WIN_AFBCD_CTRL 0x6C + +#define RK3568_CLUSTER_CTRL 0x100 + +/* (E)smart register definition, offset relative to window base */ +#define RK3568_SMART_CTRL0 0x00 +#define RK3568_SMART_CTRL1 0x04 +#define RK3568_SMART_REGION0_CTRL 0x10 +#define RK3568_SMART_REGION0_YRGB_MST 0x14 +#define RK3568_SMART_REGION0_CBR_MST 0x18 +#define RK3568_SMART_REGION0_VIR 0x1C +#define RK3568_SMART_REGION0_ACT_INFO 0x20 +#define RK3568_SMART_REGION0_DSP_INFO 0x24 +#define RK3568_SMART_REGION0_DSP_ST 0x28 +#define RK3568_SMART_REGION0_SCL_CTRL 0x30 +#define RK3568_SMART_REGION0_SCL_FACTOR_YRGB 0x34 +#define RK3568_SMART_REGION0_SCL_FACTOR_CBR 0x38 +#define RK3568_SMART_REGION0_SCL_OFFSET 0x3C +#define RK3568_SMART_REGION1_CTRL 0x40 +#define RK3568_SMART_REGION1_YRGB_MST 0x44 +#define RK3568_SMART_REGION1_CBR_MST 0x48 +#define RK3568_SMART_REGION1_VIR 0x4C +#define RK3568_SMART_REGION1_ACT_INFO 0x50 +#define RK3568_SMART_REGION1_DSP_INFO 0x54 +#define RK3568_SMART_REGION1_DSP_ST 0x58 +#define RK3568_SMART_REGION1_SCL_CTRL 0x60 +#define RK3568_SMART_REGION1_SCL_FACTOR_YRGB 0x64 +#define RK3568_SMART_REGION1_SCL_FACTOR_CBR 0x68 +#define RK3568_SMART_REGION1_SCL_OFFSET 0x6C +#define RK3568_SMART_REGION2_CTRL 0x70 +#define RK3568_SMART_REGION2_YRGB_MST 0x74 +#define RK3568_SMART_REGION2_CBR_MST 0x78 +#define RK3568_SMART_REGION2_VIR 0x7C +#define RK3568_SMART_REGION2_ACT_INFO 0x80 +#define RK3568_SMART_REGION2_DSP_INFO 0x84 +#define RK3568_SMART_REGION2_DSP_ST 0x88 +#define RK3568_SMART_REGION2_SCL_CTRL 0x90 +#define RK3568_SMART_REGION2_SCL_FACTOR_YRGB 0x94 +#define RK3568_SMART_REGION2_SCL_FACTOR_CBR 0x98 +#define RK3568_SMART_REGION2_SCL_OFFSET 0x9C +#define RK3568_SMART_REGION3_CTRL 0xA0 +#define RK3568_SMART_REGION3_YRGB_MST 0xA4 +#define RK3568_SMART_REGION3_CBR_MST 0xA8 +#define RK3568_SMART_REGION3_VIR 0xAC +#define RK3568_SMART_REGION3_ACT_INFO 0xB0 +#define RK3568_SMART_REGION3_DSP_INFO 0xB4 +#define RK3568_SMART_REGION3_DSP_ST 0xB8 +#define RK3568_SMART_REGION3_SCL_CTRL 0xC0 +#define RK3568_SMART_REGION3_SCL_FACTOR_YRGB 0xC4 +#define RK3568_SMART_REGION3_SCL_FACTOR_CBR 0xC8 +#define RK3568_SMART_REGION3_SCL_OFFSET 0xCC +#define RK3568_SMART_COLOR_KEY_CTRL 0xD0 + +/* HDR register definition */ +#define RK3568_HDR_LUT_CTRL 0x2000 +#define RK3568_HDR_LUT_MST 0x2004 +#define RK3568_SDR2HDR_CTRL 0x2010 +#define RK3568_HDR2SDR_CTRL 0x2020 +#define RK3568_HDR2SDR_SRC_RANGE 0x2024 +#define RK3568_HDR2SDR_NORMFACEETF 0x2028 +#define RK3568_HDR2SDR_DST_RANGE 0x202C +#define RK3568_HDR2SDR_NORMFACCGAMMA 0x2030 +#define RK3568_HDR_EETF_OETF_Y0 0x203C +#define RK3568_HDR_SAT_Y0 0x20C0 +#define RK3568_HDR_EOTF_OETF_Y0 0x20F0 +#define RK3568_HDR_OETF_DX_POW1 0x2200 +#define RK3568_HDR_OETF_XN1 0x2300 + +#define RK3568_REG_CFG_DONE__GLB_CFG_DONE_EN BIT(15) + +#define RK3568_VP_DSP_CTRL__STANDBY BIT(31) +#define RK3568_VP_DSP_CTRL__DITHER_DOWN_MODE BIT(20) +#define RK3568_VP_DSP_CTRL__DITHER_DOWN_SEL GENMASK(19, 18) +#define RK3568_VP_DSP_CTRL__DITHER_DOWN_EN BIT(17) +#define RK3568_VP_DSP_CTRL__PRE_DITHER_DOWN_EN BIT(16) +#define RK3568_VP_DSP_CTRL__POST_DSP_OUT_R2Y BIT(15) +#define RK3568_VP_DSP_CTRL__DSP_RB_SWAP BIT(9) +#define RK3568_VP_DSP_CTRL__DSP_INTERLACE BIT(7) +#define RK3568_VP_DSP_CTRL__DSP_FILED_POL BIT(6) +#define RK3568_VP_DSP_CTRL__P2I_EN BIT(5) +#define RK3568_VP_DSP_CTRL__CORE_DCLK_DIV BIT(4) +#define RK3568_VP_DSP_CTRL__OUT_MODE GENMASK(3, 0) + +#define RK3568_VP_POST_SCL_CTRL__VSCALEDOWN BIT(1) +#define RK3568_VP_POST_SCL_CTRL__HSCALEDOWN BIT(0) + +#define RK3568_SYS_DSP_INFACE_EN_LVDS1_MUX GENMASK(26, 25) +#define RK3568_SYS_DSP_INFACE_EN_LVDS1 BIT(24) +#define RK3568_SYS_DSP_INFACE_EN_MIPI1_MUX GENMASK(22, 21) +#define RK3568_SYS_DSP_INFACE_EN_MIPI1 BIT(20) +#define RK3568_SYS_DSP_INFACE_EN_LVDS0_MUX GENMASK(19, 18) +#define RK3568_SYS_DSP_INFACE_EN_MIPI0_MUX GENMASK(17, 16) +#define RK3568_SYS_DSP_INFACE_EN_EDP_MUX GENMASK(15, 14) +#define RK3568_SYS_DSP_INFACE_EN_HDMI_MUX GENMASK(11, 10) +#define RK3568_SYS_DSP_INFACE_EN_RGB_MUX GENMASK(9, 8) +#define RK3568_SYS_DSP_INFACE_EN_LVDS0 BIT(5) +#define RK3568_SYS_DSP_INFACE_EN_MIPI0 BIT(4) +#define RK3568_SYS_DSP_INFACE_EN_EDP BIT(3) +#define RK3568_SYS_DSP_INFACE_EN_HDMI BIT(1) +#define RK3568_SYS_DSP_INFACE_EN_RGB BIT(0) + +#define RK3568_DSP_IF_POL__MIPI_PIN_POL GENMASK(19, 16) +#define RK3568_DSP_IF_POL__EDP_PIN_POL GENMASK(15, 12) +#define RK3568_DSP_IF_POL__HDMI_PIN_POL GENMASK(7, 4) +#define RK3568_DSP_IF_POL__RGB_LVDS_PIN_POL GENMASK(3, 0) + +#define RK3568_VP0_MIPI_CTRL__DCLK_DIV2_PHASE_LOCK BIT(5) +#define RK3568_VP0_MIPI_CTRL__DCLK_DIV2 BIT(4) + +#define RK3568_SYS_AUTO_GATING_CTRL__AUTO_GATING_EN BIT(31) + +#define RK3568_DSP_IF_POL__CFG_DONE_IMD BIT(28) + +#define VOP2_SYS_AXI_BUS_NUM 2 + +#define VOP2_CLUSTER_YUV444_10 0x12 + +#define VOP2_COLOR_KEY_MASK BIT(31) + +#define RK3568_OVL_CTRL__LAYERSEL_REGDONE_IMD BIT(28) + +#define RK3568_VP_BG_MIX_CTRL__BG_DLY GENMASK(31, 24) + +#define RK3568_OVL_PORT_SEL__SEL_PORT GENMASK(31, 16) +#define RK3568_OVL_PORT_SEL__SMART1 GENMASK(31, 30) +#define RK3568_OVL_PORT_SEL__SMART0 GENMASK(29, 28) +#define RK3568_OVL_PORT_SEL__ESMART1 GENMASK(27, 26) +#define RK3568_OVL_PORT_SEL__ESMART0 GENMASK(25, 24) +#define RK3568_OVL_PORT_SEL__CLUSTER1 GENMASK(19, 18) +#define RK3568_OVL_PORT_SEL__CLUSTER0 GENMASK(17, 16) +#define RK3568_OVL_PORT_SET__PORT2_MUX GENMASK(11, 8) +#define RK3568_OVL_PORT_SET__PORT1_MUX GENMASK(7, 4) +#define RK3568_OVL_PORT_SET__PORT0_MUX GENMASK(3, 0) +#define RK3568_OVL_LAYER_SEL__LAYER(layer, x) ((x) << ((layer) * 4)) + +#define RK3568_CLUSTER_DLY_NUM__CLUSTER1_1 GENMASK(31, 24) +#define RK3568_CLUSTER_DLY_NUM__CLUSTER1_0 GENMASK(23, 16) +#define RK3568_CLUSTER_DLY_NUM__CLUSTER0_1 GENMASK(15, 8) +#define RK3568_CLUSTER_DLY_NUM__CLUSTER0_0 GENMASK(7, 0) + +#define RK3568_SMART_DLY_NUM__SMART1 GENMASK(31, 24) +#define RK3568_SMART_DLY_NUM__SMART0 GENMASK(23, 16) +#define RK3568_SMART_DLY_NUM__ESMART1 GENMASK(15, 8) +#define RK3568_SMART_DLY_NUM__ESMART0 GENMASK(7, 0) + +#define VP_INT_DSP_HOLD_VALID BIT(6) +#define VP_INT_FS_FIELD BIT(5) +#define VP_INT_POST_BUF_EMPTY BIT(4) +#define VP_INT_LINE_FLAG1 BIT(3) +#define VP_INT_LINE_FLAG0 BIT(2) +#define VOP2_INT_BUS_ERRPR BIT(1) +#define VP_INT_FS BIT(0) + +#define POLFLAG_DCLK_INV BIT(3) + +enum vop2_layer_phy_id { + ROCKCHIP_VOP2_CLUSTER0 = 0, + ROCKCHIP_VOP2_CLUSTER1, + ROCKCHIP_VOP2_ESMART0, + ROCKCHIP_VOP2_ESMART1, + ROCKCHIP_VOP2_SMART0, + ROCKCHIP_VOP2_SMART1, + ROCKCHIP_VOP2_CLUSTER2, + ROCKCHIP_VOP2_CLUSTER3, + ROCKCHIP_VOP2_ESMART2, + ROCKCHIP_VOP2_ESMART3, + ROCKCHIP_VOP2_PHY_ID_INVALID = -1, +}; + +extern const struct component_ops vop2_component_ops; + +#endif /* _ROCKCHIP_DRM_VOP2_H */ diff --git a/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c new file mode 100644 index 000000000000..9bf0637bf8e2 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_vop2_reg.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) Rockchip Electronics Co.Ltd + * Author: Andy Yan + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rockchip_drm_vop2.h" + +static const uint32_t formats_win_full_10bit[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_NV12, + DRM_FORMAT_NV16, + DRM_FORMAT_NV24, +}; + +static const uint32_t formats_win_full_10bit_yuyv[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_NV12, + DRM_FORMAT_NV16, + DRM_FORMAT_NV24, + DRM_FORMAT_YVYU, + DRM_FORMAT_VYUY, +}; + +static const uint32_t formats_win_lite[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, +}; + +static const uint64_t format_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID, +}; + +static const uint64_t format_modifiers_afbc[] = { + DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16), + + DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | + AFBC_FORMAT_MOD_SPARSE), + + DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | + AFBC_FORMAT_MOD_YTR), + + DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | + AFBC_FORMAT_MOD_CBR), + + DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | + AFBC_FORMAT_MOD_YTR | + AFBC_FORMAT_MOD_SPARSE), + + DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | + AFBC_FORMAT_MOD_CBR | + AFBC_FORMAT_MOD_SPARSE), + + DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | + AFBC_FORMAT_MOD_YTR | + AFBC_FORMAT_MOD_CBR), + + DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | + AFBC_FORMAT_MOD_YTR | + AFBC_FORMAT_MOD_CBR | + AFBC_FORMAT_MOD_SPARSE), + + /* SPLIT mandates SPARSE, RGB modes mandates YTR */ + DRM_FORMAT_MOD_ARM_AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | + AFBC_FORMAT_MOD_YTR | + AFBC_FORMAT_MOD_SPARSE | + AFBC_FORMAT_MOD_SPLIT), + DRM_FORMAT_MOD_INVALID, +}; + +static const struct vop2_video_port_data rk3568_vop_video_ports[] = { + { + .id = 0, + .feature = VOP_FEATURE_OUTPUT_10BIT, + .gamma_lut_len = 1024, + .cubic_lut_len = 9 * 9 * 9, + .max_output = { 4096, 2304 }, + .pre_scan_max_dly = { 69, 53, 53, 42 }, + .offset = 0xc00, + }, { + .id = 1, + .gamma_lut_len = 1024, + .max_output = { 2048, 1536 }, + .pre_scan_max_dly = { 40, 40, 40, 40 }, + .offset = 0xd00, + }, { + .id = 2, + .gamma_lut_len = 1024, + .max_output = { 1920, 1080 }, + .pre_scan_max_dly = { 40, 40, 40, 40 }, + .offset = 0xe00, + }, +}; + +/* + * rk3568 vop with 2 cluster, 2 esmart win, 2 smart win. + * Every cluster can work as 4K win or split into two win. + * All win in cluster support AFBCD. + * + * Every esmart win and smart win support 4 Multi-region. + * + * Scale filter mode: + * + * * Cluster: bicubic for horizontal scale up, others use bilinear + * * ESmart: + * * nearest-neighbor/bilinear/bicubic for scale up + * * nearest-neighbor/bilinear/average for scale down + * + * + * @TODO describe the wind like cpu-map dt nodes; + */ +static const struct vop2_win_data rk3568_vop_win_data[] = { + { + .name = "Smart0-win0", + .phys_id = ROCKCHIP_VOP2_SMART0, + .base = 0x1c00, + .formats = formats_win_lite, + .nformats = ARRAY_SIZE(formats_win_lite), + .format_modifiers = format_modifiers, + .layer_sel_id = 3, + .supported_rotations = DRM_MODE_REFLECT_Y, + .type = DRM_PLANE_TYPE_PRIMARY, + .max_upscale_factor = 8, + .max_downscale_factor = 8, + .dly = { 20, 47, 41 }, + }, { + .name = "Smart1-win0", + .phys_id = ROCKCHIP_VOP2_SMART1, + .formats = formats_win_lite, + .nformats = ARRAY_SIZE(formats_win_lite), + .format_modifiers = format_modifiers, + .base = 0x1e00, + .layer_sel_id = 7, + .supported_rotations = DRM_MODE_REFLECT_Y, + .type = DRM_PLANE_TYPE_PRIMARY, + .max_upscale_factor = 8, + .max_downscale_factor = 8, + .dly = { 20, 47, 41 }, + }, { + .name = "Esmart1-win0", + .phys_id = ROCKCHIP_VOP2_ESMART1, + .formats = formats_win_full_10bit_yuyv, + .nformats = ARRAY_SIZE(formats_win_full_10bit_yuyv), + .format_modifiers = format_modifiers, + .base = 0x1a00, + .layer_sel_id = 6, + .supported_rotations = DRM_MODE_REFLECT_Y, + .type = DRM_PLANE_TYPE_PRIMARY, + .max_upscale_factor = 8, + .max_downscale_factor = 8, + .dly = { 20, 47, 41 }, + }, { + .name = "Esmart0-win0", + .phys_id = ROCKCHIP_VOP2_ESMART0, + .formats = formats_win_full_10bit_yuyv, + .nformats = ARRAY_SIZE(formats_win_full_10bit_yuyv), + .format_modifiers = format_modifiers, + .base = 0x1800, + .layer_sel_id = 2, + .supported_rotations = DRM_MODE_REFLECT_Y, + .type = DRM_PLANE_TYPE_OVERLAY, + .max_upscale_factor = 8, + .max_downscale_factor = 8, + .dly = { 20, 47, 41 }, + }, { + .name = "Cluster0-win0", + .phys_id = ROCKCHIP_VOP2_CLUSTER0, + .base = 0x1000, + .formats = formats_win_full_10bit, + .nformats = ARRAY_SIZE(formats_win_full_10bit), + .format_modifiers = format_modifiers_afbc, + .layer_sel_id = 0, + .supported_rotations = DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y, + .max_upscale_factor = 4, + .max_downscale_factor = 4, + .dly = { 0, 27, 21 }, + .type = DRM_PLANE_TYPE_OVERLAY, + .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER, + }, { + .name = "Cluster1-win0", + .phys_id = ROCKCHIP_VOP2_CLUSTER1, + .base = 0x1200, + .formats = formats_win_full_10bit, + .nformats = ARRAY_SIZE(formats_win_full_10bit), + .format_modifiers = format_modifiers_afbc, + .layer_sel_id = 1, + .supported_rotations = DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270 | + DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y, + .type = DRM_PLANE_TYPE_OVERLAY, + .max_upscale_factor = 4, + .max_downscale_factor = 4, + .dly = { 0, 27, 21 }, + .feature = WIN_FEATURE_AFBDC | WIN_FEATURE_CLUSTER, + }, +}; + +static const struct vop2_data rk3566_vop = { + .nr_vps = 3, + .max_input = { 4096, 2304 }, + .max_output = { 4096, 2304 }, + .vp = rk3568_vop_video_ports, + .win = rk3568_vop_win_data, + .win_size = ARRAY_SIZE(rk3568_vop_win_data), + .soc_id = 3566, +}; + +static const struct vop2_data rk3568_vop = { + .nr_vps = 3, + .max_input = { 4096, 2304 }, + .max_output = { 4096, 2304 }, + .vp = rk3568_vop_video_ports, + .win = rk3568_vop_win_data, + .win_size = ARRAY_SIZE(rk3568_vop_win_data), + .soc_id = 3568, +}; + +static const struct of_device_id vop2_dt_match[] = { + { + .compatible = "rockchip,rk3566-vop", + .data = &rk3566_vop, + }, { + .compatible = "rockchip,rk3568-vop", + .data = &rk3568_vop, + }, { + }, +}; +MODULE_DEVICE_TABLE(of, vop2_dt_match); + +static int vop2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + return component_add(dev, &vop2_component_ops); +} + +static int vop2_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vop2_component_ops); + + return 0; +} + +struct platform_driver vop2_platform_driver = { + .probe = vop2_probe, + .remove = vop2_remove, + .driver = { + .name = "rockchip-vop2", + .of_match_table = of_match_ptr(vop2_dt_match), + }, +}; diff --git a/include/dt-bindings/soc/rockchip,vop2.h b/include/dt-bindings/soc/rockchip,vop2.h new file mode 100644 index 000000000000..6e66a802b96a --- /dev/null +++ b/include/dt-bindings/soc/rockchip,vop2.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ + +#ifndef __DT_BINDINGS_ROCKCHIP_VOP2_H +#define __DT_BINDINGS_ROCKCHIP_VOP2_H + +#define ROCKCHIP_VOP2_EP_RGB0 1 +#define ROCKCHIP_VOP2_EP_HDMI0 2 +#define ROCKCHIP_VOP2_EP_EDP0 3 +#define ROCKCHIP_VOP2_EP_MIPI0 4 +#define ROCKCHIP_VOP2_EP_LVDS0 5 +#define ROCKCHIP_VOP2_EP_MIPI1 6 +#define ROCKCHIP_VOP2_EP_LVDS1 7 + +#endif /* __DT_BINDINGS_ROCKCHIP_VOP2_H */ -- cgit v1.2.3 From 420f95b0ad603165684528276ca9bf1a290bfb4e Mon Sep 17 00:00:00 2001 From: Raphael Gallais-Pou Date: Tue, 26 Apr 2022 15:19:12 +0200 Subject: drm: fix typo in comment Fix typo in a doxygen comment (replaced green by blue) in drm_color_mgmt.c Signed-off-by: Raphael Gallais-Pou Signed-off-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220426131912.3850354-1-raphael.gallais-pou@foss.st.com --- drivers/gpu/drm/drm_color_mgmt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_color_mgmt.c b/drivers/gpu/drm/drm_color_mgmt.c index 9079fbe21d2f..17c6c3eefcd6 100644 --- a/drivers/gpu/drm/drm_color_mgmt.c +++ b/drivers/gpu/drm/drm_color_mgmt.c @@ -259,7 +259,7 @@ static bool drm_crtc_supports_legacy_gamma(struct drm_crtc *crtc) * @crtc: CRTC object * @red: red correction table * @green: green correction table - * @blue: green correction table + * @blue: blue correction table * @size: size of the tables * @ctx: lock acquire context * -- cgit v1.2.3 From e123b4edb2a13b67c4de7caee2e3d5e083bef58b Mon Sep 17 00:00:00 2001 From: Lucas Stach Date: Wed, 4 May 2022 11:02:29 +0200 Subject: drm/etnaviv: avoid cleaning up sched_job when submit succeeded While the labels may mislead the casual reader, the tail of the function etnaviv_ioctl_gem_submit is always executed, as a lot of the structures set up in this function need to be cleaned up regardless of whether the submit succeeded or failed. An exception is the newly added drm_sched_job_cleanup, which must only be called when the submit failed before handing the job to the scheduler. Fixes: b827c84f5e84 ("drm/etnaviv: Use scheduler dependency handling") Reported-by: Michael Walle Signed-off-by: Lucas Stach Signed-off-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220504090229.2506560-1-l.stach@pengutronix.de --- drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c b/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c index 98bb5c9239de..1ac916b24891 100644 --- a/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c +++ b/drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c @@ -598,7 +598,12 @@ int etnaviv_ioctl_gem_submit(struct drm_device *dev, void *data, sync_file = sync_file_create(submit->out_fence); if (!sync_file) { ret = -ENOMEM; - goto err_submit_job; + /* + * When this late error is hit, the submit has already + * been handed over to the scheduler. At this point + * the sched_job must not be cleaned up. + */ + goto err_submit_put; } fd_install(out_fence_fd, sync_file->file); } @@ -607,7 +612,8 @@ int etnaviv_ioctl_gem_submit(struct drm_device *dev, void *data, args->fence = submit->out_fence_id; err_submit_job: - drm_sched_job_cleanup(&submit->sched_job); + if (ret) + drm_sched_job_cleanup(&submit->sched_job); err_submit_put: etnaviv_submit_put(submit); -- cgit v1.2.3 From 6aba439620123c6bf2201e76d1162e380462e4d6 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Fri, 29 Apr 2022 14:29:46 +0530 Subject: Revert "drm: bridge: mcde_dsi: Drop explicit bridge remove" commit <3730bc6147b0> ("drm: bridge: mcde_dsi: Drop explicit bridge remove") has removed downstream bridge as it's prior commit <3d7039e1e649> ("drm: bridge: mcde_dsi: Switch to devm_drm_of_get_bridge") added devm_drm_of_get_bridge for looking up if child node has panel or bridge. However commit ("Revert "drm: of: Lookup if child node has panel or bridge") has reverted panel or bridge child node lookup from devm_drm_of_get_bridge as it breaks the non-trivial cases the first child node might not be a panel or bridge. So, revert this commit to switch the previous behavior of looking up child panel or bridge. Link: https://lore.kernel.org/all/20220429085947.1699963-1-jagan@amarulasolutions.com/ Signed-off-by: Jagan Teki Reviewed-by: Linus Walleij Signed-off-by: Maxime Ripard --- drivers/gpu/drm/mcde/mcde_dsi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c index 960b49ea2ee5..083a4728654d 100644 --- a/drivers/gpu/drm/mcde/mcde_dsi.c +++ b/drivers/gpu/drm/mcde/mcde_dsi.c @@ -1122,6 +1122,7 @@ static void mcde_dsi_unbind(struct device *dev, struct device *master, { struct mcde_dsi *d = dev_get_drvdata(dev); + drm_bridge_remove(d->bridge_out); regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET, PRCM_DSI_SW_RESET_DSI0_SW_RESETN, 0); } -- cgit v1.2.3 From 4b6fd25f3a4c3369679d01d127d81c7b2f384877 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Fri, 29 Apr 2022 14:29:47 +0530 Subject: Revert "drm: bridge: mcde_dsi: Switch to devm_drm_of_get_bridge" commit <3d7039e1e649> ("drm: bridge: mcde_dsi: Switch to devm_drm_of_get_bridge") switched to devm_drm_of_get_bridge for looking up if child node has panel or bridge. However commit ("Revert "drm: of: Lookup if child node has panel or bridge") has reverted panel or bridge child node lookup from devm_drm_of_get_bridge as it breaks the non-trivial cases the first child node might not be a panel or bridge. So, revert this commit to switch the previous behavior of looking up child panel or bridge. Signed-off-by: Jagan Teki Reviewed-by: Linus Walleij Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20220429085947.1699963-2-jagan@amarulasolutions.com --- drivers/gpu/drm/mcde/mcde_dsi.c | 44 +++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/mcde/mcde_dsi.c b/drivers/gpu/drm/mcde/mcde_dsi.c index 083a4728654d..5651734ce977 100644 --- a/drivers/gpu/drm/mcde/mcde_dsi.c +++ b/drivers/gpu/drm/mcde/mcde_dsi.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,7 @@ struct mcde_dsi { struct device *dev; struct mcde *mcde; struct drm_bridge bridge; + struct drm_panel *panel; struct drm_bridge *bridge_out; struct mipi_dsi_host dsi_host; struct mipi_dsi_device *mdsi; @@ -1071,7 +1073,9 @@ static int mcde_dsi_bind(struct device *dev, struct device *master, struct drm_device *drm = data; struct mcde *mcde = to_mcde(drm); struct mcde_dsi *d = dev_get_drvdata(dev); - struct drm_bridge *bridge; + struct device_node *child; + struct drm_panel *panel = NULL; + struct drm_bridge *bridge = NULL; if (!of_get_available_child_count(dev->of_node)) { dev_info(dev, "unused DSI interface\n"); @@ -1096,10 +1100,37 @@ static int mcde_dsi_bind(struct device *dev, struct device *master, return PTR_ERR(d->lp_clk); } - bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0); - if (IS_ERR(bridge)) { - dev_err(dev, "error to get bridge\n"); - return PTR_ERR(bridge); + /* Look for a panel as a child to this node */ + for_each_available_child_of_node(dev->of_node, child) { + panel = of_drm_find_panel(child); + if (IS_ERR(panel)) { + dev_err(dev, "failed to find panel try bridge (%ld)\n", + PTR_ERR(panel)); + panel = NULL; + + bridge = of_drm_find_bridge(child); + if (!bridge) { + dev_err(dev, "failed to find bridge\n"); + return -EINVAL; + } + } + } + if (panel) { + bridge = drm_panel_bridge_add_typed(panel, + DRM_MODE_CONNECTOR_DSI); + if (IS_ERR(bridge)) { + dev_err(dev, "error adding panel bridge\n"); + return PTR_ERR(bridge); + } + dev_info(dev, "connected to panel\n"); + d->panel = panel; + } else if (bridge) { + /* TODO: AV8100 HDMI encoder goes here for example */ + dev_info(dev, "connected to non-panel bridge (unsupported)\n"); + return -ENODEV; + } else { + dev_err(dev, "no panel or bridge\n"); + return -ENODEV; } d->bridge_out = bridge; @@ -1122,7 +1153,8 @@ static void mcde_dsi_unbind(struct device *dev, struct device *master, { struct mcde_dsi *d = dev_get_drvdata(dev); - drm_bridge_remove(d->bridge_out); + if (d->panel) + drm_panel_bridge_remove(d->bridge_out); regmap_update_bits(d->prcmu, PRCM_DSI_SW_RESET, PRCM_DSI_SW_RESET_DSI0_SW_RESETN, 0); } -- cgit v1.2.3 From ea16c74c29d7e5c9e526cd869f1a928c74b52c44 Mon Sep 17 00:00:00 2001 From: Jagan Teki Date: Thu, 28 Apr 2022 15:18:08 +0530 Subject: drm: exynos: dsi: Use child panel or bridge find helpers commit <711c7adc4687> ("drm: exynos: dsi: Use drm panel_bridge API") added devm_drm_of_get_bridge for looking up if child node has panel or bridge. However commit ("Revert "drm: of: Lookup if child node has panel or bridge") has reverted panel or bridge child node lookup from devm_drm_of_get_bridge which eventually failed to find the DSI devices in exynos drm dsi driver. So, use the conventional child panel bridge lookup helpers like it does before. Signed-off-by: Jagan Teki Tested-by: Marek Szyprowski Signed-off-by: Maxime Ripard Link: https://patchwork.freedesktop.org/patch/msgid/20220428094808.782938-1-jagan@amarulasolutions.com --- drivers/gpu/drm/exynos/exynos_drm_dsi.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/exynos/exynos_drm_dsi.c b/drivers/gpu/drm/exynos/exynos_drm_dsi.c index f067c86b0b12..ec673223d6b7 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_dsi.c +++ b/drivers/gpu/drm/exynos/exynos_drm_dsi.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -1451,9 +1452,18 @@ static int exynos_dsi_host_attach(struct mipi_dsi_host *host, struct device *dev = dsi->dev; struct drm_encoder *encoder = &dsi->encoder; struct drm_device *drm = encoder->dev; + struct drm_panel *panel; int ret; - dsi->out_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 1, 0); + panel = of_drm_find_panel(device->dev.of_node); + if (!IS_ERR(panel)) { + dsi->out_bridge = devm_drm_panel_bridge_add(dev, panel); + } else { + dsi->out_bridge = of_drm_find_bridge(device->dev.of_node); + if (!dsi->out_bridge) + dsi->out_bridge = ERR_PTR(-EINVAL); + } + if (IS_ERR(dsi->out_bridge)) { ret = PTR_ERR(dsi->out_bridge); DRM_DEV_ERROR(dev, "failed to find the bridge: %d\n", ret); -- cgit v1.2.3 From 60856812f07cd774831dbc7f4da5979ef62d676b Mon Sep 17 00:00:00 2001 From: Guo Zhengkui Date: Thu, 5 May 2022 00:09:29 +0800 Subject: drm/nouveau/devinit: fix returnvar.cocci warnings Fix the following coccicheck warnings: drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gf100.c:71:5-12: Unneeded variable: "disable". Return "0ULL" on line 90. drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm107.c:35:5-12: Unneeded variable: "disable". Return "0ULL" on line 44. drivers/gpu/drm/nouveau/nvkm/subdev/devinit/g98.c:35:5-12: Unneeded variable: "disable". Return "0ULL" on line 50. Signed-off-by: Guo Zhengkui Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patchwork.freedesktop.org/patch/msgid/20220504161003.9245-1-guozhengkui@vivo.com --- drivers/gpu/drm/nouveau/nvkm/subdev/devinit/g98.c | 3 +-- drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gf100.c | 3 +-- drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm107.c | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/g98.c b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/g98.c index 05729ca19e9a..8977483a9f42 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/g98.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/g98.c @@ -32,7 +32,6 @@ g98_devinit_disable(struct nvkm_devinit *init) struct nvkm_device *device = init->subdev.device; u32 r001540 = nvkm_rd32(device, 0x001540); u32 r00154c = nvkm_rd32(device, 0x00154c); - u64 disable = 0ULL; if (!(r001540 & 0x40000000)) { nvkm_subdev_disable(device, NVKM_ENGINE_MSPDEC, 0); @@ -47,7 +46,7 @@ g98_devinit_disable(struct nvkm_devinit *init) if (!(r00154c & 0x00000040)) nvkm_subdev_disable(device, NVKM_ENGINE_SEC, 0); - return disable; + return 0ULL; } static const struct nvkm_devinit_func diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gf100.c b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gf100.c index 051cfd6a5caf..5b7cb1fe7897 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gf100.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gf100.c @@ -68,7 +68,6 @@ gf100_devinit_disable(struct nvkm_devinit *init) { struct nvkm_device *device = init->subdev.device; u32 r022500 = nvkm_rd32(device, 0x022500); - u64 disable = 0ULL; if (r022500 & 0x00000001) nvkm_subdev_disable(device, NVKM_ENGINE_DISP, 0); @@ -87,7 +86,7 @@ gf100_devinit_disable(struct nvkm_devinit *init) if (r022500 & 0x00000200) nvkm_subdev_disable(device, NVKM_ENGINE_CE, 1); - return disable; + return 0ULL; } void diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm107.c b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm107.c index 4323732a3cb2..8955af2704c7 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm107.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/gm107.c @@ -32,7 +32,6 @@ gm107_devinit_disable(struct nvkm_devinit *init) struct nvkm_device *device = init->subdev.device; u32 r021c00 = nvkm_rd32(device, 0x021c00); u32 r021c04 = nvkm_rd32(device, 0x021c04); - u64 disable = 0ULL; if (r021c00 & 0x00000001) nvkm_subdev_disable(device, NVKM_ENGINE_CE, 0); @@ -41,7 +40,7 @@ gm107_devinit_disable(struct nvkm_devinit *init) if (r021c04 & 0x00000001) nvkm_subdev_disable(device, NVKM_ENGINE_DISP, 0); - return disable; + return 0ULL; } static const struct nvkm_devinit_func -- cgit v1.2.3 From 9887bda0c831df0c044d6de147d002e48024fb4a Mon Sep 17 00:00:00 2001 From: Lyude Paul Date: Fri, 29 Apr 2022 15:53:47 -0400 Subject: drm/nouveau/subdev/bus: Ratelimit logging for fault errors There's plenty of ways to fudge the GPU when developing on nouveau by mistake, some of which can result in nouveau seriously spamming dmesg with fault errors. This can be somewhat annoying, as it can quickly overrun the message buffer (or your terminal emulator's buffer) and get rid of actually useful feedback from the driver. While working on my new atomic only MST branch, I ran into this issue a couple of times. So, let's fix this by adding nvkm_error_ratelimited(), and using it to ratelimit errors from faults. This should be fine for developers, since it's nearly always only the first few faults that we care about seeing. Plus, you can turn off rate limiting in the kernel if you really need to. Signed-off-by: Lyude Paul Reviewed-by: Karol Herbst Cc: stable@vger.kernel.org Link: https://patchwork.freedesktop.org/patch/msgid/20220429195350.85620-1-lyude@redhat.com --- drivers/gpu/drm/nouveau/include/nvkm/core/subdev.h | 2 ++ drivers/gpu/drm/nouveau/nvkm/subdev/bus/gf100.c | 14 +++++++------- drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv31.c | 6 +++--- drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv50.c | 6 +++--- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/nouveau/include/nvkm/core/subdev.h b/drivers/gpu/drm/nouveau/include/nvkm/core/subdev.h index 1665738948fb..96113c8bee8c 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/core/subdev.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/core/subdev.h @@ -62,4 +62,6 @@ void nvkm_subdev_intr(struct nvkm_subdev *); #define nvkm_debug(s,f,a...) nvkm_printk((s), DEBUG, info, f, ##a) #define nvkm_trace(s,f,a...) nvkm_printk((s), TRACE, info, f, ##a) #define nvkm_spam(s,f,a...) nvkm_printk((s), SPAM, dbg, f, ##a) + +#define nvkm_error_ratelimited(s,f,a...) nvkm_printk((s), ERROR, err_ratelimited, f, ##a) #endif diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/bus/gf100.c b/drivers/gpu/drm/nouveau/nvkm/subdev/bus/gf100.c index 53a6651ac225..80b5aaceeaad 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/bus/gf100.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/bus/gf100.c @@ -35,13 +35,13 @@ gf100_bus_intr(struct nvkm_bus *bus) u32 addr = nvkm_rd32(device, 0x009084); u32 data = nvkm_rd32(device, 0x009088); - nvkm_error(subdev, - "MMIO %s of %08x FAULT at %06x [ %s%s%s]\n", - (addr & 0x00000002) ? "write" : "read", data, - (addr & 0x00fffffc), - (stat & 0x00000002) ? "!ENGINE " : "", - (stat & 0x00000004) ? "PRIVRING " : "", - (stat & 0x00000008) ? "TIMEOUT " : ""); + nvkm_error_ratelimited(subdev, + "MMIO %s of %08x FAULT at %06x [ %s%s%s]\n", + (addr & 0x00000002) ? "write" : "read", data, + (addr & 0x00fffffc), + (stat & 0x00000002) ? "!ENGINE " : "", + (stat & 0x00000004) ? "PRIVRING " : "", + (stat & 0x00000008) ? "TIMEOUT " : ""); nvkm_wr32(device, 0x009084, 0x00000000); nvkm_wr32(device, 0x001100, (stat & 0x0000000e)); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv31.c b/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv31.c index ad8da523bb22..c75e463f3501 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv31.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv31.c @@ -45,9 +45,9 @@ nv31_bus_intr(struct nvkm_bus *bus) u32 addr = nvkm_rd32(device, 0x009084); u32 data = nvkm_rd32(device, 0x009088); - nvkm_error(subdev, "MMIO %s of %08x FAULT at %06x\n", - (addr & 0x00000002) ? "write" : "read", data, - (addr & 0x00fffffc)); + nvkm_error_ratelimited(subdev, "MMIO %s of %08x FAULT at %06x\n", + (addr & 0x00000002) ? "write" : "read", data, + (addr & 0x00fffffc)); stat &= ~0x00000008; nvkm_wr32(device, 0x001100, 0x00000008); diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv50.c b/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv50.c index 3a1e45adeedc..2055d0b100d3 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv50.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/bus/nv50.c @@ -60,9 +60,9 @@ nv50_bus_intr(struct nvkm_bus *bus) u32 addr = nvkm_rd32(device, 0x009084); u32 data = nvkm_rd32(device, 0x009088); - nvkm_error(subdev, "MMIO %s of %08x FAULT at %06x\n", - (addr & 0x00000002) ? "write" : "read", data, - (addr & 0x00fffffc)); + nvkm_error_ratelimited(subdev, "MMIO %s of %08x FAULT at %06x\n", + (addr & 0x00000002) ? "write" : "read", data, + (addr & 0x00fffffc)); stat &= ~0x00000008; nvkm_wr32(device, 0x001100, 0x00000008); -- cgit v1.2.3 From c81474ee890670eb942934e5ddc9366e6ec7f381 Mon Sep 17 00:00:00 2001 From: Christian König Date: Tue, 3 May 2022 10:59:35 +0200 Subject: drm/nouveau: remove trace_dma_fence_emit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hardware drivers which don't have much difference between emit and init shouldn't use this trace point. Signed-off-by: Christian König Cc: Ben Skeggs Cc: Karol Herbst Cc: Lyude Paul Cc: nouveau@lists.freedesktop.org Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patchwork.freedesktop.org/patch/msgid/20220503085935.11023-1-christian.koenig@amd.com --- drivers/gpu/drm/nouveau/nouveau_fence.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_fence.c b/drivers/gpu/drm/nouveau/nouveau_fence.c index 7f01dcf81fab..abcac7db4347 100644 --- a/drivers/gpu/drm/nouveau/nouveau_fence.c +++ b/drivers/gpu/drm/nouveau/nouveau_fence.c @@ -224,7 +224,6 @@ nouveau_fence_emit(struct nouveau_fence *fence, struct nouveau_channel *chan) &fctx->lock, fctx->context, ++fctx->sequence); kref_get(&fctx->fence_ref); - trace_dma_fence_emit(&fence->base); ret = fctx->emit(fence); if (!ret) { dma_fence_get(&fence->base); -- cgit v1.2.3 From 05ecc678357ae0929835b24f40a9ea8a1fcb105a Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 29 Apr 2022 23:23:13 +0200 Subject: drm: mxsfb: Implement LCDIF scanout CRC32 support The LCDIF controller as present in i.MX28/i.MX6SX/i.MX8M Mini/Nano has CRC_STAT register, which contains CRC32 of the frame as it was clocked out of the DPI interface of the LCDIF. This is most likely meant as a functional safety feature. Unfortunately, there is zero documentation on how the CRC32 is calculated, there is no documentation of the polynomial, the init value, nor on which data is the checksum applied. By applying brute-force on 8 pixel / 2 line frame, which is the minimum size LCDIF would work with, it turns out the polynomial is CRC32_POLY_LE 0xedb88320 , init value is 0xffffffff , the input data are bitrev32() of the entire frame and the resulting CRC has to be also bitrev32()ed. Doing this calculation in kernel for each frame is unrealistic due to the CPU demand, so attach the CRC collected from hardware to a frame instead. The DRM subsystem already has an interface for this purpose and the CRC can be accessed e.g. via debugfs: " $ echo auto > /sys/kernel/debug/dri/1/crtc-0/crc/control $ cat /sys/kernel/debug/dri/1/crtc-0/crc/data 0x0000408c 0xa4e5cdd8 0x0000408d 0x72f537b4 " The per-frame CRC can be used by userspace e.g. during automated testing, to verify that whatever buffer was sent to be scanned out was actually scanned out of the LCDIF correctly. Acked-by: Lucas Stach Acked-by: Stefan Agner Signed-off-by: Marek Vasut Cc: Alexander Stein Cc: Laurent Pinchart Cc: Lucas Stach Cc: Peng Fan Cc: Robby Cai Cc: Sam Ravnborg Cc: Stefan Agner Link: https://patchwork.freedesktop.org/patch/msgid/20220429212313.305556-1-marex@denx.de --- drivers/gpu/drm/mxsfb/mxsfb_drv.c | 14 +++++++-- drivers/gpu/drm/mxsfb/mxsfb_drv.h | 3 ++ drivers/gpu/drm/mxsfb/mxsfb_kms.c | 61 ++++++++++++++++++++++++++++++++++++-- drivers/gpu/drm/mxsfb/mxsfb_regs.h | 1 + 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.c b/drivers/gpu/drm/mxsfb/mxsfb_drv.c index 9d71c55a31c0..55aad92e08ba 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c +++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c @@ -52,6 +52,7 @@ static const struct mxsfb_devdata mxsfb_devdata[] = { .hs_wdth_shift = 24, .has_overlay = false, .has_ctrl2 = false, + .has_crc32 = false, }, [MXSFB_V4] = { .transfer_count = LCDC_V4_TRANSFER_COUNT, @@ -61,6 +62,7 @@ static const struct mxsfb_devdata mxsfb_devdata[] = { .hs_wdth_shift = 18, .has_overlay = false, .has_ctrl2 = true, + .has_crc32 = true, }, [MXSFB_V6] = { .transfer_count = LCDC_V4_TRANSFER_COUNT, @@ -70,6 +72,7 @@ static const struct mxsfb_devdata mxsfb_devdata[] = { .hs_wdth_shift = 18, .has_overlay = true, .has_ctrl2 = true, + .has_crc32 = true, }, }; @@ -157,12 +160,19 @@ static irqreturn_t mxsfb_irq_handler(int irq, void *data) { struct drm_device *drm = data; struct mxsfb_drm_private *mxsfb = drm->dev_private; - u32 reg; + u32 reg, crc; + u64 vbc; reg = readl(mxsfb->base + LCDC_CTRL1); - if (reg & CTRL1_CUR_FRAME_DONE_IRQ) + if (reg & CTRL1_CUR_FRAME_DONE_IRQ) { drm_crtc_handle_vblank(&mxsfb->crtc); + if (mxsfb->crc_active) { + crc = readl(mxsfb->base + LCDC_V4_CRC_STAT); + vbc = drm_crtc_accurate_vblank_count(&mxsfb->crtc); + drm_crtc_add_crc_entry(&mxsfb->crtc, true, vbc, &crc); + } + } writel(CTRL1_CUR_FRAME_DONE_IRQ, mxsfb->base + LCDC_CTRL1 + REG_CLR); diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.h b/drivers/gpu/drm/mxsfb/mxsfb_drv.h index ddb5b0417a82..d160d921b25f 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_drv.h +++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.h @@ -23,6 +23,7 @@ struct mxsfb_devdata { unsigned int hs_wdth_shift; bool has_overlay; bool has_ctrl2; + bool has_crc32; }; struct mxsfb_drm_private { @@ -44,6 +45,8 @@ struct mxsfb_drm_private { struct drm_encoder encoder; struct drm_connector *connector; struct drm_bridge *bridge; + + bool crc_active; }; static inline struct mxsfb_drm_private * diff --git a/drivers/gpu/drm/mxsfb/mxsfb_kms.c b/drivers/gpu/drm/mxsfb/mxsfb_kms.c index cd2a59e110c3..f021ab2c4520 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_kms.c +++ b/drivers/gpu/drm/mxsfb/mxsfb_kms.c @@ -439,6 +439,41 @@ static void mxsfb_crtc_disable_vblank(struct drm_crtc *crtc) writel(CTRL1_CUR_FRAME_DONE_IRQ, mxsfb->base + LCDC_CTRL1 + REG_CLR); } +static int mxsfb_crtc_set_crc_source(struct drm_crtc *crtc, const char *source) +{ + struct mxsfb_drm_private *mxsfb; + + if (!crtc) + return -ENODEV; + + mxsfb = to_mxsfb_drm_private(crtc->dev); + + if (source && strcmp(source, "auto") == 0) + mxsfb->crc_active = true; + else if (!source) + mxsfb->crc_active = false; + else + return -EINVAL; + + return 0; +} + +static int mxsfb_crtc_verify_crc_source(struct drm_crtc *crtc, + const char *source, size_t *values_cnt) +{ + if (!crtc) + return -ENODEV; + + if (source && strcmp(source, "auto") != 0) { + DRM_DEBUG_DRIVER("Unknown CRC source %s for %s\n", + source, crtc->name); + return -EINVAL; + } + + *values_cnt = 1; + return 0; +} + static const struct drm_crtc_helper_funcs mxsfb_crtc_helper_funcs = { .atomic_check = mxsfb_crtc_atomic_check, .atomic_flush = mxsfb_crtc_atomic_flush, @@ -457,6 +492,19 @@ static const struct drm_crtc_funcs mxsfb_crtc_funcs = { .disable_vblank = mxsfb_crtc_disable_vblank, }; +static const struct drm_crtc_funcs mxsfb_crtc_with_crc_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, + .enable_vblank = mxsfb_crtc_enable_vblank, + .disable_vblank = mxsfb_crtc_disable_vblank, + .set_crc_source = mxsfb_crtc_set_crc_source, + .verify_crc_source = mxsfb_crtc_verify_crc_source, +}; + /* ----------------------------------------------------------------------------- * Encoder */ @@ -645,9 +693,16 @@ int mxsfb_kms_init(struct mxsfb_drm_private *mxsfb) } drm_crtc_helper_add(crtc, &mxsfb_crtc_helper_funcs); - ret = drm_crtc_init_with_planes(mxsfb->drm, crtc, - &mxsfb->planes.primary, NULL, - &mxsfb_crtc_funcs, NULL); + if (mxsfb->devdata->has_crc32) { + ret = drm_crtc_init_with_planes(mxsfb->drm, crtc, + &mxsfb->planes.primary, NULL, + &mxsfb_crtc_with_crc_funcs, + NULL); + } else { + ret = drm_crtc_init_with_planes(mxsfb->drm, crtc, + &mxsfb->planes.primary, NULL, + &mxsfb_crtc_funcs, NULL); + } if (ret) return ret; diff --git a/drivers/gpu/drm/mxsfb/mxsfb_regs.h b/drivers/gpu/drm/mxsfb/mxsfb_regs.h index 694fea13e893..cf813a1da1d7 100644 --- a/drivers/gpu/drm/mxsfb/mxsfb_regs.h +++ b/drivers/gpu/drm/mxsfb/mxsfb_regs.h @@ -26,6 +26,7 @@ #define LCDC_VDCTRL2 0x90 #define LCDC_VDCTRL3 0xa0 #define LCDC_VDCTRL4 0xb0 +#define LCDC_V4_CRC_STAT 0x1a0 #define LCDC_V4_DEBUG0 0x1d0 #define LCDC_V3_DEBUG0 0x1f0 #define LCDC_AS_CTRL 0x210 -- cgit v1.2.3 From 1bb533b6871a7e3b0692253f7f8bf18338e21627 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Fri, 29 Apr 2022 22:46:25 +0200 Subject: drm/bridge: tc358767: Fix DP bridge mode detection from DT endpoints Per toshiba,tc358767.yaml DT binding document, port@2 the output (e)DP port is optional. In case this port is not described in DT, the bridge driver operates in DPI-to-DP mode. Make sure the driver treats this as a valid mode of operation instead of reporting invalid mode. Fixes: 71f7d9c03118 ("drm/bridge: tc358767: Detect bridge mode from connected endpoints in DT") Signed-off-by: Marek Vasut Cc: Jonas Karlman Cc: Laurent Pinchart Cc: Lucas Stach Cc: Marek Vasut Cc: Maxime Ripard Cc: Neil Armstrong Cc: Robert Foss Cc: Sam Ravnborg Reviewed-by: Lucas Stach Link: https://patchwork.freedesktop.org/patch/msgid/20220429204625.241591-1-marex@denx.de --- drivers/gpu/drm/bridge/tc358767.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c index 7dde71313b84..485717c8f0b4 100644 --- a/drivers/gpu/drm/bridge/tc358767.c +++ b/drivers/gpu/drm/bridge/tc358767.c @@ -1964,7 +1964,9 @@ static int tc_probe_bridge_endpoint(struct tc_data *tc) struct of_endpoint endpoint; struct device_node *node = NULL; const u8 mode_dpi_to_edp = BIT(1) | BIT(2); + const u8 mode_dpi_to_dp = BIT(1); const u8 mode_dsi_to_edp = BIT(0) | BIT(2); + const u8 mode_dsi_to_dp = BIT(0); const u8 mode_dsi_to_dpi = BIT(0) | BIT(1); u8 mode = 0; @@ -1990,11 +1992,11 @@ static int tc_probe_bridge_endpoint(struct tc_data *tc) mode |= BIT(endpoint.port); } - if (mode == mode_dpi_to_edp) + if (mode == mode_dpi_to_edp || mode == mode_dpi_to_dp) return tc_probe_edp_bridge_endpoint(tc); else if (mode == mode_dsi_to_dpi) return tc_probe_dpi_bridge_endpoint(tc); - else if (mode == mode_dsi_to_edp) + else if (mode == mode_dsi_to_edp || mode == mode_dsi_to_dp) dev_warn(dev, "The mode DSI-to-(e)DP is not supported!\n"); else dev_warn(dev, "Invalid mode (0x%x) is not supported!\n", mode); -- cgit v1.2.3 From 41fd6f0a6dd62def79e91c2170cdfd29473fb833 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 27 Apr 2022 16:14:06 +0200 Subject: drm/format-helper: Implement drm_fb_swab() with per-line helpers Replace the inner loop of drm_fb_swab() with helper functions that swap the bytes in each pixel. This will allow to share the outer loop with other conversion helpers. Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20220427141409.22842-2-tzimmermann@suse.de --- drivers/gpu/drm/drm_format_helper.c | 60 +++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/drivers/gpu/drm/drm_format_helper.c b/drivers/gpu/drm/drm_format_helper.c index 34b7ef443ad2..f70499344a04 100644 --- a/drivers/gpu/drm/drm_format_helper.c +++ b/drivers/gpu/drm/drm_format_helper.c @@ -100,6 +100,26 @@ void drm_fb_memcpy_toio(void __iomem *dst, unsigned int dst_pitch, const void *v } EXPORT_SYMBOL(drm_fb_memcpy_toio); +static void drm_fb_swab16_line(void *dbuf, const void *sbuf, unsigned int pixels) +{ + u16 *dbuf16 = dbuf; + const u16 *sbuf16 = sbuf; + const u16 *send16 = sbuf16 + pixels; + + while (sbuf16 < send16) + *dbuf16++ = swab16(*sbuf16++); +} + +static void drm_fb_swab32_line(void *dbuf, const void *sbuf, unsigned int pixels) +{ + u32 *dbuf32 = dbuf; + const u32 *sbuf32 = sbuf; + const u32 *send32 = sbuf32 + pixels; + + while (sbuf32 < send32) + *dbuf32++ = swab32(*sbuf32++); +} + /** * drm_fb_swab - Swap bytes into clip buffer * @dst: Destination buffer @@ -120,12 +140,11 @@ void drm_fb_swab(void *dst, unsigned int dst_pitch, const void *src, bool cached) { u8 cpp = fb->format->cpp[0]; - size_t len = drm_rect_width(clip) * cpp; - const u16 *src16; - const u32 *src32; - u16 *dst16; - u32 *dst32; - unsigned int x, y; + unsigned long linepixels = drm_rect_width(clip); + size_t len = linepixels * cpp; + const void *sbuf; + void *dbuf; + unsigned int y; void *buf = NULL; if (WARN_ON_ONCE(cpp != 2 && cpp != 4)) @@ -133,31 +152,22 @@ void drm_fb_swab(void *dst, unsigned int dst_pitch, const void *src, if (!dst_pitch) dst_pitch = len; + src += clip_offset(clip, fb->pitches[0], cpp); if (!cached) buf = kmalloc(len, GFP_KERNEL); - src += clip_offset(clip, fb->pitches[0], cpp); - for (y = clip->y1; y < clip->y2; y++) { - if (buf) { - memcpy(buf, src, len); - src16 = buf; - src32 = buf; - } else { - src16 = src; - src32 = src; - } - - dst16 = dst; - dst32 = dst; + if (buf) + sbuf = memcpy(buf, src, len); + else + sbuf = src; + dbuf = dst + clip->x1 * cpp; - for (x = clip->x1; x < clip->x2; x++) { - if (cpp == 4) - *dst32++ = swab32(*src32++); - else - *dst16++ = swab16(*src16++); - } + if (cpp == 4) + drm_fb_swab32_line(dbuf, sbuf, linepixels); + else + drm_fb_swab16_line(dbuf, sbuf, linepixels); src += fb->pitches[0]; dst += dst_pitch; -- cgit v1.2.3 From 69add027fd2bac9bf757f012d0e5c53ecc15144e Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 27 Apr 2022 16:14:07 +0200 Subject: drm/format-helper: Remove optional byte-swap from line convertion Implement per-pixel byte swapping in a separate conversion helper for the single function that requires it. Select the correct helper for each conversion. Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20220427141409.22842-3-tzimmermann@suse.de --- drivers/gpu/drm/drm_format_helper.c | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/drm_format_helper.c b/drivers/gpu/drm/drm_format_helper.c index f70499344a04..b7daa40fc856 100644 --- a/drivers/gpu/drm/drm_format_helper.c +++ b/drivers/gpu/drm/drm_format_helper.c @@ -229,8 +229,7 @@ void drm_fb_xrgb8888_to_rgb332(void *dst, unsigned int dst_pitch, const void *sr EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb332); static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, const u32 *sbuf, - unsigned int pixels, - bool swab) + unsigned int pixels) { unsigned int x; u16 val16; @@ -239,10 +238,21 @@ static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, const u32 *sbuf, val16 = ((sbuf[x] & 0x00F80000) >> 8) | ((sbuf[x] & 0x0000FC00) >> 5) | ((sbuf[x] & 0x000000F8) >> 3); - if (swab) - dbuf[x] = swab16(val16); - else - dbuf[x] = val16; + dbuf[x] = val16; + } +} + +static void drm_fb_xrgb8888_to_rgb565_swab_line(u16 *dbuf, const u32 *sbuf, + unsigned int pixels) +{ + unsigned int x; + u16 val16; + + for (x = 0; x < pixels; x++) { + val16 = ((sbuf[x] & 0x00F80000) >> 8) | + ((sbuf[x] & 0x0000FC00) >> 5) | + ((sbuf[x] & 0x000000F8) >> 3); + dbuf[x] = swab16(val16); } } @@ -282,7 +292,10 @@ void drm_fb_xrgb8888_to_rgb565(void *dst, unsigned int dst_pitch, const void *va vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); for (y = 0; y < lines; y++) { memcpy(sbuf, vaddr, src_len); - drm_fb_xrgb8888_to_rgb565_line(dst, sbuf, linepixels, swab); + if (swab) + drm_fb_xrgb8888_to_rgb565_swab_line(dst, sbuf, linepixels); + else + drm_fb_xrgb8888_to_rgb565_line(dst, sbuf, linepixels); vaddr += fb->pitches[0]; dst += dst_pitch; } @@ -321,7 +334,10 @@ void drm_fb_xrgb8888_to_rgb565_toio(void __iomem *dst, unsigned int dst_pitch, vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); for (y = 0; y < lines; y++) { - drm_fb_xrgb8888_to_rgb565_line(dbuf, vaddr, linepixels, swab); + if (swab) + drm_fb_xrgb8888_to_rgb565_swab_line(dbuf, vaddr, linepixels); + else + drm_fb_xrgb8888_to_rgb565_line(dbuf, vaddr, linepixels); memcpy_toio(dst, dbuf, dst_len); vaddr += fb->pitches[0]; dst += dst_pitch; -- cgit v1.2.3 From a6fdb669bb65232a440717098e9e702e27586a0f Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 27 Apr 2022 16:14:08 +0200 Subject: drm/format-helper: Unify the parameters of all per-line conversion helpers Give each per-line conversion helper pointers of type void and the number of pixels in the line. Remove the unused swab parameters. Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20220427141409.22842-4-tzimmermann@suse.de --- drivers/gpu/drm/drm_format_helper.c | 87 +++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/drivers/gpu/drm/drm_format_helper.c b/drivers/gpu/drm/drm_format_helper.c index b7daa40fc856..21d0d282c6a1 100644 --- a/drivers/gpu/drm/drm_format_helper.c +++ b/drivers/gpu/drm/drm_format_helper.c @@ -177,16 +177,19 @@ void drm_fb_swab(void *dst, unsigned int dst_pitch, const void *src, } EXPORT_SYMBOL(drm_fb_swab); -static void drm_fb_xrgb8888_to_rgb332_line(u8 *dbuf, const __le32 *sbuf, unsigned int pixels) +static void drm_fb_xrgb8888_to_rgb332_line(void *dbuf, const void *sbuf, unsigned int pixels, + bool swab) { + u8 *dbuf8 = dbuf; + const __le32 *sbuf32 = sbuf; unsigned int x; u32 pix; for (x = 0; x < pixels; x++) { - pix = le32_to_cpu(sbuf[x]); - dbuf[x] = ((pix & 0x00e00000) >> 16) | - ((pix & 0x0000e000) >> 11) | - ((pix & 0x000000c0) >> 6); + pix = le32_to_cpu(sbuf32[x]); + dbuf8[x] = ((pix & 0x00e00000) >> 16) | + ((pix & 0x0000e000) >> 11) | + ((pix & 0x000000c0) >> 6); } } @@ -219,7 +222,7 @@ void drm_fb_xrgb8888_to_rgb332(void *dst, unsigned int dst_pitch, const void *sr src += clip_offset(clip, fb->pitches[0], sizeof(u32)); for (y = 0; y < drm_rect_height(clip); y++) { memcpy(sbuf, src, src_len); - drm_fb_xrgb8888_to_rgb332_line(dst, sbuf, width); + drm_fb_xrgb8888_to_rgb332_line(dst, sbuf, width, false); src += fb->pitches[0]; dst += dst_pitch; } @@ -228,31 +231,34 @@ void drm_fb_xrgb8888_to_rgb332(void *dst, unsigned int dst_pitch, const void *sr } EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb332); -static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, const u32 *sbuf, - unsigned int pixels) +static void drm_fb_xrgb8888_to_rgb565_line(void *dbuf, const void *sbuf, unsigned int pixels) { + u16 *dbuf16 = dbuf; + const u32 *sbuf32 = sbuf; unsigned int x; u16 val16; for (x = 0; x < pixels; x++) { - val16 = ((sbuf[x] & 0x00F80000) >> 8) | - ((sbuf[x] & 0x0000FC00) >> 5) | - ((sbuf[x] & 0x000000F8) >> 3); - dbuf[x] = val16; + val16 = ((sbuf32[x] & 0x00F80000) >> 8) | + ((sbuf32[x] & 0x0000FC00) >> 5) | + ((sbuf32[x] & 0x000000F8) >> 3); + dbuf16[x] = val16; } } -static void drm_fb_xrgb8888_to_rgb565_swab_line(u16 *dbuf, const u32 *sbuf, +static void drm_fb_xrgb8888_to_rgb565_swab_line(void *dbuf, const void *sbuf, unsigned int pixels) { + u16 *dbuf16 = dbuf; + const u32 *sbuf32 = sbuf; unsigned int x; u16 val16; for (x = 0; x < pixels; x++) { - val16 = ((sbuf[x] & 0x00F80000) >> 8) | - ((sbuf[x] & 0x0000FC00) >> 5) | - ((sbuf[x] & 0x000000F8) >> 3); - dbuf[x] = swab16(val16); + val16 = ((sbuf32[x] & 0x00F80000) >> 8) | + ((sbuf32[x] & 0x0000FC00) >> 5) | + ((sbuf32[x] & 0x000000F8) >> 3); + dbuf16[x] = swab16(val16); } } @@ -347,15 +353,16 @@ void drm_fb_xrgb8888_to_rgb565_toio(void __iomem *dst, unsigned int dst_pitch, } EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565_toio); -static void drm_fb_xrgb8888_to_rgb888_line(u8 *dbuf, const u32 *sbuf, - unsigned int pixels) +static void drm_fb_xrgb8888_to_rgb888_line(void *dbuf, const void *sbuf, unsigned int pixels) { + u8 *dbuf8 = dbuf; + const u32 *sbuf32 = sbuf; unsigned int x; for (x = 0; x < pixels; x++) { - *dbuf++ = (sbuf[x] & 0x000000FF) >> 0; - *dbuf++ = (sbuf[x] & 0x0000FF00) >> 8; - *dbuf++ = (sbuf[x] & 0x00FF0000) >> 16; + *dbuf8++ = (sbuf32[x] & 0x000000FF) >> 0; + *dbuf8++ = (sbuf32[x] & 0x0000FF00) >> 8; + *dbuf8++ = (sbuf32[x] & 0x00FF0000) >> 16; } } @@ -521,17 +528,18 @@ static void drm_fb_rgb888_to_xrgb8888_toio(void __iomem *dst, unsigned int dst_p kfree(dbuf); } -static void drm_fb_xrgb8888_to_xrgb2101010_line(u32 *dbuf, const u32 *sbuf, - unsigned int pixels) +static void drm_fb_xrgb8888_to_xrgb2101010_line(void *dbuf, const void *sbuf, unsigned int pixels) { + u32 *dbuf32 = dbuf; + const u32 *sbuf32 = sbuf; unsigned int x; u32 val32; for (x = 0; x < pixels; x++) { - val32 = ((sbuf[x] & 0x000000FF) << 2) | - ((sbuf[x] & 0x0000FF00) << 4) | - ((sbuf[x] & 0x00FF0000) << 6); - *dbuf++ = val32 | ((val32 >> 8) & 0x00300C03); + val32 = ((sbuf32[x] & 0x000000FF) << 2) | + ((sbuf32[x] & 0x0000FF00) << 4) | + ((sbuf32[x] & 0x00FF0000) << 6); + *dbuf32++ = val32 | ((val32 >> 8) & 0x00300C03); } } @@ -576,18 +584,20 @@ void drm_fb_xrgb8888_to_xrgb2101010_toio(void __iomem *dst, } EXPORT_SYMBOL(drm_fb_xrgb8888_to_xrgb2101010_toio); -static void drm_fb_xrgb8888_to_gray8_line(u8 *dst, const u32 *src, unsigned int pixels) +static void drm_fb_xrgb8888_to_gray8_line(void *dbuf, const void *sbuf, unsigned int pixels) { + u8 *dbuf8 = dbuf; + const u32 *sbuf32 = sbuf; unsigned int x; for (x = 0; x < pixels; x++) { - u8 r = (*src & 0x00ff0000) >> 16; - u8 g = (*src & 0x0000ff00) >> 8; - u8 b = *src & 0x000000ff; + u8 r = (*sbuf32 & 0x00ff0000) >> 16; + u8 g = (*sbuf32 & 0x0000ff00) >> 8; + u8 b = *sbuf32 & 0x000000ff; /* ITU BT.601: Y = 0.299 R + 0.587 G + 0.114 B */ - *dst++ = (3 * r + 6 * g + b) / 10; - src++; + *dbuf8++ = (3 * r + 6 * g + b) / 10; + sbuf32++; } } @@ -716,17 +726,20 @@ int drm_fb_blit_toio(void __iomem *dst, unsigned int dst_pitch, uint32_t dst_for EXPORT_SYMBOL(drm_fb_blit_toio); -static void drm_fb_gray8_to_mono_line(u8 *dst, const u8 *src, unsigned int pixels) +static void drm_fb_gray8_to_mono_line(void *dbuf, const void *sbuf, unsigned int pixels) { + u8 *dbuf8 = dbuf; + const u8 *sbuf8 = sbuf; + while (pixels) { unsigned int i, bits = min(pixels, 8U); u8 byte = 0; for (i = 0; i < bits; i++, pixels--) { - if (*src++ >= 128) + if (*sbuf8++ >= 128) byte |= BIT(i); } - *dst++ = byte; + *dbuf8++ = byte; } } -- cgit v1.2.3 From cce6bedb38ed08f1c3a9b1b01fbba0fdb472fb50 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Wed, 27 Apr 2022 16:14:09 +0200 Subject: drm/format-helper: Share implementation among conversion helpers Provide format-independent conversion helpers for system and I/O memory. Implement most existing helpers on top of it. The source and destination formats of each conversion is handled by a per-line helper that is given to the generic implementation. v2: * remove a blank line Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20220427141409.22842-5-tzimmermann@suse.de --- drivers/gpu/drm/drm_format_helper.c | 369 ++++++++++++------------------------ 1 file changed, 123 insertions(+), 246 deletions(-) diff --git a/drivers/gpu/drm/drm_format_helper.c b/drivers/gpu/drm/drm_format_helper.c index 21d0d282c6a1..a3ccd8bc966f 100644 --- a/drivers/gpu/drm/drm_format_helper.c +++ b/drivers/gpu/drm/drm_format_helper.c @@ -40,6 +40,94 @@ unsigned int drm_fb_clip_offset(unsigned int pitch, const struct drm_format_info } EXPORT_SYMBOL(drm_fb_clip_offset); +/* TODO: Make this functon work with multi-plane formats. */ +static int drm_fb_xfrm(void *dst, unsigned long dst_pitch, unsigned long dst_pixsize, + const void *vaddr, const struct drm_framebuffer *fb, + const struct drm_rect *clip, bool vaddr_cached_hint, + void (*xfrm_line)(void *dbuf, const void *sbuf, unsigned int npixels)) +{ + unsigned long linepixels = drm_rect_width(clip); + unsigned long lines = drm_rect_height(clip); + size_t sbuf_len = linepixels * fb->format->cpp[0]; + void *stmp = NULL; + unsigned long i; + const void *sbuf; + + /* + * Some source buffers, such as CMA memory, use write-combine + * caching, so reads are uncached. Speed up access by fetching + * one line at a time. + */ + if (!vaddr_cached_hint) { + stmp = kmalloc(sbuf_len, GFP_KERNEL); + if (!stmp) + return -ENOMEM; + } + + if (!dst_pitch) + dst_pitch = drm_rect_width(clip) * dst_pixsize; + vaddr += clip_offset(clip, fb->pitches[0], fb->format->cpp[0]); + + for (i = 0; i < lines; ++i) { + if (stmp) + sbuf = memcpy(stmp, vaddr, sbuf_len); + else + sbuf = vaddr; + xfrm_line(dst, sbuf, linepixels); + vaddr += fb->pitches[0]; + dst += dst_pitch; + } + + kfree(stmp); + + return 0; +} + +/* TODO: Make this functon work with multi-plane formats. */ +static int drm_fb_xfrm_toio(void __iomem *dst, unsigned long dst_pitch, unsigned long dst_pixsize, + const void *vaddr, const struct drm_framebuffer *fb, + const struct drm_rect *clip, bool vaddr_cached_hint, + void (*xfrm_line)(void *dbuf, const void *sbuf, unsigned int npixels)) +{ + unsigned long linepixels = drm_rect_width(clip); + unsigned long lines = drm_rect_height(clip); + size_t dbuf_len = linepixels * dst_pixsize; + size_t stmp_off = round_up(dbuf_len, ARCH_KMALLOC_MINALIGN); /* for sbuf alignment */ + size_t sbuf_len = linepixels * fb->format->cpp[0]; + void *stmp = NULL; + unsigned long i; + const void *sbuf; + void *dbuf; + + if (vaddr_cached_hint) { + dbuf = kmalloc(dbuf_len, GFP_KERNEL); + } else { + dbuf = kmalloc(stmp_off + sbuf_len, GFP_KERNEL); + stmp = dbuf + stmp_off; + } + if (!dbuf) + return -ENOMEM; + + if (!dst_pitch) + dst_pitch = linepixels * dst_pixsize; + vaddr += clip_offset(clip, fb->pitches[0], fb->format->cpp[0]); + + for (i = 0; i < lines; ++i) { + if (stmp) + sbuf = memcpy(stmp, vaddr, sbuf_len); + else + sbuf = vaddr; + xfrm_line(dbuf, sbuf, linepixels); + memcpy_toio(dst, dbuf, dbuf_len); + vaddr += fb->pitches[0]; + dst += dst_pitch; + } + + kfree(dbuf); + + return 0; +} + /** * drm_fb_memcpy - Copy clip buffer * @dst: Destination buffer @@ -140,45 +228,23 @@ void drm_fb_swab(void *dst, unsigned int dst_pitch, const void *src, bool cached) { u8 cpp = fb->format->cpp[0]; - unsigned long linepixels = drm_rect_width(clip); - size_t len = linepixels * cpp; - const void *sbuf; - void *dbuf; - unsigned int y; - void *buf = NULL; - - if (WARN_ON_ONCE(cpp != 2 && cpp != 4)) - return; - if (!dst_pitch) - dst_pitch = len; - src += clip_offset(clip, fb->pitches[0], cpp); - - if (!cached) - buf = kmalloc(len, GFP_KERNEL); - - for (y = clip->y1; y < clip->y2; y++) { - if (buf) - sbuf = memcpy(buf, src, len); - else - sbuf = src; - dbuf = dst + clip->x1 * cpp; - - if (cpp == 4) - drm_fb_swab32_line(dbuf, sbuf, linepixels); - else - drm_fb_swab16_line(dbuf, sbuf, linepixels); - - src += fb->pitches[0]; - dst += dst_pitch; + switch (cpp) { + case 4: + drm_fb_xfrm(dst, dst_pitch, cpp, src, fb, clip, cached, drm_fb_swab32_line); + break; + case 2: + drm_fb_xfrm(dst, dst_pitch, cpp, src, fb, clip, cached, drm_fb_swab16_line); + break; + default: + drm_warn_once(fb->dev, "Format %p4cc has unsupported pixel size.\n", + &fb->format->format); + break; } - - kfree(buf); } EXPORT_SYMBOL(drm_fb_swab); -static void drm_fb_xrgb8888_to_rgb332_line(void *dbuf, const void *sbuf, unsigned int pixels, - bool swab) +static void drm_fb_xrgb8888_to_rgb332_line(void *dbuf, const void *sbuf, unsigned int pixels) { u8 *dbuf8 = dbuf; const __le32 *sbuf32 = sbuf; @@ -206,28 +272,7 @@ static void drm_fb_xrgb8888_to_rgb332_line(void *dbuf, const void *sbuf, unsigne void drm_fb_xrgb8888_to_rgb332(void *dst, unsigned int dst_pitch, const void *src, const struct drm_framebuffer *fb, const struct drm_rect *clip) { - size_t width = drm_rect_width(clip); - size_t src_len = width * sizeof(u32); - unsigned int y; - void *sbuf; - - if (!dst_pitch) - dst_pitch = width; - - /* Use a buffer to speed up access on buffers with uncached read mapping (i.e. WC) */ - sbuf = kmalloc(src_len, GFP_KERNEL); - if (!sbuf) - return; - - src += clip_offset(clip, fb->pitches[0], sizeof(u32)); - for (y = 0; y < drm_rect_height(clip); y++) { - memcpy(sbuf, src, src_len); - drm_fb_xrgb8888_to_rgb332_line(dst, sbuf, width, false); - src += fb->pitches[0]; - dst += dst_pitch; - } - - kfree(sbuf); + drm_fb_xfrm(dst, dst_pitch, 1, src, fb, clip, false, drm_fb_xrgb8888_to_rgb332_line); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb332); @@ -278,35 +323,12 @@ void drm_fb_xrgb8888_to_rgb565(void *dst, unsigned int dst_pitch, const void *va const struct drm_framebuffer *fb, const struct drm_rect *clip, bool swab) { - size_t linepixels = clip->x2 - clip->x1; - size_t src_len = linepixels * sizeof(u32); - size_t dst_len = linepixels * sizeof(u16); - unsigned y, lines = clip->y2 - clip->y1; - void *sbuf; - - if (!dst_pitch) - dst_pitch = dst_len; - - /* - * The cma memory is write-combined so reads are uncached. - * Speed up by fetching one line at a time. - */ - sbuf = kmalloc(src_len, GFP_KERNEL); - if (!sbuf) - return; - - vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); - for (y = 0; y < lines; y++) { - memcpy(sbuf, vaddr, src_len); - if (swab) - drm_fb_xrgb8888_to_rgb565_swab_line(dst, sbuf, linepixels); - else - drm_fb_xrgb8888_to_rgb565_line(dst, sbuf, linepixels); - vaddr += fb->pitches[0]; - dst += dst_pitch; - } - - kfree(sbuf); + if (swab) + drm_fb_xfrm(dst, dst_pitch, 2, vaddr, fb, clip, false, + drm_fb_xrgb8888_to_rgb565_swab_line); + else + drm_fb_xfrm(dst, dst_pitch, 2, vaddr, fb, clip, false, + drm_fb_xrgb8888_to_rgb565_line); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565); @@ -326,30 +348,12 @@ void drm_fb_xrgb8888_to_rgb565_toio(void __iomem *dst, unsigned int dst_pitch, const void *vaddr, const struct drm_framebuffer *fb, const struct drm_rect *clip, bool swab) { - size_t linepixels = clip->x2 - clip->x1; - size_t dst_len = linepixels * sizeof(u16); - unsigned y, lines = clip->y2 - clip->y1; - void *dbuf; - - if (!dst_pitch) - dst_pitch = dst_len; - - dbuf = kmalloc(dst_len, GFP_KERNEL); - if (!dbuf) - return; - - vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); - for (y = 0; y < lines; y++) { - if (swab) - drm_fb_xrgb8888_to_rgb565_swab_line(dbuf, vaddr, linepixels); - else - drm_fb_xrgb8888_to_rgb565_line(dbuf, vaddr, linepixels); - memcpy_toio(dst, dbuf, dst_len); - vaddr += fb->pitches[0]; - dst += dst_pitch; - } - - kfree(dbuf); + if (swab) + drm_fb_xfrm_toio(dst, dst_pitch, 2, vaddr, fb, clip, false, + drm_fb_xrgb8888_to_rgb565_swab_line); + else + drm_fb_xfrm_toio(dst, dst_pitch, 2, vaddr, fb, clip, false, + drm_fb_xrgb8888_to_rgb565_line); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565_toio); @@ -380,28 +384,7 @@ static void drm_fb_xrgb8888_to_rgb888_line(void *dbuf, const void *sbuf, unsigne void drm_fb_xrgb8888_to_rgb888(void *dst, unsigned int dst_pitch, const void *src, const struct drm_framebuffer *fb, const struct drm_rect *clip) { - size_t width = drm_rect_width(clip); - size_t src_len = width * sizeof(u32); - unsigned int y; - void *sbuf; - - if (!dst_pitch) - dst_pitch = width * 3; - - /* Use a buffer to speed up access on buffers with uncached read mapping (i.e. WC) */ - sbuf = kmalloc(src_len, GFP_KERNEL); - if (!sbuf) - return; - - src += clip_offset(clip, fb->pitches[0], sizeof(u32)); - for (y = 0; y < drm_rect_height(clip); y++) { - memcpy(sbuf, src, src_len); - drm_fb_xrgb8888_to_rgb888_line(dst, sbuf, width); - src += fb->pitches[0]; - dst += dst_pitch; - } - - kfree(sbuf); + drm_fb_xfrm(dst, dst_pitch, 3, src, fb, clip, false, drm_fb_xrgb8888_to_rgb888_line); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888); @@ -420,27 +403,8 @@ void drm_fb_xrgb8888_to_rgb888_toio(void __iomem *dst, unsigned int dst_pitch, const void *vaddr, const struct drm_framebuffer *fb, const struct drm_rect *clip) { - size_t linepixels = clip->x2 - clip->x1; - size_t dst_len = linepixels * 3; - unsigned y, lines = clip->y2 - clip->y1; - void *dbuf; - - if (!dst_pitch) - dst_pitch = dst_len; - - dbuf = kmalloc(dst_len, GFP_KERNEL); - if (!dbuf) - return; - - vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); - for (y = 0; y < lines; y++) { - drm_fb_xrgb8888_to_rgb888_line(dbuf, vaddr, linepixels); - memcpy_toio(dst, dbuf, dst_len); - vaddr += fb->pitches[0]; - dst += dst_pitch; - } - - kfree(dbuf); + drm_fb_xfrm_toio(dst, dst_pitch, 3, vaddr, fb, clip, false, + drm_fb_xrgb8888_to_rgb888_line); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888_toio); @@ -464,27 +428,8 @@ static void drm_fb_rgb565_to_xrgb8888_toio(void __iomem *dst, unsigned int dst_p const void *vaddr, const struct drm_framebuffer *fb, const struct drm_rect *clip) { - size_t linepixels = drm_rect_width(clip); - size_t dst_len = linepixels * 4; - unsigned int y, lines = drm_rect_height(clip); - void *dbuf; - - if (!dst_pitch) - dst_pitch = dst_len; - - dbuf = kmalloc(dst_len, GFP_KERNEL); - if (!dbuf) - return; - - vaddr += clip_offset(clip, fb->pitches[0], 2); - for (y = 0; y < lines; y++) { - drm_fb_rgb565_to_xrgb8888_line(dbuf, vaddr, linepixels); - memcpy_toio(dst, dbuf, dst_len); - vaddr += fb->pitches[0]; - dst += dst_pitch; - } - - kfree(dbuf); + drm_fb_xfrm_toio(dst, dst_pitch, 4, vaddr, fb, clip, false, + drm_fb_rgb565_to_xrgb8888_line); } static void drm_fb_rgb888_to_xrgb8888_line(void *dbuf, const void *sbuf, unsigned int pixels) @@ -505,27 +450,8 @@ static void drm_fb_rgb888_to_xrgb8888_toio(void __iomem *dst, unsigned int dst_p const void *vaddr, const struct drm_framebuffer *fb, const struct drm_rect *clip) { - size_t linepixels = drm_rect_width(clip); - size_t dst_len = linepixels * 4; - unsigned int y, lines = drm_rect_height(clip); - void *dbuf; - - if (!dst_pitch) - dst_pitch = dst_len; - - dbuf = kmalloc(dst_len, GFP_KERNEL); - if (!dbuf) - return; - - vaddr += clip_offset(clip, fb->pitches[0], 3); - for (y = 0; y < lines; y++) { - drm_fb_rgb888_to_xrgb8888_line(dbuf, vaddr, linepixels); - memcpy_toio(dst, dbuf, dst_len); - vaddr += fb->pitches[0]; - dst += dst_pitch; - } - - kfree(dbuf); + drm_fb_xfrm_toio(dst, dst_pitch, 4, vaddr, fb, clip, false, + drm_fb_rgb888_to_xrgb8888_line); } static void drm_fb_xrgb8888_to_xrgb2101010_line(void *dbuf, const void *sbuf, unsigned int pixels) @@ -560,27 +486,8 @@ void drm_fb_xrgb8888_to_xrgb2101010_toio(void __iomem *dst, const struct drm_framebuffer *fb, const struct drm_rect *clip) { - size_t linepixels = clip->x2 - clip->x1; - size_t dst_len = linepixels * sizeof(u32); - unsigned int y, lines = clip->y2 - clip->y1; - void *dbuf; - - if (!dst_pitch) - dst_pitch = dst_len; - - dbuf = kmalloc(dst_len, GFP_KERNEL); - if (!dbuf) - return; - - vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); - for (y = 0; y < lines; y++) { - drm_fb_xrgb8888_to_xrgb2101010_line(dbuf, vaddr, linepixels); - memcpy_toio(dst, dbuf, dst_len); - vaddr += fb->pitches[0]; - dst += dst_pitch; - } - - kfree(dbuf); + drm_fb_xfrm_toio(dst, dst_pitch, 4, vaddr, fb, clip, false, + drm_fb_xrgb8888_to_xrgb2101010_line); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_xrgb2101010_toio); @@ -621,37 +528,7 @@ static void drm_fb_xrgb8888_to_gray8_line(void *dbuf, const void *sbuf, unsigned void drm_fb_xrgb8888_to_gray8(void *dst, unsigned int dst_pitch, const void *vaddr, const struct drm_framebuffer *fb, const struct drm_rect *clip) { - unsigned int linepixels = clip->x2 - clip->x1; - unsigned int len = linepixels * sizeof(u32); - unsigned int y; - void *buf; - u8 *dst8; - u32 *src32; - - if (WARN_ON(fb->format->format != DRM_FORMAT_XRGB8888)) - return; - - if (!dst_pitch) - dst_pitch = drm_rect_width(clip); - - /* - * The cma memory is write-combined so reads are uncached. - * Speed up by fetching one line at a time. - */ - buf = kmalloc(len, GFP_KERNEL); - if (!buf) - return; - - vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); - for (y = clip->y1; y < clip->y2; y++) { - dst8 = dst; - src32 = memcpy(buf, vaddr, len); - drm_fb_xrgb8888_to_gray8_line(dst8, src32, linepixels); - vaddr += fb->pitches[0]; - dst += dst_pitch; - } - - kfree(buf); + drm_fb_xfrm(dst, dst_pitch, 1, vaddr, fb, clip, false, drm_fb_xrgb8888_to_gray8_line); } EXPORT_SYMBOL(drm_fb_xrgb8888_to_gray8); -- cgit v1.2.3 From e13f13e039dc8f31168b5569120a7658035c80ee Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Mon, 2 May 2022 16:25:12 +0200 Subject: drm: Add DRM-managed mutex_init() Add drmm_mutex_init(), a helper that provides managed mutex cleanup. The mutex will be destroyed with the final reference of the DRM device. Signed-off-by: Thomas Zimmermann Reviewed-by: Jocelyn Falempe Reviewed-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220502142514.2174-2-tzimmermann@suse.de --- drivers/gpu/drm/drm_managed.c | 27 +++++++++++++++++++++++++++ include/drm/drm_managed.h | 3 +++ 2 files changed, 30 insertions(+) diff --git a/drivers/gpu/drm/drm_managed.c b/drivers/gpu/drm/drm_managed.c index 37d7db6223be..4cf214de50c4 100644 --- a/drivers/gpu/drm/drm_managed.c +++ b/drivers/gpu/drm/drm_managed.c @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -262,3 +263,29 @@ void drmm_kfree(struct drm_device *dev, void *data) free_dr(dr_match); } EXPORT_SYMBOL(drmm_kfree); + +static void drmm_mutex_release(struct drm_device *dev, void *res) +{ + struct mutex *lock = res; + + mutex_destroy(lock); +} + +/** + * drmm_mutex_init - &drm_device-managed mutex_init() + * @dev: DRM device + * @lock: lock to be initialized + * + * Returns: + * 0 on success, or a negative errno code otherwise. + * + * This is a &drm_device-managed version of mutex_init(). The initialized + * lock is automatically destroyed on the final drm_dev_put(). + */ +int drmm_mutex_init(struct drm_device *dev, struct mutex *lock) +{ + mutex_init(lock); + + return drmm_add_action_or_reset(dev, drmm_mutex_release, lock); +} +EXPORT_SYMBOL(drmm_mutex_init); diff --git a/include/drm/drm_managed.h b/include/drm/drm_managed.h index b45c6fbf53ac..359883942612 100644 --- a/include/drm/drm_managed.h +++ b/include/drm/drm_managed.h @@ -8,6 +8,7 @@ #include struct drm_device; +struct mutex; typedef void (*drmres_release_t)(struct drm_device *dev, void *res); @@ -104,4 +105,6 @@ char *drmm_kstrdup(struct drm_device *dev, const char *s, gfp_t gfp); void drmm_kfree(struct drm_device *dev, void *data); +int drmm_mutex_init(struct drm_device *dev, struct mutex *lock); + #endif -- cgit v1.2.3 From f870231fdd6ce2c6c040a3584a2eac97ae473780 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Mon, 2 May 2022 16:25:13 +0200 Subject: drm/ast: Protect concurrent access to I/O registers with lock Add a mutex lock to protect concurrent access to I/O registers against each other. This happens between invocation of commit- tail functions and get-mode operations. Both with use the CRTC index register AST_IO_CRTC_PORT. Concurrent access can lead to failed mode-setting operations. v2: * fix typo in commit description (Jocelyn) Signed-off-by: Thomas Zimmermann Reviewed-by: Jocelyn Falempe Acked-by: Daniel Vetter Reported-by: KuoHsiang Chou Link: https://patchwork.freedesktop.org/patch/msgid/20220502142514.2174-3-tzimmermann@suse.de --- drivers/gpu/drm/ast/ast_drv.h | 1 + drivers/gpu/drm/ast/ast_main.c | 4 ++++ drivers/gpu/drm/ast/ast_mode.c | 48 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/ast/ast_drv.h b/drivers/gpu/drm/ast/ast_drv.h index ff1ae314bd0d..afebe35f205e 100644 --- a/drivers/gpu/drm/ast/ast_drv.h +++ b/drivers/gpu/drm/ast/ast_drv.h @@ -159,6 +159,7 @@ to_ast_sil164_connector(struct drm_connector *connector) struct ast_private { struct drm_device base; + struct mutex ioregs_lock; /* Protects access to I/O registers in ioregs */ void __iomem *regs; void __iomem *ioregs; void __iomem *dp501_fw_buf; diff --git a/drivers/gpu/drm/ast/ast_main.c b/drivers/gpu/drm/ast/ast_main.c index 1113ee1cb7d4..d770d5a23c1a 100644 --- a/drivers/gpu/drm/ast/ast_main.c +++ b/drivers/gpu/drm/ast/ast_main.c @@ -421,6 +421,10 @@ struct ast_private *ast_device_create(const struct drm_driver *drv, pci_set_drvdata(pdev, dev); + ret = drmm_mutex_init(dev, &ast->ioregs_lock); + if (ret) + return ERR_PTR(ret); + ast->regs = pcim_iomap(pdev, 1, 0); if (!ast->regs) return ERR_PTR(-EIO); diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c index 4728825b7b67..323af2746aa9 100644 --- a/drivers/gpu/drm/ast/ast_mode.c +++ b/drivers/gpu/drm/ast/ast_mode.c @@ -1119,6 +1119,20 @@ static int ast_crtc_helper_atomic_check(struct drm_crtc *crtc, return 0; } +static void ast_crtc_helper_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct drm_device *dev = crtc->dev; + struct ast_private *ast = to_ast_private(dev); + + /* + * Concurrent operations could possibly trigger a call to + * drm_connector_helper_funcs.get_modes by trying to read the + * display modes. Protect access to I/O registers by acquiring + * the I/O-register lock. Released in atomic_flush(). + */ + mutex_lock(&ast->ioregs_lock); +} + static void ast_crtc_helper_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) @@ -1127,7 +1141,8 @@ ast_crtc_helper_atomic_flush(struct drm_crtc *crtc, crtc); struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc); - struct ast_private *ast = to_ast_private(crtc->dev); + struct drm_device *dev = crtc->dev; + struct ast_private *ast = to_ast_private(dev); struct ast_crtc_state *ast_crtc_state = to_ast_crtc_state(crtc_state); struct ast_crtc_state *old_ast_crtc_state = to_ast_crtc_state(old_crtc_state); struct ast_vbios_mode_info *vbios_mode_info = &ast_crtc_state->vbios_mode_info; @@ -1142,6 +1157,8 @@ ast_crtc_helper_atomic_flush(struct drm_crtc *crtc, //Set Aspeed Display-Port if (ast->tx_chip_type == AST_TX_ASTDP) ast_dp_set_mode(crtc, vbios_mode_info); + + mutex_unlock(&ast->ioregs_lock); } static void @@ -1200,6 +1217,7 @@ ast_crtc_helper_atomic_disable(struct drm_crtc *crtc, static const struct drm_crtc_helper_funcs ast_crtc_helper_funcs = { .mode_valid = ast_crtc_helper_mode_valid, .atomic_check = ast_crtc_helper_atomic_check, + .atomic_begin = ast_crtc_helper_atomic_begin, .atomic_flush = ast_crtc_helper_atomic_flush, .atomic_enable = ast_crtc_helper_atomic_enable, .atomic_disable = ast_crtc_helper_atomic_disable, @@ -1285,21 +1303,33 @@ static int ast_crtc_init(struct drm_device *dev) static int ast_vga_connector_helper_get_modes(struct drm_connector *connector) { struct ast_vga_connector *ast_vga_connector = to_ast_vga_connector(connector); + struct drm_device *dev = connector->dev; + struct ast_private *ast = to_ast_private(dev); struct edid *edid; int count; if (!ast_vga_connector->i2c) goto err_drm_connector_update_edid_property; + /* + * Protect access to I/O registers from concurrent modesetting + * by acquiring the I/O-register lock. + */ + mutex_lock(&ast->ioregs_lock); + edid = drm_get_edid(connector, &ast_vga_connector->i2c->adapter); if (!edid) - goto err_drm_connector_update_edid_property; + goto err_mutex_unlock; + + mutex_unlock(&ast->ioregs_lock); count = drm_add_edid_modes(connector, edid); kfree(edid); return count; +err_mutex_unlock: + mutex_unlock(&ast->ioregs_lock); err_drm_connector_update_edid_property: drm_connector_update_edid_property(connector, NULL); return 0; @@ -1379,21 +1409,33 @@ static int ast_vga_output_init(struct ast_private *ast) static int ast_sil164_connector_helper_get_modes(struct drm_connector *connector) { struct ast_sil164_connector *ast_sil164_connector = to_ast_sil164_connector(connector); + struct drm_device *dev = connector->dev; + struct ast_private *ast = to_ast_private(dev); struct edid *edid; int count; if (!ast_sil164_connector->i2c) goto err_drm_connector_update_edid_property; + /* + * Protect access to I/O registers from concurrent modesetting + * by acquiring the I/O-register lock. + */ + mutex_lock(&ast->ioregs_lock); + edid = drm_get_edid(connector, &ast_sil164_connector->i2c->adapter); if (!edid) - goto err_drm_connector_update_edid_property; + goto err_mutex_unlock; + + mutex_unlock(&ast->ioregs_lock); count = drm_add_edid_modes(connector, edid); kfree(edid); return count; +err_mutex_unlock: + mutex_unlock(&ast->ioregs_lock); err_drm_connector_update_edid_property: drm_connector_update_edid_property(connector, NULL); return 0; -- cgit v1.2.3 From 931e3f3a0e997c41eafbc88e4fc07ba9fef28f29 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Mon, 2 May 2022 16:25:14 +0200 Subject: drm/mgag200: Protect concurrent access to I/O registers with lock Add a mutex lock to protect concurrent access to I/O registers against each other. This happens between invocation of commit- tail functions and get-mode operations. Both with use the CRTC index registers MGA1064_GEN_IO_DATA and MGA1064_GEN_IO_CTL. Concurrent access can lead to failed mode-setting operations. v2: * fix typo in commit description (Jocelyn) * add comment to explain rmmio_lock Signed-off-by: Thomas Zimmermann Reviewed-by: Jocelyn Falempe Acked-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220502142514.2174-4-tzimmermann@suse.de --- drivers/gpu/drm/mgag200/mgag200_drv.c | 6 ++++++ drivers/gpu/drm/mgag200/mgag200_drv.h | 1 + drivers/gpu/drm/mgag200/mgag200_mode.c | 14 ++++++++++++++ 3 files changed, 21 insertions(+) diff --git a/drivers/gpu/drm/mgag200/mgag200_drv.c b/drivers/gpu/drm/mgag200/mgag200_drv.c index 217844d71ab5..08839460606f 100644 --- a/drivers/gpu/drm/mgag200/mgag200_drv.c +++ b/drivers/gpu/drm/mgag200/mgag200_drv.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -65,6 +66,11 @@ static int mgag200_regs_init(struct mga_device *mdev) struct pci_dev *pdev = to_pci_dev(dev->dev); u32 option, option2; u8 crtcext3; + int ret; + + ret = drmm_mutex_init(dev, &mdev->rmmio_lock); + if (ret) + return ret; switch (mdev->type) { case G200_PCI: diff --git a/drivers/gpu/drm/mgag200/mgag200_drv.h b/drivers/gpu/drm/mgag200/mgag200_drv.h index 4368112023f7..a18384c41fc4 100644 --- a/drivers/gpu/drm/mgag200/mgag200_drv.h +++ b/drivers/gpu/drm/mgag200/mgag200_drv.h @@ -213,6 +213,7 @@ struct mga_device { struct drm_device base; unsigned long flags; + struct mutex rmmio_lock; /* Protects access to rmmio */ resource_size_t rmmio_base; resource_size_t rmmio_size; void __iomem *rmmio; diff --git a/drivers/gpu/drm/mgag200/mgag200_mode.c b/drivers/gpu/drm/mgag200/mgag200_mode.c index 6e18d3bbd720..abde7655477d 100644 --- a/drivers/gpu/drm/mgag200/mgag200_mode.c +++ b/drivers/gpu/drm/mgag200/mgag200_mode.c @@ -881,6 +881,14 @@ mgag200_simple_display_pipe_enable(struct drm_simple_display_pipe *pipe, .y2 = fb->height, }; + /* + * Concurrent operations could possibly trigger a call to + * drm_connector_helper_funcs.get_modes by trying to read the + * display modes. Protect access to I/O registers by acquiring + * the I/O-register lock. + */ + mutex_lock(&mdev->rmmio_lock); + if (mdev->type == G200_WB || mdev->type == G200_EW3) mgag200_g200wb_hold_bmc(mdev); @@ -904,6 +912,8 @@ mgag200_simple_display_pipe_enable(struct drm_simple_display_pipe *pipe, mgag200_enable_display(mdev); mgag200_handle_damage(mdev, fb, &fullscreen, &shadow_plane_state->data[0]); + + mutex_unlock(&mdev->rmmio_lock); } static void @@ -963,8 +973,12 @@ mgag200_simple_display_pipe_update(struct drm_simple_display_pipe *pipe, if (!fb) return; + mutex_lock(&mdev->rmmio_lock); + if (drm_atomic_helper_damage_merged(old_state, state, &damage)) mgag200_handle_damage(mdev, fb, &damage, &shadow_plane_state->data[0]); + + mutex_unlock(&mdev->rmmio_lock); } static struct drm_crtc_state * -- cgit v1.2.3 From 0e1759b60ffeff949ea18c831b22822527bd6ad7 Mon Sep 17 00:00:00 2001 From: Minghao Chi Date: Thu, 5 May 2022 02:22:08 +0000 Subject: drm: simplify the return expression of ast_drm_resume() Simplify the return expression. Reported-by: Zeal Robot Signed-off-by: Minghao Chi Acked-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20220505022208.57157-1-chi.minghao@zte.com.cn --- drivers/gpu/drm/ast/ast_drv.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/gpu/drm/ast/ast_drv.c b/drivers/gpu/drm/ast/ast_drv.c index 7465c4f0156a..760b27971557 100644 --- a/drivers/gpu/drm/ast/ast_drv.c +++ b/drivers/gpu/drm/ast/ast_drv.c @@ -159,15 +159,10 @@ static int ast_drm_thaw(struct drm_device *dev) static int ast_drm_resume(struct drm_device *dev) { - int ret; - if (pci_enable_device(to_pci_dev(dev->dev))) return -EIO; - ret = ast_drm_thaw(dev); - if (ret) - return ret; - return 0; + return ast_drm_thaw(dev); } static int ast_pm_suspend(struct device *dev) -- cgit v1.2.3 From 596c35b1440e7e8a954c87ae73b3f7c4e877b055 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 4 May 2022 10:02:12 +0200 Subject: drm/todo: Add entry for using kunit in the subsystem The Kernel Unit Testing (KUnit) framework provides a common framework for unit tests within the Linux kernel. Having a test suite would allow to identify regressions earlier. Signed-off-by: Javier Martinez Canillas Acked-by: Daniel Vetter Acked-by: Maxime Ripard Acked-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20220504080212.713275-1-javierm@redhat.com --- Documentation/gpu/todo.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst index 127e76ee0b2d..10bfb50908d1 100644 --- a/Documentation/gpu/todo.rst +++ b/Documentation/gpu/todo.rst @@ -603,6 +603,20 @@ Level: Advanced Better Testing ============== +Add unit tests using the Kernel Unit Testing (KUnit) framework +-------------------------------------------------------------- + +The `KUnit `_ +provides a common framework for unit tests within the Linux kernel. Having a +test suite would allow to identify regressions earlier. + +A good candidate for the first unit tests are the format-conversion helpers in +``drm_format_helper.c``. + +Contact: Javier Martinez Canillas + +Level: Intermediate + Enable trinity for DRM ---------------------- -- cgit v1.2.3 From 6071c4c2a319da360b0bf2bc397d4fefad10b2c8 Mon Sep 17 00:00:00 2001 From: Christian König Date: Fri, 29 Apr 2022 15:29:08 +0200 Subject: drm/qxl: add drm_gem_plane_helper_prepare_fb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We could need to wait for the pin to complete here. Signed-off-by: Christian König Cc: Dave Airlie Cc: Gerd Hoffmann Cc: virtualization@lists.linux-foundation.org Cc: spice-devel@lists.freedesktop.org Reviewed-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/msgid/20220429134230.24334-4-christian.koenig@amd.com --- drivers/gpu/drm/qxl/qxl_display.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/qxl/qxl_display.c b/drivers/gpu/drm/qxl/qxl_display.c index 9a9c29b1d3e1..9a64fa4c7530 100644 --- a/drivers/gpu/drm/qxl/qxl_display.c +++ b/drivers/gpu/drm/qxl/qxl_display.c @@ -34,6 +34,7 @@ #include #include #include +#include #include "qxl_drv.h" #include "qxl_object.h" @@ -829,6 +830,7 @@ static int qxl_plane_prepare_fb(struct drm_plane *plane, struct qxl_device *qdev = to_qxl(plane->dev); struct drm_gem_object *obj; struct qxl_bo *user_bo; + int ret; if (!new_state->fb) return 0; @@ -852,7 +854,11 @@ static int qxl_plane_prepare_fb(struct drm_plane *plane, qxl_free_cursor(old_cursor_bo); } - return qxl_bo_pin(user_bo); + ret = qxl_bo_pin(user_bo); + if (ret) + return ret; + + return drm_gem_plane_helper_prepare_fb(plane, new_state); } static void qxl_plane_cleanup_fb(struct drm_plane *plane, -- cgit v1.2.3