From a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 Mon Sep 17 00:00:00 2001 From: Jinke Han Date: Tue, 8 Sep 2026 15:37:42 +0800 Subject: x86/kprobes: Fix crash when probing CS CALL instructions When using eBPF to probe CS CALL instructions within a function, a crash can be triggered. The eBPF tool probes offset 257 of the __hrtimer_run_queues() function: <__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1) <__hrtimer_run_queues+254>: mov %r14,%rdi <__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12> <__hrtimer_run_queues+263>: mov %eax,%r12d <__hrtimer_run_queues+266>: xchg %ax,%ax <__hrtimer_run_queues+268>: mov %r13,%rdi Which triggers this crash: BUG: unable to handle page fault for address: 00000000000f41c9 #PF: supervisor write access in kernel mode #PF: error_code(0x0002) - not-present page PGD 0 P4D 0 Oops: 0002 [#1] SMP NOPTI CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P RIP: 0010:__hrtimer_run_queues+0x106/0x230 Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is at the 6th byte of the above CS CALL instruction. Since the CS CALL instruction occupies 6 bytes, the exception occurred in the middle of that call instruction. The root cause is that when using eBPF tools to probe in the middle of a function, a kprobe with INT3 is used as the underlying implementation. During single-step emulation of the original CALL instruction, int3_emulate_call() assumes that the probed CALL instruction is 5 bytes long. However, the actual CS-prefixed CALL instruction occupies 6 bytes, so it constructs an incorrect exception return address. When the CPU returns from the kprobe handler, the next instruction to be executed is at the address of the last byte of that CS CALL instruction. Coincidentally, starting from that address, the CPU fetches and decodes a completely different instruction, which ultimately triggers a kernel crash. Fix the issue by using the actual instruction length obtained from the instruction decoder when constructing the exception return address, rather than relying on the hardcoded CALL_INSN_SIZE macro. [ mingo: Refined the changelog ] Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step") Suggested-by: Masami Hiramatsu (Google) Signed-off-by: Jinke Han Signed-off-by: Ingo Molnar Reviewed-by: Masami Hiramatsu (Google) Acked-by: Yafang Shao Acked-by: Borislav Petkov Cc: Peter Zijlstra Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000 --- drivers/gpu/drm/bridge/Kconfig | 516 +++ drivers/gpu/drm/bridge/Makefile | 55 + drivers/gpu/drm/bridge/adv7511/Kconfig | 28 + drivers/gpu/drm/bridge/adv7511/Makefile | 5 + drivers/gpu/drm/bridge/adv7511/adv7511.h | 442 +++ drivers/gpu/drm/bridge/adv7511/adv7511_audio.c | 206 ++ drivers/gpu/drm/bridge/adv7511/adv7511_cec.c | 383 ++ drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 1538 ++++++++ drivers/gpu/drm/bridge/adv7511/adv7533.c | 185 + drivers/gpu/drm/bridge/analogix/Kconfig | 50 + drivers/gpu/drm/bridge/analogix/Makefile | 6 + drivers/gpu/drm/bridge/analogix/analogix-anx6345.c | 810 +++++ drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c | 1364 ++++++++ drivers/gpu/drm/bridge/analogix/analogix-anx78xx.h | 249 ++ .../gpu/drm/bridge/analogix/analogix-i2c-dptx.c | 167 + .../gpu/drm/bridge/analogix/analogix-i2c-dptx.h | 256 ++ .../drm/bridge/analogix/analogix-i2c-txcommon.h | 234 ++ drivers/gpu/drm/bridge/analogix/analogix_dp_core.c | 1622 +++++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_core.h | 235 ++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c | 1107 ++++++ drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h | 426 +++ drivers/gpu/drm/bridge/analogix/anx7625.c | 3025 ++++++++++++++++ drivers/gpu/drm/bridge/analogix/anx7625.h | 514 +++ drivers/gpu/drm/bridge/aux-bridge.c | 154 + drivers/gpu/drm/bridge/aux-hpd-bridge.c | 215 ++ drivers/gpu/drm/bridge/cadence/Kconfig | 50 + drivers/gpu/drm/bridge/cadence/Makefile | 7 + drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c | 1410 ++++++++ drivers/gpu/drm/bridge/cadence/cdns-dsi-core.h | 82 + drivers/gpu/drm/bridge/cadence/cdns-dsi-j721e.c | 51 + drivers/gpu/drm/bridge/cadence/cdns-dsi-j721e.h | 16 + .../gpu/drm/bridge/cadence/cdns-mhdp8546-core.c | 2474 +++++++++++++ .../gpu/drm/bridge/cadence/cdns-mhdp8546-core.h | 422 +++ .../gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.c | 553 +++ .../gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.h | 89 + .../gpu/drm/bridge/cadence/cdns-mhdp8546-j721e.c | 77 + .../gpu/drm/bridge/cadence/cdns-mhdp8546-j721e.h | 19 + drivers/gpu/drm/bridge/chipone-icn6211.c | 821 +++++ drivers/gpu/drm/bridge/chrontel-ch7033.c | 623 ++++ drivers/gpu/drm/bridge/cros-ec-anx7688.c | 193 + drivers/gpu/drm/bridge/display-connector.c | 488 +++ drivers/gpu/drm/bridge/fsl-ldb.c | 413 +++ drivers/gpu/drm/bridge/imx/Kconfig | 121 + drivers/gpu/drm/bridge/imx/Makefile | 13 + drivers/gpu/drm/bridge/imx/imx-ldb-helper.c | 230 ++ drivers/gpu/drm/bridge/imx/imx-ldb-helper.h | 95 + drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c | 173 + drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pvi.c | 206 ++ .../bridge/imx/imx8mp-hdmi-tx-connector-fixup.c | 75 + .../bridge/imx/imx8mp-hdmi-tx-connector-fixup.dtso | 30 + drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c | 213 ++ drivers/gpu/drm/bridge/imx/imx8qm-ldb.c | 589 ++++ drivers/gpu/drm/bridge/imx/imx8qxp-ldb.c | 732 ++++ .../gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c | 439 +++ drivers/gpu/drm/bridge/imx/imx8qxp-pixel-link.c | 411 +++ drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c | 475 +++ drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c | 915 +++++ drivers/gpu/drm/bridge/imx/imx93-pdfc.c | 222 ++ drivers/gpu/drm/bridge/inno-hdmi.c | 1140 ++++++ drivers/gpu/drm/bridge/ite-it6263.c | 930 +++++ drivers/gpu/drm/bridge/ite-it6505.c | 3683 ++++++++++++++++++++ drivers/gpu/drm/bridge/ite-it66121.c | 1741 +++++++++ drivers/gpu/drm/bridge/lontium-lt8713sx.c | 600 ++++ drivers/gpu/drm/bridge/lontium-lt8912b.c | 842 +++++ drivers/gpu/drm/bridge/lontium-lt9211.c | 800 +++++ drivers/gpu/drm/bridge/lontium-lt9611.c | 1255 +++++++ drivers/gpu/drm/bridge/lontium-lt9611uxc.c | 928 +++++ drivers/gpu/drm/bridge/lvds-codec.c | 252 ++ .../drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c | 414 +++ drivers/gpu/drm/bridge/microchip-lvds.c | 243 ++ drivers/gpu/drm/bridge/nwl-dsi.c | 1226 +++++++ drivers/gpu/drm/bridge/nwl-dsi.h | 144 + drivers/gpu/drm/bridge/nxp-ptn3460.c | 350 ++ drivers/gpu/drm/bridge/of-display-mode-bridge.c | 97 + drivers/gpu/drm/bridge/panel.c | 563 +++ drivers/gpu/drm/bridge/parade-ps8622.c | 557 +++ drivers/gpu/drm/bridge/parade-ps8640.c | 768 ++++ drivers/gpu/drm/bridge/samsung-dsim.c | 2339 +++++++++++++ drivers/gpu/drm/bridge/sii902x.c | 1256 +++++++ drivers/gpu/drm/bridge/sii9234.c | 964 +++++ drivers/gpu/drm/bridge/sil-sii8620.c | 2394 +++++++++++++ drivers/gpu/drm/bridge/sil-sii8620.h | 1532 ++++++++ drivers/gpu/drm/bridge/simple-bridge.c | 331 ++ drivers/gpu/drm/bridge/ssd2825.c | 775 ++++ drivers/gpu/drm/bridge/synopsys/Kconfig | 82 + drivers/gpu/drm/bridge/synopsys/Makefile | 12 + drivers/gpu/drm/bridge/synopsys/dw-dp.c | 2103 +++++++++++ .../gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c | 660 ++++ drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h | 23 + drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c | 360 ++ drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.h | 19 + drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c | 200 ++ .../gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c | 239 ++ drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c | 1395 ++++++++ drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h | 851 +++++ drivers/gpu/drm/bridge/synopsys/dw-hdmi.c | 3658 +++++++++++++++++++ drivers/gpu/drm/bridge/synopsys/dw-hdmi.h | 1168 +++++++ drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c | 1342 +++++++ drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c | 1035 ++++++ drivers/gpu/drm/bridge/tc358762.c | 323 ++ drivers/gpu/drm/bridge/tc358764.c | 408 +++ drivers/gpu/drm/bridge/tc358767.c | 2640 ++++++++++++++ drivers/gpu/drm/bridge/tc358768.c | 1471 ++++++++ drivers/gpu/drm/bridge/tc358775.c | 756 ++++ drivers/gpu/drm/bridge/tda998x_drv.c | 2086 +++++++++++ drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 172 + drivers/gpu/drm/bridge/thc63lvd1024.c | 249 ++ drivers/gpu/drm/bridge/ti-dlpc3433.c | 417 +++ drivers/gpu/drm/bridge/ti-sn65dsi83.c | 1103 ++++++ drivers/gpu/drm/bridge/ti-sn65dsi86.c | 2191 ++++++++++++ drivers/gpu/drm/bridge/ti-tdp158.c | 115 + drivers/gpu/drm/bridge/ti-tfp410.c | 502 +++ drivers/gpu/drm/bridge/ti-tpd12s015.c | 218 ++ drivers/gpu/drm/bridge/waveshare-dsi.c | 213 ++ 114 files changed, 78654 insertions(+) create mode 100644 drivers/gpu/drm/bridge/Kconfig create mode 100644 drivers/gpu/drm/bridge/Makefile create mode 100644 drivers/gpu/drm/bridge/adv7511/Kconfig create mode 100644 drivers/gpu/drm/bridge/adv7511/Makefile create mode 100644 drivers/gpu/drm/bridge/adv7511/adv7511.h create mode 100644 drivers/gpu/drm/bridge/adv7511/adv7511_audio.c create mode 100644 drivers/gpu/drm/bridge/adv7511/adv7511_cec.c create mode 100644 drivers/gpu/drm/bridge/adv7511/adv7511_drv.c create mode 100644 drivers/gpu/drm/bridge/adv7511/adv7533.c create mode 100644 drivers/gpu/drm/bridge/analogix/Kconfig create mode 100644 drivers/gpu/drm/bridge/analogix/Makefile create mode 100644 drivers/gpu/drm/bridge/analogix/analogix-anx6345.c create mode 100644 drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c create mode 100644 drivers/gpu/drm/bridge/analogix/analogix-anx78xx.h create mode 100644 drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c create mode 100644 drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.h create mode 100644 drivers/gpu/drm/bridge/analogix/analogix-i2c-txcommon.h create mode 100644 drivers/gpu/drm/bridge/analogix/analogix_dp_core.c create mode 100644 drivers/gpu/drm/bridge/analogix/analogix_dp_core.h create mode 100644 drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c create mode 100644 drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h create mode 100644 drivers/gpu/drm/bridge/analogix/anx7625.c create mode 100644 drivers/gpu/drm/bridge/analogix/anx7625.h create mode 100644 drivers/gpu/drm/bridge/aux-bridge.c create mode 100644 drivers/gpu/drm/bridge/aux-hpd-bridge.c create mode 100644 drivers/gpu/drm/bridge/cadence/Kconfig create mode 100644 drivers/gpu/drm/bridge/cadence/Makefile create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-dsi-core.h create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-dsi-j721e.c create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-dsi-j721e.h create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.c create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-core.h create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.c create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-hdcp.h create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-j721e.c create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-j721e.h create mode 100644 drivers/gpu/drm/bridge/chipone-icn6211.c create mode 100644 drivers/gpu/drm/bridge/chrontel-ch7033.c create mode 100644 drivers/gpu/drm/bridge/cros-ec-anx7688.c create mode 100644 drivers/gpu/drm/bridge/display-connector.c create mode 100644 drivers/gpu/drm/bridge/fsl-ldb.c create mode 100644 drivers/gpu/drm/bridge/imx/Kconfig create mode 100644 drivers/gpu/drm/bridge/imx/Makefile create mode 100644 drivers/gpu/drm/bridge/imx/imx-ldb-helper.c create mode 100644 drivers/gpu/drm/bridge/imx/imx-ldb-helper.h create mode 100644 drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pai.c create mode 100644 drivers/gpu/drm/bridge/imx/imx8mp-hdmi-pvi.c create mode 100644 drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx-connector-fixup.c create mode 100644 drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx-connector-fixup.dtso create mode 100644 drivers/gpu/drm/bridge/imx/imx8mp-hdmi-tx.c create mode 100644 drivers/gpu/drm/bridge/imx/imx8qm-ldb.c create mode 100644 drivers/gpu/drm/bridge/imx/imx8qxp-ldb.c create mode 100644 drivers/gpu/drm/bridge/imx/imx8qxp-pixel-combiner.c create mode 100644 drivers/gpu/drm/bridge/imx/imx8qxp-pixel-link.c create mode 100644 drivers/gpu/drm/bridge/imx/imx8qxp-pxl2dpi.c create mode 100644 drivers/gpu/drm/bridge/imx/imx93-mipi-dsi.c create mode 100644 drivers/gpu/drm/bridge/imx/imx93-pdfc.c create mode 100644 drivers/gpu/drm/bridge/inno-hdmi.c create mode 100644 drivers/gpu/drm/bridge/ite-it6263.c create mode 100644 drivers/gpu/drm/bridge/ite-it6505.c create mode 100644 drivers/gpu/drm/bridge/ite-it66121.c create mode 100644 drivers/gpu/drm/bridge/lontium-lt8713sx.c create mode 100644 drivers/gpu/drm/bridge/lontium-lt8912b.c create mode 100644 drivers/gpu/drm/bridge/lontium-lt9211.c create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611.c create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611uxc.c create mode 100644 drivers/gpu/drm/bridge/lvds-codec.c create mode 100644 drivers/gpu/drm/bridge/megachips-stdpxxxx-ge-b850v3-fw.c create mode 100644 drivers/gpu/drm/bridge/microchip-lvds.c create mode 100644 drivers/gpu/drm/bridge/nwl-dsi.c create mode 100644 drivers/gpu/drm/bridge/nwl-dsi.h create mode 100644 drivers/gpu/drm/bridge/nxp-ptn3460.c create mode 100644 drivers/gpu/drm/bridge/of-display-mode-bridge.c create mode 100644 drivers/gpu/drm/bridge/panel.c create mode 100644 drivers/gpu/drm/bridge/parade-ps8622.c create mode 100644 drivers/gpu/drm/bridge/parade-ps8640.c create mode 100644 drivers/gpu/drm/bridge/samsung-dsim.c create mode 100644 drivers/gpu/drm/bridge/sii902x.c create mode 100644 drivers/gpu/drm/bridge/sii9234.c create mode 100644 drivers/gpu/drm/bridge/sil-sii8620.c create mode 100644 drivers/gpu/drm/bridge/sil-sii8620.h create mode 100644 drivers/gpu/drm/bridge/simple-bridge.c create mode 100644 drivers/gpu/drm/bridge/ssd2825.c create mode 100644 drivers/gpu/drm/bridge/synopsys/Kconfig create mode 100644 drivers/gpu/drm/bridge/synopsys/Makefile create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-dp.c create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi-ahb-audio.c create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi-audio.h create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.h create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi-i2s-audio.c create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi.c create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-hdmi.h create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c create mode 100644 drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi2.c create mode 100644 drivers/gpu/drm/bridge/tc358762.c create mode 100644 drivers/gpu/drm/bridge/tc358764.c create mode 100644 drivers/gpu/drm/bridge/tc358767.c create mode 100644 drivers/gpu/drm/bridge/tc358768.c create mode 100644 drivers/gpu/drm/bridge/tc358775.c create mode 100644 drivers/gpu/drm/bridge/tda998x_drv.c create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c create mode 100644 drivers/gpu/drm/bridge/thc63lvd1024.c create mode 100644 drivers/gpu/drm/bridge/ti-dlpc3433.c create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi83.c create mode 100644 drivers/gpu/drm/bridge/ti-sn65dsi86.c create mode 100644 drivers/gpu/drm/bridge/ti-tdp158.c create mode 100644 drivers/gpu/drm/bridge/ti-tfp410.c create mode 100644 drivers/gpu/drm/bridge/ti-tpd12s015.c create mode 100644 drivers/gpu/drm/bridge/waveshare-dsi.c (limited to 'drivers/gpu/drm/bridge') diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig new file mode 100644 index 000000000..4a57d49b4 --- /dev/null +++ b/drivers/gpu/drm/bridge/Kconfig @@ -0,0 +1,516 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_BRIDGE + def_bool y + depends on DRM + help + Bridge registration and lookup framework. + +config DRM_PANEL_BRIDGE + def_bool y + depends on DRM_BRIDGE + select DRM_PANEL + help + DRM bridge wrapper of DRM panels + +config DRM_AUX_BRIDGE + tristate + depends on DRM_BRIDGE && OF + select AUXILIARY_BUS + select DRM_KMS_HELPER + select DRM_PANEL_BRIDGE + help + Simple transparent bridge that is used by several non-DRM drivers to + build bridges chain. + +config DRM_AUX_HPD_BRIDGE + tristate + depends on DRM_BRIDGE && OF + select AUXILIARY_BUS + help + Simple bridge that terminates the bridge chain and provides HPD + support. + +menu "Display Interface Bridges" + depends on DRM && DRM_BRIDGE + +config DRM_CHIPONE_ICN6211 + tristate "Chipone ICN6211 MIPI-DSI/RGB Converter bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL_BRIDGE + select REGMAP_I2C + help + ICN6211 is MIPI-DSI/RGB Converter bridge from chipone. + + It has a flexible configuration of MIPI DSI signal input + and produce RGB565, RGB666, RGB888 output format. + + If in doubt, say "N". + +config DRM_CHRONTEL_CH7033 + tristate "Chrontel CH7033 Video Encoder" + depends on OF + select DRM_KMS_HELPER + help + Enable support for the Chrontel CH7033 VGA/DVI/HDMI Encoder, as + found in the Dell Wyse 3020 thin client. + + If in doubt, say "N". + +config DRM_CROS_EC_ANX7688 + tristate "ChromeOS EC ANX7688 bridge" + depends on OF + depends on I2C_CROS_EC_TUNNEL || COMPILE_TEST + select DRM_KMS_HELPER + select REGMAP_I2C + help + ChromeOS EC ANX7688 is an ultra-low power + 4K Ultra-HD (4096x2160p60) mobile HD transmitter + designed for ChromeOS devices. It converts HDMI + 2.0 to DisplayPort 1.3 Ultra-HD. It is connected + to the ChromeOS Embedded Controller. + +config DRM_DISPLAY_CONNECTOR + tristate "Display connector support" + depends on OF + help + Driver for display connectors with support for DDC and hot-plug + detection. Most display controllers handle display connectors + internally and don't need this driver, but the DRM subsystem is + moving towards separating connector handling from display controllers + on ARM-based platforms. Saying Y here when this driver is not needed + will not cause any issue. + +config DRM_FSL_LDB + tristate "Freescale i.MX8MP LDB bridge" + depends on OF + depends on ARCH_MXC || COMPILE_TEST + select DRM_KMS_HELPER + select DRM_PANEL_BRIDGE + help + Support for i.MX8MP DPI-to-LVDS on-SoC encoder. + +config DRM_I2C_NXP_TDA998X + tristate "NXP Semiconductors TDA998X HDMI encoder" + default m if DRM_TILCDC + select CEC_CORE if CEC_NOTIFIER + select DRM_KMS_HELPER + select SND_SOC_HDMI_CODEC if SND_SOC + help + Support for NXP Semiconductors TDA998X HDMI encoders. + +config DRM_INNO_HDMI + tristate + select DRM_BRIDGE_CONNECTOR + select DRM_DISPLAY_HDMI_HELPER + select DRM_DISPLAY_HELPER + select DRM_KMS_HELPER + +config DRM_ITE_IT6263 + tristate "ITE IT6263 LVDS/HDMI bridge" + depends on OF + select DRM_DISPLAY_HDMI_STATE_HELPER + select DRM_DISPLAY_HELPER + select DRM_BRIDGE_CONNECTOR + select DRM_KMS_HELPER + select REGMAP_I2C + help + ITE IT6263 LVDS to HDMI bridge chip driver. + +config DRM_ITE_IT6505 + tristate "ITE IT6505 DisplayPort bridge" + depends on OF + select DRM_DISPLAY_DP_HELPER + select DRM_DISPLAY_HDCP_HELPER + select DRM_DISPLAY_HELPER + select DRM_DISPLAY_DP_AUX_BUS + select DRM_KMS_HELPER + select EXTCON + select CRYPTO_LIB_SHA1 + select REGMAP_I2C + help + ITE IT6505 DisplayPort bridge chip driver. + +config DRM_LONTIUM_LT8912B + tristate "Lontium LT8912B DSI/HDMI bridge" + depends on OF + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select REGMAP_I2C + select VIDEOMODE_HELPERS + help + Driver for Lontium LT8912B DSI to HDMI bridge + chip driver. + Please say Y if you have such hardware. + + Say M here if you want to support this hardware as a module. + The module will be named "lontium-lt8912b". + +config DRM_LONTIUM_LT9211 + tristate "Lontium LT9211 DSI/LVDS/DPI bridge" + depends on OF + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select REGMAP_I2C + help + Driver for Lontium LT9211 Single/Dual-Link DSI/LVDS or Single DPI + input to Single-link/Dual-Link DSI/LVDS or Single DPI output bridge + chip. + Please say Y if you have such hardware. + +config DRM_LONTIUM_LT9611 + tristate "Lontium LT9611 DSI/HDMI bridge" + select SND_SOC_HDMI_CODEC if SND_SOC + depends on OF + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_DISPLAY_HELPER + select DRM_DISPLAY_HDMI_STATE_HELPER + select REGMAP_I2C + help + Driver for Lontium LT9611 DSI to HDMI bridge + chip driver that converts dual DSI and I2S to + HDMI signals + Please say Y if you have such hardware. + +config DRM_LONTIUM_LT9611UXC + tristate "Lontium LT9611UXC DSI/HDMI bridge" + select SND_SOC_HDMI_CODEC if SND_SOC + depends on OF + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select REGMAP_I2C + help + Driver for Lontium LT9611UXC DSI to HDMI bridge + chip driver that converts dual DSI and I2S to + HDMI signals + Please say Y if you have such hardware. + +config DRM_LONTIUM_LT8713SX + tristate "Lontium LT8713SX DP MST bridge" + depends on OF + select CRC8 + select REGMAP_I2C + help + Driver for Lontium LT8713SX DP MST bridge + chip firmware upgrade, which converts Type-C/DP1.4 + to 3 configurable Type-C/DP1.4/HDMI2.0 outputs + Please say Y if you have such hardware. + +config DRM_ITE_IT66121 + tristate "ITE IT66121 HDMI bridge" + depends on OF + select DRM_DISPLAY_HDMI_STATE_HELPER + select DRM_DISPLAY_HELPER + select DRM_KMS_HELPER + select REGMAP_I2C + help + Support for ITE IT66121 HDMI bridge. + +config DRM_LVDS_CODEC + tristate "Transparent LVDS encoders and decoders support" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL_BRIDGE + help + Support for transparent LVDS encoders and decoders that don't + require any configuration. + +config DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW + tristate "MegaChips stdp4028-ge-b850v3-fw and stdp2690-ge-b850v3-fw" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + help + This is a driver for the display bridges of + GE B850v3 that convert dual channel LVDS + to DP++. This is used with the i.MX6 imx-ldb + driver. You are likely to say N here. + +config DRM_MICROCHIP_LVDS_SERIALIZER + tristate "Microchip LVDS serializer support" + depends on OF + depends on DRM_ATMEL_HLCDC + help + Support for Microchip's LVDS serializer. + +config DRM_NWL_MIPI_DSI + tristate "Northwest Logic MIPI DSI Host controller" + depends on DRM + depends on COMMON_CLK + depends on OF && HAS_IOMEM + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL_BRIDGE + select GENERIC_PHY + select GENERIC_PHY_MIPI_DPHY + select MFD_SYSCON + select MULTIPLEXER + select REGMAP_MMIO + help + This enables the Northwest Logic MIPI DSI Host controller as + for example found on NXP's i.MX8 Processors. + +config DRM_NXP_PTN3460 + tristate "NXP PTN3460 DP/LVDS bridge" + depends on OF + select DRM_KMS_HELPER + select DRM_PANEL + help + NXP PTN3460 eDP-LVDS bridge chip driver. + +config DRM_OF_DISPLAY_MODE_BRIDGE + tristate + depends on DRM_BRIDGE && OF + help + This is a DRM bridge implementation that uses of_get_drm_display_mode + to acquire display mode. + + It exists for compatibility with legacy display mode parsing, in order + to conform to the panel-bridge framework. + +config DRM_PARADE_PS8622 + tristate "Parade eDP/LVDS bridge" + depends on OF + select DRM_PANEL + select DRM_KMS_HELPER + select BACKLIGHT_CLASS_DEVICE + help + Parade eDP-LVDS bridge chip driver. + +config DRM_PARADE_PS8640 + tristate "Parade PS8640 MIPI DSI to eDP Converter" + depends on OF + select DRM_DISPLAY_DP_HELPER + select DRM_DISPLAY_HELPER + select DRM_DISPLAY_DP_AUX_BUS + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL + help + Choose this option if you have PS8640 for display + The PS8640 is a high-performance and low-power + MIPI DSI to eDP converter + +config DRM_SAMSUNG_DSIM + tristate "Samsung MIPI DSIM bridge driver" + depends on COMMON_CLK + depends on OF && HAS_IOMEM + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL_BRIDGE + select GENERIC_PHY + select GENERIC_PHY_MIPI_DPHY + help + The Samsung MIPI DSIM bridge controller driver. + This MIPI DSIM bridge can be found it on Exynos SoCs and + NXP's i.MX8M Mini/Nano. + +config DRM_SIL_SII8620 + tristate "Silicon Image SII8620 HDMI/MHL bridge" + depends on OF + select DRM_KMS_HELPER + select EXTCON + depends on RC_CORE || !RC_CORE + help + Silicon Image SII8620 HDMI/MHL bridge chip driver. + +config DRM_SII902X + tristate "Silicon Image sii902x RGB/HDMI bridge" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + select I2C_MUX + select SND_SOC_HDMI_CODEC if SND_SOC + help + Silicon Image sii902x bridge chip driver. + +config DRM_SII9234 + tristate "Silicon Image SII9234 HDMI/MHL bridge" + depends on OF + help + Say Y here if you want support for the MHL interface. + It is an I2C driver, that detects connection of MHL bridge + and starts encapsulation of HDMI signal. + +config DRM_SIMPLE_BRIDGE + tristate "Simple DRM bridge support" + depends on OF + select DRM_KMS_HELPER + help + Support for non-programmable DRM bridges, such as ADI ADV7123, TI + THS8134 and THS8135 or passive resistor ladder DACs. + +config DRM_SOLOMON_SSD2825 + tristate "SSD2825 RGB/DSI bridge" + depends on SPI_MASTER && OF + select DRM_MIPI_DSI + select DRM_KMS_HELPER + select DRM_PANEL + help + Say Y here if you want support for the Solomon SSD2825 RGB/DSI + SPI bridge driver. + + Say M here if you want to support this hardware as a module. + The module will be named "ssd2825". + +config DRM_THINE_THC63LVD1024 + tristate "Thine THC63LVD1024 LVDS decoder bridge" + depends on OF + help + Thine THC63LVD1024 LVDS/parallel converter driver. + +config DRM_THEAD_TH1520_DW_HDMI + tristate "T-Head TH1520 DesignWare HDMI bridge" + depends on OF + depends on COMMON_CLK + depends on ARCH_THEAD || COMPILE_TEST + select DRM_DW_HDMI + help + Choose this to enable support for the internal HDMI bridge found + on the T-Head TH1520 SoC. + +config DRM_TOSHIBA_TC358762 + tristate "TC358762 DSI/DPI bridge" + depends on OF + select DRM_MIPI_DSI + select DRM_KMS_HELPER + select DRM_PANEL_BRIDGE + help + Toshiba TC358762 DSI/DPI bridge driver. + +config DRM_TOSHIBA_TC358764 + tristate "TC358764 DSI/LVDS bridge" + depends on OF + select DRM_MIPI_DSI + select DRM_KMS_HELPER + select DRM_PANEL + help + Toshiba TC358764 DSI/LVDS bridge driver. + +config DRM_TOSHIBA_TC358767 + tristate "Toshiba TC358767 eDP bridge" + depends on OF + select DRM_DISPLAY_DP_HELPER + select DRM_DISPLAY_HELPER + select DRM_KMS_HELPER + select REGMAP_I2C + select DRM_MIPI_DSI + select DRM_PANEL + help + Toshiba TC358767 eDP bridge chip driver. + +config DRM_TOSHIBA_TC358768 + tristate "Toshiba TC358768 MIPI DSI bridge" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + select DRM_PANEL + select DRM_MIPI_DSI + select VIDEOMODE_HELPERS + help + Toshiba TC358768AXBG/TC358778XBG DSI bridge chip driver. + +config DRM_TOSHIBA_TC358775 + tristate "Toshiba TC358775 DSI/LVDS bridge" + depends on OF + select DRM_DISPLAY_DP_HELPER + select DRM_DISPLAY_HELPER + select DRM_KMS_HELPER + select REGMAP_I2C + select DRM_PANEL + select DRM_MIPI_DSI + help + Toshiba TC358775 DSI/LVDS bridge chip driver. + +config DRM_TI_DLPC3433 + tristate "TI DLPC3433 Display controller" + depends on DRM && DRM_PANEL + depends on OF + select DRM_MIPI_DSI + help + TI DLPC3433 is a MIPI DSI based display controller bridge + for processing high resolution DMD based projectors. + + It has a flexible configuration of MIPI DSI and DPI signal + input that produces a DMD output in RGB565, RGB666, RGB888 + formats. + + It supports up to 720p resolution with 60 and 120 Hz refresh + rates. + +config DRM_TI_TDP158 + tristate "TI TDP158 HDMI/TMDS bridge" + depends on OF + select DRM_PANEL_BRIDGE + help + Texas Instruments TDP158 HDMI/TMDS Bridge driver + +config DRM_TI_TFP410 + tristate "TI TFP410 DVI/HDMI bridge" + depends on OF + select DRM_KMS_HELPER + help + Texas Instruments TFP410 DVI/HDMI Transmitter driver + +config DRM_TI_SN65DSI83 + tristate "TI SN65DSI83 and SN65DSI84 DSI to LVDS bridge" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + select DRM_PANEL + select DRM_MIPI_DSI + help + Texas Instruments SN65DSI83 and SN65DSI84 DSI to LVDS Bridge driver + +config DRM_TI_SN65DSI86 + tristate "TI SN65DSI86 DSI to eDP bridge" + depends on OF + select DRM_DISPLAY_DP_HELPER + select DRM_DISPLAY_HELPER + select DRM_BRIDGE_CONNECTOR + select DRM_KMS_HELPER + select REGMAP_I2C + select DRM_PANEL + select DRM_MIPI_DSI + select AUXILIARY_BUS + select DRM_DISPLAY_DP_AUX_BUS + help + Texas Instruments SN65DSI86 DSI to eDP Bridge driver + +config DRM_TI_TPD12S015 + tristate "TI TPD12S015 HDMI level shifter and ESD protection" + depends on OF + select DRM_KMS_HELPER + help + Texas Instruments TPD12S015 HDMI level shifter and ESD protection + driver. + +config DRM_WAVESHARE_BRIDGE + tristate "Waveshare DSI bridge" + depends on OF + depends on BACKLIGHT_CLASS_DEVICE + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select REGMAP_I2C + help + Driver for waveshare DSI to DPI bridge board. + Please say Y if you have such hardware + +source "drivers/gpu/drm/bridge/analogix/Kconfig" + +source "drivers/gpu/drm/bridge/adv7511/Kconfig" + +source "drivers/gpu/drm/bridge/cadence/Kconfig" + +source "drivers/gpu/drm/bridge/imx/Kconfig" + +source "drivers/gpu/drm/bridge/synopsys/Kconfig" + +endmenu diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile new file mode 100644 index 000000000..15cc821d8 --- /dev/null +++ b/drivers/gpu/drm/bridge/Makefile @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_DRM_AUX_BRIDGE) += aux-bridge.o +obj-$(CONFIG_DRM_AUX_HPD_BRIDGE) += aux-hpd-bridge.o +obj-$(CONFIG_DRM_CHIPONE_ICN6211) += chipone-icn6211.o +obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o +obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o +obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o +obj-$(CONFIG_DRM_FSL_LDB) += fsl-ldb.o + +tda998x-y := tda998x_drv.o +obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o + +obj-$(CONFIG_DRM_INNO_HDMI) += inno-hdmi.o +obj-$(CONFIG_DRM_ITE_IT6263) += ite-it6263.o +obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o +obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o +obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o +obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o +obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o +obj-$(CONFIG_DRM_LONTIUM_LT8713SX) += lontium-lt8713sx.o +obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o +obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o +obj-$(CONFIG_DRM_MICROCHIP_LVDS_SERIALIZER) += microchip-lvds.o +obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o +obj-$(CONFIG_DRM_OF_DISPLAY_MODE_BRIDGE) += of-display-mode-bridge.o +obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o +obj-$(CONFIG_DRM_PARADE_PS8640) += parade-ps8640.o +obj-$(CONFIG_DRM_SAMSUNG_DSIM) += samsung-dsim.o +obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o +obj-$(CONFIG_DRM_SII902X) += sii902x.o +obj-$(CONFIG_DRM_SII9234) += sii9234.o +obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.o +obj-$(CONFIG_DRM_SOLOMON_SSD2825) += ssd2825.o +obj-$(CONFIG_DRM_THEAD_TH1520_DW_HDMI) += th1520-dw-hdmi.o +obj-$(CONFIG_DRM_THINE_THC63LVD1024) += thc63lvd1024.o +obj-$(CONFIG_DRM_TOSHIBA_TC358762) += tc358762.o +obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o +obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o +obj-$(CONFIG_DRM_TOSHIBA_TC358768) += tc358768.o +obj-$(CONFIG_DRM_TOSHIBA_TC358775) += tc358775.o +obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/ +obj-$(CONFIG_DRM_TI_DLPC3433) += ti-dlpc3433.o +obj-$(CONFIG_DRM_TI_SN65DSI83) += ti-sn65dsi83.o +obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o +obj-$(CONFIG_DRM_TI_TDP158) += ti-tdp158.o +obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o +obj-$(CONFIG_DRM_TI_TPD12S015) += ti-tpd12s015.o +obj-$(CONFIG_DRM_WAVESHARE_BRIDGE) += waveshare-dsi.o +obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-dsi.o +obj-$(CONFIG_DRM_ITE_IT66121) += ite-it66121.o + +obj-y += analogix/ +obj-y += cadence/ +obj-y += imx/ +obj-y += synopsys/ diff --git a/drivers/gpu/drm/bridge/adv7511/Kconfig b/drivers/gpu/drm/bridge/adv7511/Kconfig new file mode 100644 index 000000000..59a5256ce --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/Kconfig @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_I2C_ADV7511 + tristate "ADV7511 encoder" + depends on OF + select DRM_KMS_HELPER + select REGMAP_I2C + select DRM_MIPI_DSI + select DRM_DISPLAY_HELPER + select DRM_BRIDGE_CONNECTOR + select DRM_DISPLAY_HDMI_STATE_HELPER + help + Support for the Analog Devices ADV7511(W)/13/33/35 HDMI encoders. + +config DRM_I2C_ADV7511_AUDIO + bool "ADV7511 HDMI Audio driver" + depends on DRM_I2C_ADV7511 && SND_SOC + select SND_SOC_HDMI_CODEC + help + Support the ADV7511 HDMI Audio interface. This is used in + conjunction with the AV7511 HDMI driver. + +config DRM_I2C_ADV7511_CEC + bool "ADV7511/33/35 HDMI CEC driver" + depends on DRM_I2C_ADV7511 + select DRM_DISPLAY_HDMI_CEC_HELPER + default y + help + When selected the HDMI transmitter will support the CEC feature. diff --git a/drivers/gpu/drm/bridge/adv7511/Makefile b/drivers/gpu/drm/bridge/adv7511/Makefile new file mode 100644 index 000000000..d8ceb534b --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +adv7511-y := adv7511_drv.o adv7533.o +adv7511-$(CONFIG_DRM_I2C_ADV7511_AUDIO) += adv7511_audio.o +adv7511-$(CONFIG_DRM_I2C_ADV7511_CEC) += adv7511_cec.o +obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h new file mode 100644 index 000000000..12c95198d --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -0,0 +1,442 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Analog Devices ADV7511 HDMI transmitter driver + * + * Copyright 2012 Analog Devices Inc. + */ + +#ifndef __DRM_I2C_ADV7511_H__ +#define __DRM_I2C_ADV7511_H__ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define ADV7511_REG_CHIP_REVISION 0x00 +#define ADV7511_REG_N0 0x01 +#define ADV7511_REG_N1 0x02 +#define ADV7511_REG_N2 0x03 +#define ADV7511_REG_SPDIF_FREQ 0x04 +#define ADV7511_REG_CTS_AUTOMATIC1 0x05 +#define ADV7511_REG_CTS_AUTOMATIC2 0x06 +#define ADV7511_REG_CTS_MANUAL0 0x07 +#define ADV7511_REG_CTS_MANUAL1 0x08 +#define ADV7511_REG_CTS_MANUAL2 0x09 +#define ADV7511_REG_AUDIO_SOURCE 0x0a +#define ADV7511_REG_AUDIO_CONFIG 0x0b +#define ADV7511_REG_I2S_CONFIG 0x0c +#define ADV7511_REG_I2S_WIDTH 0x0d +#define ADV7511_REG_AUDIO_SUB_SRC0 0x0e +#define ADV7511_REG_AUDIO_SUB_SRC1 0x0f +#define ADV7511_REG_AUDIO_SUB_SRC2 0x10 +#define ADV7511_REG_AUDIO_SUB_SRC3 0x11 +#define ADV7511_REG_AUDIO_CFG1 0x12 +#define ADV7511_REG_AUDIO_CFG2 0x13 +#define ADV7511_REG_AUDIO_CFG3 0x14 +#define ADV7511_REG_I2C_FREQ_ID_CFG 0x15 +#define ADV7511_REG_VIDEO_INPUT_CFG1 0x16 +#define ADV7511_REG_CSC_UPPER(x) (0x18 + (x) * 2) +#define ADV7511_REG_CSC_LOWER(x) (0x19 + (x) * 2) +#define ADV7511_REG_SYNC_DECODER(x) (0x30 + (x)) +#define ADV7511_REG_DE_GENERATOR (0x35 + (x)) +#define ADV7511_REG_PIXEL_REPETITION 0x3b +#define ADV7511_REG_VIC_MANUAL 0x3c +#define ADV7511_REG_VIC_SEND 0x3d +#define ADV7511_REG_VIC_DETECTED 0x3e +#define ADV7511_REG_AUX_VIC_DETECTED 0x3f +#define ADV7511_REG_PACKET_ENABLE0 0x40 +#define ADV7511_REG_POWER 0x41 +#define ADV7511_REG_STATUS 0x42 +#define ADV7511_REG_EDID_I2C_ADDR 0x43 +#define ADV7511_REG_PACKET_ENABLE1 0x44 +#define ADV7511_REG_PACKET_I2C_ADDR 0x45 +#define ADV7511_REG_DSD_ENABLE 0x46 +#define ADV7511_REG_VIDEO_INPUT_CFG2 0x48 +#define ADV7511_REG_INFOFRAME_UPDATE 0x4a +#define ADV7511_REG_GC(x) (0x4b + (x)) /* 0x4b - 0x51 */ +#define ADV7511_REG_AVI_INFOFRAME_VERSION 0x52 +#define ADV7511_REG_AVI_INFOFRAME_LENGTH 0x53 +#define ADV7511_REG_AVI_INFOFRAME_CHECKSUM 0x54 +#define ADV7511_REG_AVI_INFOFRAME(x) (0x55 + (x)) /* 0x55 - 0x6f */ +#define ADV7511_REG_AUDIO_INFOFRAME_VERSION 0x70 +#define ADV7511_REG_AUDIO_INFOFRAME_LENGTH 0x71 +#define ADV7511_REG_AUDIO_INFOFRAME_CHECKSUM 0x72 +#define ADV7511_REG_AUDIO_INFOFRAME(x) (0x73 + (x)) /* 0x73 - 0x7c */ +#define ADV7511_REG_INT_ENABLE(x) (0x94 + (x)) +#define ADV7511_REG_INT(x) (0x96 + (x)) +#define ADV7511_REG_INPUT_CLK_DIV 0x9d +#define ADV7511_REG_PLL_STATUS 0x9e +#define ADV7511_REG_HDMI_POWER 0xa1 +#define ADV7511_REG_HDCP_HDMI_CFG 0xaf +#define ADV7511_REG_AN(x) (0xb0 + (x)) /* 0xb0 - 0xb7 */ +#define ADV7511_REG_HDCP_STATUS 0xb8 +#define ADV7511_REG_BCAPS 0xbe +#define ADV7511_REG_BKSV(x) (0xc0 + (x)) /* 0xc0 - 0xc3 */ +#define ADV7511_REG_EDID_SEGMENT 0xc4 +#define ADV7511_REG_DDC_STATUS 0xc8 +#define ADV7511_REG_EDID_READ_CTRL 0xc9 +#define ADV7511_REG_BSTATUS(x) (0xca + (x)) /* 0xca - 0xcb */ +#define ADV7511_REG_TIMING_GEN_SEQ 0xd0 +#define ADV7511_REG_POWER2 0xd6 +#define ADV7511_REG_HSYNC_PLACEMENT_MSB 0xfa + +#define ADV7511_REG_SYNC_ADJUSTMENT(x) (0xd7 + (x)) /* 0xd7 - 0xdc */ +#define ADV7511_REG_TMDS_CLOCK_INV 0xde +#define ADV7511_REG_ARC_CTRL 0xdf +#define ADV7511_REG_CEC_I2C_ADDR 0xe1 +#define ADV7511_REG_CEC_CTRL 0xe2 +#define ADV7511_REG_CHIP_ID_HIGH 0xf5 +#define ADV7511_REG_CHIP_ID_LOW 0xf6 + +/* Hardware defined default addresses for I2C register maps */ +#define ADV7511_CEC_I2C_ADDR_DEFAULT 0x3c +#define ADV7511_EDID_I2C_ADDR_DEFAULT 0x3f +#define ADV7511_PACKET_I2C_ADDR_DEFAULT 0x38 + +#define ADV7511_CSC_ENABLE BIT(7) +#define ADV7511_CSC_UPDATE_MODE BIT(5) + +#define ADV7511_INT0_HPD BIT(7) +#define ADV7511_INT0_VSYNC BIT(5) +#define ADV7511_INT0_AUDIO_FIFO_FULL BIT(4) +#define ADV7511_INT0_EDID_READY BIT(2) +#define ADV7511_INT0_HDCP_AUTHENTICATED BIT(1) + +#define ADV7511_INT1_DDC_ERROR BIT(7) +#define ADV7511_INT1_BKSV BIT(6) +#define ADV7511_INT1_CEC_TX_READY BIT(5) +#define ADV7511_INT1_CEC_TX_ARBIT_LOST BIT(4) +#define ADV7511_INT1_CEC_TX_RETRY_TIMEOUT BIT(3) +#define ADV7511_INT1_CEC_RX_READY3 BIT(2) +#define ADV7511_INT1_CEC_RX_READY2 BIT(1) +#define ADV7511_INT1_CEC_RX_READY1 BIT(0) + +#define ADV7511_ARC_CTRL_POWER_DOWN BIT(0) + +#define ADV7511_CEC_CTRL_POWER_DOWN BIT(0) + +#define ADV7511_POWER_POWER_DOWN BIT(6) + +#define ADV7511_HDMI_CFG_MODE_MASK 0x2 +#define ADV7511_HDMI_CFG_MODE_DVI 0x0 +#define ADV7511_HDMI_CFG_MODE_HDMI 0x2 + +#define ADV7511_AUDIO_SELECT_I2C 0x0 +#define ADV7511_AUDIO_SELECT_SPDIF 0x1 +#define ADV7511_AUDIO_SELECT_DSD 0x2 +#define ADV7511_AUDIO_SELECT_HBR 0x3 +#define ADV7511_AUDIO_SELECT_DST 0x4 + +#define ADV7511_I2S_SAMPLE_LEN_16 0x2 +#define ADV7511_I2S_SAMPLE_LEN_20 0x3 +#define ADV7511_I2S_SAMPLE_LEN_18 0x4 +#define ADV7511_I2S_SAMPLE_LEN_22 0x5 +#define ADV7511_I2S_SAMPLE_LEN_19 0x8 +#define ADV7511_I2S_SAMPLE_LEN_23 0x9 +#define ADV7511_I2S_SAMPLE_LEN_24 0xb +#define ADV7511_I2S_SAMPLE_LEN_17 0xc +#define ADV7511_I2S_SAMPLE_LEN_21 0xd + +#define ADV7511_SAMPLE_FREQ_44100 0x0 +#define ADV7511_SAMPLE_FREQ_48000 0x2 +#define ADV7511_SAMPLE_FREQ_32000 0x3 +#define ADV7511_SAMPLE_FREQ_88200 0x8 +#define ADV7511_SAMPLE_FREQ_96000 0xa +#define ADV7511_SAMPLE_FREQ_176400 0xc +#define ADV7511_SAMPLE_FREQ_192000 0xe + +#define ADV7511_STATUS_POWER_DOWN_POLARITY BIT(7) +#define ADV7511_STATUS_HPD BIT(6) +#define ADV7511_STATUS_MONITOR_SENSE BIT(5) +#define ADV7511_STATUS_I2S_32BIT_MODE BIT(3) + +#define ADV7511_PACKET_ENABLE_N_CTS BIT(8+6) +#define ADV7511_PACKET_ENABLE_AUDIO_SAMPLE BIT(8+5) +#define ADV7511_PACKET_ENABLE_AVI_INFOFRAME BIT(8+4) +#define ADV7511_PACKET_ENABLE_AUDIO_INFOFRAME BIT(8+3) +#define ADV7511_PACKET_ENABLE_GC BIT(7) +#define ADV7511_PACKET_ENABLE_SPD BIT(6) +#define ADV7511_PACKET_ENABLE_MPEG BIT(5) +#define ADV7511_PACKET_ENABLE_ACP BIT(4) +#define ADV7511_PACKET_ENABLE_ISRC BIT(3) +#define ADV7511_PACKET_ENABLE_GM BIT(2) +#define ADV7511_PACKET_ENABLE_SPARE2 BIT(1) +#define ADV7511_PACKET_ENABLE_SPARE1 BIT(0) + +#define ADV7535_REG_POWER2_HPD_OVERRIDE BIT(6) +#define ADV7511_REG_POWER2_HPD_SRC_MASK 0xc0 +#define ADV7511_REG_POWER2_HPD_SRC_BOTH 0x00 +#define ADV7511_REG_POWER2_HPD_SRC_HPD 0x40 +#define ADV7511_REG_POWER2_HPD_SRC_CEC 0x80 +#define ADV7511_REG_POWER2_HPD_SRC_NONE 0xc0 +#define ADV7511_REG_POWER2_TDMS_ENABLE BIT(4) +#define ADV7511_REG_POWER2_GATE_INPUT_CLK BIT(0) + +#define ADV7511_LOW_REFRESH_RATE_NONE 0x0 +#define ADV7511_LOW_REFRESH_RATE_24HZ 0x1 +#define ADV7511_LOW_REFRESH_RATE_25HZ 0x2 +#define ADV7511_LOW_REFRESH_RATE_30HZ 0x3 + +#define ADV7511_AUDIO_CFG3_LEN_MASK 0x0f +#define ADV7511_I2C_FREQ_ID_CFG_RATE_MASK 0xf0 + +#define ADV7511_AUDIO_SOURCE_I2S 0 +#define ADV7511_AUDIO_SOURCE_SPDIF 1 + +#define ADV7511_I2S_FORMAT_I2S 0 +#define ADV7511_I2S_FORMAT_RIGHT_J 1 +#define ADV7511_I2S_FORMAT_LEFT_J 2 +#define ADV7511_I2S_IEC958_DIRECT 3 + +#define ADV7511_PACKET(p, x) ((p) * 0x20 + (x)) +#define ADV7511_PACKET_SPD(x) ADV7511_PACKET(0, x) +#define ADV7511_PACKET_MPEG(x) ADV7511_PACKET(1, x) +#define ADV7511_PACKET_ACP(x) ADV7511_PACKET(2, x) +#define ADV7511_PACKET_ISRC1(x) ADV7511_PACKET(3, x) +#define ADV7511_PACKET_ISRC2(x) ADV7511_PACKET(4, x) +#define ADV7511_PACKET_GM(x) ADV7511_PACKET(5, x) +#define ADV7511_PACKET_SPARE1(x) ADV7511_PACKET(6, x) +#define ADV7511_PACKET_SPARE2(x) ADV7511_PACKET(7, x) + +#define ADV7511_REG_CEC_TX_FRAME_HDR 0x00 +#define ADV7511_REG_CEC_TX_FRAME_DATA0 0x01 +#define ADV7511_REG_CEC_TX_FRAME_LEN 0x10 +#define ADV7511_REG_CEC_TX_ENABLE 0x11 +#define ADV7511_REG_CEC_TX_RETRY 0x12 +#define ADV7511_REG_CEC_TX_LOW_DRV_CNT 0x14 +#define ADV7511_REG_CEC_RX1_FRAME_HDR 0x15 +#define ADV7511_REG_CEC_RX1_FRAME_DATA0 0x16 +#define ADV7511_REG_CEC_RX1_FRAME_LEN 0x25 +#define ADV7511_REG_CEC_RX_STATUS 0x26 +#define ADV7511_REG_CEC_RX2_FRAME_HDR 0x27 +#define ADV7511_REG_CEC_RX2_FRAME_DATA0 0x28 +#define ADV7511_REG_CEC_RX2_FRAME_LEN 0x37 +#define ADV7511_REG_CEC_RX3_FRAME_HDR 0x38 +#define ADV7511_REG_CEC_RX3_FRAME_DATA0 0x39 +#define ADV7511_REG_CEC_RX3_FRAME_LEN 0x48 +#define ADV7511_REG_CEC_RX_BUFFERS 0x4a +#define ADV7511_REG_CEC_LOG_ADDR_MASK 0x4b +#define ADV7511_REG_CEC_LOG_ADDR_0_1 0x4c +#define ADV7511_REG_CEC_LOG_ADDR_2 0x4d +#define ADV7511_REG_CEC_CLK_DIV 0x4e +#define ADV7511_REG_CEC_SOFT_RESET 0x50 + +#define ADV7533_REG_CEC_OFFSET 0x70 + +enum adv7511_input_clock { + ADV7511_INPUT_CLOCK_1X, + ADV7511_INPUT_CLOCK_2X, + ADV7511_INPUT_CLOCK_DDR, +}; + +enum adv7511_input_justification { + ADV7511_INPUT_JUSTIFICATION_EVENLY = 0, + ADV7511_INPUT_JUSTIFICATION_RIGHT = 1, + ADV7511_INPUT_JUSTIFICATION_LEFT = 2, +}; + +enum adv7511_input_sync_pulse { + ADV7511_INPUT_SYNC_PULSE_DE = 0, + ADV7511_INPUT_SYNC_PULSE_HSYNC = 1, + ADV7511_INPUT_SYNC_PULSE_VSYNC = 2, + ADV7511_INPUT_SYNC_PULSE_NONE = 3, +}; + +/** + * enum adv7511_sync_polarity - Polarity for the input sync signals + * @ADV7511_SYNC_POLARITY_PASSTHROUGH: Sync polarity matches that of + * the currently configured mode. + * @ADV7511_SYNC_POLARITY_LOW: Sync polarity is low + * @ADV7511_SYNC_POLARITY_HIGH: Sync polarity is high + * + * If the polarity is set to either LOW or HIGH the driver will configure the + * ADV7511 to internally invert the sync signal if required to match the sync + * polarity setting for the currently selected output mode. + * + * If the polarity is set to PASSTHROUGH, the ADV7511 will route the signal + * unchanged. This is used when the upstream graphics core already generates + * the sync signals with the correct polarity. + */ +enum adv7511_sync_polarity { + ADV7511_SYNC_POLARITY_PASSTHROUGH, + ADV7511_SYNC_POLARITY_LOW, + ADV7511_SYNC_POLARITY_HIGH, +}; + +/** + * struct adv7511_link_config - Describes adv7511 hardware configuration + * @input_color_depth: Number of bits per color component (8, 10 or 12) + * @input_colorspace: The input colorspace (RGB, YUV444, YUV422) + * @input_clock: The input video clock style (1x, 2x, DDR) + * @input_style: The input component arrangement variant + * @input_justification: Video input format bit justification + * @clock_delay: Clock delay for the input clock (in ps) + * @embedded_sync: Video input uses BT.656-style embedded sync + * @sync_pulse: Select the sync pulse + * @vsync_polarity: vsync input signal configuration + * @hsync_polarity: hsync input signal configuration + */ +struct adv7511_link_config { + unsigned int input_color_depth; + enum hdmi_colorspace input_colorspace; + enum adv7511_input_clock input_clock; + unsigned int input_style; + enum adv7511_input_justification input_justification; + + int clock_delay; + + bool embedded_sync; + enum adv7511_input_sync_pulse sync_pulse; + enum adv7511_sync_polarity vsync_polarity; + enum adv7511_sync_polarity hsync_polarity; +}; + +/** + * enum adv7511_csc_scaling - Scaling factor for the ADV7511 CSC + * @ADV7511_CSC_SCALING_1: CSC results are not scaled + * @ADV7511_CSC_SCALING_2: CSC results are scaled by a factor of two + * @ADV7511_CSC_SCALING_4: CSC results are scalled by a factor of four + */ +enum adv7511_csc_scaling { + ADV7511_CSC_SCALING_1 = 0, + ADV7511_CSC_SCALING_2 = 1, + ADV7511_CSC_SCALING_4 = 2, +}; + +/** + * struct adv7511_video_config - Describes adv7511 hardware configuration + * @csc_enable: Whether to enable color space conversion + * @csc_scaling_factor: Color space conversion scaling factor + * @csc_coefficents: Color space conversion coefficents + */ +struct adv7511_video_config { + bool csc_enable; + enum adv7511_csc_scaling csc_scaling_factor; + const uint16_t *csc_coefficents; +}; + +enum adv7511_type { + ADV7511, + ADV7533, + ADV7535, +}; + +#define ADV7511_MAX_ADDRS 3 + +struct adv7511_chip_info { + enum adv7511_type type; + unsigned int max_mode_clock_khz; + unsigned int max_lane_freq_khz; + const char *name; + const char * const *supply_names; + unsigned int num_supplies; + unsigned int reg_cec_offset; + bool has_dsi; + bool link_config; + bool hpd_override_enable; +}; + +struct adv7511 { + struct i2c_client *i2c_main; + struct i2c_client *i2c_edid; + struct i2c_client *i2c_packet; + struct i2c_client *i2c_cec; + + struct regmap *regmap; + struct regmap *regmap_packet; + struct regmap *regmap_cec; + enum drm_connector_status status; + bool powered; + + struct drm_display_mode curr_mode; + + unsigned int f_tmds; + unsigned int f_audio; + unsigned int audio_source; + + unsigned int current_edid_segment; + uint8_t edid_buf[256]; + bool edid_read; + + wait_queue_head_t wq; + struct work_struct hpd_work; + + struct drm_bridge bridge; + struct drm_connector *cec_connector; + + bool embedded_sync; + enum adv7511_sync_polarity vsync_polarity; + enum adv7511_sync_polarity hsync_polarity; + bool rgb; + + struct gpio_desc *gpio_pd; + + struct regulator_bulk_data *supplies; + + /* ADV7533 DSI RX related params */ + struct device_node *host_node; + struct mipi_dsi_device *dsi; + u8 num_dsi_lanes; + bool use_timing_gen; + + const struct adv7511_chip_info *info; + + u8 cec_addr[ADV7511_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; + struct clk *cec_clk; + u32 cec_clk_freq; +}; + +static inline struct adv7511 *bridge_to_adv7511(struct drm_bridge *bridge) +{ + return container_of(bridge, struct adv7511, bridge); +} + +#ifdef CONFIG_DRM_I2C_ADV7511_CEC +int adv7511_cec_init(struct drm_bridge *bridge, + struct drm_connector *connector); +int adv7511_cec_enable(struct drm_bridge *bridge, bool enable); +int adv7511_cec_log_addr(struct drm_bridge *bridge, u8 addr); +int adv7511_cec_transmit(struct drm_bridge *bridge, u8 attempts, + u32 signal_free_time, struct cec_msg *msg); +int adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1); +#else +#define adv7511_cec_init NULL +#define adv7511_cec_enable NULL +#define adv7511_cec_log_addr NULL +#define adv7511_cec_transmit NULL +#endif + +void adv7533_dsi_power_on(struct adv7511 *adv); +void adv7533_dsi_power_off(struct adv7511 *adv); +void adv7533_dsi_config_timing_gen(struct adv7511 *adv); +enum drm_mode_status adv7533_mode_valid(struct adv7511 *adv, + const struct drm_display_mode *mode); +int adv7533_patch_registers(struct adv7511 *adv); +int adv7533_patch_cec_registers(struct adv7511 *adv); +int adv7533_attach_dsi(struct adv7511 *adv); +int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv); + +#ifdef CONFIG_DRM_I2C_ADV7511_AUDIO +int adv7511_hdmi_audio_startup(struct drm_bridge *bridge, + struct drm_connector *connector); +void adv7511_hdmi_audio_shutdown(struct drm_bridge *bridge, + struct drm_connector *connector); +int adv7511_hdmi_audio_prepare(struct drm_bridge *bridge, + struct drm_connector *connector, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms); +#else /*CONFIG_DRM_I2C_ADV7511_AUDIO */ +#define adv7511_hdmi_audio_startup NULL +#define adv7511_hdmi_audio_shutdown NULL +#define adv7511_hdmi_audio_prepare NULL +#endif /* CONFIG_DRM_I2C_ADV7511_AUDIO */ + +#endif /* __DRM_I2C_ADV7511_H__ */ diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_audio.c b/drivers/gpu/drm/bridge/adv7511/adv7511_audio.c new file mode 100644 index 000000000..87e7e8208 --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_audio.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices ADV7511 HDMI transmitter driver + * + * Copyright 2012 Analog Devices Inc. + * Copyright (c) 2016, Linaro Limited + */ + +#include +#include +#include +#include +#include + +#include + +#include "adv7511.h" + +static void adv7511_calc_cts_n(unsigned int f_tmds, unsigned int fs, + unsigned int *cts, unsigned int *n) +{ + switch (fs) { + case 32000: + case 48000: + case 96000: + case 192000: + *n = fs * 128 / 1000; + break; + case 44100: + case 88200: + case 176400: + *n = fs * 128 / 900; + break; + } + + *cts = ((f_tmds * *n) / (128 * fs)) * 1000; +} + +static int adv7511_update_cts_n(struct adv7511 *adv7511) +{ + unsigned int cts = 0; + unsigned int n = 0; + + adv7511_calc_cts_n(adv7511->f_tmds, adv7511->f_audio, &cts, &n); + + regmap_write(adv7511->regmap, ADV7511_REG_N0, (n >> 16) & 0xf); + regmap_write(adv7511->regmap, ADV7511_REG_N1, (n >> 8) & 0xff); + regmap_write(adv7511->regmap, ADV7511_REG_N2, n & 0xff); + + regmap_write(adv7511->regmap, ADV7511_REG_CTS_MANUAL0, + (cts >> 16) & 0xf); + regmap_write(adv7511->regmap, ADV7511_REG_CTS_MANUAL1, + (cts >> 8) & 0xff); + regmap_write(adv7511->regmap, ADV7511_REG_CTS_MANUAL2, + cts & 0xff); + + return 0; +} + +int adv7511_hdmi_audio_prepare(struct drm_bridge *bridge, + struct drm_connector *connector, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + unsigned int audio_source, i2s_format = 0; + unsigned int invert_clock; + unsigned int rate; + unsigned int len; + + switch (hparms->sample_rate) { + case 32000: + rate = ADV7511_SAMPLE_FREQ_32000; + break; + case 44100: + rate = ADV7511_SAMPLE_FREQ_44100; + break; + case 48000: + rate = ADV7511_SAMPLE_FREQ_48000; + break; + case 88200: + rate = ADV7511_SAMPLE_FREQ_88200; + break; + case 96000: + rate = ADV7511_SAMPLE_FREQ_96000; + break; + case 176400: + rate = ADV7511_SAMPLE_FREQ_176400; + break; + case 192000: + rate = ADV7511_SAMPLE_FREQ_192000; + break; + default: + return -EINVAL; + } + + switch (hparms->sample_width) { + case 16: + len = ADV7511_I2S_SAMPLE_LEN_16; + break; + case 18: + len = ADV7511_I2S_SAMPLE_LEN_18; + break; + case 20: + len = ADV7511_I2S_SAMPLE_LEN_20; + break; + case 32: + if (fmt->bit_fmt != SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE) + return -EINVAL; + fallthrough; + case 24: + len = ADV7511_I2S_SAMPLE_LEN_24; + break; + default: + return -EINVAL; + } + + switch (fmt->fmt) { + case HDMI_I2S: + audio_source = ADV7511_AUDIO_SOURCE_I2S; + i2s_format = ADV7511_I2S_FORMAT_I2S; + if (fmt->bit_fmt == SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE) + i2s_format = ADV7511_I2S_IEC958_DIRECT; + break; + case HDMI_RIGHT_J: + audio_source = ADV7511_AUDIO_SOURCE_I2S; + i2s_format = ADV7511_I2S_FORMAT_RIGHT_J; + break; + case HDMI_LEFT_J: + audio_source = ADV7511_AUDIO_SOURCE_I2S; + i2s_format = ADV7511_I2S_FORMAT_LEFT_J; + break; + case HDMI_SPDIF: + audio_source = ADV7511_AUDIO_SOURCE_SPDIF; + break; + default: + return -EINVAL; + } + + invert_clock = fmt->bit_clk_inv; + + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_SOURCE, 0x70, + audio_source << 4); + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG, BIT(6), + invert_clock << 6); + regmap_update_bits(adv7511->regmap, ADV7511_REG_I2S_CONFIG, 0x03, + i2s_format); + + adv7511->audio_source = audio_source; + + adv7511->f_audio = hparms->sample_rate; + + adv7511_update_cts_n(adv7511); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CFG3, + ADV7511_AUDIO_CFG3_LEN_MASK, len); + regmap_update_bits(adv7511->regmap, ADV7511_REG_I2C_FREQ_ID_CFG, + ADV7511_I2C_FREQ_ID_CFG_RATE_MASK, rate << 4); + + return drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector, + &hparms->cea); +} + +int adv7511_hdmi_audio_startup(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG, + BIT(7), 0); + + /* hide Audio infoframe updates */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE, + BIT(5), BIT(5)); + /* enable N/CTS, enable Audio sample packets */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1, + BIT(5), BIT(5)); + /* enable N/CTS */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1, + BIT(6), BIT(6)); + /* not copyrighted */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CFG1, + BIT(5), BIT(5)); + /* AV mute disable */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_GC(0), + BIT(7) | BIT(6), BIT(7)); + + /* enable SPDIF receiver */ + if (adv7511->audio_source == ADV7511_AUDIO_SOURCE_SPDIF) + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG, + BIT(7), BIT(7)); + + return 0; +} + +void adv7511_hdmi_audio_shutdown(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + if (adv7511->audio_source == ADV7511_AUDIO_SOURCE_SPDIF) + regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG, + BIT(7), 0); + + drm_atomic_helper_connector_hdmi_clear_audio_infoframe(connector); +} diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c new file mode 100644 index 000000000..8ecbc25dc --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * adv7511_cec.c - Analog Devices ADV7511/33 cec driver + * + * Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + */ + +#include +#include +#include +#include + +#include + +#include + +#include "adv7511.h" + +static const u8 ADV7511_REG_CEC_RX_FRAME_HDR[] = { + ADV7511_REG_CEC_RX1_FRAME_HDR, + ADV7511_REG_CEC_RX2_FRAME_HDR, + ADV7511_REG_CEC_RX3_FRAME_HDR, +}; + +static const u8 ADV7511_REG_CEC_RX_FRAME_LEN[] = { + ADV7511_REG_CEC_RX1_FRAME_LEN, + ADV7511_REG_CEC_RX2_FRAME_LEN, + ADV7511_REG_CEC_RX3_FRAME_LEN, +}; + +#define ADV7511_INT1_CEC_MASK \ + (ADV7511_INT1_CEC_TX_READY | ADV7511_INT1_CEC_TX_ARBIT_LOST | \ + ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1 | \ + ADV7511_INT1_CEC_RX_READY2 | ADV7511_INT1_CEC_RX_READY3) + +static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status) +{ + unsigned int offset = adv7511->info->reg_cec_offset; + unsigned int val; + + if (regmap_read(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_ENABLE + offset, &val)) + return; + + if ((val & 0x01) == 0) + return; + + if (tx_raw_status & ADV7511_INT1_CEC_TX_ARBIT_LOST) { + drm_connector_hdmi_cec_transmit_attempt_done(adv7511->cec_connector, + CEC_TX_STATUS_ARB_LOST); + return; + } + if (tx_raw_status & ADV7511_INT1_CEC_TX_RETRY_TIMEOUT) { + u8 status; + u8 err_cnt = 0; + u8 nack_cnt = 0; + u8 low_drive_cnt = 0; + unsigned int cnt; + + /* + * We set this status bit since this hardware performs + * retransmissions. + */ + status = CEC_TX_STATUS_MAX_RETRIES; + if (regmap_read(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_LOW_DRV_CNT + offset, &cnt)) { + err_cnt = 1; + status |= CEC_TX_STATUS_ERROR; + } else { + nack_cnt = cnt & 0xf; + if (nack_cnt) + status |= CEC_TX_STATUS_NACK; + low_drive_cnt = cnt >> 4; + if (low_drive_cnt) + status |= CEC_TX_STATUS_LOW_DRIVE; + } + drm_connector_hdmi_cec_transmit_done(adv7511->cec_connector, status, + 0, nack_cnt, low_drive_cnt, + err_cnt); + return; + } + if (tx_raw_status & ADV7511_INT1_CEC_TX_READY) { + drm_connector_hdmi_cec_transmit_attempt_done(adv7511->cec_connector, + CEC_TX_STATUS_OK); + return; + } +} + +static void adv7511_cec_rx(struct adv7511 *adv7511, int rx_buf) +{ + unsigned int offset = adv7511->info->reg_cec_offset; + struct cec_msg msg = {}; + unsigned int len; + unsigned int val; + u8 i; + + if (regmap_read(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_FRAME_LEN[rx_buf] + offset, &len)) + return; + + msg.len = len & 0x1f; + + if (msg.len > 16) + msg.len = 16; + + if (!msg.len) + return; + + for (i = 0; i < msg.len; i++) { + regmap_read(adv7511->regmap_cec, + i + ADV7511_REG_CEC_RX_FRAME_HDR[rx_buf] + offset, + &val); + msg.msg[i] = val; + } + + /* Toggle RX Ready Clear bit to re-enable this RX buffer */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, BIT(rx_buf), + BIT(rx_buf)); + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, BIT(rx_buf), 0); + + drm_connector_hdmi_cec_received_msg(adv7511->cec_connector, &msg); +} + +int adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1) +{ + unsigned int offset = adv7511->info->reg_cec_offset; + const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY | + ADV7511_INT1_CEC_TX_ARBIT_LOST | + ADV7511_INT1_CEC_TX_RETRY_TIMEOUT; + const u32 irq_rx_mask = ADV7511_INT1_CEC_RX_READY1 | + ADV7511_INT1_CEC_RX_READY2 | + ADV7511_INT1_CEC_RX_READY3; + unsigned int rx_status; + int rx_order[3] = { -1, -1, -1 }; + int i; + int irq_status = IRQ_NONE; + + if (irq1 & irq_tx_mask) { + adv_cec_tx_raw_status(adv7511, irq1); + irq_status = IRQ_HANDLED; + } + + if (!(irq1 & irq_rx_mask)) + return irq_status; + + if (regmap_read(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_STATUS + offset, &rx_status)) + return irq_status; + + /* + * ADV7511_REG_CEC_RX_STATUS[5:0] contains the reception order of RX + * buffers 0, 1, and 2 in bits [1:0], [3:2], and [5:4] respectively. + * The values are to be interpreted as follows: + * + * 0 = buffer unused + * 1 = buffer contains oldest received frame (if applicable) + * 2 = buffer contains second oldest received frame (if applicable) + * 3 = buffer contains third oldest received frame (if applicable) + * + * Fill rx_order with the sequence of RX buffer indices to + * read from in order, where -1 indicates that there are no + * more buffers to process. + */ + for (i = 0; i < 3; i++) { + unsigned int timestamp = (rx_status >> (2 * i)) & 0x3; + + if (timestamp) + rx_order[timestamp - 1] = i; + } + + /* Read CEC RX buffers in the appropriate order as prescribed above */ + for (i = 0; i < 3; i++) { + int rx_buf = rx_order[i]; + + if (rx_buf < 0) + break; + + adv7511_cec_rx(adv7511, rx_buf); + } + + return IRQ_HANDLED; +} + +int adv7511_cec_enable(struct drm_bridge *bridge, bool enable) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + unsigned int offset = adv7511->info->reg_cec_offset; + + if (adv7511->i2c_cec == NULL) + return -EIO; + + if (!adv7511->cec_enabled_adap && enable) { + /* power up cec section */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_CLK_DIV + offset, + 0x03, 0x01); + /* non-legacy mode and clear all rx buffers */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, 0x0f); + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, 0x08); + /* initially disable tx */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_ENABLE + offset, 1, 0); + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready 1-3 */ + regmap_update_bits(adv7511->regmap, + ADV7511_REG_INT_ENABLE(1), 0x3f, + ADV7511_INT1_CEC_MASK); + } else if (adv7511->cec_enabled_adap && !enable) { + regmap_update_bits(adv7511->regmap, + ADV7511_REG_INT_ENABLE(1), 0x3f, 0); + /* disable address mask 1-3 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x70, 0x00); + /* power down cec section */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_CLK_DIV + offset, + 0x03, 0x00); + adv7511->cec_valid_addrs = 0; + } + adv7511->cec_enabled_adap = enable; + return 0; +} + +int adv7511_cec_log_addr(struct drm_bridge *bridge, u8 addr) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + unsigned int offset = adv7511->info->reg_cec_offset; + unsigned int i, free_idx = ADV7511_MAX_ADDRS; + + if (!adv7511->cec_enabled_adap) + return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x70, 0); + adv7511->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV7511_MAX_ADDRS; i++) { + bool is_valid = adv7511->cec_valid_addrs & (1 << i); + + if (free_idx == ADV7511_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && adv7511->cec_addr[i] == addr) + return 0; + } + if (i == ADV7511_MAX_ADDRS) { + i = free_idx; + if (i == ADV7511_MAX_ADDRS) + return -ENXIO; + } + adv7511->cec_addr[i] = addr; + adv7511->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x10, 0x10); + /* set address for mask 0 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_0_1 + offset, + 0x0f, addr); + break; + case 1: + /* enable address mask 1 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x20, 0x20); + /* set address for mask 1 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_0_1 + offset, + 0xf0, addr << 4); + break; + case 2: + /* enable address mask 2 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x40, 0x40); + /* set address for mask 1 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_2 + offset, + 0x0f, addr); + break; + } + return 0; +} + +int adv7511_cec_transmit(struct drm_bridge *bridge, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + unsigned int offset = adv7511->info->reg_cec_offset; + u8 len = msg->len; + unsigned int i; + + /* + * The number of retries is the number of attempts - 1, but retry + * at least once. It's not clear if a value of 0 is allowed, so + * let's do at least one retry. + */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_RETRY + offset, + 0x70, max(1, attempts - 1) << 4); + + /* blocking, clear cec tx irq status */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_INT(1), 0x38, 0x38); + + /* write data */ + for (i = 0; i < len; i++) + regmap_write(adv7511->regmap_cec, + i + ADV7511_REG_CEC_TX_FRAME_HDR + offset, + msg->msg[i]); + + /* set length (data + header) */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_FRAME_LEN + offset, len); + /* start transmit, enable tx */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_ENABLE + offset, 0x01); + return 0; +} + +static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511) +{ + adv7511->cec_clk = devm_clk_get(dev, "cec"); + if (IS_ERR(adv7511->cec_clk)) { + int ret = PTR_ERR(adv7511->cec_clk); + + adv7511->cec_clk = NULL; + return ret; + } + clk_prepare_enable(adv7511->cec_clk); + adv7511->cec_clk_freq = clk_get_rate(adv7511->cec_clk); + return 0; +} + +int adv7511_cec_init(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + struct device *dev = &adv7511->i2c_main->dev; + unsigned int offset = adv7511->info->reg_cec_offset; + int ret = adv7511_cec_parse_dt(dev, adv7511); + + if (ret) + goto err_cec_parse_dt; + + adv7511->cec_connector = connector; + + regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL, 0); + /* cec soft reset */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_SOFT_RESET + offset, 0x01); + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_SOFT_RESET + offset, 0x00); + + /* non-legacy mode - use all three RX buffers */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, 0x08); + + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_CLK_DIV + offset, + ((adv7511->cec_clk_freq / 750000) - 1) << 2); + + return 0; + +err_cec_parse_dt: + regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL, + ADV7511_CEC_CTRL_POWER_DOWN); + return ret == -EPROBE_DEFER ? ret : 0; +} diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c new file mode 100644 index 000000000..038b85240 --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -0,0 +1,1538 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Analog Devices ADV7511 HDMI transmitter driver + * + * Copyright 2012 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "adv7511.h" + +/* ADI recommended values for proper operation. */ +static const struct reg_sequence adv7511_fixed_registers[] = { + { 0x98, 0x03 }, + { 0x9a, 0xe0 }, + { 0x9c, 0x30 }, + { 0x9d, 0x61 }, + { 0xa2, 0xa4 }, + { 0xa3, 0xa4 }, + { 0xe0, 0xd0 }, + { 0xf9, 0x00 }, + { 0x55, 0x02 }, +}; + +/* ----------------------------------------------------------------------------- + * Register access + */ + +static const uint8_t adv7511_register_defaults[] = { + 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00 */ + 0x00, 0x00, 0x01, 0x0e, 0xbc, 0x18, 0x01, 0x13, + 0x25, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10 */ + 0x46, 0x62, 0x04, 0xa8, 0x00, 0x00, 0x1c, 0x84, + 0x1c, 0xbf, 0x04, 0xa8, 0x1e, 0x70, 0x02, 0x1e, /* 20 */ + 0x00, 0x00, 0x04, 0xa8, 0x08, 0x12, 0x1b, 0xac, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 30 */ + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xb0, + 0x00, 0x50, 0x90, 0x7e, 0x79, 0x70, 0x00, 0x00, /* 40 */ + 0x00, 0xa8, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x0d, 0x00, 0x00, 0x00, 0x00, /* 50 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 60 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 70 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 80 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, /* 90 */ + 0x0b, 0x02, 0x00, 0x18, 0x5a, 0x60, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x80, 0x08, 0x04, 0x00, 0x00, /* a0 */ + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* b0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* c0 */ + 0x00, 0x03, 0x00, 0x00, 0x02, 0x00, 0x01, 0x04, + 0x30, 0xff, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, /* d0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, + 0x80, 0x75, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, /* e0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x11, 0x00, /* f0 */ + 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static bool adv7511_register_volatile(struct device *dev, unsigned int reg) +{ + switch (reg) { + case ADV7511_REG_CHIP_REVISION: + case ADV7511_REG_SPDIF_FREQ: + case ADV7511_REG_CTS_AUTOMATIC1: + case ADV7511_REG_CTS_AUTOMATIC2: + case ADV7511_REG_VIC_DETECTED: + case ADV7511_REG_VIC_SEND: + case ADV7511_REG_AUX_VIC_DETECTED: + case ADV7511_REG_STATUS: + case ADV7511_REG_GC(1): + case ADV7511_REG_INT(0): + case ADV7511_REG_INT(1): + case ADV7511_REG_PLL_STATUS: + case ADV7511_REG_AN(0): + case ADV7511_REG_AN(1): + case ADV7511_REG_AN(2): + case ADV7511_REG_AN(3): + case ADV7511_REG_AN(4): + case ADV7511_REG_AN(5): + case ADV7511_REG_AN(6): + case ADV7511_REG_AN(7): + case ADV7511_REG_HDCP_STATUS: + case ADV7511_REG_BCAPS: + case ADV7511_REG_BKSV(0): + case ADV7511_REG_BKSV(1): + case ADV7511_REG_BKSV(2): + case ADV7511_REG_BKSV(3): + case ADV7511_REG_BKSV(4): + case ADV7511_REG_DDC_STATUS: + case ADV7511_REG_EDID_READ_CTRL: + case ADV7511_REG_BSTATUS(0): + case ADV7511_REG_BSTATUS(1): + case ADV7511_REG_CHIP_ID_HIGH: + case ADV7511_REG_CHIP_ID_LOW: + return true; + } + + return false; +} + +static const struct regmap_config adv7511_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_MAPLE, + .reg_defaults_raw = adv7511_register_defaults, + .num_reg_defaults_raw = ARRAY_SIZE(adv7511_register_defaults), + + .volatile_reg = adv7511_register_volatile, +}; + +static const struct regmap_config adv7511_packet_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, +}; + +/* ----------------------------------------------------------------------------- + * Hardware configuration + */ + +static void adv7511_set_colormap(struct adv7511 *adv7511, bool enable, + const uint16_t *coeff, + unsigned int scaling_factor) +{ + unsigned int i; + + regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(1), + ADV7511_CSC_UPDATE_MODE, ADV7511_CSC_UPDATE_MODE); + + if (enable) { + for (i = 0; i < 12; ++i) { + regmap_update_bits(adv7511->regmap, + ADV7511_REG_CSC_UPPER(i), + 0x1f, coeff[i] >> 8); + regmap_write(adv7511->regmap, + ADV7511_REG_CSC_LOWER(i), + coeff[i] & 0xff); + } + } + + if (enable) + regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(0), + 0xe0, 0x80 | (scaling_factor << 5)); + else + regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(0), + 0x80, 0x00); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(1), + ADV7511_CSC_UPDATE_MODE, 0); +} + +static int adv7511_packet_enable(struct adv7511 *adv7511, unsigned int packet) +{ + if (packet & 0xff) + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE0, + packet, 0xff); + + if (packet & 0xff00) { + packet >>= 8; + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1, + packet, 0xff); + } + + return 0; +} + +static int adv7511_packet_disable(struct adv7511 *adv7511, unsigned int packet) +{ + if (packet & 0xff) + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE0, + packet, 0x00); + + if (packet & 0xff00) { + packet >>= 8; + regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1, + packet, 0x00); + } + + return 0; +} + +/* Coefficients for adv7511 color space conversion */ +static const uint16_t adv7511_csc_ycbcr_to_rgb[] = { + 0x0734, 0x04ad, 0x0000, 0x1c1b, + 0x1ddc, 0x04ad, 0x1f24, 0x0135, + 0x0000, 0x04ad, 0x087c, 0x1b77, +}; + +static void adv7511_set_config_csc(struct adv7511 *adv7511, + struct drm_connector *connector, + bool rgb) +{ + struct adv7511_video_config config; + bool output_format_422, output_format_ycbcr; + unsigned int mode; + + if (rgb) { + config.csc_enable = false; + output_format_422 = false; + output_format_ycbcr = false; + } else { + config.csc_scaling_factor = ADV7511_CSC_SCALING_4; + config.csc_coefficents = adv7511_csc_ycbcr_to_rgb; + + if ((connector->display_info.color_formats & + BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) && + connector->display_info.is_hdmi) { + config.csc_enable = false; + output_format_422 = true; + output_format_ycbcr = true; + } else { + config.csc_enable = true; + output_format_422 = false; + output_format_ycbcr = false; + } + } + + if (connector->display_info.is_hdmi) + mode = ADV7511_HDMI_CFG_MODE_HDMI; + else + mode = ADV7511_HDMI_CFG_MODE_DVI; + + adv7511_set_colormap(adv7511, config.csc_enable, + config.csc_coefficents, + config.csc_scaling_factor); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG1, 0x81, + (output_format_422 << 7) | output_format_ycbcr); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_HDCP_HDMI_CFG, + ADV7511_HDMI_CFG_MODE_MASK, mode); +} + +static void adv7511_set_link_config(struct adv7511 *adv7511, + const struct adv7511_link_config *config) +{ + /* + * The input style values documented in the datasheet don't match the + * hardware register field values :-( + */ + static const unsigned int input_styles[4] = { 0, 2, 1, 3 }; + + unsigned int clock_delay; + unsigned int color_depth; + unsigned int input_id; + + clock_delay = (config->clock_delay + 1200) / 400; + color_depth = config->input_color_depth == 8 ? 3 + : (config->input_color_depth == 10 ? 1 : 2); + + /* TODO Support input ID 6 */ + if (config->input_colorspace != HDMI_COLORSPACE_YUV422) + input_id = config->input_clock == ADV7511_INPUT_CLOCK_DDR + ? 5 : 0; + else if (config->input_clock == ADV7511_INPUT_CLOCK_DDR) + input_id = config->embedded_sync ? 8 : 7; + else if (config->input_clock == ADV7511_INPUT_CLOCK_2X) + input_id = config->embedded_sync ? 4 : 3; + else + input_id = config->embedded_sync ? 2 : 1; + + regmap_update_bits(adv7511->regmap, ADV7511_REG_I2C_FREQ_ID_CFG, 0xf, + input_id); + regmap_update_bits(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG1, 0x7e, + (color_depth << 4) | + (input_styles[config->input_style] << 2)); + regmap_write(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG2, + config->input_justification << 3); + regmap_write(adv7511->regmap, ADV7511_REG_TIMING_GEN_SEQ, + config->sync_pulse << 2); + + regmap_write(adv7511->regmap, 0xba, clock_delay << 5); + + adv7511->embedded_sync = config->embedded_sync; + adv7511->hsync_polarity = config->hsync_polarity; + adv7511->vsync_polarity = config->vsync_polarity; + adv7511->rgb = config->input_colorspace == HDMI_COLORSPACE_RGB; +} + +static void __adv7511_power_on(struct adv7511 *adv7511) +{ + adv7511->current_edid_segment = -1; + + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, + ADV7511_POWER_POWER_DOWN, 0); + if (adv7511->i2c_main->irq) { + /* + * Documentation says the INT_ENABLE registers are reset in + * POWER_DOWN mode. My 7511w preserved the bits, however. + * Still, let's be safe and stick to the documentation. + */ + regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0), + ADV7511_INT0_EDID_READY | ADV7511_INT0_HPD); + regmap_update_bits(adv7511->regmap, + ADV7511_REG_INT_ENABLE(1), + ADV7511_INT1_DDC_ERROR, + ADV7511_INT1_DDC_ERROR); + } + + /* + * Per spec it is allowed to pulse the HPD signal to indicate that the + * EDID information has changed. Some monitors do this when they wakeup + * from standby or are enabled. When the HPD goes low the adv7511 is + * reset and the outputs are disabled which might cause the monitor to + * go to standby again. To avoid this we ignore the HPD pin for the + * first few seconds after enabling the output. On the other hand + * adv7535 require to enable HPD Override bit for proper HPD. + */ + if (adv7511->info->hpd_override_enable) + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7535_REG_POWER2_HPD_OVERRIDE, + ADV7535_REG_POWER2_HPD_OVERRIDE); + else + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7511_REG_POWER2_HPD_SRC_MASK, + ADV7511_REG_POWER2_HPD_SRC_NONE); +} + +static void adv7511_power_on(struct adv7511 *adv7511) +{ + __adv7511_power_on(adv7511); + + /* + * Most of the registers are reset during power down or when HPD is low. + */ + regcache_sync(adv7511->regmap); + + if (adv7511->info->has_dsi) + adv7533_dsi_power_on(adv7511); + adv7511->powered = true; +} + +static void __adv7511_power_off(struct adv7511 *adv7511) +{ + /* TODO: setup additional power down modes */ + if (adv7511->info->hpd_override_enable) + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7535_REG_POWER2_HPD_OVERRIDE, 0); + + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, + ADV7511_POWER_POWER_DOWN, + ADV7511_POWER_POWER_DOWN); + regmap_update_bits(adv7511->regmap, + ADV7511_REG_INT_ENABLE(1), + ADV7511_INT1_DDC_ERROR, 0); + regcache_mark_dirty(adv7511->regmap); +} + +static void adv7511_power_off(struct adv7511 *adv7511) +{ + __adv7511_power_off(adv7511); + if (adv7511->info->has_dsi) + adv7533_dsi_power_off(adv7511); + adv7511->powered = false; +} + +/* ----------------------------------------------------------------------------- + * Interrupt and hotplug detection + */ + +static bool adv7511_hpd(struct adv7511 *adv7511) +{ + unsigned int irq0; + int ret; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(0), &irq0); + if (ret < 0) + return false; + + if (irq0 & ADV7511_INT0_HPD) { + regmap_write(adv7511->regmap, ADV7511_REG_INT(0), + ADV7511_INT0_HPD); + return true; + } + + return false; +} + +static void adv7511_hpd_work(struct work_struct *work) +{ + struct adv7511 *adv7511 = container_of(work, struct adv7511, hpd_work); + enum drm_connector_status status; + unsigned int val; + int ret; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_STATUS, &val); + if (ret < 0) + status = connector_status_disconnected; + else if (val & ADV7511_STATUS_HPD) + status = connector_status_connected; + else + status = connector_status_disconnected; + + /* + * The bridge resets its registers on unplug. So when we get a plug + * event and we're already supposed to be powered, cycle the bridge to + * restore its state. + */ + if (status == connector_status_connected && + adv7511->status == connector_status_disconnected && + adv7511->powered) { + regcache_mark_dirty(adv7511->regmap); + adv7511_power_on(adv7511); + } + + if (adv7511->status != status) { + adv7511->status = status; + + drm_bridge_hpd_notify(&adv7511->bridge, status); + } +} + +static int adv7511_irq_process(struct adv7511 *adv7511, bool process_hpd) +{ + unsigned int irq0, irq1; + int ret; + int cec_status = IRQ_NONE; + int irq_status = IRQ_NONE; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(0), &irq0); + if (ret < 0) + return ret; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(1), &irq1); + if (ret < 0) + return ret; + + regmap_write(adv7511->regmap, ADV7511_REG_INT(0), irq0); + regmap_write(adv7511->regmap, ADV7511_REG_INT(1), irq1); + + if (process_hpd && irq0 & ADV7511_INT0_HPD && adv7511->bridge.encoder) { + schedule_work(&adv7511->hpd_work); + irq_status = IRQ_HANDLED; + } + + if (irq0 & ADV7511_INT0_EDID_READY || irq1 & ADV7511_INT1_DDC_ERROR) { + adv7511->edid_read = true; + + if (adv7511->i2c_main->irq) + wake_up_all(&adv7511->wq); + irq_status = IRQ_HANDLED; + } + +#ifdef CONFIG_DRM_I2C_ADV7511_CEC + cec_status = adv7511_cec_irq_process(adv7511, irq1); +#endif + + /* If there is no IRQ to handle, exit indicating no IRQ data */ + if (irq_status == IRQ_HANDLED || cec_status == IRQ_HANDLED) + return IRQ_HANDLED; + + return IRQ_NONE; +} + +static irqreturn_t adv7511_irq_handler(int irq, void *devid) +{ + struct adv7511 *adv7511 = devid; + int ret; + + ret = adv7511_irq_process(adv7511, true); + return ret < 0 ? IRQ_NONE : ret; +} + +/* ----------------------------------------------------------------------------- + * EDID retrieval + */ + +static int adv7511_wait_for_edid(struct adv7511 *adv7511, int timeout) +{ + int ret; + + if (adv7511->i2c_main->irq) { + ret = wait_event_interruptible_timeout(adv7511->wq, + adv7511->edid_read, msecs_to_jiffies(timeout)); + } else { + for (; timeout > 0; timeout -= 25) { + ret = adv7511_irq_process(adv7511, false); + if (ret < 0) + break; + + if (adv7511->edid_read) + break; + + msleep(25); + } + } + + return adv7511->edid_read ? 0 : -EIO; +} + +static int adv7511_get_edid_block(void *data, u8 *buf, unsigned int block, + size_t len) +{ + struct adv7511 *adv7511 = data; + struct i2c_msg xfer[2]; + uint8_t offset; + unsigned int i; + int ret; + + if (len > 128) + return -EINVAL; + + if (adv7511->current_edid_segment != block / 2) { + unsigned int status; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_DDC_STATUS, + &status); + if (ret < 0) + return ret; + + if (status != 2) { + adv7511->edid_read = false; + regmap_write(adv7511->regmap, ADV7511_REG_EDID_SEGMENT, + block); + ret = adv7511_wait_for_edid(adv7511, 200); + if (ret < 0) + return ret; + } + + /* Break this apart, hopefully more I2C controllers will + * support 64 byte transfers than 256 byte transfers + */ + + xfer[0].addr = adv7511->i2c_edid->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = &offset; + xfer[1].addr = adv7511->i2c_edid->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 64; + xfer[1].buf = adv7511->edid_buf; + + offset = 0; + + for (i = 0; i < 4; ++i) { + ret = i2c_transfer(adv7511->i2c_edid->adapter, xfer, + ARRAY_SIZE(xfer)); + if (ret < 0) + return ret; + else if (ret != 2) + return -EIO; + + xfer[1].buf += 64; + offset += 64; + } + + adv7511->current_edid_segment = block / 2; + } + + if (block % 2 == 0) + memcpy(buf, adv7511->edid_buf, len); + else + memcpy(buf, adv7511->edid_buf + 128, len); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * ADV75xx helpers + */ + +static const struct drm_edid *adv7511_edid_read(struct adv7511 *adv7511, + struct drm_connector *connector) +{ + const struct drm_edid *drm_edid; + + /* Reading the EDID only works if the device is powered */ + if (!adv7511->powered) { + unsigned int edid_i2c_addr = + (adv7511->i2c_edid->addr << 1); + + __adv7511_power_on(adv7511); + + /* Reset the EDID_I2C_ADDR register as it might be cleared */ + regmap_write(adv7511->regmap, ADV7511_REG_EDID_I2C_ADDR, + edid_i2c_addr); + } + + drm_edid = drm_edid_read_custom(connector, adv7511_get_edid_block, adv7511); + + if (!adv7511->powered) + __adv7511_power_off(adv7511); + + return drm_edid; +} + +static enum drm_connector_status +adv7511_detect(struct adv7511 *adv7511) +{ + enum drm_connector_status status; + unsigned int val; + bool hpd; + int ret; + + ret = regmap_read(adv7511->regmap, ADV7511_REG_STATUS, &val); + if (ret < 0) + return connector_status_disconnected; + + if (val & ADV7511_STATUS_HPD) + status = connector_status_connected; + else + status = connector_status_disconnected; + + hpd = adv7511_hpd(adv7511); + + /* The chip resets itself when the cable is disconnected, so in case + * there is a pending HPD interrupt and the cable is connected there was + * at least one transition from disconnected to connected and the chip + * has to be reinitialized. */ + if (status == connector_status_connected && hpd && adv7511->powered) { + regcache_mark_dirty(adv7511->regmap); + adv7511_power_on(adv7511); + if (adv7511->status == connector_status_connected) + status = connector_status_disconnected; + } else { + /* Renable HPD sensing */ + if (adv7511->info->hpd_override_enable) + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7535_REG_POWER2_HPD_OVERRIDE, + ADV7535_REG_POWER2_HPD_OVERRIDE); + else + regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2, + ADV7511_REG_POWER2_HPD_SRC_MASK, + ADV7511_REG_POWER2_HPD_SRC_BOTH); + } + + adv7511->status = status; + return status; +} + +static void adv7511_mode_set(struct adv7511 *adv7511, + const struct drm_display_mode *adj_mode) +{ + unsigned int low_refresh_rate; + unsigned int hsync_polarity = 0; + unsigned int vsync_polarity = 0; + + if (adv7511->embedded_sync) { + unsigned int hsync_offset, hsync_len; + unsigned int vsync_offset, vsync_len; + + hsync_offset = adj_mode->crtc_hsync_start - + adj_mode->crtc_hdisplay; + vsync_offset = adj_mode->crtc_vsync_start - + adj_mode->crtc_vdisplay; + hsync_len = adj_mode->crtc_hsync_end - + adj_mode->crtc_hsync_start; + vsync_len = adj_mode->crtc_vsync_end - + adj_mode->crtc_vsync_start; + + /* The hardware vsync generator has a off-by-one bug */ + vsync_offset += 1; + + regmap_write(adv7511->regmap, ADV7511_REG_HSYNC_PLACEMENT_MSB, + ((hsync_offset >> 10) & 0x7) << 5); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(0), + (hsync_offset >> 2) & 0xff); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(1), + ((hsync_offset & 0x3) << 6) | + ((hsync_len >> 4) & 0x3f)); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(2), + ((hsync_len & 0xf) << 4) | + ((vsync_offset >> 6) & 0xf)); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(3), + ((vsync_offset & 0x3f) << 2) | + ((vsync_len >> 8) & 0x3)); + regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(4), + vsync_len & 0xff); + + hsync_polarity = !(adj_mode->flags & DRM_MODE_FLAG_PHSYNC); + vsync_polarity = !(adj_mode->flags & DRM_MODE_FLAG_PVSYNC); + } else { + enum adv7511_sync_polarity mode_hsync_polarity; + enum adv7511_sync_polarity mode_vsync_polarity; + + /** + * If the input signal is always low or always high we want to + * invert or let it passthrough depending on the polarity of the + * current mode. + **/ + if (adj_mode->flags & DRM_MODE_FLAG_NHSYNC) + mode_hsync_polarity = ADV7511_SYNC_POLARITY_LOW; + else + mode_hsync_polarity = ADV7511_SYNC_POLARITY_HIGH; + + if (adj_mode->flags & DRM_MODE_FLAG_NVSYNC) + mode_vsync_polarity = ADV7511_SYNC_POLARITY_LOW; + else + mode_vsync_polarity = ADV7511_SYNC_POLARITY_HIGH; + + if (adv7511->hsync_polarity != mode_hsync_polarity && + adv7511->hsync_polarity != + ADV7511_SYNC_POLARITY_PASSTHROUGH) + hsync_polarity = 1; + + if (adv7511->vsync_polarity != mode_vsync_polarity && + adv7511->vsync_polarity != + ADV7511_SYNC_POLARITY_PASSTHROUGH) + vsync_polarity = 1; + } + + if (drm_mode_vrefresh(adj_mode) <= 24) + low_refresh_rate = ADV7511_LOW_REFRESH_RATE_24HZ; + else if (drm_mode_vrefresh(adj_mode) <= 25) + low_refresh_rate = ADV7511_LOW_REFRESH_RATE_25HZ; + else if (drm_mode_vrefresh(adj_mode) <= 30) + low_refresh_rate = ADV7511_LOW_REFRESH_RATE_30HZ; + else + low_refresh_rate = ADV7511_LOW_REFRESH_RATE_NONE; + + if (adv7511->info->type == ADV7511) + regmap_update_bits(adv7511->regmap, 0xfb, + 0x6, low_refresh_rate << 1); + else + regmap_update_bits(adv7511->regmap, 0x4a, + 0xc, low_refresh_rate << 2); + + regmap_update_bits(adv7511->regmap, 0x17, + 0x60, (vsync_polarity << 6) | (hsync_polarity << 5)); + + drm_mode_copy(&adv7511->curr_mode, adj_mode); + + /* Update horizontal/vertical porch params */ + if (adv7511->info->has_dsi && adv7511->use_timing_gen) + adv7533_dsi_config_timing_gen(adv7511); + + /* + * TODO Test first order 4:2:2 to 4:4:4 up conversion method, which is + * supposed to give better results. + */ + + adv7511->f_tmds = adj_mode->clock; +} + +static int adv7511_connector_init(struct adv7511 *adv) +{ + struct drm_bridge *bridge = &adv->bridge; + struct drm_connector *connector; + + connector = drm_bridge_connector_init(bridge->dev, bridge->encoder); + if (IS_ERR(connector)) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return PTR_ERR(connector); + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * DRM Bridge Operations + */ + +static const struct adv7511 *bridge_to_adv7511_const(const struct drm_bridge *bridge) +{ + return container_of(bridge, struct adv7511, bridge); +} + +static void adv7511_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *state) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + struct drm_connector *connector; + struct drm_connector_state *conn_state; + struct drm_crtc_state *crtc_state; + + adv7511_power_on(adv); + + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); + if (WARN_ON(!connector)) + return; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (WARN_ON(!conn_state)) + return; + + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc); + if (WARN_ON(!crtc_state)) + return; + + adv7511_set_config_csc(adv, connector, adv->rgb); + + adv7511_mode_set(adv, &crtc_state->adjusted_mode); + + drm_atomic_helper_connector_hdmi_update_infoframes(connector, state); +} + +static void adv7511_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *state) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + adv7511_power_off(adv); +} + +static enum drm_mode_status +adv7511_bridge_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge, + const struct drm_display_mode *mode, + unsigned long long tmds_rate) +{ + const struct adv7511 *adv = bridge_to_adv7511_const(bridge); + + if (tmds_rate > 1000ULL * adv->info->max_mode_clock_khz) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static enum drm_mode_status adv7511_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + if (!adv->info->has_dsi) + return MODE_OK; + + return adv7533_mode_valid(adv, mode); +} + +static int adv7511_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + int ret = 0; + + if (adv->bridge.next_bridge) { + ret = drm_bridge_attach(encoder, adv->bridge.next_bridge, bridge, + flags | DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) + return ret; + } + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + ret = adv7511_connector_init(adv); + if (ret < 0) + return ret; + } + + if (adv->i2c_main->irq) + regmap_write(adv->regmap, ADV7511_REG_INT_ENABLE(0), + ADV7511_INT0_HPD); + + return ret; +} + +static enum drm_connector_status +adv7511_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + return adv7511_detect(adv); +} + +static const struct drm_edid *adv7511_bridge_edid_read(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct adv7511 *adv = bridge_to_adv7511(bridge); + + return adv7511_edid_read(adv, connector); +} + +static int adv7511_bridge_hdmi_clear_audio_infoframe(struct drm_bridge *bridge) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_AUDIO_INFOFRAME); + + return 0; +} + +static int adv7511_bridge_hdmi_clear_avi_infoframe(struct drm_bridge *bridge) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME); + + return 0; +} + +static int adv7511_bridge_hdmi_clear_spd_infoframe(struct drm_bridge *bridge) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_SPD); + + return 0; +} + +static int adv7511_bridge_hdmi_clear_hdmi_infoframe(struct drm_bridge *bridge) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_SPARE1); + + return 0; +} + +static int adv7511_bridge_hdmi_write_audio_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + /* send current Audio infoframe values while updating */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE, + BIT(5), BIT(5)); + + /* The Audio infoframe id is not configurable */ + regmap_bulk_write(adv7511->regmap, ADV7511_REG_AUDIO_INFOFRAME_VERSION, + buffer + 1, len - 1); + + /* use Audio infoframe updated info */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE, + BIT(5), 0); + + adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_AUDIO_INFOFRAME); + + return 0; +} + +static int adv7511_bridge_hdmi_write_avi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + /* send current AVI infoframe values while updating */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE, + BIT(6), BIT(6)); + + /* The AVI infoframe id is not configurable */ + regmap_bulk_write(adv7511->regmap, ADV7511_REG_AVI_INFOFRAME_VERSION, + buffer + 1, len - 1); + + regmap_write(adv7511->regmap, ADV7511_REG_AUDIO_INFOFRAME_LENGTH, 0x2); + regmap_write(adv7511->regmap, ADV7511_REG_AUDIO_INFOFRAME(1), 0x1); + + /* use AVI infoframe updated info */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE, + BIT(6), 0); + + adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME); + + return 0; +} + +static int adv7511_bridge_hdmi_write_spd_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_SPD); + regmap_bulk_write(adv7511->regmap_packet, ADV7511_PACKET_SPD(0), + buffer, len); + adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_SPD); + + return 0; +} + +static int adv7511_bridge_hdmi_write_hdmi_infoframe(struct drm_bridge *bridge, + const u8 *buffer, size_t len) +{ + struct adv7511 *adv7511 = bridge_to_adv7511(bridge); + + adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_SPARE1); + regmap_bulk_write(adv7511->regmap_packet, ADV7511_PACKET_SPARE1(0), + buffer, len); + adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_SPARE1); + + return 0; +} + +static const struct drm_bridge_funcs adv7511_bridge_funcs = { + .mode_valid = adv7511_bridge_mode_valid, + .attach = adv7511_bridge_attach, + .detect = adv7511_bridge_detect, + .edid_read = adv7511_bridge_edid_read, + + .atomic_enable = adv7511_bridge_atomic_enable, + .atomic_disable = adv7511_bridge_atomic_disable, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_create_state = drm_atomic_helper_bridge_create_state, + + .hdmi_tmds_char_rate_valid = adv7511_bridge_hdmi_tmds_char_rate_valid, + .hdmi_clear_audio_infoframe = adv7511_bridge_hdmi_clear_audio_infoframe, + .hdmi_write_audio_infoframe = adv7511_bridge_hdmi_write_audio_infoframe, + .hdmi_clear_avi_infoframe = adv7511_bridge_hdmi_clear_avi_infoframe, + .hdmi_write_avi_infoframe = adv7511_bridge_hdmi_write_avi_infoframe, + .hdmi_clear_spd_infoframe = adv7511_bridge_hdmi_clear_spd_infoframe, + .hdmi_write_spd_infoframe = adv7511_bridge_hdmi_write_spd_infoframe, + .hdmi_clear_hdmi_infoframe = adv7511_bridge_hdmi_clear_hdmi_infoframe, + .hdmi_write_hdmi_infoframe = adv7511_bridge_hdmi_write_hdmi_infoframe, + + .hdmi_audio_startup = adv7511_hdmi_audio_startup, + .hdmi_audio_prepare = adv7511_hdmi_audio_prepare, + .hdmi_audio_shutdown = adv7511_hdmi_audio_shutdown, + + .hdmi_cec_init = adv7511_cec_init, + .hdmi_cec_enable = adv7511_cec_enable, + .hdmi_cec_log_addr = adv7511_cec_log_addr, + .hdmi_cec_transmit = adv7511_cec_transmit, +}; + +/* ----------------------------------------------------------------------------- + * Probe & remove + */ + +static const char * const adv7511_supply_names[] = { + "avdd", + "dvdd", + "pvdd", + "bgvdd", + "dvdd-3v", +}; + +static const char * const adv7533_supply_names[] = { + "avdd", + "dvdd", + "pvdd", + "a2vdd", + "v3p3", + "v1p2", +}; + +static int adv7511_init_regulators(struct adv7511 *adv) +{ + const char * const *supply_names = adv->info->supply_names; + unsigned int num_supplies = adv->info->num_supplies; + struct device *dev = &adv->i2c_main->dev; + unsigned int i; + int ret; + + adv->supplies = devm_kcalloc(dev, num_supplies, + sizeof(*adv->supplies), GFP_KERNEL); + if (!adv->supplies) + return -ENOMEM; + + for (i = 0; i < num_supplies; i++) + adv->supplies[i].supply = supply_names[i]; + + ret = devm_regulator_bulk_get(dev, num_supplies, adv->supplies); + if (ret) + return ret; + + return regulator_bulk_enable(num_supplies, adv->supplies); +} + +static void adv7511_uninit_regulators(struct adv7511 *adv) +{ + regulator_bulk_disable(adv->info->num_supplies, adv->supplies); +} + +static bool adv7511_cec_register_volatile(struct device *dev, unsigned int reg) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct adv7511 *adv7511 = i2c_get_clientdata(i2c); + + reg -= adv7511->info->reg_cec_offset; + + switch (reg) { + case ADV7511_REG_CEC_RX1_FRAME_HDR: + case ADV7511_REG_CEC_RX1_FRAME_DATA0 ... ADV7511_REG_CEC_RX1_FRAME_DATA0 + 14: + case ADV7511_REG_CEC_RX1_FRAME_LEN: + case ADV7511_REG_CEC_RX2_FRAME_HDR: + case ADV7511_REG_CEC_RX2_FRAME_DATA0 ... ADV7511_REG_CEC_RX2_FRAME_DATA0 + 14: + case ADV7511_REG_CEC_RX2_FRAME_LEN: + case ADV7511_REG_CEC_RX3_FRAME_HDR: + case ADV7511_REG_CEC_RX3_FRAME_DATA0 ... ADV7511_REG_CEC_RX3_FRAME_DATA0 + 14: + case ADV7511_REG_CEC_RX3_FRAME_LEN: + case ADV7511_REG_CEC_RX_STATUS: + case ADV7511_REG_CEC_RX_BUFFERS: + case ADV7511_REG_CEC_TX_LOW_DRV_CNT: + return true; + } + + return false; +} + +static const struct regmap_config adv7511_cec_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_MAPLE, + .volatile_reg = adv7511_cec_register_volatile, +}; + +static int adv7511_init_cec_regmap(struct adv7511 *adv) +{ + int ret; + + adv->i2c_cec = i2c_new_ancillary_device(adv->i2c_main, "cec", + ADV7511_CEC_I2C_ADDR_DEFAULT); + if (IS_ERR(adv->i2c_cec)) + return PTR_ERR(adv->i2c_cec); + + regmap_write(adv->regmap, ADV7511_REG_CEC_I2C_ADDR, + adv->i2c_cec->addr << 1); + + i2c_set_clientdata(adv->i2c_cec, adv); + + adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec, + &adv7511_cec_regmap_config); + if (IS_ERR(adv->regmap_cec)) { + ret = PTR_ERR(adv->regmap_cec); + goto err; + } + + if (adv->info->reg_cec_offset == ADV7533_REG_CEC_OFFSET) { + ret = adv7533_patch_cec_registers(adv); + if (ret) + goto err; + } + + return 0; +err: + i2c_unregister_device(adv->i2c_cec); + return ret; +} + +static int adv7511_parse_dt(struct device_node *np, + struct adv7511_link_config *config) +{ + const char *str; + int ret; + + of_property_read_u32(np, "adi,input-depth", &config->input_color_depth); + if (config->input_color_depth != 8 && config->input_color_depth != 10 && + config->input_color_depth != 12) + return -EINVAL; + + ret = of_property_read_string(np, "adi,input-colorspace", &str); + if (ret < 0) + return ret; + + if (!strcmp(str, "rgb")) + config->input_colorspace = HDMI_COLORSPACE_RGB; + else if (!strcmp(str, "yuv422")) + config->input_colorspace = HDMI_COLORSPACE_YUV422; + else if (!strcmp(str, "yuv444")) + config->input_colorspace = HDMI_COLORSPACE_YUV444; + else + return -EINVAL; + + ret = of_property_read_string(np, "adi,input-clock", &str); + if (ret < 0) + return ret; + + if (!strcmp(str, "1x")) + config->input_clock = ADV7511_INPUT_CLOCK_1X; + else if (!strcmp(str, "2x")) + config->input_clock = ADV7511_INPUT_CLOCK_2X; + else if (!strcmp(str, "ddr")) + config->input_clock = ADV7511_INPUT_CLOCK_DDR; + else + return -EINVAL; + + if (config->input_colorspace == HDMI_COLORSPACE_YUV422 || + config->input_clock != ADV7511_INPUT_CLOCK_1X) { + ret = of_property_read_u32(np, "adi,input-style", + &config->input_style); + if (ret) + return ret; + + if (config->input_style < 1 || config->input_style > 3) + return -EINVAL; + + ret = of_property_read_string(np, "adi,input-justification", + &str); + if (ret < 0) + return ret; + + if (!strcmp(str, "left")) + config->input_justification = + ADV7511_INPUT_JUSTIFICATION_LEFT; + else if (!strcmp(str, "evenly")) + config->input_justification = + ADV7511_INPUT_JUSTIFICATION_EVENLY; + else if (!strcmp(str, "right")) + config->input_justification = + ADV7511_INPUT_JUSTIFICATION_RIGHT; + else + return -EINVAL; + + } else { + config->input_style = 1; + config->input_justification = ADV7511_INPUT_JUSTIFICATION_LEFT; + } + + of_property_read_u32(np, "adi,clock-delay", &config->clock_delay); + if (config->clock_delay < -1200 || config->clock_delay > 1600) + return -EINVAL; + + config->embedded_sync = of_property_read_bool(np, "adi,embedded-sync"); + + /* Hardcode the sync pulse configurations for now. */ + config->sync_pulse = ADV7511_INPUT_SYNC_PULSE_NONE; + config->vsync_polarity = ADV7511_SYNC_POLARITY_PASSTHROUGH; + config->hsync_polarity = ADV7511_SYNC_POLARITY_PASSTHROUGH; + + return 0; +} + +static int adv7511_probe(struct i2c_client *i2c) +{ + struct adv7511_link_config link_config; + struct adv7511 *adv7511; + struct device *dev = &i2c->dev; + unsigned int val; + int ret; + + if (!dev->of_node) + return -EINVAL; + + adv7511 = devm_drm_bridge_alloc(dev, struct adv7511, bridge, + &adv7511_bridge_funcs); + if (IS_ERR(adv7511)) + return PTR_ERR(adv7511); + + adv7511->i2c_main = i2c; + adv7511->powered = false; + adv7511->status = connector_status_disconnected; + adv7511->info = i2c_get_match_data(i2c); + + memset(&link_config, 0, sizeof(link_config)); + + adv7511->bridge.next_bridge = of_drm_get_bridge_by_endpoint(dev->of_node, 1, -1); + if (IS_ERR(adv7511->bridge.next_bridge)) { + if (PTR_ERR(adv7511->bridge.next_bridge) == -ENODEV) + adv7511->bridge.next_bridge = NULL; + else + return PTR_ERR(adv7511->bridge.next_bridge); + } + + if (adv7511->info->link_config) + ret = adv7511_parse_dt(dev->of_node, &link_config); + else + ret = adv7533_parse_dt(dev->of_node, adv7511); + if (ret) + return ret; + + ret = adv7511_init_regulators(adv7511); + if (ret) { + dev_err_probe(dev, ret, "failed to init regulators\n"); + goto err_of_node_put; + } + + /* + * The power down GPIO is optional. If present, toggle it from active to + * inactive to wake up the encoder. + */ + adv7511->gpio_pd = devm_gpiod_get_optional(dev, "pd", GPIOD_OUT_HIGH); + if (IS_ERR(adv7511->gpio_pd)) { + ret = PTR_ERR(adv7511->gpio_pd); + goto uninit_regulators; + } + + if (adv7511->gpio_pd) { + usleep_range(5000, 6000); + gpiod_set_value_cansleep(adv7511->gpio_pd, 0); + } + + adv7511->regmap = devm_regmap_init_i2c(i2c, &adv7511_regmap_config); + if (IS_ERR(adv7511->regmap)) { + ret = PTR_ERR(adv7511->regmap); + goto uninit_regulators; + } + + ret = regmap_read(adv7511->regmap, ADV7511_REG_CHIP_REVISION, &val); + if (ret) + goto uninit_regulators; + dev_dbg(dev, "Rev. %d\n", val); + + if (adv7511->info->type == ADV7511) + ret = regmap_register_patch(adv7511->regmap, + adv7511_fixed_registers, + ARRAY_SIZE(adv7511_fixed_registers)); + else + ret = adv7533_patch_registers(adv7511); + if (ret) + goto uninit_regulators; + + adv7511_packet_disable(adv7511, 0xffff); + + adv7511->i2c_edid = i2c_new_ancillary_device(i2c, "edid", + ADV7511_EDID_I2C_ADDR_DEFAULT); + if (IS_ERR(adv7511->i2c_edid)) { + ret = PTR_ERR(adv7511->i2c_edid); + goto uninit_regulators; + } + + regmap_write(adv7511->regmap, ADV7511_REG_EDID_I2C_ADDR, + adv7511->i2c_edid->addr << 1); + + adv7511->i2c_packet = i2c_new_ancillary_device(i2c, "packet", + ADV7511_PACKET_I2C_ADDR_DEFAULT); + if (IS_ERR(adv7511->i2c_packet)) { + ret = PTR_ERR(adv7511->i2c_packet); + goto err_i2c_unregister_edid; + } + + adv7511->regmap_packet = devm_regmap_init_i2c(adv7511->i2c_packet, + &adv7511_packet_config); + if (IS_ERR(adv7511->regmap_packet)) { + ret = PTR_ERR(adv7511->regmap_packet); + goto err_i2c_unregister_packet; + } + + regmap_write(adv7511->regmap, ADV7511_REG_PACKET_I2C_ADDR, + adv7511->i2c_packet->addr << 1); + + ret = adv7511_init_cec_regmap(adv7511); + if (ret) + goto err_i2c_unregister_packet; + + INIT_WORK(&adv7511->hpd_work, adv7511_hpd_work); + + adv7511_power_off(adv7511); + + i2c_set_clientdata(i2c, adv7511); + + if (adv7511->info->link_config) + adv7511_set_link_config(adv7511, &link_config); + + regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL, + ADV7511_CEC_CTRL_POWER_DOWN); + + adv7511->bridge.ops = DRM_BRIDGE_OP_DETECT | + DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HDMI | + DRM_BRIDGE_OP_HDMI_SPD_INFOFRAME; + if (adv7511->i2c_main->irq) + adv7511->bridge.ops |= DRM_BRIDGE_OP_HPD; + + adv7511->bridge.vendor = "Analog"; + adv7511->bridge.product = adv7511->info->name; + +#ifdef CONFIG_DRM_I2C_ADV7511_AUDIO + adv7511->bridge.ops |= DRM_BRIDGE_OP_HDMI_AUDIO; + adv7511->bridge.hdmi_audio_dev = dev; + adv7511->bridge.hdmi_audio_max_i2s_playback_channels = 2; + adv7511->bridge.hdmi_audio_i2s_formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_3LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE), + adv7511->bridge.hdmi_audio_spdif_playback = 1; + adv7511->bridge.hdmi_audio_dai_port = 2; +#endif + +#ifdef CONFIG_DRM_I2C_ADV7511_CEC + adv7511->bridge.ops |= DRM_BRIDGE_OP_HDMI_CEC_ADAPTER; + adv7511->bridge.hdmi_cec_dev = dev; + adv7511->bridge.hdmi_cec_adapter_name = dev_name(dev); + adv7511->bridge.hdmi_cec_available_las = ADV7511_MAX_ADDRS; +#endif + + adv7511->bridge.of_node = dev->of_node; + adv7511->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + + drm_bridge_add(&adv7511->bridge); + + if (i2c->irq) { + init_waitqueue_head(&adv7511->wq); + + ret = devm_request_threaded_irq(dev, i2c->irq, NULL, + adv7511_irq_handler, + IRQF_ONESHOT | IRQF_SHARED, + dev_name(dev), + adv7511); + if (ret) + goto err_unregister_audio; + } + + if (adv7511->info->has_dsi) { + ret = adv7533_attach_dsi(adv7511); + if (ret) + goto err_unregister_audio; + } + + return 0; + +err_unregister_audio: + drm_bridge_remove(&adv7511->bridge); + i2c_unregister_device(adv7511->i2c_cec); + clk_disable_unprepare(adv7511->cec_clk); +err_i2c_unregister_packet: + i2c_unregister_device(adv7511->i2c_packet); +err_i2c_unregister_edid: + i2c_unregister_device(adv7511->i2c_edid); +uninit_regulators: + adv7511_uninit_regulators(adv7511); +err_of_node_put: + of_node_put(adv7511->host_node); + + return ret; +} + +static void adv7511_remove(struct i2c_client *i2c) +{ + struct adv7511 *adv7511 = i2c_get_clientdata(i2c); + + of_node_put(adv7511->host_node); + + adv7511_uninit_regulators(adv7511); + + drm_bridge_remove(&adv7511->bridge); + + i2c_unregister_device(adv7511->i2c_cec); + clk_disable_unprepare(adv7511->cec_clk); + + i2c_unregister_device(adv7511->i2c_packet); + i2c_unregister_device(adv7511->i2c_edid); +} + +static const struct adv7511_chip_info adv7511_chip_info = { + .type = ADV7511, + .name = "ADV7511", + .max_mode_clock_khz = 165000, + .supply_names = adv7511_supply_names, + .num_supplies = ARRAY_SIZE(adv7511_supply_names), + .link_config = true, +}; + +static const struct adv7511_chip_info adv7533_chip_info = { + .type = ADV7533, + .name = "ADV7533", + .max_mode_clock_khz = 80000, + .max_lane_freq_khz = 800000, + .supply_names = adv7533_supply_names, + .num_supplies = ARRAY_SIZE(adv7533_supply_names), + .reg_cec_offset = ADV7533_REG_CEC_OFFSET, + .has_dsi = true, +}; + +static const struct adv7511_chip_info adv7535_chip_info = { + .type = ADV7535, + .name = "ADV7535", + .max_mode_clock_khz = 148500, + .max_lane_freq_khz = 891000, + .supply_names = adv7533_supply_names, + .num_supplies = ARRAY_SIZE(adv7533_supply_names), + .reg_cec_offset = ADV7533_REG_CEC_OFFSET, + .has_dsi = true, + .hpd_override_enable = true, +}; + +static const struct i2c_device_id adv7511_i2c_ids[] = { + { .name = "adv7511", .driver_data = (kernel_ulong_t)&adv7511_chip_info }, + { .name = "adv7511w", .driver_data = (kernel_ulong_t)&adv7511_chip_info }, + { .name = "adv7513", .driver_data = (kernel_ulong_t)&adv7511_chip_info }, + { .name = "adv7533", .driver_data = (kernel_ulong_t)&adv7533_chip_info }, + { .name = "adv7535", .driver_data = (kernel_ulong_t)&adv7535_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adv7511_i2c_ids); + +static const struct of_device_id adv7511_of_ids[] = { + { .compatible = "adi,adv7511", .data = &adv7511_chip_info }, + { .compatible = "adi,adv7511w", .data = &adv7511_chip_info }, + { .compatible = "adi,adv7513", .data = &adv7511_chip_info }, + { .compatible = "adi,adv7533", .data = &adv7533_chip_info }, + { .compatible = "adi,adv7535", .data = &adv7535_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, adv7511_of_ids); + +static struct mipi_dsi_driver adv7533_dsi_driver = { + .driver.name = "adv7533", +}; + +static struct i2c_driver adv7511_driver = { + .driver = { + .name = "adv7511", + .of_match_table = adv7511_of_ids, + }, + .id_table = adv7511_i2c_ids, + .probe = adv7511_probe, + .remove = adv7511_remove, +}; + +static int __init adv7511_init(void) +{ + int ret; + + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) { + ret = mipi_dsi_driver_register(&adv7533_dsi_driver); + if (ret) + return ret; + } + + ret = i2c_add_driver(&adv7511_driver); + if (ret) { + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) + mipi_dsi_driver_unregister(&adv7533_dsi_driver); + } + + return ret; +} +module_init(adv7511_init); + +static void __exit adv7511_exit(void) +{ + i2c_del_driver(&adv7511_driver); + + if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) + mipi_dsi_driver_unregister(&adv7533_dsi_driver); +} +module_exit(adv7511_exit); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/bridge/adv7511/adv7533.c b/drivers/gpu/drm/bridge/adv7511/adv7533.c new file mode 100644 index 000000000..188c1093a --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/adv7533.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + */ + +#include + +#include "adv7511.h" + +static const struct reg_sequence adv7533_fixed_registers[] = { + { 0x16, 0x20 }, + { 0x9a, 0xe0 }, + { 0xba, 0x70 }, + { 0xde, 0x82 }, + { 0xe4, 0x40 }, + { 0xe5, 0x80 }, +}; + +static const struct reg_sequence adv7533_cec_fixed_registers[] = { + { 0x15, 0xd0 }, + { 0x17, 0xd0 }, + { 0x24, 0x20 }, + { 0x57, 0x11 }, + { 0x05, 0xc8 }, +}; + +void adv7533_dsi_config_timing_gen(struct adv7511 *adv) +{ + struct mipi_dsi_device *dsi = adv->dsi; + struct drm_display_mode *mode = &adv->curr_mode; + unsigned int hsw, hfp, hbp, vsw, vfp, vbp; + static const u8 clock_div_by_lanes[] = { 6, 4, 3 }; /* 2, 3, 4 lanes */ + + hsw = mode->hsync_end - mode->hsync_start; + hfp = mode->hsync_start - mode->hdisplay; + hbp = mode->htotal - mode->hsync_end; + vsw = mode->vsync_end - mode->vsync_start; + vfp = mode->vsync_start - mode->vdisplay; + vbp = mode->vtotal - mode->vsync_end; + + /* set pixel clock divider mode */ + regmap_write(adv->regmap_cec, 0x16, + clock_div_by_lanes[dsi->lanes - 2] << 3); + + /* horizontal porch params */ + regmap_write(adv->regmap_cec, 0x28, mode->htotal >> 4); + regmap_write(adv->regmap_cec, 0x29, (mode->htotal << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x2a, hsw >> 4); + regmap_write(adv->regmap_cec, 0x2b, (hsw << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x2c, hfp >> 4); + regmap_write(adv->regmap_cec, 0x2d, (hfp << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x2e, hbp >> 4); + regmap_write(adv->regmap_cec, 0x2f, (hbp << 4) & 0xff); + + /* vertical porch params */ + regmap_write(adv->regmap_cec, 0x30, mode->vtotal >> 4); + regmap_write(adv->regmap_cec, 0x31, (mode->vtotal << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x32, vsw >> 4); + regmap_write(adv->regmap_cec, 0x33, (vsw << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x34, vfp >> 4); + regmap_write(adv->regmap_cec, 0x35, (vfp << 4) & 0xff); + regmap_write(adv->regmap_cec, 0x36, vbp >> 4); + regmap_write(adv->regmap_cec, 0x37, (vbp << 4) & 0xff); +} + +void adv7533_dsi_power_on(struct adv7511 *adv) +{ + struct mipi_dsi_device *dsi = adv->dsi; + + /* set number of dsi lanes */ + regmap_write(adv->regmap_cec, 0x1c, dsi->lanes << 4); + + if (adv->use_timing_gen) { + /* reset internal timing generator */ + regmap_write(adv->regmap_cec, 0x27, 0xcb); + regmap_write(adv->regmap_cec, 0x27, 0x8b); + regmap_write(adv->regmap_cec, 0x27, 0xcb); + } else { + /* disable internal timing generator */ + regmap_write(adv->regmap_cec, 0x27, 0x0b); + } + + /* enable hdmi */ + regmap_write(adv->regmap_cec, 0x03, 0x89); + /* disable test mode */ + regmap_write(adv->regmap_cec, 0x55, 0x00); + + regmap_register_patch(adv->regmap_cec, adv7533_cec_fixed_registers, + ARRAY_SIZE(adv7533_cec_fixed_registers)); +} + +void adv7533_dsi_power_off(struct adv7511 *adv) +{ + /* disable hdmi */ + regmap_write(adv->regmap_cec, 0x03, 0x0b); + /* disable internal timing generator */ + regmap_write(adv->regmap_cec, 0x27, 0x0b); +} + +enum drm_mode_status adv7533_mode_valid(struct adv7511 *adv, + const struct drm_display_mode *mode) +{ + struct mipi_dsi_device *dsi = adv->dsi; + u8 bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); + + /* Check max clock for each lane */ + if (mode->clock * bpp > adv->info->max_lane_freq_khz * adv->num_dsi_lanes) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +int adv7533_patch_registers(struct adv7511 *adv) +{ + return regmap_register_patch(adv->regmap, + adv7533_fixed_registers, + ARRAY_SIZE(adv7533_fixed_registers)); +} + +int adv7533_patch_cec_registers(struct adv7511 *adv) +{ + return regmap_register_patch(adv->regmap_cec, + adv7533_cec_fixed_registers, + ARRAY_SIZE(adv7533_cec_fixed_registers)); +} + +int adv7533_attach_dsi(struct adv7511 *adv) +{ + struct device *dev = &adv->i2c_main->dev; + struct mipi_dsi_host *host; + struct mipi_dsi_device *dsi; + int ret = 0; + const struct mipi_dsi_device_info info = { .type = "adv7533", + .channel = 0, + .node = NULL, + }; + + host = of_find_mipi_dsi_host_by_node(adv->host_node); + if (!host) + return dev_err_probe(dev, -EPROBE_DEFER, + "failed to find dsi host\n"); + + dsi = devm_mipi_dsi_device_register_full(dev, host, &info); + if (IS_ERR(dsi)) + return dev_err_probe(dev, PTR_ERR(dsi), + "failed to create dsi device\n"); + + adv->dsi = dsi; + + dsi->lanes = adv->num_dsi_lanes; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE; + + ret = devm_mipi_dsi_attach(dev, dsi); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to attach dsi to host\n"); + + return 0; +} + +int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv) +{ + u32 num_lanes; + + of_property_read_u32(np, "adi,dsi-lanes", &num_lanes); + + if (num_lanes < 2 || num_lanes > 4) + return -EINVAL; + + adv->num_dsi_lanes = num_lanes; + + adv->host_node = of_graph_get_remote_node(np, 0, 0); + if (!adv->host_node) + return -ENODEV; + + adv->use_timing_gen = !of_property_read_bool(np, + "adi,disable-timing-generator"); + + /* TODO: Check if these need to be parsed by DT or not */ + adv->rgb = true; + adv->embedded_sync = false; + + return 0; +} diff --git a/drivers/gpu/drm/bridge/analogix/Kconfig b/drivers/gpu/drm/bridge/analogix/Kconfig new file mode 100644 index 000000000..57bb2daa5 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/Kconfig @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_ANALOGIX_ANX6345 + tristate "Analogix ANX6345 bridge" + depends on OF + select DRM_ANALOGIX_DP + select DRM_DISPLAY_DP_HELPER + select DRM_DISPLAY_HELPER + select DRM_KMS_HELPER + select REGMAP_I2C + help + ANX6345 is an ultra-low power Full-HD DisplayPort/eDP + transmitter designed for portable devices. The + ANX6345 transforms the LVTTL RGB output of an + application processor to eDP or DisplayPort. + +config DRM_ANALOGIX_ANX78XX + tristate "Analogix ANX78XX bridge" + depends on OF + select DRM_ANALOGIX_DP + select DRM_DISPLAY_DP_HELPER + select DRM_DISPLAY_HELPER + select DRM_KMS_HELPER + select REGMAP_I2C + help + ANX78XX is an ultra-low power Full-HD SlimPort transmitter + designed for portable devices. The ANX78XX transforms + the HDMI output of an application processor to MyDP + or DisplayPort. + +config DRM_ANALOGIX_DP + tristate + depends on DRM + depends on OF + select DRM_DISPLAY_DP_AUX_BUS + +config DRM_ANALOGIX_ANX7625 + tristate "Analogix Anx7625 MIPI to DP interface support" + depends on DRM + depends on OF + depends on TYPEC + depends on USB_ROLE_SWITCH + select DRM_DISPLAY_DP_HELPER + select DRM_DISPLAY_HDCP_HELPER + select DRM_DISPLAY_HELPER + select DRM_DISPLAY_DP_AUX_BUS + select DRM_MIPI_DSI + help + ANX7625 is an ultra-low power 4K mobile HD transmitter + designed for portable devices. It converts MIPI/DPI to + DisplayPort1.3 4K. diff --git a/drivers/gpu/drm/bridge/analogix/Makefile b/drivers/gpu/drm/bridge/analogix/Makefile new file mode 100644 index 000000000..44da392bb --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +analogix_dp-objs := analogix_dp_core.o analogix_dp_reg.o analogix-i2c-dptx.o +obj-$(CONFIG_DRM_ANALOGIX_ANX6345) += analogix-anx6345.o +obj-$(CONFIG_DRM_ANALOGIX_ANX7625) += anx7625.o +obj-$(CONFIG_DRM_ANALOGIX_ANX78XX) += analogix-anx78xx.o +obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix_dp.o diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c new file mode 100644 index 000000000..407834a03 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx6345.c @@ -0,0 +1,810 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2016, Analogix Semiconductor. + * Copyright(c) 2017, Icenowy Zheng + * + * Based on anx7808 driver obtained from chromeos with copyright: + * Copyright(c) 2013, Google Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "analogix-i2c-dptx.h" +#include "analogix-i2c-txcommon.h" + +#define POLL_DELAY 50000 /* us */ +#define POLL_TIMEOUT 5000000 /* us */ + +#define I2C_IDX_DPTX 0 +#define I2C_IDX_TXCOM 1 + +static const u8 anx6345_i2c_addresses[] = { + [I2C_IDX_DPTX] = 0x70, + [I2C_IDX_TXCOM] = 0x72, +}; +#define I2C_NUM_ADDRESSES ARRAY_SIZE(anx6345_i2c_addresses) + +struct anx6345 { + struct drm_dp_aux aux; + struct drm_bridge bridge; + struct i2c_client *client; + const struct drm_edid *drm_edid; + struct drm_connector connector; + struct drm_panel *panel; + struct regulator *dvdd12; + struct regulator *dvdd25; + struct gpio_desc *gpiod_reset; + struct mutex lock; /* protect EDID access */ + + /* I2C Slave addresses of ANX6345 are mapped as DPTX and SYS */ + struct i2c_client *i2c_clients[I2C_NUM_ADDRESSES]; + struct regmap *map[I2C_NUM_ADDRESSES]; + + u16 chipid; + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + + bool powered; +}; + +static inline struct anx6345 *connector_to_anx6345(struct drm_connector *c) +{ + return container_of(c, struct anx6345, connector); +} + +static inline struct anx6345 *bridge_to_anx6345(struct drm_bridge *bridge) +{ + return container_of(bridge, struct anx6345, bridge); +} + +static int anx6345_set_bits(struct regmap *map, u8 reg, u8 mask) +{ + return regmap_update_bits(map, reg, mask, mask); +} + +static int anx6345_clear_bits(struct regmap *map, u8 reg, u8 mask) +{ + return regmap_update_bits(map, reg, mask, 0); +} + +static ssize_t anx6345_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct anx6345 *anx6345 = container_of(aux, struct anx6345, aux); + + return anx_dp_aux_transfer(anx6345->map[I2C_IDX_DPTX], msg); +} + +static int anx6345_dp_link_training(struct anx6345 *anx6345) +{ + unsigned int value; + u8 dp_bw, dpcd[2]; + int err; + + err = anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM], + SP_POWERDOWN_CTRL_REG, + SP_TOTAL_PD); + if (err) + return err; + + err = drm_dp_dpcd_readb(&anx6345->aux, DP_MAX_LINK_RATE, &dp_bw); + if (err < 0) + return err; + + switch (dp_bw) { + case DP_LINK_BW_1_62: + case DP_LINK_BW_2_7: + break; + + default: + DRM_DEBUG_KMS("DP bandwidth (%#02x) not supported\n", dp_bw); + return -EINVAL; + } + + err = anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM], SP_VID_CTRL1_REG, + SP_VIDEO_MUTE); + if (err) + return err; + + err = anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM], + SP_VID_CTRL1_REG, SP_VIDEO_EN); + if (err) + return err; + + /* Get DPCD info */ + err = drm_dp_dpcd_read(&anx6345->aux, DP_DPCD_REV, + &anx6345->dpcd, DP_RECEIVER_CAP_SIZE); + if (err < 0) { + DRM_ERROR("Failed to read DPCD: %d\n", err); + return err; + } + + /* Clear channel x SERDES power down */ + err = anx6345_clear_bits(anx6345->map[I2C_IDX_DPTX], + SP_DP_ANALOG_POWER_DOWN_REG, SP_CH0_PD); + if (err) + return err; + + drm_dp_link_power_up(&anx6345->aux, anx6345->dpcd[DP_DPCD_REV]); + + /* Possibly enable downspread on the sink */ + err = regmap_write(anx6345->map[I2C_IDX_DPTX], + SP_DP_DOWNSPREAD_CTRL1_REG, 0); + if (err) + return err; + + if (anx6345->dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5) { + DRM_DEBUG("Enable downspread on the sink\n"); + /* 4000PPM */ + err = regmap_write(anx6345->map[I2C_IDX_DPTX], + SP_DP_DOWNSPREAD_CTRL1_REG, 8); + if (err) + return err; + + err = drm_dp_dpcd_writeb(&anx6345->aux, DP_DOWNSPREAD_CTRL, + DP_SPREAD_AMP_0_5); + if (err < 0) + return err; + } else { + err = drm_dp_dpcd_writeb(&anx6345->aux, DP_DOWNSPREAD_CTRL, 0); + if (err < 0) + return err; + } + + /* Set the lane count and the link rate on the sink */ + if (drm_dp_enhanced_frame_cap(anx6345->dpcd)) + err = anx6345_set_bits(anx6345->map[I2C_IDX_DPTX], + SP_DP_SYSTEM_CTRL_BASE + 4, + SP_ENHANCED_MODE); + else + err = anx6345_clear_bits(anx6345->map[I2C_IDX_DPTX], + SP_DP_SYSTEM_CTRL_BASE + 4, + SP_ENHANCED_MODE); + if (err) + return err; + + dpcd[0] = dp_bw; + err = regmap_write(anx6345->map[I2C_IDX_DPTX], + SP_DP_MAIN_LINK_BW_SET_REG, dpcd[0]); + if (err) + return err; + + dpcd[1] = drm_dp_max_lane_count(anx6345->dpcd); + + err = regmap_write(anx6345->map[I2C_IDX_DPTX], + SP_DP_LANE_COUNT_SET_REG, dpcd[1]); + if (err) + return err; + + if (drm_dp_enhanced_frame_cap(anx6345->dpcd)) + dpcd[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + + err = drm_dp_dpcd_write(&anx6345->aux, DP_LINK_BW_SET, dpcd, + sizeof(dpcd)); + + if (err < 0) { + DRM_ERROR("Failed to configure link: %d\n", err); + return err; + } + + /* Start training on the source */ + err = regmap_write(anx6345->map[I2C_IDX_DPTX], SP_DP_LT_CTRL_REG, + SP_LT_EN); + if (err) + return err; + + return regmap_read_poll_timeout(anx6345->map[I2C_IDX_DPTX], + SP_DP_LT_CTRL_REG, + value, !(value & SP_DP_LT_INPROGRESS), + POLL_DELAY, POLL_TIMEOUT); +} + +static int anx6345_tx_initialization(struct anx6345 *anx6345) +{ + int err, i; + + /* FIXME: colordepth is hardcoded for now */ + err = regmap_write(anx6345->map[I2C_IDX_TXCOM], SP_VID_CTRL2_REG, + SP_IN_BPC_6BIT << SP_IN_BPC_SHIFT); + if (err) + return err; + + err = regmap_write(anx6345->map[I2C_IDX_DPTX], SP_DP_PLL_CTRL_REG, 0); + if (err) + return err; + + err = regmap_write(anx6345->map[I2C_IDX_TXCOM], + SP_ANALOG_DEBUG1_REG, 0); + if (err) + return err; + + err = regmap_write(anx6345->map[I2C_IDX_DPTX], + SP_DP_LINK_DEBUG_CTRL_REG, + SP_NEW_PRBS7 | SP_M_VID_DEBUG); + if (err) + return err; + + err = regmap_write(anx6345->map[I2C_IDX_DPTX], + SP_DP_ANALOG_POWER_DOWN_REG, 0); + if (err) + return err; + + /* Force HPD */ + err = anx6345_set_bits(anx6345->map[I2C_IDX_DPTX], + SP_DP_SYSTEM_CTRL_BASE + 3, + SP_HPD_FORCE | SP_HPD_CTRL); + if (err) + return err; + + for (i = 0; i < 4; i++) { + /* 4 lanes */ + err = regmap_write(anx6345->map[I2C_IDX_DPTX], + SP_DP_LANE0_LT_CTRL_REG + i, 0); + if (err) + return err; + } + + /* Reset AUX */ + err = anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM], + SP_RESET_CTRL2_REG, SP_AUX_RST); + if (err) + return err; + + return anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM], + SP_RESET_CTRL2_REG, SP_AUX_RST); +} + +static void anx6345_poweron(struct anx6345 *anx6345) +{ + int err; + + /* Ensure reset is asserted before starting power on sequence */ + gpiod_set_value_cansleep(anx6345->gpiod_reset, 1); + usleep_range(1000, 2000); + + err = regulator_enable(anx6345->dvdd12); + if (err) { + DRM_ERROR("Failed to enable dvdd12 regulator: %d\n", + err); + return; + } + + /* T1 - delay between VDD12 and VDD25 should be 0-2ms */ + usleep_range(1000, 2000); + + err = regulator_enable(anx6345->dvdd25); + if (err) { + DRM_ERROR("Failed to enable dvdd25 regulator: %d\n", + err); + return; + } + + /* T2 - delay between RESETN and all power rail stable, + * should be 2-5ms + */ + usleep_range(2000, 5000); + + gpiod_set_value_cansleep(anx6345->gpiod_reset, 0); + + /* Power on registers module */ + anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM], SP_POWERDOWN_CTRL_REG, + SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | SP_LINK_PD); + anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM], SP_POWERDOWN_CTRL_REG, + SP_REGISTER_PD | SP_TOTAL_PD); + + if (anx6345->panel) + drm_panel_prepare(anx6345->panel); + + anx6345->powered = true; +} + +static void anx6345_poweroff(struct anx6345 *anx6345) +{ + int err; + + gpiod_set_value_cansleep(anx6345->gpiod_reset, 1); + usleep_range(1000, 2000); + + if (anx6345->panel) + drm_panel_unprepare(anx6345->panel); + + err = regulator_disable(anx6345->dvdd25); + if (err) { + DRM_ERROR("Failed to disable dvdd25 regulator: %d\n", + err); + return; + } + + usleep_range(5000, 10000); + + err = regulator_disable(anx6345->dvdd12); + if (err) { + DRM_ERROR("Failed to disable dvdd12 regulator: %d\n", + err); + return; + } + + usleep_range(1000, 2000); + + anx6345->powered = false; +} + +static int anx6345_start(struct anx6345 *anx6345) +{ + int err; + + if (!anx6345->powered) + anx6345_poweron(anx6345); + + /* Power on needed modules */ + err = anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM], + SP_POWERDOWN_CTRL_REG, + SP_VIDEO_PD | SP_LINK_PD); + + err = anx6345_tx_initialization(anx6345); + if (err) { + DRM_ERROR("Failed eDP transmitter initialization: %d\n", err); + anx6345_poweroff(anx6345); + return err; + } + + err = anx6345_dp_link_training(anx6345); + if (err) { + DRM_ERROR("Failed link training: %d\n", err); + anx6345_poweroff(anx6345); + return err; + } + + /* + * This delay seems to help keep the hardware in a good state. Without + * it, there are times where it fails silently. + */ + usleep_range(10000, 15000); + + return 0; +} + +static int anx6345_config_dp_output(struct anx6345 *anx6345) +{ + int err; + + err = anx6345_clear_bits(anx6345->map[I2C_IDX_TXCOM], SP_VID_CTRL1_REG, + SP_VIDEO_MUTE); + if (err) + return err; + + /* Enable DP output */ + err = anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM], SP_VID_CTRL1_REG, + SP_VIDEO_EN); + if (err) + return err; + + /* Force stream valid */ + return anx6345_set_bits(anx6345->map[I2C_IDX_DPTX], + SP_DP_SYSTEM_CTRL_BASE + 3, + SP_STRM_FORCE | SP_STRM_CTRL); +} + +static int anx6345_get_downstream_info(struct anx6345 *anx6345) +{ + u8 value; + int err; + + err = drm_dp_dpcd_readb(&anx6345->aux, DP_SINK_COUNT, &value); + if (err < 0) { + DRM_ERROR("Get sink count failed %d\n", err); + return err; + } + + if (!DP_GET_SINK_COUNT(value)) { + DRM_ERROR("Downstream disconnected\n"); + return -EIO; + } + + return 0; +} + +static int anx6345_get_modes(struct drm_connector *connector) +{ + struct anx6345 *anx6345 = connector_to_anx6345(connector); + int err, num_modes = 0; + bool power_off = false; + + mutex_lock(&anx6345->lock); + + if (!anx6345->drm_edid) { + if (!anx6345->powered) { + anx6345_poweron(anx6345); + power_off = true; + } + + err = anx6345_get_downstream_info(anx6345); + if (err) { + DRM_ERROR("Failed to get downstream info: %d\n", err); + goto unlock; + } + + anx6345->drm_edid = drm_edid_read_ddc(connector, &anx6345->aux.ddc); + if (!anx6345->drm_edid) + DRM_ERROR("Failed to read EDID from panel\n"); + + err = drm_edid_connector_update(connector, anx6345->drm_edid); + if (err) { + DRM_ERROR("Failed to update EDID property: %d\n", err); + goto unlock; + } + } + + num_modes += drm_edid_connector_add_modes(connector); + + /* Driver currently supports only 6bpc */ + connector->display_info.bpc = 6; + +unlock: + if (power_off) + anx6345_poweroff(anx6345); + + mutex_unlock(&anx6345->lock); + + if (!num_modes && anx6345->panel) + num_modes += drm_panel_get_modes(anx6345->panel, connector); + + return num_modes; +} + +static const struct drm_connector_helper_funcs anx6345_connector_helper_funcs = { + .get_modes = anx6345_get_modes, +}; + +static void +anx6345_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +static const struct drm_connector_funcs anx6345_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = anx6345_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int anx6345_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct anx6345 *anx6345 = bridge_to_anx6345(bridge); + int err; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { + DRM_ERROR("Fix bridge driver to make connector optional!"); + return -EINVAL; + } + + /* Register aux channel */ + anx6345->aux.name = "DP-AUX"; + anx6345->aux.dev = &anx6345->client->dev; + anx6345->aux.drm_dev = bridge->dev; + anx6345->aux.transfer = anx6345_aux_transfer; + + err = drm_dp_aux_register(&anx6345->aux); + if (err < 0) { + DRM_ERROR("Failed to register aux channel: %d\n", err); + return err; + } + + err = drm_connector_init(bridge->dev, &anx6345->connector, + &anx6345_connector_funcs, + DRM_MODE_CONNECTOR_eDP); + if (err) { + DRM_ERROR("Failed to initialize connector: %d\n", err); + goto aux_unregister; + } + + drm_connector_helper_add(&anx6345->connector, + &anx6345_connector_helper_funcs); + + anx6345->connector.polled = DRM_CONNECTOR_POLL_HPD; + + err = drm_connector_attach_encoder(&anx6345->connector, + encoder); + if (err) { + DRM_ERROR("Failed to link up connector to encoder: %d\n", err); + goto connector_cleanup; + } + + err = drm_connector_register(&anx6345->connector); + if (err) { + DRM_ERROR("Failed to register connector: %d\n", err); + goto connector_cleanup; + } + + return 0; +connector_cleanup: + drm_connector_cleanup(&anx6345->connector); +aux_unregister: + drm_dp_aux_unregister(&anx6345->aux); + return err; +} + +static void anx6345_bridge_detach(struct drm_bridge *bridge) +{ + drm_dp_aux_unregister(&bridge_to_anx6345(bridge)->aux); +} + +static enum drm_mode_status +anx6345_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_NO_INTERLACE; + + /* Max 1200p at 5.4 Ghz, one lane */ + if (mode->clock > 154000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static void anx6345_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) +{ + struct anx6345 *anx6345 = bridge_to_anx6345(bridge); + + /* Power off all modules except configuration registers access */ + anx6345_set_bits(anx6345->map[I2C_IDX_TXCOM], SP_POWERDOWN_CTRL_REG, + SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | SP_LINK_PD); + if (anx6345->panel) + drm_panel_disable(anx6345->panel); + + if (anx6345->powered) + anx6345_poweroff(anx6345); +} + +static void anx6345_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) +{ + struct anx6345 *anx6345 = bridge_to_anx6345(bridge); + int err; + + if (anx6345->panel) + drm_panel_enable(anx6345->panel); + + err = anx6345_start(anx6345); + if (err) { + DRM_ERROR("Failed to initialize: %d\n", err); + return; + } + + err = anx6345_config_dp_output(anx6345); + if (err) + DRM_ERROR("Failed to enable DP output: %d\n", err); +} + +static const struct drm_bridge_funcs anx6345_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .attach = anx6345_bridge_attach, + .detach = anx6345_bridge_detach, + .mode_valid = anx6345_bridge_mode_valid, + .atomic_disable = anx6345_bridge_disable, + .atomic_enable = anx6345_bridge_enable, +}; + +static void unregister_i2c_dummy_clients(struct anx6345 *anx6345) +{ + unsigned int i; + + for (i = 1; i < ARRAY_SIZE(anx6345->i2c_clients); i++) + if (anx6345->i2c_clients[i] && + anx6345->i2c_clients[i]->addr != anx6345->client->addr) + i2c_unregister_device(anx6345->i2c_clients[i]); +} + +static const struct regmap_config anx6345_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .cache_type = REGCACHE_NONE, +}; + +static const u16 anx6345_chipid_list[] = { + 0x6345, +}; + +static bool anx6345_get_chip_id(struct anx6345 *anx6345) +{ + unsigned int i, idl, idh, version; + + if (regmap_read(anx6345->map[I2C_IDX_TXCOM], SP_DEVICE_IDL_REG, &idl)) + return false; + + if (regmap_read(anx6345->map[I2C_IDX_TXCOM], SP_DEVICE_IDH_REG, &idh)) + return false; + + anx6345->chipid = (u8)idl | ((u8)idh << 8); + + if (regmap_read(anx6345->map[I2C_IDX_TXCOM], SP_DEVICE_VERSION_REG, + &version)) + return false; + + for (i = 0; i < ARRAY_SIZE(anx6345_chipid_list); i++) { + if (anx6345->chipid == anx6345_chipid_list[i]) { + DRM_INFO("Found ANX%x (ver. %d) eDP Transmitter\n", + anx6345->chipid, version); + return true; + } + } + + DRM_ERROR("ANX%x (ver. %d) not supported by this driver\n", + anx6345->chipid, version); + + return false; +} + +static void anx6345_panel_put_action(void *data) +{ + drm_panel_put(data); +} + +static int anx6345_i2c_probe(struct i2c_client *client) +{ + struct anx6345 *anx6345; + struct device *dev; + int i, err; + + anx6345 = devm_drm_bridge_alloc(&client->dev, struct anx6345, bridge, + &anx6345_bridge_funcs); + if (IS_ERR(anx6345)) + return PTR_ERR(anx6345); + + mutex_init(&anx6345->lock); + + anx6345->bridge.of_node = client->dev.of_node; + + anx6345->client = client; + i2c_set_clientdata(client, anx6345); + + dev = &anx6345->client->dev; + + err = drm_of_find_panel_or_bridge(client->dev.of_node, 1, 0, + &anx6345->panel, NULL); + if (err == -EPROBE_DEFER) + return err; + + if (err) + DRM_DEBUG("No panel found\n"); + + if (anx6345->panel) { + err = devm_add_action_or_reset(dev, anx6345_panel_put_action, + anx6345->panel); + if (err) + return err; + } + + /* 1.2V digital core power regulator */ + anx6345->dvdd12 = devm_regulator_get(dev, "dvdd12"); + if (IS_ERR(anx6345->dvdd12)) { + if (PTR_ERR(anx6345->dvdd12) != -EPROBE_DEFER) + DRM_ERROR("Failed to get dvdd12 supply (%ld)\n", + PTR_ERR(anx6345->dvdd12)); + return PTR_ERR(anx6345->dvdd12); + } + + /* 2.5V digital core power regulator */ + anx6345->dvdd25 = devm_regulator_get(dev, "dvdd25"); + if (IS_ERR(anx6345->dvdd25)) { + if (PTR_ERR(anx6345->dvdd25) != -EPROBE_DEFER) + DRM_ERROR("Failed to get dvdd25 supply (%ld)\n", + PTR_ERR(anx6345->dvdd25)); + return PTR_ERR(anx6345->dvdd25); + } + + /* GPIO for chip reset */ + anx6345->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(anx6345->gpiod_reset)) { + DRM_ERROR("Reset gpio not found\n"); + return PTR_ERR(anx6345->gpiod_reset); + } + + /* Map slave addresses of ANX6345 */ + for (i = 0; i < I2C_NUM_ADDRESSES; i++) { + if (anx6345_i2c_addresses[i] >> 1 != client->addr) + anx6345->i2c_clients[i] = i2c_new_dummy_device(client->adapter, + anx6345_i2c_addresses[i] >> 1); + else + anx6345->i2c_clients[i] = client; + + if (IS_ERR(anx6345->i2c_clients[i])) { + err = PTR_ERR(anx6345->i2c_clients[i]); + DRM_ERROR("Failed to reserve I2C bus %02x\n", + anx6345_i2c_addresses[i]); + goto err_unregister_i2c; + } + + anx6345->map[i] = devm_regmap_init_i2c(anx6345->i2c_clients[i], + &anx6345_regmap_config); + if (IS_ERR(anx6345->map[i])) { + err = PTR_ERR(anx6345->map[i]); + DRM_ERROR("Failed regmap initialization %02x\n", + anx6345_i2c_addresses[i]); + goto err_unregister_i2c; + } + } + + /* Look for supported chip ID */ + anx6345_poweron(anx6345); + if (anx6345_get_chip_id(anx6345)) { + drm_bridge_add(&anx6345->bridge); + + return 0; + } else { + anx6345_poweroff(anx6345); + err = -ENODEV; + } + +err_unregister_i2c: + unregister_i2c_dummy_clients(anx6345); + return err; +} + +static void anx6345_i2c_remove(struct i2c_client *client) +{ + struct anx6345 *anx6345 = i2c_get_clientdata(client); + + drm_bridge_remove(&anx6345->bridge); + + unregister_i2c_dummy_clients(anx6345); + + drm_edid_free(anx6345->drm_edid); + + mutex_destroy(&anx6345->lock); +} + +static const struct i2c_device_id anx6345_id[] = { + { .name = "anx6345" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, anx6345_id); + +static const struct of_device_id anx6345_match_table[] = { + { .compatible = "analogix,anx6345", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, anx6345_match_table); + +static struct i2c_driver anx6345_driver = { + .driver = { + .name = "anx6345", + .of_match_table = anx6345_match_table, + }, + .probe = anx6345_i2c_probe, + .remove = anx6345_i2c_remove, + .id_table = anx6345_id, +}; +module_i2c_driver(anx6345_driver); + +MODULE_DESCRIPTION("ANX6345 eDP Transmitter driver"); +MODULE_AUTHOR("Icenowy Zheng "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c new file mode 100644 index 000000000..32186efa6 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.c @@ -0,0 +1,1364 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright(c) 2016, Analogix Semiconductor. + * + * Based on anx7808 driver obtained from chromeos with copyright: + * Copyright(c) 2013, Google Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "analogix-anx78xx.h" + +#define I2C_NUM_ADDRESSES 5 +#define I2C_IDX_TX_P0 0 +#define I2C_IDX_TX_P1 1 +#define I2C_IDX_TX_P2 2 +#define I2C_IDX_RX_P0 3 +#define I2C_IDX_RX_P1 4 + +#define XTAL_CLK 270 /* 27M */ + +static const u8 anx7808_i2c_addresses[] = { + [I2C_IDX_TX_P0] = 0x78, + [I2C_IDX_TX_P1] = 0x7a, + [I2C_IDX_TX_P2] = 0x72, + [I2C_IDX_RX_P0] = 0x7e, + [I2C_IDX_RX_P1] = 0x80, +}; + +static const u8 anx781x_i2c_addresses[] = { + [I2C_IDX_TX_P0] = 0x70, + [I2C_IDX_TX_P1] = 0x7a, + [I2C_IDX_TX_P2] = 0x72, + [I2C_IDX_RX_P0] = 0x7e, + [I2C_IDX_RX_P1] = 0x80, +}; + +struct anx78xx_platform_data { + struct regulator *dvdd10; + struct gpio_desc *gpiod_hpd; + struct gpio_desc *gpiod_pd; + struct gpio_desc *gpiod_reset; + + int hpd_irq; + int intp_irq; +}; + +struct anx78xx { + struct drm_dp_aux aux; + struct drm_bridge bridge; + struct i2c_client *client; + const struct drm_edid *drm_edid; + struct drm_connector connector; + struct anx78xx_platform_data pdata; + struct mutex lock; + + /* + * I2C Slave addresses of ANX7814 are mapped as TX_P0, TX_P1, TX_P2, + * RX_P0 and RX_P1. + */ + struct i2c_client *i2c_dummy[I2C_NUM_ADDRESSES]; + struct regmap *map[I2C_NUM_ADDRESSES]; + + u16 chipid; + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + + bool powered; +}; + +static inline struct anx78xx *connector_to_anx78xx(struct drm_connector *c) +{ + return container_of(c, struct anx78xx, connector); +} + +static inline struct anx78xx *bridge_to_anx78xx(struct drm_bridge *bridge) +{ + return container_of(bridge, struct anx78xx, bridge); +} + +static int anx78xx_set_bits(struct regmap *map, u8 reg, u8 mask) +{ + return regmap_update_bits(map, reg, mask, mask); +} + +static int anx78xx_clear_bits(struct regmap *map, u8 reg, u8 mask) +{ + return regmap_update_bits(map, reg, mask, 0); +} + +static ssize_t anx78xx_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct anx78xx *anx78xx = container_of(aux, struct anx78xx, aux); + return anx_dp_aux_transfer(anx78xx->map[I2C_IDX_TX_P0], msg); +} + +static int anx78xx_set_hpd(struct anx78xx *anx78xx) +{ + int err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_TMDS_CTRL_BASE + 7, SP_PD_RT); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL3_REG, + SP_HPD_OUT); + if (err) + return err; + + return 0; +} + +static int anx78xx_clear_hpd(struct anx78xx *anx78xx) +{ + int err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL3_REG, + SP_HPD_OUT); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_TMDS_CTRL_BASE + 7, SP_PD_RT); + if (err) + return err; + + return 0; +} + +static const struct reg_sequence tmds_phy_initialization[] = { + { SP_TMDS_CTRL_BASE + 1, 0x90 }, + { SP_TMDS_CTRL_BASE + 2, 0xa9 }, + { SP_TMDS_CTRL_BASE + 6, 0x92 }, + { SP_TMDS_CTRL_BASE + 7, 0x80 }, + { SP_TMDS_CTRL_BASE + 20, 0xf2 }, + { SP_TMDS_CTRL_BASE + 22, 0xc4 }, + { SP_TMDS_CTRL_BASE + 23, 0x18 }, +}; + +static int anx78xx_rx_initialization(struct anx78xx *anx78xx) +{ + int err; + + err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], SP_HDMI_MUTE_CTRL_REG, + SP_AUD_MUTE | SP_VID_MUTE); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], SP_CHIP_CTRL_REG, + SP_MAN_HDMI5V_DET | SP_PLLLOCK_CKDT_EN | + SP_DIGITAL_CKDT_EN); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_SOFTWARE_RESET1_REG, SP_HDCP_MAN_RST | + SP_SW_MAN_RST | SP_TMDS_RST | SP_VIDEO_RST); + if (err) + return err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_SOFTWARE_RESET1_REG, SP_HDCP_MAN_RST | + SP_SW_MAN_RST | SP_TMDS_RST | SP_VIDEO_RST); + if (err) + return err; + + /* Sync detect change, GP set mute */ + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_AUD_EXCEPTION_ENABLE_BASE + 1, BIT(5) | + BIT(6)); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_AUD_EXCEPTION_ENABLE_BASE + 3, + SP_AEC_EN21); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], SP_AUDVID_CTRL_REG, + SP_AVC_EN | SP_AAC_OE | SP_AAC_EN); + if (err) + return err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_SYSTEM_POWER_DOWN1_REG, SP_PWDN_CTRL); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_RX_P0], + SP_VID_DATA_RANGE_CTRL_REG, SP_R2Y_INPUT_LIMIT); + if (err) + return err; + + /* Enable DDC stretch */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_EXTRA_I2C_DEV_ADDR_REG, SP_I2C_EXTRA_ADDR); + if (err) + return err; + + /* TMDS phy initialization */ + err = regmap_multi_reg_write(anx78xx->map[I2C_IDX_RX_P0], + tmds_phy_initialization, + ARRAY_SIZE(tmds_phy_initialization)); + if (err) + return err; + + err = anx78xx_clear_hpd(anx78xx); + if (err) + return err; + + return 0; +} + +static const u8 dp_tx_output_precise_tune_bits[20] = { + 0x01, 0x03, 0x07, 0x7f, 0x71, 0x6b, 0x7f, + 0x73, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x42, 0x1e, 0x3e, 0x72, 0x7e, +}; + +static int anx78xx_link_phy_initialization(struct anx78xx *anx78xx) +{ + int err; + + /* + * REVISIT : It is writing to a RESERVED bits in Analog Control 0 + * register. + */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_ANALOG_CTRL0_REG, + 0x02); + if (err) + return err; + + /* + * Write DP TX output emphasis precise tune bits. + */ + err = regmap_bulk_write(anx78xx->map[I2C_IDX_TX_P1], + SP_DP_TX_LT_CTRL0_REG, + dp_tx_output_precise_tune_bits, + ARRAY_SIZE(dp_tx_output_precise_tune_bits)); + + if (err) + return err; + + return 0; +} + +static int anx78xx_xtal_clk_sel(struct anx78xx *anx78xx) +{ + unsigned int value; + int err; + + err = regmap_update_bits(anx78xx->map[I2C_IDX_TX_P2], + SP_ANALOG_DEBUG2_REG, + SP_XTAL_FRQ | SP_FORCE_SW_OFF_BYPASS, + SP_XTAL_FRQ_27M); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_DP_AUX_CH_CTRL3_REG, + XTAL_CLK & SP_WAIT_COUNTER_7_0_MASK); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_DP_AUX_CH_CTRL4_REG, + ((XTAL_CLK & 0xff00) >> 2) | (XTAL_CLK / 10)); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], + SP_I2C_GEN_10US_TIMER0_REG, XTAL_CLK & 0xff); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], + SP_I2C_GEN_10US_TIMER1_REG, + (XTAL_CLK & 0xff00) >> 8); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_AUX_MISC_CTRL_REG, + XTAL_CLK / 10 - 1); + if (err) + return err; + + err = regmap_read(anx78xx->map[I2C_IDX_RX_P0], + SP_HDMI_US_TIMER_CTRL_REG, + &value); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], + SP_HDMI_US_TIMER_CTRL_REG, + (value & SP_MS_TIMER_MARGIN_10_8_MASK) | + ((((XTAL_CLK / 10) >> 1) - 2) << 3)); + if (err) + return err; + + return 0; +} + +static const struct reg_sequence otp_key_protect[] = { + { SP_OTP_KEY_PROTECT1_REG, SP_OTP_PSW1 }, + { SP_OTP_KEY_PROTECT2_REG, SP_OTP_PSW2 }, + { SP_OTP_KEY_PROTECT3_REG, SP_OTP_PSW3 }, +}; + +static int anx78xx_tx_initialization(struct anx78xx *anx78xx) +{ + int err; + + /* Set terminal resistor to 50 ohm */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_DP_AUX_CH_CTRL2_REG, + 0x30); + if (err) + return err; + + /* Enable aux double diff output */ + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_AUX_CH_CTRL2_REG, 0x08); + if (err) + return err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_HDCP_CTRL_REG, SP_AUTO_EN | + SP_AUTO_START); + if (err) + return err; + + err = regmap_multi_reg_write(anx78xx->map[I2C_IDX_TX_P0], + otp_key_protect, + ARRAY_SIZE(otp_key_protect)); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_HDCP_KEY_COMMAND_REG, SP_DISABLE_SYNC_HDCP); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL8_REG, + SP_VID_VRES_TH); + if (err) + return err; + + /* + * DP HDCP auto authentication wait timer (when downstream starts to + * auth, DP side will wait for this period then do auth automatically) + */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_HDCP_AUTO_TIMER_REG, + 0x00); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_HDCP_CTRL_REG, SP_LINK_POLLING); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_LINK_DEBUG_CTRL_REG, SP_M_VID_DEBUG); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], + SP_ANALOG_DEBUG2_REG, SP_POWERON_TIME_1P5MS); + if (err) + return err; + + err = anx78xx_xtal_clk_sel(anx78xx); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_AUX_DEFER_CTRL_REG, + SP_DEFER_CTRL_EN | 0x0c); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_POLLING_CTRL_REG, + SP_AUTO_POLLING_DISABLE); + if (err) + return err; + + /* + * Short the link integrity check timer to speed up bstatus + * polling for HDCP CTS item 1A-07 + */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], + SP_HDCP_LINK_CHECK_TIMER_REG, 0x1d); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_MISC_CTRL_REG, SP_EQ_TRAINING_LOOP); + if (err) + return err; + + /* Power down the main link by default */ + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_ANALOG_POWER_DOWN_REG, SP_CH0_PD); + if (err) + return err; + + err = anx78xx_link_phy_initialization(anx78xx); + if (err) + return err; + + /* Gen m_clk with downspreading */ + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_M_CALCULATION_CTRL_REG, SP_M_GEN_CLK_SEL); + if (err) + return err; + + return 0; +} + +static int anx78xx_enable_interrupts(struct anx78xx *anx78xx) +{ + int err; + + /* + * BIT0: INT pin assertion polarity: 1 = assert high + * BIT1: INT pin output type: 0 = push/pull + */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_INT_CTRL_REG, 0x01); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], + SP_COMMON_INT_MASK4_REG, SP_HPD_LOST | SP_HPD_PLUG); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_DP_INT_MASK1_REG, + SP_TRAINING_FINISH); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], SP_INT_MASK1_REG, + SP_CKDT_CHG | SP_SCDT_CHG); + if (err) + return err; + + return 0; +} + +static void anx78xx_poweron(struct anx78xx *anx78xx) +{ + struct anx78xx_platform_data *pdata = &anx78xx->pdata; + int err; + + if (WARN_ON(anx78xx->powered)) + return; + + if (pdata->dvdd10) { + err = regulator_enable(pdata->dvdd10); + if (err) { + DRM_ERROR("Failed to enable DVDD10 regulator: %d\n", + err); + return; + } + + usleep_range(1000, 2000); + } + + gpiod_set_value_cansleep(pdata->gpiod_reset, 1); + usleep_range(1000, 2000); + + gpiod_set_value_cansleep(pdata->gpiod_pd, 0); + usleep_range(1000, 2000); + + gpiod_set_value_cansleep(pdata->gpiod_reset, 0); + + /* Power on registers module */ + anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_POWERDOWN_CTRL_REG, + SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | SP_LINK_PD); + anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], SP_POWERDOWN_CTRL_REG, + SP_REGISTER_PD | SP_TOTAL_PD); + + anx78xx->powered = true; +} + +static void anx78xx_poweroff(struct anx78xx *anx78xx) +{ + struct anx78xx_platform_data *pdata = &anx78xx->pdata; + int err; + + if (WARN_ON(!anx78xx->powered)) + return; + + gpiod_set_value_cansleep(pdata->gpiod_reset, 1); + usleep_range(1000, 2000); + + gpiod_set_value_cansleep(pdata->gpiod_pd, 1); + usleep_range(1000, 2000); + + if (pdata->dvdd10) { + err = regulator_disable(pdata->dvdd10); + if (err) { + DRM_ERROR("Failed to disable DVDD10 regulator: %d\n", + err); + return; + } + + usleep_range(1000, 2000); + } + + anx78xx->powered = false; +} + +static int anx78xx_start(struct anx78xx *anx78xx) +{ + int err; + + /* Power on all modules */ + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], + SP_POWERDOWN_CTRL_REG, + SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | + SP_LINK_PD); + + err = anx78xx_enable_interrupts(anx78xx); + if (err) { + DRM_ERROR("Failed to enable interrupts: %d\n", err); + goto err_poweroff; + } + + err = anx78xx_rx_initialization(anx78xx); + if (err) { + DRM_ERROR("Failed receiver initialization: %d\n", err); + goto err_poweroff; + } + + err = anx78xx_tx_initialization(anx78xx); + if (err) { + DRM_ERROR("Failed transmitter initialization: %d\n", err); + goto err_poweroff; + } + + /* + * This delay seems to help keep the hardware in a good state. Without + * it, there are times where it fails silently. + */ + usleep_range(10000, 15000); + + return 0; + +err_poweroff: + DRM_ERROR("Failed SlimPort transmitter initialization: %d\n", err); + anx78xx_poweroff(anx78xx); + + return err; +} + +static int anx78xx_init_pdata(struct anx78xx *anx78xx) +{ + struct anx78xx_platform_data *pdata = &anx78xx->pdata; + struct device *dev = &anx78xx->client->dev; + + /* 1.0V digital core power regulator */ + pdata->dvdd10 = devm_regulator_get(dev, "dvdd10"); + if (IS_ERR(pdata->dvdd10)) { + if (PTR_ERR(pdata->dvdd10) != -EPROBE_DEFER) + DRM_ERROR("DVDD10 regulator not found\n"); + + return PTR_ERR(pdata->dvdd10); + } + + /* GPIO for HPD */ + pdata->gpiod_hpd = devm_gpiod_get(dev, "hpd", GPIOD_IN); + if (IS_ERR(pdata->gpiod_hpd)) + return PTR_ERR(pdata->gpiod_hpd); + + /* GPIO for chip power down */ + pdata->gpiod_pd = devm_gpiod_get(dev, "pd", GPIOD_OUT_HIGH); + if (IS_ERR(pdata->gpiod_pd)) + return PTR_ERR(pdata->gpiod_pd); + + /* GPIO for chip reset */ + pdata->gpiod_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); + + return PTR_ERR_OR_ZERO(pdata->gpiod_reset); +} + +static int anx78xx_dp_link_training(struct anx78xx *anx78xx) +{ + u8 dp_bw, dpcd[2]; + int err; + + err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], SP_HDMI_MUTE_CTRL_REG, + 0x0); + if (err) + return err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], + SP_POWERDOWN_CTRL_REG, + SP_TOTAL_PD); + if (err) + return err; + + err = drm_dp_dpcd_readb(&anx78xx->aux, DP_MAX_LINK_RATE, &dp_bw); + if (err < 0) + return err; + + switch (dp_bw) { + case DP_LINK_BW_1_62: + case DP_LINK_BW_2_7: + case DP_LINK_BW_5_4: + break; + + default: + DRM_DEBUG_KMS("DP bandwidth (%#02x) not supported\n", dp_bw); + return -EINVAL; + } + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL1_REG, + SP_VIDEO_MUTE); + if (err) + return err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], + SP_VID_CTRL1_REG, SP_VIDEO_EN); + if (err) + return err; + + /* Get DPCD info */ + err = drm_dp_dpcd_read(&anx78xx->aux, DP_DPCD_REV, + &anx78xx->dpcd, DP_RECEIVER_CAP_SIZE); + if (err < 0) { + DRM_ERROR("Failed to read DPCD: %d\n", err); + return err; + } + + /* Clear channel x SERDES power down */ + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_ANALOG_POWER_DOWN_REG, SP_CH0_PD); + if (err) + return err; + + drm_dp_link_power_up(&anx78xx->aux, anx78xx->dpcd[DP_DPCD_REV]); + + /* Possibly enable downspread on the sink */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_DOWNSPREAD_CTRL1_REG, 0); + if (err) + return err; + + if (anx78xx->dpcd[DP_MAX_DOWNSPREAD] & DP_MAX_DOWNSPREAD_0_5) { + DRM_DEBUG("Enable downspread on the sink\n"); + /* 4000PPM */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_DOWNSPREAD_CTRL1_REG, 8); + if (err) + return err; + + err = drm_dp_dpcd_writeb(&anx78xx->aux, DP_DOWNSPREAD_CTRL, + DP_SPREAD_AMP_0_5); + if (err < 0) + return err; + } else { + err = drm_dp_dpcd_writeb(&anx78xx->aux, DP_DOWNSPREAD_CTRL, 0); + if (err < 0) + return err; + } + + /* Set the lane count and the link rate on the sink */ + if (drm_dp_enhanced_frame_cap(anx78xx->dpcd)) + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_SYSTEM_CTRL_BASE + 4, + SP_ENHANCED_MODE); + else + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_SYSTEM_CTRL_BASE + 4, + SP_ENHANCED_MODE); + if (err) + return err; + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], + SP_DP_MAIN_LINK_BW_SET_REG, + anx78xx->dpcd[DP_MAX_LINK_RATE]); + if (err) + return err; + + dpcd[1] = drm_dp_max_lane_count(anx78xx->dpcd); + + if (drm_dp_enhanced_frame_cap(anx78xx->dpcd)) + dpcd[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + + err = drm_dp_dpcd_write(&anx78xx->aux, DP_LINK_BW_SET, dpcd, + sizeof(dpcd)); + if (err < 0) { + DRM_ERROR("Failed to configure link: %d\n", err); + return err; + } + + /* Start training on the source */ + err = regmap_write(anx78xx->map[I2C_IDX_TX_P0], SP_DP_LT_CTRL_REG, + SP_LT_EN); + if (err) + return err; + + return 0; +} + +static int anx78xx_config_dp_output(struct anx78xx *anx78xx) +{ + int err; + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL1_REG, + SP_VIDEO_MUTE); + if (err) + return err; + + /* Enable DP output */ + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_VID_CTRL1_REG, + SP_VIDEO_EN); + if (err) + return err; + + return 0; +} + +static int anx78xx_send_video_infoframe(struct anx78xx *anx78xx, + struct hdmi_avi_infoframe *frame) +{ + u8 buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE]; + int err; + + err = hdmi_avi_infoframe_pack(frame, buffer, sizeof(buffer)); + if (err < 0) { + DRM_ERROR("Failed to pack AVI infoframe: %d\n", err); + return err; + } + + err = anx78xx_clear_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_PACKET_SEND_CTRL_REG, SP_AVI_IF_EN); + if (err) + return err; + + err = regmap_bulk_write(anx78xx->map[I2C_IDX_TX_P2], + SP_INFOFRAME_AVI_DB1_REG, buffer, + frame->length); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_PACKET_SEND_CTRL_REG, SP_AVI_IF_UD); + if (err) + return err; + + err = anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P0], + SP_PACKET_SEND_CTRL_REG, SP_AVI_IF_EN); + if (err) + return err; + + return 0; +} + +static int anx78xx_get_downstream_info(struct anx78xx *anx78xx) +{ + u8 value; + int err; + + err = drm_dp_dpcd_readb(&anx78xx->aux, DP_SINK_COUNT, &value); + if (err < 0) { + DRM_ERROR("Get sink count failed %d\n", err); + return err; + } + + if (!DP_GET_SINK_COUNT(value)) { + DRM_ERROR("Downstream disconnected\n"); + return -EIO; + } + + return 0; +} + +static int anx78xx_get_modes(struct drm_connector *connector) +{ + struct anx78xx *anx78xx = connector_to_anx78xx(connector); + int err, num_modes = 0; + + if (WARN_ON(!anx78xx->powered)) + return 0; + + if (anx78xx->drm_edid) + return drm_edid_connector_add_modes(connector); + + mutex_lock(&anx78xx->lock); + + err = anx78xx_get_downstream_info(anx78xx); + if (err) { + DRM_ERROR("Failed to get downstream info: %d\n", err); + goto unlock; + } + + anx78xx->drm_edid = drm_edid_read_ddc(connector, &anx78xx->aux.ddc); + + err = drm_edid_connector_update(connector, anx78xx->drm_edid); + + if (!anx78xx->drm_edid) { + DRM_ERROR("Failed to read EDID\n"); + goto unlock; + } + + if (err) { + DRM_ERROR("Failed to update EDID property: %d\n", err); + goto unlock; + } + + num_modes = drm_edid_connector_add_modes(connector); + +unlock: + mutex_unlock(&anx78xx->lock); + + return num_modes; +} + +static const struct drm_connector_helper_funcs anx78xx_connector_helper_funcs = { + .get_modes = anx78xx_get_modes, +}; + +static enum drm_connector_status anx78xx_detect(struct drm_connector *connector, + bool force) +{ + struct anx78xx *anx78xx = connector_to_anx78xx(connector); + + if (!gpiod_get_value(anx78xx->pdata.gpiod_hpd)) + return connector_status_disconnected; + + return connector_status_connected; +} + +static const struct drm_connector_funcs anx78xx_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = anx78xx_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int anx78xx_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); + int err; + + if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) { + DRM_ERROR("Fix bridge driver to make connector optional!"); + return -EINVAL; + } + + /* Register aux channel */ + anx78xx->aux.name = "DP-AUX"; + anx78xx->aux.dev = &anx78xx->client->dev; + anx78xx->aux.drm_dev = bridge->dev; + anx78xx->aux.transfer = anx78xx_aux_transfer; + + err = drm_dp_aux_register(&anx78xx->aux); + if (err < 0) { + DRM_ERROR("Failed to register aux channel: %d\n", err); + return err; + } + + err = drm_connector_init(bridge->dev, &anx78xx->connector, + &anx78xx_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + if (err) { + DRM_ERROR("Failed to initialize connector: %d\n", err); + goto aux_unregister; + } + + drm_connector_helper_add(&anx78xx->connector, + &anx78xx_connector_helper_funcs); + + anx78xx->connector.polled = DRM_CONNECTOR_POLL_HPD; + + err = drm_connector_attach_encoder(&anx78xx->connector, + encoder); + if (err) { + DRM_ERROR("Failed to link up connector to encoder: %d\n", err); + goto connector_cleanup; + } + + err = drm_connector_register(&anx78xx->connector); + if (err) { + DRM_ERROR("Failed to register connector: %d\n", err); + goto connector_cleanup; + } + + return 0; +connector_cleanup: + drm_connector_cleanup(&anx78xx->connector); +aux_unregister: + drm_dp_aux_unregister(&anx78xx->aux); + return err; +} + +static void anx78xx_bridge_detach(struct drm_bridge *bridge) +{ + drm_dp_aux_unregister(&bridge_to_anx78xx(bridge)->aux); +} + +static enum drm_mode_status +anx78xx_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_NO_INTERLACE; + + /* Max 1200p at 5.4 Ghz, one lane */ + if (mode->clock > 154000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static void anx78xx_bridge_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) +{ + struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); + + /* Power off all modules except configuration registers access */ + anx78xx_set_bits(anx78xx->map[I2C_IDX_TX_P2], SP_POWERDOWN_CTRL_REG, + SP_HDCP_PD | SP_AUDIO_PD | SP_VIDEO_PD | SP_LINK_PD); +} + +static void anx78xx_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); + struct hdmi_avi_infoframe frame; + int err; + + if (WARN_ON(!anx78xx->powered)) + return; + + mutex_lock(&anx78xx->lock); + + err = drm_hdmi_avi_infoframe_from_display_mode(&frame, + &anx78xx->connector, + adjusted_mode); + if (err) { + DRM_ERROR("Failed to setup AVI infoframe: %d\n", err); + goto unlock; + } + + err = anx78xx_send_video_infoframe(anx78xx, &frame); + if (err) + DRM_ERROR("Failed to send AVI infoframe: %d\n", err); + +unlock: + mutex_unlock(&anx78xx->lock); +} + +static void anx78xx_bridge_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *commit) +{ + struct anx78xx *anx78xx = bridge_to_anx78xx(bridge); + int err; + + err = anx78xx_start(anx78xx); + if (err) { + DRM_ERROR("Failed to initialize: %d\n", err); + return; + } + + err = anx78xx_set_hpd(anx78xx); + if (err) + DRM_ERROR("Failed to set HPD: %d\n", err); +} + +static const struct drm_bridge_funcs anx78xx_bridge_funcs = { + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .attach = anx78xx_bridge_attach, + .detach = anx78xx_bridge_detach, + .mode_valid = anx78xx_bridge_mode_valid, + .atomic_disable = anx78xx_bridge_disable, + .mode_set = anx78xx_bridge_mode_set, + .atomic_enable = anx78xx_bridge_enable, +}; + +static irqreturn_t anx78xx_hpd_threaded_handler(int irq, void *data) +{ + struct anx78xx *anx78xx = data; + int err; + + if (anx78xx->powered) + return IRQ_HANDLED; + + mutex_lock(&anx78xx->lock); + + /* Cable is pulled, power on the chip */ + anx78xx_poweron(anx78xx); + + err = anx78xx_enable_interrupts(anx78xx); + if (err) + DRM_ERROR("Failed to enable interrupts: %d\n", err); + + mutex_unlock(&anx78xx->lock); + + return IRQ_HANDLED; +} + +static int anx78xx_handle_dp_int_1(struct anx78xx *anx78xx, u8 irq) +{ + int err; + + DRM_DEBUG_KMS("Handle DP interrupt 1: %02x\n", irq); + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], SP_DP_INT_STATUS1_REG, + irq); + if (err) + return err; + + if (irq & SP_TRAINING_FINISH) { + DRM_DEBUG_KMS("IRQ: hardware link training finished\n"); + err = anx78xx_config_dp_output(anx78xx); + } + + return err; +} + +static bool anx78xx_handle_common_int_4(struct anx78xx *anx78xx, u8 irq) +{ + bool event = false; + int err; + + DRM_DEBUG_KMS("Handle common interrupt 4: %02x\n", irq); + + err = regmap_write(anx78xx->map[I2C_IDX_TX_P2], + SP_COMMON_INT_STATUS4_REG, irq); + if (err) { + DRM_ERROR("Failed to write SP_COMMON_INT_STATUS4 %d\n", err); + return event; + } + + if (irq & SP_HPD_LOST) { + DRM_DEBUG_KMS("IRQ: Hot plug detect - cable is pulled out\n"); + event = true; + anx78xx_poweroff(anx78xx); + /* Free cached EDID */ + drm_edid_free(anx78xx->drm_edid); + anx78xx->drm_edid = NULL; + } else if (irq & SP_HPD_PLUG) { + DRM_DEBUG_KMS("IRQ: Hot plug detect - cable plug\n"); + event = true; + } + + return event; +} + +static void anx78xx_handle_hdmi_int_1(struct anx78xx *anx78xx, u8 irq) +{ + unsigned int value; + int err; + + DRM_DEBUG_KMS("Handle HDMI interrupt 1: %02x\n", irq); + + err = regmap_write(anx78xx->map[I2C_IDX_RX_P0], SP_INT_STATUS1_REG, + irq); + if (err) { + DRM_ERROR("Write HDMI int 1 failed: %d\n", err); + return; + } + + if ((irq & SP_CKDT_CHG) || (irq & SP_SCDT_CHG)) { + DRM_DEBUG_KMS("IRQ: HDMI input detected\n"); + + err = regmap_read(anx78xx->map[I2C_IDX_RX_P0], + SP_SYSTEM_STATUS_REG, &value); + if (err) { + DRM_ERROR("Read system status reg failed: %d\n", err); + return; + } + + if (!(value & SP_TMDS_CLOCK_DET)) { + DRM_DEBUG_KMS("IRQ: *** Waiting for HDMI clock ***\n"); + return; + } + + if (!(value & SP_TMDS_DE_DET)) { + DRM_DEBUG_KMS("IRQ: *** Waiting for HDMI signal ***\n"); + return; + } + + err = anx78xx_dp_link_training(anx78xx); + if (err) + DRM_ERROR("Failed to start link training: %d\n", err); + } +} + +static irqreturn_t anx78xx_intp_threaded_handler(int unused, void *data) +{ + struct anx78xx *anx78xx = data; + bool event = false; + unsigned int irq; + int err; + + mutex_lock(&anx78xx->lock); + + err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], SP_DP_INT_STATUS1_REG, + &irq); + if (err) { + DRM_ERROR("Failed to read DP interrupt 1 status: %d\n", err); + goto unlock; + } + + if (irq) + anx78xx_handle_dp_int_1(anx78xx, irq); + + err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], + SP_COMMON_INT_STATUS4_REG, &irq); + if (err) { + DRM_ERROR("Failed to read common interrupt 4 status: %d\n", + err); + goto unlock; + } + + if (irq) + event = anx78xx_handle_common_int_4(anx78xx, irq); + + /* Make sure we are still powered after handle HPD events */ + if (!anx78xx->powered) + goto unlock; + + err = regmap_read(anx78xx->map[I2C_IDX_RX_P0], SP_INT_STATUS1_REG, + &irq); + if (err) { + DRM_ERROR("Failed to read HDMI int 1 status: %d\n", err); + goto unlock; + } + + if (irq) + anx78xx_handle_hdmi_int_1(anx78xx, irq); + +unlock: + mutex_unlock(&anx78xx->lock); + + if (event) + drm_helper_hpd_irq_event(anx78xx->connector.dev); + + return IRQ_HANDLED; +} + +static void unregister_i2c_dummy_clients(struct anx78xx *anx78xx) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(anx78xx->i2c_dummy); i++) + i2c_unregister_device(anx78xx->i2c_dummy[i]); +} + +static const struct regmap_config anx78xx_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const u16 anx78xx_chipid_list[] = { + 0x7808, + 0x7812, + 0x7814, + 0x7816, + 0x7818, +}; + +static int anx78xx_i2c_probe(struct i2c_client *client) +{ + struct anx78xx *anx78xx; + struct anx78xx_platform_data *pdata; + unsigned int i, idl, idh, version; + const u8 *i2c_addresses; + bool found = false; + int err; + + anx78xx = devm_drm_bridge_alloc(&client->dev, struct anx78xx, bridge, + &anx78xx_bridge_funcs); + if (IS_ERR(anx78xx)) + return PTR_ERR(anx78xx); + + pdata = &anx78xx->pdata; + + mutex_init(&anx78xx->lock); + + anx78xx->bridge.of_node = client->dev.of_node; + + anx78xx->client = client; + i2c_set_clientdata(client, anx78xx); + + err = anx78xx_init_pdata(anx78xx); + if (err) { + if (err != -EPROBE_DEFER) + DRM_ERROR("Failed to initialize pdata: %d\n", err); + + return err; + } + + pdata->hpd_irq = gpiod_to_irq(pdata->gpiod_hpd); + if (pdata->hpd_irq < 0) { + DRM_ERROR("Failed to get HPD IRQ: %d\n", pdata->hpd_irq); + return -ENODEV; + } + + pdata->intp_irq = client->irq; + if (!pdata->intp_irq) { + DRM_ERROR("Failed to get CABLE_DET and INTP IRQ\n"); + return -ENODEV; + } + + /* Map slave addresses of ANX7814 */ + i2c_addresses = device_get_match_data(&client->dev); + for (i = 0; i < I2C_NUM_ADDRESSES; i++) { + struct i2c_client *i2c_dummy; + + i2c_dummy = i2c_new_dummy_device(client->adapter, + i2c_addresses[i] >> 1); + if (IS_ERR(i2c_dummy)) { + err = PTR_ERR(i2c_dummy); + DRM_ERROR("Failed to reserve I2C bus %02x: %d\n", + i2c_addresses[i], err); + goto err_unregister_i2c; + } + + anx78xx->i2c_dummy[i] = i2c_dummy; + anx78xx->map[i] = devm_regmap_init_i2c(anx78xx->i2c_dummy[i], + &anx78xx_regmap_config); + if (IS_ERR(anx78xx->map[i])) { + err = PTR_ERR(anx78xx->map[i]); + DRM_ERROR("Failed regmap initialization %02x\n", + i2c_addresses[i]); + goto err_unregister_i2c; + } + } + + /* Look for supported chip ID */ + anx78xx_poweron(anx78xx); + + err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], SP_DEVICE_IDL_REG, + &idl); + if (err) + goto err_poweroff; + + err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], SP_DEVICE_IDH_REG, + &idh); + if (err) + goto err_poweroff; + + anx78xx->chipid = (u8)idl | ((u8)idh << 8); + + err = regmap_read(anx78xx->map[I2C_IDX_TX_P2], SP_DEVICE_VERSION_REG, + &version); + if (err) + goto err_poweroff; + + for (i = 0; i < ARRAY_SIZE(anx78xx_chipid_list); i++) { + if (anx78xx->chipid == anx78xx_chipid_list[i]) { + DRM_INFO("Found ANX%x (ver. %d) SlimPort Transmitter\n", + anx78xx->chipid, version); + found = true; + break; + } + } + + if (!found) { + DRM_ERROR("ANX%x (ver. %d) not supported by this driver\n", + anx78xx->chipid, version); + err = -ENODEV; + goto err_poweroff; + } + + err = devm_request_threaded_irq(&client->dev, pdata->hpd_irq, NULL, + anx78xx_hpd_threaded_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "anx78xx-hpd", anx78xx); + if (err) { + DRM_ERROR("Failed to request CABLE_DET threaded IRQ: %d\n", + err); + goto err_poweroff; + } + + err = devm_request_threaded_irq(&client->dev, pdata->intp_irq, NULL, + anx78xx_intp_threaded_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "anx78xx-intp", anx78xx); + if (err) { + DRM_ERROR("Failed to request INTP threaded IRQ: %d\n", err); + goto err_poweroff; + } + + drm_bridge_add(&anx78xx->bridge); + + /* If cable is pulled out, just poweroff and wait for HPD event */ + if (!gpiod_get_value(anx78xx->pdata.gpiod_hpd)) + anx78xx_poweroff(anx78xx); + + return 0; + +err_poweroff: + anx78xx_poweroff(anx78xx); + +err_unregister_i2c: + unregister_i2c_dummy_clients(anx78xx); + return err; +} + +static void anx78xx_i2c_remove(struct i2c_client *client) +{ + struct anx78xx *anx78xx = i2c_get_clientdata(client); + + drm_bridge_remove(&anx78xx->bridge); + + unregister_i2c_dummy_clients(anx78xx); + + drm_edid_free(anx78xx->drm_edid); +} + +static const struct of_device_id anx78xx_match_table[] = { + { .compatible = "analogix,anx7808", .data = anx7808_i2c_addresses }, + { .compatible = "analogix,anx7812", .data = anx781x_i2c_addresses }, + { .compatible = "analogix,anx7814", .data = anx781x_i2c_addresses }, + { .compatible = "analogix,anx7816", .data = anx781x_i2c_addresses }, + { .compatible = "analogix,anx7818", .data = anx781x_i2c_addresses }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, anx78xx_match_table); + +static struct i2c_driver anx78xx_driver = { + .driver = { + .name = "anx7814", + .of_match_table = anx78xx_match_table, + }, + .probe = anx78xx_i2c_probe, + .remove = anx78xx_i2c_remove, +}; +module_i2c_driver(anx78xx_driver); + +MODULE_DESCRIPTION("ANX78xx SlimPort Transmitter driver"); +MODULE_AUTHOR("Enric Balletbo i Serra "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.h b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.h new file mode 100644 index 000000000..db2a2725a --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix-anx78xx.h @@ -0,0 +1,249 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2016, Analogix Semiconductor. All rights reserved. + */ + +#ifndef __ANX78xx_H +#define __ANX78xx_H + +#include "analogix-i2c-dptx.h" +#include "analogix-i2c-txcommon.h" + +/***************************************************************/ +/* Register definitions for RX_PO */ +/***************************************************************/ + +/* + * System Control and Status + */ + +/* Software Reset Register 1 */ +#define SP_SOFTWARE_RESET1_REG 0x11 +#define SP_VIDEO_RST BIT(4) +#define SP_HDCP_MAN_RST BIT(2) +#define SP_TMDS_RST BIT(1) +#define SP_SW_MAN_RST BIT(0) + +/* System Status Register */ +#define SP_SYSTEM_STATUS_REG 0x14 +#define SP_TMDS_CLOCK_DET BIT(1) +#define SP_TMDS_DE_DET BIT(0) + +/* HDMI Status Register */ +#define SP_HDMI_STATUS_REG 0x15 +#define SP_HDMI_AUD_LAYOUT BIT(3) +#define SP_HDMI_DET BIT(0) +# define SP_DVI_MODE 0 +# define SP_HDMI_MODE 1 + +/* HDMI Mute Control Register */ +#define SP_HDMI_MUTE_CTRL_REG 0x16 +#define SP_AUD_MUTE BIT(1) +#define SP_VID_MUTE BIT(0) + +/* System Power Down Register 1 */ +#define SP_SYSTEM_POWER_DOWN1_REG 0x18 +#define SP_PWDN_CTRL BIT(0) + +/* + * Audio and Video Auto Control + */ + +/* Auto Audio and Video Control register */ +#define SP_AUDVID_CTRL_REG 0x20 +#define SP_AVC_OE BIT(7) +#define SP_AAC_OE BIT(6) +#define SP_AVC_EN BIT(1) +#define SP_AAC_EN BIT(0) + +/* Audio Exception Enable Registers */ +#define SP_AUD_EXCEPTION_ENABLE_BASE (0x24 - 1) +/* Bits for Audio Exception Enable Register 3 */ +#define SP_AEC_EN21 BIT(5) + +/* + * Interrupt + */ + +/* Interrupt Status Register 1 */ +#define SP_INT_STATUS1_REG 0x31 +/* Bits for Interrupt Status Register 1 */ +#define SP_HDMI_DVI BIT(7) +#define SP_CKDT_CHG BIT(6) +#define SP_SCDT_CHG BIT(5) +#define SP_PCLK_CHG BIT(4) +#define SP_PLL_UNLOCK BIT(3) +#define SP_CABLE_PLUG_CHG BIT(2) +#define SP_SET_MUTE BIT(1) +#define SP_SW_INTR BIT(0) +/* Bits for Interrupt Status Register 2 */ +#define SP_HDCP_ERR BIT(5) +#define SP_AUDIO_SAMPLE_CHG BIT(0) /* undocumented */ +/* Bits for Interrupt Status Register 3 */ +#define SP_AUD_MODE_CHG BIT(0) +/* Bits for Interrupt Status Register 5 */ +#define SP_AUDIO_RCV BIT(0) +/* Bits for Interrupt Status Register 6 */ +#define SP_INT_STATUS6_REG 0x36 +#define SP_CTS_RCV BIT(7) +#define SP_NEW_AUD_PKT BIT(4) +#define SP_NEW_AVI_PKT BIT(1) +#define SP_NEW_CP_PKT BIT(0) +/* Bits for Interrupt Status Register 7 */ +#define SP_NO_VSI BIT(7) +#define SP_NEW_VS BIT(4) + +/* Interrupt Mask 1 Status Registers */ +#define SP_INT_MASK1_REG 0x41 + +/* HDMI US TIMER Control Register */ +#define SP_HDMI_US_TIMER_CTRL_REG 0x49 +#define SP_MS_TIMER_MARGIN_10_8_MASK 0x07 + +/* + * TMDS Control + */ + +/* TMDS Control Registers */ +#define SP_TMDS_CTRL_BASE (0x50 - 1) +/* Bits for TMDS Control Register 7 */ +#define SP_PD_RT BIT(0) + +/* + * Video Control + */ + +/* Video Status Register */ +#define SP_VIDEO_STATUS_REG 0x70 +#define SP_COLOR_DEPTH_MASK 0xf0 +#define SP_COLOR_DEPTH_SHIFT 4 +# define SP_COLOR_DEPTH_MODE_LEGACY 0x00 +# define SP_COLOR_DEPTH_MODE_24BIT 0x04 +# define SP_COLOR_DEPTH_MODE_30BIT 0x05 +# define SP_COLOR_DEPTH_MODE_36BIT 0x06 +# define SP_COLOR_DEPTH_MODE_48BIT 0x07 + +/* Video Data Range Control Register */ +#define SP_VID_DATA_RANGE_CTRL_REG 0x83 +#define SP_R2Y_INPUT_LIMIT BIT(1) + +/* Pixel Clock High Resolution Counter Registers */ +#define SP_PCLK_HIGHRES_CNT_BASE (0x8c - 1) + +/* + * Audio Control + */ + +/* Number of Audio Channels Status Registers */ +#define SP_AUD_CH_STATUS_REG_NUM 6 + +/* Audio IN S/PDIF Channel Status Registers */ +#define SP_AUD_SPDIF_CH_STATUS_BASE 0xc7 + +/* Audio IN S/PDIF Channel Status Register 4 */ +#define SP_FS_FREQ_MASK 0x0f +# define SP_FS_FREQ_44100HZ 0x00 +# define SP_FS_FREQ_48000HZ 0x02 +# define SP_FS_FREQ_32000HZ 0x03 +# define SP_FS_FREQ_88200HZ 0x08 +# define SP_FS_FREQ_96000HZ 0x0a +# define SP_FS_FREQ_176400HZ 0x0c +# define SP_FS_FREQ_192000HZ 0x0e + +/* + * Micellaneous Control Block + */ + +/* CHIP Control Register */ +#define SP_CHIP_CTRL_REG 0xe3 +#define SP_MAN_HDMI5V_DET BIT(3) +#define SP_PLLLOCK_CKDT_EN BIT(2) +#define SP_ANALOG_CKDT_EN BIT(1) +#define SP_DIGITAL_CKDT_EN BIT(0) + +/* Packet Receiving Status Register */ +#define SP_PACKET_RECEIVING_STATUS_REG 0xf3 +#define SP_AVI_RCVD BIT(5) +#define SP_VSI_RCVD BIT(1) + +/***************************************************************/ +/* Register definitions for RX_P1 */ +/***************************************************************/ + +/* HDCP BCAPS Shadow Register */ +#define SP_HDCP_BCAPS_SHADOW_REG 0x2a +#define SP_BCAPS_REPEATER BIT(5) + +/* HDCP Status Register */ +#define SP_RX_HDCP_STATUS_REG 0x3f +#define SP_AUTH_EN BIT(4) + +/* + * InfoFrame and Control Packet Registers + */ + +/* AVI InfoFrame packet checksum */ +#define SP_AVI_INFOFRAME_CHECKSUM 0xa3 + +/* AVI InfoFrame Registers */ +#define SP_AVI_INFOFRAME_DATA_BASE 0xa4 + +#define SP_AVI_COLOR_F_MASK 0x60 +#define SP_AVI_COLOR_F_SHIFT 5 + +/* Audio InfoFrame Registers */ +#define SP_AUD_INFOFRAME_DATA_BASE 0xc4 +#define SP_AUD_INFOFRAME_LAYOUT_MASK 0x0f + +/* MPEG/HDMI Vendor Specific InfoFrame Packet type code */ +#define SP_MPEG_VS_INFOFRAME_TYPE_REG 0xe0 + +/* MPEG/HDMI Vendor Specific InfoFrame Packet length */ +#define SP_MPEG_VS_INFOFRAME_LEN_REG 0xe2 + +/* MPEG/HDMI Vendor Specific InfoFrame Packet version number */ +#define SP_MPEG_VS_INFOFRAME_VER_REG 0xe1 + +/* MPEG/HDMI Vendor Specific InfoFrame Packet content */ +#define SP_MPEG_VS_INFOFRAME_DATA_BASE 0xe4 + +/* General Control Packet Register */ +#define SP_GENERAL_CTRL_PACKET_REG 0x9f +#define SP_CLEAR_AVMUTE BIT(4) +#define SP_SET_AVMUTE BIT(0) + +/***************************************************************/ +/* Register definitions for TX_P1 */ +/***************************************************************/ + +/* DP TX Link Training Control Register */ +#define SP_DP_TX_LT_CTRL0_REG 0x30 + +/* PD 1.2 Lint Training 80bit Pattern Register */ +#define SP_DP_LT_80BIT_PATTERN0_REG 0x80 +#define SP_DP_LT_80BIT_PATTERN_REG_NUM 10 + +/* Audio Interface Control Register 0 */ +#define SP_AUD_INTERFACE_CTRL0_REG 0x5f +#define SP_AUD_INTERFACE_DISABLE 0x80 + +/* Audio Interface Control Register 2 */ +#define SP_AUD_INTERFACE_CTRL2_REG 0x60 +#define SP_M_AUD_ADJUST_ST 0x04 + +/* Audio Interface Control Register 3 */ +#define SP_AUD_INTERFACE_CTRL3_REG 0x62 + +/* Audio Interface Control Register 4 */ +#define SP_AUD_INTERFACE_CTRL4_REG 0x67 + +/* Audio Interface Control Register 5 */ +#define SP_AUD_INTERFACE_CTRL5_REG 0x68 + +/* Audio Interface Control Register 6 */ +#define SP_AUD_INTERFACE_CTRL6_REG 0x69 + +/* Firmware Version Register */ +#define SP_FW_VER_REG 0xb7 + +#endif diff --git a/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c b/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c new file mode 100644 index 000000000..e86621687 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.c @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2016, Analogix Semiconductor. + * + * Based on anx7808 driver obtained from chromeos with copyright: + * Copyright(c) 2013, Google Inc. + */ + +#include +#include + +#include +#include +#include + +#include "analogix-i2c-dptx.h" + +#define AUX_WAIT_TIMEOUT_MS 15 +#define AUX_CH_BUFFER_SIZE 16 + +static int anx_i2c_dp_clear_bits(struct regmap *map, u8 reg, u8 mask) +{ + return regmap_update_bits(map, reg, mask, 0); +} + +static bool anx_dp_aux_op_finished(struct regmap *map_dptx) +{ + unsigned int value; + int err; + + err = regmap_read(map_dptx, SP_DP_AUX_CH_CTRL2_REG, &value); + if (err < 0) + return false; + + return (value & SP_AUX_EN) == 0; +} + +static int anx_dp_aux_wait(struct regmap *map_dptx) +{ + unsigned long timeout; + unsigned int status; + int err; + + timeout = jiffies + msecs_to_jiffies(AUX_WAIT_TIMEOUT_MS) + 1; + + while (!anx_dp_aux_op_finished(map_dptx)) { + if (time_after(jiffies, timeout)) { + if (!anx_dp_aux_op_finished(map_dptx)) { + DRM_ERROR("Timed out waiting AUX to finish\n"); + return -ETIMEDOUT; + } + + break; + } + + usleep_range(1000, 2000); + } + + /* Read the AUX channel access status */ + err = regmap_read(map_dptx, SP_AUX_CH_STATUS_REG, &status); + if (err < 0) { + DRM_ERROR("Failed to read from AUX channel: %d\n", err); + return err; + } + + if (status & SP_AUX_STATUS) { + DRM_ERROR("Failed to wait for AUX channel (status: %02x)\n", + status); + return -ETIMEDOUT; + } + + return 0; +} + +static int anx_dp_aux_address(struct regmap *map_dptx, unsigned int addr) +{ + int err; + + err = regmap_write(map_dptx, SP_AUX_ADDR_7_0_REG, addr & 0xff); + if (err) + return err; + + err = regmap_write(map_dptx, SP_AUX_ADDR_15_8_REG, + (addr & 0xff00) >> 8); + if (err) + return err; + + /* + * DP AUX CH Address Register #2, only update bits[3:0] + * [7:4] RESERVED + * [3:0] AUX_ADDR[19:16], Register control AUX CH address. + */ + err = regmap_update_bits(map_dptx, SP_AUX_ADDR_19_16_REG, + SP_AUX_ADDR_19_16_MASK, + (addr & 0xf0000) >> 16); + + if (err) + return err; + + return 0; +} + +ssize_t anx_dp_aux_transfer(struct regmap *map_dptx, + struct drm_dp_aux_msg *msg) +{ + u8 ctrl1 = msg->request; + u8 ctrl2 = SP_AUX_EN; + u8 *buffer = msg->buffer; + int err; + + /* The DP AUX transmit and receive buffer has 16 bytes. */ + if (WARN_ON(msg->size > AUX_CH_BUFFER_SIZE)) + return -E2BIG; + + /* Zero-sized messages specify address-only transactions. */ + if (msg->size < 1) + ctrl2 |= SP_ADDR_ONLY; + else /* For non-zero-sized set the length field. */ + ctrl1 |= (msg->size - 1) << SP_AUX_LENGTH_SHIFT; + + if ((msg->size > 0) && ((msg->request & DP_AUX_I2C_READ) == 0)) { + /* When WRITE | MOT write values to data buffer */ + err = regmap_bulk_write(map_dptx, + SP_DP_BUF_DATA0_REG, buffer, + msg->size); + if (err) + return err; + } + + /* Write address and request */ + err = anx_dp_aux_address(map_dptx, msg->address); + if (err) + return err; + + err = regmap_write(map_dptx, SP_DP_AUX_CH_CTRL1_REG, ctrl1); + if (err) + return err; + + /* Start transaction */ + err = regmap_update_bits(map_dptx, SP_DP_AUX_CH_CTRL2_REG, + SP_ADDR_ONLY | SP_AUX_EN, ctrl2); + if (err) + return err; + + err = anx_dp_aux_wait(map_dptx); + if (err) + return err; + + msg->reply = DP_AUX_I2C_REPLY_ACK; + + if ((msg->size > 0) && (msg->request & DP_AUX_I2C_READ)) { + /* Read values from data buffer */ + err = regmap_bulk_read(map_dptx, + SP_DP_BUF_DATA0_REG, buffer, + msg->size); + if (err) + return err; + } + + err = anx_i2c_dp_clear_bits(map_dptx, SP_DP_AUX_CH_CTRL2_REG, + SP_ADDR_ONLY); + if (err) + return err; + + return msg->size; +} +EXPORT_SYMBOL_GPL(anx_dp_aux_transfer); diff --git a/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.h b/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.h new file mode 100644 index 000000000..663c4bea6 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix-i2c-dptx.h @@ -0,0 +1,256 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2016, Analogix Semiconductor. + * + * Based on anx7808 driver obtained from chromeos with copyright: + * Copyright(c) 2013, Google Inc. + */ +#ifndef _ANALOGIX_I2C_DPTX_H_ +#define _ANALOGIX_I2C_DPTX_H_ + +/***************************************************************/ +/* Register definitions for TX_P0 */ +/***************************************************************/ + +/* HDCP Status Register */ +#define SP_TX_HDCP_STATUS_REG 0x00 +#define SP_AUTH_FAIL BIT(5) +#define SP_AUTHEN_PASS BIT(1) + +/* HDCP Control Register 0 */ +#define SP_HDCP_CTRL0_REG 0x01 +#define SP_RX_REPEATER BIT(6) +#define SP_RE_AUTH BIT(5) +#define SP_SW_AUTH_OK BIT(4) +#define SP_HARD_AUTH_EN BIT(3) +#define SP_HDCP_ENC_EN BIT(2) +#define SP_BKSV_SRM_PASS BIT(1) +#define SP_KSVLIST_VLD BIT(0) +/* HDCP Function Enabled */ +#define SP_HDCP_FUNCTION_ENABLED (BIT(0) | BIT(1) | BIT(2) | BIT(3)) + +/* HDCP Receiver BSTATUS Register 0 */ +#define SP_HDCP_RX_BSTATUS0_REG 0x1b +/* HDCP Receiver BSTATUS Register 1 */ +#define SP_HDCP_RX_BSTATUS1_REG 0x1c + +/* HDCP Embedded "Blue Screen" Content Registers */ +#define SP_HDCP_VID0_BLUE_SCREEN_REG 0x2c +#define SP_HDCP_VID1_BLUE_SCREEN_REG 0x2d +#define SP_HDCP_VID2_BLUE_SCREEN_REG 0x2e + +/* HDCP Wait R0 Timing Register */ +#define SP_HDCP_WAIT_R0_TIME_REG 0x40 + +/* HDCP Link Integrity Check Timer Register */ +#define SP_HDCP_LINK_CHECK_TIMER_REG 0x41 + +/* HDCP Repeater Ready Wait Timer Register */ +#define SP_HDCP_RPTR_RDY_WAIT_TIME_REG 0x42 + +/* HDCP Auto Timer Register */ +#define SP_HDCP_AUTO_TIMER_REG 0x51 + +/* HDCP Key Status Register */ +#define SP_HDCP_KEY_STATUS_REG 0x5e + +/* HDCP Key Command Register */ +#define SP_HDCP_KEY_COMMAND_REG 0x5f +#define SP_DISABLE_SYNC_HDCP BIT(2) + +/* OTP Memory Key Protection Registers */ +#define SP_OTP_KEY_PROTECT1_REG 0x60 +#define SP_OTP_KEY_PROTECT2_REG 0x61 +#define SP_OTP_KEY_PROTECT3_REG 0x62 +#define SP_OTP_PSW1 0xa2 +#define SP_OTP_PSW2 0x7e +#define SP_OTP_PSW3 0xc6 + +/* DP System Control Registers */ +#define SP_DP_SYSTEM_CTRL_BASE (0x80 - 1) +/* Bits for DP System Control Register 2 */ +#define SP_CHA_STA BIT(2) +/* Bits for DP System Control Register 3 */ +#define SP_HPD_STATUS BIT(6) +#define SP_HPD_FORCE BIT(5) +#define SP_HPD_CTRL BIT(4) +#define SP_STRM_VALID BIT(2) +#define SP_STRM_FORCE BIT(1) +#define SP_STRM_CTRL BIT(0) +/* Bits for DP System Control Register 4 */ +#define SP_ENHANCED_MODE BIT(3) + +/* DP Video Control Register */ +#define SP_DP_VIDEO_CTRL_REG 0x84 +#define SP_COLOR_F_MASK 0x06 +#define SP_COLOR_F_SHIFT 1 +#define SP_BPC_MASK 0xe0 +#define SP_BPC_SHIFT 5 +# define SP_BPC_6BITS 0x00 +# define SP_BPC_8BITS 0x01 +# define SP_BPC_10BITS 0x02 +# define SP_BPC_12BITS 0x03 + +/* DP Audio Control Register */ +#define SP_DP_AUDIO_CTRL_REG 0x87 +#define SP_AUD_EN BIT(0) + +/* 10us Pulse Generate Timer Registers */ +#define SP_I2C_GEN_10US_TIMER0_REG 0x88 +#define SP_I2C_GEN_10US_TIMER1_REG 0x89 + +/* Packet Send Control Register */ +#define SP_PACKET_SEND_CTRL_REG 0x90 +#define SP_AUD_IF_UP BIT(7) +#define SP_AVI_IF_UD BIT(6) +#define SP_MPEG_IF_UD BIT(5) +#define SP_SPD_IF_UD BIT(4) +#define SP_AUD_IF_EN BIT(3) +#define SP_AVI_IF_EN BIT(2) +#define SP_MPEG_IF_EN BIT(1) +#define SP_SPD_IF_EN BIT(0) + +/* DP HDCP Control Register */ +#define SP_DP_HDCP_CTRL_REG 0x92 +#define SP_AUTO_EN BIT(7) +#define SP_AUTO_START BIT(5) +#define SP_LINK_POLLING BIT(1) + +/* DP Main Link Bandwidth Setting Register */ +#define SP_DP_MAIN_LINK_BW_SET_REG 0xa0 +#define SP_LINK_BW_SET_MASK 0x1f +#define SP_INITIAL_SLIM_M_AUD_SEL BIT(5) + +/* DP Lane Count Setting Register */ +#define SP_DP_LANE_COUNT_SET_REG 0xa1 + +/* DP Training Pattern Set Register */ +#define SP_DP_TRAINING_PATTERN_SET_REG 0xa2 + +/* DP Lane 0 Link Training Control Register */ +#define SP_DP_LANE0_LT_CTRL_REG 0xa3 +#define SP_TX_SW_SET_MASK 0x1b +#define SP_MAX_PRE_REACH BIT(5) +#define SP_MAX_DRIVE_REACH BIT(4) +#define SP_PRE_EMP_LEVEL1 BIT(3) +#define SP_DRVIE_CURRENT_LEVEL1 BIT(0) + +/* DP Link Training Control Register */ +#define SP_DP_LT_CTRL_REG 0xa8 +#define SP_DP_LT_INPROGRESS 0x80 +#define SP_LT_ERROR_TYPE_MASK 0x70 +# define SP_LT_NO_ERROR 0x00 +# define SP_LT_AUX_WRITE_ERROR 0x01 +# define SP_LT_MAX_DRIVE_REACHED 0x02 +# define SP_LT_WRONG_LANE_COUNT_SET 0x03 +# define SP_LT_LOOP_SAME_5_TIME 0x04 +# define SP_LT_CR_FAIL_IN_EQ 0x05 +# define SP_LT_EQ_LOOP_5_TIME 0x06 +#define SP_LT_EN BIT(0) + +/* DP CEP Training Control Registers */ +#define SP_DP_CEP_TRAINING_CTRL0_REG 0xa9 +#define SP_DP_CEP_TRAINING_CTRL1_REG 0xaa + +/* DP Debug Register 1 */ +#define SP_DP_DEBUG1_REG 0xb0 +#define SP_DEBUG_PLL_LOCK BIT(4) +#define SP_POLLING_EN BIT(1) + +/* DP Polling Control Register */ +#define SP_DP_POLLING_CTRL_REG 0xb4 +#define SP_AUTO_POLLING_DISABLE BIT(0) + +/* DP Link Debug Control Register */ +#define SP_DP_LINK_DEBUG_CTRL_REG 0xb8 +#define SP_M_VID_DEBUG BIT(5) +#define SP_NEW_PRBS7 BIT(4) +#define SP_INSERT_ER BIT(1) +#define SP_PRBS31_EN BIT(0) + +/* AUX Misc control Register */ +#define SP_AUX_MISC_CTRL_REG 0xbf + +/* DP PLL control Register */ +#define SP_DP_PLL_CTRL_REG 0xc7 +#define SP_PLL_RST BIT(6) + +/* DP Analog Power Down Register */ +#define SP_DP_ANALOG_POWER_DOWN_REG 0xc8 +#define SP_CH0_PD BIT(0) + +/* DP Misc Control Register */ +#define SP_DP_MISC_CTRL_REG 0xcd +#define SP_EQ_TRAINING_LOOP BIT(6) + +/* DP Extra I2C Device Address Register */ +#define SP_DP_EXTRA_I2C_DEV_ADDR_REG 0xce +#define SP_I2C_STRETCH_DISABLE BIT(7) + +#define SP_I2C_EXTRA_ADDR 0x50 + +/* DP Downspread Control Register 1 */ +#define SP_DP_DOWNSPREAD_CTRL1_REG 0xd0 + +/* DP M Value Calculation Control Register */ +#define SP_DP_M_CALCULATION_CTRL_REG 0xd9 +#define SP_M_GEN_CLK_SEL BIT(0) + +/* AUX Channel Access Status Register */ +#define SP_AUX_CH_STATUS_REG 0xe0 +#define SP_AUX_STATUS 0x0f + +/* AUX Channel DEFER Control Register */ +#define SP_AUX_DEFER_CTRL_REG 0xe2 +#define SP_DEFER_CTRL_EN BIT(7) + +/* DP Buffer Data Count Register */ +#define SP_BUF_DATA_COUNT_REG 0xe4 +#define SP_BUF_DATA_COUNT_MASK 0x1f +#define SP_BUF_CLR BIT(7) + +/* DP AUX Channel Control Register 1 */ +#define SP_DP_AUX_CH_CTRL1_REG 0xe5 +#define SP_AUX_TX_COMM_MASK 0x0f +#define SP_AUX_LENGTH_MASK 0xf0 +#define SP_AUX_LENGTH_SHIFT 4 + +/* DP AUX CH Address Register 0 */ +#define SP_AUX_ADDR_7_0_REG 0xe6 + +/* DP AUX CH Address Register 1 */ +#define SP_AUX_ADDR_15_8_REG 0xe7 + +/* DP AUX CH Address Register 2 */ +#define SP_AUX_ADDR_19_16_REG 0xe8 +#define SP_AUX_ADDR_19_16_MASK 0x0f + +/* DP AUX Channel Control Register 2 */ +#define SP_DP_AUX_CH_CTRL2_REG 0xe9 +#define SP_AUX_SEL_RXCM BIT(6) +#define SP_AUX_CHSEL BIT(3) +#define SP_AUX_PN_INV BIT(2) +#define SP_ADDR_ONLY BIT(1) +#define SP_AUX_EN BIT(0) + +/* DP Video Stream Control InfoFrame Register */ +#define SP_DP_3D_VSC_CTRL_REG 0xea +#define SP_INFO_FRAME_VSC_EN BIT(0) + +/* DP Video Stream Data Byte 1 Register */ +#define SP_DP_VSC_DB1_REG 0xeb + +/* DP AUX Channel Control Register 3 */ +#define SP_DP_AUX_CH_CTRL3_REG 0xec +#define SP_WAIT_COUNTER_7_0_MASK 0xff + +/* DP AUX Channel Control Register 4 */ +#define SP_DP_AUX_CH_CTRL4_REG 0xed + +/* DP AUX Buffer Data Registers */ +#define SP_DP_BUF_DATA0_REG 0xf0 + +ssize_t anx_dp_aux_transfer(struct regmap *map_dptx, + struct drm_dp_aux_msg *msg); + +#endif diff --git a/drivers/gpu/drm/bridge/analogix/analogix-i2c-txcommon.h b/drivers/gpu/drm/bridge/analogix/analogix-i2c-txcommon.h new file mode 100644 index 000000000..3c843497d --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix-i2c-txcommon.h @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright(c) 2016, Analogix Semiconductor. All rights reserved. + */ +#ifndef _ANALOGIX_I2C_TXCOMMON_H_ +#define _ANALOGIX_I2C_TXCOMMON_H_ + +/***************************************************************/ +/* Register definitions for TX_P2 */ +/***************************************************************/ + +/* + * Core Register Definitions + */ + +/* Device ID Low Byte Register */ +#define SP_DEVICE_IDL_REG 0x02 + +/* Device ID High Byte Register */ +#define SP_DEVICE_IDH_REG 0x03 + +/* Device version register */ +#define SP_DEVICE_VERSION_REG 0x04 + +/* Power Down Control Register */ +#define SP_POWERDOWN_CTRL_REG 0x05 +#define SP_REGISTER_PD BIT(7) +#define SP_HDCP_PD BIT(5) +#define SP_AUDIO_PD BIT(4) +#define SP_VIDEO_PD BIT(3) +#define SP_LINK_PD BIT(2) +#define SP_TOTAL_PD BIT(1) + +/* Reset Control Register 1 */ +#define SP_RESET_CTRL1_REG 0x06 +#define SP_MISC_RST BIT(7) +#define SP_VIDCAP_RST BIT(6) +#define SP_VIDFIF_RST BIT(5) +#define SP_AUDFIF_RST BIT(4) +#define SP_AUDCAP_RST BIT(3) +#define SP_HDCP_RST BIT(2) +#define SP_SW_RST BIT(1) +#define SP_HW_RST BIT(0) + +/* Reset Control Register 2 */ +#define SP_RESET_CTRL2_REG 0x07 +#define SP_AUX_RST BIT(2) +#define SP_SERDES_FIFO_RST BIT(1) +#define SP_I2C_REG_RST BIT(0) + +/* Video Control Register 1 */ +#define SP_VID_CTRL1_REG 0x08 +#define SP_VIDEO_EN BIT(7) +#define SP_VIDEO_MUTE BIT(2) +#define SP_DE_GEN BIT(1) +#define SP_DEMUX BIT(0) + +/* Video Control Register 2 */ +#define SP_VID_CTRL2_REG 0x09 +#define SP_IN_COLOR_F_MASK 0x03 +#define SP_IN_YC_BIT_SEL BIT(2) +#define SP_IN_BPC_MASK 0x70 +#define SP_IN_BPC_SHIFT 4 +# define SP_IN_BPC_12BIT 0x03 +# define SP_IN_BPC_10BIT 0x02 +# define SP_IN_BPC_8BIT 0x01 +# define SP_IN_BPC_6BIT 0x00 +#define SP_IN_D_RANGE BIT(7) + +/* Video Control Register 3 */ +#define SP_VID_CTRL3_REG 0x0a +#define SP_HPD_OUT BIT(6) + +/* Video Control Register 5 */ +#define SP_VID_CTRL5_REG 0x0c +#define SP_CSC_STD_SEL BIT(7) +#define SP_XVYCC_RNG_LMT BIT(6) +#define SP_RANGE_Y2R BIT(5) +#define SP_CSPACE_Y2R BIT(4) +#define SP_RGB_RNG_LMT BIT(3) +#define SP_Y_RNG_LMT BIT(2) +#define SP_RANGE_R2Y BIT(1) +#define SP_CSPACE_R2Y BIT(0) + +/* Video Control Register 6 */ +#define SP_VID_CTRL6_REG 0x0d +#define SP_TEST_PATTERN_EN BIT(7) +#define SP_VIDEO_PROCESS_EN BIT(6) +#define SP_VID_US_MODE BIT(3) +#define SP_VID_DS_MODE BIT(2) +#define SP_UP_SAMPLE BIT(1) +#define SP_DOWN_SAMPLE BIT(0) + +/* Video Control Register 8 */ +#define SP_VID_CTRL8_REG 0x0f +#define SP_VID_VRES_TH BIT(0) + +/* Total Line Status Low Byte Register */ +#define SP_TOTAL_LINE_STAL_REG 0x24 + +/* Total Line Status High Byte Register */ +#define SP_TOTAL_LINE_STAH_REG 0x25 + +/* Active Line Status Low Byte Register */ +#define SP_ACT_LINE_STAL_REG 0x26 + +/* Active Line Status High Byte Register */ +#define SP_ACT_LINE_STAH_REG 0x27 + +/* Vertical Front Porch Status Register */ +#define SP_V_F_PORCH_STA_REG 0x28 + +/* Vertical SYNC Width Status Register */ +#define SP_V_SYNC_STA_REG 0x29 + +/* Vertical Back Porch Status Register */ +#define SP_V_B_PORCH_STA_REG 0x2a + +/* Total Pixel Status Low Byte Register */ +#define SP_TOTAL_PIXEL_STAL_REG 0x2b + +/* Total Pixel Status High Byte Register */ +#define SP_TOTAL_PIXEL_STAH_REG 0x2c + +/* Active Pixel Status Low Byte Register */ +#define SP_ACT_PIXEL_STAL_REG 0x2d + +/* Active Pixel Status High Byte Register */ +#define SP_ACT_PIXEL_STAH_REG 0x2e + +/* Horizontal Front Porch Status Low Byte Register */ +#define SP_H_F_PORCH_STAL_REG 0x2f + +/* Horizontal Front Porch Statys High Byte Register */ +#define SP_H_F_PORCH_STAH_REG 0x30 + +/* Horizontal SYNC Width Status Low Byte Register */ +#define SP_H_SYNC_STAL_REG 0x31 + +/* Horizontal SYNC Width Status High Byte Register */ +#define SP_H_SYNC_STAH_REG 0x32 + +/* Horizontal Back Porch Status Low Byte Register */ +#define SP_H_B_PORCH_STAL_REG 0x33 + +/* Horizontal Back Porch Status High Byte Register */ +#define SP_H_B_PORCH_STAH_REG 0x34 + +/* InfoFrame AVI Packet DB1 Register */ +#define SP_INFOFRAME_AVI_DB1_REG 0x70 + +/* Bit Control Specific Register */ +#define SP_BIT_CTRL_SPECIFIC_REG 0x80 +#define SP_BIT_CTRL_SELECT_SHIFT 1 +#define SP_ENABLE_BIT_CTRL BIT(0) + +/* InfoFrame Audio Packet DB1 Register */ +#define SP_INFOFRAME_AUD_DB1_REG 0x83 + +/* InfoFrame MPEG Packet DB1 Register */ +#define SP_INFOFRAME_MPEG_DB1_REG 0xb0 + +/* Audio Channel Status Registers */ +#define SP_AUD_CH_STATUS_BASE 0xd0 + +/* Audio Channel Num Register 5 */ +#define SP_I2S_CHANNEL_NUM_MASK 0xe0 +# define SP_I2S_CH_NUM_1 (0x00 << 5) +# define SP_I2S_CH_NUM_2 (0x01 << 5) +# define SP_I2S_CH_NUM_3 (0x02 << 5) +# define SP_I2S_CH_NUM_4 (0x03 << 5) +# define SP_I2S_CH_NUM_5 (0x04 << 5) +# define SP_I2S_CH_NUM_6 (0x05 << 5) +# define SP_I2S_CH_NUM_7 (0x06 << 5) +# define SP_I2S_CH_NUM_8 (0x07 << 5) +#define SP_EXT_VUCP BIT(2) +#define SP_VBIT BIT(1) +#define SP_AUDIO_LAYOUT BIT(0) + +/* Analog Debug Register 1 */ +#define SP_ANALOG_DEBUG1_REG 0xdc + +/* Analog Debug Register 2 */ +#define SP_ANALOG_DEBUG2_REG 0xdd +#define SP_FORCE_SW_OFF_BYPASS 0x20 +#define SP_XTAL_FRQ 0x1c +# define SP_XTAL_FRQ_19M2 (0x00 << 2) +# define SP_XTAL_FRQ_24M (0x01 << 2) +# define SP_XTAL_FRQ_25M (0x02 << 2) +# define SP_XTAL_FRQ_26M (0x03 << 2) +# define SP_XTAL_FRQ_27M (0x04 << 2) +# define SP_XTAL_FRQ_38M4 (0x05 << 2) +# define SP_XTAL_FRQ_52M (0x06 << 2) +#define SP_POWERON_TIME_1P5MS 0x03 + +/* Analog Control 0 Register */ +#define SP_ANALOG_CTRL0_REG 0xe1 + +/* Common Interrupt Status Register 1 */ +#define SP_COMMON_INT_STATUS_BASE (0xf1 - 1) +#define SP_PLL_LOCK_CHG 0x40 + +/* Common Interrupt Status Register 2 */ +#define SP_COMMON_INT_STATUS2 0xf2 +#define SP_HDCP_AUTH_CHG BIT(1) +#define SP_HDCP_AUTH_DONE BIT(0) + +#define SP_HDCP_LINK_CHECK_FAIL BIT(0) + +/* Common Interrupt Status Register 4 */ +#define SP_COMMON_INT_STATUS4_REG 0xf4 +#define SP_HPD_IRQ BIT(6) +#define SP_HPD_ESYNC_ERR BIT(4) +#define SP_HPD_CHG BIT(2) +#define SP_HPD_LOST BIT(1) +#define SP_HPD_PLUG BIT(0) + +/* DP Interrupt Status Register */ +#define SP_DP_INT_STATUS1_REG 0xf7 +#define SP_TRAINING_FINISH BIT(5) +#define SP_POLLING_ERR BIT(4) + +/* Common Interrupt Mask Register */ +#define SP_COMMON_INT_MASK_BASE (0xf8 - 1) + +#define SP_COMMON_INT_MASK4_REG 0xfb + +/* DP Interrupts Mask Register */ +#define SP_DP_INT_MASK1_REG 0xfe + +/* Interrupt Control Register */ +#define SP_INT_CTRL_REG 0xff + +#endif /* _ANALOGIX_I2C_TXCOMMON_H_ */ diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c new file mode 100644 index 000000000..30c0c0b41 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -0,0 +1,1622 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* +* Analogix DP (Display Port) core interface driver. +* +* Copyright (C) 2012 Samsung Electronics Co., Ltd. +* Author: Jingoo Han +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "analogix_dp_core.h" +#include "analogix_dp_reg.h" + +#define to_dp(nm) container_of(nm, struct analogix_dp_device, nm) + +static const bool verify_fast_training; + +static void analogix_dp_init_dp(struct analogix_dp_device *dp) +{ + analogix_dp_reset(dp); + + analogix_dp_swreset(dp); + + analogix_dp_init_analog_param(dp); + analogix_dp_init_interrupt(dp); + + /* SW defined function Normal operation */ + analogix_dp_enable_sw_function(dp); + + analogix_dp_config_interrupt(dp); + + analogix_dp_init_hpd(dp); + analogix_dp_init_aux(dp); +} + +static int analogix_dp_detect_hpd(struct analogix_dp_device *dp) +{ + int timeout_loop = 0; + + while (timeout_loop < DP_TIMEOUT_LOOP_COUNT) { + if (analogix_dp_get_plug_in_status(dp) == 0) + return 0; + + timeout_loop++; + usleep_range(1000, 1100); + } + + /* + * Some edp screen do not have hpd signal, so we can't just + * return failed when hpd plug in detect failed, DT property + * "force-hpd" would indicate whether driver need this. + */ + if (!dp->force_hpd) + return -ETIMEDOUT; + + /* + * The eDP TRM indicate that if HPD_STATUS(RO) is 0, AUX CH + * will not work, so we need to give a force hpd action to + * set HPD_STATUS manually. + */ + dev_dbg(dp->dev, "failed to get hpd plug status, try to force hpd\n"); + + analogix_dp_force_hpd(dp); + + if (analogix_dp_get_plug_in_status(dp) != 0) { + dev_err(dp->dev, "failed to get hpd plug in status\n"); + return -EINVAL; + } + + dev_dbg(dp->dev, "success to get plug in status after force hpd\n"); + + return 0; +} + +static bool analogix_dp_detect_sink_psr(struct analogix_dp_device *dp) +{ + unsigned char psr_version; + int ret; + + ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_SUPPORT, &psr_version); + if (ret != 1) { + dev_err(dp->dev, "failed to get PSR version, disable it\n"); + return false; + } + + dev_dbg(dp->dev, "Panel PSR version : %x\n", psr_version); + return psr_version & DP_PSR_IS_SUPPORTED; +} + +static int analogix_dp_enable_sink_psr(struct analogix_dp_device *dp) +{ + unsigned char psr_en; + int ret; + + /* Disable psr function */ + ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_EN_CFG, &psr_en); + if (ret != 1) { + dev_err(dp->dev, "failed to get psr config\n"); + goto end; + } + + psr_en &= ~DP_PSR_ENABLE; + ret = drm_dp_dpcd_writeb(&dp->aux, DP_PSR_EN_CFG, psr_en); + if (ret != 1) { + dev_err(dp->dev, "failed to disable panel psr\n"); + goto end; + } + + /* Main-Link transmitter remains active during PSR active states */ + psr_en = DP_PSR_CRC_VERIFICATION; + ret = drm_dp_dpcd_writeb(&dp->aux, DP_PSR_EN_CFG, psr_en); + if (ret != 1) { + dev_err(dp->dev, "failed to set panel psr\n"); + goto end; + } + + /* Enable psr function */ + psr_en = DP_PSR_ENABLE | DP_PSR_CRC_VERIFICATION; + ret = drm_dp_dpcd_writeb(&dp->aux, DP_PSR_EN_CFG, psr_en); + if (ret != 1) { + dev_err(dp->dev, "failed to set panel psr\n"); + goto end; + } + + analogix_dp_enable_psr_crc(dp); + + dp->psr_supported = true; + + return 0; +end: + dev_err(dp->dev, "enable psr fail, force to disable psr\n"); + + return ret; +} + +static int +analogix_dp_enable_rx_to_enhanced_mode(struct analogix_dp_device *dp, + bool enable) +{ + u8 data; + int ret; + + ret = drm_dp_dpcd_readb(&dp->aux, DP_LANE_COUNT_SET, &data); + if (ret != 1) + return ret; + + if (enable) + ret = drm_dp_dpcd_writeb(&dp->aux, DP_LANE_COUNT_SET, + DP_LANE_COUNT_ENHANCED_FRAME_EN | + DPCD_LANE_COUNT_SET(data)); + else + ret = drm_dp_dpcd_writeb(&dp->aux, DP_LANE_COUNT_SET, + DPCD_LANE_COUNT_SET(data)); + + return ret < 0 ? ret : 0; +} + +static int analogix_dp_is_enhanced_mode_available(struct analogix_dp_device *dp, + u8 *enhanced_mode_support) +{ + u8 data; + int ret; + + ret = drm_dp_dpcd_readb(&dp->aux, DP_MAX_LANE_COUNT, &data); + if (ret != 1) { + *enhanced_mode_support = 0; + return ret; + } + + *enhanced_mode_support = DPCD_ENHANCED_FRAME_CAP(data); + + return 0; +} + +static int analogix_dp_set_enhanced_mode(struct analogix_dp_device *dp) +{ + u8 data; + int ret; + + ret = analogix_dp_is_enhanced_mode_available(dp, &data); + if (ret < 0) + return ret; + + ret = analogix_dp_enable_rx_to_enhanced_mode(dp, data); + if (ret < 0) + return ret; + + analogix_dp_enable_enhanced_mode(dp, data); + + return 0; +} + +static int analogix_dp_training_pattern_dis(struct analogix_dp_device *dp) +{ + int ret; + + analogix_dp_set_training_pattern(dp, DP_NONE); + + ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_DISABLE); + + return ret < 0 ? ret : 0; +} + +static int analogix_dp_link_start(struct analogix_dp_device *dp) +{ + u8 buf[4]; + int lane, lane_count, retval; + + lane_count = dp->link_train.lane_count; + + dp->link_train.lt_state = CLOCK_RECOVERY; + dp->link_train.eq_loop = 0; + + for (lane = 0; lane < lane_count; lane++) + dp->link_train.cr_loop[lane] = 0; + + /* Set link rate and count as you want to establish*/ + analogix_dp_set_link_bandwidth(dp, dp->link_train.link_rate); + retval = analogix_dp_wait_pll_locked(dp); + if (retval) { + DRM_DEV_ERROR(dp->dev, "Wait for pll lock failed %d\n", retval); + return retval; + } + /* + * MACRO_RST must be applied after the PLL_LOCK to avoid + * the DP inter pair skew issue for at least 10 us + */ + analogix_dp_reset_macro(dp); + analogix_dp_set_lane_count(dp, dp->link_train.lane_count); + + /* Setup RX configuration */ + buf[0] = dp->link_train.link_rate; + buf[1] = dp->link_train.lane_count; + retval = drm_dp_dpcd_write(&dp->aux, DP_LINK_BW_SET, buf, 2); + if (retval < 0) + return retval; + /* set enhanced mode if available */ + retval = analogix_dp_set_enhanced_mode(dp); + if (retval < 0) { + dev_err(dp->dev, "failed to set enhance mode\n"); + return retval; + } + + /* Set TX voltage-swing and pre-emphasis to minimum */ + for (lane = 0; lane < lane_count; lane++) + dp->link_train.training_lane[lane] = + DP_TRAIN_VOLTAGE_SWING_LEVEL_0 | + DP_TRAIN_PRE_EMPH_LEVEL_0; + analogix_dp_set_lane_link_training(dp); + + /* Set training pattern 1 */ + analogix_dp_set_training_pattern(dp, TRAINING_PTN1); + + /* Set RX training pattern */ + retval = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, + DP_LINK_SCRAMBLING_DISABLE | + DP_TRAINING_PATTERN_1); + if (retval < 0) + return retval; + + retval = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, + dp->link_train.training_lane, lane_count); + if (retval < 0) + return retval; + + return 0; +} + +static void analogix_dp_reduce_link_rate(struct analogix_dp_device *dp) +{ + analogix_dp_training_pattern_dis(dp); + analogix_dp_set_enhanced_mode(dp); + + dp->link_train.lt_state = FAILED; +} + +static void analogix_dp_get_adjust_training_lane(struct analogix_dp_device *dp, + u8 link_status[DP_LINK_STATUS_SIZE]) +{ + int lane, lane_count; + u8 voltage_swing, pre_emphasis, training_lane; + + lane_count = dp->link_train.lane_count; + for (lane = 0; lane < lane_count; lane++) { + voltage_swing = drm_dp_get_adjust_request_voltage(link_status, lane); + voltage_swing >>= DP_TRAIN_VOLTAGE_SWING_SHIFT; + pre_emphasis = drm_dp_get_adjust_request_pre_emphasis(link_status, lane); + pre_emphasis >>= DP_TRAIN_PRE_EMPHASIS_SHIFT; + training_lane = DPCD_VOLTAGE_SWING_SET(voltage_swing) | + DPCD_PRE_EMPHASIS_SET(pre_emphasis); + + if (voltage_swing == VOLTAGE_LEVEL_3) + training_lane |= DP_TRAIN_MAX_SWING_REACHED; + if (pre_emphasis == PRE_EMPHASIS_LEVEL_3) + training_lane |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + + dp->link_train.training_lane[lane] = training_lane; + } +} + +static int analogix_dp_process_clock_recovery(struct analogix_dp_device *dp) +{ + int lane, lane_count, retval; + u8 voltage_swing, pre_emphasis, training_lane; + u8 link_status[DP_LINK_STATUS_SIZE]; + + drm_dp_link_train_clock_recovery_delay(&dp->aux, dp->dpcd); + + lane_count = dp->link_train.lane_count; + + retval = drm_dp_dpcd_read_link_status(&dp->aux, link_status); + if (retval < 0) + return retval; + + if (drm_dp_clock_recovery_ok(link_status, lane_count)) { + /* set training pattern 2 for EQ */ + analogix_dp_set_training_pattern(dp, TRAINING_PTN2); + + retval = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, + DP_LINK_SCRAMBLING_DISABLE | + DP_TRAINING_PATTERN_2); + if (retval < 0) + return retval; + + dev_dbg(dp->dev, "Link Training Clock Recovery success\n"); + dp->link_train.lt_state = EQUALIZER_TRAINING; + + return 0; + } + + for (lane = 0; lane < lane_count; lane++) { + training_lane = analogix_dp_get_lane_link_training(dp, lane); + voltage_swing = drm_dp_get_adjust_request_voltage(link_status, lane); + voltage_swing >>= DP_TRAIN_VOLTAGE_SWING_SHIFT; + pre_emphasis = drm_dp_get_adjust_request_pre_emphasis(link_status, lane); + pre_emphasis >>= DP_TRAIN_PRE_EMPHASIS_SHIFT; + + if (DPCD_VOLTAGE_SWING_GET(training_lane) == voltage_swing && + DPCD_PRE_EMPHASIS_GET(training_lane) == pre_emphasis) + dp->link_train.cr_loop[lane]++; + + if (dp->link_train.cr_loop[lane] == MAX_CR_LOOP || + voltage_swing == VOLTAGE_LEVEL_3 || + pre_emphasis == PRE_EMPHASIS_LEVEL_3) { + dev_err(dp->dev, "CR Max reached (%d,%d,%d)\n", + dp->link_train.cr_loop[lane], + voltage_swing, pre_emphasis); + analogix_dp_reduce_link_rate(dp); + return -EIO; + } + } + + analogix_dp_get_adjust_training_lane(dp, link_status); + analogix_dp_set_lane_link_training(dp); + + retval = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, + dp->link_train.training_lane, lane_count); + if (retval < 0) + return retval; + + return 0; +} + +static int analogix_dp_process_equalizer_training(struct analogix_dp_device *dp) +{ + int lane_count, retval; + u32 reg; + u8 link_status[DP_LINK_STATUS_SIZE]; + + drm_dp_link_train_channel_eq_delay(&dp->aux, dp->dpcd); + + lane_count = dp->link_train.lane_count; + + retval = drm_dp_dpcd_read_link_status(&dp->aux, link_status); + if (retval < 0) + return retval; + + if (!drm_dp_clock_recovery_ok(link_status, lane_count)) { + analogix_dp_reduce_link_rate(dp); + return -EIO; + } + + analogix_dp_get_adjust_training_lane(dp, link_status); + + if (drm_dp_channel_eq_ok(link_status, lane_count)) { + /* traing pattern Set to Normal */ + retval = analogix_dp_training_pattern_dis(dp); + if (retval < 0) + return retval; + + dev_dbg(dp->dev, "Link Training success!\n"); + analogix_dp_get_link_bandwidth(dp, ®); + dp->link_train.link_rate = reg; + dev_dbg(dp->dev, "final bandwidth = %.2x\n", + dp->link_train.link_rate); + + analogix_dp_get_lane_count(dp, ®); + dp->link_train.lane_count = reg; + dev_dbg(dp->dev, "final lane count = %.2x\n", + dp->link_train.lane_count); + + dp->link_train.lt_state = FINISHED; + + return 0; + } + + /* not all locked */ + dp->link_train.eq_loop++; + + if (dp->link_train.eq_loop > MAX_EQ_LOOP) { + dev_err(dp->dev, "EQ Max loop\n"); + analogix_dp_reduce_link_rate(dp); + return -EIO; + } + + analogix_dp_set_lane_link_training(dp); + + retval = drm_dp_dpcd_write(&dp->aux, DP_TRAINING_LANE0_SET, + dp->link_train.training_lane, lane_count); + if (retval < 0) + return retval; + + return 0; +} + +static void analogix_dp_get_max_rx_bandwidth(struct analogix_dp_device *dp, + u8 *bandwidth) +{ + u8 data; + + /* + * For DP rev.1.1, Maximum link rate of Main Link lanes + * 0x06 = 1.62 Gbps, 0x0a = 2.7 Gbps + * For DP rev.1.2, Maximum link rate of Main Link lanes + * 0x06 = 1.62 Gbps, 0x0a = 2.7 Gbps, 0x14 = 5.4Gbps + */ + drm_dp_dpcd_readb(&dp->aux, DP_MAX_LINK_RATE, &data); + *bandwidth = data; +} + +static void analogix_dp_get_max_rx_lane_count(struct analogix_dp_device *dp, + u8 *lane_count) +{ + u8 data; + + /* + * For DP rev.1.1, Maximum number of Main Link lanes + * 0x01 = 1 lane, 0x02 = 2 lanes, 0x04 = 4 lanes + */ + drm_dp_dpcd_readb(&dp->aux, DP_MAX_LANE_COUNT, &data); + *lane_count = DPCD_MAX_LANE_COUNT(data); +} + +static int analogix_dp_full_link_train(struct analogix_dp_device *dp, + u32 max_lanes, u32 max_rate) +{ + int retval = 0; + bool training_finished = false; + + /* Initialize by reading RX's DPCD */ + analogix_dp_get_max_rx_bandwidth(dp, &dp->link_train.link_rate); + analogix_dp_get_max_rx_lane_count(dp, &dp->link_train.lane_count); + + if ((dp->link_train.link_rate != DP_LINK_BW_1_62) && + (dp->link_train.link_rate != DP_LINK_BW_2_7) && + (dp->link_train.link_rate != DP_LINK_BW_5_4)) { + dev_err(dp->dev, "Rx Max Link Rate is abnormal :%x !\n", + dp->link_train.link_rate); + dp->link_train.link_rate = DP_LINK_BW_1_62; + } + + if (dp->link_train.lane_count == 0) { + dev_err(dp->dev, "Rx Max Lane count is abnormal :%x !\n", + dp->link_train.lane_count); + dp->link_train.lane_count = (u8)LANE_COUNT1; + } + + /* Setup TX lane count & rate */ + if (dp->link_train.lane_count > max_lanes) + dp->link_train.lane_count = max_lanes; + if (dp->link_train.link_rate > max_rate) + dp->link_train.link_rate = max_rate; + + /* All DP analog module power up */ + analogix_dp_set_analog_power_down(dp, POWER_ALL, 0); + + dp->link_train.lt_state = START; + + /* Process here */ + while (!retval && !training_finished) { + switch (dp->link_train.lt_state) { + case START: + retval = analogix_dp_link_start(dp); + if (retval) + dev_err(dp->dev, "LT link start failed!\n"); + break; + case CLOCK_RECOVERY: + retval = analogix_dp_process_clock_recovery(dp); + if (retval) + dev_err(dp->dev, "LT CR failed!\n"); + break; + case EQUALIZER_TRAINING: + retval = analogix_dp_process_equalizer_training(dp); + if (retval) + dev_err(dp->dev, "LT EQ failed!\n"); + break; + case FINISHED: + training_finished = 1; + break; + case FAILED: + return -EREMOTEIO; + } + } + if (retval) + dev_err(dp->dev, "eDP link training failed (%d)\n", retval); + + return retval; +} + +static int analogix_dp_fast_link_train(struct analogix_dp_device *dp) +{ + int ret; + u8 link_status[DP_LINK_STATUS_SIZE]; + + analogix_dp_set_link_bandwidth(dp, dp->link_train.link_rate); + ret = analogix_dp_wait_pll_locked(dp); + if (ret) { + DRM_DEV_ERROR(dp->dev, "Wait for pll lock failed %d\n", ret); + return ret; + } + + /* + * MACRO_RST must be applied after the PLL_LOCK to avoid + * the DP inter pair skew issue for at least 10 us + */ + analogix_dp_reset_macro(dp); + analogix_dp_set_lane_count(dp, dp->link_train.lane_count); + analogix_dp_set_lane_link_training(dp); + + /* source Set training pattern 1 */ + analogix_dp_set_training_pattern(dp, TRAINING_PTN1); + /* From DP spec, pattern must be on-screen for a minimum 500us */ + usleep_range(500, 600); + + analogix_dp_set_training_pattern(dp, TRAINING_PTN2); + /* From DP spec, pattern must be on-screen for a minimum 500us */ + usleep_range(500, 600); + + /* TODO: enhanced_mode?*/ + analogix_dp_set_training_pattern(dp, DP_NONE); + + /* + * Useful for debugging issues with fast link training, disable for more + * speed + */ + if (verify_fast_training) { + ret = drm_dp_dpcd_read_link_status(&dp->aux, link_status); + if (ret < 0) { + DRM_DEV_ERROR(dp->dev, "Read link status failed %d\n", + ret); + return ret; + } + + if (!drm_dp_clock_recovery_ok(link_status, dp->link_train.lane_count)) { + DRM_DEV_ERROR(dp->dev, "Clock recovery failed\n"); + analogix_dp_reduce_link_rate(dp); + return -EIO; + } + + if (!drm_dp_channel_eq_ok(link_status, dp->link_train.lane_count)) { + DRM_DEV_ERROR(dp->dev, "Channel EQ failed\n"); + analogix_dp_reduce_link_rate(dp); + return -EIO; + } + } + + return 0; +} + +static int analogix_dp_train_link(struct analogix_dp_device *dp) +{ + if (dp->fast_train_enable) + return analogix_dp_fast_link_train(dp); + + return analogix_dp_full_link_train(dp, dp->video_info.max_lane_count, + dp->video_info.max_link_rate); +} + +static int analogix_dp_config_video(struct analogix_dp_device *dp) +{ + int timeout_loop = 0; + int done_count = 0; + + analogix_dp_config_video_slave_mode(dp); + + analogix_dp_set_video_color_format(dp); + + for (;;) { + timeout_loop++; + if (analogix_dp_is_slave_video_stream_clock_on(dp)) + break; + if (timeout_loop > DP_TIMEOUT_LOOP_COUNT) { + dev_err(dp->dev, "Timeout of slave video streamclk ok\n"); + return -ETIMEDOUT; + } + usleep_range(1000, 1001); + } + + /* Set to use the register calculated M/N video */ + analogix_dp_set_video_cr_mn(dp, CALCULATED_M, 0, 0); + + /* For video bist, Video timing must be generated by register */ + analogix_dp_set_video_timing_mode(dp, VIDEO_TIMING_FROM_CAPTURE); + + /* Disable video mute */ + analogix_dp_enable_video_mute(dp, 0); + + /* Configure video slave mode */ + analogix_dp_enable_video_master(dp, 0); + + /* Enable video */ + analogix_dp_start_video(dp); + + timeout_loop = 0; + + for (;;) { + timeout_loop++; + if (analogix_dp_is_video_stream_on(dp)) { + done_count++; + if (done_count > 10) + break; + } else if (done_count) { + done_count = 0; + } + if (timeout_loop > DP_TIMEOUT_LOOP_COUNT) { + dev_warn(dp->dev, + "Ignoring timeout of video streamclk ok\n"); + break; + } + + usleep_range(1000, 1001); + } + + return 0; +} + +static int analogix_dp_enable_scramble(struct analogix_dp_device *dp, + bool enable) +{ + u8 data; + int ret; + + if (enable) { + analogix_dp_enable_scrambling(dp); + + ret = drm_dp_dpcd_readb(&dp->aux, DP_TRAINING_PATTERN_SET, + &data); + if (ret != 1) + return ret; + ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, + (u8)(data & ~DP_LINK_SCRAMBLING_DISABLE)); + } else { + analogix_dp_disable_scrambling(dp); + + ret = drm_dp_dpcd_readb(&dp->aux, DP_TRAINING_PATTERN_SET, + &data); + if (ret != 1) + return ret; + ret = drm_dp_dpcd_writeb(&dp->aux, DP_TRAINING_PATTERN_SET, + (u8)(data | DP_LINK_SCRAMBLING_DISABLE)); + } + return ret < 0 ? ret : 0; +} + +static irqreturn_t analogix_dp_hardirq(int irq, void *arg) +{ + struct analogix_dp_device *dp = arg; + irqreturn_t ret = IRQ_NONE; + enum dp_irq_type irq_type; + + irq_type = analogix_dp_get_irq_type(dp); + if (irq_type != DP_IRQ_TYPE_UNKNOWN) { + analogix_dp_mute_hpd_interrupt(dp); + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +static irqreturn_t analogix_dp_irq_thread(int irq, void *arg) +{ + struct analogix_dp_device *dp = arg; + enum dp_irq_type irq_type; + + irq_type = analogix_dp_get_irq_type(dp); + if (irq_type & DP_IRQ_TYPE_HP_CABLE_IN || + irq_type & DP_IRQ_TYPE_HP_CABLE_OUT) { + dev_dbg(dp->dev, "Detected cable status changed!\n"); + if (dp->drm_dev) + drm_helper_hpd_irq_event(dp->drm_dev); + } + + if (irq_type != DP_IRQ_TYPE_UNKNOWN) { + analogix_dp_clear_hotplug_interrupts(dp); + analogix_dp_unmute_hpd_interrupt(dp); + } + + return IRQ_HANDLED; +} + +static int analogix_dp_fast_link_train_detection(struct analogix_dp_device *dp) +{ + int ret; + u8 spread; + + ret = drm_dp_dpcd_readb(&dp->aux, DP_MAX_DOWNSPREAD, &spread); + if (ret != 1) { + dev_err(dp->dev, "failed to read downspread %d\n", ret); + return ret; + } + dp->fast_train_enable = !!(spread & DP_NO_AUX_HANDSHAKE_LINK_TRAINING); + dev_dbg(dp->dev, "fast link training %s\n", + dp->fast_train_enable ? "supported" : "unsupported"); + return 0; +} + +static int analogix_dp_commit(struct analogix_dp_device *dp) +{ + int ret; + + ret = drm_dp_read_dpcd_caps(&dp->aux, dp->dpcd); + if (ret < 0) { + dev_err(dp->dev, "failed to read dpcd caps: %d\n", ret); + return ret; + } + + ret = analogix_dp_train_link(dp); + if (ret) { + dev_err(dp->dev, "unable to do link train, ret=%d\n", ret); + return ret; + } + + ret = analogix_dp_enable_scramble(dp, 1); + if (ret < 0) { + dev_err(dp->dev, "can not enable scramble\n"); + return ret; + } + + analogix_dp_init_video(dp); + ret = analogix_dp_config_video(dp); + if (ret) { + dev_err(dp->dev, "unable to config video\n"); + return ret; + } + + /* Check whether panel supports fast training */ + ret = analogix_dp_fast_link_train_detection(dp); + if (ret) + return ret; + + if (analogix_dp_detect_sink_psr(dp)) { + ret = analogix_dp_enable_sink_psr(dp); + if (ret) + return ret; + } + + return ret; +} + +static int analogix_dp_enable_psr(struct analogix_dp_device *dp) +{ + struct dp_sdp psr_vsc; + int ret; + u8 sink; + + ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_STATUS, &sink); + if (ret != 1) + DRM_DEV_ERROR(dp->dev, "Failed to read psr status %d\n", ret); + else if (sink == DP_PSR_SINK_ACTIVE_RFB) + return 0; + + /* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */ + memset(&psr_vsc, 0, sizeof(psr_vsc)); + psr_vsc.sdp_header.HB0 = 0; + psr_vsc.sdp_header.HB1 = 0x7; + psr_vsc.sdp_header.HB2 = 0x2; + psr_vsc.sdp_header.HB3 = 0x8; + psr_vsc.db[0] = 0; + psr_vsc.db[1] = EDP_VSC_PSR_STATE_ACTIVE | EDP_VSC_PSR_CRC_VALUES_VALID; + + ret = analogix_dp_send_psr_spd(dp, &psr_vsc, true); + if (!ret) + analogix_dp_set_analog_power_down(dp, POWER_ALL, true); + + return ret; +} + +static int analogix_dp_disable_psr(struct analogix_dp_device *dp) +{ + struct dp_sdp psr_vsc; + int ret; + u8 sink; + + analogix_dp_set_analog_power_down(dp, POWER_ALL, false); + + ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D0); + if (ret != 1) { + DRM_DEV_ERROR(dp->dev, "Failed to set DP Power0 %d\n", ret); + return ret; + } + + ret = drm_dp_dpcd_readb(&dp->aux, DP_PSR_STATUS, &sink); + if (ret != 1) { + DRM_DEV_ERROR(dp->dev, "Failed to read psr status %d\n", ret); + return ret; + } else if (sink == DP_PSR_SINK_INACTIVE) { + DRM_DEV_ERROR(dp->dev, "sink inactive, skip disable psr"); + return 0; + } + + ret = analogix_dp_train_link(dp); + if (ret) { + DRM_DEV_ERROR(dp->dev, "Failed to train the link %d\n", ret); + return ret; + } + + /* Prepare VSC packet as per EDP 1.4 spec, Table 6.9 */ + memset(&psr_vsc, 0, sizeof(psr_vsc)); + psr_vsc.sdp_header.HB0 = 0; + psr_vsc.sdp_header.HB1 = 0x7; + psr_vsc.sdp_header.HB2 = 0x2; + psr_vsc.sdp_header.HB3 = 0x8; + + psr_vsc.db[0] = 0; + psr_vsc.db[1] = 0; + + return analogix_dp_send_psr_spd(dp, &psr_vsc, true); +} + +static const struct drm_edid *analogix_dp_bridge_edid_read(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct analogix_dp_device *dp = to_dp(bridge); + + return drm_edid_read_ddc(connector, &dp->aux.ddc); +} + +static int analogix_dp_bridge_atomic_check(struct drm_bridge *bridge, + struct drm_bridge_state *bridge_state, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct analogix_dp_device *dp = to_dp(bridge); + struct drm_display_info *di = &conn_state->connector->display_info; + u32 mask = BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) | BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422); + + if (analogix_dp_is_rockchip(dp->plat_data->dev_type)) { + if ((di->color_formats & mask)) { + DRM_DEBUG_KMS("Swapping display color format from YUV to RGB\n"); + di->color_formats &= ~mask; + di->color_formats |= BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444); + di->bpc = 8; + } + } + + conn_state->self_refresh_aware = true; + + if (crtc_state->self_refresh_active && !dp->psr_supported) + return -EINVAL; + + return 0; +} + +static enum drm_connector_status +analogix_dp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector) +{ + struct analogix_dp_device *dp = to_dp(bridge); + enum drm_connector_status status = connector_status_disconnected; + + if (dp->plat_data->next_bridge) + return connector_status_connected; + + if (!analogix_dp_detect_hpd(dp)) + status = connector_status_connected; + + return status; +} + +static int analogix_dp_bridge_attach(struct drm_bridge *bridge, + struct drm_encoder *encoder, + enum drm_bridge_attach_flags flags) +{ + struct analogix_dp_device *dp = to_dp(bridge); + int ret = 0; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + DRM_ERROR("Unsupported connector creation\n"); + return -EINVAL; + } + + if (dp->plat_data->next_bridge) { + ret = drm_bridge_attach(dp->encoder, dp->plat_data->next_bridge, bridge, + DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) { + dev_err(dp->dev, "failed to attach following panel or bridge (%d)\n", ret); + return ret; + } + } + + return 0; +} + +static +struct drm_crtc *analogix_dp_get_old_crtc(struct analogix_dp_device *dp, + struct drm_atomic_commit *state) +{ + struct drm_encoder *encoder = dp->encoder; + struct drm_connector *connector; + struct drm_connector_state *conn_state; + + connector = drm_atomic_get_old_connector_for_encoder(state, encoder); + if (!connector) + return NULL; + + conn_state = drm_atomic_get_old_connector_state(state, connector); + if (!conn_state) + return NULL; + + return conn_state->crtc; +} + +static +struct drm_crtc *analogix_dp_get_new_crtc(struct analogix_dp_device *dp, + struct drm_atomic_commit *state) +{ + struct drm_encoder *encoder = dp->encoder; + struct drm_connector *connector; + struct drm_connector_state *conn_state; + + connector = drm_atomic_get_new_connector_for_encoder(state, encoder); + if (!connector) + return NULL; + + conn_state = drm_atomic_get_new_connector_state(state, connector); + if (!conn_state) + return NULL; + + return conn_state->crtc; +} + +static void analogix_dp_bridge_atomic_pre_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *old_state) +{ + struct analogix_dp_device *dp = to_dp(bridge); + struct drm_crtc *crtc; + struct drm_crtc_state *old_crtc_state; + + crtc = analogix_dp_get_new_crtc(dp, old_state); + if (!crtc) + return; + + old_crtc_state = drm_atomic_get_old_crtc_state(old_state, crtc); + /* Don't touch the panel if we're coming back from PSR */ + if (old_crtc_state && old_crtc_state->self_refresh_active) + return; +} + +static int analogix_dp_set_bridge(struct analogix_dp_device *dp) +{ + int ret; + + pm_runtime_get_sync(dp->dev); + + ret = analogix_dp_init_analog_func(dp); + if (ret) + return ret; + + /* + * According to DP spec v1.3 chap 3.5.1.2 Link Training, + * We should first make sure the HPD signal is asserted high by device + * when we want to establish a link with it. + */ + ret = analogix_dp_detect_hpd(dp); + if (ret) { + DRM_ERROR("failed to get hpd single ret = %d\n", ret); + goto out_dp_init; + } + + ret = analogix_dp_commit(dp); + if (ret) { + DRM_ERROR("dp commit error, ret = %d\n", ret); + goto out_dp_init; + } + + enable_irq(dp->irq); + return 0; + +out_dp_init: + pm_runtime_put_sync(dp->dev); + + return ret; +} + +static void analogix_dp_bridge_mode_set(struct drm_bridge *bridge, + struct drm_atomic_commit *state, + const struct drm_display_mode *mode) +{ + struct analogix_dp_device *dp = to_dp(bridge); + struct video_info *video = &dp->video_info; + struct device_node *dp_node = dp->dev->of_node; + struct drm_connector *connector; + struct drm_display_info *display_info; + int vic; + + connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder); + if (!connector) + return; + display_info = &connector->display_info; + + /* Input video interlaces & hsync pol & vsync pol */ + video->interlaced = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); + video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC); + video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC); + + /* Input video dynamic_range & colorimetry */ + vic = drm_match_cea_mode(mode); + if ((vic == 6) || (vic == 7) || (vic == 21) || (vic == 22) || + (vic == 2) || (vic == 3) || (vic == 17) || (vic == 18)) { + video->dynamic_range = CEA; + video->ycbcr_coeff = COLOR_YCBCR601; + } else if (vic) { + video->dynamic_range = CEA; + video->ycbcr_coeff = COLOR_YCBCR709; + } else { + video->dynamic_range = VESA; + video->ycbcr_coeff = COLOR_YCBCR709; + } + + /* Input vide bpc and color_formats */ + switch (display_info->bpc) { + case 12: + video->color_depth = COLOR_12; + break; + case 10: + video->color_depth = COLOR_10; + break; + case 8: + video->color_depth = COLOR_8; + break; + case 6: + video->color_depth = COLOR_6; + break; + default: + video->color_depth = COLOR_8; + break; + } + if (display_info->color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444)) + video->color_space = COLOR_YCBCR444; + else if (display_info->color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) + video->color_space = COLOR_YCBCR422; + else + video->color_space = COLOR_RGB; + + /* + * NOTE: those property parsing code is used for providing backward + * compatibility for samsung platform. + * Due to we used the "of_property_read_u32" interfaces, when this + * property isn't present, the "video_info" can keep the original + * values and wouldn't be modified. + */ + of_property_read_u32(dp_node, "samsung,color-space", + &video->color_space); + of_property_read_u32(dp_node, "samsung,dynamic-range", + &video->dynamic_range); + of_property_read_u32(dp_node, "samsung,ycbcr-coeff", + &video->ycbcr_coeff); + of_property_read_u32(dp_node, "samsung,color-depth", + &video->color_depth); + if (of_property_read_bool(dp_node, "hsync-active-high")) + video->h_sync_polarity = true; + if (of_property_read_bool(dp_node, "vsync-active-high")) + video->v_sync_polarity = true; + if (of_property_read_bool(dp_node, "interlaced")) + video->interlaced = true; +} + +static void analogix_dp_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_atomic_commit *old_state) +{ + struct analogix_dp_device *dp = to_dp(bridge); + struct drm_crtc *crtc; + struct drm_crtc_state *old_crtc_state, *new_crtc_state; + int timeout_loop = 0; + int ret; + + crtc = analogix_dp_get_new_crtc(dp, old_state); + if (!crtc) + return; + + new_crtc_state = drm_atomic_get_new_crtc_state(old_state, crtc); + if (!new_crtc_state) + return; + analogix_dp_bridge_mode_set(bridge, old_state, &new_crtc_state->adjusted_mode); + + old_crtc_state = drm_atomic_get_old_crtc_state(old_state, crtc); + /* Not a full enable, just disable PSR and continue */ + if (old_crtc_state && old_crtc_state->self_refresh_active) { + ret = analogix_dp_disable_psr(dp); + if (ret) + DRM_ERROR("Failed to disable psr %d\n", ret); + return; + } + + if (dp->dpms_mode == DRM_MODE_DPMS_ON) + return; + + while (timeout_loop < MAX_PLL_LOCK_LOOP) { + if (analogix_dp_set_bridge(dp) == 0) { + dp->dpms_mode = DRM_MODE_DPMS_ON; + return; + } + dev_err(dp->dev, "failed to set bridge, retry: %d\n", + timeout_loop); + timeout_loop++; + usleep_range(10, 11); + } + dev_err(dp->dev, "too many times retry set bridge, give it up\n"); +} + +static void analogix_dp_bridge_disable(struct drm_bridge *bridge) +{ + struct analogix_dp_device *dp = to_dp(bridge); + + if (dp->dpms_mode != DRM_MODE_DPMS_ON) + return; + + disable_irq(dp->irq); + + analogix_dp_set_analog_power_down(dp, POWER_ALL, 1); + + pm_runtime_put_sync(dp->dev); + + dp->fast_train_enable = false; + dp->psr_supported = false; + dp->dpms_mode = DRM_MODE_DPMS_OFF; +} + +static void analogix_dp_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *old_state) +{ + struct analogix_dp_device *dp = to_dp(bridge); + struct drm_crtc *old_crtc, *new_crtc; + struct drm_crtc_state *old_crtc_state = NULL; + struct drm_crtc_state *new_crtc_state = NULL; + int ret; + + new_crtc = analogix_dp_get_new_crtc(dp, old_state); + if (!new_crtc) + goto out; + + new_crtc_state = drm_atomic_get_new_crtc_state(old_state, new_crtc); + if (!new_crtc_state) + goto out; + + /* Don't do a full disable on PSR transitions */ + if (new_crtc_state->self_refresh_active) + return; + +out: + old_crtc = analogix_dp_get_old_crtc(dp, old_state); + if (old_crtc) { + old_crtc_state = drm_atomic_get_old_crtc_state(old_state, + old_crtc); + + /* When moving from PSR to fully disabled, exit PSR first. */ + if (old_crtc_state && old_crtc_state->self_refresh_active) { + ret = analogix_dp_disable_psr(dp); + if (ret) + DRM_ERROR("Failed to disable psr (%d)\n", ret); + } + } + + analogix_dp_bridge_disable(bridge); +} + +static void analogix_dp_bridge_atomic_post_disable(struct drm_bridge *bridge, + struct drm_atomic_commit *old_state) +{ + struct analogix_dp_device *dp = to_dp(bridge); + struct drm_crtc *crtc; + struct drm_crtc_state *new_crtc_state; + int ret; + + crtc = analogix_dp_get_new_crtc(dp, old_state); + if (!crtc) + return; + + new_crtc_state = drm_atomic_get_new_crtc_state(old_state, crtc); + if (!new_crtc_state || !new_crtc_state->self_refresh_active) + return; + + ret = analogix_dp_enable_psr(dp); + if (ret) + DRM_ERROR("Failed to enable psr (%d)\n", ret); +} + +static const struct drm_bridge_funcs analogix_dp_bridge_funcs = { + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, + .atomic_create_state = drm_atomic_helper_bridge_create_state, + .atomic_pre_enable = analogix_dp_bridge_atomic_pre_enable, + .atomic_enable = analogix_dp_bridge_atomic_enable, + .atomic_disable = analogix_dp_bridge_atomic_disable, + .atomic_post_disable = analogix_dp_bridge_atomic_post_disable, + .atomic_check = analogix_dp_bridge_atomic_check, + .attach = analogix_dp_bridge_attach, + .edid_read = analogix_dp_bridge_edid_read, + .detect = analogix_dp_bridge_detect, +}; + +static int analogix_dp_dt_parse_pdata(struct analogix_dp_device *dp) +{ + struct device_node *dp_node = dp->dev->of_node; + struct video_info *video_info = &dp->video_info; + + switch (dp->plat_data->dev_type) { + case RK3288_DP: + case RK3399_EDP: + /* + * Like Rk3288 DisplayPort TRM indicate that "Main link + * containing 4 physical lanes of 2.7/1.62 Gbps/lane". + */ + video_info->max_link_rate = 0x0A; + video_info->max_lane_count = 0x04; + break; + case RK3576_EDP: + case RK3588_EDP: + video_info->max_link_rate = 0x14; + video_info->max_lane_count = 0x04; + break; + case EXYNOS_DP: + /* + * NOTE: those property parseing code is used for + * providing backward compatibility for samsung platform. + */ + of_property_read_u32(dp_node, "samsung,link-rate", + &video_info->max_link_rate); + of_property_read_u32(dp_node, "samsung,lane-count", + &video_info->max_lane_count); + break; + } + + return 0; +} + +static ssize_t analogix_dpaux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct analogix_dp_device *dp = to_dp(aux); + int ret; + + pm_runtime_get_sync(dp->dev); + + ret = analogix_dp_detect_hpd(dp); + if (ret) + goto out; + + ret = analogix_dp_transfer(dp, msg); +out: + pm_runtime_mark_last_busy(dp->dev); + pm_runtime_put_autosuspend(dp->dev); + + return ret; +} + +static int analogix_dpaux_wait_hpd_asserted(struct drm_dp_aux *aux, unsigned long wait_us) +{ + struct analogix_dp_device *dp = to_dp(aux); + int val; + int ret; + + if (dp->force_hpd) + return 0; + + pm_runtime_get_sync(dp->dev); + + ret = readx_poll_timeout(analogix_dp_get_plug_in_status, dp, val, !val, + wait_us / 100, wait_us); + + pm_runtime_mark_last_busy(dp->dev); + pm_runtime_put_autosuspend(dp->dev); + + return ret; +} + +struct analogix_dp_device * +analogix_dp_probe(struct device *dev, struct analogix_dp_plat_data *plat_data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct analogix_dp_device *dp; + unsigned int irq_flags; + int ret; + + if (!plat_data) { + dev_err(dev, "Invalided input plat_data\n"); + return ERR_PTR(-EINVAL); + } + + dp = devm_drm_bridge_alloc(dev, struct analogix_dp_device, bridge, + &analogix_dp_bridge_funcs); + if (IS_ERR(dp)) + return ERR_CAST(dp); + + dp->dev = &pdev->dev; + dp->dpms_mode = DRM_MODE_DPMS_OFF; + + /* + * platform dp driver need containor_of the plat_data to get + * the driver private data, so we need to store the point of + * plat_data, not the context of plat_data. + */ + dp->plat_data = plat_data; + + ret = analogix_dp_dt_parse_pdata(dp); + if (ret) + return ERR_PTR(ret); + + dp->phy = devm_phy_get(dp->dev, "dp"); + if (IS_ERR(dp->phy)) { + dev_err(dp->dev, "no DP phy configured\n"); + ret = PTR_ERR(dp->phy); + if (ret) { + /* + * phy itself is not enabled, so we can move forward + * assigning NULL to phy pointer. + */ + if (ret == -ENOSYS || ret == -ENODEV) + dp->phy = NULL; + else + return ERR_PTR(ret); + } + } + + dp->clock = devm_clk_get(&pdev->dev, "dp"); + if (IS_ERR(dp->clock)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return ERR_CAST(dp->clock); + } + + dp->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dp->reg_base)) + return ERR_CAST(dp->reg_base); + + dp->force_hpd = of_property_read_bool(dev->of_node, "force-hpd"); + + /* Try two different names */ + dp->hpd_gpiod = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN); + if (!dp->hpd_gpiod) + dp->hpd_gpiod = devm_gpiod_get_optional(dev, "samsung,hpd", + GPIOD_IN); + if (IS_ERR(dp->hpd_gpiod)) { + dev_err(dev, "error getting HDP GPIO: %ld\n", + PTR_ERR(dp->hpd_gpiod)); + return ERR_CAST(dp->hpd_gpiod); + } + + if (dp->hpd_gpiod) { + /* + * Set up the hotplug GPIO from the device tree as an interrupt. + * Simply specifying a different interrupt in the device tree + * doesn't work since we handle hotplug rather differently when + * using a GPIO. We also need the actual GPIO specifier so + * that we can get the current state of the GPIO. + */ + dp->irq = gpiod_to_irq(dp->hpd_gpiod); + irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN; + } else { + dp->irq = platform_get_irq(pdev, 0); + irq_flags = IRQF_NO_AUTOEN; + } + + if (dp->irq == -ENXIO) { + dev_err(&pdev->dev, "failed to get irq\n"); + return ERR_PTR(-ENODEV); + } + + ret = devm_request_threaded_irq(&pdev->dev, dp->irq, + analogix_dp_hardirq, + analogix_dp_irq_thread, + irq_flags, "analogix-dp", dp); + if (ret) { + dev_err(&pdev->dev, "failed to request irq\n"); + return ERR_PTR(ret); + } + + dp->aux.name = "DP-AUX"; + dp->aux.transfer = analogix_dpaux_transfer; + dp->aux.wait_hpd_asserted = analogix_dpaux_wait_hpd_asserted; + dp->aux.dev = dp->dev; + drm_dp_aux_init(&dp->aux); + + pm_runtime_use_autosuspend(dp->dev); + pm_runtime_set_autosuspend_delay(dp->dev, 100); + ret = devm_pm_runtime_enable(dp->dev); + if (ret) + return ERR_PTR(ret); + + return dp; +} +EXPORT_SYMBOL_GPL(analogix_dp_probe); + +int analogix_dp_suspend(struct analogix_dp_device *dp) +{ + phy_power_off(dp->phy); + + if (dp->plat_data->power_off) + dp->plat_data->power_off(dp->plat_data); + + clk_disable_unprepare(dp->clock); + + return 0; +} +EXPORT_SYMBOL_GPL(analogix_dp_suspend); + +int analogix_dp_resume(struct analogix_dp_device *dp) +{ + int ret; + + ret = clk_prepare_enable(dp->clock); + if (ret < 0) { + DRM_ERROR("Failed to prepare_enable the clock clk [%d]\n", ret); + return ret; + } + + if (dp->plat_data->power_on) + dp->plat_data->power_on(dp->plat_data); + + phy_set_mode(dp->phy, PHY_MODE_DP); + phy_power_on(dp->phy); + + analogix_dp_init_dp(dp); + + return 0; +} +EXPORT_SYMBOL_GPL(analogix_dp_resume); + +int analogix_dp_bind(struct analogix_dp_device *dp, struct drm_device *drm_dev) +{ + struct drm_bridge *bridge = &dp->bridge; + int ret; + + dp->drm_dev = drm_dev; + dp->encoder = dp->plat_data->encoder; + + dp->aux.drm_dev = drm_dev; + + ret = drm_dp_aux_register(&dp->aux); + if (ret) { + DRM_ERROR("failed to register AUX (%d)\n", ret); + return ret; + } + + bridge->ops = DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_DETECT; + bridge->of_node = dp->dev->of_node; + bridge->type = DRM_MODE_CONNECTOR_eDP; + ret = devm_drm_bridge_add(dp->dev, &dp->bridge); + if (ret) + goto err_unregister_aux; + + if (dp->plat_data->panel) { + dp->plat_data->next_bridge = devm_drm_panel_bridge_add(dp->dev, + dp->plat_data->panel); + if (IS_ERR(dp->plat_data->next_bridge)) { + ret = PTR_ERR(dp->plat_data->next_bridge); + goto err_unregister_aux; + } + } + + ret = drm_bridge_attach(dp->encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR); + if (ret) { + DRM_ERROR("failed to create bridge (%d)\n", ret); + goto err_unregister_aux; + } + + return 0; + +err_unregister_aux: + drm_dp_aux_unregister(&dp->aux); + + return ret; +} +EXPORT_SYMBOL_GPL(analogix_dp_bind); + +void analogix_dp_unbind(struct analogix_dp_device *dp) +{ + drm_dp_aux_unregister(&dp->aux); +} +EXPORT_SYMBOL_GPL(analogix_dp_unbind); + +int analogix_dp_start_crc(struct drm_connector *connector) +{ + struct analogix_dp_device *dp; + struct drm_bridge *bridge __free(drm_bridge_put) = + drm_bridge_chain_get_first_bridge(connector->encoder); + + if (!connector->state->crtc) { + DRM_ERROR("Connector %s doesn't currently have a CRTC.\n", + connector->name); + return -EINVAL; + } + + if (!bridge || bridge->type != DRM_MODE_CONNECTOR_eDP) + return -EINVAL; + + dp = to_dp(bridge); + + return drm_dp_start_crc(&dp->aux, connector->state->crtc); +} +EXPORT_SYMBOL_GPL(analogix_dp_start_crc); + +int analogix_dp_stop_crc(struct drm_connector *connector) +{ + struct analogix_dp_device *dp; + struct drm_bridge *bridge __free(drm_bridge_put) = + drm_bridge_chain_get_first_bridge(connector->encoder); + + if (!bridge || bridge->type != DRM_MODE_CONNECTOR_eDP) + return -EINVAL; + + dp = to_dp(bridge); + + return drm_dp_stop_crc(&dp->aux); +} +EXPORT_SYMBOL_GPL(analogix_dp_stop_crc); + +struct analogix_dp_plat_data *analogix_dp_aux_to_plat_data(struct drm_dp_aux *aux) +{ + struct analogix_dp_device *dp = to_dp(aux); + + return dp->plat_data; +} +EXPORT_SYMBOL_GPL(analogix_dp_aux_to_plat_data); + +struct drm_dp_aux *analogix_dp_get_aux(struct analogix_dp_device *dp) +{ + return &dp->aux; +} +EXPORT_SYMBOL_GPL(analogix_dp_get_aux); + +static int analogix_dp_aux_done_probing(struct drm_dp_aux *aux) +{ + struct analogix_dp_device *dp = to_dp(aux); + struct analogix_dp_plat_data *plat_data = dp->plat_data; + int port = plat_data->dev_type == EXYNOS_DP ? 0 : 1; + int ret; + + /* + * If drm_of_find_panel_or_bridge() returns -ENODEV, there may be no valid panel + * or bridge nodes. The driver should go on for the driver-free bridge or the DP + * mode applications. + */ + ret = drm_of_find_panel_or_bridge(dp->dev->of_node, port, 0, + &plat_data->panel, &plat_data->next_bridge); + if (ret && ret != -ENODEV) + return ret; + + return component_add(dp->dev, plat_data->ops); +} + +int analogix_dp_finish_probe(struct analogix_dp_device *dp) +{ + int ret; + + ret = devm_of_dp_aux_populate_bus(&dp->aux, analogix_dp_aux_done_probing); + if (ret) { + /* + * If devm_of_dp_aux_populate_bus() returns -ENODEV, the done_probing() will + * not be called because there are no EP devices. Then the callback function + * analogix_dp_aux_done_probing() will be called directly in order to support + * the other valid DT configurations. + * + * NOTE: The devm_of_dp_aux_populate_bus() is allowed to return -EPROBE_DEFER. + */ + if (ret != -ENODEV) + return dev_err_probe(dp->dev, ret, "failed to populate aux bus\n"); + + return analogix_dp_aux_done_probing(&dp->aux); + } + + return 0; +} +EXPORT_SYMBOL_GPL(analogix_dp_finish_probe); + +MODULE_AUTHOR("Jingoo Han "); +MODULE_DESCRIPTION("Analogix DP Core Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h new file mode 100644 index 000000000..c7997677a --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Header file for Analogix DP (Display Port) core interface driver. + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han + */ + +#ifndef _ANALOGIX_DP_CORE_H +#define _ANALOGIX_DP_CORE_H + +#include +#include +#include + +#define DP_TIMEOUT_LOOP_COUNT 100 +#define MAX_CR_LOOP 5 +#define MAX_EQ_LOOP 5 +#define MAX_PLL_LOCK_LOOP 5 + +/* Training takes 22ms if AUX channel comm fails. Use this as retry interval */ +#define DP_TIMEOUT_TRAINING_US 22000 +#define DP_TIMEOUT_PSR_LOOP_MS 300 + +/* DP_MAX_LANE_COUNT */ +#define DPCD_ENHANCED_FRAME_CAP(x) (((x) >> 7) & 0x1) +#define DPCD_MAX_LANE_COUNT(x) ((x) & 0x1f) + +/* DP_LANE_COUNT_SET */ +#define DPCD_LANE_COUNT_SET(x) ((x) & 0x1f) + +/* DP_TRAINING_LANE0_SET */ +#define DPCD_PRE_EMPHASIS_SET(x) (((x) & 0x3) << 3) +#define DPCD_PRE_EMPHASIS_GET(x) (((x) >> 3) & 0x3) +#define DPCD_VOLTAGE_SWING_SET(x) (((x) & 0x3) << 0) +#define DPCD_VOLTAGE_SWING_GET(x) (((x) >> 0) & 0x3) + +struct gpio_desc; + +enum link_lane_count_type { + LANE_COUNT1 = 1, + LANE_COUNT2 = 2, + LANE_COUNT4 = 4 +}; + +enum link_training_state { + START, + CLOCK_RECOVERY, + EQUALIZER_TRAINING, + FINISHED, + FAILED +}; + +enum voltage_swing_level { + VOLTAGE_LEVEL_0, + VOLTAGE_LEVEL_1, + VOLTAGE_LEVEL_2, + VOLTAGE_LEVEL_3, +}; + +enum pre_emphasis_level { + PRE_EMPHASIS_LEVEL_0, + PRE_EMPHASIS_LEVEL_1, + PRE_EMPHASIS_LEVEL_2, + PRE_EMPHASIS_LEVEL_3, +}; + +enum pattern_set { + PRBS7, + D10_2, + TRAINING_PTN1, + TRAINING_PTN2, + DP_NONE +}; + +enum color_space { + COLOR_RGB, + COLOR_YCBCR422, + COLOR_YCBCR444 +}; + +enum color_depth { + COLOR_6, + COLOR_8, + COLOR_10, + COLOR_12 +}; + +enum color_coefficient { + COLOR_YCBCR601, + COLOR_YCBCR709 +}; + +enum dynamic_range { + VESA, + CEA +}; + +enum clock_recovery_m_value_type { + CALCULATED_M, + REGISTER_M +}; + +enum video_timing_recognition_type { + VIDEO_TIMING_FROM_CAPTURE, + VIDEO_TIMING_FROM_REGISTER +}; + +enum analog_power_block { + AUX_BLOCK, + CH0_BLOCK, + CH1_BLOCK, + CH2_BLOCK, + CH3_BLOCK, + ANALOG_TOTAL, + POWER_ALL +}; + +enum dp_irq_type { + DP_IRQ_TYPE_HP_CABLE_IN = BIT(0), + DP_IRQ_TYPE_HP_CABLE_OUT = BIT(1), + DP_IRQ_TYPE_HP_CHANGE = BIT(2), + DP_IRQ_TYPE_UNKNOWN = BIT(3), +}; + +struct video_info { + char *name; + + bool h_sync_polarity; + bool v_sync_polarity; + bool interlaced; + + enum color_space color_space; + enum dynamic_range dynamic_range; + enum color_coefficient ycbcr_coeff; + enum color_depth color_depth; + + int max_link_rate; + enum link_lane_count_type max_lane_count; +}; + +struct link_train { + int eq_loop; + int cr_loop[4]; + + u8 link_rate; + u8 lane_count; + u8 training_lane[4]; + + enum link_training_state lt_state; +}; + +struct analogix_dp_device { + struct drm_encoder *encoder; + struct device *dev; + struct drm_device *drm_dev; + struct drm_bridge bridge; + struct drm_dp_aux aux; + struct clk *clock; + unsigned int irq; + void __iomem *reg_base; + + struct video_info video_info; + struct link_train link_train; + struct phy *phy; + int dpms_mode; + struct gpio_desc *hpd_gpiod; + bool force_hpd; + bool fast_train_enable; + bool psr_supported; + + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + + struct analogix_dp_plat_data *plat_data; +}; + +/* analogix_dp_reg.c */ +void analogix_dp_enable_video_mute(struct analogix_dp_device *dp, bool enable); +void analogix_dp_stop_video(struct analogix_dp_device *dp); +void analogix_dp_lane_swap(struct analogix_dp_device *dp, bool enable); +void analogix_dp_init_analog_param(struct analogix_dp_device *dp); +void analogix_dp_init_interrupt(struct analogix_dp_device *dp); +void analogix_dp_reset(struct analogix_dp_device *dp); +void analogix_dp_swreset(struct analogix_dp_device *dp); +void analogix_dp_config_interrupt(struct analogix_dp_device *dp); +void analogix_dp_mute_hpd_interrupt(struct analogix_dp_device *dp); +void analogix_dp_unmute_hpd_interrupt(struct analogix_dp_device *dp); +int analogix_dp_wait_pll_locked(struct analogix_dp_device *dp); +void analogix_dp_set_pll_power_down(struct analogix_dp_device *dp, bool enable); +void analogix_dp_set_analog_power_down(struct analogix_dp_device *dp, + enum analog_power_block block, + bool enable); +int analogix_dp_init_analog_func(struct analogix_dp_device *dp); +void analogix_dp_init_hpd(struct analogix_dp_device *dp); +void analogix_dp_force_hpd(struct analogix_dp_device *dp); +enum dp_irq_type analogix_dp_get_irq_type(struct analogix_dp_device *dp); +void analogix_dp_clear_hotplug_interrupts(struct analogix_dp_device *dp); +void analogix_dp_reset_aux(struct analogix_dp_device *dp); +void analogix_dp_init_aux(struct analogix_dp_device *dp); +int analogix_dp_get_plug_in_status(struct analogix_dp_device *dp); +void analogix_dp_enable_sw_function(struct analogix_dp_device *dp); +void analogix_dp_set_link_bandwidth(struct analogix_dp_device *dp, u32 bwtype); +void analogix_dp_get_link_bandwidth(struct analogix_dp_device *dp, u32 *bwtype); +void analogix_dp_set_lane_count(struct analogix_dp_device *dp, u32 count); +void analogix_dp_get_lane_count(struct analogix_dp_device *dp, u32 *count); +void analogix_dp_enable_enhanced_mode(struct analogix_dp_device *dp, + bool enable); +void analogix_dp_set_training_pattern(struct analogix_dp_device *dp, + enum pattern_set pattern); +void analogix_dp_set_lane_link_training(struct analogix_dp_device *dp); +u32 analogix_dp_get_lane_link_training(struct analogix_dp_device *dp, u8 lane); +void analogix_dp_reset_macro(struct analogix_dp_device *dp); +void analogix_dp_init_video(struct analogix_dp_device *dp); + +void analogix_dp_set_video_color_format(struct analogix_dp_device *dp); +bool analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp); +void analogix_dp_set_video_cr_mn(struct analogix_dp_device *dp, + enum clock_recovery_m_value_type type, + u32 m_value, + u32 n_value); +void analogix_dp_set_video_timing_mode(struct analogix_dp_device *dp, u32 type); +void analogix_dp_enable_video_master(struct analogix_dp_device *dp, + bool enable); +void analogix_dp_start_video(struct analogix_dp_device *dp); +bool analogix_dp_is_video_stream_on(struct analogix_dp_device *dp); +void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp); +void analogix_dp_enable_scrambling(struct analogix_dp_device *dp); +void analogix_dp_disable_scrambling(struct analogix_dp_device *dp); +void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp); +int analogix_dp_send_psr_spd(struct analogix_dp_device *dp, + struct dp_sdp *vsc, bool blocking); +ssize_t analogix_dp_transfer(struct analogix_dp_device *dp, + struct drm_dp_aux_msg *msg); + +#endif /* _ANALOGIX_DP_CORE_H */ diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c new file mode 100644 index 000000000..ea8401293 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c @@ -0,0 +1,1107 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Analogix DP (Display port) core register interface driver. + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "analogix_dp_core.h" +#include "analogix_dp_reg.h" + +#define COMMON_INT_MASK_1 0 +#define COMMON_INT_MASK_2 0 +#define COMMON_INT_MASK_3 0 +#define COMMON_INT_MASK_4 (HOTPLUG_CHG | HPD_LOST | PLUG) +#define INT_STA_MASK INT_HPD + +void analogix_dp_enable_video_mute(struct analogix_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + reg |= HDCP_VIDEO_MUTE; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + reg &= ~HDCP_VIDEO_MUTE; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + } +} + +void analogix_dp_stop_video(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + reg &= ~VIDEO_EN; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); +} + +void analogix_dp_lane_swap(struct analogix_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) + reg = LANE3_MAP_LOGIC_LANE_0 | LANE2_MAP_LOGIC_LANE_1 | + LANE1_MAP_LOGIC_LANE_2 | LANE0_MAP_LOGIC_LANE_3; + else + reg = LANE3_MAP_LOGIC_LANE_3 | LANE2_MAP_LOGIC_LANE_2 | + LANE1_MAP_LOGIC_LANE_1 | LANE0_MAP_LOGIC_LANE_0; + + writel(reg, dp->reg_base + ANALOGIX_DP_LANE_MAP); +} + +void analogix_dp_init_analog_param(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = TX_TERMINAL_CTRL_50_OHM; + writel(reg, dp->reg_base + ANALOGIX_DP_ANALOG_CTL_1); + + reg = SEL_24M | TX_DVDD_BIT_1_0625V; + writel(reg, dp->reg_base + ANALOGIX_DP_ANALOG_CTL_2); + + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) { + reg = REF_CLK_24M; + if (dp->plat_data->dev_type == RK3288_DP) + reg ^= REF_CLK_MASK; + + writel(reg, dp->reg_base + ANALOGIX_DP_PLL_REG_1); + writel(0x95, dp->reg_base + ANALOGIX_DP_PLL_REG_2); + writel(0x40, dp->reg_base + ANALOGIX_DP_PLL_REG_3); + writel(0x58, dp->reg_base + ANALOGIX_DP_PLL_REG_4); + writel(0x22, dp->reg_base + ANALOGIX_DP_PLL_REG_5); + } + + reg = DRIVE_DVDD_BIT_1_0625V | VCO_BIT_600_MICRO; + writel(reg, dp->reg_base + ANALOGIX_DP_ANALOG_CTL_3); + + reg = PD_RING_OSC | AUX_TERMINAL_CTRL_50_OHM | + TX_CUR1_2X | TX_CUR_16_MA; + writel(reg, dp->reg_base + ANALOGIX_DP_PLL_FILTER_CTL_1); + + reg = CH3_AMP_400_MV | CH2_AMP_400_MV | + CH1_AMP_400_MV | CH0_AMP_400_MV; + writel(reg, dp->reg_base + ANALOGIX_DP_TX_AMP_TUNING_CTL); +} + +void analogix_dp_init_interrupt(struct analogix_dp_device *dp) +{ + /* Set interrupt pin assertion polarity as high */ + writel(INT_POL1 | INT_POL0, dp->reg_base + ANALOGIX_DP_INT_CTL); + + /* Clear pending regisers */ + writel(0xff, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_1); + writel(0x4f, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_2); + writel(0xe0, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_3); + writel(0xe7, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_4); + writel(0x63, dp->reg_base + ANALOGIX_DP_INT_STA); + + /* 0:mask,1: unmask */ + writel(0x00, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_1); + writel(0x00, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_2); + writel(0x00, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_3); + writel(0x00, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + writel(0x00, dp->reg_base + ANALOGIX_DP_INT_STA_MASK); +} + +void analogix_dp_reset(struct analogix_dp_device *dp) +{ + u32 reg; + + analogix_dp_stop_video(dp); + analogix_dp_enable_video_mute(dp, 0); + + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) + reg = RK_VID_CAP_FUNC_EN_N | RK_VID_FIFO_FUNC_EN_N | + SW_FUNC_EN_N; + else + reg = MASTER_VID_FUNC_EN_N | SLAVE_VID_FUNC_EN_N | + AUD_FIFO_FUNC_EN_N | AUD_FUNC_EN_N | + HDCP_FUNC_EN_N | SW_FUNC_EN_N; + + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_1); + + reg = SSC_FUNC_EN_N | AUX_FUNC_EN_N | + SERDES_FIFO_FUNC_EN_N | + LS_CLK_DOMAIN_FUNC_EN_N; + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_2); + + usleep_range(20, 30); + + analogix_dp_lane_swap(dp, 0); + + writel(0x0, dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + writel(0x40, dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + writel(0x0, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + writel(0x0, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + + writel(0x0, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + writel(0x0, dp->reg_base + ANALOGIX_DP_HDCP_CTL); + + writel(0x5e, dp->reg_base + ANALOGIX_DP_HPD_DEGLITCH_L); + writel(0x1a, dp->reg_base + ANALOGIX_DP_HPD_DEGLITCH_H); + + writel(0x10, dp->reg_base + ANALOGIX_DP_LINK_DEBUG_CTL); + + writel(0x0, dp->reg_base + ANALOGIX_DP_PHY_TEST); + + writel(0x0, dp->reg_base + ANALOGIX_DP_VIDEO_FIFO_THRD); + writel(0x20, dp->reg_base + ANALOGIX_DP_AUDIO_MARGIN); + + writel(0x4, dp->reg_base + ANALOGIX_DP_M_VID_GEN_FILTER_TH); + writel(0x2, dp->reg_base + ANALOGIX_DP_M_AUD_GEN_FILTER_TH); + + writel(0x00000101, dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); +} + +void analogix_dp_swreset(struct analogix_dp_device *dp) +{ + writel(RESET_DP_TX, dp->reg_base + ANALOGIX_DP_TX_SW_RESET); +} + +void analogix_dp_config_interrupt(struct analogix_dp_device *dp) +{ + u32 reg; + + /* 0: mask, 1: unmask */ + reg = COMMON_INT_MASK_1; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_1); + + reg = COMMON_INT_MASK_2; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_2); + + reg = COMMON_INT_MASK_3; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_3); + + reg = COMMON_INT_MASK_4; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + + reg = INT_STA_MASK; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA_MASK); +} + +void analogix_dp_mute_hpd_interrupt(struct analogix_dp_device *dp) +{ + u32 reg; + + /* 0: mask, 1: unmask */ + reg = readl(dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + reg &= ~COMMON_INT_MASK_4; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + + reg = readl(dp->reg_base + ANALOGIX_DP_INT_STA_MASK); + reg &= ~INT_STA_MASK; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA_MASK); +} + +void analogix_dp_unmute_hpd_interrupt(struct analogix_dp_device *dp) +{ + u32 reg; + + /* 0: mask, 1: unmask */ + reg = COMMON_INT_MASK_4; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + + reg = INT_STA_MASK; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA_MASK); +} + +int analogix_dp_wait_pll_locked(struct analogix_dp_device *dp) +{ + u32 val; + + return readl_poll_timeout(dp->reg_base + ANALOGIX_DP_DEBUG_CTL, val, + val & PLL_LOCK, 120, + 120 * DP_TIMEOUT_LOOP_COUNT); +} + +void analogix_dp_set_pll_power_down(struct analogix_dp_device *dp, bool enable) +{ + u32 reg; + u32 mask = DP_PLL_PD; + u32 pd_addr = ANALOGIX_DP_PLL_CTL; + + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) { + pd_addr = ANALOGIX_DP_PD; + mask = RK_PLL_PD; + } + + reg = readl(dp->reg_base + pd_addr); + if (enable) + reg |= mask; + else + reg &= ~mask; + writel(reg, dp->reg_base + pd_addr); +} + +void analogix_dp_set_analog_power_down(struct analogix_dp_device *dp, + enum analog_power_block block, + bool enable) +{ + u32 reg; + u32 phy_pd_addr = ANALOGIX_DP_PHY_PD; + u32 mask; + + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) + phy_pd_addr = ANALOGIX_DP_PD; + + switch (block) { + case AUX_BLOCK: + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) + mask = RK_AUX_PD; + else + mask = AUX_PD; + + reg = readl(dp->reg_base + phy_pd_addr); + if (enable) + reg |= mask; + else + reg &= ~mask; + writel(reg, dp->reg_base + phy_pd_addr); + break; + case CH0_BLOCK: + mask = CH0_PD; + reg = readl(dp->reg_base + phy_pd_addr); + + if (enable) + reg |= mask; + else + reg &= ~mask; + writel(reg, dp->reg_base + phy_pd_addr); + break; + case CH1_BLOCK: + mask = CH1_PD; + reg = readl(dp->reg_base + phy_pd_addr); + + if (enable) + reg |= mask; + else + reg &= ~mask; + writel(reg, dp->reg_base + phy_pd_addr); + break; + case CH2_BLOCK: + mask = CH2_PD; + reg = readl(dp->reg_base + phy_pd_addr); + + if (enable) + reg |= mask; + else + reg &= ~mask; + writel(reg, dp->reg_base + phy_pd_addr); + break; + case CH3_BLOCK: + mask = CH3_PD; + reg = readl(dp->reg_base + phy_pd_addr); + + if (enable) + reg |= mask; + else + reg &= ~mask; + writel(reg, dp->reg_base + phy_pd_addr); + break; + case ANALOG_TOTAL: + /* + * There is no bit named DP_PHY_PD, so We used DP_INC_BG + * to power off everything instead of DP_PHY_PD in + * Rockchip + */ + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) + mask = DP_INC_BG; + else + mask = DP_PHY_PD; + + reg = readl(dp->reg_base + phy_pd_addr); + if (enable) + reg |= mask; + else + reg &= ~mask; + + writel(reg, dp->reg_base + phy_pd_addr); + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) + usleep_range(10, 15); + break; + case POWER_ALL: + if (enable) { + reg = DP_ALL_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } else { + reg = DP_ALL_PD; + writel(reg, dp->reg_base + phy_pd_addr); + usleep_range(10, 15); + reg &= ~DP_INC_BG; + writel(reg, dp->reg_base + phy_pd_addr); + usleep_range(10, 15); + + writel(0x00, dp->reg_base + phy_pd_addr); + } + break; + default: + break; + } +} + +int analogix_dp_init_analog_func(struct analogix_dp_device *dp) +{ + u32 reg; + + analogix_dp_set_analog_power_down(dp, POWER_ALL, 0); + + reg = PLL_LOCK_CHG; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_1); + + reg = readl(dp->reg_base + ANALOGIX_DP_DEBUG_CTL); + reg &= ~(F_PLL_LOCK | PLL_LOCK_CTRL); + writel(reg, dp->reg_base + ANALOGIX_DP_DEBUG_CTL); + + /* Power up PLL */ + analogix_dp_set_pll_power_down(dp, 0); + + /* Enable Serdes FIFO function and Link symbol clock domain module */ + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_2); + reg &= ~(SERDES_FIFO_FUNC_EN_N | LS_CLK_DOMAIN_FUNC_EN_N + | AUX_FUNC_EN_N); + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_2); + return 0; +} + +void analogix_dp_clear_hotplug_interrupts(struct analogix_dp_device *dp) +{ + u32 reg; + + if (dp->hpd_gpiod) + return; + + reg = HOTPLUG_CHG | HPD_LOST | PLUG; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_4); + + reg = INT_HPD; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA); +} + +void analogix_dp_init_hpd(struct analogix_dp_device *dp) +{ + u32 reg; + + if (dp->hpd_gpiod) + return; + + analogix_dp_clear_hotplug_interrupts(dp); + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + reg &= ~(F_HPD | HPD_CTRL); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); +} + +void analogix_dp_force_hpd(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + reg = (F_HPD | HPD_CTRL); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); +} + +enum dp_irq_type analogix_dp_get_irq_type(struct analogix_dp_device *dp) +{ + u32 reg; + + if (dp->hpd_gpiod) { + reg = gpiod_get_value(dp->hpd_gpiod); + if (reg) + return DP_IRQ_TYPE_HP_CABLE_IN; + else + return DP_IRQ_TYPE_HP_CABLE_OUT; + } else { + /* Parse hotplug interrupt status register */ + reg = readl(dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_4); + + if (reg & PLUG) + return DP_IRQ_TYPE_HP_CABLE_IN; + + if (reg & HPD_LOST) + return DP_IRQ_TYPE_HP_CABLE_OUT; + + if (reg & HOTPLUG_CHG) + return DP_IRQ_TYPE_HP_CHANGE; + + return DP_IRQ_TYPE_UNKNOWN; + } +} + +void analogix_dp_reset_aux(struct analogix_dp_device *dp) +{ + u32 reg; + + /* Disable AUX channel module */ + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_2); + reg |= AUX_FUNC_EN_N; + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_2); +} + +void analogix_dp_init_aux(struct analogix_dp_device *dp) +{ + u32 reg; + + /* Clear inerrupts related to AUX channel */ + reg = RPLY_RECEIV | AUX_ERR; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA); + + analogix_dp_set_analog_power_down(dp, AUX_BLOCK, true); + usleep_range(10, 11); + analogix_dp_set_analog_power_down(dp, AUX_BLOCK, false); + + analogix_dp_reset_aux(dp); + + /* AUX_BIT_PERIOD_EXPECTED_DELAY doesn't apply to Rockchip IP */ + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) + reg = 0; + else + reg = AUX_BIT_PERIOD_EXPECTED_DELAY(3); + + /* Disable AUX transaction H/W retry */ + reg |= AUX_HW_RETRY_COUNT_SEL(0) | + AUX_HW_RETRY_INTERVAL_600_MICROSECONDS; + + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_HW_RETRY_CTL); + + /* Receive AUX Channel DEFER commands equal to DEFFER_COUNT*64 */ + reg = DEFER_CTRL_EN | DEFER_COUNT(1); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_DEFER_CTL); + + /* Enable AUX channel module */ + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_2); + reg &= ~AUX_FUNC_EN_N; + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_2); +} + +int analogix_dp_get_plug_in_status(struct analogix_dp_device *dp) +{ + u32 reg; + + if (dp->hpd_gpiod) { + if (gpiod_get_value(dp->hpd_gpiod)) + return 0; + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + if (reg & HPD_STATUS) + return 0; + } + + return -EINVAL; +} + +void analogix_dp_enable_sw_function(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_1); + reg &= ~SW_FUNC_EN_N; + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_1); +} + +void analogix_dp_set_link_bandwidth(struct analogix_dp_device *dp, u32 bwtype) +{ + u32 reg; + int ret; + + reg = bwtype; + if ((bwtype == DP_LINK_BW_2_7) || (bwtype == DP_LINK_BW_1_62)) + writel(reg, dp->reg_base + ANALOGIX_DP_LINK_BW_SET); + + if (dp->phy) { + union phy_configure_opts phy_cfg = {0}; + + phy_cfg.dp.link_rate = + drm_dp_bw_code_to_link_rate(dp->link_train.link_rate) / 100; + phy_cfg.dp.set_rate = true; + ret = phy_configure(dp->phy, &phy_cfg); + if (ret && ret != -EOPNOTSUPP) { + dev_err(dp->dev, "%s: phy_configure() failed: %d\n", __func__, ret); + return; + } + } +} + +void analogix_dp_get_link_bandwidth(struct analogix_dp_device *dp, u32 *bwtype) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LINK_BW_SET); + *bwtype = reg; +} + +void analogix_dp_set_lane_count(struct analogix_dp_device *dp, u32 count) +{ + u32 reg; + int ret; + + reg = count; + writel(reg, dp->reg_base + ANALOGIX_DP_LANE_COUNT_SET); + + if (dp->phy) { + union phy_configure_opts phy_cfg = {0}; + + phy_cfg.dp.lanes = dp->link_train.lane_count; + phy_cfg.dp.set_lanes = true; + ret = phy_configure(dp->phy, &phy_cfg); + if (ret && ret != -EOPNOTSUPP) { + dev_err(dp->dev, "%s: phy_configure() failed: %d\n", __func__, ret); + return; + } + } +} + +void analogix_dp_get_lane_count(struct analogix_dp_device *dp, u32 *count) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LANE_COUNT_SET); + *count = reg; +} + +void analogix_dp_set_lane_link_training(struct analogix_dp_device *dp) +{ + u8 lane; + int ret; + + for (lane = 0; lane < dp->link_train.lane_count; lane++) + writel(dp->link_train.training_lane[lane], + dp->reg_base + ANALOGIX_DP_LN0_LINK_TRAINING_CTL + 4 * lane); + + if (dp->phy) { + union phy_configure_opts phy_cfg = {0}; + + for (lane = 0; lane < dp->link_train.lane_count; lane++) { + u8 training_lane = dp->link_train.training_lane[lane]; + u8 vs, pe; + + vs = (training_lane & DP_TRAIN_VOLTAGE_SWING_MASK) >> + DP_TRAIN_VOLTAGE_SWING_SHIFT; + pe = (training_lane & DP_TRAIN_PRE_EMPHASIS_MASK) >> + DP_TRAIN_PRE_EMPHASIS_SHIFT; + phy_cfg.dp.voltage[lane] = vs; + phy_cfg.dp.pre[lane] = pe; + } + + phy_cfg.dp.set_voltages = true; + ret = phy_configure(dp->phy, &phy_cfg); + if (ret && ret != -EOPNOTSUPP) { + dev_err(dp->dev, "%s: phy_configure() failed: %d\n", __func__, ret); + return; + } + } +} + +u32 analogix_dp_get_lane_link_training(struct analogix_dp_device *dp, u8 lane) +{ + return readl(dp->reg_base + ANALOGIX_DP_LN0_LINK_TRAINING_CTL + 4 * lane); +} + +void analogix_dp_enable_enhanced_mode(struct analogix_dp_device *dp, + bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg |= ENHANCED; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg &= ~ENHANCED; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + } +} + +void analogix_dp_set_training_pattern(struct analogix_dp_device *dp, + enum pattern_set pattern) +{ + u32 reg; + + switch (pattern) { + case PRBS7: + reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_PRBS7; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + case D10_2: + reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_D10_2; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + case TRAINING_PTN1: + reg = SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN1; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + case TRAINING_PTN2: + reg = SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN2; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + case DP_NONE: + reg = SCRAMBLING_ENABLE | + LINK_QUAL_PATTERN_SET_DISABLE | + SW_TRAINING_PATTERN_SET_NORMAL; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + default: + break; + } +} + +void analogix_dp_reset_macro(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_PHY_TEST); + reg |= MACRO_RST; + writel(reg, dp->reg_base + ANALOGIX_DP_PHY_TEST); + + /* 10 us is the minimum reset time. */ + usleep_range(10, 20); + + reg &= ~MACRO_RST; + writel(reg, dp->reg_base + ANALOGIX_DP_PHY_TEST); +} + +void analogix_dp_init_video(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = VSYNC_DET | VID_FORMAT_CHG | VID_CLK_CHG; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_1); + + reg = 0x0; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + + reg = CHA_CRI(4) | CHA_CTRL; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + + reg = 0x0; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + + reg = VID_HRES_TH(2) | VID_VRES_TH(0); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_8); +} + +void analogix_dp_set_video_color_format(struct analogix_dp_device *dp) +{ + u32 reg; + + /* Configure the input color depth, color space, dynamic range */ + reg = (dp->video_info.dynamic_range << IN_D_RANGE_SHIFT) | + (dp->video_info.color_depth << IN_BPC_SHIFT) | + (dp->video_info.color_space << IN_COLOR_F_SHIFT); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_2); + + /* Set Input Color YCbCr Coefficients to ITU601 or ITU709 */ + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3); + reg &= ~IN_YC_COEFFI_MASK; + if (dp->video_info.ycbcr_coeff) + reg |= IN_YC_COEFFI_ITU709; + else + reg |= IN_YC_COEFFI_ITU601; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3); +} + +bool analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + + if (!(reg & DET_STA)) { + dev_dbg(dp->dev, "Input stream clock not detected.\n"); + return false; + } + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + dev_dbg(dp->dev, "wait SYS_CTL_2.\n"); + + if (reg & CHA_STA) { + dev_dbg(dp->dev, "Input stream clk is changing\n"); + return false; + } + + return true; +} + +void analogix_dp_set_video_cr_mn(struct analogix_dp_device *dp, + enum clock_recovery_m_value_type type, + u32 m_value, u32 n_value) +{ + u32 reg; + + if (type == REGISTER_M) { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg |= FIX_M_VID; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg = m_value & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_M_VID_0); + reg = (m_value >> 8) & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_M_VID_1); + reg = (m_value >> 16) & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_M_VID_2); + + reg = n_value & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_N_VID_0); + reg = (n_value >> 8) & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_N_VID_1); + reg = (n_value >> 16) & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_N_VID_2); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg &= ~FIX_M_VID; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + + writel(0x00, dp->reg_base + ANALOGIX_DP_N_VID_0); + writel(0x80, dp->reg_base + ANALOGIX_DP_N_VID_1); + writel(0x00, dp->reg_base + ANALOGIX_DP_N_VID_2); + } +} + +void analogix_dp_set_video_timing_mode(struct analogix_dp_device *dp, u32 type) +{ + u32 reg; + + if (type == VIDEO_TIMING_FROM_CAPTURE) { + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg &= ~FORMAT_SEL; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg |= FORMAT_SEL; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + } +} + +void analogix_dp_enable_video_master(struct analogix_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); + reg &= ~VIDEO_MODE_MASK; + reg |= VIDEO_MASTER_MODE_EN | VIDEO_MODE_MASTER_MODE; + writel(reg, dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); + reg &= ~VIDEO_MODE_MASK; + reg |= VIDEO_MODE_SLAVE_MODE; + writel(reg, dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); + } +} + +void analogix_dp_start_video(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + reg |= VIDEO_EN; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); +} + +bool analogix_dp_is_video_stream_on(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + if (!(reg & STRM_VALID)) { + dev_dbg(dp->dev, "Input video stream is not detected.\n"); + return false; + } + + return true; +} + +void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_1); + if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) { + reg &= ~(RK_VID_CAP_FUNC_EN_N | RK_VID_FIFO_FUNC_EN_N); + } else { + reg &= ~(MASTER_VID_FUNC_EN_N | SLAVE_VID_FUNC_EN_N); + reg |= MASTER_VID_FUNC_EN_N; + } + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_1); + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg &= ~INTERACE_SCAN_CFG; + reg |= (dp->video_info.interlaced << 2); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg &= ~VSYNC_POLARITY_CFG; + reg |= (dp->video_info.v_sync_polarity << 1); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg &= ~HSYNC_POLARITY_CFG; + reg |= (dp->video_info.h_sync_polarity << 0); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + + reg = AUDIO_MODE_SPDIF_MODE | VIDEO_MODE_SLAVE_MODE; + writel(reg, dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); +} + +void analogix_dp_enable_scrambling(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + reg &= ~SCRAMBLING_DISABLE; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); +} + +void analogix_dp_disable_scrambling(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + reg |= SCRAMBLING_DISABLE; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); +} + +void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp) +{ + writel(PSR_VID_CRC_ENABLE, dp->reg_base + ANALOGIX_DP_CRC_CON); +} + +static ssize_t analogix_dp_get_psr_status(struct analogix_dp_device *dp) +{ + ssize_t val; + u8 status; + + val = drm_dp_dpcd_readb(&dp->aux, DP_PSR_STATUS, &status); + if (val < 0) { + dev_err(dp->dev, "PSR_STATUS read failed ret=%zd", val); + return val; + } + return status; +} + +int analogix_dp_send_psr_spd(struct analogix_dp_device *dp, + struct dp_sdp *vsc, bool blocking) +{ + unsigned int val; + int ret; + ssize_t psr_status; + + /* don't send info frame */ + val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + val &= ~IF_EN; + writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + + /* configure single frame update mode */ + writel(PSR_FRAME_UP_TYPE_BURST | PSR_CRC_SEL_HARDWARE, + dp->reg_base + ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL); + + /* configure VSC HB0~HB3 */ + writel(vsc->sdp_header.HB0, dp->reg_base + ANALOGIX_DP_SPD_HB0); + writel(vsc->sdp_header.HB1, dp->reg_base + ANALOGIX_DP_SPD_HB1); + writel(vsc->sdp_header.HB2, dp->reg_base + ANALOGIX_DP_SPD_HB2); + writel(vsc->sdp_header.HB3, dp->reg_base + ANALOGIX_DP_SPD_HB3); + + /* configure reused VSC PB0~PB3, magic number from vendor */ + writel(0x00, dp->reg_base + ANALOGIX_DP_SPD_PB0); + writel(0x16, dp->reg_base + ANALOGIX_DP_SPD_PB1); + writel(0xCE, dp->reg_base + ANALOGIX_DP_SPD_PB2); + writel(0x5D, dp->reg_base + ANALOGIX_DP_SPD_PB3); + + /* configure DB0 / DB1 values */ + writel(vsc->db[0], dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB0); + writel(vsc->db[1], dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB1); + + /* set reuse spd inforframe */ + val = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3); + val |= REUSE_SPD_EN; + writel(val, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3); + + /* mark info frame update */ + val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + val = (val | IF_UP) & ~IF_EN; + writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + + /* send info frame */ + val = readl(dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + val |= IF_EN; + writel(val, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + + if (!blocking) + return 0; + + /* + * db[1]!=0: entering PSR, wait for fully active remote frame buffer. + * db[1]==0: exiting PSR, wait for either + * (a) ACTIVE_RESYNC - the sink "must display the + * incoming active frames from the Source device with no visible + * glitches and/or artifacts", even though timings may still be + * re-synchronizing; or + * (b) INACTIVE - the transition is fully complete. + */ + ret = readx_poll_timeout(analogix_dp_get_psr_status, dp, psr_status, + psr_status >= 0 && + ((vsc->db[1] && psr_status == DP_PSR_SINK_ACTIVE_RFB) || + (!vsc->db[1] && (psr_status == DP_PSR_SINK_ACTIVE_RESYNC || + psr_status == DP_PSR_SINK_INACTIVE))), + 1500, DP_TIMEOUT_PSR_LOOP_MS * 1000); + if (ret) { + dev_warn(dp->dev, "Failed to apply PSR %d\n", ret); + return ret; + } + return 0; +} + +ssize_t analogix_dp_transfer(struct analogix_dp_device *dp, + struct drm_dp_aux_msg *msg) +{ + u32 reg; + u8 *buffer = msg->buffer; + unsigned int i; + int ret; + + /* Buffer size of AUX CH is 16 bytes */ + if (WARN_ON(msg->size > 16)) + return -E2BIG; + + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + ANALOGIX_DP_BUFFER_DATA_CTL); + + switch (msg->request & ~DP_AUX_I2C_MOT) { + case DP_AUX_I2C_WRITE: + reg = AUX_TX_COMM_WRITE | AUX_TX_COMM_I2C_TRANSACTION; + if (msg->request & DP_AUX_I2C_MOT) + reg |= AUX_TX_COMM_MOT; + break; + + case DP_AUX_I2C_READ: + reg = AUX_TX_COMM_READ | AUX_TX_COMM_I2C_TRANSACTION; + if (msg->request & DP_AUX_I2C_MOT) + reg |= AUX_TX_COMM_MOT; + break; + + case DP_AUX_NATIVE_WRITE: + reg = AUX_TX_COMM_WRITE | AUX_TX_COMM_DP_TRANSACTION; + break; + + case DP_AUX_NATIVE_READ: + reg = AUX_TX_COMM_READ | AUX_TX_COMM_DP_TRANSACTION; + break; + + default: + return -EINVAL; + } + + reg |= AUX_LENGTH(msg->size); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_1); + + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(msg->address); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(msg->address); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(msg->address); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_19_16); + + if (!(msg->request & DP_AUX_I2C_READ)) { + for (i = 0; i < msg->size; i++) { + reg = buffer[i]; + writel(reg, dp->reg_base + ANALOGIX_DP_BUF_DATA_0 + + 4 * i); + } + } + + /* Enable AUX CH operation */ + reg = AUX_EN; + + /* Zero-sized messages specify address-only transactions. */ + if (msg->size < 1) + reg |= ADDR_ONLY; + + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_2); + + ret = readx_poll_timeout(readl, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_2, + reg, !(reg & AUX_EN), 25, 500 * 1000); + if (ret) { + dev_err(dp->dev, "AUX CH enable timeout!\n"); + goto aux_error; + } + + /* TODO: Wait for an interrupt instead of looping? */ + /* Is AUX CH command reply received? */ + ret = readx_poll_timeout(readl, dp->reg_base + ANALOGIX_DP_INT_STA, + reg, reg & RPLY_RECEIV, 10, 20 * 1000); + if (ret) { + dev_err(dp->dev, "AUX CH cmd reply timeout!\n"); + goto aux_error; + } + + /* Clear interrupt source for AUX CH command reply */ + writel(RPLY_RECEIV, dp->reg_base + ANALOGIX_DP_INT_STA); + + /* Clear interrupt source for AUX CH access error */ + reg = readl(dp->reg_base + ANALOGIX_DP_INT_STA); + if ((reg & AUX_ERR)) { + u32 aux_status = readl(dp->reg_base + ANALOGIX_DP_AUX_CH_STA) & + AUX_STATUS_MASK; + + writel(AUX_ERR, dp->reg_base + ANALOGIX_DP_INT_STA); + + if (aux_status == AUX_STATUS_TIMEOUT_ERROR) + return -ETIMEDOUT; + + dev_warn(dp->dev, "AUX CH error happened: %#x (%d)\n", + aux_status, !!(reg & AUX_ERR)); + goto aux_error; + } + + if (msg->request & DP_AUX_I2C_READ) { + for (i = 0; i < msg->size; i++) { + reg = readl(dp->reg_base + ANALOGIX_DP_BUF_DATA_0 + + 4 * i); + buffer[i] = (unsigned char)reg; + } + } + + /* Check if Rx sends defer */ + reg = readl(dp->reg_base + ANALOGIX_DP_AUX_RX_COMM); + if (reg == AUX_RX_COMM_AUX_DEFER) + msg->reply = DP_AUX_NATIVE_REPLY_DEFER; + else if (reg == AUX_RX_COMM_I2C_DEFER) + msg->reply = DP_AUX_I2C_REPLY_DEFER; + else if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_WRITE || + (msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_READ) + msg->reply = DP_AUX_I2C_REPLY_ACK; + else if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_NATIVE_WRITE || + (msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_NATIVE_READ) + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + + return msg->size; + +aux_error: + /* if aux err happen, reset aux */ + analogix_dp_init_aux(dp); + + return -EREMOTEIO; +} diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h new file mode 100644 index 000000000..127351390 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h @@ -0,0 +1,426 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Register definition file for Analogix DP core driver + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han + */ + +#ifndef _ANALOGIX_DP_REG_H +#define _ANALOGIX_DP_REG_H + +#define ANALOGIX_DP_TX_SW_RESET 0x14 +#define ANALOGIX_DP_FUNC_EN_1 0x18 +#define ANALOGIX_DP_FUNC_EN_2 0x1C +#define ANALOGIX_DP_VIDEO_CTL_1 0x20 +#define ANALOGIX_DP_VIDEO_CTL_2 0x24 +#define ANALOGIX_DP_VIDEO_CTL_3 0x28 + +#define ANALOGIX_DP_VIDEO_CTL_8 0x3C +#define ANALOGIX_DP_VIDEO_CTL_10 0x44 + +#define ANALOGIX_DP_SPDIF_AUDIO_CTL_0 0xD8 + +#define ANALOGIX_DP_PLL_REG_1 0xfc +#define ANALOGIX_DP_PLL_REG_2 0x9e4 +#define ANALOGIX_DP_PLL_REG_3 0x9e8 +#define ANALOGIX_DP_PLL_REG_4 0x9ec +#define ANALOGIX_DP_PLL_REG_5 0xa00 + +#define ANALOGIX_DP_PD 0x12c + +#define ANALOGIX_DP_IF_TYPE 0x244 +#define ANALOGIX_DP_IF_PKT_DB1 0x254 +#define ANALOGIX_DP_IF_PKT_DB2 0x258 +#define ANALOGIX_DP_SPD_HB0 0x2F8 +#define ANALOGIX_DP_SPD_HB1 0x2FC +#define ANALOGIX_DP_SPD_HB2 0x300 +#define ANALOGIX_DP_SPD_HB3 0x304 +#define ANALOGIX_DP_SPD_PB0 0x308 +#define ANALOGIX_DP_SPD_PB1 0x30C +#define ANALOGIX_DP_SPD_PB2 0x310 +#define ANALOGIX_DP_SPD_PB3 0x314 +#define ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL 0x318 +#define ANALOGIX_DP_VSC_SHADOW_DB0 0x31C +#define ANALOGIX_DP_VSC_SHADOW_DB1 0x320 + +#define ANALOGIX_DP_LANE_MAP 0x35C + +#define ANALOGIX_DP_ANALOG_CTL_1 0x370 +#define ANALOGIX_DP_ANALOG_CTL_2 0x374 +#define ANALOGIX_DP_ANALOG_CTL_3 0x378 +#define ANALOGIX_DP_PLL_FILTER_CTL_1 0x37C +#define ANALOGIX_DP_TX_AMP_TUNING_CTL 0x380 + +#define ANALOGIX_DP_AUX_HW_RETRY_CTL 0x390 + +#define ANALOGIX_DP_COMMON_INT_STA_1 0x3C4 +#define ANALOGIX_DP_COMMON_INT_STA_2 0x3C8 +#define ANALOGIX_DP_COMMON_INT_STA_3 0x3CC +#define ANALOGIX_DP_COMMON_INT_STA_4 0x3D0 +#define ANALOGIX_DP_INT_STA 0x3DC +#define ANALOGIX_DP_COMMON_INT_MASK_1 0x3E0 +#define ANALOGIX_DP_COMMON_INT_MASK_2 0x3E4 +#define ANALOGIX_DP_COMMON_INT_MASK_3 0x3E8 +#define ANALOGIX_DP_COMMON_INT_MASK_4 0x3EC +#define ANALOGIX_DP_INT_STA_MASK 0x3F8 +#define ANALOGIX_DP_INT_CTL 0x3FC + +#define ANALOGIX_DP_SYS_CTL_1 0x600 +#define ANALOGIX_DP_SYS_CTL_2 0x604 +#define ANALOGIX_DP_SYS_CTL_3 0x608 +#define ANALOGIX_DP_SYS_CTL_4 0x60C + +#define ANALOGIX_DP_PKT_SEND_CTL 0x640 +#define ANALOGIX_DP_HDCP_CTL 0x648 + +#define ANALOGIX_DP_LINK_BW_SET 0x680 +#define ANALOGIX_DP_LANE_COUNT_SET 0x684 +#define ANALOGIX_DP_TRAINING_PTN_SET 0x688 +#define ANALOGIX_DP_LN0_LINK_TRAINING_CTL 0x68C +#define ANALOGIX_DP_LN1_LINK_TRAINING_CTL 0x690 +#define ANALOGIX_DP_LN2_LINK_TRAINING_CTL 0x694 +#define ANALOGIX_DP_LN3_LINK_TRAINING_CTL 0x698 + +#define ANALOGIX_DP_DEBUG_CTL 0x6C0 +#define ANALOGIX_DP_HPD_DEGLITCH_L 0x6C4 +#define ANALOGIX_DP_HPD_DEGLITCH_H 0x6C8 +#define ANALOGIX_DP_LINK_DEBUG_CTL 0x6E0 + +#define ANALOGIX_DP_M_VID_0 0x700 +#define ANALOGIX_DP_M_VID_1 0x704 +#define ANALOGIX_DP_M_VID_2 0x708 +#define ANALOGIX_DP_N_VID_0 0x70C +#define ANALOGIX_DP_N_VID_1 0x710 +#define ANALOGIX_DP_N_VID_2 0x714 + +#define ANALOGIX_DP_PLL_CTL 0x71C +#define ANALOGIX_DP_PHY_PD 0x720 +#define ANALOGIX_DP_PHY_TEST 0x724 + +#define ANALOGIX_DP_VIDEO_FIFO_THRD 0x730 +#define ANALOGIX_DP_AUDIO_MARGIN 0x73C + +#define ANALOGIX_DP_M_VID_GEN_FILTER_TH 0x764 +#define ANALOGIX_DP_M_AUD_GEN_FILTER_TH 0x778 +#define ANALOGIX_DP_AUX_CH_STA 0x780 +#define ANALOGIX_DP_AUX_CH_DEFER_CTL 0x788 +#define ANALOGIX_DP_AUX_RX_COMM 0x78C +#define ANALOGIX_DP_BUFFER_DATA_CTL 0x790 +#define ANALOGIX_DP_AUX_CH_CTL_1 0x794 +#define ANALOGIX_DP_AUX_ADDR_7_0 0x798 +#define ANALOGIX_DP_AUX_ADDR_15_8 0x79C +#define ANALOGIX_DP_AUX_ADDR_19_16 0x7A0 +#define ANALOGIX_DP_AUX_CH_CTL_2 0x7A4 + +#define ANALOGIX_DP_BUF_DATA_0 0x7C0 + +#define ANALOGIX_DP_SOC_GENERAL_CTL 0x800 + +#define ANALOGIX_DP_CRC_CON 0x890 + +/* ANALOGIX_DP_TX_SW_RESET */ +#define RESET_DP_TX (0x1 << 0) + +/* ANALOGIX_DP_FUNC_EN_1 */ +#define MASTER_VID_FUNC_EN_N (0x1 << 7) +#define RK_VID_CAP_FUNC_EN_N (0x1 << 6) +#define SLAVE_VID_FUNC_EN_N (0x1 << 5) +#define RK_VID_FIFO_FUNC_EN_N (0x1 << 5) +#define AUD_FIFO_FUNC_EN_N (0x1 << 4) +#define AUD_FUNC_EN_N (0x1 << 3) +#define HDCP_FUNC_EN_N (0x1 << 2) +#define CRC_FUNC_EN_N (0x1 << 1) +#define SW_FUNC_EN_N (0x1 << 0) + +/* ANALOGIX_DP_FUNC_EN_2 */ +#define SSC_FUNC_EN_N (0x1 << 7) +#define AUX_FUNC_EN_N (0x1 << 2) +#define SERDES_FIFO_FUNC_EN_N (0x1 << 1) +#define LS_CLK_DOMAIN_FUNC_EN_N (0x1 << 0) + +/* ANALOGIX_DP_VIDEO_CTL_1 */ +#define VIDEO_EN (0x1 << 7) +#define HDCP_VIDEO_MUTE (0x1 << 6) + +/* ANALOGIX_DP_VIDEO_CTL_1 */ +#define IN_D_RANGE_MASK (0x1 << 7) +#define IN_D_RANGE_SHIFT (7) +#define IN_D_RANGE_CEA (0x1 << 7) +#define IN_D_RANGE_VESA (0x0 << 7) +#define IN_BPC_MASK (0x7 << 4) +#define IN_BPC_SHIFT (4) +#define IN_BPC_12_BITS (0x3 << 4) +#define IN_BPC_10_BITS (0x2 << 4) +#define IN_BPC_8_BITS (0x1 << 4) +#define IN_BPC_6_BITS (0x0 << 4) +#define IN_COLOR_F_MASK (0x3 << 0) +#define IN_COLOR_F_SHIFT (0) +#define IN_COLOR_F_YCBCR444 (0x2 << 0) +#define IN_COLOR_F_YCBCR422 (0x1 << 0) +#define IN_COLOR_F_RGB (0x0 << 0) + +/* ANALOGIX_DP_VIDEO_CTL_3 */ +#define IN_YC_COEFFI_MASK (0x1 << 7) +#define IN_YC_COEFFI_SHIFT (7) +#define IN_YC_COEFFI_ITU709 (0x1 << 7) +#define IN_YC_COEFFI_ITU601 (0x0 << 7) +#define VID_CHK_UPDATE_TYPE_MASK (0x1 << 4) +#define VID_CHK_UPDATE_TYPE_SHIFT (4) +#define VID_CHK_UPDATE_TYPE_1 (0x1 << 4) +#define VID_CHK_UPDATE_TYPE_0 (0x0 << 4) +#define REUSE_SPD_EN (0x1 << 3) + +/* ANALOGIX_DP_VIDEO_CTL_8 */ +#define VID_HRES_TH(x) (((x) & 0xf) << 4) +#define VID_VRES_TH(x) (((x) & 0xf) << 0) + +/* ANALOGIX_DP_VIDEO_CTL_10 */ +#define FORMAT_SEL (0x1 << 4) +#define INTERACE_SCAN_CFG (0x1 << 2) +#define VSYNC_POLARITY_CFG (0x1 << 1) +#define HSYNC_POLARITY_CFG (0x1 << 0) + +/* ANALOGIX_DP_PLL_REG_1 */ +#define REF_CLK_24M (0x1 << 0) +#define REF_CLK_27M (0x0 << 0) +#define REF_CLK_MASK (0x1 << 0) + +/* ANALOGIX_DP_PSR_FRAME_UPDATE_CTRL */ +#define PSR_FRAME_UP_TYPE_BURST (0x1 << 0) +#define PSR_FRAME_UP_TYPE_SINGLE (0x0 << 0) +#define PSR_CRC_SEL_HARDWARE (0x1 << 1) +#define PSR_CRC_SEL_MANUALLY (0x0 << 1) + +/* ANALOGIX_DP_LANE_MAP */ +#define LANE3_MAP_LOGIC_LANE_0 (0x0 << 6) +#define LANE3_MAP_LOGIC_LANE_1 (0x1 << 6) +#define LANE3_MAP_LOGIC_LANE_2 (0x2 << 6) +#define LANE3_MAP_LOGIC_LANE_3 (0x3 << 6) +#define LANE2_MAP_LOGIC_LANE_0 (0x0 << 4) +#define LANE2_MAP_LOGIC_LANE_1 (0x1 << 4) +#define LANE2_MAP_LOGIC_LANE_2 (0x2 << 4) +#define LANE2_MAP_LOGIC_LANE_3 (0x3 << 4) +#define LANE1_MAP_LOGIC_LANE_0 (0x0 << 2) +#define LANE1_MAP_LOGIC_LANE_1 (0x1 << 2) +#define LANE1_MAP_LOGIC_LANE_2 (0x2 << 2) +#define LANE1_MAP_LOGIC_LANE_3 (0x3 << 2) +#define LANE0_MAP_LOGIC_LANE_0 (0x0 << 0) +#define LANE0_MAP_LOGIC_LANE_1 (0x1 << 0) +#define LANE0_MAP_LOGIC_LANE_2 (0x2 << 0) +#define LANE0_MAP_LOGIC_LANE_3 (0x3 << 0) + +/* ANALOGIX_DP_ANALOG_CTL_1 */ +#define TX_TERMINAL_CTRL_50_OHM (0x1 << 4) + +/* ANALOGIX_DP_ANALOG_CTL_2 */ +#define SEL_24M (0x1 << 3) +#define TX_DVDD_BIT_1_0625V (0x4 << 0) + +/* ANALOGIX_DP_ANALOG_CTL_3 */ +#define DRIVE_DVDD_BIT_1_0625V (0x4 << 5) +#define VCO_BIT_600_MICRO (0x5 << 0) + +/* ANALOGIX_DP_PLL_FILTER_CTL_1 */ +#define PD_RING_OSC (0x1 << 6) +#define AUX_TERMINAL_CTRL_50_OHM (0x2 << 4) +#define TX_CUR1_2X (0x1 << 2) +#define TX_CUR_16_MA (0x3 << 0) + +/* ANALOGIX_DP_TX_AMP_TUNING_CTL */ +#define CH3_AMP_400_MV (0x0 << 24) +#define CH2_AMP_400_MV (0x0 << 16) +#define CH1_AMP_400_MV (0x0 << 8) +#define CH0_AMP_400_MV (0x0 << 0) + +/* ANALOGIX_DP_AUX_HW_RETRY_CTL */ +#define AUX_BIT_PERIOD_EXPECTED_DELAY(x) (((x) & 0x7) << 8) +#define AUX_HW_RETRY_INTERVAL_MASK (0x3 << 3) +#define AUX_HW_RETRY_INTERVAL_600_MICROSECONDS (0x0 << 3) +#define AUX_HW_RETRY_INTERVAL_800_MICROSECONDS (0x1 << 3) +#define AUX_HW_RETRY_INTERVAL_1000_MICROSECONDS (0x2 << 3) +#define AUX_HW_RETRY_INTERVAL_1800_MICROSECONDS (0x3 << 3) +#define AUX_HW_RETRY_COUNT_SEL(x) (((x) & 0x7) << 0) + +/* ANALOGIX_DP_COMMON_INT_STA_1 */ +#define VSYNC_DET (0x1 << 7) +#define PLL_LOCK_CHG (0x1 << 6) +#define SPDIF_ERR (0x1 << 5) +#define SPDIF_UNSTBL (0x1 << 4) +#define VID_FORMAT_CHG (0x1 << 3) +#define AUD_CLK_CHG (0x1 << 2) +#define VID_CLK_CHG (0x1 << 1) +#define SW_INT (0x1 << 0) + +/* ANALOGIX_DP_COMMON_INT_STA_2 */ +#define ENC_EN_CHG (0x1 << 6) +#define HW_BKSV_RDY (0x1 << 3) +#define HW_SHA_DONE (0x1 << 2) +#define HW_AUTH_STATE_CHG (0x1 << 1) +#define HW_AUTH_DONE (0x1 << 0) + +/* ANALOGIX_DP_COMMON_INT_STA_3 */ +#define AFIFO_UNDER (0x1 << 7) +#define AFIFO_OVER (0x1 << 6) +#define R0_CHK_FLAG (0x1 << 5) + +/* ANALOGIX_DP_COMMON_INT_STA_4 */ +#define PSR_ACTIVE (0x1 << 7) +#define PSR_INACTIVE (0x1 << 6) +#define SPDIF_BI_PHASE_ERR (0x1 << 5) +#define HOTPLUG_CHG (0x1 << 2) +#define HPD_LOST (0x1 << 1) +#define PLUG (0x1 << 0) + +/* ANALOGIX_DP_INT_STA */ +#define INT_HPD (0x1 << 6) +#define HW_TRAINING_FINISH (0x1 << 5) +#define RPLY_RECEIV (0x1 << 1) +#define AUX_ERR (0x1 << 0) + +/* ANALOGIX_DP_INT_CTL */ +#define SOFT_INT_CTRL (0x1 << 2) +#define INT_POL1 (0x1 << 1) +#define INT_POL0 (0x1 << 0) + +/* ANALOGIX_DP_SYS_CTL_1 */ +#define DET_STA (0x1 << 2) +#define FORCE_DET (0x1 << 1) +#define DET_CTRL (0x1 << 0) + +/* ANALOGIX_DP_SYS_CTL_2 */ +#define CHA_CRI(x) (((x) & 0xf) << 4) +#define CHA_STA (0x1 << 2) +#define FORCE_CHA (0x1 << 1) +#define CHA_CTRL (0x1 << 0) + +/* ANALOGIX_DP_SYS_CTL_3 */ +#define HPD_STATUS (0x1 << 6) +#define F_HPD (0x1 << 5) +#define HPD_CTRL (0x1 << 4) +#define HDCP_RDY (0x1 << 3) +#define STRM_VALID (0x1 << 2) +#define F_VALID (0x1 << 1) +#define VALID_CTRL (0x1 << 0) + +/* ANALOGIX_DP_SYS_CTL_4 */ +#define FIX_M_AUD (0x1 << 4) +#define ENHANCED (0x1 << 3) +#define FIX_M_VID (0x1 << 2) +#define M_VID_UPDATE_CTRL (0x3 << 0) + +/* ANALOGIX_DP_TRAINING_PTN_SET */ +#define SCRAMBLER_TYPE (0x1 << 9) +#define HW_LINK_TRAINING_PATTERN (0x1 << 8) +#define SCRAMBLING_DISABLE (0x1 << 5) +#define SCRAMBLING_ENABLE (0x0 << 5) +#define LINK_QUAL_PATTERN_SET_MASK (0x3 << 2) +#define LINK_QUAL_PATTERN_SET_PRBS7 (0x3 << 2) +#define LINK_QUAL_PATTERN_SET_D10_2 (0x1 << 2) +#define LINK_QUAL_PATTERN_SET_DISABLE (0x0 << 2) +#define SW_TRAINING_PATTERN_SET_MASK (0x3 << 0) +#define SW_TRAINING_PATTERN_SET_PTN2 (0x2 << 0) +#define SW_TRAINING_PATTERN_SET_PTN1 (0x1 << 0) +#define SW_TRAINING_PATTERN_SET_NORMAL (0x0 << 0) + +/* ANALOGIX_DP_LN0_LINK_TRAINING_CTL */ +#define PRE_EMPHASIS_SET_MASK (0x3 << 3) +#define PRE_EMPHASIS_SET_SHIFT (3) + +/* ANALOGIX_DP_DEBUG_CTL */ +#define PLL_LOCK (0x1 << 4) +#define F_PLL_LOCK (0x1 << 3) +#define PLL_LOCK_CTRL (0x1 << 2) +#define PN_INV (0x1 << 0) + +/* ANALOGIX_DP_PLL_CTL */ +#define DP_PLL_PD (0x1 << 7) +#define DP_PLL_RESET (0x1 << 6) +#define DP_PLL_LOOP_BIT_DEFAULT (0x1 << 4) +#define DP_PLL_REF_BIT_1_1250V (0x5 << 0) +#define DP_PLL_REF_BIT_1_2500V (0x7 << 0) + +/* ANALOGIX_DP_PHY_PD */ +#define DP_INC_BG (0x1 << 7) +#define DP_EXP_BG (0x1 << 6) +#define DP_PHY_PD (0x1 << 5) +#define RK_AUX_PD (0x1 << 5) +#define AUX_PD (0x1 << 4) +#define RK_PLL_PD (0x1 << 4) +#define CH3_PD (0x1 << 3) +#define CH2_PD (0x1 << 2) +#define CH1_PD (0x1 << 1) +#define CH0_PD (0x1 << 0) +#define DP_ALL_PD (0xff) + +/* ANALOGIX_DP_PHY_TEST */ +#define MACRO_RST (0x1 << 5) +#define CH1_TEST (0x1 << 1) +#define CH0_TEST (0x1 << 0) + +/* ANALOGIX_DP_AUX_CH_STA */ +#define AUX_BUSY (0x1 << 4) +#define AUX_STATUS_MASK (0xf << 0) +#define AUX_STATUS_OK (0x0 << 0) +#define AUX_STATUS_NACK_ERROR (0x1 << 0) +#define AUX_STATUS_TIMEOUT_ERROR (0x2 << 0) +#define AUX_STATUS_UNKNOWN_ERROR (0x3 << 0) +#define AUX_STATUS_MUCH_DEFER_ERROR (0x4 << 0) +#define AUX_STATUS_TX_SHORT_ERROR (0x5 << 0) +#define AUX_STATUS_RX_SHORT_ERROR (0x6 << 0) +#define AUX_STATUS_NACK_WITHOUT_M_ERROR (0x7 << 0) +#define AUX_STATUS_I2C_NACK_ERROR (0x8 << 0) + +/* ANALOGIX_DP_AUX_CH_DEFER_CTL */ +#define DEFER_CTRL_EN (0x1 << 7) +#define DEFER_COUNT(x) (((x) & 0x7f) << 0) + +/* ANALOGIX_DP_AUX_RX_COMM */ +#define AUX_RX_COMM_I2C_DEFER (0x2 << 2) +#define AUX_RX_COMM_AUX_DEFER (0x2 << 0) + +/* ANALOGIX_DP_BUFFER_DATA_CTL */ +#define BUF_CLR (0x1 << 7) +#define BUF_DATA_COUNT(x) (((x) & 0x1f) << 0) + +/* ANALOGIX_DP_AUX_CH_CTL_1 */ +#define AUX_LENGTH(x) (((x - 1) & 0xf) << 4) +#define AUX_TX_COMM_MASK (0xf << 0) +#define AUX_TX_COMM_DP_TRANSACTION (0x1 << 3) +#define AUX_TX_COMM_I2C_TRANSACTION (0x0 << 3) +#define AUX_TX_COMM_MOT (0x1 << 2) +#define AUX_TX_COMM_WRITE (0x0 << 0) +#define AUX_TX_COMM_READ (0x1 << 0) + +/* ANALOGIX_DP_AUX_ADDR_7_0 */ +#define AUX_ADDR_7_0(x) (((x) >> 0) & 0xff) + +/* ANALOGIX_DP_AUX_ADDR_15_8 */ +#define AUX_ADDR_15_8(x) (((x) >> 8) & 0xff) + +/* ANALOGIX_DP_AUX_ADDR_19_16 */ +#define AUX_ADDR_19_16(x) (((x) >> 16) & 0x0f) + +/* ANALOGIX_DP_AUX_CH_CTL_2 */ +#define ADDR_ONLY (0x1 << 1) +#define AUX_EN (0x1 << 0) + +/* ANALOGIX_DP_SOC_GENERAL_CTL */ +#define AUDIO_MODE_SPDIF_MODE (0x1 << 8) +#define AUDIO_MODE_MASTER_MODE (0x0 << 8) +#define MASTER_VIDEO_INTERLACE_EN (0x1 << 4) +#define VIDEO_MASTER_CLK_SEL (0x1 << 2) +#define VIDEO_MASTER_MODE_EN (0x1 << 1) +#define VIDEO_MODE_MASK (0x1 << 0) +#define VIDEO_MODE_SLAVE_MODE (0x1 << 0) +#define VIDEO_MODE_MASTER_MODE (0x0 << 0) + +/* ANALOGIX_DP_PKT_SEND_CTL */ +#define IF_UP (0x1 << 4) +#define IF_EN (0x1 << 0) + +/* ANALOGIX_DP_CRC_CON */ +#define PSR_VID_CRC_FLUSH (0x1 << 2) +#define PSR_VID_CRC_ENABLE (0x1 << 0) + +#endif /* _ANALOGIX_DP_REG_H */ diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c new file mode 100644 index 000000000..c3af88e14 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -0,0 +1,3025 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright(c) 2020, Analogix Semiconductor. All rights reserved. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include