From e41a2999f746e67f1230dd42930497d4e011eb9a Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Wed, 20 Apr 2022 09:24:11 +0200 Subject: drm/doc: Add sections about tiny drivers and external refs to intro page Learning about the DRM subsystem could be quite overwhelming for newcomers but there are lots of useful talks, slides and articles available that can help to understand the needed concepts and ease the learning curve. There are also simple DRM drivers that can be used as example about how a DRM driver should look like. Add sections to the introduction page, that contains references to these. Suggested-by: Daniel Vetter Signed-off-by: Javier Martinez Canillas Acked-by: Pekka Paalanen Acked-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20220420072411.15104-1-javierm@redhat.com --- Documentation/gpu/introduction.rst | 60 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) (limited to 'Documentation/gpu') diff --git a/Documentation/gpu/introduction.rst b/Documentation/gpu/introduction.rst index 25a56e9c0cfd..f05eccd2c07c 100644 --- a/Documentation/gpu/introduction.rst +++ b/Documentation/gpu/introduction.rst @@ -112,3 +112,63 @@ Please conduct yourself in a respectful and civilised manner when interacting with community members on mailing lists, IRC, or bug trackers. The community represents the project as a whole, and abusive or bullying behaviour is not tolerated by the project. + +Simple DRM drivers to use as examples +===================================== + +The DRM subsystem contains a lot of helper functions to ease writing drivers for +simple graphic devices. For example, the `drivers/gpu/drm/tiny/` directory has a +set of drivers that are simple enough to be implemented in a single source file. + +These drivers make use of the `struct drm_simple_display_pipe_funcs`, that hides +any complexity of the DRM subsystem and just requires drivers to implement a few +functions needed to operate the device. This could be used for devices that just +need a display pipeline with one full-screen scanout buffer feeding one output. + +The tiny DRM drivers are good examples to understand how DRM drivers should look +like. Since are just a few hundreds lines of code, they are quite easy to read. + +External References +=================== + +Delving into a Linux kernel subsystem for the first time can be an overwhelming +experience, one needs to get familiar with all the concepts and learn about the +subsystem's internals, among other details. + +To shallow the learning curve, this section contains a list of presentations +and documents that can be used to learn about DRM/KMS and graphics in general. + +There are different reasons why someone might want to get into DRM: porting an +existing fbdev driver, write a DRM driver for a new hardware, fixing bugs that +could face when working on the graphics user-space stack, etc. For this reason, +the learning material covers many aspects of the Linux graphics stack. From an +overview of the kernel and user-space stacks to very specific topics. + +The list is sorted in reverse chronological order, to keep the most up-to-date +material at the top. But all of them contain useful information, and it can be +valuable to go through older material to understand the rationale and context +in which the changes to the DRM subsystem were made. + +Conference talks +---------------- + +* `An Overview of the Linux and Userspace Graphics Stack `_ - Paul Kocialkowski (2020) +* `Getting pixels on screen on Linux: introduction to Kernel Mode Setting `_ - Simon Ser (2020) +* `Everything Great about Upstream Graphics `_ - Daniel Vetter (2019) +* `An introduction to the Linux DRM subsystem `_ - Maxime Ripard (2017) +* `Embrace the Atomic (Display) Age `_ - Daniel Vetter (2016) +* `Anatomy of an Atomic KMS Driver `_ - Laurent Pinchart (2015) +* `Atomic Modesetting for Drivers `_ - Daniel Vetter (2015) +* `Anatomy of an Embedded KMS Driver `_ - Laurent Pinchart (2013) + +Slides and articles +------------------- + +* `Understanding the Linux Graphics Stack `_ - Bootlin (2022) +* `DRM KMS overview `_ - STMicroelectronics (2021) +* `Linux graphic stack `_ - Nathan Gauër (2017) +* `Atomic mode setting design overview, part 1 `_ - Daniel Vetter (2015) +* `Atomic mode setting design overview, part 2 `_ - Daniel Vetter (2015) +* `The DRM/KMS subsystem from a newbie’s point of view `_ - Boris Brezillon (2014) +* `A brief introduction to the Linux graphics stack `_ - Iago Toral (2014) +* `The Linux Graphics Stack `_ - Jasper St. Pierre (2012) -- cgit v1.2.3-59-g8ed1b From da68386d9edb1f57abf886febe5c5169ebd4d2c9 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 21 Apr 2022 09:31:02 +0200 Subject: drm: Rename dp/ to display/ Rename dp/ to display/ to account for additional display-related helpers, such as HDMI. Update all related include statements. No functional changes. Various drivers, such as i915 and amdgpu, use similar naming scheme by putting code for video-output standards into a local display/ directory. The new directory's name is aligned with this convention. v2: * update commit message (Javier) Signed-off-by: Thomas Zimmermann Reviewed-by: Lyude Paul Reviewed-by: Javier Martinez Canillas Reviewed-by: Alex Deucher Link: https://patchwork.freedesktop.org/patch/msgid/20220421073108.19226-3-tzimmermann@suse.de --- Documentation/gpu/drm-kms-helpers.rst | 26 +- drivers/gpu/drm/Makefile | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h | 4 +- drivers/gpu/drm/amd/amdgpu/atombios_dp.c | 3 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 2 +- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 2 +- .../amd/display/amdgpu_dm/amdgpu_dm_mst_types.c | 4 +- drivers/gpu/drm/amd/display/dc/core/dc_link_dpcd.c | 2 +- drivers/gpu/drm/amd/display/dc/dsc/dc_dsc.c | 2 +- drivers/gpu/drm/amd/display/dc/os_types.h | 2 +- drivers/gpu/drm/amd/display/include/dpcd_defs.h | 2 +- drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h | 2 +- drivers/gpu/drm/bridge/analogix/analogix-anx6345.c | 2 +- drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c | 2 +- .../gpu/drm/bridge/analogix/analogix-i2c-dptx.c | 2 +- drivers/gpu/drm/bridge/analogix/analogix_dp_core.h | 2 +- drivers/gpu/drm/bridge/analogix/anx7625.c | 4 +- .../gpu/drm/bridge/cadence/cdns-mhdp8546-core.c | 2 +- .../gpu/drm/bridge/cadence/cdns-mhdp8546-core.h | 2 +- drivers/gpu/drm/bridge/ite-it6505.c | 2 +- drivers/gpu/drm/bridge/parade-ps8640.c | 4 +- drivers/gpu/drm/bridge/tc358767.c | 2 +- drivers/gpu/drm/bridge/tc358775.c | 2 +- drivers/gpu/drm/bridge/ti-sn65dsi86.c | 4 +- drivers/gpu/drm/display/Makefile | 9 + drivers/gpu/drm/display/drm_dp.c | 3855 +++++++++++++ drivers/gpu/drm/display/drm_dp_aux_bus.c | 323 ++ drivers/gpu/drm/display/drm_dp_aux_dev.c | 354 ++ drivers/gpu/drm/display/drm_dp_cec.c | 451 ++ drivers/gpu/drm/display/drm_dp_dual_mode_helper.c | 530 ++ drivers/gpu/drm/display/drm_dp_helper_internal.h | 33 + drivers/gpu/drm/display/drm_dp_helper_mod.c | 22 + drivers/gpu/drm/display/drm_dp_mst_topology.c | 5978 ++++++++++++++++++++ .../gpu/drm/display/drm_dp_mst_topology_internal.h | 24 + drivers/gpu/drm/dp/Makefile | 9 - drivers/gpu/drm/dp/drm_dp.c | 3855 ------------- drivers/gpu/drm/dp/drm_dp_aux_bus.c | 323 -- drivers/gpu/drm/dp/drm_dp_aux_dev.c | 354 -- drivers/gpu/drm/dp/drm_dp_cec.c | 451 -- drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c | 530 -- drivers/gpu/drm/dp/drm_dp_helper_internal.h | 33 - drivers/gpu/drm/dp/drm_dp_helper_mod.c | 22 - drivers/gpu/drm/dp/drm_dp_mst_topology.c | 5978 -------------------- drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h | 24 - drivers/gpu/drm/drm_dsc.c | 3 +- drivers/gpu/drm/gma500/cdv_intel_dp.c | 2 +- drivers/gpu/drm/gma500/intel_bios.c | 3 +- drivers/gpu/drm/i915/display/intel_bios.c | 2 +- drivers/gpu/drm/i915/display/intel_display.c | 2 +- drivers/gpu/drm/i915/display/intel_display_types.h | 4 +- drivers/gpu/drm/i915/display/intel_dp.c | 2 +- drivers/gpu/drm/i915/display/intel_dp_hdcp.c | 4 +- .../gpu/drm/i915/display/intel_dp_link_training.h | 2 +- drivers/gpu/drm/i915/display/intel_lspcon.c | 2 +- drivers/gpu/drm/msm/dp/dp_audio.c | 2 +- drivers/gpu/drm/msm/dp/dp_aux.h | 2 +- drivers/gpu/drm/msm/dp/dp_catalog.c | 2 +- drivers/gpu/drm/msm/dp/dp_ctrl.c | 3 +- drivers/gpu/drm/msm/edp/edp.h | 3 +- drivers/gpu/drm/msm/edp/edp_ctrl.c | 3 +- drivers/gpu/drm/nouveau/dispnv50/disp.c | 2 +- drivers/gpu/drm/nouveau/nouveau_connector.h | 2 +- drivers/gpu/drm/nouveau/nouveau_dp.c | 2 +- drivers/gpu/drm/nouveau/nouveau_encoder.h | 6 +- drivers/gpu/drm/panel/panel-edp.c | 4 +- drivers/gpu/drm/panel/panel-samsung-atna33xc20.c | 4 +- drivers/gpu/drm/radeon/atombios_dp.c | 2 +- drivers/gpu/drm/radeon/radeon_connectors.c | 2 +- drivers/gpu/drm/radeon/radeon_dp_mst.c | 2 +- drivers/gpu/drm/radeon/radeon_mode.h | 4 +- drivers/gpu/drm/rockchip/analogix_dp-rockchip.c | 2 +- drivers/gpu/drm/rockchip/cdn-dp-core.c | 2 +- drivers/gpu/drm/rockchip/cdn-dp-core.h | 2 +- drivers/gpu/drm/rockchip/rockchip_lvds.c | 2 +- drivers/gpu/drm/rockchip/rockchip_rgb.c | 2 +- drivers/gpu/drm/selftests/test-drm_dp_mst_helper.c | 4 +- drivers/gpu/drm/tegra/dp.c | 2 +- drivers/gpu/drm/tegra/dpaux.c | 4 +- drivers/gpu/drm/tegra/sor.c | 2 +- drivers/gpu/drm/xlnx/zynqmp_dp.c | 2 +- include/drm/display/drm_dp_aux_bus.h | 57 + include/drm/display/drm_dp_dual_mode_helper.h | 121 + include/drm/display/drm_dp_helper.h | 2380 ++++++++ include/drm/display/drm_dp_mst_helper.h | 972 ++++ include/drm/dp/drm_dp_aux_bus.h | 57 - include/drm/dp/drm_dp_dual_mode_helper.h | 121 - include/drm/dp/drm_dp_helper.h | 2380 -------- include/drm/dp/drm_dp_mst_helper.h | 972 ---- include/drm/drm_dsc.h | 2 +- 90 files changed, 15204 insertions(+), 15196 deletions(-) create mode 100644 drivers/gpu/drm/display/Makefile create mode 100644 drivers/gpu/drm/display/drm_dp.c create mode 100644 drivers/gpu/drm/display/drm_dp_aux_bus.c create mode 100644 drivers/gpu/drm/display/drm_dp_aux_dev.c create mode 100644 drivers/gpu/drm/display/drm_dp_cec.c create mode 100644 drivers/gpu/drm/display/drm_dp_dual_mode_helper.c create mode 100644 drivers/gpu/drm/display/drm_dp_helper_internal.h create mode 100644 drivers/gpu/drm/display/drm_dp_helper_mod.c create mode 100644 drivers/gpu/drm/display/drm_dp_mst_topology.c create mode 100644 drivers/gpu/drm/display/drm_dp_mst_topology_internal.h delete mode 100644 drivers/gpu/drm/dp/Makefile delete mode 100644 drivers/gpu/drm/dp/drm_dp.c delete mode 100644 drivers/gpu/drm/dp/drm_dp_aux_bus.c delete mode 100644 drivers/gpu/drm/dp/drm_dp_aux_dev.c delete mode 100644 drivers/gpu/drm/dp/drm_dp_cec.c delete mode 100644 drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c delete mode 100644 drivers/gpu/drm/dp/drm_dp_helper_internal.h delete mode 100644 drivers/gpu/drm/dp/drm_dp_helper_mod.c delete mode 100644 drivers/gpu/drm/dp/drm_dp_mst_topology.c delete mode 100644 drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h create mode 100644 include/drm/display/drm_dp_aux_bus.h create mode 100644 include/drm/display/drm_dp_dual_mode_helper.h create mode 100644 include/drm/display/drm_dp_helper.h create mode 100644 include/drm/display/drm_dp_mst_helper.h delete mode 100644 include/drm/dp/drm_dp_aux_bus.h delete mode 100644 include/drm/dp/drm_dp_dual_mode_helper.h delete mode 100644 include/drm/dp/drm_dp_helper.h delete mode 100644 include/drm/dp/drm_dp_mst_helper.h (limited to 'Documentation/gpu') diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst index c3ce91eecbc1..2584f5bff66f 100644 --- a/Documentation/gpu/drm-kms-helpers.rst +++ b/Documentation/gpu/drm-kms-helpers.rst @@ -232,34 +232,34 @@ HDCP Helper Functions Reference Display Port Helper Functions Reference ======================================= -.. kernel-doc:: drivers/gpu/drm/dp/drm_dp.c +.. kernel-doc:: drivers/gpu/drm/display/drm_dp_helper.c :doc: dp helpers -.. kernel-doc:: include/drm/dp/drm_dp_helper.h +.. kernel-doc:: include/drm/display/drm_dp_helper.h :internal: -.. kernel-doc:: drivers/gpu/drm/dp/drm_dp.c +.. kernel-doc:: drivers/gpu/drm/display/drm_dp_helper.c :export: Display Port CEC Helper Functions Reference =========================================== -.. kernel-doc:: drivers/gpu/drm/dp/drm_dp_cec.c +.. kernel-doc:: drivers/gpu/drm/display/drm_dp_cec.c :doc: dp cec helpers -.. kernel-doc:: drivers/gpu/drm/dp/drm_dp_cec.c +.. kernel-doc:: drivers/gpu/drm/display/drm_dp_cec.c :export: Display Port Dual Mode Adaptor Helper Functions Reference ========================================================= -.. kernel-doc:: drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c +.. kernel-doc:: drivers/gpu/drm/display/drm_dp_dual_mode_helper.c :doc: dp dual mode helpers -.. kernel-doc:: include/drm/dp/drm_dp_dual_mode_helper.h +.. kernel-doc:: include/drm/display/drm_dp_dual_mode_helper.h :internal: -.. kernel-doc:: drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c +.. kernel-doc:: drivers/gpu/drm/display/drm_dp_dual_mode_helper.c :export: Display Port MST Helpers @@ -268,19 +268,19 @@ Display Port MST Helpers Overview -------- -.. kernel-doc:: drivers/gpu/drm/dp/drm_dp_mst_topology.c +.. kernel-doc:: drivers/gpu/drm/display/drm_dp_mst_topology.c :doc: dp mst helper -.. kernel-doc:: drivers/gpu/drm/dp/drm_dp_mst_topology.c +.. kernel-doc:: drivers/gpu/drm/display/drm_dp_mst_topology.c :doc: Branch device and port refcounting Functions Reference ------------------- -.. kernel-doc:: include/drm/dp/drm_dp_mst_helper.h +.. kernel-doc:: include/drm/display/drm_dp_mst_helper.h :internal: -.. kernel-doc:: drivers/gpu/drm/dp/drm_dp_mst_topology.c +.. kernel-doc:: drivers/gpu/drm/display/drm_dp_mst_topology.c :export: Topology Lifetime Internals @@ -289,7 +289,7 @@ Topology Lifetime Internals These functions aren't exported to drivers, but are documented here to help make the MST topology helpers easier to understand -.. kernel-doc:: drivers/gpu/drm/dp/drm_dp_mst_topology.c +.. kernel-doc:: drivers/gpu/drm/display/drm_dp_mst_topology.c :functions: drm_dp_mst_topology_try_get_mstb drm_dp_mst_topology_get_mstb drm_dp_mst_topology_put_mstb drm_dp_mst_topology_try_get_port drm_dp_mst_topology_get_port diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index e5929437e13c..07f7a70a78ea 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -80,7 +80,7 @@ obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/ obj-$(CONFIG_DRM_MIPI_DBI) += drm_mipi_dbi.o obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o obj-y += arm/ -obj-y += dp/ +obj-y += display/ obj-$(CONFIG_DRM_TTM) += ttm/ obj-$(CONFIG_DRM_SCHED) += scheduler/ obj-$(CONFIG_DRM_TDFX) += tdfx/ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c index 673078faa27a..b7933c2ce765 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_connectors.c @@ -24,9 +24,9 @@ * Alex Deucher */ +#include #include #include -#include #include #include #include "amdgpu.h" diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h index a546cb3cfa18..72c6f6cb7a44 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h @@ -30,10 +30,10 @@ #ifndef AMDGPU_MODE_H #define AMDGPU_MODE_H +#include #include #include #include -#include #include #include #include @@ -44,7 +44,7 @@ #include #include "amdgpu_irq.h" -#include +#include #include "modules/inc/mod_freesync.h" #include "amdgpu_dm_irq_params.h" diff --git a/drivers/gpu/drm/amd/amdgpu/atombios_dp.c b/drivers/gpu/drm/amd/amdgpu/atombios_dp.c index 49a2f594fb2c..87c41e0e9b7c 100644 --- a/drivers/gpu/drm/amd/amdgpu/atombios_dp.c +++ b/drivers/gpu/drm/amd/amdgpu/atombios_dp.c @@ -26,6 +26,8 @@ */ #include +#include + #include "amdgpu.h" #include "atom.h" @@ -34,7 +36,6 @@ #include "atombios_dp.h" #include "amdgpu_connectors.h" #include "amdgpu_atombios.h" -#include /* move these to drm_dp_helper.c/h */ #define DP_LINK_CONFIGURATION_SIZE 9 diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 91e9922b95b3..7ef20da6c18f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -73,10 +73,10 @@ #include #include +#include #include #include #include -#include #include #include #include diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h index 7e44b0429448..62dc5e30d73d 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h @@ -26,10 +26,10 @@ #ifndef __AMDGPU_DM_H__ #define __AMDGPU_DM_H__ +#include #include #include #include -#include #include /* diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c index 31ac1fce36f8..43efd915ee6f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c @@ -23,10 +23,10 @@ * */ +#include +#include #include #include -#include -#include #include "dm_services.h" #include "amdgpu.h" #include "amdgpu_dm.h" diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_link_dpcd.c b/drivers/gpu/drm/amd/display/dc/core/dc_link_dpcd.c index 48a18766f002..af110bf9470f 100644 --- a/drivers/gpu/drm/amd/display/dc/core/dc_link_dpcd.c +++ b/drivers/gpu/drm/amd/display/dc/core/dc_link_dpcd.c @@ -27,8 +27,8 @@ #include #include #include -#include #include +#include #include "dm_helpers.h" #define END_ADDRESS(start, size) (start + size - 1) diff --git a/drivers/gpu/drm/amd/display/dc/dsc/dc_dsc.c b/drivers/gpu/drm/amd/display/dc/dsc/dc_dsc.c index 4385d19bc489..dc82e04e36dc 100644 --- a/drivers/gpu/drm/amd/display/dc/dsc/dc_dsc.c +++ b/drivers/gpu/drm/amd/display/dc/dsc/dc_dsc.c @@ -25,7 +25,7 @@ #include #include "dc_hw_types.h" #include "dsc.h" -#include +#include #include "dc.h" #include "rc_calc.h" #include "fixed31_32.h" diff --git a/drivers/gpu/drm/amd/display/dc/os_types.h b/drivers/gpu/drm/amd/display/dc/os_types.h index 17d05071b809..981a9ed6fb61 100644 --- a/drivers/gpu/drm/amd/display/dc/os_types.h +++ b/drivers/gpu/drm/amd/display/dc/os_types.h @@ -35,8 +35,8 @@ #include +#include #include -#include #include "cgs_common.h" diff --git a/drivers/gpu/drm/amd/display/include/dpcd_defs.h b/drivers/gpu/drm/amd/display/include/dpcd_defs.h index ac822181359c..b2df07f9e91c 100644 --- a/drivers/gpu/drm/amd/display/include/dpcd_defs.h +++ b/drivers/gpu/drm/amd/display/include/dpcd_defs.h @@ -26,7 +26,7 @@ #ifndef __DAL_DPCD_DEFS_H__ #define __DAL_DPCD_DEFS_H__ -#include +#include #ifndef DP_SINK_HW_REVISION_START // can remove this once the define gets into linux drm_dp_helper.h #define DP_SINK_HW_REVISION_START 0x409 #endif diff --git a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h index 8502263d2968..6e88705e22f7 100644 --- a/drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h +++ b/drivers/gpu/drm/amd/display/modules/hdcp/hdcp.h @@ -29,8 +29,8 @@ #include "mod_hdcp.h" #include "hdcp_log.h" +#include #include -#include enum mod_hdcp_trans_input_result { UNKNOWN = 0, diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c index 94e56a2e91f2..ae3d6e9a606c 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c @@ -18,11 +18,11 @@ #include #include +#include #include #include #include #include -#include #include #include #include diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c index 2768b41c48e9..d2fc8676fab6 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c @@ -18,10 +18,10 @@ #include #include +#include #include #include #include -#include #include #include #include diff --git a/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c b/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c index e8297168bfef..b1e482994ffe 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c +++ b/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c @@ -7,8 +7,8 @@ */ #include +#include #include -#include #include #include "analogix-i2c-dptx.h" diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h index 32665203a6ae..433f2d7efa0c 100644 --- a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h @@ -9,8 +9,8 @@ #ifndef _ANALOGIX_DP_CORE_H #define _ANALOGIX_DP_CORE_H +#include #include -#include #define DP_TIMEOUT_LOOP_COUNT 100 #define MAX_CR_LOOP 5 diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 376da01243a3..be2c096374aa 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -21,11 +21,11 @@ #include #include +#include +#include #include #include #include -#include -#include #include #include #include diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c index ac18e15aa167..dec93a6d14c7 100644 --- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c @@ -35,13 +35,13 @@ #include #include +#include #include #include #include #include #include #include -#include #include #include #include diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h index fc77f987c835..bedddd510d17 100644 --- a/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h +++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h @@ -15,9 +15,9 @@ #include #include +#include #include #include -#include struct clk; struct device; diff --git a/drivers/gpu/drm/bridge/ite-it6505.c b/drivers/gpu/drm/bridge/ite-it6505.c index f2f101220ade..85cffc108278 100644 --- a/drivers/gpu/drm/bridge/ite-it6505.c +++ b/drivers/gpu/drm/bridge/ite-it6505.c @@ -21,7 +21,7 @@ #include -#include +#include #include #include #include diff --git a/drivers/gpu/drm/bridge/parade-ps8640.c b/drivers/gpu/drm/bridge/parade-ps8640.c index 9766cbbd62ad..edb939b14c04 100644 --- a/drivers/gpu/drm/bridge/parade-ps8640.c +++ b/drivers/gpu/drm/bridge/parade-ps8640.c @@ -13,9 +13,9 @@ #include #include +#include +#include #include -#include -#include #include #include #include diff --git a/drivers/gpu/drm/bridge/tc358767.c b/drivers/gpu/drm/bridge/tc358767.c index 12355daf45ef..b20b38661002 100644 --- a/drivers/gpu/drm/bridge/tc358767.c +++ b/drivers/gpu/drm/bridge/tc358767.c @@ -31,9 +31,9 @@ #include #include +#include #include #include -#include #include #include #include diff --git a/drivers/gpu/drm/bridge/tc358775.c b/drivers/gpu/drm/bridge/tc358775.c index b987e5ac80f0..62a7ef352daa 100644 --- a/drivers/gpu/drm/bridge/tc358775.c +++ b/drivers/gpu/drm/bridge/tc358775.c @@ -19,10 +19,10 @@ #include +#include #include #include #include -#include #include #include #include diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index 28d91ea5f9fd..8cad662de9bb 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -23,12 +23,12 @@ #include +#include +#include #include #include #include #include -#include -#include #include #include #include diff --git a/drivers/gpu/drm/display/Makefile b/drivers/gpu/drm/display/Makefile new file mode 100644 index 000000000000..75faffc706b1 --- /dev/null +++ b/drivers/gpu/drm/display/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: MIT + +obj-$(CONFIG_DRM_DP_AUX_BUS) += drm_dp_aux_bus.o + +drm_dp_helper-y := drm_dp.o drm_dp_dual_mode_helper.o drm_dp_helper_mod.o drm_dp_mst_topology.o +drm_dp_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o +drm_dp_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o + +obj-$(CONFIG_DRM_DP_HELPER) += drm_dp_helper.o diff --git a/drivers/gpu/drm/display/drm_dp.c b/drivers/gpu/drm/display/drm_dp.c new file mode 100644 index 000000000000..e7c22c2ca90c --- /dev/null +++ b/drivers/gpu/drm/display/drm_dp.c @@ -0,0 +1,3855 @@ +/* + * Copyright © 2009 Keith Packard + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "drm_dp_helper_internal.h" + +struct dp_aux_backlight { + struct backlight_device *base; + struct drm_dp_aux *aux; + struct drm_edp_backlight_info info; + bool enabled; +}; + +/** + * DOC: dp helpers + * + * These functions contain some common logic and helpers at various abstraction + * levels to deal with Display Port sink devices and related things like DP aux + * channel transfers, EDID reading over DP aux channels, decoding certain DPCD + * blocks, ... + */ + +/* Helpers for DP link training */ +static u8 dp_link_status(const u8 link_status[DP_LINK_STATUS_SIZE], int r) +{ + return link_status[r - DP_LANE0_1_STATUS]; +} + +static u8 dp_get_lane_status(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane) +{ + int i = DP_LANE0_1_STATUS + (lane >> 1); + int s = (lane & 1) * 4; + u8 l = dp_link_status(link_status, i); + + return (l >> s) & 0xf; +} + +bool drm_dp_channel_eq_ok(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane_count) +{ + u8 lane_align; + u8 lane_status; + int lane; + + lane_align = dp_link_status(link_status, + DP_LANE_ALIGN_STATUS_UPDATED); + if ((lane_align & DP_INTERLANE_ALIGN_DONE) == 0) + return false; + for (lane = 0; lane < lane_count; lane++) { + lane_status = dp_get_lane_status(link_status, lane); + if ((lane_status & DP_CHANNEL_EQ_BITS) != DP_CHANNEL_EQ_BITS) + return false; + } + return true; +} +EXPORT_SYMBOL(drm_dp_channel_eq_ok); + +bool drm_dp_clock_recovery_ok(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane_count) +{ + int lane; + u8 lane_status; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = dp_get_lane_status(link_status, lane); + if ((lane_status & DP_LANE_CR_DONE) == 0) + return false; + } + return true; +} +EXPORT_SYMBOL(drm_dp_clock_recovery_ok); + +u8 drm_dp_get_adjust_request_voltage(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane) +{ + int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); + int s = ((lane & 1) ? + DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT : + DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT); + u8 l = dp_link_status(link_status, i); + + return ((l >> s) & 0x3) << DP_TRAIN_VOLTAGE_SWING_SHIFT; +} +EXPORT_SYMBOL(drm_dp_get_adjust_request_voltage); + +u8 drm_dp_get_adjust_request_pre_emphasis(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane) +{ + int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); + int s = ((lane & 1) ? + DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT : + DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT); + u8 l = dp_link_status(link_status, i); + + return ((l >> s) & 0x3) << DP_TRAIN_PRE_EMPHASIS_SHIFT; +} +EXPORT_SYMBOL(drm_dp_get_adjust_request_pre_emphasis); + +/* DP 2.0 128b/132b */ +u8 drm_dp_get_adjust_tx_ffe_preset(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane) +{ + int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); + int s = ((lane & 1) ? + DP_ADJUST_TX_FFE_PRESET_LANE1_SHIFT : + DP_ADJUST_TX_FFE_PRESET_LANE0_SHIFT); + u8 l = dp_link_status(link_status, i); + + return (l >> s) & 0xf; +} +EXPORT_SYMBOL(drm_dp_get_adjust_tx_ffe_preset); + +/* DP 2.0 errata for 128b/132b */ +bool drm_dp_128b132b_lane_channel_eq_done(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane_count) +{ + u8 lane_align, lane_status; + int lane; + + lane_align = dp_link_status(link_status, DP_LANE_ALIGN_STATUS_UPDATED); + if (!(lane_align & DP_INTERLANE_ALIGN_DONE)) + return false; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = dp_get_lane_status(link_status, lane); + if (!(lane_status & DP_LANE_CHANNEL_EQ_DONE)) + return false; + } + return true; +} +EXPORT_SYMBOL(drm_dp_128b132b_lane_channel_eq_done); + +/* DP 2.0 errata for 128b/132b */ +bool drm_dp_128b132b_lane_symbol_locked(const u8 link_status[DP_LINK_STATUS_SIZE], + int lane_count) +{ + u8 lane_status; + int lane; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = dp_get_lane_status(link_status, lane); + if (!(lane_status & DP_LANE_SYMBOL_LOCKED)) + return false; + } + return true; +} +EXPORT_SYMBOL(drm_dp_128b132b_lane_symbol_locked); + +/* DP 2.0 errata for 128b/132b */ +bool drm_dp_128b132b_eq_interlane_align_done(const u8 link_status[DP_LINK_STATUS_SIZE]) +{ + u8 status = dp_link_status(link_status, DP_LANE_ALIGN_STATUS_UPDATED); + + return status & DP_128B132B_DPRX_EQ_INTERLANE_ALIGN_DONE; +} +EXPORT_SYMBOL(drm_dp_128b132b_eq_interlane_align_done); + +/* DP 2.0 errata for 128b/132b */ +bool drm_dp_128b132b_cds_interlane_align_done(const u8 link_status[DP_LINK_STATUS_SIZE]) +{ + u8 status = dp_link_status(link_status, DP_LANE_ALIGN_STATUS_UPDATED); + + return status & DP_128B132B_DPRX_CDS_INTERLANE_ALIGN_DONE; +} +EXPORT_SYMBOL(drm_dp_128b132b_cds_interlane_align_done); + +/* DP 2.0 errata for 128b/132b */ +bool drm_dp_128b132b_link_training_failed(const u8 link_status[DP_LINK_STATUS_SIZE]) +{ + u8 status = dp_link_status(link_status, DP_LANE_ALIGN_STATUS_UPDATED); + + return status & DP_128B132B_LT_FAILED; +} +EXPORT_SYMBOL(drm_dp_128b132b_link_training_failed); + +static int __8b10b_clock_recovery_delay_us(const struct drm_dp_aux *aux, u8 rd_interval) +{ + if (rd_interval > 4) + drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x (max 4)\n", + aux->name, rd_interval); + + if (rd_interval == 0) + return 100; + + return rd_interval * 4 * USEC_PER_MSEC; +} + +static int __8b10b_channel_eq_delay_us(const struct drm_dp_aux *aux, u8 rd_interval) +{ + if (rd_interval > 4) + drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x (max 4)\n", + aux->name, rd_interval); + + if (rd_interval == 0) + return 400; + + return rd_interval * 4 * USEC_PER_MSEC; +} + +static int __128b132b_channel_eq_delay_us(const struct drm_dp_aux *aux, u8 rd_interval) +{ + switch (rd_interval) { + default: + drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x\n", + aux->name, rd_interval); + fallthrough; + case DP_128B132B_TRAINING_AUX_RD_INTERVAL_400_US: + return 400; + case DP_128B132B_TRAINING_AUX_RD_INTERVAL_4_MS: + return 4000; + case DP_128B132B_TRAINING_AUX_RD_INTERVAL_8_MS: + return 8000; + case DP_128B132B_TRAINING_AUX_RD_INTERVAL_12_MS: + return 12000; + case DP_128B132B_TRAINING_AUX_RD_INTERVAL_16_MS: + return 16000; + case DP_128B132B_TRAINING_AUX_RD_INTERVAL_32_MS: + return 32000; + case DP_128B132B_TRAINING_AUX_RD_INTERVAL_64_MS: + return 64000; + } +} + +/* + * The link training delays are different for: + * + * - Clock recovery vs. channel equalization + * - DPRX vs. LTTPR + * - 128b/132b vs. 8b/10b + * - DPCD rev 1.3 vs. later + * + * Get the correct delay in us, reading DPCD if necessary. + */ +static int __read_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], + enum drm_dp_phy dp_phy, bool uhbr, bool cr) +{ + int (*parse)(const struct drm_dp_aux *aux, u8 rd_interval); + unsigned int offset; + u8 rd_interval, mask; + + if (dp_phy == DP_PHY_DPRX) { + if (uhbr) { + if (cr) + return 100; + + offset = DP_128B132B_TRAINING_AUX_RD_INTERVAL; + mask = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK; + parse = __128b132b_channel_eq_delay_us; + } else { + if (cr && dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14) + return 100; + + offset = DP_TRAINING_AUX_RD_INTERVAL; + mask = DP_TRAINING_AUX_RD_MASK; + if (cr) + parse = __8b10b_clock_recovery_delay_us; + else + parse = __8b10b_channel_eq_delay_us; + } + } else { + if (uhbr) { + offset = DP_128B132B_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy); + mask = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK; + parse = __128b132b_channel_eq_delay_us; + } else { + if (cr) + return 100; + + offset = DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy); + mask = DP_TRAINING_AUX_RD_MASK; + parse = __8b10b_channel_eq_delay_us; + } + } + + if (offset < DP_RECEIVER_CAP_SIZE) { + rd_interval = dpcd[offset]; + } else { + if (drm_dp_dpcd_readb(aux, offset, &rd_interval) != 1) { + drm_dbg_kms(aux->drm_dev, "%s: failed rd interval read\n", + aux->name); + /* arbitrary default delay */ + return 400; + } + } + + return parse(aux, rd_interval & mask); +} + +int drm_dp_read_clock_recovery_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], + enum drm_dp_phy dp_phy, bool uhbr) +{ + return __read_delay(aux, dpcd, dp_phy, uhbr, true); +} +EXPORT_SYMBOL(drm_dp_read_clock_recovery_delay); + +int drm_dp_read_channel_eq_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], + enum drm_dp_phy dp_phy, bool uhbr) +{ + return __read_delay(aux, dpcd, dp_phy, uhbr, false); +} +EXPORT_SYMBOL(drm_dp_read_channel_eq_delay); + +/* Per DP 2.0 Errata */ +int drm_dp_128b132b_read_aux_rd_interval(struct drm_dp_aux *aux) +{ + int unit; + u8 val; + + if (drm_dp_dpcd_readb(aux, DP_128B132B_TRAINING_AUX_RD_INTERVAL, &val) != 1) { + drm_err(aux->drm_dev, "%s: failed rd interval read\n", + aux->name); + /* default to max */ + val = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK; + } + + unit = (val & DP_128B132B_TRAINING_AUX_RD_INTERVAL_1MS_UNIT) ? 1 : 2; + val &= DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK; + + return (val + 1) * unit * 1000; +} +EXPORT_SYMBOL(drm_dp_128b132b_read_aux_rd_interval); + +void drm_dp_link_train_clock_recovery_delay(const struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + u8 rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] & + DP_TRAINING_AUX_RD_MASK; + int delay_us; + + if (dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14) + delay_us = 100; + else + delay_us = __8b10b_clock_recovery_delay_us(aux, rd_interval); + + usleep_range(delay_us, delay_us * 2); +} +EXPORT_SYMBOL(drm_dp_link_train_clock_recovery_delay); + +static void __drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux, + u8 rd_interval) +{ + int delay_us = __8b10b_channel_eq_delay_us(aux, rd_interval); + + usleep_range(delay_us, delay_us * 2); +} + +void drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + __drm_dp_link_train_channel_eq_delay(aux, + dpcd[DP_TRAINING_AUX_RD_INTERVAL] & + DP_TRAINING_AUX_RD_MASK); +} +EXPORT_SYMBOL(drm_dp_link_train_channel_eq_delay); + +void drm_dp_lttpr_link_train_clock_recovery_delay(void) +{ + usleep_range(100, 200); +} +EXPORT_SYMBOL(drm_dp_lttpr_link_train_clock_recovery_delay); + +static u8 dp_lttpr_phy_cap(const u8 phy_cap[DP_LTTPR_PHY_CAP_SIZE], int r) +{ + return phy_cap[r - DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1]; +} + +void drm_dp_lttpr_link_train_channel_eq_delay(const struct drm_dp_aux *aux, + const u8 phy_cap[DP_LTTPR_PHY_CAP_SIZE]) +{ + u8 interval = dp_lttpr_phy_cap(phy_cap, + DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1) & + DP_TRAINING_AUX_RD_MASK; + + __drm_dp_link_train_channel_eq_delay(aux, interval); +} +EXPORT_SYMBOL(drm_dp_lttpr_link_train_channel_eq_delay); + +u8 drm_dp_link_rate_to_bw_code(int link_rate) +{ + switch (link_rate) { + case 1000000: + return DP_LINK_BW_10; + case 1350000: + return DP_LINK_BW_13_5; + case 2000000: + return DP_LINK_BW_20; + default: + /* Spec says link_bw = link_rate / 0.27Gbps */ + return link_rate / 27000; + } +} +EXPORT_SYMBOL(drm_dp_link_rate_to_bw_code); + +int drm_dp_bw_code_to_link_rate(u8 link_bw) +{ + switch (link_bw) { + case DP_LINK_BW_10: + return 1000000; + case DP_LINK_BW_13_5: + return 1350000; + case DP_LINK_BW_20: + return 2000000; + default: + /* Spec says link_rate = link_bw * 0.27Gbps */ + return link_bw * 27000; + } +} +EXPORT_SYMBOL(drm_dp_bw_code_to_link_rate); + +#define AUX_RETRY_INTERVAL 500 /* us */ + +static inline void +drm_dp_dump_access(const struct drm_dp_aux *aux, + u8 request, uint offset, void *buffer, int ret) +{ + const char *arrow = request == DP_AUX_NATIVE_READ ? "->" : "<-"; + + if (ret > 0) + drm_dbg_dp(aux->drm_dev, "%s: 0x%05x AUX %s (ret=%3d) %*ph\n", + aux->name, offset, arrow, ret, min(ret, 20), buffer); + else + drm_dbg_dp(aux->drm_dev, "%s: 0x%05x AUX %s (ret=%3d)\n", + aux->name, offset, arrow, ret); +} + +/** + * DOC: dp helpers + * + * The DisplayPort AUX channel is an abstraction to allow generic, driver- + * independent access to AUX functionality. Drivers can take advantage of + * this by filling in the fields of the drm_dp_aux structure. + * + * Transactions are described using a hardware-independent drm_dp_aux_msg + * structure, which is passed into a driver's .transfer() implementation. + * Both native and I2C-over-AUX transactions are supported. + */ + +static int drm_dp_dpcd_access(struct drm_dp_aux *aux, u8 request, + unsigned int offset, void *buffer, size_t size) +{ + struct drm_dp_aux_msg msg; + unsigned int retry, native_reply; + int err = 0, ret = 0; + + memset(&msg, 0, sizeof(msg)); + msg.address = offset; + msg.request = request; + msg.buffer = buffer; + msg.size = size; + + mutex_lock(&aux->hw_mutex); + + /* + * The specification doesn't give any recommendation on how often to + * retry native transactions. We used to retry 7 times like for + * aux i2c transactions but real world devices this wasn't + * sufficient, bump to 32 which makes Dell 4k monitors happier. + */ + for (retry = 0; retry < 32; retry++) { + if (ret != 0 && ret != -ETIMEDOUT) { + usleep_range(AUX_RETRY_INTERVAL, + AUX_RETRY_INTERVAL + 100); + } + + ret = aux->transfer(aux, &msg); + if (ret >= 0) { + native_reply = msg.reply & DP_AUX_NATIVE_REPLY_MASK; + if (native_reply == DP_AUX_NATIVE_REPLY_ACK) { + if (ret == size) + goto unlock; + + ret = -EPROTO; + } else + ret = -EIO; + } + + /* + * We want the error we return to be the error we received on + * the first transaction, since we may get a different error the + * next time we retry + */ + if (!err) + err = ret; + } + + drm_dbg_kms(aux->drm_dev, "%s: Too many retries, giving up. First error: %d\n", + aux->name, err); + ret = err; + +unlock: + mutex_unlock(&aux->hw_mutex); + return ret; +} + +/** + * drm_dp_dpcd_probe() - probe a given DPCD address with a 1-byte read access + * @aux: DisplayPort AUX channel (SST) + * @offset: address of the register to probe + * + * Probe the provided DPCD address by reading 1 byte from it. The function can + * be used to trigger some side-effect the read access has, like waking up the + * sink, without the need for the read-out value. + * + * Returns 0 if the read access suceeded, or a negative error code on failure. + */ +int drm_dp_dpcd_probe(struct drm_dp_aux *aux, unsigned int offset) +{ + u8 buffer; + int ret; + + ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, offset, &buffer, 1); + WARN_ON(ret == 0); + + drm_dp_dump_access(aux, DP_AUX_NATIVE_READ, offset, &buffer, ret); + + return ret < 0 ? ret : 0; +} +EXPORT_SYMBOL(drm_dp_dpcd_probe); + +/** + * drm_dp_dpcd_read() - read a series of bytes from the DPCD + * @aux: DisplayPort AUX channel (SST or MST) + * @offset: address of the (first) register to read + * @buffer: buffer to store the register values + * @size: number of bytes in @buffer + * + * Returns the number of bytes transferred on success, or a negative error + * code on failure. -EIO is returned if the request was NAKed by the sink or + * if the retry count was exceeded. If not all bytes were transferred, this + * function returns -EPROTO. Errors from the underlying AUX channel transfer + * function, with the exception of -EBUSY (which causes the transaction to + * be retried), are propagated to the caller. + */ +ssize_t drm_dp_dpcd_read(struct drm_dp_aux *aux, unsigned int offset, + void *buffer, size_t size) +{ + int ret; + + /* + * HP ZR24w corrupts the first DPCD access after entering power save + * mode. Eg. on a read, the entire buffer will be filled with the same + * byte. Do a throw away read to avoid corrupting anything we care + * about. Afterwards things will work correctly until the monitor + * gets woken up and subsequently re-enters power save mode. + * + * The user pressing any button on the monitor is enough to wake it + * up, so there is no particularly good place to do the workaround. + * We just have to do it before any DPCD access and hope that the + * monitor doesn't power down exactly after the throw away read. + */ + if (!aux->is_remote) { + ret = drm_dp_dpcd_probe(aux, DP_DPCD_REV); + if (ret < 0) + return ret; + } + + if (aux->is_remote) + ret = drm_dp_mst_dpcd_read(aux, offset, buffer, size); + else + ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, offset, + buffer, size); + + drm_dp_dump_access(aux, DP_AUX_NATIVE_READ, offset, buffer, ret); + return ret; +} +EXPORT_SYMBOL(drm_dp_dpcd_read); + +/** + * drm_dp_dpcd_write() - write a series of bytes to the DPCD + * @aux: DisplayPort AUX channel (SST or MST) + * @offset: address of the (first) register to write + * @buffer: buffer containing the values to write + * @size: number of bytes in @buffer + * + * Returns the number of bytes transferred on success, or a negative error + * code on failure. -EIO is returned if the request was NAKed by the sink or + * if the retry count was exceeded. If not all bytes were transferred, this + * function returns -EPROTO. Errors from the underlying AUX channel transfer + * function, with the exception of -EBUSY (which causes the transaction to + * be retried), are propagated to the caller. + */ +ssize_t drm_dp_dpcd_write(struct drm_dp_aux *aux, unsigned int offset, + void *buffer, size_t size) +{ + int ret; + + if (aux->is_remote) + ret = drm_dp_mst_dpcd_write(aux, offset, buffer, size); + else + ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_WRITE, offset, + buffer, size); + + drm_dp_dump_access(aux, DP_AUX_NATIVE_WRITE, offset, buffer, ret); + return ret; +} +EXPORT_SYMBOL(drm_dp_dpcd_write); + +/** + * drm_dp_dpcd_read_link_status() - read DPCD link status (bytes 0x202-0x207) + * @aux: DisplayPort AUX channel + * @status: buffer to store the link status in (must be at least 6 bytes) + * + * Returns the number of bytes transferred on success or a negative error + * code on failure. + */ +int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux, + u8 status[DP_LINK_STATUS_SIZE]) +{ + return drm_dp_dpcd_read(aux, DP_LANE0_1_STATUS, status, + DP_LINK_STATUS_SIZE); +} +EXPORT_SYMBOL(drm_dp_dpcd_read_link_status); + +/** + * drm_dp_dpcd_read_phy_link_status - get the link status information for a DP PHY + * @aux: DisplayPort AUX channel + * @dp_phy: the DP PHY to get the link status for + * @link_status: buffer to return the status in + * + * Fetch the AUX DPCD registers for the DPRX or an LTTPR PHY link status. The + * layout of the returned @link_status matches the DPCD register layout of the + * DPRX PHY link status. + * + * Returns 0 if the information was read successfully or a negative error code + * on failure. + */ +int drm_dp_dpcd_read_phy_link_status(struct drm_dp_aux *aux, + enum drm_dp_phy dp_phy, + u8 link_status[DP_LINK_STATUS_SIZE]) +{ + int ret; + + if (dp_phy == DP_PHY_DPRX) { + ret = drm_dp_dpcd_read(aux, + DP_LANE0_1_STATUS, + link_status, + DP_LINK_STATUS_SIZE); + + if (ret < 0) + return ret; + + WARN_ON(ret != DP_LINK_STATUS_SIZE); + + return 0; + } + + ret = drm_dp_dpcd_read(aux, + DP_LANE0_1_STATUS_PHY_REPEATER(dp_phy), + link_status, + DP_LINK_STATUS_SIZE - 1); + + if (ret < 0) + return ret; + + WARN_ON(ret != DP_LINK_STATUS_SIZE - 1); + + /* Convert the LTTPR to the sink PHY link status layout */ + memmove(&link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS + 1], + &link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS], + DP_LINK_STATUS_SIZE - (DP_SINK_STATUS - DP_LANE0_1_STATUS) - 1); + link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS] = 0; + + return 0; +} +EXPORT_SYMBOL(drm_dp_dpcd_read_phy_link_status); + +static bool is_edid_digital_input_dp(const struct edid *edid) +{ + return edid && edid->revision >= 4 && + edid->input & DRM_EDID_INPUT_DIGITAL && + (edid->input & DRM_EDID_DIGITAL_TYPE_MASK) == DRM_EDID_DIGITAL_TYPE_DP; +} + +/** + * drm_dp_downstream_is_type() - is the downstream facing port of certain type? + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * @type: port type to be checked. Can be: + * %DP_DS_PORT_TYPE_DP, %DP_DS_PORT_TYPE_VGA, %DP_DS_PORT_TYPE_DVI, + * %DP_DS_PORT_TYPE_HDMI, %DP_DS_PORT_TYPE_NON_EDID, + * %DP_DS_PORT_TYPE_DP_DUALMODE or %DP_DS_PORT_TYPE_WIRELESS. + * + * Caveat: Only works with DPCD 1.1+ port caps. + * + * Returns: whether the downstream facing port matches the type. + */ +bool drm_dp_downstream_is_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], u8 type) +{ + return drm_dp_is_branch(dpcd) && + dpcd[DP_DPCD_REV] >= 0x11 && + (port_cap[0] & DP_DS_PORT_TYPE_MASK) == type; +} +EXPORT_SYMBOL(drm_dp_downstream_is_type); + +/** + * drm_dp_downstream_is_tmds() - is the downstream facing port TMDS? + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * @edid: EDID + * + * Returns: whether the downstream facing port is TMDS (HDMI/DVI). + */ +bool drm_dp_downstream_is_tmds(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid) +{ + if (dpcd[DP_DPCD_REV] < 0x11) { + switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { + case DP_DWN_STRM_PORT_TYPE_TMDS: + return true; + default: + return false; + } + } + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_DP_DUALMODE: + if (is_edid_digital_input_dp(edid)) + return false; + fallthrough; + case DP_DS_PORT_TYPE_DVI: + case DP_DS_PORT_TYPE_HDMI: + return true; + default: + return false; + } +} +EXPORT_SYMBOL(drm_dp_downstream_is_tmds); + +/** + * drm_dp_send_real_edid_checksum() - send back real edid checksum value + * @aux: DisplayPort AUX channel + * @real_edid_checksum: real edid checksum for the last block + * + * Returns: + * True on success + */ +bool drm_dp_send_real_edid_checksum(struct drm_dp_aux *aux, + u8 real_edid_checksum) +{ + u8 link_edid_read = 0, auto_test_req = 0, test_resp = 0; + + if (drm_dp_dpcd_read(aux, DP_DEVICE_SERVICE_IRQ_VECTOR, + &auto_test_req, 1) < 1) { + drm_err(aux->drm_dev, "%s: DPCD failed read at register 0x%x\n", + aux->name, DP_DEVICE_SERVICE_IRQ_VECTOR); + return false; + } + auto_test_req &= DP_AUTOMATED_TEST_REQUEST; + + if (drm_dp_dpcd_read(aux, DP_TEST_REQUEST, &link_edid_read, 1) < 1) { + drm_err(aux->drm_dev, "%s: DPCD failed read at register 0x%x\n", + aux->name, DP_TEST_REQUEST); + return false; + } + link_edid_read &= DP_TEST_LINK_EDID_READ; + + if (!auto_test_req || !link_edid_read) { + drm_dbg_kms(aux->drm_dev, "%s: Source DUT does not support TEST_EDID_READ\n", + aux->name); + return false; + } + + if (drm_dp_dpcd_write(aux, DP_DEVICE_SERVICE_IRQ_VECTOR, + &auto_test_req, 1) < 1) { + drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n", + aux->name, DP_DEVICE_SERVICE_IRQ_VECTOR); + return false; + } + + /* send back checksum for the last edid extension block data */ + if (drm_dp_dpcd_write(aux, DP_TEST_EDID_CHECKSUM, + &real_edid_checksum, 1) < 1) { + drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n", + aux->name, DP_TEST_EDID_CHECKSUM); + return false; + } + + test_resp |= DP_TEST_EDID_CHECKSUM_WRITE; + if (drm_dp_dpcd_write(aux, DP_TEST_RESPONSE, &test_resp, 1) < 1) { + drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n", + aux->name, DP_TEST_RESPONSE); + return false; + } + + return true; +} +EXPORT_SYMBOL(drm_dp_send_real_edid_checksum); + +static u8 drm_dp_downstream_port_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + u8 port_count = dpcd[DP_DOWN_STREAM_PORT_COUNT] & DP_PORT_COUNT_MASK; + + if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE && port_count > 4) + port_count = 4; + + return port_count; +} + +static int drm_dp_read_extended_dpcd_caps(struct drm_dp_aux *aux, + u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + u8 dpcd_ext[DP_RECEIVER_CAP_SIZE]; + int ret; + + /* + * Prior to DP1.3 the bit represented by + * DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT was reserved. + * If it is set DP_DPCD_REV at 0000h could be at a value less than + * the true capability of the panel. The only way to check is to + * then compare 0000h and 2200h. + */ + if (!(dpcd[DP_TRAINING_AUX_RD_INTERVAL] & + DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT)) + return 0; + + ret = drm_dp_dpcd_read(aux, DP_DP13_DPCD_REV, &dpcd_ext, + sizeof(dpcd_ext)); + if (ret < 0) + return ret; + if (ret != sizeof(dpcd_ext)) + return -EIO; + + if (dpcd[DP_DPCD_REV] > dpcd_ext[DP_DPCD_REV]) { + drm_dbg_kms(aux->drm_dev, + "%s: Extended DPCD rev less than base DPCD rev (%d > %d)\n", + aux->name, dpcd[DP_DPCD_REV], dpcd_ext[DP_DPCD_REV]); + return 0; + } + + if (!memcmp(dpcd, dpcd_ext, sizeof(dpcd_ext))) + return 0; + + drm_dbg_kms(aux->drm_dev, "%s: Base DPCD: %*ph\n", aux->name, DP_RECEIVER_CAP_SIZE, dpcd); + + memcpy(dpcd, dpcd_ext, sizeof(dpcd_ext)); + + return 0; +} + +/** + * drm_dp_read_dpcd_caps() - read DPCD caps and extended DPCD caps if + * available + * @aux: DisplayPort AUX channel + * @dpcd: Buffer to store the resulting DPCD in + * + * Attempts to read the base DPCD caps for @aux. Additionally, this function + * checks for and reads the extended DPRX caps (%DP_DP13_DPCD_REV) if + * present. + * + * Returns: %0 if the DPCD was read successfully, negative error code + * otherwise. + */ +int drm_dp_read_dpcd_caps(struct drm_dp_aux *aux, + u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + int ret; + + ret = drm_dp_dpcd_read(aux, DP_DPCD_REV, dpcd, DP_RECEIVER_CAP_SIZE); + if (ret < 0) + return ret; + if (ret != DP_RECEIVER_CAP_SIZE || dpcd[DP_DPCD_REV] == 0) + return -EIO; + + ret = drm_dp_read_extended_dpcd_caps(aux, dpcd); + if (ret < 0) + return ret; + + drm_dbg_kms(aux->drm_dev, "%s: DPCD: %*ph\n", aux->name, DP_RECEIVER_CAP_SIZE, dpcd); + + return ret; +} +EXPORT_SYMBOL(drm_dp_read_dpcd_caps); + +/** + * drm_dp_read_downstream_info() - read DPCD downstream port info if available + * @aux: DisplayPort AUX channel + * @dpcd: A cached copy of the port's DPCD + * @downstream_ports: buffer to store the downstream port info in + * + * See also: + * drm_dp_downstream_max_clock() + * drm_dp_downstream_max_bpc() + * + * Returns: 0 if either the downstream port info was read successfully or + * there was no downstream info to read, or a negative error code otherwise. + */ +int drm_dp_read_downstream_info(struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS]) +{ + int ret; + u8 len; + + memset(downstream_ports, 0, DP_MAX_DOWNSTREAM_PORTS); + + /* No downstream info to read */ + if (!drm_dp_is_branch(dpcd) || dpcd[DP_DPCD_REV] == DP_DPCD_REV_10) + return 0; + + /* Some branches advertise having 0 downstream ports, despite also advertising they have a + * downstream port present. The DP spec isn't clear on if this is allowed or not, but since + * some branches do it we need to handle it regardless. + */ + len = drm_dp_downstream_port_count(dpcd); + if (!len) + return 0; + + if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) + len *= 4; + + ret = drm_dp_dpcd_read(aux, DP_DOWNSTREAM_PORT_0, downstream_ports, len); + if (ret < 0) + return ret; + if (ret != len) + return -EIO; + + drm_dbg_kms(aux->drm_dev, "%s: DPCD DFP: %*ph\n", aux->name, len, downstream_ports); + + return 0; +} +EXPORT_SYMBOL(drm_dp_read_downstream_info); + +/** + * drm_dp_downstream_max_dotclock() - extract downstream facing port max dot clock + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * + * Returns: Downstream facing port max dot clock in kHz on success, + * or 0 if max clock not defined + */ +int drm_dp_downstream_max_dotclock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]) +{ + if (!drm_dp_is_branch(dpcd)) + return 0; + + if (dpcd[DP_DPCD_REV] < 0x11) + return 0; + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_VGA: + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return 0; + return port_cap[1] * 8000; + default: + return 0; + } +} +EXPORT_SYMBOL(drm_dp_downstream_max_dotclock); + +/** + * drm_dp_downstream_max_tmds_clock() - extract downstream facing port max TMDS clock + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * @edid: EDID + * + * Returns: HDMI/DVI downstream facing port max TMDS clock in kHz on success, + * or 0 if max TMDS clock not defined + */ +int drm_dp_downstream_max_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid) +{ + if (!drm_dp_is_branch(dpcd)) + return 0; + + if (dpcd[DP_DPCD_REV] < 0x11) { + switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { + case DP_DWN_STRM_PORT_TYPE_TMDS: + return 165000; + default: + return 0; + } + } + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_DP_DUALMODE: + if (is_edid_digital_input_dp(edid)) + return 0; + /* + * It's left up to the driver to check the + * DP dual mode adapter's max TMDS clock. + * + * Unfortunately it looks like branch devices + * may not fordward that the DP dual mode i2c + * access so we just usually get i2c nak :( + */ + fallthrough; + case DP_DS_PORT_TYPE_HDMI: + /* + * We should perhaps assume 165 MHz when detailed cap + * info is not available. But looks like many typical + * branch devices fall into that category and so we'd + * probably end up with users complaining that they can't + * get high resolution modes with their favorite dongle. + * + * So let's limit to 300 MHz instead since DPCD 1.4 + * HDMI 2.0 DFPs are required to have the detailed cap + * info. So it's more likely we're dealing with a HDMI 1.4 + * compatible* device here. + */ + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return 300000; + return port_cap[1] * 2500; + case DP_DS_PORT_TYPE_DVI: + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return 165000; + /* FIXME what to do about DVI dual link? */ + return port_cap[1] * 2500; + default: + return 0; + } +} +EXPORT_SYMBOL(drm_dp_downstream_max_tmds_clock); + +/** + * drm_dp_downstream_min_tmds_clock() - extract downstream facing port min TMDS clock + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * @edid: EDID + * + * Returns: HDMI/DVI downstream facing port min TMDS clock in kHz on success, + * or 0 if max TMDS clock not defined + */ +int drm_dp_downstream_min_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid) +{ + if (!drm_dp_is_branch(dpcd)) + return 0; + + if (dpcd[DP_DPCD_REV] < 0x11) { + switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { + case DP_DWN_STRM_PORT_TYPE_TMDS: + return 25000; + default: + return 0; + } + } + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_DP_DUALMODE: + if (is_edid_digital_input_dp(edid)) + return 0; + fallthrough; + case DP_DS_PORT_TYPE_DVI: + case DP_DS_PORT_TYPE_HDMI: + /* + * Unclear whether the protocol converter could + * utilize pixel replication. Assume it won't. + */ + return 25000; + default: + return 0; + } +} +EXPORT_SYMBOL(drm_dp_downstream_min_tmds_clock); + +/** + * drm_dp_downstream_max_bpc() - extract downstream facing port max + * bits per component + * @dpcd: DisplayPort configuration data + * @port_cap: downstream facing port capabilities + * @edid: EDID + * + * Returns: Max bpc on success or 0 if max bpc not defined + */ +int drm_dp_downstream_max_bpc(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid) +{ + if (!drm_dp_is_branch(dpcd)) + return 0; + + if (dpcd[DP_DPCD_REV] < 0x11) { + switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { + case DP_DWN_STRM_PORT_TYPE_DP: + return 0; + default: + return 8; + } + } + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_DP: + return 0; + case DP_DS_PORT_TYPE_DP_DUALMODE: + if (is_edid_digital_input_dp(edid)) + return 0; + fallthrough; + case DP_DS_PORT_TYPE_HDMI: + case DP_DS_PORT_TYPE_DVI: + case DP_DS_PORT_TYPE_VGA: + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return 8; + + switch (port_cap[2] & DP_DS_MAX_BPC_MASK) { + case DP_DS_8BPC: + return 8; + case DP_DS_10BPC: + return 10; + case DP_DS_12BPC: + return 12; + case DP_DS_16BPC: + return 16; + default: + return 8; + } + break; + default: + return 8; + } +} +EXPORT_SYMBOL(drm_dp_downstream_max_bpc); + +/** + * drm_dp_downstream_420_passthrough() - determine downstream facing port + * YCbCr 4:2:0 pass-through capability + * @dpcd: DisplayPort configuration data + * @port_cap: downstream facing port capabilities + * + * Returns: whether the downstream facing port can pass through YCbCr 4:2:0 + */ +bool drm_dp_downstream_420_passthrough(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]) +{ + if (!drm_dp_is_branch(dpcd)) + return false; + + if (dpcd[DP_DPCD_REV] < 0x13) + return false; + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_DP: + return true; + case DP_DS_PORT_TYPE_HDMI: + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return false; + + return port_cap[3] & DP_DS_HDMI_YCBCR420_PASS_THROUGH; + default: + return false; + } +} +EXPORT_SYMBOL(drm_dp_downstream_420_passthrough); + +/** + * drm_dp_downstream_444_to_420_conversion() - determine downstream facing port + * YCbCr 4:4:4->4:2:0 conversion capability + * @dpcd: DisplayPort configuration data + * @port_cap: downstream facing port capabilities + * + * Returns: whether the downstream facing port can convert YCbCr 4:4:4 to 4:2:0 + */ +bool drm_dp_downstream_444_to_420_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]) +{ + if (!drm_dp_is_branch(dpcd)) + return false; + + if (dpcd[DP_DPCD_REV] < 0x13) + return false; + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_HDMI: + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return false; + + return port_cap[3] & DP_DS_HDMI_YCBCR444_TO_420_CONV; + default: + return false; + } +} +EXPORT_SYMBOL(drm_dp_downstream_444_to_420_conversion); + +/** + * drm_dp_downstream_rgb_to_ycbcr_conversion() - determine downstream facing port + * RGB->YCbCr conversion capability + * @dpcd: DisplayPort configuration data + * @port_cap: downstream facing port capabilities + * @color_spc: Colorspace for which conversion cap is sought + * + * Returns: whether the downstream facing port can convert RGB->YCbCr for a given + * colorspace. + */ +bool drm_dp_downstream_rgb_to_ycbcr_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + u8 color_spc) +{ + if (!drm_dp_is_branch(dpcd)) + return false; + + if (dpcd[DP_DPCD_REV] < 0x13) + return false; + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_HDMI: + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) + return false; + + return port_cap[3] & color_spc; + default: + return false; + } +} +EXPORT_SYMBOL(drm_dp_downstream_rgb_to_ycbcr_conversion); + +/** + * drm_dp_downstream_mode() - return a mode for downstream facing port + * @dev: DRM device + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * + * Provides a suitable mode for downstream facing ports without EDID. + * + * Returns: A new drm_display_mode on success or NULL on failure + */ +struct drm_display_mode * +drm_dp_downstream_mode(struct drm_device *dev, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]) + +{ + u8 vic; + + if (!drm_dp_is_branch(dpcd)) + return NULL; + + if (dpcd[DP_DPCD_REV] < 0x11) + return NULL; + + switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { + case DP_DS_PORT_TYPE_NON_EDID: + switch (port_cap[0] & DP_DS_NON_EDID_MASK) { + case DP_DS_NON_EDID_720x480i_60: + vic = 6; + break; + case DP_DS_NON_EDID_720x480i_50: + vic = 21; + break; + case DP_DS_NON_EDID_1920x1080i_60: + vic = 5; + break; + case DP_DS_NON_EDID_1920x1080i_50: + vic = 20; + break; + case DP_DS_NON_EDID_1280x720_60: + vic = 4; + break; + case DP_DS_NON_EDID_1280x720_50: + vic = 19; + break; + default: + return NULL; + } + return drm_display_mode_from_cea_vic(dev, vic); + default: + return NULL; + } +} +EXPORT_SYMBOL(drm_dp_downstream_mode); + +/** + * drm_dp_downstream_id() - identify branch device + * @aux: DisplayPort AUX channel + * @id: DisplayPort branch device id + * + * Returns branch device id on success or NULL on failure + */ +int drm_dp_downstream_id(struct drm_dp_aux *aux, char id[6]) +{ + return drm_dp_dpcd_read(aux, DP_BRANCH_ID, id, 6); +} +EXPORT_SYMBOL(drm_dp_downstream_id); + +/** + * drm_dp_downstream_debug() - debug DP branch devices + * @m: pointer for debugfs file + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * @edid: EDID + * @aux: DisplayPort AUX channel + * + */ +void drm_dp_downstream_debug(struct seq_file *m, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4], + const struct edid *edid, + struct drm_dp_aux *aux) +{ + bool detailed_cap_info = dpcd[DP_DOWNSTREAMPORT_PRESENT] & + DP_DETAILED_CAP_INFO_AVAILABLE; + int clk; + int bpc; + char id[7]; + int len; + uint8_t rev[2]; + int type = port_cap[0] & DP_DS_PORT_TYPE_MASK; + bool branch_device = drm_dp_is_branch(dpcd); + + seq_printf(m, "\tDP branch device present: %s\n", + str_yes_no(branch_device)); + + if (!branch_device) + return; + + switch (type) { + case DP_DS_PORT_TYPE_DP: + seq_puts(m, "\t\tType: DisplayPort\n"); + break; + case DP_DS_PORT_TYPE_VGA: + seq_puts(m, "\t\tType: VGA\n"); + break; + case DP_DS_PORT_TYPE_DVI: + seq_puts(m, "\t\tType: DVI\n"); + break; + case DP_DS_PORT_TYPE_HDMI: + seq_puts(m, "\t\tType: HDMI\n"); + break; + case DP_DS_PORT_TYPE_NON_EDID: + seq_puts(m, "\t\tType: others without EDID support\n"); + break; + case DP_DS_PORT_TYPE_DP_DUALMODE: + seq_puts(m, "\t\tType: DP++\n"); + break; + case DP_DS_PORT_TYPE_WIRELESS: + seq_puts(m, "\t\tType: Wireless\n"); + break; + default: + seq_puts(m, "\t\tType: N/A\n"); + } + + memset(id, 0, sizeof(id)); + drm_dp_downstream_id(aux, id); + seq_printf(m, "\t\tID: %s\n", id); + + len = drm_dp_dpcd_read(aux, DP_BRANCH_HW_REV, &rev[0], 1); + if (len > 0) + seq_printf(m, "\t\tHW: %d.%d\n", + (rev[0] & 0xf0) >> 4, rev[0] & 0xf); + + len = drm_dp_dpcd_read(aux, DP_BRANCH_SW_REV, rev, 2); + if (len > 0) + seq_printf(m, "\t\tSW: %d.%d\n", rev[0], rev[1]); + + if (detailed_cap_info) { + clk = drm_dp_downstream_max_dotclock(dpcd, port_cap); + if (clk > 0) + seq_printf(m, "\t\tMax dot clock: %d kHz\n", clk); + + clk = drm_dp_downstream_max_tmds_clock(dpcd, port_cap, edid); + if (clk > 0) + seq_printf(m, "\t\tMax TMDS clock: %d kHz\n", clk); + + clk = drm_dp_downstream_min_tmds_clock(dpcd, port_cap, edid); + if (clk > 0) + seq_printf(m, "\t\tMin TMDS clock: %d kHz\n", clk); + + bpc = drm_dp_downstream_max_bpc(dpcd, port_cap, edid); + + if (bpc > 0) + seq_printf(m, "\t\tMax bpc: %d\n", bpc); + } +} +EXPORT_SYMBOL(drm_dp_downstream_debug); + +/** + * drm_dp_subconnector_type() - get DP branch device type + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + */ +enum drm_mode_subconnector +drm_dp_subconnector_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]) +{ + int type; + if (!drm_dp_is_branch(dpcd)) + return DRM_MODE_SUBCONNECTOR_Native; + /* DP 1.0 approach */ + if (dpcd[DP_DPCD_REV] == DP_DPCD_REV_10) { + type = dpcd[DP_DOWNSTREAMPORT_PRESENT] & + DP_DWN_STRM_PORT_TYPE_MASK; + + switch (type) { + case DP_DWN_STRM_PORT_TYPE_TMDS: + /* Can be HDMI or DVI-D, DVI-D is a safer option */ + return DRM_MODE_SUBCONNECTOR_DVID; + case DP_DWN_STRM_PORT_TYPE_ANALOG: + /* Can be VGA or DVI-A, VGA is more popular */ + return DRM_MODE_SUBCONNECTOR_VGA; + case DP_DWN_STRM_PORT_TYPE_DP: + return DRM_MODE_SUBCONNECTOR_DisplayPort; + case DP_DWN_STRM_PORT_TYPE_OTHER: + default: + return DRM_MODE_SUBCONNECTOR_Unknown; + } + } + type = port_cap[0] & DP_DS_PORT_TYPE_MASK; + + switch (type) { + case DP_DS_PORT_TYPE_DP: + case DP_DS_PORT_TYPE_DP_DUALMODE: + return DRM_MODE_SUBCONNECTOR_DisplayPort; + case DP_DS_PORT_TYPE_VGA: + return DRM_MODE_SUBCONNECTOR_VGA; + case DP_DS_PORT_TYPE_DVI: + return DRM_MODE_SUBCONNECTOR_DVID; + case DP_DS_PORT_TYPE_HDMI: + return DRM_MODE_SUBCONNECTOR_HDMIA; + case DP_DS_PORT_TYPE_WIRELESS: + return DRM_MODE_SUBCONNECTOR_Wireless; + case DP_DS_PORT_TYPE_NON_EDID: + default: + return DRM_MODE_SUBCONNECTOR_Unknown; + } +} +EXPORT_SYMBOL(drm_dp_subconnector_type); + +/** + * drm_dp_set_subconnector_property - set subconnector for DP connector + * @connector: connector to set property on + * @status: connector status + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * + * Called by a driver on every detect event. + */ +void drm_dp_set_subconnector_property(struct drm_connector *connector, + enum drm_connector_status status, + const u8 *dpcd, + const u8 port_cap[4]) +{ + enum drm_mode_subconnector subconnector = DRM_MODE_SUBCONNECTOR_Unknown; + + if (status == connector_status_connected) + subconnector = drm_dp_subconnector_type(dpcd, port_cap); + drm_object_property_set_value(&connector->base, + connector->dev->mode_config.dp_subconnector_property, + subconnector); +} +EXPORT_SYMBOL(drm_dp_set_subconnector_property); + +/** + * drm_dp_read_sink_count_cap() - Check whether a given connector has a valid sink + * count + * @connector: The DRM connector to check + * @dpcd: A cached copy of the connector's DPCD RX capabilities + * @desc: A cached copy of the connector's DP descriptor + * + * See also: drm_dp_read_sink_count() + * + * Returns: %True if the (e)DP connector has a valid sink count that should + * be probed, %false otherwise. + */ +bool drm_dp_read_sink_count_cap(struct drm_connector *connector, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const struct drm_dp_desc *desc) +{ + /* Some eDP panels don't set a valid value for the sink count */ + return connector->connector_type != DRM_MODE_CONNECTOR_eDP && + dpcd[DP_DPCD_REV] >= DP_DPCD_REV_11 && + dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT && + !drm_dp_has_quirk(desc, DP_DPCD_QUIRK_NO_SINK_COUNT); +} +EXPORT_SYMBOL(drm_dp_read_sink_count_cap); + +/** + * drm_dp_read_sink_count() - Retrieve the sink count for a given sink + * @aux: The DP AUX channel to use + * + * See also: drm_dp_read_sink_count_cap() + * + * Returns: The current sink count reported by @aux, or a negative error code + * otherwise. + */ +int drm_dp_read_sink_count(struct drm_dp_aux *aux) +{ + u8 count; + int ret; + + ret = drm_dp_dpcd_readb(aux, DP_SINK_COUNT, &count); + if (ret < 0) + return ret; + if (ret != 1) + return -EIO; + + return DP_GET_SINK_COUNT(count); +} +EXPORT_SYMBOL(drm_dp_read_sink_count); + +/* + * I2C-over-AUX implementation + */ + +static u32 drm_dp_i2c_functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | + I2C_FUNC_SMBUS_READ_BLOCK_DATA | + I2C_FUNC_SMBUS_BLOCK_PROC_CALL | + I2C_FUNC_10BIT_ADDR; +} + +static void drm_dp_i2c_msg_write_status_update(struct drm_dp_aux_msg *msg) +{ + /* + * In case of i2c defer or short i2c ack reply to a write, + * we need to switch to WRITE_STATUS_UPDATE to drain the + * rest of the message + */ + if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_WRITE) { + msg->request &= DP_AUX_I2C_MOT; + msg->request |= DP_AUX_I2C_WRITE_STATUS_UPDATE; + } +} + +#define AUX_PRECHARGE_LEN 10 /* 10 to 16 */ +#define AUX_SYNC_LEN (16 + 4) /* preamble + AUX_SYNC_END */ +#define AUX_STOP_LEN 4 +#define AUX_CMD_LEN 4 +#define AUX_ADDRESS_LEN 20 +#define AUX_REPLY_PAD_LEN 4 +#define AUX_LENGTH_LEN 8 + +/* + * Calculate the duration of the AUX request/reply in usec. Gives the + * "best" case estimate, ie. successful while as short as possible. + */ +static int drm_dp_aux_req_duration(const struct drm_dp_aux_msg *msg) +{ + int len = AUX_PRECHARGE_LEN + AUX_SYNC_LEN + AUX_STOP_LEN + + AUX_CMD_LEN + AUX_ADDRESS_LEN + AUX_LENGTH_LEN; + + if ((msg->request & DP_AUX_I2C_READ) == 0) + len += msg->size * 8; + + return len; +} + +static int drm_dp_aux_reply_duration(const struct drm_dp_aux_msg *msg) +{ + int len = AUX_PRECHARGE_LEN + AUX_SYNC_LEN + AUX_STOP_LEN + + AUX_CMD_LEN + AUX_REPLY_PAD_LEN; + + /* + * For read we expect what was asked. For writes there will + * be 0 or 1 data bytes. Assume 0 for the "best" case. + */ + if (msg->request & DP_AUX_I2C_READ) + len += msg->size * 8; + + return len; +} + +#define I2C_START_LEN 1 +#define I2C_STOP_LEN 1 +#define I2C_ADDR_LEN 9 /* ADDRESS + R/W + ACK/NACK */ +#define I2C_DATA_LEN 9 /* DATA + ACK/NACK */ + +/* + * Calculate the length of the i2c transfer in usec, assuming + * the i2c bus speed is as specified. Gives the the "worst" + * case estimate, ie. successful while as long as possible. + * Doesn't account the the "MOT" bit, and instead assumes each + * message includes a START, ADDRESS and STOP. Neither does it + * account for additional random variables such as clock stretching. + */ +static int drm_dp_i2c_msg_duration(const struct drm_dp_aux_msg *msg, + int i2c_speed_khz) +{ + /* AUX bitrate is 1MHz, i2c bitrate as specified */ + return DIV_ROUND_UP((I2C_START_LEN + I2C_ADDR_LEN + + msg->size * I2C_DATA_LEN + + I2C_STOP_LEN) * 1000, i2c_speed_khz); +} + +/* + * Determine how many retries should be attempted to successfully transfer + * the specified message, based on the estimated durations of the + * i2c and AUX transfers. + */ +static int drm_dp_i2c_retry_count(const struct drm_dp_aux_msg *msg, + int i2c_speed_khz) +{ + int aux_time_us = drm_dp_aux_req_duration(msg) + + drm_dp_aux_reply_duration(msg); + int i2c_time_us = drm_dp_i2c_msg_duration(msg, i2c_speed_khz); + + return DIV_ROUND_UP(i2c_time_us, aux_time_us + AUX_RETRY_INTERVAL); +} + +/* + * FIXME currently assumes 10 kHz as some real world devices seem + * to require it. We should query/set the speed via DPCD if supported. + */ +static int dp_aux_i2c_speed_khz __read_mostly = 10; +module_param_unsafe(dp_aux_i2c_speed_khz, int, 0644); +MODULE_PARM_DESC(dp_aux_i2c_speed_khz, + "Assumed speed of the i2c bus in kHz, (1-400, default 10)"); + +/* + * Transfer a single I2C-over-AUX message and handle various error conditions, + * retrying the transaction as appropriate. It is assumed that the + * &drm_dp_aux.transfer function does not modify anything in the msg other than the + * reply field. + * + * Returns bytes transferred on success, or a negative error code on failure. + */ +static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) +{ + unsigned int retry, defer_i2c; + int ret; + /* + * DP1.2 sections 2.7.7.1.5.6.1 and 2.7.7.1.6.6.1: A DP Source device + * is required to retry at least seven times upon receiving AUX_DEFER + * before giving up the AUX transaction. + * + * We also try to account for the i2c bus speed. + */ + int max_retries = max(7, drm_dp_i2c_retry_count(msg, dp_aux_i2c_speed_khz)); + + for (retry = 0, defer_i2c = 0; retry < (max_retries + defer_i2c); retry++) { + ret = aux->transfer(aux, msg); + if (ret < 0) { + if (ret == -EBUSY) + continue; + + /* + * While timeouts can be errors, they're usually normal + * behavior (for instance, when a driver tries to + * communicate with a non-existent DisplayPort device). + * Avoid spamming the kernel log with timeout errors. + */ + if (ret == -ETIMEDOUT) + drm_dbg_kms_ratelimited(aux->drm_dev, "%s: transaction timed out\n", + aux->name); + else + drm_dbg_kms(aux->drm_dev, "%s: transaction failed: %d\n", + aux->name, ret); + return ret; + } + + + switch (msg->reply & DP_AUX_NATIVE_REPLY_MASK) { + case DP_AUX_NATIVE_REPLY_ACK: + /* + * For I2C-over-AUX transactions this isn't enough, we + * need to check for the I2C ACK reply. + */ + break; + + case DP_AUX_NATIVE_REPLY_NACK: + drm_dbg_kms(aux->drm_dev, "%s: native nack (result=%d, size=%zu)\n", + aux->name, ret, msg->size); + return -EREMOTEIO; + + case DP_AUX_NATIVE_REPLY_DEFER: + drm_dbg_kms(aux->drm_dev, "%s: native defer\n", aux->name); + /* + * We could check for I2C bit rate capabilities and if + * available adjust this interval. We could also be + * more careful with DP-to-legacy adapters where a + * long legacy cable may force very low I2C bit rates. + * + * For now just defer for long enough to hopefully be + * safe for all use-cases. + */ + usleep_range(AUX_RETRY_INTERVAL, AUX_RETRY_INTERVAL + 100); + continue; + + default: + drm_err(aux->drm_dev, "%s: invalid native reply %#04x\n", + aux->name, msg->reply); + return -EREMOTEIO; + } + + switch (msg->reply & DP_AUX_I2C_REPLY_MASK) { + case DP_AUX_I2C_REPLY_ACK: + /* + * Both native ACK and I2C ACK replies received. We + * can assume the transfer was successful. + */ + if (ret != msg->size) + drm_dp_i2c_msg_write_status_update(msg); + return ret; + + case DP_AUX_I2C_REPLY_NACK: + drm_dbg_kms(aux->drm_dev, "%s: I2C nack (result=%d, size=%zu)\n", + aux->name, ret, msg->size); + aux->i2c_nack_count++; + return -EREMOTEIO; + + case DP_AUX_I2C_REPLY_DEFER: + drm_dbg_kms(aux->drm_dev, "%s: I2C defer\n", aux->name); + /* DP Compliance Test 4.2.2.5 Requirement: + * Must have at least 7 retries for I2C defers on the + * transaction to pass this test + */ + aux->i2c_defer_count++; + if (defer_i2c < 7) + defer_i2c++; + usleep_range(AUX_RETRY_INTERVAL, AUX_RETRY_INTERVAL + 100); + drm_dp_i2c_msg_write_status_update(msg); + + continue; + + default: + drm_err(aux->drm_dev, "%s: invalid I2C reply %#04x\n", + aux->name, msg->reply); + return -EREMOTEIO; + } + } + + drm_dbg_kms(aux->drm_dev, "%s: Too many retries, giving up\n", aux->name); + return -EREMOTEIO; +} + +static void drm_dp_i2c_msg_set_request(struct drm_dp_aux_msg *msg, + const struct i2c_msg *i2c_msg) +{ + msg->request = (i2c_msg->flags & I2C_M_RD) ? + DP_AUX_I2C_READ : DP_AUX_I2C_WRITE; + if (!(i2c_msg->flags & I2C_M_STOP)) + msg->request |= DP_AUX_I2C_MOT; +} + +/* + * Keep retrying drm_dp_i2c_do_msg until all data has been transferred. + * + * Returns an error code on failure, or a recommended transfer size on success. + */ +static int drm_dp_i2c_drain_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *orig_msg) +{ + int err, ret = orig_msg->size; + struct drm_dp_aux_msg msg = *orig_msg; + + while (msg.size > 0) { + err = drm_dp_i2c_do_msg(aux, &msg); + if (err <= 0) + return err == 0 ? -EPROTO : err; + + if (err < msg.size && err < ret) { + drm_dbg_kms(aux->drm_dev, + "%s: Partial I2C reply: requested %zu bytes got %d bytes\n", + aux->name, msg.size, err); + ret = err; + } + + msg.size -= err; + msg.buffer += err; + } + + return ret; +} + +/* + * Bizlink designed DP->DVI-D Dual Link adapters require the I2C over AUX + * packets to be as large as possible. If not, the I2C transactions never + * succeed. Hence the default is maximum. + */ +static int dp_aux_i2c_transfer_size __read_mostly = DP_AUX_MAX_PAYLOAD_BYTES; +module_param_unsafe(dp_aux_i2c_transfer_size, int, 0644); +MODULE_PARM_DESC(dp_aux_i2c_transfer_size, + "Number of bytes to transfer in a single I2C over DP AUX CH message, (1-16, default 16)"); + +static int drm_dp_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, + int num) +{ + struct drm_dp_aux *aux = adapter->algo_data; + unsigned int i, j; + unsigned transfer_size; + struct drm_dp_aux_msg msg; + int err = 0; + + dp_aux_i2c_transfer_size = clamp(dp_aux_i2c_transfer_size, 1, DP_AUX_MAX_PAYLOAD_BYTES); + + memset(&msg, 0, sizeof(msg)); + + for (i = 0; i < num; i++) { + msg.address = msgs[i].addr; + drm_dp_i2c_msg_set_request(&msg, &msgs[i]); + /* Send a bare address packet to start the transaction. + * Zero sized messages specify an address only (bare + * address) transaction. + */ + msg.buffer = NULL; + msg.size = 0; + err = drm_dp_i2c_do_msg(aux, &msg); + + /* + * Reset msg.request in case in case it got + * changed into a WRITE_STATUS_UPDATE. + */ + drm_dp_i2c_msg_set_request(&msg, &msgs[i]); + + if (err < 0) + break; + /* We want each transaction to be as large as possible, but + * we'll go to smaller sizes if the hardware gives us a + * short reply. + */ + transfer_size = dp_aux_i2c_transfer_size; + for (j = 0; j < msgs[i].len; j += msg.size) { + msg.buffer = msgs[i].buf + j; + msg.size = min(transfer_size, msgs[i].len - j); + + err = drm_dp_i2c_drain_msg(aux, &msg); + + /* + * Reset msg.request in case in case it got + * changed into a WRITE_STATUS_UPDATE. + */ + drm_dp_i2c_msg_set_request(&msg, &msgs[i]); + + if (err < 0) + break; + transfer_size = err; + } + if (err < 0) + break; + } + if (err >= 0) + err = num; + /* Send a bare address packet to close out the transaction. + * Zero sized messages specify an address only (bare + * address) transaction. + */ + msg.request &= ~DP_AUX_I2C_MOT; + msg.buffer = NULL; + msg.size = 0; + (void)drm_dp_i2c_do_msg(aux, &msg); + + return err; +} + +static const struct i2c_algorithm drm_dp_i2c_algo = { + .functionality = drm_dp_i2c_functionality, + .master_xfer = drm_dp_i2c_xfer, +}; + +static struct drm_dp_aux *i2c_to_aux(struct i2c_adapter *i2c) +{ + return container_of(i2c, struct drm_dp_aux, ddc); +} + +static void lock_bus(struct i2c_adapter *i2c, unsigned int flags) +{ + mutex_lock(&i2c_to_aux(i2c)->hw_mutex); +} + +static int trylock_bus(struct i2c_adapter *i2c, unsigned int flags) +{ + return mutex_trylock(&i2c_to_aux(i2c)->hw_mutex); +} + +static void unlock_bus(struct i2c_adapter *i2c, unsigned int flags) +{ + mutex_unlock(&i2c_to_aux(i2c)->hw_mutex); +} + +static const struct i2c_lock_operations drm_dp_i2c_lock_ops = { + .lock_bus = lock_bus, + .trylock_bus = trylock_bus, + .unlock_bus = unlock_bus, +}; + +static int drm_dp_aux_get_crc(struct drm_dp_aux *aux, u8 *crc) +{ + u8 buf, count; + int ret; + + ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf); + if (ret < 0) + return ret; + + WARN_ON(!(buf & DP_TEST_SINK_START)); + + ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK_MISC, &buf); + if (ret < 0) + return ret; + + count = buf & DP_TEST_COUNT_MASK; + if (count == aux->crc_count) + return -EAGAIN; /* No CRC yet */ + + aux->crc_count = count; + + /* + * At DP_TEST_CRC_R_CR, there's 6 bytes containing CRC data, 2 bytes + * per component (RGB or CrYCb). + */ + ret = drm_dp_dpcd_read(aux, DP_TEST_CRC_R_CR, crc, 6); + if (ret < 0) + return ret; + + return 0; +} + +static void drm_dp_aux_crc_work(struct work_struct *work) +{ + struct drm_dp_aux *aux = container_of(work, struct drm_dp_aux, + crc_work); + struct drm_crtc *crtc; + u8 crc_bytes[6]; + uint32_t crcs[3]; + int ret; + + if (WARN_ON(!aux->crtc)) + return; + + crtc = aux->crtc; + while (crtc->crc.opened) { + drm_crtc_wait_one_vblank(crtc); + if (!crtc->crc.opened) + break; + + ret = drm_dp_aux_get_crc(aux, crc_bytes); + if (ret == -EAGAIN) { + usleep_range(1000, 2000); + ret = drm_dp_aux_get_crc(aux, crc_bytes); + } + + if (ret == -EAGAIN) { + drm_dbg_kms(aux->drm_dev, "%s: Get CRC failed after retrying: %d\n", + aux->name, ret); + continue; + } else if (ret) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to get a CRC: %d\n", aux->name, ret); + continue; + } + + crcs[0] = crc_bytes[0] | crc_bytes[1] << 8; + crcs[1] = crc_bytes[2] | crc_bytes[3] << 8; + crcs[2] = crc_bytes[4] | crc_bytes[5] << 8; + drm_crtc_add_crc_entry(crtc, false, 0, crcs); + } +} + +/** + * drm_dp_remote_aux_init() - minimally initialise a remote aux channel + * @aux: DisplayPort AUX channel + * + * Used for remote aux channel in general. Merely initialize the crc work + * struct. + */ +void drm_dp_remote_aux_init(struct drm_dp_aux *aux) +{ + INIT_WORK(&aux->crc_work, drm_dp_aux_crc_work); +} +EXPORT_SYMBOL(drm_dp_remote_aux_init); + +/** + * drm_dp_aux_init() - minimally initialise an aux channel + * @aux: DisplayPort AUX channel + * + * If you need to use the drm_dp_aux's i2c adapter prior to registering it with + * the outside world, call drm_dp_aux_init() first. For drivers which are + * grandparents to their AUX adapters (e.g. the AUX adapter is parented by a + * &drm_connector), you must still call drm_dp_aux_register() once the connector + * has been registered to allow userspace access to the auxiliary DP channel. + * Likewise, for such drivers you should also assign &drm_dp_aux.drm_dev as + * early as possible so that the &drm_device that corresponds to the AUX adapter + * may be mentioned in debugging output from the DRM DP helpers. + * + * For devices which use a separate platform device for their AUX adapters, this + * may be called as early as required by the driver. + * + */ +void drm_dp_aux_init(struct drm_dp_aux *aux) +{ + mutex_init(&aux->hw_mutex); + mutex_init(&aux->cec.lock); + INIT_WORK(&aux->crc_work, drm_dp_aux_crc_work); + + aux->ddc.algo = &drm_dp_i2c_algo; + aux->ddc.algo_data = aux; + aux->ddc.retries = 3; + + aux->ddc.lock_ops = &drm_dp_i2c_lock_ops; +} +EXPORT_SYMBOL(drm_dp_aux_init); + +/** + * drm_dp_aux_register() - initialise and register aux channel + * @aux: DisplayPort AUX channel + * + * Automatically calls drm_dp_aux_init() if this hasn't been done yet. This + * should only be called once the parent of @aux, &drm_dp_aux.dev, is + * initialized. For devices which are grandparents of their AUX channels, + * &drm_dp_aux.dev will typically be the &drm_connector &device which + * corresponds to @aux. For these devices, it's advised to call + * drm_dp_aux_register() in &drm_connector_funcs.late_register, and likewise to + * call drm_dp_aux_unregister() in &drm_connector_funcs.early_unregister. + * Functions which don't follow this will likely Oops when + * %CONFIG_DRM_DP_AUX_CHARDEV is enabled. + * + * For devices where the AUX channel is a device that exists independently of + * the &drm_device that uses it, such as SoCs and bridge devices, it is + * recommended to call drm_dp_aux_register() after a &drm_device has been + * assigned to &drm_dp_aux.drm_dev, and likewise to call + * drm_dp_aux_unregister() once the &drm_device should no longer be associated + * with the AUX channel (e.g. on bridge detach). + * + * Drivers which need to use the aux channel before either of the two points + * mentioned above need to call drm_dp_aux_init() in order to use the AUX + * channel before registration. + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_aux_register(struct drm_dp_aux *aux) +{ + int ret; + + WARN_ON_ONCE(!aux->drm_dev); + + if (!aux->ddc.algo) + drm_dp_aux_init(aux); + + aux->ddc.class = I2C_CLASS_DDC; + aux->ddc.owner = THIS_MODULE; + aux->ddc.dev.parent = aux->dev; + + strlcpy(aux->ddc.name, aux->name ? aux->name : dev_name(aux->dev), + sizeof(aux->ddc.name)); + + ret = drm_dp_aux_register_devnode(aux); + if (ret) + return ret; + + ret = i2c_add_adapter(&aux->ddc); + if (ret) { + drm_dp_aux_unregister_devnode(aux); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_aux_register); + +/** + * drm_dp_aux_unregister() - unregister an AUX adapter + * @aux: DisplayPort AUX channel + */ +void drm_dp_aux_unregister(struct drm_dp_aux *aux) +{ + drm_dp_aux_unregister_devnode(aux); + i2c_del_adapter(&aux->ddc); +} +EXPORT_SYMBOL(drm_dp_aux_unregister); + +#define PSR_SETUP_TIME(x) [DP_PSR_SETUP_TIME_ ## x >> DP_PSR_SETUP_TIME_SHIFT] = (x) + +/** + * drm_dp_psr_setup_time() - PSR setup in time usec + * @psr_cap: PSR capabilities from DPCD + * + * Returns: + * PSR setup time for the panel in microseconds, negative + * error code on failure. + */ +int drm_dp_psr_setup_time(const u8 psr_cap[EDP_PSR_RECEIVER_CAP_SIZE]) +{ + static const u16 psr_setup_time_us[] = { + PSR_SETUP_TIME(330), + PSR_SETUP_TIME(275), + PSR_SETUP_TIME(220), + PSR_SETUP_TIME(165), + PSR_SETUP_TIME(110), + PSR_SETUP_TIME(55), + PSR_SETUP_TIME(0), + }; + int i; + + i = (psr_cap[1] & DP_PSR_SETUP_TIME_MASK) >> DP_PSR_SETUP_TIME_SHIFT; + if (i >= ARRAY_SIZE(psr_setup_time_us)) + return -EINVAL; + + return psr_setup_time_us[i]; +} +EXPORT_SYMBOL(drm_dp_psr_setup_time); + +#undef PSR_SETUP_TIME + +/** + * drm_dp_start_crc() - start capture of frame CRCs + * @aux: DisplayPort AUX channel + * @crtc: CRTC displaying the frames whose CRCs are to be captured + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_start_crc(struct drm_dp_aux *aux, struct drm_crtc *crtc) +{ + u8 buf; + int ret; + + ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf); + if (ret < 0) + return ret; + + ret = drm_dp_dpcd_writeb(aux, DP_TEST_SINK, buf | DP_TEST_SINK_START); + if (ret < 0) + return ret; + + aux->crc_count = 0; + aux->crtc = crtc; + schedule_work(&aux->crc_work); + + return 0; +} +EXPORT_SYMBOL(drm_dp_start_crc); + +/** + * drm_dp_stop_crc() - stop capture of frame CRCs + * @aux: DisplayPort AUX channel + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_stop_crc(struct drm_dp_aux *aux) +{ + u8 buf; + int ret; + + ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf); + if (ret < 0) + return ret; + + ret = drm_dp_dpcd_writeb(aux, DP_TEST_SINK, buf & ~DP_TEST_SINK_START); + if (ret < 0) + return ret; + + flush_work(&aux->crc_work); + aux->crtc = NULL; + + return 0; +} +EXPORT_SYMBOL(drm_dp_stop_crc); + +struct dpcd_quirk { + u8 oui[3]; + u8 device_id[6]; + bool is_branch; + u32 quirks; +}; + +#define OUI(first, second, third) { (first), (second), (third) } +#define DEVICE_ID(first, second, third, fourth, fifth, sixth) \ + { (first), (second), (third), (fourth), (fifth), (sixth) } + +#define DEVICE_ID_ANY DEVICE_ID(0, 0, 0, 0, 0, 0) + +static const struct dpcd_quirk dpcd_quirk_list[] = { + /* Analogix 7737 needs reduced M and N at HBR2 link rates */ + { OUI(0x00, 0x22, 0xb9), DEVICE_ID_ANY, true, BIT(DP_DPCD_QUIRK_CONSTANT_N) }, + /* LG LP140WF6-SPM1 eDP panel */ + { OUI(0x00, 0x22, 0xb9), DEVICE_ID('s', 'i', 'v', 'a', 'r', 'T'), false, BIT(DP_DPCD_QUIRK_CONSTANT_N) }, + /* Apple panels need some additional handling to support PSR */ + { OUI(0x00, 0x10, 0xfa), DEVICE_ID_ANY, false, BIT(DP_DPCD_QUIRK_NO_PSR) }, + /* CH7511 seems to leave SINK_COUNT zeroed */ + { OUI(0x00, 0x00, 0x00), DEVICE_ID('C', 'H', '7', '5', '1', '1'), false, BIT(DP_DPCD_QUIRK_NO_SINK_COUNT) }, + /* Synaptics DP1.4 MST hubs can support DSC without virtual DPCD */ + { OUI(0x90, 0xCC, 0x24), DEVICE_ID_ANY, true, BIT(DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) }, + /* Apple MacBookPro 2017 15 inch eDP Retina panel reports too low DP_MAX_LINK_RATE */ + { OUI(0x00, 0x10, 0xfa), DEVICE_ID(101, 68, 21, 101, 98, 97), false, BIT(DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS) }, +}; + +#undef OUI + +/* + * Get a bit mask of DPCD quirks for the sink/branch device identified by + * ident. The quirk data is shared but it's up to the drivers to act on the + * data. + * + * For now, only the OUI (first three bytes) is used, but this may be extended + * to device identification string and hardware/firmware revisions later. + */ +static u32 +drm_dp_get_quirks(const struct drm_dp_dpcd_ident *ident, bool is_branch) +{ + const struct dpcd_quirk *quirk; + u32 quirks = 0; + int i; + u8 any_device[] = DEVICE_ID_ANY; + + for (i = 0; i < ARRAY_SIZE(dpcd_quirk_list); i++) { + quirk = &dpcd_quirk_list[i]; + + if (quirk->is_branch != is_branch) + continue; + + if (memcmp(quirk->oui, ident->oui, sizeof(ident->oui)) != 0) + continue; + + if (memcmp(quirk->device_id, any_device, sizeof(any_device)) != 0 && + memcmp(quirk->device_id, ident->device_id, sizeof(ident->device_id)) != 0) + continue; + + quirks |= quirk->quirks; + } + + return quirks; +} + +#undef DEVICE_ID_ANY +#undef DEVICE_ID + +/** + * drm_dp_read_desc - read sink/branch descriptor from DPCD + * @aux: DisplayPort AUX channel + * @desc: Device descriptor to fill from DPCD + * @is_branch: true for branch devices, false for sink devices + * + * Read DPCD 0x400 (sink) or 0x500 (branch) into @desc. Also debug log the + * identification. + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_read_desc(struct drm_dp_aux *aux, struct drm_dp_desc *desc, + bool is_branch) +{ + struct drm_dp_dpcd_ident *ident = &desc->ident; + unsigned int offset = is_branch ? DP_BRANCH_OUI : DP_SINK_OUI; + int ret, dev_id_len; + + ret = drm_dp_dpcd_read(aux, offset, ident, sizeof(*ident)); + if (ret < 0) + return ret; + + desc->quirks = drm_dp_get_quirks(ident, is_branch); + + dev_id_len = strnlen(ident->device_id, sizeof(ident->device_id)); + + drm_dbg_kms(aux->drm_dev, + "%s: DP %s: OUI %*phD dev-ID %*pE HW-rev %d.%d SW-rev %d.%d quirks 0x%04x\n", + aux->name, is_branch ? "branch" : "sink", + (int)sizeof(ident->oui), ident->oui, dev_id_len, + ident->device_id, ident->hw_rev >> 4, ident->hw_rev & 0xf, + ident->sw_major_rev, ident->sw_minor_rev, desc->quirks); + + return 0; +} +EXPORT_SYMBOL(drm_dp_read_desc); + +/** + * drm_dp_dsc_sink_max_slice_count() - Get the max slice count + * supported by the DSC sink. + * @dsc_dpcd: DSC capabilities from DPCD + * @is_edp: true if its eDP, false for DP + * + * Read the slice capabilities DPCD register from DSC sink to get + * the maximum slice count supported. This is used to populate + * the DSC parameters in the &struct drm_dsc_config by the driver. + * Driver creates an infoframe using these parameters to populate + * &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC + * infoframe using the helper function drm_dsc_pps_infoframe_pack() + * + * Returns: + * Maximum slice count supported by DSC sink or 0 its invalid + */ +u8 drm_dp_dsc_sink_max_slice_count(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE], + bool is_edp) +{ + u8 slice_cap1 = dsc_dpcd[DP_DSC_SLICE_CAP_1 - DP_DSC_SUPPORT]; + + if (is_edp) { + /* For eDP, register DSC_SLICE_CAPABILITIES_1 gives slice count */ + if (slice_cap1 & DP_DSC_4_PER_DP_DSC_SINK) + return 4; + if (slice_cap1 & DP_DSC_2_PER_DP_DSC_SINK) + return 2; + if (slice_cap1 & DP_DSC_1_PER_DP_DSC_SINK) + return 1; + } else { + /* For DP, use values from DSC_SLICE_CAP_1 and DSC_SLICE_CAP2 */ + u8 slice_cap2 = dsc_dpcd[DP_DSC_SLICE_CAP_2 - DP_DSC_SUPPORT]; + + if (slice_cap2 & DP_DSC_24_PER_DP_DSC_SINK) + return 24; + if (slice_cap2 & DP_DSC_20_PER_DP_DSC_SINK) + return 20; + if (slice_cap2 & DP_DSC_16_PER_DP_DSC_SINK) + return 16; + if (slice_cap1 & DP_DSC_12_PER_DP_DSC_SINK) + return 12; + if (slice_cap1 & DP_DSC_10_PER_DP_DSC_SINK) + return 10; + if (slice_cap1 & DP_DSC_8_PER_DP_DSC_SINK) + return 8; + if (slice_cap1 & DP_DSC_6_PER_DP_DSC_SINK) + return 6; + if (slice_cap1 & DP_DSC_4_PER_DP_DSC_SINK) + return 4; + if (slice_cap1 & DP_DSC_2_PER_DP_DSC_SINK) + return 2; + if (slice_cap1 & DP_DSC_1_PER_DP_DSC_SINK) + return 1; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_dsc_sink_max_slice_count); + +/** + * drm_dp_dsc_sink_line_buf_depth() - Get the line buffer depth in bits + * @dsc_dpcd: DSC capabilities from DPCD + * + * Read the DSC DPCD register to parse the line buffer depth in bits which is + * number of bits of precision within the decoder line buffer supported by + * the DSC sink. This is used to populate the DSC parameters in the + * &struct drm_dsc_config by the driver. + * Driver creates an infoframe using these parameters to populate + * &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC + * infoframe using the helper function drm_dsc_pps_infoframe_pack() + * + * Returns: + * Line buffer depth supported by DSC panel or 0 its invalid + */ +u8 drm_dp_dsc_sink_line_buf_depth(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]) +{ + u8 line_buf_depth = dsc_dpcd[DP_DSC_LINE_BUF_BIT_DEPTH - DP_DSC_SUPPORT]; + + switch (line_buf_depth & DP_DSC_LINE_BUF_BIT_DEPTH_MASK) { + case DP_DSC_LINE_BUF_BIT_DEPTH_9: + return 9; + case DP_DSC_LINE_BUF_BIT_DEPTH_10: + return 10; + case DP_DSC_LINE_BUF_BIT_DEPTH_11: + return 11; + case DP_DSC_LINE_BUF_BIT_DEPTH_12: + return 12; + case DP_DSC_LINE_BUF_BIT_DEPTH_13: + return 13; + case DP_DSC_LINE_BUF_BIT_DEPTH_14: + return 14; + case DP_DSC_LINE_BUF_BIT_DEPTH_15: + return 15; + case DP_DSC_LINE_BUF_BIT_DEPTH_16: + return 16; + case DP_DSC_LINE_BUF_BIT_DEPTH_8: + return 8; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_dsc_sink_line_buf_depth); + +/** + * drm_dp_dsc_sink_supported_input_bpcs() - Get all the input bits per component + * values supported by the DSC sink. + * @dsc_dpcd: DSC capabilities from DPCD + * @dsc_bpc: An array to be filled by this helper with supported + * input bpcs. + * + * Read the DSC DPCD from the sink device to parse the supported bits per + * component values. This is used to populate the DSC parameters + * in the &struct drm_dsc_config by the driver. + * Driver creates an infoframe using these parameters to populate + * &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC + * infoframe using the helper function drm_dsc_pps_infoframe_pack() + * + * Returns: + * Number of input BPC values parsed from the DPCD + */ +int drm_dp_dsc_sink_supported_input_bpcs(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE], + u8 dsc_bpc[3]) +{ + int num_bpc = 0; + u8 color_depth = dsc_dpcd[DP_DSC_DEC_COLOR_DEPTH_CAP - DP_DSC_SUPPORT]; + + if (color_depth & DP_DSC_12_BPC) + dsc_bpc[num_bpc++] = 12; + if (color_depth & DP_DSC_10_BPC) + dsc_bpc[num_bpc++] = 10; + if (color_depth & DP_DSC_8_BPC) + dsc_bpc[num_bpc++] = 8; + + return num_bpc; +} +EXPORT_SYMBOL(drm_dp_dsc_sink_supported_input_bpcs); + +static int drm_dp_read_lttpr_regs(struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], int address, + u8 *buf, int buf_size) +{ + /* + * At least the DELL P2715Q monitor with a DPCD_REV < 0x14 returns + * corrupted values when reading from the 0xF0000- range with a block + * size bigger than 1. + */ + int block_size = dpcd[DP_DPCD_REV] < 0x14 ? 1 : buf_size; + int offset; + int ret; + + for (offset = 0; offset < buf_size; offset += block_size) { + ret = drm_dp_dpcd_read(aux, + address + offset, + &buf[offset], block_size); + if (ret < 0) + return ret; + + WARN_ON(ret != block_size); + } + + return 0; +} + +/** + * drm_dp_read_lttpr_common_caps - read the LTTPR common capabilities + * @aux: DisplayPort AUX channel + * @dpcd: DisplayPort configuration data + * @caps: buffer to return the capability info in + * + * Read capabilities common to all LTTPRs. + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_read_lttpr_common_caps(struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) +{ + return drm_dp_read_lttpr_regs(aux, dpcd, + DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV, + caps, DP_LTTPR_COMMON_CAP_SIZE); +} +EXPORT_SYMBOL(drm_dp_read_lttpr_common_caps); + +/** + * drm_dp_read_lttpr_phy_caps - read the capabilities for a given LTTPR PHY + * @aux: DisplayPort AUX channel + * @dpcd: DisplayPort configuration data + * @dp_phy: LTTPR PHY to read the capabilities for + * @caps: buffer to return the capability info in + * + * Read the capabilities for the given LTTPR PHY. + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_read_lttpr_phy_caps(struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE], + enum drm_dp_phy dp_phy, + u8 caps[DP_LTTPR_PHY_CAP_SIZE]) +{ + return drm_dp_read_lttpr_regs(aux, dpcd, + DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy), + caps, DP_LTTPR_PHY_CAP_SIZE); +} +EXPORT_SYMBOL(drm_dp_read_lttpr_phy_caps); + +static u8 dp_lttpr_common_cap(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE], int r) +{ + return caps[r - DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV]; +} + +/** + * drm_dp_lttpr_count - get the number of detected LTTPRs + * @caps: LTTPR common capabilities + * + * Get the number of detected LTTPRs from the LTTPR common capabilities info. + * + * Returns: + * -ERANGE if more than supported number (8) of LTTPRs are detected + * -EINVAL if the DP_PHY_REPEATER_CNT register contains an invalid value + * otherwise the number of detected LTTPRs + */ +int drm_dp_lttpr_count(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) +{ + u8 count = dp_lttpr_common_cap(caps, DP_PHY_REPEATER_CNT); + + switch (hweight8(count)) { + case 0: + return 0; + case 1: + return 8 - ilog2(count); + case 8: + return -ERANGE; + default: + return -EINVAL; + } +} +EXPORT_SYMBOL(drm_dp_lttpr_count); + +/** + * drm_dp_lttpr_max_link_rate - get the maximum link rate supported by all LTTPRs + * @caps: LTTPR common capabilities + * + * Returns the maximum link rate supported by all detected LTTPRs. + */ +int drm_dp_lttpr_max_link_rate(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) +{ + u8 rate = dp_lttpr_common_cap(caps, DP_MAX_LINK_RATE_PHY_REPEATER); + + return drm_dp_bw_code_to_link_rate(rate); +} +EXPORT_SYMBOL(drm_dp_lttpr_max_link_rate); + +/** + * drm_dp_lttpr_max_lane_count - get the maximum lane count supported by all LTTPRs + * @caps: LTTPR common capabilities + * + * Returns the maximum lane count supported by all detected LTTPRs. + */ +int drm_dp_lttpr_max_lane_count(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) +{ + u8 max_lanes = dp_lttpr_common_cap(caps, DP_MAX_LANE_COUNT_PHY_REPEATER); + + return max_lanes & DP_MAX_LANE_COUNT_MASK; +} +EXPORT_SYMBOL(drm_dp_lttpr_max_lane_count); + +/** + * drm_dp_lttpr_voltage_swing_level_3_supported - check for LTTPR vswing3 support + * @caps: LTTPR PHY capabilities + * + * Returns true if the @caps for an LTTPR TX PHY indicate support for + * voltage swing level 3. + */ +bool +drm_dp_lttpr_voltage_swing_level_3_supported(const u8 caps[DP_LTTPR_PHY_CAP_SIZE]) +{ + u8 txcap = dp_lttpr_phy_cap(caps, DP_TRANSMITTER_CAPABILITY_PHY_REPEATER1); + + return txcap & DP_VOLTAGE_SWING_LEVEL_3_SUPPORTED; +} +EXPORT_SYMBOL(drm_dp_lttpr_voltage_swing_level_3_supported); + +/** + * drm_dp_lttpr_pre_emphasis_level_3_supported - check for LTTPR preemph3 support + * @caps: LTTPR PHY capabilities + * + * Returns true if the @caps for an LTTPR TX PHY indicate support for + * pre-emphasis level 3. + */ +bool +drm_dp_lttpr_pre_emphasis_level_3_supported(const u8 caps[DP_LTTPR_PHY_CAP_SIZE]) +{ + u8 txcap = dp_lttpr_phy_cap(caps, DP_TRANSMITTER_CAPABILITY_PHY_REPEATER1); + + return txcap & DP_PRE_EMPHASIS_LEVEL_3_SUPPORTED; +} +EXPORT_SYMBOL(drm_dp_lttpr_pre_emphasis_level_3_supported); + +/** + * drm_dp_get_phy_test_pattern() - get the requested pattern from the sink. + * @aux: DisplayPort AUX channel + * @data: DP phy compliance test parameters. + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_get_phy_test_pattern(struct drm_dp_aux *aux, + struct drm_dp_phy_test_params *data) +{ + int err; + u8 rate, lanes; + + err = drm_dp_dpcd_readb(aux, DP_TEST_LINK_RATE, &rate); + if (err < 0) + return err; + data->link_rate = drm_dp_bw_code_to_link_rate(rate); + + err = drm_dp_dpcd_readb(aux, DP_TEST_LANE_COUNT, &lanes); + if (err < 0) + return err; + data->num_lanes = lanes & DP_MAX_LANE_COUNT_MASK; + + if (lanes & DP_ENHANCED_FRAME_CAP) + data->enhanced_frame_cap = true; + + err = drm_dp_dpcd_readb(aux, DP_PHY_TEST_PATTERN, &data->phy_pattern); + if (err < 0) + return err; + + switch (data->phy_pattern) { + case DP_PHY_TEST_PATTERN_80BIT_CUSTOM: + err = drm_dp_dpcd_read(aux, DP_TEST_80BIT_CUSTOM_PATTERN_7_0, + &data->custom80, sizeof(data->custom80)); + if (err < 0) + return err; + + break; + case DP_PHY_TEST_PATTERN_CP2520: + err = drm_dp_dpcd_read(aux, DP_TEST_HBR2_SCRAMBLER_RESET, + &data->hbr2_reset, + sizeof(data->hbr2_reset)); + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_get_phy_test_pattern); + +/** + * drm_dp_set_phy_test_pattern() - set the pattern to the sink. + * @aux: DisplayPort AUX channel + * @data: DP phy compliance test parameters. + * @dp_rev: DP revision to use for compliance testing + * + * Returns 0 on success or a negative error code on failure. + */ +int drm_dp_set_phy_test_pattern(struct drm_dp_aux *aux, + struct drm_dp_phy_test_params *data, u8 dp_rev) +{ + int err, i; + u8 link_config[2]; + u8 test_pattern; + + link_config[0] = drm_dp_link_rate_to_bw_code(data->link_rate); + link_config[1] = data->num_lanes; + if (data->enhanced_frame_cap) + link_config[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, link_config, 2); + if (err < 0) + return err; + + test_pattern = data->phy_pattern; + if (dp_rev < 0x12) { + test_pattern = (test_pattern << 2) & + DP_LINK_QUAL_PATTERN_11_MASK; + err = drm_dp_dpcd_writeb(aux, DP_TRAINING_PATTERN_SET, + test_pattern); + if (err < 0) + return err; + } else { + for (i = 0; i < data->num_lanes; i++) { + err = drm_dp_dpcd_writeb(aux, + DP_LINK_QUAL_LANE0_SET + i, + test_pattern); + if (err < 0) + return err; + } + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_set_phy_test_pattern); + +static const char *dp_pixelformat_get_name(enum dp_pixelformat pixelformat) +{ + if (pixelformat < 0 || pixelformat > DP_PIXELFORMAT_RESERVED) + return "Invalid"; + + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "RGB"; + case DP_PIXELFORMAT_YUV444: + return "YUV444"; + case DP_PIXELFORMAT_YUV422: + return "YUV422"; + case DP_PIXELFORMAT_YUV420: + return "YUV420"; + case DP_PIXELFORMAT_Y_ONLY: + return "Y_ONLY"; + case DP_PIXELFORMAT_RAW: + return "RAW"; + default: + return "Reserved"; + } +} + +static const char *dp_colorimetry_get_name(enum dp_pixelformat pixelformat, + enum dp_colorimetry colorimetry) +{ + if (pixelformat < 0 || pixelformat > DP_PIXELFORMAT_RESERVED) + return "Invalid"; + + switch (colorimetry) { + case DP_COLORIMETRY_DEFAULT: + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "sRGB"; + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "BT.601"; + case DP_PIXELFORMAT_Y_ONLY: + return "DICOM PS3.14"; + case DP_PIXELFORMAT_RAW: + return "Custom Color Profile"; + default: + return "Reserved"; + } + case DP_COLORIMETRY_RGB_WIDE_FIXED: /* and DP_COLORIMETRY_BT709_YCC */ + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "Wide Fixed"; + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "BT.709"; + default: + return "Reserved"; + } + case DP_COLORIMETRY_RGB_WIDE_FLOAT: /* and DP_COLORIMETRY_XVYCC_601 */ + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "Wide Float"; + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "xvYCC 601"; + default: + return "Reserved"; + } + case DP_COLORIMETRY_OPRGB: /* and DP_COLORIMETRY_XVYCC_709 */ + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "OpRGB"; + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "xvYCC 709"; + default: + return "Reserved"; + } + case DP_COLORIMETRY_DCI_P3_RGB: /* and DP_COLORIMETRY_SYCC_601 */ + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "DCI-P3"; + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "sYCC 601"; + default: + return "Reserved"; + } + case DP_COLORIMETRY_RGB_CUSTOM: /* and DP_COLORIMETRY_OPYCC_601 */ + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "Custom Profile"; + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "OpYCC 601"; + default: + return "Reserved"; + } + case DP_COLORIMETRY_BT2020_RGB: /* and DP_COLORIMETRY_BT2020_CYCC */ + switch (pixelformat) { + case DP_PIXELFORMAT_RGB: + return "BT.2020 RGB"; + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "BT.2020 CYCC"; + default: + return "Reserved"; + } + case DP_COLORIMETRY_BT2020_YCC: + switch (pixelformat) { + case DP_PIXELFORMAT_YUV444: + case DP_PIXELFORMAT_YUV422: + case DP_PIXELFORMAT_YUV420: + return "BT.2020 YCC"; + default: + return "Reserved"; + } + default: + return "Invalid"; + } +} + +static const char *dp_dynamic_range_get_name(enum dp_dynamic_range dynamic_range) +{ + switch (dynamic_range) { + case DP_DYNAMIC_RANGE_VESA: + return "VESA range"; + case DP_DYNAMIC_RANGE_CTA: + return "CTA range"; + default: + return "Invalid"; + } +} + +static const char *dp_content_type_get_name(enum dp_content_type content_type) +{ + switch (content_type) { + case DP_CONTENT_TYPE_NOT_DEFINED: + return "Not defined"; + case DP_CONTENT_TYPE_GRAPHICS: + return "Graphics"; + case DP_CONTENT_TYPE_PHOTO: + return "Photo"; + case DP_CONTENT_TYPE_VIDEO: + return "Video"; + case DP_CONTENT_TYPE_GAME: + return "Game"; + default: + return "Reserved"; + } +} + +void drm_dp_vsc_sdp_log(const char *level, struct device *dev, + const struct drm_dp_vsc_sdp *vsc) +{ +#define DP_SDP_LOG(fmt, ...) dev_printk(level, dev, fmt, ##__VA_ARGS__) + DP_SDP_LOG("DP SDP: %s, revision %u, length %u\n", "VSC", + vsc->revision, vsc->length); + DP_SDP_LOG(" pixelformat: %s\n", + dp_pixelformat_get_name(vsc->pixelformat)); + DP_SDP_LOG(" colorimetry: %s\n", + dp_colorimetry_get_name(vsc->pixelformat, vsc->colorimetry)); + DP_SDP_LOG(" bpc: %u\n", vsc->bpc); + DP_SDP_LOG(" dynamic range: %s\n", + dp_dynamic_range_get_name(vsc->dynamic_range)); + DP_SDP_LOG(" content type: %s\n", + dp_content_type_get_name(vsc->content_type)); +#undef DP_SDP_LOG +} +EXPORT_SYMBOL(drm_dp_vsc_sdp_log); + +/** + * drm_dp_get_pcon_max_frl_bw() - maximum frl supported by PCON + * @dpcd: DisplayPort configuration data + * @port_cap: port capabilities + * + * Returns maximum frl bandwidth supported by PCON in GBPS, + * returns 0 if not supported. + */ +int drm_dp_get_pcon_max_frl_bw(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const u8 port_cap[4]) +{ + int bw; + u8 buf; + + buf = port_cap[2]; + bw = buf & DP_PCON_MAX_FRL_BW; + + switch (bw) { + case DP_PCON_MAX_9GBPS: + return 9; + case DP_PCON_MAX_18GBPS: + return 18; + case DP_PCON_MAX_24GBPS: + return 24; + case DP_PCON_MAX_32GBPS: + return 32; + case DP_PCON_MAX_40GBPS: + return 40; + case DP_PCON_MAX_48GBPS: + return 48; + case DP_PCON_MAX_0GBPS: + default: + return 0; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_get_pcon_max_frl_bw); + +/** + * drm_dp_pcon_frl_prepare() - Prepare PCON for FRL. + * @aux: DisplayPort AUX channel + * @enable_frl_ready_hpd: Configure DP_PCON_ENABLE_HPD_READY. + * + * Returns 0 if success, else returns negative error code. + */ +int drm_dp_pcon_frl_prepare(struct drm_dp_aux *aux, bool enable_frl_ready_hpd) +{ + int ret; + u8 buf = DP_PCON_ENABLE_SOURCE_CTL_MODE | + DP_PCON_ENABLE_LINK_FRL_MODE; + + if (enable_frl_ready_hpd) + buf |= DP_PCON_ENABLE_HPD_READY; + + ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf); + + return ret; +} +EXPORT_SYMBOL(drm_dp_pcon_frl_prepare); + +/** + * drm_dp_pcon_is_frl_ready() - Is PCON ready for FRL + * @aux: DisplayPort AUX channel + * + * Returns true if success, else returns false. + */ +bool drm_dp_pcon_is_frl_ready(struct drm_dp_aux *aux) +{ + int ret; + u8 buf; + + ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_TX_LINK_STATUS, &buf); + if (ret < 0) + return false; + + if (buf & DP_PCON_FRL_READY) + return true; + + return false; +} +EXPORT_SYMBOL(drm_dp_pcon_is_frl_ready); + +/** + * drm_dp_pcon_frl_configure_1() - Set HDMI LINK Configuration-Step1 + * @aux: DisplayPort AUX channel + * @max_frl_gbps: maximum frl bw to be configured between PCON and HDMI sink + * @frl_mode: FRL Training mode, it can be either Concurrent or Sequential. + * In Concurrent Mode, the FRL link bring up can be done along with + * DP Link training. In Sequential mode, the FRL link bring up is done prior to + * the DP Link training. + * + * Returns 0 if success, else returns negative error code. + */ + +int drm_dp_pcon_frl_configure_1(struct drm_dp_aux *aux, int max_frl_gbps, + u8 frl_mode) +{ + int ret; + u8 buf; + + ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_LINK_CONFIG_1, &buf); + if (ret < 0) + return ret; + + if (frl_mode == DP_PCON_ENABLE_CONCURRENT_LINK) + buf |= DP_PCON_ENABLE_CONCURRENT_LINK; + else + buf &= ~DP_PCON_ENABLE_CONCURRENT_LINK; + + switch (max_frl_gbps) { + case 9: + buf |= DP_PCON_ENABLE_MAX_BW_9GBPS; + break; + case 18: + buf |= DP_PCON_ENABLE_MAX_BW_18GBPS; + break; + case 24: + buf |= DP_PCON_ENABLE_MAX_BW_24GBPS; + break; + case 32: + buf |= DP_PCON_ENABLE_MAX_BW_32GBPS; + break; + case 40: + buf |= DP_PCON_ENABLE_MAX_BW_40GBPS; + break; + case 48: + buf |= DP_PCON_ENABLE_MAX_BW_48GBPS; + break; + case 0: + buf |= DP_PCON_ENABLE_MAX_BW_0GBPS; + break; + default: + return -EINVAL; + } + + ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_frl_configure_1); + +/** + * drm_dp_pcon_frl_configure_2() - Set HDMI Link configuration Step-2 + * @aux: DisplayPort AUX channel + * @max_frl_mask : Max FRL BW to be tried by the PCON with HDMI Sink + * @frl_type : FRL training type, can be Extended, or Normal. + * In Normal FRL training, the PCON tries each frl bw from the max_frl_mask + * starting from min, and stops when link training is successful. In Extended + * FRL training, all frl bw selected in the mask are trained by the PCON. + * + * Returns 0 if success, else returns negative error code. + */ +int drm_dp_pcon_frl_configure_2(struct drm_dp_aux *aux, int max_frl_mask, + u8 frl_type) +{ + int ret; + u8 buf = max_frl_mask; + + if (frl_type == DP_PCON_FRL_LINK_TRAIN_EXTENDED) + buf |= DP_PCON_FRL_LINK_TRAIN_EXTENDED; + else + buf &= ~DP_PCON_FRL_LINK_TRAIN_EXTENDED; + + ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_2, buf); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_frl_configure_2); + +/** + * drm_dp_pcon_reset_frl_config() - Re-Set HDMI Link configuration. + * @aux: DisplayPort AUX channel + * + * Returns 0 if success, else returns negative error code. + */ +int drm_dp_pcon_reset_frl_config(struct drm_dp_aux *aux) +{ + int ret; + + ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, 0x0); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_reset_frl_config); + +/** + * drm_dp_pcon_frl_enable() - Enable HDMI link through FRL + * @aux: DisplayPort AUX channel + * + * Returns 0 if success, else returns negative error code. + */ +int drm_dp_pcon_frl_enable(struct drm_dp_aux *aux) +{ + int ret; + u8 buf = 0; + + ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_LINK_CONFIG_1, &buf); + if (ret < 0) + return ret; + if (!(buf & DP_PCON_ENABLE_SOURCE_CTL_MODE)) { + drm_dbg_kms(aux->drm_dev, "%s: PCON in Autonomous mode, can't enable FRL\n", + aux->name); + return -EINVAL; + } + buf |= DP_PCON_ENABLE_HDMI_LINK; + ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_frl_enable); + +/** + * drm_dp_pcon_hdmi_link_active() - check if the PCON HDMI LINK status is active. + * @aux: DisplayPort AUX channel + * + * Returns true if link is active else returns false. + */ +bool drm_dp_pcon_hdmi_link_active(struct drm_dp_aux *aux) +{ + u8 buf; + int ret; + + ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_TX_LINK_STATUS, &buf); + if (ret < 0) + return false; + + return buf & DP_PCON_HDMI_TX_LINK_ACTIVE; +} +EXPORT_SYMBOL(drm_dp_pcon_hdmi_link_active); + +/** + * drm_dp_pcon_hdmi_link_mode() - get the PCON HDMI LINK MODE + * @aux: DisplayPort AUX channel + * @frl_trained_mask: pointer to store bitmask of the trained bw configuration. + * Valid only if the MODE returned is FRL. For Normal Link training mode + * only 1 of the bits will be set, but in case of Extended mode, more than + * one bits can be set. + * + * Returns the link mode : TMDS or FRL on success, else returns negative error + * code. + */ +int drm_dp_pcon_hdmi_link_mode(struct drm_dp_aux *aux, u8 *frl_trained_mask) +{ + u8 buf; + int mode; + int ret; + + ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_POST_FRL_STATUS, &buf); + if (ret < 0) + return ret; + + mode = buf & DP_PCON_HDMI_LINK_MODE; + + if (frl_trained_mask && DP_PCON_HDMI_MODE_FRL == mode) + *frl_trained_mask = (buf & DP_PCON_HDMI_FRL_TRAINED_BW) >> 1; + + return mode; +} +EXPORT_SYMBOL(drm_dp_pcon_hdmi_link_mode); + +/** + * drm_dp_pcon_hdmi_frl_link_error_count() - print the error count per lane + * during link failure between PCON and HDMI sink + * @aux: DisplayPort AUX channel + * @connector: DRM connector + * code. + **/ + +void drm_dp_pcon_hdmi_frl_link_error_count(struct drm_dp_aux *aux, + struct drm_connector *connector) +{ + u8 buf, error_count; + int i, num_error; + struct drm_hdmi_info *hdmi = &connector->display_info.hdmi; + + for (i = 0; i < hdmi->max_lanes; i++) { + if (drm_dp_dpcd_readb(aux, DP_PCON_HDMI_ERROR_STATUS_LN0 + i, &buf) < 0) + return; + + error_count = buf & DP_PCON_HDMI_ERROR_COUNT_MASK; + switch (error_count) { + case DP_PCON_HDMI_ERROR_COUNT_HUNDRED_PLUS: + num_error = 100; + break; + case DP_PCON_HDMI_ERROR_COUNT_TEN_PLUS: + num_error = 10; + break; + case DP_PCON_HDMI_ERROR_COUNT_THREE_PLUS: + num_error = 3; + break; + default: + num_error = 0; + } + + drm_err(aux->drm_dev, "%s: More than %d errors since the last read for lane %d", + aux->name, num_error, i); + } +} +EXPORT_SYMBOL(drm_dp_pcon_hdmi_frl_link_error_count); + +/* + * drm_dp_pcon_enc_is_dsc_1_2 - Does PCON Encoder supports DSC 1.2 + * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder + * + * Returns true is PCON encoder is DSC 1.2 else returns false. + */ +bool drm_dp_pcon_enc_is_dsc_1_2(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) +{ + u8 buf; + u8 major_v, minor_v; + + buf = pcon_dsc_dpcd[DP_PCON_DSC_VERSION - DP_PCON_DSC_ENCODER]; + major_v = (buf & DP_PCON_DSC_MAJOR_MASK) >> DP_PCON_DSC_MAJOR_SHIFT; + minor_v = (buf & DP_PCON_DSC_MINOR_MASK) >> DP_PCON_DSC_MINOR_SHIFT; + + if (major_v == 1 && minor_v == 2) + return true; + + return false; +} +EXPORT_SYMBOL(drm_dp_pcon_enc_is_dsc_1_2); + +/* + * drm_dp_pcon_dsc_max_slices - Get max slices supported by PCON DSC Encoder + * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder + * + * Returns maximum no. of slices supported by the PCON DSC Encoder. + */ +int drm_dp_pcon_dsc_max_slices(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) +{ + u8 slice_cap1, slice_cap2; + + slice_cap1 = pcon_dsc_dpcd[DP_PCON_DSC_SLICE_CAP_1 - DP_PCON_DSC_ENCODER]; + slice_cap2 = pcon_dsc_dpcd[DP_PCON_DSC_SLICE_CAP_2 - DP_PCON_DSC_ENCODER]; + + if (slice_cap2 & DP_PCON_DSC_24_PER_DSC_ENC) + return 24; + if (slice_cap2 & DP_PCON_DSC_20_PER_DSC_ENC) + return 20; + if (slice_cap2 & DP_PCON_DSC_16_PER_DSC_ENC) + return 16; + if (slice_cap1 & DP_PCON_DSC_12_PER_DSC_ENC) + return 12; + if (slice_cap1 & DP_PCON_DSC_10_PER_DSC_ENC) + return 10; + if (slice_cap1 & DP_PCON_DSC_8_PER_DSC_ENC) + return 8; + if (slice_cap1 & DP_PCON_DSC_6_PER_DSC_ENC) + return 6; + if (slice_cap1 & DP_PCON_DSC_4_PER_DSC_ENC) + return 4; + if (slice_cap1 & DP_PCON_DSC_2_PER_DSC_ENC) + return 2; + if (slice_cap1 & DP_PCON_DSC_1_PER_DSC_ENC) + return 1; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_dsc_max_slices); + +/* + * drm_dp_pcon_dsc_max_slice_width() - Get max slice width for Pcon DSC encoder + * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder + * + * Returns maximum width of the slices in pixel width i.e. no. of pixels x 320. + */ +int drm_dp_pcon_dsc_max_slice_width(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) +{ + u8 buf; + + buf = pcon_dsc_dpcd[DP_PCON_DSC_MAX_SLICE_WIDTH - DP_PCON_DSC_ENCODER]; + + return buf * DP_DSC_SLICE_WIDTH_MULTIPLIER; +} +EXPORT_SYMBOL(drm_dp_pcon_dsc_max_slice_width); + +/* + * drm_dp_pcon_dsc_bpp_incr() - Get bits per pixel increment for PCON DSC encoder + * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder + * + * Returns the bpp precision supported by the PCON encoder. + */ +int drm_dp_pcon_dsc_bpp_incr(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) +{ + u8 buf; + + buf = pcon_dsc_dpcd[DP_PCON_DSC_BPP_INCR - DP_PCON_DSC_ENCODER]; + + switch (buf & DP_PCON_DSC_BPP_INCR_MASK) { + case DP_PCON_DSC_ONE_16TH_BPP: + return 16; + case DP_PCON_DSC_ONE_8TH_BPP: + return 8; + case DP_PCON_DSC_ONE_4TH_BPP: + return 4; + case DP_PCON_DSC_ONE_HALF_BPP: + return 2; + case DP_PCON_DSC_ONE_BPP: + return 1; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_dsc_bpp_incr); + +static +int drm_dp_pcon_configure_dsc_enc(struct drm_dp_aux *aux, u8 pps_buf_config) +{ + u8 buf; + int ret; + + ret = drm_dp_dpcd_readb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, &buf); + if (ret < 0) + return ret; + + buf |= DP_PCON_ENABLE_DSC_ENCODER; + + if (pps_buf_config <= DP_PCON_ENC_PPS_OVERRIDE_EN_BUFFER) { + buf &= ~DP_PCON_ENCODER_PPS_OVERRIDE_MASK; + buf |= pps_buf_config << 2; + } + + ret = drm_dp_dpcd_writeb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, buf); + if (ret < 0) + return ret; + + return 0; +} + +/** + * drm_dp_pcon_pps_default() - Let PCON fill the default pps parameters + * for DSC1.2 between PCON & HDMI2.1 sink + * @aux: DisplayPort AUX channel + * + * Returns 0 on success, else returns negative error code. + */ +int drm_dp_pcon_pps_default(struct drm_dp_aux *aux) +{ + int ret; + + ret = drm_dp_pcon_configure_dsc_enc(aux, DP_PCON_ENC_PPS_OVERRIDE_DISABLED); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_pps_default); + +/** + * drm_dp_pcon_pps_override_buf() - Configure PPS encoder override buffer for + * HDMI sink + * @aux: DisplayPort AUX channel + * @pps_buf: 128 bytes to be written into PPS buffer for HDMI sink by PCON. + * + * Returns 0 on success, else returns negative error code. + */ +int drm_dp_pcon_pps_override_buf(struct drm_dp_aux *aux, u8 pps_buf[128]) +{ + int ret; + + ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVERRIDE_BASE, &pps_buf, 128); + if (ret < 0) + return ret; + + ret = drm_dp_pcon_configure_dsc_enc(aux, DP_PCON_ENC_PPS_OVERRIDE_EN_BUFFER); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_pps_override_buf); + +/* + * drm_dp_pcon_pps_override_param() - Write PPS parameters to DSC encoder + * override registers + * @aux: DisplayPort AUX channel + * @pps_param: 3 Parameters (2 Bytes each) : Slice Width, Slice Height, + * bits_per_pixel. + * + * Returns 0 on success, else returns negative error code. + */ +int drm_dp_pcon_pps_override_param(struct drm_dp_aux *aux, u8 pps_param[6]) +{ + int ret; + + ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVRD_SLICE_HEIGHT, &pps_param[0], 2); + if (ret < 0) + return ret; + ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVRD_SLICE_WIDTH, &pps_param[2], 2); + if (ret < 0) + return ret; + ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVRD_BPP, &pps_param[4], 2); + if (ret < 0) + return ret; + + ret = drm_dp_pcon_configure_dsc_enc(aux, DP_PCON_ENC_PPS_OVERRIDE_EN_BUFFER); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_pps_override_param); + +/* + * drm_dp_pcon_convert_rgb_to_ycbcr() - Configure the PCon to convert RGB to Ycbcr + * @aux: displayPort AUX channel + * @color_spc: Color-space/s for which conversion is to be enabled, 0 for disable. + * + * Returns 0 on success, else returns negative error code. + */ +int drm_dp_pcon_convert_rgb_to_ycbcr(struct drm_dp_aux *aux, u8 color_spc) +{ + int ret; + u8 buf; + + ret = drm_dp_dpcd_readb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, &buf); + if (ret < 0) + return ret; + + if (color_spc & DP_CONVERSION_RGB_YCBCR_MASK) + buf |= (color_spc & DP_CONVERSION_RGB_YCBCR_MASK); + else + buf &= ~DP_CONVERSION_RGB_YCBCR_MASK; + + ret = drm_dp_dpcd_writeb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, buf); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_dp_pcon_convert_rgb_to_ycbcr); + +/** + * drm_edp_backlight_set_level() - Set the backlight level of an eDP panel via AUX + * @aux: The DP AUX channel to use + * @bl: Backlight capability info from drm_edp_backlight_init() + * @level: The brightness level to set + * + * Sets the brightness level of an eDP panel's backlight. Note that the panel's backlight must + * already have been enabled by the driver by calling drm_edp_backlight_enable(). + * + * Returns: %0 on success, negative error code on failure + */ +int drm_edp_backlight_set_level(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, + u16 level) +{ + int ret; + u8 buf[2] = { 0 }; + + /* The panel uses the PWM for controlling brightness levels */ + if (!bl->aux_set) + return 0; + + if (bl->lsb_reg_used) { + buf[0] = (level & 0xff00) >> 8; + buf[1] = (level & 0x00ff); + } else { + buf[0] = level; + } + + ret = drm_dp_dpcd_write(aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, buf, sizeof(buf)); + if (ret != sizeof(buf)) { + drm_err(aux->drm_dev, + "%s: Failed to write aux backlight level: %d\n", + aux->name, ret); + return ret < 0 ? ret : -EIO; + } + + return 0; +} +EXPORT_SYMBOL(drm_edp_backlight_set_level); + +static int +drm_edp_backlight_set_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, + bool enable) +{ + int ret; + u8 buf; + + /* This panel uses the EDP_BL_PWR GPIO for enablement */ + if (!bl->aux_enable) + return 0; + + ret = drm_dp_dpcd_readb(aux, DP_EDP_DISPLAY_CONTROL_REGISTER, &buf); + if (ret != 1) { + drm_err(aux->drm_dev, "%s: Failed to read eDP display control register: %d\n", + aux->name, ret); + return ret < 0 ? ret : -EIO; + } + if (enable) + buf |= DP_EDP_BACKLIGHT_ENABLE; + else + buf &= ~DP_EDP_BACKLIGHT_ENABLE; + + ret = drm_dp_dpcd_writeb(aux, DP_EDP_DISPLAY_CONTROL_REGISTER, buf); + if (ret != 1) { + drm_err(aux->drm_dev, "%s: Failed to write eDP display control register: %d\n", + aux->name, ret); + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +/** + * drm_edp_backlight_enable() - Enable an eDP panel's backlight using DPCD + * @aux: The DP AUX channel to use + * @bl: Backlight capability info from drm_edp_backlight_init() + * @level: The initial backlight level to set via AUX, if there is one + * + * This function handles enabling DPCD backlight controls on a panel over DPCD, while additionally + * restoring any important backlight state such as the given backlight level, the brightness byte + * count, backlight frequency, etc. + * + * Note that certain panels do not support being enabled or disabled via DPCD, but instead require + * that the driver handle enabling/disabling the panel through implementation-specific means using + * the EDP_BL_PWR GPIO. For such panels, &drm_edp_backlight_info.aux_enable will be set to %false, + * this function becomes a no-op, and the driver is expected to handle powering the panel on using + * the EDP_BL_PWR GPIO. + * + * Returns: %0 on success, negative error code on failure. + */ +int drm_edp_backlight_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, + const u16 level) +{ + int ret; + u8 dpcd_buf; + + if (bl->aux_set) + dpcd_buf = DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD; + else + dpcd_buf = DP_EDP_BACKLIGHT_CONTROL_MODE_PWM; + + if (bl->pwmgen_bit_count) { + ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, bl->pwmgen_bit_count); + if (ret != 1) + drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n", + aux->name, ret); + } + + if (bl->pwm_freq_pre_divider) { + ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_FREQ_SET, bl->pwm_freq_pre_divider); + if (ret != 1) + drm_dbg_kms(aux->drm_dev, + "%s: Failed to write aux backlight frequency: %d\n", + aux->name, ret); + else + dpcd_buf |= DP_EDP_BACKLIGHT_FREQ_AUX_SET_ENABLE; + } + + ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, dpcd_buf); + if (ret != 1) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux backlight mode: %d\n", + aux->name, ret); + return ret < 0 ? ret : -EIO; + } + + ret = drm_edp_backlight_set_level(aux, bl, level); + if (ret < 0) + return ret; + ret = drm_edp_backlight_set_enable(aux, bl, true); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_edp_backlight_enable); + +/** + * drm_edp_backlight_disable() - Disable an eDP backlight using DPCD, if supported + * @aux: The DP AUX channel to use + * @bl: Backlight capability info from drm_edp_backlight_init() + * + * This function handles disabling DPCD backlight controls on a panel over AUX. + * + * Note that certain panels do not support being enabled or disabled via DPCD, but instead require + * that the driver handle enabling/disabling the panel through implementation-specific means using + * the EDP_BL_PWR GPIO. For such panels, &drm_edp_backlight_info.aux_enable will be set to %false, + * this function becomes a no-op, and the driver is expected to handle powering the panel off using + * the EDP_BL_PWR GPIO. + * + * Returns: %0 on success or no-op, negative error code on failure. + */ +int drm_edp_backlight_disable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl) +{ + int ret; + + ret = drm_edp_backlight_set_enable(aux, bl, false); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(drm_edp_backlight_disable); + +static inline int +drm_edp_backlight_probe_max(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, + u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]) +{ + int fxp, fxp_min, fxp_max, fxp_actual, f = 1; + int ret; + u8 pn, pn_min, pn_max; + + if (!bl->aux_set) + return 0; + + ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT, &pn); + if (ret != 1) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap: %d\n", + aux->name, ret); + return -ENODEV; + } + + pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK; + bl->max = (1 << pn) - 1; + if (!driver_pwm_freq_hz) + return 0; + + /* + * Set PWM Frequency divider to match desired frequency provided by the driver. + * The PWM Frequency is calculated as 27Mhz / (F x P). + * - Where F = PWM Frequency Pre-Divider value programmed by field 7:0 of the + * EDP_BACKLIGHT_FREQ_SET register (DPCD Address 00728h) + * - Where P = 2^Pn, where Pn is the value programmed by field 4:0 of the + * EDP_PWMGEN_BIT_COUNT register (DPCD Address 00724h) + */ + + /* Find desired value of (F x P) + * Note that, if F x P is out of supported range, the maximum value or minimum value will + * applied automatically. So no need to check that. + */ + fxp = DIV_ROUND_CLOSEST(1000 * DP_EDP_BACKLIGHT_FREQ_BASE_KHZ, driver_pwm_freq_hz); + + /* Use highest possible value of Pn for more granularity of brightness adjustment while + * satisfying the conditions below. + * - Pn is in the range of Pn_min and Pn_max + * - F is in the range of 1 and 255 + * - FxP is within 25% of desired value. + * Note: 25% is arbitrary value and may need some tweak. + */ + ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min); + if (ret != 1) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap min: %d\n", + aux->name, ret); + return 0; + } + ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max); + if (ret != 1) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap max: %d\n", + aux->name, ret); + return 0; + } + pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; + pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; + + /* Ensure frequency is within 25% of desired value */ + fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4); + fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4); + if (fxp_min < (1 << pn_min) || (255 << pn_max) < fxp_max) { + drm_dbg_kms(aux->drm_dev, + "%s: Driver defined backlight frequency (%d) out of range\n", + aux->name, driver_pwm_freq_hz); + return 0; + } + + for (pn = pn_max; pn >= pn_min; pn--) { + f = clamp(DIV_ROUND_CLOSEST(fxp, 1 << pn), 1, 255); + fxp_actual = f << pn; + if (fxp_min <= fxp_actual && fxp_actual <= fxp_max) + break; + } + + ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, pn); + if (ret != 1) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n", + aux->name, ret); + return 0; + } + bl->pwmgen_bit_count = pn; + bl->max = (1 << pn) - 1; + + if (edp_dpcd[2] & DP_EDP_BACKLIGHT_FREQ_AUX_SET_CAP) { + bl->pwm_freq_pre_divider = f; + drm_dbg_kms(aux->drm_dev, "%s: Using backlight frequency from driver (%dHz)\n", + aux->name, driver_pwm_freq_hz); + } + + return 0; +} + +static inline int +drm_edp_backlight_probe_state(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, + u8 *current_mode) +{ + int ret; + u8 buf[2]; + u8 mode_reg; + + ret = drm_dp_dpcd_readb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &mode_reg); + if (ret != 1) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to read backlight mode: %d\n", + aux->name, ret); + return ret < 0 ? ret : -EIO; + } + + *current_mode = (mode_reg & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK); + if (!bl->aux_set) + return 0; + + if (*current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) { + int size = 1 + bl->lsb_reg_used; + + ret = drm_dp_dpcd_read(aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, buf, size); + if (ret != size) { + drm_dbg_kms(aux->drm_dev, "%s: Failed to read backlight level: %d\n", + aux->name, ret); + return ret < 0 ? ret : -EIO; + } + + if (bl->lsb_reg_used) + return (buf[0] << 8) | buf[1]; + else + return buf[0]; + } + + /* + * If we're not in DPCD control mode yet, the programmed brightness value is meaningless and + * the driver should assume max brightness + */ + return bl->max; +} + +/** + * drm_edp_backlight_init() - Probe a display panel's TCON using the standard VESA eDP backlight + * interface. + * @aux: The DP aux device to use for probing + * @bl: The &drm_edp_backlight_info struct to fill out with information on the backlight + * @driver_pwm_freq_hz: Optional PWM frequency from the driver in hz + * @edp_dpcd: A cached copy of the eDP DPCD + * @current_level: Where to store the probed brightness level, if any + * @current_mode: Where to store the currently set backlight control mode + * + * Initializes a &drm_edp_backlight_info struct by probing @aux for it's backlight capabilities, + * along with also probing the current and maximum supported brightness levels. + * + * If @driver_pwm_freq_hz is non-zero, this will be used as the backlight frequency. Otherwise, the + * default frequency from the panel is used. + * + * Returns: %0 on success, negative error code on failure. + */ +int +drm_edp_backlight_init(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, + u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE], + u16 *current_level, u8 *current_mode) +{ + int ret; + + if (edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP) + bl->aux_enable = true; + if (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP) + bl->aux_set = true; + if (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) + bl->lsb_reg_used = true; + + /* Sanity check caps */ + if (!bl->aux_set && !(edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_PWM_PIN_CAP)) { + drm_dbg_kms(aux->drm_dev, + "%s: Panel supports neither AUX or PWM brightness control? Aborting\n", + aux->name); + return -EINVAL; + } + + ret = drm_edp_backlight_probe_max(aux, bl, driver_pwm_freq_hz, edp_dpcd); + if (ret < 0) + return ret; + + ret = drm_edp_backlight_probe_state(aux, bl, current_mode); + if (ret < 0) + return ret; + *current_level = ret; + + drm_dbg_kms(aux->drm_dev, + "%s: Found backlight: aux_set=%d aux_enable=%d mode=%d\n", + aux->name, bl->aux_set, bl->aux_enable, *current_mode); + if (bl->aux_set) { + drm_dbg_kms(aux->drm_dev, + "%s: Backlight caps: level=%d/%d pwm_freq_pre_divider=%d lsb_reg_used=%d\n", + aux->name, *current_level, bl->max, bl->pwm_freq_pre_divider, + bl->lsb_reg_used); + } + + return 0; +} +EXPORT_SYMBOL(drm_edp_backlight_init); + +#if IS_BUILTIN(CONFIG_BACKLIGHT_CLASS_DEVICE) || \ + (IS_MODULE(CONFIG_DRM_KMS_HELPER) && IS_MODULE(CONFIG_BACKLIGHT_CLASS_DEVICE)) + +static int dp_aux_backlight_update_status(struct backlight_device *bd) +{ + struct dp_aux_backlight *bl = bl_get_data(bd); + u16 brightness = backlight_get_brightness(bd); + int ret = 0; + + if (!backlight_is_blank(bd)) { + if (!bl->enabled) { + drm_edp_backlight_enable(bl->aux, &bl->info, brightness); + bl->enabled = true; + return 0; + } + ret = drm_edp_backlight_set_level(bl->aux, &bl->info, brightness); + } else { + if (bl->enabled) { + drm_edp_backlight_disable(bl->aux, &bl->info); + bl->enabled = false; + } + } + + return ret; +} + +static const struct backlight_ops dp_aux_bl_ops = { + .update_status = dp_aux_backlight_update_status, +}; + +/** + * drm_panel_dp_aux_backlight - create and use DP AUX backlight + * @panel: DRM panel + * @aux: The DP AUX channel to use + * + * Use this function to create and handle backlight if your panel + * supports backlight control over DP AUX channel using DPCD + * registers as per VESA's standard backlight control interface. + * + * When the panel is enabled backlight will be enabled after a + * successful call to &drm_panel_funcs.enable() + * + * When the panel is disabled backlight will be disabled before the + * call to &drm_panel_funcs.disable(). + * + * A typical implementation for a panel driver supporting backlight + * control over DP AUX will call this function at probe time. + * Backlight will then be handled transparently without requiring + * any intervention from the driver. + * + * drm_panel_dp_aux_backlight() must be called after the call to drm_panel_init(). + * + * Return: 0 on success or a negative error code on failure. + */ +int drm_panel_dp_aux_backlight(struct drm_panel *panel, struct drm_dp_aux *aux) +{ + struct dp_aux_backlight *bl; + struct backlight_properties props = { 0 }; + u16 current_level; + u8 current_mode; + u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]; + int ret; + + if (!panel || !panel->dev || !aux) + return -EINVAL; + + ret = drm_dp_dpcd_read(aux, DP_EDP_DPCD_REV, edp_dpcd, + EDP_DISPLAY_CTL_CAP_SIZE); + if (ret < 0) + return ret; + + if (!drm_edp_backlight_supported(edp_dpcd)) { + DRM_DEV_INFO(panel->dev, "DP AUX backlight is not supported\n"); + return 0; + } + + bl = devm_kzalloc(panel->dev, sizeof(*bl), GFP_KERNEL); + if (!bl) + return -ENOMEM; + + bl->aux = aux; + + ret = drm_edp_backlight_init(aux, &bl->info, 0, edp_dpcd, + ¤t_level, ¤t_mode); + if (ret < 0) + return ret; + + props.type = BACKLIGHT_RAW; + props.brightness = current_level; + props.max_brightness = bl->info.max; + + bl->base = devm_backlight_device_register(panel->dev, "dp_aux_backlight", + panel->dev, bl, + &dp_aux_bl_ops, &props); + if (IS_ERR(bl->base)) + return PTR_ERR(bl->base); + + backlight_disable(bl->base); + + panel->backlight = bl->base; + + return 0; +} +EXPORT_SYMBOL(drm_panel_dp_aux_backlight); + +#endif diff --git a/drivers/gpu/drm/display/drm_dp_aux_bus.c b/drivers/gpu/drm/display/drm_dp_aux_bus.c new file mode 100644 index 000000000000..dccf3e2ea323 --- /dev/null +++ b/drivers/gpu/drm/display/drm_dp_aux_bus.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2021 Google Inc. + * + * The DP AUX bus is used for devices that are connected over a DisplayPort + * AUX bus. The devices on the far side of the bus are referred to as + * endpoints in this code. + * + * Commonly there is only one device connected to the DP AUX bus: a panel. + * Though historically panels (even DP panels) have been modeled as simple + * platform devices, putting them under the DP AUX bus allows the panel driver + * to perform transactions on that bus. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + * dp_aux_ep_match() - The match function for the dp_aux_bus. + * @dev: The device to match. + * @drv: The driver to try to match against. + * + * At the moment, we just match on device tree. + * + * Return: True if this driver matches this device; false otherwise. + */ +static int dp_aux_ep_match(struct device *dev, struct device_driver *drv) +{ + return !!of_match_device(drv->of_match_table, dev); +} + +/** + * dp_aux_ep_probe() - The probe function for the dp_aux_bus. + * @dev: The device to probe. + * + * Calls through to the endpoint driver probe. + * + * Return: 0 if no error or negative error code. + */ +static int dp_aux_ep_probe(struct device *dev) +{ + struct dp_aux_ep_driver *aux_ep_drv = to_dp_aux_ep_drv(dev->driver); + struct dp_aux_ep_device *aux_ep = to_dp_aux_ep_dev(dev); + int ret; + + ret = dev_pm_domain_attach(dev, true); + if (ret) + return dev_err_probe(dev, ret, "Failed to attach to PM Domain\n"); + + ret = aux_ep_drv->probe(aux_ep); + if (ret) + dev_pm_domain_detach(dev, true); + + return ret; +} + +/** + * dp_aux_ep_remove() - The remove function for the dp_aux_bus. + * @dev: The device to remove. + * + * Calls through to the endpoint driver remove. + * + */ +static void dp_aux_ep_remove(struct device *dev) +{ + struct dp_aux_ep_driver *aux_ep_drv = to_dp_aux_ep_drv(dev->driver); + struct dp_aux_ep_device *aux_ep = to_dp_aux_ep_dev(dev); + + if (aux_ep_drv->remove) + aux_ep_drv->remove(aux_ep); + dev_pm_domain_detach(dev, true); +} + +/** + * dp_aux_ep_shutdown() - The shutdown function for the dp_aux_bus. + * @dev: The device to shutdown. + * + * Calls through to the endpoint driver shutdown. + */ +static void dp_aux_ep_shutdown(struct device *dev) +{ + struct dp_aux_ep_driver *aux_ep_drv; + + if (!dev->driver) + return; + + aux_ep_drv = to_dp_aux_ep_drv(dev->driver); + if (aux_ep_drv->shutdown) + aux_ep_drv->shutdown(to_dp_aux_ep_dev(dev)); +} + +static struct bus_type dp_aux_bus_type = { + .name = "dp-aux", + .match = dp_aux_ep_match, + .probe = dp_aux_ep_probe, + .remove = dp_aux_ep_remove, + .shutdown = dp_aux_ep_shutdown, +}; + +static ssize_t modalias_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return of_device_modalias(dev, buf, PAGE_SIZE); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *dp_aux_ep_dev_attrs[] = { + &dev_attr_modalias.attr, + NULL, +}; +ATTRIBUTE_GROUPS(dp_aux_ep_dev); + +/** + * dp_aux_ep_dev_release() - Free memory for the dp_aux_ep device + * @dev: The device to free. + * + * Return: 0 if no error or negative error code. + */ +static void dp_aux_ep_dev_release(struct device *dev) +{ + kfree(to_dp_aux_ep_dev(dev)); +} + +static struct device_type dp_aux_device_type_type = { + .groups = dp_aux_ep_dev_groups, + .uevent = of_device_uevent_modalias, + .release = dp_aux_ep_dev_release, +}; + +/** + * of_dp_aux_ep_destroy() - Destroy an DP AUX endpoint device + * @dev: The device to destroy. + * @data: Not used + * + * This is just used as a callback by of_dp_aux_depopulate_ep_devices() and + * is called for _all_ of the child devices of the device providing the AUX bus. + * We'll only act on those that are of type "dp_aux_bus_type". + * + * This function is effectively an inverse of what's in the loop + * in of_dp_aux_populate_ep_devices(). + * + * Return: 0 if no error or negative error code. + */ +static int of_dp_aux_ep_destroy(struct device *dev, void *data) +{ + struct device_node *np = dev->of_node; + + if (dev->bus != &dp_aux_bus_type) + return 0; + + if (!of_node_check_flag(np, OF_POPULATED)) + return 0; + + of_node_clear_flag(np, OF_POPULATED); + of_node_put(np); + + device_unregister(dev); + + return 0; +} + +/** + * of_dp_aux_depopulate_ep_devices() - Undo of_dp_aux_populate_ep_devices + * @aux: The AUX channel whose devices we want to depopulate + * + * This will destroy all devices that were created + * by of_dp_aux_populate_ep_devices(). + */ +void of_dp_aux_depopulate_ep_devices(struct drm_dp_aux *aux) +{ + device_for_each_child_reverse(aux->dev, NULL, of_dp_aux_ep_destroy); +} +EXPORT_SYMBOL_GPL(of_dp_aux_depopulate_ep_devices); + +/** + * of_dp_aux_populate_ep_devices() - Populate the endpoint devices on the DP AUX + * @aux: The AUX channel whose devices we want to populate. It is required that + * drm_dp_aux_init() has already been called for this AUX channel. + * + * This will populate all the devices under the "aux-bus" node of the device + * providing the AUX channel (AKA aux->dev). + * + * When this function finishes, it is _possible_ (but not guaranteed) that + * our sub-devices will have finished probing. It should be noted that if our + * sub-devices return -EPROBE_DEFER that we will not return any error codes + * ourselves but our sub-devices will _not_ have actually probed successfully + * yet. There may be other cases (maybe added in the future?) where sub-devices + * won't have been probed yet when this function returns, so it's best not to + * rely on that. + * + * If this function succeeds you should later make sure you call + * of_dp_aux_depopulate_ep_devices() to undo it, or just use the devm version + * of this function. + * + * Return: 0 if no error or negative error code. + */ +int of_dp_aux_populate_ep_devices(struct drm_dp_aux *aux) +{ + struct device_node *bus, *np; + struct dp_aux_ep_device *aux_ep; + int ret; + + /* drm_dp_aux_init() should have been called already; warn if not */ + WARN_ON_ONCE(!aux->ddc.algo); + + if (!aux->dev->of_node) + return 0; + + bus = of_get_child_by_name(aux->dev->of_node, "aux-bus"); + if (!bus) + return 0; + + for_each_available_child_of_node(bus, np) { + if (of_node_test_and_set_flag(np, OF_POPULATED)) + continue; + + aux_ep = kzalloc(sizeof(*aux_ep), GFP_KERNEL); + if (!aux_ep) + continue; + aux_ep->aux = aux; + + aux_ep->dev.parent = aux->dev; + aux_ep->dev.bus = &dp_aux_bus_type; + aux_ep->dev.type = &dp_aux_device_type_type; + aux_ep->dev.of_node = of_node_get(np); + dev_set_name(&aux_ep->dev, "aux-%s", dev_name(aux->dev)); + + ret = device_register(&aux_ep->dev); + if (ret) { + dev_err(aux->dev, "Failed to create AUX EP for %pOF: %d\n", np, ret); + of_node_clear_flag(np, OF_POPULATED); + of_node_put(np); + + /* + * As per docs of device_register(), call this instead + * of kfree() directly for error cases. + */ + put_device(&aux_ep->dev); + + /* + * Following in the footsteps of of_i2c_register_devices(), + * we won't fail the whole function here--we'll just + * continue registering any other devices we find. + */ + } + } + + of_node_put(bus); + + return 0; +} + +static void of_dp_aux_depopulate_ep_devices_void(void *data) +{ + of_dp_aux_depopulate_ep_devices(data); +} + +/** + * devm_of_dp_aux_populate_ep_devices() - devm wrapper for of_dp_aux_populate_ep_devices() + * @aux: The AUX channel whose devices we want to populate + * + * Handles freeing w/ devm on the device "aux->dev". + * + * Return: 0 if no error or negative error code. + */ +int devm_of_dp_aux_populate_ep_devices(struct drm_dp_aux *aux) +{ + int ret; + + ret = of_dp_aux_populate_ep_devices(aux); + if (ret) + return ret; + + return devm_add_action_or_reset(aux->dev, + of_dp_aux_depopulate_ep_devices_void, + aux); +} +EXPORT_SYMBOL_GPL(devm_of_dp_aux_populate_ep_devices); + +int __dp_aux_dp_driver_register(struct dp_aux_ep_driver *drv, struct module *owner) +{ + drv->driver.owner = owner; + drv->driver.bus = &dp_aux_bus_type; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__dp_aux_dp_driver_register); + +void dp_aux_dp_driver_unregister(struct dp_aux_ep_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(dp_aux_dp_driver_unregister); + +static int __init dp_aux_bus_init(void) +{ + int ret; + + ret = bus_register(&dp_aux_bus_type); + if (ret) + return ret; + + return 0; +} + +static void __exit dp_aux_bus_exit(void) +{ + bus_unregister(&dp_aux_bus_type); +} + +subsys_initcall(dp_aux_bus_init); +module_exit(dp_aux_bus_exit); + +MODULE_AUTHOR("Douglas Anderson "); +MODULE_DESCRIPTION("DRM DisplayPort AUX bus"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/display/drm_dp_aux_dev.c b/drivers/gpu/drm/display/drm_dp_aux_dev.c new file mode 100644 index 000000000000..098e482e65a2 --- /dev/null +++ b/drivers/gpu/drm/display/drm_dp_aux_dev.c @@ -0,0 +1,354 @@ +/* + * Copyright © 2015 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Authors: + * Rafael Antognolli + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "drm_dp_helper_internal.h" + +struct drm_dp_aux_dev { + unsigned index; + struct drm_dp_aux *aux; + struct device *dev; + struct kref refcount; + atomic_t usecount; +}; + +#define DRM_AUX_MINORS 256 +#define AUX_MAX_OFFSET (1 << 20) +static DEFINE_IDR(aux_idr); +static DEFINE_MUTEX(aux_idr_mutex); +static struct class *drm_dp_aux_dev_class; +static int drm_dev_major = -1; + +static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_minor(unsigned index) +{ + struct drm_dp_aux_dev *aux_dev = NULL; + + mutex_lock(&aux_idr_mutex); + aux_dev = idr_find(&aux_idr, index); + if (aux_dev && !kref_get_unless_zero(&aux_dev->refcount)) + aux_dev = NULL; + mutex_unlock(&aux_idr_mutex); + + return aux_dev; +} + +static struct drm_dp_aux_dev *alloc_drm_dp_aux_dev(struct drm_dp_aux *aux) +{ + struct drm_dp_aux_dev *aux_dev; + int index; + + aux_dev = kzalloc(sizeof(*aux_dev), GFP_KERNEL); + if (!aux_dev) + return ERR_PTR(-ENOMEM); + aux_dev->aux = aux; + atomic_set(&aux_dev->usecount, 1); + kref_init(&aux_dev->refcount); + + mutex_lock(&aux_idr_mutex); + index = idr_alloc(&aux_idr, aux_dev, 0, DRM_AUX_MINORS, GFP_KERNEL); + mutex_unlock(&aux_idr_mutex); + if (index < 0) { + kfree(aux_dev); + return ERR_PTR(index); + } + aux_dev->index = index; + + return aux_dev; +} + +static void release_drm_dp_aux_dev(struct kref *ref) +{ + struct drm_dp_aux_dev *aux_dev = + container_of(ref, struct drm_dp_aux_dev, refcount); + + kfree(aux_dev); +} + +static ssize_t name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t res; + struct drm_dp_aux_dev *aux_dev = + drm_dp_aux_dev_get_by_minor(MINOR(dev->devt)); + + if (!aux_dev) + return -ENODEV; + + res = sprintf(buf, "%s\n", aux_dev->aux->name); + kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); + + return res; +} +static DEVICE_ATTR_RO(name); + +static struct attribute *drm_dp_aux_attrs[] = { + &dev_attr_name.attr, + NULL, +}; +ATTRIBUTE_GROUPS(drm_dp_aux); + +static int auxdev_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + struct drm_dp_aux_dev *aux_dev; + + aux_dev = drm_dp_aux_dev_get_by_minor(minor); + if (!aux_dev) + return -ENODEV; + + file->private_data = aux_dev; + return 0; +} + +static loff_t auxdev_llseek(struct file *file, loff_t offset, int whence) +{ + return fixed_size_llseek(file, offset, whence, AUX_MAX_OFFSET); +} + +static ssize_t auxdev_read_iter(struct kiocb *iocb, struct iov_iter *to) +{ + struct drm_dp_aux_dev *aux_dev = iocb->ki_filp->private_data; + loff_t pos = iocb->ki_pos; + ssize_t res = 0; + + if (!atomic_inc_not_zero(&aux_dev->usecount)) + return -ENODEV; + + iov_iter_truncate(to, AUX_MAX_OFFSET - pos); + + while (iov_iter_count(to)) { + uint8_t buf[DP_AUX_MAX_PAYLOAD_BYTES]; + ssize_t todo = min(iov_iter_count(to), sizeof(buf)); + + if (signal_pending(current)) { + res = -ERESTARTSYS; + break; + } + + res = drm_dp_dpcd_read(aux_dev->aux, pos, buf, todo); + + if (res <= 0) + break; + + if (copy_to_iter(buf, res, to) != res) { + res = -EFAULT; + break; + } + + pos += res; + } + + if (pos != iocb->ki_pos) + res = pos - iocb->ki_pos; + iocb->ki_pos = pos; + + if (atomic_dec_and_test(&aux_dev->usecount)) + wake_up_var(&aux_dev->usecount); + + return res; +} + +static ssize_t auxdev_write_iter(struct kiocb *iocb, struct iov_iter *from) +{ + struct drm_dp_aux_dev *aux_dev = iocb->ki_filp->private_data; + loff_t pos = iocb->ki_pos; + ssize_t res = 0; + + if (!atomic_inc_not_zero(&aux_dev->usecount)) + return -ENODEV; + + iov_iter_truncate(from, AUX_MAX_OFFSET - pos); + + while (iov_iter_count(from)) { + uint8_t buf[DP_AUX_MAX_PAYLOAD_BYTES]; + ssize_t todo = min(iov_iter_count(from), sizeof(buf)); + + if (signal_pending(current)) { + res = -ERESTARTSYS; + break; + } + + if (!copy_from_iter_full(buf, todo, from)) { + res = -EFAULT; + break; + } + + res = drm_dp_dpcd_write(aux_dev->aux, pos, buf, todo); + + if (res <= 0) + break; + + pos += res; + } + + if (pos != iocb->ki_pos) + res = pos - iocb->ki_pos; + iocb->ki_pos = pos; + + if (atomic_dec_and_test(&aux_dev->usecount)) + wake_up_var(&aux_dev->usecount); + + return res; +} + +static int auxdev_release(struct inode *inode, struct file *file) +{ + struct drm_dp_aux_dev *aux_dev = file->private_data; + + kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); + return 0; +} + +static const struct file_operations auxdev_fops = { + .owner = THIS_MODULE, + .llseek = auxdev_llseek, + .read_iter = auxdev_read_iter, + .write_iter = auxdev_write_iter, + .open = auxdev_open, + .release = auxdev_release, +}; + +#define to_auxdev(d) container_of(d, struct drm_dp_aux_dev, aux) + +static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_aux(struct drm_dp_aux *aux) +{ + struct drm_dp_aux_dev *iter, *aux_dev = NULL; + int id; + + /* don't increase kref count here because this function should only be + * used by drm_dp_aux_unregister_devnode. Thus, it will always have at + * least one reference - the one that drm_dp_aux_register_devnode + * created + */ + mutex_lock(&aux_idr_mutex); + idr_for_each_entry(&aux_idr, iter, id) { + if (iter->aux == aux) { + aux_dev = iter; + break; + } + } + mutex_unlock(&aux_idr_mutex); + return aux_dev; +} + +void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) +{ + struct drm_dp_aux_dev *aux_dev; + unsigned int minor; + + aux_dev = drm_dp_aux_dev_get_by_aux(aux); + if (!aux_dev) /* attach must have failed */ + return; + + /* + * As some AUX adapters may exist as platform devices which outlive their respective DRM + * devices, we clear drm_dev to ensure that we never accidentally reference a stale pointer + */ + aux->drm_dev = NULL; + + mutex_lock(&aux_idr_mutex); + idr_remove(&aux_idr, aux_dev->index); + mutex_unlock(&aux_idr_mutex); + + atomic_dec(&aux_dev->usecount); + wait_var_event(&aux_dev->usecount, !atomic_read(&aux_dev->usecount)); + + minor = aux_dev->index; + if (aux_dev->dev) + device_destroy(drm_dp_aux_dev_class, + MKDEV(drm_dev_major, minor)); + + DRM_DEBUG("drm_dp_aux_dev: aux [%s] unregistering\n", aux->name); + kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); +} + +int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) +{ + struct drm_dp_aux_dev *aux_dev; + int res; + + aux_dev = alloc_drm_dp_aux_dev(aux); + if (IS_ERR(aux_dev)) + return PTR_ERR(aux_dev); + + aux_dev->dev = device_create(drm_dp_aux_dev_class, aux->dev, + MKDEV(drm_dev_major, aux_dev->index), NULL, + "drm_dp_aux%d", aux_dev->index); + if (IS_ERR(aux_dev->dev)) { + res = PTR_ERR(aux_dev->dev); + aux_dev->dev = NULL; + goto error; + } + + DRM_DEBUG("drm_dp_aux_dev: aux [%s] registered as minor %d\n", + aux->name, aux_dev->index); + return 0; +error: + drm_dp_aux_unregister_devnode(aux); + return res; +} + +int drm_dp_aux_dev_init(void) +{ + int res; + + drm_dp_aux_dev_class = class_create(THIS_MODULE, "drm_dp_aux_dev"); + if (IS_ERR(drm_dp_aux_dev_class)) { + return PTR_ERR(drm_dp_aux_dev_class); + } + drm_dp_aux_dev_class->dev_groups = drm_dp_aux_groups; + + res = register_chrdev(0, "aux", &auxdev_fops); + if (res < 0) + goto out; + drm_dev_major = res; + + return 0; +out: + class_destroy(drm_dp_aux_dev_class); + return res; +} + +void drm_dp_aux_dev_exit(void) +{ + unregister_chrdev(drm_dev_major, "aux"); + class_destroy(drm_dp_aux_dev_class); +} diff --git a/drivers/gpu/drm/display/drm_dp_cec.c b/drivers/gpu/drm/display/drm_dp_cec.c new file mode 100644 index 000000000000..ae39dc794190 --- /dev/null +++ b/drivers/gpu/drm/display/drm_dp_cec.c @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DisplayPort CEC-Tunneling-over-AUX support + * + * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + */ + +#include +#include +#include + +#include + +#include +#include +#include + +/* + * Unfortunately it turns out that we have a chicken-and-egg situation + * here. Quite a few active (mini-)DP-to-HDMI or USB-C-to-HDMI adapters + * have a converter chip that supports CEC-Tunneling-over-AUX (usually the + * Parade PS176), but they do not wire up the CEC pin, thus making CEC + * useless. Note that MegaChips 2900-based adapters appear to have good + * support for CEC tunneling. Those adapters that I have tested using + * this chipset all have the CEC line connected. + * + * Sadly there is no way for this driver to know this. What happens is + * that a /dev/cecX device is created that is isolated and unable to see + * any of the other CEC devices. Quite literally the CEC wire is cut + * (or in this case, never connected in the first place). + * + * The reason so few adapters support this is that this tunneling protocol + * was never supported by any OS. So there was no easy way of testing it, + * and no incentive to correctly wire up the CEC pin. + * + * Hopefully by creating this driver it will be easier for vendors to + * finally fix their adapters and test the CEC functionality. + * + * I keep a list of known working adapters here: + * + * https://hverkuil.home.xs4all.nl/cec-status.txt + * + * Please mail me (hverkuil@xs4all.nl) if you find an adapter that works + * and is not yet listed there. + * + * Note that the current implementation does not support CEC over an MST hub. + * As far as I can see there is no mechanism defined in the DisplayPort + * standard to transport CEC interrupts over an MST device. It might be + * possible to do this through polling, but I have not been able to get that + * to work. + */ + +/** + * DOC: dp cec helpers + * + * These functions take care of supporting the CEC-Tunneling-over-AUX + * feature of DisplayPort-to-HDMI adapters. + */ + +/* + * When the EDID is unset because the HPD went low, then the CEC DPCD registers + * typically can no longer be read (true for a DP-to-HDMI adapter since it is + * powered by the HPD). However, some displays toggle the HPD off and on for a + * short period for one reason or another, and that would cause the CEC adapter + * to be removed and added again, even though nothing else changed. + * + * This module parameter sets a delay in seconds before the CEC adapter is + * actually unregistered. Only if the HPD does not return within that time will + * the CEC adapter be unregistered. + * + * If it is set to a value >= NEVER_UNREG_DELAY, then the CEC adapter will never + * be unregistered for as long as the connector remains registered. + * + * If it is set to 0, then the CEC adapter will be unregistered immediately as + * soon as the HPD disappears. + * + * The default is one second to prevent short HPD glitches from unregistering + * the CEC adapter. + * + * Note that for integrated HDMI branch devices that support CEC the DPCD + * registers remain available even if the HPD goes low since it is not powered + * by the HPD. In that case the CEC adapter will never be unregistered during + * the life time of the connector. At least, this is the theory since I do not + * have hardware with an integrated HDMI branch device that supports CEC. + */ +#define NEVER_UNREG_DELAY 1000 +static unsigned int drm_dp_cec_unregister_delay = 1; +module_param(drm_dp_cec_unregister_delay, uint, 0600); +MODULE_PARM_DESC(drm_dp_cec_unregister_delay, + "CEC unregister delay in seconds, 0: no delay, >= 1000: never unregister"); + +static int drm_dp_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct drm_dp_aux *aux = cec_get_drvdata(adap); + u32 val = enable ? DP_CEC_TUNNELING_ENABLE : 0; + ssize_t err = 0; + + err = drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_CONTROL, val); + return (enable && err < 0) ? err : 0; +} + +static int drm_dp_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct drm_dp_aux *aux = cec_get_drvdata(adap); + /* Bit 15 (logical address 15) should always be set */ + u16 la_mask = 1 << CEC_LOG_ADDR_BROADCAST; + u8 mask[2]; + ssize_t err; + + if (addr != CEC_LOG_ADDR_INVALID) + la_mask |= adap->log_addrs.log_addr_mask | (1 << addr); + mask[0] = la_mask & 0xff; + mask[1] = la_mask >> 8; + err = drm_dp_dpcd_write(aux, DP_CEC_LOGICAL_ADDRESS_MASK, mask, 2); + return (addr != CEC_LOG_ADDR_INVALID && err < 0) ? err : 0; +} + +static int drm_dp_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct drm_dp_aux *aux = cec_get_drvdata(adap); + unsigned int retries = min(5, attempts - 1); + ssize_t err; + + err = drm_dp_dpcd_write(aux, DP_CEC_TX_MESSAGE_BUFFER, + msg->msg, msg->len); + if (err < 0) + return err; + + err = drm_dp_dpcd_writeb(aux, DP_CEC_TX_MESSAGE_INFO, + (msg->len - 1) | (retries << 4) | + DP_CEC_TX_MESSAGE_SEND); + return err < 0 ? err : 0; +} + +static int drm_dp_cec_adap_monitor_all_enable(struct cec_adapter *adap, + bool enable) +{ + struct drm_dp_aux *aux = cec_get_drvdata(adap); + ssize_t err; + u8 val; + + if (!(adap->capabilities & CEC_CAP_MONITOR_ALL)) + return 0; + + err = drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_CONTROL, &val); + if (err >= 0) { + if (enable) + val |= DP_CEC_SNOOPING_ENABLE; + else + val &= ~DP_CEC_SNOOPING_ENABLE; + err = drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_CONTROL, val); + } + return (enable && err < 0) ? err : 0; +} + +static void drm_dp_cec_adap_status(struct cec_adapter *adap, + struct seq_file *file) +{ + struct drm_dp_aux *aux = cec_get_drvdata(adap); + struct drm_dp_desc desc; + struct drm_dp_dpcd_ident *id = &desc.ident; + + if (drm_dp_read_desc(aux, &desc, true)) + return; + seq_printf(file, "OUI: %*phD\n", + (int)sizeof(id->oui), id->oui); + seq_printf(file, "ID: %*pE\n", + (int)strnlen(id->device_id, sizeof(id->device_id)), + id->device_id); + seq_printf(file, "HW Rev: %d.%d\n", id->hw_rev >> 4, id->hw_rev & 0xf); + /* + * Show this both in decimal and hex: at least one vendor + * always reports this in hex. + */ + seq_printf(file, "FW/SW Rev: %d.%d (0x%02x.0x%02x)\n", + id->sw_major_rev, id->sw_minor_rev, + id->sw_major_rev, id->sw_minor_rev); +} + +static const struct cec_adap_ops drm_dp_cec_adap_ops = { + .adap_enable = drm_dp_cec_adap_enable, + .adap_log_addr = drm_dp_cec_adap_log_addr, + .adap_transmit = drm_dp_cec_adap_transmit, + .adap_monitor_all_enable = drm_dp_cec_adap_monitor_all_enable, + .adap_status = drm_dp_cec_adap_status, +}; + +static int drm_dp_cec_received(struct drm_dp_aux *aux) +{ + struct cec_adapter *adap = aux->cec.adap; + struct cec_msg msg; + u8 rx_msg_info; + ssize_t err; + + err = drm_dp_dpcd_readb(aux, DP_CEC_RX_MESSAGE_INFO, &rx_msg_info); + if (err < 0) + return err; + + if (!(rx_msg_info & DP_CEC_RX_MESSAGE_ENDED)) + return 0; + + msg.len = (rx_msg_info & DP_CEC_RX_MESSAGE_LEN_MASK) + 1; + err = drm_dp_dpcd_read(aux, DP_CEC_RX_MESSAGE_BUFFER, msg.msg, msg.len); + if (err < 0) + return err; + + cec_received_msg(adap, &msg); + return 0; +} + +static void drm_dp_cec_handle_irq(struct drm_dp_aux *aux) +{ + struct cec_adapter *adap = aux->cec.adap; + u8 flags; + + if (drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_IRQ_FLAGS, &flags) < 0) + return; + + if (flags & DP_CEC_RX_MESSAGE_INFO_VALID) + drm_dp_cec_received(aux); + + if (flags & DP_CEC_TX_MESSAGE_SENT) + cec_transmit_attempt_done(adap, CEC_TX_STATUS_OK); + else if (flags & DP_CEC_TX_LINE_ERROR) + cec_transmit_attempt_done(adap, CEC_TX_STATUS_ERROR | + CEC_TX_STATUS_MAX_RETRIES); + else if (flags & + (DP_CEC_TX_ADDRESS_NACK_ERROR | DP_CEC_TX_DATA_NACK_ERROR)) + cec_transmit_attempt_done(adap, CEC_TX_STATUS_NACK | + CEC_TX_STATUS_MAX_RETRIES); + drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_IRQ_FLAGS, flags); +} + +/** + * drm_dp_cec_irq() - handle CEC interrupt, if any + * @aux: DisplayPort AUX channel + * + * Should be called when handling an IRQ_HPD request. If CEC-tunneling-over-AUX + * is present, then it will check for a CEC_IRQ and handle it accordingly. + */ +void drm_dp_cec_irq(struct drm_dp_aux *aux) +{ + u8 cec_irq; + int ret; + + /* No transfer function was set, so not a DP connector */ + if (!aux->transfer) + return; + + mutex_lock(&aux->cec.lock); + if (!aux->cec.adap) + goto unlock; + + ret = drm_dp_dpcd_readb(aux, DP_DEVICE_SERVICE_IRQ_VECTOR_ESI1, + &cec_irq); + if (ret < 0 || !(cec_irq & DP_CEC_IRQ)) + goto unlock; + + drm_dp_cec_handle_irq(aux); + drm_dp_dpcd_writeb(aux, DP_DEVICE_SERVICE_IRQ_VECTOR_ESI1, DP_CEC_IRQ); +unlock: + mutex_unlock(&aux->cec.lock); +} +EXPORT_SYMBOL(drm_dp_cec_irq); + +static bool drm_dp_cec_cap(struct drm_dp_aux *aux, u8 *cec_cap) +{ + u8 cap = 0; + + if (drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_CAPABILITY, &cap) != 1 || + !(cap & DP_CEC_TUNNELING_CAPABLE)) + return false; + if (cec_cap) + *cec_cap = cap; + return true; +} + +/* + * Called if the HPD was low for more than drm_dp_cec_unregister_delay + * seconds. This unregisters the CEC adapter. + */ +static void drm_dp_cec_unregister_work(struct work_struct *work) +{ + struct drm_dp_aux *aux = container_of(work, struct drm_dp_aux, + cec.unregister_work.work); + + mutex_lock(&aux->cec.lock); + cec_unregister_adapter(aux->cec.adap); + aux->cec.adap = NULL; + mutex_unlock(&aux->cec.lock); +} + +/* + * A new EDID is set. If there is no CEC adapter, then create one. If + * there was a CEC adapter, then check if the CEC adapter properties + * were unchanged and just update the CEC physical address. Otherwise + * unregister the old CEC adapter and create a new one. + */ +void drm_dp_cec_set_edid(struct drm_dp_aux *aux, const struct edid *edid) +{ + struct drm_connector *connector = aux->cec.connector; + u32 cec_caps = CEC_CAP_DEFAULTS | CEC_CAP_NEEDS_HPD | + CEC_CAP_CONNECTOR_INFO; + struct cec_connector_info conn_info; + unsigned int num_las = 1; + u8 cap; + + /* No transfer function was set, so not a DP connector */ + if (!aux->transfer) + return; + +#ifndef CONFIG_MEDIA_CEC_RC + /* + * CEC_CAP_RC is part of CEC_CAP_DEFAULTS, but it is stripped by + * cec_allocate_adapter() if CONFIG_MEDIA_CEC_RC is undefined. + * + * Do this here as well to ensure the tests against cec_caps are + * correct. + */ + cec_caps &= ~CEC_CAP_RC; +#endif + cancel_delayed_work_sync(&aux->cec.unregister_work); + + mutex_lock(&aux->cec.lock); + if (!drm_dp_cec_cap(aux, &cap)) { + /* CEC is not supported, unregister any existing adapter */ + cec_unregister_adapter(aux->cec.adap); + aux->cec.adap = NULL; + goto unlock; + } + + if (cap & DP_CEC_SNOOPING_CAPABLE) + cec_caps |= CEC_CAP_MONITOR_ALL; + if (cap & DP_CEC_MULTIPLE_LA_CAPABLE) + num_las = CEC_MAX_LOG_ADDRS; + + if (aux->cec.adap) { + if (aux->cec.adap->capabilities == cec_caps && + aux->cec.adap->available_log_addrs == num_las) { + /* Unchanged, so just set the phys addr */ + cec_s_phys_addr_from_edid(aux->cec.adap, edid); + goto unlock; + } + /* + * The capabilities changed, so unregister the old + * adapter first. + */ + cec_unregister_adapter(aux->cec.adap); + } + + /* Create a new adapter */ + aux->cec.adap = cec_allocate_adapter(&drm_dp_cec_adap_ops, + aux, connector->name, cec_caps, + num_las); + if (IS_ERR(aux->cec.adap)) { + aux->cec.adap = NULL; + goto unlock; + } + + cec_fill_conn_info_from_drm(&conn_info, connector); + cec_s_conn_info(aux->cec.adap, &conn_info); + + if (cec_register_adapter(aux->cec.adap, connector->dev->dev)) { + cec_delete_adapter(aux->cec.adap); + aux->cec.adap = NULL; + } else { + /* + * Update the phys addr for the new CEC adapter. When called + * from drm_dp_cec_register_connector() edid == NULL, so in + * that case the phys addr is just invalidated. + */ + cec_s_phys_addr_from_edid(aux->cec.adap, edid); + } +unlock: + mutex_unlock(&aux->cec.lock); +} +EXPORT_SYMBOL(drm_dp_cec_set_edid); + +/* + * The EDID disappeared (likely because of the HPD going down). + */ +void drm_dp_cec_unset_edid(struct drm_dp_aux *aux) +{ + /* No transfer function was set, so not a DP connector */ + if (!aux->transfer) + return; + + cancel_delayed_work_sync(&aux->cec.unregister_work); + + mutex_lock(&aux->cec.lock); + if (!aux->cec.adap) + goto unlock; + + cec_phys_addr_invalidate(aux->cec.adap); + /* + * We're done if we want to keep the CEC device + * (drm_dp_cec_unregister_delay is >= NEVER_UNREG_DELAY) or if the + * DPCD still indicates the CEC capability (expected for an integrated + * HDMI branch device). + */ + if (drm_dp_cec_unregister_delay < NEVER_UNREG_DELAY && + !drm_dp_cec_cap(aux, NULL)) { + /* + * Unregister the CEC adapter after drm_dp_cec_unregister_delay + * seconds. This to debounce short HPD off-and-on cycles from + * displays. + */ + schedule_delayed_work(&aux->cec.unregister_work, + drm_dp_cec_unregister_delay * HZ); + } +unlock: + mutex_unlock(&aux->cec.lock); +} +EXPORT_SYMBOL(drm_dp_cec_unset_edid); + +/** + * drm_dp_cec_register_connector() - register a new connector + * @aux: DisplayPort AUX channel + * @connector: drm connector + * + * A new connector was registered with associated CEC adapter name and + * CEC adapter parent device. After registering the name and parent + * drm_dp_cec_set_edid() is called to check if the connector supports + * CEC and to register a CEC adapter if that is the case. + */ +void drm_dp_cec_register_connector(struct drm_dp_aux *aux, + struct drm_connector *connector) +{ + WARN_ON(aux->cec.adap); + if (WARN_ON(!aux->transfer)) + return; + aux->cec.connector = connector; + INIT_DELAYED_WORK(&aux->cec.unregister_work, + drm_dp_cec_unregister_work); +} +EXPORT_SYMBOL(drm_dp_cec_register_connector); + +/** + * drm_dp_cec_unregister_connector() - unregister the CEC adapter, if any + * @aux: DisplayPort AUX channel + */ +void drm_dp_cec_unregister_connector(struct drm_dp_aux *aux) +{ + if (!aux->cec.adap) + return; + cancel_delayed_work_sync(&aux->cec.unregister_work); + cec_unregister_adapter(aux->cec.adap); + aux->cec.adap = NULL; +} +EXPORT_SYMBOL(drm_dp_cec_unregister_connector); diff --git a/drivers/gpu/drm/display/drm_dp_dual_mode_helper.c b/drivers/gpu/drm/display/drm_dp_dual_mode_helper.c new file mode 100644 index 000000000000..3ea53bb67d3b --- /dev/null +++ b/drivers/gpu/drm/display/drm_dp_dual_mode_helper.c @@ -0,0 +1,530 @@ +/* + * Copyright © 2016 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/** + * DOC: dp dual mode helpers + * + * Helper functions to deal with DP dual mode (aka. DP++) adaptors. + * + * Type 1: + * Adaptor registers (if any) and the sink DDC bus may be accessed via I2C. + * + * Type 2: + * Adaptor registers and sink DDC bus can be accessed either via I2C or + * I2C-over-AUX. Source devices may choose to implement either of these + * access methods. + */ + +#define DP_DUAL_MODE_SLAVE_ADDRESS 0x40 + +/** + * drm_dp_dual_mode_read - Read from the DP dual mode adaptor register(s) + * @adapter: I2C adapter for the DDC bus + * @offset: register offset + * @buffer: buffer for return data + * @size: sizo of the buffer + * + * Reads @size bytes from the DP dual mode adaptor registers + * starting at @offset. + * + * Returns: + * 0 on success, negative error code on failure + */ +ssize_t drm_dp_dual_mode_read(struct i2c_adapter *adapter, + u8 offset, void *buffer, size_t size) +{ + struct i2c_msg msgs[] = { + { + .addr = DP_DUAL_MODE_SLAVE_ADDRESS, + .flags = 0, + .len = 1, + .buf = &offset, + }, + { + .addr = DP_DUAL_MODE_SLAVE_ADDRESS, + .flags = I2C_M_RD, + .len = size, + .buf = buffer, + }, + }; + int ret; + + ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + if (ret != ARRAY_SIZE(msgs)) + return -EPROTO; + + return 0; +} +EXPORT_SYMBOL(drm_dp_dual_mode_read); + +/** + * drm_dp_dual_mode_write - Write to the DP dual mode adaptor register(s) + * @adapter: I2C adapter for the DDC bus + * @offset: register offset + * @buffer: buffer for write data + * @size: sizo of the buffer + * + * Writes @size bytes to the DP dual mode adaptor registers + * starting at @offset. + * + * Returns: + * 0 on success, negative error code on failure + */ +ssize_t drm_dp_dual_mode_write(struct i2c_adapter *adapter, + u8 offset, const void *buffer, size_t size) +{ + struct i2c_msg msg = { + .addr = DP_DUAL_MODE_SLAVE_ADDRESS, + .flags = 0, + .len = 1 + size, + .buf = NULL, + }; + void *data; + int ret; + + data = kmalloc(msg.len, GFP_KERNEL); + if (!data) + return -ENOMEM; + + msg.buf = data; + + memcpy(data, &offset, 1); + memcpy(data + 1, buffer, size); + + ret = i2c_transfer(adapter, &msg, 1); + + kfree(data); + + if (ret < 0) + return ret; + if (ret != 1) + return -EPROTO; + + return 0; +} +EXPORT_SYMBOL(drm_dp_dual_mode_write); + +static bool is_hdmi_adaptor(const char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN]) +{ + static const char dp_dual_mode_hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN] = + "DP-HDMI ADAPTOR\x04"; + + return memcmp(hdmi_id, dp_dual_mode_hdmi_id, + sizeof(dp_dual_mode_hdmi_id)) == 0; +} + +static bool is_type1_adaptor(uint8_t adaptor_id) +{ + return adaptor_id == 0 || adaptor_id == 0xff; +} + +static bool is_type2_adaptor(uint8_t adaptor_id) +{ + return adaptor_id == (DP_DUAL_MODE_TYPE_TYPE2 | + DP_DUAL_MODE_REV_TYPE2); +} + +static bool is_lspcon_adaptor(const char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN], + const uint8_t adaptor_id) +{ + return is_hdmi_adaptor(hdmi_id) && + (adaptor_id == (DP_DUAL_MODE_TYPE_TYPE2 | + DP_DUAL_MODE_TYPE_HAS_DPCD)); +} + +/** + * drm_dp_dual_mode_detect - Identify the DP dual mode adaptor + * @dev: &drm_device to use + * @adapter: I2C adapter for the DDC bus + * + * Attempt to identify the type of the DP dual mode adaptor used. + * + * Note that when the answer is @DRM_DP_DUAL_MODE_UNKNOWN it's not + * certain whether we're dealing with a native HDMI port or + * a type 1 DVI dual mode adaptor. The driver will have to use + * some other hardware/driver specific mechanism to make that + * distinction. + * + * Returns: + * The type of the DP dual mode adaptor used + */ +enum drm_dp_dual_mode_type drm_dp_dual_mode_detect(const struct drm_device *dev, + struct i2c_adapter *adapter) +{ + char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN] = {}; + uint8_t adaptor_id = 0x00; + ssize_t ret; + + /* + * Let's see if the adaptor is there the by reading the + * HDMI ID registers. + * + * Note that type 1 DVI adaptors are not required to implemnt + * any registers, and that presents a problem for detection. + * If the i2c transfer is nacked, we may or may not be dealing + * with a type 1 DVI adaptor. Some other mechanism of detecting + * the presence of the adaptor is required. One way would be + * to check the state of the CONFIG1 pin, Another method would + * simply require the driver to know whether the port is a DP++ + * port or a native HDMI port. Both of these methods are entirely + * hardware/driver specific so we can't deal with them here. + */ + ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_HDMI_ID, + hdmi_id, sizeof(hdmi_id)); + drm_dbg_kms(dev, "DP dual mode HDMI ID: %*pE (err %zd)\n", + ret ? 0 : (int)sizeof(hdmi_id), hdmi_id, ret); + if (ret) + return DRM_DP_DUAL_MODE_UNKNOWN; + + /* + * Sigh. Some (maybe all?) type 1 adaptors are broken and ack + * the offset but ignore it, and instead they just always return + * data from the start of the HDMI ID buffer. So for a broken + * type 1 HDMI adaptor a single byte read will always give us + * 0x44, and for a type 1 DVI adaptor it should give 0x00 + * (assuming it implements any registers). Fortunately neither + * of those values will match the type 2 signature of the + * DP_DUAL_MODE_ADAPTOR_ID register so we can proceed with + * the type 2 adaptor detection safely even in the presence + * of broken type 1 adaptors. + */ + ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_ADAPTOR_ID, + &adaptor_id, sizeof(adaptor_id)); + drm_dbg_kms(dev, "DP dual mode adaptor ID: %02x (err %zd)\n", adaptor_id, ret); + if (ret == 0) { + if (is_lspcon_adaptor(hdmi_id, adaptor_id)) + return DRM_DP_DUAL_MODE_LSPCON; + if (is_type2_adaptor(adaptor_id)) { + if (is_hdmi_adaptor(hdmi_id)) + return DRM_DP_DUAL_MODE_TYPE2_HDMI; + else + return DRM_DP_DUAL_MODE_TYPE2_DVI; + } + /* + * If neither a proper type 1 ID nor a broken type 1 adaptor + * as described above, assume type 1, but let the user know + * that we may have misdetected the type. + */ + if (!is_type1_adaptor(adaptor_id) && adaptor_id != hdmi_id[0]) + drm_err(dev, "Unexpected DP dual mode adaptor ID %02x\n", adaptor_id); + + } + + if (is_hdmi_adaptor(hdmi_id)) + return DRM_DP_DUAL_MODE_TYPE1_HDMI; + else + return DRM_DP_DUAL_MODE_TYPE1_DVI; +} +EXPORT_SYMBOL(drm_dp_dual_mode_detect); + +/** + * drm_dp_dual_mode_max_tmds_clock - Max TMDS clock for DP dual mode adaptor + * @dev: &drm_device to use + * @type: DP dual mode adaptor type + * @adapter: I2C adapter for the DDC bus + * + * Determine the max TMDS clock the adaptor supports based on the + * type of the dual mode adaptor and the DP_DUAL_MODE_MAX_TMDS_CLOCK + * register (on type2 adaptors). As some type 1 adaptors have + * problems with registers (see comments in drm_dp_dual_mode_detect()) + * we don't read the register on those, instead we simply assume + * a 165 MHz limit based on the specification. + * + * Returns: + * Maximum supported TMDS clock rate for the DP dual mode adaptor in kHz. + */ +int drm_dp_dual_mode_max_tmds_clock(const struct drm_device *dev, enum drm_dp_dual_mode_type type, + struct i2c_adapter *adapter) +{ + uint8_t max_tmds_clock; + ssize_t ret; + + /* native HDMI so no limit */ + if (type == DRM_DP_DUAL_MODE_NONE) + return 0; + + /* + * Type 1 adaptors are limited to 165MHz + * Type 2 adaptors can tells us their limit + */ + if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) + return 165000; + + ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_MAX_TMDS_CLOCK, + &max_tmds_clock, sizeof(max_tmds_clock)); + if (ret || max_tmds_clock == 0x00 || max_tmds_clock == 0xff) { + drm_dbg_kms(dev, "Failed to query max TMDS clock\n"); + return 165000; + } + + return max_tmds_clock * 5000 / 2; +} +EXPORT_SYMBOL(drm_dp_dual_mode_max_tmds_clock); + +/** + * drm_dp_dual_mode_get_tmds_output - Get the state of the TMDS output buffers in the DP dual mode adaptor + * @dev: &drm_device to use + * @type: DP dual mode adaptor type + * @adapter: I2C adapter for the DDC bus + * @enabled: current state of the TMDS output buffers + * + * Get the state of the TMDS output buffers in the adaptor. For + * type2 adaptors this is queried from the DP_DUAL_MODE_TMDS_OEN + * register. As some type 1 adaptors have problems with registers + * (see comments in drm_dp_dual_mode_detect()) we don't read the + * register on those, instead we simply assume that the buffers + * are always enabled. + * + * Returns: + * 0 on success, negative error code on failure + */ +int drm_dp_dual_mode_get_tmds_output(const struct drm_device *dev, + enum drm_dp_dual_mode_type type, struct i2c_adapter *adapter, + bool *enabled) +{ + uint8_t tmds_oen; + ssize_t ret; + + if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) { + *enabled = true; + return 0; + } + + ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_TMDS_OEN, + &tmds_oen, sizeof(tmds_oen)); + if (ret) { + drm_dbg_kms(dev, "Failed to query state of TMDS output buffers\n"); + return ret; + } + + *enabled = !(tmds_oen & DP_DUAL_MODE_TMDS_DISABLE); + + return 0; +} +EXPORT_SYMBOL(drm_dp_dual_mode_get_tmds_output); + +/** + * drm_dp_dual_mode_set_tmds_output - Enable/disable TMDS output buffers in the DP dual mode adaptor + * @dev: &drm_device to use + * @type: DP dual mode adaptor type + * @adapter: I2C adapter for the DDC bus + * @enable: enable (as opposed to disable) the TMDS output buffers + * + * Set the state of the TMDS output buffers in the adaptor. For + * type2 this is set via the DP_DUAL_MODE_TMDS_OEN register. As + * some type 1 adaptors have problems with registers (see comments + * in drm_dp_dual_mode_detect()) we avoid touching the register, + * making this function a no-op on type 1 adaptors. + * + * Returns: + * 0 on success, negative error code on failure + */ +int drm_dp_dual_mode_set_tmds_output(const struct drm_device *dev, enum drm_dp_dual_mode_type type, + struct i2c_adapter *adapter, bool enable) +{ + uint8_t tmds_oen = enable ? 0 : DP_DUAL_MODE_TMDS_DISABLE; + ssize_t ret; + int retry; + + if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) + return 0; + + /* + * LSPCON adapters in low-power state may ignore the first write, so + * read back and verify the written value a few times. + */ + for (retry = 0; retry < 3; retry++) { + uint8_t tmp; + + ret = drm_dp_dual_mode_write(adapter, DP_DUAL_MODE_TMDS_OEN, + &tmds_oen, sizeof(tmds_oen)); + if (ret) { + drm_dbg_kms(dev, "Failed to %s TMDS output buffers (%d attempts)\n", + enable ? "enable" : "disable", retry + 1); + return ret; + } + + ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_TMDS_OEN, + &tmp, sizeof(tmp)); + if (ret) { + drm_dbg_kms(dev, + "I2C read failed during TMDS output buffer %s (%d attempts)\n", + enable ? "enabling" : "disabling", retry + 1); + return ret; + } + + if (tmp == tmds_oen) + return 0; + } + + drm_dbg_kms(dev, "I2C write value mismatch during TMDS output buffer %s\n", + enable ? "enabling" : "disabling"); + + return -EIO; +} +EXPORT_SYMBOL(drm_dp_dual_mode_set_tmds_output); + +/** + * drm_dp_get_dual_mode_type_name - Get the name of the DP dual mode adaptor type as a string + * @type: DP dual mode adaptor type + * + * Returns: + * String representation of the DP dual mode adaptor type + */ +const char *drm_dp_get_dual_mode_type_name(enum drm_dp_dual_mode_type type) +{ + switch (type) { + case DRM_DP_DUAL_MODE_NONE: + return "none"; + case DRM_DP_DUAL_MODE_TYPE1_DVI: + return "type 1 DVI"; + case DRM_DP_DUAL_MODE_TYPE1_HDMI: + return "type 1 HDMI"; + case DRM_DP_DUAL_MODE_TYPE2_DVI: + return "type 2 DVI"; + case DRM_DP_DUAL_MODE_TYPE2_HDMI: + return "type 2 HDMI"; + case DRM_DP_DUAL_MODE_LSPCON: + return "lspcon"; + default: + WARN_ON(type != DRM_DP_DUAL_MODE_UNKNOWN); + return "unknown"; + } +} +EXPORT_SYMBOL(drm_dp_get_dual_mode_type_name); + +/** + * drm_lspcon_get_mode: Get LSPCON's current mode of operation by + * reading offset (0x80, 0x41) + * @dev: &drm_device to use + * @adapter: I2C-over-aux adapter + * @mode: current lspcon mode of operation output variable + * + * Returns: + * 0 on success, sets the current_mode value to appropriate mode + * -error on failure + */ +int drm_lspcon_get_mode(const struct drm_device *dev, struct i2c_adapter *adapter, + enum drm_lspcon_mode *mode) +{ + u8 data; + int ret = 0; + int retry; + + if (!mode) { + drm_err(dev, "NULL input\n"); + return -EINVAL; + } + + /* Read Status: i2c over aux */ + for (retry = 0; retry < 6; retry++) { + if (retry) + usleep_range(500, 1000); + + ret = drm_dp_dual_mode_read(adapter, + DP_DUAL_MODE_LSPCON_CURRENT_MODE, + &data, sizeof(data)); + if (!ret) + break; + } + + if (ret < 0) { + drm_dbg_kms(dev, "LSPCON read(0x80, 0x41) failed\n"); + return -EFAULT; + } + + if (data & DP_DUAL_MODE_LSPCON_MODE_PCON) + *mode = DRM_LSPCON_MODE_PCON; + else + *mode = DRM_LSPCON_MODE_LS; + return 0; +} +EXPORT_SYMBOL(drm_lspcon_get_mode); + +/** + * drm_lspcon_set_mode: Change LSPCON's mode of operation by + * writing offset (0x80, 0x40) + * @dev: &drm_device to use + * @adapter: I2C-over-aux adapter + * @mode: required mode of operation + * + * Returns: + * 0 on success, -error on failure/timeout + */ +int drm_lspcon_set_mode(const struct drm_device *dev, struct i2c_adapter *adapter, + enum drm_lspcon_mode mode) +{ + u8 data = 0; + int ret; + int time_out = 200; + enum drm_lspcon_mode current_mode; + + if (mode == DRM_LSPCON_MODE_PCON) + data = DP_DUAL_MODE_LSPCON_MODE_PCON; + + /* Change mode */ + ret = drm_dp_dual_mode_write(adapter, DP_DUAL_MODE_LSPCON_MODE_CHANGE, + &data, sizeof(data)); + if (ret < 0) { + drm_err(dev, "LSPCON mode change failed\n"); + return ret; + } + + /* + * Confirm mode change by reading the status bit. + * Sometimes, it takes a while to change the mode, + * so wait and retry until time out or done. + */ + do { + ret = drm_lspcon_get_mode(dev, adapter, ¤t_mode); + if (ret) { + drm_err(dev, "can't confirm LSPCON mode change\n"); + return ret; + } else { + if (current_mode != mode) { + msleep(10); + time_out -= 10; + } else { + drm_dbg_kms(dev, "LSPCON mode changed to %s\n", + mode == DRM_LSPCON_MODE_LS ? "LS" : "PCON"); + return 0; + } + } + } while (time_out); + + drm_err(dev, "LSPCON mode change timed out\n"); + return -ETIMEDOUT; +} +EXPORT_SYMBOL(drm_lspcon_set_mode); diff --git a/drivers/gpu/drm/display/drm_dp_helper_internal.h b/drivers/gpu/drm/display/drm_dp_helper_internal.h new file mode 100644 index 000000000000..8917fc3af9ec --- /dev/null +++ b/drivers/gpu/drm/display/drm_dp_helper_internal.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: MIT */ + +#ifndef DRM_DP_HELPER_INTERNAL_H +#define DRM_DP_HELPER_INTERNAL_H + +struct drm_dp_aux; + +#ifdef CONFIG_DRM_DP_AUX_CHARDEV +int drm_dp_aux_dev_init(void); +void drm_dp_aux_dev_exit(void); +int drm_dp_aux_register_devnode(struct drm_dp_aux *aux); +void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux); +#else +static inline int drm_dp_aux_dev_init(void) +{ + return 0; +} + +static inline void drm_dp_aux_dev_exit(void) +{ +} + +static inline int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) +{ + return 0; +} + +static inline void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) +{ +} +#endif + +#endif diff --git a/drivers/gpu/drm/display/drm_dp_helper_mod.c b/drivers/gpu/drm/display/drm_dp_helper_mod.c new file mode 100644 index 000000000000..db753de24000 --- /dev/null +++ b/drivers/gpu/drm/display/drm_dp_helper_mod.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +#include + +#include "drm_dp_helper_internal.h" + +MODULE_DESCRIPTION("DRM DisplayPort helper"); +MODULE_LICENSE("GPL and additional rights"); + +static int __init drm_dp_helper_module_init(void) +{ + return drm_dp_aux_dev_init(); +} + +static void __exit drm_dp_helper_module_exit(void) +{ + /* Call exit functions from specific dp helpers here */ + drm_dp_aux_dev_exit(); +} + +module_init(drm_dp_helper_module_init); +module_exit(drm_dp_helper_module_exit); diff --git a/drivers/gpu/drm/display/drm_dp_mst_topology.c b/drivers/gpu/drm/display/drm_dp_mst_topology.c new file mode 100644 index 000000000000..8526aae75c6d --- /dev/null +++ b/drivers/gpu/drm/display/drm_dp_mst_topology.c @@ -0,0 +1,5978 @@ +/* + * Copyright © 2014 Red Hat + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that copyright + * notice and this permission notice appear in supporting documentation, and + * that the name of the copyright holders not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no representations + * about the suitability of this software for any purpose. It is provided "as + * is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + * OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "drm_dp_helper_internal.h" +#include "drm_dp_mst_topology_internal.h" + +/** + * DOC: dp mst helper + * + * These functions contain parts of the DisplayPort 1.2a MultiStream Transport + * protocol. The helpers contain a topology manager and bandwidth manager. + * The helpers encapsulate the sending and received of sideband msgs. + */ +struct drm_dp_pending_up_req { + struct drm_dp_sideband_msg_hdr hdr; + struct drm_dp_sideband_msg_req_body msg; + struct list_head next; +}; + +static bool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr, + char *buf); + +static void drm_dp_mst_topology_put_port(struct drm_dp_mst_port *port); + +static int drm_dp_dpcd_write_payload(struct drm_dp_mst_topology_mgr *mgr, + int id, + struct drm_dp_payload *payload); + +static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int offset, int size, u8 *bytes); +static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int offset, int size, u8 *bytes); + +static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb); + +static void +drm_dp_send_clear_payload_id_table(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb); + +static int drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port); +static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr, + u8 *guid); + +static int drm_dp_mst_register_i2c_bus(struct drm_dp_mst_port *port); +static void drm_dp_mst_unregister_i2c_bus(struct drm_dp_mst_port *port); +static void drm_dp_mst_kick_tx(struct drm_dp_mst_topology_mgr *mgr); + +static bool drm_dp_mst_port_downstream_of_branch(struct drm_dp_mst_port *port, + struct drm_dp_mst_branch *branch); + +#define DBG_PREFIX "[dp_mst]" + +#define DP_STR(x) [DP_ ## x] = #x + +static const char *drm_dp_mst_req_type_str(u8 req_type) +{ + static const char * const req_type_str[] = { + DP_STR(GET_MSG_TRANSACTION_VERSION), + DP_STR(LINK_ADDRESS), + DP_STR(CONNECTION_STATUS_NOTIFY), + DP_STR(ENUM_PATH_RESOURCES), + DP_STR(ALLOCATE_PAYLOAD), + DP_STR(QUERY_PAYLOAD), + DP_STR(RESOURCE_STATUS_NOTIFY), + DP_STR(CLEAR_PAYLOAD_ID_TABLE), + DP_STR(REMOTE_DPCD_READ), + DP_STR(REMOTE_DPCD_WRITE), + DP_STR(REMOTE_I2C_READ), + DP_STR(REMOTE_I2C_WRITE), + DP_STR(POWER_UP_PHY), + DP_STR(POWER_DOWN_PHY), + DP_STR(SINK_EVENT_NOTIFY), + DP_STR(QUERY_STREAM_ENC_STATUS), + }; + + if (req_type >= ARRAY_SIZE(req_type_str) || + !req_type_str[req_type]) + return "unknown"; + + return req_type_str[req_type]; +} + +#undef DP_STR +#define DP_STR(x) [DP_NAK_ ## x] = #x + +static const char *drm_dp_mst_nak_reason_str(u8 nak_reason) +{ + static const char * const nak_reason_str[] = { + DP_STR(WRITE_FAILURE), + DP_STR(INVALID_READ), + DP_STR(CRC_FAILURE), + DP_STR(BAD_PARAM), + DP_STR(DEFER), + DP_STR(LINK_FAILURE), + DP_STR(NO_RESOURCES), + DP_STR(DPCD_FAIL), + DP_STR(I2C_NAK), + DP_STR(ALLOCATE_FAIL), + }; + + if (nak_reason >= ARRAY_SIZE(nak_reason_str) || + !nak_reason_str[nak_reason]) + return "unknown"; + + return nak_reason_str[nak_reason]; +} + +#undef DP_STR +#define DP_STR(x) [DRM_DP_SIDEBAND_TX_ ## x] = #x + +static const char *drm_dp_mst_sideband_tx_state_str(int state) +{ + static const char * const sideband_reason_str[] = { + DP_STR(QUEUED), + DP_STR(START_SEND), + DP_STR(SENT), + DP_STR(RX), + DP_STR(TIMEOUT), + }; + + if (state >= ARRAY_SIZE(sideband_reason_str) || + !sideband_reason_str[state]) + return "unknown"; + + return sideband_reason_str[state]; +} + +static int +drm_dp_mst_rad_to_str(const u8 rad[8], u8 lct, char *out, size_t len) +{ + int i; + u8 unpacked_rad[16]; + + for (i = 0; i < lct; i++) { + if (i % 2) + unpacked_rad[i] = rad[i / 2] >> 4; + else + unpacked_rad[i] = rad[i / 2] & BIT_MASK(4); + } + + /* TODO: Eventually add something to printk so we can format the rad + * like this: 1.2.3 + */ + return snprintf(out, len, "%*phC", lct, unpacked_rad); +} + +/* sideband msg handling */ +static u8 drm_dp_msg_header_crc4(const uint8_t *data, size_t num_nibbles) +{ + u8 bitmask = 0x80; + u8 bitshift = 7; + u8 array_index = 0; + int number_of_bits = num_nibbles * 4; + u8 remainder = 0; + + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + remainder |= (data[array_index] & bitmask) >> bitshift; + bitmask >>= 1; + bitshift--; + if (bitmask == 0) { + bitmask = 0x80; + bitshift = 7; + array_index++; + } + if ((remainder & 0x10) == 0x10) + remainder ^= 0x13; + } + + number_of_bits = 4; + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + if ((remainder & 0x10) != 0) + remainder ^= 0x13; + } + + return remainder; +} + +static u8 drm_dp_msg_data_crc4(const uint8_t *data, u8 number_of_bytes) +{ + u8 bitmask = 0x80; + u8 bitshift = 7; + u8 array_index = 0; + int number_of_bits = number_of_bytes * 8; + u16 remainder = 0; + + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + remainder |= (data[array_index] & bitmask) >> bitshift; + bitmask >>= 1; + bitshift--; + if (bitmask == 0) { + bitmask = 0x80; + bitshift = 7; + array_index++; + } + if ((remainder & 0x100) == 0x100) + remainder ^= 0xd5; + } + + number_of_bits = 8; + while (number_of_bits != 0) { + number_of_bits--; + remainder <<= 1; + if ((remainder & 0x100) != 0) + remainder ^= 0xd5; + } + + return remainder & 0xff; +} +static inline u8 drm_dp_calc_sb_hdr_size(struct drm_dp_sideband_msg_hdr *hdr) +{ + u8 size = 3; + + size += (hdr->lct / 2); + return size; +} + +static void drm_dp_encode_sideband_msg_hdr(struct drm_dp_sideband_msg_hdr *hdr, + u8 *buf, int *len) +{ + int idx = 0; + int i; + u8 crc4; + + buf[idx++] = ((hdr->lct & 0xf) << 4) | (hdr->lcr & 0xf); + for (i = 0; i < (hdr->lct / 2); i++) + buf[idx++] = hdr->rad[i]; + buf[idx++] = (hdr->broadcast << 7) | (hdr->path_msg << 6) | + (hdr->msg_len & 0x3f); + buf[idx++] = (hdr->somt << 7) | (hdr->eomt << 6) | (hdr->seqno << 4); + + crc4 = drm_dp_msg_header_crc4(buf, (idx * 2) - 1); + buf[idx - 1] |= (crc4 & 0xf); + + *len = idx; +} + +static bool drm_dp_decode_sideband_msg_hdr(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_hdr *hdr, + u8 *buf, int buflen, u8 *hdrlen) +{ + u8 crc4; + u8 len; + int i; + u8 idx; + + if (buf[0] == 0) + return false; + len = 3; + len += ((buf[0] & 0xf0) >> 4) / 2; + if (len > buflen) + return false; + crc4 = drm_dp_msg_header_crc4(buf, (len * 2) - 1); + + if ((crc4 & 0xf) != (buf[len - 1] & 0xf)) { + drm_dbg_kms(mgr->dev, "crc4 mismatch 0x%x 0x%x\n", crc4, buf[len - 1]); + return false; + } + + hdr->lct = (buf[0] & 0xf0) >> 4; + hdr->lcr = (buf[0] & 0xf); + idx = 1; + for (i = 0; i < (hdr->lct / 2); i++) + hdr->rad[i] = buf[idx++]; + hdr->broadcast = (buf[idx] >> 7) & 0x1; + hdr->path_msg = (buf[idx] >> 6) & 0x1; + hdr->msg_len = buf[idx] & 0x3f; + idx++; + hdr->somt = (buf[idx] >> 7) & 0x1; + hdr->eomt = (buf[idx] >> 6) & 0x1; + hdr->seqno = (buf[idx] >> 4) & 0x1; + idx++; + *hdrlen = idx; + return true; +} + +void +drm_dp_encode_sideband_req(const struct drm_dp_sideband_msg_req_body *req, + struct drm_dp_sideband_msg_tx *raw) +{ + int idx = 0; + int i; + u8 *buf = raw->msg; + + buf[idx++] = req->req_type & 0x7f; + + switch (req->req_type) { + case DP_ENUM_PATH_RESOURCES: + case DP_POWER_DOWN_PHY: + case DP_POWER_UP_PHY: + buf[idx] = (req->u.port_num.port_number & 0xf) << 4; + idx++; + break; + case DP_ALLOCATE_PAYLOAD: + buf[idx] = (req->u.allocate_payload.port_number & 0xf) << 4 | + (req->u.allocate_payload.number_sdp_streams & 0xf); + idx++; + buf[idx] = (req->u.allocate_payload.vcpi & 0x7f); + idx++; + buf[idx] = (req->u.allocate_payload.pbn >> 8); + idx++; + buf[idx] = (req->u.allocate_payload.pbn & 0xff); + idx++; + for (i = 0; i < req->u.allocate_payload.number_sdp_streams / 2; i++) { + buf[idx] = ((req->u.allocate_payload.sdp_stream_sink[i * 2] & 0xf) << 4) | + (req->u.allocate_payload.sdp_stream_sink[i * 2 + 1] & 0xf); + idx++; + } + if (req->u.allocate_payload.number_sdp_streams & 1) { + i = req->u.allocate_payload.number_sdp_streams - 1; + buf[idx] = (req->u.allocate_payload.sdp_stream_sink[i] & 0xf) << 4; + idx++; + } + break; + case DP_QUERY_PAYLOAD: + buf[idx] = (req->u.query_payload.port_number & 0xf) << 4; + idx++; + buf[idx] = (req->u.query_payload.vcpi & 0x7f); + idx++; + break; + case DP_REMOTE_DPCD_READ: + buf[idx] = (req->u.dpcd_read.port_number & 0xf) << 4; + buf[idx] |= ((req->u.dpcd_read.dpcd_address & 0xf0000) >> 16) & 0xf; + idx++; + buf[idx] = (req->u.dpcd_read.dpcd_address & 0xff00) >> 8; + idx++; + buf[idx] = (req->u.dpcd_read.dpcd_address & 0xff); + idx++; + buf[idx] = (req->u.dpcd_read.num_bytes); + idx++; + break; + + case DP_REMOTE_DPCD_WRITE: + buf[idx] = (req->u.dpcd_write.port_number & 0xf) << 4; + buf[idx] |= ((req->u.dpcd_write.dpcd_address & 0xf0000) >> 16) & 0xf; + idx++; + buf[idx] = (req->u.dpcd_write.dpcd_address & 0xff00) >> 8; + idx++; + buf[idx] = (req->u.dpcd_write.dpcd_address & 0xff); + idx++; + buf[idx] = (req->u.dpcd_write.num_bytes); + idx++; + memcpy(&buf[idx], req->u.dpcd_write.bytes, req->u.dpcd_write.num_bytes); + idx += req->u.dpcd_write.num_bytes; + break; + case DP_REMOTE_I2C_READ: + buf[idx] = (req->u.i2c_read.port_number & 0xf) << 4; + buf[idx] |= (req->u.i2c_read.num_transactions & 0x3); + idx++; + for (i = 0; i < (req->u.i2c_read.num_transactions & 0x3); i++) { + buf[idx] = req->u.i2c_read.transactions[i].i2c_dev_id & 0x7f; + idx++; + buf[idx] = req->u.i2c_read.transactions[i].num_bytes; + idx++; + memcpy(&buf[idx], req->u.i2c_read.transactions[i].bytes, req->u.i2c_read.transactions[i].num_bytes); + idx += req->u.i2c_read.transactions[i].num_bytes; + + buf[idx] = (req->u.i2c_read.transactions[i].no_stop_bit & 0x1) << 4; + buf[idx] |= (req->u.i2c_read.transactions[i].i2c_transaction_delay & 0xf); + idx++; + } + buf[idx] = (req->u.i2c_read.read_i2c_device_id) & 0x7f; + idx++; + buf[idx] = (req->u.i2c_read.num_bytes_read); + idx++; + break; + + case DP_REMOTE_I2C_WRITE: + buf[idx] = (req->u.i2c_write.port_number & 0xf) << 4; + idx++; + buf[idx] = (req->u.i2c_write.write_i2c_device_id) & 0x7f; + idx++; + buf[idx] = (req->u.i2c_write.num_bytes); + idx++; + memcpy(&buf[idx], req->u.i2c_write.bytes, req->u.i2c_write.num_bytes); + idx += req->u.i2c_write.num_bytes; + break; + case DP_QUERY_STREAM_ENC_STATUS: { + const struct drm_dp_query_stream_enc_status *msg; + + msg = &req->u.enc_status; + buf[idx] = msg->stream_id; + idx++; + memcpy(&buf[idx], msg->client_id, sizeof(msg->client_id)); + idx += sizeof(msg->client_id); + buf[idx] = 0; + buf[idx] |= FIELD_PREP(GENMASK(1, 0), msg->stream_event); + buf[idx] |= msg->valid_stream_event ? BIT(2) : 0; + buf[idx] |= FIELD_PREP(GENMASK(4, 3), msg->stream_behavior); + buf[idx] |= msg->valid_stream_behavior ? BIT(5) : 0; + idx++; + } + break; + } + raw->cur_len = idx; +} +EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_encode_sideband_req); + +/* Decode a sideband request we've encoded, mainly used for debugging */ +int +drm_dp_decode_sideband_req(const struct drm_dp_sideband_msg_tx *raw, + struct drm_dp_sideband_msg_req_body *req) +{ + const u8 *buf = raw->msg; + int i, idx = 0; + + req->req_type = buf[idx++] & 0x7f; + switch (req->req_type) { + case DP_ENUM_PATH_RESOURCES: + case DP_POWER_DOWN_PHY: + case DP_POWER_UP_PHY: + req->u.port_num.port_number = (buf[idx] >> 4) & 0xf; + break; + case DP_ALLOCATE_PAYLOAD: + { + struct drm_dp_allocate_payload *a = + &req->u.allocate_payload; + + a->number_sdp_streams = buf[idx] & 0xf; + a->port_number = (buf[idx] >> 4) & 0xf; + + WARN_ON(buf[++idx] & 0x80); + a->vcpi = buf[idx] & 0x7f; + + a->pbn = buf[++idx] << 8; + a->pbn |= buf[++idx]; + + idx++; + for (i = 0; i < a->number_sdp_streams; i++) { + a->sdp_stream_sink[i] = + (buf[idx + (i / 2)] >> ((i % 2) ? 0 : 4)) & 0xf; + } + } + break; + case DP_QUERY_PAYLOAD: + req->u.query_payload.port_number = (buf[idx] >> 4) & 0xf; + WARN_ON(buf[++idx] & 0x80); + req->u.query_payload.vcpi = buf[idx] & 0x7f; + break; + case DP_REMOTE_DPCD_READ: + { + struct drm_dp_remote_dpcd_read *r = &req->u.dpcd_read; + + r->port_number = (buf[idx] >> 4) & 0xf; + + r->dpcd_address = (buf[idx] << 16) & 0xf0000; + r->dpcd_address |= (buf[++idx] << 8) & 0xff00; + r->dpcd_address |= buf[++idx] & 0xff; + + r->num_bytes = buf[++idx]; + } + break; + case DP_REMOTE_DPCD_WRITE: + { + struct drm_dp_remote_dpcd_write *w = + &req->u.dpcd_write; + + w->port_number = (buf[idx] >> 4) & 0xf; + + w->dpcd_address = (buf[idx] << 16) & 0xf0000; + w->dpcd_address |= (buf[++idx] << 8) & 0xff00; + w->dpcd_address |= buf[++idx] & 0xff; + + w->num_bytes = buf[++idx]; + + w->bytes = kmemdup(&buf[++idx], w->num_bytes, + GFP_KERNEL); + if (!w->bytes) + return -ENOMEM; + } + break; + case DP_REMOTE_I2C_READ: + { + struct drm_dp_remote_i2c_read *r = &req->u.i2c_read; + struct drm_dp_remote_i2c_read_tx *tx; + bool failed = false; + + r->num_transactions = buf[idx] & 0x3; + r->port_number = (buf[idx] >> 4) & 0xf; + for (i = 0; i < r->num_transactions; i++) { + tx = &r->transactions[i]; + + tx->i2c_dev_id = buf[++idx] & 0x7f; + tx->num_bytes = buf[++idx]; + tx->bytes = kmemdup(&buf[++idx], + tx->num_bytes, + GFP_KERNEL); + if (!tx->bytes) { + failed = true; + break; + } + idx += tx->num_bytes; + tx->no_stop_bit = (buf[idx] >> 5) & 0x1; + tx->i2c_transaction_delay = buf[idx] & 0xf; + } + + if (failed) { + for (i = 0; i < r->num_transactions; i++) { + tx = &r->transactions[i]; + kfree(tx->bytes); + } + return -ENOMEM; + } + + r->read_i2c_device_id = buf[++idx] & 0x7f; + r->num_bytes_read = buf[++idx]; + } + break; + case DP_REMOTE_I2C_WRITE: + { + struct drm_dp_remote_i2c_write *w = &req->u.i2c_write; + + w->port_number = (buf[idx] >> 4) & 0xf; + w->write_i2c_device_id = buf[++idx] & 0x7f; + w->num_bytes = buf[++idx]; + w->bytes = kmemdup(&buf[++idx], w->num_bytes, + GFP_KERNEL); + if (!w->bytes) + return -ENOMEM; + } + break; + case DP_QUERY_STREAM_ENC_STATUS: + req->u.enc_status.stream_id = buf[idx++]; + for (i = 0; i < sizeof(req->u.enc_status.client_id); i++) + req->u.enc_status.client_id[i] = buf[idx++]; + + req->u.enc_status.stream_event = FIELD_GET(GENMASK(1, 0), + buf[idx]); + req->u.enc_status.valid_stream_event = FIELD_GET(BIT(2), + buf[idx]); + req->u.enc_status.stream_behavior = FIELD_GET(GENMASK(4, 3), + buf[idx]); + req->u.enc_status.valid_stream_behavior = FIELD_GET(BIT(5), + buf[idx]); + break; + } + + return 0; +} +EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_decode_sideband_req); + +void +drm_dp_dump_sideband_msg_req_body(const struct drm_dp_sideband_msg_req_body *req, + int indent, struct drm_printer *printer) +{ + int i; + +#define P(f, ...) drm_printf_indent(printer, indent, f, ##__VA_ARGS__) + if (req->req_type == DP_LINK_ADDRESS) { + /* No contents to print */ + P("type=%s\n", drm_dp_mst_req_type_str(req->req_type)); + return; + } + + P("type=%s contents:\n", drm_dp_mst_req_type_str(req->req_type)); + indent++; + + switch (req->req_type) { + case DP_ENUM_PATH_RESOURCES: + case DP_POWER_DOWN_PHY: + case DP_POWER_UP_PHY: + P("port=%d\n", req->u.port_num.port_number); + break; + case DP_ALLOCATE_PAYLOAD: + P("port=%d vcpi=%d pbn=%d sdp_streams=%d %*ph\n", + req->u.allocate_payload.port_number, + req->u.allocate_payload.vcpi, req->u.allocate_payload.pbn, + req->u.allocate_payload.number_sdp_streams, + req->u.allocate_payload.number_sdp_streams, + req->u.allocate_payload.sdp_stream_sink); + break; + case DP_QUERY_PAYLOAD: + P("port=%d vcpi=%d\n", + req->u.query_payload.port_number, + req->u.query_payload.vcpi); + break; + case DP_REMOTE_DPCD_READ: + P("port=%d dpcd_addr=%05x len=%d\n", + req->u.dpcd_read.port_number, req->u.dpcd_read.dpcd_address, + req->u.dpcd_read.num_bytes); + break; + case DP_REMOTE_DPCD_WRITE: + P("port=%d addr=%05x len=%d: %*ph\n", + req->u.dpcd_write.port_number, + req->u.dpcd_write.dpcd_address, + req->u.dpcd_write.num_bytes, req->u.dpcd_write.num_bytes, + req->u.dpcd_write.bytes); + break; + case DP_REMOTE_I2C_READ: + P("port=%d num_tx=%d id=%d size=%d:\n", + req->u.i2c_read.port_number, + req->u.i2c_read.num_transactions, + req->u.i2c_read.read_i2c_device_id, + req->u.i2c_read.num_bytes_read); + + indent++; + for (i = 0; i < req->u.i2c_read.num_transactions; i++) { + const struct drm_dp_remote_i2c_read_tx *rtx = + &req->u.i2c_read.transactions[i]; + + P("%d: id=%03d size=%03d no_stop_bit=%d tx_delay=%03d: %*ph\n", + i, rtx->i2c_dev_id, rtx->num_bytes, + rtx->no_stop_bit, rtx->i2c_transaction_delay, + rtx->num_bytes, rtx->bytes); + } + break; + case DP_REMOTE_I2C_WRITE: + P("port=%d id=%d size=%d: %*ph\n", + req->u.i2c_write.port_number, + req->u.i2c_write.write_i2c_device_id, + req->u.i2c_write.num_bytes, req->u.i2c_write.num_bytes, + req->u.i2c_write.bytes); + break; + case DP_QUERY_STREAM_ENC_STATUS: + P("stream_id=%u client_id=%*ph stream_event=%x " + "valid_event=%d stream_behavior=%x valid_behavior=%d", + req->u.enc_status.stream_id, + (int)ARRAY_SIZE(req->u.enc_status.client_id), + req->u.enc_status.client_id, req->u.enc_status.stream_event, + req->u.enc_status.valid_stream_event, + req->u.enc_status.stream_behavior, + req->u.enc_status.valid_stream_behavior); + break; + default: + P("???\n"); + break; + } +#undef P +} +EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_dump_sideband_msg_req_body); + +static inline void +drm_dp_mst_dump_sideband_msg_tx(struct drm_printer *p, + const struct drm_dp_sideband_msg_tx *txmsg) +{ + struct drm_dp_sideband_msg_req_body req; + char buf[64]; + int ret; + int i; + + drm_dp_mst_rad_to_str(txmsg->dst->rad, txmsg->dst->lct, buf, + sizeof(buf)); + drm_printf(p, "txmsg cur_offset=%x cur_len=%x seqno=%x state=%s path_msg=%d dst=%s\n", + txmsg->cur_offset, txmsg->cur_len, txmsg->seqno, + drm_dp_mst_sideband_tx_state_str(txmsg->state), + txmsg->path_msg, buf); + + ret = drm_dp_decode_sideband_req(txmsg, &req); + if (ret) { + drm_printf(p, "\n", ret); + return; + } + drm_dp_dump_sideband_msg_req_body(&req, 1, p); + + switch (req.req_type) { + case DP_REMOTE_DPCD_WRITE: + kfree(req.u.dpcd_write.bytes); + break; + case DP_REMOTE_I2C_READ: + for (i = 0; i < req.u.i2c_read.num_transactions; i++) + kfree(req.u.i2c_read.transactions[i].bytes); + break; + case DP_REMOTE_I2C_WRITE: + kfree(req.u.i2c_write.bytes); + break; + } +} + +static void drm_dp_crc_sideband_chunk_req(u8 *msg, u8 len) +{ + u8 crc4; + + crc4 = drm_dp_msg_data_crc4(msg, len); + msg[len] = crc4; +} + +static void drm_dp_encode_sideband_reply(struct drm_dp_sideband_msg_reply_body *rep, + struct drm_dp_sideband_msg_tx *raw) +{ + int idx = 0; + u8 *buf = raw->msg; + + buf[idx++] = (rep->reply_type & 0x1) << 7 | (rep->req_type & 0x7f); + + raw->cur_len = idx; +} + +static int drm_dp_sideband_msg_set_header(struct drm_dp_sideband_msg_rx *msg, + struct drm_dp_sideband_msg_hdr *hdr, + u8 hdrlen) +{ + /* + * ignore out-of-order messages or messages that are part of a + * failed transaction + */ + if (!hdr->somt && !msg->have_somt) + return false; + + /* get length contained in this portion */ + msg->curchunk_idx = 0; + msg->curchunk_len = hdr->msg_len; + msg->curchunk_hdrlen = hdrlen; + + /* we have already gotten an somt - don't bother parsing */ + if (hdr->somt && msg->have_somt) + return false; + + if (hdr->somt) { + memcpy(&msg->initial_hdr, hdr, + sizeof(struct drm_dp_sideband_msg_hdr)); + msg->have_somt = true; + } + if (hdr->eomt) + msg->have_eomt = true; + + return true; +} + +/* this adds a chunk of msg to the builder to get the final msg */ +static bool drm_dp_sideband_append_payload(struct drm_dp_sideband_msg_rx *msg, + u8 *replybuf, u8 replybuflen) +{ + u8 crc4; + + memcpy(&msg->chunk[msg->curchunk_idx], replybuf, replybuflen); + msg->curchunk_idx += replybuflen; + + if (msg->curchunk_idx >= msg->curchunk_len) { + /* do CRC */ + crc4 = drm_dp_msg_data_crc4(msg->chunk, msg->curchunk_len - 1); + if (crc4 != msg->chunk[msg->curchunk_len - 1]) + print_hex_dump(KERN_DEBUG, "wrong crc", + DUMP_PREFIX_NONE, 16, 1, + msg->chunk, msg->curchunk_len, false); + /* copy chunk into bigger msg */ + memcpy(&msg->msg[msg->curlen], msg->chunk, msg->curchunk_len - 1); + msg->curlen += msg->curchunk_len - 1; + } + return true; +} + +static bool drm_dp_sideband_parse_link_address(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + int i; + + memcpy(repmsg->u.link_addr.guid, &raw->msg[idx], 16); + idx += 16; + repmsg->u.link_addr.nports = raw->msg[idx] & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + for (i = 0; i < repmsg->u.link_addr.nports; i++) { + if (raw->msg[idx] & 0x80) + repmsg->u.link_addr.ports[i].input_port = 1; + + repmsg->u.link_addr.ports[i].peer_device_type = (raw->msg[idx] >> 4) & 0x7; + repmsg->u.link_addr.ports[i].port_number = (raw->msg[idx] & 0xf); + + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.link_addr.ports[i].mcs = (raw->msg[idx] >> 7) & 0x1; + repmsg->u.link_addr.ports[i].ddps = (raw->msg[idx] >> 6) & 0x1; + if (repmsg->u.link_addr.ports[i].input_port == 0) + repmsg->u.link_addr.ports[i].legacy_device_plug_status = (raw->msg[idx] >> 5) & 0x1; + idx++; + if (idx > raw->curlen) + goto fail_len; + if (repmsg->u.link_addr.ports[i].input_port == 0) { + repmsg->u.link_addr.ports[i].dpcd_revision = (raw->msg[idx]); + idx++; + if (idx > raw->curlen) + goto fail_len; + memcpy(repmsg->u.link_addr.ports[i].peer_guid, &raw->msg[idx], 16); + idx += 16; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.link_addr.ports[i].num_sdp_streams = (raw->msg[idx] >> 4) & 0xf; + repmsg->u.link_addr.ports[i].num_sdp_stream_sinks = (raw->msg[idx] & 0xf); + idx++; + + } + if (idx > raw->curlen) + goto fail_len; + } + + return true; +fail_len: + DRM_DEBUG_KMS("link address reply parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_remote_dpcd_read(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + + repmsg->u.remote_dpcd_read_ack.port_number = raw->msg[idx] & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.remote_dpcd_read_ack.num_bytes = raw->msg[idx]; + idx++; + if (idx > raw->curlen) + goto fail_len; + + memcpy(repmsg->u.remote_dpcd_read_ack.bytes, &raw->msg[idx], repmsg->u.remote_dpcd_read_ack.num_bytes); + return true; +fail_len: + DRM_DEBUG_KMS("link address reply parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_remote_dpcd_write(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + + repmsg->u.remote_dpcd_write_ack.port_number = raw->msg[idx] & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + return true; +fail_len: + DRM_DEBUG_KMS("parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_remote_i2c_read_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + + repmsg->u.remote_i2c_read_ack.port_number = (raw->msg[idx] & 0xf); + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.remote_i2c_read_ack.num_bytes = raw->msg[idx]; + idx++; + /* TODO check */ + memcpy(repmsg->u.remote_i2c_read_ack.bytes, &raw->msg[idx], repmsg->u.remote_i2c_read_ack.num_bytes); + return true; +fail_len: + DRM_DEBUG_KMS("remote i2c reply parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_enum_path_resources_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + + repmsg->u.path_resources.port_number = (raw->msg[idx] >> 4) & 0xf; + repmsg->u.path_resources.fec_capable = raw->msg[idx] & 0x1; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.path_resources.full_payload_bw_number = (raw->msg[idx] << 8) | (raw->msg[idx+1]); + idx += 2; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.path_resources.avail_payload_bw_number = (raw->msg[idx] << 8) | (raw->msg[idx+1]); + idx += 2; + if (idx > raw->curlen) + goto fail_len; + return true; +fail_len: + DRM_DEBUG_KMS("enum resource parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_allocate_payload_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + + repmsg->u.allocate_payload.port_number = (raw->msg[idx] >> 4) & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.allocate_payload.vcpi = raw->msg[idx]; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.allocate_payload.allocated_pbn = (raw->msg[idx] << 8) | (raw->msg[idx+1]); + idx += 2; + if (idx > raw->curlen) + goto fail_len; + return true; +fail_len: + DRM_DEBUG_KMS("allocate payload parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_query_payload_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + + repmsg->u.query_payload.port_number = (raw->msg[idx] >> 4) & 0xf; + idx++; + if (idx > raw->curlen) + goto fail_len; + repmsg->u.query_payload.allocated_pbn = (raw->msg[idx] << 8) | (raw->msg[idx + 1]); + idx += 2; + if (idx > raw->curlen) + goto fail_len; + return true; +fail_len: + DRM_DEBUG_KMS("query payload parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_power_updown_phy_ack(struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + int idx = 1; + + repmsg->u.port_number.port_number = (raw->msg[idx] >> 4) & 0xf; + idx++; + if (idx > raw->curlen) { + DRM_DEBUG_KMS("power up/down phy parse length fail %d %d\n", + idx, raw->curlen); + return false; + } + return true; +} + +static bool +drm_dp_sideband_parse_query_stream_enc_status( + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *repmsg) +{ + struct drm_dp_query_stream_enc_status_ack_reply *reply; + + reply = &repmsg->u.enc_status; + + reply->stream_id = raw->msg[3]; + + reply->reply_signed = raw->msg[2] & BIT(0); + + /* + * NOTE: It's my impression from reading the spec that the below parsing + * is correct. However I noticed while testing with an HDCP 1.4 display + * through an HDCP 2.2 hub that only bit 3 was set. In that case, I + * would expect both bits to be set. So keep the parsing following the + * spec, but beware reality might not match the spec (at least for some + * configurations). + */ + reply->hdcp_1x_device_present = raw->msg[2] & BIT(4); + reply->hdcp_2x_device_present = raw->msg[2] & BIT(3); + + reply->query_capable_device_present = raw->msg[2] & BIT(5); + reply->legacy_device_present = raw->msg[2] & BIT(6); + reply->unauthorizable_device_present = raw->msg[2] & BIT(7); + + reply->auth_completed = !!(raw->msg[1] & BIT(3)); + reply->encryption_enabled = !!(raw->msg[1] & BIT(4)); + reply->repeater_present = !!(raw->msg[1] & BIT(5)); + reply->state = (raw->msg[1] & GENMASK(7, 6)) >> 6; + + return true; +} + +static bool drm_dp_sideband_parse_reply(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_reply_body *msg) +{ + memset(msg, 0, sizeof(*msg)); + msg->reply_type = (raw->msg[0] & 0x80) >> 7; + msg->req_type = (raw->msg[0] & 0x7f); + + if (msg->reply_type == DP_SIDEBAND_REPLY_NAK) { + memcpy(msg->u.nak.guid, &raw->msg[1], 16); + msg->u.nak.reason = raw->msg[17]; + msg->u.nak.nak_data = raw->msg[18]; + return false; + } + + switch (msg->req_type) { + case DP_LINK_ADDRESS: + return drm_dp_sideband_parse_link_address(mgr, raw, msg); + case DP_QUERY_PAYLOAD: + return drm_dp_sideband_parse_query_payload_ack(raw, msg); + case DP_REMOTE_DPCD_READ: + return drm_dp_sideband_parse_remote_dpcd_read(raw, msg); + case DP_REMOTE_DPCD_WRITE: + return drm_dp_sideband_parse_remote_dpcd_write(raw, msg); + case DP_REMOTE_I2C_READ: + return drm_dp_sideband_parse_remote_i2c_read_ack(raw, msg); + case DP_REMOTE_I2C_WRITE: + return true; /* since there's nothing to parse */ + case DP_ENUM_PATH_RESOURCES: + return drm_dp_sideband_parse_enum_path_resources_ack(raw, msg); + case DP_ALLOCATE_PAYLOAD: + return drm_dp_sideband_parse_allocate_payload_ack(raw, msg); + case DP_POWER_DOWN_PHY: + case DP_POWER_UP_PHY: + return drm_dp_sideband_parse_power_updown_phy_ack(raw, msg); + case DP_CLEAR_PAYLOAD_ID_TABLE: + return true; /* since there's nothing to parse */ + case DP_QUERY_STREAM_ENC_STATUS: + return drm_dp_sideband_parse_query_stream_enc_status(raw, msg); + default: + drm_err(mgr->dev, "Got unknown reply 0x%02x (%s)\n", + msg->req_type, drm_dp_mst_req_type_str(msg->req_type)); + return false; + } +} + +static bool +drm_dp_sideband_parse_connection_status_notify(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_req_body *msg) +{ + int idx = 1; + + msg->u.conn_stat.port_number = (raw->msg[idx] & 0xf0) >> 4; + idx++; + if (idx > raw->curlen) + goto fail_len; + + memcpy(msg->u.conn_stat.guid, &raw->msg[idx], 16); + idx += 16; + if (idx > raw->curlen) + goto fail_len; + + msg->u.conn_stat.legacy_device_plug_status = (raw->msg[idx] >> 6) & 0x1; + msg->u.conn_stat.displayport_device_plug_status = (raw->msg[idx] >> 5) & 0x1; + msg->u.conn_stat.message_capability_status = (raw->msg[idx] >> 4) & 0x1; + msg->u.conn_stat.input_port = (raw->msg[idx] >> 3) & 0x1; + msg->u.conn_stat.peer_device_type = (raw->msg[idx] & 0x7); + idx++; + return true; +fail_len: + drm_dbg_kms(mgr->dev, "connection status reply parse length fail %d %d\n", + idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_resource_status_notify(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_req_body *msg) +{ + int idx = 1; + + msg->u.resource_stat.port_number = (raw->msg[idx] & 0xf0) >> 4; + idx++; + if (idx > raw->curlen) + goto fail_len; + + memcpy(msg->u.resource_stat.guid, &raw->msg[idx], 16); + idx += 16; + if (idx > raw->curlen) + goto fail_len; + + msg->u.resource_stat.available_pbn = (raw->msg[idx] << 8) | (raw->msg[idx + 1]); + idx++; + return true; +fail_len: + drm_dbg_kms(mgr->dev, "resource status reply parse length fail %d %d\n", idx, raw->curlen); + return false; +} + +static bool drm_dp_sideband_parse_req(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_rx *raw, + struct drm_dp_sideband_msg_req_body *msg) +{ + memset(msg, 0, sizeof(*msg)); + msg->req_type = (raw->msg[0] & 0x7f); + + switch (msg->req_type) { + case DP_CONNECTION_STATUS_NOTIFY: + return drm_dp_sideband_parse_connection_status_notify(mgr, raw, msg); + case DP_RESOURCE_STATUS_NOTIFY: + return drm_dp_sideband_parse_resource_status_notify(mgr, raw, msg); + default: + drm_err(mgr->dev, "Got unknown request 0x%02x (%s)\n", + msg->req_type, drm_dp_mst_req_type_str(msg->req_type)); + return false; + } +} + +static void build_dpcd_write(struct drm_dp_sideband_msg_tx *msg, + u8 port_num, u32 offset, u8 num_bytes, u8 *bytes) +{ + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_REMOTE_DPCD_WRITE; + req.u.dpcd_write.port_number = port_num; + req.u.dpcd_write.dpcd_address = offset; + req.u.dpcd_write.num_bytes = num_bytes; + req.u.dpcd_write.bytes = bytes; + drm_dp_encode_sideband_req(&req, msg); +} + +static void build_link_address(struct drm_dp_sideband_msg_tx *msg) +{ + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_LINK_ADDRESS; + drm_dp_encode_sideband_req(&req, msg); +} + +static void build_clear_payload_id_table(struct drm_dp_sideband_msg_tx *msg) +{ + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_CLEAR_PAYLOAD_ID_TABLE; + drm_dp_encode_sideband_req(&req, msg); + msg->path_msg = true; +} + +static int build_enum_path_resources(struct drm_dp_sideband_msg_tx *msg, + int port_num) +{ + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_ENUM_PATH_RESOURCES; + req.u.port_num.port_number = port_num; + drm_dp_encode_sideband_req(&req, msg); + msg->path_msg = true; + return 0; +} + +static void build_allocate_payload(struct drm_dp_sideband_msg_tx *msg, + int port_num, + u8 vcpi, uint16_t pbn, + u8 number_sdp_streams, + u8 *sdp_stream_sink) +{ + struct drm_dp_sideband_msg_req_body req; + + memset(&req, 0, sizeof(req)); + req.req_type = DP_ALLOCATE_PAYLOAD; + req.u.allocate_payload.port_number = port_num; + req.u.allocate_payload.vcpi = vcpi; + req.u.allocate_payload.pbn = pbn; + req.u.allocate_payload.number_sdp_streams = number_sdp_streams; + memcpy(req.u.allocate_payload.sdp_stream_sink, sdp_stream_sink, + number_sdp_streams); + drm_dp_encode_sideband_req(&req, msg); + msg->path_msg = true; +} + +static void build_power_updown_phy(struct drm_dp_sideband_msg_tx *msg, + int port_num, bool power_up) +{ + struct drm_dp_sideband_msg_req_body req; + + if (power_up) + req.req_type = DP_POWER_UP_PHY; + else + req.req_type = DP_POWER_DOWN_PHY; + + req.u.port_num.port_number = port_num; + drm_dp_encode_sideband_req(&req, msg); + msg->path_msg = true; +} + +static int +build_query_stream_enc_status(struct drm_dp_sideband_msg_tx *msg, u8 stream_id, + u8 *q_id) +{ + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_QUERY_STREAM_ENC_STATUS; + req.u.enc_status.stream_id = stream_id; + memcpy(req.u.enc_status.client_id, q_id, + sizeof(req.u.enc_status.client_id)); + req.u.enc_status.stream_event = 0; + req.u.enc_status.valid_stream_event = false; + req.u.enc_status.stream_behavior = 0; + req.u.enc_status.valid_stream_behavior = false; + + drm_dp_encode_sideband_req(&req, msg); + return 0; +} + +static int drm_dp_mst_assign_payload_id(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_vcpi *vcpi) +{ + int ret, vcpi_ret; + + mutex_lock(&mgr->payload_lock); + ret = find_first_zero_bit(&mgr->payload_mask, mgr->max_payloads + 1); + if (ret > mgr->max_payloads) { + ret = -EINVAL; + drm_dbg_kms(mgr->dev, "out of payload ids %d\n", ret); + goto out_unlock; + } + + vcpi_ret = find_first_zero_bit(&mgr->vcpi_mask, mgr->max_payloads + 1); + if (vcpi_ret > mgr->max_payloads) { + ret = -EINVAL; + drm_dbg_kms(mgr->dev, "out of vcpi ids %d\n", ret); + goto out_unlock; + } + + set_bit(ret, &mgr->payload_mask); + set_bit(vcpi_ret, &mgr->vcpi_mask); + vcpi->vcpi = vcpi_ret + 1; + mgr->proposed_vcpis[ret - 1] = vcpi; +out_unlock: + mutex_unlock(&mgr->payload_lock); + return ret; +} + +static void drm_dp_mst_put_payload_id(struct drm_dp_mst_topology_mgr *mgr, + int vcpi) +{ + int i; + + if (vcpi == 0) + return; + + mutex_lock(&mgr->payload_lock); + drm_dbg_kms(mgr->dev, "putting payload %d\n", vcpi); + clear_bit(vcpi - 1, &mgr->vcpi_mask); + + for (i = 0; i < mgr->max_payloads; i++) { + if (mgr->proposed_vcpis[i] && + mgr->proposed_vcpis[i]->vcpi == vcpi) { + mgr->proposed_vcpis[i] = NULL; + clear_bit(i + 1, &mgr->payload_mask); + } + } + mutex_unlock(&mgr->payload_lock); +} + +static bool check_txmsg_state(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_tx *txmsg) +{ + unsigned int state; + + /* + * All updates to txmsg->state are protected by mgr->qlock, and the two + * cases we check here are terminal states. For those the barriers + * provided by the wake_up/wait_event pair are enough. + */ + state = READ_ONCE(txmsg->state); + return (state == DRM_DP_SIDEBAND_TX_RX || + state == DRM_DP_SIDEBAND_TX_TIMEOUT); +} + +static int drm_dp_mst_wait_tx_reply(struct drm_dp_mst_branch *mstb, + struct drm_dp_sideband_msg_tx *txmsg) +{ + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + unsigned long wait_timeout = msecs_to_jiffies(4000); + unsigned long wait_expires = jiffies + wait_timeout; + int ret; + + for (;;) { + /* + * If the driver provides a way for this, change to + * poll-waiting for the MST reply interrupt if we didn't receive + * it for 50 msec. This would cater for cases where the HPD + * pulse signal got lost somewhere, even though the sink raised + * the corresponding MST interrupt correctly. One example is the + * Club 3D CAC-1557 TypeC -> DP adapter which for some reason + * filters out short pulses with a duration less than ~540 usec. + * + * The poll period is 50 msec to avoid missing an interrupt + * after the sink has cleared it (after a 110msec timeout + * since it raised the interrupt). + */ + ret = wait_event_timeout(mgr->tx_waitq, + check_txmsg_state(mgr, txmsg), + mgr->cbs->poll_hpd_irq ? + msecs_to_jiffies(50) : + wait_timeout); + + if (ret || !mgr->cbs->poll_hpd_irq || + time_after(jiffies, wait_expires)) + break; + + mgr->cbs->poll_hpd_irq(mgr); + } + + mutex_lock(&mgr->qlock); + if (ret > 0) { + if (txmsg->state == DRM_DP_SIDEBAND_TX_TIMEOUT) { + ret = -EIO; + goto out; + } + } else { + drm_dbg_kms(mgr->dev, "timedout msg send %p %d %d\n", + txmsg, txmsg->state, txmsg->seqno); + + /* dump some state */ + ret = -EIO; + + /* remove from q */ + if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED || + txmsg->state == DRM_DP_SIDEBAND_TX_START_SEND || + txmsg->state == DRM_DP_SIDEBAND_TX_SENT) + list_del(&txmsg->next); + } +out: + if (unlikely(ret == -EIO) && drm_debug_enabled(DRM_UT_DP)) { + struct drm_printer p = drm_debug_printer(DBG_PREFIX); + + drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); + } + mutex_unlock(&mgr->qlock); + + drm_dp_mst_kick_tx(mgr); + return ret; +} + +static struct drm_dp_mst_branch *drm_dp_add_mst_branch_device(u8 lct, u8 *rad) +{ + struct drm_dp_mst_branch *mstb; + + mstb = kzalloc(sizeof(*mstb), GFP_KERNEL); + if (!mstb) + return NULL; + + mstb->lct = lct; + if (lct > 1) + memcpy(mstb->rad, rad, lct / 2); + INIT_LIST_HEAD(&mstb->ports); + kref_init(&mstb->topology_kref); + kref_init(&mstb->malloc_kref); + return mstb; +} + +static void drm_dp_free_mst_branch_device(struct kref *kref) +{ + struct drm_dp_mst_branch *mstb = + container_of(kref, struct drm_dp_mst_branch, malloc_kref); + + if (mstb->port_parent) + drm_dp_mst_put_port_malloc(mstb->port_parent); + + kfree(mstb); +} + +/** + * DOC: Branch device and port refcounting + * + * Topology refcount overview + * ~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * The refcounting schemes for &struct drm_dp_mst_branch and &struct + * drm_dp_mst_port are somewhat unusual. Both ports and branch devices have + * two different kinds of refcounts: topology refcounts, and malloc refcounts. + * + * Topology refcounts are not exposed to drivers, and are handled internally + * by the DP MST helpers. The helpers use them in order to prevent the + * in-memory topology state from being changed in the middle of critical + * operations like changing the internal state of payload allocations. This + * means each branch and port will be considered to be connected to the rest + * of the topology until its topology refcount reaches zero. Additionally, + * for ports this means that their associated &struct drm_connector will stay + * registered with userspace until the port's refcount reaches 0. + * + * Malloc refcount overview + * ~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Malloc references are used to keep a &struct drm_dp_mst_port or &struct + * drm_dp_mst_branch allocated even after all of its topology references have + * been dropped, so that the driver or MST helpers can safely access each + * branch's last known state before it was disconnected from the topology. + * When the malloc refcount of a port or branch reaches 0, the memory + * allocation containing the &struct drm_dp_mst_branch or &struct + * drm_dp_mst_port respectively will be freed. + * + * For &struct drm_dp_mst_branch, malloc refcounts are not currently exposed + * to drivers. As of writing this documentation, there are no drivers that + * have a usecase for accessing &struct drm_dp_mst_branch outside of the MST + * helpers. Exposing this API to drivers in a race-free manner would take more + * tweaking of the refcounting scheme, however patches are welcome provided + * there is a legitimate driver usecase for this. + * + * Refcount relationships in a topology + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Let's take a look at why the relationship between topology and malloc + * refcounts is designed the way it is. + * + * .. kernel-figure:: dp-mst/topology-figure-1.dot + * + * An example of topology and malloc refs in a DP MST topology with two + * active payloads. Topology refcount increments are indicated by solid + * lines, and malloc refcount increments are indicated by dashed lines. + * Each starts from the branch which incremented the refcount, and ends at + * the branch to which the refcount belongs to, i.e. the arrow points the + * same way as the C pointers used to reference a structure. + * + * As you can see in the above figure, every branch increments the topology + * refcount of its children, and increments the malloc refcount of its + * parent. Additionally, every payload increments the malloc refcount of its + * assigned port by 1. + * + * So, what would happen if MSTB #3 from the above figure was unplugged from + * the system, but the driver hadn't yet removed payload #2 from port #3? The + * topology would start to look like the figure below. + * + * .. kernel-figure:: dp-mst/topology-figure-2.dot + * + * Ports and branch devices which have been released from memory are + * colored grey, and references which have been removed are colored red. + * + * Whenever a port or branch device's topology refcount reaches zero, it will + * decrement the topology refcounts of all its children, the malloc refcount + * of its parent, and finally its own malloc refcount. For MSTB #4 and port + * #4, this means they both have been disconnected from the topology and freed + * from memory. But, because payload #2 is still holding a reference to port + * #3, port #3 is removed from the topology but its &struct drm_dp_mst_port + * is still accessible from memory. This also means port #3 has not yet + * decremented the malloc refcount of MSTB #3, so its &struct + * drm_dp_mst_branch will also stay allocated in memory until port #3's + * malloc refcount reaches 0. + * + * This relationship is necessary because in order to release payload #2, we + * need to be able to figure out the last relative of port #3 that's still + * connected to the topology. In this case, we would travel up the topology as + * shown below. + * + * .. kernel-figure:: dp-mst/topology-figure-3.dot + * + * And finally, remove payload #2 by communicating with port #2 through + * sideband transactions. + */ + +/** + * drm_dp_mst_get_mstb_malloc() - Increment the malloc refcount of a branch + * device + * @mstb: The &struct drm_dp_mst_branch to increment the malloc refcount of + * + * Increments &drm_dp_mst_branch.malloc_kref. When + * &drm_dp_mst_branch.malloc_kref reaches 0, the memory allocation for @mstb + * will be released and @mstb may no longer be used. + * + * See also: drm_dp_mst_put_mstb_malloc() + */ +static void +drm_dp_mst_get_mstb_malloc(struct drm_dp_mst_branch *mstb) +{ + kref_get(&mstb->malloc_kref); + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref)); +} + +/** + * drm_dp_mst_put_mstb_malloc() - Decrement the malloc refcount of a branch + * device + * @mstb: The &struct drm_dp_mst_branch to decrement the malloc refcount of + * + * Decrements &drm_dp_mst_branch.malloc_kref. When + * &drm_dp_mst_branch.malloc_kref reaches 0, the memory allocation for @mstb + * will be released and @mstb may no longer be used. + * + * See also: drm_dp_mst_get_mstb_malloc() + */ +static void +drm_dp_mst_put_mstb_malloc(struct drm_dp_mst_branch *mstb) +{ + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref) - 1); + kref_put(&mstb->malloc_kref, drm_dp_free_mst_branch_device); +} + +static void drm_dp_free_mst_port(struct kref *kref) +{ + struct drm_dp_mst_port *port = + container_of(kref, struct drm_dp_mst_port, malloc_kref); + + drm_dp_mst_put_mstb_malloc(port->parent); + kfree(port); +} + +/** + * drm_dp_mst_get_port_malloc() - Increment the malloc refcount of an MST port + * @port: The &struct drm_dp_mst_port to increment the malloc refcount of + * + * Increments &drm_dp_mst_port.malloc_kref. When &drm_dp_mst_port.malloc_kref + * reaches 0, the memory allocation for @port will be released and @port may + * no longer be used. + * + * Because @port could potentially be freed at any time by the DP MST helpers + * if &drm_dp_mst_port.malloc_kref reaches 0, including during a call to this + * function, drivers that which to make use of &struct drm_dp_mst_port should + * ensure that they grab at least one main malloc reference to their MST ports + * in &drm_dp_mst_topology_cbs.add_connector. This callback is called before + * there is any chance for &drm_dp_mst_port.malloc_kref to reach 0. + * + * See also: drm_dp_mst_put_port_malloc() + */ +void +drm_dp_mst_get_port_malloc(struct drm_dp_mst_port *port) +{ + kref_get(&port->malloc_kref); + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->malloc_kref)); +} +EXPORT_SYMBOL(drm_dp_mst_get_port_malloc); + +/** + * drm_dp_mst_put_port_malloc() - Decrement the malloc refcount of an MST port + * @port: The &struct drm_dp_mst_port to decrement the malloc refcount of + * + * Decrements &drm_dp_mst_port.malloc_kref. When &drm_dp_mst_port.malloc_kref + * reaches 0, the memory allocation for @port will be released and @port may + * no longer be used. + * + * See also: drm_dp_mst_get_port_malloc() + */ +void +drm_dp_mst_put_port_malloc(struct drm_dp_mst_port *port) +{ + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->malloc_kref) - 1); + kref_put(&port->malloc_kref, drm_dp_free_mst_port); +} +EXPORT_SYMBOL(drm_dp_mst_put_port_malloc); + +#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) + +#define STACK_DEPTH 8 + +static noinline void +__topology_ref_save(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_topology_ref_history *history, + enum drm_dp_mst_topology_ref_type type) +{ + struct drm_dp_mst_topology_ref_entry *entry = NULL; + depot_stack_handle_t backtrace; + ulong stack_entries[STACK_DEPTH]; + uint n; + int i; + + n = stack_trace_save(stack_entries, ARRAY_SIZE(stack_entries), 1); + backtrace = stack_depot_save(stack_entries, n, GFP_KERNEL); + if (!backtrace) + return; + + /* Try to find an existing entry for this backtrace */ + for (i = 0; i < history->len; i++) { + if (history->entries[i].backtrace == backtrace) { + entry = &history->entries[i]; + break; + } + } + + /* Otherwise add one */ + if (!entry) { + struct drm_dp_mst_topology_ref_entry *new; + int new_len = history->len + 1; + + new = krealloc(history->entries, sizeof(*new) * new_len, + GFP_KERNEL); + if (!new) + return; + + entry = &new[history->len]; + history->len = new_len; + history->entries = new; + + entry->backtrace = backtrace; + entry->type = type; + entry->count = 0; + } + entry->count++; + entry->ts_nsec = ktime_get_ns(); +} + +static int +topology_ref_history_cmp(const void *a, const void *b) +{ + const struct drm_dp_mst_topology_ref_entry *entry_a = a, *entry_b = b; + + if (entry_a->ts_nsec > entry_b->ts_nsec) + return 1; + else if (entry_a->ts_nsec < entry_b->ts_nsec) + return -1; + else + return 0; +} + +static inline const char * +topology_ref_type_to_str(enum drm_dp_mst_topology_ref_type type) +{ + if (type == DRM_DP_MST_TOPOLOGY_REF_GET) + return "get"; + else + return "put"; +} + +static void +__dump_topology_ref_history(struct drm_dp_mst_topology_ref_history *history, + void *ptr, const char *type_str) +{ + struct drm_printer p = drm_debug_printer(DBG_PREFIX); + char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); + int i; + + if (!buf) + return; + + if (!history->len) + goto out; + + /* First, sort the list so that it goes from oldest to newest + * reference entry + */ + sort(history->entries, history->len, sizeof(*history->entries), + topology_ref_history_cmp, NULL); + + drm_printf(&p, "%s (%p) topology count reached 0, dumping history:\n", + type_str, ptr); + + for (i = 0; i < history->len; i++) { + const struct drm_dp_mst_topology_ref_entry *entry = + &history->entries[i]; + u64 ts_nsec = entry->ts_nsec; + u32 rem_nsec = do_div(ts_nsec, 1000000000); + + stack_depot_snprint(entry->backtrace, buf, PAGE_SIZE, 4); + + drm_printf(&p, " %d %ss (last at %5llu.%06u):\n%s", + entry->count, + topology_ref_type_to_str(entry->type), + ts_nsec, rem_nsec / 1000, buf); + } + + /* Now free the history, since this is the only time we expose it */ + kfree(history->entries); +out: + kfree(buf); +} + +static __always_inline void +drm_dp_mst_dump_mstb_topology_history(struct drm_dp_mst_branch *mstb) +{ + __dump_topology_ref_history(&mstb->topology_ref_history, mstb, + "MSTB"); +} + +static __always_inline void +drm_dp_mst_dump_port_topology_history(struct drm_dp_mst_port *port) +{ + __dump_topology_ref_history(&port->topology_ref_history, port, + "Port"); +} + +static __always_inline void +save_mstb_topology_ref(struct drm_dp_mst_branch *mstb, + enum drm_dp_mst_topology_ref_type type) +{ + __topology_ref_save(mstb->mgr, &mstb->topology_ref_history, type); +} + +static __always_inline void +save_port_topology_ref(struct drm_dp_mst_port *port, + enum drm_dp_mst_topology_ref_type type) +{ + __topology_ref_save(port->mgr, &port->topology_ref_history, type); +} + +static inline void +topology_ref_history_lock(struct drm_dp_mst_topology_mgr *mgr) +{ + mutex_lock(&mgr->topology_ref_history_lock); +} + +static inline void +topology_ref_history_unlock(struct drm_dp_mst_topology_mgr *mgr) +{ + mutex_unlock(&mgr->topology_ref_history_lock); +} +#else +static inline void +topology_ref_history_lock(struct drm_dp_mst_topology_mgr *mgr) {} +static inline void +topology_ref_history_unlock(struct drm_dp_mst_topology_mgr *mgr) {} +static inline void +drm_dp_mst_dump_mstb_topology_history(struct drm_dp_mst_branch *mstb) {} +static inline void +drm_dp_mst_dump_port_topology_history(struct drm_dp_mst_port *port) {} +#define save_mstb_topology_ref(mstb, type) +#define save_port_topology_ref(port, type) +#endif + +static void drm_dp_destroy_mst_branch_device(struct kref *kref) +{ + struct drm_dp_mst_branch *mstb = + container_of(kref, struct drm_dp_mst_branch, topology_kref); + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + + drm_dp_mst_dump_mstb_topology_history(mstb); + + INIT_LIST_HEAD(&mstb->destroy_next); + + /* + * This can get called under mgr->mutex, so we need to perform the + * actual destruction of the mstb in another worker + */ + mutex_lock(&mgr->delayed_destroy_lock); + list_add(&mstb->destroy_next, &mgr->destroy_branch_device_list); + mutex_unlock(&mgr->delayed_destroy_lock); + queue_work(mgr->delayed_destroy_wq, &mgr->delayed_destroy_work); +} + +/** + * drm_dp_mst_topology_try_get_mstb() - Increment the topology refcount of a + * branch device unless it's zero + * @mstb: &struct drm_dp_mst_branch to increment the topology refcount of + * + * Attempts to grab a topology reference to @mstb, if it hasn't yet been + * removed from the topology (e.g. &drm_dp_mst_branch.topology_kref has + * reached 0). Holding a topology reference implies that a malloc reference + * will be held to @mstb as long as the user holds the topology reference. + * + * Care should be taken to ensure that the user has at least one malloc + * reference to @mstb. If you already have a topology reference to @mstb, you + * should use drm_dp_mst_topology_get_mstb() instead. + * + * See also: + * drm_dp_mst_topology_get_mstb() + * drm_dp_mst_topology_put_mstb() + * + * Returns: + * * 1: A topology reference was grabbed successfully + * * 0: @port is no longer in the topology, no reference was grabbed + */ +static int __must_check +drm_dp_mst_topology_try_get_mstb(struct drm_dp_mst_branch *mstb) +{ + int ret; + + topology_ref_history_lock(mstb->mgr); + ret = kref_get_unless_zero(&mstb->topology_kref); + if (ret) { + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref)); + save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_GET); + } + + topology_ref_history_unlock(mstb->mgr); + + return ret; +} + +/** + * drm_dp_mst_topology_get_mstb() - Increment the topology refcount of a + * branch device + * @mstb: The &struct drm_dp_mst_branch to increment the topology refcount of + * + * Increments &drm_dp_mst_branch.topology_refcount without checking whether or + * not it's already reached 0. This is only valid to use in scenarios where + * you are already guaranteed to have at least one active topology reference + * to @mstb. Otherwise, drm_dp_mst_topology_try_get_mstb() must be used. + * + * See also: + * drm_dp_mst_topology_try_get_mstb() + * drm_dp_mst_topology_put_mstb() + */ +static void drm_dp_mst_topology_get_mstb(struct drm_dp_mst_branch *mstb) +{ + topology_ref_history_lock(mstb->mgr); + + save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_GET); + WARN_ON(kref_read(&mstb->topology_kref) == 0); + kref_get(&mstb->topology_kref); + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref)); + + topology_ref_history_unlock(mstb->mgr); +} + +/** + * drm_dp_mst_topology_put_mstb() - release a topology reference to a branch + * device + * @mstb: The &struct drm_dp_mst_branch to release the topology reference from + * + * Releases a topology reference from @mstb by decrementing + * &drm_dp_mst_branch.topology_kref. + * + * See also: + * drm_dp_mst_topology_try_get_mstb() + * drm_dp_mst_topology_get_mstb() + */ +static void +drm_dp_mst_topology_put_mstb(struct drm_dp_mst_branch *mstb) +{ + topology_ref_history_lock(mstb->mgr); + + drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref) - 1); + save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_PUT); + + topology_ref_history_unlock(mstb->mgr); + kref_put(&mstb->topology_kref, drm_dp_destroy_mst_branch_device); +} + +static void drm_dp_destroy_port(struct kref *kref) +{ + struct drm_dp_mst_port *port = + container_of(kref, struct drm_dp_mst_port, topology_kref); + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + + drm_dp_mst_dump_port_topology_history(port); + + /* There's nothing that needs locking to destroy an input port yet */ + if (port->input) { + drm_dp_mst_put_port_malloc(port); + return; + } + + kfree(port->cached_edid); + + /* + * we can't destroy the connector here, as we might be holding the + * mode_config.mutex from an EDID retrieval + */ + mutex_lock(&mgr->delayed_destroy_lock); + list_add(&port->next, &mgr->destroy_port_list); + mutex_unlock(&mgr->delayed_destroy_lock); + queue_work(mgr->delayed_destroy_wq, &mgr->delayed_destroy_work); +} + +/** + * drm_dp_mst_topology_try_get_port() - Increment the topology refcount of a + * port unless it's zero + * @port: &struct drm_dp_mst_port to increment the topology refcount of + * + * Attempts to grab a topology reference to @port, if it hasn't yet been + * removed from the topology (e.g. &drm_dp_mst_port.topology_kref has reached + * 0). Holding a topology reference implies that a malloc reference will be + * held to @port as long as the user holds the topology reference. + * + * Care should be taken to ensure that the user has at least one malloc + * reference to @port. If you already have a topology reference to @port, you + * should use drm_dp_mst_topology_get_port() instead. + * + * See also: + * drm_dp_mst_topology_get_port() + * drm_dp_mst_topology_put_port() + * + * Returns: + * * 1: A topology reference was grabbed successfully + * * 0: @port is no longer in the topology, no reference was grabbed + */ +static int __must_check +drm_dp_mst_topology_try_get_port(struct drm_dp_mst_port *port) +{ + int ret; + + topology_ref_history_lock(port->mgr); + ret = kref_get_unless_zero(&port->topology_kref); + if (ret) { + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref)); + save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_GET); + } + + topology_ref_history_unlock(port->mgr); + return ret; +} + +/** + * drm_dp_mst_topology_get_port() - Increment the topology refcount of a port + * @port: The &struct drm_dp_mst_port to increment the topology refcount of + * + * Increments &drm_dp_mst_port.topology_refcount without checking whether or + * not it's already reached 0. This is only valid to use in scenarios where + * you are already guaranteed to have at least one active topology reference + * to @port. Otherwise, drm_dp_mst_topology_try_get_port() must be used. + * + * See also: + * drm_dp_mst_topology_try_get_port() + * drm_dp_mst_topology_put_port() + */ +static void drm_dp_mst_topology_get_port(struct drm_dp_mst_port *port) +{ + topology_ref_history_lock(port->mgr); + + WARN_ON(kref_read(&port->topology_kref) == 0); + kref_get(&port->topology_kref); + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref)); + save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_GET); + + topology_ref_history_unlock(port->mgr); +} + +/** + * drm_dp_mst_topology_put_port() - release a topology reference to a port + * @port: The &struct drm_dp_mst_port to release the topology reference from + * + * Releases a topology reference from @port by decrementing + * &drm_dp_mst_port.topology_kref. + * + * See also: + * drm_dp_mst_topology_try_get_port() + * drm_dp_mst_topology_get_port() + */ +static void drm_dp_mst_topology_put_port(struct drm_dp_mst_port *port) +{ + topology_ref_history_lock(port->mgr); + + drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref) - 1); + save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_PUT); + + topology_ref_history_unlock(port->mgr); + kref_put(&port->topology_kref, drm_dp_destroy_port); +} + +static struct drm_dp_mst_branch * +drm_dp_mst_topology_get_mstb_validated_locked(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_branch *to_find) +{ + struct drm_dp_mst_port *port; + struct drm_dp_mst_branch *rmstb; + + if (to_find == mstb) + return mstb; + + list_for_each_entry(port, &mstb->ports, next) { + if (port->mstb) { + rmstb = drm_dp_mst_topology_get_mstb_validated_locked( + port->mstb, to_find); + if (rmstb) + return rmstb; + } + } + return NULL; +} + +static struct drm_dp_mst_branch * +drm_dp_mst_topology_get_mstb_validated(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb) +{ + struct drm_dp_mst_branch *rmstb = NULL; + + mutex_lock(&mgr->lock); + if (mgr->mst_primary) { + rmstb = drm_dp_mst_topology_get_mstb_validated_locked( + mgr->mst_primary, mstb); + + if (rmstb && !drm_dp_mst_topology_try_get_mstb(rmstb)) + rmstb = NULL; + } + mutex_unlock(&mgr->lock); + return rmstb; +} + +static struct drm_dp_mst_port * +drm_dp_mst_topology_get_port_validated_locked(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *to_find) +{ + struct drm_dp_mst_port *port, *mport; + + list_for_each_entry(port, &mstb->ports, next) { + if (port == to_find) + return port; + + if (port->mstb) { + mport = drm_dp_mst_topology_get_port_validated_locked( + port->mstb, to_find); + if (mport) + return mport; + } + } + return NULL; +} + +static struct drm_dp_mst_port * +drm_dp_mst_topology_get_port_validated(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ + struct drm_dp_mst_port *rport = NULL; + + mutex_lock(&mgr->lock); + if (mgr->mst_primary) { + rport = drm_dp_mst_topology_get_port_validated_locked( + mgr->mst_primary, port); + + if (rport && !drm_dp_mst_topology_try_get_port(rport)) + rport = NULL; + } + mutex_unlock(&mgr->lock); + return rport; +} + +static struct drm_dp_mst_port *drm_dp_get_port(struct drm_dp_mst_branch *mstb, u8 port_num) +{ + struct drm_dp_mst_port *port; + int ret; + + list_for_each_entry(port, &mstb->ports, next) { + if (port->port_num == port_num) { + ret = drm_dp_mst_topology_try_get_port(port); + return ret ? port : NULL; + } + } + + return NULL; +} + +/* + * calculate a new RAD for this MST branch device + * if parent has an LCT of 2 then it has 1 nibble of RAD, + * if parent has an LCT of 3 then it has 2 nibbles of RAD, + */ +static u8 drm_dp_calculate_rad(struct drm_dp_mst_port *port, + u8 *rad) +{ + int parent_lct = port->parent->lct; + int shift = 4; + int idx = (parent_lct - 1) / 2; + + if (parent_lct > 1) { + memcpy(rad, port->parent->rad, idx + 1); + shift = (parent_lct % 2) ? 4 : 0; + } else + rad[0] = 0; + + rad[idx] |= port->port_num << shift; + return parent_lct + 1; +} + +static bool drm_dp_mst_is_end_device(u8 pdt, bool mcs) +{ + switch (pdt) { + case DP_PEER_DEVICE_DP_LEGACY_CONV: + case DP_PEER_DEVICE_SST_SINK: + return true; + case DP_PEER_DEVICE_MST_BRANCHING: + /* For sst branch device */ + if (!mcs) + return true; + + return false; + } + return true; +} + +static int +drm_dp_port_set_pdt(struct drm_dp_mst_port *port, u8 new_pdt, + bool new_mcs) +{ + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + struct drm_dp_mst_branch *mstb; + u8 rad[8], lct; + int ret = 0; + + if (port->pdt == new_pdt && port->mcs == new_mcs) + return 0; + + /* Teardown the old pdt, if there is one */ + if (port->pdt != DP_PEER_DEVICE_NONE) { + if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { + /* + * If the new PDT would also have an i2c bus, + * don't bother with reregistering it + */ + if (new_pdt != DP_PEER_DEVICE_NONE && + drm_dp_mst_is_end_device(new_pdt, new_mcs)) { + port->pdt = new_pdt; + port->mcs = new_mcs; + return 0; + } + + /* remove i2c over sideband */ + drm_dp_mst_unregister_i2c_bus(port); + } else { + mutex_lock(&mgr->lock); + drm_dp_mst_topology_put_mstb(port->mstb); + port->mstb = NULL; + mutex_unlock(&mgr->lock); + } + } + + port->pdt = new_pdt; + port->mcs = new_mcs; + + if (port->pdt != DP_PEER_DEVICE_NONE) { + if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { + /* add i2c over sideband */ + ret = drm_dp_mst_register_i2c_bus(port); + } else { + lct = drm_dp_calculate_rad(port, rad); + mstb = drm_dp_add_mst_branch_device(lct, rad); + if (!mstb) { + ret = -ENOMEM; + drm_err(mgr->dev, "Failed to create MSTB for port %p", port); + goto out; + } + + mutex_lock(&mgr->lock); + port->mstb = mstb; + mstb->mgr = port->mgr; + mstb->port_parent = port; + + /* + * Make sure this port's memory allocation stays + * around until its child MSTB releases it + */ + drm_dp_mst_get_port_malloc(port); + mutex_unlock(&mgr->lock); + + /* And make sure we send a link address for this */ + ret = 1; + } + } + +out: + if (ret < 0) + port->pdt = DP_PEER_DEVICE_NONE; + return ret; +} + +/** + * drm_dp_mst_dpcd_read() - read a series of bytes from the DPCD via sideband + * @aux: Fake sideband AUX CH + * @offset: address of the (first) register to read + * @buffer: buffer to store the register values + * @size: number of bytes in @buffer + * + * Performs the same functionality for remote devices via + * sideband messaging as drm_dp_dpcd_read() does for local + * devices via actual AUX CH. + * + * Return: Number of bytes read, or negative error code on failure. + */ +ssize_t drm_dp_mst_dpcd_read(struct drm_dp_aux *aux, + unsigned int offset, void *buffer, size_t size) +{ + struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port, + aux); + + return drm_dp_send_dpcd_read(port->mgr, port, + offset, size, buffer); +} + +/** + * drm_dp_mst_dpcd_write() - write a series of bytes to the DPCD via sideband + * @aux: Fake sideband AUX CH + * @offset: address of the (first) register to write + * @buffer: buffer containing the values to write + * @size: number of bytes in @buffer + * + * Performs the same functionality for remote devices via + * sideband messaging as drm_dp_dpcd_write() does for local + * devices via actual AUX CH. + * + * Return: number of bytes written on success, negative error code on failure. + */ +ssize_t drm_dp_mst_dpcd_write(struct drm_dp_aux *aux, + unsigned int offset, void *buffer, size_t size) +{ + struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port, + aux); + + return drm_dp_send_dpcd_write(port->mgr, port, + offset, size, buffer); +} + +static int drm_dp_check_mstb_guid(struct drm_dp_mst_branch *mstb, u8 *guid) +{ + int ret = 0; + + memcpy(mstb->guid, guid, 16); + + if (!drm_dp_validate_guid(mstb->mgr, mstb->guid)) { + if (mstb->port_parent) { + ret = drm_dp_send_dpcd_write(mstb->mgr, + mstb->port_parent, + DP_GUID, 16, mstb->guid); + } else { + ret = drm_dp_dpcd_write(mstb->mgr->aux, + DP_GUID, mstb->guid, 16); + } + } + + if (ret < 16 && ret > 0) + return -EPROTO; + + return ret == 16 ? 0 : ret; +} + +static void build_mst_prop_path(const struct drm_dp_mst_branch *mstb, + int pnum, + char *proppath, + size_t proppath_size) +{ + int i; + char temp[8]; + + snprintf(proppath, proppath_size, "mst:%d", mstb->mgr->conn_base_id); + for (i = 0; i < (mstb->lct - 1); i++) { + int shift = (i % 2) ? 0 : 4; + int port_num = (mstb->rad[i / 2] >> shift) & 0xf; + + snprintf(temp, sizeof(temp), "-%d", port_num); + strlcat(proppath, temp, proppath_size); + } + snprintf(temp, sizeof(temp), "-%d", pnum); + strlcat(proppath, temp, proppath_size); +} + +/** + * drm_dp_mst_connector_late_register() - Late MST connector registration + * @connector: The MST connector + * @port: The MST port for this connector + * + * Helper to register the remote aux device for this MST port. Drivers should + * call this from their mst connector's late_register hook to enable MST aux + * devices. + * + * Return: 0 on success, negative error code on failure. + */ +int drm_dp_mst_connector_late_register(struct drm_connector *connector, + struct drm_dp_mst_port *port) +{ + drm_dbg_kms(port->mgr->dev, "registering %s remote bus for %s\n", + port->aux.name, connector->kdev->kobj.name); + + port->aux.dev = connector->kdev; + return drm_dp_aux_register_devnode(&port->aux); +} +EXPORT_SYMBOL(drm_dp_mst_connector_late_register); + +/** + * drm_dp_mst_connector_early_unregister() - Early MST connector unregistration + * @connector: The MST connector + * @port: The MST port for this connector + * + * Helper to unregister the remote aux device for this MST port, registered by + * drm_dp_mst_connector_late_register(). Drivers should call this from their mst + * connector's early_unregister hook. + */ +void drm_dp_mst_connector_early_unregister(struct drm_connector *connector, + struct drm_dp_mst_port *port) +{ + drm_dbg_kms(port->mgr->dev, "unregistering %s remote bus for %s\n", + port->aux.name, connector->kdev->kobj.name); + drm_dp_aux_unregister_devnode(&port->aux); +} +EXPORT_SYMBOL(drm_dp_mst_connector_early_unregister); + +static void +drm_dp_mst_port_add_connector(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port) +{ + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + char proppath[255]; + int ret; + + build_mst_prop_path(mstb, port->port_num, proppath, sizeof(proppath)); + port->connector = mgr->cbs->add_connector(mgr, port, proppath); + if (!port->connector) { + ret = -ENOMEM; + goto error; + } + + if (port->pdt != DP_PEER_DEVICE_NONE && + drm_dp_mst_is_end_device(port->pdt, port->mcs) && + port->port_num >= DP_MST_LOGICAL_PORT_0) + port->cached_edid = drm_get_edid(port->connector, + &port->aux.ddc); + + drm_connector_register(port->connector); + return; + +error: + drm_err(mgr->dev, "Failed to create connector for port %p: %d\n", port, ret); +} + +/* + * Drop a topology reference, and unlink the port from the in-memory topology + * layout + */ +static void +drm_dp_mst_topology_unlink_port(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ + mutex_lock(&mgr->lock); + port->parent->num_ports--; + list_del(&port->next); + mutex_unlock(&mgr->lock); + drm_dp_mst_topology_put_port(port); +} + +static struct drm_dp_mst_port * +drm_dp_mst_add_port(struct drm_device *dev, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, u8 port_number) +{ + struct drm_dp_mst_port *port = kzalloc(sizeof(*port), GFP_KERNEL); + + if (!port) + return NULL; + + kref_init(&port->topology_kref); + kref_init(&port->malloc_kref); + port->parent = mstb; + port->port_num = port_number; + port->mgr = mgr; + port->aux.name = "DPMST"; + port->aux.dev = dev->dev; + port->aux.is_remote = true; + + /* initialize the MST downstream port's AUX crc work queue */ + port->aux.drm_dev = dev; + drm_dp_remote_aux_init(&port->aux); + + /* + * Make sure the memory allocation for our parent branch stays + * around until our own memory allocation is released + */ + drm_dp_mst_get_mstb_malloc(mstb); + + return port; +} + +static int +drm_dp_mst_handle_link_address_port(struct drm_dp_mst_branch *mstb, + struct drm_device *dev, + struct drm_dp_link_addr_reply_port *port_msg) +{ + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + struct drm_dp_mst_port *port; + int old_ddps = 0, ret; + u8 new_pdt = DP_PEER_DEVICE_NONE; + bool new_mcs = 0; + bool created = false, send_link_addr = false, changed = false; + + port = drm_dp_get_port(mstb, port_msg->port_number); + if (!port) { + port = drm_dp_mst_add_port(dev, mgr, mstb, + port_msg->port_number); + if (!port) + return -ENOMEM; + created = true; + changed = true; + } else if (!port->input && port_msg->input_port && port->connector) { + /* Since port->connector can't be changed here, we create a + * new port if input_port changes from 0 to 1 + */ + drm_dp_mst_topology_unlink_port(mgr, port); + drm_dp_mst_topology_put_port(port); + port = drm_dp_mst_add_port(dev, mgr, mstb, + port_msg->port_number); + if (!port) + return -ENOMEM; + changed = true; + created = true; + } else if (port->input && !port_msg->input_port) { + changed = true; + } else if (port->connector) { + /* We're updating a port that's exposed to userspace, so do it + * under lock + */ + drm_modeset_lock(&mgr->base.lock, NULL); + + old_ddps = port->ddps; + changed = port->ddps != port_msg->ddps || + (port->ddps && + (port->ldps != port_msg->legacy_device_plug_status || + port->dpcd_rev != port_msg->dpcd_revision || + port->mcs != port_msg->mcs || + port->pdt != port_msg->peer_device_type || + port->num_sdp_stream_sinks != + port_msg->num_sdp_stream_sinks)); + } + + port->input = port_msg->input_port; + if (!port->input) + new_pdt = port_msg->peer_device_type; + new_mcs = port_msg->mcs; + port->ddps = port_msg->ddps; + port->ldps = port_msg->legacy_device_plug_status; + port->dpcd_rev = port_msg->dpcd_revision; + port->num_sdp_streams = port_msg->num_sdp_streams; + port->num_sdp_stream_sinks = port_msg->num_sdp_stream_sinks; + + /* manage mstb port lists with mgr lock - take a reference + for this list */ + if (created) { + mutex_lock(&mgr->lock); + drm_dp_mst_topology_get_port(port); + list_add(&port->next, &mstb->ports); + mstb->num_ports++; + mutex_unlock(&mgr->lock); + } + + /* + * Reprobe PBN caps on both hotplug, and when re-probing the link + * for our parent mstb + */ + if (old_ddps != port->ddps || !created) { + if (port->ddps && !port->input) { + ret = drm_dp_send_enum_path_resources(mgr, mstb, + port); + if (ret == 1) + changed = true; + } else { + port->full_pbn = 0; + } + } + + ret = drm_dp_port_set_pdt(port, new_pdt, new_mcs); + if (ret == 1) { + send_link_addr = true; + } else if (ret < 0) { + drm_err(dev, "Failed to change PDT on port %p: %d\n", port, ret); + goto fail; + } + + /* + * If this port wasn't just created, then we're reprobing because + * we're coming out of suspend. In this case, always resend the link + * address if there's an MSTB on this port + */ + if (!created && port->pdt == DP_PEER_DEVICE_MST_BRANCHING && + port->mcs) + send_link_addr = true; + + if (port->connector) + drm_modeset_unlock(&mgr->base.lock); + else if (!port->input) + drm_dp_mst_port_add_connector(mstb, port); + + if (send_link_addr && port->mstb) { + ret = drm_dp_send_link_address(mgr, port->mstb); + if (ret == 1) /* MSTB below us changed */ + changed = true; + else if (ret < 0) + goto fail_put; + } + + /* put reference to this port */ + drm_dp_mst_topology_put_port(port); + return changed; + +fail: + drm_dp_mst_topology_unlink_port(mgr, port); + if (port->connector) + drm_modeset_unlock(&mgr->base.lock); +fail_put: + drm_dp_mst_topology_put_port(port); + return ret; +} + +static void +drm_dp_mst_handle_conn_stat(struct drm_dp_mst_branch *mstb, + struct drm_dp_connection_status_notify *conn_stat) +{ + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + struct drm_dp_mst_port *port; + int old_ddps, ret; + u8 new_pdt; + bool new_mcs; + bool dowork = false, create_connector = false; + + port = drm_dp_get_port(mstb, conn_stat->port_number); + if (!port) + return; + + if (port->connector) { + if (!port->input && conn_stat->input_port) { + /* + * We can't remove a connector from an already exposed + * port, so just throw the port out and make sure we + * reprobe the link address of it's parent MSTB + */ + drm_dp_mst_topology_unlink_port(mgr, port); + mstb->link_address_sent = false; + dowork = true; + goto out; + } + + /* Locking is only needed if the port's exposed to userspace */ + drm_modeset_lock(&mgr->base.lock, NULL); + } else if (port->input && !conn_stat->input_port) { + create_connector = true; + /* Reprobe link address so we get num_sdp_streams */ + mstb->link_address_sent = false; + dowork = true; + } + + old_ddps = port->ddps; + port->input = conn_stat->input_port; + port->ldps = conn_stat->legacy_device_plug_status; + port->ddps = conn_stat->displayport_device_plug_status; + + if (old_ddps != port->ddps) { + if (port->ddps && !port->input) + drm_dp_send_enum_path_resources(mgr, mstb, port); + else + port->full_pbn = 0; + } + + new_pdt = port->input ? DP_PEER_DEVICE_NONE : conn_stat->peer_device_type; + new_mcs = conn_stat->message_capability_status; + ret = drm_dp_port_set_pdt(port, new_pdt, new_mcs); + if (ret == 1) { + dowork = true; + } else if (ret < 0) { + drm_err(mgr->dev, "Failed to change PDT for port %p: %d\n", port, ret); + dowork = false; + } + + if (port->connector) + drm_modeset_unlock(&mgr->base.lock); + else if (create_connector) + drm_dp_mst_port_add_connector(mstb, port); + +out: + drm_dp_mst_topology_put_port(port); + if (dowork) + queue_work(system_long_wq, &mstb->mgr->work); +} + +static struct drm_dp_mst_branch *drm_dp_get_mst_branch_device(struct drm_dp_mst_topology_mgr *mgr, + u8 lct, u8 *rad) +{ + struct drm_dp_mst_branch *mstb; + struct drm_dp_mst_port *port; + int i, ret; + /* find the port by iterating down */ + + mutex_lock(&mgr->lock); + mstb = mgr->mst_primary; + + if (!mstb) + goto out; + + for (i = 0; i < lct - 1; i++) { + int shift = (i % 2) ? 0 : 4; + int port_num = (rad[i / 2] >> shift) & 0xf; + + list_for_each_entry(port, &mstb->ports, next) { + if (port->port_num == port_num) { + mstb = port->mstb; + if (!mstb) { + drm_err(mgr->dev, + "failed to lookup MSTB with lct %d, rad %02x\n", + lct, rad[0]); + goto out; + } + + break; + } + } + } + ret = drm_dp_mst_topology_try_get_mstb(mstb); + if (!ret) + mstb = NULL; +out: + mutex_unlock(&mgr->lock); + return mstb; +} + +static struct drm_dp_mst_branch *get_mst_branch_device_by_guid_helper( + struct drm_dp_mst_branch *mstb, + const uint8_t *guid) +{ + struct drm_dp_mst_branch *found_mstb; + struct drm_dp_mst_port *port; + + if (memcmp(mstb->guid, guid, 16) == 0) + return mstb; + + + list_for_each_entry(port, &mstb->ports, next) { + if (!port->mstb) + continue; + + found_mstb = get_mst_branch_device_by_guid_helper(port->mstb, guid); + + if (found_mstb) + return found_mstb; + } + + return NULL; +} + +static struct drm_dp_mst_branch * +drm_dp_get_mst_branch_device_by_guid(struct drm_dp_mst_topology_mgr *mgr, + const uint8_t *guid) +{ + struct drm_dp_mst_branch *mstb; + int ret; + + /* find the port by iterating down */ + mutex_lock(&mgr->lock); + + mstb = get_mst_branch_device_by_guid_helper(mgr->mst_primary, guid); + if (mstb) { + ret = drm_dp_mst_topology_try_get_mstb(mstb); + if (!ret) + mstb = NULL; + } + + mutex_unlock(&mgr->lock); + return mstb; +} + +static int drm_dp_check_and_send_link_address(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb) +{ + struct drm_dp_mst_port *port; + int ret; + bool changed = false; + + if (!mstb->link_address_sent) { + ret = drm_dp_send_link_address(mgr, mstb); + if (ret == 1) + changed = true; + else if (ret < 0) + return ret; + } + + list_for_each_entry(port, &mstb->ports, next) { + struct drm_dp_mst_branch *mstb_child = NULL; + + if (port->input || !port->ddps) + continue; + + if (port->mstb) + mstb_child = drm_dp_mst_topology_get_mstb_validated( + mgr, port->mstb); + + if (mstb_child) { + ret = drm_dp_check_and_send_link_address(mgr, + mstb_child); + drm_dp_mst_topology_put_mstb(mstb_child); + if (ret == 1) + changed = true; + else if (ret < 0) + return ret; + } + } + + return changed; +} + +static void drm_dp_mst_link_probe_work(struct work_struct *work) +{ + struct drm_dp_mst_topology_mgr *mgr = + container_of(work, struct drm_dp_mst_topology_mgr, work); + struct drm_device *dev = mgr->dev; + struct drm_dp_mst_branch *mstb; + int ret; + bool clear_payload_id_table; + + mutex_lock(&mgr->probe_lock); + + mutex_lock(&mgr->lock); + clear_payload_id_table = !mgr->payload_id_table_cleared; + mgr->payload_id_table_cleared = true; + + mstb = mgr->mst_primary; + if (mstb) { + ret = drm_dp_mst_topology_try_get_mstb(mstb); + if (!ret) + mstb = NULL; + } + mutex_unlock(&mgr->lock); + if (!mstb) { + mutex_unlock(&mgr->probe_lock); + return; + } + + /* + * Certain branch devices seem to incorrectly report an available_pbn + * of 0 on downstream sinks, even after clearing the + * DP_PAYLOAD_ALLOCATE_* registers in + * drm_dp_mst_topology_mgr_set_mst(). Namely, the CableMatters USB-C + * 2x DP hub. Sending a CLEAR_PAYLOAD_ID_TABLE message seems to make + * things work again. + */ + if (clear_payload_id_table) { + drm_dbg_kms(dev, "Clearing payload ID table\n"); + drm_dp_send_clear_payload_id_table(mgr, mstb); + } + + ret = drm_dp_check_and_send_link_address(mgr, mstb); + drm_dp_mst_topology_put_mstb(mstb); + + mutex_unlock(&mgr->probe_lock); + if (ret > 0) + drm_kms_helper_hotplug_event(dev); +} + +static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr, + u8 *guid) +{ + u64 salt; + + if (memchr_inv(guid, 0, 16)) + return true; + + salt = get_jiffies_64(); + + memcpy(&guid[0], &salt, sizeof(u64)); + memcpy(&guid[8], &salt, sizeof(u64)); + + return false; +} + +static void build_dpcd_read(struct drm_dp_sideband_msg_tx *msg, + u8 port_num, u32 offset, u8 num_bytes) +{ + struct drm_dp_sideband_msg_req_body req; + + req.req_type = DP_REMOTE_DPCD_READ; + req.u.dpcd_read.port_number = port_num; + req.u.dpcd_read.dpcd_address = offset; + req.u.dpcd_read.num_bytes = num_bytes; + drm_dp_encode_sideband_req(&req, msg); +} + +static int drm_dp_send_sideband_msg(struct drm_dp_mst_topology_mgr *mgr, + bool up, u8 *msg, int len) +{ + int ret; + int regbase = up ? DP_SIDEBAND_MSG_UP_REP_BASE : DP_SIDEBAND_MSG_DOWN_REQ_BASE; + int tosend, total, offset; + int retries = 0; + +retry: + total = len; + offset = 0; + do { + tosend = min3(mgr->max_dpcd_transaction_bytes, 16, total); + + ret = drm_dp_dpcd_write(mgr->aux, regbase + offset, + &msg[offset], + tosend); + if (ret != tosend) { + if (ret == -EIO && retries < 5) { + retries++; + goto retry; + } + drm_dbg_kms(mgr->dev, "failed to dpcd write %d %d\n", tosend, ret); + + return -EIO; + } + offset += tosend; + total -= tosend; + } while (total > 0); + return 0; +} + +static int set_hdr_from_dst_qlock(struct drm_dp_sideband_msg_hdr *hdr, + struct drm_dp_sideband_msg_tx *txmsg) +{ + struct drm_dp_mst_branch *mstb = txmsg->dst; + u8 req_type; + + req_type = txmsg->msg[0] & 0x7f; + if (req_type == DP_CONNECTION_STATUS_NOTIFY || + req_type == DP_RESOURCE_STATUS_NOTIFY || + req_type == DP_CLEAR_PAYLOAD_ID_TABLE) + hdr->broadcast = 1; + else + hdr->broadcast = 0; + hdr->path_msg = txmsg->path_msg; + if (hdr->broadcast) { + hdr->lct = 1; + hdr->lcr = 6; + } else { + hdr->lct = mstb->lct; + hdr->lcr = mstb->lct - 1; + } + + memcpy(hdr->rad, mstb->rad, hdr->lct / 2); + + return 0; +} +/* + * process a single block of the next message in the sideband queue + */ +static int process_single_tx_qlock(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_tx *txmsg, + bool up) +{ + u8 chunk[48]; + struct drm_dp_sideband_msg_hdr hdr; + int len, space, idx, tosend; + int ret; + + if (txmsg->state == DRM_DP_SIDEBAND_TX_SENT) + return 0; + + memset(&hdr, 0, sizeof(struct drm_dp_sideband_msg_hdr)); + + if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED) + txmsg->state = DRM_DP_SIDEBAND_TX_START_SEND; + + /* make hdr from dst mst */ + ret = set_hdr_from_dst_qlock(&hdr, txmsg); + if (ret < 0) + return ret; + + /* amount left to send in this message */ + len = txmsg->cur_len - txmsg->cur_offset; + + /* 48 - sideband msg size - 1 byte for data CRC, x header bytes */ + space = 48 - 1 - drm_dp_calc_sb_hdr_size(&hdr); + + tosend = min(len, space); + if (len == txmsg->cur_len) + hdr.somt = 1; + if (space >= len) + hdr.eomt = 1; + + + hdr.msg_len = tosend + 1; + drm_dp_encode_sideband_msg_hdr(&hdr, chunk, &idx); + memcpy(&chunk[idx], &txmsg->msg[txmsg->cur_offset], tosend); + /* add crc at end */ + drm_dp_crc_sideband_chunk_req(&chunk[idx], tosend); + idx += tosend + 1; + + ret = drm_dp_send_sideband_msg(mgr, up, chunk, idx); + if (ret) { + if (drm_debug_enabled(DRM_UT_DP)) { + struct drm_printer p = drm_debug_printer(DBG_PREFIX); + + drm_printf(&p, "sideband msg failed to send\n"); + drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); + } + return ret; + } + + txmsg->cur_offset += tosend; + if (txmsg->cur_offset == txmsg->cur_len) { + txmsg->state = DRM_DP_SIDEBAND_TX_SENT; + return 1; + } + return 0; +} + +static void process_single_down_tx_qlock(struct drm_dp_mst_topology_mgr *mgr) +{ + struct drm_dp_sideband_msg_tx *txmsg; + int ret; + + WARN_ON(!mutex_is_locked(&mgr->qlock)); + + /* construct a chunk from the first msg in the tx_msg queue */ + if (list_empty(&mgr->tx_msg_downq)) + return; + + txmsg = list_first_entry(&mgr->tx_msg_downq, + struct drm_dp_sideband_msg_tx, next); + ret = process_single_tx_qlock(mgr, txmsg, false); + if (ret < 0) { + drm_dbg_kms(mgr->dev, "failed to send msg in q %d\n", ret); + list_del(&txmsg->next); + txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT; + wake_up_all(&mgr->tx_waitq); + } +} + +static void drm_dp_queue_down_tx(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_sideband_msg_tx *txmsg) +{ + mutex_lock(&mgr->qlock); + list_add_tail(&txmsg->next, &mgr->tx_msg_downq); + + if (drm_debug_enabled(DRM_UT_DP)) { + struct drm_printer p = drm_debug_printer(DBG_PREFIX); + + drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); + } + + if (list_is_singular(&mgr->tx_msg_downq)) + process_single_down_tx_qlock(mgr); + mutex_unlock(&mgr->qlock); +} + +static void +drm_dp_dump_link_address(const struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_link_address_ack_reply *reply) +{ + struct drm_dp_link_addr_reply_port *port_reply; + int i; + + for (i = 0; i < reply->nports; i++) { + port_reply = &reply->ports[i]; + drm_dbg_kms(mgr->dev, + "port %d: input %d, pdt: %d, pn: %d, dpcd_rev: %02x, mcs: %d, ddps: %d, ldps %d, sdp %d/%d\n", + i, + port_reply->input_port, + port_reply->peer_device_type, + port_reply->port_number, + port_reply->dpcd_revision, + port_reply->mcs, + port_reply->ddps, + port_reply->legacy_device_plug_status, + port_reply->num_sdp_streams, + port_reply->num_sdp_stream_sinks); + } +} + +static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb) +{ + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_link_address_ack_reply *reply; + struct drm_dp_mst_port *port, *tmp; + int i, ret, port_mask = 0; + bool changed = false; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return -ENOMEM; + + txmsg->dst = mstb; + build_link_address(txmsg); + + mstb->link_address_sent = true; + drm_dp_queue_down_tx(mgr, txmsg); + + /* FIXME: Actually do some real error handling here */ + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret <= 0) { + drm_err(mgr->dev, "Sending link address failed with %d\n", ret); + goto out; + } + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + drm_err(mgr->dev, "link address NAK received\n"); + ret = -EIO; + goto out; + } + + reply = &txmsg->reply.u.link_addr; + drm_dbg_kms(mgr->dev, "link address reply: %d\n", reply->nports); + drm_dp_dump_link_address(mgr, reply); + + ret = drm_dp_check_mstb_guid(mstb, reply->guid); + if (ret) { + char buf[64]; + + drm_dp_mst_rad_to_str(mstb->rad, mstb->lct, buf, sizeof(buf)); + drm_err(mgr->dev, "GUID check on %s failed: %d\n", buf, ret); + goto out; + } + + for (i = 0; i < reply->nports; i++) { + port_mask |= BIT(reply->ports[i].port_number); + ret = drm_dp_mst_handle_link_address_port(mstb, mgr->dev, + &reply->ports[i]); + if (ret == 1) + changed = true; + else if (ret < 0) + goto out; + } + + /* Prune any ports that are currently a part of mstb in our in-memory + * topology, but were not seen in this link address. Usually this + * means that they were removed while the topology was out of sync, + * e.g. during suspend/resume + */ + mutex_lock(&mgr->lock); + list_for_each_entry_safe(port, tmp, &mstb->ports, next) { + if (port_mask & BIT(port->port_num)) + continue; + + drm_dbg_kms(mgr->dev, "port %d was not in link address, removing\n", + port->port_num); + list_del(&port->next); + drm_dp_mst_topology_put_port(port); + changed = true; + } + mutex_unlock(&mgr->lock); + +out: + if (ret <= 0) + mstb->link_address_sent = false; + kfree(txmsg); + return ret < 0 ? ret : changed; +} + +static void +drm_dp_send_clear_payload_id_table(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb) +{ + struct drm_dp_sideband_msg_tx *txmsg; + int ret; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return; + + txmsg->dst = mstb; + build_clear_payload_id_table(txmsg); + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0 && txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) + drm_dbg_kms(mgr->dev, "clear payload table id nak received\n"); + + kfree(txmsg); +} + +static int +drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port) +{ + struct drm_dp_enum_path_resources_ack_reply *path_res; + struct drm_dp_sideband_msg_tx *txmsg; + int ret; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return -ENOMEM; + + txmsg->dst = mstb; + build_enum_path_resources(txmsg, port->port_num); + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + ret = 0; + path_res = &txmsg->reply.u.path_resources; + + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + drm_dbg_kms(mgr->dev, "enum path resources nak received\n"); + } else { + if (port->port_num != path_res->port_number) + DRM_ERROR("got incorrect port in response\n"); + + drm_dbg_kms(mgr->dev, "enum path resources %d: %d %d\n", + path_res->port_number, + path_res->full_payload_bw_number, + path_res->avail_payload_bw_number); + + /* + * If something changed, make sure we send a + * hotplug + */ + if (port->full_pbn != path_res->full_payload_bw_number || + port->fec_capable != path_res->fec_capable) + ret = 1; + + port->full_pbn = path_res->full_payload_bw_number; + port->fec_capable = path_res->fec_capable; + } + } + + kfree(txmsg); + return ret; +} + +static struct drm_dp_mst_port *drm_dp_get_last_connected_port_to_mstb(struct drm_dp_mst_branch *mstb) +{ + if (!mstb->port_parent) + return NULL; + + if (mstb->port_parent->mstb != mstb) + return mstb->port_parent; + + return drm_dp_get_last_connected_port_to_mstb(mstb->port_parent->parent); +} + +/* + * Searches upwards in the topology starting from mstb to try to find the + * closest available parent of mstb that's still connected to the rest of the + * topology. This can be used in order to perform operations like releasing + * payloads, where the branch device which owned the payload may no longer be + * around and thus would require that the payload on the last living relative + * be freed instead. + */ +static struct drm_dp_mst_branch * +drm_dp_get_last_connected_port_and_mstb(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, + int *port_num) +{ + struct drm_dp_mst_branch *rmstb = NULL; + struct drm_dp_mst_port *found_port; + + mutex_lock(&mgr->lock); + if (!mgr->mst_primary) + goto out; + + do { + found_port = drm_dp_get_last_connected_port_to_mstb(mstb); + if (!found_port) + break; + + if (drm_dp_mst_topology_try_get_mstb(found_port->parent)) { + rmstb = found_port->parent; + *port_num = found_port->port_num; + } else { + /* Search again, starting from this parent */ + mstb = found_port->parent; + } + } while (!rmstb); +out: + mutex_unlock(&mgr->lock); + return rmstb; +} + +static int drm_dp_payload_send_msg(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int id, + int pbn) +{ + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_mst_branch *mstb; + int ret, port_num; + u8 sinks[DRM_DP_MAX_SDP_STREAMS]; + int i; + + port_num = port->port_num; + mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); + if (!mstb) { + mstb = drm_dp_get_last_connected_port_and_mstb(mgr, + port->parent, + &port_num); + + if (!mstb) + return -EINVAL; + } + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto fail_put; + } + + for (i = 0; i < port->num_sdp_streams; i++) + sinks[i] = i; + + txmsg->dst = mstb; + build_allocate_payload(txmsg, port_num, + id, + pbn, port->num_sdp_streams, sinks); + + drm_dp_queue_down_tx(mgr, txmsg); + + /* + * FIXME: there is a small chance that between getting the last + * connected mstb and sending the payload message, the last connected + * mstb could also be removed from the topology. In the future, this + * needs to be fixed by restarting the + * drm_dp_get_last_connected_port_and_mstb() search in the event of a + * timeout if the topology is still connected to the system. + */ + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) + ret = -EINVAL; + else + ret = 0; + } + kfree(txmsg); +fail_put: + drm_dp_mst_topology_put_mstb(mstb); + return ret; +} + +int drm_dp_send_power_updown_phy(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, bool power_up) +{ + struct drm_dp_sideband_msg_tx *txmsg; + int ret; + + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return -EINVAL; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + drm_dp_mst_topology_put_port(port); + return -ENOMEM; + } + + txmsg->dst = port->parent; + build_power_updown_phy(txmsg, port->port_num, power_up); + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(port->parent, txmsg); + if (ret > 0) { + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) + ret = -EINVAL; + else + ret = 0; + } + kfree(txmsg); + drm_dp_mst_topology_put_port(port); + + return ret; +} +EXPORT_SYMBOL(drm_dp_send_power_updown_phy); + +int drm_dp_send_query_stream_enc_status(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + struct drm_dp_query_stream_enc_status_ack_reply *status) +{ + struct drm_dp_sideband_msg_tx *txmsg; + u8 nonce[7]; + int ret; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return -ENOMEM; + + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) { + ret = -EINVAL; + goto out_get_port; + } + + get_random_bytes(nonce, sizeof(nonce)); + + /* + * "Source device targets the QUERY_STREAM_ENCRYPTION_STATUS message + * transaction at the MST Branch device directly connected to the + * Source" + */ + txmsg->dst = mgr->mst_primary; + + build_query_stream_enc_status(txmsg, port->vcpi.vcpi, nonce); + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mgr->mst_primary, txmsg); + if (ret < 0) { + goto out; + } else if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + drm_dbg_kms(mgr->dev, "query encryption status nak received\n"); + ret = -ENXIO; + goto out; + } + + ret = 0; + memcpy(status, &txmsg->reply.u.enc_status, sizeof(*status)); + +out: + drm_dp_mst_topology_put_port(port); +out_get_port: + kfree(txmsg); + return ret; +} +EXPORT_SYMBOL(drm_dp_send_query_stream_enc_status); + +static int drm_dp_create_payload_step1(struct drm_dp_mst_topology_mgr *mgr, + int id, + struct drm_dp_payload *payload) +{ + int ret; + + ret = drm_dp_dpcd_write_payload(mgr, id, payload); + if (ret < 0) { + payload->payload_state = 0; + return ret; + } + payload->payload_state = DP_PAYLOAD_LOCAL; + return 0; +} + +static int drm_dp_create_payload_step2(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int id, + struct drm_dp_payload *payload) +{ + int ret; + + ret = drm_dp_payload_send_msg(mgr, port, id, port->vcpi.pbn); + if (ret < 0) + return ret; + payload->payload_state = DP_PAYLOAD_REMOTE; + return ret; +} + +static int drm_dp_destroy_payload_step1(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int id, + struct drm_dp_payload *payload) +{ + drm_dbg_kms(mgr->dev, "\n"); + /* it's okay for these to fail */ + if (port) { + drm_dp_payload_send_msg(mgr, port, id, 0); + } + + drm_dp_dpcd_write_payload(mgr, id, payload); + payload->payload_state = DP_PAYLOAD_DELETE_LOCAL; + return 0; +} + +static int drm_dp_destroy_payload_step2(struct drm_dp_mst_topology_mgr *mgr, + int id, + struct drm_dp_payload *payload) +{ + payload->payload_state = 0; + return 0; +} + +/** + * drm_dp_update_payload_part1() - Execute payload update part 1 + * @mgr: manager to use. + * @start_slot: this is the cur slot + * + * NOTE: start_slot is a temporary workaround for non-atomic drivers, + * this will be removed when non-atomic mst helpers are moved out of the helper + * + * This iterates over all proposed virtual channels, and tries to + * allocate space in the link for them. For 0->slots transitions, + * this step just writes the VCPI to the MST device. For slots->0 + * transitions, this writes the updated VCPIs and removes the + * remote VC payloads. + * + * after calling this the driver should generate ACT and payload + * packets. + */ +int drm_dp_update_payload_part1(struct drm_dp_mst_topology_mgr *mgr, int start_slot) +{ + struct drm_dp_payload req_payload; + struct drm_dp_mst_port *port; + int i, j; + int cur_slots = start_slot; + bool skip; + + mutex_lock(&mgr->payload_lock); + for (i = 0; i < mgr->max_payloads; i++) { + struct drm_dp_vcpi *vcpi = mgr->proposed_vcpis[i]; + struct drm_dp_payload *payload = &mgr->payloads[i]; + bool put_port = false; + + /* solve the current payloads - compare to the hw ones + - update the hw view */ + req_payload.start_slot = cur_slots; + if (vcpi) { + port = container_of(vcpi, struct drm_dp_mst_port, + vcpi); + + mutex_lock(&mgr->lock); + skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); + mutex_unlock(&mgr->lock); + + if (skip) { + drm_dbg_kms(mgr->dev, + "Virtual channel %d is not in current topology\n", + i); + continue; + } + /* Validated ports don't matter if we're releasing + * VCPI + */ + if (vcpi->num_slots) { + port = drm_dp_mst_topology_get_port_validated( + mgr, port); + if (!port) { + if (vcpi->num_slots == payload->num_slots) { + cur_slots += vcpi->num_slots; + payload->start_slot = req_payload.start_slot; + continue; + } else { + drm_dbg_kms(mgr->dev, + "Fail:set payload to invalid sink"); + mutex_unlock(&mgr->payload_lock); + return -EINVAL; + } + } + put_port = true; + } + + req_payload.num_slots = vcpi->num_slots; + req_payload.vcpi = vcpi->vcpi; + } else { + port = NULL; + req_payload.num_slots = 0; + } + + payload->start_slot = req_payload.start_slot; + /* work out what is required to happen with this payload */ + if (payload->num_slots != req_payload.num_slots) { + + /* need to push an update for this payload */ + if (req_payload.num_slots) { + drm_dp_create_payload_step1(mgr, vcpi->vcpi, + &req_payload); + payload->num_slots = req_payload.num_slots; + payload->vcpi = req_payload.vcpi; + + } else if (payload->num_slots) { + payload->num_slots = 0; + drm_dp_destroy_payload_step1(mgr, port, + payload->vcpi, + payload); + req_payload.payload_state = + payload->payload_state; + payload->start_slot = 0; + } + payload->payload_state = req_payload.payload_state; + } + cur_slots += req_payload.num_slots; + + if (put_port) + drm_dp_mst_topology_put_port(port); + } + + for (i = 0; i < mgr->max_payloads; /* do nothing */) { + if (mgr->payloads[i].payload_state != DP_PAYLOAD_DELETE_LOCAL) { + i++; + continue; + } + + drm_dbg_kms(mgr->dev, "removing payload %d\n", i); + for (j = i; j < mgr->max_payloads - 1; j++) { + mgr->payloads[j] = mgr->payloads[j + 1]; + mgr->proposed_vcpis[j] = mgr->proposed_vcpis[j + 1]; + + if (mgr->proposed_vcpis[j] && + mgr->proposed_vcpis[j]->num_slots) { + set_bit(j + 1, &mgr->payload_mask); + } else { + clear_bit(j + 1, &mgr->payload_mask); + } + } + + memset(&mgr->payloads[mgr->max_payloads - 1], 0, + sizeof(struct drm_dp_payload)); + mgr->proposed_vcpis[mgr->max_payloads - 1] = NULL; + clear_bit(mgr->max_payloads, &mgr->payload_mask); + } + mutex_unlock(&mgr->payload_lock); + + return 0; +} +EXPORT_SYMBOL(drm_dp_update_payload_part1); + +/** + * drm_dp_update_payload_part2() - Execute payload update part 2 + * @mgr: manager to use. + * + * This iterates over all proposed virtual channels, and tries to + * allocate space in the link for them. For 0->slots transitions, + * this step writes the remote VC payload commands. For slots->0 + * this just resets some internal state. + */ +int drm_dp_update_payload_part2(struct drm_dp_mst_topology_mgr *mgr) +{ + struct drm_dp_mst_port *port; + int i; + int ret = 0; + bool skip; + + mutex_lock(&mgr->payload_lock); + for (i = 0; i < mgr->max_payloads; i++) { + + if (!mgr->proposed_vcpis[i]) + continue; + + port = container_of(mgr->proposed_vcpis[i], struct drm_dp_mst_port, vcpi); + + mutex_lock(&mgr->lock); + skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); + mutex_unlock(&mgr->lock); + + if (skip) + continue; + + drm_dbg_kms(mgr->dev, "payload %d %d\n", i, mgr->payloads[i].payload_state); + if (mgr->payloads[i].payload_state == DP_PAYLOAD_LOCAL) { + ret = drm_dp_create_payload_step2(mgr, port, mgr->proposed_vcpis[i]->vcpi, &mgr->payloads[i]); + } else if (mgr->payloads[i].payload_state == DP_PAYLOAD_DELETE_LOCAL) { + ret = drm_dp_destroy_payload_step2(mgr, mgr->proposed_vcpis[i]->vcpi, &mgr->payloads[i]); + } + if (ret) { + mutex_unlock(&mgr->payload_lock); + return ret; + } + } + mutex_unlock(&mgr->payload_lock); + return 0; +} +EXPORT_SYMBOL(drm_dp_update_payload_part2); + +static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int offset, int size, u8 *bytes) +{ + int ret = 0; + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_mst_branch *mstb; + + mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); + if (!mstb) + return -EINVAL; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto fail_put; + } + + build_dpcd_read(txmsg, port->port_num, offset, size); + txmsg->dst = port->parent; + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + 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); + ret = -EIO; + goto fail_free; + } + + if (txmsg->reply.u.remote_dpcd_read_ack.num_bytes != size) { + ret = -EPROTO; + goto fail_free; + } + + ret = min_t(size_t, txmsg->reply.u.remote_dpcd_read_ack.num_bytes, + size); + memcpy(bytes, txmsg->reply.u.remote_dpcd_read_ack.bytes, ret); + +fail_free: + kfree(txmsg); +fail_put: + drm_dp_mst_topology_put_mstb(mstb); + + return ret; +} + +static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int offset, int size, u8 *bytes) +{ + int ret; + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_mst_branch *mstb; + + mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); + if (!mstb) + return -EINVAL; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto fail_put; + } + + build_dpcd_write(txmsg, port->port_num, offset, size, bytes); + txmsg->dst = mstb; + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) + ret = -EIO; + else + ret = size; + } + + kfree(txmsg); +fail_put: + drm_dp_mst_topology_put_mstb(mstb); + return ret; +} + +static int drm_dp_encode_up_ack_reply(struct drm_dp_sideband_msg_tx *msg, u8 req_type) +{ + struct drm_dp_sideband_msg_reply_body reply; + + reply.reply_type = DP_SIDEBAND_REPLY_ACK; + reply.req_type = req_type; + drm_dp_encode_sideband_reply(&reply, msg); + return 0; +} + +static int drm_dp_send_up_ack_reply(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_branch *mstb, + int req_type, bool broadcast) +{ + struct drm_dp_sideband_msg_tx *txmsg; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) + return -ENOMEM; + + txmsg->dst = mstb; + drm_dp_encode_up_ack_reply(txmsg, req_type); + + mutex_lock(&mgr->qlock); + /* construct a chunk from the first msg in the tx_msg queue */ + process_single_tx_qlock(mgr, txmsg, true); + mutex_unlock(&mgr->qlock); + + kfree(txmsg); + return 0; +} + +/** + * drm_dp_get_vc_payload_bw - get the VC payload BW for an MST link + * @mgr: The &drm_dp_mst_topology_mgr to use + * @link_rate: link rate in 10kbits/s units + * @link_lane_count: lane count + * + * Calculate the total bandwidth of a MultiStream Transport link. The returned + * value is in units of PBNs/(timeslots/1 MTP). This value can be used to + * convert the number of PBNs required for a given stream to the number of + * timeslots this stream requires in each MTP. + */ +int drm_dp_get_vc_payload_bw(const struct drm_dp_mst_topology_mgr *mgr, + int link_rate, int link_lane_count) +{ + if (link_rate == 0 || link_lane_count == 0) + drm_dbg_kms(mgr->dev, "invalid link rate/lane count: (%d / %d)\n", + link_rate, link_lane_count); + + /* See DP v2.0 2.6.4.2, VCPayload_Bandwidth_for_OneTimeSlotPer_MTP_Allocation */ + return link_rate * link_lane_count / 54000; +} +EXPORT_SYMBOL(drm_dp_get_vc_payload_bw); + +/** + * drm_dp_read_mst_cap() - check whether or not a sink supports MST + * @aux: The DP AUX channel to use + * @dpcd: A cached copy of the DPCD capabilities for this sink + * + * Returns: %True if the sink supports MST, %false otherwise + */ +bool drm_dp_read_mst_cap(struct drm_dp_aux *aux, + const u8 dpcd[DP_RECEIVER_CAP_SIZE]) +{ + u8 mstm_cap; + + if (dpcd[DP_DPCD_REV] < DP_DPCD_REV_12) + return false; + + if (drm_dp_dpcd_readb(aux, DP_MSTM_CAP, &mstm_cap) != 1) + return false; + + return mstm_cap & DP_MST_CAP; +} +EXPORT_SYMBOL(drm_dp_read_mst_cap); + +/** + * drm_dp_mst_topology_mgr_set_mst() - Set the MST state for a topology manager + * @mgr: manager to set state for + * @mst_state: true to enable MST on this connector - false to disable. + * + * This is called by the driver when it detects an MST capable device plugged + * into a DP MST capable port, or when a DP MST capable device is unplugged. + */ +int drm_dp_mst_topology_mgr_set_mst(struct drm_dp_mst_topology_mgr *mgr, bool mst_state) +{ + int ret = 0; + struct drm_dp_mst_branch *mstb = NULL; + + mutex_lock(&mgr->payload_lock); + mutex_lock(&mgr->lock); + if (mst_state == mgr->mst_state) + goto out_unlock; + + mgr->mst_state = mst_state; + /* set the device into MST mode */ + if (mst_state) { + struct drm_dp_payload reset_pay; + int lane_count; + int link_rate; + + WARN_ON(mgr->mst_primary); + + /* get dpcd info */ + ret = drm_dp_read_dpcd_caps(mgr->aux, mgr->dpcd); + if (ret < 0) { + drm_dbg_kms(mgr->dev, "%s: failed to read DPCD, ret %d\n", + mgr->aux->name, ret); + goto out_unlock; + } + + lane_count = min_t(int, mgr->dpcd[2] & DP_MAX_LANE_COUNT_MASK, mgr->max_lane_count); + link_rate = min_t(int, drm_dp_bw_code_to_link_rate(mgr->dpcd[1]), mgr->max_link_rate); + mgr->pbn_div = drm_dp_get_vc_payload_bw(mgr, + link_rate, + lane_count); + if (mgr->pbn_div == 0) { + ret = -EINVAL; + goto out_unlock; + } + + /* add initial branch device at LCT 1 */ + mstb = drm_dp_add_mst_branch_device(1, NULL); + if (mstb == NULL) { + ret = -ENOMEM; + goto out_unlock; + } + mstb->mgr = mgr; + + /* give this the main reference */ + mgr->mst_primary = mstb; + drm_dp_mst_topology_get_mstb(mgr->mst_primary); + + ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, + DP_MST_EN | + DP_UP_REQ_EN | + DP_UPSTREAM_IS_SRC); + if (ret < 0) + goto out_unlock; + + reset_pay.start_slot = 0; + reset_pay.num_slots = 0x3f; + drm_dp_dpcd_write_payload(mgr, 0, &reset_pay); + + queue_work(system_long_wq, &mgr->work); + + ret = 0; + } else { + /* disable MST on the device */ + mstb = mgr->mst_primary; + mgr->mst_primary = NULL; + /* this can fail if the device is gone */ + drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, 0); + ret = 0; + memset(mgr->payloads, 0, + mgr->max_payloads * sizeof(mgr->payloads[0])); + memset(mgr->proposed_vcpis, 0, + mgr->max_payloads * sizeof(mgr->proposed_vcpis[0])); + mgr->payload_mask = 0; + set_bit(0, &mgr->payload_mask); + mgr->vcpi_mask = 0; + mgr->payload_id_table_cleared = false; + } + +out_unlock: + mutex_unlock(&mgr->lock); + mutex_unlock(&mgr->payload_lock); + if (mstb) + drm_dp_mst_topology_put_mstb(mstb); + return ret; + +} +EXPORT_SYMBOL(drm_dp_mst_topology_mgr_set_mst); + +static void +drm_dp_mst_topology_mgr_invalidate_mstb(struct drm_dp_mst_branch *mstb) +{ + struct drm_dp_mst_port *port; + + /* The link address will need to be re-sent on resume */ + mstb->link_address_sent = false; + + list_for_each_entry(port, &mstb->ports, next) + if (port->mstb) + drm_dp_mst_topology_mgr_invalidate_mstb(port->mstb); +} + +/** + * drm_dp_mst_topology_mgr_suspend() - suspend the MST manager + * @mgr: manager to suspend + * + * This function tells the MST device that we can't handle UP messages + * anymore. This should stop it from sending any since we are suspended. + */ +void drm_dp_mst_topology_mgr_suspend(struct drm_dp_mst_topology_mgr *mgr) +{ + mutex_lock(&mgr->lock); + drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, + DP_MST_EN | DP_UPSTREAM_IS_SRC); + mutex_unlock(&mgr->lock); + flush_work(&mgr->up_req_work); + flush_work(&mgr->work); + flush_work(&mgr->delayed_destroy_work); + + mutex_lock(&mgr->lock); + if (mgr->mst_state && mgr->mst_primary) + drm_dp_mst_topology_mgr_invalidate_mstb(mgr->mst_primary); + mutex_unlock(&mgr->lock); +} +EXPORT_SYMBOL(drm_dp_mst_topology_mgr_suspend); + +/** + * drm_dp_mst_topology_mgr_resume() - resume the MST manager + * @mgr: manager to resume + * @sync: whether or not to perform topology reprobing synchronously + * + * This will fetch DPCD and see if the device is still there, + * if it is, it will rewrite the MSTM control bits, and return. + * + * If the device fails this returns -1, and the driver should do + * a full MST reprobe, in case we were undocked. + * + * During system resume (where it is assumed that the driver will be calling + * drm_atomic_helper_resume()) this function should be called beforehand with + * @sync set to true. In contexts like runtime resume where the driver is not + * expected to be calling drm_atomic_helper_resume(), this function should be + * called with @sync set to false in order to avoid deadlocking. + * + * Returns: -1 if the MST topology was removed while we were suspended, 0 + * otherwise. + */ +int drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr, + bool sync) +{ + int ret; + u8 guid[16]; + + mutex_lock(&mgr->lock); + if (!mgr->mst_primary) + goto out_fail; + + ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, mgr->dpcd, + DP_RECEIVER_CAP_SIZE); + if (ret != DP_RECEIVER_CAP_SIZE) { + drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n"); + goto out_fail; + } + + ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, + DP_MST_EN | + DP_UP_REQ_EN | + DP_UPSTREAM_IS_SRC); + if (ret < 0) { + drm_dbg_kms(mgr->dev, "mst write failed - undocked during suspend?\n"); + goto out_fail; + } + + /* Some hubs forget their guids after they resume */ + ret = drm_dp_dpcd_read(mgr->aux, DP_GUID, guid, 16); + if (ret != 16) { + drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n"); + goto out_fail; + } + + ret = drm_dp_check_mstb_guid(mgr->mst_primary, guid); + if (ret) { + drm_dbg_kms(mgr->dev, "check mstb failed - undocked during suspend?\n"); + goto out_fail; + } + + /* + * For the final step of resuming the topology, we need to bring the + * state of our in-memory topology back into sync with reality. So, + * restart the probing process as if we're probing a new hub + */ + queue_work(system_long_wq, &mgr->work); + mutex_unlock(&mgr->lock); + + if (sync) { + drm_dbg_kms(mgr->dev, + "Waiting for link probe work to finish re-syncing topology...\n"); + flush_work(&mgr->work); + } + + return 0; + +out_fail: + mutex_unlock(&mgr->lock); + return -1; +} +EXPORT_SYMBOL(drm_dp_mst_topology_mgr_resume); + +static bool +drm_dp_get_one_sb_msg(struct drm_dp_mst_topology_mgr *mgr, bool up, + struct drm_dp_mst_branch **mstb) +{ + int len; + u8 replyblock[32]; + int replylen, curreply; + int ret; + u8 hdrlen; + struct drm_dp_sideband_msg_hdr hdr; + struct drm_dp_sideband_msg_rx *msg = + up ? &mgr->up_req_recv : &mgr->down_rep_recv; + int basereg = up ? DP_SIDEBAND_MSG_UP_REQ_BASE : + DP_SIDEBAND_MSG_DOWN_REP_BASE; + + if (!up) + *mstb = NULL; + + len = min(mgr->max_dpcd_transaction_bytes, 16); + ret = drm_dp_dpcd_read(mgr->aux, basereg, replyblock, len); + if (ret != len) { + drm_dbg_kms(mgr->dev, "failed to read DPCD down rep %d %d\n", len, ret); + return false; + } + + ret = drm_dp_decode_sideband_msg_hdr(mgr, &hdr, replyblock, len, &hdrlen); + if (ret == false) { + print_hex_dump(KERN_DEBUG, "failed hdr", DUMP_PREFIX_NONE, 16, + 1, replyblock, len, false); + drm_dbg_kms(mgr->dev, "ERROR: failed header\n"); + return false; + } + + if (!up) { + /* Caller is responsible for giving back this reference */ + *mstb = drm_dp_get_mst_branch_device(mgr, hdr.lct, hdr.rad); + if (!*mstb) { + drm_dbg_kms(mgr->dev, "Got MST reply from unknown device %d\n", hdr.lct); + return false; + } + } + + if (!drm_dp_sideband_msg_set_header(msg, &hdr, hdrlen)) { + drm_dbg_kms(mgr->dev, "sideband msg set header failed %d\n", replyblock[0]); + return false; + } + + replylen = min(msg->curchunk_len, (u8)(len - hdrlen)); + ret = drm_dp_sideband_append_payload(msg, replyblock + hdrlen, replylen); + if (!ret) { + drm_dbg_kms(mgr->dev, "sideband msg build failed %d\n", replyblock[0]); + return false; + } + + replylen = msg->curchunk_len + msg->curchunk_hdrlen - len; + curreply = len; + while (replylen > 0) { + len = min3(replylen, mgr->max_dpcd_transaction_bytes, 16); + ret = drm_dp_dpcd_read(mgr->aux, basereg + curreply, + replyblock, len); + if (ret != len) { + drm_dbg_kms(mgr->dev, "failed to read a chunk (len %d, ret %d)\n", + len, ret); + return false; + } + + ret = drm_dp_sideband_append_payload(msg, replyblock, len); + if (!ret) { + drm_dbg_kms(mgr->dev, "failed to build sideband msg\n"); + return false; + } + + curreply += len; + replylen -= len; + } + return true; +} + +static int drm_dp_mst_handle_down_rep(struct drm_dp_mst_topology_mgr *mgr) +{ + struct drm_dp_sideband_msg_tx *txmsg; + struct drm_dp_mst_branch *mstb = NULL; + struct drm_dp_sideband_msg_rx *msg = &mgr->down_rep_recv; + + if (!drm_dp_get_one_sb_msg(mgr, false, &mstb)) + goto out; + + /* Multi-packet message transmission, don't clear the reply */ + if (!msg->have_eomt) + goto out; + + /* find the message */ + mutex_lock(&mgr->qlock); + txmsg = list_first_entry_or_null(&mgr->tx_msg_downq, + struct drm_dp_sideband_msg_tx, next); + mutex_unlock(&mgr->qlock); + + /* Were we actually expecting a response, and from this mstb? */ + if (!txmsg || txmsg->dst != mstb) { + struct drm_dp_sideband_msg_hdr *hdr; + + hdr = &msg->initial_hdr; + drm_dbg_kms(mgr->dev, "Got MST reply with no msg %p %d %d %02x %02x\n", + mstb, hdr->seqno, hdr->lct, hdr->rad[0], msg->msg[0]); + goto out_clear_reply; + } + + drm_dp_sideband_parse_reply(mgr, msg, &txmsg->reply); + + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + drm_dbg_kms(mgr->dev, + "Got NAK reply: req 0x%02x (%s), reason 0x%02x (%s), nak data 0x%02x\n", + txmsg->reply.req_type, + drm_dp_mst_req_type_str(txmsg->reply.req_type), + txmsg->reply.u.nak.reason, + drm_dp_mst_nak_reason_str(txmsg->reply.u.nak.reason), + txmsg->reply.u.nak.nak_data); + } + + memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx)); + drm_dp_mst_topology_put_mstb(mstb); + + mutex_lock(&mgr->qlock); + txmsg->state = DRM_DP_SIDEBAND_TX_RX; + list_del(&txmsg->next); + mutex_unlock(&mgr->qlock); + + wake_up_all(&mgr->tx_waitq); + + return 0; + +out_clear_reply: + memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx)); +out: + if (mstb) + drm_dp_mst_topology_put_mstb(mstb); + + return 0; +} + +static inline bool +drm_dp_mst_process_up_req(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_pending_up_req *up_req) +{ + struct drm_dp_mst_branch *mstb = NULL; + struct drm_dp_sideband_msg_req_body *msg = &up_req->msg; + struct drm_dp_sideband_msg_hdr *hdr = &up_req->hdr; + bool hotplug = false; + + if (hdr->broadcast) { + const u8 *guid = NULL; + + if (msg->req_type == DP_CONNECTION_STATUS_NOTIFY) + guid = msg->u.conn_stat.guid; + else if (msg->req_type == DP_RESOURCE_STATUS_NOTIFY) + guid = msg->u.resource_stat.guid; + + if (guid) + mstb = drm_dp_get_mst_branch_device_by_guid(mgr, guid); + } else { + mstb = drm_dp_get_mst_branch_device(mgr, hdr->lct, hdr->rad); + } + + if (!mstb) { + drm_dbg_kms(mgr->dev, "Got MST reply from unknown device %d\n", hdr->lct); + return false; + } + + /* TODO: Add missing handler for DP_RESOURCE_STATUS_NOTIFY events */ + if (msg->req_type == DP_CONNECTION_STATUS_NOTIFY) { + drm_dp_mst_handle_conn_stat(mstb, &msg->u.conn_stat); + hotplug = true; + } + + drm_dp_mst_topology_put_mstb(mstb); + return hotplug; +} + +static void drm_dp_mst_up_req_work(struct work_struct *work) +{ + struct drm_dp_mst_topology_mgr *mgr = + container_of(work, struct drm_dp_mst_topology_mgr, + up_req_work); + struct drm_dp_pending_up_req *up_req; + bool send_hotplug = false; + + mutex_lock(&mgr->probe_lock); + while (true) { + mutex_lock(&mgr->up_req_lock); + up_req = list_first_entry_or_null(&mgr->up_req_list, + struct drm_dp_pending_up_req, + next); + if (up_req) + list_del(&up_req->next); + mutex_unlock(&mgr->up_req_lock); + + if (!up_req) + break; + + send_hotplug |= drm_dp_mst_process_up_req(mgr, up_req); + kfree(up_req); + } + mutex_unlock(&mgr->probe_lock); + + if (send_hotplug) + drm_kms_helper_hotplug_event(mgr->dev); +} + +static int drm_dp_mst_handle_up_req(struct drm_dp_mst_topology_mgr *mgr) +{ + struct drm_dp_pending_up_req *up_req; + + if (!drm_dp_get_one_sb_msg(mgr, true, NULL)) + goto out; + + if (!mgr->up_req_recv.have_eomt) + return 0; + + up_req = kzalloc(sizeof(*up_req), GFP_KERNEL); + if (!up_req) + return -ENOMEM; + + INIT_LIST_HEAD(&up_req->next); + + drm_dp_sideband_parse_req(mgr, &mgr->up_req_recv, &up_req->msg); + + if (up_req->msg.req_type != DP_CONNECTION_STATUS_NOTIFY && + up_req->msg.req_type != DP_RESOURCE_STATUS_NOTIFY) { + drm_dbg_kms(mgr->dev, "Received unknown up req type, ignoring: %x\n", + up_req->msg.req_type); + kfree(up_req); + goto out; + } + + drm_dp_send_up_ack_reply(mgr, mgr->mst_primary, up_req->msg.req_type, + false); + + if (up_req->msg.req_type == DP_CONNECTION_STATUS_NOTIFY) { + const struct drm_dp_connection_status_notify *conn_stat = + &up_req->msg.u.conn_stat; + + drm_dbg_kms(mgr->dev, "Got CSN: pn: %d ldps:%d ddps: %d mcs: %d ip: %d pdt: %d\n", + conn_stat->port_number, + conn_stat->legacy_device_plug_status, + conn_stat->displayport_device_plug_status, + conn_stat->message_capability_status, + conn_stat->input_port, + conn_stat->peer_device_type); + } else if (up_req->msg.req_type == DP_RESOURCE_STATUS_NOTIFY) { + const struct drm_dp_resource_status_notify *res_stat = + &up_req->msg.u.resource_stat; + + drm_dbg_kms(mgr->dev, "Got RSN: pn: %d avail_pbn %d\n", + res_stat->port_number, + res_stat->available_pbn); + } + + up_req->hdr = mgr->up_req_recv.initial_hdr; + mutex_lock(&mgr->up_req_lock); + list_add_tail(&up_req->next, &mgr->up_req_list); + mutex_unlock(&mgr->up_req_lock); + queue_work(system_long_wq, &mgr->up_req_work); + +out: + memset(&mgr->up_req_recv, 0, sizeof(struct drm_dp_sideband_msg_rx)); + return 0; +} + +/** + * drm_dp_mst_hpd_irq() - MST hotplug IRQ notify + * @mgr: manager to notify irq for. + * @esi: 4 bytes from SINK_COUNT_ESI + * @handled: whether the hpd interrupt was consumed or not + * + * This should be called from the driver when it detects a short IRQ, + * along with the value of the DEVICE_SERVICE_IRQ_VECTOR_ESI0. The + * topology manager will process the sideband messages received as a result + * of this. + */ +int drm_dp_mst_hpd_irq(struct drm_dp_mst_topology_mgr *mgr, u8 *esi, bool *handled) +{ + int ret = 0; + int sc; + *handled = false; + sc = DP_GET_SINK_COUNT(esi[0]); + + if (sc != mgr->sink_count) { + mgr->sink_count = sc; + *handled = true; + } + + if (esi[1] & DP_DOWN_REP_MSG_RDY) { + ret = drm_dp_mst_handle_down_rep(mgr); + *handled = true; + } + + if (esi[1] & DP_UP_REQ_MSG_RDY) { + ret |= drm_dp_mst_handle_up_req(mgr); + *handled = true; + } + + drm_dp_mst_kick_tx(mgr); + return ret; +} +EXPORT_SYMBOL(drm_dp_mst_hpd_irq); + +/** + * drm_dp_mst_detect_port() - get connection status for an MST port + * @connector: DRM connector for this port + * @ctx: The acquisition context to use for grabbing locks + * @mgr: manager for this port + * @port: pointer to a port + * + * This returns the current connection state for a port. + */ +int +drm_dp_mst_detect_port(struct drm_connector *connector, + struct drm_modeset_acquire_ctx *ctx, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ + int ret; + + /* we need to search for the port in the mgr in case it's gone */ + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return connector_status_disconnected; + + ret = drm_modeset_lock(&mgr->base.lock, ctx); + if (ret) + goto out; + + ret = connector_status_disconnected; + + if (!port->ddps) + goto out; + + switch (port->pdt) { + case DP_PEER_DEVICE_NONE: + break; + case DP_PEER_DEVICE_MST_BRANCHING: + if (!port->mcs) + ret = connector_status_connected; + break; + + case DP_PEER_DEVICE_SST_SINK: + ret = connector_status_connected; + /* for logical ports - cache the EDID */ + if (port->port_num >= DP_MST_LOGICAL_PORT_0 && !port->cached_edid) + port->cached_edid = drm_get_edid(connector, &port->aux.ddc); + break; + case DP_PEER_DEVICE_DP_LEGACY_CONV: + if (port->ldps) + ret = connector_status_connected; + break; + } +out: + drm_dp_mst_topology_put_port(port); + return ret; +} +EXPORT_SYMBOL(drm_dp_mst_detect_port); + +/** + * drm_dp_mst_get_edid() - get EDID for an MST port + * @connector: toplevel connector to get EDID for + * @mgr: manager for this port + * @port: unverified pointer to a port. + * + * This returns an EDID for the port connected to a connector, + * It validates the pointer still exists so the caller doesn't require a + * reference. + */ +struct edid *drm_dp_mst_get_edid(struct drm_connector *connector, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) +{ + struct edid *edid = NULL; + + /* we need to search for the port in the mgr in case it's gone */ + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return NULL; + + if (port->cached_edid) + edid = drm_edid_duplicate(port->cached_edid); + else { + edid = drm_get_edid(connector, &port->aux.ddc); + } + port->has_audio = drm_detect_monitor_audio(edid); + drm_dp_mst_topology_put_port(port); + return edid; +} +EXPORT_SYMBOL(drm_dp_mst_get_edid); + +/** + * drm_dp_find_vcpi_slots() - Find VCPI slots for this PBN value + * @mgr: manager to use + * @pbn: payload bandwidth to convert into slots. + * + * Calculate the number of VCPI slots that will be required for the given PBN + * value. This function is deprecated, and should not be used in atomic + * drivers. + * + * RETURNS: + * The total slots required for this port, or error. + */ +int drm_dp_find_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, + int pbn) +{ + int num_slots; + + num_slots = DIV_ROUND_UP(pbn, mgr->pbn_div); + + /* max. time slots - one slot for MTP header */ + if (num_slots > 63) + return -ENOSPC; + return num_slots; +} +EXPORT_SYMBOL(drm_dp_find_vcpi_slots); + +static int drm_dp_init_vcpi(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_vcpi *vcpi, int pbn, int slots) +{ + int ret; + + vcpi->pbn = pbn; + vcpi->aligned_pbn = slots * mgr->pbn_div; + vcpi->num_slots = slots; + + ret = drm_dp_mst_assign_payload_id(mgr, vcpi); + if (ret < 0) + return ret; + return 0; +} + +/** + * drm_dp_atomic_find_vcpi_slots() - Find and add VCPI slots to the state + * @state: global atomic state + * @mgr: MST topology manager for the port + * @port: port to find vcpi slots for + * @pbn: bandwidth required for the mode in PBN + * @pbn_div: divider for DSC mode that takes FEC into account + * + * Allocates VCPI slots to @port, replacing any previous VCPI allocations it + * may have had. Any atomic drivers which support MST must call this function + * in their &drm_encoder_helper_funcs.atomic_check() callback to change the + * current VCPI allocation for the new state, but only when + * &drm_crtc_state.mode_changed or &drm_crtc_state.connectors_changed is set + * to ensure compatibility with userspace applications that still use the + * legacy modesetting UAPI. + * + * Allocations set by this function are not checked against the bandwidth + * restraints of @mgr until the driver calls drm_dp_mst_atomic_check(). + * + * Additionally, it is OK to call this function multiple times on the same + * @port as needed. It is not OK however, to call this function and + * drm_dp_atomic_release_vcpi_slots() in the same atomic check phase. + * + * See also: + * drm_dp_atomic_release_vcpi_slots() + * drm_dp_mst_atomic_check() + * + * Returns: + * Total slots in the atomic state assigned for this port, or a negative error + * code if the port no longer exists + */ +int drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, int pbn, + int pbn_div) +{ + struct drm_dp_mst_topology_state *topology_state; + struct drm_dp_vcpi_allocation *pos, *vcpi = NULL; + int prev_slots, prev_bw, req_slots; + + topology_state = drm_atomic_get_mst_topology_state(state, mgr); + if (IS_ERR(topology_state)) + return PTR_ERR(topology_state); + + /* Find the current allocation for this port, if any */ + list_for_each_entry(pos, &topology_state->vcpis, next) { + if (pos->port == port) { + vcpi = pos; + prev_slots = vcpi->vcpi; + prev_bw = vcpi->pbn; + + /* + * This should never happen, unless the driver tries + * releasing and allocating the same VCPI allocation, + * which is an error + */ + if (WARN_ON(!prev_slots)) { + drm_err(mgr->dev, + "cannot allocate and release VCPI on [MST PORT:%p] in the same state\n", + port); + return -EINVAL; + } + + break; + } + } + if (!vcpi) { + prev_slots = 0; + prev_bw = 0; + } + + if (pbn_div <= 0) + pbn_div = mgr->pbn_div; + + req_slots = DIV_ROUND_UP(pbn, pbn_div); + + drm_dbg_atomic(mgr->dev, "[CONNECTOR:%d:%s] [MST PORT:%p] VCPI %d -> %d\n", + port->connector->base.id, port->connector->name, + port, prev_slots, req_slots); + drm_dbg_atomic(mgr->dev, "[CONNECTOR:%d:%s] [MST PORT:%p] PBN %d -> %d\n", + port->connector->base.id, port->connector->name, + port, prev_bw, pbn); + + /* Add the new allocation to the state */ + if (!vcpi) { + vcpi = kzalloc(sizeof(*vcpi), GFP_KERNEL); + if (!vcpi) + return -ENOMEM; + + drm_dp_mst_get_port_malloc(port); + vcpi->port = port; + list_add(&vcpi->next, &topology_state->vcpis); + } + vcpi->vcpi = req_slots; + vcpi->pbn = pbn; + + return req_slots; +} +EXPORT_SYMBOL(drm_dp_atomic_find_vcpi_slots); + +/** + * drm_dp_atomic_release_vcpi_slots() - Release allocated vcpi slots + * @state: global atomic state + * @mgr: MST topology manager for the port + * @port: The port to release the VCPI slots from + * + * Releases any VCPI slots that have been allocated to a port in the atomic + * state. Any atomic drivers which support MST must call this function in + * their &drm_connector_helper_funcs.atomic_check() callback when the + * connector will no longer have VCPI allocated (e.g. because its CRTC was + * removed) when it had VCPI allocated in the previous atomic state. + * + * It is OK to call this even if @port has been removed from the system. + * Additionally, it is OK to call this function multiple times on the same + * @port as needed. It is not OK however, to call this function and + * drm_dp_atomic_find_vcpi_slots() on the same @port in a single atomic check + * phase. + * + * See also: + * drm_dp_atomic_find_vcpi_slots() + * drm_dp_mst_atomic_check() + * + * Returns: + * 0 if all slots for this port were added back to + * &drm_dp_mst_topology_state.avail_slots or negative error code + */ +int drm_dp_atomic_release_vcpi_slots(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ + struct drm_dp_mst_topology_state *topology_state; + struct drm_dp_vcpi_allocation *pos; + bool found = false; + + topology_state = drm_atomic_get_mst_topology_state(state, mgr); + if (IS_ERR(topology_state)) + return PTR_ERR(topology_state); + + list_for_each_entry(pos, &topology_state->vcpis, next) { + if (pos->port == port) { + found = true; + break; + } + } + if (WARN_ON(!found)) { + drm_err(mgr->dev, "no VCPI for [MST PORT:%p] found in mst state %p\n", + port, &topology_state->base); + return -EINVAL; + } + + drm_dbg_atomic(mgr->dev, "[MST PORT:%p] VCPI %d -> 0\n", port, pos->vcpi); + if (pos->vcpi) { + drm_dp_mst_put_port_malloc(port); + pos->vcpi = 0; + pos->pbn = 0; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_atomic_release_vcpi_slots); + +/** + * drm_dp_mst_update_slots() - updates the slot info depending on the DP ecoding format + * @mst_state: mst_state to update + * @link_encoding_cap: the ecoding format on the link + */ +void drm_dp_mst_update_slots(struct drm_dp_mst_topology_state *mst_state, uint8_t link_encoding_cap) +{ + if (link_encoding_cap == DP_CAP_ANSI_128B132B) { + mst_state->total_avail_slots = 64; + mst_state->start_slot = 0; + } else { + mst_state->total_avail_slots = 63; + mst_state->start_slot = 1; + } + + DRM_DEBUG_KMS("%s encoding format on mst_state 0x%p\n", + (link_encoding_cap == DP_CAP_ANSI_128B132B) ? "128b/132b":"8b/10b", + mst_state); +} +EXPORT_SYMBOL(drm_dp_mst_update_slots); + +/** + * drm_dp_mst_allocate_vcpi() - Allocate a virtual channel + * @mgr: manager for this port + * @port: port to allocate a virtual channel for. + * @pbn: payload bandwidth number to request + * @slots: returned number of slots for this PBN. + */ +bool drm_dp_mst_allocate_vcpi(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, int pbn, int slots) +{ + int ret; + + if (slots < 0) + return false; + + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return false; + + if (port->vcpi.vcpi > 0) { + drm_dbg_kms(mgr->dev, + "payload: vcpi %d already allocated for pbn %d - requested pbn %d\n", + port->vcpi.vcpi, port->vcpi.pbn, pbn); + if (pbn == port->vcpi.pbn) { + drm_dp_mst_topology_put_port(port); + return true; + } + } + + ret = drm_dp_init_vcpi(mgr, &port->vcpi, pbn, slots); + if (ret) { + drm_dbg_kms(mgr->dev, "failed to init vcpi slots=%d ret=%d\n", + DIV_ROUND_UP(pbn, mgr->pbn_div), ret); + drm_dp_mst_topology_put_port(port); + goto out; + } + drm_dbg_kms(mgr->dev, "initing vcpi for pbn=%d slots=%d\n", pbn, port->vcpi.num_slots); + + /* Keep port allocated until its payload has been removed */ + drm_dp_mst_get_port_malloc(port); + drm_dp_mst_topology_put_port(port); + return true; +out: + return false; +} +EXPORT_SYMBOL(drm_dp_mst_allocate_vcpi); + +int drm_dp_mst_get_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) +{ + int slots = 0; + + port = drm_dp_mst_topology_get_port_validated(mgr, port); + if (!port) + return slots; + + slots = port->vcpi.num_slots; + drm_dp_mst_topology_put_port(port); + return slots; +} +EXPORT_SYMBOL(drm_dp_mst_get_vcpi_slots); + +/** + * drm_dp_mst_reset_vcpi_slots() - Reset number of slots to 0 for VCPI + * @mgr: manager for this port + * @port: unverified pointer to a port. + * + * This just resets the number of slots for the ports VCPI for later programming. + */ +void drm_dp_mst_reset_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) +{ + /* + * A port with VCPI will remain allocated until its VCPI is + * released, no verified ref needed + */ + + port->vcpi.num_slots = 0; +} +EXPORT_SYMBOL(drm_dp_mst_reset_vcpi_slots); + +/** + * drm_dp_mst_deallocate_vcpi() - deallocate a VCPI + * @mgr: manager for this port + * @port: port to deallocate vcpi for + * + * This can be called unconditionally, regardless of whether + * drm_dp_mst_allocate_vcpi() succeeded or not. + */ +void drm_dp_mst_deallocate_vcpi(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ + bool skip; + + if (!port->vcpi.vcpi) + return; + + mutex_lock(&mgr->lock); + skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); + mutex_unlock(&mgr->lock); + + if (skip) + return; + + drm_dp_mst_put_payload_id(mgr, port->vcpi.vcpi); + port->vcpi.num_slots = 0; + port->vcpi.pbn = 0; + port->vcpi.aligned_pbn = 0; + port->vcpi.vcpi = 0; + drm_dp_mst_put_port_malloc(port); +} +EXPORT_SYMBOL(drm_dp_mst_deallocate_vcpi); + +static int drm_dp_dpcd_write_payload(struct drm_dp_mst_topology_mgr *mgr, + int id, struct drm_dp_payload *payload) +{ + u8 payload_alloc[3], status; + int ret; + int retries = 0; + + drm_dp_dpcd_writeb(mgr->aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, + DP_PAYLOAD_TABLE_UPDATED); + + payload_alloc[0] = id; + payload_alloc[1] = payload->start_slot; + payload_alloc[2] = payload->num_slots; + + ret = drm_dp_dpcd_write(mgr->aux, DP_PAYLOAD_ALLOCATE_SET, payload_alloc, 3); + if (ret != 3) { + drm_dbg_kms(mgr->dev, "failed to write payload allocation %d\n", ret); + goto fail; + } + +retry: + ret = drm_dp_dpcd_readb(mgr->aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status); + if (ret < 0) { + drm_dbg_kms(mgr->dev, "failed to read payload table status %d\n", ret); + goto fail; + } + + if (!(status & DP_PAYLOAD_TABLE_UPDATED)) { + retries++; + if (retries < 20) { + usleep_range(10000, 20000); + goto retry; + } + drm_dbg_kms(mgr->dev, "status not set after read payload table status %d\n", + status); + ret = -EINVAL; + goto fail; + } + ret = 0; +fail: + return ret; +} + +static int do_get_act_status(struct drm_dp_aux *aux) +{ + int ret; + u8 status; + + ret = drm_dp_dpcd_readb(aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status); + if (ret < 0) + return ret; + + return status; +} + +/** + * drm_dp_check_act_status() - Polls for ACT handled status. + * @mgr: manager to use + * + * Tries waiting for the MST hub to finish updating it's payload table by + * polling for the ACT handled bit for up to 3 seconds (yes-some hubs really + * take that long). + * + * Returns: + * 0 if the ACT was handled in time, negative error code on failure. + */ +int drm_dp_check_act_status(struct drm_dp_mst_topology_mgr *mgr) +{ + /* + * There doesn't seem to be any recommended retry count or timeout in + * the MST specification. Since some hubs have been observed to take + * over 1 second to update their payload allocations under certain + * conditions, we use a rather large timeout value. + */ + const int timeout_ms = 3000; + int ret, status; + + ret = readx_poll_timeout(do_get_act_status, mgr->aux, status, + status & DP_PAYLOAD_ACT_HANDLED || status < 0, + 200, timeout_ms * USEC_PER_MSEC); + if (ret < 0 && status >= 0) { + drm_err(mgr->dev, "Failed to get ACT after %dms, last status: %02x\n", + timeout_ms, status); + return -EINVAL; + } else if (status < 0) { + /* + * Failure here isn't unexpected - the hub may have + * just been unplugged + */ + drm_dbg_kms(mgr->dev, "Failed to read payload table status: %d\n", status); + return status; + } + + return 0; +} +EXPORT_SYMBOL(drm_dp_check_act_status); + +/** + * drm_dp_calc_pbn_mode() - Calculate the PBN for a mode. + * @clock: dot clock for the mode + * @bpp: bpp for the mode. + * @dsc: DSC mode. If true, bpp has units of 1/16 of a bit per pixel + * + * This uses the formula in the spec to calculate the PBN value for a mode. + */ +int drm_dp_calc_pbn_mode(int clock, int bpp, bool dsc) +{ + /* + * margin 5300ppm + 300ppm ~ 0.6% as per spec, factor is 1.006 + * The unit of 54/64Mbytes/sec is an arbitrary unit chosen based on + * common multiplier to render an integer PBN for all link rate/lane + * counts combinations + * calculate + * peak_kbps *= (1006/1000) + * peak_kbps *= (64/54) + * peak_kbps *= 8 convert to bytes + * + * If the bpp is in units of 1/16, further divide by 16. Put this + * factor in the numerator rather than the denominator to avoid + * integer overflow + */ + + if (dsc) + return DIV_ROUND_UP_ULL(mul_u32_u32(clock * (bpp / 16), 64 * 1006), + 8 * 54 * 1000 * 1000); + + return DIV_ROUND_UP_ULL(mul_u32_u32(clock * bpp, 64 * 1006), + 8 * 54 * 1000 * 1000); +} +EXPORT_SYMBOL(drm_dp_calc_pbn_mode); + +/* we want to kick the TX after we've ack the up/down IRQs. */ +static void drm_dp_mst_kick_tx(struct drm_dp_mst_topology_mgr *mgr) +{ + queue_work(system_long_wq, &mgr->tx_work); +} + +/* + * Helper function for parsing DP device types into convenient strings + * for use with dp_mst_topology + */ +static const char *pdt_to_string(u8 pdt) +{ + switch (pdt) { + case DP_PEER_DEVICE_NONE: + return "NONE"; + case DP_PEER_DEVICE_SOURCE_OR_SST: + return "SOURCE OR SST"; + case DP_PEER_DEVICE_MST_BRANCHING: + return "MST BRANCHING"; + case DP_PEER_DEVICE_SST_SINK: + return "SST SINK"; + case DP_PEER_DEVICE_DP_LEGACY_CONV: + return "DP LEGACY CONV"; + default: + return "ERR"; + } +} + +static void drm_dp_mst_dump_mstb(struct seq_file *m, + struct drm_dp_mst_branch *mstb) +{ + struct drm_dp_mst_port *port; + int tabs = mstb->lct; + char prefix[10]; + int i; + + for (i = 0; i < tabs; i++) + prefix[i] = '\t'; + prefix[i] = '\0'; + + seq_printf(m, "%smstb - [%p]: num_ports: %d\n", prefix, mstb, mstb->num_ports); + list_for_each_entry(port, &mstb->ports, next) { + seq_printf(m, "%sport %d - [%p] (%s - %s): ddps: %d, ldps: %d, sdp: %d/%d, fec: %s, conn: %p\n", + prefix, + port->port_num, + port, + port->input ? "input" : "output", + pdt_to_string(port->pdt), + port->ddps, + port->ldps, + port->num_sdp_streams, + port->num_sdp_stream_sinks, + port->fec_capable ? "true" : "false", + port->connector); + if (port->mstb) + drm_dp_mst_dump_mstb(m, port->mstb); + } +} + +#define DP_PAYLOAD_TABLE_SIZE 64 + +static bool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr, + char *buf) +{ + int i; + + for (i = 0; i < DP_PAYLOAD_TABLE_SIZE; i += 16) { + if (drm_dp_dpcd_read(mgr->aux, + DP_PAYLOAD_TABLE_UPDATE_STATUS + i, + &buf[i], 16) != 16) + return false; + } + return true; +} + +static void fetch_monitor_name(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, char *name, + int namelen) +{ + struct edid *mst_edid; + + mst_edid = drm_dp_mst_get_edid(port->connector, mgr, port); + drm_edid_get_monitor_name(mst_edid, name, namelen); +} + +/** + * drm_dp_mst_dump_topology(): dump topology to seq file. + * @m: seq_file to dump output to + * @mgr: manager to dump current topology for. + * + * helper to dump MST topology to a seq file for debugfs. + */ +void drm_dp_mst_dump_topology(struct seq_file *m, + struct drm_dp_mst_topology_mgr *mgr) +{ + int i; + struct drm_dp_mst_port *port; + + mutex_lock(&mgr->lock); + if (mgr->mst_primary) + drm_dp_mst_dump_mstb(m, mgr->mst_primary); + + /* dump VCPIs */ + mutex_unlock(&mgr->lock); + + mutex_lock(&mgr->payload_lock); + seq_printf(m, "\n*** VCPI Info ***\n"); + seq_printf(m, "payload_mask: %lx, vcpi_mask: %lx, max_payloads: %d\n", mgr->payload_mask, mgr->vcpi_mask, mgr->max_payloads); + + seq_printf(m, "\n| idx | port # | vcp_id | # slots | sink name |\n"); + for (i = 0; i < mgr->max_payloads; i++) { + if (mgr->proposed_vcpis[i]) { + char name[14]; + + port = container_of(mgr->proposed_vcpis[i], struct drm_dp_mst_port, vcpi); + fetch_monitor_name(mgr, port, name, sizeof(name)); + seq_printf(m, "%10d%10d%10d%10d%20s\n", + i, + port->port_num, + port->vcpi.vcpi, + port->vcpi.num_slots, + (*name != 0) ? name : "Unknown"); + } else + seq_printf(m, "%6d - Unused\n", i); + } + seq_printf(m, "\n*** Payload Info ***\n"); + seq_printf(m, "| idx | state | start slot | # slots |\n"); + for (i = 0; i < mgr->max_payloads; i++) { + seq_printf(m, "%10d%10d%15d%10d\n", + i, + mgr->payloads[i].payload_state, + mgr->payloads[i].start_slot, + mgr->payloads[i].num_slots); + } + mutex_unlock(&mgr->payload_lock); + + seq_printf(m, "\n*** DPCD Info ***\n"); + mutex_lock(&mgr->lock); + if (mgr->mst_primary) { + u8 buf[DP_PAYLOAD_TABLE_SIZE]; + int ret; + + ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, buf, DP_RECEIVER_CAP_SIZE); + if (ret) { + seq_printf(m, "dpcd read failed\n"); + goto out; + } + seq_printf(m, "dpcd: %*ph\n", DP_RECEIVER_CAP_SIZE, buf); + + ret = drm_dp_dpcd_read(mgr->aux, DP_FAUX_CAP, buf, 2); + if (ret) { + seq_printf(m, "faux/mst read failed\n"); + goto out; + } + seq_printf(m, "faux/mst: %*ph\n", 2, buf); + + ret = drm_dp_dpcd_read(mgr->aux, DP_MSTM_CTRL, buf, 1); + if (ret) { + seq_printf(m, "mst ctrl read failed\n"); + goto out; + } + seq_printf(m, "mst ctrl: %*ph\n", 1, buf); + + /* dump the standard OUI branch header */ + ret = drm_dp_dpcd_read(mgr->aux, DP_BRANCH_OUI, buf, DP_BRANCH_OUI_HEADER_SIZE); + if (ret) { + seq_printf(m, "branch oui read failed\n"); + goto out; + } + seq_printf(m, "branch oui: %*phN devid: ", 3, buf); + + for (i = 0x3; i < 0x8 && buf[i]; i++) + seq_printf(m, "%c", buf[i]); + seq_printf(m, " revision: hw: %x.%x sw: %x.%x\n", + buf[0x9] >> 4, buf[0x9] & 0xf, buf[0xa], buf[0xb]); + if (dump_dp_payload_table(mgr, buf)) + seq_printf(m, "payload table: %*ph\n", DP_PAYLOAD_TABLE_SIZE, buf); + } + +out: + mutex_unlock(&mgr->lock); + +} +EXPORT_SYMBOL(drm_dp_mst_dump_topology); + +static void drm_dp_tx_work(struct work_struct *work) +{ + struct drm_dp_mst_topology_mgr *mgr = container_of(work, struct drm_dp_mst_topology_mgr, tx_work); + + mutex_lock(&mgr->qlock); + if (!list_empty(&mgr->tx_msg_downq)) + process_single_down_tx_qlock(mgr); + mutex_unlock(&mgr->qlock); +} + +static inline void +drm_dp_delayed_destroy_port(struct drm_dp_mst_port *port) +{ + drm_dp_port_set_pdt(port, DP_PEER_DEVICE_NONE, port->mcs); + + if (port->connector) { + drm_connector_unregister(port->connector); + drm_connector_put(port->connector); + } + + drm_dp_mst_put_port_malloc(port); +} + +static inline void +drm_dp_delayed_destroy_mstb(struct drm_dp_mst_branch *mstb) +{ + struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; + struct drm_dp_mst_port *port, *port_tmp; + struct drm_dp_sideband_msg_tx *txmsg, *txmsg_tmp; + bool wake_tx = false; + + mutex_lock(&mgr->lock); + list_for_each_entry_safe(port, port_tmp, &mstb->ports, next) { + list_del(&port->next); + drm_dp_mst_topology_put_port(port); + } + mutex_unlock(&mgr->lock); + + /* drop any tx slot msg */ + mutex_lock(&mstb->mgr->qlock); + list_for_each_entry_safe(txmsg, txmsg_tmp, &mgr->tx_msg_downq, next) { + if (txmsg->dst != mstb) + continue; + + txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT; + list_del(&txmsg->next); + wake_tx = true; + } + mutex_unlock(&mstb->mgr->qlock); + + if (wake_tx) + wake_up_all(&mstb->mgr->tx_waitq); + + drm_dp_mst_put_mstb_malloc(mstb); +} + +static void drm_dp_delayed_destroy_work(struct work_struct *work) +{ + struct drm_dp_mst_topology_mgr *mgr = + container_of(work, struct drm_dp_mst_topology_mgr, + delayed_destroy_work); + bool send_hotplug = false, go_again; + + /* + * Not a regular list traverse as we have to drop the destroy + * connector lock before destroying the mstb/port, to avoid AB->BA + * ordering between this lock and the config mutex. + */ + do { + go_again = false; + + for (;;) { + struct drm_dp_mst_branch *mstb; + + mutex_lock(&mgr->delayed_destroy_lock); + mstb = list_first_entry_or_null(&mgr->destroy_branch_device_list, + struct drm_dp_mst_branch, + destroy_next); + if (mstb) + list_del(&mstb->destroy_next); + mutex_unlock(&mgr->delayed_destroy_lock); + + if (!mstb) + break; + + drm_dp_delayed_destroy_mstb(mstb); + go_again = true; + } + + for (;;) { + struct drm_dp_mst_port *port; + + mutex_lock(&mgr->delayed_destroy_lock); + port = list_first_entry_or_null(&mgr->destroy_port_list, + struct drm_dp_mst_port, + next); + if (port) + list_del(&port->next); + mutex_unlock(&mgr->delayed_destroy_lock); + + if (!port) + break; + + drm_dp_delayed_destroy_port(port); + send_hotplug = true; + go_again = true; + } + } while (go_again); + + if (send_hotplug) + drm_kms_helper_hotplug_event(mgr->dev); +} + +static struct drm_private_state * +drm_dp_mst_duplicate_state(struct drm_private_obj *obj) +{ + struct drm_dp_mst_topology_state *state, *old_state = + to_dp_mst_topology_state(obj->state); + struct drm_dp_vcpi_allocation *pos, *vcpi; + + state = kmemdup(old_state, sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base); + + INIT_LIST_HEAD(&state->vcpis); + + list_for_each_entry(pos, &old_state->vcpis, next) { + /* Prune leftover freed VCPI allocations */ + if (!pos->vcpi) + continue; + + vcpi = kmemdup(pos, sizeof(*vcpi), GFP_KERNEL); + if (!vcpi) + goto fail; + + drm_dp_mst_get_port_malloc(vcpi->port); + list_add(&vcpi->next, &state->vcpis); + } + + return &state->base; + +fail: + list_for_each_entry_safe(pos, vcpi, &state->vcpis, next) { + drm_dp_mst_put_port_malloc(pos->port); + kfree(pos); + } + kfree(state); + + return NULL; +} + +static void drm_dp_mst_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) +{ + struct drm_dp_mst_topology_state *mst_state = + to_dp_mst_topology_state(state); + struct drm_dp_vcpi_allocation *pos, *tmp; + + list_for_each_entry_safe(pos, tmp, &mst_state->vcpis, next) { + /* We only keep references to ports with non-zero VCPIs */ + if (pos->vcpi) + drm_dp_mst_put_port_malloc(pos->port); + kfree(pos); + } + + kfree(mst_state); +} + +static bool drm_dp_mst_port_downstream_of_branch(struct drm_dp_mst_port *port, + struct drm_dp_mst_branch *branch) +{ + while (port->parent) { + if (port->parent == branch) + return true; + + if (port->parent->port_parent) + port = port->parent->port_parent; + else + break; + } + return false; +} + +static int +drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port, + struct drm_dp_mst_topology_state *state); + +static int +drm_dp_mst_atomic_check_mstb_bw_limit(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_topology_state *state) +{ + struct drm_dp_vcpi_allocation *vcpi; + struct drm_dp_mst_port *port; + int pbn_used = 0, ret; + bool found = false; + + /* Check that we have at least one port in our state that's downstream + * of this branch, otherwise we can skip this branch + */ + list_for_each_entry(vcpi, &state->vcpis, next) { + if (!vcpi->pbn || + !drm_dp_mst_port_downstream_of_branch(vcpi->port, mstb)) + continue; + + found = true; + break; + } + if (!found) + return 0; + + if (mstb->port_parent) + drm_dbg_atomic(mstb->mgr->dev, + "[MSTB:%p] [MST PORT:%p] Checking bandwidth limits on [MSTB:%p]\n", + mstb->port_parent->parent, mstb->port_parent, mstb); + else + drm_dbg_atomic(mstb->mgr->dev, "[MSTB:%p] Checking bandwidth limits\n", mstb); + + list_for_each_entry(port, &mstb->ports, next) { + ret = drm_dp_mst_atomic_check_port_bw_limit(port, state); + if (ret < 0) + return ret; + + pbn_used += ret; + } + + return pbn_used; +} + +static int +drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port, + struct drm_dp_mst_topology_state *state) +{ + struct drm_dp_vcpi_allocation *vcpi; + int pbn_used = 0; + + if (port->pdt == DP_PEER_DEVICE_NONE) + return 0; + + if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { + bool found = false; + + list_for_each_entry(vcpi, &state->vcpis, next) { + if (vcpi->port != port) + continue; + if (!vcpi->pbn) + return 0; + + found = true; + break; + } + if (!found) + return 0; + + /* + * This could happen if the sink deasserted its HPD line, but + * the branch device still reports it as attached (PDT != NONE). + */ + if (!port->full_pbn) { + drm_dbg_atomic(port->mgr->dev, + "[MSTB:%p] [MST PORT:%p] no BW available for the port\n", + port->parent, port); + return -EINVAL; + } + + pbn_used = vcpi->pbn; + } else { + pbn_used = drm_dp_mst_atomic_check_mstb_bw_limit(port->mstb, + state); + if (pbn_used <= 0) + return pbn_used; + } + + if (pbn_used > port->full_pbn) { + drm_dbg_atomic(port->mgr->dev, + "[MSTB:%p] [MST PORT:%p] required PBN of %d exceeds port limit of %d\n", + port->parent, port, pbn_used, port->full_pbn); + return -ENOSPC; + } + + drm_dbg_atomic(port->mgr->dev, "[MSTB:%p] [MST PORT:%p] uses %d out of %d PBN\n", + port->parent, port, pbn_used, port->full_pbn); + + return pbn_used; +} + +static inline int +drm_dp_mst_atomic_check_vcpi_alloc_limit(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_topology_state *mst_state) +{ + struct drm_dp_vcpi_allocation *vcpi; + int avail_slots = mst_state->total_avail_slots, payload_count = 0; + + list_for_each_entry(vcpi, &mst_state->vcpis, next) { + /* Releasing VCPI is always OK-even if the port is gone */ + if (!vcpi->vcpi) { + drm_dbg_atomic(mgr->dev, "[MST PORT:%p] releases all VCPI slots\n", + vcpi->port); + continue; + } + + drm_dbg_atomic(mgr->dev, "[MST PORT:%p] requires %d vcpi slots\n", + vcpi->port, vcpi->vcpi); + + avail_slots -= vcpi->vcpi; + if (avail_slots < 0) { + drm_dbg_atomic(mgr->dev, + "[MST PORT:%p] not enough VCPI slots in mst state %p (avail=%d)\n", + vcpi->port, mst_state, avail_slots + vcpi->vcpi); + return -ENOSPC; + } + + if (++payload_count > mgr->max_payloads) { + drm_dbg_atomic(mgr->dev, + "[MST MGR:%p] state %p has too many payloads (max=%d)\n", + mgr, mst_state, mgr->max_payloads); + return -EINVAL; + } + } + drm_dbg_atomic(mgr->dev, "[MST MGR:%p] mst state %p VCPI avail=%d used=%d\n", + mgr, mst_state, avail_slots, mst_state->total_avail_slots - avail_slots); + + return 0; +} + +/** + * drm_dp_mst_add_affected_dsc_crtcs + * @state: Pointer to the new struct drm_dp_mst_topology_state + * @mgr: MST topology manager + * + * Whenever there is a change in mst topology + * DSC configuration would have to be recalculated + * therefore we need to trigger modeset on all affected + * CRTCs in that topology + * + * See also: + * drm_dp_mst_atomic_enable_dsc() + */ +int drm_dp_mst_add_affected_dsc_crtcs(struct drm_atomic_state *state, struct drm_dp_mst_topology_mgr *mgr) +{ + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_vcpi_allocation *pos; + struct drm_connector *connector; + struct drm_connector_state *conn_state; + struct drm_crtc *crtc; + struct drm_crtc_state *crtc_state; + + mst_state = drm_atomic_get_mst_topology_state(state, mgr); + + if (IS_ERR(mst_state)) + return -EINVAL; + + list_for_each_entry(pos, &mst_state->vcpis, next) { + + connector = pos->port->connector; + + if (!connector) + return -EINVAL; + + conn_state = drm_atomic_get_connector_state(state, connector); + + if (IS_ERR(conn_state)) + return PTR_ERR(conn_state); + + crtc = conn_state->crtc; + + if (!crtc) + continue; + + if (!drm_dp_mst_dsc_aux_for_port(pos->port)) + continue; + + crtc_state = drm_atomic_get_crtc_state(mst_state->base.state, crtc); + + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + drm_dbg_atomic(mgr->dev, "[MST MGR:%p] Setting mode_changed flag on CRTC %p\n", + mgr, crtc); + + crtc_state->mode_changed = true; + } + return 0; +} +EXPORT_SYMBOL(drm_dp_mst_add_affected_dsc_crtcs); + +/** + * drm_dp_mst_atomic_enable_dsc - Set DSC Enable Flag to On/Off + * @state: Pointer to the new drm_atomic_state + * @port: Pointer to the affected MST Port + * @pbn: Newly recalculated bw required for link with DSC enabled + * @pbn_div: Divider to calculate correct number of pbn per slot + * @enable: Boolean flag to enable or disable DSC on the port + * + * This function enables DSC on the given Port + * by recalculating its vcpi from pbn provided + * and sets dsc_enable flag to keep track of which + * ports have DSC enabled + * + */ +int drm_dp_mst_atomic_enable_dsc(struct drm_atomic_state *state, + struct drm_dp_mst_port *port, + int pbn, int pbn_div, + bool enable) +{ + struct drm_dp_mst_topology_state *mst_state; + struct drm_dp_vcpi_allocation *pos; + bool found = false; + int vcpi = 0; + + mst_state = drm_atomic_get_mst_topology_state(state, port->mgr); + + if (IS_ERR(mst_state)) + return PTR_ERR(mst_state); + + list_for_each_entry(pos, &mst_state->vcpis, next) { + if (pos->port == port) { + found = true; + break; + } + } + + if (!found) { + drm_dbg_atomic(state->dev, + "[MST PORT:%p] Couldn't find VCPI allocation in mst state %p\n", + port, mst_state); + return -EINVAL; + } + + if (pos->dsc_enabled == enable) { + drm_dbg_atomic(state->dev, + "[MST PORT:%p] DSC flag is already set to %d, returning %d VCPI slots\n", + port, enable, pos->vcpi); + vcpi = pos->vcpi; + } + + if (enable) { + vcpi = drm_dp_atomic_find_vcpi_slots(state, port->mgr, port, pbn, pbn_div); + drm_dbg_atomic(state->dev, + "[MST PORT:%p] Enabling DSC flag, reallocating %d VCPI slots on the port\n", + port, vcpi); + if (vcpi < 0) + return -EINVAL; + } + + pos->dsc_enabled = enable; + + return vcpi; +} +EXPORT_SYMBOL(drm_dp_mst_atomic_enable_dsc); +/** + * drm_dp_mst_atomic_check - Check that the new state of an MST topology in an + * atomic update is valid + * @state: Pointer to the new &struct drm_dp_mst_topology_state + * + * Checks the given topology state for an atomic update to ensure that it's + * valid. This includes checking whether there's enough bandwidth to support + * the new VCPI allocations in the atomic update. + * + * Any atomic drivers supporting DP MST must make sure to call this after + * checking the rest of their state in their + * &drm_mode_config_funcs.atomic_check() callback. + * + * See also: + * drm_dp_atomic_find_vcpi_slots() + * drm_dp_atomic_release_vcpi_slots() + * + * Returns: + * + * 0 if the new state is valid, negative error code otherwise. + */ +int drm_dp_mst_atomic_check(struct drm_atomic_state *state) +{ + struct drm_dp_mst_topology_mgr *mgr; + struct drm_dp_mst_topology_state *mst_state; + int i, ret = 0; + + for_each_new_mst_mgr_in_state(state, mgr, mst_state, i) { + if (!mgr->mst_state) + continue; + + ret = drm_dp_mst_atomic_check_vcpi_alloc_limit(mgr, mst_state); + if (ret) + break; + + mutex_lock(&mgr->lock); + ret = drm_dp_mst_atomic_check_mstb_bw_limit(mgr->mst_primary, + mst_state); + mutex_unlock(&mgr->lock); + if (ret < 0) + break; + else + ret = 0; + } + + return ret; +} +EXPORT_SYMBOL(drm_dp_mst_atomic_check); + +const struct drm_private_state_funcs drm_dp_mst_topology_state_funcs = { + .atomic_duplicate_state = drm_dp_mst_duplicate_state, + .atomic_destroy_state = drm_dp_mst_destroy_state, +}; +EXPORT_SYMBOL(drm_dp_mst_topology_state_funcs); + +/** + * drm_atomic_get_mst_topology_state: get MST topology state + * + * @state: global atomic state + * @mgr: MST topology manager, also the private object in this case + * + * This function wraps drm_atomic_get_priv_obj_state() passing in the MST atomic + * state vtable so that the private object state returned is that of a MST + * topology object. Also, drm_atomic_get_private_obj_state() expects the caller + * to care of the locking, so warn if don't hold the connection_mutex. + * + * RETURNS: + * + * The MST topology state or error pointer. + */ +struct drm_dp_mst_topology_state *drm_atomic_get_mst_topology_state(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr) +{ + return to_dp_mst_topology_state(drm_atomic_get_private_obj_state(state, &mgr->base)); +} +EXPORT_SYMBOL(drm_atomic_get_mst_topology_state); + +/** + * drm_dp_mst_topology_mgr_init - initialise a topology manager + * @mgr: manager struct to initialise + * @dev: device providing this structure - for i2c addition. + * @aux: DP helper aux channel to talk to this device + * @max_dpcd_transaction_bytes: hw specific DPCD transaction limit + * @max_payloads: maximum number of payloads this GPU can source + * @max_lane_count: maximum number of lanes this GPU supports + * @max_link_rate: maximum link rate per lane this GPU supports in kHz + * @conn_base_id: the connector object ID the MST device is connected to. + * + * Return 0 for success, or negative error code on failure + */ +int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr, + struct drm_device *dev, struct drm_dp_aux *aux, + int max_dpcd_transaction_bytes, int max_payloads, + int max_lane_count, int max_link_rate, + int conn_base_id) +{ + struct drm_dp_mst_topology_state *mst_state; + + mutex_init(&mgr->lock); + mutex_init(&mgr->qlock); + mutex_init(&mgr->payload_lock); + mutex_init(&mgr->delayed_destroy_lock); + mutex_init(&mgr->up_req_lock); + mutex_init(&mgr->probe_lock); +#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) + mutex_init(&mgr->topology_ref_history_lock); + stack_depot_init(); +#endif + INIT_LIST_HEAD(&mgr->tx_msg_downq); + INIT_LIST_HEAD(&mgr->destroy_port_list); + INIT_LIST_HEAD(&mgr->destroy_branch_device_list); + INIT_LIST_HEAD(&mgr->up_req_list); + + /* + * delayed_destroy_work will be queued on a dedicated WQ, so that any + * requeuing will be also flushed when deiniting the topology manager. + */ + mgr->delayed_destroy_wq = alloc_ordered_workqueue("drm_dp_mst_wq", 0); + if (mgr->delayed_destroy_wq == NULL) + return -ENOMEM; + + INIT_WORK(&mgr->work, drm_dp_mst_link_probe_work); + INIT_WORK(&mgr->tx_work, drm_dp_tx_work); + INIT_WORK(&mgr->delayed_destroy_work, drm_dp_delayed_destroy_work); + INIT_WORK(&mgr->up_req_work, drm_dp_mst_up_req_work); + init_waitqueue_head(&mgr->tx_waitq); + mgr->dev = dev; + mgr->aux = aux; + mgr->max_dpcd_transaction_bytes = max_dpcd_transaction_bytes; + mgr->max_payloads = max_payloads; + mgr->max_lane_count = max_lane_count; + mgr->max_link_rate = max_link_rate; + mgr->conn_base_id = conn_base_id; + if (max_payloads + 1 > sizeof(mgr->payload_mask) * 8 || + max_payloads + 1 > sizeof(mgr->vcpi_mask) * 8) + return -EINVAL; + mgr->payloads = kcalloc(max_payloads, sizeof(struct drm_dp_payload), GFP_KERNEL); + if (!mgr->payloads) + return -ENOMEM; + mgr->proposed_vcpis = kcalloc(max_payloads, sizeof(struct drm_dp_vcpi *), GFP_KERNEL); + if (!mgr->proposed_vcpis) + return -ENOMEM; + set_bit(0, &mgr->payload_mask); + + mst_state = kzalloc(sizeof(*mst_state), GFP_KERNEL); + if (mst_state == NULL) + return -ENOMEM; + + mst_state->total_avail_slots = 63; + mst_state->start_slot = 1; + + mst_state->mgr = mgr; + INIT_LIST_HEAD(&mst_state->vcpis); + + drm_atomic_private_obj_init(dev, &mgr->base, + &mst_state->base, + &drm_dp_mst_topology_state_funcs); + + return 0; +} +EXPORT_SYMBOL(drm_dp_mst_topology_mgr_init); + +/** + * drm_dp_mst_topology_mgr_destroy() - destroy topology manager. + * @mgr: manager to destroy + */ +void drm_dp_mst_topology_mgr_destroy(struct drm_dp_mst_topology_mgr *mgr) +{ + drm_dp_mst_topology_mgr_set_mst(mgr, false); + flush_work(&mgr->work); + /* The following will also drain any requeued work on the WQ. */ + if (mgr->delayed_destroy_wq) { + destroy_workqueue(mgr->delayed_destroy_wq); + mgr->delayed_destroy_wq = NULL; + } + mutex_lock(&mgr->payload_lock); + kfree(mgr->payloads); + mgr->payloads = NULL; + kfree(mgr->proposed_vcpis); + mgr->proposed_vcpis = NULL; + mutex_unlock(&mgr->payload_lock); + mgr->dev = NULL; + mgr->aux = NULL; + drm_atomic_private_obj_fini(&mgr->base); + mgr->funcs = NULL; + + mutex_destroy(&mgr->delayed_destroy_lock); + mutex_destroy(&mgr->payload_lock); + mutex_destroy(&mgr->qlock); + mutex_destroy(&mgr->lock); + mutex_destroy(&mgr->up_req_lock); + mutex_destroy(&mgr->probe_lock); +#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) + mutex_destroy(&mgr->topology_ref_history_lock); +#endif +} +EXPORT_SYMBOL(drm_dp_mst_topology_mgr_destroy); + +static bool remote_i2c_read_ok(const struct i2c_msg msgs[], int num) +{ + int i; + + if (num - 1 > DP_REMOTE_I2C_READ_MAX_TRANSACTIONS) + return false; + + for (i = 0; i < num - 1; i++) { + if (msgs[i].flags & I2C_M_RD || + msgs[i].len > 0xff) + return false; + } + + return msgs[num - 1].flags & I2C_M_RD && + msgs[num - 1].len <= 0xff; +} + +static bool remote_i2c_write_ok(const struct i2c_msg msgs[], int num) +{ + int i; + + for (i = 0; i < num - 1; i++) { + if (msgs[i].flags & I2C_M_RD || !(msgs[i].flags & I2C_M_STOP) || + msgs[i].len > 0xff) + return false; + } + + return !(msgs[num - 1].flags & I2C_M_RD) && msgs[num - 1].len <= 0xff; +} + +static int drm_dp_mst_i2c_read(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port, + struct i2c_msg *msgs, int num) +{ + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + unsigned int i; + struct drm_dp_sideband_msg_req_body msg; + struct drm_dp_sideband_msg_tx *txmsg = NULL; + int ret; + + memset(&msg, 0, sizeof(msg)); + msg.req_type = DP_REMOTE_I2C_READ; + msg.u.i2c_read.num_transactions = num - 1; + msg.u.i2c_read.port_number = port->port_num; + for (i = 0; i < num - 1; i++) { + msg.u.i2c_read.transactions[i].i2c_dev_id = msgs[i].addr; + msg.u.i2c_read.transactions[i].num_bytes = msgs[i].len; + msg.u.i2c_read.transactions[i].bytes = msgs[i].buf; + msg.u.i2c_read.transactions[i].no_stop_bit = !(msgs[i].flags & I2C_M_STOP); + } + msg.u.i2c_read.read_i2c_device_id = msgs[num - 1].addr; + msg.u.i2c_read.num_bytes_read = msgs[num - 1].len; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto out; + } + + txmsg->dst = mstb; + drm_dp_encode_sideband_req(&msg, txmsg); + + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + ret = -EREMOTEIO; + goto out; + } + if (txmsg->reply.u.remote_i2c_read_ack.num_bytes != msgs[num - 1].len) { + ret = -EIO; + goto out; + } + memcpy(msgs[num - 1].buf, txmsg->reply.u.remote_i2c_read_ack.bytes, msgs[num - 1].len); + ret = num; + } +out: + kfree(txmsg); + return ret; +} + +static int drm_dp_mst_i2c_write(struct drm_dp_mst_branch *mstb, + struct drm_dp_mst_port *port, + struct i2c_msg *msgs, int num) +{ + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + unsigned int i; + struct drm_dp_sideband_msg_req_body msg; + struct drm_dp_sideband_msg_tx *txmsg = NULL; + int ret; + + txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); + if (!txmsg) { + ret = -ENOMEM; + goto out; + } + for (i = 0; i < num; i++) { + memset(&msg, 0, sizeof(msg)); + msg.req_type = DP_REMOTE_I2C_WRITE; + msg.u.i2c_write.port_number = port->port_num; + msg.u.i2c_write.write_i2c_device_id = msgs[i].addr; + msg.u.i2c_write.num_bytes = msgs[i].len; + msg.u.i2c_write.bytes = msgs[i].buf; + + memset(txmsg, 0, sizeof(*txmsg)); + txmsg->dst = mstb; + + drm_dp_encode_sideband_req(&msg, txmsg); + drm_dp_queue_down_tx(mgr, txmsg); + + ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); + if (ret > 0) { + if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { + ret = -EREMOTEIO; + goto out; + } + } else { + goto out; + } + } + ret = num; +out: + kfree(txmsg); + return ret; +} + +/* I2C device */ +static int drm_dp_mst_i2c_xfer(struct i2c_adapter *adapter, + struct i2c_msg *msgs, int num) +{ + struct drm_dp_aux *aux = adapter->algo_data; + struct drm_dp_mst_port *port = + container_of(aux, struct drm_dp_mst_port, aux); + struct drm_dp_mst_branch *mstb; + struct drm_dp_mst_topology_mgr *mgr = port->mgr; + int ret; + + mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); + if (!mstb) + return -EREMOTEIO; + + if (remote_i2c_read_ok(msgs, num)) { + ret = drm_dp_mst_i2c_read(mstb, port, msgs, num); + } else if (remote_i2c_write_ok(msgs, num)) { + ret = drm_dp_mst_i2c_write(mstb, port, msgs, num); + } else { + drm_dbg_kms(mgr->dev, "Unsupported I2C transaction for MST device\n"); + ret = -EIO; + } + + drm_dp_mst_topology_put_mstb(mstb); + return ret; +} + +static u32 drm_dp_mst_i2c_functionality(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | + I2C_FUNC_SMBUS_READ_BLOCK_DATA | + I2C_FUNC_SMBUS_BLOCK_PROC_CALL | + I2C_FUNC_10BIT_ADDR; +} + +static const struct i2c_algorithm drm_dp_mst_i2c_algo = { + .functionality = drm_dp_mst_i2c_functionality, + .master_xfer = drm_dp_mst_i2c_xfer, +}; + +/** + * drm_dp_mst_register_i2c_bus() - register an I2C adapter for I2C-over-AUX + * @port: The port to add the I2C bus on + * + * Returns 0 on success or a negative error code on failure. + */ +static int drm_dp_mst_register_i2c_bus(struct drm_dp_mst_port *port) +{ + struct drm_dp_aux *aux = &port->aux; + struct device *parent_dev = port->mgr->dev->dev; + + aux->ddc.algo = &drm_dp_mst_i2c_algo; + aux->ddc.algo_data = aux; + aux->ddc.retries = 3; + + aux->ddc.class = I2C_CLASS_DDC; + aux->ddc.owner = THIS_MODULE; + /* FIXME: set the kdev of the port's connector as parent */ + aux->ddc.dev.parent = parent_dev; + aux->ddc.dev.of_node = parent_dev->of_node; + + strlcpy(aux->ddc.name, aux->name ? aux->name : dev_name(parent_dev), + sizeof(aux->ddc.name)); + + return i2c_add_adapter(&aux->ddc); +} + +/** + * drm_dp_mst_unregister_i2c_bus() - unregister an I2C-over-AUX adapter + * @port: The port to remove the I2C bus from + */ +static void drm_dp_mst_unregister_i2c_bus(struct drm_dp_mst_port *port) +{ + i2c_del_adapter(&port->aux.ddc); +} + +/** + * drm_dp_mst_is_virtual_dpcd() - Is the given port a virtual DP Peer Device + * @port: The port to check + * + * A single physical MST hub object can be represented in the topology + * by multiple branches, with virtual ports between those branches. + * + * As of DP1.4, An MST hub with internal (virtual) ports must expose + * certain DPCD registers over those ports. See sections 2.6.1.1.1 + * and 2.6.1.1.2 of Display Port specification v1.4 for details. + * + * May acquire mgr->lock + * + * Returns: + * true if the port is a virtual DP peer device, false otherwise + */ +static bool drm_dp_mst_is_virtual_dpcd(struct drm_dp_mst_port *port) +{ + struct drm_dp_mst_port *downstream_port; + + if (!port || port->dpcd_rev < DP_DPCD_REV_14) + return false; + + /* Virtual DP Sink (Internal Display Panel) */ + if (port->port_num >= 8) + return true; + + /* DP-to-HDMI Protocol Converter */ + if (port->pdt == DP_PEER_DEVICE_DP_LEGACY_CONV && + !port->mcs && + port->ldps) + return true; + + /* DP-to-DP */ + mutex_lock(&port->mgr->lock); + if (port->pdt == DP_PEER_DEVICE_MST_BRANCHING && + port->mstb && + port->mstb->num_ports == 2) { + list_for_each_entry(downstream_port, &port->mstb->ports, next) { + if (downstream_port->pdt == DP_PEER_DEVICE_SST_SINK && + !downstream_port->input) { + mutex_unlock(&port->mgr->lock); + return true; + } + } + } + mutex_unlock(&port->mgr->lock); + + return false; +} + +/** + * drm_dp_mst_dsc_aux_for_port() - Find the correct aux for DSC + * @port: The port to check. A leaf of the MST tree with an attached display. + * + * Depending on the situation, DSC may be enabled via the endpoint aux, + * the immediately upstream aux, or the connector's physical aux. + * + * This is both the correct aux to read DSC_CAPABILITY and the + * correct aux to write DSC_ENABLED. + * + * This operation can be expensive (up to four aux reads), so + * the caller should cache the return. + * + * Returns: + * NULL if DSC cannot be enabled on this port, otherwise the aux device + */ +struct drm_dp_aux *drm_dp_mst_dsc_aux_for_port(struct drm_dp_mst_port *port) +{ + struct drm_dp_mst_port *immediate_upstream_port; + struct drm_dp_mst_port *fec_port; + struct drm_dp_desc desc = {}; + u8 endpoint_fec; + u8 endpoint_dsc; + + if (!port) + return NULL; + + if (port->parent->port_parent) + immediate_upstream_port = port->parent->port_parent; + else + immediate_upstream_port = NULL; + + fec_port = immediate_upstream_port; + while (fec_port) { + /* + * Each physical link (i.e. not a virtual port) between the + * output and the primary device must support FEC + */ + if (!drm_dp_mst_is_virtual_dpcd(fec_port) && + !fec_port->fec_capable) + return NULL; + + fec_port = fec_port->parent->port_parent; + } + + /* DP-to-DP peer device */ + if (drm_dp_mst_is_virtual_dpcd(immediate_upstream_port)) { + u8 upstream_dsc; + + if (drm_dp_dpcd_read(&port->aux, + DP_DSC_SUPPORT, &endpoint_dsc, 1) != 1) + return NULL; + if (drm_dp_dpcd_read(&port->aux, + DP_FEC_CAPABILITY, &endpoint_fec, 1) != 1) + return NULL; + if (drm_dp_dpcd_read(&immediate_upstream_port->aux, + DP_DSC_SUPPORT, &upstream_dsc, 1) != 1) + return NULL; + + /* Enpoint decompression with DP-to-DP peer device */ + if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) && + (endpoint_fec & DP_FEC_CAPABLE) && + (upstream_dsc & 0x2) /* DSC passthrough */) + return &port->aux; + + /* Virtual DPCD decompression with DP-to-DP peer device */ + return &immediate_upstream_port->aux; + } + + /* Virtual DPCD decompression with DP-to-HDMI or Virtual DP Sink */ + if (drm_dp_mst_is_virtual_dpcd(port)) + return &port->aux; + + /* + * Synaptics quirk + * Applies to ports for which: + * - Physical aux has Synaptics OUI + * - DPv1.4 or higher + * - Port is on primary branch device + * - Not a VGA adapter (DP_DWN_STRM_PORT_TYPE_ANALOG) + */ + if (drm_dp_read_desc(port->mgr->aux, &desc, true)) + return NULL; + + if (drm_dp_has_quirk(&desc, DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) && + port->mgr->dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14 && + port->parent == port->mgr->mst_primary) { + u8 dpcd_ext[DP_RECEIVER_CAP_SIZE]; + + if (drm_dp_read_dpcd_caps(port->mgr->aux, dpcd_ext) < 0) + return NULL; + + if ((dpcd_ext[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT) && + ((dpcd_ext[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) + != DP_DWN_STRM_PORT_TYPE_ANALOG)) + return port->mgr->aux; + } + + /* + * The check below verifies if the MST sink + * connected to the GPU is capable of DSC - + * therefore the endpoint needs to be + * both DSC and FEC capable. + */ + if (drm_dp_dpcd_read(&port->aux, + DP_DSC_SUPPORT, &endpoint_dsc, 1) != 1) + return NULL; + if (drm_dp_dpcd_read(&port->aux, + DP_FEC_CAPABILITY, &endpoint_fec, 1) != 1) + return NULL; + if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) && + (endpoint_fec & DP_FEC_CAPABLE)) + return &port->aux; + + return NULL; +} +EXPORT_SYMBOL(drm_dp_mst_dsc_aux_for_port); diff --git a/drivers/gpu/drm/display/drm_dp_mst_topology_internal.h b/drivers/gpu/drm/display/drm_dp_mst_topology_internal.h new file mode 100644 index 000000000000..a785ccbfdd73 --- /dev/null +++ b/drivers/gpu/drm/display/drm_dp_mst_topology_internal.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Declarations for DP MST related functions which are only used in selftests + * + * Copyright © 2018 Red Hat + * Authors: + * Lyude Paul + */ + +#ifndef _DRM_DP_MST_HELPER_INTERNAL_H_ +#define _DRM_DP_MST_HELPER_INTERNAL_H_ + +#include + +void +drm_dp_encode_sideband_req(const struct drm_dp_sideband_msg_req_body *req, + struct drm_dp_sideband_msg_tx *raw); +int drm_dp_decode_sideband_req(const struct drm_dp_sideband_msg_tx *raw, + struct drm_dp_sideband_msg_req_body *req); +void +drm_dp_dump_sideband_msg_req_body(const struct drm_dp_sideband_msg_req_body *req, + int indent, struct drm_printer *printer); + +#endif /* !_DRM_DP_MST_HELPER_INTERNAL_H_ */ diff --git a/drivers/gpu/drm/dp/Makefile b/drivers/gpu/drm/dp/Makefile deleted file mode 100644 index 75faffc706b1..000000000000 --- a/drivers/gpu/drm/dp/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: MIT - -obj-$(CONFIG_DRM_DP_AUX_BUS) += drm_dp_aux_bus.o - -drm_dp_helper-y := drm_dp.o drm_dp_dual_mode_helper.o drm_dp_helper_mod.o drm_dp_mst_topology.o -drm_dp_helper-$(CONFIG_DRM_DP_AUX_CHARDEV) += drm_dp_aux_dev.o -drm_dp_helper-$(CONFIG_DRM_DP_CEC) += drm_dp_cec.o - -obj-$(CONFIG_DRM_DP_HELPER) += drm_dp_helper.o diff --git a/drivers/gpu/drm/dp/drm_dp.c b/drivers/gpu/drm/dp/drm_dp.c deleted file mode 100644 index 2a1f5ff6633c..000000000000 --- a/drivers/gpu/drm/dp/drm_dp.c +++ /dev/null @@ -1,3855 +0,0 @@ -/* - * Copyright © 2009 Keith Packard - * - * Permission to use, copy, modify, distribute, and sell this software and its - * documentation for any purpose is hereby granted without fee, provided that - * the above copyright notice appear in all copies and that both that copyright - * notice and this permission notice appear in supporting documentation, and - * that the name of the copyright holders not be used in advertising or - * publicity pertaining to distribution of the software without specific, - * written prior permission. The copyright holders make no representations - * about the suitability of this software for any purpose. It is provided "as - * is" without express or implied warranty. - * - * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, - * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO - * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR - * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, - * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER - * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE - * OF THIS SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "drm_dp_helper_internal.h" - -struct dp_aux_backlight { - struct backlight_device *base; - struct drm_dp_aux *aux; - struct drm_edp_backlight_info info; - bool enabled; -}; - -/** - * DOC: dp helpers - * - * These functions contain some common logic and helpers at various abstraction - * levels to deal with Display Port sink devices and related things like DP aux - * channel transfers, EDID reading over DP aux channels, decoding certain DPCD - * blocks, ... - */ - -/* Helpers for DP link training */ -static u8 dp_link_status(const u8 link_status[DP_LINK_STATUS_SIZE], int r) -{ - return link_status[r - DP_LANE0_1_STATUS]; -} - -static u8 dp_get_lane_status(const u8 link_status[DP_LINK_STATUS_SIZE], - int lane) -{ - int i = DP_LANE0_1_STATUS + (lane >> 1); - int s = (lane & 1) * 4; - u8 l = dp_link_status(link_status, i); - - return (l >> s) & 0xf; -} - -bool drm_dp_channel_eq_ok(const u8 link_status[DP_LINK_STATUS_SIZE], - int lane_count) -{ - u8 lane_align; - u8 lane_status; - int lane; - - lane_align = dp_link_status(link_status, - DP_LANE_ALIGN_STATUS_UPDATED); - if ((lane_align & DP_INTERLANE_ALIGN_DONE) == 0) - return false; - for (lane = 0; lane < lane_count; lane++) { - lane_status = dp_get_lane_status(link_status, lane); - if ((lane_status & DP_CHANNEL_EQ_BITS) != DP_CHANNEL_EQ_BITS) - return false; - } - return true; -} -EXPORT_SYMBOL(drm_dp_channel_eq_ok); - -bool drm_dp_clock_recovery_ok(const u8 link_status[DP_LINK_STATUS_SIZE], - int lane_count) -{ - int lane; - u8 lane_status; - - for (lane = 0; lane < lane_count; lane++) { - lane_status = dp_get_lane_status(link_status, lane); - if ((lane_status & DP_LANE_CR_DONE) == 0) - return false; - } - return true; -} -EXPORT_SYMBOL(drm_dp_clock_recovery_ok); - -u8 drm_dp_get_adjust_request_voltage(const u8 link_status[DP_LINK_STATUS_SIZE], - int lane) -{ - int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); - int s = ((lane & 1) ? - DP_ADJUST_VOLTAGE_SWING_LANE1_SHIFT : - DP_ADJUST_VOLTAGE_SWING_LANE0_SHIFT); - u8 l = dp_link_status(link_status, i); - - return ((l >> s) & 0x3) << DP_TRAIN_VOLTAGE_SWING_SHIFT; -} -EXPORT_SYMBOL(drm_dp_get_adjust_request_voltage); - -u8 drm_dp_get_adjust_request_pre_emphasis(const u8 link_status[DP_LINK_STATUS_SIZE], - int lane) -{ - int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); - int s = ((lane & 1) ? - DP_ADJUST_PRE_EMPHASIS_LANE1_SHIFT : - DP_ADJUST_PRE_EMPHASIS_LANE0_SHIFT); - u8 l = dp_link_status(link_status, i); - - return ((l >> s) & 0x3) << DP_TRAIN_PRE_EMPHASIS_SHIFT; -} -EXPORT_SYMBOL(drm_dp_get_adjust_request_pre_emphasis); - -/* DP 2.0 128b/132b */ -u8 drm_dp_get_adjust_tx_ffe_preset(const u8 link_status[DP_LINK_STATUS_SIZE], - int lane) -{ - int i = DP_ADJUST_REQUEST_LANE0_1 + (lane >> 1); - int s = ((lane & 1) ? - DP_ADJUST_TX_FFE_PRESET_LANE1_SHIFT : - DP_ADJUST_TX_FFE_PRESET_LANE0_SHIFT); - u8 l = dp_link_status(link_status, i); - - return (l >> s) & 0xf; -} -EXPORT_SYMBOL(drm_dp_get_adjust_tx_ffe_preset); - -/* DP 2.0 errata for 128b/132b */ -bool drm_dp_128b132b_lane_channel_eq_done(const u8 link_status[DP_LINK_STATUS_SIZE], - int lane_count) -{ - u8 lane_align, lane_status; - int lane; - - lane_align = dp_link_status(link_status, DP_LANE_ALIGN_STATUS_UPDATED); - if (!(lane_align & DP_INTERLANE_ALIGN_DONE)) - return false; - - for (lane = 0; lane < lane_count; lane++) { - lane_status = dp_get_lane_status(link_status, lane); - if (!(lane_status & DP_LANE_CHANNEL_EQ_DONE)) - return false; - } - return true; -} -EXPORT_SYMBOL(drm_dp_128b132b_lane_channel_eq_done); - -/* DP 2.0 errata for 128b/132b */ -bool drm_dp_128b132b_lane_symbol_locked(const u8 link_status[DP_LINK_STATUS_SIZE], - int lane_count) -{ - u8 lane_status; - int lane; - - for (lane = 0; lane < lane_count; lane++) { - lane_status = dp_get_lane_status(link_status, lane); - if (!(lane_status & DP_LANE_SYMBOL_LOCKED)) - return false; - } - return true; -} -EXPORT_SYMBOL(drm_dp_128b132b_lane_symbol_locked); - -/* DP 2.0 errata for 128b/132b */ -bool drm_dp_128b132b_eq_interlane_align_done(const u8 link_status[DP_LINK_STATUS_SIZE]) -{ - u8 status = dp_link_status(link_status, DP_LANE_ALIGN_STATUS_UPDATED); - - return status & DP_128B132B_DPRX_EQ_INTERLANE_ALIGN_DONE; -} -EXPORT_SYMBOL(drm_dp_128b132b_eq_interlane_align_done); - -/* DP 2.0 errata for 128b/132b */ -bool drm_dp_128b132b_cds_interlane_align_done(const u8 link_status[DP_LINK_STATUS_SIZE]) -{ - u8 status = dp_link_status(link_status, DP_LANE_ALIGN_STATUS_UPDATED); - - return status & DP_128B132B_DPRX_CDS_INTERLANE_ALIGN_DONE; -} -EXPORT_SYMBOL(drm_dp_128b132b_cds_interlane_align_done); - -/* DP 2.0 errata for 128b/132b */ -bool drm_dp_128b132b_link_training_failed(const u8 link_status[DP_LINK_STATUS_SIZE]) -{ - u8 status = dp_link_status(link_status, DP_LANE_ALIGN_STATUS_UPDATED); - - return status & DP_128B132B_LT_FAILED; -} -EXPORT_SYMBOL(drm_dp_128b132b_link_training_failed); - -static int __8b10b_clock_recovery_delay_us(const struct drm_dp_aux *aux, u8 rd_interval) -{ - if (rd_interval > 4) - drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x (max 4)\n", - aux->name, rd_interval); - - if (rd_interval == 0) - return 100; - - return rd_interval * 4 * USEC_PER_MSEC; -} - -static int __8b10b_channel_eq_delay_us(const struct drm_dp_aux *aux, u8 rd_interval) -{ - if (rd_interval > 4) - drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x (max 4)\n", - aux->name, rd_interval); - - if (rd_interval == 0) - return 400; - - return rd_interval * 4 * USEC_PER_MSEC; -} - -static int __128b132b_channel_eq_delay_us(const struct drm_dp_aux *aux, u8 rd_interval) -{ - switch (rd_interval) { - default: - drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x\n", - aux->name, rd_interval); - fallthrough; - case DP_128B132B_TRAINING_AUX_RD_INTERVAL_400_US: - return 400; - case DP_128B132B_TRAINING_AUX_RD_INTERVAL_4_MS: - return 4000; - case DP_128B132B_TRAINING_AUX_RD_INTERVAL_8_MS: - return 8000; - case DP_128B132B_TRAINING_AUX_RD_INTERVAL_12_MS: - return 12000; - case DP_128B132B_TRAINING_AUX_RD_INTERVAL_16_MS: - return 16000; - case DP_128B132B_TRAINING_AUX_RD_INTERVAL_32_MS: - return 32000; - case DP_128B132B_TRAINING_AUX_RD_INTERVAL_64_MS: - return 64000; - } -} - -/* - * The link training delays are different for: - * - * - Clock recovery vs. channel equalization - * - DPRX vs. LTTPR - * - 128b/132b vs. 8b/10b - * - DPCD rev 1.3 vs. later - * - * Get the correct delay in us, reading DPCD if necessary. - */ -static int __read_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], - enum drm_dp_phy dp_phy, bool uhbr, bool cr) -{ - int (*parse)(const struct drm_dp_aux *aux, u8 rd_interval); - unsigned int offset; - u8 rd_interval, mask; - - if (dp_phy == DP_PHY_DPRX) { - if (uhbr) { - if (cr) - return 100; - - offset = DP_128B132B_TRAINING_AUX_RD_INTERVAL; - mask = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK; - parse = __128b132b_channel_eq_delay_us; - } else { - if (cr && dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14) - return 100; - - offset = DP_TRAINING_AUX_RD_INTERVAL; - mask = DP_TRAINING_AUX_RD_MASK; - if (cr) - parse = __8b10b_clock_recovery_delay_us; - else - parse = __8b10b_channel_eq_delay_us; - } - } else { - if (uhbr) { - offset = DP_128B132B_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy); - mask = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK; - parse = __128b132b_channel_eq_delay_us; - } else { - if (cr) - return 100; - - offset = DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy); - mask = DP_TRAINING_AUX_RD_MASK; - parse = __8b10b_channel_eq_delay_us; - } - } - - if (offset < DP_RECEIVER_CAP_SIZE) { - rd_interval = dpcd[offset]; - } else { - if (drm_dp_dpcd_readb(aux, offset, &rd_interval) != 1) { - drm_dbg_kms(aux->drm_dev, "%s: failed rd interval read\n", - aux->name); - /* arbitrary default delay */ - return 400; - } - } - - return parse(aux, rd_interval & mask); -} - -int drm_dp_read_clock_recovery_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], - enum drm_dp_phy dp_phy, bool uhbr) -{ - return __read_delay(aux, dpcd, dp_phy, uhbr, true); -} -EXPORT_SYMBOL(drm_dp_read_clock_recovery_delay); - -int drm_dp_read_channel_eq_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE], - enum drm_dp_phy dp_phy, bool uhbr) -{ - return __read_delay(aux, dpcd, dp_phy, uhbr, false); -} -EXPORT_SYMBOL(drm_dp_read_channel_eq_delay); - -/* Per DP 2.0 Errata */ -int drm_dp_128b132b_read_aux_rd_interval(struct drm_dp_aux *aux) -{ - int unit; - u8 val; - - if (drm_dp_dpcd_readb(aux, DP_128B132B_TRAINING_AUX_RD_INTERVAL, &val) != 1) { - drm_err(aux->drm_dev, "%s: failed rd interval read\n", - aux->name); - /* default to max */ - val = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK; - } - - unit = (val & DP_128B132B_TRAINING_AUX_RD_INTERVAL_1MS_UNIT) ? 1 : 2; - val &= DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK; - - return (val + 1) * unit * 1000; -} -EXPORT_SYMBOL(drm_dp_128b132b_read_aux_rd_interval); - -void drm_dp_link_train_clock_recovery_delay(const struct drm_dp_aux *aux, - const u8 dpcd[DP_RECEIVER_CAP_SIZE]) -{ - u8 rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] & - DP_TRAINING_AUX_RD_MASK; - int delay_us; - - if (dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14) - delay_us = 100; - else - delay_us = __8b10b_clock_recovery_delay_us(aux, rd_interval); - - usleep_range(delay_us, delay_us * 2); -} -EXPORT_SYMBOL(drm_dp_link_train_clock_recovery_delay); - -static void __drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux, - u8 rd_interval) -{ - int delay_us = __8b10b_channel_eq_delay_us(aux, rd_interval); - - usleep_range(delay_us, delay_us * 2); -} - -void drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux, - const u8 dpcd[DP_RECEIVER_CAP_SIZE]) -{ - __drm_dp_link_train_channel_eq_delay(aux, - dpcd[DP_TRAINING_AUX_RD_INTERVAL] & - DP_TRAINING_AUX_RD_MASK); -} -EXPORT_SYMBOL(drm_dp_link_train_channel_eq_delay); - -void drm_dp_lttpr_link_train_clock_recovery_delay(void) -{ - usleep_range(100, 200); -} -EXPORT_SYMBOL(drm_dp_lttpr_link_train_clock_recovery_delay); - -static u8 dp_lttpr_phy_cap(const u8 phy_cap[DP_LTTPR_PHY_CAP_SIZE], int r) -{ - return phy_cap[r - DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1]; -} - -void drm_dp_lttpr_link_train_channel_eq_delay(const struct drm_dp_aux *aux, - const u8 phy_cap[DP_LTTPR_PHY_CAP_SIZE]) -{ - u8 interval = dp_lttpr_phy_cap(phy_cap, - DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER1) & - DP_TRAINING_AUX_RD_MASK; - - __drm_dp_link_train_channel_eq_delay(aux, interval); -} -EXPORT_SYMBOL(drm_dp_lttpr_link_train_channel_eq_delay); - -u8 drm_dp_link_rate_to_bw_code(int link_rate) -{ - switch (link_rate) { - case 1000000: - return DP_LINK_BW_10; - case 1350000: - return DP_LINK_BW_13_5; - case 2000000: - return DP_LINK_BW_20; - default: - /* Spec says link_bw = link_rate / 0.27Gbps */ - return link_rate / 27000; - } -} -EXPORT_SYMBOL(drm_dp_link_rate_to_bw_code); - -int drm_dp_bw_code_to_link_rate(u8 link_bw) -{ - switch (link_bw) { - case DP_LINK_BW_10: - return 1000000; - case DP_LINK_BW_13_5: - return 1350000; - case DP_LINK_BW_20: - return 2000000; - default: - /* Spec says link_rate = link_bw * 0.27Gbps */ - return link_bw * 27000; - } -} -EXPORT_SYMBOL(drm_dp_bw_code_to_link_rate); - -#define AUX_RETRY_INTERVAL 500 /* us */ - -static inline void -drm_dp_dump_access(const struct drm_dp_aux *aux, - u8 request, uint offset, void *buffer, int ret) -{ - const char *arrow = request == DP_AUX_NATIVE_READ ? "->" : "<-"; - - if (ret > 0) - drm_dbg_dp(aux->drm_dev, "%s: 0x%05x AUX %s (ret=%3d) %*ph\n", - aux->name, offset, arrow, ret, min(ret, 20), buffer); - else - drm_dbg_dp(aux->drm_dev, "%s: 0x%05x AUX %s (ret=%3d)\n", - aux->name, offset, arrow, ret); -} - -/** - * DOC: dp helpers - * - * The DisplayPort AUX channel is an abstraction to allow generic, driver- - * independent access to AUX functionality. Drivers can take advantage of - * this by filling in the fields of the drm_dp_aux structure. - * - * Transactions are described using a hardware-independent drm_dp_aux_msg - * structure, which is passed into a driver's .transfer() implementation. - * Both native and I2C-over-AUX transactions are supported. - */ - -static int drm_dp_dpcd_access(struct drm_dp_aux *aux, u8 request, - unsigned int offset, void *buffer, size_t size) -{ - struct drm_dp_aux_msg msg; - unsigned int retry, native_reply; - int err = 0, ret = 0; - - memset(&msg, 0, sizeof(msg)); - msg.address = offset; - msg.request = request; - msg.buffer = buffer; - msg.size = size; - - mutex_lock(&aux->hw_mutex); - - /* - * The specification doesn't give any recommendation on how often to - * retry native transactions. We used to retry 7 times like for - * aux i2c transactions but real world devices this wasn't - * sufficient, bump to 32 which makes Dell 4k monitors happier. - */ - for (retry = 0; retry < 32; retry++) { - if (ret != 0 && ret != -ETIMEDOUT) { - usleep_range(AUX_RETRY_INTERVAL, - AUX_RETRY_INTERVAL + 100); - } - - ret = aux->transfer(aux, &msg); - if (ret >= 0) { - native_reply = msg.reply & DP_AUX_NATIVE_REPLY_MASK; - if (native_reply == DP_AUX_NATIVE_REPLY_ACK) { - if (ret == size) - goto unlock; - - ret = -EPROTO; - } else - ret = -EIO; - } - - /* - * We want the error we return to be the error we received on - * the first transaction, since we may get a different error the - * next time we retry - */ - if (!err) - err = ret; - } - - drm_dbg_kms(aux->drm_dev, "%s: Too many retries, giving up. First error: %d\n", - aux->name, err); - ret = err; - -unlock: - mutex_unlock(&aux->hw_mutex); - return ret; -} - -/** - * drm_dp_dpcd_probe() - probe a given DPCD address with a 1-byte read access - * @aux: DisplayPort AUX channel (SST) - * @offset: address of the register to probe - * - * Probe the provided DPCD address by reading 1 byte from it. The function can - * be used to trigger some side-effect the read access has, like waking up the - * sink, without the need for the read-out value. - * - * Returns 0 if the read access suceeded, or a negative error code on failure. - */ -int drm_dp_dpcd_probe(struct drm_dp_aux *aux, unsigned int offset) -{ - u8 buffer; - int ret; - - ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, offset, &buffer, 1); - WARN_ON(ret == 0); - - drm_dp_dump_access(aux, DP_AUX_NATIVE_READ, offset, &buffer, ret); - - return ret < 0 ? ret : 0; -} -EXPORT_SYMBOL(drm_dp_dpcd_probe); - -/** - * drm_dp_dpcd_read() - read a series of bytes from the DPCD - * @aux: DisplayPort AUX channel (SST or MST) - * @offset: address of the (first) register to read - * @buffer: buffer to store the register values - * @size: number of bytes in @buffer - * - * Returns the number of bytes transferred on success, or a negative error - * code on failure. -EIO is returned if the request was NAKed by the sink or - * if the retry count was exceeded. If not all bytes were transferred, this - * function returns -EPROTO. Errors from the underlying AUX channel transfer - * function, with the exception of -EBUSY (which causes the transaction to - * be retried), are propagated to the caller. - */ -ssize_t drm_dp_dpcd_read(struct drm_dp_aux *aux, unsigned int offset, - void *buffer, size_t size) -{ - int ret; - - /* - * HP ZR24w corrupts the first DPCD access after entering power save - * mode. Eg. on a read, the entire buffer will be filled with the same - * byte. Do a throw away read to avoid corrupting anything we care - * about. Afterwards things will work correctly until the monitor - * gets woken up and subsequently re-enters power save mode. - * - * The user pressing any button on the monitor is enough to wake it - * up, so there is no particularly good place to do the workaround. - * We just have to do it before any DPCD access and hope that the - * monitor doesn't power down exactly after the throw away read. - */ - if (!aux->is_remote) { - ret = drm_dp_dpcd_probe(aux, DP_DPCD_REV); - if (ret < 0) - return ret; - } - - if (aux->is_remote) - ret = drm_dp_mst_dpcd_read(aux, offset, buffer, size); - else - ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, offset, - buffer, size); - - drm_dp_dump_access(aux, DP_AUX_NATIVE_READ, offset, buffer, ret); - return ret; -} -EXPORT_SYMBOL(drm_dp_dpcd_read); - -/** - * drm_dp_dpcd_write() - write a series of bytes to the DPCD - * @aux: DisplayPort AUX channel (SST or MST) - * @offset: address of the (first) register to write - * @buffer: buffer containing the values to write - * @size: number of bytes in @buffer - * - * Returns the number of bytes transferred on success, or a negative error - * code on failure. -EIO is returned if the request was NAKed by the sink or - * if the retry count was exceeded. If not all bytes were transferred, this - * function returns -EPROTO. Errors from the underlying AUX channel transfer - * function, with the exception of -EBUSY (which causes the transaction to - * be retried), are propagated to the caller. - */ -ssize_t drm_dp_dpcd_write(struct drm_dp_aux *aux, unsigned int offset, - void *buffer, size_t size) -{ - int ret; - - if (aux->is_remote) - ret = drm_dp_mst_dpcd_write(aux, offset, buffer, size); - else - ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_WRITE, offset, - buffer, size); - - drm_dp_dump_access(aux, DP_AUX_NATIVE_WRITE, offset, buffer, ret); - return ret; -} -EXPORT_SYMBOL(drm_dp_dpcd_write); - -/** - * drm_dp_dpcd_read_link_status() - read DPCD link status (bytes 0x202-0x207) - * @aux: DisplayPort AUX channel - * @status: buffer to store the link status in (must be at least 6 bytes) - * - * Returns the number of bytes transferred on success or a negative error - * code on failure. - */ -int drm_dp_dpcd_read_link_status(struct drm_dp_aux *aux, - u8 status[DP_LINK_STATUS_SIZE]) -{ - return drm_dp_dpcd_read(aux, DP_LANE0_1_STATUS, status, - DP_LINK_STATUS_SIZE); -} -EXPORT_SYMBOL(drm_dp_dpcd_read_link_status); - -/** - * drm_dp_dpcd_read_phy_link_status - get the link status information for a DP PHY - * @aux: DisplayPort AUX channel - * @dp_phy: the DP PHY to get the link status for - * @link_status: buffer to return the status in - * - * Fetch the AUX DPCD registers for the DPRX or an LTTPR PHY link status. The - * layout of the returned @link_status matches the DPCD register layout of the - * DPRX PHY link status. - * - * Returns 0 if the information was read successfully or a negative error code - * on failure. - */ -int drm_dp_dpcd_read_phy_link_status(struct drm_dp_aux *aux, - enum drm_dp_phy dp_phy, - u8 link_status[DP_LINK_STATUS_SIZE]) -{ - int ret; - - if (dp_phy == DP_PHY_DPRX) { - ret = drm_dp_dpcd_read(aux, - DP_LANE0_1_STATUS, - link_status, - DP_LINK_STATUS_SIZE); - - if (ret < 0) - return ret; - - WARN_ON(ret != DP_LINK_STATUS_SIZE); - - return 0; - } - - ret = drm_dp_dpcd_read(aux, - DP_LANE0_1_STATUS_PHY_REPEATER(dp_phy), - link_status, - DP_LINK_STATUS_SIZE - 1); - - if (ret < 0) - return ret; - - WARN_ON(ret != DP_LINK_STATUS_SIZE - 1); - - /* Convert the LTTPR to the sink PHY link status layout */ - memmove(&link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS + 1], - &link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS], - DP_LINK_STATUS_SIZE - (DP_SINK_STATUS - DP_LANE0_1_STATUS) - 1); - link_status[DP_SINK_STATUS - DP_LANE0_1_STATUS] = 0; - - return 0; -} -EXPORT_SYMBOL(drm_dp_dpcd_read_phy_link_status); - -static bool is_edid_digital_input_dp(const struct edid *edid) -{ - return edid && edid->revision >= 4 && - edid->input & DRM_EDID_INPUT_DIGITAL && - (edid->input & DRM_EDID_DIGITAL_TYPE_MASK) == DRM_EDID_DIGITAL_TYPE_DP; -} - -/** - * drm_dp_downstream_is_type() - is the downstream facing port of certain type? - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * @type: port type to be checked. Can be: - * %DP_DS_PORT_TYPE_DP, %DP_DS_PORT_TYPE_VGA, %DP_DS_PORT_TYPE_DVI, - * %DP_DS_PORT_TYPE_HDMI, %DP_DS_PORT_TYPE_NON_EDID, - * %DP_DS_PORT_TYPE_DP_DUALMODE or %DP_DS_PORT_TYPE_WIRELESS. - * - * Caveat: Only works with DPCD 1.1+ port caps. - * - * Returns: whether the downstream facing port matches the type. - */ -bool drm_dp_downstream_is_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4], u8 type) -{ - return drm_dp_is_branch(dpcd) && - dpcd[DP_DPCD_REV] >= 0x11 && - (port_cap[0] & DP_DS_PORT_TYPE_MASK) == type; -} -EXPORT_SYMBOL(drm_dp_downstream_is_type); - -/** - * drm_dp_downstream_is_tmds() - is the downstream facing port TMDS? - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * @edid: EDID - * - * Returns: whether the downstream facing port is TMDS (HDMI/DVI). - */ -bool drm_dp_downstream_is_tmds(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4], - const struct edid *edid) -{ - if (dpcd[DP_DPCD_REV] < 0x11) { - switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { - case DP_DWN_STRM_PORT_TYPE_TMDS: - return true; - default: - return false; - } - } - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_DP_DUALMODE: - if (is_edid_digital_input_dp(edid)) - return false; - fallthrough; - case DP_DS_PORT_TYPE_DVI: - case DP_DS_PORT_TYPE_HDMI: - return true; - default: - return false; - } -} -EXPORT_SYMBOL(drm_dp_downstream_is_tmds); - -/** - * drm_dp_send_real_edid_checksum() - send back real edid checksum value - * @aux: DisplayPort AUX channel - * @real_edid_checksum: real edid checksum for the last block - * - * Returns: - * True on success - */ -bool drm_dp_send_real_edid_checksum(struct drm_dp_aux *aux, - u8 real_edid_checksum) -{ - u8 link_edid_read = 0, auto_test_req = 0, test_resp = 0; - - if (drm_dp_dpcd_read(aux, DP_DEVICE_SERVICE_IRQ_VECTOR, - &auto_test_req, 1) < 1) { - drm_err(aux->drm_dev, "%s: DPCD failed read at register 0x%x\n", - aux->name, DP_DEVICE_SERVICE_IRQ_VECTOR); - return false; - } - auto_test_req &= DP_AUTOMATED_TEST_REQUEST; - - if (drm_dp_dpcd_read(aux, DP_TEST_REQUEST, &link_edid_read, 1) < 1) { - drm_err(aux->drm_dev, "%s: DPCD failed read at register 0x%x\n", - aux->name, DP_TEST_REQUEST); - return false; - } - link_edid_read &= DP_TEST_LINK_EDID_READ; - - if (!auto_test_req || !link_edid_read) { - drm_dbg_kms(aux->drm_dev, "%s: Source DUT does not support TEST_EDID_READ\n", - aux->name); - return false; - } - - if (drm_dp_dpcd_write(aux, DP_DEVICE_SERVICE_IRQ_VECTOR, - &auto_test_req, 1) < 1) { - drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n", - aux->name, DP_DEVICE_SERVICE_IRQ_VECTOR); - return false; - } - - /* send back checksum for the last edid extension block data */ - if (drm_dp_dpcd_write(aux, DP_TEST_EDID_CHECKSUM, - &real_edid_checksum, 1) < 1) { - drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n", - aux->name, DP_TEST_EDID_CHECKSUM); - return false; - } - - test_resp |= DP_TEST_EDID_CHECKSUM_WRITE; - if (drm_dp_dpcd_write(aux, DP_TEST_RESPONSE, &test_resp, 1) < 1) { - drm_err(aux->drm_dev, "%s: DPCD failed write at register 0x%x\n", - aux->name, DP_TEST_RESPONSE); - return false; - } - - return true; -} -EXPORT_SYMBOL(drm_dp_send_real_edid_checksum); - -static u8 drm_dp_downstream_port_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE]) -{ - u8 port_count = dpcd[DP_DOWN_STREAM_PORT_COUNT] & DP_PORT_COUNT_MASK; - - if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE && port_count > 4) - port_count = 4; - - return port_count; -} - -static int drm_dp_read_extended_dpcd_caps(struct drm_dp_aux *aux, - u8 dpcd[DP_RECEIVER_CAP_SIZE]) -{ - u8 dpcd_ext[DP_RECEIVER_CAP_SIZE]; - int ret; - - /* - * Prior to DP1.3 the bit represented by - * DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT was reserved. - * If it is set DP_DPCD_REV at 0000h could be at a value less than - * the true capability of the panel. The only way to check is to - * then compare 0000h and 2200h. - */ - if (!(dpcd[DP_TRAINING_AUX_RD_INTERVAL] & - DP_EXTENDED_RECEIVER_CAP_FIELD_PRESENT)) - return 0; - - ret = drm_dp_dpcd_read(aux, DP_DP13_DPCD_REV, &dpcd_ext, - sizeof(dpcd_ext)); - if (ret < 0) - return ret; - if (ret != sizeof(dpcd_ext)) - return -EIO; - - if (dpcd[DP_DPCD_REV] > dpcd_ext[DP_DPCD_REV]) { - drm_dbg_kms(aux->drm_dev, - "%s: Extended DPCD rev less than base DPCD rev (%d > %d)\n", - aux->name, dpcd[DP_DPCD_REV], dpcd_ext[DP_DPCD_REV]); - return 0; - } - - if (!memcmp(dpcd, dpcd_ext, sizeof(dpcd_ext))) - return 0; - - drm_dbg_kms(aux->drm_dev, "%s: Base DPCD: %*ph\n", aux->name, DP_RECEIVER_CAP_SIZE, dpcd); - - memcpy(dpcd, dpcd_ext, sizeof(dpcd_ext)); - - return 0; -} - -/** - * drm_dp_read_dpcd_caps() - read DPCD caps and extended DPCD caps if - * available - * @aux: DisplayPort AUX channel - * @dpcd: Buffer to store the resulting DPCD in - * - * Attempts to read the base DPCD caps for @aux. Additionally, this function - * checks for and reads the extended DPRX caps (%DP_DP13_DPCD_REV) if - * present. - * - * Returns: %0 if the DPCD was read successfully, negative error code - * otherwise. - */ -int drm_dp_read_dpcd_caps(struct drm_dp_aux *aux, - u8 dpcd[DP_RECEIVER_CAP_SIZE]) -{ - int ret; - - ret = drm_dp_dpcd_read(aux, DP_DPCD_REV, dpcd, DP_RECEIVER_CAP_SIZE); - if (ret < 0) - return ret; - if (ret != DP_RECEIVER_CAP_SIZE || dpcd[DP_DPCD_REV] == 0) - return -EIO; - - ret = drm_dp_read_extended_dpcd_caps(aux, dpcd); - if (ret < 0) - return ret; - - drm_dbg_kms(aux->drm_dev, "%s: DPCD: %*ph\n", aux->name, DP_RECEIVER_CAP_SIZE, dpcd); - - return ret; -} -EXPORT_SYMBOL(drm_dp_read_dpcd_caps); - -/** - * drm_dp_read_downstream_info() - read DPCD downstream port info if available - * @aux: DisplayPort AUX channel - * @dpcd: A cached copy of the port's DPCD - * @downstream_ports: buffer to store the downstream port info in - * - * See also: - * drm_dp_downstream_max_clock() - * drm_dp_downstream_max_bpc() - * - * Returns: 0 if either the downstream port info was read successfully or - * there was no downstream info to read, or a negative error code otherwise. - */ -int drm_dp_read_downstream_info(struct drm_dp_aux *aux, - const u8 dpcd[DP_RECEIVER_CAP_SIZE], - u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS]) -{ - int ret; - u8 len; - - memset(downstream_ports, 0, DP_MAX_DOWNSTREAM_PORTS); - - /* No downstream info to read */ - if (!drm_dp_is_branch(dpcd) || dpcd[DP_DPCD_REV] == DP_DPCD_REV_10) - return 0; - - /* Some branches advertise having 0 downstream ports, despite also advertising they have a - * downstream port present. The DP spec isn't clear on if this is allowed or not, but since - * some branches do it we need to handle it regardless. - */ - len = drm_dp_downstream_port_count(dpcd); - if (!len) - return 0; - - if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) - len *= 4; - - ret = drm_dp_dpcd_read(aux, DP_DOWNSTREAM_PORT_0, downstream_ports, len); - if (ret < 0) - return ret; - if (ret != len) - return -EIO; - - drm_dbg_kms(aux->drm_dev, "%s: DPCD DFP: %*ph\n", aux->name, len, downstream_ports); - - return 0; -} -EXPORT_SYMBOL(drm_dp_read_downstream_info); - -/** - * drm_dp_downstream_max_dotclock() - extract downstream facing port max dot clock - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * - * Returns: Downstream facing port max dot clock in kHz on success, - * or 0 if max clock not defined - */ -int drm_dp_downstream_max_dotclock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4]) -{ - if (!drm_dp_is_branch(dpcd)) - return 0; - - if (dpcd[DP_DPCD_REV] < 0x11) - return 0; - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_VGA: - if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) - return 0; - return port_cap[1] * 8000; - default: - return 0; - } -} -EXPORT_SYMBOL(drm_dp_downstream_max_dotclock); - -/** - * drm_dp_downstream_max_tmds_clock() - extract downstream facing port max TMDS clock - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * @edid: EDID - * - * Returns: HDMI/DVI downstream facing port max TMDS clock in kHz on success, - * or 0 if max TMDS clock not defined - */ -int drm_dp_downstream_max_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4], - const struct edid *edid) -{ - if (!drm_dp_is_branch(dpcd)) - return 0; - - if (dpcd[DP_DPCD_REV] < 0x11) { - switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { - case DP_DWN_STRM_PORT_TYPE_TMDS: - return 165000; - default: - return 0; - } - } - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_DP_DUALMODE: - if (is_edid_digital_input_dp(edid)) - return 0; - /* - * It's left up to the driver to check the - * DP dual mode adapter's max TMDS clock. - * - * Unfortunately it looks like branch devices - * may not fordward that the DP dual mode i2c - * access so we just usually get i2c nak :( - */ - fallthrough; - case DP_DS_PORT_TYPE_HDMI: - /* - * We should perhaps assume 165 MHz when detailed cap - * info is not available. But looks like many typical - * branch devices fall into that category and so we'd - * probably end up with users complaining that they can't - * get high resolution modes with their favorite dongle. - * - * So let's limit to 300 MHz instead since DPCD 1.4 - * HDMI 2.0 DFPs are required to have the detailed cap - * info. So it's more likely we're dealing with a HDMI 1.4 - * compatible* device here. - */ - if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) - return 300000; - return port_cap[1] * 2500; - case DP_DS_PORT_TYPE_DVI: - if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) - return 165000; - /* FIXME what to do about DVI dual link? */ - return port_cap[1] * 2500; - default: - return 0; - } -} -EXPORT_SYMBOL(drm_dp_downstream_max_tmds_clock); - -/** - * drm_dp_downstream_min_tmds_clock() - extract downstream facing port min TMDS clock - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * @edid: EDID - * - * Returns: HDMI/DVI downstream facing port min TMDS clock in kHz on success, - * or 0 if max TMDS clock not defined - */ -int drm_dp_downstream_min_tmds_clock(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4], - const struct edid *edid) -{ - if (!drm_dp_is_branch(dpcd)) - return 0; - - if (dpcd[DP_DPCD_REV] < 0x11) { - switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { - case DP_DWN_STRM_PORT_TYPE_TMDS: - return 25000; - default: - return 0; - } - } - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_DP_DUALMODE: - if (is_edid_digital_input_dp(edid)) - return 0; - fallthrough; - case DP_DS_PORT_TYPE_DVI: - case DP_DS_PORT_TYPE_HDMI: - /* - * Unclear whether the protocol converter could - * utilize pixel replication. Assume it won't. - */ - return 25000; - default: - return 0; - } -} -EXPORT_SYMBOL(drm_dp_downstream_min_tmds_clock); - -/** - * drm_dp_downstream_max_bpc() - extract downstream facing port max - * bits per component - * @dpcd: DisplayPort configuration data - * @port_cap: downstream facing port capabilities - * @edid: EDID - * - * Returns: Max bpc on success or 0 if max bpc not defined - */ -int drm_dp_downstream_max_bpc(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4], - const struct edid *edid) -{ - if (!drm_dp_is_branch(dpcd)) - return 0; - - if (dpcd[DP_DPCD_REV] < 0x11) { - switch (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) { - case DP_DWN_STRM_PORT_TYPE_DP: - return 0; - default: - return 8; - } - } - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_DP: - return 0; - case DP_DS_PORT_TYPE_DP_DUALMODE: - if (is_edid_digital_input_dp(edid)) - return 0; - fallthrough; - case DP_DS_PORT_TYPE_HDMI: - case DP_DS_PORT_TYPE_DVI: - case DP_DS_PORT_TYPE_VGA: - if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) - return 8; - - switch (port_cap[2] & DP_DS_MAX_BPC_MASK) { - case DP_DS_8BPC: - return 8; - case DP_DS_10BPC: - return 10; - case DP_DS_12BPC: - return 12; - case DP_DS_16BPC: - return 16; - default: - return 8; - } - break; - default: - return 8; - } -} -EXPORT_SYMBOL(drm_dp_downstream_max_bpc); - -/** - * drm_dp_downstream_420_passthrough() - determine downstream facing port - * YCbCr 4:2:0 pass-through capability - * @dpcd: DisplayPort configuration data - * @port_cap: downstream facing port capabilities - * - * Returns: whether the downstream facing port can pass through YCbCr 4:2:0 - */ -bool drm_dp_downstream_420_passthrough(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4]) -{ - if (!drm_dp_is_branch(dpcd)) - return false; - - if (dpcd[DP_DPCD_REV] < 0x13) - return false; - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_DP: - return true; - case DP_DS_PORT_TYPE_HDMI: - if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) - return false; - - return port_cap[3] & DP_DS_HDMI_YCBCR420_PASS_THROUGH; - default: - return false; - } -} -EXPORT_SYMBOL(drm_dp_downstream_420_passthrough); - -/** - * drm_dp_downstream_444_to_420_conversion() - determine downstream facing port - * YCbCr 4:4:4->4:2:0 conversion capability - * @dpcd: DisplayPort configuration data - * @port_cap: downstream facing port capabilities - * - * Returns: whether the downstream facing port can convert YCbCr 4:4:4 to 4:2:0 - */ -bool drm_dp_downstream_444_to_420_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4]) -{ - if (!drm_dp_is_branch(dpcd)) - return false; - - if (dpcd[DP_DPCD_REV] < 0x13) - return false; - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_HDMI: - if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) - return false; - - return port_cap[3] & DP_DS_HDMI_YCBCR444_TO_420_CONV; - default: - return false; - } -} -EXPORT_SYMBOL(drm_dp_downstream_444_to_420_conversion); - -/** - * drm_dp_downstream_rgb_to_ycbcr_conversion() - determine downstream facing port - * RGB->YCbCr conversion capability - * @dpcd: DisplayPort configuration data - * @port_cap: downstream facing port capabilities - * @color_spc: Colorspace for which conversion cap is sought - * - * Returns: whether the downstream facing port can convert RGB->YCbCr for a given - * colorspace. - */ -bool drm_dp_downstream_rgb_to_ycbcr_conversion(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4], - u8 color_spc) -{ - if (!drm_dp_is_branch(dpcd)) - return false; - - if (dpcd[DP_DPCD_REV] < 0x13) - return false; - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_HDMI: - if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DETAILED_CAP_INFO_AVAILABLE) == 0) - return false; - - return port_cap[3] & color_spc; - default: - return false; - } -} -EXPORT_SYMBOL(drm_dp_downstream_rgb_to_ycbcr_conversion); - -/** - * drm_dp_downstream_mode() - return a mode for downstream facing port - * @dev: DRM device - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * - * Provides a suitable mode for downstream facing ports without EDID. - * - * Returns: A new drm_display_mode on success or NULL on failure - */ -struct drm_display_mode * -drm_dp_downstream_mode(struct drm_device *dev, - const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4]) - -{ - u8 vic; - - if (!drm_dp_is_branch(dpcd)) - return NULL; - - if (dpcd[DP_DPCD_REV] < 0x11) - return NULL; - - switch (port_cap[0] & DP_DS_PORT_TYPE_MASK) { - case DP_DS_PORT_TYPE_NON_EDID: - switch (port_cap[0] & DP_DS_NON_EDID_MASK) { - case DP_DS_NON_EDID_720x480i_60: - vic = 6; - break; - case DP_DS_NON_EDID_720x480i_50: - vic = 21; - break; - case DP_DS_NON_EDID_1920x1080i_60: - vic = 5; - break; - case DP_DS_NON_EDID_1920x1080i_50: - vic = 20; - break; - case DP_DS_NON_EDID_1280x720_60: - vic = 4; - break; - case DP_DS_NON_EDID_1280x720_50: - vic = 19; - break; - default: - return NULL; - } - return drm_display_mode_from_cea_vic(dev, vic); - default: - return NULL; - } -} -EXPORT_SYMBOL(drm_dp_downstream_mode); - -/** - * drm_dp_downstream_id() - identify branch device - * @aux: DisplayPort AUX channel - * @id: DisplayPort branch device id - * - * Returns branch device id on success or NULL on failure - */ -int drm_dp_downstream_id(struct drm_dp_aux *aux, char id[6]) -{ - return drm_dp_dpcd_read(aux, DP_BRANCH_ID, id, 6); -} -EXPORT_SYMBOL(drm_dp_downstream_id); - -/** - * drm_dp_downstream_debug() - debug DP branch devices - * @m: pointer for debugfs file - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * @edid: EDID - * @aux: DisplayPort AUX channel - * - */ -void drm_dp_downstream_debug(struct seq_file *m, - const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4], - const struct edid *edid, - struct drm_dp_aux *aux) -{ - bool detailed_cap_info = dpcd[DP_DOWNSTREAMPORT_PRESENT] & - DP_DETAILED_CAP_INFO_AVAILABLE; - int clk; - int bpc; - char id[7]; - int len; - uint8_t rev[2]; - int type = port_cap[0] & DP_DS_PORT_TYPE_MASK; - bool branch_device = drm_dp_is_branch(dpcd); - - seq_printf(m, "\tDP branch device present: %s\n", - str_yes_no(branch_device)); - - if (!branch_device) - return; - - switch (type) { - case DP_DS_PORT_TYPE_DP: - seq_puts(m, "\t\tType: DisplayPort\n"); - break; - case DP_DS_PORT_TYPE_VGA: - seq_puts(m, "\t\tType: VGA\n"); - break; - case DP_DS_PORT_TYPE_DVI: - seq_puts(m, "\t\tType: DVI\n"); - break; - case DP_DS_PORT_TYPE_HDMI: - seq_puts(m, "\t\tType: HDMI\n"); - break; - case DP_DS_PORT_TYPE_NON_EDID: - seq_puts(m, "\t\tType: others without EDID support\n"); - break; - case DP_DS_PORT_TYPE_DP_DUALMODE: - seq_puts(m, "\t\tType: DP++\n"); - break; - case DP_DS_PORT_TYPE_WIRELESS: - seq_puts(m, "\t\tType: Wireless\n"); - break; - default: - seq_puts(m, "\t\tType: N/A\n"); - } - - memset(id, 0, sizeof(id)); - drm_dp_downstream_id(aux, id); - seq_printf(m, "\t\tID: %s\n", id); - - len = drm_dp_dpcd_read(aux, DP_BRANCH_HW_REV, &rev[0], 1); - if (len > 0) - seq_printf(m, "\t\tHW: %d.%d\n", - (rev[0] & 0xf0) >> 4, rev[0] & 0xf); - - len = drm_dp_dpcd_read(aux, DP_BRANCH_SW_REV, rev, 2); - if (len > 0) - seq_printf(m, "\t\tSW: %d.%d\n", rev[0], rev[1]); - - if (detailed_cap_info) { - clk = drm_dp_downstream_max_dotclock(dpcd, port_cap); - if (clk > 0) - seq_printf(m, "\t\tMax dot clock: %d kHz\n", clk); - - clk = drm_dp_downstream_max_tmds_clock(dpcd, port_cap, edid); - if (clk > 0) - seq_printf(m, "\t\tMax TMDS clock: %d kHz\n", clk); - - clk = drm_dp_downstream_min_tmds_clock(dpcd, port_cap, edid); - if (clk > 0) - seq_printf(m, "\t\tMin TMDS clock: %d kHz\n", clk); - - bpc = drm_dp_downstream_max_bpc(dpcd, port_cap, edid); - - if (bpc > 0) - seq_printf(m, "\t\tMax bpc: %d\n", bpc); - } -} -EXPORT_SYMBOL(drm_dp_downstream_debug); - -/** - * drm_dp_subconnector_type() - get DP branch device type - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - */ -enum drm_mode_subconnector -drm_dp_subconnector_type(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4]) -{ - int type; - if (!drm_dp_is_branch(dpcd)) - return DRM_MODE_SUBCONNECTOR_Native; - /* DP 1.0 approach */ - if (dpcd[DP_DPCD_REV] == DP_DPCD_REV_10) { - type = dpcd[DP_DOWNSTREAMPORT_PRESENT] & - DP_DWN_STRM_PORT_TYPE_MASK; - - switch (type) { - case DP_DWN_STRM_PORT_TYPE_TMDS: - /* Can be HDMI or DVI-D, DVI-D is a safer option */ - return DRM_MODE_SUBCONNECTOR_DVID; - case DP_DWN_STRM_PORT_TYPE_ANALOG: - /* Can be VGA or DVI-A, VGA is more popular */ - return DRM_MODE_SUBCONNECTOR_VGA; - case DP_DWN_STRM_PORT_TYPE_DP: - return DRM_MODE_SUBCONNECTOR_DisplayPort; - case DP_DWN_STRM_PORT_TYPE_OTHER: - default: - return DRM_MODE_SUBCONNECTOR_Unknown; - } - } - type = port_cap[0] & DP_DS_PORT_TYPE_MASK; - - switch (type) { - case DP_DS_PORT_TYPE_DP: - case DP_DS_PORT_TYPE_DP_DUALMODE: - return DRM_MODE_SUBCONNECTOR_DisplayPort; - case DP_DS_PORT_TYPE_VGA: - return DRM_MODE_SUBCONNECTOR_VGA; - case DP_DS_PORT_TYPE_DVI: - return DRM_MODE_SUBCONNECTOR_DVID; - case DP_DS_PORT_TYPE_HDMI: - return DRM_MODE_SUBCONNECTOR_HDMIA; - case DP_DS_PORT_TYPE_WIRELESS: - return DRM_MODE_SUBCONNECTOR_Wireless; - case DP_DS_PORT_TYPE_NON_EDID: - default: - return DRM_MODE_SUBCONNECTOR_Unknown; - } -} -EXPORT_SYMBOL(drm_dp_subconnector_type); - -/** - * drm_dp_set_subconnector_property - set subconnector for DP connector - * @connector: connector to set property on - * @status: connector status - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * - * Called by a driver on every detect event. - */ -void drm_dp_set_subconnector_property(struct drm_connector *connector, - enum drm_connector_status status, - const u8 *dpcd, - const u8 port_cap[4]) -{ - enum drm_mode_subconnector subconnector = DRM_MODE_SUBCONNECTOR_Unknown; - - if (status == connector_status_connected) - subconnector = drm_dp_subconnector_type(dpcd, port_cap); - drm_object_property_set_value(&connector->base, - connector->dev->mode_config.dp_subconnector_property, - subconnector); -} -EXPORT_SYMBOL(drm_dp_set_subconnector_property); - -/** - * drm_dp_read_sink_count_cap() - Check whether a given connector has a valid sink - * count - * @connector: The DRM connector to check - * @dpcd: A cached copy of the connector's DPCD RX capabilities - * @desc: A cached copy of the connector's DP descriptor - * - * See also: drm_dp_read_sink_count() - * - * Returns: %True if the (e)DP connector has a valid sink count that should - * be probed, %false otherwise. - */ -bool drm_dp_read_sink_count_cap(struct drm_connector *connector, - const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const struct drm_dp_desc *desc) -{ - /* Some eDP panels don't set a valid value for the sink count */ - return connector->connector_type != DRM_MODE_CONNECTOR_eDP && - dpcd[DP_DPCD_REV] >= DP_DPCD_REV_11 && - dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT && - !drm_dp_has_quirk(desc, DP_DPCD_QUIRK_NO_SINK_COUNT); -} -EXPORT_SYMBOL(drm_dp_read_sink_count_cap); - -/** - * drm_dp_read_sink_count() - Retrieve the sink count for a given sink - * @aux: The DP AUX channel to use - * - * See also: drm_dp_read_sink_count_cap() - * - * Returns: The current sink count reported by @aux, or a negative error code - * otherwise. - */ -int drm_dp_read_sink_count(struct drm_dp_aux *aux) -{ - u8 count; - int ret; - - ret = drm_dp_dpcd_readb(aux, DP_SINK_COUNT, &count); - if (ret < 0) - return ret; - if (ret != 1) - return -EIO; - - return DP_GET_SINK_COUNT(count); -} -EXPORT_SYMBOL(drm_dp_read_sink_count); - -/* - * I2C-over-AUX implementation - */ - -static u32 drm_dp_i2c_functionality(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | - I2C_FUNC_SMBUS_READ_BLOCK_DATA | - I2C_FUNC_SMBUS_BLOCK_PROC_CALL | - I2C_FUNC_10BIT_ADDR; -} - -static void drm_dp_i2c_msg_write_status_update(struct drm_dp_aux_msg *msg) -{ - /* - * In case of i2c defer or short i2c ack reply to a write, - * we need to switch to WRITE_STATUS_UPDATE to drain the - * rest of the message - */ - if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_WRITE) { - msg->request &= DP_AUX_I2C_MOT; - msg->request |= DP_AUX_I2C_WRITE_STATUS_UPDATE; - } -} - -#define AUX_PRECHARGE_LEN 10 /* 10 to 16 */ -#define AUX_SYNC_LEN (16 + 4) /* preamble + AUX_SYNC_END */ -#define AUX_STOP_LEN 4 -#define AUX_CMD_LEN 4 -#define AUX_ADDRESS_LEN 20 -#define AUX_REPLY_PAD_LEN 4 -#define AUX_LENGTH_LEN 8 - -/* - * Calculate the duration of the AUX request/reply in usec. Gives the - * "best" case estimate, ie. successful while as short as possible. - */ -static int drm_dp_aux_req_duration(const struct drm_dp_aux_msg *msg) -{ - int len = AUX_PRECHARGE_LEN + AUX_SYNC_LEN + AUX_STOP_LEN + - AUX_CMD_LEN + AUX_ADDRESS_LEN + AUX_LENGTH_LEN; - - if ((msg->request & DP_AUX_I2C_READ) == 0) - len += msg->size * 8; - - return len; -} - -static int drm_dp_aux_reply_duration(const struct drm_dp_aux_msg *msg) -{ - int len = AUX_PRECHARGE_LEN + AUX_SYNC_LEN + AUX_STOP_LEN + - AUX_CMD_LEN + AUX_REPLY_PAD_LEN; - - /* - * For read we expect what was asked. For writes there will - * be 0 or 1 data bytes. Assume 0 for the "best" case. - */ - if (msg->request & DP_AUX_I2C_READ) - len += msg->size * 8; - - return len; -} - -#define I2C_START_LEN 1 -#define I2C_STOP_LEN 1 -#define I2C_ADDR_LEN 9 /* ADDRESS + R/W + ACK/NACK */ -#define I2C_DATA_LEN 9 /* DATA + ACK/NACK */ - -/* - * Calculate the length of the i2c transfer in usec, assuming - * the i2c bus speed is as specified. Gives the the "worst" - * case estimate, ie. successful while as long as possible. - * Doesn't account the the "MOT" bit, and instead assumes each - * message includes a START, ADDRESS and STOP. Neither does it - * account for additional random variables such as clock stretching. - */ -static int drm_dp_i2c_msg_duration(const struct drm_dp_aux_msg *msg, - int i2c_speed_khz) -{ - /* AUX bitrate is 1MHz, i2c bitrate as specified */ - return DIV_ROUND_UP((I2C_START_LEN + I2C_ADDR_LEN + - msg->size * I2C_DATA_LEN + - I2C_STOP_LEN) * 1000, i2c_speed_khz); -} - -/* - * Determine how many retries should be attempted to successfully transfer - * the specified message, based on the estimated durations of the - * i2c and AUX transfers. - */ -static int drm_dp_i2c_retry_count(const struct drm_dp_aux_msg *msg, - int i2c_speed_khz) -{ - int aux_time_us = drm_dp_aux_req_duration(msg) + - drm_dp_aux_reply_duration(msg); - int i2c_time_us = drm_dp_i2c_msg_duration(msg, i2c_speed_khz); - - return DIV_ROUND_UP(i2c_time_us, aux_time_us + AUX_RETRY_INTERVAL); -} - -/* - * FIXME currently assumes 10 kHz as some real world devices seem - * to require it. We should query/set the speed via DPCD if supported. - */ -static int dp_aux_i2c_speed_khz __read_mostly = 10; -module_param_unsafe(dp_aux_i2c_speed_khz, int, 0644); -MODULE_PARM_DESC(dp_aux_i2c_speed_khz, - "Assumed speed of the i2c bus in kHz, (1-400, default 10)"); - -/* - * Transfer a single I2C-over-AUX message and handle various error conditions, - * retrying the transaction as appropriate. It is assumed that the - * &drm_dp_aux.transfer function does not modify anything in the msg other than the - * reply field. - * - * Returns bytes transferred on success, or a negative error code on failure. - */ -static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) -{ - unsigned int retry, defer_i2c; - int ret; - /* - * DP1.2 sections 2.7.7.1.5.6.1 and 2.7.7.1.6.6.1: A DP Source device - * is required to retry at least seven times upon receiving AUX_DEFER - * before giving up the AUX transaction. - * - * We also try to account for the i2c bus speed. - */ - int max_retries = max(7, drm_dp_i2c_retry_count(msg, dp_aux_i2c_speed_khz)); - - for (retry = 0, defer_i2c = 0; retry < (max_retries + defer_i2c); retry++) { - ret = aux->transfer(aux, msg); - if (ret < 0) { - if (ret == -EBUSY) - continue; - - /* - * While timeouts can be errors, they're usually normal - * behavior (for instance, when a driver tries to - * communicate with a non-existent DisplayPort device). - * Avoid spamming the kernel log with timeout errors. - */ - if (ret == -ETIMEDOUT) - drm_dbg_kms_ratelimited(aux->drm_dev, "%s: transaction timed out\n", - aux->name); - else - drm_dbg_kms(aux->drm_dev, "%s: transaction failed: %d\n", - aux->name, ret); - return ret; - } - - - switch (msg->reply & DP_AUX_NATIVE_REPLY_MASK) { - case DP_AUX_NATIVE_REPLY_ACK: - /* - * For I2C-over-AUX transactions this isn't enough, we - * need to check for the I2C ACK reply. - */ - break; - - case DP_AUX_NATIVE_REPLY_NACK: - drm_dbg_kms(aux->drm_dev, "%s: native nack (result=%d, size=%zu)\n", - aux->name, ret, msg->size); - return -EREMOTEIO; - - case DP_AUX_NATIVE_REPLY_DEFER: - drm_dbg_kms(aux->drm_dev, "%s: native defer\n", aux->name); - /* - * We could check for I2C bit rate capabilities and if - * available adjust this interval. We could also be - * more careful with DP-to-legacy adapters where a - * long legacy cable may force very low I2C bit rates. - * - * For now just defer for long enough to hopefully be - * safe for all use-cases. - */ - usleep_range(AUX_RETRY_INTERVAL, AUX_RETRY_INTERVAL + 100); - continue; - - default: - drm_err(aux->drm_dev, "%s: invalid native reply %#04x\n", - aux->name, msg->reply); - return -EREMOTEIO; - } - - switch (msg->reply & DP_AUX_I2C_REPLY_MASK) { - case DP_AUX_I2C_REPLY_ACK: - /* - * Both native ACK and I2C ACK replies received. We - * can assume the transfer was successful. - */ - if (ret != msg->size) - drm_dp_i2c_msg_write_status_update(msg); - return ret; - - case DP_AUX_I2C_REPLY_NACK: - drm_dbg_kms(aux->drm_dev, "%s: I2C nack (result=%d, size=%zu)\n", - aux->name, ret, msg->size); - aux->i2c_nack_count++; - return -EREMOTEIO; - - case DP_AUX_I2C_REPLY_DEFER: - drm_dbg_kms(aux->drm_dev, "%s: I2C defer\n", aux->name); - /* DP Compliance Test 4.2.2.5 Requirement: - * Must have at least 7 retries for I2C defers on the - * transaction to pass this test - */ - aux->i2c_defer_count++; - if (defer_i2c < 7) - defer_i2c++; - usleep_range(AUX_RETRY_INTERVAL, AUX_RETRY_INTERVAL + 100); - drm_dp_i2c_msg_write_status_update(msg); - - continue; - - default: - drm_err(aux->drm_dev, "%s: invalid I2C reply %#04x\n", - aux->name, msg->reply); - return -EREMOTEIO; - } - } - - drm_dbg_kms(aux->drm_dev, "%s: Too many retries, giving up\n", aux->name); - return -EREMOTEIO; -} - -static void drm_dp_i2c_msg_set_request(struct drm_dp_aux_msg *msg, - const struct i2c_msg *i2c_msg) -{ - msg->request = (i2c_msg->flags & I2C_M_RD) ? - DP_AUX_I2C_READ : DP_AUX_I2C_WRITE; - if (!(i2c_msg->flags & I2C_M_STOP)) - msg->request |= DP_AUX_I2C_MOT; -} - -/* - * Keep retrying drm_dp_i2c_do_msg until all data has been transferred. - * - * Returns an error code on failure, or a recommended transfer size on success. - */ -static int drm_dp_i2c_drain_msg(struct drm_dp_aux *aux, struct drm_dp_aux_msg *orig_msg) -{ - int err, ret = orig_msg->size; - struct drm_dp_aux_msg msg = *orig_msg; - - while (msg.size > 0) { - err = drm_dp_i2c_do_msg(aux, &msg); - if (err <= 0) - return err == 0 ? -EPROTO : err; - - if (err < msg.size && err < ret) { - drm_dbg_kms(aux->drm_dev, - "%s: Partial I2C reply: requested %zu bytes got %d bytes\n", - aux->name, msg.size, err); - ret = err; - } - - msg.size -= err; - msg.buffer += err; - } - - return ret; -} - -/* - * Bizlink designed DP->DVI-D Dual Link adapters require the I2C over AUX - * packets to be as large as possible. If not, the I2C transactions never - * succeed. Hence the default is maximum. - */ -static int dp_aux_i2c_transfer_size __read_mostly = DP_AUX_MAX_PAYLOAD_BYTES; -module_param_unsafe(dp_aux_i2c_transfer_size, int, 0644); -MODULE_PARM_DESC(dp_aux_i2c_transfer_size, - "Number of bytes to transfer in a single I2C over DP AUX CH message, (1-16, default 16)"); - -static int drm_dp_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, - int num) -{ - struct drm_dp_aux *aux = adapter->algo_data; - unsigned int i, j; - unsigned transfer_size; - struct drm_dp_aux_msg msg; - int err = 0; - - dp_aux_i2c_transfer_size = clamp(dp_aux_i2c_transfer_size, 1, DP_AUX_MAX_PAYLOAD_BYTES); - - memset(&msg, 0, sizeof(msg)); - - for (i = 0; i < num; i++) { - msg.address = msgs[i].addr; - drm_dp_i2c_msg_set_request(&msg, &msgs[i]); - /* Send a bare address packet to start the transaction. - * Zero sized messages specify an address only (bare - * address) transaction. - */ - msg.buffer = NULL; - msg.size = 0; - err = drm_dp_i2c_do_msg(aux, &msg); - - /* - * Reset msg.request in case in case it got - * changed into a WRITE_STATUS_UPDATE. - */ - drm_dp_i2c_msg_set_request(&msg, &msgs[i]); - - if (err < 0) - break; - /* We want each transaction to be as large as possible, but - * we'll go to smaller sizes if the hardware gives us a - * short reply. - */ - transfer_size = dp_aux_i2c_transfer_size; - for (j = 0; j < msgs[i].len; j += msg.size) { - msg.buffer = msgs[i].buf + j; - msg.size = min(transfer_size, msgs[i].len - j); - - err = drm_dp_i2c_drain_msg(aux, &msg); - - /* - * Reset msg.request in case in case it got - * changed into a WRITE_STATUS_UPDATE. - */ - drm_dp_i2c_msg_set_request(&msg, &msgs[i]); - - if (err < 0) - break; - transfer_size = err; - } - if (err < 0) - break; - } - if (err >= 0) - err = num; - /* Send a bare address packet to close out the transaction. - * Zero sized messages specify an address only (bare - * address) transaction. - */ - msg.request &= ~DP_AUX_I2C_MOT; - msg.buffer = NULL; - msg.size = 0; - (void)drm_dp_i2c_do_msg(aux, &msg); - - return err; -} - -static const struct i2c_algorithm drm_dp_i2c_algo = { - .functionality = drm_dp_i2c_functionality, - .master_xfer = drm_dp_i2c_xfer, -}; - -static struct drm_dp_aux *i2c_to_aux(struct i2c_adapter *i2c) -{ - return container_of(i2c, struct drm_dp_aux, ddc); -} - -static void lock_bus(struct i2c_adapter *i2c, unsigned int flags) -{ - mutex_lock(&i2c_to_aux(i2c)->hw_mutex); -} - -static int trylock_bus(struct i2c_adapter *i2c, unsigned int flags) -{ - return mutex_trylock(&i2c_to_aux(i2c)->hw_mutex); -} - -static void unlock_bus(struct i2c_adapter *i2c, unsigned int flags) -{ - mutex_unlock(&i2c_to_aux(i2c)->hw_mutex); -} - -static const struct i2c_lock_operations drm_dp_i2c_lock_ops = { - .lock_bus = lock_bus, - .trylock_bus = trylock_bus, - .unlock_bus = unlock_bus, -}; - -static int drm_dp_aux_get_crc(struct drm_dp_aux *aux, u8 *crc) -{ - u8 buf, count; - int ret; - - ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf); - if (ret < 0) - return ret; - - WARN_ON(!(buf & DP_TEST_SINK_START)); - - ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK_MISC, &buf); - if (ret < 0) - return ret; - - count = buf & DP_TEST_COUNT_MASK; - if (count == aux->crc_count) - return -EAGAIN; /* No CRC yet */ - - aux->crc_count = count; - - /* - * At DP_TEST_CRC_R_CR, there's 6 bytes containing CRC data, 2 bytes - * per component (RGB or CrYCb). - */ - ret = drm_dp_dpcd_read(aux, DP_TEST_CRC_R_CR, crc, 6); - if (ret < 0) - return ret; - - return 0; -} - -static void drm_dp_aux_crc_work(struct work_struct *work) -{ - struct drm_dp_aux *aux = container_of(work, struct drm_dp_aux, - crc_work); - struct drm_crtc *crtc; - u8 crc_bytes[6]; - uint32_t crcs[3]; - int ret; - - if (WARN_ON(!aux->crtc)) - return; - - crtc = aux->crtc; - while (crtc->crc.opened) { - drm_crtc_wait_one_vblank(crtc); - if (!crtc->crc.opened) - break; - - ret = drm_dp_aux_get_crc(aux, crc_bytes); - if (ret == -EAGAIN) { - usleep_range(1000, 2000); - ret = drm_dp_aux_get_crc(aux, crc_bytes); - } - - if (ret == -EAGAIN) { - drm_dbg_kms(aux->drm_dev, "%s: Get CRC failed after retrying: %d\n", - aux->name, ret); - continue; - } else if (ret) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to get a CRC: %d\n", aux->name, ret); - continue; - } - - crcs[0] = crc_bytes[0] | crc_bytes[1] << 8; - crcs[1] = crc_bytes[2] | crc_bytes[3] << 8; - crcs[2] = crc_bytes[4] | crc_bytes[5] << 8; - drm_crtc_add_crc_entry(crtc, false, 0, crcs); - } -} - -/** - * drm_dp_remote_aux_init() - minimally initialise a remote aux channel - * @aux: DisplayPort AUX channel - * - * Used for remote aux channel in general. Merely initialize the crc work - * struct. - */ -void drm_dp_remote_aux_init(struct drm_dp_aux *aux) -{ - INIT_WORK(&aux->crc_work, drm_dp_aux_crc_work); -} -EXPORT_SYMBOL(drm_dp_remote_aux_init); - -/** - * drm_dp_aux_init() - minimally initialise an aux channel - * @aux: DisplayPort AUX channel - * - * If you need to use the drm_dp_aux's i2c adapter prior to registering it with - * the outside world, call drm_dp_aux_init() first. For drivers which are - * grandparents to their AUX adapters (e.g. the AUX adapter is parented by a - * &drm_connector), you must still call drm_dp_aux_register() once the connector - * has been registered to allow userspace access to the auxiliary DP channel. - * Likewise, for such drivers you should also assign &drm_dp_aux.drm_dev as - * early as possible so that the &drm_device that corresponds to the AUX adapter - * may be mentioned in debugging output from the DRM DP helpers. - * - * For devices which use a separate platform device for their AUX adapters, this - * may be called as early as required by the driver. - * - */ -void drm_dp_aux_init(struct drm_dp_aux *aux) -{ - mutex_init(&aux->hw_mutex); - mutex_init(&aux->cec.lock); - INIT_WORK(&aux->crc_work, drm_dp_aux_crc_work); - - aux->ddc.algo = &drm_dp_i2c_algo; - aux->ddc.algo_data = aux; - aux->ddc.retries = 3; - - aux->ddc.lock_ops = &drm_dp_i2c_lock_ops; -} -EXPORT_SYMBOL(drm_dp_aux_init); - -/** - * drm_dp_aux_register() - initialise and register aux channel - * @aux: DisplayPort AUX channel - * - * Automatically calls drm_dp_aux_init() if this hasn't been done yet. This - * should only be called once the parent of @aux, &drm_dp_aux.dev, is - * initialized. For devices which are grandparents of their AUX channels, - * &drm_dp_aux.dev will typically be the &drm_connector &device which - * corresponds to @aux. For these devices, it's advised to call - * drm_dp_aux_register() in &drm_connector_funcs.late_register, and likewise to - * call drm_dp_aux_unregister() in &drm_connector_funcs.early_unregister. - * Functions which don't follow this will likely Oops when - * %CONFIG_DRM_DP_AUX_CHARDEV is enabled. - * - * For devices where the AUX channel is a device that exists independently of - * the &drm_device that uses it, such as SoCs and bridge devices, it is - * recommended to call drm_dp_aux_register() after a &drm_device has been - * assigned to &drm_dp_aux.drm_dev, and likewise to call - * drm_dp_aux_unregister() once the &drm_device should no longer be associated - * with the AUX channel (e.g. on bridge detach). - * - * Drivers which need to use the aux channel before either of the two points - * mentioned above need to call drm_dp_aux_init() in order to use the AUX - * channel before registration. - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_aux_register(struct drm_dp_aux *aux) -{ - int ret; - - WARN_ON_ONCE(!aux->drm_dev); - - if (!aux->ddc.algo) - drm_dp_aux_init(aux); - - aux->ddc.class = I2C_CLASS_DDC; - aux->ddc.owner = THIS_MODULE; - aux->ddc.dev.parent = aux->dev; - - strlcpy(aux->ddc.name, aux->name ? aux->name : dev_name(aux->dev), - sizeof(aux->ddc.name)); - - ret = drm_dp_aux_register_devnode(aux); - if (ret) - return ret; - - ret = i2c_add_adapter(&aux->ddc); - if (ret) { - drm_dp_aux_unregister_devnode(aux); - return ret; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_aux_register); - -/** - * drm_dp_aux_unregister() - unregister an AUX adapter - * @aux: DisplayPort AUX channel - */ -void drm_dp_aux_unregister(struct drm_dp_aux *aux) -{ - drm_dp_aux_unregister_devnode(aux); - i2c_del_adapter(&aux->ddc); -} -EXPORT_SYMBOL(drm_dp_aux_unregister); - -#define PSR_SETUP_TIME(x) [DP_PSR_SETUP_TIME_ ## x >> DP_PSR_SETUP_TIME_SHIFT] = (x) - -/** - * drm_dp_psr_setup_time() - PSR setup in time usec - * @psr_cap: PSR capabilities from DPCD - * - * Returns: - * PSR setup time for the panel in microseconds, negative - * error code on failure. - */ -int drm_dp_psr_setup_time(const u8 psr_cap[EDP_PSR_RECEIVER_CAP_SIZE]) -{ - static const u16 psr_setup_time_us[] = { - PSR_SETUP_TIME(330), - PSR_SETUP_TIME(275), - PSR_SETUP_TIME(220), - PSR_SETUP_TIME(165), - PSR_SETUP_TIME(110), - PSR_SETUP_TIME(55), - PSR_SETUP_TIME(0), - }; - int i; - - i = (psr_cap[1] & DP_PSR_SETUP_TIME_MASK) >> DP_PSR_SETUP_TIME_SHIFT; - if (i >= ARRAY_SIZE(psr_setup_time_us)) - return -EINVAL; - - return psr_setup_time_us[i]; -} -EXPORT_SYMBOL(drm_dp_psr_setup_time); - -#undef PSR_SETUP_TIME - -/** - * drm_dp_start_crc() - start capture of frame CRCs - * @aux: DisplayPort AUX channel - * @crtc: CRTC displaying the frames whose CRCs are to be captured - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_start_crc(struct drm_dp_aux *aux, struct drm_crtc *crtc) -{ - u8 buf; - int ret; - - ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf); - if (ret < 0) - return ret; - - ret = drm_dp_dpcd_writeb(aux, DP_TEST_SINK, buf | DP_TEST_SINK_START); - if (ret < 0) - return ret; - - aux->crc_count = 0; - aux->crtc = crtc; - schedule_work(&aux->crc_work); - - return 0; -} -EXPORT_SYMBOL(drm_dp_start_crc); - -/** - * drm_dp_stop_crc() - stop capture of frame CRCs - * @aux: DisplayPort AUX channel - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_stop_crc(struct drm_dp_aux *aux) -{ - u8 buf; - int ret; - - ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf); - if (ret < 0) - return ret; - - ret = drm_dp_dpcd_writeb(aux, DP_TEST_SINK, buf & ~DP_TEST_SINK_START); - if (ret < 0) - return ret; - - flush_work(&aux->crc_work); - aux->crtc = NULL; - - return 0; -} -EXPORT_SYMBOL(drm_dp_stop_crc); - -struct dpcd_quirk { - u8 oui[3]; - u8 device_id[6]; - bool is_branch; - u32 quirks; -}; - -#define OUI(first, second, third) { (first), (second), (third) } -#define DEVICE_ID(first, second, third, fourth, fifth, sixth) \ - { (first), (second), (third), (fourth), (fifth), (sixth) } - -#define DEVICE_ID_ANY DEVICE_ID(0, 0, 0, 0, 0, 0) - -static const struct dpcd_quirk dpcd_quirk_list[] = { - /* Analogix 7737 needs reduced M and N at HBR2 link rates */ - { OUI(0x00, 0x22, 0xb9), DEVICE_ID_ANY, true, BIT(DP_DPCD_QUIRK_CONSTANT_N) }, - /* LG LP140WF6-SPM1 eDP panel */ - { OUI(0x00, 0x22, 0xb9), DEVICE_ID('s', 'i', 'v', 'a', 'r', 'T'), false, BIT(DP_DPCD_QUIRK_CONSTANT_N) }, - /* Apple panels need some additional handling to support PSR */ - { OUI(0x00, 0x10, 0xfa), DEVICE_ID_ANY, false, BIT(DP_DPCD_QUIRK_NO_PSR) }, - /* CH7511 seems to leave SINK_COUNT zeroed */ - { OUI(0x00, 0x00, 0x00), DEVICE_ID('C', 'H', '7', '5', '1', '1'), false, BIT(DP_DPCD_QUIRK_NO_SINK_COUNT) }, - /* Synaptics DP1.4 MST hubs can support DSC without virtual DPCD */ - { OUI(0x90, 0xCC, 0x24), DEVICE_ID_ANY, true, BIT(DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) }, - /* Apple MacBookPro 2017 15 inch eDP Retina panel reports too low DP_MAX_LINK_RATE */ - { OUI(0x00, 0x10, 0xfa), DEVICE_ID(101, 68, 21, 101, 98, 97), false, BIT(DP_DPCD_QUIRK_CAN_DO_MAX_LINK_RATE_3_24_GBPS) }, -}; - -#undef OUI - -/* - * Get a bit mask of DPCD quirks for the sink/branch device identified by - * ident. The quirk data is shared but it's up to the drivers to act on the - * data. - * - * For now, only the OUI (first three bytes) is used, but this may be extended - * to device identification string and hardware/firmware revisions later. - */ -static u32 -drm_dp_get_quirks(const struct drm_dp_dpcd_ident *ident, bool is_branch) -{ - const struct dpcd_quirk *quirk; - u32 quirks = 0; - int i; - u8 any_device[] = DEVICE_ID_ANY; - - for (i = 0; i < ARRAY_SIZE(dpcd_quirk_list); i++) { - quirk = &dpcd_quirk_list[i]; - - if (quirk->is_branch != is_branch) - continue; - - if (memcmp(quirk->oui, ident->oui, sizeof(ident->oui)) != 0) - continue; - - if (memcmp(quirk->device_id, any_device, sizeof(any_device)) != 0 && - memcmp(quirk->device_id, ident->device_id, sizeof(ident->device_id)) != 0) - continue; - - quirks |= quirk->quirks; - } - - return quirks; -} - -#undef DEVICE_ID_ANY -#undef DEVICE_ID - -/** - * drm_dp_read_desc - read sink/branch descriptor from DPCD - * @aux: DisplayPort AUX channel - * @desc: Device descriptor to fill from DPCD - * @is_branch: true for branch devices, false for sink devices - * - * Read DPCD 0x400 (sink) or 0x500 (branch) into @desc. Also debug log the - * identification. - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_read_desc(struct drm_dp_aux *aux, struct drm_dp_desc *desc, - bool is_branch) -{ - struct drm_dp_dpcd_ident *ident = &desc->ident; - unsigned int offset = is_branch ? DP_BRANCH_OUI : DP_SINK_OUI; - int ret, dev_id_len; - - ret = drm_dp_dpcd_read(aux, offset, ident, sizeof(*ident)); - if (ret < 0) - return ret; - - desc->quirks = drm_dp_get_quirks(ident, is_branch); - - dev_id_len = strnlen(ident->device_id, sizeof(ident->device_id)); - - drm_dbg_kms(aux->drm_dev, - "%s: DP %s: OUI %*phD dev-ID %*pE HW-rev %d.%d SW-rev %d.%d quirks 0x%04x\n", - aux->name, is_branch ? "branch" : "sink", - (int)sizeof(ident->oui), ident->oui, dev_id_len, - ident->device_id, ident->hw_rev >> 4, ident->hw_rev & 0xf, - ident->sw_major_rev, ident->sw_minor_rev, desc->quirks); - - return 0; -} -EXPORT_SYMBOL(drm_dp_read_desc); - -/** - * drm_dp_dsc_sink_max_slice_count() - Get the max slice count - * supported by the DSC sink. - * @dsc_dpcd: DSC capabilities from DPCD - * @is_edp: true if its eDP, false for DP - * - * Read the slice capabilities DPCD register from DSC sink to get - * the maximum slice count supported. This is used to populate - * the DSC parameters in the &struct drm_dsc_config by the driver. - * Driver creates an infoframe using these parameters to populate - * &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC - * infoframe using the helper function drm_dsc_pps_infoframe_pack() - * - * Returns: - * Maximum slice count supported by DSC sink or 0 its invalid - */ -u8 drm_dp_dsc_sink_max_slice_count(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE], - bool is_edp) -{ - u8 slice_cap1 = dsc_dpcd[DP_DSC_SLICE_CAP_1 - DP_DSC_SUPPORT]; - - if (is_edp) { - /* For eDP, register DSC_SLICE_CAPABILITIES_1 gives slice count */ - if (slice_cap1 & DP_DSC_4_PER_DP_DSC_SINK) - return 4; - if (slice_cap1 & DP_DSC_2_PER_DP_DSC_SINK) - return 2; - if (slice_cap1 & DP_DSC_1_PER_DP_DSC_SINK) - return 1; - } else { - /* For DP, use values from DSC_SLICE_CAP_1 and DSC_SLICE_CAP2 */ - u8 slice_cap2 = dsc_dpcd[DP_DSC_SLICE_CAP_2 - DP_DSC_SUPPORT]; - - if (slice_cap2 & DP_DSC_24_PER_DP_DSC_SINK) - return 24; - if (slice_cap2 & DP_DSC_20_PER_DP_DSC_SINK) - return 20; - if (slice_cap2 & DP_DSC_16_PER_DP_DSC_SINK) - return 16; - if (slice_cap1 & DP_DSC_12_PER_DP_DSC_SINK) - return 12; - if (slice_cap1 & DP_DSC_10_PER_DP_DSC_SINK) - return 10; - if (slice_cap1 & DP_DSC_8_PER_DP_DSC_SINK) - return 8; - if (slice_cap1 & DP_DSC_6_PER_DP_DSC_SINK) - return 6; - if (slice_cap1 & DP_DSC_4_PER_DP_DSC_SINK) - return 4; - if (slice_cap1 & DP_DSC_2_PER_DP_DSC_SINK) - return 2; - if (slice_cap1 & DP_DSC_1_PER_DP_DSC_SINK) - return 1; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_dsc_sink_max_slice_count); - -/** - * drm_dp_dsc_sink_line_buf_depth() - Get the line buffer depth in bits - * @dsc_dpcd: DSC capabilities from DPCD - * - * Read the DSC DPCD register to parse the line buffer depth in bits which is - * number of bits of precision within the decoder line buffer supported by - * the DSC sink. This is used to populate the DSC parameters in the - * &struct drm_dsc_config by the driver. - * Driver creates an infoframe using these parameters to populate - * &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC - * infoframe using the helper function drm_dsc_pps_infoframe_pack() - * - * Returns: - * Line buffer depth supported by DSC panel or 0 its invalid - */ -u8 drm_dp_dsc_sink_line_buf_depth(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE]) -{ - u8 line_buf_depth = dsc_dpcd[DP_DSC_LINE_BUF_BIT_DEPTH - DP_DSC_SUPPORT]; - - switch (line_buf_depth & DP_DSC_LINE_BUF_BIT_DEPTH_MASK) { - case DP_DSC_LINE_BUF_BIT_DEPTH_9: - return 9; - case DP_DSC_LINE_BUF_BIT_DEPTH_10: - return 10; - case DP_DSC_LINE_BUF_BIT_DEPTH_11: - return 11; - case DP_DSC_LINE_BUF_BIT_DEPTH_12: - return 12; - case DP_DSC_LINE_BUF_BIT_DEPTH_13: - return 13; - case DP_DSC_LINE_BUF_BIT_DEPTH_14: - return 14; - case DP_DSC_LINE_BUF_BIT_DEPTH_15: - return 15; - case DP_DSC_LINE_BUF_BIT_DEPTH_16: - return 16; - case DP_DSC_LINE_BUF_BIT_DEPTH_8: - return 8; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_dsc_sink_line_buf_depth); - -/** - * drm_dp_dsc_sink_supported_input_bpcs() - Get all the input bits per component - * values supported by the DSC sink. - * @dsc_dpcd: DSC capabilities from DPCD - * @dsc_bpc: An array to be filled by this helper with supported - * input bpcs. - * - * Read the DSC DPCD from the sink device to parse the supported bits per - * component values. This is used to populate the DSC parameters - * in the &struct drm_dsc_config by the driver. - * Driver creates an infoframe using these parameters to populate - * &struct drm_dsc_pps_infoframe. These are sent to the sink using DSC - * infoframe using the helper function drm_dsc_pps_infoframe_pack() - * - * Returns: - * Number of input BPC values parsed from the DPCD - */ -int drm_dp_dsc_sink_supported_input_bpcs(const u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE], - u8 dsc_bpc[3]) -{ - int num_bpc = 0; - u8 color_depth = dsc_dpcd[DP_DSC_DEC_COLOR_DEPTH_CAP - DP_DSC_SUPPORT]; - - if (color_depth & DP_DSC_12_BPC) - dsc_bpc[num_bpc++] = 12; - if (color_depth & DP_DSC_10_BPC) - dsc_bpc[num_bpc++] = 10; - if (color_depth & DP_DSC_8_BPC) - dsc_bpc[num_bpc++] = 8; - - return num_bpc; -} -EXPORT_SYMBOL(drm_dp_dsc_sink_supported_input_bpcs); - -static int drm_dp_read_lttpr_regs(struct drm_dp_aux *aux, - const u8 dpcd[DP_RECEIVER_CAP_SIZE], int address, - u8 *buf, int buf_size) -{ - /* - * At least the DELL P2715Q monitor with a DPCD_REV < 0x14 returns - * corrupted values when reading from the 0xF0000- range with a block - * size bigger than 1. - */ - int block_size = dpcd[DP_DPCD_REV] < 0x14 ? 1 : buf_size; - int offset; - int ret; - - for (offset = 0; offset < buf_size; offset += block_size) { - ret = drm_dp_dpcd_read(aux, - address + offset, - &buf[offset], block_size); - if (ret < 0) - return ret; - - WARN_ON(ret != block_size); - } - - return 0; -} - -/** - * drm_dp_read_lttpr_common_caps - read the LTTPR common capabilities - * @aux: DisplayPort AUX channel - * @dpcd: DisplayPort configuration data - * @caps: buffer to return the capability info in - * - * Read capabilities common to all LTTPRs. - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_read_lttpr_common_caps(struct drm_dp_aux *aux, - const u8 dpcd[DP_RECEIVER_CAP_SIZE], - u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) -{ - return drm_dp_read_lttpr_regs(aux, dpcd, - DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV, - caps, DP_LTTPR_COMMON_CAP_SIZE); -} -EXPORT_SYMBOL(drm_dp_read_lttpr_common_caps); - -/** - * drm_dp_read_lttpr_phy_caps - read the capabilities for a given LTTPR PHY - * @aux: DisplayPort AUX channel - * @dpcd: DisplayPort configuration data - * @dp_phy: LTTPR PHY to read the capabilities for - * @caps: buffer to return the capability info in - * - * Read the capabilities for the given LTTPR PHY. - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_read_lttpr_phy_caps(struct drm_dp_aux *aux, - const u8 dpcd[DP_RECEIVER_CAP_SIZE], - enum drm_dp_phy dp_phy, - u8 caps[DP_LTTPR_PHY_CAP_SIZE]) -{ - return drm_dp_read_lttpr_regs(aux, dpcd, - DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy), - caps, DP_LTTPR_PHY_CAP_SIZE); -} -EXPORT_SYMBOL(drm_dp_read_lttpr_phy_caps); - -static u8 dp_lttpr_common_cap(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE], int r) -{ - return caps[r - DP_LT_TUNABLE_PHY_REPEATER_FIELD_DATA_STRUCTURE_REV]; -} - -/** - * drm_dp_lttpr_count - get the number of detected LTTPRs - * @caps: LTTPR common capabilities - * - * Get the number of detected LTTPRs from the LTTPR common capabilities info. - * - * Returns: - * -ERANGE if more than supported number (8) of LTTPRs are detected - * -EINVAL if the DP_PHY_REPEATER_CNT register contains an invalid value - * otherwise the number of detected LTTPRs - */ -int drm_dp_lttpr_count(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) -{ - u8 count = dp_lttpr_common_cap(caps, DP_PHY_REPEATER_CNT); - - switch (hweight8(count)) { - case 0: - return 0; - case 1: - return 8 - ilog2(count); - case 8: - return -ERANGE; - default: - return -EINVAL; - } -} -EXPORT_SYMBOL(drm_dp_lttpr_count); - -/** - * drm_dp_lttpr_max_link_rate - get the maximum link rate supported by all LTTPRs - * @caps: LTTPR common capabilities - * - * Returns the maximum link rate supported by all detected LTTPRs. - */ -int drm_dp_lttpr_max_link_rate(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) -{ - u8 rate = dp_lttpr_common_cap(caps, DP_MAX_LINK_RATE_PHY_REPEATER); - - return drm_dp_bw_code_to_link_rate(rate); -} -EXPORT_SYMBOL(drm_dp_lttpr_max_link_rate); - -/** - * drm_dp_lttpr_max_lane_count - get the maximum lane count supported by all LTTPRs - * @caps: LTTPR common capabilities - * - * Returns the maximum lane count supported by all detected LTTPRs. - */ -int drm_dp_lttpr_max_lane_count(const u8 caps[DP_LTTPR_COMMON_CAP_SIZE]) -{ - u8 max_lanes = dp_lttpr_common_cap(caps, DP_MAX_LANE_COUNT_PHY_REPEATER); - - return max_lanes & DP_MAX_LANE_COUNT_MASK; -} -EXPORT_SYMBOL(drm_dp_lttpr_max_lane_count); - -/** - * drm_dp_lttpr_voltage_swing_level_3_supported - check for LTTPR vswing3 support - * @caps: LTTPR PHY capabilities - * - * Returns true if the @caps for an LTTPR TX PHY indicate support for - * voltage swing level 3. - */ -bool -drm_dp_lttpr_voltage_swing_level_3_supported(const u8 caps[DP_LTTPR_PHY_CAP_SIZE]) -{ - u8 txcap = dp_lttpr_phy_cap(caps, DP_TRANSMITTER_CAPABILITY_PHY_REPEATER1); - - return txcap & DP_VOLTAGE_SWING_LEVEL_3_SUPPORTED; -} -EXPORT_SYMBOL(drm_dp_lttpr_voltage_swing_level_3_supported); - -/** - * drm_dp_lttpr_pre_emphasis_level_3_supported - check for LTTPR preemph3 support - * @caps: LTTPR PHY capabilities - * - * Returns true if the @caps for an LTTPR TX PHY indicate support for - * pre-emphasis level 3. - */ -bool -drm_dp_lttpr_pre_emphasis_level_3_supported(const u8 caps[DP_LTTPR_PHY_CAP_SIZE]) -{ - u8 txcap = dp_lttpr_phy_cap(caps, DP_TRANSMITTER_CAPABILITY_PHY_REPEATER1); - - return txcap & DP_PRE_EMPHASIS_LEVEL_3_SUPPORTED; -} -EXPORT_SYMBOL(drm_dp_lttpr_pre_emphasis_level_3_supported); - -/** - * drm_dp_get_phy_test_pattern() - get the requested pattern from the sink. - * @aux: DisplayPort AUX channel - * @data: DP phy compliance test parameters. - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_get_phy_test_pattern(struct drm_dp_aux *aux, - struct drm_dp_phy_test_params *data) -{ - int err; - u8 rate, lanes; - - err = drm_dp_dpcd_readb(aux, DP_TEST_LINK_RATE, &rate); - if (err < 0) - return err; - data->link_rate = drm_dp_bw_code_to_link_rate(rate); - - err = drm_dp_dpcd_readb(aux, DP_TEST_LANE_COUNT, &lanes); - if (err < 0) - return err; - data->num_lanes = lanes & DP_MAX_LANE_COUNT_MASK; - - if (lanes & DP_ENHANCED_FRAME_CAP) - data->enhanced_frame_cap = true; - - err = drm_dp_dpcd_readb(aux, DP_PHY_TEST_PATTERN, &data->phy_pattern); - if (err < 0) - return err; - - switch (data->phy_pattern) { - case DP_PHY_TEST_PATTERN_80BIT_CUSTOM: - err = drm_dp_dpcd_read(aux, DP_TEST_80BIT_CUSTOM_PATTERN_7_0, - &data->custom80, sizeof(data->custom80)); - if (err < 0) - return err; - - break; - case DP_PHY_TEST_PATTERN_CP2520: - err = drm_dp_dpcd_read(aux, DP_TEST_HBR2_SCRAMBLER_RESET, - &data->hbr2_reset, - sizeof(data->hbr2_reset)); - if (err < 0) - return err; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_get_phy_test_pattern); - -/** - * drm_dp_set_phy_test_pattern() - set the pattern to the sink. - * @aux: DisplayPort AUX channel - * @data: DP phy compliance test parameters. - * @dp_rev: DP revision to use for compliance testing - * - * Returns 0 on success or a negative error code on failure. - */ -int drm_dp_set_phy_test_pattern(struct drm_dp_aux *aux, - struct drm_dp_phy_test_params *data, u8 dp_rev) -{ - int err, i; - u8 link_config[2]; - u8 test_pattern; - - link_config[0] = drm_dp_link_rate_to_bw_code(data->link_rate); - link_config[1] = data->num_lanes; - if (data->enhanced_frame_cap) - link_config[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; - err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, link_config, 2); - if (err < 0) - return err; - - test_pattern = data->phy_pattern; - if (dp_rev < 0x12) { - test_pattern = (test_pattern << 2) & - DP_LINK_QUAL_PATTERN_11_MASK; - err = drm_dp_dpcd_writeb(aux, DP_TRAINING_PATTERN_SET, - test_pattern); - if (err < 0) - return err; - } else { - for (i = 0; i < data->num_lanes; i++) { - err = drm_dp_dpcd_writeb(aux, - DP_LINK_QUAL_LANE0_SET + i, - test_pattern); - if (err < 0) - return err; - } - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_set_phy_test_pattern); - -static const char *dp_pixelformat_get_name(enum dp_pixelformat pixelformat) -{ - if (pixelformat < 0 || pixelformat > DP_PIXELFORMAT_RESERVED) - return "Invalid"; - - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "RGB"; - case DP_PIXELFORMAT_YUV444: - return "YUV444"; - case DP_PIXELFORMAT_YUV422: - return "YUV422"; - case DP_PIXELFORMAT_YUV420: - return "YUV420"; - case DP_PIXELFORMAT_Y_ONLY: - return "Y_ONLY"; - case DP_PIXELFORMAT_RAW: - return "RAW"; - default: - return "Reserved"; - } -} - -static const char *dp_colorimetry_get_name(enum dp_pixelformat pixelformat, - enum dp_colorimetry colorimetry) -{ - if (pixelformat < 0 || pixelformat > DP_PIXELFORMAT_RESERVED) - return "Invalid"; - - switch (colorimetry) { - case DP_COLORIMETRY_DEFAULT: - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "sRGB"; - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "BT.601"; - case DP_PIXELFORMAT_Y_ONLY: - return "DICOM PS3.14"; - case DP_PIXELFORMAT_RAW: - return "Custom Color Profile"; - default: - return "Reserved"; - } - case DP_COLORIMETRY_RGB_WIDE_FIXED: /* and DP_COLORIMETRY_BT709_YCC */ - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "Wide Fixed"; - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "BT.709"; - default: - return "Reserved"; - } - case DP_COLORIMETRY_RGB_WIDE_FLOAT: /* and DP_COLORIMETRY_XVYCC_601 */ - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "Wide Float"; - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "xvYCC 601"; - default: - return "Reserved"; - } - case DP_COLORIMETRY_OPRGB: /* and DP_COLORIMETRY_XVYCC_709 */ - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "OpRGB"; - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "xvYCC 709"; - default: - return "Reserved"; - } - case DP_COLORIMETRY_DCI_P3_RGB: /* and DP_COLORIMETRY_SYCC_601 */ - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "DCI-P3"; - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "sYCC 601"; - default: - return "Reserved"; - } - case DP_COLORIMETRY_RGB_CUSTOM: /* and DP_COLORIMETRY_OPYCC_601 */ - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "Custom Profile"; - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "OpYCC 601"; - default: - return "Reserved"; - } - case DP_COLORIMETRY_BT2020_RGB: /* and DP_COLORIMETRY_BT2020_CYCC */ - switch (pixelformat) { - case DP_PIXELFORMAT_RGB: - return "BT.2020 RGB"; - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "BT.2020 CYCC"; - default: - return "Reserved"; - } - case DP_COLORIMETRY_BT2020_YCC: - switch (pixelformat) { - case DP_PIXELFORMAT_YUV444: - case DP_PIXELFORMAT_YUV422: - case DP_PIXELFORMAT_YUV420: - return "BT.2020 YCC"; - default: - return "Reserved"; - } - default: - return "Invalid"; - } -} - -static const char *dp_dynamic_range_get_name(enum dp_dynamic_range dynamic_range) -{ - switch (dynamic_range) { - case DP_DYNAMIC_RANGE_VESA: - return "VESA range"; - case DP_DYNAMIC_RANGE_CTA: - return "CTA range"; - default: - return "Invalid"; - } -} - -static const char *dp_content_type_get_name(enum dp_content_type content_type) -{ - switch (content_type) { - case DP_CONTENT_TYPE_NOT_DEFINED: - return "Not defined"; - case DP_CONTENT_TYPE_GRAPHICS: - return "Graphics"; - case DP_CONTENT_TYPE_PHOTO: - return "Photo"; - case DP_CONTENT_TYPE_VIDEO: - return "Video"; - case DP_CONTENT_TYPE_GAME: - return "Game"; - default: - return "Reserved"; - } -} - -void drm_dp_vsc_sdp_log(const char *level, struct device *dev, - const struct drm_dp_vsc_sdp *vsc) -{ -#define DP_SDP_LOG(fmt, ...) dev_printk(level, dev, fmt, ##__VA_ARGS__) - DP_SDP_LOG("DP SDP: %s, revision %u, length %u\n", "VSC", - vsc->revision, vsc->length); - DP_SDP_LOG(" pixelformat: %s\n", - dp_pixelformat_get_name(vsc->pixelformat)); - DP_SDP_LOG(" colorimetry: %s\n", - dp_colorimetry_get_name(vsc->pixelformat, vsc->colorimetry)); - DP_SDP_LOG(" bpc: %u\n", vsc->bpc); - DP_SDP_LOG(" dynamic range: %s\n", - dp_dynamic_range_get_name(vsc->dynamic_range)); - DP_SDP_LOG(" content type: %s\n", - dp_content_type_get_name(vsc->content_type)); -#undef DP_SDP_LOG -} -EXPORT_SYMBOL(drm_dp_vsc_sdp_log); - -/** - * drm_dp_get_pcon_max_frl_bw() - maximum frl supported by PCON - * @dpcd: DisplayPort configuration data - * @port_cap: port capabilities - * - * Returns maximum frl bandwidth supported by PCON in GBPS, - * returns 0 if not supported. - */ -int drm_dp_get_pcon_max_frl_bw(const u8 dpcd[DP_RECEIVER_CAP_SIZE], - const u8 port_cap[4]) -{ - int bw; - u8 buf; - - buf = port_cap[2]; - bw = buf & DP_PCON_MAX_FRL_BW; - - switch (bw) { - case DP_PCON_MAX_9GBPS: - return 9; - case DP_PCON_MAX_18GBPS: - return 18; - case DP_PCON_MAX_24GBPS: - return 24; - case DP_PCON_MAX_32GBPS: - return 32; - case DP_PCON_MAX_40GBPS: - return 40; - case DP_PCON_MAX_48GBPS: - return 48; - case DP_PCON_MAX_0GBPS: - default: - return 0; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_get_pcon_max_frl_bw); - -/** - * drm_dp_pcon_frl_prepare() - Prepare PCON for FRL. - * @aux: DisplayPort AUX channel - * @enable_frl_ready_hpd: Configure DP_PCON_ENABLE_HPD_READY. - * - * Returns 0 if success, else returns negative error code. - */ -int drm_dp_pcon_frl_prepare(struct drm_dp_aux *aux, bool enable_frl_ready_hpd) -{ - int ret; - u8 buf = DP_PCON_ENABLE_SOURCE_CTL_MODE | - DP_PCON_ENABLE_LINK_FRL_MODE; - - if (enable_frl_ready_hpd) - buf |= DP_PCON_ENABLE_HPD_READY; - - ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf); - - return ret; -} -EXPORT_SYMBOL(drm_dp_pcon_frl_prepare); - -/** - * drm_dp_pcon_is_frl_ready() - Is PCON ready for FRL - * @aux: DisplayPort AUX channel - * - * Returns true if success, else returns false. - */ -bool drm_dp_pcon_is_frl_ready(struct drm_dp_aux *aux) -{ - int ret; - u8 buf; - - ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_TX_LINK_STATUS, &buf); - if (ret < 0) - return false; - - if (buf & DP_PCON_FRL_READY) - return true; - - return false; -} -EXPORT_SYMBOL(drm_dp_pcon_is_frl_ready); - -/** - * drm_dp_pcon_frl_configure_1() - Set HDMI LINK Configuration-Step1 - * @aux: DisplayPort AUX channel - * @max_frl_gbps: maximum frl bw to be configured between PCON and HDMI sink - * @frl_mode: FRL Training mode, it can be either Concurrent or Sequential. - * In Concurrent Mode, the FRL link bring up can be done along with - * DP Link training. In Sequential mode, the FRL link bring up is done prior to - * the DP Link training. - * - * Returns 0 if success, else returns negative error code. - */ - -int drm_dp_pcon_frl_configure_1(struct drm_dp_aux *aux, int max_frl_gbps, - u8 frl_mode) -{ - int ret; - u8 buf; - - ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_LINK_CONFIG_1, &buf); - if (ret < 0) - return ret; - - if (frl_mode == DP_PCON_ENABLE_CONCURRENT_LINK) - buf |= DP_PCON_ENABLE_CONCURRENT_LINK; - else - buf &= ~DP_PCON_ENABLE_CONCURRENT_LINK; - - switch (max_frl_gbps) { - case 9: - buf |= DP_PCON_ENABLE_MAX_BW_9GBPS; - break; - case 18: - buf |= DP_PCON_ENABLE_MAX_BW_18GBPS; - break; - case 24: - buf |= DP_PCON_ENABLE_MAX_BW_24GBPS; - break; - case 32: - buf |= DP_PCON_ENABLE_MAX_BW_32GBPS; - break; - case 40: - buf |= DP_PCON_ENABLE_MAX_BW_40GBPS; - break; - case 48: - buf |= DP_PCON_ENABLE_MAX_BW_48GBPS; - break; - case 0: - buf |= DP_PCON_ENABLE_MAX_BW_0GBPS; - break; - default: - return -EINVAL; - } - - ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_frl_configure_1); - -/** - * drm_dp_pcon_frl_configure_2() - Set HDMI Link configuration Step-2 - * @aux: DisplayPort AUX channel - * @max_frl_mask : Max FRL BW to be tried by the PCON with HDMI Sink - * @frl_type : FRL training type, can be Extended, or Normal. - * In Normal FRL training, the PCON tries each frl bw from the max_frl_mask - * starting from min, and stops when link training is successful. In Extended - * FRL training, all frl bw selected in the mask are trained by the PCON. - * - * Returns 0 if success, else returns negative error code. - */ -int drm_dp_pcon_frl_configure_2(struct drm_dp_aux *aux, int max_frl_mask, - u8 frl_type) -{ - int ret; - u8 buf = max_frl_mask; - - if (frl_type == DP_PCON_FRL_LINK_TRAIN_EXTENDED) - buf |= DP_PCON_FRL_LINK_TRAIN_EXTENDED; - else - buf &= ~DP_PCON_FRL_LINK_TRAIN_EXTENDED; - - ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_2, buf); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_frl_configure_2); - -/** - * drm_dp_pcon_reset_frl_config() - Re-Set HDMI Link configuration. - * @aux: DisplayPort AUX channel - * - * Returns 0 if success, else returns negative error code. - */ -int drm_dp_pcon_reset_frl_config(struct drm_dp_aux *aux) -{ - int ret; - - ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, 0x0); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_reset_frl_config); - -/** - * drm_dp_pcon_frl_enable() - Enable HDMI link through FRL - * @aux: DisplayPort AUX channel - * - * Returns 0 if success, else returns negative error code. - */ -int drm_dp_pcon_frl_enable(struct drm_dp_aux *aux) -{ - int ret; - u8 buf = 0; - - ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_LINK_CONFIG_1, &buf); - if (ret < 0) - return ret; - if (!(buf & DP_PCON_ENABLE_SOURCE_CTL_MODE)) { - drm_dbg_kms(aux->drm_dev, "%s: PCON in Autonomous mode, can't enable FRL\n", - aux->name); - return -EINVAL; - } - buf |= DP_PCON_ENABLE_HDMI_LINK; - ret = drm_dp_dpcd_writeb(aux, DP_PCON_HDMI_LINK_CONFIG_1, buf); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_frl_enable); - -/** - * drm_dp_pcon_hdmi_link_active() - check if the PCON HDMI LINK status is active. - * @aux: DisplayPort AUX channel - * - * Returns true if link is active else returns false. - */ -bool drm_dp_pcon_hdmi_link_active(struct drm_dp_aux *aux) -{ - u8 buf; - int ret; - - ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_TX_LINK_STATUS, &buf); - if (ret < 0) - return false; - - return buf & DP_PCON_HDMI_TX_LINK_ACTIVE; -} -EXPORT_SYMBOL(drm_dp_pcon_hdmi_link_active); - -/** - * drm_dp_pcon_hdmi_link_mode() - get the PCON HDMI LINK MODE - * @aux: DisplayPort AUX channel - * @frl_trained_mask: pointer to store bitmask of the trained bw configuration. - * Valid only if the MODE returned is FRL. For Normal Link training mode - * only 1 of the bits will be set, but in case of Extended mode, more than - * one bits can be set. - * - * Returns the link mode : TMDS or FRL on success, else returns negative error - * code. - */ -int drm_dp_pcon_hdmi_link_mode(struct drm_dp_aux *aux, u8 *frl_trained_mask) -{ - u8 buf; - int mode; - int ret; - - ret = drm_dp_dpcd_readb(aux, DP_PCON_HDMI_POST_FRL_STATUS, &buf); - if (ret < 0) - return ret; - - mode = buf & DP_PCON_HDMI_LINK_MODE; - - if (frl_trained_mask && DP_PCON_HDMI_MODE_FRL == mode) - *frl_trained_mask = (buf & DP_PCON_HDMI_FRL_TRAINED_BW) >> 1; - - return mode; -} -EXPORT_SYMBOL(drm_dp_pcon_hdmi_link_mode); - -/** - * drm_dp_pcon_hdmi_frl_link_error_count() - print the error count per lane - * during link failure between PCON and HDMI sink - * @aux: DisplayPort AUX channel - * @connector: DRM connector - * code. - **/ - -void drm_dp_pcon_hdmi_frl_link_error_count(struct drm_dp_aux *aux, - struct drm_connector *connector) -{ - u8 buf, error_count; - int i, num_error; - struct drm_hdmi_info *hdmi = &connector->display_info.hdmi; - - for (i = 0; i < hdmi->max_lanes; i++) { - if (drm_dp_dpcd_readb(aux, DP_PCON_HDMI_ERROR_STATUS_LN0 + i, &buf) < 0) - return; - - error_count = buf & DP_PCON_HDMI_ERROR_COUNT_MASK; - switch (error_count) { - case DP_PCON_HDMI_ERROR_COUNT_HUNDRED_PLUS: - num_error = 100; - break; - case DP_PCON_HDMI_ERROR_COUNT_TEN_PLUS: - num_error = 10; - break; - case DP_PCON_HDMI_ERROR_COUNT_THREE_PLUS: - num_error = 3; - break; - default: - num_error = 0; - } - - drm_err(aux->drm_dev, "%s: More than %d errors since the last read for lane %d", - aux->name, num_error, i); - } -} -EXPORT_SYMBOL(drm_dp_pcon_hdmi_frl_link_error_count); - -/* - * drm_dp_pcon_enc_is_dsc_1_2 - Does PCON Encoder supports DSC 1.2 - * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder - * - * Returns true is PCON encoder is DSC 1.2 else returns false. - */ -bool drm_dp_pcon_enc_is_dsc_1_2(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) -{ - u8 buf; - u8 major_v, minor_v; - - buf = pcon_dsc_dpcd[DP_PCON_DSC_VERSION - DP_PCON_DSC_ENCODER]; - major_v = (buf & DP_PCON_DSC_MAJOR_MASK) >> DP_PCON_DSC_MAJOR_SHIFT; - minor_v = (buf & DP_PCON_DSC_MINOR_MASK) >> DP_PCON_DSC_MINOR_SHIFT; - - if (major_v == 1 && minor_v == 2) - return true; - - return false; -} -EXPORT_SYMBOL(drm_dp_pcon_enc_is_dsc_1_2); - -/* - * drm_dp_pcon_dsc_max_slices - Get max slices supported by PCON DSC Encoder - * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder - * - * Returns maximum no. of slices supported by the PCON DSC Encoder. - */ -int drm_dp_pcon_dsc_max_slices(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) -{ - u8 slice_cap1, slice_cap2; - - slice_cap1 = pcon_dsc_dpcd[DP_PCON_DSC_SLICE_CAP_1 - DP_PCON_DSC_ENCODER]; - slice_cap2 = pcon_dsc_dpcd[DP_PCON_DSC_SLICE_CAP_2 - DP_PCON_DSC_ENCODER]; - - if (slice_cap2 & DP_PCON_DSC_24_PER_DSC_ENC) - return 24; - if (slice_cap2 & DP_PCON_DSC_20_PER_DSC_ENC) - return 20; - if (slice_cap2 & DP_PCON_DSC_16_PER_DSC_ENC) - return 16; - if (slice_cap1 & DP_PCON_DSC_12_PER_DSC_ENC) - return 12; - if (slice_cap1 & DP_PCON_DSC_10_PER_DSC_ENC) - return 10; - if (slice_cap1 & DP_PCON_DSC_8_PER_DSC_ENC) - return 8; - if (slice_cap1 & DP_PCON_DSC_6_PER_DSC_ENC) - return 6; - if (slice_cap1 & DP_PCON_DSC_4_PER_DSC_ENC) - return 4; - if (slice_cap1 & DP_PCON_DSC_2_PER_DSC_ENC) - return 2; - if (slice_cap1 & DP_PCON_DSC_1_PER_DSC_ENC) - return 1; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_dsc_max_slices); - -/* - * drm_dp_pcon_dsc_max_slice_width() - Get max slice width for Pcon DSC encoder - * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder - * - * Returns maximum width of the slices in pixel width i.e. no. of pixels x 320. - */ -int drm_dp_pcon_dsc_max_slice_width(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) -{ - u8 buf; - - buf = pcon_dsc_dpcd[DP_PCON_DSC_MAX_SLICE_WIDTH - DP_PCON_DSC_ENCODER]; - - return buf * DP_DSC_SLICE_WIDTH_MULTIPLIER; -} -EXPORT_SYMBOL(drm_dp_pcon_dsc_max_slice_width); - -/* - * drm_dp_pcon_dsc_bpp_incr() - Get bits per pixel increment for PCON DSC encoder - * @pcon_dsc_dpcd: DSC capabilities of the PCON DSC Encoder - * - * Returns the bpp precision supported by the PCON encoder. - */ -int drm_dp_pcon_dsc_bpp_incr(const u8 pcon_dsc_dpcd[DP_PCON_DSC_ENCODER_CAP_SIZE]) -{ - u8 buf; - - buf = pcon_dsc_dpcd[DP_PCON_DSC_BPP_INCR - DP_PCON_DSC_ENCODER]; - - switch (buf & DP_PCON_DSC_BPP_INCR_MASK) { - case DP_PCON_DSC_ONE_16TH_BPP: - return 16; - case DP_PCON_DSC_ONE_8TH_BPP: - return 8; - case DP_PCON_DSC_ONE_4TH_BPP: - return 4; - case DP_PCON_DSC_ONE_HALF_BPP: - return 2; - case DP_PCON_DSC_ONE_BPP: - return 1; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_dsc_bpp_incr); - -static -int drm_dp_pcon_configure_dsc_enc(struct drm_dp_aux *aux, u8 pps_buf_config) -{ - u8 buf; - int ret; - - ret = drm_dp_dpcd_readb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, &buf); - if (ret < 0) - return ret; - - buf |= DP_PCON_ENABLE_DSC_ENCODER; - - if (pps_buf_config <= DP_PCON_ENC_PPS_OVERRIDE_EN_BUFFER) { - buf &= ~DP_PCON_ENCODER_PPS_OVERRIDE_MASK; - buf |= pps_buf_config << 2; - } - - ret = drm_dp_dpcd_writeb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, buf); - if (ret < 0) - return ret; - - return 0; -} - -/** - * drm_dp_pcon_pps_default() - Let PCON fill the default pps parameters - * for DSC1.2 between PCON & HDMI2.1 sink - * @aux: DisplayPort AUX channel - * - * Returns 0 on success, else returns negative error code. - */ -int drm_dp_pcon_pps_default(struct drm_dp_aux *aux) -{ - int ret; - - ret = drm_dp_pcon_configure_dsc_enc(aux, DP_PCON_ENC_PPS_OVERRIDE_DISABLED); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_pps_default); - -/** - * drm_dp_pcon_pps_override_buf() - Configure PPS encoder override buffer for - * HDMI sink - * @aux: DisplayPort AUX channel - * @pps_buf: 128 bytes to be written into PPS buffer for HDMI sink by PCON. - * - * Returns 0 on success, else returns negative error code. - */ -int drm_dp_pcon_pps_override_buf(struct drm_dp_aux *aux, u8 pps_buf[128]) -{ - int ret; - - ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVERRIDE_BASE, &pps_buf, 128); - if (ret < 0) - return ret; - - ret = drm_dp_pcon_configure_dsc_enc(aux, DP_PCON_ENC_PPS_OVERRIDE_EN_BUFFER); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_pps_override_buf); - -/* - * drm_dp_pcon_pps_override_param() - Write PPS parameters to DSC encoder - * override registers - * @aux: DisplayPort AUX channel - * @pps_param: 3 Parameters (2 Bytes each) : Slice Width, Slice Height, - * bits_per_pixel. - * - * Returns 0 on success, else returns negative error code. - */ -int drm_dp_pcon_pps_override_param(struct drm_dp_aux *aux, u8 pps_param[6]) -{ - int ret; - - ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVRD_SLICE_HEIGHT, &pps_param[0], 2); - if (ret < 0) - return ret; - ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVRD_SLICE_WIDTH, &pps_param[2], 2); - if (ret < 0) - return ret; - ret = drm_dp_dpcd_write(aux, DP_PCON_HDMI_PPS_OVRD_BPP, &pps_param[4], 2); - if (ret < 0) - return ret; - - ret = drm_dp_pcon_configure_dsc_enc(aux, DP_PCON_ENC_PPS_OVERRIDE_EN_BUFFER); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_pps_override_param); - -/* - * drm_dp_pcon_convert_rgb_to_ycbcr() - Configure the PCon to convert RGB to Ycbcr - * @aux: displayPort AUX channel - * @color_spc: Color-space/s for which conversion is to be enabled, 0 for disable. - * - * Returns 0 on success, else returns negative error code. - */ -int drm_dp_pcon_convert_rgb_to_ycbcr(struct drm_dp_aux *aux, u8 color_spc) -{ - int ret; - u8 buf; - - ret = drm_dp_dpcd_readb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, &buf); - if (ret < 0) - return ret; - - if (color_spc & DP_CONVERSION_RGB_YCBCR_MASK) - buf |= (color_spc & DP_CONVERSION_RGB_YCBCR_MASK); - else - buf &= ~DP_CONVERSION_RGB_YCBCR_MASK; - - ret = drm_dp_dpcd_writeb(aux, DP_PROTOCOL_CONVERTER_CONTROL_2, buf); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_dp_pcon_convert_rgb_to_ycbcr); - -/** - * drm_edp_backlight_set_level() - Set the backlight level of an eDP panel via AUX - * @aux: The DP AUX channel to use - * @bl: Backlight capability info from drm_edp_backlight_init() - * @level: The brightness level to set - * - * Sets the brightness level of an eDP panel's backlight. Note that the panel's backlight must - * already have been enabled by the driver by calling drm_edp_backlight_enable(). - * - * Returns: %0 on success, negative error code on failure - */ -int drm_edp_backlight_set_level(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, - u16 level) -{ - int ret; - u8 buf[2] = { 0 }; - - /* The panel uses the PWM for controlling brightness levels */ - if (!bl->aux_set) - return 0; - - if (bl->lsb_reg_used) { - buf[0] = (level & 0xff00) >> 8; - buf[1] = (level & 0x00ff); - } else { - buf[0] = level; - } - - ret = drm_dp_dpcd_write(aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, buf, sizeof(buf)); - if (ret != sizeof(buf)) { - drm_err(aux->drm_dev, - "%s: Failed to write aux backlight level: %d\n", - aux->name, ret); - return ret < 0 ? ret : -EIO; - } - - return 0; -} -EXPORT_SYMBOL(drm_edp_backlight_set_level); - -static int -drm_edp_backlight_set_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, - bool enable) -{ - int ret; - u8 buf; - - /* This panel uses the EDP_BL_PWR GPIO for enablement */ - if (!bl->aux_enable) - return 0; - - ret = drm_dp_dpcd_readb(aux, DP_EDP_DISPLAY_CONTROL_REGISTER, &buf); - if (ret != 1) { - drm_err(aux->drm_dev, "%s: Failed to read eDP display control register: %d\n", - aux->name, ret); - return ret < 0 ? ret : -EIO; - } - if (enable) - buf |= DP_EDP_BACKLIGHT_ENABLE; - else - buf &= ~DP_EDP_BACKLIGHT_ENABLE; - - ret = drm_dp_dpcd_writeb(aux, DP_EDP_DISPLAY_CONTROL_REGISTER, buf); - if (ret != 1) { - drm_err(aux->drm_dev, "%s: Failed to write eDP display control register: %d\n", - aux->name, ret); - return ret < 0 ? ret : -EIO; - } - - return 0; -} - -/** - * drm_edp_backlight_enable() - Enable an eDP panel's backlight using DPCD - * @aux: The DP AUX channel to use - * @bl: Backlight capability info from drm_edp_backlight_init() - * @level: The initial backlight level to set via AUX, if there is one - * - * This function handles enabling DPCD backlight controls on a panel over DPCD, while additionally - * restoring any important backlight state such as the given backlight level, the brightness byte - * count, backlight frequency, etc. - * - * Note that certain panels do not support being enabled or disabled via DPCD, but instead require - * that the driver handle enabling/disabling the panel through implementation-specific means using - * the EDP_BL_PWR GPIO. For such panels, &drm_edp_backlight_info.aux_enable will be set to %false, - * this function becomes a no-op, and the driver is expected to handle powering the panel on using - * the EDP_BL_PWR GPIO. - * - * Returns: %0 on success, negative error code on failure. - */ -int drm_edp_backlight_enable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl, - const u16 level) -{ - int ret; - u8 dpcd_buf; - - if (bl->aux_set) - dpcd_buf = DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD; - else - dpcd_buf = DP_EDP_BACKLIGHT_CONTROL_MODE_PWM; - - if (bl->pwmgen_bit_count) { - ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, bl->pwmgen_bit_count); - if (ret != 1) - drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n", - aux->name, ret); - } - - if (bl->pwm_freq_pre_divider) { - ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_FREQ_SET, bl->pwm_freq_pre_divider); - if (ret != 1) - drm_dbg_kms(aux->drm_dev, - "%s: Failed to write aux backlight frequency: %d\n", - aux->name, ret); - else - dpcd_buf |= DP_EDP_BACKLIGHT_FREQ_AUX_SET_ENABLE; - } - - ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, dpcd_buf); - if (ret != 1) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux backlight mode: %d\n", - aux->name, ret); - return ret < 0 ? ret : -EIO; - } - - ret = drm_edp_backlight_set_level(aux, bl, level); - if (ret < 0) - return ret; - ret = drm_edp_backlight_set_enable(aux, bl, true); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_edp_backlight_enable); - -/** - * drm_edp_backlight_disable() - Disable an eDP backlight using DPCD, if supported - * @aux: The DP AUX channel to use - * @bl: Backlight capability info from drm_edp_backlight_init() - * - * This function handles disabling DPCD backlight controls on a panel over AUX. - * - * Note that certain panels do not support being enabled or disabled via DPCD, but instead require - * that the driver handle enabling/disabling the panel through implementation-specific means using - * the EDP_BL_PWR GPIO. For such panels, &drm_edp_backlight_info.aux_enable will be set to %false, - * this function becomes a no-op, and the driver is expected to handle powering the panel off using - * the EDP_BL_PWR GPIO. - * - * Returns: %0 on success or no-op, negative error code on failure. - */ -int drm_edp_backlight_disable(struct drm_dp_aux *aux, const struct drm_edp_backlight_info *bl) -{ - int ret; - - ret = drm_edp_backlight_set_enable(aux, bl, false); - if (ret < 0) - return ret; - - return 0; -} -EXPORT_SYMBOL(drm_edp_backlight_disable); - -static inline int -drm_edp_backlight_probe_max(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, - u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]) -{ - int fxp, fxp_min, fxp_max, fxp_actual, f = 1; - int ret; - u8 pn, pn_min, pn_max; - - if (!bl->aux_set) - return 0; - - ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT, &pn); - if (ret != 1) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap: %d\n", - aux->name, ret); - return -ENODEV; - } - - pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK; - bl->max = (1 << pn) - 1; - if (!driver_pwm_freq_hz) - return 0; - - /* - * Set PWM Frequency divider to match desired frequency provided by the driver. - * The PWM Frequency is calculated as 27Mhz / (F x P). - * - Where F = PWM Frequency Pre-Divider value programmed by field 7:0 of the - * EDP_BACKLIGHT_FREQ_SET register (DPCD Address 00728h) - * - Where P = 2^Pn, where Pn is the value programmed by field 4:0 of the - * EDP_PWMGEN_BIT_COUNT register (DPCD Address 00724h) - */ - - /* Find desired value of (F x P) - * Note that, if F x P is out of supported range, the maximum value or minimum value will - * applied automatically. So no need to check that. - */ - fxp = DIV_ROUND_CLOSEST(1000 * DP_EDP_BACKLIGHT_FREQ_BASE_KHZ, driver_pwm_freq_hz); - - /* Use highest possible value of Pn for more granularity of brightness adjustment while - * satisfying the conditions below. - * - Pn is in the range of Pn_min and Pn_max - * - F is in the range of 1 and 255 - * - FxP is within 25% of desired value. - * Note: 25% is arbitrary value and may need some tweak. - */ - ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min); - if (ret != 1) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap min: %d\n", - aux->name, ret); - return 0; - } - ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max); - if (ret != 1) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap max: %d\n", - aux->name, ret); - return 0; - } - pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; - pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; - - /* Ensure frequency is within 25% of desired value */ - fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4); - fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4); - if (fxp_min < (1 << pn_min) || (255 << pn_max) < fxp_max) { - drm_dbg_kms(aux->drm_dev, - "%s: Driver defined backlight frequency (%d) out of range\n", - aux->name, driver_pwm_freq_hz); - return 0; - } - - for (pn = pn_max; pn >= pn_min; pn--) { - f = clamp(DIV_ROUND_CLOSEST(fxp, 1 << pn), 1, 255); - fxp_actual = f << pn; - if (fxp_min <= fxp_actual && fxp_actual <= fxp_max) - break; - } - - ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, pn); - if (ret != 1) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n", - aux->name, ret); - return 0; - } - bl->pwmgen_bit_count = pn; - bl->max = (1 << pn) - 1; - - if (edp_dpcd[2] & DP_EDP_BACKLIGHT_FREQ_AUX_SET_CAP) { - bl->pwm_freq_pre_divider = f; - drm_dbg_kms(aux->drm_dev, "%s: Using backlight frequency from driver (%dHz)\n", - aux->name, driver_pwm_freq_hz); - } - - return 0; -} - -static inline int -drm_edp_backlight_probe_state(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, - u8 *current_mode) -{ - int ret; - u8 buf[2]; - u8 mode_reg; - - ret = drm_dp_dpcd_readb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &mode_reg); - if (ret != 1) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to read backlight mode: %d\n", - aux->name, ret); - return ret < 0 ? ret : -EIO; - } - - *current_mode = (mode_reg & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK); - if (!bl->aux_set) - return 0; - - if (*current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) { - int size = 1 + bl->lsb_reg_used; - - ret = drm_dp_dpcd_read(aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, buf, size); - if (ret != size) { - drm_dbg_kms(aux->drm_dev, "%s: Failed to read backlight level: %d\n", - aux->name, ret); - return ret < 0 ? ret : -EIO; - } - - if (bl->lsb_reg_used) - return (buf[0] << 8) | buf[1]; - else - return buf[0]; - } - - /* - * If we're not in DPCD control mode yet, the programmed brightness value is meaningless and - * the driver should assume max brightness - */ - return bl->max; -} - -/** - * drm_edp_backlight_init() - Probe a display panel's TCON using the standard VESA eDP backlight - * interface. - * @aux: The DP aux device to use for probing - * @bl: The &drm_edp_backlight_info struct to fill out with information on the backlight - * @driver_pwm_freq_hz: Optional PWM frequency from the driver in hz - * @edp_dpcd: A cached copy of the eDP DPCD - * @current_level: Where to store the probed brightness level, if any - * @current_mode: Where to store the currently set backlight control mode - * - * Initializes a &drm_edp_backlight_info struct by probing @aux for it's backlight capabilities, - * along with also probing the current and maximum supported brightness levels. - * - * If @driver_pwm_freq_hz is non-zero, this will be used as the backlight frequency. Otherwise, the - * default frequency from the panel is used. - * - * Returns: %0 on success, negative error code on failure. - */ -int -drm_edp_backlight_init(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl, - u16 driver_pwm_freq_hz, const u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE], - u16 *current_level, u8 *current_mode) -{ - int ret; - - if (edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP) - bl->aux_enable = true; - if (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP) - bl->aux_set = true; - if (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) - bl->lsb_reg_used = true; - - /* Sanity check caps */ - if (!bl->aux_set && !(edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_PWM_PIN_CAP)) { - drm_dbg_kms(aux->drm_dev, - "%s: Panel supports neither AUX or PWM brightness control? Aborting\n", - aux->name); - return -EINVAL; - } - - ret = drm_edp_backlight_probe_max(aux, bl, driver_pwm_freq_hz, edp_dpcd); - if (ret < 0) - return ret; - - ret = drm_edp_backlight_probe_state(aux, bl, current_mode); - if (ret < 0) - return ret; - *current_level = ret; - - drm_dbg_kms(aux->drm_dev, - "%s: Found backlight: aux_set=%d aux_enable=%d mode=%d\n", - aux->name, bl->aux_set, bl->aux_enable, *current_mode); - if (bl->aux_set) { - drm_dbg_kms(aux->drm_dev, - "%s: Backlight caps: level=%d/%d pwm_freq_pre_divider=%d lsb_reg_used=%d\n", - aux->name, *current_level, bl->max, bl->pwm_freq_pre_divider, - bl->lsb_reg_used); - } - - return 0; -} -EXPORT_SYMBOL(drm_edp_backlight_init); - -#if IS_BUILTIN(CONFIG_BACKLIGHT_CLASS_DEVICE) || \ - (IS_MODULE(CONFIG_DRM_KMS_HELPER) && IS_MODULE(CONFIG_BACKLIGHT_CLASS_DEVICE)) - -static int dp_aux_backlight_update_status(struct backlight_device *bd) -{ - struct dp_aux_backlight *bl = bl_get_data(bd); - u16 brightness = backlight_get_brightness(bd); - int ret = 0; - - if (!backlight_is_blank(bd)) { - if (!bl->enabled) { - drm_edp_backlight_enable(bl->aux, &bl->info, brightness); - bl->enabled = true; - return 0; - } - ret = drm_edp_backlight_set_level(bl->aux, &bl->info, brightness); - } else { - if (bl->enabled) { - drm_edp_backlight_disable(bl->aux, &bl->info); - bl->enabled = false; - } - } - - return ret; -} - -static const struct backlight_ops dp_aux_bl_ops = { - .update_status = dp_aux_backlight_update_status, -}; - -/** - * drm_panel_dp_aux_backlight - create and use DP AUX backlight - * @panel: DRM panel - * @aux: The DP AUX channel to use - * - * Use this function to create and handle backlight if your panel - * supports backlight control over DP AUX channel using DPCD - * registers as per VESA's standard backlight control interface. - * - * When the panel is enabled backlight will be enabled after a - * successful call to &drm_panel_funcs.enable() - * - * When the panel is disabled backlight will be disabled before the - * call to &drm_panel_funcs.disable(). - * - * A typical implementation for a panel driver supporting backlight - * control over DP AUX will call this function at probe time. - * Backlight will then be handled transparently without requiring - * any intervention from the driver. - * - * drm_panel_dp_aux_backlight() must be called after the call to drm_panel_init(). - * - * Return: 0 on success or a negative error code on failure. - */ -int drm_panel_dp_aux_backlight(struct drm_panel *panel, struct drm_dp_aux *aux) -{ - struct dp_aux_backlight *bl; - struct backlight_properties props = { 0 }; - u16 current_level; - u8 current_mode; - u8 edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]; - int ret; - - if (!panel || !panel->dev || !aux) - return -EINVAL; - - ret = drm_dp_dpcd_read(aux, DP_EDP_DPCD_REV, edp_dpcd, - EDP_DISPLAY_CTL_CAP_SIZE); - if (ret < 0) - return ret; - - if (!drm_edp_backlight_supported(edp_dpcd)) { - DRM_DEV_INFO(panel->dev, "DP AUX backlight is not supported\n"); - return 0; - } - - bl = devm_kzalloc(panel->dev, sizeof(*bl), GFP_KERNEL); - if (!bl) - return -ENOMEM; - - bl->aux = aux; - - ret = drm_edp_backlight_init(aux, &bl->info, 0, edp_dpcd, - ¤t_level, ¤t_mode); - if (ret < 0) - return ret; - - props.type = BACKLIGHT_RAW; - props.brightness = current_level; - props.max_brightness = bl->info.max; - - bl->base = devm_backlight_device_register(panel->dev, "dp_aux_backlight", - panel->dev, bl, - &dp_aux_bl_ops, &props); - if (IS_ERR(bl->base)) - return PTR_ERR(bl->base); - - backlight_disable(bl->base); - - panel->backlight = bl->base; - - return 0; -} -EXPORT_SYMBOL(drm_panel_dp_aux_backlight); - -#endif diff --git a/drivers/gpu/drm/dp/drm_dp_aux_bus.c b/drivers/gpu/drm/dp/drm_dp_aux_bus.c deleted file mode 100644 index 415afce3cf96..000000000000 --- a/drivers/gpu/drm/dp/drm_dp_aux_bus.c +++ /dev/null @@ -1,323 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright 2021 Google Inc. - * - * The DP AUX bus is used for devices that are connected over a DisplayPort - * AUX bus. The devices on the far side of the bus are referred to as - * endpoints in this code. - * - * Commonly there is only one device connected to the DP AUX bus: a panel. - * Though historically panels (even DP panels) have been modeled as simple - * platform devices, putting them under the DP AUX bus allows the panel driver - * to perform transactions on that bus. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include - -/** - * dp_aux_ep_match() - The match function for the dp_aux_bus. - * @dev: The device to match. - * @drv: The driver to try to match against. - * - * At the moment, we just match on device tree. - * - * Return: True if this driver matches this device; false otherwise. - */ -static int dp_aux_ep_match(struct device *dev, struct device_driver *drv) -{ - return !!of_match_device(drv->of_match_table, dev); -} - -/** - * dp_aux_ep_probe() - The probe function for the dp_aux_bus. - * @dev: The device to probe. - * - * Calls through to the endpoint driver probe. - * - * Return: 0 if no error or negative error code. - */ -static int dp_aux_ep_probe(struct device *dev) -{ - struct dp_aux_ep_driver *aux_ep_drv = to_dp_aux_ep_drv(dev->driver); - struct dp_aux_ep_device *aux_ep = to_dp_aux_ep_dev(dev); - int ret; - - ret = dev_pm_domain_attach(dev, true); - if (ret) - return dev_err_probe(dev, ret, "Failed to attach to PM Domain\n"); - - ret = aux_ep_drv->probe(aux_ep); - if (ret) - dev_pm_domain_detach(dev, true); - - return ret; -} - -/** - * dp_aux_ep_remove() - The remove function for the dp_aux_bus. - * @dev: The device to remove. - * - * Calls through to the endpoint driver remove. - * - */ -static void dp_aux_ep_remove(struct device *dev) -{ - struct dp_aux_ep_driver *aux_ep_drv = to_dp_aux_ep_drv(dev->driver); - struct dp_aux_ep_device *aux_ep = to_dp_aux_ep_dev(dev); - - if (aux_ep_drv->remove) - aux_ep_drv->remove(aux_ep); - dev_pm_domain_detach(dev, true); -} - -/** - * dp_aux_ep_shutdown() - The shutdown function for the dp_aux_bus. - * @dev: The device to shutdown. - * - * Calls through to the endpoint driver shutdown. - */ -static void dp_aux_ep_shutdown(struct device *dev) -{ - struct dp_aux_ep_driver *aux_ep_drv; - - if (!dev->driver) - return; - - aux_ep_drv = to_dp_aux_ep_drv(dev->driver); - if (aux_ep_drv->shutdown) - aux_ep_drv->shutdown(to_dp_aux_ep_dev(dev)); -} - -static struct bus_type dp_aux_bus_type = { - .name = "dp-aux", - .match = dp_aux_ep_match, - .probe = dp_aux_ep_probe, - .remove = dp_aux_ep_remove, - .shutdown = dp_aux_ep_shutdown, -}; - -static ssize_t modalias_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return of_device_modalias(dev, buf, PAGE_SIZE); -} -static DEVICE_ATTR_RO(modalias); - -static struct attribute *dp_aux_ep_dev_attrs[] = { - &dev_attr_modalias.attr, - NULL, -}; -ATTRIBUTE_GROUPS(dp_aux_ep_dev); - -/** - * dp_aux_ep_dev_release() - Free memory for the dp_aux_ep device - * @dev: The device to free. - * - * Return: 0 if no error or negative error code. - */ -static void dp_aux_ep_dev_release(struct device *dev) -{ - kfree(to_dp_aux_ep_dev(dev)); -} - -static struct device_type dp_aux_device_type_type = { - .groups = dp_aux_ep_dev_groups, - .uevent = of_device_uevent_modalias, - .release = dp_aux_ep_dev_release, -}; - -/** - * of_dp_aux_ep_destroy() - Destroy an DP AUX endpoint device - * @dev: The device to destroy. - * @data: Not used - * - * This is just used as a callback by of_dp_aux_depopulate_ep_devices() and - * is called for _all_ of the child devices of the device providing the AUX bus. - * We'll only act on those that are of type "dp_aux_bus_type". - * - * This function is effectively an inverse of what's in the loop - * in of_dp_aux_populate_ep_devices(). - * - * Return: 0 if no error or negative error code. - */ -static int of_dp_aux_ep_destroy(struct device *dev, void *data) -{ - struct device_node *np = dev->of_node; - - if (dev->bus != &dp_aux_bus_type) - return 0; - - if (!of_node_check_flag(np, OF_POPULATED)) - return 0; - - of_node_clear_flag(np, OF_POPULATED); - of_node_put(np); - - device_unregister(dev); - - return 0; -} - -/** - * of_dp_aux_depopulate_ep_devices() - Undo of_dp_aux_populate_ep_devices - * @aux: The AUX channel whose devices we want to depopulate - * - * This will destroy all devices that were created - * by of_dp_aux_populate_ep_devices(). - */ -void of_dp_aux_depopulate_ep_devices(struct drm_dp_aux *aux) -{ - device_for_each_child_reverse(aux->dev, NULL, of_dp_aux_ep_destroy); -} -EXPORT_SYMBOL_GPL(of_dp_aux_depopulate_ep_devices); - -/** - * of_dp_aux_populate_ep_devices() - Populate the endpoint devices on the DP AUX - * @aux: The AUX channel whose devices we want to populate. It is required that - * drm_dp_aux_init() has already been called for this AUX channel. - * - * This will populate all the devices under the "aux-bus" node of the device - * providing the AUX channel (AKA aux->dev). - * - * When this function finishes, it is _possible_ (but not guaranteed) that - * our sub-devices will have finished probing. It should be noted that if our - * sub-devices return -EPROBE_DEFER that we will not return any error codes - * ourselves but our sub-devices will _not_ have actually probed successfully - * yet. There may be other cases (maybe added in the future?) where sub-devices - * won't have been probed yet when this function returns, so it's best not to - * rely on that. - * - * If this function succeeds you should later make sure you call - * of_dp_aux_depopulate_ep_devices() to undo it, or just use the devm version - * of this function. - * - * Return: 0 if no error or negative error code. - */ -int of_dp_aux_populate_ep_devices(struct drm_dp_aux *aux) -{ - struct device_node *bus, *np; - struct dp_aux_ep_device *aux_ep; - int ret; - - /* drm_dp_aux_init() should have been called already; warn if not */ - WARN_ON_ONCE(!aux->ddc.algo); - - if (!aux->dev->of_node) - return 0; - - bus = of_get_child_by_name(aux->dev->of_node, "aux-bus"); - if (!bus) - return 0; - - for_each_available_child_of_node(bus, np) { - if (of_node_test_and_set_flag(np, OF_POPULATED)) - continue; - - aux_ep = kzalloc(sizeof(*aux_ep), GFP_KERNEL); - if (!aux_ep) - continue; - aux_ep->aux = aux; - - aux_ep->dev.parent = aux->dev; - aux_ep->dev.bus = &dp_aux_bus_type; - aux_ep->dev.type = &dp_aux_device_type_type; - aux_ep->dev.of_node = of_node_get(np); - dev_set_name(&aux_ep->dev, "aux-%s", dev_name(aux->dev)); - - ret = device_register(&aux_ep->dev); - if (ret) { - dev_err(aux->dev, "Failed to create AUX EP for %pOF: %d\n", np, ret); - of_node_clear_flag(np, OF_POPULATED); - of_node_put(np); - - /* - * As per docs of device_register(), call this instead - * of kfree() directly for error cases. - */ - put_device(&aux_ep->dev); - - /* - * Following in the footsteps of of_i2c_register_devices(), - * we won't fail the whole function here--we'll just - * continue registering any other devices we find. - */ - } - } - - of_node_put(bus); - - return 0; -} - -static void of_dp_aux_depopulate_ep_devices_void(void *data) -{ - of_dp_aux_depopulate_ep_devices(data); -} - -/** - * devm_of_dp_aux_populate_ep_devices() - devm wrapper for of_dp_aux_populate_ep_devices() - * @aux: The AUX channel whose devices we want to populate - * - * Handles freeing w/ devm on the device "aux->dev". - * - * Return: 0 if no error or negative error code. - */ -int devm_of_dp_aux_populate_ep_devices(struct drm_dp_aux *aux) -{ - int ret; - - ret = of_dp_aux_populate_ep_devices(aux); - if (ret) - return ret; - - return devm_add_action_or_reset(aux->dev, - of_dp_aux_depopulate_ep_devices_void, - aux); -} -EXPORT_SYMBOL_GPL(devm_of_dp_aux_populate_ep_devices); - -int __dp_aux_dp_driver_register(struct dp_aux_ep_driver *drv, struct module *owner) -{ - drv->driver.owner = owner; - drv->driver.bus = &dp_aux_bus_type; - - return driver_register(&drv->driver); -} -EXPORT_SYMBOL_GPL(__dp_aux_dp_driver_register); - -void dp_aux_dp_driver_unregister(struct dp_aux_ep_driver *drv) -{ - driver_unregister(&drv->driver); -} -EXPORT_SYMBOL_GPL(dp_aux_dp_driver_unregister); - -static int __init dp_aux_bus_init(void) -{ - int ret; - - ret = bus_register(&dp_aux_bus_type); - if (ret) - return ret; - - return 0; -} - -static void __exit dp_aux_bus_exit(void) -{ - bus_unregister(&dp_aux_bus_type); -} - -subsys_initcall(dp_aux_bus_init); -module_exit(dp_aux_bus_exit); - -MODULE_AUTHOR("Douglas Anderson "); -MODULE_DESCRIPTION("DRM DisplayPort AUX bus"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/dp/drm_dp_aux_dev.c b/drivers/gpu/drm/dp/drm_dp_aux_dev.c deleted file mode 100644 index 53ad4e72790b..000000000000 --- a/drivers/gpu/drm/dp/drm_dp_aux_dev.c +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright © 2015 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - * - * Authors: - * Rafael Antognolli - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "drm_dp_helper_internal.h" - -struct drm_dp_aux_dev { - unsigned index; - struct drm_dp_aux *aux; - struct device *dev; - struct kref refcount; - atomic_t usecount; -}; - -#define DRM_AUX_MINORS 256 -#define AUX_MAX_OFFSET (1 << 20) -static DEFINE_IDR(aux_idr); -static DEFINE_MUTEX(aux_idr_mutex); -static struct class *drm_dp_aux_dev_class; -static int drm_dev_major = -1; - -static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_minor(unsigned index) -{ - struct drm_dp_aux_dev *aux_dev = NULL; - - mutex_lock(&aux_idr_mutex); - aux_dev = idr_find(&aux_idr, index); - if (aux_dev && !kref_get_unless_zero(&aux_dev->refcount)) - aux_dev = NULL; - mutex_unlock(&aux_idr_mutex); - - return aux_dev; -} - -static struct drm_dp_aux_dev *alloc_drm_dp_aux_dev(struct drm_dp_aux *aux) -{ - struct drm_dp_aux_dev *aux_dev; - int index; - - aux_dev = kzalloc(sizeof(*aux_dev), GFP_KERNEL); - if (!aux_dev) - return ERR_PTR(-ENOMEM); - aux_dev->aux = aux; - atomic_set(&aux_dev->usecount, 1); - kref_init(&aux_dev->refcount); - - mutex_lock(&aux_idr_mutex); - index = idr_alloc(&aux_idr, aux_dev, 0, DRM_AUX_MINORS, GFP_KERNEL); - mutex_unlock(&aux_idr_mutex); - if (index < 0) { - kfree(aux_dev); - return ERR_PTR(index); - } - aux_dev->index = index; - - return aux_dev; -} - -static void release_drm_dp_aux_dev(struct kref *ref) -{ - struct drm_dp_aux_dev *aux_dev = - container_of(ref, struct drm_dp_aux_dev, refcount); - - kfree(aux_dev); -} - -static ssize_t name_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - ssize_t res; - struct drm_dp_aux_dev *aux_dev = - drm_dp_aux_dev_get_by_minor(MINOR(dev->devt)); - - if (!aux_dev) - return -ENODEV; - - res = sprintf(buf, "%s\n", aux_dev->aux->name); - kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); - - return res; -} -static DEVICE_ATTR_RO(name); - -static struct attribute *drm_dp_aux_attrs[] = { - &dev_attr_name.attr, - NULL, -}; -ATTRIBUTE_GROUPS(drm_dp_aux); - -static int auxdev_open(struct inode *inode, struct file *file) -{ - unsigned int minor = iminor(inode); - struct drm_dp_aux_dev *aux_dev; - - aux_dev = drm_dp_aux_dev_get_by_minor(minor); - if (!aux_dev) - return -ENODEV; - - file->private_data = aux_dev; - return 0; -} - -static loff_t auxdev_llseek(struct file *file, loff_t offset, int whence) -{ - return fixed_size_llseek(file, offset, whence, AUX_MAX_OFFSET); -} - -static ssize_t auxdev_read_iter(struct kiocb *iocb, struct iov_iter *to) -{ - struct drm_dp_aux_dev *aux_dev = iocb->ki_filp->private_data; - loff_t pos = iocb->ki_pos; - ssize_t res = 0; - - if (!atomic_inc_not_zero(&aux_dev->usecount)) - return -ENODEV; - - iov_iter_truncate(to, AUX_MAX_OFFSET - pos); - - while (iov_iter_count(to)) { - uint8_t buf[DP_AUX_MAX_PAYLOAD_BYTES]; - ssize_t todo = min(iov_iter_count(to), sizeof(buf)); - - if (signal_pending(current)) { - res = -ERESTARTSYS; - break; - } - - res = drm_dp_dpcd_read(aux_dev->aux, pos, buf, todo); - - if (res <= 0) - break; - - if (copy_to_iter(buf, res, to) != res) { - res = -EFAULT; - break; - } - - pos += res; - } - - if (pos != iocb->ki_pos) - res = pos - iocb->ki_pos; - iocb->ki_pos = pos; - - if (atomic_dec_and_test(&aux_dev->usecount)) - wake_up_var(&aux_dev->usecount); - - return res; -} - -static ssize_t auxdev_write_iter(struct kiocb *iocb, struct iov_iter *from) -{ - struct drm_dp_aux_dev *aux_dev = iocb->ki_filp->private_data; - loff_t pos = iocb->ki_pos; - ssize_t res = 0; - - if (!atomic_inc_not_zero(&aux_dev->usecount)) - return -ENODEV; - - iov_iter_truncate(from, AUX_MAX_OFFSET - pos); - - while (iov_iter_count(from)) { - uint8_t buf[DP_AUX_MAX_PAYLOAD_BYTES]; - ssize_t todo = min(iov_iter_count(from), sizeof(buf)); - - if (signal_pending(current)) { - res = -ERESTARTSYS; - break; - } - - if (!copy_from_iter_full(buf, todo, from)) { - res = -EFAULT; - break; - } - - res = drm_dp_dpcd_write(aux_dev->aux, pos, buf, todo); - - if (res <= 0) - break; - - pos += res; - } - - if (pos != iocb->ki_pos) - res = pos - iocb->ki_pos; - iocb->ki_pos = pos; - - if (atomic_dec_and_test(&aux_dev->usecount)) - wake_up_var(&aux_dev->usecount); - - return res; -} - -static int auxdev_release(struct inode *inode, struct file *file) -{ - struct drm_dp_aux_dev *aux_dev = file->private_data; - - kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); - return 0; -} - -static const struct file_operations auxdev_fops = { - .owner = THIS_MODULE, - .llseek = auxdev_llseek, - .read_iter = auxdev_read_iter, - .write_iter = auxdev_write_iter, - .open = auxdev_open, - .release = auxdev_release, -}; - -#define to_auxdev(d) container_of(d, struct drm_dp_aux_dev, aux) - -static struct drm_dp_aux_dev *drm_dp_aux_dev_get_by_aux(struct drm_dp_aux *aux) -{ - struct drm_dp_aux_dev *iter, *aux_dev = NULL; - int id; - - /* don't increase kref count here because this function should only be - * used by drm_dp_aux_unregister_devnode. Thus, it will always have at - * least one reference - the one that drm_dp_aux_register_devnode - * created - */ - mutex_lock(&aux_idr_mutex); - idr_for_each_entry(&aux_idr, iter, id) { - if (iter->aux == aux) { - aux_dev = iter; - break; - } - } - mutex_unlock(&aux_idr_mutex); - return aux_dev; -} - -void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) -{ - struct drm_dp_aux_dev *aux_dev; - unsigned int minor; - - aux_dev = drm_dp_aux_dev_get_by_aux(aux); - if (!aux_dev) /* attach must have failed */ - return; - - /* - * As some AUX adapters may exist as platform devices which outlive their respective DRM - * devices, we clear drm_dev to ensure that we never accidentally reference a stale pointer - */ - aux->drm_dev = NULL; - - mutex_lock(&aux_idr_mutex); - idr_remove(&aux_idr, aux_dev->index); - mutex_unlock(&aux_idr_mutex); - - atomic_dec(&aux_dev->usecount); - wait_var_event(&aux_dev->usecount, !atomic_read(&aux_dev->usecount)); - - minor = aux_dev->index; - if (aux_dev->dev) - device_destroy(drm_dp_aux_dev_class, - MKDEV(drm_dev_major, minor)); - - DRM_DEBUG("drm_dp_aux_dev: aux [%s] unregistering\n", aux->name); - kref_put(&aux_dev->refcount, release_drm_dp_aux_dev); -} - -int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) -{ - struct drm_dp_aux_dev *aux_dev; - int res; - - aux_dev = alloc_drm_dp_aux_dev(aux); - if (IS_ERR(aux_dev)) - return PTR_ERR(aux_dev); - - aux_dev->dev = device_create(drm_dp_aux_dev_class, aux->dev, - MKDEV(drm_dev_major, aux_dev->index), NULL, - "drm_dp_aux%d", aux_dev->index); - if (IS_ERR(aux_dev->dev)) { - res = PTR_ERR(aux_dev->dev); - aux_dev->dev = NULL; - goto error; - } - - DRM_DEBUG("drm_dp_aux_dev: aux [%s] registered as minor %d\n", - aux->name, aux_dev->index); - return 0; -error: - drm_dp_aux_unregister_devnode(aux); - return res; -} - -int drm_dp_aux_dev_init(void) -{ - int res; - - drm_dp_aux_dev_class = class_create(THIS_MODULE, "drm_dp_aux_dev"); - if (IS_ERR(drm_dp_aux_dev_class)) { - return PTR_ERR(drm_dp_aux_dev_class); - } - drm_dp_aux_dev_class->dev_groups = drm_dp_aux_groups; - - res = register_chrdev(0, "aux", &auxdev_fops); - if (res < 0) - goto out; - drm_dev_major = res; - - return 0; -out: - class_destroy(drm_dp_aux_dev_class); - return res; -} - -void drm_dp_aux_dev_exit(void) -{ - unregister_chrdev(drm_dev_major, "aux"); - class_destroy(drm_dp_aux_dev_class); -} diff --git a/drivers/gpu/drm/dp/drm_dp_cec.c b/drivers/gpu/drm/dp/drm_dp_cec.c deleted file mode 100644 index f9e927355879..000000000000 --- a/drivers/gpu/drm/dp/drm_dp_cec.c +++ /dev/null @@ -1,451 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * DisplayPort CEC-Tunneling-over-AUX support - * - * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved. - */ - -#include -#include -#include - -#include - -#include -#include -#include - -/* - * Unfortunately it turns out that we have a chicken-and-egg situation - * here. Quite a few active (mini-)DP-to-HDMI or USB-C-to-HDMI adapters - * have a converter chip that supports CEC-Tunneling-over-AUX (usually the - * Parade PS176), but they do not wire up the CEC pin, thus making CEC - * useless. Note that MegaChips 2900-based adapters appear to have good - * support for CEC tunneling. Those adapters that I have tested using - * this chipset all have the CEC line connected. - * - * Sadly there is no way for this driver to know this. What happens is - * that a /dev/cecX device is created that is isolated and unable to see - * any of the other CEC devices. Quite literally the CEC wire is cut - * (or in this case, never connected in the first place). - * - * The reason so few adapters support this is that this tunneling protocol - * was never supported by any OS. So there was no easy way of testing it, - * and no incentive to correctly wire up the CEC pin. - * - * Hopefully by creating this driver it will be easier for vendors to - * finally fix their adapters and test the CEC functionality. - * - * I keep a list of known working adapters here: - * - * https://hverkuil.home.xs4all.nl/cec-status.txt - * - * Please mail me (hverkuil@xs4all.nl) if you find an adapter that works - * and is not yet listed there. - * - * Note that the current implementation does not support CEC over an MST hub. - * As far as I can see there is no mechanism defined in the DisplayPort - * standard to transport CEC interrupts over an MST device. It might be - * possible to do this through polling, but I have not been able to get that - * to work. - */ - -/** - * DOC: dp cec helpers - * - * These functions take care of supporting the CEC-Tunneling-over-AUX - * feature of DisplayPort-to-HDMI adapters. - */ - -/* - * When the EDID is unset because the HPD went low, then the CEC DPCD registers - * typically can no longer be read (true for a DP-to-HDMI adapter since it is - * powered by the HPD). However, some displays toggle the HPD off and on for a - * short period for one reason or another, and that would cause the CEC adapter - * to be removed and added again, even though nothing else changed. - * - * This module parameter sets a delay in seconds before the CEC adapter is - * actually unregistered. Only if the HPD does not return within that time will - * the CEC adapter be unregistered. - * - * If it is set to a value >= NEVER_UNREG_DELAY, then the CEC adapter will never - * be unregistered for as long as the connector remains registered. - * - * If it is set to 0, then the CEC adapter will be unregistered immediately as - * soon as the HPD disappears. - * - * The default is one second to prevent short HPD glitches from unregistering - * the CEC adapter. - * - * Note that for integrated HDMI branch devices that support CEC the DPCD - * registers remain available even if the HPD goes low since it is not powered - * by the HPD. In that case the CEC adapter will never be unregistered during - * the life time of the connector. At least, this is the theory since I do not - * have hardware with an integrated HDMI branch device that supports CEC. - */ -#define NEVER_UNREG_DELAY 1000 -static unsigned int drm_dp_cec_unregister_delay = 1; -module_param(drm_dp_cec_unregister_delay, uint, 0600); -MODULE_PARM_DESC(drm_dp_cec_unregister_delay, - "CEC unregister delay in seconds, 0: no delay, >= 1000: never unregister"); - -static int drm_dp_cec_adap_enable(struct cec_adapter *adap, bool enable) -{ - struct drm_dp_aux *aux = cec_get_drvdata(adap); - u32 val = enable ? DP_CEC_TUNNELING_ENABLE : 0; - ssize_t err = 0; - - err = drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_CONTROL, val); - return (enable && err < 0) ? err : 0; -} - -static int drm_dp_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) -{ - struct drm_dp_aux *aux = cec_get_drvdata(adap); - /* Bit 15 (logical address 15) should always be set */ - u16 la_mask = 1 << CEC_LOG_ADDR_BROADCAST; - u8 mask[2]; - ssize_t err; - - if (addr != CEC_LOG_ADDR_INVALID) - la_mask |= adap->log_addrs.log_addr_mask | (1 << addr); - mask[0] = la_mask & 0xff; - mask[1] = la_mask >> 8; - err = drm_dp_dpcd_write(aux, DP_CEC_LOGICAL_ADDRESS_MASK, mask, 2); - return (addr != CEC_LOG_ADDR_INVALID && err < 0) ? err : 0; -} - -static int drm_dp_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, - u32 signal_free_time, struct cec_msg *msg) -{ - struct drm_dp_aux *aux = cec_get_drvdata(adap); - unsigned int retries = min(5, attempts - 1); - ssize_t err; - - err = drm_dp_dpcd_write(aux, DP_CEC_TX_MESSAGE_BUFFER, - msg->msg, msg->len); - if (err < 0) - return err; - - err = drm_dp_dpcd_writeb(aux, DP_CEC_TX_MESSAGE_INFO, - (msg->len - 1) | (retries << 4) | - DP_CEC_TX_MESSAGE_SEND); - return err < 0 ? err : 0; -} - -static int drm_dp_cec_adap_monitor_all_enable(struct cec_adapter *adap, - bool enable) -{ - struct drm_dp_aux *aux = cec_get_drvdata(adap); - ssize_t err; - u8 val; - - if (!(adap->capabilities & CEC_CAP_MONITOR_ALL)) - return 0; - - err = drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_CONTROL, &val); - if (err >= 0) { - if (enable) - val |= DP_CEC_SNOOPING_ENABLE; - else - val &= ~DP_CEC_SNOOPING_ENABLE; - err = drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_CONTROL, val); - } - return (enable && err < 0) ? err : 0; -} - -static void drm_dp_cec_adap_status(struct cec_adapter *adap, - struct seq_file *file) -{ - struct drm_dp_aux *aux = cec_get_drvdata(adap); - struct drm_dp_desc desc; - struct drm_dp_dpcd_ident *id = &desc.ident; - - if (drm_dp_read_desc(aux, &desc, true)) - return; - seq_printf(file, "OUI: %*phD\n", - (int)sizeof(id->oui), id->oui); - seq_printf(file, "ID: %*pE\n", - (int)strnlen(id->device_id, sizeof(id->device_id)), - id->device_id); - seq_printf(file, "HW Rev: %d.%d\n", id->hw_rev >> 4, id->hw_rev & 0xf); - /* - * Show this both in decimal and hex: at least one vendor - * always reports this in hex. - */ - seq_printf(file, "FW/SW Rev: %d.%d (0x%02x.0x%02x)\n", - id->sw_major_rev, id->sw_minor_rev, - id->sw_major_rev, id->sw_minor_rev); -} - -static const struct cec_adap_ops drm_dp_cec_adap_ops = { - .adap_enable = drm_dp_cec_adap_enable, - .adap_log_addr = drm_dp_cec_adap_log_addr, - .adap_transmit = drm_dp_cec_adap_transmit, - .adap_monitor_all_enable = drm_dp_cec_adap_monitor_all_enable, - .adap_status = drm_dp_cec_adap_status, -}; - -static int drm_dp_cec_received(struct drm_dp_aux *aux) -{ - struct cec_adapter *adap = aux->cec.adap; - struct cec_msg msg; - u8 rx_msg_info; - ssize_t err; - - err = drm_dp_dpcd_readb(aux, DP_CEC_RX_MESSAGE_INFO, &rx_msg_info); - if (err < 0) - return err; - - if (!(rx_msg_info & DP_CEC_RX_MESSAGE_ENDED)) - return 0; - - msg.len = (rx_msg_info & DP_CEC_RX_MESSAGE_LEN_MASK) + 1; - err = drm_dp_dpcd_read(aux, DP_CEC_RX_MESSAGE_BUFFER, msg.msg, msg.len); - if (err < 0) - return err; - - cec_received_msg(adap, &msg); - return 0; -} - -static void drm_dp_cec_handle_irq(struct drm_dp_aux *aux) -{ - struct cec_adapter *adap = aux->cec.adap; - u8 flags; - - if (drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_IRQ_FLAGS, &flags) < 0) - return; - - if (flags & DP_CEC_RX_MESSAGE_INFO_VALID) - drm_dp_cec_received(aux); - - if (flags & DP_CEC_TX_MESSAGE_SENT) - cec_transmit_attempt_done(adap, CEC_TX_STATUS_OK); - else if (flags & DP_CEC_TX_LINE_ERROR) - cec_transmit_attempt_done(adap, CEC_TX_STATUS_ERROR | - CEC_TX_STATUS_MAX_RETRIES); - else if (flags & - (DP_CEC_TX_ADDRESS_NACK_ERROR | DP_CEC_TX_DATA_NACK_ERROR)) - cec_transmit_attempt_done(adap, CEC_TX_STATUS_NACK | - CEC_TX_STATUS_MAX_RETRIES); - drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_IRQ_FLAGS, flags); -} - -/** - * drm_dp_cec_irq() - handle CEC interrupt, if any - * @aux: DisplayPort AUX channel - * - * Should be called when handling an IRQ_HPD request. If CEC-tunneling-over-AUX - * is present, then it will check for a CEC_IRQ and handle it accordingly. - */ -void drm_dp_cec_irq(struct drm_dp_aux *aux) -{ - u8 cec_irq; - int ret; - - /* No transfer function was set, so not a DP connector */ - if (!aux->transfer) - return; - - mutex_lock(&aux->cec.lock); - if (!aux->cec.adap) - goto unlock; - - ret = drm_dp_dpcd_readb(aux, DP_DEVICE_SERVICE_IRQ_VECTOR_ESI1, - &cec_irq); - if (ret < 0 || !(cec_irq & DP_CEC_IRQ)) - goto unlock; - - drm_dp_cec_handle_irq(aux); - drm_dp_dpcd_writeb(aux, DP_DEVICE_SERVICE_IRQ_VECTOR_ESI1, DP_CEC_IRQ); -unlock: - mutex_unlock(&aux->cec.lock); -} -EXPORT_SYMBOL(drm_dp_cec_irq); - -static bool drm_dp_cec_cap(struct drm_dp_aux *aux, u8 *cec_cap) -{ - u8 cap = 0; - - if (drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_CAPABILITY, &cap) != 1 || - !(cap & DP_CEC_TUNNELING_CAPABLE)) - return false; - if (cec_cap) - *cec_cap = cap; - return true; -} - -/* - * Called if the HPD was low for more than drm_dp_cec_unregister_delay - * seconds. This unregisters the CEC adapter. - */ -static void drm_dp_cec_unregister_work(struct work_struct *work) -{ - struct drm_dp_aux *aux = container_of(work, struct drm_dp_aux, - cec.unregister_work.work); - - mutex_lock(&aux->cec.lock); - cec_unregister_adapter(aux->cec.adap); - aux->cec.adap = NULL; - mutex_unlock(&aux->cec.lock); -} - -/* - * A new EDID is set. If there is no CEC adapter, then create one. If - * there was a CEC adapter, then check if the CEC adapter properties - * were unchanged and just update the CEC physical address. Otherwise - * unregister the old CEC adapter and create a new one. - */ -void drm_dp_cec_set_edid(struct drm_dp_aux *aux, const struct edid *edid) -{ - struct drm_connector *connector = aux->cec.connector; - u32 cec_caps = CEC_CAP_DEFAULTS | CEC_CAP_NEEDS_HPD | - CEC_CAP_CONNECTOR_INFO; - struct cec_connector_info conn_info; - unsigned int num_las = 1; - u8 cap; - - /* No transfer function was set, so not a DP connector */ - if (!aux->transfer) - return; - -#ifndef CONFIG_MEDIA_CEC_RC - /* - * CEC_CAP_RC is part of CEC_CAP_DEFAULTS, but it is stripped by - * cec_allocate_adapter() if CONFIG_MEDIA_CEC_RC is undefined. - * - * Do this here as well to ensure the tests against cec_caps are - * correct. - */ - cec_caps &= ~CEC_CAP_RC; -#endif - cancel_delayed_work_sync(&aux->cec.unregister_work); - - mutex_lock(&aux->cec.lock); - if (!drm_dp_cec_cap(aux, &cap)) { - /* CEC is not supported, unregister any existing adapter */ - cec_unregister_adapter(aux->cec.adap); - aux->cec.adap = NULL; - goto unlock; - } - - if (cap & DP_CEC_SNOOPING_CAPABLE) - cec_caps |= CEC_CAP_MONITOR_ALL; - if (cap & DP_CEC_MULTIPLE_LA_CAPABLE) - num_las = CEC_MAX_LOG_ADDRS; - - if (aux->cec.adap) { - if (aux->cec.adap->capabilities == cec_caps && - aux->cec.adap->available_log_addrs == num_las) { - /* Unchanged, so just set the phys addr */ - cec_s_phys_addr_from_edid(aux->cec.adap, edid); - goto unlock; - } - /* - * The capabilities changed, so unregister the old - * adapter first. - */ - cec_unregister_adapter(aux->cec.adap); - } - - /* Create a new adapter */ - aux->cec.adap = cec_allocate_adapter(&drm_dp_cec_adap_ops, - aux, connector->name, cec_caps, - num_las); - if (IS_ERR(aux->cec.adap)) { - aux->cec.adap = NULL; - goto unlock; - } - - cec_fill_conn_info_from_drm(&conn_info, connector); - cec_s_conn_info(aux->cec.adap, &conn_info); - - if (cec_register_adapter(aux->cec.adap, connector->dev->dev)) { - cec_delete_adapter(aux->cec.adap); - aux->cec.adap = NULL; - } else { - /* - * Update the phys addr for the new CEC adapter. When called - * from drm_dp_cec_register_connector() edid == NULL, so in - * that case the phys addr is just invalidated. - */ - cec_s_phys_addr_from_edid(aux->cec.adap, edid); - } -unlock: - mutex_unlock(&aux->cec.lock); -} -EXPORT_SYMBOL(drm_dp_cec_set_edid); - -/* - * The EDID disappeared (likely because of the HPD going down). - */ -void drm_dp_cec_unset_edid(struct drm_dp_aux *aux) -{ - /* No transfer function was set, so not a DP connector */ - if (!aux->transfer) - return; - - cancel_delayed_work_sync(&aux->cec.unregister_work); - - mutex_lock(&aux->cec.lock); - if (!aux->cec.adap) - goto unlock; - - cec_phys_addr_invalidate(aux->cec.adap); - /* - * We're done if we want to keep the CEC device - * (drm_dp_cec_unregister_delay is >= NEVER_UNREG_DELAY) or if the - * DPCD still indicates the CEC capability (expected for an integrated - * HDMI branch device). - */ - if (drm_dp_cec_unregister_delay < NEVER_UNREG_DELAY && - !drm_dp_cec_cap(aux, NULL)) { - /* - * Unregister the CEC adapter after drm_dp_cec_unregister_delay - * seconds. This to debounce short HPD off-and-on cycles from - * displays. - */ - schedule_delayed_work(&aux->cec.unregister_work, - drm_dp_cec_unregister_delay * HZ); - } -unlock: - mutex_unlock(&aux->cec.lock); -} -EXPORT_SYMBOL(drm_dp_cec_unset_edid); - -/** - * drm_dp_cec_register_connector() - register a new connector - * @aux: DisplayPort AUX channel - * @connector: drm connector - * - * A new connector was registered with associated CEC adapter name and - * CEC adapter parent device. After registering the name and parent - * drm_dp_cec_set_edid() is called to check if the connector supports - * CEC and to register a CEC adapter if that is the case. - */ -void drm_dp_cec_register_connector(struct drm_dp_aux *aux, - struct drm_connector *connector) -{ - WARN_ON(aux->cec.adap); - if (WARN_ON(!aux->transfer)) - return; - aux->cec.connector = connector; - INIT_DELAYED_WORK(&aux->cec.unregister_work, - drm_dp_cec_unregister_work); -} -EXPORT_SYMBOL(drm_dp_cec_register_connector); - -/** - * drm_dp_cec_unregister_connector() - unregister the CEC adapter, if any - * @aux: DisplayPort AUX channel - */ -void drm_dp_cec_unregister_connector(struct drm_dp_aux *aux) -{ - if (!aux->cec.adap) - return; - cancel_delayed_work_sync(&aux->cec.unregister_work); - cec_unregister_adapter(aux->cec.adap); - aux->cec.adap = NULL; -} -EXPORT_SYMBOL(drm_dp_cec_unregister_connector); diff --git a/drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c b/drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c deleted file mode 100644 index 2049cb0f7ed0..000000000000 --- a/drivers/gpu/drm/dp/drm_dp_dual_mode_helper.c +++ /dev/null @@ -1,530 +0,0 @@ -/* - * Copyright © 2016 Intel Corporation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR - * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -/** - * DOC: dp dual mode helpers - * - * Helper functions to deal with DP dual mode (aka. DP++) adaptors. - * - * Type 1: - * Adaptor registers (if any) and the sink DDC bus may be accessed via I2C. - * - * Type 2: - * Adaptor registers and sink DDC bus can be accessed either via I2C or - * I2C-over-AUX. Source devices may choose to implement either of these - * access methods. - */ - -#define DP_DUAL_MODE_SLAVE_ADDRESS 0x40 - -/** - * drm_dp_dual_mode_read - Read from the DP dual mode adaptor register(s) - * @adapter: I2C adapter for the DDC bus - * @offset: register offset - * @buffer: buffer for return data - * @size: sizo of the buffer - * - * Reads @size bytes from the DP dual mode adaptor registers - * starting at @offset. - * - * Returns: - * 0 on success, negative error code on failure - */ -ssize_t drm_dp_dual_mode_read(struct i2c_adapter *adapter, - u8 offset, void *buffer, size_t size) -{ - struct i2c_msg msgs[] = { - { - .addr = DP_DUAL_MODE_SLAVE_ADDRESS, - .flags = 0, - .len = 1, - .buf = &offset, - }, - { - .addr = DP_DUAL_MODE_SLAVE_ADDRESS, - .flags = I2C_M_RD, - .len = size, - .buf = buffer, - }, - }; - int ret; - - ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs)); - if (ret < 0) - return ret; - if (ret != ARRAY_SIZE(msgs)) - return -EPROTO; - - return 0; -} -EXPORT_SYMBOL(drm_dp_dual_mode_read); - -/** - * drm_dp_dual_mode_write - Write to the DP dual mode adaptor register(s) - * @adapter: I2C adapter for the DDC bus - * @offset: register offset - * @buffer: buffer for write data - * @size: sizo of the buffer - * - * Writes @size bytes to the DP dual mode adaptor registers - * starting at @offset. - * - * Returns: - * 0 on success, negative error code on failure - */ -ssize_t drm_dp_dual_mode_write(struct i2c_adapter *adapter, - u8 offset, const void *buffer, size_t size) -{ - struct i2c_msg msg = { - .addr = DP_DUAL_MODE_SLAVE_ADDRESS, - .flags = 0, - .len = 1 + size, - .buf = NULL, - }; - void *data; - int ret; - - data = kmalloc(msg.len, GFP_KERNEL); - if (!data) - return -ENOMEM; - - msg.buf = data; - - memcpy(data, &offset, 1); - memcpy(data + 1, buffer, size); - - ret = i2c_transfer(adapter, &msg, 1); - - kfree(data); - - if (ret < 0) - return ret; - if (ret != 1) - return -EPROTO; - - return 0; -} -EXPORT_SYMBOL(drm_dp_dual_mode_write); - -static bool is_hdmi_adaptor(const char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN]) -{ - static const char dp_dual_mode_hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN] = - "DP-HDMI ADAPTOR\x04"; - - return memcmp(hdmi_id, dp_dual_mode_hdmi_id, - sizeof(dp_dual_mode_hdmi_id)) == 0; -} - -static bool is_type1_adaptor(uint8_t adaptor_id) -{ - return adaptor_id == 0 || adaptor_id == 0xff; -} - -static bool is_type2_adaptor(uint8_t adaptor_id) -{ - return adaptor_id == (DP_DUAL_MODE_TYPE_TYPE2 | - DP_DUAL_MODE_REV_TYPE2); -} - -static bool is_lspcon_adaptor(const char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN], - const uint8_t adaptor_id) -{ - return is_hdmi_adaptor(hdmi_id) && - (adaptor_id == (DP_DUAL_MODE_TYPE_TYPE2 | - DP_DUAL_MODE_TYPE_HAS_DPCD)); -} - -/** - * drm_dp_dual_mode_detect - Identify the DP dual mode adaptor - * @dev: &drm_device to use - * @adapter: I2C adapter for the DDC bus - * - * Attempt to identify the type of the DP dual mode adaptor used. - * - * Note that when the answer is @DRM_DP_DUAL_MODE_UNKNOWN it's not - * certain whether we're dealing with a native HDMI port or - * a type 1 DVI dual mode adaptor. The driver will have to use - * some other hardware/driver specific mechanism to make that - * distinction. - * - * Returns: - * The type of the DP dual mode adaptor used - */ -enum drm_dp_dual_mode_type drm_dp_dual_mode_detect(const struct drm_device *dev, - struct i2c_adapter *adapter) -{ - char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN] = {}; - uint8_t adaptor_id = 0x00; - ssize_t ret; - - /* - * Let's see if the adaptor is there the by reading the - * HDMI ID registers. - * - * Note that type 1 DVI adaptors are not required to implemnt - * any registers, and that presents a problem for detection. - * If the i2c transfer is nacked, we may or may not be dealing - * with a type 1 DVI adaptor. Some other mechanism of detecting - * the presence of the adaptor is required. One way would be - * to check the state of the CONFIG1 pin, Another method would - * simply require the driver to know whether the port is a DP++ - * port or a native HDMI port. Both of these methods are entirely - * hardware/driver specific so we can't deal with them here. - */ - ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_HDMI_ID, - hdmi_id, sizeof(hdmi_id)); - drm_dbg_kms(dev, "DP dual mode HDMI ID: %*pE (err %zd)\n", - ret ? 0 : (int)sizeof(hdmi_id), hdmi_id, ret); - if (ret) - return DRM_DP_DUAL_MODE_UNKNOWN; - - /* - * Sigh. Some (maybe all?) type 1 adaptors are broken and ack - * the offset but ignore it, and instead they just always return - * data from the start of the HDMI ID buffer. So for a broken - * type 1 HDMI adaptor a single byte read will always give us - * 0x44, and for a type 1 DVI adaptor it should give 0x00 - * (assuming it implements any registers). Fortunately neither - * of those values will match the type 2 signature of the - * DP_DUAL_MODE_ADAPTOR_ID register so we can proceed with - * the type 2 adaptor detection safely even in the presence - * of broken type 1 adaptors. - */ - ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_ADAPTOR_ID, - &adaptor_id, sizeof(adaptor_id)); - drm_dbg_kms(dev, "DP dual mode adaptor ID: %02x (err %zd)\n", adaptor_id, ret); - if (ret == 0) { - if (is_lspcon_adaptor(hdmi_id, adaptor_id)) - return DRM_DP_DUAL_MODE_LSPCON; - if (is_type2_adaptor(adaptor_id)) { - if (is_hdmi_adaptor(hdmi_id)) - return DRM_DP_DUAL_MODE_TYPE2_HDMI; - else - return DRM_DP_DUAL_MODE_TYPE2_DVI; - } - /* - * If neither a proper type 1 ID nor a broken type 1 adaptor - * as described above, assume type 1, but let the user know - * that we may have misdetected the type. - */ - if (!is_type1_adaptor(adaptor_id) && adaptor_id != hdmi_id[0]) - drm_err(dev, "Unexpected DP dual mode adaptor ID %02x\n", adaptor_id); - - } - - if (is_hdmi_adaptor(hdmi_id)) - return DRM_DP_DUAL_MODE_TYPE1_HDMI; - else - return DRM_DP_DUAL_MODE_TYPE1_DVI; -} -EXPORT_SYMBOL(drm_dp_dual_mode_detect); - -/** - * drm_dp_dual_mode_max_tmds_clock - Max TMDS clock for DP dual mode adaptor - * @dev: &drm_device to use - * @type: DP dual mode adaptor type - * @adapter: I2C adapter for the DDC bus - * - * Determine the max TMDS clock the adaptor supports based on the - * type of the dual mode adaptor and the DP_DUAL_MODE_MAX_TMDS_CLOCK - * register (on type2 adaptors). As some type 1 adaptors have - * problems with registers (see comments in drm_dp_dual_mode_detect()) - * we don't read the register on those, instead we simply assume - * a 165 MHz limit based on the specification. - * - * Returns: - * Maximum supported TMDS clock rate for the DP dual mode adaptor in kHz. - */ -int drm_dp_dual_mode_max_tmds_clock(const struct drm_device *dev, enum drm_dp_dual_mode_type type, - struct i2c_adapter *adapter) -{ - uint8_t max_tmds_clock; - ssize_t ret; - - /* native HDMI so no limit */ - if (type == DRM_DP_DUAL_MODE_NONE) - return 0; - - /* - * Type 1 adaptors are limited to 165MHz - * Type 2 adaptors can tells us their limit - */ - if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) - return 165000; - - ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_MAX_TMDS_CLOCK, - &max_tmds_clock, sizeof(max_tmds_clock)); - if (ret || max_tmds_clock == 0x00 || max_tmds_clock == 0xff) { - drm_dbg_kms(dev, "Failed to query max TMDS clock\n"); - return 165000; - } - - return max_tmds_clock * 5000 / 2; -} -EXPORT_SYMBOL(drm_dp_dual_mode_max_tmds_clock); - -/** - * drm_dp_dual_mode_get_tmds_output - Get the state of the TMDS output buffers in the DP dual mode adaptor - * @dev: &drm_device to use - * @type: DP dual mode adaptor type - * @adapter: I2C adapter for the DDC bus - * @enabled: current state of the TMDS output buffers - * - * Get the state of the TMDS output buffers in the adaptor. For - * type2 adaptors this is queried from the DP_DUAL_MODE_TMDS_OEN - * register. As some type 1 adaptors have problems with registers - * (see comments in drm_dp_dual_mode_detect()) we don't read the - * register on those, instead we simply assume that the buffers - * are always enabled. - * - * Returns: - * 0 on success, negative error code on failure - */ -int drm_dp_dual_mode_get_tmds_output(const struct drm_device *dev, - enum drm_dp_dual_mode_type type, struct i2c_adapter *adapter, - bool *enabled) -{ - uint8_t tmds_oen; - ssize_t ret; - - if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) { - *enabled = true; - return 0; - } - - ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_TMDS_OEN, - &tmds_oen, sizeof(tmds_oen)); - if (ret) { - drm_dbg_kms(dev, "Failed to query state of TMDS output buffers\n"); - return ret; - } - - *enabled = !(tmds_oen & DP_DUAL_MODE_TMDS_DISABLE); - - return 0; -} -EXPORT_SYMBOL(drm_dp_dual_mode_get_tmds_output); - -/** - * drm_dp_dual_mode_set_tmds_output - Enable/disable TMDS output buffers in the DP dual mode adaptor - * @dev: &drm_device to use - * @type: DP dual mode adaptor type - * @adapter: I2C adapter for the DDC bus - * @enable: enable (as opposed to disable) the TMDS output buffers - * - * Set the state of the TMDS output buffers in the adaptor. For - * type2 this is set via the DP_DUAL_MODE_TMDS_OEN register. As - * some type 1 adaptors have problems with registers (see comments - * in drm_dp_dual_mode_detect()) we avoid touching the register, - * making this function a no-op on type 1 adaptors. - * - * Returns: - * 0 on success, negative error code on failure - */ -int drm_dp_dual_mode_set_tmds_output(const struct drm_device *dev, enum drm_dp_dual_mode_type type, - struct i2c_adapter *adapter, bool enable) -{ - uint8_t tmds_oen = enable ? 0 : DP_DUAL_MODE_TMDS_DISABLE; - ssize_t ret; - int retry; - - if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) - return 0; - - /* - * LSPCON adapters in low-power state may ignore the first write, so - * read back and verify the written value a few times. - */ - for (retry = 0; retry < 3; retry++) { - uint8_t tmp; - - ret = drm_dp_dual_mode_write(adapter, DP_DUAL_MODE_TMDS_OEN, - &tmds_oen, sizeof(tmds_oen)); - if (ret) { - drm_dbg_kms(dev, "Failed to %s TMDS output buffers (%d attempts)\n", - enable ? "enable" : "disable", retry + 1); - return ret; - } - - ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_TMDS_OEN, - &tmp, sizeof(tmp)); - if (ret) { - drm_dbg_kms(dev, - "I2C read failed during TMDS output buffer %s (%d attempts)\n", - enable ? "enabling" : "disabling", retry + 1); - return ret; - } - - if (tmp == tmds_oen) - return 0; - } - - drm_dbg_kms(dev, "I2C write value mismatch during TMDS output buffer %s\n", - enable ? "enabling" : "disabling"); - - return -EIO; -} -EXPORT_SYMBOL(drm_dp_dual_mode_set_tmds_output); - -/** - * drm_dp_get_dual_mode_type_name - Get the name of the DP dual mode adaptor type as a string - * @type: DP dual mode adaptor type - * - * Returns: - * String representation of the DP dual mode adaptor type - */ -const char *drm_dp_get_dual_mode_type_name(enum drm_dp_dual_mode_type type) -{ - switch (type) { - case DRM_DP_DUAL_MODE_NONE: - return "none"; - case DRM_DP_DUAL_MODE_TYPE1_DVI: - return "type 1 DVI"; - case DRM_DP_DUAL_MODE_TYPE1_HDMI: - return "type 1 HDMI"; - case DRM_DP_DUAL_MODE_TYPE2_DVI: - return "type 2 DVI"; - case DRM_DP_DUAL_MODE_TYPE2_HDMI: - return "type 2 HDMI"; - case DRM_DP_DUAL_MODE_LSPCON: - return "lspcon"; - default: - WARN_ON(type != DRM_DP_DUAL_MODE_UNKNOWN); - return "unknown"; - } -} -EXPORT_SYMBOL(drm_dp_get_dual_mode_type_name); - -/** - * drm_lspcon_get_mode: Get LSPCON's current mode of operation by - * reading offset (0x80, 0x41) - * @dev: &drm_device to use - * @adapter: I2C-over-aux adapter - * @mode: current lspcon mode of operation output variable - * - * Returns: - * 0 on success, sets the current_mode value to appropriate mode - * -error on failure - */ -int drm_lspcon_get_mode(const struct drm_device *dev, struct i2c_adapter *adapter, - enum drm_lspcon_mode *mode) -{ - u8 data; - int ret = 0; - int retry; - - if (!mode) { - drm_err(dev, "NULL input\n"); - return -EINVAL; - } - - /* Read Status: i2c over aux */ - for (retry = 0; retry < 6; retry++) { - if (retry) - usleep_range(500, 1000); - - ret = drm_dp_dual_mode_read(adapter, - DP_DUAL_MODE_LSPCON_CURRENT_MODE, - &data, sizeof(data)); - if (!ret) - break; - } - - if (ret < 0) { - drm_dbg_kms(dev, "LSPCON read(0x80, 0x41) failed\n"); - return -EFAULT; - } - - if (data & DP_DUAL_MODE_LSPCON_MODE_PCON) - *mode = DRM_LSPCON_MODE_PCON; - else - *mode = DRM_LSPCON_MODE_LS; - return 0; -} -EXPORT_SYMBOL(drm_lspcon_get_mode); - -/** - * drm_lspcon_set_mode: Change LSPCON's mode of operation by - * writing offset (0x80, 0x40) - * @dev: &drm_device to use - * @adapter: I2C-over-aux adapter - * @mode: required mode of operation - * - * Returns: - * 0 on success, -error on failure/timeout - */ -int drm_lspcon_set_mode(const struct drm_device *dev, struct i2c_adapter *adapter, - enum drm_lspcon_mode mode) -{ - u8 data = 0; - int ret; - int time_out = 200; - enum drm_lspcon_mode current_mode; - - if (mode == DRM_LSPCON_MODE_PCON) - data = DP_DUAL_MODE_LSPCON_MODE_PCON; - - /* Change mode */ - ret = drm_dp_dual_mode_write(adapter, DP_DUAL_MODE_LSPCON_MODE_CHANGE, - &data, sizeof(data)); - if (ret < 0) { - drm_err(dev, "LSPCON mode change failed\n"); - return ret; - } - - /* - * Confirm mode change by reading the status bit. - * Sometimes, it takes a while to change the mode, - * so wait and retry until time out or done. - */ - do { - ret = drm_lspcon_get_mode(dev, adapter, ¤t_mode); - if (ret) { - drm_err(dev, "can't confirm LSPCON mode change\n"); - return ret; - } else { - if (current_mode != mode) { - msleep(10); - time_out -= 10; - } else { - drm_dbg_kms(dev, "LSPCON mode changed to %s\n", - mode == DRM_LSPCON_MODE_LS ? "LS" : "PCON"); - return 0; - } - } - } while (time_out); - - drm_err(dev, "LSPCON mode change timed out\n"); - return -ETIMEDOUT; -} -EXPORT_SYMBOL(drm_lspcon_set_mode); diff --git a/drivers/gpu/drm/dp/drm_dp_helper_internal.h b/drivers/gpu/drm/dp/drm_dp_helper_internal.h deleted file mode 100644 index 8917fc3af9ec..000000000000 --- a/drivers/gpu/drm/dp/drm_dp_helper_internal.h +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-License-Identifier: MIT */ - -#ifndef DRM_DP_HELPER_INTERNAL_H -#define DRM_DP_HELPER_INTERNAL_H - -struct drm_dp_aux; - -#ifdef CONFIG_DRM_DP_AUX_CHARDEV -int drm_dp_aux_dev_init(void); -void drm_dp_aux_dev_exit(void); -int drm_dp_aux_register_devnode(struct drm_dp_aux *aux); -void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux); -#else -static inline int drm_dp_aux_dev_init(void) -{ - return 0; -} - -static inline void drm_dp_aux_dev_exit(void) -{ -} - -static inline int drm_dp_aux_register_devnode(struct drm_dp_aux *aux) -{ - return 0; -} - -static inline void drm_dp_aux_unregister_devnode(struct drm_dp_aux *aux) -{ -} -#endif - -#endif diff --git a/drivers/gpu/drm/dp/drm_dp_helper_mod.c b/drivers/gpu/drm/dp/drm_dp_helper_mod.c deleted file mode 100644 index db753de24000..000000000000 --- a/drivers/gpu/drm/dp/drm_dp_helper_mod.c +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT - -#include - -#include "drm_dp_helper_internal.h" - -MODULE_DESCRIPTION("DRM DisplayPort helper"); -MODULE_LICENSE("GPL and additional rights"); - -static int __init drm_dp_helper_module_init(void) -{ - return drm_dp_aux_dev_init(); -} - -static void __exit drm_dp_helper_module_exit(void) -{ - /* Call exit functions from specific dp helpers here */ - drm_dp_aux_dev_exit(); -} - -module_init(drm_dp_helper_module_init); -module_exit(drm_dp_helper_module_exit); diff --git a/drivers/gpu/drm/dp/drm_dp_mst_topology.c b/drivers/gpu/drm/dp/drm_dp_mst_topology.c deleted file mode 100644 index 11300b53d24f..000000000000 --- a/drivers/gpu/drm/dp/drm_dp_mst_topology.c +++ /dev/null @@ -1,5978 +0,0 @@ -/* - * Copyright © 2014 Red Hat - * - * Permission to use, copy, modify, distribute, and sell this software and its - * documentation for any purpose is hereby granted without fee, provided that - * the above copyright notice appear in all copies and that both that copyright - * notice and this permission notice appear in supporting documentation, and - * that the name of the copyright holders not be used in advertising or - * publicity pertaining to distribution of the software without specific, - * written prior permission. The copyright holders make no representations - * about the suitability of this software for any purpose. It is provided "as - * is" without express or implied warranty. - * - * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, - * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO - * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR - * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, - * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER - * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE - * OF THIS SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) -#include -#include -#include -#include -#endif - -#include -#include -#include -#include -#include -#include - -#include "drm_dp_helper_internal.h" -#include "drm_dp_mst_topology_internal.h" - -/** - * DOC: dp mst helper - * - * These functions contain parts of the DisplayPort 1.2a MultiStream Transport - * protocol. The helpers contain a topology manager and bandwidth manager. - * The helpers encapsulate the sending and received of sideband msgs. - */ -struct drm_dp_pending_up_req { - struct drm_dp_sideband_msg_hdr hdr; - struct drm_dp_sideband_msg_req_body msg; - struct list_head next; -}; - -static bool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr, - char *buf); - -static void drm_dp_mst_topology_put_port(struct drm_dp_mst_port *port); - -static int drm_dp_dpcd_write_payload(struct drm_dp_mst_topology_mgr *mgr, - int id, - struct drm_dp_payload *payload); - -static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - int offset, int size, u8 *bytes); -static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - int offset, int size, u8 *bytes); - -static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb); - -static void -drm_dp_send_clear_payload_id_table(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb); - -static int drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_port *port); -static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr, - u8 *guid); - -static int drm_dp_mst_register_i2c_bus(struct drm_dp_mst_port *port); -static void drm_dp_mst_unregister_i2c_bus(struct drm_dp_mst_port *port); -static void drm_dp_mst_kick_tx(struct drm_dp_mst_topology_mgr *mgr); - -static bool drm_dp_mst_port_downstream_of_branch(struct drm_dp_mst_port *port, - struct drm_dp_mst_branch *branch); - -#define DBG_PREFIX "[dp_mst]" - -#define DP_STR(x) [DP_ ## x] = #x - -static const char *drm_dp_mst_req_type_str(u8 req_type) -{ - static const char * const req_type_str[] = { - DP_STR(GET_MSG_TRANSACTION_VERSION), - DP_STR(LINK_ADDRESS), - DP_STR(CONNECTION_STATUS_NOTIFY), - DP_STR(ENUM_PATH_RESOURCES), - DP_STR(ALLOCATE_PAYLOAD), - DP_STR(QUERY_PAYLOAD), - DP_STR(RESOURCE_STATUS_NOTIFY), - DP_STR(CLEAR_PAYLOAD_ID_TABLE), - DP_STR(REMOTE_DPCD_READ), - DP_STR(REMOTE_DPCD_WRITE), - DP_STR(REMOTE_I2C_READ), - DP_STR(REMOTE_I2C_WRITE), - DP_STR(POWER_UP_PHY), - DP_STR(POWER_DOWN_PHY), - DP_STR(SINK_EVENT_NOTIFY), - DP_STR(QUERY_STREAM_ENC_STATUS), - }; - - if (req_type >= ARRAY_SIZE(req_type_str) || - !req_type_str[req_type]) - return "unknown"; - - return req_type_str[req_type]; -} - -#undef DP_STR -#define DP_STR(x) [DP_NAK_ ## x] = #x - -static const char *drm_dp_mst_nak_reason_str(u8 nak_reason) -{ - static const char * const nak_reason_str[] = { - DP_STR(WRITE_FAILURE), - DP_STR(INVALID_READ), - DP_STR(CRC_FAILURE), - DP_STR(BAD_PARAM), - DP_STR(DEFER), - DP_STR(LINK_FAILURE), - DP_STR(NO_RESOURCES), - DP_STR(DPCD_FAIL), - DP_STR(I2C_NAK), - DP_STR(ALLOCATE_FAIL), - }; - - if (nak_reason >= ARRAY_SIZE(nak_reason_str) || - !nak_reason_str[nak_reason]) - return "unknown"; - - return nak_reason_str[nak_reason]; -} - -#undef DP_STR -#define DP_STR(x) [DRM_DP_SIDEBAND_TX_ ## x] = #x - -static const char *drm_dp_mst_sideband_tx_state_str(int state) -{ - static const char * const sideband_reason_str[] = { - DP_STR(QUEUED), - DP_STR(START_SEND), - DP_STR(SENT), - DP_STR(RX), - DP_STR(TIMEOUT), - }; - - if (state >= ARRAY_SIZE(sideband_reason_str) || - !sideband_reason_str[state]) - return "unknown"; - - return sideband_reason_str[state]; -} - -static int -drm_dp_mst_rad_to_str(const u8 rad[8], u8 lct, char *out, size_t len) -{ - int i; - u8 unpacked_rad[16]; - - for (i = 0; i < lct; i++) { - if (i % 2) - unpacked_rad[i] = rad[i / 2] >> 4; - else - unpacked_rad[i] = rad[i / 2] & BIT_MASK(4); - } - - /* TODO: Eventually add something to printk so we can format the rad - * like this: 1.2.3 - */ - return snprintf(out, len, "%*phC", lct, unpacked_rad); -} - -/* sideband msg handling */ -static u8 drm_dp_msg_header_crc4(const uint8_t *data, size_t num_nibbles) -{ - u8 bitmask = 0x80; - u8 bitshift = 7; - u8 array_index = 0; - int number_of_bits = num_nibbles * 4; - u8 remainder = 0; - - while (number_of_bits != 0) { - number_of_bits--; - remainder <<= 1; - remainder |= (data[array_index] & bitmask) >> bitshift; - bitmask >>= 1; - bitshift--; - if (bitmask == 0) { - bitmask = 0x80; - bitshift = 7; - array_index++; - } - if ((remainder & 0x10) == 0x10) - remainder ^= 0x13; - } - - number_of_bits = 4; - while (number_of_bits != 0) { - number_of_bits--; - remainder <<= 1; - if ((remainder & 0x10) != 0) - remainder ^= 0x13; - } - - return remainder; -} - -static u8 drm_dp_msg_data_crc4(const uint8_t *data, u8 number_of_bytes) -{ - u8 bitmask = 0x80; - u8 bitshift = 7; - u8 array_index = 0; - int number_of_bits = number_of_bytes * 8; - u16 remainder = 0; - - while (number_of_bits != 0) { - number_of_bits--; - remainder <<= 1; - remainder |= (data[array_index] & bitmask) >> bitshift; - bitmask >>= 1; - bitshift--; - if (bitmask == 0) { - bitmask = 0x80; - bitshift = 7; - array_index++; - } - if ((remainder & 0x100) == 0x100) - remainder ^= 0xd5; - } - - number_of_bits = 8; - while (number_of_bits != 0) { - number_of_bits--; - remainder <<= 1; - if ((remainder & 0x100) != 0) - remainder ^= 0xd5; - } - - return remainder & 0xff; -} -static inline u8 drm_dp_calc_sb_hdr_size(struct drm_dp_sideband_msg_hdr *hdr) -{ - u8 size = 3; - - size += (hdr->lct / 2); - return size; -} - -static void drm_dp_encode_sideband_msg_hdr(struct drm_dp_sideband_msg_hdr *hdr, - u8 *buf, int *len) -{ - int idx = 0; - int i; - u8 crc4; - - buf[idx++] = ((hdr->lct & 0xf) << 4) | (hdr->lcr & 0xf); - for (i = 0; i < (hdr->lct / 2); i++) - buf[idx++] = hdr->rad[i]; - buf[idx++] = (hdr->broadcast << 7) | (hdr->path_msg << 6) | - (hdr->msg_len & 0x3f); - buf[idx++] = (hdr->somt << 7) | (hdr->eomt << 6) | (hdr->seqno << 4); - - crc4 = drm_dp_msg_header_crc4(buf, (idx * 2) - 1); - buf[idx - 1] |= (crc4 & 0xf); - - *len = idx; -} - -static bool drm_dp_decode_sideband_msg_hdr(const struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_hdr *hdr, - u8 *buf, int buflen, u8 *hdrlen) -{ - u8 crc4; - u8 len; - int i; - u8 idx; - - if (buf[0] == 0) - return false; - len = 3; - len += ((buf[0] & 0xf0) >> 4) / 2; - if (len > buflen) - return false; - crc4 = drm_dp_msg_header_crc4(buf, (len * 2) - 1); - - if ((crc4 & 0xf) != (buf[len - 1] & 0xf)) { - drm_dbg_kms(mgr->dev, "crc4 mismatch 0x%x 0x%x\n", crc4, buf[len - 1]); - return false; - } - - hdr->lct = (buf[0] & 0xf0) >> 4; - hdr->lcr = (buf[0] & 0xf); - idx = 1; - for (i = 0; i < (hdr->lct / 2); i++) - hdr->rad[i] = buf[idx++]; - hdr->broadcast = (buf[idx] >> 7) & 0x1; - hdr->path_msg = (buf[idx] >> 6) & 0x1; - hdr->msg_len = buf[idx] & 0x3f; - idx++; - hdr->somt = (buf[idx] >> 7) & 0x1; - hdr->eomt = (buf[idx] >> 6) & 0x1; - hdr->seqno = (buf[idx] >> 4) & 0x1; - idx++; - *hdrlen = idx; - return true; -} - -void -drm_dp_encode_sideband_req(const struct drm_dp_sideband_msg_req_body *req, - struct drm_dp_sideband_msg_tx *raw) -{ - int idx = 0; - int i; - u8 *buf = raw->msg; - - buf[idx++] = req->req_type & 0x7f; - - switch (req->req_type) { - case DP_ENUM_PATH_RESOURCES: - case DP_POWER_DOWN_PHY: - case DP_POWER_UP_PHY: - buf[idx] = (req->u.port_num.port_number & 0xf) << 4; - idx++; - break; - case DP_ALLOCATE_PAYLOAD: - buf[idx] = (req->u.allocate_payload.port_number & 0xf) << 4 | - (req->u.allocate_payload.number_sdp_streams & 0xf); - idx++; - buf[idx] = (req->u.allocate_payload.vcpi & 0x7f); - idx++; - buf[idx] = (req->u.allocate_payload.pbn >> 8); - idx++; - buf[idx] = (req->u.allocate_payload.pbn & 0xff); - idx++; - for (i = 0; i < req->u.allocate_payload.number_sdp_streams / 2; i++) { - buf[idx] = ((req->u.allocate_payload.sdp_stream_sink[i * 2] & 0xf) << 4) | - (req->u.allocate_payload.sdp_stream_sink[i * 2 + 1] & 0xf); - idx++; - } - if (req->u.allocate_payload.number_sdp_streams & 1) { - i = req->u.allocate_payload.number_sdp_streams - 1; - buf[idx] = (req->u.allocate_payload.sdp_stream_sink[i] & 0xf) << 4; - idx++; - } - break; - case DP_QUERY_PAYLOAD: - buf[idx] = (req->u.query_payload.port_number & 0xf) << 4; - idx++; - buf[idx] = (req->u.query_payload.vcpi & 0x7f); - idx++; - break; - case DP_REMOTE_DPCD_READ: - buf[idx] = (req->u.dpcd_read.port_number & 0xf) << 4; - buf[idx] |= ((req->u.dpcd_read.dpcd_address & 0xf0000) >> 16) & 0xf; - idx++; - buf[idx] = (req->u.dpcd_read.dpcd_address & 0xff00) >> 8; - idx++; - buf[idx] = (req->u.dpcd_read.dpcd_address & 0xff); - idx++; - buf[idx] = (req->u.dpcd_read.num_bytes); - idx++; - break; - - case DP_REMOTE_DPCD_WRITE: - buf[idx] = (req->u.dpcd_write.port_number & 0xf) << 4; - buf[idx] |= ((req->u.dpcd_write.dpcd_address & 0xf0000) >> 16) & 0xf; - idx++; - buf[idx] = (req->u.dpcd_write.dpcd_address & 0xff00) >> 8; - idx++; - buf[idx] = (req->u.dpcd_write.dpcd_address & 0xff); - idx++; - buf[idx] = (req->u.dpcd_write.num_bytes); - idx++; - memcpy(&buf[idx], req->u.dpcd_write.bytes, req->u.dpcd_write.num_bytes); - idx += req->u.dpcd_write.num_bytes; - break; - case DP_REMOTE_I2C_READ: - buf[idx] = (req->u.i2c_read.port_number & 0xf) << 4; - buf[idx] |= (req->u.i2c_read.num_transactions & 0x3); - idx++; - for (i = 0; i < (req->u.i2c_read.num_transactions & 0x3); i++) { - buf[idx] = req->u.i2c_read.transactions[i].i2c_dev_id & 0x7f; - idx++; - buf[idx] = req->u.i2c_read.transactions[i].num_bytes; - idx++; - memcpy(&buf[idx], req->u.i2c_read.transactions[i].bytes, req->u.i2c_read.transactions[i].num_bytes); - idx += req->u.i2c_read.transactions[i].num_bytes; - - buf[idx] = (req->u.i2c_read.transactions[i].no_stop_bit & 0x1) << 4; - buf[idx] |= (req->u.i2c_read.transactions[i].i2c_transaction_delay & 0xf); - idx++; - } - buf[idx] = (req->u.i2c_read.read_i2c_device_id) & 0x7f; - idx++; - buf[idx] = (req->u.i2c_read.num_bytes_read); - idx++; - break; - - case DP_REMOTE_I2C_WRITE: - buf[idx] = (req->u.i2c_write.port_number & 0xf) << 4; - idx++; - buf[idx] = (req->u.i2c_write.write_i2c_device_id) & 0x7f; - idx++; - buf[idx] = (req->u.i2c_write.num_bytes); - idx++; - memcpy(&buf[idx], req->u.i2c_write.bytes, req->u.i2c_write.num_bytes); - idx += req->u.i2c_write.num_bytes; - break; - case DP_QUERY_STREAM_ENC_STATUS: { - const struct drm_dp_query_stream_enc_status *msg; - - msg = &req->u.enc_status; - buf[idx] = msg->stream_id; - idx++; - memcpy(&buf[idx], msg->client_id, sizeof(msg->client_id)); - idx += sizeof(msg->client_id); - buf[idx] = 0; - buf[idx] |= FIELD_PREP(GENMASK(1, 0), msg->stream_event); - buf[idx] |= msg->valid_stream_event ? BIT(2) : 0; - buf[idx] |= FIELD_PREP(GENMASK(4, 3), msg->stream_behavior); - buf[idx] |= msg->valid_stream_behavior ? BIT(5) : 0; - idx++; - } - break; - } - raw->cur_len = idx; -} -EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_encode_sideband_req); - -/* Decode a sideband request we've encoded, mainly used for debugging */ -int -drm_dp_decode_sideband_req(const struct drm_dp_sideband_msg_tx *raw, - struct drm_dp_sideband_msg_req_body *req) -{ - const u8 *buf = raw->msg; - int i, idx = 0; - - req->req_type = buf[idx++] & 0x7f; - switch (req->req_type) { - case DP_ENUM_PATH_RESOURCES: - case DP_POWER_DOWN_PHY: - case DP_POWER_UP_PHY: - req->u.port_num.port_number = (buf[idx] >> 4) & 0xf; - break; - case DP_ALLOCATE_PAYLOAD: - { - struct drm_dp_allocate_payload *a = - &req->u.allocate_payload; - - a->number_sdp_streams = buf[idx] & 0xf; - a->port_number = (buf[idx] >> 4) & 0xf; - - WARN_ON(buf[++idx] & 0x80); - a->vcpi = buf[idx] & 0x7f; - - a->pbn = buf[++idx] << 8; - a->pbn |= buf[++idx]; - - idx++; - for (i = 0; i < a->number_sdp_streams; i++) { - a->sdp_stream_sink[i] = - (buf[idx + (i / 2)] >> ((i % 2) ? 0 : 4)) & 0xf; - } - } - break; - case DP_QUERY_PAYLOAD: - req->u.query_payload.port_number = (buf[idx] >> 4) & 0xf; - WARN_ON(buf[++idx] & 0x80); - req->u.query_payload.vcpi = buf[idx] & 0x7f; - break; - case DP_REMOTE_DPCD_READ: - { - struct drm_dp_remote_dpcd_read *r = &req->u.dpcd_read; - - r->port_number = (buf[idx] >> 4) & 0xf; - - r->dpcd_address = (buf[idx] << 16) & 0xf0000; - r->dpcd_address |= (buf[++idx] << 8) & 0xff00; - r->dpcd_address |= buf[++idx] & 0xff; - - r->num_bytes = buf[++idx]; - } - break; - case DP_REMOTE_DPCD_WRITE: - { - struct drm_dp_remote_dpcd_write *w = - &req->u.dpcd_write; - - w->port_number = (buf[idx] >> 4) & 0xf; - - w->dpcd_address = (buf[idx] << 16) & 0xf0000; - w->dpcd_address |= (buf[++idx] << 8) & 0xff00; - w->dpcd_address |= buf[++idx] & 0xff; - - w->num_bytes = buf[++idx]; - - w->bytes = kmemdup(&buf[++idx], w->num_bytes, - GFP_KERNEL); - if (!w->bytes) - return -ENOMEM; - } - break; - case DP_REMOTE_I2C_READ: - { - struct drm_dp_remote_i2c_read *r = &req->u.i2c_read; - struct drm_dp_remote_i2c_read_tx *tx; - bool failed = false; - - r->num_transactions = buf[idx] & 0x3; - r->port_number = (buf[idx] >> 4) & 0xf; - for (i = 0; i < r->num_transactions; i++) { - tx = &r->transactions[i]; - - tx->i2c_dev_id = buf[++idx] & 0x7f; - tx->num_bytes = buf[++idx]; - tx->bytes = kmemdup(&buf[++idx], - tx->num_bytes, - GFP_KERNEL); - if (!tx->bytes) { - failed = true; - break; - } - idx += tx->num_bytes; - tx->no_stop_bit = (buf[idx] >> 5) & 0x1; - tx->i2c_transaction_delay = buf[idx] & 0xf; - } - - if (failed) { - for (i = 0; i < r->num_transactions; i++) { - tx = &r->transactions[i]; - kfree(tx->bytes); - } - return -ENOMEM; - } - - r->read_i2c_device_id = buf[++idx] & 0x7f; - r->num_bytes_read = buf[++idx]; - } - break; - case DP_REMOTE_I2C_WRITE: - { - struct drm_dp_remote_i2c_write *w = &req->u.i2c_write; - - w->port_number = (buf[idx] >> 4) & 0xf; - w->write_i2c_device_id = buf[++idx] & 0x7f; - w->num_bytes = buf[++idx]; - w->bytes = kmemdup(&buf[++idx], w->num_bytes, - GFP_KERNEL); - if (!w->bytes) - return -ENOMEM; - } - break; - case DP_QUERY_STREAM_ENC_STATUS: - req->u.enc_status.stream_id = buf[idx++]; - for (i = 0; i < sizeof(req->u.enc_status.client_id); i++) - req->u.enc_status.client_id[i] = buf[idx++]; - - req->u.enc_status.stream_event = FIELD_GET(GENMASK(1, 0), - buf[idx]); - req->u.enc_status.valid_stream_event = FIELD_GET(BIT(2), - buf[idx]); - req->u.enc_status.stream_behavior = FIELD_GET(GENMASK(4, 3), - buf[idx]); - req->u.enc_status.valid_stream_behavior = FIELD_GET(BIT(5), - buf[idx]); - break; - } - - return 0; -} -EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_decode_sideband_req); - -void -drm_dp_dump_sideband_msg_req_body(const struct drm_dp_sideband_msg_req_body *req, - int indent, struct drm_printer *printer) -{ - int i; - -#define P(f, ...) drm_printf_indent(printer, indent, f, ##__VA_ARGS__) - if (req->req_type == DP_LINK_ADDRESS) { - /* No contents to print */ - P("type=%s\n", drm_dp_mst_req_type_str(req->req_type)); - return; - } - - P("type=%s contents:\n", drm_dp_mst_req_type_str(req->req_type)); - indent++; - - switch (req->req_type) { - case DP_ENUM_PATH_RESOURCES: - case DP_POWER_DOWN_PHY: - case DP_POWER_UP_PHY: - P("port=%d\n", req->u.port_num.port_number); - break; - case DP_ALLOCATE_PAYLOAD: - P("port=%d vcpi=%d pbn=%d sdp_streams=%d %*ph\n", - req->u.allocate_payload.port_number, - req->u.allocate_payload.vcpi, req->u.allocate_payload.pbn, - req->u.allocate_payload.number_sdp_streams, - req->u.allocate_payload.number_sdp_streams, - req->u.allocate_payload.sdp_stream_sink); - break; - case DP_QUERY_PAYLOAD: - P("port=%d vcpi=%d\n", - req->u.query_payload.port_number, - req->u.query_payload.vcpi); - break; - case DP_REMOTE_DPCD_READ: - P("port=%d dpcd_addr=%05x len=%d\n", - req->u.dpcd_read.port_number, req->u.dpcd_read.dpcd_address, - req->u.dpcd_read.num_bytes); - break; - case DP_REMOTE_DPCD_WRITE: - P("port=%d addr=%05x len=%d: %*ph\n", - req->u.dpcd_write.port_number, - req->u.dpcd_write.dpcd_address, - req->u.dpcd_write.num_bytes, req->u.dpcd_write.num_bytes, - req->u.dpcd_write.bytes); - break; - case DP_REMOTE_I2C_READ: - P("port=%d num_tx=%d id=%d size=%d:\n", - req->u.i2c_read.port_number, - req->u.i2c_read.num_transactions, - req->u.i2c_read.read_i2c_device_id, - req->u.i2c_read.num_bytes_read); - - indent++; - for (i = 0; i < req->u.i2c_read.num_transactions; i++) { - const struct drm_dp_remote_i2c_read_tx *rtx = - &req->u.i2c_read.transactions[i]; - - P("%d: id=%03d size=%03d no_stop_bit=%d tx_delay=%03d: %*ph\n", - i, rtx->i2c_dev_id, rtx->num_bytes, - rtx->no_stop_bit, rtx->i2c_transaction_delay, - rtx->num_bytes, rtx->bytes); - } - break; - case DP_REMOTE_I2C_WRITE: - P("port=%d id=%d size=%d: %*ph\n", - req->u.i2c_write.port_number, - req->u.i2c_write.write_i2c_device_id, - req->u.i2c_write.num_bytes, req->u.i2c_write.num_bytes, - req->u.i2c_write.bytes); - break; - case DP_QUERY_STREAM_ENC_STATUS: - P("stream_id=%u client_id=%*ph stream_event=%x " - "valid_event=%d stream_behavior=%x valid_behavior=%d", - req->u.enc_status.stream_id, - (int)ARRAY_SIZE(req->u.enc_status.client_id), - req->u.enc_status.client_id, req->u.enc_status.stream_event, - req->u.enc_status.valid_stream_event, - req->u.enc_status.stream_behavior, - req->u.enc_status.valid_stream_behavior); - break; - default: - P("???\n"); - break; - } -#undef P -} -EXPORT_SYMBOL_FOR_TESTS_ONLY(drm_dp_dump_sideband_msg_req_body); - -static inline void -drm_dp_mst_dump_sideband_msg_tx(struct drm_printer *p, - const struct drm_dp_sideband_msg_tx *txmsg) -{ - struct drm_dp_sideband_msg_req_body req; - char buf[64]; - int ret; - int i; - - drm_dp_mst_rad_to_str(txmsg->dst->rad, txmsg->dst->lct, buf, - sizeof(buf)); - drm_printf(p, "txmsg cur_offset=%x cur_len=%x seqno=%x state=%s path_msg=%d dst=%s\n", - txmsg->cur_offset, txmsg->cur_len, txmsg->seqno, - drm_dp_mst_sideband_tx_state_str(txmsg->state), - txmsg->path_msg, buf); - - ret = drm_dp_decode_sideband_req(txmsg, &req); - if (ret) { - drm_printf(p, "\n", ret); - return; - } - drm_dp_dump_sideband_msg_req_body(&req, 1, p); - - switch (req.req_type) { - case DP_REMOTE_DPCD_WRITE: - kfree(req.u.dpcd_write.bytes); - break; - case DP_REMOTE_I2C_READ: - for (i = 0; i < req.u.i2c_read.num_transactions; i++) - kfree(req.u.i2c_read.transactions[i].bytes); - break; - case DP_REMOTE_I2C_WRITE: - kfree(req.u.i2c_write.bytes); - break; - } -} - -static void drm_dp_crc_sideband_chunk_req(u8 *msg, u8 len) -{ - u8 crc4; - - crc4 = drm_dp_msg_data_crc4(msg, len); - msg[len] = crc4; -} - -static void drm_dp_encode_sideband_reply(struct drm_dp_sideband_msg_reply_body *rep, - struct drm_dp_sideband_msg_tx *raw) -{ - int idx = 0; - u8 *buf = raw->msg; - - buf[idx++] = (rep->reply_type & 0x1) << 7 | (rep->req_type & 0x7f); - - raw->cur_len = idx; -} - -static int drm_dp_sideband_msg_set_header(struct drm_dp_sideband_msg_rx *msg, - struct drm_dp_sideband_msg_hdr *hdr, - u8 hdrlen) -{ - /* - * ignore out-of-order messages or messages that are part of a - * failed transaction - */ - if (!hdr->somt && !msg->have_somt) - return false; - - /* get length contained in this portion */ - msg->curchunk_idx = 0; - msg->curchunk_len = hdr->msg_len; - msg->curchunk_hdrlen = hdrlen; - - /* we have already gotten an somt - don't bother parsing */ - if (hdr->somt && msg->have_somt) - return false; - - if (hdr->somt) { - memcpy(&msg->initial_hdr, hdr, - sizeof(struct drm_dp_sideband_msg_hdr)); - msg->have_somt = true; - } - if (hdr->eomt) - msg->have_eomt = true; - - return true; -} - -/* this adds a chunk of msg to the builder to get the final msg */ -static bool drm_dp_sideband_append_payload(struct drm_dp_sideband_msg_rx *msg, - u8 *replybuf, u8 replybuflen) -{ - u8 crc4; - - memcpy(&msg->chunk[msg->curchunk_idx], replybuf, replybuflen); - msg->curchunk_idx += replybuflen; - - if (msg->curchunk_idx >= msg->curchunk_len) { - /* do CRC */ - crc4 = drm_dp_msg_data_crc4(msg->chunk, msg->curchunk_len - 1); - if (crc4 != msg->chunk[msg->curchunk_len - 1]) - print_hex_dump(KERN_DEBUG, "wrong crc", - DUMP_PREFIX_NONE, 16, 1, - msg->chunk, msg->curchunk_len, false); - /* copy chunk into bigger msg */ - memcpy(&msg->msg[msg->curlen], msg->chunk, msg->curchunk_len - 1); - msg->curlen += msg->curchunk_len - 1; - } - return true; -} - -static bool drm_dp_sideband_parse_link_address(const struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - int i; - - memcpy(repmsg->u.link_addr.guid, &raw->msg[idx], 16); - idx += 16; - repmsg->u.link_addr.nports = raw->msg[idx] & 0xf; - idx++; - if (idx > raw->curlen) - goto fail_len; - for (i = 0; i < repmsg->u.link_addr.nports; i++) { - if (raw->msg[idx] & 0x80) - repmsg->u.link_addr.ports[i].input_port = 1; - - repmsg->u.link_addr.ports[i].peer_device_type = (raw->msg[idx] >> 4) & 0x7; - repmsg->u.link_addr.ports[i].port_number = (raw->msg[idx] & 0xf); - - idx++; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.link_addr.ports[i].mcs = (raw->msg[idx] >> 7) & 0x1; - repmsg->u.link_addr.ports[i].ddps = (raw->msg[idx] >> 6) & 0x1; - if (repmsg->u.link_addr.ports[i].input_port == 0) - repmsg->u.link_addr.ports[i].legacy_device_plug_status = (raw->msg[idx] >> 5) & 0x1; - idx++; - if (idx > raw->curlen) - goto fail_len; - if (repmsg->u.link_addr.ports[i].input_port == 0) { - repmsg->u.link_addr.ports[i].dpcd_revision = (raw->msg[idx]); - idx++; - if (idx > raw->curlen) - goto fail_len; - memcpy(repmsg->u.link_addr.ports[i].peer_guid, &raw->msg[idx], 16); - idx += 16; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.link_addr.ports[i].num_sdp_streams = (raw->msg[idx] >> 4) & 0xf; - repmsg->u.link_addr.ports[i].num_sdp_stream_sinks = (raw->msg[idx] & 0xf); - idx++; - - } - if (idx > raw->curlen) - goto fail_len; - } - - return true; -fail_len: - DRM_DEBUG_KMS("link address reply parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_remote_dpcd_read(struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - - repmsg->u.remote_dpcd_read_ack.port_number = raw->msg[idx] & 0xf; - idx++; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.remote_dpcd_read_ack.num_bytes = raw->msg[idx]; - idx++; - if (idx > raw->curlen) - goto fail_len; - - memcpy(repmsg->u.remote_dpcd_read_ack.bytes, &raw->msg[idx], repmsg->u.remote_dpcd_read_ack.num_bytes); - return true; -fail_len: - DRM_DEBUG_KMS("link address reply parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_remote_dpcd_write(struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - - repmsg->u.remote_dpcd_write_ack.port_number = raw->msg[idx] & 0xf; - idx++; - if (idx > raw->curlen) - goto fail_len; - return true; -fail_len: - DRM_DEBUG_KMS("parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_remote_i2c_read_ack(struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - - repmsg->u.remote_i2c_read_ack.port_number = (raw->msg[idx] & 0xf); - idx++; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.remote_i2c_read_ack.num_bytes = raw->msg[idx]; - idx++; - /* TODO check */ - memcpy(repmsg->u.remote_i2c_read_ack.bytes, &raw->msg[idx], repmsg->u.remote_i2c_read_ack.num_bytes); - return true; -fail_len: - DRM_DEBUG_KMS("remote i2c reply parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_enum_path_resources_ack(struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - - repmsg->u.path_resources.port_number = (raw->msg[idx] >> 4) & 0xf; - repmsg->u.path_resources.fec_capable = raw->msg[idx] & 0x1; - idx++; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.path_resources.full_payload_bw_number = (raw->msg[idx] << 8) | (raw->msg[idx+1]); - idx += 2; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.path_resources.avail_payload_bw_number = (raw->msg[idx] << 8) | (raw->msg[idx+1]); - idx += 2; - if (idx > raw->curlen) - goto fail_len; - return true; -fail_len: - DRM_DEBUG_KMS("enum resource parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_allocate_payload_ack(struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - - repmsg->u.allocate_payload.port_number = (raw->msg[idx] >> 4) & 0xf; - idx++; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.allocate_payload.vcpi = raw->msg[idx]; - idx++; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.allocate_payload.allocated_pbn = (raw->msg[idx] << 8) | (raw->msg[idx+1]); - idx += 2; - if (idx > raw->curlen) - goto fail_len; - return true; -fail_len: - DRM_DEBUG_KMS("allocate payload parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_query_payload_ack(struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - - repmsg->u.query_payload.port_number = (raw->msg[idx] >> 4) & 0xf; - idx++; - if (idx > raw->curlen) - goto fail_len; - repmsg->u.query_payload.allocated_pbn = (raw->msg[idx] << 8) | (raw->msg[idx + 1]); - idx += 2; - if (idx > raw->curlen) - goto fail_len; - return true; -fail_len: - DRM_DEBUG_KMS("query payload parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_power_updown_phy_ack(struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - int idx = 1; - - repmsg->u.port_number.port_number = (raw->msg[idx] >> 4) & 0xf; - idx++; - if (idx > raw->curlen) { - DRM_DEBUG_KMS("power up/down phy parse length fail %d %d\n", - idx, raw->curlen); - return false; - } - return true; -} - -static bool -drm_dp_sideband_parse_query_stream_enc_status( - struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *repmsg) -{ - struct drm_dp_query_stream_enc_status_ack_reply *reply; - - reply = &repmsg->u.enc_status; - - reply->stream_id = raw->msg[3]; - - reply->reply_signed = raw->msg[2] & BIT(0); - - /* - * NOTE: It's my impression from reading the spec that the below parsing - * is correct. However I noticed while testing with an HDCP 1.4 display - * through an HDCP 2.2 hub that only bit 3 was set. In that case, I - * would expect both bits to be set. So keep the parsing following the - * spec, but beware reality might not match the spec (at least for some - * configurations). - */ - reply->hdcp_1x_device_present = raw->msg[2] & BIT(4); - reply->hdcp_2x_device_present = raw->msg[2] & BIT(3); - - reply->query_capable_device_present = raw->msg[2] & BIT(5); - reply->legacy_device_present = raw->msg[2] & BIT(6); - reply->unauthorizable_device_present = raw->msg[2] & BIT(7); - - reply->auth_completed = !!(raw->msg[1] & BIT(3)); - reply->encryption_enabled = !!(raw->msg[1] & BIT(4)); - reply->repeater_present = !!(raw->msg[1] & BIT(5)); - reply->state = (raw->msg[1] & GENMASK(7, 6)) >> 6; - - return true; -} - -static bool drm_dp_sideband_parse_reply(const struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_reply_body *msg) -{ - memset(msg, 0, sizeof(*msg)); - msg->reply_type = (raw->msg[0] & 0x80) >> 7; - msg->req_type = (raw->msg[0] & 0x7f); - - if (msg->reply_type == DP_SIDEBAND_REPLY_NAK) { - memcpy(msg->u.nak.guid, &raw->msg[1], 16); - msg->u.nak.reason = raw->msg[17]; - msg->u.nak.nak_data = raw->msg[18]; - return false; - } - - switch (msg->req_type) { - case DP_LINK_ADDRESS: - return drm_dp_sideband_parse_link_address(mgr, raw, msg); - case DP_QUERY_PAYLOAD: - return drm_dp_sideband_parse_query_payload_ack(raw, msg); - case DP_REMOTE_DPCD_READ: - return drm_dp_sideband_parse_remote_dpcd_read(raw, msg); - case DP_REMOTE_DPCD_WRITE: - return drm_dp_sideband_parse_remote_dpcd_write(raw, msg); - case DP_REMOTE_I2C_READ: - return drm_dp_sideband_parse_remote_i2c_read_ack(raw, msg); - case DP_REMOTE_I2C_WRITE: - return true; /* since there's nothing to parse */ - case DP_ENUM_PATH_RESOURCES: - return drm_dp_sideband_parse_enum_path_resources_ack(raw, msg); - case DP_ALLOCATE_PAYLOAD: - return drm_dp_sideband_parse_allocate_payload_ack(raw, msg); - case DP_POWER_DOWN_PHY: - case DP_POWER_UP_PHY: - return drm_dp_sideband_parse_power_updown_phy_ack(raw, msg); - case DP_CLEAR_PAYLOAD_ID_TABLE: - return true; /* since there's nothing to parse */ - case DP_QUERY_STREAM_ENC_STATUS: - return drm_dp_sideband_parse_query_stream_enc_status(raw, msg); - default: - drm_err(mgr->dev, "Got unknown reply 0x%02x (%s)\n", - msg->req_type, drm_dp_mst_req_type_str(msg->req_type)); - return false; - } -} - -static bool -drm_dp_sideband_parse_connection_status_notify(const struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_req_body *msg) -{ - int idx = 1; - - msg->u.conn_stat.port_number = (raw->msg[idx] & 0xf0) >> 4; - idx++; - if (idx > raw->curlen) - goto fail_len; - - memcpy(msg->u.conn_stat.guid, &raw->msg[idx], 16); - idx += 16; - if (idx > raw->curlen) - goto fail_len; - - msg->u.conn_stat.legacy_device_plug_status = (raw->msg[idx] >> 6) & 0x1; - msg->u.conn_stat.displayport_device_plug_status = (raw->msg[idx] >> 5) & 0x1; - msg->u.conn_stat.message_capability_status = (raw->msg[idx] >> 4) & 0x1; - msg->u.conn_stat.input_port = (raw->msg[idx] >> 3) & 0x1; - msg->u.conn_stat.peer_device_type = (raw->msg[idx] & 0x7); - idx++; - return true; -fail_len: - drm_dbg_kms(mgr->dev, "connection status reply parse length fail %d %d\n", - idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_resource_status_notify(const struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_req_body *msg) -{ - int idx = 1; - - msg->u.resource_stat.port_number = (raw->msg[idx] & 0xf0) >> 4; - idx++; - if (idx > raw->curlen) - goto fail_len; - - memcpy(msg->u.resource_stat.guid, &raw->msg[idx], 16); - idx += 16; - if (idx > raw->curlen) - goto fail_len; - - msg->u.resource_stat.available_pbn = (raw->msg[idx] << 8) | (raw->msg[idx + 1]); - idx++; - return true; -fail_len: - drm_dbg_kms(mgr->dev, "resource status reply parse length fail %d %d\n", idx, raw->curlen); - return false; -} - -static bool drm_dp_sideband_parse_req(const struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_rx *raw, - struct drm_dp_sideband_msg_req_body *msg) -{ - memset(msg, 0, sizeof(*msg)); - msg->req_type = (raw->msg[0] & 0x7f); - - switch (msg->req_type) { - case DP_CONNECTION_STATUS_NOTIFY: - return drm_dp_sideband_parse_connection_status_notify(mgr, raw, msg); - case DP_RESOURCE_STATUS_NOTIFY: - return drm_dp_sideband_parse_resource_status_notify(mgr, raw, msg); - default: - drm_err(mgr->dev, "Got unknown request 0x%02x (%s)\n", - msg->req_type, drm_dp_mst_req_type_str(msg->req_type)); - return false; - } -} - -static void build_dpcd_write(struct drm_dp_sideband_msg_tx *msg, - u8 port_num, u32 offset, u8 num_bytes, u8 *bytes) -{ - struct drm_dp_sideband_msg_req_body req; - - req.req_type = DP_REMOTE_DPCD_WRITE; - req.u.dpcd_write.port_number = port_num; - req.u.dpcd_write.dpcd_address = offset; - req.u.dpcd_write.num_bytes = num_bytes; - req.u.dpcd_write.bytes = bytes; - drm_dp_encode_sideband_req(&req, msg); -} - -static void build_link_address(struct drm_dp_sideband_msg_tx *msg) -{ - struct drm_dp_sideband_msg_req_body req; - - req.req_type = DP_LINK_ADDRESS; - drm_dp_encode_sideband_req(&req, msg); -} - -static void build_clear_payload_id_table(struct drm_dp_sideband_msg_tx *msg) -{ - struct drm_dp_sideband_msg_req_body req; - - req.req_type = DP_CLEAR_PAYLOAD_ID_TABLE; - drm_dp_encode_sideband_req(&req, msg); - msg->path_msg = true; -} - -static int build_enum_path_resources(struct drm_dp_sideband_msg_tx *msg, - int port_num) -{ - struct drm_dp_sideband_msg_req_body req; - - req.req_type = DP_ENUM_PATH_RESOURCES; - req.u.port_num.port_number = port_num; - drm_dp_encode_sideband_req(&req, msg); - msg->path_msg = true; - return 0; -} - -static void build_allocate_payload(struct drm_dp_sideband_msg_tx *msg, - int port_num, - u8 vcpi, uint16_t pbn, - u8 number_sdp_streams, - u8 *sdp_stream_sink) -{ - struct drm_dp_sideband_msg_req_body req; - - memset(&req, 0, sizeof(req)); - req.req_type = DP_ALLOCATE_PAYLOAD; - req.u.allocate_payload.port_number = port_num; - req.u.allocate_payload.vcpi = vcpi; - req.u.allocate_payload.pbn = pbn; - req.u.allocate_payload.number_sdp_streams = number_sdp_streams; - memcpy(req.u.allocate_payload.sdp_stream_sink, sdp_stream_sink, - number_sdp_streams); - drm_dp_encode_sideband_req(&req, msg); - msg->path_msg = true; -} - -static void build_power_updown_phy(struct drm_dp_sideband_msg_tx *msg, - int port_num, bool power_up) -{ - struct drm_dp_sideband_msg_req_body req; - - if (power_up) - req.req_type = DP_POWER_UP_PHY; - else - req.req_type = DP_POWER_DOWN_PHY; - - req.u.port_num.port_number = port_num; - drm_dp_encode_sideband_req(&req, msg); - msg->path_msg = true; -} - -static int -build_query_stream_enc_status(struct drm_dp_sideband_msg_tx *msg, u8 stream_id, - u8 *q_id) -{ - struct drm_dp_sideband_msg_req_body req; - - req.req_type = DP_QUERY_STREAM_ENC_STATUS; - req.u.enc_status.stream_id = stream_id; - memcpy(req.u.enc_status.client_id, q_id, - sizeof(req.u.enc_status.client_id)); - req.u.enc_status.stream_event = 0; - req.u.enc_status.valid_stream_event = false; - req.u.enc_status.stream_behavior = 0; - req.u.enc_status.valid_stream_behavior = false; - - drm_dp_encode_sideband_req(&req, msg); - return 0; -} - -static int drm_dp_mst_assign_payload_id(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_vcpi *vcpi) -{ - int ret, vcpi_ret; - - mutex_lock(&mgr->payload_lock); - ret = find_first_zero_bit(&mgr->payload_mask, mgr->max_payloads + 1); - if (ret > mgr->max_payloads) { - ret = -EINVAL; - drm_dbg_kms(mgr->dev, "out of payload ids %d\n", ret); - goto out_unlock; - } - - vcpi_ret = find_first_zero_bit(&mgr->vcpi_mask, mgr->max_payloads + 1); - if (vcpi_ret > mgr->max_payloads) { - ret = -EINVAL; - drm_dbg_kms(mgr->dev, "out of vcpi ids %d\n", ret); - goto out_unlock; - } - - set_bit(ret, &mgr->payload_mask); - set_bit(vcpi_ret, &mgr->vcpi_mask); - vcpi->vcpi = vcpi_ret + 1; - mgr->proposed_vcpis[ret - 1] = vcpi; -out_unlock: - mutex_unlock(&mgr->payload_lock); - return ret; -} - -static void drm_dp_mst_put_payload_id(struct drm_dp_mst_topology_mgr *mgr, - int vcpi) -{ - int i; - - if (vcpi == 0) - return; - - mutex_lock(&mgr->payload_lock); - drm_dbg_kms(mgr->dev, "putting payload %d\n", vcpi); - clear_bit(vcpi - 1, &mgr->vcpi_mask); - - for (i = 0; i < mgr->max_payloads; i++) { - if (mgr->proposed_vcpis[i] && - mgr->proposed_vcpis[i]->vcpi == vcpi) { - mgr->proposed_vcpis[i] = NULL; - clear_bit(i + 1, &mgr->payload_mask); - } - } - mutex_unlock(&mgr->payload_lock); -} - -static bool check_txmsg_state(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_tx *txmsg) -{ - unsigned int state; - - /* - * All updates to txmsg->state are protected by mgr->qlock, and the two - * cases we check here are terminal states. For those the barriers - * provided by the wake_up/wait_event pair are enough. - */ - state = READ_ONCE(txmsg->state); - return (state == DRM_DP_SIDEBAND_TX_RX || - state == DRM_DP_SIDEBAND_TX_TIMEOUT); -} - -static int drm_dp_mst_wait_tx_reply(struct drm_dp_mst_branch *mstb, - struct drm_dp_sideband_msg_tx *txmsg) -{ - struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; - unsigned long wait_timeout = msecs_to_jiffies(4000); - unsigned long wait_expires = jiffies + wait_timeout; - int ret; - - for (;;) { - /* - * If the driver provides a way for this, change to - * poll-waiting for the MST reply interrupt if we didn't receive - * it for 50 msec. This would cater for cases where the HPD - * pulse signal got lost somewhere, even though the sink raised - * the corresponding MST interrupt correctly. One example is the - * Club 3D CAC-1557 TypeC -> DP adapter which for some reason - * filters out short pulses with a duration less than ~540 usec. - * - * The poll period is 50 msec to avoid missing an interrupt - * after the sink has cleared it (after a 110msec timeout - * since it raised the interrupt). - */ - ret = wait_event_timeout(mgr->tx_waitq, - check_txmsg_state(mgr, txmsg), - mgr->cbs->poll_hpd_irq ? - msecs_to_jiffies(50) : - wait_timeout); - - if (ret || !mgr->cbs->poll_hpd_irq || - time_after(jiffies, wait_expires)) - break; - - mgr->cbs->poll_hpd_irq(mgr); - } - - mutex_lock(&mgr->qlock); - if (ret > 0) { - if (txmsg->state == DRM_DP_SIDEBAND_TX_TIMEOUT) { - ret = -EIO; - goto out; - } - } else { - drm_dbg_kms(mgr->dev, "timedout msg send %p %d %d\n", - txmsg, txmsg->state, txmsg->seqno); - - /* dump some state */ - ret = -EIO; - - /* remove from q */ - if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED || - txmsg->state == DRM_DP_SIDEBAND_TX_START_SEND || - txmsg->state == DRM_DP_SIDEBAND_TX_SENT) - list_del(&txmsg->next); - } -out: - if (unlikely(ret == -EIO) && drm_debug_enabled(DRM_UT_DP)) { - struct drm_printer p = drm_debug_printer(DBG_PREFIX); - - drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); - } - mutex_unlock(&mgr->qlock); - - drm_dp_mst_kick_tx(mgr); - return ret; -} - -static struct drm_dp_mst_branch *drm_dp_add_mst_branch_device(u8 lct, u8 *rad) -{ - struct drm_dp_mst_branch *mstb; - - mstb = kzalloc(sizeof(*mstb), GFP_KERNEL); - if (!mstb) - return NULL; - - mstb->lct = lct; - if (lct > 1) - memcpy(mstb->rad, rad, lct / 2); - INIT_LIST_HEAD(&mstb->ports); - kref_init(&mstb->topology_kref); - kref_init(&mstb->malloc_kref); - return mstb; -} - -static void drm_dp_free_mst_branch_device(struct kref *kref) -{ - struct drm_dp_mst_branch *mstb = - container_of(kref, struct drm_dp_mst_branch, malloc_kref); - - if (mstb->port_parent) - drm_dp_mst_put_port_malloc(mstb->port_parent); - - kfree(mstb); -} - -/** - * DOC: Branch device and port refcounting - * - * Topology refcount overview - * ~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * The refcounting schemes for &struct drm_dp_mst_branch and &struct - * drm_dp_mst_port are somewhat unusual. Both ports and branch devices have - * two different kinds of refcounts: topology refcounts, and malloc refcounts. - * - * Topology refcounts are not exposed to drivers, and are handled internally - * by the DP MST helpers. The helpers use them in order to prevent the - * in-memory topology state from being changed in the middle of critical - * operations like changing the internal state of payload allocations. This - * means each branch and port will be considered to be connected to the rest - * of the topology until its topology refcount reaches zero. Additionally, - * for ports this means that their associated &struct drm_connector will stay - * registered with userspace until the port's refcount reaches 0. - * - * Malloc refcount overview - * ~~~~~~~~~~~~~~~~~~~~~~~~ - * - * Malloc references are used to keep a &struct drm_dp_mst_port or &struct - * drm_dp_mst_branch allocated even after all of its topology references have - * been dropped, so that the driver or MST helpers can safely access each - * branch's last known state before it was disconnected from the topology. - * When the malloc refcount of a port or branch reaches 0, the memory - * allocation containing the &struct drm_dp_mst_branch or &struct - * drm_dp_mst_port respectively will be freed. - * - * For &struct drm_dp_mst_branch, malloc refcounts are not currently exposed - * to drivers. As of writing this documentation, there are no drivers that - * have a usecase for accessing &struct drm_dp_mst_branch outside of the MST - * helpers. Exposing this API to drivers in a race-free manner would take more - * tweaking of the refcounting scheme, however patches are welcome provided - * there is a legitimate driver usecase for this. - * - * Refcount relationships in a topology - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * Let's take a look at why the relationship between topology and malloc - * refcounts is designed the way it is. - * - * .. kernel-figure:: dp-mst/topology-figure-1.dot - * - * An example of topology and malloc refs in a DP MST topology with two - * active payloads. Topology refcount increments are indicated by solid - * lines, and malloc refcount increments are indicated by dashed lines. - * Each starts from the branch which incremented the refcount, and ends at - * the branch to which the refcount belongs to, i.e. the arrow points the - * same way as the C pointers used to reference a structure. - * - * As you can see in the above figure, every branch increments the topology - * refcount of its children, and increments the malloc refcount of its - * parent. Additionally, every payload increments the malloc refcount of its - * assigned port by 1. - * - * So, what would happen if MSTB #3 from the above figure was unplugged from - * the system, but the driver hadn't yet removed payload #2 from port #3? The - * topology would start to look like the figure below. - * - * .. kernel-figure:: dp-mst/topology-figure-2.dot - * - * Ports and branch devices which have been released from memory are - * colored grey, and references which have been removed are colored red. - * - * Whenever a port or branch device's topology refcount reaches zero, it will - * decrement the topology refcounts of all its children, the malloc refcount - * of its parent, and finally its own malloc refcount. For MSTB #4 and port - * #4, this means they both have been disconnected from the topology and freed - * from memory. But, because payload #2 is still holding a reference to port - * #3, port #3 is removed from the topology but its &struct drm_dp_mst_port - * is still accessible from memory. This also means port #3 has not yet - * decremented the malloc refcount of MSTB #3, so its &struct - * drm_dp_mst_branch will also stay allocated in memory until port #3's - * malloc refcount reaches 0. - * - * This relationship is necessary because in order to release payload #2, we - * need to be able to figure out the last relative of port #3 that's still - * connected to the topology. In this case, we would travel up the topology as - * shown below. - * - * .. kernel-figure:: dp-mst/topology-figure-3.dot - * - * And finally, remove payload #2 by communicating with port #2 through - * sideband transactions. - */ - -/** - * drm_dp_mst_get_mstb_malloc() - Increment the malloc refcount of a branch - * device - * @mstb: The &struct drm_dp_mst_branch to increment the malloc refcount of - * - * Increments &drm_dp_mst_branch.malloc_kref. When - * &drm_dp_mst_branch.malloc_kref reaches 0, the memory allocation for @mstb - * will be released and @mstb may no longer be used. - * - * See also: drm_dp_mst_put_mstb_malloc() - */ -static void -drm_dp_mst_get_mstb_malloc(struct drm_dp_mst_branch *mstb) -{ - kref_get(&mstb->malloc_kref); - drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref)); -} - -/** - * drm_dp_mst_put_mstb_malloc() - Decrement the malloc refcount of a branch - * device - * @mstb: The &struct drm_dp_mst_branch to decrement the malloc refcount of - * - * Decrements &drm_dp_mst_branch.malloc_kref. When - * &drm_dp_mst_branch.malloc_kref reaches 0, the memory allocation for @mstb - * will be released and @mstb may no longer be used. - * - * See also: drm_dp_mst_get_mstb_malloc() - */ -static void -drm_dp_mst_put_mstb_malloc(struct drm_dp_mst_branch *mstb) -{ - drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref) - 1); - kref_put(&mstb->malloc_kref, drm_dp_free_mst_branch_device); -} - -static void drm_dp_free_mst_port(struct kref *kref) -{ - struct drm_dp_mst_port *port = - container_of(kref, struct drm_dp_mst_port, malloc_kref); - - drm_dp_mst_put_mstb_malloc(port->parent); - kfree(port); -} - -/** - * drm_dp_mst_get_port_malloc() - Increment the malloc refcount of an MST port - * @port: The &struct drm_dp_mst_port to increment the malloc refcount of - * - * Increments &drm_dp_mst_port.malloc_kref. When &drm_dp_mst_port.malloc_kref - * reaches 0, the memory allocation for @port will be released and @port may - * no longer be used. - * - * Because @port could potentially be freed at any time by the DP MST helpers - * if &drm_dp_mst_port.malloc_kref reaches 0, including during a call to this - * function, drivers that which to make use of &struct drm_dp_mst_port should - * ensure that they grab at least one main malloc reference to their MST ports - * in &drm_dp_mst_topology_cbs.add_connector. This callback is called before - * there is any chance for &drm_dp_mst_port.malloc_kref to reach 0. - * - * See also: drm_dp_mst_put_port_malloc() - */ -void -drm_dp_mst_get_port_malloc(struct drm_dp_mst_port *port) -{ - kref_get(&port->malloc_kref); - drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->malloc_kref)); -} -EXPORT_SYMBOL(drm_dp_mst_get_port_malloc); - -/** - * drm_dp_mst_put_port_malloc() - Decrement the malloc refcount of an MST port - * @port: The &struct drm_dp_mst_port to decrement the malloc refcount of - * - * Decrements &drm_dp_mst_port.malloc_kref. When &drm_dp_mst_port.malloc_kref - * reaches 0, the memory allocation for @port will be released and @port may - * no longer be used. - * - * See also: drm_dp_mst_get_port_malloc() - */ -void -drm_dp_mst_put_port_malloc(struct drm_dp_mst_port *port) -{ - drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->malloc_kref) - 1); - kref_put(&port->malloc_kref, drm_dp_free_mst_port); -} -EXPORT_SYMBOL(drm_dp_mst_put_port_malloc); - -#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) - -#define STACK_DEPTH 8 - -static noinline void -__topology_ref_save(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_topology_ref_history *history, - enum drm_dp_mst_topology_ref_type type) -{ - struct drm_dp_mst_topology_ref_entry *entry = NULL; - depot_stack_handle_t backtrace; - ulong stack_entries[STACK_DEPTH]; - uint n; - int i; - - n = stack_trace_save(stack_entries, ARRAY_SIZE(stack_entries), 1); - backtrace = stack_depot_save(stack_entries, n, GFP_KERNEL); - if (!backtrace) - return; - - /* Try to find an existing entry for this backtrace */ - for (i = 0; i < history->len; i++) { - if (history->entries[i].backtrace == backtrace) { - entry = &history->entries[i]; - break; - } - } - - /* Otherwise add one */ - if (!entry) { - struct drm_dp_mst_topology_ref_entry *new; - int new_len = history->len + 1; - - new = krealloc(history->entries, sizeof(*new) * new_len, - GFP_KERNEL); - if (!new) - return; - - entry = &new[history->len]; - history->len = new_len; - history->entries = new; - - entry->backtrace = backtrace; - entry->type = type; - entry->count = 0; - } - entry->count++; - entry->ts_nsec = ktime_get_ns(); -} - -static int -topology_ref_history_cmp(const void *a, const void *b) -{ - const struct drm_dp_mst_topology_ref_entry *entry_a = a, *entry_b = b; - - if (entry_a->ts_nsec > entry_b->ts_nsec) - return 1; - else if (entry_a->ts_nsec < entry_b->ts_nsec) - return -1; - else - return 0; -} - -static inline const char * -topology_ref_type_to_str(enum drm_dp_mst_topology_ref_type type) -{ - if (type == DRM_DP_MST_TOPOLOGY_REF_GET) - return "get"; - else - return "put"; -} - -static void -__dump_topology_ref_history(struct drm_dp_mst_topology_ref_history *history, - void *ptr, const char *type_str) -{ - struct drm_printer p = drm_debug_printer(DBG_PREFIX); - char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL); - int i; - - if (!buf) - return; - - if (!history->len) - goto out; - - /* First, sort the list so that it goes from oldest to newest - * reference entry - */ - sort(history->entries, history->len, sizeof(*history->entries), - topology_ref_history_cmp, NULL); - - drm_printf(&p, "%s (%p) topology count reached 0, dumping history:\n", - type_str, ptr); - - for (i = 0; i < history->len; i++) { - const struct drm_dp_mst_topology_ref_entry *entry = - &history->entries[i]; - u64 ts_nsec = entry->ts_nsec; - u32 rem_nsec = do_div(ts_nsec, 1000000000); - - stack_depot_snprint(entry->backtrace, buf, PAGE_SIZE, 4); - - drm_printf(&p, " %d %ss (last at %5llu.%06u):\n%s", - entry->count, - topology_ref_type_to_str(entry->type), - ts_nsec, rem_nsec / 1000, buf); - } - - /* Now free the history, since this is the only time we expose it */ - kfree(history->entries); -out: - kfree(buf); -} - -static __always_inline void -drm_dp_mst_dump_mstb_topology_history(struct drm_dp_mst_branch *mstb) -{ - __dump_topology_ref_history(&mstb->topology_ref_history, mstb, - "MSTB"); -} - -static __always_inline void -drm_dp_mst_dump_port_topology_history(struct drm_dp_mst_port *port) -{ - __dump_topology_ref_history(&port->topology_ref_history, port, - "Port"); -} - -static __always_inline void -save_mstb_topology_ref(struct drm_dp_mst_branch *mstb, - enum drm_dp_mst_topology_ref_type type) -{ - __topology_ref_save(mstb->mgr, &mstb->topology_ref_history, type); -} - -static __always_inline void -save_port_topology_ref(struct drm_dp_mst_port *port, - enum drm_dp_mst_topology_ref_type type) -{ - __topology_ref_save(port->mgr, &port->topology_ref_history, type); -} - -static inline void -topology_ref_history_lock(struct drm_dp_mst_topology_mgr *mgr) -{ - mutex_lock(&mgr->topology_ref_history_lock); -} - -static inline void -topology_ref_history_unlock(struct drm_dp_mst_topology_mgr *mgr) -{ - mutex_unlock(&mgr->topology_ref_history_lock); -} -#else -static inline void -topology_ref_history_lock(struct drm_dp_mst_topology_mgr *mgr) {} -static inline void -topology_ref_history_unlock(struct drm_dp_mst_topology_mgr *mgr) {} -static inline void -drm_dp_mst_dump_mstb_topology_history(struct drm_dp_mst_branch *mstb) {} -static inline void -drm_dp_mst_dump_port_topology_history(struct drm_dp_mst_port *port) {} -#define save_mstb_topology_ref(mstb, type) -#define save_port_topology_ref(port, type) -#endif - -static void drm_dp_destroy_mst_branch_device(struct kref *kref) -{ - struct drm_dp_mst_branch *mstb = - container_of(kref, struct drm_dp_mst_branch, topology_kref); - struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; - - drm_dp_mst_dump_mstb_topology_history(mstb); - - INIT_LIST_HEAD(&mstb->destroy_next); - - /* - * This can get called under mgr->mutex, so we need to perform the - * actual destruction of the mstb in another worker - */ - mutex_lock(&mgr->delayed_destroy_lock); - list_add(&mstb->destroy_next, &mgr->destroy_branch_device_list); - mutex_unlock(&mgr->delayed_destroy_lock); - queue_work(mgr->delayed_destroy_wq, &mgr->delayed_destroy_work); -} - -/** - * drm_dp_mst_topology_try_get_mstb() - Increment the topology refcount of a - * branch device unless it's zero - * @mstb: &struct drm_dp_mst_branch to increment the topology refcount of - * - * Attempts to grab a topology reference to @mstb, if it hasn't yet been - * removed from the topology (e.g. &drm_dp_mst_branch.topology_kref has - * reached 0). Holding a topology reference implies that a malloc reference - * will be held to @mstb as long as the user holds the topology reference. - * - * Care should be taken to ensure that the user has at least one malloc - * reference to @mstb. If you already have a topology reference to @mstb, you - * should use drm_dp_mst_topology_get_mstb() instead. - * - * See also: - * drm_dp_mst_topology_get_mstb() - * drm_dp_mst_topology_put_mstb() - * - * Returns: - * * 1: A topology reference was grabbed successfully - * * 0: @port is no longer in the topology, no reference was grabbed - */ -static int __must_check -drm_dp_mst_topology_try_get_mstb(struct drm_dp_mst_branch *mstb) -{ - int ret; - - topology_ref_history_lock(mstb->mgr); - ret = kref_get_unless_zero(&mstb->topology_kref); - if (ret) { - drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref)); - save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_GET); - } - - topology_ref_history_unlock(mstb->mgr); - - return ret; -} - -/** - * drm_dp_mst_topology_get_mstb() - Increment the topology refcount of a - * branch device - * @mstb: The &struct drm_dp_mst_branch to increment the topology refcount of - * - * Increments &drm_dp_mst_branch.topology_refcount without checking whether or - * not it's already reached 0. This is only valid to use in scenarios where - * you are already guaranteed to have at least one active topology reference - * to @mstb. Otherwise, drm_dp_mst_topology_try_get_mstb() must be used. - * - * See also: - * drm_dp_mst_topology_try_get_mstb() - * drm_dp_mst_topology_put_mstb() - */ -static void drm_dp_mst_topology_get_mstb(struct drm_dp_mst_branch *mstb) -{ - topology_ref_history_lock(mstb->mgr); - - save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_GET); - WARN_ON(kref_read(&mstb->topology_kref) == 0); - kref_get(&mstb->topology_kref); - drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref)); - - topology_ref_history_unlock(mstb->mgr); -} - -/** - * drm_dp_mst_topology_put_mstb() - release a topology reference to a branch - * device - * @mstb: The &struct drm_dp_mst_branch to release the topology reference from - * - * Releases a topology reference from @mstb by decrementing - * &drm_dp_mst_branch.topology_kref. - * - * See also: - * drm_dp_mst_topology_try_get_mstb() - * drm_dp_mst_topology_get_mstb() - */ -static void -drm_dp_mst_topology_put_mstb(struct drm_dp_mst_branch *mstb) -{ - topology_ref_history_lock(mstb->mgr); - - drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref) - 1); - save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_PUT); - - topology_ref_history_unlock(mstb->mgr); - kref_put(&mstb->topology_kref, drm_dp_destroy_mst_branch_device); -} - -static void drm_dp_destroy_port(struct kref *kref) -{ - struct drm_dp_mst_port *port = - container_of(kref, struct drm_dp_mst_port, topology_kref); - struct drm_dp_mst_topology_mgr *mgr = port->mgr; - - drm_dp_mst_dump_port_topology_history(port); - - /* There's nothing that needs locking to destroy an input port yet */ - if (port->input) { - drm_dp_mst_put_port_malloc(port); - return; - } - - kfree(port->cached_edid); - - /* - * we can't destroy the connector here, as we might be holding the - * mode_config.mutex from an EDID retrieval - */ - mutex_lock(&mgr->delayed_destroy_lock); - list_add(&port->next, &mgr->destroy_port_list); - mutex_unlock(&mgr->delayed_destroy_lock); - queue_work(mgr->delayed_destroy_wq, &mgr->delayed_destroy_work); -} - -/** - * drm_dp_mst_topology_try_get_port() - Increment the topology refcount of a - * port unless it's zero - * @port: &struct drm_dp_mst_port to increment the topology refcount of - * - * Attempts to grab a topology reference to @port, if it hasn't yet been - * removed from the topology (e.g. &drm_dp_mst_port.topology_kref has reached - * 0). Holding a topology reference implies that a malloc reference will be - * held to @port as long as the user holds the topology reference. - * - * Care should be taken to ensure that the user has at least one malloc - * reference to @port. If you already have a topology reference to @port, you - * should use drm_dp_mst_topology_get_port() instead. - * - * See also: - * drm_dp_mst_topology_get_port() - * drm_dp_mst_topology_put_port() - * - * Returns: - * * 1: A topology reference was grabbed successfully - * * 0: @port is no longer in the topology, no reference was grabbed - */ -static int __must_check -drm_dp_mst_topology_try_get_port(struct drm_dp_mst_port *port) -{ - int ret; - - topology_ref_history_lock(port->mgr); - ret = kref_get_unless_zero(&port->topology_kref); - if (ret) { - drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref)); - save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_GET); - } - - topology_ref_history_unlock(port->mgr); - return ret; -} - -/** - * drm_dp_mst_topology_get_port() - Increment the topology refcount of a port - * @port: The &struct drm_dp_mst_port to increment the topology refcount of - * - * Increments &drm_dp_mst_port.topology_refcount without checking whether or - * not it's already reached 0. This is only valid to use in scenarios where - * you are already guaranteed to have at least one active topology reference - * to @port. Otherwise, drm_dp_mst_topology_try_get_port() must be used. - * - * See also: - * drm_dp_mst_topology_try_get_port() - * drm_dp_mst_topology_put_port() - */ -static void drm_dp_mst_topology_get_port(struct drm_dp_mst_port *port) -{ - topology_ref_history_lock(port->mgr); - - WARN_ON(kref_read(&port->topology_kref) == 0); - kref_get(&port->topology_kref); - drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref)); - save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_GET); - - topology_ref_history_unlock(port->mgr); -} - -/** - * drm_dp_mst_topology_put_port() - release a topology reference to a port - * @port: The &struct drm_dp_mst_port to release the topology reference from - * - * Releases a topology reference from @port by decrementing - * &drm_dp_mst_port.topology_kref. - * - * See also: - * drm_dp_mst_topology_try_get_port() - * drm_dp_mst_topology_get_port() - */ -static void drm_dp_mst_topology_put_port(struct drm_dp_mst_port *port) -{ - topology_ref_history_lock(port->mgr); - - drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref) - 1); - save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_PUT); - - topology_ref_history_unlock(port->mgr); - kref_put(&port->topology_kref, drm_dp_destroy_port); -} - -static struct drm_dp_mst_branch * -drm_dp_mst_topology_get_mstb_validated_locked(struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_branch *to_find) -{ - struct drm_dp_mst_port *port; - struct drm_dp_mst_branch *rmstb; - - if (to_find == mstb) - return mstb; - - list_for_each_entry(port, &mstb->ports, next) { - if (port->mstb) { - rmstb = drm_dp_mst_topology_get_mstb_validated_locked( - port->mstb, to_find); - if (rmstb) - return rmstb; - } - } - return NULL; -} - -static struct drm_dp_mst_branch * -drm_dp_mst_topology_get_mstb_validated(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb) -{ - struct drm_dp_mst_branch *rmstb = NULL; - - mutex_lock(&mgr->lock); - if (mgr->mst_primary) { - rmstb = drm_dp_mst_topology_get_mstb_validated_locked( - mgr->mst_primary, mstb); - - if (rmstb && !drm_dp_mst_topology_try_get_mstb(rmstb)) - rmstb = NULL; - } - mutex_unlock(&mgr->lock); - return rmstb; -} - -static struct drm_dp_mst_port * -drm_dp_mst_topology_get_port_validated_locked(struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_port *to_find) -{ - struct drm_dp_mst_port *port, *mport; - - list_for_each_entry(port, &mstb->ports, next) { - if (port == to_find) - return port; - - if (port->mstb) { - mport = drm_dp_mst_topology_get_port_validated_locked( - port->mstb, to_find); - if (mport) - return mport; - } - } - return NULL; -} - -static struct drm_dp_mst_port * -drm_dp_mst_topology_get_port_validated(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port) -{ - struct drm_dp_mst_port *rport = NULL; - - mutex_lock(&mgr->lock); - if (mgr->mst_primary) { - rport = drm_dp_mst_topology_get_port_validated_locked( - mgr->mst_primary, port); - - if (rport && !drm_dp_mst_topology_try_get_port(rport)) - rport = NULL; - } - mutex_unlock(&mgr->lock); - return rport; -} - -static struct drm_dp_mst_port *drm_dp_get_port(struct drm_dp_mst_branch *mstb, u8 port_num) -{ - struct drm_dp_mst_port *port; - int ret; - - list_for_each_entry(port, &mstb->ports, next) { - if (port->port_num == port_num) { - ret = drm_dp_mst_topology_try_get_port(port); - return ret ? port : NULL; - } - } - - return NULL; -} - -/* - * calculate a new RAD for this MST branch device - * if parent has an LCT of 2 then it has 1 nibble of RAD, - * if parent has an LCT of 3 then it has 2 nibbles of RAD, - */ -static u8 drm_dp_calculate_rad(struct drm_dp_mst_port *port, - u8 *rad) -{ - int parent_lct = port->parent->lct; - int shift = 4; - int idx = (parent_lct - 1) / 2; - - if (parent_lct > 1) { - memcpy(rad, port->parent->rad, idx + 1); - shift = (parent_lct % 2) ? 4 : 0; - } else - rad[0] = 0; - - rad[idx] |= port->port_num << shift; - return parent_lct + 1; -} - -static bool drm_dp_mst_is_end_device(u8 pdt, bool mcs) -{ - switch (pdt) { - case DP_PEER_DEVICE_DP_LEGACY_CONV: - case DP_PEER_DEVICE_SST_SINK: - return true; - case DP_PEER_DEVICE_MST_BRANCHING: - /* For sst branch device */ - if (!mcs) - return true; - - return false; - } - return true; -} - -static int -drm_dp_port_set_pdt(struct drm_dp_mst_port *port, u8 new_pdt, - bool new_mcs) -{ - struct drm_dp_mst_topology_mgr *mgr = port->mgr; - struct drm_dp_mst_branch *mstb; - u8 rad[8], lct; - int ret = 0; - - if (port->pdt == new_pdt && port->mcs == new_mcs) - return 0; - - /* Teardown the old pdt, if there is one */ - if (port->pdt != DP_PEER_DEVICE_NONE) { - if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { - /* - * If the new PDT would also have an i2c bus, - * don't bother with reregistering it - */ - if (new_pdt != DP_PEER_DEVICE_NONE && - drm_dp_mst_is_end_device(new_pdt, new_mcs)) { - port->pdt = new_pdt; - port->mcs = new_mcs; - return 0; - } - - /* remove i2c over sideband */ - drm_dp_mst_unregister_i2c_bus(port); - } else { - mutex_lock(&mgr->lock); - drm_dp_mst_topology_put_mstb(port->mstb); - port->mstb = NULL; - mutex_unlock(&mgr->lock); - } - } - - port->pdt = new_pdt; - port->mcs = new_mcs; - - if (port->pdt != DP_PEER_DEVICE_NONE) { - if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { - /* add i2c over sideband */ - ret = drm_dp_mst_register_i2c_bus(port); - } else { - lct = drm_dp_calculate_rad(port, rad); - mstb = drm_dp_add_mst_branch_device(lct, rad); - if (!mstb) { - ret = -ENOMEM; - drm_err(mgr->dev, "Failed to create MSTB for port %p", port); - goto out; - } - - mutex_lock(&mgr->lock); - port->mstb = mstb; - mstb->mgr = port->mgr; - mstb->port_parent = port; - - /* - * Make sure this port's memory allocation stays - * around until its child MSTB releases it - */ - drm_dp_mst_get_port_malloc(port); - mutex_unlock(&mgr->lock); - - /* And make sure we send a link address for this */ - ret = 1; - } - } - -out: - if (ret < 0) - port->pdt = DP_PEER_DEVICE_NONE; - return ret; -} - -/** - * drm_dp_mst_dpcd_read() - read a series of bytes from the DPCD via sideband - * @aux: Fake sideband AUX CH - * @offset: address of the (first) register to read - * @buffer: buffer to store the register values - * @size: number of bytes in @buffer - * - * Performs the same functionality for remote devices via - * sideband messaging as drm_dp_dpcd_read() does for local - * devices via actual AUX CH. - * - * Return: Number of bytes read, or negative error code on failure. - */ -ssize_t drm_dp_mst_dpcd_read(struct drm_dp_aux *aux, - unsigned int offset, void *buffer, size_t size) -{ - struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port, - aux); - - return drm_dp_send_dpcd_read(port->mgr, port, - offset, size, buffer); -} - -/** - * drm_dp_mst_dpcd_write() - write a series of bytes to the DPCD via sideband - * @aux: Fake sideband AUX CH - * @offset: address of the (first) register to write - * @buffer: buffer containing the values to write - * @size: number of bytes in @buffer - * - * Performs the same functionality for remote devices via - * sideband messaging as drm_dp_dpcd_write() does for local - * devices via actual AUX CH. - * - * Return: number of bytes written on success, negative error code on failure. - */ -ssize_t drm_dp_mst_dpcd_write(struct drm_dp_aux *aux, - unsigned int offset, void *buffer, size_t size) -{ - struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port, - aux); - - return drm_dp_send_dpcd_write(port->mgr, port, - offset, size, buffer); -} - -static int drm_dp_check_mstb_guid(struct drm_dp_mst_branch *mstb, u8 *guid) -{ - int ret = 0; - - memcpy(mstb->guid, guid, 16); - - if (!drm_dp_validate_guid(mstb->mgr, mstb->guid)) { - if (mstb->port_parent) { - ret = drm_dp_send_dpcd_write(mstb->mgr, - mstb->port_parent, - DP_GUID, 16, mstb->guid); - } else { - ret = drm_dp_dpcd_write(mstb->mgr->aux, - DP_GUID, mstb->guid, 16); - } - } - - if (ret < 16 && ret > 0) - return -EPROTO; - - return ret == 16 ? 0 : ret; -} - -static void build_mst_prop_path(const struct drm_dp_mst_branch *mstb, - int pnum, - char *proppath, - size_t proppath_size) -{ - int i; - char temp[8]; - - snprintf(proppath, proppath_size, "mst:%d", mstb->mgr->conn_base_id); - for (i = 0; i < (mstb->lct - 1); i++) { - int shift = (i % 2) ? 0 : 4; - int port_num = (mstb->rad[i / 2] >> shift) & 0xf; - - snprintf(temp, sizeof(temp), "-%d", port_num); - strlcat(proppath, temp, proppath_size); - } - snprintf(temp, sizeof(temp), "-%d", pnum); - strlcat(proppath, temp, proppath_size); -} - -/** - * drm_dp_mst_connector_late_register() - Late MST connector registration - * @connector: The MST connector - * @port: The MST port for this connector - * - * Helper to register the remote aux device for this MST port. Drivers should - * call this from their mst connector's late_register hook to enable MST aux - * devices. - * - * Return: 0 on success, negative error code on failure. - */ -int drm_dp_mst_connector_late_register(struct drm_connector *connector, - struct drm_dp_mst_port *port) -{ - drm_dbg_kms(port->mgr->dev, "registering %s remote bus for %s\n", - port->aux.name, connector->kdev->kobj.name); - - port->aux.dev = connector->kdev; - return drm_dp_aux_register_devnode(&port->aux); -} -EXPORT_SYMBOL(drm_dp_mst_connector_late_register); - -/** - * drm_dp_mst_connector_early_unregister() - Early MST connector unregistration - * @connector: The MST connector - * @port: The MST port for this connector - * - * Helper to unregister the remote aux device for this MST port, registered by - * drm_dp_mst_connector_late_register(). Drivers should call this from their mst - * connector's early_unregister hook. - */ -void drm_dp_mst_connector_early_unregister(struct drm_connector *connector, - struct drm_dp_mst_port *port) -{ - drm_dbg_kms(port->mgr->dev, "unregistering %s remote bus for %s\n", - port->aux.name, connector->kdev->kobj.name); - drm_dp_aux_unregister_devnode(&port->aux); -} -EXPORT_SYMBOL(drm_dp_mst_connector_early_unregister); - -static void -drm_dp_mst_port_add_connector(struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_port *port) -{ - struct drm_dp_mst_topology_mgr *mgr = port->mgr; - char proppath[255]; - int ret; - - build_mst_prop_path(mstb, port->port_num, proppath, sizeof(proppath)); - port->connector = mgr->cbs->add_connector(mgr, port, proppath); - if (!port->connector) { - ret = -ENOMEM; - goto error; - } - - if (port->pdt != DP_PEER_DEVICE_NONE && - drm_dp_mst_is_end_device(port->pdt, port->mcs) && - port->port_num >= DP_MST_LOGICAL_PORT_0) - port->cached_edid = drm_get_edid(port->connector, - &port->aux.ddc); - - drm_connector_register(port->connector); - return; - -error: - drm_err(mgr->dev, "Failed to create connector for port %p: %d\n", port, ret); -} - -/* - * Drop a topology reference, and unlink the port from the in-memory topology - * layout - */ -static void -drm_dp_mst_topology_unlink_port(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port) -{ - mutex_lock(&mgr->lock); - port->parent->num_ports--; - list_del(&port->next); - mutex_unlock(&mgr->lock); - drm_dp_mst_topology_put_port(port); -} - -static struct drm_dp_mst_port * -drm_dp_mst_add_port(struct drm_device *dev, - struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb, u8 port_number) -{ - struct drm_dp_mst_port *port = kzalloc(sizeof(*port), GFP_KERNEL); - - if (!port) - return NULL; - - kref_init(&port->topology_kref); - kref_init(&port->malloc_kref); - port->parent = mstb; - port->port_num = port_number; - port->mgr = mgr; - port->aux.name = "DPMST"; - port->aux.dev = dev->dev; - port->aux.is_remote = true; - - /* initialize the MST downstream port's AUX crc work queue */ - port->aux.drm_dev = dev; - drm_dp_remote_aux_init(&port->aux); - - /* - * Make sure the memory allocation for our parent branch stays - * around until our own memory allocation is released - */ - drm_dp_mst_get_mstb_malloc(mstb); - - return port; -} - -static int -drm_dp_mst_handle_link_address_port(struct drm_dp_mst_branch *mstb, - struct drm_device *dev, - struct drm_dp_link_addr_reply_port *port_msg) -{ - struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; - struct drm_dp_mst_port *port; - int old_ddps = 0, ret; - u8 new_pdt = DP_PEER_DEVICE_NONE; - bool new_mcs = 0; - bool created = false, send_link_addr = false, changed = false; - - port = drm_dp_get_port(mstb, port_msg->port_number); - if (!port) { - port = drm_dp_mst_add_port(dev, mgr, mstb, - port_msg->port_number); - if (!port) - return -ENOMEM; - created = true; - changed = true; - } else if (!port->input && port_msg->input_port && port->connector) { - /* Since port->connector can't be changed here, we create a - * new port if input_port changes from 0 to 1 - */ - drm_dp_mst_topology_unlink_port(mgr, port); - drm_dp_mst_topology_put_port(port); - port = drm_dp_mst_add_port(dev, mgr, mstb, - port_msg->port_number); - if (!port) - return -ENOMEM; - changed = true; - created = true; - } else if (port->input && !port_msg->input_port) { - changed = true; - } else if (port->connector) { - /* We're updating a port that's exposed to userspace, so do it - * under lock - */ - drm_modeset_lock(&mgr->base.lock, NULL); - - old_ddps = port->ddps; - changed = port->ddps != port_msg->ddps || - (port->ddps && - (port->ldps != port_msg->legacy_device_plug_status || - port->dpcd_rev != port_msg->dpcd_revision || - port->mcs != port_msg->mcs || - port->pdt != port_msg->peer_device_type || - port->num_sdp_stream_sinks != - port_msg->num_sdp_stream_sinks)); - } - - port->input = port_msg->input_port; - if (!port->input) - new_pdt = port_msg->peer_device_type; - new_mcs = port_msg->mcs; - port->ddps = port_msg->ddps; - port->ldps = port_msg->legacy_device_plug_status; - port->dpcd_rev = port_msg->dpcd_revision; - port->num_sdp_streams = port_msg->num_sdp_streams; - port->num_sdp_stream_sinks = port_msg->num_sdp_stream_sinks; - - /* manage mstb port lists with mgr lock - take a reference - for this list */ - if (created) { - mutex_lock(&mgr->lock); - drm_dp_mst_topology_get_port(port); - list_add(&port->next, &mstb->ports); - mstb->num_ports++; - mutex_unlock(&mgr->lock); - } - - /* - * Reprobe PBN caps on both hotplug, and when re-probing the link - * for our parent mstb - */ - if (old_ddps != port->ddps || !created) { - if (port->ddps && !port->input) { - ret = drm_dp_send_enum_path_resources(mgr, mstb, - port); - if (ret == 1) - changed = true; - } else { - port->full_pbn = 0; - } - } - - ret = drm_dp_port_set_pdt(port, new_pdt, new_mcs); - if (ret == 1) { - send_link_addr = true; - } else if (ret < 0) { - drm_err(dev, "Failed to change PDT on port %p: %d\n", port, ret); - goto fail; - } - - /* - * If this port wasn't just created, then we're reprobing because - * we're coming out of suspend. In this case, always resend the link - * address if there's an MSTB on this port - */ - if (!created && port->pdt == DP_PEER_DEVICE_MST_BRANCHING && - port->mcs) - send_link_addr = true; - - if (port->connector) - drm_modeset_unlock(&mgr->base.lock); - else if (!port->input) - drm_dp_mst_port_add_connector(mstb, port); - - if (send_link_addr && port->mstb) { - ret = drm_dp_send_link_address(mgr, port->mstb); - if (ret == 1) /* MSTB below us changed */ - changed = true; - else if (ret < 0) - goto fail_put; - } - - /* put reference to this port */ - drm_dp_mst_topology_put_port(port); - return changed; - -fail: - drm_dp_mst_topology_unlink_port(mgr, port); - if (port->connector) - drm_modeset_unlock(&mgr->base.lock); -fail_put: - drm_dp_mst_topology_put_port(port); - return ret; -} - -static void -drm_dp_mst_handle_conn_stat(struct drm_dp_mst_branch *mstb, - struct drm_dp_connection_status_notify *conn_stat) -{ - struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; - struct drm_dp_mst_port *port; - int old_ddps, ret; - u8 new_pdt; - bool new_mcs; - bool dowork = false, create_connector = false; - - port = drm_dp_get_port(mstb, conn_stat->port_number); - if (!port) - return; - - if (port->connector) { - if (!port->input && conn_stat->input_port) { - /* - * We can't remove a connector from an already exposed - * port, so just throw the port out and make sure we - * reprobe the link address of it's parent MSTB - */ - drm_dp_mst_topology_unlink_port(mgr, port); - mstb->link_address_sent = false; - dowork = true; - goto out; - } - - /* Locking is only needed if the port's exposed to userspace */ - drm_modeset_lock(&mgr->base.lock, NULL); - } else if (port->input && !conn_stat->input_port) { - create_connector = true; - /* Reprobe link address so we get num_sdp_streams */ - mstb->link_address_sent = false; - dowork = true; - } - - old_ddps = port->ddps; - port->input = conn_stat->input_port; - port->ldps = conn_stat->legacy_device_plug_status; - port->ddps = conn_stat->displayport_device_plug_status; - - if (old_ddps != port->ddps) { - if (port->ddps && !port->input) - drm_dp_send_enum_path_resources(mgr, mstb, port); - else - port->full_pbn = 0; - } - - new_pdt = port->input ? DP_PEER_DEVICE_NONE : conn_stat->peer_device_type; - new_mcs = conn_stat->message_capability_status; - ret = drm_dp_port_set_pdt(port, new_pdt, new_mcs); - if (ret == 1) { - dowork = true; - } else if (ret < 0) { - drm_err(mgr->dev, "Failed to change PDT for port %p: %d\n", port, ret); - dowork = false; - } - - if (port->connector) - drm_modeset_unlock(&mgr->base.lock); - else if (create_connector) - drm_dp_mst_port_add_connector(mstb, port); - -out: - drm_dp_mst_topology_put_port(port); - if (dowork) - queue_work(system_long_wq, &mstb->mgr->work); -} - -static struct drm_dp_mst_branch *drm_dp_get_mst_branch_device(struct drm_dp_mst_topology_mgr *mgr, - u8 lct, u8 *rad) -{ - struct drm_dp_mst_branch *mstb; - struct drm_dp_mst_port *port; - int i, ret; - /* find the port by iterating down */ - - mutex_lock(&mgr->lock); - mstb = mgr->mst_primary; - - if (!mstb) - goto out; - - for (i = 0; i < lct - 1; i++) { - int shift = (i % 2) ? 0 : 4; - int port_num = (rad[i / 2] >> shift) & 0xf; - - list_for_each_entry(port, &mstb->ports, next) { - if (port->port_num == port_num) { - mstb = port->mstb; - if (!mstb) { - drm_err(mgr->dev, - "failed to lookup MSTB with lct %d, rad %02x\n", - lct, rad[0]); - goto out; - } - - break; - } - } - } - ret = drm_dp_mst_topology_try_get_mstb(mstb); - if (!ret) - mstb = NULL; -out: - mutex_unlock(&mgr->lock); - return mstb; -} - -static struct drm_dp_mst_branch *get_mst_branch_device_by_guid_helper( - struct drm_dp_mst_branch *mstb, - const uint8_t *guid) -{ - struct drm_dp_mst_branch *found_mstb; - struct drm_dp_mst_port *port; - - if (memcmp(mstb->guid, guid, 16) == 0) - return mstb; - - - list_for_each_entry(port, &mstb->ports, next) { - if (!port->mstb) - continue; - - found_mstb = get_mst_branch_device_by_guid_helper(port->mstb, guid); - - if (found_mstb) - return found_mstb; - } - - return NULL; -} - -static struct drm_dp_mst_branch * -drm_dp_get_mst_branch_device_by_guid(struct drm_dp_mst_topology_mgr *mgr, - const uint8_t *guid) -{ - struct drm_dp_mst_branch *mstb; - int ret; - - /* find the port by iterating down */ - mutex_lock(&mgr->lock); - - mstb = get_mst_branch_device_by_guid_helper(mgr->mst_primary, guid); - if (mstb) { - ret = drm_dp_mst_topology_try_get_mstb(mstb); - if (!ret) - mstb = NULL; - } - - mutex_unlock(&mgr->lock); - return mstb; -} - -static int drm_dp_check_and_send_link_address(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb) -{ - struct drm_dp_mst_port *port; - int ret; - bool changed = false; - - if (!mstb->link_address_sent) { - ret = drm_dp_send_link_address(mgr, mstb); - if (ret == 1) - changed = true; - else if (ret < 0) - return ret; - } - - list_for_each_entry(port, &mstb->ports, next) { - struct drm_dp_mst_branch *mstb_child = NULL; - - if (port->input || !port->ddps) - continue; - - if (port->mstb) - mstb_child = drm_dp_mst_topology_get_mstb_validated( - mgr, port->mstb); - - if (mstb_child) { - ret = drm_dp_check_and_send_link_address(mgr, - mstb_child); - drm_dp_mst_topology_put_mstb(mstb_child); - if (ret == 1) - changed = true; - else if (ret < 0) - return ret; - } - } - - return changed; -} - -static void drm_dp_mst_link_probe_work(struct work_struct *work) -{ - struct drm_dp_mst_topology_mgr *mgr = - container_of(work, struct drm_dp_mst_topology_mgr, work); - struct drm_device *dev = mgr->dev; - struct drm_dp_mst_branch *mstb; - int ret; - bool clear_payload_id_table; - - mutex_lock(&mgr->probe_lock); - - mutex_lock(&mgr->lock); - clear_payload_id_table = !mgr->payload_id_table_cleared; - mgr->payload_id_table_cleared = true; - - mstb = mgr->mst_primary; - if (mstb) { - ret = drm_dp_mst_topology_try_get_mstb(mstb); - if (!ret) - mstb = NULL; - } - mutex_unlock(&mgr->lock); - if (!mstb) { - mutex_unlock(&mgr->probe_lock); - return; - } - - /* - * Certain branch devices seem to incorrectly report an available_pbn - * of 0 on downstream sinks, even after clearing the - * DP_PAYLOAD_ALLOCATE_* registers in - * drm_dp_mst_topology_mgr_set_mst(). Namely, the CableMatters USB-C - * 2x DP hub. Sending a CLEAR_PAYLOAD_ID_TABLE message seems to make - * things work again. - */ - if (clear_payload_id_table) { - drm_dbg_kms(dev, "Clearing payload ID table\n"); - drm_dp_send_clear_payload_id_table(mgr, mstb); - } - - ret = drm_dp_check_and_send_link_address(mgr, mstb); - drm_dp_mst_topology_put_mstb(mstb); - - mutex_unlock(&mgr->probe_lock); - if (ret > 0) - drm_kms_helper_hotplug_event(dev); -} - -static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr, - u8 *guid) -{ - u64 salt; - - if (memchr_inv(guid, 0, 16)) - return true; - - salt = get_jiffies_64(); - - memcpy(&guid[0], &salt, sizeof(u64)); - memcpy(&guid[8], &salt, sizeof(u64)); - - return false; -} - -static void build_dpcd_read(struct drm_dp_sideband_msg_tx *msg, - u8 port_num, u32 offset, u8 num_bytes) -{ - struct drm_dp_sideband_msg_req_body req; - - req.req_type = DP_REMOTE_DPCD_READ; - req.u.dpcd_read.port_number = port_num; - req.u.dpcd_read.dpcd_address = offset; - req.u.dpcd_read.num_bytes = num_bytes; - drm_dp_encode_sideband_req(&req, msg); -} - -static int drm_dp_send_sideband_msg(struct drm_dp_mst_topology_mgr *mgr, - bool up, u8 *msg, int len) -{ - int ret; - int regbase = up ? DP_SIDEBAND_MSG_UP_REP_BASE : DP_SIDEBAND_MSG_DOWN_REQ_BASE; - int tosend, total, offset; - int retries = 0; - -retry: - total = len; - offset = 0; - do { - tosend = min3(mgr->max_dpcd_transaction_bytes, 16, total); - - ret = drm_dp_dpcd_write(mgr->aux, regbase + offset, - &msg[offset], - tosend); - if (ret != tosend) { - if (ret == -EIO && retries < 5) { - retries++; - goto retry; - } - drm_dbg_kms(mgr->dev, "failed to dpcd write %d %d\n", tosend, ret); - - return -EIO; - } - offset += tosend; - total -= tosend; - } while (total > 0); - return 0; -} - -static int set_hdr_from_dst_qlock(struct drm_dp_sideband_msg_hdr *hdr, - struct drm_dp_sideband_msg_tx *txmsg) -{ - struct drm_dp_mst_branch *mstb = txmsg->dst; - u8 req_type; - - req_type = txmsg->msg[0] & 0x7f; - if (req_type == DP_CONNECTION_STATUS_NOTIFY || - req_type == DP_RESOURCE_STATUS_NOTIFY || - req_type == DP_CLEAR_PAYLOAD_ID_TABLE) - hdr->broadcast = 1; - else - hdr->broadcast = 0; - hdr->path_msg = txmsg->path_msg; - if (hdr->broadcast) { - hdr->lct = 1; - hdr->lcr = 6; - } else { - hdr->lct = mstb->lct; - hdr->lcr = mstb->lct - 1; - } - - memcpy(hdr->rad, mstb->rad, hdr->lct / 2); - - return 0; -} -/* - * process a single block of the next message in the sideband queue - */ -static int process_single_tx_qlock(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_tx *txmsg, - bool up) -{ - u8 chunk[48]; - struct drm_dp_sideband_msg_hdr hdr; - int len, space, idx, tosend; - int ret; - - if (txmsg->state == DRM_DP_SIDEBAND_TX_SENT) - return 0; - - memset(&hdr, 0, sizeof(struct drm_dp_sideband_msg_hdr)); - - if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED) - txmsg->state = DRM_DP_SIDEBAND_TX_START_SEND; - - /* make hdr from dst mst */ - ret = set_hdr_from_dst_qlock(&hdr, txmsg); - if (ret < 0) - return ret; - - /* amount left to send in this message */ - len = txmsg->cur_len - txmsg->cur_offset; - - /* 48 - sideband msg size - 1 byte for data CRC, x header bytes */ - space = 48 - 1 - drm_dp_calc_sb_hdr_size(&hdr); - - tosend = min(len, space); - if (len == txmsg->cur_len) - hdr.somt = 1; - if (space >= len) - hdr.eomt = 1; - - - hdr.msg_len = tosend + 1; - drm_dp_encode_sideband_msg_hdr(&hdr, chunk, &idx); - memcpy(&chunk[idx], &txmsg->msg[txmsg->cur_offset], tosend); - /* add crc at end */ - drm_dp_crc_sideband_chunk_req(&chunk[idx], tosend); - idx += tosend + 1; - - ret = drm_dp_send_sideband_msg(mgr, up, chunk, idx); - if (ret) { - if (drm_debug_enabled(DRM_UT_DP)) { - struct drm_printer p = drm_debug_printer(DBG_PREFIX); - - drm_printf(&p, "sideband msg failed to send\n"); - drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); - } - return ret; - } - - txmsg->cur_offset += tosend; - if (txmsg->cur_offset == txmsg->cur_len) { - txmsg->state = DRM_DP_SIDEBAND_TX_SENT; - return 1; - } - return 0; -} - -static void process_single_down_tx_qlock(struct drm_dp_mst_topology_mgr *mgr) -{ - struct drm_dp_sideband_msg_tx *txmsg; - int ret; - - WARN_ON(!mutex_is_locked(&mgr->qlock)); - - /* construct a chunk from the first msg in the tx_msg queue */ - if (list_empty(&mgr->tx_msg_downq)) - return; - - txmsg = list_first_entry(&mgr->tx_msg_downq, - struct drm_dp_sideband_msg_tx, next); - ret = process_single_tx_qlock(mgr, txmsg, false); - if (ret < 0) { - drm_dbg_kms(mgr->dev, "failed to send msg in q %d\n", ret); - list_del(&txmsg->next); - txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT; - wake_up_all(&mgr->tx_waitq); - } -} - -static void drm_dp_queue_down_tx(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_sideband_msg_tx *txmsg) -{ - mutex_lock(&mgr->qlock); - list_add_tail(&txmsg->next, &mgr->tx_msg_downq); - - if (drm_debug_enabled(DRM_UT_DP)) { - struct drm_printer p = drm_debug_printer(DBG_PREFIX); - - drm_dp_mst_dump_sideband_msg_tx(&p, txmsg); - } - - if (list_is_singular(&mgr->tx_msg_downq)) - process_single_down_tx_qlock(mgr); - mutex_unlock(&mgr->qlock); -} - -static void -drm_dp_dump_link_address(const struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_link_address_ack_reply *reply) -{ - struct drm_dp_link_addr_reply_port *port_reply; - int i; - - for (i = 0; i < reply->nports; i++) { - port_reply = &reply->ports[i]; - drm_dbg_kms(mgr->dev, - "port %d: input %d, pdt: %d, pn: %d, dpcd_rev: %02x, mcs: %d, ddps: %d, ldps %d, sdp %d/%d\n", - i, - port_reply->input_port, - port_reply->peer_device_type, - port_reply->port_number, - port_reply->dpcd_revision, - port_reply->mcs, - port_reply->ddps, - port_reply->legacy_device_plug_status, - port_reply->num_sdp_streams, - port_reply->num_sdp_stream_sinks); - } -} - -static int drm_dp_send_link_address(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb) -{ - struct drm_dp_sideband_msg_tx *txmsg; - struct drm_dp_link_address_ack_reply *reply; - struct drm_dp_mst_port *port, *tmp; - int i, ret, port_mask = 0; - bool changed = false; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) - return -ENOMEM; - - txmsg->dst = mstb; - build_link_address(txmsg); - - mstb->link_address_sent = true; - drm_dp_queue_down_tx(mgr, txmsg); - - /* FIXME: Actually do some real error handling here */ - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret <= 0) { - drm_err(mgr->dev, "Sending link address failed with %d\n", ret); - goto out; - } - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { - drm_err(mgr->dev, "link address NAK received\n"); - ret = -EIO; - goto out; - } - - reply = &txmsg->reply.u.link_addr; - drm_dbg_kms(mgr->dev, "link address reply: %d\n", reply->nports); - drm_dp_dump_link_address(mgr, reply); - - ret = drm_dp_check_mstb_guid(mstb, reply->guid); - if (ret) { - char buf[64]; - - drm_dp_mst_rad_to_str(mstb->rad, mstb->lct, buf, sizeof(buf)); - drm_err(mgr->dev, "GUID check on %s failed: %d\n", buf, ret); - goto out; - } - - for (i = 0; i < reply->nports; i++) { - port_mask |= BIT(reply->ports[i].port_number); - ret = drm_dp_mst_handle_link_address_port(mstb, mgr->dev, - &reply->ports[i]); - if (ret == 1) - changed = true; - else if (ret < 0) - goto out; - } - - /* Prune any ports that are currently a part of mstb in our in-memory - * topology, but were not seen in this link address. Usually this - * means that they were removed while the topology was out of sync, - * e.g. during suspend/resume - */ - mutex_lock(&mgr->lock); - list_for_each_entry_safe(port, tmp, &mstb->ports, next) { - if (port_mask & BIT(port->port_num)) - continue; - - drm_dbg_kms(mgr->dev, "port %d was not in link address, removing\n", - port->port_num); - list_del(&port->next); - drm_dp_mst_topology_put_port(port); - changed = true; - } - mutex_unlock(&mgr->lock); - -out: - if (ret <= 0) - mstb->link_address_sent = false; - kfree(txmsg); - return ret < 0 ? ret : changed; -} - -static void -drm_dp_send_clear_payload_id_table(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb) -{ - struct drm_dp_sideband_msg_tx *txmsg; - int ret; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) - return; - - txmsg->dst = mstb; - build_clear_payload_id_table(txmsg); - - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret > 0 && txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) - drm_dbg_kms(mgr->dev, "clear payload table id nak received\n"); - - kfree(txmsg); -} - -static int -drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_port *port) -{ - struct drm_dp_enum_path_resources_ack_reply *path_res; - struct drm_dp_sideband_msg_tx *txmsg; - int ret; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) - return -ENOMEM; - - txmsg->dst = mstb; - build_enum_path_resources(txmsg, port->port_num); - - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret > 0) { - ret = 0; - path_res = &txmsg->reply.u.path_resources; - - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { - drm_dbg_kms(mgr->dev, "enum path resources nak received\n"); - } else { - if (port->port_num != path_res->port_number) - DRM_ERROR("got incorrect port in response\n"); - - drm_dbg_kms(mgr->dev, "enum path resources %d: %d %d\n", - path_res->port_number, - path_res->full_payload_bw_number, - path_res->avail_payload_bw_number); - - /* - * If something changed, make sure we send a - * hotplug - */ - if (port->full_pbn != path_res->full_payload_bw_number || - port->fec_capable != path_res->fec_capable) - ret = 1; - - port->full_pbn = path_res->full_payload_bw_number; - port->fec_capable = path_res->fec_capable; - } - } - - kfree(txmsg); - return ret; -} - -static struct drm_dp_mst_port *drm_dp_get_last_connected_port_to_mstb(struct drm_dp_mst_branch *mstb) -{ - if (!mstb->port_parent) - return NULL; - - if (mstb->port_parent->mstb != mstb) - return mstb->port_parent; - - return drm_dp_get_last_connected_port_to_mstb(mstb->port_parent->parent); -} - -/* - * Searches upwards in the topology starting from mstb to try to find the - * closest available parent of mstb that's still connected to the rest of the - * topology. This can be used in order to perform operations like releasing - * payloads, where the branch device which owned the payload may no longer be - * around and thus would require that the payload on the last living relative - * be freed instead. - */ -static struct drm_dp_mst_branch * -drm_dp_get_last_connected_port_and_mstb(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb, - int *port_num) -{ - struct drm_dp_mst_branch *rmstb = NULL; - struct drm_dp_mst_port *found_port; - - mutex_lock(&mgr->lock); - if (!mgr->mst_primary) - goto out; - - do { - found_port = drm_dp_get_last_connected_port_to_mstb(mstb); - if (!found_port) - break; - - if (drm_dp_mst_topology_try_get_mstb(found_port->parent)) { - rmstb = found_port->parent; - *port_num = found_port->port_num; - } else { - /* Search again, starting from this parent */ - mstb = found_port->parent; - } - } while (!rmstb); -out: - mutex_unlock(&mgr->lock); - return rmstb; -} - -static int drm_dp_payload_send_msg(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - int id, - int pbn) -{ - struct drm_dp_sideband_msg_tx *txmsg; - struct drm_dp_mst_branch *mstb; - int ret, port_num; - u8 sinks[DRM_DP_MAX_SDP_STREAMS]; - int i; - - port_num = port->port_num; - mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); - if (!mstb) { - mstb = drm_dp_get_last_connected_port_and_mstb(mgr, - port->parent, - &port_num); - - if (!mstb) - return -EINVAL; - } - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) { - ret = -ENOMEM; - goto fail_put; - } - - for (i = 0; i < port->num_sdp_streams; i++) - sinks[i] = i; - - txmsg->dst = mstb; - build_allocate_payload(txmsg, port_num, - id, - pbn, port->num_sdp_streams, sinks); - - drm_dp_queue_down_tx(mgr, txmsg); - - /* - * FIXME: there is a small chance that between getting the last - * connected mstb and sending the payload message, the last connected - * mstb could also be removed from the topology. In the future, this - * needs to be fixed by restarting the - * drm_dp_get_last_connected_port_and_mstb() search in the event of a - * timeout if the topology is still connected to the system. - */ - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret > 0) { - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) - ret = -EINVAL; - else - ret = 0; - } - kfree(txmsg); -fail_put: - drm_dp_mst_topology_put_mstb(mstb); - return ret; -} - -int drm_dp_send_power_updown_phy(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, bool power_up) -{ - struct drm_dp_sideband_msg_tx *txmsg; - int ret; - - port = drm_dp_mst_topology_get_port_validated(mgr, port); - if (!port) - return -EINVAL; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) { - drm_dp_mst_topology_put_port(port); - return -ENOMEM; - } - - txmsg->dst = port->parent; - build_power_updown_phy(txmsg, port->port_num, power_up); - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(port->parent, txmsg); - if (ret > 0) { - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) - ret = -EINVAL; - else - ret = 0; - } - kfree(txmsg); - drm_dp_mst_topology_put_port(port); - - return ret; -} -EXPORT_SYMBOL(drm_dp_send_power_updown_phy); - -int drm_dp_send_query_stream_enc_status(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - struct drm_dp_query_stream_enc_status_ack_reply *status) -{ - struct drm_dp_sideband_msg_tx *txmsg; - u8 nonce[7]; - int ret; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) - return -ENOMEM; - - port = drm_dp_mst_topology_get_port_validated(mgr, port); - if (!port) { - ret = -EINVAL; - goto out_get_port; - } - - get_random_bytes(nonce, sizeof(nonce)); - - /* - * "Source device targets the QUERY_STREAM_ENCRYPTION_STATUS message - * transaction at the MST Branch device directly connected to the - * Source" - */ - txmsg->dst = mgr->mst_primary; - - build_query_stream_enc_status(txmsg, port->vcpi.vcpi, nonce); - - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(mgr->mst_primary, txmsg); - if (ret < 0) { - goto out; - } else if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { - drm_dbg_kms(mgr->dev, "query encryption status nak received\n"); - ret = -ENXIO; - goto out; - } - - ret = 0; - memcpy(status, &txmsg->reply.u.enc_status, sizeof(*status)); - -out: - drm_dp_mst_topology_put_port(port); -out_get_port: - kfree(txmsg); - return ret; -} -EXPORT_SYMBOL(drm_dp_send_query_stream_enc_status); - -static int drm_dp_create_payload_step1(struct drm_dp_mst_topology_mgr *mgr, - int id, - struct drm_dp_payload *payload) -{ - int ret; - - ret = drm_dp_dpcd_write_payload(mgr, id, payload); - if (ret < 0) { - payload->payload_state = 0; - return ret; - } - payload->payload_state = DP_PAYLOAD_LOCAL; - return 0; -} - -static int drm_dp_create_payload_step2(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - int id, - struct drm_dp_payload *payload) -{ - int ret; - - ret = drm_dp_payload_send_msg(mgr, port, id, port->vcpi.pbn); - if (ret < 0) - return ret; - payload->payload_state = DP_PAYLOAD_REMOTE; - return ret; -} - -static int drm_dp_destroy_payload_step1(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - int id, - struct drm_dp_payload *payload) -{ - drm_dbg_kms(mgr->dev, "\n"); - /* it's okay for these to fail */ - if (port) { - drm_dp_payload_send_msg(mgr, port, id, 0); - } - - drm_dp_dpcd_write_payload(mgr, id, payload); - payload->payload_state = DP_PAYLOAD_DELETE_LOCAL; - return 0; -} - -static int drm_dp_destroy_payload_step2(struct drm_dp_mst_topology_mgr *mgr, - int id, - struct drm_dp_payload *payload) -{ - payload->payload_state = 0; - return 0; -} - -/** - * drm_dp_update_payload_part1() - Execute payload update part 1 - * @mgr: manager to use. - * @start_slot: this is the cur slot - * - * NOTE: start_slot is a temporary workaround for non-atomic drivers, - * this will be removed when non-atomic mst helpers are moved out of the helper - * - * This iterates over all proposed virtual channels, and tries to - * allocate space in the link for them. For 0->slots transitions, - * this step just writes the VCPI to the MST device. For slots->0 - * transitions, this writes the updated VCPIs and removes the - * remote VC payloads. - * - * after calling this the driver should generate ACT and payload - * packets. - */ -int drm_dp_update_payload_part1(struct drm_dp_mst_topology_mgr *mgr, int start_slot) -{ - struct drm_dp_payload req_payload; - struct drm_dp_mst_port *port; - int i, j; - int cur_slots = start_slot; - bool skip; - - mutex_lock(&mgr->payload_lock); - for (i = 0; i < mgr->max_payloads; i++) { - struct drm_dp_vcpi *vcpi = mgr->proposed_vcpis[i]; - struct drm_dp_payload *payload = &mgr->payloads[i]; - bool put_port = false; - - /* solve the current payloads - compare to the hw ones - - update the hw view */ - req_payload.start_slot = cur_slots; - if (vcpi) { - port = container_of(vcpi, struct drm_dp_mst_port, - vcpi); - - mutex_lock(&mgr->lock); - skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); - mutex_unlock(&mgr->lock); - - if (skip) { - drm_dbg_kms(mgr->dev, - "Virtual channel %d is not in current topology\n", - i); - continue; - } - /* Validated ports don't matter if we're releasing - * VCPI - */ - if (vcpi->num_slots) { - port = drm_dp_mst_topology_get_port_validated( - mgr, port); - if (!port) { - if (vcpi->num_slots == payload->num_slots) { - cur_slots += vcpi->num_slots; - payload->start_slot = req_payload.start_slot; - continue; - } else { - drm_dbg_kms(mgr->dev, - "Fail:set payload to invalid sink"); - mutex_unlock(&mgr->payload_lock); - return -EINVAL; - } - } - put_port = true; - } - - req_payload.num_slots = vcpi->num_slots; - req_payload.vcpi = vcpi->vcpi; - } else { - port = NULL; - req_payload.num_slots = 0; - } - - payload->start_slot = req_payload.start_slot; - /* work out what is required to happen with this payload */ - if (payload->num_slots != req_payload.num_slots) { - - /* need to push an update for this payload */ - if (req_payload.num_slots) { - drm_dp_create_payload_step1(mgr, vcpi->vcpi, - &req_payload); - payload->num_slots = req_payload.num_slots; - payload->vcpi = req_payload.vcpi; - - } else if (payload->num_slots) { - payload->num_slots = 0; - drm_dp_destroy_payload_step1(mgr, port, - payload->vcpi, - payload); - req_payload.payload_state = - payload->payload_state; - payload->start_slot = 0; - } - payload->payload_state = req_payload.payload_state; - } - cur_slots += req_payload.num_slots; - - if (put_port) - drm_dp_mst_topology_put_port(port); - } - - for (i = 0; i < mgr->max_payloads; /* do nothing */) { - if (mgr->payloads[i].payload_state != DP_PAYLOAD_DELETE_LOCAL) { - i++; - continue; - } - - drm_dbg_kms(mgr->dev, "removing payload %d\n", i); - for (j = i; j < mgr->max_payloads - 1; j++) { - mgr->payloads[j] = mgr->payloads[j + 1]; - mgr->proposed_vcpis[j] = mgr->proposed_vcpis[j + 1]; - - if (mgr->proposed_vcpis[j] && - mgr->proposed_vcpis[j]->num_slots) { - set_bit(j + 1, &mgr->payload_mask); - } else { - clear_bit(j + 1, &mgr->payload_mask); - } - } - - memset(&mgr->payloads[mgr->max_payloads - 1], 0, - sizeof(struct drm_dp_payload)); - mgr->proposed_vcpis[mgr->max_payloads - 1] = NULL; - clear_bit(mgr->max_payloads, &mgr->payload_mask); - } - mutex_unlock(&mgr->payload_lock); - - return 0; -} -EXPORT_SYMBOL(drm_dp_update_payload_part1); - -/** - * drm_dp_update_payload_part2() - Execute payload update part 2 - * @mgr: manager to use. - * - * This iterates over all proposed virtual channels, and tries to - * allocate space in the link for them. For 0->slots transitions, - * this step writes the remote VC payload commands. For slots->0 - * this just resets some internal state. - */ -int drm_dp_update_payload_part2(struct drm_dp_mst_topology_mgr *mgr) -{ - struct drm_dp_mst_port *port; - int i; - int ret = 0; - bool skip; - - mutex_lock(&mgr->payload_lock); - for (i = 0; i < mgr->max_payloads; i++) { - - if (!mgr->proposed_vcpis[i]) - continue; - - port = container_of(mgr->proposed_vcpis[i], struct drm_dp_mst_port, vcpi); - - mutex_lock(&mgr->lock); - skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); - mutex_unlock(&mgr->lock); - - if (skip) - continue; - - drm_dbg_kms(mgr->dev, "payload %d %d\n", i, mgr->payloads[i].payload_state); - if (mgr->payloads[i].payload_state == DP_PAYLOAD_LOCAL) { - ret = drm_dp_create_payload_step2(mgr, port, mgr->proposed_vcpis[i]->vcpi, &mgr->payloads[i]); - } else if (mgr->payloads[i].payload_state == DP_PAYLOAD_DELETE_LOCAL) { - ret = drm_dp_destroy_payload_step2(mgr, mgr->proposed_vcpis[i]->vcpi, &mgr->payloads[i]); - } - if (ret) { - mutex_unlock(&mgr->payload_lock); - return ret; - } - } - mutex_unlock(&mgr->payload_lock); - return 0; -} -EXPORT_SYMBOL(drm_dp_update_payload_part2); - -static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - int offset, int size, u8 *bytes) -{ - int ret = 0; - struct drm_dp_sideband_msg_tx *txmsg; - struct drm_dp_mst_branch *mstb; - - mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); - if (!mstb) - return -EINVAL; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) { - ret = -ENOMEM; - goto fail_put; - } - - build_dpcd_read(txmsg, port->port_num, offset, size); - txmsg->dst = port->parent; - - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - 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); - ret = -EIO; - goto fail_free; - } - - if (txmsg->reply.u.remote_dpcd_read_ack.num_bytes != size) { - ret = -EPROTO; - goto fail_free; - } - - ret = min_t(size_t, txmsg->reply.u.remote_dpcd_read_ack.num_bytes, - size); - memcpy(bytes, txmsg->reply.u.remote_dpcd_read_ack.bytes, ret); - -fail_free: - kfree(txmsg); -fail_put: - drm_dp_mst_topology_put_mstb(mstb); - - return ret; -} - -static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, - int offset, int size, u8 *bytes) -{ - int ret; - struct drm_dp_sideband_msg_tx *txmsg; - struct drm_dp_mst_branch *mstb; - - mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); - if (!mstb) - return -EINVAL; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) { - ret = -ENOMEM; - goto fail_put; - } - - build_dpcd_write(txmsg, port->port_num, offset, size, bytes); - txmsg->dst = mstb; - - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret > 0) { - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) - ret = -EIO; - else - ret = size; - } - - kfree(txmsg); -fail_put: - drm_dp_mst_topology_put_mstb(mstb); - return ret; -} - -static int drm_dp_encode_up_ack_reply(struct drm_dp_sideband_msg_tx *msg, u8 req_type) -{ - struct drm_dp_sideband_msg_reply_body reply; - - reply.reply_type = DP_SIDEBAND_REPLY_ACK; - reply.req_type = req_type; - drm_dp_encode_sideband_reply(&reply, msg); - return 0; -} - -static int drm_dp_send_up_ack_reply(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_branch *mstb, - int req_type, bool broadcast) -{ - struct drm_dp_sideband_msg_tx *txmsg; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) - return -ENOMEM; - - txmsg->dst = mstb; - drm_dp_encode_up_ack_reply(txmsg, req_type); - - mutex_lock(&mgr->qlock); - /* construct a chunk from the first msg in the tx_msg queue */ - process_single_tx_qlock(mgr, txmsg, true); - mutex_unlock(&mgr->qlock); - - kfree(txmsg); - return 0; -} - -/** - * drm_dp_get_vc_payload_bw - get the VC payload BW for an MST link - * @mgr: The &drm_dp_mst_topology_mgr to use - * @link_rate: link rate in 10kbits/s units - * @link_lane_count: lane count - * - * Calculate the total bandwidth of a MultiStream Transport link. The returned - * value is in units of PBNs/(timeslots/1 MTP). This value can be used to - * convert the number of PBNs required for a given stream to the number of - * timeslots this stream requires in each MTP. - */ -int drm_dp_get_vc_payload_bw(const struct drm_dp_mst_topology_mgr *mgr, - int link_rate, int link_lane_count) -{ - if (link_rate == 0 || link_lane_count == 0) - drm_dbg_kms(mgr->dev, "invalid link rate/lane count: (%d / %d)\n", - link_rate, link_lane_count); - - /* See DP v2.0 2.6.4.2, VCPayload_Bandwidth_for_OneTimeSlotPer_MTP_Allocation */ - return link_rate * link_lane_count / 54000; -} -EXPORT_SYMBOL(drm_dp_get_vc_payload_bw); - -/** - * drm_dp_read_mst_cap() - check whether or not a sink supports MST - * @aux: The DP AUX channel to use - * @dpcd: A cached copy of the DPCD capabilities for this sink - * - * Returns: %True if the sink supports MST, %false otherwise - */ -bool drm_dp_read_mst_cap(struct drm_dp_aux *aux, - const u8 dpcd[DP_RECEIVER_CAP_SIZE]) -{ - u8 mstm_cap; - - if (dpcd[DP_DPCD_REV] < DP_DPCD_REV_12) - return false; - - if (drm_dp_dpcd_readb(aux, DP_MSTM_CAP, &mstm_cap) != 1) - return false; - - return mstm_cap & DP_MST_CAP; -} -EXPORT_SYMBOL(drm_dp_read_mst_cap); - -/** - * drm_dp_mst_topology_mgr_set_mst() - Set the MST state for a topology manager - * @mgr: manager to set state for - * @mst_state: true to enable MST on this connector - false to disable. - * - * This is called by the driver when it detects an MST capable device plugged - * into a DP MST capable port, or when a DP MST capable device is unplugged. - */ -int drm_dp_mst_topology_mgr_set_mst(struct drm_dp_mst_topology_mgr *mgr, bool mst_state) -{ - int ret = 0; - struct drm_dp_mst_branch *mstb = NULL; - - mutex_lock(&mgr->payload_lock); - mutex_lock(&mgr->lock); - if (mst_state == mgr->mst_state) - goto out_unlock; - - mgr->mst_state = mst_state; - /* set the device into MST mode */ - if (mst_state) { - struct drm_dp_payload reset_pay; - int lane_count; - int link_rate; - - WARN_ON(mgr->mst_primary); - - /* get dpcd info */ - ret = drm_dp_read_dpcd_caps(mgr->aux, mgr->dpcd); - if (ret < 0) { - drm_dbg_kms(mgr->dev, "%s: failed to read DPCD, ret %d\n", - mgr->aux->name, ret); - goto out_unlock; - } - - lane_count = min_t(int, mgr->dpcd[2] & DP_MAX_LANE_COUNT_MASK, mgr->max_lane_count); - link_rate = min_t(int, drm_dp_bw_code_to_link_rate(mgr->dpcd[1]), mgr->max_link_rate); - mgr->pbn_div = drm_dp_get_vc_payload_bw(mgr, - link_rate, - lane_count); - if (mgr->pbn_div == 0) { - ret = -EINVAL; - goto out_unlock; - } - - /* add initial branch device at LCT 1 */ - mstb = drm_dp_add_mst_branch_device(1, NULL); - if (mstb == NULL) { - ret = -ENOMEM; - goto out_unlock; - } - mstb->mgr = mgr; - - /* give this the main reference */ - mgr->mst_primary = mstb; - drm_dp_mst_topology_get_mstb(mgr->mst_primary); - - ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, - DP_MST_EN | - DP_UP_REQ_EN | - DP_UPSTREAM_IS_SRC); - if (ret < 0) - goto out_unlock; - - reset_pay.start_slot = 0; - reset_pay.num_slots = 0x3f; - drm_dp_dpcd_write_payload(mgr, 0, &reset_pay); - - queue_work(system_long_wq, &mgr->work); - - ret = 0; - } else { - /* disable MST on the device */ - mstb = mgr->mst_primary; - mgr->mst_primary = NULL; - /* this can fail if the device is gone */ - drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, 0); - ret = 0; - memset(mgr->payloads, 0, - mgr->max_payloads * sizeof(mgr->payloads[0])); - memset(mgr->proposed_vcpis, 0, - mgr->max_payloads * sizeof(mgr->proposed_vcpis[0])); - mgr->payload_mask = 0; - set_bit(0, &mgr->payload_mask); - mgr->vcpi_mask = 0; - mgr->payload_id_table_cleared = false; - } - -out_unlock: - mutex_unlock(&mgr->lock); - mutex_unlock(&mgr->payload_lock); - if (mstb) - drm_dp_mst_topology_put_mstb(mstb); - return ret; - -} -EXPORT_SYMBOL(drm_dp_mst_topology_mgr_set_mst); - -static void -drm_dp_mst_topology_mgr_invalidate_mstb(struct drm_dp_mst_branch *mstb) -{ - struct drm_dp_mst_port *port; - - /* The link address will need to be re-sent on resume */ - mstb->link_address_sent = false; - - list_for_each_entry(port, &mstb->ports, next) - if (port->mstb) - drm_dp_mst_topology_mgr_invalidate_mstb(port->mstb); -} - -/** - * drm_dp_mst_topology_mgr_suspend() - suspend the MST manager - * @mgr: manager to suspend - * - * This function tells the MST device that we can't handle UP messages - * anymore. This should stop it from sending any since we are suspended. - */ -void drm_dp_mst_topology_mgr_suspend(struct drm_dp_mst_topology_mgr *mgr) -{ - mutex_lock(&mgr->lock); - drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, - DP_MST_EN | DP_UPSTREAM_IS_SRC); - mutex_unlock(&mgr->lock); - flush_work(&mgr->up_req_work); - flush_work(&mgr->work); - flush_work(&mgr->delayed_destroy_work); - - mutex_lock(&mgr->lock); - if (mgr->mst_state && mgr->mst_primary) - drm_dp_mst_topology_mgr_invalidate_mstb(mgr->mst_primary); - mutex_unlock(&mgr->lock); -} -EXPORT_SYMBOL(drm_dp_mst_topology_mgr_suspend); - -/** - * drm_dp_mst_topology_mgr_resume() - resume the MST manager - * @mgr: manager to resume - * @sync: whether or not to perform topology reprobing synchronously - * - * This will fetch DPCD and see if the device is still there, - * if it is, it will rewrite the MSTM control bits, and return. - * - * If the device fails this returns -1, and the driver should do - * a full MST reprobe, in case we were undocked. - * - * During system resume (where it is assumed that the driver will be calling - * drm_atomic_helper_resume()) this function should be called beforehand with - * @sync set to true. In contexts like runtime resume where the driver is not - * expected to be calling drm_atomic_helper_resume(), this function should be - * called with @sync set to false in order to avoid deadlocking. - * - * Returns: -1 if the MST topology was removed while we were suspended, 0 - * otherwise. - */ -int drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr, - bool sync) -{ - int ret; - u8 guid[16]; - - mutex_lock(&mgr->lock); - if (!mgr->mst_primary) - goto out_fail; - - ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, mgr->dpcd, - DP_RECEIVER_CAP_SIZE); - if (ret != DP_RECEIVER_CAP_SIZE) { - drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n"); - goto out_fail; - } - - ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL, - DP_MST_EN | - DP_UP_REQ_EN | - DP_UPSTREAM_IS_SRC); - if (ret < 0) { - drm_dbg_kms(mgr->dev, "mst write failed - undocked during suspend?\n"); - goto out_fail; - } - - /* Some hubs forget their guids after they resume */ - ret = drm_dp_dpcd_read(mgr->aux, DP_GUID, guid, 16); - if (ret != 16) { - drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n"); - goto out_fail; - } - - ret = drm_dp_check_mstb_guid(mgr->mst_primary, guid); - if (ret) { - drm_dbg_kms(mgr->dev, "check mstb failed - undocked during suspend?\n"); - goto out_fail; - } - - /* - * For the final step of resuming the topology, we need to bring the - * state of our in-memory topology back into sync with reality. So, - * restart the probing process as if we're probing a new hub - */ - queue_work(system_long_wq, &mgr->work); - mutex_unlock(&mgr->lock); - - if (sync) { - drm_dbg_kms(mgr->dev, - "Waiting for link probe work to finish re-syncing topology...\n"); - flush_work(&mgr->work); - } - - return 0; - -out_fail: - mutex_unlock(&mgr->lock); - return -1; -} -EXPORT_SYMBOL(drm_dp_mst_topology_mgr_resume); - -static bool -drm_dp_get_one_sb_msg(struct drm_dp_mst_topology_mgr *mgr, bool up, - struct drm_dp_mst_branch **mstb) -{ - int len; - u8 replyblock[32]; - int replylen, curreply; - int ret; - u8 hdrlen; - struct drm_dp_sideband_msg_hdr hdr; - struct drm_dp_sideband_msg_rx *msg = - up ? &mgr->up_req_recv : &mgr->down_rep_recv; - int basereg = up ? DP_SIDEBAND_MSG_UP_REQ_BASE : - DP_SIDEBAND_MSG_DOWN_REP_BASE; - - if (!up) - *mstb = NULL; - - len = min(mgr->max_dpcd_transaction_bytes, 16); - ret = drm_dp_dpcd_read(mgr->aux, basereg, replyblock, len); - if (ret != len) { - drm_dbg_kms(mgr->dev, "failed to read DPCD down rep %d %d\n", len, ret); - return false; - } - - ret = drm_dp_decode_sideband_msg_hdr(mgr, &hdr, replyblock, len, &hdrlen); - if (ret == false) { - print_hex_dump(KERN_DEBUG, "failed hdr", DUMP_PREFIX_NONE, 16, - 1, replyblock, len, false); - drm_dbg_kms(mgr->dev, "ERROR: failed header\n"); - return false; - } - - if (!up) { - /* Caller is responsible for giving back this reference */ - *mstb = drm_dp_get_mst_branch_device(mgr, hdr.lct, hdr.rad); - if (!*mstb) { - drm_dbg_kms(mgr->dev, "Got MST reply from unknown device %d\n", hdr.lct); - return false; - } - } - - if (!drm_dp_sideband_msg_set_header(msg, &hdr, hdrlen)) { - drm_dbg_kms(mgr->dev, "sideband msg set header failed %d\n", replyblock[0]); - return false; - } - - replylen = min(msg->curchunk_len, (u8)(len - hdrlen)); - ret = drm_dp_sideband_append_payload(msg, replyblock + hdrlen, replylen); - if (!ret) { - drm_dbg_kms(mgr->dev, "sideband msg build failed %d\n", replyblock[0]); - return false; - } - - replylen = msg->curchunk_len + msg->curchunk_hdrlen - len; - curreply = len; - while (replylen > 0) { - len = min3(replylen, mgr->max_dpcd_transaction_bytes, 16); - ret = drm_dp_dpcd_read(mgr->aux, basereg + curreply, - replyblock, len); - if (ret != len) { - drm_dbg_kms(mgr->dev, "failed to read a chunk (len %d, ret %d)\n", - len, ret); - return false; - } - - ret = drm_dp_sideband_append_payload(msg, replyblock, len); - if (!ret) { - drm_dbg_kms(mgr->dev, "failed to build sideband msg\n"); - return false; - } - - curreply += len; - replylen -= len; - } - return true; -} - -static int drm_dp_mst_handle_down_rep(struct drm_dp_mst_topology_mgr *mgr) -{ - struct drm_dp_sideband_msg_tx *txmsg; - struct drm_dp_mst_branch *mstb = NULL; - struct drm_dp_sideband_msg_rx *msg = &mgr->down_rep_recv; - - if (!drm_dp_get_one_sb_msg(mgr, false, &mstb)) - goto out; - - /* Multi-packet message transmission, don't clear the reply */ - if (!msg->have_eomt) - goto out; - - /* find the message */ - mutex_lock(&mgr->qlock); - txmsg = list_first_entry_or_null(&mgr->tx_msg_downq, - struct drm_dp_sideband_msg_tx, next); - mutex_unlock(&mgr->qlock); - - /* Were we actually expecting a response, and from this mstb? */ - if (!txmsg || txmsg->dst != mstb) { - struct drm_dp_sideband_msg_hdr *hdr; - - hdr = &msg->initial_hdr; - drm_dbg_kms(mgr->dev, "Got MST reply with no msg %p %d %d %02x %02x\n", - mstb, hdr->seqno, hdr->lct, hdr->rad[0], msg->msg[0]); - goto out_clear_reply; - } - - drm_dp_sideband_parse_reply(mgr, msg, &txmsg->reply); - - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { - drm_dbg_kms(mgr->dev, - "Got NAK reply: req 0x%02x (%s), reason 0x%02x (%s), nak data 0x%02x\n", - txmsg->reply.req_type, - drm_dp_mst_req_type_str(txmsg->reply.req_type), - txmsg->reply.u.nak.reason, - drm_dp_mst_nak_reason_str(txmsg->reply.u.nak.reason), - txmsg->reply.u.nak.nak_data); - } - - memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx)); - drm_dp_mst_topology_put_mstb(mstb); - - mutex_lock(&mgr->qlock); - txmsg->state = DRM_DP_SIDEBAND_TX_RX; - list_del(&txmsg->next); - mutex_unlock(&mgr->qlock); - - wake_up_all(&mgr->tx_waitq); - - return 0; - -out_clear_reply: - memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx)); -out: - if (mstb) - drm_dp_mst_topology_put_mstb(mstb); - - return 0; -} - -static inline bool -drm_dp_mst_process_up_req(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_pending_up_req *up_req) -{ - struct drm_dp_mst_branch *mstb = NULL; - struct drm_dp_sideband_msg_req_body *msg = &up_req->msg; - struct drm_dp_sideband_msg_hdr *hdr = &up_req->hdr; - bool hotplug = false; - - if (hdr->broadcast) { - const u8 *guid = NULL; - - if (msg->req_type == DP_CONNECTION_STATUS_NOTIFY) - guid = msg->u.conn_stat.guid; - else if (msg->req_type == DP_RESOURCE_STATUS_NOTIFY) - guid = msg->u.resource_stat.guid; - - if (guid) - mstb = drm_dp_get_mst_branch_device_by_guid(mgr, guid); - } else { - mstb = drm_dp_get_mst_branch_device(mgr, hdr->lct, hdr->rad); - } - - if (!mstb) { - drm_dbg_kms(mgr->dev, "Got MST reply from unknown device %d\n", hdr->lct); - return false; - } - - /* TODO: Add missing handler for DP_RESOURCE_STATUS_NOTIFY events */ - if (msg->req_type == DP_CONNECTION_STATUS_NOTIFY) { - drm_dp_mst_handle_conn_stat(mstb, &msg->u.conn_stat); - hotplug = true; - } - - drm_dp_mst_topology_put_mstb(mstb); - return hotplug; -} - -static void drm_dp_mst_up_req_work(struct work_struct *work) -{ - struct drm_dp_mst_topology_mgr *mgr = - container_of(work, struct drm_dp_mst_topology_mgr, - up_req_work); - struct drm_dp_pending_up_req *up_req; - bool send_hotplug = false; - - mutex_lock(&mgr->probe_lock); - while (true) { - mutex_lock(&mgr->up_req_lock); - up_req = list_first_entry_or_null(&mgr->up_req_list, - struct drm_dp_pending_up_req, - next); - if (up_req) - list_del(&up_req->next); - mutex_unlock(&mgr->up_req_lock); - - if (!up_req) - break; - - send_hotplug |= drm_dp_mst_process_up_req(mgr, up_req); - kfree(up_req); - } - mutex_unlock(&mgr->probe_lock); - - if (send_hotplug) - drm_kms_helper_hotplug_event(mgr->dev); -} - -static int drm_dp_mst_handle_up_req(struct drm_dp_mst_topology_mgr *mgr) -{ - struct drm_dp_pending_up_req *up_req; - - if (!drm_dp_get_one_sb_msg(mgr, true, NULL)) - goto out; - - if (!mgr->up_req_recv.have_eomt) - return 0; - - up_req = kzalloc(sizeof(*up_req), GFP_KERNEL); - if (!up_req) - return -ENOMEM; - - INIT_LIST_HEAD(&up_req->next); - - drm_dp_sideband_parse_req(mgr, &mgr->up_req_recv, &up_req->msg); - - if (up_req->msg.req_type != DP_CONNECTION_STATUS_NOTIFY && - up_req->msg.req_type != DP_RESOURCE_STATUS_NOTIFY) { - drm_dbg_kms(mgr->dev, "Received unknown up req type, ignoring: %x\n", - up_req->msg.req_type); - kfree(up_req); - goto out; - } - - drm_dp_send_up_ack_reply(mgr, mgr->mst_primary, up_req->msg.req_type, - false); - - if (up_req->msg.req_type == DP_CONNECTION_STATUS_NOTIFY) { - const struct drm_dp_connection_status_notify *conn_stat = - &up_req->msg.u.conn_stat; - - drm_dbg_kms(mgr->dev, "Got CSN: pn: %d ldps:%d ddps: %d mcs: %d ip: %d pdt: %d\n", - conn_stat->port_number, - conn_stat->legacy_device_plug_status, - conn_stat->displayport_device_plug_status, - conn_stat->message_capability_status, - conn_stat->input_port, - conn_stat->peer_device_type); - } else if (up_req->msg.req_type == DP_RESOURCE_STATUS_NOTIFY) { - const struct drm_dp_resource_status_notify *res_stat = - &up_req->msg.u.resource_stat; - - drm_dbg_kms(mgr->dev, "Got RSN: pn: %d avail_pbn %d\n", - res_stat->port_number, - res_stat->available_pbn); - } - - up_req->hdr = mgr->up_req_recv.initial_hdr; - mutex_lock(&mgr->up_req_lock); - list_add_tail(&up_req->next, &mgr->up_req_list); - mutex_unlock(&mgr->up_req_lock); - queue_work(system_long_wq, &mgr->up_req_work); - -out: - memset(&mgr->up_req_recv, 0, sizeof(struct drm_dp_sideband_msg_rx)); - return 0; -} - -/** - * drm_dp_mst_hpd_irq() - MST hotplug IRQ notify - * @mgr: manager to notify irq for. - * @esi: 4 bytes from SINK_COUNT_ESI - * @handled: whether the hpd interrupt was consumed or not - * - * This should be called from the driver when it detects a short IRQ, - * along with the value of the DEVICE_SERVICE_IRQ_VECTOR_ESI0. The - * topology manager will process the sideband messages received as a result - * of this. - */ -int drm_dp_mst_hpd_irq(struct drm_dp_mst_topology_mgr *mgr, u8 *esi, bool *handled) -{ - int ret = 0; - int sc; - *handled = false; - sc = DP_GET_SINK_COUNT(esi[0]); - - if (sc != mgr->sink_count) { - mgr->sink_count = sc; - *handled = true; - } - - if (esi[1] & DP_DOWN_REP_MSG_RDY) { - ret = drm_dp_mst_handle_down_rep(mgr); - *handled = true; - } - - if (esi[1] & DP_UP_REQ_MSG_RDY) { - ret |= drm_dp_mst_handle_up_req(mgr); - *handled = true; - } - - drm_dp_mst_kick_tx(mgr); - return ret; -} -EXPORT_SYMBOL(drm_dp_mst_hpd_irq); - -/** - * drm_dp_mst_detect_port() - get connection status for an MST port - * @connector: DRM connector for this port - * @ctx: The acquisition context to use for grabbing locks - * @mgr: manager for this port - * @port: pointer to a port - * - * This returns the current connection state for a port. - */ -int -drm_dp_mst_detect_port(struct drm_connector *connector, - struct drm_modeset_acquire_ctx *ctx, - struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port) -{ - int ret; - - /* we need to search for the port in the mgr in case it's gone */ - port = drm_dp_mst_topology_get_port_validated(mgr, port); - if (!port) - return connector_status_disconnected; - - ret = drm_modeset_lock(&mgr->base.lock, ctx); - if (ret) - goto out; - - ret = connector_status_disconnected; - - if (!port->ddps) - goto out; - - switch (port->pdt) { - case DP_PEER_DEVICE_NONE: - break; - case DP_PEER_DEVICE_MST_BRANCHING: - if (!port->mcs) - ret = connector_status_connected; - break; - - case DP_PEER_DEVICE_SST_SINK: - ret = connector_status_connected; - /* for logical ports - cache the EDID */ - if (port->port_num >= DP_MST_LOGICAL_PORT_0 && !port->cached_edid) - port->cached_edid = drm_get_edid(connector, &port->aux.ddc); - break; - case DP_PEER_DEVICE_DP_LEGACY_CONV: - if (port->ldps) - ret = connector_status_connected; - break; - } -out: - drm_dp_mst_topology_put_port(port); - return ret; -} -EXPORT_SYMBOL(drm_dp_mst_detect_port); - -/** - * drm_dp_mst_get_edid() - get EDID for an MST port - * @connector: toplevel connector to get EDID for - * @mgr: manager for this port - * @port: unverified pointer to a port. - * - * This returns an EDID for the port connected to a connector, - * It validates the pointer still exists so the caller doesn't require a - * reference. - */ -struct edid *drm_dp_mst_get_edid(struct drm_connector *connector, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) -{ - struct edid *edid = NULL; - - /* we need to search for the port in the mgr in case it's gone */ - port = drm_dp_mst_topology_get_port_validated(mgr, port); - if (!port) - return NULL; - - if (port->cached_edid) - edid = drm_edid_duplicate(port->cached_edid); - else { - edid = drm_get_edid(connector, &port->aux.ddc); - } - port->has_audio = drm_detect_monitor_audio(edid); - drm_dp_mst_topology_put_port(port); - return edid; -} -EXPORT_SYMBOL(drm_dp_mst_get_edid); - -/** - * drm_dp_find_vcpi_slots() - Find VCPI slots for this PBN value - * @mgr: manager to use - * @pbn: payload bandwidth to convert into slots. - * - * Calculate the number of VCPI slots that will be required for the given PBN - * value. This function is deprecated, and should not be used in atomic - * drivers. - * - * RETURNS: - * The total slots required for this port, or error. - */ -int drm_dp_find_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, - int pbn) -{ - int num_slots; - - num_slots = DIV_ROUND_UP(pbn, mgr->pbn_div); - - /* max. time slots - one slot for MTP header */ - if (num_slots > 63) - return -ENOSPC; - return num_slots; -} -EXPORT_SYMBOL(drm_dp_find_vcpi_slots); - -static int drm_dp_init_vcpi(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_vcpi *vcpi, int pbn, int slots) -{ - int ret; - - vcpi->pbn = pbn; - vcpi->aligned_pbn = slots * mgr->pbn_div; - vcpi->num_slots = slots; - - ret = drm_dp_mst_assign_payload_id(mgr, vcpi); - if (ret < 0) - return ret; - return 0; -} - -/** - * drm_dp_atomic_find_vcpi_slots() - Find and add VCPI slots to the state - * @state: global atomic state - * @mgr: MST topology manager for the port - * @port: port to find vcpi slots for - * @pbn: bandwidth required for the mode in PBN - * @pbn_div: divider for DSC mode that takes FEC into account - * - * Allocates VCPI slots to @port, replacing any previous VCPI allocations it - * may have had. Any atomic drivers which support MST must call this function - * in their &drm_encoder_helper_funcs.atomic_check() callback to change the - * current VCPI allocation for the new state, but only when - * &drm_crtc_state.mode_changed or &drm_crtc_state.connectors_changed is set - * to ensure compatibility with userspace applications that still use the - * legacy modesetting UAPI. - * - * Allocations set by this function are not checked against the bandwidth - * restraints of @mgr until the driver calls drm_dp_mst_atomic_check(). - * - * Additionally, it is OK to call this function multiple times on the same - * @port as needed. It is not OK however, to call this function and - * drm_dp_atomic_release_vcpi_slots() in the same atomic check phase. - * - * See also: - * drm_dp_atomic_release_vcpi_slots() - * drm_dp_mst_atomic_check() - * - * Returns: - * Total slots in the atomic state assigned for this port, or a negative error - * code if the port no longer exists - */ -int drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state *state, - struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, int pbn, - int pbn_div) -{ - struct drm_dp_mst_topology_state *topology_state; - struct drm_dp_vcpi_allocation *pos, *vcpi = NULL; - int prev_slots, prev_bw, req_slots; - - topology_state = drm_atomic_get_mst_topology_state(state, mgr); - if (IS_ERR(topology_state)) - return PTR_ERR(topology_state); - - /* Find the current allocation for this port, if any */ - list_for_each_entry(pos, &topology_state->vcpis, next) { - if (pos->port == port) { - vcpi = pos; - prev_slots = vcpi->vcpi; - prev_bw = vcpi->pbn; - - /* - * This should never happen, unless the driver tries - * releasing and allocating the same VCPI allocation, - * which is an error - */ - if (WARN_ON(!prev_slots)) { - drm_err(mgr->dev, - "cannot allocate and release VCPI on [MST PORT:%p] in the same state\n", - port); - return -EINVAL; - } - - break; - } - } - if (!vcpi) { - prev_slots = 0; - prev_bw = 0; - } - - if (pbn_div <= 0) - pbn_div = mgr->pbn_div; - - req_slots = DIV_ROUND_UP(pbn, pbn_div); - - drm_dbg_atomic(mgr->dev, "[CONNECTOR:%d:%s] [MST PORT:%p] VCPI %d -> %d\n", - port->connector->base.id, port->connector->name, - port, prev_slots, req_slots); - drm_dbg_atomic(mgr->dev, "[CONNECTOR:%d:%s] [MST PORT:%p] PBN %d -> %d\n", - port->connector->base.id, port->connector->name, - port, prev_bw, pbn); - - /* Add the new allocation to the state */ - if (!vcpi) { - vcpi = kzalloc(sizeof(*vcpi), GFP_KERNEL); - if (!vcpi) - return -ENOMEM; - - drm_dp_mst_get_port_malloc(port); - vcpi->port = port; - list_add(&vcpi->next, &topology_state->vcpis); - } - vcpi->vcpi = req_slots; - vcpi->pbn = pbn; - - return req_slots; -} -EXPORT_SYMBOL(drm_dp_atomic_find_vcpi_slots); - -/** - * drm_dp_atomic_release_vcpi_slots() - Release allocated vcpi slots - * @state: global atomic state - * @mgr: MST topology manager for the port - * @port: The port to release the VCPI slots from - * - * Releases any VCPI slots that have been allocated to a port in the atomic - * state. Any atomic drivers which support MST must call this function in - * their &drm_connector_helper_funcs.atomic_check() callback when the - * connector will no longer have VCPI allocated (e.g. because its CRTC was - * removed) when it had VCPI allocated in the previous atomic state. - * - * It is OK to call this even if @port has been removed from the system. - * Additionally, it is OK to call this function multiple times on the same - * @port as needed. It is not OK however, to call this function and - * drm_dp_atomic_find_vcpi_slots() on the same @port in a single atomic check - * phase. - * - * See also: - * drm_dp_atomic_find_vcpi_slots() - * drm_dp_mst_atomic_check() - * - * Returns: - * 0 if all slots for this port were added back to - * &drm_dp_mst_topology_state.avail_slots or negative error code - */ -int drm_dp_atomic_release_vcpi_slots(struct drm_atomic_state *state, - struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port) -{ - struct drm_dp_mst_topology_state *topology_state; - struct drm_dp_vcpi_allocation *pos; - bool found = false; - - topology_state = drm_atomic_get_mst_topology_state(state, mgr); - if (IS_ERR(topology_state)) - return PTR_ERR(topology_state); - - list_for_each_entry(pos, &topology_state->vcpis, next) { - if (pos->port == port) { - found = true; - break; - } - } - if (WARN_ON(!found)) { - drm_err(mgr->dev, "no VCPI for [MST PORT:%p] found in mst state %p\n", - port, &topology_state->base); - return -EINVAL; - } - - drm_dbg_atomic(mgr->dev, "[MST PORT:%p] VCPI %d -> 0\n", port, pos->vcpi); - if (pos->vcpi) { - drm_dp_mst_put_port_malloc(port); - pos->vcpi = 0; - pos->pbn = 0; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_atomic_release_vcpi_slots); - -/** - * drm_dp_mst_update_slots() - updates the slot info depending on the DP ecoding format - * @mst_state: mst_state to update - * @link_encoding_cap: the ecoding format on the link - */ -void drm_dp_mst_update_slots(struct drm_dp_mst_topology_state *mst_state, uint8_t link_encoding_cap) -{ - if (link_encoding_cap == DP_CAP_ANSI_128B132B) { - mst_state->total_avail_slots = 64; - mst_state->start_slot = 0; - } else { - mst_state->total_avail_slots = 63; - mst_state->start_slot = 1; - } - - DRM_DEBUG_KMS("%s encoding format on mst_state 0x%p\n", - (link_encoding_cap == DP_CAP_ANSI_128B132B) ? "128b/132b":"8b/10b", - mst_state); -} -EXPORT_SYMBOL(drm_dp_mst_update_slots); - -/** - * drm_dp_mst_allocate_vcpi() - Allocate a virtual channel - * @mgr: manager for this port - * @port: port to allocate a virtual channel for. - * @pbn: payload bandwidth number to request - * @slots: returned number of slots for this PBN. - */ -bool drm_dp_mst_allocate_vcpi(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, int pbn, int slots) -{ - int ret; - - if (slots < 0) - return false; - - port = drm_dp_mst_topology_get_port_validated(mgr, port); - if (!port) - return false; - - if (port->vcpi.vcpi > 0) { - drm_dbg_kms(mgr->dev, - "payload: vcpi %d already allocated for pbn %d - requested pbn %d\n", - port->vcpi.vcpi, port->vcpi.pbn, pbn); - if (pbn == port->vcpi.pbn) { - drm_dp_mst_topology_put_port(port); - return true; - } - } - - ret = drm_dp_init_vcpi(mgr, &port->vcpi, pbn, slots); - if (ret) { - drm_dbg_kms(mgr->dev, "failed to init vcpi slots=%d ret=%d\n", - DIV_ROUND_UP(pbn, mgr->pbn_div), ret); - drm_dp_mst_topology_put_port(port); - goto out; - } - drm_dbg_kms(mgr->dev, "initing vcpi for pbn=%d slots=%d\n", pbn, port->vcpi.num_slots); - - /* Keep port allocated until its payload has been removed */ - drm_dp_mst_get_port_malloc(port); - drm_dp_mst_topology_put_port(port); - return true; -out: - return false; -} -EXPORT_SYMBOL(drm_dp_mst_allocate_vcpi); - -int drm_dp_mst_get_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) -{ - int slots = 0; - - port = drm_dp_mst_topology_get_port_validated(mgr, port); - if (!port) - return slots; - - slots = port->vcpi.num_slots; - drm_dp_mst_topology_put_port(port); - return slots; -} -EXPORT_SYMBOL(drm_dp_mst_get_vcpi_slots); - -/** - * drm_dp_mst_reset_vcpi_slots() - Reset number of slots to 0 for VCPI - * @mgr: manager for this port - * @port: unverified pointer to a port. - * - * This just resets the number of slots for the ports VCPI for later programming. - */ -void drm_dp_mst_reset_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port) -{ - /* - * A port with VCPI will remain allocated until its VCPI is - * released, no verified ref needed - */ - - port->vcpi.num_slots = 0; -} -EXPORT_SYMBOL(drm_dp_mst_reset_vcpi_slots); - -/** - * drm_dp_mst_deallocate_vcpi() - deallocate a VCPI - * @mgr: manager for this port - * @port: port to deallocate vcpi for - * - * This can be called unconditionally, regardless of whether - * drm_dp_mst_allocate_vcpi() succeeded or not. - */ -void drm_dp_mst_deallocate_vcpi(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port) -{ - bool skip; - - if (!port->vcpi.vcpi) - return; - - mutex_lock(&mgr->lock); - skip = !drm_dp_mst_port_downstream_of_branch(port, mgr->mst_primary); - mutex_unlock(&mgr->lock); - - if (skip) - return; - - drm_dp_mst_put_payload_id(mgr, port->vcpi.vcpi); - port->vcpi.num_slots = 0; - port->vcpi.pbn = 0; - port->vcpi.aligned_pbn = 0; - port->vcpi.vcpi = 0; - drm_dp_mst_put_port_malloc(port); -} -EXPORT_SYMBOL(drm_dp_mst_deallocate_vcpi); - -static int drm_dp_dpcd_write_payload(struct drm_dp_mst_topology_mgr *mgr, - int id, struct drm_dp_payload *payload) -{ - u8 payload_alloc[3], status; - int ret; - int retries = 0; - - drm_dp_dpcd_writeb(mgr->aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, - DP_PAYLOAD_TABLE_UPDATED); - - payload_alloc[0] = id; - payload_alloc[1] = payload->start_slot; - payload_alloc[2] = payload->num_slots; - - ret = drm_dp_dpcd_write(mgr->aux, DP_PAYLOAD_ALLOCATE_SET, payload_alloc, 3); - if (ret != 3) { - drm_dbg_kms(mgr->dev, "failed to write payload allocation %d\n", ret); - goto fail; - } - -retry: - ret = drm_dp_dpcd_readb(mgr->aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status); - if (ret < 0) { - drm_dbg_kms(mgr->dev, "failed to read payload table status %d\n", ret); - goto fail; - } - - if (!(status & DP_PAYLOAD_TABLE_UPDATED)) { - retries++; - if (retries < 20) { - usleep_range(10000, 20000); - goto retry; - } - drm_dbg_kms(mgr->dev, "status not set after read payload table status %d\n", - status); - ret = -EINVAL; - goto fail; - } - ret = 0; -fail: - return ret; -} - -static int do_get_act_status(struct drm_dp_aux *aux) -{ - int ret; - u8 status; - - ret = drm_dp_dpcd_readb(aux, DP_PAYLOAD_TABLE_UPDATE_STATUS, &status); - if (ret < 0) - return ret; - - return status; -} - -/** - * drm_dp_check_act_status() - Polls for ACT handled status. - * @mgr: manager to use - * - * Tries waiting for the MST hub to finish updating it's payload table by - * polling for the ACT handled bit for up to 3 seconds (yes-some hubs really - * take that long). - * - * Returns: - * 0 if the ACT was handled in time, negative error code on failure. - */ -int drm_dp_check_act_status(struct drm_dp_mst_topology_mgr *mgr) -{ - /* - * There doesn't seem to be any recommended retry count or timeout in - * the MST specification. Since some hubs have been observed to take - * over 1 second to update their payload allocations under certain - * conditions, we use a rather large timeout value. - */ - const int timeout_ms = 3000; - int ret, status; - - ret = readx_poll_timeout(do_get_act_status, mgr->aux, status, - status & DP_PAYLOAD_ACT_HANDLED || status < 0, - 200, timeout_ms * USEC_PER_MSEC); - if (ret < 0 && status >= 0) { - drm_err(mgr->dev, "Failed to get ACT after %dms, last status: %02x\n", - timeout_ms, status); - return -EINVAL; - } else if (status < 0) { - /* - * Failure here isn't unexpected - the hub may have - * just been unplugged - */ - drm_dbg_kms(mgr->dev, "Failed to read payload table status: %d\n", status); - return status; - } - - return 0; -} -EXPORT_SYMBOL(drm_dp_check_act_status); - -/** - * drm_dp_calc_pbn_mode() - Calculate the PBN for a mode. - * @clock: dot clock for the mode - * @bpp: bpp for the mode. - * @dsc: DSC mode. If true, bpp has units of 1/16 of a bit per pixel - * - * This uses the formula in the spec to calculate the PBN value for a mode. - */ -int drm_dp_calc_pbn_mode(int clock, int bpp, bool dsc) -{ - /* - * margin 5300ppm + 300ppm ~ 0.6% as per spec, factor is 1.006 - * The unit of 54/64Mbytes/sec is an arbitrary unit chosen based on - * common multiplier to render an integer PBN for all link rate/lane - * counts combinations - * calculate - * peak_kbps *= (1006/1000) - * peak_kbps *= (64/54) - * peak_kbps *= 8 convert to bytes - * - * If the bpp is in units of 1/16, further divide by 16. Put this - * factor in the numerator rather than the denominator to avoid - * integer overflow - */ - - if (dsc) - return DIV_ROUND_UP_ULL(mul_u32_u32(clock * (bpp / 16), 64 * 1006), - 8 * 54 * 1000 * 1000); - - return DIV_ROUND_UP_ULL(mul_u32_u32(clock * bpp, 64 * 1006), - 8 * 54 * 1000 * 1000); -} -EXPORT_SYMBOL(drm_dp_calc_pbn_mode); - -/* we want to kick the TX after we've ack the up/down IRQs. */ -static void drm_dp_mst_kick_tx(struct drm_dp_mst_topology_mgr *mgr) -{ - queue_work(system_long_wq, &mgr->tx_work); -} - -/* - * Helper function for parsing DP device types into convenient strings - * for use with dp_mst_topology - */ -static const char *pdt_to_string(u8 pdt) -{ - switch (pdt) { - case DP_PEER_DEVICE_NONE: - return "NONE"; - case DP_PEER_DEVICE_SOURCE_OR_SST: - return "SOURCE OR SST"; - case DP_PEER_DEVICE_MST_BRANCHING: - return "MST BRANCHING"; - case DP_PEER_DEVICE_SST_SINK: - return "SST SINK"; - case DP_PEER_DEVICE_DP_LEGACY_CONV: - return "DP LEGACY CONV"; - default: - return "ERR"; - } -} - -static void drm_dp_mst_dump_mstb(struct seq_file *m, - struct drm_dp_mst_branch *mstb) -{ - struct drm_dp_mst_port *port; - int tabs = mstb->lct; - char prefix[10]; - int i; - - for (i = 0; i < tabs; i++) - prefix[i] = '\t'; - prefix[i] = '\0'; - - seq_printf(m, "%smstb - [%p]: num_ports: %d\n", prefix, mstb, mstb->num_ports); - list_for_each_entry(port, &mstb->ports, next) { - seq_printf(m, "%sport %d - [%p] (%s - %s): ddps: %d, ldps: %d, sdp: %d/%d, fec: %s, conn: %p\n", - prefix, - port->port_num, - port, - port->input ? "input" : "output", - pdt_to_string(port->pdt), - port->ddps, - port->ldps, - port->num_sdp_streams, - port->num_sdp_stream_sinks, - port->fec_capable ? "true" : "false", - port->connector); - if (port->mstb) - drm_dp_mst_dump_mstb(m, port->mstb); - } -} - -#define DP_PAYLOAD_TABLE_SIZE 64 - -static bool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr, - char *buf) -{ - int i; - - for (i = 0; i < DP_PAYLOAD_TABLE_SIZE; i += 16) { - if (drm_dp_dpcd_read(mgr->aux, - DP_PAYLOAD_TABLE_UPDATE_STATUS + i, - &buf[i], 16) != 16) - return false; - } - return true; -} - -static void fetch_monitor_name(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_port *port, char *name, - int namelen) -{ - struct edid *mst_edid; - - mst_edid = drm_dp_mst_get_edid(port->connector, mgr, port); - drm_edid_get_monitor_name(mst_edid, name, namelen); -} - -/** - * drm_dp_mst_dump_topology(): dump topology to seq file. - * @m: seq_file to dump output to - * @mgr: manager to dump current topology for. - * - * helper to dump MST topology to a seq file for debugfs. - */ -void drm_dp_mst_dump_topology(struct seq_file *m, - struct drm_dp_mst_topology_mgr *mgr) -{ - int i; - struct drm_dp_mst_port *port; - - mutex_lock(&mgr->lock); - if (mgr->mst_primary) - drm_dp_mst_dump_mstb(m, mgr->mst_primary); - - /* dump VCPIs */ - mutex_unlock(&mgr->lock); - - mutex_lock(&mgr->payload_lock); - seq_printf(m, "\n*** VCPI Info ***\n"); - seq_printf(m, "payload_mask: %lx, vcpi_mask: %lx, max_payloads: %d\n", mgr->payload_mask, mgr->vcpi_mask, mgr->max_payloads); - - seq_printf(m, "\n| idx | port # | vcp_id | # slots | sink name |\n"); - for (i = 0; i < mgr->max_payloads; i++) { - if (mgr->proposed_vcpis[i]) { - char name[14]; - - port = container_of(mgr->proposed_vcpis[i], struct drm_dp_mst_port, vcpi); - fetch_monitor_name(mgr, port, name, sizeof(name)); - seq_printf(m, "%10d%10d%10d%10d%20s\n", - i, - port->port_num, - port->vcpi.vcpi, - port->vcpi.num_slots, - (*name != 0) ? name : "Unknown"); - } else - seq_printf(m, "%6d - Unused\n", i); - } - seq_printf(m, "\n*** Payload Info ***\n"); - seq_printf(m, "| idx | state | start slot | # slots |\n"); - for (i = 0; i < mgr->max_payloads; i++) { - seq_printf(m, "%10d%10d%15d%10d\n", - i, - mgr->payloads[i].payload_state, - mgr->payloads[i].start_slot, - mgr->payloads[i].num_slots); - } - mutex_unlock(&mgr->payload_lock); - - seq_printf(m, "\n*** DPCD Info ***\n"); - mutex_lock(&mgr->lock); - if (mgr->mst_primary) { - u8 buf[DP_PAYLOAD_TABLE_SIZE]; - int ret; - - ret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, buf, DP_RECEIVER_CAP_SIZE); - if (ret) { - seq_printf(m, "dpcd read failed\n"); - goto out; - } - seq_printf(m, "dpcd: %*ph\n", DP_RECEIVER_CAP_SIZE, buf); - - ret = drm_dp_dpcd_read(mgr->aux, DP_FAUX_CAP, buf, 2); - if (ret) { - seq_printf(m, "faux/mst read failed\n"); - goto out; - } - seq_printf(m, "faux/mst: %*ph\n", 2, buf); - - ret = drm_dp_dpcd_read(mgr->aux, DP_MSTM_CTRL, buf, 1); - if (ret) { - seq_printf(m, "mst ctrl read failed\n"); - goto out; - } - seq_printf(m, "mst ctrl: %*ph\n", 1, buf); - - /* dump the standard OUI branch header */ - ret = drm_dp_dpcd_read(mgr->aux, DP_BRANCH_OUI, buf, DP_BRANCH_OUI_HEADER_SIZE); - if (ret) { - seq_printf(m, "branch oui read failed\n"); - goto out; - } - seq_printf(m, "branch oui: %*phN devid: ", 3, buf); - - for (i = 0x3; i < 0x8 && buf[i]; i++) - seq_printf(m, "%c", buf[i]); - seq_printf(m, " revision: hw: %x.%x sw: %x.%x\n", - buf[0x9] >> 4, buf[0x9] & 0xf, buf[0xa], buf[0xb]); - if (dump_dp_payload_table(mgr, buf)) - seq_printf(m, "payload table: %*ph\n", DP_PAYLOAD_TABLE_SIZE, buf); - } - -out: - mutex_unlock(&mgr->lock); - -} -EXPORT_SYMBOL(drm_dp_mst_dump_topology); - -static void drm_dp_tx_work(struct work_struct *work) -{ - struct drm_dp_mst_topology_mgr *mgr = container_of(work, struct drm_dp_mst_topology_mgr, tx_work); - - mutex_lock(&mgr->qlock); - if (!list_empty(&mgr->tx_msg_downq)) - process_single_down_tx_qlock(mgr); - mutex_unlock(&mgr->qlock); -} - -static inline void -drm_dp_delayed_destroy_port(struct drm_dp_mst_port *port) -{ - drm_dp_port_set_pdt(port, DP_PEER_DEVICE_NONE, port->mcs); - - if (port->connector) { - drm_connector_unregister(port->connector); - drm_connector_put(port->connector); - } - - drm_dp_mst_put_port_malloc(port); -} - -static inline void -drm_dp_delayed_destroy_mstb(struct drm_dp_mst_branch *mstb) -{ - struct drm_dp_mst_topology_mgr *mgr = mstb->mgr; - struct drm_dp_mst_port *port, *port_tmp; - struct drm_dp_sideband_msg_tx *txmsg, *txmsg_tmp; - bool wake_tx = false; - - mutex_lock(&mgr->lock); - list_for_each_entry_safe(port, port_tmp, &mstb->ports, next) { - list_del(&port->next); - drm_dp_mst_topology_put_port(port); - } - mutex_unlock(&mgr->lock); - - /* drop any tx slot msg */ - mutex_lock(&mstb->mgr->qlock); - list_for_each_entry_safe(txmsg, txmsg_tmp, &mgr->tx_msg_downq, next) { - if (txmsg->dst != mstb) - continue; - - txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT; - list_del(&txmsg->next); - wake_tx = true; - } - mutex_unlock(&mstb->mgr->qlock); - - if (wake_tx) - wake_up_all(&mstb->mgr->tx_waitq); - - drm_dp_mst_put_mstb_malloc(mstb); -} - -static void drm_dp_delayed_destroy_work(struct work_struct *work) -{ - struct drm_dp_mst_topology_mgr *mgr = - container_of(work, struct drm_dp_mst_topology_mgr, - delayed_destroy_work); - bool send_hotplug = false, go_again; - - /* - * Not a regular list traverse as we have to drop the destroy - * connector lock before destroying the mstb/port, to avoid AB->BA - * ordering between this lock and the config mutex. - */ - do { - go_again = false; - - for (;;) { - struct drm_dp_mst_branch *mstb; - - mutex_lock(&mgr->delayed_destroy_lock); - mstb = list_first_entry_or_null(&mgr->destroy_branch_device_list, - struct drm_dp_mst_branch, - destroy_next); - if (mstb) - list_del(&mstb->destroy_next); - mutex_unlock(&mgr->delayed_destroy_lock); - - if (!mstb) - break; - - drm_dp_delayed_destroy_mstb(mstb); - go_again = true; - } - - for (;;) { - struct drm_dp_mst_port *port; - - mutex_lock(&mgr->delayed_destroy_lock); - port = list_first_entry_or_null(&mgr->destroy_port_list, - struct drm_dp_mst_port, - next); - if (port) - list_del(&port->next); - mutex_unlock(&mgr->delayed_destroy_lock); - - if (!port) - break; - - drm_dp_delayed_destroy_port(port); - send_hotplug = true; - go_again = true; - } - } while (go_again); - - if (send_hotplug) - drm_kms_helper_hotplug_event(mgr->dev); -} - -static struct drm_private_state * -drm_dp_mst_duplicate_state(struct drm_private_obj *obj) -{ - struct drm_dp_mst_topology_state *state, *old_state = - to_dp_mst_topology_state(obj->state); - struct drm_dp_vcpi_allocation *pos, *vcpi; - - state = kmemdup(old_state, sizeof(*state), GFP_KERNEL); - if (!state) - return NULL; - - __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base); - - INIT_LIST_HEAD(&state->vcpis); - - list_for_each_entry(pos, &old_state->vcpis, next) { - /* Prune leftover freed VCPI allocations */ - if (!pos->vcpi) - continue; - - vcpi = kmemdup(pos, sizeof(*vcpi), GFP_KERNEL); - if (!vcpi) - goto fail; - - drm_dp_mst_get_port_malloc(vcpi->port); - list_add(&vcpi->next, &state->vcpis); - } - - return &state->base; - -fail: - list_for_each_entry_safe(pos, vcpi, &state->vcpis, next) { - drm_dp_mst_put_port_malloc(pos->port); - kfree(pos); - } - kfree(state); - - return NULL; -} - -static void drm_dp_mst_destroy_state(struct drm_private_obj *obj, - struct drm_private_state *state) -{ - struct drm_dp_mst_topology_state *mst_state = - to_dp_mst_topology_state(state); - struct drm_dp_vcpi_allocation *pos, *tmp; - - list_for_each_entry_safe(pos, tmp, &mst_state->vcpis, next) { - /* We only keep references to ports with non-zero VCPIs */ - if (pos->vcpi) - drm_dp_mst_put_port_malloc(pos->port); - kfree(pos); - } - - kfree(mst_state); -} - -static bool drm_dp_mst_port_downstream_of_branch(struct drm_dp_mst_port *port, - struct drm_dp_mst_branch *branch) -{ - while (port->parent) { - if (port->parent == branch) - return true; - - if (port->parent->port_parent) - port = port->parent->port_parent; - else - break; - } - return false; -} - -static int -drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port, - struct drm_dp_mst_topology_state *state); - -static int -drm_dp_mst_atomic_check_mstb_bw_limit(struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_topology_state *state) -{ - struct drm_dp_vcpi_allocation *vcpi; - struct drm_dp_mst_port *port; - int pbn_used = 0, ret; - bool found = false; - - /* Check that we have at least one port in our state that's downstream - * of this branch, otherwise we can skip this branch - */ - list_for_each_entry(vcpi, &state->vcpis, next) { - if (!vcpi->pbn || - !drm_dp_mst_port_downstream_of_branch(vcpi->port, mstb)) - continue; - - found = true; - break; - } - if (!found) - return 0; - - if (mstb->port_parent) - drm_dbg_atomic(mstb->mgr->dev, - "[MSTB:%p] [MST PORT:%p] Checking bandwidth limits on [MSTB:%p]\n", - mstb->port_parent->parent, mstb->port_parent, mstb); - else - drm_dbg_atomic(mstb->mgr->dev, "[MSTB:%p] Checking bandwidth limits\n", mstb); - - list_for_each_entry(port, &mstb->ports, next) { - ret = drm_dp_mst_atomic_check_port_bw_limit(port, state); - if (ret < 0) - return ret; - - pbn_used += ret; - } - - return pbn_used; -} - -static int -drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port, - struct drm_dp_mst_topology_state *state) -{ - struct drm_dp_vcpi_allocation *vcpi; - int pbn_used = 0; - - if (port->pdt == DP_PEER_DEVICE_NONE) - return 0; - - if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { - bool found = false; - - list_for_each_entry(vcpi, &state->vcpis, next) { - if (vcpi->port != port) - continue; - if (!vcpi->pbn) - return 0; - - found = true; - break; - } - if (!found) - return 0; - - /* - * This could happen if the sink deasserted its HPD line, but - * the branch device still reports it as attached (PDT != NONE). - */ - if (!port->full_pbn) { - drm_dbg_atomic(port->mgr->dev, - "[MSTB:%p] [MST PORT:%p] no BW available for the port\n", - port->parent, port); - return -EINVAL; - } - - pbn_used = vcpi->pbn; - } else { - pbn_used = drm_dp_mst_atomic_check_mstb_bw_limit(port->mstb, - state); - if (pbn_used <= 0) - return pbn_used; - } - - if (pbn_used > port->full_pbn) { - drm_dbg_atomic(port->mgr->dev, - "[MSTB:%p] [MST PORT:%p] required PBN of %d exceeds port limit of %d\n", - port->parent, port, pbn_used, port->full_pbn); - return -ENOSPC; - } - - drm_dbg_atomic(port->mgr->dev, "[MSTB:%p] [MST PORT:%p] uses %d out of %d PBN\n", - port->parent, port, pbn_used, port->full_pbn); - - return pbn_used; -} - -static inline int -drm_dp_mst_atomic_check_vcpi_alloc_limit(struct drm_dp_mst_topology_mgr *mgr, - struct drm_dp_mst_topology_state *mst_state) -{ - struct drm_dp_vcpi_allocation *vcpi; - int avail_slots = mst_state->total_avail_slots, payload_count = 0; - - list_for_each_entry(vcpi, &mst_state->vcpis, next) { - /* Releasing VCPI is always OK-even if the port is gone */ - if (!vcpi->vcpi) { - drm_dbg_atomic(mgr->dev, "[MST PORT:%p] releases all VCPI slots\n", - vcpi->port); - continue; - } - - drm_dbg_atomic(mgr->dev, "[MST PORT:%p] requires %d vcpi slots\n", - vcpi->port, vcpi->vcpi); - - avail_slots -= vcpi->vcpi; - if (avail_slots < 0) { - drm_dbg_atomic(mgr->dev, - "[MST PORT:%p] not enough VCPI slots in mst state %p (avail=%d)\n", - vcpi->port, mst_state, avail_slots + vcpi->vcpi); - return -ENOSPC; - } - - if (++payload_count > mgr->max_payloads) { - drm_dbg_atomic(mgr->dev, - "[MST MGR:%p] state %p has too many payloads (max=%d)\n", - mgr, mst_state, mgr->max_payloads); - return -EINVAL; - } - } - drm_dbg_atomic(mgr->dev, "[MST MGR:%p] mst state %p VCPI avail=%d used=%d\n", - mgr, mst_state, avail_slots, mst_state->total_avail_slots - avail_slots); - - return 0; -} - -/** - * drm_dp_mst_add_affected_dsc_crtcs - * @state: Pointer to the new struct drm_dp_mst_topology_state - * @mgr: MST topology manager - * - * Whenever there is a change in mst topology - * DSC configuration would have to be recalculated - * therefore we need to trigger modeset on all affected - * CRTCs in that topology - * - * See also: - * drm_dp_mst_atomic_enable_dsc() - */ -int drm_dp_mst_add_affected_dsc_crtcs(struct drm_atomic_state *state, struct drm_dp_mst_topology_mgr *mgr) -{ - struct drm_dp_mst_topology_state *mst_state; - struct drm_dp_vcpi_allocation *pos; - struct drm_connector *connector; - struct drm_connector_state *conn_state; - struct drm_crtc *crtc; - struct drm_crtc_state *crtc_state; - - mst_state = drm_atomic_get_mst_topology_state(state, mgr); - - if (IS_ERR(mst_state)) - return -EINVAL; - - list_for_each_entry(pos, &mst_state->vcpis, next) { - - connector = pos->port->connector; - - if (!connector) - return -EINVAL; - - conn_state = drm_atomic_get_connector_state(state, connector); - - if (IS_ERR(conn_state)) - return PTR_ERR(conn_state); - - crtc = conn_state->crtc; - - if (!crtc) - continue; - - if (!drm_dp_mst_dsc_aux_for_port(pos->port)) - continue; - - crtc_state = drm_atomic_get_crtc_state(mst_state->base.state, crtc); - - if (IS_ERR(crtc_state)) - return PTR_ERR(crtc_state); - - drm_dbg_atomic(mgr->dev, "[MST MGR:%p] Setting mode_changed flag on CRTC %p\n", - mgr, crtc); - - crtc_state->mode_changed = true; - } - return 0; -} -EXPORT_SYMBOL(drm_dp_mst_add_affected_dsc_crtcs); - -/** - * drm_dp_mst_atomic_enable_dsc - Set DSC Enable Flag to On/Off - * @state: Pointer to the new drm_atomic_state - * @port: Pointer to the affected MST Port - * @pbn: Newly recalculated bw required for link with DSC enabled - * @pbn_div: Divider to calculate correct number of pbn per slot - * @enable: Boolean flag to enable or disable DSC on the port - * - * This function enables DSC on the given Port - * by recalculating its vcpi from pbn provided - * and sets dsc_enable flag to keep track of which - * ports have DSC enabled - * - */ -int drm_dp_mst_atomic_enable_dsc(struct drm_atomic_state *state, - struct drm_dp_mst_port *port, - int pbn, int pbn_div, - bool enable) -{ - struct drm_dp_mst_topology_state *mst_state; - struct drm_dp_vcpi_allocation *pos; - bool found = false; - int vcpi = 0; - - mst_state = drm_atomic_get_mst_topology_state(state, port->mgr); - - if (IS_ERR(mst_state)) - return PTR_ERR(mst_state); - - list_for_each_entry(pos, &mst_state->vcpis, next) { - if (pos->port == port) { - found = true; - break; - } - } - - if (!found) { - drm_dbg_atomic(state->dev, - "[MST PORT:%p] Couldn't find VCPI allocation in mst state %p\n", - port, mst_state); - return -EINVAL; - } - - if (pos->dsc_enabled == enable) { - drm_dbg_atomic(state->dev, - "[MST PORT:%p] DSC flag is already set to %d, returning %d VCPI slots\n", - port, enable, pos->vcpi); - vcpi = pos->vcpi; - } - - if (enable) { - vcpi = drm_dp_atomic_find_vcpi_slots(state, port->mgr, port, pbn, pbn_div); - drm_dbg_atomic(state->dev, - "[MST PORT:%p] Enabling DSC flag, reallocating %d VCPI slots on the port\n", - port, vcpi); - if (vcpi < 0) - return -EINVAL; - } - - pos->dsc_enabled = enable; - - return vcpi; -} -EXPORT_SYMBOL(drm_dp_mst_atomic_enable_dsc); -/** - * drm_dp_mst_atomic_check - Check that the new state of an MST topology in an - * atomic update is valid - * @state: Pointer to the new &struct drm_dp_mst_topology_state - * - * Checks the given topology state for an atomic update to ensure that it's - * valid. This includes checking whether there's enough bandwidth to support - * the new VCPI allocations in the atomic update. - * - * Any atomic drivers supporting DP MST must make sure to call this after - * checking the rest of their state in their - * &drm_mode_config_funcs.atomic_check() callback. - * - * See also: - * drm_dp_atomic_find_vcpi_slots() - * drm_dp_atomic_release_vcpi_slots() - * - * Returns: - * - * 0 if the new state is valid, negative error code otherwise. - */ -int drm_dp_mst_atomic_check(struct drm_atomic_state *state) -{ - struct drm_dp_mst_topology_mgr *mgr; - struct drm_dp_mst_topology_state *mst_state; - int i, ret = 0; - - for_each_new_mst_mgr_in_state(state, mgr, mst_state, i) { - if (!mgr->mst_state) - continue; - - ret = drm_dp_mst_atomic_check_vcpi_alloc_limit(mgr, mst_state); - if (ret) - break; - - mutex_lock(&mgr->lock); - ret = drm_dp_mst_atomic_check_mstb_bw_limit(mgr->mst_primary, - mst_state); - mutex_unlock(&mgr->lock); - if (ret < 0) - break; - else - ret = 0; - } - - return ret; -} -EXPORT_SYMBOL(drm_dp_mst_atomic_check); - -const struct drm_private_state_funcs drm_dp_mst_topology_state_funcs = { - .atomic_duplicate_state = drm_dp_mst_duplicate_state, - .atomic_destroy_state = drm_dp_mst_destroy_state, -}; -EXPORT_SYMBOL(drm_dp_mst_topology_state_funcs); - -/** - * drm_atomic_get_mst_topology_state: get MST topology state - * - * @state: global atomic state - * @mgr: MST topology manager, also the private object in this case - * - * This function wraps drm_atomic_get_priv_obj_state() passing in the MST atomic - * state vtable so that the private object state returned is that of a MST - * topology object. Also, drm_atomic_get_private_obj_state() expects the caller - * to care of the locking, so warn if don't hold the connection_mutex. - * - * RETURNS: - * - * The MST topology state or error pointer. - */ -struct drm_dp_mst_topology_state *drm_atomic_get_mst_topology_state(struct drm_atomic_state *state, - struct drm_dp_mst_topology_mgr *mgr) -{ - return to_dp_mst_topology_state(drm_atomic_get_private_obj_state(state, &mgr->base)); -} -EXPORT_SYMBOL(drm_atomic_get_mst_topology_state); - -/** - * drm_dp_mst_topology_mgr_init - initialise a topology manager - * @mgr: manager struct to initialise - * @dev: device providing this structure - for i2c addition. - * @aux: DP helper aux channel to talk to this device - * @max_dpcd_transaction_bytes: hw specific DPCD transaction limit - * @max_payloads: maximum number of payloads this GPU can source - * @max_lane_count: maximum number of lanes this GPU supports - * @max_link_rate: maximum link rate per lane this GPU supports in kHz - * @conn_base_id: the connector object ID the MST device is connected to. - * - * Return 0 for success, or negative error code on failure - */ -int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr, - struct drm_device *dev, struct drm_dp_aux *aux, - int max_dpcd_transaction_bytes, int max_payloads, - int max_lane_count, int max_link_rate, - int conn_base_id) -{ - struct drm_dp_mst_topology_state *mst_state; - - mutex_init(&mgr->lock); - mutex_init(&mgr->qlock); - mutex_init(&mgr->payload_lock); - mutex_init(&mgr->delayed_destroy_lock); - mutex_init(&mgr->up_req_lock); - mutex_init(&mgr->probe_lock); -#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) - mutex_init(&mgr->topology_ref_history_lock); - stack_depot_init(); -#endif - INIT_LIST_HEAD(&mgr->tx_msg_downq); - INIT_LIST_HEAD(&mgr->destroy_port_list); - INIT_LIST_HEAD(&mgr->destroy_branch_device_list); - INIT_LIST_HEAD(&mgr->up_req_list); - - /* - * delayed_destroy_work will be queued on a dedicated WQ, so that any - * requeuing will be also flushed when deiniting the topology manager. - */ - mgr->delayed_destroy_wq = alloc_ordered_workqueue("drm_dp_mst_wq", 0); - if (mgr->delayed_destroy_wq == NULL) - return -ENOMEM; - - INIT_WORK(&mgr->work, drm_dp_mst_link_probe_work); - INIT_WORK(&mgr->tx_work, drm_dp_tx_work); - INIT_WORK(&mgr->delayed_destroy_work, drm_dp_delayed_destroy_work); - INIT_WORK(&mgr->up_req_work, drm_dp_mst_up_req_work); - init_waitqueue_head(&mgr->tx_waitq); - mgr->dev = dev; - mgr->aux = aux; - mgr->max_dpcd_transaction_bytes = max_dpcd_transaction_bytes; - mgr->max_payloads = max_payloads; - mgr->max_lane_count = max_lane_count; - mgr->max_link_rate = max_link_rate; - mgr->conn_base_id = conn_base_id; - if (max_payloads + 1 > sizeof(mgr->payload_mask) * 8 || - max_payloads + 1 > sizeof(mgr->vcpi_mask) * 8) - return -EINVAL; - mgr->payloads = kcalloc(max_payloads, sizeof(struct drm_dp_payload), GFP_KERNEL); - if (!mgr->payloads) - return -ENOMEM; - mgr->proposed_vcpis = kcalloc(max_payloads, sizeof(struct drm_dp_vcpi *), GFP_KERNEL); - if (!mgr->proposed_vcpis) - return -ENOMEM; - set_bit(0, &mgr->payload_mask); - - mst_state = kzalloc(sizeof(*mst_state), GFP_KERNEL); - if (mst_state == NULL) - return -ENOMEM; - - mst_state->total_avail_slots = 63; - mst_state->start_slot = 1; - - mst_state->mgr = mgr; - INIT_LIST_HEAD(&mst_state->vcpis); - - drm_atomic_private_obj_init(dev, &mgr->base, - &mst_state->base, - &drm_dp_mst_topology_state_funcs); - - return 0; -} -EXPORT_SYMBOL(drm_dp_mst_topology_mgr_init); - -/** - * drm_dp_mst_topology_mgr_destroy() - destroy topology manager. - * @mgr: manager to destroy - */ -void drm_dp_mst_topology_mgr_destroy(struct drm_dp_mst_topology_mgr *mgr) -{ - drm_dp_mst_topology_mgr_set_mst(mgr, false); - flush_work(&mgr->work); - /* The following will also drain any requeued work on the WQ. */ - if (mgr->delayed_destroy_wq) { - destroy_workqueue(mgr->delayed_destroy_wq); - mgr->delayed_destroy_wq = NULL; - } - mutex_lock(&mgr->payload_lock); - kfree(mgr->payloads); - mgr->payloads = NULL; - kfree(mgr->proposed_vcpis); - mgr->proposed_vcpis = NULL; - mutex_unlock(&mgr->payload_lock); - mgr->dev = NULL; - mgr->aux = NULL; - drm_atomic_private_obj_fini(&mgr->base); - mgr->funcs = NULL; - - mutex_destroy(&mgr->delayed_destroy_lock); - mutex_destroy(&mgr->payload_lock); - mutex_destroy(&mgr->qlock); - mutex_destroy(&mgr->lock); - mutex_destroy(&mgr->up_req_lock); - mutex_destroy(&mgr->probe_lock); -#if IS_ENABLED(CONFIG_DRM_DEBUG_DP_MST_TOPOLOGY_REFS) - mutex_destroy(&mgr->topology_ref_history_lock); -#endif -} -EXPORT_SYMBOL(drm_dp_mst_topology_mgr_destroy); - -static bool remote_i2c_read_ok(const struct i2c_msg msgs[], int num) -{ - int i; - - if (num - 1 > DP_REMOTE_I2C_READ_MAX_TRANSACTIONS) - return false; - - for (i = 0; i < num - 1; i++) { - if (msgs[i].flags & I2C_M_RD || - msgs[i].len > 0xff) - return false; - } - - return msgs[num - 1].flags & I2C_M_RD && - msgs[num - 1].len <= 0xff; -} - -static bool remote_i2c_write_ok(const struct i2c_msg msgs[], int num) -{ - int i; - - for (i = 0; i < num - 1; i++) { - if (msgs[i].flags & I2C_M_RD || !(msgs[i].flags & I2C_M_STOP) || - msgs[i].len > 0xff) - return false; - } - - return !(msgs[num - 1].flags & I2C_M_RD) && msgs[num - 1].len <= 0xff; -} - -static int drm_dp_mst_i2c_read(struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_port *port, - struct i2c_msg *msgs, int num) -{ - struct drm_dp_mst_topology_mgr *mgr = port->mgr; - unsigned int i; - struct drm_dp_sideband_msg_req_body msg; - struct drm_dp_sideband_msg_tx *txmsg = NULL; - int ret; - - memset(&msg, 0, sizeof(msg)); - msg.req_type = DP_REMOTE_I2C_READ; - msg.u.i2c_read.num_transactions = num - 1; - msg.u.i2c_read.port_number = port->port_num; - for (i = 0; i < num - 1; i++) { - msg.u.i2c_read.transactions[i].i2c_dev_id = msgs[i].addr; - msg.u.i2c_read.transactions[i].num_bytes = msgs[i].len; - msg.u.i2c_read.transactions[i].bytes = msgs[i].buf; - msg.u.i2c_read.transactions[i].no_stop_bit = !(msgs[i].flags & I2C_M_STOP); - } - msg.u.i2c_read.read_i2c_device_id = msgs[num - 1].addr; - msg.u.i2c_read.num_bytes_read = msgs[num - 1].len; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) { - ret = -ENOMEM; - goto out; - } - - txmsg->dst = mstb; - drm_dp_encode_sideband_req(&msg, txmsg); - - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret > 0) { - - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { - ret = -EREMOTEIO; - goto out; - } - if (txmsg->reply.u.remote_i2c_read_ack.num_bytes != msgs[num - 1].len) { - ret = -EIO; - goto out; - } - memcpy(msgs[num - 1].buf, txmsg->reply.u.remote_i2c_read_ack.bytes, msgs[num - 1].len); - ret = num; - } -out: - kfree(txmsg); - return ret; -} - -static int drm_dp_mst_i2c_write(struct drm_dp_mst_branch *mstb, - struct drm_dp_mst_port *port, - struct i2c_msg *msgs, int num) -{ - struct drm_dp_mst_topology_mgr *mgr = port->mgr; - unsigned int i; - struct drm_dp_sideband_msg_req_body msg; - struct drm_dp_sideband_msg_tx *txmsg = NULL; - int ret; - - txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); - if (!txmsg) { - ret = -ENOMEM; - goto out; - } - for (i = 0; i < num; i++) { - memset(&msg, 0, sizeof(msg)); - msg.req_type = DP_REMOTE_I2C_WRITE; - msg.u.i2c_write.port_number = port->port_num; - msg.u.i2c_write.write_i2c_device_id = msgs[i].addr; - msg.u.i2c_write.num_bytes = msgs[i].len; - msg.u.i2c_write.bytes = msgs[i].buf; - - memset(txmsg, 0, sizeof(*txmsg)); - txmsg->dst = mstb; - - drm_dp_encode_sideband_req(&msg, txmsg); - drm_dp_queue_down_tx(mgr, txmsg); - - ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); - if (ret > 0) { - if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { - ret = -EREMOTEIO; - goto out; - } - } else { - goto out; - } - } - ret = num; -out: - kfree(txmsg); - return ret; -} - -/* I2C device */ -static int drm_dp_mst_i2c_xfer(struct i2c_adapter *adapter, - struct i2c_msg *msgs, int num) -{ - struct drm_dp_aux *aux = adapter->algo_data; - struct drm_dp_mst_port *port = - container_of(aux, struct drm_dp_mst_port, aux); - struct drm_dp_mst_branch *mstb; - struct drm_dp_mst_topology_mgr *mgr = port->mgr; - int ret; - - mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent); - if (!mstb) - return -EREMOTEIO; - - if (remote_i2c_read_ok(msgs, num)) { - ret = drm_dp_mst_i2c_read(mstb, port, msgs, num); - } else if (remote_i2c_write_ok(msgs, num)) { - ret = drm_dp_mst_i2c_write(mstb, port, msgs, num); - } else { - drm_dbg_kms(mgr->dev, "Unsupported I2C transaction for MST device\n"); - ret = -EIO; - } - - drm_dp_mst_topology_put_mstb(mstb); - return ret; -} - -static u32 drm_dp_mst_i2c_functionality(struct i2c_adapter *adapter) -{ - return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | - I2C_FUNC_SMBUS_READ_BLOCK_DATA | - I2C_FUNC_SMBUS_BLOCK_PROC_CALL | - I2C_FUNC_10BIT_ADDR; -} - -static const struct i2c_algorithm drm_dp_mst_i2c_algo = { - .functionality = drm_dp_mst_i2c_functionality, - .master_xfer = drm_dp_mst_i2c_xfer, -}; - -/** - * drm_dp_mst_register_i2c_bus() - register an I2C adapter for I2C-over-AUX - * @port: The port to add the I2C bus on - * - * Returns 0 on success or a negative error code on failure. - */ -static int drm_dp_mst_register_i2c_bus(struct drm_dp_mst_port *port) -{ - struct drm_dp_aux *aux = &port->aux; - struct device *parent_dev = port->mgr->dev->dev; - - aux->ddc.algo = &drm_dp_mst_i2c_algo; - aux->ddc.algo_data = aux; - aux->ddc.retries = 3; - - aux->ddc.class = I2C_CLASS_DDC; - aux->ddc.owner = THIS_MODULE; - /* FIXME: set the kdev of the port's connector as parent */ - aux->ddc.dev.parent = parent_dev; - aux->ddc.dev.of_node = parent_dev->of_node; - - strlcpy(aux->ddc.name, aux->name ? aux->name : dev_name(parent_dev), - sizeof(aux->ddc.name)); - - return i2c_add_adapter(&aux->ddc); -} - -/** - * drm_dp_mst_unregister_i2c_bus() - unregister an I2C-over-AUX adapter - * @port: The port to remove the I2C bus from - */ -static void drm_dp_mst_unregister_i2c_bus(struct drm_dp_mst_port *port) -{ - i2c_del_adapter(&port->aux.ddc); -} - -/** - * drm_dp_mst_is_virtual_dpcd() - Is the given port a virtual DP Peer Device - * @port: The port to check - * - * A single physical MST hub object can be represented in the topology - * by multiple branches, with virtual ports between those branches. - * - * As of DP1.4, An MST hub with internal (virtual) ports must expose - * certain DPCD registers over those ports. See sections 2.6.1.1.1 - * and 2.6.1.1.2 of Display Port specification v1.4 for details. - * - * May acquire mgr->lock - * - * Returns: - * true if the port is a virtual DP peer device, false otherwise - */ -static bool drm_dp_mst_is_virtual_dpcd(struct drm_dp_mst_port *port) -{ - struct drm_dp_mst_port *downstream_port; - - if (!port || port->dpcd_rev < DP_DPCD_REV_14) - return false; - - /* Virtual DP Sink (Internal Display Panel) */ - if (port->port_num >= 8) - return true; - - /* DP-to-HDMI Protocol Converter */ - if (port->pdt == DP_PEER_DEVICE_DP_LEGACY_CONV && - !port->mcs && - port->ldps) - return true; - - /* DP-to-DP */ - mutex_lock(&port->mgr->lock); - if (port->pdt == DP_PEER_DEVICE_MST_BRANCHING && - port->mstb && - port->mstb->num_ports == 2) { - list_for_each_entry(downstream_port, &port->mstb->ports, next) { - if (downstream_port->pdt == DP_PEER_DEVICE_SST_SINK && - !downstream_port->input) { - mutex_unlock(&port->mgr->lock); - return true; - } - } - } - mutex_unlock(&port->mgr->lock); - - return false; -} - -/** - * drm_dp_mst_dsc_aux_for_port() - Find the correct aux for DSC - * @port: The port to check. A leaf of the MST tree with an attached display. - * - * Depending on the situation, DSC may be enabled via the endpoint aux, - * the immediately upstream aux, or the connector's physical aux. - * - * This is both the correct aux to read DSC_CAPABILITY and the - * correct aux to write DSC_ENABLED. - * - * This operation can be expensive (up to four aux reads), so - * the caller should cache the return. - * - * Returns: - * NULL if DSC cannot be enabled on this port, otherwise the aux device - */ -struct drm_dp_aux *drm_dp_mst_dsc_aux_for_port(struct drm_dp_mst_port *port) -{ - struct drm_dp_mst_port *immediate_upstream_port; - struct drm_dp_mst_port *fec_port; - struct drm_dp_desc desc = {}; - u8 endpoint_fec; - u8 endpoint_dsc; - - if (!port) - return NULL; - - if (port->parent->port_parent) - immediate_upstream_port = port->parent->port_parent; - else - immediate_upstream_port = NULL; - - fec_port = immediate_upstream_port; - while (fec_port) { - /* - * Each physical link (i.e. not a virtual port) between the - * output and the primary device must support FEC - */ - if (!drm_dp_mst_is_virtual_dpcd(fec_port) && - !fec_port->fec_capable) - return NULL; - - fec_port = fec_port->parent->port_parent; - } - - /* DP-to-DP peer device */ - if (drm_dp_mst_is_virtual_dpcd(immediate_upstream_port)) { - u8 upstream_dsc; - - if (drm_dp_dpcd_read(&port->aux, - DP_DSC_SUPPORT, &endpoint_dsc, 1) != 1) - return NULL; - if (drm_dp_dpcd_read(&port->aux, - DP_FEC_CAPABILITY, &endpoint_fec, 1) != 1) - return NULL; - if (drm_dp_dpcd_read(&immediate_upstream_port->aux, - DP_DSC_SUPPORT, &upstream_dsc, 1) != 1) - return NULL; - - /* Enpoint decompression with DP-to-DP peer device */ - if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) && - (endpoint_fec & DP_FEC_CAPABLE) && - (upstream_dsc & 0x2) /* DSC passthrough */) - return &port->aux; - - /* Virtual DPCD decompression with DP-to-DP peer device */ - return &immediate_upstream_port->aux; - } - - /* Virtual DPCD decompression with DP-to-HDMI or Virtual DP Sink */ - if (drm_dp_mst_is_virtual_dpcd(port)) - return &port->aux; - - /* - * Synaptics quirk - * Applies to ports for which: - * - Physical aux has Synaptics OUI - * - DPv1.4 or higher - * - Port is on primary branch device - * - Not a VGA adapter (DP_DWN_STRM_PORT_TYPE_ANALOG) - */ - if (drm_dp_read_desc(port->mgr->aux, &desc, true)) - return NULL; - - if (drm_dp_has_quirk(&desc, DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD) && - port->mgr->dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14 && - port->parent == port->mgr->mst_primary) { - u8 dpcd_ext[DP_RECEIVER_CAP_SIZE]; - - if (drm_dp_read_dpcd_caps(port->mgr->aux, dpcd_ext) < 0) - return NULL; - - if ((dpcd_ext[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT) && - ((dpcd_ext[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_TYPE_MASK) - != DP_DWN_STRM_PORT_TYPE_ANALOG)) - return port->mgr->aux; - } - - /* - * The check below verifies if the MST sink - * connected to the GPU is capable of DSC - - * therefore the endpoint needs to be - * both DSC and FEC capable. - */ - if (drm_dp_dpcd_read(&port->aux, - DP_DSC_SUPPORT, &endpoint_dsc, 1) != 1) - return NULL; - if (drm_dp_dpcd_read(&port->aux, - DP_FEC_CAPABILITY, &endpoint_fec, 1) != 1) - return NULL; - if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) && - (endpoint_fec & DP_FEC_CAPABLE)) - return &port->aux; - - return NULL; -} -EXPORT_SYMBOL(drm_dp_mst_dsc_aux_for_port); diff --git a/drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h b/drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h deleted file mode 100644 index 401953b59d45..000000000000 --- a/drivers/gpu/drm/dp/drm_dp_mst_topology_internal.h +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only - * - * Declarations for DP MST related functions which are only used in selftests - * - * Copyright © 2018 Red Hat - * Authors: - * Lyude Paul - */ - -#ifndef _DRM_DP_MST_HELPER_INTERNAL_H_ -#define _DRM_DP_MST_HELPER_INTERNAL_H_ - -#include - -void -drm_dp_encode_sideband_req(const struct drm_dp_sideband_msg_req_body *req, - struct drm_dp_sideband_msg_tx *raw); -int drm_dp_decode_sideband_req(const struct drm_dp_sideband_msg_tx *raw, - struct drm_dp_sideband_msg_req_body *req); -void -drm_dp_dump_sideband_msg_req_body(const struct drm_dp_sideband_msg_req_body *req, - int indent, struct drm_printer *printer); - -#endif /* !_DRM_DP_MST_HELPER_INTERNAL_H_ */ diff --git a/drivers/gpu/drm/drm_dsc.c b/drivers/gpu/drm/drm_dsc.c index fdd8d5f42622..2428bdfc4c8f 100644 --- a/drivers/gpu/drm/drm_dsc.c +++ b/drivers/gpu/drm/drm_dsc.c @@ -11,8 +11,9 @@ #include #include #include + +#include #include -#include #include /** diff --git a/drivers/gpu/drm/gma500/cdv_intel_dp.c b/drivers/gpu/drm/gma500/cdv_intel_dp.c index 72b1b2fc3c27..9ee99a7d4fbe 100644 --- a/drivers/gpu/drm/gma500/cdv_intel_dp.c +++ b/drivers/gpu/drm/gma500/cdv_intel_dp.c @@ -29,9 +29,9 @@ #include #include +#include #include #include -#include #include #include "gma_display.h" diff --git a/drivers/gpu/drm/gma500/intel_bios.c b/drivers/gpu/drm/gma500/intel_bios.c index ea7c16f33a0e..8245b5603d2c 100644 --- a/drivers/gpu/drm/gma500/intel_bios.c +++ b/drivers/gpu/drm/gma500/intel_bios.c @@ -5,8 +5,9 @@ * Authors: * Eric Anholt */ + +#include #include -#include #include "intel_bios.h" #include "psb_drv.h" diff --git a/drivers/gpu/drm/i915/display/intel_bios.c b/drivers/gpu/drm/i915/display/intel_bios.c index bc195769dd07..cf56c0109f49 100644 --- a/drivers/gpu/drm/i915/display/intel_bios.c +++ b/drivers/gpu/drm/i915/display/intel_bios.c @@ -25,7 +25,7 @@ * */ -#include +#include #include "display/intel_display.h" #include "display/intel_display_types.h" diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c index 29044cf58b87..5fab9fb1d2f5 100644 --- a/drivers/gpu/drm/i915/display/intel_display.c +++ b/drivers/gpu/drm/i915/display/intel_display.c @@ -35,11 +35,11 @@ #include #include +#include #include #include #include #include -#include #include #include #include diff --git a/drivers/gpu/drm/i915/display/intel_display_types.h b/drivers/gpu/drm/i915/display/intel_display_types.h index cfd042117b10..efc42af60440 100644 --- a/drivers/gpu/drm/i915/display/intel_display_types.h +++ b/drivers/gpu/drm/i915/display/intel_display_types.h @@ -31,8 +31,8 @@ #include #include -#include -#include +#include +#include #include #include #include diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c index d55acc4a028a..9801e6f0ad3b 100644 --- a/drivers/gpu/drm/i915/display/intel_dp.c +++ b/drivers/gpu/drm/i915/display/intel_dp.c @@ -35,9 +35,9 @@ #include +#include #include #include -#include #include #include diff --git a/drivers/gpu/drm/i915/display/intel_dp_hdcp.c b/drivers/gpu/drm/i915/display/intel_dp_hdcp.c index 82d024dafe7b..598cad09d499 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_hdcp.c +++ b/drivers/gpu/drm/i915/display/intel_dp_hdcp.c @@ -6,8 +6,8 @@ * Sean Paul */ -#include -#include +#include +#include #include #include diff --git a/drivers/gpu/drm/i915/display/intel_dp_link_training.h b/drivers/gpu/drm/i915/display/intel_dp_link_training.h index dc1556b46b85..7fa1c0833096 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_link_training.h +++ b/drivers/gpu/drm/i915/display/intel_dp_link_training.h @@ -6,7 +6,7 @@ #ifndef __INTEL_DP_LINK_TRAINING_H__ #define __INTEL_DP_LINK_TRAINING_H__ -#include +#include struct intel_crtc_state; struct intel_dp; diff --git a/drivers/gpu/drm/i915/display/intel_lspcon.c b/drivers/gpu/drm/i915/display/intel_lspcon.c index 76357c9b76e4..be0b1010b304 100644 --- a/drivers/gpu/drm/i915/display/intel_lspcon.c +++ b/drivers/gpu/drm/i915/display/intel_lspcon.c @@ -23,8 +23,8 @@ * */ +#include #include -#include #include #include "intel_de.h" diff --git a/drivers/gpu/drm/msm/dp/dp_audio.c b/drivers/gpu/drm/msm/dp/dp_audio.c index 4553f4985434..077d3b6507e7 100644 --- a/drivers/gpu/drm/msm/dp/dp_audio.c +++ b/drivers/gpu/drm/msm/dp/dp_audio.c @@ -8,7 +8,7 @@ #include -#include +#include #include #include "dp_catalog.h" diff --git a/drivers/gpu/drm/msm/dp/dp_aux.h b/drivers/gpu/drm/msm/dp/dp_aux.h index 82afc8d5210f..c64951215ab5 100644 --- a/drivers/gpu/drm/msm/dp/dp_aux.h +++ b/drivers/gpu/drm/msm/dp/dp_aux.h @@ -7,7 +7,7 @@ #define _DP_AUX_H_ #include "dp_catalog.h" -#include +#include int dp_aux_register(struct drm_dp_aux *dp_aux); void dp_aux_unregister(struct drm_dp_aux *dp_aux); diff --git a/drivers/gpu/drm/msm/dp/dp_catalog.c b/drivers/gpu/drm/msm/dp/dp_catalog.c index fac815fb6d91..b5dd0240d1dc 100644 --- a/drivers/gpu/drm/msm/dp/dp_catalog.c +++ b/drivers/gpu/drm/msm/dp/dp_catalog.c @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include "dp_catalog.h" diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index 53568567e05b..a96f6a8fa9bd 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -11,8 +11,9 @@ #include #include #include + +#include #include -#include #include #include "dp_reg.h" diff --git a/drivers/gpu/drm/msm/edp/edp.h b/drivers/gpu/drm/msm/edp/edp.h index 1a82d7a4af9f..14b0ef02287e 100644 --- a/drivers/gpu/drm/msm/edp/edp.h +++ b/drivers/gpu/drm/msm/edp/edp.h @@ -10,7 +10,8 @@ #include #include #include -#include + +#include #include #include diff --git a/drivers/gpu/drm/msm/edp/edp_ctrl.c b/drivers/gpu/drm/msm/edp/edp_ctrl.c index 9f537b1fd849..9ac1963c679e 100644 --- a/drivers/gpu/drm/msm/edp/edp_ctrl.c +++ b/drivers/gpu/drm/msm/edp/edp_ctrl.c @@ -6,7 +6,8 @@ #include #include #include -#include + +#include #include #include diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c index df58c6445c51..45db61ac2bfe 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/disp.c +++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c @@ -33,9 +33,9 @@ #include #include +#include #include #include -#include #include #include #include diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h b/drivers/gpu/drm/nouveau/nouveau_connector.h index 1b173191cc41..b0773af5a98f 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.h +++ b/drivers/gpu/drm/nouveau/nouveau_connector.h @@ -33,10 +33,10 @@ #include #include +#include #include #include #include -#include #include #include "nouveau_crtc.h" diff --git a/drivers/gpu/drm/nouveau/nouveau_dp.c b/drivers/gpu/drm/nouveau/nouveau_dp.c index 724d40ddd452..c36f510d5d4c 100644 --- a/drivers/gpu/drm/nouveau/nouveau_dp.c +++ b/drivers/gpu/drm/nouveau/nouveau_dp.c @@ -22,7 +22,7 @@ * Authors: Ben Skeggs */ -#include +#include #include "nouveau_drv.h" #include "nouveau_connector.h" diff --git a/drivers/gpu/drm/nouveau/nouveau_encoder.h b/drivers/gpu/drm/nouveau/nouveau_encoder.h index 65ed84f88cca..c2f5f0cb70d5 100644 --- a/drivers/gpu/drm/nouveau/nouveau_encoder.h +++ b/drivers/gpu/drm/nouveau/nouveau_encoder.h @@ -29,10 +29,12 @@ #include +#include +#include #include -#include -#include + #include "dispnv04/disp.h" + struct nv50_head_atom; struct nouveau_connector; diff --git a/drivers/gpu/drm/panel/panel-edp.c b/drivers/gpu/drm/panel/panel-edp.c index 1732b4f56e38..c96014464355 100644 --- a/drivers/gpu/drm/panel/panel-edp.c +++ b/drivers/gpu/drm/panel/panel-edp.c @@ -35,10 +35,10 @@ #include