* [PATCH 0/6] Add support for RK3588 DisplayPort Controller
@ 2025-02-23 11:30 Andy Yan
2025-02-23 11:30 ` [PATCH 1/6] dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller Andy Yan
` (6 more replies)
0 siblings, 7 replies; 32+ messages in thread
From: Andy Yan @ 2025-02-23 11:30 UTC (permalink / raw)
To: heiko
Cc: hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang,
krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel,
linux-rockchip, robh, sebastian.reichel, Andy Yan
From: Andy Yan <andy.yan@rock-chips.com>
There are two DW DPTX based DisplayPort Controller on rk3588 which
are compliant with the DisplayPort Specification Version 1.4 with
the following features:
* DisplayPort 1.4a
* Main Link: 1/2/4 lanes
* Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps
* AUX channel 1Mbps
* Single Stream Transport(SST)
* Multistream Transport (MST)
*Type-C support (alternate mode)
* HDCP 2.2, HDCP 1.3
* Supports up to 8/10 bits per color component
* Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0
* Pixel clock up to 594MHz
* I2S, SPDIF audio interface
The current version of this patch series only supports basic display outputs.
I conducted tests in 1080p and 4K@60 YCbCr4:2:0 modes; the ALT/Type-C mode
hasn't been tested yet, but I suspect it will likely work. HDCP and audio
features remain unimplemented. For RK3588, it's only support SST, while in
the upcoming RK3576, it can support MST output.
Andy Yan (6):
dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller
drm/bridge: synopsys: Add DW DPTX Controller support library
drm/rockchip: Add RK3588 DPTX output support
arm64: dts: rockchip: Add DP0 for rk3588
arm64: dts: rockchip: Add DP1 for rk3588
arm64: dts: rockchip: Enable DisplayPort for rk3588s Cool Pi 4B
.../display/rockchip/rockchip,dw-dp.yaml | 150 ++
arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 30 +
.../arm64/boot/dts/rockchip/rk3588-extra.dtsi | 30 +
.../boot/dts/rockchip/rk3588s-coolpi-4b.dts | 37 +
drivers/gpu/drm/bridge/synopsys/Kconfig | 7 +
drivers/gpu/drm/bridge/synopsys/Makefile | 1 +
drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2155 +++++++++++++++++
drivers/gpu/drm/rockchip/Kconfig | 7 +
drivers/gpu/drm/rockchip/Makefile | 1 +
drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 162 ++
drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 1 +
drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 1 +
include/drm/bridge/dw_dp.h | 19 +
13 files changed, 2601 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml
create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-dp.c
create mode 100644 drivers/gpu/drm/rockchip/dw_dp-rockchip.c
create mode 100644 include/drm/bridge/dw_dp.h
--
2.34.1
^ permalink raw reply [flat|nested] 32+ messages in thread* [PATCH 1/6] dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller 2025-02-23 11:30 [PATCH 0/6] Add support for RK3588 DisplayPort Controller Andy Yan @ 2025-02-23 11:30 ` Andy Yan 2025-02-24 20:03 ` Rob Herring 2025-02-23 11:30 ` [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library Andy Yan ` (5 subsequent siblings) 6 siblings, 1 reply; 32+ messages in thread From: Andy Yan @ 2025-02-23 11:30 UTC (permalink / raw) To: heiko Cc: hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan From: Andy Yan <andy.yan@rock-chips.com> The Rockchip RK3588 SoC integrates the Synopsys DesignWare DPTX controller. And this DPTX controller need share a USBDP PHY with the USB 3.0 OTG controller during operation. Signed-off-by: Andy Yan <andy.yan@rock-chips.com> --- .../display/rockchip/rockchip,dw-dp.yaml | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml new file mode 100644 index 000000000000..b48af8c3e68b --- /dev/null +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml @@ -0,0 +1,150 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/rockchip/rockchip,dw-dp.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip DW DisplayPort Transmitter + +maintainers: + - Andy Yan <andy.yan@rock-chips.com> + +description: | + The Rockchip RK3588 SoC integrates the Synopsys DesignWare DPTX controller + which is compliant with the DisplayPort Specification Version 1.4 with the + following features: + + * DisplayPort 1.4a + * Main Link: 1/2/4 lanes + * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps + * AUX channel 1Mbps + * Single Stream Transport(SST) + * Multistream Transport (MST) + *Type-C support (alternate mode) + * HDCP 2.2, HDCP 1.3 + * Supports up to 8/10 bits per color component + * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0 + * Pixel clock up to 594MHz + * I2S, SPDIF audio interface + +allOf: + - $ref: /schemas/sound/dai-common.yaml# + +properties: + compatible: + enum: + - rockchip,rk3588-dp + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + items: + - description: Peripheral/APB bus clock + - description: DisplayPort AUX clock + - description: HDCP clock + - description: I2S interface clock + - description: SPDIF interfce clock + + clock-names: + items: + - const: apb + - const: aux + - const: hdcp + - const: i2s + - const: spdif + + phys: + maxItems: 1 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/properties/port + description: Video port for RGB/YUV input. + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: Video port for DP output. + + required: + - port@0 + - port@1 + + power-domains: + maxItems: 1 + + resets: + maxItems: 1 + + "#sound-dai-cells": + const: 0 + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - phys + - ports + - resets + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/clock/rockchip,rk3588-cru.h> + #include <dt-bindings/phy/phy.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/power/rk3588-power.h> + #include <dt-bindings/reset/rockchip,rk3588-cru.h> + + soc { + #address-cells = <2>; + #size-cells = <2>; + + dp@fde50000 { + compatible = "rockchip,rk3588-dp"; + reg = <0x0 0xfde50000 0x0 0x4000>; + interrupts = <GIC_SPI 161 IRQ_TYPE_LEVEL_HIGH 0>; + clocks = <&cru PCLK_DP0>, <&cru CLK_AUX16M_0>, + <&cru CLK_DP0>, <&cru MCLK_I2S4_8CH_TX>, + <&cru MCLK_SPDIF2_DP0>; + clock-names = "apb", "aux", "hdcp", "i2s", "spdif"; + assigned-clocks = <&cru CLK_AUX16M_0>; + assigned-clock-rates = <16000000>; + resets = <&cru SRST_DP0>; + phys = <&usbdp_phy0 PHY_TYPE_DP>; + power-domains = <&power RK3588_PD_VO0>; + #sound-dai-cells = <0>; + + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + dp0_in_vp2: endpoint { + remote-endpoint = <&vp2_out_dp0>; + }; + }; + + port@1 { + reg = <1>; + + dp0_out_con0: endpoint { + remote-endpoint = <&dp_con0_in>; + }; + }; + }; + }; + }; -- 2.34.1 ^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH 1/6] dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller 2025-02-23 11:30 ` [PATCH 1/6] dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller Andy Yan @ 2025-02-24 20:03 ` Rob Herring 0 siblings, 0 replies; 32+ messages in thread From: Rob Herring @ 2025-02-24 20:03 UTC (permalink / raw) To: Andy Yan Cc: heiko, hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, sebastian.reichel, Andy Yan On Sun, Feb 23, 2025 at 07:30:24PM +0800, Andy Yan wrote: > From: Andy Yan <andy.yan@rock-chips.com> > > The Rockchip RK3588 SoC integrates the Synopsys DesignWare DPTX > controller. And this DPTX controller need share a USBDP PHY with > the USB 3.0 OTG controller during operation. > > Signed-off-by: Andy Yan <andy.yan@rock-chips.com> > > --- > > .../display/rockchip/rockchip,dw-dp.yaml | 150 ++++++++++++++++++ > 1 file changed, 150 insertions(+) > create mode 100644 Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml > > diff --git a/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml > new file mode 100644 > index 000000000000..b48af8c3e68b > --- /dev/null > +++ b/Documentation/devicetree/bindings/display/rockchip/rockchip,dw-dp.yaml > @@ -0,0 +1,150 @@ > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/display/rockchip/rockchip,dw-dp.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: Rockchip DW DisplayPort Transmitter > + > +maintainers: > + - Andy Yan <andy.yan@rock-chips.com> > + > +description: | > + The Rockchip RK3588 SoC integrates the Synopsys DesignWare DPTX controller > + which is compliant with the DisplayPort Specification Version 1.4 with the > + following features: > + > + * DisplayPort 1.4a > + * Main Link: 1/2/4 lanes > + * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps > + * AUX channel 1Mbps > + * Single Stream Transport(SST) > + * Multistream Transport (MST) > + *Type-C support (alternate mode) ??? ^ Otherwise, Reviewed-by: Rob Herring (Arm) <robh@kernel.org> ^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library 2025-02-23 11:30 [PATCH 0/6] Add support for RK3588 DisplayPort Controller Andy Yan 2025-02-23 11:30 ` [PATCH 1/6] dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller Andy Yan @ 2025-02-23 11:30 ` Andy Yan 2025-03-01 18:14 ` Dmitry Baryshkov 2025-03-09 6:51 ` FUKAUMI Naoki 2025-02-23 11:30 ` [PATCH 3/6] drm/rockchip: Add RK3588 DPTX output support Andy Yan ` (4 subsequent siblings) 6 siblings, 2 replies; 32+ messages in thread From: Andy Yan @ 2025-02-23 11:30 UTC (permalink / raw) To: heiko Cc: hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan From: Andy Yan <andy.yan@rock-chips.com> The DW DP TX Controller is compliant with the DisplayPort Specification Version 1.4 with the following features: * DisplayPort 1.4a * Main Link: 1/2/4 lanes * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps * AUX channel 1Mbps * Single Stream Transport(SST) * Multistream Transport (MST) *Type-C support (alternate mode) * HDCP 2.2, HDCP 1.3 * Supports up to 8/10 bits per color component * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0 * Pixel clock up to 594MHz * I2S, SPDIF audio interface Add library with common helpers to make it can be shared with other SoC. Signed-off-by: Andy Yan <andy.yan@rock-chips.com> drm/bridge: cleanup --- drivers/gpu/drm/bridge/synopsys/Kconfig | 7 + drivers/gpu/drm/bridge/synopsys/Makefile | 1 + drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2155 ++++++++++++++++++++++ include/drm/bridge/dw_dp.h | 19 + 4 files changed, 2182 insertions(+) create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-dp.c create mode 100644 include/drm/bridge/dw_dp.h diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig index f3ab2f985f8c..2c5e532410de 100644 --- a/drivers/gpu/drm/bridge/synopsys/Kconfig +++ b/drivers/gpu/drm/bridge/synopsys/Kconfig @@ -1,4 +1,11 @@ # SPDX-License-Identifier: GPL-2.0-only +config DRM_DW_DP + tristate + select DRM_DISPLAY_HELPER + select DRM_DISPLAY_DP_HELPER + select DRM_KMS_HELPER + select REGMAP_MMIO + config DRM_DW_HDMI tristate select DRM_DISPLAY_HDMI_HELPER diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile index 9dc376d220ad..4dada44029ac 100644 --- a/drivers/gpu/drm/bridge/synopsys/Makefile +++ b/drivers/gpu/drm/bridge/synopsys/Makefile @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_DRM_DW_DP) += dw-dp.o obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o obj-$(CONFIG_DRM_DW_HDMI_GP_AUDIO) += dw-hdmi-gp-audio.o diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c new file mode 100644 index 000000000000..6ecbe9851369 --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c @@ -0,0 +1,2155 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synopsys DesignWare Cores DisplayPort Transmitter Controller + * + * Copyright (c) 2024 Rockchip Electronics Co., Ltd. + * + * Author: Andy Yan <andy.yan@rock-chips.com> + */ +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/iopoll.h> +#include <linux/irq.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/phy/phy.h> +#include <linux/unaligned.h> + +#include <drm/bridge/dw_dp.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_bridge_connector.h> +#include <drm/display/drm_dp_helper.h> +#include <drm/display/drm_hdmi_helper.h> +#include <drm/drm_edid.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> + +#include <uapi/linux/media-bus-format.h> + +#define DW_DP_VERSION_NUMBER 0x0000 +#define DW_DP_VERSION_TYPE 0x0004 +#define DW_DP_ID 0x0008 + +#define DW_DP_CONFIG_REG1 0x0100 +#define DW_DP_CONFIG_REG2 0x0104 +#define DW_DP_CONFIG_REG3 0x0108 + +#define DW_DP_CCTL 0x0200 +#define FORCE_HPD BIT(4) +#define DEFAULT_FAST_LINK_TRAIN_EN BIT(2) +#define ENHANCE_FRAMING_EN BIT(1) +#define SCRAMBLE_DIS BIT(0) +#define DW_DP_SOFT_RESET_CTRL 0x0204 +#define VIDEO_RESET BIT(5) +#define AUX_RESET BIT(4) +#define AUDIO_SAMPLER_RESET BIT(3) +#define HDCP_MODULE_RESET BIT(2) +#define PHY_SOFT_RESET BIT(1) +#define CONTROLLER_RESET BIT(0) + +#define DW_DP_VSAMPLE_CTRL 0x0300 +#define PIXEL_MODE_SELECT GENMASK(22, 21) +#define VIDEO_MAPPING GENMASK(20, 16) +#define VIDEO_STREAM_ENABLE BIT(5) + +#define DW_DP_VSAMPLE_STUFF_CTRL1 0x0304 + +#define DW_DP_VSAMPLE_STUFF_CTRL2 0x0308 + +#define DW_DP_VINPUT_POLARITY_CTRL 0x030c +#define DE_IN_POLARITY BIT(2) +#define HSYNC_IN_POLARITY BIT(1) +#define VSYNC_IN_POLARITY BIT(0) + +#define DW_DP_VIDEO_CONFIG1 0x0310 +#define HACTIVE GENMASK(31, 16) +#define HBLANK GENMASK(15, 2) +#define I_P BIT(1) +#define R_V_BLANK_IN_OSC BIT(0) + +#define DW_DP_VIDEO_CONFIG2 0x0314 +#define VBLANK GENMASK(31, 16) +#define VACTIVE GENMASK(15, 0) + +#define DW_DP_VIDEO_CONFIG3 0x0318 +#define H_SYNC_WIDTH GENMASK(31, 16) +#define H_FRONT_PORCH GENMASK(15, 0) + +#define DW_DP_VIDEO_CONFIG4 0x031c +#define V_SYNC_WIDTH GENMASK(31, 16) +#define V_FRONT_PORCH GENMASK(15, 0) + +#define DW_DP_VIDEO_CONFIG5 0x0320 +#define INIT_THRESHOLD_HI GENMASK(22, 21) +#define AVERAGE_BYTES_PER_TU_FRAC GENMASK(19, 16) +#define INIT_THRESHOLD GENMASK(13, 7) +#define AVERAGE_BYTES_PER_TU GENMASK(6, 0) + +#define DW_DP_VIDEO_MSA1 0x0324 +#define VSTART GENMASK(31, 16) +#define HSTART GENMASK(15, 0) + +#define DW_DP_VIDEO_MSA2 0x0328 +#define MISC0 GENMASK(31, 24) + +#define DW_DP_VIDEO_MSA3 0x032c +#define MISC1 GENMASK(31, 24) + +#define DW_DP_VIDEO_HBLANK_INTERVAL 0x0330 +#define HBLANK_INTERVAL_EN BIT(16) +#define HBLANK_INTERVAL GENMASK(15, 0) + +#define DW_DP_AUD_CONFIG1 0x0400 +#define AUDIO_TIMESTAMP_VERSION_NUM GENMASK(29, 24) +#define AUDIO_PACKET_ID GENMASK(23, 16) +#define AUDIO_MUTE BIT(15) +#define NUM_CHANNELS GENMASK(14, 12) +#define HBR_MODE_ENABLE BIT(10) +#define AUDIO_DATA_WIDTH GENMASK(9, 5) +#define AUDIO_DATA_IN_EN GENMASK(4, 1) +#define AUDIO_INF_SELECT BIT(0) + +#define DW_DP_SDP_VERTICAL_CTRL 0x0500 +#define EN_VERTICAL_SDP BIT(2) +#define EN_AUDIO_STREAM_SDP BIT(1) +#define EN_AUDIO_TIMESTAMP_SDP BIT(0) +#define DW_DP_SDP_HORIZONTAL_CTRL 0x0504 +#define EN_HORIZONTAL_SDP BIT(2) +#define DW_DP_SDP_STATUS_REGISTER 0x0508 +#define DW_DP_SDP_MANUAL_CTRL 0x050c +#define DW_DP_SDP_STATUS_EN 0x0510 + +#define DW_DP_SDP_REGISTER_BANK 0x0600 +#define SDP_REGS GENMASK(31, 0) + +#define DW_DP_PHYIF_CTRL 0x0a00 +#define PHY_WIDTH BIT(25) +#define PHY_POWERDOWN GENMASK(20, 17) +#define PHY_BUSY GENMASK(15, 12) +#define SSC_DIS BIT(16) +#define XMIT_ENABLE GENMASK(11, 8) +#define PHY_LANES GENMASK(7, 6) +#define PHY_RATE GENMASK(5, 4) +#define TPS_SEL GENMASK(3, 0) + +#define DW_DP_PHY_TX_EQ 0x0a04 +#define DW_DP_CUSTOMPAT0 0x0a08 +#define DW_DP_CUSTOMPAT1 0x0a0c +#define DW_DP_CUSTOMPAT2 0x0a10 +#define DW_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET 0x0a14 +#define DW_DP_PHYIF_PWRDOWN_CTRL 0x0a18 + +#define DW_DP_AUX_CMD 0x0b00 +#define AUX_CMD_TYPE GENMASK(31, 28) +#define AUX_ADDR GENMASK(27, 8) +#define I2C_ADDR_ONLY BIT(4) +#define AUX_LEN_REQ GENMASK(3, 0) + +#define DW_DP_AUX_STATUS 0x0b04 +#define AUX_TIMEOUT BIT(17) +#define AUX_BYTES_READ GENMASK(23, 19) +#define AUX_STATUS GENMASK(7, 4) + +#define DW_DP_AUX_DATA0 0x0b08 +#define DW_DP_AUX_DATA1 0x0b0c +#define DW_DP_AUX_DATA2 0x0b10 +#define DW_DP_AUX_DATA3 0x0b14 + +#define DW_DP_GENERAL_INTERRUPT 0x0d00 +#define VIDEO_FIFO_OVERFLOW_STREAM0 BIT(6) +#define AUDIO_FIFO_OVERFLOW_STREAM0 BIT(5) +#define SDP_EVENT_STREAM0 BIT(4) +#define AUX_CMD_INVALID BIT(3) +#define HDCP_EVENT BIT(2) +#define AUX_REPLY_EVENT BIT(1) +#define HPD_EVENT BIT(0) + +#define DW_DP_GENERAL_INTERRUPT_ENABLE 0x0d04 +#define HDCP_EVENT_EN BIT(2) +#define AUX_REPLY_EVENT_EN BIT(1) +#define HPD_EVENT_EN BIT(0) + +#define DW_DP_HPD_STATUS 0x0d08 +#define HPD_STATE GENMASK(11, 9) +#define HPD_STATUS BIT(8) +#define HPD_HOT_UNPLUG BIT(2) +#define HPD_HOT_PLUG BIT(1) +#define HPD_IRQ BIT(0) + +#define DW_DP_HPD_INTERRUPT_ENABLE 0x0d0c +#define HPD_UNPLUG_ERR_EN BIT(3) +#define HPD_UNPLUG_EN BIT(2) +#define HPD_PLUG_EN BIT(1) +#define HPD_IRQ_EN BIT(0) + +#define DW_DP_HDCP_CFG 0x0e00 +#define DPCD12PLUS BIT(7) +#define CP_IRQ BIT(6) +#define BYPENCRYPTION BIT(5) +#define HDCP_LOCK BIT(4) +#define ENCRYPTIONDISABLE BIT(3) +#define ENABLE_HDCP_13 BIT(2) +#define ENABLE_HDCP BIT(1) + +#define DW_DP_HDCP_OBS 0x0e04 +#define HDCP22_RE_AUTHENTICATION_REQ BIT(31) +#define HDCP22_AUTHENTICATION_FAILED BIT(30) +#define HDCP22_AUTHENTICATION_SUCCESS BIT(29) +#define HDCP22_CAPABLE_SINK BIT(28) +#define HDCP22_SINK_CAP_CHECK_COMPLETE BIT(27) +#define HDCP22_STATE GENMASK(26, 24) +#define HDCP22_BOOTED BIT(23) +#define HDCP13_BSTATUS GENMASK(22, 19) +#define REPEATER BIT(18) +#define HDCP_CAPABLE BIT(17) +#define STATEE GENMASK(16, 14) +#define STATEOEG GENMASK(13, 11) +#define STATER GENMASK(10, 8) +#define STATEA GENMASK(7, 4) +#define SUBSTATEA GENMASK(3, 1) +#define HDCPENGAGED BIT(0) + +#define DW_DP_HDCP_APIINTCLR 0x0e08 +#define DW_DP_HDCP_APIINTSTAT 0x0e0c +#define DW_DP_HDCP_APIINTMSK 0x0e10 +#define HDCP22_GPIOINT BIT(8) +#define HDCP_ENGAGED BIT(7) +#define HDCP_FAILED BIT(6) +#define KSVSHA1CALCDONEINT BIT(5) +#define AUXRESPNACK7TIMES BIT(4) +#define AUXRESPTIMEOUT BIT(3) +#define AUXRESPDEFER7TIMES BIT(2) +#define KSVACCESSINT BIT(0) + +#define DW_DP_HDCP_KSVMEMCTRL 0x0e18 +#define KSVSHA1STATUS BIT(4) +#define KSVMEMACCESS BIT(1) +#define KSVMEMREQUEST BIT(0) + +#define DW_DP_HDCP_REG_BKSV0 0x3600 +#define DW_DP_HDCP_REG_BKSV1 0x3604 +#define DW_DP_HDCP_REG_ANCONF 0x3608 +#define AN_BYPASS BIT(0) + +#define DW_DP_HDCP_REG_AN0 0x360c +#define DW_DP_HDCP_REG_AN1 0x3610 +#define DW_DP_HDCP_REG_RMLCTL 0x3614 +#define ODPK_DECRYPT_ENABLE BIT(0) + +#define DW_DP_HDCP_REG_RMLSTS 0x3618 +#define IDPK_WR_OK_STS BIT(6) +#define IDPK_DATA_INDEX GENMASK(5, 0) +#define DW_DP_HDCP_REG_SEED 0x361c +#define DW_DP_HDCP_REG_DPK0 0x3620 +#define DW_DP_HDCP_REG_DPK1 0x3624 +#define DW_DP_HDCP22_GPIOSTS 0x3628 +#define DW_DP_HDCP22_GPIOCHNGSTS 0x362c +#define DW_DP_HDCP_REG_DPK_CRC 0x3630 + +#define DW_DP_MAX_REGISTER DW_DP_HDCP_REG_DPK_CRC + +#define SDP_REG_BANK_SIZE 16 + +struct dw_dp_link_caps { + bool enhanced_framing; + bool tps3_supported; + bool tps4_supported; + bool fast_training; + bool channel_coding; + bool ssc; +}; + +struct dw_dp_link_train_set { + unsigned int voltage_swing[4]; + unsigned int pre_emphasis[4]; + bool voltage_max_reached[4]; + bool pre_max_reached[4]; +}; + +struct dw_dp_link_train { + struct dw_dp_link_train_set request; + struct dw_dp_link_train_set adjust; + bool clock_recovered; + bool channel_equalized; +}; + +struct dw_dp_link { + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + unsigned char revision; + unsigned int rate; + unsigned int lanes; + u8 sink_count; + u8 vsc_sdp_supported; + struct dw_dp_link_caps caps; + struct dw_dp_link_train train; + struct drm_dp_desc desc; +}; + +struct dw_dp_video { + struct drm_display_mode mode; + u8 video_mapping; + u8 pixel_mode; + u8 color_format; + u8 bpc; + u8 bpp; +}; + +struct dw_dp_sdp { + struct dp_sdp_header header; + u8 db[32]; + unsigned long flags; +}; + +struct dw_dp_hotplug { + bool long_hpd; +}; + +struct dw_dp { + struct drm_bridge bridge; + struct device *dev; + struct regmap *regmap; + struct phy *phy; + struct clk *apb_clk; + struct clk *aux_clk; + struct clk *i2s_clk; + struct clk *spdif_clk; + struct clk *hdcp_clk; + struct reset_control *rstc; + struct completion complete; + int irq; + struct work_struct hpd_work; + struct dw_dp_hotplug hotplug; + struct mutex irq_lock; + + struct drm_dp_aux aux; + + struct dw_dp_link link; + struct dw_dp_video video; + const struct dw_dp_plat_data *plat_data; + + DECLARE_BITMAP(sdp_reg_bank, SDP_REG_BANK_SIZE); +}; + +enum { + DW_DP_RGB_6BIT, + DW_DP_RGB_8BIT, + DW_DP_RGB_10BIT, + DW_DP_RGB_12BIT, + DW_DP_RGB_16BIT, + DW_DP_YCBCR444_8BIT, + DW_DP_YCBCR444_10BIT, + DW_DP_YCBCR444_12BIT, + DW_DP_YCBCR444_16BIT, + DW_DP_YCBCR422_8BIT, + DW_DP_YCBCR422_10BIT, + DW_DP_YCBCR422_12BIT, + DW_DP_YCBCR422_16BIT, + DW_DP_YCBCR420_8BIT, + DW_DP_YCBCR420_10BIT, + DW_DP_YCBCR420_12BIT, + DW_DP_YCBCR420_16BIT, +}; + +enum { + DW_DP_MP_SINGLE_PIXEL, + DW_DP_MP_DUAL_PIXEL, + DW_DP_MP_QUAD_PIXEL, +}; + +enum { + DW_DP_SDP_VERTICAL_INTERVAL = BIT(0), + DW_DP_SDP_HORIZONTAL_INTERVAL = BIT(1), +}; + +enum { + DW_DP_HPD_STATE_IDLE, + DW_DP_HPD_STATE_UNPLUG, + DP_DP_HPD_STATE_TIMEOUT = 4, + DW_DP_HPD_STATE_PLUG = 7 +}; + +enum { + DW_DP_PHY_PATTERN_NONE, + DW_DP_PHY_PATTERN_TPS_1, + DW_DP_PHY_PATTERN_TPS_2, + DW_DP_PHY_PATTERN_TPS_3, + DW_DP_PHY_PATTERN_TPS_4, + DW_DP_PHY_PATTERN_SERM, + DW_DP_PHY_PATTERN_PBRS7, + DW_DP_PHY_PATTERN_CUSTOM_80BIT, + DW_DP_PHY_PATTERN_CP2520_1, + DW_DP_PHY_PATTERN_CP2520_2, +}; + +struct dw_dp_output_format { + u32 bus_format; + u32 color_format; + u8 video_mapping; + u8 bpc; + u8 bpp; +}; + +static const struct dw_dp_output_format dw_dp_output_formats[] = { + { MEDIA_BUS_FMT_RGB101010_1X30, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_10BIT, 10, 30 }, + { MEDIA_BUS_FMT_RGB888_1X24, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_8BIT, 8, 24 }, + { MEDIA_BUS_FMT_YUV10_1X30, DRM_COLOR_FORMAT_YCBCR444, DW_DP_YCBCR444_10BIT, 10, 30 }, + { MEDIA_BUS_FMT_YUV8_1X24, DRM_COLOR_FORMAT_YCBCR444, DW_DP_YCBCR444_8BIT, 8, 24}, + { MEDIA_BUS_FMT_YUYV10_1X20, DRM_COLOR_FORMAT_YCBCR422, DW_DP_YCBCR422_10BIT, 10, 20 }, + { MEDIA_BUS_FMT_YUYV8_1X16, DRM_COLOR_FORMAT_YCBCR422, DW_DP_YCBCR422_8BIT, 8, 16 }, + { MEDIA_BUS_FMT_UYYVYY10_0_5X30, DRM_COLOR_FORMAT_YCBCR420, DW_DP_YCBCR420_10BIT, 10, 15 }, + { MEDIA_BUS_FMT_UYYVYY8_0_5X24, DRM_COLOR_FORMAT_YCBCR420, DW_DP_YCBCR420_8BIT, 8, 12 }, + { MEDIA_BUS_FMT_RGB666_1X24_CPADHI, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_6BIT, 6, 18 }, +}; + +static const struct dw_dp_output_format *dw_dp_get_output_format(u32 bus_format) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) + if (dw_dp_output_formats[i].bus_format == bus_format) + return &dw_dp_output_formats[i]; + + return &dw_dp_output_formats[1]; +} + +static inline struct dw_dp *bridge_to_dp(struct drm_bridge *b) +{ + return container_of(b, struct dw_dp, bridge); +} + +static inline void dw_dp_phy_set_pattern(struct dw_dp *dp, u32 pattern) +{ + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, TPS_SEL, + FIELD_PREP(TPS_SEL, pattern)); +} + +static void dw_dp_phy_xmit_enable(struct dw_dp *dp, u32 lanes) +{ + u32 xmit_enable; + + switch (lanes) { + case 4: + case 2: + case 1: + xmit_enable = GENMASK(lanes - 1, 0); + break; + case 0: + default: + xmit_enable = 0; + break; + } + + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, XMIT_ENABLE, + FIELD_PREP(XMIT_ENABLE, xmit_enable)); +} + +static bool dw_dp_bandwidth_ok(struct dw_dp *dp, + const struct drm_display_mode *mode, u32 bpp, + unsigned int lanes, unsigned int rate) +{ + u32 max_bw, req_bw; + + req_bw = mode->clock * bpp / 8; + max_bw = lanes * rate; + if (req_bw > max_bw) + return false; + + return true; +} + +static bool dw_dp_hpd_detect(struct dw_dp *dp) +{ + u32 value; + + regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value); + + return FIELD_GET(HPD_STATE, value) == DW_DP_HPD_STATE_PLUG; +} + +static void dw_dp_link_caps_reset(struct dw_dp_link_caps *caps) +{ + caps->enhanced_framing = false; + caps->tps3_supported = false; + caps->tps4_supported = false; + caps->fast_training = false; + caps->channel_coding = false; +} + +static void dw_dp_link_reset(struct dw_dp_link *link) +{ + link->vsc_sdp_supported = 0; + link->sink_count = 0; + link->revision = 0; + link->rate = 0; + link->lanes = 0; + + dw_dp_link_caps_reset(&link->caps); + memset(link->dpcd, 0, sizeof(link->dpcd)); +} + +static int dw_dp_link_power_up(struct dw_dp *dp) +{ + struct dw_dp_link *link = &dp->link; + int ret; + u8 value; + + if (link->revision < DP_DPCD_REV_11) + return 0; + + ret = drm_dp_dpcd_readb(&dp->aux, DP_SET_POWER, &value); + if (ret < 0) + return ret; + + value &= ~DP_SET_POWER_MASK; + value |= DP_SET_POWER_D0; + + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, value); + if (ret < 0) + return ret; + + usleep_range(1000, 2000); + + return 0; +} + +static int dw_dp_link_power_down(struct dw_dp *dp) +{ + struct dw_dp_link *link = &dp->link; + int ret; + u8 value; + + if (link->revision < DP_DPCD_REV_11) + return 0; + + ret = drm_dp_dpcd_readb(&dp->aux, DP_SET_POWER, &value); + if (ret < 0) + return ret; + + value &= ~DP_SET_POWER_MASK; + value |= DP_SET_POWER_D3; + + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, value); + if (ret < 0) + return ret; + + return 0; +} + +static bool dw_dp_has_sink_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE], + const struct drm_dp_desc *desc) +{ + return 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); +} + +static int dw_dp_link_parse(struct dw_dp *dp) +{ + struct dw_dp_link *link = &dp->link; + u8 dpcd; + int ret; + + dw_dp_link_reset(link); + + ret = drm_dp_read_dpcd_caps(&dp->aux, link->dpcd); + if (ret < 0) + return ret; + + drm_dp_read_desc(&dp->aux, &link->desc, drm_dp_is_branch(link->dpcd)); + + if (dw_dp_has_sink_count(link->dpcd, &link->desc)) { + ret = drm_dp_read_sink_count(&dp->aux); + if (ret < 0) + return ret; + + link->sink_count = ret; + + /* Dongle connected, but no display */ + if (!link->sink_count) + return -ENODEV; + } + + ret = drm_dp_dpcd_readb(&dp->aux, DP_DPRX_FEATURE_ENUMERATION_LIST, &dpcd); + if (ret < 0) + return ret; + + link->vsc_sdp_supported = !!(dpcd & DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED); + + link->revision = link->dpcd[DP_DPCD_REV]; + link->rate = min_t(u32, min(dp->plat_data->max_link_rate, + dp->phy->attrs.max_link_rate * 100), + drm_dp_max_link_rate(link->dpcd)); + link->lanes = min_t(u8, phy_get_bus_width(dp->phy), + drm_dp_max_lane_count(link->dpcd)); + + link->caps.enhanced_framing = drm_dp_enhanced_frame_cap(link->dpcd); + link->caps.tps3_supported = drm_dp_tps3_supported(link->dpcd); + link->caps.tps4_supported = drm_dp_tps4_supported(link->dpcd); + link->caps.fast_training = drm_dp_fast_training_cap(link->dpcd); + link->caps.channel_coding = drm_dp_channel_coding_supported(link->dpcd); + link->caps.ssc = !!(link->dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5); + + return 0; +} + +static int dw_dp_phy_update_vs_emph(struct dw_dp *dp, unsigned int rate, unsigned int lanes, + struct dw_dp_link_train_set *train_set) +{ + union phy_configure_opts phy_cfg; + unsigned int *vs, *pe; + u8 buf[4]; + int i, ret; + + vs = train_set->voltage_swing; + pe = train_set->pre_emphasis; + + for (i = 0; i < lanes; i++) { + phy_cfg.dp.voltage[i] = vs[i]; + phy_cfg.dp.pre[i] = pe[i]; + } + + phy_cfg.dp.lanes = lanes; + phy_cfg.dp.link_rate = rate / 100; + phy_cfg.dp.set_lanes = false; + phy_cfg.dp.set_rate = false; + phy_cfg.dp.set_voltages = true; + + ret = phy_configure(dp->phy, &phy_cfg); + if (ret) + return ret; + + for (i = 0; i < lanes; i++) { + buf[i] = (vs[i] << DP_TRAIN_VOLTAGE_SWING_SHIFT) | + (pe[i] << DP_TRAIN_PRE_EMPHASIS_SHIFT); + if (train_set->voltage_max_reached[i]) + buf[i] |= DP_TRAIN_MAX_SWING_REACHED; + if (train_set->pre_max_reached[i]) + buf[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + } + + ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, buf, lanes); + if (ret < 0) + return ret; + + return 0; +} + +static int dw_dp_link_train_update_vs_emph(struct dw_dp *dp) +{ + struct dw_dp_link *link = &dp->link; + struct dw_dp_link_train_set *request = &link->train.request; + + return dw_dp_phy_update_vs_emph(dp, dp->link.rate, dp->link.lanes, request); +} + +static int dw_dp_phy_configure(struct dw_dp *dp, unsigned int rate, + unsigned int lanes, bool ssc) +{ + union phy_configure_opts phy_cfg; + int ret; + + /* Move PHY to P3 */ + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_POWERDOWN, + FIELD_PREP(PHY_POWERDOWN, 0x3)); + + phy_cfg.dp.lanes = lanes; + phy_cfg.dp.link_rate = rate / 100; + phy_cfg.dp.ssc = ssc; + phy_cfg.dp.set_lanes = true; + phy_cfg.dp.set_rate = true; + phy_cfg.dp.set_voltages = false; + ret = phy_configure(dp->phy, &phy_cfg); + if (ret) + return ret; + + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_LANES, + FIELD_PREP(PHY_LANES, lanes / 2)); + + /* Move PHY to P0 */ + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_POWERDOWN, + FIELD_PREP(PHY_POWERDOWN, 0x0)); + + dw_dp_phy_xmit_enable(dp, lanes); + + return 0; +} + +static int dw_dp_link_configure(struct dw_dp *dp) +{ + struct dw_dp_link *link = &dp->link; + u8 buf[2]; + int ret; + + ret = dw_dp_phy_configure(dp, link->rate, link->lanes, link->caps.ssc); + if (ret) + return ret; + + buf[0] = drm_dp_link_rate_to_bw_code(link->rate); + buf[1] = link->lanes; + + if (link->caps.enhanced_framing) { + buf[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + regmap_update_bits(dp->regmap, DW_DP_CCTL, ENHANCE_FRAMING_EN, + FIELD_PREP(ENHANCE_FRAMING_EN, 1)); + } else { + regmap_update_bits(dp->regmap, DW_DP_CCTL, ENHANCE_FRAMING_EN, + FIELD_PREP(ENHANCE_FRAMING_EN, 0)); + } + + ret = drm_dp_dpcd_write(&dp->aux, DP_LINK_BW_SET, buf, sizeof(buf)); + if (ret < 0) + return ret; + + buf[0] = link->caps.ssc ? DP_SPREAD_AMP_0_5 : 0; + buf[1] = link->caps.channel_coding ? DP_SET_ANSI_8B10B : 0; + + ret = drm_dp_dpcd_write(&dp->aux, DP_DOWNSPREAD_CTRL, buf, sizeof(buf)); + if (ret < 0) + return ret; + + return 0; +} + +static void dw_dp_link_train_init(struct dw_dp_link_train *train) +{ + struct dw_dp_link_train_set *request = &train->request; + struct dw_dp_link_train_set *adjust = &train->adjust; + unsigned int i; + + for (i = 0; i < 4; i++) { + request->voltage_swing[i] = 0; + adjust->voltage_swing[i] = 0; + + request->pre_emphasis[i] = 0; + adjust->pre_emphasis[i] = 0; + + request->voltage_max_reached[i] = false; + adjust->voltage_max_reached[i] = false; + + request->pre_max_reached[i] = false; + adjust->pre_max_reached[i] = false; + } + + train->clock_recovered = false; + train->channel_equalized = false; +} + +static bool dw_dp_link_train_valid(const struct dw_dp_link_train *train) +{ + return train->clock_recovered && train->channel_equalized; +} + +static int dw_dp_link_train_set_pattern(struct dw_dp *dp, u32 pattern) +{ + u8 buf = 0; + int ret; + + if (pattern && pattern != DP_TRAINING_PATTERN_4) { + buf |= DP_LINK_SCRAMBLING_DISABLE; + + regmap_update_bits(dp->regmap, DW_DP_CCTL, SCRAMBLE_DIS, + FIELD_PREP(SCRAMBLE_DIS, 1)); + } else { + regmap_update_bits(dp->regmap, DW_DP_CCTL, SCRAMBLE_DIS, + FIELD_PREP(SCRAMBLE_DIS, 0)); + } + + switch (pattern) { + case DP_TRAINING_PATTERN_DISABLE: + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_NONE); + break; + case DP_TRAINING_PATTERN_1: + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_1); + break; + case DP_TRAINING_PATTERN_2: + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_2); + break; + case DP_TRAINING_PATTERN_3: + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_3); + break; + case DP_TRAINING_PATTERN_4: + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_4); + break; + default: + return -EINVAL; + } + + ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, + buf | pattern); + if (ret < 0) + return ret; + + return 0; +} + +static u8 dw_dp_voltage_max(u8 preemph) +{ + switch (preemph & DP_TRAIN_PRE_EMPHASIS_MASK) { + case DP_TRAIN_PRE_EMPH_LEVEL_0: + return DP_TRAIN_VOLTAGE_SWING_LEVEL_3; + case DP_TRAIN_PRE_EMPH_LEVEL_1: + return DP_TRAIN_VOLTAGE_SWING_LEVEL_2; + case DP_TRAIN_PRE_EMPH_LEVEL_2: + return DP_TRAIN_VOLTAGE_SWING_LEVEL_1; + case DP_TRAIN_PRE_EMPH_LEVEL_3: + default: + return DP_TRAIN_VOLTAGE_SWING_LEVEL_0; + } +} + +static void dw_dp_link_get_adjustments(struct dw_dp_link *link, + u8 status[DP_LINK_STATUS_SIZE]) +{ + struct dw_dp_link_train_set *adjust = &link->train.adjust; + u8 v = 0; + u8 p = 0; + unsigned int i; + + for (i = 0; i < link->lanes; i++) { + v = drm_dp_get_adjust_request_voltage(status, i); + p = drm_dp_get_adjust_request_pre_emphasis(status, i); + if (p >= DP_TRAIN_PRE_EMPH_LEVEL_3) { + adjust->pre_emphasis[i] = DP_TRAIN_PRE_EMPH_LEVEL_3 >> + DP_TRAIN_PRE_EMPHASIS_SHIFT; + adjust->pre_max_reached[i] = true; + } else { + adjust->pre_emphasis[i] = p >> DP_TRAIN_PRE_EMPHASIS_SHIFT; + adjust->pre_max_reached[i] = false; + } + v = min(v, dw_dp_voltage_max(p)); + if (v >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3) { + adjust->voltage_swing[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_3 >> + DP_TRAIN_VOLTAGE_SWING_SHIFT; + adjust->voltage_max_reached[i] = true; + } else { + adjust->voltage_swing[i] = v >> DP_TRAIN_VOLTAGE_SWING_SHIFT; + adjust->voltage_max_reached[i] = false; + } + } +} + +static void dw_dp_link_train_adjust(struct dw_dp_link_train *train) +{ + struct dw_dp_link_train_set *request = &train->request; + struct dw_dp_link_train_set *adjust = &train->adjust; + unsigned int i; + + for (i = 0; i < 4; i++) { + if (request->voltage_swing[i] != adjust->voltage_swing[i]) + request->voltage_swing[i] = adjust->voltage_swing[i]; + if (request->voltage_max_reached[i] != adjust->voltage_max_reached[i]) + request->voltage_max_reached[i] = adjust->voltage_max_reached[i]; + } + + for (i = 0; i < 4; i++) { + if (request->pre_emphasis[i] != adjust->pre_emphasis[i]) + request->pre_emphasis[i] = adjust->pre_emphasis[i]; + if (request->pre_max_reached[i] != adjust->pre_max_reached[i]) + request->pre_max_reached[i] = adjust->pre_max_reached[i]; + } +} + +static int dw_dp_link_clock_recovery(struct dw_dp *dp) +{ + struct dw_dp_link *link = &dp->link; + u8 status[DP_LINK_STATUS_SIZE]; + unsigned int tries = 0; + int ret; + + ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1); + if (ret) + return ret; + + for (;;) { + ret = dw_dp_link_train_update_vs_emph(dp); + if (ret) + return ret; + + drm_dp_link_train_clock_recovery_delay(&dp->aux, link->dpcd); + + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); + if (ret < 0) { + dev_err(dp->dev, "failed to read link status: %d\n", ret); + return ret; + } + + if (drm_dp_clock_recovery_ok(status, link->lanes)) { + link->train.clock_recovered = true; + break; + } + + dw_dp_link_get_adjustments(link, status); + + if (link->train.request.voltage_swing[0] == + link->train.adjust.voltage_swing[0]) + tries++; + else + tries = 0; + + if (tries == 5) + break; + + dw_dp_link_train_adjust(&link->train); + } + + return 0; +} + +static int dw_dp_link_channel_equalization(struct dw_dp *dp) +{ + struct dw_dp_link *link = &dp->link; + u8 status[DP_LINK_STATUS_SIZE], pattern; + unsigned int tries; + int ret; + + if (link->caps.tps4_supported) + pattern = DP_TRAINING_PATTERN_4; + else if (link->caps.tps3_supported) + pattern = DP_TRAINING_PATTERN_3; + else + pattern = DP_TRAINING_PATTERN_2; + ret = dw_dp_link_train_set_pattern(dp, pattern); + if (ret) + return ret; + + for (tries = 1; tries < 5; tries++) { + ret = dw_dp_link_train_update_vs_emph(dp); + if (ret) + return ret; + + drm_dp_link_train_channel_eq_delay(&dp->aux, link->dpcd); + + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); + if (ret < 0) + return ret; + + if (!drm_dp_clock_recovery_ok(status, link->lanes)) { + dev_err(dp->dev, "clock recovery lost while equalizing channel\n"); + link->train.clock_recovered = false; + break; + } + + if (drm_dp_channel_eq_ok(status, link->lanes)) { + link->train.channel_equalized = true; + break; + } + + dw_dp_link_get_adjustments(link, status); + dw_dp_link_train_adjust(&link->train); + } + + return 0; +} + +static int dw_dp_link_downgrade(struct dw_dp *dp) +{ + struct dw_dp_link *link = &dp->link; + struct dw_dp_video *video = &dp->video; + + switch (link->rate) { + case 162000: + return -EINVAL; + case 270000: + link->rate = 162000; + break; + case 540000: + link->rate = 270000; + break; + case 810000: + link->rate = 540000; + break; + } + + if (!dw_dp_bandwidth_ok(dp, &video->mode, video->bpp, link->lanes, + link->rate)) + return -E2BIG; + + return 0; +} + +static int dw_dp_link_train_full(struct dw_dp *dp) +{ + struct dw_dp_link *link = &dp->link; + int ret; + +retry: + dw_dp_link_train_init(&link->train); + + dev_dbg(dp->dev, "full-training link: %u lane%s at %u MHz\n", + link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100); + + ret = dw_dp_link_configure(dp); + if (ret < 0) { + dev_err(dp->dev, "failed to configure DP link: %d\n", ret); + return ret; + } + + ret = dw_dp_link_clock_recovery(dp); + if (ret < 0) { + dev_err(dp->dev, "clock recovery failed: %d\n", ret); + goto out; + } + + if (!link->train.clock_recovered) { + dev_err(dp->dev, "clock recovery failed, downgrading link\n"); + + ret = dw_dp_link_downgrade(dp); + if (ret < 0) + goto out; + else + goto retry; + } + + dev_dbg(dp->dev, "clock recovery succeeded\n"); + + ret = dw_dp_link_channel_equalization(dp); + if (ret < 0) { + dev_err(dp->dev, "channel equalization failed: %d\n", ret); + goto out; + } + + if (!link->train.channel_equalized) { + dev_err(dp->dev, "channel equalization failed, downgrading link\n"); + + ret = dw_dp_link_downgrade(dp); + if (ret < 0) + goto out; + else + goto retry; + } + + dev_dbg(dp->dev, "channel equalization succeeded\n"); + +out: + dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE); + return ret; +} + +static int dw_dp_link_train_fast(struct dw_dp *dp) +{ + struct dw_dp_link *link = &dp->link; + int ret; + u8 status[DP_LINK_STATUS_SIZE]; + u8 pattern; + + dw_dp_link_train_init(&link->train); + + dev_dbg(dp->dev, "fast-training link: %u lane%s at %u MHz\n", + link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100); + + ret = dw_dp_link_configure(dp); + if (ret < 0) { + dev_err(dp->dev, "failed to configure DP link: %d\n", ret); + return ret; + } + + ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1); + if (ret) + goto out; + + usleep_range(500, 1000); + + if (link->caps.tps4_supported) + pattern = DP_TRAINING_PATTERN_4; + else if (link->caps.tps3_supported) + pattern = DP_TRAINING_PATTERN_3; + else + pattern = DP_TRAINING_PATTERN_2; + ret = dw_dp_link_train_set_pattern(dp, pattern); + if (ret) + goto out; + + usleep_range(500, 1000); + + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); + if (ret < 0) { + dev_err(dp->dev, "failed to read link status: %d\n", ret); + goto out; + } + + if (!drm_dp_clock_recovery_ok(status, link->lanes)) { + dev_err(dp->dev, "clock recovery failed\n"); + ret = -EIO; + goto out; + } + + if (!drm_dp_channel_eq_ok(status, link->lanes)) { + dev_err(dp->dev, "channel equalization failed\n"); + ret = -EIO; + goto out; + } + +out: + dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE); + return ret; +} + +static int dw_dp_link_train(struct dw_dp *dp) +{ + struct dw_dp_link *link = &dp->link; + int ret; + + if (link->caps.fast_training) { + if (dw_dp_link_train_valid(&link->train)) { + ret = dw_dp_link_train_fast(dp); + if (ret < 0) + dev_err(dp->dev, "fast link training failed: %d\n", ret); + else + return 0; + } + } + + ret = dw_dp_link_train_full(dp); + if (ret < 0) { + dev_err(dp->dev, "full link training failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int dw_dp_send_sdp(struct dw_dp *dp, struct dw_dp_sdp *sdp) +{ + const u8 *payload = sdp->db; + u32 reg; + int i, nr; + + nr = find_first_zero_bit(dp->sdp_reg_bank, SDP_REG_BANK_SIZE); + if (nr < SDP_REG_BANK_SIZE) + set_bit(nr, dp->sdp_reg_bank); + else + return -EBUSY; + + reg = DW_DP_SDP_REGISTER_BANK + nr * 9 * 4; + + /* SDP header */ + regmap_write(dp->regmap, reg, get_unaligned_le32(&sdp->header)); + + /* SDP data payload */ + for (i = 1; i < 9; i++, payload += 4) + regmap_write(dp->regmap, reg + i * 4, + FIELD_PREP(SDP_REGS, get_unaligned_le32(payload))); + + if (sdp->flags & DW_DP_SDP_VERTICAL_INTERVAL) + regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL, + EN_VERTICAL_SDP << nr, + EN_VERTICAL_SDP << nr); + + if (sdp->flags & DW_DP_SDP_HORIZONTAL_INTERVAL) + regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL, + EN_HORIZONTAL_SDP << nr, + EN_HORIZONTAL_SDP << nr); + + return 0; +} + +static void dw_dp_vsc_sdp_pack(const struct drm_dp_vsc_sdp *vsc, + struct dw_dp_sdp *sdp) +{ + sdp->header.HB0 = 0; + sdp->header.HB1 = DP_SDP_VSC; + sdp->header.HB2 = vsc->revision; + sdp->header.HB3 = vsc->length; + + sdp->db[16] = (vsc->pixelformat & 0xf) << 4; + sdp->db[16] |= vsc->colorimetry & 0xf; + + switch (vsc->bpc) { + case 8: + sdp->db[17] = 0x1; + break; + case 10: + sdp->db[17] = 0x2; + break; + case 12: + sdp->db[17] = 0x3; + break; + case 16: + sdp->db[17] = 0x4; + break; + case 6: + default: + break; + } + + if (vsc->dynamic_range == DP_DYNAMIC_RANGE_CTA) + sdp->db[17] |= 0x80; + + sdp->db[18] = vsc->content_type & 0x7; + + sdp->flags |= DW_DP_SDP_VERTICAL_INTERVAL; +} + +static int dw_dp_send_vsc_sdp(struct dw_dp *dp) +{ + struct dw_dp_video *video = &dp->video; + struct drm_dp_vsc_sdp vsc = {}; + struct dw_dp_sdp sdp = {}; + + vsc.revision = 0x5; + vsc.length = 0x13; + + switch (video->color_format) { + case DRM_COLOR_FORMAT_YCBCR444: + vsc.pixelformat = DP_PIXELFORMAT_YUV444; + break; + case DRM_COLOR_FORMAT_YCBCR420: + vsc.pixelformat = DP_PIXELFORMAT_YUV420; + break; + case DRM_COLOR_FORMAT_YCBCR422: + vsc.pixelformat = DP_PIXELFORMAT_YUV422; + break; + case DRM_COLOR_FORMAT_RGB444: + default: + vsc.pixelformat = DP_PIXELFORMAT_RGB; + break; + } + + if (video->color_format == DRM_COLOR_FORMAT_RGB444) { + vsc.colorimetry = DP_COLORIMETRY_DEFAULT; + vsc.dynamic_range = DP_DYNAMIC_RANGE_VESA; + } else { + vsc.colorimetry = DP_COLORIMETRY_BT709_YCC; + vsc.dynamic_range = DP_DYNAMIC_RANGE_CTA; + } + + vsc.bpc = video->bpc; + vsc.content_type = DP_CONTENT_TYPE_NOT_DEFINED; + + dw_dp_vsc_sdp_pack(&vsc, &sdp); + + return dw_dp_send_sdp(dp, &sdp); +} + +static int dw_dp_video_set_pixel_mode(struct dw_dp *dp, u8 pixel_mode) +{ + switch (pixel_mode) { + case DW_DP_MP_SINGLE_PIXEL: + case DW_DP_MP_DUAL_PIXEL: + case DW_DP_MP_QUAD_PIXEL: + break; + default: + return -EINVAL; + } + + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, PIXEL_MODE_SELECT, + FIELD_PREP(PIXEL_MODE_SELECT, pixel_mode)); + + return 0; +} + +static bool dw_dp_video_need_vsc_sdp(struct dw_dp *dp) +{ + struct dw_dp_link *link = &dp->link; + struct dw_dp_video *video = &dp->video; + + if (!link->vsc_sdp_supported) + return false; + + if (video->color_format == DRM_COLOR_FORMAT_YCBCR420) + return true; + + return false; +} + +static int dw_dp_video_set_msa(struct dw_dp *dp, u8 color_format, u8 bpc, + u16 vstart, u16 hstart) +{ + u16 misc = 0; + + if (dw_dp_video_need_vsc_sdp(dp)) + misc |= DP_MSA_MISC_COLOR_VSC_SDP; + + switch (color_format) { + case DRM_COLOR_FORMAT_RGB444: + misc |= DP_MSA_MISC_COLOR_RGB; + break; + case DRM_COLOR_FORMAT_YCBCR444: + misc |= DP_MSA_MISC_COLOR_YCBCR_444_BT709; + break; + case DRM_COLOR_FORMAT_YCBCR422: + misc |= DP_MSA_MISC_COLOR_YCBCR_422_BT709; + break; + case DRM_COLOR_FORMAT_YCBCR420: + break; + default: + return -EINVAL; + } + + switch (bpc) { + case 6: + misc |= DP_MSA_MISC_6_BPC; + break; + case 8: + misc |= DP_MSA_MISC_8_BPC; + break; + case 10: + misc |= DP_MSA_MISC_10_BPC; + break; + case 12: + misc |= DP_MSA_MISC_12_BPC; + break; + case 16: + misc |= DP_MSA_MISC_16_BPC; + break; + default: + return -EINVAL; + } + + regmap_write(dp->regmap, DW_DP_VIDEO_MSA1, + FIELD_PREP(VSTART, vstart) | FIELD_PREP(HSTART, hstart)); + regmap_write(dp->regmap, DW_DP_VIDEO_MSA2, FIELD_PREP(MISC0, misc)); + regmap_write(dp->regmap, DW_DP_VIDEO_MSA3, FIELD_PREP(MISC1, misc >> 8)); + + return 0; +} + +static void dw_dp_video_disable(struct dw_dp *dp) +{ + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE, + FIELD_PREP(VIDEO_STREAM_ENABLE, 0)); +} + +static int dw_dp_video_enable(struct dw_dp *dp) +{ + struct dw_dp_video *video = &dp->video; + struct dw_dp_link *link = &dp->link; + struct drm_display_mode *mode = &video->mode; + u8 color_format = video->color_format; + u8 bpc = video->bpc; + u8 pixel_mode = video->pixel_mode; + u8 bpp = video->bpp, init_threshold, vic; + u32 hactive, hblank, h_sync_width, h_front_porch; + u32 vactive, vblank, v_sync_width, v_front_porch; + u32 vstart = mode->vtotal - mode->vsync_start; + u32 hstart = mode->htotal - mode->hsync_start; + u32 peak_stream_bandwidth, link_bandwidth; + u32 average_bytes_per_tu, average_bytes_per_tu_frac; + u32 ts, hblank_interval; + u32 value; + int ret; + + ret = dw_dp_video_set_pixel_mode(dp, pixel_mode); + if (ret) + return ret; + + ret = dw_dp_video_set_msa(dp, color_format, bpc, vstart, hstart); + if (ret) + return ret; + + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_MAPPING, + FIELD_PREP(VIDEO_MAPPING, video->video_mapping)); + + /* Configure DW_DP_VINPUT_POLARITY_CTRL register */ + value = 0; + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + value |= FIELD_PREP(HSYNC_IN_POLARITY, 1); + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + value |= FIELD_PREP(VSYNC_IN_POLARITY, 1); + regmap_write(dp->regmap, DW_DP_VINPUT_POLARITY_CTRL, value); + + /* Configure DW_DP_VIDEO_CONFIG1 register */ + hactive = mode->hdisplay; + hblank = mode->htotal - mode->hdisplay; + value = FIELD_PREP(HACTIVE, hactive) | FIELD_PREP(HBLANK, hblank); + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + value |= FIELD_PREP(I_P, 1); + vic = drm_match_cea_mode(mode); + if (vic == 5 || vic == 6 || vic == 7 || + vic == 10 || vic == 11 || vic == 20 || + vic == 21 || vic == 22 || vic == 39 || + vic == 25 || vic == 26 || vic == 40 || + vic == 44 || vic == 45 || vic == 46 || + vic == 50 || vic == 51 || vic == 54 || + vic == 55 || vic == 58 || vic == 59) + value |= R_V_BLANK_IN_OSC; + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG1, value); + + /* Configure DW_DP_VIDEO_CONFIG2 register */ + vblank = mode->vtotal - mode->vdisplay; + vactive = mode->vdisplay; + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG2, + FIELD_PREP(VBLANK, vblank) | FIELD_PREP(VACTIVE, vactive)); + + /* Configure DW_DP_VIDEO_CONFIG3 register */ + h_sync_width = mode->hsync_end - mode->hsync_start; + h_front_porch = mode->hsync_start - mode->hdisplay; + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG3, + FIELD_PREP(H_SYNC_WIDTH, h_sync_width) | + FIELD_PREP(H_FRONT_PORCH, h_front_porch)); + + /* Configure DW_DP_VIDEO_CONFIG4 register */ + v_sync_width = mode->vsync_end - mode->vsync_start; + v_front_porch = mode->vsync_start - mode->vdisplay; + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG4, + FIELD_PREP(V_SYNC_WIDTH, v_sync_width) | + FIELD_PREP(V_FRONT_PORCH, v_front_porch)); + + /* Configure DW_DP_VIDEO_CONFIG5 register */ + peak_stream_bandwidth = mode->clock * bpp / 8; + link_bandwidth = (link->rate / 1000) * link->lanes; + ts = peak_stream_bandwidth * 64 / link_bandwidth; + average_bytes_per_tu = ts / 1000; + average_bytes_per_tu_frac = ts / 100 - average_bytes_per_tu * 10; + if (pixel_mode == DW_DP_MP_SINGLE_PIXEL) { + if (average_bytes_per_tu < 6) + init_threshold = 32; + else if (hblank <= 80 && color_format != DRM_COLOR_FORMAT_YCBCR420) + init_threshold = 12; + else if (hblank <= 40 && color_format == DRM_COLOR_FORMAT_YCBCR420) + init_threshold = 3; + else + init_threshold = 16; + } else { + u32 t1 = 0, t2 = 0, t3 = 0; + + switch (bpc) { + case 6: + t1 = (4 * 1000 / 9) * link->lanes; + break; + case 8: + if (color_format == DRM_COLOR_FORMAT_YCBCR422) { + t1 = (1000 / 2) * link->lanes; + } else { + if (pixel_mode == DW_DP_MP_DUAL_PIXEL) + t1 = (1000 / 3) * link->lanes; + else + t1 = (3000 / 16) * link->lanes; + } + break; + case 10: + if (color_format == DRM_COLOR_FORMAT_YCBCR422) + t1 = (2000 / 5) * link->lanes; + else + t1 = (4000 / 15) * link->lanes; + break; + case 12: + if (color_format == DRM_COLOR_FORMAT_YCBCR422) { + if (pixel_mode == DW_DP_MP_DUAL_PIXEL) + t1 = (1000 / 6) * link->lanes; + else + t1 = (1000 / 3) * link->lanes; + } else { + t1 = (2000 / 9) * link->lanes; + } + break; + case 16: + if (color_format != DRM_COLOR_FORMAT_YCBCR422 && + pixel_mode == DW_DP_MP_DUAL_PIXEL) + t1 = (1000 / 6) * link->lanes; + else + t1 = (1000 / 4) * link->lanes; + break; + default: + return -EINVAL; + } + + if (color_format == DRM_COLOR_FORMAT_YCBCR420) + t2 = (link->rate / 4) * 1000 / (mode->clock / 2); + else + t2 = (link->rate / 4) * 1000 / mode->clock; + + if (average_bytes_per_tu_frac) + t3 = average_bytes_per_tu + 1; + else + t3 = average_bytes_per_tu; + init_threshold = t1 * t2 * t3 / (1000 * 1000); + if (init_threshold <= 16 || average_bytes_per_tu < 10) + init_threshold = 40; + } + + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG5, + FIELD_PREP(INIT_THRESHOLD_HI, init_threshold >> 6) | + FIELD_PREP(AVERAGE_BYTES_PER_TU_FRAC, average_bytes_per_tu_frac) | + FIELD_PREP(INIT_THRESHOLD, init_threshold) | + FIELD_PREP(AVERAGE_BYTES_PER_TU, average_bytes_per_tu)); + + /* Configure DW_DP_VIDEO_HBLANK_INTERVAL register */ + hblank_interval = hblank * (link->rate / 4) / mode->clock; + regmap_write(dp->regmap, DW_DP_VIDEO_HBLANK_INTERVAL, + FIELD_PREP(HBLANK_INTERVAL_EN, 1) | + FIELD_PREP(HBLANK_INTERVAL, hblank_interval)); + + /* Video stream enable */ + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE, + FIELD_PREP(VIDEO_STREAM_ENABLE, 1)); + + if (dw_dp_video_need_vsc_sdp(dp)) + dw_dp_send_vsc_sdp(dp); + + return 0; +} + +static void dw_dp_hpd_init(struct dw_dp *dp) +{ + /* Enable all HPD interrupts */ + regmap_update_bits(dp->regmap, DW_DP_HPD_INTERRUPT_ENABLE, + HPD_UNPLUG_EN | HPD_PLUG_EN | HPD_IRQ_EN, + FIELD_PREP(HPD_UNPLUG_EN, 1) | + FIELD_PREP(HPD_PLUG_EN, 1) | + FIELD_PREP(HPD_IRQ_EN, 1)); + + /* Enable all top-level interrupts */ + regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE, + HPD_EVENT_EN, FIELD_PREP(HPD_EVENT_EN, 1)); +} + +static void dw_dp_aux_init(struct dw_dp *dp) +{ + regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE, + AUX_REPLY_EVENT_EN, FIELD_PREP(AUX_REPLY_EVENT_EN, 1)); +} + +static void dw_dp_init_hw(struct dw_dp *dp) +{ + regmap_update_bits(dp->regmap, DW_DP_CCTL, DEFAULT_FAST_LINK_TRAIN_EN, + FIELD_PREP(DEFAULT_FAST_LINK_TRAIN_EN, 0)); + + dw_dp_hpd_init(dp); + dw_dp_aux_init(dp); +} + +static int dw_dp_aux_write_data(struct dw_dp *dp, const u8 *buffer, size_t size) +{ + size_t i, j; + + for (i = 0; i < DIV_ROUND_UP(size, 4); i++) { + size_t num = min_t(size_t, size - i * 4, 4); + u32 value = 0; + + for (j = 0; j < num; j++) + value |= buffer[i * 4 + j] << (j * 8); + + regmap_write(dp->regmap, DW_DP_AUX_DATA0 + i * 4, value); + } + + return size; +} + +static int dw_dp_aux_read_data(struct dw_dp *dp, u8 *buffer, size_t size) +{ + size_t i, j; + + for (i = 0; i < DIV_ROUND_UP(size, 4); i++) { + size_t num = min_t(size_t, size - i * 4, 4); + u32 value; + + regmap_read(dp->regmap, DW_DP_AUX_DATA0 + i * 4, &value); + + for (j = 0; j < num; j++) + buffer[i * 4 + j] = value >> (j * 8); + } + + return size; +} + +static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct dw_dp *dp = container_of(aux, struct dw_dp, aux); + unsigned long timeout = msecs_to_jiffies(10); + u32 status, value; + ssize_t ret = 0; + + if (WARN_ON(msg->size > 16)) + return -E2BIG; + + switch (msg->request & ~DP_AUX_I2C_MOT) { + case DP_AUX_NATIVE_WRITE: + case DP_AUX_I2C_WRITE: + case DP_AUX_I2C_WRITE_STATUS_UPDATE: + ret = dw_dp_aux_write_data(dp, msg->buffer, msg->size); + if (ret < 0) + return ret; + break; + case DP_AUX_NATIVE_READ: + case DP_AUX_I2C_READ: + break; + default: + return -EINVAL; + } + + if (msg->size > 0) + value = FIELD_PREP(AUX_LEN_REQ, msg->size - 1); + else + value = FIELD_PREP(I2C_ADDR_ONLY, 1); + value |= FIELD_PREP(AUX_CMD_TYPE, msg->request); + value |= FIELD_PREP(AUX_ADDR, msg->address); + regmap_write(dp->regmap, DW_DP_AUX_CMD, value); + + status = wait_for_completion_timeout(&dp->complete, timeout); + if (!status) { + dev_err(dp->dev, "timeout waiting for AUX reply\n"); + return -ETIMEDOUT; + } + + regmap_read(dp->regmap, DW_DP_AUX_STATUS, &value); + if (value & AUX_TIMEOUT) + return -ETIMEDOUT; + + msg->reply = FIELD_GET(AUX_STATUS, value); + + if (msg->size > 0 && msg->reply == DP_AUX_NATIVE_REPLY_ACK) { + if (msg->request & DP_AUX_I2C_READ) { + size_t count = FIELD_GET(AUX_BYTES_READ, value) - 1; + + if (count != msg->size) + return -EBUSY; + + ret = dw_dp_aux_read_data(dp, msg->buffer, count); + if (ret < 0) + return ret; + } + } + + return ret; +} + +/* + * Limits for the video timing output by DP: + * 1. the hfp should be 2 pixels aligned; + * 2. the minimum hsync should be 9 pixel; + * 3. the minimum hbp should be 16 pixel; + */ +static bool dw_dp_bridge_mode_fixup(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + int min_hbp = 16; + int min_hsync = 9; + + if ((adjusted_mode->hsync_start - adjusted_mode->hdisplay) & 0x1) { + adjusted_mode->hsync_start += 1; + dev_warn(dp->dev, "hfp is not 2 pixeel aligned, fixup to aligned hfp\n"); + } + if (adjusted_mode->hsync_end - adjusted_mode->hsync_start < min_hsync) { + adjusted_mode->hsync_end = adjusted_mode->hsync_start + min_hsync; + dev_warn(dp->dev, "hsync is too narrow, fixup to min hsync:%d\n", min_hsync); + } + if (adjusted_mode->htotal - adjusted_mode->hsync_end < min_hbp) { + adjusted_mode->htotal = adjusted_mode->hsync_end + min_hbp; + dev_warn(dp->dev, "hbp is too narrow, fixup to min hbp:%d\n", min_hbp); + } + + return true; +} + +static enum drm_mode_status dw_dp_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + struct dw_dp_link *link = &dp->link; + u32 min_bpp; + + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR420 && + link->vsc_sdp_supported && + (drm_mode_is_420_only(info, mode) || drm_mode_is_420_also(info, mode))) + min_bpp = 12; + else if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) + min_bpp = 16; + else if (info->color_formats & DRM_COLOR_FORMAT_RGB444) + min_bpp = 18; + else + min_bpp = 24; + + if (!link->vsc_sdp_supported && + drm_mode_is_420_only(info, mode)) + return MODE_NO_420; + + if (!dw_dp_bandwidth_ok(dp, mode, min_bpp, link->lanes, link->rate)) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static void dw_dp_bridge_atomic_pre_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct drm_atomic_state *state = old_bridge_state->base.state; + struct drm_crtc_state *crtc_state = bridge->encoder->crtc->state; + struct dw_dp *dp = bridge_to_dp(bridge); + struct dw_dp_video *video = &dp->video; + struct drm_display_mode *m = &video->mode; + struct drm_bridge_state *bridge_state; + const struct dw_dp_output_format *fmt; + + drm_mode_copy(m, &crtc_state->adjusted_mode); + + bridge_state = drm_atomic_get_new_bridge_state(state, bridge); + fmt = dw_dp_get_output_format(bridge_state->output_bus_cfg.format); + + video->video_mapping = fmt->video_mapping; + video->color_format = fmt->color_format; + video->bpc = fmt->bpc; + video->bpp = fmt->bpp; +} + +static bool dw_dp_needs_link_retrain(struct dw_dp *dp) +{ + struct dw_dp_link *link = &dp->link; + u8 link_status[DP_LINK_STATUS_SIZE]; + + if (!dw_dp_link_train_valid(&link->train)) + return false; + + if (drm_dp_dpcd_read_link_status(&dp->aux, link_status) < 0) + return false; + + /* Retrain if Channel EQ or CR not ok */ + return !drm_dp_channel_eq_ok(link_status, dp->link.lanes); +} + +static void dw_dp_link_disable(struct dw_dp *dp) +{ + struct dw_dp_link *link = &dp->link; + + if (dw_dp_hpd_detect(dp)) + dw_dp_link_power_down(dp); + + dw_dp_phy_xmit_enable(dp, 0); + + phy_power_off(dp->phy); + + link->train.clock_recovered = false; + link->train.channel_equalized = false; +} + +static int dw_dp_link_enable(struct dw_dp *dp) +{ + int ret; + + ret = phy_power_on(dp->phy); + if (ret) + return ret; + + ret = dw_dp_link_power_up(dp); + if (ret < 0) + return ret; + + ret = dw_dp_link_train(dp); + + return ret; +} + +static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_state) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + struct drm_atomic_state *state = old_state->base.state; + struct drm_connector *connector; + struct drm_connector_state *conn_state; + int ret; + + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); + if (!connector) { + dev_err(dp->dev, "failed to get connector\n"); + return; + } + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (!conn_state) { + dev_err(dp->dev, "failed to get connector state\n"); + return; + } + + set_bit(0, dp->sdp_reg_bank); + + ret = dw_dp_link_enable(dp); + if (ret < 0) { + dev_err(dp->dev, "failed to enable link: %d\n", ret); + return; + } + + ret = dw_dp_video_enable(dp); + if (ret < 0) { + dev_err(dp->dev, "failed to enable video: %d\n", ret); + return; + } +} + +static void dw_dp_reset(struct dw_dp *dp) +{ + int val; + + disable_irq(dp->irq); + regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET, + FIELD_PREP(CONTROLLER_RESET, 1)); + udelay(10); + regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET, + FIELD_PREP(CONTROLLER_RESET, 0)); + + dw_dp_init_hw(dp); + regmap_read_poll_timeout(dp->regmap, DW_DP_HPD_STATUS, val, + FIELD_GET(HPD_HOT_PLUG, val), 200, 200000); + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG); + enable_irq(dp->irq); +} + +static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + + dw_dp_video_disable(dp); + dw_dp_link_disable(dp); + bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE); + dw_dp_reset(dp); +} + +static bool dw_dp_hpd_detect_link(struct dw_dp *dp) +{ + int ret; + + ret = phy_power_on(dp->phy); + if (ret < 0) + return false; + ret = dw_dp_link_parse(dp); + phy_power_off(dp->phy); + + return !ret; +} + +static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + + if (!dw_dp_hpd_detect(dp)) + return connector_status_disconnected; + + if (!dw_dp_hpd_detect_link(dp)) + return connector_status_disconnected; + + return connector_status_connected; +} + +static const struct drm_edid *dw_dp_bridge_edid_read(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + const struct drm_edid *edid; + int ret; + + ret = phy_power_on(dp->phy); + if (ret) + return NULL; + + edid = drm_edid_read_ddc(connector, &dp->aux.ddc); + + phy_power_off(dp->phy); + + return edid; +} + +static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state, + unsigned int *num_output_fmts) +{ + struct dw_dp *dp = bridge_to_dp(bridge); + struct dw_dp_link *link = &dp->link; + struct drm_display_info *di = &conn_state->connector->display_info; + struct drm_display_mode mode = crtc_state->mode; + const struct dw_dp_output_format *fmt; + u32 i, j = 0; + u32 *output_fmts; + + *num_output_fmts = 0; + + output_fmts = kcalloc(ARRAY_SIZE(dw_dp_output_formats), sizeof(*output_fmts), GFP_KERNEL); + if (!output_fmts) + return NULL; + + for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) { + fmt = &dw_dp_output_formats[i]; + + if (fmt->bpc > conn_state->max_bpc) + continue; + + if (!(fmt->color_format & di->color_formats)) + continue; + + if (fmt->color_format == DRM_COLOR_FORMAT_YCBCR420 && + !link->vsc_sdp_supported) + continue; + + if (fmt->color_format != DRM_COLOR_FORMAT_YCBCR420 && + drm_mode_is_420_only(di, &mode)) + continue; + + if (!dw_dp_bandwidth_ok(dp, &mode, fmt->bpp, link->lanes, link->rate)) + continue; + + output_fmts[j++] = fmt->bus_format; + } + + *num_output_fmts = j; + + return output_fmts; +} + +static const struct drm_bridge_funcs dw_dp_bridge_funcs = { + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_reset = drm_atomic_helper_bridge_reset, + .atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt, + .atomic_get_output_bus_fmts = dw_dp_bridge_atomic_get_output_bus_fmts, + .mode_fixup = dw_dp_bridge_mode_fixup, + .mode_valid = dw_dp_bridge_mode_valid, + .atomic_pre_enable = dw_dp_bridge_atomic_pre_enable, + .atomic_enable = dw_dp_bridge_atomic_enable, + .atomic_disable = dw_dp_bridge_atomic_disable, + .detect = dw_dp_bridge_detect, + .edid_read = dw_dp_bridge_edid_read, +}; + +static int dw_dp_link_retrain(struct dw_dp *dp) +{ + struct drm_device *dev = dp->bridge.dev; + struct drm_modeset_acquire_ctx ctx; + int ret; + + if (!dw_dp_needs_link_retrain(dp)) + return 0; + + dev_dbg(dp->dev, "Retraining link\n"); + + drm_modeset_acquire_init(&ctx, 0); + for (;;) { + ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx); + if (ret != -EDEADLK) + break; + + drm_modeset_backoff(&ctx); + } + + ret = dw_dp_link_train(dp); + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); + + return ret; +} + +static void dw_dp_hpd_work(struct work_struct *work) +{ + struct dw_dp *dp = container_of(work, struct dw_dp, hpd_work); + bool long_hpd; + int ret; + + mutex_lock(&dp->irq_lock); + long_hpd = dp->hotplug.long_hpd; + mutex_unlock(&dp->irq_lock); + + dev_dbg(dp->dev, "[drm] Get hpd irq - %s\n", long_hpd ? "long" : "short"); + + if (!long_hpd) { + if (dw_dp_needs_link_retrain(dp)) { + ret = dw_dp_link_retrain(dp); + if (ret) + dev_warn(dp->dev, "Retrain link failed\n"); + } + } else { + drm_helper_hpd_irq_event(dp->bridge.dev); + } +} + +static void dw_dp_handle_hpd_event(struct dw_dp *dp) +{ + u32 value; + + mutex_lock(&dp->irq_lock); + regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value); + + if (value & HPD_IRQ) { + dev_dbg(dp->dev, "IRQ from the HPD\n"); + dp->hotplug.long_hpd = false; + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_IRQ); + } + + if (value & HPD_HOT_PLUG) { + dev_dbg(dp->dev, "Hot plug detected\n"); + dp->hotplug.long_hpd = true; + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG); + } + + if (value & HPD_HOT_UNPLUG) { + dev_dbg(dp->dev, "Unplug detected\n"); + dp->hotplug.long_hpd = true; + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_UNPLUG); + } + mutex_unlock(&dp->irq_lock); + + schedule_work(&dp->hpd_work); +} + +static irqreturn_t dw_dp_irq(int irq, void *data) +{ + struct dw_dp *dp = data; + u32 value; + + regmap_read(dp->regmap, DW_DP_GENERAL_INTERRUPT, &value); + if (!value) + return IRQ_NONE; + + if (value & HPD_EVENT) + dw_dp_handle_hpd_event(dp); + + if (value & AUX_REPLY_EVENT) { + regmap_write(dp->regmap, DW_DP_GENERAL_INTERRUPT, AUX_REPLY_EVENT); + complete(&dp->complete); + } + + return IRQ_HANDLED; +} + +static const struct regmap_range dw_dp_readable_ranges[] = { + regmap_reg_range(DW_DP_VERSION_NUMBER, DW_DP_ID), + regmap_reg_range(DW_DP_CONFIG_REG1, DW_DP_CONFIG_REG3), + regmap_reg_range(DW_DP_CCTL, DW_DP_SOFT_RESET_CTRL), + regmap_reg_range(DW_DP_VSAMPLE_CTRL, DW_DP_VIDEO_HBLANK_INTERVAL), + regmap_reg_range(DW_DP_AUD_CONFIG1, DW_DP_AUD_CONFIG1), + regmap_reg_range(DW_DP_SDP_VERTICAL_CTRL, DW_DP_SDP_STATUS_EN), + regmap_reg_range(DW_DP_PHYIF_CTRL, DW_DP_PHYIF_PWRDOWN_CTRL), + regmap_reg_range(DW_DP_AUX_CMD, DW_DP_AUX_DATA3), + regmap_reg_range(DW_DP_GENERAL_INTERRUPT, DW_DP_HPD_INTERRUPT_ENABLE), +}; + +static const struct regmap_access_table dw_dp_readable_table = { + .yes_ranges = dw_dp_readable_ranges, + .n_yes_ranges = ARRAY_SIZE(dw_dp_readable_ranges), +}; + +static const struct regmap_config dw_dp_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .fast_io = true, + .max_register = DW_DP_MAX_REGISTER, + .rd_table = &dw_dp_readable_table, +}; + +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, + const struct dw_dp_plat_data *plat_data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dw_dp *dp; + struct drm_bridge *bridge; + void __iomem *res; + int ret; + + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); + if (!dp) + return ERR_PTR(-ENOMEM); + + dp->dev = dev; + dp->video.pixel_mode = DW_DP_MP_QUAD_PIXEL; + + dp->plat_data = plat_data; + bridge = &dp->bridge; + mutex_init(&dp->irq_lock); + INIT_WORK(&dp->hpd_work, dw_dp_hpd_work); + init_completion(&dp->complete); + + res = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(res)) + return ERR_CAST(res); + + dp->regmap = devm_regmap_init_mmio(dev, res, &dw_dp_regmap_config); + if (IS_ERR(dp->regmap)) { + dev_err_probe(dev, PTR_ERR(dp->regmap), "failed to create regmap\n"); + return ERR_CAST(dp->regmap); + } + + dp->phy = devm_of_phy_get(dev, dev->of_node, NULL); + if (IS_ERR(dp->phy)) { + dev_err_probe(dev, PTR_ERR(dp->phy), "failed to get phy\n"); + return ERR_CAST(dp->phy); + } + + dp->apb_clk = devm_clk_get_enabled(dev, "apb"); + if (IS_ERR(dp->apb_clk)) { + dev_err_probe(dev, PTR_ERR(dp->apb_clk), "failed to get apb clock\n"); + return ERR_CAST(dp->apb_clk); + } + + dp->aux_clk = devm_clk_get_enabled(dev, "aux"); + if (IS_ERR(dp->aux_clk)) { + dev_err_probe(dev, PTR_ERR(dp->aux_clk), "failed to get aux clock\n"); + return ERR_CAST(dp->aux_clk); + } + + dp->i2s_clk = devm_clk_get(dev, "i2s"); + if (IS_ERR(dp->i2s_clk)) { + dev_err_probe(dev, PTR_ERR(dp->i2s_clk), "failed to get i2s clock\n"); + return ERR_CAST(dp->i2s_clk); + } + + dp->spdif_clk = devm_clk_get(dev, "spdif"); + if (IS_ERR(dp->spdif_clk)) { + dev_err_probe(dev, PTR_ERR(dp->spdif_clk), "failed to get spdif clock\n"); + return ERR_CAST(dp->spdif_clk); + } + + dp->hdcp_clk = devm_clk_get(dev, "hdcp"); + if (IS_ERR(dp->hdcp_clk)) { + dev_err_probe(dev, PTR_ERR(dp->hdcp_clk), "failed to get hdcp clock\n"); + return ERR_CAST(dp->hdcp_clk); + } + + dp->rstc = devm_reset_control_get(dev, NULL); + if (IS_ERR(dp->rstc)) { + dev_err_probe(dev, PTR_ERR(dp->rstc), "failed to get reset control\n"); + return ERR_CAST(dp->rstc); + } + + dp->irq = platform_get_irq(pdev, 0); + if (dp->irq < 0) + return ERR_PTR(ret); + + ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq, + IRQF_ONESHOT, dev_name(dev), dp); + if (ret) { + dev_err_probe(dev, ret, "failed to request irq\n"); + return ERR_PTR(ret); + } + + bridge->of_node = dev->of_node; + bridge->funcs = &dw_dp_bridge_funcs; + bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; + bridge->type = DRM_MODE_CONNECTOR_DisplayPort; + bridge->ycbcr_420_allowed = true; + bridge->vendor = "Synopsys"; + bridge->product = "DW DP TX"; + + platform_set_drvdata(pdev, dp); + + dp->aux.dev = dev; + dp->aux.drm_dev = encoder->dev; + dp->aux.name = dev_name(dev); + dp->aux.transfer = dw_dp_aux_transfer; + ret = drm_dp_aux_register(&dp->aux); + if (ret) { + dev_err_probe(dev, ret, "Aux register failed\n"); + return ERR_PTR(ret); + } + + ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + dev_err_probe(dev, ret, "Failed to attach bridge\n"); + + dw_dp_init_hw(dp); + + return dp; +} +MODULE_AUTHOR("Andy Yan <andyshrk@163.com>"); +MODULE_DESCRIPTION("DW DP Core Library"); +MODULE_LICENSE("GPL"); diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h new file mode 100644 index 000000000000..07845f609eca --- /dev/null +++ b/include/drm/bridge/dw_dp.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2025 Rockchip Electronics Co., Ltd. + */ + +#ifndef __DW_DP__ +#define __DW_DP__ + +struct device; +struct drm_encoder; +struct dw_dp; + +struct dw_dp_plat_data { + u32 max_link_rate; +}; + +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, + const struct dw_dp_plat_data *plat_data); +#endif /* __DW_DP__ */ -- 2.34.1 ^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library 2025-02-23 11:30 ` [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library Andy Yan @ 2025-03-01 18:14 ` Dmitry Baryshkov 2025-03-02 10:34 ` Andy Yan 2025-03-03 8:59 ` Yubing Zhang 2025-03-09 6:51 ` FUKAUMI Naoki 1 sibling, 2 replies; 32+ messages in thread From: Dmitry Baryshkov @ 2025-03-01 18:14 UTC (permalink / raw) To: Andy Yan Cc: heiko, hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan On Sun, Feb 23, 2025 at 07:30:25PM +0800, Andy Yan wrote: > From: Andy Yan <andy.yan@rock-chips.com> > > The DW DP TX Controller is compliant with the DisplayPort Specification > Version 1.4 with the following features: > > * DisplayPort 1.4a > * Main Link: 1/2/4 lanes > * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps > * AUX channel 1Mbps > * Single Stream Transport(SST) > * Multistream Transport (MST) > *Type-C support (alternate mode) > * HDCP 2.2, HDCP 1.3 > * Supports up to 8/10 bits per color component > * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0 > * Pixel clock up to 594MHz > * I2S, SPDIF audio interface > > Add library with common helpers to make it can be shared with > other SoC. > > Signed-off-by: Andy Yan <andy.yan@rock-chips.com> > > drm/bridge: cleanup Stray line? > > --- > > drivers/gpu/drm/bridge/synopsys/Kconfig | 7 + > drivers/gpu/drm/bridge/synopsys/Makefile | 1 + > drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2155 ++++++++++++++++++++++ > include/drm/bridge/dw_dp.h | 19 + > 4 files changed, 2182 insertions(+) > create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-dp.c > create mode 100644 include/drm/bridge/dw_dp.h > > diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig > index f3ab2f985f8c..2c5e532410de 100644 > --- a/drivers/gpu/drm/bridge/synopsys/Kconfig > +++ b/drivers/gpu/drm/bridge/synopsys/Kconfig > @@ -1,4 +1,11 @@ > # SPDX-License-Identifier: GPL-2.0-only > +config DRM_DW_DP > + tristate > + select DRM_DISPLAY_HELPER > + select DRM_DISPLAY_DP_HELPER > + select DRM_KMS_HELPER > + select REGMAP_MMIO > + > config DRM_DW_HDMI > tristate > select DRM_DISPLAY_HDMI_HELPER > diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile > index 9dc376d220ad..4dada44029ac 100644 > --- a/drivers/gpu/drm/bridge/synopsys/Makefile > +++ b/drivers/gpu/drm/bridge/synopsys/Makefile > @@ -1,4 +1,5 @@ > # SPDX-License-Identifier: GPL-2.0-only > +obj-$(CONFIG_DRM_DW_DP) += dw-dp.o > obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > obj-$(CONFIG_DRM_DW_HDMI_GP_AUDIO) += dw-hdmi-gp-audio.o > diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c > new file mode 100644 > index 000000000000..6ecbe9851369 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c > @@ -0,0 +1,2155 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Synopsys DesignWare Cores DisplayPort Transmitter Controller > + * > + * Copyright (c) 2024 Rockchip Electronics Co., Ltd. > + * > + * Author: Andy Yan <andy.yan@rock-chips.com> > + */ > +#include <linux/bitfield.h> > +#include <linux/clk.h> > +#include <linux/component.h> > +#include <linux/iopoll.h> > +#include <linux/irq.h> > +#include <linux/of_device.h> > +#include <linux/of_graph.h> > +#include <linux/platform_device.h> > +#include <linux/regmap.h> > +#include <linux/reset.h> > +#include <linux/phy/phy.h> > +#include <linux/unaligned.h> > + > +#include <drm/bridge/dw_dp.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_bridge.h> > +#include <drm/drm_bridge_connector.h> > +#include <drm/display/drm_dp_helper.h> > +#include <drm/display/drm_hdmi_helper.h> > +#include <drm/drm_edid.h> > +#include <drm/drm_of.h> > +#include <drm/drm_print.h> > +#include <drm/drm_probe_helper.h> > +#include <drm/drm_simple_kms_helper.h> > + > +#include <uapi/linux/media-bus-format.h> > + > +#define DW_DP_VERSION_NUMBER 0x0000 > +#define DW_DP_VERSION_TYPE 0x0004 > +#define DW_DP_ID 0x0008 > + > +#define DW_DP_CONFIG_REG1 0x0100 > +#define DW_DP_CONFIG_REG2 0x0104 > +#define DW_DP_CONFIG_REG3 0x0108 > + > +#define DW_DP_CCTL 0x0200 > +#define FORCE_HPD BIT(4) > +#define DEFAULT_FAST_LINK_TRAIN_EN BIT(2) > +#define ENHANCE_FRAMING_EN BIT(1) > +#define SCRAMBLE_DIS BIT(0) > +#define DW_DP_SOFT_RESET_CTRL 0x0204 > +#define VIDEO_RESET BIT(5) > +#define AUX_RESET BIT(4) > +#define AUDIO_SAMPLER_RESET BIT(3) > +#define HDCP_MODULE_RESET BIT(2) > +#define PHY_SOFT_RESET BIT(1) > +#define CONTROLLER_RESET BIT(0) > + > +#define DW_DP_VSAMPLE_CTRL 0x0300 > +#define PIXEL_MODE_SELECT GENMASK(22, 21) > +#define VIDEO_MAPPING GENMASK(20, 16) > +#define VIDEO_STREAM_ENABLE BIT(5) > + > +#define DW_DP_VSAMPLE_STUFF_CTRL1 0x0304 > + > +#define DW_DP_VSAMPLE_STUFF_CTRL2 0x0308 > + > +#define DW_DP_VINPUT_POLARITY_CTRL 0x030c > +#define DE_IN_POLARITY BIT(2) > +#define HSYNC_IN_POLARITY BIT(1) > +#define VSYNC_IN_POLARITY BIT(0) > + > +#define DW_DP_VIDEO_CONFIG1 0x0310 > +#define HACTIVE GENMASK(31, 16) > +#define HBLANK GENMASK(15, 2) > +#define I_P BIT(1) > +#define R_V_BLANK_IN_OSC BIT(0) > + > +#define DW_DP_VIDEO_CONFIG2 0x0314 > +#define VBLANK GENMASK(31, 16) > +#define VACTIVE GENMASK(15, 0) > + > +#define DW_DP_VIDEO_CONFIG3 0x0318 > +#define H_SYNC_WIDTH GENMASK(31, 16) > +#define H_FRONT_PORCH GENMASK(15, 0) > + > +#define DW_DP_VIDEO_CONFIG4 0x031c > +#define V_SYNC_WIDTH GENMASK(31, 16) > +#define V_FRONT_PORCH GENMASK(15, 0) > + > +#define DW_DP_VIDEO_CONFIG5 0x0320 > +#define INIT_THRESHOLD_HI GENMASK(22, 21) > +#define AVERAGE_BYTES_PER_TU_FRAC GENMASK(19, 16) > +#define INIT_THRESHOLD GENMASK(13, 7) > +#define AVERAGE_BYTES_PER_TU GENMASK(6, 0) > + > +#define DW_DP_VIDEO_MSA1 0x0324 > +#define VSTART GENMASK(31, 16) > +#define HSTART GENMASK(15, 0) > + > +#define DW_DP_VIDEO_MSA2 0x0328 > +#define MISC0 GENMASK(31, 24) > + > +#define DW_DP_VIDEO_MSA3 0x032c > +#define MISC1 GENMASK(31, 24) > + > +#define DW_DP_VIDEO_HBLANK_INTERVAL 0x0330 > +#define HBLANK_INTERVAL_EN BIT(16) > +#define HBLANK_INTERVAL GENMASK(15, 0) > + > +#define DW_DP_AUD_CONFIG1 0x0400 > +#define AUDIO_TIMESTAMP_VERSION_NUM GENMASK(29, 24) > +#define AUDIO_PACKET_ID GENMASK(23, 16) > +#define AUDIO_MUTE BIT(15) > +#define NUM_CHANNELS GENMASK(14, 12) > +#define HBR_MODE_ENABLE BIT(10) > +#define AUDIO_DATA_WIDTH GENMASK(9, 5) > +#define AUDIO_DATA_IN_EN GENMASK(4, 1) > +#define AUDIO_INF_SELECT BIT(0) > + > +#define DW_DP_SDP_VERTICAL_CTRL 0x0500 > +#define EN_VERTICAL_SDP BIT(2) > +#define EN_AUDIO_STREAM_SDP BIT(1) > +#define EN_AUDIO_TIMESTAMP_SDP BIT(0) > +#define DW_DP_SDP_HORIZONTAL_CTRL 0x0504 > +#define EN_HORIZONTAL_SDP BIT(2) > +#define DW_DP_SDP_STATUS_REGISTER 0x0508 > +#define DW_DP_SDP_MANUAL_CTRL 0x050c > +#define DW_DP_SDP_STATUS_EN 0x0510 > + > +#define DW_DP_SDP_REGISTER_BANK 0x0600 > +#define SDP_REGS GENMASK(31, 0) > + > +#define DW_DP_PHYIF_CTRL 0x0a00 > +#define PHY_WIDTH BIT(25) > +#define PHY_POWERDOWN GENMASK(20, 17) > +#define PHY_BUSY GENMASK(15, 12) > +#define SSC_DIS BIT(16) > +#define XMIT_ENABLE GENMASK(11, 8) > +#define PHY_LANES GENMASK(7, 6) > +#define PHY_RATE GENMASK(5, 4) > +#define TPS_SEL GENMASK(3, 0) > + > +#define DW_DP_PHY_TX_EQ 0x0a04 > +#define DW_DP_CUSTOMPAT0 0x0a08 > +#define DW_DP_CUSTOMPAT1 0x0a0c > +#define DW_DP_CUSTOMPAT2 0x0a10 > +#define DW_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET 0x0a14 > +#define DW_DP_PHYIF_PWRDOWN_CTRL 0x0a18 > + > +#define DW_DP_AUX_CMD 0x0b00 > +#define AUX_CMD_TYPE GENMASK(31, 28) > +#define AUX_ADDR GENMASK(27, 8) > +#define I2C_ADDR_ONLY BIT(4) > +#define AUX_LEN_REQ GENMASK(3, 0) > + > +#define DW_DP_AUX_STATUS 0x0b04 > +#define AUX_TIMEOUT BIT(17) > +#define AUX_BYTES_READ GENMASK(23, 19) > +#define AUX_STATUS GENMASK(7, 4) > + > +#define DW_DP_AUX_DATA0 0x0b08 > +#define DW_DP_AUX_DATA1 0x0b0c > +#define DW_DP_AUX_DATA2 0x0b10 > +#define DW_DP_AUX_DATA3 0x0b14 > + > +#define DW_DP_GENERAL_INTERRUPT 0x0d00 > +#define VIDEO_FIFO_OVERFLOW_STREAM0 BIT(6) > +#define AUDIO_FIFO_OVERFLOW_STREAM0 BIT(5) > +#define SDP_EVENT_STREAM0 BIT(4) > +#define AUX_CMD_INVALID BIT(3) > +#define HDCP_EVENT BIT(2) > +#define AUX_REPLY_EVENT BIT(1) > +#define HPD_EVENT BIT(0) > + > +#define DW_DP_GENERAL_INTERRUPT_ENABLE 0x0d04 > +#define HDCP_EVENT_EN BIT(2) > +#define AUX_REPLY_EVENT_EN BIT(1) > +#define HPD_EVENT_EN BIT(0) > + > +#define DW_DP_HPD_STATUS 0x0d08 > +#define HPD_STATE GENMASK(11, 9) > +#define HPD_STATUS BIT(8) > +#define HPD_HOT_UNPLUG BIT(2) > +#define HPD_HOT_PLUG BIT(1) > +#define HPD_IRQ BIT(0) > + > +#define DW_DP_HPD_INTERRUPT_ENABLE 0x0d0c > +#define HPD_UNPLUG_ERR_EN BIT(3) > +#define HPD_UNPLUG_EN BIT(2) > +#define HPD_PLUG_EN BIT(1) > +#define HPD_IRQ_EN BIT(0) > + > +#define DW_DP_HDCP_CFG 0x0e00 > +#define DPCD12PLUS BIT(7) > +#define CP_IRQ BIT(6) > +#define BYPENCRYPTION BIT(5) > +#define HDCP_LOCK BIT(4) > +#define ENCRYPTIONDISABLE BIT(3) > +#define ENABLE_HDCP_13 BIT(2) > +#define ENABLE_HDCP BIT(1) > + > +#define DW_DP_HDCP_OBS 0x0e04 > +#define HDCP22_RE_AUTHENTICATION_REQ BIT(31) > +#define HDCP22_AUTHENTICATION_FAILED BIT(30) > +#define HDCP22_AUTHENTICATION_SUCCESS BIT(29) > +#define HDCP22_CAPABLE_SINK BIT(28) > +#define HDCP22_SINK_CAP_CHECK_COMPLETE BIT(27) > +#define HDCP22_STATE GENMASK(26, 24) > +#define HDCP22_BOOTED BIT(23) > +#define HDCP13_BSTATUS GENMASK(22, 19) > +#define REPEATER BIT(18) > +#define HDCP_CAPABLE BIT(17) > +#define STATEE GENMASK(16, 14) > +#define STATEOEG GENMASK(13, 11) > +#define STATER GENMASK(10, 8) > +#define STATEA GENMASK(7, 4) > +#define SUBSTATEA GENMASK(3, 1) > +#define HDCPENGAGED BIT(0) > + > +#define DW_DP_HDCP_APIINTCLR 0x0e08 > +#define DW_DP_HDCP_APIINTSTAT 0x0e0c > +#define DW_DP_HDCP_APIINTMSK 0x0e10 > +#define HDCP22_GPIOINT BIT(8) > +#define HDCP_ENGAGED BIT(7) > +#define HDCP_FAILED BIT(6) > +#define KSVSHA1CALCDONEINT BIT(5) > +#define AUXRESPNACK7TIMES BIT(4) > +#define AUXRESPTIMEOUT BIT(3) > +#define AUXRESPDEFER7TIMES BIT(2) > +#define KSVACCESSINT BIT(0) > + > +#define DW_DP_HDCP_KSVMEMCTRL 0x0e18 > +#define KSVSHA1STATUS BIT(4) > +#define KSVMEMACCESS BIT(1) > +#define KSVMEMREQUEST BIT(0) > + > +#define DW_DP_HDCP_REG_BKSV0 0x3600 > +#define DW_DP_HDCP_REG_BKSV1 0x3604 > +#define DW_DP_HDCP_REG_ANCONF 0x3608 > +#define AN_BYPASS BIT(0) > + > +#define DW_DP_HDCP_REG_AN0 0x360c > +#define DW_DP_HDCP_REG_AN1 0x3610 > +#define DW_DP_HDCP_REG_RMLCTL 0x3614 > +#define ODPK_DECRYPT_ENABLE BIT(0) > + > +#define DW_DP_HDCP_REG_RMLSTS 0x3618 > +#define IDPK_WR_OK_STS BIT(6) > +#define IDPK_DATA_INDEX GENMASK(5, 0) > +#define DW_DP_HDCP_REG_SEED 0x361c > +#define DW_DP_HDCP_REG_DPK0 0x3620 > +#define DW_DP_HDCP_REG_DPK1 0x3624 > +#define DW_DP_HDCP22_GPIOSTS 0x3628 > +#define DW_DP_HDCP22_GPIOCHNGSTS 0x362c > +#define DW_DP_HDCP_REG_DPK_CRC 0x3630 > + > +#define DW_DP_MAX_REGISTER DW_DP_HDCP_REG_DPK_CRC > + > +#define SDP_REG_BANK_SIZE 16 > + > +struct dw_dp_link_caps { > + bool enhanced_framing; > + bool tps3_supported; > + bool tps4_supported; > + bool fast_training; > + bool channel_coding; > + bool ssc; > +}; > + > +struct dw_dp_link_train_set { > + unsigned int voltage_swing[4]; > + unsigned int pre_emphasis[4]; > + bool voltage_max_reached[4]; > + bool pre_max_reached[4]; > +}; > + > +struct dw_dp_link_train { > + struct dw_dp_link_train_set request; > + struct dw_dp_link_train_set adjust; > + bool clock_recovered; > + bool channel_equalized; > +}; > + > +struct dw_dp_link { > + u8 dpcd[DP_RECEIVER_CAP_SIZE]; > + unsigned char revision; > + unsigned int rate; > + unsigned int lanes; > + u8 sink_count; > + u8 vsc_sdp_supported; > + struct dw_dp_link_caps caps; > + struct dw_dp_link_train train; > + struct drm_dp_desc desc; > +}; > + > +struct dw_dp_video { > + struct drm_display_mode mode; > + u8 video_mapping; > + u8 pixel_mode; > + u8 color_format; > + u8 bpc; > + u8 bpp; > +}; > + > +struct dw_dp_sdp { > + struct dp_sdp_header header; > + u8 db[32]; struct dp_sdp, please > + unsigned long flags; > +}; > + > +struct dw_dp_hotplug { > + bool long_hpd; > +}; > + > +struct dw_dp { > + struct drm_bridge bridge; > + struct device *dev; > + struct regmap *regmap; > + struct phy *phy; > + struct clk *apb_clk; > + struct clk *aux_clk; > + struct clk *i2s_clk; > + struct clk *spdif_clk; > + struct clk *hdcp_clk; > + struct reset_control *rstc; > + struct completion complete; > + int irq; > + struct work_struct hpd_work; > + struct dw_dp_hotplug hotplug; > + struct mutex irq_lock; > + > + struct drm_dp_aux aux; > + > + struct dw_dp_link link; > + struct dw_dp_video video; > + const struct dw_dp_plat_data *plat_data; > + > + DECLARE_BITMAP(sdp_reg_bank, SDP_REG_BANK_SIZE); > +}; > + > +enum { > + DW_DP_RGB_6BIT, > + DW_DP_RGB_8BIT, > + DW_DP_RGB_10BIT, > + DW_DP_RGB_12BIT, > + DW_DP_RGB_16BIT, > + DW_DP_YCBCR444_8BIT, > + DW_DP_YCBCR444_10BIT, > + DW_DP_YCBCR444_12BIT, > + DW_DP_YCBCR444_16BIT, > + DW_DP_YCBCR422_8BIT, > + DW_DP_YCBCR422_10BIT, > + DW_DP_YCBCR422_12BIT, > + DW_DP_YCBCR422_16BIT, > + DW_DP_YCBCR420_8BIT, > + DW_DP_YCBCR420_10BIT, > + DW_DP_YCBCR420_12BIT, > + DW_DP_YCBCR420_16BIT, > +}; > + > +enum { > + DW_DP_MP_SINGLE_PIXEL, > + DW_DP_MP_DUAL_PIXEL, > + DW_DP_MP_QUAD_PIXEL, > +}; > + > +enum { > + DW_DP_SDP_VERTICAL_INTERVAL = BIT(0), > + DW_DP_SDP_HORIZONTAL_INTERVAL = BIT(1), > +}; > + > +enum { > + DW_DP_HPD_STATE_IDLE, > + DW_DP_HPD_STATE_UNPLUG, > + DP_DP_HPD_STATE_TIMEOUT = 4, > + DW_DP_HPD_STATE_PLUG = 7 > +}; > + > +enum { > + DW_DP_PHY_PATTERN_NONE, > + DW_DP_PHY_PATTERN_TPS_1, > + DW_DP_PHY_PATTERN_TPS_2, > + DW_DP_PHY_PATTERN_TPS_3, > + DW_DP_PHY_PATTERN_TPS_4, > + DW_DP_PHY_PATTERN_SERM, > + DW_DP_PHY_PATTERN_PBRS7, > + DW_DP_PHY_PATTERN_CUSTOM_80BIT, > + DW_DP_PHY_PATTERN_CP2520_1, > + DW_DP_PHY_PATTERN_CP2520_2, > +}; > + > +struct dw_dp_output_format { > + u32 bus_format; > + u32 color_format; > + u8 video_mapping; > + u8 bpc; > + u8 bpp; > +}; > + > +static const struct dw_dp_output_format dw_dp_output_formats[] = { > + { MEDIA_BUS_FMT_RGB101010_1X30, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_10BIT, 10, 30 }, > + { MEDIA_BUS_FMT_RGB888_1X24, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_8BIT, 8, 24 }, > + { MEDIA_BUS_FMT_YUV10_1X30, DRM_COLOR_FORMAT_YCBCR444, DW_DP_YCBCR444_10BIT, 10, 30 }, > + { MEDIA_BUS_FMT_YUV8_1X24, DRM_COLOR_FORMAT_YCBCR444, DW_DP_YCBCR444_8BIT, 8, 24}, > + { MEDIA_BUS_FMT_YUYV10_1X20, DRM_COLOR_FORMAT_YCBCR422, DW_DP_YCBCR422_10BIT, 10, 20 }, > + { MEDIA_BUS_FMT_YUYV8_1X16, DRM_COLOR_FORMAT_YCBCR422, DW_DP_YCBCR422_8BIT, 8, 16 }, > + { MEDIA_BUS_FMT_UYYVYY10_0_5X30, DRM_COLOR_FORMAT_YCBCR420, DW_DP_YCBCR420_10BIT, 10, 15 }, > + { MEDIA_BUS_FMT_UYYVYY8_0_5X24, DRM_COLOR_FORMAT_YCBCR420, DW_DP_YCBCR420_8BIT, 8, 12 }, > + { MEDIA_BUS_FMT_RGB666_1X24_CPADHI, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_6BIT, 6, 18 }, > +}; > + > +static const struct dw_dp_output_format *dw_dp_get_output_format(u32 bus_format) > +{ > + unsigned int i; > + > + for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) > + if (dw_dp_output_formats[i].bus_format == bus_format) > + return &dw_dp_output_formats[i]; > + > + return &dw_dp_output_formats[1]; Why? Shouldn't it be NULL? > +} > + > +static inline struct dw_dp *bridge_to_dp(struct drm_bridge *b) > +{ > + return container_of(b, struct dw_dp, bridge); > +} > + > +static inline void dw_dp_phy_set_pattern(struct dw_dp *dp, u32 pattern) > +{ > + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, TPS_SEL, > + FIELD_PREP(TPS_SEL, pattern)); > +} > + > +static void dw_dp_phy_xmit_enable(struct dw_dp *dp, u32 lanes) > +{ > + u32 xmit_enable; > + > + switch (lanes) { > + case 4: > + case 2: > + case 1: > + xmit_enable = GENMASK(lanes - 1, 0); > + break; > + case 0: > + default: > + xmit_enable = 0; > + break; > + } > + > + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, XMIT_ENABLE, > + FIELD_PREP(XMIT_ENABLE, xmit_enable)); > +} > + > +static bool dw_dp_bandwidth_ok(struct dw_dp *dp, > + const struct drm_display_mode *mode, u32 bpp, > + unsigned int lanes, unsigned int rate) > +{ > + u32 max_bw, req_bw; > + > + req_bw = mode->clock * bpp / 8; > + max_bw = lanes * rate; > + if (req_bw > max_bw) > + return false; > + > + return true; > +} > + > +static bool dw_dp_hpd_detect(struct dw_dp *dp) > +{ > + u32 value; > + > + regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value); > + > + return FIELD_GET(HPD_STATE, value) == DW_DP_HPD_STATE_PLUG; > +} > + > +static void dw_dp_link_caps_reset(struct dw_dp_link_caps *caps) > +{ > + caps->enhanced_framing = false; > + caps->tps3_supported = false; > + caps->tps4_supported = false; > + caps->fast_training = false; > + caps->channel_coding = false; > +} > + > +static void dw_dp_link_reset(struct dw_dp_link *link) > +{ > + link->vsc_sdp_supported = 0; > + link->sink_count = 0; > + link->revision = 0; > + link->rate = 0; > + link->lanes = 0; > + > + dw_dp_link_caps_reset(&link->caps); > + memset(link->dpcd, 0, sizeof(link->dpcd)); > +} > + > +static int dw_dp_link_power_up(struct dw_dp *dp) > +{ > + struct dw_dp_link *link = &dp->link; > + int ret; > + u8 value; > + > + if (link->revision < DP_DPCD_REV_11) > + return 0; > + > + ret = drm_dp_dpcd_readb(&dp->aux, DP_SET_POWER, &value); > + if (ret < 0) > + return ret; > + > + value &= ~DP_SET_POWER_MASK; > + value |= DP_SET_POWER_D0; > + > + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, value); > + if (ret < 0) > + return ret; > + > + usleep_range(1000, 2000); > + > + return 0; > +} > + > +static int dw_dp_link_power_down(struct dw_dp *dp) > +{ > + struct dw_dp_link *link = &dp->link; > + int ret; > + u8 value; > + > + if (link->revision < DP_DPCD_REV_11) > + return 0; > + > + ret = drm_dp_dpcd_readb(&dp->aux, DP_SET_POWER, &value); > + if (ret < 0) > + return ret; > + > + value &= ~DP_SET_POWER_MASK; > + value |= DP_SET_POWER_D3; > + > + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, value); > + if (ret < 0) > + return ret; > + Would you please mind pulling these two functions to DRM DP helpers? There are enough users to make this into a common code. > + return 0; > +} > + > +static bool dw_dp_has_sink_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE], > + const struct drm_dp_desc *desc) > +{ > + return 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); isn't it drm_dp_read_sink_count_cap() ? > +} > + > +static int dw_dp_link_parse(struct dw_dp *dp) > +{ > + struct dw_dp_link *link = &dp->link; > + u8 dpcd; > + int ret; > + > + dw_dp_link_reset(link); > + > + ret = drm_dp_read_dpcd_caps(&dp->aux, link->dpcd); > + if (ret < 0) > + return ret; > + > + drm_dp_read_desc(&dp->aux, &link->desc, drm_dp_is_branch(link->dpcd)); > + > + if (dw_dp_has_sink_count(link->dpcd, &link->desc)) { > + ret = drm_dp_read_sink_count(&dp->aux); > + if (ret < 0) > + return ret; > + > + link->sink_count = ret; > + > + /* Dongle connected, but no display */ > + if (!link->sink_count) > + return -ENODEV; > + } > + > + ret = drm_dp_dpcd_readb(&dp->aux, DP_DPRX_FEATURE_ENUMERATION_LIST, &dpcd); > + if (ret < 0) > + return ret; > + > + link->vsc_sdp_supported = !!(dpcd & DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED); > + > + link->revision = link->dpcd[DP_DPCD_REV]; > + link->rate = min_t(u32, min(dp->plat_data->max_link_rate, > + dp->phy->attrs.max_link_rate * 100), > + drm_dp_max_link_rate(link->dpcd)); > + link->lanes = min_t(u8, phy_get_bus_width(dp->phy), > + drm_dp_max_lane_count(link->dpcd)); > + > + link->caps.enhanced_framing = drm_dp_enhanced_frame_cap(link->dpcd); > + link->caps.tps3_supported = drm_dp_tps3_supported(link->dpcd); > + link->caps.tps4_supported = drm_dp_tps4_supported(link->dpcd); > + link->caps.fast_training = drm_dp_fast_training_cap(link->dpcd); > + link->caps.channel_coding = drm_dp_channel_coding_supported(link->dpcd); > + link->caps.ssc = !!(link->dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5); > + > + return 0; > +} > + > +static int dw_dp_phy_update_vs_emph(struct dw_dp *dp, unsigned int rate, unsigned int lanes, > + struct dw_dp_link_train_set *train_set) > +{ > + union phy_configure_opts phy_cfg; > + unsigned int *vs, *pe; > + u8 buf[4]; > + int i, ret; > + > + vs = train_set->voltage_swing; > + pe = train_set->pre_emphasis; > + > + for (i = 0; i < lanes; i++) { > + phy_cfg.dp.voltage[i] = vs[i]; > + phy_cfg.dp.pre[i] = pe[i]; > + } > + > + phy_cfg.dp.lanes = lanes; > + phy_cfg.dp.link_rate = rate / 100; > + phy_cfg.dp.set_lanes = false; > + phy_cfg.dp.set_rate = false; You don't need to set lanes / link_rate if set_lanes and set_rate is false. > + phy_cfg.dp.set_voltages = true; > + > + ret = phy_configure(dp->phy, &phy_cfg); > + if (ret) > + return ret; > + > + for (i = 0; i < lanes; i++) { > + buf[i] = (vs[i] << DP_TRAIN_VOLTAGE_SWING_SHIFT) | > + (pe[i] << DP_TRAIN_PRE_EMPHASIS_SHIFT); > + if (train_set->voltage_max_reached[i]) > + buf[i] |= DP_TRAIN_MAX_SWING_REACHED; > + if (train_set->pre_max_reached[i]) > + buf[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; > + } > + > + ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, buf, lanes); > + if (ret < 0) > + return ret; > + > + return 0; > +} > + > +static int dw_dp_link_train_update_vs_emph(struct dw_dp *dp) > +{ > + struct dw_dp_link *link = &dp->link; > + struct dw_dp_link_train_set *request = &link->train.request; > + > + return dw_dp_phy_update_vs_emph(dp, dp->link.rate, dp->link.lanes, request); Inline it mayeb? > +} > + > +static int dw_dp_phy_configure(struct dw_dp *dp, unsigned int rate, > + unsigned int lanes, bool ssc) > +{ > + union phy_configure_opts phy_cfg; > + int ret; > + > + /* Move PHY to P3 */ > + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_POWERDOWN, > + FIELD_PREP(PHY_POWERDOWN, 0x3)); > + > + phy_cfg.dp.lanes = lanes; > + phy_cfg.dp.link_rate = rate / 100; > + phy_cfg.dp.ssc = ssc; > + phy_cfg.dp.set_lanes = true; > + phy_cfg.dp.set_rate = true; > + phy_cfg.dp.set_voltages = false; > + ret = phy_configure(dp->phy, &phy_cfg); > + if (ret) > + return ret; > + > + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_LANES, > + FIELD_PREP(PHY_LANES, lanes / 2)); > + > + /* Move PHY to P0 */ > + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_POWERDOWN, > + FIELD_PREP(PHY_POWERDOWN, 0x0)); > + > + dw_dp_phy_xmit_enable(dp, lanes); > + > + return 0; > +} > + > +static int dw_dp_link_configure(struct dw_dp *dp) > +{ > + struct dw_dp_link *link = &dp->link; > + u8 buf[2]; > + int ret; > + > + ret = dw_dp_phy_configure(dp, link->rate, link->lanes, link->caps.ssc); > + if (ret) > + return ret; > + > + buf[0] = drm_dp_link_rate_to_bw_code(link->rate); > + buf[1] = link->lanes; > + > + if (link->caps.enhanced_framing) { > + buf[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; > + regmap_update_bits(dp->regmap, DW_DP_CCTL, ENHANCE_FRAMING_EN, > + FIELD_PREP(ENHANCE_FRAMING_EN, 1)); > + } else { > + regmap_update_bits(dp->regmap, DW_DP_CCTL, ENHANCE_FRAMING_EN, > + FIELD_PREP(ENHANCE_FRAMING_EN, 0)); > + } > + > + ret = drm_dp_dpcd_write(&dp->aux, DP_LINK_BW_SET, buf, sizeof(buf)); > + if (ret < 0) > + return ret; > + > + buf[0] = link->caps.ssc ? DP_SPREAD_AMP_0_5 : 0; > + buf[1] = link->caps.channel_coding ? DP_SET_ANSI_8B10B : 0; > + > + ret = drm_dp_dpcd_write(&dp->aux, DP_DOWNSPREAD_CTRL, buf, sizeof(buf)); > + if (ret < 0) > + return ret; > + > + return 0; > +} > + > +static void dw_dp_link_train_init(struct dw_dp_link_train *train) > +{ > + struct dw_dp_link_train_set *request = &train->request; > + struct dw_dp_link_train_set *adjust = &train->adjust; > + unsigned int i; > + > + for (i = 0; i < 4; i++) { > + request->voltage_swing[i] = 0; > + adjust->voltage_swing[i] = 0; > + > + request->pre_emphasis[i] = 0; > + adjust->pre_emphasis[i] = 0; > + > + request->voltage_max_reached[i] = false; > + adjust->voltage_max_reached[i] = false; > + > + request->pre_max_reached[i] = false; > + adjust->pre_max_reached[i] = false; > + } > + > + train->clock_recovered = false; > + train->channel_equalized = false; > +} > + > +static bool dw_dp_link_train_valid(const struct dw_dp_link_train *train) > +{ > + return train->clock_recovered && train->channel_equalized; > +} > + > +static int dw_dp_link_train_set_pattern(struct dw_dp *dp, u32 pattern) > +{ > + u8 buf = 0; > + int ret; > + > + if (pattern && pattern != DP_TRAINING_PATTERN_4) { > + buf |= DP_LINK_SCRAMBLING_DISABLE; > + > + regmap_update_bits(dp->regmap, DW_DP_CCTL, SCRAMBLE_DIS, > + FIELD_PREP(SCRAMBLE_DIS, 1)); > + } else { > + regmap_update_bits(dp->regmap, DW_DP_CCTL, SCRAMBLE_DIS, > + FIELD_PREP(SCRAMBLE_DIS, 0)); > + } > + > + switch (pattern) { > + case DP_TRAINING_PATTERN_DISABLE: > + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_NONE); > + break; > + case DP_TRAINING_PATTERN_1: > + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_1); > + break; > + case DP_TRAINING_PATTERN_2: > + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_2); > + break; > + case DP_TRAINING_PATTERN_3: > + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_3); > + break; > + case DP_TRAINING_PATTERN_4: > + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_4); > + break; > + default: > + return -EINVAL; > + } > + > + ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, > + buf | pattern); > + if (ret < 0) > + return ret; > + > + return 0; > +} > + > +static u8 dw_dp_voltage_max(u8 preemph) > +{ > + switch (preemph & DP_TRAIN_PRE_EMPHASIS_MASK) { > + case DP_TRAIN_PRE_EMPH_LEVEL_0: > + return DP_TRAIN_VOLTAGE_SWING_LEVEL_3; > + case DP_TRAIN_PRE_EMPH_LEVEL_1: > + return DP_TRAIN_VOLTAGE_SWING_LEVEL_2; > + case DP_TRAIN_PRE_EMPH_LEVEL_2: > + return DP_TRAIN_VOLTAGE_SWING_LEVEL_1; > + case DP_TRAIN_PRE_EMPH_LEVEL_3: > + default: > + return DP_TRAIN_VOLTAGE_SWING_LEVEL_0; > + } > +} > + > +static void dw_dp_link_get_adjustments(struct dw_dp_link *link, > + u8 status[DP_LINK_STATUS_SIZE]) > +{ > + struct dw_dp_link_train_set *adjust = &link->train.adjust; > + u8 v = 0; > + u8 p = 0; > + unsigned int i; > + > + for (i = 0; i < link->lanes; i++) { > + v = drm_dp_get_adjust_request_voltage(status, i); > + p = drm_dp_get_adjust_request_pre_emphasis(status, i); > + if (p >= DP_TRAIN_PRE_EMPH_LEVEL_3) { > + adjust->pre_emphasis[i] = DP_TRAIN_PRE_EMPH_LEVEL_3 >> > + DP_TRAIN_PRE_EMPHASIS_SHIFT; > + adjust->pre_max_reached[i] = true; > + } else { > + adjust->pre_emphasis[i] = p >> DP_TRAIN_PRE_EMPHASIS_SHIFT; > + adjust->pre_max_reached[i] = false; > + } > + v = min(v, dw_dp_voltage_max(p)); > + if (v >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3) { > + adjust->voltage_swing[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_3 >> > + DP_TRAIN_VOLTAGE_SWING_SHIFT; > + adjust->voltage_max_reached[i] = true; > + } else { > + adjust->voltage_swing[i] = v >> DP_TRAIN_VOLTAGE_SWING_SHIFT; > + adjust->voltage_max_reached[i] = false; > + } > + } > +} > + > +static void dw_dp_link_train_adjust(struct dw_dp_link_train *train) > +{ > + struct dw_dp_link_train_set *request = &train->request; > + struct dw_dp_link_train_set *adjust = &train->adjust; > + unsigned int i; > + > + for (i = 0; i < 4; i++) { Shouldn't it be a loop up to link->lanes? > + if (request->voltage_swing[i] != adjust->voltage_swing[i]) > + request->voltage_swing[i] = adjust->voltage_swing[i]; > + if (request->voltage_max_reached[i] != adjust->voltage_max_reached[i]) > + request->voltage_max_reached[i] = adjust->voltage_max_reached[i]; > + } > + > + for (i = 0; i < 4; i++) { > + if (request->pre_emphasis[i] != adjust->pre_emphasis[i]) > + request->pre_emphasis[i] = adjust->pre_emphasis[i]; > + if (request->pre_max_reached[i] != adjust->pre_max_reached[i]) > + request->pre_max_reached[i] = adjust->pre_max_reached[i]; Why do you need separate request and adjustment structs? > + } > +} > + > +static int dw_dp_link_clock_recovery(struct dw_dp *dp) > +{ > + struct dw_dp_link *link = &dp->link; > + u8 status[DP_LINK_STATUS_SIZE]; > + unsigned int tries = 0; > + int ret; > + > + ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1); > + if (ret) > + return ret; > + > + for (;;) { > + ret = dw_dp_link_train_update_vs_emph(dp); > + if (ret) > + return ret; > + > + drm_dp_link_train_clock_recovery_delay(&dp->aux, link->dpcd); > + > + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); > + if (ret < 0) { > + dev_err(dp->dev, "failed to read link status: %d\n", ret); > + return ret; > + } > + > + if (drm_dp_clock_recovery_ok(status, link->lanes)) { > + link->train.clock_recovered = true; > + break; > + } > + > + dw_dp_link_get_adjustments(link, status); > + > + if (link->train.request.voltage_swing[0] == > + link->train.adjust.voltage_swing[0]) Should this take all lanes to account? I think it might be posssible to drop the adjust / request split and adjust tries in dw_dp_link_get_adjustments() instead. > + tries++; > + else > + tries = 0; > + > + if (tries == 5) > + break; > + > + dw_dp_link_train_adjust(&link->train); > + } > + > + return 0; > +} > + > +static int dw_dp_link_channel_equalization(struct dw_dp *dp) > +{ > + struct dw_dp_link *link = &dp->link; > + u8 status[DP_LINK_STATUS_SIZE], pattern; > + unsigned int tries; > + int ret; > + > + if (link->caps.tps4_supported) > + pattern = DP_TRAINING_PATTERN_4; > + else if (link->caps.tps3_supported) > + pattern = DP_TRAINING_PATTERN_3; > + else > + pattern = DP_TRAINING_PATTERN_2; > + ret = dw_dp_link_train_set_pattern(dp, pattern); > + if (ret) > + return ret; > + > + for (tries = 1; tries < 5; tries++) { > + ret = dw_dp_link_train_update_vs_emph(dp); > + if (ret) > + return ret; > + > + drm_dp_link_train_channel_eq_delay(&dp->aux, link->dpcd); > + > + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); > + if (ret < 0) > + return ret; > + > + if (!drm_dp_clock_recovery_ok(status, link->lanes)) { > + dev_err(dp->dev, "clock recovery lost while equalizing channel\n"); > + link->train.clock_recovered = false; > + break; > + } > + > + if (drm_dp_channel_eq_ok(status, link->lanes)) { > + link->train.channel_equalized = true; > + break; > + } > + > + dw_dp_link_get_adjustments(link, status); > + dw_dp_link_train_adjust(&link->train); > + } > + > + return 0; > +} > + > +static int dw_dp_link_downgrade(struct dw_dp *dp) > +{ > + struct dw_dp_link *link = &dp->link; > + struct dw_dp_video *video = &dp->video; > + > + switch (link->rate) { > + case 162000: > + return -EINVAL; > + case 270000: > + link->rate = 162000; > + break; > + case 540000: > + link->rate = 270000; > + break; > + case 810000: > + link->rate = 540000; > + break; > + } > + > + if (!dw_dp_bandwidth_ok(dp, &video->mode, video->bpp, link->lanes, > + link->rate)) > + return -E2BIG; > + > + return 0; > +} > + > +static int dw_dp_link_train_full(struct dw_dp *dp) > +{ > + struct dw_dp_link *link = &dp->link; > + int ret; > + > +retry: > + dw_dp_link_train_init(&link->train); > + > + dev_dbg(dp->dev, "full-training link: %u lane%s at %u MHz\n", > + link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100); > + > + ret = dw_dp_link_configure(dp); > + if (ret < 0) { > + dev_err(dp->dev, "failed to configure DP link: %d\n", ret); > + return ret; > + } > + > + ret = dw_dp_link_clock_recovery(dp); > + if (ret < 0) { > + dev_err(dp->dev, "clock recovery failed: %d\n", ret); > + goto out; > + } > + > + if (!link->train.clock_recovered) { > + dev_err(dp->dev, "clock recovery failed, downgrading link\n"); > + > + ret = dw_dp_link_downgrade(dp); > + if (ret < 0) > + goto out; > + else > + goto retry; > + } > + > + dev_dbg(dp->dev, "clock recovery succeeded\n"); > + > + ret = dw_dp_link_channel_equalization(dp); > + if (ret < 0) { > + dev_err(dp->dev, "channel equalization failed: %d\n", ret); > + goto out; > + } > + > + if (!link->train.channel_equalized) { > + dev_err(dp->dev, "channel equalization failed, downgrading link\n"); > + > + ret = dw_dp_link_downgrade(dp); > + if (ret < 0) > + goto out; > + else > + goto retry; > + } > + > + dev_dbg(dp->dev, "channel equalization succeeded\n"); > + > +out: > + dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE); > + return ret; > +} > + > +static int dw_dp_link_train_fast(struct dw_dp *dp) > +{ > + struct dw_dp_link *link = &dp->link; > + int ret; > + u8 status[DP_LINK_STATUS_SIZE]; > + u8 pattern; > + > + dw_dp_link_train_init(&link->train); > + > + dev_dbg(dp->dev, "fast-training link: %u lane%s at %u MHz\n", > + link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100); > + > + ret = dw_dp_link_configure(dp); > + if (ret < 0) { > + dev_err(dp->dev, "failed to configure DP link: %d\n", ret); > + return ret; > + } > + > + ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1); > + if (ret) > + goto out; > + > + usleep_range(500, 1000); > + > + if (link->caps.tps4_supported) > + pattern = DP_TRAINING_PATTERN_4; > + else if (link->caps.tps3_supported) > + pattern = DP_TRAINING_PATTERN_3; > + else > + pattern = DP_TRAINING_PATTERN_2; > + ret = dw_dp_link_train_set_pattern(dp, pattern); > + if (ret) > + goto out; > + > + usleep_range(500, 1000); > + > + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); > + if (ret < 0) { > + dev_err(dp->dev, "failed to read link status: %d\n", ret); > + goto out; > + } > + > + if (!drm_dp_clock_recovery_ok(status, link->lanes)) { > + dev_err(dp->dev, "clock recovery failed\n"); > + ret = -EIO; > + goto out; > + } > + > + if (!drm_dp_channel_eq_ok(status, link->lanes)) { > + dev_err(dp->dev, "channel equalization failed\n"); > + ret = -EIO; > + goto out; > + } > + > +out: > + dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE); > + return ret; > +} > + > +static int dw_dp_link_train(struct dw_dp *dp) > +{ > + struct dw_dp_link *link = &dp->link; > + int ret; > + > + if (link->caps.fast_training) { > + if (dw_dp_link_train_valid(&link->train)) { > + ret = dw_dp_link_train_fast(dp); > + if (ret < 0) > + dev_err(dp->dev, "fast link training failed: %d\n", ret); > + else > + return 0; > + } > + } > + > + ret = dw_dp_link_train_full(dp); > + if (ret < 0) { > + dev_err(dp->dev, "full link training failed: %d\n", ret); > + return ret; > + } > + > + return 0; > +} > + > +static int dw_dp_send_sdp(struct dw_dp *dp, struct dw_dp_sdp *sdp) > +{ > + const u8 *payload = sdp->db; > + u32 reg; > + int i, nr; > + > + nr = find_first_zero_bit(dp->sdp_reg_bank, SDP_REG_BANK_SIZE); > + if (nr < SDP_REG_BANK_SIZE) > + set_bit(nr, dp->sdp_reg_bank); > + else > + return -EBUSY; > + > + reg = DW_DP_SDP_REGISTER_BANK + nr * 9 * 4; > + > + /* SDP header */ > + regmap_write(dp->regmap, reg, get_unaligned_le32(&sdp->header)); > + > + /* SDP data payload */ > + for (i = 1; i < 9; i++, payload += 4) > + regmap_write(dp->regmap, reg + i * 4, > + FIELD_PREP(SDP_REGS, get_unaligned_le32(payload))); > + > + if (sdp->flags & DW_DP_SDP_VERTICAL_INTERVAL) > + regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL, > + EN_VERTICAL_SDP << nr, > + EN_VERTICAL_SDP << nr); > + > + if (sdp->flags & DW_DP_SDP_HORIZONTAL_INTERVAL) > + regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL, > + EN_HORIZONTAL_SDP << nr, > + EN_HORIZONTAL_SDP << nr); > + > + return 0; > +} > + > +static void dw_dp_vsc_sdp_pack(const struct drm_dp_vsc_sdp *vsc, > + struct dw_dp_sdp *sdp) Use drm_dp_vsc_sdp_pack() instead. > +{ > + sdp->header.HB0 = 0; > + sdp->header.HB1 = DP_SDP_VSC; > + sdp->header.HB2 = vsc->revision; > + sdp->header.HB3 = vsc->length; > + > + sdp->db[16] = (vsc->pixelformat & 0xf) << 4; > + sdp->db[16] |= vsc->colorimetry & 0xf; > + > + switch (vsc->bpc) { > + case 8: > + sdp->db[17] = 0x1; > + break; > + case 10: > + sdp->db[17] = 0x2; > + break; > + case 12: > + sdp->db[17] = 0x3; > + break; > + case 16: > + sdp->db[17] = 0x4; > + break; > + case 6: > + default: > + break; > + } > + > + if (vsc->dynamic_range == DP_DYNAMIC_RANGE_CTA) > + sdp->db[17] |= 0x80; > + > + sdp->db[18] = vsc->content_type & 0x7; > + > + sdp->flags |= DW_DP_SDP_VERTICAL_INTERVAL; > +} > + > +static int dw_dp_send_vsc_sdp(struct dw_dp *dp) > +{ > + struct dw_dp_video *video = &dp->video; > + struct drm_dp_vsc_sdp vsc = {}; > + struct dw_dp_sdp sdp = {}; > + > + vsc.revision = 0x5; > + vsc.length = 0x13; > + > + switch (video->color_format) { > + case DRM_COLOR_FORMAT_YCBCR444: > + vsc.pixelformat = DP_PIXELFORMAT_YUV444; > + break; > + case DRM_COLOR_FORMAT_YCBCR420: > + vsc.pixelformat = DP_PIXELFORMAT_YUV420; > + break; > + case DRM_COLOR_FORMAT_YCBCR422: > + vsc.pixelformat = DP_PIXELFORMAT_YUV422; > + break; > + case DRM_COLOR_FORMAT_RGB444: > + default: > + vsc.pixelformat = DP_PIXELFORMAT_RGB; > + break; > + } > + > + if (video->color_format == DRM_COLOR_FORMAT_RGB444) { > + vsc.colorimetry = DP_COLORIMETRY_DEFAULT; > + vsc.dynamic_range = DP_DYNAMIC_RANGE_VESA; > + } else { > + vsc.colorimetry = DP_COLORIMETRY_BT709_YCC; > + vsc.dynamic_range = DP_DYNAMIC_RANGE_CTA; > + } > + > + vsc.bpc = video->bpc; > + vsc.content_type = DP_CONTENT_TYPE_NOT_DEFINED; > + > + dw_dp_vsc_sdp_pack(&vsc, &sdp); > + > + return dw_dp_send_sdp(dp, &sdp); > +} > + > +static int dw_dp_video_set_pixel_mode(struct dw_dp *dp, u8 pixel_mode) > +{ > + switch (pixel_mode) { > + case DW_DP_MP_SINGLE_PIXEL: > + case DW_DP_MP_DUAL_PIXEL: > + case DW_DP_MP_QUAD_PIXEL: > + break; > + default: > + return -EINVAL; Is it possible? > + } > + > + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, PIXEL_MODE_SELECT, > + FIELD_PREP(PIXEL_MODE_SELECT, pixel_mode)); > + > + return 0; > +} > + > +static bool dw_dp_video_need_vsc_sdp(struct dw_dp *dp) > +{ > + struct dw_dp_link *link = &dp->link; > + struct dw_dp_video *video = &dp->video; > + > + if (!link->vsc_sdp_supported) > + return false; > + > + if (video->color_format == DRM_COLOR_FORMAT_YCBCR420) > + return true; > + > + return false; > +} > + > +static int dw_dp_video_set_msa(struct dw_dp *dp, u8 color_format, u8 bpc, > + u16 vstart, u16 hstart) > +{ > + u16 misc = 0; > + > + if (dw_dp_video_need_vsc_sdp(dp)) > + misc |= DP_MSA_MISC_COLOR_VSC_SDP; > + > + switch (color_format) { > + case DRM_COLOR_FORMAT_RGB444: > + misc |= DP_MSA_MISC_COLOR_RGB; > + break; > + case DRM_COLOR_FORMAT_YCBCR444: > + misc |= DP_MSA_MISC_COLOR_YCBCR_444_BT709; > + break; > + case DRM_COLOR_FORMAT_YCBCR422: > + misc |= DP_MSA_MISC_COLOR_YCBCR_422_BT709; > + break; > + case DRM_COLOR_FORMAT_YCBCR420: > + break; > + default: > + return -EINVAL; > + } > + > + switch (bpc) { > + case 6: > + misc |= DP_MSA_MISC_6_BPC; > + break; > + case 8: > + misc |= DP_MSA_MISC_8_BPC; > + break; > + case 10: > + misc |= DP_MSA_MISC_10_BPC; > + break; > + case 12: > + misc |= DP_MSA_MISC_12_BPC; > + break; > + case 16: > + misc |= DP_MSA_MISC_16_BPC; > + break; > + default: > + return -EINVAL; > + } > + > + regmap_write(dp->regmap, DW_DP_VIDEO_MSA1, > + FIELD_PREP(VSTART, vstart) | FIELD_PREP(HSTART, hstart)); > + regmap_write(dp->regmap, DW_DP_VIDEO_MSA2, FIELD_PREP(MISC0, misc)); > + regmap_write(dp->regmap, DW_DP_VIDEO_MSA3, FIELD_PREP(MISC1, misc >> 8)); > + > + return 0; > +} > + > +static void dw_dp_video_disable(struct dw_dp *dp) > +{ > + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE, > + FIELD_PREP(VIDEO_STREAM_ENABLE, 0)); > +} > + > +static int dw_dp_video_enable(struct dw_dp *dp) > +{ > + struct dw_dp_video *video = &dp->video; > + struct dw_dp_link *link = &dp->link; > + struct drm_display_mode *mode = &video->mode; > + u8 color_format = video->color_format; > + u8 bpc = video->bpc; > + u8 pixel_mode = video->pixel_mode; > + u8 bpp = video->bpp, init_threshold, vic; > + u32 hactive, hblank, h_sync_width, h_front_porch; > + u32 vactive, vblank, v_sync_width, v_front_porch; > + u32 vstart = mode->vtotal - mode->vsync_start; > + u32 hstart = mode->htotal - mode->hsync_start; > + u32 peak_stream_bandwidth, link_bandwidth; > + u32 average_bytes_per_tu, average_bytes_per_tu_frac; > + u32 ts, hblank_interval; > + u32 value; > + int ret; > + > + ret = dw_dp_video_set_pixel_mode(dp, pixel_mode); > + if (ret) > + return ret; > + > + ret = dw_dp_video_set_msa(dp, color_format, bpc, vstart, hstart); > + if (ret) > + return ret; > + > + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_MAPPING, > + FIELD_PREP(VIDEO_MAPPING, video->video_mapping)); > + > + /* Configure DW_DP_VINPUT_POLARITY_CTRL register */ > + value = 0; > + if (mode->flags & DRM_MODE_FLAG_PHSYNC) > + value |= FIELD_PREP(HSYNC_IN_POLARITY, 1); > + if (mode->flags & DRM_MODE_FLAG_PVSYNC) > + value |= FIELD_PREP(VSYNC_IN_POLARITY, 1); > + regmap_write(dp->regmap, DW_DP_VINPUT_POLARITY_CTRL, value); > + > + /* Configure DW_DP_VIDEO_CONFIG1 register */ > + hactive = mode->hdisplay; > + hblank = mode->htotal - mode->hdisplay; > + value = FIELD_PREP(HACTIVE, hactive) | FIELD_PREP(HBLANK, hblank); > + if (mode->flags & DRM_MODE_FLAG_INTERLACE) > + value |= FIELD_PREP(I_P, 1); > + vic = drm_match_cea_mode(mode); > + if (vic == 5 || vic == 6 || vic == 7 || > + vic == 10 || vic == 11 || vic == 20 || > + vic == 21 || vic == 22 || vic == 39 || > + vic == 25 || vic == 26 || vic == 40 || > + vic == 44 || vic == 45 || vic == 46 || > + vic == 50 || vic == 51 || vic == 54 || > + vic == 55 || vic == 58 || vic == 59) > + value |= R_V_BLANK_IN_OSC; > + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG1, value); > + > + /* Configure DW_DP_VIDEO_CONFIG2 register */ > + vblank = mode->vtotal - mode->vdisplay; > + vactive = mode->vdisplay; > + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG2, > + FIELD_PREP(VBLANK, vblank) | FIELD_PREP(VACTIVE, vactive)); > + > + /* Configure DW_DP_VIDEO_CONFIG3 register */ > + h_sync_width = mode->hsync_end - mode->hsync_start; > + h_front_porch = mode->hsync_start - mode->hdisplay; > + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG3, > + FIELD_PREP(H_SYNC_WIDTH, h_sync_width) | > + FIELD_PREP(H_FRONT_PORCH, h_front_porch)); > + > + /* Configure DW_DP_VIDEO_CONFIG4 register */ > + v_sync_width = mode->vsync_end - mode->vsync_start; > + v_front_porch = mode->vsync_start - mode->vdisplay; > + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG4, > + FIELD_PREP(V_SYNC_WIDTH, v_sync_width) | > + FIELD_PREP(V_FRONT_PORCH, v_front_porch)); > + > + /* Configure DW_DP_VIDEO_CONFIG5 register */ > + peak_stream_bandwidth = mode->clock * bpp / 8; > + link_bandwidth = (link->rate / 1000) * link->lanes; > + ts = peak_stream_bandwidth * 64 / link_bandwidth; > + average_bytes_per_tu = ts / 1000; > + average_bytes_per_tu_frac = ts / 100 - average_bytes_per_tu * 10; > + if (pixel_mode == DW_DP_MP_SINGLE_PIXEL) { > + if (average_bytes_per_tu < 6) > + init_threshold = 32; > + else if (hblank <= 80 && color_format != DRM_COLOR_FORMAT_YCBCR420) > + init_threshold = 12; > + else if (hblank <= 40 && color_format == DRM_COLOR_FORMAT_YCBCR420) > + init_threshold = 3; > + else > + init_threshold = 16; > + } else { > + u32 t1 = 0, t2 = 0, t3 = 0; > + > + switch (bpc) { > + case 6: > + t1 = (4 * 1000 / 9) * link->lanes; > + break; > + case 8: > + if (color_format == DRM_COLOR_FORMAT_YCBCR422) { > + t1 = (1000 / 2) * link->lanes; > + } else { > + if (pixel_mode == DW_DP_MP_DUAL_PIXEL) > + t1 = (1000 / 3) * link->lanes; > + else > + t1 = (3000 / 16) * link->lanes; > + } > + break; > + case 10: > + if (color_format == DRM_COLOR_FORMAT_YCBCR422) > + t1 = (2000 / 5) * link->lanes; > + else > + t1 = (4000 / 15) * link->lanes; > + break; > + case 12: > + if (color_format == DRM_COLOR_FORMAT_YCBCR422) { > + if (pixel_mode == DW_DP_MP_DUAL_PIXEL) > + t1 = (1000 / 6) * link->lanes; > + else > + t1 = (1000 / 3) * link->lanes; > + } else { > + t1 = (2000 / 9) * link->lanes; > + } > + break; > + case 16: > + if (color_format != DRM_COLOR_FORMAT_YCBCR422 && > + pixel_mode == DW_DP_MP_DUAL_PIXEL) > + t1 = (1000 / 6) * link->lanes; > + else > + t1 = (1000 / 4) * link->lanes; > + break; > + default: > + return -EINVAL; > + } > + > + if (color_format == DRM_COLOR_FORMAT_YCBCR420) > + t2 = (link->rate / 4) * 1000 / (mode->clock / 2); > + else > + t2 = (link->rate / 4) * 1000 / mode->clock; > + > + if (average_bytes_per_tu_frac) > + t3 = average_bytes_per_tu + 1; > + else > + t3 = average_bytes_per_tu; > + init_threshold = t1 * t2 * t3 / (1000 * 1000); > + if (init_threshold <= 16 || average_bytes_per_tu < 10) > + init_threshold = 40; > + } > + > + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG5, > + FIELD_PREP(INIT_THRESHOLD_HI, init_threshold >> 6) | > + FIELD_PREP(AVERAGE_BYTES_PER_TU_FRAC, average_bytes_per_tu_frac) | > + FIELD_PREP(INIT_THRESHOLD, init_threshold) | > + FIELD_PREP(AVERAGE_BYTES_PER_TU, average_bytes_per_tu)); > + > + /* Configure DW_DP_VIDEO_HBLANK_INTERVAL register */ > + hblank_interval = hblank * (link->rate / 4) / mode->clock; > + regmap_write(dp->regmap, DW_DP_VIDEO_HBLANK_INTERVAL, > + FIELD_PREP(HBLANK_INTERVAL_EN, 1) | > + FIELD_PREP(HBLANK_INTERVAL, hblank_interval)); > + > + /* Video stream enable */ > + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE, > + FIELD_PREP(VIDEO_STREAM_ENABLE, 1)); > + > + if (dw_dp_video_need_vsc_sdp(dp)) > + dw_dp_send_vsc_sdp(dp); > + > + return 0; > +} > + > +static void dw_dp_hpd_init(struct dw_dp *dp) > +{ > + /* Enable all HPD interrupts */ > + regmap_update_bits(dp->regmap, DW_DP_HPD_INTERRUPT_ENABLE, > + HPD_UNPLUG_EN | HPD_PLUG_EN | HPD_IRQ_EN, > + FIELD_PREP(HPD_UNPLUG_EN, 1) | > + FIELD_PREP(HPD_PLUG_EN, 1) | > + FIELD_PREP(HPD_IRQ_EN, 1)); > + > + /* Enable all top-level interrupts */ > + regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE, > + HPD_EVENT_EN, FIELD_PREP(HPD_EVENT_EN, 1)); > +} > + > +static void dw_dp_aux_init(struct dw_dp *dp) > +{ > + regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE, > + AUX_REPLY_EVENT_EN, FIELD_PREP(AUX_REPLY_EVENT_EN, 1)); > +} > + > +static void dw_dp_init_hw(struct dw_dp *dp) > +{ > + regmap_update_bits(dp->regmap, DW_DP_CCTL, DEFAULT_FAST_LINK_TRAIN_EN, > + FIELD_PREP(DEFAULT_FAST_LINK_TRAIN_EN, 0)); > + > + dw_dp_hpd_init(dp); > + dw_dp_aux_init(dp); > +} > + > +static int dw_dp_aux_write_data(struct dw_dp *dp, const u8 *buffer, size_t size) > +{ > + size_t i, j; > + > + for (i = 0; i < DIV_ROUND_UP(size, 4); i++) { > + size_t num = min_t(size_t, size - i * 4, 4); > + u32 value = 0; > + > + for (j = 0; j < num; j++) > + value |= buffer[i * 4 + j] << (j * 8); > + > + regmap_write(dp->regmap, DW_DP_AUX_DATA0 + i * 4, value); > + } > + > + return size; > +} > + > +static int dw_dp_aux_read_data(struct dw_dp *dp, u8 *buffer, size_t size) > +{ > + size_t i, j; > + > + for (i = 0; i < DIV_ROUND_UP(size, 4); i++) { > + size_t num = min_t(size_t, size - i * 4, 4); > + u32 value; > + > + regmap_read(dp->regmap, DW_DP_AUX_DATA0 + i * 4, &value); > + > + for (j = 0; j < num; j++) > + buffer[i * 4 + j] = value >> (j * 8); > + } > + > + return size; > +} > + > +static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux, > + struct drm_dp_aux_msg *msg) > +{ > + struct dw_dp *dp = container_of(aux, struct dw_dp, aux); > + unsigned long timeout = msecs_to_jiffies(10); > + u32 status, value; > + ssize_t ret = 0; > + > + if (WARN_ON(msg->size > 16)) > + return -E2BIG; > + > + switch (msg->request & ~DP_AUX_I2C_MOT) { > + case DP_AUX_NATIVE_WRITE: > + case DP_AUX_I2C_WRITE: > + case DP_AUX_I2C_WRITE_STATUS_UPDATE: > + ret = dw_dp_aux_write_data(dp, msg->buffer, msg->size); > + if (ret < 0) > + return ret; > + break; > + case DP_AUX_NATIVE_READ: > + case DP_AUX_I2C_READ: > + break; > + default: > + return -EINVAL; > + } > + > + if (msg->size > 0) > + value = FIELD_PREP(AUX_LEN_REQ, msg->size - 1); > + else > + value = FIELD_PREP(I2C_ADDR_ONLY, 1); > + value |= FIELD_PREP(AUX_CMD_TYPE, msg->request); > + value |= FIELD_PREP(AUX_ADDR, msg->address); > + regmap_write(dp->regmap, DW_DP_AUX_CMD, value); > + > + status = wait_for_completion_timeout(&dp->complete, timeout); > + if (!status) { > + dev_err(dp->dev, "timeout waiting for AUX reply\n"); > + return -ETIMEDOUT; > + } > + > + regmap_read(dp->regmap, DW_DP_AUX_STATUS, &value); > + if (value & AUX_TIMEOUT) > + return -ETIMEDOUT; > + > + msg->reply = FIELD_GET(AUX_STATUS, value); > + > + if (msg->size > 0 && msg->reply == DP_AUX_NATIVE_REPLY_ACK) { > + if (msg->request & DP_AUX_I2C_READ) { > + size_t count = FIELD_GET(AUX_BYTES_READ, value) - 1; > + > + if (count != msg->size) > + return -EBUSY; > + > + ret = dw_dp_aux_read_data(dp, msg->buffer, count); > + if (ret < 0) > + return ret; > + } > + } > + > + return ret; > +} > + > +/* > + * Limits for the video timing output by DP: > + * 1. the hfp should be 2 pixels aligned; > + * 2. the minimum hsync should be 9 pixel; > + * 3. the minimum hbp should be 16 pixel; > + */ > +static bool dw_dp_bridge_mode_fixup(struct drm_bridge *bridge, > + const struct drm_display_mode *mode, > + struct drm_display_mode *adjusted_mode) > +{ > + struct dw_dp *dp = bridge_to_dp(bridge); > + int min_hbp = 16; > + int min_hsync = 9; > + > + if ((adjusted_mode->hsync_start - adjusted_mode->hdisplay) & 0x1) { > + adjusted_mode->hsync_start += 1; > + dev_warn(dp->dev, "hfp is not 2 pixeel aligned, fixup to aligned hfp\n"); > + } > + if (adjusted_mode->hsync_end - adjusted_mode->hsync_start < min_hsync) { > + adjusted_mode->hsync_end = adjusted_mode->hsync_start + min_hsync; > + dev_warn(dp->dev, "hsync is too narrow, fixup to min hsync:%d\n", min_hsync); > + } > + if (adjusted_mode->htotal - adjusted_mode->hsync_end < min_hbp) { > + adjusted_mode->htotal = adjusted_mode->hsync_end + min_hbp; > + dev_warn(dp->dev, "hbp is too narrow, fixup to min hbp:%d\n", min_hbp); > + } > + > + return true; > +} > + > +static enum drm_mode_status dw_dp_bridge_mode_valid(struct drm_bridge *bridge, > + const struct drm_display_info *info, > + const struct drm_display_mode *mode) > +{ > + struct dw_dp *dp = bridge_to_dp(bridge); > + struct dw_dp_link *link = &dp->link; > + u32 min_bpp; > + > + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR420 && > + link->vsc_sdp_supported && > + (drm_mode_is_420_only(info, mode) || drm_mode_is_420_also(info, mode))) > + min_bpp = 12; > + else if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) > + min_bpp = 16; > + else if (info->color_formats & DRM_COLOR_FORMAT_RGB444) > + min_bpp = 18; > + else > + min_bpp = 24; > + > + if (!link->vsc_sdp_supported && > + drm_mode_is_420_only(info, mode)) > + return MODE_NO_420; > + > + if (!dw_dp_bandwidth_ok(dp, mode, min_bpp, link->lanes, link->rate)) > + return MODE_CLOCK_HIGH; > + > + return MODE_OK; > +} > + > +static void dw_dp_bridge_atomic_pre_enable(struct drm_bridge *bridge, > + struct drm_bridge_state *old_bridge_state) > +{ > + struct drm_atomic_state *state = old_bridge_state->base.state; > + struct drm_crtc_state *crtc_state = bridge->encoder->crtc->state; > + struct dw_dp *dp = bridge_to_dp(bridge); > + struct dw_dp_video *video = &dp->video; > + struct drm_display_mode *m = &video->mode; > + struct drm_bridge_state *bridge_state; > + const struct dw_dp_output_format *fmt; > + > + drm_mode_copy(m, &crtc_state->adjusted_mode); > + > + bridge_state = drm_atomic_get_new_bridge_state(state, bridge); > + fmt = dw_dp_get_output_format(bridge_state->output_bus_cfg.format); Should it be a part of the atomic_check() instead? > + > + video->video_mapping = fmt->video_mapping; > + video->color_format = fmt->color_format; > + video->bpc = fmt->bpc; > + video->bpp = fmt->bpp; > +} > + > +static bool dw_dp_needs_link_retrain(struct dw_dp *dp) > +{ > + struct dw_dp_link *link = &dp->link; > + u8 link_status[DP_LINK_STATUS_SIZE]; > + > + if (!dw_dp_link_train_valid(&link->train)) > + return false; > + > + if (drm_dp_dpcd_read_link_status(&dp->aux, link_status) < 0) > + return false; > + > + /* Retrain if Channel EQ or CR not ok */ > + return !drm_dp_channel_eq_ok(link_status, dp->link.lanes); > +} > + > +static void dw_dp_link_disable(struct dw_dp *dp) > +{ > + struct dw_dp_link *link = &dp->link; > + > + if (dw_dp_hpd_detect(dp)) > + dw_dp_link_power_down(dp); > + > + dw_dp_phy_xmit_enable(dp, 0); > + > + phy_power_off(dp->phy); > + > + link->train.clock_recovered = false; > + link->train.channel_equalized = false; > +} > + > +static int dw_dp_link_enable(struct dw_dp *dp) > +{ > + int ret; > + > + ret = phy_power_on(dp->phy); > + if (ret) > + return ret; > + > + ret = dw_dp_link_power_up(dp); > + if (ret < 0) > + return ret; > + > + ret = dw_dp_link_train(dp); > + > + return ret; > +} > + > +static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge, > + struct drm_bridge_state *old_state) > +{ > + struct dw_dp *dp = bridge_to_dp(bridge); > + struct drm_atomic_state *state = old_state->base.state; > + struct drm_connector *connector; > + struct drm_connector_state *conn_state; > + int ret; > + > + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); > + if (!connector) { > + dev_err(dp->dev, "failed to get connector\n"); > + return; > + } > + > + conn_state = drm_atomic_get_new_connector_state(state, connector); > + if (!conn_state) { > + dev_err(dp->dev, "failed to get connector state\n"); > + return; > + } > + > + set_bit(0, dp->sdp_reg_bank); > + > + ret = dw_dp_link_enable(dp); > + if (ret < 0) { > + dev_err(dp->dev, "failed to enable link: %d\n", ret); > + return; > + } > + > + ret = dw_dp_video_enable(dp); > + if (ret < 0) { > + dev_err(dp->dev, "failed to enable video: %d\n", ret); > + return; > + } > +} > + > +static void dw_dp_reset(struct dw_dp *dp) > +{ > + int val; > + > + disable_irq(dp->irq); > + regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET, > + FIELD_PREP(CONTROLLER_RESET, 1)); > + udelay(10); > + regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET, > + FIELD_PREP(CONTROLLER_RESET, 0)); > + > + dw_dp_init_hw(dp); > + regmap_read_poll_timeout(dp->regmap, DW_DP_HPD_STATUS, val, > + FIELD_GET(HPD_HOT_PLUG, val), 200, 200000); > + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG); > + enable_irq(dp->irq); > +} > + > +static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge, > + struct drm_bridge_state *old_bridge_state) > +{ > + struct dw_dp *dp = bridge_to_dp(bridge); > + > + dw_dp_video_disable(dp); > + dw_dp_link_disable(dp); > + bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE); > + dw_dp_reset(dp); > +} > + > +static bool dw_dp_hpd_detect_link(struct dw_dp *dp) > +{ > + int ret; > + > + ret = phy_power_on(dp->phy); > + if (ret < 0) > + return false; > + ret = dw_dp_link_parse(dp); > + phy_power_off(dp->phy); > + > + return !ret; > +} > + > +static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge) > +{ > + struct dw_dp *dp = bridge_to_dp(bridge); > + > + if (!dw_dp_hpd_detect(dp)) > + return connector_status_disconnected; > + > + if (!dw_dp_hpd_detect_link(dp)) > + return connector_status_disconnected; > + > + return connector_status_connected; > +} > + > +static const struct drm_edid *dw_dp_bridge_edid_read(struct drm_bridge *bridge, > + struct drm_connector *connector) > +{ > + struct dw_dp *dp = bridge_to_dp(bridge); > + const struct drm_edid *edid; > + int ret; > + > + ret = phy_power_on(dp->phy); > + if (ret) > + return NULL; > + > + edid = drm_edid_read_ddc(connector, &dp->aux.ddc); > + > + phy_power_off(dp->phy); > + > + return edid; > +} > + > +static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, > + struct drm_bridge_state *bridge_state, > + struct drm_crtc_state *crtc_state, > + struct drm_connector_state *conn_state, > + unsigned int *num_output_fmts) > +{ > + struct dw_dp *dp = bridge_to_dp(bridge); > + struct dw_dp_link *link = &dp->link; > + struct drm_display_info *di = &conn_state->connector->display_info; > + struct drm_display_mode mode = crtc_state->mode; > + const struct dw_dp_output_format *fmt; > + u32 i, j = 0; > + u32 *output_fmts; > + > + *num_output_fmts = 0; > + > + output_fmts = kcalloc(ARRAY_SIZE(dw_dp_output_formats), sizeof(*output_fmts), GFP_KERNEL); > + if (!output_fmts) > + return NULL; > + > + for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) { > + fmt = &dw_dp_output_formats[i]; > + > + if (fmt->bpc > conn_state->max_bpc) > + continue; > + > + if (!(fmt->color_format & di->color_formats)) > + continue; > + > + if (fmt->color_format == DRM_COLOR_FORMAT_YCBCR420 && > + !link->vsc_sdp_supported) > + continue; > + > + if (fmt->color_format != DRM_COLOR_FORMAT_YCBCR420 && > + drm_mode_is_420_only(di, &mode)) > + continue; > + > + if (!dw_dp_bandwidth_ok(dp, &mode, fmt->bpp, link->lanes, link->rate)) > + continue; > + > + output_fmts[j++] = fmt->bus_format; > + } > + > + *num_output_fmts = j; > + > + return output_fmts; > +} > + > +static const struct drm_bridge_funcs dw_dp_bridge_funcs = { > + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, > + .atomic_reset = drm_atomic_helper_bridge_reset, > + .atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt, > + .atomic_get_output_bus_fmts = dw_dp_bridge_atomic_get_output_bus_fmts, > + .mode_fixup = dw_dp_bridge_mode_fixup, > + .mode_valid = dw_dp_bridge_mode_valid, > + .atomic_pre_enable = dw_dp_bridge_atomic_pre_enable, > + .atomic_enable = dw_dp_bridge_atomic_enable, > + .atomic_disable = dw_dp_bridge_atomic_disable, > + .detect = dw_dp_bridge_detect, > + .edid_read = dw_dp_bridge_edid_read, > +}; > + > +static int dw_dp_link_retrain(struct dw_dp *dp) > +{ > + struct drm_device *dev = dp->bridge.dev; > + struct drm_modeset_acquire_ctx ctx; > + int ret; > + > + if (!dw_dp_needs_link_retrain(dp)) > + return 0; > + > + dev_dbg(dp->dev, "Retraining link\n"); > + > + drm_modeset_acquire_init(&ctx, 0); > + for (;;) { > + ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx); > + if (ret != -EDEADLK) > + break; I think it's an error to continue in such a case. There should be a return after the loop if ret is not 0. > + > + drm_modeset_backoff(&ctx); > + } > + > + ret = dw_dp_link_train(dp); > + drm_modeset_drop_locks(&ctx); > + drm_modeset_acquire_fini(&ctx); > + > + return ret; > +} > + > +static void dw_dp_hpd_work(struct work_struct *work) > +{ > + struct dw_dp *dp = container_of(work, struct dw_dp, hpd_work); > + bool long_hpd; > + int ret; > + > + mutex_lock(&dp->irq_lock); > + long_hpd = dp->hotplug.long_hpd; > + mutex_unlock(&dp->irq_lock); > + > + dev_dbg(dp->dev, "[drm] Get hpd irq - %s\n", long_hpd ? "long" : "short"); > + > + if (!long_hpd) { > + if (dw_dp_needs_link_retrain(dp)) { > + ret = dw_dp_link_retrain(dp); > + if (ret) > + dev_warn(dp->dev, "Retrain link failed\n"); > + } > + } else { > + drm_helper_hpd_irq_event(dp->bridge.dev); > + } > +} > + > +static void dw_dp_handle_hpd_event(struct dw_dp *dp) > +{ > + u32 value; > + > + mutex_lock(&dp->irq_lock); > + regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value); > + > + if (value & HPD_IRQ) { > + dev_dbg(dp->dev, "IRQ from the HPD\n"); > + dp->hotplug.long_hpd = false; > + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_IRQ); > + } > + > + if (value & HPD_HOT_PLUG) { > + dev_dbg(dp->dev, "Hot plug detected\n"); > + dp->hotplug.long_hpd = true; > + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG); > + } > + > + if (value & HPD_HOT_UNPLUG) { > + dev_dbg(dp->dev, "Unplug detected\n"); > + dp->hotplug.long_hpd = true; > + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_UNPLUG); > + } > + mutex_unlock(&dp->irq_lock); > + > + schedule_work(&dp->hpd_work); > +} > + > +static irqreturn_t dw_dp_irq(int irq, void *data) > +{ > + struct dw_dp *dp = data; > + u32 value; > + > + regmap_read(dp->regmap, DW_DP_GENERAL_INTERRUPT, &value); > + if (!value) > + return IRQ_NONE; > + > + if (value & HPD_EVENT) > + dw_dp_handle_hpd_event(dp); > + > + if (value & AUX_REPLY_EVENT) { > + regmap_write(dp->regmap, DW_DP_GENERAL_INTERRUPT, AUX_REPLY_EVENT); > + complete(&dp->complete); > + } > + > + return IRQ_HANDLED; > +} > + > +static const struct regmap_range dw_dp_readable_ranges[] = { > + regmap_reg_range(DW_DP_VERSION_NUMBER, DW_DP_ID), > + regmap_reg_range(DW_DP_CONFIG_REG1, DW_DP_CONFIG_REG3), > + regmap_reg_range(DW_DP_CCTL, DW_DP_SOFT_RESET_CTRL), > + regmap_reg_range(DW_DP_VSAMPLE_CTRL, DW_DP_VIDEO_HBLANK_INTERVAL), > + regmap_reg_range(DW_DP_AUD_CONFIG1, DW_DP_AUD_CONFIG1), > + regmap_reg_range(DW_DP_SDP_VERTICAL_CTRL, DW_DP_SDP_STATUS_EN), > + regmap_reg_range(DW_DP_PHYIF_CTRL, DW_DP_PHYIF_PWRDOWN_CTRL), > + regmap_reg_range(DW_DP_AUX_CMD, DW_DP_AUX_DATA3), > + regmap_reg_range(DW_DP_GENERAL_INTERRUPT, DW_DP_HPD_INTERRUPT_ENABLE), > +}; > + > +static const struct regmap_access_table dw_dp_readable_table = { > + .yes_ranges = dw_dp_readable_ranges, > + .n_yes_ranges = ARRAY_SIZE(dw_dp_readable_ranges), > +}; > + > +static const struct regmap_config dw_dp_regmap_config = { > + .reg_bits = 32, > + .reg_stride = 4, > + .val_bits = 32, > + .fast_io = true, > + .max_register = DW_DP_MAX_REGISTER, > + .rd_table = &dw_dp_readable_table, > +}; > + > +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, > + const struct dw_dp_plat_data *plat_data) > +{ > + struct platform_device *pdev = to_platform_device(dev); > + struct dw_dp *dp; > + struct drm_bridge *bridge; > + void __iomem *res; > + int ret; > + > + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); > + if (!dp) > + return ERR_PTR(-ENOMEM); > + > + dp->dev = dev; > + dp->video.pixel_mode = DW_DP_MP_QUAD_PIXEL; > + > + dp->plat_data = plat_data; > + bridge = &dp->bridge; > + mutex_init(&dp->irq_lock); > + INIT_WORK(&dp->hpd_work, dw_dp_hpd_work); > + init_completion(&dp->complete); > + > + res = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(res)) > + return ERR_CAST(res); > + > + dp->regmap = devm_regmap_init_mmio(dev, res, &dw_dp_regmap_config); > + if (IS_ERR(dp->regmap)) { > + dev_err_probe(dev, PTR_ERR(dp->regmap), "failed to create regmap\n"); > + return ERR_CAST(dp->regmap); > + } > + > + dp->phy = devm_of_phy_get(dev, dev->of_node, NULL); > + if (IS_ERR(dp->phy)) { > + dev_err_probe(dev, PTR_ERR(dp->phy), "failed to get phy\n"); > + return ERR_CAST(dp->phy); > + } > + > + dp->apb_clk = devm_clk_get_enabled(dev, "apb"); > + if (IS_ERR(dp->apb_clk)) { > + dev_err_probe(dev, PTR_ERR(dp->apb_clk), "failed to get apb clock\n"); > + return ERR_CAST(dp->apb_clk); > + } > + > + dp->aux_clk = devm_clk_get_enabled(dev, "aux"); > + if (IS_ERR(dp->aux_clk)) { > + dev_err_probe(dev, PTR_ERR(dp->aux_clk), "failed to get aux clock\n"); > + return ERR_CAST(dp->aux_clk); > + } > + > + dp->i2s_clk = devm_clk_get(dev, "i2s"); > + if (IS_ERR(dp->i2s_clk)) { > + dev_err_probe(dev, PTR_ERR(dp->i2s_clk), "failed to get i2s clock\n"); > + return ERR_CAST(dp->i2s_clk); > + } > + > + dp->spdif_clk = devm_clk_get(dev, "spdif"); > + if (IS_ERR(dp->spdif_clk)) { > + dev_err_probe(dev, PTR_ERR(dp->spdif_clk), "failed to get spdif clock\n"); > + return ERR_CAST(dp->spdif_clk); > + } > + > + dp->hdcp_clk = devm_clk_get(dev, "hdcp"); > + if (IS_ERR(dp->hdcp_clk)) { > + dev_err_probe(dev, PTR_ERR(dp->hdcp_clk), "failed to get hdcp clock\n"); > + return ERR_CAST(dp->hdcp_clk); > + } > + > + dp->rstc = devm_reset_control_get(dev, NULL); > + if (IS_ERR(dp->rstc)) { > + dev_err_probe(dev, PTR_ERR(dp->rstc), "failed to get reset control\n"); > + return ERR_CAST(dp->rstc); > + } > + > + dp->irq = platform_get_irq(pdev, 0); > + if (dp->irq < 0) > + return ERR_PTR(ret); > + > + ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq, > + IRQF_ONESHOT, dev_name(dev), dp); Are you ready to handle the IRQ here, before the bridge is filled? > + if (ret) { > + dev_err_probe(dev, ret, "failed to request irq\n"); > + return ERR_PTR(ret); > + } > + > + bridge->of_node = dev->of_node; > + bridge->funcs = &dw_dp_bridge_funcs; > + bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; > + bridge->type = DRM_MODE_CONNECTOR_DisplayPort; > + bridge->ycbcr_420_allowed = true; > + bridge->vendor = "Synopsys"; > + bridge->product = "DW DP TX"; These two are unused > + > + platform_set_drvdata(pdev, dp); Unused > + > + dp->aux.dev = dev; > + dp->aux.drm_dev = encoder->dev; > + dp->aux.name = dev_name(dev); > + dp->aux.transfer = dw_dp_aux_transfer; > + ret = drm_dp_aux_register(&dp->aux); > + if (ret) { > + dev_err_probe(dev, ret, "Aux register failed\n"); > + return ERR_PTR(ret); > + } > + > + ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); > + if (ret) > + dev_err_probe(dev, ret, "Failed to attach bridge\n"); > + > + dw_dp_init_hw(dp); > + > + return dp; > +} > +MODULE_AUTHOR("Andy Yan <andyshrk@163.com>"); > +MODULE_DESCRIPTION("DW DP Core Library"); > +MODULE_LICENSE("GPL"); > diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h > new file mode 100644 > index 000000000000..07845f609eca > --- /dev/null > +++ b/include/drm/bridge/dw_dp.h > @@ -0,0 +1,19 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > +/* > + * Copyright (c) 2025 Rockchip Electronics Co., Ltd. > + */ > + > +#ifndef __DW_DP__ > +#define __DW_DP__ > + > +struct device; > +struct drm_encoder; > +struct dw_dp; > + > +struct dw_dp_plat_data { > + u32 max_link_rate; > +}; > + > +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, > + const struct dw_dp_plat_data *plat_data); > +#endif /* __DW_DP__ */ > -- > 2.34.1 > -- With best wishes Dmitry ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re:Re: [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library 2025-03-01 18:14 ` Dmitry Baryshkov @ 2025-03-02 10:34 ` Andy Yan 2025-03-02 16:21 ` Dmitry Baryshkov 2025-03-03 8:59 ` Yubing Zhang 1 sibling, 1 reply; 32+ messages in thread From: Andy Yan @ 2025-03-02 10:34 UTC (permalink / raw) To: Dmitry Baryshkov Cc: heiko, hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan Hi Dmitry, Thank you for your review。 Please also review my inline reply. 在 2025-03-02 02:14:19,"Dmitry Baryshkov" <dmitry.baryshkov@linaro.org> 写道: >On Sun, Feb 23, 2025 at 07:30:25PM +0800, Andy Yan wrote: >> From: Andy Yan <andy.yan@rock-chips.com> >> >> The DW DP TX Controller is compliant with the DisplayPort Specification >> Version 1.4 with the following features: >> >> * DisplayPort 1.4a >> * Main Link: 1/2/4 lanes >> * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps >> * AUX channel 1Mbps >> * Single Stream Transport(SST) >> * Multistream Transport (MST) >> *Type-C support (alternate mode) >> * HDCP 2.2, HDCP 1.3 >> * Supports up to 8/10 bits per color component >> * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0 >> * Pixel clock up to 594MHz >> * I2S, SPDIF audio interface >> >> Add library with common helpers to make it can be shared with >> other SoC. >> >> Signed-off-by: Andy Yan <andy.yan@rock-chips.com> >> >> drm/bridge: cleanup > >Stray line? Sorry, will be removed. > >> >> --- >> >> drivers/gpu/drm/bridge/synopsys/Kconfig | 7 + >> drivers/gpu/drm/bridge/synopsys/Makefile | 1 + >> drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2155 ++++++++++++++++++++++ >> include/drm/bridge/dw_dp.h | 19 + >> 4 files changed, 2182 insertions(+) >> create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-dp.c >> create mode 100644 include/drm/bridge/dw_dp.h >> >> diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig >> index f3ab2f985f8c..2c5e532410de 100644 >> --- a/drivers/gpu/drm/bridge/synopsys/Kconfig >> +++ b/drivers/gpu/drm/bridge/synopsys/Kconfig >> @@ -1,4 +1,11 @@ >> # SPDX-License-Identifier: GPL-2.0-only >> +config DRM_DW_DP >> + tristate >> + select DRM_DISPLAY_HELPER >> + select DRM_DISPLAY_DP_HELPER >> + select DRM_KMS_HELPER >> + select REGMAP_MMIO >> + >> config DRM_DW_HDMI >> tristate >> select DRM_DISPLAY_HDMI_HELPER >> diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile >> index 9dc376d220ad..4dada44029ac 100644 >> --- a/drivers/gpu/drm/bridge/synopsys/Makefile >> +++ b/drivers/gpu/drm/bridge/synopsys/Makefile >> @@ -1,4 +1,5 @@ >> # SPDX-License-Identifier: GPL-2.0-only >> +obj-$(CONFIG_DRM_DW_DP) += dw-dp.o >> obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o >> obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o >> obj-$(CONFIG_DRM_DW_HDMI_GP_AUDIO) += dw-hdmi-gp-audio.o >> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c >> new file mode 100644 >> index 000000000000..6ecbe9851369 >> --- /dev/null >> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c >> @@ -0,0 +1,2155 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> +/* >> + * Synopsys DesignWare Cores DisplayPort Transmitter Controller >> + * >> + * Copyright (c) 2024 Rockchip Electronics Co., Ltd. >> + * >> + * Author: Andy Yan <andy.yan@rock-chips.com> >> + */ >> +#include <linux/bitfield.h> >> +#include <linux/clk.h> >> +#include <linux/component.h> >> +#include <linux/iopoll.h> >> +#include <linux/irq.h> >> +#include <linux/of_device.h> >> +#include <linux/of_graph.h> >> +#include <linux/platform_device.h> >> +#include <linux/regmap.h> >> +#include <linux/reset.h> >> +#include <linux/phy/phy.h> >> +#include <linux/unaligned.h> >> + >> +#include <drm/bridge/dw_dp.h> >> +#include <drm/drm_atomic_helper.h> >> +#include <drm/drm_bridge.h> >> +#include <drm/drm_bridge_connector.h> >> +#include <drm/display/drm_dp_helper.h> >> +#include <drm/display/drm_hdmi_helper.h> >> +#include <drm/drm_edid.h> >> +#include <drm/drm_of.h> >> +#include <drm/drm_print.h> >> +#include <drm/drm_probe_helper.h> >> +#include <drm/drm_simple_kms_helper.h> >> + >> +#include <uapi/linux/media-bus-format.h> >> + >> +#define DW_DP_VERSION_NUMBER 0x0000 >> +#define DW_DP_VERSION_TYPE 0x0004 >> +#define DW_DP_ID 0x0008 >> + >> +#define DW_DP_CONFIG_REG1 0x0100 >> +#define DW_DP_CONFIG_REG2 0x0104 >> +#define DW_DP_CONFIG_REG3 0x0108 >> + >> +#define DW_DP_CCTL 0x0200 >> +#define FORCE_HPD BIT(4) >> +#define DEFAULT_FAST_LINK_TRAIN_EN BIT(2) >> +#define ENHANCE_FRAMING_EN BIT(1) >> +#define SCRAMBLE_DIS BIT(0) >> +#define DW_DP_SOFT_RESET_CTRL 0x0204 >> +#define VIDEO_RESET BIT(5) >> +#define AUX_RESET BIT(4) >> +#define AUDIO_SAMPLER_RESET BIT(3) >> +#define HDCP_MODULE_RESET BIT(2) >> +#define PHY_SOFT_RESET BIT(1) >> +#define CONTROLLER_RESET BIT(0) >> + >> +#define DW_DP_VSAMPLE_CTRL 0x0300 >> +#define PIXEL_MODE_SELECT GENMASK(22, 21) >> +#define VIDEO_MAPPING GENMASK(20, 16) >> +#define VIDEO_STREAM_ENABLE BIT(5) >> + >> +#define DW_DP_VSAMPLE_STUFF_CTRL1 0x0304 >> + >> +#define DW_DP_VSAMPLE_STUFF_CTRL2 0x0308 >> + >> +#define DW_DP_VINPUT_POLARITY_CTRL 0x030c >> +#define DE_IN_POLARITY BIT(2) >> +#define HSYNC_IN_POLARITY BIT(1) >> +#define VSYNC_IN_POLARITY BIT(0) >> + >> +#define DW_DP_VIDEO_CONFIG1 0x0310 >> +#define HACTIVE GENMASK(31, 16) >> +#define HBLANK GENMASK(15, 2) >> +#define I_P BIT(1) >> +#define R_V_BLANK_IN_OSC BIT(0) >> + >> +#define DW_DP_VIDEO_CONFIG2 0x0314 >> +#define VBLANK GENMASK(31, 16) >> +#define VACTIVE GENMASK(15, 0) >> + >> +#define DW_DP_VIDEO_CONFIG3 0x0318 >> +#define H_SYNC_WIDTH GENMASK(31, 16) >> +#define H_FRONT_PORCH GENMASK(15, 0) >> + >> +#define DW_DP_VIDEO_CONFIG4 0x031c >> +#define V_SYNC_WIDTH GENMASK(31, 16) >> +#define V_FRONT_PORCH GENMASK(15, 0) >> + >> +#define DW_DP_VIDEO_CONFIG5 0x0320 >> +#define INIT_THRESHOLD_HI GENMASK(22, 21) >> +#define AVERAGE_BYTES_PER_TU_FRAC GENMASK(19, 16) >> +#define INIT_THRESHOLD GENMASK(13, 7) >> +#define AVERAGE_BYTES_PER_TU GENMASK(6, 0) >> + >> +#define DW_DP_VIDEO_MSA1 0x0324 >> +#define VSTART GENMASK(31, 16) >> +#define HSTART GENMASK(15, 0) >> + >> +#define DW_DP_VIDEO_MSA2 0x0328 >> +#define MISC0 GENMASK(31, 24) >> + >> +#define DW_DP_VIDEO_MSA3 0x032c >> +#define MISC1 GENMASK(31, 24) >> + >> +#define DW_DP_VIDEO_HBLANK_INTERVAL 0x0330 >> +#define HBLANK_INTERVAL_EN BIT(16) >> +#define HBLANK_INTERVAL GENMASK(15, 0) >> + >> +#define DW_DP_AUD_CONFIG1 0x0400 >> +#define AUDIO_TIMESTAMP_VERSION_NUM GENMASK(29, 24) >> +#define AUDIO_PACKET_ID GENMASK(23, 16) >> +#define AUDIO_MUTE BIT(15) >> +#define NUM_CHANNELS GENMASK(14, 12) >> +#define HBR_MODE_ENABLE BIT(10) >> +#define AUDIO_DATA_WIDTH GENMASK(9, 5) >> +#define AUDIO_DATA_IN_EN GENMASK(4, 1) >> +#define AUDIO_INF_SELECT BIT(0) >> + >> +#define DW_DP_SDP_VERTICAL_CTRL 0x0500 >> +#define EN_VERTICAL_SDP BIT(2) >> +#define EN_AUDIO_STREAM_SDP BIT(1) >> +#define EN_AUDIO_TIMESTAMP_SDP BIT(0) >> +#define DW_DP_SDP_HORIZONTAL_CTRL 0x0504 >> +#define EN_HORIZONTAL_SDP BIT(2) >> +#define DW_DP_SDP_STATUS_REGISTER 0x0508 >> +#define DW_DP_SDP_MANUAL_CTRL 0x050c >> +#define DW_DP_SDP_STATUS_EN 0x0510 >> + >> +#define DW_DP_SDP_REGISTER_BANK 0x0600 >> +#define SDP_REGS GENMASK(31, 0) >> + >> +#define DW_DP_PHYIF_CTRL 0x0a00 >> +#define PHY_WIDTH BIT(25) >> +#define PHY_POWERDOWN GENMASK(20, 17) >> +#define PHY_BUSY GENMASK(15, 12) >> +#define SSC_DIS BIT(16) >> +#define XMIT_ENABLE GENMASK(11, 8) >> +#define PHY_LANES GENMASK(7, 6) >> +#define PHY_RATE GENMASK(5, 4) >> +#define TPS_SEL GENMASK(3, 0) >> + >> +#define DW_DP_PHY_TX_EQ 0x0a04 >> +#define DW_DP_CUSTOMPAT0 0x0a08 >> +#define DW_DP_CUSTOMPAT1 0x0a0c >> +#define DW_DP_CUSTOMPAT2 0x0a10 >> +#define DW_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET 0x0a14 >> +#define DW_DP_PHYIF_PWRDOWN_CTRL 0x0a18 >> + >> +#define DW_DP_AUX_CMD 0x0b00 >> +#define AUX_CMD_TYPE GENMASK(31, 28) >> +#define AUX_ADDR GENMASK(27, 8) >> +#define I2C_ADDR_ONLY BIT(4) >> +#define AUX_LEN_REQ GENMASK(3, 0) >> + >> +#define DW_DP_AUX_STATUS 0x0b04 >> +#define AUX_TIMEOUT BIT(17) >> +#define AUX_BYTES_READ GENMASK(23, 19) >> +#define AUX_STATUS GENMASK(7, 4) >> + >> +#define DW_DP_AUX_DATA0 0x0b08 >> +#define DW_DP_AUX_DATA1 0x0b0c >> +#define DW_DP_AUX_DATA2 0x0b10 >> +#define DW_DP_AUX_DATA3 0x0b14 >> + >> +#define DW_DP_GENERAL_INTERRUPT 0x0d00 >> +#define VIDEO_FIFO_OVERFLOW_STREAM0 BIT(6) >> +#define AUDIO_FIFO_OVERFLOW_STREAM0 BIT(5) >> +#define SDP_EVENT_STREAM0 BIT(4) >> +#define AUX_CMD_INVALID BIT(3) >> +#define HDCP_EVENT BIT(2) >> +#define AUX_REPLY_EVENT BIT(1) >> +#define HPD_EVENT BIT(0) >> + >> +#define DW_DP_GENERAL_INTERRUPT_ENABLE 0x0d04 >> +#define HDCP_EVENT_EN BIT(2) >> +#define AUX_REPLY_EVENT_EN BIT(1) >> +#define HPD_EVENT_EN BIT(0) >> + >> +#define DW_DP_HPD_STATUS 0x0d08 >> +#define HPD_STATE GENMASK(11, 9) >> +#define HPD_STATUS BIT(8) >> +#define HPD_HOT_UNPLUG BIT(2) >> +#define HPD_HOT_PLUG BIT(1) >> +#define HPD_IRQ BIT(0) >> + >> +#define DW_DP_HPD_INTERRUPT_ENABLE 0x0d0c >> +#define HPD_UNPLUG_ERR_EN BIT(3) >> +#define HPD_UNPLUG_EN BIT(2) >> +#define HPD_PLUG_EN BIT(1) >> +#define HPD_IRQ_EN BIT(0) >> + >> +#define DW_DP_HDCP_CFG 0x0e00 >> +#define DPCD12PLUS BIT(7) >> +#define CP_IRQ BIT(6) >> +#define BYPENCRYPTION BIT(5) >> +#define HDCP_LOCK BIT(4) >> +#define ENCRYPTIONDISABLE BIT(3) >> +#define ENABLE_HDCP_13 BIT(2) >> +#define ENABLE_HDCP BIT(1) >> + >> +#define DW_DP_HDCP_OBS 0x0e04 >> +#define HDCP22_RE_AUTHENTICATION_REQ BIT(31) >> +#define HDCP22_AUTHENTICATION_FAILED BIT(30) >> +#define HDCP22_AUTHENTICATION_SUCCESS BIT(29) >> +#define HDCP22_CAPABLE_SINK BIT(28) >> +#define HDCP22_SINK_CAP_CHECK_COMPLETE BIT(27) >> +#define HDCP22_STATE GENMASK(26, 24) >> +#define HDCP22_BOOTED BIT(23) >> +#define HDCP13_BSTATUS GENMASK(22, 19) >> +#define REPEATER BIT(18) >> +#define HDCP_CAPABLE BIT(17) >> +#define STATEE GENMASK(16, 14) >> +#define STATEOEG GENMASK(13, 11) >> +#define STATER GENMASK(10, 8) >> +#define STATEA GENMASK(7, 4) >> +#define SUBSTATEA GENMASK(3, 1) >> +#define HDCPENGAGED BIT(0) >> + >> +#define DW_DP_HDCP_APIINTCLR 0x0e08 >> +#define DW_DP_HDCP_APIINTSTAT 0x0e0c >> +#define DW_DP_HDCP_APIINTMSK 0x0e10 >> +#define HDCP22_GPIOINT BIT(8) >> +#define HDCP_ENGAGED BIT(7) >> +#define HDCP_FAILED BIT(6) >> +#define KSVSHA1CALCDONEINT BIT(5) >> +#define AUXRESPNACK7TIMES BIT(4) >> +#define AUXRESPTIMEOUT BIT(3) >> +#define AUXRESPDEFER7TIMES BIT(2) >> +#define KSVACCESSINT BIT(0) >> + >> +#define DW_DP_HDCP_KSVMEMCTRL 0x0e18 >> +#define KSVSHA1STATUS BIT(4) >> +#define KSVMEMACCESS BIT(1) >> +#define KSVMEMREQUEST BIT(0) >> + >> +#define DW_DP_HDCP_REG_BKSV0 0x3600 >> +#define DW_DP_HDCP_REG_BKSV1 0x3604 >> +#define DW_DP_HDCP_REG_ANCONF 0x3608 >> +#define AN_BYPASS BIT(0) >> + >> +#define DW_DP_HDCP_REG_AN0 0x360c >> +#define DW_DP_HDCP_REG_AN1 0x3610 >> +#define DW_DP_HDCP_REG_RMLCTL 0x3614 >> +#define ODPK_DECRYPT_ENABLE BIT(0) >> + >> +#define DW_DP_HDCP_REG_RMLSTS 0x3618 >> +#define IDPK_WR_OK_STS BIT(6) >> +#define IDPK_DATA_INDEX GENMASK(5, 0) >> +#define DW_DP_HDCP_REG_SEED 0x361c >> +#define DW_DP_HDCP_REG_DPK0 0x3620 >> +#define DW_DP_HDCP_REG_DPK1 0x3624 >> +#define DW_DP_HDCP22_GPIOSTS 0x3628 >> +#define DW_DP_HDCP22_GPIOCHNGSTS 0x362c >> +#define DW_DP_HDCP_REG_DPK_CRC 0x3630 >> + >> +#define DW_DP_MAX_REGISTER DW_DP_HDCP_REG_DPK_CRC >> + >> +#define SDP_REG_BANK_SIZE 16 >> + >> +struct dw_dp_link_caps { >> + bool enhanced_framing; >> + bool tps3_supported; >> + bool tps4_supported; >> + bool fast_training; >> + bool channel_coding; >> + bool ssc; >> +}; >> + >> +struct dw_dp_link_train_set { >> + unsigned int voltage_swing[4]; >> + unsigned int pre_emphasis[4]; >> + bool voltage_max_reached[4]; >> + bool pre_max_reached[4]; >> +}; >> + >> +struct dw_dp_link_train { >> + struct dw_dp_link_train_set request; >> + struct dw_dp_link_train_set adjust; >> + bool clock_recovered; >> + bool channel_equalized; >> +}; >> + >> +struct dw_dp_link { >> + u8 dpcd[DP_RECEIVER_CAP_SIZE]; >> + unsigned char revision; >> + unsigned int rate; >> + unsigned int lanes; >> + u8 sink_count; >> + u8 vsc_sdp_supported; >> + struct dw_dp_link_caps caps; >> + struct dw_dp_link_train train; >> + struct drm_dp_desc desc; >> +}; >> + >> +struct dw_dp_video { >> + struct drm_display_mode mode; >> + u8 video_mapping; >> + u8 pixel_mode; >> + u8 color_format; >> + u8 bpc; >> + u8 bpp; >> +}; >> + >> +struct dw_dp_sdp { >> + struct dp_sdp_header header; >> + u8 db[32]; > >struct dp_sdp, please Ok, will do. > >> + unsigned long flags; >> +}; >> + >> +struct dw_dp_hotplug { >> + bool long_hpd; >> +}; >> + >> +struct dw_dp { >> + struct drm_bridge bridge; >> + struct device *dev; >> + struct regmap *regmap; >> + struct phy *phy; >> + struct clk *apb_clk; >> + struct clk *aux_clk; >> + struct clk *i2s_clk; >> + struct clk *spdif_clk; >> + struct clk *hdcp_clk; >> + struct reset_control *rstc; >> + struct completion complete; >> + int irq; >> + struct work_struct hpd_work; >> + struct dw_dp_hotplug hotplug; >> + struct mutex irq_lock; >> + >> + struct drm_dp_aux aux; >> + >> + struct dw_dp_link link; >> + struct dw_dp_video video; >> + const struct dw_dp_plat_data *plat_data; >> + >> + DECLARE_BITMAP(sdp_reg_bank, SDP_REG_BANK_SIZE); >> +}; >> + >> +enum { >> + DW_DP_RGB_6BIT, >> + DW_DP_RGB_8BIT, >> + DW_DP_RGB_10BIT, >> + DW_DP_RGB_12BIT, >> + DW_DP_RGB_16BIT, >> + DW_DP_YCBCR444_8BIT, >> + DW_DP_YCBCR444_10BIT, >> + DW_DP_YCBCR444_12BIT, >> + DW_DP_YCBCR444_16BIT, >> + DW_DP_YCBCR422_8BIT, >> + DW_DP_YCBCR422_10BIT, >> + DW_DP_YCBCR422_12BIT, >> + DW_DP_YCBCR422_16BIT, >> + DW_DP_YCBCR420_8BIT, >> + DW_DP_YCBCR420_10BIT, >> + DW_DP_YCBCR420_12BIT, >> + DW_DP_YCBCR420_16BIT, >> +}; >> + >> +enum { >> + DW_DP_MP_SINGLE_PIXEL, >> + DW_DP_MP_DUAL_PIXEL, >> + DW_DP_MP_QUAD_PIXEL, >> +}; >> + >> +enum { >> + DW_DP_SDP_VERTICAL_INTERVAL = BIT(0), >> + DW_DP_SDP_HORIZONTAL_INTERVAL = BIT(1), >> +}; >> + >> +enum { >> + DW_DP_HPD_STATE_IDLE, >> + DW_DP_HPD_STATE_UNPLUG, >> + DP_DP_HPD_STATE_TIMEOUT = 4, >> + DW_DP_HPD_STATE_PLUG = 7 >> +}; >> + >> +enum { >> + DW_DP_PHY_PATTERN_NONE, >> + DW_DP_PHY_PATTERN_TPS_1, >> + DW_DP_PHY_PATTERN_TPS_2, >> + DW_DP_PHY_PATTERN_TPS_3, >> + DW_DP_PHY_PATTERN_TPS_4, >> + DW_DP_PHY_PATTERN_SERM, >> + DW_DP_PHY_PATTERN_PBRS7, >> + DW_DP_PHY_PATTERN_CUSTOM_80BIT, >> + DW_DP_PHY_PATTERN_CP2520_1, >> + DW_DP_PHY_PATTERN_CP2520_2, >> +}; >> + >> +struct dw_dp_output_format { >> + u32 bus_format; >> + u32 color_format; >> + u8 video_mapping; >> + u8 bpc; >> + u8 bpp; >> +}; >> + >> +static const struct dw_dp_output_format dw_dp_output_formats[] = { >> + { MEDIA_BUS_FMT_RGB101010_1X30, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_10BIT, 10, 30 }, >> + { MEDIA_BUS_FMT_RGB888_1X24, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_8BIT, 8, 24 }, >> + { MEDIA_BUS_FMT_YUV10_1X30, DRM_COLOR_FORMAT_YCBCR444, DW_DP_YCBCR444_10BIT, 10, 30 }, >> + { MEDIA_BUS_FMT_YUV8_1X24, DRM_COLOR_FORMAT_YCBCR444, DW_DP_YCBCR444_8BIT, 8, 24}, >> + { MEDIA_BUS_FMT_YUYV10_1X20, DRM_COLOR_FORMAT_YCBCR422, DW_DP_YCBCR422_10BIT, 10, 20 }, >> + { MEDIA_BUS_FMT_YUYV8_1X16, DRM_COLOR_FORMAT_YCBCR422, DW_DP_YCBCR422_8BIT, 8, 16 }, >> + { MEDIA_BUS_FMT_UYYVYY10_0_5X30, DRM_COLOR_FORMAT_YCBCR420, DW_DP_YCBCR420_10BIT, 10, 15 }, >> + { MEDIA_BUS_FMT_UYYVYY8_0_5X24, DRM_COLOR_FORMAT_YCBCR420, DW_DP_YCBCR420_8BIT, 8, 12 }, >> + { MEDIA_BUS_FMT_RGB666_1X24_CPADHI, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_6BIT, 6, 18 }, >> +}; >> + >> +static const struct dw_dp_output_format *dw_dp_get_output_format(u32 bus_format) >> +{ >> + unsigned int i; >> + >> + for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) >> + if (dw_dp_output_formats[i].bus_format == bus_format) >> + return &dw_dp_output_formats[i]; >> + >> + return &dw_dp_output_formats[1]; > >Why? Shouldn't it be NULL? At first, we considered RGB888_1X24 to be universally applicable across all scenarios, but upon re-evaluation, this assumption proved inaccurate. Specifically, RGB888 may encounter bandwidth limitations in high-resolution or high-refresh-rate environments So returning NULL would be more appropriate here. > >> +} >> + >> +static inline struct dw_dp *bridge_to_dp(struct drm_bridge *b) >> +{ >> + return container_of(b, struct dw_dp, bridge); >> +} >> + >> +static inline void dw_dp_phy_set_pattern(struct dw_dp *dp, u32 pattern) >> +{ >> + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, TPS_SEL, >> + FIELD_PREP(TPS_SEL, pattern)); >> +} >> + >> +static void dw_dp_phy_xmit_enable(struct dw_dp *dp, u32 lanes) >> +{ >> + u32 xmit_enable; >> + >> + switch (lanes) { >> + case 4: >> + case 2: >> + case 1: >> + xmit_enable = GENMASK(lanes - 1, 0); >> + break; >> + case 0: >> + default: >> + xmit_enable = 0; >> + break; >> + } >> + >> + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, XMIT_ENABLE, >> + FIELD_PREP(XMIT_ENABLE, xmit_enable)); >> +} >> + >> +static bool dw_dp_bandwidth_ok(struct dw_dp *dp, >> + const struct drm_display_mode *mode, u32 bpp, >> + unsigned int lanes, unsigned int rate) >> +{ >> + u32 max_bw, req_bw; >> + >> + req_bw = mode->clock * bpp / 8; >> + max_bw = lanes * rate; >> + if (req_bw > max_bw) >> + return false; >> + >> + return true; >> +} >> + >> +static bool dw_dp_hpd_detect(struct dw_dp *dp) >> +{ >> + u32 value; >> + >> + regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value); >> + >> + return FIELD_GET(HPD_STATE, value) == DW_DP_HPD_STATE_PLUG; >> +} >> + >> +static void dw_dp_link_caps_reset(struct dw_dp_link_caps *caps) >> +{ >> + caps->enhanced_framing = false; >> + caps->tps3_supported = false; >> + caps->tps4_supported = false; >> + caps->fast_training = false; >> + caps->channel_coding = false; >> +} >> + >> +static void dw_dp_link_reset(struct dw_dp_link *link) >> +{ >> + link->vsc_sdp_supported = 0; >> + link->sink_count = 0; >> + link->revision = 0; >> + link->rate = 0; >> + link->lanes = 0; >> + >> + dw_dp_link_caps_reset(&link->caps); >> + memset(link->dpcd, 0, sizeof(link->dpcd)); >> +} >> + >> +static int dw_dp_link_power_up(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + int ret; >> + u8 value; >> + >> + if (link->revision < DP_DPCD_REV_11) >> + return 0; >> + >> + ret = drm_dp_dpcd_readb(&dp->aux, DP_SET_POWER, &value); >> + if (ret < 0) >> + return ret; >> + >> + value &= ~DP_SET_POWER_MASK; >> + value |= DP_SET_POWER_D0; >> + >> + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, value); >> + if (ret < 0) >> + return ret; >> + >> + usleep_range(1000, 2000); >> + >> + return 0; >> +} >> + >> +static int dw_dp_link_power_down(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + int ret; >> + u8 value; >> + >> + if (link->revision < DP_DPCD_REV_11) >> + return 0; >> + >> + ret = drm_dp_dpcd_readb(&dp->aux, DP_SET_POWER, &value); >> + if (ret < 0) >> + return ret; >> + >> + value &= ~DP_SET_POWER_MASK; >> + value |= DP_SET_POWER_D3; >> + >> + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, value); >> + if (ret < 0) >> + return ret; >> + > >Would you please mind pulling these two functions to DRM DP helpers? >There are enough users to make this into a common code. Yes, when developing this patch, I had the same realization. The processing logic for MSM/Cadence/ANX6345/tegra follows a similar approach. I had originally planned to defer this refactoring until the series of patches is accepted. Additionally, I have other pending tasks in my TODO list, such as fully decoupling the bridge driver and encoder/connector components across all Rockchip platforms。 But I prioritize completing these foundational features before go to next steps, Is this acceptable ? > >> + return 0; >> +} >> + >> +static bool dw_dp_has_sink_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE], >> + const struct drm_dp_desc *desc) >> +{ >> + return 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); > > >isn't it drm_dp_read_sink_count_cap() ? I will have a try, since this drm_dp_read_sink_count_cap requires a connector parameter, I need to figure out how to get the connector here. > >> +} >> + >> +static int dw_dp_link_parse(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + u8 dpcd; >> + int ret; >> + >> + dw_dp_link_reset(link); >> + >> + ret = drm_dp_read_dpcd_caps(&dp->aux, link->dpcd); >> + if (ret < 0) >> + return ret; >> + >> + drm_dp_read_desc(&dp->aux, &link->desc, drm_dp_is_branch(link->dpcd)); >> + >> + if (dw_dp_has_sink_count(link->dpcd, &link->desc)) { >> + ret = drm_dp_read_sink_count(&dp->aux); >> + if (ret < 0) >> + return ret; >> + >> + link->sink_count = ret; >> + >> + /* Dongle connected, but no display */ >> + if (!link->sink_count) >> + return -ENODEV; >> + } >> + >> + ret = drm_dp_dpcd_readb(&dp->aux, DP_DPRX_FEATURE_ENUMERATION_LIST, &dpcd); >> + if (ret < 0) >> + return ret; >> + >> + link->vsc_sdp_supported = !!(dpcd & DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED); >> + >> + link->revision = link->dpcd[DP_DPCD_REV]; >> + link->rate = min_t(u32, min(dp->plat_data->max_link_rate, >> + dp->phy->attrs.max_link_rate * 100), >> + drm_dp_max_link_rate(link->dpcd)); >> + link->lanes = min_t(u8, phy_get_bus_width(dp->phy), >> + drm_dp_max_lane_count(link->dpcd)); >> + >> + link->caps.enhanced_framing = drm_dp_enhanced_frame_cap(link->dpcd); >> + link->caps.tps3_supported = drm_dp_tps3_supported(link->dpcd); >> + link->caps.tps4_supported = drm_dp_tps4_supported(link->dpcd); >> + link->caps.fast_training = drm_dp_fast_training_cap(link->dpcd); >> + link->caps.channel_coding = drm_dp_channel_coding_supported(link->dpcd); >> + link->caps.ssc = !!(link->dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5); >> + >> + return 0; >> +} >> + >> +static int dw_dp_phy_update_vs_emph(struct dw_dp *dp, unsigned int rate, unsigned int lanes, >> + struct dw_dp_link_train_set *train_set) >> +{ >> + union phy_configure_opts phy_cfg; >> + unsigned int *vs, *pe; >> + u8 buf[4]; >> + int i, ret; >> + >> + vs = train_set->voltage_swing; >> + pe = train_set->pre_emphasis; >> + >> + for (i = 0; i < lanes; i++) { >> + phy_cfg.dp.voltage[i] = vs[i]; >> + phy_cfg.dp.pre[i] = pe[i]; >> + } >> + >> + phy_cfg.dp.lanes = lanes; >> + phy_cfg.dp.link_rate = rate / 100; >> + phy_cfg.dp.set_lanes = false; >> + phy_cfg.dp.set_rate = false; > >You don't need to set lanes / link_rate if set_lanes and set_rate is >false. This is because the rk_udphy_dp_phy_configure function first verifies the rate and lane settings, treating any uninitialized parameters as invalid configurations > >> + phy_cfg.dp.set_voltages = true; >> + >> + ret = phy_configure(dp->phy, &phy_cfg); >> + if (ret) >> + return ret; >> + >> + for (i = 0; i < lanes; i++) { >> + buf[i] = (vs[i] << DP_TRAIN_VOLTAGE_SWING_SHIFT) | >> + (pe[i] << DP_TRAIN_PRE_EMPHASIS_SHIFT); >> + if (train_set->voltage_max_reached[i]) >> + buf[i] |= DP_TRAIN_MAX_SWING_REACHED; >> + if (train_set->pre_max_reached[i]) >> + buf[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; >> + } >> + >> + ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, buf, lanes); >> + if (ret < 0) >> + return ret; >> + >> + return 0; >> +} >> + >> +static int dw_dp_link_train_update_vs_emph(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + struct dw_dp_link_train_set *request = &link->train.request; >> + >> + return dw_dp_phy_update_vs_emph(dp, dp->link.rate, dp->link.lanes, request); > >Inline it mayeb? Okay, will do。 > >> +} >> + >> +static int dw_dp_phy_configure(struct dw_dp *dp, unsigned int rate, >> + unsigned int lanes, bool ssc) >> +{ >> + union phy_configure_opts phy_cfg; >> + int ret; >> + >> + /* Move PHY to P3 */ >> + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_POWERDOWN, >> + FIELD_PREP(PHY_POWERDOWN, 0x3)); >> + >> + phy_cfg.dp.lanes = lanes; >> + phy_cfg.dp.link_rate = rate / 100; >> + phy_cfg.dp.ssc = ssc; >> + phy_cfg.dp.set_lanes = true; >> + phy_cfg.dp.set_rate = true; >> + phy_cfg.dp.set_voltages = false; >> + ret = phy_configure(dp->phy, &phy_cfg); >> + if (ret) >> + return ret; >> + >> + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_LANES, >> + FIELD_PREP(PHY_LANES, lanes / 2)); >> + >> + /* Move PHY to P0 */ >> + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_POWERDOWN, >> + FIELD_PREP(PHY_POWERDOWN, 0x0)); >> + >> + dw_dp_phy_xmit_enable(dp, lanes); >> + >> + return 0; >> +} >> + >> +static int dw_dp_link_configure(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + u8 buf[2]; >> + int ret; >> + >> + ret = dw_dp_phy_configure(dp, link->rate, link->lanes, link->caps.ssc); >> + if (ret) >> + return ret; >> + >> + buf[0] = drm_dp_link_rate_to_bw_code(link->rate); >> + buf[1] = link->lanes; >> + >> + if (link->caps.enhanced_framing) { >> + buf[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; >> + regmap_update_bits(dp->regmap, DW_DP_CCTL, ENHANCE_FRAMING_EN, >> + FIELD_PREP(ENHANCE_FRAMING_EN, 1)); >> + } else { >> + regmap_update_bits(dp->regmap, DW_DP_CCTL, ENHANCE_FRAMING_EN, >> + FIELD_PREP(ENHANCE_FRAMING_EN, 0)); >> + } >> + >> + ret = drm_dp_dpcd_write(&dp->aux, DP_LINK_BW_SET, buf, sizeof(buf)); >> + if (ret < 0) >> + return ret; >> + >> + buf[0] = link->caps.ssc ? DP_SPREAD_AMP_0_5 : 0; >> + buf[1] = link->caps.channel_coding ? DP_SET_ANSI_8B10B : 0; >> + >> + ret = drm_dp_dpcd_write(&dp->aux, DP_DOWNSPREAD_CTRL, buf, sizeof(buf)); >> + if (ret < 0) >> + return ret; >> + >> + return 0; >> +} >> + >> +static void dw_dp_link_train_init(struct dw_dp_link_train *train) >> +{ >> + struct dw_dp_link_train_set *request = &train->request; >> + struct dw_dp_link_train_set *adjust = &train->adjust; >> + unsigned int i; >> + >> + for (i = 0; i < 4; i++) { >> + request->voltage_swing[i] = 0; >> + adjust->voltage_swing[i] = 0; >> + >> + request->pre_emphasis[i] = 0; >> + adjust->pre_emphasis[i] = 0; >> + >> + request->voltage_max_reached[i] = false; >> + adjust->voltage_max_reached[i] = false; >> + >> + request->pre_max_reached[i] = false; >> + adjust->pre_max_reached[i] = false; >> + } >> + >> + train->clock_recovered = false; >> + train->channel_equalized = false; >> +} >> + >> +static bool dw_dp_link_train_valid(const struct dw_dp_link_train *train) >> +{ >> + return train->clock_recovered && train->channel_equalized; >> +} >> + >> +static int dw_dp_link_train_set_pattern(struct dw_dp *dp, u32 pattern) >> +{ >> + u8 buf = 0; >> + int ret; >> + >> + if (pattern && pattern != DP_TRAINING_PATTERN_4) { >> + buf |= DP_LINK_SCRAMBLING_DISABLE; >> + >> + regmap_update_bits(dp->regmap, DW_DP_CCTL, SCRAMBLE_DIS, >> + FIELD_PREP(SCRAMBLE_DIS, 1)); >> + } else { >> + regmap_update_bits(dp->regmap, DW_DP_CCTL, SCRAMBLE_DIS, >> + FIELD_PREP(SCRAMBLE_DIS, 0)); >> + } >> + >> + switch (pattern) { >> + case DP_TRAINING_PATTERN_DISABLE: >> + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_NONE); >> + break; >> + case DP_TRAINING_PATTERN_1: >> + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_1); >> + break; >> + case DP_TRAINING_PATTERN_2: >> + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_2); >> + break; >> + case DP_TRAINING_PATTERN_3: >> + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_3); >> + break; >> + case DP_TRAINING_PATTERN_4: >> + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_4); >> + break; >> + default: >> + return -EINVAL; >> + } >> + >> + ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, >> + buf | pattern); >> + if (ret < 0) >> + return ret; >> + >> + return 0; >> +} >> + >> +static u8 dw_dp_voltage_max(u8 preemph) >> +{ >> + switch (preemph & DP_TRAIN_PRE_EMPHASIS_MASK) { >> + case DP_TRAIN_PRE_EMPH_LEVEL_0: >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_3; >> + case DP_TRAIN_PRE_EMPH_LEVEL_1: >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_2; >> + case DP_TRAIN_PRE_EMPH_LEVEL_2: >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_1; >> + case DP_TRAIN_PRE_EMPH_LEVEL_3: >> + default: >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_0; >> + } >> +} >> + >> +static void dw_dp_link_get_adjustments(struct dw_dp_link *link, >> + u8 status[DP_LINK_STATUS_SIZE]) >> +{ >> + struct dw_dp_link_train_set *adjust = &link->train.adjust; >> + u8 v = 0; >> + u8 p = 0; >> + unsigned int i; >> + >> + for (i = 0; i < link->lanes; i++) { >> + v = drm_dp_get_adjust_request_voltage(status, i); >> + p = drm_dp_get_adjust_request_pre_emphasis(status, i); >> + if (p >= DP_TRAIN_PRE_EMPH_LEVEL_3) { >> + adjust->pre_emphasis[i] = DP_TRAIN_PRE_EMPH_LEVEL_3 >> >> + DP_TRAIN_PRE_EMPHASIS_SHIFT; >> + adjust->pre_max_reached[i] = true; >> + } else { >> + adjust->pre_emphasis[i] = p >> DP_TRAIN_PRE_EMPHASIS_SHIFT; >> + adjust->pre_max_reached[i] = false; >> + } >> + v = min(v, dw_dp_voltage_max(p)); >> + if (v >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3) { >> + adjust->voltage_swing[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_3 >> >> + DP_TRAIN_VOLTAGE_SWING_SHIFT; >> + adjust->voltage_max_reached[i] = true; >> + } else { >> + adjust->voltage_swing[i] = v >> DP_TRAIN_VOLTAGE_SWING_SHIFT; >> + adjust->voltage_max_reached[i] = false; >> + } >> + } >> +} >> + >> +static void dw_dp_link_train_adjust(struct dw_dp_link_train *train) >> +{ >> + struct dw_dp_link_train_set *request = &train->request; >> + struct dw_dp_link_train_set *adjust = &train->adjust; >> + unsigned int i; >> + >> + for (i = 0; i < 4; i++) { > >Shouldn't it be a loop up to link->lanes? Yeah, this makes sense。 > >> + if (request->voltage_swing[i] != adjust->voltage_swing[i]) >> + request->voltage_swing[i] = adjust->voltage_swing[i]; >> + if (request->voltage_max_reached[i] != adjust->voltage_max_reached[i]) >> + request->voltage_max_reached[i] = adjust->voltage_max_reached[i]; >> + } >> + >> + for (i = 0; i < 4; i++) { >> + if (request->pre_emphasis[i] != adjust->pre_emphasis[i]) >> + request->pre_emphasis[i] = adjust->pre_emphasis[i]; >> + if (request->pre_max_reached[i] != adjust->pre_max_reached[i]) >> + request->pre_max_reached[i] = adjust->pre_max_reached[i]; > >Why do you need separate request and adjustment structs? I need to think about this issue further and will confirm how to proceed later. > >> + } >> +} >> + >> +static int dw_dp_link_clock_recovery(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + u8 status[DP_LINK_STATUS_SIZE]; >> + unsigned int tries = 0; >> + int ret; >> + >> + ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1); >> + if (ret) >> + return ret; >> + >> + for (;;) { >> + ret = dw_dp_link_train_update_vs_emph(dp); >> + if (ret) >> + return ret; >> + >> + drm_dp_link_train_clock_recovery_delay(&dp->aux, link->dpcd); >> + >> + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); >> + if (ret < 0) { >> + dev_err(dp->dev, "failed to read link status: %d\n", ret); >> + return ret; >> + } >> + >> + if (drm_dp_clock_recovery_ok(status, link->lanes)) { >> + link->train.clock_recovered = true; >> + break; >> + } >> + >> + dw_dp_link_get_adjustments(link, status); >> + >> + if (link->train.request.voltage_swing[0] == >> + link->train.adjust.voltage_swing[0]) > >Should this take all lanes to account? I think it might be posssible to >drop the adjust / request split and adjust tries in >dw_dp_link_get_adjustments() instead. Let me figure it out later。 > >> + tries++; >> + else >> + tries = 0; >> + >> + if (tries == 5) >> + break; >> + >> + dw_dp_link_train_adjust(&link->train); >> + } >> + >> + return 0; >> +} >> + >> +static int dw_dp_link_channel_equalization(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + u8 status[DP_LINK_STATUS_SIZE], pattern; >> + unsigned int tries; >> + int ret; >> + >> + if (link->caps.tps4_supported) >> + pattern = DP_TRAINING_PATTERN_4; >> + else if (link->caps.tps3_supported) >> + pattern = DP_TRAINING_PATTERN_3; >> + else >> + pattern = DP_TRAINING_PATTERN_2; >> + ret = dw_dp_link_train_set_pattern(dp, pattern); >> + if (ret) >> + return ret; >> + >> + for (tries = 1; tries < 5; tries++) { >> + ret = dw_dp_link_train_update_vs_emph(dp); >> + if (ret) >> + return ret; >> + >> + drm_dp_link_train_channel_eq_delay(&dp->aux, link->dpcd); >> + >> + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); >> + if (ret < 0) >> + return ret; >> + >> + if (!drm_dp_clock_recovery_ok(status, link->lanes)) { >> + dev_err(dp->dev, "clock recovery lost while equalizing channel\n"); >> + link->train.clock_recovered = false; >> + break; >> + } >> + >> + if (drm_dp_channel_eq_ok(status, link->lanes)) { >> + link->train.channel_equalized = true; >> + break; >> + } >> + >> + dw_dp_link_get_adjustments(link, status); >> + dw_dp_link_train_adjust(&link->train); >> + } >> + >> + return 0; >> +} >> + >> +static int dw_dp_link_downgrade(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + struct dw_dp_video *video = &dp->video; >> + >> + switch (link->rate) { >> + case 162000: >> + return -EINVAL; >> + case 270000: >> + link->rate = 162000; >> + break; >> + case 540000: >> + link->rate = 270000; >> + break; >> + case 810000: >> + link->rate = 540000; >> + break; >> + } >> + >> + if (!dw_dp_bandwidth_ok(dp, &video->mode, video->bpp, link->lanes, >> + link->rate)) >> + return -E2BIG; >> + >> + return 0; >> +} >> + >> +static int dw_dp_link_train_full(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + int ret; >> + >> +retry: >> + dw_dp_link_train_init(&link->train); >> + >> + dev_dbg(dp->dev, "full-training link: %u lane%s at %u MHz\n", >> + link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100); >> + >> + ret = dw_dp_link_configure(dp); >> + if (ret < 0) { >> + dev_err(dp->dev, "failed to configure DP link: %d\n", ret); >> + return ret; >> + } >> + >> + ret = dw_dp_link_clock_recovery(dp); >> + if (ret < 0) { >> + dev_err(dp->dev, "clock recovery failed: %d\n", ret); >> + goto out; >> + } >> + >> + if (!link->train.clock_recovered) { >> + dev_err(dp->dev, "clock recovery failed, downgrading link\n"); >> + >> + ret = dw_dp_link_downgrade(dp); >> + if (ret < 0) >> + goto out; >> + else >> + goto retry; >> + } >> + >> + dev_dbg(dp->dev, "clock recovery succeeded\n"); >> + >> + ret = dw_dp_link_channel_equalization(dp); >> + if (ret < 0) { >> + dev_err(dp->dev, "channel equalization failed: %d\n", ret); >> + goto out; >> + } >> + >> + if (!link->train.channel_equalized) { >> + dev_err(dp->dev, "channel equalization failed, downgrading link\n"); >> + >> + ret = dw_dp_link_downgrade(dp); >> + if (ret < 0) >> + goto out; >> + else >> + goto retry; >> + } >> + >> + dev_dbg(dp->dev, "channel equalization succeeded\n"); >> + >> +out: >> + dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE); >> + return ret; >> +} >> + >> +static int dw_dp_link_train_fast(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + int ret; >> + u8 status[DP_LINK_STATUS_SIZE]; >> + u8 pattern; >> + >> + dw_dp_link_train_init(&link->train); >> + >> + dev_dbg(dp->dev, "fast-training link: %u lane%s at %u MHz\n", >> + link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100); >> + >> + ret = dw_dp_link_configure(dp); >> + if (ret < 0) { >> + dev_err(dp->dev, "failed to configure DP link: %d\n", ret); >> + return ret; >> + } >> + >> + ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1); >> + if (ret) >> + goto out; >> + >> + usleep_range(500, 1000); >> + >> + if (link->caps.tps4_supported) >> + pattern = DP_TRAINING_PATTERN_4; >> + else if (link->caps.tps3_supported) >> + pattern = DP_TRAINING_PATTERN_3; >> + else >> + pattern = DP_TRAINING_PATTERN_2; >> + ret = dw_dp_link_train_set_pattern(dp, pattern); >> + if (ret) >> + goto out; >> + >> + usleep_range(500, 1000); >> + >> + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); >> + if (ret < 0) { >> + dev_err(dp->dev, "failed to read link status: %d\n", ret); >> + goto out; >> + } >> + >> + if (!drm_dp_clock_recovery_ok(status, link->lanes)) { >> + dev_err(dp->dev, "clock recovery failed\n"); >> + ret = -EIO; >> + goto out; >> + } >> + >> + if (!drm_dp_channel_eq_ok(status, link->lanes)) { >> + dev_err(dp->dev, "channel equalization failed\n"); >> + ret = -EIO; >> + goto out; >> + } >> + >> +out: >> + dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE); >> + return ret; >> +} >> + >> +static int dw_dp_link_train(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + int ret; >> + >> + if (link->caps.fast_training) { >> + if (dw_dp_link_train_valid(&link->train)) { >> + ret = dw_dp_link_train_fast(dp); >> + if (ret < 0) >> + dev_err(dp->dev, "fast link training failed: %d\n", ret); >> + else >> + return 0; >> + } >> + } >> + >> + ret = dw_dp_link_train_full(dp); >> + if (ret < 0) { >> + dev_err(dp->dev, "full link training failed: %d\n", ret); >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static int dw_dp_send_sdp(struct dw_dp *dp, struct dw_dp_sdp *sdp) >> +{ >> + const u8 *payload = sdp->db; >> + u32 reg; >> + int i, nr; >> + >> + nr = find_first_zero_bit(dp->sdp_reg_bank, SDP_REG_BANK_SIZE); >> + if (nr < SDP_REG_BANK_SIZE) >> + set_bit(nr, dp->sdp_reg_bank); >> + else >> + return -EBUSY; >> + >> + reg = DW_DP_SDP_REGISTER_BANK + nr * 9 * 4; >> + >> + /* SDP header */ >> + regmap_write(dp->regmap, reg, get_unaligned_le32(&sdp->header)); >> + >> + /* SDP data payload */ >> + for (i = 1; i < 9; i++, payload += 4) >> + regmap_write(dp->regmap, reg + i * 4, >> + FIELD_PREP(SDP_REGS, get_unaligned_le32(payload))); >> + >> + if (sdp->flags & DW_DP_SDP_VERTICAL_INTERVAL) >> + regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL, >> + EN_VERTICAL_SDP << nr, >> + EN_VERTICAL_SDP << nr); >> + >> + if (sdp->flags & DW_DP_SDP_HORIZONTAL_INTERVAL) >> + regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL, >> + EN_HORIZONTAL_SDP << nr, >> + EN_HORIZONTAL_SDP << nr); >> + >> + return 0; >> +} >> + >> +static void dw_dp_vsc_sdp_pack(const struct drm_dp_vsc_sdp *vsc, >> + struct dw_dp_sdp *sdp) > >Use drm_dp_vsc_sdp_pack() instead. Ok, I will try。 > >> +{ >> + sdp->header.HB0 = 0; >> + sdp->header.HB1 = DP_SDP_VSC; >> + sdp->header.HB2 = vsc->revision; >> + sdp->header.HB3 = vsc->length; >> + >> + sdp->db[16] = (vsc->pixelformat & 0xf) << 4; >> + sdp->db[16] |= vsc->colorimetry & 0xf; >> + >> + switch (vsc->bpc) { >> + case 8: >> + sdp->db[17] = 0x1; >> + break; >> + case 10: >> + sdp->db[17] = 0x2; >> + break; >> + case 12: >> + sdp->db[17] = 0x3; >> + break; >> + case 16: >> + sdp->db[17] = 0x4; >> + break; >> + case 6: >> + default: >> + break; >> + } >> + >> + if (vsc->dynamic_range == DP_DYNAMIC_RANGE_CTA) >> + sdp->db[17] |= 0x80; >> + >> + sdp->db[18] = vsc->content_type & 0x7; >> + >> + sdp->flags |= DW_DP_SDP_VERTICAL_INTERVAL; >> +} >> + >> +static int dw_dp_send_vsc_sdp(struct dw_dp *dp) >> +{ >> + struct dw_dp_video *video = &dp->video; >> + struct drm_dp_vsc_sdp vsc = {}; >> + struct dw_dp_sdp sdp = {}; >> + >> + vsc.revision = 0x5; >> + vsc.length = 0x13; >> + >> + switch (video->color_format) { >> + case DRM_COLOR_FORMAT_YCBCR444: >> + vsc.pixelformat = DP_PIXELFORMAT_YUV444; >> + break; >> + case DRM_COLOR_FORMAT_YCBCR420: >> + vsc.pixelformat = DP_PIXELFORMAT_YUV420; >> + break; >> + case DRM_COLOR_FORMAT_YCBCR422: >> + vsc.pixelformat = DP_PIXELFORMAT_YUV422; >> + break; >> + case DRM_COLOR_FORMAT_RGB444: >> + default: >> + vsc.pixelformat = DP_PIXELFORMAT_RGB; >> + break; >> + } >> + >> + if (video->color_format == DRM_COLOR_FORMAT_RGB444) { >> + vsc.colorimetry = DP_COLORIMETRY_DEFAULT; >> + vsc.dynamic_range = DP_DYNAMIC_RANGE_VESA; >> + } else { >> + vsc.colorimetry = DP_COLORIMETRY_BT709_YCC; >> + vsc.dynamic_range = DP_DYNAMIC_RANGE_CTA; >> + } >> + >> + vsc.bpc = video->bpc; >> + vsc.content_type = DP_CONTENT_TYPE_NOT_DEFINED; >> + >> + dw_dp_vsc_sdp_pack(&vsc, &sdp); >> + >> + return dw_dp_send_sdp(dp, &sdp); >> +} >> + >> +static int dw_dp_video_set_pixel_mode(struct dw_dp *dp, u8 pixel_mode) >> +{ >> + switch (pixel_mode) { >> + case DW_DP_MP_SINGLE_PIXEL: >> + case DW_DP_MP_DUAL_PIXEL: >> + case DW_DP_MP_QUAD_PIXEL: >> + break; >> + default: >> + return -EINVAL; > >Is it possible? This IP is configurable for single/dual/quad pixel modes。 It's quad pixel mode on rk3588 and dual pixel mode on rk3576, so we add this check here. > >> + } >> + >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, PIXEL_MODE_SELECT, >> + FIELD_PREP(PIXEL_MODE_SELECT, pixel_mode)); >> + >> + return 0; >> +} >> + >> +static bool dw_dp_video_need_vsc_sdp(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + struct dw_dp_video *video = &dp->video; >> + >> + if (!link->vsc_sdp_supported) >> + return false; >> + >> + if (video->color_format == DRM_COLOR_FORMAT_YCBCR420) >> + return true; >> + >> + return false; >> +} >> + >> +static int dw_dp_video_set_msa(struct dw_dp *dp, u8 color_format, u8 bpc, >> + u16 vstart, u16 hstart) >> +{ >> + u16 misc = 0; >> + >> + if (dw_dp_video_need_vsc_sdp(dp)) >> + misc |= DP_MSA_MISC_COLOR_VSC_SDP; >> + >> + switch (color_format) { >> + case DRM_COLOR_FORMAT_RGB444: >> + misc |= DP_MSA_MISC_COLOR_RGB; >> + break; >> + case DRM_COLOR_FORMAT_YCBCR444: >> + misc |= DP_MSA_MISC_COLOR_YCBCR_444_BT709; >> + break; >> + case DRM_COLOR_FORMAT_YCBCR422: >> + misc |= DP_MSA_MISC_COLOR_YCBCR_422_BT709; >> + break; >> + case DRM_COLOR_FORMAT_YCBCR420: >> + break; >> + default: >> + return -EINVAL; >> + } >> + >> + switch (bpc) { >> + case 6: >> + misc |= DP_MSA_MISC_6_BPC; >> + break; >> + case 8: >> + misc |= DP_MSA_MISC_8_BPC; >> + break; >> + case 10: >> + misc |= DP_MSA_MISC_10_BPC; >> + break; >> + case 12: >> + misc |= DP_MSA_MISC_12_BPC; >> + break; >> + case 16: >> + misc |= DP_MSA_MISC_16_BPC; >> + break; >> + default: >> + return -EINVAL; >> + } >> + >> + regmap_write(dp->regmap, DW_DP_VIDEO_MSA1, >> + FIELD_PREP(VSTART, vstart) | FIELD_PREP(HSTART, hstart)); >> + regmap_write(dp->regmap, DW_DP_VIDEO_MSA2, FIELD_PREP(MISC0, misc)); >> + regmap_write(dp->regmap, DW_DP_VIDEO_MSA3, FIELD_PREP(MISC1, misc >> 8)); >> + >> + return 0; >> +} >> + >> +static void dw_dp_video_disable(struct dw_dp *dp) >> +{ >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE, >> + FIELD_PREP(VIDEO_STREAM_ENABLE, 0)); >> +} >> + >> +static int dw_dp_video_enable(struct dw_dp *dp) >> +{ >> + struct dw_dp_video *video = &dp->video; >> + struct dw_dp_link *link = &dp->link; >> + struct drm_display_mode *mode = &video->mode; >> + u8 color_format = video->color_format; >> + u8 bpc = video->bpc; >> + u8 pixel_mode = video->pixel_mode; >> + u8 bpp = video->bpp, init_threshold, vic; >> + u32 hactive, hblank, h_sync_width, h_front_porch; >> + u32 vactive, vblank, v_sync_width, v_front_porch; >> + u32 vstart = mode->vtotal - mode->vsync_start; >> + u32 hstart = mode->htotal - mode->hsync_start; >> + u32 peak_stream_bandwidth, link_bandwidth; >> + u32 average_bytes_per_tu, average_bytes_per_tu_frac; >> + u32 ts, hblank_interval; >> + u32 value; >> + int ret; >> + >> + ret = dw_dp_video_set_pixel_mode(dp, pixel_mode); >> + if (ret) >> + return ret; >> + >> + ret = dw_dp_video_set_msa(dp, color_format, bpc, vstart, hstart); >> + if (ret) >> + return ret; >> + >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_MAPPING, >> + FIELD_PREP(VIDEO_MAPPING, video->video_mapping)); >> + >> + /* Configure DW_DP_VINPUT_POLARITY_CTRL register */ >> + value = 0; >> + if (mode->flags & DRM_MODE_FLAG_PHSYNC) >> + value |= FIELD_PREP(HSYNC_IN_POLARITY, 1); >> + if (mode->flags & DRM_MODE_FLAG_PVSYNC) >> + value |= FIELD_PREP(VSYNC_IN_POLARITY, 1); >> + regmap_write(dp->regmap, DW_DP_VINPUT_POLARITY_CTRL, value); >> + >> + /* Configure DW_DP_VIDEO_CONFIG1 register */ >> + hactive = mode->hdisplay; >> + hblank = mode->htotal - mode->hdisplay; >> + value = FIELD_PREP(HACTIVE, hactive) | FIELD_PREP(HBLANK, hblank); >> + if (mode->flags & DRM_MODE_FLAG_INTERLACE) >> + value |= FIELD_PREP(I_P, 1); >> + vic = drm_match_cea_mode(mode); >> + if (vic == 5 || vic == 6 || vic == 7 || >> + vic == 10 || vic == 11 || vic == 20 || >> + vic == 21 || vic == 22 || vic == 39 || >> + vic == 25 || vic == 26 || vic == 40 || >> + vic == 44 || vic == 45 || vic == 46 || >> + vic == 50 || vic == 51 || vic == 54 || >> + vic == 55 || vic == 58 || vic == 59) >> + value |= R_V_BLANK_IN_OSC; >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG1, value); >> + >> + /* Configure DW_DP_VIDEO_CONFIG2 register */ >> + vblank = mode->vtotal - mode->vdisplay; >> + vactive = mode->vdisplay; >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG2, >> + FIELD_PREP(VBLANK, vblank) | FIELD_PREP(VACTIVE, vactive)); >> + >> + /* Configure DW_DP_VIDEO_CONFIG3 register */ >> + h_sync_width = mode->hsync_end - mode->hsync_start; >> + h_front_porch = mode->hsync_start - mode->hdisplay; >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG3, >> + FIELD_PREP(H_SYNC_WIDTH, h_sync_width) | >> + FIELD_PREP(H_FRONT_PORCH, h_front_porch)); >> + >> + /* Configure DW_DP_VIDEO_CONFIG4 register */ >> + v_sync_width = mode->vsync_end - mode->vsync_start; >> + v_front_porch = mode->vsync_start - mode->vdisplay; >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG4, >> + FIELD_PREP(V_SYNC_WIDTH, v_sync_width) | >> + FIELD_PREP(V_FRONT_PORCH, v_front_porch)); >> + >> + /* Configure DW_DP_VIDEO_CONFIG5 register */ >> + peak_stream_bandwidth = mode->clock * bpp / 8; >> + link_bandwidth = (link->rate / 1000) * link->lanes; >> + ts = peak_stream_bandwidth * 64 / link_bandwidth; >> + average_bytes_per_tu = ts / 1000; >> + average_bytes_per_tu_frac = ts / 100 - average_bytes_per_tu * 10; >> + if (pixel_mode == DW_DP_MP_SINGLE_PIXEL) { >> + if (average_bytes_per_tu < 6) >> + init_threshold = 32; >> + else if (hblank <= 80 && color_format != DRM_COLOR_FORMAT_YCBCR420) >> + init_threshold = 12; >> + else if (hblank <= 40 && color_format == DRM_COLOR_FORMAT_YCBCR420) >> + init_threshold = 3; >> + else >> + init_threshold = 16; >> + } else { >> + u32 t1 = 0, t2 = 0, t3 = 0; >> + >> + switch (bpc) { >> + case 6: >> + t1 = (4 * 1000 / 9) * link->lanes; >> + break; >> + case 8: >> + if (color_format == DRM_COLOR_FORMAT_YCBCR422) { >> + t1 = (1000 / 2) * link->lanes; >> + } else { >> + if (pixel_mode == DW_DP_MP_DUAL_PIXEL) >> + t1 = (1000 / 3) * link->lanes; >> + else >> + t1 = (3000 / 16) * link->lanes; >> + } >> + break; >> + case 10: >> + if (color_format == DRM_COLOR_FORMAT_YCBCR422) >> + t1 = (2000 / 5) * link->lanes; >> + else >> + t1 = (4000 / 15) * link->lanes; >> + break; >> + case 12: >> + if (color_format == DRM_COLOR_FORMAT_YCBCR422) { >> + if (pixel_mode == DW_DP_MP_DUAL_PIXEL) >> + t1 = (1000 / 6) * link->lanes; >> + else >> + t1 = (1000 / 3) * link->lanes; >> + } else { >> + t1 = (2000 / 9) * link->lanes; >> + } >> + break; >> + case 16: >> + if (color_format != DRM_COLOR_FORMAT_YCBCR422 && >> + pixel_mode == DW_DP_MP_DUAL_PIXEL) >> + t1 = (1000 / 6) * link->lanes; >> + else >> + t1 = (1000 / 4) * link->lanes; >> + break; >> + default: >> + return -EINVAL; >> + } >> + >> + if (color_format == DRM_COLOR_FORMAT_YCBCR420) >> + t2 = (link->rate / 4) * 1000 / (mode->clock / 2); >> + else >> + t2 = (link->rate / 4) * 1000 / mode->clock; >> + >> + if (average_bytes_per_tu_frac) >> + t3 = average_bytes_per_tu + 1; >> + else >> + t3 = average_bytes_per_tu; >> + init_threshold = t1 * t2 * t3 / (1000 * 1000); >> + if (init_threshold <= 16 || average_bytes_per_tu < 10) >> + init_threshold = 40; >> + } >> + >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG5, >> + FIELD_PREP(INIT_THRESHOLD_HI, init_threshold >> 6) | >> + FIELD_PREP(AVERAGE_BYTES_PER_TU_FRAC, average_bytes_per_tu_frac) | >> + FIELD_PREP(INIT_THRESHOLD, init_threshold) | >> + FIELD_PREP(AVERAGE_BYTES_PER_TU, average_bytes_per_tu)); >> + >> + /* Configure DW_DP_VIDEO_HBLANK_INTERVAL register */ >> + hblank_interval = hblank * (link->rate / 4) / mode->clock; >> + regmap_write(dp->regmap, DW_DP_VIDEO_HBLANK_INTERVAL, >> + FIELD_PREP(HBLANK_INTERVAL_EN, 1) | >> + FIELD_PREP(HBLANK_INTERVAL, hblank_interval)); >> + >> + /* Video stream enable */ >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE, >> + FIELD_PREP(VIDEO_STREAM_ENABLE, 1)); >> + >> + if (dw_dp_video_need_vsc_sdp(dp)) >> + dw_dp_send_vsc_sdp(dp); >> + >> + return 0; >> +} >> + >> +static void dw_dp_hpd_init(struct dw_dp *dp) >> +{ >> + /* Enable all HPD interrupts */ >> + regmap_update_bits(dp->regmap, DW_DP_HPD_INTERRUPT_ENABLE, >> + HPD_UNPLUG_EN | HPD_PLUG_EN | HPD_IRQ_EN, >> + FIELD_PREP(HPD_UNPLUG_EN, 1) | >> + FIELD_PREP(HPD_PLUG_EN, 1) | >> + FIELD_PREP(HPD_IRQ_EN, 1)); >> + >> + /* Enable all top-level interrupts */ >> + regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE, >> + HPD_EVENT_EN, FIELD_PREP(HPD_EVENT_EN, 1)); >> +} >> + >> +static void dw_dp_aux_init(struct dw_dp *dp) >> +{ >> + regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE, >> + AUX_REPLY_EVENT_EN, FIELD_PREP(AUX_REPLY_EVENT_EN, 1)); >> +} >> + >> +static void dw_dp_init_hw(struct dw_dp *dp) >> +{ >> + regmap_update_bits(dp->regmap, DW_DP_CCTL, DEFAULT_FAST_LINK_TRAIN_EN, >> + FIELD_PREP(DEFAULT_FAST_LINK_TRAIN_EN, 0)); >> + >> + dw_dp_hpd_init(dp); >> + dw_dp_aux_init(dp); >> +} >> + >> +static int dw_dp_aux_write_data(struct dw_dp *dp, const u8 *buffer, size_t size) >> +{ >> + size_t i, j; >> + >> + for (i = 0; i < DIV_ROUND_UP(size, 4); i++) { >> + size_t num = min_t(size_t, size - i * 4, 4); >> + u32 value = 0; >> + >> + for (j = 0; j < num; j++) >> + value |= buffer[i * 4 + j] << (j * 8); >> + >> + regmap_write(dp->regmap, DW_DP_AUX_DATA0 + i * 4, value); >> + } >> + >> + return size; >> +} >> + >> +static int dw_dp_aux_read_data(struct dw_dp *dp, u8 *buffer, size_t size) >> +{ >> + size_t i, j; >> + >> + for (i = 0; i < DIV_ROUND_UP(size, 4); i++) { >> + size_t num = min_t(size_t, size - i * 4, 4); >> + u32 value; >> + >> + regmap_read(dp->regmap, DW_DP_AUX_DATA0 + i * 4, &value); >> + >> + for (j = 0; j < num; j++) >> + buffer[i * 4 + j] = value >> (j * 8); >> + } >> + >> + return size; >> +} >> + >> +static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux, >> + struct drm_dp_aux_msg *msg) >> +{ >> + struct dw_dp *dp = container_of(aux, struct dw_dp, aux); >> + unsigned long timeout = msecs_to_jiffies(10); >> + u32 status, value; >> + ssize_t ret = 0; >> + >> + if (WARN_ON(msg->size > 16)) >> + return -E2BIG; >> + >> + switch (msg->request & ~DP_AUX_I2C_MOT) { >> + case DP_AUX_NATIVE_WRITE: >> + case DP_AUX_I2C_WRITE: >> + case DP_AUX_I2C_WRITE_STATUS_UPDATE: >> + ret = dw_dp_aux_write_data(dp, msg->buffer, msg->size); >> + if (ret < 0) >> + return ret; >> + break; >> + case DP_AUX_NATIVE_READ: >> + case DP_AUX_I2C_READ: >> + break; >> + default: >> + return -EINVAL; >> + } >> + >> + if (msg->size > 0) >> + value = FIELD_PREP(AUX_LEN_REQ, msg->size - 1); >> + else >> + value = FIELD_PREP(I2C_ADDR_ONLY, 1); >> + value |= FIELD_PREP(AUX_CMD_TYPE, msg->request); >> + value |= FIELD_PREP(AUX_ADDR, msg->address); >> + regmap_write(dp->regmap, DW_DP_AUX_CMD, value); >> + >> + status = wait_for_completion_timeout(&dp->complete, timeout); >> + if (!status) { >> + dev_err(dp->dev, "timeout waiting for AUX reply\n"); >> + return -ETIMEDOUT; >> + } >> + >> + regmap_read(dp->regmap, DW_DP_AUX_STATUS, &value); >> + if (value & AUX_TIMEOUT) >> + return -ETIMEDOUT; >> + >> + msg->reply = FIELD_GET(AUX_STATUS, value); >> + >> + if (msg->size > 0 && msg->reply == DP_AUX_NATIVE_REPLY_ACK) { >> + if (msg->request & DP_AUX_I2C_READ) { >> + size_t count = FIELD_GET(AUX_BYTES_READ, value) - 1; >> + >> + if (count != msg->size) >> + return -EBUSY; >> + >> + ret = dw_dp_aux_read_data(dp, msg->buffer, count); >> + if (ret < 0) >> + return ret; >> + } >> + } >> + >> + return ret; >> +} >> + >> +/* >> + * Limits for the video timing output by DP: >> + * 1. the hfp should be 2 pixels aligned; >> + * 2. the minimum hsync should be 9 pixel; >> + * 3. the minimum hbp should be 16 pixel; >> + */ >> +static bool dw_dp_bridge_mode_fixup(struct drm_bridge *bridge, >> + const struct drm_display_mode *mode, >> + struct drm_display_mode *adjusted_mode) >> +{ >> + struct dw_dp *dp = bridge_to_dp(bridge); >> + int min_hbp = 16; >> + int min_hsync = 9; >> + >> + if ((adjusted_mode->hsync_start - adjusted_mode->hdisplay) & 0x1) { >> + adjusted_mode->hsync_start += 1; >> + dev_warn(dp->dev, "hfp is not 2 pixeel aligned, fixup to aligned hfp\n"); >> + } >> + if (adjusted_mode->hsync_end - adjusted_mode->hsync_start < min_hsync) { >> + adjusted_mode->hsync_end = adjusted_mode->hsync_start + min_hsync; >> + dev_warn(dp->dev, "hsync is too narrow, fixup to min hsync:%d\n", min_hsync); >> + } >> + if (adjusted_mode->htotal - adjusted_mode->hsync_end < min_hbp) { >> + adjusted_mode->htotal = adjusted_mode->hsync_end + min_hbp; >> + dev_warn(dp->dev, "hbp is too narrow, fixup to min hbp:%d\n", min_hbp); >> + } >> + >> + return true; >> +} >> + >> +static enum drm_mode_status dw_dp_bridge_mode_valid(struct drm_bridge *bridge, >> + const struct drm_display_info *info, >> + const struct drm_display_mode *mode) >> +{ >> + struct dw_dp *dp = bridge_to_dp(bridge); >> + struct dw_dp_link *link = &dp->link; >> + u32 min_bpp; >> + >> + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR420 && >> + link->vsc_sdp_supported && >> + (drm_mode_is_420_only(info, mode) || drm_mode_is_420_also(info, mode))) >> + min_bpp = 12; >> + else if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) >> + min_bpp = 16; >> + else if (info->color_formats & DRM_COLOR_FORMAT_RGB444) >> + min_bpp = 18; >> + else >> + min_bpp = 24; >> + >> + if (!link->vsc_sdp_supported && >> + drm_mode_is_420_only(info, mode)) >> + return MODE_NO_420; >> + >> + if (!dw_dp_bandwidth_ok(dp, mode, min_bpp, link->lanes, link->rate)) >> + return MODE_CLOCK_HIGH; >> + >> + return MODE_OK; >> +} >> + >> +static void dw_dp_bridge_atomic_pre_enable(struct drm_bridge *bridge, >> + struct drm_bridge_state *old_bridge_state) >> +{ >> + struct drm_atomic_state *state = old_bridge_state->base.state; >> + struct drm_crtc_state *crtc_state = bridge->encoder->crtc->state; >> + struct dw_dp *dp = bridge_to_dp(bridge); >> + struct dw_dp_video *video = &dp->video; >> + struct drm_display_mode *m = &video->mode; >> + struct drm_bridge_state *bridge_state; >> + const struct dw_dp_output_format *fmt; >> + >> + drm_mode_copy(m, &crtc_state->adjusted_mode); >> + >> + bridge_state = drm_atomic_get_new_bridge_state(state, bridge); >> + fmt = dw_dp_get_output_format(bridge_state->output_bus_cfg.format); > >Should it be a part of the atomic_check() instead? Since we have modef_fixup in this driver, which is mutually exclusive with atomic_check. > >> + >> + video->video_mapping = fmt->video_mapping; >> + video->color_format = fmt->color_format; >> + video->bpc = fmt->bpc; >> + video->bpp = fmt->bpp; >> +} >> + >> +static bool dw_dp_needs_link_retrain(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + u8 link_status[DP_LINK_STATUS_SIZE]; >> + >> + if (!dw_dp_link_train_valid(&link->train)) >> + return false; >> + >> + if (drm_dp_dpcd_read_link_status(&dp->aux, link_status) < 0) >> + return false; >> + >> + /* Retrain if Channel EQ or CR not ok */ >> + return !drm_dp_channel_eq_ok(link_status, dp->link.lanes); >> +} >> + >> +static void dw_dp_link_disable(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + >> + if (dw_dp_hpd_detect(dp)) >> + dw_dp_link_power_down(dp); >> + >> + dw_dp_phy_xmit_enable(dp, 0); >> + >> + phy_power_off(dp->phy); >> + >> + link->train.clock_recovered = false; >> + link->train.channel_equalized = false; >> +} >> + >> +static int dw_dp_link_enable(struct dw_dp *dp) >> +{ >> + int ret; >> + >> + ret = phy_power_on(dp->phy); >> + if (ret) >> + return ret; >> + >> + ret = dw_dp_link_power_up(dp); >> + if (ret < 0) >> + return ret; >> + >> + ret = dw_dp_link_train(dp); >> + >> + return ret; >> +} >> + >> +static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge, >> + struct drm_bridge_state *old_state) >> +{ >> + struct dw_dp *dp = bridge_to_dp(bridge); >> + struct drm_atomic_state *state = old_state->base.state; >> + struct drm_connector *connector; >> + struct drm_connector_state *conn_state; >> + int ret; >> + >> + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); >> + if (!connector) { >> + dev_err(dp->dev, "failed to get connector\n"); >> + return; >> + } >> + >> + conn_state = drm_atomic_get_new_connector_state(state, connector); >> + if (!conn_state) { >> + dev_err(dp->dev, "failed to get connector state\n"); >> + return; >> + } >> + >> + set_bit(0, dp->sdp_reg_bank); >> + >> + ret = dw_dp_link_enable(dp); >> + if (ret < 0) { >> + dev_err(dp->dev, "failed to enable link: %d\n", ret); >> + return; >> + } >> + >> + ret = dw_dp_video_enable(dp); >> + if (ret < 0) { >> + dev_err(dp->dev, "failed to enable video: %d\n", ret); >> + return; >> + } >> +} >> + >> +static void dw_dp_reset(struct dw_dp *dp) >> +{ >> + int val; >> + >> + disable_irq(dp->irq); >> + regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET, >> + FIELD_PREP(CONTROLLER_RESET, 1)); >> + udelay(10); >> + regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET, >> + FIELD_PREP(CONTROLLER_RESET, 0)); >> + >> + dw_dp_init_hw(dp); >> + regmap_read_poll_timeout(dp->regmap, DW_DP_HPD_STATUS, val, >> + FIELD_GET(HPD_HOT_PLUG, val), 200, 200000); >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG); >> + enable_irq(dp->irq); >> +} >> + >> +static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge, >> + struct drm_bridge_state *old_bridge_state) >> +{ >> + struct dw_dp *dp = bridge_to_dp(bridge); >> + >> + dw_dp_video_disable(dp); >> + dw_dp_link_disable(dp); >> + bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE); >> + dw_dp_reset(dp); >> +} >> + >> +static bool dw_dp_hpd_detect_link(struct dw_dp *dp) >> +{ >> + int ret; >> + >> + ret = phy_power_on(dp->phy); >> + if (ret < 0) >> + return false; >> + ret = dw_dp_link_parse(dp); >> + phy_power_off(dp->phy); >> + >> + return !ret; >> +} >> + >> +static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge) >> +{ >> + struct dw_dp *dp = bridge_to_dp(bridge); >> + >> + if (!dw_dp_hpd_detect(dp)) >> + return connector_status_disconnected; >> + >> + if (!dw_dp_hpd_detect_link(dp)) >> + return connector_status_disconnected; >> + >> + return connector_status_connected; >> +} >> + >> +static const struct drm_edid *dw_dp_bridge_edid_read(struct drm_bridge *bridge, >> + struct drm_connector *connector) >> +{ >> + struct dw_dp *dp = bridge_to_dp(bridge); >> + const struct drm_edid *edid; >> + int ret; >> + >> + ret = phy_power_on(dp->phy); >> + if (ret) >> + return NULL; >> + >> + edid = drm_edid_read_ddc(connector, &dp->aux.ddc); >> + >> + phy_power_off(dp->phy); >> + >> + return edid; >> +} >> + >> +static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, >> + struct drm_bridge_state *bridge_state, >> + struct drm_crtc_state *crtc_state, >> + struct drm_connector_state *conn_state, >> + unsigned int *num_output_fmts) >> +{ >> + struct dw_dp *dp = bridge_to_dp(bridge); >> + struct dw_dp_link *link = &dp->link; >> + struct drm_display_info *di = &conn_state->connector->display_info; >> + struct drm_display_mode mode = crtc_state->mode; >> + const struct dw_dp_output_format *fmt; >> + u32 i, j = 0; >> + u32 *output_fmts; >> + >> + *num_output_fmts = 0; >> + >> + output_fmts = kcalloc(ARRAY_SIZE(dw_dp_output_formats), sizeof(*output_fmts), GFP_KERNEL); >> + if (!output_fmts) >> + return NULL; >> + >> + for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) { >> + fmt = &dw_dp_output_formats[i]; >> + >> + if (fmt->bpc > conn_state->max_bpc) >> + continue; >> + >> + if (!(fmt->color_format & di->color_formats)) >> + continue; >> + >> + if (fmt->color_format == DRM_COLOR_FORMAT_YCBCR420 && >> + !link->vsc_sdp_supported) >> + continue; >> + >> + if (fmt->color_format != DRM_COLOR_FORMAT_YCBCR420 && >> + drm_mode_is_420_only(di, &mode)) >> + continue; >> + >> + if (!dw_dp_bandwidth_ok(dp, &mode, fmt->bpp, link->lanes, link->rate)) >> + continue; >> + >> + output_fmts[j++] = fmt->bus_format; >> + } >> + >> + *num_output_fmts = j; >> + >> + return output_fmts; >> +} >> + >> +static const struct drm_bridge_funcs dw_dp_bridge_funcs = { >> + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, >> + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, >> + .atomic_reset = drm_atomic_helper_bridge_reset, >> + .atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt, >> + .atomic_get_output_bus_fmts = dw_dp_bridge_atomic_get_output_bus_fmts, >> + .mode_fixup = dw_dp_bridge_mode_fixup, >> + .mode_valid = dw_dp_bridge_mode_valid, >> + .atomic_pre_enable = dw_dp_bridge_atomic_pre_enable, >> + .atomic_enable = dw_dp_bridge_atomic_enable, >> + .atomic_disable = dw_dp_bridge_atomic_disable, >> + .detect = dw_dp_bridge_detect, >> + .edid_read = dw_dp_bridge_edid_read, >> +}; >> + >> +static int dw_dp_link_retrain(struct dw_dp *dp) >> +{ >> + struct drm_device *dev = dp->bridge.dev; >> + struct drm_modeset_acquire_ctx ctx; >> + int ret; >> + >> + if (!dw_dp_needs_link_retrain(dp)) >> + return 0; >> + >> + dev_dbg(dp->dev, "Retraining link\n"); >> + >> + drm_modeset_acquire_init(&ctx, 0); >> + for (;;) { >> + ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx); >> + if (ret != -EDEADLK) >> + break; > >I think it's an error to continue in such a case. There should be a >return after the loop if ret is not 0. Thank you for catching this, I will add a check fater the loop. > >> + >> + drm_modeset_backoff(&ctx); >> + } >> + >> + ret = dw_dp_link_train(dp); >> + drm_modeset_drop_locks(&ctx); >> + drm_modeset_acquire_fini(&ctx); >> + >> + return ret; >> +} >> + >> +static void dw_dp_hpd_work(struct work_struct *work) >> +{ >> + struct dw_dp *dp = container_of(work, struct dw_dp, hpd_work); >> + bool long_hpd; >> + int ret; >> + >> + mutex_lock(&dp->irq_lock); >> + long_hpd = dp->hotplug.long_hpd; >> + mutex_unlock(&dp->irq_lock); >> + >> + dev_dbg(dp->dev, "[drm] Get hpd irq - %s\n", long_hpd ? "long" : "short"); >> + >> + if (!long_hpd) { >> + if (dw_dp_needs_link_retrain(dp)) { >> + ret = dw_dp_link_retrain(dp); >> + if (ret) >> + dev_warn(dp->dev, "Retrain link failed\n"); >> + } >> + } else { >> + drm_helper_hpd_irq_event(dp->bridge.dev); >> + } >> +} >> + >> +static void dw_dp_handle_hpd_event(struct dw_dp *dp) >> +{ >> + u32 value; >> + >> + mutex_lock(&dp->irq_lock); >> + regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value); >> + >> + if (value & HPD_IRQ) { >> + dev_dbg(dp->dev, "IRQ from the HPD\n"); >> + dp->hotplug.long_hpd = false; >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_IRQ); >> + } >> + >> + if (value & HPD_HOT_PLUG) { >> + dev_dbg(dp->dev, "Hot plug detected\n"); >> + dp->hotplug.long_hpd = true; >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG); >> + } >> + >> + if (value & HPD_HOT_UNPLUG) { >> + dev_dbg(dp->dev, "Unplug detected\n"); >> + dp->hotplug.long_hpd = true; >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_UNPLUG); >> + } >> + mutex_unlock(&dp->irq_lock); >> + >> + schedule_work(&dp->hpd_work); >> +} >> + >> +static irqreturn_t dw_dp_irq(int irq, void *data) >> +{ >> + struct dw_dp *dp = data; >> + u32 value; >> + >> + regmap_read(dp->regmap, DW_DP_GENERAL_INTERRUPT, &value); >> + if (!value) >> + return IRQ_NONE; >> + >> + if (value & HPD_EVENT) >> + dw_dp_handle_hpd_event(dp); >> + >> + if (value & AUX_REPLY_EVENT) { >> + regmap_write(dp->regmap, DW_DP_GENERAL_INTERRUPT, AUX_REPLY_EVENT); >> + complete(&dp->complete); >> + } >> + >> + return IRQ_HANDLED; >> +} >> + >> +static const struct regmap_range dw_dp_readable_ranges[] = { >> + regmap_reg_range(DW_DP_VERSION_NUMBER, DW_DP_ID), >> + regmap_reg_range(DW_DP_CONFIG_REG1, DW_DP_CONFIG_REG3), >> + regmap_reg_range(DW_DP_CCTL, DW_DP_SOFT_RESET_CTRL), >> + regmap_reg_range(DW_DP_VSAMPLE_CTRL, DW_DP_VIDEO_HBLANK_INTERVAL), >> + regmap_reg_range(DW_DP_AUD_CONFIG1, DW_DP_AUD_CONFIG1), >> + regmap_reg_range(DW_DP_SDP_VERTICAL_CTRL, DW_DP_SDP_STATUS_EN), >> + regmap_reg_range(DW_DP_PHYIF_CTRL, DW_DP_PHYIF_PWRDOWN_CTRL), >> + regmap_reg_range(DW_DP_AUX_CMD, DW_DP_AUX_DATA3), >> + regmap_reg_range(DW_DP_GENERAL_INTERRUPT, DW_DP_HPD_INTERRUPT_ENABLE), >> +}; >> + >> +static const struct regmap_access_table dw_dp_readable_table = { >> + .yes_ranges = dw_dp_readable_ranges, >> + .n_yes_ranges = ARRAY_SIZE(dw_dp_readable_ranges), >> +}; >> + >> +static const struct regmap_config dw_dp_regmap_config = { >> + .reg_bits = 32, >> + .reg_stride = 4, >> + .val_bits = 32, >> + .fast_io = true, >> + .max_register = DW_DP_MAX_REGISTER, >> + .rd_table = &dw_dp_readable_table, >> +}; >> + >> +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, >> + const struct dw_dp_plat_data *plat_data) >> +{ >> + struct platform_device *pdev = to_platform_device(dev); >> + struct dw_dp *dp; >> + struct drm_bridge *bridge; >> + void __iomem *res; >> + int ret; >> + >> + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); >> + if (!dp) >> + return ERR_PTR(-ENOMEM); >> + >> + dp->dev = dev; >> + dp->video.pixel_mode = DW_DP_MP_QUAD_PIXEL; >> + >> + dp->plat_data = plat_data; >> + bridge = &dp->bridge; >> + mutex_init(&dp->irq_lock); >> + INIT_WORK(&dp->hpd_work, dw_dp_hpd_work); >> + init_completion(&dp->complete); >> + >> + res = devm_platform_ioremap_resource(pdev, 0); >> + if (IS_ERR(res)) >> + return ERR_CAST(res); >> + >> + dp->regmap = devm_regmap_init_mmio(dev, res, &dw_dp_regmap_config); >> + if (IS_ERR(dp->regmap)) { >> + dev_err_probe(dev, PTR_ERR(dp->regmap), "failed to create regmap\n"); >> + return ERR_CAST(dp->regmap); >> + } >> + >> + dp->phy = devm_of_phy_get(dev, dev->of_node, NULL); >> + if (IS_ERR(dp->phy)) { >> + dev_err_probe(dev, PTR_ERR(dp->phy), "failed to get phy\n"); >> + return ERR_CAST(dp->phy); >> + } >> + >> + dp->apb_clk = devm_clk_get_enabled(dev, "apb"); >> + if (IS_ERR(dp->apb_clk)) { >> + dev_err_probe(dev, PTR_ERR(dp->apb_clk), "failed to get apb clock\n"); >> + return ERR_CAST(dp->apb_clk); >> + } >> + >> + dp->aux_clk = devm_clk_get_enabled(dev, "aux"); >> + if (IS_ERR(dp->aux_clk)) { >> + dev_err_probe(dev, PTR_ERR(dp->aux_clk), "failed to get aux clock\n"); >> + return ERR_CAST(dp->aux_clk); >> + } >> + >> + dp->i2s_clk = devm_clk_get(dev, "i2s"); >> + if (IS_ERR(dp->i2s_clk)) { >> + dev_err_probe(dev, PTR_ERR(dp->i2s_clk), "failed to get i2s clock\n"); >> + return ERR_CAST(dp->i2s_clk); >> + } >> + >> + dp->spdif_clk = devm_clk_get(dev, "spdif"); >> + if (IS_ERR(dp->spdif_clk)) { >> + dev_err_probe(dev, PTR_ERR(dp->spdif_clk), "failed to get spdif clock\n"); >> + return ERR_CAST(dp->spdif_clk); >> + } >> + >> + dp->hdcp_clk = devm_clk_get(dev, "hdcp"); >> + if (IS_ERR(dp->hdcp_clk)) { >> + dev_err_probe(dev, PTR_ERR(dp->hdcp_clk), "failed to get hdcp clock\n"); >> + return ERR_CAST(dp->hdcp_clk); >> + } >> + >> + dp->rstc = devm_reset_control_get(dev, NULL); >> + if (IS_ERR(dp->rstc)) { >> + dev_err_probe(dev, PTR_ERR(dp->rstc), "failed to get reset control\n"); >> + return ERR_CAST(dp->rstc); >> + } >> + >> + dp->irq = platform_get_irq(pdev, 0); >> + if (dp->irq < 0) >> + return ERR_PTR(ret); >> + >> + ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq, >> + IRQF_ONESHOT, dev_name(dev), dp); > >Are you ready to handle the IRQ here, before the bridge is filled? I will move to devm_request_threaded_irq after dw_dp_init_hw. > >> + if (ret) { >> + dev_err_probe(dev, ret, "failed to request irq\n"); >> + return ERR_PTR(ret); >> + } >> + >> + bridge->of_node = dev->of_node; >> + bridge->funcs = &dw_dp_bridge_funcs; >> + bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; >> + bridge->type = DRM_MODE_CONNECTOR_DisplayPort; >> + bridge->ycbcr_420_allowed = true; >> + bridge->vendor = "Synopsys"; >> + bridge->product = "DW DP TX"; > >These two are unused > >> + >> + platform_set_drvdata(pdev, dp); > >Unused I will remove them. Thanks again. > >> + >> + dp->aux.dev = dev; >> + dp->aux.drm_dev = encoder->dev; >> + dp->aux.name = dev_name(dev); >> + dp->aux.transfer = dw_dp_aux_transfer; >> + ret = drm_dp_aux_register(&dp->aux); >> + if (ret) { >> + dev_err_probe(dev, ret, "Aux register failed\n"); >> + return ERR_PTR(ret); >> + } >> + >> + ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); >> + if (ret) >> + dev_err_probe(dev, ret, "Failed to attach bridge\n"); >> + >> + dw_dp_init_hw(dp); >> + >> + return dp; >> +} >> +MODULE_AUTHOR("Andy Yan <andyshrk@163.com>"); >> +MODULE_DESCRIPTION("DW DP Core Library"); >> +MODULE_LICENSE("GPL"); >> diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h >> new file mode 100644 >> index 000000000000..07845f609eca >> --- /dev/null >> +++ b/include/drm/bridge/dw_dp.h >> @@ -0,0 +1,19 @@ >> +/* SPDX-License-Identifier: GPL-2.0-or-later */ >> +/* >> + * Copyright (c) 2025 Rockchip Electronics Co., Ltd. >> + */ >> + >> +#ifndef __DW_DP__ >> +#define __DW_DP__ >> + >> +struct device; >> +struct drm_encoder; >> +struct dw_dp; >> + >> +struct dw_dp_plat_data { >> + u32 max_link_rate; >> +}; >> + >> +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, >> + const struct dw_dp_plat_data *plat_data); >> +#endif /* __DW_DP__ */ >> -- >> 2.34.1 >> > >-- >With best wishes >Dmitry ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library 2025-03-02 10:34 ` Andy Yan @ 2025-03-02 16:21 ` Dmitry Baryshkov 2025-03-04 7:16 ` Andy Yan 2025-03-04 10:16 ` Andy Yan 0 siblings, 2 replies; 32+ messages in thread From: Dmitry Baryshkov @ 2025-03-02 16:21 UTC (permalink / raw) To: Andy Yan Cc: heiko, hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan On Sun, Mar 02, 2025 at 06:34:22PM +0800, Andy Yan wrote: > > > Hi Dmitry, > Thank you for your review。 > Please also review my inline reply. > > 在 2025-03-02 02:14:19,"Dmitry Baryshkov" <dmitry.baryshkov@linaro.org> 写道: > >On Sun, Feb 23, 2025 at 07:30:25PM +0800, Andy Yan wrote: > >> From: Andy Yan <andy.yan@rock-chips.com> > >> > >> The DW DP TX Controller is compliant with the DisplayPort Specification > >> Version 1.4 with the following features: > >> > >> * DisplayPort 1.4a > >> * Main Link: 1/2/4 lanes > >> * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps > >> * AUX channel 1Mbps > >> * Single Stream Transport(SST) > >> * Multistream Transport (MST) > >> *Type-C support (alternate mode) > >> * HDCP 2.2, HDCP 1.3 > >> * Supports up to 8/10 bits per color component > >> * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0 > >> * Pixel clock up to 594MHz > >> * I2S, SPDIF audio interface > >> > >> Add library with common helpers to make it can be shared with > >> other SoC. > >> > >> Signed-off-by: Andy Yan <andy.yan@rock-chips.com> > >> > >> drm/bridge: cleanup > > > >Stray line? > > Sorry, will be removed. > > > > >> > >> --- > >> > >> drivers/gpu/drm/bridge/synopsys/Kconfig | 7 + > >> drivers/gpu/drm/bridge/synopsys/Makefile | 1 + > >> drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2155 ++++++++++++++++++++++ > >> include/drm/bridge/dw_dp.h | 19 + > >> 4 files changed, 2182 insertions(+) > >> create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-dp.c > >> create mode 100644 include/drm/bridge/dw_dp.h > >> > >> diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig > >> index f3ab2f985f8c..2c5e532410de 100644 > >> --- a/drivers/gpu/drm/bridge/synopsys/Kconfig > >> +++ b/drivers/gpu/drm/bridge/synopsys/Kconfig > >> @@ -1,4 +1,11 @@ > >> # SPDX-License-Identifier: GPL-2.0-only > >> +config DRM_DW_DP > >> + tristate > >> + select DRM_DISPLAY_HELPER > >> + select DRM_DISPLAY_DP_HELPER > >> + select DRM_KMS_HELPER > >> + select REGMAP_MMIO > >> + > >> config DRM_DW_HDMI > >> tristate > >> select DRM_DISPLAY_HDMI_HELPER > >> diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile > >> index 9dc376d220ad..4dada44029ac 100644 > >> --- a/drivers/gpu/drm/bridge/synopsys/Makefile > >> +++ b/drivers/gpu/drm/bridge/synopsys/Makefile > >> @@ -1,4 +1,5 @@ > >> # SPDX-License-Identifier: GPL-2.0-only > >> +obj-$(CONFIG_DRM_DW_DP) += dw-dp.o > >> obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o > >> obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o > >> obj-$(CONFIG_DRM_DW_HDMI_GP_AUDIO) += dw-hdmi-gp-audio.o > >> diff --git a/drivers/gpu/drm/bridge/synopsys/dw-dp.c b/drivers/gpu/drm/bridge/synopsys/dw-dp.c > >> new file mode 100644 > >> index 000000000000..6ecbe9851369 > >> --- /dev/null > >> +++ b/drivers/gpu/drm/bridge/synopsys/dw-dp.c > >> @@ -0,0 +1,2155 @@ > >> +// SPDX-License-Identifier: GPL-2.0 > >> +/* > >> + * Synopsys DesignWare Cores DisplayPort Transmitter Controller > >> + * > >> + * Copyright (c) 2024 Rockchip Electronics Co., Ltd. > >> + * > >> + * Author: Andy Yan <andy.yan@rock-chips.com> > >> + */ > >> +#include <linux/bitfield.h> > >> +#include <linux/clk.h> > >> +#include <linux/component.h> > >> +#include <linux/iopoll.h> > >> +#include <linux/irq.h> > >> +#include <linux/of_device.h> > >> +#include <linux/of_graph.h> > >> +#include <linux/platform_device.h> > >> +#include <linux/regmap.h> > >> +#include <linux/reset.h> > >> +#include <linux/phy/phy.h> > >> +#include <linux/unaligned.h> > >> + > >> +#include <drm/bridge/dw_dp.h> > >> +#include <drm/drm_atomic_helper.h> > >> +#include <drm/drm_bridge.h> > >> +#include <drm/drm_bridge_connector.h> > >> +#include <drm/display/drm_dp_helper.h> > >> +#include <drm/display/drm_hdmi_helper.h> > >> +#include <drm/drm_edid.h> > >> +#include <drm/drm_of.h> > >> +#include <drm/drm_print.h> > >> +#include <drm/drm_probe_helper.h> > >> +#include <drm/drm_simple_kms_helper.h> > >> + > >> +#include <uapi/linux/media-bus-format.h> > >> + > >> +#define DW_DP_VERSION_NUMBER 0x0000 > >> +#define DW_DP_VERSION_TYPE 0x0004 > >> +#define DW_DP_ID 0x0008 > >> + > >> +#define DW_DP_CONFIG_REG1 0x0100 > >> +#define DW_DP_CONFIG_REG2 0x0104 > >> +#define DW_DP_CONFIG_REG3 0x0108 > >> + > >> +#define DW_DP_CCTL 0x0200 > >> +#define FORCE_HPD BIT(4) > >> +#define DEFAULT_FAST_LINK_TRAIN_EN BIT(2) > >> +#define ENHANCE_FRAMING_EN BIT(1) > >> +#define SCRAMBLE_DIS BIT(0) > >> +#define DW_DP_SOFT_RESET_CTRL 0x0204 > >> +#define VIDEO_RESET BIT(5) > >> +#define AUX_RESET BIT(4) > >> +#define AUDIO_SAMPLER_RESET BIT(3) > >> +#define HDCP_MODULE_RESET BIT(2) > >> +#define PHY_SOFT_RESET BIT(1) > >> +#define CONTROLLER_RESET BIT(0) > >> + > >> +#define DW_DP_VSAMPLE_CTRL 0x0300 > >> +#define PIXEL_MODE_SELECT GENMASK(22, 21) > >> +#define VIDEO_MAPPING GENMASK(20, 16) > >> +#define VIDEO_STREAM_ENABLE BIT(5) > >> + > >> +#define DW_DP_VSAMPLE_STUFF_CTRL1 0x0304 > >> + > >> +#define DW_DP_VSAMPLE_STUFF_CTRL2 0x0308 > >> + > >> +#define DW_DP_VINPUT_POLARITY_CTRL 0x030c > >> +#define DE_IN_POLARITY BIT(2) > >> +#define HSYNC_IN_POLARITY BIT(1) > >> +#define VSYNC_IN_POLARITY BIT(0) > >> + > >> +#define DW_DP_VIDEO_CONFIG1 0x0310 > >> +#define HACTIVE GENMASK(31, 16) > >> +#define HBLANK GENMASK(15, 2) > >> +#define I_P BIT(1) > >> +#define R_V_BLANK_IN_OSC BIT(0) > >> + > >> +#define DW_DP_VIDEO_CONFIG2 0x0314 > >> +#define VBLANK GENMASK(31, 16) > >> +#define VACTIVE GENMASK(15, 0) > >> + > >> +#define DW_DP_VIDEO_CONFIG3 0x0318 > >> +#define H_SYNC_WIDTH GENMASK(31, 16) > >> +#define H_FRONT_PORCH GENMASK(15, 0) > >> + > >> +#define DW_DP_VIDEO_CONFIG4 0x031c > >> +#define V_SYNC_WIDTH GENMASK(31, 16) > >> +#define V_FRONT_PORCH GENMASK(15, 0) > >> + > >> +#define DW_DP_VIDEO_CONFIG5 0x0320 > >> +#define INIT_THRESHOLD_HI GENMASK(22, 21) > >> +#define AVERAGE_BYTES_PER_TU_FRAC GENMASK(19, 16) > >> +#define INIT_THRESHOLD GENMASK(13, 7) > >> +#define AVERAGE_BYTES_PER_TU GENMASK(6, 0) > >> + > >> +#define DW_DP_VIDEO_MSA1 0x0324 > >> +#define VSTART GENMASK(31, 16) > >> +#define HSTART GENMASK(15, 0) > >> + > >> +#define DW_DP_VIDEO_MSA2 0x0328 > >> +#define MISC0 GENMASK(31, 24) > >> + > >> +#define DW_DP_VIDEO_MSA3 0x032c > >> +#define MISC1 GENMASK(31, 24) > >> + > >> +#define DW_DP_VIDEO_HBLANK_INTERVAL 0x0330 > >> +#define HBLANK_INTERVAL_EN BIT(16) > >> +#define HBLANK_INTERVAL GENMASK(15, 0) > >> + > >> +#define DW_DP_AUD_CONFIG1 0x0400 > >> +#define AUDIO_TIMESTAMP_VERSION_NUM GENMASK(29, 24) > >> +#define AUDIO_PACKET_ID GENMASK(23, 16) > >> +#define AUDIO_MUTE BIT(15) > >> +#define NUM_CHANNELS GENMASK(14, 12) > >> +#define HBR_MODE_ENABLE BIT(10) > >> +#define AUDIO_DATA_WIDTH GENMASK(9, 5) > >> +#define AUDIO_DATA_IN_EN GENMASK(4, 1) > >> +#define AUDIO_INF_SELECT BIT(0) > >> + > >> +#define DW_DP_SDP_VERTICAL_CTRL 0x0500 > >> +#define EN_VERTICAL_SDP BIT(2) > >> +#define EN_AUDIO_STREAM_SDP BIT(1) > >> +#define EN_AUDIO_TIMESTAMP_SDP BIT(0) > >> +#define DW_DP_SDP_HORIZONTAL_CTRL 0x0504 > >> +#define EN_HORIZONTAL_SDP BIT(2) > >> +#define DW_DP_SDP_STATUS_REGISTER 0x0508 > >> +#define DW_DP_SDP_MANUAL_CTRL 0x050c > >> +#define DW_DP_SDP_STATUS_EN 0x0510 > >> + > >> +#define DW_DP_SDP_REGISTER_BANK 0x0600 > >> +#define SDP_REGS GENMASK(31, 0) > >> + > >> +#define DW_DP_PHYIF_CTRL 0x0a00 > >> +#define PHY_WIDTH BIT(25) > >> +#define PHY_POWERDOWN GENMASK(20, 17) > >> +#define PHY_BUSY GENMASK(15, 12) > >> +#define SSC_DIS BIT(16) > >> +#define XMIT_ENABLE GENMASK(11, 8) > >> +#define PHY_LANES GENMASK(7, 6) > >> +#define PHY_RATE GENMASK(5, 4) > >> +#define TPS_SEL GENMASK(3, 0) > >> + > >> +#define DW_DP_PHY_TX_EQ 0x0a04 > >> +#define DW_DP_CUSTOMPAT0 0x0a08 > >> +#define DW_DP_CUSTOMPAT1 0x0a0c > >> +#define DW_DP_CUSTOMPAT2 0x0a10 > >> +#define DW_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET 0x0a14 > >> +#define DW_DP_PHYIF_PWRDOWN_CTRL 0x0a18 > >> + > >> +#define DW_DP_AUX_CMD 0x0b00 > >> +#define AUX_CMD_TYPE GENMASK(31, 28) > >> +#define AUX_ADDR GENMASK(27, 8) > >> +#define I2C_ADDR_ONLY BIT(4) > >> +#define AUX_LEN_REQ GENMASK(3, 0) > >> + > >> +#define DW_DP_AUX_STATUS 0x0b04 > >> +#define AUX_TIMEOUT BIT(17) > >> +#define AUX_BYTES_READ GENMASK(23, 19) > >> +#define AUX_STATUS GENMASK(7, 4) > >> + > >> +#define DW_DP_AUX_DATA0 0x0b08 > >> +#define DW_DP_AUX_DATA1 0x0b0c > >> +#define DW_DP_AUX_DATA2 0x0b10 > >> +#define DW_DP_AUX_DATA3 0x0b14 > >> + > >> +#define DW_DP_GENERAL_INTERRUPT 0x0d00 > >> +#define VIDEO_FIFO_OVERFLOW_STREAM0 BIT(6) > >> +#define AUDIO_FIFO_OVERFLOW_STREAM0 BIT(5) > >> +#define SDP_EVENT_STREAM0 BIT(4) > >> +#define AUX_CMD_INVALID BIT(3) > >> +#define HDCP_EVENT BIT(2) > >> +#define AUX_REPLY_EVENT BIT(1) > >> +#define HPD_EVENT BIT(0) > >> + > >> +#define DW_DP_GENERAL_INTERRUPT_ENABLE 0x0d04 > >> +#define HDCP_EVENT_EN BIT(2) > >> +#define AUX_REPLY_EVENT_EN BIT(1) > >> +#define HPD_EVENT_EN BIT(0) > >> + > >> +#define DW_DP_HPD_STATUS 0x0d08 > >> +#define HPD_STATE GENMASK(11, 9) > >> +#define HPD_STATUS BIT(8) > >> +#define HPD_HOT_UNPLUG BIT(2) > >> +#define HPD_HOT_PLUG BIT(1) > >> +#define HPD_IRQ BIT(0) > >> + > >> +#define DW_DP_HPD_INTERRUPT_ENABLE 0x0d0c > >> +#define HPD_UNPLUG_ERR_EN BIT(3) > >> +#define HPD_UNPLUG_EN BIT(2) > >> +#define HPD_PLUG_EN BIT(1) > >> +#define HPD_IRQ_EN BIT(0) > >> + > >> +#define DW_DP_HDCP_CFG 0x0e00 > >> +#define DPCD12PLUS BIT(7) > >> +#define CP_IRQ BIT(6) > >> +#define BYPENCRYPTION BIT(5) > >> +#define HDCP_LOCK BIT(4) > >> +#define ENCRYPTIONDISABLE BIT(3) > >> +#define ENABLE_HDCP_13 BIT(2) > >> +#define ENABLE_HDCP BIT(1) > >> + > >> +#define DW_DP_HDCP_OBS 0x0e04 > >> +#define HDCP22_RE_AUTHENTICATION_REQ BIT(31) > >> +#define HDCP22_AUTHENTICATION_FAILED BIT(30) > >> +#define HDCP22_AUTHENTICATION_SUCCESS BIT(29) > >> +#define HDCP22_CAPABLE_SINK BIT(28) > >> +#define HDCP22_SINK_CAP_CHECK_COMPLETE BIT(27) > >> +#define HDCP22_STATE GENMASK(26, 24) > >> +#define HDCP22_BOOTED BIT(23) > >> +#define HDCP13_BSTATUS GENMASK(22, 19) > >> +#define REPEATER BIT(18) > >> +#define HDCP_CAPABLE BIT(17) > >> +#define STATEE GENMASK(16, 14) > >> +#define STATEOEG GENMASK(13, 11) > >> +#define STATER GENMASK(10, 8) > >> +#define STATEA GENMASK(7, 4) > >> +#define SUBSTATEA GENMASK(3, 1) > >> +#define HDCPENGAGED BIT(0) > >> + > >> +#define DW_DP_HDCP_APIINTCLR 0x0e08 > >> +#define DW_DP_HDCP_APIINTSTAT 0x0e0c > >> +#define DW_DP_HDCP_APIINTMSK 0x0e10 > >> +#define HDCP22_GPIOINT BIT(8) > >> +#define HDCP_ENGAGED BIT(7) > >> +#define HDCP_FAILED BIT(6) > >> +#define KSVSHA1CALCDONEINT BIT(5) > >> +#define AUXRESPNACK7TIMES BIT(4) > >> +#define AUXRESPTIMEOUT BIT(3) > >> +#define AUXRESPDEFER7TIMES BIT(2) > >> +#define KSVACCESSINT BIT(0) > >> + > >> +#define DW_DP_HDCP_KSVMEMCTRL 0x0e18 > >> +#define KSVSHA1STATUS BIT(4) > >> +#define KSVMEMACCESS BIT(1) > >> +#define KSVMEMREQUEST BIT(0) > >> + > >> +#define DW_DP_HDCP_REG_BKSV0 0x3600 > >> +#define DW_DP_HDCP_REG_BKSV1 0x3604 > >> +#define DW_DP_HDCP_REG_ANCONF 0x3608 > >> +#define AN_BYPASS BIT(0) > >> + > >> +#define DW_DP_HDCP_REG_AN0 0x360c > >> +#define DW_DP_HDCP_REG_AN1 0x3610 > >> +#define DW_DP_HDCP_REG_RMLCTL 0x3614 > >> +#define ODPK_DECRYPT_ENABLE BIT(0) > >> + > >> +#define DW_DP_HDCP_REG_RMLSTS 0x3618 > >> +#define IDPK_WR_OK_STS BIT(6) > >> +#define IDPK_DATA_INDEX GENMASK(5, 0) > >> +#define DW_DP_HDCP_REG_SEED 0x361c > >> +#define DW_DP_HDCP_REG_DPK0 0x3620 > >> +#define DW_DP_HDCP_REG_DPK1 0x3624 > >> +#define DW_DP_HDCP22_GPIOSTS 0x3628 > >> +#define DW_DP_HDCP22_GPIOCHNGSTS 0x362c > >> +#define DW_DP_HDCP_REG_DPK_CRC 0x3630 > >> + > >> +#define DW_DP_MAX_REGISTER DW_DP_HDCP_REG_DPK_CRC > >> + > >> +#define SDP_REG_BANK_SIZE 16 > >> + > >> +struct dw_dp_link_caps { > >> + bool enhanced_framing; > >> + bool tps3_supported; > >> + bool tps4_supported; > >> + bool fast_training; > >> + bool channel_coding; > >> + bool ssc; > >> +}; > >> + > >> +struct dw_dp_link_train_set { > >> + unsigned int voltage_swing[4]; > >> + unsigned int pre_emphasis[4]; > >> + bool voltage_max_reached[4]; > >> + bool pre_max_reached[4]; > >> +}; > >> + > >> +struct dw_dp_link_train { > >> + struct dw_dp_link_train_set request; > >> + struct dw_dp_link_train_set adjust; > >> + bool clock_recovered; > >> + bool channel_equalized; > >> +}; > >> + > >> +struct dw_dp_link { > >> + u8 dpcd[DP_RECEIVER_CAP_SIZE]; > >> + unsigned char revision; > >> + unsigned int rate; > >> + unsigned int lanes; > >> + u8 sink_count; > >> + u8 vsc_sdp_supported; > >> + struct dw_dp_link_caps caps; > >> + struct dw_dp_link_train train; > >> + struct drm_dp_desc desc; > >> +}; > >> + > >> +struct dw_dp_video { > >> + struct drm_display_mode mode; > >> + u8 video_mapping; > >> + u8 pixel_mode; > >> + u8 color_format; > >> + u8 bpc; > >> + u8 bpp; > >> +}; > >> + > >> +struct dw_dp_sdp { > >> + struct dp_sdp_header header; > >> + u8 db[32]; > > > >struct dp_sdp, please > > Ok, will do. > > > > >> + unsigned long flags; > >> +}; > >> + > >> +struct dw_dp_hotplug { > >> + bool long_hpd; > >> +}; > >> + > >> +struct dw_dp { > >> + struct drm_bridge bridge; > >> + struct device *dev; > >> + struct regmap *regmap; > >> + struct phy *phy; > >> + struct clk *apb_clk; > >> + struct clk *aux_clk; > >> + struct clk *i2s_clk; > >> + struct clk *spdif_clk; > >> + struct clk *hdcp_clk; > >> + struct reset_control *rstc; > >> + struct completion complete; > >> + int irq; > >> + struct work_struct hpd_work; > >> + struct dw_dp_hotplug hotplug; > >> + struct mutex irq_lock; > >> + > >> + struct drm_dp_aux aux; > >> + > >> + struct dw_dp_link link; > >> + struct dw_dp_video video; > >> + const struct dw_dp_plat_data *plat_data; > >> + > >> + DECLARE_BITMAP(sdp_reg_bank, SDP_REG_BANK_SIZE); > >> +}; > >> + > >> +enum { > >> + DW_DP_RGB_6BIT, > >> + DW_DP_RGB_8BIT, > >> + DW_DP_RGB_10BIT, > >> + DW_DP_RGB_12BIT, > >> + DW_DP_RGB_16BIT, > >> + DW_DP_YCBCR444_8BIT, > >> + DW_DP_YCBCR444_10BIT, > >> + DW_DP_YCBCR444_12BIT, > >> + DW_DP_YCBCR444_16BIT, > >> + DW_DP_YCBCR422_8BIT, > >> + DW_DP_YCBCR422_10BIT, > >> + DW_DP_YCBCR422_12BIT, > >> + DW_DP_YCBCR422_16BIT, > >> + DW_DP_YCBCR420_8BIT, > >> + DW_DP_YCBCR420_10BIT, > >> + DW_DP_YCBCR420_12BIT, > >> + DW_DP_YCBCR420_16BIT, > >> +}; > >> + > >> +enum { > >> + DW_DP_MP_SINGLE_PIXEL, > >> + DW_DP_MP_DUAL_PIXEL, > >> + DW_DP_MP_QUAD_PIXEL, > >> +}; > >> + > >> +enum { > >> + DW_DP_SDP_VERTICAL_INTERVAL = BIT(0), > >> + DW_DP_SDP_HORIZONTAL_INTERVAL = BIT(1), > >> +}; > >> + > >> +enum { > >> + DW_DP_HPD_STATE_IDLE, > >> + DW_DP_HPD_STATE_UNPLUG, > >> + DP_DP_HPD_STATE_TIMEOUT = 4, > >> + DW_DP_HPD_STATE_PLUG = 7 > >> +}; > >> + > >> +enum { > >> + DW_DP_PHY_PATTERN_NONE, > >> + DW_DP_PHY_PATTERN_TPS_1, > >> + DW_DP_PHY_PATTERN_TPS_2, > >> + DW_DP_PHY_PATTERN_TPS_3, > >> + DW_DP_PHY_PATTERN_TPS_4, > >> + DW_DP_PHY_PATTERN_SERM, > >> + DW_DP_PHY_PATTERN_PBRS7, > >> + DW_DP_PHY_PATTERN_CUSTOM_80BIT, > >> + DW_DP_PHY_PATTERN_CP2520_1, > >> + DW_DP_PHY_PATTERN_CP2520_2, > >> +}; > >> + > >> +struct dw_dp_output_format { > >> + u32 bus_format; > >> + u32 color_format; > >> + u8 video_mapping; > >> + u8 bpc; > >> + u8 bpp; > >> +}; > >> + > >> +static const struct dw_dp_output_format dw_dp_output_formats[] = { > >> + { MEDIA_BUS_FMT_RGB101010_1X30, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_10BIT, 10, 30 }, > >> + { MEDIA_BUS_FMT_RGB888_1X24, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_8BIT, 8, 24 }, > >> + { MEDIA_BUS_FMT_YUV10_1X30, DRM_COLOR_FORMAT_YCBCR444, DW_DP_YCBCR444_10BIT, 10, 30 }, > >> + { MEDIA_BUS_FMT_YUV8_1X24, DRM_COLOR_FORMAT_YCBCR444, DW_DP_YCBCR444_8BIT, 8, 24}, > >> + { MEDIA_BUS_FMT_YUYV10_1X20, DRM_COLOR_FORMAT_YCBCR422, DW_DP_YCBCR422_10BIT, 10, 20 }, > >> + { MEDIA_BUS_FMT_YUYV8_1X16, DRM_COLOR_FORMAT_YCBCR422, DW_DP_YCBCR422_8BIT, 8, 16 }, > >> + { MEDIA_BUS_FMT_UYYVYY10_0_5X30, DRM_COLOR_FORMAT_YCBCR420, DW_DP_YCBCR420_10BIT, 10, 15 }, > >> + { MEDIA_BUS_FMT_UYYVYY8_0_5X24, DRM_COLOR_FORMAT_YCBCR420, DW_DP_YCBCR420_8BIT, 8, 12 }, > >> + { MEDIA_BUS_FMT_RGB666_1X24_CPADHI, DRM_COLOR_FORMAT_RGB444, DW_DP_RGB_6BIT, 6, 18 }, > >> +}; > >> + > >> +static const struct dw_dp_output_format *dw_dp_get_output_format(u32 bus_format) > >> +{ > >> + unsigned int i; > >> + > >> + for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) > >> + if (dw_dp_output_formats[i].bus_format == bus_format) > >> + return &dw_dp_output_formats[i]; > >> + > >> + return &dw_dp_output_formats[1]; > > > >Why? Shouldn't it be NULL? > > At first, we considered RGB888_1X24 to be universally applicable across all scenarios, > but upon re-evaluation, this assumption proved inaccurate. Specifically, RGB888 may > encounter bandwidth limitations in high-resolution or high-refresh-rate environments > > So returning NULL would be more appropriate here. > > > > >> +} > >> + > >> +static inline struct dw_dp *bridge_to_dp(struct drm_bridge *b) > >> +{ > >> + return container_of(b, struct dw_dp, bridge); > >> +} > >> + > >> +static inline void dw_dp_phy_set_pattern(struct dw_dp *dp, u32 pattern) > >> +{ > >> + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, TPS_SEL, > >> + FIELD_PREP(TPS_SEL, pattern)); > >> +} > >> + > >> +static void dw_dp_phy_xmit_enable(struct dw_dp *dp, u32 lanes) > >> +{ > >> + u32 xmit_enable; > >> + > >> + switch (lanes) { > >> + case 4: > >> + case 2: > >> + case 1: > >> + xmit_enable = GENMASK(lanes - 1, 0); > >> + break; > >> + case 0: > >> + default: > >> + xmit_enable = 0; > >> + break; > >> + } > >> + > >> + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, XMIT_ENABLE, > >> + FIELD_PREP(XMIT_ENABLE, xmit_enable)); > >> +} > >> + > >> +static bool dw_dp_bandwidth_ok(struct dw_dp *dp, > >> + const struct drm_display_mode *mode, u32 bpp, > >> + unsigned int lanes, unsigned int rate) > >> +{ > >> + u32 max_bw, req_bw; > >> + > >> + req_bw = mode->clock * bpp / 8; > >> + max_bw = lanes * rate; > >> + if (req_bw > max_bw) > >> + return false; > >> + > >> + return true; > >> +} > >> + > >> +static bool dw_dp_hpd_detect(struct dw_dp *dp) > >> +{ > >> + u32 value; > >> + > >> + regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value); > >> + > >> + return FIELD_GET(HPD_STATE, value) == DW_DP_HPD_STATE_PLUG; > >> +} > >> + > >> +static void dw_dp_link_caps_reset(struct dw_dp_link_caps *caps) > >> +{ > >> + caps->enhanced_framing = false; > >> + caps->tps3_supported = false; > >> + caps->tps4_supported = false; > >> + caps->fast_training = false; > >> + caps->channel_coding = false; > >> +} > >> + > >> +static void dw_dp_link_reset(struct dw_dp_link *link) > >> +{ > >> + link->vsc_sdp_supported = 0; > >> + link->sink_count = 0; > >> + link->revision = 0; > >> + link->rate = 0; > >> + link->lanes = 0; > >> + > >> + dw_dp_link_caps_reset(&link->caps); > >> + memset(link->dpcd, 0, sizeof(link->dpcd)); > >> +} > >> + > >> +static int dw_dp_link_power_up(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_link *link = &dp->link; > >> + int ret; > >> + u8 value; > >> + > >> + if (link->revision < DP_DPCD_REV_11) > >> + return 0; > >> + > >> + ret = drm_dp_dpcd_readb(&dp->aux, DP_SET_POWER, &value); > >> + if (ret < 0) > >> + return ret; > >> + > >> + value &= ~DP_SET_POWER_MASK; > >> + value |= DP_SET_POWER_D0; > >> + > >> + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, value); > >> + if (ret < 0) > >> + return ret; > >> + > >> + usleep_range(1000, 2000); > >> + > >> + return 0; > >> +} > >> + > >> +static int dw_dp_link_power_down(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_link *link = &dp->link; > >> + int ret; > >> + u8 value; > >> + > >> + if (link->revision < DP_DPCD_REV_11) > >> + return 0; > >> + > >> + ret = drm_dp_dpcd_readb(&dp->aux, DP_SET_POWER, &value); > >> + if (ret < 0) > >> + return ret; > >> + > >> + value &= ~DP_SET_POWER_MASK; > >> + value |= DP_SET_POWER_D3; > >> + > >> + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, value); > >> + if (ret < 0) > >> + return ret; > >> + > > > >Would you please mind pulling these two functions to DRM DP helpers? > >There are enough users to make this into a common code. > > Yes, when developing this patch, I had the same realization. > The processing logic for MSM/Cadence/ANX6345/tegra follows > a similar approach. > I had originally planned to defer this refactoring until the series of patches is accepted. > Additionally, I have other pending tasks in my TODO list, such as fully decoupling the > bridge driver and encoder/connector components across all Rockchip platforms。 > But I prioritize completing these foundational features before go to next steps, > Is this acceptable ? You can add necessary helpers as one of the first patches in the series. Convert one or several platforms that use drm-misc (i.e. not MSM, AMD, i915, nouveau) and send conversion of the rest of the platforms as followups. > > > > >> + return 0; > >> +} > >> + > >> +static bool dw_dp_has_sink_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE], > >> + const struct drm_dp_desc *desc) > >> +{ > >> + return 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); > > > > > >isn't it drm_dp_read_sink_count_cap() ? > > I will have a try, since this drm_dp_read_sink_count_cap requires a connector parameter, > I need to figure out how to get the connector here. Maybe Maxime's series will you here, see [1]. [1] https://lore.kernel.org/dri-devel/20250225-bridge-connector-v4-4-7ecb07b09cad@kernel.org/ > > > > >> +} > >> + > >> +static int dw_dp_link_parse(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_link *link = &dp->link; > >> + u8 dpcd; > >> + int ret; > >> + > >> + dw_dp_link_reset(link); > >> + > >> + ret = drm_dp_read_dpcd_caps(&dp->aux, link->dpcd); > >> + if (ret < 0) > >> + return ret; > >> + > >> + drm_dp_read_desc(&dp->aux, &link->desc, drm_dp_is_branch(link->dpcd)); > >> + > >> + if (dw_dp_has_sink_count(link->dpcd, &link->desc)) { > >> + ret = drm_dp_read_sink_count(&dp->aux); > >> + if (ret < 0) > >> + return ret; > >> + > >> + link->sink_count = ret; > >> + > >> + /* Dongle connected, but no display */ > >> + if (!link->sink_count) > >> + return -ENODEV; > >> + } > >> + > >> + ret = drm_dp_dpcd_readb(&dp->aux, DP_DPRX_FEATURE_ENUMERATION_LIST, &dpcd); > >> + if (ret < 0) > >> + return ret; > >> + > >> + link->vsc_sdp_supported = !!(dpcd & DP_VSC_SDP_EXT_FOR_COLORIMETRY_SUPPORTED); > >> + > >> + link->revision = link->dpcd[DP_DPCD_REV]; > >> + link->rate = min_t(u32, min(dp->plat_data->max_link_rate, > >> + dp->phy->attrs.max_link_rate * 100), > >> + drm_dp_max_link_rate(link->dpcd)); > >> + link->lanes = min_t(u8, phy_get_bus_width(dp->phy), > >> + drm_dp_max_lane_count(link->dpcd)); > >> + > >> + link->caps.enhanced_framing = drm_dp_enhanced_frame_cap(link->dpcd); > >> + link->caps.tps3_supported = drm_dp_tps3_supported(link->dpcd); > >> + link->caps.tps4_supported = drm_dp_tps4_supported(link->dpcd); > >> + link->caps.fast_training = drm_dp_fast_training_cap(link->dpcd); > >> + link->caps.channel_coding = drm_dp_channel_coding_supported(link->dpcd); > >> + link->caps.ssc = !!(link->dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5); > >> + > >> + return 0; > >> +} > >> + > >> +static int dw_dp_phy_update_vs_emph(struct dw_dp *dp, unsigned int rate, unsigned int lanes, > >> + struct dw_dp_link_train_set *train_set) > >> +{ > >> + union phy_configure_opts phy_cfg; > >> + unsigned int *vs, *pe; > >> + u8 buf[4]; > >> + int i, ret; > >> + > >> + vs = train_set->voltage_swing; > >> + pe = train_set->pre_emphasis; > >> + > >> + for (i = 0; i < lanes; i++) { > >> + phy_cfg.dp.voltage[i] = vs[i]; > >> + phy_cfg.dp.pre[i] = pe[i]; > >> + } > >> + > >> + phy_cfg.dp.lanes = lanes; > >> + phy_cfg.dp.link_rate = rate / 100; > >> + phy_cfg.dp.set_lanes = false; > >> + phy_cfg.dp.set_rate = false; > > > >You don't need to set lanes / link_rate if set_lanes and set_rate is > >false. > This is because the rk_udphy_dp_phy_configure function first verifies the rate and lane settings, > treating any uninitialized parameters as invalid configurations I think this breaks the API. One should not be verifying the fields which are not to be set. Please update the PHY driver. > > > > >> + phy_cfg.dp.set_voltages = true; > >> + > >> + ret = phy_configure(dp->phy, &phy_cfg); > >> + if (ret) > >> + return ret; > >> + > >> + for (i = 0; i < lanes; i++) { > >> + buf[i] = (vs[i] << DP_TRAIN_VOLTAGE_SWING_SHIFT) | > >> + (pe[i] << DP_TRAIN_PRE_EMPHASIS_SHIFT); > >> + if (train_set->voltage_max_reached[i]) > >> + buf[i] |= DP_TRAIN_MAX_SWING_REACHED; > >> + if (train_set->pre_max_reached[i]) > >> + buf[i] |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; > >> + } > >> + > >> + ret = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, buf, lanes); > >> + if (ret < 0) > >> + return ret; > >> + > >> + return 0; > >> +} > >> + > >> +static int dw_dp_link_train_update_vs_emph(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_link *link = &dp->link; > >> + struct dw_dp_link_train_set *request = &link->train.request; > >> + > >> + return dw_dp_phy_update_vs_emph(dp, dp->link.rate, dp->link.lanes, request); > > > >Inline it mayeb? > > Okay, will do。 > > > >> +} > >> + > >> +static int dw_dp_phy_configure(struct dw_dp *dp, unsigned int rate, > >> + unsigned int lanes, bool ssc) > >> +{ > >> + union phy_configure_opts phy_cfg; > >> + int ret; > >> + > >> + /* Move PHY to P3 */ > >> + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_POWERDOWN, > >> + FIELD_PREP(PHY_POWERDOWN, 0x3)); > >> + > >> + phy_cfg.dp.lanes = lanes; > >> + phy_cfg.dp.link_rate = rate / 100; > >> + phy_cfg.dp.ssc = ssc; > >> + phy_cfg.dp.set_lanes = true; > >> + phy_cfg.dp.set_rate = true; > >> + phy_cfg.dp.set_voltages = false; > >> + ret = phy_configure(dp->phy, &phy_cfg); > >> + if (ret) > >> + return ret; > >> + > >> + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_LANES, > >> + FIELD_PREP(PHY_LANES, lanes / 2)); > >> + > >> + /* Move PHY to P0 */ > >> + regmap_update_bits(dp->regmap, DW_DP_PHYIF_CTRL, PHY_POWERDOWN, > >> + FIELD_PREP(PHY_POWERDOWN, 0x0)); > >> + > >> + dw_dp_phy_xmit_enable(dp, lanes); > >> + > >> + return 0; > >> +} > >> + > >> +static int dw_dp_link_configure(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_link *link = &dp->link; > >> + u8 buf[2]; > >> + int ret; > >> + > >> + ret = dw_dp_phy_configure(dp, link->rate, link->lanes, link->caps.ssc); > >> + if (ret) > >> + return ret; > >> + > >> + buf[0] = drm_dp_link_rate_to_bw_code(link->rate); > >> + buf[1] = link->lanes; > >> + > >> + if (link->caps.enhanced_framing) { > >> + buf[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; > >> + regmap_update_bits(dp->regmap, DW_DP_CCTL, ENHANCE_FRAMING_EN, > >> + FIELD_PREP(ENHANCE_FRAMING_EN, 1)); > >> + } else { > >> + regmap_update_bits(dp->regmap, DW_DP_CCTL, ENHANCE_FRAMING_EN, > >> + FIELD_PREP(ENHANCE_FRAMING_EN, 0)); > >> + } > >> + > >> + ret = drm_dp_dpcd_write(&dp->aux, DP_LINK_BW_SET, buf, sizeof(buf)); > >> + if (ret < 0) > >> + return ret; > >> + > >> + buf[0] = link->caps.ssc ? DP_SPREAD_AMP_0_5 : 0; > >> + buf[1] = link->caps.channel_coding ? DP_SET_ANSI_8B10B : 0; > >> + > >> + ret = drm_dp_dpcd_write(&dp->aux, DP_DOWNSPREAD_CTRL, buf, sizeof(buf)); > >> + if (ret < 0) > >> + return ret; > >> + > >> + return 0; > >> +} > >> + > >> +static void dw_dp_link_train_init(struct dw_dp_link_train *train) > >> +{ > >> + struct dw_dp_link_train_set *request = &train->request; > >> + struct dw_dp_link_train_set *adjust = &train->adjust; > >> + unsigned int i; > >> + > >> + for (i = 0; i < 4; i++) { > >> + request->voltage_swing[i] = 0; > >> + adjust->voltage_swing[i] = 0; > >> + > >> + request->pre_emphasis[i] = 0; > >> + adjust->pre_emphasis[i] = 0; > >> + > >> + request->voltage_max_reached[i] = false; > >> + adjust->voltage_max_reached[i] = false; > >> + > >> + request->pre_max_reached[i] = false; > >> + adjust->pre_max_reached[i] = false; > >> + } > >> + > >> + train->clock_recovered = false; > >> + train->channel_equalized = false; > >> +} > >> + > >> +static bool dw_dp_link_train_valid(const struct dw_dp_link_train *train) > >> +{ > >> + return train->clock_recovered && train->channel_equalized; > >> +} > >> + > >> +static int dw_dp_link_train_set_pattern(struct dw_dp *dp, u32 pattern) > >> +{ > >> + u8 buf = 0; > >> + int ret; > >> + > >> + if (pattern && pattern != DP_TRAINING_PATTERN_4) { > >> + buf |= DP_LINK_SCRAMBLING_DISABLE; > >> + > >> + regmap_update_bits(dp->regmap, DW_DP_CCTL, SCRAMBLE_DIS, > >> + FIELD_PREP(SCRAMBLE_DIS, 1)); > >> + } else { > >> + regmap_update_bits(dp->regmap, DW_DP_CCTL, SCRAMBLE_DIS, > >> + FIELD_PREP(SCRAMBLE_DIS, 0)); > >> + } > >> + > >> + switch (pattern) { > >> + case DP_TRAINING_PATTERN_DISABLE: > >> + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_NONE); > >> + break; > >> + case DP_TRAINING_PATTERN_1: > >> + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_1); > >> + break; > >> + case DP_TRAINING_PATTERN_2: > >> + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_2); > >> + break; > >> + case DP_TRAINING_PATTERN_3: > >> + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_3); > >> + break; > >> + case DP_TRAINING_PATTERN_4: > >> + dw_dp_phy_set_pattern(dp, DW_DP_PHY_PATTERN_TPS_4); > >> + break; > >> + default: > >> + return -EINVAL; > >> + } > >> + > >> + ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, > >> + buf | pattern); > >> + if (ret < 0) > >> + return ret; > >> + > >> + return 0; > >> +} > >> + > >> +static u8 dw_dp_voltage_max(u8 preemph) > >> +{ > >> + switch (preemph & DP_TRAIN_PRE_EMPHASIS_MASK) { > >> + case DP_TRAIN_PRE_EMPH_LEVEL_0: > >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_3; > >> + case DP_TRAIN_PRE_EMPH_LEVEL_1: > >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_2; > >> + case DP_TRAIN_PRE_EMPH_LEVEL_2: > >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_1; > >> + case DP_TRAIN_PRE_EMPH_LEVEL_3: > >> + default: > >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_0; > >> + } > >> +} > >> + > >> +static void dw_dp_link_get_adjustments(struct dw_dp_link *link, > >> + u8 status[DP_LINK_STATUS_SIZE]) > >> +{ > >> + struct dw_dp_link_train_set *adjust = &link->train.adjust; > >> + u8 v = 0; > >> + u8 p = 0; > >> + unsigned int i; > >> + > >> + for (i = 0; i < link->lanes; i++) { > >> + v = drm_dp_get_adjust_request_voltage(status, i); > >> + p = drm_dp_get_adjust_request_pre_emphasis(status, i); > >> + if (p >= DP_TRAIN_PRE_EMPH_LEVEL_3) { > >> + adjust->pre_emphasis[i] = DP_TRAIN_PRE_EMPH_LEVEL_3 >> > >> + DP_TRAIN_PRE_EMPHASIS_SHIFT; > >> + adjust->pre_max_reached[i] = true; > >> + } else { > >> + adjust->pre_emphasis[i] = p >> DP_TRAIN_PRE_EMPHASIS_SHIFT; > >> + adjust->pre_max_reached[i] = false; > >> + } > >> + v = min(v, dw_dp_voltage_max(p)); > >> + if (v >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3) { > >> + adjust->voltage_swing[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_3 >> > >> + DP_TRAIN_VOLTAGE_SWING_SHIFT; > >> + adjust->voltage_max_reached[i] = true; > >> + } else { > >> + adjust->voltage_swing[i] = v >> DP_TRAIN_VOLTAGE_SWING_SHIFT; > >> + adjust->voltage_max_reached[i] = false; > >> + } > >> + } > >> +} > >> + > >> +static void dw_dp_link_train_adjust(struct dw_dp_link_train *train) > >> +{ > >> + struct dw_dp_link_train_set *request = &train->request; > >> + struct dw_dp_link_train_set *adjust = &train->adjust; > >> + unsigned int i; > >> + > >> + for (i = 0; i < 4; i++) { > > > >Shouldn't it be a loop up to link->lanes? > > Yeah, this makes sense。 > > > > > >> + if (request->voltage_swing[i] != adjust->voltage_swing[i]) > >> + request->voltage_swing[i] = adjust->voltage_swing[i]; > >> + if (request->voltage_max_reached[i] != adjust->voltage_max_reached[i]) > >> + request->voltage_max_reached[i] = adjust->voltage_max_reached[i]; > >> + } > >> + > >> + for (i = 0; i < 4; i++) { > >> + if (request->pre_emphasis[i] != adjust->pre_emphasis[i]) > >> + request->pre_emphasis[i] = adjust->pre_emphasis[i]; > >> + if (request->pre_max_reached[i] != adjust->pre_max_reached[i]) > >> + request->pre_max_reached[i] = adjust->pre_max_reached[i]; > > > >Why do you need separate request and adjustment structs? > > I need to think about this issue further and will confirm how to proceed later. Thanks! I really like your link training implementation, it is pretty clear. > > > > > >> + } > >> +} > >> + > >> +static int dw_dp_link_clock_recovery(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_link *link = &dp->link; > >> + u8 status[DP_LINK_STATUS_SIZE]; > >> + unsigned int tries = 0; > >> + int ret; > >> + > >> + ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1); > >> + if (ret) > >> + return ret; > >> + > >> + for (;;) { > >> + ret = dw_dp_link_train_update_vs_emph(dp); > >> + if (ret) > >> + return ret; > >> + > >> + drm_dp_link_train_clock_recovery_delay(&dp->aux, link->dpcd); > >> + > >> + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); > >> + if (ret < 0) { > >> + dev_err(dp->dev, "failed to read link status: %d\n", ret); > >> + return ret; > >> + } > >> + > >> + if (drm_dp_clock_recovery_ok(status, link->lanes)) { > >> + link->train.clock_recovered = true; > >> + break; > >> + } > >> + > >> + dw_dp_link_get_adjustments(link, status); > >> + > >> + if (link->train.request.voltage_swing[0] == > >> + link->train.adjust.voltage_swing[0]) > > > >Should this take all lanes to account? I think it might be posssible to > >drop the adjust / request split and adjust tries in > >dw_dp_link_get_adjustments() instead. > > Let me figure it out later。 > > > > >> + tries++; > >> + else > >> + tries = 0; > >> + > >> + if (tries == 5) > >> + break; > >> + > >> + dw_dp_link_train_adjust(&link->train); > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +static int dw_dp_link_channel_equalization(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_link *link = &dp->link; > >> + u8 status[DP_LINK_STATUS_SIZE], pattern; > >> + unsigned int tries; > >> + int ret; > >> + > >> + if (link->caps.tps4_supported) > >> + pattern = DP_TRAINING_PATTERN_4; > >> + else if (link->caps.tps3_supported) > >> + pattern = DP_TRAINING_PATTERN_3; > >> + else > >> + pattern = DP_TRAINING_PATTERN_2; > >> + ret = dw_dp_link_train_set_pattern(dp, pattern); > >> + if (ret) > >> + return ret; > >> + > >> + for (tries = 1; tries < 5; tries++) { > >> + ret = dw_dp_link_train_update_vs_emph(dp); > >> + if (ret) > >> + return ret; > >> + > >> + drm_dp_link_train_channel_eq_delay(&dp->aux, link->dpcd); > >> + > >> + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); > >> + if (ret < 0) > >> + return ret; > >> + > >> + if (!drm_dp_clock_recovery_ok(status, link->lanes)) { > >> + dev_err(dp->dev, "clock recovery lost while equalizing channel\n"); > >> + link->train.clock_recovered = false; > >> + break; > >> + } > >> + > >> + if (drm_dp_channel_eq_ok(status, link->lanes)) { > >> + link->train.channel_equalized = true; > >> + break; > >> + } > >> + > >> + dw_dp_link_get_adjustments(link, status); > >> + dw_dp_link_train_adjust(&link->train); > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +static int dw_dp_link_downgrade(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_link *link = &dp->link; > >> + struct dw_dp_video *video = &dp->video; > >> + > >> + switch (link->rate) { > >> + case 162000: > >> + return -EINVAL; > >> + case 270000: > >> + link->rate = 162000; > >> + break; > >> + case 540000: > >> + link->rate = 270000; > >> + break; > >> + case 810000: > >> + link->rate = 540000; > >> + break; > >> + } > >> + > >> + if (!dw_dp_bandwidth_ok(dp, &video->mode, video->bpp, link->lanes, > >> + link->rate)) > >> + return -E2BIG; > >> + > >> + return 0; > >> +} > >> + > >> +static int dw_dp_link_train_full(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_link *link = &dp->link; > >> + int ret; > >> + > >> +retry: > >> + dw_dp_link_train_init(&link->train); > >> + > >> + dev_dbg(dp->dev, "full-training link: %u lane%s at %u MHz\n", > >> + link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100); > >> + > >> + ret = dw_dp_link_configure(dp); > >> + if (ret < 0) { > >> + dev_err(dp->dev, "failed to configure DP link: %d\n", ret); > >> + return ret; > >> + } > >> + > >> + ret = dw_dp_link_clock_recovery(dp); > >> + if (ret < 0) { > >> + dev_err(dp->dev, "clock recovery failed: %d\n", ret); > >> + goto out; > >> + } > >> + > >> + if (!link->train.clock_recovered) { > >> + dev_err(dp->dev, "clock recovery failed, downgrading link\n"); > >> + > >> + ret = dw_dp_link_downgrade(dp); > >> + if (ret < 0) > >> + goto out; > >> + else > >> + goto retry; > >> + } > >> + > >> + dev_dbg(dp->dev, "clock recovery succeeded\n"); > >> + > >> + ret = dw_dp_link_channel_equalization(dp); > >> + if (ret < 0) { > >> + dev_err(dp->dev, "channel equalization failed: %d\n", ret); > >> + goto out; > >> + } > >> + > >> + if (!link->train.channel_equalized) { > >> + dev_err(dp->dev, "channel equalization failed, downgrading link\n"); > >> + > >> + ret = dw_dp_link_downgrade(dp); > >> + if (ret < 0) > >> + goto out; > >> + else > >> + goto retry; > >> + } > >> + > >> + dev_dbg(dp->dev, "channel equalization succeeded\n"); > >> + > >> +out: > >> + dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE); > >> + return ret; > >> +} > >> + > >> +static int dw_dp_link_train_fast(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_link *link = &dp->link; > >> + int ret; > >> + u8 status[DP_LINK_STATUS_SIZE]; > >> + u8 pattern; > >> + > >> + dw_dp_link_train_init(&link->train); > >> + > >> + dev_dbg(dp->dev, "fast-training link: %u lane%s at %u MHz\n", > >> + link->lanes, (link->lanes > 1) ? "s" : "", link->rate / 100); > >> + > >> + ret = dw_dp_link_configure(dp); > >> + if (ret < 0) { > >> + dev_err(dp->dev, "failed to configure DP link: %d\n", ret); > >> + return ret; > >> + } > >> + > >> + ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1); > >> + if (ret) > >> + goto out; > >> + > >> + usleep_range(500, 1000); > >> + > >> + if (link->caps.tps4_supported) > >> + pattern = DP_TRAINING_PATTERN_4; > >> + else if (link->caps.tps3_supported) > >> + pattern = DP_TRAINING_PATTERN_3; > >> + else > >> + pattern = DP_TRAINING_PATTERN_2; > >> + ret = dw_dp_link_train_set_pattern(dp, pattern); > >> + if (ret) > >> + goto out; > >> + > >> + usleep_range(500, 1000); > >> + > >> + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); > >> + if (ret < 0) { > >> + dev_err(dp->dev, "failed to read link status: %d\n", ret); > >> + goto out; > >> + } > >> + > >> + if (!drm_dp_clock_recovery_ok(status, link->lanes)) { > >> + dev_err(dp->dev, "clock recovery failed\n"); > >> + ret = -EIO; > >> + goto out; > >> + } > >> + > >> + if (!drm_dp_channel_eq_ok(status, link->lanes)) { > >> + dev_err(dp->dev, "channel equalization failed\n"); > >> + ret = -EIO; > >> + goto out; > >> + } > >> + > >> +out: > >> + dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_DISABLE); > >> + return ret; > >> +} > >> + > >> +static int dw_dp_link_train(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_link *link = &dp->link; > >> + int ret; > >> + > >> + if (link->caps.fast_training) { > >> + if (dw_dp_link_train_valid(&link->train)) { > >> + ret = dw_dp_link_train_fast(dp); > >> + if (ret < 0) > >> + dev_err(dp->dev, "fast link training failed: %d\n", ret); > >> + else > >> + return 0; > >> + } > >> + } > >> + > >> + ret = dw_dp_link_train_full(dp); > >> + if (ret < 0) { > >> + dev_err(dp->dev, "full link training failed: %d\n", ret); > >> + return ret; > >> + } > >> + > >> + return 0; > >> +} > >> + > >> +static int dw_dp_send_sdp(struct dw_dp *dp, struct dw_dp_sdp *sdp) > >> +{ > >> + const u8 *payload = sdp->db; > >> + u32 reg; > >> + int i, nr; > >> + > >> + nr = find_first_zero_bit(dp->sdp_reg_bank, SDP_REG_BANK_SIZE); > >> + if (nr < SDP_REG_BANK_SIZE) > >> + set_bit(nr, dp->sdp_reg_bank); > >> + else > >> + return -EBUSY; > >> + > >> + reg = DW_DP_SDP_REGISTER_BANK + nr * 9 * 4; > >> + > >> + /* SDP header */ > >> + regmap_write(dp->regmap, reg, get_unaligned_le32(&sdp->header)); > >> + > >> + /* SDP data payload */ > >> + for (i = 1; i < 9; i++, payload += 4) > >> + regmap_write(dp->regmap, reg + i * 4, > >> + FIELD_PREP(SDP_REGS, get_unaligned_le32(payload))); > >> + > >> + if (sdp->flags & DW_DP_SDP_VERTICAL_INTERVAL) > >> + regmap_update_bits(dp->regmap, DW_DP_SDP_VERTICAL_CTRL, > >> + EN_VERTICAL_SDP << nr, > >> + EN_VERTICAL_SDP << nr); > >> + > >> + if (sdp->flags & DW_DP_SDP_HORIZONTAL_INTERVAL) > >> + regmap_update_bits(dp->regmap, DW_DP_SDP_HORIZONTAL_CTRL, > >> + EN_HORIZONTAL_SDP << nr, > >> + EN_HORIZONTAL_SDP << nr); > >> + > >> + return 0; > >> +} > >> + > >> +static void dw_dp_vsc_sdp_pack(const struct drm_dp_vsc_sdp *vsc, > >> + struct dw_dp_sdp *sdp) > > > >Use drm_dp_vsc_sdp_pack() instead. > > Ok, I will try。 > > > > >> +{ > >> + sdp->header.HB0 = 0; > >> + sdp->header.HB1 = DP_SDP_VSC; > >> + sdp->header.HB2 = vsc->revision; > >> + sdp->header.HB3 = vsc->length; > >> + > >> + sdp->db[16] = (vsc->pixelformat & 0xf) << 4; > >> + sdp->db[16] |= vsc->colorimetry & 0xf; > >> + > >> + switch (vsc->bpc) { > >> + case 8: > >> + sdp->db[17] = 0x1; > >> + break; > >> + case 10: > >> + sdp->db[17] = 0x2; > >> + break; > >> + case 12: > >> + sdp->db[17] = 0x3; > >> + break; > >> + case 16: > >> + sdp->db[17] = 0x4; > >> + break; > >> + case 6: > >> + default: > >> + break; > >> + } > >> + > >> + if (vsc->dynamic_range == DP_DYNAMIC_RANGE_CTA) > >> + sdp->db[17] |= 0x80; > >> + > >> + sdp->db[18] = vsc->content_type & 0x7; > >> + > >> + sdp->flags |= DW_DP_SDP_VERTICAL_INTERVAL; > >> +} > >> + > >> +static int dw_dp_send_vsc_sdp(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_video *video = &dp->video; > >> + struct drm_dp_vsc_sdp vsc = {}; > >> + struct dw_dp_sdp sdp = {}; > >> + > >> + vsc.revision = 0x5; > >> + vsc.length = 0x13; > >> + > >> + switch (video->color_format) { > >> + case DRM_COLOR_FORMAT_YCBCR444: > >> + vsc.pixelformat = DP_PIXELFORMAT_YUV444; > >> + break; > >> + case DRM_COLOR_FORMAT_YCBCR420: > >> + vsc.pixelformat = DP_PIXELFORMAT_YUV420; > >> + break; > >> + case DRM_COLOR_FORMAT_YCBCR422: > >> + vsc.pixelformat = DP_PIXELFORMAT_YUV422; > >> + break; > >> + case DRM_COLOR_FORMAT_RGB444: > >> + default: > >> + vsc.pixelformat = DP_PIXELFORMAT_RGB; > >> + break; > >> + } > >> + > >> + if (video->color_format == DRM_COLOR_FORMAT_RGB444) { > >> + vsc.colorimetry = DP_COLORIMETRY_DEFAULT; > >> + vsc.dynamic_range = DP_DYNAMIC_RANGE_VESA; > >> + } else { > >> + vsc.colorimetry = DP_COLORIMETRY_BT709_YCC; > >> + vsc.dynamic_range = DP_DYNAMIC_RANGE_CTA; > >> + } > >> + > >> + vsc.bpc = video->bpc; > >> + vsc.content_type = DP_CONTENT_TYPE_NOT_DEFINED; > >> + > >> + dw_dp_vsc_sdp_pack(&vsc, &sdp); > >> + > >> + return dw_dp_send_sdp(dp, &sdp); > >> +} > >> + > >> +static int dw_dp_video_set_pixel_mode(struct dw_dp *dp, u8 pixel_mode) > >> +{ > >> + switch (pixel_mode) { > >> + case DW_DP_MP_SINGLE_PIXEL: > >> + case DW_DP_MP_DUAL_PIXEL: > >> + case DW_DP_MP_QUAD_PIXEL: > >> + break; > >> + default: > >> + return -EINVAL; > > > >Is it possible? > > This IP is configurable for single/dual/quad pixel modes。 > It's quad pixel mode on rk3588 and dual pixel mode on rk3576, > so we add this check here. My question was slightly different: is it possible to end up here in the 'default' branch? > > > > > >> + } > >> + > >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, PIXEL_MODE_SELECT, > >> + FIELD_PREP(PIXEL_MODE_SELECT, pixel_mode)); > >> + > >> + return 0; > >> +} > >> + > >> +static bool dw_dp_video_need_vsc_sdp(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_link *link = &dp->link; > >> + struct dw_dp_video *video = &dp->video; > >> + > >> + if (!link->vsc_sdp_supported) > >> + return false; > >> + > >> + if (video->color_format == DRM_COLOR_FORMAT_YCBCR420) > >> + return true; > >> + > >> + return false; > >> +} > >> + > >> +static int dw_dp_video_set_msa(struct dw_dp *dp, u8 color_format, u8 bpc, > >> + u16 vstart, u16 hstart) > >> +{ > >> + u16 misc = 0; > >> + > >> + if (dw_dp_video_need_vsc_sdp(dp)) > >> + misc |= DP_MSA_MISC_COLOR_VSC_SDP; > >> + > >> + switch (color_format) { > >> + case DRM_COLOR_FORMAT_RGB444: > >> + misc |= DP_MSA_MISC_COLOR_RGB; > >> + break; > >> + case DRM_COLOR_FORMAT_YCBCR444: > >> + misc |= DP_MSA_MISC_COLOR_YCBCR_444_BT709; > >> + break; > >> + case DRM_COLOR_FORMAT_YCBCR422: > >> + misc |= DP_MSA_MISC_COLOR_YCBCR_422_BT709; > >> + break; > >> + case DRM_COLOR_FORMAT_YCBCR420: > >> + break; > >> + default: > >> + return -EINVAL; > >> + } > >> + > >> + switch (bpc) { > >> + case 6: > >> + misc |= DP_MSA_MISC_6_BPC; > >> + break; > >> + case 8: > >> + misc |= DP_MSA_MISC_8_BPC; > >> + break; > >> + case 10: > >> + misc |= DP_MSA_MISC_10_BPC; > >> + break; > >> + case 12: > >> + misc |= DP_MSA_MISC_12_BPC; > >> + break; > >> + case 16: > >> + misc |= DP_MSA_MISC_16_BPC; > >> + break; > >> + default: > >> + return -EINVAL; > >> + } > >> + > >> + regmap_write(dp->regmap, DW_DP_VIDEO_MSA1, > >> + FIELD_PREP(VSTART, vstart) | FIELD_PREP(HSTART, hstart)); > >> + regmap_write(dp->regmap, DW_DP_VIDEO_MSA2, FIELD_PREP(MISC0, misc)); > >> + regmap_write(dp->regmap, DW_DP_VIDEO_MSA3, FIELD_PREP(MISC1, misc >> 8)); > >> + > >> + return 0; > >> +} > >> + > >> +static void dw_dp_video_disable(struct dw_dp *dp) > >> +{ > >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE, > >> + FIELD_PREP(VIDEO_STREAM_ENABLE, 0)); > >> +} > >> + > >> +static int dw_dp_video_enable(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_video *video = &dp->video; > >> + struct dw_dp_link *link = &dp->link; > >> + struct drm_display_mode *mode = &video->mode; > >> + u8 color_format = video->color_format; > >> + u8 bpc = video->bpc; > >> + u8 pixel_mode = video->pixel_mode; > >> + u8 bpp = video->bpp, init_threshold, vic; > >> + u32 hactive, hblank, h_sync_width, h_front_porch; > >> + u32 vactive, vblank, v_sync_width, v_front_porch; > >> + u32 vstart = mode->vtotal - mode->vsync_start; > >> + u32 hstart = mode->htotal - mode->hsync_start; > >> + u32 peak_stream_bandwidth, link_bandwidth; > >> + u32 average_bytes_per_tu, average_bytes_per_tu_frac; > >> + u32 ts, hblank_interval; > >> + u32 value; > >> + int ret; > >> + > >> + ret = dw_dp_video_set_pixel_mode(dp, pixel_mode); > >> + if (ret) > >> + return ret; > >> + > >> + ret = dw_dp_video_set_msa(dp, color_format, bpc, vstart, hstart); > >> + if (ret) > >> + return ret; > >> + > >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_MAPPING, > >> + FIELD_PREP(VIDEO_MAPPING, video->video_mapping)); > >> + > >> + /* Configure DW_DP_VINPUT_POLARITY_CTRL register */ > >> + value = 0; > >> + if (mode->flags & DRM_MODE_FLAG_PHSYNC) > >> + value |= FIELD_PREP(HSYNC_IN_POLARITY, 1); > >> + if (mode->flags & DRM_MODE_FLAG_PVSYNC) > >> + value |= FIELD_PREP(VSYNC_IN_POLARITY, 1); > >> + regmap_write(dp->regmap, DW_DP_VINPUT_POLARITY_CTRL, value); > >> + > >> + /* Configure DW_DP_VIDEO_CONFIG1 register */ > >> + hactive = mode->hdisplay; > >> + hblank = mode->htotal - mode->hdisplay; > >> + value = FIELD_PREP(HACTIVE, hactive) | FIELD_PREP(HBLANK, hblank); > >> + if (mode->flags & DRM_MODE_FLAG_INTERLACE) > >> + value |= FIELD_PREP(I_P, 1); > >> + vic = drm_match_cea_mode(mode); > >> + if (vic == 5 || vic == 6 || vic == 7 || > >> + vic == 10 || vic == 11 || vic == 20 || > >> + vic == 21 || vic == 22 || vic == 39 || > >> + vic == 25 || vic == 26 || vic == 40 || > >> + vic == 44 || vic == 45 || vic == 46 || > >> + vic == 50 || vic == 51 || vic == 54 || > >> + vic == 55 || vic == 58 || vic == 59) > >> + value |= R_V_BLANK_IN_OSC; > >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG1, value); > >> + > >> + /* Configure DW_DP_VIDEO_CONFIG2 register */ > >> + vblank = mode->vtotal - mode->vdisplay; > >> + vactive = mode->vdisplay; > >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG2, > >> + FIELD_PREP(VBLANK, vblank) | FIELD_PREP(VACTIVE, vactive)); > >> + > >> + /* Configure DW_DP_VIDEO_CONFIG3 register */ > >> + h_sync_width = mode->hsync_end - mode->hsync_start; > >> + h_front_porch = mode->hsync_start - mode->hdisplay; > >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG3, > >> + FIELD_PREP(H_SYNC_WIDTH, h_sync_width) | > >> + FIELD_PREP(H_FRONT_PORCH, h_front_porch)); > >> + > >> + /* Configure DW_DP_VIDEO_CONFIG4 register */ > >> + v_sync_width = mode->vsync_end - mode->vsync_start; > >> + v_front_porch = mode->vsync_start - mode->vdisplay; > >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG4, > >> + FIELD_PREP(V_SYNC_WIDTH, v_sync_width) | > >> + FIELD_PREP(V_FRONT_PORCH, v_front_porch)); > >> + > >> + /* Configure DW_DP_VIDEO_CONFIG5 register */ > >> + peak_stream_bandwidth = mode->clock * bpp / 8; > >> + link_bandwidth = (link->rate / 1000) * link->lanes; > >> + ts = peak_stream_bandwidth * 64 / link_bandwidth; > >> + average_bytes_per_tu = ts / 1000; > >> + average_bytes_per_tu_frac = ts / 100 - average_bytes_per_tu * 10; > >> + if (pixel_mode == DW_DP_MP_SINGLE_PIXEL) { > >> + if (average_bytes_per_tu < 6) > >> + init_threshold = 32; > >> + else if (hblank <= 80 && color_format != DRM_COLOR_FORMAT_YCBCR420) > >> + init_threshold = 12; > >> + else if (hblank <= 40 && color_format == DRM_COLOR_FORMAT_YCBCR420) > >> + init_threshold = 3; > >> + else > >> + init_threshold = 16; > >> + } else { > >> + u32 t1 = 0, t2 = 0, t3 = 0; > >> + > >> + switch (bpc) { > >> + case 6: > >> + t1 = (4 * 1000 / 9) * link->lanes; > >> + break; > >> + case 8: > >> + if (color_format == DRM_COLOR_FORMAT_YCBCR422) { > >> + t1 = (1000 / 2) * link->lanes; > >> + } else { > >> + if (pixel_mode == DW_DP_MP_DUAL_PIXEL) > >> + t1 = (1000 / 3) * link->lanes; > >> + else > >> + t1 = (3000 / 16) * link->lanes; > >> + } > >> + break; > >> + case 10: > >> + if (color_format == DRM_COLOR_FORMAT_YCBCR422) > >> + t1 = (2000 / 5) * link->lanes; > >> + else > >> + t1 = (4000 / 15) * link->lanes; > >> + break; > >> + case 12: > >> + if (color_format == DRM_COLOR_FORMAT_YCBCR422) { > >> + if (pixel_mode == DW_DP_MP_DUAL_PIXEL) > >> + t1 = (1000 / 6) * link->lanes; > >> + else > >> + t1 = (1000 / 3) * link->lanes; > >> + } else { > >> + t1 = (2000 / 9) * link->lanes; > >> + } > >> + break; > >> + case 16: > >> + if (color_format != DRM_COLOR_FORMAT_YCBCR422 && > >> + pixel_mode == DW_DP_MP_DUAL_PIXEL) > >> + t1 = (1000 / 6) * link->lanes; > >> + else > >> + t1 = (1000 / 4) * link->lanes; > >> + break; > >> + default: > >> + return -EINVAL; > >> + } > >> + > >> + if (color_format == DRM_COLOR_FORMAT_YCBCR420) > >> + t2 = (link->rate / 4) * 1000 / (mode->clock / 2); > >> + else > >> + t2 = (link->rate / 4) * 1000 / mode->clock; > >> + > >> + if (average_bytes_per_tu_frac) > >> + t3 = average_bytes_per_tu + 1; > >> + else > >> + t3 = average_bytes_per_tu; > >> + init_threshold = t1 * t2 * t3 / (1000 * 1000); > >> + if (init_threshold <= 16 || average_bytes_per_tu < 10) > >> + init_threshold = 40; > >> + } > >> + > >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG5, > >> + FIELD_PREP(INIT_THRESHOLD_HI, init_threshold >> 6) | > >> + FIELD_PREP(AVERAGE_BYTES_PER_TU_FRAC, average_bytes_per_tu_frac) | > >> + FIELD_PREP(INIT_THRESHOLD, init_threshold) | > >> + FIELD_PREP(AVERAGE_BYTES_PER_TU, average_bytes_per_tu)); > >> + > >> + /* Configure DW_DP_VIDEO_HBLANK_INTERVAL register */ > >> + hblank_interval = hblank * (link->rate / 4) / mode->clock; > >> + regmap_write(dp->regmap, DW_DP_VIDEO_HBLANK_INTERVAL, > >> + FIELD_PREP(HBLANK_INTERVAL_EN, 1) | > >> + FIELD_PREP(HBLANK_INTERVAL, hblank_interval)); > >> + > >> + /* Video stream enable */ > >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE, > >> + FIELD_PREP(VIDEO_STREAM_ENABLE, 1)); > >> + > >> + if (dw_dp_video_need_vsc_sdp(dp)) > >> + dw_dp_send_vsc_sdp(dp); > >> + > >> + return 0; > >> +} > >> + > >> +static void dw_dp_hpd_init(struct dw_dp *dp) > >> +{ > >> + /* Enable all HPD interrupts */ > >> + regmap_update_bits(dp->regmap, DW_DP_HPD_INTERRUPT_ENABLE, > >> + HPD_UNPLUG_EN | HPD_PLUG_EN | HPD_IRQ_EN, > >> + FIELD_PREP(HPD_UNPLUG_EN, 1) | > >> + FIELD_PREP(HPD_PLUG_EN, 1) | > >> + FIELD_PREP(HPD_IRQ_EN, 1)); > >> + > >> + /* Enable all top-level interrupts */ > >> + regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE, > >> + HPD_EVENT_EN, FIELD_PREP(HPD_EVENT_EN, 1)); > >> +} > >> + > >> +static void dw_dp_aux_init(struct dw_dp *dp) > >> +{ > >> + regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE, > >> + AUX_REPLY_EVENT_EN, FIELD_PREP(AUX_REPLY_EVENT_EN, 1)); > >> +} > >> + > >> +static void dw_dp_init_hw(struct dw_dp *dp) > >> +{ > >> + regmap_update_bits(dp->regmap, DW_DP_CCTL, DEFAULT_FAST_LINK_TRAIN_EN, > >> + FIELD_PREP(DEFAULT_FAST_LINK_TRAIN_EN, 0)); > >> + > >> + dw_dp_hpd_init(dp); > >> + dw_dp_aux_init(dp); > >> +} > >> + > >> +static int dw_dp_aux_write_data(struct dw_dp *dp, const u8 *buffer, size_t size) > >> +{ > >> + size_t i, j; > >> + > >> + for (i = 0; i < DIV_ROUND_UP(size, 4); i++) { > >> + size_t num = min_t(size_t, size - i * 4, 4); > >> + u32 value = 0; > >> + > >> + for (j = 0; j < num; j++) > >> + value |= buffer[i * 4 + j] << (j * 8); > >> + > >> + regmap_write(dp->regmap, DW_DP_AUX_DATA0 + i * 4, value); > >> + } > >> + > >> + return size; > >> +} > >> + > >> +static int dw_dp_aux_read_data(struct dw_dp *dp, u8 *buffer, size_t size) > >> +{ > >> + size_t i, j; > >> + > >> + for (i = 0; i < DIV_ROUND_UP(size, 4); i++) { > >> + size_t num = min_t(size_t, size - i * 4, 4); > >> + u32 value; > >> + > >> + regmap_read(dp->regmap, DW_DP_AUX_DATA0 + i * 4, &value); > >> + > >> + for (j = 0; j < num; j++) > >> + buffer[i * 4 + j] = value >> (j * 8); > >> + } > >> + > >> + return size; > >> +} > >> + > >> +static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux, > >> + struct drm_dp_aux_msg *msg) > >> +{ > >> + struct dw_dp *dp = container_of(aux, struct dw_dp, aux); > >> + unsigned long timeout = msecs_to_jiffies(10); > >> + u32 status, value; > >> + ssize_t ret = 0; > >> + > >> + if (WARN_ON(msg->size > 16)) > >> + return -E2BIG; > >> + > >> + switch (msg->request & ~DP_AUX_I2C_MOT) { > >> + case DP_AUX_NATIVE_WRITE: > >> + case DP_AUX_I2C_WRITE: > >> + case DP_AUX_I2C_WRITE_STATUS_UPDATE: > >> + ret = dw_dp_aux_write_data(dp, msg->buffer, msg->size); > >> + if (ret < 0) > >> + return ret; > >> + break; > >> + case DP_AUX_NATIVE_READ: > >> + case DP_AUX_I2C_READ: > >> + break; > >> + default: > >> + return -EINVAL; > >> + } > >> + > >> + if (msg->size > 0) > >> + value = FIELD_PREP(AUX_LEN_REQ, msg->size - 1); > >> + else > >> + value = FIELD_PREP(I2C_ADDR_ONLY, 1); > >> + value |= FIELD_PREP(AUX_CMD_TYPE, msg->request); > >> + value |= FIELD_PREP(AUX_ADDR, msg->address); > >> + regmap_write(dp->regmap, DW_DP_AUX_CMD, value); > >> + > >> + status = wait_for_completion_timeout(&dp->complete, timeout); > >> + if (!status) { > >> + dev_err(dp->dev, "timeout waiting for AUX reply\n"); > >> + return -ETIMEDOUT; > >> + } > >> + > >> + regmap_read(dp->regmap, DW_DP_AUX_STATUS, &value); > >> + if (value & AUX_TIMEOUT) > >> + return -ETIMEDOUT; > >> + > >> + msg->reply = FIELD_GET(AUX_STATUS, value); > >> + > >> + if (msg->size > 0 && msg->reply == DP_AUX_NATIVE_REPLY_ACK) { > >> + if (msg->request & DP_AUX_I2C_READ) { > >> + size_t count = FIELD_GET(AUX_BYTES_READ, value) - 1; > >> + > >> + if (count != msg->size) > >> + return -EBUSY; > >> + > >> + ret = dw_dp_aux_read_data(dp, msg->buffer, count); > >> + if (ret < 0) > >> + return ret; > >> + } > >> + } > >> + > >> + return ret; > >> +} > >> + > >> +/* > >> + * Limits for the video timing output by DP: > >> + * 1. the hfp should be 2 pixels aligned; > >> + * 2. the minimum hsync should be 9 pixel; > >> + * 3. the minimum hbp should be 16 pixel; > >> + */ > >> +static bool dw_dp_bridge_mode_fixup(struct drm_bridge *bridge, > >> + const struct drm_display_mode *mode, > >> + struct drm_display_mode *adjusted_mode) > >> +{ > >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> + int min_hbp = 16; > >> + int min_hsync = 9; > >> + > >> + if ((adjusted_mode->hsync_start - adjusted_mode->hdisplay) & 0x1) { > >> + adjusted_mode->hsync_start += 1; > >> + dev_warn(dp->dev, "hfp is not 2 pixeel aligned, fixup to aligned hfp\n"); > >> + } > >> + if (adjusted_mode->hsync_end - adjusted_mode->hsync_start < min_hsync) { > >> + adjusted_mode->hsync_end = adjusted_mode->hsync_start + min_hsync; > >> + dev_warn(dp->dev, "hsync is too narrow, fixup to min hsync:%d\n", min_hsync); > >> + } > >> + if (adjusted_mode->htotal - adjusted_mode->hsync_end < min_hbp) { > >> + adjusted_mode->htotal = adjusted_mode->hsync_end + min_hbp; > >> + dev_warn(dp->dev, "hbp is too narrow, fixup to min hbp:%d\n", min_hbp); > >> + } > >> + > >> + return true; > >> +} > >> + > >> +static enum drm_mode_status dw_dp_bridge_mode_valid(struct drm_bridge *bridge, > >> + const struct drm_display_info *info, > >> + const struct drm_display_mode *mode) > >> +{ > >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> + struct dw_dp_link *link = &dp->link; > >> + u32 min_bpp; > >> + > >> + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR420 && > >> + link->vsc_sdp_supported && > >> + (drm_mode_is_420_only(info, mode) || drm_mode_is_420_also(info, mode))) > >> + min_bpp = 12; > >> + else if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) > >> + min_bpp = 16; > >> + else if (info->color_formats & DRM_COLOR_FORMAT_RGB444) > >> + min_bpp = 18; > >> + else > >> + min_bpp = 24; > >> + > >> + if (!link->vsc_sdp_supported && > >> + drm_mode_is_420_only(info, mode)) > >> + return MODE_NO_420; > >> + > >> + if (!dw_dp_bandwidth_ok(dp, mode, min_bpp, link->lanes, link->rate)) > >> + return MODE_CLOCK_HIGH; > >> + > >> + return MODE_OK; > >> +} > >> + > >> +static void dw_dp_bridge_atomic_pre_enable(struct drm_bridge *bridge, > >> + struct drm_bridge_state *old_bridge_state) > >> +{ > >> + struct drm_atomic_state *state = old_bridge_state->base.state; > >> + struct drm_crtc_state *crtc_state = bridge->encoder->crtc->state; > >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> + struct dw_dp_video *video = &dp->video; > >> + struct drm_display_mode *m = &video->mode; > >> + struct drm_bridge_state *bridge_state; > >> + const struct dw_dp_output_format *fmt; > >> + > >> + drm_mode_copy(m, &crtc_state->adjusted_mode); > >> + > >> + bridge_state = drm_atomic_get_new_bridge_state(state, bridge); > >> + fmt = dw_dp_get_output_format(bridge_state->output_bus_cfg.format); > > > >Should it be a part of the atomic_check() instead? > > Since we have modef_fixup in this driver, which is mutually exclusive with atomic_check. You can convet .mode_fixup into .atomic_check. The latter can change the crtc_state->adjusted_mode. It is better to reject the invalid combinations from the .atomic_check rather than having to handled them later. > > > > > > >> + > >> + video->video_mapping = fmt->video_mapping; > >> + video->color_format = fmt->color_format; > >> + video->bpc = fmt->bpc; > >> + video->bpp = fmt->bpp; > >> +} > >> + > >> +static bool dw_dp_needs_link_retrain(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_link *link = &dp->link; > >> + u8 link_status[DP_LINK_STATUS_SIZE]; > >> + > >> + if (!dw_dp_link_train_valid(&link->train)) > >> + return false; > >> + > >> + if (drm_dp_dpcd_read_link_status(&dp->aux, link_status) < 0) > >> + return false; > >> + > >> + /* Retrain if Channel EQ or CR not ok */ > >> + return !drm_dp_channel_eq_ok(link_status, dp->link.lanes); > >> +} > >> + > >> +static void dw_dp_link_disable(struct dw_dp *dp) > >> +{ > >> + struct dw_dp_link *link = &dp->link; > >> + > >> + if (dw_dp_hpd_detect(dp)) > >> + dw_dp_link_power_down(dp); > >> + > >> + dw_dp_phy_xmit_enable(dp, 0); > >> + > >> + phy_power_off(dp->phy); > >> + > >> + link->train.clock_recovered = false; > >> + link->train.channel_equalized = false; > >> +} > >> + > >> +static int dw_dp_link_enable(struct dw_dp *dp) > >> +{ > >> + int ret; > >> + > >> + ret = phy_power_on(dp->phy); > >> + if (ret) > >> + return ret; > >> + > >> + ret = dw_dp_link_power_up(dp); > >> + if (ret < 0) > >> + return ret; > >> + > >> + ret = dw_dp_link_train(dp); > >> + > >> + return ret; > >> +} > >> + > >> +static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge, > >> + struct drm_bridge_state *old_state) > >> +{ > >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> + struct drm_atomic_state *state = old_state->base.state; > >> + struct drm_connector *connector; > >> + struct drm_connector_state *conn_state; > >> + int ret; > >> + > >> + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); > >> + if (!connector) { > >> + dev_err(dp->dev, "failed to get connector\n"); > >> + return; > >> + } > >> + > >> + conn_state = drm_atomic_get_new_connector_state(state, connector); > >> + if (!conn_state) { > >> + dev_err(dp->dev, "failed to get connector state\n"); > >> + return; > >> + } > >> + > >> + set_bit(0, dp->sdp_reg_bank); > >> + > >> + ret = dw_dp_link_enable(dp); > >> + if (ret < 0) { > >> + dev_err(dp->dev, "failed to enable link: %d\n", ret); > >> + return; > >> + } > >> + > >> + ret = dw_dp_video_enable(dp); > >> + if (ret < 0) { > >> + dev_err(dp->dev, "failed to enable video: %d\n", ret); > >> + return; > >> + } > >> +} > >> + > >> +static void dw_dp_reset(struct dw_dp *dp) > >> +{ > >> + int val; > >> + > >> + disable_irq(dp->irq); > >> + regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET, > >> + FIELD_PREP(CONTROLLER_RESET, 1)); > >> + udelay(10); > >> + regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET, > >> + FIELD_PREP(CONTROLLER_RESET, 0)); > >> + > >> + dw_dp_init_hw(dp); > >> + regmap_read_poll_timeout(dp->regmap, DW_DP_HPD_STATUS, val, > >> + FIELD_GET(HPD_HOT_PLUG, val), 200, 200000); > >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG); > >> + enable_irq(dp->irq); > >> +} > >> + > >> +static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge, > >> + struct drm_bridge_state *old_bridge_state) > >> +{ > >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> + > >> + dw_dp_video_disable(dp); > >> + dw_dp_link_disable(dp); > >> + bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE); > >> + dw_dp_reset(dp); > >> +} > >> + > >> +static bool dw_dp_hpd_detect_link(struct dw_dp *dp) > >> +{ > >> + int ret; > >> + > >> + ret = phy_power_on(dp->phy); > >> + if (ret < 0) > >> + return false; > >> + ret = dw_dp_link_parse(dp); > >> + phy_power_off(dp->phy); > >> + > >> + return !ret; > >> +} > >> + > >> +static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge) > >> +{ > >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> + > >> + if (!dw_dp_hpd_detect(dp)) > >> + return connector_status_disconnected; > >> + > >> + if (!dw_dp_hpd_detect_link(dp)) > >> + return connector_status_disconnected; > >> + > >> + return connector_status_connected; > >> +} > >> + > >> +static const struct drm_edid *dw_dp_bridge_edid_read(struct drm_bridge *bridge, > >> + struct drm_connector *connector) > >> +{ > >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> + const struct drm_edid *edid; > >> + int ret; > >> + > >> + ret = phy_power_on(dp->phy); > >> + if (ret) > >> + return NULL; > >> + > >> + edid = drm_edid_read_ddc(connector, &dp->aux.ddc); > >> + > >> + phy_power_off(dp->phy); > >> + > >> + return edid; > >> +} > >> + > >> +static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, > >> + struct drm_bridge_state *bridge_state, > >> + struct drm_crtc_state *crtc_state, > >> + struct drm_connector_state *conn_state, > >> + unsigned int *num_output_fmts) > >> +{ > >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> + struct dw_dp_link *link = &dp->link; > >> + struct drm_display_info *di = &conn_state->connector->display_info; > >> + struct drm_display_mode mode = crtc_state->mode; > >> + const struct dw_dp_output_format *fmt; > >> + u32 i, j = 0; > >> + u32 *output_fmts; > >> + > >> + *num_output_fmts = 0; > >> + > >> + output_fmts = kcalloc(ARRAY_SIZE(dw_dp_output_formats), sizeof(*output_fmts), GFP_KERNEL); > >> + if (!output_fmts) > >> + return NULL; > >> + > >> + for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) { > >> + fmt = &dw_dp_output_formats[i]; > >> + > >> + if (fmt->bpc > conn_state->max_bpc) > >> + continue; > >> + > >> + if (!(fmt->color_format & di->color_formats)) > >> + continue; > >> + > >> + if (fmt->color_format == DRM_COLOR_FORMAT_YCBCR420 && > >> + !link->vsc_sdp_supported) > >> + continue; > >> + > >> + if (fmt->color_format != DRM_COLOR_FORMAT_YCBCR420 && > >> + drm_mode_is_420_only(di, &mode)) > >> + continue; > >> + > >> + if (!dw_dp_bandwidth_ok(dp, &mode, fmt->bpp, link->lanes, link->rate)) > >> + continue; > >> + > >> + output_fmts[j++] = fmt->bus_format; > >> + } > >> + > >> + *num_output_fmts = j; > >> + > >> + return output_fmts; > >> +} > >> + > >> +static const struct drm_bridge_funcs dw_dp_bridge_funcs = { > >> + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, > >> + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, > >> + .atomic_reset = drm_atomic_helper_bridge_reset, > >> + .atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt, > >> + .atomic_get_output_bus_fmts = dw_dp_bridge_atomic_get_output_bus_fmts, > >> + .mode_fixup = dw_dp_bridge_mode_fixup, > >> + .mode_valid = dw_dp_bridge_mode_valid, > >> + .atomic_pre_enable = dw_dp_bridge_atomic_pre_enable, > >> + .atomic_enable = dw_dp_bridge_atomic_enable, > >> + .atomic_disable = dw_dp_bridge_atomic_disable, > >> + .detect = dw_dp_bridge_detect, > >> + .edid_read = dw_dp_bridge_edid_read, > >> +}; > >> + > >> +static int dw_dp_link_retrain(struct dw_dp *dp) > >> +{ > >> + struct drm_device *dev = dp->bridge.dev; > >> + struct drm_modeset_acquire_ctx ctx; > >> + int ret; > >> + > >> + if (!dw_dp_needs_link_retrain(dp)) > >> + return 0; > >> + > >> + dev_dbg(dp->dev, "Retraining link\n"); > >> + > >> + drm_modeset_acquire_init(&ctx, 0); > >> + for (;;) { > >> + ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx); > >> + if (ret != -EDEADLK) > >> + break; > > > >I think it's an error to continue in such a case. There should be a > >return after the loop if ret is not 0. > > Thank you for catching this, I will add a check fater the loop. > > > > > >> + > >> + drm_modeset_backoff(&ctx); > >> + } > >> + > >> + ret = dw_dp_link_train(dp); > >> + drm_modeset_drop_locks(&ctx); > >> + drm_modeset_acquire_fini(&ctx); > >> + > >> + return ret; > >> +} > >> + > >> +static void dw_dp_hpd_work(struct work_struct *work) > >> +{ > >> + struct dw_dp *dp = container_of(work, struct dw_dp, hpd_work); > >> + bool long_hpd; > >> + int ret; > >> + > >> + mutex_lock(&dp->irq_lock); > >> + long_hpd = dp->hotplug.long_hpd; > >> + mutex_unlock(&dp->irq_lock); > >> + > >> + dev_dbg(dp->dev, "[drm] Get hpd irq - %s\n", long_hpd ? "long" : "short"); > >> + > >> + if (!long_hpd) { > >> + if (dw_dp_needs_link_retrain(dp)) { > >> + ret = dw_dp_link_retrain(dp); > >> + if (ret) > >> + dev_warn(dp->dev, "Retrain link failed\n"); > >> + } > >> + } else { > >> + drm_helper_hpd_irq_event(dp->bridge.dev); > >> + } > >> +} > >> + > >> +static void dw_dp_handle_hpd_event(struct dw_dp *dp) > >> +{ > >> + u32 value; > >> + > >> + mutex_lock(&dp->irq_lock); > >> + regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value); > >> + > >> + if (value & HPD_IRQ) { > >> + dev_dbg(dp->dev, "IRQ from the HPD\n"); > >> + dp->hotplug.long_hpd = false; > >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_IRQ); > >> + } > >> + > >> + if (value & HPD_HOT_PLUG) { > >> + dev_dbg(dp->dev, "Hot plug detected\n"); > >> + dp->hotplug.long_hpd = true; > >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG); > >> + } > >> + > >> + if (value & HPD_HOT_UNPLUG) { > >> + dev_dbg(dp->dev, "Unplug detected\n"); > >> + dp->hotplug.long_hpd = true; > >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_UNPLUG); > >> + } > >> + mutex_unlock(&dp->irq_lock); > >> + > >> + schedule_work(&dp->hpd_work); > >> +} > >> + > >> +static irqreturn_t dw_dp_irq(int irq, void *data) > >> +{ > >> + struct dw_dp *dp = data; > >> + u32 value; > >> + > >> + regmap_read(dp->regmap, DW_DP_GENERAL_INTERRUPT, &value); > >> + if (!value) > >> + return IRQ_NONE; > >> + > >> + if (value & HPD_EVENT) > >> + dw_dp_handle_hpd_event(dp); > >> + > >> + if (value & AUX_REPLY_EVENT) { > >> + regmap_write(dp->regmap, DW_DP_GENERAL_INTERRUPT, AUX_REPLY_EVENT); > >> + complete(&dp->complete); > >> + } > >> + > >> + return IRQ_HANDLED; > >> +} > >> + > >> +static const struct regmap_range dw_dp_readable_ranges[] = { > >> + regmap_reg_range(DW_DP_VERSION_NUMBER, DW_DP_ID), > >> + regmap_reg_range(DW_DP_CONFIG_REG1, DW_DP_CONFIG_REG3), > >> + regmap_reg_range(DW_DP_CCTL, DW_DP_SOFT_RESET_CTRL), > >> + regmap_reg_range(DW_DP_VSAMPLE_CTRL, DW_DP_VIDEO_HBLANK_INTERVAL), > >> + regmap_reg_range(DW_DP_AUD_CONFIG1, DW_DP_AUD_CONFIG1), > >> + regmap_reg_range(DW_DP_SDP_VERTICAL_CTRL, DW_DP_SDP_STATUS_EN), > >> + regmap_reg_range(DW_DP_PHYIF_CTRL, DW_DP_PHYIF_PWRDOWN_CTRL), > >> + regmap_reg_range(DW_DP_AUX_CMD, DW_DP_AUX_DATA3), > >> + regmap_reg_range(DW_DP_GENERAL_INTERRUPT, DW_DP_HPD_INTERRUPT_ENABLE), > >> +}; > >> + > >> +static const struct regmap_access_table dw_dp_readable_table = { > >> + .yes_ranges = dw_dp_readable_ranges, > >> + .n_yes_ranges = ARRAY_SIZE(dw_dp_readable_ranges), > >> +}; > >> + > >> +static const struct regmap_config dw_dp_regmap_config = { > >> + .reg_bits = 32, > >> + .reg_stride = 4, > >> + .val_bits = 32, > >> + .fast_io = true, > >> + .max_register = DW_DP_MAX_REGISTER, > >> + .rd_table = &dw_dp_readable_table, > >> +}; > >> + > >> +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, > >> + const struct dw_dp_plat_data *plat_data) > >> +{ > >> + struct platform_device *pdev = to_platform_device(dev); > >> + struct dw_dp *dp; > >> + struct drm_bridge *bridge; > >> + void __iomem *res; > >> + int ret; > >> + > >> + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); > >> + if (!dp) > >> + return ERR_PTR(-ENOMEM); > >> + > >> + dp->dev = dev; > >> + dp->video.pixel_mode = DW_DP_MP_QUAD_PIXEL; > >> + > >> + dp->plat_data = plat_data; > >> + bridge = &dp->bridge; > >> + mutex_init(&dp->irq_lock); > >> + INIT_WORK(&dp->hpd_work, dw_dp_hpd_work); > >> + init_completion(&dp->complete); > >> + > >> + res = devm_platform_ioremap_resource(pdev, 0); > >> + if (IS_ERR(res)) > >> + return ERR_CAST(res); > >> + > >> + dp->regmap = devm_regmap_init_mmio(dev, res, &dw_dp_regmap_config); > >> + if (IS_ERR(dp->regmap)) { > >> + dev_err_probe(dev, PTR_ERR(dp->regmap), "failed to create regmap\n"); > >> + return ERR_CAST(dp->regmap); > >> + } > >> + > >> + dp->phy = devm_of_phy_get(dev, dev->of_node, NULL); > >> + if (IS_ERR(dp->phy)) { > >> + dev_err_probe(dev, PTR_ERR(dp->phy), "failed to get phy\n"); > >> + return ERR_CAST(dp->phy); > >> + } > >> + > >> + dp->apb_clk = devm_clk_get_enabled(dev, "apb"); > >> + if (IS_ERR(dp->apb_clk)) { > >> + dev_err_probe(dev, PTR_ERR(dp->apb_clk), "failed to get apb clock\n"); > >> + return ERR_CAST(dp->apb_clk); > >> + } > >> + > >> + dp->aux_clk = devm_clk_get_enabled(dev, "aux"); > >> + if (IS_ERR(dp->aux_clk)) { > >> + dev_err_probe(dev, PTR_ERR(dp->aux_clk), "failed to get aux clock\n"); > >> + return ERR_CAST(dp->aux_clk); > >> + } > >> + > >> + dp->i2s_clk = devm_clk_get(dev, "i2s"); > >> + if (IS_ERR(dp->i2s_clk)) { > >> + dev_err_probe(dev, PTR_ERR(dp->i2s_clk), "failed to get i2s clock\n"); > >> + return ERR_CAST(dp->i2s_clk); > >> + } > >> + > >> + dp->spdif_clk = devm_clk_get(dev, "spdif"); > >> + if (IS_ERR(dp->spdif_clk)) { > >> + dev_err_probe(dev, PTR_ERR(dp->spdif_clk), "failed to get spdif clock\n"); > >> + return ERR_CAST(dp->spdif_clk); > >> + } > >> + > >> + dp->hdcp_clk = devm_clk_get(dev, "hdcp"); > >> + if (IS_ERR(dp->hdcp_clk)) { > >> + dev_err_probe(dev, PTR_ERR(dp->hdcp_clk), "failed to get hdcp clock\n"); > >> + return ERR_CAST(dp->hdcp_clk); > >> + } > >> + > >> + dp->rstc = devm_reset_control_get(dev, NULL); > >> + if (IS_ERR(dp->rstc)) { > >> + dev_err_probe(dev, PTR_ERR(dp->rstc), "failed to get reset control\n"); > >> + return ERR_CAST(dp->rstc); > >> + } > >> + > >> + dp->irq = platform_get_irq(pdev, 0); > >> + if (dp->irq < 0) > >> + return ERR_PTR(ret); > >> + > >> + ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq, > >> + IRQF_ONESHOT, dev_name(dev), dp); > > > >Are you ready to handle the IRQ here, before the bridge is filled? > > I will move to devm_request_threaded_irq after dw_dp_init_hw. > > > > > >> + if (ret) { > >> + dev_err_probe(dev, ret, "failed to request irq\n"); > >> + return ERR_PTR(ret); > >> + } > >> + > >> + bridge->of_node = dev->of_node; > >> + bridge->funcs = &dw_dp_bridge_funcs; > >> + bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; > >> + bridge->type = DRM_MODE_CONNECTOR_DisplayPort; > >> + bridge->ycbcr_420_allowed = true; > >> + bridge->vendor = "Synopsys"; > >> + bridge->product = "DW DP TX"; > > > >These two are unused > > > >> + > >> + platform_set_drvdata(pdev, dp); > > > >Unused > > I will remove them. > > Thanks again. > > > > > >> + > >> + dp->aux.dev = dev; > >> + dp->aux.drm_dev = encoder->dev; > >> + dp->aux.name = dev_name(dev); > >> + dp->aux.transfer = dw_dp_aux_transfer; > >> + ret = drm_dp_aux_register(&dp->aux); > >> + if (ret) { > >> + dev_err_probe(dev, ret, "Aux register failed\n"); > >> + return ERR_PTR(ret); > >> + } > >> + > >> + ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); > >> + if (ret) > >> + dev_err_probe(dev, ret, "Failed to attach bridge\n"); > >> + > >> + dw_dp_init_hw(dp); > >> + > >> + return dp; > >> +} > >> +MODULE_AUTHOR("Andy Yan <andyshrk@163.com>"); > >> +MODULE_DESCRIPTION("DW DP Core Library"); > >> +MODULE_LICENSE("GPL"); > >> diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h > >> new file mode 100644 > >> index 000000000000..07845f609eca > >> --- /dev/null > >> +++ b/include/drm/bridge/dw_dp.h > >> @@ -0,0 +1,19 @@ > >> +/* SPDX-License-Identifier: GPL-2.0-or-later */ > >> +/* > >> + * Copyright (c) 2025 Rockchip Electronics Co., Ltd. > >> + */ > >> + > >> +#ifndef __DW_DP__ > >> +#define __DW_DP__ > >> + > >> +struct device; > >> +struct drm_encoder; > >> +struct dw_dp; > >> + > >> +struct dw_dp_plat_data { > >> + u32 max_link_rate; > >> +}; > >> + > >> +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, > >> + const struct dw_dp_plat_data *plat_data); > >> +#endif /* __DW_DP__ */ > >> -- > >> 2.34.1 > >> > > > >-- > >With best wishes > >Dmitry -- With best wishes Dmitry ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re:Re: [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library 2025-03-02 16:21 ` Dmitry Baryshkov @ 2025-03-04 7:16 ` Andy Yan 2025-03-04 9:18 ` Dmitry Baryshkov 2025-03-04 10:16 ` Andy Yan 1 sibling, 1 reply; 32+ messages in thread From: Andy Yan @ 2025-03-04 7:16 UTC (permalink / raw) To: Dmitry Baryshkov Cc: heiko, hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan Hi Dmitry, 在 2025-03-03 00:21:06,"Dmitry Baryshkov" <dmitry.baryshkov@linaro.org> 写道: >On Sun, Mar 02, 2025 at 06:34:22PM +0800, Andy Yan wrote: >> >> >> Hi Dmitry, >> Thank you for your review。 >> Please also review my inline reply. >> >> 在 2025-03-02 02:14:19,"Dmitry Baryshkov" <dmitry.baryshkov@linaro.org> 写道: >> >On Sun, Feb 23, 2025 at 07:30:25PM +0800, Andy Yan wrote: >> >> From: Andy Yan <andy.yan@rock-chips.com> >> >> >> >> The DW DP TX Controller is compliant with the DisplayPort Specification >> >> Version 1.4 with the following features: >> >> >> >> * DisplayPort 1.4a >> >> * Main Link: 1/2/4 lanes >> >> * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps >> >> * AUX channel 1Mbps >> >> * Single Stream Transport(SST) >> >> * Multistream Transport (MST) >> >> *Type-C support (alternate mode) >> >> * HDCP 2.2, HDCP 1.3 >> >> * Supports up to 8/10 bits per color component >> >> * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0 >> >> * Pixel clock up to 594MHz >> >> * I2S, SPDIF audio interface >> >> >> >> Add library with common helpers to make it can be shared with >> >> other SoC. >> >> >> >> Signed-off-by: Andy Yan <andy.yan@rock-chips.com> >> >> >> >> drm/bridge: cleanup >> > >> >Stray line? >> >> Sorry, will be removed. >> >> > >> >> >> >> --- ........ >> >> +static int dw_dp_link_power_up(struct dw_dp *dp) >> >> +{ >> >> + struct dw_dp_link *link = &dp->link; >> >> + int ret; >> >> + u8 value; >> >> + >> >> + if (link->revision < DP_DPCD_REV_11) >> >> + return 0; >> >> + >> >> + ret = drm_dp_dpcd_readb(&dp->aux, DP_SET_POWER, &value); >> >> + if (ret < 0) >> >> + return ret; >> >> + >> >> + value &= ~DP_SET_POWER_MASK; >> >> + value |= DP_SET_POWER_D0; >> >> + >> >> + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, value); >> >> + if (ret < 0) >> >> + return ret; >> >> + >> >> + usleep_range(1000, 2000); >> >> + >> >> + return 0; >> >> +} >> >> + >> >> +static int dw_dp_link_power_down(struct dw_dp *dp) >> >> +{ >> >> + struct dw_dp_link *link = &dp->link; >> >> + int ret; >> >> + u8 value; >> >> + >> >> + if (link->revision < DP_DPCD_REV_11) >> >> + return 0; >> >> + >> >> + ret = drm_dp_dpcd_readb(&dp->aux, DP_SET_POWER, &value); >> >> + if (ret < 0) >> >> + return ret; >> >> + >> >> + value &= ~DP_SET_POWER_MASK; >> >> + value |= DP_SET_POWER_D3; >> >> + >> >> + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, value); >> >> + if (ret < 0) >> >> + return ret; >> >> + >> > >> >Would you please mind pulling these two functions to DRM DP helpers? >> >There are enough users to make this into a common code. >> >> Yes, when developing this patch, I had the same realization. >> The processing logic for MSM/Cadence/ANX6345/tegra follows >> a similar approach. >> I had originally planned to defer this refactoring until the series of patches is accepted. >> Additionally, I have other pending tasks in my TODO list, such as fully decoupling the >> bridge driver and encoder/connector components across all Rockchip platforms。 >> But I prioritize completing these foundational features before go to next steps, >> Is this acceptable ? > >You can add necessary helpers as one of the first patches in the >series. Convert one or several platforms that use drm-misc (i.e. not >MSM, AMD, i915, nouveau) and send conversion of the rest of the >platforms as followups. If pull these two functions to DRM DP helpers, I think they should be named as drm_dp_link_power_up/down ?. I found that tegra/dp.c already use this name, so I need to do a symbols rename for tegra first to avoid build error like[2]. Can the patch for Tegra be merged through drm-misc branch? [2]https://lore.kernel.org/oe-kbuild-all/202410250305.UHKDhtxy-lkp@intel.com/ > >> >> > >> >> + return 0; >> >> +} >> >> + >> >> +static bool dw_dp_has_sink_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE], >> >> + const struct drm_dp_desc *desc) >> >> +{ >> >> + return 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); >> > >> > >> >isn't it drm_dp_read_sink_count_cap() ? >> >> I will have a try, since this drm_dp_read_sink_count_cap requires a connector parameter, >> I need to figure out how to get the connector here. > >Maybe Maxime's series will you here, see [1]. > >[1] https://lore.kernel.org/dri-devel/20250225-bridge-connector-v4-4-7ecb07b09cad@kernel.org/ > >> >> > >> >> +} >> >> + >> >> +static int dw_dp_link_parse(struct dw_dp *dp) >> >> +{ >> >> + struct dw_dp_link *link = &dp->link; >> >> + u8 dpcd; >> >> + int ret; ...... >> >> + break; >> >> + } >> >> + >> >> + if (video->color_format == DRM_COLOR_FORMAT_RGB444) { >> >> + vsc.colorimetry = DP_COLORIMETRY_DEFAULT; >> >> + vsc.dynamic_range = DP_DYNAMIC_RANGE_VESA; >> >> + } else { >> >> + vsc.colorimetry = DP_COLORIMETRY_BT709_YCC; >> >> + vsc.dynamic_range = DP_DYNAMIC_RANGE_CTA; >> >> + } >> >> + >> >> + vsc.bpc = video->bpc; >> >> + vsc.content_type = DP_CONTENT_TYPE_NOT_DEFINED; >> >> + >> >> + dw_dp_vsc_sdp_pack(&vsc, &sdp); >> >> + >> >> + return dw_dp_send_sdp(dp, &sdp); >> >> +} >> >> + >> >> +static int dw_dp_video_set_pixel_mode(struct dw_dp *dp, u8 pixel_mode) >> >> +{ >> >> + switch (pixel_mode) { >> >> + case DW_DP_MP_SINGLE_PIXEL: >> >> + case DW_DP_MP_DUAL_PIXEL: >> >> + case DW_DP_MP_QUAD_PIXEL: >> >> + break; >> >> + default: >> >> + return -EINVAL; >> > >> >Is it possible? >> >> This IP is configurable for single/dual/quad pixel modes。 >> It's quad pixel mode on rk3588 and dual pixel mode on rk3576, >> so we add this check here. > >My question was slightly different: is it possible to end up here in the >'default' branch? Sorry, I think I didn't get your key point here. If someone give a invalid pixe mode(not any of SINGLE/DUAL/QUAD_PIXEL ), then we go to default branch return -ENVIAL, then the dp_bridge enable stop. This is what we want here. > >> >> >> > >> >> + } >> >> + >> >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, PIXEL_MODE_SELECT, >> >> + FIELD_PREP(PIXEL_MODE_SELECT, pixel_mode)); >> >> + >> >> + return 0; >> >> +} >> >> + >> >> +static bool dw_dp_video_need_vsc_sdp(struct dw_dp *dp) >> >> +{ >> >> + struct dw_dp_link *link = &dp->link; >> >> + struct dw_dp_video *video = &dp->video; >> >> + >> >> + if (!link->vsc_sdp_supported) >> >> + return false; >> >> + >> >> + if (video->color_format == DRM_COLOR_FORMAT_YCBCR420) >> >> + return true; >> >> + >> >> + return false; >> >> +} >> >> + >> >> +static int dw_dp_video_set_msa(struct dw_dp *dp, u8 color_format, u8 bpc, >> >> + u16 vstart, u16 hstart) >> >> +{ >> >> + u16 misc = 0; >> >> + >> >> + if (dw_dp_video_need_vsc_sdp(dp)) >> >> + misc |= DP_MSA_MISC_COLOR_VSC_SDP; >> >> + >> >> + switch (color_format) { >> >> + case DRM_COLOR_FORMAT_RGB444: >> >> + misc |= DP_MSA_MISC_COLOR_RGB; >> >> + break; >> >> + case DRM_COLOR_FORMAT_YCBCR444: >> >> + misc |= DP_MSA_MISC_COLOR_YCBCR_444_BT709; >> >> + break; >> >> + case DRM_COLOR_FORMAT_YCBCR422: >> >> + misc |= DP_MSA_MISC_COLOR_YCBCR_422_BT709; >> >> + break; >> >> + case DRM_COLOR_FORMAT_YCBCR420: >> >> + break; >> >> + default: >> >> + return -EINVAL; >> >> + } >> >> + >> >> + switch (bpc) { >> >> + case 6: >> >> + misc |= DP_MSA_MISC_6_BPC; >> >> + break; >> >> + case 8: >> >> + misc |= DP_MSA_MISC_8_BPC; >> >> + break; >> >> + case 10: >> >> + misc |= DP_MSA_MISC_10_BPC; >> >> + break; >> >> + case 12: >> >> + misc |= DP_MSA_MISC_12_BPC; >> >> + break; >> >> + case 16: >> >> + misc |= DP_MSA_MISC_16_BPC; >> >> + break; >> >> + default: >> >> + return -EINVAL; >> >> + } >> >> + >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_MSA1, >> >> + FIELD_PREP(VSTART, vstart) | FIELD_PREP(HSTART, hstart)); >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_MSA2, FIELD_PREP(MISC0, misc)); >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_MSA3, FIELD_PREP(MISC1, misc >> 8)); >> >> + >> >> + return 0; >> >> +} >> >> + >> >> +static void dw_dp_video_disable(struct dw_dp *dp) >> >> +{ >> >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE, >> >> + FIELD_PREP(VIDEO_STREAM_ENABLE, 0)); >> >> +} >> >> + >> >> +static int dw_dp_video_enable(struct dw_dp *dp) >> >> +{ >> >> + struct dw_dp_video *video = &dp->video; >> >> + struct dw_dp_link *link = &dp->link; >> >> + struct drm_display_mode *mode = &video->mode; >> >> + u8 color_format = video->color_format; >> >> + u8 bpc = video->bpc; >> >> + u8 pixel_mode = video->pixel_mode; >> >> + u8 bpp = video->bpp, init_threshold, vic; >> >> + u32 hactive, hblank, h_sync_width, h_front_porch; >> >> + u32 vactive, vblank, v_sync_width, v_front_porch; >> >> + u32 vstart = mode->vtotal - mode->vsync_start; >> >> + u32 hstart = mode->htotal - mode->hsync_start; >> >> + u32 peak_stream_bandwidth, link_bandwidth; >> >> + u32 average_bytes_per_tu, average_bytes_per_tu_frac; >> >> + u32 ts, hblank_interval; >> >> + u32 value; >> >> + int ret; >> >> + >> >> + ret = dw_dp_video_set_pixel_mode(dp, pixel_mode); >> >> + if (ret) >> >> + return ret; >> >> + >> >> + ret = dw_dp_video_set_msa(dp, color_format, bpc, vstart, hstart); >> >> + if (ret) >> >> + return ret; >> >> + >> >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_MAPPING, >> >> + FIELD_PREP(VIDEO_MAPPING, video->video_mapping)); >> >> + >> >> + /* Configure DW_DP_VINPUT_POLARITY_CTRL register */ >> >> + value = 0; >> >> + if (mode->flags & DRM_MODE_FLAG_PHSYNC) >> >> + value |= FIELD_PREP(HSYNC_IN_POLARITY, 1); >> >> + if (mode->flags & DRM_MODE_FLAG_PVSYNC) >> >> + value |= FIELD_PREP(VSYNC_IN_POLARITY, 1); >> >> + regmap_write(dp->regmap, DW_DP_VINPUT_POLARITY_CTRL, value); >> >> + >> >> + /* Configure DW_DP_VIDEO_CONFIG1 register */ >> >> + hactive = mode->hdisplay; >> >> + hblank = mode->htotal - mode->hdisplay; >> >> + value = FIELD_PREP(HACTIVE, hactive) | FIELD_PREP(HBLANK, hblank); >> >> + if (mode->flags & DRM_MODE_FLAG_INTERLACE) >> >> + value |= FIELD_PREP(I_P, 1); >> >> + vic = drm_match_cea_mode(mode); >> >> + if (vic == 5 || vic == 6 || vic == 7 || >> >> + vic == 10 || vic == 11 || vic == 20 || >> >> + vic == 21 || vic == 22 || vic == 39 || >> >> + vic == 25 || vic == 26 || vic == 40 || >> >> + vic == 44 || vic == 45 || vic == 46 || >> >> + vic == 50 || vic == 51 || vic == 54 || >> >> + vic == 55 || vic == 58 || vic == 59) >> >> + value |= R_V_BLANK_IN_OSC; >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG1, value); >> >> + >> >> + /* Configure DW_DP_VIDEO_CONFIG2 register */ >> >> + vblank = mode->vtotal - mode->vdisplay; >> >> + vactive = mode->vdisplay; >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG2, >> >> + FIELD_PREP(VBLANK, vblank) | FIELD_PREP(VACTIVE, vactive)); >> >> + >> >> + /* Configure DW_DP_VIDEO_CONFIG3 register */ >> >> + h_sync_width = mode->hsync_end - mode->hsync_start; >> >> + h_front_porch = mode->hsync_start - mode->hdisplay; >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG3, >> >> + FIELD_PREP(H_SYNC_WIDTH, h_sync_width) | >> >> + FIELD_PREP(H_FRONT_PORCH, h_front_porch)); >> >> + >> >> + /* Configure DW_DP_VIDEO_CONFIG4 register */ >> >> + v_sync_width = mode->vsync_end - mode->vsync_start; >> >> + v_front_porch = mode->vsync_start - mode->vdisplay; >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG4, >> >> + FIELD_PREP(V_SYNC_WIDTH, v_sync_width) | >> >> + FIELD_PREP(V_FRONT_PORCH, v_front_porch)); >> >> + >> >> + /* Configure DW_DP_VIDEO_CONFIG5 register */ >> >> + peak_stream_bandwidth = mode->clock * bpp / 8; >> >> + link_bandwidth = (link->rate / 1000) * link->lanes; >> >> + ts = peak_stream_bandwidth * 64 / link_bandwidth; >> >> + average_bytes_per_tu = ts / 1000; >> >> + average_bytes_per_tu_frac = ts / 100 - average_bytes_per_tu * 10; >> >> + if (pixel_mode == DW_DP_MP_SINGLE_PIXEL) { >> >> + if (average_bytes_per_tu < 6) >> >> + init_threshold = 32; >> >> + else if (hblank <= 80 && color_format != DRM_COLOR_FORMAT_YCBCR420) >> >> + init_threshold = 12; >> >> + else if (hblank <= 40 && color_format == DRM_COLOR_FORMAT_YCBCR420) >> >> + init_threshold = 3; >> >> + else >> >> + init_threshold = 16; >> >> + } else { >> >> + u32 t1 = 0, t2 = 0, t3 = 0; >> >> + >> >> + switch (bpc) { >> >> + case 6: >> >> + t1 = (4 * 1000 / 9) * link->lanes; >> >> + break; >> >> + case 8: >> >> + if (color_format == DRM_COLOR_FORMAT_YCBCR422) { >> >> + t1 = (1000 / 2) * link->lanes; >> >> + } else { >> >> + if (pixel_mode == DW_DP_MP_DUAL_PIXEL) >> >> + t1 = (1000 / 3) * link->lanes; >> >> + else >> >> + t1 = (3000 / 16) * link->lanes; >> >> + } >> >> + break; >> >> + case 10: >> >> + if (color_format == DRM_COLOR_FORMAT_YCBCR422) >> >> + t1 = (2000 / 5) * link->lanes; >> >> + else >> >> + t1 = (4000 / 15) * link->lanes; >> >> + break; >> >> + case 12: >> >> + if (color_format == DRM_COLOR_FORMAT_YCBCR422) { >> >> + if (pixel_mode == DW_DP_MP_DUAL_PIXEL) >> >> + t1 = (1000 / 6) * link->lanes; >> >> + else >> >> + t1 = (1000 / 3) * link->lanes; >> >> + } else { >> >> + t1 = (2000 / 9) * link->lanes; >> >> + } >> >> + break; >> >> + case 16: >> >> + if (color_format != DRM_COLOR_FORMAT_YCBCR422 && >> >> + pixel_mode == DW_DP_MP_DUAL_PIXEL) >> >> + t1 = (1000 / 6) * link->lanes; >> >> + else >> >> + t1 = (1000 / 4) * link->lanes; >> >> + break; >> >> + default: >> >> + return -EINVAL; >> >> + } >> >> + >> >> + if (color_format == DRM_COLOR_FORMAT_YCBCR420) >> >> + t2 = (link->rate / 4) * 1000 / (mode->clock / 2); >> >> + else >> >> + t2 = (link->rate / 4) * 1000 / mode->clock; >> >> + >> >> + if (average_bytes_per_tu_frac) >> >> + t3 = average_bytes_per_tu + 1; >> >> + else >> >> + t3 = average_bytes_per_tu; >> >> + init_threshold = t1 * t2 * t3 / (1000 * 1000); >> >> + if (init_threshold <= 16 || average_bytes_per_tu < 10) >> >> + init_threshold = 40; >> >> + } >> >> + >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG5, >> >> + FIELD_PREP(INIT_THRESHOLD_HI, init_threshold >> 6) | >> >> + FIELD_PREP(AVERAGE_BYTES_PER_TU_FRAC, average_bytes_per_tu_frac) | >> >> + FIELD_PREP(INIT_THRESHOLD, init_threshold) | >> >> + FIELD_PREP(AVERAGE_BYTES_PER_TU, average_bytes_per_tu)); >> >> + >> >> + /* Configure DW_DP_VIDEO_HBLANK_INTERVAL register */ >> >> + hblank_interval = hblank * (link->rate / 4) / mode->clock; >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_HBLANK_INTERVAL, >> >> + FIELD_PREP(HBLANK_INTERVAL_EN, 1) | >> >> + FIELD_PREP(HBLANK_INTERVAL, hblank_interval)); >> >> + >> >> + /* Video stream enable */ >> >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE, >> >> + FIELD_PREP(VIDEO_STREAM_ENABLE, 1)); >> >> + >> >> + if (dw_dp_video_need_vsc_sdp(dp)) >> >> + dw_dp_send_vsc_sdp(dp); >> >> + >> >> + return 0; >> >> +} >> >> + >> >> +static void dw_dp_hpd_init(struct dw_dp *dp) >> >> +{ >> >> + /* Enable all HPD interrupts */ >> >> + regmap_update_bits(dp->regmap, DW_DP_HPD_INTERRUPT_ENABLE, >> >> + HPD_UNPLUG_EN | HPD_PLUG_EN | HPD_IRQ_EN, >> >> + FIELD_PREP(HPD_UNPLUG_EN, 1) | >> >> + FIELD_PREP(HPD_PLUG_EN, 1) | >> >> + FIELD_PREP(HPD_IRQ_EN, 1)); >> >> + >> >> + /* Enable all top-level interrupts */ >> >> + regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE, >> >> + HPD_EVENT_EN, FIELD_PREP(HPD_EVENT_EN, 1)); >> >> +} >> >> + >> >> +static void dw_dp_aux_init(struct dw_dp *dp) >> >> +{ >> >> + regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE, >> >> + AUX_REPLY_EVENT_EN, FIELD_PREP(AUX_REPLY_EVENT_EN, 1)); >> >> +} >> >> + >> >> +static void dw_dp_init_hw(struct dw_dp *dp) >> >> +{ >> >> + regmap_update_bits(dp->regmap, DW_DP_CCTL, DEFAULT_FAST_LINK_TRAIN_EN, >> >> + FIELD_PREP(DEFAULT_FAST_LINK_TRAIN_EN, 0)); >> >> + >> >> + dw_dp_hpd_init(dp); >> >> + dw_dp_aux_init(dp); >> >> +} >> >> + >> >> +static int dw_dp_aux_write_data(struct dw_dp *dp, const u8 *buffer, size_t size) >> >> +{ >> >> + size_t i, j; >> >> + >> >> + for (i = 0; i < DIV_ROUND_UP(size, 4); i++) { >> >> + size_t num = min_t(size_t, size - i * 4, 4); >> >> + u32 value = 0; >> >> + >> >> + for (j = 0; j < num; j++) >> >> + value |= buffer[i * 4 + j] << (j * 8); >> >> + >> >> + regmap_write(dp->regmap, DW_DP_AUX_DATA0 + i * 4, value); >> >> + } >> >> + >> >> + return size; >> >> +} >> >> + >> >> +static int dw_dp_aux_read_data(struct dw_dp *dp, u8 *buffer, size_t size) >> >> +{ >> >> + size_t i, j; >> >> + >> >> + for (i = 0; i < DIV_ROUND_UP(size, 4); i++) { >> >> + size_t num = min_t(size_t, size - i * 4, 4); >> >> + u32 value; >> >> + >> >> + regmap_read(dp->regmap, DW_DP_AUX_DATA0 + i * 4, &value); >> >> + >> >> + for (j = 0; j < num; j++) >> >> + buffer[i * 4 + j] = value >> (j * 8); >> >> + } >> >> + >> >> + return size; >> >> +} >> >> + >> >> +static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux, >> >> + struct drm_dp_aux_msg *msg) >> >> +{ >> >> + struct dw_dp *dp = container_of(aux, struct dw_dp, aux); >> >> + unsigned long timeout = msecs_to_jiffies(10); >> >> + u32 status, value; >> >> + ssize_t ret = 0; >> >> + >> >> + if (WARN_ON(msg->size > 16)) >> >> + return -E2BIG; >> >> + >> >> + switch (msg->request & ~DP_AUX_I2C_MOT) { >> >> + case DP_AUX_NATIVE_WRITE: >> >> + case DP_AUX_I2C_WRITE: >> >> + case DP_AUX_I2C_WRITE_STATUS_UPDATE: >> >> + ret = dw_dp_aux_write_data(dp, msg->buffer, msg->size); >> >> + if (ret < 0) >> >> + return ret; >> >> + break; >> >> + case DP_AUX_NATIVE_READ: >> >> + case DP_AUX_I2C_READ: >> >> + break; >> >> + default: >> >> + return -EINVAL; >> >> + } >> >> + >> >> + if (msg->size > 0) >> >> + value = FIELD_PREP(AUX_LEN_REQ, msg->size - 1); >> >> + else >> >> + value = FIELD_PREP(I2C_ADDR_ONLY, 1); >> >> + value |= FIELD_PREP(AUX_CMD_TYPE, msg->request); >> >> + value |= FIELD_PREP(AUX_ADDR, msg->address); >> >> + regmap_write(dp->regmap, DW_DP_AUX_CMD, value); >> >> + >> >> + status = wait_for_completion_timeout(&dp->complete, timeout); >> >> + if (!status) { >> >> + dev_err(dp->dev, "timeout waiting for AUX reply\n"); >> >> + return -ETIMEDOUT; >> >> + } >> >> + >> >> + regmap_read(dp->regmap, DW_DP_AUX_STATUS, &value); >> >> + if (value & AUX_TIMEOUT) >> >> + return -ETIMEDOUT; >> >> + >> >> + msg->reply = FIELD_GET(AUX_STATUS, value); >> >> + >> >> + if (msg->size > 0 && msg->reply == DP_AUX_NATIVE_REPLY_ACK) { >> >> + if (msg->request & DP_AUX_I2C_READ) { >> >> + size_t count = FIELD_GET(AUX_BYTES_READ, value) - 1; >> >> + >> >> + if (count != msg->size) >> >> + return -EBUSY; >> >> + >> >> + ret = dw_dp_aux_read_data(dp, msg->buffer, count); >> >> + if (ret < 0) >> >> + return ret; >> >> + } >> >> + } >> >> + >> >> + return ret; >> >> +} >> >> + >> >> +/* >> >> + * Limits for the video timing output by DP: >> >> + * 1. the hfp should be 2 pixels aligned; >> >> + * 2. the minimum hsync should be 9 pixel; >> >> + * 3. the minimum hbp should be 16 pixel; >> >> + */ >> >> +static bool dw_dp_bridge_mode_fixup(struct drm_bridge *bridge, >> >> + const struct drm_display_mode *mode, >> >> + struct drm_display_mode *adjusted_mode) >> >> +{ >> >> + struct dw_dp *dp = bridge_to_dp(bridge); >> >> + int min_hbp = 16; >> >> + int min_hsync = 9; >> >> + >> >> + if ((adjusted_mode->hsync_start - adjusted_mode->hdisplay) & 0x1) { >> >> + adjusted_mode->hsync_start += 1; >> >> + dev_warn(dp->dev, "hfp is not 2 pixeel aligned, fixup to aligned hfp\n"); >> >> + } >> >> + if (adjusted_mode->hsync_end - adjusted_mode->hsync_start < min_hsync) { >> >> + adjusted_mode->hsync_end = adjusted_mode->hsync_start + min_hsync; >> >> + dev_warn(dp->dev, "hsync is too narrow, fixup to min hsync:%d\n", min_hsync); >> >> + } >> >> + if (adjusted_mode->htotal - adjusted_mode->hsync_end < min_hbp) { >> >> + adjusted_mode->htotal = adjusted_mode->hsync_end + min_hbp; >> >> + dev_warn(dp->dev, "hbp is too narrow, fixup to min hbp:%d\n", min_hbp); >> >> + } >> >> + >> >> + return true; >> >> +} >> >> + >> >> +static enum drm_mode_status dw_dp_bridge_mode_valid(struct drm_bridge *bridge, >> >> + const struct drm_display_info *info, >> >> + const struct drm_display_mode *mode) >> >> +{ >> >> + struct dw_dp *dp = bridge_to_dp(bridge); >> >> + struct dw_dp_link *link = &dp->link; >> >> + u32 min_bpp; >> >> + >> >> + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR420 && >> >> + link->vsc_sdp_supported && >> >> + (drm_mode_is_420_only(info, mode) || drm_mode_is_420_also(info, mode))) >> >> + min_bpp = 12; >> >> + else if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) >> >> + min_bpp = 16; >> >> + else if (info->color_formats & DRM_COLOR_FORMAT_RGB444) >> >> + min_bpp = 18; >> >> + else >> >> + min_bpp = 24; >> >> + >> >> + if (!link->vsc_sdp_supported && >> >> + drm_mode_is_420_only(info, mode)) >> >> + return MODE_NO_420; >> >> + >> >> + if (!dw_dp_bandwidth_ok(dp, mode, min_bpp, link->lanes, link->rate)) >> >> + return MODE_CLOCK_HIGH; >> >> + >> >> + return MODE_OK; >> >> +} >> >> + >> >> +static void dw_dp_bridge_atomic_pre_enable(struct drm_bridge *bridge, >> >> + struct drm_bridge_state *old_bridge_state) >> >> +{ >> >> + struct drm_atomic_state *state = old_bridge_state->base.state; >> >> + struct drm_crtc_state *crtc_state = bridge->encoder->crtc->state; >> >> + struct dw_dp *dp = bridge_to_dp(bridge); >> >> + struct dw_dp_video *video = &dp->video; >> >> + struct drm_display_mode *m = &video->mode; >> >> + struct drm_bridge_state *bridge_state; >> >> + const struct dw_dp_output_format *fmt; >> >> + >> >> + drm_mode_copy(m, &crtc_state->adjusted_mode); >> >> + >> >> + bridge_state = drm_atomic_get_new_bridge_state(state, bridge); >> >> + fmt = dw_dp_get_output_format(bridge_state->output_bus_cfg.format); >> > >> >Should it be a part of the atomic_check() instead? >> >> Since we have modef_fixup in this driver, which is mutually exclusive with atomic_check. > >You can convet .mode_fixup into .atomic_check. The latter can change the >crtc_state->adjusted_mode. It is better to reject the invalid >combinations from the .atomic_check rather than having to handled them >later. > >> >> >> >> > >> >> + >> >> + video->video_mapping = fmt->video_mapping; >> >> + video->color_format = fmt->color_format; >> >> + video->bpc = fmt->bpc; >> >> + video->bpp = fmt->bpp; >> >> +} >> >> + >> >> +static bool dw_dp_needs_link_retrain(struct dw_dp *dp) >> >> +{ >> >> + struct dw_dp_link *link = &dp->link; >> >> + u8 link_status[DP_LINK_STATUS_SIZE]; >> >> + >> >> + if (!dw_dp_link_train_valid(&link->train)) >> >> + return false; >> >> + >> >> + if (drm_dp_dpcd_read_link_status(&dp->aux, link_status) < 0) >> >> + return false; >> >> + >> >> + /* Retrain if Channel EQ or CR not ok */ >> >> + return !drm_dp_channel_eq_ok(link_status, dp->link.lanes); >> >> +} >> >> + >> >> +static void dw_dp_link_disable(struct dw_dp *dp) >> >> +{ >> >> + struct dw_dp_link *link = &dp->link; >> >> + >> >> + if (dw_dp_hpd_detect(dp)) >> >> + dw_dp_link_power_down(dp); >> >> + >> >> + dw_dp_phy_xmit_enable(dp, 0); >> >> + >> >> + phy_power_off(dp->phy); >> >> + >> >> + link->train.clock_recovered = false; >> >> + link->train.channel_equalized = false; >> >> +} >> >> + >> >> +static int dw_dp_link_enable(struct dw_dp *dp) >> >> +{ >> >> + int ret; >> >> + >> >> + ret = phy_power_on(dp->phy); >> >> + if (ret) >> >> + return ret; >> >> + >> >> + ret = dw_dp_link_power_up(dp); >> >> + if (ret < 0) >> >> + return ret; >> >> + >> >> + ret = dw_dp_link_train(dp); >> >> + >> >> + return ret; >> >> +} >> >> + >> >> +static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge, >> >> + struct drm_bridge_state *old_state) >> >> +{ >> >> + struct dw_dp *dp = bridge_to_dp(bridge); >> >> + struct drm_atomic_state *state = old_state->base.state; >> >> + struct drm_connector *connector; >> >> + struct drm_connector_state *conn_state; >> >> + int ret; >> >> + >> >> + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); >> >> + if (!connector) { >> >> + dev_err(dp->dev, "failed to get connector\n"); >> >> + return; >> >> + } >> >> + >> >> + conn_state = drm_atomic_get_new_connector_state(state, connector); >> >> + if (!conn_state) { >> >> + dev_err(dp->dev, "failed to get connector state\n"); >> >> + return; >> >> + } >> >> + >> >> + set_bit(0, dp->sdp_reg_bank); >> >> + >> >> + ret = dw_dp_link_enable(dp); >> >> + if (ret < 0) { >> >> + dev_err(dp->dev, "failed to enable link: %d\n", ret); >> >> + return; >> >> + } >> >> + >> >> + ret = dw_dp_video_enable(dp); >> >> + if (ret < 0) { >> >> + dev_err(dp->dev, "failed to enable video: %d\n", ret); >> >> + return; >> >> + } >> >> +} >> >> + >> >> +static void dw_dp_reset(struct dw_dp *dp) >> >> +{ >> >> + int val; >> >> + >> >> + disable_irq(dp->irq); >> >> + regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET, >> >> + FIELD_PREP(CONTROLLER_RESET, 1)); >> >> + udelay(10); >> >> + regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET, >> >> + FIELD_PREP(CONTROLLER_RESET, 0)); >> >> + >> >> + dw_dp_init_hw(dp); >> >> + regmap_read_poll_timeout(dp->regmap, DW_DP_HPD_STATUS, val, >> >> + FIELD_GET(HPD_HOT_PLUG, val), 200, 200000); >> >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG); >> >> + enable_irq(dp->irq); >> >> +} >> >> + >> >> +static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge, >> >> + struct drm_bridge_state *old_bridge_state) >> >> +{ >> >> + struct dw_dp *dp = bridge_to_dp(bridge); >> >> + >> >> + dw_dp_video_disable(dp); >> >> + dw_dp_link_disable(dp); >> >> + bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE); >> >> + dw_dp_reset(dp); >> >> +} >> >> + >> >> +static bool dw_dp_hpd_detect_link(struct dw_dp *dp) >> >> +{ >> >> + int ret; >> >> + >> >> + ret = phy_power_on(dp->phy); >> >> + if (ret < 0) >> >> + return false; >> >> + ret = dw_dp_link_parse(dp); >> >> + phy_power_off(dp->phy); >> >> + >> >> + return !ret; >> >> +} >> >> + >> >> +static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge) >> >> +{ >> >> + struct dw_dp *dp = bridge_to_dp(bridge); >> >> + >> >> + if (!dw_dp_hpd_detect(dp)) >> >> + return connector_status_disconnected; >> >> + >> >> + if (!dw_dp_hpd_detect_link(dp)) >> >> + return connector_status_disconnected; >> >> + >> >> + return connector_status_connected; >> >> +} >> >> + >> >> +static const struct drm_edid *dw_dp_bridge_edid_read(struct drm_bridge *bridge, >> >> + struct drm_connector *connector) >> >> +{ >> >> + struct dw_dp *dp = bridge_to_dp(bridge); >> >> + const struct drm_edid *edid; >> >> + int ret; >> >> + >> >> + ret = phy_power_on(dp->phy); >> >> + if (ret) >> >> + return NULL; >> >> + >> >> + edid = drm_edid_read_ddc(connector, &dp->aux.ddc); >> >> + >> >> + phy_power_off(dp->phy); >> >> + >> >> + return edid; >> >> +} >> >> + >> >> +static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, >> >> + struct drm_bridge_state *bridge_state, >> >> + struct drm_crtc_state *crtc_state, >> >> + struct drm_connector_state *conn_state, >> >> + unsigned int *num_output_fmts) >> >> +{ >> >> + struct dw_dp *dp = bridge_to_dp(bridge); >> >> + struct dw_dp_link *link = &dp->link; >> >> + struct drm_display_info *di = &conn_state->connector->display_info; >> >> + struct drm_display_mode mode = crtc_state->mode; >> >> + const struct dw_dp_output_format *fmt; >> >> + u32 i, j = 0; >> >> + u32 *output_fmts; >> >> + >> >> + *num_output_fmts = 0; >> >> + >> >> + output_fmts = kcalloc(ARRAY_SIZE(dw_dp_output_formats), sizeof(*output_fmts), GFP_KERNEL); >> >> + if (!output_fmts) >> >> + return NULL; >> >> + >> >> + for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) { >> >> + fmt = &dw_dp_output_formats[i]; >> >> + >> >> + if (fmt->bpc > conn_state->max_bpc) >> >> + continue; >> >> + >> >> + if (!(fmt->color_format & di->color_formats)) >> >> + continue; >> >> + >> >> + if (fmt->color_format == DRM_COLOR_FORMAT_YCBCR420 && >> >> + !link->vsc_sdp_supported) >> >> + continue; >> >> + >> >> + if (fmt->color_format != DRM_COLOR_FORMAT_YCBCR420 && >> >> + drm_mode_is_420_only(di, &mode)) >> >> + continue; >> >> + >> >> + if (!dw_dp_bandwidth_ok(dp, &mode, fmt->bpp, link->lanes, link->rate)) >> >> + continue; >> >> + >> >> + output_fmts[j++] = fmt->bus_format; >> >> + } >> >> + >> >> + *num_output_fmts = j; >> >> + >> >> + return output_fmts; >> >> +} >> >> + >> >> +static const struct drm_bridge_funcs dw_dp_bridge_funcs = { >> >> + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, >> >> + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, >> >> + .atomic_reset = drm_atomic_helper_bridge_reset, >> >> + .atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt, >> >> + .atomic_get_output_bus_fmts = dw_dp_bridge_atomic_get_output_bus_fmts, >> >> + .mode_fixup = dw_dp_bridge_mode_fixup, >> >> + .mode_valid = dw_dp_bridge_mode_valid, >> >> + .atomic_pre_enable = dw_dp_bridge_atomic_pre_enable, >> >> + .atomic_enable = dw_dp_bridge_atomic_enable, >> >> + .atomic_disable = dw_dp_bridge_atomic_disable, >> >> + .detect = dw_dp_bridge_detect, >> >> + .edid_read = dw_dp_bridge_edid_read, >> >> +}; >> >> + >> >> +static int dw_dp_link_retrain(struct dw_dp *dp) >> >> +{ >> >> + struct drm_device *dev = dp->bridge.dev; >> >> + struct drm_modeset_acquire_ctx ctx; >> >> + int ret; >> >> + >> >> + if (!dw_dp_needs_link_retrain(dp)) >> >> + return 0; >> >> + >> >> + dev_dbg(dp->dev, "Retraining link\n"); >> >> + >> >> + drm_modeset_acquire_init(&ctx, 0); >> >> + for (;;) { >> >> + ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx); >> >> + if (ret != -EDEADLK) >> >> + break; >> > >> >I think it's an error to continue in such a case. There should be a >> >return after the loop if ret is not 0. >> >> Thank you for catching this, I will add a check fater the loop. >> >> >> > >> >> + >> >> + drm_modeset_backoff(&ctx); >> >> + } >> >> + >> >> + ret = dw_dp_link_train(dp); >> >> + drm_modeset_drop_locks(&ctx); >> >> + drm_modeset_acquire_fini(&ctx); >> >> + >> >> + return ret; >> >> +} >> >> + >> >> +static void dw_dp_hpd_work(struct work_struct *work) >> >> +{ >> >> + struct dw_dp *dp = container_of(work, struct dw_dp, hpd_work); >> >> + bool long_hpd; >> >> + int ret; >> >> + >> >> + mutex_lock(&dp->irq_lock); >> >> + long_hpd = dp->hotplug.long_hpd; >> >> + mutex_unlock(&dp->irq_lock); >> >> + >> >> + dev_dbg(dp->dev, "[drm] Get hpd irq - %s\n", long_hpd ? "long" : "short"); >> >> + >> >> + if (!long_hpd) { >> >> + if (dw_dp_needs_link_retrain(dp)) { >> >> + ret = dw_dp_link_retrain(dp); >> >> + if (ret) >> >> + dev_warn(dp->dev, "Retrain link failed\n"); >> >> + } >> >> + } else { >> >> + drm_helper_hpd_irq_event(dp->bridge.dev); >> >> + } >> >> +} >> >> + >> >> +static void dw_dp_handle_hpd_event(struct dw_dp *dp) >> >> +{ >> >> + u32 value; >> >> + >> >> + mutex_lock(&dp->irq_lock); >> >> + regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value); >> >> + >> >> + if (value & HPD_IRQ) { >> >> + dev_dbg(dp->dev, "IRQ from the HPD\n"); >> >> + dp->hotplug.long_hpd = false; >> >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_IRQ); >> >> + } >> >> + >> >> + if (value & HPD_HOT_PLUG) { >> >> + dev_dbg(dp->dev, "Hot plug detected\n"); >> >> + dp->hotplug.long_hpd = true; >> >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG); >> >> + } >> >> + >> >> + if (value & HPD_HOT_UNPLUG) { >> >> + dev_dbg(dp->dev, "Unplug detected\n"); >> >> + dp->hotplug.long_hpd = true; >> >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_UNPLUG); >> >> + } >> >> + mutex_unlock(&dp->irq_lock); >> >> + >> >> + schedule_work(&dp->hpd_work); >> >> +} >> >> + >> >> +static irqreturn_t dw_dp_irq(int irq, void *data) >> >> +{ >> >> + struct dw_dp *dp = data; >> >> + u32 value; >> >> + >> >> + regmap_read(dp->regmap, DW_DP_GENERAL_INTERRUPT, &value); >> >> + if (!value) >> >> + return IRQ_NONE; >> >> + >> >> + if (value & HPD_EVENT) >> >> + dw_dp_handle_hpd_event(dp); >> >> + >> >> + if (value & AUX_REPLY_EVENT) { >> >> + regmap_write(dp->regmap, DW_DP_GENERAL_INTERRUPT, AUX_REPLY_EVENT); >> >> + complete(&dp->complete); >> >> + } >> >> + >> >> + return IRQ_HANDLED; >> >> +} >> >> + >> >> +static const struct regmap_range dw_dp_readable_ranges[] = { >> >> + regmap_reg_range(DW_DP_VERSION_NUMBER, DW_DP_ID), >> >> + regmap_reg_range(DW_DP_CONFIG_REG1, DW_DP_CONFIG_REG3), >> >> + regmap_reg_range(DW_DP_CCTL, DW_DP_SOFT_RESET_CTRL), >> >> + regmap_reg_range(DW_DP_VSAMPLE_CTRL, DW_DP_VIDEO_HBLANK_INTERVAL), >> >> + regmap_reg_range(DW_DP_AUD_CONFIG1, DW_DP_AUD_CONFIG1), >> >> + regmap_reg_range(DW_DP_SDP_VERTICAL_CTRL, DW_DP_SDP_STATUS_EN), >> >> + regmap_reg_range(DW_DP_PHYIF_CTRL, DW_DP_PHYIF_PWRDOWN_CTRL), >> >> + regmap_reg_range(DW_DP_AUX_CMD, DW_DP_AUX_DATA3), >> >> + regmap_reg_range(DW_DP_GENERAL_INTERRUPT, DW_DP_HPD_INTERRUPT_ENABLE), >> >> +}; >> >> + >> >> +static const struct regmap_access_table dw_dp_readable_table = { >> >> + .yes_ranges = dw_dp_readable_ranges, >> >> + .n_yes_ranges = ARRAY_SIZE(dw_dp_readable_ranges), >> >> +}; >> >> + >> >> +static const struct regmap_config dw_dp_regmap_config = { >> >> + .reg_bits = 32, >> >> + .reg_stride = 4, >> >> + .val_bits = 32, >> >> + .fast_io = true, >> >> + .max_register = DW_DP_MAX_REGISTER, >> >> + .rd_table = &dw_dp_readable_table, >> >> +}; >> >> + >> >> +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, >> >> + const struct dw_dp_plat_data *plat_data) >> >> +{ >> >> + struct platform_device *pdev = to_platform_device(dev); >> >> + struct dw_dp *dp; >> >> + struct drm_bridge *bridge; >> >> + void __iomem *res; >> >> + int ret; >> >> + >> >> + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); >> >> + if (!dp) >> >> + return ERR_PTR(-ENOMEM); >> >> + >> >> + dp->dev = dev; >> >> + dp->video.pixel_mode = DW_DP_MP_QUAD_PIXEL; >> >> + >> >> + dp->plat_data = plat_data; >> >> + bridge = &dp->bridge; >> >> + mutex_init(&dp->irq_lock); >> >> + INIT_WORK(&dp->hpd_work, dw_dp_hpd_work); >> >> + init_completion(&dp->complete); >> >> + >> >> + res = devm_platform_ioremap_resource(pdev, 0); >> >> + if (IS_ERR(res)) >> >> + return ERR_CAST(res); >> >> + >> >> + dp->regmap = devm_regmap_init_mmio(dev, res, &dw_dp_regmap_config); >> >> + if (IS_ERR(dp->regmap)) { >> >> + dev_err_probe(dev, PTR_ERR(dp->regmap), "failed to create regmap\n"); >> >> + return ERR_CAST(dp->regmap); >> >> + } >> >> + >> >> + dp->phy = devm_of_phy_get(dev, dev->of_node, NULL); >> >> + if (IS_ERR(dp->phy)) { >> >> + dev_err_probe(dev, PTR_ERR(dp->phy), "failed to get phy\n"); >> >> + return ERR_CAST(dp->phy); >> >> + } >> >> + >> >> + dp->apb_clk = devm_clk_get_enabled(dev, "apb"); >> >> + if (IS_ERR(dp->apb_clk)) { >> >> + dev_err_probe(dev, PTR_ERR(dp->apb_clk), "failed to get apb clock\n"); >> >> + return ERR_CAST(dp->apb_clk); >> >> + } >> >> + >> >> + dp->aux_clk = devm_clk_get_enabled(dev, "aux"); >> >> + if (IS_ERR(dp->aux_clk)) { >> >> + dev_err_probe(dev, PTR_ERR(dp->aux_clk), "failed to get aux clock\n"); >> >> + return ERR_CAST(dp->aux_clk); >> >> + } >> >> + >> >> + dp->i2s_clk = devm_clk_get(dev, "i2s"); >> >> + if (IS_ERR(dp->i2s_clk)) { >> >> + dev_err_probe(dev, PTR_ERR(dp->i2s_clk), "failed to get i2s clock\n"); >> >> + return ERR_CAST(dp->i2s_clk); >> >> + } >> >> + >> >> + dp->spdif_clk = devm_clk_get(dev, "spdif"); >> >> + if (IS_ERR(dp->spdif_clk)) { >> >> + dev_err_probe(dev, PTR_ERR(dp->spdif_clk), "failed to get spdif clock\n"); >> >> + return ERR_CAST(dp->spdif_clk); >> >> + } >> >> + >> >> + dp->hdcp_clk = devm_clk_get(dev, "hdcp"); >> >> + if (IS_ERR(dp->hdcp_clk)) { >> >> + dev_err_probe(dev, PTR_ERR(dp->hdcp_clk), "failed to get hdcp clock\n"); >> >> + return ERR_CAST(dp->hdcp_clk); >> >> + } >> >> + >> >> + dp->rstc = devm_reset_control_get(dev, NULL); >> >> + if (IS_ERR(dp->rstc)) { >> >> + dev_err_probe(dev, PTR_ERR(dp->rstc), "failed to get reset control\n"); >> >> + return ERR_CAST(dp->rstc); >> >> + } >> >> + >> >> + dp->irq = platform_get_irq(pdev, 0); >> >> + if (dp->irq < 0) >> >> + return ERR_PTR(ret); >> >> + >> >> + ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq, >> >> + IRQF_ONESHOT, dev_name(dev), dp); >> > >> >Are you ready to handle the IRQ here, before the bridge is filled? >> >> I will move to devm_request_threaded_irq after dw_dp_init_hw. >> >> >> > >> >> + if (ret) { >> >> + dev_err_probe(dev, ret, "failed to request irq\n"); >> >> + return ERR_PTR(ret); >> >> + } >> >> + >> >> + bridge->of_node = dev->of_node; >> >> + bridge->funcs = &dw_dp_bridge_funcs; >> >> + bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; >> >> + bridge->type = DRM_MODE_CONNECTOR_DisplayPort; >> >> + bridge->ycbcr_420_allowed = true; >> >> + bridge->vendor = "Synopsys"; >> >> + bridge->product = "DW DP TX"; >> > >> >These two are unused >> > >> >> + >> >> + platform_set_drvdata(pdev, dp); >> > >> >Unused >> >> I will remove them. >> >> Thanks again. >> >> >> > >> >> + >> >> + dp->aux.dev = dev; >> >> + dp->aux.drm_dev = encoder->dev; >> >> + dp->aux.name = dev_name(dev); >> >> + dp->aux.transfer = dw_dp_aux_transfer; >> >> + ret = drm_dp_aux_register(&dp->aux); >> >> + if (ret) { >> >> + dev_err_probe(dev, ret, "Aux register failed\n"); >> >> + return ERR_PTR(ret); >> >> + } >> >> + >> >> + ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); >> >> + if (ret) >> >> + dev_err_probe(dev, ret, "Failed to attach bridge\n"); >> >> + >> >> + dw_dp_init_hw(dp); >> >> + >> >> + return dp; >> >> +} >> >> +MODULE_AUTHOR("Andy Yan <andyshrk@163.com>"); >> >> +MODULE_DESCRIPTION("DW DP Core Library"); >> >> +MODULE_LICENSE("GPL"); >> >> diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h >> >> new file mode 100644 >> >> index 000000000000..07845f609eca >> >> --- /dev/null >> >> +++ b/include/drm/bridge/dw_dp.h >> >> @@ -0,0 +1,19 @@ >> >> +/* SPDX-License-Identifier: GPL-2.0-or-later */ >> >> +/* >> >> + * Copyright (c) 2025 Rockchip Electronics Co., Ltd. >> >> + */ >> >> + >> >> +#ifndef __DW_DP__ >> >> +#define __DW_DP__ >> >> + >> >> +struct device; >> >> +struct drm_encoder; >> >> +struct dw_dp; >> >> + >> >> +struct dw_dp_plat_data { >> >> + u32 max_link_rate; >> >> +}; >> >> + >> >> +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, >> >> + const struct dw_dp_plat_data *plat_data); >> >> +#endif /* __DW_DP__ */ >> >> -- >> >> 2.34.1 >> >> >> > >> >-- >> >With best wishes >> >Dmitry > >-- >With best wishes >Dmitry ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library 2025-03-04 7:16 ` Andy Yan @ 2025-03-04 9:18 ` Dmitry Baryshkov 0 siblings, 0 replies; 32+ messages in thread From: Dmitry Baryshkov @ 2025-03-04 9:18 UTC (permalink / raw) To: Andy Yan Cc: heiko, hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan On Tue, Mar 04, 2025 at 03:16:16PM +0800, Andy Yan wrote: > > Hi Dmitry, > > 在 2025-03-03 00:21:06,"Dmitry Baryshkov" <dmitry.baryshkov@linaro.org> 写道: > >On Sun, Mar 02, 2025 at 06:34:22PM +0800, Andy Yan wrote: > >> > >> > >> Hi Dmitry, > >> Thank you for your review。 > >> Please also review my inline reply. > >> > >> 在 2025-03-02 02:14:19,"Dmitry Baryshkov" <dmitry.baryshkov@linaro.org> 写道: > >> >On Sun, Feb 23, 2025 at 07:30:25PM +0800, Andy Yan wrote: > >> >> From: Andy Yan <andy.yan@rock-chips.com> > >> >> > >> >> The DW DP TX Controller is compliant with the DisplayPort Specification > >> >> Version 1.4 with the following features: > >> >> > >> >> * DisplayPort 1.4a > >> >> * Main Link: 1/2/4 lanes > >> >> * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps > >> >> * AUX channel 1Mbps > >> >> * Single Stream Transport(SST) > >> >> * Multistream Transport (MST) > >> >> *Type-C support (alternate mode) > >> >> * HDCP 2.2, HDCP 1.3 > >> >> * Supports up to 8/10 bits per color component > >> >> * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0 > >> >> * Pixel clock up to 594MHz > >> >> * I2S, SPDIF audio interface > >> >> > >> >> Add library with common helpers to make it can be shared with > >> >> other SoC. > >> >> > >> >> Signed-off-by: Andy Yan <andy.yan@rock-chips.com> > >> >> > >> >> drm/bridge: cleanup > >> > > >> >Stray line? > >> > >> Sorry, will be removed. > >> > >> > > >> >> > >> >> --- > > ........ > > >> >> +static int dw_dp_link_power_up(struct dw_dp *dp) > >> >> +{ > >> >> + struct dw_dp_link *link = &dp->link; > >> >> + int ret; > >> >> + u8 value; > >> >> + > >> >> + if (link->revision < DP_DPCD_REV_11) > >> >> + return 0; > >> >> + > >> >> + ret = drm_dp_dpcd_readb(&dp->aux, DP_SET_POWER, &value); > >> >> + if (ret < 0) > >> >> + return ret; > >> >> + > >> >> + value &= ~DP_SET_POWER_MASK; > >> >> + value |= DP_SET_POWER_D0; > >> >> + > >> >> + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, value); > >> >> + if (ret < 0) > >> >> + return ret; > >> >> + > >> >> + usleep_range(1000, 2000); > >> >> + > >> >> + return 0; > >> >> +} > >> >> + > >> >> +static int dw_dp_link_power_down(struct dw_dp *dp) > >> >> +{ > >> >> + struct dw_dp_link *link = &dp->link; > >> >> + int ret; > >> >> + u8 value; > >> >> + > >> >> + if (link->revision < DP_DPCD_REV_11) > >> >> + return 0; > >> >> + > >> >> + ret = drm_dp_dpcd_readb(&dp->aux, DP_SET_POWER, &value); > >> >> + if (ret < 0) > >> >> + return ret; > >> >> + > >> >> + value &= ~DP_SET_POWER_MASK; > >> >> + value |= DP_SET_POWER_D3; > >> >> + > >> >> + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, value); > >> >> + if (ret < 0) > >> >> + return ret; > >> >> + > >> > > >> >Would you please mind pulling these two functions to DRM DP helpers? > >> >There are enough users to make this into a common code. > >> > >> Yes, when developing this patch, I had the same realization. > >> The processing logic for MSM/Cadence/ANX6345/tegra follows > >> a similar approach. > >> I had originally planned to defer this refactoring until the series of patches is accepted. > >> Additionally, I have other pending tasks in my TODO list, such as fully decoupling the > >> bridge driver and encoder/connector components across all Rockchip platforms。 > >> But I prioritize completing these foundational features before go to next steps, > >> Is this acceptable ? > > > >You can add necessary helpers as one of the first patches in the > >series. Convert one or several platforms that use drm-misc (i.e. not > >MSM, AMD, i915, nouveau) and send conversion of the rest of the > >platforms as followups. > > If pull these two functions to DRM DP helpers, I think they should be > named as drm_dp_link_power_up/down ?. I think so. > I found that tegra/dp.c already use this name, so I need to do a symbols rename > for tegra first to avoid build error like[2]. > > Can the patch for Tegra be merged through drm-misc branch? Yes, when drm_dp_link code was moved to the Tegra driver, nobody renamed those symboles. Maybe in the hope of moving them back. Please rename those symbols. > > > [2]https://lore.kernel.org/oe-kbuild-all/202410250305.UHKDhtxy-lkp@intel.com/ > > > >> > >> > > >> >> + return 0; > >> >> +} > >> >> + > >> >> +static bool dw_dp_has_sink_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE], > >> >> + const struct drm_dp_desc *desc) > >> >> +{ > >> >> + return 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); > >> > > >> > > >> >isn't it drm_dp_read_sink_count_cap() ? > >> > >> I will have a try, since this drm_dp_read_sink_count_cap requires a connector parameter, > >> I need to figure out how to get the connector here. > > > >Maybe Maxime's series will you here, see [1]. > > > >[1] https://lore.kernel.org/dri-devel/20250225-bridge-connector-v4-4-7ecb07b09cad@kernel.org/ > > > >> > >> > > >> >> +} > >> >> + > >> >> +static int dw_dp_link_parse(struct dw_dp *dp) > >> >> +{ > >> >> + struct dw_dp_link *link = &dp->link; > >> >> + u8 dpcd; > >> >> + int ret; > > ...... > > >> >> + break; > >> >> + } > >> >> + > >> >> + if (video->color_format == DRM_COLOR_FORMAT_RGB444) { > >> >> + vsc.colorimetry = DP_COLORIMETRY_DEFAULT; > >> >> + vsc.dynamic_range = DP_DYNAMIC_RANGE_VESA; > >> >> + } else { > >> >> + vsc.colorimetry = DP_COLORIMETRY_BT709_YCC; > >> >> + vsc.dynamic_range = DP_DYNAMIC_RANGE_CTA; > >> >> + } > >> >> + > >> >> + vsc.bpc = video->bpc; > >> >> + vsc.content_type = DP_CONTENT_TYPE_NOT_DEFINED; > >> >> + > >> >> + dw_dp_vsc_sdp_pack(&vsc, &sdp); > >> >> + > >> >> + return dw_dp_send_sdp(dp, &sdp); > >> >> +} > >> >> + > >> >> +static int dw_dp_video_set_pixel_mode(struct dw_dp *dp, u8 pixel_mode) > >> >> +{ > >> >> + switch (pixel_mode) { > >> >> + case DW_DP_MP_SINGLE_PIXEL: > >> >> + case DW_DP_MP_DUAL_PIXEL: > >> >> + case DW_DP_MP_QUAD_PIXEL: > >> >> + break; > >> >> + default: > >> >> + return -EINVAL; > >> > > >> >Is it possible? > >> > >> This IP is configurable for single/dual/quad pixel modes。 > >> It's quad pixel mode on rk3588 and dual pixel mode on rk3576, > >> so we add this check here. > > > >My question was slightly different: is it possible to end up here in the > >'default' branch? > > Sorry, I think I didn't get your key point here. > If someone give a invalid pixe mode(not any of SINGLE/DUAL/QUAD_PIXEL ), > then we go to default branch return -ENVIAL, then the dp_bridge enable stop. > This is what we want here. > > > > > > > >> > >> > >> > > >> >> + } > >> >> + > >> >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, PIXEL_MODE_SELECT, > >> >> + FIELD_PREP(PIXEL_MODE_SELECT, pixel_mode)); > >> >> + > >> >> + return 0; > >> >> +} > >> >> + > >> >> +static bool dw_dp_video_need_vsc_sdp(struct dw_dp *dp) > >> >> +{ > >> >> + struct dw_dp_link *link = &dp->link; > >> >> + struct dw_dp_video *video = &dp->video; > >> >> + > >> >> + if (!link->vsc_sdp_supported) > >> >> + return false; > >> >> + > >> >> + if (video->color_format == DRM_COLOR_FORMAT_YCBCR420) > >> >> + return true; > >> >> + > >> >> + return false; > >> >> +} > >> >> + > >> >> +static int dw_dp_video_set_msa(struct dw_dp *dp, u8 color_format, u8 bpc, > >> >> + u16 vstart, u16 hstart) > >> >> +{ > >> >> + u16 misc = 0; > >> >> + > >> >> + if (dw_dp_video_need_vsc_sdp(dp)) > >> >> + misc |= DP_MSA_MISC_COLOR_VSC_SDP; > >> >> + > >> >> + switch (color_format) { > >> >> + case DRM_COLOR_FORMAT_RGB444: > >> >> + misc |= DP_MSA_MISC_COLOR_RGB; > >> >> + break; > >> >> + case DRM_COLOR_FORMAT_YCBCR444: > >> >> + misc |= DP_MSA_MISC_COLOR_YCBCR_444_BT709; > >> >> + break; > >> >> + case DRM_COLOR_FORMAT_YCBCR422: > >> >> + misc |= DP_MSA_MISC_COLOR_YCBCR_422_BT709; > >> >> + break; > >> >> + case DRM_COLOR_FORMAT_YCBCR420: > >> >> + break; > >> >> + default: > >> >> + return -EINVAL; > >> >> + } > >> >> + > >> >> + switch (bpc) { > >> >> + case 6: > >> >> + misc |= DP_MSA_MISC_6_BPC; > >> >> + break; > >> >> + case 8: > >> >> + misc |= DP_MSA_MISC_8_BPC; > >> >> + break; > >> >> + case 10: > >> >> + misc |= DP_MSA_MISC_10_BPC; > >> >> + break; > >> >> + case 12: > >> >> + misc |= DP_MSA_MISC_12_BPC; > >> >> + break; > >> >> + case 16: > >> >> + misc |= DP_MSA_MISC_16_BPC; > >> >> + break; > >> >> + default: > >> >> + return -EINVAL; > >> >> + } > >> >> + > >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_MSA1, > >> >> + FIELD_PREP(VSTART, vstart) | FIELD_PREP(HSTART, hstart)); > >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_MSA2, FIELD_PREP(MISC0, misc)); > >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_MSA3, FIELD_PREP(MISC1, misc >> 8)); > >> >> + > >> >> + return 0; > >> >> +} > >> >> + > >> >> +static void dw_dp_video_disable(struct dw_dp *dp) > >> >> +{ > >> >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE, > >> >> + FIELD_PREP(VIDEO_STREAM_ENABLE, 0)); > >> >> +} > >> >> + > >> >> +static int dw_dp_video_enable(struct dw_dp *dp) > >> >> +{ > >> >> + struct dw_dp_video *video = &dp->video; > >> >> + struct dw_dp_link *link = &dp->link; > >> >> + struct drm_display_mode *mode = &video->mode; > >> >> + u8 color_format = video->color_format; > >> >> + u8 bpc = video->bpc; > >> >> + u8 pixel_mode = video->pixel_mode; > >> >> + u8 bpp = video->bpp, init_threshold, vic; > >> >> + u32 hactive, hblank, h_sync_width, h_front_porch; > >> >> + u32 vactive, vblank, v_sync_width, v_front_porch; > >> >> + u32 vstart = mode->vtotal - mode->vsync_start; > >> >> + u32 hstart = mode->htotal - mode->hsync_start; > >> >> + u32 peak_stream_bandwidth, link_bandwidth; > >> >> + u32 average_bytes_per_tu, average_bytes_per_tu_frac; > >> >> + u32 ts, hblank_interval; > >> >> + u32 value; > >> >> + int ret; > >> >> + > >> >> + ret = dw_dp_video_set_pixel_mode(dp, pixel_mode); > >> >> + if (ret) > >> >> + return ret; > >> >> + > >> >> + ret = dw_dp_video_set_msa(dp, color_format, bpc, vstart, hstart); > >> >> + if (ret) > >> >> + return ret; > >> >> + > >> >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_MAPPING, > >> >> + FIELD_PREP(VIDEO_MAPPING, video->video_mapping)); > >> >> + > >> >> + /* Configure DW_DP_VINPUT_POLARITY_CTRL register */ > >> >> + value = 0; > >> >> + if (mode->flags & DRM_MODE_FLAG_PHSYNC) > >> >> + value |= FIELD_PREP(HSYNC_IN_POLARITY, 1); > >> >> + if (mode->flags & DRM_MODE_FLAG_PVSYNC) > >> >> + value |= FIELD_PREP(VSYNC_IN_POLARITY, 1); > >> >> + regmap_write(dp->regmap, DW_DP_VINPUT_POLARITY_CTRL, value); > >> >> + > >> >> + /* Configure DW_DP_VIDEO_CONFIG1 register */ > >> >> + hactive = mode->hdisplay; > >> >> + hblank = mode->htotal - mode->hdisplay; > >> >> + value = FIELD_PREP(HACTIVE, hactive) | FIELD_PREP(HBLANK, hblank); > >> >> + if (mode->flags & DRM_MODE_FLAG_INTERLACE) > >> >> + value |= FIELD_PREP(I_P, 1); > >> >> + vic = drm_match_cea_mode(mode); > >> >> + if (vic == 5 || vic == 6 || vic == 7 || > >> >> + vic == 10 || vic == 11 || vic == 20 || > >> >> + vic == 21 || vic == 22 || vic == 39 || > >> >> + vic == 25 || vic == 26 || vic == 40 || > >> >> + vic == 44 || vic == 45 || vic == 46 || > >> >> + vic == 50 || vic == 51 || vic == 54 || > >> >> + vic == 55 || vic == 58 || vic == 59) > >> >> + value |= R_V_BLANK_IN_OSC; > >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG1, value); > >> >> + > >> >> + /* Configure DW_DP_VIDEO_CONFIG2 register */ > >> >> + vblank = mode->vtotal - mode->vdisplay; > >> >> + vactive = mode->vdisplay; > >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG2, > >> >> + FIELD_PREP(VBLANK, vblank) | FIELD_PREP(VACTIVE, vactive)); > >> >> + > >> >> + /* Configure DW_DP_VIDEO_CONFIG3 register */ > >> >> + h_sync_width = mode->hsync_end - mode->hsync_start; > >> >> + h_front_porch = mode->hsync_start - mode->hdisplay; > >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG3, > >> >> + FIELD_PREP(H_SYNC_WIDTH, h_sync_width) | > >> >> + FIELD_PREP(H_FRONT_PORCH, h_front_porch)); > >> >> + > >> >> + /* Configure DW_DP_VIDEO_CONFIG4 register */ > >> >> + v_sync_width = mode->vsync_end - mode->vsync_start; > >> >> + v_front_porch = mode->vsync_start - mode->vdisplay; > >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG4, > >> >> + FIELD_PREP(V_SYNC_WIDTH, v_sync_width) | > >> >> + FIELD_PREP(V_FRONT_PORCH, v_front_porch)); > >> >> + > >> >> + /* Configure DW_DP_VIDEO_CONFIG5 register */ > >> >> + peak_stream_bandwidth = mode->clock * bpp / 8; > >> >> + link_bandwidth = (link->rate / 1000) * link->lanes; > >> >> + ts = peak_stream_bandwidth * 64 / link_bandwidth; > >> >> + average_bytes_per_tu = ts / 1000; > >> >> + average_bytes_per_tu_frac = ts / 100 - average_bytes_per_tu * 10; > >> >> + if (pixel_mode == DW_DP_MP_SINGLE_PIXEL) { > >> >> + if (average_bytes_per_tu < 6) > >> >> + init_threshold = 32; > >> >> + else if (hblank <= 80 && color_format != DRM_COLOR_FORMAT_YCBCR420) > >> >> + init_threshold = 12; > >> >> + else if (hblank <= 40 && color_format == DRM_COLOR_FORMAT_YCBCR420) > >> >> + init_threshold = 3; > >> >> + else > >> >> + init_threshold = 16; > >> >> + } else { > >> >> + u32 t1 = 0, t2 = 0, t3 = 0; > >> >> + > >> >> + switch (bpc) { > >> >> + case 6: > >> >> + t1 = (4 * 1000 / 9) * link->lanes; > >> >> + break; > >> >> + case 8: > >> >> + if (color_format == DRM_COLOR_FORMAT_YCBCR422) { > >> >> + t1 = (1000 / 2) * link->lanes; > >> >> + } else { > >> >> + if (pixel_mode == DW_DP_MP_DUAL_PIXEL) > >> >> + t1 = (1000 / 3) * link->lanes; > >> >> + else > >> >> + t1 = (3000 / 16) * link->lanes; > >> >> + } > >> >> + break; > >> >> + case 10: > >> >> + if (color_format == DRM_COLOR_FORMAT_YCBCR422) > >> >> + t1 = (2000 / 5) * link->lanes; > >> >> + else > >> >> + t1 = (4000 / 15) * link->lanes; > >> >> + break; > >> >> + case 12: > >> >> + if (color_format == DRM_COLOR_FORMAT_YCBCR422) { > >> >> + if (pixel_mode == DW_DP_MP_DUAL_PIXEL) > >> >> + t1 = (1000 / 6) * link->lanes; > >> >> + else > >> >> + t1 = (1000 / 3) * link->lanes; > >> >> + } else { > >> >> + t1 = (2000 / 9) * link->lanes; > >> >> + } > >> >> + break; > >> >> + case 16: > >> >> + if (color_format != DRM_COLOR_FORMAT_YCBCR422 && > >> >> + pixel_mode == DW_DP_MP_DUAL_PIXEL) > >> >> + t1 = (1000 / 6) * link->lanes; > >> >> + else > >> >> + t1 = (1000 / 4) * link->lanes; > >> >> + break; > >> >> + default: > >> >> + return -EINVAL; > >> >> + } > >> >> + > >> >> + if (color_format == DRM_COLOR_FORMAT_YCBCR420) > >> >> + t2 = (link->rate / 4) * 1000 / (mode->clock / 2); > >> >> + else > >> >> + t2 = (link->rate / 4) * 1000 / mode->clock; > >> >> + > >> >> + if (average_bytes_per_tu_frac) > >> >> + t3 = average_bytes_per_tu + 1; > >> >> + else > >> >> + t3 = average_bytes_per_tu; > >> >> + init_threshold = t1 * t2 * t3 / (1000 * 1000); > >> >> + if (init_threshold <= 16 || average_bytes_per_tu < 10) > >> >> + init_threshold = 40; > >> >> + } > >> >> + > >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_CONFIG5, > >> >> + FIELD_PREP(INIT_THRESHOLD_HI, init_threshold >> 6) | > >> >> + FIELD_PREP(AVERAGE_BYTES_PER_TU_FRAC, average_bytes_per_tu_frac) | > >> >> + FIELD_PREP(INIT_THRESHOLD, init_threshold) | > >> >> + FIELD_PREP(AVERAGE_BYTES_PER_TU, average_bytes_per_tu)); > >> >> + > >> >> + /* Configure DW_DP_VIDEO_HBLANK_INTERVAL register */ > >> >> + hblank_interval = hblank * (link->rate / 4) / mode->clock; > >> >> + regmap_write(dp->regmap, DW_DP_VIDEO_HBLANK_INTERVAL, > >> >> + FIELD_PREP(HBLANK_INTERVAL_EN, 1) | > >> >> + FIELD_PREP(HBLANK_INTERVAL, hblank_interval)); > >> >> + > >> >> + /* Video stream enable */ > >> >> + regmap_update_bits(dp->regmap, DW_DP_VSAMPLE_CTRL, VIDEO_STREAM_ENABLE, > >> >> + FIELD_PREP(VIDEO_STREAM_ENABLE, 1)); > >> >> + > >> >> + if (dw_dp_video_need_vsc_sdp(dp)) > >> >> + dw_dp_send_vsc_sdp(dp); > >> >> + > >> >> + return 0; > >> >> +} > >> >> + > >> >> +static void dw_dp_hpd_init(struct dw_dp *dp) > >> >> +{ > >> >> + /* Enable all HPD interrupts */ > >> >> + regmap_update_bits(dp->regmap, DW_DP_HPD_INTERRUPT_ENABLE, > >> >> + HPD_UNPLUG_EN | HPD_PLUG_EN | HPD_IRQ_EN, > >> >> + FIELD_PREP(HPD_UNPLUG_EN, 1) | > >> >> + FIELD_PREP(HPD_PLUG_EN, 1) | > >> >> + FIELD_PREP(HPD_IRQ_EN, 1)); > >> >> + > >> >> + /* Enable all top-level interrupts */ > >> >> + regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE, > >> >> + HPD_EVENT_EN, FIELD_PREP(HPD_EVENT_EN, 1)); > >> >> +} > >> >> + > >> >> +static void dw_dp_aux_init(struct dw_dp *dp) > >> >> +{ > >> >> + regmap_update_bits(dp->regmap, DW_DP_GENERAL_INTERRUPT_ENABLE, > >> >> + AUX_REPLY_EVENT_EN, FIELD_PREP(AUX_REPLY_EVENT_EN, 1)); > >> >> +} > >> >> + > >> >> +static void dw_dp_init_hw(struct dw_dp *dp) > >> >> +{ > >> >> + regmap_update_bits(dp->regmap, DW_DP_CCTL, DEFAULT_FAST_LINK_TRAIN_EN, > >> >> + FIELD_PREP(DEFAULT_FAST_LINK_TRAIN_EN, 0)); > >> >> + > >> >> + dw_dp_hpd_init(dp); > >> >> + dw_dp_aux_init(dp); > >> >> +} > >> >> + > >> >> +static int dw_dp_aux_write_data(struct dw_dp *dp, const u8 *buffer, size_t size) > >> >> +{ > >> >> + size_t i, j; > >> >> + > >> >> + for (i = 0; i < DIV_ROUND_UP(size, 4); i++) { > >> >> + size_t num = min_t(size_t, size - i * 4, 4); > >> >> + u32 value = 0; > >> >> + > >> >> + for (j = 0; j < num; j++) > >> >> + value |= buffer[i * 4 + j] << (j * 8); > >> >> + > >> >> + regmap_write(dp->regmap, DW_DP_AUX_DATA0 + i * 4, value); > >> >> + } > >> >> + > >> >> + return size; > >> >> +} > >> >> + > >> >> +static int dw_dp_aux_read_data(struct dw_dp *dp, u8 *buffer, size_t size) > >> >> +{ > >> >> + size_t i, j; > >> >> + > >> >> + for (i = 0; i < DIV_ROUND_UP(size, 4); i++) { > >> >> + size_t num = min_t(size_t, size - i * 4, 4); > >> >> + u32 value; > >> >> + > >> >> + regmap_read(dp->regmap, DW_DP_AUX_DATA0 + i * 4, &value); > >> >> + > >> >> + for (j = 0; j < num; j++) > >> >> + buffer[i * 4 + j] = value >> (j * 8); > >> >> + } > >> >> + > >> >> + return size; > >> >> +} > >> >> + > >> >> +static ssize_t dw_dp_aux_transfer(struct drm_dp_aux *aux, > >> >> + struct drm_dp_aux_msg *msg) > >> >> +{ > >> >> + struct dw_dp *dp = container_of(aux, struct dw_dp, aux); > >> >> + unsigned long timeout = msecs_to_jiffies(10); > >> >> + u32 status, value; > >> >> + ssize_t ret = 0; > >> >> + > >> >> + if (WARN_ON(msg->size > 16)) > >> >> + return -E2BIG; > >> >> + > >> >> + switch (msg->request & ~DP_AUX_I2C_MOT) { > >> >> + case DP_AUX_NATIVE_WRITE: > >> >> + case DP_AUX_I2C_WRITE: > >> >> + case DP_AUX_I2C_WRITE_STATUS_UPDATE: > >> >> + ret = dw_dp_aux_write_data(dp, msg->buffer, msg->size); > >> >> + if (ret < 0) > >> >> + return ret; > >> >> + break; > >> >> + case DP_AUX_NATIVE_READ: > >> >> + case DP_AUX_I2C_READ: > >> >> + break; > >> >> + default: > >> >> + return -EINVAL; > >> >> + } > >> >> + > >> >> + if (msg->size > 0) > >> >> + value = FIELD_PREP(AUX_LEN_REQ, msg->size - 1); > >> >> + else > >> >> + value = FIELD_PREP(I2C_ADDR_ONLY, 1); > >> >> + value |= FIELD_PREP(AUX_CMD_TYPE, msg->request); > >> >> + value |= FIELD_PREP(AUX_ADDR, msg->address); > >> >> + regmap_write(dp->regmap, DW_DP_AUX_CMD, value); > >> >> + > >> >> + status = wait_for_completion_timeout(&dp->complete, timeout); > >> >> + if (!status) { > >> >> + dev_err(dp->dev, "timeout waiting for AUX reply\n"); > >> >> + return -ETIMEDOUT; > >> >> + } > >> >> + > >> >> + regmap_read(dp->regmap, DW_DP_AUX_STATUS, &value); > >> >> + if (value & AUX_TIMEOUT) > >> >> + return -ETIMEDOUT; > >> >> + > >> >> + msg->reply = FIELD_GET(AUX_STATUS, value); > >> >> + > >> >> + if (msg->size > 0 && msg->reply == DP_AUX_NATIVE_REPLY_ACK) { > >> >> + if (msg->request & DP_AUX_I2C_READ) { > >> >> + size_t count = FIELD_GET(AUX_BYTES_READ, value) - 1; > >> >> + > >> >> + if (count != msg->size) > >> >> + return -EBUSY; > >> >> + > >> >> + ret = dw_dp_aux_read_data(dp, msg->buffer, count); > >> >> + if (ret < 0) > >> >> + return ret; > >> >> + } > >> >> + } > >> >> + > >> >> + return ret; > >> >> +} > >> >> + > >> >> +/* > >> >> + * Limits for the video timing output by DP: > >> >> + * 1. the hfp should be 2 pixels aligned; > >> >> + * 2. the minimum hsync should be 9 pixel; > >> >> + * 3. the minimum hbp should be 16 pixel; > >> >> + */ > >> >> +static bool dw_dp_bridge_mode_fixup(struct drm_bridge *bridge, > >> >> + const struct drm_display_mode *mode, > >> >> + struct drm_display_mode *adjusted_mode) > >> >> +{ > >> >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> >> + int min_hbp = 16; > >> >> + int min_hsync = 9; > >> >> + > >> >> + if ((adjusted_mode->hsync_start - adjusted_mode->hdisplay) & 0x1) { > >> >> + adjusted_mode->hsync_start += 1; > >> >> + dev_warn(dp->dev, "hfp is not 2 pixeel aligned, fixup to aligned hfp\n"); > >> >> + } > >> >> + if (adjusted_mode->hsync_end - adjusted_mode->hsync_start < min_hsync) { > >> >> + adjusted_mode->hsync_end = adjusted_mode->hsync_start + min_hsync; > >> >> + dev_warn(dp->dev, "hsync is too narrow, fixup to min hsync:%d\n", min_hsync); > >> >> + } > >> >> + if (adjusted_mode->htotal - adjusted_mode->hsync_end < min_hbp) { > >> >> + adjusted_mode->htotal = adjusted_mode->hsync_end + min_hbp; > >> >> + dev_warn(dp->dev, "hbp is too narrow, fixup to min hbp:%d\n", min_hbp); > >> >> + } > >> >> + > >> >> + return true; > >> >> +} > >> >> + > >> >> +static enum drm_mode_status dw_dp_bridge_mode_valid(struct drm_bridge *bridge, > >> >> + const struct drm_display_info *info, > >> >> + const struct drm_display_mode *mode) > >> >> +{ > >> >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> >> + struct dw_dp_link *link = &dp->link; > >> >> + u32 min_bpp; > >> >> + > >> >> + if (info->color_formats & DRM_COLOR_FORMAT_YCBCR420 && > >> >> + link->vsc_sdp_supported && > >> >> + (drm_mode_is_420_only(info, mode) || drm_mode_is_420_also(info, mode))) > >> >> + min_bpp = 12; > >> >> + else if (info->color_formats & DRM_COLOR_FORMAT_YCBCR422) > >> >> + min_bpp = 16; > >> >> + else if (info->color_formats & DRM_COLOR_FORMAT_RGB444) > >> >> + min_bpp = 18; > >> >> + else > >> >> + min_bpp = 24; > >> >> + > >> >> + if (!link->vsc_sdp_supported && > >> >> + drm_mode_is_420_only(info, mode)) > >> >> + return MODE_NO_420; > >> >> + > >> >> + if (!dw_dp_bandwidth_ok(dp, mode, min_bpp, link->lanes, link->rate)) > >> >> + return MODE_CLOCK_HIGH; > >> >> + > >> >> + return MODE_OK; > >> >> +} > >> >> + > >> >> +static void dw_dp_bridge_atomic_pre_enable(struct drm_bridge *bridge, > >> >> + struct drm_bridge_state *old_bridge_state) > >> >> +{ > >> >> + struct drm_atomic_state *state = old_bridge_state->base.state; > >> >> + struct drm_crtc_state *crtc_state = bridge->encoder->crtc->state; > >> >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> >> + struct dw_dp_video *video = &dp->video; > >> >> + struct drm_display_mode *m = &video->mode; > >> >> + struct drm_bridge_state *bridge_state; > >> >> + const struct dw_dp_output_format *fmt; > >> >> + > >> >> + drm_mode_copy(m, &crtc_state->adjusted_mode); > >> >> + > >> >> + bridge_state = drm_atomic_get_new_bridge_state(state, bridge); > >> >> + fmt = dw_dp_get_output_format(bridge_state->output_bus_cfg.format); > >> > > >> >Should it be a part of the atomic_check() instead? > >> > >> Since we have modef_fixup in this driver, which is mutually exclusive with atomic_check. > > > >You can convet .mode_fixup into .atomic_check. The latter can change the > >crtc_state->adjusted_mode. It is better to reject the invalid > >combinations from the .atomic_check rather than having to handled them > >later. > > > >> > >> > >> > >> > > >> >> + > >> >> + video->video_mapping = fmt->video_mapping; > >> >> + video->color_format = fmt->color_format; > >> >> + video->bpc = fmt->bpc; > >> >> + video->bpp = fmt->bpp; > >> >> +} > >> >> + > >> >> +static bool dw_dp_needs_link_retrain(struct dw_dp *dp) > >> >> +{ > >> >> + struct dw_dp_link *link = &dp->link; > >> >> + u8 link_status[DP_LINK_STATUS_SIZE]; > >> >> + > >> >> + if (!dw_dp_link_train_valid(&link->train)) > >> >> + return false; > >> >> + > >> >> + if (drm_dp_dpcd_read_link_status(&dp->aux, link_status) < 0) > >> >> + return false; > >> >> + > >> >> + /* Retrain if Channel EQ or CR not ok */ > >> >> + return !drm_dp_channel_eq_ok(link_status, dp->link.lanes); > >> >> +} > >> >> + > >> >> +static void dw_dp_link_disable(struct dw_dp *dp) > >> >> +{ > >> >> + struct dw_dp_link *link = &dp->link; > >> >> + > >> >> + if (dw_dp_hpd_detect(dp)) > >> >> + dw_dp_link_power_down(dp); > >> >> + > >> >> + dw_dp_phy_xmit_enable(dp, 0); > >> >> + > >> >> + phy_power_off(dp->phy); > >> >> + > >> >> + link->train.clock_recovered = false; > >> >> + link->train.channel_equalized = false; > >> >> +} > >> >> + > >> >> +static int dw_dp_link_enable(struct dw_dp *dp) > >> >> +{ > >> >> + int ret; > >> >> + > >> >> + ret = phy_power_on(dp->phy); > >> >> + if (ret) > >> >> + return ret; > >> >> + > >> >> + ret = dw_dp_link_power_up(dp); > >> >> + if (ret < 0) > >> >> + return ret; > >> >> + > >> >> + ret = dw_dp_link_train(dp); > >> >> + > >> >> + return ret; > >> >> +} > >> >> + > >> >> +static void dw_dp_bridge_atomic_enable(struct drm_bridge *bridge, > >> >> + struct drm_bridge_state *old_state) > >> >> +{ > >> >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> >> + struct drm_atomic_state *state = old_state->base.state; > >> >> + struct drm_connector *connector; > >> >> + struct drm_connector_state *conn_state; > >> >> + int ret; > >> >> + > >> >> + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); > >> >> + if (!connector) { > >> >> + dev_err(dp->dev, "failed to get connector\n"); > >> >> + return; > >> >> + } > >> >> + > >> >> + conn_state = drm_atomic_get_new_connector_state(state, connector); > >> >> + if (!conn_state) { > >> >> + dev_err(dp->dev, "failed to get connector state\n"); > >> >> + return; > >> >> + } > >> >> + > >> >> + set_bit(0, dp->sdp_reg_bank); > >> >> + > >> >> + ret = dw_dp_link_enable(dp); > >> >> + if (ret < 0) { > >> >> + dev_err(dp->dev, "failed to enable link: %d\n", ret); > >> >> + return; > >> >> + } > >> >> + > >> >> + ret = dw_dp_video_enable(dp); > >> >> + if (ret < 0) { > >> >> + dev_err(dp->dev, "failed to enable video: %d\n", ret); > >> >> + return; > >> >> + } > >> >> +} > >> >> + > >> >> +static void dw_dp_reset(struct dw_dp *dp) > >> >> +{ > >> >> + int val; > >> >> + > >> >> + disable_irq(dp->irq); > >> >> + regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET, > >> >> + FIELD_PREP(CONTROLLER_RESET, 1)); > >> >> + udelay(10); > >> >> + regmap_update_bits(dp->regmap, DW_DP_SOFT_RESET_CTRL, CONTROLLER_RESET, > >> >> + FIELD_PREP(CONTROLLER_RESET, 0)); > >> >> + > >> >> + dw_dp_init_hw(dp); > >> >> + regmap_read_poll_timeout(dp->regmap, DW_DP_HPD_STATUS, val, > >> >> + FIELD_GET(HPD_HOT_PLUG, val), 200, 200000); > >> >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG); > >> >> + enable_irq(dp->irq); > >> >> +} > >> >> + > >> >> +static void dw_dp_bridge_atomic_disable(struct drm_bridge *bridge, > >> >> + struct drm_bridge_state *old_bridge_state) > >> >> +{ > >> >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> >> + > >> >> + dw_dp_video_disable(dp); > >> >> + dw_dp_link_disable(dp); > >> >> + bitmap_zero(dp->sdp_reg_bank, SDP_REG_BANK_SIZE); > >> >> + dw_dp_reset(dp); > >> >> +} > >> >> + > >> >> +static bool dw_dp_hpd_detect_link(struct dw_dp *dp) > >> >> +{ > >> >> + int ret; > >> >> + > >> >> + ret = phy_power_on(dp->phy); > >> >> + if (ret < 0) > >> >> + return false; > >> >> + ret = dw_dp_link_parse(dp); > >> >> + phy_power_off(dp->phy); > >> >> + > >> >> + return !ret; > >> >> +} > >> >> + > >> >> +static enum drm_connector_status dw_dp_bridge_detect(struct drm_bridge *bridge) > >> >> +{ > >> >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> >> + > >> >> + if (!dw_dp_hpd_detect(dp)) > >> >> + return connector_status_disconnected; > >> >> + > >> >> + if (!dw_dp_hpd_detect_link(dp)) > >> >> + return connector_status_disconnected; > >> >> + > >> >> + return connector_status_connected; > >> >> +} > >> >> + > >> >> +static const struct drm_edid *dw_dp_bridge_edid_read(struct drm_bridge *bridge, > >> >> + struct drm_connector *connector) > >> >> +{ > >> >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> >> + const struct drm_edid *edid; > >> >> + int ret; > >> >> + > >> >> + ret = phy_power_on(dp->phy); > >> >> + if (ret) > >> >> + return NULL; > >> >> + > >> >> + edid = drm_edid_read_ddc(connector, &dp->aux.ddc); > >> >> + > >> >> + phy_power_off(dp->phy); > >> >> + > >> >> + return edid; > >> >> +} > >> >> + > >> >> +static u32 *dw_dp_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge, > >> >> + struct drm_bridge_state *bridge_state, > >> >> + struct drm_crtc_state *crtc_state, > >> >> + struct drm_connector_state *conn_state, > >> >> + unsigned int *num_output_fmts) > >> >> +{ > >> >> + struct dw_dp *dp = bridge_to_dp(bridge); > >> >> + struct dw_dp_link *link = &dp->link; > >> >> + struct drm_display_info *di = &conn_state->connector->display_info; > >> >> + struct drm_display_mode mode = crtc_state->mode; > >> >> + const struct dw_dp_output_format *fmt; > >> >> + u32 i, j = 0; > >> >> + u32 *output_fmts; > >> >> + > >> >> + *num_output_fmts = 0; > >> >> + > >> >> + output_fmts = kcalloc(ARRAY_SIZE(dw_dp_output_formats), sizeof(*output_fmts), GFP_KERNEL); > >> >> + if (!output_fmts) > >> >> + return NULL; > >> >> + > >> >> + for (i = 0; i < ARRAY_SIZE(dw_dp_output_formats); i++) { > >> >> + fmt = &dw_dp_output_formats[i]; > >> >> + > >> >> + if (fmt->bpc > conn_state->max_bpc) > >> >> + continue; > >> >> + > >> >> + if (!(fmt->color_format & di->color_formats)) > >> >> + continue; > >> >> + > >> >> + if (fmt->color_format == DRM_COLOR_FORMAT_YCBCR420 && > >> >> + !link->vsc_sdp_supported) > >> >> + continue; > >> >> + > >> >> + if (fmt->color_format != DRM_COLOR_FORMAT_YCBCR420 && > >> >> + drm_mode_is_420_only(di, &mode)) > >> >> + continue; > >> >> + > >> >> + if (!dw_dp_bandwidth_ok(dp, &mode, fmt->bpp, link->lanes, link->rate)) > >> >> + continue; > >> >> + > >> >> + output_fmts[j++] = fmt->bus_format; > >> >> + } > >> >> + > >> >> + *num_output_fmts = j; > >> >> + > >> >> + return output_fmts; > >> >> +} > >> >> + > >> >> +static const struct drm_bridge_funcs dw_dp_bridge_funcs = { > >> >> + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, > >> >> + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, > >> >> + .atomic_reset = drm_atomic_helper_bridge_reset, > >> >> + .atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt, > >> >> + .atomic_get_output_bus_fmts = dw_dp_bridge_atomic_get_output_bus_fmts, > >> >> + .mode_fixup = dw_dp_bridge_mode_fixup, > >> >> + .mode_valid = dw_dp_bridge_mode_valid, > >> >> + .atomic_pre_enable = dw_dp_bridge_atomic_pre_enable, > >> >> + .atomic_enable = dw_dp_bridge_atomic_enable, > >> >> + .atomic_disable = dw_dp_bridge_atomic_disable, > >> >> + .detect = dw_dp_bridge_detect, > >> >> + .edid_read = dw_dp_bridge_edid_read, > >> >> +}; > >> >> + > >> >> +static int dw_dp_link_retrain(struct dw_dp *dp) > >> >> +{ > >> >> + struct drm_device *dev = dp->bridge.dev; > >> >> + struct drm_modeset_acquire_ctx ctx; > >> >> + int ret; > >> >> + > >> >> + if (!dw_dp_needs_link_retrain(dp)) > >> >> + return 0; > >> >> + > >> >> + dev_dbg(dp->dev, "Retraining link\n"); > >> >> + > >> >> + drm_modeset_acquire_init(&ctx, 0); > >> >> + for (;;) { > >> >> + ret = drm_modeset_lock(&dev->mode_config.connection_mutex, &ctx); > >> >> + if (ret != -EDEADLK) > >> >> + break; > >> > > >> >I think it's an error to continue in such a case. There should be a > >> >return after the loop if ret is not 0. > >> > >> Thank you for catching this, I will add a check fater the loop. > >> > >> > >> > > >> >> + > >> >> + drm_modeset_backoff(&ctx); > >> >> + } > >> >> + > >> >> + ret = dw_dp_link_train(dp); > >> >> + drm_modeset_drop_locks(&ctx); > >> >> + drm_modeset_acquire_fini(&ctx); > >> >> + > >> >> + return ret; > >> >> +} > >> >> + > >> >> +static void dw_dp_hpd_work(struct work_struct *work) > >> >> +{ > >> >> + struct dw_dp *dp = container_of(work, struct dw_dp, hpd_work); > >> >> + bool long_hpd; > >> >> + int ret; > >> >> + > >> >> + mutex_lock(&dp->irq_lock); > >> >> + long_hpd = dp->hotplug.long_hpd; > >> >> + mutex_unlock(&dp->irq_lock); > >> >> + > >> >> + dev_dbg(dp->dev, "[drm] Get hpd irq - %s\n", long_hpd ? "long" : "short"); > >> >> + > >> >> + if (!long_hpd) { > >> >> + if (dw_dp_needs_link_retrain(dp)) { > >> >> + ret = dw_dp_link_retrain(dp); > >> >> + if (ret) > >> >> + dev_warn(dp->dev, "Retrain link failed\n"); > >> >> + } > >> >> + } else { > >> >> + drm_helper_hpd_irq_event(dp->bridge.dev); > >> >> + } > >> >> +} > >> >> + > >> >> +static void dw_dp_handle_hpd_event(struct dw_dp *dp) > >> >> +{ > >> >> + u32 value; > >> >> + > >> >> + mutex_lock(&dp->irq_lock); > >> >> + regmap_read(dp->regmap, DW_DP_HPD_STATUS, &value); > >> >> + > >> >> + if (value & HPD_IRQ) { > >> >> + dev_dbg(dp->dev, "IRQ from the HPD\n"); > >> >> + dp->hotplug.long_hpd = false; > >> >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_IRQ); > >> >> + } > >> >> + > >> >> + if (value & HPD_HOT_PLUG) { > >> >> + dev_dbg(dp->dev, "Hot plug detected\n"); > >> >> + dp->hotplug.long_hpd = true; > >> >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_PLUG); > >> >> + } > >> >> + > >> >> + if (value & HPD_HOT_UNPLUG) { > >> >> + dev_dbg(dp->dev, "Unplug detected\n"); > >> >> + dp->hotplug.long_hpd = true; > >> >> + regmap_write(dp->regmap, DW_DP_HPD_STATUS, HPD_HOT_UNPLUG); > >> >> + } > >> >> + mutex_unlock(&dp->irq_lock); > >> >> + > >> >> + schedule_work(&dp->hpd_work); > >> >> +} > >> >> + > >> >> +static irqreturn_t dw_dp_irq(int irq, void *data) > >> >> +{ > >> >> + struct dw_dp *dp = data; > >> >> + u32 value; > >> >> + > >> >> + regmap_read(dp->regmap, DW_DP_GENERAL_INTERRUPT, &value); > >> >> + if (!value) > >> >> + return IRQ_NONE; > >> >> + > >> >> + if (value & HPD_EVENT) > >> >> + dw_dp_handle_hpd_event(dp); > >> >> + > >> >> + if (value & AUX_REPLY_EVENT) { > >> >> + regmap_write(dp->regmap, DW_DP_GENERAL_INTERRUPT, AUX_REPLY_EVENT); > >> >> + complete(&dp->complete); > >> >> + } > >> >> + > >> >> + return IRQ_HANDLED; > >> >> +} > >> >> + > >> >> +static const struct regmap_range dw_dp_readable_ranges[] = { > >> >> + regmap_reg_range(DW_DP_VERSION_NUMBER, DW_DP_ID), > >> >> + regmap_reg_range(DW_DP_CONFIG_REG1, DW_DP_CONFIG_REG3), > >> >> + regmap_reg_range(DW_DP_CCTL, DW_DP_SOFT_RESET_CTRL), > >> >> + regmap_reg_range(DW_DP_VSAMPLE_CTRL, DW_DP_VIDEO_HBLANK_INTERVAL), > >> >> + regmap_reg_range(DW_DP_AUD_CONFIG1, DW_DP_AUD_CONFIG1), > >> >> + regmap_reg_range(DW_DP_SDP_VERTICAL_CTRL, DW_DP_SDP_STATUS_EN), > >> >> + regmap_reg_range(DW_DP_PHYIF_CTRL, DW_DP_PHYIF_PWRDOWN_CTRL), > >> >> + regmap_reg_range(DW_DP_AUX_CMD, DW_DP_AUX_DATA3), > >> >> + regmap_reg_range(DW_DP_GENERAL_INTERRUPT, DW_DP_HPD_INTERRUPT_ENABLE), > >> >> +}; > >> >> + > >> >> +static const struct regmap_access_table dw_dp_readable_table = { > >> >> + .yes_ranges = dw_dp_readable_ranges, > >> >> + .n_yes_ranges = ARRAY_SIZE(dw_dp_readable_ranges), > >> >> +}; > >> >> + > >> >> +static const struct regmap_config dw_dp_regmap_config = { > >> >> + .reg_bits = 32, > >> >> + .reg_stride = 4, > >> >> + .val_bits = 32, > >> >> + .fast_io = true, > >> >> + .max_register = DW_DP_MAX_REGISTER, > >> >> + .rd_table = &dw_dp_readable_table, > >> >> +}; > >> >> + > >> >> +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, > >> >> + const struct dw_dp_plat_data *plat_data) > >> >> +{ > >> >> + struct platform_device *pdev = to_platform_device(dev); > >> >> + struct dw_dp *dp; > >> >> + struct drm_bridge *bridge; > >> >> + void __iomem *res; > >> >> + int ret; > >> >> + > >> >> + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); > >> >> + if (!dp) > >> >> + return ERR_PTR(-ENOMEM); > >> >> + > >> >> + dp->dev = dev; > >> >> + dp->video.pixel_mode = DW_DP_MP_QUAD_PIXEL; > >> >> + > >> >> + dp->plat_data = plat_data; > >> >> + bridge = &dp->bridge; > >> >> + mutex_init(&dp->irq_lock); > >> >> + INIT_WORK(&dp->hpd_work, dw_dp_hpd_work); > >> >> + init_completion(&dp->complete); > >> >> + > >> >> + res = devm_platform_ioremap_resource(pdev, 0); > >> >> + if (IS_ERR(res)) > >> >> + return ERR_CAST(res); > >> >> + > >> >> + dp->regmap = devm_regmap_init_mmio(dev, res, &dw_dp_regmap_config); > >> >> + if (IS_ERR(dp->regmap)) { > >> >> + dev_err_probe(dev, PTR_ERR(dp->regmap), "failed to create regmap\n"); > >> >> + return ERR_CAST(dp->regmap); > >> >> + } > >> >> + > >> >> + dp->phy = devm_of_phy_get(dev, dev->of_node, NULL); > >> >> + if (IS_ERR(dp->phy)) { > >> >> + dev_err_probe(dev, PTR_ERR(dp->phy), "failed to get phy\n"); > >> >> + return ERR_CAST(dp->phy); > >> >> + } > >> >> + > >> >> + dp->apb_clk = devm_clk_get_enabled(dev, "apb"); > >> >> + if (IS_ERR(dp->apb_clk)) { > >> >> + dev_err_probe(dev, PTR_ERR(dp->apb_clk), "failed to get apb clock\n"); > >> >> + return ERR_CAST(dp->apb_clk); > >> >> + } > >> >> + > >> >> + dp->aux_clk = devm_clk_get_enabled(dev, "aux"); > >> >> + if (IS_ERR(dp->aux_clk)) { > >> >> + dev_err_probe(dev, PTR_ERR(dp->aux_clk), "failed to get aux clock\n"); > >> >> + return ERR_CAST(dp->aux_clk); > >> >> + } > >> >> + > >> >> + dp->i2s_clk = devm_clk_get(dev, "i2s"); > >> >> + if (IS_ERR(dp->i2s_clk)) { > >> >> + dev_err_probe(dev, PTR_ERR(dp->i2s_clk), "failed to get i2s clock\n"); > >> >> + return ERR_CAST(dp->i2s_clk); > >> >> + } > >> >> + > >> >> + dp->spdif_clk = devm_clk_get(dev, "spdif"); > >> >> + if (IS_ERR(dp->spdif_clk)) { > >> >> + dev_err_probe(dev, PTR_ERR(dp->spdif_clk), "failed to get spdif clock\n"); > >> >> + return ERR_CAST(dp->spdif_clk); > >> >> + } > >> >> + > >> >> + dp->hdcp_clk = devm_clk_get(dev, "hdcp"); > >> >> + if (IS_ERR(dp->hdcp_clk)) { > >> >> + dev_err_probe(dev, PTR_ERR(dp->hdcp_clk), "failed to get hdcp clock\n"); > >> >> + return ERR_CAST(dp->hdcp_clk); > >> >> + } > >> >> + > >> >> + dp->rstc = devm_reset_control_get(dev, NULL); > >> >> + if (IS_ERR(dp->rstc)) { > >> >> + dev_err_probe(dev, PTR_ERR(dp->rstc), "failed to get reset control\n"); > >> >> + return ERR_CAST(dp->rstc); > >> >> + } > >> >> + > >> >> + dp->irq = platform_get_irq(pdev, 0); > >> >> + if (dp->irq < 0) > >> >> + return ERR_PTR(ret); > >> >> + > >> >> + ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq, > >> >> + IRQF_ONESHOT, dev_name(dev), dp); > >> > > >> >Are you ready to handle the IRQ here, before the bridge is filled? > >> > >> I will move to devm_request_threaded_irq after dw_dp_init_hw. > >> > >> > >> > > >> >> + if (ret) { > >> >> + dev_err_probe(dev, ret, "failed to request irq\n"); > >> >> + return ERR_PTR(ret); > >> >> + } > >> >> + > >> >> + bridge->of_node = dev->of_node; > >> >> + bridge->funcs = &dw_dp_bridge_funcs; > >> >> + bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; > >> >> + bridge->type = DRM_MODE_CONNECTOR_DisplayPort; > >> >> + bridge->ycbcr_420_allowed = true; > >> >> + bridge->vendor = "Synopsys"; > >> >> + bridge->product = "DW DP TX"; > >> > > >> >These two are unused > >> > > >> >> + > >> >> + platform_set_drvdata(pdev, dp); > >> > > >> >Unused > >> > >> I will remove them. > >> > >> Thanks again. > >> > >> > >> > > >> >> + > >> >> + dp->aux.dev = dev; > >> >> + dp->aux.drm_dev = encoder->dev; > >> >> + dp->aux.name = dev_name(dev); > >> >> + dp->aux.transfer = dw_dp_aux_transfer; > >> >> + ret = drm_dp_aux_register(&dp->aux); > >> >> + if (ret) { > >> >> + dev_err_probe(dev, ret, "Aux register failed\n"); > >> >> + return ERR_PTR(ret); > >> >> + } > >> >> + > >> >> + ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); > >> >> + if (ret) > >> >> + dev_err_probe(dev, ret, "Failed to attach bridge\n"); > >> >> + > >> >> + dw_dp_init_hw(dp); > >> >> + > >> >> + return dp; > >> >> +} > >> >> +MODULE_AUTHOR("Andy Yan <andyshrk@163.com>"); > >> >> +MODULE_DESCRIPTION("DW DP Core Library"); > >> >> +MODULE_LICENSE("GPL"); > >> >> diff --git a/include/drm/bridge/dw_dp.h b/include/drm/bridge/dw_dp.h > >> >> new file mode 100644 > >> >> index 000000000000..07845f609eca > >> >> --- /dev/null > >> >> +++ b/include/drm/bridge/dw_dp.h > >> >> @@ -0,0 +1,19 @@ > >> >> +/* SPDX-License-Identifier: GPL-2.0-or-later */ > >> >> +/* > >> >> + * Copyright (c) 2025 Rockchip Electronics Co., Ltd. > >> >> + */ > >> >> + > >> >> +#ifndef __DW_DP__ > >> >> +#define __DW_DP__ > >> >> + > >> >> +struct device; > >> >> +struct drm_encoder; > >> >> +struct dw_dp; > >> >> + > >> >> +struct dw_dp_plat_data { > >> >> + u32 max_link_rate; > >> >> +}; > >> >> + > >> >> +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, > >> >> + const struct dw_dp_plat_data *plat_data); > >> >> +#endif /* __DW_DP__ */ > >> >> -- > >> >> 2.34.1 > >> >> > >> > > >> >-- > >> >With best wishes > >> >Dmitry > > > >-- > >With best wishes > >Dmitry -- With best wishes Dmitry ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re:Re: [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library 2025-03-02 16:21 ` Dmitry Baryshkov 2025-03-04 7:16 ` Andy Yan @ 2025-03-04 10:16 ` Andy Yan 1 sibling, 0 replies; 32+ messages in thread From: Andy Yan @ 2025-03-04 10:16 UTC (permalink / raw) To: Dmitry Baryshkov Cc: heiko, hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan Hi Dmitry, 在 2025-03-03 00:21:06,"Dmitry Baryshkov" <dmitry.baryshkov@linaro.org> 写道: >On Sun, Mar 02, 2025 at 06:34:22PM +0800, Andy Yan wrote: >> >> >> Hi Dmitry, >> Thank you for your review。 >> Please also review my inline reply. >> >> 在 2025-03-02 02:14:19,"Dmitry Baryshkov" <dmitry.baryshkov@linaro.org> 写道: >> >On Sun, Feb 23, 2025 at 07:30:25PM +0800, Andy Yan wrote: >> >> From: Andy Yan <andy.yan@rock-chips.com> >> >> >> >> The DW DP TX Controller is compliant with the DisplayPort Specification >> >> Version 1.4 with the following features: >> >> >> >> * DisplayPort 1.4a >> >> * Main Link: 1/2/4 lanes >> >> * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps >> >> * AUX channel 1Mbps >> >> * Single Stream Transport(SST) >> >> * Multistream Transport (MST) >> >> *Type-C support (alternate mode) >> >> * HDCP 2.2, HDCP 1.3 >> >> * Supports up to 8/10 bits per color component >> >> * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0 >> >> * Pixel clock up to 594MHz >> >> * I2S, SPDIF audio interface >> >> >> >> Add library with common helpers to make it can be shared with >> >> other SoC. >> >> >> >> Signed-off-by: Andy Yan <andy.yan@rock-chips.com> >> >> >> >> drm/bridge: cleanup >> > >> >Stray line? >> >> Sorry, will be removed. >> >> > >> >> >> >> --- >> >> >> >> drivers/gpu/drm/bridge/synopsys/Kconfig | 7 + >> >> drivers/gpu/drm/bridge/synopsys/Makefile | 1 + >> >> drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2155 ++++++++++++++++++++++ >> >> include/drm/bridge/dw_dp.h | 19 + >> >> 4 files changed, 2182 insertions(+) >> >> create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-dp.c >> >> create mode 100644 include/drm/bridge/dw_dp.h >> >> >> >> diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig >> >> index f3ab2f985f8c..2c5e532410de 100644 >> >> --- a/drivers/gpu/drm/bridge/synopsys/Kconfig >> >> +++ b/drivers/gpu/drm/bridge/synopsys/Kconfig >> >> @@ -1,4 +1,11 @@ >> >> # SPDX-License-Identifier: GPL-2.0-only >> >> +config DRM_DW_DP >> >> + tristate >> >> + select DRM_DISPLAY_HELPER >> >> + select DRM_DISPLAY_DP_HELPER >> >> + select DRM_KMS_HELPER >> >> + select REGMAP_MMIO >> >> + >> >> config DRM_DW_HDMI .............. >> >> + vsc.pixelformat = DP_PIXELFORMAT_RGB; >> >> + break; >> >> + } >> >> + >> >> + if (video->color_format == DRM_COLOR_FORMAT_RGB444) { >> >> + vsc.colorimetry = DP_COLORIMETRY_DEFAULT; >> >> + vsc.dynamic_range = DP_DYNAMIC_RANGE_VESA; >> >> + } else { >> >> + vsc.colorimetry = DP_COLORIMETRY_BT709_YCC; >> >> + vsc.dynamic_range = DP_DYNAMIC_RANGE_CTA; >> >> + } >> >> + >> >> + vsc.bpc = video->bpc; >> >> + vsc.content_type = DP_CONTENT_TYPE_NOT_DEFINED; >> >> + >> >> + dw_dp_vsc_sdp_pack(&vsc, &sdp); >> >> + >> >> + return dw_dp_send_sdp(dp, &sdp); >> >> +} >> >> + >> >> +static int dw_dp_video_set_pixel_mode(struct dw_dp *dp, u8 pixel_mode) >> >> +{ >> >> + switch (pixel_mode) { >> >> + case DW_DP_MP_SINGLE_PIXEL: >> >> + case DW_DP_MP_DUAL_PIXEL: >> >> + case DW_DP_MP_QUAD_PIXEL: >> >> + break; >> >> + default: >> >> + return -EINVAL; >> > >> >Is it possible? >> >> This IP is configurable for single/dual/quad pixel modes。 >> It's quad pixel mode on rk3588 and dual pixel mode on rk3576, >> so we add this check here. > >My question was slightly different: is it possible to end up here in the >'default' branch? Sorry, I think I didn't get your key point here. If someone gives an invalid pixel mode(not any of SINGLE/DUAL/QUAD_PIXEL ), then we go to the default branch return -ENVIAL, then the dp_bridge_enable call stop. This is what we want here. > >> >> >> > >> >> + } >> >> +#endif /* __DW_DP__ */ >> >> -- >> >> 2.34.1 >> >> >> > >> >-- >> >With best wishes >> >Dmitry > >-- >With best wishes >Dmitry ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library 2025-03-01 18:14 ` Dmitry Baryshkov 2025-03-02 10:34 ` Andy Yan @ 2025-03-03 8:59 ` Yubing Zhang 2025-03-03 8:59 ` Yubing Zhang 2025-03-03 12:57 ` Dmitry Baryshkov 1 sibling, 2 replies; 32+ messages in thread From: Yubing Zhang @ 2025-03-03 8:59 UTC (permalink / raw) To: Dmitry Baryshkov, Andy Yan Cc: neil.armstrong, sebastian.reichel, heiko, devicetree, hjc, mripard, linux-kernel, linux-rockchip, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel Hi Dmitry, On 2025/3/2 2:14, Dmitry Baryshkov wrote: > On Sun, Feb 23, 2025 at 07:30:25PM +0800, Andy Yan wrote: >> From: Andy Yan <andy.yan@rock-chips.com> >> >> The DW DP TX Controller is compliant with the DisplayPort Specification >> Version 1.4 with the following features: >> >> * DisplayPort 1.4a >> * Main Link: 1/2/4 lanes >> * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps >> * AUX channel 1Mbps >> * Single Stream Transport(SST) >> * Multistream Transport (MST) >> *Type-C support (alternate mode) >> * HDCP 2.2, HDCP 1.3 >> * Supports up to 8/10 bits per color component >> * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0 >> * Pixel clock up to 594MHz >> * I2S, SPDIF audio interface >> >> Add library with common helpers to make it can be shared with >> other SoC. >> >> Signed-off-by: Andy Yan <andy.yan@rock-chips.com> >> >> drm/bridge: cleanup > > Stray line? > >> >> --- >> >> drivers/gpu/drm/bridge/synopsys/Kconfig | 7 + >> drivers/gpu/drm/bridge/synopsys/Makefile | 1 + >> drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2155 ++++++++++++++++++++++ >> include/drm/bridge/dw_dp.h | 19 + >> 4 files changed, 2182 insertions(+) >> create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-dp.c ...... >> + >> +static u8 dw_dp_voltage_max(u8 preemph) >> +{ >> + switch (preemph & DP_TRAIN_PRE_EMPHASIS_MASK) { >> + case DP_TRAIN_PRE_EMPH_LEVEL_0: >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_3; >> + case DP_TRAIN_PRE_EMPH_LEVEL_1: >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_2; >> + case DP_TRAIN_PRE_EMPH_LEVEL_2: >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_1; >> + case DP_TRAIN_PRE_EMPH_LEVEL_3: >> + default: >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_0; >> + } >> +} >> + >> +static void dw_dp_link_get_adjustments(struct dw_dp_link *link, >> + u8 status[DP_LINK_STATUS_SIZE]) >> +{ >> + struct dw_dp_link_train_set *adjust = &link->train.adjust; >> + u8 v = 0; >> + u8 p = 0; >> + unsigned int i; >> + >> + for (i = 0; i < link->lanes; i++) { >> + v = drm_dp_get_adjust_request_voltage(status, i); >> + p = drm_dp_get_adjust_request_pre_emphasis(status, i); >> + if (p >= DP_TRAIN_PRE_EMPH_LEVEL_3) { >> + adjust->pre_emphasis[i] = DP_TRAIN_PRE_EMPH_LEVEL_3 >> >> + DP_TRAIN_PRE_EMPHASIS_SHIFT; >> + adjust->pre_max_reached[i] = true; >> + } else { >> + adjust->pre_emphasis[i] = p >> DP_TRAIN_PRE_EMPHASIS_SHIFT; >> + adjust->pre_max_reached[i] = false; >> + } >> + v = min(v, dw_dp_voltage_max(p)); >> + if (v >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3) { >> + adjust->voltage_swing[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_3 >> >> + DP_TRAIN_VOLTAGE_SWING_SHIFT; >> + adjust->voltage_max_reached[i] = true; >> + } else { >> + adjust->voltage_swing[i] = v >> DP_TRAIN_VOLTAGE_SWING_SHIFT; >> + adjust->voltage_max_reached[i] = false; >> + } >> + } >> +} >> + >> +static void dw_dp_link_train_adjust(struct dw_dp_link_train *train) >> +{ >> + struct dw_dp_link_train_set *request = &train->request; >> + struct dw_dp_link_train_set *adjust = &train->adjust; >> + unsigned int i; >> + >> + for (i = 0; i < 4; i++) { > > Shouldn't it be a loop up to link->lanes? > >> + if (request->voltage_swing[i] != adjust->voltage_swing[i]) >> + request->voltage_swing[i] = adjust->voltage_swing[i]; >> + if (request->voltage_max_reached[i] != adjust->voltage_max_reached[i]) >> + request->voltage_max_reached[i] = adjust->voltage_max_reached[i]; >> + } >> + >> + for (i = 0; i < 4; i++) { >> + if (request->pre_emphasis[i] != adjust->pre_emphasis[i]) >> + request->pre_emphasis[i] = adjust->pre_emphasis[i]; >> + if (request->pre_max_reached[i] != adjust->pre_max_reached[i]) >> + request->pre_max_reached[i] = adjust->pre_max_reached[i]; > > Why do you need separate request and adjustment structs? During link training cr sequence, if dprx keep the LANEx_CR_DONE bit(s) cleared, the request and adjustment structs are used to check the old and new valud of ADJUST_REQUEST_LANEx_y register(s) is changed or not. > >> + } >> +} >> + >> +static int dw_dp_link_clock_recovery(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + u8 status[DP_LINK_STATUS_SIZE]; >> + unsigned int tries = 0; >> + int ret; >> + >> + ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1); >> + if (ret) >> + return ret; >> + >> + for (;;) { >> + ret = dw_dp_link_train_update_vs_emph(dp); >> + if (ret) >> + return ret; >> + >> + drm_dp_link_train_clock_recovery_delay(&dp->aux, link->dpcd); >> + >> + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); >> + if (ret < 0) { >> + dev_err(dp->dev, "failed to read link status: %d\n", ret); >> + return ret; >> + } >> + >> + if (drm_dp_clock_recovery_ok(status, link->lanes)) { >> + link->train.clock_recovered = true; >> + break; >> + } >> + >> + dw_dp_link_get_adjustments(link, status); >> + >> + if (link->train.request.voltage_swing[0] == >> + link->train.adjust.voltage_swing[0]) > > Should this take all lanes to account? I think it might be posssible to > drop the adjust / request split and adjust tries in > dw_dp_link_get_adjustments() instead. Yes, here shall compare both swing and pre-emphasis for all lanes. It's a good idea to drop the adjust / request split. The swing and pre-emphasis compare just need by cr sequence. But both cr and eq sequences use dw_dp_link_get_adjustments(). It will be better to delete dw_dp_link_train_adjust() and add a new function to adjust tries for cr sequence. > >> + tries++; >> + else >> + tries = 0; >> + >> + if (tries == 5) >> + break; >> + >> + dw_dp_link_train_adjust(&link->train); >> + } >> + >> + return 0; >> +} >> + >> +static int dw_dp_link_channel_equalization(struct dw_dp *dp) >> +{ >> + struct dw_dp_link *link = &dp->link; >> + u8 status[DP_LINK_STATUS_SIZE], pattern; >> + unsigned int tries; >> + int ret; >> + >> + if (link->caps.tps4_supported) >> + pattern = DP_TRAINING_PATTERN_4; >> + else if (link->caps.tps3_supported) >> + pattern = DP_TRAINING_PATTERN_3; >> + else >> + pattern = DP_TRAINING_PATTERN_2; >> + ret = dw_dp_link_train_set_pattern(dp, pattern); >> + if (ret) >> + return ret; >> + >> + for (tries = 1; tries < 5; tries++) { >> + ret = dw_dp_link_train_update_vs_emph(dp); >> + if (ret) >> + return ret; >> + >> + drm_dp_link_train_channel_eq_delay(&dp->aux, link->dpcd); >> + >> + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); >> + if (ret < 0) >> + return ret; >> + >> + if (!drm_dp_clock_recovery_ok(status, link->lanes)) { >> + dev_err(dp->dev, "clock recovery lost while equalizing channel\n"); >> + link->train.clock_recovered = false; >> + break; >> + } >> + >> + if (drm_dp_channel_eq_ok(status, link->lanes)) { >> + link->train.channel_equalized = true; >> + break; >> + } >> + >> + dw_dp_link_get_adjustments(link, status); >> + dw_dp_link_train_adjust(&link->train); >> + } >> + >> + return 0; >> +} ...... >> + >> +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, >> + const struct dw_dp_plat_data *plat_data); >> +#endif /* __DW_DP__ */ >> -- >> 2.34.1 >> > ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library 2025-03-03 8:59 ` Yubing Zhang @ 2025-03-03 8:59 ` Yubing Zhang 2025-03-03 12:57 ` Dmitry Baryshkov 1 sibling, 0 replies; 32+ messages in thread From: Yubing Zhang @ 2025-03-03 8:59 UTC (permalink / raw) To: Dmitry Baryshkov, Andy Yan Cc: neil.armstrong, sebastian.reichel, heiko, devicetree, hjc, mripard, linux-kernel, linux-rockchip, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel Hi Dmitry, On 2025/3/2 2:14, Dmitry Baryshkov wrote: > On Sun, Feb 23, 2025 at 07:30:25PM +0800, Andy Yan wrote: >> From: Andy Yan <andy.yan@rock-chips.com> >> >> The DW DP TX Controller is compliant with the DisplayPort Specification >> Version 1.4 with the following features: >> >> * DisplayPort 1.4a >> * Main Link: 1/2/4 lanes >> * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps >> * AUX channel 1Mbps >> * Single Stream Transport(SST) >> * Multistream Transport (MST) >> *Type-C support (alternate mode) >> * HDCP 2.2, HDCP 1.3 >> * Supports up to 8/10 bits per color component >> * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0 >> * Pixel clock up to 594MHz >> * I2S, SPDIF audio interface >> >> Add library with common helpers to make it can be shared with >> other SoC. >> >> Signed-off-by: Andy Yan <andy.yan@rock-chips.com> >> >> drm/bridge: cleanup > > Stray line? > >> >> --- >> >> drivers/gpu/drm/bridge/synopsys/Kconfig | 7 + >> drivers/gpu/drm/bridge/synopsys/Makefile | 1 + >> drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2155 ++++++++++++++++++++++ >> include/drm/bridge/dw_dp.h | 19 + >> 4 files changed, 2182 insertions(+) >> create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-dp.c ...... >> + >> +static u8 dw_dp_voltage_max(u8 preemph) >> +{ >> + switch (preemph & DP_TRAIN_PRE_EMPHASIS_MASK) { >> + case DP_TRAIN_PRE_EMPH_LEVEL_0: >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_3; >> + case DP_TRAIN_PRE_EMPH_LEVEL_1: >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_2; >> + case DP_TRAIN_PRE_EMPH_LEVEL_2: >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_1; >> + case DP_TRAIN_PRE_EMPH_LEVEL_3: >> + default: >> + return DP_TRAIN_VOLTAGE_SWING_LEVEL_0; >> + } >> +} >> + >> +static void dw_dp_link_get_adjustments(struct dw_dp_link *link, >> + u8 status[DP_LINK_STATUS_SIZE]) >> +{ >> + struct dw_dp_link_train_set *adjust = &link->train.adjust; >> + u8 v = 0; >> + u8 p = 0; >> + unsigned int i; >> + >> + for (i = 0; i < link->lanes; i++) { >> + v = drm_dp_get_adjust_request_voltage(status, i); >> + p = drm_dp_get_adjust_request_pre_emphasis(status, i); >> + if (p >= DP_TRAIN_PRE_EMPH_LEVEL_3) { >> + adjust->pre_emphasis[i] = DP_TRAIN_PRE_EMPH_LEVEL_3 >> >> + DP_TRAIN_PRE_EMPHASIS_SHIFT; >> + adjust->pre_max_reached[i] = true; >> + } else { >> + adjust->pre_emphasis[i] = p >> DP_TRAIN_PRE_EMPHASIS_SHIFT; >> + adjust->pre_max_reached[i] = false; >> + } >> + v = min(v, dw_dp_voltage_max(p)); >> + if (v >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3) { >> + adjust->voltage_swing[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_3 >> >> + DP_TRAIN_VOLTAGE_SWING_SHIFT; >> + adjust->voltage_max_reached[i] = true; >> + } else { >> + adjust->voltage_swing[i] = v >> DP_TRAIN_VOLTAGE_SWING_SHIFT; >> + adjust->voltage_max_reached[i] = false; >> + } >> + } >> +} >> + >> +static void dw_dp_link_train_adjust(struct dw_dp_link_train *train) >> +{ >> + struct dw_dp_link_train_set *request = &train->request; >> + struct dw_dp_link_train_set *adjust = &train->adjust; >> + unsigned int i; >> + >> + for (i = 0; i < 4; i++) { > > Shouldn't it be a loop up to link->lanes? > >> + if (request->voltage_swing[i] != adjust->voltage_swing[i]) >> + request->voltage_swing[i] = adjust->voltage_swing[i]; >> + if (request->voltage_max_reached[i] != adjust->voltage_max_reached[i]) >> + request->voltage_max_reached[i] = adjust->voltage_max_reached[i]; >> + } >> + >> + for (i = 0; i < 4; i++) { >> + if (request->pre_emphasis[i] != adjust->pre_emphasis[i]) >> + request->pre_emphasis[i] = adjust->pre_emphasis[i]; >> + if (request->pre_max_reached[i] != adjust->pre_max_reached[i]) >> + request->pre_max_reached[i] = adjust->pre_max_reached[i]; > > Why do you need separate request and adjustment structs? During link training cr sequence, if dprx keep the LANEx_CR_DONE bit(s) cleared, the request and adjustment structs are used to check the old and new valud of ADJUST_REQUEST_LANEx_y register(s) is changed or not ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library 2025-03-03 8:59 ` Yubing Zhang 2025-03-03 8:59 ` Yubing Zhang @ 2025-03-03 12:57 ` Dmitry Baryshkov 1 sibling, 0 replies; 32+ messages in thread From: Dmitry Baryshkov @ 2025-03-03 12:57 UTC (permalink / raw) To: Yubing Zhang Cc: Andy Yan, neil.armstrong, sebastian.reichel, heiko, devicetree, hjc, mripard, linux-kernel, linux-rockchip, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel On Mon, Mar 03, 2025 at 04:59:50PM +0800, Yubing Zhang wrote: > Hi Dmitry, > > On 2025/3/2 2:14, Dmitry Baryshkov wrote: > > On Sun, Feb 23, 2025 at 07:30:25PM +0800, Andy Yan wrote: > > > From: Andy Yan <andy.yan@rock-chips.com> > > > > > > The DW DP TX Controller is compliant with the DisplayPort Specification > > > Version 1.4 with the following features: > > > > > > * DisplayPort 1.4a > > > * Main Link: 1/2/4 lanes > > > * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps > > > * AUX channel 1Mbps > > > * Single Stream Transport(SST) > > > * Multistream Transport (MST) > > > *Type-C support (alternate mode) > > > * HDCP 2.2, HDCP 1.3 > > > * Supports up to 8/10 bits per color component > > > * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0 > > > * Pixel clock up to 594MHz > > > * I2S, SPDIF audio interface > > > > > > Add library with common helpers to make it can be shared with > > > other SoC. > > > > > > Signed-off-by: Andy Yan <andy.yan@rock-chips.com> > > > > > > drm/bridge: cleanup > > > > Stray line? > > > > > > > > --- > > > > > > drivers/gpu/drm/bridge/synopsys/Kconfig | 7 + > > > drivers/gpu/drm/bridge/synopsys/Makefile | 1 + > > > drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2155 ++++++++++++++++++++++ > > > include/drm/bridge/dw_dp.h | 19 + > > > 4 files changed, 2182 insertions(+) > > > create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-dp.c > > ...... > > > > + > > > +static u8 dw_dp_voltage_max(u8 preemph) > > > +{ > > > + switch (preemph & DP_TRAIN_PRE_EMPHASIS_MASK) { > > > + case DP_TRAIN_PRE_EMPH_LEVEL_0: > > > + return DP_TRAIN_VOLTAGE_SWING_LEVEL_3; > > > + case DP_TRAIN_PRE_EMPH_LEVEL_1: > > > + return DP_TRAIN_VOLTAGE_SWING_LEVEL_2; > > > + case DP_TRAIN_PRE_EMPH_LEVEL_2: > > > + return DP_TRAIN_VOLTAGE_SWING_LEVEL_1; > > > + case DP_TRAIN_PRE_EMPH_LEVEL_3: > > > + default: > > > + return DP_TRAIN_VOLTAGE_SWING_LEVEL_0; > > > + } > > > +} > > > + > > > +static void dw_dp_link_get_adjustments(struct dw_dp_link *link, > > > + u8 status[DP_LINK_STATUS_SIZE]) > > > +{ > > > + struct dw_dp_link_train_set *adjust = &link->train.adjust; > > > + u8 v = 0; > > > + u8 p = 0; > > > + unsigned int i; > > > + > > > + for (i = 0; i < link->lanes; i++) { > > > + v = drm_dp_get_adjust_request_voltage(status, i); > > > + p = drm_dp_get_adjust_request_pre_emphasis(status, i); > > > + if (p >= DP_TRAIN_PRE_EMPH_LEVEL_3) { > > > + adjust->pre_emphasis[i] = DP_TRAIN_PRE_EMPH_LEVEL_3 >> > > > + DP_TRAIN_PRE_EMPHASIS_SHIFT; > > > + adjust->pre_max_reached[i] = true; > > > + } else { > > > + adjust->pre_emphasis[i] = p >> DP_TRAIN_PRE_EMPHASIS_SHIFT; > > > + adjust->pre_max_reached[i] = false; > > > + } > > > + v = min(v, dw_dp_voltage_max(p)); > > > + if (v >= DP_TRAIN_VOLTAGE_SWING_LEVEL_3) { > > > + adjust->voltage_swing[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL_3 >> > > > + DP_TRAIN_VOLTAGE_SWING_SHIFT; > > > + adjust->voltage_max_reached[i] = true; > > > + } else { > > > + adjust->voltage_swing[i] = v >> DP_TRAIN_VOLTAGE_SWING_SHIFT; > > > + adjust->voltage_max_reached[i] = false; > > > + } > > > + } > > > +} > > > + > > > +static void dw_dp_link_train_adjust(struct dw_dp_link_train *train) > > > +{ > > > + struct dw_dp_link_train_set *request = &train->request; > > > + struct dw_dp_link_train_set *adjust = &train->adjust; > > > + unsigned int i; > > > + > > > + for (i = 0; i < 4; i++) { > > > > Shouldn't it be a loop up to link->lanes? > > > > > + if (request->voltage_swing[i] != adjust->voltage_swing[i]) > > > + request->voltage_swing[i] = adjust->voltage_swing[i]; > > > + if (request->voltage_max_reached[i] != adjust->voltage_max_reached[i]) > > > + request->voltage_max_reached[i] = adjust->voltage_max_reached[i]; > > > + } > > > + > > > + for (i = 0; i < 4; i++) { > > > + if (request->pre_emphasis[i] != adjust->pre_emphasis[i]) > > > + request->pre_emphasis[i] = adjust->pre_emphasis[i]; > > > + if (request->pre_max_reached[i] != adjust->pre_max_reached[i]) > > > + request->pre_max_reached[i] = adjust->pre_max_reached[i]; > > > > Why do you need separate request and adjustment structs? > During link training cr sequence, if dprx keep the LANEx_CR_DONE bit(s) > cleared, the request and adjustment structs are used to check the > old and new valud of ADJUST_REQUEST_LANEx_y register(s) is changed or not. This can be compared in dw_dp_link_get_adjustments(), before / while updating the request data. > > > > > > + } > > > +} > > > + > > > +static int dw_dp_link_clock_recovery(struct dw_dp *dp) > > > +{ > > > + struct dw_dp_link *link = &dp->link; > > > + u8 status[DP_LINK_STATUS_SIZE]; > > > + unsigned int tries = 0; > > > + int ret; > > > + > > > + ret = dw_dp_link_train_set_pattern(dp, DP_TRAINING_PATTERN_1); > > > + if (ret) > > > + return ret; > > > + > > > + for (;;) { > > > + ret = dw_dp_link_train_update_vs_emph(dp); > > > + if (ret) > > > + return ret; > > > + > > > + drm_dp_link_train_clock_recovery_delay(&dp->aux, link->dpcd); > > > + > > > + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); > > > + if (ret < 0) { > > > + dev_err(dp->dev, "failed to read link status: %d\n", ret); > > > + return ret; > > > + } > > > + > > > + if (drm_dp_clock_recovery_ok(status, link->lanes)) { > > > + link->train.clock_recovered = true; > > > + break; > > > + } > > > + > > > + dw_dp_link_get_adjustments(link, status); > > > + > > > + if (link->train.request.voltage_swing[0] == > > > + link->train.adjust.voltage_swing[0]) > > > > Should this take all lanes to account? I think it might be posssible to > > drop the adjust / request split and adjust tries in > > dw_dp_link_get_adjustments() instead. > Yes, here shall compare both swing and pre-emphasis for all lanes. It's a > good idea to drop the adjust / request split. The swing and pre-emphasis > compare just need by cr sequence. But both cr and eq sequences use > dw_dp_link_get_adjustments(). It will be better to delete > dw_dp_link_train_adjust() and add a new function to adjust tries for cr > sequence. SGTM. > > > > > + tries++; > > > + else > > > + tries = 0; > > > + > > > + if (tries == 5) > > > + break; > > > + > > > + dw_dp_link_train_adjust(&link->train); > > > + } > > > + > > > + return 0; > > > +} > > > + > > > +static int dw_dp_link_channel_equalization(struct dw_dp *dp) > > > +{ > > > + struct dw_dp_link *link = &dp->link; > > > + u8 status[DP_LINK_STATUS_SIZE], pattern; > > > + unsigned int tries; > > > + int ret; > > > + > > > + if (link->caps.tps4_supported) > > > + pattern = DP_TRAINING_PATTERN_4; > > > + else if (link->caps.tps3_supported) > > > + pattern = DP_TRAINING_PATTERN_3; > > > + else > > > + pattern = DP_TRAINING_PATTERN_2; > > > + ret = dw_dp_link_train_set_pattern(dp, pattern); > > > + if (ret) > > > + return ret; > > > + > > > + for (tries = 1; tries < 5; tries++) { > > > + ret = dw_dp_link_train_update_vs_emph(dp); > > > + if (ret) > > > + return ret; > > > + > > > + drm_dp_link_train_channel_eq_delay(&dp->aux, link->dpcd); > > > + > > > + ret = drm_dp_dpcd_read_link_status(&dp->aux, status); > > > + if (ret < 0) > > > + return ret; > > > + > > > + if (!drm_dp_clock_recovery_ok(status, link->lanes)) { > > > + dev_err(dp->dev, "clock recovery lost while equalizing channel\n"); > > > + link->train.clock_recovered = false; > > > + break; > > > + } > > > + > > > + if (drm_dp_channel_eq_ok(status, link->lanes)) { > > > + link->train.channel_equalized = true; > > > + break; > > > + } > > > + > > > + dw_dp_link_get_adjustments(link, status); > > > + dw_dp_link_train_adjust(&link->train); > > > + } > > > + > > > + return 0; > > > +} > ...... > > > + > > > +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, > > > + const struct dw_dp_plat_data *plat_data); > > > +#endif /* __DW_DP__ */ > > > -- > > > 2.34.1 > > > > > > -- With best wishes Dmitry ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library 2025-02-23 11:30 ` [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library Andy Yan 2025-03-01 18:14 ` Dmitry Baryshkov @ 2025-03-09 6:51 ` FUKAUMI Naoki 1 sibling, 0 replies; 32+ messages in thread From: FUKAUMI Naoki @ 2025-03-09 6:51 UTC (permalink / raw) To: Andy Yan, heiko Cc: neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel Hi Andy, On 2/23/25 20:30, Andy Yan wrote: (snip) > +struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder, > + const struct dw_dp_plat_data *plat_data) > +{ > + struct platform_device *pdev = to_platform_device(dev); > + struct dw_dp *dp; > + struct drm_bridge *bridge; > + void __iomem *res; > + int ret; > + > + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); > + if (!dp) > + return ERR_PTR(-ENOMEM); > + > + dp->dev = dev; > + dp->video.pixel_mode = DW_DP_MP_QUAD_PIXEL; > + > + dp->plat_data = plat_data; > + bridge = &dp->bridge; > + mutex_init(&dp->irq_lock); > + INIT_WORK(&dp->hpd_work, dw_dp_hpd_work); > + init_completion(&dp->complete); > + > + res = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(res)) > + return ERR_CAST(res); > + > + dp->regmap = devm_regmap_init_mmio(dev, res, &dw_dp_regmap_config); > + if (IS_ERR(dp->regmap)) { > + dev_err_probe(dev, PTR_ERR(dp->regmap), "failed to create regmap\n"); > + return ERR_CAST(dp->regmap); > + } > + > + dp->phy = devm_of_phy_get(dev, dev->of_node, NULL); > + if (IS_ERR(dp->phy)) { > + dev_err_probe(dev, PTR_ERR(dp->phy), "failed to get phy\n"); > + return ERR_CAST(dp->phy); > + } > + > + dp->apb_clk = devm_clk_get_enabled(dev, "apb"); > + if (IS_ERR(dp->apb_clk)) { > + dev_err_probe(dev, PTR_ERR(dp->apb_clk), "failed to get apb clock\n"); > + return ERR_CAST(dp->apb_clk); > + } > + > + dp->aux_clk = devm_clk_get_enabled(dev, "aux"); > + if (IS_ERR(dp->aux_clk)) { > + dev_err_probe(dev, PTR_ERR(dp->aux_clk), "failed to get aux clock\n"); > + return ERR_CAST(dp->aux_clk); > + } > + > + dp->i2s_clk = devm_clk_get(dev, "i2s"); > + if (IS_ERR(dp->i2s_clk)) { > + dev_err_probe(dev, PTR_ERR(dp->i2s_clk), "failed to get i2s clock\n"); > + return ERR_CAST(dp->i2s_clk); > + } > + > + dp->spdif_clk = devm_clk_get(dev, "spdif"); > + if (IS_ERR(dp->spdif_clk)) { > + dev_err_probe(dev, PTR_ERR(dp->spdif_clk), "failed to get spdif clock\n"); > + return ERR_CAST(dp->spdif_clk); > + } > + > + dp->hdcp_clk = devm_clk_get(dev, "hdcp"); > + if (IS_ERR(dp->hdcp_clk)) { > + dev_err_probe(dev, PTR_ERR(dp->hdcp_clk), "failed to get hdcp clock\n"); > + return ERR_CAST(dp->hdcp_clk); > + } > + > + dp->rstc = devm_reset_control_get(dev, NULL); > + if (IS_ERR(dp->rstc)) { > + dev_err_probe(dev, PTR_ERR(dp->rstc), "failed to get reset control\n"); > + return ERR_CAST(dp->rstc); > + } > + > + dp->irq = platform_get_irq(pdev, 0); > + if (dp->irq < 0) > + return ERR_PTR(ret); > + > + ret = devm_request_threaded_irq(dev, dp->irq, NULL, dw_dp_irq, > + IRQF_ONESHOT, dev_name(dev), dp); > + if (ret) { > + dev_err_probe(dev, ret, "failed to request irq\n"); > + return ERR_PTR(ret); > + } > + > + bridge->of_node = dev->of_node; > + bridge->funcs = &dw_dp_bridge_funcs; > + bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; > + bridge->type = DRM_MODE_CONNECTOR_DisplayPort; > + bridge->ycbcr_420_allowed = true; > + bridge->vendor = "Synopsys"; > + bridge->product = "DW DP TX"; > + > + platform_set_drvdata(pdev, dp); > + > + dp->aux.dev = dev; > + dp->aux.drm_dev = encoder->dev; > + dp->aux.name = dev_name(dev); > + dp->aux.transfer = dw_dp_aux_transfer; > + ret = drm_dp_aux_register(&dp->aux); > + if (ret) { > + dev_err_probe(dev, ret, "Aux register failed\n"); > + return ERR_PTR(ret); > + } > + > + ret = drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); > + if (ret) > + dev_err_probe(dev, ret, "Failed to attach bridge\n"); > + > + dw_dp_init_hw(dp); > + > + return dp; > +} EXPORT_SYMBOL_GPL(dw_dp_bind); is required to build it as a module. Best regards, -- FUKAUMI Naoki Radxa Computer (Shenzhen) Co., Ltd. ^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 3/6] drm/rockchip: Add RK3588 DPTX output support 2025-02-23 11:30 [PATCH 0/6] Add support for RK3588 DisplayPort Controller Andy Yan 2025-02-23 11:30 ` [PATCH 1/6] dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller Andy Yan 2025-02-23 11:30 ` [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library Andy Yan @ 2025-02-23 11:30 ` Andy Yan 2025-02-24 14:56 ` kernel test robot 2025-03-01 18:19 ` Dmitry Baryshkov 2025-02-23 11:30 ` [PATCH 4/6] arm64: dts: rockchip: Add DP0 for rk3588 Andy Yan ` (3 subsequent siblings) 6 siblings, 2 replies; 32+ messages in thread From: Andy Yan @ 2025-02-23 11:30 UTC (permalink / raw) To: heiko Cc: hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan From: Andy Yan <andy.yan@rock-chips.com> Add driver extension for Synopsys DesignWare DPTX IP used on Rockchip RK3588 SoC. Signed-off-by: Andy Yan <andy.yan@rock-chips.com> --- drivers/gpu/drm/rockchip/Kconfig | 7 + drivers/gpu/drm/rockchip/Makefile | 1 + drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 162 ++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 1 + drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 1 + 5 files changed, 172 insertions(+) create mode 100644 drivers/gpu/drm/rockchip/dw_dp-rockchip.c diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index 26c4410b2407..c8638baf9641 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -8,6 +8,7 @@ config DRM_ROCKCHIP select DRM_PANEL select VIDEOMODE_HELPERS select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP + select DRM_DW_DP if ROCKCHIP_DW_DP select DRM_DW_HDMI if ROCKCHIP_DW_HDMI select DRM_DW_HDMI_QP if ROCKCHIP_DW_HDMI_QP select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI @@ -58,6 +59,12 @@ config ROCKCHIP_CDN_DP RK3399 based SoC, you should select this option. +config ROCKCHIP_DW_DP + bool "Rockchip specific extensions for Synopsys DW DP" + help + Choose this option for Synopsys DesignWare Cores DisplayPort + transmit controller support on Rockchip SoC. + config ROCKCHIP_DW_HDMI bool "Rockchip specific extensions for Synopsys DW HDMI" help diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index 2b867cebbc12..097f062399c7 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -14,6 +14,7 @@ rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI_QP) += dw_hdmi_qp-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI2) += dw-mipi-dsi2-rockchip.o +rockchipdrm-$(CONFIG_ROCKCHIP_DW_DP) += dw_dp-rockchip.o rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o rockchipdrm-$(CONFIG_ROCKCHIP_LVDS) += rockchip_lvds.o rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c new file mode 100644 index 000000000000..b41a41eb74d7 --- /dev/null +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020 Rockchip Electronics Co., Ltd. + * + * Author: Zhang Yubing <yubing.zhang@rock-chips.com> + * Author: Andy Yan <andy.yan@rock-chips.com> + */ + +#include <linux/component.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <drm/bridge/dw_dp.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_bridge.h> +#include <drm/drm_bridge_connector.h> +#include <drm/drm_of.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_simple_kms_helper.h> + +#include <uapi/linux/media-bus-format.h> +#include <uapi/linux/videodev2.h> + +#include "rockchip_drm_drv.h" +#include "rockchip_drm_vop.h" + +struct rockchip_dw_dp { + struct dw_dp *base; + struct device *dev; + struct rockchip_encoder encoder; +}; + +static inline struct rockchip_dw_dp *encoder_to_dp(struct drm_encoder *encoder) +{ + struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); + + return container_of(rkencoder, struct rockchip_dw_dp, encoder); +} + +static int dw_dp_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); + struct drm_atomic_state *state = conn_state->state; + struct drm_display_info *di = &conn_state->connector->display_info; + struct drm_bridge *bridge = drm_bridge_chain_get_first_bridge(encoder); + struct drm_bridge_state *bridge_state = drm_atomic_get_new_bridge_state(state, bridge); + u32 bus_format = bridge_state->input_bus_cfg.format; + + switch (bus_format) { + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: + s->output_mode = ROCKCHIP_OUT_MODE_YUV420; + break; + case MEDIA_BUS_FMT_YUYV10_1X20: + case MEDIA_BUS_FMT_YUYV8_1X16: + s->output_mode = ROCKCHIP_OUT_MODE_S888_DUMMY; + break; + case MEDIA_BUS_FMT_RGB101010_1X30: + case MEDIA_BUS_FMT_RGB888_1X24: + case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: + case MEDIA_BUS_FMT_YUV10_1X30: + case MEDIA_BUS_FMT_YUV8_1X24: + default: + s->output_mode = ROCKCHIP_OUT_MODE_AAAA; + break; + } + + s->output_type = DRM_MODE_CONNECTOR_DisplayPort; + s->bus_format = bus_format; + s->bus_flags = di->bus_flags; + s->color_space = V4L2_COLORSPACE_DEFAULT; + + return 0; +} + +static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = { + .atomic_check = dw_dp_encoder_atomic_check, +}; + +static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data) +{ + struct dw_dp_plat_data plat_data; + struct drm_device *drm_dev = data; + struct rockchip_dw_dp *dp; + struct drm_encoder *encoder; + struct drm_connector *connector; + int ret; + + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); + if (!dp) + return -ENOMEM; + + dp->dev = dev; + plat_data.max_link_rate = 810000; + encoder = &dp->encoder.encoder; + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node); + rockchip_drm_encoder_set_crtc_endpoint_id(&dp->encoder, dev->of_node, 0, 0); + + drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS); + drm_encoder_helper_add(encoder, &dw_dp_encoder_helper_funcs); + + dp->base = dw_dp_bind(dev, encoder, &plat_data); + if (IS_ERR(dp->base)) { + ret = PTR_ERR(dp->base); + drm_encoder_cleanup(encoder); + return ret; + } + + connector = drm_bridge_connector_init(drm_dev, encoder); + if (IS_ERR(connector)) { + ret = PTR_ERR(connector); + dev_err(dev, "Failed to init bridge connector: %d\n", ret); + return ret; + } + + drm_connector_attach_encoder(connector, encoder); + + return 0; +} + +static void dw_dp_rockchip_unbind(struct device *dev, struct device *master, void *data) +{ + struct rockchip_dw_dp *dp = dev_get_drvdata(dev); + + drm_encoder_cleanup(&dp->encoder.encoder); +} + +static const struct component_ops dw_dp_rockchip_component_ops = { + .bind = dw_dp_rockchip_bind, + .unbind = dw_dp_rockchip_unbind, +}; + +static int dw_dp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + return component_add(dev, &dw_dp_rockchip_component_ops); +} + +static void dw_dp_remove(struct platform_device *pdev) +{ + struct rockchip_dw_dp *dp = platform_get_drvdata(pdev); + + component_del(dp->dev, &dw_dp_rockchip_component_ops); +} + +static const struct of_device_id dw_dp_of_match[] = { + { .compatible = "rockchip,rk3588-dp", }, + {} +}; +MODULE_DEVICE_TABLE(of, dw_dp_of_match); + +struct platform_driver dw_dp_driver = { + .probe = dw_dp_probe, + .remove = dw_dp_remove, + .driver = { + .name = "dw-dp", + .of_match_table = dw_dp_of_match, + }, +}; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c index 9cf311b5dec1..2b245491c71d 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -529,6 +529,7 @@ static int __init rockchip_drm_init(void) ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver, CONFIG_ROCKCHIP_ANALOGIX_DP); ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP); + ADD_ROCKCHIP_SUB_DRIVER(dw_dp_driver, CONFIG_ROCKCHIP_DW_DP); ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver, CONFIG_ROCKCHIP_DW_HDMI); ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_qp_rockchip_pltfm_driver, diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h index c183e82a42a5..2e86ad00979c 100644 --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -87,6 +87,7 @@ int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rencoder, struct device_node *np, int port, int reg); int rockchip_drm_endpoint_is_subdriver(struct device_node *ep); extern struct platform_driver cdn_dp_driver; +extern struct platform_driver dw_dp_driver; extern struct platform_driver dw_hdmi_rockchip_pltfm_driver; extern struct platform_driver dw_hdmi_qp_rockchip_pltfm_driver; extern struct platform_driver dw_mipi_dsi_rockchip_driver; -- 2.34.1 ^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH 3/6] drm/rockchip: Add RK3588 DPTX output support 2025-02-23 11:30 ` [PATCH 3/6] drm/rockchip: Add RK3588 DPTX output support Andy Yan @ 2025-02-24 14:56 ` kernel test robot 2025-03-01 18:19 ` Dmitry Baryshkov 1 sibling, 0 replies; 32+ messages in thread From: kernel test robot @ 2025-02-24 14:56 UTC (permalink / raw) To: Andy Yan, heiko Cc: oe-kbuild-all, hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan Hi Andy, kernel test robot noticed the following build errors: [auto build test ERROR on linus/master] [also build test ERROR on v6.14-rc4] [cannot apply to rockchip/for-next] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Andy-Yan/dt-bindings-display-rockchip-Add-schema-for-RK3588-DPTX-Controller/20250223-193323 base: linus/master patch link: https://lore.kernel.org/r/20250223113036.74252-4-andyshrk%40163.com patch subject: [PATCH 3/6] drm/rockchip: Add RK3588 DPTX output support config: sparc-allmodconfig (https://download.01.org/0day-ci/archive/20250224/202502242201.3fE7QpMy-lkp@intel.com/config) compiler: sparc64-linux-gcc (GCC) 14.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250224/202502242201.3fE7QpMy-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202502242201.3fE7QpMy-lkp@intel.com/ All errors (new ones prefixed by >>, old ones prefixed by <<): >> ERROR: modpost: "dw_dp_bind" [drivers/gpu/drm/rockchip/rockchipdrm.ko] undefined! -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 3/6] drm/rockchip: Add RK3588 DPTX output support 2025-02-23 11:30 ` [PATCH 3/6] drm/rockchip: Add RK3588 DPTX output support Andy Yan 2025-02-24 14:56 ` kernel test robot @ 2025-03-01 18:19 ` Dmitry Baryshkov 2025-03-02 10:37 ` Andy Yan 1 sibling, 1 reply; 32+ messages in thread From: Dmitry Baryshkov @ 2025-03-01 18:19 UTC (permalink / raw) To: Andy Yan Cc: heiko, hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan On Sun, Feb 23, 2025 at 07:30:26PM +0800, Andy Yan wrote: > From: Andy Yan <andy.yan@rock-chips.com> > > Add driver extension for Synopsys DesignWare DPTX IP used > on Rockchip RK3588 SoC. > > Signed-off-by: Andy Yan <andy.yan@rock-chips.com> > --- > > drivers/gpu/drm/rockchip/Kconfig | 7 + > drivers/gpu/drm/rockchip/Makefile | 1 + > drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 162 ++++++++++++++++++++ > drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 1 + > drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 1 + > 5 files changed, 172 insertions(+) > create mode 100644 drivers/gpu/drm/rockchip/dw_dp-rockchip.c > > diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig > index 26c4410b2407..c8638baf9641 100644 > --- a/drivers/gpu/drm/rockchip/Kconfig > +++ b/drivers/gpu/drm/rockchip/Kconfig > @@ -8,6 +8,7 @@ config DRM_ROCKCHIP > select DRM_PANEL > select VIDEOMODE_HELPERS > select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP > + select DRM_DW_DP if ROCKCHIP_DW_DP > select DRM_DW_HDMI if ROCKCHIP_DW_HDMI > select DRM_DW_HDMI_QP if ROCKCHIP_DW_HDMI_QP > select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI > @@ -58,6 +59,12 @@ config ROCKCHIP_CDN_DP > RK3399 based SoC, you should select this > option. > > +config ROCKCHIP_DW_DP > + bool "Rockchip specific extensions for Synopsys DW DP" > + help > + Choose this option for Synopsys DesignWare Cores DisplayPort > + transmit controller support on Rockchip SoC. > + > config ROCKCHIP_DW_HDMI > bool "Rockchip specific extensions for Synopsys DW HDMI" > help > diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile > index 2b867cebbc12..097f062399c7 100644 > --- a/drivers/gpu/drm/rockchip/Makefile > +++ b/drivers/gpu/drm/rockchip/Makefile > @@ -14,6 +14,7 @@ rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o > rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI_QP) += dw_hdmi_qp-rockchip.o > rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi-rockchip.o > rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI2) += dw-mipi-dsi2-rockchip.o > +rockchipdrm-$(CONFIG_ROCKCHIP_DW_DP) += dw_dp-rockchip.o > rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o > rockchipdrm-$(CONFIG_ROCKCHIP_LVDS) += rockchip_lvds.o > rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o > diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c > new file mode 100644 > index 000000000000..b41a41eb74d7 > --- /dev/null > +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c > @@ -0,0 +1,162 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (c) 2020 Rockchip Electronics Co., Ltd. > + * > + * Author: Zhang Yubing <yubing.zhang@rock-chips.com> > + * Author: Andy Yan <andy.yan@rock-chips.com> > + */ > + > +#include <linux/component.h> > +#include <linux/of_device.h> > +#include <linux/platform_device.h> > +#include <drm/bridge/dw_dp.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_bridge.h> > +#include <drm/drm_bridge_connector.h> > +#include <drm/drm_of.h> > +#include <drm/drm_print.h> > +#include <drm/drm_probe_helper.h> > +#include <drm/drm_simple_kms_helper.h> > + > +#include <uapi/linux/media-bus-format.h> > +#include <uapi/linux/videodev2.h> I'd say, include those two headers directly. Rockchip / Synopsys are the only drivers including these uapi headers directly. > + > +#include "rockchip_drm_drv.h" > +#include "rockchip_drm_vop.h" > + > +struct rockchip_dw_dp { > + struct dw_dp *base; > + struct device *dev; > + struct rockchip_encoder encoder; > +}; > + > +static inline struct rockchip_dw_dp *encoder_to_dp(struct drm_encoder *encoder) > +{ > + struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); > + > + return container_of(rkencoder, struct rockchip_dw_dp, encoder); > +} > + > +static int dw_dp_encoder_atomic_check(struct drm_encoder *encoder, > + struct drm_crtc_state *crtc_state, > + struct drm_connector_state *conn_state) > +{ > + struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); > + struct drm_atomic_state *state = conn_state->state; > + struct drm_display_info *di = &conn_state->connector->display_info; > + struct drm_bridge *bridge = drm_bridge_chain_get_first_bridge(encoder); > + struct drm_bridge_state *bridge_state = drm_atomic_get_new_bridge_state(state, bridge); > + u32 bus_format = bridge_state->input_bus_cfg.format; > + > + switch (bus_format) { > + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: > + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: > + s->output_mode = ROCKCHIP_OUT_MODE_YUV420; > + break; > + case MEDIA_BUS_FMT_YUYV10_1X20: > + case MEDIA_BUS_FMT_YUYV8_1X16: > + s->output_mode = ROCKCHIP_OUT_MODE_S888_DUMMY; > + break; > + case MEDIA_BUS_FMT_RGB101010_1X30: > + case MEDIA_BUS_FMT_RGB888_1X24: > + case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: > + case MEDIA_BUS_FMT_YUV10_1X30: > + case MEDIA_BUS_FMT_YUV8_1X24: > + default: > + s->output_mode = ROCKCHIP_OUT_MODE_AAAA; > + break; > + } > + > + s->output_type = DRM_MODE_CONNECTOR_DisplayPort; > + s->bus_format = bus_format; > + s->bus_flags = di->bus_flags; > + s->color_space = V4L2_COLORSPACE_DEFAULT; > + > + return 0; > +} > + > +static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = { > + .atomic_check = dw_dp_encoder_atomic_check, > +}; > + > +static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data) > +{ > + struct dw_dp_plat_data plat_data; > + struct drm_device *drm_dev = data; > + struct rockchip_dw_dp *dp; > + struct drm_encoder *encoder; > + struct drm_connector *connector; > + int ret; > + > + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); > + if (!dp) > + return -ENOMEM; > + > + dp->dev = dev; > + plat_data.max_link_rate = 810000; > + encoder = &dp->encoder.encoder; > + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node); > + rockchip_drm_encoder_set_crtc_endpoint_id(&dp->encoder, dev->of_node, 0, 0); > + > + drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS); drmm_encoder_init() ? This will allow you to get rid of drm_encoder_cleanup() calls. > + drm_encoder_helper_add(encoder, &dw_dp_encoder_helper_funcs); > + > + dp->base = dw_dp_bind(dev, encoder, &plat_data); > + if (IS_ERR(dp->base)) { > + ret = PTR_ERR(dp->base); > + drm_encoder_cleanup(encoder); > + return ret; > + } > + > + connector = drm_bridge_connector_init(drm_dev, encoder); > + if (IS_ERR(connector)) { > + ret = PTR_ERR(connector); > + dev_err(dev, "Failed to init bridge connector: %d\n", ret); > + return ret; > + } > + > + drm_connector_attach_encoder(connector, encoder); > + > + return 0; > +} > + > +static void dw_dp_rockchip_unbind(struct device *dev, struct device *master, void *data) > +{ > + struct rockchip_dw_dp *dp = dev_get_drvdata(dev); > + > + drm_encoder_cleanup(&dp->encoder.encoder); > +} > + > +static const struct component_ops dw_dp_rockchip_component_ops = { > + .bind = dw_dp_rockchip_bind, > + .unbind = dw_dp_rockchip_unbind, > +}; > + > +static int dw_dp_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + > + return component_add(dev, &dw_dp_rockchip_component_ops); > +} > + > +static void dw_dp_remove(struct platform_device *pdev) > +{ > + struct rockchip_dw_dp *dp = platform_get_drvdata(pdev); > + > + component_del(dp->dev, &dw_dp_rockchip_component_ops); > +} > + > +static const struct of_device_id dw_dp_of_match[] = { > + { .compatible = "rockchip,rk3588-dp", }, > + {} > +}; > +MODULE_DEVICE_TABLE(of, dw_dp_of_match); > + > +struct platform_driver dw_dp_driver = { > + .probe = dw_dp_probe, > + .remove = dw_dp_remove, > + .driver = { > + .name = "dw-dp", > + .of_match_table = dw_dp_of_match, > + }, > +}; > diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c > index 9cf311b5dec1..2b245491c71d 100644 > --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c > +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c > @@ -529,6 +529,7 @@ static int __init rockchip_drm_init(void) > ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver, > CONFIG_ROCKCHIP_ANALOGIX_DP); > ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP); > + ADD_ROCKCHIP_SUB_DRIVER(dw_dp_driver, CONFIG_ROCKCHIP_DW_DP); > ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver, > CONFIG_ROCKCHIP_DW_HDMI); > ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_qp_rockchip_pltfm_driver, > diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h > index c183e82a42a5..2e86ad00979c 100644 > --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h > +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h > @@ -87,6 +87,7 @@ int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rencoder, > struct device_node *np, int port, int reg); > int rockchip_drm_endpoint_is_subdriver(struct device_node *ep); > extern struct platform_driver cdn_dp_driver; > +extern struct platform_driver dw_dp_driver; > extern struct platform_driver dw_hdmi_rockchip_pltfm_driver; > extern struct platform_driver dw_hdmi_qp_rockchip_pltfm_driver; > extern struct platform_driver dw_mipi_dsi_rockchip_driver; > -- > 2.34.1 > -- With best wishes Dmitry ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re:Re: [PATCH 3/6] drm/rockchip: Add RK3588 DPTX output support 2025-03-01 18:19 ` Dmitry Baryshkov @ 2025-03-02 10:37 ` Andy Yan 0 siblings, 0 replies; 32+ messages in thread From: Andy Yan @ 2025-03-02 10:37 UTC (permalink / raw) To: Dmitry Baryshkov Cc: heiko, hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan Hi Dmitry, At 2025-03-02 02:19:24, "Dmitry Baryshkov" <dmitry.baryshkov@linaro.org> wrote: >On Sun, Feb 23, 2025 at 07:30:26PM +0800, Andy Yan wrote: >> From: Andy Yan <andy.yan@rock-chips.com> >> >> Add driver extension for Synopsys DesignWare DPTX IP used >> on Rockchip RK3588 SoC. >> >> Signed-off-by: Andy Yan <andy.yan@rock-chips.com> >> --- >> >> drivers/gpu/drm/rockchip/Kconfig | 7 + >> drivers/gpu/drm/rockchip/Makefile | 1 + >> drivers/gpu/drm/rockchip/dw_dp-rockchip.c | 162 ++++++++++++++++++++ >> drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 1 + >> drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 1 + >> 5 files changed, 172 insertions(+) >> create mode 100644 drivers/gpu/drm/rockchip/dw_dp-rockchip.c >> >> diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig >> index 26c4410b2407..c8638baf9641 100644 >> --- a/drivers/gpu/drm/rockchip/Kconfig >> +++ b/drivers/gpu/drm/rockchip/Kconfig >> @@ -8,6 +8,7 @@ config DRM_ROCKCHIP >> select DRM_PANEL >> select VIDEOMODE_HELPERS >> select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP >> + select DRM_DW_DP if ROCKCHIP_DW_DP >> select DRM_DW_HDMI if ROCKCHIP_DW_HDMI >> select DRM_DW_HDMI_QP if ROCKCHIP_DW_HDMI_QP >> select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI >> @@ -58,6 +59,12 @@ config ROCKCHIP_CDN_DP >> RK3399 based SoC, you should select this >> option. >> >> +config ROCKCHIP_DW_DP >> + bool "Rockchip specific extensions for Synopsys DW DP" >> + help >> + Choose this option for Synopsys DesignWare Cores DisplayPort >> + transmit controller support on Rockchip SoC. >> + >> config ROCKCHIP_DW_HDMI >> bool "Rockchip specific extensions for Synopsys DW HDMI" >> help >> diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile >> index 2b867cebbc12..097f062399c7 100644 >> --- a/drivers/gpu/drm/rockchip/Makefile >> +++ b/drivers/gpu/drm/rockchip/Makefile >> @@ -14,6 +14,7 @@ rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o >> rockchipdrm-$(CONFIG_ROCKCHIP_DW_HDMI_QP) += dw_hdmi_qp-rockchip.o >> rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi-rockchip.o >> rockchipdrm-$(CONFIG_ROCKCHIP_DW_MIPI_DSI2) += dw-mipi-dsi2-rockchip.o >> +rockchipdrm-$(CONFIG_ROCKCHIP_DW_DP) += dw_dp-rockchip.o >> rockchipdrm-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o >> rockchipdrm-$(CONFIG_ROCKCHIP_LVDS) += rockchip_lvds.o >> rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o >> diff --git a/drivers/gpu/drm/rockchip/dw_dp-rockchip.c b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c >> new file mode 100644 >> index 000000000000..b41a41eb74d7 >> --- /dev/null >> +++ b/drivers/gpu/drm/rockchip/dw_dp-rockchip.c >> @@ -0,0 +1,162 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> +/* >> + * Copyright (c) 2020 Rockchip Electronics Co., Ltd. >> + * >> + * Author: Zhang Yubing <yubing.zhang@rock-chips.com> >> + * Author: Andy Yan <andy.yan@rock-chips.com> >> + */ >> + >> +#include <linux/component.h> >> +#include <linux/of_device.h> >> +#include <linux/platform_device.h> >> +#include <drm/bridge/dw_dp.h> >> +#include <drm/drm_atomic_helper.h> >> +#include <drm/drm_bridge.h> >> +#include <drm/drm_bridge_connector.h> >> +#include <drm/drm_of.h> >> +#include <drm/drm_print.h> >> +#include <drm/drm_probe_helper.h> >> +#include <drm/drm_simple_kms_helper.h> >> + >> +#include <uapi/linux/media-bus-format.h> >> +#include <uapi/linux/videodev2.h> > >I'd say, include those two headers directly. Rockchip / Synopsys are the >only drivers including these uapi headers directly. Okay, I will include them directly in V2 like this: +#include <linux/media-bus-format.h> +#include <linux/videodev2.h> > >> + >> +#include "rockchip_drm_drv.h" >> +#include "rockchip_drm_vop.h" >> + >> +struct rockchip_dw_dp { >> + struct dw_dp *base; >> + struct device *dev; >> + struct rockchip_encoder encoder; >> +}; >> + >> +static inline struct rockchip_dw_dp *encoder_to_dp(struct drm_encoder *encoder) >> +{ >> + struct rockchip_encoder *rkencoder = to_rockchip_encoder(encoder); >> + >> + return container_of(rkencoder, struct rockchip_dw_dp, encoder); >> +} >> + >> +static int dw_dp_encoder_atomic_check(struct drm_encoder *encoder, >> + struct drm_crtc_state *crtc_state, >> + struct drm_connector_state *conn_state) >> +{ >> + struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state); >> + struct drm_atomic_state *state = conn_state->state; >> + struct drm_display_info *di = &conn_state->connector->display_info; >> + struct drm_bridge *bridge = drm_bridge_chain_get_first_bridge(encoder); >> + struct drm_bridge_state *bridge_state = drm_atomic_get_new_bridge_state(state, bridge); >> + u32 bus_format = bridge_state->input_bus_cfg.format; >> + >> + switch (bus_format) { >> + case MEDIA_BUS_FMT_UYYVYY10_0_5X30: >> + case MEDIA_BUS_FMT_UYYVYY8_0_5X24: >> + s->output_mode = ROCKCHIP_OUT_MODE_YUV420; >> + break; >> + case MEDIA_BUS_FMT_YUYV10_1X20: >> + case MEDIA_BUS_FMT_YUYV8_1X16: >> + s->output_mode = ROCKCHIP_OUT_MODE_S888_DUMMY; >> + break; >> + case MEDIA_BUS_FMT_RGB101010_1X30: >> + case MEDIA_BUS_FMT_RGB888_1X24: >> + case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: >> + case MEDIA_BUS_FMT_YUV10_1X30: >> + case MEDIA_BUS_FMT_YUV8_1X24: >> + default: >> + s->output_mode = ROCKCHIP_OUT_MODE_AAAA; >> + break; >> + } >> + >> + s->output_type = DRM_MODE_CONNECTOR_DisplayPort; >> + s->bus_format = bus_format; >> + s->bus_flags = di->bus_flags; >> + s->color_space = V4L2_COLORSPACE_DEFAULT; >> + >> + return 0; >> +} >> + >> +static const struct drm_encoder_helper_funcs dw_dp_encoder_helper_funcs = { >> + .atomic_check = dw_dp_encoder_atomic_check, >> +}; >> + >> +static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *data) >> +{ >> + struct dw_dp_plat_data plat_data; >> + struct drm_device *drm_dev = data; >> + struct rockchip_dw_dp *dp; >> + struct drm_encoder *encoder; >> + struct drm_connector *connector; >> + int ret; >> + >> + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); >> + if (!dp) >> + return -ENOMEM; >> + >> + dp->dev = dev; >> + plat_data.max_link_rate = 810000; >> + encoder = &dp->encoder.encoder; >> + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node); >> + rockchip_drm_encoder_set_crtc_endpoint_id(&dp->encoder, dev->of_node, 0, 0); >> + >> + drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_TMDS); > >drmm_encoder_init() ? This will allow you to get rid of >drm_encoder_cleanup() calls. Okay, I will try in V2. > >> + drm_encoder_helper_add(encoder, &dw_dp_encoder_helper_funcs); >> + >> + dp->base = dw_dp_bind(dev, encoder, &plat_data); >> + if (IS_ERR(dp->base)) { >> + ret = PTR_ERR(dp->base); >> + drm_encoder_cleanup(encoder); >> + return ret; >> + } >> + >> + connector = drm_bridge_connector_init(drm_dev, encoder); >> + if (IS_ERR(connector)) { >> + ret = PTR_ERR(connector); >> + dev_err(dev, "Failed to init bridge connector: %d\n", ret); >> + return ret; >> + } >> + >> + drm_connector_attach_encoder(connector, encoder); >> + >> + return 0; >> +} >> + >> +static void dw_dp_rockchip_unbind(struct device *dev, struct device *master, void *data) >> +{ >> + struct rockchip_dw_dp *dp = dev_get_drvdata(dev); >> + >> + drm_encoder_cleanup(&dp->encoder.encoder); >> +} >> + >> +static const struct component_ops dw_dp_rockchip_component_ops = { >> + .bind = dw_dp_rockchip_bind, >> + .unbind = dw_dp_rockchip_unbind, >> +}; >> + >> +static int dw_dp_probe(struct platform_device *pdev) >> +{ >> + struct device *dev = &pdev->dev; >> + >> + return component_add(dev, &dw_dp_rockchip_component_ops); >> +} >> + >> +static void dw_dp_remove(struct platform_device *pdev) >> +{ >> + struct rockchip_dw_dp *dp = platform_get_drvdata(pdev); >> + >> + component_del(dp->dev, &dw_dp_rockchip_component_ops); >> +} >> + >> +static const struct of_device_id dw_dp_of_match[] = { >> + { .compatible = "rockchip,rk3588-dp", }, >> + {} >> +}; >> +MODULE_DEVICE_TABLE(of, dw_dp_of_match); >> + >> +struct platform_driver dw_dp_driver = { >> + .probe = dw_dp_probe, >> + .remove = dw_dp_remove, >> + .driver = { >> + .name = "dw-dp", >> + .of_match_table = dw_dp_of_match, >> + }, >> +}; >> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c >> index 9cf311b5dec1..2b245491c71d 100644 >> --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c >> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c >> @@ -529,6 +529,7 @@ static int __init rockchip_drm_init(void) >> ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver, >> CONFIG_ROCKCHIP_ANALOGIX_DP); >> ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP); >> + ADD_ROCKCHIP_SUB_DRIVER(dw_dp_driver, CONFIG_ROCKCHIP_DW_DP); >> ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver, >> CONFIG_ROCKCHIP_DW_HDMI); >> ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_qp_rockchip_pltfm_driver, >> diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h >> index c183e82a42a5..2e86ad00979c 100644 >> --- a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h >> +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h >> @@ -87,6 +87,7 @@ int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rencoder, >> struct device_node *np, int port, int reg); >> int rockchip_drm_endpoint_is_subdriver(struct device_node *ep); >> extern struct platform_driver cdn_dp_driver; >> +extern struct platform_driver dw_dp_driver; >> extern struct platform_driver dw_hdmi_rockchip_pltfm_driver; >> extern struct platform_driver dw_hdmi_qp_rockchip_pltfm_driver; >> extern struct platform_driver dw_mipi_dsi_rockchip_driver; >> -- >> 2.34.1 >> > >-- >With best wishes >Dmitry ^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 4/6] arm64: dts: rockchip: Add DP0 for rk3588 2025-02-23 11:30 [PATCH 0/6] Add support for RK3588 DisplayPort Controller Andy Yan ` (2 preceding siblings ...) 2025-02-23 11:30 ` [PATCH 3/6] drm/rockchip: Add RK3588 DPTX output support Andy Yan @ 2025-02-23 11:30 ` Andy Yan 2025-02-23 11:30 ` [PATCH 5/6] arm64: dts: rockchip: Add DP1 " Andy Yan ` (2 subsequent siblings) 6 siblings, 0 replies; 32+ messages in thread From: Andy Yan @ 2025-02-23 11:30 UTC (permalink / raw) To: heiko Cc: hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan From: Andy Yan <andy.yan@rock-chips.com> The DP0 is compliant with the DisplayPort Specification Version 1.4, and share the USBDP combo PHY0 with USB 3.1 HOST0 controller. Signed-off-by: Andy Yan <andy.yan@rock-chips.com> --- arch/arm64/boot/dts/rockchip/rk3588-base.dtsi | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi index 4989fcd5e592..0aa8026fce95 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-base.dtsi @@ -1371,6 +1371,36 @@ i2s9_8ch: i2s@fddfc000 { status = "disabled"; }; + dp0: dp@fde50000 { + compatible = "rockchip,rk3588-dp"; + reg = <0x0 0xfde50000 0x0 0x4000>; + interrupts = <GIC_SPI 161 IRQ_TYPE_LEVEL_HIGH 0>; + clocks = <&cru PCLK_DP0>, <&cru CLK_AUX16M_0>, + <&cru CLK_DP0>, <&cru MCLK_I2S4_8CH_TX>, + <&cru MCLK_SPDIF2_DP0>; + clock-names = "apb", "aux", "hdcp", "i2s", "spdif"; + assigned-clocks = <&cru CLK_AUX16M_0>; + assigned-clock-rates = <16000000>; + resets = <&cru SRST_DP0>; + phys = <&usbdp_phy0 PHY_TYPE_DP>; + power-domains = <&power RK3588_PD_VO0>; + #sound-dai-cells = <0>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + dp0_in: port@0 { + reg = <0>; + }; + + dp0_out: port@1 { + reg = <1>; + }; + }; + }; + hdmi0: hdmi@fde80000 { compatible = "rockchip,rk3588-dw-hdmi-qp"; reg = <0x0 0xfde80000 0x0 0x20000>; -- 2.34.1 ^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 5/6] arm64: dts: rockchip: Add DP1 for rk3588 2025-02-23 11:30 [PATCH 0/6] Add support for RK3588 DisplayPort Controller Andy Yan ` (3 preceding siblings ...) 2025-02-23 11:30 ` [PATCH 4/6] arm64: dts: rockchip: Add DP0 for rk3588 Andy Yan @ 2025-02-23 11:30 ` Andy Yan 2025-02-23 11:30 ` [PATCH 6/6] arm64: dts: rockchip: Enable DisplayPort for rk3588s Cool Pi 4B Andy Yan 2025-02-28 20:30 ` [PATCH 0/6] Add support for RK3588 DisplayPort Controller Piotr Oniszczuk 6 siblings, 0 replies; 32+ messages in thread From: Andy Yan @ 2025-02-23 11:30 UTC (permalink / raw) To: heiko Cc: hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan From: Andy Yan <andy.yan@rock-chips.com> The DP1 is compliant with the DisplayPort Specification Version 1.4, and share the USBDP combo PHY1 with USB 3.1 HOST1 controller. Signed-off-by: Andy Yan <andy.yan@rock-chips.com> --- .../arm64/boot/dts/rockchip/rk3588-extra.dtsi | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi b/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi index 4a950907ea6f..427d2a39f79a 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588-extra.dtsi @@ -135,6 +135,36 @@ i2s10_8ch: i2s@fde00000 { status = "disabled"; }; + dp1: dp@fde60000 { + compatible = "rockchip,rk3588-dp"; + reg = <0x0 0xfde60000 0x0 0x4000>; + interrupts = <GIC_SPI 162 IRQ_TYPE_LEVEL_HIGH 0>; + clocks = <&cru PCLK_DP1>, <&cru CLK_AUX16M_1>, + <&cru CLK_DP1>, <&cru MCLK_I2S8_8CH_TX>, + <&cru MCLK_SPDIF5_DP1>; + clock-names = "apb", "aux", "hdcp", "i2s", "spdif"; + assigned-clocks = <&cru CLK_AUX16M_1>; + assigned-clock-rates = <16000000>; + resets = <&cru SRST_DP1>; + phys = <&usbdp_phy1 PHY_TYPE_DP>; + power-domains = <&power RK3588_PD_VO0>; + #sound-dai-cells = <0>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + dp1_in: port@0 { + reg = <0>; + }; + + dp1_out: port@1 { + reg = <1>; + }; + }; + }; + pcie3x4: pcie@fe150000 { compatible = "rockchip,rk3588-pcie", "rockchip,rk3568-pcie"; #address-cells = <3>; -- 2.34.1 ^ permalink raw reply related [flat|nested] 32+ messages in thread
* [PATCH 6/6] arm64: dts: rockchip: Enable DisplayPort for rk3588s Cool Pi 4B 2025-02-23 11:30 [PATCH 0/6] Add support for RK3588 DisplayPort Controller Andy Yan ` (4 preceding siblings ...) 2025-02-23 11:30 ` [PATCH 5/6] arm64: dts: rockchip: Add DP1 " Andy Yan @ 2025-02-23 11:30 ` Andy Yan 2025-03-09 6:35 ` FUKAUMI Naoki 2025-02-28 20:30 ` [PATCH 0/6] Add support for RK3588 DisplayPort Controller Piotr Oniszczuk 6 siblings, 1 reply; 32+ messages in thread From: Andy Yan @ 2025-02-23 11:30 UTC (permalink / raw) To: heiko Cc: hjc, mripard, cristian.ciocaltea, neil.armstrong, yubing.zhang, krzk+dt, devicetree, dri-devel, linux-arm-kernel, linux-kernel, linux-rockchip, robh, sebastian.reichel, Andy Yan From: Andy Yan <andy.yan@rock-chips.com> Enable the Mini DisplayPort on this board. Note that ROCKCHIP_VOP2_EP_DP0 is defined as 10 in dt-binding header, but it will trigger a dtc warning like "graph node unit address error, expected "a"" if we use it directly after endpoint, so we use "a" instead here. Signed-off-by: Andy Yan <andy.yan@rock-chips.com> --- .../boot/dts/rockchip/rk3588s-coolpi-4b.dts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts b/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts index e892dd7c91aa..0f58eb56557f 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts +++ b/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts @@ -39,6 +39,18 @@ chosen { stdout-path = "serial2:1500000n8"; }; + dp-con { + compatible = "dp-connector"; + label = "DP OUT"; + type = "mini"; + + port { + dp_con_in: endpoint { + remote-endpoint = <&dp0_out_con>; + }; + }; + }; + hdmi-con { compatible = "hdmi-connector"; type = "d"; @@ -220,6 +232,24 @@ &gpu { status = "okay"; }; +&dp0 { + status = "okay"; + pinctrl-0 = <&dp0m0_pins>; + pinctrl-names = "default"; +}; + +&dp0_in { + dp0_in_vp2: endpoint { + remote-endpoint = <&vp2_out_dp0>; + }; +}; + +&dp0_out { + dp0_out_con: endpoint { + remote-endpoint = <&dp_con_in>; + }; +}; + &hdmi0 { status = "okay"; }; @@ -889,3 +919,10 @@ vp0_out_hdmi0: endpoint@ROCKCHIP_VOP2_EP_HDMI0 { remote-endpoint = <&hdmi0_in_vp0>; }; }; + +&vp2 { + vp2_out_dp0: endpoint@a { + reg = <ROCKCHIP_VOP2_EP_DP0>; + remote-endpoint = <&dp0_in_vp2>; + }; +}; -- 2.34.1 ^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH 6/6] arm64: dts: rockchip: Enable DisplayPort for rk3588s Cool Pi 4B 2025-02-23 11:30 ` [PATCH 6/6] arm64: dts: rockchip: Enable DisplayPort for rk3588s Cool Pi 4B Andy Yan @ 2025-03-09 6:35 ` FUKAUMI Naoki 0 siblings, 0 replies; 32+ messages in thread From: FUKAUMI Naoki @ 2025-03-09 6:35 UTC (permalink / raw) To: Andy Yan, heiko Cc: neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel Hi Andy, On 2/23/25 20:30, Andy Yan wrote: > From: Andy Yan <andy.yan@rock-chips.com> > > Enable the Mini DisplayPort on this board. > Note that ROCKCHIP_VOP2_EP_DP0 is defined as 10 in dt-binding header, > but it will trigger a dtc warning like "graph node unit address error, > expected "a"" if we use it directly after endpoint, so we use "a" > instead here. > > Signed-off-by: Andy Yan <andy.yan@rock-chips.com> > --- > > .../boot/dts/rockchip/rk3588s-coolpi-4b.dts | 37 +++++++++++++++++++ > 1 file changed, 37 insertions(+) > > diff --git a/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts b/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts > index e892dd7c91aa..0f58eb56557f 100644 > --- a/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts > +++ b/arch/arm64/boot/dts/rockchip/rk3588s-coolpi-4b.dts > @@ -39,6 +39,18 @@ chosen { > stdout-path = "serial2:1500000n8"; > }; > > + dp-con { > + compatible = "dp-connector"; > + label = "DP OUT"; > + type = "mini"; > + > + port { > + dp_con_in: endpoint { > + remote-endpoint = <&dp0_out_con>; > + }; > + }; > + }; > + > hdmi-con { > compatible = "hdmi-connector"; > type = "d"; > @@ -220,6 +232,24 @@ &gpu { > status = "okay"; > }; > > +&dp0 { > + status = "okay"; > + pinctrl-0 = <&dp0m0_pins>; > + pinctrl-names = "default"; > +}; > + > +&dp0_in { > + dp0_in_vp2: endpoint { > + remote-endpoint = <&vp2_out_dp0>; > + }; > +}; > + > +&dp0_out { > + dp0_out_con: endpoint { > + remote-endpoint = <&dp_con_in>; > + }; > +}; > + These three nodes should be placed above the &gpu node. Best regards, -- FUKAUMI Naoki Radxa Computer (Shenzhen) Co., Ltd. > &hdmi0 { > status = "okay"; > }; > @@ -889,3 +919,10 @@ vp0_out_hdmi0: endpoint@ROCKCHIP_VOP2_EP_HDMI0 { > remote-endpoint = <&hdmi0_in_vp0>; > }; > }; > + > +&vp2 { > + vp2_out_dp0: endpoint@a { > + reg = <ROCKCHIP_VOP2_EP_DP0>; > + remote-endpoint = <&dp0_in_vp2>; > + }; > +}; ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller 2025-02-23 11:30 [PATCH 0/6] Add support for RK3588 DisplayPort Controller Andy Yan ` (5 preceding siblings ...) 2025-02-23 11:30 ` [PATCH 6/6] arm64: dts: rockchip: Enable DisplayPort for rk3588s Cool Pi 4B Andy Yan @ 2025-02-28 20:30 ` Piotr Oniszczuk 2025-03-01 12:24 ` Andy Yan 6 siblings, 1 reply; 32+ messages in thread From: Piotr Oniszczuk @ 2025-02-28 20:30 UTC (permalink / raw) To: Andy Yan Cc: heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel > Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 23 lut 2025, o godz. 12:30: > > From: Andy Yan <andy.yan@rock-chips.com> > > > There are two DW DPTX based DisplayPort Controller on rk3588 which > are compliant with the DisplayPort Specification Version 1.4 with > the following features: > > * DisplayPort 1.4a > * Main Link: 1/2/4 lanes > * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps > * AUX channel 1Mbps > * Single Stream Transport(SST) > * Multistream Transport (MST) > *Type-C support (alternate mode) > * HDCP 2.2, HDCP 1.3 > * Supports up to 8/10 bits per color component > * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0 > * Pixel clock up to 594MHz > * I2S, SPDIF audio interface > > The current version of this patch series only supports basic display outputs. > I conducted tests in 1080p and 4K@60 YCbCr4:2:0 modes; the ALT/Type-C mode > hasn't been tested yet, but I suspect it will likely work. HDCP and audio > features remain unimplemented. For RK3588, it's only support SST, while in > the upcoming RK3576, it can support MST output. > Andy, Is tis series enough to get usbc1/dp1 working as video output ? (assuming i will add necessary code in dt) rock5-itx has second hdmi port using usbc1/dp1 lanes 2,3 to ra620 dp->hdmi converter is it worth to play with this or it is too early? ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re:Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller 2025-02-28 20:30 ` [PATCH 0/6] Add support for RK3588 DisplayPort Controller Piotr Oniszczuk @ 2025-03-01 12:24 ` Andy Yan 2025-03-04 13:12 ` Piotr Oniszczuk 0 siblings, 1 reply; 32+ messages in thread From: Andy Yan @ 2025-03-01 12:24 UTC (permalink / raw) To: Piotr Oniszczuk Cc: heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel Hi Piotr, 在 2025-03-01 04:30:33,"Piotr Oniszczuk" <piotr.oniszczuk@gmail.com> 写道: > > >> Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 23 lut 2025, o godz. 12:30: >> >> From: Andy Yan <andy.yan@rock-chips.com> >> >> >> There are two DW DPTX based DisplayPort Controller on rk3588 which >> are compliant with the DisplayPort Specification Version 1.4 with >> the following features: >> >> * DisplayPort 1.4a >> * Main Link: 1/2/4 lanes >> * Main Link Support 1.62Gbps, 2.7Gbps, 5.4Gbps and 8.1Gbps >> * AUX channel 1Mbps >> * Single Stream Transport(SST) >> * Multistream Transport (MST) >> *Type-C support (alternate mode) >> * HDCP 2.2, HDCP 1.3 >> * Supports up to 8/10 bits per color component >> * Supports RBG, YCbCr4:4:4, YCbCr4:2:2, YCbCr4:2:0 >> * Pixel clock up to 594MHz >> * I2S, SPDIF audio interface >> >> The current version of this patch series only supports basic display outputs. >> I conducted tests in 1080p and 4K@60 YCbCr4:2:0 modes; the ALT/Type-C mode >> hasn't been tested yet, but I suspect it will likely work. HDCP and audio >> features remain unimplemented. For RK3588, it's only support SST, while in >> the upcoming RK3576, it can support MST output. >> > >Andy, > >Is tis series enough to get usbc1/dp1 working as video output ? >(assuming i will add necessary code in dt) > >rock5-itx has second hdmi port using usbc1/dp1 lanes 2,3 to ra620 dp->hdmi converter > >is it worth to play with this or it is too early? I think you could give it a try if it using the Standard DP(non-ALT mode) port for output. Since I don't currently have a development board with DP1 output available, I haven't been able to test it yet. As for the Type-C Alternate Mode output, some patches are still required I'll send it with V2 tomorrow or next week. Feel free to let me know if If you encounter any issues。 > ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller 2025-03-01 12:24 ` Andy Yan @ 2025-03-04 13:12 ` Piotr Oniszczuk 0 siblings, 0 replies; 32+ messages in thread From: Piotr Oniszczuk @ 2025-03-04 13:12 UTC (permalink / raw) To: Andy Yan Cc: heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel > Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 1 mar 2025, o godz. 13:24: > > > Hi Piotr, > >> >> is it worth to play with this or it is too early? > > I think you could give it a try if it using the Standard DP(non-ALT mode) port for output. > Since I don't currently have a development board with DP1 output available, I haven't been > able to test it yet. As for the Type-C Alternate Mode output, some patches are still required > I'll send it with V2 tomorrow or next week. > Feel free to let me know if If you encounter any issues。 > Andy, I added dp1 enablement in rock5 itx like this: https://gist.github.com/warpme/bddf75912193f57724c49216d5d85d4a Unfortunately it not works. For /sys/kernel/debug/dri/0/state - pls see above link… I’m not sure: do i missed something in dt or rather issue issue is in dp code... FYI: schematic: https://dl.radxa.com/rock5/5itx/v1110/radxa_rock_5itx_v1110_schematic.pdf ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re:Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller @ 2025-03-05 2:30 Andy Yan 2025-03-05 13:56 ` Piotr Oniszczuk 0 siblings, 1 reply; 32+ messages in thread From: Andy Yan @ 2025-03-05 2:30 UTC (permalink / raw) To: Piotr Oniszczuk Cc: heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel Hi Piotr, 在 2025-03-04 21:12:50,"Piotr Oniszczuk" <piotr.oniszczuk@gmail.com> 写道: > > >> Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 1 mar 2025, o godz. 13:24: >> >> >> Hi Piotr, >> >>> >>> is it worth to play with this or it is too early? >> >> I think you could give it a try if it using the Standard DP(non-ALT mode) port for output. >> Since I don't currently have a development board with DP1 output available, I haven't been >> able to test it yet. As for the Type-C Alternate Mode output, some patches are still required >> I'll send it with V2 tomorrow or next week. >> Feel free to let me know if If you encounter any issues。 >> > >Andy, >I added dp1 enablement in rock5 itx like this: https://gist.github.com/warpme/bddf75912193f57724c49216d5d85d4a >Unfortunately it not works. >For /sys/kernel/debug/dri/0/state - pls see above link… >I’m not sure: do i missed something in dt or rather issue issue is in dp code... I only see the HDMI connector from your dri/state ; so it appears the DP driver hasn't been successfully initialized(I think kernel dmesg can tell that). Have you enabled CONFIG_ROCKCHIP_DW_DP ? This is needed. > >FYI: schematic: https://dl.radxa.com/rock5/5itx/v1110/radxa_rock_5itx_v1110_schematic.pdf > > > ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller 2025-03-05 2:30 Andy Yan @ 2025-03-05 13:56 ` Piotr Oniszczuk 2025-03-06 0:59 ` Andy Yan 0 siblings, 1 reply; 32+ messages in thread From: Piotr Oniszczuk @ 2025-03-05 13:56 UTC (permalink / raw) To: Andy Yan Cc: heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel > Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 5 mar 2025, o godz. 03:30: > >> > > I only see the HDMI connector from your dri/state ; so it appears the DP driver hasn't been successfully > initialized(I think kernel dmesg can tell that). > Have you enabled CONFIG_ROCKCHIP_DW_DP ? This is needed. > Andy, Do you mean CONFIG_ROCKCHIP_DW_DP or CONFIG_DRM_DW_DP ? If CONFIG_DRM_DW_DP - then yes - i have it: https://github.com/warpme/minimyth2/blob/2e267842b1033bbc4c2c5d80c1756a142e347cc5/script/kernel/linux-6.14/files/linux-6.14-arm64-armv8.config#L5024 Kernel dmesg: https://termbin.com/uiup ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re:Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller 2025-03-05 13:56 ` Piotr Oniszczuk @ 2025-03-06 0:59 ` Andy Yan 2025-03-06 8:42 ` Piotr Oniszczuk 0 siblings, 1 reply; 32+ messages in thread From: Andy Yan @ 2025-03-06 0:59 UTC (permalink / raw) To: Piotr Oniszczuk Cc: heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel Hi Piotr, 在 2025-03-05 21:56:12,"Piotr Oniszczuk" <piotr.oniszczuk@gmail.com> 写道: > > >> Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 5 mar 2025, o godz. 03:30: >> >>> >> >> I only see the HDMI connector from your dri/state ; so it appears the DP driver hasn't been successfully >> initialized(I think kernel dmesg can tell that). >> Have you enabled CONFIG_ROCKCHIP_DW_DP ? This is needed. >> > > >Andy, > >Do you mean CONFIG_ROCKCHIP_DW_DP or CONFIG_DRM_DW_DP ? > >If CONFIG_DRM_DW_DP - then yes - i have it: https://github.com/warpme/minimyth2/blob/2e267842b1033bbc4c2c5d80c1756a142e347cc5/script/kernel/linux-6.14/files/linux-6.14-arm64-armv8.config#L5024 > >Kernel dmesg: https://termbin.com/uiup Both of the two config options should be enabled. andy@Pro480:~/WorkSpace/linux-next$ rg DW_DP .config 4044:CONFIG_ROCKCHIP_DW_DP=y 4218:CONFIG_DRM_DW_DP=y And if dw dp driver boots success, you will see dmesg like bellow: [ 1.058634] arm-smmu-v3 fc900000.iommu: msi_domain absent - falling back to wired irqs [ 1.062458] rockchip-vop2 fdd90000.vop: Adding to iommu group 0 [ 1.067908] rockchip-drm display-subsystem: bound fdd90000.vop (ops vop2_component_ops) [ 1.068981] rockchip-drm display-subsystem: bound fde60000.dp (ops dw_dp_rockchip_component_ops) [ 1.070133] dwhdmiqp-rockchip fde80000.hdmi: registered DesignWare HDMI QP I2C bus driver [ 1.070859] rockchip-drm display-subsystem: bound fde80000.hdmi (ops dw_hdmi_qp_rockchip_ops) [ 1.072103] [drm] Initialized rockchip 1.0.0 for display-subsystem on minor 0 [ 1.080191] loop: module loaded But I just see hdmi driver boots from your dmesg. > > > ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller 2025-03-06 0:59 ` Andy Yan @ 2025-03-06 8:42 ` Piotr Oniszczuk 2025-03-06 8:53 ` Andy Yan 0 siblings, 1 reply; 32+ messages in thread From: Piotr Oniszczuk @ 2025-03-06 8:42 UTC (permalink / raw) To: Andy Yan Cc: heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel > Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 6 mar 2025, o godz. 01:59: > > > > > Both of the two config options should be enabled. > andy@Pro480:~/WorkSpace/linux-next$ rg DW_DP .config > 4044:CONFIG_ROCKCHIP_DW_DP=y here i’m a bit lost…. greping on full kernel sources (with applied https://patchwork.kernel.org/project/linux-rockchip/list/?series=936784) gives me no single appearance of ROCKCHIP_DW_DP… Do i miss something? ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re:Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller 2025-03-06 8:42 ` Piotr Oniszczuk @ 2025-03-06 8:53 ` Andy Yan 2025-03-06 11:47 ` Piotr Oniszczuk 0 siblings, 1 reply; 32+ messages in thread From: Andy Yan @ 2025-03-06 8:53 UTC (permalink / raw) To: Piotr Oniszczuk Cc: heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel Hi, 在 2025-03-06 16:42:00,"Piotr Oniszczuk" <piotr.oniszczuk@gmail.com> 写道: > > >> Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 6 mar 2025, o godz. 01:59: >> >> >> >> >> Both of the two config options should be enabled. >> andy@Pro480:~/WorkSpace/linux-next$ rg DW_DP .config >> 4044:CONFIG_ROCKCHIP_DW_DP=y > >here i’m a bit lost…. >greping on full kernel sources (with applied https://patchwork.kernel.org/project/linux-rockchip/list/?series=936784) gives me no single appearance of ROCKCHIP_DW_DP… >Do i miss something? see PATCH 3/6: diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index 26c4410b2407..c8638baf9641 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -8,6 +8,7 @@ config DRM_ROCKCHIP select DRM_PANEL select VIDEOMODE_HELPERS select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP + select DRM_DW_DP if ROCKCHIP_DW_DP select DRM_DW_HDMI if ROCKCHIP_DW_HDMI select DRM_DW_HDMI_QP if ROCKCHIP_DW_HDMI_QP select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI @@ -58,6 +59,12 @@ config ROCKCHIP_CDN_DP RK3399 based SoC, you should select this option. +config ROCKCHIP_DW_DP + bool "Rockchip specific extensions for Synopsys DW DP" + help + Choose this option for Synopsys DesignWare Cores DisplayPort + transmit controller support on Rockchip SoC. https://lore.kernel.org/linux-rockchip/047EECFC-7E55-44EC-896F-13FE04333E4D@gmail.com/T/#m178a325ea0ebc64187aae474d77c3f7a9e0bc93d > ^ permalink raw reply related [flat|nested] 32+ messages in thread
* Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller 2025-03-06 8:53 ` Andy Yan @ 2025-03-06 11:47 ` Piotr Oniszczuk 2025-03-06 12:15 ` Andy Yan 0 siblings, 1 reply; 32+ messages in thread From: Piotr Oniszczuk @ 2025-03-06 11:47 UTC (permalink / raw) To: Andy Yan Cc: heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel > Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 6 mar 2025, o godz. 09:53: > > > Hi, > > 在 2025-03-06 16:42:00,"Piotr Oniszczuk" <piotr.oniszczuk@gmail.com> 写道: >> >> >>> Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 6 mar 2025, o godz. 01:59: >>> >>> >>> >>> >>> Both of the two config options should be enabled. >>> andy@Pro480:~/WorkSpace/linux-next$ rg DW_DP .config >>> 4044:CONFIG_ROCKCHIP_DW_DP=y >> >> here i’m a bit lost…. >> greping on full kernel sources (with applied https://patchwork.kernel.org/project/linux-rockchip/list/?series=936784) gives me no single appearance of ROCKCHIP_DW_DP… >> Do i miss something? > > see PATCH 3/6: > > > diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig > index 26c4410b2407..c8638baf9641 100644 > --- a/drivers/gpu/drm/rockchip/Kconfig > +++ b/drivers/gpu/drm/rockchip/Kconfig > @@ -8,6 +8,7 @@ config DRM_ROCKCHIP > select DRM_PANEL > select VIDEOMODE_HELPERS > select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP > + select DRM_DW_DP if ROCKCHIP_DW_DP > select DRM_DW_HDMI if ROCKCHIP_DW_HDMI > select DRM_DW_HDMI_QP if ROCKCHIP_DW_HDMI_QP > select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI > @@ -58,6 +59,12 @@ config ROCKCHIP_CDN_DP > RK3399 based SoC, you should select this > option. > > +config ROCKCHIP_DW_DP > + bool "Rockchip specific extensions for Synopsys DW DP" > + help > + Choose this option for Synopsys DesignWare Cores DisplayPort > + transmit controller support on Rockchip SoC. > > https://lore.kernel.org/linux-rockchip/047EECFC-7E55-44EC-896F-13FE04333E4D@gmail.com/T/#m178a325ea0ebc64187aae474d77c3f7a9e0bc93d >> Ah my bad! One patch patch was commented - so not all dp code was applied. Now it is much better: root@myth-frontend-56b0f018b5e0:~ # dmesg | grep drm [ 9.795380] panthor fb000000.gpu: [drm] clock rate = 198000000 [ 9.796257] panthor fb000000.gpu: [drm] mali-g610 id 0xa867 major 0x0 minor 0x0 status 0x5 [ 9.796262] panthor fb000000.gpu: [drm] Features: L2:0x7120306 Tiler:0x809 Mem:0x301 MMU:0x2830 AS:0xff [ 9.796265] panthor fb000000.gpu: [drm] shader_present=0x50005 l2_present=0x1 tiler_present=0x1 [ 9.851869] panthor fb000000.gpu: [drm] Firmware protected mode entry not be supported, ignoring [ 9.851921] panthor fb000000.gpu: [drm] Firmware git sha: 814b47b551159067b67a37c4e9adda458ad9d852 [ 9.852127] panthor fb000000.gpu: [drm] CSF FW using interface v1.1.0, Features 0x0 Instrumentation features 0x71 [ 9.852436] [drm] Initialized panthor 1.3.0 for fb000000.gpu on minor 0 [ 10.003108] rockchip-drm display-subsystem: bound fdd90000.vop (ops vop2_component_ops) [ 10.004705] rockchip-drm display-subsystem: bound fde60000.dp (ops dw_dp_rockchip_component_ops) [ 10.006085] rockchip-drm display-subsystem: bound fdea0000.hdmi (ops dw_hdmi_qp_rockchip_ops) [ 10.006679] [drm] Initialized rockchip 1.0.0 for display-subsystem on minor 1 [ 10.006737] rockchip-drm display-subsystem: [drm] Cannot find any crtc or sizes [ 10.007663] rockchip-drm display-subsystem: [drm] Cannot find any crtc or sizes Unfortunately still nothing on screen dri state: https://gist.github.com/warpme/5e971dfd2e9fd52fae76641831cebe46 and kernel dmesg https://termbin.com/r0m3 i’m not sure what is missing (some dts enablement or….) ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re:Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller 2025-03-06 11:47 ` Piotr Oniszczuk @ 2025-03-06 12:15 ` Andy Yan 2025-03-06 14:08 ` Piotr Oniszczuk 2025-03-06 16:39 ` Dmitry Baryshkov 0 siblings, 2 replies; 32+ messages in thread From: Andy Yan @ 2025-03-06 12:15 UTC (permalink / raw) To: Piotr Oniszczuk Cc: heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel Hi Piotr, 在 2025-03-06 19:47:31,"Piotr Oniszczuk" <piotr.oniszczuk@gmail.com> 写道: > >> Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 6 mar 2025, o godz. 09:53: >> >> >> Hi, >> >> 在 2025-03-06 16:42:00,"Piotr Oniszczuk" <piotr.oniszczuk@gmail.com> 写道: >>> >>> >>>> Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 6 mar 2025, o godz. 01:59: >>>> >>>> >>>> >>>> >>>> Both of the two config options should be enabled. >>>> andy@Pro480:~/WorkSpace/linux-next$ rg DW_DP .config >>>> 4044:CONFIG_ROCKCHIP_DW_DP=y >>> >>> here i’m a bit lost…. >>> greping on full kernel sources (with applied https://patchwork.kernel.org/project/linux-rockchip/list/?series=936784) gives me no single appearance of ROCKCHIP_DW_DP… >>> Do i miss something? >> >> see PATCH 3/6: >> >> >> diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig >> index 26c4410b2407..c8638baf9641 100644 >> --- a/drivers/gpu/drm/rockchip/Kconfig >> +++ b/drivers/gpu/drm/rockchip/Kconfig >> @@ -8,6 +8,7 @@ config DRM_ROCKCHIP >> select DRM_PANEL >> select VIDEOMODE_HELPERS >> select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP >> + select DRM_DW_DP if ROCKCHIP_DW_DP >> select DRM_DW_HDMI if ROCKCHIP_DW_HDMI >> select DRM_DW_HDMI_QP if ROCKCHIP_DW_HDMI_QP >> select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI >> @@ -58,6 +59,12 @@ config ROCKCHIP_CDN_DP >> RK3399 based SoC, you should select this >> option. >> >> +config ROCKCHIP_DW_DP >> + bool "Rockchip specific extensions for Synopsys DW DP" >> + help >> + Choose this option for Synopsys DesignWare Cores DisplayPort >> + transmit controller support on Rockchip SoC. >> >> https://lore.kernel.org/linux-rockchip/047EECFC-7E55-44EC-896F-13FE04333E4D@gmail.com/T/#m178a325ea0ebc64187aae474d77c3f7a9e0bc93d >>> > > >Ah my bad! >One patch patch was commented - so not all dp code was applied. > >Now it is much better: > >root@myth-frontend-56b0f018b5e0:~ # dmesg | grep drm >[ 9.795380] panthor fb000000.gpu: [drm] clock rate = 198000000 >[ 9.796257] panthor fb000000.gpu: [drm] mali-g610 id 0xa867 major 0x0 minor 0x0 status 0x5 >[ 9.796262] panthor fb000000.gpu: [drm] Features: L2:0x7120306 Tiler:0x809 Mem:0x301 MMU:0x2830 AS:0xff >[ 9.796265] panthor fb000000.gpu: [drm] shader_present=0x50005 l2_present=0x1 tiler_present=0x1 >[ 9.851869] panthor fb000000.gpu: [drm] Firmware protected mode entry not be supported, ignoring >[ 9.851921] panthor fb000000.gpu: [drm] Firmware git sha: 814b47b551159067b67a37c4e9adda458ad9d852 >[ 9.852127] panthor fb000000.gpu: [drm] CSF FW using interface v1.1.0, Features 0x0 Instrumentation features 0x71 >[ 9.852436] [drm] Initialized panthor 1.3.0 for fb000000.gpu on minor 0 >[ 10.003108] rockchip-drm display-subsystem: bound fdd90000.vop (ops vop2_component_ops) >[ 10.004705] rockchip-drm display-subsystem: bound fde60000.dp (ops dw_dp_rockchip_component_ops) >[ 10.006085] rockchip-drm display-subsystem: bound fdea0000.hdmi (ops dw_hdmi_qp_rockchip_ops) >[ 10.006679] [drm] Initialized rockchip 1.0.0 for display-subsystem on minor 1 >[ 10.006737] rockchip-drm display-subsystem: [drm] Cannot find any crtc or sizes >[ 10.007663] rockchip-drm display-subsystem: [drm] Cannot find any crtc or sizes > >Unfortunately still nothing on screen > >dri state: https://gist.github.com/warpme/5e971dfd2e9fd52fae76641831cebe46 > >and kernel dmesg https://termbin.com/r0m3 > >i’m not sure what is missing (some dts enablement or….) From your dts, I find you use gpio for hpd, I'm not sure how to handle it with upstream code now. &dp1 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&dp1_hpd>; hpd-gpios = <&gpio3 RK_PD5 GPIO_ACTIVE_HIGH>; }; I suggest a change like this: &dp1 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&dp1m0_pins>; }; And also remove dp1_hpd pinctrl in your dts. Then when you DP cable plugin, you can run command as bellow to see if the driver detects the HPD: # cat /sys/class/drm/card0-DP-1/status connected # > > > > > > ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller 2025-03-06 12:15 ` Andy Yan @ 2025-03-06 14:08 ` Piotr Oniszczuk 2025-03-06 14:28 ` Piotr Oniszczuk 2025-03-06 16:39 ` Dmitry Baryshkov 1 sibling, 1 reply; 32+ messages in thread From: Piotr Oniszczuk @ 2025-03-06 14:08 UTC (permalink / raw) To: Andy Yan Cc: heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel > Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 6 mar 2025, o godz. 13:15: > > Hi Piotr, > > > > Then when you DP cable plugin, you can run command as bellow to see if the driver detects the HPD: > > # cat /sys/class/drm/card0-DP-1/status > connected > # > Andy, Thx! With above changes i’m getting „connected”. Also it looks crtc gets reasonable mode: https://gist.github.com/warpme/d6220e3cc502086a4c95f05bd9f9cf0c Still black screen however... ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller 2025-03-06 14:08 ` Piotr Oniszczuk @ 2025-03-06 14:28 ` Piotr Oniszczuk 2025-03-07 0:48 ` Andy Yan 0 siblings, 1 reply; 32+ messages in thread From: Piotr Oniszczuk @ 2025-03-06 14:28 UTC (permalink / raw) To: Andy Yan Cc: heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel > Wiadomość napisana przez Piotr Oniszczuk <piotr.oniszczuk@gmail.com> w dniu 6 mar 2025, o godz. 15:08: > > > >> Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 6 mar 2025, o godz. 13:15: >> >> Hi Piotr, >> >> >> >> Then when you DP cable plugin, you can run command as bellow to see if the driver detects the HPD: >> >> # cat /sys/class/drm/card0-DP-1/status >> connected >> # >> > > > Andy, > Thx! > > With above changes i’m getting „connected”. > Also it looks crtc gets reasonable mode: https://gist.github.com/warpme/d6220e3cc502086a4c95f05bd9f9cf0c > > Still black screen however… > some additional data point: /sys/kernel/debug/dri/1/vop2/summary working hdmi: Video Port1: ACTIVE Connector: HDMI-A-1 bus_format[0]: Unknown output_mode[f] color_space[0] Display mode: 1920x1080p60 clk[148500] real_clk[148500] type[48] flag[5] H: 1920 2008 2052 2200 V: 1080 1084 1089 1125 Cluster0-win0: ACTIVE win_id: 0 format: XR24 little-endian (0x34325258) glb_alpha[0xff] rotate: xmirror: 0 ymirror: 0 rotate_90: 0 rotate_270: 0 zpos: 0 src: pos[0, 0] rect[1920 x 1080] dst: pos[0, 0] rect[1920 x 1080] buf[0]: addr: 0x00000000017e1000 pitch: 7680 offset: 0 Video Port2: DISABLED non-working DP: Video Port1: DISABLED Video Port2: ACTIVE Connector: DP-1 bus_format[100a]: RGB888_1X24 output_mode[f] color_space[0] Display mode: 1920x1080p60 clk[148500] real_clk[148500] type[48] flag[5] H: 1920 2008 2052 2200 V: 1080 1084 1089 1125 Cluster1-win0: ACTIVE win_id: 1 format: XR24 little-endian (0x34325258) glb_alpha[0xff] rotate: xmirror: 0 ymirror: 0 rotate_90: 0 rotate_270: 0 zpos: 0 src: pos[0, 0] rect[1920 x 1080] dst: pos[0, 0] rect[1920 x 1080] buf[0]: addr: 0x00000000007ed000 pitch: 7680 offset: 0 ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re:Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller 2025-03-06 14:28 ` Piotr Oniszczuk @ 2025-03-07 0:48 ` Andy Yan 2025-03-09 20:53 ` Piotr Oniszczuk 0 siblings, 1 reply; 32+ messages in thread From: Andy Yan @ 2025-03-07 0:48 UTC (permalink / raw) To: Piotr Oniszczuk Cc: heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel Hi Piotr, 在 2025-03-06 22:28:08,"Piotr Oniszczuk" <piotr.oniszczuk@gmail.com> 写道: > > >> Wiadomość napisana przez Piotr Oniszczuk <piotr.oniszczuk@gmail.com> w dniu 6 mar 2025, o godz. 15:08: >> >> >> >>> Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 6 mar 2025, o godz. 13:15: >>> >>> Hi Piotr, >>> >>> >>> >>> Then when you DP cable plugin, you can run command as bellow to see if the driver detects the HPD: >>> >>> # cat /sys/class/drm/card0-DP-1/status >>> connected >>> # >>> >> >> >> Andy, >> Thx! >> >> With above changes i’m getting „connected”. >> Also it looks crtc gets reasonable mode: https://gist.github.com/warpme/d6220e3cc502086a4c95f05bd9f9cf0c >> >> Still black screen however… >> > >some additional data point: /sys/kernel/debug/dri/1/vop2/summary > > >working hdmi: > >Video Port1: ACTIVE > Connector: HDMI-A-1 > bus_format[0]: Unknown > output_mode[f] color_space[0] > Display mode: 1920x1080p60 > clk[148500] real_clk[148500] type[48] flag[5] > H: 1920 2008 2052 2200 > V: 1080 1084 1089 1125 > Cluster0-win0: ACTIVE > win_id: 0 > format: XR24 little-endian (0x34325258) glb_alpha[0xff] > rotate: xmirror: 0 ymirror: 0 rotate_90: 0 rotate_270: 0 > zpos: 0 > src: pos[0, 0] rect[1920 x 1080] > dst: pos[0, 0] rect[1920 x 1080] > buf[0]: addr: 0x00000000017e1000 pitch: 7680 offset: 0 >Video Port2: DISABLED > > > > >non-working DP: > >Video Port1: DISABLED >Video Port2: ACTIVE > Connector: DP-1 > bus_format[100a]: RGB888_1X24 > output_mode[f] color_space[0] > Display mode: 1920x1080p60 > clk[148500] real_clk[148500] type[48] flag[5] > H: 1920 2008 2052 2200 > V: 1080 1084 1089 1125 > Cluster1-win0: ACTIVE > win_id: 1 > format: XR24 little-endian (0x34325258) glb_alpha[0xff] > rotate: xmirror: 0 ymirror: 0 rotate_90: 0 rotate_270: 0 > zpos: 0 > src: pos[0, 0] rect[1920 x 1080] > dst: pos[0, 0] rect[1920 x 1080] > buf[0]: addr: 0x00000000007ed000 pitch: 7680 offset: 0 > All dump information currently appears to be correct, so I'm temporarily unsure why there is no display on the monitor. Maybe try some plug and unplug for the DP cable, or try another cable or monitor? It seems that this board uses a DP to HDMI converter? Does this transmitter need a driver? I won't be at my computer over the next two or three days, so any further replies to your email might have to wait until next week. ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller 2025-03-07 0:48 ` Andy Yan @ 2025-03-09 20:53 ` Piotr Oniszczuk 0 siblings, 0 replies; 32+ messages in thread From: Piotr Oniszczuk @ 2025-03-09 20:53 UTC (permalink / raw) To: Andy Yan Cc: heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel > Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 7 mar 2025, o godz. 01:48: > > > Hi Piotr, > 在 2025-03-06 22:28:08,"Piotr Oniszczuk" <piotr.oniszczuk@gmail.com> 写道: >> >> > > All dump information currently appears to be correct, so I'm temporarily unsure why > there is no display on the monitor. > Maybe try some plug and unplug for the DP cable, or try another cable or monitor? > > It seems that this board uses a DP to HDMI converter? Does this transmitter need a driver? > > I won't be at my computer over the next two or three days, so any further replies to your email > might have to wait until next week. > > Andy, FYI: I done test on mine rock5a with applied Naoki dp0 enablement in dts (and only in dts). No any changes in dw dp driver (so i’m on vanilla https://patchwork.kernel.org/project/linux-rockchip/cover/20250223113036.74252-1-andyshrk@163.com/ ) on mine rock5a ra620 hdmi port works ok. (I contacted also Radxa about ra620 and they confirmed: ra620 is just DP->HDMI converter. No any driver nor special programming/enablement is needed) This tells me that dp0 (rock5a) works ok while dp1 (rock5itx) not. i suspect issue is probably in https://patchwork.kernel.org/project/linux-rockchip/cover/20250223113036.74252-1-andyshrk@163.com/ and is related to dp1 handling? BTW: there seems to be issue with video modes handling on dp0 port: -playing video 1920@1080@50 - ok -playing then video1920@1080@59,64 hangs board…. hdmi0 works ok. video modes issue is only on dp0 ^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 0/6] Add support for RK3588 DisplayPort Controller 2025-03-06 12:15 ` Andy Yan 2025-03-06 14:08 ` Piotr Oniszczuk @ 2025-03-06 16:39 ` Dmitry Baryshkov 1 sibling, 0 replies; 32+ messages in thread From: Dmitry Baryshkov @ 2025-03-06 16:39 UTC (permalink / raw) To: Andy Yan Cc: Piotr Oniszczuk, heiko, neil.armstrong, sebastian.reichel, devicetree, hjc, mripard, linux-kernel, linux-rockchip, yubing.zhang, dri-devel, Andy Yan, krzk+dt, robh, linux-arm-kernel On Thu, Mar 06, 2025 at 08:15:13PM +0800, Andy Yan wrote: > Hi Piotr, > > 在 2025-03-06 19:47:31,"Piotr Oniszczuk" <piotr.oniszczuk@gmail.com> 写道: > > > >> Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 6 mar 2025, o godz. 09:53: > >> > >> > >> Hi, > >> > >> 在 2025-03-06 16:42:00,"Piotr Oniszczuk" <piotr.oniszczuk@gmail.com> 写道: > >>> > >>> > >>>> Wiadomość napisana przez Andy Yan <andyshrk@163.com> w dniu 6 mar 2025, o godz. 01:59: > >>>> > >>>> > >>>> > >>>> > >>>> Both of the two config options should be enabled. > >>>> andy@Pro480:~/WorkSpace/linux-next$ rg DW_DP .config > >>>> 4044:CONFIG_ROCKCHIP_DW_DP=y > >>> > >>> here i’m a bit lost…. > >>> greping on full kernel sources (with applied https://patchwork.kernel.org/project/linux-rockchip/list/?series=936784) gives me no single appearance of ROCKCHIP_DW_DP… > >>> Do i miss something? > >> > >> see PATCH 3/6: > >> > >> > >> diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig > >> index 26c4410b2407..c8638baf9641 100644 > >> --- a/drivers/gpu/drm/rockchip/Kconfig > >> +++ b/drivers/gpu/drm/rockchip/Kconfig > >> @@ -8,6 +8,7 @@ config DRM_ROCKCHIP > >> select DRM_PANEL > >> select VIDEOMODE_HELPERS > >> select DRM_ANALOGIX_DP if ROCKCHIP_ANALOGIX_DP > >> + select DRM_DW_DP if ROCKCHIP_DW_DP > >> select DRM_DW_HDMI if ROCKCHIP_DW_HDMI > >> select DRM_DW_HDMI_QP if ROCKCHIP_DW_HDMI_QP > >> select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI > >> @@ -58,6 +59,12 @@ config ROCKCHIP_CDN_DP > >> RK3399 based SoC, you should select this > >> option. > >> > >> +config ROCKCHIP_DW_DP > >> + bool "Rockchip specific extensions for Synopsys DW DP" > >> + help > >> + Choose this option for Synopsys DesignWare Cores DisplayPort > >> + transmit controller support on Rockchip SoC. > >> > >> https://lore.kernel.org/linux-rockchip/047EECFC-7E55-44EC-896F-13FE04333E4D@gmail.com/T/#m178a325ea0ebc64187aae474d77c3f7a9e0bc93d > >>> > > > > > >Ah my bad! > >One patch patch was commented - so not all dp code was applied. > > > >Now it is much better: > > > >root@myth-frontend-56b0f018b5e0:~ # dmesg | grep drm > >[ 9.795380] panthor fb000000.gpu: [drm] clock rate = 198000000 > >[ 9.796257] panthor fb000000.gpu: [drm] mali-g610 id 0xa867 major 0x0 minor 0x0 status 0x5 > >[ 9.796262] panthor fb000000.gpu: [drm] Features: L2:0x7120306 Tiler:0x809 Mem:0x301 MMU:0x2830 AS:0xff > >[ 9.796265] panthor fb000000.gpu: [drm] shader_present=0x50005 l2_present=0x1 tiler_present=0x1 > >[ 9.851869] panthor fb000000.gpu: [drm] Firmware protected mode entry not be supported, ignoring > >[ 9.851921] panthor fb000000.gpu: [drm] Firmware git sha: 814b47b551159067b67a37c4e9adda458ad9d852 > >[ 9.852127] panthor fb000000.gpu: [drm] CSF FW using interface v1.1.0, Features 0x0 Instrumentation features 0x71 > >[ 9.852436] [drm] Initialized panthor 1.3.0 for fb000000.gpu on minor 0 > >[ 10.003108] rockchip-drm display-subsystem: bound fdd90000.vop (ops vop2_component_ops) > >[ 10.004705] rockchip-drm display-subsystem: bound fde60000.dp (ops dw_dp_rockchip_component_ops) > >[ 10.006085] rockchip-drm display-subsystem: bound fdea0000.hdmi (ops dw_hdmi_qp_rockchip_ops) > >[ 10.006679] [drm] Initialized rockchip 1.0.0 for display-subsystem on minor 1 > >[ 10.006737] rockchip-drm display-subsystem: [drm] Cannot find any crtc or sizes > >[ 10.007663] rockchip-drm display-subsystem: [drm] Cannot find any crtc or sizes > > > >Unfortunately still nothing on screen > > > >dri state: https://gist.github.com/warpme/5e971dfd2e9fd52fae76641831cebe46 > > > >and kernel dmesg https://termbin.com/r0m3 > > > >i’m not sure what is missing (some dts enablement or….) > > From your dts, I find you use gpio for hpd, I'm not sure how to handle it with upstream code now. > > > &dp1 { > status = "okay"; > pinctrl-names = "default"; > pinctrl-0 = <&dp1_hpd>; > hpd-gpios = <&gpio3 RK_PD5 GPIO_ACTIVE_HIGH>; If this GPIO is handled by the DP controller itself, it should be a part of DP's node. Otherwise please move it to the correctponding dp-connector node. > }; > > I suggest a change like this: > > &dp1 { > status = "okay"; > pinctrl-names = "default"; > pinctrl-0 = <&dp1m0_pins>; > > }; > > And also remove dp1_hpd pinctrl in your dts. > > Then when you DP cable plugin, you can run command as bellow to see if the driver detects the HPD: > > # cat /sys/class/drm/card0-DP-1/status > connected > # -- With best wishes Dmitry ^ permalink raw reply [flat|nested] 32+ messages in thread
end of thread, other threads:[~2025-03-09 20:54 UTC | newest] Thread overview: 32+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-02-23 11:30 [PATCH 0/6] Add support for RK3588 DisplayPort Controller Andy Yan 2025-02-23 11:30 ` [PATCH 1/6] dt-bindings: display: rockchip: Add schema for RK3588 DPTX Controller Andy Yan 2025-02-24 20:03 ` Rob Herring 2025-02-23 11:30 ` [PATCH 2/6] drm/bridge: synopsys: Add DW DPTX Controller support library Andy Yan 2025-03-01 18:14 ` Dmitry Baryshkov 2025-03-02 10:34 ` Andy Yan 2025-03-02 16:21 ` Dmitry Baryshkov 2025-03-04 7:16 ` Andy Yan 2025-03-04 9:18 ` Dmitry Baryshkov 2025-03-04 10:16 ` Andy Yan 2025-03-03 8:59 ` Yubing Zhang 2025-03-03 8:59 ` Yubing Zhang 2025-03-03 12:57 ` Dmitry Baryshkov 2025-03-09 6:51 ` FUKAUMI Naoki 2025-02-23 11:30 ` [PATCH 3/6] drm/rockchip: Add RK3588 DPTX output support Andy Yan 2025-02-24 14:56 ` kernel test robot 2025-03-01 18:19 ` Dmitry Baryshkov 2025-03-02 10:37 ` Andy Yan 2025-02-23 11:30 ` [PATCH 4/6] arm64: dts: rockchip: Add DP0 for rk3588 Andy Yan 2025-02-23 11:30 ` [PATCH 5/6] arm64: dts: rockchip: Add DP1 " Andy Yan 2025-02-23 11:30 ` [PATCH 6/6] arm64: dts: rockchip: Enable DisplayPort for rk3588s Cool Pi 4B Andy Yan 2025-03-09 6:35 ` FUKAUMI Naoki 2025-02-28 20:30 ` [PATCH 0/6] Add support for RK3588 DisplayPort Controller Piotr Oniszczuk 2025-03-01 12:24 ` Andy Yan 2025-03-04 13:12 ` Piotr Oniszczuk -- strict thread matches above, loose matches on Subject: below -- 2025-03-05 2:30 Andy Yan 2025-03-05 13:56 ` Piotr Oniszczuk 2025-03-06 0:59 ` Andy Yan 2025-03-06 8:42 ` Piotr Oniszczuk 2025-03-06 8:53 ` Andy Yan 2025-03-06 11:47 ` Piotr Oniszczuk 2025-03-06 12:15 ` Andy Yan 2025-03-06 14:08 ` Piotr Oniszczuk 2025-03-06 14:28 ` Piotr Oniszczuk 2025-03-07 0:48 ` Andy Yan 2025-03-09 20:53 ` Piotr Oniszczuk 2025-03-06 16:39 ` Dmitry Baryshkov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox